@tale-ui/utils 0.0.3
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/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/detectBrowser.d.ts +8 -0
- package/detectBrowser.js +64 -0
- package/empty.d.ts +3 -0
- package/empty.js +10 -0
- package/error.d.ts +2 -0
- package/error.js +23 -0
- package/esm/detectBrowser.d.ts +8 -0
- package/esm/detectBrowser.js +58 -0
- package/esm/empty.d.ts +3 -0
- package/esm/empty.js +3 -0
- package/esm/error.d.ts +2 -0
- package/esm/error.js +16 -0
- package/esm/fastHooks.d.ts +14 -0
- package/esm/fastHooks.js +43 -0
- package/esm/fastObjectShallowCompare.d.ts +1 -0
- package/esm/fastObjectShallowCompare.js +29 -0
- package/esm/formatErrorMessage.d.ts +18 -0
- package/esm/formatErrorMessage.js +26 -0
- package/esm/generateId.d.ts +1 -0
- package/esm/generateId.js +5 -0
- package/esm/getReactElementRef.d.ts +5 -0
- package/esm/getReactElementRef.js +14 -0
- package/esm/inertValue.d.ts +1 -0
- package/esm/inertValue.js +8 -0
- package/esm/isElementDisabled.d.ts +1 -0
- package/esm/isElementDisabled.js +3 -0
- package/esm/isMouseWithinBounds.d.ts +1 -0
- package/esm/isMouseWithinBounds.js +10 -0
- package/esm/mergeObjects.d.ts +1 -0
- package/esm/mergeObjects.js +15 -0
- package/esm/owner.d.ts +2 -0
- package/esm/owner.js +4 -0
- package/esm/package.json +1 -0
- package/esm/reactVersion.d.ts +3 -0
- package/esm/reactVersion.js +5 -0
- package/esm/safeReact.d.ts +2 -0
- package/esm/safeReact.js +6 -0
- package/esm/store/ReactStore.d.ts +91 -0
- package/esm/store/ReactStore.js +199 -0
- package/esm/store/Store.d.ts +58 -0
- package/esm/store/Store.js +111 -0
- package/esm/store/StoreInspector.d.ts +41 -0
- package/esm/store/StoreInspector.js +537 -0
- package/esm/store/createSelector.d.ts +30 -0
- package/esm/store/createSelector.js +148 -0
- package/esm/store/index.d.ts +5 -0
- package/esm/store/index.js +5 -0
- package/esm/store/useStore.d.ts +22 -0
- package/esm/store/useStore.js +107 -0
- package/esm/testUtils.d.ts +17 -0
- package/esm/testUtils.js +19 -0
- package/esm/useAnimationFrame.d.ts +18 -0
- package/esm/useAnimationFrame.js +106 -0
- package/esm/useControlled.d.ts +24 -0
- package/esm/useControlled.js +40 -0
- package/esm/useEnhancedClickHandler.d.ts +13 -0
- package/esm/useEnhancedClickHandler.js +38 -0
- package/esm/useForcedRerendering.d.ts +4 -0
- package/esm/useForcedRerendering.js +13 -0
- package/esm/useId.d.ts +7 -0
- package/esm/useId.js +41 -0
- package/esm/useInterval.d.ts +13 -0
- package/esm/useInterval.js +36 -0
- package/esm/useIsoLayoutEffect.d.ts +2 -0
- package/esm/useIsoLayoutEffect.js +5 -0
- package/esm/useMergedRefs.d.ts +21 -0
- package/esm/useMergedRefs.js +108 -0
- package/esm/useOnFirstRender.d.ts +1 -0
- package/esm/useOnFirstRender.js +10 -0
- package/esm/useOnMount.d.ts +5 -0
- package/esm/useOnMount.js +14 -0
- package/esm/usePreviousValue.d.ts +6 -0
- package/esm/usePreviousValue.js +22 -0
- package/esm/useRefWithInit.d.ts +10 -0
- package/esm/useRefWithInit.js +20 -0
- package/esm/useScrollLock.d.ts +7 -0
- package/esm/useScrollLock.js +244 -0
- package/esm/useStableCallback.d.ts +13 -0
- package/esm/useStableCallback.js +44 -0
- package/esm/useTimeout.d.ts +17 -0
- package/esm/useTimeout.js +43 -0
- package/esm/useValueAsRef.d.ts +10 -0
- package/esm/useValueAsRef.js +28 -0
- package/esm/visuallyHidden.d.ts +3 -0
- package/esm/visuallyHidden.js +20 -0
- package/esm/warn.d.ts +1 -0
- package/esm/warn.js +13 -0
- package/fastHooks.d.ts +14 -0
- package/fastHooks.js +54 -0
- package/fastObjectShallowCompare.d.ts +1 -0
- package/fastObjectShallowCompare.js +35 -0
- package/formatErrorMessage.d.ts +18 -0
- package/formatErrorMessage.js +33 -0
- package/generateId.d.ts +1 -0
- package/generateId.js +11 -0
- package/getReactElementRef.d.ts +5 -0
- package/getReactElementRef.js +20 -0
- package/inertValue.d.ts +1 -0
- package/inertValue.js +14 -0
- package/isElementDisabled.d.ts +1 -0
- package/isElementDisabled.js +9 -0
- package/isMouseWithinBounds.d.ts +1 -0
- package/isMouseWithinBounds.js +16 -0
- package/mergeObjects.d.ts +1 -0
- package/mergeObjects.js +21 -0
- package/owner.d.ts +2 -0
- package/owner.js +16 -0
- package/package.json +64 -0
- package/reactVersion.d.ts +3 -0
- package/reactVersion.js +12 -0
- package/safeReact.d.ts +2 -0
- package/safeReact.js +12 -0
- package/store/ReactStore.d.ts +91 -0
- package/store/ReactStore.js +205 -0
- package/store/Store.d.ts +58 -0
- package/store/Store.js +118 -0
- package/store/StoreInspector.d.ts +41 -0
- package/store/StoreInspector.js +544 -0
- package/store/createSelector.d.ts +30 -0
- package/store/createSelector.js +154 -0
- package/store/index.d.ts +5 -0
- package/store/index.js +60 -0
- package/store/useStore.d.ts +22 -0
- package/store/useStore.js +115 -0
- package/testUtils.d.ts +17 -0
- package/testUtils.js +26 -0
- package/useAnimationFrame.d.ts +18 -0
- package/useAnimationFrame.js +113 -0
- package/useControlled.d.ts +24 -0
- package/useControlled.js +46 -0
- package/useEnhancedClickHandler.d.ts +13 -0
- package/useEnhancedClickHandler.js +44 -0
- package/useForcedRerendering.d.ts +4 -0
- package/useForcedRerendering.js +18 -0
- package/useId.d.ts +7 -0
- package/useId.js +47 -0
- package/useInterval.d.ts +13 -0
- package/useInterval.js +43 -0
- package/useIsoLayoutEffect.d.ts +2 -0
- package/useIsoLayoutEffect.js +11 -0
- package/useMergedRefs.d.ts +21 -0
- package/useMergedRefs.js +114 -0
- package/useOnFirstRender.d.ts +1 -0
- package/useOnFirstRender.js +16 -0
- package/useOnMount.d.ts +5 -0
- package/useOnMount.js +20 -0
- package/usePreviousValue.d.ts +6 -0
- package/usePreviousValue.js +27 -0
- package/useRefWithInit.d.ts +10 -0
- package/useRefWithInit.js +26 -0
- package/useScrollLock.d.ts +7 -0
- package/useScrollLock.js +249 -0
- package/useStableCallback.d.ts +13 -0
- package/useStableCallback.js +49 -0
- package/useTimeout.d.ts +17 -0
- package/useTimeout.js +50 -0
- package/useValueAsRef.d.ts +10 -0
- package/useValueAsRef.js +32 -0
- package/visuallyHidden.d.ts +3 -0
- package/visuallyHidden.js +26 -0
- package/warn.d.ts +1 -0
- package/warn.js +19 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Instance } from "../fastHooks.js";
|
|
2
|
+
import type { ReadonlyStore } from "./Store.js";
|
|
3
|
+
export declare function useStore<State, Value>(store: ReadonlyStore<State>, selector: (state: State) => Value): Value;
|
|
4
|
+
export declare function useStore<State, Value, A1>(store: ReadonlyStore<State>, selector: (state: State, a1: A1) => Value, a1: A1): Value;
|
|
5
|
+
export declare function useStore<State, Value, A1, A2>(store: ReadonlyStore<State>, selector: (state: State, a1: A1, a2: A2) => Value, a1: A1, a2: A2): Value;
|
|
6
|
+
export declare function useStore<State, Value, A1, A2, A3>(store: ReadonlyStore<State>, selector: (state: State, a1: A1, a2: A2, a3: A3) => Value, a1: A1, a2: A2, a3: A3): Value;
|
|
7
|
+
export type StoreInstance = Instance & {
|
|
8
|
+
syncIndex: number;
|
|
9
|
+
syncTick: number;
|
|
10
|
+
syncHooks: {
|
|
11
|
+
store: any;
|
|
12
|
+
selector: Function;
|
|
13
|
+
a1: unknown;
|
|
14
|
+
a2: unknown;
|
|
15
|
+
a3: unknown;
|
|
16
|
+
value: unknown;
|
|
17
|
+
didChange: boolean;
|
|
18
|
+
}[];
|
|
19
|
+
didChangeStore: boolean;
|
|
20
|
+
subscribe: (onStoreChange: any) => () => void;
|
|
21
|
+
getSnapshot: () => unknown;
|
|
22
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/* We need to import the shim because React 17 does not support the `useSyncExternalStore` API.
|
|
3
|
+
* More info: https://github.com/mui/mui-x/issues/18303#issuecomment-2958392341 */
|
|
4
|
+
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
|
5
|
+
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector';
|
|
6
|
+
import { isReactVersionAtLeast } from "../reactVersion.js";
|
|
7
|
+
import { register, getInstance } from "../fastHooks.js";
|
|
8
|
+
/* Some tests fail in R18 with the raw useSyncExternalStore. It may be possible to make it work
|
|
9
|
+
* but for now we only enable it for R19+. */
|
|
10
|
+
const canUseRawUseSyncExternalStore = isReactVersionAtLeast(19);
|
|
11
|
+
const useStoreImplementation = canUseRawUseSyncExternalStore ? useStoreFast : useStoreLegacy;
|
|
12
|
+
export function useStore(store, selector, a1, a2, a3) {
|
|
13
|
+
return useStoreImplementation(store, selector, a1, a2, a3);
|
|
14
|
+
}
|
|
15
|
+
function useStoreR19(store, selector, a1, a2, a3) {
|
|
16
|
+
const getSelection = React.useCallback(() => selector(store.getSnapshot(), a1, a2, a3), [store, selector, a1, a2, a3]);
|
|
17
|
+
return useSyncExternalStore(store.subscribe, getSelection, getSelection);
|
|
18
|
+
}
|
|
19
|
+
register({
|
|
20
|
+
before(instance) {
|
|
21
|
+
instance.syncIndex = 0;
|
|
22
|
+
if (!instance.didInitialize) {
|
|
23
|
+
instance.syncTick = 1;
|
|
24
|
+
instance.syncHooks = [];
|
|
25
|
+
instance.didChangeStore = true;
|
|
26
|
+
instance.getSnapshot = () => {
|
|
27
|
+
let didChange = false;
|
|
28
|
+
for (let i = 0; i < instance.syncHooks.length; i += 1) {
|
|
29
|
+
const hook = instance.syncHooks[i];
|
|
30
|
+
const value = hook.selector(hook.store.state, hook.a1, hook.a2, hook.a3);
|
|
31
|
+
if (hook.didChange || !Object.is(hook.value, value)) {
|
|
32
|
+
didChange = true;
|
|
33
|
+
hook.value = value;
|
|
34
|
+
hook.didChange = false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (didChange) {
|
|
38
|
+
instance.syncTick += 1;
|
|
39
|
+
}
|
|
40
|
+
return instance.syncTick;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
after(instance) {
|
|
45
|
+
if (instance.syncHooks.length > 0) {
|
|
46
|
+
if (instance.didChangeStore) {
|
|
47
|
+
instance.didChangeStore = false;
|
|
48
|
+
instance.subscribe = onStoreChange => {
|
|
49
|
+
const stores = new Set();
|
|
50
|
+
for (const hook of instance.syncHooks) {
|
|
51
|
+
stores.add(hook.store);
|
|
52
|
+
}
|
|
53
|
+
const unsubscribes = [];
|
|
54
|
+
for (const store of stores) {
|
|
55
|
+
unsubscribes.push(store.subscribe(onStoreChange));
|
|
56
|
+
}
|
|
57
|
+
return () => {
|
|
58
|
+
for (const unsubscribe of unsubscribes) {
|
|
59
|
+
unsubscribe();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
65
|
+
useSyncExternalStore(instance.subscribe, instance.getSnapshot, instance.getSnapshot);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
function useStoreFast(store, selector, a1, a2, a3) {
|
|
70
|
+
const instance = getInstance();
|
|
71
|
+
if (!instance) {
|
|
72
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
73
|
+
return useStoreR19(store, selector, a1, a2, a3);
|
|
74
|
+
}
|
|
75
|
+
const index = instance.syncIndex;
|
|
76
|
+
instance.syncIndex += 1;
|
|
77
|
+
let hook;
|
|
78
|
+
if (!instance.didInitialize) {
|
|
79
|
+
hook = {
|
|
80
|
+
store,
|
|
81
|
+
selector,
|
|
82
|
+
a1,
|
|
83
|
+
a2,
|
|
84
|
+
a3,
|
|
85
|
+
value: selector(store.getSnapshot(), a1, a2, a3),
|
|
86
|
+
didChange: false
|
|
87
|
+
};
|
|
88
|
+
instance.syncHooks.push(hook);
|
|
89
|
+
} else {
|
|
90
|
+
hook = instance.syncHooks[index];
|
|
91
|
+
if (hook.store !== store || hook.selector !== selector || !Object.is(hook.a1, a1) || !Object.is(hook.a2, a2) || !Object.is(hook.a3, a3)) {
|
|
92
|
+
if (hook.store !== store) {
|
|
93
|
+
instance.didChangeStore = true;
|
|
94
|
+
}
|
|
95
|
+
hook.store = store;
|
|
96
|
+
hook.selector = selector;
|
|
97
|
+
hook.a1 = a1;
|
|
98
|
+
hook.a2 = a2;
|
|
99
|
+
hook.a3 = a3;
|
|
100
|
+
hook.didChange = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return hook.value;
|
|
104
|
+
}
|
|
105
|
+
function useStoreLegacy(store, selector, a1, a2, a3) {
|
|
106
|
+
return useSyncExternalStoreWithSelector(store.subscribe, store.getSnapshot, store.getSnapshot, state => selector(state, a1, a2, a3));
|
|
107
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the test runs in JSDOM environment
|
|
3
|
+
*/
|
|
4
|
+
export declare const isJSDOM: boolean;
|
|
5
|
+
export type IfEquals<T, U, Y = unknown, N = never> = (<G>() => G extends T ? 1 : 2) extends (<G>() => G extends U ? 1 : 2) ? Y : N;
|
|
6
|
+
/**
|
|
7
|
+
* Issues a type error if `Expected` is not identical to `Actual`.
|
|
8
|
+
*
|
|
9
|
+
* `Expected` should be declared when invoking `expectType`.
|
|
10
|
+
* `Actual` should almost always we be a `typeof value` statement.
|
|
11
|
+
*
|
|
12
|
+
* @example `expectType<number | string, typeof value>(value)`
|
|
13
|
+
* TypeScript issues a type error since `value is not assignable to never`.
|
|
14
|
+
* This means `typeof value` is not identical to `number | string`
|
|
15
|
+
* @param _actual
|
|
16
|
+
*/
|
|
17
|
+
export declare function expectType<Expected, Actual>(_actual: IfEquals<Actual, Expected, Actual>): void;
|
package/esm/testUtils.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the test runs in JSDOM environment
|
|
3
|
+
*/
|
|
4
|
+
export const isJSDOM = /jsdom/.test(window.navigator.userAgent);
|
|
5
|
+
|
|
6
|
+
// https://stackoverflow.com/questions/53807517/how-to-test-if-two-types-are-exactly-the-same
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Issues a type error if `Expected` is not identical to `Actual`.
|
|
10
|
+
*
|
|
11
|
+
* `Expected` should be declared when invoking `expectType`.
|
|
12
|
+
* `Actual` should almost always we be a `typeof value` statement.
|
|
13
|
+
*
|
|
14
|
+
* @example `expectType<number | string, typeof value>(value)`
|
|
15
|
+
* TypeScript issues a type error since `value is not assignable to never`.
|
|
16
|
+
* This means `typeof value` is not identical to `number | string`
|
|
17
|
+
* @param _actual
|
|
18
|
+
*/
|
|
19
|
+
export function expectType(_actual) {}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type AnimationFrameId = number;
|
|
2
|
+
export declare class AnimationFrame {
|
|
3
|
+
static create(): AnimationFrame;
|
|
4
|
+
static request(fn: FrameRequestCallback): number;
|
|
5
|
+
static cancel(id: AnimationFrameId): void;
|
|
6
|
+
currentId: AnimationFrameId | null;
|
|
7
|
+
/**
|
|
8
|
+
* Executes `fn` after `delay`, clearing any previously scheduled call.
|
|
9
|
+
*/
|
|
10
|
+
request(fn: Function): void;
|
|
11
|
+
cancel: () => void;
|
|
12
|
+
disposeEffect: () => () => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A `requestAnimationFrame` with automatic cleanup and guard.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useAnimationFrame(): AnimationFrame;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useRefWithInit } from "./useRefWithInit.js";
|
|
4
|
+
import { useOnMount } from "./useOnMount.js";
|
|
5
|
+
/** Unlike `setTimeout`, rAF doesn't guarantee a positive integer return value, so we can't have
|
|
6
|
+
* a monomorphic `uint` type with `0` meaning empty.
|
|
7
|
+
* See warning note at:
|
|
8
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value */
|
|
9
|
+
const EMPTY = null;
|
|
10
|
+
let LAST_RAF = globalThis.requestAnimationFrame;
|
|
11
|
+
class Scheduler {
|
|
12
|
+
/* This implementation uses an array as a backing data-structure for frame callbacks.
|
|
13
|
+
* It allows `O(1)` callback cancelling by inserting a `null` in the array, though it
|
|
14
|
+
* never calls the native `cancelAnimationFrame` if there are no frames left. This can
|
|
15
|
+
* be much more efficient if there is a call pattern that alterns as
|
|
16
|
+
* "request-cancel-request-cancel-…".
|
|
17
|
+
* But in the case of "request-request-…-cancel-cancel-…", it leaves the final animation
|
|
18
|
+
* frame to run anyway. We turn that frame into a `O(1)` no-op via `callbacksCount`. */
|
|
19
|
+
|
|
20
|
+
callbacks = [];
|
|
21
|
+
callbacksCount = 0;
|
|
22
|
+
nextId = 1;
|
|
23
|
+
startId = 1;
|
|
24
|
+
isScheduled = false;
|
|
25
|
+
tick = timestamp => {
|
|
26
|
+
this.isScheduled = false;
|
|
27
|
+
const currentCallbacks = this.callbacks;
|
|
28
|
+
const currentCallbacksCount = this.callbacksCount;
|
|
29
|
+
|
|
30
|
+
// Update these before iterating, callbacks could call `requestAnimationFrame` again.
|
|
31
|
+
this.callbacks = [];
|
|
32
|
+
this.callbacksCount = 0;
|
|
33
|
+
this.startId = this.nextId;
|
|
34
|
+
if (currentCallbacksCount > 0) {
|
|
35
|
+
for (let i = 0; i < currentCallbacks.length; i += 1) {
|
|
36
|
+
currentCallbacks[i]?.(timestamp);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
request(fn) {
|
|
41
|
+
const id = this.nextId;
|
|
42
|
+
this.nextId += 1;
|
|
43
|
+
this.callbacks.push(fn);
|
|
44
|
+
this.callbacksCount += 1;
|
|
45
|
+
|
|
46
|
+
/* In a test environment with fake timers, a fake `requestAnimationFrame` can be called
|
|
47
|
+
* but there's no guarantee that the animation frame will actually run before the fake
|
|
48
|
+
* timers are teared, which leaves `isScheduled` set, but won't run our `tick()`. */
|
|
49
|
+
const didRAFChange = process.env.NODE_ENV !== 'production' && LAST_RAF !== requestAnimationFrame && (LAST_RAF = requestAnimationFrame, true);
|
|
50
|
+
if (!this.isScheduled || didRAFChange) {
|
|
51
|
+
requestAnimationFrame(this.tick);
|
|
52
|
+
this.isScheduled = true;
|
|
53
|
+
}
|
|
54
|
+
return id;
|
|
55
|
+
}
|
|
56
|
+
cancel(id) {
|
|
57
|
+
const index = id - this.startId;
|
|
58
|
+
if (index < 0 || index >= this.callbacks.length) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.callbacks[index] = null;
|
|
62
|
+
this.callbacksCount -= 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const scheduler = new Scheduler();
|
|
66
|
+
export class AnimationFrame {
|
|
67
|
+
static create() {
|
|
68
|
+
return new AnimationFrame();
|
|
69
|
+
}
|
|
70
|
+
static request(fn) {
|
|
71
|
+
return scheduler.request(fn);
|
|
72
|
+
}
|
|
73
|
+
static cancel(id) {
|
|
74
|
+
return scheduler.cancel(id);
|
|
75
|
+
}
|
|
76
|
+
currentId = EMPTY;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Executes `fn` after `delay`, clearing any previously scheduled call.
|
|
80
|
+
*/
|
|
81
|
+
request(fn) {
|
|
82
|
+
this.cancel();
|
|
83
|
+
this.currentId = scheduler.request(() => {
|
|
84
|
+
this.currentId = EMPTY;
|
|
85
|
+
fn();
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
cancel = () => {
|
|
89
|
+
if (this.currentId !== EMPTY) {
|
|
90
|
+
scheduler.cancel(this.currentId);
|
|
91
|
+
this.currentId = EMPTY;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
disposeEffect = () => {
|
|
95
|
+
return this.cancel;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A `requestAnimationFrame` with automatic cleanup and guard.
|
|
101
|
+
*/
|
|
102
|
+
export function useAnimationFrame() {
|
|
103
|
+
const timeout = useRefWithInit(AnimationFrame.create).current;
|
|
104
|
+
useOnMount(timeout.disposeEffect);
|
|
105
|
+
return timeout;
|
|
106
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface UseControlledProps<T = unknown> {
|
|
2
|
+
/**
|
|
3
|
+
* Holds the component value when it's controlled.
|
|
4
|
+
*/
|
|
5
|
+
controlled: T | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* The default value when uncontrolled.
|
|
8
|
+
*/
|
|
9
|
+
default: T | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* The component name displayed in warnings.
|
|
12
|
+
*/
|
|
13
|
+
name: string;
|
|
14
|
+
/**
|
|
15
|
+
* The name of the state variable displayed in warnings.
|
|
16
|
+
*/
|
|
17
|
+
state?: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
export declare function useControlled<T = unknown>({
|
|
20
|
+
controlled,
|
|
21
|
+
default: defaultProp,
|
|
22
|
+
name,
|
|
23
|
+
state
|
|
24
|
+
}: UseControlledProps<T>): [T, (newValue: T | ((prevValue: T) => T)) => void];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- process.env never changes, dependency arrays are intentionally ignored
|
|
4
|
+
/* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
export function useControlled({
|
|
7
|
+
controlled,
|
|
8
|
+
default: defaultProp,
|
|
9
|
+
name,
|
|
10
|
+
state = 'value'
|
|
11
|
+
}) {
|
|
12
|
+
// isControlled is ignored in the hook dependency lists as it should never change.
|
|
13
|
+
const {
|
|
14
|
+
current: isControlled
|
|
15
|
+
} = React.useRef(controlled !== undefined);
|
|
16
|
+
const [valueState, setValue] = React.useState(defaultProp);
|
|
17
|
+
const value = isControlled ? controlled : valueState;
|
|
18
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
if (isControlled !== (controlled !== undefined)) {
|
|
21
|
+
console.error([`Tale UI: A component is changing the ${isControlled ? '' : 'un'}controlled ${state} state of ${name} to be ${isControlled ? 'un' : ''}controlled.`, 'Elements should not switch from uncontrolled to controlled (or vice versa).', `Decide between using a controlled or uncontrolled ${name} ` + 'element for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n'));
|
|
22
|
+
}
|
|
23
|
+
}, [state, name, controlled]);
|
|
24
|
+
const {
|
|
25
|
+
current: defaultValue
|
|
26
|
+
} = React.useRef(defaultProp);
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is for more details.
|
|
29
|
+
if (!isControlled && JSON.stringify(defaultValue) !== JSON.stringify(defaultProp)) {
|
|
30
|
+
console.error([`Tale UI: A component is changing the default ${state} state of an uncontrolled ${name} after being initialized. ` + `To suppress this warning opt to use a controlled ${name}.`].join('\n'));
|
|
31
|
+
}
|
|
32
|
+
}, [JSON.stringify(defaultProp)]);
|
|
33
|
+
}
|
|
34
|
+
const setValueIfUncontrolled = React.useCallback(newValue => {
|
|
35
|
+
if (!isControlled) {
|
|
36
|
+
setValue(newValue);
|
|
37
|
+
}
|
|
38
|
+
}, []);
|
|
39
|
+
return [value, setValueIfUncontrolled];
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export type InteractionType = 'mouse' | 'touch' | 'pen' | 'keyboard' | '';
|
|
3
|
+
/**
|
|
4
|
+
* Provides a cross-browser way to determine the type of the pointer used to click.
|
|
5
|
+
* Safari and Firefox do not provide the PointerEvent to the click handler (they use MouseEvent) yet.
|
|
6
|
+
* Additionally, this implementation detects if the click was triggered by the keyboard.
|
|
7
|
+
*
|
|
8
|
+
* @param handler The function to be called when the button is clicked. The first parameter is the original event and the second parameter is the pointer type.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useEnhancedClickHandler(handler: (event: React.MouseEvent | React.PointerEvent, interactionType: InteractionType) => void): {
|
|
11
|
+
onClick: (event: React.MouseEvent | React.PointerEvent) => void;
|
|
12
|
+
onPointerDown: (event: React.PointerEvent) => void;
|
|
13
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Provides a cross-browser way to determine the type of the pointer used to click.
|
|
6
|
+
* Safari and Firefox do not provide the PointerEvent to the click handler (they use MouseEvent) yet.
|
|
7
|
+
* Additionally, this implementation detects if the click was triggered by the keyboard.
|
|
8
|
+
*
|
|
9
|
+
* @param handler The function to be called when the button is clicked. The first parameter is the original event and the second parameter is the pointer type.
|
|
10
|
+
*/
|
|
11
|
+
export function useEnhancedClickHandler(handler) {
|
|
12
|
+
const lastClickInteractionTypeRef = React.useRef('');
|
|
13
|
+
const handlePointerDown = React.useCallback(event => {
|
|
14
|
+
if (event.defaultPrevented) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
lastClickInteractionTypeRef.current = event.pointerType;
|
|
18
|
+
handler(event, event.pointerType);
|
|
19
|
+
}, [handler]);
|
|
20
|
+
const handleClick = React.useCallback(event => {
|
|
21
|
+
// event.detail has the number of clicks performed on the element. 0 means it was triggered by the keyboard.
|
|
22
|
+
if (event.detail === 0) {
|
|
23
|
+
handler(event, 'keyboard');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if ('pointerType' in event) {
|
|
27
|
+
// Chrome and Edge correctly use PointerEvent
|
|
28
|
+
handler(event, event.pointerType);
|
|
29
|
+
} else {
|
|
30
|
+
handler(event, lastClickInteractionTypeRef.current);
|
|
31
|
+
}
|
|
32
|
+
lastClickInteractionTypeRef.current = '';
|
|
33
|
+
}, [handler]);
|
|
34
|
+
return {
|
|
35
|
+
onClick: handleClick,
|
|
36
|
+
onPointerDown: handlePointerDown
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns a function that forces a rerender.
|
|
7
|
+
*/
|
|
8
|
+
export function useForcedRerendering() {
|
|
9
|
+
const [, setState] = React.useState({});
|
|
10
|
+
return React.useCallback(() => {
|
|
11
|
+
setState({});
|
|
12
|
+
}, []);
|
|
13
|
+
}
|
package/esm/useId.d.ts
ADDED
package/esm/useId.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { SafeReact } from "./safeReact.js";
|
|
5
|
+
let globalId = 0;
|
|
6
|
+
|
|
7
|
+
// TODO React 17: Remove `useGlobalId` once React 17 support is removed
|
|
8
|
+
function useGlobalId(idOverride, prefix = 'tale-ui') {
|
|
9
|
+
const [defaultId, setDefaultId] = React.useState(idOverride);
|
|
10
|
+
const id = idOverride || defaultId;
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
if (defaultId == null) {
|
|
13
|
+
// Fallback to this default id when possible.
|
|
14
|
+
// Use the incrementing value for client-side rendering only.
|
|
15
|
+
// We can't use it server-side.
|
|
16
|
+
// If you want to use random values please consider the Birthday Problem: https://en.wikipedia.org/wiki/Birthday_problem
|
|
17
|
+
globalId += 1;
|
|
18
|
+
setDefaultId(`${prefix}-${globalId}`);
|
|
19
|
+
}
|
|
20
|
+
}, [defaultId, prefix]);
|
|
21
|
+
return id;
|
|
22
|
+
}
|
|
23
|
+
const maybeReactUseId = SafeReact.useId;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @example <div id={useId()} />
|
|
28
|
+
* @param idOverride
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
export function useId(idOverride, prefix) {
|
|
32
|
+
// React.useId() is only available from React 17.0.0.
|
|
33
|
+
if (maybeReactUseId !== undefined) {
|
|
34
|
+
const reactId = maybeReactUseId();
|
|
35
|
+
return idOverride ?? (prefix ? `${prefix}-${reactId}` : reactId);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
|
|
39
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks -- `React.useId` is invariant at runtime.
|
|
40
|
+
return useGlobalId(idOverride, prefix);
|
|
41
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Timeout } from "./useTimeout.js";
|
|
2
|
+
export declare class Interval extends Timeout {
|
|
3
|
+
static create(): Interval;
|
|
4
|
+
/**
|
|
5
|
+
* Executes `fn` at `delay` interval, clearing any previously scheduled call.
|
|
6
|
+
*/
|
|
7
|
+
start(delay: number, fn: Function): void;
|
|
8
|
+
clear: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A `setInterval` with automatic cleanup and guard.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useInterval(): Interval;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useRefWithInit } from "./useRefWithInit.js";
|
|
4
|
+
import { useOnMount } from "./useOnMount.js";
|
|
5
|
+
import { Timeout } from "./useTimeout.js";
|
|
6
|
+
const EMPTY = 0;
|
|
7
|
+
export class Interval extends Timeout {
|
|
8
|
+
static create() {
|
|
9
|
+
return new Interval();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Executes `fn` at `delay` interval, clearing any previously scheduled call.
|
|
14
|
+
*/
|
|
15
|
+
start(delay, fn) {
|
|
16
|
+
this.clear();
|
|
17
|
+
this.currentId = setInterval(() => {
|
|
18
|
+
fn();
|
|
19
|
+
}, delay);
|
|
20
|
+
}
|
|
21
|
+
clear = () => {
|
|
22
|
+
if (this.currentId !== EMPTY) {
|
|
23
|
+
clearInterval(this.currentId);
|
|
24
|
+
this.currentId = EMPTY;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A `setInterval` with automatic cleanup and guard.
|
|
31
|
+
*/
|
|
32
|
+
export function useInterval() {
|
|
33
|
+
const timeout = useRefWithInit(Interval.create).current;
|
|
34
|
+
useOnMount(timeout.disposeEffect);
|
|
35
|
+
return timeout;
|
|
36
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
type Empty = null | undefined;
|
|
3
|
+
type InputRef<I> = React.Ref<I> | Empty;
|
|
4
|
+
type Result<I> = React.RefCallback<I> | null;
|
|
5
|
+
/**
|
|
6
|
+
* Merges refs into a single memoized callback ref or `null`.
|
|
7
|
+
* This makes sure multiple refs are updated together and have the same value.
|
|
8
|
+
*
|
|
9
|
+
* This function accepts up to four refs. If you need to merge more, or have an unspecified number of refs to merge,
|
|
10
|
+
* use `useMergedRefsN` instead.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useMergedRefs<I>(a: InputRef<I>, b: InputRef<I>): Result<I>;
|
|
13
|
+
export declare function useMergedRefs<I>(a: InputRef<I>, b: InputRef<I>, c: InputRef<I>): Result<I>;
|
|
14
|
+
export declare function useMergedRefs<I>(a: InputRef<I>, b: InputRef<I>, c: InputRef<I>, d: InputRef<I>): Result<I>;
|
|
15
|
+
/**
|
|
16
|
+
* Merges an array of refs into a single memoized callback ref or `null`.
|
|
17
|
+
*
|
|
18
|
+
* If you need to merge a fixed number (up to four) of refs, use `useMergedRefs` instead for better performance.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useMergedRefsN<I>(refs: InputRef<I>[]): Result<I>;
|
|
21
|
+
export {};
|