gridsmith-ui 0.22.0 → 0.23.0

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.
@@ -1,11 +1,12 @@
1
1
  import { PreferencesContext, Button } from './chunk-7TOJ4G7S.js';
2
2
  import { useBreakpoint, readBreakpointPx } from './chunk-CRQ6KCRD.js';
3
- import { FormChangeCtx, useReturnFocus, focusInitial, useOverlayLayer, useFocusTrap, OVERLAY_CLOSE_ATTR, useNonModalDialog } from './chunk-RT7RYGC3.js';
3
+ import { FormChangeCtx, useOverlayLayer, useReturnFocus, useFocusTrap, focusInitial, useNonModalDialog, OVERLAY_CLOSE_ATTR } from './chunk-RT7RYGC3.js';
4
+ import { SurfaceBoundary } from './chunk-CKWITUSH.js';
4
5
  import { useTextOverflow, Tooltip } from './chunk-CXMV7SXJ.js';
5
6
  import { useLatest } from './chunk-WH2FUFAC.js';
6
7
  import { Icon } from './chunk-4LDYR7QP.js';
7
8
  import { cn, lockBodyScroll, unlockBodyScroll, OVERLAY_SURFACE, GLASS_OVERLAY } from './chunk-M2QRNBDQ.js';
8
- import { createContext, version, forwardRef, useState, useCallback, useRef, useEffect, isValidElement, useMemo, useId, useContext, useLayoutEffect } from 'react';
9
+ import { createContext, version, forwardRef, useState, useCallback, useRef, useEffect, isValidElement, useContext, useMemo, useId, useLayoutEffect } from 'react';
9
10
  import { createPortal } from 'react-dom';
10
11
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
12
 
@@ -54,6 +55,126 @@ function useCloseSettle(open, elRef, onSettled) {
54
55
  };
55
56
  }, [open, elRef, cb]);
56
57
  }
58
+ var STEP = 16;
59
+ var BIG_STEP = 64;
60
+ function clamp(v, min, max) {
61
+ return Math.min(Math.max(v, min), max);
62
+ }
63
+ function ResizeEdge({ edge, width, min, max, defaultWidth, onResize, label, onDraggingChange, className }) {
64
+ const [dragging, setDragging] = useState(false);
65
+ const start = useRef(null);
66
+ const latestOnResize = useLatest(onResize);
67
+ const direction = edge === "left" ? -1 : 1;
68
+ const onPointerDown = useCallback(
69
+ (e) => {
70
+ if (e.button !== 0) return;
71
+ e.preventDefault();
72
+ start.current = { x: e.clientX, width };
73
+ setDragging(true);
74
+ e.currentTarget.setPointerCapture(e.pointerId);
75
+ },
76
+ [width]
77
+ );
78
+ const onPointerMove = useCallback(
79
+ (e) => {
80
+ if (!start.current) return;
81
+ const next = clamp(start.current.width + direction * (e.clientX - start.current.x), min, max());
82
+ latestOnResize.current(next);
83
+ },
84
+ [direction, min, max, latestOnResize]
85
+ );
86
+ const endDrag = useCallback(() => {
87
+ start.current = null;
88
+ setDragging(false);
89
+ }, []);
90
+ const latestOnDraggingChange = useLatest(onDraggingChange);
91
+ useEffect(() => {
92
+ latestOnDraggingChange.current?.(dragging);
93
+ }, [dragging, latestOnDraggingChange]);
94
+ useEffect(() => {
95
+ if (!dragging) return;
96
+ const onKey = (e) => {
97
+ if (e.key !== "Escape" || !start.current) return;
98
+ latestOnResize.current(start.current.width);
99
+ endDrag();
100
+ };
101
+ document.addEventListener("keydown", onKey);
102
+ return () => document.removeEventListener("keydown", onKey);
103
+ }, [dragging, endDrag, latestOnResize]);
104
+ const onKeyDown = (e) => {
105
+ const step = e.shiftKey ? BIG_STEP : STEP;
106
+ let next = null;
107
+ if (e.key === "ArrowLeft") next = width - direction * step;
108
+ else if (e.key === "ArrowRight") next = width + direction * step;
109
+ else if (e.key === "Home") next = min;
110
+ else if (e.key === "End") next = max();
111
+ else if (e.key === "Enter") next = defaultWidth;
112
+ if (next === null) return;
113
+ e.preventDefault();
114
+ onResize(clamp(next, min, max()));
115
+ };
116
+ return /* @__PURE__ */ jsx(
117
+ "div",
118
+ {
119
+ role: "separator",
120
+ tabIndex: 0,
121
+ "aria-orientation": "vertical",
122
+ "aria-label": label,
123
+ "aria-valuemin": min,
124
+ "aria-valuemax": max(),
125
+ "aria-valuenow": Math.round(width),
126
+ "data-resize-edge": edge,
127
+ "data-dragging": dragging || void 0,
128
+ onPointerDown,
129
+ onPointerMove,
130
+ onPointerUp: endDrag,
131
+ onPointerCancel: endDrag,
132
+ onDoubleClick: () => onResize(clamp(defaultWidth, min, max())),
133
+ onKeyDown,
134
+ className: cn(
135
+ // A narrow absolutely-positioned strip over the edge — never a flex child that takes width.
136
+ "group/edge absolute top-0 bottom-0 z-10 w-ds-2 cursor-col-resize touch-none select-none outline-none",
137
+ edge === "left" ? "-left-ds-1" : "-right-ds-1",
138
+ className
139
+ ),
140
+ children: /* @__PURE__ */ jsx(
141
+ "div",
142
+ {
143
+ "aria-hidden": "true",
144
+ className: cn(
145
+ "absolute top-0 bottom-0 left-1/2 w-px -translate-x-1/2 transition-[width,background-color] duration-[var(--ds-transition-speed)]",
146
+ "group-hover/edge:w-[3px] group-hover/edge:bg-[var(--ds-accent-primary)]",
147
+ "group-focus-visible/edge:w-[3px] group-focus-visible/edge:bg-[var(--ds-accent-primary)]",
148
+ dragging && "w-[3px] bg-[var(--ds-accent-primary)]"
149
+ )
150
+ }
151
+ )
152
+ }
153
+ );
154
+ }
155
+ var remembered = /* @__PURE__ */ new Map();
156
+ function useRememberedWidth(key) {
157
+ const [width, setWidth] = useState(() => key ? remembered.get(key) ?? null : null);
158
+ const remember = useCallback(
159
+ (w) => {
160
+ setWidth(w);
161
+ if (!key) return;
162
+ if (w === null) remembered.delete(key);
163
+ else remembered.set(key, w);
164
+ },
165
+ [key]
166
+ );
167
+ return [width, remember];
168
+ }
169
+ function pxOf(length) {
170
+ if (!length) return null;
171
+ const m = /^(\d+(?:\.\d+)?)px$/.exec(length.trim());
172
+ return m ? parseFloat(m[1]) : null;
173
+ }
174
+ function defaultMaxWidth(container) {
175
+ const base = container instanceof HTMLElement ? container.clientWidth : typeof window !== "undefined" ? window.innerWidth : 1200;
176
+ return Math.round(base * 0.6);
177
+ }
57
178
  var OVERLAY_ROOT_ATTR = "data-overlay-root";
58
179
  function controlsOf(root) {
59
180
  if (!root) return "";
@@ -209,7 +330,7 @@ function DiscardConfirm({ open, onKeep, onDiscard }) {
209
330
  "aria-label": "Unsaved changes",
210
331
  "data-discard-confirm": "",
211
332
  className: "shrink-0 flex flex-wrap items-center justify-between gap-ds-3 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] bg-[var(--ds-status-warning-bg)]",
212
- style: { padding: "var(--ds-space-3) var(--ds-space-5)" },
333
+ style: { padding: "var(--ds-space-3) var(--ds-page-gutter)" },
213
334
  children: [
214
335
  /* @__PURE__ */ jsx("p", { className: "text-sm text-[var(--ds-text-primary)]", children: "You have unsaved changes." }),
215
336
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-2", children: [
@@ -220,328 +341,26 @@ function DiscardConfirm({ open, onKeep, onDiscard }) {
220
341
  }
221
342
  );
222
343
  }
223
- function Sheet({ open, onClose: onCloseProp, title, children, footer, confirm, dirty, onDiscard, snapPoints = [0.5, 0.92], container, flush, keepMounted = false, id, labelledBy }) {
224
- const standalone = confirm === void 0;
225
- const { marked, mark } = useOverlayMark(open);
226
- const guards = standalone || dirty !== void 0;
227
- const guard = useDirtyGuard(standalone ? dirty ?? marked : dirty, onCloseProp, onDiscard);
228
- const onClose = guard.requestClose;
229
- const titleId = useId();
230
- const panelRef = useRef(null);
231
- const [mounted, setMounted] = useState(open);
232
- const [hidden, setHidden] = useState(!open);
233
- const [snapIdx, setSnapIdx] = useState(0);
234
- const [dragOffset, setDragOffset] = useState(0);
235
- const [dragging, setDragging] = useState(false);
236
- const dragStartY = useRef(0);
237
- const startHeight = useRef(0);
238
- const onCloseRef = useLatest(onClose);
239
- const [prevOpen, setPrevOpen] = useState(open);
240
- if (open !== prevOpen) {
241
- setPrevOpen(open);
242
- if (open) {
243
- setMounted(true);
244
- setHidden(false);
245
- setSnapIdx(0);
246
- setDragOffset(0);
247
- }
248
- }
249
- if (open && !mounted) setMounted(true);
250
- if (open && hidden) setHidden(false);
251
- const viewportHeight = useCallback(
252
- () => container instanceof HTMLElement ? container.clientHeight : window.innerHeight,
253
- [container]
254
- );
255
- useCloseSettle(open, panelRef, () => {
256
- if (keepMounted) setHidden(true);
257
- else setMounted(false);
258
- });
259
- useReturnFocus(open, panelRef);
260
- useEffect(() => {
261
- if (!open || !mounted) return;
262
- const raf = requestAnimationFrame(() => focusInitial(panelRef.current));
263
- return () => cancelAnimationFrame(raf);
264
- }, [open, mounted]);
265
- const isTopLayer = useOverlayLayer(open);
266
- useFocusTrap(panelRef, open, isTopLayer);
267
- useEffect(() => {
268
- if (!open) return;
269
- const onKeyDown = (e) => {
270
- if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
271
- e.preventDefault();
272
- onCloseRef.current();
273
- };
274
- document.addEventListener("keydown", onKeyDown);
275
- if (!container) lockBodyScroll();
276
- return () => {
277
- document.removeEventListener("keydown", onKeyDown);
278
- if (!container) unlockBodyScroll();
344
+ var DrawerWidthCtx = createContext(null);
345
+ function useScrollbarInset() {
346
+ const observer = useRef(null);
347
+ return useCallback((el) => {
348
+ observer.current?.disconnect();
349
+ observer.current = null;
350
+ if (!el) return;
351
+ const measure = () => {
352
+ const style = getComputedStyle(el);
353
+ const borders = (parseFloat(style.borderLeftWidth) || 0) + (parseFloat(style.borderRightWidth) || 0);
354
+ el.style.setProperty("--gs-scrollbar", `${Math.max(0, el.offsetWidth - el.clientWidth - borders)}px`);
279
355
  };
280
- }, [open, container, onCloseRef, isTopLayer]);
281
- const handlePointerDown = useCallback((e) => {
282
- setDragging(true);
283
- dragStartY.current = e.clientY;
284
- startHeight.current = (snapPoints[snapIdx] ?? snapPoints[0]) * viewportHeight();
285
- setDragOffset(0);
286
- e.target.setPointerCapture(e.pointerId);
287
- }, [snapIdx, snapPoints, viewportHeight]);
288
- const handlePointerMove = useCallback((e) => {
289
- if (!dragging) return;
290
- const delta = dragStartY.current - e.clientY;
291
- setDragOffset(delta);
292
- }, [dragging]);
293
- const handlePointerUp = useCallback(() => {
294
- if (!dragging) return;
295
- setDragging(false);
296
- const currentHeightPx = startHeight.current + dragOffset;
297
- const currentFraction = currentHeightPx / viewportHeight();
298
- if (currentFraction < snapPoints[0] * 0.6) {
299
- onClose();
300
- setDragOffset(0);
301
- return;
302
- }
303
- let bestIdx = 0;
304
- let bestDist = Math.abs(currentFraction - snapPoints[0]);
305
- for (let i = 1; i < snapPoints.length; i++) {
306
- const dist = Math.abs(currentFraction - snapPoints[i]);
307
- if (dist < bestDist) {
308
- bestDist = dist;
309
- bestIdx = i;
310
- }
311
- }
312
- setSnapIdx(bestIdx);
313
- setDragOffset(0);
314
- }, [dragging, dragOffset, snapPoints, onClose, viewportHeight]);
315
- if (!mounted) return null;
316
- const baseHeight = (snapPoints[snapIdx] ?? snapPoints[0]) * 100;
317
- const heightUnit = container ? "%" : "vh";
318
- const dragPx = dragging ? dragOffset : 0;
319
- const positionClass = container ? "absolute" : "fixed";
320
- return createPortal(
321
- /* @__PURE__ */ jsxs(
322
- "div",
323
- {
324
- hidden: keepMounted && hidden ? true : void 0,
325
- className: cn(
326
- `${positionClass} inset-0 z-[var(--ds-z-modal)] transition-opacity duration-[var(--ds-transition-speed)]`,
327
- open ? "pointer-events-auto" : "pointer-events-none"
328
- ),
329
- children: [
330
- /* @__PURE__ */ jsx(
331
- "div",
332
- {
333
- className: cn(
334
- "absolute inset-0 bg-black/50 transition-opacity duration-[var(--ds-transition-speed)]",
335
- open ? "opacity-100" : "opacity-0"
336
- ),
337
- onClick: onClose
338
- }
339
- ),
340
- /* @__PURE__ */ jsxs(
341
- "div",
342
- {
343
- ref: panelRef,
344
- id,
345
- role: "dialog",
346
- "data-gs": "sheet",
347
- ...{ [OVERLAY_ROOT_ATTR]: "" },
348
- "aria-modal": "true",
349
- "aria-labelledby": title ? titleId : labelledBy,
350
- "data-ds-sheet": "",
351
- className: cn(
352
- `${positionClass} bottom-0 left-0 right-0 z-10 flex flex-col`,
353
- OVERLAY_SURFACE,
354
- "text-[var(--ds-text-primary)]",
355
- GLASS_OVERLAY,
356
- "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
357
- "rounded-t-[var(--ds-radius-xl)]",
358
- !dragging && "transition-[transform,height] duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
359
- open ? "translate-y-0" : "translate-y-full"
360
- ),
361
- style: {
362
- containerType: "inline-size",
363
- height: dragPx ? `calc(${baseHeight}${heightUnit} + ${dragPx}px)` : `${baseHeight}${heightUnit}`
364
- },
365
- children: [
366
- /* @__PURE__ */ jsx(
367
- "div",
368
- {
369
- className: "flex justify-center pt-ds-2 pb-ds-1 cursor-grab active:cursor-grabbing touch-none",
370
- onPointerDown: handlePointerDown,
371
- onPointerMove: handlePointerMove,
372
- onPointerUp: handlePointerUp,
373
- onPointerCancel: handlePointerUp,
374
- children: /* @__PURE__ */ jsx("div", { className: "w-10 h-1 rounded-[var(--ds-radius-full)] bg-[var(--ds-border-default)]" })
375
- }
376
- ),
377
- title && /* @__PURE__ */ jsxs(
378
- "div",
379
- {
380
- className: "flex items-center justify-between shrink-0 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
381
- style: { padding: "var(--ds-space-2) var(--ds-space-5)" },
382
- children: [
383
- /* @__PURE__ */ jsx("h2", { id: titleId, className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] truncate", children: title }),
384
- /* @__PURE__ */ jsx(
385
- "button",
386
- {
387
- type: "button",
388
- onClick: onClose,
389
- ...{ [OVERLAY_CLOSE_ATTR]: "" },
390
- className: "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] transition-colors cursor-pointer",
391
- "aria-label": "Close",
392
- children: /* @__PURE__ */ jsx(Icon, { name: "close", size: 18 })
393
- }
394
- )
395
- ]
396
- }
397
- ),
398
- /* @__PURE__ */ jsx(
399
- "div",
400
- {
401
- className: "flex-1 overflow-y-auto",
402
- style: flush ? void 0 : { padding: "var(--ds-space-5)" },
403
- children: guards ? /* @__PURE__ */ jsx(OverlayCloseCtx, { value: guard.requestClose, children: standalone ? /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children }) : children }) : children
404
- }
405
- ),
406
- confirm,
407
- guards && /* @__PURE__ */ jsx(DiscardConfirm, { open: guard.confirming, onKeep: guard.keep, onDiscard: guard.discard }),
408
- footer && /* @__PURE__ */ jsx(
409
- "div",
410
- {
411
- className: "shrink-0 flex items-center justify-end gap-ds-2 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
412
- style: { padding: "var(--ds-space-3) var(--ds-space-5)" },
413
- children: footer
414
- }
415
- )
416
- ]
417
- }
418
- )
419
- ]
420
- }
421
- ),
422
- container ?? document.body
423
- );
424
- }
425
- var STEP = 16;
426
- var BIG_STEP = 64;
427
- function clamp(v, min, max) {
428
- return Math.min(Math.max(v, min), max);
429
- }
430
- function ResizeEdge({ edge, width, min, max, defaultWidth, onResize, label, onDraggingChange, className }) {
431
- const [dragging, setDragging] = useState(false);
432
- const start = useRef(null);
433
- const latestOnResize = useLatest(onResize);
434
- const direction = edge === "left" ? -1 : 1;
435
- const onPointerDown = useCallback(
436
- (e) => {
437
- if (e.button !== 0) return;
438
- e.preventDefault();
439
- start.current = { x: e.clientX, width };
440
- setDragging(true);
441
- e.currentTarget.setPointerCapture(e.pointerId);
442
- },
443
- [width]
444
- );
445
- const onPointerMove = useCallback(
446
- (e) => {
447
- if (!start.current) return;
448
- const next = clamp(start.current.width + direction * (e.clientX - start.current.x), min, max());
449
- latestOnResize.current(next);
450
- },
451
- [direction, min, max, latestOnResize]
452
- );
453
- const endDrag = useCallback(() => {
454
- start.current = null;
455
- setDragging(false);
356
+ measure();
357
+ if (typeof ResizeObserver === "undefined") return;
358
+ const o = new ResizeObserver(measure);
359
+ o.observe(el);
360
+ observer.current = o;
456
361
  }, []);
457
- const latestOnDraggingChange = useLatest(onDraggingChange);
458
- useEffect(() => {
459
- latestOnDraggingChange.current?.(dragging);
460
- }, [dragging, latestOnDraggingChange]);
461
- useEffect(() => {
462
- if (!dragging) return;
463
- const onKey = (e) => {
464
- if (e.key !== "Escape" || !start.current) return;
465
- latestOnResize.current(start.current.width);
466
- endDrag();
467
- };
468
- document.addEventListener("keydown", onKey);
469
- return () => document.removeEventListener("keydown", onKey);
470
- }, [dragging, endDrag, latestOnResize]);
471
- const onKeyDown = (e) => {
472
- const step = e.shiftKey ? BIG_STEP : STEP;
473
- let next = null;
474
- if (e.key === "ArrowLeft") next = width - direction * step;
475
- else if (e.key === "ArrowRight") next = width + direction * step;
476
- else if (e.key === "Home") next = min;
477
- else if (e.key === "End") next = max();
478
- else if (e.key === "Enter") next = defaultWidth;
479
- if (next === null) return;
480
- e.preventDefault();
481
- onResize(clamp(next, min, max()));
482
- };
483
- return /* @__PURE__ */ jsx(
484
- "div",
485
- {
486
- role: "separator",
487
- tabIndex: 0,
488
- "aria-orientation": "vertical",
489
- "aria-label": label,
490
- "aria-valuemin": min,
491
- "aria-valuemax": max(),
492
- "aria-valuenow": Math.round(width),
493
- "data-resize-edge": edge,
494
- "data-dragging": dragging || void 0,
495
- onPointerDown,
496
- onPointerMove,
497
- onPointerUp: endDrag,
498
- onPointerCancel: endDrag,
499
- onDoubleClick: () => onResize(clamp(defaultWidth, min, max())),
500
- onKeyDown,
501
- className: cn(
502
- // A narrow absolutely-positioned strip over the edge — never a flex child that takes width.
503
- "group/edge absolute top-0 bottom-0 z-10 w-ds-2 cursor-col-resize touch-none select-none outline-none",
504
- edge === "left" ? "-left-ds-1" : "-right-ds-1",
505
- className
506
- ),
507
- children: /* @__PURE__ */ jsx(
508
- "div",
509
- {
510
- "aria-hidden": "true",
511
- className: cn(
512
- "absolute top-0 bottom-0 left-1/2 w-px -translate-x-1/2 transition-[width,background-color] duration-[var(--ds-transition-speed)]",
513
- "group-hover/edge:w-[3px] group-hover/edge:bg-[var(--ds-accent-primary)]",
514
- "group-focus-visible/edge:w-[3px] group-focus-visible/edge:bg-[var(--ds-accent-primary)]",
515
- dragging && "w-[3px] bg-[var(--ds-accent-primary)]"
516
- )
517
- }
518
- )
519
- }
520
- );
521
- }
522
- var remembered = /* @__PURE__ */ new Map();
523
- function useRememberedWidth(key) {
524
- const [width, setWidth] = useState(() => key ? remembered.get(key) ?? null : null);
525
- const remember = useCallback(
526
- (w) => {
527
- setWidth(w);
528
- if (!key) return;
529
- if (w === null) remembered.delete(key);
530
- else remembered.set(key, w);
531
- },
532
- [key]
533
- );
534
- return [width, remember];
535
- }
536
- function pxOf(length) {
537
- if (!length) return null;
538
- const m = /^(\d+(?:\.\d+)?)px$/.exec(length.trim());
539
- return m ? parseFloat(m[1]) : null;
540
- }
541
- function defaultMaxWidth(container) {
542
- const base = container instanceof HTMLElement ? container.clientWidth : typeof window !== "undefined" ? window.innerWidth : 1200;
543
- return Math.round(base * 0.6);
544
362
  }
363
+ var insetLessScrollbar = (gutter) => `max(0px, calc(${gutter} - var(--gs-scrollbar, 0px)))`;
545
364
  var DrawerFooterCtx = createContext(null);
546
365
  var slideClasses = {
547
366
  right: {
@@ -566,7 +385,8 @@ var borderByPosition = {
566
385
  bottom: "border-t-[length:var(--ds-border-width)] border-t-[var(--ds-border-default)]"
567
386
  };
568
387
  function Drawer(props) {
569
- const { position = "right", mobile, snapPoints, open, onClose, title, children, footer, container, flush, keepMounted, id, labelledBy, dirty, onDiscard } = props;
388
+ const { position = "right", mobile, open, onClose, footer, dirty, onDiscard } = props;
389
+ const shellWidth = useContext(DrawerWidthCtx) ?? void 0;
570
390
  const isPhone = useBreakpoint("sm");
571
391
  const mobileMode = mobile ?? (position === "left" ? "panel" : "sheet");
572
392
  const { marked, mark } = useOverlayMark(open);
@@ -589,15 +409,16 @@ function Drawer(props) {
589
409
  () => ({ registerActions, unregisterActions }),
590
410
  [registerActions, unregisterActions]
591
411
  );
592
- if (isPhone && mobileMode === "sheet") {
593
- return /* @__PURE__ */ jsx(DrawerFooterCtx, { value: footerCtx, children: /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: guard.requestClose, children: /* @__PURE__ */ jsx(Sheet, { open, onClose: guard.requestClose, title, footer: effectiveFooter, confirm, container, flush, snapPoints, keepMounted, id, labelledBy, children }) }) }) });
594
- }
595
- return /* @__PURE__ */ jsx(DrawerFooterCtx, { value: footerCtx, children: /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: guard.requestClose, children: /* @__PURE__ */ jsx(DrawerPanel, { ...props, footer: effectiveFooter, onClose: guard.requestClose, confirm }) }) }) });
412
+ return /* @__PURE__ */ jsx(DrawerFooterCtx, { value: footerCtx, children: /* @__PURE__ */ jsx(OverlayMarkScope, { mark, children: /* @__PURE__ */ jsx(OverlayCloseCtx, { value: guard.requestClose, children: /* @__PURE__ */ jsx(DrawerPanel, { ...props, width: props.width ?? shellWidth, form: isPhone && mobileMode === "sheet" ? "sheet" : "panel", footer: effectiveFooter, onClose: guard.requestClose, confirm }) }) }) });
596
413
  }
597
- function DrawerPanel({ open, onClose, position = "right", width = "400px", title, children, footer, container, flush, lightBackdrop, topOffset, modal = true, keepMounted = false, id, labelledBy, resizable = false, maxWidth, confirm }) {
414
+ var INSET = "var(--ds-page-gutter)";
415
+ var DEFAULT_SNAPS = [0.5, 0.92];
416
+ function DrawerPanel({ form, open, onClose, position = "right", width = "400px", title, children, footer, container, flush, lightBackdrop, topOffset, modal = true, keepMounted = false, id, labelledBy, resizable = false, maxWidth, snapPoints = DEFAULT_SNAPS, confirm }) {
417
+ const sheet = form === "sheet";
598
418
  const nonModal = !modal;
419
+ const blocking = !nonModal || sheet;
599
420
  const [chosen, remember] = useRememberedWidth(id ?? labelledBy);
600
- const canResize = nonModal && resizable && position !== "bottom";
421
+ const canResize = !sheet && nonModal && resizable && position !== "bottom";
601
422
  const effectiveWidth = canResize && chosen !== null ? `${chosen}px` : width;
602
423
  const minPx = pxOf(width) ?? 320;
603
424
  const maxPx = () => pxOf(maxWidth) ?? defaultMaxWidth(container instanceof Element ? container : null);
@@ -607,40 +428,104 @@ function DrawerPanel({ open, onClose, position = "right", width = "400px", title
607
428
  const panelRef = useRef(null);
608
429
  const [mounted, setMounted] = useState(open);
609
430
  const [hidden, setHidden] = useState(!open);
431
+ const [snapIdx, setSnapIdx] = useState(0);
432
+ const [dragOffset, setDragOffset] = useState(0);
433
+ const [dragging, setDragging] = useState(false);
434
+ const dragStartY = useRef(0);
435
+ const startHeight = useRef(0);
610
436
  const onCloseRef = useLatest(onClose);
437
+ const [prevOpen, setPrevOpen] = useState(open);
438
+ if (open !== prevOpen) {
439
+ setPrevOpen(open);
440
+ if (open) {
441
+ setSnapIdx(0);
442
+ setDragOffset(0);
443
+ }
444
+ }
611
445
  if (open && !mounted) setMounted(true);
612
446
  if (open && hidden) setHidden(false);
613
447
  const isTopLayer = useOverlayLayer(open);
614
448
  useEffect(() => {
615
- if (!open || nonModal) return;
449
+ if (!open || !blocking) return;
616
450
  const onKeyDown = (e) => {
617
451
  if (e.key !== "Escape" || e.defaultPrevented || !isTopLayer()) return;
618
452
  e.preventDefault();
619
453
  onCloseRef.current();
620
454
  };
621
455
  document.addEventListener("keydown", onKeyDown);
622
- if (!container) lockBodyScroll();
623
- return () => {
624
- document.removeEventListener("keydown", onKeyDown);
625
- if (!container) unlockBodyScroll();
626
- };
627
- }, [open, nonModal, container, onCloseRef, isTopLayer]);
456
+ return () => document.removeEventListener("keydown", onKeyDown);
457
+ }, [open, blocking, onCloseRef, isTopLayer]);
458
+ useEffect(() => {
459
+ if (!open || !blocking || container) return;
460
+ lockBodyScroll();
461
+ return () => unlockBodyScroll();
462
+ }, [open, blocking, container]);
628
463
  useReturnFocus(open && !nonModal, panelRef);
629
- useFocusTrap(panelRef, open && !topOffset && !nonModal, isTopLayer);
464
+ useFocusTrap(panelRef, open && blocking && (sheet || !topOffset), isTopLayer);
630
465
  useEffect(() => {
631
466
  if (!open || !mounted || nonModal) return;
632
467
  const raf = requestAnimationFrame(() => focusInitial(panelRef.current));
633
468
  return () => cancelAnimationFrame(raf);
634
469
  }, [open, mounted, nonModal]);
635
470
  useNonModalDialog(panelRef, open && mounted && nonModal, onClose, isTopLayer);
471
+ const wasBlocking = useRef(blocking);
472
+ useEffect(() => {
473
+ const was = wasBlocking.current;
474
+ wasBlocking.current = blocking;
475
+ if (!open || !mounted || !blocking || was) return;
476
+ const panel = panelRef.current;
477
+ if (panel && !panel.contains(document.activeElement)) focusInitial(panel);
478
+ }, [open, mounted, blocking]);
479
+ const bodyRef = useScrollbarInset();
636
480
  useCloseSettle(open, panelRef, () => {
637
481
  if (keepMounted) setHidden(true);
638
482
  else setMounted(false);
639
483
  });
484
+ const viewportHeight = useCallback(
485
+ () => container instanceof HTMLElement ? container.clientHeight : window.innerHeight,
486
+ [container]
487
+ );
488
+ const handlePointerDown = useCallback((e) => {
489
+ setDragging(true);
490
+ dragStartY.current = e.clientY;
491
+ startHeight.current = (snapPoints[snapIdx] ?? snapPoints[0]) * viewportHeight();
492
+ setDragOffset(0);
493
+ e.target.setPointerCapture(e.pointerId);
494
+ }, [snapIdx, snapPoints, viewportHeight]);
495
+ const handlePointerMove = useCallback((e) => {
496
+ if (!dragging) return;
497
+ setDragOffset(dragStartY.current - e.clientY);
498
+ }, [dragging]);
499
+ const handlePointerUp = useCallback(() => {
500
+ if (!dragging) return;
501
+ setDragging(false);
502
+ const currentFraction = (startHeight.current + dragOffset) / viewportHeight();
503
+ if (currentFraction < snapPoints[0] * 0.6) {
504
+ onClose();
505
+ setDragOffset(0);
506
+ return;
507
+ }
508
+ let bestIdx = 0;
509
+ let bestDist = Math.abs(currentFraction - snapPoints[0]);
510
+ for (let i = 1; i < snapPoints.length; i++) {
511
+ const dist = Math.abs(currentFraction - snapPoints[i]);
512
+ if (dist < bestDist) {
513
+ bestDist = dist;
514
+ bestIdx = i;
515
+ }
516
+ }
517
+ setSnapIdx(bestIdx);
518
+ setDragOffset(0);
519
+ }, [dragging, dragOffset, snapPoints, onClose, viewportHeight]);
640
520
  if (!mounted) return null;
641
521
  const slide = slideClasses[position];
642
522
  const isHorizontal = position === "left" || position === "right";
643
523
  const positionClass = container ? "absolute" : "fixed";
524
+ const offset = sheet ? void 0 : topOffset;
525
+ const baseHeight = (snapPoints[snapIdx] ?? snapPoints[0]) * 100;
526
+ const heightUnit = container ? "%" : "vh";
527
+ const dragPx = dragging ? dragOffset : 0;
528
+ const heading = "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] truncate";
644
529
  return createPortal(
645
530
  /* @__PURE__ */ jsxs(
646
531
  "div",
@@ -650,17 +535,17 @@ function DrawerPanel({ open, onClose, position = "right", width = "400px", title
650
535
  `${positionClass} transition-opacity duration-[var(--ds-transition-speed)]`,
651
536
  // A non-modal panel never intercepts the page: the wrapper is inert to the
652
537
  // pointer and only the panel itself accepts events.
653
- nonModal ? "z-[var(--ds-z-overlay)] pointer-events-none" : cn("z-[var(--ds-z-modal)]", open ? "pointer-events-auto" : "pointer-events-none"),
654
- topOffset ? "left-0 right-0 bottom-0" : "inset-0"
538
+ blocking ? cn("z-[var(--ds-z-modal)]", open ? "pointer-events-auto" : "pointer-events-none") : "z-[var(--ds-z-overlay)] pointer-events-none",
539
+ offset ? "left-0 right-0 bottom-0" : "inset-0"
655
540
  ),
656
- style: topOffset ? { top: topOffset } : void 0,
541
+ style: offset ? { top: offset } : void 0,
657
542
  children: [
658
- !nonModal && /* @__PURE__ */ jsx(
543
+ blocking && /* @__PURE__ */ jsx(
659
544
  "div",
660
545
  {
661
546
  className: cn(
662
547
  "absolute inset-0 transition-opacity duration-[var(--ds-transition-speed)]",
663
- lightBackdrop ? "bg-black/15" : cn("bg-black/50", "[backdrop-filter:blur(calc(var(--ds-glass-blur)*0.3))] [-webkit-backdrop-filter:blur(calc(var(--ds-glass-blur)*0.3))]"),
548
+ sheet ? "bg-black/50" : lightBackdrop ? "bg-black/15" : cn("bg-black/50", "[backdrop-filter:blur(calc(var(--ds-glass-blur)*0.3))] [-webkit-backdrop-filter:blur(calc(var(--ds-glass-blur)*0.3))]"),
664
549
  open ? "opacity-100" : "opacity-0"
665
550
  ),
666
551
  onClick: onClose
@@ -672,11 +557,15 @@ function DrawerPanel({ open, onClose, position = "right", width = "400px", title
672
557
  ref: panelRef,
673
558
  id,
674
559
  role: "dialog",
675
- "data-gs": "drawer",
560
+ "data-gs": sheet ? "sheet" : "drawer",
561
+ "data-ds-sheet": sheet ? "" : void 0,
676
562
  ...{ [OVERLAY_ROOT_ATTR]: "" },
677
- "aria-modal": nonModal ? void 0 : "true",
563
+ "aria-modal": blocking ? "true" : void 0,
678
564
  "aria-labelledby": title ? titleId : labelledBy,
679
- style: {
565
+ style: sheet ? {
566
+ containerType: "inline-size",
567
+ height: dragPx ? `calc(${baseHeight}${heightUnit} + ${dragPx}px)` : `${baseHeight}${heightUnit}`
568
+ } : {
680
569
  containerType: "inline-size",
681
570
  width: isHorizontal ? effectiveWidth : void 0,
682
571
  maxWidth: isHorizontal ? "100%" : void 0,
@@ -685,23 +574,42 @@ function DrawerPanel({ open, onClose, position = "right", width = "400px", title
685
574
  },
686
575
  className: cn(
687
576
  `${positionClass} z-10 flex flex-col`,
688
- nonModal && "pointer-events-auto",
577
+ !blocking && "pointer-events-auto",
689
578
  OVERLAY_SURFACE,
690
579
  "text-[var(--ds-text-primary)]",
691
580
  GLASS_OVERLAY,
692
- // flush drops the edge border for children that draw their own (the mobile
693
- // menu's Sidebar); a non-modal panel has no backdrop to separate it from
694
- // the page, so it always keeps the border.
695
- (!flush || nonModal) && borderByPosition[position],
696
- "transition-transform duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
697
- isHorizontal && position === "left" && "rounded-r-[var(--ds-radius-lg)] overflow-hidden",
698
- isHorizontal && position === "right" && "rounded-l-[var(--ds-radius-lg)] overflow-hidden",
699
- !isHorizontal && "rounded-t-[var(--ds-radius-lg)] overflow-hidden",
700
- (lightBackdrop || nonModal) && "shadow-[var(--ds-shadow-overlay)]",
701
- slide.panel,
702
- open ? slide.open : slide.closed
581
+ sheet ? cn(
582
+ "bottom-0 left-0 right-0",
583
+ "border-t-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
584
+ "rounded-t-[var(--ds-radius-xl)]",
585
+ !dragging && "transition-[transform,height] duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
586
+ open ? "translate-y-0" : "translate-y-full"
587
+ ) : cn(
588
+ // flush drops the edge border for children that draw their own (the mobile
589
+ // menu's Sidebar); a non-modal panel has no backdrop to separate it from
590
+ // the page, so it always keeps the border.
591
+ (!flush || nonModal) && borderByPosition[position],
592
+ "transition-transform duration-[var(--ds-transition-speed)] ease-[var(--ds-transition-ease)]",
593
+ isHorizontal && position === "left" && "rounded-r-[var(--ds-radius-lg)] overflow-hidden",
594
+ isHorizontal && position === "right" && "rounded-l-[var(--ds-radius-lg)] overflow-hidden",
595
+ !isHorizontal && "rounded-t-[var(--ds-radius-lg)] overflow-hidden",
596
+ (lightBackdrop || nonModal) && "shadow-[var(--ds-shadow-overlay)]",
597
+ slide.panel,
598
+ open ? slide.open : slide.closed
599
+ )
703
600
  ),
704
601
  children: [
602
+ sheet && /* @__PURE__ */ jsx(
603
+ "div",
604
+ {
605
+ className: "flex justify-center pt-ds-2 pb-ds-1 cursor-grab active:cursor-grabbing touch-none",
606
+ onPointerDown: handlePointerDown,
607
+ onPointerMove: handlePointerMove,
608
+ onPointerUp: handlePointerUp,
609
+ onPointerCancel: handlePointerUp,
610
+ children: /* @__PURE__ */ jsx("div", { className: "w-10 h-1 rounded-[var(--ds-radius-full)] bg-[var(--ds-border-default)]" })
611
+ }
612
+ ),
705
613
  canResize && open && /* @__PURE__ */ jsx(
706
614
  ResizeEdge,
707
615
  {
@@ -717,13 +625,13 @@ function DrawerPanel({ open, onClose, position = "right", width = "400px", title
717
625
  title && /* @__PURE__ */ jsxs(
718
626
  "div",
719
627
  {
720
- className: "flex items-center justify-between shrink-0 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
721
- style: {
722
- padding: "var(--ds-space-3) var(--ds-space-6)",
628
+ className: "flex items-center justify-between gap-ds-3 shrink-0 border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
629
+ style: sheet ? { padding: `var(--ds-space-2) ${INSET}` } : {
630
+ padding: `var(--ds-space-3) ${INSET}`,
723
631
  minHeight: "calc(var(--ds-space-unit) * 6 + 1.75rem + 2px + var(--ds-border-width))"
724
632
  },
725
633
  children: [
726
- titleTruncated ? /* @__PURE__ */ jsx(Tooltip, { content: title, children: /* @__PURE__ */ jsx("h2", { ref: titleOverflowRef, id: titleId, className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] truncate", children: title }) }) : /* @__PURE__ */ jsx("h2", { ref: titleOverflowRef, id: titleId, className: "font-[family-name:var(--ds-font-display)] text-lg font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)] truncate", children: title }),
634
+ !sheet && titleTruncated ? /* @__PURE__ */ jsx(Tooltip, { content: title, children: /* @__PURE__ */ jsx("h2", { ref: titleOverflowRef, id: titleId, className: heading, children: title }) }) : /* @__PURE__ */ jsx("h2", { ref: titleOverflowRef, id: titleId, className: heading, children: title }),
727
635
  /* @__PURE__ */ jsx(
728
636
  "button",
729
637
  {
@@ -741,9 +649,10 @@ function DrawerPanel({ open, onClose, position = "right", width = "400px", title
741
649
  /* @__PURE__ */ jsx(
742
650
  "div",
743
651
  {
652
+ ref: bodyRef,
744
653
  className: "flex-1 overflow-y-auto",
745
- style: flush ? void 0 : { padding: "var(--ds-space-5)" },
746
- children
654
+ style: flush ? void 0 : { padding: `var(--ds-space-5) ${insetLessScrollbar(INSET)} var(--ds-space-5) ${INSET}` },
655
+ children: /* @__PURE__ */ jsx(SurfaceBoundary, { children })
747
656
  }
748
657
  ),
749
658
  confirm,
@@ -751,7 +660,7 @@ function DrawerPanel({ open, onClose, position = "right", width = "400px", title
751
660
  "div",
752
661
  {
753
662
  className: "shrink-0 flex items-center justify-end gap-ds-2 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
754
- style: { padding: "var(--ds-space-3) var(--ds-space-5)" },
663
+ style: { padding: `var(--ds-space-3) ${INSET}` },
755
664
  children: footer
756
665
  }
757
666
  )
@@ -819,7 +728,8 @@ var AppShell = forwardRef(function AppShell2({
819
728
  preferences,
820
729
  currentPath,
821
730
  homePath = "/",
822
- focus
731
+ focus,
732
+ drawerWidth = "520px"
823
733
  }, ref) {
824
734
  if (REACT_VERSION_PROBLEM) throw new Error(REACT_VERSION_PROBLEM);
825
735
  const [sidebarOpen, setSidebarOpen] = useState(false);
@@ -878,10 +788,10 @@ var AppShell = forwardRef(function AppShell2({
878
788
  const hasLauncher = !!launcher && !isSmallMobile;
879
789
  const launcherProps = isValidElement(launcher) ? launcher.props : null;
880
790
  const launcherCorner = !hasLauncher ? null : launcherProps?.variant === "ribbon" ? null : launcherProps?.edge === "bottom-left" ? "bottom-left" : launcherProps?.edge === "right" || launcherProps?.edge === "left" || launcherProps?.edge === "bottom" ? null : "bottom-right";
881
- return /* @__PURE__ */ jsx(PreferencesContext, { value: preferences ?? null, children: /* @__PURE__ */ jsx(
791
+ return /* @__PURE__ */ jsx(PreferencesContext, { value: preferences ?? null, children: /* @__PURE__ */ jsx(DrawerWidthCtx, { value: drawerWidth, children: /* @__PURE__ */ jsx(
882
792
  AppShellContext,
883
793
  {
884
- value: { hasSidebar, hasMobileMenu, hasMobileNav, sidebarOpen, setSidebarOpen, isMobile, isTablet, sidebarCollapsed, setSidebarCollapsed: setUserCollapsed, asideOpen, setAsideOpen, asideSide, asideFloating, setAsideFloating, container: shellEl, theme: theme ?? null, isHome, navbarFloating, sidebarFloating, registerChrome, hasLauncher, launcherCorner, hasFooter, focus: focus ?? null, hasUnsavedChanges: unsavedCount > 0, addUnsavedChanges },
794
+ value: { hasSidebar, hasMobileMenu, hasMobileNav, sidebarOpen, setSidebarOpen, isMobile, isTablet, sidebarCollapsed, setSidebarCollapsed: setUserCollapsed, asideOpen, setAsideOpen, asideSide, asideFloating, setAsideFloating, container: shellEl, theme: theme ?? null, isHome, navbarFloating, sidebarFloating, registerChrome, hasLauncher, launcherCorner, hasFooter, focus: focus ?? null, hasUnsavedChanges: unsavedCount > 0, addUnsavedChanges, drawerWidth },
885
795
  children: /* @__PURE__ */ jsxs("div", { ref: (el) => {
886
796
  setShellEl(el);
887
797
  if (typeof ref === "function") ref(el);
@@ -935,7 +845,7 @@ var AppShell = forwardRef(function AppShell2({
935
845
  !isSmallMobile && launcher
936
846
  ] })
937
847
  }
938
- ) });
848
+ ) }) });
939
849
  });
940
850
  function useCompact(ref, breakpoint, band = 0) {
941
851
  const px = typeof breakpoint === "number" ? breakpoint : readBreakpointPx(breakpoint);
@@ -964,6 +874,6 @@ function useElementWidth(ref) {
964
874
  return width;
965
875
  }
966
876
 
967
- export { AppShell, DiscardConfirm, Drawer, DrawerFooterCtx, OVERLAY_ROOT_ATTR, OverlayCloseCtx, OverlayMarkCtx, OverlayMarkScope, REACT_VERSION_PROBLEM, ResizeEdge, Sheet, SkipLink, defaultMaxWidth, pxOf, useAppShell, useBelow, useCloseSettle, useCompact, useDirtyGuard, useElementWidth, useOverlayMark, useRememberedWidth, useUnsavedChanges };
968
- //# sourceMappingURL=chunk-AAYCQXZQ.js.map
969
- //# sourceMappingURL=chunk-AAYCQXZQ.js.map
877
+ export { AppShell, DiscardConfirm, Drawer, DrawerFooterCtx, DrawerPanel, OVERLAY_ROOT_ATTR, OverlayCloseCtx, OverlayMarkCtx, OverlayMarkScope, REACT_VERSION_PROBLEM, ResizeEdge, SkipLink, defaultMaxWidth, insetLessScrollbar, pxOf, useAppShell, useBelow, useCloseSettle, useCompact, useDirtyGuard, useElementWidth, useOverlayMark, useRememberedWidth, useScrollbarInset, useUnsavedChanges };
878
+ //# sourceMappingURL=chunk-6PL7FXYH.js.map
879
+ //# sourceMappingURL=chunk-6PL7FXYH.js.map