ng-hub-ui-nav 22.14.0 β†’ 22.14.2

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/README.md CHANGED
@@ -3,12 +3,12 @@
3
3
  [EspaΓ±ol](./README.es.md) | **English**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/ng-hub-ui-nav.svg)](https://www.npmjs.com/package/ng-hub-ui-nav)
6
- [![license](https://img.shields.io/npm/l/ng-hub-ui-nav.svg)](https://github.com/carlos-morcillo/ng-hub-ui-nav/blob/main/LICENSE)
6
+ [![license](https://img.shields.io/npm/l/ng-hub-ui-nav.svg)](https://github.com/hub-env/ng-hub-ui-nav/blob/main/LICENSE)
7
7
 
8
8
  A flexible, accessible, and highly customizable navigation component for Angular 21+. It supports horizontal menus, vertical sidebars, mobile collapse modes, stacked drill-down panels, projected start/end slots, and scroll-spy integration.
9
9
 
10
10
  > [!IMPORTANT]
11
- > Version `22.14.0` targets Angular 21 and follows the signal-first architecture used across `ng-hub-ui`.
11
+ > Version `22.14.2` targets Angular 22 and follows the signal-first architecture used across `ng-hub-ui`.
12
12
 
13
13
  ## Documentation and Live Examples
14
14
 
@@ -17,6 +17,7 @@ This package is part of [Hub UI](https://hubui.dev/en/), a collection of Angular
17
17
  - Docs: https://hubui.dev/en/nav/overview/
18
18
  - Live examples: https://hubui.dev/en/nav/examples/
19
19
  - Hub UI: https://hubui.dev/en/
20
+ - Hub UI on GitHub (issues, roadmap and contributing): https://github.com/hub-env/hub-ui
20
21
 
21
22
  ## 🧩 Library Family `ng-hub-ui`
22
23
 
@@ -452,6 +452,39 @@ class HubNavStateService {
452
452
  // ──────────────────────────────────────────────
453
453
  // Panel stack management
454
454
  // ──────────────────────────────────────────────
455
+ /**
456
+ * Whether the open panels are exactly these parents, in this order, with no drill-down
457
+ * and within the visible limit.
458
+ *
459
+ * A route change that stays inside them can then keep the panels instead of rebuilding
460
+ * them: a rebuilt panel gets a new id, the container tracks panels by id, and the panel
461
+ * is mounted again with its entrance animation.
462
+ *
463
+ * @param parents - The items whose panels the current route calls for, outermost first.
464
+ */
465
+ holdsPanelChain(parents) {
466
+ const stack = this._panelStack();
467
+ const effectiveMaxVisible = Math.max(1, this._config().panelMaxVisible - 1);
468
+ return (stack.length === parents.length &&
469
+ stack.length <= effectiveMaxVisible &&
470
+ stack.every((panel, index) => panel.parentItem.id === parents[index].id && !panel.isDrillDown && panel.history.length === 0));
471
+ }
472
+ /**
473
+ * Re-reads the children of the open panels in place, keeping each panel's id.
474
+ *
475
+ * The route-driven counterpart of {@link openPanel} for panels that are already open:
476
+ * their parents may have been rebuilt with different children (an item added, a badge
477
+ * changed), and the panel has to show them without being mounted again.
478
+ *
479
+ * @param parents - The same parents {@link holdsPanelChain} was asked about.
480
+ */
481
+ refreshPanelItems(parents) {
482
+ this._panelStack.update((stack) => stack.map((panel, index) => ({
483
+ ...panel,
484
+ parentItem: parents[index],
485
+ items: parents[index].children ?? []
486
+ })));
487
+ }
455
488
  /**
456
489
  * Opens a new panel for the given parent item's children.
457
490
  * If the panel count has reached `panelMaxVisible`, drills down within the last panel instead.
@@ -3309,11 +3342,20 @@ class HubNavComponent {
3309
3342
  return;
3310
3343
  }
3311
3344
  this._lastSyncedPath = path;
3312
- // Always rebuild route-driven panel state from scratch to avoid stale
3313
- // panel stacks when visibility limits or orientation changed.
3345
+ const sectionItem = rootItem.children.find((child) => child.children?.length && this.state.isItemOrDescendantActive(child, url));
3346
+ // Moving between entries of the panels already open keeps them. Rebuilding gave each
3347
+ // panel a new id, the container tracks panels by id, and so every click inside a
3348
+ // panel mounted it again: its entrance animation replayed and its items jumped. The
3349
+ // contents are re-read in place, so a changed list of items still shows.
3350
+ const wanted = sectionItem ? [rootItem, sectionItem] : [rootItem];
3351
+ if (this.state.holdsPanelChain(wanted)) {
3352
+ this.state.refreshPanelItems(wanted);
3353
+ return;
3354
+ }
3355
+ // Anything else rebuilds from scratch, which also covers a visibility limit or an
3356
+ // orientation that changed since the panels were opened.
3314
3357
  this.state.closeAllPanels();
3315
3358
  this.state.openPanel(rootItem, rootItems);
3316
- const sectionItem = rootItem.children.find((child) => child.children?.length && this.state.isItemOrDescendantActive(child, url));
3317
3359
  if (sectionItem) {
3318
3360
  // Keep root-level replacement logic scoped to true root items.
3319
3361
  // Passing rootItems here preserves drill-down behavior when