@mks2508/mks-ui 0.2.1 → 0.3.1

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 (155) hide show
  1. package/dist/react-ui/hooks/Animation/UseAutoHeight.js +7 -7
  2. package/dist/react-ui/hooks/DOM/UseIsInView.js +3 -3
  3. package/dist/react-ui/hooks/Formatting/UseListFormat.d.ts +49 -0
  4. package/dist/react-ui/hooks/Formatting/UseListFormat.d.ts.map +1 -0
  5. package/dist/react-ui/hooks/Formatting/UseListFormat.js +105 -0
  6. package/dist/react-ui/hooks/State/UseControlledState.js +4 -4
  7. package/dist/react-ui/hooks/State/UseDataState.js +5 -5
  8. package/dist/react-ui/hooks/index.d.ts +2 -0
  9. package/dist/react-ui/hooks/index.d.ts.map +1 -1
  10. package/dist/react-ui/hooks/index.js +1 -0
  11. package/dist/react-ui/index.js +22 -2
  12. package/dist/react-ui/lib/get-strict-context.js +3 -3
  13. package/dist/react-ui/primitives/CountingNumber/index.js +3 -3
  14. package/dist/react-ui/primitives/Highlight/index.js +26 -26
  15. package/dist/react-ui/primitives/Slot/index.js +3 -3
  16. package/dist/react-ui/primitives/index.d.ts +1 -0
  17. package/dist/react-ui/primitives/index.d.ts.map +1 -1
  18. package/dist/react-ui/primitives/index.js +18 -0
  19. package/dist/react-ui/primitives/waapi/Morph/Morph.types.d.ts +76 -0
  20. package/dist/react-ui/primitives/waapi/Morph/Morph.types.d.ts.map +1 -0
  21. package/dist/react-ui/primitives/waapi/Morph/MorphContext.d.ts +11 -0
  22. package/dist/react-ui/primitives/waapi/Morph/MorphContext.d.ts.map +1 -0
  23. package/dist/react-ui/primitives/waapi/Morph/MorphContext.js +19 -0
  24. package/dist/react-ui/primitives/waapi/Morph/index.d.ts +23 -0
  25. package/dist/react-ui/primitives/waapi/Morph/index.d.ts.map +1 -0
  26. package/dist/react-ui/primitives/waapi/Morph/index.js +45 -0
  27. package/dist/react-ui/primitives/waapi/Morph/techniques/index.d.ts +12 -0
  28. package/dist/react-ui/primitives/waapi/Morph/techniques/index.d.ts.map +1 -0
  29. package/dist/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.d.ts +38 -0
  30. package/dist/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.d.ts.map +1 -0
  31. package/dist/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.js +78 -0
  32. package/dist/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.d.ts +23 -0
  33. package/dist/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.d.ts.map +1 -0
  34. package/dist/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.js +140 -0
  35. package/dist/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.d.ts +28 -0
  36. package/dist/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.d.ts.map +1 -0
  37. package/dist/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.js +77 -0
  38. package/dist/react-ui/primitives/waapi/Morph/useMorph.d.ts +27 -0
  39. package/dist/react-ui/primitives/waapi/Morph/useMorph.d.ts.map +1 -0
  40. package/dist/react-ui/primitives/waapi/Morph/useMorph.js +86 -0
  41. package/dist/react-ui/primitives/waapi/Reorder/Reorder.types.d.ts +168 -0
  42. package/dist/react-ui/primitives/waapi/Reorder/Reorder.types.d.ts.map +1 -0
  43. package/dist/react-ui/primitives/waapi/Reorder/index.d.ts +25 -0
  44. package/dist/react-ui/primitives/waapi/Reorder/index.d.ts.map +1 -0
  45. package/dist/react-ui/primitives/waapi/Reorder/index.js +186 -0
  46. package/dist/react-ui/primitives/waapi/Reorder/useReorder.d.ts +26 -0
  47. package/dist/react-ui/primitives/waapi/Reorder/useReorder.d.ts.map +1 -0
  48. package/dist/react-ui/primitives/waapi/Reorder/useReorder.js +48 -0
  49. package/dist/react-ui/primitives/waapi/Reorder/useReorderPresence.d.ts +33 -0
  50. package/dist/react-ui/primitives/waapi/Reorder/useReorderPresence.d.ts.map +1 -0
  51. package/dist/react-ui/primitives/waapi/Reorder/useReorderPresence.js +137 -0
  52. package/dist/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.d.ts +47 -0
  53. package/dist/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.d.ts.map +1 -0
  54. package/dist/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.js +72 -0
  55. package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.d.ts +10 -0
  56. package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.d.ts.map +1 -0
  57. package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.d.ts +74 -0
  58. package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.d.ts.map +1 -0
  59. package/dist/react-ui/primitives/waapi/SlidingNumber/index.d.ts +33 -0
  60. package/dist/react-ui/primitives/waapi/SlidingNumber/index.d.ts.map +1 -0
  61. package/dist/react-ui/primitives/waapi/SlidingNumber/index.js +354 -0
  62. package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.styles.d.ts +25 -0
  63. package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.styles.d.ts.map +1 -0
  64. package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.types.d.ts +57 -0
  65. package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.types.d.ts.map +1 -0
  66. package/dist/react-ui/primitives/waapi/SlidingText/index.d.ts +26 -0
  67. package/dist/react-ui/primitives/waapi/SlidingText/index.d.ts.map +1 -0
  68. package/dist/react-ui/primitives/waapi/SlidingText/index.js +105 -0
  69. package/dist/react-ui/primitives/waapi/core/animationConstants.d.ts +156 -0
  70. package/dist/react-ui/primitives/waapi/core/animationConstants.d.ts.map +1 -0
  71. package/dist/react-ui/primitives/waapi/core/animationConstants.js +180 -0
  72. package/dist/react-ui/primitives/waapi/core/index.d.ts +16 -0
  73. package/dist/react-ui/primitives/waapi/core/index.d.ts.map +1 -0
  74. package/dist/react-ui/primitives/waapi/core/index.js +5 -0
  75. package/dist/react-ui/primitives/waapi/core/types.d.ts +143 -0
  76. package/dist/react-ui/primitives/waapi/core/types.d.ts.map +1 -0
  77. package/dist/react-ui/primitives/waapi/core/useAnimationOrchestrator.d.ts +32 -0
  78. package/dist/react-ui/primitives/waapi/core/useAnimationOrchestrator.d.ts.map +1 -0
  79. package/dist/react-ui/primitives/waapi/core/useAnimationOrchestrator.js +322 -0
  80. package/dist/react-ui/primitives/waapi/core/useElementRegistry.d.ts +21 -0
  81. package/dist/react-ui/primitives/waapi/core/useElementRegistry.d.ts.map +1 -0
  82. package/dist/react-ui/primitives/waapi/core/useElementRegistry.js +65 -0
  83. package/dist/react-ui/primitives/waapi/core/useFLIPAnimation.d.ts +20 -0
  84. package/dist/react-ui/primitives/waapi/core/useFLIPAnimation.d.ts.map +1 -0
  85. package/dist/react-ui/primitives/waapi/core/useFLIPAnimation.js +99 -0
  86. package/dist/react-ui/primitives/waapi/core/usePositionCapture.d.ts +24 -0
  87. package/dist/react-ui/primitives/waapi/core/usePositionCapture.d.ts.map +1 -0
  88. package/dist/react-ui/primitives/waapi/core/usePositionCapture.js +75 -0
  89. package/dist/react-ui/primitives/waapi/index.d.ts +33 -0
  90. package/dist/react-ui/primitives/waapi/index.d.ts.map +1 -0
  91. package/dist/react-ui/primitives/waapi/index.js +18 -0
  92. package/dist/react-ui/ui/Accordion/index.js +3 -3
  93. package/dist/react-ui/ui/Button/index.js +8 -8
  94. package/dist/react-ui/ui/Combobox/index.js +2 -2
  95. package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts +35 -0
  96. package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts.map +1 -0
  97. package/dist/react-ui/ui/DataCard/DataCard.styles.js +114 -0
  98. package/dist/react-ui/ui/DataCard/DataCard.types.d.ts +135 -0
  99. package/dist/react-ui/ui/DataCard/DataCard.types.d.ts.map +1 -0
  100. package/dist/react-ui/ui/DataCard/index.d.ts +129 -0
  101. package/dist/react-ui/ui/DataCard/index.d.ts.map +1 -0
  102. package/dist/react-ui/ui/DataCard/index.js +276 -0
  103. package/dist/react-ui/ui/Menu/index.js +2 -2
  104. package/dist/react-ui/ui/Switch/index.js +3 -3
  105. package/dist/react-ui/ui/Tabs/index.js +3 -3
  106. package/dist/react-ui/ui/TextFlow/TextFlow.styles.d.ts +16 -0
  107. package/dist/react-ui/ui/TextFlow/TextFlow.styles.d.ts.map +1 -0
  108. package/dist/react-ui/ui/TextFlow/TextFlow.types.d.ts +101 -0
  109. package/dist/react-ui/ui/TextFlow/TextFlow.types.d.ts.map +1 -0
  110. package/dist/react-ui/ui/TextFlow/index.d.ts +26 -0
  111. package/dist/react-ui/ui/TextFlow/index.d.ts.map +1 -0
  112. package/dist/react-ui/ui/TextFlow/index.js +187 -0
  113. package/dist/react-ui/ui/index.d.ts +2 -1
  114. package/dist/react-ui/ui/index.d.ts.map +1 -1
  115. package/dist/react-ui/ui/index.js +3 -1
  116. package/package.json +6 -2
  117. package/src/react-ui/hooks/Formatting/UseListFormat.ts +134 -0
  118. package/src/react-ui/hooks/index.ts +3 -0
  119. package/src/react-ui/primitives/index.ts +3 -0
  120. package/src/react-ui/primitives/waapi/Morph/Morph.types.ts +106 -0
  121. package/src/react-ui/primitives/waapi/Morph/MorphContext.tsx +21 -0
  122. package/src/react-ui/primitives/waapi/Morph/index.tsx +56 -0
  123. package/src/react-ui/primitives/waapi/Morph/techniques/index.ts +12 -0
  124. package/src/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.ts +88 -0
  125. package/src/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.ts +175 -0
  126. package/src/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.ts +86 -0
  127. package/src/react-ui/primitives/waapi/Morph/useMorph.ts +100 -0
  128. package/src/react-ui/primitives/waapi/Reorder/Reorder.types.ts +177 -0
  129. package/src/react-ui/primitives/waapi/Reorder/index.tsx +260 -0
  130. package/src/react-ui/primitives/waapi/Reorder/useReorder.ts +46 -0
  131. package/src/react-ui/primitives/waapi/Reorder/useReorderPresence.ts +208 -0
  132. package/src/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.ts +104 -0
  133. package/src/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.ts +14 -0
  134. package/src/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.ts +84 -0
  135. package/src/react-ui/primitives/waapi/SlidingNumber/index.tsx +474 -0
  136. package/src/react-ui/primitives/waapi/SlidingText/SlidingText.styles.ts +32 -0
  137. package/src/react-ui/primitives/waapi/SlidingText/SlidingText.types.ts +69 -0
  138. package/src/react-ui/primitives/waapi/SlidingText/index.tsx +140 -0
  139. package/src/react-ui/primitives/waapi/core/animationConstants.ts +215 -0
  140. package/src/react-ui/primitives/waapi/core/index.ts +53 -0
  141. package/src/react-ui/primitives/waapi/core/types.ts +200 -0
  142. package/src/react-ui/primitives/waapi/core/useAnimationOrchestrator.ts +429 -0
  143. package/src/react-ui/primitives/waapi/core/useElementRegistry.ts +80 -0
  144. package/src/react-ui/primitives/waapi/core/useFLIPAnimation.ts +137 -0
  145. package/src/react-ui/primitives/waapi/core/usePositionCapture.ts +105 -0
  146. package/src/react-ui/primitives/waapi/index.ts +116 -0
  147. package/src/react-ui/styles/animations.css +369 -0
  148. package/src/react-ui/ui/DataCard/DataCard.styles.ts +150 -0
  149. package/src/react-ui/ui/DataCard/DataCard.types.ts +146 -0
  150. package/src/react-ui/ui/DataCard/index.tsx +406 -0
  151. package/src/react-ui/ui/TextFlow/TextFlow.styles.ts +36 -0
  152. package/src/react-ui/ui/TextFlow/TextFlow.types.ts +118 -0
  153. package/src/react-ui/ui/TextFlow/index.tsx +276 -0
  154. package/src/react-ui/ui/index.ts +4 -1
  155. /package/dist/react-ui/components/MorphingPopover/{morphing-popover.module-CgbYV_HS.css → morphing-popover.module-BycNI8nU.css} +0 -0
@@ -0,0 +1,260 @@
1
+ "use client";
2
+
3
+ import React, { useMemo, useState, useEffect, useRef, useCallback, useImperativeHandle, Children, isValidElement, cloneElement, forwardRef, type CSSProperties, type ReactNode, type ReactElement } from 'react';
4
+ import { useReorder } from './useReorder';
5
+ import type { IReorderProps, ReorderLayout, DurationConfig } from './Reorder.types';
6
+ import { cn } from '@/react-ui/lib/utils';
7
+
8
+ function getDuration(config: DurationConfig | undefined, type: 'enter' | 'exit'): number | undefined {
9
+ if (config === undefined) return undefined;
10
+ if (typeof config === 'number') return config;
11
+ return config[type];
12
+ }
13
+
14
+ const layoutStyles: Record<ReorderLayout, CSSProperties> = {
15
+ auto: { position: 'relative' },
16
+ horizontal: { display: 'flex', flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', position: 'relative' },
17
+ 'inline-horizontal': { display: 'inline-flex', flexDirection: 'row', flexWrap: 'nowrap', alignItems: 'center', position: 'relative' },
18
+ vertical: { display: 'flex', flexDirection: 'column', position: 'relative' },
19
+ grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(100px, 1fr))', gap: '8px', position: 'relative' }
20
+ };
21
+
22
+ interface IChildRecord {
23
+ key: string;
24
+ element: ReactElement;
25
+ }
26
+
27
+ /**
28
+ * Reorder - Agnostic container for reorderable items with FLIP animations
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * <Reorder
33
+ * layout="horizontal"
34
+ * onItemExit={(id) => handleRemove(id)}
35
+ * >
36
+ * {items.map(item => (
37
+ * <div key={item.id}>{item.name}</div>
38
+ * ))}
39
+ * </Reorder>
40
+ * ```
41
+ */
42
+ const ReorderRoot = forwardRef<{
43
+ startItemExit: (id: string) => Promise<void>;
44
+ startItemEnter: (id: string) => Promise<void>;
45
+ isAnimating: (id?: string) => boolean;
46
+ }, IReorderProps>(({
47
+ children,
48
+ stagger,
49
+ duration,
50
+ layout = 'auto',
51
+ className = '',
52
+ flipBehavior,
53
+ exitPositionStrategy,
54
+ onItemExit,
55
+ onItemEnter
56
+ }, ref) => {
57
+ // Completion handler - removes from displayChildren
58
+ const handleAnimationComplete = useCallback((id: string) => {
59
+ setDisplayChildren(prev => prev.filter(child => child.key !== id));
60
+ }, []);
61
+
62
+ // Reorder hook — delegates to full FLIP orchestrator
63
+ const reorder = useReorder({
64
+ enterDuration: getDuration(duration, 'enter'),
65
+ exitDuration: getDuration(duration, 'exit'),
66
+ flipBehavior,
67
+ exitPositionStrategy,
68
+ onComplete: handleAnimationComplete
69
+ });
70
+
71
+ // Expose reorder methods via ref
72
+ useImperativeHandle(ref, () => ({
73
+ startItemExit: reorder.startItemExit,
74
+ startItemEnter: reorder.startItemEnter,
75
+ isAnimating: reorder.isAnimating
76
+ }), [reorder.startItemExit, reorder.startItemEnter, reorder.isAnimating]);
77
+
78
+ // Store reorder functions in refs for stable callbacks
79
+ const startExitRef = useRef(reorder.startItemExit);
80
+ const startEnterRef = useRef(reorder.startItemEnter);
81
+ useEffect(() => {
82
+ startExitRef.current = reorder.startItemExit;
83
+ startEnterRef.current = reorder.startItemEnter;
84
+ }, [reorder.startItemExit, reorder.startItemEnter]);
85
+
86
+ // Extract current children as records
87
+ const currentChildren = useMemo<IChildRecord[]>(() => {
88
+ const records: IChildRecord[] = [];
89
+ Children.forEach(children, child => {
90
+ if (isValidElement(child) && child.key != null) {
91
+ records.push({
92
+ key: String(child.key),
93
+ element: child as ReactElement
94
+ });
95
+ }
96
+ });
97
+ return records;
98
+ }, [children]);
99
+
100
+ // Internal display state
101
+ const [displayChildren, setDisplayChildren] = useState<IChildRecord[]>(currentChildren);
102
+
103
+ // Track which keys are currently exiting
104
+ const exitingKeysRef = useRef<Set<string>>(new Set());
105
+
106
+ // Calculate stagger delay
107
+ const getStaggerDelay = useCallback((index: number, type: 'enter' | 'exit'): number => {
108
+ if (!stagger) return 0;
109
+ if (typeof stagger === 'number') return stagger * index;
110
+ return (stagger[type] ?? 0) * index;
111
+ }, [stagger]);
112
+
113
+ // Detect changes and sync displayChildren
114
+ useEffect(() => {
115
+ const currentKeys = new Set(currentChildren.map(c => c.key));
116
+ const displayKeys = new Set(displayChildren.map(c => c.key));
117
+
118
+ // Removals: in display but not in current, and not already exiting
119
+ const removed = displayChildren.filter(c =>
120
+ !currentKeys.has(c.key) && !exitingKeysRef.current.has(c.key)
121
+ );
122
+
123
+ // Additions: in current but not in display
124
+ const added = currentChildren.filter(c => !displayKeys.has(c.key));
125
+
126
+ // No structural changes
127
+ if (removed.length === 0 && added.length === 0) {
128
+ const hasUpdates = currentChildren.some((c) => {
129
+ const displayChild = displayChildren.find(dc => dc.key === c.key);
130
+ return displayChild && displayChild.element !== c.element;
131
+ });
132
+
133
+ if (hasUpdates) {
134
+ setDisplayChildren(prev => prev.map(dc => {
135
+ if (exitingKeysRef.current.has(dc.key)) return dc;
136
+ const currentChild = currentChildren.find(c => c.key === dc.key);
137
+ return currentChild ?? dc;
138
+ }));
139
+ }
140
+ return;
141
+ }
142
+
143
+ // Handle removals - start exit animations
144
+ removed.forEach((child, index) => {
145
+ const delay = getStaggerDelay(index, 'exit');
146
+ exitingKeysRef.current.add(child.key);
147
+
148
+ const processExit = () => {
149
+ onItemExit?.(child.key);
150
+
151
+ // Start exit animation
152
+ startExitRef.current(child.key).finally(() => {
153
+ exitingKeysRef.current.delete(child.key);
154
+ });
155
+ };
156
+
157
+ if (delay > 0) {
158
+ setTimeout(processExit, delay);
159
+ } else {
160
+ processExit();
161
+ }
162
+ });
163
+
164
+ // Handle additions - merge into display
165
+ if (added.length > 0) {
166
+ const exitingChildren = displayChildren.filter(c => !currentKeys.has(c.key));
167
+ let result = [...currentChildren];
168
+
169
+ exitingChildren.forEach(ec => {
170
+ const oldIdx = displayChildren.findIndex(c => c.key === ec.key);
171
+ if (oldIdx !== -1 && oldIdx <= result.length) {
172
+ result.splice(oldIdx, 0, ec);
173
+ }
174
+ });
175
+
176
+ setDisplayChildren(result);
177
+
178
+ added.forEach((child, index) => {
179
+ const delay = getStaggerDelay(index, 'enter');
180
+
181
+ const processEnter = () => {
182
+ onItemEnter?.(child.key);
183
+ };
184
+
185
+ if (delay > 0) {
186
+ setTimeout(processEnter, delay);
187
+ } else {
188
+ processEnter();
189
+ }
190
+ });
191
+ }
192
+ }, [currentChildren, displayChildren, getStaggerDelay, onItemExit, onItemEnter]);
193
+
194
+ // Build present children with data attributes and ref registration
195
+ const presentChildren = useMemo<ReactNode[]>(() => {
196
+ const currentKeys = new Set(currentChildren.map(c => c.key));
197
+
198
+ return displayChildren.map(({ key, element }) => {
199
+ const isNewElement = currentKeys.has(key);
200
+ const isExiting = exitingKeysRef.current.has(key);
201
+
202
+ const elementWithRef = cloneElement(element as ReactElement<any>, {
203
+ ref: (el: HTMLElement | null) => {
204
+ if (el) {
205
+ reorder.registerElement(key, el);
206
+
207
+ const currentState = el.dataset.reorderState;
208
+
209
+ if (!currentState && isNewElement && !isExiting) {
210
+ el.dataset.reorderState = 'entering';
211
+ startEnterRef.current(key);
212
+ }
213
+ } else {
214
+ reorder.registerElement(key, null);
215
+ }
216
+
217
+ // Preserve original ref
218
+ const originalRef = (element as any).ref;
219
+ if (typeof originalRef === 'function') {
220
+ originalRef(el);
221
+ } else if (originalRef && typeof originalRef === 'object') {
222
+ (originalRef as React.MutableRefObject<HTMLElement | null>).current = el;
223
+ }
224
+ },
225
+ 'data-reorder-id': key
226
+ });
227
+
228
+ return elementWithRef;
229
+ });
230
+ }, [displayChildren, currentChildren, reorder.registerElement]);
231
+
232
+ const layoutClass = layout !== 'auto' ? `reorder--${layout}` : '';
233
+
234
+ return (
235
+ <div
236
+ className={cn('waapi-reorder-container', 'reorder', layoutClass, className)}
237
+ style={layoutStyles[layout]}
238
+ >
239
+ {presentChildren}
240
+ </div>
241
+ );
242
+ });
243
+
244
+ ReorderRoot.displayName = 'Reorder';
245
+
246
+ export { ReorderRoot as Reorder };
247
+ export type {
248
+ IReorderProps,
249
+ ReorderLayout,
250
+ DurationConfig,
251
+ StaggerConfig,
252
+ IUseReorderReturn,
253
+ IUseReorderConfig,
254
+ IReorderItemState,
255
+ IReorderContextValue,
256
+ IUseReorderPresenceConfig,
257
+ IUseReorderPresenceReturn,
258
+ FLIPBehavior,
259
+ ExitPositionStrategy,
260
+ } from './Reorder.types';
@@ -0,0 +1,46 @@
1
+ import { useAnimationOrchestrator } from '../core/useAnimationOrchestrator';
2
+ import type { IUseReorderReturn, IUseReorderConfig } from './Reorder.types';
3
+
4
+ /**
5
+ * Hook for managing reorderable lists with FLIP animations.
6
+ *
7
+ * Architecture: Thin wrapper around useAnimationOrchestrator.
8
+ * Delegates all animation logic to the orchestrator and provides
9
+ * a stable API surface for the Reorder component.
10
+ *
11
+ * @param config - Optional reorder configuration (timing, easing, callbacks)
12
+ * @returns Reorder API with register, exit, enter, and query methods
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * const reorder = useReorder({
17
+ * onComplete: (id) => removeFromList(id)
18
+ * });
19
+ *
20
+ * <div ref={el => reorder.registerElement('item-1', el)}>
21
+ * Item 1
22
+ * </div>
23
+ *
24
+ * await reorder.startItemExit('item-1');
25
+ * ```
26
+ */
27
+ export function useReorder(config?: IUseReorderConfig): IUseReorderReturn {
28
+ const orchestrator = useAnimationOrchestrator({
29
+ enterDuration: config?.enterDuration,
30
+ exitDuration: config?.exitDuration,
31
+ flipDuration: config?.flipDuration,
32
+ enterEasing: config?.enterEasing,
33
+ exitEasing: config?.exitEasing,
34
+ flipEasing: config?.flipEasing,
35
+ flipBehavior: config?.flipBehavior,
36
+ exitPositionStrategy: config?.exitPositionStrategy,
37
+ onExitComplete: config?.onComplete
38
+ });
39
+
40
+ return {
41
+ ...orchestrator,
42
+ registerElement: orchestrator.registerElement,
43
+ startItemExit: orchestrator.startExit,
44
+ startItemEnter: orchestrator.startEnter
45
+ };
46
+ }
@@ -0,0 +1,208 @@
1
+ import {
2
+ useRef,
3
+ useCallback,
4
+ useMemo,
5
+ useLayoutEffect,
6
+ useEffect,
7
+ Children,
8
+ isValidElement,
9
+ cloneElement,
10
+ type ReactNode,
11
+ type ReactElement
12
+ } from 'react';
13
+ import { useReorder } from './useReorder';
14
+ import type {
15
+ IUseReorderPresenceConfig,
16
+ IUseReorderPresenceReturn
17
+ } from './Reorder.types';
18
+
19
+ /**
20
+ * Hook for managing presence animations in reorderable lists.
21
+ *
22
+ * Follows the pattern where elements stay in DOM until animation completes.
23
+ * Consumer must keep items in their state until onAnimationComplete fires.
24
+ * No "ghost" cloning — animations run on actual DOM elements.
25
+ *
26
+ * @param children - React children with unique keys
27
+ * @param config - Presence animation configuration
28
+ * @returns Presence API with presentChildren, triggerExit, and state queries
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const [items, setItems] = useState(INITIAL_ITEMS);
33
+ *
34
+ * const { presentChildren, triggerExit } = useReorderPresence(
35
+ * items.map(item => <div key={item.id}>{item.name}</div>),
36
+ * {
37
+ * onAnimationComplete: (id) => {
38
+ * setItems(prev => prev.filter(item => item.id !== id));
39
+ * }
40
+ * }
41
+ * );
42
+ *
43
+ * const handleDelete = (id: string) => {
44
+ * triggerExit(id);
45
+ * };
46
+ * ```
47
+ */
48
+ export function useReorderPresence(
49
+ children: ReactNode,
50
+ config: IUseReorderPresenceConfig = {}
51
+ ): IUseReorderPresenceReturn {
52
+ const {
53
+ autoAnimate = true,
54
+ stagger,
55
+ enterDuration,
56
+ exitDuration,
57
+ flipDuration,
58
+ enterEasing,
59
+ exitEasing,
60
+ flipEasing
61
+ } = config;
62
+
63
+ const configRef = useRef(config);
64
+ useEffect(() => {
65
+ configRef.current = config;
66
+ }, [config]);
67
+
68
+ // Track animating IDs via REF (not state) — like AnimatedTokens
69
+ const exitingIdsRef = useRef<Set<string>>(new Set());
70
+ const enteringIdsRef = useRef<Set<string>>(new Set());
71
+
72
+ // Track previous children keys for change detection
73
+ const prevKeysRef = useRef<Set<string>>(new Set());
74
+
75
+ const getStaggerDelay = useCallback((index: number, type: 'enter' | 'exit'): number => {
76
+ if (!stagger) return 0;
77
+ if (typeof stagger === 'number') return stagger * index;
78
+ return (stagger[type] ?? 0) * index;
79
+ }, [stagger]);
80
+
81
+ const handleAnimationComplete = useCallback((id: string) => {
82
+ exitingIdsRef.current.delete(id);
83
+ configRef.current.onAnimationComplete?.(id);
84
+ }, []);
85
+
86
+ const reorder = useReorder({
87
+ enterDuration,
88
+ exitDuration,
89
+ flipDuration,
90
+ enterEasing,
91
+ exitEasing,
92
+ flipEasing,
93
+ onComplete: handleAnimationComplete
94
+ });
95
+
96
+ const currentKeys = useMemo(() => {
97
+ const keys = new Set<string>();
98
+ Children.forEach(children, child => {
99
+ if (isValidElement(child) && child.key != null) {
100
+ keys.add(String(child.key));
101
+ }
102
+ });
103
+ return keys;
104
+ }, [children]);
105
+
106
+ // Detect and process changes
107
+ useLayoutEffect(() => {
108
+ const prevKeys = prevKeysRef.current;
109
+
110
+ // Detect additions (in current but not in prev)
111
+ const added: string[] = [];
112
+ currentKeys.forEach(key => {
113
+ if (!prevKeys.has(key) && !enteringIdsRef.current.has(key)) {
114
+ added.push(key);
115
+ }
116
+ });
117
+
118
+ // Process additions
119
+ if (added.length > 0) {
120
+ added.forEach((key, index) => {
121
+ const delay = getStaggerDelay(index, 'enter');
122
+
123
+ const processEnter = () => {
124
+ enteringIdsRef.current.add(key);
125
+ configRef.current.onItemEnter?.(key);
126
+
127
+ requestAnimationFrame(() => {
128
+ reorder.startItemEnter(key).then(() => {
129
+ enteringIdsRef.current.delete(key);
130
+ });
131
+ });
132
+ };
133
+
134
+ if (delay > 0) {
135
+ setTimeout(processEnter, delay);
136
+ } else {
137
+ processEnter();
138
+ }
139
+ });
140
+ }
141
+
142
+ prevKeysRef.current = new Set(currentKeys);
143
+ }, [currentKeys, autoAnimate, reorder, getStaggerDelay]);
144
+
145
+ // Manual exit trigger
146
+ const triggerExit = useCallback((id: string) => {
147
+ if (exitingIdsRef.current.has(id)) return;
148
+ if (!reorder.registry.has(id)) return;
149
+
150
+ exitingIdsRef.current.add(id);
151
+ configRef.current.onItemExit?.(id);
152
+
153
+ reorder.startItemExit(id);
154
+ }, [reorder]);
155
+
156
+ const isExiting = useCallback((id: string): boolean => {
157
+ return exitingIdsRef.current.has(id);
158
+ }, []);
159
+
160
+ const isEntering = useCallback((id: string): boolean => {
161
+ return enteringIdsRef.current.has(id);
162
+ }, []);
163
+
164
+ const getExitingIds = useCallback((): string[] => {
165
+ return Array.from(exitingIdsRef.current);
166
+ }, []);
167
+
168
+ const getEnteringIds = useCallback((): string[] => {
169
+ return Array.from(enteringIdsRef.current);
170
+ }, []);
171
+
172
+ // presentChildren — children as-is with data attribute for exiting
173
+ const presentChildren = useMemo(() => {
174
+ const result: ReactNode[] = [];
175
+
176
+ Children.forEach(children, child => {
177
+ if (!isValidElement(child)) {
178
+ result.push(child);
179
+ return;
180
+ }
181
+
182
+ const key = child.key != null ? String(child.key) : null;
183
+ const isCurrentlyExiting = key != null && exitingIdsRef.current.has(key);
184
+
185
+ if (isCurrentlyExiting) {
186
+ result.push(
187
+ cloneElement(child as ReactElement<{ 'data-reorder-state'?: string }>, {
188
+ 'data-reorder-state': 'exiting'
189
+ })
190
+ );
191
+ } else {
192
+ result.push(child);
193
+ }
194
+ });
195
+
196
+ return result;
197
+ }, [children]);
198
+
199
+ return {
200
+ presentChildren,
201
+ triggerExit,
202
+ isExiting,
203
+ isEntering,
204
+ exitingIds: getExitingIds(),
205
+ enteringIds: getEnteringIds(),
206
+ reorder
207
+ };
208
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Utility functions for separator animation coordination.
3
+ *
4
+ * When a token exits, determines which separators should animate:
5
+ * 1. The exiting token's own separator (if it has one)
6
+ * 2. If the exiting token is the last visible, the previous token's separator
7
+ *
8
+ * @module primitives/waapi/Reorder/utils/separatorCoordination
9
+ */
10
+
11
+ /**
12
+ * Calculate which separator IDs should animate along with an exiting item.
13
+ *
14
+ * Logic:
15
+ * - If the exiting item is NOT the last in the list, animate its own separator
16
+ * - If the exiting item IS the last in the list, animate the previous item's separator
17
+ * (because the previous item will become the new last and shouldn't have a separator)
18
+ *
19
+ * @param exitingId - The ID of the item being removed
20
+ * @param currentKeys - Set of all current (non-exiting) item keys
21
+ * @param exitingIds - Set of all currently exiting item IDs
22
+ * @param orderedKeys - Array of keys in render order
23
+ * @returns Array of separator IDs to animate
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * // Items: [A, B, C, D] with separators after A, B, C
28
+ * // Removing C (not last):
29
+ * calculateSeparatorCoordination('C', {'A','B','D'}, {'C'}, ['A','B','C','D'])
30
+ * // Returns: ['C'] - animate C's separator
31
+ *
32
+ * // Removing D (last item):
33
+ * calculateSeparatorCoordination('D', {'A','B','C'}, {'D'}, ['A','B','C','D'])
34
+ * // Returns: ['C'] - animate C's separator (C becomes new last)
35
+ * ```
36
+ */
37
+ export function calculateSeparatorCoordination(
38
+ exitingId: string,
39
+ currentKeys: Set<string>,
40
+ exitingIds: Set<string>,
41
+ orderedKeys: string[]
42
+ ): string[] {
43
+ const additionalSeparators: string[] = [];
44
+
45
+ const visibleKeys = orderedKeys.filter(
46
+ key => currentKeys.has(key) || exitingIds.has(key)
47
+ );
48
+
49
+ const indexInVisible = visibleKeys.indexOf(exitingId);
50
+ if (indexInVisible === -1) {
51
+ return additionalSeparators;
52
+ }
53
+
54
+ const isLastVisible = indexInVisible === visibleKeys.length - 1;
55
+ const isNotLastVisible = indexInVisible < visibleKeys.length - 1;
56
+
57
+ if (isNotLastVisible) {
58
+ additionalSeparators.push(exitingId);
59
+ }
60
+
61
+ if (isLastVisible && indexInVisible > 0) {
62
+ const previousKey = visibleKeys[indexInVisible - 1];
63
+ if (previousKey && !exitingIds.has(previousKey)) {
64
+ additionalSeparators.push(previousKey);
65
+ }
66
+ }
67
+
68
+ return additionalSeparators;
69
+ }
70
+
71
+ /**
72
+ * Determine if a separator should be visible based on position and animation state.
73
+ *
74
+ * @param _tokenId - The token ID to check (unused, kept for API compatibility)
75
+ * @param tokenIndex - Index of the token in the visible list
76
+ * @param totalVisible - Total number of visible tokens
77
+ * @param separatorPhase - Current animation phase of the separator
78
+ * @returns Whether the separator should be rendered
79
+ */
80
+ export function shouldShowSeparator(
81
+ _tokenId: string,
82
+ tokenIndex: number,
83
+ totalVisible: number,
84
+ separatorPhase?: 'idle' | 'exit-coordinated' | 'flip-coordinated' | 'completed'
85
+ ): boolean {
86
+ if (tokenIndex >= totalVisible - 1) {
87
+ return false;
88
+ }
89
+
90
+ if (!separatorPhase) {
91
+ return true;
92
+ }
93
+
94
+ switch (separatorPhase) {
95
+ case 'idle':
96
+ case 'exit-coordinated':
97
+ case 'flip-coordinated':
98
+ return true;
99
+ case 'completed':
100
+ return false;
101
+ default:
102
+ return true;
103
+ }
104
+ }
@@ -0,0 +1,14 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority';
2
+ import type { StyleSlots } from '@/core/types';
3
+
4
+ /**
5
+ * Style slots for SlidingNumber component
6
+ */
7
+ export type SlidingNumberSlot = 'root';
8
+
9
+ /**
10
+ * Base styles for SlidingNumber component
11
+ */
12
+ export const slidingNumberBaseStyles: StyleSlots<SlidingNumberSlot> = {
13
+ root: 'waapi-sliding-number',
14
+ };