@mks2508/mks-ui 0.5.7 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react-ui/index.js +2 -2
- package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts +2 -0
- package/dist/react-ui/primitives/waapi/Gooey/Gooey.types.d.ts.map +1 -1
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts +1 -1
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.d.ts.map +1 -1
- package/dist/react-ui/primitives/waapi/Gooey/GooeyCanvas.js +173 -43
- package/dist/react-ui/ui/Dialog/Dialog.styles.d.ts.map +1 -1
- package/dist/react-ui/ui/Dialog/Dialog.styles.js +1 -0
- package/dist/react-ui/ui/Dialog/Dialog.types.d.ts +20 -1
- package/dist/react-ui/ui/Dialog/Dialog.types.d.ts.map +1 -1
- package/dist/react-ui/ui/Dialog/index.d.ts +28 -7
- package/dist/react-ui/ui/Dialog/index.d.ts.map +1 -1
- package/dist/react-ui/ui/Dialog/index.js +32 -5
- package/dist/react-ui/ui/DynamicToggle/{DynamicToggle-DOR3Ld-k.css → DynamicToggle-DJLwEkHr.css} +12 -19
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.css +12 -20
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.d.ts +1 -0
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.styles.js +7 -1
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts +55 -31
- package/dist/react-ui/ui/DynamicToggle/DynamicToggle.types.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/index.d.ts +9 -3
- package/dist/react-ui/ui/DynamicToggle/index.d.ts.map +1 -1
- package/dist/react-ui/ui/DynamicToggle/index.js +47 -34
- package/dist/react-ui/ui/index.js +1 -1
- package/package.json +52 -13
- package/src/assets/react.svg +0 -1
- package/src/core/index.ts +0 -7
- package/src/core/types.ts +0 -82
- package/src/css.d.ts +0 -7
- package/src/index.css +0 -129
- package/src/index.ts +0 -29
- package/src/react-ui/blocks/Terminal/ResttyAdapter.ts +0 -278
- package/src/react-ui/blocks/Terminal/Terminal.adapter.ts +0 -97
- package/src/react-ui/blocks/Terminal/Terminal.theme.restty.ts +0 -155
- package/src/react-ui/blocks/Terminal/Terminal.theme.ts +0 -80
- package/src/react-ui/blocks/Terminal/Terminal.types.ts +0 -438
- package/src/react-ui/blocks/Terminal/TerminalDisplay.styles.ts +0 -38
- package/src/react-ui/blocks/Terminal/TerminalDisplay.tsx +0 -254
- package/src/react-ui/blocks/Terminal/TerminalDisplay.types.ts +0 -73
- package/src/react-ui/blocks/Terminal/TerminalPanel.tsx +0 -269
- package/src/react-ui/blocks/Terminal/TerminalRestty.tsx +0 -326
- package/src/react-ui/blocks/Terminal/TerminalXterm.tsx +0 -230
- package/src/react-ui/blocks/Terminal/XTermAdapter.ts +0 -163
- package/src/react-ui/blocks/Terminal/chrome.ts +0 -25
- package/src/react-ui/blocks/Terminal/components/LogLineBadges.tsx +0 -316
- package/src/react-ui/blocks/Terminal/components/SpecializedSyntaxHighlighter.tsx +0 -218
- package/src/react-ui/blocks/Terminal/components/SyntaxHighlight.tsx +0 -386
- package/src/react-ui/blocks/Terminal/components/TerminalLogBadge.tsx +0 -67
- package/src/react-ui/blocks/Terminal/components/index.ts +0 -10
- package/src/react-ui/blocks/Terminal/display.ts +0 -46
- package/src/react-ui/blocks/Terminal/hooks/index.ts +0 -22
- package/src/react-ui/blocks/Terminal/hooks/useTerminalSettings.ts +0 -229
- package/src/react-ui/blocks/Terminal/hooks/useTerminalWebSocket.ts +0 -292
- package/src/react-ui/blocks/Terminal/index.ts +0 -111
- package/src/react-ui/blocks/Terminal/panel/LogLinesViewer.tsx +0 -330
- package/src/react-ui/blocks/Terminal/panel/TerminalDebugPanel.tsx +0 -242
- package/src/react-ui/blocks/Terminal/panel/TerminalFilterDropdown.tsx +0 -202
- package/src/react-ui/blocks/Terminal/panel/TerminalFilterTabs.tsx +0 -140
- package/src/react-ui/blocks/Terminal/panel/TerminalInteractivePanel.tsx +0 -68
- package/src/react-ui/blocks/Terminal/panel/TerminalInteractivePanel.types.ts +0 -85
- package/src/react-ui/blocks/Terminal/panel/TerminalInteractivePanelRestty.tsx +0 -383
- package/src/react-ui/blocks/Terminal/panel/TerminalInteractivePanelXterm.tsx +0 -439
- package/src/react-ui/blocks/Terminal/panel/TerminalLogsPanel.tsx +0 -550
- package/src/react-ui/blocks/Terminal/panel/TerminalLogsPanel.types.ts +0 -259
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelChrome.styles.ts +0 -75
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelChrome.tsx +0 -266
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelChrome.types.ts +0 -82
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelFooter.tsx +0 -112
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelHeader.tsx +0 -178
- package/src/react-ui/blocks/Terminal/panel/TerminalPanelToolbar.tsx +0 -203
- package/src/react-ui/blocks/Terminal/panel/TerminalSessionControl.tsx +0 -252
- package/src/react-ui/blocks/Terminal/panel/TerminalSessionTabs.tsx +0 -334
- package/src/react-ui/blocks/Terminal/panel/TerminalSettingsPopover.tsx +0 -261
- package/src/react-ui/blocks/Terminal/panel/TerminalThemeSelector.tsx +0 -248
- package/src/react-ui/blocks/Terminal/panel/index.ts +0 -72
- package/src/react-ui/blocks/Terminal/panel/terminal-filter-dropdown.module.css +0 -59
- package/src/react-ui/blocks/Terminal/panel/terminal-session-tabs.module.css +0 -59
- package/src/react-ui/blocks/Terminal/parsing/BadgeFormatter.ts +0 -180
- package/src/react-ui/blocks/Terminal/parsing/HttpLogParser.ts +0 -248
- package/src/react-ui/blocks/Terminal/parsing/LogParser.types.ts +0 -283
- package/src/react-ui/blocks/Terminal/parsing/LogParserService.ts +0 -686
- package/src/react-ui/blocks/Terminal/parsing/MultilineAggregator.ts +0 -466
- package/src/react-ui/blocks/Terminal/parsing/PersistentLogBuffer.ts +0 -343
- package/src/react-ui/blocks/Terminal/parsing/SyntaxHighlighter.ts +0 -167
- package/src/react-ui/blocks/Terminal/parsing/TableParser.ts +0 -348
- package/src/react-ui/blocks/Terminal/parsing/ansi/AnsiColorMapper.ts +0 -251
- package/src/react-ui/blocks/Terminal/parsing/ansi/AnsiParser.ts +0 -390
- package/src/react-ui/blocks/Terminal/parsing/ansi/ansi.constants.ts +0 -320
- package/src/react-ui/blocks/Terminal/parsing/ansi/index.ts +0 -20
- package/src/react-ui/blocks/Terminal/parsing/index.ts +0 -69
- package/src/react-ui/blocks/Terminal/parsing/levels/LogLevel.types.ts +0 -68
- package/src/react-ui/blocks/Terminal/parsing/levels/LogLevelDetector.ts +0 -436
- package/src/react-ui/blocks/Terminal/parsing/levels/index.ts +0 -14
- package/src/react-ui/blocks/index.ts +0 -11
- package/src/react-ui/components/MorphingPopover/MorphingPopover.types.ts +0 -49
- package/src/react-ui/components/MorphingPopover/index.tsx +0 -186
- package/src/react-ui/components/MorphingPopover/morphing-popover.module.css +0 -153
- package/src/react-ui/components/index.ts +0 -9
- package/src/react-ui/hooks/Animation/UseAutoHeight.tsx +0 -123
- package/src/react-ui/hooks/DOM/UseIsInView.tsx +0 -44
- package/src/react-ui/hooks/Formatting/UseListFormat.ts +0 -134
- package/src/react-ui/hooks/State/UseControlledState.tsx +0 -57
- package/src/react-ui/hooks/State/UseDataState.tsx +0 -76
- package/src/react-ui/hooks/index.ts +0 -20
- package/src/react-ui/icons/index.ts +0 -12
- package/src/react-ui/icons/lucide-animated/activity.tsx +0 -109
- package/src/react-ui/icons/lucide-animated/arrow-down-to-line.tsx +0 -51
- package/src/react-ui/icons/lucide-animated/arrow-up.tsx +0 -50
- package/src/react-ui/icons/lucide-animated/bell-electric.tsx +0 -124
- package/src/react-ui/icons/lucide-animated/bell.tsx +0 -93
- package/src/react-ui/icons/lucide-animated/bot.tsx +0 -122
- package/src/react-ui/icons/lucide-animated/box.tsx +0 -117
- package/src/react-ui/icons/lucide-animated/check.tsx +0 -21
- package/src/react-ui/icons/lucide-animated/circle-check.tsx +0 -107
- package/src/react-ui/icons/lucide-animated/delete.tsx +0 -133
- package/src/react-ui/icons/lucide-animated/download.tsx +0 -99
- package/src/react-ui/icons/lucide-animated/edit-2.tsx +0 -21
- package/src/react-ui/icons/lucide-animated/globe.tsx +0 -23
- package/src/react-ui/icons/lucide-animated/home.tsx +0 -103
- package/src/react-ui/icons/lucide-animated/index.ts +0 -38
- package/src/react-ui/icons/lucide-animated/layers.tsx +0 -23
- package/src/react-ui/icons/lucide-animated/layout-panel-top.tsx +0 -143
- package/src/react-ui/icons/lucide-animated/list.tsx +0 -54
- package/src/react-ui/icons/lucide-animated/package.tsx +0 -24
- package/src/react-ui/icons/lucide-animated/palette.tsx +0 -25
- package/src/react-ui/icons/lucide-animated/plus.tsx +0 -92
- package/src/react-ui/icons/lucide-animated/refresh-cw.tsx +0 -24
- package/src/react-ui/icons/lucide-animated/rocket.tsx +0 -24
- package/src/react-ui/icons/lucide-animated/save.tsx +0 -23
- package/src/react-ui/icons/lucide-animated/search.tsx +0 -94
- package/src/react-ui/icons/lucide-animated/settings.tsx +0 -92
- package/src/react-ui/icons/lucide-animated/terminal.tsx +0 -46
- package/src/react-ui/icons/lucide-animated/trash-2.tsx +0 -25
- package/src/react-ui/icons/lucide-animated/trending-down.tsx +0 -151
- package/src/react-ui/icons/lucide-animated/trending-up.tsx +0 -150
- package/src/react-ui/icons/lucide-animated/type.tsx +0 -23
- package/src/react-ui/icons/lucide-animated/upload.tsx +0 -23
- package/src/react-ui/icons/lucide-animated/x.tsx +0 -102
- package/src/react-ui/index.ts +0 -30
- package/src/react-ui/lib/get-strict-context.tsx +0 -56
- package/src/react-ui/lib/icon-wrapper.tsx +0 -70
- package/src/react-ui/lib/index.ts +0 -9
- package/src/react-ui/lib/utils.ts +0 -24
- package/src/react-ui/primitives/AutoHeight/index.tsx +0 -74
- package/src/react-ui/primitives/CountingNumber/index.tsx +0 -147
- package/src/react-ui/primitives/Highlight/Highlight.types.ts +0 -136
- package/src/react-ui/primitives/Highlight/index.tsx +0 -577
- package/src/react-ui/primitives/Slot/index.tsx +0 -128
- package/src/react-ui/primitives/index.ts +0 -16
- package/src/react-ui/primitives/waapi/Gooey/Gooey.types.ts +0 -141
- package/src/react-ui/primitives/waapi/Gooey/GooeyCanvas.tsx +0 -217
- package/src/react-ui/primitives/waapi/Gooey/GooeyFilter.tsx +0 -77
- package/src/react-ui/primitives/waapi/Gooey/MorphPath.tsx +0 -58
- package/src/react-ui/primitives/waapi/Gooey/gooey-utils.ts +0 -253
- package/src/react-ui/primitives/waapi/Gooey/index.ts +0 -50
- package/src/react-ui/primitives/waapi/Gooey/useMorphPath.ts +0 -48
- package/src/react-ui/primitives/waapi/Morph/Morph.types.ts +0 -106
- package/src/react-ui/primitives/waapi/Morph/MorphContext.tsx +0 -21
- package/src/react-ui/primitives/waapi/Morph/index.tsx +0 -56
- package/src/react-ui/primitives/waapi/Morph/techniques/index.ts +0 -12
- package/src/react-ui/primitives/waapi/Morph/techniques/useCSSGridMorph.ts +0 -89
- package/src/react-ui/primitives/waapi/Morph/techniques/useFLIPClipPath.ts +0 -176
- package/src/react-ui/primitives/waapi/Morph/techniques/useViewTransitions.ts +0 -87
- package/src/react-ui/primitives/waapi/Morph/useMorph.ts +0 -101
- package/src/react-ui/primitives/waapi/Reorder/Reorder.types.ts +0 -177
- package/src/react-ui/primitives/waapi/Reorder/index.tsx +0 -260
- package/src/react-ui/primitives/waapi/Reorder/useReorder.ts +0 -47
- package/src/react-ui/primitives/waapi/Reorder/useReorderPresence.ts +0 -209
- package/src/react-ui/primitives/waapi/Reorder/utils/separatorCoordination.ts +0 -104
- package/src/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.styles.ts +0 -14
- package/src/react-ui/primitives/waapi/SlidingNumber/SlidingNumber.types.ts +0 -84
- package/src/react-ui/primitives/waapi/SlidingNumber/index.tsx +0 -474
- package/src/react-ui/primitives/waapi/SlidingText/SlidingText.styles.ts +0 -32
- package/src/react-ui/primitives/waapi/SlidingText/SlidingText.types.ts +0 -69
- package/src/react-ui/primitives/waapi/SlidingText/index.tsx +0 -140
- package/src/react-ui/primitives/waapi/core/animationConstants.ts +0 -215
- package/src/react-ui/primitives/waapi/core/index.ts +0 -53
- package/src/react-ui/primitives/waapi/core/types.ts +0 -200
- package/src/react-ui/primitives/waapi/core/useAnimationOrchestrator.ts +0 -430
- package/src/react-ui/primitives/waapi/core/useElementRegistry.ts +0 -81
- package/src/react-ui/primitives/waapi/core/useFLIPAnimation.ts +0 -138
- package/src/react-ui/primitives/waapi/core/usePositionCapture.ts +0 -106
- package/src/react-ui/primitives/waapi/index.ts +0 -139
- package/src/react-ui/styles/animations.css +0 -369
- package/src/react-ui/ui/Accordion/Accordion.styles.ts +0 -72
- package/src/react-ui/ui/Accordion/Accordion.types.ts +0 -199
- package/src/react-ui/ui/Accordion/index.tsx +0 -362
- package/src/react-ui/ui/AlertDialog/AlertDialog.styles.ts +0 -38
- package/src/react-ui/ui/AlertDialog/AlertDialog.types.ts +0 -296
- package/src/react-ui/ui/AlertDialog/index.tsx +0 -540
- package/src/react-ui/ui/Badge/Badge.styles.ts +0 -43
- package/src/react-ui/ui/Badge/Badge.types.ts +0 -26
- package/src/react-ui/ui/Badge/index.tsx +0 -34
- package/src/react-ui/ui/Button/Button.styles.ts +0 -57
- package/src/react-ui/ui/Button/Button.types.ts +0 -63
- package/src/react-ui/ui/Button/index.tsx +0 -155
- package/src/react-ui/ui/Card/Card.styles.ts +0 -32
- package/src/react-ui/ui/Card/Card.types.ts +0 -39
- package/src/react-ui/ui/Card/index.tsx +0 -130
- package/src/react-ui/ui/Checkbox/Checkbox.styles.ts +0 -40
- package/src/react-ui/ui/Checkbox/Checkbox.types.ts +0 -98
- package/src/react-ui/ui/Checkbox/index.tsx +0 -233
- package/src/react-ui/ui/Combobox/Combobox.styles.ts +0 -34
- package/src/react-ui/ui/Combobox/Combobox.types.ts +0 -89
- package/src/react-ui/ui/Combobox/index.tsx +0 -331
- package/src/react-ui/ui/CornerBracket/CornerBracket.styles.ts +0 -38
- package/src/react-ui/ui/CornerBracket/CornerBracket.types.ts +0 -15
- package/src/react-ui/ui/CornerBracket/index.tsx +0 -49
- package/src/react-ui/ui/DataCard/DataCard.styles.ts +0 -94
- package/src/react-ui/ui/DataCard/DataCard.types.ts +0 -125
- package/src/react-ui/ui/DataCard/index.tsx +0 -340
- package/src/react-ui/ui/Dialog/Dialog.styles.ts +0 -59
- package/src/react-ui/ui/Dialog/Dialog.types.ts +0 -284
- package/src/react-ui/ui/Dialog/index.tsx +0 -452
- package/src/react-ui/ui/DropdownMenu/DropdownMenu.styles.ts +0 -35
- package/src/react-ui/ui/DropdownMenu/DropdownMenu.types.ts +0 -81
- package/src/react-ui/ui/DropdownMenu/index.tsx +0 -300
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.css +0 -376
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.styles.ts +0 -85
- package/src/react-ui/ui/DynamicToggle/DynamicToggle.types.ts +0 -180
- package/src/react-ui/ui/DynamicToggle/index.tsx +0 -316
- package/src/react-ui/ui/DynamicToggle/prototype-v7-ios.html +0 -413
- package/src/react-ui/ui/DynamicToggle/prototype-v7.html +0 -615
- package/src/react-ui/ui/DynamicToggle/prototype-v8-gooey-safari.html +0 -560
- package/src/react-ui/ui/DynamicToggle/prototype-v8b-react-structure.html +0 -227
- package/src/react-ui/ui/DynamicToggle/prototype.html +0 -419
- package/src/react-ui/ui/Field/Field.styles.ts +0 -47
- package/src/react-ui/ui/Field/Field.types.ts +0 -60
- package/src/react-ui/ui/Field/index.tsx +0 -254
- package/src/react-ui/ui/Input/Input.styles.ts +0 -11
- package/src/react-ui/ui/Input/Input.types.ts +0 -10
- package/src/react-ui/ui/Input/index.tsx +0 -32
- package/src/react-ui/ui/InputGroup/InputGroup.styles.ts +0 -53
- package/src/react-ui/ui/InputGroup/InputGroup.types.ts +0 -44
- package/src/react-ui/ui/InputGroup/index.tsx +0 -149
- package/src/react-ui/ui/Label/Label.styles.ts +0 -10
- package/src/react-ui/ui/Label/Label.types.ts +0 -9
- package/src/react-ui/ui/Label/index.tsx +0 -27
- package/src/react-ui/ui/Menu/Menu.styles.ts +0 -71
- package/src/react-ui/ui/Menu/Menu.types.ts +0 -425
- package/src/react-ui/ui/Menu/index.tsx +0 -900
- package/src/react-ui/ui/Popover/Popover.styles.ts +0 -55
- package/src/react-ui/ui/Popover/Popover.types.ts +0 -261
- package/src/react-ui/ui/Popover/index.tsx +0 -422
- package/src/react-ui/ui/Progress/Progress.styles.ts +0 -36
- package/src/react-ui/ui/Progress/Progress.types.ts +0 -162
- package/src/react-ui/ui/Progress/index.tsx +0 -254
- package/src/react-ui/ui/Select/Select.styles.ts +0 -30
- package/src/react-ui/ui/Select/Select.types.ts +0 -51
- package/src/react-ui/ui/Select/index.tsx +0 -225
- package/src/react-ui/ui/Separator/Separator.styles.ts +0 -10
- package/src/react-ui/ui/Separator/Separator.types.ts +0 -10
- package/src/react-ui/ui/Separator/index.tsx +0 -37
- package/src/react-ui/ui/Switch/Switch.styles.ts +0 -50
- package/src/react-ui/ui/Switch/Switch.types.ts +0 -155
- package/src/react-ui/ui/Switch/index.tsx +0 -253
- package/src/react-ui/ui/Tabs/Tabs.css +0 -39
- package/src/react-ui/ui/Tabs/Tabs.styles.ts +0 -148
- package/src/react-ui/ui/Tabs/Tabs.types.ts +0 -255
- package/src/react-ui/ui/Tabs/index.tsx +0 -529
- package/src/react-ui/ui/TextFlow/TextFlow.styles.ts +0 -36
- package/src/react-ui/ui/TextFlow/TextFlow.types.ts +0 -118
- package/src/react-ui/ui/TextFlow/index.tsx +0 -276
- package/src/react-ui/ui/Textarea/Textarea.styles.ts +0 -10
- package/src/react-ui/ui/Textarea/Textarea.types.ts +0 -9
- package/src/react-ui/ui/Textarea/index.tsx +0 -27
- package/src/react-ui/ui/Tooltip/Tooltip.styles.ts +0 -43
- package/src/react-ui/ui/Tooltip/Tooltip.types.ts +0 -253
- package/src/react-ui/ui/Tooltip/index.tsx +0 -394
- package/src/react-ui/ui/index.ts +0 -41
- package/src/types/css-modules.d.ts +0 -18
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useRef, useCallback, useEffect } from 'react';
|
|
3
|
-
import { TIMING, TRANSFORMS, EFFECTS, EASINGS } from './animationConstants';
|
|
4
|
-
import { useElementRegistry } from './useElementRegistry';
|
|
5
|
-
import { usePositionCapture } from './usePositionCapture';
|
|
6
|
-
import { useFLIPAnimation } from './useFLIPAnimation';
|
|
7
|
-
import type {
|
|
8
|
-
IAnimationOrchestratorAPI,
|
|
9
|
-
IAnimationOrchestratorConfig,
|
|
10
|
-
IExitOptions,
|
|
11
|
-
IEnterOptions,
|
|
12
|
-
IOrchestratorState
|
|
13
|
-
} from './types';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Main animation orchestrator hook.
|
|
17
|
-
* Composes useElementRegistry, usePositionCapture, and useFLIPAnimation
|
|
18
|
-
* into a full FLIP exit/enter pipeline.
|
|
19
|
-
*
|
|
20
|
-
* Handles the full exit animation sequence:
|
|
21
|
-
* 1. Capture positions of remaining elements (FIRST)
|
|
22
|
-
* 2. Apply position:absolute to exiting element
|
|
23
|
-
* 3. Force synchronous reflow
|
|
24
|
-
* 4. Capture AFTER positions (LAST)
|
|
25
|
-
* 5. Calculate INVERT deltas and PLAY FLIP animations
|
|
26
|
-
* 6. Run exit animation (per-token stagger or whole-element)
|
|
27
|
-
* 7. Wait for all animations to complete
|
|
28
|
-
* 8. Cleanup and notify completion
|
|
29
|
-
*
|
|
30
|
-
* @param config - Optional orchestrator configuration
|
|
31
|
-
* @returns Full orchestrator API
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```tsx
|
|
35
|
-
* const orchestrator = useAnimationOrchestrator({
|
|
36
|
-
* onExitComplete: (id) => removeFromList(id)
|
|
37
|
-
* });
|
|
38
|
-
*
|
|
39
|
-
* <div ref={el => orchestrator.registerElement('item-1', el)} />
|
|
40
|
-
*
|
|
41
|
-
* await orchestrator.startExit('item-1');
|
|
42
|
-
* ```
|
|
43
|
-
*/
|
|
44
|
-
export function useAnimationOrchestrator(
|
|
45
|
-
config?: IAnimationOrchestratorConfig
|
|
46
|
-
): IAnimationOrchestratorAPI {
|
|
47
|
-
const configRef = useRef(config);
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
configRef.current = config;
|
|
50
|
-
}, [config]);
|
|
51
|
-
|
|
52
|
-
const stateRef = useRef<IOrchestratorState>({
|
|
53
|
-
animatingIds: new Set(),
|
|
54
|
-
positions: new Map(),
|
|
55
|
-
activeAnimations: new Map()
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const registry = useElementRegistry();
|
|
59
|
-
const positions = usePositionCapture(registry.getAll, {
|
|
60
|
-
minDeltaPx: config?.minDeltaPx ?? TIMING.MIN_DELTA_PX
|
|
61
|
-
});
|
|
62
|
-
const flip = useFLIPAnimation();
|
|
63
|
-
|
|
64
|
-
const exitDuration = config?.exitDuration ?? TIMING.EXIT_DURATION;
|
|
65
|
-
const flipDuration = config?.flipDuration ?? TIMING.FLIP_DURATION;
|
|
66
|
-
const exitEasing = config?.exitEasing ?? EASINGS.EASE_IN_CUBIC;
|
|
67
|
-
|
|
68
|
-
const cancelAnimation = useCallback((id: string) => {
|
|
69
|
-
const animations = stateRef.current.activeAnimations.get(id);
|
|
70
|
-
if (animations) {
|
|
71
|
-
animations.forEach(anim => anim.cancel());
|
|
72
|
-
stateRef.current.activeAnimations.delete(id);
|
|
73
|
-
}
|
|
74
|
-
stateRef.current.animatingIds.delete(id);
|
|
75
|
-
flip.cancel(id);
|
|
76
|
-
}, [flip]);
|
|
77
|
-
|
|
78
|
-
const cancelAllAnimations = useCallback(() => {
|
|
79
|
-
stateRef.current.activeAnimations.forEach(animations => {
|
|
80
|
-
animations.forEach(anim => anim.cancel());
|
|
81
|
-
});
|
|
82
|
-
stateRef.current.activeAnimations.clear();
|
|
83
|
-
stateRef.current.animatingIds.clear();
|
|
84
|
-
flip.cancelAll();
|
|
85
|
-
}, [flip]);
|
|
86
|
-
|
|
87
|
-
const flipBehavior = config?.flipBehavior ?? 'all';
|
|
88
|
-
const exitPositionStrategy = config?.exitPositionStrategy ?? 'absolute-fixed';
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Full FLIP exit implementation.
|
|
92
|
-
* Based on research from Paul Lewis (aerotwist.com), react-flip-move,
|
|
93
|
-
* and Framer Motion popLayout mode.
|
|
94
|
-
*/
|
|
95
|
-
const startExit = useCallback(async (id: string, options?: IExitOptions): Promise<void> => {
|
|
96
|
-
const el = registry.get(id);
|
|
97
|
-
|
|
98
|
-
if (!el || stateRef.current.animatingIds.has(id)) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
stateRef.current.animatingIds.add(id);
|
|
103
|
-
|
|
104
|
-
const duration = options?.duration ?? exitDuration;
|
|
105
|
-
const easing = options?.easing ?? exitEasing;
|
|
106
|
-
|
|
107
|
-
// ========================================
|
|
108
|
-
// STEP 1: FIRST - Capture ALL positions BEFORE any DOM/CSS changes
|
|
109
|
-
// CRITICAL: Must capture BEFORE setting data attributes!
|
|
110
|
-
// ========================================
|
|
111
|
-
const allElements = registry.getAll();
|
|
112
|
-
const beforePositions = new Map<string, DOMRect>();
|
|
113
|
-
|
|
114
|
-
const exitingRect = el.getBoundingClientRect();
|
|
115
|
-
beforePositions.set(id, exitingRect);
|
|
116
|
-
|
|
117
|
-
allElements.forEach((element, elemId) => {
|
|
118
|
-
if (elemId !== id) {
|
|
119
|
-
const rect = element.getBoundingClientRect();
|
|
120
|
-
if (rect.width > 0 && rect.height > 0) {
|
|
121
|
-
beforePositions.set(elemId, rect);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const parent = el.parentElement;
|
|
127
|
-
|
|
128
|
-
// Set data attributes (triggers CSS position:absolute)
|
|
129
|
-
el.dataset.reorderState = 'exiting';
|
|
130
|
-
if (exitPositionStrategy === 'absolute-fixed') {
|
|
131
|
-
el.dataset.exitPosition = 'absolute';
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ========================================
|
|
135
|
-
// STEP 2: Apply position:absolute to exiting element
|
|
136
|
-
// Calculate position relative to parent BEFORE it re-centers
|
|
137
|
-
// ========================================
|
|
138
|
-
const parentRectBefore = parent?.getBoundingClientRect() || { left: 0, top: 0 };
|
|
139
|
-
const absoluteLeft = exitingRect.left - parentRectBefore.left;
|
|
140
|
-
const absoluteTop = exitingRect.top - parentRectBefore.top;
|
|
141
|
-
|
|
142
|
-
if (exitPositionStrategy === 'absolute-fixed') {
|
|
143
|
-
el.style.position = 'absolute';
|
|
144
|
-
el.style.left = `${absoluteLeft}px`;
|
|
145
|
-
el.style.top = `${absoluteTop}px`;
|
|
146
|
-
el.style.width = `${exitingRect.width}px`;
|
|
147
|
-
el.style.height = `${exitingRect.height}px`;
|
|
148
|
-
el.style.margin = '0';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Compensate for parent movement after absolute positioning
|
|
152
|
-
const parentRectAfter = parent?.getBoundingClientRect() || { left: 0, top: 0 };
|
|
153
|
-
const parentDeltaX = parentRectAfter.left - parentRectBefore.left;
|
|
154
|
-
const parentDeltaY = parentRectAfter.top - parentRectBefore.top;
|
|
155
|
-
|
|
156
|
-
if (Math.abs(parentDeltaX) > 0.5 || Math.abs(parentDeltaY) > 0.5) {
|
|
157
|
-
el.style.transform = `translate(${-parentDeltaX}px, ${-parentDeltaY}px)`;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ========================================
|
|
161
|
-
// STEP 3: SYNCHRONOUS reflow via getBoundingClientRect
|
|
162
|
-
// ========================================
|
|
163
|
-
// (Forces layout recalculation before paint)
|
|
164
|
-
|
|
165
|
-
// ========================================
|
|
166
|
-
// STEP 4: LAST - Capture AFTER positions IMMEDIATELY (same frame)
|
|
167
|
-
// ========================================
|
|
168
|
-
const afterPositions = new Map<string, DOMRect>();
|
|
169
|
-
allElements.forEach((element, elemId) => {
|
|
170
|
-
if (elemId !== id) {
|
|
171
|
-
const rect = element.getBoundingClientRect();
|
|
172
|
-
if (rect.width > 0 && rect.height > 0) {
|
|
173
|
-
afterPositions.set(elemId, rect);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// ========================================
|
|
179
|
-
// STEP 5: INVERT + PLAY - Calculate deltas and animate siblings
|
|
180
|
-
// ========================================
|
|
181
|
-
const flipAnimations: Animation[] = [];
|
|
182
|
-
|
|
183
|
-
if (flipBehavior !== 'none') {
|
|
184
|
-
let siblingsToAnimate: string[] = [];
|
|
185
|
-
|
|
186
|
-
if (flipBehavior === 'all') {
|
|
187
|
-
siblingsToAnimate = [...afterPositions.keys()];
|
|
188
|
-
} else if (flipBehavior === 'siblings-after') {
|
|
189
|
-
// Sort by actual DOM order using compareDocumentPosition
|
|
190
|
-
const elementsWithIds = Array.from(allElements.entries());
|
|
191
|
-
elementsWithIds.sort((a, b) => {
|
|
192
|
-
const posA = a[1];
|
|
193
|
-
const posB = b[1];
|
|
194
|
-
const position = posA.compareDocumentPosition(posB);
|
|
195
|
-
return position & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const sortedKeys = elementsWithIds.map(([k]) => k);
|
|
199
|
-
const exitingIndex = sortedKeys.indexOf(id);
|
|
200
|
-
|
|
201
|
-
if (exitingIndex !== -1) {
|
|
202
|
-
siblingsToAnimate = sortedKeys.slice(exitingIndex + 1).filter(k => afterPositions.has(k));
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
siblingsToAnimate.forEach(siblingId => {
|
|
207
|
-
const before = beforePositions.get(siblingId);
|
|
208
|
-
const after = afterPositions.get(siblingId);
|
|
209
|
-
|
|
210
|
-
if (!before || !after) return;
|
|
211
|
-
|
|
212
|
-
const deltaX = before.left - after.left;
|
|
213
|
-
const deltaY = before.top - after.top;
|
|
214
|
-
|
|
215
|
-
const isSignificant = Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1;
|
|
216
|
-
|
|
217
|
-
if (isSignificant) {
|
|
218
|
-
const siblingEl = allElements.get(siblingId);
|
|
219
|
-
if (siblingEl) {
|
|
220
|
-
siblingEl.dataset.reorderState = 'flipping';
|
|
221
|
-
|
|
222
|
-
// Apply initial transform SYNCHRONOUSLY via CSS
|
|
223
|
-
// element.animate() does NOT apply initial keyframe synchronously
|
|
224
|
-
siblingEl.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
|
|
225
|
-
|
|
226
|
-
const anim = siblingEl.animate([
|
|
227
|
-
{ transform: `translate(${deltaX}px, ${deltaY}px)` },
|
|
228
|
-
{ transform: 'none' }
|
|
229
|
-
], {
|
|
230
|
-
duration: flipDuration,
|
|
231
|
-
easing: EASINGS.MATERIAL_DECELERATE,
|
|
232
|
-
fill: 'forwards'
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
flipAnimations.push(anim);
|
|
236
|
-
|
|
237
|
-
anim.finished.then(() => {
|
|
238
|
-
siblingEl.style.transform = '';
|
|
239
|
-
siblingEl.dataset.reorderState = 'idle';
|
|
240
|
-
}).catch(() => {
|
|
241
|
-
siblingEl.style.transform = '';
|
|
242
|
-
siblingEl.dataset.reorderState = 'idle';
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ========================================
|
|
250
|
-
// STEP 6: Animate exiting element (fade out)
|
|
251
|
-
// ========================================
|
|
252
|
-
const exitAnimations: Animation[] = [];
|
|
253
|
-
// FIX: Use .waapi-sliding-text-token (not .sliding-text-token)
|
|
254
|
-
const tokens = el.querySelectorAll('.waapi-sliding-text-token');
|
|
255
|
-
|
|
256
|
-
if (tokens.length > 0) {
|
|
257
|
-
tokens.forEach((token, index) => {
|
|
258
|
-
const delay = index * TIMING.EXIT_STAGGER;
|
|
259
|
-
const anim = (token as HTMLElement).animate([
|
|
260
|
-
{ opacity: 1, transform: 'translateY(0) scale(1)', filter: 'blur(0px)' },
|
|
261
|
-
{
|
|
262
|
-
opacity: 0,
|
|
263
|
-
transform: `translateY(${TRANSFORMS.OFFSET_Y_EXIT}px) scale(${TRANSFORMS.SCALE_EXIT})`,
|
|
264
|
-
filter: `blur(${EFFECTS.BLUR_EXIT}px)`
|
|
265
|
-
}
|
|
266
|
-
], {
|
|
267
|
-
duration,
|
|
268
|
-
easing,
|
|
269
|
-
delay,
|
|
270
|
-
fill: 'forwards'
|
|
271
|
-
});
|
|
272
|
-
exitAnimations.push(anim);
|
|
273
|
-
});
|
|
274
|
-
} else {
|
|
275
|
-
const anim = el.animate([
|
|
276
|
-
{ opacity: 1, transform: 'scale(1)' },
|
|
277
|
-
{ opacity: 0, transform: `scale(${TRANSFORMS.SCALE_EXIT})` }
|
|
278
|
-
], {
|
|
279
|
-
duration,
|
|
280
|
-
easing,
|
|
281
|
-
fill: 'forwards'
|
|
282
|
-
});
|
|
283
|
-
exitAnimations.push(anim);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
stateRef.current.activeAnimations.set(id, [...exitAnimations, ...flipAnimations]);
|
|
287
|
-
|
|
288
|
-
// ========================================
|
|
289
|
-
// STEP 7: Wait for all animations to complete
|
|
290
|
-
// ========================================
|
|
291
|
-
try {
|
|
292
|
-
await Promise.all([
|
|
293
|
-
...exitAnimations.map(a => a.finished),
|
|
294
|
-
...flipAnimations.map(a => a.finished)
|
|
295
|
-
]);
|
|
296
|
-
} catch {
|
|
297
|
-
stateRef.current.animatingIds.delete(id);
|
|
298
|
-
stateRef.current.activeAnimations.delete(id);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// ========================================
|
|
303
|
-
// STEP 8: Cleanup
|
|
304
|
-
// ========================================
|
|
305
|
-
stateRef.current.animatingIds.delete(id);
|
|
306
|
-
stateRef.current.activeAnimations.delete(id);
|
|
307
|
-
|
|
308
|
-
el.dataset.reorderState = 'completed';
|
|
309
|
-
|
|
310
|
-
if (exitPositionStrategy === 'absolute-fixed') {
|
|
311
|
-
el.style.removeProperty('position');
|
|
312
|
-
el.style.removeProperty('left');
|
|
313
|
-
el.style.removeProperty('top');
|
|
314
|
-
el.style.removeProperty('width');
|
|
315
|
-
el.style.removeProperty('height');
|
|
316
|
-
el.style.removeProperty('margin');
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
delete el.dataset.exitPosition;
|
|
320
|
-
registry.unregister(id);
|
|
321
|
-
|
|
322
|
-
options?.onComplete?.();
|
|
323
|
-
configRef.current?.onExitComplete?.(id);
|
|
324
|
-
}, [registry, exitDuration, exitEasing, flipDuration, flipBehavior, exitPositionStrategy]);
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Start enter animation for an element.
|
|
328
|
-
*
|
|
329
|
-
* Architecture: CSS + WAAPI
|
|
330
|
-
* - Assumes element already has data-reorder-state="entering" (set by Reorder.tsx)
|
|
331
|
-
* - CSS [data-reorder-state="entering"] { opacity: 0; transform: translateY(-8px) scale(0.95); }
|
|
332
|
-
* - WAAPI animates FROM that state TO visible
|
|
333
|
-
* - Sets data-reorder-state="idle" on completion
|
|
334
|
-
*/
|
|
335
|
-
const startEnter = useCallback(async (id: string, options?: IEnterOptions): Promise<void> => {
|
|
336
|
-
const el = registry.get(id);
|
|
337
|
-
if (!el) return;
|
|
338
|
-
|
|
339
|
-
// Fallback: set entering state if not already set
|
|
340
|
-
if (!el.dataset.reorderState) {
|
|
341
|
-
el.dataset.reorderState = 'entering';
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const enterDuration = configRef.current?.enterDuration ?? TIMING.ENTER_DURATION;
|
|
345
|
-
const enterEasing = configRef.current?.enterEasing ?? EASINGS.MATERIAL_DECELERATE;
|
|
346
|
-
|
|
347
|
-
const duration = options?.duration ?? enterDuration;
|
|
348
|
-
const easing = options?.easing ?? enterEasing;
|
|
349
|
-
const stagger = options?.stagger ?? TIMING.ENTER_STAGGER;
|
|
350
|
-
|
|
351
|
-
// FIX: Use .waapi-sliding-text-token (not .sliding-text-token)
|
|
352
|
-
const tokens = el.querySelectorAll('.waapi-sliding-text-token');
|
|
353
|
-
const animations: Animation[] = [];
|
|
354
|
-
|
|
355
|
-
// Keyframes match CSS entering state -> visible
|
|
356
|
-
const fromKeyframe = {
|
|
357
|
-
opacity: 0,
|
|
358
|
-
transform: 'translateY(-8px) scale(0.95)'
|
|
359
|
-
};
|
|
360
|
-
const toKeyframe = {
|
|
361
|
-
opacity: 1,
|
|
362
|
-
transform: 'none'
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
if (tokens.length > 0) {
|
|
366
|
-
tokens.forEach((token, index) => {
|
|
367
|
-
const delay = index * stagger;
|
|
368
|
-
(token as HTMLElement).dataset.reorderIndex = String(index);
|
|
369
|
-
|
|
370
|
-
const anim = (token as HTMLElement).animate([
|
|
371
|
-
{ ...fromKeyframe, filter: `blur(${EFFECTS.BLUR_ENTER}px)` },
|
|
372
|
-
{ ...toKeyframe, filter: 'blur(0px)' }
|
|
373
|
-
], {
|
|
374
|
-
duration,
|
|
375
|
-
easing,
|
|
376
|
-
delay,
|
|
377
|
-
fill: 'forwards'
|
|
378
|
-
});
|
|
379
|
-
animations.push(anim);
|
|
380
|
-
});
|
|
381
|
-
} else {
|
|
382
|
-
const anim = el.animate([fromKeyframe, toKeyframe], {
|
|
383
|
-
duration,
|
|
384
|
-
easing,
|
|
385
|
-
fill: 'forwards'
|
|
386
|
-
});
|
|
387
|
-
animations.push(anim);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
try {
|
|
391
|
-
await Promise.all(animations.map(a => a.finished));
|
|
392
|
-
} catch {
|
|
393
|
-
el.dataset.reorderState = 'idle';
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
el.dataset.reorderState = 'idle';
|
|
398
|
-
|
|
399
|
-
options?.onComplete?.();
|
|
400
|
-
configRef.current?.onEnterComplete?.(id);
|
|
401
|
-
}, [registry]);
|
|
402
|
-
|
|
403
|
-
const isAnimating = useCallback((id?: string): boolean => {
|
|
404
|
-
if (id) return stateRef.current.animatingIds.has(id);
|
|
405
|
-
return stateRef.current.animatingIds.size > 0;
|
|
406
|
-
}, []);
|
|
407
|
-
|
|
408
|
-
const capturePositions = useCallback((excludeIds?: Set<string>) => {
|
|
409
|
-
return positions.capture(excludeIds);
|
|
410
|
-
}, [positions]);
|
|
411
|
-
|
|
412
|
-
useEffect(() => {
|
|
413
|
-
return () => {
|
|
414
|
-
cancelAllAnimations();
|
|
415
|
-
};
|
|
416
|
-
}, [cancelAllAnimations]);
|
|
417
|
-
|
|
418
|
-
return {
|
|
419
|
-
registry,
|
|
420
|
-
positions,
|
|
421
|
-
flip,
|
|
422
|
-
registerElement: registry.register,
|
|
423
|
-
startExit,
|
|
424
|
-
startEnter,
|
|
425
|
-
isAnimating,
|
|
426
|
-
cancelAnimation,
|
|
427
|
-
cancelAllAnimations,
|
|
428
|
-
capturePositions
|
|
429
|
-
};
|
|
430
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useRef, useCallback, useEffect } from 'react';
|
|
3
|
-
import type { IElementRegistryAPI, IElementRegistryCallbacks } from './types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Hook for tracking DOM elements by ID.
|
|
7
|
-
* Extracted from the WAAPI animation system's element registration logic.
|
|
8
|
-
*
|
|
9
|
-
* @param callbacks - Optional lifecycle callbacks for register/unregister events
|
|
10
|
-
* @returns Registry API for managing elements by ID
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```tsx
|
|
14
|
-
* const registry = useElementRegistry({
|
|
15
|
-
* onRegister: (id) => console.log(`Registered: ${id}`),
|
|
16
|
-
* onUnregister: (id) => console.log(`Unregistered: ${id}`)
|
|
17
|
-
* });
|
|
18
|
-
*
|
|
19
|
-
* // In render:
|
|
20
|
-
* <div ref={el => registry.register('item-1', el)} />
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export function useElementRegistry(
|
|
24
|
-
callbacks?: IElementRegistryCallbacks
|
|
25
|
-
): IElementRegistryAPI {
|
|
26
|
-
const elementsRef = useRef<Map<string, HTMLElement>>(new Map());
|
|
27
|
-
|
|
28
|
-
const callbacksRef = useRef(callbacks);
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
callbacksRef.current = callbacks;
|
|
31
|
-
}, [callbacks]);
|
|
32
|
-
|
|
33
|
-
const register = useCallback((id: string, el: HTMLElement | null) => {
|
|
34
|
-
if (el) {
|
|
35
|
-
elementsRef.current.set(id, el);
|
|
36
|
-
callbacksRef.current?.onRegister?.(id, el);
|
|
37
|
-
} else {
|
|
38
|
-
if (elementsRef.current.has(id)) {
|
|
39
|
-
elementsRef.current.delete(id);
|
|
40
|
-
callbacksRef.current?.onUnregister?.(id);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}, []);
|
|
44
|
-
|
|
45
|
-
const unregister = useCallback((id: string) => {
|
|
46
|
-
if (elementsRef.current.has(id)) {
|
|
47
|
-
elementsRef.current.delete(id);
|
|
48
|
-
callbacksRef.current?.onUnregister?.(id);
|
|
49
|
-
}
|
|
50
|
-
}, []);
|
|
51
|
-
|
|
52
|
-
const get = useCallback((id: string): HTMLElement | undefined => {
|
|
53
|
-
return elementsRef.current.get(id);
|
|
54
|
-
}, []);
|
|
55
|
-
|
|
56
|
-
const getAll = useCallback((): Map<string, HTMLElement> => {
|
|
57
|
-
return new Map(elementsRef.current);
|
|
58
|
-
}, []);
|
|
59
|
-
|
|
60
|
-
const has = useCallback((id: string): boolean => {
|
|
61
|
-
return elementsRef.current.has(id);
|
|
62
|
-
}, []);
|
|
63
|
-
|
|
64
|
-
const clear = useCallback(() => {
|
|
65
|
-
const ids = Array.from(elementsRef.current.keys());
|
|
66
|
-
elementsRef.current.clear();
|
|
67
|
-
ids.forEach(id => callbacksRef.current?.onUnregister?.(id));
|
|
68
|
-
}, []);
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
register,
|
|
72
|
-
unregister,
|
|
73
|
-
get,
|
|
74
|
-
getAll,
|
|
75
|
-
has,
|
|
76
|
-
clear,
|
|
77
|
-
get size() {
|
|
78
|
-
return elementsRef.current.size;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useRef, useCallback, useEffect } from 'react';
|
|
3
|
-
import { TIMING, EASINGS } from './animationConstants';
|
|
4
|
-
import type { IFLIPAnimationAPI, IFLIPAnimationOptions, IFLIPDelta } from './types';
|
|
5
|
-
|
|
6
|
-
const DEFAULT_DURATION = TIMING.FLIP_DURATION;
|
|
7
|
-
const DEFAULT_EASING = EASINGS.SPRING_GENTLE;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Hook for executing FLIP (First-Last-Invert-Play) animations.
|
|
11
|
-
* Uses spring easing for natural motion with slight overshoot.
|
|
12
|
-
*
|
|
13
|
-
* @returns FLIP animation API with animate, cancel, and status methods
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```tsx
|
|
17
|
-
* const flip = useFLIPAnimation();
|
|
18
|
-
*
|
|
19
|
-
* // After calculating deltas:
|
|
20
|
-
* await flip.animateAll(elements, deltas, {
|
|
21
|
-
* duration: 300,
|
|
22
|
-
* onComplete: (id) => console.log(`${id} completed`)
|
|
23
|
-
* });
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
export function useFLIPAnimation(): IFLIPAnimationAPI {
|
|
27
|
-
const activeAnimationsRef = useRef<Map<string, Animation>>(new Map());
|
|
28
|
-
const animatingIdsRef = useRef<Set<string>>(new Set());
|
|
29
|
-
|
|
30
|
-
const animate = useCallback((
|
|
31
|
-
element: HTMLElement,
|
|
32
|
-
delta: IFLIPDelta,
|
|
33
|
-
options?: IFLIPAnimationOptions
|
|
34
|
-
): Animation => {
|
|
35
|
-
const duration = options?.duration ?? DEFAULT_DURATION;
|
|
36
|
-
const easing = options?.easing ?? DEFAULT_EASING;
|
|
37
|
-
|
|
38
|
-
if (activeAnimationsRef.current.has(delta.id)) {
|
|
39
|
-
activeAnimationsRef.current.get(delta.id)?.cancel();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
animatingIdsRef.current.add(delta.id);
|
|
43
|
-
options?.onStart?.(delta.id);
|
|
44
|
-
|
|
45
|
-
const animation = element.animate([
|
|
46
|
-
{ transform: `translate3d(${delta.deltaX}px, ${delta.deltaY}px, 0)` },
|
|
47
|
-
{ transform: 'translate3d(0, 0, 0)' }
|
|
48
|
-
], {
|
|
49
|
-
duration,
|
|
50
|
-
easing
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
activeAnimationsRef.current.set(delta.id, animation);
|
|
54
|
-
|
|
55
|
-
animation.onfinish = () => {
|
|
56
|
-
animatingIdsRef.current.delete(delta.id);
|
|
57
|
-
activeAnimationsRef.current.delete(delta.id);
|
|
58
|
-
options?.onComplete?.(delta.id);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
animation.oncancel = () => {
|
|
62
|
-
animatingIdsRef.current.delete(delta.id);
|
|
63
|
-
activeAnimationsRef.current.delete(delta.id);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
return animation;
|
|
67
|
-
}, []);
|
|
68
|
-
|
|
69
|
-
const animateAll = useCallback(async (
|
|
70
|
-
elements: Map<string, HTMLElement>,
|
|
71
|
-
deltas: Map<string, IFLIPDelta>,
|
|
72
|
-
options?: IFLIPAnimationOptions
|
|
73
|
-
): Promise<void> => {
|
|
74
|
-
const animations: Animation[] = [];
|
|
75
|
-
|
|
76
|
-
deltas.forEach((delta, id) => {
|
|
77
|
-
if (!delta.isSignificant) return;
|
|
78
|
-
|
|
79
|
-
const element = elements.get(id);
|
|
80
|
-
if (!element) return;
|
|
81
|
-
|
|
82
|
-
const anim = animate(element, delta, options);
|
|
83
|
-
animations.push(anim);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
if (animations.length === 0) return;
|
|
87
|
-
|
|
88
|
-
await Promise.all(
|
|
89
|
-
animations.map(anim =>
|
|
90
|
-
anim.finished.catch(() => {})
|
|
91
|
-
)
|
|
92
|
-
);
|
|
93
|
-
}, [animate]);
|
|
94
|
-
|
|
95
|
-
const cancel = useCallback((id: string) => {
|
|
96
|
-
const animation = activeAnimationsRef.current.get(id);
|
|
97
|
-
if (animation) {
|
|
98
|
-
animation.cancel();
|
|
99
|
-
activeAnimationsRef.current.delete(id);
|
|
100
|
-
animatingIdsRef.current.delete(id);
|
|
101
|
-
}
|
|
102
|
-
}, []);
|
|
103
|
-
|
|
104
|
-
const cancelAll = useCallback(() => {
|
|
105
|
-
activeAnimationsRef.current.forEach(animation => animation.cancel());
|
|
106
|
-
activeAnimationsRef.current.clear();
|
|
107
|
-
animatingIdsRef.current.clear();
|
|
108
|
-
}, []);
|
|
109
|
-
|
|
110
|
-
const isAnimating = useCallback((id?: string): boolean => {
|
|
111
|
-
if (id) return animatingIdsRef.current.has(id);
|
|
112
|
-
return animatingIdsRef.current.size > 0;
|
|
113
|
-
}, []);
|
|
114
|
-
|
|
115
|
-
useEffect(() => {
|
|
116
|
-
return () => {
|
|
117
|
-
cancelAll();
|
|
118
|
-
};
|
|
119
|
-
}, [cancelAll]);
|
|
120
|
-
|
|
121
|
-
// Memoize return object to prevent cascading re-renders
|
|
122
|
-
// that would cancel animations via useEffect cleanup chains
|
|
123
|
-
const api = useRef<IFLIPAnimationAPI>({
|
|
124
|
-
animate,
|
|
125
|
-
animateAll,
|
|
126
|
-
cancel,
|
|
127
|
-
cancelAll,
|
|
128
|
-
isAnimating
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
api.current.animate = animate;
|
|
132
|
-
api.current.animateAll = animateAll;
|
|
133
|
-
api.current.cancel = cancel;
|
|
134
|
-
api.current.cancelAll = cancelAll;
|
|
135
|
-
api.current.isAnimating = isAnimating;
|
|
136
|
-
|
|
137
|
-
return api.current;
|
|
138
|
-
}
|