@ringcentral/spring-base 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/chunks/twMerge.js +0 -133
- package/config.js +0 -24
- package/contexts.js +0 -1
- package/hooks.js +61 -642
- package/index.js +3 -2
- package/package.json +1 -1
- package/src/config.d.ts +1 -26
- package/src/contexts/ResponsiveContext/breakpointList.d.ts +0 -1
- package/src/contexts/ResponsiveContext/types.d.ts +0 -11
- package/src/hooks/index.d.ts +2 -0
- package/src/hooks/useA11yKeyEvent/useA11yKeyEvent.d.ts +0 -17
- package/src/hooks/useActive/useActive.d.ts +4 -10
- package/src/hooks/useAnnouncer/useAnnouncer.d.ts +0 -10
- package/src/hooks/useAriaHideOutside/useAriaHideOutside.d.ts +0 -4
- package/src/hooks/useAudio/useAudio.d.ts +0 -20
- package/src/hooks/useBodyScrollLock/useBodyScrollLock.d.ts +0 -19
- package/src/hooks/useButton/index.d.ts +1 -0
- package/src/hooks/useButton/useButton.d.ts +2 -0
- package/src/hooks/useChange/useChange.d.ts +0 -10
- package/src/hooks/useClickAway/useClickAway.d.ts +0 -10
- package/src/hooks/useDebounce/useDebounce.d.ts +0 -6
- package/src/hooks/useEventListener/useEventListener.d.ts +0 -20
- package/src/hooks/useEver/useEver.d.ts +0 -12
- package/src/hooks/useFocusVisible/useFocusVisible.d.ts +0 -6
- package/src/hooks/useFontLoaded/useFontLoaded.d.ts +0 -12
- package/src/hooks/useForceUpdate/useForceUpdate.d.ts +0 -4
- package/src/hooks/useForkRef/useForkRef.d.ts +0 -17
- package/src/hooks/useGlobalListener/createGlobalListener.d.ts +0 -37
- package/src/hooks/useGlobalListener/useGlobalListener.d.ts +0 -5
- package/src/hooks/useHiddenTabindex/useHiddenTabindex.d.ts +0 -4
- package/src/hooks/useHover/useHover.d.ts +0 -3
- package/src/hooks/useId/useId.d.ts +0 -14
- package/src/hooks/useImgLoaded/useImgLoaded.d.ts +0 -14
- package/src/hooks/useInterval/useInterval.d.ts +0 -37
- package/src/hooks/useKeyDownOnce/useKeyDownOnce.d.ts +0 -6
- package/src/hooks/useKeyboardMoveFocus/moveIndexInTwoDimension.d.ts +0 -11
- package/src/hooks/useKeyboardMoveFocus/useKeyboardMoveFocus.d.ts +0 -33
- package/src/hooks/useKeyboardMoveFocus/useOnlyOneFocusable.d.ts +0 -6
- package/src/hooks/useLongPress/useLongPress.d.ts +0 -16
- package/src/hooks/useMountState/useMountState.d.ts +0 -14
- package/src/hooks/useOnLayoutReRender/useOnLayoutReRender.d.ts +0 -3
- package/src/hooks/useOnReRender/useOnReRender.d.ts +0 -3
- package/src/hooks/useOverflow/useOverflow.d.ts +0 -3
- package/src/hooks/usePrevious/usePrevious.d.ts +0 -6
- package/src/hooks/useRefState/useRefState.d.ts +0 -19
- package/src/hooks/useResizeObserver/useResizeObserver.d.ts +0 -15
- package/src/hooks/useResponsiveMatch/useResponsiveMatch.d.ts +0 -4
- package/src/hooks/useResultRef/useResultRef.d.ts +0 -8
- package/src/hooks/useRetry/useRetry.d.ts +2 -25
- package/src/hooks/useSleep/useSleep.d.ts +0 -17
- package/src/hooks/useTabOut/useTabOut.d.ts +0 -3
- package/src/hooks/useTestRunnerDetection.d.ts +0 -6
- package/src/hooks/useThrottle/useThrottle.d.ts +0 -6
- package/src/hooks/useTouchMouseEvent/useTouchMouseEvent.d.ts +2 -41
- package/src/hooks/useVirtuosoExternalWindow/index.d.ts +1 -0
- package/src/hooks/useVirtuosoExternalWindow/useVirtuosoExternalWindow.d.ts +5 -0
- package/src/typings/BaseColor.d.ts +0 -1
- package/src/typings/ComponentTypes.d.ts +4 -30
- package/src/typings/OverridableTypes.d.ts +0 -29
- package/src/typings/UnitMap.d.ts +0 -15
- package/src/utils/ariaHideOutside.d.ts +0 -8
- package/src/utils/checkDefaultPrevented.d.ts +0 -1
- package/src/utils/classes/useClasses.d.ts +0 -29
- package/src/utils/combineProps.d.ts +0 -88
- package/src/utils/getRefElement.d.ts +0 -3
- package/src/utils/isWebKit154.d.ts +0 -8
- package/src/utils/omit.d.ts +0 -1
- package/src/utils/outerSize.d.ts +0 -4
- package/src/utils/pick.d.ts +0 -1
- package/src/utils/preloadImg.d.ts +0 -1
- package/src/utils/removeClassName.d.ts +0 -28
- package/src/utils/runKeyHandler.d.ts +0 -8
- package/src/utils/selectionHandler.d.ts +0 -8
- package/src/utils/twMerge.d.ts +0 -3
- package/utils.js +0 -99
package/hooks.js
CHANGED
|
@@ -2,31 +2,25 @@ import { unstable_useEventCallback, unstable_ownerDocument, unstable_Timeout, un
|
|
|
2
2
|
export { unstable_useControlled as useControlled } from '@mui/utils';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { useState, useEffect, useRef, useCallback, useLayoutEffect, useMemo } from 'react';
|
|
5
|
-
import uniqueId from 'lodash/uniqueId';
|
|
6
5
|
import { useWindowContext, useResponsiveContext, getMatchDetail } from './contexts.js';
|
|
7
|
-
import
|
|
8
|
-
import '
|
|
6
|
+
import uniqueId from 'lodash/uniqueId';
|
|
7
|
+
import { useButton as useButton$1 } from '@mui/base/useButton';
|
|
8
|
+
import { c as combineProps, a as ariaHideOutside, g as getRefElement, l as logInDev, o as ownerWindow } from './chunks/twMerge.js';
|
|
9
9
|
import './config.js';
|
|
10
10
|
import 'lodash/omit';
|
|
11
11
|
import 'lodash/pick';
|
|
12
|
+
import 'lodash/isPlainObject';
|
|
12
13
|
import debounce from 'lodash/debounce';
|
|
13
14
|
import noop from 'lodash/noop';
|
|
14
15
|
import throttle from 'lodash/throttle';
|
|
15
16
|
import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock';
|
|
16
17
|
import 'tailwind-merge';
|
|
17
18
|
|
|
18
|
-
// this file template replace material-ui/core/utils method
|
|
19
|
-
// when upgrade mui that will not need assign type anymore
|
|
20
19
|
const useEventCallback = (func) => {
|
|
21
20
|
return unstable_useEventCallback(func);
|
|
22
21
|
};
|
|
23
22
|
|
|
24
23
|
const defaultCheckKeys = ['Enter', ' '];
|
|
25
|
-
/**
|
|
26
|
-
* bind event with a11y keyboard, `space`, `enter`, only trigger when key includes in `checkKeys`
|
|
27
|
-
*
|
|
28
|
-
* that will auto preventDefault by default, if you don't want that, set `false` with `preventDefault`
|
|
29
|
-
*/
|
|
30
24
|
const useA11yKeyEvent = (fn, { onlyOnFocus, preventDefault = true, checkKeys = defaultCheckKeys, } = {}) => {
|
|
31
25
|
return useEventCallback((event) => {
|
|
32
26
|
if (!fn || (onlyOnFocus && event.target !== event.currentTarget))
|
|
@@ -39,7 +33,6 @@ const useA11yKeyEvent = (fn, { onlyOnFocus, preventDefault = true, checkKeys = d
|
|
|
39
33
|
});
|
|
40
34
|
};
|
|
41
35
|
|
|
42
|
-
// base https://github.com/mui/material-ui/blob/master/packages/mui-base/src/useButton/useButton.ts
|
|
43
36
|
const isNativeButton = (el) => {
|
|
44
37
|
const tag = el.tagName;
|
|
45
38
|
return (tag === 'BUTTON' ||
|
|
@@ -47,23 +40,29 @@ const isNativeButton = (el) => {
|
|
|
47
40
|
['button', 'submit', 'reset'].includes(el?.type)) ||
|
|
48
41
|
(tag === 'A' && el?.href));
|
|
49
42
|
};
|
|
50
|
-
|
|
51
|
-
* bind event with a11y keyboard, `space`. help not native button to implement `:active`
|
|
52
|
-
*
|
|
53
|
-
* that will auto preventDefault by default, if you don't want that, set `false` with `preventDefault`
|
|
54
|
-
*/
|
|
55
|
-
const useActive = ({ onKeyDown, onKeyUp, onClick, disabled = false, } = {}) => {
|
|
43
|
+
const useActive = ({ onKeyDown, onKeyUp, onMouseDown, onClick, disabled = false, } = {}) => {
|
|
56
44
|
const [active, setActive] = useState(false);
|
|
45
|
+
const { document: targetDocument = document } = useWindowContext();
|
|
57
46
|
useEffect(() => {
|
|
58
47
|
if (disabled)
|
|
59
48
|
setActive(false);
|
|
60
49
|
}, [disabled]);
|
|
61
50
|
if (disabled) {
|
|
62
51
|
return {
|
|
63
|
-
activeHandlers: { onKeyDown, onKeyUp, onClick },
|
|
52
|
+
activeHandlers: { onKeyDown, onKeyUp, onMouseDown, onClick },
|
|
64
53
|
active: false,
|
|
65
54
|
};
|
|
66
55
|
}
|
|
56
|
+
const handleMouseDown = (event) => {
|
|
57
|
+
onMouseDown?.(event);
|
|
58
|
+
if (event.defaultPrevented) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
setActive(true);
|
|
62
|
+
targetDocument.addEventListener('mouseup', () => {
|
|
63
|
+
setActive(false);
|
|
64
|
+
}, { once: true });
|
|
65
|
+
};
|
|
67
66
|
const handleKeyDown = (event) => {
|
|
68
67
|
onKeyDown?.(event);
|
|
69
68
|
if (event.defaultPrevented) {
|
|
@@ -77,7 +76,6 @@ const useActive = ({ onKeyDown, onKeyUp, onClick, disabled = false, } = {}) => {
|
|
|
77
76
|
if (event.target === event.currentTarget && event.key === ' ') {
|
|
78
77
|
setActive(true);
|
|
79
78
|
}
|
|
80
|
-
// Keyboard accessibility for non interactive elements
|
|
81
79
|
if (event.target === event.currentTarget &&
|
|
82
80
|
!isNativeButton(event.target) &&
|
|
83
81
|
event.key === 'Enter') {
|
|
@@ -86,13 +84,10 @@ const useActive = ({ onKeyDown, onKeyUp, onClick, disabled = false, } = {}) => {
|
|
|
86
84
|
}
|
|
87
85
|
};
|
|
88
86
|
const handleKeyUp = (event) => {
|
|
89
|
-
// calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
|
|
90
|
-
// https://codesandbox.io/s/button-keyup-preventdefault-dn7f0
|
|
91
87
|
if (event.target === event.currentTarget) {
|
|
92
88
|
setActive(false);
|
|
93
89
|
}
|
|
94
90
|
onKeyUp?.(event);
|
|
95
|
-
// Keyboard accessibility for non interactive elements
|
|
96
91
|
if (event.target === event.currentTarget &&
|
|
97
92
|
!isNativeButton(event.target) &&
|
|
98
93
|
event.key === ' ' &&
|
|
@@ -101,19 +96,16 @@ const useActive = ({ onKeyDown, onKeyUp, onClick, disabled = false, } = {}) => {
|
|
|
101
96
|
}
|
|
102
97
|
};
|
|
103
98
|
return {
|
|
104
|
-
activeHandlers: {
|
|
99
|
+
activeHandlers: {
|
|
100
|
+
onKeyDown: handleKeyDown,
|
|
101
|
+
onKeyUp: handleKeyUp,
|
|
102
|
+
onMouseDown: handleMouseDown,
|
|
103
|
+
onClick,
|
|
104
|
+
},
|
|
105
105
|
active,
|
|
106
106
|
};
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
-
/**
|
|
110
|
-
* provide a ref for only calculate `cb` once
|
|
111
|
-
* prevent `useRef(getValue())`, that `getValue` calculate every re-render
|
|
112
|
-
*
|
|
113
|
-
* @param cb that get method of get this ref init value, only calculate once
|
|
114
|
-
* @returns same as `useRef` result
|
|
115
|
-
*
|
|
116
|
-
*/
|
|
117
109
|
function useResultRef(cb) {
|
|
118
110
|
const renderRef = useRef(0);
|
|
119
111
|
const ref = useRef();
|
|
@@ -125,20 +117,6 @@ function useResultRef(cb) {
|
|
|
125
117
|
}
|
|
126
118
|
|
|
127
119
|
const DEFAULT_PREFIX = 'spring-id-';
|
|
128
|
-
/**
|
|
129
|
-
* Generates a unique ID.
|
|
130
|
-
*
|
|
131
|
-
* @param id - Default component id or prefix id.
|
|
132
|
-
* @param prefixMode Whether enable prefix the ID with.
|
|
133
|
-
* @returns unique ID
|
|
134
|
-
* @example
|
|
135
|
-
* useId() => 'spring-id-xxxx'
|
|
136
|
-
* useId('') => 'spring-id-xxxx'
|
|
137
|
-
* useId(null) => 'spring-id-xxxx'
|
|
138
|
-
* useId(undefined) => 'spring-id-xxxx'
|
|
139
|
-
* useId('nameId') => 'nameId'
|
|
140
|
-
* useId('nameId', true) => 'nameId-xxxx'
|
|
141
|
-
*/
|
|
142
120
|
function useId(id, prefixMode) {
|
|
143
121
|
const { current: resultId } = useResultRef(() => {
|
|
144
122
|
if (id) {
|
|
@@ -161,16 +139,6 @@ const visuallyHiddenStyles = `
|
|
|
161
139
|
white-space: nowrap;
|
|
162
140
|
width: 1px;
|
|
163
141
|
`;
|
|
164
|
-
/**
|
|
165
|
-
* read announce when need read message.
|
|
166
|
-
*
|
|
167
|
-
* @example
|
|
168
|
-
* ```ts
|
|
169
|
-
* const announce = useAnnouncer('simpleId');
|
|
170
|
-
*
|
|
171
|
-
* announce(`You click "Announce" button`);
|
|
172
|
-
* ```
|
|
173
|
-
*/
|
|
174
142
|
const useAnnouncer = (id) => {
|
|
175
143
|
const _id = useId(id ? `rc-announcer-${id}` : 'rc-announcer', !id);
|
|
176
144
|
const ref = useRef(null);
|
|
@@ -195,7 +163,6 @@ const useAnnouncer = (id) => {
|
|
|
195
163
|
});
|
|
196
164
|
};
|
|
197
165
|
}, [_id]);
|
|
198
|
-
/** announce method, call with message that want let reader speak */
|
|
199
166
|
return useCallback((message) => {
|
|
200
167
|
const el = ref.current;
|
|
201
168
|
if (el && message) {
|
|
@@ -205,6 +172,23 @@ const useAnnouncer = (id) => {
|
|
|
205
172
|
}, []);
|
|
206
173
|
};
|
|
207
174
|
|
|
175
|
+
function useButton(parameters) {
|
|
176
|
+
const muiButtonResult = useButton$1(parameters);
|
|
177
|
+
const { active: customActive, activeHandlers } = useActive({
|
|
178
|
+
disabled: parameters.disabled,
|
|
179
|
+
});
|
|
180
|
+
const originalGetRootProps = muiButtonResult.getRootProps;
|
|
181
|
+
const getRootProps = (externalProps) => {
|
|
182
|
+
const mergedProps = combineProps(externalProps, activeHandlers);
|
|
183
|
+
return originalGetRootProps(mergedProps);
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
...muiButtonResult,
|
|
187
|
+
active: customActive,
|
|
188
|
+
getRootProps,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
208
192
|
const useAriaHideOutside = (props = {}) => {
|
|
209
193
|
const { enable = true } = props;
|
|
210
194
|
const { externalWindow, document } = useWindowContext();
|
|
@@ -217,34 +201,10 @@ const useAriaHideOutside = (props = {}) => {
|
|
|
217
201
|
}, [enable, externalWindow, document?.body]);
|
|
218
202
|
return {
|
|
219
203
|
ariaHideOutsideRef: ariaHideOutsideRef,
|
|
220
|
-
/**
|
|
221
|
-
* Whether the `aria-hidden` attribute is being applied to outside elements.
|
|
222
|
-
* When true, elements outside the ref are hidden from screen readers.
|
|
223
|
-
*/
|
|
224
204
|
ariaHideEnabled: enable,
|
|
225
205
|
};
|
|
226
206
|
};
|
|
227
207
|
|
|
228
|
-
/**
|
|
229
|
-
* get Browser `Audio` instance, and auto destroy when component be destroyed.
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* @param init a callback when you init that audio
|
|
233
|
-
*
|
|
234
|
-
* @example
|
|
235
|
-
*
|
|
236
|
-
* ```ts
|
|
237
|
-
* const audio = useAudio((instance) => {
|
|
238
|
-
* instance.src = 'https://your.audio'
|
|
239
|
-
* });
|
|
240
|
-
*
|
|
241
|
-
* const click = () => audio.play();
|
|
242
|
-
*
|
|
243
|
-
* click();
|
|
244
|
-
* ```
|
|
245
|
-
* @see stories {@link https://develop-rcui-int.fiji.gliprc.com/?path=/story/%F0%9F%94%A7-foundation-hooks-useaudio--simple-announcer 🔧-foundation-hooks-useaudio--simple-announcer}
|
|
246
|
-
* @see {@link https://chromium-review.googlesource.com/c/chromium/src/+/2816118}
|
|
247
|
-
*/
|
|
248
208
|
const useAudio = (init) => {
|
|
249
209
|
const audioRef = useResultRef(() => {
|
|
250
210
|
const audio = new Audio();
|
|
@@ -254,20 +214,12 @@ const useAudio = (init) => {
|
|
|
254
214
|
useEffect(() => {
|
|
255
215
|
return () => {
|
|
256
216
|
audioRef.current.srcObject = null;
|
|
257
|
-
// just for remove ref, confirm browser GC work correctly
|
|
258
217
|
audioRef.current = null;
|
|
259
218
|
};
|
|
260
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
261
219
|
}, []);
|
|
262
220
|
return audioRef.current;
|
|
263
221
|
};
|
|
264
222
|
|
|
265
|
-
/**
|
|
266
|
-
* get previous render value
|
|
267
|
-
* @param cb that get method of target value
|
|
268
|
-
* @param isDefault if set `true`, the first time will get value from `cb`
|
|
269
|
-
* @returns previous value get from `cb`
|
|
270
|
-
*/
|
|
271
223
|
function usePrevious(cb, isDefault) {
|
|
272
224
|
const ref = useRef(isDefault ? cb() : undefined);
|
|
273
225
|
useEffect(() => {
|
|
@@ -276,11 +228,6 @@ function usePrevious(cb, isDefault) {
|
|
|
276
228
|
return ref.current;
|
|
277
229
|
}
|
|
278
230
|
|
|
279
|
-
/**
|
|
280
|
-
* get value when prev listen value change
|
|
281
|
-
* @param cb that method you want calculate to get value
|
|
282
|
-
* @param listenCb prev compare listener value
|
|
283
|
-
*/
|
|
284
231
|
const useChange = (cb, listenCb) => {
|
|
285
232
|
const countRef = useRef(0);
|
|
286
233
|
const valueRef = useRef();
|
|
@@ -292,11 +239,6 @@ const useChange = (cb, listenCb) => {
|
|
|
292
239
|
}
|
|
293
240
|
return valueRef.current;
|
|
294
241
|
};
|
|
295
|
-
/**
|
|
296
|
-
* get value when prev deps one of array value change
|
|
297
|
-
* @param cb that method you want calculate to get value
|
|
298
|
-
* @param deps prev compare value array
|
|
299
|
-
*/
|
|
300
242
|
const useDepsChange = (cb, deps) => {
|
|
301
243
|
const countRef = useRef(0);
|
|
302
244
|
const valueRef = useRef();
|
|
@@ -310,24 +252,16 @@ const useDepsChange = (cb, deps) => {
|
|
|
310
252
|
return valueRef.current;
|
|
311
253
|
};
|
|
312
254
|
|
|
313
|
-
// base https://github.com/mui/material-ui/blob/master/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx
|
|
314
255
|
function clickedRootScrollbar(event, doc) {
|
|
315
256
|
return (doc.documentElement.clientWidth < event.clientX ||
|
|
316
257
|
doc.documentElement.clientHeight < event.clientY);
|
|
317
258
|
}
|
|
318
|
-
/**
|
|
319
|
-
* @deprecated Use `ClickAwayListener` from `@mui/base` instead.
|
|
320
|
-
*
|
|
321
|
-
* @param onClickAway Callback fired when a "click away" event is detected.
|
|
322
|
-
*/
|
|
323
259
|
const useClickAway = (onClickAway, props = {}) => {
|
|
324
260
|
const { disableReactTree = false, isEnabled = true } = props;
|
|
325
261
|
const movedRef = useRef(false);
|
|
326
262
|
const activatedRef = useRef(false);
|
|
327
263
|
const syntheticEventRef = useRef(false);
|
|
328
264
|
const [target, setTarget] = useState();
|
|
329
|
-
// This hook is usually triggered after target appears
|
|
330
|
-
// so we need to trigger re-render component in ref callback to make sure useEffect can get target correctly
|
|
331
265
|
const clickAwayRef = useEventCallback((e) => {
|
|
332
266
|
setTarget(e);
|
|
333
267
|
});
|
|
@@ -336,8 +270,6 @@ const useClickAway = (onClickAway, props = {}) => {
|
|
|
336
270
|
if (!isEnabled)
|
|
337
271
|
return;
|
|
338
272
|
if (shouldActive) {
|
|
339
|
-
// Ensure that this component is not "activated" synchronously.
|
|
340
|
-
// https://github.com/facebook/react/issues/20074
|
|
341
273
|
const timer = setTimeout(() => {
|
|
342
274
|
activatedRef.current = true;
|
|
343
275
|
}, 0);
|
|
@@ -348,50 +280,32 @@ const useClickAway = (onClickAway, props = {}) => {
|
|
|
348
280
|
}
|
|
349
281
|
return undefined;
|
|
350
282
|
}, [isEnabled, shouldActive]);
|
|
351
|
-
// The handler doesn't take event.defaultPrevented into account:
|
|
352
|
-
//
|
|
353
|
-
// event.preventDefault() is meant to stop default behaviors like
|
|
354
|
-
// clicking a checkbox to check it, hitting a button to submit a form,
|
|
355
|
-
// and hitting left arrow to move the cursor in a text input etc.
|
|
356
|
-
// Only special HTML elements have these default behaviors.
|
|
357
283
|
const handleClickAway = useEventCallback((event) => {
|
|
358
|
-
// Given developers can stop the propagation of the synthetic event,
|
|
359
|
-
// we can only be confident with a positive value.
|
|
360
284
|
const insideReactTree = syntheticEventRef.current;
|
|
361
285
|
syntheticEventRef.current = false;
|
|
362
286
|
const doc = unstable_ownerDocument(target);
|
|
363
|
-
// 1. IE11 support, which trigger the handleClickAway even after the unbind
|
|
364
|
-
// 2. The child might render null.
|
|
365
|
-
// 3. Behave like a blur listener.
|
|
366
287
|
if (!activatedRef.current ||
|
|
367
288
|
!target ||
|
|
368
289
|
('clientX' in event && clickedRootScrollbar(event, doc))) {
|
|
369
290
|
return;
|
|
370
291
|
}
|
|
371
|
-
// Do not act if user performed touchmove
|
|
372
292
|
if (movedRef.current) {
|
|
373
293
|
movedRef.current = false;
|
|
374
294
|
return;
|
|
375
295
|
}
|
|
376
296
|
let insideDOM;
|
|
377
|
-
// If not enough, can use https://github.com/DieterHolvoet/event-propagation-path/blob/master/propagationPath.js
|
|
378
297
|
if (event.composedPath) {
|
|
379
298
|
insideDOM = event.composedPath().indexOf(target) > -1;
|
|
380
299
|
}
|
|
381
300
|
else {
|
|
382
301
|
insideDOM =
|
|
383
|
-
!doc.documentElement.contains(
|
|
384
|
-
|
|
385
|
-
event.target) ||
|
|
386
|
-
target.contains(
|
|
387
|
-
// @ts-expect-error returns `false` as intended when not dispatched from a Node
|
|
388
|
-
event.target);
|
|
302
|
+
!doc.documentElement.contains(event.target) ||
|
|
303
|
+
target.contains(event.target);
|
|
389
304
|
}
|
|
390
305
|
if (!insideDOM && (disableReactTree || !insideReactTree)) {
|
|
391
306
|
onClickAway(event);
|
|
392
307
|
}
|
|
393
308
|
});
|
|
394
|
-
// Keep track of mouse/touch events that bubbled up through the portal.
|
|
395
309
|
const handleSynthetic = (e) => {
|
|
396
310
|
syntheticEventRef.current = true;
|
|
397
311
|
};
|
|
@@ -423,37 +337,13 @@ const useClickAway = (onClickAway, props = {}) => {
|
|
|
423
337
|
};
|
|
424
338
|
};
|
|
425
339
|
|
|
426
|
-
/**
|
|
427
|
-
* provide a debounce method for debounce method
|
|
428
|
-
* @param fn method for debounce
|
|
429
|
-
* @param debounceTime debounce timeout value, default is `200`ms
|
|
430
|
-
* @param options debounce setting, default is `{trailing: true, leading: flase}`, detail see lodash
|
|
431
|
-
*/
|
|
432
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
433
340
|
const useDebounce = (fn, debounceTime = 200, options) => {
|
|
434
341
|
const memoFn = useEventCallback(fn);
|
|
435
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
436
342
|
const debounceFn = useCallback(debounce(memoFn, debounceTime, options), []);
|
|
437
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
438
343
|
useEffect(() => () => debounceFn.cancel(), []);
|
|
439
344
|
return debounceFn;
|
|
440
345
|
};
|
|
441
346
|
|
|
442
|
-
/**
|
|
443
|
-
* bind event when component mount and auto remove event when destroy.
|
|
444
|
-
* can remove listener by call returned function or pass empty 'target'.
|
|
445
|
-
*
|
|
446
|
-
* @param target
|
|
447
|
-
* listener will be removed when target is changed to null,
|
|
448
|
-
* or rebinding to new element when `ref.current` change
|
|
449
|
-
* @returns a method object
|
|
450
|
-
* - listen() listen listener again, if that have listener, that will not listen again
|
|
451
|
-
* - remove() remove listener
|
|
452
|
-
* @example
|
|
453
|
-
* ```ts
|
|
454
|
-
* useEventListener(window, 'keyup', () => console.log('window key up'))
|
|
455
|
-
* ```
|
|
456
|
-
*/
|
|
457
347
|
function useEventListener(target, key, callback, options) {
|
|
458
348
|
const { startImmediately = true, ...addEventListenerOptions } = options ?? {};
|
|
459
349
|
const listener = useEventCallback(callback);
|
|
@@ -472,7 +362,6 @@ function useEventListener(target, key, callback, options) {
|
|
|
472
362
|
bindRef.current();
|
|
473
363
|
}
|
|
474
364
|
return cancelRef.current;
|
|
475
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
476
365
|
}, [target]);
|
|
477
366
|
return {
|
|
478
367
|
listen: () => bindRef.current(),
|
|
@@ -481,27 +370,7 @@ function useEventListener(target, key, callback, options) {
|
|
|
481
370
|
}
|
|
482
371
|
|
|
483
372
|
const defaultEverTargetValue = (value) => !!value;
|
|
484
|
-
|
|
485
|
-
* that value ever to be `you want value`
|
|
486
|
-
* Return true when the value ever to be `you want value` once.
|
|
487
|
-
*
|
|
488
|
-
* @example
|
|
489
|
-
* ```ts
|
|
490
|
-
* // once that isOpen is true, that result will always be `true`
|
|
491
|
-
* const isEverOpen = useEver(isOpen);
|
|
492
|
-
*
|
|
493
|
-
* const isEverToBe2 = useEver(count, (val) => val === 2);
|
|
494
|
-
* ```
|
|
495
|
-
*/
|
|
496
|
-
const useEver = (
|
|
497
|
-
/** to be checked value */
|
|
498
|
-
value,
|
|
499
|
-
/**
|
|
500
|
-
* value ever to be callback, default when value to be exited.
|
|
501
|
-
*
|
|
502
|
-
* @default (value) => !!value
|
|
503
|
-
*/
|
|
504
|
-
targetValue = defaultEverTargetValue) => {
|
|
373
|
+
const useEver = (value, targetValue = defaultEverTargetValue) => {
|
|
505
374
|
const result = useRef(false);
|
|
506
375
|
if (!result.current) {
|
|
507
376
|
result.current = targetValue(value);
|
|
@@ -509,8 +378,6 @@ targetValue = defaultEverTargetValue) => {
|
|
|
509
378
|
return result.current;
|
|
510
379
|
};
|
|
511
380
|
|
|
512
|
-
/* eslint-disable sonarjs/no-collapsible-if */
|
|
513
|
-
/* eslint-disable sonarjs/prefer-single-boolean-return */
|
|
514
381
|
let hadKeyboardEvent = true;
|
|
515
382
|
let hadFocusVisibleRecently = false;
|
|
516
383
|
const hadFocusVisibleRecentlyTimeout = new unstable_Timeout();
|
|
@@ -529,13 +396,6 @@ const inputTypesWhitelist = {
|
|
|
529
396
|
datetime: true,
|
|
530
397
|
'datetime-local': true,
|
|
531
398
|
};
|
|
532
|
-
/**
|
|
533
|
-
* Computes whether the given element should automatically trigger the
|
|
534
|
-
* `focus-visible` class being added, i.e. whether it should always match
|
|
535
|
-
* `:focus-visible` when focused.
|
|
536
|
-
* @param {Element} node
|
|
537
|
-
* @returns {boolean}
|
|
538
|
-
*/
|
|
539
399
|
function focusTriggersKeyboardModality(node) {
|
|
540
400
|
const { type, tagName } = node;
|
|
541
401
|
if (tagName === 'INPUT' &&
|
|
@@ -551,35 +411,17 @@ function focusTriggersKeyboardModality(node) {
|
|
|
551
411
|
}
|
|
552
412
|
return false;
|
|
553
413
|
}
|
|
554
|
-
/**
|
|
555
|
-
* Keep track of our keyboard modality state with `hadKeyboardEvent`.
|
|
556
|
-
* If the most recent user interaction was via the keyboard;
|
|
557
|
-
* and the key press did not include a meta, alt/option, or control key;
|
|
558
|
-
* then the modality is keyboard. Otherwise, the modality is not keyboard.
|
|
559
|
-
* @param {KeyboardEvent} event
|
|
560
|
-
*/
|
|
561
414
|
function handleKeyDown(event) {
|
|
562
415
|
if (event.metaKey || event.altKey || event.ctrlKey) {
|
|
563
416
|
return;
|
|
564
417
|
}
|
|
565
418
|
hadKeyboardEvent = true;
|
|
566
419
|
}
|
|
567
|
-
/**
|
|
568
|
-
* If at any point a user clicks with a pointing device, ensure that we change
|
|
569
|
-
* the modality away from keyboard.
|
|
570
|
-
* This avoids the situation where a user presses a key on an already focused
|
|
571
|
-
* element, and then clicks on a different element, focusing it with a
|
|
572
|
-
* pointing device, while we still think we're in keyboard modality.
|
|
573
|
-
*/
|
|
574
420
|
function handlePointerDown() {
|
|
575
421
|
hadKeyboardEvent = false;
|
|
576
422
|
}
|
|
577
423
|
function handleVisibilityChange() {
|
|
578
424
|
if (this.visibilityState === 'hidden') {
|
|
579
|
-
// If the tab becomes active again, the browser will handle calling focus
|
|
580
|
-
// on the element (Safari actually calls it twice).
|
|
581
|
-
// If this tab change caused a blur on an element with focus-visible,
|
|
582
|
-
// re-apply the class when the user switches back to the tab.
|
|
583
425
|
if (hadFocusVisibleRecently) {
|
|
584
426
|
hadKeyboardEvent = true;
|
|
585
427
|
}
|
|
@@ -605,13 +447,7 @@ function isFocusVisible(event) {
|
|
|
605
447
|
return target.matches(':focus-visible');
|
|
606
448
|
}
|
|
607
449
|
catch (error) {
|
|
608
|
-
// Browsers not implementing :focus-visible will throw a SyntaxError.
|
|
609
|
-
// We use our own heuristic for those browsers.
|
|
610
|
-
// Rethrow might be better if it's not the expected error but do we really
|
|
611
|
-
// want to crash if focus-visible malfunctioned?
|
|
612
450
|
}
|
|
613
|
-
// No need for validFocusTarget check. The user does that by attaching it to
|
|
614
|
-
// focusable events only.
|
|
615
451
|
return hadKeyboardEvent || focusTriggersKeyboardModality(target);
|
|
616
452
|
}
|
|
617
453
|
function useIsFocusVisible() {
|
|
@@ -621,20 +457,8 @@ function useIsFocusVisible() {
|
|
|
621
457
|
}
|
|
622
458
|
}, []);
|
|
623
459
|
const isFocusVisibleRef = React.useRef(false);
|
|
624
|
-
/**
|
|
625
|
-
* Should be called if a blur event is fired
|
|
626
|
-
*/
|
|
627
460
|
function handleBlurVisible() {
|
|
628
|
-
// checking against potential state variable does not suffice if we focus and blur synchronously.
|
|
629
|
-
// React wouldn't have time to trigger a re-render so `focusVisible` would be stale.
|
|
630
|
-
// Ideally we would adjust `isFocusVisible(event)` to look at `relatedTarget` for blur events.
|
|
631
|
-
// This doesn't work in IE11 due to https://github.com/facebook/react/issues/3751
|
|
632
|
-
// TODO: check again if React releases their internal changes to focus event handling (https://github.com/facebook/react/pull/19186).
|
|
633
461
|
if (isFocusVisibleRef.current) {
|
|
634
|
-
// To detect a tab/window switch, we look for a blur event followed
|
|
635
|
-
// rapidly by a visibility change.
|
|
636
|
-
// If we don't see a visibility change within 100ms, it's probably a
|
|
637
|
-
// regular focus change.
|
|
638
462
|
hadFocusVisibleRecently = true;
|
|
639
463
|
hadFocusVisibleRecentlyTimeout.start(100, () => {
|
|
640
464
|
hadFocusVisibleRecently = false;
|
|
@@ -644,9 +468,6 @@ function useIsFocusVisible() {
|
|
|
644
468
|
}
|
|
645
469
|
return false;
|
|
646
470
|
}
|
|
647
|
-
/**
|
|
648
|
-
* Should be called if a blur event is fired
|
|
649
|
-
*/
|
|
650
471
|
function handleFocusVisible(event) {
|
|
651
472
|
if (isFocusVisible(event)) {
|
|
652
473
|
isFocusVisibleRef.current = true;
|
|
@@ -662,11 +483,6 @@ function useIsFocusVisible() {
|
|
|
662
483
|
};
|
|
663
484
|
}
|
|
664
485
|
|
|
665
|
-
/**
|
|
666
|
-
* bind event with a11y keyboard, `space`, `enter`, only trigger when key includes in `checkKeys`
|
|
667
|
-
*
|
|
668
|
-
* that will auto preventDefault by default, if you don't want that, set `false` with `preventDefault`
|
|
669
|
-
*/
|
|
670
486
|
const useFocusVisible = ({ onFocusVisible, onFocus, onBlur, within = false, } = {}) => {
|
|
671
487
|
const { isFocusVisibleRef, onFocus: handleFocusVisible, onBlur: handleBlurVisible, ref: focusVisibleRef, } = useIsFocusVisible();
|
|
672
488
|
const [focusVisible, setFocusVisible] = useState(false);
|
|
@@ -675,15 +491,6 @@ const useFocusVisible = ({ onFocusVisible, onFocus, onBlur, within = false, } =
|
|
|
675
491
|
handleBlurVisible(event);
|
|
676
492
|
if (isFocusVisibleRef.current === false) {
|
|
677
493
|
setFocusVisible(false);
|
|
678
|
-
/**
|
|
679
|
-
* when focus element by press `tab`, will trigger focus event in next event loop
|
|
680
|
-
*
|
|
681
|
-
* if current target is containing related target and we don't check whether it is containing related target,
|
|
682
|
-
* `FocusVisibleWithin` will be false before `focus` trigger,
|
|
683
|
-
* then `FocusVisibleWithin` will be true after `focus` trigger
|
|
684
|
-
*
|
|
685
|
-
* And if current target is containing related target, `FocusVisibleWithin` will never be false
|
|
686
|
-
*/
|
|
687
494
|
if (within &&
|
|
688
495
|
!event.currentTarget.contains(event.relatedTarget)) {
|
|
689
496
|
setFocusVisibleWithin(false);
|
|
@@ -726,17 +533,12 @@ const useFocusVisible = ({ onFocusVisible, onFocus, onBlur, within = false, } =
|
|
|
726
533
|
onBlur: handleBlur,
|
|
727
534
|
onFocus: handleFocus,
|
|
728
535
|
},
|
|
729
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
730
536
|
focusVisibleRef: focusVisibleRef,
|
|
731
537
|
focusVisible,
|
|
732
538
|
focusVisibleWithin,
|
|
733
539
|
};
|
|
734
540
|
};
|
|
735
541
|
|
|
736
|
-
/**
|
|
737
|
-
* a method for trigger force update
|
|
738
|
-
* @returns force update method
|
|
739
|
-
*/
|
|
740
542
|
function useForceUpdate() {
|
|
741
543
|
const [, dispatch] = useState(Object.create(null));
|
|
742
544
|
return useEventCallback(() => {
|
|
@@ -744,39 +546,9 @@ function useForceUpdate() {
|
|
|
744
546
|
});
|
|
745
547
|
}
|
|
746
548
|
|
|
747
|
-
/**
|
|
748
|
-
* That method will give you ability fork same value `ref object` or `method`
|
|
749
|
-
* Example:
|
|
750
|
-
* ```ts
|
|
751
|
-
* const refA = useRef();
|
|
752
|
-
* const refB = useRef();
|
|
753
|
-
*
|
|
754
|
-
* const ref = useForkRef(refA, refB)
|
|
755
|
-
*
|
|
756
|
-
* useEffect(() => {
|
|
757
|
-
* console.log(refA); // get that current div dom
|
|
758
|
-
* console.log(refB); // also get that current div dom
|
|
759
|
-
* },[])
|
|
760
|
-
*
|
|
761
|
-
* return <div ref={ref} />
|
|
762
|
-
* ```
|
|
763
|
-
*/
|
|
764
549
|
const useForkRef = unstable_useForkRef;
|
|
765
550
|
|
|
766
551
|
const globalListenerEventMap = new Map();
|
|
767
|
-
/**
|
|
768
|
-
* create globalListener handler for share one listener event
|
|
769
|
-
*
|
|
770
|
-
* @example
|
|
771
|
-
* ```ts
|
|
772
|
-
* const globalListener = createGlobalListener('focus', () => { console.log('focus') });
|
|
773
|
-
* const globalListener2 = createGlobalListener('focus', () => { console.log('focus') });
|
|
774
|
-
* globalListener.listen();
|
|
775
|
-
* globalListener2.listen();
|
|
776
|
-
*
|
|
777
|
-
* => // that will only create an event listener on window, but every callback will get emit value when event triggered
|
|
778
|
-
* ```
|
|
779
|
-
*/
|
|
780
552
|
const createGlobalListener = (key, listener, options, { customKey = key, target: targetProp = window, } = {}) => {
|
|
781
553
|
let listening = false;
|
|
782
554
|
const getMapValue = () => globalListenerEventMap.get(customKey);
|
|
@@ -818,63 +590,18 @@ const createGlobalListener = (key, listener, options, { customKey = key, target:
|
|
|
818
590
|
};
|
|
819
591
|
return {
|
|
820
592
|
listen: () => addListener(),
|
|
821
|
-
/**
|
|
822
|
-
* remove listener
|
|
823
|
-
*/
|
|
824
593
|
remove: () => removeListener(),
|
|
825
|
-
/**
|
|
826
|
-
* current listener state
|
|
827
|
-
*/
|
|
828
594
|
get state() {
|
|
829
595
|
return {
|
|
830
|
-
/**
|
|
831
|
-
* current listener state count
|
|
832
|
-
*/
|
|
833
596
|
get count() {
|
|
834
597
|
return getMapValue()?.listeners.size || 0;
|
|
835
598
|
},
|
|
836
|
-
/**
|
|
837
|
-
* is that be listening
|
|
838
|
-
*/
|
|
839
599
|
listening,
|
|
840
600
|
};
|
|
841
601
|
},
|
|
842
602
|
};
|
|
843
603
|
};
|
|
844
604
|
|
|
845
|
-
/**
|
|
846
|
-
* bind global event, when you bind same key event,
|
|
847
|
-
* that will reuse one event listener and trigger both callback once that listener be triggered.
|
|
848
|
-
*
|
|
849
|
-
* also you can control listener with method `listen` and `remove`
|
|
850
|
-
* and get listener `state` for check listener count number and current listing state.
|
|
851
|
-
*
|
|
852
|
-
* config:
|
|
853
|
-
* - `target`: custom binding event target, default is `window`
|
|
854
|
-
* - `customKey`: custom key for determining different event group, default is same as `key`
|
|
855
|
-
* - `startImmediately`: start listener immediately, default is `true`
|
|
856
|
-
*
|
|
857
|
-
* * `key`, `options` only work when set first time, never change after first render
|
|
858
|
-
*
|
|
859
|
-
* @example
|
|
860
|
-
* ```ts
|
|
861
|
-
* useGlobalListener('keyup', () => console.log('window key up1'))
|
|
862
|
-
*
|
|
863
|
-
* useGlobalListener('keyup', () => console.log('window key up2'))
|
|
864
|
-
*
|
|
865
|
-
* useGlobalListener('touchend', () => console.log('window key up3'), {
|
|
866
|
-
* target: someWantHostElement,
|
|
867
|
-
* customKey: 'hostTouchend',
|
|
868
|
-
* })
|
|
869
|
-
*
|
|
870
|
-
* useGlobalListener('touchend', () => console.log('window key up3'), { passive: false }, {
|
|
871
|
-
* target: someWantHostElement,
|
|
872
|
-
* customKey: 'hostTouchend',
|
|
873
|
-
* })
|
|
874
|
-
*
|
|
875
|
-
* => // that will only create an event listener on `window`, but every callback will get emit value when event triggered
|
|
876
|
-
* ```
|
|
877
|
-
*/
|
|
878
605
|
function useGlobalListener(key, listener, ...args) {
|
|
879
606
|
const { options, config } = getListenerOverloadOption(args);
|
|
880
607
|
const { customKey = key, target: targetProp = window, startImmediately = true, } = config || {};
|
|
@@ -884,7 +611,6 @@ function useGlobalListener(key, listener, ...args) {
|
|
|
884
611
|
target: targetProp,
|
|
885
612
|
}));
|
|
886
613
|
if (process.env.NODE_ENV !== 'production') {
|
|
887
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
888
614
|
useEffect(() => {
|
|
889
615
|
if (customKey === key) {
|
|
890
616
|
if (Object.keys(options).length > 0) {
|
|
@@ -902,7 +628,6 @@ function useGlobalListener(key, listener, ...args) {
|
|
|
902
628
|
});
|
|
903
629
|
}
|
|
904
630
|
}
|
|
905
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
906
631
|
}, []);
|
|
907
632
|
}
|
|
908
633
|
useEffect(() => {
|
|
@@ -910,7 +635,6 @@ function useGlobalListener(key, listener, ...args) {
|
|
|
910
635
|
globalListener.listen();
|
|
911
636
|
}
|
|
912
637
|
return globalListener.remove;
|
|
913
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
914
638
|
}, []);
|
|
915
639
|
return globalListener;
|
|
916
640
|
}
|
|
@@ -936,10 +660,6 @@ function getListenerOverloadOption(args) {
|
|
|
936
660
|
return { options, config };
|
|
937
661
|
}
|
|
938
662
|
|
|
939
|
-
/**
|
|
940
|
-
* group hidden logic, when binding events element focus,
|
|
941
|
-
* that hiddenRef will auto be `tabindex={-1}` *
|
|
942
|
-
*/
|
|
943
663
|
const useHiddenTabindex = (target) => {
|
|
944
664
|
const handleFocus = useEventCallback(() => {
|
|
945
665
|
const hiddenElm = getRefElement(target);
|
|
@@ -1014,20 +734,6 @@ const useHoverDirty = ({ target, disabled = false, }) => {
|
|
|
1014
734
|
};
|
|
1015
735
|
};
|
|
1016
736
|
|
|
1017
|
-
/**
|
|
1018
|
-
* get load state of an image by passing src or srcSet
|
|
1019
|
-
* - false: not load yet
|
|
1020
|
-
* - 'loaded': successfully loaded
|
|
1021
|
-
* - 'error': fail to load
|
|
1022
|
-
*
|
|
1023
|
-
* @example
|
|
1024
|
-
* ```ts
|
|
1025
|
-
* const loaded = useImgLoaded({
|
|
1026
|
-
* src,
|
|
1027
|
-
* srcSet,
|
|
1028
|
-
* });
|
|
1029
|
-
* ```
|
|
1030
|
-
*/
|
|
1031
737
|
const useImgLoaded = ({ crossOrigin, referrerPolicy, src, srcSet, }) => {
|
|
1032
738
|
const [loaded, setLoaded] = useState(false);
|
|
1033
739
|
useEffect(() => {
|
|
@@ -1064,38 +770,7 @@ const useImgLoaded = ({ crossOrigin, referrerPolicy, src, srcSet, }) => {
|
|
|
1064
770
|
return loaded;
|
|
1065
771
|
};
|
|
1066
772
|
|
|
1067
|
-
|
|
1068
|
-
* controllable interval, provide easy way to control interval
|
|
1069
|
-
*
|
|
1070
|
-
* - cancel: cancel current interval
|
|
1071
|
-
* - pause: pause current interval
|
|
1072
|
-
* - play: play current interval
|
|
1073
|
-
* - getRunning: return current interval running state
|
|
1074
|
-
*
|
|
1075
|
-
* @example
|
|
1076
|
-
* ```ts
|
|
1077
|
-
* const { cancel, play, pause } = useInterval(() => {
|
|
1078
|
-
* console.log('interval be triggered');
|
|
1079
|
-
* }, 1000)
|
|
1080
|
-
*
|
|
1081
|
-
* // get current run times
|
|
1082
|
-
* useInterval((times) => {
|
|
1083
|
-
* console.log('current times start from 1', times);
|
|
1084
|
-
* }, 2000)
|
|
1085
|
-
*
|
|
1086
|
-
* // not start interval immediately
|
|
1087
|
-
* useInterval((times) => {
|
|
1088
|
-
* console.log('current times start from 1', times);
|
|
1089
|
-
* }, 2000, false)
|
|
1090
|
-
* ```
|
|
1091
|
-
*/
|
|
1092
|
-
const useInterval = (
|
|
1093
|
-
/** callback with times, times start from 1 */
|
|
1094
|
-
callback,
|
|
1095
|
-
/** what delay time of that interval */
|
|
1096
|
-
delay = 0,
|
|
1097
|
-
/** is that start interval immediately */
|
|
1098
|
-
startImmediately = true) => {
|
|
773
|
+
const useInterval = (callback, delay = 0, startImmediately = true) => {
|
|
1099
774
|
const intervalRef = useRef();
|
|
1100
775
|
const countRef = useRef(0);
|
|
1101
776
|
const getRunning = useEventCallback(() => !!intervalRef.current);
|
|
@@ -1128,31 +803,13 @@ startImmediately = true) => {
|
|
|
1128
803
|
return () => cancel();
|
|
1129
804
|
}, [cancel, play, startImmediately]);
|
|
1130
805
|
return {
|
|
1131
|
-
/**
|
|
1132
|
-
* cancel current interval
|
|
1133
|
-
*/
|
|
1134
806
|
cancel,
|
|
1135
|
-
/**
|
|
1136
|
-
* pause current interval
|
|
1137
|
-
*/
|
|
1138
807
|
pause,
|
|
1139
|
-
/**
|
|
1140
|
-
* play current interval
|
|
1141
|
-
*/
|
|
1142
808
|
play,
|
|
1143
|
-
/**
|
|
1144
|
-
* return current interval running state
|
|
1145
|
-
*/
|
|
1146
809
|
getRunning,
|
|
1147
810
|
};
|
|
1148
811
|
};
|
|
1149
812
|
|
|
1150
|
-
/**
|
|
1151
|
-
* Provide user trigger `onKeyDown` only one time when hold press on key.
|
|
1152
|
-
* @param includeKeys that check include keys
|
|
1153
|
-
* @param methods trigger when first keydown or keyup
|
|
1154
|
-
* @returns
|
|
1155
|
-
*/
|
|
1156
813
|
const useKeyDownOnce = (includeKeys, { onKeyDown, onKeyUp }) => {
|
|
1157
814
|
const isKeyDownMapRef = useRef({});
|
|
1158
815
|
const handleKeyDown = useEventCallback((e) => {
|
|
@@ -1182,13 +839,8 @@ const useKeyDownOnce = (includeKeys, { onKeyDown, onKeyUp }) => {
|
|
|
1182
839
|
};
|
|
1183
840
|
};
|
|
1184
841
|
|
|
1185
|
-
/**
|
|
1186
|
-
* that can move index in column array
|
|
1187
|
-
*/
|
|
1188
842
|
function moveIndexInTwoDimension(direction, options) {
|
|
1189
843
|
const { currIndex, columns, total, infinite } = options;
|
|
1190
|
-
// start index never small than 0
|
|
1191
|
-
// const index = Math.max(currIndex, 0);
|
|
1192
844
|
const lastIndex = total - 1;
|
|
1193
845
|
const isInOrder = infinite === 'order';
|
|
1194
846
|
const fillTotal = Math.ceil(total / columns) * columns;
|
|
@@ -1237,8 +889,6 @@ function moveIndexInTwoDimension(direction, options) {
|
|
|
1237
889
|
if (!infinite && resultIndex >= total) {
|
|
1238
890
|
return currIndex;
|
|
1239
891
|
}
|
|
1240
|
-
// * when that bigger then sourceTotal, mean that touch end line
|
|
1241
|
-
// * move to current full latest and move again.
|
|
1242
892
|
if (resultIndex >= total) {
|
|
1243
893
|
if (direction === 'ArrowRight') {
|
|
1244
894
|
resultIndex = fillTotal - 1;
|
|
@@ -1251,9 +901,6 @@ function moveIndexInTwoDimension(direction, options) {
|
|
|
1251
901
|
return resultIndex;
|
|
1252
902
|
}
|
|
1253
903
|
|
|
1254
|
-
/**
|
|
1255
|
-
* use keyboard move index in two dimensional
|
|
1256
|
-
*/
|
|
1257
904
|
const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocusedIndexChange, getOptionDisabled, getOptionSearchText, columns = 1, endSearchDebounceTime = 500, }) => {
|
|
1258
905
|
const total = options.length;
|
|
1259
906
|
const searchText = useRef('');
|
|
@@ -1280,7 +927,6 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1280
927
|
let findDirection;
|
|
1281
928
|
if (total === 0)
|
|
1282
929
|
return;
|
|
1283
|
-
// eslint-disable-next-line no-constant-condition
|
|
1284
930
|
while (true) {
|
|
1285
931
|
const toIndex = findDirection
|
|
1286
932
|
? findDirection === 'next'
|
|
@@ -1302,22 +948,16 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1302
948
|
}
|
|
1303
949
|
break;
|
|
1304
950
|
}
|
|
1305
|
-
// * mean that item is disabled, use same direction find next
|
|
1306
951
|
else {
|
|
1307
|
-
// * find toIndex is same as current, mean that already find all
|
|
1308
952
|
if (focusedIndexRef.current !== -1 &&
|
|
1309
953
|
toIndex === Math.max(focusedIndexRef.current, 0)) {
|
|
1310
954
|
break;
|
|
1311
955
|
}
|
|
1312
|
-
// * if no item is focused currently and toIndex is already the last item checked,
|
|
1313
|
-
// * it means no item in the list is focusable
|
|
1314
956
|
if (focusedIndexRef.current === -1) {
|
|
1315
|
-
// from bottom to top and the first item in the list is not focusable
|
|
1316
957
|
if ((Math.abs(currIndex - toIndex) !== 1 || currIndex > toIndex) &&
|
|
1317
958
|
toIndex === 0) {
|
|
1318
959
|
break;
|
|
1319
960
|
}
|
|
1320
|
-
// from top to bottom and the last item in the list is not focusable
|
|
1321
961
|
if (Math.abs(currIndex - toIndex) === 1 &&
|
|
1322
962
|
currIndex < toIndex &&
|
|
1323
963
|
toIndex === total - 1) {
|
|
@@ -1325,7 +965,6 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1325
965
|
}
|
|
1326
966
|
}
|
|
1327
967
|
currIndex = toIndex;
|
|
1328
|
-
// * if that is Hom or End, find use increase or decrease
|
|
1329
968
|
if (key === 'Home') {
|
|
1330
969
|
findDirection = 'next';
|
|
1331
970
|
}
|
|
@@ -1335,16 +974,13 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1335
974
|
}
|
|
1336
975
|
}
|
|
1337
976
|
};
|
|
1338
|
-
const getPrevFocusableOption = useCallback((index = focusedIndexRef.current,
|
|
1339
|
-
/** is that infinite search */
|
|
1340
|
-
isInfinite = true) => {
|
|
977
|
+
const getPrevFocusableOption = useCallback((index = focusedIndexRef.current, isInfinite = true) => {
|
|
1341
978
|
for (let i = index; i >= 0; i--) {
|
|
1342
979
|
if (!getOptionDisabled?.(options[i])) {
|
|
1343
980
|
return i;
|
|
1344
981
|
}
|
|
1345
982
|
}
|
|
1346
983
|
if (isInfinite) {
|
|
1347
|
-
// loop find
|
|
1348
984
|
for (let i = total - 1; i > index; i--) {
|
|
1349
985
|
if (!getOptionDisabled?.(options[i])) {
|
|
1350
986
|
return i;
|
|
@@ -1353,16 +989,13 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1353
989
|
}
|
|
1354
990
|
return -1;
|
|
1355
991
|
}, [focusedIndexRef, getOptionDisabled, options, total]);
|
|
1356
|
-
const getNextFocusableOption = useCallback((index = focusedIndexRef.current,
|
|
1357
|
-
/** is that infinite search */
|
|
1358
|
-
isInfinite = true) => {
|
|
992
|
+
const getNextFocusableOption = useCallback((index = focusedIndexRef.current, isInfinite = true) => {
|
|
1359
993
|
for (let i = index; i < total; i++) {
|
|
1360
994
|
if (!getOptionDisabled?.(options[i])) {
|
|
1361
995
|
return i;
|
|
1362
996
|
}
|
|
1363
997
|
}
|
|
1364
998
|
if (isInfinite) {
|
|
1365
|
-
// loop find
|
|
1366
999
|
for (let i = 0; i < index; i++) {
|
|
1367
1000
|
if (!getOptionDisabled?.(options[i])) {
|
|
1368
1001
|
return i;
|
|
@@ -1372,26 +1005,13 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1372
1005
|
return -1;
|
|
1373
1006
|
}, [focusedIndexRef, getOptionDisabled, options, total]);
|
|
1374
1007
|
return {
|
|
1375
|
-
/**
|
|
1376
|
-
* Get previous focusable option(include current index).
|
|
1377
|
-
*/
|
|
1378
1008
|
getPrevFocusableOption,
|
|
1379
|
-
/**
|
|
1380
|
-
* Get next focusable option(include current index).
|
|
1381
|
-
*/
|
|
1382
1009
|
getNextFocusableOption,
|
|
1383
|
-
/**
|
|
1384
|
-
* Use to handle keyboard event.
|
|
1385
|
-
*/
|
|
1386
1010
|
onKeyFocusedIndexHandle,
|
|
1387
1011
|
};
|
|
1388
1012
|
};
|
|
1389
1013
|
|
|
1390
1014
|
const MOVE_ATTR_KEY = 'data-move-focused';
|
|
1391
|
-
/**
|
|
1392
|
-
* when focus in container below element with binding `getItemProps`,
|
|
1393
|
-
* that will only have one element can be focusable.
|
|
1394
|
-
*/
|
|
1395
1015
|
const useOnlyOneFocusable = ({ focusedIndexRef, containerRef, }) => {
|
|
1396
1016
|
const moveFocusedId = useId(MOVE_ATTR_KEY, true);
|
|
1397
1017
|
const getIndexElm = (toIndex) => containerRef.current?.querySelector(`[${moveFocusedId}="${toIndex}"]`);
|
|
@@ -1424,28 +1044,6 @@ const useOnlyOneFocusable = ({ focusedIndexRef, containerRef, }) => {
|
|
|
1424
1044
|
};
|
|
1425
1045
|
};
|
|
1426
1046
|
|
|
1427
|
-
/**
|
|
1428
|
-
* Provide a utils for switch trigger touch event or mouse event between different device
|
|
1429
|
-
*
|
|
1430
|
-
* - Trigger `touch` event in `touch support` device
|
|
1431
|
-
* - onTouchStart
|
|
1432
|
-
* - onTouchEnd
|
|
1433
|
-
* - Trigger `mouse` event in `non touch support` device
|
|
1434
|
-
* - onMouseDown
|
|
1435
|
-
* - onMouseUp
|
|
1436
|
-
*
|
|
1437
|
-
* ### Once that is touch support device, that mouse event will never can be trigger
|
|
1438
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent
|
|
1439
|
-
*
|
|
1440
|
-
* Event order:
|
|
1441
|
-
* - touchstart
|
|
1442
|
-
* - Zero or more touchmove events, depending on movement of the finger(s)
|
|
1443
|
-
* - touchend
|
|
1444
|
-
* - mousemove
|
|
1445
|
-
* - mousedown
|
|
1446
|
-
* - mouseup
|
|
1447
|
-
* - click
|
|
1448
|
-
*/
|
|
1449
1047
|
const useTouchMouseEvent = ({ onMouseDown: onMouseDownArg, onMouseUp: onMouseUpArg, onTouchStart: onTouchStartArg, onTouchEnd: onTouchEndArg, }, { actionRef } = {}) => {
|
|
1450
1048
|
const stopMouseEventRef = useRef(false);
|
|
1451
1049
|
const onTouchStart = useEventCallback((e) => {
|
|
@@ -1491,9 +1089,6 @@ const useTouchMouseEvent = ({ onMouseDown: onMouseDownArg, onMouseUp: onMouseUpA
|
|
|
1491
1089
|
onMouseUp,
|
|
1492
1090
|
};
|
|
1493
1091
|
};
|
|
1494
|
-
/**
|
|
1495
|
-
* check is element equal or contains target element
|
|
1496
|
-
*/
|
|
1497
1092
|
const isElmEqualOrContain = (sourceTarget, containTarget) => {
|
|
1498
1093
|
const elm = getRefElement(sourceTarget);
|
|
1499
1094
|
const target = getRefElement(containTarget);
|
|
@@ -1503,28 +1098,18 @@ const isElmEqualOrContain = (sourceTarget, containTarget) => {
|
|
|
1503
1098
|
const isTouchEvent = (ev) => {
|
|
1504
1099
|
return 'touches' in ev;
|
|
1505
1100
|
};
|
|
1506
|
-
/**
|
|
1507
|
-
* Provide longPress helper, both `click`/`tab`/`keydown` will trigger event
|
|
1508
|
-
*
|
|
1509
|
-
* - Trigger `onTap` when user action leave small than delay time.
|
|
1510
|
-
* - Trigger `onPress` when action time is long than delay time.
|
|
1511
|
-
*
|
|
1512
|
-
* @default UseLongPressOptions { delay = 300 }
|
|
1513
|
-
*/
|
|
1514
1101
|
const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTouchEnd: onTouchEndArg, onMouseDown, onKeyDown, onKeyUp, onMouseUp: onMouseUpArg, } = {}, { isPreventDefault = true, delay = 300, externalWindow = window, } = {}) => {
|
|
1515
1102
|
const isEmittedRef = useRef(false);
|
|
1516
1103
|
const isA11yDownRef = useRef(false);
|
|
1517
1104
|
const actionRef = useRef(null);
|
|
1518
1105
|
const reasonRef = useRef('click');
|
|
1519
1106
|
const emitLongPress = useDebounce((e) => {
|
|
1520
|
-
// * only when have onPress set have emit
|
|
1521
1107
|
if (onPress) {
|
|
1522
1108
|
onPress(e, reasonRef.current);
|
|
1523
1109
|
isEmittedRef.current = true;
|
|
1524
1110
|
}
|
|
1525
1111
|
}, delay);
|
|
1526
1112
|
const end = (e, isInside) => {
|
|
1527
|
-
// ! mouse up only trigger when up on element, so we host that in document
|
|
1528
1113
|
globalMouseUpListener.remove();
|
|
1529
1114
|
if (!isEmittedRef.current && isInside) {
|
|
1530
1115
|
onTap?.(e, reasonRef.current);
|
|
@@ -1534,7 +1119,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1534
1119
|
emitLongPress.cancel();
|
|
1535
1120
|
};
|
|
1536
1121
|
const start = (e) => {
|
|
1537
|
-
// ! mouse up only trigger when up on element, so we host that in document
|
|
1538
1122
|
globalMouseUpListener.listen();
|
|
1539
1123
|
const isTouch = e.type === 'touchstart';
|
|
1540
1124
|
if (isTouch) {
|
|
@@ -1544,7 +1128,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1544
1128
|
};
|
|
1545
1129
|
const { onMouseUp, onTouchEnd, onTouchStart, ...events } = useTouchMouseEvent({
|
|
1546
1130
|
onTouchStart: (e) => {
|
|
1547
|
-
// prevent long touch leave element focus
|
|
1548
1131
|
if (isPreventDefault) {
|
|
1549
1132
|
e.preventDefault();
|
|
1550
1133
|
}
|
|
@@ -1552,7 +1135,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1552
1135
|
start(e);
|
|
1553
1136
|
},
|
|
1554
1137
|
onTouchEnd: (e, isInside) => {
|
|
1555
|
-
// prevent leave element focus
|
|
1556
1138
|
if (isPreventDefault) {
|
|
1557
1139
|
e.preventDefault();
|
|
1558
1140
|
}
|
|
@@ -1567,7 +1149,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1567
1149
|
start(e);
|
|
1568
1150
|
},
|
|
1569
1151
|
onMouseUp: (e, isInside) => {
|
|
1570
|
-
// prevent leave element focus
|
|
1571
1152
|
if (isPreventDefault) {
|
|
1572
1153
|
e.preventDefault();
|
|
1573
1154
|
}
|
|
@@ -1575,11 +1156,9 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1575
1156
|
end(e, isInside);
|
|
1576
1157
|
},
|
|
1577
1158
|
}, { actionRef });
|
|
1578
|
-
// use event listener for make event `passive` work in safari
|
|
1579
1159
|
useEventListener(actionRef, 'touchstart', onTouchStart, {
|
|
1580
1160
|
passive: false,
|
|
1581
1161
|
});
|
|
1582
|
-
// use event listener for make event `passive` work in safari
|
|
1583
1162
|
useEventListener(actionRef, 'touchend', onTouchEnd, {
|
|
1584
1163
|
passive: false,
|
|
1585
1164
|
});
|
|
@@ -1589,7 +1168,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1589
1168
|
});
|
|
1590
1169
|
const handleDeleteKeyDown = useA11yKeyEvent((e) => {
|
|
1591
1170
|
reasonRef.current = 'keyboard';
|
|
1592
|
-
// * keydown when hold will trigger many time, show when current is a11y down, not trigger again
|
|
1593
1171
|
if (!isA11yDownRef.current) {
|
|
1594
1172
|
emitLongPress(e);
|
|
1595
1173
|
}
|
|
@@ -1613,20 +1191,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1613
1191
|
};
|
|
1614
1192
|
};
|
|
1615
1193
|
|
|
1616
|
-
/**
|
|
1617
|
-
* get that component mounted state `ref`
|
|
1618
|
-
*
|
|
1619
|
-
* @example
|
|
1620
|
-
*
|
|
1621
|
-
* ```ts
|
|
1622
|
-
const isMountedRef = useMountState();
|
|
1623
|
-
|
|
1624
|
-
if (isMountedRef.current) {
|
|
1625
|
-
console.log('component mounted');
|
|
1626
|
-
}
|
|
1627
|
-
*
|
|
1628
|
-
* ```
|
|
1629
|
-
*/
|
|
1630
1194
|
const useMountState = () => {
|
|
1631
1195
|
const isMountedRef = useRef(false);
|
|
1632
1196
|
useEffect(() => {
|
|
@@ -1638,9 +1202,6 @@ const useMountState = () => {
|
|
|
1638
1202
|
return isMountedRef;
|
|
1639
1203
|
};
|
|
1640
1204
|
|
|
1641
|
-
/**
|
|
1642
|
-
* only trigger when re-render (in useLayoutEffect), not trigger on component first render
|
|
1643
|
-
*/
|
|
1644
1205
|
const useOnLayoutReRender = (cb, deps) => {
|
|
1645
1206
|
const count = useRef(0);
|
|
1646
1207
|
const method = useEventCallback(cb);
|
|
@@ -1648,17 +1209,12 @@ const useOnLayoutReRender = (cb, deps) => {
|
|
|
1648
1209
|
if (count.current === 0) {
|
|
1649
1210
|
count.current = 1;
|
|
1650
1211
|
return () => {
|
|
1651
|
-
//
|
|
1652
1212
|
};
|
|
1653
1213
|
}
|
|
1654
1214
|
return method();
|
|
1655
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1656
1215
|
}, deps);
|
|
1657
1216
|
};
|
|
1658
1217
|
|
|
1659
|
-
/**
|
|
1660
|
-
* only trigger when re-render, not trigger on component first render
|
|
1661
|
-
*/
|
|
1662
1218
|
const useOnReRender = (cb, deps) => {
|
|
1663
1219
|
const count = useRef(0);
|
|
1664
1220
|
const method = useEventCallback(cb);
|
|
@@ -1666,28 +1222,13 @@ const useOnReRender = (cb, deps) => {
|
|
|
1666
1222
|
if (count.current === 0) {
|
|
1667
1223
|
count.current = 1;
|
|
1668
1224
|
return () => {
|
|
1669
|
-
//
|
|
1670
1225
|
};
|
|
1671
1226
|
}
|
|
1672
1227
|
return method();
|
|
1673
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1674
1228
|
}, deps);
|
|
1675
1229
|
};
|
|
1676
1230
|
|
|
1677
|
-
|
|
1678
|
-
* get resize event with auto disconnect when layout destroy.
|
|
1679
|
-
* bring with debounce for event not too many
|
|
1680
|
-
* @param ref that ref for element
|
|
1681
|
-
* @param callback callback when element resize
|
|
1682
|
-
* @param performance options that improve performance
|
|
1683
|
-
*/
|
|
1684
|
-
const useResizeObserver = (
|
|
1685
|
-
/** target element ref */
|
|
1686
|
-
target,
|
|
1687
|
-
/** trigger when `ResizeObserver` emit */
|
|
1688
|
-
callback,
|
|
1689
|
-
/** performance options, `mode`, `time`, `ignoreFireWhenObserve` */
|
|
1690
|
-
performance = {}) => {
|
|
1231
|
+
const useResizeObserver = (target, callback, performance = {}) => {
|
|
1691
1232
|
const { mode = 'debounce', time: performanceTime = 200, ignoreFireWhenObserve, options, } = performance;
|
|
1692
1233
|
const countRef = useRef(0);
|
|
1693
1234
|
const cb = useEventCallback((...args) => {
|
|
@@ -1725,29 +1266,14 @@ performance = {}) => {
|
|
|
1725
1266
|
return () => {
|
|
1726
1267
|
observer.disconnect();
|
|
1727
1268
|
};
|
|
1728
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1729
1269
|
}, [el]);
|
|
1730
1270
|
return observerRef.current;
|
|
1731
1271
|
};
|
|
1732
1272
|
|
|
1733
|
-
|
|
1734
|
-
* trigger when overflow state change
|
|
1735
|
-
*/
|
|
1736
|
-
const useOverflow = (
|
|
1737
|
-
/** target element ref */
|
|
1738
|
-
target,
|
|
1739
|
-
/** trigger when `ResizeObserver` emit */
|
|
1740
|
-
cb,
|
|
1741
|
-
/**
|
|
1742
|
-
* performance options, `mode`, `time`, `ignoreFireWhenObserve`
|
|
1743
|
-
*
|
|
1744
|
-
* @default { mode: 'throttle' time: 200 }
|
|
1745
|
-
*/
|
|
1746
|
-
performance = {
|
|
1273
|
+
const useOverflow = (target, cb, performance = {
|
|
1747
1274
|
mode: 'throttle',
|
|
1748
1275
|
time: 200,
|
|
1749
1276
|
}) => {
|
|
1750
|
-
// * not have default value for that can trigger change first time.
|
|
1751
1277
|
const showRef = useRef();
|
|
1752
1278
|
return useResizeObserver(target, () => {
|
|
1753
1279
|
const element = getRefElement(target);
|
|
@@ -1757,7 +1283,6 @@ performance = {
|
|
|
1757
1283
|
const show = scrollWidth > clientWidth || scrollHeight > clientHeight;
|
|
1758
1284
|
if (showRef.current === show)
|
|
1759
1285
|
return;
|
|
1760
|
-
// only emit when state change
|
|
1761
1286
|
showRef.current = show;
|
|
1762
1287
|
cb(show, {
|
|
1763
1288
|
scrollWidth,
|
|
@@ -1768,38 +1293,10 @@ performance = {
|
|
|
1768
1293
|
}, performance);
|
|
1769
1294
|
};
|
|
1770
1295
|
|
|
1771
|
-
/**
|
|
1772
|
-
* Returns a stateful like state with Ref, and a function to update it current value.
|
|
1773
|
-
* can trigger update value without re-render
|
|
1774
|
-
*
|
|
1775
|
-
* @param value init value
|
|
1776
|
-
* @param customUpdate you can custom you update method, or just set your `forceUpdate` to reuse same instance
|
|
1777
|
-
* @returns an array with [ref, update]
|
|
1778
|
-
*
|
|
1779
|
-
* @example
|
|
1780
|
-
* ```ts
|
|
1781
|
-
* const [inputRef, updateInput] = useRefState(123);
|
|
1782
|
-
*
|
|
1783
|
-
* if(needReRender){
|
|
1784
|
-
* updateInput(456); // trigger re-render
|
|
1785
|
-
* } else {
|
|
1786
|
-
* updateInput(456, false); // not trigger re-render
|
|
1787
|
-
* }
|
|
1788
|
-
* ```
|
|
1789
|
-
*/
|
|
1790
1296
|
const useRefState = (value, customUpdate) => {
|
|
1791
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
1792
1297
|
const forceUpdate = customUpdate ?? useForceUpdate();
|
|
1793
1298
|
const innerValueRef = useRef(value);
|
|
1794
|
-
|
|
1795
|
-
* update ref value, set isUpdate to `false` will not trigger re-render
|
|
1796
|
-
*/
|
|
1797
|
-
const setInnerValue = useEventCallback((toValue,
|
|
1798
|
-
/**
|
|
1799
|
-
* is re-render
|
|
1800
|
-
* @default true
|
|
1801
|
-
*/
|
|
1802
|
-
isUpdate = true) => {
|
|
1299
|
+
const setInnerValue = useEventCallback((toValue, isUpdate = true) => {
|
|
1803
1300
|
innerValueRef.current = toValue;
|
|
1804
1301
|
if (isUpdate) {
|
|
1805
1302
|
forceUpdate();
|
|
@@ -1808,10 +1305,6 @@ const useRefState = (value, customUpdate) => {
|
|
|
1808
1305
|
return [innerValueRef, setInnerValue];
|
|
1809
1306
|
};
|
|
1810
1307
|
|
|
1811
|
-
/**
|
|
1812
|
-
* get all matched breakpoints
|
|
1813
|
-
* @param defaultBreakpoint use defaultBreakpoint when Responsive init (will pass undefined)
|
|
1814
|
-
*/
|
|
1815
1308
|
const useResponsiveMatch = (defaultBreakpoint) => {
|
|
1816
1309
|
const _breakpoint = useResponsiveContext();
|
|
1817
1310
|
const breakpoint = _breakpoint === undefined ? defaultBreakpoint : _breakpoint;
|
|
@@ -1821,13 +1314,6 @@ const useResponsiveMatch = (defaultBreakpoint) => {
|
|
|
1821
1314
|
return matchDetail.matchMap;
|
|
1822
1315
|
};
|
|
1823
1316
|
|
|
1824
|
-
/**
|
|
1825
|
-
* cancelable sleep method.
|
|
1826
|
-
*
|
|
1827
|
-
* - sleep: a promise sleep
|
|
1828
|
-
* - cancel: cancel that sleep event
|
|
1829
|
-
* - getSleeping: get current sleeping state
|
|
1830
|
-
*/
|
|
1831
1317
|
const useSleep = () => {
|
|
1832
1318
|
const timeoutIdRef = useRef();
|
|
1833
1319
|
const rejectRef = useRef();
|
|
@@ -1857,36 +1343,13 @@ const useSleep = () => {
|
|
|
1857
1343
|
};
|
|
1858
1344
|
}, []);
|
|
1859
1345
|
return {
|
|
1860
|
-
/**
|
|
1861
|
-
* sleep method, that will be cancelable with `cancel`,
|
|
1862
|
-
* and when trigger many time that will always clear previous sleep
|
|
1863
|
-
*/
|
|
1864
1346
|
sleep,
|
|
1865
|
-
/**
|
|
1866
|
-
* cancel current sleep timer
|
|
1867
|
-
*/
|
|
1868
1347
|
cancel,
|
|
1869
|
-
/**
|
|
1870
|
-
* return is current is sleeping
|
|
1871
|
-
*/
|
|
1872
1348
|
getSleeping: () => sleepingRef.current,
|
|
1873
1349
|
};
|
|
1874
1350
|
};
|
|
1875
1351
|
|
|
1876
|
-
|
|
1877
|
-
/**
|
|
1878
|
-
* retry times when exec callback fail
|
|
1879
|
-
*
|
|
1880
|
-
* * `retry`: that will auto add `times` end of your call back
|
|
1881
|
-
* * `cancel`: cancel that retry event, that only cancel inner interval promise, **if you need cancel your `cb` promise, you should handle by yourself.**
|
|
1882
|
-
*/
|
|
1883
|
-
const useRetry = (
|
|
1884
|
-
/**
|
|
1885
|
-
* exec callback, when you want to end that retry, return `true` with your callback
|
|
1886
|
-
*/
|
|
1887
|
-
cb,
|
|
1888
|
-
/** retry options */
|
|
1889
|
-
options) => {
|
|
1352
|
+
const useRetry = (cb, options) => {
|
|
1890
1353
|
const { sleep, cancel: cancelSleep } = useSleep();
|
|
1891
1354
|
const isCancelRef = useRef(true);
|
|
1892
1355
|
const complete = () => {
|
|
@@ -1895,7 +1358,6 @@ options) => {
|
|
|
1895
1358
|
};
|
|
1896
1359
|
const cancel = () => {
|
|
1897
1360
|
if (!isCancelRef.current) {
|
|
1898
|
-
// * when outside cancel
|
|
1899
1361
|
options?.onCancel?.();
|
|
1900
1362
|
complete();
|
|
1901
1363
|
}
|
|
@@ -1905,7 +1367,6 @@ options) => {
|
|
|
1905
1367
|
cancel();
|
|
1906
1368
|
isCancelRef.current = false;
|
|
1907
1369
|
options?.onStart?.();
|
|
1908
|
-
// * when exec success, just return directly.
|
|
1909
1370
|
if (await cb(...args)) {
|
|
1910
1371
|
if (!isCancelRef.current) {
|
|
1911
1372
|
options?.onSuccess?.();
|
|
@@ -1937,21 +1398,11 @@ options) => {
|
|
|
1937
1398
|
complete();
|
|
1938
1399
|
};
|
|
1939
1400
|
return {
|
|
1940
|
-
/**
|
|
1941
|
-
* retry method, that will be cancelable with `cancel`,
|
|
1942
|
-
* and when trigger many time that will always clear previous retry
|
|
1943
|
-
*/
|
|
1944
1401
|
retry,
|
|
1945
|
-
/**
|
|
1946
|
-
* cancel current retry
|
|
1947
|
-
*/
|
|
1948
1402
|
cancel,
|
|
1949
1403
|
};
|
|
1950
1404
|
};
|
|
1951
1405
|
|
|
1952
|
-
/**
|
|
1953
|
-
* @param onTabOut Callback fired when a "tab out" event is detected.
|
|
1954
|
-
*/
|
|
1955
1406
|
const useTabOut = (onTabOut) => {
|
|
1956
1407
|
const lastKeyDownIsTabRef = useRef(false);
|
|
1957
1408
|
const handleKeyDown = (e) => {
|
|
@@ -1977,28 +1428,13 @@ const useTabOut = (onTabOut) => {
|
|
|
1977
1428
|
};
|
|
1978
1429
|
};
|
|
1979
1430
|
|
|
1980
|
-
/**
|
|
1981
|
-
* provide a throttle method for throttle method
|
|
1982
|
-
* @param fn method for throttle
|
|
1983
|
-
* @param throttleTime throttle timeout value, default is `200`ms
|
|
1984
|
-
* @param options throttle setting, default is `{trailing: true, leading: true}`, detail see lodash
|
|
1985
|
-
*/
|
|
1986
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1987
1431
|
const useThrottle = (fn, throttleTime = 200, options) => {
|
|
1988
1432
|
const memoFn = useEventCallback(fn);
|
|
1989
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1990
1433
|
const throttleFn = useCallback(throttle(memoFn, throttleTime, options), []);
|
|
1991
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1992
1434
|
useEffect(() => () => throttleFn.cancel(), []);
|
|
1993
1435
|
return throttleFn;
|
|
1994
1436
|
};
|
|
1995
1437
|
|
|
1996
|
-
/**
|
|
1997
|
-
* Internal hook to detect if the component is running in a test runner environment
|
|
1998
|
-
* Used by Storybook stories to disable network requests and animations during testing
|
|
1999
|
-
* @returns {boolean} true if running in test runner (headless browser), false otherwise
|
|
2000
|
-
* @internal
|
|
2001
|
-
*/
|
|
2002
1438
|
const useTestRunnerDetection = () => {
|
|
2003
1439
|
return useMemo(() => {
|
|
2004
1440
|
return (typeof window !== 'undefined' &&
|
|
@@ -2009,10 +1445,7 @@ const useTestRunnerDetection = () => {
|
|
|
2009
1445
|
function useTimeout(callback, delay) {
|
|
2010
1446
|
const savedCallback = useRef(callback);
|
|
2011
1447
|
savedCallback.current = callback;
|
|
2012
|
-
// Set up the timeout.
|
|
2013
1448
|
useEffect(() => {
|
|
2014
|
-
// Don't schedule if no delay is specified.
|
|
2015
|
-
// Note: 0 is a valid value for delay.
|
|
2016
1449
|
if (!delay && delay !== 0) {
|
|
2017
1450
|
return;
|
|
2018
1451
|
}
|
|
@@ -2021,28 +1454,12 @@ function useTimeout(callback, delay) {
|
|
|
2021
1454
|
}, [delay]);
|
|
2022
1455
|
}
|
|
2023
1456
|
|
|
2024
|
-
/**
|
|
2025
|
-
* Check if Font Loading API is available in the current environment
|
|
2026
|
-
* @returns boolean indicating if document.fonts.ready is available
|
|
2027
|
-
*/
|
|
2028
1457
|
const isFontLoadingAPIAvailable = () => {
|
|
2029
1458
|
return (typeof document !== 'undefined' &&
|
|
2030
1459
|
document.fonts &&
|
|
2031
1460
|
typeof document.fonts.ready === 'object' &&
|
|
2032
1461
|
typeof document.fonts.ready.then === 'function');
|
|
2033
1462
|
};
|
|
2034
|
-
/**
|
|
2035
|
-
* Hook to detect when fonts are loaded
|
|
2036
|
-
*
|
|
2037
|
-
* This hook provides graceful degradation:
|
|
2038
|
-
* - When Font Loading API is not available (older browsers, SSR, test environments),
|
|
2039
|
-
* it assumes fonts are loaded to prevent blocking UI
|
|
2040
|
-
* - When disabled, it immediately returns true without listening to font events
|
|
2041
|
-
* - When enabled and API is available, it listens for font loading completion
|
|
2042
|
-
*
|
|
2043
|
-
* @param enabled - Whether to listen for font loading events (default: true)
|
|
2044
|
-
* @returns boolean indicating whether fonts are loaded
|
|
2045
|
-
*/
|
|
2046
1463
|
const useFontLoaded = (enabled = true) => {
|
|
2047
1464
|
const [loaded, setLoaded] = useState(() => !enabled || !isFontLoadingAPIAvailable());
|
|
2048
1465
|
useEffect(() => {
|
|
@@ -2062,14 +1479,6 @@ const useFontLoaded = (enabled = true) => {
|
|
|
2062
1479
|
return loaded;
|
|
2063
1480
|
};
|
|
2064
1481
|
|
|
2065
|
-
/**
|
|
2066
|
-
* Hook to lock body scroll while allowing scroll on target element
|
|
2067
|
-
* Uses body-scroll-lock library for cross-platform compatibility
|
|
2068
|
-
*
|
|
2069
|
-
* @param isLocked - Whether scroll should be locked
|
|
2070
|
-
* @param targetElement - Element that should remain scrollable (optional)
|
|
2071
|
-
* @param options - Configuration options
|
|
2072
|
-
*/
|
|
2073
1482
|
const useBodyScrollLock = (isLocked, targetElement, options = {}) => {
|
|
2074
1483
|
const { disabled = false, reserveScrollBarGap = true, allowTouchMove, } = options;
|
|
2075
1484
|
const { document } = useWindowContext();
|
|
@@ -2098,4 +1507,14 @@ const useBodyScrollLock = (isLocked, targetElement, options = {}) => {
|
|
|
2098
1507
|
]);
|
|
2099
1508
|
};
|
|
2100
1509
|
|
|
2101
|
-
|
|
1510
|
+
const useVirtuosoExternalWindow = () => {
|
|
1511
|
+
const { externalWindow } = useWindowContext();
|
|
1512
|
+
if (externalWindow && externalWindow !== globalThis.window) {
|
|
1513
|
+
return {
|
|
1514
|
+
externalWindow,
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
return undefined;
|
|
1518
|
+
};
|
|
1519
|
+
|
|
1520
|
+
export { createGlobalListener, globalListenerEventMap, isElmEqualOrContain, isTouchEvent, moveIndexInTwoDimension, teardown, useA11yKeyEvent, useActive, useAnnouncer, useAriaHideOutside, useAudio, useBodyScrollLock, useButton, useChange, useClickAway, useDebounce, useDepsChange, useEventCallback, useEventListener, useEver, useFocusVisible, useFontLoaded, useForceUpdate, useForkRef, useGlobalListener, useHiddenTabindex, useHover, useHoverDirty, useId, useImgLoaded, useInterval, useIsFocusVisible, useKeyDownOnce, useKeyboardMoveFocus, useLongPress, useMountState, useOnLayoutReRender, useOnReRender, useOnlyOneFocusable, useOverflow, usePrevious, useRefState, useResizeObserver, useResponsiveMatch, useResultRef, useRetry, useSleep, useTabOut, useTestRunnerDetection, useThrottle, useTimeout, useTouchMouseEvent, useVirtuosoExternalWindow, visuallyHiddenStyles };
|