@astryxdesign/lab 0.2.0-canary.8d64648 → 0.2.0-canary.8dfd7cf
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/BottomSheet/BottomSheet.d.ts +41 -35
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +222 -75
- package/dist/BottomSheet/snapOffsets.d.ts +54 -0
- package/dist/BottomSheet/snapOffsets.d.ts.map +1 -0
- package/dist/BottomSheet/snapOffsets.js +101 -0
- package/dist/BottomSheet/useSheetGestures.d.ts +55 -12
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +383 -40
- package/dist/Chart/index.d.ts +1 -1
- package/dist/Chart/index.d.ts.map +1 -1
- package/dist/Chart/index.js +1 -1
- package/dist/Stepper/Step.d.ts +1 -1
- package/dist/Stepper/Step.js +1 -1
- package/dist/Stepper/Stepper.d.ts +1 -1
- package/dist/Stepper/Stepper.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lab.css +15 -8
- package/package.json +3 -2
- package/src/BottomSheet/BottomSheet.doc.mjs +13 -13
- package/src/BottomSheet/BottomSheet.test.tsx +81 -4
- package/src/BottomSheet/BottomSheet.tsx +333 -97
- package/src/BottomSheet/snapOffsets.test.ts +146 -0
- package/src/BottomSheet/snapOffsets.ts +126 -0
- package/src/BottomSheet/useSheetGestures.doc.mjs +7 -6
- package/src/BottomSheet/useSheetGestures.test.ts +315 -65
- package/src/BottomSheet/useSheetGestures.ts +515 -55
- package/src/Chart/ChartArea.doc.mjs +1 -1
- package/src/Chart/ChartBar.doc.mjs +1 -1
- package/src/Chart/ChartCandlestick.doc.mjs +1 -1
- package/src/Chart/ChartDot.doc.mjs +1 -1
- package/src/Chart/ChartDotGL.doc.mjs +1 -1
- package/src/Chart/ChartDotGLInteractive.doc.mjs +1 -1
- package/src/Chart/ChartHeatmapGL.doc.mjs +1 -1
- package/src/Chart/ChartLine.doc.mjs +1 -1
- package/src/Chart/ChartStreamGL.doc.mjs +1 -1
- package/src/Chart/index.ts +0 -1
- package/src/Chat/ChatEmojiPicker.doc.mjs +1 -1
- package/src/Chat/ChatReactionBar.doc.mjs +1 -1
- package/src/Chat/ChatTypingIndicator.doc.mjs +1 -1
- package/src/Chat/ChatUnreadDivider.doc.mjs +1 -1
- package/src/ChatReasoning/ChatReasoning.doc.mjs +1 -1
- package/src/CircularProgress/CircularProgress.doc.mjs +3 -3
- package/src/CodeEditor/CodeEditor.doc.mjs +1 -1
- package/src/Drawer/Drawer.doc.mjs +3 -3
- package/src/InfoTip/InfoTip.doc.mjs +3 -3
- package/src/LogStream/LogStream.doc.mjs +1 -1
- package/src/Radial/RadialChart.doc.mjs +1 -1
- package/src/RichTextEditor/RichTextEditor.doc.mjs +1 -1
- package/src/SVGIcon/SVGIcon.doc.mjs +1 -1
- package/src/Sankey/SankeyChart.doc.mjs +1 -1
- package/src/Schedule/Schedule.doc.mjs +1 -1
- package/src/Stat/Stat.doc.mjs +3 -3
- package/src/Stepper/Step.doc.mjs +1 -1
- package/src/Stepper/Step.tsx +1 -1
- package/src/Stepper/Stepper.doc.mjs +3 -3
- package/src/Stepper/Stepper.tsx +1 -1
- package/src/ThreeD/3DBar.doc.mjs +1 -1
- package/src/ThreeD/3DChart.doc.mjs +1 -1
- package/src/ThreeD/3DScatter.doc.mjs +1 -1
- package/src/ThreeD/3DScatterGL.doc.mjs +1 -1
- package/src/ThreeD/3DSurface.doc.mjs +1 -1
- package/src/index.ts +1 -1
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
* @output Exports useSheetGestures hook and its option/result types
|
|
9
9
|
* @position Internal to BottomSheet; not exported from the lab entry point
|
|
10
10
|
*
|
|
11
|
-
* Drag
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Drag + snap machinery for the bottom sheet. Tracks a pointer drag down the
|
|
12
|
+
* block axis, translates the sliding surface live, and on release either
|
|
13
|
+
* settles to the nearest snap detent (a slow drag) or dismisses (a fast flick
|
|
14
|
+
* down). A fast flick up expands to the tallest detent. This is the core
|
|
15
|
+
* behavior split the sheet needs: DRAG places, SWIPE closes.
|
|
15
16
|
*
|
|
16
|
-
* Kept private to BottomSheet:
|
|
17
|
-
*
|
|
17
|
+
* Kept private to BottomSheet: a dismiss edge + detents on a bottom-anchored
|
|
18
|
+
* surface are inherently sheet concepts. It is not a general primitive and is
|
|
18
19
|
* intentionally not exported.
|
|
19
20
|
*
|
|
20
21
|
* SSR-safe: no window/document access at module scope; all measurement
|
|
@@ -26,12 +27,61 @@
|
|
|
26
27
|
* - /packages/lab/src/BottomSheet/useSheetGestures.test.ts
|
|
27
28
|
*/
|
|
28
29
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
30
|
+
import { computeDetentOffsets, resolveSettleOffset, scrimOpacityForOffset } from "./snapOffsets.js";
|
|
29
31
|
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
32
|
+
// A flick (fast throw) dismisses (down) or expands (up) regardless of where
|
|
33
|
+
// it ends. Requires both a speed and a distance floor so a small nudge
|
|
34
|
+
// doesn't trigger it.
|
|
35
|
+
const FLICK_VELOCITY = 1.2; // px/ms
|
|
36
|
+
const FLICK_MIN_DISTANCE = 48; // px traveled during the gesture
|
|
37
|
+
// On a slow drag below the shortest detent, dismiss once dragged past it by
|
|
38
|
+
// more than this fraction of that detent's height; otherwise snap back to it.
|
|
39
|
+
const DISMISS_OVERSHOOT_RATIO = 0.4;
|
|
40
|
+
// Within this many px of a detent, the live drag is magnetically eased toward
|
|
41
|
+
// it so it "clicks" into place instead of hovering just off the mark.
|
|
42
|
+
const MAGNET_RANGE = 40;
|
|
43
|
+
// Rubber-band factor for dragging up past fully-open, capped at OVERSCROLL_MAX
|
|
44
|
+
// (the sheet reserves that much bottom padding for the lift to reveal).
|
|
45
|
+
const OVERSCROLL_RESISTANCE = 0.35;
|
|
46
|
+
// SYNC: must match OVERSCROLL_PADDING in BottomSheet.tsx (the reserved bottom
|
|
47
|
+
// padding the lift reveals). Kept as a local const rather than a shared import
|
|
48
|
+
// so it can be used inside stylex.create there.
|
|
49
|
+
const OVERSCROLL_MAX = 48;
|
|
50
|
+
|
|
51
|
+
// Short haptic tick on detent settle where supported. iOS Safari doesn't
|
|
52
|
+
// expose navigator.vibrate, so this is a no-op there; skipped under
|
|
53
|
+
// reduced-motion.
|
|
54
|
+
function hapticTick() {
|
|
55
|
+
if (typeof navigator === 'undefined' || typeof navigator.vibrate !== 'function') {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (prefersReducedMotion()) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
navigator.vibrate(8);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Pull a value toward the nearest of `targets` when within MAGNET_RANGE, easing
|
|
65
|
+
// the last stretch so the surface settles crisply onto a detent while dragging.
|
|
66
|
+
function magnetize(value, targets) {
|
|
67
|
+
let nearestTarget = targets[0];
|
|
68
|
+
let nearestDist = Math.abs(value - nearestTarget);
|
|
69
|
+
for (const t of targets) {
|
|
70
|
+
const d = Math.abs(value - t);
|
|
71
|
+
if (d < nearestDist) {
|
|
72
|
+
nearestDist = d;
|
|
73
|
+
nearestTarget = t;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (nearestDist >= MAGNET_RANGE) {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
// Ease-in over the range: pull grows as you approach (t^2), so the click
|
|
80
|
+
// feels magnetic near the detent but doesn't fight a deliberate drag-through.
|
|
81
|
+
const t = nearestDist / MAGNET_RANGE;
|
|
82
|
+
const pull = 1 - t * t;
|
|
83
|
+
return value + (nearestTarget - value) * pull;
|
|
84
|
+
}
|
|
35
85
|
function prefersReducedMotion() {
|
|
36
86
|
if (typeof window === 'undefined' || !window.matchMedia) {
|
|
37
87
|
return false;
|
|
@@ -40,15 +90,17 @@ function prefersReducedMotion() {
|
|
|
40
90
|
}
|
|
41
91
|
|
|
42
92
|
/**
|
|
43
|
-
* Drag
|
|
44
|
-
*
|
|
45
|
-
*
|
|
93
|
+
* Drag + snap machinery for the bottom sheet. Returns props to spread on the
|
|
94
|
+
* grab handle and the sliding surface, plus live drag state. A slow drag
|
|
95
|
+
* settles to the nearest detent; a fast downward flick dismisses; a fast
|
|
96
|
+
* upward flick expands to the tallest detent.
|
|
46
97
|
*
|
|
47
98
|
* @example
|
|
48
99
|
* ```
|
|
49
100
|
* const {contentProps, handleProps} = useSheetGestures({
|
|
50
101
|
* isOpen,
|
|
51
102
|
* onDismiss: () => onOpenChange(false),
|
|
103
|
+
* snapHeights: () => [240, 0.5 * (window.visualViewport?.height ?? 0)],
|
|
52
104
|
* });
|
|
53
105
|
* <div {...contentProps}>
|
|
54
106
|
* <div {...handleProps} />
|
|
@@ -58,60 +110,146 @@ function prefersReducedMotion() {
|
|
|
58
110
|
*/
|
|
59
111
|
export function useSheetGestures({
|
|
60
112
|
isOpen,
|
|
61
|
-
onDismiss
|
|
113
|
+
onDismiss,
|
|
114
|
+
snapHeights,
|
|
115
|
+
onSnap,
|
|
116
|
+
onScrimOpacity
|
|
62
117
|
}) {
|
|
63
118
|
const [dragOffset, setDragOffset] = useState(0);
|
|
119
|
+
const [settledOffset, setSettledOffset] = useState(0);
|
|
64
120
|
const [isDragging, setIsDragging] = useState(false);
|
|
65
|
-
|
|
66
|
-
// Latest callback ref so pointer handlers stay stable across renders.
|
|
67
121
|
const onDismissRef = useRef(onDismiss);
|
|
122
|
+
const onSnapRef = useRef(onSnap);
|
|
123
|
+
const onScrimOpacityRef = useRef(onScrimOpacity);
|
|
124
|
+
const snapHeightsRef = useRef(snapHeights);
|
|
68
125
|
useEffect(() => {
|
|
69
126
|
onDismissRef.current = onDismiss;
|
|
127
|
+
onSnapRef.current = onSnap;
|
|
128
|
+
onScrimOpacityRef.current = onScrimOpacity;
|
|
129
|
+
snapHeightsRef.current = snapHeights;
|
|
70
130
|
});
|
|
71
131
|
|
|
72
132
|
// Live drag bookkeeping (refs so pointermove doesn't churn renders).
|
|
73
133
|
const dragStateRef = useRef(null);
|
|
74
134
|
|
|
75
|
-
//
|
|
135
|
+
// Fully-open height, tracked by a ResizeObserver (see sheetRef) so detents
|
|
136
|
+
// stay correct across rotation / viewport changes without re-measuring.
|
|
137
|
+
const sheetHeightRef = useRef(0);
|
|
138
|
+
const sheetElRef = useRef(null);
|
|
139
|
+
const observerRef = useRef(null);
|
|
140
|
+
const sheetRef = useCallback(node => {
|
|
141
|
+
observerRef.current?.disconnect();
|
|
142
|
+
observerRef.current = null;
|
|
143
|
+
sheetElRef.current = node;
|
|
144
|
+
if (!node || typeof ResizeObserver === 'undefined') {
|
|
145
|
+
if (node) {
|
|
146
|
+
sheetHeightRef.current = node.getBoundingClientRect().height;
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
sheetHeightRef.current = node.getBoundingClientRect().height;
|
|
151
|
+
const ro = new ResizeObserver(entries => {
|
|
152
|
+
const box = entries[0]?.contentRect;
|
|
153
|
+
if (box && box.height > 0) {
|
|
154
|
+
sheetHeightRef.current = box.height;
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
ro.observe(node);
|
|
158
|
+
observerRef.current = ro;
|
|
159
|
+
}, []);
|
|
160
|
+
useEffect(() => () => observerRef.current?.disconnect(), []);
|
|
161
|
+
|
|
162
|
+
// Reset to the tallest detent each time the sheet re-opens.
|
|
76
163
|
useEffect(() => {
|
|
77
164
|
if (isOpen) {
|
|
78
165
|
setDragOffset(0);
|
|
166
|
+
setSettledOffset(0);
|
|
79
167
|
setIsDragging(false);
|
|
80
168
|
}
|
|
81
169
|
}, [isOpen]);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
170
|
+
|
|
171
|
+
// The fully-open height for detent math. Prefer the ResizeObserver-locked
|
|
172
|
+
// value; fall back to a live measure of the tracked sheet element if the
|
|
173
|
+
// observer hasn't reported yet.
|
|
174
|
+
const measureHeight = useCallback(() => {
|
|
175
|
+
if (sheetHeightRef.current > 0) {
|
|
176
|
+
return sheetHeightRef.current;
|
|
177
|
+
}
|
|
178
|
+
return sheetElRef.current?.getBoundingClientRect().height ?? 0;
|
|
179
|
+
}, []);
|
|
180
|
+
|
|
181
|
+
// Detent translate offsets (px) from the tallest detent, ascending. The
|
|
182
|
+
// tallest detent is offset 0; shorter detents translate further down.
|
|
183
|
+
// Snap heights are resolved lazily here (at drag start) so they reflect the
|
|
184
|
+
// current viewport without a persistent listener.
|
|
185
|
+
const detentOffsets = useCallback(height => {
|
|
186
|
+
return computeDetentOffsets(height, snapHeightsRef.current?.() ?? []);
|
|
85
187
|
}, []);
|
|
86
|
-
const settleFromDrag = useCallback((offset, velocity, height) => {
|
|
87
|
-
const
|
|
88
|
-
|
|
188
|
+
const settleFromDrag = useCallback((offset, velocity, height, dir, travel, baseOffset) => {
|
|
189
|
+
const offsets = detentOffsets(height);
|
|
190
|
+
const maxOffset = offsets[offsets.length - 1];
|
|
191
|
+
const shortestDetentHeight = height - maxOffset;
|
|
192
|
+
const speed = Math.abs(velocity);
|
|
193
|
+
const isFlick = speed > FLICK_VELOCITY && travel > FLICK_MIN_DISTANCE;
|
|
194
|
+
if (dir > 0 && isFlick) {
|
|
89
195
|
onDismissRef.current();
|
|
90
196
|
return;
|
|
91
197
|
}
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
198
|
+
// Fast upward flick = expand to the tallest detent (the sheet's full
|
|
199
|
+
// provided height).
|
|
200
|
+
if (dir < 0 && isFlick) {
|
|
201
|
+
setSettledOffset(0);
|
|
202
|
+
onSnapRef.current?.(height);
|
|
203
|
+
onScrimOpacityRef.current?.(1);
|
|
204
|
+
hapticTick();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (offset > maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO) {
|
|
208
|
+
onDismissRef.current();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// Settle to the nearest detent in the drag direction (never back past
|
|
212
|
+
// the starting detent), de-duped and direction-clamped by the util.
|
|
213
|
+
const target = resolveSettleOffset(offset, offsets, dir, baseOffset);
|
|
214
|
+
setSettledOffset(target);
|
|
215
|
+
onSnapRef.current?.(height - target);
|
|
216
|
+
// Keep the scrim in sync with the resting detent (hidden at the peek).
|
|
217
|
+
const dismissOffset = maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO;
|
|
218
|
+
onScrimOpacityRef.current?.(scrimOpacityForOffset(target, offsets, dismissOffset));
|
|
219
|
+
// Haptic "tick" when landing on a different detent (where supported).
|
|
220
|
+
if (target !== baseOffset) {
|
|
221
|
+
hapticTick();
|
|
222
|
+
}
|
|
223
|
+
}, [detentOffsets]);
|
|
224
|
+
const beginDrag = useCallback((event, sheetHeight, startCoord) => {
|
|
96
225
|
const target = event.currentTarget;
|
|
97
226
|
target.setPointerCapture?.(event.pointerId);
|
|
227
|
+
// `startCoord` lets a body-overscroll drag anchor at the original
|
|
228
|
+
// pointer-down position (not the promotion point), so the first frame's
|
|
229
|
+
// delta reflects the full pull distance.
|
|
230
|
+
const start = startCoord ?? event.clientY;
|
|
98
231
|
dragStateRef.current = {
|
|
99
232
|
pointerId: event.pointerId,
|
|
100
|
-
startCoord:
|
|
233
|
+
startCoord: start,
|
|
101
234
|
lastCoord: event.clientY,
|
|
102
235
|
lastTime: event.timeStamp,
|
|
103
236
|
velocity: 0,
|
|
104
|
-
height:
|
|
237
|
+
height: sheetHeight,
|
|
238
|
+
baseOffset: settledOffset
|
|
105
239
|
};
|
|
240
|
+
// Seed dragOffset at the resting detent so flipping isDragging doesn't
|
|
241
|
+
// jump the sheet to fully-open for one frame.
|
|
242
|
+
setDragOffset(settledOffset);
|
|
106
243
|
setIsDragging(true);
|
|
107
|
-
}, [
|
|
244
|
+
}, [settledOffset]);
|
|
245
|
+
const handlePointerDown = useCallback(event => {
|
|
246
|
+
beginDrag(event, measureHeight());
|
|
247
|
+
}, [beginDrag, measureHeight]);
|
|
108
248
|
const handlePointerMove = useCallback(event => {
|
|
109
249
|
const state = dragStateRef.current;
|
|
110
250
|
if (!state || state.pointerId !== event.pointerId) {
|
|
111
251
|
return;
|
|
112
252
|
}
|
|
113
|
-
// Dragging DOWN (increasing Y) collapses/dismisses. Only allow drag in
|
|
114
|
-
// the closing direction past the fully-open position (no overscroll).
|
|
115
253
|
const delta = event.clientY - state.startCoord;
|
|
116
254
|
const dt = event.timeStamp - state.lastTime;
|
|
117
255
|
if (dt > 0) {
|
|
@@ -119,8 +257,30 @@ export function useSheetGestures({
|
|
|
119
257
|
state.lastCoord = event.clientY;
|
|
120
258
|
state.lastTime = event.timeStamp;
|
|
121
259
|
}
|
|
122
|
-
|
|
123
|
-
|
|
260
|
+
const offsets = detentOffsets(state.height);
|
|
261
|
+
const raw = state.baseOffset + delta;
|
|
262
|
+
const maxDetentOffset = offsets[offsets.length - 1];
|
|
263
|
+
let next;
|
|
264
|
+
if (raw < 0) {
|
|
265
|
+
// Up past fully-open: damped + capped rubber-band; springs back on release.
|
|
266
|
+
next = Math.max(-OVERSCROLL_MAX, raw * OVERSCROLL_RESISTANCE);
|
|
267
|
+
} else if (raw > maxDetentOffset) {
|
|
268
|
+
// In the dismiss zone: no magnet, so it doesn't fight a drag-to-close.
|
|
269
|
+
next = raw;
|
|
270
|
+
} else {
|
|
271
|
+
// Between detents: magnetically ease toward a nearby one.
|
|
272
|
+
next = magnetize(raw, offsets);
|
|
273
|
+
}
|
|
274
|
+
setDragOffset(next);
|
|
275
|
+
|
|
276
|
+
// Mirror the scrim to the live drag: full at/above the mid detent,
|
|
277
|
+
// fading to hidden as it collapses onto the peek detent and through the
|
|
278
|
+
// dismiss overshoot.
|
|
279
|
+
const floorOffset = offsets[offsets.length - 1];
|
|
280
|
+
const shortestDetentHeight = state.height - floorOffset;
|
|
281
|
+
const dismissOffset = floorOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO;
|
|
282
|
+
onScrimOpacityRef.current?.(scrimOpacityForOffset(next, offsets, dismissOffset));
|
|
283
|
+
}, [detentOffsets]);
|
|
124
284
|
const endDrag = useCallback(event => {
|
|
125
285
|
const state = dragStateRef.current;
|
|
126
286
|
if (!state || state.pointerId !== event.pointerId) {
|
|
@@ -128,22 +288,195 @@ export function useSheetGestures({
|
|
|
128
288
|
}
|
|
129
289
|
const target = event.currentTarget;
|
|
130
290
|
target.releasePointerCapture?.(event.pointerId);
|
|
131
|
-
const
|
|
291
|
+
const delta = event.clientY - state.startCoord;
|
|
292
|
+
const offset = Math.max(0, state.baseOffset + delta);
|
|
293
|
+
const dir = delta === 0 ? 0 : delta > 0 ? 1 : -1;
|
|
132
294
|
dragStateRef.current = null;
|
|
133
295
|
setIsDragging(false);
|
|
134
|
-
settleFromDrag(offset, state.velocity, state.height || 1);
|
|
296
|
+
settleFromDrag(offset, state.velocity, state.height || 1, dir, Math.abs(delta), state.baseOffset);
|
|
135
297
|
}, [settleFromDrag]);
|
|
136
298
|
|
|
137
|
-
//
|
|
299
|
+
// Pointer path for the body at-top pull-down (desktop / mouse). Touch uses
|
|
300
|
+
// the non-passive listener below, since pointer events are cancelled once a
|
|
301
|
+
// native pan starts.
|
|
302
|
+
const armedBodyRef = useRef(null);
|
|
303
|
+
const handleBodyPointerDown = useCallback(event => {
|
|
304
|
+
const scroller = event.currentTarget;
|
|
305
|
+
if (scroller.scrollTop > 0) {
|
|
306
|
+
armedBodyRef.current = null;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
armedBodyRef.current = {
|
|
310
|
+
pointerId: event.pointerId,
|
|
311
|
+
startCoord: event.clientY,
|
|
312
|
+
scroller
|
|
313
|
+
};
|
|
314
|
+
}, []);
|
|
315
|
+
const handleBodyPointerMove = useCallback(event => {
|
|
316
|
+
if (dragStateRef.current) {
|
|
317
|
+
handlePointerMove(event);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const armed = armedBodyRef.current;
|
|
321
|
+
if (!armed || armed.pointerId !== event.pointerId) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const delta = event.clientY - armed.startCoord;
|
|
325
|
+
if (delta > 0 && armed.scroller.scrollTop <= 0) {
|
|
326
|
+
// Downward pull at the top: promote to a sheet drag, anchored at the
|
|
327
|
+
// original pointer-down position so the pull distance carries over.
|
|
328
|
+
armedBodyRef.current = null;
|
|
329
|
+
beginDrag(event, measureHeight(), armed.startCoord);
|
|
330
|
+
handlePointerMove(event);
|
|
331
|
+
} else if (delta < 0) {
|
|
332
|
+
// Upward move = the user is scrolling; disarm so we don't hijack it.
|
|
333
|
+
armedBodyRef.current = null;
|
|
334
|
+
}
|
|
335
|
+
}, [beginDrag, handlePointerMove, measureHeight]);
|
|
336
|
+
const handleBodyEnd = useCallback(event => {
|
|
337
|
+
armedBodyRef.current = null;
|
|
338
|
+
if (dragStateRef.current) {
|
|
339
|
+
endDrag(event);
|
|
340
|
+
}
|
|
341
|
+
}, [endDrag]);
|
|
342
|
+
|
|
343
|
+
// Non-passive touchmove is the reliable scroll<->drag handoff on touch:
|
|
344
|
+
// preventDefault() at a scroll edge stops the native scroll and drives the
|
|
345
|
+
// sheet drag through the pointer math (Touch adapted to the fields it reads).
|
|
346
|
+
const bodyNodeRef = useRef(null);
|
|
347
|
+
const touchDragRef = useRef(null);
|
|
348
|
+
const prevTouchHandlers = useRef(null);
|
|
349
|
+
const beginDragRef = useRef(beginDrag);
|
|
350
|
+
const pointerMoveRef = useRef(handlePointerMove);
|
|
351
|
+
const endDragRef = useRef(endDrag);
|
|
352
|
+
const measureHeightRef = useRef(measureHeight);
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
beginDragRef.current = beginDrag;
|
|
355
|
+
pointerMoveRef.current = handlePointerMove;
|
|
356
|
+
endDragRef.current = endDrag;
|
|
357
|
+
measureHeightRef.current = measureHeight;
|
|
358
|
+
});
|
|
359
|
+
const bodyRef = useCallback(node => {
|
|
360
|
+
const asPointer = (touch, target) => ({
|
|
361
|
+
pointerId: touch.identifier,
|
|
362
|
+
clientY: touch.clientY,
|
|
363
|
+
timeStamp: Date.now(),
|
|
364
|
+
currentTarget: target,
|
|
365
|
+
setPointerCapture: () => {},
|
|
366
|
+
releasePointerCapture: () => {}
|
|
367
|
+
});
|
|
368
|
+
const atTop = el => el.scrollTop <= 0;
|
|
369
|
+
const atBottom = el => el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
|
|
370
|
+
const onTouchStart = event => {
|
|
371
|
+
const scroller = event.currentTarget;
|
|
372
|
+
const touch = event.changedTouches[0];
|
|
373
|
+
// Arm only at a scroll edge; from mid-content this is an ordinary scroll.
|
|
374
|
+
// At the top, a pull DOWN hands off (collapse); at the bottom, a pull UP
|
|
375
|
+
// hands off (expand). Record the edge so the move handler matches it.
|
|
376
|
+
if (!touch) {
|
|
377
|
+
touchDragRef.current = null;
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const top = atTop(scroller);
|
|
381
|
+
const bottom = atBottom(scroller);
|
|
382
|
+
if (!top && !bottom) {
|
|
383
|
+
touchDragRef.current = null;
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
touchDragRef.current = {
|
|
387
|
+
id: touch.identifier,
|
|
388
|
+
startY: touch.clientY,
|
|
389
|
+
top,
|
|
390
|
+
bottom
|
|
391
|
+
};
|
|
392
|
+
};
|
|
393
|
+
const onTouchMove = event => {
|
|
394
|
+
const scroller = event.currentTarget;
|
|
395
|
+
if (dragStateRef.current) {
|
|
396
|
+
const t = [...event.changedTouches].find(x => x.identifier === dragStateRef.current?.pointerId);
|
|
397
|
+
if (t) {
|
|
398
|
+
event.preventDefault();
|
|
399
|
+
pointerMoveRef.current(asPointer(t, scroller));
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const armed = touchDragRef.current;
|
|
404
|
+
if (!armed) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const t = [...event.changedTouches].find(x => x.identifier === armed.id);
|
|
408
|
+
if (!t) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const delta = t.clientY - armed.startY;
|
|
412
|
+
// Promote to a sheet drag on a pull that opposes the armed edge and can
|
|
413
|
+
// no longer scroll that way: at the top, a downward pull (delta > 0)
|
|
414
|
+
// collapses; at the bottom, an upward pull (delta < 0) expands. The
|
|
415
|
+
// opposite direction is a real scroll, so disarm and let it through.
|
|
416
|
+
const pullDownAtTop = armed.top && delta > 0 && atTop(scroller);
|
|
417
|
+
const pullUpAtBottom = armed.bottom && delta < 0 && atBottom(scroller);
|
|
418
|
+
if (pullDownAtTop || pullUpAtBottom) {
|
|
419
|
+
event.preventDefault();
|
|
420
|
+
touchDragRef.current = null;
|
|
421
|
+
beginDragRef.current(asPointer(t, scroller), measureHeightRef.current(), armed.startY);
|
|
422
|
+
pointerMoveRef.current(asPointer(t, scroller));
|
|
423
|
+
} else if (armed.top && delta < 0 || armed.bottom && delta > 0) {
|
|
424
|
+
// Scrolling away from the armed edge; hand back to native scroll.
|
|
425
|
+
touchDragRef.current = null;
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
const onTouchEnd = event => {
|
|
429
|
+
touchDragRef.current = null;
|
|
430
|
+
if (dragStateRef.current) {
|
|
431
|
+
const t = event.changedTouches[0];
|
|
432
|
+
if (t) {
|
|
433
|
+
endDragRef.current(asPointer(t, event.currentTarget));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
const prev = bodyNodeRef.current;
|
|
438
|
+
if (prev && prevTouchHandlers.current) {
|
|
439
|
+
const h = prevTouchHandlers.current;
|
|
440
|
+
prev.removeEventListener('touchstart', h.start);
|
|
441
|
+
prev.removeEventListener('touchmove', h.move);
|
|
442
|
+
prev.removeEventListener('touchend', h.end);
|
|
443
|
+
prev.removeEventListener('touchcancel', h.end);
|
|
444
|
+
}
|
|
445
|
+
bodyNodeRef.current = node;
|
|
446
|
+
if (node) {
|
|
447
|
+
node.addEventListener('touchstart', onTouchStart, {
|
|
448
|
+
passive: true
|
|
449
|
+
});
|
|
450
|
+
node.addEventListener('touchmove', onTouchMove, {
|
|
451
|
+
passive: false
|
|
452
|
+
});
|
|
453
|
+
node.addEventListener('touchend', onTouchEnd, {
|
|
454
|
+
passive: true
|
|
455
|
+
});
|
|
456
|
+
node.addEventListener('touchcancel', onTouchEnd, {
|
|
457
|
+
passive: true
|
|
458
|
+
});
|
|
459
|
+
prevTouchHandlers.current = {
|
|
460
|
+
start: onTouchStart,
|
|
461
|
+
move: onTouchMove,
|
|
462
|
+
end: onTouchEnd
|
|
463
|
+
};
|
|
464
|
+
} else {
|
|
465
|
+
prevTouchHandlers.current = null;
|
|
466
|
+
}
|
|
467
|
+
}, []);
|
|
138
468
|
const reducedMotion = useMemo(prefersReducedMotion, [isOpen]);
|
|
469
|
+
|
|
470
|
+
// While dragging, follow the finger; otherwise rest at the settled detent.
|
|
471
|
+
const activeOffset = isDragging ? dragOffset : settledOffset;
|
|
139
472
|
const contentProps = useMemo(() => ({
|
|
140
473
|
style: {
|
|
141
|
-
transform:
|
|
474
|
+
transform: activeOffset !== 0 ? `translateY(${activeOffset}px)` : undefined,
|
|
142
475
|
transition: isDragging || reducedMotion ? 'none' : undefined,
|
|
143
476
|
touchAction: 'none',
|
|
144
477
|
overscrollBehavior: 'contain'
|
|
145
478
|
}
|
|
146
|
-
}), [
|
|
479
|
+
}), [activeOffset, isDragging, reducedMotion]);
|
|
147
480
|
const handleProps = useMemo(() => ({
|
|
148
481
|
style: {
|
|
149
482
|
touchAction: 'none',
|
|
@@ -154,10 +487,20 @@ export function useSheetGestures({
|
|
|
154
487
|
onPointerUp: endDrag,
|
|
155
488
|
onPointerCancel: endDrag
|
|
156
489
|
}), [handlePointerDown, handlePointerMove, endDrag]);
|
|
490
|
+
const bodyProps = useMemo(() => ({
|
|
491
|
+
ref: bodyRef,
|
|
492
|
+
onPointerDown: handleBodyPointerDown,
|
|
493
|
+
onPointerMove: handleBodyPointerMove,
|
|
494
|
+
onPointerUp: handleBodyEnd,
|
|
495
|
+
onPointerCancel: handleBodyEnd
|
|
496
|
+
}), [bodyRef, handleBodyPointerDown, handleBodyPointerMove, handleBodyEnd]);
|
|
157
497
|
return {
|
|
498
|
+
sheetRef,
|
|
158
499
|
contentProps,
|
|
159
500
|
handleProps,
|
|
501
|
+
bodyProps,
|
|
160
502
|
dragOffset,
|
|
503
|
+
settledOffset,
|
|
161
504
|
isDragging
|
|
162
505
|
};
|
|
163
506
|
}
|
package/dist/Chart/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export { isBandScale, xPixel } from './utils';
|
|
|
21
21
|
export { useChartColors } from './useChartColors';
|
|
22
22
|
export { getChartColors, getChartColorsFromResolver, type ChartColorsAPI, type SequentialHue, type TokenResolver, } from './getChartColors';
|
|
23
23
|
export { useChartRange, type UseChartRangeOptions, type UseChartRangeReturn, } from './useChartRange';
|
|
24
|
-
export { hexToGL, getCanvasDPR, getWebGLContext, setupGLState, sizeCanvas, mountCanvasOverSVG, compileShader,
|
|
24
|
+
export { hexToGL, getCanvasDPR, getWebGLContext, setupGLState, sizeCanvas, mountCanvasOverSVG, compileShader, CIRCLE_FRAG_BODY, POINT_SIZE_COMPENSATION, } from './webgl';
|
|
25
25
|
export { ChartBrush, type ChartBrushProps, type BrushMode, type BrushRange, } from './ChartBrush';
|
|
26
26
|
export { ChartZoom, type ChartZoomProps, type ZoomToolbarPosition, } from './ChartZoom';
|
|
27
27
|
export { ChartSelect, type ChartSelectProps } from './ChartSelect';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Chart/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAC,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAC,SAAS,EAAE,KAAK,cAAc,EAAC,MAAM,aAAa,CAAC;AAC3D,OAAO,EACL,aAAa,EACb,QAAQ,EACR,OAAO,EACP,SAAS,EACT,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,SAAS,EAAE,KAAK,cAAc,EAAC,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAC,QAAQ,EAAE,KAAK,aAAa,EAAC,MAAM,YAAY,CAAC;AACxD,OAAO,EAAC,SAAS,EAAE,KAAK,cAAc,EAAC,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAC,SAAS,EAAE,KAAK,cAAc,EAAC,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAC,aAAa,EAAE,KAAK,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AACvE,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,QAAQ,EAAE,KAAK,aAAa,EAAC,MAAM,YAAY,CAAC;AACxD,OAAO,EAAC,UAAU,EAAE,KAAK,eAAe,EAAC,MAAM,cAAc,CAAC;AAC9D,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AACxC,YAAY,EAAC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAC,QAAQ,EAAE,KAAK,OAAO,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,EAAC,WAAW,EAAE,MAAM,EAAC,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;AAChD,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,OAAO,EACP,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/Chart/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAC,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAC,SAAS,EAAE,KAAK,cAAc,EAAC,MAAM,aAAa,CAAC;AAC3D,OAAO,EACL,aAAa,EACb,QAAQ,EACR,OAAO,EACP,SAAS,EACT,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAC,SAAS,EAAE,KAAK,cAAc,EAAC,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAC,QAAQ,EAAE,KAAK,aAAa,EAAC,MAAM,YAAY,CAAC;AACxD,OAAO,EAAC,SAAS,EAAE,KAAK,cAAc,EAAC,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAC,SAAS,EAAE,KAAK,cAAc,EAAC,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAC,aAAa,EAAE,KAAK,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AACvE,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,QAAQ,EAAE,KAAK,aAAa,EAAC,MAAM,YAAY,CAAC;AACxD,OAAO,EAAC,UAAU,EAAE,KAAK,eAAe,EAAC,MAAM,cAAc,CAAC;AAC9D,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAC,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AACxC,YAAY,EAAC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAC,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAC,QAAQ,EAAE,KAAK,OAAO,EAAC,MAAM,MAAM,CAAC;AAC5C,OAAO,EAAC,WAAW,EAAE,MAAM,EAAC,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAC;AAChD,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,OAAO,EACP,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAC,WAAW,EAAE,KAAK,gBAAgB,EAAC,MAAM,eAAe,CAAC;AACjE,OAAO,EACL,kBAAkB,EAClB,KAAK,uBAAuB,GAC7B,MAAM,sBAAsB,CAAC"}
|
package/dist/Chart/index.js
CHANGED
|
@@ -22,7 +22,7 @@ export { isBandScale, xPixel } from "./utils.js";
|
|
|
22
22
|
export { useChartColors } from "./useChartColors.js";
|
|
23
23
|
export { getChartColors, getChartColorsFromResolver } from "./getChartColors.js";
|
|
24
24
|
export { useChartRange } from "./useChartRange.js";
|
|
25
|
-
export { hexToGL, getCanvasDPR, getWebGLContext, setupGLState, sizeCanvas, mountCanvasOverSVG, compileShader,
|
|
25
|
+
export { hexToGL, getCanvasDPR, getWebGLContext, setupGLState, sizeCanvas, mountCanvasOverSVG, compileShader, CIRCLE_FRAG_BODY, POINT_SIZE_COMPENSATION } from "./webgl.js";
|
|
26
26
|
export { ChartBrush } from "./ChartBrush.js";
|
|
27
27
|
export { ChartZoom } from "./ChartZoom.js";
|
|
28
28
|
export { ChartSelect } from "./ChartSelect.js";
|
package/dist/Stepper/Step.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - /packages/lab/src/Stepper/Stepper.test.tsx
|
|
10
10
|
* - /packages/lab/src/Stepper/index.ts
|
|
11
11
|
* - /apps/storybook/stories/Stepper.stories.tsx
|
|
12
|
-
* - /packages/cli/templates/blocks/components/Stepper/ (showcase blocks)
|
|
12
|
+
* - /packages/cli/assets/templates/blocks/components/Stepper/ (showcase blocks)
|
|
13
13
|
*/
|
|
14
14
|
import { type ReactNode } from 'react';
|
|
15
15
|
import type { BaseProps } from '@astryxdesign/core';
|
package/dist/Stepper/Step.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - /packages/lab/src/Stepper/Stepper.test.tsx
|
|
14
14
|
* - /packages/lab/src/Stepper/index.ts
|
|
15
15
|
* - /apps/storybook/stories/Stepper.stories.tsx
|
|
16
|
-
* - /packages/cli/templates/blocks/components/Stepper/ (showcase blocks)
|
|
16
|
+
* - /packages/cli/assets/templates/blocks/components/Stepper/ (showcase blocks)
|
|
17
17
|
*/
|
|
18
18
|
import * as stylex from '@stylexjs/stylex';
|
|
19
19
|
import '@astryxdesign/core/theme/tokens.stylex';
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - /packages/lab/src/Stepper/Stepper.test.tsx (tests for new/changed behavior)
|
|
10
10
|
* - /packages/lab/src/Stepper/index.ts (exports if types change)
|
|
11
11
|
* - /apps/storybook/stories/Stepper.stories.tsx (storybook stories)
|
|
12
|
-
* - /packages/cli/templates/blocks/components/Stepper/ (showcase blocks)
|
|
12
|
+
* - /packages/cli/assets/templates/blocks/components/Stepper/ (showcase blocks)
|
|
13
13
|
*/
|
|
14
14
|
import { type ReactNode } from 'react';
|
|
15
15
|
import type { BaseProps } from '@astryxdesign/core';
|
package/dist/Stepper/Stepper.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - /packages/lab/src/Stepper/Stepper.test.tsx (tests for new/changed behavior)
|
|
14
14
|
* - /packages/lab/src/Stepper/index.ts (exports if types change)
|
|
15
15
|
* - /apps/storybook/stories/Stepper.stories.tsx (storybook stories)
|
|
16
|
-
* - /packages/cli/templates/blocks/components/Stepper/ (showcase blocks)
|
|
16
|
+
* - /packages/cli/assets/templates/blocks/components/Stepper/ (showcase blocks)
|
|
17
17
|
*/
|
|
18
18
|
import { Children, useMemo } from 'react';
|
|
19
19
|
import * as stylex from '@stylexjs/stylex';
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ export * from "./Chat/index.js";
|
|
|
27
27
|
// Drawer — experimental overlay panel
|
|
28
28
|
export { Drawer } from "./Drawer/index.js";
|
|
29
29
|
|
|
30
|
-
// BottomSheet — mobile touch sheet
|
|
30
|
+
// BottomSheet — mobile touch sheet on a native modal <dialog>
|
|
31
31
|
export { BottomSheet } from "./BottomSheet/index.js";
|
|
32
32
|
|
|
33
33
|
// Stat — experimental KPI/metric display
|