@zambon-dev/library 1.4.1 → 1.5.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 +35 -1
- package/fesm2022/zambon-dev-library.mjs +71 -8
- package/fesm2022/zambon-dev-library.mjs.map +1 -1
- package/lib/components/sidebar/sidebar.component.d.ts +9 -0
- package/lib/models/sidebar/sidebar-configs.d.ts +11 -0
- package/lib/services/sidebar.service.d.ts +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -23,6 +23,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
23
23
|
|
|
24
24
|
### ⚠ Breaking Changes / Migration
|
|
25
25
|
|
|
26
|
+
## [1.5.0] - 2026-09-09
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **`SidebarConfigs.shouldDeriveAreasFromRootMenus`** — derive region headers from the menu tree
|
|
31
|
+
instead of from `SidebarMenu.region`. Off by default, so nothing changes until you opt in.
|
|
32
|
+
|
|
33
|
+
With it on, a top-level menu that has children **and no URL** stops being a collapsible node and
|
|
34
|
+
becomes an area header, with its children rendered flat beneath it as top-level items — icons
|
|
35
|
+
included. A top-level menu that has its own URL stays an item, and a parent that carries a URL
|
|
36
|
+
stays collapsible, so only the menus that were already acting purely as groups change shape.
|
|
37
|
+
|
|
38
|
+
The point is where the grouping lives. `region` is a label repeated on every item that belongs to
|
|
39
|
+
a group, matched by exact string equality: a typo silently splits one area into two, and the
|
|
40
|
+
area has no row of its own to carry an order or a translation. Derived from the tree, the area
|
|
41
|
+
*is* a menu row — it already has a translated label and an order — and nothing has to be
|
|
42
|
+
duplicated across its items.
|
|
43
|
+
|
|
44
|
+
The children of an area are fetched **eagerly**, at load, because they are rendered without a
|
|
45
|
+
click and the lazy load a collapsible parent relies on would never fire. That is one extra
|
|
46
|
+
request per area. `region` keeps working exactly as before for anyone who prefers it; the two
|
|
47
|
+
mechanisms are independent and the flag chooses between them.
|
|
48
|
+
|
|
49
|
+
- **`SidebarService.loadChildrenFor(parentMenu)`** — loads a parent’s children and returns them as
|
|
50
|
+
an observable, for callers that need them before the user clicks. `loadChildren` is unchanged: it
|
|
51
|
+
is still the fire-and-forget variant that raises `childrenLoading` and `childrenFailed`, and it
|
|
52
|
+
now delegates to this one.
|
|
53
|
+
|
|
54
|
+
### ⚠ Breaking Changes / Migration
|
|
55
|
+
|
|
56
|
+
None. `shouldDeriveAreasFromRootMenus` defaults to `false`, so a sidebar keeps grouping by
|
|
57
|
+
`region` and keeps rendering top-level parents as collapsible nodes until you set it.
|
|
58
|
+
|
|
26
59
|
## [1.4.1] - 2026-09-08
|
|
27
60
|
|
|
28
61
|
- Maintenance release (no consumer-facing changes were documented).
|
|
@@ -216,7 +249,8 @@ config options and the `getUserProfile()` method still exist but are no longer c
|
|
|
216
249
|
available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
|
|
217
250
|
`library-v*` tags.
|
|
218
251
|
|
|
219
|
-
[Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/library-v1.
|
|
252
|
+
[Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/library-v1.5.0...HEAD
|
|
253
|
+
[1.5.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.5.0
|
|
220
254
|
[1.4.1]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.4.1
|
|
221
255
|
[1.4.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.4.0
|
|
222
256
|
[1.3.2]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.3.2
|
|
@@ -977,6 +977,17 @@ class SidebarConfigs {
|
|
|
977
977
|
/** Tooltip for items that open in a new browser tab. Rendered as-is, like {@link errorText}. */
|
|
978
978
|
externalLinkText = 'Opens in a new browser tab';
|
|
979
979
|
loadingText = 'Loading';
|
|
980
|
+
/**
|
|
981
|
+
* Derive region headers from the menu tree instead of from {@link SidebarMenu.region}.
|
|
982
|
+
*
|
|
983
|
+
* With this on, a top-level menu that has children and no URL stops being a collapsible node
|
|
984
|
+
* and becomes an area header, with its children rendered flat beneath it as top-level items --
|
|
985
|
+
* icons included. The area is then a real menu row, so it carries its own translated label and
|
|
986
|
+
* its own order, and no `region` label has to be repeated across every item that belongs to it.
|
|
987
|
+
*
|
|
988
|
+
* Off by default: without it a top-level parent stays the collapsible group it has always been.
|
|
989
|
+
*/
|
|
990
|
+
shouldDeriveAreasFromRootMenus = false;
|
|
980
991
|
logoCollapsedPath;
|
|
981
992
|
logoExpandedPath;
|
|
982
993
|
constructor(options = {}) {
|
|
@@ -1088,19 +1099,26 @@ class SidebarService {
|
|
|
1088
1099
|
}
|
|
1089
1100
|
loadChildren(parentMenu) {
|
|
1090
1101
|
this.childrenLoading.emit(parentMenu);
|
|
1091
|
-
this.
|
|
1092
|
-
.pipe(take(1), map((menus) => menus.map((menu) => new SidebarMenu(menu))))
|
|
1102
|
+
this.loadChildrenFor(parentMenu)
|
|
1093
1103
|
.subscribe({
|
|
1094
|
-
next: (childrenMenus) => {
|
|
1095
|
-
parentMenu.children = childrenMenus;
|
|
1096
|
-
childrenMenus.forEach((childMenu) => childMenu.parent = parentMenu);
|
|
1097
|
-
},
|
|
1098
1104
|
error: (exception) => {
|
|
1099
1105
|
this.childrenFailed.emit(parentMenu);
|
|
1100
1106
|
throw exception;
|
|
1101
1107
|
}
|
|
1102
1108
|
});
|
|
1103
1109
|
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Loads a parent's children and hands them back, for callers that need them before the user
|
|
1112
|
+
* clicks -- a sidebar rendering areas flat has to have them up front. {@link loadChildren} is
|
|
1113
|
+
* the fire-and-forget variant that also raises the loading and failure events.
|
|
1114
|
+
*/
|
|
1115
|
+
loadChildrenFor(parentMenu) {
|
|
1116
|
+
return this.loadMenus(parentMenu)
|
|
1117
|
+
.pipe(take(1), map((menus) => menus.map((menu) => new SidebarMenu(menu))), tap((childrenMenus) => {
|
|
1118
|
+
parentMenu.children = childrenMenus;
|
|
1119
|
+
childrenMenus.forEach((childMenu) => childMenu.parent = parentMenu);
|
|
1120
|
+
}));
|
|
1121
|
+
}
|
|
1104
1122
|
loadRoot() {
|
|
1105
1123
|
return this.loadMenus(null)
|
|
1106
1124
|
.pipe(take(1), map((menus) => menus.map((menu) => new SidebarMenu(menu))), tap((menus) => this.menus = menus));
|
|
@@ -3410,7 +3428,7 @@ class SidebarComponent extends BaseComponent {
|
|
|
3410
3428
|
.pipe(takeUntil(this.destroy$))
|
|
3411
3429
|
.subscribe((_menu) => this.deactivate());
|
|
3412
3430
|
this.sidebarService.loadRoot()
|
|
3413
|
-
.pipe(take(1))
|
|
3431
|
+
.pipe(take(1), switchMap((menus) => this.loadAreaChildren(menus)))
|
|
3414
3432
|
.subscribe({
|
|
3415
3433
|
next: (menus) => {
|
|
3416
3434
|
this.menus = menus;
|
|
@@ -3439,9 +3457,13 @@ class SidebarComponent extends BaseComponent {
|
|
|
3439
3457
|
trackByRegion(_index, region) {
|
|
3440
3458
|
return region.name ?? '';
|
|
3441
3459
|
}
|
|
3460
|
+
/** Whether this top-level menu stands for an area rather than a destination of its own. */
|
|
3461
|
+
isArea(menu) {
|
|
3462
|
+
return menu.childCount > 0 && (menu.url?.length ?? 0) === 0;
|
|
3463
|
+
}
|
|
3442
3464
|
// Group top-level menus into regions by their optional `region` label, preserving
|
|
3443
3465
|
// first-appearance order. Ungrouped menus fall into a single header-less region.
|
|
3444
|
-
|
|
3466
|
+
groupByRegionLabel(menus) {
|
|
3445
3467
|
const regions = [];
|
|
3446
3468
|
const byName = new Map();
|
|
3447
3469
|
menus.forEach((menu) => {
|
|
@@ -3455,6 +3477,47 @@ class SidebarComponent extends BaseComponent {
|
|
|
3455
3477
|
});
|
|
3456
3478
|
return regions;
|
|
3457
3479
|
}
|
|
3480
|
+
// Derive the regions from the tree: an area menu becomes a header and lends the group its own
|
|
3481
|
+
// children, so the area carries one translated label and one order instead of a string repeated
|
|
3482
|
+
// across every item. Anything that is not an area keeps falling into a header-less region, which
|
|
3483
|
+
// is created where the first such item appears so the original ordering still holds.
|
|
3484
|
+
groupByRootMenus(menus) {
|
|
3485
|
+
const regions = [];
|
|
3486
|
+
let ungrouped;
|
|
3487
|
+
menus.forEach((menu) => {
|
|
3488
|
+
if (this.isArea(menu)) {
|
|
3489
|
+
regions.push({ name: menu.label, items: menu.children });
|
|
3490
|
+
return;
|
|
3491
|
+
}
|
|
3492
|
+
if (!ungrouped) {
|
|
3493
|
+
ungrouped = { name: undefined, items: [] };
|
|
3494
|
+
regions.push(ungrouped);
|
|
3495
|
+
}
|
|
3496
|
+
ungrouped.items.push(menu);
|
|
3497
|
+
});
|
|
3498
|
+
return regions;
|
|
3499
|
+
}
|
|
3500
|
+
groupIntoRegions(menus) {
|
|
3501
|
+
return this.sidebarConfigs.shouldDeriveAreasFromRootMenus
|
|
3502
|
+
? this.groupByRootMenus(menus)
|
|
3503
|
+
: this.groupByRegionLabel(menus);
|
|
3504
|
+
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Fetches the children of every area up front, because they are rendered flat rather than
|
|
3507
|
+
* behind a click, so the lazy load a collapsible parent relies on would never be triggered.
|
|
3508
|
+
*/
|
|
3509
|
+
loadAreaChildren(menus) {
|
|
3510
|
+
if (!this.sidebarConfigs.shouldDeriveAreasFromRootMenus) {
|
|
3511
|
+
return of(menus);
|
|
3512
|
+
}
|
|
3513
|
+
const areas = menus.filter((menu) => this.isArea(menu));
|
|
3514
|
+
// forkJoin never emits on an empty array, so a menu with no areas has to short-circuit.
|
|
3515
|
+
if (areas.length === 0) {
|
|
3516
|
+
return of(menus);
|
|
3517
|
+
}
|
|
3518
|
+
return forkJoin(areas.map((area) => this.sidebarService.loadChildrenFor(area)))
|
|
3519
|
+
.pipe(map(() => menus));
|
|
3520
|
+
}
|
|
3458
3521
|
deactivate() {
|
|
3459
3522
|
if (this.sidebarService.isActive) {
|
|
3460
3523
|
this.sidebarService.isActive = false;
|