@dloizides/ui-nav 1.16.1 → 1.18.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
@@ -1,18 +1,99 @@
1
1
  'use strict';
2
2
 
3
- var React3 = require('react');
3
+ var React = require('react');
4
4
  var reactNative = require('react-native');
5
5
  var uiFeedback = require('@dloizides/ui-feedback');
6
- var uiMotion = require('@dloizides/ui-motion');
7
6
  var jsxRuntime = require('react/jsx-runtime');
7
+ var uiMotion = require('@dloizides/ui-motion');
8
8
  var uiLayout = require('@dloizides/ui-layout');
9
9
  var authWeb = require('@dloizides/auth-web');
10
10
 
11
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
12
 
13
- var React3__default = /*#__PURE__*/_interopDefault(React3);
13
+ var React__default = /*#__PURE__*/_interopDefault(React);
14
14
 
15
15
  // src/Sidebar.tsx
16
+ var IS_WEB = reactNative.Platform.OS === "web";
17
+ var FOCUS_RING_WIDTH = 2;
18
+ var FOCUS_RING_OFFSET = 2;
19
+ var HOVER_TRANSITION_MS = 150;
20
+ var TINT_TRANSITION_MS = 160;
21
+ function focusRingStyle(focused, ringColor) {
22
+ if (!IS_WEB || !focused) return void 0;
23
+ const ring = {
24
+ outlineWidth: FOCUS_RING_WIDTH,
25
+ outlineStyle: "solid",
26
+ outlineColor: ringColor,
27
+ outlineOffset: FOCUS_RING_OFFSET
28
+ };
29
+ return ring;
30
+ }
31
+ function webTransition(properties, durationMs, reducedMotion) {
32
+ if (!IS_WEB) return void 0;
33
+ const transition = {
34
+ transitionProperty: properties,
35
+ transitionDuration: reducedMotion ? "0ms" : `${durationMs}ms`
36
+ };
37
+ return transition;
38
+ }
39
+ function hoverTransitionStyle(reducedMotion) {
40
+ return webTransition("color, background-color", HOVER_TRANSITION_MS, reducedMotion);
41
+ }
42
+ function tintTransitionStyle(reducedMotion) {
43
+ return webTransition("opacity", TINT_TRANSITION_MS, reducedMotion);
44
+ }
45
+ function rotateTransitionStyle(reducedMotion) {
46
+ return webTransition("transform", TINT_TRANSITION_MS, reducedMotion);
47
+ }
48
+ var MIN_TARGET = 36;
49
+ var styles = reactNative.StyleSheet.create({
50
+ trigger: {
51
+ flexDirection: "row",
52
+ alignItems: "center",
53
+ justifyContent: "space-between",
54
+ columnGap: 8,
55
+ paddingHorizontal: 12,
56
+ minHeight: MIN_TARGET,
57
+ borderWidth: 1,
58
+ borderRadius: 8
59
+ },
60
+ label: { fontSize: 14 },
61
+ badge: { paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, borderWidth: 1 },
62
+ badgeText: { fontSize: 11, fontWeight: "700" }
63
+ });
64
+ var CommandPaletteTrigger = ({
65
+ label,
66
+ hint,
67
+ shortcut,
68
+ onPress,
69
+ testID
70
+ }) => {
71
+ const { theme } = uiFeedback.useUi();
72
+ const colors = theme.colors;
73
+ const primary = theme.palette.primary["500"];
74
+ const [focused, setFocused] = React__default.default.useState(false);
75
+ return /* @__PURE__ */ jsxRuntime.jsxs(
76
+ reactNative.Pressable,
77
+ {
78
+ accessibilityHint: hint,
79
+ accessibilityLabel: label,
80
+ accessibilityRole: "button",
81
+ style: [
82
+ styles.trigger,
83
+ { backgroundColor: colors.surface, borderColor: colors.border },
84
+ focusRingStyle(focused, primary)
85
+ ],
86
+ testID,
87
+ onBlur: () => setFocused(false),
88
+ onFocus: () => setFocused(true),
89
+ onPress,
90
+ children: [
91
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.label, { color: colors.textSecondary }], children: label }),
92
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.badge, { borderColor: colors.border }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.badgeText, { color: colors.textSecondary }], children: shortcut }) })
93
+ ]
94
+ }
95
+ );
96
+ };
16
97
 
17
98
  // src/sidebarFilter.ts
18
99
  function searchTokens(query) {
@@ -47,15 +128,40 @@ function isFilterActive(query) {
47
128
  }
48
129
 
49
130
  // src/isRouteActive.ts
50
- function isRouteActive(pathname, route) {
131
+ function isRouteActive(pathname, route, exact = false) {
132
+ if (exact) return pathname === route;
51
133
  if (route === "/") return pathname === "/";
52
134
  return pathname === route || pathname.startsWith(`${route}/`);
53
135
  }
136
+ function leafRoutes(items) {
137
+ return items.flatMap((item) => {
138
+ const children = item.children ?? [];
139
+ const isGroup = children.length > 0;
140
+ return isGroup ? leafRoutes(children) : [{ route: item.route, exact: item.exact === true }];
141
+ });
142
+ }
143
+ function resolveActiveRoute(items, pathname) {
144
+ let winner;
145
+ let winnerLength = -1;
146
+ for (const leaf of leafRoutes(items)) {
147
+ const matches = isRouteActive(pathname, leaf.route, leaf.exact);
148
+ const isLonger = leaf.route.length > winnerLength;
149
+ if (matches && isLonger) {
150
+ winner = leaf.route;
151
+ winnerLength = leaf.route.length;
152
+ }
153
+ }
154
+ return winner;
155
+ }
54
156
  var ACTIVE_BORDER_RADIUS = 4;
55
157
  var ACTIVE_ACCENT_WIDTH = 3;
56
158
  var NAV_ROW_RADIUS = 8;
57
159
  var ACTIVE_TINT_OPACITY = 0.14;
58
160
  var HOVER_TINT_OPACITY = 0.06;
161
+ function rowTintOpacity(isActive, hovered) {
162
+ if (isActive) return ACTIVE_TINT_OPACITY;
163
+ return hovered ? HOVER_TINT_OPACITY : 0;
164
+ }
59
165
  var NAV_LINK_GAP = 4;
60
166
  var navStyles = reactNative.StyleSheet.create({
61
167
  // --- Sidebar ---
@@ -81,6 +187,12 @@ var navStyles = reactNative.StyleSheet.create({
81
187
  sidebarSpacer: {
82
188
  flex: 1
83
189
  },
190
+ // Bottom rhythm under the ⌘K palette trigger (the narrow-viewport search
191
+ // affordance), matching the inline field's own `marginBottom` so swapping
192
+ // affordances by width keeps the same gap above the nav list.
193
+ sidebarTriggerSlot: {
194
+ marginBottom: 12
195
+ },
84
196
  // Inline-search no-match text — sits where the nav list would, muted + padded.
85
197
  sidebarEmpty: {
86
198
  fontSize: 13,
@@ -343,46 +455,14 @@ var expandableStyles = reactNative.StyleSheet.create({
343
455
  var BASE_INDENT = 12;
344
456
  var NAV_ICON_SIZE = 14;
345
457
  var CHEVRON_ICON_SIZE = 12;
346
- var IS_WEB = reactNative.Platform.OS === "web";
347
- var FOCUS_RING_WIDTH = 2;
348
- var FOCUS_RING_OFFSET = 2;
349
- var HOVER_TRANSITION_MS = 150;
350
- var TINT_TRANSITION_MS = 160;
351
- function focusRingStyle(focused, ringColor) {
352
- if (!IS_WEB || !focused) return void 0;
353
- const ring = {
354
- outlineWidth: FOCUS_RING_WIDTH,
355
- outlineStyle: "solid",
356
- outlineColor: ringColor,
357
- outlineOffset: FOCUS_RING_OFFSET
358
- };
359
- return ring;
360
- }
361
- function webTransition(properties, durationMs, reducedMotion) {
362
- if (!IS_WEB) return void 0;
363
- const transition = {
364
- transitionProperty: properties,
365
- transitionDuration: reducedMotion ? "0ms" : `${durationMs}ms`
366
- };
367
- return transition;
368
- }
369
- function hoverTransitionStyle(reducedMotion) {
370
- return webTransition("color, background-color", HOVER_TRANSITION_MS, reducedMotion);
371
- }
372
- function tintTransitionStyle(reducedMotion) {
373
- return webTransition("opacity", TINT_TRANSITION_MS, reducedMotion);
374
- }
375
- function rotateTransitionStyle(reducedMotion) {
376
- return webTransition("transform", TINT_TRANSITION_MS, reducedMotion);
377
- }
378
458
  var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
379
459
  function getReducedMotionQuery() {
380
460
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
381
461
  return window.matchMedia(REDUCED_MOTION_QUERY);
382
462
  }
383
463
  function useReducedMotion() {
384
- const [reduced, setReduced] = React3.useState(() => getReducedMotionQuery()?.matches ?? false);
385
- React3.useEffect(() => {
464
+ const [reduced, setReduced] = React.useState(() => getReducedMotionQuery()?.matches ?? false);
465
+ React.useEffect(() => {
386
466
  const mql = getReducedMotionQuery();
387
467
  if (mql === void 0 || mql.addEventListener === void 0) return void 0;
388
468
  const onChange = () => setReduced(mql.matches);
@@ -428,9 +508,19 @@ function hasActiveDescendant(item, pathname) {
428
508
  if (isRouteActive(pathname, item.route)) return true;
429
509
  return (item.children ?? []).some((child) => hasActiveDescendant(child, pathname));
430
510
  }
431
- function TintOverlay({ opacity, color }) {
511
+ function TintOverlay({
512
+ opacity,
513
+ color,
514
+ testID
515
+ }) {
432
516
  const reducedMotion = useReducedMotion();
433
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [expandableStyles.tintOverlay, { backgroundColor: color, opacity }, tintTransitionStyle(reducedMotion)] });
517
+ return /* @__PURE__ */ jsxRuntime.jsx(
518
+ reactNative.View,
519
+ {
520
+ style: [expandableStyles.tintOverlay, { backgroundColor: color, opacity }, tintTransitionStyle(reducedMotion)],
521
+ testID
522
+ }
523
+ );
434
524
  }
435
525
  var NavExpandableItem = ({
436
526
  item,
@@ -440,32 +530,34 @@ var NavExpandableItem = ({
440
530
  expandHint,
441
531
  collapseHint,
442
532
  renderChevron,
533
+ activeRoute,
443
534
  depth = 0
444
535
  }) => {
445
- const [expanded, setExpanded] = React3.useState(() => hasActiveDescendant(item, pathname));
446
- const [focused, setFocused] = React3.useState(false);
447
- const [hovered, setHovered] = React3.useState(false);
536
+ const [expanded, setExpanded] = React.useState(() => hasActiveDescendant(item, pathname));
537
+ const [focused, setFocused] = React.useState(false);
538
+ const [hovered, setHovered] = React.useState(false);
448
539
  const { theme } = uiFeedback.useUi();
449
540
  const colors = theme.colors;
450
541
  const primaryColor = theme.palette.primary["500"];
451
- const toggle = React3.useCallback(() => setExpanded((v) => !v), []);
452
- const onHoverIn = React3.useCallback(() => setHovered(true), []);
453
- const onHoverOut = React3.useCallback(() => setHovered(false), []);
542
+ const toggle = React.useCallback(() => setExpanded((v) => !v), []);
543
+ const onHoverIn = React.useCallback(() => setHovered(true), []);
544
+ const onHoverOut = React.useCallback(() => setHovered(false), []);
454
545
  const indent = depth * BASE_INDENT;
455
546
  const hasChildren = Array.isArray(item.children) && item.children.length > 0;
456
- const isActive = isRouteActive(pathname, item.route);
547
+ const isActive = activeRoute !== void 0 ? item.route === activeRoute : isRouteActive(pathname, item.route, item.exact === true);
457
548
  const isSectionHeader = hasChildren && depth === 0;
458
- const activeAccentStyle = React3.useMemo(
549
+ const activeAccentStyle = React.useMemo(
459
550
  () => ({ borderLeftWidth: ACTIVE_ACCENT_WIDTH, borderLeftColor: primaryColor }),
460
551
  [primaryColor]
461
552
  );
462
- const paddingStyle = React3.useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
463
- const headerPaddingStyle = React3.useMemo(() => ({ paddingLeft: indent }), [indent]);
464
- const leafTintOpacity = isActive ? ACTIVE_TINT_OPACITY : hovered ? HOVER_TINT_OPACITY : 0;
465
- const headerTintOpacity = hovered ? HOVER_TINT_OPACITY : 0;
553
+ const paddingStyle = React.useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
554
+ const headerPaddingStyle = React.useMemo(() => ({ paddingLeft: indent }), [indent]);
555
+ const leafTintOpacity = rowTintOpacity(isActive, hovered);
556
+ const headerTintOpacity = rowTintOpacity(false, hovered);
557
+ const rowTestID = item.testID ?? item.key;
466
558
  if (!hasChildren)
467
559
  return /* @__PURE__ */ jsxRuntime.jsxs(
468
- reactNative.TouchableOpacity,
560
+ reactNative.Pressable,
469
561
  {
470
562
  accessibilityHint: navigateHint(item.label),
471
563
  accessibilityLabel: item.label,
@@ -477,14 +569,14 @@ var NavExpandableItem = ({
477
569
  isActive ? activeAccentStyle : void 0,
478
570
  focusRingStyle(focused, primaryColor)
479
571
  ],
480
- testID: item.testID ?? item.key,
572
+ testID: rowTestID,
481
573
  onBlur: () => setFocused(false),
482
574
  onFocus: () => setFocused(true),
483
575
  onPress: () => onNavigate(item.route),
484
576
  ...hoverHandlerProps(onHoverIn, onHoverOut),
485
577
  ...ariaCurrentProps(isActive),
486
578
  children: [
487
- /* @__PURE__ */ jsxRuntime.jsx(TintOverlay, { color: primaryColor, opacity: leafTintOpacity }),
579
+ /* @__PURE__ */ jsxRuntime.jsx(TintOverlay, { color: primaryColor, opacity: leafTintOpacity, testID: `${rowTestID}-tint` }),
488
580
  typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) }) : null,
489
581
  /* @__PURE__ */ jsxRuntime.jsx(
490
582
  reactNative.Text,
@@ -502,7 +594,7 @@ var NavExpandableItem = ({
502
594
  const chevronColor = colors.textSecondary;
503
595
  return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { children: [
504
596
  /* @__PURE__ */ jsxRuntime.jsxs(
505
- reactNative.TouchableOpacity,
597
+ reactNative.Pressable,
506
598
  {
507
599
  accessibilityHint: expanded ? collapseHint : expandHint,
508
600
  accessibilityLabel: item.label,
@@ -514,14 +606,14 @@ var NavExpandableItem = ({
514
606
  headerPaddingStyle,
515
607
  focusRingStyle(focused, primaryColor)
516
608
  ],
517
- testID: item.testID ?? item.key,
609
+ testID: rowTestID,
518
610
  onBlur: () => setFocused(false),
519
611
  onFocus: () => setFocused(true),
520
612
  onPress: toggle,
521
613
  ...hoverHandlerProps(onHoverIn, onHoverOut),
522
614
  ...ariaExpandedProps(expanded),
523
615
  children: [
524
- /* @__PURE__ */ jsxRuntime.jsx(TintOverlay, { color: primaryColor, opacity: headerTintOpacity }),
616
+ /* @__PURE__ */ jsxRuntime.jsx(TintOverlay, { color: primaryColor, opacity: headerTintOpacity, testID: `${rowTestID}-tint` }),
525
617
  typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(isSectionHeader ? colors.textSecondary : colors.text, NAV_ICON_SIZE) }) : null,
526
618
  /* @__PURE__ */ jsxRuntime.jsx(
527
619
  reactNative.Text,
@@ -540,6 +632,7 @@ var NavExpandableItem = ({
540
632
  /* @__PURE__ */ jsxRuntime.jsx(uiMotion.Collapse, { open: expanded, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.childrenContainer, children: item.children?.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
541
633
  NavExpandableItem,
542
634
  {
635
+ activeRoute,
543
636
  collapseHint,
544
637
  depth: depth + 1,
545
638
  expandHint,
@@ -553,10 +646,16 @@ var NavExpandableItem = ({
553
646
  )) }) })
554
647
  ] });
555
648
  };
649
+
650
+ // src/searchAffordance.ts
651
+ var DEFAULT_SEARCH_BREAKPOINT = 768;
652
+ function resolveSearchAffordance(viewport, breakpoint = DEFAULT_SEARCH_BREAKPOINT) {
653
+ return viewport >= breakpoint ? "inline" : "palette";
654
+ }
556
655
  var FIELD_MIN_HEIGHT = 38;
557
656
  var CLEAR_GLYPH = "\u2715";
558
657
  var CLEAR_MIN_TARGET = 28;
559
- var styles = reactNative.StyleSheet.create({
658
+ var styles2 = reactNative.StyleSheet.create({
560
659
  wrap: {
561
660
  flexDirection: "row",
562
661
  alignItems: "center",
@@ -591,13 +690,13 @@ var SidebarSearch = ({
591
690
  const { theme } = uiFeedback.useUi();
592
691
  const colors = theme.colors;
593
692
  const primary = theme.palette.primary["500"];
594
- const [focused, setFocused] = React3.useState(false);
693
+ const [focused, setFocused] = React.useState(false);
595
694
  const hasQuery = value.length > 0;
596
695
  return /* @__PURE__ */ jsxRuntime.jsxs(
597
696
  reactNative.View,
598
697
  {
599
698
  style: [
600
- styles.wrap,
699
+ styles2.wrap,
601
700
  { backgroundColor: colors.surface, borderColor: focused ? primary : colors.border },
602
701
  focusRingStyle(focused, primary)
603
702
  ],
@@ -609,7 +708,7 @@ var SidebarSearch = ({
609
708
  accessibilityLabel: labels.placeholder,
610
709
  placeholder: labels.placeholder,
611
710
  placeholderTextColor: colors.textSecondary,
612
- style: [styles.input, { color: colors.text }],
711
+ style: [styles2.input, { color: colors.text }],
613
712
  testID,
614
713
  value,
615
714
  onBlur: () => setFocused(false),
@@ -623,10 +722,10 @@ var SidebarSearch = ({
623
722
  accessibilityHint: labels.clearHint,
624
723
  accessibilityLabel: labels.clearLabel,
625
724
  accessibilityRole: "button",
626
- style: styles.clear,
725
+ style: styles2.clear,
627
726
  testID: `${testID}-clear`,
628
727
  onPress: onClear,
629
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.clearGlyph, { color: colors.textSecondary }], children: CLEAR_GLYPH })
728
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.clearGlyph, { color: colors.textSecondary }], children: CLEAR_GLYPH })
630
729
  }
631
730
  ) : null
632
731
  ]
@@ -639,11 +738,12 @@ function isBareText(node) {
639
738
  function renderTextSlot(node, style) {
640
739
  if (node === void 0 || node === null) return node;
641
740
  if (isBareText(node)) return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style, children: node });
642
- return React3__default.default.Children.map(
741
+ return React__default.default.Children.map(
643
742
  node,
644
743
  (child) => isBareText(child) ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style, children: child }) : child
645
744
  );
646
745
  }
746
+ var DEFAULT_TRIGGER_TEST_ID = "sidebar-command-palette-trigger";
647
747
  var Sidebar = ({
648
748
  items,
649
749
  pathname,
@@ -656,6 +756,7 @@ var Sidebar = ({
656
756
  renderChevron,
657
757
  enableInlineSearch = false,
658
758
  search,
759
+ paletteTrigger,
659
760
  searchQuery,
660
761
  onSearchChange,
661
762
  header,
@@ -664,20 +765,25 @@ var Sidebar = ({
664
765
  }) => {
665
766
  const { theme } = uiFeedback.useUi();
666
767
  const colors = theme.colors;
667
- const [internalQuery, setInternalQuery] = React3.useState("");
768
+ const { width } = reactNative.useWindowDimensions();
769
+ const [internalQuery, setInternalQuery] = React.useState("");
668
770
  const controlled = searchQuery !== void 0;
669
771
  const query = controlled ? searchQuery : internalQuery;
670
- const setQuery = React3.useCallback(
772
+ const setQuery = React.useCallback(
671
773
  (next) => {
672
774
  if (!controlled) setInternalQuery(next);
673
775
  onSearchChange?.(next);
674
776
  },
675
777
  [controlled, onSearchChange]
676
778
  );
677
- const clearQuery = React3.useCallback(() => setQuery(""), [setQuery]);
678
- const searchOn = enableInlineSearch && search !== void 0;
679
- const visibleItems = searchOn ? filterNavItems(items, query) : items;
680
- const showEmpty = searchOn && isFilterActive(query) && visibleItems.length === 0;
779
+ const clearQuery = React.useCallback(() => setQuery(""), [setQuery]);
780
+ const affordance = resolveSearchAffordance(width);
781
+ const isInlineAffordance = affordance === "inline";
782
+ const showInlineSearch = enableInlineSearch && search !== void 0 && isInlineAffordance;
783
+ const showPaletteTrigger = paletteTrigger !== void 0 && !isInlineAffordance;
784
+ const visibleItems = showInlineSearch ? filterNavItems(items, query) : items;
785
+ const showEmpty = showInlineSearch && isFilterActive(query) && visibleItems.length === 0;
786
+ const activeRoute = resolveActiveRoute(items, pathname);
681
787
  return /* @__PURE__ */ jsxRuntime.jsxs(
682
788
  reactNative.View,
683
789
  {
@@ -691,7 +797,7 @@ var Sidebar = ({
691
797
  ],
692
798
  children: [
693
799
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { accessibilityRole: "header", style: [navStyles.sidebarTitle, { color: colors.text }], children: title }),
694
- searchOn && search !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
800
+ showInlineSearch && search !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
695
801
  SidebarSearch,
696
802
  {
697
803
  labels: search,
@@ -701,10 +807,21 @@ var Sidebar = ({
701
807
  onClear: clearQuery
702
808
  }
703
809
  ) : null,
810
+ showPaletteTrigger && paletteTrigger !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.sidebarTriggerSlot, children: /* @__PURE__ */ jsxRuntime.jsx(
811
+ CommandPaletteTrigger,
812
+ {
813
+ hint: paletteTrigger.hint,
814
+ label: paletteTrigger.label,
815
+ shortcut: paletteTrigger.shortcut,
816
+ testID: paletteTrigger.testID ?? DEFAULT_TRIGGER_TEST_ID,
817
+ onPress: paletteTrigger.onOpen
818
+ }
819
+ ) }) : null,
704
820
  renderTextSlot(header, { color: colors.text }),
705
821
  showEmpty && search !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.sidebarEmpty, { color: colors.textSecondary }], testID: "sidebar-search-empty", children: search.emptyText }) : visibleItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
706
822
  NavExpandableItem,
707
823
  {
824
+ activeRoute,
708
825
  collapseHint,
709
826
  expandHint,
710
827
  item,
@@ -722,12 +839,6 @@ var Sidebar = ({
722
839
  );
723
840
  };
724
841
 
725
- // src/searchAffordance.ts
726
- var DEFAULT_SEARCH_BREAKPOINT = 768;
727
- function resolveSearchAffordance(viewport, breakpoint = DEFAULT_SEARCH_BREAKPOINT) {
728
- return viewport >= breakpoint ? "inline" : "palette";
729
- }
730
-
731
842
  // src/constants.ts
732
843
  var NAV_TEST_IDS = {
733
844
  /** displayName line in the rich account header (tappable when `onAccount` set). */
@@ -775,7 +886,7 @@ var FocusableTouchable = ({
775
886
  testID,
776
887
  children
777
888
  }) => {
778
- const [focused, setFocused] = React3.useState(false);
889
+ const [focused, setFocused] = React.useState(false);
779
890
  return /* @__PURE__ */ jsxRuntime.jsx(
780
891
  reactNative.Pressable,
781
892
  {
@@ -928,8 +1039,8 @@ var NavBarLink = ({
928
1039
  navigateHint,
929
1040
  onPress
930
1041
  }) => {
931
- const [hovered, setHovered] = React3.useState(false);
932
- const [focused, setFocused] = React3.useState(false);
1042
+ const [hovered, setHovered] = React.useState(false);
1043
+ const [focused, setFocused] = React.useState(false);
933
1044
  const isHovered = hovered && !isActive;
934
1045
  const textColor = isActive ? TEXT_ON_PRIMARY2 : isHovered ? colors.hoverText : colors.rest;
935
1046
  const pillStyle = isActive ? { backgroundColor: colors.activeBg } : isHovered ? { backgroundColor: colors.hoverBg } : void 0;
@@ -973,12 +1084,12 @@ var NavOverflowMenu = ({
973
1084
  testID,
974
1085
  variant
975
1086
  }) => {
976
- const options = React3.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
977
- const activeRoute = React3.useMemo(
978
- () => items.find((item) => isRouteActive(pathname, item.route))?.route ?? NO_ACTIVE_ROUTE,
1087
+ const options = React.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
1088
+ const activeRoute = React.useMemo(
1089
+ () => resolveActiveRoute(items, pathname) ?? NO_ACTIVE_ROUTE,
979
1090
  [items, pathname]
980
1091
  );
981
- const optionTestID = React3.useMemo(() => {
1092
+ const optionTestID = React.useMemo(() => {
982
1093
  const byRoute = new Map(items.map((item) => [item.route, item.testID ?? item.key]));
983
1094
  return (route) => byRoute.get(route) ?? `${testID}-option-${route}`;
984
1095
  }, [items, testID]);
@@ -1035,13 +1146,13 @@ function resizeWidthBuffer(previous, count) {
1035
1146
  return next;
1036
1147
  }
1037
1148
  function useNavOverflow(itemCount, gap) {
1038
- const [availableWidth, setAvailableWidthState] = React3.useState(0);
1039
- const [moreWidth, setMoreWidthState] = React3.useState(0);
1040
- const [itemWidths, setItemWidths] = React3.useState(() => new Array(itemCount).fill(0));
1041
- React3.useEffect(() => {
1149
+ const [availableWidth, setAvailableWidthState] = React.useState(0);
1150
+ const [moreWidth, setMoreWidthState] = React.useState(0);
1151
+ const [itemWidths, setItemWidths] = React.useState(() => new Array(itemCount).fill(0));
1152
+ React.useEffect(() => {
1042
1153
  setItemWidths((previous) => previous.length === itemCount ? previous : resizeWidthBuffer(previous, itemCount));
1043
1154
  }, [itemCount]);
1044
- const setItemWidth = React3.useCallback((index, width) => {
1155
+ const setItemWidth = React.useCallback((index, width) => {
1045
1156
  setItemWidths((previous) => {
1046
1157
  if (index < 0 || index >= previous.length || previous[index] === width) return previous;
1047
1158
  const next = previous.slice();
@@ -1049,15 +1160,15 @@ function useNavOverflow(itemCount, gap) {
1049
1160
  return next;
1050
1161
  });
1051
1162
  }, []);
1052
- const setAvailableWidth = React3.useCallback(
1163
+ const setAvailableWidth = React.useCallback(
1053
1164
  (width) => setAvailableWidthState((previous) => previous === width ? previous : width),
1054
1165
  []
1055
1166
  );
1056
- const setMoreWidth = React3.useCallback(
1167
+ const setMoreWidth = React.useCallback(
1057
1168
  (width) => setMoreWidthState((previous) => previous === width ? previous : width),
1058
1169
  []
1059
1170
  );
1060
- const visibleCount = React3.useMemo(
1171
+ const visibleCount = React.useMemo(
1061
1172
  () => computeVisibleCount({ availableWidth, itemWidths, moreWidth, gap }),
1062
1173
  [availableWidth, itemWidths, moreWidth, gap]
1063
1174
  );
@@ -1091,24 +1202,25 @@ var NavBarInner = ({
1091
1202
  const primaryColor = theme.palette.primary["500"];
1092
1203
  const { width } = reactNative.useWindowDimensions();
1093
1204
  const reducedMotion = useReducedMotion();
1094
- const [open, setOpen] = React3.useState(false);
1095
- const [toggleFocused, setToggleFocused] = React3.useState(false);
1205
+ const [open, setOpen] = React.useState(false);
1206
+ const [toggleFocused, setToggleFocused] = React.useState(false);
1096
1207
  const { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth } = useNavOverflow(items.length, NAV_LINK_GAP);
1097
1208
  const collapsed = width < collapseBelow;
1098
1209
  const showLinks = !collapsed || open;
1099
- const innerCapStyle = React3.useMemo(
1210
+ const innerCapStyle = React.useMemo(
1100
1211
  () => contentMaxWidth === void 0 ? null : { maxWidth: contentMaxWidth, width: "100%", alignSelf: "center" },
1101
1212
  [contentMaxWidth]
1102
1213
  );
1103
- const toggleMenu = React3.useCallback(() => setOpen((v) => !v), []);
1104
- const handlePress = React3.useCallback(
1214
+ const toggleMenu = React.useCallback(() => setOpen((v) => !v), []);
1215
+ const handlePress = React.useCallback(
1105
1216
  (route) => {
1106
1217
  setOpen(false);
1107
1218
  onNavigate(route);
1108
1219
  },
1109
1220
  [onNavigate]
1110
1221
  );
1111
- const linkColors = React3.useMemo(
1222
+ const activeRoute = resolveActiveRoute(items, pathname);
1223
+ const linkColors = React.useMemo(
1112
1224
  () => ({
1113
1225
  rest: colors.textSecondary,
1114
1226
  hoverText: colors.text,
@@ -1155,7 +1267,7 @@ var NavBarInner = ({
1155
1267
  collapsed,
1156
1268
  colors: linkColors,
1157
1269
  iconSize: NAV_ICON_SIZE,
1158
- isActive: isRouteActive(pathname, item.route),
1270
+ isActive: item.route === activeRoute,
1159
1271
  item,
1160
1272
  navigateHint,
1161
1273
  reducedMotion,
@@ -1163,7 +1275,7 @@ var NavBarInner = ({
1163
1275
  }
1164
1276
  );
1165
1277
  if (collapsed) {
1166
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { nativeID: LINKS_REGION_ID, style: navBarStyles.linksStacked, testID: NAV_TEST_IDS.navBarLinks, children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(React3__default.default.Fragment, { children: renderLink(item) }, item.key)) });
1278
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { nativeID: LINKS_REGION_ID, style: navBarStyles.linksStacked, testID: NAV_TEST_IDS.navBarLinks, children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children: renderLink(item) }, item.key)) });
1167
1279
  }
1168
1280
  const visibleItems = items.slice(0, visibleCount);
1169
1281
  const overflowItems = items.slice(visibleCount);
@@ -1229,7 +1341,7 @@ var CARD_BORDER_WIDTH = 1;
1229
1341
  var CARD_TITLE_FONT_SIZE = 16;
1230
1342
  var CARD_MESSAGE_FONT_SIZE = 14;
1231
1343
  var CARD_TITLE_MARGIN_BOTTOM = 8;
1232
- var styles2 = reactNative.StyleSheet.create({
1344
+ var styles3 = reactNative.StyleSheet.create({
1233
1345
  centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
1234
1346
  card: {
1235
1347
  padding: CARD_PADDING,
@@ -1246,9 +1358,9 @@ function MessageCard({
1246
1358
  }) {
1247
1359
  const { theme } = uiFeedback.useUi();
1248
1360
  const colors = theme.colors;
1249
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles2.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
1250
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.cardTitle, { color: accentColor }], children: message.titleText }),
1251
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.cardMessage, { color: colors.textSecondary }], children: message.messageText })
1361
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
1362
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.cardTitle, { color: accentColor }], children: message.titleText }),
1363
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.cardMessage, { color: colors.textSecondary }], children: message.messageText })
1252
1364
  ] });
1253
1365
  }
1254
1366
  function useContentBody(state, children, testID) {
@@ -1260,7 +1372,7 @@ function useContentBody(state, children, testID) {
1260
1372
  if (state?.error)
1261
1373
  return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
1262
1374
  if (state?.loading === true)
1263
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.centerFill, testID: `${testID}${APP_SHELL_SUFFIX.loading}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
1375
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.centerFill, testID: `${testID}${APP_SHELL_SUFFIX.loading}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
1264
1376
  return children;
1265
1377
  }
1266
1378
  var SCRIM_COLOR = "rgba(0, 0, 0, 0.5)";
@@ -1284,7 +1396,7 @@ var DEFAULT_DRAWER_LABELS = {
1284
1396
  closeLabel: "Close menu",
1285
1397
  closeHint: "Close the navigation menu"
1286
1398
  };
1287
- var styles3 = reactNative.StyleSheet.create({
1399
+ var styles4 = reactNative.StyleSheet.create({
1288
1400
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: DRAWER_Z_INDEX },
1289
1401
  // The scrim's tap-to-close hit area is anchored to the RIGHT of the panel
1290
1402
  // (`left: DRAWER_WIDTH`), so it NEVER overlaps the panel: nav-item taps always
@@ -1319,10 +1431,10 @@ var MenuToggle = ({ label, hint, onOpen, testID }) => {
1319
1431
  accessibilityHint: hint,
1320
1432
  accessibilityLabel: label,
1321
1433
  ringColor: theme.palette.primary["500"],
1322
- style: styles3.menuToggle,
1434
+ style: styles4.menuToggle,
1323
1435
  testID,
1324
1436
  onPress: onOpen,
1325
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1437
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1326
1438
  }
1327
1439
  );
1328
1440
  };
@@ -1334,8 +1446,8 @@ var MobileDrawer = ({
1334
1446
  scrimTestID
1335
1447
  }) => {
1336
1448
  const { theme } = uiFeedback.useUi();
1337
- const panelRef = React3.useRef(null);
1338
- React3.useEffect(() => {
1449
+ const panelRef = React.useRef(null);
1450
+ React.useEffect(() => {
1339
1451
  const node = panelRef.current;
1340
1452
  if (node === null || typeof node.addEventListener !== "function") return void 0;
1341
1453
  const handleClick = (event) => {
@@ -1348,14 +1460,14 @@ var MobileDrawer = ({
1348
1460
  node.addEventListener("click", handleClick);
1349
1461
  return () => node.removeEventListener("click", handleClick);
1350
1462
  }, [onClose]);
1351
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.overlay, children: [
1463
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.overlay, children: [
1352
1464
  /* @__PURE__ */ jsxRuntime.jsx(
1353
1465
  reactNative.Pressable,
1354
1466
  {
1355
1467
  accessibilityHint: labels.closeHint,
1356
1468
  accessibilityLabel: labels.closeLabel,
1357
1469
  accessibilityRole: "button",
1358
- style: styles3.scrim,
1470
+ style: styles4.scrim,
1359
1471
  testID: scrimTestID,
1360
1472
  onPress: onClose
1361
1473
  }
@@ -1366,7 +1478,7 @@ var MobileDrawer = ({
1366
1478
  ref: panelRef,
1367
1479
  "aria-modal": true,
1368
1480
  role: "dialog",
1369
- style: [styles3.panel, { backgroundColor: theme.colors.surface }],
1481
+ style: [styles4.panel, { backgroundColor: theme.colors.surface }],
1370
1482
  testID: drawerTestID,
1371
1483
  children: renderTextSlot(sidebar, { color: theme.colors.text })
1372
1484
  }
@@ -1397,7 +1509,7 @@ function useContentMaxWidth(width) {
1397
1509
  return resolveContentMaxWidth(width, viewport);
1398
1510
  }
1399
1511
  var DEFAULT_CONTENT_PADDING = 24;
1400
- var styles4 = reactNative.StyleSheet.create({
1512
+ var styles5 = reactNative.StyleSheet.create({
1401
1513
  root: { flex: 1 },
1402
1514
  centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
1403
1515
  scroll: { flex: 1 },
@@ -1442,32 +1554,32 @@ var AppShell = ({
1442
1554
  range: railRange
1443
1555
  });
1444
1556
  const useDrawer = railMode === "drawer";
1445
- const [drawerOpen, setDrawerOpen] = React3.useState(false);
1446
- const openDrawer = React3.useCallback(() => setDrawerOpen(true), []);
1447
- const closeDrawer = React3.useCallback(() => setDrawerOpen(false), []);
1448
- React3.useEffect(() => {
1557
+ const [drawerOpen, setDrawerOpen] = React.useState(false);
1558
+ const openDrawer = React.useCallback(() => setDrawerOpen(true), []);
1559
+ const closeDrawer = React.useCallback(() => setDrawerOpen(false), []);
1560
+ React.useEffect(() => {
1449
1561
  if (!useDrawer && drawerOpen) setDrawerOpen(false);
1450
1562
  }, [useDrawer, drawerOpen]);
1451
1563
  const drawerLabels = { ...DEFAULT_DRAWER_LABELS, ...mobileMenu };
1452
1564
  const isUnauthenticated = gate !== void 0 && !gate.pending && !gate.authenticated;
1453
- React3.useEffect(() => {
1565
+ React.useEffect(() => {
1454
1566
  if (isUnauthenticated && gate !== void 0) gate.onRedirect();
1455
1567
  }, [isUnauthenticated, gate]);
1456
1568
  if (gate?.pending === true)
1457
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles4.root, styles4.centerFill, { backgroundColor: theme.colors.background }], testID: `${testID}${APP_SHELL_SUFFIX.pending}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
1569
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles5.root, styles5.centerFill, { backgroundColor: theme.colors.background }], testID: `${testID}${APP_SHELL_SUFFIX.pending}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
1458
1570
  if (isUnauthenticated) return null;
1459
- const columnStyle = maxWidth === "full" ? styles4.columnFull : [styles4.columnCapped, { maxWidth }];
1460
- const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles4.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1571
+ const columnStyle = maxWidth === "full" ? styles5.columnFull : [styles5.columnCapped, { maxWidth }];
1572
+ const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles5.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1461
1573
  const scroller = /* @__PURE__ */ jsxRuntime.jsx(
1462
1574
  reactNative.ScrollView,
1463
1575
  {
1464
- contentContainerStyle: [styles4.scrollContent, { padding: contentPadding }],
1465
- style: styles4.scroll,
1576
+ contentContainerStyle: [styles5.scrollContent, { padding: contentPadding }],
1577
+ style: styles5.scroll,
1466
1578
  testID: `${testID}${APP_SHELL_SUFFIX.content}`,
1467
1579
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: columnStyle, children: body })
1468
1580
  }
1469
1581
  );
1470
- const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1582
+ const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1471
1583
  /* @__PURE__ */ jsxRuntime.jsx(
1472
1584
  MenuToggle,
1473
1585
  {
@@ -1477,14 +1589,14 @@ var AppShell = ({
1477
1589
  onOpen: openDrawer
1478
1590
  }
1479
1591
  ),
1480
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles4.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
1592
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles5.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
1481
1593
  ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: renderTextSlot(header, { color: theme.colors.text }) });
1482
1594
  const railRegion = railMode === "full" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.sidebar}`, children: renderTextSlot(sidebar, { color: theme.colors.text }) }) : railMode === "collapsed" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.collapsedSidebar}`, children: renderTextSlot(collapsedSidebar, { color: theme.colors.text }) }) : null;
1483
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles4.root, { backgroundColor: theme.colors.background }], testID, children: [
1595
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles5.root, { backgroundColor: theme.colors.background }], testID, children: [
1484
1596
  headerRegion,
1485
1597
  nav !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.nav}`, children: navInnerStyle !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navInnerStyle, children: renderTextSlot(nav, { color: theme.colors.text }) }) : renderTextSlot(nav, { color: theme.colors.text }) }) : null,
1486
1598
  banner !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: renderTextSlot(banner, { color: theme.colors.text }) }) : null,
1487
- railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.bodyRow, children: [
1599
+ railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.bodyRow, children: [
1488
1600
  railRegion,
1489
1601
  scroller
1490
1602
  ] }) : scroller,
@@ -1517,6 +1629,7 @@ var CollapsedRail = ({
1517
1629
  const { theme } = uiFeedback.useUi();
1518
1630
  const colors = theme.colors;
1519
1631
  const primaryColor = theme.palette.primary["500"];
1632
+ const activeRoute = resolveActiveRoute(items, pathname);
1520
1633
  return /* @__PURE__ */ jsxRuntime.jsxs(
1521
1634
  reactNative.View,
1522
1635
  {
@@ -1531,7 +1644,7 @@ var CollapsedRail = ({
1531
1644
  children: [
1532
1645
  header !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: renderTextSlot(header, { color: colors.text }) }) : null,
1533
1646
  items.map((item) => {
1534
- const active = isRouteActive(pathname, item.route);
1647
+ const active = item.route === activeRoute;
1535
1648
  const iconColor = active ? primaryColor : colors.textSecondary;
1536
1649
  return /* @__PURE__ */ jsxRuntime.jsx(
1537
1650
  FocusableTouchable,
@@ -1607,6 +1720,7 @@ var NavShell = ({
1607
1720
  header: sideRail.header,
1608
1721
  items: sideRail.items,
1609
1722
  navigateHint,
1723
+ paletteTrigger: sideRail.paletteTrigger,
1610
1724
  pathname,
1611
1725
  regionLabel,
1612
1726
  renderChevron: sideRail.renderChevron,
@@ -1658,6 +1772,7 @@ var PillNav = ({
1658
1772
  const colors = theme.colors;
1659
1773
  const primaryColor = theme.palette.primary["500"];
1660
1774
  if (items.length < minItems) return null;
1775
+ const activeRoute = resolveActiveRoute(items, pathname);
1661
1776
  return /* @__PURE__ */ jsxRuntime.jsx(
1662
1777
  reactNative.View,
1663
1778
  {
@@ -1666,7 +1781,7 @@ var PillNav = ({
1666
1781
  role: "navigation",
1667
1782
  style: [pillNavStyles.container, containerStyle],
1668
1783
  children: items.map((item) => {
1669
- const active = isRouteActive(pathname, item.route);
1784
+ const active = item.route === activeRoute;
1670
1785
  const textColor = active ? TEXT_ON_PRIMARY4 : colors.textSecondary;
1671
1786
  const pillStyle = active ? { backgroundColor: primaryColor } : { backgroundColor: colors.surfaceElevated };
1672
1787
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -1706,7 +1821,7 @@ var DarkModeControl = ({
1706
1821
  const primaryColor = theme.palette.primary["500"];
1707
1822
  const currentIndex = options.findIndex((option) => option.value === value);
1708
1823
  const current = currentIndex >= 0 ? options[currentIndex] : options[0];
1709
- const advance = React3.useCallback(() => {
1824
+ const advance = React.useCallback(() => {
1710
1825
  if (options.length === 0) return;
1711
1826
  const from = currentIndex >= 0 ? currentIndex : 0;
1712
1827
  const next = options[(from + 1) % options.length];
@@ -1782,7 +1897,7 @@ var KEY_DOWN = "ArrowDown";
1782
1897
  var KEY_UP = "ArrowUp";
1783
1898
  var KEY_ENTER = "Enter";
1784
1899
  var KEY_ESCAPE = "Escape";
1785
- var styles5 = reactNative.StyleSheet.create({
1900
+ var styles6 = reactNative.StyleSheet.create({
1786
1901
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: OVERLAY_Z_INDEX, alignItems: "center" },
1787
1902
  scrim: { ...reactNative.StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR2 },
1788
1903
  panel: {
@@ -1811,14 +1926,14 @@ var PaletteRow = ({ item, selected, onChoose }) => {
1811
1926
  accessibilityLabel: item.label,
1812
1927
  accessibilityRole: "button",
1813
1928
  accessibilityState: { selected },
1814
- style: [styles5.row, selected ? { backgroundColor: colors.border } : void 0],
1929
+ style: [styles6.row, selected ? { backgroundColor: colors.border } : void 0],
1815
1930
  testID: item.testID ?? item.key,
1816
1931
  onPress: () => onChoose(item),
1817
1932
  children: [
1818
1933
  typeof item.renderIcon === "function" ? item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) : null,
1819
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.rowText, children: [
1820
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.rowLabel, { color: colors.text }], children: item.label }),
1821
- item.hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
1934
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.rowText, children: [
1935
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.rowLabel, { color: colors.text }], children: item.label }),
1936
+ item.hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
1822
1937
  ] })
1823
1938
  ]
1824
1939
  }
@@ -1827,27 +1942,27 @@ var PaletteRow = ({ item, selected, onChoose }) => {
1827
1942
  var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1828
1943
  const { theme } = uiFeedback.useUi();
1829
1944
  const colors = theme.colors;
1830
- const [query, setQuery] = React3.useState("");
1831
- const [selected, setSelected] = React3.useState(0);
1832
- const inputRef = React3.useRef(null);
1833
- const results = React3.useMemo(() => filterCommands(items, query), [items, query]);
1834
- React3.useEffect(() => {
1945
+ const [query, setQuery] = React.useState("");
1946
+ const [selected, setSelected] = React.useState(0);
1947
+ const inputRef = React.useRef(null);
1948
+ const results = React.useMemo(() => filterCommands(items, query), [items, query]);
1949
+ React.useEffect(() => {
1835
1950
  if (!open) return;
1836
1951
  setQuery("");
1837
1952
  setSelected(0);
1838
1953
  inputRef.current?.focus();
1839
1954
  }, [open]);
1840
- React3.useEffect(() => {
1955
+ React.useEffect(() => {
1841
1956
  setSelected((prev) => Math.min(prev, Math.max(0, results.length - 1)));
1842
1957
  }, [results.length]);
1843
- const choose = React3.useCallback(
1958
+ const choose = React.useCallback(
1844
1959
  (item) => {
1845
1960
  item.onSelect();
1846
1961
  onClose();
1847
1962
  },
1848
1963
  [onClose]
1849
1964
  );
1850
- const onKeyPress = React3.useCallback(
1965
+ const onKeyPress = React.useCallback(
1851
1966
  (event) => {
1852
1967
  const key = event.nativeEvent.key;
1853
1968
  if (key === KEY_DOWN) setSelected((i) => Math.min(i + 1, results.length - 1));
@@ -1858,14 +1973,14 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1858
1973
  [results, selected, choose, onClose]
1859
1974
  );
1860
1975
  if (!open) return null;
1861
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.overlay, children: [
1976
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.overlay, children: [
1862
1977
  /* @__PURE__ */ jsxRuntime.jsx(
1863
1978
  reactNative.Pressable,
1864
1979
  {
1865
1980
  accessibilityHint: labels.closeHint,
1866
1981
  accessibilityLabel: labels.closeLabel,
1867
1982
  accessibilityRole: "button",
1868
- style: styles5.scrim,
1983
+ style: styles6.scrim,
1869
1984
  testID: `${testID ?? "command-palette"}-scrim`,
1870
1985
  onPress: onClose
1871
1986
  }
@@ -1876,7 +1991,7 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1876
1991
  "aria-modal": true,
1877
1992
  "aria-label": labels.regionLabel,
1878
1993
  role: "dialog",
1879
- style: [styles5.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
1994
+ style: [styles6.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
1880
1995
  testID: testID ?? "command-palette",
1881
1996
  children: [
1882
1997
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1886,71 +2001,22 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1886
2001
  accessibilityLabel: labels.placeholder,
1887
2002
  placeholder: labels.placeholder,
1888
2003
  placeholderTextColor: colors.textSecondary,
1889
- style: [styles5.input, { color: colors.text, borderBottomColor: colors.border }],
2004
+ style: [styles6.input, { color: colors.text, borderBottomColor: colors.border }],
1890
2005
  testID: `${testID ?? "command-palette"}-input`,
1891
2006
  value: query,
1892
2007
  onChangeText: setQuery,
1893
2008
  onKeyPress
1894
2009
  }
1895
2010
  ),
1896
- results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.empty, { color: colors.textSecondary }], children: labels.emptyText }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { keyboardShouldPersistTaps: "handled", style: styles5.list, children: results.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(PaletteRow, { item, selected: index === selected, onChoose: choose }, item.key)) })
2011
+ results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.empty, { color: colors.textSecondary }], children: labels.emptyText }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { keyboardShouldPersistTaps: "handled", style: styles6.list, children: results.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(PaletteRow, { item, selected: index === selected, onChoose: choose }, item.key)) })
1897
2012
  ]
1898
2013
  }
1899
2014
  )
1900
2015
  ] });
1901
2016
  };
1902
- var MIN_TARGET = 36;
1903
- var styles6 = reactNative.StyleSheet.create({
1904
- trigger: {
1905
- flexDirection: "row",
1906
- alignItems: "center",
1907
- justifyContent: "space-between",
1908
- columnGap: 8,
1909
- paddingHorizontal: 12,
1910
- minHeight: MIN_TARGET,
1911
- borderWidth: 1,
1912
- borderRadius: 8
1913
- },
1914
- label: { fontSize: 14 },
1915
- badge: { paddingHorizontal: 6, paddingVertical: 2, borderRadius: 4, borderWidth: 1 },
1916
- badgeText: { fontSize: 11, fontWeight: "700" }
1917
- });
1918
- var CommandPaletteTrigger = ({
1919
- label,
1920
- hint,
1921
- shortcut,
1922
- onPress,
1923
- testID
1924
- }) => {
1925
- const { theme } = uiFeedback.useUi();
1926
- const colors = theme.colors;
1927
- const primary = theme.palette.primary["500"];
1928
- const [focused, setFocused] = React3__default.default.useState(false);
1929
- return /* @__PURE__ */ jsxRuntime.jsxs(
1930
- reactNative.Pressable,
1931
- {
1932
- accessibilityHint: hint,
1933
- accessibilityLabel: label,
1934
- accessibilityRole: "button",
1935
- style: [
1936
- styles6.trigger,
1937
- { backgroundColor: colors.surface, borderColor: colors.border },
1938
- focusRingStyle(focused, primary)
1939
- ],
1940
- testID,
1941
- onBlur: () => setFocused(false),
1942
- onFocus: () => setFocused(true),
1943
- onPress,
1944
- children: [
1945
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.label, { color: colors.textSecondary }], children: label }),
1946
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles6.badge, { borderColor: colors.border }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.badgeText, { color: colors.textSecondary }], children: shortcut }) })
1947
- ]
1948
- }
1949
- );
1950
- };
1951
2017
  var LAUNCH_KEY = "k";
1952
2018
  function useCommandPaletteHotkey(onOpen, enabled = true) {
1953
- React3.useEffect(() => {
2019
+ React.useEffect(() => {
1954
2020
  if (!enabled || typeof document === "undefined") return void 0;
1955
2021
  const handler = (event) => {
1956
2022
  const isLauncher = event.key.toLowerCase() === LAUNCH_KEY && (event.metaKey || event.ctrlKey);
@@ -2011,6 +2077,7 @@ exports.isFilterActive = isFilterActive;
2011
2077
  exports.isRouteActive = isRouteActive;
2012
2078
  exports.navStyles = navStyles;
2013
2079
  exports.pillNavStyles = pillNavStyles;
2080
+ exports.resolveActiveRoute = resolveActiveRoute;
2014
2081
  exports.resolveContentMaxWidth = resolveContentMaxWidth;
2015
2082
  exports.resolveRailMode = resolveRailMode;
2016
2083
  exports.resolveSearchAffordance = resolveSearchAffordance;