hiraki 0.0.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/README.md +177 -0
- package/dist/index.cjs +1333 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +113 -0
- package/dist/index.d.ts +113 -0
- package/dist/index.js +1331 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1331 @@
|
|
|
1
|
+
import { createContext, forwardRef, isValidElement, cloneElement, useContext, useState, useRef, useMemo, useCallback, useEffect, useLayoutEffect } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
|
|
5
|
+
// src/components/root.tsx
|
|
6
|
+
var DrawerContext = createContext(null);
|
|
7
|
+
function useDrawerContext() {
|
|
8
|
+
const ctx = useContext(DrawerContext);
|
|
9
|
+
if (!ctx) {
|
|
10
|
+
throw new Error("Drawer compound components must be used within <Drawer.Root>");
|
|
11
|
+
}
|
|
12
|
+
return ctx;
|
|
13
|
+
}
|
|
14
|
+
function useControllableState({
|
|
15
|
+
controlled,
|
|
16
|
+
defaultValue,
|
|
17
|
+
onChange
|
|
18
|
+
}) {
|
|
19
|
+
const [uncontrolled, setUncontrolled] = useState(defaultValue);
|
|
20
|
+
const isControlled = controlled !== void 0;
|
|
21
|
+
const value = isControlled ? controlled : uncontrolled;
|
|
22
|
+
const onChangeRef = useRef(onChange);
|
|
23
|
+
onChangeRef.current = onChange;
|
|
24
|
+
const setValue = useCallback(
|
|
25
|
+
(next) => {
|
|
26
|
+
const nextValue = typeof next === "function" ? next(value) : next;
|
|
27
|
+
if (!isControlled) setUncontrolled(nextValue);
|
|
28
|
+
onChangeRef.current?.(nextValue);
|
|
29
|
+
},
|
|
30
|
+
[isControlled, value]
|
|
31
|
+
);
|
|
32
|
+
return [value, setValue];
|
|
33
|
+
}
|
|
34
|
+
function resolveSnapPoint(snap, viewportSize, contentSize) {
|
|
35
|
+
if (snap === "content") return Math.min(contentSize ?? viewportSize, viewportSize);
|
|
36
|
+
if (typeof snap === "number") return Math.min(Math.max(snap, 0), viewportSize);
|
|
37
|
+
const percent = parseFloat(snap) / 100;
|
|
38
|
+
return Math.round(viewportSize * percent);
|
|
39
|
+
}
|
|
40
|
+
function useSnapPoints({
|
|
41
|
+
snapPoints,
|
|
42
|
+
viewportSize,
|
|
43
|
+
contentSize,
|
|
44
|
+
activeSnapPoint,
|
|
45
|
+
onSnapPointChange
|
|
46
|
+
}) {
|
|
47
|
+
const [rawActiveSnapIndex, setRawActiveSnapIndex] = useControllableState({
|
|
48
|
+
controlled: activeSnapPoint,
|
|
49
|
+
defaultValue: snapPoints.length > 0 ? snapPoints.length - 1 : 0,
|
|
50
|
+
onChange: onSnapPointChange
|
|
51
|
+
});
|
|
52
|
+
const resolvedSnapPoints = useMemo(() => {
|
|
53
|
+
if (snapPoints.length === 0) return [{ value: viewportSize }];
|
|
54
|
+
return snapPoints.map((sp) => ({
|
|
55
|
+
value: resolveSnapPoint(sp, viewportSize, contentSize),
|
|
56
|
+
label: typeof sp === "string" ? sp : void 0
|
|
57
|
+
}));
|
|
58
|
+
}, [snapPoints, viewportSize, contentSize]);
|
|
59
|
+
const snapEntries = useMemo(
|
|
60
|
+
() => resolvedSnapPoints.map((sp, i) => ({ value: sp.value, index: i })),
|
|
61
|
+
[resolvedSnapPoints]
|
|
62
|
+
);
|
|
63
|
+
const maxIndex = Math.max(resolvedSnapPoints.length - 1, 0);
|
|
64
|
+
const activeSnapIndex = Math.min(Math.max(rawActiveSnapIndex, 0), maxIndex);
|
|
65
|
+
const setActiveSnapIndex = useCallback(
|
|
66
|
+
(index) => {
|
|
67
|
+
setRawActiveSnapIndex(Math.min(Math.max(index, 0), maxIndex));
|
|
68
|
+
},
|
|
69
|
+
[maxIndex, setRawActiveSnapIndex]
|
|
70
|
+
);
|
|
71
|
+
const activeSnapPx = resolvedSnapPoints[activeSnapIndex]?.value ?? viewportSize;
|
|
72
|
+
const translateForSnap = useCallback(
|
|
73
|
+
(index, maxTranslate) => {
|
|
74
|
+
const clampedIndex = Math.min(Math.max(index, 0), maxIndex);
|
|
75
|
+
const snapPx = resolvedSnapPoints[clampedIndex]?.value ?? 0;
|
|
76
|
+
return Math.max(0, maxTranslate - snapPx);
|
|
77
|
+
},
|
|
78
|
+
[maxIndex, resolvedSnapPoints]
|
|
79
|
+
);
|
|
80
|
+
return { resolvedSnapPoints, snapEntries, activeSnapIndex, setActiveSnapIndex, activeSnapPx, translateForSnap };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/engine/pointer-tracker.ts
|
|
84
|
+
var DEFAULT_SMOOTHING = 0.3;
|
|
85
|
+
var DEFAULT_AXIS_LOCK = 8;
|
|
86
|
+
function createPointerTracker(options = {}) {
|
|
87
|
+
const smoothingFactor = options.smoothingFactor ?? DEFAULT_SMOOTHING;
|
|
88
|
+
const axisLockThreshold = options.axisLockThreshold ?? DEFAULT_AXIS_LOCK;
|
|
89
|
+
let state = {
|
|
90
|
+
startX: 0,
|
|
91
|
+
startY: 0,
|
|
92
|
+
currentX: 0,
|
|
93
|
+
currentY: 0,
|
|
94
|
+
deltaX: 0,
|
|
95
|
+
deltaY: 0,
|
|
96
|
+
velocityX: 0,
|
|
97
|
+
velocityY: 0,
|
|
98
|
+
dominantAxis: null,
|
|
99
|
+
isActive: false,
|
|
100
|
+
pointerId: null,
|
|
101
|
+
timestamp: 0,
|
|
102
|
+
startTimestamp: 0
|
|
103
|
+
};
|
|
104
|
+
let lastX = 0;
|
|
105
|
+
let lastY = 0;
|
|
106
|
+
let lastTimestamp = 0;
|
|
107
|
+
function onPointerDown(event) {
|
|
108
|
+
if (state.isActive) return false;
|
|
109
|
+
state = {
|
|
110
|
+
startX: event.clientX,
|
|
111
|
+
startY: event.clientY,
|
|
112
|
+
currentX: event.clientX,
|
|
113
|
+
currentY: event.clientY,
|
|
114
|
+
deltaX: 0,
|
|
115
|
+
deltaY: 0,
|
|
116
|
+
velocityX: 0,
|
|
117
|
+
velocityY: 0,
|
|
118
|
+
dominantAxis: null,
|
|
119
|
+
isActive: true,
|
|
120
|
+
pointerId: event.pointerId,
|
|
121
|
+
timestamp: event.timeStamp,
|
|
122
|
+
startTimestamp: event.timeStamp
|
|
123
|
+
};
|
|
124
|
+
lastX = event.clientX;
|
|
125
|
+
lastY = event.clientY;
|
|
126
|
+
lastTimestamp = event.timeStamp;
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
function onPointerMove(event) {
|
|
130
|
+
if (!state.isActive || state.pointerId !== event.pointerId) return false;
|
|
131
|
+
const dt = event.timeStamp - lastTimestamp;
|
|
132
|
+
const rawVx = dt > 0 ? (event.clientX - lastX) / dt : 0;
|
|
133
|
+
const rawVy = dt > 0 ? (event.clientY - lastY) / dt : 0;
|
|
134
|
+
const vx = smoothingFactor * rawVx + (1 - smoothingFactor) * state.velocityX;
|
|
135
|
+
const vy = smoothingFactor * rawVy + (1 - smoothingFactor) * state.velocityY;
|
|
136
|
+
const deltaX = event.clientX - state.startX;
|
|
137
|
+
const deltaY = event.clientY - state.startY;
|
|
138
|
+
let dominantAxis = state.dominantAxis;
|
|
139
|
+
if (dominantAxis === null) {
|
|
140
|
+
const absX = Math.abs(deltaX);
|
|
141
|
+
const absY = Math.abs(deltaY);
|
|
142
|
+
if (Math.max(absX, absY) >= axisLockThreshold) {
|
|
143
|
+
dominantAxis = absX > absY ? "x" : "y";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
state = {
|
|
147
|
+
...state,
|
|
148
|
+
currentX: event.clientX,
|
|
149
|
+
currentY: event.clientY,
|
|
150
|
+
deltaX,
|
|
151
|
+
deltaY,
|
|
152
|
+
velocityX: vx,
|
|
153
|
+
velocityY: vy,
|
|
154
|
+
dominantAxis,
|
|
155
|
+
timestamp: event.timeStamp
|
|
156
|
+
};
|
|
157
|
+
lastX = event.clientX;
|
|
158
|
+
lastY = event.clientY;
|
|
159
|
+
lastTimestamp = event.timeStamp;
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
function onPointerUp(event) {
|
|
163
|
+
if (!state.isActive || state.pointerId !== event.pointerId) return false;
|
|
164
|
+
state = { ...state, isActive: false };
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
function getState() {
|
|
168
|
+
return state;
|
|
169
|
+
}
|
|
170
|
+
function reset() {
|
|
171
|
+
state = { ...state, isActive: false, pointerId: null, dominantAxis: null, velocityX: 0, velocityY: 0 };
|
|
172
|
+
}
|
|
173
|
+
return { onPointerDown, onPointerMove, onPointerUp, getState, reset };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/engine/physics.ts
|
|
177
|
+
function rubberBand(value, min, max, factor = 0.55) {
|
|
178
|
+
if (value >= min && value <= max) return value;
|
|
179
|
+
const distance = value < min ? min - value : value - max;
|
|
180
|
+
const sign = value < min ? -1 : 1;
|
|
181
|
+
const dampened = factor * Math.log(1 + distance);
|
|
182
|
+
return (value < min ? min : max) + sign * dampened;
|
|
183
|
+
}
|
|
184
|
+
function decayPosition(position, velocity, deceleration = 3e-3) {
|
|
185
|
+
if (Math.abs(velocity) < 0.01) return position;
|
|
186
|
+
return position + velocity * Math.abs(velocity) / (2 * deceleration);
|
|
187
|
+
}
|
|
188
|
+
function findNearestSnapPoint(currentPx, velocityPxMs, snapPoints, inertia = true) {
|
|
189
|
+
if (snapPoints.length === 0) return 0;
|
|
190
|
+
const projected = inertia ? decayPosition(currentPx, velocityPxMs) : currentPx;
|
|
191
|
+
let nearestIndex = 0;
|
|
192
|
+
let nearestDist = Infinity;
|
|
193
|
+
for (const { value, index } of snapPoints) {
|
|
194
|
+
const dist = Math.abs(projected - value);
|
|
195
|
+
if (dist < nearestDist) {
|
|
196
|
+
nearestDist = dist;
|
|
197
|
+
nearestIndex = index;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return nearestIndex;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/engine/gesture-engine.ts
|
|
204
|
+
function isHorizontal(direction) {
|
|
205
|
+
return direction === "left" || direction === "right";
|
|
206
|
+
}
|
|
207
|
+
function getDirectionSign(direction) {
|
|
208
|
+
return direction === "top" || direction === "left" ? -1 : 1;
|
|
209
|
+
}
|
|
210
|
+
function shouldDrag(target, direction) {
|
|
211
|
+
if (!(target instanceof Element)) return true;
|
|
212
|
+
const horizontal = isHorizontal(direction);
|
|
213
|
+
const scrollParent = getScrollParent(target, horizontal);
|
|
214
|
+
if (!scrollParent) return true;
|
|
215
|
+
const { scrollTop, scrollLeft, scrollHeight, scrollWidth, clientHeight, clientWidth } = scrollParent;
|
|
216
|
+
if (horizontal) {
|
|
217
|
+
return direction === "right" ? scrollLeft <= 0 : scrollLeft + clientWidth >= scrollWidth - 1;
|
|
218
|
+
}
|
|
219
|
+
return direction === "bottom" ? scrollTop <= 0 : scrollTop + clientHeight >= scrollHeight - 1;
|
|
220
|
+
}
|
|
221
|
+
function getScrollParent(el, horizontal) {
|
|
222
|
+
let current = el;
|
|
223
|
+
while (current) {
|
|
224
|
+
if (current === document.body || current === document.documentElement) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const style = window.getComputedStyle(current);
|
|
228
|
+
const overflow = horizontal ? `${style.overflowX} ${style.overflow}` : `${style.overflowY} ${style.overflow}`;
|
|
229
|
+
if (/auto|scroll/.test(overflow)) {
|
|
230
|
+
const hasScroll = horizontal ? current.scrollWidth > current.clientWidth : current.scrollHeight > current.clientHeight;
|
|
231
|
+
if (hasScroll) return current;
|
|
232
|
+
}
|
|
233
|
+
current = current.parentElement;
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
function createGestureEngine(options) {
|
|
238
|
+
let opts = { ...options };
|
|
239
|
+
const tracker = createPointerTracker();
|
|
240
|
+
let isDragging = false;
|
|
241
|
+
let dragStartTranslate = 0;
|
|
242
|
+
let translateValue = 0;
|
|
243
|
+
let capturedElement = null;
|
|
244
|
+
function update(newOpts) {
|
|
245
|
+
opts = { ...opts, ...newOpts };
|
|
246
|
+
}
|
|
247
|
+
function getDelta() {
|
|
248
|
+
const state = tracker.getState();
|
|
249
|
+
const delta = isHorizontal(opts.direction) ? state.deltaX : state.deltaY;
|
|
250
|
+
return delta * getDirectionSign(opts.direction);
|
|
251
|
+
}
|
|
252
|
+
function getVelocity() {
|
|
253
|
+
const state = tracker.getState();
|
|
254
|
+
const velocity = isHorizontal(opts.direction) ? state.velocityX : state.velocityY;
|
|
255
|
+
return velocity * getDirectionSign(opts.direction);
|
|
256
|
+
}
|
|
257
|
+
function clampWithRubberBand(raw) {
|
|
258
|
+
const min = 0;
|
|
259
|
+
const max = opts.maxTranslate;
|
|
260
|
+
if (raw >= min && raw <= max) return raw;
|
|
261
|
+
if (!opts.rubberBandEnabled) return Math.min(Math.max(raw, min), max);
|
|
262
|
+
return rubberBand(raw, min, max);
|
|
263
|
+
}
|
|
264
|
+
function onPointerDown(event) {
|
|
265
|
+
const started = tracker.onPointerDown(event);
|
|
266
|
+
if (!started) return false;
|
|
267
|
+
dragStartTranslate = translateValue;
|
|
268
|
+
capturedElement = event.target;
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
function onPointerMove(event) {
|
|
272
|
+
if (!tracker.getState().isActive) return false;
|
|
273
|
+
tracker.onPointerMove(event);
|
|
274
|
+
const state = tracker.getState();
|
|
275
|
+
if (!isDragging) {
|
|
276
|
+
if (state.dominantAxis === null) return false;
|
|
277
|
+
const horizontal = isHorizontal(opts.direction);
|
|
278
|
+
const correctAxis = horizontal ? state.dominantAxis === "x" : state.dominantAxis === "y";
|
|
279
|
+
if (!correctAxis) {
|
|
280
|
+
tracker.reset();
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
if (!shouldDrag(capturedElement, opts.direction)) {
|
|
284
|
+
tracker.reset();
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
isDragging = true;
|
|
288
|
+
opts.onDragStart?.(translateValue);
|
|
289
|
+
}
|
|
290
|
+
translateValue = clampWithRubberBand(dragStartTranslate + getDelta());
|
|
291
|
+
opts.onDrag?.(translateValue);
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
function onPointerUp(event) {
|
|
295
|
+
const wasActive = tracker.getState().isActive;
|
|
296
|
+
tracker.onPointerUp(event);
|
|
297
|
+
if (!wasActive || !isDragging) {
|
|
298
|
+
isDragging = false;
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
isDragging = false;
|
|
302
|
+
const velocityPxMs = getVelocity() * 1e3;
|
|
303
|
+
const snapVelocityPxMs = -velocityPxMs;
|
|
304
|
+
const closeThresholdPx = opts.closeThreshold * opts.maxTranslate;
|
|
305
|
+
const shouldClose = translateValue > closeThresholdPx;
|
|
306
|
+
let targetSnapIndex;
|
|
307
|
+
if (shouldClose) {
|
|
308
|
+
targetSnapIndex = -1;
|
|
309
|
+
} else {
|
|
310
|
+
targetSnapIndex = findNearestSnapPoint(
|
|
311
|
+
opts.maxTranslate - translateValue,
|
|
312
|
+
snapVelocityPxMs,
|
|
313
|
+
opts.snapPoints,
|
|
314
|
+
opts.inertia
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
opts.onDragEnd?.({ translateValue, velocityPxMs, targetSnapIndex, shouldClose });
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
function setTranslate(value) {
|
|
321
|
+
translateValue = value;
|
|
322
|
+
}
|
|
323
|
+
function getIsDragging() {
|
|
324
|
+
return isDragging;
|
|
325
|
+
}
|
|
326
|
+
function getTranslate() {
|
|
327
|
+
return translateValue;
|
|
328
|
+
}
|
|
329
|
+
return { update, onPointerDown, onPointerMove, onPointerUp, setTranslate, getIsDragging, getTranslate };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/utils/focus-trap.ts
|
|
333
|
+
var FOCUSABLE_SELECTORS = [
|
|
334
|
+
"a[href]",
|
|
335
|
+
"area[href]",
|
|
336
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
337
|
+
"select:not([disabled])",
|
|
338
|
+
"textarea:not([disabled])",
|
|
339
|
+
"button:not([disabled])",
|
|
340
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
341
|
+
"details > summary",
|
|
342
|
+
"audio[controls]",
|
|
343
|
+
"video[controls]",
|
|
344
|
+
'[contenteditable]:not([contenteditable="false"])'
|
|
345
|
+
].join(", ");
|
|
346
|
+
function getFocusableElements(container) {
|
|
347
|
+
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTORS)).filter((el) => {
|
|
348
|
+
const style = window.getComputedStyle(el);
|
|
349
|
+
return style.visibility !== "hidden" && style.display !== "none";
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
function createFocusTrap(initialContainer) {
|
|
353
|
+
let container = initialContainer;
|
|
354
|
+
let active = false;
|
|
355
|
+
let previouslyFocused = null;
|
|
356
|
+
function handleKeyDown(event) {
|
|
357
|
+
if (!active || event.key !== "Tab") return;
|
|
358
|
+
const focusable = getFocusableElements(container);
|
|
359
|
+
if (focusable.length === 0) {
|
|
360
|
+
event.preventDefault();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const firstEl = focusable[0];
|
|
364
|
+
const lastEl = focusable[focusable.length - 1];
|
|
365
|
+
if (event.shiftKey) {
|
|
366
|
+
if (document.activeElement === firstEl) {
|
|
367
|
+
event.preventDefault();
|
|
368
|
+
lastEl?.focus();
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
if (document.activeElement === lastEl) {
|
|
372
|
+
event.preventDefault();
|
|
373
|
+
firstEl?.focus();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function handleFocusOut(event) {
|
|
378
|
+
if (!active) return;
|
|
379
|
+
const relatedTarget = event.relatedTarget;
|
|
380
|
+
if (relatedTarget && !container.contains(relatedTarget)) {
|
|
381
|
+
getFocusableElements(container)[0]?.focus();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function activate() {
|
|
385
|
+
if (active) return;
|
|
386
|
+
active = true;
|
|
387
|
+
previouslyFocused = document.activeElement;
|
|
388
|
+
getFocusableElements(container)[0]?.focus();
|
|
389
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
390
|
+
container.addEventListener("focusout", handleFocusOut);
|
|
391
|
+
}
|
|
392
|
+
function deactivate() {
|
|
393
|
+
if (!active) return;
|
|
394
|
+
active = false;
|
|
395
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
396
|
+
container.removeEventListener("focusout", handleFocusOut);
|
|
397
|
+
if (previouslyFocused && typeof previouslyFocused.focus === "function") {
|
|
398
|
+
previouslyFocused.focus();
|
|
399
|
+
}
|
|
400
|
+
previouslyFocused = null;
|
|
401
|
+
}
|
|
402
|
+
function updateContainer(newContainer) {
|
|
403
|
+
if (active) {
|
|
404
|
+
container.removeEventListener("focusout", handleFocusOut);
|
|
405
|
+
container = newContainer;
|
|
406
|
+
container.addEventListener("focusout", handleFocusOut);
|
|
407
|
+
} else {
|
|
408
|
+
container = newContainer;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return { activate, deactivate, updateContainer };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// src/utils/scroll-lock.ts
|
|
415
|
+
var lockCount = 0;
|
|
416
|
+
var savedScrollY = 0;
|
|
417
|
+
var savedScrollbarWidth = 0;
|
|
418
|
+
var originalStyles = null;
|
|
419
|
+
function getScrollbarWidth() {
|
|
420
|
+
if (typeof document === "undefined") return 0;
|
|
421
|
+
const outer = document.createElement("div");
|
|
422
|
+
outer.style.cssText = "visibility:hidden;overflow:scroll;position:absolute;top:-9999px;width:50px";
|
|
423
|
+
document.body.appendChild(outer);
|
|
424
|
+
const inner = document.createElement("div");
|
|
425
|
+
outer.appendChild(inner);
|
|
426
|
+
const width = outer.offsetWidth - inner.offsetWidth;
|
|
427
|
+
document.body.removeChild(outer);
|
|
428
|
+
return width;
|
|
429
|
+
}
|
|
430
|
+
function lockScroll() {
|
|
431
|
+
if (typeof document === "undefined") return;
|
|
432
|
+
lockCount++;
|
|
433
|
+
if (lockCount > 1) return;
|
|
434
|
+
savedScrollY = window.scrollY;
|
|
435
|
+
savedScrollbarWidth = getScrollbarWidth();
|
|
436
|
+
const body = document.body;
|
|
437
|
+
originalStyles = {
|
|
438
|
+
overflow: body.style.overflow,
|
|
439
|
+
paddingRight: body.style.paddingRight,
|
|
440
|
+
position: body.style.position,
|
|
441
|
+
top: body.style.top,
|
|
442
|
+
width: body.style.width
|
|
443
|
+
};
|
|
444
|
+
const isIOS = /iPhone|iPad|iPod/.test(navigator.userAgent);
|
|
445
|
+
if (isIOS) {
|
|
446
|
+
body.style.position = "fixed";
|
|
447
|
+
body.style.top = `-${savedScrollY}px`;
|
|
448
|
+
body.style.width = "100%";
|
|
449
|
+
} else {
|
|
450
|
+
body.style.overflow = "hidden";
|
|
451
|
+
}
|
|
452
|
+
if (savedScrollbarWidth > 0) {
|
|
453
|
+
body.style.paddingRight = `${savedScrollbarWidth}px`;
|
|
454
|
+
document.documentElement.style.setProperty("--hiraki-scrollbar-width", `${savedScrollbarWidth}px`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function unlockScroll() {
|
|
458
|
+
if (typeof document === "undefined") return;
|
|
459
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
460
|
+
if (lockCount > 0 || !originalStyles) return;
|
|
461
|
+
const body = document.body;
|
|
462
|
+
body.style.overflow = originalStyles.overflow;
|
|
463
|
+
body.style.paddingRight = originalStyles.paddingRight;
|
|
464
|
+
body.style.position = originalStyles.position;
|
|
465
|
+
body.style.top = originalStyles.top;
|
|
466
|
+
body.style.width = originalStyles.width;
|
|
467
|
+
document.documentElement.style.removeProperty("--hiraki-scrollbar-width");
|
|
468
|
+
const isIOS = /iPhone|iPad|iPod/.test(navigator.userAgent);
|
|
469
|
+
if (isIOS) window.scrollTo(0, savedScrollY);
|
|
470
|
+
originalStyles = null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/utils/dom.ts
|
|
474
|
+
function composeRefs(...refs) {
|
|
475
|
+
return (node) => {
|
|
476
|
+
for (const ref of refs) {
|
|
477
|
+
if (typeof ref === "function") {
|
|
478
|
+
ref(node);
|
|
479
|
+
} else if (ref !== null && ref !== void 0) {
|
|
480
|
+
ref.current = node;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function composeEventHandlers(originalHandler, ourHandler, { checkForDefaultPrevented = true } = {}) {
|
|
486
|
+
return function handleEvent(event) {
|
|
487
|
+
originalHandler?.(event);
|
|
488
|
+
if (!checkForDefaultPrevented || !event.defaultPrevented) {
|
|
489
|
+
ourHandler?.(event);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
var idCounter = 0;
|
|
494
|
+
function generateId(prefix = "hiraki") {
|
|
495
|
+
return `${prefix}-${++idCounter}`;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/animations/presets.ts
|
|
499
|
+
var SPRING_EASING = "cubic-bezier(0.32, 0.08, 0.38, 1.1)";
|
|
500
|
+
var EASE_OUT = "cubic-bezier(0.16, 1, 0.3, 1)";
|
|
501
|
+
var EASE_IN_OUT = "cubic-bezier(0.4, 0, 0.2, 1)";
|
|
502
|
+
function getTransform(direction, value) {
|
|
503
|
+
switch (direction) {
|
|
504
|
+
case "top":
|
|
505
|
+
return `translate3d(0, ${-value}px, 0)`;
|
|
506
|
+
case "left":
|
|
507
|
+
return `translate3d(${-value}px, 0, 0)`;
|
|
508
|
+
case "right":
|
|
509
|
+
return `translate3d(${value}px, 0, 0)`;
|
|
510
|
+
default:
|
|
511
|
+
return `translate3d(0, ${value}px, 0)`;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function getEnterTransition(preset, duration = 350) {
|
|
515
|
+
switch (preset) {
|
|
516
|
+
case "spring":
|
|
517
|
+
return { duration, easing: SPRING_EASING, property: "transform" };
|
|
518
|
+
case "scale":
|
|
519
|
+
return { duration, easing: EASE_OUT, property: "transform, opacity" };
|
|
520
|
+
case "morph":
|
|
521
|
+
return { duration, easing: EASE_OUT, property: "transform, border-radius, width, height" };
|
|
522
|
+
default:
|
|
523
|
+
return { duration, easing: EASE_OUT, property: "transform" };
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function getExitTransition(preset, duration = 300) {
|
|
527
|
+
switch (preset) {
|
|
528
|
+
case "spring":
|
|
529
|
+
return { duration, easing: EASE_IN_OUT, property: "transform" };
|
|
530
|
+
case "scale":
|
|
531
|
+
return { duration, easing: EASE_IN_OUT, property: "transform, opacity" };
|
|
532
|
+
case "morph":
|
|
533
|
+
return { duration, easing: EASE_IN_OUT, property: "transform, border-radius, width, height" };
|
|
534
|
+
default:
|
|
535
|
+
return { duration, easing: EASE_IN_OUT, property: "transform" };
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function getSnapTransition(velocityPxMs, distancePx) {
|
|
539
|
+
const absVelocity = Math.abs(velocityPxMs);
|
|
540
|
+
const duration = absVelocity > 0.5 ? Math.max(120, Math.min(250, distancePx / absVelocity)) : 250;
|
|
541
|
+
return { duration, easing: EASE_OUT, property: "transform" };
|
|
542
|
+
}
|
|
543
|
+
function applyTransition(el, config) {
|
|
544
|
+
el.style.transition = `${config.property} ${config.duration}ms ${config.easing}`;
|
|
545
|
+
}
|
|
546
|
+
function removeTransition(el) {
|
|
547
|
+
el.style.transition = "none";
|
|
548
|
+
}
|
|
549
|
+
function prefersReducedMotion() {
|
|
550
|
+
if (typeof window === "undefined") return false;
|
|
551
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/animations/transitions.ts
|
|
555
|
+
function applyDragTransition(el) {
|
|
556
|
+
if (!el) return;
|
|
557
|
+
removeTransition(el);
|
|
558
|
+
}
|
|
559
|
+
function applySnapTransition(el, velocityPxMs, distancePx) {
|
|
560
|
+
if (!el) return;
|
|
561
|
+
if (prefersReducedMotion()) {
|
|
562
|
+
applyTransition(el, { duration: 0, easing: "linear", property: "transform" });
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
applyTransition(el, getSnapTransition(velocityPxMs, distancePx));
|
|
566
|
+
}
|
|
567
|
+
function applyEnterTransition(el, preset = "slide", duration) {
|
|
568
|
+
if (!el) return;
|
|
569
|
+
if (prefersReducedMotion()) {
|
|
570
|
+
applyTransition(el, { duration: 0, easing: "linear", property: "transform" });
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
applyTransition(el, getEnterTransition(preset, duration));
|
|
574
|
+
}
|
|
575
|
+
function applyExitTransition(el, preset = "slide", duration) {
|
|
576
|
+
if (!el) return;
|
|
577
|
+
if (prefersReducedMotion()) {
|
|
578
|
+
applyTransition(el, { duration: 0, easing: "linear", property: "transform" });
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
applyTransition(el, getExitTransition(preset, duration));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/utils/css-properties.ts
|
|
585
|
+
function calcOverlayOpacity(progress, maxOpacity = 0.5) {
|
|
586
|
+
return Math.min(Math.max(progress, 0), 1) * maxOpacity;
|
|
587
|
+
}
|
|
588
|
+
function calcBackgroundScale(progress, minScale = 0.96) {
|
|
589
|
+
return minScale + (1 - minScale) * (1 - Math.min(Math.max(progress, 0), 1));
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/animations/micro-interactions.ts
|
|
593
|
+
function updateOverlayProgress(overlayEl, progress) {
|
|
594
|
+
if (!overlayEl) return;
|
|
595
|
+
overlayEl.style.opacity = String(calcOverlayOpacity(progress));
|
|
596
|
+
}
|
|
597
|
+
function updateBackgroundScale(backgroundEl, progress) {
|
|
598
|
+
if (!backgroundEl) return;
|
|
599
|
+
backgroundEl.style.transform = `scale(${calcBackgroundScale(progress)})`;
|
|
600
|
+
backgroundEl.style.borderRadius = `${progress * 12}px`;
|
|
601
|
+
}
|
|
602
|
+
function updateDragProgress(contentEl, progress) {
|
|
603
|
+
if (!contentEl) return;
|
|
604
|
+
contentEl.style.setProperty("--hiraki-drag-progress", String(progress));
|
|
605
|
+
}
|
|
606
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
|
607
|
+
var EXIT_DURATION = 300;
|
|
608
|
+
var INTERACTIVE_SELECTOR = [
|
|
609
|
+
"button",
|
|
610
|
+
"a[href]",
|
|
611
|
+
"input",
|
|
612
|
+
"select",
|
|
613
|
+
"textarea",
|
|
614
|
+
"summary",
|
|
615
|
+
'[role="button"]',
|
|
616
|
+
'[contenteditable="true"]',
|
|
617
|
+
"[data-hiraki-close]",
|
|
618
|
+
"[data-hiraki-trigger]"
|
|
619
|
+
].join(", ");
|
|
620
|
+
function getViewportSize(direction) {
|
|
621
|
+
if (typeof window === "undefined") return 600;
|
|
622
|
+
return direction === "left" || direction === "right" ? window.innerWidth : window.innerHeight;
|
|
623
|
+
}
|
|
624
|
+
function measureElementExtent(element, direction) {
|
|
625
|
+
return direction === "left" || direction === "right" ? element.offsetWidth : element.offsetHeight;
|
|
626
|
+
}
|
|
627
|
+
function measureContentExtent(element, direction) {
|
|
628
|
+
return direction === "left" || direction === "right" ? element.scrollWidth : element.scrollHeight;
|
|
629
|
+
}
|
|
630
|
+
function shouldIgnoreGestureTarget(target, container) {
|
|
631
|
+
if (!(target instanceof Element)) return false;
|
|
632
|
+
if (target.closest("[data-hiraki-handle]")) return false;
|
|
633
|
+
const interactiveTarget = target.closest(INTERACTIVE_SELECTOR);
|
|
634
|
+
return interactiveTarget !== null && container.contains(interactiveTarget);
|
|
635
|
+
}
|
|
636
|
+
function Root({
|
|
637
|
+
children,
|
|
638
|
+
open: controlledOpen,
|
|
639
|
+
defaultOpen = false,
|
|
640
|
+
onOpenChange,
|
|
641
|
+
direction = "bottom",
|
|
642
|
+
variant = "default",
|
|
643
|
+
modal = true,
|
|
644
|
+
dismissible = true,
|
|
645
|
+
snapPoints: snapPointsProp = [],
|
|
646
|
+
activeSnapPoint: controlledSnap,
|
|
647
|
+
onSnapPointChange,
|
|
648
|
+
closeThreshold = 0.5,
|
|
649
|
+
rubberBand: rubberBand2 = true,
|
|
650
|
+
inertia = true,
|
|
651
|
+
shouldScaleBackground = false,
|
|
652
|
+
onDragStart,
|
|
653
|
+
onDrag,
|
|
654
|
+
onDragEnd
|
|
655
|
+
}) {
|
|
656
|
+
const [open, setOpen] = useControllableState({
|
|
657
|
+
controlled: controlledOpen,
|
|
658
|
+
defaultValue: defaultOpen,
|
|
659
|
+
onChange: onOpenChange
|
|
660
|
+
});
|
|
661
|
+
const [mounted, setMounted] = useState(false);
|
|
662
|
+
const [contentSize, setContentSize] = useState(0);
|
|
663
|
+
const contentRef = useRef(null);
|
|
664
|
+
const overlayRef = useRef(null);
|
|
665
|
+
const triggerRef = useRef(null);
|
|
666
|
+
const translateRef = useRef(0);
|
|
667
|
+
const maxTranslateRef = useRef(0);
|
|
668
|
+
const isDraggingRef = useRef(false);
|
|
669
|
+
const dragProgressRef = useRef(0);
|
|
670
|
+
const isEnteringRef = useRef(false);
|
|
671
|
+
const titleId = useMemo(() => generateId("hiraki-title"), []);
|
|
672
|
+
const descriptionId = useMemo(() => generateId("hiraki-desc"), []);
|
|
673
|
+
const [viewportSize, setViewportSize] = useState(() => getViewportSize(direction));
|
|
674
|
+
useIsomorphicLayoutEffect(() => {
|
|
675
|
+
const update = () => setViewportSize(getViewportSize(direction));
|
|
676
|
+
update();
|
|
677
|
+
window.addEventListener("resize", update);
|
|
678
|
+
return () => window.removeEventListener("resize", update);
|
|
679
|
+
}, [direction]);
|
|
680
|
+
const {
|
|
681
|
+
resolvedSnapPoints,
|
|
682
|
+
snapEntries,
|
|
683
|
+
activeSnapIndex,
|
|
684
|
+
setActiveSnapIndex,
|
|
685
|
+
translateForSnap
|
|
686
|
+
} = useSnapPoints({
|
|
687
|
+
snapPoints: snapPointsProp,
|
|
688
|
+
viewportSize,
|
|
689
|
+
contentSize,
|
|
690
|
+
activeSnapPoint: controlledSnap,
|
|
691
|
+
onSnapPointChange
|
|
692
|
+
});
|
|
693
|
+
function applyTranslate(value) {
|
|
694
|
+
translateRef.current = value;
|
|
695
|
+
if (contentRef.current) {
|
|
696
|
+
contentRef.current.style.transform = getTransform(direction, value);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function applyProgress(value) {
|
|
700
|
+
dragProgressRef.current = value;
|
|
701
|
+
updateOverlayProgress(overlayRef.current, value);
|
|
702
|
+
updateDragProgress(contentRef.current, value);
|
|
703
|
+
if (shouldScaleBackground) {
|
|
704
|
+
const bgEl = document.querySelector("[data-hiraki-background]");
|
|
705
|
+
updateBackgroundScale(bgEl, value);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const stableRefs = useRef({ onDragStart, onDrag, onDragEnd, direction, shouldScaleBackground });
|
|
709
|
+
stableRefs.current = { onDragStart, onDrag, onDragEnd, direction, shouldScaleBackground };
|
|
710
|
+
const engineRef = useRef(null);
|
|
711
|
+
const getEngine = useCallback(() => {
|
|
712
|
+
if (!engineRef.current) {
|
|
713
|
+
engineRef.current = createGestureEngine({
|
|
714
|
+
direction,
|
|
715
|
+
snapPoints: snapEntries,
|
|
716
|
+
maxTranslate: maxTranslateRef.current,
|
|
717
|
+
activeSnapIndex,
|
|
718
|
+
closeThreshold,
|
|
719
|
+
rubberBandEnabled: rubberBand2,
|
|
720
|
+
inertia,
|
|
721
|
+
onDragStart: void 0,
|
|
722
|
+
onDrag: void 0,
|
|
723
|
+
onDragEnd: void 0
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
return engineRef.current;
|
|
727
|
+
}, []);
|
|
728
|
+
const updateMeasuredSizes = useCallback(() => {
|
|
729
|
+
const element = contentRef.current;
|
|
730
|
+
if (!element) return 0;
|
|
731
|
+
const nextMaxTranslate = measureElementExtent(element, direction);
|
|
732
|
+
const nextContentSize = measureContentExtent(element, direction);
|
|
733
|
+
maxTranslateRef.current = nextMaxTranslate;
|
|
734
|
+
getEngine().update({ maxTranslate: nextMaxTranslate });
|
|
735
|
+
setContentSize((prev) => prev === nextContentSize ? prev : nextContentSize);
|
|
736
|
+
return nextMaxTranslate;
|
|
737
|
+
}, [direction, getEngine]);
|
|
738
|
+
const triggerClose = useCallback(() => {
|
|
739
|
+
const mt = maxTranslateRef.current;
|
|
740
|
+
if (contentRef.current) applyExitTransition(contentRef.current);
|
|
741
|
+
applyTranslate(mt);
|
|
742
|
+
getEngine().setTranslate(mt);
|
|
743
|
+
applyProgress(0);
|
|
744
|
+
setTimeout(() => {
|
|
745
|
+
setOpen(false);
|
|
746
|
+
setMounted(false);
|
|
747
|
+
}, EXIT_DURATION);
|
|
748
|
+
}, [getEngine, setOpen]);
|
|
749
|
+
useEffect(() => {
|
|
750
|
+
const engine = getEngine();
|
|
751
|
+
engine.update({
|
|
752
|
+
direction,
|
|
753
|
+
snapPoints: snapEntries,
|
|
754
|
+
maxTranslate: maxTranslateRef.current,
|
|
755
|
+
activeSnapIndex,
|
|
756
|
+
closeThreshold,
|
|
757
|
+
rubberBandEnabled: rubberBand2,
|
|
758
|
+
inertia,
|
|
759
|
+
onDragStart: (tv) => {
|
|
760
|
+
isDraggingRef.current = true;
|
|
761
|
+
if (contentRef.current) {
|
|
762
|
+
applyDragTransition(contentRef.current);
|
|
763
|
+
contentRef.current.dataset["hirakiDragging"] = "true";
|
|
764
|
+
}
|
|
765
|
+
stableRefs.current.onDragStart?.({
|
|
766
|
+
velocity: 0,
|
|
767
|
+
direction: stableRefs.current.direction,
|
|
768
|
+
translateValue: tv
|
|
769
|
+
});
|
|
770
|
+
},
|
|
771
|
+
onDrag: (tv) => {
|
|
772
|
+
applyTranslate(tv);
|
|
773
|
+
const mt = maxTranslateRef.current;
|
|
774
|
+
const progress = mt > 0 ? Math.max(0, 1 - tv / mt) : 0;
|
|
775
|
+
applyProgress(progress);
|
|
776
|
+
stableRefs.current.onDrag?.({
|
|
777
|
+
velocity: 0,
|
|
778
|
+
direction: stableRefs.current.direction,
|
|
779
|
+
translateValue: tv
|
|
780
|
+
});
|
|
781
|
+
},
|
|
782
|
+
onDragEnd: (result) => {
|
|
783
|
+
isDraggingRef.current = false;
|
|
784
|
+
if (contentRef.current) {
|
|
785
|
+
contentRef.current.dataset["hirakiDragging"] = "false";
|
|
786
|
+
}
|
|
787
|
+
if (result.shouldClose) {
|
|
788
|
+
triggerClose();
|
|
789
|
+
} else {
|
|
790
|
+
const snapPx = resolvedSnapPoints[result.targetSnapIndex]?.value ?? 0;
|
|
791
|
+
const mt = maxTranslateRef.current;
|
|
792
|
+
const targetTranslate = Math.max(0, mt - snapPx);
|
|
793
|
+
const dist = Math.abs(result.translateValue - targetTranslate);
|
|
794
|
+
if (contentRef.current) applySnapTransition(contentRef.current, result.velocityPxMs / 1e3, dist);
|
|
795
|
+
applyTranslate(targetTranslate);
|
|
796
|
+
getEngine().setTranslate(targetTranslate);
|
|
797
|
+
const progress = mt > 0 ? Math.max(0, 1 - targetTranslate / mt) : 0;
|
|
798
|
+
applyProgress(progress);
|
|
799
|
+
setActiveSnapIndex(result.targetSnapIndex);
|
|
800
|
+
stableRefs.current.onDragEnd?.({
|
|
801
|
+
velocity: result.velocityPxMs,
|
|
802
|
+
direction: stableRefs.current.direction,
|
|
803
|
+
translateValue: result.translateValue
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
}, [direction, snapEntries, activeSnapIndex, closeThreshold, rubberBand2, inertia]);
|
|
809
|
+
useEffect(() => {
|
|
810
|
+
if (open) setMounted(true);
|
|
811
|
+
}, [open]);
|
|
812
|
+
useIsomorphicLayoutEffect(() => {
|
|
813
|
+
if (!mounted || !contentRef.current) return;
|
|
814
|
+
const element = contentRef.current;
|
|
815
|
+
updateMeasuredSizes();
|
|
816
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
817
|
+
const observer = new ResizeObserver(() => {
|
|
818
|
+
updateMeasuredSizes();
|
|
819
|
+
});
|
|
820
|
+
observer.observe(element);
|
|
821
|
+
return () => observer.disconnect();
|
|
822
|
+
}, [mounted, updateMeasuredSizes]);
|
|
823
|
+
useIsomorphicLayoutEffect(() => {
|
|
824
|
+
if (!mounted || !open || !contentRef.current) return;
|
|
825
|
+
const mt = updateMeasuredSizes();
|
|
826
|
+
if (mt === 0) return;
|
|
827
|
+
isEnteringRef.current = true;
|
|
828
|
+
applyTranslate(mt);
|
|
829
|
+
getEngine().setTranslate(mt);
|
|
830
|
+
let firstFrame = 0;
|
|
831
|
+
let secondFrame = 0;
|
|
832
|
+
firstFrame = requestAnimationFrame(() => {
|
|
833
|
+
secondFrame = requestAnimationFrame(() => {
|
|
834
|
+
if (!contentRef.current) return;
|
|
835
|
+
const targetTranslate = translateForSnap(activeSnapIndex, mt);
|
|
836
|
+
const progress = mt > 0 ? Math.max(0, 1 - targetTranslate / mt) : 0;
|
|
837
|
+
applyEnterTransition(contentRef.current);
|
|
838
|
+
applyTranslate(targetTranslate);
|
|
839
|
+
getEngine().setTranslate(targetTranslate);
|
|
840
|
+
applyProgress(progress);
|
|
841
|
+
isEnteringRef.current = false;
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
return () => {
|
|
845
|
+
cancelAnimationFrame(firstFrame);
|
|
846
|
+
cancelAnimationFrame(secondFrame);
|
|
847
|
+
isEnteringRef.current = false;
|
|
848
|
+
};
|
|
849
|
+
}, [mounted, open, updateMeasuredSizes]);
|
|
850
|
+
useIsomorphicLayoutEffect(() => {
|
|
851
|
+
if (!mounted || !open || !contentRef.current) return;
|
|
852
|
+
if (isDraggingRef.current || isEnteringRef.current) return;
|
|
853
|
+
const mt = updateMeasuredSizes();
|
|
854
|
+
if (mt === 0) return;
|
|
855
|
+
const targetTranslate = translateForSnap(activeSnapIndex, mt);
|
|
856
|
+
const distance = Math.abs(translateRef.current - targetTranslate);
|
|
857
|
+
const progress = mt > 0 ? Math.max(0, 1 - targetTranslate / mt) : 0;
|
|
858
|
+
if (distance > 0.5) {
|
|
859
|
+
applySnapTransition(contentRef.current, 0, distance);
|
|
860
|
+
applyTranslate(targetTranslate);
|
|
861
|
+
}
|
|
862
|
+
getEngine().setTranslate(targetTranslate);
|
|
863
|
+
applyProgress(progress);
|
|
864
|
+
}, [activeSnapIndex, contentSize, mounted, open, translateForSnap, updateMeasuredSizes, viewportSize]);
|
|
865
|
+
useEffect(() => {
|
|
866
|
+
if (!open || !contentRef.current) return;
|
|
867
|
+
const trap = createFocusTrap(contentRef.current);
|
|
868
|
+
const timeout = setTimeout(() => trap.activate(), 100);
|
|
869
|
+
return () => {
|
|
870
|
+
clearTimeout(timeout);
|
|
871
|
+
trap.deactivate();
|
|
872
|
+
};
|
|
873
|
+
}, [open]);
|
|
874
|
+
useEffect(() => {
|
|
875
|
+
if (!open || !modal) return;
|
|
876
|
+
lockScroll();
|
|
877
|
+
return () => unlockScroll();
|
|
878
|
+
}, [open, modal]);
|
|
879
|
+
useEffect(() => {
|
|
880
|
+
if (!open) return;
|
|
881
|
+
const handler = (e) => {
|
|
882
|
+
if (e.key === "Escape" && dismissible) triggerClose();
|
|
883
|
+
};
|
|
884
|
+
document.addEventListener("keydown", handler);
|
|
885
|
+
return () => document.removeEventListener("keydown", handler);
|
|
886
|
+
}, [open, dismissible, triggerClose]);
|
|
887
|
+
const openDrawer = useCallback(() => {
|
|
888
|
+
setMounted(true);
|
|
889
|
+
setOpen(true);
|
|
890
|
+
}, [setOpen]);
|
|
891
|
+
const forceClose = useCallback(() => {
|
|
892
|
+
triggerClose();
|
|
893
|
+
}, [triggerClose]);
|
|
894
|
+
const closeDrawer = useCallback(() => {
|
|
895
|
+
if (dismissible) triggerClose();
|
|
896
|
+
}, [dismissible, triggerClose]);
|
|
897
|
+
const gestureHandlers = useMemo(
|
|
898
|
+
() => ({
|
|
899
|
+
onPointerDown: (e) => {
|
|
900
|
+
if (shouldIgnoreGestureTarget(e.target, e.currentTarget)) return;
|
|
901
|
+
const engine = getEngine();
|
|
902
|
+
engine.onPointerDown(e.nativeEvent);
|
|
903
|
+
},
|
|
904
|
+
onPointerMove: (e) => {
|
|
905
|
+
const engine = getEngine();
|
|
906
|
+
const handled = engine.onPointerMove(e.nativeEvent);
|
|
907
|
+
if (handled && engine.getIsDragging() && !e.currentTarget.hasPointerCapture(e.pointerId)) {
|
|
908
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
909
|
+
}
|
|
910
|
+
},
|
|
911
|
+
onPointerUp: (e) => {
|
|
912
|
+
getEngine().onPointerUp(e.nativeEvent);
|
|
913
|
+
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
|
914
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
onPointerCancel: (e) => {
|
|
918
|
+
getEngine().onPointerUp(e.nativeEvent);
|
|
919
|
+
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
|
920
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}),
|
|
924
|
+
[getEngine]
|
|
925
|
+
);
|
|
926
|
+
const contextValue = {
|
|
927
|
+
open,
|
|
928
|
+
mounted,
|
|
929
|
+
openDrawer,
|
|
930
|
+
closeDrawer,
|
|
931
|
+
forceClose,
|
|
932
|
+
direction,
|
|
933
|
+
variant,
|
|
934
|
+
modal,
|
|
935
|
+
dismissible,
|
|
936
|
+
isDraggingRef,
|
|
937
|
+
translateRef,
|
|
938
|
+
maxTranslateRef,
|
|
939
|
+
dragProgressRef,
|
|
940
|
+
resolvedSnapPoints,
|
|
941
|
+
activeSnapIndex,
|
|
942
|
+
contentRef,
|
|
943
|
+
overlayRef,
|
|
944
|
+
triggerRef,
|
|
945
|
+
titleId,
|
|
946
|
+
descriptionId,
|
|
947
|
+
gestureHandlers
|
|
948
|
+
};
|
|
949
|
+
return /* @__PURE__ */ jsx(DrawerContext.Provider, { value: contextValue, children });
|
|
950
|
+
}
|
|
951
|
+
Root.displayName = "Drawer.Root";
|
|
952
|
+
var Trigger = forwardRef(
|
|
953
|
+
function Trigger2({ children, onClick, asChild: _asChild, ...props }, ref) {
|
|
954
|
+
const { openDrawer, triggerRef } = useDrawerContext();
|
|
955
|
+
return /* @__PURE__ */ jsx(
|
|
956
|
+
"button",
|
|
957
|
+
{
|
|
958
|
+
ref: composeRefs(triggerRef, ref),
|
|
959
|
+
type: "button",
|
|
960
|
+
"data-hiraki-trigger": "",
|
|
961
|
+
onClick: composeEventHandlers2(onClick, openDrawer),
|
|
962
|
+
...props,
|
|
963
|
+
children
|
|
964
|
+
}
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
);
|
|
968
|
+
Trigger.displayName = "Drawer.Trigger";
|
|
969
|
+
function composeEventHandlers2(original, ours) {
|
|
970
|
+
return (e) => {
|
|
971
|
+
original?.(e);
|
|
972
|
+
if (!e.defaultPrevented) ours?.(e);
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function Portal({ children, container }) {
|
|
976
|
+
const { mounted } = useDrawerContext();
|
|
977
|
+
const [hydrated, setHydrated] = useState(false);
|
|
978
|
+
useEffect(() => {
|
|
979
|
+
setHydrated(true);
|
|
980
|
+
}, []);
|
|
981
|
+
if (!hydrated || !mounted) return null;
|
|
982
|
+
return createPortal(children, container ?? document.body);
|
|
983
|
+
}
|
|
984
|
+
Portal.displayName = "Drawer.Portal";
|
|
985
|
+
var Overlay = forwardRef(
|
|
986
|
+
function Overlay2({ onClick, style, ...props }, ref) {
|
|
987
|
+
const { overlayRef, closeDrawer, dismissible } = useDrawerContext();
|
|
988
|
+
return /* @__PURE__ */ jsx(
|
|
989
|
+
"div",
|
|
990
|
+
{
|
|
991
|
+
ref: composeRefs(overlayRef, ref),
|
|
992
|
+
"data-hiraki-overlay": "",
|
|
993
|
+
"aria-hidden": "true",
|
|
994
|
+
onClick: composeEventHandlers(onClick, () => {
|
|
995
|
+
if (dismissible) closeDrawer();
|
|
996
|
+
}),
|
|
997
|
+
style: {
|
|
998
|
+
position: "fixed",
|
|
999
|
+
inset: 0,
|
|
1000
|
+
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
1001
|
+
opacity: 0,
|
|
1002
|
+
zIndex: "var(--hiraki-overlay-z, 49)",
|
|
1003
|
+
...style
|
|
1004
|
+
},
|
|
1005
|
+
...props
|
|
1006
|
+
}
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
);
|
|
1010
|
+
Overlay.displayName = "Drawer.Overlay";
|
|
1011
|
+
|
|
1012
|
+
// src/variants/variant-config.ts
|
|
1013
|
+
var VARIANT_CONFIGS = {
|
|
1014
|
+
default: {
|
|
1015
|
+
showHandle: true,
|
|
1016
|
+
borderRadius: 16,
|
|
1017
|
+
fullWidth: true
|
|
1018
|
+
},
|
|
1019
|
+
floating: {
|
|
1020
|
+
showHandle: true,
|
|
1021
|
+
margin: 12,
|
|
1022
|
+
borderRadius: 16,
|
|
1023
|
+
fullWidth: false
|
|
1024
|
+
},
|
|
1025
|
+
sheet: {
|
|
1026
|
+
showHandle: true,
|
|
1027
|
+
borderRadius: 0,
|
|
1028
|
+
fullWidth: true
|
|
1029
|
+
},
|
|
1030
|
+
fullscreen: {
|
|
1031
|
+
showHandle: false,
|
|
1032
|
+
borderRadius: 0,
|
|
1033
|
+
fullWidth: true,
|
|
1034
|
+
morph: true
|
|
1035
|
+
},
|
|
1036
|
+
nested: {
|
|
1037
|
+
showHandle: true,
|
|
1038
|
+
borderRadius: 16,
|
|
1039
|
+
fullWidth: true,
|
|
1040
|
+
nested: true
|
|
1041
|
+
},
|
|
1042
|
+
stack: {
|
|
1043
|
+
showHandle: true,
|
|
1044
|
+
borderRadius: 16,
|
|
1045
|
+
fullWidth: true,
|
|
1046
|
+
stackOffset: 8
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
function getVariantConfig(variant) {
|
|
1050
|
+
return VARIANT_CONFIGS[variant];
|
|
1051
|
+
}
|
|
1052
|
+
function getContentStyle(variant, direction) {
|
|
1053
|
+
const config = getVariantConfig(variant);
|
|
1054
|
+
const style = {};
|
|
1055
|
+
if (config.margin) {
|
|
1056
|
+
switch (direction) {
|
|
1057
|
+
case "bottom":
|
|
1058
|
+
style.left = config.margin;
|
|
1059
|
+
style.right = config.margin;
|
|
1060
|
+
style.bottom = config.margin;
|
|
1061
|
+
break;
|
|
1062
|
+
case "top":
|
|
1063
|
+
style.left = config.margin;
|
|
1064
|
+
style.right = config.margin;
|
|
1065
|
+
style.top = config.margin;
|
|
1066
|
+
break;
|
|
1067
|
+
case "left":
|
|
1068
|
+
style.top = config.margin;
|
|
1069
|
+
style.bottom = config.margin;
|
|
1070
|
+
style.left = config.margin;
|
|
1071
|
+
break;
|
|
1072
|
+
case "right":
|
|
1073
|
+
style.top = config.margin;
|
|
1074
|
+
style.bottom = config.margin;
|
|
1075
|
+
style.right = config.margin;
|
|
1076
|
+
break;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
if (config.borderRadius != null && config.borderRadius > 0) {
|
|
1080
|
+
const r = `var(--hiraki-radius, ${config.borderRadius}px)`;
|
|
1081
|
+
if (config.margin) {
|
|
1082
|
+
style.borderRadius = r;
|
|
1083
|
+
} else {
|
|
1084
|
+
switch (direction) {
|
|
1085
|
+
case "bottom":
|
|
1086
|
+
style.borderTopLeftRadius = r;
|
|
1087
|
+
style.borderTopRightRadius = r;
|
|
1088
|
+
break;
|
|
1089
|
+
case "top":
|
|
1090
|
+
style.borderBottomLeftRadius = r;
|
|
1091
|
+
style.borderBottomRightRadius = r;
|
|
1092
|
+
break;
|
|
1093
|
+
case "left":
|
|
1094
|
+
style.borderTopRightRadius = r;
|
|
1095
|
+
style.borderBottomRightRadius = r;
|
|
1096
|
+
break;
|
|
1097
|
+
case "right":
|
|
1098
|
+
style.borderTopLeftRadius = r;
|
|
1099
|
+
style.borderBottomLeftRadius = r;
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return style;
|
|
1105
|
+
}
|
|
1106
|
+
function getPositionStyle(direction) {
|
|
1107
|
+
switch (direction) {
|
|
1108
|
+
case "top":
|
|
1109
|
+
return { top: 0, left: 0, right: 0 };
|
|
1110
|
+
case "left":
|
|
1111
|
+
return { left: 0, top: 0, bottom: 0 };
|
|
1112
|
+
case "right":
|
|
1113
|
+
return { right: 0, top: 0, bottom: 0 };
|
|
1114
|
+
default:
|
|
1115
|
+
return { bottom: 0, left: 0, right: 0 };
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
var Content = forwardRef(
|
|
1119
|
+
function Content2({ children, style, ...props }, ref) {
|
|
1120
|
+
const {
|
|
1121
|
+
contentRef,
|
|
1122
|
+
direction,
|
|
1123
|
+
variant,
|
|
1124
|
+
open,
|
|
1125
|
+
titleId,
|
|
1126
|
+
descriptionId,
|
|
1127
|
+
gestureHandlers
|
|
1128
|
+
} = useDrawerContext();
|
|
1129
|
+
return /* @__PURE__ */ jsx(
|
|
1130
|
+
"div",
|
|
1131
|
+
{
|
|
1132
|
+
ref: composeRefs(contentRef, ref),
|
|
1133
|
+
role: "dialog",
|
|
1134
|
+
"aria-modal": "true",
|
|
1135
|
+
"aria-labelledby": titleId,
|
|
1136
|
+
"aria-describedby": descriptionId,
|
|
1137
|
+
"data-hiraki-content": "",
|
|
1138
|
+
"data-hiraki-direction": direction,
|
|
1139
|
+
"data-hiraki-variant": variant,
|
|
1140
|
+
"data-hiraki-open": open ? "true" : "false",
|
|
1141
|
+
"data-hiraki-dragging": "false",
|
|
1142
|
+
"data-state": open ? "open" : "closed",
|
|
1143
|
+
style: {
|
|
1144
|
+
position: "fixed",
|
|
1145
|
+
zIndex: "var(--hiraki-content-z, 50)",
|
|
1146
|
+
willChange: "transform",
|
|
1147
|
+
touchAction: "none",
|
|
1148
|
+
...getPositionStyle(direction),
|
|
1149
|
+
...getContentStyle(variant, direction),
|
|
1150
|
+
...style
|
|
1151
|
+
},
|
|
1152
|
+
...gestureHandlers,
|
|
1153
|
+
...props,
|
|
1154
|
+
children
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
);
|
|
1159
|
+
Content.displayName = "Drawer.Content";
|
|
1160
|
+
var Handle = forwardRef(
|
|
1161
|
+
function Handle2({
|
|
1162
|
+
handleOnly: _handleOnly = false,
|
|
1163
|
+
visible = true,
|
|
1164
|
+
style,
|
|
1165
|
+
...props
|
|
1166
|
+
}, ref) {
|
|
1167
|
+
const { direction } = useDrawerContext();
|
|
1168
|
+
const isHorizontal2 = direction === "left" || direction === "right";
|
|
1169
|
+
if (!visible) return null;
|
|
1170
|
+
return /* @__PURE__ */ jsx(
|
|
1171
|
+
"div",
|
|
1172
|
+
{
|
|
1173
|
+
ref,
|
|
1174
|
+
"data-hiraki-handle": "",
|
|
1175
|
+
"aria-hidden": "true",
|
|
1176
|
+
style: {
|
|
1177
|
+
display: "flex",
|
|
1178
|
+
alignItems: "center",
|
|
1179
|
+
justifyContent: "center",
|
|
1180
|
+
width: isHorizontal2 ? 20 : "100%",
|
|
1181
|
+
height: isHorizontal2 ? 52 : 20,
|
|
1182
|
+
margin: isHorizontal2 ? "0 8px" : "0",
|
|
1183
|
+
flexShrink: 0,
|
|
1184
|
+
cursor: isHorizontal2 ? "ew-resize" : "ns-resize",
|
|
1185
|
+
touchAction: "none",
|
|
1186
|
+
userSelect: "none",
|
|
1187
|
+
WebkitUserSelect: "none",
|
|
1188
|
+
...style
|
|
1189
|
+
},
|
|
1190
|
+
...props,
|
|
1191
|
+
children: /* @__PURE__ */ jsx(
|
|
1192
|
+
"span",
|
|
1193
|
+
{
|
|
1194
|
+
style: {
|
|
1195
|
+
width: isHorizontal2 ? 5 : 44,
|
|
1196
|
+
height: isHorizontal2 ? 44 : 5,
|
|
1197
|
+
borderRadius: 9999,
|
|
1198
|
+
backgroundColor: "var(--hiraki-handle-bg, rgba(120, 120, 128, 0.42))",
|
|
1199
|
+
boxShadow: "var(--hiraki-handle-shadow, inset 0 1px 0 rgba(255, 255, 255, 0.22))",
|
|
1200
|
+
pointerEvents: "none"
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
)
|
|
1204
|
+
}
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
);
|
|
1208
|
+
Handle.displayName = "Drawer.Handle";
|
|
1209
|
+
var Title = forwardRef(
|
|
1210
|
+
function Title2({ children, ...props }, ref) {
|
|
1211
|
+
const { titleId } = useDrawerContext();
|
|
1212
|
+
return /* @__PURE__ */ jsx("h2", { ref, id: titleId, "data-hiraki-title": "", ...props, children });
|
|
1213
|
+
}
|
|
1214
|
+
);
|
|
1215
|
+
Title.displayName = "Drawer.Title";
|
|
1216
|
+
var Description = forwardRef(
|
|
1217
|
+
function Description2({ children, ...props }, ref) {
|
|
1218
|
+
const { descriptionId } = useDrawerContext();
|
|
1219
|
+
return /* @__PURE__ */ jsx("p", { ref, id: descriptionId, "data-hiraki-description": "", ...props, children });
|
|
1220
|
+
}
|
|
1221
|
+
);
|
|
1222
|
+
Description.displayName = "Drawer.Description";
|
|
1223
|
+
var Close = forwardRef(
|
|
1224
|
+
function Close2({ children, onClick, asChild = false, ...props }, ref) {
|
|
1225
|
+
const { forceClose } = useDrawerContext();
|
|
1226
|
+
const handleClick = (e) => {
|
|
1227
|
+
onClick?.(e);
|
|
1228
|
+
forceClose();
|
|
1229
|
+
};
|
|
1230
|
+
if (asChild && isValidElement(children)) {
|
|
1231
|
+
const child = children;
|
|
1232
|
+
return cloneElement(child, {
|
|
1233
|
+
...props,
|
|
1234
|
+
ref,
|
|
1235
|
+
"data-hiraki-close": "",
|
|
1236
|
+
onClick: (e) => {
|
|
1237
|
+
if (typeof child.props.onClick === "function") child.props.onClick(e);
|
|
1238
|
+
handleClick(e);
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
return /* @__PURE__ */ jsx(
|
|
1243
|
+
"button",
|
|
1244
|
+
{
|
|
1245
|
+
ref,
|
|
1246
|
+
type: "button",
|
|
1247
|
+
"data-hiraki-close": "",
|
|
1248
|
+
onClick: handleClick,
|
|
1249
|
+
...props,
|
|
1250
|
+
children
|
|
1251
|
+
}
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
);
|
|
1255
|
+
Close.displayName = "Drawer.Close";
|
|
1256
|
+
var SnapIndicator = forwardRef(
|
|
1257
|
+
function SnapIndicator2({ style, ...props }, ref) {
|
|
1258
|
+
const { resolvedSnapPoints, activeSnapIndex, direction } = useDrawerContext();
|
|
1259
|
+
const isHorizontal2 = direction === "left" || direction === "right";
|
|
1260
|
+
return /* @__PURE__ */ jsx(
|
|
1261
|
+
"div",
|
|
1262
|
+
{
|
|
1263
|
+
ref,
|
|
1264
|
+
"data-hiraki-snap-indicator": "",
|
|
1265
|
+
"aria-hidden": "true",
|
|
1266
|
+
style: {
|
|
1267
|
+
display: "flex",
|
|
1268
|
+
gap: 4,
|
|
1269
|
+
flexDirection: isHorizontal2 ? "column" : "row",
|
|
1270
|
+
justifyContent: "center",
|
|
1271
|
+
...style
|
|
1272
|
+
},
|
|
1273
|
+
...props,
|
|
1274
|
+
children: resolvedSnapPoints.map((_, i) => /* @__PURE__ */ jsx(
|
|
1275
|
+
"div",
|
|
1276
|
+
{
|
|
1277
|
+
"data-active": i === activeSnapIndex ? "true" : "false",
|
|
1278
|
+
style: {
|
|
1279
|
+
width: 6,
|
|
1280
|
+
height: 6,
|
|
1281
|
+
borderRadius: "50%",
|
|
1282
|
+
backgroundColor: i === activeSnapIndex ? "var(--hiraki-snap-active, currentColor)" : "var(--hiraki-snap-inactive, rgba(120, 120, 128, 0.32))",
|
|
1283
|
+
transition: "background-color 200ms ease"
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
i
|
|
1287
|
+
))
|
|
1288
|
+
}
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
);
|
|
1292
|
+
SnapIndicator.displayName = "Drawer.SnapIndicator";
|
|
1293
|
+
var ScrollArea = forwardRef(
|
|
1294
|
+
function ScrollArea2({ children, style, ...props }, ref) {
|
|
1295
|
+
return /* @__PURE__ */ jsx(
|
|
1296
|
+
"div",
|
|
1297
|
+
{
|
|
1298
|
+
ref,
|
|
1299
|
+
"data-hiraki-scroll-area": "",
|
|
1300
|
+
style: {
|
|
1301
|
+
overflowY: "auto",
|
|
1302
|
+
overscrollBehavior: "contain",
|
|
1303
|
+
WebkitOverflowScrolling: "touch",
|
|
1304
|
+
...style
|
|
1305
|
+
},
|
|
1306
|
+
...props,
|
|
1307
|
+
children
|
|
1308
|
+
}
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
);
|
|
1312
|
+
ScrollArea.displayName = "Drawer.ScrollArea";
|
|
1313
|
+
|
|
1314
|
+
// src/index.ts
|
|
1315
|
+
var Drawer = {
|
|
1316
|
+
Root,
|
|
1317
|
+
Trigger,
|
|
1318
|
+
Portal,
|
|
1319
|
+
Overlay,
|
|
1320
|
+
Content,
|
|
1321
|
+
Handle,
|
|
1322
|
+
Title,
|
|
1323
|
+
Description,
|
|
1324
|
+
Close,
|
|
1325
|
+
SnapIndicator,
|
|
1326
|
+
ScrollArea
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
export { Drawer };
|
|
1330
|
+
//# sourceMappingURL=index.js.map
|
|
1331
|
+
//# sourceMappingURL=index.js.map
|