@zambon-dev/ui 0.0.0-stage → 0.0.1-bootstrap.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +2325 -0
  2. package/README.md +81 -3
  3. package/fesm2022/zambon-dev-ui-framework.mjs +3997 -0
  4. package/fesm2022/zambon-dev-ui-framework.mjs.map +1 -0
  5. package/fesm2022/zambon-dev-ui-library.mjs +4280 -0
  6. package/fesm2022/zambon-dev-ui-library.mjs.map +1 -0
  7. package/fesm2022/zambon-dev-ui-shared.mjs +1802 -0
  8. package/fesm2022/zambon-dev-ui-shared.mjs.map +1 -0
  9. package/fesm2022/zambon-dev-ui-testing.mjs +80 -0
  10. package/fesm2022/zambon-dev-ui-testing.mjs.map +1 -0
  11. package/fesm2022/zambon-dev-ui.mjs +8 -0
  12. package/fesm2022/zambon-dev-ui.mjs.map +1 -0
  13. package/i18n/framework/en.json +63 -0
  14. package/i18n/framework/pt.json +63 -0
  15. package/i18n/shared/external-content/en.json +12 -0
  16. package/i18n/shared/external-content/pt.json +12 -0
  17. package/i18n/shared/language-selector/en.json +5 -0
  18. package/i18n/shared/language-selector/pt.json +5 -0
  19. package/i18n/shared/login/en.json +14 -0
  20. package/i18n/shared/login/pt.json +14 -0
  21. package/i18n/shared/operations-history/en.json +5 -0
  22. package/i18n/shared/operations-history/pt.json +5 -0
  23. package/i18n/shared/services-history/en.json +14 -0
  24. package/i18n/shared/services-history/pt.json +14 -0
  25. package/i18n/shared/top-bar/en.json +7 -0
  26. package/i18n/shared/top-bar/pt.json +7 -0
  27. package/package.json +106 -4
  28. package/styles/common.scss +10 -0
  29. package/styles/components/button.scss +101 -0
  30. package/styles/components/card.scss +13 -0
  31. package/styles/components/dropdown-static.scss +35 -0
  32. package/styles/components/input-group.scss +44 -0
  33. package/styles/components/input.scss +99 -0
  34. package/styles/components/label.scss +3 -0
  35. package/styles/components/row.scss +48 -0
  36. package/styles/mixins/background.scss +31 -0
  37. package/styles/theme.css +75 -0
  38. package/styles/variables.scss +60 -0
  39. package/types/zambon-dev-ui-framework.d.ts +1066 -0
  40. package/types/zambon-dev-ui-library.d.ts +1233 -0
  41. package/types/zambon-dev-ui-shared.d.ts +655 -0
  42. package/types/zambon-dev-ui-testing.d.ts +56 -0
  43. package/types/zambon-dev-ui.d.ts +3 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,2325 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@zambon-dev/ui` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ > **Upgrading?** Each release lists what changed and, under
9
+ > **⚠ Breaking Changes / Migration**, the concrete steps required to move to that version.
10
+ > When no migration is needed, that subsection is omitted.
11
+
12
+ ## [Unreleased]
13
+
14
+ ### Added
15
+
16
+ ### Changed
17
+
18
+ ### Deprecated
19
+
20
+ ### Removed
21
+
22
+ ### Fixed
23
+
24
+ ### ⚠ Breaking Changes / Migration
25
+
26
+ - **The layers in this release are `library` 4.0.5, `framework` 5.0.5 and `shared` 6.0.4.**
27
+ Moving here from an earlier version of any of the three means taking that layer's remaining
28
+ releases too; they are under **Package history** below. The one worth naming is
29
+ `ModalComponent.isShown`, which became `isOpen` and is a `Signal<boolean>`: reading the
30
+ signal object itself is valid TypeScript and always truthy, so the build will not catch a miss.
31
+ Grep for `isShown` and `isModalShown`.
32
+
33
+ - **This package replaces `@zambon-dev/library`, `@zambon-dev/framework` and
34
+ `@zambon-dev/shared`.** Install `@zambon-dev/ui` in place of all three and rewrite the
35
+ imports:
36
+
37
+ | Was | Now |
38
+ | ----------------------------- | -------------------------- |
39
+ | `@zambon-dev/library` | `@zambon-dev/ui/library` |
40
+ | `@zambon-dev/framework` | `@zambon-dev/ui/framework` |
41
+ | `@zambon-dev/shared` | `@zambon-dev/ui/shared` |
42
+ | `@zambon-dev/library/testing` | `@zambon-dev/ui/testing` |
43
+
44
+ `@zambon-dev/ui` also re-exports all three layers, so a single import works where the layer
45
+ does not matter. See [the migration guide](https://github.com/RicardoZambon/ZLibraries/blob/main/docs/guides/migrating-to-ui.md).
46
+
47
+ - **Stylesheet subpaths name their file.** `@zambon-dev/library/styles` and
48
+ `@zambon-dev/shared/styles` both existed and resolved to different stylesheets, so the bare
49
+ subpath is gone: use `@zambon-dev/ui/styles/variables.scss`,
50
+ `@zambon-dev/ui/styles/common.scss` or `@zambon-dev/ui/styles/theme.css`.
51
+
52
+ - **Translation assets moved under a per-layer folder**, to `@zambon-dev/ui/i18n/framework` and
53
+ `@zambon-dev/ui/i18n/shared`. Applications that spread `ZAMBON_FRAMEWORK_I18N_ASSET` or
54
+ `ZAMBON_SHARED_I18N_ASSET` into their build assets need no change — the constants carry the
55
+ new path. A hand-written asset entry does.
56
+
57
+ - **The peer set is the union of the three packages'**, so `@angular/cdk`,
58
+ `@angular/platform-browser`, `@auth0/angular-jwt`, `@microsoft/signalr`,
59
+ `ngx-resize-observer` and `ngx-translate-multi-http-loader` are all required. Every
60
+ application consuming these packages already installs them.
61
+
62
+ - **`AngularAssetPattern` is exported once**, from the library layer, rather than declared
63
+ separately by each i18n helper. Import it from `@zambon-dev/ui` or
64
+ `@zambon-dev/ui/library`; the framework and shared layers no longer export it.
65
+
66
+ ## Package history
67
+
68
+ The three packages this one replaces. Their releases stay installable from npm; nothing further
69
+ will be published on them.
70
+
71
+ ### Library
72
+
73
+ UI components, directives, pipes and the data-grid services. Published as `@zambon-dev/library` through 4.0.5.
74
+
75
+ #### [4.0.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.5) - 2026-10-24
76
+
77
+ ##### Changed
78
+
79
+ - **A grid row's checkbox follows the grid's selection by being told, not by forcing a pass.**
80
+ `DataGridRowComponent` held its selected state in a plain field and called
81
+ `ChangeDetectorRef.detectChanges()` on itself whenever the grid announced a change. That
82
+ rendered -- there is no behaviour change here -- but it rendered because the component forced
83
+ a pass rather than because anything was notified, which is the one thing this package's
84
+ components are not supposed to need. The state is a signal now and the `ChangeDetectorRef` is
85
+ gone; it was the last one in the package.
86
+
87
+ #### [4.0.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.4) - 2026-10-24
88
+
89
+ ##### Changed
90
+
91
+ - **`DataProviderService` announces the entity it holds.** The identifier behind `entityID`
92
+ and `hasEntityID` is a signal now. It is read from templates -- `@zambon-dev/framework`'s New,
93
+ Edit and Views buttons all appear only once a record exists -- and the route resolving or a
94
+ save assigning an id told Angular nothing. Both accessors keep their shape, so no call site
95
+ changes.
96
+
97
+ #### [4.0.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.3) - 2026-10-24
98
+
99
+ ##### Changed
100
+
101
+ - **Every component in this package now renders under any change-detection strategy the host
102
+ application uses, including zoneless.** `CatalogSelectComponent` was the last one. Nothing here
103
+ depends on being walked on every event, and
104
+ `@angular-eslint/prefer-on-push-component-change-detection` is an error in this package rather
105
+ than a warning, so it stays that way.
106
+
107
+ ##### ⚠ Breaking Changes / Migration
108
+
109
+ - **`CatalogSelectComponent` exposes its inputs as signals.** Binding to it from a template is
110
+ unchanged -- `[label]="..."`, `[searchEndpoint]="..."`, `[filters]="..."` and the rest all
111
+ still work. Only code reading one off a component instance needs `()`. `controlName` and
112
+ `displayControlName` are required now, which they always were in practice.
113
+
114
+ `entriesList`, `filters` and `searchEndpoint` stay plain inputs. They are imperative
115
+ configuration that re-initialises the search, no template reads them, and making them signals
116
+ would have moved that side effect into an `effect()` for no gain.
117
+
118
+ #### [4.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.2) - 2026-10-24
119
+
120
+ - Maintenance release (no consumer-facing changes were documented).
121
+
122
+ #### [4.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.1) - 2026-10-24
123
+
124
+ ##### Changed
125
+
126
+ - **`FormService` and the datasets announce their own changes.** A component reading
127
+ `formService.loading`, `formService.model`, `formService.isEditMode`, `formService.isValid`,
128
+ `dataGridDataset.selectedRowKeys`, `resultDataset.displayedRows` or a grid's loading state from
129
+ its template now re-renders when that state changes, whatever strategy the host application
130
+ uses. `columns` is deliberately left alone: a concrete dataset declares it once and never
131
+ changes it, so making it reactive would only have broken every subclass that declares it.
132
+ **Nothing to do on your side** -- these were already accessors, or became accessors of the same
133
+ shape, so every read and write keeps working. `isValid` also
134
+ mirrors the `FormGroup`'s status, which is not a signal and never notified one, so a control
135
+ bound to it no longer keeps its first answer.
136
+
137
+ - **`RibbonButtonComponent`, `FormInputComponent` and `DataGridComponent` render under any
138
+ change-detection strategy.** Their inputs are signals, and none of them depends on being walked
139
+ on every event any more. The grid also stops calling `ChangeDetectorRef.detectChanges()` and
140
+ stops re-entering the Angular zone to publish a measurement -- a signal write reaches the
141
+ screen on its own.
142
+
143
+ - **Seven more components render under any change-detection strategy**, continuing the work the last
144
+ major started: `ModalComponent`, `GroupAccordionComponent`, `GroupScrollSpyComponent`,
145
+ `MultiSelectComponent`, `MultiEditorComponent`, `FormInputGroupComponent` and
146
+ `MultiSelectResultGridComponent`. Their state is signals now, and none of them declares a
147
+ strategy. Four library components are still to go; they are named in
148
+ `change-detection-contract.spec.ts`.
149
+
150
+ ##### ⚠ Breaking Changes / Migration
151
+
152
+ - **`ModalComponent.isShown` is now `isOpen`, and it is a `Signal<boolean>`.** The rename is
153
+ deliberate: a signal keeping the old name would leave `if (modal.isShown)` compiling and
154
+ permanently true. Replace `modal.isShown` with `modal.isOpen()`.
155
+
156
+ - **`IModal` changed with it.** It declares `readonly isOpen: Signal<boolean>` instead of
157
+ `get isShown(): boolean`, and no longer declares `size` or `title` -- those are presentational
158
+ inputs each component declares for itself, and nothing drove a modal through them. If you
159
+ implement `IModal` by wrapping our modal, delegate with a signal view query:
160
+
161
+ ```ts
162
+ private readonly modal = viewChild(ModalComponent);
163
+ public readonly isOpen: Signal<boolean> = computed(() => this.modal()?.isOpen() ?? false);
164
+ ```
165
+
166
+ A `computed` reading a plain `@ViewChild` never re-runs when the query resolves, so the wrapper
167
+ would answer against an undefined child for ever.
168
+
169
+ - **`ModalComponent`'s inputs are signals.** Reading one in your own template needs `()`:
170
+ `title()`, `size()`, `position()`, `dialog()`, `closeButtonText()`, `modalProcessing()`. Binding
171
+ to them from a template is unchanged. `closeButtonText` and `modalProcessing` are `model()`, so
172
+ code that sets them keeps working through `.set(...)`; the other four are `input()` and can only
173
+ be bound.
174
+
175
+ - **`GroupAccordionComponent.label` is a required signal input**, and `maxHeight` is a signal.
176
+ `GroupScrollSpyComponent` exposes `titles`, `activeSection`, `sections` and `scrollSpy` as
177
+ signals. `MultiSelectComponent` and `MultiEditorComponent` expose their inputs as signals --
178
+ `itemTemplate()`, `formGroup()`, `addButtonLabel()`, `removeButtonLabel()`, `showAddButton()`
179
+ and `showDeleteButton()`.
180
+
181
+ - **`RibbonButtonComponent`, `FormInputComponent` and `DataGridComponent` expose their inputs as
182
+ signals.** Binding to them from a template is unchanged. Only code reading one off an instance needs `()` --
183
+ `button.loading()` rather than `button.loading`.
184
+
185
+ Two are `model()` rather than `input()` because the components write them as well:
186
+ `RibbonButtonComponent.loading`, which `BaseButton` drives through `startLoading` and
187
+ `finishLoading`, and `FormInputComponent.fixedValue`, which is two-way bound through `ngModel`.
188
+ `FormInputComponent.format` is also a `model()`, since it defaults itself for date types when
189
+ the consumer supplies none.
190
+
191
+ `RibbonButtonComponent.action` is an `output()`, which has no no-argument overload, so emitting
192
+ "no option chosen" is now an explicit `emit(undefined)`.
193
+
194
+ - **`FormInputGroupComponent` and `MultiSelectResultGridComponent` expose their inputs as signals
195
+ too.** Binding to them from a template is unchanged -- `[label]="..."`, `[type]="..."`,
196
+ `[validations]="..."` and the rest all still work. Only code reading one off a component
197
+ instance needs `()`. `controlName` is required now, which it always was in practice.
198
+
199
+ #### [4.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.0) - 2026-10-24
200
+
201
+ ##### Added
202
+
203
+ - **A test harness that catches a component which silently stops rendering**, published as
204
+ `@zambon-dev/library/testing`. `renderUnderOnPush(MyComponent)` renders a component beneath a
205
+ clean OnPush ancestor -- which is what your application is -- and `render.tick()` runs a pass
206
+ that such an ancestor can still short-circuit. A component that updates state without telling
207
+ Angular cannot update its DOM under it, which is the failure a plain `TestBed` fixture cannot
208
+ express. Use it on your own components as you remove `ChangeDetectionStrategy.Eager` from them.
209
+ It works under both a zone and a zoneless test environment, so you can adopt it without
210
+ changing your test setup first.
211
+
212
+ ##### Fixed
213
+
214
+ - **The sidebar fills in on its own again.** In an application whose components use Angular 22's
215
+ default strategy -- which is now OnPush -- the sidebar rendered its loading spinner and only
216
+ filled in when the user next clicked something. Its menus arrived from HTTP, were assigned to a
217
+ field, and nothing told Angular to render. `SidebarService` and `SidebarComponent` now hold that
218
+ state in signals, so it reaches the screen whatever strategy your application uses.
219
+ - **A sidebar item's selected state reaches its host element.** `[class.selected]` is a host
220
+ binding, which Angular evaluates during the _parent's_ pass, so the component's own
221
+ `detectChanges()` could never refresh it and writing it afterwards raised NG0100.
222
+ - **A ribbon group hides and shows itself when its buttons' visibility resolves.** The group reads
223
+ its children's visibility from a host binding; nothing notified it when an asynchronous access
224
+ check flipped one.
225
+
226
+ ##### ⚠ Breaking Changes / Migration
227
+
228
+ - **`RibbonGroupChild.visible` is now `isVisible`, and it is a `Signal<boolean>`.** If you
229
+ implement `RibbonGroupChild` yourself -- a custom ribbon button -- replace the field:
230
+
231
+ ```ts
232
+ // before
233
+ public visible = true;
234
+ // after
235
+ public readonly isVisible: Signal<boolean> = computed(() => ...); // or signal(true)
236
+ ```
237
+
238
+ A plain field cannot work here: the group reads it from a host binding, and nothing can notify
239
+ the group when a field changes.
240
+
241
+ - **`RibbonGroupComponent.children` is a signal query**, `Signal<readonly RibbonGroupChild[]>`
242
+ instead of `QueryList`. Replace `children.length` with `children().length` and
243
+ `children.get(0)` with `children()[0]`.
244
+
245
+ - **`SidebarService.isActive`, `isCollapsed` and `shouldActivate` are accessors over signals.**
246
+ Reading and writing them is unchanged. But if your own `SidebarService` subclass **redeclares**
247
+ any of them as a field -- `public override isActive = false;` -- delete that line. A field
248
+ shadows the accessor, and the sidebar stops reacting to it.
249
+
250
+ #### [3.2.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.5) - 2026-10-22
251
+
252
+ ##### Fixed
253
+
254
+ - **A list reopened from the menu no longer comes back empty.** Opening a record from a list that
255
+ had been scrolled, then reopening that list from the menu, showed the column headers over an empty
256
+ body. The tab reuses the list's cached view, as it should, but while that view sat detached the
257
+ grid kept measuring its CDK viewport: the body leaving the document is a resize, and a load that
258
+ finishes after the user has left is followed by a re-measure too. Measured detached, the viewport
259
+ worked out its range from a scroll offset of 0 and left its own change-detection flag stuck, so
260
+ once reattached it computed the right range but could not write the content offset out — the rows
261
+ were rendered, translated to the top, while the viewport showed where the user had scrolled to.
262
+ Whether something later rescued it was a matter of timing, which is why it came and went. A
263
+ detached grid now measures nothing; coming back is a resize as well, so it measures on the way in.
264
+
265
+ #### [3.2.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.4) - 2026-10-22
266
+
267
+ ##### Fixed
268
+
269
+ - **A submit button inside a modal no longer reloads the whole application.** Confirming a filter
270
+ applied it and then, about a second later, reloaded the page. The modal's content sits in a native
271
+ `<form>`, and a `type="submit"` button — `framework-button-filters`' Confirm, or any `<button>`
272
+ without a type — made the browser submit it: a GET to the current URL, leaving a bare `?` behind.
273
+ Nothing in the modal ever cancelled that; the consumer's `FormGroupDirective` did, by accident,
274
+ for as long as the submit bubbled through it. Since 3.2.1 moved the modal's host to `<body>` it no
275
+ longer does, so the form now prevents its own native submission. The event still bubbles.
276
+
277
+ #### [3.2.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.3) - 2026-09-26
278
+
279
+ ##### Fixed
280
+
281
+ - **A ribbon button's options are no longer cut off by whatever the button sits in.** The list was
282
+ absolutely positioned inside the button, which works only while nothing between it and the page
283
+ clips. `lib-data-grid` does: it is `overflow: hidden`, its buttons bar is its first child, and a
284
+ child list's grid is only a few rows tall -- so the export button's options opened downwards into
285
+ the grid and lost their lower part, measured at 129px of list against 65px of room. The list now
286
+ renders in a CDK overlay attached to the document, as `lib-catalog-select` already did, and the
287
+ overlay also flips it above the button when there is no room below. The ribbon no longer needs
288
+ its own rule to pin a right-hand button’s list to the right, since the overlay works that out.
289
+
290
+ #### [3.2.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.2) - 2026-09-26
291
+
292
+ ##### Fixed
293
+
294
+ - **Component styles are compiled again.** The Tailwind 4 migration left every component's own
295
+ stylesheet unprocessed in the published bundle, so its `@apply` rules reached the browser as an
296
+ at-rule the browser does not know and it dropped every rule that used one. In practice the
297
+ components rendered unstyled in any application installing 3.2.1. ng-packagr resolves its
298
+ PostCSS configuration from the library directory rather than from the workspace root, and the
299
+ migration added one only at the root. Nothing failed along the way: Storybook compiles from
300
+ source and never sees the packaged artifact, which is why `tools/check-packaged-styles.mjs` now
301
+ runs against that artifact in CI and again before every publish.
302
+
303
+ #### [3.2.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.1) - 2026-09-26
304
+
305
+ ##### Fixed
306
+
307
+ - **Dialogs no longer open underneath the navigation.** `lib-modal` is `fixed` at `z-index: 50`,
308
+ which should put it over everything, but z-index only ranks siblings inside one stacking context
309
+ and the tab panel these views render in is its own. Inside it the 50 counted for nothing against
310
+ the sidebar outside it, and the tab strip stayed visible and clickable behind the dialog.
311
+
312
+ The modal now moves its host to `<body>` on init, which is the one place with nothing above it.
313
+ This covers every dialog built on it: multi-editor, multi-select, the filter panels, the confirm
314
+ and error modals, and the operations-history detail.
315
+
316
+ #### [3.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.0) - 2026-09-25
317
+
318
+ ##### Changed
319
+
320
+ - **Tailwind CSS 4.** The component styles in this package are compiled against Tailwind 4 now.
321
+ They are expanded at build time, so nothing in the published bundle changed shape -- but the
322
+ application consuming it has to be on Tailwind 4 as well.
323
+
324
+ #### [3.1.7](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.7) - 2026-09-25
325
+
326
+ ##### Fixed
327
+
328
+ - **A menu with children no longer runs its label under the expand chevron.** The chevron and the
329
+ external-link glyph sit in an absolute slot at the row's right edge, outside the anchor's flow,
330
+ so nothing reserved room for them: "Fluxo de delegação" overlapped the chevron by 11px. The row
331
+ reserves the slot now and the anchor fills what is left, so a long label ellipsises before it.
332
+
333
+ Rows with one of those glyphs give up the fixed anchor width to do this, so their label reflows
334
+ while the rail collapses instead of sliding out of view. Only those rows, only during the
335
+ animation.
336
+
337
+ - **`lib-data-grid` and `lib-group-container` follow their container's corner.** Both were a flat
338
+ `rounded-lg`, which inside the tab panel put an 8px corner 9px in from a 20px one — close enough
339
+ to look like a mistake rather than a choice. They read `var(--surface-radius, 0.5rem)` now: the
340
+ panel publishes the radius that keeps the two parallel, and everywhere else the 0.5rem default
341
+ is exactly what they had.
342
+
343
+ #### [3.1.6](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.6) - 2026-09-25
344
+
345
+ ##### Fixed
346
+
347
+ - **Icons drawn in CSS survive a FontAwesome major.** Four pseudo-elements set
348
+ `font-family: 'Font Awesome 6 Free'` literally: the sidebar item's expand/collapse chevron and
349
+ its external-link marker, and the checked-checkbox tick in `form-input` and in the published
350
+ `styles/components/input.scss`. FontAwesome 7 renames the family to `Font Awesome 7 Free` and,
351
+ unlike the FontAwesome 5 names, does not keep a 6 alias — so on an application that had moved to
352
+ v7 the family stopped resolving and every one of those glyphs rendered as a fallback box.
353
+
354
+ They now read `var(--fa-family-classic, 'Font Awesome 6 Free')`. FontAwesome 7 defines that
355
+ variable; 6 does not, so it takes the fallback. The same stylesheet is correct on both, and on
356
+ 8 as well as long as FontAwesome keeps the variable.
357
+
358
+ #### [3.1.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.5) - 2026-09-24
359
+
360
+ ##### Fixed
361
+
362
+ - **Pressing Enter in the multi-select search no longer saves the changes.** The search box sits
363
+ inside the modal's form, so Enter submitted it and the confirmation ran alongside the search --
364
+ committing a selection the user was still building. Enter now searches and nothing else.
365
+
366
+ - **The remove button in the multi-select panel works for an entry the search never showed.** It
367
+ removed by deselecting the row in the grid on the left, which knows nothing about a row it has
368
+ not loaded, so clicking the cross on an entry further down the list did nothing. The panel now
369
+ tells the dataset directly, and still deselects the grid row when there is one.
370
+
371
+ - **Grid checkboxes are no longer drawn as disabled.** The disabled attribute was bound to the
372
+ boolean itself, and an attribute binding writes `disabled="false"` for false -- which disables a
373
+ control just as firmly as any other value. Every checkbox rendered faded and unusable, leaving
374
+ the row label as the only way to toggle a selection.
375
+
376
+ - **A multi-select or multi-editor no longer adds a gap below the page.** Both render nothing but
377
+ a fixed-position modal, yet their host still counted as a child in the parent's layout, so a
378
+ flex column with a gap reserved a slot for it under the last visible element.
379
+
380
+ **Requires Tailwind CSS 4**, and `@zambon-dev/shared` 5.x, which ships the theme. The migration
381
+ steps are in the `@zambon-dev/shared` changelog for this release; there is nothing specific to
382
+ this package to do beyond following them.
383
+
384
+ #### [3.1.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.4) - 2026-09-23
385
+
386
+ ##### Fixed
387
+
388
+ - **A `lib-catalog-select` with `searchable` off no longer looks disabled.** It was drawn on the
389
+ same grey as a disabled field, so nothing distinguished a picker you are meant to click from a
390
+ control that is not yours to touch.
391
+
392
+ Turning searching off makes the inner input read-only to the keyboard, and `lib-form-input`
393
+ paints a read-only input as disabled by default. That default is right for `readOnly`, which
394
+ means the value cannot be changed at all; it is wrong here, where only typing is withheld. The
395
+ disabled appearance is now tied to `readOnly` alone.
396
+
397
+ #### [3.1.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.3) - 2026-09-23
398
+
399
+ ##### Added
400
+
401
+ - **`lib-catalog-select` takes a `searchable` input.** A catalog of one or two entries is a picker,
402
+ not a search: the text box invites typing that only gets in the way, and the minimum-length rule
403
+ hides the very entries the user opened the field to choose from. Setting `[searchable]="false"`
404
+ leaves the input read-only to the keyboard while the dropdown still opens on click and the clear
405
+ button still works.
406
+
407
+ It differs from `readOnly`, which means the value cannot be changed at all and takes the clear
408
+ button away. It does not suit an endpoint that answers `shouldUseCriteria`: that endpoint
409
+ returns nothing until it is given criteria, and there would be no way left to type any.
410
+
411
+ - **`lib-catalog-select` exposes `refresh()`.** The entries are fetched once, when the field
412
+ initialises, so a screen that changes the data behind the catalog -- granting a role, moving an
413
+ employee -- kept offering the list it read before the change. Calling `refresh()` re-reads the
414
+ catalog, including whether its endpoint now requires search criteria.
415
+
416
+ ##### Fixed
417
+
418
+ - **`lib-catalog-select` no longer reports "no results" when its endpoint arrives after the field
419
+ does.** A screen that reads the endpoint off a model it loads over HTTP binds an empty string
420
+ first. The field initialised against that, showed _no results found_ on the first open, and only
421
+ filled in once the user typed something and cleared it again.
422
+
423
+ With no endpoint the component takes its static-list path, finds an empty list and latches the
424
+ message. `searchEndpoint` was a plain input, so nothing re-ran the search when the real value
425
+ landed -- unlike `entriesList` and `filters`, which have always refreshed on change. It is now a
426
+ setter and re-reads the catalog, treating a new endpoint as a new catalog rather than carrying
427
+ over what the previous one reported about needing search criteria.
428
+
429
+ #### [3.1.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.2) - 2026-09-22
430
+
431
+ ##### Fixed
432
+
433
+ - **Group headings in a narrow navigation panel no longer spill their label past the edge.** On a
434
+ window under 768px the panel opens at its collapsed width, but each group still printed its full
435
+ label -- clipped mid-word against a bar with no room for it -- where it should show the short
436
+ divider the collapsed panel uses.
437
+
438
+ The width that widens the panel is declared inside the `md` query, so below that breakpoint the
439
+ panel stays narrow whatever its expanded class says. The heading, though, was keyed off that
440
+ class rather than the width it actually got. It now follows the width, with the overlay still
441
+ excluded -- that one does widen the panel at any size.
442
+
443
+ #### [3.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.1) - 2026-09-22
444
+
445
+ ##### Fixed
446
+
447
+ - **`lib-catalog-select` now offers its clear button for a value it did not see arrive.** A field
448
+ restored from a saved filter showed the value and its label but no way to empty it, so the only
449
+ escape was to clear the whole filter and start again.
450
+
451
+ The button asked an internal field that is written when the component itself takes the value --
452
+ a pick in the dropdown, or the initial read in `ngOnInit`. A form patched after the component
453
+ has initialised, and patched without an event, reaches the form control and the display and
454
+ nothing else. The button now asks the form control, which is the value that actually counts.
455
+
456
+ #### [3.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.0) - 2026-09-21
457
+
458
+ ##### Added
459
+
460
+ - Added the --tabs-* design tokens to the published stylesheet. They style @zambon-dev/framework's
461
+ tab strip; they live here because this package owns the shared token layer, alongside --sidebar-*.
462
+
463
+ #### [3.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.0.1) - 2026-09-21
464
+
465
+ - Maintenance release (no consumer-facing changes were documented).
466
+
467
+ #### [3.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.0.0) - 2026-09-21
468
+
469
+ - Maintenance release (no consumer-facing changes were documented).
470
+
471
+ #### [2.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v2.0.1) - 2026-09-21
472
+
473
+ ##### Changed
474
+
475
+ - `RibbonComponent`, `FormGroupComponent` and `GroupContainerComponent` now use
476
+ `ChangeDetectionStrategy.OnPush`. They render from their inputs only, so behaviour is
477
+ unchanged, but consuming applications no longer check them on every change detection pass.
478
+
479
+ #### [2.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v2.0.0) - 2026-09-21
480
+
481
+ ##### Changed
482
+
483
+ - **Accessibility.** Every interactive element that previously responded only to a mouse is now
484
+ reachable and operable by keyboard. `RibbonButtonComponent` (button, dropdown toggle and each
485
+ option), `GroupAccordionComponent`, `GroupScrollSpyComponent`, `SidebarItemComponent` and the
486
+ `MultiSelectComponent` result grid gained focus, `Enter`/`Space` activation and the matching ARIA
487
+ roles and state. `CatalogSelectComponent` status rows are now `role="presentation"`; its retry
488
+ action is a real button.
489
+ - `MultiEditorComponent` and the multi-select result grid render their actions as `<button>`
490
+ rather than `<a>`. They are styled identically but now honour `disabled` natively.
491
+
492
+ > **The 1.6.x line is retired.** It carried the library for applications still on Angular 19 while
493
+ > `main` moved to Angular 22, publishing under its own `1.6.x` dist-tag. Its branch is gone; the
494
+ > final state is kept in the `angular-19/library-1.6.x` tag. Every fix recorded below also shipped
495
+ > in a 3.x release, which requires Angular 22.
496
+
497
+ #### [1.6.9](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.9) - 2026-09-24
498
+
499
+ ##### Fixed
500
+
501
+ - **Pressing Enter in the multi-select search no longer saves the changes.** The search box sits
502
+ inside the modal's form, so Enter submitted it and the confirmation ran alongside the search --
503
+ committing a selection the user was still building. Enter now searches and nothing else.
504
+
505
+ - **The remove button in the multi-select panel works for an entry the search never showed.** It
506
+ removed by deselecting the row in the grid on the left, which knows nothing about a row it has
507
+ not loaded, so clicking the cross on an entry further down the list did nothing. The panel now
508
+ tells the dataset directly, and still deselects the grid row when there is one.
509
+
510
+ - **Grid checkboxes are no longer drawn as disabled.** The disabled attribute was bound to the
511
+ boolean itself, and an attribute binding writes `disabled="false"` for false -- which disables a
512
+ control just as firmly as any other value. Every checkbox rendered faded and unusable, leaving
513
+ the row label as the only way to toggle a selection.
514
+
515
+ - **A multi-select or multi-editor no longer adds a gap below the page.** Both render nothing but
516
+ a fixed-position modal, yet their host still counted as a child in the parent's layout, so a
517
+ flex column with a gap reserved a slot for it under the last visible element.
518
+
519
+ #### [1.6.8](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.8) - 2026-09-23
520
+
521
+ ##### Fixed
522
+
523
+ - **A `lib-catalog-select` with `searchable` off no longer looks disabled.** It was drawn on the
524
+ same grey as a disabled field, so nothing distinguished a picker you are meant to click from a
525
+ control that is not yours to touch.
526
+
527
+ Turning searching off makes the inner input read-only to the keyboard, and `lib-form-input`
528
+ paints a read-only input as disabled by default. That default is right for `readOnly`, which
529
+ means the value cannot be changed at all; it is wrong here, where only typing is withheld. The
530
+ disabled appearance is now tied to `readOnly` alone.
531
+
532
+ #### [1.6.7](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.7) - 2026-09-23
533
+
534
+ ##### Added
535
+
536
+ - **`lib-catalog-select` takes a `searchable` input.** A catalog of one or two entries is a picker,
537
+ not a search: the text box invites typing that only gets in the way, and the minimum-length rule
538
+ hides the very entries the user opened the field to choose from. Setting `[searchable]="false"`
539
+ leaves the input read-only to the keyboard while the dropdown still opens on click and the clear
540
+ button still works.
541
+
542
+ It differs from `readOnly`, which means the value cannot be changed at all and takes the clear
543
+ button away. It does not suit an endpoint that answers `shouldUseCriteria`: that endpoint
544
+ returns nothing until it is given criteria, and there would be no way left to type any.
545
+
546
+ - **`lib-catalog-select` exposes `refresh()`.** The entries are fetched once, when the field
547
+ initialises, so a screen that changes the data behind the catalog -- granting a role, moving an
548
+ employee -- kept offering the list it read before the change. Calling `refresh()` re-reads the
549
+ catalog, including whether its endpoint now requires search criteria.
550
+
551
+ ##### Fixed
552
+
553
+ - **`lib-catalog-select` no longer reports "no results" when its endpoint arrives after the field
554
+ does.** A screen that reads the endpoint off a model it loads over HTTP binds an empty string
555
+ first. The field initialised against that, showed _no results found_ on the first open, and only
556
+ filled in once the user typed something and cleared it again.
557
+
558
+ With no endpoint the component takes its static-list path, finds an empty list and latches the
559
+ message. `searchEndpoint` was a plain input, so nothing re-ran the search when the real value
560
+ landed -- unlike `entriesList` and `filters`, which have always refreshed on change. It is now a
561
+ setter and re-reads the catalog, treating a new endpoint as a new catalog rather than carrying
562
+ over what the previous one reported about needing search criteria.
563
+
564
+ #### [1.6.6](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.6) - 2026-09-22
565
+
566
+ ##### Fixed
567
+
568
+ - **Group headings in a narrow navigation panel no longer spill their label past the edge.** On a
569
+ window under 768px the panel opens at its collapsed width, but each group still printed its full
570
+ label -- clipped mid-word against a bar with no room for it -- where it should show the short
571
+ divider the collapsed panel uses.
572
+
573
+ The width that widens the panel is declared inside the `md` query, so below that breakpoint the
574
+ panel stays narrow whatever its expanded class says. The heading, though, was keyed off that
575
+ class rather than the width it actually got. It now follows the width, with the overlay still
576
+ excluded -- that one does widen the panel at any size.
577
+
578
+ #### [1.6.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.5) - 2026-09-22
579
+
580
+ ##### Fixed
581
+
582
+ - **`lib-catalog-select` now offers its clear button for a value it did not see arrive.** A field
583
+ restored from a saved filter showed the value and its label but no way to empty it, so the only
584
+ escape was to clear the whole filter and start again.
585
+
586
+ The button asked an internal field that is written when the component itself takes the value --
587
+ a pick in the dropdown, or the initial read in `ngOnInit`. A form patched after the component
588
+ has initialised, and patched without an event, reaches the form control and the display and
589
+ nothing else. The button now asks the form control, which is the value that actually counts.
590
+
591
+ #### [1.6.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.4) - 2026-09-21
592
+
593
+ ##### Fixed
594
+
595
+ - **Host styles now apply.** Ten components declared their host block as `:host-context {` with no
596
+ argument. That is not a valid host selector, so every rule inside was silently dropped -- the
597
+ ribbon never became a flex row (its buttons stacked vertically), `lib-group-container` never got
598
+ its card background or border, and `lib-data-grid`, `lib-form-group`, `lib-group-accordion`,
599
+ `lib-group-scroll-spy`, `lib-catalog-select`, `lib-ribbon-group`, `lib-ribbon-button` and the
600
+ multi-select result grid all rendered unstyled at the host level. They are `:host {` now.
601
+
602
+ - `CatalogSelectComponent` subscribed to its `forkJoin` with an empty handler; it now subscribes
603
+ without one. No behavioural change.
604
+
605
+ #### [1.6.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.3) - 2026-09-20
606
+
607
+ ##### Fixed
608
+
609
+ - Declared `ngx-resize-observer` and `rxjs` in `peerDependencies`. `DataGridRowComponent` imports
610
+ `ngx-resize-observer` at runtime, but the package was never declared, so installing
611
+ `@zambon-dev/library` on its own left the dependency unresolved.
612
+ - Added a README to the published package.
613
+
614
+ ##### ⚠ Breaking Changes / Migration
615
+
616
+ **Requires Angular 22.** Every `@angular/*` peer range moved from `^19.1.0` to `^22.0.0`, and
617
+ the package is built and tested against Angular 22.1 with TypeScript 6.0.
618
+
619
+ Upgrade your application to Angular 22 first — Angular does not support skipping majors, so go
620
+ 19 → 20 → 21 → 22 with `ng update`. Angular 22 also requires Node.js 22.22.3 or newer.
621
+
622
+ - Requires `ngx-resize-observer` 4.x (the Angular 22 line); the peer range moved from `^3.0.0`.
623
+
624
+ **`FormInputComponent` outputs renamed.** Four outputs were named after native DOM events. Because
625
+ `<lib-form-input>` is a real element, `(change)` and `(input)` fired a consumer's handler twice --
626
+ once from the native event bubbling out of the inner control, once from the component -- and
627
+ `(blur)`/`(focus)` shadowed the native events. All four are typed `any`, so TypeScript could not
628
+ catch the mismatch.
629
+
630
+ Rename the bindings in your templates:
631
+
632
+ | Before | After |
633
+ | ---------- | ---------------- |
634
+ | `(blur)` | `(blurred)` |
635
+ | `(change)` | `(changed)` |
636
+ | `(focus)` | `(focused)` |
637
+ | `(input)` | `(inputChanged)` |
638
+
639
+ `(fixedValueChanged)` is unchanged, but its payload type is now `string` instead of `any` --
640
+ matching what it always emitted.
641
+
642
+ If you were relying on the double-fire, expect one call per event now instead of two.
643
+
644
+ #### [1.6.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.2) - 2026-09-20
645
+
646
+ ##### Fixed
647
+
648
+ - **A `lib-data-grid` column with no room from the start no longer drags its heading out of line.**
649
+ 1.6.1 removes the heading of a collapsed column, but only once that column had measured a real
650
+ width at some point. A column that never has any room does not, so it kept a heading over nothing
651
+ and pushed every column to its right along -- up to 263px on a fourteen-column screen, which is
652
+ the same drift 1.6.1 set out to remove.
653
+
654
+ Whether a zero is believable is now settled by the row rather than by what the column measured
655
+ before: if the row has a width the layout is live, so a column reporting zero really has
656
+ collapsed.
657
+
658
+ - **`lib-data-grid` headings no longer stay behind after you revisit a tab.** Re-activating a
659
+ detached tab puts the grid back in the page with its horizontal scroll at the start again, and no
660
+ scroll event follows to say so, so the headings kept the offset they had when you left: on a grid
661
+ scrolled 300px every heading sat 300px away from its column. The header now reconciles with the
662
+ body whenever the grid is re-measured, which is what re-inserting it triggers.
663
+
664
+ #### [1.6.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.1) - 2026-09-19
665
+
666
+ ##### Fixed
667
+
668
+ - **`lib-data-grid` headings now sit over their own column.** On a grid with many columns each
669
+ heading drifted further from its cells than the last -- up to 263px on a fourteen-column screen --
670
+ and scrolling sideways separated them completely.
671
+
672
+ The body lays the columns out as a CSS grid inside the CDK viewport; the heading row is a flex
673
+ row outside it that copies each column's measured width. Four things pulled the two apart:
674
+
675
+ - The heading cells could **shrink**, so once the columns together were wider than the view every
676
+ heading was compressed a little and the error accumulated across the row.
677
+ - The heading row **did not follow the body sideways**. The rows scroll inside the viewport and
678
+ the headings simply stayed where they were.
679
+ - A column was **measured once and never again**, while the body keeps resizing its
680
+ `minmax(x, min-content)` columns as virtual scrolling brings different content into view.
681
+ - A column measured at **zero** -- a `1fr` column collapsed for want of room -- left its heading
682
+ with no width at all, so it kept its text width and shoved everything to its right along.
683
+
684
+ Headings are now fixed to the measured width, translate with the body's horizontal scroll, track
685
+ every resize, and a heading whose column has collapsed is removed rather than left taking a gap
686
+ the body does not have. Measured in a running application the drift is zero across every column,
687
+ at rest and scrolled.
688
+
689
+ A column that declares no `size` is laid out as `minmax(0, 1fr)`, which is free to collapse. If
690
+ one of yours does, its heading now goes with it rather than staying behind as a label over
691
+ nothing. Give such a column a real minimum -- `size: 'minmax(8rem, 1fr)'` -- to keep both on
692
+ screen.
693
+
694
+ #### [1.6.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.0) - 2026-09-16
695
+
696
+ ##### Added
697
+
698
+ - **`DisplayControls`**, which records the form controls that exist only to show a catalog
699
+ selection's label. `lib-catalog-select` works in pairs — `controlName` holds the identifier,
700
+ `displayControlName` holds the text — and it creates the second itself when a screen does not
701
+ declare one, so a form carries controls nobody wrote down. It now marks whichever control it
702
+ drives as its display, and `framework-button-filters` reads that mark to keep those out of what
703
+ it submits.
704
+
705
+ The mark is held in a `WeakSet` keyed by the control instance, not by its name: two forms may
706
+ each have an `employeeName`, and only the one a catalog select drives is a display control.
707
+ Nothing about an application changes — declaring the display control yourself still works, and
708
+ it is marked just the same.
709
+
710
+ #### [1.5.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.5.0) - 2026-09-09
711
+
712
+ ##### Added
713
+
714
+ - **`SidebarConfigs.shouldDeriveAreasFromRootMenus`** — derive region headers from the menu tree
715
+ instead of from `SidebarMenu.region`. Off by default, so nothing changes until you opt in.
716
+
717
+ With it on, a top-level menu that has children **and no URL** stops being a collapsible node and
718
+ becomes an area header, with its children rendered flat beneath it as top-level items — icons
719
+ included. A top-level menu that has its own URL stays an item, and a parent that carries a URL
720
+ stays collapsible, so only the menus that were already acting purely as groups change shape.
721
+
722
+ The point is where the grouping lives. `region` is a label repeated on every item that belongs to
723
+ a group, matched by exact string equality: a typo silently splits one area into two, and the
724
+ area has no row of its own to carry an order or a translation. Derived from the tree, the area
725
+ _is_ a menu row — it already has a translated label and an order — and nothing has to be
726
+ duplicated across its items.
727
+
728
+ The children of an area are fetched **eagerly**, at load, because they are rendered without a
729
+ click and the lazy load a collapsible parent relies on would never fire. That is one extra
730
+ request per area. `region` keeps working exactly as before for anyone who prefers it; the two
731
+ mechanisms are independent and the flag chooses between them.
732
+
733
+ - **`SidebarService.loadChildrenFor(parentMenu)`** — loads a parent’s children and returns them as
734
+ an observable, for callers that need them before the user clicks. `loadChildren` is unchanged: it
735
+ is still the fire-and-forget variant that raises `childrenLoading` and `childrenFailed`, and it
736
+ now delegates to this one.
737
+
738
+ ##### ⚠ Breaking Changes / Migration
739
+
740
+ None. `shouldDeriveAreasFromRootMenus` defaults to `false`, so a sidebar keeps grouping by
741
+ `region` and keeps rendering top-level parents as collapsible nodes until you set it.
742
+
743
+ #### [1.4.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.4.1) - 2026-09-08
744
+
745
+ - Maintenance release (no consumer-facing changes were documented).
746
+
747
+ #### [1.4.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.4.0) - 2026-09-08
748
+
749
+ ##### Added
750
+
751
+ - **Sidebar menu items can now open an external destination instead of an internal route.**
752
+ `SidebarMenu` gained an optional `openMode` (`SidebarMenuOpenMode`): `Internal` (the default),
753
+ `ExternalNewTab`, or `ExternalEmbedded`. Read it through the exported
754
+ `toSidebarMenuOpenMode(menu.openMode)` helper rather than comparing the field directly — the
755
+ value is deserialized straight from your menu endpoint, so the helper accepts the mode as a
756
+ camelCase string, as a case-insensitive variant of it, or as the enum ordinal an ASP.NET Core
757
+ enum property serializes to. Anything unrecognized — an omitted field from a backend that
758
+ predates this feature, `null`, or a mode a newer version adds — degrades to `Internal`, so a
759
+ menu you have not touched behaves exactly as it does today.
760
+
761
+ - **`SidebarService.menuExternalUrlSelected`** — a separate output carrying the items whose mode
762
+ is not `Internal`. They are deliberately **not** announced on `menuUrlSelected`, which keeps its
763
+ existing contract: the URL it carries is always an Angular route. Acting on external items needs
764
+ `@zambon-dev/shared` (which opens the browser tab or the embedded view), or your own subscription
765
+ to the new output. Until then an external item highlights and does nothing, rather than sending
766
+ `https://…` to the router.
767
+
768
+ - **`SidebarConfigs.externalLinkText`** — the tooltip on items that open in a new browser tab.
769
+ Defaults to `Opens in a new browser tab` and, like `errorText` and `loadingText`, is rendered
770
+ as-is rather than through the translate pipe, so pass an already-localized string.
771
+
772
+ ##### Changed
773
+
774
+ - **Selection now follows the open mode.** An item that opens in a new browser tab is treated as an
775
+ action rather than a destination: it takes no selection pill and, importantly, does not clear the
776
+ selection of the view the user is still looking at. Items that open embedded become real
777
+ application tabs and keep today’s selection behaviour exactly. A parent that also carries a URL
778
+ still expands, so it cannot become impossible to open.
779
+
780
+ - **Items that open in a new browser tab show a trailing outbound glyph.** It sits in the same
781
+ absolutely-positioned slot as the parent chevron, so a long label still ellipsises inside the
782
+ anchor and the collapsed rail is unchanged, and it fades in with the sidebar like the other
783
+ affordances. Embedded items get no glyph — they stay inside the application. Items with no
784
+ `openMode`, or with `openMode: Internal`, render exactly as before.
785
+
786
+ ##### ⚠ Breaking Changes / Migration
787
+
788
+ None. `openMode` is optional, so a backend that does not send it produces today’s behaviour, and
789
+ `menuUrlSelected` is unchanged for internal items. If you subscribe to `menuUrlSelected` yourself
790
+ and want external items too, add a subscription to `menuExternalUrlSelected`.
791
+
792
+ #### [1.3.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.3.2) - 2026-07-30
793
+
794
+ ##### Added
795
+
796
+ - **Storybook: `Data Grid/Data Grid ▸ Multi Select Tall Rows` story** — multi-selection over rows
797
+ taller than the default `rowHeight` (41.6px), the height a row needs once it shows a thumbnail.
798
+ Misalignment in the selection column is obvious at that height, so this is the story to check the
799
+ selection checkbox against. Development-only: stories are excluded from the package build.
800
+
801
+ ##### Changed
802
+
803
+ - **`lib-ribbon` now hides itself when it contains no visible buttons.** A screen with no ribbon
804
+ actions — a dashboard, or any view that projects an empty `#ribbon` template — previously still
805
+ rendered the bar's border, background and padding around nothing, wasting vertical space at the top
806
+ of the page. This mirrors `lib-ribbon-group`, which already hid itself when none of its children
807
+ were visible. Ribbons with at least one visible group are unaffected. See
808
+ **⚠ Breaking Changes / Migration** below.
809
+
810
+ ##### Fixed
811
+
812
+ - **`lib-data-grid`'s multi-selection checkbox is now vertically centred in the row.** The checkbox is
813
+ styled with `vertical-align: super`, which lifts it off the middle of its line box — roughly 6px at
814
+ the default font size. At the default `configs.rowHeight` (41.6px) the drift is easy to miss, but on
815
+ taller rows (say `rowHeight: 56` for a row showing a thumbnail) the checkbox visibly hugged the top
816
+ of the row while the text columns stayed centred. The selection cell now lays its checkbox out as a
817
+ flex item, so it is centred by the box model and `vertical-align` no longer applies to it. The cell
818
+ is also tagged with a `selection` class, mirroring the header's `header-cell selection`. Horizontal
819
+ placement is unchanged, and no consumer changes are required.
820
+ - **Form inputs no longer render a stray bordered box when `type` is set as a static attribute.**
821
+ Angular keeps static attributes in the DOM even when a directive consumes them as an `@Input()`, so
822
+ `<lib-form-input-group type="checkbox">` left a `type="checkbox"` attribute on the host element —
823
+ where `@tailwindcss/forms`' tag-agnostic `[type="checkbox"]` base rules styled the host itself as a
824
+ checkbox (1px border, white fill, `height: 1rem`), drawing an empty-input box around the field row
825
+ and tinting its icon. `FormInputGroupComponent` and `FormInputComponent` now clear the attribute via
826
+ a host binding. This affects every value of `type` (`checkbox`, `date`, `number`, `password`, …);
827
+ both `type="checkbox"` and `[type]="'checkbox'"` now render identically, so existing call sites are
828
+ fixed with no changes required.
829
+ - **`lib-multi-editor` no longer discards rows the user did not type into.** `MultiEditorDataset.newData()`
830
+ exists so implementations can pre-fill a new row, but `onNewClick` reached the form through
831
+ `reset()` + `patchValue()`, which leaves it pristine — and the change tracker only records a row
832
+ while `formGroup.dirty`. Pre-filled values were therefore shown in the grid and then dropped from
833
+ the save batch. The visible symptom was worse than lost defaults: adding several rows and editing
834
+ only the last one saved just that row, because `onSaveClick` validates the current selection only,
835
+ so the earlier rows silently vanished on refresh. `newData()`'s values are now registered when the
836
+ row is added.
837
+ - **`lib-catalog-select` no longer renders an empty caption after a form reset.** `FormService.resetForm()`
838
+ clears every control and then patches the model back, so the component receives the value it already
839
+ held. It skipped refreshing the caption in that case, on the assumption that an unchanged value means
840
+ the caption is still on screen — but the reset had emptied the display control a moment earlier. The
841
+ field therefore rendered blank whenever the stored value equalled the control's initial value, which
842
+ is why it only ever showed on the entry the form defaults to: a `nonNullable` control declared as
843
+ `new FormControl(0)` bound to an enum whose first member is `0`, for instance. Values differing from
844
+ the initial one refreshed normally and always looked right, which made the fault read like a
845
+ translation or data problem rather than a reset one. The caption is now rewritten whenever it has
846
+ drifted from the selected entry, so the value alone no longer decides. Selects backed by a
847
+ `searchEndpoint` are unaffected, since their caption comes from the server-side selection rather than
848
+ `entriesList`. No consumer changes are required.
849
+
850
+ ##### ⚠ Breaking Changes / Migration
851
+
852
+ `lib-multi-editor` now sends rows that `newData()` pre-filled but the user never edited. Nothing to do
853
+ if your `newData()` returns an empty object (`{}`) — the common "blank row" convention — since a
854
+ value-less row is still skipped and the posted batch is byte-for-byte what it was before. If your
855
+ `newData()` _does_ return field values, those rows now reach `saveData()` where they were previously
856
+ dropped, so a batch may contain entries it did not before; verify your backend rejects or defaults
857
+ them as you expect.
858
+
859
+ `lib-ribbon` now hides itself when it has neither a visible `lib-ribbon-group` nor any other
860
+ projected content. No action needed if your ribbons use groups (the normal case). The rule is a
861
+ global selector at specificity (0,1,2), so overriding it requires `!important`. Because the bar can
862
+ now appear and disappear rather than merely resize, anything positioned relative to the ribbon may
863
+ shift when a button's visibility resolves (e.g. as permissions arrive).
864
+
865
+ #### [1.3.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.3.1) - 2026-07-28
866
+
867
+ > There is no `1.3.0` on npm: the version number was taken by an early publish that was later
868
+ > unpublished, and npm never allows a retired version number to be reused. The sidebar rework below
869
+ > therefore shipped as `1.3.1`. Upgrade straight from `1.2.0` to `1.3.1`.
870
+
871
+ ##### Added
872
+
873
+ - Semantic `--sidebar-*` design tokens covering the sidebar's surfaces, text, selection, glass, and
874
+ radii (`--sidebar-bg`, `--sidebar-nav-bg`, `--sidebar-text`, `--sidebar-text-muted`,
875
+ `--sidebar-item-hover-bg`, `--sidebar-item-selected-bg`, `--sidebar-blur`, `--sidebar-shadow`,
876
+ `--sidebar-tree-line`, `--sidebar-radius`, `--sidebar-item-radius`, …) so consumers can re-theme
877
+ the sidebar entirely via CSS custom properties.
878
+ - **Menu regions**: an optional `SidebarMenu.region` label groups top-level items under an uppercase
879
+ group header (e.g. "MAIN"). Items without a `region` render ungrouped, so it stays
880
+ backward-compatible. When the rail is collapsed, each header cross-fades to a short separator line
881
+ so the groups remain visually distinct. The grouping type is exported as `SidebarRegion`.
882
+
883
+ ##### Changed
884
+
885
+ - Selected menu items now render as a rounded **pill** highlight (inset when expanded) instead of the
886
+ previous 4px left accent bar.
887
+ - The sidebar is now a **translucent, blurred "glass" panel** — rounded corners, a soft drop shadow,
888
+ and a semi-transparent brand surface — rather than a solid, square, edge-to-edge rail (all
889
+ token-driven via `--sidebar-bg`, `--sidebar-blur`, `--sidebar-shadow`, `--sidebar-radius`).
890
+ - **Child (sub-menu) items** now render a rounded **tree-connector** line instead of a menu icon;
891
+ top-level items keep their icons.
892
+ - Collapsing/expanding the rail is now **animated end to end**: the toggle chevron and the
893
+ expandable-parent caret rotate in the same direction, the selection pill inset and item icons ease
894
+ into place, and region headers cross-fade to their separators — all in sync with the rail width.
895
+
896
+ ##### ⚠ Breaking Changes / Migration
897
+
898
+ The sidebar surface is now **translucent** ("glass"). If your app does not place a colored backdrop
899
+ behind the sidebar, the rail will look faint over plain/neutral content — either add a
900
+ colored/gradient backdrop behind it (see `@shared`'s `--app-backdrop` in `MainLayoutComponent`) or
901
+ override `--sidebar-bg` (and the other `--sidebar-*` surface tokens) with opaque values to keep a
902
+ solid rail.
903
+
904
+ #### [1.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.2.0) - 2026-07-24
905
+
906
+ ##### Added
907
+
908
+ - `SidebarComponent` now renders its own collapse/expand toggle: a light circular chevron "handle"
909
+ that straddles the sidebar's right edge (`angle-left` to collapse when expanded, `angle-right` to
910
+ expand when collapsed), matching the conventional rail-collapse affordance.
911
+ - `SidebarComponent` now has a content-projection slot (`<ng-content>`) rendered as a footer at the
912
+ bottom of the sidebar — for extra elements after the menu (e.g. an app version). It is hidden when
913
+ the rail is collapsed and collapses entirely when nothing is projected.
914
+
915
+ ##### Changed
916
+
917
+ - `SidebarComponent` no longer renders the logo or the user-profile header — it now shows only the
918
+ navigation toggle and menu. Application branding and user identity are expected to live in the top
919
+ bar (`shared-top-bar` in `@shared`).
920
+ - Sidebar menu items now use an on-palette hover surface (a subtle light overlay) instead of the
921
+ previous off-palette `slate-700`.
922
+
923
+ ##### ⚠ Breaking Changes / Migration
924
+
925
+ If you relied on the sidebar's logo (`SidebarConfigs.logoCollapsedPath` / `logoExpandedPath`) or its
926
+ user profile (`SidebarService.getUserProfile()`), move that presentation to the top bar. Those
927
+ config options and the `getUserProfile()` method still exist but are no longer consumed by
928
+ `SidebarComponent`.
929
+
930
+ #### [1.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.1.1) - 2026-06-17
931
+
932
+ - Baseline release: the changelog starts being tracked from this version. Earlier history is
933
+ available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
934
+ `library-v*` tags.
935
+
936
+ ### Framework
937
+
938
+ Tabbed navigation, view scaffolding, ribbon buttons and modals. Published as `@zambon-dev/framework` through 5.0.5.
939
+
940
+ #### [5.0.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.5) - 2026-10-24
941
+
942
+ - Maintenance release (no consumer-facing changes were documented).
943
+
944
+ #### [5.0.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.4) - 2026-10-24
945
+
946
+ ##### Changed
947
+
948
+ - **Every component in this package now renders under any change-detection strategy the host
949
+ application chooses, zoneless included.** The ribbon buttons were the last group still
950
+ declaring `Eager`, and the one that depended on it most: a button answers to things that
951
+ arrive after the first paint -- an access check resolving, a grid announcing a selection, a
952
+ load finishing -- and none of that reached the screen beneath a clean `OnPush` ancestor.
953
+ Their state is signals now, and `src/lib/change-detection-contract.spec.ts` asserts over the
954
+ public API that nothing in this package opts out again.
955
+
956
+ - **A view's `loading` flag notifies.** `ViewBase` backs it with a signal behind the same
957
+ accessors, so a subclass reading or writing `this.loading` needs no change and its own
958
+ template finally reflects it.
959
+
960
+ - **The legacy form's `viewMode` notifies.** Same treatment, same accessors. It is what the
961
+ legacy Edit and Save buttons render from.
962
+
963
+ ##### Fixed
964
+
965
+ - **A button no longer asks twice whether its actions are allowed.** `allowedActions` fired the
966
+ check from its setter _and_ the constructor fired it again before any input had arrived, so
967
+ every button with an access list made two requests. It is one stream over the input now.
968
+
969
+ - **A button with an access list is disabled until the answer arrives**, rather than clickable
970
+ and then hidden. This is what `RibbonButtonComponent` already does with a dropdown option
971
+ whose access has not come back.
972
+
973
+ - **The confirm and delete buttons show their modal's spinner again.** Their footer button read
974
+ `modal.isLoading` through a template reference, which became a signal object in 5.0.3 -- and
975
+ an object is always truthy, so the spinner was permanently on.
976
+
977
+ ##### ⚠ Breaking Changes / Migration
978
+
979
+ - **`BaseButton`'s state is signals.** Binding to any of them from a template is unchanged.
980
+ Code that touches them is not:
981
+
982
+ | Before | After |
983
+ | ---------------------------- | -------------------------- |
984
+ | `this.disabled = true` | `this.disabled.set(true)` |
985
+ | `this.loading = true` | `this.loading.set(true)` |
986
+ | `this.options = [...]` | `this.options.set([...])` |
987
+ | `this.options.find(...)` | `this.options().find(...)` |
988
+ | `this.allowedActions` | `this.allowedActions()` |
989
+ | `this.iconSize` | `this.iconSize()` |
990
+ | `this.button.startLoading()` | `this.startLoading()` |
991
+
992
+ `startLoading()` and `finishLoading(status)` keep their signatures. `disabled`, `loading` and
993
+ `options` are `model()`, so a subclass can still drive them; the rest are `input()`.
994
+
995
+ - **Every button component's own inputs are signals too**, and so are the view queries they
996
+ expose. The ones a host application is most likely to read directly:
997
+ `ButtonFiltersComponent.modal` is now a `Signal<ModalComponent | undefined>`, and
998
+ `ButtonViewsLegacyComponent.views` is an `input()`. A template reading one through a
999
+ reference -- `#btn` then `btn.disabled` -- needs `btn.disabled()`; without the call it reads
1000
+ the signal object, which is always truthy.
1001
+
1002
+ - **`ViewBase` has a new protected member, `loadingState`**, the signal behind `loading`. A
1003
+ subclass that already declares something by that name has to rename it.
1004
+
1005
+ - **`DefaultDetailsTabViewComponent`'s protected state is signals**: `containerIcon`,
1006
+ `containerTitle` and `detailsViewRoute`. A subclass that reads one needs `()`; one that
1007
+ assigns one needs `.set(...)`.
1008
+
1009
+ - The New, Edit and Views buttons only appear once a record exists, which they read from
1010
+ `DataProviderService`. That now announces its entity id, so install the `@zambon-dev/library`
1011
+ released alongside this version rather than an older 4.x.
1012
+
1013
+ #### [5.0.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.3) - 2026-10-24
1014
+
1015
+ ##### Changed
1016
+
1017
+ - **The breadcrumbs, the confirm modal and the error modal render under any change-detection
1018
+ strategy.** The breadcrumbs were the clearest case of a getter over state a service mutates
1019
+ elsewhere, and both modals are driven imperatively by a parent calling `showModal` from a
1020
+ subscription -- a message or a title landed with nothing marking the view dirty.
1021
+
1022
+ - **The ribbon strip renders without the view forcing a change-detection pass.**
1023
+ `DefaultTabViewComponent` holds the published template in a signal, and `TabViewService` holds
1024
+ its active view in one. The `detectChanges()`/`markForCheck()` pair they used to need is gone,
1025
+ and with it the rule that the ribbon template could never be assigned after the first pass --
1026
+ a signal write there schedules another pass rather than throwing NG0100.
1027
+
1028
+ ##### ⚠ Breaking Changes / Migration
1029
+
1030
+ - **`ConfirmModalComponent` and `ErrorModalComponent` expose their inputs as signals.** Binding to
1031
+ them from a template is unchanged. Code that sets one before showing the modal keeps working
1032
+ through `.set(...)`, because the ones a parent drives -- `errorMessage`, `isLoading`, and the
1033
+ error modal's `title` and `subtitle` -- are `model()`. Code that _reads_ one needs `()`:
1034
+ `confirmModal.isLoading()` rather than `confirmModal.isLoading`.
1035
+
1036
+ - **`DefaultTabViewComponent` no longer takes constructor arguments.** It injects `TabViewService`
1037
+ itself and no longer injects `ChangeDetectorRef` at all. A subclass calling
1038
+ `super(changeDetectorRef, tabViewService)` should call `super()`, or drop its constructor.
1039
+
1040
+ - **`DefaultTabViewComponent.ribbonTemplate` is a `Signal<TemplateRef | undefined>`.** Read it as
1041
+ `ribbonTemplate()`. `TabViewService.onUpdateRibbonTemplate` is unchanged, so anything
1042
+ subscribing to it keeps working.
1043
+
1044
+ #### [5.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.2) - 2026-10-24
1045
+
1046
+ ##### Changed
1047
+
1048
+ - **Internal only: `ButtonFiltersComponent` reads `RibbonButtonComponent.loading` as a signal.**
1049
+ The compiler caught the old form: in a condition, the signal object itself is always true.
1050
+ Nothing in this package's own API changed.
1051
+
1052
+ #### [5.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.1) - 2026-10-24
1053
+
1054
+ ##### Changed
1055
+
1056
+ - **Internal only: call sites moved with `@zambon-dev/library`'s modal API.** `ModalComponent.isShown`
1057
+ became `isOpen()`, and the confirm and error modals delegate to it through a signal view query.
1058
+ Nothing in this package's own API changed.
1059
+
1060
+ ##### Fixed
1061
+
1062
+ - **The peer range on its sibling packages now admits the majors they were released at.** The last
1063
+ release published `@zambon-dev/library` 4.0.0, `@zambon-dev/framework` 5.0.0 and
1064
+ `@zambon-dev/shared` 6.0.0, but the ranges here still asked for the previous majors, so an
1065
+ application installing two of them together got `ERESOLVE`. Nothing else changed.
1066
+
1067
+ #### [5.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.0) - 2026-10-24
1068
+
1069
+ ##### Changed
1070
+
1071
+ - **The view and button base classes no longer declare a change-detection strategy.** This is not
1072
+ a behaviour change: Angular has never inherited `changeDetection`, so the `Eager` these carried
1073
+ applied to the base class itself -- which is never instantiated -- and never to your subclasses.
1074
+ Your components have compiled to OnPush since you upgraded to Angular 22, whatever the base said.
1075
+ The declaration is gone because it described nothing.
1076
+
1077
+ ##### Fixed
1078
+
1079
+ - **Ribbon buttons appear once their access check resolves.** `BaseButton` resolved visibility
1080
+ from an asynchronous `checkActionsAreAllowed` call and assigned a plain field, which told Angular
1081
+ nothing. Under Angular 22's default strategy the button stayed hidden until something else forced
1082
+ a render.
1083
+ - **The tab strip renders the tabs you open.** In an application whose root component uses
1084
+ Angular 22's default strategy, opening anything from the menu changed the URL and left the tab
1085
+ bar, the view, the ribbon and the breadcrumbs blank: `TabsComponent` reads the open tabs
1086
+ through getters on `TabService`, which mutates its arrays in place, and nothing told the
1087
+ component to render. `TabService` now announces its own changes, so the tabs, the titles that
1088
+ resolve later, and the breadcrumbs all reach the screen. Found by running SecurityHub against
1089
+ this package rather than by a test -- which is why one now guards it.
1090
+
1091
+ - **The tab strip re-measures when it narrows.** Bringing the selected tab back into view hung off
1092
+ `ngAfterViewChecked`, which only ever ran because zone.js produced a pass on every event. The
1093
+ `ResizeObserver` -- which is how a browser actually reports a box changing -- now does the full
1094
+ re-measure, and the post-render work uses `afterEveryRender`.
1095
+
1096
+ ##### ⚠ Breaking Changes / Migration
1097
+
1098
+ - **`BaseButton.visible` is now `isVisible`, and it is a signal.** Every component that extends
1099
+ `BaseButton` is affected, in its template and its code:
1100
+
1101
+ | Replace | With |
1102
+ | --------------------- | ----------------------- |
1103
+ | `@if (visible)` | `@if (isVisible())` |
1104
+ | `this.visible` | `this.isVisible()` |
1105
+ | `!isAccessLoaded` | `!isAccessLoaded()` |
1106
+ | `this.isAccessLoaded` | `this.isAccessLoaded()` |
1107
+
1108
+ Both were renamed or re-typed deliberately, so a call site you miss is a compile error rather
1109
+ than a button that quietly never appears. Before you start, turn on `strictTemplates` and
1110
+ `"interpolatedSignalNotInvoked": "error"` under `angularCompilerOptions.extendedDiagnostics` in
1111
+ your `tsconfig.json`, which makes `{{ someSignal }}` a build failure too.
1112
+
1113
+ - **A component that delegates its visibility to an inner button needs a signal view query.** If
1114
+ you implement `RibbonGroupChild` by wrapping a `framework-button` and forwarding its state,
1115
+ a rename alone is not enough -- a `computed` reading a plain `@ViewChild` never re-runs when
1116
+ the query resolves:
1117
+
1118
+ ```ts
1119
+ // before
1120
+ @ViewChild(ButtonComponent) private buttonComponent!: ButtonComponent;
1121
+ public get visible(): boolean {
1122
+ return this.buttonComponent?.visible ?? false;
1123
+ }
1124
+
1125
+ // after
1126
+ private readonly buttonComponent = viewChild(ButtonComponent);
1127
+ public readonly isVisible: Signal<boolean> = computed(() => this.buttonComponent()?.isVisible() ?? false);
1128
+ ```
1129
+
1130
+ Every other read of that query moves to `this.buttonComponent()?.…` as well.
1131
+
1132
+ - **`TabsComponent` no longer injects `ChangeDetectorRef`.** Only relevant if you construct it
1133
+ yourself; used through its selector, nothing changes.
1134
+
1135
+ #### [4.0.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.5) - 2026-10-22
1136
+
1137
+ ##### Fixed
1138
+
1139
+ - **Going back in one tab no longer drops a view another tab still has open.** Every open tab keeps
1140
+ its views in the reuse cache — the one it shows or one in its history — and a view should leave
1141
+ that cache only once no tab has its URL open. `navigateCurrentTabBack` cleared the handle of every
1142
+ entry it removed regardless, so a record open in two tabs lost its cached view in the other one
1143
+ too. It now checks first, as `closeTab` and `replaceCurrentTabSubView` already did.
1144
+
1145
+ #### [4.0.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.4) - 2026-10-10
1146
+
1147
+ ##### Fixed
1148
+
1149
+ - **A record’s non-form views can render their edit button again.** `framework-button-edit` asks
1150
+ for `FormService` and `DataProviderService` together, and the details shell provided only the
1151
+ second — so a child list routed as its own view (Aplicações → Visões → Business Places, Centros
1152
+ de Custo) threw `NG0201: No provider found for FormService` before it rendered at all. The shell
1153
+ provides one now; a form view’s own provider still shadows it, so forms are unchanged.
1154
+
1155
+ - **The breadcrumbs no longer sit further from the ribbon than the ribbon does from its content.**
1156
+ They carried `padding-bottom` _and_ `margin-bottom` around their rule, 8px + rule + 8px, against
1157
+ the 8px below the ribbon. The tab panel spaces its children with a gap now, as the views do, and
1158
+ the breadcrumbs carry neither — 9px, the gap plus the rule itself.
1159
+
1160
+ #### [4.0.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.3) - 2026-10-02
1161
+
1162
+ ##### Fixed
1163
+
1164
+ - **Closing a tab no longer sends the tab beside it back to its root.** The focused tab was
1165
+ navigated to `activeTabs[i]`, which is each tab’s _root_ — the entry the strip takes its label
1166
+ from — rather than the top of its history. A tab opened as a list and then drilled into a record
1167
+ went back to the list, under the record’s own tab, until the tab was clicked again. Every other
1168
+ navigation in the service already took the top of the stack; this one is now shared with
1169
+ `activateTab` so the two cannot drift apart.
1170
+
1171
+ - **The gap between the ribbon and a list is the one the view asks for.** `router-outlet` renders
1172
+ nothing but is still a flex item, and a flex item of zero height takes a gap on each side, so a
1173
+ list view showed 1rem where its host asked for 0.5rem — while a detail view, whose outlet is
1174
+ nested inside `lib-group-container`, showed 0.5rem. The outlet is out of the flow now, which
1175
+ leaves the spacing to the host rather than to what each view happens to contain.
1176
+
1177
+ #### [4.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.2) - 2026-10-01
1178
+
1179
+ ##### Fixed
1180
+
1181
+ - **Loading the package under Jest no longer throws
1182
+ `Cannot access 'ButtonNewLegacyComponent' before initialization`.** `LegacyTabViewDetails`
1183
+ queries its New button with `@ViewChild`, and the legacy buttons import the views barrel back,
1184
+ so the class is defined before the button: in the published bundle the button sits further down
1185
+ the file. An application build never noticed, because its linker moves the reference into a
1186
+ function, but a JIT runtime reads it the moment the package is imported, so every spec that
1187
+ imported anything from `@zambon-dev/framework` failed before running a test. The query is a
1188
+ forward reference now. Nothing to change in applications, and specs excluded for this reason can
1189
+ be put back.
1190
+
1191
+ #### [4.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.1) - 2026-09-26
1192
+
1193
+ ##### Fixed
1194
+
1195
+ - **The selected tab and the panel no longer have a seam between them.** The panel drew its rim
1196
+ on all four sides, and along the top that rim met the selected tab’s fill rather than the
1197
+ backdrop it was meant for: `--tabs-border` is 60% white against the tab’s 88%, so a _darker_
1198
+ hairline ran exactly along the join and made the tab read as a separate, slightly lighter shape
1199
+ sitting on the panel. The top edge belongs to the tabs, which carry no border, so the panel no
1200
+ longer draws one there.
1201
+
1202
+ #### [4.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.0) - 2026-09-26
1203
+
1204
+ ##### Fixed
1205
+
1206
+ - **The filters button no longer waits for an event that has already happened.** It starts
1207
+ disabled and learns otherwise from the grid's `loadFinished`, which is right only for a
1208
+ button that exists before its grid first loads. A ribbon template is re-created whenever a
1209
+ record's views are switched, so a button built then reached a grid that had finished loading
1210
+ long ago and stayed unclickable until something made the grid load again. It now reads the
1211
+ state from the dataset as well.
1212
+
1213
+ #### [3.2.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.2.2) - 2026-09-26
1214
+
1215
+ ##### Fixed
1216
+
1217
+ - **Component styles are compiled again.** The Tailwind 4 migration left every component's own
1218
+ stylesheet unprocessed in the published bundle, so its `@apply` rules reached the browser as an
1219
+ at-rule the browser does not know and it dropped every rule that used one. In practice the
1220
+ components rendered unstyled in any application installing 3.2.1. ng-packagr resolves its
1221
+ PostCSS configuration from the library directory rather than from the workspace root, and the
1222
+ migration added one only at the root. Nothing failed along the way: Storybook compiles from
1223
+ source and never sees the packaged artifact, which is why `tools/check-packaged-styles.mjs` now
1224
+ runs against that artifact in CI and again before every publish.
1225
+
1226
+ #### [3.2.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.2.1) - 2026-09-26
1227
+
1228
+ ##### Fixed
1229
+
1230
+ - **Both tab scroll controls stay put while the strip overflows**, disabled at the end they point
1231
+ at rather than removed. Rendering them per-direction meant the one being reached for disappeared
1232
+ on arrival: the strip changed width under the pointer and the click landed on nothing, which is
1233
+ what made the arrows look like they sometimes did nothing.
1234
+
1235
+ - **The last tab no longer floats past the panel's rounded corner.** While a tab or a scroll
1236
+ control holds the strip's right edge, the panel's top-right corner is square, because the card's
1237
+ corner is up on the strip; rounding both left whatever sat at the edge standing over the sweep
1238
+ where the panel had curved away. With the tabs short of the edge the corner is the panel's again
1239
+ and stays round.
1240
+
1241
+ #### [3.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.2.0) - 2026-09-25
1242
+
1243
+ ##### Changed
1244
+
1245
+ - **Tailwind CSS 4.** The component styles in this package are compiled against Tailwind 4 now.
1246
+ They are expanded at build time, so nothing in the published bundle changed shape -- but the
1247
+ application consuming it has to be on Tailwind 4 as well.
1248
+
1249
+ #### [3.1.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.5) - 2026-09-25
1250
+
1251
+ ##### Fixed
1252
+
1253
+ - **The selected tab and the panel are the same colour again.** Both already named an 88% white,
1254
+ but the panel lays its fill over a backdrop it has blurred and the tab laid the identical fill
1255
+ over an unblurred one — two results from one token, and a visible seam between them. The tab
1256
+ carries the same blur now.
1257
+
1258
+ - **A tab reaching the right edge no longer sticks out past the panel's rounded corner.** The
1259
+ strip spans the panel's full width, so once the tabs filled it the last one's square corner was
1260
+ left standing over the point where the panel had already curved away. The strip carries the
1261
+ panel's corner now, which clips whatever reaches it; the scroll chevron does the same when it
1262
+ is the element at the edge.
1263
+
1264
+ - **The tab panel publishes `--surface-radius`** for whatever sits directly inside it — the
1265
+ container's radius minus its own padding and border, which is the value that keeps an inner box
1266
+ looking parallel to it. `@zambon-dev/library`'s grid and group container read it.
1267
+
1268
+ ##### ⚠ Breaking Changes / Migration
1269
+
1270
+ **Requires Tailwind CSS 4**, and `@zambon-dev/shared` 5.x, which ships the theme. The migration
1271
+ steps are in the `@zambon-dev/shared` changelog for this release; there is nothing specific to
1272
+ this package to do beyond following them.
1273
+
1274
+ #### [3.1.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.4) - 2026-09-25
1275
+
1276
+ ##### Fixed
1277
+
1278
+ - **The peer range on `@zambon-dev/library` now matches the version this package is built against.**
1279
+ It still asked for `^2.0.0` after the library moved to 3.x, so installing `framework` and
1280
+ `library` together at their current releases failed `npm install` with ERESOLVE. An application
1281
+ could only get past it with `--legacy-peer-deps` or an override, both of which switch off the
1282
+ peer checking that would have caught a genuine mismatch.
1283
+
1284
+ #### [3.1.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.3) - 2026-09-24
1285
+
1286
+ ##### Fixed
1287
+
1288
+ - **A confirm or error modal no longer adds a gap below the page.** Both render nothing but a
1289
+ fixed-position modal, yet their host still counted as a child in the parent's layout, so a flex
1290
+ column with a gap reserved a slot for it under the last visible element -- which is why a detail
1291
+ view and some list views sat further from the bottom of the window than the rest.
1292
+
1293
+ #### [3.1.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.2) - 2026-09-22
1294
+
1295
+ ##### Fixed
1296
+
1297
+ - **A filtered view no longer loses its Filter and Clear filters state when another details tab is
1298
+ opened.** The grid stayed filtered while the ribbon came back as though nothing were filtered, so
1299
+ the only way out was to filter again. Opening a list view never did it -- which is what made the
1300
+ behaviour look arbitrary.
1301
+
1302
+ Every details tab keeps its router subscription alive, including the ones the reuse strategy has
1303
+ detached, and the lookup that finds the active details route reads the router, which names
1304
+ whichever tab the navigation went to. A detached tab therefore adopted the incoming tab's base
1305
+ path and switched itself to that tab's view, tearing its own ribbon down and building a fresh
1306
+ one -- and the new buttons knew nothing of the filter still held by the grid. A list view is not
1307
+ a details route, so the lookup found nothing and the tab was left alone.
1308
+
1309
+ A tab now answers only to navigations matching its own route definition, which still covers the
1310
+ one navigation that legitimately changes its path: the redirect from `/new` to `/:id` after a
1311
+ save.
1312
+
1313
+ #### [3.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.1) - 2026-09-22
1314
+
1315
+ ##### Fixed
1316
+
1317
+ - **Switching views in a details tab no longer moves the buttons to the wrong view.** Opening a
1318
+ second view showed the first view's buttons instead of its own, and going back to the first view
1319
+ left it with no buttons at all -- on the employee screen, Histórico showed the Detalhes ribbon and
1320
+ Detalhes then showed an empty one.
1321
+
1322
+ 1.4.1 taught a tab to claim a ribbon published before its view had been named, which is what a tab
1323
+ opened straight at a non-default view needs. It claimed on every naming, though, and the empty id
1324
+ is not a spare bucket: it is the default view's own id, since that view is the one whose URL
1325
+ carries no sub-path. From the second switch onwards the template being claimed belonged to the
1326
+ view being left. Only the first naming may claim it now.
1327
+
1328
+ Also released as 1.4.2 on the 1.4.x line, for applications still on Angular 19.
1329
+
1330
+ #### [3.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.0) - 2026-09-21
1331
+
1332
+ ##### Added
1333
+
1334
+ - **Tabs close on middle click**, and with `Delete` while a tab has focus.
1335
+ - **The tab strip scrolls.** It was `overflow: hidden`, so once the tabs were wider than the bar the
1336
+ ones past the right edge could not be reached at all. Selecting a tab also scrolls it into view.
1337
+ - **Scroll controls at each end of the strip**, shown only while there is something to scroll to.
1338
+ - **A vertical mouse wheel over the strip scrolls it horizontally**, so a plain wheel mouse can
1339
+ reach an off-screen tab without going to the controls. Trackpad and `Shift`+wheel gestures, which
1340
+ already produce horizontal deltas, are left to the browser.
1341
+
1342
+ ##### Changed
1343
+
1344
+ - **Tabs restyled to match the sidebar's glass**: a translucent, blurred, rounded panel, with the
1345
+ active tab and the content sharing one fill so they read as a single card. Every colour, radius
1346
+ and dimension comes from a `--tabs-*` custom property now, so the look can be retuned without
1347
+ touching the component. Breadcrumbs follow the same tokens.
1348
+
1349
+ The glass is painted on a `::before` layer rather than on the panel itself. `backdrop-filter`
1350
+ makes an element the containing block for its `position: fixed` descendants, and views render
1351
+ hidden, viewport-width modal overlays inside the panel — filtering it directly pulled those into
1352
+ its scroll width and produced a phantom horizontal scrollbar.
1353
+
1354
+ - **The tablist is a single tab stop.** `Tab` reaches the selected tab and the arrow keys move
1355
+ between tabs from there, with `Home`/`End` for the ends. Previously every tab was its own tab
1356
+ stop and there was no arrow-key support.
1357
+ - **The panel is a real `tabpanel`**, with `aria-controls` on each tab and `aria-labelledby` on the
1358
+ panel, so assistive technology can tell which panel a tab owns.
1359
+ - **Close buttons are named after their tab** — "Close Acme Industries" rather than six identical
1360
+ "Close tab". `Tabs-CloseTab` takes a `title` parameter now; if you have overridden that key in
1361
+ your own translations, add `{{title}}` to it.
1362
+ - **A tab keeps its width while its title loads.** The spinner sits beside the label instead of
1363
+ replacing it, so the strip no longer reflows as each title resolves.
1364
+ - **Tabs compress before the strip scrolls**, down to `--tabs-item-min-width`, so a few more fit
1365
+ before any of them goes off screen. Overflowing tabs fade out at the edges instead of being cut
1366
+ off mid-tab.
1367
+ - **The selected tab is pulled back into view when the strip narrows**, not only when the selection
1368
+ changes. Resizing the window or collapsing the sidebar used to be able to leave you looking at a
1369
+ panel whose tab was nowhere on screen.
1370
+ - Breadcrumb separators are drawn in CSS rather than as a FontAwesome glyph codepoint, which was
1371
+ tied to a specific FontAwesome major.
1372
+
1373
+ #### [3.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.0.1) - 2026-09-21
1374
+
1375
+ - Maintenance release (no consumer-facing changes were documented).
1376
+
1377
+ #### [3.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.0.0) - 2026-09-21
1378
+
1379
+ - Maintenance release (no consumer-facing changes were documented).
1380
+
1381
+ #### [2.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v2.0.0) - 2026-09-21
1382
+
1383
+ ##### Changed
1384
+
1385
+ - **Accessibility.** `TabsComponent` renders proper `tablist`/`tab` semantics with `aria-selected`,
1386
+ its tabs are keyboard-activatable and the close control is a labelled `<button>`.
1387
+ `TabBreadcrumbsComponent` entries are focusable and activate with `Enter`/`Space`.
1388
+ - Added the `Tabs-CloseTab` translation key (en, pt) for the tab close button's accessible name.
1389
+
1390
+ > **The 1.4.x line is retired.** It carried the framework for applications still on Angular 19
1391
+ > while `main` moved to Angular 22, publishing under its own `1.4.x` dist-tag. Its branch is gone;
1392
+ > the final state is kept in the `angular-19/framework-1.4.x` tag. Every fix recorded below also
1393
+ > shipped in a 3.x or 4.x release, which requires Angular 22.
1394
+
1395
+ #### [1.4.7](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.7) - 2026-10-01
1396
+
1397
+ ##### Fixed
1398
+
1399
+ - **Loading the package under Jest no longer throws `Cannot access 'ButtonNewLegacyComponent'
1400
+ before initialization`.** `LegacyTabViewDetails` queries its New button with `@ViewChild`, and
1401
+ the legacy buttons import the views barrel back, so the class is defined before the button: in
1402
+ the published bundle the button sits further down the file. An application build never noticed,
1403
+ because its linker moves the reference into a function, but a JIT runtime reads it the moment
1404
+ the package is imported, so every spec that imported anything from `@zambon-dev/framework`
1405
+ failed before running a test. The query is a forward reference now. Nothing to change in
1406
+ applications, and specs excluded for this reason can be put back.
1407
+
1408
+ #### [1.4.6](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.6) - 2026-09-24
1409
+
1410
+ ##### Fixed
1411
+
1412
+ - **A confirm or error modal no longer adds a gap below the page.** Both render nothing but a
1413
+ fixed-position modal, yet their host still counted as a child in the parent's layout, so a flex
1414
+ column with a gap reserved a slot for it under the last visible element -- which is why a detail
1415
+ view and some list views sat further from the bottom of the window than the rest.
1416
+
1417
+ #### [1.4.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.5) - 2026-09-22
1418
+
1419
+ ##### Fixed
1420
+
1421
+ - **A filtered view no longer loses its Filter and Clear filters state when another details tab is
1422
+ opened.** The grid stayed filtered while the ribbon came back as though nothing were filtered, so
1423
+ the only way out was to filter again. Opening a list view never did it -- which is what made the
1424
+ behaviour look arbitrary.
1425
+
1426
+ Every details tab keeps its router subscription alive, including the ones the reuse strategy has
1427
+ detached, and the lookup that finds the active details route reads the router, which names
1428
+ whichever tab the navigation went to. A detached tab therefore adopted the incoming tab's base
1429
+ path and switched itself to that tab's view, tearing its own ribbon down and building a fresh
1430
+ one -- and the new buttons knew nothing of the filter still held by the grid. A list view is not
1431
+ a details route, so the lookup found nothing and the tab was left alone.
1432
+
1433
+ A tab now answers only to navigations matching its own route definition, which still covers the
1434
+ one navigation that legitimately changes its path: the redirect from `/new` to `/:id` after a
1435
+ save.
1436
+
1437
+ #### [1.4.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.4) - 2026-09-22
1438
+
1439
+ ##### Fixed
1440
+
1441
+ - **Switching views in a details tab no longer moves the buttons to the wrong view.** Opening a
1442
+ second view showed the first view's buttons instead of its own, and going back to the first view
1443
+ left it with no buttons at all -- on the employee screen, Histórico showed the Detalhes ribbon and
1444
+ Detalhes then showed an empty one.
1445
+
1446
+ 1.4.1 taught a tab to claim a ribbon published before its view had been named, which is what a tab
1447
+ opened straight at a non-default view needs. It claimed on every naming, though, and the empty id
1448
+ is not a spare bucket: it is the default view's own id, since that view is the one whose URL
1449
+ carries no sub-path. From the second switch onwards the template being claimed belonged to the
1450
+ view being left. Only the first naming may claim it now.
1451
+
1452
+ #### [1.4.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.3) - 2026-09-21
1453
+
1454
+ ##### Fixed
1455
+
1456
+ - **Host styles now apply.** `DefaultTabViewComponent`, `DefaultDetailsTabViewComponent`,
1457
+ `TabBreadcrumbsComponent` and `ButtonFiltersComponent` declared their host block as
1458
+ `:host-context {` with no argument, so every rule inside was silently dropped. Visible effects:
1459
+ no gap between the ribbon and the view below it, and breadcrumbs with neither their bottom rule
1460
+ nor their spacing. They are `:host {` now.
1461
+
1462
+ - `ButtonFiltersComponent.validateFormFunction` was typed `Function`, which accepted any
1463
+ function-like value. It is now `() => void`, matching how it is actually invoked. Consumers
1464
+ passing a function that takes arguments or returns a value will now see a type error.
1465
+ - Removed empty constructors from `ConfirmModalComponent` and `TabViewService`.
1466
+
1467
+ - `DefaultDetailsTabViewComponent` imported `ButtonViewsComponent` through the `buttons` barrel,
1468
+ which re-exports `./legacy`, whose buttons import the `views` barrel, which re-exports this
1469
+ component. The cycle left one of the component's own imports undefined at evaluation time and
1470
+ broke any attempt to instantiate it outside a fully booted application. It now imports the
1471
+ button by its concrete path.
1472
+
1473
+ #### [1.4.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.2) - 2026-09-20
1474
+
1475
+ ##### Fixed
1476
+
1477
+ - Declared `rxjs` in `peerDependencies`; it is used throughout the package but was never declared.
1478
+ - Added a README to the published package.
1479
+
1480
+ ##### ⚠ Breaking Changes / Migration
1481
+
1482
+ **Requires Angular 22.** Every `@angular/*` peer range moved from `^19.1.0` to `^22.0.0`, and
1483
+ the package is built and tested against Angular 22.1 with TypeScript 6.0.
1484
+
1485
+ Upgrade your application to Angular 22 first — Angular does not support skipping majors, so go
1486
+ 19 → 20 → 21 → 22 with `ng update`. Angular 22 also requires Node.js 22.22.3 or newer.
1487
+
1488
+ **Requires `@zambon-dev/library` 2.x.** The peer range moved from `^1.6.0` to `^2.0.0`. See the
1489
+ library changelog for the `FormInputComponent` output renames; if your application binds those
1490
+ outputs directly, apply that migration too.
1491
+
1492
+ #### [1.4.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.1) - 2026-09-19
1493
+
1494
+ ##### Fixed
1495
+
1496
+ - **A details tab with child views no longer loses its ribbon after you visit another tab and come
1497
+ back.** Every button of the child view disappeared -- filter, clear filters, refresh, export --
1498
+ while the grid below carried on showing its filtered rows, and nothing brought them back short of
1499
+ switching views.
1500
+
1501
+ A child view publishes its ribbon from `ngAfterViewInit`, and the router only names the active
1502
+ view afterwards, so the first template of a tab was cached under an empty id. Looking it up later
1503
+ under the real name missed and emptied the ribbon. It never recovered because a tab being
1504
+ re-activated has its child **re-attached** rather than re-created, so `ngAfterViewInit` does not
1505
+ run again and nothing publishes a second time.
1506
+
1507
+ A list screen was unaffected: it has no view switching, so the lookup never happened.
1508
+
1509
+ #### [1.4.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.0) - 2026-09-16
1510
+
1511
+ ##### Changed
1512
+
1513
+ - **`framework-button-filters` no longer submits the labels of catalog selections.** A filters
1514
+ form reads its values straight off the form, so the display control `lib-catalog-select` adds
1515
+ for its own use went to the backend beside the real filters — a request filtering by employee
1516
+ carried `employeeName: "753 - ADEMILSON LOPES MAGALHAES"` next to `employeeID: 125`.
1517
+
1518
+ They were ignored, being filters no service declares, but the day one does filter by a name it
1519
+ would receive the formatted label rather than the stored value and quietly match nothing.
1520
+
1521
+ The labels are still **kept** for the modal: reopening it patches them back into the form, and a
1522
+ catalog select backed by a `searchEndpoint` cannot recover its text from the identifier alone —
1523
+ it only resolves a display out of a local entries list. Dropping them from what is stored would
1524
+ have left the field showing a selection with nothing written in it.
1525
+
1526
+ ##### ⚠ Breaking Changes / Migration
1527
+
1528
+ - **Upgrade `@zambon-dev/library` together with this release.** The peer range moved from
1529
+ `^1.0.0` to `^1.6.0`, because `framework-button-filters` now imports `DisplayControls` from it.
1530
+ On an older library that export does not exist, and the filters button fails — at build time if
1531
+ the bundler checks exports, otherwise the first time a filter is submitted. The old range would
1532
+ have let npm resolve that combination without a word of warning.
1533
+
1534
+ - **A backend no longer receives the label of a catalog selection.** If one of your services
1535
+ filters by a key that a `lib-catalog-select` uses as its `displayControlName` — the `xxxName`
1536
+ that comes paired with an `xxxID` — that filter now arrives empty and the query stops narrowing,
1537
+ silently returning more rows than before rather than failing.
1538
+
1539
+ To check, take each filters form and, for every `lib-catalog-select` in it, note the
1540
+ `displayControlName`. Then look for a `TryFilter` on that name in the service behind the list.
1541
+ Any hit has to move to the identifier instead: the label was never the stored value — it is what
1542
+ the catalog chose to display, so filtering by it was already matching on formatting.
1543
+
1544
+ #### [1.3.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.3.1) - 2026-09-10
1545
+
1546
+ ##### Fixed
1547
+
1548
+ - **Returning to an already-open tab no longer crashes with `Maximum call stack size exceeded`.**
1549
+ It affected any screen whose route nests two empty-path levels — the shape every list screen uses:
1550
+ `path: ''` with `DefaultTabViewComponent`, and a child `path: ''` with the list component.
1551
+
1552
+ `CustomReuseStrategy` keyed its detached-view cache on `route.component.name`, and a production
1553
+ build renames every class: esbuild wraps each component as `X = (() => { class i { } return i; })()`,
1554
+ so the name is one mangled letter that the whole chunk shares. An empty path contributes no URL
1555
+ segment, so both levels also resolved to the same URL — which left the two cache keys identical.
1556
+ Storing one handle overwrote the other, both route levels were then handed the same detached view,
1557
+ and Angular blew the stack building a router state whose node was its own descendant.
1558
+
1559
+ The key is now built from the component's **identity** and the route's depth rather than its name.
1560
+ Applications need to change nothing. Worth knowing when reading old reports of this crash: it only
1561
+ ever reproduced in a minified build — `ng serve` keeps real class names — and only on the screens
1562
+ whose chunk happened to mangle to the same letter as the framework's, so a rebuild could move the
1563
+ symptom from one screen to another.
1564
+
1565
+ #### [1.3.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.3.0) - 2026-07-30
1566
+
1567
+ ##### Added
1568
+
1569
+ - `framework-button-export` now accepts a **`defaultOption` `@Input()`** — the index into `options`
1570
+ exported when the main button itself is clicked, defaulting to `0` (Excel). This mirrors the input
1571
+ `framework-button-save` already had; `-1` means "no default" and makes the button only toggle its
1572
+ dropdown. See **Changed** below for the behaviour this changes by default.
1573
+
1574
+ ##### Changed
1575
+
1576
+ - **`framework-button-export` now exports on a single click**, to Excel by default, instead of only
1577
+ opening its format menu. `RibbonButtonComponent` treats a `defaultOption` of -1 as "no default" and
1578
+ merely toggles the dropdown; Export never set one, so the common case took two clicks while
1579
+ `framework-button-save` (which sets 0) took one. Export now sets 0 as well, through the new
1580
+ `defaultOption` `@Input()`. The format menu is unchanged and still reachable from the button's
1581
+ dropdown caret, so every format remains available.
1582
+
1583
+ ##### Fixed
1584
+
1585
+ - **Routed list views now fill the available height automatically.** `DefaultTabViewComponent` gave its
1586
+ routed child no layout and `lib-data-grid` has no `flex-grow` of its own, so a grid collapsed to its
1587
+ `rowsToDisplay × rowHeight` minimum with empty space beneath it, and every consuming list component
1588
+ had to repeat the same `:host { flex; flex-grow; overflow: hidden }` + `lib-data-grid { flex-grow }`
1589
+ stylesheet to compensate. `TabViewList` now carries a `framework-view-list` host class — inherited by
1590
+ every subclass — which `DefaultTabViewComponent` styles, so **consuming apps can delete those
1591
+ per-component stylesheets**. Routed screens that do not extend `TabViewList` (forms, dashboards) are
1592
+ unaffected.
1593
+
1594
+ - `framework-default-tab-view` and `framework-default-details-tab-view` no longer log
1595
+ `NG0100: ExpressionChangedAfterItHasBeenCheckedError` on every render in development mode.
1596
+ Both views assigned a placeholder ribbon template from `ngAfterViewInit` — after Angular had
1597
+ already checked the ribbon's `*ngTemplateOutlet` binding — which tripped the dev-mode change
1598
+ detection check one to three times per load. The placeholder rendered no content, so it has
1599
+ been removed and the outlet is now left empty until a view publishes its own `#ribbon`
1600
+ template. The rendered output is unchanged, and ribbon buttons still appear immediately for
1601
+ list and detail screens.
1602
+
1603
+ ##### ⚠ Breaking Changes / Migration
1604
+
1605
+ None — nothing has to change for your app to compile against this release. Two behaviour changes to
1606
+ check, and one optional cleanup:
1607
+
1608
+ - **Absolutely-positioned content inside a routed list view is now clipped.** The full-height layout
1609
+ applied to `framework-view-list` includes `overflow: hidden`, so a popover, dropdown or tooltip that
1610
+ a list screen renders with `position: absolute` inside its own host is now cut off at the host's
1611
+ edges. Apps that had already written the equivalent `:host { overflow: hidden }` stylesheet by hand
1612
+ are unaffected — the clipping was already in place there. Only list screens that had no such
1613
+ stylesheet change. Fix by portaling the content out of the host (CDK Overlay, as `lib-catalog-select`
1614
+ does) rather than relying on `position: absolute`.
1615
+ - If you relied on `framework-button-export`'s first click opening the format menu rather than
1616
+ exporting, set `[defaultOption]="-1"` to restore that behaviour.
1617
+ - _Optional cleanup:_ components extending `TabViewList` can now delete their
1618
+ `:host { display: flex; flex-grow: 1; overflow: hidden }` / `lib-data-grid { flex-grow: 1 }`
1619
+ stylesheets — `DefaultTabViewComponent` styles the inherited `framework-view-list` host class.
1620
+
1621
+ #### [1.2.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.2.1) - 2026-07-29
1622
+
1623
+ ##### Fixed
1624
+
1625
+ - Lint configuration only: the library's ESLint selector rules now accept the `framework-` prefix
1626
+ that every `@framework` component already uses. They previously still expected the Angular
1627
+ scaffold's `lib` prefix, so the entire component surface reported
1628
+ `@angular-eslint/component-selector` errors. No selector was renamed, and no published API or
1629
+ runtime behavior changed — nothing to do on upgrade.
1630
+
1631
+ ##### ⚠ Breaking Changes / Migration
1632
+
1633
+ None.
1634
+
1635
+ #### [1.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.2.0) - 2026-07-24
1636
+
1637
+ ##### Added
1638
+
1639
+ - `AppConfig` now accepts optional application metadata through a second `options` argument:
1640
+ `appName`, `companyName`, `environment`, `logoUrl`, `version`, `notificationsEnabled`, and
1641
+ `notificationsUrl`. These are consumed by the shared application shell (top bar + navigation).
1642
+ Existing `new AppConfig(baseUrl)` calls continue to work unchanged (`notificationsEnabled`
1643
+ defaults to `false`, other new fields to empty strings).
1644
+
1645
+ ##### ⚠ Breaking Changes / Migration
1646
+
1647
+ None.
1648
+
1649
+ #### [1.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.1.1) - 2026-06-20
1650
+
1651
+ ##### Fixed
1652
+
1653
+ - `framework-button-export` now forwards its `iconSize` input to the underlying ribbon button,
1654
+ so setting `iconSize` correctly resizes the export button's icon. Previously the input was
1655
+ ignored and the icon always rendered at the default size.
1656
+
1657
+ #### [1.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.1.0) - 2026-06-02
1658
+
1659
+ - Baseline release: the changelog starts being tracked from this version. Earlier history is
1660
+ available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
1661
+ `framework-v*` tags.
1662
+
1663
+ ### Shared
1664
+
1665
+ Authentication, layouts, audit-history features and cross-cutting services. Published as `@zambon-dev/shared` through 6.0.4.
1666
+
1667
+ #### [6.0.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v6.0.4) - 2026-10-24
1668
+
1669
+ - Maintenance release (no consumer-facing changes were documented).
1670
+
1671
+ #### [6.0.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v6.0.3) - 2026-10-24
1672
+
1673
+ ##### Changed
1674
+
1675
+ - **Every component in this package now renders under any change-detection strategy the host
1676
+ application chooses, zoneless included.** This was the last package. The state each one shows
1677
+ is signals: the embedded-content screen, which decides everything after its menu lookup
1678
+ answers; the notifications panel and the language list, both closed from a window click that
1679
+ is no binding of theirs; the user profile, filled on init; the login form's error, spinner and
1680
+ success state; and the audit screens' selected service and operation.
1681
+
1682
+ ##### ⚠ Breaking Changes / Migration
1683
+
1684
+ - **The state these components expose is signals.** Binding to an input from a template is
1685
+ unchanged. Code that reads one needs `()`:
1686
+
1687
+ | Component | Members |
1688
+ | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
1689
+ | `ServicesHistoryViewComponent` | `controllerName`, `entityID`, `usersCatalogEndpoint` -- `model()`, so setting one still works through `.set(...)` |
1690
+ | `ServicesHistoryChildListComponent` | `selectedServiceID` |
1691
+ | `OperationsHistoryChildListComponent` | `selectedNewValues`, `selectedOldValues` |
1692
+ | `OperationsHistoryModalComponent` | `newValues`, `oldValues` |
1693
+ | `NotificationsComponent` | `dropdown` |
1694
+ | `LanguageSelectorComponent` | `dropdown`, `showFlag`, `title` |
1695
+ | `LoginComponent` | `username` |
1696
+
1697
+ A template reading one through a reference -- `#list` then `list.selectedServiceID` -- needs
1698
+ the call; without it the signal object itself is read, and an object is always truthy.
1699
+
1700
+ - **`ServicesHistoryViewComponent.controllerName` is no longer a required input.** It is a
1701
+ `model()` defaulting to `''`, because the component also fills it from the route's
1702
+ `controllerName` data -- where nothing binds it and `required` never applied. Routed usage is
1703
+ unchanged; a template that binds it is unchanged; a template that forgot to is no longer a
1704
+ compile error, so check yours.
1705
+
1706
+ - **`TopBarComponent.logout` and `ServicesHistoryChildListComponent.filtersChanged` are
1707
+ `output()` rather than `EventEmitter`.** Binding to them is unchanged; calling `.emit()` on
1708
+ one from outside the component no longer compiles, which was never supported anyway.
1709
+
1710
+ #### [6.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v6.0.2) - 2026-10-24
1711
+
1712
+ ##### Changed
1713
+
1714
+ - **Internal only: one spec follows `ViewBase`'s `loading` onto a signal.** `@zambon-dev/framework`
1715
+ backs that flag with a signal now, and a spec here stubs the component with `Object.create`,
1716
+ which runs no field initialiser. Nothing in this package's own API changed.
1717
+
1718
+ #### [6.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v6.0.1) - 2026-10-24
1719
+
1720
+ ##### Fixed
1721
+
1722
+ - **The peer range on its sibling packages now admits the majors they were released at.** The last
1723
+ release published `@zambon-dev/library` 4.0.0, `@zambon-dev/framework` 5.0.0 and
1724
+ `@zambon-dev/shared` 6.0.0, but the ranges here still asked for the previous majors, so an
1725
+ application installing two of them together got `ERESOLVE`. Nothing else changed.
1726
+
1727
+ ##### ⚠ Breaking Changes / Migration
1728
+
1729
+ - **`OperationsHistoryModalComponent.isModalShown` is a `Signal<boolean>`.** It followed
1730
+ `@zambon-dev/library`'s `ModalComponent.isShown` becoming `isOpen`, and delegates through a
1731
+ signal view query so it answers correctly once that child resolves. Read it as
1732
+ `isModalShown()`. The compiler will not catch a miss here -- reading the signal object itself is
1733
+ valid TypeScript and always truthy -- so grep for `isModalShown` rather than relying on the
1734
+ build.
1735
+
1736
+ #### [6.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v6.0.0) - 2026-10-24
1737
+
1738
+ ##### Changed
1739
+
1740
+ - **`FiltersBase` no longer declares a change-detection strategy**, for the same reason as the
1741
+ framework's view bases: it was never inherited. Your filter components are unaffected.
1742
+
1743
+ ##### Fixed
1744
+
1745
+ - **The services-history author filter appears when its catalog is supplied.** The template read
1746
+ the endpoint through a getter over a plain `@Input()`, which under Angular 22's default strategy
1747
+ raised NG0100 rather than rendering.
1748
+
1749
+ ##### ⚠ Breaking Changes / Migration
1750
+
1751
+ - **`ServicesHistoryFilterComponent.usersCatalogEndpoint` is a signal input.** Binding it from a
1752
+ template -- `[usersCatalogEndpoint]="endpoint"` -- is unchanged. Assigning it directly on a
1753
+ component instance no longer works; use the binding, or `componentRef.setInput` in a test.
1754
+
1755
+ #### [5.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v5.0.2) - 2026-09-30
1756
+
1757
+ ##### Changed
1758
+
1759
+ - `UtcDatePipe.transform()` accepts `Date | string`. An API response holds the timestamp as a
1760
+ string even where the model types it as `Date`; existing calls compile unchanged.
1761
+
1762
+ ##### Fixed
1763
+
1764
+ - **`utcDate` no longer moves a timestamp to the previous day east of Greenwich.** It re-read the
1765
+ UTC wall-clock time with hour arithmetic that kept the time of day but, at any positive UTC
1766
+ offset, landed on the day before: `2026-06-15T14:30` showed as June 14 in Madrid (UTC+2). It
1767
+ never showed on CI, which runs in UTC, nor west of Greenwich, where the arithmetic happens to
1768
+ come out right. The pipe now shifts by the zone's offset.
1769
+
1770
+ - **A timestamp that already declares its zone is no longer shifted a second time.** A string
1771
+ ending in `Z` or `±hh:mm` is parsed as the right instant by `Date` itself, so the pipe now
1772
+ returns it as is, and keeps working if an API starts sending the marker.
1773
+
1774
+ - **A value that is not a date returns `null`**, the same as an empty one, instead of an invalid
1775
+ `Date` that makes Angular's `date` pipe throw.
1776
+
1777
+ #### [5.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v5.0.1) - 2026-09-26
1778
+
1779
+ ##### Fixed
1780
+
1781
+ - **The operations list no longer keeps a service the services list has dropped.** Reloading the
1782
+ services grid clears its selection but left the last chosen service behind, so the panel beside
1783
+ it went on showing that service’s operations -- including after a filter that no longer lists
1784
+ it. The selection is dropped with the reload now.
1785
+
1786
+ - **The audit date range no longer depends on the server’s time zone.** The two bounds were sent
1787
+ as instants with a trailing `Z`, and `DateTime.TryParse` reads that as an instant and answers
1788
+ with the _machine’s_ local time for it -- so the same request meant different things on a server
1789
+ in UTC and one in UTC-3, while the stored column is plain UTC either way. They are sent as the
1790
+ UTC wall clock now, with no designator.
1791
+
1792
+ - **The peer range on `@zambon-dev/framework` admits 4.x.** 5.0.0 shipped peering `^3.0.0`, hours
1793
+ after framework reached 4.0.0, so installing the two together failed with ERESOLVE. Nothing in
1794
+ this repository could see it -- the workspace resolves siblings from `dist`, so every build,
1795
+ test and Storybook passes with a range no consumer can satisfy. `tools/check-peer-ranges.mjs`
1796
+ now compares each range against the release tags, in PR validation.
1797
+
1798
+ #### [5.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v5.0.0) - 2026-09-26
1799
+
1800
+ ##### Added
1801
+
1802
+ - **Refresh and filters on the audit view.** `ServicesHistoryViewComponent` now contributes a
1803
+ ribbon, with a refresh button and `ServicesHistoryFilterComponent`: the action as text
1804
+ (matched with LIKE), the author, a date range, and whether to list only the operations on the
1805
+ record being audited. The backend side is `AuditFilters` in ZWebAPI; the author filter needs a
1806
+ catalog, which belongs to the application, so it appears only when the view's
1807
+ `usersCatalogEndpoint` is supplied — through the route's data or bound directly.
1808
+
1809
+ The two picked dates are widened to whole local days and sent as UTC instants, because the
1810
+ history stores UTC and the backend compares what it is given: sent as picked, "from the 5th to
1811
+ the 5th" is one instant at midnight and matches nothing.
1812
+
1813
+ ##### ⚠ Breaking Changes / Migration
1814
+
1815
+ - **`ServicesHistoryChildListComponent` no longer provides its own `DataGridDataset`.**
1816
+ `ServicesHistoryViewComponent` provides it instead, so that the ribbon's refresh and filter
1817
+ buttons — which resolve the dataset from their own injector — reach the same instance the grid
1818
+ reads. Nothing changes for the view, which is how this component is meant to be used. Rendering
1819
+ the child list anywhere else now needs the dataset provided above it:
1820
+
1821
+ ```ts
1822
+ providers: [{ provide: DataGridDataset, useClass: ServicesHistoryDataset }],
1823
+ ```
1824
+
1825
+ #### [4.2.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.2.2) - 2026-09-26
1826
+
1827
+ ##### Fixed
1828
+
1829
+ - **Component styles are compiled again.** The Tailwind 4 migration left every component's own
1830
+ stylesheet unprocessed in the published bundle, so its `@apply` rules reached the browser as an
1831
+ at-rule the browser does not know and it dropped every rule that used one. In practice the
1832
+ components rendered unstyled in any application installing 4.2.0 or 4.2.1. ng-packagr resolves its
1833
+ PostCSS configuration from the library directory rather than from the workspace root, and the
1834
+ migration added one only at the root. Nothing failed along the way: Storybook compiles from source
1835
+ and never sees the packaged artifact, which is why `tools/check-packaged-styles.mjs` now runs
1836
+ against that artifact in CI and again before every publish.
1837
+
1838
+ #### [4.2.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.2.1) - 2026-09-26
1839
+
1840
+ ##### Fixed
1841
+
1842
+ - **`styles/theme.css` is reachable from an application.** It shipped with the Tailwind 4 release
1843
+ but was missing from the `exports` map, so the one supported way to use it —
1844
+ `@reference '@zambon-dev/shared/styles/theme.css'` in a consumer stylesheet — failed the export
1845
+ check and the build stopped. Found on the first application to migrate.
1846
+
1847
+ #### [4.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.2.0) - 2026-09-25
1848
+
1849
+ ##### Changed
1850
+
1851
+ - **Tailwind CSS 4.** The stylesheets these packages ship are built with Tailwind 4 now. Nothing
1852
+ about the components changed; the framework underneath them did.
1853
+
1854
+ #### [4.1.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.1.3) - 2026-09-25
1855
+
1856
+ ##### Fixed
1857
+
1858
+ - **The tab panel's drop shadow is no longer sliced off.** The layout's `.content` was
1859
+ `overflow-y-auto`, which made it a clipping box on both axes, and what it clipped was the
1860
+ panel's shadow — 18px of shadow against the 8px it left below. It never needed to scroll:
1861
+ `framework-tabs` is full height and the panel scrolls inside it, measured as
1862
+ `scrollHeight === clientHeight` with the panel in place. Two nested scroll containers where one
1863
+ will do.
1864
+
1865
+ #### [4.1.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.1.2) - 2026-09-25
1866
+
1867
+ ##### Fixed
1868
+
1869
+ - **Icons drawn in CSS survive a FontAwesome major.** Four pseudo-elements set
1870
+ `font-family: 'Font Awesome 6 Free'` literally: the sidebar item's expand/collapse chevron and
1871
+ its external-link marker, and the checked-checkbox tick in `form-input` and in the published
1872
+ `styles/components/input.scss`. FontAwesome 7 renames the family to `Font Awesome 7 Free` and,
1873
+ unlike the FontAwesome 5 names, does not keep a 6 alias — so on an application that had moved to
1874
+ v7 the family stopped resolving and every one of those glyphs rendered as a fallback box.
1875
+
1876
+ They now read `var(--fa-family-classic, 'Font Awesome 6 Free')`. FontAwesome 7 defines that
1877
+ variable; 6 does not, so it takes the fallback. The same stylesheet is correct on both, and on
1878
+ 8 as well as long as FontAwesome keeps the variable.
1879
+
1880
+ #### [4.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.1.1) - 2026-09-25
1881
+
1882
+ ##### Fixed
1883
+
1884
+ - **The peer ranges on `@zambon-dev/library` and `@zambon-dev/framework` now match the versions
1885
+ this package is built against.** Both still asked for `^2.0.0` after those packages moved to 3.x,
1886
+ so installing the three together at their current releases failed `npm install` with ERESOLVE. An
1887
+ application could only get past it with `--legacy-peer-deps` or an override, both of which switch
1888
+ off the peer checking that would have caught a genuine mismatch.
1889
+
1890
+ **Requires Tailwind CSS 4.** An application on 3.x cannot consume this release: `common.scss`
1891
+ references a theme in v4 syntax, and the two versions do not understand each other's directives.
1892
+
1893
+ Tailwind 4 has no JavaScript config — the theme is CSS custom properties — so the `primary` palette
1894
+ that consumers used to declare in their own `tailwind.config.js` now ships here, in
1895
+ `@zambon-dev/shared/styles/theme.css`. That file is the Tailwind entry point and the theme in one.
1896
+
1897
+ In the application:
1898
+
1899
+ 1. Install `tailwindcss@^4` and `@tailwindcss/postcss@^4`, and remove `tailwind.config.js`. A
1900
+ `.postcssrc.json` with `{"plugins": {"@tailwindcss/postcss": {}}}` replaces it.
1901
+
1902
+ 2. Load the theme **before** the shared stylesheet, and load it as CSS rather than through Sass.
1903
+ Sass's `@use` of a plain CSS file drops the `@layer` blocks the theme relies on, silently:
1904
+
1905
+ ```jsonc
1906
+ // angular.json / project.json, styles:
1907
+ "node_modules/@zambon-dev/shared/styles/theme.css",
1908
+ "node_modules/@zambon-dev/shared/styles/common.scss",
1909
+ ```
1910
+
1911
+ 3. Any stylesheet of your own that uses `@apply` needs to say where its theme comes from:
1912
+ `@reference '@zambon-dev/shared/styles/theme.css';`. In 3.x the PostCSS plugin carried the
1913
+ config to every file; in 4 a file is on its own without this.
1914
+
1915
+ 4. If you kept a custom palette in `tailwind.config.js`, move it into a `@theme` block. Colours
1916
+ become `--color-<name>-<shade>`.
1917
+
1918
+ `theme.css` also restores two v3 defaults that Tailwind 4 dropped, because the components were
1919
+ built against them: the default border colour (`gray-200`, not `currentColor`) and the pointer
1920
+ cursor on buttons. Both are marked in the file. Taking either away is a visual decision, not a
1921
+ cleanup.
1922
+
1923
+ **What was verified, and what was not.** `tools/style-snapshot` captures the computed styles of
1924
+ every element in all 43 stories and diffs two builds; the migration was driven by that diff rather
1925
+ than by reading release notes, and it caught three regressions nothing else reported — 615 elements
1926
+ whose borders turned black, 41 buttons that lost their pointer cursor, and a sidebar that rendered
1927
+ collapsed because `@screen md` is a v3 directive that 4 leaves as an unknown at-rule for the browser
1928
+ to drop.
1929
+
1930
+ What remains is 138 differing values in 20 distinct changes, all triaged and none of them obviously
1931
+ wrong: `divide-*` moved its border from the top of one element to the bottom of the previous one,
1932
+ which is the documented v4 selector change; a handful of rings and shadows compose their transparent
1933
+ layers differently; and three elements are 8px shorter. **These want an eye on them before release.**
1934
+ Colour values across the board are the same colours in OKLCH, which is what v4's palette is.
1935
+
1936
+ #### [4.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.1.0) - 2026-09-21
1937
+
1938
+ ##### Added
1939
+
1940
+ - Added the `--tabs-*` design tokens to the published stylesheet, mirroring the set
1941
+ `@zambon-dev/library` gained in 3.1.0. They style `@zambon-dev/framework`'s tab strip —
1942
+ colours, radii, blur, the shadow, the minimum and maximum tab width, and the width of the
1943
+ scroll controls and edge fades — and they sit alongside `--sidebar-*` in the same layer, so
1944
+ the strip can be retuned without touching the component.
1945
+
1946
+ This release shipped them but originally recorded no entry; the note was added afterwards.
1947
+
1948
+ #### [4.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.0.1) - 2026-09-21
1949
+
1950
+ ##### Fixed
1951
+
1952
+ - **Host styles now apply.** The services-history view and both history child lists declared their
1953
+ host block as `:host-context {` with no argument, so every rule inside was silently dropped.
1954
+ They are `:host {` now.
1955
+
1956
+ #### [4.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.0.0) - 2026-09-21
1957
+
1958
+ - Maintenance release (no consumer-facing changes were documented).
1959
+
1960
+ #### [3.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v3.0.1) - 2026-09-21
1961
+
1962
+ ##### Changed
1963
+
1964
+ - `LoginLayoutComponent`, `HomeComponent`, `BrandComponent` and `EnvironmentBadgeComponent` now
1965
+ use `ChangeDetectionStrategy.OnPush`. Behaviour is unchanged; they render from inputs or from
1966
+ immutable application config.
1967
+
1968
+ - **Accessibility.** The operations-history row opens its detail modal from the keyboard as well as
1969
+ the mouse.
1970
+
1971
+ #### [3.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v3.0.0) - 2026-09-21
1972
+
1973
+ ##### Changed
1974
+
1975
+ - **Accessibility.** The operations-history row opens its detail modal from the keyboard as well as
1976
+ the mouse.
1977
+
1978
+ > **The 2.1.x line is retired.** It carried shared for applications still on Angular 19 while
1979
+ > `main` moved to Angular 22, publishing under its own `2.1.x` dist-tag. Its branch is gone; the
1980
+ > final state is kept in the `angular-19/shared-2.1.x` tag. Every fix recorded below also shipped
1981
+ > in a 5.x release, which requires Angular 22.
1982
+
1983
+ #### [2.1.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.4) - 2026-09-30
1984
+
1985
+ ##### Changed
1986
+
1987
+ - `UtcDatePipe.transform()` accepts `Date | string`. An API response holds the timestamp as a
1988
+ string even where the model types it as `Date`; existing calls compile unchanged.
1989
+
1990
+ ##### Fixed
1991
+
1992
+ - **`utcDate` no longer moves a timestamp to the previous day east of Greenwich.** It re-read the
1993
+ UTC wall-clock time with hour arithmetic that kept the time of day but, at any positive UTC
1994
+ offset, landed on the day before: `2026-06-15T14:30` showed as June 14 in Madrid (UTC+2). It
1995
+ never showed on CI, which runs in UTC, nor west of Greenwich, where the arithmetic happens to
1996
+ come out right. The pipe now shifts by the zone's offset.
1997
+
1998
+ - **A timestamp that already declares its zone is no longer shifted a second time.** A string
1999
+ ending in `Z` or `±hh:mm` is parsed as the right instant by `Date` itself, so the pipe now
2000
+ returns it as is, and keeps working if an API starts sending the marker.
2001
+
2002
+ - **A value that is not a date returns `null`**, the same as an empty one, instead of an invalid
2003
+ `Date` that makes Angular's `date` pipe throw.
2004
+
2005
+ #### [2.1.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.3) - 2026-09-21
2006
+
2007
+ - Maintenance release (no consumer-facing changes were documented).
2008
+
2009
+ #### [2.1.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.2) - 2026-09-20
2010
+
2011
+ ##### Fixed
2012
+
2013
+ - Declared `rxjs` and `@angular/platform-browser` in `peerDependencies`. `DomSanitizer` is used by
2014
+ `ExternalContentComponent` and `BypassHtmlSanitizerPipe`, but neither package was declared.
2015
+ - Added a README to the published package.
2016
+
2017
+ ##### ⚠ Breaking Changes / Migration
2018
+
2019
+ **Requires Angular 22.** Every `@angular/*` peer range moved from `^19.1.0` to `^22.0.0`, and
2020
+ the package is built and tested against Angular 22.1 with TypeScript 6.0.
2021
+
2022
+ Upgrade your application to Angular 22 first — Angular does not support skipping majors, so go
2023
+ 19 → 20 → 21 → 22 with `ng update`. Angular 22 also requires Node.js 22.22.3 or newer.
2024
+
2025
+ **Requires `@zambon-dev/library` 2.x and `@zambon-dev/framework` 2.x.** Both peer ranges moved to
2026
+ `^2.0.0`. See the library changelog for the `FormInputComponent` output renames.
2027
+
2028
+ #### [2.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.1) - 2026-09-15
2029
+
2030
+ ##### Fixed
2031
+
2032
+ - **An embedded destination served over `http` now says why it cannot be shown, instead of
2033
+ rendering an empty frame.** A browser refuses to embed an `http://` frame inside an `https://`
2034
+ page — an iframe is active mixed content, and the block is unconditional. Nothing on the page
2035
+ can permit it: no attribute, no header, and not CSP, which only ever restricts further. The
2036
+ frame was mounted anyway, the browser dropped it silently, and after the timeout the screen
2037
+ offered the “still loading” hint, which pointed at the wrong cause.
2038
+
2039
+ Unlike a site refusing to be framed — which is genuinely unknowable from JavaScript — this one
2040
+ is decidable up front, so `ExternalContentComponent` now compares the page’s protocol with the
2041
+ destination’s and shows a message naming the real reason. **Open in a new browser tab keeps
2042
+ working and is the way out:** a top-level navigation to `http://` is not mixed content. For a
2043
+ destination that only speaks `http`, configure the menu item as _external, new tab_ rather than
2044
+ _external, embedded_; to embed it, put it behind an `https` reverse proxy.
2045
+
2046
+ Two new keys, `ExternalContent-Insecure-Title` and `ExternalContent-Insecure-Message`, ship in
2047
+ `en` and `pt`. An application that overrides this feature’s translations needs to add them.
2048
+
2049
+ #### [2.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.0) - 2026-09-08
2050
+
2051
+ ##### Added
2052
+
2053
+ - **External sidebar destinations are now opened, in either of two modes.** `MainLayoutComponent`
2054
+ subscribes to `@zambon-dev/library`’s new `SidebarService.menuExternalUrlSelected`: an item whose
2055
+ `openMode` is `ExternalNewTab` opens in a new browser tab (`noopener,noreferrer`), and one marked
2056
+ `ExternalEmbedded` opens an application tab that displays the destination in a sandboxed iframe,
2057
+ keeping the user inside the application. Internal routes are untouched.
2058
+
2059
+ - **`ExternalUrlResolverService`** — substitutes the runtime placeholders in an external menu URL
2060
+ and vets the result. The placeholder set is closed and case-sensitive: `{email}`, `{language}`,
2061
+ `{userId}`, `{userName}`. Every substituted value is `encodeURIComponent`-ed, so a name containing
2062
+ `&` cannot inject a query parameter — which also means one placeholder must occupy one whole path
2063
+ segment or query-parameter value. A supported placeholder with no value becomes an empty string
2064
+ (with a console warning); an unrecognized `{…}` is left exactly as configured, so a report URL
2065
+ that legitimately contains braces, such as `?filter={"a":1}`, is not corrupted.
2066
+
2067
+ No authentication token is ever substituted, and the set is closed by construction rather than
2068
+ reflected off the stored user info — `AuthenticationService` persists the whole sign-in response
2069
+ under `userInfo`, tokens included, so a reflective implementation would let a URL configured as
2070
+ `?t={token}` hand the JWT to a third party.
2071
+
2072
+ `{language}` and `{userName}` work today. `{userId}` and `{email}` resolve to an empty string
2073
+ until your `Authentication/SignIn` and `Authentication/RefreshToken` responses include `userID`
2074
+ and `email` (see `ICurrentUserInfo` below).
2075
+
2076
+ - **`ExternalContentComponent` + `externalContentRoutes`** — the embedded view. Spread the routes
2077
+ into `MainLayoutComponent`’s children:
2078
+
2079
+ ```ts
2080
+ { path: '', component: MainLayoutComponent, canActivate: [AuthGuard], children: [
2081
+ ...externalContentRoutes,
2082
+ // your own features
2083
+ ] }
2084
+ ```
2085
+
2086
+ Register them even if you only plan to use `ExternalNewTab`: an embedded item configured without
2087
+ them opens a tab that immediately bounces to the home route.
2088
+
2089
+ The view is an ordinary `TabViewBase` hosted by `DefaultTabViewComponent`, so its actions appear
2090
+ in the application ribbon (a **Page** group with **Refresh** and **Open in a new browser tab**)
2091
+ and look like every other screen’s — the shipped `externalContentRoutes` already wires that host
2092
+ up, which is the other reason not to hand-write the routes. **Refresh** carries the same icon and
2093
+ label as `framework-button-refresh`, because reloading a report is the same action as refreshing
2094
+ a grid.
2095
+
2096
+ Refreshing genuinely tears the frame down and builds a new one — a cross-origin frame cannot be
2097
+ navigated any other way — so the old render visibly goes away instead of sitting there while you
2098
+ wonder whether anything happened. The button spins and the panel shows a loading overlay until
2099
+ the destination reports `load`, or until the slow-frame delay elapses, so a destination that
2100
+ never reports one cannot leave the controls stuck.
2101
+
2102
+ **Open in a new browser tab** is always available, because many sites refuse to be embedded
2103
+ (`X-Frame-Options`, CSP `frame-ancestors`) and a browser gives JavaScript no reliable way to
2104
+ detect that — if nothing has loaded after a few seconds the view also shows a hint saying so. An
2105
+ `https` application cannot embed an `http` destination at all; the same button is the way out.
2106
+
2107
+ The tab URL is `/external-content/<menu id>` and never carries the destination, so no one can
2108
+ hand-craft a link that makes your application frame an arbitrary site. Pressing F5 on an embedded
2109
+ tab restores both the frame and the tab title from `sessionStorage`. Opening that URL in a _fresh_
2110
+ browser tab can only work if your `SidebarService.getMenuFromUrl()` resolves
2111
+ `/external-content/<id>`; otherwise the view says the content is unavailable and asks the user to
2112
+ reopen it from the menu.
2113
+
2114
+ - **`EXTERNAL_CONTENT_CONFIGS`** — `allowedOrigins` (default `[]`, meaning any `http`/`https`
2115
+ origin) and `slowFrameHintDelay` (default 5000 ms). **Populate `allowedOrigins` in production.**
2116
+ Embedded and new-tab destinations are already rejected unless they are absolute `http`/`https`
2117
+ URLs, which stops a `javascript:` or `data:` URL in your menu table from executing in your users’
2118
+ session; an origin allowlist narrows what is left from “any site on the internet” to your known
2119
+ report hosts. The iframe is sandboxed with a fixed, non-configurable token list that withholds
2120
+ `allow-top-navigation`, so a framed site cannot navigate your application away. Do not point an
2121
+ embedded item at your own application’s origin — use an internal route for that.
2122
+
2123
+ - **`ICurrentUserInfo.email`, `.userID`, `.username`** (all optional) — the values behind
2124
+ `{email}`, `{userId}` and `{userName}`. `username` was already being persisted in the base64
2125
+ `userInfo` entry and is now simply typed, so `{userName}` needs no backend change; `email` and
2126
+ `userID` must be added to your sign-in and refresh responses.
2127
+
2128
+ - **Translations** for the embedded view under `i18n/external-content/`, already included in
2129
+ `ZAMBON_SHARED_I18N_RESOURCES`.
2130
+
2131
+ ##### Fixed
2132
+
2133
+ - **A deep link whose URL the menu API cannot resolve no longer surfaces an unhandled error.**
2134
+ `MainLayoutComponent` resolves a deep-linked tab’s title through `SidebarService.getMenuFromUrl()`
2135
+ and had no error handler, so a 404 became an unhandled rejection in the console. The title is
2136
+ best-effort and the failure is now swallowed — which matters more now that the embedded-content
2137
+ route is a URL shape most menu endpoints do not know.
2138
+
2139
+ ##### ⚠ Breaking Changes / Migration
2140
+
2141
+ - **Requires `@zambon-dev/library` 1.4.0 or later.** `SidebarMenuOpenMode`,
2142
+ `toSidebarMenuOpenMode` and `SidebarService.menuExternalUrlSelected` ship in that release. The
2143
+ declared peer range still allows older versions, so upgrade both packages together.
2144
+ - **To use external menu items, register `externalContentRoutes`** as children of
2145
+ `MainLayoutComponent` (snippet above), and make your menu endpoint return `openMode`. The
2146
+ resolver accepts it as the camelCase string (`"internal"`, `"externalNewTab"`,
2147
+ `"externalEmbedded"`, matched case-insensitively) or as the enum ordinal (`0`, `1`, `2`), so a
2148
+ plain ASP.NET Core enum property works with no converter.
2149
+ - **The embedded view needs both i18n bundles registered.** Its own strings ship in this package
2150
+ (`ZAMBON_SHARED_I18N_RESOURCES`, under `assets/i18n/zambon-dev/shared/external-content/`), but the
2151
+ ribbon reuses three keys owned by `@zambon-dev/framework` — `RibbonGroup-Page`, `Button-Refresh`
2152
+ and `Loading` — so `ZAMBON_FRAMEWORK_I18N_RESOURCES` has to be registered too, or those render as
2153
+ raw keys. Any application that already uses framework buttons registers it; if your
2154
+ `TranslateLoader` hand-lists prefixes instead of spreading the two constants, add both.
2155
+ - Nothing else changes: applications with no `openMode` on any menu item behave exactly as before
2156
+ and need no action.
2157
+
2158
+ #### [2.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.0.0) - 2026-07-30
2159
+
2160
+ ##### Added
2161
+
2162
+ - **Storybook: `Shared/App Showcase` story** — a full-height, navigable demo of the complete
2163
+ application shell, backed by in-memory mock data. Clicking the sidebar entries (Dashboard,
2164
+ General ▸ Customers/Units, Security ▸ Users) opens tabs that render working list-views and
2165
+ detail-views through the real hosts (`DefaultTabViewComponent` /
2166
+ `DefaultDetailsTabViewComponent`, `TabViewList` / `FormView`, and the `framework-button-*`
2167
+ ribbon buttons). It covers:
2168
+
2169
+ - a branded top bar (logo, app name, subtitle, environment badge and a working notifications
2170
+ bell) and a versioned sidebar footer;
2171
+ - a mocked audit/history view, reachable from the detail views' Views button;
2172
+ - `framework-button-filters` (via a `FiltersBase` component per entity) and
2173
+ `framework-button-export` on every list: the mock dataset honours `IListParameters.filters`, so
2174
+ filtering visibly narrows the grid, and exporting downloads a real CSV of the filtered rows;
2175
+ - a **child list of addresses** on the Customers detail view, edited through `lib-multi-editor` —
2176
+ `ChildList` and `MultiEditorModal` together, with the accordion gated on `hasEntityID` (a child
2177
+ collection needs a persisted parent), the Edit button opening the multi-editor rather than
2178
+ entering form edit mode, and add/remove/edit applied as one batch. The Customers grid's City
2179
+ column is **derived** from the customer's first address, so editing an address is reflected in
2180
+ the parent list and its filter with nothing to keep in sync;
2181
+ - mocked request latency, so loading states are observable;
2182
+ - full `en`/`pt` translation, so the language selector switches the entire showcase.
2183
+
2184
+ Development-only: it lives entirely in `app-showcase.stories.ts`, which is excluded from the
2185
+ package build.
2186
+
2187
+ ##### Fixed
2188
+
2189
+ - **Audit history models now match the JSON the audit endpoints actually return.**
2190
+ `IServicesHistoryList` and `IOperationsHistoryList` declared an `ID` property, and
2191
+ `IOperationsHistoryList` declared `entityId`. Neither key is ever sent. The audit endpoints
2192
+ (`POST /{controller}/{entityID}/Audit` and `.../Audit/{serviceHistoryID}`) serialize under
2193
+ ASP.NET Core's default camelCase policy, which lowercases only a _leading_ run of capitals — so the
2194
+ backend's `ID` goes out as `id` and its `EntityID` goes out as `entityID`. Typing a row against
2195
+ `.ID` or `.entityId` therefore compiled fine and read `undefined` at run time. See
2196
+ **⚠ Breaking Changes / Migration** below.
2197
+
2198
+ This was also a latent trap for anything supplying its own audit rows: the grid resolves row
2199
+ identity through `compareProperty`, which is `'id'`, so a row carrying only `ID` could never be
2200
+ selected — clicking a service entry left the operations grid empty. Rows shaped like the real
2201
+ payload work unchanged; `@shared`'s own runtime behaviour is not affected by this release.
2202
+
2203
+ - **The top bar's notifications bell is no longer pushed off-centre by its unread badge.** The badge
2204
+ is an absolutely-positioned overlay, so it adds no width — but it is still a DOM sibling after the
2205
+ bell icon, which defeated the `.btn i:not(:last-child)` margin guard in the global button styles.
2206
+ That left 8px of dead space to the icon's right and widened the button from 38px to 46px, but only
2207
+ while an unread count was showing, so it came and went with the count. The margin is now cleared
2208
+ for that button specifically; the shared guard, which is correct for icon-plus-label buttons, is
2209
+ unchanged.
2210
+
2211
+ - **Lint: `shared-` component/directive selector prefix is now accepted.**
2212
+ `libs/shared/eslint.config.mjs` still carried the scaffolded `prefix: 'lib'`, so the library's own
2213
+ `shared-`-prefixed components (`shared-main-layout`, `shared-login-layout`) failed
2214
+ `@angular-eslint/component-selector`. Both selector rules now accept `['lib', 'shared']`.
2215
+
2216
+ ##### ⚠ Breaking Changes / Migration
2217
+
2218
+ Two properties on the audit history models were renamed to the keys the backend actually sends. Both
2219
+ models are exported from the package root, so anything typed against the old names will now fail to
2220
+ compile. Nothing in `@shared` changes behaviour at run time — the fix is to the declared types.
2221
+
2222
+ | Model | Before | After |
2223
+ | ------------------------ | ------------------- | ------------------- |
2224
+ | `IServicesHistoryList` | `ID: number` | `id: number` |
2225
+ | `IOperationsHistoryList` | `ID: number` | `id: number` |
2226
+ | `IOperationsHistoryList` | `entityId?: number` | `entityID?: number` |
2227
+
2228
+ To upgrade:
2229
+
2230
+ 1. Rename `.ID` to `.id` wherever you read or construct an `IServicesHistoryList` or
2231
+ `IOperationsHistoryList`. If the compiler now reports the property as missing, that code was
2232
+ reading `undefined` before — it never matched the payload.
2233
+ 2. Rename `.entityId` to `.entityID` on `IOperationsHistoryList`. The capitals are deliberate and
2234
+ match the backend's `EntityID`; do not "correct" them back.
2235
+ 3. If you supply audit rows yourself — a test double, a Storybook mock, a hand-rolled
2236
+ `ServicesHistoryService` / `OperationsHistoryService` — emit `id`, not `ID`. Rows keyed only on
2237
+ `ID` were never selectable, so a service row's selection could not reach the operations grid.
2238
+ 4. If you worked around that by overriding `compareProperty` to `'ID'` in a `ServicesHistoryDataset`
2239
+ or `OperationsHistoryDataset` subclass, remove the override. The inherited `'id'` is now correct.
2240
+
2241
+ #### [1.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.2.0) - 2026-07-28
2242
+
2243
+ ##### Added
2244
+
2245
+ - A tokenized app backdrop — `--app-backdrop`, a subtle light-gray gradient — rendered behind the
2246
+ whole layout shell so the sidebar's new translucent "glass" surface (from `@library`) reads against
2247
+ a colored background.
2248
+
2249
+ ##### Changed
2250
+
2251
+ - `MainLayoutComponent` now floats the sidebar in a padded region over the shared `--app-backdrop`
2252
+ gradient (the shell background was a flat gray). This gives the `@library` glass sidebar a colored
2253
+ surface to read against; the content area shares the same subtle backdrop.
2254
+
2255
+ ##### ⚠ Breaking Changes / Migration
2256
+
2257
+ None. The `--app-backdrop` and floating sidebar region are applied automatically by
2258
+ `MainLayoutComponent`; no consumer action is required.
2259
+
2260
+ #### [1.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.1.0) - 2026-07-24
2261
+
2262
+ ##### Added
2263
+
2264
+ - New application top bar (`shared-top-bar`), rendered by `MainLayoutComponent`, composing the
2265
+ brand (app name, company name, optional logo), an environment badge, notifications, the language
2266
+ selector, and the user profile — alongside the existing sidebar-collapse and logout controls.
2267
+ - `NotificationsService` (root-provided) that streams notifications from a **SignalR hub**. It
2268
+ exposes `getNotifications()`, `getUnreadCount()`, `markAsRead()`, `markAllAsRead()`, `start()`,
2269
+ `stop()`, and an `isEnabled` flag. When enabled it connects (authenticated with the current JWT)
2270
+ to `AppConfig.notificationsUrl` and listens for the server's `ReceiveNotifications` push. The top
2271
+ bar drives it — apps do not call it directly.
2272
+ - The notifications feature is toggleable and configurable via `AppConfig.notificationsEnabled` and
2273
+ `AppConfig.notificationsUrl`; when disabled (or no URL is set) the bell is not rendered.
2274
+ - Notifications show an icon, title, and description, and — when a `callToActionUrl` is present —
2275
+ clicking navigates to it (internal routes via the Angular router, external URLs in a new tab).
2276
+ - Individually reusable top-bar components: `BrandComponent`, `EnvironmentBadgeComponent`,
2277
+ `NotificationsComponent`, `UserProfileComponent`, and the composing `TopBarComponent`.
2278
+ - `top-bar` i18n bundle (`en`/`pt`) registered in `ZAMBON_SHARED_I18N_RESOURCES`.
2279
+ - New peer dependency `@microsoft/signalr` (^10.0.0), used by `NotificationsService`.
2280
+ - `LanguageSelectorComponent` gained a `showFlag` input; when `true` the toggle shows the current
2281
+ language's flag on a light-gray button instead of the text label (used by the top bar; the login
2282
+ page keeps the text label).
2283
+ - `MainLayoutComponent` projects the application version (`AppConfig.version`) into the sidebar's
2284
+ footer slot, shown at the bottom of the navigation when set.
2285
+
2286
+ ##### Changed
2287
+
2288
+ - `ICurrentUserInfo` gained optional `pictureUrl` and `position` fields, displayed in the top-bar
2289
+ user profile. The avatar falls back to the user's initials when `pictureUrl` is absent, and the
2290
+ position line is hidden when `position` is empty.
2291
+ - **`INotification` redefined** to `{ title, description, icon, callToActionUrl?, isRead }` (was
2292
+ `{ id, title, message, read, createdAt }`) to match the SignalR hub contract.
2293
+ - The top bar no longer renders a sidebar-collapse button — the sidebar owns its own collapse/expand
2294
+ toggle. The user profile now uses a light, on-palette surface (neutral chip + brand-colored avatar)
2295
+ instead of the previous dark chip.
2296
+ - On small screens (< 768px) the top bar hides the brand text (app name + company; the logo remains)
2297
+ and the entire user profile, for a compact layout.
2298
+ - `LanguageSelectorComponent` now renders language flags as self-contained inline SVGs (US for `en`,
2299
+ Brazil for `pt`) instead of `/flags/*.png` background images, in both the flag toggle and the
2300
+ dropdown. The selector no longer depends on app-provided flag assets; apps that supplied custom
2301
+ `/flags/*.png` images will no longer see them in the language selector.
2302
+
2303
+ ##### ⚠ Breaking Changes / Migration
2304
+
2305
+ - **`INotification` shape changed** (see above). If you referenced the old fields (`id`, `message`,
2306
+ `read`, `createdAt`), update to `title`, `description`, `icon`, `callToActionUrl`, `isRead`.
2307
+ - **Notifications now require `@microsoft/signalr`.** Install it in the consuming app
2308
+ (`npm i @microsoft/signalr`) — it is a peer dependency.
2309
+
2310
+ To populate the top bar, provide `appName`, `companyName`, `environment`, and optionally `logoUrl`
2311
+ via `AppConfig` options (requires `@zambon-dev/framework` with the new `AppConfig` options). The
2312
+ environment badge maps `DEV`/`QA`/`STG` to colors, renders any other non-empty value with a neutral
2313
+ style, and is hidden for `PROD` or when no environment is set.
2314
+
2315
+ To enable notifications, set `notificationsEnabled: true` and `notificationsUrl` (the SignalR hub
2316
+ URL) in `AppConfig`, and implement a hub that pushes the notification list to clients via a
2317
+ `ReceiveNotifications` invocation (and optionally handles a `MarkAllAsRead` invoke). See
2318
+ `docs/guides/top-bar-configuration.md` for the full consuming-app guide. The user's
2319
+ `pictureUrl`/`position` are supplied by the auth response when available.
2320
+
2321
+ #### [1.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.0.2) - 2026-05-02
2322
+
2323
+ - Baseline release: the changelog starts being tracked from this version. Earlier history is
2324
+ available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
2325
+ `shared-v*` tags.