@sovereignfs/ui 0.54.0 → 0.83.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.
- package/dist/Accordion.module.css +1 -1
- package/dist/Alert.module.css +1 -1
- package/dist/AppsLauncher.module.css +96 -0
- package/dist/Avatar.module.css +1 -1
- package/dist/Badge.module.css +30 -7
- package/dist/BalanceChip.module.css +1 -1
- package/dist/Button.module.css +18 -2
- package/dist/Card.module.css +1 -1
- package/dist/CardTile.module.css +181 -0
- package/dist/CodeTextarea.module.css +1 -1
- package/dist/Collapsible.module.css +1 -1
- package/dist/ColorPicker.module.css +132 -0
- package/dist/Combobox.module.css +2 -2
- package/dist/Command.module.css +1 -1
- package/dist/ConfirmDialog.module.css +2 -2
- package/dist/ConsentPrompt.module.css +21 -0
- package/dist/DatePicker.module.css +1 -1
- package/dist/Dialog.module.css +130 -21
- package/dist/Drawer.module.css +36 -7
- package/dist/Header.module.css +62 -0
- package/dist/HeaderFooterLayout.module.css +21 -0
- package/dist/IconPicker.module.css +1 -1
- package/dist/Input.module.css +1 -1
- package/dist/Kbd.module.css +1 -1
- package/dist/Markdown.module.css +24 -0
- package/dist/Menu.module.css +1 -0
- package/dist/Message.module.css +1 -1
- package/dist/MessageScroller.module.css +12 -1
- package/dist/MobileAppsDrawer.module.css +10 -9
- package/dist/MobileFooter.module.css +1 -1
- package/dist/MobileHeader.module.css +14 -1
- package/dist/NavList.module.css +130 -0
- package/dist/NavTabs.module.css +1 -1
- package/dist/NotificationsPanel.module.css +258 -0
- package/dist/OverlayHeader.module.css +1 -1
- package/dist/PageContainer.module.css +62 -0
- package/dist/PageHeader.module.css +54 -0
- package/dist/PageLayout.module.css +51 -0
- package/dist/Popover.module.css +1 -1
- package/dist/QuantityStepper.module.css +1 -1
- package/dist/Resizable.module.css +3 -3
- package/dist/RootLayout.module.css +12 -0
- package/dist/SegmentedControl.module.css +1 -1
- package/dist/Select.module.css +1 -1
- package/dist/Sheet.module.css +4 -1
- package/dist/SplitPane.module.css +4 -4
- package/dist/StatusBadge.module.css +1 -1
- package/dist/StepStrip.module.css +53 -0
- package/dist/SuggestionInput.module.css +2 -2
- package/dist/SwipableMobileCarousel.module.css +12 -3
- package/dist/SwipableMobileCarouselDots.module.css +15 -0
- package/dist/SwipeStack.module.css +101 -0
- package/dist/SystemBanner.module.css +10 -0
- package/dist/Table.module.css +2 -2
- package/dist/Tabs.module.css +1 -1
- package/dist/TagInput.module.css +2 -2
- package/dist/Textarea.module.css +9 -1
- package/dist/ThreeColumnLayout.module.css +35 -0
- package/dist/Toast.module.css +62 -3
- package/dist/Tooltip.module.css +1 -1
- package/dist/UserMenu.module.css +90 -0
- package/dist/index.d.ts +1047 -45
- package/dist/index.js +3908 -1456
- package/dist/tokens/primitives.css +11 -0
- package/dist/tokens/semantic.css +32 -0
- package/dist/tokens/theme-presets.css +33 -0
- package/package.json +25 -9
- package/src/__tests__/package-json.test.ts +8 -0
- package/src/components/Accordion/Accordion.module.css +1 -1
- package/src/components/Alert/Alert.module.css +1 -1
- package/src/components/AppsLauncher/AppsLauncher.module.css +96 -0
- package/src/components/AppsLauncher/AppsLauncher.stories.tsx +58 -0
- package/src/components/AppsLauncher/AppsLauncher.tsx +117 -0
- package/src/components/AppsLauncher/__tests__/AppsLauncher.test.tsx +52 -0
- package/src/components/Avatar/Avatar.module.css +1 -1
- package/src/components/Badge/Badge.module.css +30 -7
- package/src/components/Badge/Badge.stories.tsx +16 -4
- package/src/components/Badge/Badge.tsx +7 -4
- package/src/components/Badge/__tests__/Badge.test.tsx +4 -2
- package/src/components/BalanceChip/BalanceChip.module.css +1 -1
- package/src/components/Button/Button.module.css +18 -2
- package/src/components/Card/Card.module.css +1 -1
- package/src/components/CardTile/CardTile.module.css +181 -0
- package/src/components/CardTile/CardTile.stories.tsx +86 -0
- package/src/components/CardTile/CardTile.tsx +128 -0
- package/src/components/CardTile/__tests__/CardTile.test.tsx +56 -0
- package/src/components/CodeTextarea/CodeTextarea.module.css +1 -1
- package/src/components/Collapsible/Collapsible.module.css +1 -1
- package/src/components/ColorPicker/ColorPicker.module.css +132 -0
- package/src/components/ColorPicker/ColorPicker.stories.tsx +80 -0
- package/src/components/ColorPicker/ColorPicker.tsx +157 -0
- package/src/components/ColorPicker/__tests__/ColorPicker.test.tsx +117 -0
- package/src/components/Combobox/Combobox.module.css +2 -2
- package/src/components/Command/Command.module.css +1 -1
- package/src/components/ConfirmDialog/ConfirmDialog.module.css +2 -2
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +28 -2
- package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +71 -3
- package/src/components/ConsentPrompt/ConsentPrompt.module.css +21 -0
- package/src/components/ConsentPrompt/ConsentPrompt.stories.tsx +31 -0
- package/src/components/ConsentPrompt/ConsentPrompt.tsx +66 -0
- package/src/components/ContextMenu/ContextMenu.tsx +1 -1
- package/src/components/DatePicker/DatePicker.module.css +1 -1
- package/src/components/Dialog/Dialog.module.css +130 -21
- package/src/components/Dialog/Dialog.stories.tsx +179 -2
- package/src/components/Dialog/Dialog.tsx +129 -35
- package/src/components/Dialog/__tests__/Dialog.test.tsx +190 -9
- package/src/components/DragHandleRow/DragHandleRow.tsx +2 -21
- package/src/components/Drawer/Drawer.module.css +36 -7
- package/src/components/Drawer/Drawer.tsx +9 -10
- package/src/components/Drawer/__tests__/Drawer.test.tsx +33 -6
- package/src/components/GripIcon/GripIcon.tsx +27 -0
- package/src/components/Header/Header.module.css +62 -0
- package/src/components/Header/Header.stories.tsx +452 -0
- package/src/components/Header/Header.tsx +102 -0
- package/src/components/Header/__tests__/Header.test.tsx +104 -0
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.module.css +21 -0
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.stories.tsx +111 -0
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.tsx +54 -0
- package/src/components/HeaderFooterLayout/__tests__/HeaderFooterLayout.test.tsx +58 -0
- package/src/components/Icon/icons/arrow-left-right.tsx +23 -0
- package/src/components/Icon/icons/bold.tsx +20 -0
- package/src/components/Icon/icons/calendar.tsx +4 -4
- package/src/components/Icon/icons/carrot.tsx +3 -3
- package/src/components/Icon/icons/circle-chevron-left.tsx +21 -0
- package/src/components/Icon/icons/code.tsx +21 -0
- package/src/components/Icon/icons/copy.tsx +21 -0
- package/src/components/Icon/icons/credit-card.tsx +21 -0
- package/src/components/Icon/icons/database.tsx +22 -0
- package/src/components/Icon/icons/download.tsx +22 -0
- package/src/components/Icon/icons/file-text.tsx +24 -0
- package/src/components/Icon/icons/folder-closed.tsx +21 -0
- package/src/components/Icon/icons/folder-open.tsx +20 -0
- package/src/components/Icon/icons/folder.tsx +20 -0
- package/src/components/Icon/icons/folders.tsx +21 -0
- package/src/components/Icon/icons/hat-glasses.tsx +24 -0
- package/src/components/Icon/icons/history.tsx +22 -0
- package/src/components/Icon/icons/inbox.tsx +21 -0
- package/src/components/Icon/icons/index.ts +161 -1
- package/src/components/Icon/icons/italic.tsx +22 -0
- package/src/components/Icon/icons/layers.tsx +22 -0
- package/src/components/Icon/icons/layout-dashboard.tsx +23 -0
- package/src/components/Icon/icons/layout-grid.tsx +23 -0
- package/src/components/Icon/icons/link.tsx +21 -0
- package/src/components/Icon/icons/list-ordered.tsx +25 -0
- package/src/components/Icon/icons/list.tsx +25 -0
- package/src/components/Icon/icons/luggage.tsx +24 -0
- package/src/components/Icon/icons/map-pin.tsx +21 -0
- package/src/components/Icon/icons/minus.tsx +20 -0
- package/src/components/Icon/icons/paint-bucket.tsx +23 -0
- package/src/components/Icon/icons/panel-left.tsx +21 -0
- package/src/components/Icon/icons/pin.tsx +21 -0
- package/src/components/Icon/icons/rectangle-ellipsis.tsx +23 -0
- package/src/components/Icon/icons/refresh-cw.tsx +23 -0
- package/src/components/Icon/icons/rotate-cw.tsx +21 -0
- package/src/components/Icon/icons/route.tsx +22 -0
- package/src/components/Icon/icons/send.tsx +21 -0
- package/src/components/Icon/icons/share-2.tsx +24 -0
- package/src/components/Icon/icons/sheet.tsx +24 -0
- package/src/components/Icon/icons/square-kanban.tsx +23 -0
- package/src/components/Icon/icons/table.tsx +23 -0
- package/src/components/Icon/icons/user-round-plus.tsx +23 -0
- package/src/components/Icon/icons/users.tsx +23 -0
- package/src/components/IconPicker/IconPicker.module.css +1 -1
- package/src/components/Input/Input.module.css +1 -1
- package/src/components/Input/Input.tsx +8 -2
- package/src/components/Kbd/Kbd.module.css +1 -1
- package/src/components/Markdown/Markdown.module.css +24 -0
- package/src/components/Markdown/Markdown.stories.tsx +10 -0
- package/src/components/Markdown/Markdown.tsx +166 -29
- package/src/components/Markdown/__tests__/Markdown.test.tsx +144 -0
- package/src/components/Menu/Menu.module.css +1 -0
- package/src/components/Menu/Menu.tsx +72 -22
- package/src/components/Menu/__tests__/Menu.test.tsx +30 -0
- package/src/components/Menubar/Menubar.tsx +1 -1
- package/src/components/Message/Message.module.css +1 -1
- package/src/components/MessageScroller/MessageScroller.module.css +12 -1
- package/src/components/MessageScroller/MessageScroller.tsx +12 -0
- package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +10 -9
- package/src/components/MobileFooter/MobileFooter.module.css +1 -1
- package/src/components/MobileFooter/MobileFooter.tsx +15 -2
- package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +58 -0
- package/src/components/MobileHeader/MobileHeader.module.css +14 -1
- package/src/components/MobileHeader/MobileHeader.tsx +15 -2
- package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +56 -1
- package/src/components/NavList/NavList.module.css +130 -0
- package/src/components/NavList/NavList.tsx +136 -0
- package/src/components/NavList/__tests__/NavList.test.tsx +109 -0
- package/src/components/NavTabs/NavTabs.module.css +1 -1
- package/src/components/NotificationsPanel/NotificationsPanel.module.css +258 -0
- package/src/components/NotificationsPanel/NotificationsPanel.stories.tsx +61 -0
- package/src/components/NotificationsPanel/NotificationsPanel.tsx +187 -0
- package/src/components/NotificationsPanel/__tests__/NotificationsPanel.test.tsx +78 -0
- package/src/components/OverlayHeader/OverlayHeader.module.css +1 -1
- package/src/components/OverlayHeader/OverlayHeader.tsx +19 -4
- package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +18 -0
- package/src/components/PageContainer/PageContainer.module.css +62 -0
- package/src/components/PageContainer/PageContainer.tsx +37 -4
- package/src/components/PageContainer/__tests__/PageContainer.test.tsx +41 -8
- package/src/components/PageHeader/PageHeader.module.css +54 -0
- package/src/components/PageHeader/PageHeader.tsx +146 -5
- package/src/components/PageHeader/__tests__/PageHeader.test.tsx +103 -0
- package/src/components/PageLayout/PageLayout.module.css +51 -0
- package/src/components/PageLayout/PageLayout.stories.tsx +73 -0
- package/src/components/PageLayout/PageLayout.tsx +51 -0
- package/src/components/PageLayout/__tests__/PageLayout.test.tsx +34 -0
- package/src/components/Popover/Popover.module.css +1 -1
- package/src/components/Popover/Popover.tsx +86 -4
- package/src/components/Popover/__tests__/Popover.test.tsx +165 -0
- package/src/components/QuantityStepper/QuantityStepper.module.css +1 -1
- package/src/components/Resizable/Resizable.module.css +3 -3
- package/src/components/RootLayout/RootLayout.module.css +12 -0
- package/src/components/RootLayout/RootLayout.stories.tsx +125 -0
- package/src/components/RootLayout/RootLayout.tsx +185 -0
- package/src/components/RootLayout/__tests__/RootLayout.test.tsx +161 -0
- package/src/components/SegmentedControl/SegmentedControl.module.css +1 -1
- package/src/components/Select/Select.module.css +1 -1
- package/src/components/Sheet/Sheet.module.css +4 -1
- package/src/components/Sheet/Sheet.tsx +5 -5
- package/src/components/SplitPane/SplitPane.module.css +4 -4
- package/src/components/StatusBadge/StatusBadge.module.css +1 -1
- package/src/components/StepStrip/StepStrip.module.css +53 -0
- package/src/components/StepStrip/StepStrip.tsx +74 -0
- package/src/components/SuggestionInput/SuggestionInput.module.css +2 -2
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +12 -3
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +25 -9
- package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +81 -0
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +15 -0
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +12 -1
- package/src/components/SwipeStack/SwipeStack.module.css +101 -0
- package/src/components/SwipeStack/SwipeStack.tsx +199 -0
- package/src/components/SwipeStack/SwipeStackCard.tsx +22 -0
- package/src/components/SwipeStack/__tests__/SwipeStack.test.tsx +169 -0
- package/src/components/SystemBanner/SystemBanner.module.css +10 -0
- package/src/components/Table/Table.module.css +2 -2
- package/src/components/Tabs/Tabs.module.css +1 -1
- package/src/components/TagInput/TagInput.module.css +2 -2
- package/src/components/Textarea/Textarea.module.css +9 -1
- package/src/components/Textarea/Textarea.tsx +28 -5
- package/src/components/Textarea/__tests__/Textarea.test.tsx +23 -1
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.module.css +35 -0
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.stories.tsx +152 -0
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.tsx +87 -0
- package/src/components/ThreeColumnLayout/__tests__/ThreeColumnLayout.test.tsx +78 -0
- package/src/components/Toast/Toast.module.css +62 -3
- package/src/components/Toast/Toast.stories.tsx +1 -1
- package/src/components/Tooltip/Tooltip.module.css +1 -1
- package/src/components/UserMenu/UserMenu.module.css +90 -0
- package/src/components/UserMenu/UserMenu.stories.tsx +49 -0
- package/src/components/UserMenu/UserMenu.tsx +132 -0
- package/src/components/UserMenu/__tests__/UserMenu.test.tsx +77 -0
- package/src/hooks/__tests__/usePublishShellChromeHeight.test.tsx +118 -0
- package/src/hooks/__tests__/useReorderSensors.test.ts +26 -0
- package/src/hooks/__tests__/useSnapCarousel.test.tsx +82 -0
- package/src/hooks/__tests__/useSwipeStack.test.tsx +155 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/usePublishShellChromeHeight.ts +96 -0
- package/src/hooks/useReorderSensors.ts +88 -0
- package/src/hooks/useSnapCarousel.ts +211 -6
- package/src/hooks/useSwipeStack.ts +235 -0
- package/src/index.ts +61 -1
- package/src/overlay-shell.ts +65 -0
- package/src/stories/DesignSystemOverview.stories.tsx +617 -11
- package/src/stories/GripIcon.stories.tsx +29 -0
- package/src/stories/LayoutSystem.stories.tsx +639 -0
- package/src/stories/MenuEntries.stories.tsx +86 -0
- package/src/stories/NavList.stories.tsx +147 -0
- package/src/stories/PageContainer.stories.tsx +52 -7
- package/src/stories/PageHeader.stories.tsx +54 -1
- package/src/stories/StepStrip.stories.tsx +76 -0
- package/src/stories/SwipableMobileCarousel.stories.tsx +58 -0
- package/src/stories/SwipeStack.stories.tsx +200 -0
- package/src/stories/Textarea.stories.tsx +12 -0
- package/src/stories/TokenGallery.stories.tsx +50 -0
- package/src/tokens/primitives.css +11 -0
- package/src/tokens/semantic.css +32 -0
- package/src/tokens/theme-presets.css +33 -0
- package/src/tokens/theme-presets.ts +72 -0
|
@@ -36,6 +36,7 @@ describe('useSnapCarousel', () => {
|
|
|
36
36
|
const onSettle = vi.fn();
|
|
37
37
|
const { getByTestId } = render(<Harness itemCount={3} onSettle={onSettle} debounceMs={120} />);
|
|
38
38
|
const el = getByTestId('scroller');
|
|
39
|
+
fireEvent.touchStart(el);
|
|
39
40
|
setLayout(el, 300, 300); // scrollLeft 300 / width 300 -> index 1
|
|
40
41
|
fireEvent.scroll(el);
|
|
41
42
|
expect(onSettle).not.toHaveBeenCalled();
|
|
@@ -47,6 +48,7 @@ describe('useSnapCarousel', () => {
|
|
|
47
48
|
const onSettle = vi.fn();
|
|
48
49
|
const { getByTestId } = render(<Harness itemCount={3} onSettle={onSettle} debounceMs={120} />);
|
|
49
50
|
const el = getByTestId('scroller');
|
|
51
|
+
fireEvent.touchStart(el);
|
|
50
52
|
setLayout(el, 300, 0);
|
|
51
53
|
fireEvent.scroll(el);
|
|
52
54
|
act(() => void vi.advanceTimersByTime(60));
|
|
@@ -61,9 +63,11 @@ describe('useSnapCarousel', () => {
|
|
|
61
63
|
const onSettle = vi.fn();
|
|
62
64
|
const { getByTestId } = render(<Harness itemCount={3} onSettle={onSettle} debounceMs={120} />);
|
|
63
65
|
const el = getByTestId('scroller');
|
|
66
|
+
fireEvent.touchStart(el);
|
|
64
67
|
setLayout(el, 300, 300);
|
|
65
68
|
fireEvent.scroll(el);
|
|
66
69
|
act(() => void vi.advanceTimersByTime(120));
|
|
70
|
+
fireEvent.touchStart(el);
|
|
67
71
|
fireEvent.scroll(el);
|
|
68
72
|
act(() => void vi.advanceTimersByTime(120));
|
|
69
73
|
expect(onSettle).toHaveBeenCalledOnce();
|
|
@@ -73,12 +77,90 @@ describe('useSnapCarousel', () => {
|
|
|
73
77
|
const onSettle = vi.fn();
|
|
74
78
|
const { getByTestId } = render(<Harness itemCount={2} onSettle={onSettle} debounceMs={120} />);
|
|
75
79
|
const el = getByTestId('scroller');
|
|
80
|
+
fireEvent.touchStart(el);
|
|
76
81
|
setLayout(el, 300, 900); // rounds to index 3, clamps to itemCount - 1 = 1
|
|
77
82
|
fireEvent.scroll(el);
|
|
78
83
|
act(() => void vi.advanceTimersByTime(120));
|
|
79
84
|
expect(onSettle).toHaveBeenCalledWith(1);
|
|
80
85
|
});
|
|
81
86
|
|
|
87
|
+
it('ignores a scroll event with no active gesture (drift, not a settle)', () => {
|
|
88
|
+
// Regression test: reproduced live via a user screen recording — a
|
|
89
|
+
// slide's content changing size while still loading can nudge
|
|
90
|
+
// scrollLeft enough to round to a different index. Previously any such
|
|
91
|
+
// stray `scroll` event was enough to fire a second, contradicting
|
|
92
|
+
// onSettle, silently "auto-swiping" the carousel back to the wrong
|
|
93
|
+
// slide with no further touch input.
|
|
94
|
+
const onSettle = vi.fn();
|
|
95
|
+
const { getByTestId } = render(<Harness itemCount={10} onSettle={onSettle} debounceMs={120} />);
|
|
96
|
+
const el = getByTestId('scroller');
|
|
97
|
+
fireEvent.touchStart(el);
|
|
98
|
+
setLayout(el, 300, 1800); // settles cleanly on index 6
|
|
99
|
+
fireEvent.scroll(el);
|
|
100
|
+
act(() => void vi.advanceTimersByTime(120));
|
|
101
|
+
expect(onSettle).toHaveBeenCalledExactlyOnceWith(6);
|
|
102
|
+
|
|
103
|
+
// Well outside the grace window and no further touch/wheel/pointer
|
|
104
|
+
// activity — this scroll event is drift, not a resettle.
|
|
105
|
+
act(() => void vi.advanceTimersByTime(1000));
|
|
106
|
+
setLayout(el, 300, 1620); // would round to index 5
|
|
107
|
+
fireEvent.scroll(el);
|
|
108
|
+
act(() => void vi.advanceTimersByTime(120));
|
|
109
|
+
expect(onSettle).toHaveBeenCalledExactlyOnceWith(6);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('still settles on long-running momentum/snap-correction scroll with no further touch input', () => {
|
|
113
|
+
// Regression test for a bug in the FIRST version of the drift fix above:
|
|
114
|
+
// gating on "was there a touch/wheel/pointer event within a fixed
|
|
115
|
+
// debounceMs * 2 slack of settle-check time" dropped real settles once
|
|
116
|
+
// native momentum/snap-correction ran longer than that slack — which it
|
|
117
|
+
// routinely does for a fast flick. That left activeIndex stuck on the
|
|
118
|
+
// old slide while the container had already scrolled to the new one,
|
|
119
|
+
// and since the mount window is keyed off activeIndex, the slide the
|
|
120
|
+
// user was actually looking at could get unmounted — a blank carousel,
|
|
121
|
+
// worse than the bug being fixed. The chain stays trusted for as long as
|
|
122
|
+
// scroll events keep arriving without a debounceMs-or-longer gap,
|
|
123
|
+
// regardless of how long that run lasts or how few touch events back it.
|
|
124
|
+
const onSettle = vi.fn();
|
|
125
|
+
const { getByTestId } = render(<Harness itemCount={10} onSettle={onSettle} debounceMs={120} />);
|
|
126
|
+
const el = getByTestId('scroller');
|
|
127
|
+
fireEvent.touchStart(el);
|
|
128
|
+
setLayout(el, 300, 1700);
|
|
129
|
+
fireEvent.scroll(el); // starts the run — trusted (touchstart just happened)
|
|
130
|
+
fireEvent.touchEnd(el);
|
|
131
|
+
// Momentum keeps producing scroll events, each within debounceMs of the
|
|
132
|
+
// last (so the run never goes quiet), for far longer than any fixed
|
|
133
|
+
// slack window would tolerate — no further touch input at all.
|
|
134
|
+
for (const scrollLeft of [1720, 1740, 1760, 1780, 1800]) {
|
|
135
|
+
act(() => void vi.advanceTimersByTime(80));
|
|
136
|
+
setLayout(el, 300, scrollLeft);
|
|
137
|
+
fireEvent.scroll(el);
|
|
138
|
+
}
|
|
139
|
+
act(() => void vi.advanceTimersByTime(120));
|
|
140
|
+
expect(onSettle).toHaveBeenCalledExactlyOnceWith(6);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('settles correctly on two independent real swipes fired back-to-back', () => {
|
|
144
|
+
// Regression test: two genuine swipes with essentially no gap between
|
|
145
|
+
// them (an impatient fast double-swipe) must not corrupt or drop either
|
|
146
|
+
// settle — each is its own trusted run.
|
|
147
|
+
const onSettle = vi.fn();
|
|
148
|
+
const { getByTestId } = render(<Harness itemCount={10} onSettle={onSettle} debounceMs={120} />);
|
|
149
|
+
const el = getByTestId('scroller');
|
|
150
|
+
fireEvent.touchStart(el);
|
|
151
|
+
setLayout(el, 300, 300);
|
|
152
|
+
fireEvent.scroll(el);
|
|
153
|
+
act(() => void vi.advanceTimersByTime(120));
|
|
154
|
+
expect(onSettle).toHaveBeenCalledExactlyOnceWith(1);
|
|
155
|
+
|
|
156
|
+
fireEvent.touchStart(el);
|
|
157
|
+
setLayout(el, 300, 600);
|
|
158
|
+
fireEvent.scroll(el);
|
|
159
|
+
act(() => void vi.advanceTimersByTime(120));
|
|
160
|
+
expect(onSettle).toHaveBeenCalledTimes(2);
|
|
161
|
+
expect(onSettle).toHaveBeenLastCalledWith(2);
|
|
162
|
+
});
|
|
163
|
+
|
|
82
164
|
it('scrollToIndex scrolls the container to the target slide', () => {
|
|
83
165
|
let scrollToIndex: ((index: number) => void) | undefined;
|
|
84
166
|
const { getByTestId } = render(
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { cleanup, fireEvent, render } from '@testing-library/react';
|
|
4
|
+
import { useSwipeStack, type SwipeDirection, type UseSwipeStackOptions } from '../useSwipeStack';
|
|
5
|
+
|
|
6
|
+
// jsdom doesn't implement pointer capture — stub it as a no-op so
|
|
7
|
+
// `e.currentTarget.setPointerCapture(...)` doesn't throw.
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
if (!HTMLElement.prototype.setPointerCapture) {
|
|
10
|
+
HTMLElement.prototype.setPointerCapture = vi.fn();
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(cleanup);
|
|
15
|
+
|
|
16
|
+
function Harness(props: UseSwipeStackOptions & { triggerDirection?: SwipeDirection }) {
|
|
17
|
+
const { triggerDirection, ...options } = props;
|
|
18
|
+
const { cardRef, wrapRef, handlers, touchAction, triggerCommit } = useSwipeStack(options);
|
|
19
|
+
return (
|
|
20
|
+
<div data-testid="wrap" ref={wrapRef}>
|
|
21
|
+
<div data-testid="card" ref={cardRef} data-touch-action={touchAction} {...handlers}>
|
|
22
|
+
card
|
|
23
|
+
</div>
|
|
24
|
+
{triggerDirection && (
|
|
25
|
+
<button data-testid="trigger" onClick={() => triggerCommit(triggerDirection)}>
|
|
26
|
+
trigger
|
|
27
|
+
</button>
|
|
28
|
+
)}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function pointer(x: number, y: number) {
|
|
34
|
+
return { clientX: x, clientY: y, pointerId: 1 };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function drag(el: HTMLElement, dx: number, dy: number) {
|
|
38
|
+
fireEvent.pointerDown(el, pointer(0, 0));
|
|
39
|
+
fireEvent.pointerMove(el, pointer(dx, dy));
|
|
40
|
+
fireEvent.pointerUp(el, pointer(dx, dy));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('useSwipeStack', () => {
|
|
44
|
+
it('commits when a drag crosses threshold in a live direction', () => {
|
|
45
|
+
const onCommit = vi.fn();
|
|
46
|
+
const { getByTestId } = render(
|
|
47
|
+
<Harness directions={['up', 'down', 'left', 'right']} threshold={100} onCommit={onCommit} />,
|
|
48
|
+
);
|
|
49
|
+
drag(getByTestId('card'), 0, -120);
|
|
50
|
+
expect(onCommit).toHaveBeenCalledWith('up');
|
|
51
|
+
expect(onCommit).toHaveBeenCalledOnce();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('springs back without committing when under threshold', () => {
|
|
55
|
+
const onCommit = vi.fn();
|
|
56
|
+
const { getByTestId } = render(
|
|
57
|
+
<Harness directions={['up', 'down', 'left', 'right']} threshold={100} onCommit={onCommit} />,
|
|
58
|
+
);
|
|
59
|
+
drag(getByTestId('card'), 0, -40);
|
|
60
|
+
expect(onCommit).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('does not commit within the deadzone even if threshold is set very low', () => {
|
|
64
|
+
const onCommit = vi.fn();
|
|
65
|
+
const { getByTestId } = render(
|
|
66
|
+
<Harness directions={['up']} threshold={2} onCommit={onCommit} />,
|
|
67
|
+
);
|
|
68
|
+
drag(getByTestId('card'), 0, -5);
|
|
69
|
+
expect(onCommit).not.toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('treats a non-configured direction as a wall — no commit even past what would be threshold distance', () => {
|
|
73
|
+
const onCommit = vi.fn();
|
|
74
|
+
const { getByTestId } = render(
|
|
75
|
+
<Harness directions={['up', 'down']} threshold={100} onCommit={onCommit} />,
|
|
76
|
+
);
|
|
77
|
+
drag(getByTestId('card'), 150, 0);
|
|
78
|
+
expect(onCommit).not.toHaveBeenCalled();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('does nothing when disabled', () => {
|
|
82
|
+
const onCommit = vi.fn();
|
|
83
|
+
const { getByTestId } = render(
|
|
84
|
+
<Harness
|
|
85
|
+
directions={['up', 'down', 'left', 'right']}
|
|
86
|
+
threshold={100}
|
|
87
|
+
onCommit={onCommit}
|
|
88
|
+
disabled
|
|
89
|
+
/>,
|
|
90
|
+
);
|
|
91
|
+
drag(getByTestId('card'), 0, -150);
|
|
92
|
+
expect(onCommit).not.toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('ignores pointerup without a preceding pointerdown', () => {
|
|
96
|
+
const onCommit = vi.fn();
|
|
97
|
+
const { getByTestId } = render(
|
|
98
|
+
<Harness directions={['up', 'down', 'left', 'right']} threshold={100} onCommit={onCommit} />,
|
|
99
|
+
);
|
|
100
|
+
fireEvent.pointerUp(getByTestId('card'), pointer(0, -150));
|
|
101
|
+
expect(onCommit).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('triggerCommit commits a live direction without a drag', () => {
|
|
105
|
+
const onCommit = vi.fn();
|
|
106
|
+
const { getByTestId } = render(
|
|
107
|
+
<Harness directions={['up']} onCommit={onCommit} triggerDirection="up" />,
|
|
108
|
+
);
|
|
109
|
+
fireEvent.click(getByTestId('trigger'));
|
|
110
|
+
expect(onCommit).toHaveBeenCalledWith('up');
|
|
111
|
+
expect(onCommit).toHaveBeenCalledOnce();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('triggerCommit no-ops for a direction that is not live', () => {
|
|
115
|
+
const onCommit = vi.fn();
|
|
116
|
+
const { getByTestId } = render(
|
|
117
|
+
<Harness directions={['up']} onCommit={onCommit} triggerDirection="down" />,
|
|
118
|
+
);
|
|
119
|
+
fireEvent.click(getByTestId('trigger'));
|
|
120
|
+
expect(onCommit).not.toHaveBeenCalled();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('triggerCommit does nothing when disabled', () => {
|
|
124
|
+
const onCommit = vi.fn();
|
|
125
|
+
const { getByTestId } = render(
|
|
126
|
+
<Harness directions={['up']} onCommit={onCommit} triggerDirection="up" disabled />,
|
|
127
|
+
);
|
|
128
|
+
fireEvent.click(getByTestId('trigger'));
|
|
129
|
+
expect(onCommit).not.toHaveBeenCalled();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('touchAction', () => {
|
|
133
|
+
it('is none when both axes have a live direction', () => {
|
|
134
|
+
const { getByTestId } = render(
|
|
135
|
+
<Harness directions={['left', 'right', 'up', 'down']} onCommit={vi.fn()} />,
|
|
136
|
+
);
|
|
137
|
+
expect(getByTestId('card').dataset.touchAction).toBe('none');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('is pan-y when only horizontal directions are live', () => {
|
|
141
|
+
const { getByTestId } = render(<Harness directions={['left', 'right']} onCommit={vi.fn()} />);
|
|
142
|
+
expect(getByTestId('card').dataset.touchAction).toBe('pan-y');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('is pan-x when only vertical directions are live', () => {
|
|
146
|
+
const { getByTestId } = render(<Harness directions={['up', 'down']} onCommit={vi.fn()} />);
|
|
147
|
+
expect(getByTestId('card').dataset.touchAction).toBe('pan-x');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('is auto when no directions are live', () => {
|
|
151
|
+
const { getByTestId } = render(<Harness directions={[]} onCommit={vi.fn()} />);
|
|
152
|
+
expect(getByTestId('card').dataset.touchAction).toBe('auto');
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
package/src/hooks/index.ts
CHANGED
|
@@ -22,3 +22,12 @@ export type {
|
|
|
22
22
|
UseCarouselRouteSyncOptions,
|
|
23
23
|
UseCarouselRouteSyncResult,
|
|
24
24
|
} from './useCarouselRouteSync';
|
|
25
|
+
export { useReorderSensors, shouldHandleDndEvent } from './useReorderSensors';
|
|
26
|
+
export type { UseReorderSensorsOptions } from './useReorderSensors';
|
|
27
|
+
export { useSwipeStack, SWIPE_STACK_TRANSITION_MS } from './useSwipeStack';
|
|
28
|
+
export type {
|
|
29
|
+
SwipeDirection,
|
|
30
|
+
UseSwipeStackOptions,
|
|
31
|
+
UseSwipeStackHandlers,
|
|
32
|
+
UseSwipeStackResult,
|
|
33
|
+
} from './useSwipeStack';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useLayoutEffect } from 'react';
|
|
4
|
+
import type { RefObject } from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Internal implementation detail of `MobileHeader`/`MobileFooter` — not part
|
|
8
|
+
* of the package's public surface (not re-exported from `hooks/index.ts`).
|
|
9
|
+
*
|
|
10
|
+
* Measures `ref`'s own rendered height and publishes it as an inline
|
|
11
|
+
* `var(--sv-shell-header-height|--sv-shell-footer-height)` override on
|
|
12
|
+
* `#sv-app-shell` (the runtime shell's stable root id, `runtime/app/
|
|
13
|
+
* (platform)/layout.tsx`), removing it on unmount. This is what makes a
|
|
14
|
+
* *self-rendered* `MobileHeader`/`MobileFooter` — the sanctioned pattern for
|
|
15
|
+
* a plugin that opts out of the platform's own chrome via
|
|
16
|
+
* `shellConfig.mobileHeader`/`mobileFooter: false` (RFC 0075/0088) — work
|
|
17
|
+
* correctly with `Sheet`/`Drawer`/`Dialog`, which all size themselves against
|
|
18
|
+
* these same two variables so they stop above/below the chrome instead of
|
|
19
|
+
* sliding underneath it.
|
|
20
|
+
*
|
|
21
|
+
* Before this existed, only the *platform's own* header/footer had its
|
|
22
|
+
* height accounted for (a fixed CSS `calc()` in `shell.module.css`, driven by
|
|
23
|
+
* `data-mobile-header-hidden`/`data-mobile-footer-hidden`, which collapses
|
|
24
|
+
* the variable to `0px` whenever a plugin hides that chrome) — a plugin
|
|
25
|
+
* self-rendering its own via this same component instead had no path to tell
|
|
26
|
+
* the shell what its real height was, and silently inherited that `0px`.
|
|
27
|
+
* `Sheet`/`Drawer` then extended full-height, and since the header/footer's
|
|
28
|
+
* own `z-index` (101) beats an overlay's (100), the chrome visibly covered
|
|
29
|
+
* the overlay's last ~60px. First found and fixed ad hoc in `sovereign-tasks`
|
|
30
|
+
* (measuring in the plugin itself, `getBoundingClientRect()` in a
|
|
31
|
+
* `useLayoutEffect`); moved here so every consumer of these two components —
|
|
32
|
+
* present and future, in this repo or externally maintained — gets it for
|
|
33
|
+
* free, without having to know the mechanism exists. See
|
|
34
|
+
* `docs/architecture-rules.md` for the full rule.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately **not** `ResizeObserver`: in live testing (both this repo's
|
|
37
|
+
* own Chromium-based browser-preview tooling and a real WebKit iOS Simulator
|
|
38
|
+
* session) a freshly created `ResizeObserver` never fired its callback even
|
|
39
|
+
* once for an already-rendered, stably-sized, non-zero element — cause not
|
|
40
|
+
* fully root-caused. A synchronous `getBoundingClientRect()` read in
|
|
41
|
+
* `useLayoutEffect`, re-run on `window`'s `resize` event, worked immediately
|
|
42
|
+
* and reliably in both environments instead.
|
|
43
|
+
*
|
|
44
|
+
* No-ops safely wherever `#sv-app-shell` doesn't exist (Storybook, a unit
|
|
45
|
+
* test, or any host other than the platform's own runtime shell) — every CSS
|
|
46
|
+
* consumer reads these variables with a `60px` fallback, so an absent
|
|
47
|
+
* override is harmless.
|
|
48
|
+
*
|
|
49
|
+
* For `--sv-shell-header-height` specifically, this also mirrors the same
|
|
50
|
+
* measured height onto `--sv-dialog-inset-top` on `:root` (`document.
|
|
51
|
+
* documentElement`, not `#sv-app-shell`) — `Dialog`'s mobile `.scrim` is
|
|
52
|
+
* `position: fixed` and reads that variable for its own `top` offset, and
|
|
53
|
+
* per `globals.css`'s own comment, iOS Safari has a documented quirk where a
|
|
54
|
+
* `position: fixed` element inherits custom properties from `:root` rather
|
|
55
|
+
* than from its DOM ancestors, so a value set only on `#sv-app-shell` (an
|
|
56
|
+
* ancestor, not `:root`) silently never reaches it there. The platform's own
|
|
57
|
+
* header already keeps `--sv-dialog-inset-top` correct this way, via
|
|
58
|
+
* `ClientShell.tsx`'s own `syncViewport()` — but that function is part of
|
|
59
|
+
* `(platform)/layout.tsx` and never runs for a `shell: minimal` plugin, so a
|
|
60
|
+
* self-rendered `MobileHeader` (this hook's entire reason to exist) had no
|
|
61
|
+
* equivalent, and `--sv-dialog-inset-top` silently kept `globals.css`'s
|
|
62
|
+
* hardcoded platform-header-height fallback (60px) regardless of this
|
|
63
|
+
* header's real height. Found live in `sovereign-plugin-kanban`: its
|
|
64
|
+
* self-rendered header measures 69px, so its card-detail `Dialog` opened
|
|
65
|
+
* with a real, reproducible ~9px misalignment against that header's own
|
|
66
|
+
* bottom edge — confirmed via `getBoundingClientRect()` on both elements,
|
|
67
|
+
* not just visually. */
|
|
68
|
+
export function usePublishShellChromeHeight(
|
|
69
|
+
ref: RefObject<HTMLElement | null>,
|
|
70
|
+
cssVariable: '--sv-shell-header-height' | '--sv-shell-footer-height',
|
|
71
|
+
) {
|
|
72
|
+
useLayoutEffect(() => {
|
|
73
|
+
const el = ref.current;
|
|
74
|
+
if (!el) return;
|
|
75
|
+
|
|
76
|
+
function publish() {
|
|
77
|
+
const shell = document.getElementById('sv-app-shell');
|
|
78
|
+
if (!shell || !el) return;
|
|
79
|
+
const height = `${el.getBoundingClientRect().height}px`;
|
|
80
|
+
shell.style.setProperty(cssVariable, height);
|
|
81
|
+
if (cssVariable === '--sv-shell-header-height') {
|
|
82
|
+
document.documentElement.style.setProperty('--sv-dialog-inset-top', height);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
publish();
|
|
87
|
+
window.addEventListener('resize', publish);
|
|
88
|
+
return () => {
|
|
89
|
+
window.removeEventListener('resize', publish);
|
|
90
|
+
document.getElementById('sv-app-shell')?.style.removeProperty(cssVariable);
|
|
91
|
+
if (cssVariable === '--sv-shell-header-height') {
|
|
92
|
+
document.documentElement.style.removeProperty('--sv-dialog-inset-top');
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}, [ref, cssVariable]);
|
|
96
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
KeyboardSensor,
|
|
5
|
+
MouseSensor as LibMouseSensor,
|
|
6
|
+
TouchSensor as LibTouchSensor,
|
|
7
|
+
useSensor,
|
|
8
|
+
useSensors,
|
|
9
|
+
} from '@dnd-kit/core';
|
|
10
|
+
import { sortableKeyboardCoordinates } from '@dnd-kit/sortable';
|
|
11
|
+
import type { MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
|
|
12
|
+
|
|
13
|
+
/** Plain pointer drag (desktop, handle-initiated). */
|
|
14
|
+
const DEFAULT_MOUSE_ACTIVATION_DISTANCE_PX = 8;
|
|
15
|
+
|
|
16
|
+
/** Long-press-to-lift on touch. `delay` is how long a still hold takes before
|
|
17
|
+
* the row lifts; `tolerance` is how far the finger may drift during that
|
|
18
|
+
* hold before it's treated as a scroll instead — a finger that moves further
|
|
19
|
+
* than this within the delay window cancels activation and the native
|
|
20
|
+
* vertical scroll wins. */
|
|
21
|
+
const DEFAULT_TOUCH_ACTIVATION_DELAY_MS = 300;
|
|
22
|
+
const DEFAULT_TOUCH_ACTIVATION_TOLERANCE_PX = 8;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* True when a drag should be allowed to start from `target`. Refused when
|
|
26
|
+
* `target` sits inside an element marked `data-no-dnd` — a nested control
|
|
27
|
+
* (e.g. a visibility toggle) opts out so tapping it fires its own handler
|
|
28
|
+
* instead of lifting the row. Exported standalone so it's unit-testable
|
|
29
|
+
* without spinning up dnd-kit.
|
|
30
|
+
*/
|
|
31
|
+
export function shouldHandleDndEvent(target: EventTarget | null): boolean {
|
|
32
|
+
if (!(target instanceof Element)) return true;
|
|
33
|
+
return target.closest('[data-no-dnd]') === null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface UseReorderSensorsOptions {
|
|
37
|
+
mouseActivationDistancePx?: number;
|
|
38
|
+
touchActivationDelayMs?: number;
|
|
39
|
+
touchActivationTolerancePx?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// dnd-kit's documented pattern for scoping sensor activation to specific
|
|
43
|
+
// elements: subclass the built-in sensor and replace its static `activators`
|
|
44
|
+
// with a handler that inspects the originating event's target before
|
|
45
|
+
// deferring to the base activation logic (distance/delay/tolerance), which
|
|
46
|
+
// dnd-kit re-checks internally regardless of what this returns.
|
|
47
|
+
class MouseSensor extends LibMouseSensor {
|
|
48
|
+
static override activators = [
|
|
49
|
+
{
|
|
50
|
+
eventName: 'onMouseDown' as const,
|
|
51
|
+
handler: ({ nativeEvent: event }: ReactMouseEvent) => shouldHandleDndEvent(event.target),
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class TouchSensor extends LibTouchSensor {
|
|
57
|
+
static override activators = [
|
|
58
|
+
{
|
|
59
|
+
eventName: 'onTouchStart' as const,
|
|
60
|
+
handler: ({ nativeEvent: event }: ReactTouchEvent) => shouldHandleDndEvent(event.target),
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Sensor set for a `data-no-dnd`-aware drag-reorder list — MouseSensor for
|
|
67
|
+
* desktop's handle-initiated drag, TouchSensor for mobile's long-press lift
|
|
68
|
+
* (fixes reordering being unusable on iOS PWA/Safari, which never implements
|
|
69
|
+
* the native HTML5 Drag-and-Drop API for touch input), KeyboardSensor
|
|
70
|
+
* unchanged. A bare call keeps the historical 8px/300ms/8px defaults.
|
|
71
|
+
*/
|
|
72
|
+
export function useReorderSensors(options: UseReorderSensorsOptions = {}) {
|
|
73
|
+
const {
|
|
74
|
+
mouseActivationDistancePx = DEFAULT_MOUSE_ACTIVATION_DISTANCE_PX,
|
|
75
|
+
touchActivationDelayMs = DEFAULT_TOUCH_ACTIVATION_DELAY_MS,
|
|
76
|
+
touchActivationTolerancePx = DEFAULT_TOUCH_ACTIVATION_TOLERANCE_PX,
|
|
77
|
+
} = options;
|
|
78
|
+
return useSensors(
|
|
79
|
+
useSensor(MouseSensor, { activationConstraint: { distance: mouseActivationDistancePx } }),
|
|
80
|
+
useSensor(TouchSensor, {
|
|
81
|
+
activationConstraint: {
|
|
82
|
+
delay: touchActivationDelayMs,
|
|
83
|
+
tolerance: touchActivationTolerancePx,
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
|
|
87
|
+
);
|
|
88
|
+
}
|