@sigmela/router 0.7.2 → 0.9.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.
package/dist/index.js CHANGED
@@ -236,15 +236,52 @@ function useSheetDragEngine({
236
236
  targetRef
237
237
  ]);
238
238
  }
239
+ function useMediaQuery(query) {
240
+ const [matches, setMatches] = useState(() => {
241
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
242
+ return false;
243
+ }
244
+ return window.matchMedia(query).matches;
245
+ });
246
+ useEffect(() => {
247
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
248
+ return;
249
+ }
250
+ const mediaQuery = window.matchMedia(query);
251
+ const handleChange = (event) => setMatches(event.matches);
252
+ setMatches(mediaQuery.matches);
253
+ if (typeof mediaQuery.addEventListener === "function") {
254
+ mediaQuery.addEventListener("change", handleChange);
255
+ return () => mediaQuery.removeEventListener("change", handleChange);
256
+ }
257
+ const mediaQueryWithListener = mediaQuery;
258
+ mediaQueryWithListener.addListener?.(handleChange);
259
+ return () => mediaQueryWithListener.removeListener?.(handleChange);
260
+ }, [query]);
261
+ return matches;
262
+ }
263
+ function useMinWidth(minWidth) {
264
+ return useMediaQuery(`(min-width: ${minWidth}px)`);
265
+ }
266
+ function prefersReducedMotion() {
267
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
268
+ }
239
269
  var SHEET_SPRING = { type: "spring", bounce: 0, duration: 0.38 };
240
270
  var CLOSE_DISTANCE = 112;
241
271
  var CLOSE_VELOCITY = 850;
242
272
  var DRAG_START_DISTANCE = 6;
243
273
  var PROJECT_VELOCITY_FACTOR = 0.18;
274
+ var SIDE_PANEL_TWEEN = { duration: 0.3, ease: [0.22, 0.61, 0.36, 1] };
275
+ var INSTANT = { duration: 0 };
244
276
  function closeTargetY(surface, target) {
245
277
  if (target === "viewport") return window.innerHeight + 32;
246
278
  return (surface?.getBoundingClientRect().height || window.innerHeight) + 32;
247
279
  }
280
+ function closeTargetX(surface, currentX) {
281
+ if (!surface) return window.innerWidth;
282
+ const left = surface.getBoundingClientRect().left - currentX;
283
+ return Math.max(0, window.innerWidth - left) + 32;
284
+ }
248
285
  function SheetSurface({
249
286
  children,
250
287
  open,
@@ -263,15 +300,20 @@ function SheetSurface({
263
300
  scrollElement,
264
301
  ariaLabel,
265
302
  shell = "mobile-contained",
266
- closeTarget = "surface"
303
+ closeTarget = "surface",
304
+ edge = "bottom"
267
305
  }) {
268
306
  const surfaceRef = useRef(null);
269
307
  const removeRef = useRef(onExitComplete);
270
308
  const requestCloseRef = useRef(onRequestClose);
271
309
  const closingOffsetRef = useRef(0);
272
310
  const closingRef = useRef(false);
273
- const y = useMotionValue(window.innerHeight);
311
+ const edgeRef = useRef(edge);
312
+ const enteredRef = useRef(false);
313
+ const y = useMotionValue(edge === "bottom" ? window.innerHeight : 0);
314
+ const x = useMotionValue(edge === "right" ? window.innerWidth : 0);
274
315
  removeRef.current = onExitComplete;
316
+ edgeRef.current = edge;
275
317
  requestCloseRef.current = onRequestClose;
276
318
  usePageScrollLock(true);
277
319
  const requestClose = useCallback(
@@ -286,10 +328,18 @@ function SheetSurface({
286
328
  (offset = 0) => {
287
329
  if (closingRef.current) return;
288
330
  closingRef.current = true;
289
- const target = closeTargetY(surfaceRef.current, closeTarget);
290
- y.stop();
291
- y.set(Math.max(0, offset));
292
- const controls = animate(y, target, SHEET_SPRING);
331
+ const reduced = prefersReducedMotion();
332
+ let controls;
333
+ if (edgeRef.current === "right") {
334
+ x.stop();
335
+ const target = closeTargetX(surfaceRef.current, x.get());
336
+ controls = animate(x, target, reduced ? INSTANT : SIDE_PANEL_TWEEN);
337
+ } else {
338
+ const target = closeTargetY(surfaceRef.current, closeTarget);
339
+ y.stop();
340
+ y.set(Math.max(0, offset));
341
+ controls = animate(y, target, SHEET_SPRING);
342
+ }
293
343
  void controls.finished.then(
294
344
  () => {
295
345
  removeRef.current();
@@ -299,25 +349,55 @@ function SheetSurface({
299
349
  }
300
350
  );
301
351
  },
302
- [closeTarget, y]
352
+ [closeTarget, x, y]
303
353
  );
304
354
  useEffect(() => {
305
355
  if (!open) {
306
356
  closeWithAnimation(closingOffsetRef.current);
307
357
  return;
308
358
  }
309
- y.set(window.innerHeight);
310
- const controls = animate(y, 0, SHEET_SPRING);
311
- return () => controls.stop();
312
- }, [closeWithAnimation, open, y]);
359
+ if (enteredRef.current) {
360
+ x.stop();
361
+ y.stop();
362
+ x.set(0);
363
+ y.set(0);
364
+ return;
365
+ }
366
+ let controls;
367
+ if (edge === "right") {
368
+ y.set(0);
369
+ x.set(closeTargetX(surfaceRef.current, 0));
370
+ controls = animate(x, 0, prefersReducedMotion() ? INSTANT : SIDE_PANEL_TWEEN);
371
+ } else {
372
+ x.set(0);
373
+ y.set(window.innerHeight);
374
+ controls = animate(y, 0, SHEET_SPRING);
375
+ }
376
+ let cancelled = false;
377
+ void controls.finished.then(
378
+ () => {
379
+ if (!cancelled) enteredRef.current = true;
380
+ },
381
+ () => {
382
+ }
383
+ );
384
+ return () => {
385
+ cancelled = true;
386
+ controls.stop();
387
+ };
388
+ }, [closeWithAnimation, edge, open, x, y]);
313
389
  useEffect(() => {
314
- if (!canDismiss) return;
390
+ if (!canDismiss || !open) return;
315
391
  const handler = (event) => {
316
- if (event.key === "Escape") requestClose();
392
+ if (event.key !== "Escape") return;
393
+ const higherSurface = Array.from(
394
+ document.querySelectorAll(".router-overlay-shell")
395
+ ).some((shell2) => Number(shell2.style.zIndex) > zIndex);
396
+ if (!higherSurface) requestClose();
317
397
  };
318
398
  document.addEventListener("keydown", handler);
319
399
  return () => document.removeEventListener("keydown", handler);
320
- }, [canDismiss, requestClose]);
400
+ }, [canDismiss, open, requestClose, zIndex]);
321
401
  const shouldStartDrag = useCallback(
322
402
  (update) => update.totalY > 0 && sheetScrollAtTop(scrollElement ?? null),
323
403
  [scrollElement]
@@ -347,7 +427,7 @@ function SheetSurface({
347
427
  );
348
428
  useSheetDragEngine({
349
429
  targetRef: surfaceRef,
350
- enabled: swipeToClose && canDismiss,
430
+ enabled: swipeToClose && canDismiss && edge === "bottom",
351
431
  minDragDistance: DRAG_START_DISTANCE,
352
432
  ignoreStartTarget: sheetDragStartsInTextInput,
353
433
  shouldStart: shouldStartDrag,
@@ -383,7 +463,7 @@ function SheetSurface({
383
463
  {
384
464
  ref: surfaceRef,
385
465
  className: surfaceClassName,
386
- style: { ...surfaceStyle, y },
466
+ style: { ...surfaceStyle, x, y },
387
467
  role: "dialog",
388
468
  "aria-modal": "true",
389
469
  "aria-label": ariaLabel,
@@ -441,6 +521,7 @@ function OverlayContentScope({
441
521
  function useOverlayContentKey() {
442
522
  return useContext(OverlayContentKeyContext);
443
523
  }
524
+ var DESKTOP_MIN_WIDTH = 640;
444
525
  var ModalContext = createContext(null);
445
526
  function useModalContext() {
446
527
  const ctx = useContext(ModalContext);
@@ -505,7 +586,15 @@ function ModalFrame({
505
586
  const options = activeRegistration?.options;
506
587
  const swipeToClose = options?.swipeToClose ?? true;
507
588
  const ariaLabel = options?.ariaLabel;
508
- const isContentSized = contentSized || options?.presentation === "content";
589
+ const isDesktop = useMinWidth(DESKTOP_MIN_WIDTH);
590
+ const isRightPanel = isDesktop && options?.presentation === "right";
591
+ const isContentSized = !isRightPanel && (contentSized || options?.presentation === "content");
592
+ const surfaceClassName = [
593
+ "router-modal-surface",
594
+ isContentSized && "router-modal-surface--content-sized",
595
+ isRightPanel && "router-modal-surface--right",
596
+ !isRightPanel && options?.size === "wide" && "router-modal-surface--wide"
597
+ ].filter(Boolean).join(" ");
509
598
  const requestClose = useCallback(() => {
510
599
  onRequestClose();
511
600
  }, [onRequestClose]);
@@ -525,11 +614,16 @@ function ModalFrame({
525
614
  shell,
526
615
  backdrop: true,
527
616
  backdropClassName: `router-modal-backdrop${isContentSized ? " router-modal-backdrop--transparent" : ""}`,
528
- shellClassName: "router-modal-shell",
529
- surfaceClassName: `router-modal-surface${isContentSized ? " router-modal-surface--content-sized" : ""}${options?.size === "wide" ? " router-modal-surface--wide" : ""}`,
617
+ shellClassName: `router-modal-shell${isRightPanel ? " router-modal-shell--right" : ""}`,
618
+ surfaceClassName,
619
+ surfaceStyle: {
620
+ "--router-modal-width": options?.width ? `${options.width}px` : void 0,
621
+ "--router-modal-height": options?.height ? `${options.height}px` : void 0
622
+ },
530
623
  scrollElement: swipeTarget,
531
624
  ariaLabel,
532
625
  closeTarget: isContentSized ? "surface" : "viewport",
626
+ edge: isRightPanel ? "right" : "bottom",
533
627
  children
534
628
  }
535
629
  ) });
@@ -539,14 +633,16 @@ function ModalRoot({
539
633
  swipeToClose = true,
540
634
  ariaLabel,
541
635
  size = "regular",
636
+ width,
637
+ height,
542
638
  presentation = "fullscreen"
543
639
  }) {
544
640
  const { setOptions } = useModalContext();
545
641
  const contentKey = useOverlayContentKey();
546
642
  useLayoutEffect(() => {
547
- setOptions(contentKey, { swipeToClose, ariaLabel, size, presentation });
643
+ setOptions(contentKey, { swipeToClose, ariaLabel, size, width, height, presentation });
548
644
  return () => setOptions(contentKey, null);
549
- }, [ariaLabel, contentKey, presentation, setOptions, size, swipeToClose]);
645
+ }, [ariaLabel, contentKey, height, presentation, setOptions, size, swipeToClose, width]);
550
646
  return /* @__PURE__ */ jsx(Fragment, { children });
551
647
  }
552
648
  function ModalHeader({
@@ -941,7 +1037,7 @@ function splitNavigationFromState(state) {
941
1037
  if (!isRecord(state)) return void 0;
942
1038
  const value = state[SPLIT_NAVIGATION_KEY];
943
1039
  if (!isRecord(value)) return void 0;
944
- if (typeof value.splitViewId !== "string" || typeof value.detailPathId !== "string" || typeof value.detailDepth !== "number" || value.operation !== "selection" && value.operation !== "push" && value.operation !== "replace") {
1040
+ if (typeof value.splitViewId !== "string" || typeof value.detailPathId !== "string" || typeof value.detailDepth !== "number" || value.operation !== "selection" && value.operation !== "push" && value.operation !== "replace" && value.operation !== "pop") {
945
1041
  return void 0;
946
1042
  }
947
1043
  return {
@@ -994,7 +1090,7 @@ function historyNavigationKeyFromState(state) {
994
1090
  function historyCommitFromState(state) {
995
1091
  if (isRecord(state)) {
996
1092
  const commit = state[HISTORY_COMMIT_KEY];
997
- if (commit === "sync" || commit === "replace") return commit;
1093
+ if (commit === "sync" || commit === "replace" || commit === "pop") return commit;
998
1094
  }
999
1095
  return "push";
1000
1096
  }
@@ -1119,6 +1215,7 @@ function splitRouteChildren(children) {
1119
1215
  const routeChildren = [];
1120
1216
  const nonRouteChildren = [];
1121
1217
  for (const child of childArray) {
1218
+ if (child == null || typeof child === "boolean") continue;
1122
1219
  if (isStackRouteElement(child)) {
1123
1220
  routeChildren.push(child);
1124
1221
  } else {
@@ -1506,7 +1603,8 @@ function compileStackRoutes(children, parentPath, parentId = "root", inheritedGu
1506
1603
  container: descriptor.container,
1507
1604
  childRoute,
1508
1605
  drawerAside: isTabBar ? void 0 : props.aside,
1509
- drawerFooter: isTabBar ? void 0 : props.footer
1606
+ drawerFooter: isTabBar ? void 0 : props.footer,
1607
+ drawerDesktopCollapsible: isTabBar ? void 0 : props.desktopCollapsible
1510
1608
  };
1511
1609
  if (isTabBar && tabs) {
1512
1610
  containerRoute.tabBar = {
@@ -1622,8 +1720,8 @@ function stateForSplitNavigationTarget(state, targetMatch, action) {
1622
1720
  } : {
1623
1721
  splitViewId: origin.splitViewId,
1624
1722
  detailPathId: origin.detailPathId,
1625
- detailDepth: action === "navigate" ? origin.detailDepth + 1 : origin.detailDepth,
1626
- operation: action === "navigate" ? "push" : "replace"
1723
+ detailDepth: action === "navigate" ? origin.detailDepth + 1 : action === "pop" ? Math.max(0, origin.detailDepth - 1) : origin.detailDepth,
1724
+ operation: action === "navigate" ? "push" : action
1627
1725
  }
1628
1726
  );
1629
1727
  next[SPLIT_NAVIGATION_KEY] = metadata;
@@ -1987,30 +2085,6 @@ function pathnameForRouteContext(route, fallbackPathname) {
1987
2085
  }
1988
2086
  return normalizedPath(routePath);
1989
2087
  }
1990
- function useMinWidth(minWidth) {
1991
- const [matches, setMatches] = useState(() => {
1992
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
1993
- return false;
1994
- }
1995
- return window.matchMedia(`(min-width: ${minWidth}px)`).matches;
1996
- });
1997
- useEffect(() => {
1998
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
1999
- return;
2000
- }
2001
- const mediaQuery = window.matchMedia(`(min-width: ${minWidth}px)`);
2002
- const handleChange = (event) => setMatches(event.matches);
2003
- setMatches(mediaQuery.matches);
2004
- if (typeof mediaQuery.addEventListener === "function") {
2005
- mediaQuery.addEventListener("change", handleChange);
2006
- return () => mediaQuery.removeEventListener("change", handleChange);
2007
- }
2008
- const mediaQueryWithListener = mediaQuery;
2009
- mediaQueryWithListener.addListener?.(handleChange);
2010
- return () => mediaQueryWithListener.removeListener?.(handleChange);
2011
- }, [minWidth]);
2012
- return matches;
2013
- }
2014
2088
  function measuredElementWidth(element) {
2015
2089
  return element.getBoundingClientRect().width || element.clientWidth || 0;
2016
2090
  }
@@ -2099,6 +2173,12 @@ function useRouter() {
2099
2173
  ...options,
2100
2174
  state: stateWithSplitNavigationOrigin(options.state, splitPaneOrigin)
2101
2175
  });
2176
+ },
2177
+ popTo: (to, options = {}) => {
2178
+ router.popTo(to, {
2179
+ ...options,
2180
+ state: stateWithSplitNavigationOrigin(options.state, splitPaneOrigin)
2181
+ });
2102
2182
  }
2103
2183
  };
2104
2184
  }, [router, splitPaneOrigin]);
@@ -2118,7 +2198,6 @@ function useCurrentModal() {
2118
2198
  return current;
2119
2199
  }
2120
2200
  function DefaultSplitViewLayout({
2121
- children,
2122
2201
  primary,
2123
2202
  secondary,
2124
2203
  isWide,
@@ -2135,8 +2214,15 @@ function DefaultSplitViewLayout({
2135
2214
  "data-router-split-view-id": splitViewLayoutId,
2136
2215
  style: { "--sigmela-split-primary-max-width": `${primaryMaxWidth}px` },
2137
2216
  children: [
2138
- /* @__PURE__ */ jsx("div", { className: "sigmela-split-view__primary", children: isWide ? primary : children }),
2139
- isWide ? /* @__PURE__ */ jsx("div", { className: "sigmela-split-view__secondary", children: secondary }) : null
2217
+ /* @__PURE__ */ jsx(
2218
+ "div",
2219
+ {
2220
+ className: "sigmela-split-view__primary",
2221
+ style: !isWide && secondary ? { display: "none" } : void 0,
2222
+ children: primary
2223
+ }
2224
+ ),
2225
+ /* @__PURE__ */ jsx("div", { className: "sigmela-split-view__secondary", children: secondary })
2140
2226
  ]
2141
2227
  }
2142
2228
  );
@@ -2161,88 +2247,119 @@ function DefaultDrawerLayout({
2161
2247
  return next;
2162
2248
  });
2163
2249
  }, []);
2164
- return /* @__PURE__ */ jsxs("div", { className: "sigmela-drawer", "data-drawer-open": drawer?.isOpen ? "true" : "false", children: [
2165
- /* @__PURE__ */ jsxs("aside", { className: "sigmela-drawer__sidebar", children: [
2166
- /* @__PURE__ */ jsx("div", { className: "sigmela-drawer__sidebar-content", children: items.filter((node) => !node.hidden).map((node) => {
2167
- if (node.type === "item") {
2168
- return /* @__PURE__ */ jsxs(
2169
- "button",
2170
- {
2171
- type: "button",
2172
- className: "sigmela-drawer__item",
2173
- "data-active": node.id === activeItem ? "true" : void 0,
2174
- onClick: () => onItemChange?.(node.id),
2175
- children: [
2176
- node.icon ? /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-icon", children: node.icon }) : null,
2177
- /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-label", children: node.label }),
2178
- node.badge ? /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-badge", children: node.badge }) : null
2179
- ]
2180
- },
2181
- node.id
2182
- );
2183
- }
2184
- if (node.type === "section") {
2185
- return /* @__PURE__ */ jsx("div", { className: "sigmela-drawer__section-label", children: node.title }, node.id);
2186
- }
2187
- const visibleItems = node.items.filter((item) => !item.hidden);
2188
- if (visibleItems.length === 0) return null;
2189
- const isExpanded = expandedGroups.has(node.id);
2190
- const itemsId = `${node.id}-drawer-items`;
2191
- return /* @__PURE__ */ jsxs("div", { className: "sigmela-drawer__group-container", children: [
2192
- /* @__PURE__ */ jsxs(
2193
- "button",
2194
- {
2195
- type: "button",
2196
- className: "sigmela-drawer__item sigmela-drawer__group-label",
2197
- "aria-controls": itemsId,
2198
- "aria-expanded": isExpanded,
2199
- onClick: () => toggleGroup(node.id),
2200
- children: [
2201
- node.icon ? /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-icon", children: node.icon }) : null,
2202
- /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-label", children: node.label }),
2203
- node.chevron ? /* @__PURE__ */ jsx(
2204
- "span",
2205
- {
2206
- className: "sigmela-drawer__group-chevron",
2207
- "data-expanded": isExpanded ? "true" : void 0,
2208
- "aria-hidden": "true",
2209
- children: node.chevron
2210
- }
2211
- ) : null
2212
- ]
2213
- }
2214
- ),
2215
- isExpanded ? /* @__PURE__ */ jsx("div", { id: itemsId, className: "sigmela-drawer__group-items", children: visibleItems.map((item) => /* @__PURE__ */ jsxs(
2216
- "button",
2217
- {
2218
- type: "button",
2219
- className: "sigmela-drawer__item sigmela-drawer__subitem",
2220
- "data-active": item.id === activeItem ? "true" : void 0,
2221
- onClick: () => onItemChange?.(item.id),
2222
- children: [
2223
- item.icon ? /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-icon", children: item.icon }) : null,
2224
- /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-label", children: item.label }),
2225
- item.badge ? /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-badge", children: item.badge }) : null
2226
- ]
2227
- },
2228
- item.id
2229
- )) }) : null
2230
- ] }, node.id);
2231
- }) }),
2232
- footer ? /* @__PURE__ */ jsx("div", { className: "sigmela-drawer__footer", children: footer }) : null
2233
- ] }),
2234
- /* @__PURE__ */ jsx(
2235
- "button",
2236
- {
2237
- type: "button",
2238
- className: "sigmela-drawer__overlay",
2239
- "aria-label": "Close navigation",
2240
- onClick: drawer?.close
2241
- }
2242
- ),
2243
- /* @__PURE__ */ jsx("main", { className: "sigmela-drawer__main", children }),
2244
- aside ? /* @__PURE__ */ jsx("aside", { className: "sigmela-drawer__assistant", children: aside }) : null
2245
- ] });
2250
+ return /* @__PURE__ */ jsxs(
2251
+ "div",
2252
+ {
2253
+ className: "sigmela-drawer",
2254
+ "data-drawer-open": drawer?.isOpen ? "true" : "false",
2255
+ "data-drawer-collapsed": drawer?.isCollapsed ? "true" : "false",
2256
+ "data-drawer-desktop": drawer?.desktopCollapsible,
2257
+ children: [
2258
+ /* @__PURE__ */ jsxs(
2259
+ "aside",
2260
+ {
2261
+ className: "sigmela-drawer__sidebar",
2262
+ inert: drawer?.isMobile && !drawer.isOpen ? true : void 0,
2263
+ children: [
2264
+ /* @__PURE__ */ jsx("div", { className: "sigmela-drawer__sidebar-content", children: items.filter((node) => !node.hidden).map((node) => {
2265
+ if (node.type === "item") {
2266
+ return /* @__PURE__ */ jsxs(
2267
+ "button",
2268
+ {
2269
+ type: "button",
2270
+ className: "sigmela-drawer__item",
2271
+ "aria-label": node.label,
2272
+ title: drawer?.isCollapsed ? node.label : void 0,
2273
+ "data-active": node.id === activeItem ? "true" : void 0,
2274
+ onClick: () => onItemChange?.(node.id),
2275
+ children: [
2276
+ node.icon || drawer?.isCollapsed ? /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-icon", "aria-hidden": "true", children: node.icon ?? node.label.slice(0, 1) }) : null,
2277
+ /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-label", children: node.label }),
2278
+ node.badge ? /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-badge", children: node.badge }) : null
2279
+ ]
2280
+ },
2281
+ node.id
2282
+ );
2283
+ }
2284
+ if (node.type === "section") {
2285
+ return /* @__PURE__ */ jsx("div", { className: "sigmela-drawer__section-label", children: node.title }, node.id);
2286
+ }
2287
+ const visibleItems = node.items.filter((item) => !item.hidden);
2288
+ if (visibleItems.length === 0) return null;
2289
+ const isExpanded = expandedGroups.has(node.id);
2290
+ const itemsId = `${node.id}-drawer-items`;
2291
+ return /* @__PURE__ */ jsxs("div", { className: "sigmela-drawer__group-container", children: [
2292
+ /* @__PURE__ */ jsxs(
2293
+ "button",
2294
+ {
2295
+ type: "button",
2296
+ className: "sigmela-drawer__item sigmela-drawer__group-label",
2297
+ "aria-label": node.label,
2298
+ title: drawer?.isCollapsed ? node.label : void 0,
2299
+ "aria-controls": itemsId,
2300
+ "aria-expanded": isExpanded,
2301
+ onClick: () => {
2302
+ if (drawer?.isCollapsed) drawer.open();
2303
+ toggleGroup(node.id);
2304
+ },
2305
+ children: [
2306
+ node.icon || drawer?.isCollapsed ? /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-icon", "aria-hidden": "true", children: node.icon ?? node.label.slice(0, 1) }) : null,
2307
+ /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-label", children: node.label }),
2308
+ node.chevron ? /* @__PURE__ */ jsx(
2309
+ "span",
2310
+ {
2311
+ className: "sigmela-drawer__group-chevron",
2312
+ "data-expanded": isExpanded ? "true" : void 0,
2313
+ "aria-hidden": "true",
2314
+ children: node.chevron
2315
+ }
2316
+ ) : null
2317
+ ]
2318
+ }
2319
+ ),
2320
+ isExpanded ? /* @__PURE__ */ jsx("div", { id: itemsId, className: "sigmela-drawer__group-items", children: visibleItems.map((item) => /* @__PURE__ */ jsxs(
2321
+ "button",
2322
+ {
2323
+ type: "button",
2324
+ className: "sigmela-drawer__item sigmela-drawer__subitem",
2325
+ "data-active": item.id === activeItem ? "true" : void 0,
2326
+ onClick: () => onItemChange?.(item.id),
2327
+ children: [
2328
+ item.icon ? /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-icon", children: item.icon }) : null,
2329
+ /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-label", children: item.label }),
2330
+ item.badge ? /* @__PURE__ */ jsx("span", { className: "sigmela-drawer__item-badge", children: item.badge }) : null
2331
+ ]
2332
+ },
2333
+ item.id
2334
+ )) }) : null
2335
+ ] }, node.id);
2336
+ }) }),
2337
+ footer ? /* @__PURE__ */ jsx("div", { className: "sigmela-drawer__footer", children: footer }) : null
2338
+ ]
2339
+ }
2340
+ ),
2341
+ /* @__PURE__ */ jsx(
2342
+ "button",
2343
+ {
2344
+ type: "button",
2345
+ className: "sigmela-drawer__overlay",
2346
+ "aria-label": "Close navigation",
2347
+ tabIndex: drawer?.isMobile && drawer.isOpen ? 0 : -1,
2348
+ onClick: drawer?.close
2349
+ }
2350
+ ),
2351
+ /* @__PURE__ */ jsx(
2352
+ "main",
2353
+ {
2354
+ className: "sigmela-drawer__main",
2355
+ inert: drawer?.isMobile && drawer.isOpen ? true : void 0,
2356
+ children
2357
+ }
2358
+ ),
2359
+ aside ? /* @__PURE__ */ jsx("aside", { className: "sigmela-drawer__assistant", children: aside }) : null
2360
+ ]
2361
+ }
2362
+ );
2246
2363
  }
2247
2364
  function FrozenPaneRouteContent({
2248
2365
  route,
@@ -2568,7 +2685,7 @@ function SplitViewRouteContent({
2568
2685
  fallback: splitView.primaryRoute.fallback ?? fallback
2569
2686
  }
2570
2687
  ) });
2571
- const secondary = splitView.secondaryRoute ? /* @__PURE__ */ jsx(SplitPaneNavigationContext.Provider, { value: secondaryNavigationOrigin, children: isWide ? /* @__PURE__ */ jsx(
2688
+ const secondary = splitView.secondaryRoute ? /* @__PURE__ */ jsx(SplitPaneNavigationContext.Provider, { value: secondaryNavigationOrigin, children: /* @__PURE__ */ jsx(
2572
2689
  RouteStackViewport,
2573
2690
  {
2574
2691
  route: splitView.secondaryRoute,
@@ -2577,13 +2694,6 @@ function SplitViewRouteContent({
2577
2694
  stackKey: `${route.id}:secondary`
2578
2695
  },
2579
2696
  detailPathId
2580
- ) : /* @__PURE__ */ jsx(
2581
- FrozenPaneRouteContent,
2582
- {
2583
- route: splitView.secondaryRoute,
2584
- props: secondaryProps,
2585
- fallback: splitView.secondaryRoute.fallback ?? fallback
2586
- }
2587
2697
  ) }) : null;
2588
2698
  const activeContent = splitView.activePane === "secondary" && secondary ? secondary : primary;
2589
2699
  const layoutProps = {
@@ -2614,13 +2724,44 @@ function DrawerRouteContent({
2614
2724
  route,
2615
2725
  props
2616
2726
  }) {
2617
- const [isOpen, setIsOpen] = useState(false);
2618
- const open = useCallback(() => setIsOpen(true), []);
2619
- const close = useCallback(() => setIsOpen(false), []);
2620
- const toggle = useCallback(() => setIsOpen((value) => !value), []);
2727
+ const isMobile = !useMinWidth(641);
2728
+ const [mobileOpen, setMobileOpen] = useState(false);
2729
+ const [desktopCollapsed, setDesktopCollapsed] = useState(false);
2730
+ const desktopCollapsible = route.drawerDesktopCollapsible ?? "offcanvas";
2731
+ const isCollapsed = !isMobile && desktopCollapsed;
2732
+ const isOpen = isMobile ? mobileOpen : !desktopCollapsed;
2733
+ const open = useCallback(() => {
2734
+ if (isMobile) setMobileOpen(true);
2735
+ else setDesktopCollapsed(false);
2736
+ }, [isMobile]);
2737
+ const close = useCallback(() => {
2738
+ if (isMobile) setMobileOpen(false);
2739
+ else setDesktopCollapsed(true);
2740
+ }, [isMobile]);
2741
+ const toggle = useCallback(() => {
2742
+ if (isMobile) setMobileOpen((value) => !value);
2743
+ else setDesktopCollapsed((value) => !value);
2744
+ }, [isMobile]);
2745
+ useEffect(() => setMobileOpen(false), [isMobile, props.pathname]);
2746
+ useEffect(() => {
2747
+ if (!isMobile || !mobileOpen) return;
2748
+ const onKeyDown = (event) => {
2749
+ if (event.key === "Escape" && !event.defaultPrevented) setMobileOpen(false);
2750
+ };
2751
+ window.addEventListener("keydown", onKeyDown);
2752
+ return () => window.removeEventListener("keydown", onKeyDown);
2753
+ }, [isMobile, mobileOpen]);
2621
2754
  const drawerApi = useMemo(
2622
- () => ({ isOpen, open, close, toggle }),
2623
- [close, isOpen, open, toggle]
2755
+ () => ({
2756
+ isOpen,
2757
+ isMobile,
2758
+ isCollapsed,
2759
+ desktopCollapsible,
2760
+ open,
2761
+ close,
2762
+ toggle
2763
+ }),
2764
+ [close, isOpen, isMobile, isCollapsed, desktopCollapsible, open, toggle]
2624
2765
  );
2625
2766
  const childFallback = route.childRoute?.fallback ?? route.fallback ?? null;
2626
2767
  const drawer = route.drawer;
@@ -2640,7 +2781,7 @@ function DrawerRouteContent({
2640
2781
  if (item && normalizedPath(item.path) !== props.pathname) {
2641
2782
  navigateWithHistoryIndex(item.path);
2642
2783
  }
2643
- close();
2784
+ setMobileOpen(false);
2644
2785
  } : void 0;
2645
2786
  const layoutProps = {
2646
2787
  children: content,
@@ -2909,6 +3050,13 @@ function resolveStackChange(prev, snapshot) {
2909
3050
  };
2910
3051
  }
2911
3052
  const direction = resolveDirection(top, snapshot);
3053
+ if (snapshot.historyCommit === "pop" && snapshot.splitPane === "secondary") {
3054
+ const existingIndex2 = existingStackEntryIndexForSnapshot(prev, snapshot);
3055
+ return {
3056
+ entries: existingIndex2 !== -1 && existingIndex2 < prev.length - 1 ? entriesRevealingSnapshot(prev, existingIndex2, snapshot) : [createEntry(snapshot, "back", true)],
3057
+ direction: "back"
3058
+ };
3059
+ }
2912
3060
  if (isExplicitStackReplace(top, snapshot)) {
2913
3061
  return {
2914
3062
  entries: replaceActiveStackEntry(prev, top.id, snapshot, direction, true),
@@ -2970,6 +3118,10 @@ function visibleEntriesForSnapshot(entries, snapshot) {
2970
3118
  (entry) => entry.id === top.id ? mergeEntrySnapshot(entry, snapshot) : entry
2971
3119
  );
2972
3120
  }
3121
+ if (snapshot.historyCommit === "pop" && snapshot.splitPane === "secondary") {
3122
+ const existingIndex2 = existingStackEntryIndexForSnapshot(entries, snapshot);
3123
+ return existingIndex2 !== -1 && existingIndex2 < entries.length - 1 ? entriesRevealingSnapshot(entries, existingIndex2, snapshot) : [createEntry(snapshot, "back", true)];
3124
+ }
2973
3125
  if (isExplicitStackReplace(top, snapshot)) {
2974
3126
  return replaceActiveStackEntry(
2975
3127
  entries,
@@ -3640,6 +3792,20 @@ function RouterRuntime({
3640
3792
  },
3641
3793
  [pushModalEntry, updateActiveModalFlow]
3642
3794
  );
3795
+ const popTo = useCallback((to, options = {}) => {
3796
+ const { compiledRoutes: compiledRoutes2, pathname: pathname2, parser } = navigationScopeRef.current;
3797
+ const target = parseNavigationTarget(to, pathname2);
3798
+ const pushMatch = matchStackRouteByPresentation(
3799
+ compiledRoutes2,
3800
+ parser,
3801
+ target.pathname,
3802
+ PUSH_PRESENTATIONS
3803
+ );
3804
+ navigateWithHistoryIndex(to, {
3805
+ commit: "pop",
3806
+ state: stateForSplitNavigationTarget(options.state, pushMatch, "pop")
3807
+ });
3808
+ }, []);
3643
3809
  const goBack = useCallback(() => {
3644
3810
  if (goBackTopModal()) return true;
3645
3811
  const activeEntry = visibleActiveEntryRef.current;
@@ -3686,11 +3852,12 @@ function RouterRuntime({
3686
3852
  () => ({
3687
3853
  navigate,
3688
3854
  replace,
3855
+ popTo,
3689
3856
  close: closeModal,
3690
3857
  closeAll: closeAllModals,
3691
3858
  goBack
3692
3859
  }),
3693
- [closeAllModals, closeModal, goBack, navigate, replace]
3860
+ [closeAllModals, closeModal, goBack, navigate, popTo, replace]
3694
3861
  );
3695
3862
  useEffect(() => {
3696
3863
  if (visibleActiveEntry) {
@@ -3762,6 +3929,8 @@ function RouterRuntime({
3762
3929
  "data-route-stack-phase": phase,
3763
3930
  "data-route-stack-transition": transitionStatus,
3764
3931
  className: screenClassName(entry, phase, transitionStatus, animationType),
3932
+ "aria-hidden": phase === "active" ? void 0 : true,
3933
+ inert: phase === "active" ? void 0 : true,
3765
3934
  style: { zIndex },
3766
3935
  children: /* @__PURE__ */ jsx("div", { className: "stack-screen-container", children: /* @__PURE__ */ jsx(FrozenStackEntry, { entry, fallback: fallbackNode }) })
3767
3936
  },