forty-cdk 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, booleanAttribute, inject, isSignal, signal, computed, input, model, ElementRef, Directive, TemplateRef, contentChild, Injector, DOCUMENT, output, PLATFORM_ID, DestroyRef, effect, afterNextRender, numberAttribute, contentChildren, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { InjectionToken, booleanAttribute, inject, isSignal, signal, computed, input, model, ElementRef, Directive, TemplateRef, contentChild, Injector, PLATFORM_ID, output, DestroyRef, DOCUMENT, effect, afterNextRender, numberAttribute, viewChildren, afterEveryRender, contentChildren, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { injectTextDirection, injectElementSize, Collection, RovingTabindex, firstEnabledHost, computeFlatHierarchy, reconcileRovingActive, findFirstFocusable, resolveTreegridExpandCollapse, resolveGridNavigation, moveGridIndex, registerHandle, createPointerDragSession, DRAG_DEAD_ZONE_PX, clampToRange, translateWindowReorder, LiveAnnouncer, createKeyboardDragMediator, isDragLiftKey, resolveLiftedDragControl, resolveScrubReorder } from 'forty-cdk/core';
4
4
  import { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
5
5
  import * as i1 from 'forty-cdk/drag-drop';
6
- import { ForDropList, moveItemInArray, FOR_DROP_LIST_DEFAULT_ORIENTATION, FOR_DROP_LIST_ROVING_DELEGATE, FOR_DRAGGABLE_LIFT_GUARD, FOR_DRAG_DROP_DEFAULTS } from 'forty-cdk/drag-drop';
6
+ import { ForDropList, moveItemInArray, FOR_DROP_LIST_DEFAULT_ORIENTATION, FOR_DROP_LIST_ROVING_DELEGATE, FOR_DRAGGABLE_LIFT_GUARD, ForDraggable, ForDragPlaceholder, FOR_DRAG_DROP_DEFAULTS } from 'forty-cdk/drag-drop';
7
7
 
8
8
  const FOR_TABLE_CONTEXT = new InjectionToken('FOR_TABLE_CONTEXT');
9
9
  const FOR_TABLE_ROW_CONTEXT = new InjectionToken('FOR_TABLE_ROW_CONTEXT');
@@ -346,12 +346,25 @@ class ForTable {
346
346
  _dirInput = input(null, { ...(ngDevMode ? { debugName: "_dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
347
347
  dir = injectTextDirection(this._dirInput);
348
348
  /**
349
- * True total number of data rows for `aria-rowcount`, e.g. for a server-paged or
350
- * (later) virtualized table that renders only a window of rows. Defaults to the
351
- * rendered data-row count. Ignored in `mode="table"`.
349
+ * Explicit override for the true total data-row count (`aria-rowcount` and the
350
+ * virtualized scroll range). A declarative `<for-table-body>` supplies this
351
+ * automatically from its `rows` dataset length, so bind `[rowCount]` only for a
352
+ * server-known total larger than the loaded rows; when set it wins over the
353
+ * body-derived count. Defaults to the body count, else the rendered data-row
354
+ * count. Ignored in `mode="table"`.
352
355
  */
353
- rowCount = input(/* @ts-ignore */
354
- ...(ngDevMode ? [undefined, { debugName: "rowCount" }] : /* istanbul ignore next */ []));
356
+ _rowCountInput = input(undefined, { ...(ngDevMode ? { debugName: "_rowCountInput" } : /* istanbul ignore next */ {}), alias: 'rowCount' });
357
+ /** The declarative body's dataset length, registered by `<for-table-body>`; `null` when none. */
358
+ #bodyRowCount = signal(null, /* @ts-ignore */
359
+ ...(ngDevMode ? [{ debugName: "#bodyRowCount" }] : /* istanbul ignore next */ []));
360
+ /**
361
+ * Resolved true total data-row count: the explicit `[rowCount]` input when set,
362
+ * else the declarative `<for-table-body>`'s dataset length, else `undefined`
363
+ * (readers fall back to the rendered count). Feeds `aria-rowcount`, the
364
+ * cross-window navigation total, and the virtualizer's count.
365
+ */
366
+ rowCount = computed(() => this._rowCountInput() ?? this.#bodyRowCount()?.(), /* @ts-ignore */
367
+ ...(ngDevMode ? [{ debugName: "rowCount" }] : /* istanbul ignore next */ []));
355
368
  /**
356
369
  * True total number of columns for `aria-colcount`. Defaults to the rendered
357
370
  * column count (the cells of the first data row that has any). Ignored in
@@ -558,6 +571,9 @@ class ForTable {
558
571
  registerVirtualWindow(window) {
559
572
  this.#virtualWindow.set(window);
560
573
  }
574
+ registerBodyRowCount(count) {
575
+ this.#bodyRowCount.set(count);
576
+ }
561
577
  setReorderingRow(index) {
562
578
  this.#reorderingRow.set(index);
563
579
  }
@@ -774,7 +790,7 @@ class ForTable {
774
790
  return Math.max(1, this.#rows.items().length);
775
791
  }
776
792
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTable, deps: [], target: i0.ɵɵFactoryTarget.Directive });
777
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTable, isStandalone: true, selector: "[forTable]", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, rowCount: { classPropertyName: "rowCount", publicName: "rowCount", isSignal: true, isRequired: false, transformFunction: null }, colCount: { classPropertyName: "colCount", publicName: "colCount", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selectionBehavior: { classPropertyName: "selectionBehavior", publicName: "selectionBehavior", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, selectableValues: { classPropertyName: "selectableValues", publicName: "selectableValues", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", expanded: "expandedChange" }, host: { properties: { "attr.role": "mode()", "attr.aria-label": "ariaLabel() || null", "attr.dir": "dir()", "attr.data-mode": "mode()", "style.--for-table-header-height.px": "headerSize()?.height ?? null", "attr.aria-rowcount": "rowCountAttr()", "attr.aria-colcount": "colCountAttr()", "attr.aria-multiselectable": "selectionMode() === \"multiple\" ? \"true\" : null" } }, providers: [{ provide: FOR_TABLE_CONTEXT, useExisting: ForTable }], exportAs: ["forTable"], ngImport: i0 });
793
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTable, isStandalone: true, selector: "[forTable]", inputs: { mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, _rowCountInput: { classPropertyName: "_rowCountInput", publicName: "rowCount", isSignal: true, isRequired: false, transformFunction: null }, colCount: { classPropertyName: "colCount", publicName: "colCount", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, selectionBehavior: { classPropertyName: "selectionBehavior", publicName: "selectionBehavior", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, selectableValues: { classPropertyName: "selectableValues", publicName: "selectableValues", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", expanded: "expandedChange" }, host: { properties: { "attr.role": "mode()", "attr.aria-label": "ariaLabel() || null", "attr.dir": "dir()", "attr.data-mode": "mode()", "style.--for-table-header-height.px": "headerSize()?.height ?? null", "attr.aria-rowcount": "rowCountAttr()", "attr.aria-colcount": "colCountAttr()", "attr.aria-multiselectable": "selectionMode() === \"multiple\" ? \"true\" : null" } }, providers: [{ provide: FOR_TABLE_CONTEXT, useExisting: ForTable }], exportAs: ["forTable"], ngImport: i0 });
778
794
  }
779
795
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTable, decorators: [{
780
796
  type: Directive,
@@ -793,7 +809,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
793
809
  },
794
810
  providers: [{ provide: FOR_TABLE_CONTEXT, useExisting: ForTable }],
795
811
  }]
796
- }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], _dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], rowCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowCount", required: false }] }], colCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "colCount", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], selectionBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionBehavior", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], selectableValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectableValues", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }] } });
812
+ }], ctorParameters: () => [], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], _dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], _rowCountInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowCount", required: false }] }], colCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "colCount", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], selectionBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionBehavior", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], selectableValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectableValues", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }] } });
797
813
  /**
798
814
  * Resolves the absolute `(row, 0-based column)` target and travel `direction`
799
815
  * for a row-crossing grid action against the true `total` row count. Arrow
@@ -851,6 +867,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
851
867
  * Bind `[forDataCellRow]` to the same array passed to `ForTableBody`'s `rows`
852
868
  * to type `let-row` — the input is read only for type inference, never at
853
869
  * runtime.
870
+ *
871
+ * When the row type is a discriminated union whose variant members render
872
+ * through a `[forRowDef]` instead of the per-column cells, bind
873
+ * `[forDataCellUnless]` to the same type guard(s) used on those defs' `[when]`
874
+ * so `let-row` is narrowed to the variant-excluded members (`Exclude<T, V>`).
854
875
  */
855
876
  class ForDataCell {
856
877
  /** The captured data-cell template, typed with `ForDataCellContext<T>`. */
@@ -861,17 +882,26 @@ class ForDataCell {
861
882
  * directive never touches its value.
862
883
  */
863
884
  rowType = input([], { ...(ngDevMode ? { debugName: "rowType" } : /* istanbul ignore next */ {}), alias: 'forDataCellRow' });
885
+ /**
886
+ * Type-inference hint: bind the type guard(s) that match the variant rows
887
+ * rendered by `[forRowDef]` (the same predicate used on their `[when]`) so
888
+ * `let-row` is narrowed to `Exclude<T, V>` — the members this per-column
889
+ * template actually receives. Compose several variants into one union guard
890
+ * (`(r): r is A | B => …`). Read only by the compiler; the directive never
891
+ * touches its value. Omitting it leaves `let-row` typed as the full `T`.
892
+ */
893
+ excludeType = input(null, { ...(ngDevMode ? { debugName: "excludeType" } : /* istanbul ignore next */ {}), alias: 'forDataCellUnless' });
864
894
  /** Narrows the template context type for `let-row` under strict template checking. */
865
895
  static ngTemplateContextGuard(_directive, _context) {
866
896
  return true;
867
897
  }
868
898
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForDataCell, deps: [], target: i0.ɵɵFactoryTarget.Directive });
869
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForDataCell, isStandalone: true, selector: "ng-template[forDataCell]", inputs: { rowType: { classPropertyName: "rowType", publicName: "forDataCellRow", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
899
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForDataCell, isStandalone: true, selector: "ng-template[forDataCell]", inputs: { rowType: { classPropertyName: "rowType", publicName: "forDataCellRow", isSignal: true, isRequired: false, transformFunction: null }, excludeType: { classPropertyName: "excludeType", publicName: "forDataCellUnless", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
870
900
  }
871
901
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForDataCell, decorators: [{
872
902
  type: Directive,
873
903
  args: [{ selector: 'ng-template[forDataCell]' }]
874
- }], propDecorators: { rowType: [{ type: i0.Input, args: [{ isSignal: true, alias: "forDataCellRow", required: false }] }] } });
904
+ }], propDecorators: { rowType: [{ type: i0.Input, args: [{ isSignal: true, alias: "forDataCellRow", required: false }] }], excludeType: [{ type: i0.Input, args: [{ isSignal: true, alias: "forDataCellUnless", required: false }] }] } });
875
905
  /**
876
906
  * Marks the placeholder/skeleton template of a column definition. Optional;
877
907
  * place on an `<ng-template forPlaceholderCell>` inside a `[forColumnDef]`. When
@@ -889,6 +919,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
889
919
  type: Directive,
890
920
  args: [{ selector: 'ng-template[forPlaceholderCell]' }]
891
921
  }] });
922
+ /**
923
+ * Marks the shared drag placeholder for the reorderable columns of a
924
+ * `<for-table-body>`. Optional and declared **once per body** (not per column);
925
+ * place on an `<ng-template forColumnDragPlaceholder>` among the `[forColumnDef]`s.
926
+ * `ForTableBody` stamps it as every reorderable header cell's
927
+ * `[forDragPlaceholder]`, so during a pointer reorder the dragged column's slot
928
+ * shows this template. Omit it to keep drag-drop's default placeholder behaviour.
929
+ */
930
+ class ForColumnDragPlaceholder {
931
+ /** The captured placeholder template rendered in a reordered column's slot. */
932
+ template = inject(TemplateRef);
933
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForColumnDragPlaceholder, deps: [], target: i0.ɵɵFactoryTarget.Directive });
934
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForColumnDragPlaceholder, isStandalone: true, selector: "ng-template[forColumnDragPlaceholder]", ngImport: i0 });
935
+ }
936
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForColumnDragPlaceholder, decorators: [{
937
+ type: Directive,
938
+ args: [{ selector: 'ng-template[forColumnDragPlaceholder]' }]
939
+ }] });
892
940
  /**
893
941
  * Declarative definition of a single table column, co-locating its header,
894
942
  * data, and (optional) placeholder templates plus its per-column config in one
@@ -921,11 +969,22 @@ class ForColumnDef {
921
969
  */
922
970
  sortable = input(false, { ...(ngDevMode ? { debugName: "sortable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
923
971
  /**
924
- * When set, `ForTableBody` renders a `[forTableColumnResizer]` (with `autoFit`)
925
- * inside the column's header cell and re-emits its commits through the body's
926
- * `resizeCommit` output. Provide `resizeAriaLabel` so the handle is named.
972
+ * When set, `ForTableBody` renders a `[forTableColumnResizer]` inside the column's
973
+ * header cell and re-emits its commits through the body's `resizeCommit` output.
974
+ * Provide `resizeAriaLabel` so the handle is named. Tune the handle per column with
975
+ * `resizeMin` / `resizeMax` / `resizeStep` / `autoFit` / `fitIncludesHeader`, and
976
+ * seed / track its width through the body's `[(columnWidths)]`.
927
977
  */
928
978
  resizable = input(false, { ...(ngDevMode ? { debugName: "resizable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
979
+ /**
980
+ * When set, the column's header cell becomes a drag-reorder handle: with at least
981
+ * one `reorderable` column, `ForTableBody` applies `[forTableColumnReorder]` to the
982
+ * stamped header row and `[forDraggable]` (with `[dragData]` set to this column's
983
+ * `name`) to this header cell, and re-emits committed reorders through the body's
984
+ * `columnReorder` output. Non-reorderable columns stay static (not draggable). Note
985
+ * that any `reorderable` column makes the body bundle `forty-cdk/drag-drop`.
986
+ */
987
+ reorderable = input(false, { ...(ngDevMode ? { debugName: "reorderable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
929
988
  /**
930
989
  * Accessible name for the auto-wired resize handle (only meaningful with
931
990
  * `resizable`). Supplied by the consumer so it is localizable; `null` (default)
@@ -933,14 +992,70 @@ class ForColumnDef {
933
992
  */
934
993
  resizeAriaLabel = input(null, /* @ts-ignore */
935
994
  ...(ngDevMode ? [{ debugName: "resizeAriaLabel" }] : /* istanbul ignore next */ []));
995
+ /**
996
+ * Minimum width (px) the auto-wired resize handle clamps to (only meaningful with
997
+ * `resizable`). Forwarded to the stamped `[forTableColumnResizer]`'s `min`; drives
998
+ * its `aria-valuemin`. Default `0`.
999
+ */
1000
+ resizeMin = input(0, /* @ts-ignore */
1001
+ ...(ngDevMode ? [{ debugName: "resizeMin" }] : /* istanbul ignore next */ []));
1002
+ /**
1003
+ * Maximum width (px) the auto-wired resize handle clamps to (only meaningful with
1004
+ * `resizable`). Forwarded to the stamped `[forTableColumnResizer]`'s `max`; drives
1005
+ * its `aria-valuemax` (omitted when non-finite). Default `Infinity` (no upper bound).
1006
+ */
1007
+ resizeMax = input(Infinity, /* @ts-ignore */
1008
+ ...(ngDevMode ? [{ debugName: "resizeMax" }] : /* istanbul ignore next */ []));
1009
+ /**
1010
+ * Pixels applied per `ArrowLeft` / `ArrowRight` press on the auto-wired resize
1011
+ * handle (only meaningful with `resizable`). Forwarded to the stamped
1012
+ * `[forTableColumnResizer]`'s `step`. Default `10`.
1013
+ */
1014
+ resizeStep = input(10, /* @ts-ignore */
1015
+ ...(ngDevMode ? [{ debugName: "resizeStep" }] : /* istanbul ignore next */ []));
1016
+ /**
1017
+ * Whether double-clicking the auto-wired resize handle fits the column to its
1018
+ * widest content (only meaningful with `resizable`). Forwarded to the stamped
1019
+ * `[forTableColumnResizer]`'s `autoFit`. Default `true` — the historical
1020
+ * hardcoded behaviour; set `false` to make the double-click a no-op.
1021
+ */
1022
+ autoFit = input(true, { ...(ngDevMode ? { debugName: "autoFit" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1023
+ /**
1024
+ * Whether header-inclusive auto-fit also accounts for the column header's label
1025
+ * (only meaningful with `resizable` + `autoFit`). Forwarded to the stamped
1026
+ * `[forTableColumnResizer]`'s `fitIncludesHeader`; isolate the header text with a
1027
+ * `[forTableColumnLabel]` inside the `[forHeaderCell]` template. Default `false`.
1028
+ */
1029
+ fitIncludesHeader = input(false, { ...(ngDevMode ? { debugName: "fitIncludesHeader" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
936
1030
  /**
937
1031
  * `grid-template-columns` track fragment for this column (e.g. `'160px'`,
938
1032
  * `'minmax(160px, 1fr)'`). When unset, `ForTableBody` falls back to the
939
1033
  * published `--for-table-col-<name>-width` resize var with a `minmax(0, 1fr)`
940
- * default, so a resized column drives its own track.
1034
+ * default, so a resized column drives its own track. A static `width`
1035
+ * **takes precedence** over that resize var — so leave it unset on a
1036
+ * `resizable` column whose width you drive through `resizeCommit` or the
1037
+ * body's `[(columnWidths)]`, otherwise the pinned track ignores the resized
1038
+ * width (the handle still reports `aria-valuenow` but the column won't move).
941
1039
  */
942
1040
  width = input(null, /* @ts-ignore */
943
1041
  ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
1042
+ /**
1043
+ * Static class(es) applied to this column's stamped `[forTableHeaderCell]`.
1044
+ * `ForTableBody` owns the header cell element, so this is the styling seam a
1045
+ * consumer (or wrapping design system) uses to reach it without scoping CSS to
1046
+ * the body's template internals. `null` (default) adds no class attribute.
1047
+ */
1048
+ headerClass = input(null, /* @ts-ignore */
1049
+ ...(ngDevMode ? [{ debugName: "headerClass" }] : /* istanbul ignore next */ []));
1050
+ /**
1051
+ * Static class(es) applied to this column's stamped `[forTableCell]` on every
1052
+ * data **and** placeholder row. The styling seam for the cell box itself
1053
+ * (padding, truncation, alignment, sticky backgrounds) that `ForTableBody`
1054
+ * owns. Per-datum row styling is out of scope. `null` (default) adds no class
1055
+ * attribute.
1056
+ */
1057
+ cellClass = input(null, /* @ts-ignore */
1058
+ ...(ngDevMode ? [{ debugName: "cellClass" }] : /* istanbul ignore next */ []));
944
1059
  /** The column's header-cell template. */
945
1060
  header = contentChild.required(ForHeaderCell);
946
1061
  /** The column's data-cell template. */
@@ -949,12 +1064,12 @@ class ForColumnDef {
949
1064
  placeholderCell = contentChild(ForPlaceholderCell, /* @ts-ignore */
950
1065
  ...(ngDevMode ? [{ debugName: "placeholderCell" }] : /* istanbul ignore next */ []));
951
1066
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForColumnDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
952
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.2", type: ForColumnDef, isStandalone: true, selector: "[forColumnDef]", inputs: { name: { classPropertyName: "name", publicName: "forColumnDef", isSignal: true, isRequired: true, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, resizeAriaLabel: { classPropertyName: "resizeAriaLabel", publicName: "resizeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "header", first: true, predicate: ForHeaderCell, descendants: true, isSignal: true }, { propertyName: "dataCell", first: true, predicate: ForDataCell, descendants: true, isSignal: true }, { propertyName: "placeholderCell", first: true, predicate: ForPlaceholderCell, descendants: true, isSignal: true }], ngImport: i0 });
1067
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.2", type: ForColumnDef, isStandalone: true, selector: "[forColumnDef]", inputs: { name: { classPropertyName: "name", publicName: "forColumnDef", isSignal: true, isRequired: true, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null }, resizeAriaLabel: { classPropertyName: "resizeAriaLabel", publicName: "resizeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, resizeMin: { classPropertyName: "resizeMin", publicName: "resizeMin", isSignal: true, isRequired: false, transformFunction: null }, resizeMax: { classPropertyName: "resizeMax", publicName: "resizeMax", isSignal: true, isRequired: false, transformFunction: null }, resizeStep: { classPropertyName: "resizeStep", publicName: "resizeStep", isSignal: true, isRequired: false, transformFunction: null }, autoFit: { classPropertyName: "autoFit", publicName: "autoFit", isSignal: true, isRequired: false, transformFunction: null }, fitIncludesHeader: { classPropertyName: "fitIncludesHeader", publicName: "fitIncludesHeader", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, headerClass: { classPropertyName: "headerClass", publicName: "headerClass", isSignal: true, isRequired: false, transformFunction: null }, cellClass: { classPropertyName: "cellClass", publicName: "cellClass", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "header", first: true, predicate: ForHeaderCell, descendants: true, isSignal: true }, { propertyName: "dataCell", first: true, predicate: ForDataCell, descendants: true, isSignal: true }, { propertyName: "placeholderCell", first: true, predicate: ForPlaceholderCell, descendants: true, isSignal: true }], ngImport: i0 });
953
1068
  }
954
1069
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForColumnDef, decorators: [{
955
1070
  type: Directive,
956
1071
  args: [{ selector: '[forColumnDef]' }]
957
- }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "forColumnDef", required: true }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], resizeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeAriaLabel", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], header: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ForHeaderCell), { isSignal: true }] }], dataCell: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ForDataCell), { isSignal: true }] }], placeholderCell: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ForPlaceholderCell), { isSignal: true }] }] } });
1072
+ }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "forColumnDef", required: true }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], resizable: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizable", required: false }] }], reorderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderable", required: false }] }], resizeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeAriaLabel", required: false }] }], resizeMin: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeMin", required: false }] }], resizeMax: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeMax", required: false }] }], resizeStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeStep", required: false }] }], autoFit: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFit", required: false }] }], fitIncludesHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitIncludesHeader", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], headerClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerClass", required: false }] }], cellClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "cellClass", required: false }] }], header: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ForHeaderCell), { isSignal: true }] }], dataCell: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ForDataCell), { isSignal: true }] }], placeholderCell: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ForPlaceholderCell), { isSignal: true }] }] } });
958
1073
 
959
1074
  /**
960
1075
  * Marks the content template of a full-span row variant. Place on an
@@ -970,6 +1085,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
970
1085
  *
971
1086
  * Bind `[forRowCellRow]` to the same array passed to `ForTableBody`'s `rows` to
972
1087
  * type `let-row` — the input is read only for type inference, never at runtime.
1088
+ *
1089
+ * When the row type is a discriminated union, also bind `[forRowCellWhen]` to
1090
+ * the same type guard used on the def's `[when]` so `let-row` is narrowed to the
1091
+ * matched variant member (`V`) instead of staying the full union.
973
1092
  */
974
1093
  class ForRowCell {
975
1094
  /** The captured row-variant template, typed with `ForDataCellContext<T>`. */
@@ -980,31 +1099,48 @@ class ForRowCell {
980
1099
  * directive never touches its value.
981
1100
  */
982
1101
  rowType = input([], { ...(ngDevMode ? { debugName: "rowType" } : /* istanbul ignore next */ {}), alias: 'forRowCellRow' });
1102
+ /**
1103
+ * Type-inference hint: bind the same type guard used on this def's `[when]`
1104
+ * so `let-row` is narrowed to the matched variant member (`V`). Read only by
1105
+ * the compiler; the directive never touches its value. Omitting it leaves
1106
+ * `let-row` typed as the full `T`.
1107
+ */
1108
+ narrowType = input(null, { ...(ngDevMode ? { debugName: "narrowType" } : /* istanbul ignore next */ {}), alias: 'forRowCellWhen' });
983
1109
  /** Narrows the template context type for `let-row` under strict template checking. */
984
1110
  static ngTemplateContextGuard(_directive, _context) {
985
1111
  return true;
986
1112
  }
987
1113
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForRowCell, deps: [], target: i0.ɵɵFactoryTarget.Directive });
988
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForRowCell, isStandalone: true, selector: "ng-template[forRowCell]", inputs: { rowType: { classPropertyName: "rowType", publicName: "forRowCellRow", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
1114
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForRowCell, isStandalone: true, selector: "ng-template[forRowCell]", inputs: { rowType: { classPropertyName: "rowType", publicName: "forRowCellRow", isSignal: true, isRequired: false, transformFunction: null }, narrowType: { classPropertyName: "narrowType", publicName: "forRowCellWhen", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
989
1115
  }
990
1116
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForRowCell, decorators: [{
991
1117
  type: Directive,
992
1118
  args: [{ selector: 'ng-template[forRowCell]' }]
993
- }], propDecorators: { rowType: [{ type: i0.Input, args: [{ isSignal: true, alias: "forRowCellRow", required: false }] }] } });
1119
+ }], propDecorators: { rowType: [{ type: i0.Input, args: [{ isSignal: true, alias: "forRowCellRow", required: false }] }], narrowType: [{ type: i0.Input, args: [{ isSignal: true, alias: "forRowCellWhen", required: false }] }] } });
994
1120
  /**
995
- * Declarative definition of a full-span row variant for `<for-table-body>`. Place
1121
+ * Declarative definition of a row variant for `<for-table-body>`. Place
996
1122
  * `[forRowDef]` on an `<ng-container>` alongside the `[forColumnDef]`s and bind a
997
- * `[when]` predicate; for every datum the predicate matches, `ForTableBody` stamps
998
- * a row whose single cell spans all columns and renders the `[forRowCell]` template
999
- * instead of the per-column data cells. This covers group-header rows, section
1000
- * separators, and full-width summary / empty-state rows.
1123
+ * `[when]` predicate; for every datum the predicate matches, `ForTableBody` renders
1124
+ * this variant instead of the per-column data cells. A def comes in one of two
1125
+ * shapes, and must declare **exactly one** of them:
1001
1126
  *
1002
- * The variant row is **presentational**: its spanning cell carries the row's
1003
- * `role` (`gridcell` in grid / treegrid mode) plus `aria-colindex="1"` and
1004
- * `aria-colspan` equal to the column count, but it does not join the roving 2D
1005
- * navigation grid, so arrow-key navigation moves between the regular data cells
1006
- * and steps over variant rows (their `aria-rowindex` still counts them in reading
1007
- * order). Variant rows are also non-selectable.
1127
+ * - **Full-span** (a `[forRowCell]` template): the matched row's single cell spans
1128
+ * all columns and renders the template — group-header rows, section separators,
1129
+ * full-width summary / empty-state rows. The spanning cell carries the row's
1130
+ * `role` (`gridcell` in grid / treegrid mode) plus `aria-colindex="1"` and
1131
+ * `aria-colspan` equal to the column count, but it does **not** join the roving 2D
1132
+ * navigation grid (it registers no cell handle), so arrow-key navigation moves
1133
+ * between the regular data cells and steps over the variant row.
1134
+ * - **Placeholder cells** (the `placeholderCells` flag, no `[forRowCell]`): the
1135
+ * matched row stamps one cell per displayed column from each column's
1136
+ * `[forPlaceholderCell]` template — interleaved / trailing skeleton rows for
1137
+ * infinite-scroll or paginated tables. Unlike the full-span shape these keep the
1138
+ * roving grid **rectangular** (one cell per column), and the cells are stamped
1139
+ * disabled so grid-mode arrow navigation steps over them.
1140
+ *
1141
+ * Either way the variant row is **presentational** and **non-selectable** (its
1142
+ * `value` stays `undefined`), while still occupying a row slot and counting towards
1143
+ * `aria-rowindex` / `aria-rowcount` (reading order is preserved).
1008
1144
  *
1009
1145
  * When several `[forRowDef]`s match a datum, the first in DOM order wins. A datum
1010
1146
  * matched by no `[forRowDef]` renders the standard per-column row.
@@ -1015,11 +1151,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
1015
1151
  * <ng-container forColumnDef="name">
1016
1152
  * <ng-template forHeaderCell>Name</ng-template>
1017
1153
  * <ng-template forDataCell [forDataCellRow]="rows()" let-row>{{ row.name }}</ng-template>
1154
+ * <ng-template forPlaceholderCell><span class="skeleton"></span></ng-template>
1018
1155
  * </ng-container>
1019
1156
  *
1157
+ * <!-- full-span group header -->
1020
1158
  * <ng-container forRowDef [when]="isGroupHeader">
1021
1159
  * <ng-template forRowCell [forRowCellRow]="rows()" let-row>{{ row.group }}</ng-template>
1022
1160
  * </ng-container>
1161
+ *
1162
+ * <!-- per-column skeleton rows while the next page loads -->
1163
+ * <ng-container forRowDef [when]="isPlaceholder" placeholderCells />
1023
1164
  * </for-table-body>
1024
1165
  * ```
1025
1166
  */
@@ -1034,15 +1175,58 @@ class ForRowDef {
1034
1175
  */
1035
1176
  when = input.required(/* @ts-ignore */
1036
1177
  ...(ngDevMode ? [{ debugName: "when" }] : /* istanbul ignore next */ []));
1037
- /** The variant's full-span content template. */
1038
- cell = contentChild.required(ForRowCell);
1178
+ /**
1179
+ * The variant's full-span content template. Present for a full-span def; absent
1180
+ * (and unused) when `placeholderCells` is set. A def must declare exactly one of
1181
+ * a `[forRowCell]` template or `placeholderCells` — the body validates this and
1182
+ * throws a `[forty-cdk/table]` error otherwise.
1183
+ */
1184
+ cell = contentChild(ForRowCell, /* @ts-ignore */
1185
+ ...(ngDevMode ? [{ debugName: "cell" }] : /* istanbul ignore next */ []));
1186
+ /**
1187
+ * Render this variant's matched rows as **per-column placeholder cells** instead
1188
+ * of a full-span `[forRowCell]`. Set it (the bare `placeholderCells` attribute)
1189
+ * for interleaved / trailing skeleton rows — infinite-scroll or paginated tables
1190
+ * that keep their loaded rows and append placeholder rows while the next page
1191
+ * loads. The body stamps one `[forTableCell]` per displayed column from that
1192
+ * column's `[forPlaceholderCell]` template (an empty cell when the column omits
1193
+ * it), exactly like the `loading` state, but stamps the cells disabled so
1194
+ * grid-mode arrow navigation steps over them and the roving grid stays
1195
+ * rectangular.
1196
+ *
1197
+ * A def must declare **exactly one** of a `[forRowCell]` template or
1198
+ * `placeholderCells`; declaring both or neither throws a `[forty-cdk/table]`
1199
+ * error.
1200
+ */
1201
+ placeholderCells = input(false, { ...(ngDevMode ? { debugName: "placeholderCells" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1039
1202
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForRowDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1040
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.2", type: ForRowDef, isStandalone: true, selector: "[forRowDef]", inputs: { when: { classPropertyName: "when", publicName: "when", isSignal: true, isRequired: true, transformFunction: null } }, queries: [{ propertyName: "cell", first: true, predicate: ForRowCell, descendants: true, isSignal: true }], ngImport: i0 });
1203
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.2", type: ForRowDef, isStandalone: true, selector: "[forRowDef]", inputs: { when: { classPropertyName: "when", publicName: "when", isSignal: true, isRequired: true, transformFunction: null }, placeholderCells: { classPropertyName: "placeholderCells", publicName: "placeholderCells", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "cell", first: true, predicate: ForRowCell, descendants: true, isSignal: true }], ngImport: i0 });
1041
1204
  }
1042
1205
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForRowDef, decorators: [{
1043
1206
  type: Directive,
1044
1207
  args: [{ selector: '[forRowDef]' }]
1045
- }], propDecorators: { when: [{ type: i0.Input, args: [{ isSignal: true, alias: "when", required: true }] }], cell: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ForRowCell), { isSignal: true }] }] } });
1208
+ }], propDecorators: { when: [{ type: i0.Input, args: [{ isSignal: true, alias: "when", required: true }] }], cell: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ForRowCell), { isSignal: true }] }], placeholderCells: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderCells", required: false }] }] } });
1209
+
1210
+ const INTERACTIVE_DESCENDANT_SELECTOR = 'button, a[href], input, select, textarea, summary, [contenteditable="true"]';
1211
+ /**
1212
+ * Whether `event` originated from an interactive element nested inside the row it
1213
+ * is bound to — a consumer-placed `button`, `a[href]`, `input`, `select`,
1214
+ * `textarea`, `summary`, or `contenteditable` descendant of the row host. Resolves
1215
+ * the event target's closest interactive element and reports `true` only when it is
1216
+ * a strict descendant of `event.currentTarget` (the row), so a plain click on cell
1217
+ * text, the gaps between cells, or the row host itself reports `false`. Shared by
1218
+ * the two row-interaction call sites — `ForTableBody`'s whole-row activation and
1219
+ * `ForTableRow`'s selection — so both skip firing for clicks the inner control owns.
1220
+ */
1221
+ function eventFromInteractiveDescendant(event) {
1222
+ const target = event.target;
1223
+ const rowEl = event.currentTarget;
1224
+ if (!(target instanceof HTMLElement) || !(rowEl instanceof HTMLElement)) {
1225
+ return false;
1226
+ }
1227
+ const interactive = target.closest(INTERACTIVE_DESCENDANT_SELECTOR);
1228
+ return interactive !== null && interactive !== rowEl && rowEl.contains(interactive);
1229
+ }
1046
1230
 
1047
1231
  /**
1048
1232
  * Marks a data cell. The `role` is derived from the root's `mode`: `'cell'` in
@@ -1130,6 +1314,157 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
1130
1314
  }]
1131
1315
  }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
1132
1316
 
1317
+ /**
1318
+ * Opt-in **column reordering** for `ForTable`, composed over the drag-drop primitive.
1319
+ *
1320
+ * Apply on the `[forTableHeaderRow]` element. It wraps `[forDropList]` (via
1321
+ * `hostDirectives`) so the header cells become a reorderable list, then translates
1322
+ * drag-drop's generic drop into the table-friendly `columnReorder` output. Mark each
1323
+ * `[forTableHeaderCell]` as `[forDraggable]` with `[dragData]` set to the column name.
1324
+ * On a committed drop (pointer or keyboard) it emits the previous / new index and the
1325
+ * new column-name order; the consumer applies it to their own column array. **It never
1326
+ * reorders columns itself** (BYO-data).
1327
+ *
1328
+ * The wrapped list defaults to `orientation="horizontal"` (a column reorder is always along
1329
+ * the row axis), so no `orientation` binding is needed. Bind `orientation="vertical"` to
1330
+ * override for the rare case.
1331
+ *
1332
+ * In `mode="grid"` / `mode="treegrid"` the draggable header cells join the table's composite
1333
+ * roving grid as its first row, so a sortable + column-reorderable grid keeps the **single
1334
+ * tab stop** the WAI-ARIA Data Grid pattern calls for: `Tab` enters the grid once, Arrow keys
1335
+ * cross between header and body, and `Space` on a header cell lifts it for keyboard reordering.
1336
+ * It hands its drop-list roving to the grid via `FOR_DROP_LIST_ROVING_DELEGATE` and routes idle
1337
+ * header navigation through the table's grid keyboard handler.
1338
+ *
1339
+ * When a header cell is both sortable (`[forTableSortHeader]`) and reorderable, the two
1340
+ * keyboard activations split along WAI-ARIA lines so a single key never both sorts and lifts:
1341
+ * `Space` lifts the column, `Enter` toggles the sort. The split is enforced via a
1342
+ * `FOR_DRAGGABLE_LIFT_GUARD` that defers `Enter` to the sort header on cells carrying the
1343
+ * `data-sortable` marker — detected by DOM marker, so `forty-cdk/drag-drop` needs no table
1344
+ * import. A reorder-only header (no sort header) still lifts on both `Enter` and `Space`.
1345
+ *
1346
+ * @example
1347
+ * ```html
1348
+ * <div forTableHeaderRow forTableColumnReorder (columnReorder)="columns.set($event.columns)">
1349
+ * @for (col of columns(); track col) {
1350
+ * <div forTableHeaderCell [name]="col" forDraggable [dragData]="col">{{ col }}</div>
1351
+ * }
1352
+ * </div>
1353
+ * ```
1354
+ */
1355
+ class ForTableColumnReorder {
1356
+ ctx = injectTableContext('ForTableColumnReorder');
1357
+ #list = inject(ForDropList);
1358
+ #host = inject(ElementRef).nativeElement;
1359
+ #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
1360
+ /**
1361
+ * Fires once per committed reorder gesture (pointer drop or keyboard drop) with the
1362
+ * previous / new column index and the full column-name order after the move.
1363
+ */
1364
+ columnReorder = output();
1365
+ constructor() {
1366
+ const destroyRef = inject(DestroyRef);
1367
+ const sub = this.#list.dragDrop.subscribe((event) => this.#emit(event));
1368
+ destroyRef.onDestroy(() => sub.unsubscribe());
1369
+ if (this.#isBrowser) {
1370
+ const onCaptureKeydown = (event) => this.#onCaptureKeydown(event);
1371
+ this.#host.addEventListener('keydown', onCaptureKeydown, { capture: true });
1372
+ destroyRef.onDestroy(() => this.#host.removeEventListener('keydown', onCaptureKeydown, { capture: true }));
1373
+ }
1374
+ }
1375
+ /**
1376
+ * When the header row joins the composite grid, intercepts idle (not-lifted) Arrow /
1377
+ * Home / End / Page keys on a draggable header cell in the capture phase and routes them
1378
+ * through the table's grid navigation, then stops propagation so the draggable's own
1379
+ * keydown does not also navigate. Space / Enter (and every key while a keyboard drag is in
1380
+ * progress) fall through untouched, so the draggable still owns the lift / move / drop /
1381
+ * cancel gesture — keeping the header one navigation continuum with the body while
1382
+ * preserving column reordering.
1383
+ */
1384
+ #onCaptureKeydown(event) {
1385
+ if (!this.ctx.headerParticipatesInRoving()) {
1386
+ return;
1387
+ }
1388
+ const target = event.target;
1389
+ if (!(target instanceof HTMLElement)) {
1390
+ return;
1391
+ }
1392
+ if (!this.#list.items().some((handle) => handle.host === target) ||
1393
+ this.#list.isLifted(target)) {
1394
+ return;
1395
+ }
1396
+ if (this.ctx.handleHeaderCellKeydown(event, target)) {
1397
+ event.stopPropagation();
1398
+ }
1399
+ }
1400
+ #emit(event) {
1401
+ const names = this.#list.items().map((handle) => String(handle.data()));
1402
+ const columns = moveItemInArray(names, event.previousIndex, event.currentIndex);
1403
+ this.columnReorder.emit({ from: event.previousIndex, to: event.currentIndex, columns });
1404
+ }
1405
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableColumnReorder, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1406
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForTableColumnReorder, isStandalone: true, selector: "[forTableColumnReorder]", outputs: { columnReorder: "columnReorder" }, providers: [
1407
+ { provide: FOR_DROP_LIST_DEFAULT_ORIENTATION, useValue: 'horizontal' },
1408
+ {
1409
+ provide: FOR_DROP_LIST_ROVING_DELEGATE,
1410
+ useFactory: () => {
1411
+ const ctx = injectTableContext('ForTableColumnReorder');
1412
+ return {
1413
+ itemTabindex: (el) => ctx.headerParticipatesInRoving() ? ctx.headerCellTabIndex(el) : null,
1414
+ isItemHighlighted: (el) => ctx.headerParticipatesInRoving() ? ctx.isCellHighlighted(el) : null,
1415
+ };
1416
+ },
1417
+ },
1418
+ {
1419
+ provide: FOR_DRAGGABLE_LIFT_GUARD,
1420
+ useValue: {
1421
+ canLiftOnKey: (event, host) => !(event.key === 'Enter' && hostHasSortActivation(host)),
1422
+ },
1423
+ },
1424
+ ], exportAs: ["forTableColumnReorder"], hostDirectives: [{ directive: i1.ForDropList, inputs: ["orientation", "orientation", "dir", "dir", "disabled", "disabled", "autoScroll", "autoScroll", "animateReorder", "animateReorder", "liveSort", "liveSort", "boundary", "boundary", "lockAxis", "lockAxis"] }], ngImport: i0 });
1425
+ }
1426
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableColumnReorder, decorators: [{
1427
+ type: Directive,
1428
+ args: [{
1429
+ selector: '[forTableColumnReorder]',
1430
+ exportAs: 'forTableColumnReorder',
1431
+ providers: [
1432
+ { provide: FOR_DROP_LIST_DEFAULT_ORIENTATION, useValue: 'horizontal' },
1433
+ {
1434
+ provide: FOR_DROP_LIST_ROVING_DELEGATE,
1435
+ useFactory: () => {
1436
+ const ctx = injectTableContext('ForTableColumnReorder');
1437
+ return {
1438
+ itemTabindex: (el) => ctx.headerParticipatesInRoving() ? ctx.headerCellTabIndex(el) : null,
1439
+ isItemHighlighted: (el) => ctx.headerParticipatesInRoving() ? ctx.isCellHighlighted(el) : null,
1440
+ };
1441
+ },
1442
+ },
1443
+ {
1444
+ provide: FOR_DRAGGABLE_LIFT_GUARD,
1445
+ useValue: {
1446
+ canLiftOnKey: (event, host) => !(event.key === 'Enter' && hostHasSortActivation(host)),
1447
+ },
1448
+ },
1449
+ ],
1450
+ hostDirectives: [
1451
+ {
1452
+ directive: ForDropList,
1453
+ inputs: [
1454
+ 'orientation',
1455
+ 'dir',
1456
+ 'disabled',
1457
+ 'autoScroll',
1458
+ 'animateReorder',
1459
+ 'liveSort',
1460
+ 'boundary',
1461
+ 'lockAxis',
1462
+ ],
1463
+ },
1464
+ ],
1465
+ }]
1466
+ }], ctorParameters: () => [], propDecorators: { columnReorder: [{ type: i0.Output, args: ["columnReorder"] }] } });
1467
+
1133
1468
  /**
1134
1469
  * Marks a header cell (`role="columnheader"`). Requires a `name` input that
1135
1470
  * identifies the column — reflected as `data-column` for later phases (sort,
@@ -1731,7 +2066,9 @@ class ForTableRow {
1731
2066
  }
1732
2067
  onClick(event) {
1733
2068
  const v = this.value();
1734
- if (this.ctx.selectionMode() === 'none' || v === undefined) {
2069
+ if (this.ctx.selectionMode() === 'none' ||
2070
+ v === undefined ||
2071
+ eventFromInteractiveDescendant(event)) {
1735
2072
  return;
1736
2073
  }
1737
2074
  this.ctx.selectRow(v, {
@@ -1764,6 +2101,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
1764
2101
  }]
1765
2102
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], virtualIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualIndex", required: false }] }] } });
1766
2103
 
2104
+ /**
2105
+ * Applies a per-row attribute map to a `<for-table-body>` stamped row. The body
2106
+ * places it on every stamped row so its `rowAttrs` hook can set or remove
2107
+ * arbitrary attributes derived from the row datum, without the body owning a
2108
+ * static list of attribute names. A key mapped to `null` (or dropped from a
2109
+ * later map) removes that attribute. Internal to `forty-cdk/table`.
2110
+ */
2111
+ class ForTableRowAttrs {
2112
+ #host = inject(ElementRef).nativeElement;
2113
+ /** The attribute map to reflect on the row host, or `undefined` for none. */
2114
+ attrs = input(undefined, { ...(ngDevMode ? { debugName: "attrs" } : /* istanbul ignore next */ {}), alias: 'forTableRowAttrs' });
2115
+ constructor() {
2116
+ let applied = [];
2117
+ effect(() => {
2118
+ const next = this.attrs() ?? {};
2119
+ for (const key of applied) {
2120
+ if (!(key in next)) {
2121
+ this.#host.removeAttribute(key);
2122
+ }
2123
+ }
2124
+ const keys = [];
2125
+ for (const [key, value] of Object.entries(next)) {
2126
+ if (value == null) {
2127
+ this.#host.removeAttribute(key);
2128
+ }
2129
+ else {
2130
+ this.#host.setAttribute(key, value);
2131
+ }
2132
+ keys.push(key);
2133
+ }
2134
+ applied = keys;
2135
+ });
2136
+ }
2137
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableRowAttrs, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2138
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTableRowAttrs, isStandalone: true, selector: "[forTableRowAttrs]", inputs: { attrs: { classPropertyName: "attrs", publicName: "forTableRowAttrs", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
2139
+ }
2140
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableRowAttrs, decorators: [{
2141
+ type: Directive,
2142
+ args: [{ selector: '[forTableRowAttrs]' }]
2143
+ }], ctorParameters: () => [], propDecorators: { attrs: [{ type: i0.Input, args: [{ isSignal: true, alias: "forTableRowAttrs", required: false }] }] } });
2144
+
1767
2145
  /**
1768
2146
  * Turns a `[forTableHeaderCell]` into a sortable affordance that emits `aria-sort`
1769
2147
  * and fires `sortChange` on activation (click, Enter, Space). The directive is
@@ -1913,13 +2291,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
1913
2291
  }], ctorParameters: () => [], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }, { type: i0.Output, args: ["directionChange"] }], disableClear: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableClear", required: false }] }], firstClickDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstClickDirection", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }] } });
1914
2292
 
1915
2293
  /**
1916
- * Ergonomic declarative renderer for `[forTable]` in `<div role>` grid mode.
2294
+ * Ergonomic declarative renderer for the columns of a `[forTable]`.
1917
2295
  * Place `<for-table-body>` inside a `[forTable]` element and declare one
1918
2296
  * `[forColumnDef]` per column; the body harvests the defs and stamps the header
1919
2297
  * row and one data row per item out of the raw cell primitives, so a column is
1920
2298
  * authored in a single block instead of being smeared across header, data, and
1921
2299
  * placeholder rows.
1922
2300
  *
2301
+ * **Supported modes: `table` and `grid`.** Nothing in the body is grid-specific
2302
+ * — it derives each stamped cell's role from the table `mode` (`cell` in
2303
+ * `table`, `gridcell` in `grid`) and applies no mode guard. Choose `mode="grid"`
2304
+ * for interactive cells (roving 2D navigation, cell widgets, cell-entry); choose
2305
+ * the default `mode="table"` for read-only or whole-row navigation lists, where
2306
+ * `role="grid"` would announce an interaction model the list does not have. The
2307
+ * table-mode row-interaction companion feature (whole-row activate / context
2308
+ * menu) is tracked in
2309
+ * [#1349](https://github.com/tutkli/forty-cdk/issues/1349). `mode="treegrid"` is
2310
+ * out of scope — the body stamps no expansion affordances.
2311
+ *
1923
2312
  * It owns only the grid **structure** — it applies `display: grid` and the
1924
2313
  * derived `grid-template-columns` track (from each def's `width` and the
1925
2314
  * published `--for-table-col-<name>-width` resize var) to the header row and
@@ -1931,33 +2320,72 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
1931
2320
  * raw `[forTableCell]` / `[forTableHeaderCell]` primitives directly.
1932
2321
  *
1933
2322
  * Sort and resize affordances are auto-wired from the per-column `sortable` /
1934
- * `resizable` flags and surfaced through `sortChange` / `resizeCommit`; the sort
1935
- * and width descriptors stay consumer-applied (BYO-data). Selection stays
1936
- * consumer-placed: drop `[forTableRowSelector]` / `[forTableSelectAll]` into the
1937
- * cell templates and set `rowKey` so each row carries a selection identity.
2323
+ * `resizable` flags. Sort stays consumer-applied (BYO-data): the body derives each
2324
+ * header's `aria-sort` from `sort` and re-emits activation through `sortChange`.
2325
+ * Resize width state can be owned by the body instead — `[(columnWidths)]` seeds
2326
+ * each `resizable` handle's width (keyed by column name) and tracks its live
2327
+ * changes, so the resized track drives itself with no per-consumer plumbing; each
2328
+ * `resizable` column is tuned per def via `resizeMin` / `resizeMax` / `resizeStep`
2329
+ * / `autoFit` / `fitIncludesHeader`, and gesture-end commits still surface through
2330
+ * `resizeCommit`. Selection stays consumer-placed: drop `[forTableRowSelector]` /
2331
+ * `[forTableSelectAll]` into the cell templates and set `rowKey` so each row
2332
+ * carries a selection identity.
1938
2333
  *
1939
2334
  * **Virtualization is transparent.** Adding `[forTableVirtualized]` to the same
1940
2335
  * `[forTable]` element switches the body to windowed rendering automatically:
1941
2336
  * it reads the published window off the table context (no cross-entry import),
1942
2337
  * renders only the visible slice indexed into `rows`, sizes its rowgroup to the
1943
2338
  * full scroll height, and absolutely positions each row at its offset. The
1944
- * consumer passes the whole dataset to `rows` and sets `[rowCount]` on
1945
- * `[forTable]` — no `#v` reference, manual sizer, `@for` window, or
1946
- * `[virtualIndex]` binding. Fixed-size rows only for now (drive row height in
1947
- * CSS); measured / dynamic row heights stay on the raw `[forTableRow]` path.
2339
+ * consumer passes the whole dataset to `rows` — the body derives the true total
2340
+ * from its length, so `[rowCount]` on `[forTable]` is unnecessary (bind it only
2341
+ * for a server-known total larger than the loaded rows). No `#v` reference,
2342
+ * manual sizer, `@for` window, or `[virtualIndex]` binding. Rows are fixed-size
2343
+ * by default (drive row height in CSS); set `measureRows` for measured /
2344
+ * variable row heights (denser variant rows, group separators) — the body feeds
2345
+ * each rendered row's real height back to the virtualizer so the window stays
2346
+ * aligned after scroll.
1948
2347
  *
1949
2348
  * **Row variants.** Declare one or more `[forRowDef]` alongside the columns to
1950
- * render a full-span row for the data they match (group headers, section
1951
- * separators, summary / empty-state rows). For each datum the body picks the
1952
- * first `[forRowDef]` whose `[when]` predicate returns `true` and stamps a row
1953
- * whose single cell spans every column and renders the `[forRowCell]` template;
1954
- * unmatched data renders the standard per-column row. Variant rows are
1955
- * presentational — their spanning cell stays out of the roving 2D navigation
1956
- * grid (arrow keys step over them) and they are non-selectable — but they still
1957
- * occupy a row slot and count towards `aria-rowindex` / `aria-rowcount`.
2349
+ * render a variant row for the data they match. For each datum the body picks the
2350
+ * first `[forRowDef]` whose `[when]` predicate returns `true`; unmatched data
2351
+ * renders the standard per-column row. Each def declares one of two shapes: a
2352
+ * `[forRowCell]` template stamps a **full-span** row (group headers, section
2353
+ * separators, summary / empty-state rows) whose single cell spans every column;
2354
+ * the `placeholderCells` flag stamps **per-column placeholder cells** from each
2355
+ * column's `[forPlaceholderCell]` (interleaved / trailing skeleton rows for
2356
+ * infinite scroll). Either way variant rows are presentational and
2357
+ * non-selectable, and still count towards `aria-rowindex` / `aria-rowcount`. A
2358
+ * full-span cell stays out of the roving 2D navigation grid (registers no cell
2359
+ * handle, so arrow keys step over the row); placeholder cells keep the grid
2360
+ * rectangular (one cell per column) but are stamped disabled so grid navigation
2361
+ * steps over them.
1958
2362
  */
1959
2363
  class ForTableBody {
1960
2364
  #ctx = injectTableContext('ForTableBody');
2365
+ rowEls = viewChildren('rowEl', /* @ts-ignore */
2366
+ ...(ngDevMode ? [{ debugName: "rowEls" }] : /* istanbul ignore next */ []));
2367
+ #measuredAt = new WeakMap();
2368
+ constructor() {
2369
+ const bodyRowCount = computed(() => this.rows().length, /* @ts-ignore */
2370
+ ...(ngDevMode ? [{ debugName: "bodyRowCount" }] : /* istanbul ignore next */ []));
2371
+ this.#ctx.registerBodyRowCount(bodyRowCount);
2372
+ inject(DestroyRef).onDestroy(() => this.#ctx.registerBodyRowCount(null));
2373
+ afterEveryRender(() => {
2374
+ const window = this.#ctx.virtualWindow();
2375
+ if (!window || !this.measureRows()) {
2376
+ return;
2377
+ }
2378
+ for (const ref of this.rowEls()) {
2379
+ const el = ref.nativeElement;
2380
+ const index = el.getAttribute('data-index');
2381
+ if (index === null || this.#measuredAt.get(el) === index) {
2382
+ continue;
2383
+ }
2384
+ this.#measuredAt.set(el, index);
2385
+ window.measureRow(el);
2386
+ }
2387
+ });
2388
+ }
1961
2389
  /** The rows to render — already sorted / filtered / paged by the consumer (BYO-data). */
1962
2390
  rows = input.required(/* @ts-ignore */
1963
2391
  ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
@@ -1986,16 +2414,108 @@ class ForTableBody {
1986
2414
  /** Number of placeholder rows rendered while `loading`. Default `3`. */
1987
2415
  placeholderRows = input(3, /* @ts-ignore */
1988
2416
  ...(ngDevMode ? [{ debugName: "placeholderRows" }] : /* istanbul ignore next */ []));
2417
+ /**
2418
+ * Opt in to **measured (variable) row heights** under `[forTableVirtualized]`.
2419
+ * When set, the body measures each stamped row after render and feeds its real
2420
+ * height back to the virtualizer, which replaces the `estimateRowSize` estimate
2421
+ * and re-aligns the offsets of the rows below — so a window mixing row shapes
2422
+ * (denser variant rows, group separators) stays contiguous after scroll.
2423
+ *
2424
+ * Off by default: a uniform-height table keeps the pure `estimateRowSize` fast
2425
+ * path with no per-row measurement work. Has no effect without
2426
+ * `[forTableVirtualized]` (there is no window to measure against).
2427
+ */
2428
+ measureRows = input(false, { ...(ngDevMode ? { debugName: "measureRows" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
1989
2429
  /** Fires when a `sortable` header is activated; forwarded from the internal `[forTableSortHeader]`. */
1990
2430
  sortChange = output();
1991
2431
  /** Fires when a `resizable` column commits a width; forwarded from the internal `[forTableColumnResizer]`. */
1992
2432
  resizeCommit = output();
2433
+ /**
2434
+ * Fires once per committed column reorder gesture (pointer drop or keyboard drop),
2435
+ * forwarded unchanged from the internal `[forTableColumnReorder]`. Its `columns`
2436
+ * lists the reorderable columns in their new order (equal to the full displayed
2437
+ * order when every displayed column is `reorderable`); apply it to your own column
2438
+ * order and feed it back through `displayedColumns` (BYO-data). Only present when at
2439
+ * least one `[forColumnDef]` is `reorderable`.
2440
+ */
2441
+ columnReorder = output();
2442
+ /**
2443
+ * Two-way map of column widths (px), keyed by column `name`. It seeds each
2444
+ * `resizable` column's stamped handle `[width]` — so the `role="separator"`
2445
+ * handle exposes `aria-valuenow` from the first render and the column's grid
2446
+ * track picks up the seeded width immediately — and is updated immutably on
2447
+ * every live width change the handle reports (pointer drag, keyboard resize,
2448
+ * auto-fit). Only `resizable` columns participate; other names are ignored.
2449
+ *
2450
+ * The map is JSON-serializable, so persisting a user's column layout is
2451
+ * `[(columnWidths)]` plus one storage write. Together with `[displayedColumns]`
2452
+ * and `[sort]` it makes the full user-configurable table state three bindings.
2453
+ * Its implicit `columnWidthsChange` fires only on handle-driven updates, not on
2454
+ * consumer writes through `[(columnWidths)]`.
2455
+ */
2456
+ columnWidths = model({}, /* @ts-ignore */
2457
+ ...(ngDevMode ? [{ debugName: "columnWidths" }] : /* istanbul ignore next */ []));
2458
+ /**
2459
+ * Opt-in whole-row interaction for **navigation lists**, active only in the
2460
+ * default `mode="table"`. When set, each data row becomes a focusable tab stop
2461
+ * (`tabindex="0"`) and a pointer click or `Enter` emits `rowActivate`, while a
2462
+ * `contextmenu` (right-click or the context-menu key) emits `rowContextMenu`.
2463
+ * Full-span `[forRowDef]` variant rows stay non-interactive. Ignored in `grid`
2464
+ * / `treegrid` mode, where roving 2D navigation and cell-entry own the keyboard
2465
+ * and whole-row activation would conflict.
2466
+ *
2467
+ * Interactive content inside a data cell owns its own events: a click or
2468
+ * `Enter` originating from a `button`, `a[href]`, `input`, `select`,
2469
+ * `textarea`, `summary`, or `contenteditable` descendant does **not** emit
2470
+ * `rowActivate`, and its native default action is left intact — a trailing
2471
+ * per-row action button keeps working, and `Enter` on it is not
2472
+ * `preventDefault`ed. The row still activates from anywhere else: cell text,
2473
+ * the gaps between cells, or the focused row host itself. `rowContextMenu` is
2474
+ * deliberately unguarded — a right-click anywhere on the row, including over an
2475
+ * inner control, still offers the row's context menu, matching native lists.
2476
+ */
2477
+ interactiveRows = input(false, { ...(ngDevMode ? { debugName: "interactiveRows" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2478
+ /**
2479
+ * Fires when a data row is activated by a pointer click or `Enter`, carrying
2480
+ * the row datum, its dataset index, and the originating event. Requires
2481
+ * `interactiveRows` and `mode="table"`.
2482
+ */
2483
+ rowActivate = output();
2484
+ /**
2485
+ * Fires when a data row receives a `contextmenu` event (right-click or the
2486
+ * context-menu key), carrying the row datum, its dataset index, and the event —
2487
+ * position your own overlay from it. Requires `interactiveRows` and
2488
+ * `mode="table"`.
2489
+ */
2490
+ rowContextMenu = output();
2491
+ /**
2492
+ * Per-row class hook, applied to data **and** variant rows in **every** mode.
2493
+ * Receives the row datum and its 0-based dataset index and returns the class(es)
2494
+ * to apply — a string, a `{ className: boolean }` map, or `undefined` for none.
2495
+ * This is the only seam for styling a body-owned row from its datum (an
2496
+ * "active" / "menu-open" highlight, error or dimmed rows). Evaluated on every
2497
+ * change-detection pass, so keep it cheap and free of side effects.
2498
+ */
2499
+ rowClass = input(/* @ts-ignore */
2500
+ ...(ngDevMode ? [undefined, { debugName: "rowClass" }] : /* istanbul ignore next */ []));
2501
+ /**
2502
+ * Per-row attribute hook, applied to data **and** variant rows in **every**
2503
+ * mode. Receives the row datum and its 0-based dataset index and returns an
2504
+ * attribute map to reflect on the row host; a key mapped to `null` (or dropped
2505
+ * from a later map) removes that attribute. Evaluated on every change-detection
2506
+ * pass, so keep it cheap and free of side effects.
2507
+ */
2508
+ rowAttrs = input(/* @ts-ignore */
2509
+ ...(ngDevMode ? [undefined, { debugName: "rowAttrs" }] : /* istanbul ignore next */ []));
1993
2510
  /** Declared column definitions, in DOM order. */
1994
2511
  columns = contentChildren(ForColumnDef, /* @ts-ignore */
1995
2512
  ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
1996
2513
  /** Declared full-span row variants, in DOM order (first match wins per datum). */
1997
2514
  rowDefs = contentChildren(ForRowDef, /* @ts-ignore */
1998
2515
  ...(ngDevMode ? [{ debugName: "rowDefs" }] : /* istanbul ignore next */ []));
2516
+ /** The optional shared drag placeholder for reorderable columns, or `undefined`. */
2517
+ columnDragPlaceholder = contentChild(ForColumnDragPlaceholder, /* @ts-ignore */
2518
+ ...(ngDevMode ? [{ debugName: "columnDragPlaceholder" }] : /* istanbul ignore next */ []));
1999
2519
  /** The role a stamped cell carries: `'cell'` in `table` mode, `'gridcell'` otherwise. */
2000
2520
  cellRole = computed(() => this.#ctx.mode() === 'table' ? 'cell' : 'gridcell', /* @ts-ignore */
2001
2521
  ...(ngDevMode ? [{ debugName: "cellRole" }] : /* istanbul ignore next */ []));
@@ -2010,6 +2530,13 @@ class ForTableBody {
2010
2530
  return order.map((name) => byName.get(name)).filter((def) => def != null);
2011
2531
  }, /* @ts-ignore */
2012
2532
  ...(ngDevMode ? [{ debugName: "orderedColumns" }] : /* istanbul ignore next */ []));
2533
+ /**
2534
+ * `true` when at least one displayed column is `reorderable`, switching the stamped
2535
+ * header row to the drag-reorder path (`[forTableColumnReorder]` + per-cell
2536
+ * `[forDraggable]`). A body with no reorderable column keeps the plain header row.
2537
+ */
2538
+ hasReorderable = computed(() => this.orderedColumns().some((col) => col.reorderable()), /* @ts-ignore */
2539
+ ...(ngDevMode ? [{ debugName: "hasReorderable" }] : /* istanbul ignore next */ []));
2013
2540
  /**
2014
2541
  * The derived `grid-template-columns` track, applied to the header row and every
2015
2542
  * data row and exposed for consumers who want to bind it elsewhere. Each column
@@ -2029,6 +2556,9 @@ class ForTableBody {
2029
2556
  const data = this.rows();
2030
2557
  const key = this.rowKey();
2031
2558
  const variants = this.rowDefs();
2559
+ for (const def of variants) {
2560
+ this.#assertRowDefConfig(def);
2561
+ }
2032
2562
  const matchVariant = (datum, index) => variants.find((def) => def.when()(datum, index)) ?? null;
2033
2563
  if (window) {
2034
2564
  const out = [];
@@ -2066,6 +2596,14 @@ class ForTableBody {
2066
2596
  });
2067
2597
  }, /* @ts-ignore */
2068
2598
  ...(ngDevMode ? [{ debugName: "renderRows" }] : /* istanbul ignore next */ []));
2599
+ /** Enforces that each `[forRowDef]` declares exactly one of `[forRowCell]` / `placeholderCells`. */
2600
+ #assertRowDefConfig(def) {
2601
+ const hasCell = def.cell() != null;
2602
+ const hasPlaceholder = def.placeholderCells();
2603
+ if (hasCell === hasPlaceholder) {
2604
+ throw new Error(`[forty-cdk/table] A [forRowDef] must declare exactly one of a [forRowCell] template or the placeholderCells flag (this def declares ${hasCell ? 'both' : 'neither'}).`);
2605
+ }
2606
+ }
2069
2607
  /** Full scroll height (px) applied to the rowgroup when virtualized, else `null` (natural height). */
2070
2608
  sizerHeight = computed(() => {
2071
2609
  const window = this.#ctx.virtualWindow();
@@ -2079,37 +2617,163 @@ class ForTableBody {
2079
2617
  const descriptor = this.sort();
2080
2618
  return descriptor && descriptor.column === column ? descriptor.direction : 'none';
2081
2619
  }
2620
+ /** Whether whole-row interaction is active: opted in via `interactiveRows` and in `table` mode. */
2621
+ rowsInteractive = computed(() => this.interactiveRows() && this.#ctx.mode() === 'table', /* @ts-ignore */
2622
+ ...(ngDevMode ? [{ debugName: "rowsInteractive" }] : /* istanbul ignore next */ []));
2623
+ /** The `tabindex` for a stamped row: `0` for an interactive data row, `null` otherwise. */
2624
+ rowTabIndex(row) {
2625
+ return this.rowsInteractive() && !row.variant ? 0 : null;
2626
+ }
2627
+ /** Resolves the `rowClass` hook for a stamped row, or `undefined` when unset. */
2628
+ rowClassFor(row) {
2629
+ return this.rowClass()?.(row.datum, row.index);
2630
+ }
2631
+ /** Resolves the `rowAttrs` hook for a stamped row, or `undefined` when unset. */
2632
+ rowAttrsFor(row) {
2633
+ return this.rowAttrs()?.(row.datum, row.index);
2634
+ }
2635
+ /**
2636
+ * Folds a stamped resizer's live width update into the `[(columnWidths)]` map
2637
+ * immutably, keyed by the column name. Ignores the resizer's initial unset
2638
+ * (`undefined`) emission and no-ops when the width is unchanged, so seeding the
2639
+ * handle from `columnWidths` never loops back into a redundant model write.
2640
+ */
2641
+ onColumnWidthChange(column, width) {
2642
+ if (width === undefined) {
2643
+ return;
2644
+ }
2645
+ const current = this.columnWidths();
2646
+ if (current[column] === width) {
2647
+ return;
2648
+ }
2649
+ this.columnWidths.set({ ...current, [column]: width });
2650
+ }
2651
+ onRowClick(row, event) {
2652
+ this.#activateRow(row, event);
2653
+ }
2654
+ onRowEnter(row, event) {
2655
+ if (this.#activateRow(row, event)) {
2656
+ event.preventDefault();
2657
+ }
2658
+ }
2659
+ onRowContextMenu(row, event) {
2660
+ if (!this.rowsInteractive() || row.variant) {
2661
+ return;
2662
+ }
2663
+ this.rowContextMenu.emit({ row: row.datum, index: row.index, event });
2664
+ }
2665
+ #activateRow(row, event) {
2666
+ if (!this.rowsInteractive() || row.variant || eventFromInteractiveDescendant(event)) {
2667
+ return false;
2668
+ }
2669
+ this.rowActivate.emit({ row: row.datum, index: row.index, event });
2670
+ return true;
2671
+ }
2082
2672
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableBody, deps: [], target: i0.ɵɵFactoryTarget.Component });
2083
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ForTableBody, isStandalone: true, selector: "for-table-body", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: false, transformFunction: null }, displayedColumns: { classPropertyName: "displayedColumns", publicName: "displayedColumns", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, placeholderRows: { classPropertyName: "placeholderRows", publicName: "placeholderRows", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortChange: "sortChange", resizeCommit: "resizeCommit" }, host: { styleAttribute: "display: contents" }, queries: [{ propertyName: "columns", predicate: ForColumnDef, isSignal: true }, { propertyName: "rowDefs", predicate: ForRowDef, isSignal: true }], ngImport: i0, template: `
2084
- <div forTableHeaderRow [style.display]="'grid'" [style.grid-template-columns]="track()">
2085
- @for (col of orderedColumns(); track col.name()) {
2086
- <div
2087
- #headerCell="forTableHeaderCell"
2088
- forTableHeaderCell
2089
- [name]="col.name()"
2090
- [sticky]="col.sticky()"
2091
- forTableSortHeader
2673
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: ForTableBody, isStandalone: true, selector: "for-table-body", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null }, rowKey: { classPropertyName: "rowKey", publicName: "rowKey", isSignal: true, isRequired: false, transformFunction: null }, displayedColumns: { classPropertyName: "displayedColumns", publicName: "displayedColumns", isSignal: true, isRequired: false, transformFunction: null }, sort: { classPropertyName: "sort", publicName: "sort", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, placeholderRows: { classPropertyName: "placeholderRows", publicName: "placeholderRows", isSignal: true, isRequired: false, transformFunction: null }, measureRows: { classPropertyName: "measureRows", publicName: "measureRows", isSignal: true, isRequired: false, transformFunction: null }, columnWidths: { classPropertyName: "columnWidths", publicName: "columnWidths", isSignal: true, isRequired: false, transformFunction: null }, interactiveRows: { classPropertyName: "interactiveRows", publicName: "interactiveRows", isSignal: true, isRequired: false, transformFunction: null }, rowClass: { classPropertyName: "rowClass", publicName: "rowClass", isSignal: true, isRequired: false, transformFunction: null }, rowAttrs: { classPropertyName: "rowAttrs", publicName: "rowAttrs", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sortChange: "sortChange", resizeCommit: "resizeCommit", columnReorder: "columnReorder", columnWidths: "columnWidthsChange", rowActivate: "rowActivate", rowContextMenu: "rowContextMenu" }, host: { styleAttribute: "display: contents" }, queries: [{ propertyName: "columns", predicate: ForColumnDef, isSignal: true }, { propertyName: "rowDefs", predicate: ForRowDef, isSignal: true }, { propertyName: "columnDragPlaceholder", first: true, predicate: ForColumnDragPlaceholder, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "rowEls", predicate: ["rowEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
2674
+ <ng-template #headerCellContent let-col let-cell="cell">
2675
+ <ng-container
2676
+ [ngTemplateOutlet]="col.header().template"
2677
+ [ngTemplateOutletInjector]="cell.injector"
2678
+ />
2679
+ @if (col.resizable()) {
2680
+ <button
2681
+ forTableColumnResizer
2092
2682
  [column]="col.name()"
2093
- [sortable]="col.sortable()"
2094
- [direction]="directionFor(col.name())"
2095
- (sortChange)="sortChange.emit($event)"
2096
- >
2097
- <ng-container
2098
- [ngTemplateOutlet]="col.header().template"
2099
- [ngTemplateOutletInjector]="headerCell.injector"
2100
- />
2101
- @if (col.resizable()) {
2102
- <button
2103
- forTableColumnResizer
2104
- autoFit
2683
+ [width]="columnWidths()[col.name()]"
2684
+ [min]="col.resizeMin()"
2685
+ [max]="col.resizeMax()"
2686
+ [step]="col.resizeStep()"
2687
+ [autoFit]="col.autoFit()"
2688
+ [fitIncludesHeader]="col.fitIncludesHeader()"
2689
+ [attr.aria-label]="col.resizeAriaLabel()"
2690
+ (widthChange)="onColumnWidthChange(col.name(), $event)"
2691
+ (resizeCommit)="resizeCommit.emit($event)"
2692
+ ></button>
2693
+ }
2694
+ </ng-template>
2695
+
2696
+ @if (hasReorderable()) {
2697
+ <div
2698
+ forTableHeaderRow
2699
+ forTableColumnReorder
2700
+ [style.display]="'grid'"
2701
+ [style.grid-template-columns]="track()"
2702
+ (columnReorder)="columnReorder.emit($event)"
2703
+ >
2704
+ @for (col of orderedColumns(); track col.name()) {
2705
+ @if (col.reorderable()) {
2706
+ <div
2707
+ #headerCell="forTableHeaderCell"
2708
+ forTableHeaderCell
2709
+ [name]="col.name()"
2710
+ [sticky]="col.sticky()"
2711
+ [class]="col.headerClass()"
2712
+ forTableSortHeader
2713
+ [column]="col.name()"
2714
+ [sortable]="col.sortable()"
2715
+ [direction]="directionFor(col.name())"
2716
+ (sortChange)="sortChange.emit($event)"
2717
+ forDraggable
2718
+ [dragData]="col.name()"
2719
+ >
2720
+ <ng-container
2721
+ [ngTemplateOutlet]="headerCellContent"
2722
+ [ngTemplateOutletInjector]="headerCell.injector"
2723
+ [ngTemplateOutletContext]="{ $implicit: col, cell: headerCell }"
2724
+ />
2725
+ @if (columnDragPlaceholder(); as placeholder) {
2726
+ <ng-template forDragPlaceholder>
2727
+ <ng-container [ngTemplateOutlet]="placeholder.template" />
2728
+ </ng-template>
2729
+ }
2730
+ </div>
2731
+ } @else {
2732
+ <div
2733
+ #headerCell="forTableHeaderCell"
2734
+ forTableHeaderCell
2735
+ [name]="col.name()"
2736
+ [sticky]="col.sticky()"
2737
+ [class]="col.headerClass()"
2738
+ forTableSortHeader
2105
2739
  [column]="col.name()"
2106
- [attr.aria-label]="col.resizeAriaLabel()"
2107
- (resizeCommit)="resizeCommit.emit($event)"
2108
- ></button>
2740
+ [sortable]="col.sortable()"
2741
+ [direction]="directionFor(col.name())"
2742
+ (sortChange)="sortChange.emit($event)"
2743
+ >
2744
+ <ng-container
2745
+ [ngTemplateOutlet]="headerCellContent"
2746
+ [ngTemplateOutletInjector]="headerCell.injector"
2747
+ [ngTemplateOutletContext]="{ $implicit: col, cell: headerCell }"
2748
+ />
2749
+ </div>
2109
2750
  }
2110
- </div>
2111
- }
2112
- </div>
2751
+ }
2752
+ </div>
2753
+ } @else {
2754
+ <div forTableHeaderRow [style.display]="'grid'" [style.grid-template-columns]="track()">
2755
+ @for (col of orderedColumns(); track col.name()) {
2756
+ <div
2757
+ #headerCell="forTableHeaderCell"
2758
+ forTableHeaderCell
2759
+ [name]="col.name()"
2760
+ [sticky]="col.sticky()"
2761
+ [class]="col.headerClass()"
2762
+ forTableSortHeader
2763
+ [column]="col.name()"
2764
+ [sortable]="col.sortable()"
2765
+ [direction]="directionFor(col.name())"
2766
+ (sortChange)="sortChange.emit($event)"
2767
+ >
2768
+ <ng-container
2769
+ [ngTemplateOutlet]="headerCellContent"
2770
+ [ngTemplateOutletInjector]="headerCell.injector"
2771
+ [ngTemplateOutletContext]="{ $implicit: col, cell: headerCell }"
2772
+ />
2773
+ </div>
2774
+ }
2775
+ </div>
2776
+ }
2113
2777
  <div
2114
2778
  role="rowgroup"
2115
2779
  [style.position]="sizerHeight() !== null ? 'relative' : null"
@@ -2119,7 +2783,13 @@ class ForTableBody {
2119
2783
  @for (placeholder of placeholderRange(); track placeholder) {
2120
2784
  <div forTableRow [style.display]="'grid'" [style.grid-template-columns]="track()">
2121
2785
  @for (col of orderedColumns(); track col.name()) {
2122
- <div #cell="forTableCell" forTableCell [name]="col.name()" [sticky]="col.sticky()">
2786
+ <div
2787
+ #cell="forTableCell"
2788
+ forTableCell
2789
+ [name]="col.name()"
2790
+ [sticky]="col.sticky()"
2791
+ [class]="col.cellClass()"
2792
+ >
2123
2793
  @if (col.placeholderCell(); as placeholderCell) {
2124
2794
  <ng-container
2125
2795
  [ngTemplateOutlet]="placeholderCell.template"
@@ -2134,34 +2804,67 @@ class ForTableBody {
2134
2804
  @for (r of renderRows(); track r.key) {
2135
2805
  <div
2136
2806
  #rowRef="forTableRow"
2807
+ #rowEl
2137
2808
  forTableRow
2138
2809
  [value]="r.value"
2139
2810
  [virtualIndex]="r.virtualIndex"
2140
2811
  [attr.data-index]="r.virtualIndex"
2812
+ [attr.tabindex]="rowTabIndex(r)"
2813
+ [class]="rowClassFor(r)"
2814
+ [forTableRowAttrs]="rowAttrsFor(r)"
2141
2815
  [style.display]="'grid'"
2142
2816
  [style.grid-template-columns]="track()"
2143
2817
  [style.position]="r.start !== null ? 'absolute' : null"
2144
2818
  [style.left]="r.start !== null ? '0' : null"
2145
2819
  [style.right]="r.start !== null ? '0' : null"
2146
2820
  [style.transform]="r.start !== null ? 'translateY(' + r.start + 'px)' : null"
2821
+ (click)="onRowClick(r, $event)"
2822
+ (keydown.enter)="onRowEnter(r, $event)"
2823
+ (contextmenu)="onRowContextMenu(r, $event)"
2147
2824
  >
2148
2825
  @if (r.variant; as variant) {
2149
- <div
2150
- [attr.role]="cellRole()"
2151
- [attr.aria-colindex]="1"
2152
- [attr.aria-colspan]="orderedColumns().length"
2153
- [attr.data-row-variant]="''"
2154
- [style.grid-column]="'1 / -1'"
2155
- >
2156
- <ng-container
2157
- [ngTemplateOutlet]="variant.cell().template"
2158
- [ngTemplateOutletInjector]="rowRef.injector"
2159
- [ngTemplateOutletContext]="{ $implicit: r.datum, index: r.index }"
2160
- />
2161
- </div>
2826
+ @if (variant.placeholderCells()) {
2827
+ @for (col of orderedColumns(); track col.name()) {
2828
+ <div
2829
+ #cell="forTableCell"
2830
+ forTableCell
2831
+ disabled
2832
+ [name]="col.name()"
2833
+ [sticky]="col.sticky()"
2834
+ [class]="col.cellClass()"
2835
+ >
2836
+ @if (col.placeholderCell(); as placeholderCell) {
2837
+ <ng-container
2838
+ [ngTemplateOutlet]="placeholderCell.template"
2839
+ [ngTemplateOutletInjector]="cell.injector"
2840
+ />
2841
+ }
2842
+ </div>
2843
+ }
2844
+ } @else {
2845
+ <div
2846
+ [attr.role]="cellRole()"
2847
+ [attr.aria-colindex]="1"
2848
+ [attr.aria-colspan]="orderedColumns().length"
2849
+ [attr.data-row-variant]="''"
2850
+ [style.grid-column]="'1 / -1'"
2851
+ >
2852
+ <ng-container
2853
+ [ngTemplateOutlet]="variant.cell()?.template ?? null"
2854
+ [ngTemplateOutletInjector]="rowRef.injector"
2855
+ [ngTemplateOutletContext]="{ $implicit: r.datum, index: r.index }"
2856
+ />
2857
+ </div>
2858
+ }
2162
2859
  } @else {
2163
2860
  @for (col of orderedColumns(); track col.name()) {
2164
- <div #cell="forTableCell" forTableCell [name]="col.name()" [sticky]="col.sticky()">
2861
+ <div
2862
+ #cell="forTableCell"
2863
+ forTableCell
2864
+ [name]="col.name()"
2865
+ [sticky]="col.sticky()"
2866
+ [class]="col.cellClass()"
2867
+ >
2165
2868
  <ng-container
2166
2869
  [ngTemplateOutlet]="col.dataCell().template"
2167
2870
  [ngTemplateOutletInjector]="cell.injector"
@@ -2175,7 +2878,7 @@ class ForTableBody {
2175
2878
  }
2176
2879
  </div>
2177
2880
  <ng-content />
2178
- `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: ForTableHeaderRow, selector: "[forTableHeaderRow]", exportAs: ["forTableHeaderRow"] }, { kind: "directive", type: ForTableHeaderCell, selector: "[forTableHeaderCell]", inputs: ["name", "sticky"], exportAs: ["forTableHeaderCell"] }, { kind: "directive", type: ForTableCell, selector: "[forTableCell]", inputs: ["name", "sticky", "disabled"], exportAs: ["forTableCell"] }, { kind: "directive", type: ForTableRow, selector: "[forTableRow]", inputs: ["value", "level", "expandable", "virtualIndex"], exportAs: ["forTableRow"] }, { kind: "directive", type: ForTableSortHeader, selector: "[forTableSortHeader]", inputs: ["column", "direction", "disableClear", "firstClickDirection", "sortable"], outputs: ["directionChange", "sortChange"], exportAs: ["forTableSortHeader"] }, { kind: "directive", type: ForTableColumnResizer, selector: "[forTableColumnResizer]", inputs: ["column", "width", "min", "max", "step", "autoFit", "fitIncludesHeader"], outputs: ["widthChange", "resizeCommit"], exportAs: ["forTableColumnResizer"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2881
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: ForTableHeaderRow, selector: "[forTableHeaderRow]", exportAs: ["forTableHeaderRow"] }, { kind: "directive", type: ForTableHeaderCell, selector: "[forTableHeaderCell]", inputs: ["name", "sticky"], exportAs: ["forTableHeaderCell"] }, { kind: "directive", type: ForTableCell, selector: "[forTableCell]", inputs: ["name", "sticky", "disabled"], exportAs: ["forTableCell"] }, { kind: "directive", type: ForTableRow, selector: "[forTableRow]", inputs: ["value", "level", "expandable", "virtualIndex"], exportAs: ["forTableRow"] }, { kind: "directive", type: ForTableRowAttrs, selector: "[forTableRowAttrs]", inputs: ["forTableRowAttrs"] }, { kind: "directive", type: ForTableSortHeader, selector: "[forTableSortHeader]", inputs: ["column", "direction", "disableClear", "firstClickDirection", "sortable"], outputs: ["directionChange", "sortChange"], exportAs: ["forTableSortHeader"] }, { kind: "directive", type: ForTableColumnResizer, selector: "[forTableColumnResizer]", inputs: ["column", "width", "min", "max", "step", "autoFit", "fitIncludesHeader"], outputs: ["widthChange", "resizeCommit"], exportAs: ["forTableColumnResizer"] }, { kind: "directive", type: ForTableColumnReorder, selector: "[forTableColumnReorder]", outputs: ["columnReorder"], exportAs: ["forTableColumnReorder"] }, { kind: "directive", type: ForDraggable, selector: "[forDraggable]", inputs: ["dragData", "dragDisabled"], outputs: ["dragStart", "dragEnd"], exportAs: ["forDraggable"] }, { kind: "directive", type: ForDragPlaceholder, selector: "ng-template[forDragPlaceholder]", exportAs: ["forDragPlaceholder"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2179
2882
  }
2180
2883
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableBody, decorators: [{
2181
2884
  type: Component,
@@ -2189,39 +2892,117 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2189
2892
  ForTableHeaderCell,
2190
2893
  ForTableCell,
2191
2894
  ForTableRow,
2895
+ ForTableRowAttrs,
2192
2896
  ForTableSortHeader,
2193
2897
  ForTableColumnResizer,
2898
+ ForTableColumnReorder,
2899
+ ForDraggable,
2900
+ ForDragPlaceholder,
2194
2901
  ],
2195
2902
  template: `
2196
- <div forTableHeaderRow [style.display]="'grid'" [style.grid-template-columns]="track()">
2197
- @for (col of orderedColumns(); track col.name()) {
2198
- <div
2199
- #headerCell="forTableHeaderCell"
2200
- forTableHeaderCell
2201
- [name]="col.name()"
2202
- [sticky]="col.sticky()"
2203
- forTableSortHeader
2903
+ <ng-template #headerCellContent let-col let-cell="cell">
2904
+ <ng-container
2905
+ [ngTemplateOutlet]="col.header().template"
2906
+ [ngTemplateOutletInjector]="cell.injector"
2907
+ />
2908
+ @if (col.resizable()) {
2909
+ <button
2910
+ forTableColumnResizer
2204
2911
  [column]="col.name()"
2205
- [sortable]="col.sortable()"
2206
- [direction]="directionFor(col.name())"
2207
- (sortChange)="sortChange.emit($event)"
2208
- >
2209
- <ng-container
2210
- [ngTemplateOutlet]="col.header().template"
2211
- [ngTemplateOutletInjector]="headerCell.injector"
2212
- />
2213
- @if (col.resizable()) {
2214
- <button
2215
- forTableColumnResizer
2216
- autoFit
2912
+ [width]="columnWidths()[col.name()]"
2913
+ [min]="col.resizeMin()"
2914
+ [max]="col.resizeMax()"
2915
+ [step]="col.resizeStep()"
2916
+ [autoFit]="col.autoFit()"
2917
+ [fitIncludesHeader]="col.fitIncludesHeader()"
2918
+ [attr.aria-label]="col.resizeAriaLabel()"
2919
+ (widthChange)="onColumnWidthChange(col.name(), $event)"
2920
+ (resizeCommit)="resizeCommit.emit($event)"
2921
+ ></button>
2922
+ }
2923
+ </ng-template>
2924
+
2925
+ @if (hasReorderable()) {
2926
+ <div
2927
+ forTableHeaderRow
2928
+ forTableColumnReorder
2929
+ [style.display]="'grid'"
2930
+ [style.grid-template-columns]="track()"
2931
+ (columnReorder)="columnReorder.emit($event)"
2932
+ >
2933
+ @for (col of orderedColumns(); track col.name()) {
2934
+ @if (col.reorderable()) {
2935
+ <div
2936
+ #headerCell="forTableHeaderCell"
2937
+ forTableHeaderCell
2938
+ [name]="col.name()"
2939
+ [sticky]="col.sticky()"
2940
+ [class]="col.headerClass()"
2941
+ forTableSortHeader
2942
+ [column]="col.name()"
2943
+ [sortable]="col.sortable()"
2944
+ [direction]="directionFor(col.name())"
2945
+ (sortChange)="sortChange.emit($event)"
2946
+ forDraggable
2947
+ [dragData]="col.name()"
2948
+ >
2949
+ <ng-container
2950
+ [ngTemplateOutlet]="headerCellContent"
2951
+ [ngTemplateOutletInjector]="headerCell.injector"
2952
+ [ngTemplateOutletContext]="{ $implicit: col, cell: headerCell }"
2953
+ />
2954
+ @if (columnDragPlaceholder(); as placeholder) {
2955
+ <ng-template forDragPlaceholder>
2956
+ <ng-container [ngTemplateOutlet]="placeholder.template" />
2957
+ </ng-template>
2958
+ }
2959
+ </div>
2960
+ } @else {
2961
+ <div
2962
+ #headerCell="forTableHeaderCell"
2963
+ forTableHeaderCell
2964
+ [name]="col.name()"
2965
+ [sticky]="col.sticky()"
2966
+ [class]="col.headerClass()"
2967
+ forTableSortHeader
2217
2968
  [column]="col.name()"
2218
- [attr.aria-label]="col.resizeAriaLabel()"
2219
- (resizeCommit)="resizeCommit.emit($event)"
2220
- ></button>
2969
+ [sortable]="col.sortable()"
2970
+ [direction]="directionFor(col.name())"
2971
+ (sortChange)="sortChange.emit($event)"
2972
+ >
2973
+ <ng-container
2974
+ [ngTemplateOutlet]="headerCellContent"
2975
+ [ngTemplateOutletInjector]="headerCell.injector"
2976
+ [ngTemplateOutletContext]="{ $implicit: col, cell: headerCell }"
2977
+ />
2978
+ </div>
2221
2979
  }
2222
- </div>
2223
- }
2224
- </div>
2980
+ }
2981
+ </div>
2982
+ } @else {
2983
+ <div forTableHeaderRow [style.display]="'grid'" [style.grid-template-columns]="track()">
2984
+ @for (col of orderedColumns(); track col.name()) {
2985
+ <div
2986
+ #headerCell="forTableHeaderCell"
2987
+ forTableHeaderCell
2988
+ [name]="col.name()"
2989
+ [sticky]="col.sticky()"
2990
+ [class]="col.headerClass()"
2991
+ forTableSortHeader
2992
+ [column]="col.name()"
2993
+ [sortable]="col.sortable()"
2994
+ [direction]="directionFor(col.name())"
2995
+ (sortChange)="sortChange.emit($event)"
2996
+ >
2997
+ <ng-container
2998
+ [ngTemplateOutlet]="headerCellContent"
2999
+ [ngTemplateOutletInjector]="headerCell.injector"
3000
+ [ngTemplateOutletContext]="{ $implicit: col, cell: headerCell }"
3001
+ />
3002
+ </div>
3003
+ }
3004
+ </div>
3005
+ }
2225
3006
  <div
2226
3007
  role="rowgroup"
2227
3008
  [style.position]="sizerHeight() !== null ? 'relative' : null"
@@ -2231,7 +3012,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2231
3012
  @for (placeholder of placeholderRange(); track placeholder) {
2232
3013
  <div forTableRow [style.display]="'grid'" [style.grid-template-columns]="track()">
2233
3014
  @for (col of orderedColumns(); track col.name()) {
2234
- <div #cell="forTableCell" forTableCell [name]="col.name()" [sticky]="col.sticky()">
3015
+ <div
3016
+ #cell="forTableCell"
3017
+ forTableCell
3018
+ [name]="col.name()"
3019
+ [sticky]="col.sticky()"
3020
+ [class]="col.cellClass()"
3021
+ >
2235
3022
  @if (col.placeholderCell(); as placeholderCell) {
2236
3023
  <ng-container
2237
3024
  [ngTemplateOutlet]="placeholderCell.template"
@@ -2246,34 +3033,67 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2246
3033
  @for (r of renderRows(); track r.key) {
2247
3034
  <div
2248
3035
  #rowRef="forTableRow"
3036
+ #rowEl
2249
3037
  forTableRow
2250
3038
  [value]="r.value"
2251
3039
  [virtualIndex]="r.virtualIndex"
2252
3040
  [attr.data-index]="r.virtualIndex"
3041
+ [attr.tabindex]="rowTabIndex(r)"
3042
+ [class]="rowClassFor(r)"
3043
+ [forTableRowAttrs]="rowAttrsFor(r)"
2253
3044
  [style.display]="'grid'"
2254
3045
  [style.grid-template-columns]="track()"
2255
3046
  [style.position]="r.start !== null ? 'absolute' : null"
2256
3047
  [style.left]="r.start !== null ? '0' : null"
2257
3048
  [style.right]="r.start !== null ? '0' : null"
2258
3049
  [style.transform]="r.start !== null ? 'translateY(' + r.start + 'px)' : null"
3050
+ (click)="onRowClick(r, $event)"
3051
+ (keydown.enter)="onRowEnter(r, $event)"
3052
+ (contextmenu)="onRowContextMenu(r, $event)"
2259
3053
  >
2260
3054
  @if (r.variant; as variant) {
2261
- <div
2262
- [attr.role]="cellRole()"
2263
- [attr.aria-colindex]="1"
2264
- [attr.aria-colspan]="orderedColumns().length"
2265
- [attr.data-row-variant]="''"
2266
- [style.grid-column]="'1 / -1'"
2267
- >
2268
- <ng-container
2269
- [ngTemplateOutlet]="variant.cell().template"
2270
- [ngTemplateOutletInjector]="rowRef.injector"
2271
- [ngTemplateOutletContext]="{ $implicit: r.datum, index: r.index }"
2272
- />
2273
- </div>
3055
+ @if (variant.placeholderCells()) {
3056
+ @for (col of orderedColumns(); track col.name()) {
3057
+ <div
3058
+ #cell="forTableCell"
3059
+ forTableCell
3060
+ disabled
3061
+ [name]="col.name()"
3062
+ [sticky]="col.sticky()"
3063
+ [class]="col.cellClass()"
3064
+ >
3065
+ @if (col.placeholderCell(); as placeholderCell) {
3066
+ <ng-container
3067
+ [ngTemplateOutlet]="placeholderCell.template"
3068
+ [ngTemplateOutletInjector]="cell.injector"
3069
+ />
3070
+ }
3071
+ </div>
3072
+ }
3073
+ } @else {
3074
+ <div
3075
+ [attr.role]="cellRole()"
3076
+ [attr.aria-colindex]="1"
3077
+ [attr.aria-colspan]="orderedColumns().length"
3078
+ [attr.data-row-variant]="''"
3079
+ [style.grid-column]="'1 / -1'"
3080
+ >
3081
+ <ng-container
3082
+ [ngTemplateOutlet]="variant.cell()?.template ?? null"
3083
+ [ngTemplateOutletInjector]="rowRef.injector"
3084
+ [ngTemplateOutletContext]="{ $implicit: r.datum, index: r.index }"
3085
+ />
3086
+ </div>
3087
+ }
2274
3088
  } @else {
2275
3089
  @for (col of orderedColumns(); track col.name()) {
2276
- <div #cell="forTableCell" forTableCell [name]="col.name()" [sticky]="col.sticky()">
3090
+ <div
3091
+ #cell="forTableCell"
3092
+ forTableCell
3093
+ [name]="col.name()"
3094
+ [sticky]="col.sticky()"
3095
+ [class]="col.cellClass()"
3096
+ >
2277
3097
  <ng-container
2278
3098
  [ngTemplateOutlet]="col.dataCell().template"
2279
3099
  [ngTemplateOutletInjector]="cell.injector"
@@ -2289,7 +3109,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2289
3109
  <ng-content />
2290
3110
  `,
2291
3111
  }]
2292
- }], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: false }] }], displayedColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayedColumns", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], placeholderRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderRows", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], resizeCommit: [{ type: i0.Output, args: ["resizeCommit"] }], columns: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ForColumnDef), { isSignal: true }] }], rowDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ForRowDef), { isSignal: true }] }] } });
3112
+ }], ctorParameters: () => [], propDecorators: { rowEls: [{ type: i0.ViewChildren, args: ['rowEl', { isSignal: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], rowKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowKey", required: false }] }], displayedColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayedColumns", required: false }] }], sort: [{ type: i0.Input, args: [{ isSignal: true, alias: "sort", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], placeholderRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderRows", required: false }] }], measureRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "measureRows", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], resizeCommit: [{ type: i0.Output, args: ["resizeCommit"] }], columnReorder: [{ type: i0.Output, args: ["columnReorder"] }], columnWidths: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnWidths", required: false }] }, { type: i0.Output, args: ["columnWidthsChange"] }], interactiveRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactiveRows", required: false }] }], rowActivate: [{ type: i0.Output, args: ["rowActivate"] }], rowContextMenu: [{ type: i0.Output, args: ["rowContextMenu"] }], rowClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClass", required: false }] }], rowAttrs: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowAttrs", required: false }] }], columns: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ForColumnDef), { isSignal: true }] }], rowDefs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => ForRowDef), { isSignal: true }] }], columnDragPlaceholder: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ForColumnDragPlaceholder), { isSignal: true }] }] } });
2293
3113
 
2294
3114
  /**
2295
3115
  * Decorative per-row selection affordance inside a `[forTableRow]`. Clicking it
@@ -2412,157 +3232,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2412
3232
  }]
2413
3233
  }], ctorParameters: () => [] });
2414
3234
 
2415
- /**
2416
- * Opt-in **column reordering** for `ForTable`, composed over the drag-drop primitive.
2417
- *
2418
- * Apply on the `[forTableHeaderRow]` element. It wraps `[forDropList]` (via
2419
- * `hostDirectives`) so the header cells become a reorderable list, then translates
2420
- * drag-drop's generic drop into the table-friendly `columnReorder` output. Mark each
2421
- * `[forTableHeaderCell]` as `[forDraggable]` with `[dragData]` set to the column name.
2422
- * On a committed drop (pointer or keyboard) it emits the previous / new index and the
2423
- * new column-name order; the consumer applies it to their own column array. **It never
2424
- * reorders columns itself** (BYO-data).
2425
- *
2426
- * The wrapped list defaults to `orientation="horizontal"` (a column reorder is always along
2427
- * the row axis), so no `orientation` binding is needed. Bind `orientation="vertical"` to
2428
- * override for the rare case.
2429
- *
2430
- * In `mode="grid"` / `mode="treegrid"` the draggable header cells join the table's composite
2431
- * roving grid as its first row, so a sortable + column-reorderable grid keeps the **single
2432
- * tab stop** the WAI-ARIA Data Grid pattern calls for: `Tab` enters the grid once, Arrow keys
2433
- * cross between header and body, and `Space` on a header cell lifts it for keyboard reordering.
2434
- * It hands its drop-list roving to the grid via `FOR_DROP_LIST_ROVING_DELEGATE` and routes idle
2435
- * header navigation through the table's grid keyboard handler.
2436
- *
2437
- * When a header cell is both sortable (`[forTableSortHeader]`) and reorderable, the two
2438
- * keyboard activations split along WAI-ARIA lines so a single key never both sorts and lifts:
2439
- * `Space` lifts the column, `Enter` toggles the sort. The split is enforced via a
2440
- * `FOR_DRAGGABLE_LIFT_GUARD` that defers `Enter` to the sort header on cells carrying the
2441
- * `data-sortable` marker — detected by DOM marker, so `forty-cdk/drag-drop` needs no table
2442
- * import. A reorder-only header (no sort header) still lifts on both `Enter` and `Space`.
2443
- *
2444
- * @example
2445
- * ```html
2446
- * <div forTableHeaderRow forTableColumnReorder (columnReorder)="columns.set($event.columns)">
2447
- * @for (col of columns(); track col) {
2448
- * <div forTableHeaderCell [name]="col" forDraggable [dragData]="col">{{ col }}</div>
2449
- * }
2450
- * </div>
2451
- * ```
2452
- */
2453
- class ForTableColumnReorder {
2454
- ctx = injectTableContext('ForTableColumnReorder');
2455
- #list = inject(ForDropList);
2456
- #host = inject(ElementRef).nativeElement;
2457
- #isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
2458
- /**
2459
- * Fires once per committed reorder gesture (pointer drop or keyboard drop) with the
2460
- * previous / new column index and the full column-name order after the move.
2461
- */
2462
- columnReorder = output();
2463
- constructor() {
2464
- const destroyRef = inject(DestroyRef);
2465
- const sub = this.#list.dragDrop.subscribe((event) => this.#emit(event));
2466
- destroyRef.onDestroy(() => sub.unsubscribe());
2467
- if (this.#isBrowser) {
2468
- const onCaptureKeydown = (event) => this.#onCaptureKeydown(event);
2469
- this.#host.addEventListener('keydown', onCaptureKeydown, { capture: true });
2470
- destroyRef.onDestroy(() => this.#host.removeEventListener('keydown', onCaptureKeydown, { capture: true }));
2471
- }
2472
- }
2473
- /**
2474
- * When the header row joins the composite grid, intercepts idle (not-lifted) Arrow /
2475
- * Home / End / Page keys on a draggable header cell in the capture phase and routes them
2476
- * through the table's grid navigation, then stops propagation so the draggable's own
2477
- * keydown does not also navigate. Space / Enter (and every key while a keyboard drag is in
2478
- * progress) fall through untouched, so the draggable still owns the lift / move / drop /
2479
- * cancel gesture — keeping the header one navigation continuum with the body while
2480
- * preserving column reordering.
2481
- */
2482
- #onCaptureKeydown(event) {
2483
- if (!this.ctx.headerParticipatesInRoving()) {
2484
- return;
2485
- }
2486
- const target = event.target;
2487
- if (!(target instanceof HTMLElement)) {
2488
- return;
2489
- }
2490
- if (!this.#list.items().some((handle) => handle.host === target) ||
2491
- this.#list.isLifted(target)) {
2492
- return;
2493
- }
2494
- if (this.ctx.handleHeaderCellKeydown(event, target)) {
2495
- event.stopPropagation();
2496
- }
2497
- }
2498
- #emit(event) {
2499
- const names = this.#list.items().map((handle) => String(handle.data()));
2500
- const columns = moveItemInArray(names, event.previousIndex, event.currentIndex);
2501
- this.columnReorder.emit({ from: event.previousIndex, to: event.currentIndex, columns });
2502
- }
2503
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableColumnReorder, deps: [], target: i0.ɵɵFactoryTarget.Directive });
2504
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForTableColumnReorder, isStandalone: true, selector: "[forTableColumnReorder]", outputs: { columnReorder: "columnReorder" }, providers: [
2505
- { provide: FOR_DROP_LIST_DEFAULT_ORIENTATION, useValue: 'horizontal' },
2506
- {
2507
- provide: FOR_DROP_LIST_ROVING_DELEGATE,
2508
- useFactory: () => {
2509
- const ctx = injectTableContext('ForTableColumnReorder');
2510
- return {
2511
- itemTabindex: (el) => ctx.headerParticipatesInRoving() ? ctx.headerCellTabIndex(el) : null,
2512
- isItemHighlighted: (el) => ctx.headerParticipatesInRoving() ? ctx.isCellHighlighted(el) : null,
2513
- };
2514
- },
2515
- },
2516
- {
2517
- provide: FOR_DRAGGABLE_LIFT_GUARD,
2518
- useValue: {
2519
- canLiftOnKey: (event, host) => !(event.key === 'Enter' && hostHasSortActivation(host)),
2520
- },
2521
- },
2522
- ], exportAs: ["forTableColumnReorder"], hostDirectives: [{ directive: i1.ForDropList, inputs: ["orientation", "orientation", "dir", "dir", "disabled", "disabled", "autoScroll", "autoScroll", "animateReorder", "animateReorder", "liveSort", "liveSort", "boundary", "boundary", "lockAxis", "lockAxis"] }], ngImport: i0 });
2523
- }
2524
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTableColumnReorder, decorators: [{
2525
- type: Directive,
2526
- args: [{
2527
- selector: '[forTableColumnReorder]',
2528
- exportAs: 'forTableColumnReorder',
2529
- providers: [
2530
- { provide: FOR_DROP_LIST_DEFAULT_ORIENTATION, useValue: 'horizontal' },
2531
- {
2532
- provide: FOR_DROP_LIST_ROVING_DELEGATE,
2533
- useFactory: () => {
2534
- const ctx = injectTableContext('ForTableColumnReorder');
2535
- return {
2536
- itemTabindex: (el) => ctx.headerParticipatesInRoving() ? ctx.headerCellTabIndex(el) : null,
2537
- isItemHighlighted: (el) => ctx.headerParticipatesInRoving() ? ctx.isCellHighlighted(el) : null,
2538
- };
2539
- },
2540
- },
2541
- {
2542
- provide: FOR_DRAGGABLE_LIFT_GUARD,
2543
- useValue: {
2544
- canLiftOnKey: (event, host) => !(event.key === 'Enter' && hostHasSortActivation(host)),
2545
- },
2546
- },
2547
- ],
2548
- hostDirectives: [
2549
- {
2550
- directive: ForDropList,
2551
- inputs: [
2552
- 'orientation',
2553
- 'dir',
2554
- 'disabled',
2555
- 'autoScroll',
2556
- 'animateReorder',
2557
- 'liveSort',
2558
- 'boundary',
2559
- 'lockAxis',
2560
- ],
2561
- },
2562
- ],
2563
- }]
2564
- }], ctorParameters: () => [], propDecorators: { columnReorder: [{ type: i0.Output, args: ["columnReorder"] }] } });
2565
-
2566
3235
  const POINTER_ARM_THRESHOLD_PX = 5;
2567
3236
  const LIFTED_NAV_KEYS = new Set([
2568
3237
  'ArrowDown',
@@ -2978,5 +3647,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2978
3647
  * Generated bundle index. Do not edit.
2979
3648
  */
2980
3649
 
2981
- export { FOR_TABLE_CONTEXT, ForColumnDef, ForDataCell, ForHeaderCell, ForPlaceholderCell, ForRowCell, ForRowDef, ForTable, ForTableBody, ForTableCell, ForTableColumnLabel, ForTableColumnReorder, ForTableColumnResizer, ForTableHeaderCell, ForTableHeaderRow, ForTableRow, ForTableRowReorder, ForTableRowSelector, ForTableSelectAll, ForTableSortHeader };
3650
+ export { FOR_TABLE_CONTEXT, ForColumnDef, ForColumnDragPlaceholder, ForDataCell, ForHeaderCell, ForPlaceholderCell, ForRowCell, ForRowDef, ForTable, ForTableBody, ForTableCell, ForTableColumnLabel, ForTableColumnReorder, ForTableColumnResizer, ForTableHeaderCell, ForTableHeaderRow, ForTableRow, ForTableRowReorder, ForTableRowSelector, ForTableSelectAll, ForTableSortHeader };
2982
3651
  //# sourceMappingURL=forty-cdk-table.mjs.map