@solid2-aria/vaul 0.0.0-stage → 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.
@@ -0,0 +1,1058 @@
1
+ /*!
2
+ * Derived from Vaul (https://github.com/emilkowalski/vaul), src/.
3
+ * Copyright (c) 2023 Emil Kowalski
4
+ * Modified for Solid Aria: native Solid 2 rendering, state, events, and lifecycle.
5
+ * MIT licensed. See LICENSE, VAUL-LICENSE, and NOTICE.txt in the package root.
6
+ */
7
+ import { Portal as Portal$1, escape, memo, scope, ssr, ssrElement, ssrElementAttribute } from "@solidjs/web";
8
+ import { FocusScope } from "@solid2-aria/core/focus/focus-scope";
9
+ import { Show, createContext, createEffect, createMemo, createSignal, createUniqueId, merge, omit, onSettled, untrack, useContext } from "solid-js";
10
+ import { ariaHideOutside } from "@solid2-aria/core/overlays/aria-hide-outside";
11
+ import { createPreventScroll } from "@solid2-aria/core/overlays/create-prevent-scroll";
12
+ // src/context.ts
13
+ const DrawerContext = createContext();
14
+ const PortalContext = createContext(false);
15
+ // src/constants.ts
16
+ const TRANSITIONS = {
17
+ DURATION: .5,
18
+ EASE: [
19
+ .32,
20
+ .72,
21
+ 0,
22
+ 1
23
+ ]
24
+ };
25
+ const DRAG_CLASS = "vaul-dragging";
26
+ // src/helpers.ts
27
+ function isVertical(direction) {
28
+ return direction === "top" || direction === "bottom";
29
+ }
30
+ function directionSign(direction) {
31
+ return direction === "bottom" || direction === "right" ? 1 : -1;
32
+ }
33
+ function translate(distance, direction) {
34
+ return isVertical(direction) ? `translate3d(0, ${distance}px, 0)` : `translate3d(${distance}px, 0, 0)`;
35
+ }
36
+ function dampenValue(value) {
37
+ return Math.max(0, 8 * (Math.log(value + 1) - 2));
38
+ }
39
+ function clamp(value, min = 0, max = 1) {
40
+ return Math.min(max, Math.max(min, value));
41
+ }
42
+ /** Restore only the properties this operation owns, including priorities. */
43
+ function assignStyle(element, styles) {
44
+ const previous = Object.keys(styles).map((property) => ({
45
+ priority: element.style.getPropertyPriority(property),
46
+ property,
47
+ value: element.style.getPropertyValue(property)
48
+ }));
49
+ for (const [property, value] of Object.entries(styles)) element.style.setProperty(property, value);
50
+ return () => {
51
+ for (const { property, value, priority } of previous) if (value) element.style.setProperty(property, value, priority);
52
+ else element.style.removeProperty(property);
53
+ };
54
+ }
55
+ function isSafari(view) {
56
+ return /^(?:(?!chrome|android).)*safari/iu.test(view.navigator.userAgent);
57
+ }
58
+ function canScroll(target, content, direction, distance) {
59
+ const vertical = isVertical(direction);
60
+ const forward = distance * directionSign(direction) < 0;
61
+ for (let node = target; node && content.contains(node); node = node.parentElement) {
62
+ const scrollSize = vertical ? node.scrollHeight : node.scrollWidth;
63
+ const size = vertical ? node.clientHeight : node.clientWidth;
64
+ if (scrollSize <= size) continue;
65
+ const position = vertical ? node.scrollTop : node.scrollLeft;
66
+ if (forward && position < scrollSize - size || !forward && position > 0) return true;
67
+ }
68
+ return false;
69
+ }
70
+ // src/create-background.ts
71
+ function createBackground(context) {
72
+ let background = null;
73
+ let initialScale = 1;
74
+ let vertical = true;
75
+ createEffect(() => ({
76
+ color: context.props.setBackgroundColorOnScale !== false && context.props.noBodyStyles !== true,
77
+ direction: context.direction,
78
+ element: context.content,
79
+ enabled: context.props.shouldScaleBackground === true,
80
+ nested: context.props.nested === true,
81
+ open: context.isOpen
82
+ }), ({ element, open, enabled, color, direction, nested }) => {
83
+ if (!element || !open || !enabled || nested) return;
84
+ const doc = element.ownerDocument;
85
+ const wrapper = doc.querySelector("[data-vaul-drawer-wrapper], [vaul-drawer-wrapper]");
86
+ const view = doc.defaultView;
87
+ if (!wrapper || !view) return;
88
+ background = wrapper;
89
+ initialScale = (view.innerWidth - 26) / view.innerWidth;
90
+ vertical = isVertical(direction);
91
+ const restoreBody = color ? assignStyle(doc.body, { background: "black" }) : void 0;
92
+ const restoreWrapper = assignStyle(wrapper, {
93
+ "border-radius": `8px`,
94
+ overflow: "hidden",
95
+ transform: `scale(${initialScale}) ${vertical ? "translate3d(0, 14px, 0)" : "translate3d(14px, 0, 0)"}`,
96
+ "transform-origin": vertical ? "top" : "left",
97
+ "transition-duration": `${TRANSITIONS.DURATION}s`,
98
+ "transition-property": "transform, border-radius",
99
+ "transition-timing-function": `cubic-bezier(${TRANSITIONS.EASE.join(",")})`
100
+ });
101
+ return () => {
102
+ background = null;
103
+ restoreWrapper();
104
+ restoreBody?.();
105
+ };
106
+ }, { ssrSource: "client" });
107
+ createEffect(() => ({
108
+ dragging: context.isDragging,
109
+ element: context.content,
110
+ open: context.isOpen,
111
+ progress: context.dragProgress
112
+ }), ({ progress, dragging, open }) => {
113
+ if (!background || !open) return;
114
+ const scale = initialScale + progress * (1 - initialScale);
115
+ const displacement = 14 * (1 - progress);
116
+ background.style.transitionDuration = dragging ? "0s" : `${TRANSITIONS.DURATION}s`;
117
+ background.style.borderRadius = `${8 * (1 - progress)}px`;
118
+ background.style.transform = `scale(${scale}) ${vertical ? `translate3d(0, ${displacement}px, 0)` : `translate3d(${displacement}px, 0, 0)`}`;
119
+ }, { ssrSource: "client" });
120
+ }
121
+ // src/create-snap-points.ts
122
+ function getSnapPointOffsets(points, dimension, direction) {
123
+ return points.map((point) => {
124
+ return (dimension - (typeof point === "number" ? point * dimension : Number(point.replace(/px$/u, "")))) * directionSign(direction);
125
+ });
126
+ }
127
+ function getOverlayOpacity(offsets, offset, fadeFromIndex, dimension) {
128
+ const faded = Math.abs(offsets[fadeFromIndex] ?? 0);
129
+ const clear = fadeFromIndex === 0 ? dimension : Math.abs(offsets[fadeFromIndex - 1] ?? faded);
130
+ if (clear === faded) return offset <= faded ? 1 : 0;
131
+ return clamp((clear - offset) / (clear - faded));
132
+ }
133
+ function selectSnapPoint(options) {
134
+ const { offsets, activeIndex, distance, velocity, dimension, dismissible, sequential } = options;
135
+ const [first] = offsets;
136
+ if (first === void 0) return activeIndex;
137
+ const position = Math.abs(offsets[activeIndex] ?? first) + distance;
138
+ if (!sequential && velocity > 2) {
139
+ if (distance <= 0) return offsets.length - 1;
140
+ return dismissible ? "close" : 0;
141
+ }
142
+ if (velocity > .4 && Math.abs(distance) < dimension * .4) {
143
+ if (distance > 0 && activeIndex === 0 && dismissible) return "close";
144
+ return clamp(activeIndex + (distance > 0 ? -1 : 1), 0, offsets.length - 1);
145
+ }
146
+ const remaining = dimension - Math.abs(first);
147
+ if (dismissible && distance > 0 && position > Math.abs(first) + remaining / 2) return "close";
148
+ let closest = 0;
149
+ let difference = Infinity;
150
+ for (const [index, offset] of offsets.entries()) {
151
+ if (sequential && Math.abs(index - activeIndex) > 1) continue;
152
+ const nextDifference = Math.abs(Math.abs(offset) - position);
153
+ if (nextDifference < difference) {
154
+ difference = nextDifference;
155
+ closest = index;
156
+ }
157
+ }
158
+ return closest;
159
+ }
160
+ // src/create-drawer.ts
161
+ const transition = `${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`;
162
+ function createDrawer(props, parent) {
163
+ const [internalOpen, setInternalOpen] = createSignal(untrack(() => props.defaultOpen ?? false));
164
+ const isOpen = createMemo(() => props.open ?? internalOpen());
165
+ const [present, setPresent] = createSignal(untrack(isOpen));
166
+ const [animate, setAnimate] = createSignal(untrack(() => props.defaultOpen !== true));
167
+ const [internalSnap, setInternalSnap] = createSignal(untrack(() => props.snapPoints?.[0] ?? null));
168
+ const activeSnap = createMemo(() => props.activeSnapPoint === void 0 ? internalSnap() : props.activeSnapPoint);
169
+ const [content, setContent] = createSignal(null, { ownedWrite: true });
170
+ const [overlay, setOverlay] = createSignal(null, { ownedWrite: true });
171
+ const [trigger, setTrigger] = createSignal(null, { ownedWrite: true });
172
+ const [dimension, setDimension] = createSignal(0);
173
+ const [dragging, setDragging] = createSignal(false);
174
+ const [dragProgress, setDragProgress] = createSignal(0);
175
+ const [childOpen, setChildOpen] = createSignal(false);
176
+ const [childProgress, setChildProgress] = createSignal(0);
177
+ const direction = () => props.direction ?? "bottom";
178
+ const points = () => props.snapPoints ?? [];
179
+ const offsets = createMemo(() => getSnapPointOffsets(points(), dimension(), direction()));
180
+ const activeIndex = createMemo(() => Math.max(0, points().indexOf(activeSnap() ?? points()[0] ?? 0)));
181
+ const offset = () => offsets()[activeIndex()] ?? 0;
182
+ const fadeFromIndex = () => props.fadeFromIndex ?? Math.max(0, points().length - 1);
183
+ const id = createUniqueId();
184
+ let gesture = null;
185
+ let lastScrollTime = -Infinity;
186
+ let openedAt = -Infinity;
187
+ const onScroll = () => {
188
+ lastScrollTime = Date.now();
189
+ };
190
+ function setOpen(open) {
191
+ if (open === isOpen()) return;
192
+ if (props.open === void 0) setInternalOpen(open);
193
+ props.onOpenChange?.(open);
194
+ }
195
+ function setActiveSnapPoint(point) {
196
+ if (point === activeSnap()) return;
197
+ if (props.activeSnapPoint === void 0) setInternalSnap(point);
198
+ props.setActiveSnapPoint?.(point);
199
+ }
200
+ function resetPosition() {
201
+ const element = content();
202
+ if (!element) return;
203
+ element.classList.remove(DRAG_CLASS);
204
+ element.style.transition = `transform ${transition}`;
205
+ element.style.transform = translate(offset(), direction());
206
+ const backdrop = overlay();
207
+ if (backdrop) {
208
+ backdrop.style.transition = `opacity ${transition}`;
209
+ backdrop.style.opacity = points().length ? `${getOverlayOpacity(offsets(), Math.abs(offset()), fadeFromIndex(), dimension())}` : "1";
210
+ }
211
+ }
212
+ function cancelDrag() {
213
+ if (!gesture) return;
214
+ if (gesture.target.hasPointerCapture?.(gesture.pointerId)) gesture.target.releasePointerCapture(gesture.pointerId);
215
+ gesture = null;
216
+ setDragging(false);
217
+ setDragProgress(0);
218
+ untrack(resetPosition);
219
+ parent?.setChildProgress(0);
220
+ }
221
+ function close() {
222
+ cancelDrag();
223
+ if (props.dismissible !== false) setOpen(false);
224
+ }
225
+ createEffect(isOpen, (open, previous) => {
226
+ if (open) {
227
+ openedAt = Date.now();
228
+ setPresent(true);
229
+ } else {
230
+ cancelDrag();
231
+ if (previous === true) props.onClose?.();
232
+ }
233
+ parent?.setChildOpen(open);
234
+ if (previous === void 0 && !open) return;
235
+ const duration = (untrack(content)?.ownerDocument.defaultView)?.matchMedia("(prefers-reduced-motion: reduce)").matches === true ? 0 : TRANSITIONS.DURATION * 1e3;
236
+ const timer = setTimeout(() => {
237
+ if (!open) {
238
+ setPresent(false);
239
+ setActiveSnapPoint(points()[0] ?? null);
240
+ }
241
+ if (previous !== void 0) props.onAnimationEnd?.(open);
242
+ }, duration);
243
+ return () => {
244
+ clearTimeout(timer);
245
+ };
246
+ }, { ssrSource: "client" });
247
+ createEffect(() => ({
248
+ container: props.container,
249
+ direction: direction(),
250
+ element: content()
251
+ }), ({ element, container, direction: currentDirection }) => {
252
+ if (!element) return;
253
+ const view = element.ownerDocument.defaultView;
254
+ if (!view) return;
255
+ const measure = () => {
256
+ const rect = container?.getBoundingClientRect();
257
+ setDimension(isVertical(currentDirection) ? rect?.height ?? view.innerHeight : rect?.width ?? view.innerWidth);
258
+ };
259
+ measure();
260
+ view.addEventListener("resize", measure);
261
+ const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(measure);
262
+ if (container) observer?.observe(container);
263
+ element.addEventListener("scroll", onScroll, true);
264
+ return () => {
265
+ view.removeEventListener("resize", measure);
266
+ observer?.disconnect();
267
+ element.removeEventListener("scroll", onScroll, true);
268
+ cancelDrag();
269
+ };
270
+ }, { ssrSource: "client" });
271
+ createEffect(() => ({
272
+ animate: animate(),
273
+ backdrop: overlay(),
274
+ child: childOpen(),
275
+ dimension: dimension(),
276
+ direction: direction(),
277
+ element: content(),
278
+ hasSnapPoints: points().length > 0,
279
+ offset: offset(),
280
+ opacity: points().length ? getOverlayOpacity(offsets(), Math.abs(offset()), fadeFromIndex(), dimension()) : 1,
281
+ open: isOpen(),
282
+ progress: childProgress()
283
+ }), ({ animate: shouldAnimate, element, backdrop, dimension: currentDimension, hasSnapPoints, open, offset: currentOffset, child, progress, direction: currentDirection, opacity }) => {
284
+ if (!element || hasSnapPoints && currentDimension === 0) return;
285
+ const sign = directionSign(currentDirection);
286
+ const view = element.ownerDocument.defaultView;
287
+ const size = isVertical(currentDirection) ? view?.innerHeight : view?.innerWidth;
288
+ const scale = child ? 1 - 16 / Math.max(1, size ?? 1) * (1 - progress) : 1;
289
+ const displacement = child ? -16 * (1 - progress) * sign : 0;
290
+ if (hasSnapPoints && shouldAnimate && element.style.transform === "") element.getBoundingClientRect();
291
+ element.style.transition = shouldAnimate ? `transform ${transition}` : "none";
292
+ element.style.transform = `${translate(open ? currentOffset + displacement : sign * (size ?? currentDimension), currentDirection)} scale(${scale})`;
293
+ if (backdrop) {
294
+ backdrop.style.transition = shouldAnimate ? `opacity ${transition}` : "none";
295
+ backdrop.style.opacity = open ? `${opacity}` : "0";
296
+ }
297
+ }, { ssrSource: "client" });
298
+ onSettled(() => {
299
+ const frame = requestAnimationFrame(() => {
300
+ setAnimate(true);
301
+ });
302
+ return () => {
303
+ cancelAnimationFrame(frame);
304
+ parent?.setChildOpen(false);
305
+ };
306
+ });
307
+ function press(event, fromHandle = false) {
308
+ const element = content();
309
+ const { target } = event;
310
+ if (!element || !isOpen() || childOpen() || event.defaultPrevented || event.button > 0) return;
311
+ if (!(target instanceof HTMLElement) || target.closest("[data-vaul-drawer]") !== element) return;
312
+ if (target.closest("[data-vaul-no-drag],select,option") || props.handleOnly === true && !fromHandle) return;
313
+ if (props.dismissible === false && !points().length) return;
314
+ const rect = element.getBoundingClientRect();
315
+ gesture = {
316
+ allowed: false,
317
+ dimension: isVertical(direction()) ? rect.height : rect.width,
318
+ distance: 0,
319
+ fromHandle,
320
+ pointerId: event.pointerId,
321
+ startOffset: Math.abs(offset()),
322
+ startedAt: Date.now(),
323
+ target,
324
+ x: event.clientX,
325
+ y: event.clientY
326
+ };
327
+ }
328
+ function canDrag(target, distance) {
329
+ if (!gesture || target.closest("[data-vaul-no-drag],select,option")) return false;
330
+ if (gesture.fromHandle) return true;
331
+ if ((target.ownerDocument.getSelection()?.toString().length ?? 0) > 0) return false;
332
+ if (Date.now() - openedAt < TRANSITIONS.DURATION * 1e3) return false;
333
+ if (Date.now() - lastScrollTime < (props.scrollLockTimeout ?? 100)) return false;
334
+ const lastOffset = Math.abs(offsets().at(-1) ?? 0);
335
+ if (distance < 0 && gesture.startOffset > lastOffset) return true;
336
+ const element = content();
337
+ return element !== null && !canScroll(target, element, direction(), distance);
338
+ }
339
+ function claimGesture(event, current, axis, cross, distance) {
340
+ if (current.allowed) return true;
341
+ const threshold = event.pointerType === "touch" ? 10 : 2;
342
+ if (Math.abs(axis) < threshold && Math.abs(cross) < threshold) return false;
343
+ if (Math.abs(cross) > Math.abs(axis)) {
344
+ gesture = null;
345
+ return false;
346
+ }
347
+ if (!canDrag(current.target, distance)) return false;
348
+ current.allowed = true;
349
+ current.target.setPointerCapture?.(event.pointerId);
350
+ setDragging(true);
351
+ return true;
352
+ }
353
+ function drag(event) {
354
+ const current = gesture;
355
+ const element = content();
356
+ if (!current || !element || current.pointerId !== event.pointerId) return;
357
+ const dx = event.clientX - current.x;
358
+ const dy = event.clientY - current.y;
359
+ const vertical = isVertical(direction());
360
+ const axis = vertical ? dy : dx;
361
+ const cross = vertical ? dx : dy;
362
+ const distance = axis * directionSign(direction());
363
+ if (!claimGesture(event, current, axis, cross, distance)) return;
364
+ event.preventDefault();
365
+ const minimum = Math.abs(offsets().at(-1) ?? 0);
366
+ let position = current.startOffset + distance;
367
+ if (position < minimum) position = minimum - dampenValue(minimum - position);
368
+ if (props.dismissible === false) position = Math.min(position, Math.abs(offsets()[0] ?? 0));
369
+ current.distance = position - current.startOffset;
370
+ element.classList.add(DRAG_CLASS);
371
+ element.style.transition = "none";
372
+ element.style.transform = translate(position * directionSign(direction()), direction());
373
+ const percentage = clamp(Math.max(0, distance) / Math.max(1, current.dimension - current.startOffset));
374
+ const backdrop = overlay();
375
+ if (backdrop) {
376
+ backdrop.style.transition = "none";
377
+ backdrop.style.opacity = points().length ? `${getOverlayOpacity(offsets(), position, fadeFromIndex(), dimension())}` : `${1 - percentage}`;
378
+ }
379
+ setDragProgress(percentage);
380
+ props.onDrag?.(event, percentage);
381
+ parent?.setChildProgress(percentage);
382
+ }
383
+ function release(event) {
384
+ const current = gesture;
385
+ if (!current || event.pointerId !== current.pointerId) return;
386
+ const velocity = Math.abs(current.distance) / Math.max(1, Date.now() - current.startedAt);
387
+ let next = activeIndex();
388
+ if (current.allowed && points().length) next = selectSnapPoint({
389
+ activeIndex: activeIndex(),
390
+ dimension: dimension(),
391
+ dismissible: props.dismissible !== false,
392
+ distance: current.distance,
393
+ offsets: offsets(),
394
+ sequential: props.snapToSequentialPoint ?? false,
395
+ velocity
396
+ });
397
+ else if (current.allowed && current.distance > 0 && props.dismissible !== false && (velocity > .4 || current.distance >= current.dimension * (props.closeThreshold ?? .25))) next = "close";
398
+ cancelDrag();
399
+ if (!current.allowed) return;
400
+ if (next === "close") close();
401
+ else if (points().length) setActiveSnapPoint(points()[next] ?? null);
402
+ props.onRelease?.(event, next !== "close");
403
+ }
404
+ return {
405
+ get activeSnapPoint() {
406
+ return activeSnap();
407
+ },
408
+ get activeSnapPointIndex() {
409
+ return activeIndex();
410
+ },
411
+ cancelDrag,
412
+ get childOpen() {
413
+ return childOpen();
414
+ },
415
+ close,
416
+ get content() {
417
+ return content();
418
+ },
419
+ contentId: `vaul-${id}`,
420
+ descriptionId: `vaul-${id}-description`,
421
+ get direction() {
422
+ return direction();
423
+ },
424
+ get dismissible() {
425
+ return props.dismissible ?? true;
426
+ },
427
+ drag,
428
+ get dragProgress() {
429
+ return dragProgress();
430
+ },
431
+ get isDragging() {
432
+ return dragging();
433
+ },
434
+ get isOpen() {
435
+ return isOpen();
436
+ },
437
+ get modal() {
438
+ return props.modal ?? true;
439
+ },
440
+ get overlay() {
441
+ return overlay();
442
+ },
443
+ get present() {
444
+ return present();
445
+ },
446
+ press,
447
+ props,
448
+ release,
449
+ setActiveSnapPoint,
450
+ setChildOpen(open) {
451
+ setChildOpen(open);
452
+ setChildProgress(0);
453
+ },
454
+ setChildProgress(progress) {
455
+ setChildProgress(progress);
456
+ },
457
+ setContent(element) {
458
+ setContent(element);
459
+ },
460
+ setOpen,
461
+ setOverlay(element) {
462
+ setOverlay(element);
463
+ },
464
+ setTrigger(element) {
465
+ setTrigger(element);
466
+ },
467
+ get shouldAnimate() {
468
+ return animate();
469
+ },
470
+ get shouldFade() {
471
+ return !points().length || activeIndex() >= fadeFromIndex();
472
+ },
473
+ get snapPointsOffset() {
474
+ return offsets();
475
+ },
476
+ titleId: `vaul-${id}-title`,
477
+ get trigger() {
478
+ return trigger();
479
+ }
480
+ };
481
+ }
482
+ // src/create-layer.ts
483
+ const layers = /* @__PURE__ */ new WeakMap();
484
+ function createLayer(context, props) {
485
+ createPreventScroll({ get isDisabled() {
486
+ return !context.isOpen || !context.content || !context.modal || context.props.noBodyStyles === true || !context.overlay && context.props.disablePreventScroll !== true;
487
+ } });
488
+ createEffect(() => ({
489
+ element: context.content,
490
+ modal: context.modal,
491
+ open: context.isOpen,
492
+ overlay: context.overlay
493
+ }), ({ element, overlay, open, modal }) => {
494
+ if (!element || !open || !modal) return;
495
+ return ariaHideOutside(overlay ? [element, overlay] : [element], { shouldUseInert: true });
496
+ }, { ssrSource: "client" });
497
+ createEffect(() => ({
498
+ element: context.content,
499
+ open: context.isOpen
500
+ }), ({ element, open }) => {
501
+ if (!element || !open) return;
502
+ const doc = element.ownerDocument;
503
+ const stack = layers.get(doc) ?? [];
504
+ layers.set(doc, stack);
505
+ stack.push(element);
506
+ const previous = doc.activeElement;
507
+ let interactedOutside = false;
508
+ const autoFocus = new Event("openAutoFocus", { cancelable: true });
509
+ props.onOpenAutoFocus?.(autoFocus);
510
+ if (!autoFocus.defaultPrevented && context.props.autoFocus === true) (element.querySelector("[autofocus],button:not([disabled]),[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex='0']") ?? element).focus({ preventScroll: true });
511
+ const isTop = () => stack.at(-1) === element;
512
+ const isOutside = (event) => !event.composedPath().includes(element);
513
+ function pointerDown(event) {
514
+ if (!isTop() || !isOutside(event)) return;
515
+ const outside = new CustomEvent("pointerDownOutside", {
516
+ cancelable: true,
517
+ detail: { originalEvent: event }
518
+ });
519
+ props.onPointerDownOutside?.(outside);
520
+ props.onInteractOutside?.(outside);
521
+ if (outside.defaultPrevented || !context.modal || event.button > 0 || event.ctrlKey) return;
522
+ interactedOutside = !context.modal;
523
+ context.close();
524
+ }
525
+ function focusIn(event) {
526
+ if (!isTop() || !isOutside(event)) return;
527
+ const outside = new CustomEvent("focusOutside", {
528
+ cancelable: true,
529
+ detail: { originalEvent: event }
530
+ });
531
+ props.onFocusOutside?.(outside);
532
+ props.onInteractOutside?.(outside);
533
+ if (!context.modal) interactedOutside = true;
534
+ }
535
+ function keyDown(event) {
536
+ if (event.key !== "Escape" || !isTop() || event.defaultPrevented) return;
537
+ props.onEscapeKeyDown?.(event);
538
+ if (event.defaultPrevented) return;
539
+ event.preventDefault();
540
+ context.close();
541
+ }
542
+ doc.addEventListener("pointerdown", pointerDown);
543
+ doc.addEventListener("focusin", focusIn);
544
+ doc.addEventListener("keydown", keyDown);
545
+ const touchMove = (event) => {
546
+ if (element.classList.contains("vaul-dragging") && event.cancelable) event.preventDefault();
547
+ };
548
+ element.addEventListener("touchmove", touchMove, { passive: false });
549
+ return () => {
550
+ const index = stack.indexOf(element);
551
+ const wasTop = isTop();
552
+ if (index !== -1) stack.splice(index, 1);
553
+ if (!stack.length) layers.delete(doc);
554
+ doc.removeEventListener("pointerdown", pointerDown);
555
+ doc.removeEventListener("focusin", focusIn);
556
+ doc.removeEventListener("keydown", keyDown);
557
+ element.removeEventListener("touchmove", touchMove);
558
+ const closeAutoFocus = new Event("closeAutoFocus", { cancelable: true });
559
+ props.onCloseAutoFocus?.(closeAutoFocus);
560
+ if (closeAutoFocus.defaultPrevented || interactedOutside || !wasTop) return;
561
+ const target = untrack(() => context.trigger) ?? previous;
562
+ queueMicrotask(() => {
563
+ if (target instanceof HTMLElement && target.isConnected) target.focus({ preventScroll: true });
564
+ });
565
+ };
566
+ }, { ssrSource: "client" });
567
+ }
568
+ // src/create-position.ts
569
+ function createPosition(context) {
570
+ createEffect(() => ({
571
+ element: context.content,
572
+ modal: context.modal,
573
+ nested: context.props.nested === true,
574
+ noBodyStyles: context.props.noBodyStyles === true,
575
+ open: context.isOpen,
576
+ preventScrollRestoration: context.props.preventScrollRestoration === true
577
+ }), ({ element, open, modal, noBodyStyles, nested, preventScrollRestoration }) => {
578
+ if (!element || !open || !modal || noBodyStyles || nested) return;
579
+ const doc = element.ownerDocument;
580
+ const view = doc.defaultView;
581
+ if (!view || !isSafari(view) || view.matchMedia("(display-mode: standalone)").matches) return;
582
+ const { scrollX, scrollY } = view;
583
+ const url = view.location.href;
584
+ const restore = assignStyle(doc.body, {
585
+ height: "auto",
586
+ left: `${-scrollX}px`,
587
+ position: "fixed",
588
+ right: "0",
589
+ top: `${-scrollY}px`
590
+ });
591
+ return () => {
592
+ restore();
593
+ if (!preventScrollRestoration || url === view.location.href) view.scrollTo(scrollX, scrollY);
594
+ };
595
+ }, { ssrSource: "client" });
596
+ createEffect(() => ({
597
+ direction: context.direction,
598
+ element: context.content,
599
+ fixed: context.props.fixed === true,
600
+ open: context.isOpen,
601
+ reposition: context.props.repositionInputs !== false
602
+ }), ({ element, open, reposition, fixed, direction }) => {
603
+ if (!element || !open || !reposition || direction !== "bottom") return;
604
+ const doc = element.ownerDocument;
605
+ const view = doc.defaultView;
606
+ const viewport = view?.visualViewport;
607
+ if (!view || !viewport) return;
608
+ const { height } = element.style;
609
+ const { bottom } = element.style;
610
+ let keyboardOpen = false;
611
+ const initialHeight = element.getBoundingClientRect().height;
612
+ const resize = () => {
613
+ const focused = doc.activeElement;
614
+ if (!keyboardOpen && (!(focused instanceof HTMLElement) || !element.contains(focused) || !focused.matches("input,textarea,[contenteditable]"))) return;
615
+ const diff = Math.max(0, view.innerHeight - viewport.height - viewport.offsetTop);
616
+ keyboardOpen = diff > 60;
617
+ if (!keyboardOpen) {
618
+ element.style.height = height;
619
+ element.style.bottom = bottom;
620
+ return;
621
+ }
622
+ const rect = element.getBoundingClientRect();
623
+ const available = Math.max(0, viewport.height - Math.max(26, rect.top));
624
+ element.style.height = `${fixed ? Math.max(0, initialHeight - diff) : available}px`;
625
+ element.style.bottom = `${diff}px`;
626
+ if (focused instanceof HTMLElement) focused.scrollIntoView({ block: "nearest" });
627
+ };
628
+ viewport.addEventListener("resize", resize);
629
+ viewport.addEventListener("scroll", resize);
630
+ return () => {
631
+ viewport.removeEventListener("resize", resize);
632
+ viewport.removeEventListener("scroll", resize);
633
+ element.style.height = height;
634
+ element.style.bottom = bottom;
635
+ };
636
+ }, { ssrSource: "client" });
637
+ }
638
+ // src/drawer.tsx
639
+ var _tmpl$ = ["<span data-vaul-handle-hitarea aria-hidden=\"true\">", "</span>"];
640
+ var _sk$ = (k) => k === "data-vaul-overlay" || k === "data-state" || k === "data-vaul-snap-points" || k === "data-vaul-snap-points-overlay" || k === "data-vaul-animate";
641
+ var _sk$2 = (k) => k === "data-state" || k === "data-vaul-drawer" || k === "data-vaul-drawer-direction" || k === "data-vaul-custom-container" || k === "data-vaul-snap-points" || k === "data-vaul-delayed-snap-points" || k === "data-vaul-handle-only" || k === "data-vaul-animate";
642
+ var _sk$3 = (k) => k === "data-vaul-handle" || k === "aria-hidden" || k === "data-vaul-drawer-visible";
643
+ var _m$ = Symbol();
644
+ var _m$2 = Symbol();
645
+ var _m$3 = Symbol();
646
+ var _d$ = {
647
+ get() {
648
+ return this[_m$].children;
649
+ },
650
+ enumerable: true,
651
+ configurable: true
652
+ };
653
+ function _P$(_p, _p2) {
654
+ this[_m$] = _p;
655
+ this.value = _p2;
656
+ Object.defineProperty(this, "children", _d$);
657
+ }
658
+ _P$.prototype = Object.prototype;
659
+ var _d$2 = {
660
+ get() {
661
+ return this[_m$].children;
662
+ },
663
+ enumerable: true,
664
+ configurable: true
665
+ };
666
+ function _P$2(_p3, _p4) {
667
+ this[_m$] = _p3;
668
+ this.value = _p4;
669
+ Object.defineProperty(this, "children", _d$2);
670
+ }
671
+ _P$2.prototype = Object.prototype;
672
+ var _d$3 = {
673
+ get() {
674
+ return this[_m$].forceMount ?? false;
675
+ },
676
+ enumerable: true,
677
+ configurable: true
678
+ };
679
+ var _d$4 = {
680
+ get() {
681
+ return this[_m$].children;
682
+ },
683
+ enumerable: true,
684
+ configurable: true
685
+ };
686
+ function _P$3(_p5) {
687
+ this[_m$] = _p5;
688
+ Object.defineProperty(this, "value", _d$3);
689
+ Object.defineProperty(this, "children", _d$4);
690
+ }
691
+ _P$3.prototype = Object.prototype;
692
+ var _d$5 = {
693
+ get() {
694
+ const props = this[_m$];
695
+ const context = this[_m$2];
696
+ return props.container ?? context.props.container ?? document.body;
697
+ },
698
+ enumerable: true,
699
+ configurable: true
700
+ };
701
+ var _d$6 = {
702
+ get() {
703
+ const props = this[_m$];
704
+ return PortalContext(new _P$3(props));
705
+ },
706
+ enumerable: true,
707
+ configurable: true
708
+ };
709
+ function _P$4(_p6, _p7) {
710
+ this[_m$] = _p6;
711
+ this[_m$2] = _p7;
712
+ Object.defineProperty(this, "mount", _d$5);
713
+ Object.defineProperty(this, "children", _d$6);
714
+ }
715
+ _P$4.prototype = Object.prototype;
716
+ var _d$7 = {
717
+ get() {
718
+ const mounted = this[_m$];
719
+ const context = this[_m$2];
720
+ const props = this[_m$3];
721
+ return memo(() => {
722
+ return !!mounted();
723
+ })() ? context.present || props.forceMount === true : mounted();
724
+ },
725
+ enumerable: true,
726
+ configurable: true
727
+ };
728
+ var _d$8 = {
729
+ get() {
730
+ const props = this[_m$3];
731
+ const context = this[_m$2];
732
+ return Portal$1(new _P$4(props, context));
733
+ },
734
+ enumerable: true,
735
+ configurable: true
736
+ };
737
+ function _P$5(_p8, _p9, _p0) {
738
+ this[_m$] = _p8;
739
+ this[_m$2] = _p9;
740
+ this[_m$3] = _p0;
741
+ Object.defineProperty(this, "when", _d$7);
742
+ Object.defineProperty(this, "children", _d$8);
743
+ }
744
+ _P$5.prototype = Object.prototype;
745
+ var _d$9 = {
746
+ get() {
747
+ return this[_m$].render;
748
+ },
749
+ enumerable: true,
750
+ configurable: true
751
+ };
752
+ var _d$10 = {
753
+ get() {
754
+ const attributes = this[_m$2];
755
+ return ssrElement("button", attributes, void 0, true);
756
+ },
757
+ enumerable: true,
758
+ configurable: true
759
+ };
760
+ function _P$6(_p1, _p10, _p11, _p12) {
761
+ this[_m$] = _p1;
762
+ this[_m$2] = _p10;
763
+ Object.defineProperty(this, "when", _d$9);
764
+ this.keyed = _p11;
765
+ Object.defineProperty(this, "fallback", _d$10);
766
+ this.children = _p12;
767
+ }
768
+ _P$6.prototype = Object.prototype;
769
+ var _d$11 = {
770
+ get() {
771
+ return this[_m$].render;
772
+ },
773
+ enumerable: true,
774
+ configurable: true
775
+ };
776
+ var _d$12 = {
777
+ get() {
778
+ const attributes = this[_m$2];
779
+ return ssrElement("button", attributes, void 0, true);
780
+ },
781
+ enumerable: true,
782
+ configurable: true
783
+ };
784
+ function _P$7(_p13, _p14, _p15, _p16) {
785
+ this[_m$] = _p13;
786
+ this[_m$2] = _p14;
787
+ Object.defineProperty(this, "when", _d$11);
788
+ this.keyed = _p15;
789
+ Object.defineProperty(this, "fallback", _d$12);
790
+ this.children = _p16;
791
+ }
792
+ _P$7.prototype = Object.prototype;
793
+ var _d$13 = {
794
+ get() {
795
+ const context = this[_m$];
796
+ const props = this[_m$2];
797
+ const forceMount = this[_m$3];
798
+ return memo(() => {
799
+ return !!context.modal;
800
+ })() ? context.present || props.forceMount === true || forceMount : context.modal;
801
+ },
802
+ enumerable: true,
803
+ configurable: true
804
+ };
805
+ var _d$14 = {
806
+ get() {
807
+ const props = this[_m$2];
808
+ return OverlayElement(props);
809
+ },
810
+ enumerable: true,
811
+ configurable: true
812
+ };
813
+ function _P$8(_p17, _p18, _p19) {
814
+ this[_m$] = _p17;
815
+ this[_m$2] = _p18;
816
+ this[_m$3] = _p19;
817
+ Object.defineProperty(this, "when", _d$13);
818
+ Object.defineProperty(this, "children", _d$14);
819
+ }
820
+ _P$8.prototype = Object.prototype;
821
+ var _d$15 = {
822
+ get() {
823
+ const context = this[_m$];
824
+ const props = this[_m$2];
825
+ const forceMount = this[_m$3];
826
+ return context.present || props.forceMount === true || forceMount;
827
+ },
828
+ enumerable: true,
829
+ configurable: true
830
+ };
831
+ var _d$16 = {
832
+ get() {
833
+ const props = this[_m$2];
834
+ return ContentElement(props);
835
+ },
836
+ enumerable: true,
837
+ configurable: true
838
+ };
839
+ function _P$9(_p20, _p21, _p22) {
840
+ this[_m$] = _p20;
841
+ this[_m$2] = _p21;
842
+ this[_m$3] = _p22;
843
+ Object.defineProperty(this, "when", _d$15);
844
+ Object.defineProperty(this, "children", _d$16);
845
+ }
846
+ _P$9.prototype = Object.prototype;
847
+ var _d$17 = {
848
+ get() {
849
+ return this[_m$].contentId;
850
+ },
851
+ enumerable: true,
852
+ configurable: true
853
+ };
854
+ var _d$18 = {
855
+ get() {
856
+ return this[_m$].modal ? "true" : void 0;
857
+ },
858
+ enumerable: true,
859
+ configurable: true
860
+ };
861
+ var _d$19 = {
862
+ get() {
863
+ return this[_m$].titleId;
864
+ },
865
+ enumerable: true,
866
+ configurable: true
867
+ };
868
+ var _d$20 = {
869
+ get() {
870
+ return this[_m$].descriptionId;
871
+ },
872
+ enumerable: true,
873
+ configurable: true
874
+ };
875
+ function _P$10(_p23, _p24, _p25) {
876
+ this[_m$] = _p23;
877
+ Object.defineProperty(this, "id", _d$17);
878
+ this.role = _p24;
879
+ Object.defineProperty(this, "aria-modal", _d$18);
880
+ Object.defineProperty(this, "aria-labelledby", _d$19);
881
+ Object.defineProperty(this, "aria-describedby", _d$20);
882
+ this.tabindex = _p25;
883
+ }
884
+ _P$10.prototype = Object.prototype;
885
+ var _d$21 = {
886
+ get() {
887
+ const context = this[_m$];
888
+ return memo(() => {
889
+ return !!(context.isOpen && context.modal);
890
+ })() ? !context.childOpen : context.isOpen && context.modal;
891
+ },
892
+ enumerable: true,
893
+ configurable: true
894
+ };
895
+ var _d$22 = {
896
+ get() {
897
+ const context = this[_m$];
898
+ const rest = this[_m$2];
899
+ return ssrElement("div", [new _P$10(context, "dialog", -1), rest], void 0, true, _sk$2, () => ssrElementAttribute("data-state", context.isOpen ? "open" : "closed") + " data-vaul-drawer" + ssrElementAttribute("data-vaul-drawer-direction", context.direction) + ssrElementAttribute("data-vaul-custom-container", context.props.container ? "true" : "false") + ssrElementAttribute("data-vaul-snap-points", (context.props.snapPoints?.length ?? 0) > 0 ? "true" : "false") + ssrElementAttribute("data-vaul-delayed-snap-points", context.snapPointsOffset.length > 0 ? "true" : "false") + ssrElementAttribute("data-vaul-handle-only", context.props.handleOnly === true ? "true" : "false") + ssrElementAttribute("data-vaul-animate", context.shouldAnimate ? "true" : "false"));
900
+ },
901
+ enumerable: true,
902
+ configurable: true
903
+ };
904
+ function _P$11(_p26, _p27) {
905
+ this[_m$] = _p26;
906
+ this[_m$2] = _p27;
907
+ Object.defineProperty(this, "contain", _d$21);
908
+ Object.defineProperty(this, "children", _d$22);
909
+ }
910
+ _P$11.prototype = Object.prototype;
911
+ var _d$23 = {
912
+ get() {
913
+ return this[_m$].titleId;
914
+ },
915
+ enumerable: true,
916
+ configurable: true
917
+ };
918
+ function _P$12(_p28) {
919
+ this[_m$] = _p28;
920
+ Object.defineProperty(this, "id", _d$23);
921
+ }
922
+ _P$12.prototype = Object.prototype;
923
+ var _d$24 = {
924
+ get() {
925
+ return this[_m$].descriptionId;
926
+ },
927
+ enumerable: true,
928
+ configurable: true
929
+ };
930
+ function _P$13(_p29) {
931
+ this[_m$] = _p29;
932
+ Object.defineProperty(this, "id", _d$24);
933
+ }
934
+ _P$13.prototype = Object.prototype;
935
+ function Root(props) {
936
+ const context = createDrawer(props);
937
+ createBackground(context);
938
+ createPosition(context);
939
+ return DrawerContext(new _P$(props, context));
940
+ }
941
+ function NestedRoot(props) {
942
+ const parent = useContext(DrawerContext);
943
+ const context = createDrawer(merge(props, { nested: true }), parent);
944
+ createPosition(context);
945
+ return DrawerContext(new _P$2(props, context));
946
+ }
947
+ function Portal(props) {
948
+ const context = useContext(DrawerContext);
949
+ const [mounted, setMounted] = createSignal(false);
950
+ onSettled(() => {
951
+ setMounted(true);
952
+ });
953
+ return Show(new _P$5(mounted, context, props));
954
+ }
955
+ function Trigger(props) {
956
+ const context = useContext(DrawerContext);
957
+ const attributes = merge(omit(props, "render", "ref", "onClick"), {
958
+ "aria-controls": context.contentId,
959
+ get "aria-expanded"() {
960
+ return context.isOpen ? "true" : "false";
961
+ },
962
+ "aria-haspopup": "dialog",
963
+ get "data-state"() {
964
+ return context.isOpen ? "open" : "closed";
965
+ },
966
+ onClick(event) {
967
+ callHandler(props.onClick, event);
968
+ if (!event.defaultPrevented) context.setOpen(!context.isOpen);
969
+ },
970
+ get ref() {
971
+ return [props.ref, (element) => {
972
+ context.setTrigger(element);
973
+ }];
974
+ },
975
+ get type() {
976
+ return props.type ?? "button";
977
+ }
978
+ });
979
+ return Show(new _P$6(props, attributes, true, (render) => render(attributes)));
980
+ }
981
+ function Close(props) {
982
+ const context = useContext(DrawerContext);
983
+ const attributes = merge(omit(props, "render", "onClick"), {
984
+ onClick(event) {
985
+ callHandler(props.onClick, event);
986
+ if (!event.defaultPrevented) context.close();
987
+ },
988
+ get type() {
989
+ return props.type ?? "button";
990
+ }
991
+ });
992
+ return Show(new _P$7(props, attributes, true, (render) => render(attributes)));
993
+ }
994
+ function Overlay(props) {
995
+ const context = useContext(DrawerContext);
996
+ const forceMount = useContext(PortalContext);
997
+ return Show(new _P$8(context, props, forceMount));
998
+ }
999
+ function OverlayElement(props) {
1000
+ const context = useContext(DrawerContext);
1001
+ const rest = omit(props, "forceMount", "ref");
1002
+ onSettled(() => () => {
1003
+ context.setOverlay(null);
1004
+ });
1005
+ return ssrElement("div", rest, void 0, true, _sk$, () => " data-vaul-overlay" + ssrElementAttribute("data-state", context.isOpen ? "open" : "closed") + ssrElementAttribute("data-vaul-snap-points", (context.props.snapPoints?.length ?? 0) > 0 ? "true" : "false") + ssrElementAttribute("data-vaul-snap-points-overlay", context.shouldFade ? "true" : "false") + ssrElementAttribute("data-vaul-animate", context.shouldAnimate ? "true" : "false"));
1006
+ }
1007
+ function Content(props) {
1008
+ const context = useContext(DrawerContext);
1009
+ const forceMount = useContext(PortalContext);
1010
+ return Show(new _P$9(context, props, forceMount));
1011
+ }
1012
+ function ContentElement(props) {
1013
+ const context = useContext(DrawerContext);
1014
+ const rest = omit(props, "forceMount", "ref", "onOpenAutoFocus", "onCloseAutoFocus", "onEscapeKeyDown", "onPointerDownOutside", "onFocusOutside", "onInteractOutside", "onPointerDown", "onPointerMove", "onPointerUp", "onPointerCancel", "onLostPointerCapture");
1015
+ createLayer(context, props);
1016
+ onSettled(() => () => {
1017
+ context.setContent(null);
1018
+ });
1019
+ return FocusScope(new _P$11(context, rest));
1020
+ }
1021
+ function Title(props) {
1022
+ const context = useContext(DrawerContext);
1023
+ return ssrElement("h2", [new _P$12(context), props], void 0, true);
1024
+ }
1025
+ function Description(props) {
1026
+ const context = useContext(DrawerContext);
1027
+ return ssrElement("p", [new _P$13(context), props], void 0, true);
1028
+ }
1029
+ function Handle(props) {
1030
+ var _v$;
1031
+ const context = useContext(DrawerContext);
1032
+ const rest = omit(props, "preventCycle", "children", "onClick", "onPointerDown", "onPointerMove", "onPointerCancel");
1033
+ return ssrElement("div", rest, () => {
1034
+ return _v$ = scope(() => {
1035
+ return escape(props.children);
1036
+ }), ssr(_tmpl$, _v$);
1037
+ }, true, _sk$3, () => " data-vaul-handle aria-hidden=\"true\"" + ssrElementAttribute("data-vaul-drawer-visible", context.isOpen ? "true" : "false"));
1038
+ }
1039
+ function callHandler(handler, event) {
1040
+ if (typeof handler === "function") handler(event);
1041
+ else if (handler) handler[0](handler[1], event);
1042
+ }
1043
+ // src/index.ts
1044
+ const Drawer = {
1045
+ Close,
1046
+ Content,
1047
+ Description,
1048
+ Handle,
1049
+ NestedRoot,
1050
+ Overlay,
1051
+ Portal,
1052
+ Root,
1053
+ Title,
1054
+ Trigger
1055
+ };
1056
+ export { Close, Content, Description, Drawer, Handle, NestedRoot, Overlay, Portal, Root, Title, Trigger };
1057
+
1058
+ //# sourceMappingURL=index.js.map