@dloizides/ui-nav 1.10.0 → 1.10.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.10.1
4
+
5
+ - **Fix (a plain-string slot logged `console.error` on EVERY render, in all seven portals).**
6
+ Every caller-supplied slot in the kit is typed `React.ReactNode`, which legitimately includes
7
+ `string` — and callers pass exactly that (`brand: FM('app.name')`, a plain localized string).
8
+ The slot was dropped straight into a `<View>`, and react-native-web refuses to render a bare
9
+ text node there: `Unexpected text node: <s>. A text node cannot be a child of a <View>.`
10
+ Nothing in the type system objected, so the defect only ever surfaced as browser console
11
+ noise — which is why it survived to 1.10.0 across the whole fleet.
12
+ - Fixed once, in the kit, via the new internal `renderTextSlot` helper: a `string`/`number`
13
+ slot is wrapped in `<Text>` (routed through the theme's `colors.text`, so it honours dark
14
+ mode rather than defaulting to black); an element slot passes through **untouched**, so
15
+ every existing caller renders byte-identically. Array slots have each string member wrapped
16
+ individually, keys preserved.
17
+ - Applied to **all 12 slot render sites**, not just the reported one — `NavBar` (`brand`,
18
+ `right`), `Topbar` (`left`, `notificationSlot`), `Sidebar` (`header`, `footer`),
19
+ `CollapsedRail` (`header`, `footer`), `AppShell` (`header`, `nav`, `sidebar`,
20
+ `collapsedSidebar`, `banner`) and `MobileDrawer` (`sidebar`).
21
+ - Guarded by `src/rawTextSlots.test.tsx`, which spies on `console.error` and asserts silence.
22
+ Unit tests that only assert "the slot rendered" pass against the bug; this one does not.
23
+ - **No API change.** Consumers need no edit — the `<Text>` wrapper they were expected to
24
+ remember is now the kit's job.
25
+
3
26
  ## 1.10.0
4
27
 
5
28
  - **Fix (`layout` was not authoritative — `layout="side"` rendered a top bar anyway).**
package/dist/index.js CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  var reactNative = require('react-native');
4
4
  var uiFeedback = require('@dloizides/ui-feedback');
5
- var React5 = require('react');
5
+ var React2 = require('react');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var uiLayout = require('@dloizides/ui-layout');
8
8
  var authWeb = require('@dloizides/auth-web');
9
9
 
10
10
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
11
 
12
- var React5__default = /*#__PURE__*/_interopDefault(React5);
12
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
13
13
 
14
14
  // src/Sidebar.tsx
15
15
 
@@ -299,21 +299,21 @@ var NavExpandableItem = ({
299
299
  renderChevron,
300
300
  depth = 0
301
301
  }) => {
302
- const [expanded, setExpanded] = React5.useState(false);
303
- const [focused, setFocused] = React5.useState(false);
302
+ const [expanded, setExpanded] = React2.useState(false);
303
+ const [focused, setFocused] = React2.useState(false);
304
304
  const { theme } = uiFeedback.useUi();
305
305
  const colors = theme.colors;
306
306
  const primaryColor = theme.palette.primary["500"];
307
- const toggle = React5.useCallback(() => setExpanded((v) => !v), []);
307
+ const toggle = React2.useCallback(() => setExpanded((v) => !v), []);
308
308
  const indent = depth * BASE_INDENT;
309
309
  const hasChildren = Array.isArray(item.children) && item.children.length > 0;
310
310
  const isActive = isRouteActive(pathname, item.route);
311
- const activeItemStyle = React5.useMemo(
311
+ const activeItemStyle = React2.useMemo(
312
312
  () => ({ backgroundColor: colors.border, borderRadius: ACTIVE_BORDER_RADIUS }),
313
313
  [colors.border]
314
314
  );
315
- const paddingStyle = React5.useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
316
- const headerPaddingStyle = React5.useMemo(() => ({ paddingLeft: indent }), [indent]);
315
+ const paddingStyle = React2.useMemo(() => ({ paddingLeft: indent + BASE_INDENT }), [indent]);
316
+ const headerPaddingStyle = React2.useMemo(() => ({ paddingLeft: indent }), [indent]);
317
317
  if (!hasChildren)
318
318
  return /* @__PURE__ */ jsxRuntime.jsxs(
319
319
  reactNative.TouchableOpacity,
@@ -384,6 +384,17 @@ var NavExpandableItem = ({
384
384
  )) }) : null
385
385
  ] });
386
386
  };
387
+ function isBareText(node) {
388
+ return typeof node === "string" || typeof node === "number";
389
+ }
390
+ function renderTextSlot(node, style) {
391
+ if (node === void 0 || node === null) return node;
392
+ if (isBareText(node)) return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style, children: node });
393
+ return React2__default.default.Children.map(
394
+ node,
395
+ (child) => isBareText(child) ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style, children: child }) : child
396
+ );
397
+ }
387
398
  var Sidebar = ({
388
399
  items,
389
400
  pathname,
@@ -413,7 +424,7 @@ var Sidebar = ({
413
424
  ],
414
425
  children: [
415
426
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { accessibilityRole: "header", style: [navStyles.sidebarTitle, { color: colors.text }], children: title }),
416
- header,
427
+ renderTextSlot(header, { color: colors.text }),
417
428
  items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
418
429
  NavExpandableItem,
419
430
  {
@@ -428,7 +439,7 @@ var Sidebar = ({
428
439
  item.key
429
440
  )),
430
441
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.sidebarSpacer }),
431
- footer
442
+ renderTextSlot(footer, { color: colors.text })
432
443
  ]
433
444
  }
434
445
  );
@@ -481,7 +492,7 @@ var FocusableTouchable = ({
481
492
  testID,
482
493
  children
483
494
  }) => {
484
- const [focused, setFocused] = React5.useState(false);
495
+ const [focused, setFocused] = React2.useState(false);
485
496
  return /* @__PURE__ */ jsxRuntime.jsx(
486
497
  reactNative.Pressable,
487
498
  {
@@ -531,7 +542,7 @@ var Topbar = ({
531
542
  containerStyle
532
543
  ],
533
544
  children: [
534
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.topbarLeft, children: left }),
545
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navStyles.topbarLeft, children: renderTextSlot(left, { color: colors.text }) }),
535
546
  /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.topbarRight, children: [
536
547
  language ? /* @__PURE__ */ jsxRuntime.jsx(
537
548
  FocusableTouchable,
@@ -545,7 +556,7 @@ var Topbar = ({
545
556
  children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.topbarLabel, { color: colors.text }], children: language.label })
546
557
  }
547
558
  ) : null,
548
- notificationSlot,
559
+ renderTextSlot(notificationSlot, { color: colors.text }),
549
560
  user ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: navStyles.userBlock, children: [
550
561
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userName, { color: colors.text }], children: user.name }),
551
562
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navStyles.userEmail, { color: colors.textSecondary }], children: user.email })
@@ -634,8 +645,8 @@ var NavBarLink = ({
634
645
  navigateHint,
635
646
  onPress
636
647
  }) => {
637
- const [hovered, setHovered] = React5.useState(false);
638
- const [focused, setFocused] = React5.useState(false);
648
+ const [hovered, setHovered] = React2.useState(false);
649
+ const [focused, setFocused] = React2.useState(false);
639
650
  const isHovered = hovered && !isActive;
640
651
  const textColor = isActive ? TEXT_ON_PRIMARY2 : isHovered ? colors.hoverText : colors.rest;
641
652
  const pillStyle = isActive ? { backgroundColor: colors.activeBg } : isHovered ? { backgroundColor: colors.hoverBg } : void 0;
@@ -679,12 +690,12 @@ var NavOverflowMenu = ({
679
690
  testID,
680
691
  variant
681
692
  }) => {
682
- const options = React5.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
683
- const activeRoute = React5.useMemo(
693
+ const options = React2.useMemo(() => items.map((item) => ({ label: item.label, value: item.route })), [items]);
694
+ const activeRoute = React2.useMemo(
684
695
  () => items.find((item) => isRouteActive(pathname, item.route))?.route ?? NO_ACTIVE_ROUTE,
685
696
  [items, pathname]
686
697
  );
687
- const optionTestID = React5.useMemo(() => {
698
+ const optionTestID = React2.useMemo(() => {
688
699
  const byRoute = new Map(items.map((item) => [item.route, item.testID ?? item.key]));
689
700
  return (route) => byRoute.get(route) ?? `${testID}-option-${route}`;
690
701
  }, [items, testID]);
@@ -741,13 +752,13 @@ function resizeWidthBuffer(previous, count) {
741
752
  return next;
742
753
  }
743
754
  function useNavOverflow(itemCount, gap) {
744
- const [availableWidth, setAvailableWidthState] = React5.useState(0);
745
- const [moreWidth, setMoreWidthState] = React5.useState(0);
746
- const [itemWidths, setItemWidths] = React5.useState(() => new Array(itemCount).fill(0));
747
- React5.useEffect(() => {
755
+ const [availableWidth, setAvailableWidthState] = React2.useState(0);
756
+ const [moreWidth, setMoreWidthState] = React2.useState(0);
757
+ const [itemWidths, setItemWidths] = React2.useState(() => new Array(itemCount).fill(0));
758
+ React2.useEffect(() => {
748
759
  setItemWidths((previous) => previous.length === itemCount ? previous : resizeWidthBuffer(previous, itemCount));
749
760
  }, [itemCount]);
750
- const setItemWidth = React5.useCallback((index, width) => {
761
+ const setItemWidth = React2.useCallback((index, width) => {
751
762
  setItemWidths((previous) => {
752
763
  if (index < 0 || index >= previous.length || previous[index] === width) return previous;
753
764
  const next = previous.slice();
@@ -755,15 +766,15 @@ function useNavOverflow(itemCount, gap) {
755
766
  return next;
756
767
  });
757
768
  }, []);
758
- const setAvailableWidth = React5.useCallback(
769
+ const setAvailableWidth = React2.useCallback(
759
770
  (width) => setAvailableWidthState((previous) => previous === width ? previous : width),
760
771
  []
761
772
  );
762
- const setMoreWidth = React5.useCallback(
773
+ const setMoreWidth = React2.useCallback(
763
774
  (width) => setMoreWidthState((previous) => previous === width ? previous : width),
764
775
  []
765
776
  );
766
- const visibleCount = React5.useMemo(
777
+ const visibleCount = React2.useMemo(
767
778
  () => computeVisibleCount({ availableWidth, itemWidths, moreWidth, gap }),
768
779
  [availableWidth, itemWidths, moreWidth, gap]
769
780
  );
@@ -775,8 +786,8 @@ function getReducedMotionQuery() {
775
786
  return window.matchMedia(REDUCED_MOTION_QUERY);
776
787
  }
777
788
  function useReducedMotion() {
778
- const [reduced, setReduced] = React5.useState(() => getReducedMotionQuery()?.matches ?? false);
779
- React5.useEffect(() => {
789
+ const [reduced, setReduced] = React2.useState(() => getReducedMotionQuery()?.matches ?? false);
790
+ React2.useEffect(() => {
780
791
  const mql = getReducedMotionQuery();
781
792
  if (mql === void 0 || mql.addEventListener === void 0) return void 0;
782
793
  const onChange = () => setReduced(mql.matches);
@@ -815,24 +826,24 @@ var NavBarInner = ({
815
826
  const primaryColor = theme.palette.primary["500"];
816
827
  const { width } = reactNative.useWindowDimensions();
817
828
  const reducedMotion = useReducedMotion();
818
- const [open, setOpen] = React5.useState(false);
819
- const [toggleFocused, setToggleFocused] = React5.useState(false);
829
+ const [open, setOpen] = React2.useState(false);
830
+ const [toggleFocused, setToggleFocused] = React2.useState(false);
820
831
  const { visibleCount, setAvailableWidth, setItemWidth, setMoreWidth } = useNavOverflow(items.length, NAV_LINK_GAP);
821
832
  const collapsed = width < collapseBelow;
822
833
  const showLinks = !collapsed || open;
823
- const innerCapStyle = React5.useMemo(
834
+ const innerCapStyle = React2.useMemo(
824
835
  () => contentMaxWidth === void 0 ? null : { maxWidth: contentMaxWidth, width: "100%", alignSelf: "center" },
825
836
  [contentMaxWidth]
826
837
  );
827
- const toggleMenu = React5.useCallback(() => setOpen((v) => !v), []);
828
- const handlePress = React5.useCallback(
838
+ const toggleMenu = React2.useCallback(() => setOpen((v) => !v), []);
839
+ const handlePress = React2.useCallback(
829
840
  (route) => {
830
841
  setOpen(false);
831
842
  onNavigate(route);
832
843
  },
833
844
  [onNavigate]
834
845
  );
835
- const linkColors = React5.useMemo(
846
+ const linkColors = React2.useMemo(
836
847
  () => ({
837
848
  rest: colors.textSecondary,
838
849
  hoverText: colors.text,
@@ -854,7 +865,7 @@ var NavBarInner = ({
854
865
  containerStyle
855
866
  ],
856
867
  children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [navBarStyles.inner, collapsed ? null : navBarStyles.innerNoWrap, innerCapStyle], children: [
857
- brand !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.brand, children: brand }) : null,
868
+ brand !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.brand, children: renderTextSlot(brand, { color: colors.text }) }) : null,
858
869
  collapsed ? /* @__PURE__ */ jsxRuntime.jsx(
859
870
  reactNative.Pressable,
860
871
  {
@@ -887,7 +898,7 @@ var NavBarInner = ({
887
898
  }
888
899
  );
889
900
  if (collapsed) {
890
- 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(React5__default.default.Fragment, { children: renderLink(item) }, item.key)) });
901
+ 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)) });
891
902
  }
892
903
  const visibleItems = items.slice(0, visibleCount);
893
904
  const overflowItems = items.slice(visibleCount);
@@ -931,7 +942,7 @@ var NavBarInner = ({
931
942
  }
932
943
  );
933
944
  })() : null,
934
- showLinks && right !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsed ? navBarStyles.rightStacked : navBarStyles.right, children: right }) : null
945
+ showLinks && right !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsed ? navBarStyles.rightStacked : navBarStyles.right, children: renderTextSlot(right, { color: colors.text }) }) : null
935
946
  ] })
936
947
  }
937
948
  );
@@ -1035,8 +1046,9 @@ var MobileDrawer = ({
1035
1046
  drawerTestID,
1036
1047
  scrimTestID
1037
1048
  }) => {
1038
- const panelRef = React5.useRef(null);
1039
- React5.useEffect(() => {
1049
+ const { theme } = uiFeedback.useUi();
1050
+ const panelRef = React2.useRef(null);
1051
+ React2.useEffect(() => {
1040
1052
  const node = panelRef.current;
1041
1053
  if (node === null || typeof node.addEventListener !== "function") return void 0;
1042
1054
  const handleClick = (event) => {
@@ -1059,7 +1071,7 @@ var MobileDrawer = ({
1059
1071
  onPress: onClose
1060
1072
  }
1061
1073
  ),
1062
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { ref: panelRef, "aria-modal": true, role: "dialog", style: styles2.panel, testID: drawerTestID, children: sidebar })
1074
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { ref: panelRef, "aria-modal": true, role: "dialog", style: styles2.panel, testID: drawerTestID, children: renderTextSlot(sidebar, { color: theme.colors.text }) })
1063
1075
  ] });
1064
1076
  };
1065
1077
 
@@ -1131,15 +1143,15 @@ var AppShell = ({
1131
1143
  range: railRange
1132
1144
  });
1133
1145
  const useDrawer = railMode === "drawer";
1134
- const [drawerOpen, setDrawerOpen] = React5.useState(false);
1135
- const openDrawer = React5.useCallback(() => setDrawerOpen(true), []);
1136
- const closeDrawer = React5.useCallback(() => setDrawerOpen(false), []);
1137
- React5.useEffect(() => {
1146
+ const [drawerOpen, setDrawerOpen] = React2.useState(false);
1147
+ const openDrawer = React2.useCallback(() => setDrawerOpen(true), []);
1148
+ const closeDrawer = React2.useCallback(() => setDrawerOpen(false), []);
1149
+ React2.useEffect(() => {
1138
1150
  if (!useDrawer && drawerOpen) setDrawerOpen(false);
1139
1151
  }, [useDrawer, drawerOpen]);
1140
1152
  const drawerLabels = { ...DEFAULT_DRAWER_LABELS, ...mobileMenu };
1141
1153
  const isUnauthenticated = gate !== void 0 && !gate.pending && !gate.authenticated;
1142
- React5.useEffect(() => {
1154
+ React2.useEffect(() => {
1143
1155
  if (isUnauthenticated && gate !== void 0) gate.onRedirect();
1144
1156
  }, [isUnauthenticated, gate]);
1145
1157
  if (gate?.pending === true)
@@ -1166,13 +1178,13 @@ var AppShell = ({
1166
1178
  onOpen: openDrawer
1167
1179
  }
1168
1180
  ),
1169
- /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.headerFill, children: header })
1170
- ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: header });
1171
- const railRegion = railMode === "full" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.sidebar}`, children: sidebar }) : railMode === "collapsed" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.collapsedSidebar}`, children: collapsedSidebar }) : null;
1181
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.headerFill, children: renderTextSlot(header, { color: theme.colors.text }) })
1182
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.header}`, children: renderTextSlot(header, { color: theme.colors.text }) });
1183
+ 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;
1172
1184
  return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles3.root, { backgroundColor: theme.colors.background }], testID, children: [
1173
1185
  headerRegion,
1174
- 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: nav }) : nav }) : null,
1175
- banner !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: banner }) : null,
1186
+ 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,
1187
+ banner !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: `${testID}${APP_SHELL_SUFFIX.banner}`, children: renderTextSlot(banner, { color: theme.colors.text }) }) : null,
1176
1188
  railRegion !== null ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.bodyRow, children: [
1177
1189
  railRegion,
1178
1190
  scroller
@@ -1218,7 +1230,7 @@ var CollapsedRail = ({
1218
1230
  containerStyle
1219
1231
  ],
1220
1232
  children: [
1221
- header !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: header }) : null,
1233
+ header !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: renderTextSlot(header, { color: colors.text }) }) : null,
1222
1234
  items.map((item) => {
1223
1235
  const active = isRouteActive(pathname, item.route);
1224
1236
  const iconColor = active ? primaryColor : colors.textSecondary;
@@ -1237,7 +1249,7 @@ var CollapsedRail = ({
1237
1249
  );
1238
1250
  }),
1239
1251
  /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.spacer }),
1240
- footer !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: footer }) : null
1252
+ footer !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: collapsedRailStyles.slot, children: renderTextSlot(footer, { color: colors.text }) }) : null
1241
1253
  ]
1242
1254
  }
1243
1255
  );
@@ -1391,7 +1403,7 @@ var DarkModeControl = ({
1391
1403
  const primaryColor = theme.palette.primary["500"];
1392
1404
  const currentIndex = options.findIndex((option) => option.value === value);
1393
1405
  const current = currentIndex >= 0 ? options[currentIndex] : options[0];
1394
- const advance = React5.useCallback(() => {
1406
+ const advance = React2.useCallback(() => {
1395
1407
  if (options.length === 0) return;
1396
1408
  const from = currentIndex >= 0 ? currentIndex : 0;
1397
1409
  const next = options[(from + 1) % options.length];