@egose/shadcn-theme-ng 0.6.0 → 0.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.
Files changed (50) hide show
  1. package/README.md +119 -1
  2. package/autocomplete/fesm2022/autocomplete.mjs +14 -4
  3. package/autocomplete/types/autocomplete.d.ts +4 -1
  4. package/checkbox/README.md +6 -2
  5. package/checkbox/fesm2022/checkbox.mjs +11 -7
  6. package/checkbox/types/checkbox.d.ts +6 -4
  7. package/combobox/fesm2022/combobox.mjs +13 -7
  8. package/combobox/types/combobox.d.ts +8 -2
  9. package/data-table/README.md +10 -0
  10. package/data-table/fesm2022/data-table.mjs +48 -12
  11. package/data-table/types/data-table.d.ts +16 -4
  12. package/date-picker/README.md +41 -9
  13. package/date-picker/fesm2022/date-picker.mjs +265 -59
  14. package/date-picker/types/date-picker.d.ts +58 -27
  15. package/form-autocomplete/README.md +4 -2
  16. package/form-autocomplete/fesm2022/form-autocomplete.mjs +19 -6
  17. package/form-autocomplete/types/form-autocomplete.d.ts +9 -3
  18. package/form-combobox/README.md +4 -2
  19. package/form-combobox/fesm2022/form-combobox.mjs +23 -6
  20. package/form-combobox/types/form-combobox.d.ts +10 -3
  21. package/form-input-otp/README.md +7 -5
  22. package/form-input-otp/fesm2022/form-input-otp.mjs +20 -8
  23. package/form-input-otp/types/form-input-otp.d.ts +9 -3
  24. package/form-searchable-multiselect/README.md +15 -1
  25. package/form-searchable-multiselect/fesm2022/form-searchable-multiselect.mjs +15 -3
  26. package/form-searchable-multiselect/types/form-searchable-multiselect.d.ts +9 -1
  27. package/form-slider/README.md +3 -1
  28. package/form-slider/fesm2022/form-slider.mjs +19 -6
  29. package/form-slider/types/form-slider.d.ts +9 -3
  30. package/form-toggle/README.md +22 -18
  31. package/form-toggle/fesm2022/form-toggle.mjs +22 -9
  32. package/form-toggle/types/form-toggle.d.ts +6 -2
  33. package/input-otp/README.md +33 -9
  34. package/input-otp/fesm2022/input-otp.mjs +127 -11
  35. package/input-otp/types/input-otp.d.ts +24 -4
  36. package/package.json +1 -1
  37. package/pagination/README.md +21 -12
  38. package/pagination/fesm2022/pagination.mjs +73 -65
  39. package/pagination/types/pagination.d.ts +21 -8
  40. package/phone-input/README.md +16 -0
  41. package/phone-input/fesm2022/phone-input.mjs +41 -3
  42. package/phone-input/types/phone-input.d.ts +5 -2
  43. package/searchable-multiselect/README.md +62 -40
  44. package/searchable-multiselect/fesm2022/searchable-multiselect.mjs +59 -32
  45. package/searchable-multiselect/types/searchable-multiselect.d.ts +23 -6
  46. package/slider/fesm2022/slider.mjs +9 -5
  47. package/slider/types/slider.d.ts +6 -2
  48. package/stepper/README.md +39 -19
  49. package/stepper/fesm2022/stepper.mjs +64 -12
  50. package/stepper/types/stepper.d.ts +12 -1
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, computed, ChangeDetectionStrategy, Component, model, output, signal, linkedSignal, effect, NgModule } from '@angular/core';
2
+ import { input, computed, ChangeDetectionStrategy, Component, model, output, signal, linkedSignal, effect, untracked, NgModule } from '@angular/core';
3
3
  import { NgTemplateOutlet } from '@angular/common';
4
4
  import { HlmButton } from '@egose/shadcn-theme-ng/button';
5
5
  import { HlmCard, HlmCardContent } from '@egose/shadcn-theme-ng/card';
@@ -254,6 +254,16 @@ class EgDataTable {
254
254
  /** Client rows, or a server page slice when `manualPagination` is set (`null` while loading). */
255
255
  data = input([], /* @ts-ignore */
256
256
  ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
257
+ /**
258
+ * Pure stable identity callback, e.g. `(record) => record.id`. Return a unique
259
+ * string per entity, independent of position, and keep the callback reference stable.
260
+ * Selection survives refreshes only for IDs still in the supplied rows (after
261
+ * `filterRows`); server selection is limited to the loaded slice. Without this
262
+ * callback, IDs are positional and replacing the rows clears selection.
263
+ * Changing the callback clears selection. Update data immutably.
264
+ */
265
+ getRowId = input(/* @ts-ignore */
266
+ ...(ngDevMode ? [undefined, { debugName: "getRowId" }] : /* istanbul ignore next */ []));
257
267
  /** When true, `data` is a server page slice and the table uses TanStack `manualPagination`. Sorting and filtering are then display-only: the slice is shown as-is while `sortingChange` / `filterChange` (`columnFiltersChange`) notify the caller to refetch. */
258
268
  manualPagination = input(false, /* @ts-ignore */
259
269
  ...(ngDevMode ? [{ debugName: "manualPagination" }] : /* istanbul ignore next */ []));
@@ -307,7 +317,7 @@ class EgDataTable {
307
317
  /** Seeds the column-visibility state once at creation. Later changes are ignored. */
308
318
  initialColumnVisibility = input({}, /* @ts-ignore */
309
319
  ...(ngDevMode ? [{ debugName: "initialColumnVisibility" }] : /* istanbul ignore next */ []));
310
- /** Seeds the row-selection state once at creation (keys are row ids). Later changes are ignored. */
320
+ /** Seeds selection once for current rows: keys come from `getRowId`, or positional indices without it. Missing IDs are discarded. Later changes are ignored. */
311
321
  initialRowSelection = input({}, /* @ts-ignore */
312
322
  ...(ngDevMode ? [{ debugName: "initialRowSelection" }] : /* istanbul ignore next */ []));
313
323
  /** Show the rows-per-page selector in the footer pager. */
@@ -320,7 +330,7 @@ class EgDataTable {
320
330
  navMode = input('icons', /* @ts-ignore */
321
331
  ...(ngDevMode ? [{ debugName: "navMode" }] : /* istanbul ignore next */ []));
322
332
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
323
- /** Emits the selected rows whenever selection changes (requires `enableSelection`). */
333
+ /** Emits on init and when selected row identities/order or object references change. Includes rows hidden by built-in filters/client pagination, but never rows absent from `data`/`filterRows`. */
324
334
  selectionChange = output();
325
335
  /** Emits the target page index on pager navigation (primary hook for server fetching). */
326
336
  pageChange = output();
@@ -343,8 +353,6 @@ class EgDataTable {
343
353
  ...(ngDevMode ? [{ debugName: "_columnFilters" }] : /* istanbul ignore next */ []));
344
354
  _columnVisibility = signal({}, /* @ts-ignore */
345
355
  ...(ngDevMode ? [{ debugName: "_columnVisibility" }] : /* istanbul ignore next */ []));
346
- _rowSelection = signal({}, /* @ts-ignore */
347
- ...(ngDevMode ? [{ debugName: "_rowSelection" }] : /* istanbul ignore next */ []));
348
356
  _clientPageIndex = signal(0, /* @ts-ignore */
349
357
  ...(ngDevMode ? [{ debugName: "_clientPageIndex" }] : /* istanbul ignore next */ []));
350
358
  // Linked so the bound `defaultPageSize` applies once inputs are set, while
@@ -376,6 +384,32 @@ class EgDataTable {
376
384
  return filter ? filter(rows) : rows;
377
385
  }, /* @ts-ignore */
378
386
  ...(ngDevMode ? [{ debugName: "_items" }] : /* istanbul ignore next */ []));
387
+ // TanStack uses data-reference changes to reset pagination. State/layout
388
+ // updates must not look like a new data set (including selecting page rows).
389
+ _tableItems = computed(() => {
390
+ // A different identity function must also rebuild TanStack's cached rows.
391
+ this.getRowId();
392
+ return [...this._items()];
393
+ }, /* @ts-ignore */
394
+ ...(ngDevMode ? [{ debugName: "_tableItems" }] : /* istanbul ignore next */ []));
395
+ // Reconcile synchronously with the data source, before either TanStack or the
396
+ // output can observe old positional keys against new records. An effect-based
397
+ // reset would permit a transient wrong-entity selection.
398
+ _rowSelection = linkedSignal({ ...(ngDevMode ? { debugName: "_rowSelection" } : /* istanbul ignore next */ {}), source: () => ({ items: this._items(), getRowId: this.getRowId() }),
399
+ computation: (source, previous) => {
400
+ if (!previous || source.getRowId !== previous.source.getRowId)
401
+ return {};
402
+ if (!source.getRowId && source.items !== previous.source.items)
403
+ return {};
404
+ return this._keepCurrentSelection(previous.value);
405
+ } });
406
+ _keepCurrentSelection(selection) {
407
+ const getRowId = this.getRowId();
408
+ return Object.fromEntries(this._items()
409
+ .map((row, index) => (getRowId ? getRowId(row) : String(index)))
410
+ .filter((id) => Object.hasOwn(selection, id) && selection[id])
411
+ .map((id) => [id, true]));
412
+ }
379
413
  _serverPageCount = computed(() => {
380
414
  const paginated = this._page();
381
415
  if (!paginated || paginated.limit <= 0)
@@ -422,7 +456,8 @@ class EgDataTable {
422
456
  return {
423
457
  features: this.features(),
424
458
  columns: this._finalColumns(),
425
- data: [...this._items()],
459
+ data: this._tableItems(),
460
+ getRowId: this.getRowId(),
426
461
  manualPagination: server,
427
462
  manualSorting: server,
428
463
  manualFiltering: server,
@@ -472,10 +507,11 @@ class EgDataTable {
472
507
  },
473
508
  };
474
509
  });
510
+ _selectedRows = computed(() => this._table.getSelectedRowModel().rows.map((row) => row.original), { ...(ngDevMode ? { debugName: "_selectedRows" } : /* istanbul ignore next */ {}), equal: (a, b) => a.length === b.length && a.every((row, index) => row === b[index]) });
475
511
  _emitSelection = effect(() => {
476
- this._rowSelection();
477
- const selected = this._table.getSelectedRowModel().rows.map((row) => row.original);
478
- this.selectionChange.emit(selected);
512
+ const selected = this._selectedRows();
513
+ // Consumer handlers may read signals; those reads must not retrigger us.
514
+ untracked(() => this.selectionChange.emit(selected));
479
515
  }, /* @ts-ignore */
480
516
  ...(ngDevMode ? [{ debugName: "_emitSelection" }] : /* istanbul ignore next */ []));
481
517
  /**
@@ -488,7 +524,7 @@ class EgDataTable {
488
524
  this._sorting.set([...this.initialSorting()]);
489
525
  this._columnFilters.set([...this.initialColumnFilters()]);
490
526
  this._columnVisibility.set({ ...this.initialColumnVisibility() });
491
- this._rowSelection.set({ ...this.initialRowSelection() });
527
+ this._rowSelection.set(this._keepCurrentSelection(this.initialRowSelection()));
492
528
  }
493
529
  _hasGrid = computed(() => this.gridColumns() !== undefined, /* @ts-ignore */
494
530
  ...(ngDevMode ? [{ debugName: "_hasGrid" }] : /* istanbul ignore next */ []));
@@ -568,7 +604,7 @@ class EgDataTable {
568
604
  return cell.column.id.replace(/_/g, ' ');
569
605
  }
570
606
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: EgDataTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
571
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgDataTable, isStandalone: true, selector: "eg-data-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, gridColumns: { classPropertyName: "gridColumns", publicName: "gridColumns", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, manualPagination: { classPropertyName: "manualPagination", publicName: "manualPagination", isSignal: true, isRequired: false, transformFunction: null }, features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, enableSelection: { classPropertyName: "enableSelection", publicName: "enableSelection", isSignal: true, isRequired: false, transformFunction: null }, hideFooter: { classPropertyName: "hideFooter", publicName: "hideFooter", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, showLayoutToggle: { classPropertyName: "showLayoutToggle", publicName: "showLayoutToggle", isSignal: true, isRequired: false, transformFunction: null }, showToolbar: { classPropertyName: "showToolbar", publicName: "showToolbar", isSignal: true, isRequired: false, transformFunction: null }, filterColumnId: { classPropertyName: "filterColumnId", publicName: "filterColumnId", isSignal: true, isRequired: false, transformFunction: null }, filterPlaceholder: { classPropertyName: "filterPlaceholder", publicName: "filterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, showColumnToggle: { classPropertyName: "showColumnToggle", publicName: "showColumnToggle", isSignal: true, isRequired: false, transformFunction: null }, toolbarActions: { classPropertyName: "toolbarActions", publicName: "toolbarActions", isSignal: true, isRequired: false, transformFunction: null }, defaultPageSize: { classPropertyName: "defaultPageSize", publicName: "defaultPageSize", isSignal: true, isRequired: false, transformFunction: null }, filterRows: { classPropertyName: "filterRows", publicName: "filterRows", isSignal: true, isRequired: false, transformFunction: null }, initialSorting: { classPropertyName: "initialSorting", publicName: "initialSorting", isSignal: true, isRequired: false, transformFunction: null }, initialColumnFilters: { classPropertyName: "initialColumnFilters", publicName: "initialColumnFilters", isSignal: true, isRequired: false, transformFunction: null }, initialColumnVisibility: { classPropertyName: "initialColumnVisibility", publicName: "initialColumnVisibility", isSignal: true, isRequired: false, transformFunction: null }, initialRowSelection: { classPropertyName: "initialRowSelection", publicName: "initialRowSelection", isSignal: true, isRequired: false, transformFunction: null }, showPageSize: { classPropertyName: "showPageSize", publicName: "showPageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizes: { classPropertyName: "pageSizes", publicName: "pageSizes", isSignal: true, isRequired: false, transformFunction: null }, navMode: { classPropertyName: "navMode", publicName: "navMode", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { layout: "layoutChange", selectionChange: "selectionChange", pageChange: "pageChange", pageSizeChange: "pageSizeChange", rowClick: "rowClick", sortingChange: "sortingChange", columnFiltersChange: "columnFiltersChange", filterChange: "filterChange", columnVisibilityChange: "columnVisibilityChange" }, providers: [
607
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: EgDataTable, isStandalone: true, selector: "eg-data-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, gridColumns: { classPropertyName: "gridColumns", publicName: "gridColumns", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, getRowId: { classPropertyName: "getRowId", publicName: "getRowId", isSignal: true, isRequired: false, transformFunction: null }, manualPagination: { classPropertyName: "manualPagination", publicName: "manualPagination", isSignal: true, isRequired: false, transformFunction: null }, features: { classPropertyName: "features", publicName: "features", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, enableSelection: { classPropertyName: "enableSelection", publicName: "enableSelection", isSignal: true, isRequired: false, transformFunction: null }, hideFooter: { classPropertyName: "hideFooter", publicName: "hideFooter", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, showLayoutToggle: { classPropertyName: "showLayoutToggle", publicName: "showLayoutToggle", isSignal: true, isRequired: false, transformFunction: null }, showToolbar: { classPropertyName: "showToolbar", publicName: "showToolbar", isSignal: true, isRequired: false, transformFunction: null }, filterColumnId: { classPropertyName: "filterColumnId", publicName: "filterColumnId", isSignal: true, isRequired: false, transformFunction: null }, filterPlaceholder: { classPropertyName: "filterPlaceholder", publicName: "filterPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, showColumnToggle: { classPropertyName: "showColumnToggle", publicName: "showColumnToggle", isSignal: true, isRequired: false, transformFunction: null }, toolbarActions: { classPropertyName: "toolbarActions", publicName: "toolbarActions", isSignal: true, isRequired: false, transformFunction: null }, defaultPageSize: { classPropertyName: "defaultPageSize", publicName: "defaultPageSize", isSignal: true, isRequired: false, transformFunction: null }, filterRows: { classPropertyName: "filterRows", publicName: "filterRows", isSignal: true, isRequired: false, transformFunction: null }, initialSorting: { classPropertyName: "initialSorting", publicName: "initialSorting", isSignal: true, isRequired: false, transformFunction: null }, initialColumnFilters: { classPropertyName: "initialColumnFilters", publicName: "initialColumnFilters", isSignal: true, isRequired: false, transformFunction: null }, initialColumnVisibility: { classPropertyName: "initialColumnVisibility", publicName: "initialColumnVisibility", isSignal: true, isRequired: false, transformFunction: null }, initialRowSelection: { classPropertyName: "initialRowSelection", publicName: "initialRowSelection", isSignal: true, isRequired: false, transformFunction: null }, showPageSize: { classPropertyName: "showPageSize", publicName: "showPageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizes: { classPropertyName: "pageSizes", publicName: "pageSizes", isSignal: true, isRequired: false, transformFunction: null }, navMode: { classPropertyName: "navMode", publicName: "navMode", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { layout: "layoutChange", selectionChange: "selectionChange", pageChange: "pageChange", pageSizeChange: "pageSizeChange", rowClick: "rowClick", sortingChange: "sortingChange", columnFiltersChange: "columnFiltersChange", filterChange: "filterChange", columnVisibilityChange: "columnVisibilityChange" }, providers: [
572
608
  provideIcons({ lucideArrowDown, lucideArrowUp, lucideChevronsUpDown, lucideLayoutGrid, lucideLayoutList }),
573
609
  ], ngImport: i0, template: "\n <div [class]=\"_containerClass()\">\n @if (_showLayoutToggle()) {\n <div class=\"flex items-center justify-between\">\n <div hlmToggleGroup type=\"single\" [value]=\"_effectiveLayout()\" (valueChange)=\"_onLayoutToggle($event)\">\n <button hlmToggleGroupItem value=\"table\" aria-label=\"Table view\">\n <ng-icon hlm name=\"lucideLayoutList\" />\n </button>\n <button hlmToggleGroupItem value=\"grid\" aria-label=\"Grid view\">\n <ng-icon hlm name=\"lucideLayoutGrid\" />\n </button>\n </div>\n <div></div>\n </div>\n }\n\n @if (showToolbar() || toolbarActions()) {\n <div class=\"flex flex-wrap items-center justify-between gap-2 py-1\" [tanStackTable]=\"_table\">\n @if (showToolbar()) {\n <div class=\"flex flex-wrap items-center gap-2\">\n <input\n hlmInput\n class=\"w-full min-w-0 md:w-80\"\n [size]=\"size()\"\n [placeholder]=\"filterPlaceholder()\"\n (input)=\"_onFilterInput($event)\"\n />\n @if (showColumnToggle()) {\n <eg-data-table-view-options [size]=\"size()\" />\n }\n </div>\n }\n @if (toolbarActions()) {\n <div class=\"flex flex-wrap items-center gap-2\">\n <ng-container *ngTemplateOutlet=\"toolbarActions()\" />\n </div>\n }\n </div>\n }\n\n @if (_effectiveLayout() === 'grid') {\n <ng-container *ngTemplateOutlet=\"_gridView\" />\n } @else {\n <ng-container *ngTemplateOutlet=\"_tableView\" />\n }\n\n <ng-template #_tableView>\n <div class=\"overflow-hidden rounded-md border\">\n <div hlmTableContainer>\n <table hlmTable>\n <thead hlmTHead>\n @for (headerGroup of _table.getHeaderGroups(); track headerGroup.id) {\n <tr hlmTr>\n @for (header of headerGroup.headers; track header.id) {\n <th hlmTh [size]=\"size()\" [attr.colSpan]=\"header.colSpan\">\n @if (!header.isPlaceholder) {\n <div class=\"flex items-center gap-1\">\n <ng-container\n *flexRender=\"header.column.columnDef.header; props: header.getContext(); let headerText\"\n >\n <div [innerHTML]=\"headerText\"></div>\n </ng-container>\n @if (_showHeaderSortButton(header)) {\n <button\n hlmButton\n variant=\"ghost\"\n size=\"sm\"\n [class]=\"_sortButtonClass()\"\n type=\"button\"\n [attr.aria-label]=\"'Sort by ' + header.column.id\"\n (click)=\"_toggleHeaderSorting(header)\"\n >\n @switch (header.column.getIsSorted()) {\n @case ('asc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowUp\" />\n }\n @case ('desc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowDown\" />\n }\n @default {\n <ng-icon hlm size=\"sm\" name=\"lucideChevronsUpDown\" />\n }\n }\n </button>\n }\n </div>\n }\n </th>\n }\n </tr>\n }\n </thead>\n <tbody hlmTBody>\n @if (isLoading()) {\n <tr hlmTr>\n <td hlmTd [attr.colspan]=\"_visibleColumnCount()\" class=\"h-24 text-center\">\n <hlm-spinner class=\"inline-flex\" />\n </td>\n </tr>\n } @else if (_table.getRowModel().rows.length === 0) {\n <tr hlmTr>\n <td hlmTd [attr.colspan]=\"_visibleColumnCount()\" class=\"h-24 text-center\">\n {{ emptyMessage() }}\n </td>\n </tr>\n } @else {\n @for (row of _table.getRowModel().rows; track row.id) {\n <tr\n hlmTr\n [attr.data-state]=\"row.getIsSelected() ? 'selected' : null\"\n (click)=\"_onRowClick($event, row)\"\n >\n @for (cell of row.getVisibleCells(); track cell.id) {\n <td hlmTd [size]=\"size()\">\n <ng-container\n *flexRender=\"cell.column.columnDef.cell; props: cell.getContext(); let cellText\"\n >\n <div [innerHTML]=\"cellText\"></div>\n </ng-container>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n </div>\n </ng-template>\n\n <ng-template #_gridView>\n @if (isLoading()) {\n <div\n class=\"flex h-64 items-center justify-center rounded-lg border border-dashed\"\n role=\"status\"\n >\n <hlm-spinner />\n </div>\n } @else if (_table.getRowModel().rows.length === 0) {\n <div\n class=\"text-muted-foreground flex h-64 items-center justify-center rounded-lg border border-dashed\"\n >\n {{ emptyMessage() }}\n </div>\n } @else {\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4\">\n @for (row of _table.getRowModel().rows; track row.id) {\n <div\n hlmCard\n class=\"group relative transition-all hover:shadow-md data-[state=selected]:border-primary data-[state=selected]:ring-1 data-[state=selected]:ring-primary/20\"\n [attr.data-state]=\"row.getIsSelected() ? 'selected' : null\"\n (click)=\"_onRowClick($event, row)\"\n >\n @if (_thumbnailCell(row); as thumbnail) {\n <div class=\"border-b bg-muted/30\">\n <ng-container\n *flexRender=\"thumbnail.column.columnDef.cell; props: thumbnail.getContext(); let thumbHtml\"\n >\n <div [innerHTML]=\"thumbHtml\"></div>\n </ng-container>\n </div>\n }\n @if (_selectionCell(row); as selection) {\n <div class=\"absolute top-4 right-4\">\n <ng-container\n *flexRender=\"selection.column.columnDef.cell; props: selection.getContext(); let selectHtml\"\n >\n <div [innerHTML]=\"selectHtml\"></div>\n </ng-container>\n </div>\n }\n <div hlmCardContent class=\"space-y-3\">\n @for (cell of _contentCells(row); track cell.id) {\n <div class=\"flex flex-col\">\n <dt\n class=\"text-[10px] font-bold tracking-wider uppercase text-muted-foreground/80\"\n >\n {{ _cellLabel(cell) }}\n </dt>\n <dd class=\"mt-0.5 text-sm leading-none font-medium\">\n <ng-container *flexRender=\"cell.column.columnDef.cell; props: cell.getContext(); let valueHtml\">\n <div [innerHTML]=\"valueHtml\"></div>\n </ng-container>\n </dd>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n </ng-template>\n\n @if (!hideFooter()) {\n <div\n class=\"flex flex-col gap-2 px-2 py-4 sm:flex-row sm:items-center sm:justify-between\"\n [tanStackTable]=\"_table\"\n >\n <div class=\"text-muted-foreground flex-1 text-sm\">{{ _statusText() }}</div>\n <eg-data-table-pagination\n [showStatus]=\"false\"\n [showPageSize]=\"showPageSize()\"\n [pageSizes]=\"pageSizes()\"\n [navMode]=\"navMode()\"\n [size]=\"size()\"\n />\n </div>\n }\n </div>\n ", isInline: true, dependencies: [{ kind: "directive", type: i1$2.FlexRenderDirective, selector: "ng-template[flexRender]", inputs: ["flexRender", "flexRenderProps", "flexRenderInjector"] }, { kind: "directive", type: TanStackTable, selector: "[tanStackTable]", inputs: ["tanStackTable"], exportAs: ["table"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.HlmTableContainer, selector: "div[hlmTableContainer]" }, { kind: "directive", type: i2.HlmTable, selector: "table[hlmTable]" }, { kind: "directive", type: i2.HlmTBody, selector: "tbody[hlmTBody],tbody[hlmTableBody]" }, { kind: "directive", type: i2.HlmTd, selector: "td[hlmTd],td[hlmTableCell]", inputs: ["size"] }, { kind: "directive", type: i2.HlmTh, selector: "th[hlmTh],th[hlmTableHead]", inputs: ["size"] }, { kind: "directive", type: i2.HlmTHead, selector: "thead[hlmTHead],thead[hlmTableHeader]" }, { kind: "directive", type: i2.HlmTr, selector: "tr[hlmTr],tr[hlmTableRow]" }, { kind: "component", type: HlmButton, selector: "button[hlmButton], a[hlmButton]", inputs: ["variant", "size", "appearance", "loading", "icon", "iconPosition", "class", "spinnerUserClass", "disabled", "type"] }, { kind: "directive", type: HlmCard, selector: "[hlmCard],hlm-card", inputs: ["size"] }, { kind: "directive", type: HlmCardContent, selector: "[hlmCardContent]" }, { kind: "directive", type: HlmIcon, selector: "ng-icon[hlm]", inputs: ["size"] }, { kind: "directive", type: HlmInput, selector: "[hlmInput]", inputs: ["ariaDescribedby", "size"] }, { kind: "component", type: HlmSpinner, selector: "hlm-spinner", inputs: ["size", "show", "class", "spinnerClass"] }, { kind: "directive", type: HlmToggleGroup, selector: "[hlmToggleGroup],hlm-toggle-group", inputs: ["variant", "size", "spacing", "orientation"] }, { kind: "directive", type: HlmToggleGroupItem, selector: "button[hlmToggleGroupItem]", inputs: ["variant", "size"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: EgDataTablePagination, selector: "eg-data-table-pagination", inputs: ["showStatus", "showPageSize", "pageSizes", "navMode", "size"] }, { kind: "component", type: EgDataTableViewOptions, selector: "eg-data-table-view-options", inputs: ["size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
574
610
  }
@@ -599,7 +635,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImpor
599
635
  template: "\n <div [class]=\"_containerClass()\">\n @if (_showLayoutToggle()) {\n <div class=\"flex items-center justify-between\">\n <div hlmToggleGroup type=\"single\" [value]=\"_effectiveLayout()\" (valueChange)=\"_onLayoutToggle($event)\">\n <button hlmToggleGroupItem value=\"table\" aria-label=\"Table view\">\n <ng-icon hlm name=\"lucideLayoutList\" />\n </button>\n <button hlmToggleGroupItem value=\"grid\" aria-label=\"Grid view\">\n <ng-icon hlm name=\"lucideLayoutGrid\" />\n </button>\n </div>\n <div></div>\n </div>\n }\n\n @if (showToolbar() || toolbarActions()) {\n <div class=\"flex flex-wrap items-center justify-between gap-2 py-1\" [tanStackTable]=\"_table\">\n @if (showToolbar()) {\n <div class=\"flex flex-wrap items-center gap-2\">\n <input\n hlmInput\n class=\"w-full min-w-0 md:w-80\"\n [size]=\"size()\"\n [placeholder]=\"filterPlaceholder()\"\n (input)=\"_onFilterInput($event)\"\n />\n @if (showColumnToggle()) {\n <eg-data-table-view-options [size]=\"size()\" />\n }\n </div>\n }\n @if (toolbarActions()) {\n <div class=\"flex flex-wrap items-center gap-2\">\n <ng-container *ngTemplateOutlet=\"toolbarActions()\" />\n </div>\n }\n </div>\n }\n\n @if (_effectiveLayout() === 'grid') {\n <ng-container *ngTemplateOutlet=\"_gridView\" />\n } @else {\n <ng-container *ngTemplateOutlet=\"_tableView\" />\n }\n\n <ng-template #_tableView>\n <div class=\"overflow-hidden rounded-md border\">\n <div hlmTableContainer>\n <table hlmTable>\n <thead hlmTHead>\n @for (headerGroup of _table.getHeaderGroups(); track headerGroup.id) {\n <tr hlmTr>\n @for (header of headerGroup.headers; track header.id) {\n <th hlmTh [size]=\"size()\" [attr.colSpan]=\"header.colSpan\">\n @if (!header.isPlaceholder) {\n <div class=\"flex items-center gap-1\">\n <ng-container\n *flexRender=\"header.column.columnDef.header; props: header.getContext(); let headerText\"\n >\n <div [innerHTML]=\"headerText\"></div>\n </ng-container>\n @if (_showHeaderSortButton(header)) {\n <button\n hlmButton\n variant=\"ghost\"\n size=\"sm\"\n [class]=\"_sortButtonClass()\"\n type=\"button\"\n [attr.aria-label]=\"'Sort by ' + header.column.id\"\n (click)=\"_toggleHeaderSorting(header)\"\n >\n @switch (header.column.getIsSorted()) {\n @case ('asc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowUp\" />\n }\n @case ('desc') {\n <ng-icon hlm size=\"sm\" name=\"lucideArrowDown\" />\n }\n @default {\n <ng-icon hlm size=\"sm\" name=\"lucideChevronsUpDown\" />\n }\n }\n </button>\n }\n </div>\n }\n </th>\n }\n </tr>\n }\n </thead>\n <tbody hlmTBody>\n @if (isLoading()) {\n <tr hlmTr>\n <td hlmTd [attr.colspan]=\"_visibleColumnCount()\" class=\"h-24 text-center\">\n <hlm-spinner class=\"inline-flex\" />\n </td>\n </tr>\n } @else if (_table.getRowModel().rows.length === 0) {\n <tr hlmTr>\n <td hlmTd [attr.colspan]=\"_visibleColumnCount()\" class=\"h-24 text-center\">\n {{ emptyMessage() }}\n </td>\n </tr>\n } @else {\n @for (row of _table.getRowModel().rows; track row.id) {\n <tr\n hlmTr\n [attr.data-state]=\"row.getIsSelected() ? 'selected' : null\"\n (click)=\"_onRowClick($event, row)\"\n >\n @for (cell of row.getVisibleCells(); track cell.id) {\n <td hlmTd [size]=\"size()\">\n <ng-container\n *flexRender=\"cell.column.columnDef.cell; props: cell.getContext(); let cellText\"\n >\n <div [innerHTML]=\"cellText\"></div>\n </ng-container>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n </div>\n </div>\n </ng-template>\n\n <ng-template #_gridView>\n @if (isLoading()) {\n <div\n class=\"flex h-64 items-center justify-center rounded-lg border border-dashed\"\n role=\"status\"\n >\n <hlm-spinner />\n </div>\n } @else if (_table.getRowModel().rows.length === 0) {\n <div\n class=\"text-muted-foreground flex h-64 items-center justify-center rounded-lg border border-dashed\"\n >\n {{ emptyMessage() }}\n </div>\n } @else {\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4\">\n @for (row of _table.getRowModel().rows; track row.id) {\n <div\n hlmCard\n class=\"group relative transition-all hover:shadow-md data-[state=selected]:border-primary data-[state=selected]:ring-1 data-[state=selected]:ring-primary/20\"\n [attr.data-state]=\"row.getIsSelected() ? 'selected' : null\"\n (click)=\"_onRowClick($event, row)\"\n >\n @if (_thumbnailCell(row); as thumbnail) {\n <div class=\"border-b bg-muted/30\">\n <ng-container\n *flexRender=\"thumbnail.column.columnDef.cell; props: thumbnail.getContext(); let thumbHtml\"\n >\n <div [innerHTML]=\"thumbHtml\"></div>\n </ng-container>\n </div>\n }\n @if (_selectionCell(row); as selection) {\n <div class=\"absolute top-4 right-4\">\n <ng-container\n *flexRender=\"selection.column.columnDef.cell; props: selection.getContext(); let selectHtml\"\n >\n <div [innerHTML]=\"selectHtml\"></div>\n </ng-container>\n </div>\n }\n <div hlmCardContent class=\"space-y-3\">\n @for (cell of _contentCells(row); track cell.id) {\n <div class=\"flex flex-col\">\n <dt\n class=\"text-[10px] font-bold tracking-wider uppercase text-muted-foreground/80\"\n >\n {{ _cellLabel(cell) }}\n </dt>\n <dd class=\"mt-0.5 text-sm leading-none font-medium\">\n <ng-container *flexRender=\"cell.column.columnDef.cell; props: cell.getContext(); let valueHtml\">\n <div [innerHTML]=\"valueHtml\"></div>\n </ng-container>\n </dd>\n </div>\n }\n </div>\n </div>\n }\n </div>\n }\n </ng-template>\n\n @if (!hideFooter()) {\n <div\n class=\"flex flex-col gap-2 px-2 py-4 sm:flex-row sm:items-center sm:justify-between\"\n [tanStackTable]=\"_table\"\n >\n <div class=\"text-muted-foreground flex-1 text-sm\">{{ _statusText() }}</div>\n <eg-data-table-pagination\n [showStatus]=\"false\"\n [showPageSize]=\"showPageSize()\"\n [pageSizes]=\"pageSizes()\"\n [navMode]=\"navMode()\"\n [size]=\"size()\"\n />\n </div>\n }\n </div>\n ",
600
636
  changeDetection: ChangeDetectionStrategy.OnPush,
601
637
  }]
602
- }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], gridColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridColumns", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], manualPagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "manualPagination", required: false }] }], features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: false }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], enableSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableSelection", required: false }] }], hideFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideFooter", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }, { type: i0.Output, args: ["layoutChange"] }], showLayoutToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLayoutToggle", required: false }] }], showToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToolbar", required: false }] }], filterColumnId: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterColumnId", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], showColumnToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnToggle", required: false }] }], toolbarActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbarActions", required: false }] }], defaultPageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultPageSize", required: false }] }], filterRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterRows", required: false }] }], initialSorting: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSorting", required: false }] }], initialColumnFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialColumnFilters", required: false }] }], initialColumnVisibility: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialColumnVisibility", required: false }] }], initialRowSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialRowSelection", required: false }] }], showPageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageSize", required: false }] }], pageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizes", required: false }] }], navMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "navMode", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], sortingChange: [{ type: i0.Output, args: ["sortingChange"] }], columnFiltersChange: [{ type: i0.Output, args: ["columnFiltersChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], columnVisibilityChange: [{ type: i0.Output, args: ["columnVisibilityChange"] }] } });
638
+ }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], gridColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridColumns", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], getRowId: [{ type: i0.Input, args: [{ isSignal: true, alias: "getRowId", required: false }] }], manualPagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "manualPagination", required: false }] }], features: [{ type: i0.Input, args: [{ isSignal: true, alias: "features", required: false }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], enableSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableSelection", required: false }] }], hideFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideFooter", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }, { type: i0.Output, args: ["layoutChange"] }], showLayoutToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLayoutToggle", required: false }] }], showToolbar: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToolbar", required: false }] }], filterColumnId: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterColumnId", required: false }] }], filterPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPlaceholder", required: false }] }], showColumnToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showColumnToggle", required: false }] }], toolbarActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "toolbarActions", required: false }] }], defaultPageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultPageSize", required: false }] }], filterRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterRows", required: false }] }], initialSorting: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialSorting", required: false }] }], initialColumnFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialColumnFilters", required: false }] }], initialColumnVisibility: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialColumnVisibility", required: false }] }], initialRowSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialRowSelection", required: false }] }], showPageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageSize", required: false }] }], pageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizes", required: false }] }], navMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "navMode", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], pageSizeChange: [{ type: i0.Output, args: ["pageSizeChange"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], sortingChange: [{ type: i0.Output, args: ["sortingChange"] }], columnFiltersChange: [{ type: i0.Output, args: ["columnFiltersChange"] }], filterChange: [{ type: i0.Output, args: ["filterChange"] }], columnVisibilityChange: [{ type: i0.Output, args: ["columnVisibilityChange"] }] } });
603
639
 
604
640
  /**
605
641
  * Sortable + hideable column header.
@@ -188,6 +188,15 @@ declare class EgDataTable<TData extends RowData> implements OnInit {
188
188
  }, TData>[] | undefined>;
189
189
  /** Client rows, or a server page slice when `manualPagination` is set (`null` while loading). */
190
190
  readonly data: _angular_core.InputSignal<EgPaginatedResponse<TData> | readonly TData[] | null>;
191
+ /**
192
+ * Pure stable identity callback, e.g. `(record) => record.id`. Return a unique
193
+ * string per entity, independent of position, and keep the callback reference stable.
194
+ * Selection survives refreshes only for IDs still in the supplied rows (after
195
+ * `filterRows`); server selection is limited to the loaded slice. Without this
196
+ * callback, IDs are positional and replacing the rows clears selection.
197
+ * Changing the callback clears selection. Update data immutably.
198
+ */
199
+ readonly getRowId: _angular_core.InputSignal<((row: TData) => string) | undefined>;
191
200
  /** When true, `data` is a server page slice and the table uses TanStack `manualPagination`. Sorting and filtering are then display-only: the slice is shown as-is while `sortingChange` / `filterChange` (`columnFiltersChange`) notify the caller to refetch. */
192
201
  readonly manualPagination: _angular_core.InputSignal<boolean>;
193
202
  /** TanStack feature registry. Defaults to filtering + visibility + pagination + selection + sorting. */
@@ -242,7 +251,7 @@ declare class EgDataTable<TData extends RowData> implements OnInit {
242
251
  readonly initialColumnFilters: _angular_core.InputSignal<ColumnFiltersState>;
243
252
  /** Seeds the column-visibility state once at creation. Later changes are ignored. */
244
253
  readonly initialColumnVisibility: _angular_core.InputSignal<ColumnVisibilityState>;
245
- /** Seeds the row-selection state once at creation (keys are row ids). Later changes are ignored. */
254
+ /** Seeds selection once for current rows: keys come from `getRowId`, or positional indices without it. Missing IDs are discarded. Later changes are ignored. */
246
255
  readonly initialRowSelection: _angular_core.InputSignal<RowSelectionState>;
247
256
  /** Show the rows-per-page selector in the footer pager. */
248
257
  readonly showPageSize: _angular_core.InputSignal<boolean>;
@@ -251,7 +260,7 @@ declare class EgDataTable<TData extends RowData> implements OnInit {
251
260
  /** Footer navigation-button content: icons only, text labels only, or both. */
252
261
  readonly navMode: _angular_core.InputSignal<EgPaginationNavMode>;
253
262
  readonly userClass: _angular_core.InputSignal<string>;
254
- /** Emits the selected rows whenever selection changes (requires `enableSelection`). */
263
+ /** Emits on init and when selected row identities/order or object references change. Includes rows hidden by built-in filters/client pagination, but never rows absent from `data`/`filterRows`. */
255
264
  readonly selectionChange: _angular_core.OutputEmitterRef<readonly TData[]>;
256
265
  /** Emits the target page index on pager navigation (primary hook for server fetching). */
257
266
  readonly pageChange: _angular_core.OutputEmitterRef<number>;
@@ -269,13 +278,15 @@ declare class EgDataTable<TData extends RowData> implements OnInit {
269
278
  private readonly _sorting;
270
279
  private readonly _columnFilters;
271
280
  private readonly _columnVisibility;
272
- private readonly _rowSelection;
273
281
  private readonly _clientPageIndex;
274
282
  private readonly _clientPageSize;
275
283
  private readonly _isServer;
276
284
  /** The server page slice when in server mode; `null` for client data, misuse shapes, or `null` while loading. */
277
285
  private readonly _page;
278
286
  private readonly _items;
287
+ private readonly _tableItems;
288
+ private readonly _rowSelection;
289
+ private _keepCurrentSelection;
279
290
  private readonly _serverPageCount;
280
291
  private readonly _serverPageIndex;
281
292
  private readonly _paginationState;
@@ -323,6 +334,7 @@ declare class EgDataTable<TData extends RowData> implements OnInit {
323
334
  readonly thumbnail?: boolean;
324
335
  };
325
336
  }, TData>;
337
+ private readonly _selectedRows;
326
338
  private readonly _emitSelection;
327
339
  /**
328
340
  * Seeds internal state from the `initial*` inputs. Runs in `ngOnInit` (after
@@ -352,7 +364,7 @@ declare class EgDataTable<TData extends RowData> implements OnInit {
352
364
  protected _contentCells(row: Row<EgDataTableFeatures, TData>): Cell<EgDataTableFeatures, TData, CellData>[];
353
365
  protected _cellLabel(cell: Cell<EgDataTableFeatures, TData, CellData>): string;
354
366
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<EgDataTable<any>, never>;
355
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgDataTable<any>, "eg-data-table", never, { "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "gridColumns": { "alias": "gridColumns"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; "manualPagination": { "alias": "manualPagination"; "required": false; "isSignal": true; }; "features": { "alias": "features"; "required": false; "isSignal": true; }; "isLoading": { "alias": "isLoading"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "enableSelection": { "alias": "enableSelection"; "required": false; "isSignal": true; }; "hideFooter": { "alias": "hideFooter"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "showLayoutToggle": { "alias": "showLayoutToggle"; "required": false; "isSignal": true; }; "showToolbar": { "alias": "showToolbar"; "required": false; "isSignal": true; }; "filterColumnId": { "alias": "filterColumnId"; "required": false; "isSignal": true; }; "filterPlaceholder": { "alias": "filterPlaceholder"; "required": false; "isSignal": true; }; "showColumnToggle": { "alias": "showColumnToggle"; "required": false; "isSignal": true; }; "toolbarActions": { "alias": "toolbarActions"; "required": false; "isSignal": true; }; "defaultPageSize": { "alias": "defaultPageSize"; "required": false; "isSignal": true; }; "filterRows": { "alias": "filterRows"; "required": false; "isSignal": true; }; "initialSorting": { "alias": "initialSorting"; "required": false; "isSignal": true; }; "initialColumnFilters": { "alias": "initialColumnFilters"; "required": false; "isSignal": true; }; "initialColumnVisibility": { "alias": "initialColumnVisibility"; "required": false; "isSignal": true; }; "initialRowSelection": { "alias": "initialRowSelection"; "required": false; "isSignal": true; }; "showPageSize": { "alias": "showPageSize"; "required": false; "isSignal": true; }; "pageSizes": { "alias": "pageSizes"; "required": false; "isSignal": true; }; "navMode": { "alias": "navMode"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; }, { "layout": "layoutChange"; "selectionChange": "selectionChange"; "pageChange": "pageChange"; "pageSizeChange": "pageSizeChange"; "rowClick": "rowClick"; "sortingChange": "sortingChange"; "columnFiltersChange": "columnFiltersChange"; "filterChange": "filterChange"; "columnVisibilityChange": "columnVisibilityChange"; }, never, never, true, never>;
367
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<EgDataTable<any>, "eg-data-table", never, { "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "gridColumns": { "alias": "gridColumns"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; "getRowId": { "alias": "getRowId"; "required": false; "isSignal": true; }; "manualPagination": { "alias": "manualPagination"; "required": false; "isSignal": true; }; "features": { "alias": "features"; "required": false; "isSignal": true; }; "isLoading": { "alias": "isLoading"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "enableSelection": { "alias": "enableSelection"; "required": false; "isSignal": true; }; "hideFooter": { "alias": "hideFooter"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "showLayoutToggle": { "alias": "showLayoutToggle"; "required": false; "isSignal": true; }; "showToolbar": { "alias": "showToolbar"; "required": false; "isSignal": true; }; "filterColumnId": { "alias": "filterColumnId"; "required": false; "isSignal": true; }; "filterPlaceholder": { "alias": "filterPlaceholder"; "required": false; "isSignal": true; }; "showColumnToggle": { "alias": "showColumnToggle"; "required": false; "isSignal": true; }; "toolbarActions": { "alias": "toolbarActions"; "required": false; "isSignal": true; }; "defaultPageSize": { "alias": "defaultPageSize"; "required": false; "isSignal": true; }; "filterRows": { "alias": "filterRows"; "required": false; "isSignal": true; }; "initialSorting": { "alias": "initialSorting"; "required": false; "isSignal": true; }; "initialColumnFilters": { "alias": "initialColumnFilters"; "required": false; "isSignal": true; }; "initialColumnVisibility": { "alias": "initialColumnVisibility"; "required": false; "isSignal": true; }; "initialRowSelection": { "alias": "initialRowSelection"; "required": false; "isSignal": true; }; "showPageSize": { "alias": "showPageSize"; "required": false; "isSignal": true; }; "pageSizes": { "alias": "pageSizes"; "required": false; "isSignal": true; }; "navMode": { "alias": "navMode"; "required": false; "isSignal": true; }; "userClass": { "alias": "class"; "required": false; "isSignal": true; }; }, { "layout": "layoutChange"; "selectionChange": "selectionChange"; "pageChange": "pageChange"; "pageSizeChange": "pageSizeChange"; "rowClick": "rowClick"; "sortingChange": "sortingChange"; "columnFiltersChange": "columnFiltersChange"; "filterChange": "filterChange"; "columnVisibilityChange": "columnVisibilityChange"; }, never, never, true, never>;
356
368
  }
357
369
 
358
370
  /**
@@ -135,6 +135,38 @@ Real selectors (from source):
135
135
 
136
136
  ## API reference
137
137
 
138
+ ### User commits, invalid drafts, and programmatic writes
139
+
140
+ The single, range and multi text inputs commit on **blur or Enter**. Both parsed and transformed values
141
+ must have a finite `getTime` through the configured Spartan date adapter and satisfy inclusive whole-day
142
+ `min`/`max` bounds (`startOfDay(min)` through `endOfDay(max)`). Provide matching parsing, formatting and
143
+ pure transformation callbacks when using a custom adapter/date type. A transformed range must be ordered;
144
+ custom transforms may sort endpoints. The default range parser still treats one date as a same-day range,
145
+ but an explicitly unparseable second endpoint is rejected.
146
+
147
+ For multi selection, `maxSelection` caps the raw and transformed array counts. `minSelection` is the
148
+ calendar's **deselection floor**: reductions below it are rejected, while growth from empty is allowed.
149
+ Supply unique dates. When the calendar proposes resetting a full selection to one new date, that reset
150
+ must also satisfy the floor. Rejected calendar commits restore the previous selection without user emissions.
151
+
152
+ On rejection the committed value stays unchanged, no `dateChange`/CVA `onChange` fires, and the input
153
+ preserves editable text across blur, Enter and refocus. `inputInvalid()` exposes local rejection state;
154
+ the native input receives `aria-invalid="true"` and invalid styling, combined with `forceInvalid`.
155
+ The interaction touches the form control but does not install an Angular validator or overwrite its
156
+ errors: form validators continue to see the last committed value. If unresolved drafts should block
157
+ submission, include `inputInvalid()` in that decision. Use `ariaDescribedby` to associate explanatory text.
158
+
159
+ A successful text/calendar commit, explicit clear, or external value write clears the local rejection.
160
+ Empty text and the clear button bypass transforms and the selection floor, emitting `null` (single/range)
161
+ or `[]` (multi); whitespace is passed to the parser. Enter keeps edit formatting for a subsequent blur;
162
+ blur restores display formatting. Disabled/readonly text inputs do not commit or clear.
163
+
164
+ `updateDate(value)` returns a boolean indicating whether a user commit was accepted. Programmatic CVA
165
+ `writeValue` applies configured transforms without enforcing user constraints or emitting; it replaces
166
+ rejected drafts, including repeated same-value/null writes. `[date]` remains a direct, untransformed
167
+ standalone input. `reset()` is an explicit programmatic clear and emits. Month/year retains its existing
168
+ brain-input lifecycle; this constraint/draft contract applies to single, range and multi pickers.
169
+
138
170
  ### `HlmDatePicker` — `hlm-date-picker`
139
171
 
140
172
  Single-date picker. Generic `<T>` (usually `Date`). Implements `BrnDatePickerBase<T>` + `ControlValueAccessor`, hosts `BrnFieldControl`.
@@ -230,15 +262,15 @@ Outputs/methods mirror `HlmDatePicker` (`dateChange: T | null`, `updateDate`, `o
230
262
 
231
263
  `HlmDatePickerInput<T>`, `HlmDateMultiInput<T>`, `HlmDateRangeInput<T>`, `HlmMonthYearInput<T>` extend `BrnDateInput` (inheriting `placeholder`, `inputId`, disabled/invalid wiring, clear-button and calendar-button behavior) and add:
232
264
 
233
- | Input | Type | Default | Notes |
234
- | -------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------- |
235
- | `parseDate` | `(value: string) => T \| null` (or `T[] \| null` / `[T,T] \| null` per variant) | from matching `provideHlm*Config` | Return `null` for invalid text; picker clears while text is preserved. |
236
- | `formatInputDate` / `formatInputDates` | `(date) => string` | from config | Edit format shown while focused. |
237
- | `ariaLabel` / `ariaDescribedby` | `string` | — | Accessible labelling for the inner `<input>`. |
238
- | `name` | `string \| undefined` | — | Native `name` forwarded to the inner `<input>`. |
239
- | `readonly` | `boolean` | `false` | Native `readonly` forwarded to the inner `<input>`. |
240
- | `class` | `ClassValue` | `''` | Extra classes on the host group container. |
241
- | `inputClass` | `ClassValue` | `''` | Extra classes on the inner `<input>` (e.g. text size). |
265
+ | Input | Type | Default | Notes |
266
+ | -------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------- |
267
+ | `parseDate` | `(value: string) => T \| null` (or `T[] \| null` / `[T,T] \| null` per variant) | from matching `provideHlm*Config` | Return `null` for invalid text; single/range/multi preserve the committed value and rejected text. |
268
+ | `formatInputDate` / `formatInputDates` | `(date) => string` | from config | Edit format shown while focused. |
269
+ | `ariaLabel` / `ariaDescribedby` | `string` | — | Accessible labelling for the inner `<input>`. |
270
+ | `name` | `string \| undefined` | — | Native `name` forwarded to the inner `<input>`. |
271
+ | `readonly` | `boolean` | `false` | Native `readonly` forwarded to the inner `<input>`. |
272
+ | `class` | `ClassValue` | `''` | Extra classes on the host group container. |
273
+ | `inputClass` | `ClassValue` | `''` | Extra classes on the inner `<input>` (e.g. text size). |
242
274
 
243
275
  ### `HlmDatePickerTrigger` — `hlm-date-picker-trigger`
244
276