useStatus.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  2. import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
  3. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  4. import { useEvent } from 'rc-util';
  5. import useState from "rc-util/es/hooks/useState";
  6. import useSyncState from "rc-util/es/hooks/useSyncState";
  7. import * as React from 'react';
  8. import { useEffect, useRef } from 'react';
  9. import { STATUS_APPEAR, STATUS_ENTER, STATUS_LEAVE, STATUS_NONE, STEP_ACTIVE, STEP_PREPARE, STEP_PREPARED, STEP_START } from "../interface";
  10. import useDomMotionEvents from "./useDomMotionEvents";
  11. import useIsomorphicLayoutEffect from "./useIsomorphicLayoutEffect";
  12. import useStepQueue, { DoStep, isActive, SkipStep } from "./useStepQueue";
  13. export default function useStatus(supportMotion, visible, getElement, _ref) {
  14. var _ref$motionEnter = _ref.motionEnter,
  15. motionEnter = _ref$motionEnter === void 0 ? true : _ref$motionEnter,
  16. _ref$motionAppear = _ref.motionAppear,
  17. motionAppear = _ref$motionAppear === void 0 ? true : _ref$motionAppear,
  18. _ref$motionLeave = _ref.motionLeave,
  19. motionLeave = _ref$motionLeave === void 0 ? true : _ref$motionLeave,
  20. motionDeadline = _ref.motionDeadline,
  21. motionLeaveImmediately = _ref.motionLeaveImmediately,
  22. onAppearPrepare = _ref.onAppearPrepare,
  23. onEnterPrepare = _ref.onEnterPrepare,
  24. onLeavePrepare = _ref.onLeavePrepare,
  25. onAppearStart = _ref.onAppearStart,
  26. onEnterStart = _ref.onEnterStart,
  27. onLeaveStart = _ref.onLeaveStart,
  28. onAppearActive = _ref.onAppearActive,
  29. onEnterActive = _ref.onEnterActive,
  30. onLeaveActive = _ref.onLeaveActive,
  31. onAppearEnd = _ref.onAppearEnd,
  32. onEnterEnd = _ref.onEnterEnd,
  33. onLeaveEnd = _ref.onLeaveEnd,
  34. onVisibleChanged = _ref.onVisibleChanged;
  35. // Used for outer render usage to avoid `visible: false & status: none` to render nothing
  36. var _useState = useState(),
  37. _useState2 = _slicedToArray(_useState, 2),
  38. asyncVisible = _useState2[0],
  39. setAsyncVisible = _useState2[1];
  40. var _useSyncState = useSyncState(STATUS_NONE),
  41. _useSyncState2 = _slicedToArray(_useSyncState, 2),
  42. getStatus = _useSyncState2[0],
  43. setStatus = _useSyncState2[1];
  44. var _useState3 = useState(null),
  45. _useState4 = _slicedToArray(_useState3, 2),
  46. style = _useState4[0],
  47. setStyle = _useState4[1];
  48. var currentStatus = getStatus();
  49. var mountedRef = useRef(false);
  50. var deadlineRef = useRef(null);
  51. // =========================== Dom Node ===========================
  52. function getDomElement() {
  53. return getElement();
  54. }
  55. // ========================== Motion End ==========================
  56. var activeRef = useRef(false);
  57. /**
  58. * Clean up status & style
  59. */
  60. function updateMotionEndStatus() {
  61. setStatus(STATUS_NONE);
  62. setStyle(null, true);
  63. }
  64. var onInternalMotionEnd = useEvent(function (event) {
  65. var status = getStatus();
  66. // Do nothing since not in any transition status.
  67. // This may happen when `motionDeadline` trigger.
  68. if (status === STATUS_NONE) {
  69. return;
  70. }
  71. var element = getDomElement();
  72. if (event && !event.deadline && event.target !== element) {
  73. // event exists
  74. // not initiated by deadline
  75. // transitionEnd not fired by inner elements
  76. return;
  77. }
  78. var currentActive = activeRef.current;
  79. var canEnd;
  80. if (status === STATUS_APPEAR && currentActive) {
  81. canEnd = onAppearEnd === null || onAppearEnd === void 0 ? void 0 : onAppearEnd(element, event);
  82. } else if (status === STATUS_ENTER && currentActive) {
  83. canEnd = onEnterEnd === null || onEnterEnd === void 0 ? void 0 : onEnterEnd(element, event);
  84. } else if (status === STATUS_LEAVE && currentActive) {
  85. canEnd = onLeaveEnd === null || onLeaveEnd === void 0 ? void 0 : onLeaveEnd(element, event);
  86. }
  87. // Only update status when `canEnd` and not destroyed
  88. if (currentActive && canEnd !== false) {
  89. updateMotionEndStatus();
  90. }
  91. });
  92. var _useDomMotionEvents = useDomMotionEvents(onInternalMotionEnd),
  93. _useDomMotionEvents2 = _slicedToArray(_useDomMotionEvents, 1),
  94. patchMotionEvents = _useDomMotionEvents2[0];
  95. // ============================= Step =============================
  96. var getEventHandlers = function getEventHandlers(targetStatus) {
  97. switch (targetStatus) {
  98. case STATUS_APPEAR:
  99. return _defineProperty(_defineProperty(_defineProperty({}, STEP_PREPARE, onAppearPrepare), STEP_START, onAppearStart), STEP_ACTIVE, onAppearActive);
  100. case STATUS_ENTER:
  101. return _defineProperty(_defineProperty(_defineProperty({}, STEP_PREPARE, onEnterPrepare), STEP_START, onEnterStart), STEP_ACTIVE, onEnterActive);
  102. case STATUS_LEAVE:
  103. return _defineProperty(_defineProperty(_defineProperty({}, STEP_PREPARE, onLeavePrepare), STEP_START, onLeaveStart), STEP_ACTIVE, onLeaveActive);
  104. default:
  105. return {};
  106. }
  107. };
  108. var eventHandlers = React.useMemo(function () {
  109. return getEventHandlers(currentStatus);
  110. }, [currentStatus]);
  111. var _useStepQueue = useStepQueue(currentStatus, !supportMotion, function (newStep) {
  112. // Only prepare step can be skip
  113. if (newStep === STEP_PREPARE) {
  114. var onPrepare = eventHandlers[STEP_PREPARE];
  115. if (!onPrepare) {
  116. return SkipStep;
  117. }
  118. return onPrepare(getDomElement());
  119. }
  120. // Rest step is sync update
  121. if (step in eventHandlers) {
  122. var _eventHandlers$step;
  123. setStyle(((_eventHandlers$step = eventHandlers[step]) === null || _eventHandlers$step === void 0 ? void 0 : _eventHandlers$step.call(eventHandlers, getDomElement(), null)) || null);
  124. }
  125. if (step === STEP_ACTIVE && currentStatus !== STATUS_NONE) {
  126. // Patch events when motion needed
  127. patchMotionEvents(getDomElement());
  128. if (motionDeadline > 0) {
  129. clearTimeout(deadlineRef.current);
  130. deadlineRef.current = setTimeout(function () {
  131. onInternalMotionEnd({
  132. deadline: true
  133. });
  134. }, motionDeadline);
  135. }
  136. }
  137. if (step === STEP_PREPARED) {
  138. updateMotionEndStatus();
  139. }
  140. return DoStep;
  141. }),
  142. _useStepQueue2 = _slicedToArray(_useStepQueue, 2),
  143. startStep = _useStepQueue2[0],
  144. step = _useStepQueue2[1];
  145. var active = isActive(step);
  146. activeRef.current = active;
  147. // ============================ Status ============================
  148. var visibleRef = useRef(null);
  149. // Update with new status
  150. useIsomorphicLayoutEffect(function () {
  151. // When use Suspense, the `visible` will repeat trigger,
  152. // But not real change of the `visible`, we need to skip it.
  153. // https://github.com/ant-design/ant-design/issues/44379
  154. if (mountedRef.current && visibleRef.current === visible) {
  155. return;
  156. }
  157. setAsyncVisible(visible);
  158. var isMounted = mountedRef.current;
  159. mountedRef.current = true;
  160. // if (!supportMotion) {
  161. // return;
  162. // }
  163. var nextStatus;
  164. // Appear
  165. if (!isMounted && visible && motionAppear) {
  166. nextStatus = STATUS_APPEAR;
  167. }
  168. // Enter
  169. if (isMounted && visible && motionEnter) {
  170. nextStatus = STATUS_ENTER;
  171. }
  172. // Leave
  173. if (isMounted && !visible && motionLeave || !isMounted && motionLeaveImmediately && !visible && motionLeave) {
  174. nextStatus = STATUS_LEAVE;
  175. }
  176. var nextEventHandlers = getEventHandlers(nextStatus);
  177. // Update to next status
  178. if (nextStatus && (supportMotion || nextEventHandlers[STEP_PREPARE])) {
  179. setStatus(nextStatus);
  180. startStep();
  181. } else {
  182. // Set back in case no motion but prev status has prepare step
  183. setStatus(STATUS_NONE);
  184. }
  185. visibleRef.current = visible;
  186. }, [visible]);
  187. // ============================ Effect ============================
  188. // Reset when motion changed
  189. useEffect(function () {
  190. if (
  191. // Cancel appear
  192. currentStatus === STATUS_APPEAR && !motionAppear ||
  193. // Cancel enter
  194. currentStatus === STATUS_ENTER && !motionEnter ||
  195. // Cancel leave
  196. currentStatus === STATUS_LEAVE && !motionLeave) {
  197. setStatus(STATUS_NONE);
  198. }
  199. }, [motionAppear, motionEnter, motionLeave]);
  200. useEffect(function () {
  201. return function () {
  202. mountedRef.current = false;
  203. clearTimeout(deadlineRef.current);
  204. };
  205. }, []);
  206. // Trigger `onVisibleChanged`
  207. var firstMountChangeRef = React.useRef(false);
  208. useEffect(function () {
  209. // [visible & motion not end] => [!visible & motion end] still need trigger onVisibleChanged
  210. if (asyncVisible) {
  211. firstMountChangeRef.current = true;
  212. }
  213. if (asyncVisible !== undefined && currentStatus === STATUS_NONE) {
  214. // Skip first render is invisible since it's nothing changed
  215. if (firstMountChangeRef.current || asyncVisible) {
  216. onVisibleChanged === null || onVisibleChanged === void 0 || onVisibleChanged(asyncVisible);
  217. }
  218. firstMountChangeRef.current = true;
  219. }
  220. }, [asyncVisible, currentStatus]);
  221. // ============================ Styles ============================
  222. var mergedStyle = style;
  223. if (eventHandlers[STEP_PREPARE] && step === STEP_START) {
  224. mergedStyle = _objectSpread({
  225. transition: 'none'
  226. }, mergedStyle);
  227. }
  228. return [currentStatus, step, mergedStyle, asyncVisible !== null && asyncVisible !== void 0 ? asyncVisible : visible];
  229. }