@ringcentral/spring-base 1.7.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.
Files changed (146) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +61 -0
  3. package/chunks/twMerge.js +409 -0
  4. package/config.js +78 -0
  5. package/contexts.js +71 -0
  6. package/hooks.js +2101 -0
  7. package/index.d.ts +1 -0
  8. package/index.js +16 -0
  9. package/package.json +30 -0
  10. package/src/config.d.ts +40 -0
  11. package/src/contexts/ResponsiveContext/ResponsiveContext.d.ts +7 -0
  12. package/src/contexts/ResponsiveContext/breakpointList.d.ts +3 -0
  13. package/src/contexts/ResponsiveContext/getMatchDetail.d.ts +2 -0
  14. package/src/contexts/ResponsiveContext/index.d.ts +4 -0
  15. package/src/contexts/ResponsiveContext/types.d.ts +24 -0
  16. package/src/contexts/WindowContext.d.ts +9 -0
  17. package/src/contexts/index.d.ts +2 -0
  18. package/src/hooks/index.d.ts +43 -0
  19. package/src/hooks/useA11yKeyEvent/index.d.ts +1 -0
  20. package/src/hooks/useA11yKeyEvent/useA11yKeyEvent.d.ts +23 -0
  21. package/src/hooks/useActive/index.d.ts +1 -0
  22. package/src/hooks/useActive/useActive.d.ts +23 -0
  23. package/src/hooks/useAnnouncer/index.d.ts +1 -0
  24. package/src/hooks/useAnnouncer/useAnnouncer.d.ts +12 -0
  25. package/src/hooks/useAriaHideOutside/index.d.ts +1 -0
  26. package/src/hooks/useAriaHideOutside/useAriaHideOutside.d.ts +12 -0
  27. package/src/hooks/useAudio/index.d.ts +1 -0
  28. package/src/hooks/useAudio/useAudio.d.ts +21 -0
  29. package/src/hooks/useBodyScrollLock/index.d.ts +1 -0
  30. package/src/hooks/useBodyScrollLock/useBodyScrollLock.d.ts +25 -0
  31. package/src/hooks/useChange/index.d.ts +1 -0
  32. package/src/hooks/useChange/useChange.d.ts +12 -0
  33. package/src/hooks/useClickAway/index.d.ts +1 -0
  34. package/src/hooks/useClickAway/useClickAway.d.ts +22 -0
  35. package/src/hooks/useControlled/index.d.ts +1 -0
  36. package/src/hooks/useControlled/useControlled.d.ts +1 -0
  37. package/src/hooks/useDebounce/index.d.ts +1 -0
  38. package/src/hooks/useDebounce/useDebounce.d.ts +8 -0
  39. package/src/hooks/useEventCallback/index.d.ts +1 -0
  40. package/src/hooks/useEventCallback/useEventCallback.d.ts +1 -0
  41. package/src/hooks/useEventListener/index.d.ts +1 -0
  42. package/src/hooks/useEventListener/useEventListener.d.ts +32 -0
  43. package/src/hooks/useEver/index.d.ts +1 -0
  44. package/src/hooks/useEver/useEver.d.ts +13 -0
  45. package/src/hooks/useFocusVisible/index.d.ts +1 -0
  46. package/src/hooks/useFocusVisible/useFocusVisible.d.ts +22 -0
  47. package/src/hooks/useFontLoaded/index.d.ts +1 -0
  48. package/src/hooks/useFontLoaded/useFontLoaded.d.ts +13 -0
  49. package/src/hooks/useForceUpdate/index.d.ts +1 -0
  50. package/src/hooks/useForceUpdate/useForceUpdate.d.ts +5 -0
  51. package/src/hooks/useForkRef/index.d.ts +1 -0
  52. package/src/hooks/useForkRef/useForkRef.d.ts +19 -0
  53. package/src/hooks/useGlobalListener/createGlobalListener.d.ts +55 -0
  54. package/src/hooks/useGlobalListener/index.d.ts +2 -0
  55. package/src/hooks/useGlobalListener/useGlobalListener.d.ts +14 -0
  56. package/src/hooks/useHiddenTabindex/index.d.ts +1 -0
  57. package/src/hooks/useHiddenTabindex/useHiddenTabindex.d.ts +9 -0
  58. package/src/hooks/useHover/index.d.ts +2 -0
  59. package/src/hooks/useHover/useHover.d.ts +17 -0
  60. package/src/hooks/useHover/useHoverDirty.d.ts +8 -0
  61. package/src/hooks/useId/index.d.ts +1 -0
  62. package/src/hooks/useId/useId.d.ts +15 -0
  63. package/src/hooks/useImgLoaded/index.d.ts +1 -0
  64. package/src/hooks/useImgLoaded/useImgLoaded.d.ts +24 -0
  65. package/src/hooks/useInterval/index.d.ts +1 -0
  66. package/src/hooks/useInterval/useInterval.d.ts +43 -0
  67. package/src/hooks/useIsFocusVisible/index.d.ts +2 -0
  68. package/src/hooks/useIsFocusVisible/useIsFocusVisible.d.ts +9 -0
  69. package/src/hooks/useKeyDownOnce/index.d.ts +1 -0
  70. package/src/hooks/useKeyDownOnce/useKeyDownOnce.d.ts +15 -0
  71. package/src/hooks/useKeyboardMoveFocus/index.d.ts +3 -0
  72. package/src/hooks/useKeyboardMoveFocus/moveIndexInTwoDimension.d.ts +18 -0
  73. package/src/hooks/useKeyboardMoveFocus/useKeyboardMoveFocus.d.ts +49 -0
  74. package/src/hooks/useKeyboardMoveFocus/useOnlyOneFocusable.d.ts +17 -0
  75. package/src/hooks/useLongPress/index.d.ts +1 -0
  76. package/src/hooks/useLongPress/useLongPress.d.ts +39 -0
  77. package/src/hooks/useMountState/index.d.ts +1 -0
  78. package/src/hooks/useMountState/useMountState.d.ts +16 -0
  79. package/src/hooks/useOnLayoutReRender/index.d.ts +1 -0
  80. package/src/hooks/useOnLayoutReRender/useOnLayoutReRender.d.ts +5 -0
  81. package/src/hooks/useOnReRender/index.d.ts +1 -0
  82. package/src/hooks/useOnReRender/useOnReRender.d.ts +5 -0
  83. package/src/hooks/useOverflow/index.d.ts +1 -0
  84. package/src/hooks/useOverflow/useOverflow.d.ts +11 -0
  85. package/src/hooks/usePrevious/index.d.ts +1 -0
  86. package/src/hooks/usePrevious/usePrevious.d.ts +7 -0
  87. package/src/hooks/useRefState/index.d.ts +1 -0
  88. package/src/hooks/useRefState/useRefState.d.ts +21 -0
  89. package/src/hooks/useResizeObserver/index.d.ts +1 -0
  90. package/src/hooks/useResizeObserver/useResizeObserver.d.ts +24 -0
  91. package/src/hooks/useResponsiveMatch/index.d.ts +1 -0
  92. package/src/hooks/useResponsiveMatch/useResponsiveMatch.d.ts +7 -0
  93. package/src/hooks/useResultRef/index.d.ts +1 -0
  94. package/src/hooks/useResultRef/useResultRef.d.ts +10 -0
  95. package/src/hooks/useRetry/index.d.ts +1 -0
  96. package/src/hooks/useRetry/useRetry.d.ts +35 -0
  97. package/src/hooks/useSleep/index.d.ts +1 -0
  98. package/src/hooks/useSleep/useSleep.d.ts +22 -0
  99. package/src/hooks/useTabOut/index.d.ts +1 -0
  100. package/src/hooks/useTabOut/useTabOut.d.ts +10 -0
  101. package/src/hooks/useTestRunnerDetection.d.ts +7 -0
  102. package/src/hooks/useThrottle/index.d.ts +1 -0
  103. package/src/hooks/useThrottle/useThrottle.d.ts +8 -0
  104. package/src/hooks/useTimeout/index.d.ts +1 -0
  105. package/src/hooks/useTimeout/useTimeout.d.ts +2 -0
  106. package/src/hooks/useTouchMouseEvent/index.d.ts +1 -0
  107. package/src/hooks/useTouchMouseEvent/useTouchMouseEvent.d.ts +56 -0
  108. package/src/index.d.ts +5 -0
  109. package/src/typings/BaseColor.d.ts +14 -0
  110. package/src/typings/BaseSize.d.ts +15 -0
  111. package/src/typings/ComponentTypes.d.ts +50 -0
  112. package/src/typings/ExtractValue.d.ts +2 -0
  113. package/src/typings/OverridableTypes.d.ts +45 -0
  114. package/src/typings/UnionOmit.d.ts +3 -0
  115. package/src/typings/UnitMap.d.ts +28 -0
  116. package/src/typings/index.d.ts +7 -0
  117. package/src/utils/a11yKeyboardCode.d.ts +8 -0
  118. package/src/utils/ariaHideOutside.d.ts +9 -0
  119. package/src/utils/capitalize.d.ts +1 -0
  120. package/src/utils/checkDefaultPrevented.d.ts +3 -0
  121. package/src/utils/checkElementName.d.ts +2 -0
  122. package/src/utils/checkKeyboardEvent.d.ts +2 -0
  123. package/src/utils/classes/globalStateClasses.d.ts +14 -0
  124. package/src/utils/classes/index.d.ts +2 -0
  125. package/src/utils/classes/useClasses.d.ts +41 -0
  126. package/src/utils/combineProps.d.ts +90 -0
  127. package/src/utils/devlog.d.ts +7 -0
  128. package/src/utils/getRefElement.d.ts +6 -0
  129. package/src/utils/getTitle.d.ts +1 -0
  130. package/src/utils/index.d.ts +25 -0
  131. package/src/utils/isRef.d.ts +2 -0
  132. package/src/utils/isSuiElement.d.ts +1 -0
  133. package/src/utils/isUrl.d.ts +1 -0
  134. package/src/utils/isWebKit154.d.ts +9 -0
  135. package/src/utils/omit.d.ts +2 -0
  136. package/src/utils/outerSize.d.ts +8 -0
  137. package/src/utils/ownerDocument.d.ts +1 -0
  138. package/src/utils/ownerWindow.d.ts +1 -0
  139. package/src/utils/pick.d.ts +2 -0
  140. package/src/utils/preloadImg.d.ts +2 -0
  141. package/src/utils/removeClassName.d.ts +35 -0
  142. package/src/utils/runKeyHandler.d.ts +12 -0
  143. package/src/utils/selectionHandler.d.ts +23 -0
  144. package/src/utils/twMerge.d.ts +4 -0
  145. package/typings.js +1 -0
  146. package/utils.js +333 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 RingCentral, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @ringcentral/spring-base
2
+
3
+ Core utilities and hooks for Spring UI.
4
+
5
+ Note: If you already use `@ringcentral/spring-ui`, these utilities are re-exported there. You can import them directly from `@ringcentral/spring-ui` (recommended), or from `@ringcentral/spring-base` if you only use the base package.
6
+
7
+
8
+ ## Installation
9
+
10
+ Using yarn:
11
+
12
+ ```bash
13
+ yarn add @ringcentral/spring-base
14
+ ```
15
+
16
+ Using npm:
17
+
18
+ ```bash
19
+ npm i @ringcentral/spring-base
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ```ts
25
+ // If your app already installs @ringcentral/spring-ui (recommended)
26
+ import { twMerge } from '@ringcentral/spring-ui';
27
+
28
+ // Or, when using the package directly
29
+ import { twMerge } from '@ringcentral/spring-base';
30
+ ```
31
+
32
+ ## Utilities
33
+
34
+ ### twMerge
35
+ Merge Tailwind classes with Spring UI class groups.
36
+
37
+ ```ts
38
+ import { twMerge } from '@ringcentral/spring-base';
39
+
40
+ twMerge('typography-label', 'typography-detailBold');
41
+ // => 'typography-detailBold'
42
+ ```
43
+
44
+ ### Example Hook
45
+
46
+ ```tsx
47
+ import * as React from 'react';
48
+ import { useDebounce } from '@ringcentral/spring-base';
49
+
50
+ function Search({ onQuery }: { onQuery: (q: string) => void }) {
51
+ const [value, setValue] = React.useState('');
52
+ const debounced = useDebounce(value, 300);
53
+ React.useEffect(() => onQuery(debounced), [debounced, onQuery]);
54
+ return <input value={value} onChange={e => setValue(e.target.value)} />;
55
+ }
56
+ ```
57
+
58
+ ## License
59
+
60
+ MIT
61
+
@@ -0,0 +1,409 @@
1
+ import { unstable_ownerWindow } from '@mui/utils';
2
+ import { isValidElement } from 'react';
3
+ import isPlainObject from 'lodash/isPlainObject';
4
+ import { rcConfiguration } from '../config.js';
5
+ import { extendTailwindMerge } from 'tailwind-merge';
6
+
7
+ const ownerWindow = unstable_ownerWindow;
8
+
9
+ // Copy from https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/overlays/src/ariaHideOutside.ts
10
+ // And support multiple window
11
+ const ARIA_HIDDEN = 'aria-hidden';
12
+ const REF_COUNT_MAP = '__SUI_ARIA_HIDE_OUTSIDE_REF_COUNT_MAP__';
13
+ const getRefCountMap = (window) => {
14
+ // @ts-ignore
15
+ if (!window[REF_COUNT_MAP]) {
16
+ // @ts-ignore
17
+ window[REF_COUNT_MAP] = new WeakMap();
18
+ }
19
+ // @ts-ignore
20
+ return window[REF_COUNT_MAP];
21
+ };
22
+ const OBSERVER_STACK = '__SUI_ARIA_HIDE_OUTSIDE_OBSERVER_STACK__';
23
+ const getObserverStack = (window) => {
24
+ // @ts-ignore
25
+ if (!window[OBSERVER_STACK]) {
26
+ // @ts-ignore
27
+ window[OBSERVER_STACK] = [];
28
+ }
29
+ // @ts-ignore
30
+ return window[OBSERVER_STACK];
31
+ };
32
+ const isHTMLOrSVGElement = (node) => {
33
+ return 'dataset' in node;
34
+ };
35
+ const isElement = (node) => {
36
+ return node.nodeType === Node.ELEMENT_NODE;
37
+ };
38
+ /**
39
+ * Hides all elements in the DOM outside the given targets from screen readers using aria-hidden,
40
+ * and returns a function to revert these changes. In addition, changes to the DOM are watched
41
+ * and new elements outside the targets are automatically hidden.
42
+ * @param targets - The elements that should remain visible.
43
+ * @param root - Nothing will be hidden above this element.
44
+ * @returns - A function to restore all hidden elements.
45
+ */
46
+ const ariaHideOutside = (targets, root = document.body) => {
47
+ const currentWindow = ownerWindow(root);
48
+ if (!currentWindow) {
49
+ // Current JS environment doesn't have a window object, do nothing.
50
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
51
+ return () => { };
52
+ }
53
+ const visibleNodes = new Set(targets);
54
+ const hiddenNodes = new Set();
55
+ // Keeps a ref count of all hidden elements. Added to when hiding an element, and
56
+ // subtracted from when showing it again. When it reaches zero, aria-hidden is removed.
57
+ const refCountMap = getRefCountMap(currentWindow);
58
+ const observerStack = getObserverStack(currentWindow);
59
+ const walker = currentWindow.document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
60
+ acceptNode(node) {
61
+ // If this node is a live announcer, add it to the set of nodes to keep visible.
62
+ if (isHTMLOrSVGElement(node) &&
63
+ node.dataset['liveAnnouncer'] === 'true') {
64
+ visibleNodes.add(node);
65
+ }
66
+ // Skip this node and its children if it is one of the target nodes, or a live announcer.
67
+ // Also skip children of already hidden nodes, as aria-hidden is recursive. An exception is
68
+ // made for elements with role="row" since VoiceOver on iOS has issues hiding elements with role="row".
69
+ // For that case we want to hide the cells inside as well (https://bugs.webkit.org/show_bug.cgi?id=222623).
70
+ if (visibleNodes.has(node) ||
71
+ (node.parentElement &&
72
+ hiddenNodes.has(node.parentElement) &&
73
+ node.parentElement.getAttribute('role') !== 'row')) {
74
+ return NodeFilter.FILTER_REJECT;
75
+ }
76
+ // Skip this node but continue to children if one of the targets is inside the node.
77
+ if (targets.some((target) => node.contains(target))) {
78
+ return NodeFilter.FILTER_SKIP;
79
+ }
80
+ return NodeFilter.FILTER_ACCEPT;
81
+ },
82
+ });
83
+ const hide = (node) => {
84
+ const refCount = refCountMap.get(node) ?? 0;
85
+ // If already aria-hidden, and the ref count is zero, then this element
86
+ // was already hidden and there's nothing for us to do.
87
+ if (node.getAttribute(ARIA_HIDDEN) === 'true' && refCount === 0) {
88
+ return;
89
+ }
90
+ if (refCount === 0) {
91
+ node.setAttribute(ARIA_HIDDEN, 'true');
92
+ }
93
+ hiddenNodes.add(node);
94
+ refCountMap.set(node, refCount + 1);
95
+ };
96
+ // If there is already a MutationObserver listening from a previous call,
97
+ // disconnect it so the new on takes over.
98
+ if (observerStack.length) {
99
+ observerStack[observerStack.length - 1].disconnect();
100
+ }
101
+ let node = walker.nextNode();
102
+ while (node != null) {
103
+ hide(node);
104
+ node = walker.nextNode();
105
+ }
106
+ const observer = new currentWindow.MutationObserver((changes) => {
107
+ for (const change of changes) {
108
+ if (change.type !== 'childList' || change.addedNodes.length === 0) {
109
+ continue;
110
+ }
111
+ // If the parent element of the added nodes is not within one of the targets,
112
+ // and not already inside a hidden node, hide all of the new children.
113
+ if (![...visibleNodes, ...hiddenNodes].some((node) => node.contains(change.target))) {
114
+ for (const node of change.addedNodes) {
115
+ if (isHTMLOrSVGElement(node) &&
116
+ node.dataset['liveAnnouncer'] === 'true') {
117
+ visibleNodes.add(node);
118
+ }
119
+ else if (isElement(node)) {
120
+ hide(node);
121
+ }
122
+ }
123
+ }
124
+ }
125
+ });
126
+ observer.observe(root, { childList: true, subtree: true });
127
+ const observerWrapper = {
128
+ observe() {
129
+ observer.observe(root, { childList: true, subtree: true });
130
+ },
131
+ disconnect() {
132
+ observer.disconnect();
133
+ },
134
+ };
135
+ observerStack.push(observerWrapper);
136
+ return () => {
137
+ observer.disconnect();
138
+ for (const node of hiddenNodes) {
139
+ const count = refCountMap.get(node);
140
+ if (count === 1) {
141
+ node.removeAttribute(ARIA_HIDDEN);
142
+ refCountMap.delete(node);
143
+ }
144
+ else if (typeof count === 'number') {
145
+ refCountMap.set(node, count - 1);
146
+ }
147
+ }
148
+ // Remove this observer from the stack, and start the previous one.
149
+ if (observerWrapper === observerStack[observerStack.length - 1]) {
150
+ observerStack.pop();
151
+ if (observerStack.length) {
152
+ observerStack[observerStack.length - 1].observe();
153
+ }
154
+ }
155
+ else {
156
+ observerStack.splice(observerStack.indexOf(observerWrapper), 1);
157
+ }
158
+ };
159
+ };
160
+
161
+ /**
162
+ * Combine props object together
163
+ *
164
+ * that will group className, classes together
165
+ * function will auto combine together,
166
+ * trigger order is `intoProps.someFn(e) => defaultProps.someFn(e)`
167
+ *
168
+ * @param baseProps base props you want to combine together
169
+ * @param newProps want to combine in to props
170
+ * @param coverPropKeys some prop key you want to cover, not combine when type is function, ex. `ComponentType`
171
+ *
172
+ *
173
+ * @example
174
+ * ```tsx
175
+ * combineProps(
176
+ {
177
+ children: <A />,
178
+ value: '1',
179
+ className: 'a1',
180
+ classes: {
181
+ root: 'root1'
182
+ },
183
+ count: 1,
184
+ size: 'large',
185
+ onClick: fn1
186
+ },
187
+ {
188
+ children: <B />,
189
+ value: '2',
190
+ className: 'a2',
191
+ classes: {
192
+ root: 'root2'
193
+ },
194
+ count: 2,
195
+ size: undefined,
196
+ onClick: fn2
197
+ },
198
+ )
199
+ // output result:
200
+ {
201
+ children: <B />,
202
+ value: '2',
203
+ className: 'a1 a2',
204
+ classes: {
205
+ root: 'root1 root2'
206
+ },
207
+ count: 2,
208
+ size: 'large',
209
+ onClick: (e) => {
210
+ fn2(e);
211
+ fn1(e);
212
+ }
213
+ }
214
+ * ```
215
+ */
216
+ const combineProps = (baseProps, newProps, coverPropKeys) => innerCombineProps({
217
+ baseProps,
218
+ newProps,
219
+ coverPropKeys,
220
+ });
221
+ /**
222
+ * Combine two classes
223
+ *
224
+ * @param baseClasses base classes you want to combine together
225
+ * @param newClasses new classes want to combine in to props
226
+ *
227
+ * @example
228
+ * ```ts
229
+ * combineClasses(
230
+ {
231
+ root: 'a1',
232
+ input: 'a1',
233
+ un1: undefined,
234
+ un2: 'a4',
235
+ },
236
+ {
237
+ root: 'a1',
238
+ input: 'a2',
239
+ un1: 'a3',
240
+ un2: undefined,
241
+ un3: 'a5',
242
+ },
243
+ )
244
+ // output result:
245
+ {
246
+ root: 'a1 a1',
247
+ input: 'a1 a2',
248
+ un1: 'a3',
249
+ un2: 'a4',
250
+ un3: 'a5',
251
+ }
252
+ * ```
253
+ */
254
+ const combineClasses = (baseClasses, newClasses) => innerCombineProps({
255
+ baseProps: baseClasses,
256
+ newProps: newClasses,
257
+ combineAllString: true,
258
+ });
259
+ const innerCombineProps = (params) => {
260
+ const { baseProps, newProps } = params;
261
+ if (!newProps || Object.keys(newProps).length === 0) {
262
+ return baseProps;
263
+ }
264
+ const outputProps = mergeChildProps(params);
265
+ return {
266
+ ...newProps,
267
+ ...outputProps,
268
+ };
269
+ };
270
+ function mergeChildProps({ baseProps, newProps, combineAllString, coverPropKeys, }) {
271
+ const result = Object.keys(baseProps || {}).reduce((output, curr) => {
272
+ const prop = baseProps[curr];
273
+ const propTo = newProps[curr];
274
+ if (propTo !== undefined) {
275
+ // * all typeof type below
276
+ // "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
277
+ switch (typeof prop) {
278
+ case 'undefined':
279
+ output[curr] = propTo;
280
+ break;
281
+ case 'object': {
282
+ if (!isValidElement(propTo) &&
283
+ !isValidElement(prop) &&
284
+ isPlainObject(propTo) &&
285
+ isPlainObject(prop)) {
286
+ output[curr] = mergeChildProps({
287
+ baseProps: prop,
288
+ newProps: propTo || {},
289
+ combineAllString: combineAllString || curr === 'classes',
290
+ });
291
+ }
292
+ else {
293
+ output[curr] = propTo;
294
+ }
295
+ break;
296
+ }
297
+ case 'string':
298
+ if (combineAllString || curr === 'className') {
299
+ output[curr] = [prop, propTo]
300
+ .filter((x) => typeof x === 'string' && x)
301
+ .join(' ');
302
+ }
303
+ else {
304
+ output[curr] = propTo;
305
+ }
306
+ break;
307
+ case 'boolean':
308
+ case 'symbol':
309
+ case 'bigint':
310
+ case 'number':
311
+ output[curr] = propTo;
312
+ break;
313
+ case 'function':
314
+ if (coverPropKeys && coverPropKeys.includes(curr) && propTo) {
315
+ output[curr] = propTo;
316
+ }
317
+ else {
318
+ output[curr] = (...args) => {
319
+ // * exec outside first
320
+ propTo?.(...args);
321
+ prop?.(...args);
322
+ };
323
+ }
324
+ break;
325
+ }
326
+ }
327
+ else {
328
+ output[curr] = prop;
329
+ }
330
+ return output;
331
+ }, {});
332
+ // if result is an empty {} reset that to undefined
333
+ if (Object.keys(result).length === 0) {
334
+ return undefined;
335
+ }
336
+ return {
337
+ ...newProps,
338
+ ...result,
339
+ };
340
+ }
341
+
342
+ /* eslint-disable no-console */
343
+ function logInDev({ scope, message, level = 'warn', condition = true, }) {
344
+ if (process.env.NODE_ENV !== 'production' &&
345
+ condition &&
346
+ !rcConfiguration.WARNING_IGNORE) {
347
+ const log = `SPRING [${scope}]: ${message}`;
348
+ if (process.env.NODE_ENV === 'test' && level === 'error') {
349
+ throw Error(log);
350
+ }
351
+ console[level](log);
352
+ }
353
+ }
354
+
355
+ const isRef = (obj) => {
356
+ return obj && 'current' in obj;
357
+ };
358
+
359
+ /**
360
+ * get element from React ref or callback
361
+ */
362
+ const getRefElement = (ref) => {
363
+ if (typeof ref === 'function') {
364
+ return ref();
365
+ }
366
+ if (typeof ref === 'object' && isRef(ref)) {
367
+ return ref.current;
368
+ }
369
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
370
+ return ref;
371
+ };
372
+
373
+ /**
374
+ * Merge tailwind classes with additional class groups for spring-ui
375
+ */
376
+ const twMerge = extendTailwindMerge({
377
+ extend: {
378
+ classGroups: {
379
+ typography: [
380
+ {
381
+ typography: [() => true],
382
+ },
383
+ ],
384
+ 'focus-ring-rounded': [
385
+ 'focus-ring-rounded',
386
+ {
387
+ 'focus-ring-rounded': [() => true],
388
+ },
389
+ ],
390
+ 'focus-ring-border': [
391
+ 'focus-ring-border',
392
+ {
393
+ 'focus-ring-border': [() => true],
394
+ },
395
+ ],
396
+ 'focus-ring-border-recoup': [
397
+ 'focus-ring-border-recoup',
398
+ 'no-focus-ring-border-recoup',
399
+ ],
400
+ 'focus-ring': [
401
+ {
402
+ 'focus-ring': [() => true],
403
+ },
404
+ ],
405
+ },
406
+ },
407
+ });
408
+
409
+ export { ariaHideOutside as a, combineClasses as b, combineProps as c, getRefElement as g, isRef as i, logInDev as l, ownerWindow as o, twMerge as t };
package/config.js ADDED
@@ -0,0 +1,78 @@
1
+ // TODO: refactor config
2
+ /* eslint-disable no-console */
3
+ const SPRING_WARNING_IGNORE_KEY = '__SPRING_WARNING_IGNORE__';
4
+ // * use that way to avoid that code be exist in production code base
5
+ /**
6
+ * config global setting
7
+ *
8
+ * ```ts
9
+ * configure({
10
+ * warning: false
11
+ * })
12
+ * ```
13
+ * ## WARNING related method and only work in `development mode`
14
+ *
15
+ * `WARNING_IGNORE`: only work in `development mode`
16
+ * `WARNING_FUNCTION` only work in `development mode`
17
+ */
18
+ let rcConfiguration = {};
19
+ if (process.env.NODE_ENV !== 'production') {
20
+ class RcConfig {
21
+ _WARNING_IGNORE = false;
22
+ _WARNING_FUNCTION = (message, options) => {
23
+ console[options?.level || 'warn'](message);
24
+ };
25
+ constructor() {
26
+ if (typeof window !== 'undefined') {
27
+ // SSR support
28
+ // * get value once when init
29
+ this._WARNING_IGNORE =
30
+ window?.localStorage?.getItem(SPRING_WARNING_IGNORE_KEY) === 'true';
31
+ // * also provide user to set variable from window
32
+ window.__SPRING__ = this;
33
+ }
34
+ }
35
+ set WARNING_IGNORE(value) {
36
+ if (value) {
37
+ console.log('---------------- [Spring] ----------------');
38
+ console.log(`Don't recommended you close it, we should avoid use any deprecated props or methods.`);
39
+ console.log('---------------- [Spring] ----------------');
40
+ }
41
+ this._WARNING_IGNORE = !!value;
42
+ window?.localStorage?.setItem(SPRING_WARNING_IGNORE_KEY, `${this._WARNING_IGNORE}`);
43
+ }
44
+ get WARNING_IGNORE() {
45
+ return this._WARNING_IGNORE;
46
+ }
47
+ set WARNING_FUNCTION(func) {
48
+ if (typeof func === 'function') {
49
+ this._WARNING_FUNCTION = func;
50
+ }
51
+ else {
52
+ console.warn('[Spring] config warningFunction is not a function.');
53
+ }
54
+ }
55
+ get WARNING_FUNCTION() {
56
+ return this._WARNING_FUNCTION;
57
+ }
58
+ }
59
+ rcConfiguration = new RcConfig();
60
+ }
61
+ /**
62
+ * set config with Spring
63
+ *
64
+ * ## WARNING method only work in `development mode`
65
+ */
66
+ const configure = (options) => {
67
+ if (process.env.NODE_ENV !== 'production') {
68
+ const { warning, warningFunction } = options;
69
+ if (warning !== undefined) {
70
+ rcConfiguration.WARNING_IGNORE = !warning;
71
+ }
72
+ if (warningFunction !== undefined) {
73
+ rcConfiguration.WARNING_FUNCTION = warningFunction;
74
+ }
75
+ }
76
+ };
77
+
78
+ export { configure, rcConfiguration };
package/contexts.js ADDED
@@ -0,0 +1,71 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ const WindowContext = createContext({
4
+ document: typeof window !== 'undefined'
5
+ ? window.document
6
+ : globalThis.window?.document
7
+ ? globalThis.window.document
8
+ : undefined,
9
+ globalThis: globalThis,
10
+ externalWindow: typeof window !== 'undefined' ? window : globalThis.window,
11
+ });
12
+ const useWindowContext = () => useContext(WindowContext);
13
+
14
+ /** small to large */
15
+ const breakpointList = ['xs', 'sm', 'md', 'lg', 'xl'];
16
+
17
+ const matchCache = new Map();
18
+ const getMatchGroups = (breakpoint) => {
19
+ const ltMatchGroup = [];
20
+ const gtMatchGroup = [];
21
+ if (breakpoint === null) {
22
+ for (let i = 0; i < breakpointList.length; i++) {
23
+ ltMatchGroup.push(`lt-${breakpointList[i]}`);
24
+ }
25
+ }
26
+ else {
27
+ const bpIndex = breakpointList.indexOf(breakpoint);
28
+ for (let i = 0; i < bpIndex; i++) {
29
+ gtMatchGroup.unshift(`gt-${breakpointList[i]}`);
30
+ }
31
+ for (let i = bpIndex + 1; i < breakpointList.length; i++) {
32
+ ltMatchGroup.push(`lt-${breakpointList[i]}`);
33
+ }
34
+ }
35
+ return { ltMatchGroup, gtMatchGroup };
36
+ };
37
+ const getMatchMap = ({ ltMatchGroup, gtMatchGroup }, matchBP) => {
38
+ const matchMap = {};
39
+ if (matchBP !== null)
40
+ matchMap[matchBP] = true;
41
+ for (const ltBP of ltMatchGroup) {
42
+ const [, bp] = ltBP.split('-');
43
+ matchMap[`lt${bp.toUpperCase()}`] = true;
44
+ matchMap[ltBP] = true;
45
+ }
46
+ for (const gtBP of gtMatchGroup) {
47
+ const [, bp] = gtBP.split('-');
48
+ matchMap[`gt${bp.toUpperCase()}`] = true;
49
+ matchMap[gtBP] = true;
50
+ }
51
+ return matchMap;
52
+ };
53
+ const getMatchDetail = (breakpoint) => {
54
+ if (!matchCache.has(breakpoint)) {
55
+ const matchGroups = getMatchGroups(breakpoint);
56
+ const matchMap = getMatchMap(matchGroups, breakpoint);
57
+ matchCache.set(breakpoint, {
58
+ ...matchGroups,
59
+ matchBP: breakpoint,
60
+ matchMap,
61
+ });
62
+ }
63
+ return matchCache.get(breakpoint);
64
+ };
65
+
66
+ const RcResponsiveContext = createContext(undefined);
67
+ const useResponsiveContext = () => {
68
+ return useContext(RcResponsiveContext);
69
+ };
70
+
71
+ export { RcResponsiveContext, WindowContext, breakpointList, getMatchDetail, useResponsiveContext, useWindowContext };