@mk-kit/ui 0.43.0 → 0.45.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,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, TemplateRef, Directive, input, ElementRef, Injector, PLATFORM_ID, DestroyRef, signal, effect, afterNextRender, booleanAttribute, numberAttribute, model, output, computed, viewChild, contentChild, contentChildren, ChangeDetectionStrategy, Component } from '@angular/core';
2
+ import { inject, TemplateRef, Directive, input, ElementRef, Injector, PLATFORM_ID, DestroyRef, signal, effect, afterNextRender, afterRenderEffect, untracked, booleanAttribute, numberAttribute, model, output, computed, viewChild, contentChild, contentChildren, ChangeDetectionStrategy, Component } from '@angular/core';
3
3
  import { DOCUMENT, isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
4
4
  import { MkLiveAnnouncer, MK_I18N, mkUniqueId, mkQueryCompact } from '@mk-kit/ui/core';
5
5
  import { MkCheckbox } from '@mk-kit/ui/checkbox';
@@ -171,6 +171,53 @@ function mkExportCsv(rows, columns, options = {}) {
171
171
 
172
172
  /** Hard floor (px) for column resize when a column sets no `minWidth`. */
173
173
  const MIN_COL_WIDTH = 60;
174
+ /**
175
+ * Row height (px) assumed by `virtual` until the first row is measured: the
176
+ * comfortable density row — `--mk-space-3` padding twice around a
177
+ * `--mk-font-size-sm` line, plus the 1px row border.
178
+ */
179
+ const DEFAULT_ROW_HEIGHT = 44;
180
+ /** Rows rendered before the viewport has been measured (SSR, first paint). */
181
+ const UNMEASURED_VIEWPORT_ROWS = 20;
182
+ const ISO_DAY = /^\d{4}-\d{2}-\d{2}/;
183
+ /** Local calendar day (`YYYY-MM-DD`) of a Date, ISO string or timestamp. */
184
+ function dayKey(value) {
185
+ if (value == null || value === '')
186
+ return null;
187
+ if (typeof value === 'string' && ISO_DAY.test(value))
188
+ return value.slice(0, 10);
189
+ const d = value instanceof Date ? value : new Date(value);
190
+ if (Number.isNaN(d.getTime()))
191
+ return null;
192
+ const pad = (n) => String(n).padStart(2, '0');
193
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
194
+ }
195
+ /** Whether a filter value is a `{ min, max }` range rather than a single bound. */
196
+ function isFilterRange(v) {
197
+ return (typeof v === 'object' && v !== null && !(v instanceof Date) && ('min' in v || 'max' in v));
198
+ }
199
+ /** `null`, `''` and empty ranges all mean "no filter on this column". */
200
+ function isEmptyFilter(v) {
201
+ if (v == null || v === '')
202
+ return true;
203
+ if (isFilterRange(v))
204
+ return isEmptyFilter(v.min) && isEmptyFilter(v.max);
205
+ return false;
206
+ }
207
+ /** Drop the empty entries of a filter map (`null` when nothing is left). */
208
+ function mkCompactFilters(filters) {
209
+ if (!filters)
210
+ return null;
211
+ const out = {};
212
+ let any = false;
213
+ for (const key of Object.keys(filters)) {
214
+ if (isEmptyFilter(filters[key]))
215
+ continue;
216
+ out[key] = filters[key];
217
+ any = true;
218
+ }
219
+ return any ? out : null;
220
+ }
174
221
  /** Upper bound advertised on resize separators (`aria-valuemax`). */
175
222
  const MAX_COL_WIDTH = 2000;
176
223
  /**
@@ -178,6 +225,8 @@ const MAX_COL_WIDTH = 2000;
178
225
  * Supply `columns` and `data`; opt into sortable columns, sticky header,
179
226
  * zebra striping, hover and density. Sorting is fully keyboard operable
180
227
  * (Enter/Space on a header) and announces changes via {@link MkLiveAnnouncer}.
228
+ * `virtual` windows the rows of very large tables; `filterable` adds a
229
+ * per-column filter row (`[(filters)]`).
181
230
  *
182
231
  * ```html
183
232
  * <mk-table
@@ -217,13 +266,45 @@ class MkTable {
217
266
  stacked = signal(false, /* @ts-ignore */
218
267
  ...(ngDevMode ? [{ debugName: "stacked" }] : /* istanbul ignore next */ []));
219
268
  constructor() {
220
- // Keep the sticky group-header offset in sync with the rendered thead
221
- // height (it shifts with density, sticky mode and grouping itself).
269
+ // Keep the sticky offsets (filter row under the header row, group headers
270
+ // under the thead) in sync with the rendered heights they shift with
271
+ // density, sticky mode, the filter row and grouping itself.
222
272
  effect(() => {
223
273
  this.stickyHeader();
224
274
  this.density();
225
275
  this.groupBy();
226
- afterNextRender({ read: () => this.applyGroupTop() }, { injector: this.injector });
276
+ this.filterable();
277
+ this.virtual();
278
+ afterNextRender({ read: () => this.applyStickyOffsets() }, { injector: this.injector });
279
+ });
280
+ // Virtual mode: after every render that could change what is on screen,
281
+ // read back the real row / detail heights and the viewport, so the
282
+ // spacers and the window stay honest for whatever density is in force.
283
+ afterRenderEffect({
284
+ read: () => {
285
+ if (!this.isVirtual())
286
+ return;
287
+ this.windowRange();
288
+ this.density();
289
+ this.expandedKeys();
290
+ const el = this.scroller()?.nativeElement;
291
+ if (el)
292
+ untracked(() => this.measureViewport(el));
293
+ },
294
+ });
295
+ // Announce how many rows a change of filters left (the visible count).
296
+ let firstFilters = true;
297
+ effect(() => {
298
+ this.filters();
299
+ if (firstFilters) {
300
+ firstFilters = false;
301
+ return;
302
+ }
303
+ untracked(() => {
304
+ if (!this.clientFilter())
305
+ return;
306
+ this.announcer.announce(this.i18n.resultsCount(this.allRows().length));
307
+ });
227
308
  });
228
309
  // Watch the host's width against `stackAt`. ResizeObserver rather than
229
310
  // matchMedia because the trigger is the element's width, not the window's.
@@ -240,17 +321,43 @@ class MkTable {
240
321
  this.stacked.set(limit > 0 && entry.contentRect.width < limit);
241
322
  });
242
323
  observer.observe(el);
243
- this.destroyRef.onDestroy(() => observer.disconnect());
324
+ // The scroller's height is the virtual viewport; the table grows or
325
+ // shrinks when a density change re-sizes its rows.
326
+ const scroller = this.scroller()?.nativeElement;
327
+ const viewport = new ResizeObserver(() => {
328
+ if (scroller && this.isVirtual())
329
+ this.measureViewport(scroller);
330
+ });
331
+ if (scroller) {
332
+ viewport.observe(scroller);
333
+ const table = scroller.querySelector('table');
334
+ if (table)
335
+ viewport.observe(table);
336
+ }
337
+ this.destroyRef.onDestroy(() => {
338
+ observer.disconnect();
339
+ viewport.disconnect();
340
+ });
244
341
  },
245
342
  }, { injector: this.injector });
246
343
  }
247
- /** Measures the thead and exposes it as the group rows' sticky offset. */
248
- applyGroupTop() {
344
+ /**
345
+ * Measures the header row and the thead and exposes them as the sticky
346
+ * offsets of the filter row and of the group rows.
347
+ */
348
+ applyStickyOffsets() {
349
+ const host = this.host.nativeElement;
350
+ const sticky = this.stickyHeader() || this.isVirtual();
351
+ if (this.filterable()) {
352
+ const headRow = host.querySelector('thead > tr');
353
+ const h = sticky && headRow ? headRow.getBoundingClientRect().height : 0;
354
+ host.style.setProperty('--_filter-top', `${Math.round(h)}px`);
355
+ }
249
356
  if (this.groupBy() == null)
250
357
  return;
251
- const thead = this.host.nativeElement.querySelector('thead');
252
- const h = this.stickyHeader() && thead ? thead.getBoundingClientRect().height : 0;
253
- this.host.nativeElement.style.setProperty('--_group-top', `${Math.round(h)}px`);
358
+ const thead = host.querySelector('thead');
359
+ const h = sticky && thead ? thead.getBoundingClientRect().height : 0;
360
+ host.style.setProperty('--_group-top', `${Math.round(h)}px`);
254
361
  }
255
362
  /** Column definitions (order = display order). */
256
363
  columns = input.required(/* @ts-ignore */
@@ -334,6 +441,55 @@ class MkTable {
334
441
  /** Formats a group header label; defaults to `String(value)`. */
335
442
  groupLabel = input(null, /* @ts-ignore */
336
443
  ...(ngDevMode ? [{ debugName: "groupLabel" }] : /* istanbul ignore next */ []));
444
+ // --- Virtualisation inputs -------------------------------------------------
445
+ /**
446
+ * Render only the rows in view (plus {@link overscan}) — for tables of
447
+ * thousands of rows. The `<table>` scrolls inside its own box sized by
448
+ * {@link height} / {@link maxHeight} (`max-height: 60vh` when neither is
449
+ * set) with the header pinned; spacer rows keep the scrollbar honest.
450
+ *
451
+ * Works with sorting, selection (select-all still covers every row), tree
452
+ * rows, grouping (group headers are rows too), expandable detail rows (their
453
+ * height is measured once rendered; an unmeasured detail counts as one row)
454
+ * and the header filter row. Falls back to the full render while stacked
455
+ * into cards. Export and select-all always see every row, not the window.
456
+ */
457
+ virtual = input(false, { ...(ngDevMode ? { debugName: "virtual" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
458
+ /**
459
+ * Height (px) of one row in virtual mode. Leave unset to have it measured
460
+ * from the first rendered row — which follows `density` and
461
+ * `data-mk-density` automatically — starting from 44 (comfortable).
462
+ */
463
+ rowHeight = input(null, { ...(ngDevMode ? { debugName: "rowHeight" } : /* istanbul ignore next */ {}), transform: (v) => (v == null || v === '' ? null : numberAttribute(v)) });
464
+ /** Rows rendered beyond each edge of the viewport in virtual mode. */
465
+ overscan = input(6, { ...(ngDevMode ? { debugName: "overscan" } : /* istanbul ignore next */ {}), transform: numberAttribute });
466
+ /** Fixed height of the scroll box (CSS length, or px as a number). */
467
+ height = input(null, /* @ts-ignore */
468
+ ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
469
+ /** Maximum height of the scroll box (CSS length, or px as a number). */
470
+ maxHeight = input(null, /* @ts-ignore */
471
+ ...(ngDevMode ? [{ debugName: "maxHeight" }] : /* istanbul ignore next */ []));
472
+ // --- Filter row inputs -----------------------------------------------------
473
+ /**
474
+ * Render a second header row with a filter control per column — a text
475
+ * box, a select, a number or a date field as the column's
476
+ * {@link MkTableColumn.filter} says. Values live in {@link filters}.
477
+ */
478
+ filterable = input(false, { ...(ngDevMode ? { debugName: "filterable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
479
+ /**
480
+ * The active filters, keyed by column key (`[(filters)]`; `filtersChange`
481
+ * emits the whole map on every edit). Text filters hold the typed string,
482
+ * select filters the chosen option value, number / date filters the lower
483
+ * bound or a `{ min, max }` range. Set programmatically to pre-filter.
484
+ */
485
+ filters = model({}, /* @ts-ignore */
486
+ ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
487
+ /**
488
+ * Filter rows in the browser (default). Turn off when the server applies
489
+ * the filters — `MkTableDataSource.setFilters($event)` on `filtersChange` —
490
+ * so the page it returns is shown as-is.
491
+ */
492
+ clientFilter = input(true, { ...(ngDevMode ? { debugName: "clientFilter" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
337
493
  /** Emitted when the sort column/direction changes. */
338
494
  sortChange = output();
339
495
  /** Emitted when a row is clicked (enable via `clickableRows`). */
@@ -728,8 +884,226 @@ class MkTable {
728
884
  * large-table sorts several-fold faster with the same default-locale order.
729
885
  */
730
886
  /** Data sorted by the active column, or the input order when unsorted. */
731
- sortedData = computed(() => this.sortRows(this.data()), /* @ts-ignore */
887
+ sortedData = computed(() => this.sortRows(this.filteredData()), /* @ts-ignore */
732
888
  ...(ngDevMode ? [{ debugName: "sortedData" }] : /* istanbul ignore next */ []));
889
+ // --- Filtering ---------------------------------------------------------------
890
+ /** The filter control a column renders, or `null` for none. */
891
+ filterKind(col) {
892
+ if (col.filter === false)
893
+ return null;
894
+ return col.filter ?? 'text';
895
+ }
896
+ /** One predicate per active filter, or `null` when nothing is filtered. */
897
+ rowPredicate = computed(() => {
898
+ if (!this.clientFilter())
899
+ return null;
900
+ const filters = this.filters();
901
+ const tests = [];
902
+ for (const col of this.columns()) {
903
+ const value = filters[col.key];
904
+ if (col.filter === false || isEmptyFilter(value))
905
+ continue;
906
+ tests.push(this.columnTest(col, value));
907
+ }
908
+ if (!tests.length)
909
+ return null;
910
+ return (row) => tests.every((test) => test(row));
911
+ }, /* @ts-ignore */
912
+ ...(ngDevMode ? [{ debugName: "rowPredicate" }] : /* istanbul ignore next */ []));
913
+ /** Build the predicate for one column's filter value. */
914
+ columnTest(col, value) {
915
+ const raw = (row) => row[col.key];
916
+ switch (col.filter) {
917
+ case 'select': {
918
+ const wanted = String(value);
919
+ return (row) => String(raw(row) ?? '') === wanted;
920
+ }
921
+ case 'number': {
922
+ const [lo, hi] = isFilterRange(value) ? [value.min, value.max] : [value, null];
923
+ const min = isEmptyFilter(lo) ? null : Number(lo);
924
+ const max = isEmptyFilter(hi) ? null : Number(hi);
925
+ return (row) => {
926
+ const v = raw(row);
927
+ const n = typeof v === 'number' ? v : Number(v);
928
+ if (v == null || v === '' || Number.isNaN(n))
929
+ return false;
930
+ return (min == null || n >= min) && (max == null || n <= max);
931
+ };
932
+ }
933
+ case 'date': {
934
+ const [lo, hi] = isFilterRange(value) ? [value.min, value.max] : [value, null];
935
+ const min = dayKey(lo);
936
+ const max = dayKey(hi);
937
+ return (row) => {
938
+ const day = dayKey(raw(row));
939
+ if (!day)
940
+ return false;
941
+ return (!min || day >= min) && (!max || day <= max);
942
+ };
943
+ }
944
+ default: {
945
+ const needle = String(value).trim().toLowerCase();
946
+ if (!needle)
947
+ return () => true;
948
+ return (row) => this.cellText(row, col).toLowerCase().includes(needle);
949
+ }
950
+ }
951
+ }
952
+ /**
953
+ * Tree mode: which rows survive the filters — a row is kept when it matches
954
+ * or any descendant does, so a matching child keeps its parents.
955
+ */
956
+ treeKeep = computed(() => {
957
+ const keep = this.rowPredicate();
958
+ const map = new Map();
959
+ if (!keep || !this.childrenKey())
960
+ return map;
961
+ const walk = (row) => {
962
+ let any = keep(row);
963
+ for (const child of this.childrenOf(row))
964
+ if (walk(child))
965
+ any = true;
966
+ map.set(row, any);
967
+ return any;
968
+ };
969
+ for (const row of this.data())
970
+ walk(row);
971
+ return map;
972
+ }, /* @ts-ignore */
973
+ ...(ngDevMode ? [{ debugName: "treeKeep" }] : /* istanbul ignore next */ []));
974
+ /** The input rows with the active filters applied (input order). */
975
+ filteredData = computed(() => {
976
+ const keep = this.rowPredicate();
977
+ const data = this.data();
978
+ if (!keep)
979
+ return data;
980
+ if (!this.childrenKey())
981
+ return data.filter(keep);
982
+ const kept = this.treeKeep();
983
+ return data.filter((row) => kept.get(row) === true);
984
+ }, /* @ts-ignore */
985
+ ...(ngDevMode ? [{ debugName: "filteredData" }] : /* istanbul ignore next */ []));
986
+ /** The child rows of `row` that survive the filters (tree mode). */
987
+ visibleChildrenOf(row) {
988
+ const children = this.childrenOf(row);
989
+ if (!children.length || !this.rowPredicate())
990
+ return children;
991
+ const kept = this.treeKeep();
992
+ return children.filter((child) => kept.get(child) === true);
993
+ }
994
+ /** Options of every `select` filter, keyed by column (derived when unset). */
995
+ selectOptions = computed(() => {
996
+ const map = new Map();
997
+ for (const col of this.columns()) {
998
+ if (col.filter !== 'select')
999
+ continue;
1000
+ if (col.filterOptions) {
1001
+ map.set(col.key, col.filterOptions.map((o) => typeof o === 'object' ? o : { value: o, label: String(o) }));
1002
+ continue;
1003
+ }
1004
+ const seen = new Map();
1005
+ const walk = (rows) => {
1006
+ for (const row of rows) {
1007
+ const v = row[col.key];
1008
+ if (v != null && v !== '')
1009
+ seen.set(String(v), v);
1010
+ walk(this.childrenOf(row));
1011
+ }
1012
+ };
1013
+ walk(this.data());
1014
+ map.set(col.key, [...seen.entries()]
1015
+ .sort(([a], [b]) => sortCollator().compare(a, b))
1016
+ .map(([label, value]) => ({ value, label })));
1017
+ }
1018
+ return map;
1019
+ }, /* @ts-ignore */
1020
+ ...(ngDevMode ? [{ debugName: "selectOptions" }] : /* istanbul ignore next */ []));
1021
+ /** The options a column's select filter offers. */
1022
+ filterOptionsFor(col) {
1023
+ return this.selectOptions().get(col.key) ?? [];
1024
+ }
1025
+ /** Whether `option` is the column's current select-filter value. */
1026
+ isFilterOption(col, option) {
1027
+ const v = this.filters()[col.key];
1028
+ return !isEmptyFilter(v) && String(v) === String(option.value);
1029
+ }
1030
+ /** Whether a column has an active filter. */
1031
+ hasFilter(key) {
1032
+ return !isEmptyFilter(this.filters()[key]);
1033
+ }
1034
+ /** The text a column's filter box shows (a range shows its lower bound). */
1035
+ filterText(key) {
1036
+ const v = this.filters()[key];
1037
+ if (isEmptyFilter(v))
1038
+ return '';
1039
+ const shown = isFilterRange(v) ? v.min : v;
1040
+ if (shown instanceof Date)
1041
+ return dayKey(shown) ?? '';
1042
+ return shown == null ? '' : String(shown);
1043
+ }
1044
+ /** Accessible name of a column's filter control. */
1045
+ filterLabel(col) {
1046
+ return this.i18n.filterColumn(col.header || col.key);
1047
+ }
1048
+ /** Placeholder of a column's filter control. */
1049
+ filterPlaceholder(col) {
1050
+ if (col.filterPlaceholder != null)
1051
+ return col.filterPlaceholder;
1052
+ return this.filterKind(col) === 'text' ? this.i18n.filter : this.i18n.filterMin;
1053
+ }
1054
+ /**
1055
+ * Set one column's filter (`null` / `''` clears it). Updates
1056
+ * {@link filters} and emits `filtersChange`; a no-op when unchanged.
1057
+ */
1058
+ setFilter(key, value) {
1059
+ const current = this.filters();
1060
+ const empty = isEmptyFilter(value);
1061
+ if (empty ? !(key in current) : Object.is(current[key], value))
1062
+ return;
1063
+ const next = { ...current };
1064
+ if (empty)
1065
+ delete next[key];
1066
+ else
1067
+ next[key] = value;
1068
+ this.filters.set(next);
1069
+ }
1070
+ /** Clear one column's filter. */
1071
+ clearFilter(key) {
1072
+ this.setFilter(key, null);
1073
+ }
1074
+ /** Clear every filter. */
1075
+ clearFilters() {
1076
+ if (Object.keys(this.filters()).length)
1077
+ this.filters.set({});
1078
+ }
1079
+ /** Text / number / date filter box input. */
1080
+ onFilterInput(col, event) {
1081
+ const text = event.target.value;
1082
+ if (col.filter === 'number') {
1083
+ const n = text === '' ? NaN : Number(text);
1084
+ this.setFilter(col.key, Number.isNaN(n) ? null : n);
1085
+ }
1086
+ else {
1087
+ this.setFilter(col.key, text);
1088
+ }
1089
+ }
1090
+ /** Select filter change: map the option string back to its original value. */
1091
+ onFilterSelect(col, event) {
1092
+ const chosen = event.target.value;
1093
+ const option = this.filterOptionsFor(col).find((o) => String(o.value) === chosen);
1094
+ this.setFilter(col.key, chosen === '' || !option ? null : option.value);
1095
+ }
1096
+ /** Escape in a filter box clears that filter (and stays in the box). */
1097
+ onFilterKeydown(col, event) {
1098
+ if (event.key !== 'Escape' || !this.hasFilter(col.key))
1099
+ return;
1100
+ event.preventDefault();
1101
+ event.stopPropagation();
1102
+ this.clearFilter(col.key);
1103
+ }
1104
+ /** Header rows above the body (for `aria-rowindex`). */
1105
+ headerRows = computed(() => 1 + (this.filterable() && !this.stacked() ? 1 : 0), /* @ts-ignore */
1106
+ ...(ngDevMode ? [{ debugName: "headerRows" }] : /* istanbul ignore next */ []));
733
1107
  /** Sort one sibling group by the active column (input order when unsorted). */
734
1108
  sortRows(rows) {
735
1109
  const key = this.sortKey();
@@ -850,10 +1224,10 @@ class MkTable {
850
1224
  const walk = (rows) => {
851
1225
  for (const row of this.sortRows(rows)) {
852
1226
  out.push(row);
853
- walk(this.childrenOf(row));
1227
+ walk(this.visibleChildrenOf(row));
854
1228
  }
855
1229
  };
856
- walk(this.data());
1230
+ walk(this.filteredData());
857
1231
  return out;
858
1232
  }, /* @ts-ignore */
859
1233
  ...(ngDevMode ? [{ debugName: "allRows" }] : /* istanbul ignore next */ []));
@@ -957,7 +1331,7 @@ class MkTable {
957
1331
  const tree = !!this.childrenKey();
958
1332
  const expandedKeys = this.treeExpanded();
959
1333
  for (const row of rows) {
960
- const children = tree ? this.childrenOf(row) : [];
1334
+ const children = tree ? this.visibleChildrenOf(row) : [];
961
1335
  const hasChildren = children.length > 0;
962
1336
  const expanded = hasChildren && expandedKeys.has(this.rowKey(row));
963
1337
  items.push({ kind: 'row', row, depth, hasChildren, expanded });
@@ -973,6 +1347,236 @@ class MkTable {
973
1347
  const value = row[key];
974
1348
  return Array.isArray(value) ? value : [];
975
1349
  }
1350
+ // --- Row virtualisation -------------------------------------------------------
1351
+ // Own windowing rather than `mk-virtual-scroll` from `@mk-kit/ui/data`: the
1352
+ // rows must stay real `<tr>`s inside the one `<table>` (column widths,
1353
+ // sticky header, selection, a11y), the window has to know about group and
1354
+ // detail rows, and importing the data entry point would drag ~680 KiB of
1355
+ // unrelated components into every table consumer's dependency graph.
1356
+ /** The scroll box around the table. */
1357
+ scroller = viewChild('scroller', /* @ts-ignore */
1358
+ ...(ngDevMode ? [{ debugName: "scroller" }] : /* istanbul ignore next */ []));
1359
+ /** Virtual mode in force (cards always render in full). */
1360
+ isVirtual = computed(() => this.virtual() && !this.stacked(), /* @ts-ignore */
1361
+ ...(ngDevMode ? [{ debugName: "isVirtual" }] : /* istanbul ignore next */ []));
1362
+ /** Current scroll offset of the scroll box (virtual mode). */
1363
+ scrollTop = signal(0, /* @ts-ignore */
1364
+ ...(ngDevMode ? [{ debugName: "scrollTop" }] : /* istanbul ignore next */ []));
1365
+ /** Measured height of the scroll box (0 until measured / on the server). */
1366
+ viewportHeight = signal(0, /* @ts-ignore */
1367
+ ...(ngDevMode ? [{ debugName: "viewportHeight" }] : /* istanbul ignore next */ []));
1368
+ /** Measured height of the thead — the sticky header hides that much of the top. */
1369
+ headHeight = signal(0, /* @ts-ignore */
1370
+ ...(ngDevMode ? [{ debugName: "headHeight" }] : /* istanbul ignore next */ []));
1371
+ /** Row height read back from the first rendered row. */
1372
+ measuredRowHeight = signal(null, /* @ts-ignore */
1373
+ ...(ngDevMode ? [{ debugName: "measuredRowHeight" }] : /* istanbul ignore next */ []));
1374
+ /** Measured heights of expanded detail rows, by row key. */
1375
+ detailHeights = signal(new Map(), /* @ts-ignore */
1376
+ ...(ngDevMode ? [{ debugName: "detailHeights" }] : /* istanbul ignore next */ []));
1377
+ /** The row height virtual mode lays rows out with. */
1378
+ effectiveRowHeight = computed(() => this.rowHeight() ?? this.measuredRowHeight() ?? DEFAULT_ROW_HEIGHT, /* @ts-ignore */
1379
+ ...(ngDevMode ? [{ debugName: "effectiveRowHeight" }] : /* istanbul ignore next */ []));
1380
+ cssLength(v) {
1381
+ if (v == null || v === '')
1382
+ return null;
1383
+ return typeof v === 'number' ? `${v}px` : v;
1384
+ }
1385
+ /** `height` of the scroll box. */
1386
+ scrollHeight = computed(() => this.cssLength(this.height()), /* @ts-ignore */
1387
+ ...(ngDevMode ? [{ debugName: "scrollHeight" }] : /* istanbul ignore next */ []));
1388
+ /** `max-height` of the scroll box (60vh in virtual mode with no size given). */
1389
+ scrollMaxHeight = computed(() => {
1390
+ const max = this.cssLength(this.maxHeight());
1391
+ if (max)
1392
+ return max;
1393
+ return this.isVirtual() && this.height() == null ? '60vh' : null;
1394
+ }, /* @ts-ignore */
1395
+ ...(ngDevMode ? [{ debugName: "scrollMaxHeight" }] : /* istanbul ignore next */ []));
1396
+ /**
1397
+ * Top offset of every display item, or `null` while every item is exactly
1398
+ * one row tall (the common case — then offsets are plain multiplication).
1399
+ * Only expanded detail rows make heights vary.
1400
+ */
1401
+ itemOffsets = computed(() => {
1402
+ if (!this.isVirtual() || !this.expandable() || !this.rowDetail())
1403
+ return null;
1404
+ const expanded = this.expandedKeys();
1405
+ if (!expanded.size)
1406
+ return null;
1407
+ const items = this.displayItems();
1408
+ const rh = this.effectiveRowHeight();
1409
+ const details = this.detailHeights();
1410
+ const offsets = new Float64Array(items.length + 1);
1411
+ let y = 0;
1412
+ for (let i = 0; i < items.length; i++) {
1413
+ offsets[i] = y;
1414
+ y += rh;
1415
+ const item = items[i];
1416
+ if (item.kind === 'row') {
1417
+ const key = this.rowKey(item.row);
1418
+ if (expanded.has(key))
1419
+ y += details.get(key) ?? rh;
1420
+ }
1421
+ }
1422
+ offsets[items.length] = y;
1423
+ return offsets;
1424
+ }, /* @ts-ignore */
1425
+ ...(ngDevMode ? [{ debugName: "itemOffsets" }] : /* istanbul ignore next */ []));
1426
+ /** Full height of the body, spacers included. */
1427
+ totalHeight = computed(() => {
1428
+ const offsets = this.itemOffsets();
1429
+ if (offsets)
1430
+ return offsets[offsets.length - 1];
1431
+ return this.displayItems().length * this.effectiveRowHeight();
1432
+ }, /* @ts-ignore */
1433
+ ...(ngDevMode ? [{ debugName: "totalHeight" }] : /* istanbul ignore next */ []));
1434
+ /** Top offset (px) of display item `index`. */
1435
+ offsetOf(index) {
1436
+ const offsets = this.itemOffsets();
1437
+ return offsets
1438
+ ? offsets[Math.min(index, offsets.length - 1)]
1439
+ : index * this.effectiveRowHeight();
1440
+ }
1441
+ /** Index of the display item covering the body offset `y`. */
1442
+ indexAt(y) {
1443
+ const count = this.displayItems().length;
1444
+ if (count === 0)
1445
+ return 0;
1446
+ const offsets = this.itemOffsets();
1447
+ if (!offsets) {
1448
+ return Math.min(count - 1, Math.max(0, Math.floor(y / this.effectiveRowHeight())));
1449
+ }
1450
+ let lo = 0;
1451
+ let hi = count - 1;
1452
+ while (lo < hi) {
1453
+ const mid = (lo + hi + 1) >> 1;
1454
+ if (offsets[mid] <= y)
1455
+ lo = mid;
1456
+ else
1457
+ hi = mid - 1;
1458
+ }
1459
+ return lo;
1460
+ }
1461
+ /** The `[start, end)` slice of display items currently rendered. */
1462
+ windowRange = computed(() => {
1463
+ const count = this.displayItems().length;
1464
+ if (!this.isVirtual())
1465
+ return { start: 0, end: count };
1466
+ const over = Math.max(0, this.overscan());
1467
+ const vh = this.viewportHeight();
1468
+ const top = Math.max(0, this.scrollTop() - this.headHeight());
1469
+ const first = this.indexAt(top);
1470
+ const last = vh > 0 ? this.indexAt(top + vh) : first + UNMEASURED_VIEWPORT_ROWS;
1471
+ return {
1472
+ start: Math.max(0, first - over),
1473
+ end: Math.min(count, last + 1 + over),
1474
+ };
1475
+ }, { ...(ngDevMode ? { debugName: "windowRange" } : /* istanbul ignore next */ {}), equal: (a, b) => a.start === b.start && a.end === b.end });
1476
+ /** Display index of the first rendered item. */
1477
+ windowStart = computed(() => this.windowRange().start, /* @ts-ignore */
1478
+ ...(ngDevMode ? [{ debugName: "windowStart" }] : /* istanbul ignore next */ []));
1479
+ /** The items the tbody renders: the window in virtual mode, else all. */
1480
+ renderedItems = computed(() => {
1481
+ const items = this.displayItems();
1482
+ if (!this.isVirtual())
1483
+ return items;
1484
+ const { start, end } = this.windowRange();
1485
+ return items.slice(start, end);
1486
+ }, /* @ts-ignore */
1487
+ ...(ngDevMode ? [{ debugName: "renderedItems" }] : /* istanbul ignore next */ []));
1488
+ /** Height (px) of the spacer above the window. */
1489
+ topSpace = computed(() => this.isVirtual() ? this.offsetOf(this.windowRange().start) : 0, /* @ts-ignore */
1490
+ ...(ngDevMode ? [{ debugName: "topSpace" }] : /* istanbul ignore next */ []));
1491
+ /** Height (px) of the spacer below the window. */
1492
+ bottomSpace = computed(() => this.isVirtual()
1493
+ ? Math.max(0, this.totalHeight() - this.offsetOf(this.windowRange().end))
1494
+ : 0, /* @ts-ignore */
1495
+ ...(ngDevMode ? [{ debugName: "bottomSpace" }] : /* istanbul ignore next */ []));
1496
+ /** Scroll box scrolled: move the window. */
1497
+ onScroll(event) {
1498
+ if (!this.isVirtual())
1499
+ return;
1500
+ this.scrollTop.set(event.target.scrollTop);
1501
+ }
1502
+ /**
1503
+ * Read the viewport, thead, first-row and detail-row heights back from the
1504
+ * DOM. Heights are accepted only when they move by more than a pixel:
1505
+ * collapsed table borders make the same row measure 44 or 44.5 depending
1506
+ * on where it sits, and letting that flip the row height would shift a
1507
+ * 10,000-row body by thousands of pixels on every window change. A density
1508
+ * change (44 → 36 or 52) still gets through.
1509
+ */
1510
+ measureViewport(el) {
1511
+ const settled = (next, current) => current != null && Math.abs(next - current) <= 1;
1512
+ const vh = el.clientHeight;
1513
+ if (vh > 0 && vh !== this.viewportHeight())
1514
+ this.viewportHeight.set(vh);
1515
+ const thead = el.querySelector('thead');
1516
+ const hh = thead ? thead.getBoundingClientRect().height : 0;
1517
+ if (!settled(hh, this.headHeight()))
1518
+ this.headHeight.set(hh);
1519
+ if (this.rowHeight() == null) {
1520
+ const row = el.querySelector('tr.mk-table__row:not(.mk-table__row--empty)');
1521
+ const rh = row ? row.getBoundingClientRect().height : 0;
1522
+ if (rh > 0 && !settled(rh, this.measuredRowHeight()))
1523
+ this.measuredRowHeight.set(rh);
1524
+ }
1525
+ if (!this.expandable())
1526
+ return;
1527
+ const items = this.displayItems();
1528
+ let next = null;
1529
+ for (const detail of el.querySelectorAll('tr.mk-table__detail-row')) {
1530
+ const index = Number(detail.dataset['index']);
1531
+ const item = items[index];
1532
+ if (!item || item.kind !== 'row')
1533
+ continue;
1534
+ const h = detail.getBoundingClientRect().height;
1535
+ if (!(h > 0))
1536
+ continue;
1537
+ const key = this.rowKey(item.row);
1538
+ if (settled(h, this.detailHeights().get(key) ?? null))
1539
+ continue;
1540
+ next ??= new Map(this.detailHeights());
1541
+ next.set(key, h);
1542
+ }
1543
+ if (next)
1544
+ this.detailHeights.set(next);
1545
+ }
1546
+ /**
1547
+ * Scroll a row into view. A number is the row's **display index** — its
1548
+ * position among the rendered rows, group headers included, after sorting,
1549
+ * filtering and tree expansion; anything else is a row key (the `trackKey`
1550
+ * value, or the row object when there is none). Pass `'key'` to look a
1551
+ * numeric key up. Returns `false` when the row is not currently displayed
1552
+ * (filtered out, under a collapsed parent or group).
1553
+ *
1554
+ * In virtual mode the row lands at the top of the viewport, just under the
1555
+ * header; otherwise it is scrolled to the nearest edge.
1556
+ */
1557
+ scrollToRow(target, by = typeof target === 'number' ? 'index' : 'key') {
1558
+ const items = this.displayItems();
1559
+ const index = by === 'index'
1560
+ ? target
1561
+ : items.findIndex((it) => it.kind === 'row' && this.rowKey(it.row) === target);
1562
+ if (!Number.isInteger(index) || index < 0 || index >= items.length)
1563
+ return false;
1564
+ const el = this.scroller()?.nativeElement;
1565
+ if (!el)
1566
+ return false;
1567
+ if (this.isVirtual()) {
1568
+ const vh = this.viewportHeight();
1569
+ const max = vh > 0 ? Math.max(0, this.totalHeight() + this.headHeight() - vh) : Infinity;
1570
+ const top = Math.min(this.offsetOf(index), max);
1571
+ el.scrollTop = top;
1572
+ this.scrollTop.set(top);
1573
+ }
1574
+ else {
1575
+ const row = el.querySelector(`tbody > tr[data-index="${index}"]`);
1576
+ row?.scrollIntoView?.({ block: 'nearest' });
1577
+ }
1578
+ return true;
1579
+ }
976
1580
  // --- Tree rows ------------------------------------------------------------
977
1581
  /** Keys of parent rows whose children are shown. */
978
1582
  treeExpanded = signal(new Set(), /* @ts-ignore */
@@ -1011,7 +1615,8 @@ class MkTable {
1011
1615
  /**
1012
1616
  * The rows and columns an export writes — exactly what {@link exportCsv}
1013
1617
  * serialises, for other formats (XLSX, PDF, the clipboard, …): rows in
1014
- * display order with the current sort applied and tree children
1618
+ * display order with the current sort and header {@link filters} applied
1619
+ * (every matching row, never just the virtual window) and tree children
1015
1620
  * (`childrenKey`) flattened under their parent whether or not they are
1016
1621
  * expanded, optionally only the selected ones; columns in the table's
1017
1622
  * current order, restricted to `options.columns` when given, each carrying
@@ -1038,8 +1643,8 @@ class MkTable {
1038
1643
  }
1039
1644
  /**
1040
1645
  * The table's rows as CSV: current column order, column formatters applied,
1041
- * sorted the way they are shown, tree children flattened under their parent
1042
- * whether or not they are expanded. Downloads the file (default name
1646
+ * sorted and filtered the way they are shown, tree children flattened under
1647
+ * their parent whether or not they are expanded. Downloads the file (default name
1043
1648
  * `table.csv`) and returns the text. Built on {@link getExportRows}.
1044
1649
  */
1045
1650
  exportCsv(options = {}) {
@@ -1140,13 +1745,15 @@ class MkTable {
1140
1745
  this.expandedChange.emit(this.data().filter((r) => next.has(this.rowKey(r))));
1141
1746
  }
1142
1747
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
1143
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkTable, isStandalone: true, selector: "mk-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, zebra: { classPropertyName: "zebra", publicName: "zebra", isSignal: true, isRequired: false, transformFunction: null }, hover: { classPropertyName: "hover", publicName: "hover", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, stackAt: { classPropertyName: "stackAt", publicName: "stackAt", isSignal: true, isRequired: false, transformFunction: null }, clickableRows: { classPropertyName: "clickableRows", publicName: "clickableRows", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, trackKey: { classPropertyName: "trackKey", publicName: "trackKey", isSignal: true, isRequired: false, transformFunction: null }, rowClass: { classPropertyName: "rowClass", publicName: "rowClass", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, singleExpand: { classPropertyName: "singleExpand", publicName: "singleExpand", isSignal: true, isRequired: false, transformFunction: null }, resizableColumns: { classPropertyName: "resizableColumns", publicName: "resizableColumns", isSignal: true, isRequired: false, transformFunction: null }, reorderableColumns: { classPropertyName: "reorderableColumns", publicName: "reorderableColumns", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, groupLabel: { classPropertyName: "groupLabel", publicName: "groupLabel", isSignal: true, isRequired: false, transformFunction: null }, childrenKey: { classPropertyName: "childrenKey", publicName: "childrenKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange", sortChange: "sortChange", rowClick: "rowClick", selectionChange: "selectionChange", expandedChange: "expandedChange", columnResize: "columnResize", columnReorder: "columnReorder", cellEdit: "cellEdit", groupToggle: "groupToggle", treeToggle: "treeToggle" }, host: { properties: { "class.mk-table--sticky": "stickyHeader()", "class.mk-table--zebra": "zebra()", "class.mk-table--hover": "hover()", "class.mk-table--compact": "density() === 'compact'", "class.mk-table--clickable": "clickableRows()", "class.mk-table--selectable": "selectable()", "class.mk-table--expandable": "expandable()", "class.mk-table--grouped": "groupBy() !== null", "class.mk-table--stacked": "stacked()" }, classAttribute: "mk-table" }, queries: [{ propertyName: "rowDetail", first: true, predicate: MkTableRowDetail, descendants: true, isSignal: true }, { propertyName: "cellTemplates", predicate: MkTableCell, isSignal: true }], viewQueries: [{ propertyName: "editInput", first: true, predicate: ["editInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"mk-table__scroll\">\n <!-- Explicit roles ONLY while stacked: `display: block` strips a table\n element of its implicit role, so without these a card layout stops\n being announced as tabular data at all. Redundant in the grid, so\n they are left off there rather than duplicating what the element\n already says. -->\n <table\n class=\"mk-table__table\"\n [attr.role]=\"childrenKey() ? 'treegrid' : stacked() ? 'table' : null\"\n >\n <thead class=\"mk-table__head\" [attr.role]=\"stacked() ? 'rowgroup' : null\">\n <tr>\n @if (expandable()) {\n <th scope=\"col\" class=\"mk-table__th mk-table__th--expand\">\n <span class=\"mk-visually-hidden\">{{ i18n.expandHeader }}</span>\n </th>\n }\n @if (selectable()) {\n <th scope=\"col\" class=\"mk-table__th mk-table__th--select\">\n <mk-checkbox\n [aria-label]=\"i18n.selectAllRows\"\n [checked]=\"allSelected()\"\n [indeterminate]=\"someSelected()\"\n (checkedChange)=\"toggleAll()\"\n />\n </th>\n }\n @for (col of orderedColumns(); track col.key) {\n <th\n scope=\"col\"\n class=\"mk-table__th\"\n [class.mk-table__th--sortable]=\"col.sortable\"\n [class.mk-table__th--pinned]=\"col.pinned\"\n [class.mk-table__th--pinned-right]=\"col.pinned === 'right'\"\n [class.mk-table__th--dragging]=\"dragKey() === col.key\"\n [style.width]=\"colStyleWidth(col)\"\n [style.inset-inline-start.px]=\"col.pinned === 'left' ? pinnedOffset(col) : null\"\n [style.inset-inline-end.px]=\"col.pinned === 'right' ? pinnedOffset(col) : null\"\n [attr.data-align]=\"col.align ?? 'start'\"\n [attr.aria-sort]=\"ariaSort(col)\"\n [attr.draggable]=\"reorderableColumns() && !col.pinned ? true : null\"\n (dragstart)=\"onColDragStart($event, col)\"\n (dragover)=\"onColDragOver($event)\"\n (drop)=\"onColDrop($event, col)\"\n (dragend)=\"onColDragEnd()\"\n >\n @if (col.sortable || (reorderableColumns() && !col.pinned)) {\n <button\n type=\"button\"\n class=\"mk-table__th-button\"\n [class.mk-table__th-button--static]=\"!col.sortable\"\n (click)=\"onSort(col)\"\n (keydown)=\"onReorderKeydown($event, col)\"\n >\n <span class=\"mk-table__th-label\">{{ col.header }}</span>\n @if (col.sortable) {\n <span class=\"mk-table__sort\" aria-hidden=\"true\">{{ sortGlyph(col) }}</span>\n }\n </button>\n } @else {\n <span class=\"mk-table__th-inner\">\n <span class=\"mk-table__th-label\">{{ col.header }}</span>\n </span>\n }\n @if (resizableColumns() && col.resizable) {\n <span\n class=\"mk-table__resize\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"i18n.resizeColumn\"\n [attr.aria-valuemin]=\"resizeValueMin(col)\"\n [attr.aria-valuenow]=\"resizeValueNow(col)\"\n [attr.aria-valuemax]=\"resizeValueMax\"\n (pointerdown)=\"startResize($event, col)\"\n (keydown)=\"onResizeKeydown($event, col)\"\n (click)=\"$event.stopPropagation()\"\n ></span>\n }\n </th>\n }\n </tr>\n </thead>\n <tbody class=\"mk-table__body\" [attr.role]=\"stacked() ? 'rowgroup' : null\">\n @for (item of displayItems(); track trackItem(item); let i = $index) {\n @if (item.kind === 'group') {\n <tr class=\"mk-table__group-row\" [attr.role]=\"stacked() ? 'row' : null\">\n <th\n class=\"mk-table__group\"\n scope=\"colgroup\"\n [attr.role]=\"stacked() ? 'rowheader' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <button\n type=\"button\"\n class=\"mk-table__group-toggle\"\n [attr.aria-expanded]=\"!isGroupCollapsed(item.group.key)\"\n (click)=\"onGroupToggle(item.group)\"\n >\n <span\n class=\"mk-table__expander-icon\"\n [class.mk-table__expander-icon--open]=\"!isGroupCollapsed(item.group.key)\"\n aria-hidden=\"true\"\n >\u203A</span\n >\n <span class=\"mk-visually-hidden\">{{\n isGroupCollapsed(item.group.key) ? i18n.expandGroup : i18n.collapseGroup\n }}</span>\n <span class=\"mk-table__group-label\">{{ item.group.label }}</span>\n <span class=\"mk-table__group-count\">{{\n i18n.groupCount(item.group.rows.length)\n }}</span>\n </button>\n </th>\n </tr>\n } @else {\n <ng-container>\n <tr\n class=\"mk-table__row\"\n [attr.role]=\"stacked() || childrenKey() ? 'row' : null\"\n [class]=\"rowClassFor(item.row)\"\n [class.mk-table__row--selected]=\"selectable() && isSelected(item.row)\"\n [class.mk-table__row--expanded]=\"expandable() && isExpanded(item.row)\"\n [class.mk-table__row--parent]=\"item.hasChildren\"\n [style.--mk-tree-depth]=\"childrenKey() ? item.depth : null\"\n [style.margin-inline-start.px]=\"stacked() && item.depth ? item.depth * 16 : null\"\n [attr.aria-level]=\"childrenKey() ? item.depth + 1 : null\"\n [attr.aria-expanded]=\"item.hasChildren ? item.expanded : null\"\n [attr.tabindex]=\"clickableRows() ? 0 : null\"\n (click)=\"onRowClick(item.row)\"\n (keydown)=\"onRowKeydown($event, item.row)\"\n >\n @if (expandable()) {\n <td\n class=\"mk-table__td mk-table__td--expand\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <button\n type=\"button\"\n class=\"mk-table__expander\"\n [class.mk-table__expander--open]=\"isExpanded(item.row)\"\n [attr.aria-expanded]=\"isExpanded(item.row)\"\n [attr.aria-controls]=\"detailId(i)\"\n [attr.aria-label]=\"isExpanded(item.row) ? i18n.collapseRow : i18n.expandRow\"\n (click)=\"toggleExpand(item.row, $event)\"\n >\n <span class=\"mk-table__expander-icon\" aria-hidden=\"true\">\u203A</span>\n </button>\n </td>\n }\n @if (selectable()) {\n <td\n class=\"mk-table__td mk-table__td--select\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <mk-checkbox\n [aria-label]=\"i18n.selectRow(rowLabel(item.row))\"\n [checked]=\"isSelected(item.row)\"\n (checkedChange)=\"toggleRow(item.row)\"\n />\n </td>\n }\n @if (!stacked()) {\n @for (col of orderedColumns(); track col.key; let first = $first) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i, slot: null, tree: first ? item : null }\"\n />\n }\n } @else {\n <!-- Card layout. Same <td> elements, restyled \u2014 keeping the table\n DOM means selection, expansion, inline edit and every cell\n template keep working, since all of them reach for a `td`. -->\n @for (col of stackTitleColumns(); track col.key; let first = $first) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i, slot: 'title', tree: first ? item : null }\"\n />\n }\n @for (col of stackFieldColumns(); track col.key) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i, slot: 'field' }\"\n />\n }\n @for (col of stackFooterColumns(); track col.key) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i, slot: 'footer' }\"\n />\n }\n }\n </tr>\n @if (expandable() && isExpanded(item.row) && rowDetail()) {\n <tr class=\"mk-table__detail-row\" [attr.role]=\"stacked() ? 'row' : null\">\n <td\n class=\"mk-table__detail\"\n [id]=\"detailId(i)\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <div class=\"mk-table__detail-inner\">\n <ng-container\n [ngTemplateOutlet]=\"rowDetail()!.template\"\n [ngTemplateOutletContext]=\"{ $implicit: item.row }\"\n />\n </div>\n </td>\n </tr>\n }\n </ng-container>\n }\n } @empty {\n <tr class=\"mk-table__row mk-table__row--empty\" [attr.role]=\"stacked() ? 'row' : null\">\n <td\n class=\"mk-table__empty\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <ng-content select=\"[mkTableEmpty]\">{{ emptyMessage() }}</ng-content>\n </td>\n </tr>\n }\n </tbody>\n </table>\n</div>\n\n<!-- One cell, rendered by both layouts. `slot` is null in the grid and\n 'title' | 'field' | 'footer' in a card; everything else \u2014 the editor, the\n consumer's mkTableCell template, the formatted fallback \u2014 is identical, so\n a card can never drift from the grid it replaces. -->\n<ng-template #cellTpl let-col=\"col\" let-row=\"row\" let-i=\"i\" let-slot=\"slot\" let-tree=\"tree\">\n <td\n class=\"mk-table__td\"\n [class.mk-table__td--tree]=\"!!tree && !!childrenKey()\"\n [class.mk-table__td--pinned]=\"isPinned(col, 'left') || isPinned(col, 'right')\"\n [class.mk-table__td--pinned-right]=\"isPinned(col, 'right')\"\n [class.mk-table__td--editable]=\"col.editable\"\n [class.mk-table__td--stack-title]=\"slot === 'title'\"\n [class.mk-table__td--stack-field]=\"slot === 'field'\"\n [class.mk-table__td--stack-footer]=\"slot === 'footer'\"\n [style.width]=\"colStyleWidth(col)\"\n [style.inset-inline-start.px]=\"isPinned(col, 'left') ? pinnedOffset(col) : null\"\n [style.inset-inline-end.px]=\"isPinned(col, 'right') ? pinnedOffset(col) : null\"\n [attr.data-align]=\"col.align ?? 'start'\"\n [attr.tabindex]=\"col.editable ? 0 : null\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (dblclick)=\"startEdit(i, col, $event)\"\n (keydown)=\"onCellKeydown($event, i, col)\"\n >\n @if (slot === 'field' && hasStackLabel(col)) {\n <!-- The column header, moved beside the value. Not aria-hidden: the\n <thead> is display:none while stacked, so this label is the only\n thing naming the value for a screen reader. -->\n <span class=\"mk-table__cell-label\">{{ col.header }}</span>\n }\n @if (tree && childrenKey()) {\n <!-- Tree toggle (or a spacer on leaves) ahead of the first cell's value,\n so the indent and the caret read as one column. -->\n @if (tree.hasChildren) {\n <button\n type=\"button\"\n class=\"mk-table__tree-toggle\"\n [class.mk-table__tree-toggle--open]=\"tree.expanded\"\n [attr.aria-expanded]=\"tree.expanded\"\n [attr.aria-label]=\"tree.expanded ? i18n.collapseTreeRow : i18n.expandTreeRow\"\n (click)=\"toggleTreeRow(row, $event)\"\n >\n <span class=\"mk-table__expander-icon\" [class.mk-table__expander-icon--open]=\"tree.expanded\" aria-hidden=\"true\">\u203A</span>\n </button>\n } @else {\n <span class=\"mk-table__tree-spacer\" aria-hidden=\"true\"></span>\n }\n }\n <span class=\"mk-table__cell-value\">\n @if (isEditing(i, col)) {\n <input\n #editInput\n class=\"mk-table__cell-input\"\n [value]=\"cellText(row, col)\"\n [attr.aria-label]=\"col.header\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onEditKeydown($event, row, col)\"\n (blur)=\"commitEdit(row, col, $any($event.target).value)\"\n />\n } @else if (cellTemplateFor(col.key); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: cellValue(row, col), row }\"\n />\n } @else {\n {{ cellText(row, col) }}\n @if (col.editable) {\n <span class=\"mk-visually-hidden\">{{ i18n.editCell }}</span>\n }\n }\n </span>\n </td>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{--_cell-pad-y: var(--mk-space-3);--_cell-pad-x: var(--mk-space-4);display:block;color:var(--mk-text)}:host(.mk-table--compact){--_cell-pad-y: var(--mk-space-2);--_cell-pad-x: var(--mk-space-3)}.mk-table__scroll{width:100%;overflow-x:auto;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg)}.mk-table__table{width:100%;border-collapse:collapse;font-size:var(--mk-font-size-sm);background-color:var(--mk-surface)}.mk-table__th{padding:var(--_cell-pad-y) var(--_cell-pad-x);background-color:var(--mk-surface-2);color:var(--mk-text-muted);font-weight:var(--mk-font-weight-semibold);text-align:start;white-space:nowrap;border-bottom:var(--mk-border-width) solid var(--mk-border);vertical-align:middle}:host(.mk-table--sticky) .mk-table__head .mk-table__th{position:sticky;top:0;z-index:var(--mk-z-sticky)}.mk-table__th-inner{display:inline-flex;align-items:center;gap:var(--mk-space-1)}.mk-table__th--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.mk-table__th--sortable:hover{background-color:var(--mk-surface-3);color:var(--mk-text)}.mk-table__th-button{display:inline-flex;align-items:center;gap:var(--mk-space-1);width:100%;margin:calc(-1 * var(--_cell-pad-y)) calc(-1 * var(--_cell-pad-x));padding:var(--_cell-pad-y) var(--_cell-pad-x);font:inherit;font-weight:inherit;color:inherit;text-align:inherit;background:transparent;border:0;cursor:pointer}.mk-table__th-button--static{cursor:grab}.mk-table__th-button:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th[data-align=center] .mk-table__th-button{justify-content:center}.mk-table__th[data-align=end] .mk-table__th-button{justify-content:flex-end}.mk-table__th[aria-sort=ascending],.mk-table__th[aria-sort=descending]{color:var(--mk-text)}.mk-table__sort{font-size:var(--mk-font-size-xs);opacity:.7;line-height:1}.mk-table__td{padding:var(--_cell-pad-y) var(--_cell-pad-x);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);vertical-align:middle}.mk-table__row:last-child .mk-table__td{border-bottom:0}.mk-table__th[data-align=center],.mk-table__td[data-align=center]{text-align:center}.mk-table__th[data-align=end],.mk-table__td[data-align=end]{text-align:right}:host(.mk-table--zebra) .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td{background-color:var(--mk-surface-2)}:host(.mk-table--hover) .mk-table__body .mk-table__row:not(.mk-table__row--empty):hover .mk-table__td{background-color:var(--mk-neutral-subtle)}:host(.mk-table--clickable) .mk-table__row:not(.mk-table__row--empty){cursor:pointer}:host(.mk-table--clickable) .mk-table__row:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th--select,.mk-table__td--select{width:1%;white-space:nowrap;text-align:center;vertical-align:middle}:host .mk-table__body .mk-table__row--selected .mk-table__td,:host(.mk-table--zebra) .mk-table__body .mk-table__row--selected:nth-child(2n) .mk-table__td,:host(.mk-table--hover) .mk-table__body .mk-table__row--selected:hover .mk-table__td{background-color:var(--mk-selected-bg);color:var(--mk-selected-text)}.mk-table__th--expand,.mk-table__td--expand{width:1%;white-space:nowrap;text-align:center;vertical-align:middle}.mk-table__expander{display:inline-grid;place-items:center;width:1.5rem;height:1.5rem;padding:0;color:var(--mk-text-muted);background:transparent;border:none;border-radius:var(--mk-radius-sm);cursor:pointer}.mk-table__expander:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-table__expander:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__expander-icon{display:inline-block;font-size:var(--mk-font-size-lg);line-height:1;transition:transform var(--mk-transition-fast, .12s) ease}.mk-table__expander--open .mk-table__expander-icon,.mk-table__expander-icon--open{transform:rotate(90deg)}:host(:dir(rtl)) .mk-table__expander-icon{transform:scaleX(-1)}:host(:dir(rtl)) .mk-table__expander--open .mk-table__expander-icon,:host(:dir(rtl)) .mk-table__expander-icon--open{transform:scaleX(-1) rotate(-90deg)}.mk-table__detail{padding:0;background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle)}.mk-table__detail-inner{padding:var(--_cell-pad-y) var(--_cell-pad-x)}.mk-table__th{position:relative}.mk-table__resize{position:absolute;top:0;inset-inline-end:0;width:8px;height:100%;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}@media(pointer:coarse){.mk-table__resize:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-table__resize:after{content:\"\";position:absolute;top:25%;inset-inline-end:3px;width:2px;height:50%;background-color:var(--mk-border)}.mk-table__resize:hover:after,.mk-table__resize:focus-visible:after{background-color:var(--mk-primary)}.mk-table__resize:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th[draggable=true]{cursor:grab}.mk-table__th--dragging{opacity:.5}.mk-table__th--pinned,.mk-table__td--pinned{position:sticky;z-index:1;background-color:var(--mk-surface)}.mk-table__th--pinned{z-index:2;background-color:var(--mk-surface-2)}.mk-table__th--pinned:not(.mk-table__th--pinned-right),.mk-table__td--pinned:not(.mk-table__td--pinned-right){box-shadow:2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}.mk-table__th--pinned-right,.mk-table__td--pinned-right{box-shadow:-2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(:dir(rtl)) .mk-table__th--pinned:not(.mk-table__th--pinned-right),:host(:dir(rtl)) .mk-table__td--pinned:not(.mk-table__td--pinned-right){box-shadow:-2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(:dir(rtl)) .mk-table__th--pinned-right,:host(:dir(rtl)) .mk-table__td--pinned-right{box-shadow:2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(.mk-table--zebra) .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td--pinned{background-color:var(--mk-surface-2)}.mk-table__td--editable{cursor:text}.mk-table__td--editable:hover{background-color:var(--mk-hover-overlay)}.mk-table__td--editable:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__cell-input{width:100%;margin:calc(-1 * var(--mk-space-1)) 0;padding:var(--mk-space-1) var(--mk-space-2);font:inherit;color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-primary);border-radius:var(--mk-radius-sm);outline:none}.mk-table__empty{padding:var(--mk-space-8) var(--_cell-pad-x);text-align:center;color:var(--mk-text-subtle)}.mk-table__td--tree{padding-inline-start:calc(var(--_cell-pad-x) + var(--mk-tree-depth, 0) * 1.25rem);white-space:nowrap}.mk-table__tree-toggle,.mk-table__tree-spacer{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;margin-inline-end:var(--mk-space-1);vertical-align:middle;flex:none}.mk-table__tree-toggle{padding:0;border:0;border-radius:var(--mk-radius-sm);background:transparent;color:var(--mk-text-muted);cursor:pointer}.mk-table__tree-toggle:hover{color:var(--mk-text);background:var(--mk-hover-overlay)}.mk-table__tree-toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1)}.mk-table__td--tree .mk-table__cell-value{display:inline;vertical-align:middle}.mk-table__group{padding:0;background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);text-align:start}:host(.mk-table--grouped) .mk-table__group{position:sticky;top:var(--_group-top, 0px);z-index:calc(var(--mk-z-sticky) - 1)}.mk-table__group-toggle{display:flex;align-items:center;gap:var(--mk-space-2);width:100%;padding:var(--mk-space-2) var(--mk-space-3);border:none;background:none;color:var(--mk-text);font:inherit;font-weight:var(--mk-font-weight-semibold);cursor:pointer}.mk-table__group-toggle:hover{background-color:var(--mk-hover-overlay)}.mk-table__group-toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__group-count{padding:0 var(--mk-space-2);border-radius:var(--mk-radius-full);background-color:var(--mk-surface-3);color:var(--mk-text-muted);font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-regular)}:host(.mk-table--stacked) .mk-table__scroll{overflow-x:visible;border:none;border-radius:0;background:none}:host(.mk-table--stacked) .mk-table__table,:host(.mk-table--stacked) .mk-table__body{display:block;background:none}:host(.mk-table--stacked) .mk-table__head{display:none}:host(.mk-table--stacked) .mk-table__row{display:grid;grid-template-columns:1fr;gap:var(--mk-space-1);padding:var(--mk-space-3);margin-bottom:var(--mk-space-3);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);background-color:var(--mk-surface);box-shadow:var(--mk-shadow-xs)}:host(.mk-table--stacked) .mk-table__td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--mk-space-3);padding:var(--mk-space-1) 0;border:none;text-align:start}:host(.mk-table--stacked) .mk-table__td--stack-title{justify-content:flex-start;padding-bottom:var(--mk-space-2);font-size:var(--mk-font-size-lg);font-weight:var(--mk-font-weight-semibold);color:var(--mk-text)}:host(.mk-table--stacked) .mk-table__td--stack-title~.mk-table__td--stack-title{justify-content:flex-end;margin-top:calc(-1 * var(--mk-space-2) - 1.5em);font-size:var(--mk-font-size-md)}:host(.mk-table--stacked) .mk-table__cell-label{flex:none;color:var(--mk-text-muted);font-size:var(--mk-font-size-sm)}:host(.mk-table--stacked) .mk-table__cell-value{min-width:0;text-align:end;overflow-wrap:anywhere}:host(.mk-table--stacked) .mk-table__td--stack-title .mk-table__cell-value,:host(.mk-table--stacked) .mk-table__td--stack-footer .mk-table__cell-value{text-align:start}:host(.mk-table--stacked) .mk-table__td--stack-footer{justify-content:flex-start;margin-top:var(--mk-space-2);padding-top:var(--mk-space-3);border-top:var(--mk-border-width) solid var(--mk-border-subtle)}:host(.mk-table--stacked) .mk-table__td--stack-footer .mk-table__cell-value{display:flex;flex-wrap:wrap;gap:var(--mk-space-2);width:100%}:host(.mk-table--stacked) .mk-table__td--select,:host(.mk-table--stacked) .mk-table__td--expand{width:auto;justify-content:flex-start}:host(.mk-table--stacked).mk-table--zebra .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td{background:none}:host(.mk-table--stacked) .mk-table__detail-row,:host(.mk-table--stacked) .mk-table__detail{display:block;padding:0}:host(.mk-table--stacked) .mk-table__detail-row{margin:calc(-1 * var(--mk-space-3)) 0 var(--mk-space-3)}:host(.mk-table--stacked) .mk-table__group-row,:host(.mk-table--stacked) .mk-table__group{display:block;top:0}:host(.mk-table--stacked) .mk-table__row--empty,:host(.mk-table--stacked) .mk-table__empty{display:block;border:none;box-shadow:none;background:none}@media(pointer:coarse){.mk-table__cell-input{font-size:max(var(--mk-font-size-md),16px)}}@media print{.mk-table__scroll{overflow:visible;border-radius:0}.mk-table__head{display:table-header-group}.mk-table__th,.mk-table__td{position:static;box-shadow:none;background-color:transparent!important;color:inherit!important;border-bottom:1px solid var(--mk-border)}.mk-table__row,.mk-table__group-row,.mk-table__detail-row{break-inside:avoid}.mk-table__sort,.mk-table__resize,.mk-table__th--select,.mk-table__td--select,.mk-table__th--expand,.mk-table__td--expand,.mk-table__tree-toggle .mk-table__expander-icon{display:none}.mk-table__th-button,.mk-table__tree-toggle,.mk-table__group-toggle{color:inherit;cursor:default}}\n"], dependencies: [{ kind: "component", type: MkCheckbox, selector: "mk-checkbox", inputs: ["checked", "indeterminate", "disabled", "invalid", "required", "size", "tone", "aria-label"], outputs: ["checkedChange", "indeterminateChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1748
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkTable, isStandalone: true, selector: "mk-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, zebra: { classPropertyName: "zebra", publicName: "zebra", isSignal: true, isRequired: false, transformFunction: null }, hover: { classPropertyName: "hover", publicName: "hover", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, stackAt: { classPropertyName: "stackAt", publicName: "stackAt", isSignal: true, isRequired: false, transformFunction: null }, clickableRows: { classPropertyName: "clickableRows", publicName: "clickableRows", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, trackKey: { classPropertyName: "trackKey", publicName: "trackKey", isSignal: true, isRequired: false, transformFunction: null }, rowClass: { classPropertyName: "rowClass", publicName: "rowClass", isSignal: true, isRequired: false, transformFunction: null }, expandable: { classPropertyName: "expandable", publicName: "expandable", isSignal: true, isRequired: false, transformFunction: null }, singleExpand: { classPropertyName: "singleExpand", publicName: "singleExpand", isSignal: true, isRequired: false, transformFunction: null }, resizableColumns: { classPropertyName: "resizableColumns", publicName: "resizableColumns", isSignal: true, isRequired: false, transformFunction: null }, reorderableColumns: { classPropertyName: "reorderableColumns", publicName: "reorderableColumns", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, groupLabel: { classPropertyName: "groupLabel", publicName: "groupLabel", isSignal: true, isRequired: false, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, filterable: { classPropertyName: "filterable", publicName: "filterable", isSignal: true, isRequired: false, transformFunction: null }, filters: { classPropertyName: "filters", publicName: "filters", isSignal: true, isRequired: false, transformFunction: null }, clientFilter: { classPropertyName: "clientFilter", publicName: "clientFilter", isSignal: true, isRequired: false, transformFunction: null }, childrenKey: { classPropertyName: "childrenKey", publicName: "childrenKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange", filters: "filtersChange", sortChange: "sortChange", rowClick: "rowClick", selectionChange: "selectionChange", expandedChange: "expandedChange", columnResize: "columnResize", columnReorder: "columnReorder", cellEdit: "cellEdit", groupToggle: "groupToggle", treeToggle: "treeToggle" }, host: { properties: { "class.mk-table--sticky": "stickyHeader() || isVirtual()", "class.mk-table--virtual": "isVirtual()", "class.mk-table--filterable": "filterable() && !stacked()", "class.mk-table--zebra": "zebra()", "class.mk-table--hover": "hover()", "class.mk-table--compact": "density() === 'compact'", "class.mk-table--clickable": "clickableRows()", "class.mk-table--selectable": "selectable()", "class.mk-table--expandable": "expandable()", "class.mk-table--grouped": "groupBy() !== null", "class.mk-table--stacked": "stacked()" }, classAttribute: "mk-table" }, queries: [{ propertyName: "rowDetail", first: true, predicate: MkTableRowDetail, descendants: true, isSignal: true }, { propertyName: "cellTemplates", predicate: MkTableCell, isSignal: true }], viewQueries: [{ propertyName: "editInput", first: true, predicate: ["editInput"], descendants: true, isSignal: true }, { propertyName: "scroller", first: true, predicate: ["scroller"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #scroller\n class=\"mk-table__scroll\"\n [style.height]=\"scrollHeight()\"\n [style.max-height]=\"scrollMaxHeight()\"\n [attr.data-row-height]=\"isVirtual() ? effectiveRowHeight() : null\"\n (scroll)=\"onScroll($event)\"\n>\n <!-- Explicit roles ONLY while stacked: `display: block` strips a table\n element of its implicit role, so without these a card layout stops\n being announced as tabular data at all. Redundant in the grid, so\n they are left off there rather than duplicating what the element\n already says. -->\n <table\n class=\"mk-table__table\"\n [attr.role]=\"childrenKey() ? 'treegrid' : stacked() ? 'table' : null\"\n [attr.aria-rowcount]=\"isVirtual() ? displayItems().length + headerRows() : null\"\n >\n <thead class=\"mk-table__head\" [attr.role]=\"stacked() ? 'rowgroup' : null\">\n <tr>\n @if (expandable()) {\n <th scope=\"col\" class=\"mk-table__th mk-table__th--expand\">\n <span class=\"mk-visually-hidden\">{{ i18n.expandHeader }}</span>\n </th>\n }\n @if (selectable()) {\n <th scope=\"col\" class=\"mk-table__th mk-table__th--select\">\n <mk-checkbox\n [aria-label]=\"i18n.selectAllRows\"\n [checked]=\"allSelected()\"\n [indeterminate]=\"someSelected()\"\n (checkedChange)=\"toggleAll()\"\n />\n </th>\n }\n @for (col of orderedColumns(); track col.key) {\n <th\n scope=\"col\"\n class=\"mk-table__th\"\n [class.mk-table__th--sortable]=\"col.sortable\"\n [class.mk-table__th--pinned]=\"col.pinned\"\n [class.mk-table__th--pinned-right]=\"col.pinned === 'right'\"\n [class.mk-table__th--dragging]=\"dragKey() === col.key\"\n [style.width]=\"colStyleWidth(col)\"\n [style.inset-inline-start.px]=\"col.pinned === 'left' ? pinnedOffset(col) : null\"\n [style.inset-inline-end.px]=\"col.pinned === 'right' ? pinnedOffset(col) : null\"\n [attr.data-align]=\"col.align ?? 'start'\"\n [attr.aria-sort]=\"ariaSort(col)\"\n [attr.draggable]=\"reorderableColumns() && !col.pinned ? true : null\"\n (dragstart)=\"onColDragStart($event, col)\"\n (dragover)=\"onColDragOver($event)\"\n (drop)=\"onColDrop($event, col)\"\n (dragend)=\"onColDragEnd()\"\n >\n @if (col.sortable || (reorderableColumns() && !col.pinned)) {\n <button\n type=\"button\"\n class=\"mk-table__th-button\"\n [class.mk-table__th-button--static]=\"!col.sortable\"\n (click)=\"onSort(col)\"\n (keydown)=\"onReorderKeydown($event, col)\"\n >\n <span class=\"mk-table__th-label\">{{ col.header }}</span>\n @if (col.sortable) {\n <span class=\"mk-table__sort\" aria-hidden=\"true\">{{ sortGlyph(col) }}</span>\n }\n </button>\n } @else {\n <span class=\"mk-table__th-inner\">\n <span class=\"mk-table__th-label\">{{ col.header }}</span>\n </span>\n }\n @if (resizableColumns() && col.resizable) {\n <span\n class=\"mk-table__resize\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"i18n.resizeColumn\"\n [attr.aria-valuemin]=\"resizeValueMin(col)\"\n [attr.aria-valuenow]=\"resizeValueNow(col)\"\n [attr.aria-valuemax]=\"resizeValueMax\"\n (pointerdown)=\"startResize($event, col)\"\n (keydown)=\"onResizeKeydown($event, col)\"\n (click)=\"$event.stopPropagation()\"\n ></span>\n }\n </th>\n }\n </tr>\n @if (filterable() && !stacked()) {\n <!-- Filter row: native controls styled with tokens, so the table entry\n point stays free of the forms / datetime entries. -->\n <tr class=\"mk-table__filter-row\">\n @if (expandable()) {\n <td class=\"mk-table__filter-cell mk-table__th--expand\"></td>\n }\n @if (selectable()) {\n <td class=\"mk-table__filter-cell mk-table__th--select\"></td>\n }\n @for (col of orderedColumns(); track col.key) {\n @let kind = filterKind(col);\n <td\n class=\"mk-table__filter-cell\"\n [class.mk-table__th--pinned]=\"col.pinned\"\n [class.mk-table__th--pinned-right]=\"col.pinned === 'right'\"\n [style.width]=\"colStyleWidth(col)\"\n [style.inset-inline-start.px]=\"col.pinned === 'left' ? pinnedOffset(col) : null\"\n [style.inset-inline-end.px]=\"col.pinned === 'right' ? pinnedOffset(col) : null\"\n >\n @if (kind === 'select') {\n <select\n class=\"mk-table__filter-control mk-table__filter-select\"\n [class.mk-table__filter-control--active]=\"hasFilter(col.key)\"\n [attr.data-filter-key]=\"col.key\"\n [attr.aria-label]=\"filterLabel(col)\"\n (change)=\"onFilterSelect(col, $event)\"\n >\n <option value=\"\" [selected]=\"!hasFilter(col.key)\">\n {{ col.filterPlaceholder ?? i18n.filterAny }}\n </option>\n @for (option of filterOptionsFor(col); track $index) {\n <option [value]=\"option.value\" [selected]=\"isFilterOption(col, option)\">\n {{ option.label }}\n </option>\n }\n </select>\n } @else if (kind) {\n <span class=\"mk-table__filter\" [class.mk-table__filter--active]=\"hasFilter(col.key)\">\n <input\n class=\"mk-table__filter-control mk-table__filter-input\"\n [type]=\"kind === 'text' ? 'search' : kind\"\n [attr.inputmode]=\"kind === 'number' ? 'decimal' : null\"\n [attr.data-filter-key]=\"col.key\"\n [attr.aria-label]=\"filterLabel(col)\"\n [attr.placeholder]=\"filterPlaceholder(col)\"\n [value]=\"filterText(col.key)\"\n (input)=\"onFilterInput(col, $event)\"\n (keydown)=\"onFilterKeydown(col, $event)\"\n />\n @if (hasFilter(col.key)) {\n <button\n type=\"button\"\n class=\"mk-table__filter-clear\"\n [attr.aria-label]=\"i18n.clearFilter(col.header || col.key)\"\n (click)=\"clearFilter(col.key)\"\n >\n <span aria-hidden=\"true\">\u00D7</span>\n </button>\n }\n </span>\n }\n </td>\n }\n </tr>\n }\n </thead>\n <tbody class=\"mk-table__body\" [attr.role]=\"stacked() ? 'rowgroup' : null\">\n @if (isVirtual() && topSpace() > 0) {\n <tr class=\"mk-table__spacer\" aria-hidden=\"true\">\n <td [attr.colspan]=\"totalColumns()\" [style.height.px]=\"topSpace()\"></td>\n </tr>\n }\n @for (item of renderedItems(); track trackItem(item); let i = $index) {\n @let idx = windowStart() + i;\n @if (item.kind === 'group') {\n <tr\n class=\"mk-table__group-row\"\n [attr.role]=\"stacked() ? 'row' : null\"\n [attr.data-index]=\"idx\"\n [attr.aria-rowindex]=\"isVirtual() ? idx + headerRows() + 1 : null\"\n >\n <th\n class=\"mk-table__group\"\n scope=\"colgroup\"\n [attr.role]=\"stacked() ? 'rowheader' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <button\n type=\"button\"\n class=\"mk-table__group-toggle\"\n [attr.aria-expanded]=\"!isGroupCollapsed(item.group.key)\"\n (click)=\"onGroupToggle(item.group)\"\n >\n <span\n class=\"mk-table__expander-icon\"\n [class.mk-table__expander-icon--open]=\"!isGroupCollapsed(item.group.key)\"\n aria-hidden=\"true\"\n >\u203A</span\n >\n <span class=\"mk-visually-hidden\">{{\n isGroupCollapsed(item.group.key) ? i18n.expandGroup : i18n.collapseGroup\n }}</span>\n <span class=\"mk-table__group-label\">{{ item.group.label }}</span>\n <span class=\"mk-table__group-count\">{{\n i18n.groupCount(item.group.rows.length)\n }}</span>\n </button>\n </th>\n </tr>\n } @else {\n <ng-container>\n <tr\n class=\"mk-table__row\"\n [attr.role]=\"stacked() || childrenKey() ? 'row' : null\"\n [class]=\"rowClassFor(item.row)\"\n [class.mk-table__row--selected]=\"selectable() && isSelected(item.row)\"\n [class.mk-table__row--expanded]=\"expandable() && isExpanded(item.row)\"\n [class.mk-table__row--parent]=\"item.hasChildren\"\n [class.mk-table__row--even]=\"isVirtual() && idx % 2 === 1\"\n [style.--mk-tree-depth]=\"childrenKey() ? item.depth : null\"\n [style.margin-inline-start.px]=\"stacked() && item.depth ? item.depth * 16 : null\"\n [attr.data-index]=\"idx\"\n [attr.aria-rowindex]=\"isVirtual() ? idx + headerRows() + 1 : null\"\n [attr.aria-level]=\"childrenKey() ? item.depth + 1 : null\"\n [attr.aria-expanded]=\"item.hasChildren ? item.expanded : null\"\n [attr.tabindex]=\"clickableRows() ? 0 : null\"\n (click)=\"onRowClick(item.row)\"\n (keydown)=\"onRowKeydown($event, item.row)\"\n >\n @if (expandable()) {\n <td\n class=\"mk-table__td mk-table__td--expand\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <button\n type=\"button\"\n class=\"mk-table__expander\"\n [class.mk-table__expander--open]=\"isExpanded(item.row)\"\n [attr.aria-expanded]=\"isExpanded(item.row)\"\n [attr.aria-controls]=\"detailId(idx)\"\n [attr.aria-label]=\"isExpanded(item.row) ? i18n.collapseRow : i18n.expandRow\"\n (click)=\"toggleExpand(item.row, $event)\"\n >\n <span class=\"mk-table__expander-icon\" aria-hidden=\"true\">\u203A</span>\n </button>\n </td>\n }\n @if (selectable()) {\n <td\n class=\"mk-table__td mk-table__td--select\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <mk-checkbox\n [aria-label]=\"i18n.selectRow(rowLabel(item.row))\"\n [checked]=\"isSelected(item.row)\"\n (checkedChange)=\"toggleRow(item.row)\"\n />\n </td>\n }\n @if (!stacked()) {\n @for (col of orderedColumns(); track col.key; let first = $first) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i: idx, slot: null, tree: first ? item : null }\"\n />\n }\n } @else {\n <!-- Card layout. Same <td> elements, restyled \u2014 keeping the table\n DOM means selection, expansion, inline edit and every cell\n template keep working, since all of them reach for a `td`. -->\n @for (col of stackTitleColumns(); track col.key; let first = $first) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i: idx, slot: 'title', tree: first ? item : null }\"\n />\n }\n @for (col of stackFieldColumns(); track col.key) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i: idx, slot: 'field' }\"\n />\n }\n @for (col of stackFooterColumns(); track col.key) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i: idx, slot: 'footer' }\"\n />\n }\n }\n </tr>\n @if (expandable() && isExpanded(item.row) && rowDetail()) {\n <tr\n class=\"mk-table__detail-row\"\n [attr.role]=\"stacked() ? 'row' : null\"\n [attr.data-index]=\"idx\"\n >\n <td\n class=\"mk-table__detail\"\n [id]=\"detailId(idx)\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <div class=\"mk-table__detail-inner\">\n <ng-container\n [ngTemplateOutlet]=\"rowDetail()!.template\"\n [ngTemplateOutletContext]=\"{ $implicit: item.row }\"\n />\n </div>\n </td>\n </tr>\n }\n </ng-container>\n }\n } @empty {\n <tr class=\"mk-table__row mk-table__row--empty\" [attr.role]=\"stacked() ? 'row' : null\">\n <td\n class=\"mk-table__empty\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <ng-content select=\"[mkTableEmpty]\">{{ emptyMessage() }}</ng-content>\n </td>\n </tr>\n }\n @if (isVirtual() && bottomSpace() > 0) {\n <tr class=\"mk-table__spacer\" aria-hidden=\"true\">\n <td [attr.colspan]=\"totalColumns()\" [style.height.px]=\"bottomSpace()\"></td>\n </tr>\n }\n </tbody>\n </table>\n</div>\n\n<!-- One cell, rendered by both layouts. `slot` is null in the grid and\n 'title' | 'field' | 'footer' in a card; everything else \u2014 the editor, the\n consumer's mkTableCell template, the formatted fallback \u2014 is identical, so\n a card can never drift from the grid it replaces. -->\n<ng-template #cellTpl let-col=\"col\" let-row=\"row\" let-i=\"i\" let-slot=\"slot\" let-tree=\"tree\">\n <td\n class=\"mk-table__td\"\n [class.mk-table__td--tree]=\"!!tree && !!childrenKey()\"\n [class.mk-table__td--pinned]=\"isPinned(col, 'left') || isPinned(col, 'right')\"\n [class.mk-table__td--pinned-right]=\"isPinned(col, 'right')\"\n [class.mk-table__td--editable]=\"col.editable\"\n [class.mk-table__td--stack-title]=\"slot === 'title'\"\n [class.mk-table__td--stack-field]=\"slot === 'field'\"\n [class.mk-table__td--stack-footer]=\"slot === 'footer'\"\n [style.width]=\"colStyleWidth(col)\"\n [style.inset-inline-start.px]=\"isPinned(col, 'left') ? pinnedOffset(col) : null\"\n [style.inset-inline-end.px]=\"isPinned(col, 'right') ? pinnedOffset(col) : null\"\n [attr.data-align]=\"col.align ?? 'start'\"\n [attr.tabindex]=\"col.editable ? 0 : null\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (dblclick)=\"startEdit(i, col, $event)\"\n (keydown)=\"onCellKeydown($event, i, col)\"\n >\n @if (slot === 'field' && hasStackLabel(col)) {\n <!-- The column header, moved beside the value. Not aria-hidden: the\n <thead> is display:none while stacked, so this label is the only\n thing naming the value for a screen reader. -->\n <span class=\"mk-table__cell-label\">{{ col.header }}</span>\n }\n @if (tree && childrenKey()) {\n <!-- Tree toggle (or a spacer on leaves) ahead of the first cell's value,\n so the indent and the caret read as one column. -->\n @if (tree.hasChildren) {\n <button\n type=\"button\"\n class=\"mk-table__tree-toggle\"\n [class.mk-table__tree-toggle--open]=\"tree.expanded\"\n [attr.aria-expanded]=\"tree.expanded\"\n [attr.aria-label]=\"tree.expanded ? i18n.collapseTreeRow : i18n.expandTreeRow\"\n (click)=\"toggleTreeRow(row, $event)\"\n >\n <span class=\"mk-table__expander-icon\" [class.mk-table__expander-icon--open]=\"tree.expanded\" aria-hidden=\"true\">\u203A</span>\n </button>\n } @else {\n <span class=\"mk-table__tree-spacer\" aria-hidden=\"true\"></span>\n }\n }\n <span class=\"mk-table__cell-value\">\n @if (isEditing(i, col)) {\n <input\n #editInput\n class=\"mk-table__cell-input\"\n [value]=\"cellText(row, col)\"\n [attr.aria-label]=\"col.header\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onEditKeydown($event, row, col)\"\n (blur)=\"commitEdit(row, col, $any($event.target).value)\"\n />\n } @else if (cellTemplateFor(col.key); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: cellValue(row, col), row }\"\n />\n } @else {\n {{ cellText(row, col) }}\n @if (col.editable) {\n <span class=\"mk-visually-hidden\">{{ i18n.editCell }}</span>\n }\n }\n </span>\n </td>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{--_cell-pad-y: var(--mk-space-3);--_cell-pad-x: var(--mk-space-4);display:block;color:var(--mk-text)}:host(.mk-table--compact){--_cell-pad-y: var(--mk-space-2);--_cell-pad-x: var(--mk-space-3)}.mk-table__scroll{width:100%;overflow:auto;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg)}.mk-table__table{width:100%;border-collapse:collapse;font-size:var(--mk-font-size-sm);background-color:var(--mk-surface)}.mk-table__th{padding:var(--_cell-pad-y) var(--_cell-pad-x);background-color:var(--mk-surface-2);color:var(--mk-text-muted);font-weight:var(--mk-font-weight-semibold);text-align:start;white-space:nowrap;border-bottom:var(--mk-border-width) solid var(--mk-border);vertical-align:middle}:host(.mk-table--sticky) .mk-table__head .mk-table__th{position:sticky;top:0;z-index:var(--mk-z-sticky)}.mk-table__th-inner{display:inline-flex;align-items:center;gap:var(--mk-space-1)}.mk-table__th--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.mk-table__th--sortable:hover{background-color:var(--mk-surface-3);color:var(--mk-text)}.mk-table__th-button{display:inline-flex;align-items:center;gap:var(--mk-space-1);width:100%;margin:calc(-1 * var(--_cell-pad-y)) calc(-1 * var(--_cell-pad-x));padding:var(--_cell-pad-y) var(--_cell-pad-x);font:inherit;font-weight:inherit;color:inherit;text-align:inherit;background:transparent;border:0;cursor:pointer}.mk-table__th-button--static{cursor:grab}.mk-table__th-button:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th[data-align=center] .mk-table__th-button{justify-content:center}.mk-table__th[data-align=end] .mk-table__th-button{justify-content:flex-end}.mk-table__th[aria-sort=ascending],.mk-table__th[aria-sort=descending]{color:var(--mk-text)}.mk-table__sort{font-size:var(--mk-font-size-xs);opacity:.7;line-height:1}.mk-table__td{padding:var(--_cell-pad-y) var(--_cell-pad-x);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);vertical-align:middle}.mk-table__row:last-child .mk-table__td{border-bottom:0}.mk-table__th[data-align=center],.mk-table__td[data-align=center]{text-align:center}.mk-table__th[data-align=end],.mk-table__td[data-align=end]{text-align:right}:host(.mk-table--zebra:not(.mk-table--virtual)) .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td,:host(.mk-table--zebra) .mk-table__body .mk-table__row--even .mk-table__td{background-color:var(--mk-surface-2)}:host(.mk-table--hover) .mk-table__body .mk-table__row:not(.mk-table__row--empty):hover .mk-table__td{background-color:var(--mk-neutral-subtle)}:host(.mk-table--clickable) .mk-table__row:not(.mk-table__row--empty){cursor:pointer}:host(.mk-table--clickable) .mk-table__row:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th--select,.mk-table__td--select{width:1%;white-space:nowrap;text-align:center;vertical-align:middle}:host .mk-table__body .mk-table__row--selected .mk-table__td,:host(.mk-table--zebra) .mk-table__body .mk-table__row--selected:nth-child(2n) .mk-table__td,:host(.mk-table--zebra) .mk-table__body .mk-table__row--selected.mk-table__row--even .mk-table__td,:host(.mk-table--hover) .mk-table__body .mk-table__row--selected:hover .mk-table__td{background-color:var(--mk-selected-bg);color:var(--mk-selected-text)}.mk-table__th--expand,.mk-table__td--expand{width:1%;white-space:nowrap;text-align:center;vertical-align:middle}.mk-table__expander{display:inline-grid;place-items:center;width:1.5rem;height:1.5rem;padding:0;color:var(--mk-text-muted);background:transparent;border:none;border-radius:var(--mk-radius-sm);cursor:pointer}.mk-table__expander:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-table__expander:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__expander-icon{display:inline-block;font-size:var(--mk-font-size-lg);line-height:1;transition:transform var(--mk-transition-fast, .12s) ease}.mk-table__expander--open .mk-table__expander-icon,.mk-table__expander-icon--open{transform:rotate(90deg)}:host(:dir(rtl)) .mk-table__expander-icon{transform:scaleX(-1)}:host(:dir(rtl)) .mk-table__expander--open .mk-table__expander-icon,:host(:dir(rtl)) .mk-table__expander-icon--open{transform:scaleX(-1) rotate(-90deg)}.mk-table__detail{padding:0;background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle)}.mk-table__detail-inner{padding:var(--_cell-pad-y) var(--_cell-pad-x)}.mk-table__th{position:relative}.mk-table__resize{position:absolute;top:0;inset-inline-end:0;width:8px;height:100%;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}@media(pointer:coarse){.mk-table__resize:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-table__resize:after{content:\"\";position:absolute;top:25%;inset-inline-end:3px;width:2px;height:50%;background-color:var(--mk-border)}.mk-table__resize:hover:after,.mk-table__resize:focus-visible:after{background-color:var(--mk-primary)}.mk-table__resize:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th[draggable=true]{cursor:grab}.mk-table__th--dragging{opacity:.5}.mk-table__th--pinned,.mk-table__td--pinned{position:sticky;z-index:1;background-color:var(--mk-surface)}.mk-table__th--pinned{z-index:2;background-color:var(--mk-surface-2)}.mk-table__th--pinned:not(.mk-table__th--pinned-right),.mk-table__td--pinned:not(.mk-table__td--pinned-right){box-shadow:2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}.mk-table__th--pinned-right,.mk-table__td--pinned-right{box-shadow:-2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(:dir(rtl)) .mk-table__th--pinned:not(.mk-table__th--pinned-right),:host(:dir(rtl)) .mk-table__td--pinned:not(.mk-table__td--pinned-right){box-shadow:-2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(:dir(rtl)) .mk-table__th--pinned-right,:host(:dir(rtl)) .mk-table__td--pinned-right{box-shadow:2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(.mk-table--zebra:not(.mk-table--virtual)) .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td--pinned,:host(.mk-table--zebra) .mk-table__body .mk-table__row--even .mk-table__td--pinned{background-color:var(--mk-surface-2)}.mk-table__filter-cell{position:relative;padding:var(--mk-space-1) var(--mk-space-2);background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border);vertical-align:middle}:host(.mk-table--sticky) .mk-table__filter-cell{position:sticky;top:var(--_filter-top, 0px);z-index:var(--mk-z-sticky)}:host(.mk-table--sticky) .mk-table__filter-cell.mk-table__th--pinned{z-index:calc(var(--mk-z-sticky) + 1)}.mk-table__filter{position:relative;display:flex;align-items:center;width:100%}.mk-table__filter-control{width:100%;min-width:4rem;height:var(--mk-control-height-sm);padding:0 var(--mk-space-2);font:inherit;font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-normal);color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-sm);outline:none;transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}.mk-table__filter-control::placeholder{color:var(--mk-text-subtle)}.mk-table__filter-control:hover{border-color:var(--mk-border-strong)}.mk-table__filter-control:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:0px;border-color:var(--mk-primary)}.mk-table__filter-control--active,.mk-table__filter--active .mk-table__filter-control{border-color:var(--mk-primary)}.mk-table__filter--active .mk-table__filter-input{padding-inline-end:calc(var(--mk-space-2) + 1.5rem)}.mk-table__filter-input::-webkit-search-cancel-button,.mk-table__filter-input::-webkit-search-decoration{-webkit-appearance:none;appearance:none}.mk-table__filter-select{cursor:pointer}.mk-table__filter-clear{position:absolute;inset-inline-end:var(--mk-space-1);top:50%;display:inline-grid;place-items:center;width:1.25rem;height:1.25rem;padding:0;transform:translateY(-50%);font-size:var(--mk-font-size-md);line-height:1;color:var(--mk-text-muted);background:transparent;border:0;border-radius:var(--mk-radius-sm);cursor:pointer}@media(pointer:coarse){.mk-table__filter-clear:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-table__filter-clear:hover{color:var(--mk-text);background-color:var(--mk-hover-overlay)}.mk-table__filter-clear:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:0px}.mk-table__spacer>td{padding:0;border:0}:host(.mk-table--virtual) .mk-table__row .mk-table__td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mk-table__td--editable{cursor:text}.mk-table__td--editable:hover{background-color:var(--mk-hover-overlay)}.mk-table__td--editable:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__cell-input{width:100%;margin:calc(-1 * var(--mk-space-1)) 0;padding:var(--mk-space-1) var(--mk-space-2);font:inherit;color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-primary);border-radius:var(--mk-radius-sm);outline:none}.mk-table__empty{padding:var(--mk-space-8) var(--_cell-pad-x);text-align:center;color:var(--mk-text-subtle)}.mk-table__td--tree{padding-inline-start:calc(var(--_cell-pad-x) + var(--mk-tree-depth, 0) * 1.25rem);white-space:nowrap}.mk-table__tree-toggle,.mk-table__tree-spacer{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;margin-inline-end:var(--mk-space-1);vertical-align:middle;flex:none}.mk-table__tree-toggle{padding:0;border:0;border-radius:var(--mk-radius-sm);background:transparent;color:var(--mk-text-muted);cursor:pointer}.mk-table__tree-toggle:hover{color:var(--mk-text);background:var(--mk-hover-overlay)}.mk-table__tree-toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1)}.mk-table__td--tree .mk-table__cell-value{display:inline;vertical-align:middle}.mk-table__group{padding:0;background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);text-align:start}:host(.mk-table--grouped) .mk-table__group{position:sticky;top:var(--_group-top, 0px);z-index:calc(var(--mk-z-sticky) - 1)}.mk-table__group-toggle{display:flex;align-items:center;gap:var(--mk-space-2);width:100%;padding:var(--mk-space-2) var(--mk-space-3);border:none;background:none;color:var(--mk-text);font:inherit;font-weight:var(--mk-font-weight-semibold);cursor:pointer}.mk-table__group-toggle:hover{background-color:var(--mk-hover-overlay)}.mk-table__group-toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__group-count{padding:0 var(--mk-space-2);border-radius:var(--mk-radius-full);background-color:var(--mk-surface-3);color:var(--mk-text-muted);font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-regular)}:host(.mk-table--stacked) .mk-table__scroll{overflow:visible;max-height:none;border:none;border-radius:0;background:none}:host(.mk-table--stacked) .mk-table__table,:host(.mk-table--stacked) .mk-table__body{display:block;background:none}:host(.mk-table--stacked) .mk-table__head{display:none}:host(.mk-table--stacked) .mk-table__spacer{display:none}:host(.mk-table--stacked) .mk-table__row{display:grid;grid-template-columns:1fr;gap:var(--mk-space-1);padding:var(--mk-space-3);margin-bottom:var(--mk-space-3);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);background-color:var(--mk-surface);box-shadow:var(--mk-shadow-xs)}:host(.mk-table--stacked) .mk-table__td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--mk-space-3);padding:var(--mk-space-1) 0;border:none;text-align:start}:host(.mk-table--stacked) .mk-table__td--stack-title{justify-content:flex-start;padding-bottom:var(--mk-space-2);font-size:var(--mk-font-size-lg);font-weight:var(--mk-font-weight-semibold);color:var(--mk-text)}:host(.mk-table--stacked) .mk-table__td--stack-title~.mk-table__td--stack-title{justify-content:flex-end;margin-top:calc(-1 * var(--mk-space-2) - 1.5em);font-size:var(--mk-font-size-md)}:host(.mk-table--stacked) .mk-table__cell-label{flex:none;color:var(--mk-text-muted);font-size:var(--mk-font-size-sm)}:host(.mk-table--stacked) .mk-table__cell-value{min-width:0;text-align:end;overflow-wrap:anywhere}:host(.mk-table--stacked) .mk-table__td--stack-title .mk-table__cell-value,:host(.mk-table--stacked) .mk-table__td--stack-footer .mk-table__cell-value{text-align:start}:host(.mk-table--stacked) .mk-table__td--stack-footer{justify-content:flex-start;margin-top:var(--mk-space-2);padding-top:var(--mk-space-3);border-top:var(--mk-border-width) solid var(--mk-border-subtle)}:host(.mk-table--stacked) .mk-table__td--stack-footer .mk-table__cell-value{display:flex;flex-wrap:wrap;gap:var(--mk-space-2);width:100%}:host(.mk-table--stacked) .mk-table__td--select,:host(.mk-table--stacked) .mk-table__td--expand{width:auto;justify-content:flex-start}:host(.mk-table--stacked).mk-table--zebra .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td{background:none}:host(.mk-table--stacked) .mk-table__detail-row,:host(.mk-table--stacked) .mk-table__detail{display:block;padding:0}:host(.mk-table--stacked) .mk-table__detail-row{margin:calc(-1 * var(--mk-space-3)) 0 var(--mk-space-3)}:host(.mk-table--stacked) .mk-table__group-row,:host(.mk-table--stacked) .mk-table__group{display:block;top:0}:host(.mk-table--stacked) .mk-table__row--empty,:host(.mk-table--stacked) .mk-table__empty{display:block;border:none;box-shadow:none;background:none}@media(pointer:coarse){.mk-table__cell-input{font-size:max(var(--mk-font-size-md),16px)}}@media print{.mk-table__scroll{overflow:visible;max-height:none;height:auto;border-radius:0}.mk-table__filter-row{display:none}.mk-table__head{display:table-header-group}.mk-table__th,.mk-table__td,.mk-table__filter-cell{position:static;box-shadow:none;background-color:transparent!important;color:inherit!important;border-bottom:1px solid var(--mk-border)}.mk-table__row,.mk-table__group-row,.mk-table__detail-row{break-inside:avoid}.mk-table__sort,.mk-table__resize,.mk-table__th--select,.mk-table__td--select,.mk-table__th--expand,.mk-table__td--expand,.mk-table__tree-toggle .mk-table__expander-icon{display:none}.mk-table__th-button,.mk-table__tree-toggle,.mk-table__group-toggle{color:inherit;cursor:default}}\n"], dependencies: [{ kind: "component", type: MkCheckbox, selector: "mk-checkbox", inputs: ["checked", "indeterminate", "disabled", "invalid", "required", "size", "tone", "aria-label"], outputs: ["checkedChange", "indeterminateChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1144
1749
  }
1145
1750
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTable, decorators: [{
1146
1751
  type: Component,
1147
1752
  args: [{ selector: 'mk-table', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MkCheckbox, NgTemplateOutlet], host: {
1148
1753
  class: 'mk-table',
1149
- '[class.mk-table--sticky]': 'stickyHeader()',
1754
+ '[class.mk-table--sticky]': 'stickyHeader() || isVirtual()',
1755
+ '[class.mk-table--virtual]': 'isVirtual()',
1756
+ '[class.mk-table--filterable]': 'filterable() && !stacked()',
1150
1757
  '[class.mk-table--zebra]': 'zebra()',
1151
1758
  '[class.mk-table--hover]': 'hover()',
1152
1759
  '[class.mk-table--compact]': "density() === 'compact'",
@@ -1155,8 +1762,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
1155
1762
  '[class.mk-table--expandable]': 'expandable()',
1156
1763
  '[class.mk-table--grouped]': 'groupBy() !== null',
1157
1764
  '[class.mk-table--stacked]': 'stacked()',
1158
- }, template: "<div class=\"mk-table__scroll\">\n <!-- Explicit roles ONLY while stacked: `display: block` strips a table\n element of its implicit role, so without these a card layout stops\n being announced as tabular data at all. Redundant in the grid, so\n they are left off there rather than duplicating what the element\n already says. -->\n <table\n class=\"mk-table__table\"\n [attr.role]=\"childrenKey() ? 'treegrid' : stacked() ? 'table' : null\"\n >\n <thead class=\"mk-table__head\" [attr.role]=\"stacked() ? 'rowgroup' : null\">\n <tr>\n @if (expandable()) {\n <th scope=\"col\" class=\"mk-table__th mk-table__th--expand\">\n <span class=\"mk-visually-hidden\">{{ i18n.expandHeader }}</span>\n </th>\n }\n @if (selectable()) {\n <th scope=\"col\" class=\"mk-table__th mk-table__th--select\">\n <mk-checkbox\n [aria-label]=\"i18n.selectAllRows\"\n [checked]=\"allSelected()\"\n [indeterminate]=\"someSelected()\"\n (checkedChange)=\"toggleAll()\"\n />\n </th>\n }\n @for (col of orderedColumns(); track col.key) {\n <th\n scope=\"col\"\n class=\"mk-table__th\"\n [class.mk-table__th--sortable]=\"col.sortable\"\n [class.mk-table__th--pinned]=\"col.pinned\"\n [class.mk-table__th--pinned-right]=\"col.pinned === 'right'\"\n [class.mk-table__th--dragging]=\"dragKey() === col.key\"\n [style.width]=\"colStyleWidth(col)\"\n [style.inset-inline-start.px]=\"col.pinned === 'left' ? pinnedOffset(col) : null\"\n [style.inset-inline-end.px]=\"col.pinned === 'right' ? pinnedOffset(col) : null\"\n [attr.data-align]=\"col.align ?? 'start'\"\n [attr.aria-sort]=\"ariaSort(col)\"\n [attr.draggable]=\"reorderableColumns() && !col.pinned ? true : null\"\n (dragstart)=\"onColDragStart($event, col)\"\n (dragover)=\"onColDragOver($event)\"\n (drop)=\"onColDrop($event, col)\"\n (dragend)=\"onColDragEnd()\"\n >\n @if (col.sortable || (reorderableColumns() && !col.pinned)) {\n <button\n type=\"button\"\n class=\"mk-table__th-button\"\n [class.mk-table__th-button--static]=\"!col.sortable\"\n (click)=\"onSort(col)\"\n (keydown)=\"onReorderKeydown($event, col)\"\n >\n <span class=\"mk-table__th-label\">{{ col.header }}</span>\n @if (col.sortable) {\n <span class=\"mk-table__sort\" aria-hidden=\"true\">{{ sortGlyph(col) }}</span>\n }\n </button>\n } @else {\n <span class=\"mk-table__th-inner\">\n <span class=\"mk-table__th-label\">{{ col.header }}</span>\n </span>\n }\n @if (resizableColumns() && col.resizable) {\n <span\n class=\"mk-table__resize\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"i18n.resizeColumn\"\n [attr.aria-valuemin]=\"resizeValueMin(col)\"\n [attr.aria-valuenow]=\"resizeValueNow(col)\"\n [attr.aria-valuemax]=\"resizeValueMax\"\n (pointerdown)=\"startResize($event, col)\"\n (keydown)=\"onResizeKeydown($event, col)\"\n (click)=\"$event.stopPropagation()\"\n ></span>\n }\n </th>\n }\n </tr>\n </thead>\n <tbody class=\"mk-table__body\" [attr.role]=\"stacked() ? 'rowgroup' : null\">\n @for (item of displayItems(); track trackItem(item); let i = $index) {\n @if (item.kind === 'group') {\n <tr class=\"mk-table__group-row\" [attr.role]=\"stacked() ? 'row' : null\">\n <th\n class=\"mk-table__group\"\n scope=\"colgroup\"\n [attr.role]=\"stacked() ? 'rowheader' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <button\n type=\"button\"\n class=\"mk-table__group-toggle\"\n [attr.aria-expanded]=\"!isGroupCollapsed(item.group.key)\"\n (click)=\"onGroupToggle(item.group)\"\n >\n <span\n class=\"mk-table__expander-icon\"\n [class.mk-table__expander-icon--open]=\"!isGroupCollapsed(item.group.key)\"\n aria-hidden=\"true\"\n >\u203A</span\n >\n <span class=\"mk-visually-hidden\">{{\n isGroupCollapsed(item.group.key) ? i18n.expandGroup : i18n.collapseGroup\n }}</span>\n <span class=\"mk-table__group-label\">{{ item.group.label }}</span>\n <span class=\"mk-table__group-count\">{{\n i18n.groupCount(item.group.rows.length)\n }}</span>\n </button>\n </th>\n </tr>\n } @else {\n <ng-container>\n <tr\n class=\"mk-table__row\"\n [attr.role]=\"stacked() || childrenKey() ? 'row' : null\"\n [class]=\"rowClassFor(item.row)\"\n [class.mk-table__row--selected]=\"selectable() && isSelected(item.row)\"\n [class.mk-table__row--expanded]=\"expandable() && isExpanded(item.row)\"\n [class.mk-table__row--parent]=\"item.hasChildren\"\n [style.--mk-tree-depth]=\"childrenKey() ? item.depth : null\"\n [style.margin-inline-start.px]=\"stacked() && item.depth ? item.depth * 16 : null\"\n [attr.aria-level]=\"childrenKey() ? item.depth + 1 : null\"\n [attr.aria-expanded]=\"item.hasChildren ? item.expanded : null\"\n [attr.tabindex]=\"clickableRows() ? 0 : null\"\n (click)=\"onRowClick(item.row)\"\n (keydown)=\"onRowKeydown($event, item.row)\"\n >\n @if (expandable()) {\n <td\n class=\"mk-table__td mk-table__td--expand\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <button\n type=\"button\"\n class=\"mk-table__expander\"\n [class.mk-table__expander--open]=\"isExpanded(item.row)\"\n [attr.aria-expanded]=\"isExpanded(item.row)\"\n [attr.aria-controls]=\"detailId(i)\"\n [attr.aria-label]=\"isExpanded(item.row) ? i18n.collapseRow : i18n.expandRow\"\n (click)=\"toggleExpand(item.row, $event)\"\n >\n <span class=\"mk-table__expander-icon\" aria-hidden=\"true\">\u203A</span>\n </button>\n </td>\n }\n @if (selectable()) {\n <td\n class=\"mk-table__td mk-table__td--select\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <mk-checkbox\n [aria-label]=\"i18n.selectRow(rowLabel(item.row))\"\n [checked]=\"isSelected(item.row)\"\n (checkedChange)=\"toggleRow(item.row)\"\n />\n </td>\n }\n @if (!stacked()) {\n @for (col of orderedColumns(); track col.key; let first = $first) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i, slot: null, tree: first ? item : null }\"\n />\n }\n } @else {\n <!-- Card layout. Same <td> elements, restyled \u2014 keeping the table\n DOM means selection, expansion, inline edit and every cell\n template keep working, since all of them reach for a `td`. -->\n @for (col of stackTitleColumns(); track col.key; let first = $first) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i, slot: 'title', tree: first ? item : null }\"\n />\n }\n @for (col of stackFieldColumns(); track col.key) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i, slot: 'field' }\"\n />\n }\n @for (col of stackFooterColumns(); track col.key) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i, slot: 'footer' }\"\n />\n }\n }\n </tr>\n @if (expandable() && isExpanded(item.row) && rowDetail()) {\n <tr class=\"mk-table__detail-row\" [attr.role]=\"stacked() ? 'row' : null\">\n <td\n class=\"mk-table__detail\"\n [id]=\"detailId(i)\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <div class=\"mk-table__detail-inner\">\n <ng-container\n [ngTemplateOutlet]=\"rowDetail()!.template\"\n [ngTemplateOutletContext]=\"{ $implicit: item.row }\"\n />\n </div>\n </td>\n </tr>\n }\n </ng-container>\n }\n } @empty {\n <tr class=\"mk-table__row mk-table__row--empty\" [attr.role]=\"stacked() ? 'row' : null\">\n <td\n class=\"mk-table__empty\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <ng-content select=\"[mkTableEmpty]\">{{ emptyMessage() }}</ng-content>\n </td>\n </tr>\n }\n </tbody>\n </table>\n</div>\n\n<!-- One cell, rendered by both layouts. `slot` is null in the grid and\n 'title' | 'field' | 'footer' in a card; everything else \u2014 the editor, the\n consumer's mkTableCell template, the formatted fallback \u2014 is identical, so\n a card can never drift from the grid it replaces. -->\n<ng-template #cellTpl let-col=\"col\" let-row=\"row\" let-i=\"i\" let-slot=\"slot\" let-tree=\"tree\">\n <td\n class=\"mk-table__td\"\n [class.mk-table__td--tree]=\"!!tree && !!childrenKey()\"\n [class.mk-table__td--pinned]=\"isPinned(col, 'left') || isPinned(col, 'right')\"\n [class.mk-table__td--pinned-right]=\"isPinned(col, 'right')\"\n [class.mk-table__td--editable]=\"col.editable\"\n [class.mk-table__td--stack-title]=\"slot === 'title'\"\n [class.mk-table__td--stack-field]=\"slot === 'field'\"\n [class.mk-table__td--stack-footer]=\"slot === 'footer'\"\n [style.width]=\"colStyleWidth(col)\"\n [style.inset-inline-start.px]=\"isPinned(col, 'left') ? pinnedOffset(col) : null\"\n [style.inset-inline-end.px]=\"isPinned(col, 'right') ? pinnedOffset(col) : null\"\n [attr.data-align]=\"col.align ?? 'start'\"\n [attr.tabindex]=\"col.editable ? 0 : null\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (dblclick)=\"startEdit(i, col, $event)\"\n (keydown)=\"onCellKeydown($event, i, col)\"\n >\n @if (slot === 'field' && hasStackLabel(col)) {\n <!-- The column header, moved beside the value. Not aria-hidden: the\n <thead> is display:none while stacked, so this label is the only\n thing naming the value for a screen reader. -->\n <span class=\"mk-table__cell-label\">{{ col.header }}</span>\n }\n @if (tree && childrenKey()) {\n <!-- Tree toggle (or a spacer on leaves) ahead of the first cell's value,\n so the indent and the caret read as one column. -->\n @if (tree.hasChildren) {\n <button\n type=\"button\"\n class=\"mk-table__tree-toggle\"\n [class.mk-table__tree-toggle--open]=\"tree.expanded\"\n [attr.aria-expanded]=\"tree.expanded\"\n [attr.aria-label]=\"tree.expanded ? i18n.collapseTreeRow : i18n.expandTreeRow\"\n (click)=\"toggleTreeRow(row, $event)\"\n >\n <span class=\"mk-table__expander-icon\" [class.mk-table__expander-icon--open]=\"tree.expanded\" aria-hidden=\"true\">\u203A</span>\n </button>\n } @else {\n <span class=\"mk-table__tree-spacer\" aria-hidden=\"true\"></span>\n }\n }\n <span class=\"mk-table__cell-value\">\n @if (isEditing(i, col)) {\n <input\n #editInput\n class=\"mk-table__cell-input\"\n [value]=\"cellText(row, col)\"\n [attr.aria-label]=\"col.header\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onEditKeydown($event, row, col)\"\n (blur)=\"commitEdit(row, col, $any($event.target).value)\"\n />\n } @else if (cellTemplateFor(col.key); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: cellValue(row, col), row }\"\n />\n } @else {\n {{ cellText(row, col) }}\n @if (col.editable) {\n <span class=\"mk-visually-hidden\">{{ i18n.editCell }}</span>\n }\n }\n </span>\n </td>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{--_cell-pad-y: var(--mk-space-3);--_cell-pad-x: var(--mk-space-4);display:block;color:var(--mk-text)}:host(.mk-table--compact){--_cell-pad-y: var(--mk-space-2);--_cell-pad-x: var(--mk-space-3)}.mk-table__scroll{width:100%;overflow-x:auto;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg)}.mk-table__table{width:100%;border-collapse:collapse;font-size:var(--mk-font-size-sm);background-color:var(--mk-surface)}.mk-table__th{padding:var(--_cell-pad-y) var(--_cell-pad-x);background-color:var(--mk-surface-2);color:var(--mk-text-muted);font-weight:var(--mk-font-weight-semibold);text-align:start;white-space:nowrap;border-bottom:var(--mk-border-width) solid var(--mk-border);vertical-align:middle}:host(.mk-table--sticky) .mk-table__head .mk-table__th{position:sticky;top:0;z-index:var(--mk-z-sticky)}.mk-table__th-inner{display:inline-flex;align-items:center;gap:var(--mk-space-1)}.mk-table__th--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.mk-table__th--sortable:hover{background-color:var(--mk-surface-3);color:var(--mk-text)}.mk-table__th-button{display:inline-flex;align-items:center;gap:var(--mk-space-1);width:100%;margin:calc(-1 * var(--_cell-pad-y)) calc(-1 * var(--_cell-pad-x));padding:var(--_cell-pad-y) var(--_cell-pad-x);font:inherit;font-weight:inherit;color:inherit;text-align:inherit;background:transparent;border:0;cursor:pointer}.mk-table__th-button--static{cursor:grab}.mk-table__th-button:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th[data-align=center] .mk-table__th-button{justify-content:center}.mk-table__th[data-align=end] .mk-table__th-button{justify-content:flex-end}.mk-table__th[aria-sort=ascending],.mk-table__th[aria-sort=descending]{color:var(--mk-text)}.mk-table__sort{font-size:var(--mk-font-size-xs);opacity:.7;line-height:1}.mk-table__td{padding:var(--_cell-pad-y) var(--_cell-pad-x);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);vertical-align:middle}.mk-table__row:last-child .mk-table__td{border-bottom:0}.mk-table__th[data-align=center],.mk-table__td[data-align=center]{text-align:center}.mk-table__th[data-align=end],.mk-table__td[data-align=end]{text-align:right}:host(.mk-table--zebra) .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td{background-color:var(--mk-surface-2)}:host(.mk-table--hover) .mk-table__body .mk-table__row:not(.mk-table__row--empty):hover .mk-table__td{background-color:var(--mk-neutral-subtle)}:host(.mk-table--clickable) .mk-table__row:not(.mk-table__row--empty){cursor:pointer}:host(.mk-table--clickable) .mk-table__row:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th--select,.mk-table__td--select{width:1%;white-space:nowrap;text-align:center;vertical-align:middle}:host .mk-table__body .mk-table__row--selected .mk-table__td,:host(.mk-table--zebra) .mk-table__body .mk-table__row--selected:nth-child(2n) .mk-table__td,:host(.mk-table--hover) .mk-table__body .mk-table__row--selected:hover .mk-table__td{background-color:var(--mk-selected-bg);color:var(--mk-selected-text)}.mk-table__th--expand,.mk-table__td--expand{width:1%;white-space:nowrap;text-align:center;vertical-align:middle}.mk-table__expander{display:inline-grid;place-items:center;width:1.5rem;height:1.5rem;padding:0;color:var(--mk-text-muted);background:transparent;border:none;border-radius:var(--mk-radius-sm);cursor:pointer}.mk-table__expander:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-table__expander:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__expander-icon{display:inline-block;font-size:var(--mk-font-size-lg);line-height:1;transition:transform var(--mk-transition-fast, .12s) ease}.mk-table__expander--open .mk-table__expander-icon,.mk-table__expander-icon--open{transform:rotate(90deg)}:host(:dir(rtl)) .mk-table__expander-icon{transform:scaleX(-1)}:host(:dir(rtl)) .mk-table__expander--open .mk-table__expander-icon,:host(:dir(rtl)) .mk-table__expander-icon--open{transform:scaleX(-1) rotate(-90deg)}.mk-table__detail{padding:0;background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle)}.mk-table__detail-inner{padding:var(--_cell-pad-y) var(--_cell-pad-x)}.mk-table__th{position:relative}.mk-table__resize{position:absolute;top:0;inset-inline-end:0;width:8px;height:100%;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}@media(pointer:coarse){.mk-table__resize:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-table__resize:after{content:\"\";position:absolute;top:25%;inset-inline-end:3px;width:2px;height:50%;background-color:var(--mk-border)}.mk-table__resize:hover:after,.mk-table__resize:focus-visible:after{background-color:var(--mk-primary)}.mk-table__resize:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th[draggable=true]{cursor:grab}.mk-table__th--dragging{opacity:.5}.mk-table__th--pinned,.mk-table__td--pinned{position:sticky;z-index:1;background-color:var(--mk-surface)}.mk-table__th--pinned{z-index:2;background-color:var(--mk-surface-2)}.mk-table__th--pinned:not(.mk-table__th--pinned-right),.mk-table__td--pinned:not(.mk-table__td--pinned-right){box-shadow:2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}.mk-table__th--pinned-right,.mk-table__td--pinned-right{box-shadow:-2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(:dir(rtl)) .mk-table__th--pinned:not(.mk-table__th--pinned-right),:host(:dir(rtl)) .mk-table__td--pinned:not(.mk-table__td--pinned-right){box-shadow:-2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(:dir(rtl)) .mk-table__th--pinned-right,:host(:dir(rtl)) .mk-table__td--pinned-right{box-shadow:2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(.mk-table--zebra) .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td--pinned{background-color:var(--mk-surface-2)}.mk-table__td--editable{cursor:text}.mk-table__td--editable:hover{background-color:var(--mk-hover-overlay)}.mk-table__td--editable:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__cell-input{width:100%;margin:calc(-1 * var(--mk-space-1)) 0;padding:var(--mk-space-1) var(--mk-space-2);font:inherit;color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-primary);border-radius:var(--mk-radius-sm);outline:none}.mk-table__empty{padding:var(--mk-space-8) var(--_cell-pad-x);text-align:center;color:var(--mk-text-subtle)}.mk-table__td--tree{padding-inline-start:calc(var(--_cell-pad-x) + var(--mk-tree-depth, 0) * 1.25rem);white-space:nowrap}.mk-table__tree-toggle,.mk-table__tree-spacer{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;margin-inline-end:var(--mk-space-1);vertical-align:middle;flex:none}.mk-table__tree-toggle{padding:0;border:0;border-radius:var(--mk-radius-sm);background:transparent;color:var(--mk-text-muted);cursor:pointer}.mk-table__tree-toggle:hover{color:var(--mk-text);background:var(--mk-hover-overlay)}.mk-table__tree-toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1)}.mk-table__td--tree .mk-table__cell-value{display:inline;vertical-align:middle}.mk-table__group{padding:0;background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);text-align:start}:host(.mk-table--grouped) .mk-table__group{position:sticky;top:var(--_group-top, 0px);z-index:calc(var(--mk-z-sticky) - 1)}.mk-table__group-toggle{display:flex;align-items:center;gap:var(--mk-space-2);width:100%;padding:var(--mk-space-2) var(--mk-space-3);border:none;background:none;color:var(--mk-text);font:inherit;font-weight:var(--mk-font-weight-semibold);cursor:pointer}.mk-table__group-toggle:hover{background-color:var(--mk-hover-overlay)}.mk-table__group-toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__group-count{padding:0 var(--mk-space-2);border-radius:var(--mk-radius-full);background-color:var(--mk-surface-3);color:var(--mk-text-muted);font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-regular)}:host(.mk-table--stacked) .mk-table__scroll{overflow-x:visible;border:none;border-radius:0;background:none}:host(.mk-table--stacked) .mk-table__table,:host(.mk-table--stacked) .mk-table__body{display:block;background:none}:host(.mk-table--stacked) .mk-table__head{display:none}:host(.mk-table--stacked) .mk-table__row{display:grid;grid-template-columns:1fr;gap:var(--mk-space-1);padding:var(--mk-space-3);margin-bottom:var(--mk-space-3);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);background-color:var(--mk-surface);box-shadow:var(--mk-shadow-xs)}:host(.mk-table--stacked) .mk-table__td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--mk-space-3);padding:var(--mk-space-1) 0;border:none;text-align:start}:host(.mk-table--stacked) .mk-table__td--stack-title{justify-content:flex-start;padding-bottom:var(--mk-space-2);font-size:var(--mk-font-size-lg);font-weight:var(--mk-font-weight-semibold);color:var(--mk-text)}:host(.mk-table--stacked) .mk-table__td--stack-title~.mk-table__td--stack-title{justify-content:flex-end;margin-top:calc(-1 * var(--mk-space-2) - 1.5em);font-size:var(--mk-font-size-md)}:host(.mk-table--stacked) .mk-table__cell-label{flex:none;color:var(--mk-text-muted);font-size:var(--mk-font-size-sm)}:host(.mk-table--stacked) .mk-table__cell-value{min-width:0;text-align:end;overflow-wrap:anywhere}:host(.mk-table--stacked) .mk-table__td--stack-title .mk-table__cell-value,:host(.mk-table--stacked) .mk-table__td--stack-footer .mk-table__cell-value{text-align:start}:host(.mk-table--stacked) .mk-table__td--stack-footer{justify-content:flex-start;margin-top:var(--mk-space-2);padding-top:var(--mk-space-3);border-top:var(--mk-border-width) solid var(--mk-border-subtle)}:host(.mk-table--stacked) .mk-table__td--stack-footer .mk-table__cell-value{display:flex;flex-wrap:wrap;gap:var(--mk-space-2);width:100%}:host(.mk-table--stacked) .mk-table__td--select,:host(.mk-table--stacked) .mk-table__td--expand{width:auto;justify-content:flex-start}:host(.mk-table--stacked).mk-table--zebra .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td{background:none}:host(.mk-table--stacked) .mk-table__detail-row,:host(.mk-table--stacked) .mk-table__detail{display:block;padding:0}:host(.mk-table--stacked) .mk-table__detail-row{margin:calc(-1 * var(--mk-space-3)) 0 var(--mk-space-3)}:host(.mk-table--stacked) .mk-table__group-row,:host(.mk-table--stacked) .mk-table__group{display:block;top:0}:host(.mk-table--stacked) .mk-table__row--empty,:host(.mk-table--stacked) .mk-table__empty{display:block;border:none;box-shadow:none;background:none}@media(pointer:coarse){.mk-table__cell-input{font-size:max(var(--mk-font-size-md),16px)}}@media print{.mk-table__scroll{overflow:visible;border-radius:0}.mk-table__head{display:table-header-group}.mk-table__th,.mk-table__td{position:static;box-shadow:none;background-color:transparent!important;color:inherit!important;border-bottom:1px solid var(--mk-border)}.mk-table__row,.mk-table__group-row,.mk-table__detail-row{break-inside:avoid}.mk-table__sort,.mk-table__resize,.mk-table__th--select,.mk-table__td--select,.mk-table__th--expand,.mk-table__td--expand,.mk-table__tree-toggle .mk-table__expander-icon{display:none}.mk-table__th-button,.mk-table__tree-toggle,.mk-table__group-toggle{color:inherit;cursor:default}}\n"] }]
1159
- }], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], stickyHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyHeader", required: false }] }], zebra: [{ type: i0.Input, args: [{ isSignal: true, alias: "zebra", required: false }] }], hover: [{ type: i0.Input, args: [{ isSignal: true, alias: "hover", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], stackAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackAt", required: false }] }], clickableRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickableRows", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], trackKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackKey", required: false }] }], rowClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClass", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], singleExpand: [{ type: i0.Input, args: [{ isSignal: true, alias: "singleExpand", required: false }] }], resizableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableColumns", required: false }] }], reorderableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderableColumns", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], groupLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupLabel", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], expandedChange: [{ type: i0.Output, args: ["expandedChange"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], columnReorder: [{ type: i0.Output, args: ["columnReorder"] }], cellEdit: [{ type: i0.Output, args: ["cellEdit"] }], groupToggle: [{ type: i0.Output, args: ["groupToggle"] }], childrenKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "childrenKey", required: false }] }], treeToggle: [{ type: i0.Output, args: ["treeToggle"] }], editInput: [{ type: i0.ViewChild, args: ['editInput', { isSignal: true }] }], rowDetail: [{ type: i0.ContentChild, args: [i0.forwardRef(() => MkTableRowDetail), { isSignal: true }] }], cellTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkTableCell), { isSignal: true }] }] } });
1765
+ }, template: "<div\n #scroller\n class=\"mk-table__scroll\"\n [style.height]=\"scrollHeight()\"\n [style.max-height]=\"scrollMaxHeight()\"\n [attr.data-row-height]=\"isVirtual() ? effectiveRowHeight() : null\"\n (scroll)=\"onScroll($event)\"\n>\n <!-- Explicit roles ONLY while stacked: `display: block` strips a table\n element of its implicit role, so without these a card layout stops\n being announced as tabular data at all. Redundant in the grid, so\n they are left off there rather than duplicating what the element\n already says. -->\n <table\n class=\"mk-table__table\"\n [attr.role]=\"childrenKey() ? 'treegrid' : stacked() ? 'table' : null\"\n [attr.aria-rowcount]=\"isVirtual() ? displayItems().length + headerRows() : null\"\n >\n <thead class=\"mk-table__head\" [attr.role]=\"stacked() ? 'rowgroup' : null\">\n <tr>\n @if (expandable()) {\n <th scope=\"col\" class=\"mk-table__th mk-table__th--expand\">\n <span class=\"mk-visually-hidden\">{{ i18n.expandHeader }}</span>\n </th>\n }\n @if (selectable()) {\n <th scope=\"col\" class=\"mk-table__th mk-table__th--select\">\n <mk-checkbox\n [aria-label]=\"i18n.selectAllRows\"\n [checked]=\"allSelected()\"\n [indeterminate]=\"someSelected()\"\n (checkedChange)=\"toggleAll()\"\n />\n </th>\n }\n @for (col of orderedColumns(); track col.key) {\n <th\n scope=\"col\"\n class=\"mk-table__th\"\n [class.mk-table__th--sortable]=\"col.sortable\"\n [class.mk-table__th--pinned]=\"col.pinned\"\n [class.mk-table__th--pinned-right]=\"col.pinned === 'right'\"\n [class.mk-table__th--dragging]=\"dragKey() === col.key\"\n [style.width]=\"colStyleWidth(col)\"\n [style.inset-inline-start.px]=\"col.pinned === 'left' ? pinnedOffset(col) : null\"\n [style.inset-inline-end.px]=\"col.pinned === 'right' ? pinnedOffset(col) : null\"\n [attr.data-align]=\"col.align ?? 'start'\"\n [attr.aria-sort]=\"ariaSort(col)\"\n [attr.draggable]=\"reorderableColumns() && !col.pinned ? true : null\"\n (dragstart)=\"onColDragStart($event, col)\"\n (dragover)=\"onColDragOver($event)\"\n (drop)=\"onColDrop($event, col)\"\n (dragend)=\"onColDragEnd()\"\n >\n @if (col.sortable || (reorderableColumns() && !col.pinned)) {\n <button\n type=\"button\"\n class=\"mk-table__th-button\"\n [class.mk-table__th-button--static]=\"!col.sortable\"\n (click)=\"onSort(col)\"\n (keydown)=\"onReorderKeydown($event, col)\"\n >\n <span class=\"mk-table__th-label\">{{ col.header }}</span>\n @if (col.sortable) {\n <span class=\"mk-table__sort\" aria-hidden=\"true\">{{ sortGlyph(col) }}</span>\n }\n </button>\n } @else {\n <span class=\"mk-table__th-inner\">\n <span class=\"mk-table__th-label\">{{ col.header }}</span>\n </span>\n }\n @if (resizableColumns() && col.resizable) {\n <span\n class=\"mk-table__resize\"\n role=\"separator\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n [attr.aria-label]=\"i18n.resizeColumn\"\n [attr.aria-valuemin]=\"resizeValueMin(col)\"\n [attr.aria-valuenow]=\"resizeValueNow(col)\"\n [attr.aria-valuemax]=\"resizeValueMax\"\n (pointerdown)=\"startResize($event, col)\"\n (keydown)=\"onResizeKeydown($event, col)\"\n (click)=\"$event.stopPropagation()\"\n ></span>\n }\n </th>\n }\n </tr>\n @if (filterable() && !stacked()) {\n <!-- Filter row: native controls styled with tokens, so the table entry\n point stays free of the forms / datetime entries. -->\n <tr class=\"mk-table__filter-row\">\n @if (expandable()) {\n <td class=\"mk-table__filter-cell mk-table__th--expand\"></td>\n }\n @if (selectable()) {\n <td class=\"mk-table__filter-cell mk-table__th--select\"></td>\n }\n @for (col of orderedColumns(); track col.key) {\n @let kind = filterKind(col);\n <td\n class=\"mk-table__filter-cell\"\n [class.mk-table__th--pinned]=\"col.pinned\"\n [class.mk-table__th--pinned-right]=\"col.pinned === 'right'\"\n [style.width]=\"colStyleWidth(col)\"\n [style.inset-inline-start.px]=\"col.pinned === 'left' ? pinnedOffset(col) : null\"\n [style.inset-inline-end.px]=\"col.pinned === 'right' ? pinnedOffset(col) : null\"\n >\n @if (kind === 'select') {\n <select\n class=\"mk-table__filter-control mk-table__filter-select\"\n [class.mk-table__filter-control--active]=\"hasFilter(col.key)\"\n [attr.data-filter-key]=\"col.key\"\n [attr.aria-label]=\"filterLabel(col)\"\n (change)=\"onFilterSelect(col, $event)\"\n >\n <option value=\"\" [selected]=\"!hasFilter(col.key)\">\n {{ col.filterPlaceholder ?? i18n.filterAny }}\n </option>\n @for (option of filterOptionsFor(col); track $index) {\n <option [value]=\"option.value\" [selected]=\"isFilterOption(col, option)\">\n {{ option.label }}\n </option>\n }\n </select>\n } @else if (kind) {\n <span class=\"mk-table__filter\" [class.mk-table__filter--active]=\"hasFilter(col.key)\">\n <input\n class=\"mk-table__filter-control mk-table__filter-input\"\n [type]=\"kind === 'text' ? 'search' : kind\"\n [attr.inputmode]=\"kind === 'number' ? 'decimal' : null\"\n [attr.data-filter-key]=\"col.key\"\n [attr.aria-label]=\"filterLabel(col)\"\n [attr.placeholder]=\"filterPlaceholder(col)\"\n [value]=\"filterText(col.key)\"\n (input)=\"onFilterInput(col, $event)\"\n (keydown)=\"onFilterKeydown(col, $event)\"\n />\n @if (hasFilter(col.key)) {\n <button\n type=\"button\"\n class=\"mk-table__filter-clear\"\n [attr.aria-label]=\"i18n.clearFilter(col.header || col.key)\"\n (click)=\"clearFilter(col.key)\"\n >\n <span aria-hidden=\"true\">\u00D7</span>\n </button>\n }\n </span>\n }\n </td>\n }\n </tr>\n }\n </thead>\n <tbody class=\"mk-table__body\" [attr.role]=\"stacked() ? 'rowgroup' : null\">\n @if (isVirtual() && topSpace() > 0) {\n <tr class=\"mk-table__spacer\" aria-hidden=\"true\">\n <td [attr.colspan]=\"totalColumns()\" [style.height.px]=\"topSpace()\"></td>\n </tr>\n }\n @for (item of renderedItems(); track trackItem(item); let i = $index) {\n @let idx = windowStart() + i;\n @if (item.kind === 'group') {\n <tr\n class=\"mk-table__group-row\"\n [attr.role]=\"stacked() ? 'row' : null\"\n [attr.data-index]=\"idx\"\n [attr.aria-rowindex]=\"isVirtual() ? idx + headerRows() + 1 : null\"\n >\n <th\n class=\"mk-table__group\"\n scope=\"colgroup\"\n [attr.role]=\"stacked() ? 'rowheader' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <button\n type=\"button\"\n class=\"mk-table__group-toggle\"\n [attr.aria-expanded]=\"!isGroupCollapsed(item.group.key)\"\n (click)=\"onGroupToggle(item.group)\"\n >\n <span\n class=\"mk-table__expander-icon\"\n [class.mk-table__expander-icon--open]=\"!isGroupCollapsed(item.group.key)\"\n aria-hidden=\"true\"\n >\u203A</span\n >\n <span class=\"mk-visually-hidden\">{{\n isGroupCollapsed(item.group.key) ? i18n.expandGroup : i18n.collapseGroup\n }}</span>\n <span class=\"mk-table__group-label\">{{ item.group.label }}</span>\n <span class=\"mk-table__group-count\">{{\n i18n.groupCount(item.group.rows.length)\n }}</span>\n </button>\n </th>\n </tr>\n } @else {\n <ng-container>\n <tr\n class=\"mk-table__row\"\n [attr.role]=\"stacked() || childrenKey() ? 'row' : null\"\n [class]=\"rowClassFor(item.row)\"\n [class.mk-table__row--selected]=\"selectable() && isSelected(item.row)\"\n [class.mk-table__row--expanded]=\"expandable() && isExpanded(item.row)\"\n [class.mk-table__row--parent]=\"item.hasChildren\"\n [class.mk-table__row--even]=\"isVirtual() && idx % 2 === 1\"\n [style.--mk-tree-depth]=\"childrenKey() ? item.depth : null\"\n [style.margin-inline-start.px]=\"stacked() && item.depth ? item.depth * 16 : null\"\n [attr.data-index]=\"idx\"\n [attr.aria-rowindex]=\"isVirtual() ? idx + headerRows() + 1 : null\"\n [attr.aria-level]=\"childrenKey() ? item.depth + 1 : null\"\n [attr.aria-expanded]=\"item.hasChildren ? item.expanded : null\"\n [attr.tabindex]=\"clickableRows() ? 0 : null\"\n (click)=\"onRowClick(item.row)\"\n (keydown)=\"onRowKeydown($event, item.row)\"\n >\n @if (expandable()) {\n <td\n class=\"mk-table__td mk-table__td--expand\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <button\n type=\"button\"\n class=\"mk-table__expander\"\n [class.mk-table__expander--open]=\"isExpanded(item.row)\"\n [attr.aria-expanded]=\"isExpanded(item.row)\"\n [attr.aria-controls]=\"detailId(idx)\"\n [attr.aria-label]=\"isExpanded(item.row) ? i18n.collapseRow : i18n.expandRow\"\n (click)=\"toggleExpand(item.row, $event)\"\n >\n <span class=\"mk-table__expander-icon\" aria-hidden=\"true\">\u203A</span>\n </button>\n </td>\n }\n @if (selectable()) {\n <td\n class=\"mk-table__td mk-table__td--select\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (click)=\"$event.stopPropagation()\"\n >\n <mk-checkbox\n [aria-label]=\"i18n.selectRow(rowLabel(item.row))\"\n [checked]=\"isSelected(item.row)\"\n (checkedChange)=\"toggleRow(item.row)\"\n />\n </td>\n }\n @if (!stacked()) {\n @for (col of orderedColumns(); track col.key; let first = $first) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i: idx, slot: null, tree: first ? item : null }\"\n />\n }\n } @else {\n <!-- Card layout. Same <td> elements, restyled \u2014 keeping the table\n DOM means selection, expansion, inline edit and every cell\n template keep working, since all of them reach for a `td`. -->\n @for (col of stackTitleColumns(); track col.key; let first = $first) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i: idx, slot: 'title', tree: first ? item : null }\"\n />\n }\n @for (col of stackFieldColumns(); track col.key) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i: idx, slot: 'field' }\"\n />\n }\n @for (col of stackFooterColumns(); track col.key) {\n <ng-container\n [ngTemplateOutlet]=\"cellTpl\"\n [ngTemplateOutletContext]=\"{ col, row: item.row, i: idx, slot: 'footer' }\"\n />\n }\n }\n </tr>\n @if (expandable() && isExpanded(item.row) && rowDetail()) {\n <tr\n class=\"mk-table__detail-row\"\n [attr.role]=\"stacked() ? 'row' : null\"\n [attr.data-index]=\"idx\"\n >\n <td\n class=\"mk-table__detail\"\n [id]=\"detailId(idx)\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <div class=\"mk-table__detail-inner\">\n <ng-container\n [ngTemplateOutlet]=\"rowDetail()!.template\"\n [ngTemplateOutletContext]=\"{ $implicit: item.row }\"\n />\n </div>\n </td>\n </tr>\n }\n </ng-container>\n }\n } @empty {\n <tr class=\"mk-table__row mk-table__row--empty\" [attr.role]=\"stacked() ? 'row' : null\">\n <td\n class=\"mk-table__empty\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n [attr.colspan]=\"totalColumns()\"\n >\n <ng-content select=\"[mkTableEmpty]\">{{ emptyMessage() }}</ng-content>\n </td>\n </tr>\n }\n @if (isVirtual() && bottomSpace() > 0) {\n <tr class=\"mk-table__spacer\" aria-hidden=\"true\">\n <td [attr.colspan]=\"totalColumns()\" [style.height.px]=\"bottomSpace()\"></td>\n </tr>\n }\n </tbody>\n </table>\n</div>\n\n<!-- One cell, rendered by both layouts. `slot` is null in the grid and\n 'title' | 'field' | 'footer' in a card; everything else \u2014 the editor, the\n consumer's mkTableCell template, the formatted fallback \u2014 is identical, so\n a card can never drift from the grid it replaces. -->\n<ng-template #cellTpl let-col=\"col\" let-row=\"row\" let-i=\"i\" let-slot=\"slot\" let-tree=\"tree\">\n <td\n class=\"mk-table__td\"\n [class.mk-table__td--tree]=\"!!tree && !!childrenKey()\"\n [class.mk-table__td--pinned]=\"isPinned(col, 'left') || isPinned(col, 'right')\"\n [class.mk-table__td--pinned-right]=\"isPinned(col, 'right')\"\n [class.mk-table__td--editable]=\"col.editable\"\n [class.mk-table__td--stack-title]=\"slot === 'title'\"\n [class.mk-table__td--stack-field]=\"slot === 'field'\"\n [class.mk-table__td--stack-footer]=\"slot === 'footer'\"\n [style.width]=\"colStyleWidth(col)\"\n [style.inset-inline-start.px]=\"isPinned(col, 'left') ? pinnedOffset(col) : null\"\n [style.inset-inline-end.px]=\"isPinned(col, 'right') ? pinnedOffset(col) : null\"\n [attr.data-align]=\"col.align ?? 'start'\"\n [attr.tabindex]=\"col.editable ? 0 : null\"\n [attr.role]=\"stacked() ? 'cell' : null\"\n (dblclick)=\"startEdit(i, col, $event)\"\n (keydown)=\"onCellKeydown($event, i, col)\"\n >\n @if (slot === 'field' && hasStackLabel(col)) {\n <!-- The column header, moved beside the value. Not aria-hidden: the\n <thead> is display:none while stacked, so this label is the only\n thing naming the value for a screen reader. -->\n <span class=\"mk-table__cell-label\">{{ col.header }}</span>\n }\n @if (tree && childrenKey()) {\n <!-- Tree toggle (or a spacer on leaves) ahead of the first cell's value,\n so the indent and the caret read as one column. -->\n @if (tree.hasChildren) {\n <button\n type=\"button\"\n class=\"mk-table__tree-toggle\"\n [class.mk-table__tree-toggle--open]=\"tree.expanded\"\n [attr.aria-expanded]=\"tree.expanded\"\n [attr.aria-label]=\"tree.expanded ? i18n.collapseTreeRow : i18n.expandTreeRow\"\n (click)=\"toggleTreeRow(row, $event)\"\n >\n <span class=\"mk-table__expander-icon\" [class.mk-table__expander-icon--open]=\"tree.expanded\" aria-hidden=\"true\">\u203A</span>\n </button>\n } @else {\n <span class=\"mk-table__tree-spacer\" aria-hidden=\"true\"></span>\n }\n }\n <span class=\"mk-table__cell-value\">\n @if (isEditing(i, col)) {\n <input\n #editInput\n class=\"mk-table__cell-input\"\n [value]=\"cellText(row, col)\"\n [attr.aria-label]=\"col.header\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onEditKeydown($event, row, col)\"\n (blur)=\"commitEdit(row, col, $any($event.target).value)\"\n />\n } @else if (cellTemplateFor(col.key); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: cellValue(row, col), row }\"\n />\n } @else {\n {{ cellText(row, col) }}\n @if (col.editable) {\n <span class=\"mk-visually-hidden\">{{ i18n.editCell }}</span>\n }\n }\n </span>\n </td>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{--_cell-pad-y: var(--mk-space-3);--_cell-pad-x: var(--mk-space-4);display:block;color:var(--mk-text)}:host(.mk-table--compact){--_cell-pad-y: var(--mk-space-2);--_cell-pad-x: var(--mk-space-3)}.mk-table__scroll{width:100%;overflow:auto;border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg)}.mk-table__table{width:100%;border-collapse:collapse;font-size:var(--mk-font-size-sm);background-color:var(--mk-surface)}.mk-table__th{padding:var(--_cell-pad-y) var(--_cell-pad-x);background-color:var(--mk-surface-2);color:var(--mk-text-muted);font-weight:var(--mk-font-weight-semibold);text-align:start;white-space:nowrap;border-bottom:var(--mk-border-width) solid var(--mk-border);vertical-align:middle}:host(.mk-table--sticky) .mk-table__head .mk-table__th{position:sticky;top:0;z-index:var(--mk-z-sticky)}.mk-table__th-inner{display:inline-flex;align-items:center;gap:var(--mk-space-1)}.mk-table__th--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.mk-table__th--sortable:hover{background-color:var(--mk-surface-3);color:var(--mk-text)}.mk-table__th-button{display:inline-flex;align-items:center;gap:var(--mk-space-1);width:100%;margin:calc(-1 * var(--_cell-pad-y)) calc(-1 * var(--_cell-pad-x));padding:var(--_cell-pad-y) var(--_cell-pad-x);font:inherit;font-weight:inherit;color:inherit;text-align:inherit;background:transparent;border:0;cursor:pointer}.mk-table__th-button--static{cursor:grab}.mk-table__th-button:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th[data-align=center] .mk-table__th-button{justify-content:center}.mk-table__th[data-align=end] .mk-table__th-button{justify-content:flex-end}.mk-table__th[aria-sort=ascending],.mk-table__th[aria-sort=descending]{color:var(--mk-text)}.mk-table__sort{font-size:var(--mk-font-size-xs);opacity:.7;line-height:1}.mk-table__td{padding:var(--_cell-pad-y) var(--_cell-pad-x);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);vertical-align:middle}.mk-table__row:last-child .mk-table__td{border-bottom:0}.mk-table__th[data-align=center],.mk-table__td[data-align=center]{text-align:center}.mk-table__th[data-align=end],.mk-table__td[data-align=end]{text-align:right}:host(.mk-table--zebra:not(.mk-table--virtual)) .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td,:host(.mk-table--zebra) .mk-table__body .mk-table__row--even .mk-table__td{background-color:var(--mk-surface-2)}:host(.mk-table--hover) .mk-table__body .mk-table__row:not(.mk-table__row--empty):hover .mk-table__td{background-color:var(--mk-neutral-subtle)}:host(.mk-table--clickable) .mk-table__row:not(.mk-table__row--empty){cursor:pointer}:host(.mk-table--clickable) .mk-table__row:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th--select,.mk-table__td--select{width:1%;white-space:nowrap;text-align:center;vertical-align:middle}:host .mk-table__body .mk-table__row--selected .mk-table__td,:host(.mk-table--zebra) .mk-table__body .mk-table__row--selected:nth-child(2n) .mk-table__td,:host(.mk-table--zebra) .mk-table__body .mk-table__row--selected.mk-table__row--even .mk-table__td,:host(.mk-table--hover) .mk-table__body .mk-table__row--selected:hover .mk-table__td{background-color:var(--mk-selected-bg);color:var(--mk-selected-text)}.mk-table__th--expand,.mk-table__td--expand{width:1%;white-space:nowrap;text-align:center;vertical-align:middle}.mk-table__expander{display:inline-grid;place-items:center;width:1.5rem;height:1.5rem;padding:0;color:var(--mk-text-muted);background:transparent;border:none;border-radius:var(--mk-radius-sm);cursor:pointer}.mk-table__expander:hover{background-color:var(--mk-hover-overlay);color:var(--mk-text)}.mk-table__expander:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__expander-icon{display:inline-block;font-size:var(--mk-font-size-lg);line-height:1;transition:transform var(--mk-transition-fast, .12s) ease}.mk-table__expander--open .mk-table__expander-icon,.mk-table__expander-icon--open{transform:rotate(90deg)}:host(:dir(rtl)) .mk-table__expander-icon{transform:scaleX(-1)}:host(:dir(rtl)) .mk-table__expander--open .mk-table__expander-icon,:host(:dir(rtl)) .mk-table__expander-icon--open{transform:scaleX(-1) rotate(-90deg)}.mk-table__detail{padding:0;background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle)}.mk-table__detail-inner{padding:var(--_cell-pad-y) var(--_cell-pad-x)}.mk-table__th{position:relative}.mk-table__resize{position:absolute;top:0;inset-inline-end:0;width:8px;height:100%;cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none}@media(pointer:coarse){.mk-table__resize:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-table__resize:after{content:\"\";position:absolute;top:25%;inset-inline-end:3px;width:2px;height:50%;background-color:var(--mk-border)}.mk-table__resize:hover:after,.mk-table__resize:focus-visible:after{background-color:var(--mk-primary)}.mk-table__resize:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__th[draggable=true]{cursor:grab}.mk-table__th--dragging{opacity:.5}.mk-table__th--pinned,.mk-table__td--pinned{position:sticky;z-index:1;background-color:var(--mk-surface)}.mk-table__th--pinned{z-index:2;background-color:var(--mk-surface-2)}.mk-table__th--pinned:not(.mk-table__th--pinned-right),.mk-table__td--pinned:not(.mk-table__td--pinned-right){box-shadow:2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}.mk-table__th--pinned-right,.mk-table__td--pinned-right{box-shadow:-2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(:dir(rtl)) .mk-table__th--pinned:not(.mk-table__th--pinned-right),:host(:dir(rtl)) .mk-table__td--pinned:not(.mk-table__td--pinned-right){box-shadow:-2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(:dir(rtl)) .mk-table__th--pinned-right,:host(:dir(rtl)) .mk-table__td--pinned-right{box-shadow:2px 0 4px -2px var(--mk-shadow-color, rgba(0, 0, 0, .15))}:host(.mk-table--zebra:not(.mk-table--virtual)) .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td--pinned,:host(.mk-table--zebra) .mk-table__body .mk-table__row--even .mk-table__td--pinned{background-color:var(--mk-surface-2)}.mk-table__filter-cell{position:relative;padding:var(--mk-space-1) var(--mk-space-2);background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border);vertical-align:middle}:host(.mk-table--sticky) .mk-table__filter-cell{position:sticky;top:var(--_filter-top, 0px);z-index:var(--mk-z-sticky)}:host(.mk-table--sticky) .mk-table__filter-cell.mk-table__th--pinned{z-index:calc(var(--mk-z-sticky) + 1)}.mk-table__filter{position:relative;display:flex;align-items:center;width:100%}.mk-table__filter-control{width:100%;min-width:4rem;height:var(--mk-control-height-sm);padding:0 var(--mk-space-2);font:inherit;font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-normal);color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-sm);outline:none;transition:border-color var(--mk-duration-fast) var(--mk-ease-standard),box-shadow var(--mk-duration-fast) var(--mk-ease-standard)}.mk-table__filter-control::placeholder{color:var(--mk-text-subtle)}.mk-table__filter-control:hover{border-color:var(--mk-border-strong)}.mk-table__filter-control:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:0px;border-color:var(--mk-primary)}.mk-table__filter-control--active,.mk-table__filter--active .mk-table__filter-control{border-color:var(--mk-primary)}.mk-table__filter--active .mk-table__filter-input{padding-inline-end:calc(var(--mk-space-2) + 1.5rem)}.mk-table__filter-input::-webkit-search-cancel-button,.mk-table__filter-input::-webkit-search-decoration{-webkit-appearance:none;appearance:none}.mk-table__filter-select{cursor:pointer}.mk-table__filter-clear{position:absolute;inset-inline-end:var(--mk-space-1);top:50%;display:inline-grid;place-items:center;width:1.25rem;height:1.25rem;padding:0;transform:translateY(-50%);font-size:var(--mk-font-size-md);line-height:1;color:var(--mk-text-muted);background:transparent;border:0;border-radius:var(--mk-radius-sm);cursor:pointer}@media(pointer:coarse){.mk-table__filter-clear:before{content:\"\";position:absolute;inset:min(0px,(100% - 24px) / 2)}}.mk-table__filter-clear:hover{color:var(--mk-text);background-color:var(--mk-hover-overlay)}.mk-table__filter-clear:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:0px}.mk-table__spacer>td{padding:0;border:0}:host(.mk-table--virtual) .mk-table__row .mk-table__td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mk-table__td--editable{cursor:text}.mk-table__td--editable:hover{background-color:var(--mk-hover-overlay)}.mk-table__td--editable:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__cell-input{width:100%;margin:calc(-1 * var(--mk-space-1)) 0;padding:var(--mk-space-1) var(--mk-space-2);font:inherit;color:var(--mk-text);background-color:var(--mk-surface);border:var(--mk-border-width) solid var(--mk-primary);border-radius:var(--mk-radius-sm);outline:none}.mk-table__empty{padding:var(--mk-space-8) var(--_cell-pad-x);text-align:center;color:var(--mk-text-subtle)}.mk-table__td--tree{padding-inline-start:calc(var(--_cell-pad-x) + var(--mk-tree-depth, 0) * 1.25rem);white-space:nowrap}.mk-table__tree-toggle,.mk-table__tree-spacer{display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;margin-inline-end:var(--mk-space-1);vertical-align:middle;flex:none}.mk-table__tree-toggle{padding:0;border:0;border-radius:var(--mk-radius-sm);background:transparent;color:var(--mk-text-muted);cursor:pointer}.mk-table__tree-toggle:hover{color:var(--mk-text);background:var(--mk-hover-overlay)}.mk-table__tree-toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(var(--mk-focus-ring-offset) * -1)}.mk-table__td--tree .mk-table__cell-value{display:inline;vertical-align:middle}.mk-table__group{padding:0;background-color:var(--mk-surface-2);border-bottom:var(--mk-border-width) solid var(--mk-border-subtle);text-align:start}:host(.mk-table--grouped) .mk-table__group{position:sticky;top:var(--_group-top, 0px);z-index:calc(var(--mk-z-sticky) - 1)}.mk-table__group-toggle{display:flex;align-items:center;gap:var(--mk-space-2);width:100%;padding:var(--mk-space-2) var(--mk-space-3);border:none;background:none;color:var(--mk-text);font:inherit;font-weight:var(--mk-font-weight-semibold);cursor:pointer}.mk-table__group-toggle:hover{background-color:var(--mk-hover-overlay)}.mk-table__group-toggle:focus-visible{outline:var(--mk-focus-ring-width) solid var(--mk-focus-ring);outline-offset:calc(-1 * var(--mk-focus-ring-width))}.mk-table__group-count{padding:0 var(--mk-space-2);border-radius:var(--mk-radius-full);background-color:var(--mk-surface-3);color:var(--mk-text-muted);font-size:var(--mk-font-size-sm);font-weight:var(--mk-font-weight-regular)}:host(.mk-table--stacked) .mk-table__scroll{overflow:visible;max-height:none;border:none;border-radius:0;background:none}:host(.mk-table--stacked) .mk-table__table,:host(.mk-table--stacked) .mk-table__body{display:block;background:none}:host(.mk-table--stacked) .mk-table__head{display:none}:host(.mk-table--stacked) .mk-table__spacer{display:none}:host(.mk-table--stacked) .mk-table__row{display:grid;grid-template-columns:1fr;gap:var(--mk-space-1);padding:var(--mk-space-3);margin-bottom:var(--mk-space-3);border:var(--mk-border-width) solid var(--mk-border);border-radius:var(--mk-radius-lg);background-color:var(--mk-surface);box-shadow:var(--mk-shadow-xs)}:host(.mk-table--stacked) .mk-table__td{display:flex;align-items:baseline;justify-content:space-between;gap:var(--mk-space-3);padding:var(--mk-space-1) 0;border:none;text-align:start}:host(.mk-table--stacked) .mk-table__td--stack-title{justify-content:flex-start;padding-bottom:var(--mk-space-2);font-size:var(--mk-font-size-lg);font-weight:var(--mk-font-weight-semibold);color:var(--mk-text)}:host(.mk-table--stacked) .mk-table__td--stack-title~.mk-table__td--stack-title{justify-content:flex-end;margin-top:calc(-1 * var(--mk-space-2) - 1.5em);font-size:var(--mk-font-size-md)}:host(.mk-table--stacked) .mk-table__cell-label{flex:none;color:var(--mk-text-muted);font-size:var(--mk-font-size-sm)}:host(.mk-table--stacked) .mk-table__cell-value{min-width:0;text-align:end;overflow-wrap:anywhere}:host(.mk-table--stacked) .mk-table__td--stack-title .mk-table__cell-value,:host(.mk-table--stacked) .mk-table__td--stack-footer .mk-table__cell-value{text-align:start}:host(.mk-table--stacked) .mk-table__td--stack-footer{justify-content:flex-start;margin-top:var(--mk-space-2);padding-top:var(--mk-space-3);border-top:var(--mk-border-width) solid var(--mk-border-subtle)}:host(.mk-table--stacked) .mk-table__td--stack-footer .mk-table__cell-value{display:flex;flex-wrap:wrap;gap:var(--mk-space-2);width:100%}:host(.mk-table--stacked) .mk-table__td--select,:host(.mk-table--stacked) .mk-table__td--expand{width:auto;justify-content:flex-start}:host(.mk-table--stacked).mk-table--zebra .mk-table__body .mk-table__row:nth-child(2n) .mk-table__td{background:none}:host(.mk-table--stacked) .mk-table__detail-row,:host(.mk-table--stacked) .mk-table__detail{display:block;padding:0}:host(.mk-table--stacked) .mk-table__detail-row{margin:calc(-1 * var(--mk-space-3)) 0 var(--mk-space-3)}:host(.mk-table--stacked) .mk-table__group-row,:host(.mk-table--stacked) .mk-table__group{display:block;top:0}:host(.mk-table--stacked) .mk-table__row--empty,:host(.mk-table--stacked) .mk-table__empty{display:block;border:none;box-shadow:none;background:none}@media(pointer:coarse){.mk-table__cell-input{font-size:max(var(--mk-font-size-md),16px)}}@media print{.mk-table__scroll{overflow:visible;max-height:none;height:auto;border-radius:0}.mk-table__filter-row{display:none}.mk-table__head{display:table-header-group}.mk-table__th,.mk-table__td,.mk-table__filter-cell{position:static;box-shadow:none;background-color:transparent!important;color:inherit!important;border-bottom:1px solid var(--mk-border)}.mk-table__row,.mk-table__group-row,.mk-table__detail-row{break-inside:avoid}.mk-table__sort,.mk-table__resize,.mk-table__th--select,.mk-table__td--select,.mk-table__th--expand,.mk-table__td--expand,.mk-table__tree-toggle .mk-table__expander-icon{display:none}.mk-table__th-button,.mk-table__tree-toggle,.mk-table__group-toggle{color:inherit;cursor:default}}\n"] }]
1766
+ }], ctorParameters: () => [], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], stickyHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyHeader", required: false }] }], zebra: [{ type: i0.Input, args: [{ isSignal: true, alias: "zebra", required: false }] }], hover: [{ type: i0.Input, args: [{ isSignal: true, alias: "hover", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], stackAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackAt", required: false }] }], clickableRows: [{ type: i0.Input, args: [{ isSignal: true, alias: "clickableRows", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], trackKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackKey", required: false }] }], rowClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowClass", required: false }] }], expandable: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandable", required: false }] }], singleExpand: [{ type: i0.Input, args: [{ isSignal: true, alias: "singleExpand", required: false }] }], resizableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizableColumns", required: false }] }], reorderableColumns: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderableColumns", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], groupLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupLabel", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], filterable: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterable", required: false }] }], filters: [{ type: i0.Input, args: [{ isSignal: true, alias: "filters", required: false }] }, { type: i0.Output, args: ["filtersChange"] }], clientFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientFilter", required: false }] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], expandedChange: [{ type: i0.Output, args: ["expandedChange"] }], columnResize: [{ type: i0.Output, args: ["columnResize"] }], columnReorder: [{ type: i0.Output, args: ["columnReorder"] }], cellEdit: [{ type: i0.Output, args: ["cellEdit"] }], groupToggle: [{ type: i0.Output, args: ["groupToggle"] }], childrenKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "childrenKey", required: false }] }], treeToggle: [{ type: i0.Output, args: ["treeToggle"] }], editInput: [{ type: i0.ViewChild, args: ['editInput', { isSignal: true }] }], rowDetail: [{ type: i0.ContentChild, args: [i0.forwardRef(() => MkTableRowDetail), { isSignal: true }] }], cellTemplates: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => MkTableCell), { isSignal: true }] }], scroller: [{ type: i0.ViewChild, args: ['scroller', { isSignal: true }] }] } });
1160
1767
 
1161
1768
  /**
1162
1769
  * Sort coordinator — apply `mkSort` to a table (or any container) to track
@@ -1326,6 +1933,14 @@ function sameSort(a, b) {
1326
1933
  return false;
1327
1934
  return a.active === b.active && a.direction === b.direction;
1328
1935
  }
1936
+ /** JSON with object keys sorted, so `{ a, b }` and `{ b, a }` compare equal. */
1937
+ function canonicalJson(value) {
1938
+ return JSON.stringify(value, (_key, v) => v && typeof v === 'object' && !Array.isArray(v)
1939
+ ? Object.fromEntries(Object.keys(v)
1940
+ .sort()
1941
+ .map((k) => [k, v[k]]))
1942
+ : v);
1943
+ }
1329
1944
  /** Duck-typed Observable check, so rxjs is a type-only dependency here. */
1330
1945
  function isSubscribable(value) {
1331
1946
  return typeof value.subscribe === 'function';
@@ -1430,6 +2045,8 @@ class MkTableDataSource {
1430
2045
  ...(ngDevMode ? [{ debugName: "_filter" }] : /* istanbul ignore next */ []));
1431
2046
  _query = signal(null, /* @ts-ignore */
1432
2047
  ...(ngDevMode ? [{ debugName: "_query" }] : /* istanbul ignore next */ []));
2048
+ _filters = signal(null, /* @ts-ignore */
2049
+ ...(ngDevMode ? [{ debugName: "_filters" }] : /* istanbul ignore next */ []));
1433
2050
  /** Rows of the current page (`[]` until the first load lands). */
1434
2051
  rows = this._rows.asReadonly();
1435
2052
  /** Total row count across all pages (feed to `mk-pagination`'s `total`). */
@@ -1448,6 +2065,8 @@ class MkTableDataSource {
1448
2065
  filter = this._filter.asReadonly();
1449
2066
  /** Current structured query, or `null`. */
1450
2067
  query = this._query.asReadonly();
2068
+ /** Current per-column filters, or `null`. */
2069
+ filters = this._filters.asReadonly();
1451
2070
  /** True when a settled load reported no rows at all. */
1452
2071
  empty = computed(() => !this._loading() && this._total() === 0, /* @ts-ignore */
1453
2072
  ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
@@ -1539,6 +2158,22 @@ class MkTableDataSource {
1539
2158
  this._page.set(1);
1540
2159
  this.load();
1541
2160
  }
2161
+ /**
2162
+ * Set (or clear with `null` / `{}`) the per-column filters from `mk-table`'s
2163
+ * header filter row — bind `(filtersChange)="ds.setFilters($event)"` and
2164
+ * turn the table's `clientFilter` off. Empty entries are dropped before the
2165
+ * request; nothing left is sent as `null`. Resets to page 1 and loads at
2166
+ * once (the table already debounces nothing, so debounce the fetcher if
2167
+ * your API needs it). A no-op when unchanged.
2168
+ */
2169
+ setFilters(filters) {
2170
+ const next = mkCompactFilters(filters);
2171
+ if (canonicalJson(next) === canonicalJson(this._filters()))
2172
+ return;
2173
+ this._filters.set(next);
2174
+ this._page.set(1);
2175
+ this.load();
2176
+ }
1542
2177
  /**
1543
2178
  * Re-run the current request immediately (e.g. after a mutation). Flushes a
1544
2179
  * pending debounced filter, since the request reads the live filter value.
@@ -1586,6 +2221,7 @@ class MkTableDataSource {
1586
2221
  sort: this._sort(),
1587
2222
  filter: this._filter(),
1588
2223
  query: this._query(),
2224
+ filters: this._filters(),
1589
2225
  };
1590
2226
  let result;
1591
2227
  try {
@@ -1673,5 +2309,5 @@ class MkTableDataSource {
1673
2309
  * Generated bundle index. Do not edit.
1674
2310
  */
1675
2311
 
1676
- export { MkSort, MkSortHeader, MkTable, MkTableCell, MkTableDataSource, MkTableRowDetail, mkDownloadText, mkExportCsv, mkToCsv };
2312
+ export { MkSort, MkSortHeader, MkTable, MkTableCell, MkTableDataSource, MkTableRowDetail, mkCompactFilters, mkDownloadText, mkExportCsv, mkToCsv };
1677
2313
  //# sourceMappingURL=mk-kit-ui-table.mjs.map