@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,108 @@
1
+ import { useRefWithInit } from "./useRefWithInit.js";
2
+
3
+ /**
4
+ * Merges refs into a single memoized callback ref or `null`.
5
+ * This makes sure multiple refs are updated together and have the same value.
6
+ *
7
+ * This function accepts up to four refs. If you need to merge more, or have an unspecified number of refs to merge,
8
+ * use `useMergedRefsN` instead.
9
+ */
10
+
11
+ export function useMergedRefs(a, b, c, d) {
12
+ const forkRef = useRefWithInit(createForkRef).current;
13
+ if (didChange(forkRef, a, b, c, d)) {
14
+ update(forkRef, [a, b, c, d]);
15
+ }
16
+ return forkRef.callback;
17
+ }
18
+
19
+ /**
20
+ * Merges an array of refs into a single memoized callback ref or `null`.
21
+ *
22
+ * If you need to merge a fixed number (up to four) of refs, use `useMergedRefs` instead for better performance.
23
+ */
24
+ export function useMergedRefsN(refs) {
25
+ const forkRef = useRefWithInit(createForkRef).current;
26
+ if (didChangeN(forkRef, refs)) {
27
+ update(forkRef, refs);
28
+ }
29
+ return forkRef.callback;
30
+ }
31
+ function createForkRef() {
32
+ return {
33
+ callback: null,
34
+ cleanup: null,
35
+ refs: []
36
+ };
37
+ }
38
+ function didChange(forkRef, a, b, c, d) {
39
+ // prettier-ignore
40
+ return forkRef.refs[0] !== a || forkRef.refs[1] !== b || forkRef.refs[2] !== c || forkRef.refs[3] !== d;
41
+ }
42
+ function didChangeN(forkRef, newRefs) {
43
+ return forkRef.refs.length !== newRefs.length || forkRef.refs.some((ref, index) => ref !== newRefs[index]);
44
+ }
45
+ function update(forkRef, refs) {
46
+ forkRef.refs = refs;
47
+ if (refs.every(ref => ref == null)) {
48
+ forkRef.callback = null;
49
+ return;
50
+ }
51
+ forkRef.callback = instance => {
52
+ if (forkRef.cleanup) {
53
+ forkRef.cleanup();
54
+ forkRef.cleanup = null;
55
+ }
56
+ if (instance != null) {
57
+ const cleanupCallbacks = Array(refs.length).fill(null);
58
+ for (let i = 0; i < refs.length; i += 1) {
59
+ const ref = refs[i];
60
+ if (ref == null) {
61
+ continue;
62
+ }
63
+ switch (typeof ref) {
64
+ case 'function':
65
+ {
66
+ const refCleanup = ref(instance);
67
+ if (typeof refCleanup === 'function') {
68
+ cleanupCallbacks[i] = refCleanup;
69
+ }
70
+ break;
71
+ }
72
+ case 'object':
73
+ {
74
+ ref.current = instance;
75
+ break;
76
+ }
77
+ default:
78
+ }
79
+ }
80
+ forkRef.cleanup = () => {
81
+ for (let i = 0; i < refs.length; i += 1) {
82
+ const ref = refs[i];
83
+ if (ref == null) {
84
+ continue;
85
+ }
86
+ switch (typeof ref) {
87
+ case 'function':
88
+ {
89
+ const cleanupCallback = cleanupCallbacks[i];
90
+ if (typeof cleanupCallback === 'function') {
91
+ cleanupCallback();
92
+ } else {
93
+ ref(null);
94
+ }
95
+ break;
96
+ }
97
+ case 'object':
98
+ {
99
+ ref.current = null;
100
+ break;
101
+ }
102
+ default:
103
+ }
104
+ }
105
+ };
106
+ }
107
+ };
108
+ }
@@ -0,0 +1 @@
1
+ export declare function useOnFirstRender(fn: Function): void;
@@ -0,0 +1,10 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ export function useOnFirstRender(fn) {
5
+ const ref = React.useRef(true);
6
+ if (ref.current) {
7
+ ref.current = false;
8
+ fn();
9
+ }
10
+ }
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * A React.useEffect equivalent that runs once, when the component is mounted.
4
+ */
5
+ export declare function useOnMount(fn: React.EffectCallback): void;
@@ -0,0 +1,14 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ const EMPTY = [];
5
+
6
+ /**
7
+ * A React.useEffect equivalent that runs once, when the component is mounted.
8
+ */
9
+ export function useOnMount(fn) {
10
+ // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- no need to put `fn` in the dependency array
11
+ /* eslint-disable react-hooks/exhaustive-deps */
12
+ React.useEffect(fn, EMPTY);
13
+ /* eslint-enable react-hooks/exhaustive-deps */
14
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Returns a previous value of its argument.
3
+ * @param value Current value.
4
+ * @returns Previous value, or null if there is no previous value.
5
+ */
6
+ export declare function usePreviousValue<T>(value: T): T | null;
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+
5
+ /**
6
+ * Returns a previous value of its argument.
7
+ * @param value Current value.
8
+ * @returns Previous value, or null if there is no previous value.
9
+ */
10
+ export function usePreviousValue(value) {
11
+ const [state, setState] = React.useState({
12
+ current: value,
13
+ previous: null
14
+ });
15
+ if (value !== state.current) {
16
+ setState({
17
+ current: value,
18
+ previous: state.current
19
+ });
20
+ }
21
+ return state.previous;
22
+ }
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * A React.useRef() that is initialized with a function. Note that it accepts an optional
4
+ * initialization argument, so the initialization function doesn't need to be an inline closure.
5
+ *
6
+ * @usage
7
+ * const ref = useRefWithInit(sortColumns, columns)
8
+ */
9
+ export declare function useRefWithInit<T>(init: () => T): React.RefObject<T>;
10
+ export declare function useRefWithInit<T, U>(init: (arg: U) => T, initArg: U): React.RefObject<T>;
@@ -0,0 +1,20 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ const UNINITIALIZED = {};
5
+
6
+ /**
7
+ * A React.useRef() that is initialized with a function. Note that it accepts an optional
8
+ * initialization argument, so the initialization function doesn't need to be an inline closure.
9
+ *
10
+ * @usage
11
+ * const ref = useRefWithInit(sortColumns, columns)
12
+ */
13
+
14
+ export function useRefWithInit(init, initArg) {
15
+ const ref = React.useRef(UNINITIALIZED);
16
+ if (ref.current === UNINITIALIZED) {
17
+ ref.current = init(initArg);
18
+ }
19
+ return ref;
20
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Locks the scroll of the document when enabled.
3
+ *
4
+ * @param enabled - Whether to enable the scroll lock.
5
+ * @param referenceElement - Element to use as a reference for lock calculations.
6
+ */
7
+ export declare function useScrollLock(enabled?: boolean, referenceElement?: Element | null): void;
@@ -0,0 +1,244 @@
1
+ 'use client';
2
+
3
+ import { isOverflowElement } from '@floating-ui/utils/dom';
4
+ import { isIOS, isWebKit } from "./detectBrowser.js";
5
+ import { ownerDocument, ownerWindow } from "./owner.js";
6
+ import { useIsoLayoutEffect } from "./useIsoLayoutEffect.js";
7
+ import { Timeout } from "./useTimeout.js";
8
+ import { AnimationFrame } from "./useAnimationFrame.js";
9
+ import { NOOP } from "./empty.js";
10
+ let originalHtmlStyles = {};
11
+ let originalBodyStyles = {};
12
+ let originalHtmlScrollBehavior = '';
13
+ function hasInsetScrollbars(referenceElement) {
14
+ if (typeof document === 'undefined') {
15
+ return false;
16
+ }
17
+ const doc = ownerDocument(referenceElement);
18
+ const win = ownerWindow(doc);
19
+ return win.innerWidth - doc.documentElement.clientWidth > 0;
20
+ }
21
+ function supportsStableScrollbarGutter(referenceElement) {
22
+ const supported = typeof CSS !== 'undefined' && CSS.supports && CSS.supports('scrollbar-gutter', 'stable');
23
+ if (!supported || typeof document === 'undefined') {
24
+ return false;
25
+ }
26
+ const doc = ownerDocument(referenceElement);
27
+ const html = doc.documentElement;
28
+ const body = doc.body;
29
+ const scrollContainer = isOverflowElement(html) ? html : body;
30
+ const originalScrollContainerOverflowY = scrollContainer.style.overflowY;
31
+ const originalHtmlStyleGutter = html.style.scrollbarGutter;
32
+ html.style.scrollbarGutter = 'stable';
33
+ scrollContainer.style.overflowY = 'scroll';
34
+ const before = scrollContainer.offsetWidth;
35
+ scrollContainer.style.overflowY = 'hidden';
36
+ const after = scrollContainer.offsetWidth;
37
+ scrollContainer.style.overflowY = originalScrollContainerOverflowY;
38
+ html.style.scrollbarGutter = originalHtmlStyleGutter;
39
+ return before === after;
40
+ }
41
+ function preventScrollOverlayScrollbars(referenceElement) {
42
+ const doc = ownerDocument(referenceElement);
43
+ const html = doc.documentElement;
44
+ const body = doc.body;
45
+
46
+ // If an `overflow` style is present on <html>, we need to lock it, because a lock on <body>
47
+ // won't have any effect.
48
+ // But if <body> has an `overflow` style (like `overflow-x: hidden`), we need to lock it
49
+ // instead, as sticky elements shift otherwise.
50
+ const elementToLock = isOverflowElement(html) ? html : body;
51
+ const originalElementToLockStyles = {
52
+ overflowY: elementToLock.style.overflowY,
53
+ overflowX: elementToLock.style.overflowX
54
+ };
55
+ Object.assign(elementToLock.style, {
56
+ overflowY: 'hidden',
57
+ overflowX: 'hidden'
58
+ });
59
+ return () => {
60
+ Object.assign(elementToLock.style, originalElementToLockStyles);
61
+ };
62
+ }
63
+ function preventScrollInsetScrollbars(referenceElement) {
64
+ const doc = ownerDocument(referenceElement);
65
+ const html = doc.documentElement;
66
+ const body = doc.body;
67
+ const win = ownerWindow(html);
68
+ let scrollTop = 0;
69
+ let scrollLeft = 0;
70
+ let updateGutterOnly = false;
71
+ const resizeFrame = AnimationFrame.create();
72
+
73
+ // Pinch-zoom in Safari causes a shift. Just don't lock scroll if there's any pinch-zoom.
74
+ if (isWebKit && (win.visualViewport?.scale ?? 1) !== 1) {
75
+ return () => {};
76
+ }
77
+ function lockScroll() {
78
+ /* DOM reads: */
79
+
80
+ const htmlStyles = win.getComputedStyle(html);
81
+ const bodyStyles = win.getComputedStyle(body);
82
+ const htmlScrollbarGutterValue = htmlStyles.scrollbarGutter || '';
83
+ const hasBothEdges = htmlScrollbarGutterValue.includes('both-edges');
84
+ const scrollbarGutterValue = hasBothEdges ? 'stable both-edges' : 'stable';
85
+ scrollTop = html.scrollTop;
86
+ scrollLeft = html.scrollLeft;
87
+ originalHtmlStyles = {
88
+ scrollbarGutter: html.style.scrollbarGutter,
89
+ overflowY: html.style.overflowY,
90
+ overflowX: html.style.overflowX
91
+ };
92
+ originalHtmlScrollBehavior = html.style.scrollBehavior;
93
+ originalBodyStyles = {
94
+ position: body.style.position,
95
+ height: body.style.height,
96
+ width: body.style.width,
97
+ boxSizing: body.style.boxSizing,
98
+ overflowY: body.style.overflowY,
99
+ overflowX: body.style.overflowX,
100
+ scrollBehavior: body.style.scrollBehavior
101
+ };
102
+ const isScrollableY = html.scrollHeight > html.clientHeight;
103
+ const isScrollableX = html.scrollWidth > html.clientWidth;
104
+ const hasConstantOverflowY = htmlStyles.overflowY === 'scroll' || bodyStyles.overflowY === 'scroll';
105
+ const hasConstantOverflowX = htmlStyles.overflowX === 'scroll' || bodyStyles.overflowX === 'scroll';
106
+
107
+ // Values can be negative in Firefox
108
+ const scrollbarWidth = Math.max(0, win.innerWidth - body.clientWidth);
109
+ const scrollbarHeight = Math.max(0, win.innerHeight - body.clientHeight);
110
+
111
+ // Avoid shift due to the default <body> margin. This does cause elements to be clipped
112
+ // with whitespace. Warn if <body> has margins?
113
+ const marginY = parseFloat(bodyStyles.marginTop) + parseFloat(bodyStyles.marginBottom);
114
+ const marginX = parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight);
115
+ const elementToLock = isOverflowElement(html) ? html : body;
116
+ updateGutterOnly = supportsStableScrollbarGutter(referenceElement);
117
+
118
+ /*
119
+ * DOM writes:
120
+ * Do not read the DOM past this point!
121
+ */
122
+
123
+ if (updateGutterOnly) {
124
+ html.style.scrollbarGutter = scrollbarGutterValue;
125
+ elementToLock.style.overflowY = 'hidden';
126
+ elementToLock.style.overflowX = 'hidden';
127
+ return;
128
+ }
129
+ Object.assign(html.style, {
130
+ scrollbarGutter: scrollbarGutterValue,
131
+ overflowY: 'hidden',
132
+ overflowX: 'hidden'
133
+ });
134
+ if (isScrollableY || hasConstantOverflowY) {
135
+ html.style.overflowY = 'scroll';
136
+ }
137
+ if (isScrollableX || hasConstantOverflowX) {
138
+ html.style.overflowX = 'scroll';
139
+ }
140
+ Object.assign(body.style, {
141
+ position: 'relative',
142
+ height: marginY || scrollbarHeight ? `calc(100dvh - ${marginY + scrollbarHeight}px)` : '100dvh',
143
+ width: marginX || scrollbarWidth ? `calc(100vw - ${marginX + scrollbarWidth}px)` : '100vw',
144
+ boxSizing: 'border-box',
145
+ overflow: 'hidden',
146
+ scrollBehavior: 'unset'
147
+ });
148
+ body.scrollTop = scrollTop;
149
+ body.scrollLeft = scrollLeft;
150
+ html.setAttribute('data-tale-ui-scroll-locked', '');
151
+ html.style.scrollBehavior = 'unset';
152
+ }
153
+ function cleanup() {
154
+ Object.assign(html.style, originalHtmlStyles);
155
+ Object.assign(body.style, originalBodyStyles);
156
+ if (!updateGutterOnly) {
157
+ html.scrollTop = scrollTop;
158
+ html.scrollLeft = scrollLeft;
159
+ html.removeAttribute('data-tale-ui-scroll-locked');
160
+ html.style.scrollBehavior = originalHtmlScrollBehavior;
161
+ }
162
+ }
163
+ function handleResize() {
164
+ cleanup();
165
+ resizeFrame.request(lockScroll);
166
+ }
167
+ lockScroll();
168
+ win.addEventListener('resize', handleResize);
169
+ return () => {
170
+ resizeFrame.cancel();
171
+ cleanup();
172
+ // Sometimes this cleanup can be run after test teardown
173
+ // because it is called in a `setTimeout(fn, 0)`,
174
+ // in which case `removeEventListener` wouldn't be available,
175
+ // so we check for it to avoid test failures.
176
+ if (typeof win.removeEventListener === 'function') {
177
+ win.removeEventListener('resize', handleResize);
178
+ }
179
+ };
180
+ }
181
+ class ScrollLocker {
182
+ lockCount = 0;
183
+ restore = null;
184
+ timeoutLock = Timeout.create();
185
+ timeoutUnlock = Timeout.create();
186
+ acquire(referenceElement) {
187
+ this.lockCount += 1;
188
+ if (this.lockCount === 1 && this.restore === null) {
189
+ this.timeoutLock.start(0, () => this.lock(referenceElement));
190
+ }
191
+ return this.release;
192
+ }
193
+ release = () => {
194
+ this.lockCount -= 1;
195
+ if (this.lockCount === 0 && this.restore) {
196
+ this.timeoutUnlock.start(0, this.unlock);
197
+ }
198
+ };
199
+ unlock = () => {
200
+ if (this.lockCount === 0 && this.restore) {
201
+ this.restore?.();
202
+ this.restore = null;
203
+ }
204
+ };
205
+ lock(referenceElement) {
206
+ if (this.lockCount === 0 || this.restore !== null) {
207
+ return;
208
+ }
209
+ const doc = ownerDocument(referenceElement);
210
+ const html = doc.documentElement;
211
+ const htmlOverflowY = ownerWindow(html).getComputedStyle(html).overflowY;
212
+
213
+ // If the site author already hid overflow on <html>, respect it and bail out.
214
+ if (htmlOverflowY === 'hidden' || htmlOverflowY === 'clip') {
215
+ this.restore = NOOP;
216
+ return;
217
+ }
218
+ const hasOverlayScrollbars = isIOS || !hasInsetScrollbars(referenceElement);
219
+
220
+ // On iOS, scroll locking does not work if the navbar is collapsed. Due to numerous
221
+ // side effects and bugs that arise on iOS, it must be researched extensively before
222
+ // being enabled to ensure it doesn't cause the following issues:
223
+ // - Textboxes must scroll into view when focused, nor cause a glitchy scroll animation.
224
+ // - The navbar must not force itself into view and cause layout shift.
225
+ // - Scroll containers must not flicker upon closing a popup when it has an exit animation.
226
+ this.restore = hasOverlayScrollbars ? preventScrollOverlayScrollbars(referenceElement) : preventScrollInsetScrollbars(referenceElement);
227
+ }
228
+ }
229
+ const SCROLL_LOCKER = new ScrollLocker();
230
+
231
+ /**
232
+ * Locks the scroll of the document when enabled.
233
+ *
234
+ * @param enabled - Whether to enable the scroll lock.
235
+ * @param referenceElement - Element to use as a reference for lock calculations.
236
+ */
237
+ export function useScrollLock(enabled = true, referenceElement = null) {
238
+ useIsoLayoutEffect(() => {
239
+ if (!enabled) {
240
+ return undefined;
241
+ }
242
+ return SCROLL_LOCKER.acquire(referenceElement);
243
+ }, [enabled, referenceElement]);
244
+ }
@@ -0,0 +1,13 @@
1
+ type Callback = (...args: any[]) => any;
2
+ /**
3
+ * Stabilizes the function passed so it's always the same between renders.
4
+ *
5
+ * The function becomes non-reactive to any values it captures.
6
+ * It can safely be passed as a dependency of `React.useMemo` and `React.useEffect` without re-triggering them if its captured values change.
7
+ *
8
+ * The function must only be called inside effects and event handlers, never during render (which throws an error).
9
+ *
10
+ * This hook is a more permissive version of React 19.2's `React.useEffectEvent` in that it can be passed through contexts and called in event handler props, not just effects.
11
+ */
12
+ export declare function useStableCallback<T extends Callback>(callback: T | undefined): T;
13
+ export {};
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { useRefWithInit } from "./useRefWithInit.js";
5
+
6
+ // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
7
+ const useInsertionEffect = React[`useInsertionEffect${Math.random().toFixed(1)}`.slice(0, -3)];
8
+ const useSafeInsertionEffect =
9
+ // React 17 doesn't have useInsertionEffect.
10
+ useInsertionEffect &&
11
+ // Preact replaces useInsertionEffect with useLayoutEffect and fires too late.
12
+ useInsertionEffect !== React.useLayoutEffect ? useInsertionEffect : fn => fn();
13
+ /**
14
+ * Stabilizes the function passed so it's always the same between renders.
15
+ *
16
+ * The function becomes non-reactive to any values it captures.
17
+ * It can safely be passed as a dependency of `React.useMemo` and `React.useEffect` without re-triggering them if its captured values change.
18
+ *
19
+ * The function must only be called inside effects and event handlers, never during render (which throws an error).
20
+ *
21
+ * This hook is a more permissive version of React 19.2's `React.useEffectEvent` in that it can be passed through contexts and called in event handler props, not just effects.
22
+ */
23
+ export function useStableCallback(callback) {
24
+ const stable = useRefWithInit(createStableCallback).current;
25
+ stable.next = callback;
26
+ useSafeInsertionEffect(stable.effect);
27
+ return stable.trampoline;
28
+ }
29
+ function createStableCallback() {
30
+ const stable = {
31
+ next: undefined,
32
+ callback: assertNotCalled,
33
+ trampoline: (...args) => stable.callback?.(...args),
34
+ effect: () => {
35
+ stable.callback = stable.next;
36
+ }
37
+ };
38
+ return stable;
39
+ }
40
+ function assertNotCalled() {
41
+ if (process.env.NODE_ENV !== 'production') {
42
+ throw /* minify-error-disabled */new Error('Tale UI: Cannot call an event handler while rendering.');
43
+ }
44
+ }
@@ -0,0 +1,17 @@
1
+ type TimeoutId = number;
2
+ export declare class Timeout {
3
+ static create(): Timeout;
4
+ currentId: TimeoutId;
5
+ /**
6
+ * Executes `fn` after `delay`, clearing any previously scheduled call.
7
+ */
8
+ start(delay: number, fn: Function): void;
9
+ isStarted(): boolean;
10
+ clear: () => void;
11
+ disposeEffect: () => () => void;
12
+ }
13
+ /**
14
+ * A `setTimeout` with automatic cleanup and guard.
15
+ */
16
+ export declare function useTimeout(): Timeout;
17
+ export {};
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+ import { useRefWithInit } from "./useRefWithInit.js";
4
+ import { useOnMount } from "./useOnMount.js";
5
+ const EMPTY = 0;
6
+ export class Timeout {
7
+ static create() {
8
+ return new Timeout();
9
+ }
10
+ currentId = EMPTY;
11
+
12
+ /**
13
+ * Executes `fn` after `delay`, clearing any previously scheduled call.
14
+ */
15
+ start(delay, fn) {
16
+ this.clear();
17
+ this.currentId = setTimeout(() => {
18
+ this.currentId = EMPTY;
19
+ fn();
20
+ }, delay); /* Node.js types are enabled in development */
21
+ }
22
+ isStarted() {
23
+ return this.currentId !== EMPTY;
24
+ }
25
+ clear = () => {
26
+ if (this.currentId !== EMPTY) {
27
+ clearTimeout(this.currentId);
28
+ this.currentId = EMPTY;
29
+ }
30
+ };
31
+ disposeEffect = () => {
32
+ return this.clear;
33
+ };
34
+ }
35
+
36
+ /**
37
+ * A `setTimeout` with automatic cleanup and guard.
38
+ */
39
+ export function useTimeout() {
40
+ const timeout = useRefWithInit(Timeout.create).current;
41
+ useOnMount(timeout.disposeEffect);
42
+ return timeout;
43
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Untracks the provided value by turning it into a ref to remove its reactivity.
3
+ *
4
+ * Used to access the passed value inside `React.useEffect` without causing the effect to re-run when the value changes.
5
+ */
6
+ export declare function useValueAsRef<T>(value: T): {
7
+ current: T;
8
+ next: T;
9
+ effect: () => void;
10
+ };
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+ import { useIsoLayoutEffect } from "./useIsoLayoutEffect.js";
4
+ import { useRefWithInit } from "./useRefWithInit.js";
5
+
6
+ /**
7
+ * Untracks the provided value by turning it into a ref to remove its reactivity.
8
+ *
9
+ * Used to access the passed value inside `React.useEffect` without causing the effect to re-run when the value changes.
10
+ */
11
+ export function useValueAsRef(value) {
12
+ const latest = useRefWithInit(createLatestRef, value).current;
13
+ latest.next = value;
14
+
15
+ // eslint-disable-next-line react-hooks/exhaustive-deps
16
+ useIsoLayoutEffect(latest.effect);
17
+ return latest;
18
+ }
19
+ function createLatestRef(value) {
20
+ const latest = {
21
+ current: value,
22
+ next: value,
23
+ effect: () => {
24
+ latest.current = latest.next;
25
+ }
26
+ };
27
+ return latest;
28
+ }
@@ -0,0 +1,3 @@
1
+ import * as React from 'react';
2
+ export declare const visuallyHidden: React.CSSProperties;
3
+ export declare const visuallyHiddenInput: React.CSSProperties;
@@ -0,0 +1,20 @@
1
+ const visuallyHiddenBase = {
2
+ clipPath: 'inset(50%)',
3
+ overflow: 'hidden',
4
+ whiteSpace: 'nowrap',
5
+ border: 0,
6
+ padding: 0,
7
+ width: 1,
8
+ height: 1,
9
+ margin: -1
10
+ };
11
+ export const visuallyHidden = {
12
+ ...visuallyHiddenBase,
13
+ position: 'fixed',
14
+ top: 0,
15
+ left: 0
16
+ };
17
+ export const visuallyHiddenInput = {
18
+ ...visuallyHiddenBase,
19
+ position: 'absolute'
20
+ };
package/esm/warn.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function warn(...messages: string[]): void;
package/esm/warn.js ADDED
@@ -0,0 +1,13 @@
1
+ let set;
2
+ if (process.env.NODE_ENV !== 'production') {
3
+ set = new Set();
4
+ }
5
+ export function warn(...messages) {
6
+ if (process.env.NODE_ENV !== 'production') {
7
+ const messageKey = messages.join(' ');
8
+ if (!set.has(messageKey)) {
9
+ set.add(messageKey);
10
+ console.warn(`Tale UI: ${messageKey}`);
11
+ }
12
+ }
13
+ }
package/fastHooks.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ export type Instance = {
3
+ didInitialize: boolean;
4
+ };
5
+ type HookType = {
6
+ before: (instance: any) => void;
7
+ after: (instance: any) => void;
8
+ };
9
+ export declare function getInstance(): Instance | undefined;
10
+ export declare function setInstance(instance: Instance | undefined): void;
11
+ export declare function register(hook: HookType): void;
12
+ export declare function fastComponent<P extends object, E extends HTMLElement, R extends React.ReactNode>(fn: (props: P) => R): typeof fn;
13
+ export declare function fastComponentRef<P extends object, E extends Element, R extends React.ReactNode>(fn: (props: React.PropsWithoutRef<P>, forwardedRef: React.Ref<E>) => R): React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<E>>;
14
+ export {};