@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,175 @@
1
+ import { useRef, useCallback, useEffect, useState } from 'react';
2
+ import { TIMING, EASINGS } from '../../core/animationConstants';
3
+ import type { IFLIPClipPathOptions, IFLIPClipPathAPI } from '../Morph.types';
4
+
5
+ const DEFAULT_DURATION = TIMING.FLIP_DURATION;
6
+ const DEFAULT_EASING = EASINGS.EASE_OUT_CUBIC;
7
+
8
+ /**
9
+ * Hook for FLIP + clip-path morphing technique.
10
+ *
11
+ * Combines:
12
+ * 1. FLIP for position/size transitions
13
+ * 2. clip-path for shape morphing
14
+ * 3. Opacity crossfade for smooth visual transition
15
+ *
16
+ * @param options - Duration, easing, and clip-path overrides
17
+ * @returns API with morph trigger, cancel, and morphing state
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * const { isMorphing, morph, cancel } = useFLIPClipPath({ duration: 300 });
22
+ *
23
+ * const handleMorph = async () => {
24
+ * await morph(fromRef.current, toRef.current);
25
+ * };
26
+ * ```
27
+ */
28
+ export function useFLIPClipPath(options?: IFLIPClipPathOptions): IFLIPClipPathAPI {
29
+ const [isMorphing, setIsMorphing] = useState(false);
30
+ const activeAnimationsRef = useRef<Animation[]>([]);
31
+ const optionsRef = useRef(options);
32
+
33
+ useEffect(() => {
34
+ optionsRef.current = options;
35
+ }, [options]);
36
+
37
+ const cancel = useCallback(() => {
38
+ activeAnimationsRef.current.forEach(anim => anim.cancel());
39
+ activeAnimationsRef.current = [];
40
+ setIsMorphing(false);
41
+ }, []);
42
+
43
+ const morph = useCallback(async (
44
+ fromElement: HTMLElement,
45
+ toElement: HTMLElement
46
+ ): Promise<void> => {
47
+ if (isMorphing) {
48
+ cancel();
49
+ }
50
+
51
+ setIsMorphing(true);
52
+
53
+ const duration = optionsRef.current?.duration ?? DEFAULT_DURATION;
54
+ const easing = optionsRef.current?.easing ?? DEFAULT_EASING;
55
+
56
+ const fromRect = fromElement.getBoundingClientRect();
57
+ const toRect = toElement.getBoundingClientRect();
58
+
59
+ const deltaX = fromRect.left - toRect.left;
60
+ const deltaY = fromRect.top - toRect.top;
61
+ const scaleX = fromRect.width / toRect.width;
62
+ const scaleY = fromRect.height / toRect.height;
63
+
64
+ const fromBorderRadius = getComputedStyle(fromElement).borderRadius;
65
+ const toBorderRadius = getComputedStyle(toElement).borderRadius;
66
+
67
+ const fromClipPath = optionsRef.current?.clipPathStart ||
68
+ borderRadiusToInset(fromBorderRadius, fromRect);
69
+ const toClipPath = optionsRef.current?.clipPathEnd ||
70
+ borderRadiusToInset(toBorderRadius, toRect);
71
+
72
+ const originalFromStyles = {
73
+ position: fromElement.style.position,
74
+ opacity: fromElement.style.opacity,
75
+ pointerEvents: fromElement.style.pointerEvents
76
+ };
77
+
78
+ const originalToStyles = {
79
+ position: toElement.style.position,
80
+ opacity: toElement.style.opacity,
81
+ transform: toElement.style.transform,
82
+ clipPath: toElement.style.clipPath
83
+ };
84
+
85
+ fromElement.style.position = 'absolute';
86
+ fromElement.style.pointerEvents = 'none';
87
+
88
+ toElement.style.position = 'relative';
89
+ toElement.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${scaleX}, ${scaleY})`;
90
+ toElement.style.clipPath = fromClipPath;
91
+ toElement.style.opacity = '0';
92
+
93
+ const fromFadeOut = fromElement.animate([
94
+ { opacity: 1 },
95
+ { opacity: 0 }
96
+ ], {
97
+ duration: duration * 0.5,
98
+ easing,
99
+ fill: 'forwards'
100
+ });
101
+
102
+ const toFadeIn = toElement.animate([
103
+ { opacity: 0 },
104
+ { opacity: 1 }
105
+ ], {
106
+ duration: duration * 0.5,
107
+ delay: duration * 0.25,
108
+ easing,
109
+ fill: 'forwards'
110
+ });
111
+
112
+ const toTransform = toElement.animate([
113
+ {
114
+ transform: `translate(${deltaX}px, ${deltaY}px) scale(${scaleX}, ${scaleY})`,
115
+ clipPath: fromClipPath
116
+ },
117
+ {
118
+ transform: 'translate(0, 0) scale(1, 1)',
119
+ clipPath: toClipPath
120
+ }
121
+ ], {
122
+ duration,
123
+ easing,
124
+ fill: 'forwards'
125
+ });
126
+
127
+ activeAnimationsRef.current = [fromFadeOut, toFadeIn, toTransform];
128
+
129
+ try {
130
+ await Promise.all([
131
+ fromFadeOut.finished,
132
+ toFadeIn.finished,
133
+ toTransform.finished
134
+ ]);
135
+ } catch {
136
+ Object.assign(fromElement.style, originalFromStyles);
137
+ Object.assign(toElement.style, originalToStyles);
138
+ setIsMorphing(false);
139
+ return;
140
+ }
141
+
142
+ Object.assign(fromElement.style, originalFromStyles);
143
+ toElement.style.transform = '';
144
+ toElement.style.clipPath = '';
145
+ toElement.style.opacity = '1';
146
+
147
+ activeAnimationsRef.current = [];
148
+ setIsMorphing(false);
149
+ }, [isMorphing, cancel]);
150
+
151
+ useEffect(() => {
152
+ return () => {
153
+ cancel();
154
+ };
155
+ }, [cancel]);
156
+
157
+ return {
158
+ isMorphing,
159
+ morph,
160
+ cancel
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Convert a CSS border-radius value to an inset() clip-path with rounded corners.
166
+ * @param borderRadius - CSS border-radius string
167
+ * @param rect - Bounding rect of the element
168
+ * @returns inset() clip-path string
169
+ */
170
+ function borderRadiusToInset(borderRadius: string, rect: DOMRect): string {
171
+ const radius = parseFloat(borderRadius) || 0;
172
+ const percentX = (radius / rect.width) * 100;
173
+ const percentY = (radius / rect.height) * 100;
174
+ return `inset(0 round ${percentX}% ${percentY}%)`;
175
+ }
@@ -0,0 +1,86 @@
1
+ import { useRef, useCallback, useMemo } from 'react';
2
+ import type { IViewTransitionsOptions, IViewTransitionsAPI } from '../Morph.types';
3
+
4
+ /**
5
+ * Hook for the View Transitions API.
6
+ *
7
+ * Provides a wrapper around the native View Transitions API
8
+ * with automatic feature detection and fallback handling.
9
+ *
10
+ * Browser support:
11
+ * - Chrome 111+
12
+ * - Firefox 144+
13
+ * - Safari latest
14
+ *
15
+ * @param options - Optional view transition name and types
16
+ * @returns View Transitions API with support detection and transition trigger
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * const { isSupported, startTransition } = useViewTransitions({ name: 'hero' });
21
+ *
22
+ * const handleTransition = async () => {
23
+ * await startTransition(() => {
24
+ * setActiveImage(nextImage);
25
+ * });
26
+ * };
27
+ * ```
28
+ */
29
+ export function useViewTransitions(options?: IViewTransitionsOptions): IViewTransitionsAPI {
30
+ const optionsRef = useRef(options);
31
+ const typesRef = useRef<string[]>(options?.types || []);
32
+
33
+ const isSupported = useMemo(() => {
34
+ if (typeof document === 'undefined') return false;
35
+ return 'startViewTransition' in document;
36
+ }, []);
37
+
38
+ const setTypes = useCallback((types: string[]) => {
39
+ typesRef.current = types;
40
+ }, []);
41
+
42
+ const startTransition = useCallback(async (
43
+ callback: () => void | Promise<void>
44
+ ): Promise<void> => {
45
+ if (!document.startViewTransition) {
46
+ await callback();
47
+ return;
48
+ }
49
+
50
+ const viewTransitionName = optionsRef.current?.name;
51
+
52
+ const setupStyles = () => {
53
+ if (viewTransitionName) {
54
+ const styleEl = document.createElement('style');
55
+ styleEl.id = `view-transition-${viewTransitionName}`;
56
+ styleEl.textContent = `
57
+ ::view-transition-old(${viewTransitionName}),
58
+ ::view-transition-new(${viewTransitionName}) {
59
+ animation-duration: 300ms;
60
+ animation-timing-function: ease-out;
61
+ }
62
+ `;
63
+ document.head.appendChild(styleEl);
64
+ return () => {
65
+ styleEl.remove();
66
+ };
67
+ }
68
+ return () => {};
69
+ };
70
+
71
+ const cleanup = setupStyles();
72
+
73
+ try {
74
+ const transition = document.startViewTransition(callback);
75
+ await transition.finished;
76
+ } finally {
77
+ cleanup();
78
+ }
79
+ }, []);
80
+
81
+ return {
82
+ isSupported,
83
+ startTransition,
84
+ setTypes
85
+ };
86
+ }
@@ -0,0 +1,100 @@
1
+ import { useCallback, useRef, useEffect } from 'react';
2
+ import { useFLIPClipPath } from './techniques/useFLIPClipPath';
3
+ import { useCSSGridMorph } from './techniques/useCSSGridMorph';
4
+ import { useViewTransitions } from './techniques/useViewTransitions';
5
+ import type { IUseMorphReturn, IUseMorphConfig, MorphTechnique } from './Morph.types';
6
+
7
+ /**
8
+ * Unified hook for morphing animations.
9
+ *
10
+ * Provides access to multiple morphing techniques:
11
+ * - FLIP + clip-path (default): Best for element-to-element transitions
12
+ * - CSS Grid: Best for expand/collapse animations
13
+ * - View Transitions: Best for page/route transitions (requires browser support)
14
+ *
15
+ * Falls back to FLIP + clip-path when View Transitions API is unsupported.
16
+ *
17
+ * @param options - Technique selection, timing, and callbacks
18
+ * @returns Unified morph API with access to individual techniques
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const morph = useMorph({ technique: 'flip-clip-path' });
23
+ *
24
+ * // Unified API:
25
+ * await morph.morph(fromElement, toElement);
26
+ *
27
+ * // Or individual techniques:
28
+ * morph.cssGrid.toggle();
29
+ * ```
30
+ */
31
+ export function useMorph(options?: IUseMorphConfig): IUseMorphReturn {
32
+ const technique: MorphTechnique = options?.technique ?? 'flip-clip-path';
33
+
34
+ const optionsRef = useRef(options);
35
+ useEffect(() => {
36
+ optionsRef.current = options;
37
+ }, [options]);
38
+
39
+ const flipClipPath = useFLIPClipPath({
40
+ duration: options?.duration,
41
+ easing: options?.easing
42
+ });
43
+
44
+ const cssGrid = useCSSGridMorph({
45
+ duration: options?.duration,
46
+ easing: options?.easing
47
+ });
48
+
49
+ const viewTransitions = useViewTransitions();
50
+
51
+ const isMorphing = flipClipPath.isMorphing;
52
+
53
+ const morph = useCallback(async (
54
+ fromElement: HTMLElement,
55
+ toElement: HTMLElement
56
+ ): Promise<void> => {
57
+ optionsRef.current?.onMorphStart?.();
58
+
59
+ const currentTechnique = optionsRef.current?.technique ?? 'flip-clip-path';
60
+
61
+ switch (currentTechnique) {
62
+ case 'flip-clip-path':
63
+ await flipClipPath.morph(fromElement, toElement);
64
+ break;
65
+
66
+ case 'view-transitions':
67
+ if (viewTransitions.isSupported) {
68
+ await viewTransitions.startTransition(() => {
69
+ fromElement.style.opacity = '0';
70
+ toElement.style.opacity = '1';
71
+ });
72
+ } else {
73
+ // Fallback to FLIP when View Transitions unsupported
74
+ await flipClipPath.morph(fromElement, toElement);
75
+ }
76
+ break;
77
+
78
+ case 'css-grid':
79
+ cssGrid.toggle();
80
+ break;
81
+ }
82
+
83
+ optionsRef.current?.onMorphEnd?.();
84
+ }, [flipClipPath, viewTransitions, cssGrid]);
85
+
86
+ const cancel = useCallback(() => {
87
+ flipClipPath.cancel();
88
+ }, [flipClipPath]);
89
+
90
+ return {
91
+ isMorphing,
92
+ technique,
93
+ isViewTransitionsSupported: viewTransitions.isSupported,
94
+ morph,
95
+ cancel,
96
+ flipClipPath,
97
+ cssGrid,
98
+ viewTransitions
99
+ };
100
+ }
@@ -0,0 +1,177 @@
1
+ import type { ReactNode, ElementType } from 'react';
2
+ import type {
3
+ IAnimationOrchestratorAPI,
4
+ FLIPBehavior,
5
+ ExitPositionStrategy
6
+ } from '../core/types';
7
+
8
+ // Re-export core types used by consumers
9
+ export type { FLIPBehavior, ExitPositionStrategy } from '../core/types';
10
+
11
+ /**
12
+ * Layout direction for reorder items.
13
+ *
14
+ * - `auto`: No layout applied (consumer handles positioning)
15
+ * - `horizontal`: Flexbox row layout with wrapping
16
+ * - `inline-horizontal`: Inline-flexbox row layout without wrapping
17
+ * - `vertical`: Flexbox column layout
18
+ * - `grid`: CSS Grid with auto-fill columns
19
+ */
20
+ export type ReorderLayout = 'auto' | 'horizontal' | 'inline-horizontal' | 'vertical' | 'grid';
21
+
22
+ /**
23
+ * Stagger configuration for animations.
24
+ * A single number applies to both enter/exit; an object allows separate values.
25
+ */
26
+ export type StaggerConfig = number | { enter: number; exit: number };
27
+
28
+ /**
29
+ * Duration configuration for animations.
30
+ * A single number applies to both enter/exit; an object allows separate values.
31
+ */
32
+ export type DurationConfig = number | { enter: number; exit: number };
33
+
34
+ /**
35
+ * Animation state for individual reorder items.
36
+ */
37
+ export interface IReorderItemState {
38
+ /** True when the item is currently animating out */
39
+ isExiting: boolean;
40
+ /** True when the item is currently animating in */
41
+ isEntering: boolean;
42
+ }
43
+
44
+ /**
45
+ * Props for Reorder container component.
46
+ * Agnostic primitive for animated list reordering using FLIP technique.
47
+ */
48
+ export interface IReorderProps {
49
+ /** Child elements to be animated. Each child should have a unique `key` prop */
50
+ children: ReactNode;
51
+ /** Layout arrangement for child elements */
52
+ layout?: ReorderLayout;
53
+ /** Stagger delay between item animations */
54
+ stagger?: StaggerConfig;
55
+ /** Animation duration in milliseconds */
56
+ duration?: DurationConfig;
57
+ /** Additional CSS class name for the container */
58
+ className?: string;
59
+ /** FLIP animation behavior strategy */
60
+ flipBehavior?: FLIPBehavior;
61
+ /** Strategy for positioning elements during exit animation */
62
+ exitPositionStrategy?: ExitPositionStrategy;
63
+ /** Callback fired when an item starts its exit animation */
64
+ onItemExit?: (id: string) => void;
65
+ /** Callback fired when an item completes its enter animation */
66
+ onItemEnter?: (id: string) => void;
67
+ }
68
+
69
+ /**
70
+ * Configuration options for useReorder hook.
71
+ * All timing values are in milliseconds.
72
+ */
73
+ export interface IUseReorderConfig {
74
+ /** Duration of enter animation in milliseconds. @default 200 */
75
+ enterDuration?: number;
76
+ /** Duration of exit animation in milliseconds. @default 180 */
77
+ exitDuration?: number;
78
+ /** Duration of FLIP animation in milliseconds. @default 300 */
79
+ flipDuration?: number;
80
+ /** Easing function for enter animation */
81
+ enterEasing?: string;
82
+ /** Easing function for exit animation */
83
+ exitEasing?: string;
84
+ /** Easing function for FLIP animation */
85
+ flipEasing?: string;
86
+ /** FLIP animation behavior strategy. @default 'all' */
87
+ flipBehavior?: FLIPBehavior;
88
+ /** Strategy for positioning elements during exit animation. @default 'absolute-fixed' */
89
+ exitPositionStrategy?: ExitPositionStrategy;
90
+ /** Callback called when an item's exit animation completes */
91
+ onComplete?: (id: string) => void;
92
+ }
93
+
94
+ /**
95
+ * Return type for useReorder hook.
96
+ * Extends the full AnimationOrchestratorAPI with semantic aliases.
97
+ */
98
+ export interface IUseReorderReturn extends IAnimationOrchestratorAPI {
99
+ /** Start exit animation for an item (alias for startExit) */
100
+ startItemExit: (id: string) => Promise<void>;
101
+ /** Start enter animation for an item (alias for startEnter) */
102
+ startItemEnter: (id: string) => Promise<void>;
103
+ }
104
+
105
+ /**
106
+ * Context value passed to Reorder children.
107
+ * @internal Consumed by ReorderItem component.
108
+ */
109
+ export interface IReorderContextValue {
110
+ /** Reorder instance with animation methods */
111
+ reorder: IUseReorderReturn;
112
+ /** Current layout setting */
113
+ layout: ReorderLayout;
114
+ /** Check if an item is currently exiting */
115
+ isExiting: (id: string) => boolean;
116
+ /** Check if an item is currently entering */
117
+ isEntering: (id: string) => boolean;
118
+ /** Array of item IDs currently exiting */
119
+ exitingIds: string[];
120
+ /** Array of item IDs currently entering */
121
+ enteringIds: string[];
122
+ }
123
+
124
+ /**
125
+ * Configuration for useReorderPresence hook.
126
+ * An alternative to the Reorder component giving full control
127
+ * over rendering while handling animation orchestration.
128
+ */
129
+ export interface IUseReorderPresenceConfig {
130
+ /** Enable automatic enter animations for new children. @default true */
131
+ autoAnimate?: boolean;
132
+ /** Stagger delay between item animations */
133
+ stagger?: StaggerConfig;
134
+ /** Duration of enter animation in milliseconds */
135
+ enterDuration?: number;
136
+ /** Duration of exit animation in milliseconds */
137
+ exitDuration?: number;
138
+ /** Duration of FLIP animation in milliseconds */
139
+ flipDuration?: number;
140
+ /** Easing function for enter animation */
141
+ enterEasing?: string;
142
+ /** Easing function for exit animation */
143
+ exitEasing?: string;
144
+ /** Easing function for FLIP animation */
145
+ flipEasing?: string;
146
+ /** FLIP animation behavior strategy */
147
+ flipBehavior?: FLIPBehavior;
148
+ /** Strategy for positioning elements during exit animation */
149
+ exitPositionStrategy?: ExitPositionStrategy;
150
+ /** Callback fired when an item starts its exit animation */
151
+ onItemExit?: (id: string) => void;
152
+ /** Callback fired when an item completes its enter animation */
153
+ onItemEnter?: (id: string) => void;
154
+ /** Callback fired when an item's exit animation completes */
155
+ onAnimationComplete?: (id: string) => void;
156
+ }
157
+
158
+ /**
159
+ * Return type for useReorderPresence hook.
160
+ * Provides rendered children and methods for triggering animations.
161
+ */
162
+ export interface IUseReorderPresenceReturn {
163
+ /** Rendered children with animation data attributes applied */
164
+ presentChildren: ReactNode;
165
+ /** Trigger exit animation for an item */
166
+ triggerExit: (id: string) => void;
167
+ /** Check if an item is currently exiting */
168
+ isExiting: (id: string) => boolean;
169
+ /** Check if an item is currently entering */
170
+ isEntering: (id: string) => boolean;
171
+ /** Array of item IDs currently exiting */
172
+ exitingIds: string[];
173
+ /** Array of item IDs currently entering */
174
+ enteringIds: string[];
175
+ /** Underlying reorder instance */
176
+ reorder: IUseReorderReturn;
177
+ }