@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.
Files changed (165) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +3 -0
  4. package/detectBrowser.d.ts +8 -0
  5. package/detectBrowser.js +64 -0
  6. package/empty.d.ts +3 -0
  7. package/empty.js +10 -0
  8. package/error.d.ts +2 -0
  9. package/error.js +23 -0
  10. package/esm/detectBrowser.d.ts +8 -0
  11. package/esm/detectBrowser.js +58 -0
  12. package/esm/empty.d.ts +3 -0
  13. package/esm/empty.js +3 -0
  14. package/esm/error.d.ts +2 -0
  15. package/esm/error.js +16 -0
  16. package/esm/fastHooks.d.ts +14 -0
  17. package/esm/fastHooks.js +43 -0
  18. package/esm/fastObjectShallowCompare.d.ts +1 -0
  19. package/esm/fastObjectShallowCompare.js +29 -0
  20. package/esm/formatErrorMessage.d.ts +18 -0
  21. package/esm/formatErrorMessage.js +26 -0
  22. package/esm/generateId.d.ts +1 -0
  23. package/esm/generateId.js +5 -0
  24. package/esm/getReactElementRef.d.ts +5 -0
  25. package/esm/getReactElementRef.js +14 -0
  26. package/esm/inertValue.d.ts +1 -0
  27. package/esm/inertValue.js +8 -0
  28. package/esm/isElementDisabled.d.ts +1 -0
  29. package/esm/isElementDisabled.js +3 -0
  30. package/esm/isMouseWithinBounds.d.ts +1 -0
  31. package/esm/isMouseWithinBounds.js +10 -0
  32. package/esm/mergeObjects.d.ts +1 -0
  33. package/esm/mergeObjects.js +15 -0
  34. package/esm/owner.d.ts +2 -0
  35. package/esm/owner.js +4 -0
  36. package/esm/package.json +1 -0
  37. package/esm/reactVersion.d.ts +3 -0
  38. package/esm/reactVersion.js +5 -0
  39. package/esm/safeReact.d.ts +2 -0
  40. package/esm/safeReact.js +6 -0
  41. package/esm/store/ReactStore.d.ts +91 -0
  42. package/esm/store/ReactStore.js +199 -0
  43. package/esm/store/Store.d.ts +58 -0
  44. package/esm/store/Store.js +111 -0
  45. package/esm/store/StoreInspector.d.ts +41 -0
  46. package/esm/store/StoreInspector.js +537 -0
  47. package/esm/store/createSelector.d.ts +30 -0
  48. package/esm/store/createSelector.js +148 -0
  49. package/esm/store/index.d.ts +5 -0
  50. package/esm/store/index.js +5 -0
  51. package/esm/store/useStore.d.ts +22 -0
  52. package/esm/store/useStore.js +107 -0
  53. package/esm/testUtils.d.ts +17 -0
  54. package/esm/testUtils.js +19 -0
  55. package/esm/useAnimationFrame.d.ts +18 -0
  56. package/esm/useAnimationFrame.js +106 -0
  57. package/esm/useControlled.d.ts +24 -0
  58. package/esm/useControlled.js +40 -0
  59. package/esm/useEnhancedClickHandler.d.ts +13 -0
  60. package/esm/useEnhancedClickHandler.js +38 -0
  61. package/esm/useForcedRerendering.d.ts +4 -0
  62. package/esm/useForcedRerendering.js +13 -0
  63. package/esm/useId.d.ts +7 -0
  64. package/esm/useId.js +41 -0
  65. package/esm/useInterval.d.ts +13 -0
  66. package/esm/useInterval.js +36 -0
  67. package/esm/useIsoLayoutEffect.d.ts +2 -0
  68. package/esm/useIsoLayoutEffect.js +5 -0
  69. package/esm/useMergedRefs.d.ts +21 -0
  70. package/esm/useMergedRefs.js +108 -0
  71. package/esm/useOnFirstRender.d.ts +1 -0
  72. package/esm/useOnFirstRender.js +10 -0
  73. package/esm/useOnMount.d.ts +5 -0
  74. package/esm/useOnMount.js +14 -0
  75. package/esm/usePreviousValue.d.ts +6 -0
  76. package/esm/usePreviousValue.js +22 -0
  77. package/esm/useRefWithInit.d.ts +10 -0
  78. package/esm/useRefWithInit.js +20 -0
  79. package/esm/useScrollLock.d.ts +7 -0
  80. package/esm/useScrollLock.js +244 -0
  81. package/esm/useStableCallback.d.ts +13 -0
  82. package/esm/useStableCallback.js +44 -0
  83. package/esm/useTimeout.d.ts +17 -0
  84. package/esm/useTimeout.js +43 -0
  85. package/esm/useValueAsRef.d.ts +10 -0
  86. package/esm/useValueAsRef.js +28 -0
  87. package/esm/visuallyHidden.d.ts +3 -0
  88. package/esm/visuallyHidden.js +20 -0
  89. package/esm/warn.d.ts +1 -0
  90. package/esm/warn.js +13 -0
  91. package/fastHooks.d.ts +14 -0
  92. package/fastHooks.js +54 -0
  93. package/fastObjectShallowCompare.d.ts +1 -0
  94. package/fastObjectShallowCompare.js +35 -0
  95. package/formatErrorMessage.d.ts +18 -0
  96. package/formatErrorMessage.js +33 -0
  97. package/generateId.d.ts +1 -0
  98. package/generateId.js +11 -0
  99. package/getReactElementRef.d.ts +5 -0
  100. package/getReactElementRef.js +20 -0
  101. package/inertValue.d.ts +1 -0
  102. package/inertValue.js +14 -0
  103. package/isElementDisabled.d.ts +1 -0
  104. package/isElementDisabled.js +9 -0
  105. package/isMouseWithinBounds.d.ts +1 -0
  106. package/isMouseWithinBounds.js +16 -0
  107. package/mergeObjects.d.ts +1 -0
  108. package/mergeObjects.js +21 -0
  109. package/owner.d.ts +2 -0
  110. package/owner.js +16 -0
  111. package/package.json +64 -0
  112. package/reactVersion.d.ts +3 -0
  113. package/reactVersion.js +12 -0
  114. package/safeReact.d.ts +2 -0
  115. package/safeReact.js +12 -0
  116. package/store/ReactStore.d.ts +91 -0
  117. package/store/ReactStore.js +205 -0
  118. package/store/Store.d.ts +58 -0
  119. package/store/Store.js +118 -0
  120. package/store/StoreInspector.d.ts +41 -0
  121. package/store/StoreInspector.js +544 -0
  122. package/store/createSelector.d.ts +30 -0
  123. package/store/createSelector.js +154 -0
  124. package/store/index.d.ts +5 -0
  125. package/store/index.js +60 -0
  126. package/store/useStore.d.ts +22 -0
  127. package/store/useStore.js +115 -0
  128. package/testUtils.d.ts +17 -0
  129. package/testUtils.js +26 -0
  130. package/useAnimationFrame.d.ts +18 -0
  131. package/useAnimationFrame.js +113 -0
  132. package/useControlled.d.ts +24 -0
  133. package/useControlled.js +46 -0
  134. package/useEnhancedClickHandler.d.ts +13 -0
  135. package/useEnhancedClickHandler.js +44 -0
  136. package/useForcedRerendering.d.ts +4 -0
  137. package/useForcedRerendering.js +18 -0
  138. package/useId.d.ts +7 -0
  139. package/useId.js +47 -0
  140. package/useInterval.d.ts +13 -0
  141. package/useInterval.js +43 -0
  142. package/useIsoLayoutEffect.d.ts +2 -0
  143. package/useIsoLayoutEffect.js +11 -0
  144. package/useMergedRefs.d.ts +21 -0
  145. package/useMergedRefs.js +114 -0
  146. package/useOnFirstRender.d.ts +1 -0
  147. package/useOnFirstRender.js +16 -0
  148. package/useOnMount.d.ts +5 -0
  149. package/useOnMount.js +20 -0
  150. package/usePreviousValue.d.ts +6 -0
  151. package/usePreviousValue.js +27 -0
  152. package/useRefWithInit.d.ts +10 -0
  153. package/useRefWithInit.js +26 -0
  154. package/useScrollLock.d.ts +7 -0
  155. package/useScrollLock.js +249 -0
  156. package/useStableCallback.d.ts +13 -0
  157. package/useStableCallback.js +49 -0
  158. package/useTimeout.d.ts +17 -0
  159. package/useTimeout.js +50 -0
  160. package/useValueAsRef.d.ts +10 -0
  161. package/useValueAsRef.js +32 -0
  162. package/visuallyHidden.d.ts +3 -0
  163. package/visuallyHidden.js +26 -0
  164. package/warn.d.ts +1 -0
  165. package/warn.js +19 -0
@@ -0,0 +1,5 @@
1
+ export * from "./createSelector.js";
2
+ export * from "./useStore.js";
3
+ export * from "./Store.js";
4
+ export * from "./ReactStore.js";
5
+ export * from "./StoreInspector.js";
@@ -0,0 +1,5 @@
1
+ export * from "./createSelector.js";
2
+ export * from "./useStore.js";
3
+ export * from "./Store.js";
4
+ export * from "./ReactStore.js";
5
+ export * from "./StoreInspector.js";
@@ -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;
@@ -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,4 @@
1
+ /**
2
+ * Returns a function that forces a rerender.
3
+ */
4
+ export declare function useForcedRerendering(): () => void;
@@ -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
@@ -0,0 +1,7 @@
1
+ /**
2
+ *
3
+ * @example <div id={useId()} />
4
+ * @param idOverride
5
+ * @returns {string}
6
+ */
7
+ export declare function useId(idOverride?: string, prefix?: string): string | undefined;
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,2 @@
1
+ import * as React from 'react';
2
+ export declare const useIsoLayoutEffect: typeof React.useLayoutEffect;
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ const noop = () => {};
5
+ export const useIsoLayoutEffect = typeof document !== 'undefined' ? React.useLayoutEffect : noop;
@@ -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 {};