@zambon-dev/library 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -23,6 +23,56 @@ 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.2] - 2026-09-20
27
+
28
+ ### Fixed
29
+
30
+ - **A `lib-data-grid` column with no room from the start no longer drags its heading out of line.**
31
+ 1.6.1 removes the heading of a collapsed column, but only once that column had measured a real
32
+ width at some point. A column that never has any room does not, so it kept a heading over nothing
33
+ and pushed every column to its right along -- up to 263px on a fourteen-column screen, which is
34
+ the same drift 1.6.1 set out to remove.
35
+
36
+ Whether a zero is believable is now settled by the row rather than by what the column measured
37
+ before: if the row has a width the layout is live, so a column reporting zero really has
38
+ collapsed.
39
+
40
+ - **`lib-data-grid` headings no longer stay behind after you revisit a tab.** Re-activating a
41
+ detached tab puts the grid back in the page with its horizontal scroll at the start again, and no
42
+ scroll event follows to say so, so the headings kept the offset they had when you left: on a grid
43
+ scrolled 300px every heading sat 300px away from its column. The header now reconciles with the
44
+ body whenever the grid is re-measured, which is what re-inserting it triggers.
45
+
46
+ ## [1.6.1] - 2026-09-19
47
+
48
+ ### Fixed
49
+
50
+ - **`lib-data-grid` headings now sit over their own column.** On a grid with many columns each
51
+ heading drifted further from its cells than the last -- up to 263px on a fourteen-column screen --
52
+ and scrolling sideways separated them completely.
53
+
54
+ The body lays the columns out as a CSS grid inside the CDK viewport; the heading row is a flex
55
+ row outside it that copies each column's measured width. Four things pulled the two apart:
56
+
57
+ - The heading cells could **shrink**, so once the columns together were wider than the view every
58
+ heading was compressed a little and the error accumulated across the row.
59
+ - The heading row **did not follow the body sideways**. The rows scroll inside the viewport and
60
+ the headings simply stayed where they were.
61
+ - A column was **measured once and never again**, while the body keeps resizing its
62
+ `minmax(x, min-content)` columns as virtual scrolling brings different content into view.
63
+ - A column measured at **zero** -- a `1fr` column collapsed for want of room -- left its heading
64
+ with no width at all, so it kept its text width and shoved everything to its right along.
65
+
66
+ Headings are now fixed to the measured width, translate with the body's horizontal scroll, track
67
+ every resize, and a heading whose column has collapsed is removed rather than left taking a gap
68
+ the body does not have. Measured in a running application the drift is zero across every column,
69
+ at rest and scrolled.
70
+
71
+ A column that declares no `size` is laid out as `minmax(0, 1fr)`, which is free to collapse. If
72
+ one of yours does, its heading now goes with it rather than staying behind as a label over
73
+ nothing. Give such a column a real minimum -- `size: 'minmax(8rem, 1fr)'` -- to keep both on
74
+ screen.
75
+
26
76
  ## [1.6.0] - 2026-09-16
27
77
 
28
78
  ### Added
@@ -265,7 +315,9 @@ config options and the `getUserProfile()` method still exist but are no longer c
265
315
  available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
266
316
  `library-v*` tags.
267
317
 
268
- [Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/library-v1.6.0...HEAD
318
+ [Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/library-v1.6.2...HEAD
319
+ [1.6.2]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.2
320
+ [1.6.1]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.1
269
321
  [1.6.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.0
270
322
  [1.5.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.5.0
271
323
  [1.4.1]: https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.4.1
@@ -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';
@@ -2215,7 +2215,7 @@ class DataGridRowComponent extends BaseComponent {
2215
2215
  dataGridDataset = inject(DataGridDataset);
2216
2216
  selected = false;
2217
2217
  _rowData;
2218
- isWidthSet = {};
2218
+ elementRef = inject(ElementRef);
2219
2219
  //#endregion
2220
2220
  //#region Properties
2221
2221
  get rowData() {
@@ -2224,6 +2224,9 @@ class DataGridRowComponent extends BaseComponent {
2224
2224
  get columns() {
2225
2225
  return this.dataGridDataset.columns;
2226
2226
  }
2227
+ get isLaidOut() {
2228
+ return (this.elementRef.nativeElement?.getBoundingClientRect().width ?? 0) > 0;
2229
+ }
2227
2230
  get isRowDataSelected() {
2228
2231
  return this.dataGridDataset.isKeySelected(this.rowKey);
2229
2232
  }
@@ -2249,16 +2252,24 @@ class DataGridRowComponent extends BaseComponent {
2249
2252
  //#endregion
2250
2253
  //#region Event handlers
2251
2254
  onResize(colIndex, event) {
2252
- if (this.isWidthSet[colIndex]) {
2253
- return;
2254
- }
2255
2255
  const width = event.contentRect.width;
2256
- if (width <= 0) {
2256
+ // A zero means one of two very different things. Before the grid is laid out every column
2257
+ // reports zero, and taking that at face value would pin every heading at zero. Once the row
2258
+ // itself has a width the layout is live, and a zero is then the real thing: a 1fr column
2259
+ // collapsed for want of room, whose heading has to collapse with it or every column to its
2260
+ // right sits under the wrong one. Asking the row -- rather than remembering whether this
2261
+ // column once measured something -- also covers a column that is born collapsed and never
2262
+ // measures anything else.
2263
+ if (width <= 0 && !this.isLaidOut) {
2257
2264
  return;
2258
2265
  }
2259
- if (this.columns[colIndex].realSize != width) {
2266
+ // Reported on every change, not just the first. The body lays the columns out as a CSS grid of
2267
+ // minmax(x, min-content), so a column is only as wide as the content currently rendered --
2268
+ // which virtual scrolling keeps changing. Measuring once and latching left the header copying
2269
+ // a width the body no longer had, and with many columns that drift accumulated into cells
2270
+ // sitting under the wrong heading.
2271
+ if (this.columns[colIndex].realSize !== width) {
2260
2272
  this.columns[colIndex].realSize = width;
2261
- this.isWidthSet[colIndex] = true;
2262
2273
  }
2263
2274
  }
2264
2275
  onResizeSelection(event) {
@@ -2329,6 +2340,7 @@ class DataGridComponent extends BaseComponent {
2329
2340
  dataGridDataset = inject(DataGridDataset);
2330
2341
  hasFailed = false;
2331
2342
  headerRightMargin = 0;
2343
+ headerScrollLeft = 0;
2332
2344
  loading = false;
2333
2345
  bodyResized$ = new Subject();
2334
2346
  bodyResizeObserver;
@@ -2445,6 +2457,26 @@ class DataGridComponent extends BaseComponent {
2445
2457
  }
2446
2458
  }
2447
2459
  ngAfterViewInit() {
2460
+ // The rows scroll sideways inside the CDK viewport while the header sits outside it, so the
2461
+ // header has to be moved by hand or the headings stay behind as the cells travel. Read outside
2462
+ // Angular and written back inside it, because this fires for every frame of a scroll.
2463
+ const viewportElement = this.viewport?.elementRef?.nativeElement;
2464
+ if (viewportElement) {
2465
+ this.ngZone.runOutsideAngular(() => {
2466
+ fromEvent(viewportElement, 'scroll')
2467
+ .pipe(takeUntil(this.destroy$))
2468
+ .subscribe(() => {
2469
+ const scrollLeft = viewportElement.scrollLeft;
2470
+ if (this.headerScrollLeft === scrollLeft) {
2471
+ return;
2472
+ }
2473
+ this.ngZone.run(() => {
2474
+ this.headerScrollLeft = scrollLeft;
2475
+ this.changeDetectorRef.detectChanges();
2476
+ });
2477
+ });
2478
+ });
2479
+ }
2448
2480
  this.viewport?.renderedRangeStream
2449
2481
  .pipe(takeUntil(this.destroy$))
2450
2482
  .subscribe((listRange) => {
@@ -2462,6 +2494,7 @@ class DataGridComponent extends BaseComponent {
2462
2494
  this.headerRightMargin = this.bodyElement.nativeElement.clientWidth - viewportWidth;
2463
2495
  }
2464
2496
  this.viewport?.checkViewportSize();
2497
+ this.syncHeaderScroll();
2465
2498
  this.changeDetectorRef.detectChanges();
2466
2499
  });
2467
2500
  this.ngZone.runOutsideAngular(() => {
@@ -2493,11 +2526,20 @@ class DataGridComponent extends BaseComponent {
2493
2526
  }
2494
2527
  //#endregion
2495
2528
  //#region Private methods
2529
+ syncHeaderScroll() {
2530
+ // Re-inserting the viewport into the DOM -- which is what re-activating a detached tab does --
2531
+ // puts its scroll position back to zero without firing a scroll event, so nothing tells the
2532
+ // header to come back with it and the headings sit at an offset the body no longer has.
2533
+ const scrollLeft = this.viewport?.elementRef?.nativeElement?.scrollLeft ?? 0;
2534
+ if (this.headerScrollLeft !== scrollLeft) {
2535
+ this.headerScrollLeft = scrollLeft;
2536
+ }
2537
+ }
2496
2538
  trackByFn(index, item) {
2497
2539
  return item.key;
2498
2540
  }
2499
2541
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: DataGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2500
- 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" }] });
2542
+ 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" }] });
2501
2543
  }
2502
2544
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImport: i0, type: DataGridComponent, decorators: [{
2503
2545
  type: Component,
@@ -2510,7 +2552,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.6", ngImpor
2510
2552
  NgStyle,
2511
2553
  ScrollingModule,
2512
2554
  TranslatePipe,
2513
- ], 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"] }]
2555
+ ], 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"] }]
2514
2556
  }], ctorParameters: () => [], propDecorators: { bodyElement: [{
2515
2557
  type: ViewChild,
2516
2558
  args: ['body']