@dloizides/ui-nav 1.10.1 → 1.11.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/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.11.0
4
+
5
+ - **Feature (an expandable nav group now auto-opens when the current route is inside it).**
6
+ `NavExpandableItem` used to mount every group collapsed (`useState(false)`), so on a fresh
7
+ page load the group containing the active route was closed and its active leaf was hidden.
8
+ A persistent-rail console that groups its nav by module (e.g. finreg-web's Payments / CRM /
9
+ Accounting rail) needs the active module's group open on load, the others collapsed — with
10
+ no way for the caller to express that, since expansion state was internal and uncontrolled.
11
+ - The group now initializes its expansion from whether the current route is inside it:
12
+ `useState(() => hasActiveDescendant(item, pathname))`, where `hasActiveDescendant` is true
13
+ when the item's own route is active (`isRouteActive`) or any descendant's is (recursive, so
14
+ it holds at any nesting depth). The group of the active route mounts EXPANDED (active leaf
15
+ visible + highlighted); a group with no active descendant mounts COLLAPSED — exactly the
16
+ prior behaviour.
17
+ - **No API change.** `NavItem` is unchanged — the behaviour is derived from the `pathname`
18
+ the component already receives. Consumers with no expandable groups, or whose groups hold no
19
+ active route, are byte-for-byte unaffected; all 7 portals gain the auto-open on their next
20
+ bump for free.
21
+ - Guarded by `src/navExpandableItem.test.tsx` (RED-first): the two "mounts EXPANDED" assertions
22
+ FAIL against the pre-change `useState(false)` (the active group's child is absent from the DOM)
23
+ and pass after; the "mounts COLLAPSED" + "header always renders" assertions lock the
24
+ backward-compatible half.
25
+
26
+ ## 1.10.2
27
+
28
+ - **Fix (the mobile nav DRAWER was not clickable — a tap on a nav item did not navigate, it
29
+ just closed the drawer / did nothing, in all seven portals below 768px).**
30
+ `MobileDrawer` rendered a full-bleed scrim `Pressable` (`StyleSheet.absoluteFillObject`) and a
31
+ panel `View` with `{ position: 'absolute', top: 0, bottom: 0, left: 0 }` — **no width and no
32
+ zIndex**. Reproduced structurally: the panel's computed `width` was empty and its `zIndex` was
33
+ `0`; the scrim's computed `left`/`right` were both `0px` at `zIndex` `0`. So the scrim FULLY
34
+ overlapped the panel at the SAME stacking level, and on react-native-web the equal-zIndex scrim
35
+ won the pointer (the estate's known "RN-web Pressable swallows a sibling/child press" trap):
36
+ the tap closed the drawer and the nav-item's `TouchableOpacity` `onPress` (→ the app's router)
37
+ never fired.
38
+ - Fixed once, in the kit: the panel now has an explicit drawer **width** (`280px`), a solid
39
+ surface background, and a `zIndex` **above** the scrim; the scrim is anchored BESIDE the panel
40
+ (`left: DRAWER_WIDTH`) so its tap-to-close hit area never overlaps the panel. Nav-item taps
41
+ reach the `Sidebar` leaves; taps beside the drawer still close it.
42
+ - Guarded by `src/mobileDrawer.test.tsx` (RED-first): three structural assertions read the
43
+ resolved geometry via `getComputedStyle` and FAIL against the pre-fix panel/scrim (no width,
44
+ equal zIndex, overlapping scrim); two behavioural assertions lock the contract (nav-item tap
45
+ navigates + closes; scrim tap only closes).
46
+ - **No API change.** Consumers need no edit — bump the dependency and the drawer works.
47
+
3
48
  ## 1.10.1
4
49
 
5
50
  - **Fix (a plain-string slot logged `console.error` on EVERY render, in all seven portals).**
package/dist/index.js CHANGED
@@ -289,6 +289,10 @@ function hoverTransitionStyle(reducedMotion) {
289
289
  function ariaCurrentProps(isActive) {
290
290
  return isActive ? { "aria-current": "page" } : {};
291
291
  }
292
+ function hasActiveDescendant(item, pathname) {
293
+ if (isRouteActive(pathname, item.route)) return true;
294
+ return (item.children ?? []).some((child) => hasActiveDescendant(child, pathname));
295
+ }
292
296
  var NavExpandableItem = ({
293
297
  item,
294
298
  pathname,
@@ -299,7 +303,7 @@ var NavExpandableItem = ({
299
303
  renderChevron,
300
304
  depth = 0
301
305
  }) => {
302
- const [expanded, setExpanded] = React2.useState(false);
306
+ const [expanded, setExpanded] = React2.useState(() => hasActiveDescendant(item, pathname));
303
307
  const [focused, setFocused] = React2.useState(false);
304
308
  const { theme } = uiFeedback.useUi();
305
309
  const colors = theme.colors;
@@ -1004,6 +1008,9 @@ var MENU_MIN_TARGET = 44;
1004
1008
  var MENU_GLYPH_FONT_SIZE = 20;
1005
1009
  var MENU_TOGGLE_PADDING = 12;
1006
1010
  var DRAWER_Z_INDEX = 50;
1011
+ var DRAWER_WIDTH = 280;
1012
+ var SCRIM_Z_INDEX = 1;
1013
+ var PANEL_Z_INDEX = 2;
1007
1014
  var NAV_ACTIVATABLE_SELECTOR = 'a, [role="button"], [role="link"]';
1008
1015
  var DEFAULT_DRAWER_LABELS = {
1009
1016
  openLabel: "Menu",
@@ -1013,8 +1020,22 @@ var DEFAULT_DRAWER_LABELS = {
1013
1020
  };
1014
1021
  var styles2 = reactNative.StyleSheet.create({
1015
1022
  overlay: { ...reactNative.StyleSheet.absoluteFillObject, zIndex: DRAWER_Z_INDEX },
1016
- scrim: { ...reactNative.StyleSheet.absoluteFillObject, backgroundColor: SCRIM_COLOR },
1017
- panel: { position: "absolute", top: 0, bottom: 0, left: 0 },
1023
+ // The scrim's tap-to-close hit area is anchored to the RIGHT of the panel
1024
+ // (`left: DRAWER_WIDTH`), so it NEVER overlaps the panel: nav-item taps always
1025
+ // reach the Sidebar's TouchableOpacity, while taps beside the drawer still close.
1026
+ scrim: {
1027
+ position: "absolute",
1028
+ top: 0,
1029
+ bottom: 0,
1030
+ left: DRAWER_WIDTH,
1031
+ right: 0,
1032
+ backgroundColor: SCRIM_COLOR,
1033
+ zIndex: SCRIM_Z_INDEX
1034
+ },
1035
+ // The panel gets an explicit width (a real drawer) AND a zIndex above the scrim,
1036
+ // so on react-native-web it — and the nav leaves inside it — sit above the scrim
1037
+ // and receive presses instead of the scrim swallowing them.
1038
+ panel: { position: "absolute", top: 0, bottom: 0, left: 0, width: DRAWER_WIDTH, zIndex: PANEL_Z_INDEX },
1018
1039
  menuToggle: {
1019
1040
  minWidth: MENU_MIN_TARGET,
1020
1041
  minHeight: MENU_MIN_TARGET,
@@ -1071,7 +1092,17 @@ var MobileDrawer = ({
1071
1092
  onPress: onClose
1072
1093
  }
1073
1094
  ),
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 }) })
1095
+ /* @__PURE__ */ jsxRuntime.jsx(
1096
+ reactNative.View,
1097
+ {
1098
+ ref: panelRef,
1099
+ "aria-modal": true,
1100
+ role: "dialog",
1101
+ style: [styles2.panel, { backgroundColor: theme.colors.surface }],
1102
+ testID: drawerTestID,
1103
+ children: renderTextSlot(sidebar, { color: theme.colors.text })
1104
+ }
1105
+ )
1075
1106
  ] });
1076
1107
  };
1077
1108