@dloizides/ui-nav 1.13.1 → 1.16.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,51 @@
1
1
  'use strict';
2
2
 
3
+ var React3 = require('react');
3
4
  var reactNative = require('react-native');
4
5
  var uiFeedback = require('@dloizides/ui-feedback');
5
- var React2 = require('react');
6
+ var uiMotion = require('@dloizides/ui-motion');
6
7
  var jsxRuntime = require('react/jsx-runtime');
7
8
  var uiLayout = require('@dloizides/ui-layout');
8
9
  var authWeb = require('@dloizides/auth-web');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
12
 
12
- var React2__default = /*#__PURE__*/_interopDefault(React2);
13
+ var React3__default = /*#__PURE__*/_interopDefault(React3);
13
14
 
14
15
  // src/Sidebar.tsx
15
16
 
17
+ // src/sidebarFilter.ts
18
+ function searchTokens(query) {
19
+ return query.trim().toLowerCase().split(/\s+/).filter(Boolean);
20
+ }
21
+ function labelMatches(item, tokens) {
22
+ const label = item.label.toLowerCase();
23
+ return tokens.every((token) => label.includes(token));
24
+ }
25
+ function filterNavItems(items, query) {
26
+ const tokens = searchTokens(query);
27
+ if (tokens.length === 0) return [...items];
28
+ return filterByTokens(items, tokens);
29
+ }
30
+ function filterByTokens(items, tokens) {
31
+ const kept = [];
32
+ for (const item of items) {
33
+ const selfMatch = labelMatches(item, tokens);
34
+ const children = item.children ?? [];
35
+ if (selfMatch) {
36
+ kept.push(item);
37
+ continue;
38
+ }
39
+ if (children.length === 0) continue;
40
+ const prunedChildren = filterByTokens(children, tokens);
41
+ if (prunedChildren.length > 0) kept.push({ ...item, children: prunedChildren });
42
+ }
43
+ return kept;
44
+ }
45
+ function isFilterActive(query) {
46
+ return searchTokens(query).length > 0;
47
+ }
48
+
16
49
  // src/isRouteActive.ts
17
50
  function isRouteActive(pathname, route) {
18
51
  if (route === "/") return pathname === "/";
@@ -20,6 +53,9 @@ function isRouteActive(pathname, route) {
20
53
  }
21
54
  var ACTIVE_BORDER_RADIUS = 4;
22
55
  var ACTIVE_ACCENT_WIDTH = 3;
56
+ var NAV_ROW_RADIUS = 8;
57
+ var ACTIVE_TINT_OPACITY = 0.14;
58
+ var HOVER_TINT_OPACITY = 0.06;
23
59
  var NAV_LINK_GAP = 4;
24
60
  var navStyles = reactNative.StyleSheet.create({
25
61
  // --- Sidebar ---
@@ -45,6 +81,12 @@ var navStyles = reactNative.StyleSheet.create({
45
81
  sidebarSpacer: {
46
82
  flex: 1
47
83
  },
84
+ // Inline-search no-match text — sits where the nav list would, muted + padded.
85
+ sidebarEmpty: {
86
+ fontSize: 13,
87
+ paddingVertical: 10,
88
+ paddingHorizontal: 12
89
+ },
48
90
  // --- Topbar ---
49
91
  topbarContainer: {
50
92
  height: 64,
@@ -245,8 +287,12 @@ var collapsedRailStyles = reactNative.StyleSheet.create({
245
287
  var expandableStyles = reactNative.StyleSheet.create({
246
288
  // Every leaf reserves the accent-bar gutter (a TRANSPARENT left border of the
247
289
  // same width the active item colours in) so activation never shifts the row.
290
+ // `overflow: hidden` clips the rounded {@link tintOverlay} to the row's corners;
291
+ // `position: relative` anchors that absolutely-filled overlay to the row.
248
292
  childItem: {
249
- borderRadius: 6,
293
+ position: "relative",
294
+ overflow: "hidden",
295
+ borderRadius: NAV_ROW_RADIUS,
250
296
  flexDirection: "row",
251
297
  alignItems: "center",
252
298
  paddingVertical: 9,
@@ -257,8 +303,24 @@ var expandableStyles = reactNative.StyleSheet.create({
257
303
  childItemTextWithIcon: { fontSize: 14, marginLeft: 6 },
258
304
  chevron: { marginLeft: "auto" },
259
305
  childrenContainer: { overflow: "hidden", marginBottom: 4 },
306
+ // The hover/active brand wash: a rounded fill behind the row's icon + label,
307
+ // its opacity eased between 0 / hover / active. Colour + opacity are applied at
308
+ // render time (theme primary); this only positions and rounds it.
309
+ tintOverlay: {
310
+ position: "absolute",
311
+ top: 0,
312
+ left: 0,
313
+ right: 0,
314
+ bottom: 0,
315
+ borderRadius: NAV_ROW_RADIUS,
316
+ // `style.pointerEvents` (not the deprecated prop) so the overlay never
317
+ // intercepts the row's press/hover — RNW forwards this style key to the DOM.
318
+ pointerEvents: "none"
319
+ },
260
320
  header: {
261
- borderRadius: 6,
321
+ position: "relative",
322
+ overflow: "hidden",
323
+ borderRadius: NAV_ROW_RADIUS,
262
324
  flexDirection: "row",
263
325
  alignItems: "center",
264
326
  paddingVertical: 8
@@ -285,6 +347,7 @@ var IS_WEB = reactNative.Platform.OS === "web";
285
347
  var FOCUS_RING_WIDTH = 2;
286
348
  var FOCUS_RING_OFFSET = 2;
287
349
  var HOVER_TRANSITION_MS = 150;
350
+ var TINT_TRANSITION_MS = 160;
288
351
  function focusRingStyle(focused, ringColor) {
289
352
  if (!IS_WEB || !focused) return void 0;
290
353
  const ring = {
@@ -295,24 +358,80 @@ function focusRingStyle(focused, ringColor) {
295
358
  };
296
359
  return ring;
297
360
  }
298
- function hoverTransitionStyle(reducedMotion) {
361
+ function webTransition(properties, durationMs, reducedMotion) {
299
362
  if (!IS_WEB) return void 0;
300
363
  const transition = {
301
- transitionProperty: "color, background-color",
302
- transitionDuration: reducedMotion ? "0ms" : `${HOVER_TRANSITION_MS}ms`
364
+ transitionProperty: properties,
365
+ transitionDuration: reducedMotion ? "0ms" : `${durationMs}ms`
303
366
  };
304
367
  return transition;
305
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
+ var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
379
+ function getReducedMotionQuery() {
380
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
381
+ return window.matchMedia(REDUCED_MOTION_QUERY);
382
+ }
383
+ function useReducedMotion() {
384
+ const [reduced, setReduced] = React3.useState(() => getReducedMotionQuery()?.matches ?? false);
385
+ React3.useEffect(() => {
386
+ const mql = getReducedMotionQuery();
387
+ if (mql === void 0 || mql.addEventListener === void 0) return void 0;
388
+ const onChange = () => setReduced(mql.matches);
389
+ mql.addEventListener("change", onChange);
390
+ return () => {
391
+ mql.removeEventListener?.("change", onChange);
392
+ };
393
+ }, []);
394
+ return reduced;
395
+ }
396
+ var CHEVRON_GLYPH = "\u203A";
397
+ var EXPANDED_ROTATION = "90deg";
398
+ var COLLAPSED_ROTATION = "0deg";
399
+ var CHEVRON_FONT_WEIGHT = "700";
400
+ var SidebarChevron = ({ expanded, color, size }) => {
401
+ const reducedMotion = useReducedMotion();
402
+ const style = {
403
+ color,
404
+ fontSize: size,
405
+ fontWeight: CHEVRON_FONT_WEIGHT,
406
+ transform: [{ rotate: expanded ? EXPANDED_ROTATION : COLLAPSED_ROTATION }]
407
+ };
408
+ return /* @__PURE__ */ jsxRuntime.jsx(
409
+ reactNative.Text,
410
+ {
411
+ accessibilityElementsHidden: true,
412
+ importantForAccessibility: "no",
413
+ style: [style, rotateTransitionStyle(reducedMotion)],
414
+ children: CHEVRON_GLYPH
415
+ }
416
+ );
417
+ };
306
418
  function ariaCurrentProps(isActive) {
307
419
  return isActive ? { "aria-current": "page" } : {};
308
420
  }
309
421
  function ariaExpandedProps(expanded) {
310
422
  return { "aria-expanded": expanded };
311
423
  }
424
+ function hoverHandlerProps(onIn, onOut) {
425
+ return IS_WEB ? { onHoverIn: onIn, onHoverOut: onOut } : {};
426
+ }
312
427
  function hasActiveDescendant(item, pathname) {
313
428
  if (isRouteActive(pathname, item.route)) return true;
314
429
  return (item.children ?? []).some((child) => hasActiveDescendant(child, pathname));
315
430
  }
431
+ function TintOverlay({ opacity, color }) {
432
+ const reducedMotion = useReducedMotion();
433
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [expandableStyles.tintOverlay, { backgroundColor: color, opacity }, tintTransitionStyle(reducedMotion)] });
434
+ }
316
435
  var NavExpandableItem = ({
317
436
  item,
318
437
  pathname,
@@ -323,27 +442,27 @@ var NavExpandableItem = ({
323
442
  renderChevron,
324
443
  depth = 0
325
444
  }) => {
326
- const [expanded, setExpanded] = React2.useState(() => hasActiveDescendant(item, pathname));
327
- const [focused, setFocused] = React2.useState(false);
445
+ const [expanded, setExpanded] = React3.useState(() => hasActiveDescendant(item, pathname));
446
+ const [focused, setFocused] = React3.useState(false);
447
+ const [hovered, setHovered] = React3.useState(false);
328
448
  const { theme } = uiFeedback.useUi();
329
449
  const colors = theme.colors;
330
450
  const primaryColor = theme.palette.primary["500"];
331
- const toggle = React2.useCallback(() => setExpanded((v) => !v), []);
451
+ const toggle = React3.useCallback(() => setExpanded((v) => !v), []);
452
+ const onHoverIn = React3.useCallback(() => setHovered(true), []);
453
+ const onHoverOut = React3.useCallback(() => setHovered(false), []);
332
454
  const indent = depth * BASE_INDENT;
333
455
  const hasChildren = Array.isArray(item.children) && item.children.length > 0;
334
456
  const isActive = isRouteActive(pathname, item.route);
335
457
  const isSectionHeader = hasChildren && depth === 0;
336
- const activeItemStyle = React2.useMemo(
337
- () => ({
338
- backgroundColor: colors.border,
339
- borderRadius: ACTIVE_BORDER_RADIUS,
340
- borderLeftWidth: ACTIVE_ACCENT_WIDTH,
341
- borderLeftColor: primaryColor
342
- }),
343
- [colors.border, primaryColor]
458
+ const activeAccentStyle = React3.useMemo(
459
+ () => ({ borderLeftWidth: ACTIVE_ACCENT_WIDTH, borderLeftColor: primaryColor }),
460
+ [primaryColor]
344
461
  );
345
- const paddingStyle = React2.useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
346
- const headerPaddingStyle = React2.useMemo(() => ({ paddingLeft: indent }), [indent]);
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;
347
466
  if (!hasChildren)
348
467
  return /* @__PURE__ */ jsxRuntime.jsxs(
349
468
  reactNative.TouchableOpacity,
@@ -355,15 +474,17 @@ var NavExpandableItem = ({
355
474
  style: [
356
475
  expandableStyles.childItem,
357
476
  paddingStyle,
358
- isActive ? activeItemStyle : void 0,
477
+ isActive ? activeAccentStyle : void 0,
359
478
  focusRingStyle(focused, primaryColor)
360
479
  ],
361
480
  testID: item.testID ?? item.key,
362
481
  onBlur: () => setFocused(false),
363
482
  onFocus: () => setFocused(true),
364
483
  onPress: () => onNavigate(item.route),
484
+ ...hoverHandlerProps(onHoverIn, onHoverOut),
365
485
  ...ariaCurrentProps(isActive),
366
486
  children: [
487
+ /* @__PURE__ */ jsxRuntime.jsx(TintOverlay, { color: primaryColor, opacity: leafTintOpacity }),
367
488
  typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) }) : null,
368
489
  /* @__PURE__ */ jsxRuntime.jsx(
369
490
  reactNative.Text,
@@ -378,6 +499,7 @@ var NavExpandableItem = ({
378
499
  ]
379
500
  }
380
501
  );
502
+ const chevronColor = colors.textSecondary;
381
503
  return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { children: [
382
504
  /* @__PURE__ */ jsxRuntime.jsxs(
383
505
  reactNative.TouchableOpacity,
@@ -396,8 +518,10 @@ var NavExpandableItem = ({
396
518
  onBlur: () => setFocused(false),
397
519
  onFocus: () => setFocused(true),
398
520
  onPress: toggle,
521
+ ...hoverHandlerProps(onHoverIn, onHoverOut),
399
522
  ...ariaExpandedProps(expanded),
400
523
  children: [
524
+ /* @__PURE__ */ jsxRuntime.jsx(TintOverlay, { color: primaryColor, opacity: headerTintOpacity }),
401
525
  typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.iconWrapper, children: item.renderIcon(isSectionHeader ? colors.textSecondary : colors.text, NAV_ICON_SIZE) }) : null,
402
526
  /* @__PURE__ */ jsxRuntime.jsx(
403
527
  reactNative.Text,
@@ -409,11 +533,11 @@ var NavExpandableItem = ({
409
533
  children: item.label
410
534
  }
411
535
  ),
412
- typeof renderChevron === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.chevron, children: renderChevron(expanded, colors.textSecondary, CHEVRON_ICON_SIZE) }) : null
536
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.chevron, children: typeof renderChevron === "function" ? renderChevron(expanded, chevronColor, CHEVRON_ICON_SIZE) : /* @__PURE__ */ jsxRuntime.jsx(SidebarChevron, { color: chevronColor, expanded, size: CHEVRON_ICON_SIZE }) })
413
537
  ]
414
538
  }
415
539
  ),
416
- expanded ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.childrenContainer, children: item.children?.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
540
+ /* @__PURE__ */ jsxRuntime.jsx(uiMotion.Collapse, { open: expanded, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: expandableStyles.childrenContainer, children: item.children?.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
417
541
  NavExpandableItem,
418
542
  {
419
543
  collapseHint,
@@ -426,16 +550,96 @@ var NavExpandableItem = ({
426
550
  onNavigate
427
551
  },
428
552
  child.key
429
- )) }) : null
553
+ )) }) })
430
554
  ] });
431
555
  };
556
+ var FIELD_MIN_HEIGHT = 38;
557
+ var CLEAR_GLYPH = "\u2715";
558
+ var CLEAR_MIN_TARGET = 28;
559
+ var styles = reactNative.StyleSheet.create({
560
+ wrap: {
561
+ flexDirection: "row",
562
+ alignItems: "center",
563
+ borderWidth: 1,
564
+ borderRadius: 8,
565
+ paddingLeft: 12,
566
+ paddingRight: 6,
567
+ marginBottom: 12
568
+ },
569
+ input: {
570
+ flex: 1,
571
+ fontSize: 14,
572
+ paddingVertical: 8,
573
+ minHeight: FIELD_MIN_HEIGHT
574
+ },
575
+ clear: {
576
+ minWidth: CLEAR_MIN_TARGET,
577
+ minHeight: CLEAR_MIN_TARGET,
578
+ alignItems: "center",
579
+ justifyContent: "center",
580
+ borderRadius: 6
581
+ },
582
+ clearGlyph: { fontSize: 13, fontWeight: "700" }
583
+ });
584
+ var SidebarSearch = ({
585
+ value,
586
+ onChangeText,
587
+ onClear,
588
+ labels,
589
+ testID
590
+ }) => {
591
+ const { theme } = uiFeedback.useUi();
592
+ const colors = theme.colors;
593
+ const primary = theme.palette.primary["500"];
594
+ const [focused, setFocused] = React3.useState(false);
595
+ const hasQuery = value.length > 0;
596
+ return /* @__PURE__ */ jsxRuntime.jsxs(
597
+ reactNative.View,
598
+ {
599
+ style: [
600
+ styles.wrap,
601
+ { backgroundColor: colors.surface, borderColor: focused ? primary : colors.border },
602
+ focusRingStyle(focused, primary)
603
+ ],
604
+ children: [
605
+ /* @__PURE__ */ jsxRuntime.jsx(
606
+ reactNative.TextInput,
607
+ {
608
+ accessibilityHint: labels.hint,
609
+ accessibilityLabel: labels.placeholder,
610
+ placeholder: labels.placeholder,
611
+ placeholderTextColor: colors.textSecondary,
612
+ style: [styles.input, { color: colors.text }],
613
+ testID,
614
+ value,
615
+ onBlur: () => setFocused(false),
616
+ onChangeText,
617
+ onFocus: () => setFocused(true)
618
+ }
619
+ ),
620
+ hasQuery ? /* @__PURE__ */ jsxRuntime.jsx(
621
+ reactNative.Pressable,
622
+ {
623
+ accessibilityHint: labels.clearHint,
624
+ accessibilityLabel: labels.clearLabel,
625
+ accessibilityRole: "button",
626
+ style: styles.clear,
627
+ testID: `${testID}-clear`,
628
+ onPress: onClear,
629
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.clearGlyph, { color: colors.textSecondary }], children: CLEAR_GLYPH })
630
+ }
631
+ ) : null
632
+ ]
633
+ }
634
+ );
635
+ };
432
636
  function isBareText(node) {
433
637
  return typeof node === "string" || typeof node === "number";
434
638
  }
435
639
  function renderTextSlot(node, style) {
436
640
  if (node === void 0 || node === null) return node;
437
641
  if (isBareText(node)) return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style, children: node });
438
- return React2__default.default.Children.map(
642
+ return React3__default.default.Children.map(
439
643
  node,
440
644
  (child) => isBareText(child) ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style, children: child }) : child
441
645
  );
@@ -450,12 +654,30 @@ var Sidebar = ({
450
654
  expandHint = "",
451
655
  collapseHint = "",
452
656
  renderChevron,
657
+ enableInlineSearch = false,
658
+ search,
659
+ searchQuery,
660
+ onSearchChange,
453
661
  header,
454
662
  footer,
455
663
  containerStyle
456
664
  }) => {
457
665
  const { theme } = uiFeedback.useUi();
458
666
  const colors = theme.colors;
667
+ const [internalQuery, setInternalQuery] = React3.useState("");
668
+ const controlled = searchQuery !== void 0;
669
+ const query = controlled ? searchQuery : internalQuery;
670
+ const setQuery = React3.useCallback(
671
+ (next) => {
672
+ if (!controlled) setInternalQuery(next);
673
+ onSearchChange?.(next);
674
+ },
675
+ [controlled, onSearchChange]
676
+ );
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;
459
681
  return /* @__PURE__ */ jsxRuntime.jsxs(
460
682
  reactNative.View,
461
683
  {
@@ -469,8 +691,18 @@ var Sidebar = ({
469
691
  ],
470
692
  children: [
471
693
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { accessibilityRole: "header", style: [navStyles.sidebarTitle, { color: colors.text }], children: title }),
694
+ searchOn && search !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
695
+ SidebarSearch,
696
+ {
697
+ labels: search,
698
+ testID: "sidebar-search",
699
+ value: query,
700
+ onChangeText: setQuery,
701
+ onClear: clearQuery
702
+ }
703
+ ) : null,
472
704
  renderTextSlot(header, { color: colors.text }),
473
- items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
705
+ 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(
474
706
  NavExpandableItem,
475
707
  {
476
708
  collapseHint,
@@ -490,6 +722,12 @@ var Sidebar = ({
490
722
  );
491
723
  };
492
724
 
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
+
493
731
  // src/constants.ts
494
732
  var NAV_TEST_IDS = {
495
733
  /** displayName line in the rich account header (tappable when `onAccount` set). */
@@ -537,7 +775,7 @@ var FocusableTouchable = ({
537
775
  testID,
538
776
  children
539
777
  }) => {
540
- const [focused, setFocused] = React2.useState(false);
778
+ const [focused, setFocused] = React3.useState(false);
541
779
  return /* @__PURE__ */ jsxRuntime.jsx(
542
780
  reactNative.Pressable,
543
781
  {
@@ -690,8 +928,8 @@ var NavBarLink = ({
690
928
  navigateHint,
691
929
  onPress
692
930
  }) => {
693
- const [hovered, setHovered] = React2.useState(false);
694
- const [focused, setFocused] = React2.useState(false);
931
+ const [hovered, setHovered] = React3.useState(false);
932
+ const [focused, setFocused] = React3.useState(false);
695
933
  const isHovered = hovered && !isActive;
696
934
  const textColor = isActive ? TEXT_ON_PRIMARY2 : isHovered ? colors.hoverText : colors.rest;
697
935
  const pillStyle = isActive ? { backgroundColor: colors.activeBg } : isHovered ? { backgroundColor: colors.hoverBg } : void 0;
@@ -735,12 +973,12 @@ var NavOverflowMenu = ({
735
973
  testID,
736
974
  variant
737
975
  }) => {
738
- const options = React2.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
739
- const activeRoute = React2.useMemo(
976
+ const options = React3.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
977
+ const activeRoute = React3.useMemo(
740
978
  () => items.find((item) => isRouteActive(pathname, item.route))?.route ?? NO_ACTIVE_ROUTE,
741
979
  [items, pathname]
742
980
  );
743
- const optionTestID = React2.useMemo(() => {
981
+ const optionTestID = React3.useMemo(() => {
744
982
  const byRoute = new Map(items.map((item) => [item.route, item.testID ?? item.key]));
745
983
  return (route) => byRoute.get(route) ?? `${testID}-option-${route}`;
746
984
  }, [items, testID]);
@@ -797,13 +1035,13 @@ function resizeWidthBuffer(previous, count) {
797
1035
  return next;
798
1036
  }
799
1037
  function useNavOverflow(itemCount, gap) {
800
- const [availableWidth, setAvailableWidthState] = React2.useState(0);
801
- const [moreWidth, setMoreWidthState] = React2.useState(0);
802
- const [itemWidths, setItemWidths] = React2.useState(() => new Array(itemCount).fill(0));
803
- React2.useEffect(() => {
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(() => {
804
1042
  setItemWidths((previous) => previous.length === itemCount ? previous : resizeWidthBuffer(previous, itemCount));
805
1043
  }, [itemCount]);
806
- const setItemWidth = React2.useCallback((index, width) => {
1044
+ const setItemWidth = React3.useCallback((index, width) => {
807
1045
  setItemWidths((previous) => {
808
1046
  if (index < 0 || index >= previous.length || previous[index] === width) return previous;
809
1047
  const next = previous.slice();
@@ -811,38 +1049,20 @@ function useNavOverflow(itemCount, gap) {
811
1049
  return next;
812
1050
  });
813
1051
  }, []);
814
- const setAvailableWidth = React2.useCallback(
1052
+ const setAvailableWidth = React3.useCallback(
815
1053
  (width) => setAvailableWidthState((previous) => previous === width ? previous : width),
816
1054
  []
817
1055
  );
818
- const setMoreWidth = React2.useCallback(
1056
+ const setMoreWidth = React3.useCallback(
819
1057
  (width) => setMoreWidthState((previous) => previous === width ? previous : width),
820
1058
  []
821
1059
  );
822
- const visibleCount = React2.useMemo(
1060
+ const visibleCount = React3.useMemo(
823
1061
  () => computeVisibleCount({ availableWidth, itemWidths, moreWidth, gap }),
824
1062
  [availableWidth, itemWidths, moreWidth, gap]
825
1063
  );
826
1064
  return { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth };
827
1065
  }
828
- var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
829
- function getReducedMotionQuery() {
830
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return void 0;
831
- return window.matchMedia(REDUCED_MOTION_QUERY);
832
- }
833
- function useReducedMotion() {
834
- const [reduced, setReduced] = React2.useState(() => getReducedMotionQuery()?.matches ?? false);
835
- React2.useEffect(() => {
836
- const mql = getReducedMotionQuery();
837
- if (mql === void 0 || mql.addEventListener === void 0) return void 0;
838
- const onChange = () => setReduced(mql.matches);
839
- mql.addEventListener("change", onChange);
840
- return () => {
841
- mql.removeEventListener?.("change", onChange);
842
- };
843
- }, []);
844
- return reduced;
845
- }
846
1066
  var LINKS_REGION_ID = "navbar-links-region";
847
1067
  var DEFAULT_COLLAPSE_BELOW = 760;
848
1068
  var MENU_GLYPH = "\u2630";
@@ -871,24 +1091,24 @@ var NavBarInner = ({
871
1091
  const primaryColor = theme.palette.primary["500"];
872
1092
  const { width } = reactNative.useWindowDimensions();
873
1093
  const reducedMotion = useReducedMotion();
874
- const [open, setOpen] = React2.useState(false);
875
- const [toggleFocused, setToggleFocused] = React2.useState(false);
1094
+ const [open, setOpen] = React3.useState(false);
1095
+ const [toggleFocused, setToggleFocused] = React3.useState(false);
876
1096
  const { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth } = useNavOverflow(items.length, NAV_LINK_GAP);
877
1097
  const collapsed = width < collapseBelow;
878
1098
  const showLinks = !collapsed || open;
879
- const innerCapStyle = React2.useMemo(
1099
+ const innerCapStyle = React3.useMemo(
880
1100
  () => contentMaxWidth === void 0 ? null : { maxWidth: contentMaxWidth, width: "100%", alignSelf: "center" },
881
1101
  [contentMaxWidth]
882
1102
  );
883
- const toggleMenu = React2.useCallback(() => setOpen((v) => !v), []);
884
- const handlePress = React2.useCallback(
1103
+ const toggleMenu = React3.useCallback(() => setOpen((v) => !v), []);
1104
+ const handlePress = React3.useCallback(
885
1105
  (route) => {
886
1106
  setOpen(false);
887
1107
  onNavigate(route);
888
1108
  },
889
1109
  [onNavigate]
890
1110
  );
891
- const linkColors = React2.useMemo(
1111
+ const linkColors = React3.useMemo(
892
1112
  () => ({
893
1113
  rest: colors.textSecondary,
894
1114
  hoverText: colors.text,
@@ -943,7 +1163,7 @@ var NavBarInner = ({
943
1163
  }
944
1164
  );
945
1165
  if (collapsed) {
946
- 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(React2__default.default.Fragment, { children: renderLink(item) }, item.key)) });
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)) });
947
1167
  }
948
1168
  const visibleItems = items.slice(0, visibleCount);
949
1169
  const overflowItems = items.slice(visibleCount);
@@ -1009,7 +1229,7 @@ var CARD_BORDER_WIDTH = 1;
1009
1229
  var CARD_TITLE_FONT_SIZE = 16;
1010
1230
  var CARD_MESSAGE_FONT_SIZE = 14;
1011
1231
  var CARD_TITLE_MARGIN_BOTTOM = 8;
1012
- var styles = reactNative.StyleSheet.create({
1232
+ var styles2 = reactNative.StyleSheet.create({
1013
1233
  centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
1014
1234
  card: {
1015
1235
  padding: CARD_PADDING,
@@ -1026,9 +1246,9 @@ function MessageCard({
1026
1246
  }) {
1027
1247
  const { theme } = uiFeedback.useUi();
1028
1248
  const colors = theme.colors;
1029
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles.card, { backgroundColor: colors.surface, borderColor: accentColor }], testID, children: [
1030
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.cardTitle, { color: accentColor }], children: message.titleText }),
1031
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.cardMessage, { color: colors.textSecondary }], children: message.messageText })
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 })
1032
1252
  ] });
1033
1253
  }
1034
1254
  function useContentBody(state, children, testID) {
@@ -1040,7 +1260,7 @@ function useContentBody(state, children, testID) {
1040
1260
  if (state?.error)
1041
1261
  return /* @__PURE__ */ jsxRuntime.jsx(MessageCard, { accentColor: errorColor, message: state.error, testID: `${testID}${APP_SHELL_SUFFIX.error}` });
1042
1262
  if (state?.loading === true)
1043
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles.centerFill, testID: `${testID}${APP_SHELL_SUFFIX.loading}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
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" }) });
1044
1264
  return children;
1045
1265
  }
1046
1266
  var SCRIM_COLOR = "rgba(0, 0, 0, 0.5)";
@@ -1064,7 +1284,7 @@ var DEFAULT_DRAWER_LABELS = {
1064
1284
  closeLabel: "Close menu",
1065
1285
  closeHint: "Close the navigation menu"
1066
1286
  };
1067
- var styles2 = reactNative.StyleSheet.create({
1287
+ var styles3 = reactNative.StyleSheet.create({
1068
1288
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: DRAWER_Z_INDEX },
1069
1289
  // The scrim's tap-to-close hit area is anchored to the RIGHT of the panel
1070
1290
  // (`left: DRAWER_WIDTH`), so it NEVER overlaps the panel: nav-item taps always
@@ -1099,10 +1319,10 @@ var MenuToggle = ({ label, hint, onOpen, testID }) => {
1099
1319
  accessibilityHint: hint,
1100
1320
  accessibilityLabel: label,
1101
1321
  ringColor: theme.palette.primary["500"],
1102
- style: styles2.menuToggle,
1322
+ style: styles3.menuToggle,
1103
1323
  testID,
1104
1324
  onPress: onOpen,
1105
- children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles2.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1325
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.menuToggleGlyph, { color: theme.colors.text }], children: MENU_GLYPH2 })
1106
1326
  }
1107
1327
  );
1108
1328
  };
@@ -1114,8 +1334,8 @@ var MobileDrawer = ({
1114
1334
  scrimTestID
1115
1335
  }) => {
1116
1336
  const { theme } = uiFeedback.useUi();
1117
- const panelRef = React2.useRef(null);
1118
- React2.useEffect(() => {
1337
+ const panelRef = React3.useRef(null);
1338
+ React3.useEffect(() => {
1119
1339
  const node = panelRef.current;
1120
1340
  if (node === null || typeof node.addEventListener !== "function") return void 0;
1121
1341
  const handleClick = (event) => {
@@ -1128,14 +1348,14 @@ var MobileDrawer = ({
1128
1348
  node.addEventListener("click", handleClick);
1129
1349
  return () => node.removeEventListener("click", handleClick);
1130
1350
  }, [onClose]);
1131
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.overlay, children: [
1351
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.overlay, children: [
1132
1352
  /* @__PURE__ */ jsxRuntime.jsx(
1133
1353
  reactNative.Pressable,
1134
1354
  {
1135
1355
  accessibilityHint: labels.closeHint,
1136
1356
  accessibilityLabel: labels.closeLabel,
1137
1357
  accessibilityRole: "button",
1138
- style: styles2.scrim,
1358
+ style: styles3.scrim,
1139
1359
  testID: scrimTestID,
1140
1360
  onPress: onClose
1141
1361
  }
@@ -1146,7 +1366,7 @@ var MobileDrawer = ({
1146
1366
  ref: panelRef,
1147
1367
  "aria-modal": true,
1148
1368
  role: "dialog",
1149
- style: [styles2.panel, { backgroundColor: theme.colors.surface }],
1369
+ style: [styles3.panel, { backgroundColor: theme.colors.surface }],
1150
1370
  testID: drawerTestID,
1151
1371
  children: renderTextSlot(sidebar, { color: theme.colors.text })
1152
1372
  }
@@ -1177,7 +1397,7 @@ function useContentMaxWidth(width) {
1177
1397
  return resolveContentMaxWidth(width, viewport);
1178
1398
  }
1179
1399
  var DEFAULT_CONTENT_PADDING = 24;
1180
- var styles3 = reactNative.StyleSheet.create({
1400
+ var styles4 = reactNative.StyleSheet.create({
1181
1401
  root: { flex: 1 },
1182
1402
  centerFill: { flex: 1, justifyContent: "center", alignItems: "center" },
1183
1403
  scroll: { flex: 1 },
@@ -1222,32 +1442,32 @@ var AppShell = ({
1222
1442
  range: railRange
1223
1443
  });
1224
1444
  const useDrawer = railMode === "drawer";
1225
- const [drawerOpen, setDrawerOpen] = React2.useState(false);
1226
- const openDrawer = React2.useCallback(() => setDrawerOpen(true), []);
1227
- const closeDrawer = React2.useCallback(() => setDrawerOpen(false), []);
1228
- React2.useEffect(() => {
1445
+ const [drawerOpen, setDrawerOpen] = React3.useState(false);
1446
+ const openDrawer = React3.useCallback(() => setDrawerOpen(true), []);
1447
+ const closeDrawer = React3.useCallback(() => setDrawerOpen(false), []);
1448
+ React3.useEffect(() => {
1229
1449
  if (!useDrawer && drawerOpen) setDrawerOpen(false);
1230
1450
  }, [useDrawer, drawerOpen]);
1231
1451
  const drawerLabels = { ...DEFAULT_DRAWER_LABELS, ...mobileMenu };
1232
1452
  const isUnauthenticated = gate !== void 0 && !gate.pending && !gate.authenticated;
1233
- React2.useEffect(() => {
1453
+ React3.useEffect(() => {
1234
1454
  if (isUnauthenticated && gate !== void 0) gate.onRedirect();
1235
1455
  }, [isUnauthenticated, gate]);
1236
1456
  if (gate?.pending === true)
1237
- return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles3.root, styles3.centerFill, { backgroundColor: theme.colors.background }], testID: `${testID}${APP_SHELL_SUFFIX.pending}`, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: primary, size: "large" }) });
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" }) });
1238
1458
  if (isUnauthenticated) return null;
1239
- const columnStyle = maxWidth === "full" ? styles3.columnFull : [styles3.columnCapped, { maxWidth }];
1240
- const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles3.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1459
+ const columnStyle = maxWidth === "full" ? styles4.columnFull : [styles4.columnCapped, { maxWidth }];
1460
+ const navInnerStyle = chromeAlignment === "content" && maxWidth !== "full" ? [styles4.chromeCapped, { maxWidth, paddingHorizontal: contentPadding }] : void 0;
1241
1461
  const scroller = /* @__PURE__ */ jsxRuntime.jsx(
1242
1462
  reactNative.ScrollView,
1243
1463
  {
1244
- contentContainerStyle: [styles3.scrollContent, { padding: contentPadding }],
1245
- style: styles3.scroll,
1464
+ contentContainerStyle: [styles4.scrollContent, { padding: contentPadding }],
1465
+ style: styles4.scroll,
1246
1466
  testID: `${testID}${APP_SHELL_SUFFIX.content}`,
1247
1467
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: columnStyle, children: body })
1248
1468
  }
1249
1469
  );
1250
- const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1470
+ const headerRegion = useDrawer ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.headerRow, testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: [
1251
1471
  /* @__PURE__ */ jsxRuntime.jsx(
1252
1472
  MenuToggle,
1253
1473
  {
@@ -1257,14 +1477,14 @@ var AppShell = ({
1257
1477
  onOpen: openDrawer
1258
1478
  }
1259
1479
  ),
1260
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
1480
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles4.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
1261
1481
  ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: renderTextSlot(header, { color: theme.colors.text }) });
1262
1482
  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;
1263
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.root, { backgroundColor: theme.colors.background }], testID, children: [
1483
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles4.root, { backgroundColor: theme.colors.background }], testID, children: [
1264
1484
  headerRegion,
1265
1485
  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,
1266
1486
  banner !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: renderTextSlot(banner, { color: theme.colors.text }) }) : null,
1267
- railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.bodyRow, children: [
1487
+ railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.bodyRow, children: [
1268
1488
  railRegion,
1269
1489
  scroller
1270
1490
  ] }) : scroller,
@@ -1482,7 +1702,7 @@ var DarkModeControl = ({
1482
1702
  const primaryColor = theme.palette.primary["500"];
1483
1703
  const currentIndex = options.findIndex((option) => option.value === value);
1484
1704
  const current = currentIndex >= 0 ? options[currentIndex] : options[0];
1485
- const advance = React2.useCallback(() => {
1705
+ const advance = React3.useCallback(() => {
1486
1706
  if (options.length === 0) return;
1487
1707
  const from = currentIndex >= 0 ? currentIndex : 0;
1488
1708
  const next = options[(from + 1) % options.length];
@@ -1558,7 +1778,7 @@ var KEY_DOWN = "ArrowDown";
1558
1778
  var KEY_UP = "ArrowUp";
1559
1779
  var KEY_ENTER = "Enter";
1560
1780
  var KEY_ESCAPE = "Escape";
1561
- var styles4 = reactNative.StyleSheet.create({
1781
+ var styles5 = reactNative.StyleSheet.create({
1562
1782
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: OVERLAY_Z_INDEX, alignItems: "center" },
1563
1783
  scrim: { ...reactNative.StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR2 },
1564
1784
  panel: {
@@ -1587,14 +1807,14 @@ var PaletteRow = ({ item, selected, onChoose }) => {
1587
1807
  accessibilityLabel: item.label,
1588
1808
  accessibilityRole: "button",
1589
1809
  accessibilityState: { selected },
1590
- style: [styles4.row, selected ? { backgroundColor: colors.border } : void 0],
1810
+ style: [styles5.row, selected ? { backgroundColor: colors.border } : void 0],
1591
1811
  testID: item.testID ?? item.key,
1592
1812
  onPress: () => onChoose(item),
1593
1813
  children: [
1594
1814
  typeof item.renderIcon === "function" ? item.renderIcon(colors.textSecondary, NAV_ICON_SIZE) : null,
1595
- /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.rowText, children: [
1596
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.rowLabel, { color: colors.text }], children: item.label }),
1597
- item.hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
1815
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.rowText, children: [
1816
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.rowLabel, { color: colors.text }], children: item.label }),
1817
+ item.hint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.rowHint, { color: colors.textSecondary }], children: item.hint }) : null
1598
1818
  ] })
1599
1819
  ]
1600
1820
  }
@@ -1603,27 +1823,27 @@ var PaletteRow = ({ item, selected, onChoose }) => {
1603
1823
  var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1604
1824
  const { theme } = uiFeedback.useUi();
1605
1825
  const colors = theme.colors;
1606
- const [query, setQuery] = React2.useState("");
1607
- const [selected, setSelected] = React2.useState(0);
1608
- const inputRef = React2.useRef(null);
1609
- const results = React2.useMemo(() => filterCommands(items, query), [items, query]);
1610
- React2.useEffect(() => {
1826
+ const [query, setQuery] = React3.useState("");
1827
+ const [selected, setSelected] = React3.useState(0);
1828
+ const inputRef = React3.useRef(null);
1829
+ const results = React3.useMemo(() => filterCommands(items, query), [items, query]);
1830
+ React3.useEffect(() => {
1611
1831
  if (!open) return;
1612
1832
  setQuery("");
1613
1833
  setSelected(0);
1614
1834
  inputRef.current?.focus();
1615
1835
  }, [open]);
1616
- React2.useEffect(() => {
1836
+ React3.useEffect(() => {
1617
1837
  setSelected((prev) => Math.min(prev, Math.max(0, results.length - 1)));
1618
1838
  }, [results.length]);
1619
- const choose = React2.useCallback(
1839
+ const choose = React3.useCallback(
1620
1840
  (item) => {
1621
1841
  item.onSelect();
1622
1842
  onClose();
1623
1843
  },
1624
1844
  [onClose]
1625
1845
  );
1626
- const onKeyPress = React2.useCallback(
1846
+ const onKeyPress = React3.useCallback(
1627
1847
  (event) => {
1628
1848
  const key = event.nativeEvent.key;
1629
1849
  if (key === KEY_DOWN) setSelected((i) => Math.min(i + 1, results.length - 1));
@@ -1634,14 +1854,14 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1634
1854
  [results, selected, choose, onClose]
1635
1855
  );
1636
1856
  if (!open) return null;
1637
- return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles4.overlay, children: [
1857
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles5.overlay, children: [
1638
1858
  /* @__PURE__ */ jsxRuntime.jsx(
1639
1859
  reactNative.Pressable,
1640
1860
  {
1641
1861
  accessibilityHint: labels.closeHint,
1642
1862
  accessibilityLabel: labels.closeLabel,
1643
1863
  accessibilityRole: "button",
1644
- style: styles4.scrim,
1864
+ style: styles5.scrim,
1645
1865
  testID: `${testID ?? "command-palette"}-scrim`,
1646
1866
  onPress: onClose
1647
1867
  }
@@ -1652,7 +1872,7 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1652
1872
  "aria-modal": true,
1653
1873
  "aria-label": labels.regionLabel,
1654
1874
  role: "dialog",
1655
- style: [styles4.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
1875
+ style: [styles5.panel, { backgroundColor: colors.surface, borderColor: colors.border }],
1656
1876
  testID: testID ?? "command-palette",
1657
1877
  children: [
1658
1878
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1662,21 +1882,21 @@ var CommandPalette = ({ open, items, onClose, labels, testID }) => {
1662
1882
  accessibilityLabel: labels.placeholder,
1663
1883
  placeholder: labels.placeholder,
1664
1884
  placeholderTextColor: colors.textSecondary,
1665
- style: [styles4.input, { color: colors.text, borderBottomColor: colors.border }],
1885
+ style: [styles5.input, { color: colors.text, borderBottomColor: colors.border }],
1666
1886
  testID: `${testID ?? "command-palette"}-input`,
1667
1887
  value: query,
1668
1888
  onChangeText: setQuery,
1669
1889
  onKeyPress
1670
1890
  }
1671
1891
  ),
1672
- results.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles4.empty, { color: colors.textSecondary }], children: labels.emptyText }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { keyboardShouldPersistTaps: "handled", style: styles4.list, children: results.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(PaletteRow, { item, selected: index === selected, onChoose: choose }, item.key)) })
1892
+ 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)) })
1673
1893
  ]
1674
1894
  }
1675
1895
  )
1676
1896
  ] });
1677
1897
  };
1678
1898
  var MIN_TARGET = 36;
1679
- var styles5 = reactNative.StyleSheet.create({
1899
+ var styles6 = reactNative.StyleSheet.create({
1680
1900
  trigger: {
1681
1901
  flexDirection: "row",
1682
1902
  alignItems: "center",
@@ -1701,7 +1921,7 @@ var CommandPaletteTrigger = ({
1701
1921
  const { theme } = uiFeedback.useUi();
1702
1922
  const colors = theme.colors;
1703
1923
  const primary = theme.palette.primary["500"];
1704
- const [focused, setFocused] = React2__default.default.useState(false);
1924
+ const [focused, setFocused] = React3__default.default.useState(false);
1705
1925
  return /* @__PURE__ */ jsxRuntime.jsxs(
1706
1926
  reactNative.Pressable,
1707
1927
  {
@@ -1709,7 +1929,7 @@ var CommandPaletteTrigger = ({
1709
1929
  accessibilityLabel: label,
1710
1930
  accessibilityRole: "button",
1711
1931
  style: [
1712
- styles5.trigger,
1932
+ styles6.trigger,
1713
1933
  { backgroundColor: colors.surface, borderColor: colors.border },
1714
1934
  focusRingStyle(focused, primary)
1715
1935
  ],
@@ -1718,15 +1938,15 @@ var CommandPaletteTrigger = ({
1718
1938
  onFocus: () => setFocused(true),
1719
1939
  onPress,
1720
1940
  children: [
1721
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.label, { color: colors.textSecondary }], children: label }),
1722
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles5.badge, { borderColor: colors.border }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles5.badgeText, { color: colors.textSecondary }], children: shortcut }) })
1941
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.label, { color: colors.textSecondary }], children: label }),
1942
+ /* @__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 }) })
1723
1943
  ]
1724
1944
  }
1725
1945
  );
1726
1946
  };
1727
1947
  var LAUNCH_KEY = "k";
1728
1948
  function useCommandPaletteHotkey(onOpen, enabled = true) {
1729
- React2.useEffect(() => {
1949
+ React3.useEffect(() => {
1730
1950
  if (!enabled || typeof document === "undefined") return void 0;
1731
1951
  const handler = (event) => {
1732
1952
  const isLauncher = event.key.toLowerCase() === LAUNCH_KEY && (event.metaKey || event.ctrlKey);
@@ -1750,6 +1970,7 @@ function accessibleNavItems(user, table, translate) {
1750
1970
  }
1751
1971
 
1752
1972
  exports.ACTIVE_BORDER_RADIUS = ACTIVE_BORDER_RADIUS;
1973
+ exports.ACTIVE_TINT_OPACITY = ACTIVE_TINT_OPACITY;
1753
1974
  exports.APP_SHELL_SUFFIX = APP_SHELL_SUFFIX;
1754
1975
  exports.AppShell = AppShell;
1755
1976
  exports.BASE_INDENT = BASE_INDENT;
@@ -1758,9 +1979,12 @@ exports.CollapsedRail = CollapsedRail;
1758
1979
  exports.CommandPalette = CommandPalette;
1759
1980
  exports.CommandPaletteTrigger = CommandPaletteTrigger;
1760
1981
  exports.DEFAULT_COLLAPSED_RAIL_MAX = DEFAULT_COLLAPSED_RAIL_MAX;
1982
+ exports.DEFAULT_SEARCH_BREAKPOINT = DEFAULT_SEARCH_BREAKPOINT;
1761
1983
  exports.DarkModeControl = DarkModeControl;
1984
+ exports.HOVER_TINT_OPACITY = HOVER_TINT_OPACITY;
1762
1985
  exports.NAV_ICON_SIZE = NAV_ICON_SIZE;
1763
1986
  exports.NAV_LINK_GAP = NAV_LINK_GAP;
1987
+ exports.NAV_ROW_RADIUS = NAV_ROW_RADIUS;
1764
1988
  exports.NAV_TEST_IDS = NAV_TEST_IDS;
1765
1989
  exports.Nav = Nav;
1766
1990
  exports.NavBar = NavBar;
@@ -1770,18 +1994,24 @@ exports.NavShell = NavShell;
1770
1994
  exports.PillNav = PillNav;
1771
1995
  exports.RAIL_FULL_BREAKPOINT = RAIL_FULL_BREAKPOINT;
1772
1996
  exports.Sidebar = Sidebar;
1997
+ exports.SidebarChevron = SidebarChevron;
1998
+ exports.SidebarSearch = SidebarSearch;
1773
1999
  exports.Topbar = Topbar;
1774
2000
  exports.accessibleNavItems = accessibleNavItems;
1775
2001
  exports.collapsedRailStyles = collapsedRailStyles;
1776
2002
  exports.darkModeStyles = darkModeStyles;
1777
2003
  exports.expandableStyles = expandableStyles;
1778
2004
  exports.filterCommands = filterCommands;
2005
+ exports.filterNavItems = filterNavItems;
2006
+ exports.isFilterActive = isFilterActive;
1779
2007
  exports.isRouteActive = isRouteActive;
1780
2008
  exports.navStyles = navStyles;
1781
2009
  exports.pillNavStyles = pillNavStyles;
1782
2010
  exports.resolveContentMaxWidth = resolveContentMaxWidth;
1783
2011
  exports.resolveRailMode = resolveRailMode;
2012
+ exports.resolveSearchAffordance = resolveSearchAffordance;
1784
2013
  exports.roleRoutesToNavItems = roleRoutesToNavItems;
2014
+ exports.searchTokens = searchTokens;
1785
2015
  exports.useCommandPaletteHotkey = useCommandPaletteHotkey;
1786
2016
  exports.useContentMaxWidth = useContentMaxWidth;
1787
2017
  //# sourceMappingURL=index.js.map