@zambon-dev/framework 1.3.0 → 1.4.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
|
@@ -23,6 +23,62 @@ 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.4.0] - 2026-09-16
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **`framework-button-filters` no longer submits the labels of catalog selections.** A filters
|
|
31
|
+
form reads its values straight off the form, so the display control `lib-catalog-select` adds
|
|
32
|
+
for its own use went to the backend beside the real filters — a request filtering by employee
|
|
33
|
+
carried `employeeName: "753 - ADEMILSON LOPES MAGALHAES"` next to `employeeID: 125`.
|
|
34
|
+
|
|
35
|
+
They were ignored, being filters no service declares, but the day one does filter by a name it
|
|
36
|
+
would receive the formatted label rather than the stored value and quietly match nothing.
|
|
37
|
+
|
|
38
|
+
The labels are still **kept** for the modal: reopening it patches them back into the form, and a
|
|
39
|
+
catalog select backed by a `searchEndpoint` cannot recover its text from the identifier alone —
|
|
40
|
+
it only resolves a display out of a local entries list. Dropping them from what is stored would
|
|
41
|
+
have left the field showing a selection with nothing written in it.
|
|
42
|
+
|
|
43
|
+
### ⚠ Breaking Changes / Migration
|
|
44
|
+
|
|
45
|
+
- **Upgrade `@zambon-dev/library` together with this release.** The peer range moved from
|
|
46
|
+
`^1.0.0` to `^1.6.0`, because `framework-button-filters` now imports `DisplayControls` from it.
|
|
47
|
+
On an older library that export does not exist, and the filters button fails — at build time if
|
|
48
|
+
the bundler checks exports, otherwise the first time a filter is submitted. The old range would
|
|
49
|
+
have let npm resolve that combination without a word of warning.
|
|
50
|
+
|
|
51
|
+
- **A backend no longer receives the label of a catalog selection.** If one of your services
|
|
52
|
+
filters by a key that a `lib-catalog-select` uses as its `displayControlName` — the `xxxName`
|
|
53
|
+
that comes paired with an `xxxID` — that filter now arrives empty and the query stops narrowing,
|
|
54
|
+
silently returning more rows than before rather than failing.
|
|
55
|
+
|
|
56
|
+
To check, take each filters form and, for every `lib-catalog-select` in it, note the
|
|
57
|
+
`displayControlName`. Then look for a `TryFilter` on that name in the service behind the list.
|
|
58
|
+
Any hit has to move to the identifier instead: the label was never the stored value — it is what
|
|
59
|
+
the catalog chose to display, so filtering by it was already matching on formatting.
|
|
60
|
+
|
|
61
|
+
## [1.3.1] - 2026-09-10
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- **Returning to an already-open tab no longer crashes with `Maximum call stack size exceeded`.**
|
|
66
|
+
It affected any screen whose route nests two empty-path levels — the shape every list screen uses:
|
|
67
|
+
`path: ''` with `DefaultTabViewComponent`, and a child `path: ''` with the list component.
|
|
68
|
+
|
|
69
|
+
`CustomReuseStrategy` keyed its detached-view cache on `route.component.name`, and a production
|
|
70
|
+
build renames every class: esbuild wraps each component as `X = (() => { class i { } return i; })()`,
|
|
71
|
+
so the name is one mangled letter that the whole chunk shares. An empty path contributes no URL
|
|
72
|
+
segment, so both levels also resolved to the same URL — which left the two cache keys identical.
|
|
73
|
+
Storing one handle overwrote the other, both route levels were then handed the same detached view,
|
|
74
|
+
and Angular blew the stack building a router state whose node was its own descendant.
|
|
75
|
+
|
|
76
|
+
The key is now built from the component's **identity** and the route's depth rather than its name.
|
|
77
|
+
Applications need to change nothing. Worth knowing when reading old reports of this crash: it only
|
|
78
|
+
ever reproduced in a minified build — `ng serve` keeps real class names — and only on the screens
|
|
79
|
+
whose chunk happened to mangle to the same letter as the framework's, so a rebuild could move the
|
|
80
|
+
symptom from one screen to another.
|
|
81
|
+
|
|
26
82
|
## [1.3.0] - 2026-07-30
|
|
27
83
|
|
|
28
84
|
### Added
|
|
@@ -121,7 +177,9 @@ None.
|
|
|
121
177
|
available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
|
|
122
178
|
`framework-v*` tags.
|
|
123
179
|
|
|
124
|
-
[Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/framework-v1.
|
|
180
|
+
[Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/framework-v1.4.0...HEAD
|
|
181
|
+
[1.4.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.0
|
|
182
|
+
[1.3.1]: https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.3.1
|
|
125
183
|
[1.3.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.3.0
|
|
126
184
|
[1.2.1]: https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.2.1
|
|
127
185
|
[1.2.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.2.0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, ApplicationRef, Inject, Injectable, Component, ViewChild, Input, EventEmitter, Output, NgModule, forwardRef, ChangeDetectorRef, InjectionToken } from '@angular/core';
|
|
3
3
|
import * as i1$2 from '@zambon-dev/library';
|
|
4
|
-
import { RibbonGroupChild, RibbonButtonComponent, DataProviderService, FormService, ModalComponent, DataGridDataset, MultiSelectResultDataset, RouterFormatter, MultiEditorComponent, MultiEditorDataset, MultiSelectComponent, RibbonComponent, GroupContainerComponent, RibbonGroupComponent, DataGridConfigsProvider } from '@zambon-dev/library';
|
|
4
|
+
import { RibbonGroupChild, RibbonButtonComponent, DataProviderService, FormService, ModalComponent, DataGridDataset, DisplayControls, MultiSelectResultDataset, RouterFormatter, MultiEditorComponent, MultiEditorDataset, MultiSelectComponent, RibbonComponent, GroupContainerComponent, RibbonGroupComponent, DataGridConfigsProvider } from '@zambon-dev/library';
|
|
5
5
|
export { DateValidators } from '@zambon-dev/library';
|
|
6
6
|
import { ReplaySubject, take, map, switchMap, of, Subject, takeUntil, filter, skip, tap, throwError } from 'rxjs';
|
|
7
7
|
import { __decorate } from 'tslib';
|
|
@@ -121,6 +121,8 @@ let CustomReuseStrategy = class CustomReuseStrategy {
|
|
|
121
121
|
tabService;
|
|
122
122
|
applicationRef = inject(ApplicationRef);
|
|
123
123
|
cachedHandles = {};
|
|
124
|
+
componentIDs = new WeakMap();
|
|
125
|
+
nextComponentID = 1;
|
|
124
126
|
//#endregion
|
|
125
127
|
//#region Properties
|
|
126
128
|
//#endregion
|
|
@@ -193,8 +195,39 @@ let CustomReuseStrategy = class CustomReuseStrategy {
|
|
|
193
195
|
if (this.clones[url]) {
|
|
194
196
|
url = this.clones[url];
|
|
195
197
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
// The component is identified by identity, never by name. A production build renames every
|
|
199
|
+
// class: esbuild wraps each component as `X = (() => { class i { } return i; })()`, so
|
|
200
|
+
// `component.name` is a single mangled letter that the whole chunk shares. Nested empty-path
|
|
201
|
+
// routes already resolve to the same URL, because an empty path contributes no segment, so a
|
|
202
|
+
// key built from the name collapsed a screen's tab view and its list into one entry: storing
|
|
203
|
+
// one overwrote the other, both levels were then handed the same detached view, and Angular
|
|
204
|
+
// blew the stack building a router state whose node was its own descendant. That reproduced
|
|
205
|
+
// only in a minified build, and only on the screens whose chunk happened to mangle to the
|
|
206
|
+
// same letter as the framework's.
|
|
207
|
+
//
|
|
208
|
+
// The depth is part of the key too, so two sibling routes sharing a component type under one
|
|
209
|
+
// URL cannot collide either.
|
|
210
|
+
return `${url}-${this.getRouteDepth(route)}-${this.getComponentID(route.component)}`;
|
|
211
|
+
}
|
|
212
|
+
getComponentID(component) {
|
|
213
|
+
if (typeof component !== 'function') {
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
let componentID = this.componentIDs.get(component);
|
|
217
|
+
if (componentID === undefined) {
|
|
218
|
+
componentID = this.nextComponentID++;
|
|
219
|
+
this.componentIDs.set(component, componentID);
|
|
220
|
+
}
|
|
221
|
+
return componentID;
|
|
222
|
+
}
|
|
223
|
+
getRouteDepth(route) {
|
|
224
|
+
let depth = 0;
|
|
225
|
+
let current = route.parent;
|
|
226
|
+
while (current) {
|
|
227
|
+
depth++;
|
|
228
|
+
current = current.parent;
|
|
229
|
+
}
|
|
230
|
+
return depth;
|
|
198
231
|
}
|
|
199
232
|
getUrlFromRoute(route) {
|
|
200
233
|
const segments = [];
|
|
@@ -1430,14 +1463,35 @@ class ButtonFiltersComponent extends BaseButton {
|
|
|
1430
1463
|
//#endregion
|
|
1431
1464
|
//#region Public methods
|
|
1432
1465
|
setFilters(filters) {
|
|
1466
|
+
// Kept whole, because reopening the modal patches this straight back into the form and a
|
|
1467
|
+
// catalog select backed by a search endpoint cannot recover its label from the identifier --
|
|
1468
|
+
// it only resolves a display from a local entries list. Dropping the labels here would leave
|
|
1469
|
+
// the field showing a selection with no text.
|
|
1433
1470
|
this.filters = filters;
|
|
1434
1471
|
if (this.hasFiltersApplied) {
|
|
1435
|
-
this.gridDataset.setFilters(filters);
|
|
1472
|
+
this.gridDataset.setFilters(this.withoutDisplayControls(filters));
|
|
1436
1473
|
}
|
|
1437
1474
|
else {
|
|
1438
1475
|
this.gridDataset.setFilters();
|
|
1439
1476
|
}
|
|
1440
1477
|
}
|
|
1478
|
+
//#endregion
|
|
1479
|
+
//#region Private methods
|
|
1480
|
+
/**
|
|
1481
|
+
* Drops the controls that only hold a catalog selection's label.
|
|
1482
|
+
*
|
|
1483
|
+
* They are half of how `lib-catalog-select` works and are created by it, so a screen never asked
|
|
1484
|
+
* for them and a backend has no filter behind them. Sent anyway they are dead weight, and worse:
|
|
1485
|
+
* the day a service does filter by a name, it would receive the formatted label rather than the
|
|
1486
|
+
* stored value and quietly match nothing.
|
|
1487
|
+
*
|
|
1488
|
+
* @param filters The filters as submitted.
|
|
1489
|
+
* @returns The filters a backend should receive.
|
|
1490
|
+
*/
|
|
1491
|
+
withoutDisplayControls(filters) {
|
|
1492
|
+
return Object.fromEntries(Object.entries(filters)
|
|
1493
|
+
.filter(([key]) => !DisplayControls.isDisplayControl(this.formGroup.form.get(key))));
|
|
1494
|
+
}
|
|
1441
1495
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: ButtonFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1442
1496
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.6", type: ButtonFiltersComponent, isStandalone: true, selector: "framework-button-filters", inputs: { modalSize: "modalSize", modalTitle: "modalTitle", validateFormFunction: "validateFormFunction" }, providers: [
|
|
1443
1497
|
FormService,
|