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/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