@ringcentral/spring-base 1.7.0 → 1.8.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 +10 -635
- 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/useA11yKeyEvent/useA11yKeyEvent.d.ts +0 -17
- package/src/hooks/useActive/useActive.d.ts +0 -8
- 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/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/typings/BaseColor.d.ts +0 -1
- package/src/typings/ComponentTypes.d.ts +0 -26
- 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
|
@@ -15,18 +15,11 @@ import throttle from 'lodash/throttle';
|
|
|
15
15
|
import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock';
|
|
16
16
|
import 'tailwind-merge';
|
|
17
17
|
|
|
18
|
-
// this file template replace material-ui/core/utils method
|
|
19
|
-
// when upgrade mui that will not need assign type anymore
|
|
20
18
|
const useEventCallback = (func) => {
|
|
21
19
|
return unstable_useEventCallback(func);
|
|
22
20
|
};
|
|
23
21
|
|
|
24
22
|
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
23
|
const useA11yKeyEvent = (fn, { onlyOnFocus, preventDefault = true, checkKeys = defaultCheckKeys, } = {}) => {
|
|
31
24
|
return useEventCallback((event) => {
|
|
32
25
|
if (!fn || (onlyOnFocus && event.target !== event.currentTarget))
|
|
@@ -39,7 +32,6 @@ const useA11yKeyEvent = (fn, { onlyOnFocus, preventDefault = true, checkKeys = d
|
|
|
39
32
|
});
|
|
40
33
|
};
|
|
41
34
|
|
|
42
|
-
// base https://github.com/mui/material-ui/blob/master/packages/mui-base/src/useButton/useButton.ts
|
|
43
35
|
const isNativeButton = (el) => {
|
|
44
36
|
const tag = el.tagName;
|
|
45
37
|
return (tag === 'BUTTON' ||
|
|
@@ -47,11 +39,6 @@ const isNativeButton = (el) => {
|
|
|
47
39
|
['button', 'submit', 'reset'].includes(el?.type)) ||
|
|
48
40
|
(tag === 'A' && el?.href));
|
|
49
41
|
};
|
|
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
42
|
const useActive = ({ onKeyDown, onKeyUp, onClick, disabled = false, } = {}) => {
|
|
56
43
|
const [active, setActive] = useState(false);
|
|
57
44
|
useEffect(() => {
|
|
@@ -77,7 +64,6 @@ const useActive = ({ onKeyDown, onKeyUp, onClick, disabled = false, } = {}) => {
|
|
|
77
64
|
if (event.target === event.currentTarget && event.key === ' ') {
|
|
78
65
|
setActive(true);
|
|
79
66
|
}
|
|
80
|
-
// Keyboard accessibility for non interactive elements
|
|
81
67
|
if (event.target === event.currentTarget &&
|
|
82
68
|
!isNativeButton(event.target) &&
|
|
83
69
|
event.key === 'Enter') {
|
|
@@ -86,13 +72,10 @@ const useActive = ({ onKeyDown, onKeyUp, onClick, disabled = false, } = {}) => {
|
|
|
86
72
|
}
|
|
87
73
|
};
|
|
88
74
|
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
75
|
if (event.target === event.currentTarget) {
|
|
92
76
|
setActive(false);
|
|
93
77
|
}
|
|
94
78
|
onKeyUp?.(event);
|
|
95
|
-
// Keyboard accessibility for non interactive elements
|
|
96
79
|
if (event.target === event.currentTarget &&
|
|
97
80
|
!isNativeButton(event.target) &&
|
|
98
81
|
event.key === ' ' &&
|
|
@@ -106,14 +89,6 @@ const useActive = ({ onKeyDown, onKeyUp, onClick, disabled = false, } = {}) => {
|
|
|
106
89
|
};
|
|
107
90
|
};
|
|
108
91
|
|
|
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
92
|
function useResultRef(cb) {
|
|
118
93
|
const renderRef = useRef(0);
|
|
119
94
|
const ref = useRef();
|
|
@@ -125,20 +100,6 @@ function useResultRef(cb) {
|
|
|
125
100
|
}
|
|
126
101
|
|
|
127
102
|
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
103
|
function useId(id, prefixMode) {
|
|
143
104
|
const { current: resultId } = useResultRef(() => {
|
|
144
105
|
if (id) {
|
|
@@ -161,16 +122,6 @@ const visuallyHiddenStyles = `
|
|
|
161
122
|
white-space: nowrap;
|
|
162
123
|
width: 1px;
|
|
163
124
|
`;
|
|
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
125
|
const useAnnouncer = (id) => {
|
|
175
126
|
const _id = useId(id ? `rc-announcer-${id}` : 'rc-announcer', !id);
|
|
176
127
|
const ref = useRef(null);
|
|
@@ -195,7 +146,6 @@ const useAnnouncer = (id) => {
|
|
|
195
146
|
});
|
|
196
147
|
};
|
|
197
148
|
}, [_id]);
|
|
198
|
-
/** announce method, call with message that want let reader speak */
|
|
199
149
|
return useCallback((message) => {
|
|
200
150
|
const el = ref.current;
|
|
201
151
|
if (el && message) {
|
|
@@ -217,34 +167,10 @@ const useAriaHideOutside = (props = {}) => {
|
|
|
217
167
|
}, [enable, externalWindow, document?.body]);
|
|
218
168
|
return {
|
|
219
169
|
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
170
|
ariaHideEnabled: enable,
|
|
225
171
|
};
|
|
226
172
|
};
|
|
227
173
|
|
|
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
174
|
const useAudio = (init) => {
|
|
249
175
|
const audioRef = useResultRef(() => {
|
|
250
176
|
const audio = new Audio();
|
|
@@ -254,20 +180,12 @@ const useAudio = (init) => {
|
|
|
254
180
|
useEffect(() => {
|
|
255
181
|
return () => {
|
|
256
182
|
audioRef.current.srcObject = null;
|
|
257
|
-
// just for remove ref, confirm browser GC work correctly
|
|
258
183
|
audioRef.current = null;
|
|
259
184
|
};
|
|
260
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
261
185
|
}, []);
|
|
262
186
|
return audioRef.current;
|
|
263
187
|
};
|
|
264
188
|
|
|
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
189
|
function usePrevious(cb, isDefault) {
|
|
272
190
|
const ref = useRef(isDefault ? cb() : undefined);
|
|
273
191
|
useEffect(() => {
|
|
@@ -276,11 +194,6 @@ function usePrevious(cb, isDefault) {
|
|
|
276
194
|
return ref.current;
|
|
277
195
|
}
|
|
278
196
|
|
|
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
197
|
const useChange = (cb, listenCb) => {
|
|
285
198
|
const countRef = useRef(0);
|
|
286
199
|
const valueRef = useRef();
|
|
@@ -292,11 +205,6 @@ const useChange = (cb, listenCb) => {
|
|
|
292
205
|
}
|
|
293
206
|
return valueRef.current;
|
|
294
207
|
};
|
|
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
208
|
const useDepsChange = (cb, deps) => {
|
|
301
209
|
const countRef = useRef(0);
|
|
302
210
|
const valueRef = useRef();
|
|
@@ -310,24 +218,16 @@ const useDepsChange = (cb, deps) => {
|
|
|
310
218
|
return valueRef.current;
|
|
311
219
|
};
|
|
312
220
|
|
|
313
|
-
// base https://github.com/mui/material-ui/blob/master/packages/mui-base/src/ClickAwayListener/ClickAwayListener.tsx
|
|
314
221
|
function clickedRootScrollbar(event, doc) {
|
|
315
222
|
return (doc.documentElement.clientWidth < event.clientX ||
|
|
316
223
|
doc.documentElement.clientHeight < event.clientY);
|
|
317
224
|
}
|
|
318
|
-
/**
|
|
319
|
-
* @deprecated Use `ClickAwayListener` from `@mui/base` instead.
|
|
320
|
-
*
|
|
321
|
-
* @param onClickAway Callback fired when a "click away" event is detected.
|
|
322
|
-
*/
|
|
323
225
|
const useClickAway = (onClickAway, props = {}) => {
|
|
324
226
|
const { disableReactTree = false, isEnabled = true } = props;
|
|
325
227
|
const movedRef = useRef(false);
|
|
326
228
|
const activatedRef = useRef(false);
|
|
327
229
|
const syntheticEventRef = useRef(false);
|
|
328
230
|
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
231
|
const clickAwayRef = useEventCallback((e) => {
|
|
332
232
|
setTarget(e);
|
|
333
233
|
});
|
|
@@ -336,8 +236,6 @@ const useClickAway = (onClickAway, props = {}) => {
|
|
|
336
236
|
if (!isEnabled)
|
|
337
237
|
return;
|
|
338
238
|
if (shouldActive) {
|
|
339
|
-
// Ensure that this component is not "activated" synchronously.
|
|
340
|
-
// https://github.com/facebook/react/issues/20074
|
|
341
239
|
const timer = setTimeout(() => {
|
|
342
240
|
activatedRef.current = true;
|
|
343
241
|
}, 0);
|
|
@@ -348,50 +246,32 @@ const useClickAway = (onClickAway, props = {}) => {
|
|
|
348
246
|
}
|
|
349
247
|
return undefined;
|
|
350
248
|
}, [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
249
|
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
250
|
const insideReactTree = syntheticEventRef.current;
|
|
361
251
|
syntheticEventRef.current = false;
|
|
362
252
|
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
253
|
if (!activatedRef.current ||
|
|
367
254
|
!target ||
|
|
368
255
|
('clientX' in event && clickedRootScrollbar(event, doc))) {
|
|
369
256
|
return;
|
|
370
257
|
}
|
|
371
|
-
// Do not act if user performed touchmove
|
|
372
258
|
if (movedRef.current) {
|
|
373
259
|
movedRef.current = false;
|
|
374
260
|
return;
|
|
375
261
|
}
|
|
376
262
|
let insideDOM;
|
|
377
|
-
// If not enough, can use https://github.com/DieterHolvoet/event-propagation-path/blob/master/propagationPath.js
|
|
378
263
|
if (event.composedPath) {
|
|
379
264
|
insideDOM = event.composedPath().indexOf(target) > -1;
|
|
380
265
|
}
|
|
381
266
|
else {
|
|
382
267
|
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);
|
|
268
|
+
!doc.documentElement.contains(event.target) ||
|
|
269
|
+
target.contains(event.target);
|
|
389
270
|
}
|
|
390
271
|
if (!insideDOM && (disableReactTree || !insideReactTree)) {
|
|
391
272
|
onClickAway(event);
|
|
392
273
|
}
|
|
393
274
|
});
|
|
394
|
-
// Keep track of mouse/touch events that bubbled up through the portal.
|
|
395
275
|
const handleSynthetic = (e) => {
|
|
396
276
|
syntheticEventRef.current = true;
|
|
397
277
|
};
|
|
@@ -423,37 +303,13 @@ const useClickAway = (onClickAway, props = {}) => {
|
|
|
423
303
|
};
|
|
424
304
|
};
|
|
425
305
|
|
|
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
306
|
const useDebounce = (fn, debounceTime = 200, options) => {
|
|
434
307
|
const memoFn = useEventCallback(fn);
|
|
435
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
436
308
|
const debounceFn = useCallback(debounce(memoFn, debounceTime, options), []);
|
|
437
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
438
309
|
useEffect(() => () => debounceFn.cancel(), []);
|
|
439
310
|
return debounceFn;
|
|
440
311
|
};
|
|
441
312
|
|
|
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
313
|
function useEventListener(target, key, callback, options) {
|
|
458
314
|
const { startImmediately = true, ...addEventListenerOptions } = options ?? {};
|
|
459
315
|
const listener = useEventCallback(callback);
|
|
@@ -472,7 +328,6 @@ function useEventListener(target, key, callback, options) {
|
|
|
472
328
|
bindRef.current();
|
|
473
329
|
}
|
|
474
330
|
return cancelRef.current;
|
|
475
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
476
331
|
}, [target]);
|
|
477
332
|
return {
|
|
478
333
|
listen: () => bindRef.current(),
|
|
@@ -481,27 +336,7 @@ function useEventListener(target, key, callback, options) {
|
|
|
481
336
|
}
|
|
482
337
|
|
|
483
338
|
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) => {
|
|
339
|
+
const useEver = (value, targetValue = defaultEverTargetValue) => {
|
|
505
340
|
const result = useRef(false);
|
|
506
341
|
if (!result.current) {
|
|
507
342
|
result.current = targetValue(value);
|
|
@@ -509,8 +344,6 @@ targetValue = defaultEverTargetValue) => {
|
|
|
509
344
|
return result.current;
|
|
510
345
|
};
|
|
511
346
|
|
|
512
|
-
/* eslint-disable sonarjs/no-collapsible-if */
|
|
513
|
-
/* eslint-disable sonarjs/prefer-single-boolean-return */
|
|
514
347
|
let hadKeyboardEvent = true;
|
|
515
348
|
let hadFocusVisibleRecently = false;
|
|
516
349
|
const hadFocusVisibleRecentlyTimeout = new unstable_Timeout();
|
|
@@ -529,13 +362,6 @@ const inputTypesWhitelist = {
|
|
|
529
362
|
datetime: true,
|
|
530
363
|
'datetime-local': true,
|
|
531
364
|
};
|
|
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
365
|
function focusTriggersKeyboardModality(node) {
|
|
540
366
|
const { type, tagName } = node;
|
|
541
367
|
if (tagName === 'INPUT' &&
|
|
@@ -551,35 +377,17 @@ function focusTriggersKeyboardModality(node) {
|
|
|
551
377
|
}
|
|
552
378
|
return false;
|
|
553
379
|
}
|
|
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
380
|
function handleKeyDown(event) {
|
|
562
381
|
if (event.metaKey || event.altKey || event.ctrlKey) {
|
|
563
382
|
return;
|
|
564
383
|
}
|
|
565
384
|
hadKeyboardEvent = true;
|
|
566
385
|
}
|
|
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
386
|
function handlePointerDown() {
|
|
575
387
|
hadKeyboardEvent = false;
|
|
576
388
|
}
|
|
577
389
|
function handleVisibilityChange() {
|
|
578
390
|
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
391
|
if (hadFocusVisibleRecently) {
|
|
584
392
|
hadKeyboardEvent = true;
|
|
585
393
|
}
|
|
@@ -605,13 +413,7 @@ function isFocusVisible(event) {
|
|
|
605
413
|
return target.matches(':focus-visible');
|
|
606
414
|
}
|
|
607
415
|
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
416
|
}
|
|
613
|
-
// No need for validFocusTarget check. The user does that by attaching it to
|
|
614
|
-
// focusable events only.
|
|
615
417
|
return hadKeyboardEvent || focusTriggersKeyboardModality(target);
|
|
616
418
|
}
|
|
617
419
|
function useIsFocusVisible() {
|
|
@@ -621,20 +423,8 @@ function useIsFocusVisible() {
|
|
|
621
423
|
}
|
|
622
424
|
}, []);
|
|
623
425
|
const isFocusVisibleRef = React.useRef(false);
|
|
624
|
-
/**
|
|
625
|
-
* Should be called if a blur event is fired
|
|
626
|
-
*/
|
|
627
426
|
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
427
|
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
428
|
hadFocusVisibleRecently = true;
|
|
639
429
|
hadFocusVisibleRecentlyTimeout.start(100, () => {
|
|
640
430
|
hadFocusVisibleRecently = false;
|
|
@@ -644,9 +434,6 @@ function useIsFocusVisible() {
|
|
|
644
434
|
}
|
|
645
435
|
return false;
|
|
646
436
|
}
|
|
647
|
-
/**
|
|
648
|
-
* Should be called if a blur event is fired
|
|
649
|
-
*/
|
|
650
437
|
function handleFocusVisible(event) {
|
|
651
438
|
if (isFocusVisible(event)) {
|
|
652
439
|
isFocusVisibleRef.current = true;
|
|
@@ -662,11 +449,6 @@ function useIsFocusVisible() {
|
|
|
662
449
|
};
|
|
663
450
|
}
|
|
664
451
|
|
|
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
452
|
const useFocusVisible = ({ onFocusVisible, onFocus, onBlur, within = false, } = {}) => {
|
|
671
453
|
const { isFocusVisibleRef, onFocus: handleFocusVisible, onBlur: handleBlurVisible, ref: focusVisibleRef, } = useIsFocusVisible();
|
|
672
454
|
const [focusVisible, setFocusVisible] = useState(false);
|
|
@@ -675,15 +457,6 @@ const useFocusVisible = ({ onFocusVisible, onFocus, onBlur, within = false, } =
|
|
|
675
457
|
handleBlurVisible(event);
|
|
676
458
|
if (isFocusVisibleRef.current === false) {
|
|
677
459
|
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
460
|
if (within &&
|
|
688
461
|
!event.currentTarget.contains(event.relatedTarget)) {
|
|
689
462
|
setFocusVisibleWithin(false);
|
|
@@ -726,17 +499,12 @@ const useFocusVisible = ({ onFocusVisible, onFocus, onBlur, within = false, } =
|
|
|
726
499
|
onBlur: handleBlur,
|
|
727
500
|
onFocus: handleFocus,
|
|
728
501
|
},
|
|
729
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
730
502
|
focusVisibleRef: focusVisibleRef,
|
|
731
503
|
focusVisible,
|
|
732
504
|
focusVisibleWithin,
|
|
733
505
|
};
|
|
734
506
|
};
|
|
735
507
|
|
|
736
|
-
/**
|
|
737
|
-
* a method for trigger force update
|
|
738
|
-
* @returns force update method
|
|
739
|
-
*/
|
|
740
508
|
function useForceUpdate() {
|
|
741
509
|
const [, dispatch] = useState(Object.create(null));
|
|
742
510
|
return useEventCallback(() => {
|
|
@@ -744,39 +512,9 @@ function useForceUpdate() {
|
|
|
744
512
|
});
|
|
745
513
|
}
|
|
746
514
|
|
|
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
515
|
const useForkRef = unstable_useForkRef;
|
|
765
516
|
|
|
766
517
|
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
518
|
const createGlobalListener = (key, listener, options, { customKey = key, target: targetProp = window, } = {}) => {
|
|
781
519
|
let listening = false;
|
|
782
520
|
const getMapValue = () => globalListenerEventMap.get(customKey);
|
|
@@ -818,63 +556,18 @@ const createGlobalListener = (key, listener, options, { customKey = key, target:
|
|
|
818
556
|
};
|
|
819
557
|
return {
|
|
820
558
|
listen: () => addListener(),
|
|
821
|
-
/**
|
|
822
|
-
* remove listener
|
|
823
|
-
*/
|
|
824
559
|
remove: () => removeListener(),
|
|
825
|
-
/**
|
|
826
|
-
* current listener state
|
|
827
|
-
*/
|
|
828
560
|
get state() {
|
|
829
561
|
return {
|
|
830
|
-
/**
|
|
831
|
-
* current listener state count
|
|
832
|
-
*/
|
|
833
562
|
get count() {
|
|
834
563
|
return getMapValue()?.listeners.size || 0;
|
|
835
564
|
},
|
|
836
|
-
/**
|
|
837
|
-
* is that be listening
|
|
838
|
-
*/
|
|
839
565
|
listening,
|
|
840
566
|
};
|
|
841
567
|
},
|
|
842
568
|
};
|
|
843
569
|
};
|
|
844
570
|
|
|
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
571
|
function useGlobalListener(key, listener, ...args) {
|
|
879
572
|
const { options, config } = getListenerOverloadOption(args);
|
|
880
573
|
const { customKey = key, target: targetProp = window, startImmediately = true, } = config || {};
|
|
@@ -884,7 +577,6 @@ function useGlobalListener(key, listener, ...args) {
|
|
|
884
577
|
target: targetProp,
|
|
885
578
|
}));
|
|
886
579
|
if (process.env.NODE_ENV !== 'production') {
|
|
887
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
888
580
|
useEffect(() => {
|
|
889
581
|
if (customKey === key) {
|
|
890
582
|
if (Object.keys(options).length > 0) {
|
|
@@ -902,7 +594,6 @@ function useGlobalListener(key, listener, ...args) {
|
|
|
902
594
|
});
|
|
903
595
|
}
|
|
904
596
|
}
|
|
905
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
906
597
|
}, []);
|
|
907
598
|
}
|
|
908
599
|
useEffect(() => {
|
|
@@ -910,7 +601,6 @@ function useGlobalListener(key, listener, ...args) {
|
|
|
910
601
|
globalListener.listen();
|
|
911
602
|
}
|
|
912
603
|
return globalListener.remove;
|
|
913
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
914
604
|
}, []);
|
|
915
605
|
return globalListener;
|
|
916
606
|
}
|
|
@@ -936,10 +626,6 @@ function getListenerOverloadOption(args) {
|
|
|
936
626
|
return { options, config };
|
|
937
627
|
}
|
|
938
628
|
|
|
939
|
-
/**
|
|
940
|
-
* group hidden logic, when binding events element focus,
|
|
941
|
-
* that hiddenRef will auto be `tabindex={-1}` *
|
|
942
|
-
*/
|
|
943
629
|
const useHiddenTabindex = (target) => {
|
|
944
630
|
const handleFocus = useEventCallback(() => {
|
|
945
631
|
const hiddenElm = getRefElement(target);
|
|
@@ -1014,20 +700,6 @@ const useHoverDirty = ({ target, disabled = false, }) => {
|
|
|
1014
700
|
};
|
|
1015
701
|
};
|
|
1016
702
|
|
|
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
703
|
const useImgLoaded = ({ crossOrigin, referrerPolicy, src, srcSet, }) => {
|
|
1032
704
|
const [loaded, setLoaded] = useState(false);
|
|
1033
705
|
useEffect(() => {
|
|
@@ -1064,38 +736,7 @@ const useImgLoaded = ({ crossOrigin, referrerPolicy, src, srcSet, }) => {
|
|
|
1064
736
|
return loaded;
|
|
1065
737
|
};
|
|
1066
738
|
|
|
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) => {
|
|
739
|
+
const useInterval = (callback, delay = 0, startImmediately = true) => {
|
|
1099
740
|
const intervalRef = useRef();
|
|
1100
741
|
const countRef = useRef(0);
|
|
1101
742
|
const getRunning = useEventCallback(() => !!intervalRef.current);
|
|
@@ -1128,31 +769,13 @@ startImmediately = true) => {
|
|
|
1128
769
|
return () => cancel();
|
|
1129
770
|
}, [cancel, play, startImmediately]);
|
|
1130
771
|
return {
|
|
1131
|
-
/**
|
|
1132
|
-
* cancel current interval
|
|
1133
|
-
*/
|
|
1134
772
|
cancel,
|
|
1135
|
-
/**
|
|
1136
|
-
* pause current interval
|
|
1137
|
-
*/
|
|
1138
773
|
pause,
|
|
1139
|
-
/**
|
|
1140
|
-
* play current interval
|
|
1141
|
-
*/
|
|
1142
774
|
play,
|
|
1143
|
-
/**
|
|
1144
|
-
* return current interval running state
|
|
1145
|
-
*/
|
|
1146
775
|
getRunning,
|
|
1147
776
|
};
|
|
1148
777
|
};
|
|
1149
778
|
|
|
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
779
|
const useKeyDownOnce = (includeKeys, { onKeyDown, onKeyUp }) => {
|
|
1157
780
|
const isKeyDownMapRef = useRef({});
|
|
1158
781
|
const handleKeyDown = useEventCallback((e) => {
|
|
@@ -1182,13 +805,8 @@ const useKeyDownOnce = (includeKeys, { onKeyDown, onKeyUp }) => {
|
|
|
1182
805
|
};
|
|
1183
806
|
};
|
|
1184
807
|
|
|
1185
|
-
/**
|
|
1186
|
-
* that can move index in column array
|
|
1187
|
-
*/
|
|
1188
808
|
function moveIndexInTwoDimension(direction, options) {
|
|
1189
809
|
const { currIndex, columns, total, infinite } = options;
|
|
1190
|
-
// start index never small than 0
|
|
1191
|
-
// const index = Math.max(currIndex, 0);
|
|
1192
810
|
const lastIndex = total - 1;
|
|
1193
811
|
const isInOrder = infinite === 'order';
|
|
1194
812
|
const fillTotal = Math.ceil(total / columns) * columns;
|
|
@@ -1237,8 +855,6 @@ function moveIndexInTwoDimension(direction, options) {
|
|
|
1237
855
|
if (!infinite && resultIndex >= total) {
|
|
1238
856
|
return currIndex;
|
|
1239
857
|
}
|
|
1240
|
-
// * when that bigger then sourceTotal, mean that touch end line
|
|
1241
|
-
// * move to current full latest and move again.
|
|
1242
858
|
if (resultIndex >= total) {
|
|
1243
859
|
if (direction === 'ArrowRight') {
|
|
1244
860
|
resultIndex = fillTotal - 1;
|
|
@@ -1251,9 +867,6 @@ function moveIndexInTwoDimension(direction, options) {
|
|
|
1251
867
|
return resultIndex;
|
|
1252
868
|
}
|
|
1253
869
|
|
|
1254
|
-
/**
|
|
1255
|
-
* use keyboard move index in two dimensional
|
|
1256
|
-
*/
|
|
1257
870
|
const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocusedIndexChange, getOptionDisabled, getOptionSearchText, columns = 1, endSearchDebounceTime = 500, }) => {
|
|
1258
871
|
const total = options.length;
|
|
1259
872
|
const searchText = useRef('');
|
|
@@ -1280,7 +893,6 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1280
893
|
let findDirection;
|
|
1281
894
|
if (total === 0)
|
|
1282
895
|
return;
|
|
1283
|
-
// eslint-disable-next-line no-constant-condition
|
|
1284
896
|
while (true) {
|
|
1285
897
|
const toIndex = findDirection
|
|
1286
898
|
? findDirection === 'next'
|
|
@@ -1302,22 +914,16 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1302
914
|
}
|
|
1303
915
|
break;
|
|
1304
916
|
}
|
|
1305
|
-
// * mean that item is disabled, use same direction find next
|
|
1306
917
|
else {
|
|
1307
|
-
// * find toIndex is same as current, mean that already find all
|
|
1308
918
|
if (focusedIndexRef.current !== -1 &&
|
|
1309
919
|
toIndex === Math.max(focusedIndexRef.current, 0)) {
|
|
1310
920
|
break;
|
|
1311
921
|
}
|
|
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
922
|
if (focusedIndexRef.current === -1) {
|
|
1315
|
-
// from bottom to top and the first item in the list is not focusable
|
|
1316
923
|
if ((Math.abs(currIndex - toIndex) !== 1 || currIndex > toIndex) &&
|
|
1317
924
|
toIndex === 0) {
|
|
1318
925
|
break;
|
|
1319
926
|
}
|
|
1320
|
-
// from top to bottom and the last item in the list is not focusable
|
|
1321
927
|
if (Math.abs(currIndex - toIndex) === 1 &&
|
|
1322
928
|
currIndex < toIndex &&
|
|
1323
929
|
toIndex === total - 1) {
|
|
@@ -1325,7 +931,6 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1325
931
|
}
|
|
1326
932
|
}
|
|
1327
933
|
currIndex = toIndex;
|
|
1328
|
-
// * if that is Hom or End, find use increase or decrease
|
|
1329
934
|
if (key === 'Home') {
|
|
1330
935
|
findDirection = 'next';
|
|
1331
936
|
}
|
|
@@ -1335,16 +940,13 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1335
940
|
}
|
|
1336
941
|
}
|
|
1337
942
|
};
|
|
1338
|
-
const getPrevFocusableOption = useCallback((index = focusedIndexRef.current,
|
|
1339
|
-
/** is that infinite search */
|
|
1340
|
-
isInfinite = true) => {
|
|
943
|
+
const getPrevFocusableOption = useCallback((index = focusedIndexRef.current, isInfinite = true) => {
|
|
1341
944
|
for (let i = index; i >= 0; i--) {
|
|
1342
945
|
if (!getOptionDisabled?.(options[i])) {
|
|
1343
946
|
return i;
|
|
1344
947
|
}
|
|
1345
948
|
}
|
|
1346
949
|
if (isInfinite) {
|
|
1347
|
-
// loop find
|
|
1348
950
|
for (let i = total - 1; i > index; i--) {
|
|
1349
951
|
if (!getOptionDisabled?.(options[i])) {
|
|
1350
952
|
return i;
|
|
@@ -1353,16 +955,13 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1353
955
|
}
|
|
1354
956
|
return -1;
|
|
1355
957
|
}, [focusedIndexRef, getOptionDisabled, options, total]);
|
|
1356
|
-
const getNextFocusableOption = useCallback((index = focusedIndexRef.current,
|
|
1357
|
-
/** is that infinite search */
|
|
1358
|
-
isInfinite = true) => {
|
|
958
|
+
const getNextFocusableOption = useCallback((index = focusedIndexRef.current, isInfinite = true) => {
|
|
1359
959
|
for (let i = index; i < total; i++) {
|
|
1360
960
|
if (!getOptionDisabled?.(options[i])) {
|
|
1361
961
|
return i;
|
|
1362
962
|
}
|
|
1363
963
|
}
|
|
1364
964
|
if (isInfinite) {
|
|
1365
|
-
// loop find
|
|
1366
965
|
for (let i = 0; i < index; i++) {
|
|
1367
966
|
if (!getOptionDisabled?.(options[i])) {
|
|
1368
967
|
return i;
|
|
@@ -1372,26 +971,13 @@ const useKeyboardMoveFocus = ({ infinite, options = [], focusedIndexRef, onFocus
|
|
|
1372
971
|
return -1;
|
|
1373
972
|
}, [focusedIndexRef, getOptionDisabled, options, total]);
|
|
1374
973
|
return {
|
|
1375
|
-
/**
|
|
1376
|
-
* Get previous focusable option(include current index).
|
|
1377
|
-
*/
|
|
1378
974
|
getPrevFocusableOption,
|
|
1379
|
-
/**
|
|
1380
|
-
* Get next focusable option(include current index).
|
|
1381
|
-
*/
|
|
1382
975
|
getNextFocusableOption,
|
|
1383
|
-
/**
|
|
1384
|
-
* Use to handle keyboard event.
|
|
1385
|
-
*/
|
|
1386
976
|
onKeyFocusedIndexHandle,
|
|
1387
977
|
};
|
|
1388
978
|
};
|
|
1389
979
|
|
|
1390
980
|
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
981
|
const useOnlyOneFocusable = ({ focusedIndexRef, containerRef, }) => {
|
|
1396
982
|
const moveFocusedId = useId(MOVE_ATTR_KEY, true);
|
|
1397
983
|
const getIndexElm = (toIndex) => containerRef.current?.querySelector(`[${moveFocusedId}="${toIndex}"]`);
|
|
@@ -1424,28 +1010,6 @@ const useOnlyOneFocusable = ({ focusedIndexRef, containerRef, }) => {
|
|
|
1424
1010
|
};
|
|
1425
1011
|
};
|
|
1426
1012
|
|
|
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
1013
|
const useTouchMouseEvent = ({ onMouseDown: onMouseDownArg, onMouseUp: onMouseUpArg, onTouchStart: onTouchStartArg, onTouchEnd: onTouchEndArg, }, { actionRef } = {}) => {
|
|
1450
1014
|
const stopMouseEventRef = useRef(false);
|
|
1451
1015
|
const onTouchStart = useEventCallback((e) => {
|
|
@@ -1491,9 +1055,6 @@ const useTouchMouseEvent = ({ onMouseDown: onMouseDownArg, onMouseUp: onMouseUpA
|
|
|
1491
1055
|
onMouseUp,
|
|
1492
1056
|
};
|
|
1493
1057
|
};
|
|
1494
|
-
/**
|
|
1495
|
-
* check is element equal or contains target element
|
|
1496
|
-
*/
|
|
1497
1058
|
const isElmEqualOrContain = (sourceTarget, containTarget) => {
|
|
1498
1059
|
const elm = getRefElement(sourceTarget);
|
|
1499
1060
|
const target = getRefElement(containTarget);
|
|
@@ -1503,28 +1064,18 @@ const isElmEqualOrContain = (sourceTarget, containTarget) => {
|
|
|
1503
1064
|
const isTouchEvent = (ev) => {
|
|
1504
1065
|
return 'touches' in ev;
|
|
1505
1066
|
};
|
|
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
1067
|
const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTouchEnd: onTouchEndArg, onMouseDown, onKeyDown, onKeyUp, onMouseUp: onMouseUpArg, } = {}, { isPreventDefault = true, delay = 300, externalWindow = window, } = {}) => {
|
|
1515
1068
|
const isEmittedRef = useRef(false);
|
|
1516
1069
|
const isA11yDownRef = useRef(false);
|
|
1517
1070
|
const actionRef = useRef(null);
|
|
1518
1071
|
const reasonRef = useRef('click');
|
|
1519
1072
|
const emitLongPress = useDebounce((e) => {
|
|
1520
|
-
// * only when have onPress set have emit
|
|
1521
1073
|
if (onPress) {
|
|
1522
1074
|
onPress(e, reasonRef.current);
|
|
1523
1075
|
isEmittedRef.current = true;
|
|
1524
1076
|
}
|
|
1525
1077
|
}, delay);
|
|
1526
1078
|
const end = (e, isInside) => {
|
|
1527
|
-
// ! mouse up only trigger when up on element, so we host that in document
|
|
1528
1079
|
globalMouseUpListener.remove();
|
|
1529
1080
|
if (!isEmittedRef.current && isInside) {
|
|
1530
1081
|
onTap?.(e, reasonRef.current);
|
|
@@ -1534,7 +1085,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1534
1085
|
emitLongPress.cancel();
|
|
1535
1086
|
};
|
|
1536
1087
|
const start = (e) => {
|
|
1537
|
-
// ! mouse up only trigger when up on element, so we host that in document
|
|
1538
1088
|
globalMouseUpListener.listen();
|
|
1539
1089
|
const isTouch = e.type === 'touchstart';
|
|
1540
1090
|
if (isTouch) {
|
|
@@ -1544,7 +1094,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1544
1094
|
};
|
|
1545
1095
|
const { onMouseUp, onTouchEnd, onTouchStart, ...events } = useTouchMouseEvent({
|
|
1546
1096
|
onTouchStart: (e) => {
|
|
1547
|
-
// prevent long touch leave element focus
|
|
1548
1097
|
if (isPreventDefault) {
|
|
1549
1098
|
e.preventDefault();
|
|
1550
1099
|
}
|
|
@@ -1552,7 +1101,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1552
1101
|
start(e);
|
|
1553
1102
|
},
|
|
1554
1103
|
onTouchEnd: (e, isInside) => {
|
|
1555
|
-
// prevent leave element focus
|
|
1556
1104
|
if (isPreventDefault) {
|
|
1557
1105
|
e.preventDefault();
|
|
1558
1106
|
}
|
|
@@ -1567,7 +1115,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1567
1115
|
start(e);
|
|
1568
1116
|
},
|
|
1569
1117
|
onMouseUp: (e, isInside) => {
|
|
1570
|
-
// prevent leave element focus
|
|
1571
1118
|
if (isPreventDefault) {
|
|
1572
1119
|
e.preventDefault();
|
|
1573
1120
|
}
|
|
@@ -1575,11 +1122,9 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1575
1122
|
end(e, isInside);
|
|
1576
1123
|
},
|
|
1577
1124
|
}, { actionRef });
|
|
1578
|
-
// use event listener for make event `passive` work in safari
|
|
1579
1125
|
useEventListener(actionRef, 'touchstart', onTouchStart, {
|
|
1580
1126
|
passive: false,
|
|
1581
1127
|
});
|
|
1582
|
-
// use event listener for make event `passive` work in safari
|
|
1583
1128
|
useEventListener(actionRef, 'touchend', onTouchEnd, {
|
|
1584
1129
|
passive: false,
|
|
1585
1130
|
});
|
|
@@ -1589,7 +1134,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1589
1134
|
});
|
|
1590
1135
|
const handleDeleteKeyDown = useA11yKeyEvent((e) => {
|
|
1591
1136
|
reasonRef.current = 'keyboard';
|
|
1592
|
-
// * keydown when hold will trigger many time, show when current is a11y down, not trigger again
|
|
1593
1137
|
if (!isA11yDownRef.current) {
|
|
1594
1138
|
emitLongPress(e);
|
|
1595
1139
|
}
|
|
@@ -1613,20 +1157,6 @@ const useLongPress = ({ onTap, onPress }, { onTouchStart: onTouchStartArg, onTou
|
|
|
1613
1157
|
};
|
|
1614
1158
|
};
|
|
1615
1159
|
|
|
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
1160
|
const useMountState = () => {
|
|
1631
1161
|
const isMountedRef = useRef(false);
|
|
1632
1162
|
useEffect(() => {
|
|
@@ -1638,9 +1168,6 @@ const useMountState = () => {
|
|
|
1638
1168
|
return isMountedRef;
|
|
1639
1169
|
};
|
|
1640
1170
|
|
|
1641
|
-
/**
|
|
1642
|
-
* only trigger when re-render (in useLayoutEffect), not trigger on component first render
|
|
1643
|
-
*/
|
|
1644
1171
|
const useOnLayoutReRender = (cb, deps) => {
|
|
1645
1172
|
const count = useRef(0);
|
|
1646
1173
|
const method = useEventCallback(cb);
|
|
@@ -1648,17 +1175,12 @@ const useOnLayoutReRender = (cb, deps) => {
|
|
|
1648
1175
|
if (count.current === 0) {
|
|
1649
1176
|
count.current = 1;
|
|
1650
1177
|
return () => {
|
|
1651
|
-
//
|
|
1652
1178
|
};
|
|
1653
1179
|
}
|
|
1654
1180
|
return method();
|
|
1655
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1656
1181
|
}, deps);
|
|
1657
1182
|
};
|
|
1658
1183
|
|
|
1659
|
-
/**
|
|
1660
|
-
* only trigger when re-render, not trigger on component first render
|
|
1661
|
-
*/
|
|
1662
1184
|
const useOnReRender = (cb, deps) => {
|
|
1663
1185
|
const count = useRef(0);
|
|
1664
1186
|
const method = useEventCallback(cb);
|
|
@@ -1666,28 +1188,13 @@ const useOnReRender = (cb, deps) => {
|
|
|
1666
1188
|
if (count.current === 0) {
|
|
1667
1189
|
count.current = 1;
|
|
1668
1190
|
return () => {
|
|
1669
|
-
//
|
|
1670
1191
|
};
|
|
1671
1192
|
}
|
|
1672
1193
|
return method();
|
|
1673
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1674
1194
|
}, deps);
|
|
1675
1195
|
};
|
|
1676
1196
|
|
|
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 = {}) => {
|
|
1197
|
+
const useResizeObserver = (target, callback, performance = {}) => {
|
|
1691
1198
|
const { mode = 'debounce', time: performanceTime = 200, ignoreFireWhenObserve, options, } = performance;
|
|
1692
1199
|
const countRef = useRef(0);
|
|
1693
1200
|
const cb = useEventCallback((...args) => {
|
|
@@ -1725,29 +1232,14 @@ performance = {}) => {
|
|
|
1725
1232
|
return () => {
|
|
1726
1233
|
observer.disconnect();
|
|
1727
1234
|
};
|
|
1728
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1729
1235
|
}, [el]);
|
|
1730
1236
|
return observerRef.current;
|
|
1731
1237
|
};
|
|
1732
1238
|
|
|
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 = {
|
|
1239
|
+
const useOverflow = (target, cb, performance = {
|
|
1747
1240
|
mode: 'throttle',
|
|
1748
1241
|
time: 200,
|
|
1749
1242
|
}) => {
|
|
1750
|
-
// * not have default value for that can trigger change first time.
|
|
1751
1243
|
const showRef = useRef();
|
|
1752
1244
|
return useResizeObserver(target, () => {
|
|
1753
1245
|
const element = getRefElement(target);
|
|
@@ -1757,7 +1249,6 @@ performance = {
|
|
|
1757
1249
|
const show = scrollWidth > clientWidth || scrollHeight > clientHeight;
|
|
1758
1250
|
if (showRef.current === show)
|
|
1759
1251
|
return;
|
|
1760
|
-
// only emit when state change
|
|
1761
1252
|
showRef.current = show;
|
|
1762
1253
|
cb(show, {
|
|
1763
1254
|
scrollWidth,
|
|
@@ -1768,38 +1259,10 @@ performance = {
|
|
|
1768
1259
|
}, performance);
|
|
1769
1260
|
};
|
|
1770
1261
|
|
|
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
1262
|
const useRefState = (value, customUpdate) => {
|
|
1791
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
1792
1263
|
const forceUpdate = customUpdate ?? useForceUpdate();
|
|
1793
1264
|
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) => {
|
|
1265
|
+
const setInnerValue = useEventCallback((toValue, isUpdate = true) => {
|
|
1803
1266
|
innerValueRef.current = toValue;
|
|
1804
1267
|
if (isUpdate) {
|
|
1805
1268
|
forceUpdate();
|
|
@@ -1808,10 +1271,6 @@ const useRefState = (value, customUpdate) => {
|
|
|
1808
1271
|
return [innerValueRef, setInnerValue];
|
|
1809
1272
|
};
|
|
1810
1273
|
|
|
1811
|
-
/**
|
|
1812
|
-
* get all matched breakpoints
|
|
1813
|
-
* @param defaultBreakpoint use defaultBreakpoint when Responsive init (will pass undefined)
|
|
1814
|
-
*/
|
|
1815
1274
|
const useResponsiveMatch = (defaultBreakpoint) => {
|
|
1816
1275
|
const _breakpoint = useResponsiveContext();
|
|
1817
1276
|
const breakpoint = _breakpoint === undefined ? defaultBreakpoint : _breakpoint;
|
|
@@ -1821,13 +1280,6 @@ const useResponsiveMatch = (defaultBreakpoint) => {
|
|
|
1821
1280
|
return matchDetail.matchMap;
|
|
1822
1281
|
};
|
|
1823
1282
|
|
|
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
1283
|
const useSleep = () => {
|
|
1832
1284
|
const timeoutIdRef = useRef();
|
|
1833
1285
|
const rejectRef = useRef();
|
|
@@ -1857,36 +1309,13 @@ const useSleep = () => {
|
|
|
1857
1309
|
};
|
|
1858
1310
|
}, []);
|
|
1859
1311
|
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
1312
|
sleep,
|
|
1865
|
-
/**
|
|
1866
|
-
* cancel current sleep timer
|
|
1867
|
-
*/
|
|
1868
1313
|
cancel,
|
|
1869
|
-
/**
|
|
1870
|
-
* return is current is sleeping
|
|
1871
|
-
*/
|
|
1872
1314
|
getSleeping: () => sleepingRef.current,
|
|
1873
1315
|
};
|
|
1874
1316
|
};
|
|
1875
1317
|
|
|
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) => {
|
|
1318
|
+
const useRetry = (cb, options) => {
|
|
1890
1319
|
const { sleep, cancel: cancelSleep } = useSleep();
|
|
1891
1320
|
const isCancelRef = useRef(true);
|
|
1892
1321
|
const complete = () => {
|
|
@@ -1895,7 +1324,6 @@ options) => {
|
|
|
1895
1324
|
};
|
|
1896
1325
|
const cancel = () => {
|
|
1897
1326
|
if (!isCancelRef.current) {
|
|
1898
|
-
// * when outside cancel
|
|
1899
1327
|
options?.onCancel?.();
|
|
1900
1328
|
complete();
|
|
1901
1329
|
}
|
|
@@ -1905,7 +1333,6 @@ options) => {
|
|
|
1905
1333
|
cancel();
|
|
1906
1334
|
isCancelRef.current = false;
|
|
1907
1335
|
options?.onStart?.();
|
|
1908
|
-
// * when exec success, just return directly.
|
|
1909
1336
|
if (await cb(...args)) {
|
|
1910
1337
|
if (!isCancelRef.current) {
|
|
1911
1338
|
options?.onSuccess?.();
|
|
@@ -1937,21 +1364,11 @@ options) => {
|
|
|
1937
1364
|
complete();
|
|
1938
1365
|
};
|
|
1939
1366
|
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
1367
|
retry,
|
|
1945
|
-
/**
|
|
1946
|
-
* cancel current retry
|
|
1947
|
-
*/
|
|
1948
1368
|
cancel,
|
|
1949
1369
|
};
|
|
1950
1370
|
};
|
|
1951
1371
|
|
|
1952
|
-
/**
|
|
1953
|
-
* @param onTabOut Callback fired when a "tab out" event is detected.
|
|
1954
|
-
*/
|
|
1955
1372
|
const useTabOut = (onTabOut) => {
|
|
1956
1373
|
const lastKeyDownIsTabRef = useRef(false);
|
|
1957
1374
|
const handleKeyDown = (e) => {
|
|
@@ -1977,28 +1394,13 @@ const useTabOut = (onTabOut) => {
|
|
|
1977
1394
|
};
|
|
1978
1395
|
};
|
|
1979
1396
|
|
|
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
1397
|
const useThrottle = (fn, throttleTime = 200, options) => {
|
|
1988
1398
|
const memoFn = useEventCallback(fn);
|
|
1989
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1990
1399
|
const throttleFn = useCallback(throttle(memoFn, throttleTime, options), []);
|
|
1991
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1992
1400
|
useEffect(() => () => throttleFn.cancel(), []);
|
|
1993
1401
|
return throttleFn;
|
|
1994
1402
|
};
|
|
1995
1403
|
|
|
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
1404
|
const useTestRunnerDetection = () => {
|
|
2003
1405
|
return useMemo(() => {
|
|
2004
1406
|
return (typeof window !== 'undefined' &&
|
|
@@ -2009,10 +1411,7 @@ const useTestRunnerDetection = () => {
|
|
|
2009
1411
|
function useTimeout(callback, delay) {
|
|
2010
1412
|
const savedCallback = useRef(callback);
|
|
2011
1413
|
savedCallback.current = callback;
|
|
2012
|
-
// Set up the timeout.
|
|
2013
1414
|
useEffect(() => {
|
|
2014
|
-
// Don't schedule if no delay is specified.
|
|
2015
|
-
// Note: 0 is a valid value for delay.
|
|
2016
1415
|
if (!delay && delay !== 0) {
|
|
2017
1416
|
return;
|
|
2018
1417
|
}
|
|
@@ -2021,28 +1420,12 @@ function useTimeout(callback, delay) {
|
|
|
2021
1420
|
}, [delay]);
|
|
2022
1421
|
}
|
|
2023
1422
|
|
|
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
1423
|
const isFontLoadingAPIAvailable = () => {
|
|
2029
1424
|
return (typeof document !== 'undefined' &&
|
|
2030
1425
|
document.fonts &&
|
|
2031
1426
|
typeof document.fonts.ready === 'object' &&
|
|
2032
1427
|
typeof document.fonts.ready.then === 'function');
|
|
2033
1428
|
};
|
|
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
1429
|
const useFontLoaded = (enabled = true) => {
|
|
2047
1430
|
const [loaded, setLoaded] = useState(() => !enabled || !isFontLoadingAPIAvailable());
|
|
2048
1431
|
useEffect(() => {
|
|
@@ -2062,14 +1445,6 @@ const useFontLoaded = (enabled = true) => {
|
|
|
2062
1445
|
return loaded;
|
|
2063
1446
|
};
|
|
2064
1447
|
|
|
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
1448
|
const useBodyScrollLock = (isLocked, targetElement, options = {}) => {
|
|
2074
1449
|
const { disabled = false, reserveScrollBarGap = true, allowTouchMove, } = options;
|
|
2075
1450
|
const { document } = useWindowContext();
|