@mks2508/mks-ui 0.2.1 → 0.3.2
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.
- package/dist/react-ui/hooks/Animation/UseAutoHeight.js +7 -7
- package/dist/react-ui/hooks/DOM/UseIsInView.js +3 -3
- package/dist/react-ui/hooks/Formatting/UseListFormat.d.ts +49 -0
- package/dist/react-ui/hooks/Formatting/UseListFormat.d.ts.map +1 -0
- package/dist/react-ui/hooks/Formatting/UseListFormat.js +105 -0
- package/dist/react-ui/hooks/State/UseControlledState.js +4 -4
- package/dist/react-ui/hooks/State/UseDataState.js +5 -5
- package/dist/react-ui/hooks/index.d.ts +2 -0
- package/dist/react-ui/hooks/index.d.ts.map +1 -1
- package/dist/react-ui/hooks/index.js +1 -0
- package/dist/react-ui/index.js +22 -1
- package/dist/react-ui/lib/get-strict-context.js +3 -3
- package/dist/react-ui/primitives/CountingNumber/index.js +3 -3
- package/dist/react-ui/primitives/Highlight/index.js +26 -26
- package/dist/react-ui/primitives/Slot/index.js +3 -3
- package/dist/react-ui/primitives/index.d.ts +1 -0
- package/dist/react-ui/primitives/index.d.ts.map +1 -1
- package/dist/react-ui/primitives/index.js +18 -0
- package/dist/react-ui/primitives/waapi/Morph/Morph.types.d.ts +76 -0
- package/dist/react-ui/primitives/waapi/Morph/Morph.types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/MorphContext.d.ts +11 -0
- package/dist/react-ui/primitives/waapi/Morph/MorphContext.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/MorphContext.js +19 -0
- package/dist/react-ui/primitives/waapi/Morph/index.d.ts +23 -0
- package/dist/react-ui/primitives/waapi/Morph/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/index.js +45 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/index.d.ts +12 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.d.ts +38 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.js +78 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.d.ts +23 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.js +140 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.d.ts +28 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.js +77 -0
- package/dist/react-ui/primitives/waapi/Morph/useMorph.d.ts +27 -0
- package/dist/react-ui/primitives/waapi/Morph/useMorph.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Morph/useMorph.js +86 -0
- package/dist/react-ui/primitives/waapi/Reorder/Reorder.types.d.ts +168 -0
- package/dist/react-ui/primitives/waapi/Reorder/Reorder.types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Reorder/index.d.ts +25 -0
- package/dist/react-ui/primitives/waapi/Reorder/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Reorder/index.js +186 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorder.d.ts +26 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorder.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorder.js +48 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorderPresence.d.ts +33 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorderPresence.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Reorder/useReorderPresence.js +137 -0
- package/dist/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.d.ts +47 -0
- package/dist/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.js +72 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.d.ts +10 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.d.ts +74 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/index.d.ts +33 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingNumber/index.js +354 -0
- package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.styles.d.ts +25 -0
- package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.styles.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.types.d.ts +57 -0
- package/dist/react-ui/primitives/waapi/SlidingText/SlidingText.types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingText/index.d.ts +26 -0
- package/dist/react-ui/primitives/waapi/SlidingText/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/SlidingText/index.js +105 -0
- package/dist/react-ui/primitives/waapi/core/animationConstants.d.ts +156 -0
- package/dist/react-ui/primitives/waapi/core/animationConstants.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/animationConstants.js +180 -0
- package/dist/react-ui/primitives/waapi/core/index.d.ts +16 -0
- package/dist/react-ui/primitives/waapi/core/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/index.js +5 -0
- package/dist/react-ui/primitives/waapi/core/types.d.ts +143 -0
- package/dist/react-ui/primitives/waapi/core/types.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/useAnimationOrchestrator.d.ts +32 -0
- package/dist/react-ui/primitives/waapi/core/useAnimationOrchestrator.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/useAnimationOrchestrator.js +322 -0
- package/dist/react-ui/primitives/waapi/core/useElementRegistry.d.ts +21 -0
- package/dist/react-ui/primitives/waapi/core/useElementRegistry.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/useElementRegistry.js +65 -0
- package/dist/react-ui/primitives/waapi/core/useFLIPAnimation.d.ts +20 -0
- package/dist/react-ui/primitives/waapi/core/useFLIPAnimation.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/useFLIPAnimation.js +99 -0
- package/dist/react-ui/primitives/waapi/core/usePositionCapture.d.ts +24 -0
- package/dist/react-ui/primitives/waapi/core/usePositionCapture.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/core/usePositionCapture.js +75 -0
- package/dist/react-ui/primitives/waapi/index.d.ts +33 -0
- package/dist/react-ui/primitives/waapi/index.d.ts.map +1 -0
- package/dist/react-ui/primitives/waapi/index.js +18 -0
- package/dist/react-ui/ui/Accordion/index.js +3 -3
- package/dist/react-ui/ui/Button/index.js +8 -8
- package/dist/react-ui/ui/Combobox/index.js +2 -2
- package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts +35 -0
- package/dist/react-ui/ui/DataCard/DataCard.styles.d.ts.map +1 -0
- package/dist/react-ui/ui/DataCard/DataCard.styles.js +114 -0
- package/dist/react-ui/ui/DataCard/DataCard.types.d.ts +135 -0
- package/dist/react-ui/ui/DataCard/DataCard.types.d.ts.map +1 -0
- package/dist/react-ui/ui/DataCard/index.d.ts +129 -0
- package/dist/react-ui/ui/DataCard/index.d.ts.map +1 -0
- package/dist/react-ui/ui/DataCard/index.js +276 -0
- package/dist/react-ui/ui/Menu/index.js +2 -2
- package/dist/react-ui/ui/Switch/index.js +3 -3
- package/dist/react-ui/ui/Tabs/index.js +3 -3
- package/dist/react-ui/ui/TextFlow/TextFlow.styles.d.ts +16 -0
- package/dist/react-ui/ui/TextFlow/TextFlow.styles.d.ts.map +1 -0
- package/dist/react-ui/ui/TextFlow/TextFlow.types.d.ts +101 -0
- package/dist/react-ui/ui/TextFlow/TextFlow.types.d.ts.map +1 -0
- package/dist/react-ui/ui/TextFlow/index.d.ts +26 -0
- package/dist/react-ui/ui/TextFlow/index.d.ts.map +1 -0
- package/dist/react-ui/ui/TextFlow/index.js +187 -0
- package/dist/react-ui/ui/index.d.ts +2 -0
- package/dist/react-ui/ui/index.d.ts.map +1 -1
- package/dist/react-ui/ui/index.js +3 -0
- package/package.json +6 -2
- package/src/react-ui/hooks/Formatting/UseListFormat.ts +134 -0
- package/src/react-ui/hooks/index.ts +3 -0
- package/src/react-ui/primitives/index.ts +3 -0
- package/src/react-ui/primitives/waapi/Morph/Morph.types.ts +106 -0
- package/src/react-ui/primitives/waapi/Morph/MorphContext.tsx +21 -0
- package/src/react-ui/primitives/waapi/Morph/index.tsx +56 -0
- package/src/react-ui/primitives/waapi/Morph/techniques/index.ts +12 -0
- package/src/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.ts +88 -0
- package/src/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.ts +175 -0
- package/src/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.ts +86 -0
- package/src/react-ui/primitives/waapi/Morph/useMorph.ts +100 -0
- package/src/react-ui/primitives/waapi/Reorder/Reorder.types.ts +177 -0
- package/src/react-ui/primitives/waapi/Reorder/index.tsx +260 -0
- package/src/react-ui/primitives/waapi/Reorder/useReorder.ts +46 -0
- package/src/react-ui/primitives/waapi/Reorder/useReorderPresence.ts +208 -0
- package/src/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.ts +104 -0
- package/src/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.ts +14 -0
- package/src/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.ts +84 -0
- package/src/react-ui/primitives/waapi/SlidingNumber/index.tsx +474 -0
- package/src/react-ui/primitives/waapi/SlidingText/SlidingText.styles.ts +32 -0
- package/src/react-ui/primitives/waapi/SlidingText/SlidingText.types.ts +69 -0
- package/src/react-ui/primitives/waapi/SlidingText/index.tsx +140 -0
- package/src/react-ui/primitives/waapi/core/animationConstants.ts +215 -0
- package/src/react-ui/primitives/waapi/core/index.ts +53 -0
- package/src/react-ui/primitives/waapi/core/types.ts +200 -0
- package/src/react-ui/primitives/waapi/core/useAnimationOrchestrator.ts +429 -0
- package/src/react-ui/primitives/waapi/core/useElementRegistry.ts +80 -0
- package/src/react-ui/primitives/waapi/core/useFLIPAnimation.ts +137 -0
- package/src/react-ui/primitives/waapi/core/usePositionCapture.ts +105 -0
- package/src/react-ui/primitives/waapi/index.ts +116 -0
- package/src/react-ui/styles/animations.css +369 -0
- package/src/react-ui/ui/DataCard/DataCard.styles.ts +150 -0
- package/src/react-ui/ui/DataCard/DataCard.types.ts +146 -0
- package/src/react-ui/ui/DataCard/index.tsx +406 -0
- package/src/react-ui/ui/TextFlow/TextFlow.styles.ts +36 -0
- package/src/react-ui/ui/TextFlow/TextFlow.types.ts +118 -0
- package/src/react-ui/ui/TextFlow/index.tsx +276 -0
- package/src/react-ui/ui/index.ts +4 -0
- /package/dist/react-ui/components/MorphingPopover/{morphing-popover.module-CgbYV_HS.css → morphing-popover.module-BycNI8nU.css} +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useRef, useState, useLayoutEffect, useEffect } from 'react';
|
|
4
|
+
import type { ISlidingTextProps } from './SlidingText.types';
|
|
5
|
+
import { cn } from '@/react-ui/lib/utils';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SlidingText - Character or word-by-word text animations
|
|
9
|
+
*
|
|
10
|
+
* Animated text component with entrance/exit transitions and blur effects.
|
|
11
|
+
* Supports character-level or word-level animation modes.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // Character animation (default)
|
|
16
|
+
* <SlidingText text="Hello World" mode="character" />
|
|
17
|
+
*
|
|
18
|
+
* // Word animation
|
|
19
|
+
* <SlidingText text="React Components" mode="word" />
|
|
20
|
+
*
|
|
21
|
+
* // Exit animation
|
|
22
|
+
* <SlidingText text="Goodbye" exit="exit" />
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export const SlidingText = ({
|
|
26
|
+
text,
|
|
27
|
+
mode = 'word',
|
|
28
|
+
direction = 'vertical',
|
|
29
|
+
staggerDelay = 15,
|
|
30
|
+
duration = 200,
|
|
31
|
+
easing = 'cubic-bezier(0.33, 1, 0.68, 1)',
|
|
32
|
+
blur = true,
|
|
33
|
+
widthAnimation = false,
|
|
34
|
+
initial = 'initial',
|
|
35
|
+
exit,
|
|
36
|
+
className = '',
|
|
37
|
+
style,
|
|
38
|
+
}: ISlidingTextProps): React.ReactElement => {
|
|
39
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
40
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
41
|
+
|
|
42
|
+
// Track if we've triggered the enter animation
|
|
43
|
+
const hasTriggeredEnterRef = useRef(false);
|
|
44
|
+
const [showAnimate, setShowAnimate] = useState(initial !== 'initial');
|
|
45
|
+
|
|
46
|
+
// Trigger enter animation after mount
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (initial === 'initial' && !hasTriggeredEnterRef.current) {
|
|
49
|
+
hasTriggeredEnterRef.current = true;
|
|
50
|
+
requestAnimationFrame(() => {
|
|
51
|
+
setShowAnimate(true);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}, [initial]);
|
|
55
|
+
|
|
56
|
+
// Compute the actual visual state
|
|
57
|
+
const visualState = exit === 'exit' ? 'exit' : showAnimate ? 'animate' : 'initial';
|
|
58
|
+
|
|
59
|
+
// Split text based on mode
|
|
60
|
+
const elements = mode === 'character' ? text.split('') : [text];
|
|
61
|
+
|
|
62
|
+
// Width Animation Logic
|
|
63
|
+
useLayoutEffect(() => {
|
|
64
|
+
if (!widthAnimation || !containerRef.current || !contentRef.current) return;
|
|
65
|
+
|
|
66
|
+
const container = containerRef.current;
|
|
67
|
+
const content = contentRef.current;
|
|
68
|
+
|
|
69
|
+
if (visualState === 'initial') {
|
|
70
|
+
container.style.width = '0px';
|
|
71
|
+
} else if (visualState === 'animate') {
|
|
72
|
+
const supportsInterpolateSize = CSS.supports('interpolate-size', 'allow-keywords');
|
|
73
|
+
|
|
74
|
+
if (supportsInterpolateSize) {
|
|
75
|
+
container.style.width = 'auto';
|
|
76
|
+
container.style.transition = `width ${duration}ms ${easing}`;
|
|
77
|
+
} else {
|
|
78
|
+
const targetWidth = content.scrollWidth;
|
|
79
|
+
container.style.width = `${targetWidth}px`;
|
|
80
|
+
container.style.transition = `width ${duration}ms ${easing}`;
|
|
81
|
+
|
|
82
|
+
const timer = setTimeout(() => {
|
|
83
|
+
container.style.width = 'auto';
|
|
84
|
+
}, duration);
|
|
85
|
+
return () => clearTimeout(timer);
|
|
86
|
+
}
|
|
87
|
+
} else if (visualState === 'exit') {
|
|
88
|
+
const currentWidth = container.getBoundingClientRect().width;
|
|
89
|
+
container.style.width = `${currentWidth}px`;
|
|
90
|
+
container.getBoundingClientRect(); // Force reflow
|
|
91
|
+
container.style.width = '0px';
|
|
92
|
+
container.style.transition = `width 180ms cubic-bezier(0.32, 0, 0.67, 0)`;
|
|
93
|
+
}
|
|
94
|
+
}, [visualState, widthAnimation, duration, easing, text]);
|
|
95
|
+
|
|
96
|
+
const getTransitionStyle = (index: number): React.CSSProperties => {
|
|
97
|
+
const delay = index * staggerDelay;
|
|
98
|
+
const isExit = visualState === 'exit';
|
|
99
|
+
const currentDuration = isExit ? 180 : duration;
|
|
100
|
+
const currentEasing = isExit ? 'cubic-bezier(0.32, 0, 0.67, 0)' : easing;
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
transition: `
|
|
104
|
+
opacity ${currentDuration}ms ${currentEasing} ${delay}ms,
|
|
105
|
+
transform ${currentDuration}ms ${currentEasing} ${delay}ms,
|
|
106
|
+
filter ${currentDuration}ms ${currentEasing} ${delay}ms
|
|
107
|
+
`,
|
|
108
|
+
'--blur-amount': blur ? '4px' : '0px',
|
|
109
|
+
'--offset': direction === 'vertical' ? '8px' : '16px'
|
|
110
|
+
} as React.CSSProperties;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div
|
|
115
|
+
ref={containerRef}
|
|
116
|
+
className={cn('waapi-sliding-text-container', className)}
|
|
117
|
+
style={style}
|
|
118
|
+
>
|
|
119
|
+
<div ref={contentRef} className={cn('waapi-sliding-text-content', `waapi-direction-${direction}`)}>
|
|
120
|
+
{elements.map((char, index) => (
|
|
121
|
+
<span
|
|
122
|
+
key={index}
|
|
123
|
+
className={cn('waapi-sliding-text-token',
|
|
124
|
+
visualState === 'initial' && 'enter-from',
|
|
125
|
+
visualState === 'animate' && 'enter-to',
|
|
126
|
+
visualState === 'exit' && 'exit-active'
|
|
127
|
+
)}
|
|
128
|
+
style={getTransitionStyle(index)}
|
|
129
|
+
>
|
|
130
|
+
{char}
|
|
131
|
+
</span>
|
|
132
|
+
))}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
SlidingText.displayName = 'SlidingText';
|
|
139
|
+
|
|
140
|
+
export type { ISlidingTextProps };
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// ANIMATION CONFIGURATION
|
|
3
|
+
// Single Source of Truth for all animation values.
|
|
4
|
+
// Research-based values from:
|
|
5
|
+
// - Material Design Motion
|
|
6
|
+
// - CSS-Tricks FLIP Technique
|
|
7
|
+
// - Josh W. Comeau Spring Animations
|
|
8
|
+
// - Easings.net
|
|
9
|
+
// ============================================
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// RESPONSIVE & ACCESSIBILITY HELPERS
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Obtains responsive animation duration for the current device.
|
|
17
|
+
* Returns 0 if the user prefers reduced motion, 60% on mobile.
|
|
18
|
+
* @param baseDuration - Base duration in ms
|
|
19
|
+
* @returns Adjusted duration for the current device
|
|
20
|
+
*/
|
|
21
|
+
export const getResponsiveDuration = (baseDuration: number): number => {
|
|
22
|
+
if (typeof window === 'undefined') return baseDuration;
|
|
23
|
+
|
|
24
|
+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
25
|
+
if (prefersReducedMotion) return 0;
|
|
26
|
+
|
|
27
|
+
const isMobile = window.innerWidth < 768;
|
|
28
|
+
if (isMobile) {
|
|
29
|
+
return Math.round(baseDuration * 0.6);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return baseDuration;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Obtains responsive stagger delay for the current device.
|
|
37
|
+
* Returns 0 if the user prefers reduced motion, 50% on mobile.
|
|
38
|
+
* @param baseDelay - Base delay in ms
|
|
39
|
+
* @returns Adjusted delay for the current device
|
|
40
|
+
*/
|
|
41
|
+
export const getResponsiveStagger = (baseDelay: number): number => {
|
|
42
|
+
if (typeof window === 'undefined') return baseDelay;
|
|
43
|
+
|
|
44
|
+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
45
|
+
if (prefersReducedMotion) return 0;
|
|
46
|
+
|
|
47
|
+
const isMobile = window.innerWidth < 768;
|
|
48
|
+
return isMobile ? Math.round(baseDelay * 0.5) : baseDelay;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Core timing constants (ms) */
|
|
52
|
+
export const TIMING = {
|
|
53
|
+
ENTER_DURATION: 200,
|
|
54
|
+
EXIT_DURATION: 180,
|
|
55
|
+
COLLAPSE_DURATION: 200,
|
|
56
|
+
FLIP_DURATION: 300,
|
|
57
|
+
|
|
58
|
+
ENTER_STAGGER: 15,
|
|
59
|
+
EXIT_STAGGER: 0,
|
|
60
|
+
|
|
61
|
+
COLLAPSE_DELAY: 30,
|
|
62
|
+
FLIP_DELAY_PERCENT: 0.25,
|
|
63
|
+
|
|
64
|
+
MIN_DELTA_PX: 1,
|
|
65
|
+
} as const;
|
|
66
|
+
|
|
67
|
+
/** Transform offsets (px/deg) */
|
|
68
|
+
export const TRANSFORMS = {
|
|
69
|
+
OFFSET_Y_ENTER: 8,
|
|
70
|
+
OFFSET_Y_EXIT: -8,
|
|
71
|
+
OFFSET_X: 16,
|
|
72
|
+
|
|
73
|
+
SCALE_ENTER: 0.85,
|
|
74
|
+
SCALE_EXIT: 0.85,
|
|
75
|
+
|
|
76
|
+
ROTATE_EXIT: 0,
|
|
77
|
+
} as const;
|
|
78
|
+
|
|
79
|
+
/** Visual effects */
|
|
80
|
+
export const EFFECTS = {
|
|
81
|
+
BLUR_ENTER: 4,
|
|
82
|
+
BLUR_EXIT: 2,
|
|
83
|
+
} as const;
|
|
84
|
+
|
|
85
|
+
/** Research-based easing curves (Material Design + CSS-Tricks + Josh W. Comeau) */
|
|
86
|
+
export const EASINGS = {
|
|
87
|
+
MATERIAL_DECELERATE: 'cubic-bezier(0, 0, 0.2, 1)',
|
|
88
|
+
MATERIAL_ACCELERATE: 'cubic-bezier(0.4, 0, 1, 1)',
|
|
89
|
+
MATERIAL_STANDARD: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
90
|
+
|
|
91
|
+
EASE_REORDER: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
|
|
92
|
+
|
|
93
|
+
EASE_OUT_CUBIC: 'cubic-bezier(0.33, 1, 0.68, 1)',
|
|
94
|
+
EASE_IN_CUBIC: 'cubic-bezier(0.32, 0, 0.67, 0)',
|
|
95
|
+
EASE_IN_OUT: 'cubic-bezier(0.42, 0, 0.58, 1)',
|
|
96
|
+
EASE_OUT_EXPO: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
|
97
|
+
|
|
98
|
+
EASE_FLIP: 'cubic-bezier(0.2, 0, 0.2, 1)',
|
|
99
|
+
|
|
100
|
+
SPRING_GENTLE: `linear(0, 0.009, 0.035 2.1%, 0.141 4.4%, 0.723 12.9%, 0.938 16.7%, 1.017 19.4%, 1.067, 1.099 24.3%, 1.108 26%, 1.100, 1.078 30.1%, 1.049 32.5%, 0.994 37.3%, 0.981 40.2%, 0.974 43.4%, 0.975 50.2%, 0.997 62.5%, 1.001 74.7%, 1)`,
|
|
101
|
+
|
|
102
|
+
SPRING_SNAPPY: `linear(0, 0.006, 0.024 2%, 0.096 4.2%, 0.397 9.3%, 0.861 15.8%, 1.002 18.7%, 1.093 21.4%, 1.143 24%, 1.156, 1.149 28.3%, 1.115 31.5%, 1.022 40%, 0.988 47.1%, 0.984 55.1%, 0.998 72.3%, 1.001 85.4%, 1)`,
|
|
103
|
+
} as const;
|
|
104
|
+
|
|
105
|
+
/** Responsive animation configurations with accessibility */
|
|
106
|
+
export const RESPONSIVE_CONFIGS = {
|
|
107
|
+
tokenEnter: {
|
|
108
|
+
get duration() { return getResponsiveDuration(TIMING.ENTER_DURATION); },
|
|
109
|
+
get stagger() { return getResponsiveStagger(TIMING.ENTER_STAGGER); },
|
|
110
|
+
easing: EASINGS.MATERIAL_DECELERATE,
|
|
111
|
+
blur: EFFECTS.BLUR_ENTER,
|
|
112
|
+
offsetY: TRANSFORMS.OFFSET_Y_ENTER,
|
|
113
|
+
scale: TRANSFORMS.SCALE_ENTER,
|
|
114
|
+
},
|
|
115
|
+
tokenExit: {
|
|
116
|
+
get duration() { return getResponsiveDuration(TIMING.EXIT_DURATION); },
|
|
117
|
+
get stagger() { return getResponsiveStagger(TIMING.EXIT_STAGGER); },
|
|
118
|
+
easing: EASINGS.MATERIAL_ACCELERATE,
|
|
119
|
+
blur: EFFECTS.BLUR_EXIT,
|
|
120
|
+
offsetY: TRANSFORMS.OFFSET_Y_EXIT,
|
|
121
|
+
scale: TRANSFORMS.SCALE_EXIT,
|
|
122
|
+
},
|
|
123
|
+
collapse: {
|
|
124
|
+
get duration() { return getResponsiveDuration(TIMING.COLLAPSE_DURATION); },
|
|
125
|
+
delay: TIMING.COLLAPSE_DELAY,
|
|
126
|
+
easing: EASINGS.MATERIAL_STANDARD,
|
|
127
|
+
},
|
|
128
|
+
flip: {
|
|
129
|
+
get duration() { return getResponsiveDuration(TIMING.FLIP_DURATION); },
|
|
130
|
+
delayPercent: TIMING.FLIP_DELAY_PERCENT,
|
|
131
|
+
easing: EASINGS.EASE_REORDER,
|
|
132
|
+
},
|
|
133
|
+
} as const;
|
|
134
|
+
|
|
135
|
+
/** Legacy defaults for backward compatibility */
|
|
136
|
+
export const ANIMATION_DEFAULTS = {
|
|
137
|
+
DURATION_ENTER: TIMING.ENTER_DURATION,
|
|
138
|
+
DURATION_EXIT: TIMING.EXIT_DURATION,
|
|
139
|
+
DURATION_FLIP: TIMING.FLIP_DURATION,
|
|
140
|
+
STAGGER_DELAY: TIMING.ENTER_STAGGER,
|
|
141
|
+
|
|
142
|
+
OFFSET_VERTICAL: TRANSFORMS.OFFSET_Y_ENTER,
|
|
143
|
+
OFFSET_HORIZONTAL: TRANSFORMS.OFFSET_X,
|
|
144
|
+
|
|
145
|
+
BLUR_AMOUNT: EFFECTS.BLUR_ENTER,
|
|
146
|
+
|
|
147
|
+
EASING_ENTER: EASINGS.EASE_OUT_CUBIC,
|
|
148
|
+
EASING_EXIT: EASINGS.EASE_IN_CUBIC,
|
|
149
|
+
EASING_FLIP: EASINGS.SPRING_GENTLE,
|
|
150
|
+
|
|
151
|
+
SPRING_EASING: EASINGS.SPRING_GENTLE,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** Composite presets for specific use cases */
|
|
155
|
+
export const ANIMATION_CONFIGS = {
|
|
156
|
+
tokenEnter: {
|
|
157
|
+
duration: TIMING.ENTER_DURATION,
|
|
158
|
+
stagger: TIMING.ENTER_STAGGER,
|
|
159
|
+
easing: EASINGS.MATERIAL_DECELERATE,
|
|
160
|
+
blur: EFFECTS.BLUR_ENTER,
|
|
161
|
+
offsetY: TRANSFORMS.OFFSET_Y_ENTER,
|
|
162
|
+
scale: TRANSFORMS.SCALE_ENTER,
|
|
163
|
+
},
|
|
164
|
+
tokenExit: {
|
|
165
|
+
duration: TIMING.EXIT_DURATION,
|
|
166
|
+
stagger: TIMING.EXIT_STAGGER,
|
|
167
|
+
easing: EASINGS.MATERIAL_ACCELERATE,
|
|
168
|
+
blur: EFFECTS.BLUR_EXIT,
|
|
169
|
+
offsetY: TRANSFORMS.OFFSET_Y_EXIT,
|
|
170
|
+
scale: TRANSFORMS.SCALE_EXIT,
|
|
171
|
+
},
|
|
172
|
+
collapse: {
|
|
173
|
+
duration: TIMING.COLLAPSE_DURATION,
|
|
174
|
+
delay: TIMING.COLLAPSE_DELAY,
|
|
175
|
+
easing: EASINGS.MATERIAL_STANDARD,
|
|
176
|
+
},
|
|
177
|
+
flip: {
|
|
178
|
+
duration: TIMING.FLIP_DURATION,
|
|
179
|
+
delayPercent: TIMING.FLIP_DELAY_PERCENT,
|
|
180
|
+
easing: EASINGS.EASE_REORDER,
|
|
181
|
+
},
|
|
182
|
+
} as const;
|
|
183
|
+
|
|
184
|
+
/** High-level animation presets */
|
|
185
|
+
export const PRESETS = {
|
|
186
|
+
newToken: {
|
|
187
|
+
mode: 'character' as const,
|
|
188
|
+
direction: 'vertical' as const,
|
|
189
|
+
staggerDelay: 15,
|
|
190
|
+
blur: true,
|
|
191
|
+
widthAnimation: true,
|
|
192
|
+
duration: 200,
|
|
193
|
+
initial: 'initial' as const,
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
existingToken: {
|
|
197
|
+
mode: 'none' as const,
|
|
198
|
+
blur: false,
|
|
199
|
+
widthAnimation: false,
|
|
200
|
+
initial: false as const,
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
placeholder: {
|
|
204
|
+
mode: 'word' as const,
|
|
205
|
+
direction: 'vertical' as const,
|
|
206
|
+
blur: true,
|
|
207
|
+
widthAnimation: false,
|
|
208
|
+
duration: 150,
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
separator: {
|
|
212
|
+
duration: 100,
|
|
213
|
+
widthAnimation: true,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WAAPI Core — FLIP animation infrastructure.
|
|
3
|
+
*
|
|
4
|
+
* Provides the composable hooks and constants that power
|
|
5
|
+
* Reorder, Morph, and other WAAPI primitives.
|
|
6
|
+
*
|
|
7
|
+
* @module primitives/waapi/core
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Constants (SSOT)
|
|
11
|
+
export {
|
|
12
|
+
TIMING,
|
|
13
|
+
TRANSFORMS,
|
|
14
|
+
EFFECTS,
|
|
15
|
+
EASINGS,
|
|
16
|
+
RESPONSIVE_CONFIGS,
|
|
17
|
+
ANIMATION_CONFIGS,
|
|
18
|
+
ANIMATION_DEFAULTS,
|
|
19
|
+
PRESETS,
|
|
20
|
+
getResponsiveDuration,
|
|
21
|
+
getResponsiveStagger,
|
|
22
|
+
} from './animationConstants';
|
|
23
|
+
|
|
24
|
+
// Core types
|
|
25
|
+
export type {
|
|
26
|
+
IFLIPDelta,
|
|
27
|
+
IAnimationTiming,
|
|
28
|
+
IPositionRect,
|
|
29
|
+
IElementRegistryAPI,
|
|
30
|
+
IElementRegistryCallbacks,
|
|
31
|
+
IPositionCaptureAPI,
|
|
32
|
+
IPositionCaptureOptions,
|
|
33
|
+
IFLIPAnimationAPI,
|
|
34
|
+
IFLIPAnimationOptions,
|
|
35
|
+
IExitOptions,
|
|
36
|
+
IEnterOptions,
|
|
37
|
+
IAnimationOrchestratorConfig,
|
|
38
|
+
IAnimationOrchestratorAPI,
|
|
39
|
+
IOrchestratorState,
|
|
40
|
+
IAnimationEvent,
|
|
41
|
+
} from './types';
|
|
42
|
+
|
|
43
|
+
export type {
|
|
44
|
+
FLIPBehavior,
|
|
45
|
+
ExitPositionStrategy,
|
|
46
|
+
AnimationPhase,
|
|
47
|
+
} from './types';
|
|
48
|
+
|
|
49
|
+
// Core hooks
|
|
50
|
+
export { useElementRegistry } from './useElementRegistry';
|
|
51
|
+
export { usePositionCapture } from './usePositionCapture';
|
|
52
|
+
export { useFLIPAnimation } from './useFLIPAnimation';
|
|
53
|
+
export { useAnimationOrchestrator } from './useAnimationOrchestrator';
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for headless animation primitives.
|
|
3
|
+
* Based on WAAPI (Web Animations API) patterns.
|
|
4
|
+
*
|
|
5
|
+
* @module primitives/waapi/core/types
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// FLIP ANIMATION TYPES
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
/** Calculated delta between two captured positions of an element */
|
|
13
|
+
export interface IFLIPDelta {
|
|
14
|
+
id: string;
|
|
15
|
+
deltaX: number;
|
|
16
|
+
deltaY: number;
|
|
17
|
+
deltaWidth: number;
|
|
18
|
+
deltaHeight: number;
|
|
19
|
+
isSignificant: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Timing configuration for a single animation */
|
|
23
|
+
export interface IAnimationTiming {
|
|
24
|
+
duration: number;
|
|
25
|
+
delay?: number;
|
|
26
|
+
easing: string;
|
|
27
|
+
fill?: FillMode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Captured position rectangle */
|
|
31
|
+
export interface IPositionRect {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// ELEMENT REGISTRY API
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
/** API for tracking DOM elements by ID */
|
|
43
|
+
export interface IElementRegistryAPI {
|
|
44
|
+
register: (id: string, el: HTMLElement | null) => void;
|
|
45
|
+
unregister: (id: string) => void;
|
|
46
|
+
get: (id: string) => HTMLElement | undefined;
|
|
47
|
+
getAll: () => Map<string, HTMLElement>;
|
|
48
|
+
has: (id: string) => boolean;
|
|
49
|
+
clear: () => void;
|
|
50
|
+
size: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Callbacks for element registry lifecycle events */
|
|
54
|
+
export interface IElementRegistryCallbacks {
|
|
55
|
+
onRegister?: (id: string, el: HTMLElement) => void;
|
|
56
|
+
onUnregister?: (id: string) => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// POSITION CAPTURE API
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
/** API for capturing element positions and calculating FLIP deltas */
|
|
64
|
+
export interface IPositionCaptureAPI {
|
|
65
|
+
capture: (excludeIds?: Set<string>) => Map<string, DOMRect>;
|
|
66
|
+
getPosition: (id: string) => DOMRect | undefined;
|
|
67
|
+
calculateDeltas: (before: Map<string, DOMRect>, after: Map<string, DOMRect>) => Map<string, IFLIPDelta>;
|
|
68
|
+
getLastCapture: () => Map<string, DOMRect>;
|
|
69
|
+
clear: () => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Options for position capture behavior */
|
|
73
|
+
export interface IPositionCaptureOptions {
|
|
74
|
+
minDeltaPx?: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// =============================================================================
|
|
78
|
+
// FLIP ANIMATION API
|
|
79
|
+
// =============================================================================
|
|
80
|
+
|
|
81
|
+
/** Options for individual FLIP animations */
|
|
82
|
+
export interface IFLIPAnimationOptions {
|
|
83
|
+
duration?: number;
|
|
84
|
+
easing?: string;
|
|
85
|
+
onStart?: (id: string) => void;
|
|
86
|
+
onComplete?: (id: string) => void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** API for executing FLIP animations */
|
|
90
|
+
export interface IFLIPAnimationAPI {
|
|
91
|
+
animate: (element: HTMLElement, delta: IFLIPDelta, options?: IFLIPAnimationOptions) => Animation;
|
|
92
|
+
animateAll: (elements: Map<string, HTMLElement>, deltas: Map<string, IFLIPDelta>, options?: IFLIPAnimationOptions) => Promise<void>;
|
|
93
|
+
cancel: (id: string) => void;
|
|
94
|
+
cancelAll: () => void;
|
|
95
|
+
isAnimating: (id?: string) => boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// =============================================================================
|
|
99
|
+
// EXIT ANIMATION TYPES
|
|
100
|
+
// =============================================================================
|
|
101
|
+
|
|
102
|
+
/** Options for exit animations */
|
|
103
|
+
export interface IExitOptions {
|
|
104
|
+
duration?: number;
|
|
105
|
+
easing?: string;
|
|
106
|
+
collapseWidth?: boolean;
|
|
107
|
+
additionalIds?: string[];
|
|
108
|
+
onComplete?: () => void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Options for enter animations */
|
|
112
|
+
export interface IEnterOptions {
|
|
113
|
+
duration?: number;
|
|
114
|
+
easing?: string;
|
|
115
|
+
stagger?: number;
|
|
116
|
+
onComplete?: () => void;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// =============================================================================
|
|
120
|
+
// FLIP BEHAVIOR & POSITIONING STRATEGIES
|
|
121
|
+
// =============================================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Controls which elements animate when an item exits.
|
|
125
|
+
* - `'all'`: All remaining elements animate to fill the gap (default)
|
|
126
|
+
* - `'siblings-after'`: Only elements after the exiting one animate
|
|
127
|
+
* - `'none'`: No FLIP animation, item exits in-place
|
|
128
|
+
*/
|
|
129
|
+
export type FLIPBehavior = 'all' | 'siblings-after' | 'none';
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Controls how the exiting element is positioned during exit animation.
|
|
133
|
+
* - `'absolute-fixed'`: Position absolute with fixed coordinates (default)
|
|
134
|
+
* - `'in-place'`: Stay in flow, no position change
|
|
135
|
+
* - `'custom'`: Consumer handles positioning via CSS/callbacks
|
|
136
|
+
*/
|
|
137
|
+
export type ExitPositionStrategy = 'absolute-fixed' | 'in-place' | 'custom';
|
|
138
|
+
|
|
139
|
+
// =============================================================================
|
|
140
|
+
// ANIMATION ORCHESTRATOR API
|
|
141
|
+
// =============================================================================
|
|
142
|
+
|
|
143
|
+
/** Configuration for the animation orchestrator */
|
|
144
|
+
export interface IAnimationOrchestratorConfig {
|
|
145
|
+
exitDuration?: number;
|
|
146
|
+
flipDuration?: number;
|
|
147
|
+
enterDuration?: number;
|
|
148
|
+
exitEasing?: string;
|
|
149
|
+
flipEasing?: string;
|
|
150
|
+
enterEasing?: string;
|
|
151
|
+
|
|
152
|
+
flipBehavior?: FLIPBehavior;
|
|
153
|
+
exitPositionStrategy?: ExitPositionStrategy;
|
|
154
|
+
|
|
155
|
+
minDeltaPx?: number;
|
|
156
|
+
|
|
157
|
+
onExitComplete?: (id: string) => void;
|
|
158
|
+
onEnterComplete?: (id: string) => void;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Full API surface of the animation orchestrator */
|
|
162
|
+
export interface IAnimationOrchestratorAPI {
|
|
163
|
+
registry: IElementRegistryAPI;
|
|
164
|
+
positions: IPositionCaptureAPI;
|
|
165
|
+
flip: IFLIPAnimationAPI;
|
|
166
|
+
|
|
167
|
+
registerElement: (id: string, el: HTMLElement | null) => void;
|
|
168
|
+
startExit: (id: string, options?: IExitOptions) => Promise<void>;
|
|
169
|
+
startEnter: (id: string, options?: IEnterOptions) => Promise<void>;
|
|
170
|
+
isAnimating: (id?: string) => boolean;
|
|
171
|
+
cancelAnimation: (id: string) => void;
|
|
172
|
+
cancelAllAnimations: () => void;
|
|
173
|
+
capturePositions: (excludeIds?: Set<string>) => Map<string, DOMRect>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// =============================================================================
|
|
177
|
+
// INTERNAL STATE TYPES
|
|
178
|
+
// =============================================================================
|
|
179
|
+
|
|
180
|
+
/** Internal state tracked by the orchestrator */
|
|
181
|
+
export interface IOrchestratorState {
|
|
182
|
+
animatingIds: Set<string>;
|
|
183
|
+
positions: Map<string, DOMRect>;
|
|
184
|
+
activeAnimations: Map<string, Animation[]>;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// =============================================================================
|
|
188
|
+
// UTILITY TYPES
|
|
189
|
+
// =============================================================================
|
|
190
|
+
|
|
191
|
+
/** Phase of an animation lifecycle */
|
|
192
|
+
export type AnimationPhase = 'idle' | 'exit' | 'flip' | 'enter' | 'completed';
|
|
193
|
+
|
|
194
|
+
/** Animation event emitted during orchestration */
|
|
195
|
+
export interface IAnimationEvent {
|
|
196
|
+
id: string;
|
|
197
|
+
phase: AnimationPhase;
|
|
198
|
+
timestamp: number;
|
|
199
|
+
duration?: number;
|
|
200
|
+
}
|