@dloizides/ui-nav 1.16.1 → 1.17.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) {
@@ -56,6 +137,10 @@ var ACTIVE_ACCENT_WIDTH = 3;
56
137
  var NAV_ROW_RADIUS = 8;
57
138
  var ACTIVE_TINT_OPACITY = 0.14;
58
139
  var HOVER_TINT_OPACITY = 0.06;
140
+ function rowTintOpacity(isActive, hovered) {
141
+ if (isActive) return ACTIVE_TINT_OPACITY;
142
+ return hovered ? HOVER_TINT_OPACITY : 0;
143
+ }
59
144
  var NAV_LINK_GAP = 4;
60
145
  var navStyles = reactNative.StyleSheet.create({
61
146
  // --- Sidebar ---
@@ -81,6 +166,12 @@ var navStyles = reactNative.StyleSheet.create({
81
166
  sidebarSpacer: {
82
167
  flex: 1
83
168
  },
169
+ // Bottom rhythm under the ⌘K palette trigger (the narrow-viewport search
170
+ // affordance), matching the inline field's own `marginBottom` so swapping
171
+ // affordances by width keeps the same gap above the nav list.
172
+ sidebarTriggerSlot: {
173
+ marginBottom: 12
174
+ },
84
175
  // Inline-search no-match text — sits where the nav list would, muted + padded.
85
176
  sidebarEmpty: {
86
177
  fontSize: 13,
@@ -343,46 +434,14 @@ var expandableStyles = reactNative.StyleSheet.create({
343
434
  var BASE_INDENT = 12;
344
435
  var NAV_ICON_SIZE = 14;
345
436
  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
437
  var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
379
438
  function getReducedMotionQuery() {
380
439
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
381
440
  return window.matchMedia(REDUCED_MOTION_QUERY);
382
441
  }
383
442
  function useReducedMotion() {
384
- const [reduced, setReduced] = React3.useState(() => getReducedMotionQuery()?.matches ?? false);
385
- React3.useEffect(() => {
443
+ const [reduced, setReduced] = React.useState(() => getReducedMotionQuery()?.matches ?? false);
444
+ React.useEffect(() => {
386
445
  const mql = getReducedMotionQuery();
387
446
  if (mql === void 0 || mql.addEventListener === void 0) return void 0;
388
447
  const onChange = () => setReduced(mql.matches);
@@ -428,9 +487,19 @@ function hasActiveDescendant(item, pathname) {
428
487
  if (isRouteActive(pathname, item.route)) return true;
429
488
  return (item.children ?? []).some((child) => hasActiveDescendant(child, pathname));
430
489
  }
431
- function TintOverlay({ opacity, color }) {
490
+ function TintOverlay({
491
+ opacity,
492
+ color,
493
+ testID
494
+ }) {
432
495
  const reducedMotion = useReducedMotion();
433
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [expandableStyles.tintOverlay, { backgroundColor: color, opacity }, tintTransitionStyle(reducedMotion)] });
496
+ return /* @__PURE__ */ jsxRuntime.jsx(
497
+ reactNative.View,
498
+ {
499
+ style: [expandableStyles.tintOverlay, { backgroundColor: color, opacity }, tintTransitionStyle(reducedMotion)],
500
+ testID
501
+ }
502
+ );
434
503
  }
435
504
  var NavExpandableItem = ({
436
505
  item,
@@ -442,30 +511,31 @@ var NavExpandableItem = ({
442
511
  renderChevron,
443
512
  depth = 0
444
513
  }) => {
445
- const [expanded, setExpanded] = React3.useState(() => hasActiveDescendant(item, pathname));
446
- const [focused, setFocused] = React3.useState(false);
447
- const [hovered, setHovered] = React3.useState(false);
514
+ const [expanded, setExpanded] = React.useState(() => hasActiveDescendant(item, pathname));
515
+ const [focused, setFocused] = React.useState(false);
516
+ const [hovered, setHovered] = React.useState(false);
448
517
  const { theme } = uiFeedback.useUi();
449
518
  const colors = theme.colors;
450
519
  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), []);
520
+ const toggle = React.useCallback(() => setExpanded((v) => !v), []);
521
+ const onHoverIn = React.useCallback(() => setHovered(true), []);
522
+ const onHoverOut = React.useCallback(() => setHovered(false), []);
454
523
  const indent = depth * BASE_INDENT;
455
524
  const hasChildren = Array.isArray(item.children) && item.children.length > 0;
456
525
  const isActive = isRouteActive(pathname, item.route);
457
526
  const isSectionHeader = hasChildren && depth === 0;
458
- const activeAccentStyle = React3.useMemo(
527
+ const activeAccentStyle = React.useMemo(
459
528
  () => ({ borderLeftWidth: ACTIVE_ACCENT_WIDTH, borderLeftColor: primaryColor }),
460
529
  [primaryColor]
461
530
  );
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;
531
+ const paddingStyle = React.useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
532
+ const headerPaddingStyle = React.useMemo(() => ({ paddingLeft: indent }), [indent]);
533
+ const leafTintOpacity = rowTintOpacity(isActive, hovered);
534
+ const headerTintOpacity = rowTintOpacity(false, hovered);
535
+ const rowTestID = item.testID ?? item.key;
466
536
  if (!hasChildren)
467
537
  return /* @__PURE__ */ jsxRuntime.jsxs(
468
- reactNative.TouchableOpacity,
538
+ reactNative.Pressable,
469
539
  {
470
540
  accessibilityHint: navigateHint(item.label),
471
541
  accessibilityLabel: item.label,
@@ -477,14 +547,14 @@ var NavExpandableItem = ({
477
547
  isActive ? activeAccentStyle : void 0,
478
548
  focusRingStyle(focused, primaryColor)
479
549
  ],
480
- testID: item.testID ?? item.key,
550
+ testID: rowTestID,
481
551
  onBlur: () => setFocused(false),
482
552
  onFocus: () => setFocused(true),
483
553
  onPress: () => onNavigate(item.route),
484
554
  ...hoverHandlerProps(onHoverIn, onHoverOut),
485
555
  ...ariaCurrentProps(isActive),
486
556
  children: [
487
- /* @__PURE__ */ jsxRuntime.jsx(TintOverlay, { color: primaryColor, opacity: leafTintOpacity }),
557
+ /* @__PURE__ */ jsxRuntime.jsx(TintOverlay, { color: primaryColor, opacity: leafTintOpacity, testID: `${rowTestID}-tint` }),
488
558
  typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) }) : null,
489
559
  /* @__PURE__ */ jsxRuntime.jsx(
490
560
  reactNative.Text,
@@ -502,7 +572,7 @@ var NavExpandableItem = ({
502
572
  const chevronColor = colors.textSecondary;
503
573
  return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { children: [
504
574
  /* @__PURE__ */ jsxRuntime.jsxs(
505
- reactNative.TouchableOpacity,
575
+ reactNative.Pressable,
506
576
  {
507
577
  accessibilityHint: expanded ? collapseHint : expandHint,
508
578
  accessibilityLabel: item.label,
@@ -514,14 +584,14 @@ var NavExpandableItem = ({
514
584
  headerPaddingStyle,
515
585
  focusRingStyle(focused, primaryColor)
516
586
  ],
517
- testID: item.testID ?? item.key,
587
+ testID: rowTestID,
518
588
  onBlur: () => setFocused(false),
519
589
  onFocus: () => setFocused(true),
520
590
  onPress: toggle,
521
591
  ...hoverHandlerProps(onHoverIn, onHoverOut),
522
592
  ...ariaExpandedProps(expanded),
523
593
  children: [
524
- /* @__PURE__ */ jsxRuntime.jsx(TintOverlay, { color: primaryColor, opacity: headerTintOpacity }),
594
+ /* @__PURE__ */ jsxRuntime.jsx(TintOverlay, { color: primaryColor, opacity: headerTintOpacity, testID: `${rowTestID}-tint` }),
525
595
  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
596
  /* @__PURE__ */ jsxRuntime.jsx(
527
597
  reactNative.Text,
@@ -553,10 +623,16 @@ var NavExpandableItem = ({
553
623
  )) }) })
554
624
  ] });
555
625
  };
626
+
627
+ // src/searchAffordance.ts
628
+ var DEFAULT_SEARCH_BREAKPOINT = 768;
629
+ function resolveSearchAffordance(viewport, breakpoint = DEFAULT_SEARCH_BREAKPOINT) {
630
+ return viewport >= breakpoint ? "inline" : "palette";
631
+ }
556
632
  var FIELD_MIN_HEIGHT = 38;
557
633
  var CLEAR_GLYPH = "\u2715";
558
634
  var CLEAR_MIN_TARGET = 28;
559
- var styles = reactNative.StyleSheet.create({
635
+ var styles2 = reactNative.StyleSheet.create({
560
636
  wrap: {
561
637
  flexDirection: "row",
562
638
  alignItems: "center",
@@ -591,13 +667,13 @@ var SidebarSearch = ({
591
667
  const { theme } = uiFeedback.useUi();
592
668
  const colors = theme.colors;
593
669
  const primary = theme.palette.primary["500"];
594
- const [focused, setFocused] = React3.useState(false);
670
+ const [focused, setFocused] = React.useState(false);
595
671
  const hasQuery = value.length > 0;
596
672
  return /* @__PURE__ */ jsxRuntime.jsxs(
597
673
  reactNative.View,
598
674
  {
599
675
  style: [
600
- styles.wrap,
676
+ styles2.wrap,
601
677
  { backgroundColor: colors.surface, borderColor: focused ? primary : colors.border },
602
678
  focusRingStyle(focused, primary)
603
679
  ],
@@ -609,7 +685,7 @@ var SidebarSearch = ({
609
685
  accessibilityLabel: labels.placeholder,
610
686
  placeholder: labels.placeholder,
611
687
  placeholderTextColor: colors.textSecondary,
612
- style: [styles.input, { color: colors.text }],
688
+ style: [styles2.input, { color: colors.text }],
613
689
  testID,
614
690
  value,
615
691
  onBlur: () => setFocused(false),
@@ -623,10 +699,10 @@ var SidebarSearch = ({
623
699
  accessibilityHint: labels.clearHint,
624
700
  accessibilityLabel: labels.clearLabel,
625
701
  accessibilityRole: "button",
626
- style: styles.clear,
702
+ style: styles2.clear,
627
703
  testID: `${testID}-clear`,
628
704
  onPress: onClear,
629
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.clearGlyph, { color: colors.textSecondary }], children: CLEAR_GLYPH })
705
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.clearGlyph, { color: colors.textSecondary }], children: CLEAR_GLYPH })
630
706
  }
631
707
  ) : null
632
708
  ]
@@ -639,11 +715,12 @@ function isBareText(node) {
639
715
  function renderTextSlot(node, style) {
640
716
  if (node === void 0 || node === null) return node;
641
717
  if (isBareText(node)) return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style, children: node });
642
- return React3__default.default.Children.map(
718
+ return React__default.default.Children.map(
643
719
  node,
644
720
  (child) => isBareText(child) ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style, children: child }) : child
645
721
  );
646
722
  }
723
+ var DEFAULT_TRIGGER_TEST_ID = "sidebar-command-palette-trigger";
647
724
  var Sidebar = ({
648
725
  items,
649
726
  pathname,
@@ -656,6 +733,7 @@ var Sidebar = ({
656
733
  renderChevron,
657
734
  enableInlineSearch = false,
658
735
  search,
736
+ paletteTrigger,
659
737
  searchQuery,
660
738
  onSearchChange,
661
739
  header,
@@ -664,20 +742,24 @@ var Sidebar = ({
664
742
  }) => {
665
743
  const { theme } = uiFeedback.useUi();
666
744
  const colors = theme.colors;
667
- const [internalQuery, setInternalQuery] = React3.useState("");
745
+ const { width } = reactNative.useWindowDimensions();
746
+ const [internalQuery, setInternalQuery] = React.useState("");
668
747
  const controlled = searchQuery !== void 0;
669
748
  const query = controlled ? searchQuery : internalQuery;
670
- const setQuery = React3.useCallback(
749
+ const setQuery = React.useCallback(
671
750
  (next) => {
672
751
  if (!controlled) setInternalQuery(next);
673
752
  onSearchChange?.(next);
674
753
  },
675
754
  [controlled, onSearchChange]
676
755
  );
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;
756
+ const clearQuery = React.useCallback(() => setQuery(""), [setQuery]);
757
+ const affordance = resolveSearchAffordance(width);
758
+ const isInlineAffordance = affordance === "inline";
759
+ const showInlineSearch = enableInlineSearch && search !== void 0 && isInlineAffordance;
760
+ const showPaletteTrigger = paletteTrigger !== void 0 && !isInlineAffordance;
761
+ const visibleItems = showInlineSearch ? filterNavItems(items, query) : items;
762
+ const showEmpty = showInlineSearch && isFilterActive(query) && visibleItems.length === 0;
681
763
  return /* @__PURE__ */ jsxRuntime.jsxs(
682
764
  reactNative.View,
683
765
  {
@@ -691,7 +773,7 @@ var Sidebar = ({
691
773
  ],
692
774
  children: [
693
775
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { accessibilityRole: "header", style: [navStyles.sidebarTitle, { color: colors.text }], children: title }),
694
- searchOn && search !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
776
+ showInlineSearch && search !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
695
777
  SidebarSearch,
696
778
  {
697
779
  labels: search,
@@ -701,6 +783,16 @@ var Sidebar = ({
701
783
  onClear: clearQuery
702
784
  }
703
785
  ) : null,
786
+ showPaletteTrigger && paletteTrigger !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.sidebarTriggerSlot, children: /* @__PURE__ */ jsxRuntime.jsx(
787
+ CommandPaletteTrigger,
788
+ {
789
+ hint: paletteTrigger.hint,
790
+ label: paletteTrigger.label,
791
+ shortcut: paletteTrigger.shortcut,
792
+ testID: paletteTrigger.testID ?? DEFAULT_TRIGGER_TEST_ID,
793
+ onPress: paletteTrigger.onOpen
794
+ }
795
+ ) }) : null,
704
796
  renderTextSlot(header, { color: colors.text }),
705
797
  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
798
  NavExpandableItem,
@@ -722,12 +814,6 @@ var Sidebar = ({
722
814
  );
723
815
  };
724
816
 
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
817
  // src/constants.ts
732
818
  var NAV_TEST_IDS = {
733
819
  /** displayName line in the rich account header (tappable when `onAccount` set). */
@@ -775,7 +861,7 @@ var FocusableTouchable = ({
775
861
  testID,
776
862
  children
777
863
  }) => {
778
- const [focused, setFocused] = React3.useState(false);
864
+ const [focused, setFocused] = React.useState(false);
779
865
  return /* @__PURE__ */ jsxRuntime.jsx(
780
866
  reactNative.Pressable,
781
867
  {
@@ -928,8 +1014,8 @@ var NavBarLink = ({
928
1014
  navigateHint,
929
1015
  onPress
930
1016
  }) => {
931
- const [hovered, setHovered] = React3.useState(false);
932
- const [focused, setFocused] = React3.useState(false);
1017
+ const [hovered, setHovered] = React.useState(false);
1018
+ const [focused, setFocused] = React.useState(false);
933
1019
  const isHovered = hovered && !isActive;
934
1020
  const textColor = isActive ? TEXT_ON_PRIMARY2 : isHovered ? colors.hoverText : colors.rest;
935
1021
  const pillStyle = isActive ? { backgroundColor: colors.activeBg } : isHovered ? { backgroundColor: colors.hoverBg } : void 0;
@@ -973,12 +1059,12 @@ var NavOverflowMenu = ({
973
1059
  testID,
974
1060
  variant
975
1061
  }) => {
976
- const options = React3.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
977
- const activeRoute = React3.useMemo(
1062
+ const options = React.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
1063
+ const activeRoute = React.useMemo(
978
1064
  () => items.find((item) => isRouteActive(pathname, item.route))?.route ?? NO_ACTIVE_ROUTE,
979
1065
  [items, pathname]
980
1066
  );
981
- const optionTestID = React3.useMemo(() => {
1067
+ const optionTestID = React.useMemo(() => {
982
1068
  const byRoute = new Map(items.map((item) => [item.route, item.testID ?? item.key]));
983
1069
  return (route) => byRoute.get(route) ?? `${testID}-option-${route}`;
984
1070
  }, [items, testID]);
@@ -1035,13 +1121,13 @@ function resizeWidthBuffer(previous, count) {
1035
1121
  return next;
1036
1122
  }
1037
1123
  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(() => {
1124
+ const [availableWidth, setAvailableWidthState] = React.useState(0);
1125
+ const [moreWidth, setMoreWidthState] = React.useState(0);
1126
+ const [itemWidths, setItemWidths] = React.useState(() => new Array(itemCount).fill(0));
1127
+ React.useEffect(() => {
1042
1128
  setItemWidths((previous) => previous.length === itemCount ? previous : resizeWidthBuffer(previous, itemCount));
1043
1129
  }, [itemCount]);
1044
- const setItemWidth = React3.useCallback((index, width) => {
1130
+ const setItemWidth = React.useCallback((index, width) => {
1045
1131
  setItemWidths((previous) => {
1046
1132
  if (index < 0 || index >= previous.length || previous[index] === width) return previous;
1047
1133
  const next = previous.slice();
@@ -1049,15 +1135,15 @@ function useNavOverflow(itemCount, gap) {
1049
1135
  return next;
1050
1136
  });
1051
1137
  }, []);
1052
- const setAvailableWidth = React3.useCallback(
1138
+ const setAvailableWidth = React.useCallback(
1053
1139
  (width) => setAvailableWidthState((previous) => previous === width ? previous : width),
1054
1140
  []
1055
1141
  );
1056
- const setMoreWidth = React3.useCallback(
1142
+ const setMoreWidth = React.useCallback(
1057
1143
  (width) => setMoreWidthState((previous) => previous === width ? previous : width),
1058
1144
  []
1059
1145
  );
1060
- const visibleCount = React3.useMemo(
1146
+ const visibleCount = React.useMemo(
1061
1147
  () => computeVisibleCount({ availableWidth, itemWidths, moreWidth, gap }),
1062
1148
  [availableWidth, itemWidths, moreWidth, gap]
1063
1149
  );
@@ -1091,24 +1177,24 @@ var NavBarInner = ({
1091
1177
  const primaryColor = theme.palette.primary["500"];
1092
1178
  const { width } = reactNative.useWindowDimensions();
1093
1179
  const reducedMotion = useReducedMotion();
1094
- const [open, setOpen] = React3.useState(false);
1095
- const [toggleFocused, setToggleFocused] = React3.useState(false);
1180
+ const [open, setOpen] = React.useState(false);
1181
+ const [toggleFocused, setToggleFocused] = React.useState(false);
1096
1182
  const { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth } = useNavOverflow(items.length, NAV_LINK_GAP);
1097
1183
  const collapsed = width < collapseBelow;
1098
1184
  const showLinks = !collapsed || open;
1099
- const innerCapStyle = React3.useMemo(
1185
+ const innerCapStyle = React.useMemo(
1100
1186
  () => contentMaxWidth === void 0 ? null : { maxWidth: contentMaxWidth, width: "100%", alignSelf: "center" },
1101
1187
  [contentMaxWidth]
1102
1188
  );
1103
- const toggleMenu = React3.useCallback(() => setOpen((v) => !v), []);
1104
- const handlePress = React3.useCallback(
1189
+ const toggleMenu = React.useCallback(() => setOpen((v) => !v), []);
1190
+ const handlePress = React.useCallback(
1105
1191
  (route) => {
1106
1192
  setOpen(false);
1107
1193
  onNavigate(route);
1108
1194
  },
1109
1195
  [onNavigate]
1110
1196
  );
1111
- const linkColors = React3.useMemo(
1197
+ const linkColors = React.useMemo(
1112
1198
  () => ({
1113
1199
  rest: colors.textSecondary,
1114
1200
  hoverText: colors.text,
@@ -1163,7 +1249,7 @@ var NavBarInner = ({
1163
1249
  }
1164
1250
  );
1165
1251
  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)) });
1252
+ 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
1253
  }
1168
1254
  const visibleItems = items.slice(0, visibleCount);
1169
1255
  const overflowItems = items.slice(visibleCount);
@@ -1229,7 +1315,7 @@ var CARD_BORDER_WIDTH = 1;
1229
1315
  var CARD_TITLE_FONT_SIZE = 16;
1230
1316
  var CARD_MESSAGE_FONT_SIZE = 14;
1231
1317
  var CARD_TITLE_MARGIN_BOTTOM = 8;
1232
- var styles2 = reactNative.StyleSheet.create({
1318
+ var styles3 = reactNative.StyleSheet.create({
1233
1319
  centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
1234
1320
  card: {
1235
1321
  padding: CARD_PADDING,
@@ -1246,9 +1332,9 @@ function MessageCard({
1246
1332
  }) {
1247
1333
  const { theme } = uiFeedback.useUi();
1248
1334
  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 })
1335
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
1336
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.cardTitle, { color: accentColor }], children: message.titleText }),
1337
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.cardMessage, { color: colors.textSecondary }], children: message.messageText })
1252
1338
  ] });
1253
1339
  }
1254
1340
  function useContentBody(state, children, testID) {
@@ -1260,7 +1346,7 @@ function useContentBody(state, children, testID) {
1260
1346
  if (state?.error)
1261
1347
  return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
1262
1348
  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" }) });
1349
+ 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
1350
  return children;
1265
1351
  }
1266
1352
  var SCRIM_COLOR = "rgba(0, 0, 0, 0.5)";
@@ -1284,7 +1370,7 @@ var DEFAULT_DRAWER_LABELS = {
1284
1370
  closeLabel: "Close menu",
1285
1371
  closeHint: "Close the navigation menu"
1286
1372
  };
1287
- var styles3 = reactNative.StyleSheet.create({
1373
+ var styles4 = reactNative.StyleSheet.create({
1288
1374
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: DRAWER_Z_INDEX },
1289
1375
  // The scrim's tap-to-close hit area is anchored to the RIGHT of the panel
1290
1376
  // (`left: DRAWER_WIDTH`), so it NEVER overlaps the panel: nav-item taps always
@@ -1319,10 +1405,10 @@ var MenuToggle = ({ label, hint, onOpen, testID }) => {
1319
1405
  accessibilityHint: hint,
1320
1406
  accessibilityLabel: label,
1321
1407
  ringColor: theme.palette.primary["500"],
1322
- style: styles3.menuToggle,
1408
+ style: styles4.menuToggle,
1323
1409
  testID,
1324
1410
  onPress: onOpen,
1325
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1411
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1326
1412
  }
1327
1413
  );
1328
1414
  };
@@ -1334,8 +1420,8 @@ var MobileDrawer = ({
1334
1420
  scrimTestID
1335
1421
  }) => {
1336
1422
  const { theme } = uiFeedback.useUi();
1337
- const panelRef = React3.useRef(null);
1338
- React3.useEffect(() => {
1423
+ const panelRef = React.useRef(null);
1424
+ React.useEffect(() => {
1339
1425
  const node = panelRef.current;
1340
1426
  if (node === null || typeof node.addEventListener !== "function") return void 0;
1341
1427
  const handleClick = (event) => {
@@ -1348,14 +1434,14 @@ var MobileDrawer = ({
1348
1434
  node.addEventListener("click", handleClick);
1349
1435
  return () => node.removeEventListener("click", handleClick);
1350
1436
  }, [onClose]);
1351
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.overlay, children: [
1437
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.overlay, children: [
1352
1438
  /* @__PURE__ */ jsxRuntime.jsx(
1353
1439
  reactNative.Pressable,
1354
1440
  {
1355
1441
  accessibilityHint: labels.closeHint,
1356
1442
  accessibilityLabel: labels.closeLabel,
1357
1443
  accessibilityRole: "button",
1358
- style: styles3.scrim,
1444
+ style: styles4.scrim,
1359
1445
  testID: scrimTestID,
1360
1446
  onPress: onClose
1361
1447
  }
@@ -1366,7 +1452,7 @@ var MobileDrawer = ({
1366
1452
  ref: panelRef,
1367
1453
  "aria-modal": true,
1368
1454
  role: "dialog",
1369
- style: [styles3.panel, { backgroundColor: theme.colors.surface }],
1455
+ style: [styles4.panel, { backgroundColor: theme.colors.surface }],
1370
1456
  testID: drawerTestID,
1371
1457
  children: renderTextSlot(sidebar, { color: theme.colors.text })
1372
1458
  }
@@ -1397,7 +1483,7 @@ function useContentMaxWidth(width) {
1397
1483
  return resolveContentMaxWidth(width, viewport);
1398
1484
  }
1399
1485
  var DEFAULT_CONTENT_PADDING = 24;
1400
- var styles4 = reactNative.StyleSheet.create({
1486
+ var styles5 = reactNative.StyleSheet.create({
1401
1487
  root: { flex: 1 },
1402
1488
  centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
1403
1489
  scroll: { flex: 1 },
@@ -1442,32 +1528,32 @@ var AppShell = ({
1442
1528
  range: railRange
1443
1529
  });
1444
1530
  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(() => {
1531
+ const [drawerOpen, setDrawerOpen] = React.useState(false);
1532
+ const openDrawer = React.useCallback(() => setDrawerOpen(true), []);
1533
+ const closeDrawer = React.useCallback(() => setDrawerOpen(false), []);
1534
+ React.useEffect(() => {
1449
1535
  if (!useDrawer && drawerOpen) setDrawerOpen(false);
1450
1536
  }, [useDrawer, drawerOpen]);
1451
1537
  const drawerLabels = { ...DEFAULT_DRAWER_LABELS, ...mobileMenu };
1452
1538
  const isUnauthenticated = gate !== void 0 && !gate.pending && !gate.authenticated;
1453
- React3.useEffect(() => {
1539
+ React.useEffect(() => {
1454
1540
  if (isUnauthenticated && gate !== void 0) gate.onRedirect();
1455
1541
  }, [isUnauthenticated, gate]);
1456
1542
  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" }) });
1543
+ 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
1544
  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;
1545
+ const columnStyle = maxWidth === "full" ? styles5.columnFull : [styles5.columnCapped, { maxWidth }];
1546
+ const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles5.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1461
1547
  const scroller = /* @__PURE__ */ jsxRuntime.jsx(
1462
1548
  reactNative.ScrollView,
1463
1549
  {
1464
- contentContainerStyle: [styles4.scrollContent, { padding: contentPadding }],
1465
- style: styles4.scroll,
1550
+ contentContainerStyle: [styles5.scrollContent, { padding: contentPadding }],
1551
+ style: styles5.scroll,
1466
1552
  testID: `${testID}${APP_SHELL_SUFFIX.content}`,
1467
1553
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: columnStyle, children: body })
1468
1554
  }
1469
1555
  );
1470
- const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1556
+ const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1471
1557
  /* @__PURE__ */ jsxRuntime.jsx(
1472
1558
  MenuToggle,
1473
1559
  {
@@ -1477,14 +1563,14 @@ var AppShell = ({
1477
1563
  onOpen: openDrawer
1478
1564
  }
1479
1565
  ),
1480
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles4.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
1566
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles5.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
1481
1567
  ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: renderTextSlot(header, { color: theme.colors.text }) });
1482
1568
  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: [
1569
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles5.root, { backgroundColor: theme.colors.background }], testID, children: [
1484
1570
  headerRegion,
1485
1571
  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
1572
  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: [
1573
+ railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.bodyRow, children: [
1488
1574
  railRegion,
1489
1575
  scroller
1490
1576
  ] }) : scroller,
@@ -1607,6 +1693,7 @@ var NavShell = ({
1607
1693
  header: sideRail.header,
1608
1694
  items: sideRail.items,
1609
1695
  navigateHint,
1696
+ paletteTrigger: sideRail.paletteTrigger,
1610
1697
  pathname,
1611
1698
  regionLabel,
1612
1699
  renderChevron: sideRail.renderChevron,
@@ -1706,7 +1793,7 @@ var DarkModeControl = ({
1706
1793
  const primaryColor = theme.palette.primary["500"];
1707
1794
  const currentIndex = options.findIndex((option) => option.value === value);
1708
1795
  const current = currentIndex >= 0 ? options[currentIndex] : options[0];
1709
- const advance = React3.useCallback(() => {
1796
+ const advance = React.useCallback(() => {
1710
1797
  if (options.length === 0) return;
1711
1798
  const from = currentIndex >= 0 ? currentIndex : 0;
1712
1799
  const next = options[(from + 1) % options.length];
@@ -1782,7 +1869,7 @@ var KEY_DOWN = "ArrowDown";
1782
1869
  var KEY_UP = "ArrowUp";
1783
1870
  var KEY_ENTER = "Enter";
1784
1871
  var KEY_ESCAPE = "Escape";
1785
- var styles5 = reactNative.StyleSheet.create({
1872
+ var styles6 = reactNative.StyleSheet.create({
1786
1873
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: OVERLAY_Z_INDEX, alignItems: "center" },
1787
1874
  scrim: { ...reactNative.StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR2 },
1788
1875
  panel: {
@@ -1811,14 +1898,14 @@ var PaletteRow = ({ item, selected, onChoose }) => {
1811
1898
  accessibilityLabel: item.label,
1812
1899
  accessibilityRole: "button",
1813
1900
  accessibilityState: { selected },
1814
- style: [styles5.row, selected ? { backgroundColor: colors.border } : void 0],
1901
+ style: [styles6.row, selected ? { backgroundColor: colors.border } : void 0],
1815
1902
  testID: item.testID ?? item.key,
1816
1903
  onPress: () => onChoose(item),
1817
1904
  children: [
1818
1905
  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
1906
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.rowText, children: [
1907
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.rowLabel, { color: colors.text }], children: item.label }),
1908
+ item.hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
1822
1909
  ] })
1823
1910
  ]
1824
1911
  }
@@ -1827,27 +1914,27 @@ var PaletteRow = ({ item, selected, onChoose }) => {
1827
1914
  var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1828
1915
  const { theme } = uiFeedback.useUi();
1829
1916
  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(() => {
1917
+ const [query, setQuery] = React.useState("");
1918
+ const [selected, setSelected] = React.useState(0);
1919
+ const inputRef = React.useRef(null);
1920
+ const results = React.useMemo(() => filterCommands(items, query), [items, query]);
1921
+ React.useEffect(() => {
1835
1922
  if (!open) return;
1836
1923
  setQuery("");
1837
1924
  setSelected(0);
1838
1925
  inputRef.current?.focus();
1839
1926
  }, [open]);
1840
- React3.useEffect(() => {
1927
+ React.useEffect(() => {
1841
1928
  setSelected((prev) => Math.min(prev, Math.max(0, results.length - 1)));
1842
1929
  }, [results.length]);
1843
- const choose = React3.useCallback(
1930
+ const choose = React.useCallback(
1844
1931
  (item) => {
1845
1932
  item.onSelect();
1846
1933
  onClose();
1847
1934
  },
1848
1935
  [onClose]
1849
1936
  );
1850
- const onKeyPress = React3.useCallback(
1937
+ const onKeyPress = React.useCallback(
1851
1938
  (event) => {
1852
1939
  const key = event.nativeEvent.key;
1853
1940
  if (key === KEY_DOWN) setSelected((i) => Math.min(i + 1, results.length - 1));
@@ -1858,14 +1945,14 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1858
1945
  [results, selected, choose, onClose]
1859
1946
  );
1860
1947
  if (!open) return null;
1861
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.overlay, children: [
1948
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles6.overlay, children: [
1862
1949
  /* @__PURE__ */ jsxRuntime.jsx(
1863
1950
  reactNative.Pressable,
1864
1951
  {
1865
1952
  accessibilityHint: labels.closeHint,
1866
1953
  accessibilityLabel: labels.closeLabel,
1867
1954
  accessibilityRole: "button",
1868
- style: styles5.scrim,
1955
+ style: styles6.scrim,
1869
1956
  testID: `${testID ?? "command-palette"}-scrim`,
1870
1957
  onPress: onClose
1871
1958
  }
@@ -1876,7 +1963,7 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1876
1963
  "aria-modal": true,
1877
1964
  "aria-label": labels.regionLabel,
1878
1965
  role: "dialog",
1879
- style: [styles5.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
1966
+ style: [styles6.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
1880
1967
  testID: testID ?? "command-palette",
1881
1968
  children: [
1882
1969
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1886,71 +1973,22 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1886
1973
  accessibilityLabel: labels.placeholder,
1887
1974
  placeholder: labels.placeholder,
1888
1975
  placeholderTextColor: colors.textSecondary,
1889
- style: [styles5.input, { color: colors.text, borderBottomColor: colors.border }],
1976
+ style: [styles6.input, { color: colors.text, borderBottomColor: colors.border }],
1890
1977
  testID: `${testID ?? "command-palette"}-input`,
1891
1978
  value: query,
1892
1979
  onChangeText: setQuery,
1893
1980
  onKeyPress
1894
1981
  }
1895
1982
  ),
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)) })
1983
+ 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
1984
  ]
1898
1985
  }
1899
1986
  )
1900
1987
  ] });
1901
1988
  };
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
1989
  var LAUNCH_KEY = "k";
1952
1990
  function useCommandPaletteHotkey(onOpen, enabled = true) {
1953
- React3.useEffect(() => {
1991
+ React.useEffect(() => {
1954
1992
  if (!enabled || typeof document === "undefined") return void 0;
1955
1993
  const handler = (event) => {
1956
1994
  const isLauncher = event.key.toLowerCase() === LAUNCH_KEY && (event.metaKey || event.ctrlKey);