@zambon-dev/library 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -23,6 +23,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
23
23
 
24
24
  ### ⚠ Breaking Changes / Migration
25
25
 
26
+ ## [1.6.1] - 2026-09-19
27
+
28
+ ### Fixed
29
+
30
+ - **`lib-data-grid` headings now sit over their own column.** On a grid with many columns each
31
+ heading drifted further from its cells than the last -- up to 263px on a fourteen-column screen --
32
+ and scrolling sideways separated them completely.
33
+
34
+ The body lays the columns out as a CSS grid inside the CDK viewport; the heading row is a flex
35
+ row outside it that copies each column's measured width. Four things pulled the two apart:
36
+
37
+ - The heading cells could **shrink**, so once the columns together were wider than the view every
38
+ heading was compressed a little and the error accumulated across the row.
39
+ - The heading row **did not follow the body sideways**. The rows scroll inside the viewport and
40
+ the headings simply stayed where they were.
41
+ - A column was **measured once and never again**, while the body keeps resizing its
42
+ `minmax(x, min-content)` columns as virtual scrolling brings different content into view.
43
+ - A column measured at **zero** -- a `1fr` column collapsed for want of room -- left its heading
44
+ with no width at all, so it kept its text width and shoved everything to its right along.
45
+
46
+ Headings are now fixed to the measured width, translate with the body's horizontal scroll, track
47
+ every resize, and a heading whose column has collapsed is removed rather than left taking a gap
48
+ the body does not have. Measured in a running application the drift is zero across every column,
49
+ at rest and scrolled.
50
+
51
+ ## [1.6.0] - 2026-09-16
52
+
53
+ ### Added
54
+
55
+ - **`DisplayControls`**, which records the form controls that exist only to show a catalog
56
+ selection's label. `lib-catalog-select` works in pairs — `controlName` holds the identifier,
57
+ `displayControlName` holds the text — and it creates the second itself when a screen does not
58
+ declare one, so a form carries controls nobody wrote down. It now marks whichever control it
59
+ drives as its display, and `framework-button-filters` reads that mark to keep those out of what
60
+ it submits.
61
+
62
+ The mark is held in a `WeakSet` keyed by the control instance, not by its name: two forms may
63
+ each have an `employeeName`, and only the one a catalog select drives is a display control.
64
+ Nothing about an application changes — declaring the display control yourself still works, and
65
+ it is marked just the same.
66
+
26
67
  ## [1.5.0] - 2026-09-09
27
68
 
28
69
  ### Added
@@ -249,7 +290,9 @@ config options and the `getUserProfile()` method still exist but are no longer c
249
290
  available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
250
291
  `library-v*` tags.
251
292
 
252
- [Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/library-v1.5.0...HEAD
293
+ [Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/library-v1.6.1...HEAD
294
+ [1.6.1]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.1
295
+ [1.6.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.0
253
296
  [1.5.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.5.0
254
297
  [1.4.1]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.4.1
255
298
  [1.4.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.4.0
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { Component, inject, Injectable, EventEmitter, InjectionToken, Input, Output, KeyValueDiffers, ViewContainerRef, TemplateRef, ElementRef, HostListener, ViewChild, ChangeDetectorRef, ChangeDetectionStrategy, NgZone, Directive, ContentChildren, Pipe, NgModule } from '@angular/core';
3
- import { Subject, ReplaySubject, take, throwError, tap, map, takeUntil, filter, debounceTime, startWith, pairwise, Observable, forkJoin, switchMap, of, delay, merge } from 'rxjs';
3
+ import { Subject, ReplaySubject, take, throwError, tap, map, takeUntil, filter, debounceTime, startWith, pairwise, Observable, forkJoin, switchMap, of, fromEvent, delay, merge } from 'rxjs';
4
4
  import { Overlay, OverlayPositionBuilder } from '@angular/cdk/overlay';
5
5
  import { TemplatePortal } from '@angular/cdk/portal';
6
6
  import * as i1 from '@angular/common';
@@ -38,6 +38,95 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImpor
38
38
  args: [{ template: '' }]
39
39
  }] });
40
40
 
41
+ class DateHelpers {
42
+ //#region Public methods
43
+ static diffDays(initialDate, finalDate) {
44
+ const initialDateOnly = new Date(initialDate.getFullYear(), initialDate.getMonth(), initialDate.getDate());
45
+ const finalDateOnly = new Date(finalDate.getFullYear(), finalDate.getMonth(), finalDate.getDate());
46
+ const diff = initialDateOnly.getTime() - finalDateOnly.getTime();
47
+ return Math.floor(diff / (1000 * 60 * 60 * 24));
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Tracks the form controls that exist only to show a catalog selection's label.
53
+ *
54
+ * `lib-catalog-select` works in pairs: the control named by `controlName` holds the identifier, and
55
+ * the one named by `displayControlName` holds the text the user reads. The second is created by the
56
+ * component itself when a screen does not declare it, so a form ends up carrying controls nobody
57
+ * wrote down — and a filters form then submits them alongside the real filters.
58
+ *
59
+ * The mark lives in a `WeakSet` keyed by the control instance rather than by its name. That is the
60
+ * only scope that is actually correct: two forms may each have an `employeeName`, and only the one
61
+ * a catalog select drives is a display control. It also keeps nothing alive.
62
+ */
63
+ class DisplayControls {
64
+ //#region Variables
65
+ static marked = new WeakSet();
66
+ //#endregion
67
+ //#region Public methods
68
+ /**
69
+ * Whether the control exists only to display a catalog selection's label.
70
+ *
71
+ * @param control The control, which may be absent.
72
+ * @returns `true` when a catalog select drives it as its display control.
73
+ */
74
+ static isDisplayControl(control) {
75
+ return !!control && DisplayControls.marked.has(control);
76
+ }
77
+ /**
78
+ * Marks the control as existing only to display a catalog selection's label.
79
+ *
80
+ * @param control The control, which may be absent.
81
+ */
82
+ static markAsDisplayControl(control) {
83
+ if (control) {
84
+ DisplayControls.marked.add(control);
85
+ }
86
+ }
87
+ }
88
+
89
+ class GuidHelper {
90
+ static generateGUID() {
91
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
92
+ const r = (Math.random() * 16) | 0;
93
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
94
+ return v.toString(16);
95
+ });
96
+ }
97
+ }
98
+
99
+ class RouterFormatter {
100
+ //#region ViewChilds, Inputs, Outputs
101
+ //#endregion
102
+ //#region Variables
103
+ //#endregion
104
+ //#region Properties
105
+ //#endregion
106
+ //#region Constructor and Angular life cycle methods
107
+ //#endregion
108
+ //#region Event handlers
109
+ //#endregion
110
+ //#region Public methods
111
+ static getURL(route) {
112
+ let parentRoute = '';
113
+ if (route.parent) {
114
+ parentRoute = this.getURL(route.parent);
115
+ }
116
+ let path = route.routeConfig?.path ?? '';
117
+ route.paramMap.keys
118
+ .forEach(k => {
119
+ path = path.replace(':' + k, k === 'view'
120
+ ? ''
121
+ : (route.paramMap.get(k) ?? ''));
122
+ });
123
+ if (!route.parent || (parentRoute !== '' && parentRoute !== '/' && route.routeConfig?.path && path)) {
124
+ parentRoute += '/';
125
+ }
126
+ return parentRoute + path;
127
+ }
128
+ }
129
+
41
130
  class CatalogService {
42
131
  //#region ViewChilds, Inputs, Outputs
43
132
  //#endregion
@@ -193,57 +282,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImpor
193
282
  type: Injectable
194
283
  }], ctorParameters: () => [] });
195
284
 
196
- class DateHelpers {
197
- //#region Public methods
198
- static diffDays(initialDate, finalDate) {
199
- const initialDateOnly = new Date(initialDate.getFullYear(), initialDate.getMonth(), initialDate.getDate());
200
- const finalDateOnly = new Date(finalDate.getFullYear(), finalDate.getMonth(), finalDate.getDate());
201
- const diff = initialDateOnly.getTime() - finalDateOnly.getTime();
202
- return Math.floor(diff / (1000 * 60 * 60 * 24));
203
- }
204
- }
205
-
206
- class GuidHelper {
207
- static generateGUID() {
208
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
209
- const r = (Math.random() * 16) | 0;
210
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
211
- return v.toString(16);
212
- });
213
- }
214
- }
215
-
216
- class RouterFormatter {
217
- //#region ViewChilds, Inputs, Outputs
218
- //#endregion
219
- //#region Variables
220
- //#endregion
221
- //#region Properties
222
- //#endregion
223
- //#region Constructor and Angular life cycle methods
224
- //#endregion
225
- //#region Event handlers
226
- //#endregion
227
- //#region Public methods
228
- static getURL(route) {
229
- let parentRoute = '';
230
- if (route.parent) {
231
- parentRoute = this.getURL(route.parent);
232
- }
233
- let path = route.routeConfig?.path ?? '';
234
- route.paramMap.keys
235
- .forEach(k => {
236
- path = path.replace(':' + k, k === 'view'
237
- ? ''
238
- : (route.paramMap.get(k) ?? ''));
239
- });
240
- if (!route.parent || (parentRoute !== '' && parentRoute !== '/' && route.routeConfig?.path && path)) {
241
- parentRoute += '/';
242
- }
243
- return parentRoute + path;
244
- }
245
- }
246
-
247
285
  class GridConfigsProvider {
248
286
  //#region ViewChilds, Inputs, Outputs
249
287
  //#endregion
@@ -1833,6 +1871,10 @@ class CatalogSelectComponent extends BaseComponent {
1833
1871
  if (!parent.get(name)) {
1834
1872
  parent.addControl(name, new FormControl(''));
1835
1873
  }
1874
+ // Marked whether this component created it or the screen declared it: what makes a control
1875
+ // a display control is being driven as one, not who wrote it down. A filters form reads the
1876
+ // mark to keep it out of what it submits.
1877
+ DisplayControls.markAsDisplayControl(parent.get(name));
1836
1878
  }
1837
1879
  });
1838
1880
  }
@@ -2173,7 +2215,7 @@ class DataGridRowComponent extends BaseComponent {
2173
2215
  dataGridDataset = inject(DataGridDataset);
2174
2216
  selected = false;
2175
2217
  _rowData;
2176
- isWidthSet = {};
2218
+ hasMeasured = {};
2177
2219
  //#endregion
2178
2220
  //#region Properties
2179
2221
  get rowData() {
@@ -2207,16 +2249,22 @@ class DataGridRowComponent extends BaseComponent {
2207
2249
  //#endregion
2208
2250
  //#region Event handlers
2209
2251
  onResize(colIndex, event) {
2210
- if (this.isWidthSet[colIndex]) {
2211
- return;
2212
- }
2213
2252
  const width = event.contentRect.width;
2214
- if (width <= 0) {
2253
+ // The very first callback can arrive before the grid is laid out and reports zero for every
2254
+ // column; taking that at face value would pin the heading at zero. Once a column has measured
2255
+ // something real, a later zero is believable -- that is a 1fr column collapsing, and the
2256
+ // heading has to collapse with it or every column to its right sits under the wrong one.
2257
+ if (width <= 0 && !this.hasMeasured[colIndex]) {
2215
2258
  return;
2216
2259
  }
2217
- if (this.columns[colIndex].realSize != width) {
2260
+ this.hasMeasured[colIndex] = true;
2261
+ // Reported on every change, not just the first. The body lays the columns out as a CSS grid of
2262
+ // minmax(x, min-content), so a column is only as wide as the content currently rendered --
2263
+ // which virtual scrolling keeps changing. Measuring once and latching left the header copying
2264
+ // a width the body no longer had, and with many columns that drift accumulated into cells
2265
+ // sitting under the wrong heading.
2266
+ if (this.columns[colIndex].realSize !== width) {
2218
2267
  this.columns[colIndex].realSize = width;
2219
- this.isWidthSet[colIndex] = true;
2220
2268
  }
2221
2269
  }
2222
2270
  onResizeSelection(event) {
@@ -2287,6 +2335,7 @@ class DataGridComponent extends BaseComponent {
2287
2335
  dataGridDataset = inject(DataGridDataset);
2288
2336
  hasFailed = false;
2289
2337
  headerRightMargin = 0;
2338
+ headerScrollLeft = 0;
2290
2339
  loading = false;
2291
2340
  bodyResized$ = new Subject();
2292
2341
  bodyResizeObserver;
@@ -2403,6 +2452,26 @@ class DataGridComponent extends BaseComponent {
2403
2452
  }
2404
2453
  }
2405
2454
  ngAfterViewInit() {
2455
+ // The rows scroll sideways inside the CDK viewport while the header sits outside it, so the
2456
+ // header has to be moved by hand or the headings stay behind as the cells travel. Read outside
2457
+ // Angular and written back inside it, because this fires for every frame of a scroll.
2458
+ const viewportElement = this.viewport?.elementRef?.nativeElement;
2459
+ if (viewportElement) {
2460
+ this.ngZone.runOutsideAngular(() => {
2461
+ fromEvent(viewportElement, 'scroll')
2462
+ .pipe(takeUntil(this.destroy$))
2463
+ .subscribe(() => {
2464
+ const scrollLeft = viewportElement.scrollLeft;
2465
+ if (this.headerScrollLeft === scrollLeft) {
2466
+ return;
2467
+ }
2468
+ this.ngZone.run(() => {
2469
+ this.headerScrollLeft = scrollLeft;
2470
+ this.changeDetectorRef.detectChanges();
2471
+ });
2472
+ });
2473
+ });
2474
+ }
2406
2475
  this.viewport?.renderedRangeStream
2407
2476
  .pipe(takeUntil(this.destroy$))
2408
2477
  .subscribe((listRange) => {
@@ -2455,7 +2524,7 @@ class DataGridComponent extends BaseComponent {
2455
2524
  return item.key;
2456
2525
  }
2457
2526
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: DataGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2458
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.6", type: DataGridComponent, isStandalone: true, selector: "lib-data-grid", inputs: { disabled: "disabled", lazyLoadRows: "lazyLoadRows", showButtons: "showButtons" }, host: { properties: { "class.no-borders": "showButtons" } }, viewQueries: [{ propertyName: "bodyElement", first: true, predicate: ["body"], descendants: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"buttons\"\n*ngIf=\"showButtons\">\n <ng-content select=\"[buttons]\"></ng-content>\n</div>\n\n<div class=\"header\"\n[class.hide]=\"isHideColumns\"\n[style.padding-right.px]=\"headerRightMargin\">\n <div class=\"header-row\">\n <div *ngIf=\"isShowMultiSelect\"\n class=\"header-cell selection\"\n [ngStyle]=\"{\n 'width': selectionColWidth ? selectionColWidth : null\n }\">\n &nbsp;\n </div>\n \n <div *ngFor=\"let column of columns\"\n class=\"header-cell\"\n [ngStyle]=\"{\n 'width.px': column.realSize ?? 0 > 0 ? column.realSize : null\n }\">\n <span>{{ column.headerName | translate }}</span>\n </div>\n </div>\n</div>\n\n<div #body\nclass=\"body\"\n[style.min-height.px]=\"bodyMinHeight\">\n <cdk-virtual-scroll-viewport\n [itemSize]=\"configs.rowHeight\"\n [style.grid-template-columns]=\"gridColumns\">\n <lib-data-grid-row #row\n *cdkVirtualFor=\"let row of dataGridDataset.loadedRows; index as i; trackBy: trackByFn\"\n [disabled]=\"disabled\"\n [isFirstRow]=\"isFirstVisibleRow(i)\"\n [rowData]=\"row\"\n [style.height.px]=\"configs.rowHeight\"\n (selectionResized)=\"onSelectionResized($event)\">\n </lib-data-grid-row>\n\n <div *ngIf=\"loading\"\n class=\"loading\"\n [style.height.px]=\"configs.rowHeight\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n \n <span>\n {{ dataGridDataset.configs.loadingDisplayText ?? '' | translate }} \n </span>\n </div>\n \n <div *ngIf=\"!loading && !hasBeenLoaded && lazyLoadRows\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n {{ messageToDisplayWhenLazyLoad | translate }}\n </span>\n </div>\n\n <ng-container *ngIf=\"!loading && hasBeenLoaded && (!hasLoadedRows || hasFailed)\">\n <div *ngIf=\"!hasFailed && !hasLoadedRows && hasMessageToDisplayWhenEmpty && shouldDisplayMessageWhenEmpty\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n {{ messageToDisplayWhenEmpty | translate }}\n </span>\n </div>\n\n <div *ngIf=\"hasFailed\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n <i class=\"fas fa-exclamation-triangle mr-2 text-yellow-600\"></i>{{ messageToDisplayWhenFailed | translate }}\n </span>\n </div>\n </ng-container>\n </cdk-virtual-scroll-viewport>\n</div>", styles: [":host-context{display:flex;-webkit-user-select:none;user-select:none;flex-direction:column;overflow:hidden}:host-context:not(.no-borders){border-radius:.5rem;border-width:1px;border-color:#d1d5dbe6;--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}:host-context.no-borders .header .header-row{padding-top:.375rem;padding-bottom:.375rem}:host-context.no-borders .body lib-data-grid-row{padding-top:.25rem;padding-bottom:.25rem}.header-row,lib-data-grid-row,.loading,.message{gap:1rem;padding-left:1rem;padding-right:1rem}.header{border-bottom-width:1px;border-color:#d1d5dbe6;background-color:#e5e7eb66;font-weight:700}.header.hide{display:none}.header .header-row{display:flex;flex-direction:row;padding-top:.75rem;padding-bottom:.75rem}.header .header-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.header .header-cell:not(.selection){flex-grow:1}.body{position:relative;flex-grow:1}.body cdk-virtual-scroll-viewport{position:absolute;inset:0}.body cdk-virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-content-wrapper{display:grid;grid-template-columns:inherit}.body lib-data-grid-row{margin-top:-1px;cursor:pointer;border-top-width:1px;border-bottom-width:1px;border-color:#cbd5e180;padding-top:.5rem;padding-bottom:.5rem}.body lib-data-grid-row:nth-child(2n){background-color:#f8fafcb3}.body lib-data-grid-row:hover{background-color:#bfdbfe33}.body lib-data-grid-row.focused{z-index:1;border-color:#94a3b899}.body lib-data-grid-row.selected{position:relative;z-index:10;--tw-border-opacity: 1;border-color:rgb(147 197 253 / var(--tw-border-opacity, 1));background-color:#bfdbfe4d}.body lib-data-grid-row.selected:hover{background-color:#bfdbfe66}.body .loading,.body .message{display:flex;align-items:center;gap:.5rem;white-space:nowrap;height:2.5rem;border-bottom-width:1px;border-color:#cbd5e180;grid-column:1 / -1}.body .loading span,.body .message span{overflow:hidden;text-overflow:ellipsis}.body .loading svg{height:1.5rem;min-height:1.5rem;width:1.5rem;min-width:1.5rem}@keyframes spin{to{transform:rotate(360deg)}}.body .loading svg{animation:spin 1s linear infinite;transition-property:opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.buttons{display:flex;border-top-width:1px;border-bottom-width:1px;border-color:#d1d5dbe6;padding:.25rem}\n"], dependencies: [{ kind: "component", type: DataGridRowComponent, selector: "lib-data-grid-row", inputs: ["disabled", "isFirstRow", "rowData"], outputs: ["selectionResized"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1$2.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i1$2.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1$2.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2527
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.6", type: DataGridComponent, isStandalone: true, selector: "lib-data-grid", inputs: { disabled: "disabled", lazyLoadRows: "lazyLoadRows", showButtons: "showButtons" }, host: { properties: { "class.no-borders": "showButtons" } }, viewQueries: [{ propertyName: "bodyElement", first: true, predicate: ["body"], descendants: true }, { propertyName: "viewport", first: true, predicate: CdkVirtualScrollViewport, descendants: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"buttons\"\n*ngIf=\"showButtons\">\n <ng-content select=\"[buttons]\"></ng-content>\n</div>\n\n<div class=\"header\"\n[class.hide]=\"isHideColumns\"\n[style.padding-right.px]=\"headerRightMargin\">\n <!-- Translated by however far the body is scrolled sideways. The rows scroll inside the CDK\n viewport and the header sits outside it, so without this the headings simply stay put\n while the cells move away from them. -->\n <div class=\"header-row\"\n [style.transform]=\"headerScrollLeft ? 'translateX(-' + headerScrollLeft + 'px)' : null\">\n <div *ngIf=\"isShowMultiSelect\"\n class=\"header-cell selection\"\n [ngStyle]=\"{\n 'width': selectionColWidth ? selectionColWidth : null\n }\">\n &nbsp;\n </div>\n \n <!-- A measured zero is a width, not the absence of one: it is a 1fr column that collapsed,\n and its heading has to collapse too. The old test read `realSize ?? 0 > 0`, which binds\n as `realSize ?? (0 > 0)` and so turned zero into no width at all.\n\n Such a heading is removed rather than merely made zero wide, because a flex item still\n takes a gap on each side while the body grid allows the collapsed track only one, and\n that spare gap pushed every heading to its right out by 16px. -->\n <div *ngFor=\"let column of columns\"\n class=\"header-cell\"\n [ngStyle]=\"{\n 'display': column.realSize === 0 ? 'none' : null,\n 'width.px': column.realSize ?? null\n }\">\n <span>{{ column.headerName | translate }}</span>\n </div>\n </div>\n</div>\n\n<div #body\nclass=\"body\"\n[style.min-height.px]=\"bodyMinHeight\">\n <cdk-virtual-scroll-viewport\n [itemSize]=\"configs.rowHeight\"\n [style.grid-template-columns]=\"gridColumns\">\n <lib-data-grid-row #row\n *cdkVirtualFor=\"let row of dataGridDataset.loadedRows; index as i; trackBy: trackByFn\"\n [disabled]=\"disabled\"\n [isFirstRow]=\"isFirstVisibleRow(i)\"\n [rowData]=\"row\"\n [style.height.px]=\"configs.rowHeight\"\n (selectionResized)=\"onSelectionResized($event)\">\n </lib-data-grid-row>\n\n <div *ngIf=\"loading\"\n class=\"loading\"\n [style.height.px]=\"configs.rowHeight\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n \n <span>\n {{ dataGridDataset.configs.loadingDisplayText ?? '' | translate }} \n </span>\n </div>\n \n <div *ngIf=\"!loading && !hasBeenLoaded && lazyLoadRows\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n {{ messageToDisplayWhenLazyLoad | translate }}\n </span>\n </div>\n\n <ng-container *ngIf=\"!loading && hasBeenLoaded && (!hasLoadedRows || hasFailed)\">\n <div *ngIf=\"!hasFailed && !hasLoadedRows && hasMessageToDisplayWhenEmpty && shouldDisplayMessageWhenEmpty\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n {{ messageToDisplayWhenEmpty | translate }}\n </span>\n </div>\n\n <div *ngIf=\"hasFailed\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n <i class=\"fas fa-exclamation-triangle mr-2 text-yellow-600\"></i>{{ messageToDisplayWhenFailed | translate }}\n </span>\n </div>\n </ng-container>\n </cdk-virtual-scroll-viewport>\n</div>", styles: [":host-context{display:flex;-webkit-user-select:none;user-select:none;flex-direction:column;overflow:hidden}:host-context:not(.no-borders){border-radius:.5rem;border-width:1px;border-color:#d1d5dbe6;--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}:host-context.no-borders .header .header-row{padding-top:.375rem;padding-bottom:.375rem}:host-context.no-borders .body lib-data-grid-row{padding-top:.25rem;padding-bottom:.25rem}.header-row,lib-data-grid-row,.loading,.message{gap:1rem;padding-left:1rem;padding-right:1rem}.header{overflow:hidden;border-bottom-width:1px;border-color:#d1d5dbe6;background-color:#e5e7eb66;font-weight:700}.header.hide{display:none}.header .header-row{display:flex;flex-direction:row;padding-top:.75rem;padding-bottom:.75rem}.header .header-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.header .header-cell:not(.selection){flex:none}.body{position:relative;flex-grow:1}.body cdk-virtual-scroll-viewport{position:absolute;inset:0}.body cdk-virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-content-wrapper{display:grid;grid-template-columns:inherit}.body lib-data-grid-row{margin-top:-1px;cursor:pointer;border-top-width:1px;border-bottom-width:1px;border-color:#cbd5e180;padding-top:.5rem;padding-bottom:.5rem}.body lib-data-grid-row:nth-child(2n){background-color:#f8fafcb3}.body lib-data-grid-row:hover{background-color:#bfdbfe33}.body lib-data-grid-row.focused{z-index:1;border-color:#94a3b899}.body lib-data-grid-row.selected{position:relative;z-index:10;--tw-border-opacity: 1;border-color:rgb(147 197 253 / var(--tw-border-opacity, 1));background-color:#bfdbfe4d}.body lib-data-grid-row.selected:hover{background-color:#bfdbfe66}.body .loading,.body .message{display:flex;align-items:center;gap:.5rem;white-space:nowrap;height:2.5rem;border-bottom-width:1px;border-color:#cbd5e180;grid-column:1 / -1}.body .loading span,.body .message span{overflow:hidden;text-overflow:ellipsis}.body .loading svg{height:1.5rem;min-height:1.5rem;width:1.5rem;min-width:1.5rem}@keyframes spin{to{transform:rotate(360deg)}}.body .loading svg{animation:spin 1s linear infinite;transition-property:opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.buttons{display:flex;border-top-width:1px;border-bottom-width:1px;border-color:#d1d5dbe6;padding:.25rem}\n"], dependencies: [{ kind: "component", type: DataGridRowComponent, selector: "lib-data-grid-row", inputs: ["disabled", "isFirstRow", "rowData"], outputs: ["selectionResized"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i1$2.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i1$2.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i1$2.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2459
2528
  }
2460
2529
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: DataGridComponent, decorators: [{
2461
2530
  type: Component,
@@ -2468,7 +2537,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImpor
2468
2537
  NgStyle,
2469
2538
  ScrollingModule,
2470
2539
  TranslatePipe,
2471
- ], template: "<div class=\"buttons\"\n*ngIf=\"showButtons\">\n <ng-content select=\"[buttons]\"></ng-content>\n</div>\n\n<div class=\"header\"\n[class.hide]=\"isHideColumns\"\n[style.padding-right.px]=\"headerRightMargin\">\n <div class=\"header-row\">\n <div *ngIf=\"isShowMultiSelect\"\n class=\"header-cell selection\"\n [ngStyle]=\"{\n 'width': selectionColWidth ? selectionColWidth : null\n }\">\n &nbsp;\n </div>\n \n <div *ngFor=\"let column of columns\"\n class=\"header-cell\"\n [ngStyle]=\"{\n 'width.px': column.realSize ?? 0 > 0 ? column.realSize : null\n }\">\n <span>{{ column.headerName | translate }}</span>\n </div>\n </div>\n</div>\n\n<div #body\nclass=\"body\"\n[style.min-height.px]=\"bodyMinHeight\">\n <cdk-virtual-scroll-viewport\n [itemSize]=\"configs.rowHeight\"\n [style.grid-template-columns]=\"gridColumns\">\n <lib-data-grid-row #row\n *cdkVirtualFor=\"let row of dataGridDataset.loadedRows; index as i; trackBy: trackByFn\"\n [disabled]=\"disabled\"\n [isFirstRow]=\"isFirstVisibleRow(i)\"\n [rowData]=\"row\"\n [style.height.px]=\"configs.rowHeight\"\n (selectionResized)=\"onSelectionResized($event)\">\n </lib-data-grid-row>\n\n <div *ngIf=\"loading\"\n class=\"loading\"\n [style.height.px]=\"configs.rowHeight\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n \n <span>\n {{ dataGridDataset.configs.loadingDisplayText ?? '' | translate }} \n </span>\n </div>\n \n <div *ngIf=\"!loading && !hasBeenLoaded && lazyLoadRows\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n {{ messageToDisplayWhenLazyLoad | translate }}\n </span>\n </div>\n\n <ng-container *ngIf=\"!loading && hasBeenLoaded && (!hasLoadedRows || hasFailed)\">\n <div *ngIf=\"!hasFailed && !hasLoadedRows && hasMessageToDisplayWhenEmpty && shouldDisplayMessageWhenEmpty\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n {{ messageToDisplayWhenEmpty | translate }}\n </span>\n </div>\n\n <div *ngIf=\"hasFailed\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n <i class=\"fas fa-exclamation-triangle mr-2 text-yellow-600\"></i>{{ messageToDisplayWhenFailed | translate }}\n </span>\n </div>\n </ng-container>\n </cdk-virtual-scroll-viewport>\n</div>", styles: [":host-context{display:flex;-webkit-user-select:none;user-select:none;flex-direction:column;overflow:hidden}:host-context:not(.no-borders){border-radius:.5rem;border-width:1px;border-color:#d1d5dbe6;--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}:host-context.no-borders .header .header-row{padding-top:.375rem;padding-bottom:.375rem}:host-context.no-borders .body lib-data-grid-row{padding-top:.25rem;padding-bottom:.25rem}.header-row,lib-data-grid-row,.loading,.message{gap:1rem;padding-left:1rem;padding-right:1rem}.header{border-bottom-width:1px;border-color:#d1d5dbe6;background-color:#e5e7eb66;font-weight:700}.header.hide{display:none}.header .header-row{display:flex;flex-direction:row;padding-top:.75rem;padding-bottom:.75rem}.header .header-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.header .header-cell:not(.selection){flex-grow:1}.body{position:relative;flex-grow:1}.body cdk-virtual-scroll-viewport{position:absolute;inset:0}.body cdk-virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-content-wrapper{display:grid;grid-template-columns:inherit}.body lib-data-grid-row{margin-top:-1px;cursor:pointer;border-top-width:1px;border-bottom-width:1px;border-color:#cbd5e180;padding-top:.5rem;padding-bottom:.5rem}.body lib-data-grid-row:nth-child(2n){background-color:#f8fafcb3}.body lib-data-grid-row:hover{background-color:#bfdbfe33}.body lib-data-grid-row.focused{z-index:1;border-color:#94a3b899}.body lib-data-grid-row.selected{position:relative;z-index:10;--tw-border-opacity: 1;border-color:rgb(147 197 253 / var(--tw-border-opacity, 1));background-color:#bfdbfe4d}.body lib-data-grid-row.selected:hover{background-color:#bfdbfe66}.body .loading,.body .message{display:flex;align-items:center;gap:.5rem;white-space:nowrap;height:2.5rem;border-bottom-width:1px;border-color:#cbd5e180;grid-column:1 / -1}.body .loading span,.body .message span{overflow:hidden;text-overflow:ellipsis}.body .loading svg{height:1.5rem;min-height:1.5rem;width:1.5rem;min-width:1.5rem}@keyframes spin{to{transform:rotate(360deg)}}.body .loading svg{animation:spin 1s linear infinite;transition-property:opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.buttons{display:flex;border-top-width:1px;border-bottom-width:1px;border-color:#d1d5dbe6;padding:.25rem}\n"] }]
2540
+ ], template: "<div class=\"buttons\"\n*ngIf=\"showButtons\">\n <ng-content select=\"[buttons]\"></ng-content>\n</div>\n\n<div class=\"header\"\n[class.hide]=\"isHideColumns\"\n[style.padding-right.px]=\"headerRightMargin\">\n <!-- Translated by however far the body is scrolled sideways. The rows scroll inside the CDK\n viewport and the header sits outside it, so without this the headings simply stay put\n while the cells move away from them. -->\n <div class=\"header-row\"\n [style.transform]=\"headerScrollLeft ? 'translateX(-' + headerScrollLeft + 'px)' : null\">\n <div *ngIf=\"isShowMultiSelect\"\n class=\"header-cell selection\"\n [ngStyle]=\"{\n 'width': selectionColWidth ? selectionColWidth : null\n }\">\n &nbsp;\n </div>\n \n <!-- A measured zero is a width, not the absence of one: it is a 1fr column that collapsed,\n and its heading has to collapse too. The old test read `realSize ?? 0 > 0`, which binds\n as `realSize ?? (0 > 0)` and so turned zero into no width at all.\n\n Such a heading is removed rather than merely made zero wide, because a flex item still\n takes a gap on each side while the body grid allows the collapsed track only one, and\n that spare gap pushed every heading to its right out by 16px. -->\n <div *ngFor=\"let column of columns\"\n class=\"header-cell\"\n [ngStyle]=\"{\n 'display': column.realSize === 0 ? 'none' : null,\n 'width.px': column.realSize ?? null\n }\">\n <span>{{ column.headerName | translate }}</span>\n </div>\n </div>\n</div>\n\n<div #body\nclass=\"body\"\n[style.min-height.px]=\"bodyMinHeight\">\n <cdk-virtual-scroll-viewport\n [itemSize]=\"configs.rowHeight\"\n [style.grid-template-columns]=\"gridColumns\">\n <lib-data-grid-row #row\n *cdkVirtualFor=\"let row of dataGridDataset.loadedRows; index as i; trackBy: trackByFn\"\n [disabled]=\"disabled\"\n [isFirstRow]=\"isFirstVisibleRow(i)\"\n [rowData]=\"row\"\n [style.height.px]=\"configs.rowHeight\"\n (selectionResized)=\"onSelectionResized($event)\">\n </lib-data-grid-row>\n\n <div *ngIf=\"loading\"\n class=\"loading\"\n [style.height.px]=\"configs.rowHeight\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n \n <span>\n {{ dataGridDataset.configs.loadingDisplayText ?? '' | translate }} \n </span>\n </div>\n \n <div *ngIf=\"!loading && !hasBeenLoaded && lazyLoadRows\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n {{ messageToDisplayWhenLazyLoad | translate }}\n </span>\n </div>\n\n <ng-container *ngIf=\"!loading && hasBeenLoaded && (!hasLoadedRows || hasFailed)\">\n <div *ngIf=\"!hasFailed && !hasLoadedRows && hasMessageToDisplayWhenEmpty && shouldDisplayMessageWhenEmpty\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n {{ messageToDisplayWhenEmpty | translate }}\n </span>\n </div>\n\n <div *ngIf=\"hasFailed\"\n class=\"message\"\n [style.height.px]=\"configs.rowHeight\">\n <span>\n <i class=\"fas fa-exclamation-triangle mr-2 text-yellow-600\"></i>{{ messageToDisplayWhenFailed | translate }}\n </span>\n </div>\n </ng-container>\n </cdk-virtual-scroll-viewport>\n</div>", styles: [":host-context{display:flex;-webkit-user-select:none;user-select:none;flex-direction:column;overflow:hidden}:host-context:not(.no-borders){border-radius:.5rem;border-width:1px;border-color:#d1d5dbe6;--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}:host-context.no-borders .header .header-row{padding-top:.375rem;padding-bottom:.375rem}:host-context.no-borders .body lib-data-grid-row{padding-top:.25rem;padding-bottom:.25rem}.header-row,lib-data-grid-row,.loading,.message{gap:1rem;padding-left:1rem;padding-right:1rem}.header{overflow:hidden;border-bottom-width:1px;border-color:#d1d5dbe6;background-color:#e5e7eb66;font-weight:700}.header.hide{display:none}.header .header-row{display:flex;flex-direction:row;padding-top:.75rem;padding-bottom:.75rem}.header .header-cell{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.header .header-cell:not(.selection){flex:none}.body{position:relative;flex-grow:1}.body cdk-virtual-scroll-viewport{position:absolute;inset:0}.body cdk-virtual-scroll-viewport ::ng-deep .cdk-virtual-scroll-content-wrapper{display:grid;grid-template-columns:inherit}.body lib-data-grid-row{margin-top:-1px;cursor:pointer;border-top-width:1px;border-bottom-width:1px;border-color:#cbd5e180;padding-top:.5rem;padding-bottom:.5rem}.body lib-data-grid-row:nth-child(2n){background-color:#f8fafcb3}.body lib-data-grid-row:hover{background-color:#bfdbfe33}.body lib-data-grid-row.focused{z-index:1;border-color:#94a3b899}.body lib-data-grid-row.selected{position:relative;z-index:10;--tw-border-opacity: 1;border-color:rgb(147 197 253 / var(--tw-border-opacity, 1));background-color:#bfdbfe4d}.body lib-data-grid-row.selected:hover{background-color:#bfdbfe66}.body .loading,.body .message{display:flex;align-items:center;gap:.5rem;white-space:nowrap;height:2.5rem;border-bottom-width:1px;border-color:#cbd5e180;grid-column:1 / -1}.body .loading span,.body .message span{overflow:hidden;text-overflow:ellipsis}.body .loading svg{height:1.5rem;min-height:1.5rem;width:1.5rem;min-width:1.5rem}@keyframes spin{to{transform:rotate(360deg)}}.body .loading svg{animation:spin 1s linear infinite;transition-property:opacity;transition-duration:.2s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.buttons{display:flex;border-top-width:1px;border-bottom-width:1px;border-color:#d1d5dbe6;padding:.25rem}\n"] }]
2472
2541
  }], ctorParameters: () => [], propDecorators: { bodyElement: [{
2473
2542
  type: ViewChild,
2474
2543
  args: ['body']
@@ -4013,5 +4082,5 @@ class DateValidators {
4013
4082
  * Generated bundle index. Do not edit.
4014
4083
  */
4015
4084
 
4016
- export { BaseComponent, BaseDataset, CatalogSelectComponent, CatalogService, DataGridComponent, DataGridConfigsProvider, DataGridDataset, DataGridRowComponent, DataProviderService, DateHelpers, DateValidators, EnumTranslatePipe, FormGroupComponent, FormGroupService, FormInputComponent, FormInputGroupComponent, FormService, GridConfigsProvider, GridDataset, GroupAccordionComponent, GroupContainerComponent, GroupScrollSpyComponent, GuidHelper, LibraryModule, ModalComponent, MultiEditorComponent, MultiEditorDataset, MultiSelectComponent, MultiSelectResultDataset, MultiSelectResultGridComponent, ReplaceManyPipe, ReplacePipe, RibbonButtonComponent, RibbonComponent, RibbonGroupChild, RibbonGroupComponent, RouterFormatter, SIDEBAR_CONFIGS, ScrollSpyDirective, SidebarComponent, SidebarConfigs, SidebarMenu, SidebarMenuOpenMode, SidebarService, toSidebarMenuOpenMode };
4085
+ export { BaseComponent, BaseDataset, CatalogSelectComponent, CatalogService, DataGridComponent, DataGridConfigsProvider, DataGridDataset, DataGridRowComponent, DataProviderService, DateHelpers, DateValidators, DisplayControls, EnumTranslatePipe, FormGroupComponent, FormGroupService, FormInputComponent, FormInputGroupComponent, FormService, GridConfigsProvider, GridDataset, GroupAccordionComponent, GroupContainerComponent, GroupScrollSpyComponent, GuidHelper, LibraryModule, ModalComponent, MultiEditorComponent, MultiEditorDataset, MultiSelectComponent, MultiSelectResultDataset, MultiSelectResultGridComponent, ReplaceManyPipe, ReplacePipe, RibbonButtonComponent, RibbonComponent, RibbonGroupChild, RibbonGroupComponent, RouterFormatter, SIDEBAR_CONFIGS, ScrollSpyDirective, SidebarComponent, SidebarConfigs, SidebarMenu, SidebarMenuOpenMode, SidebarService, toSidebarMenuOpenMode };
4017
4086
  //# sourceMappingURL=zambon-dev-library.mjs.map