@mk-kit/ui 0.42.0 → 0.44.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.
Files changed (57) hide show
  1. package/README.md +5 -3
  2. package/THIRD_PARTY_NOTICES.md +4 -2
  3. package/fesm2022/mk-kit-ui-block-editor.mjs +4 -2
  4. package/fesm2022/mk-kit-ui-block-editor.mjs.map +1 -1
  5. package/fesm2022/mk-kit-ui-checkbox.mjs +4 -2
  6. package/fesm2022/mk-kit-ui-checkbox.mjs.map +1 -1
  7. package/fesm2022/mk-kit-ui-core.mjs +136 -1
  8. package/fesm2022/mk-kit-ui-core.mjs.map +1 -1
  9. package/fesm2022/mk-kit-ui-data.mjs +438 -8
  10. package/fesm2022/mk-kit-ui-data.mjs.map +1 -1
  11. package/fesm2022/mk-kit-ui-datetime.mjs +88 -50
  12. package/fesm2022/mk-kit-ui-datetime.mjs.map +1 -1
  13. package/fesm2022/mk-kit-ui-directives.mjs +27 -8
  14. package/fesm2022/mk-kit-ui-directives.mjs.map +1 -1
  15. package/fesm2022/mk-kit-ui-dnd.mjs +43 -8
  16. package/fesm2022/mk-kit-ui-dnd.mjs.map +1 -1
  17. package/fesm2022/mk-kit-ui-dynamic-form.mjs +149 -2
  18. package/fesm2022/mk-kit-ui-dynamic-form.mjs.map +1 -1
  19. package/fesm2022/mk-kit-ui-feedback.mjs +1 -1
  20. package/fesm2022/mk-kit-ui-feedback.mjs.map +1 -1
  21. package/fesm2022/mk-kit-ui-forms.mjs +326 -78
  22. package/fesm2022/mk-kit-ui-forms.mjs.map +1 -1
  23. package/fesm2022/mk-kit-ui-icon-extended.mjs +425 -0
  24. package/fesm2022/mk-kit-ui-icon-extended.mjs.map +1 -0
  25. package/fesm2022/mk-kit-ui-icon.mjs +296 -459
  26. package/fesm2022/mk-kit-ui-icon.mjs.map +1 -1
  27. package/fesm2022/mk-kit-ui-media.mjs +1 -1
  28. package/fesm2022/mk-kit-ui-media.mjs.map +1 -1
  29. package/fesm2022/mk-kit-ui-query-builder.mjs +1 -1
  30. package/fesm2022/mk-kit-ui-query-builder.mjs.map +1 -1
  31. package/fesm2022/mk-kit-ui-rich-text.mjs +4 -2
  32. package/fesm2022/mk-kit-ui-rich-text.mjs.map +1 -1
  33. package/fesm2022/mk-kit-ui-table.mjs +668 -24
  34. package/fesm2022/mk-kit-ui-table.mjs.map +1 -1
  35. package/fesm2022/mk-kit-ui-testing.mjs +54 -0
  36. package/fesm2022/mk-kit-ui-testing.mjs.map +1 -1
  37. package/fesm2022/mk-kit-ui.mjs +1 -0
  38. package/fesm2022/mk-kit-ui.mjs.map +1 -1
  39. package/package.json +5 -1
  40. package/schematics/migrate-primeng/transform.js +4 -1
  41. package/schematics/ng-add/index.js +30 -2
  42. package/schematics/ng-add/schema.json +6 -0
  43. package/types/mk-kit-ui-block-editor.d.ts +2 -0
  44. package/types/mk-kit-ui-checkbox.d.ts +2 -0
  45. package/types/mk-kit-ui-core.d.ts +82 -2
  46. package/types/mk-kit-ui-data.d.ts +203 -2
  47. package/types/mk-kit-ui-datetime.d.ts +41 -19
  48. package/types/mk-kit-ui-directives.d.ts +12 -6
  49. package/types/mk-kit-ui-dnd.d.ts +32 -4
  50. package/types/mk-kit-ui-dynamic-form.d.ts +44 -1
  51. package/types/mk-kit-ui-forms.d.ts +154 -16
  52. package/types/mk-kit-ui-icon-extended.d.ts +83 -0
  53. package/types/mk-kit-ui-icon.d.ts +100 -17
  54. package/types/mk-kit-ui-rich-text.d.ts +2 -0
  55. package/types/mk-kit-ui-table.d.ts +234 -25
  56. package/types/mk-kit-ui-testing.d.ts +16 -0
  57. package/types/mk-kit-ui.d.ts +1 -0
@@ -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
@@ -189,6 +238,15 @@ const MAX_COL_WIDTH = 2000;
189
238
  * (rowClick)="open($event)" />
190
239
  * ```
191
240
  */
241
+ /**
242
+ * Shared collator for string sorting. Created lazily at module scope rather
243
+ * than as a `static` class field: a static initializer that *calls* something
244
+ * stops Angular's build optimizer from marking the class IIFE as pure, which
245
+ * pinned `MkTable` — and, through it, the whole table entry point — into every
246
+ * bundle that imported any export of `@mk-kit/ui/table`.
247
+ */
248
+ let collator;
249
+ const sortCollator = () => (collator ??= new Intl.Collator());
192
250
  class MkTable {
193
251
  announcer = inject(MkLiveAnnouncer);
194
252
  i18n = inject(MK_I18N);
@@ -208,13 +266,45 @@ class MkTable {
208
266
  stacked = signal(false, /* @ts-ignore */
209
267
  ...(ngDevMode ? [{ debugName: "stacked" }] : /* istanbul ignore next */ []));
210
268
  constructor() {
211
- // Keep the sticky group-header offset in sync with the rendered thead
212
- // 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.
213
272
  effect(() => {
214
273
  this.stickyHeader();
215
274
  this.density();
216
275
  this.groupBy();
217
- 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
+ });
218
308
  });
219
309
  // Watch the host's width against `stackAt`. ResizeObserver rather than
220
310
  // matchMedia because the trigger is the element's width, not the window's.
@@ -231,17 +321,43 @@ class MkTable {
231
321
  this.stacked.set(limit > 0 && entry.contentRect.width < limit);
232
322
  });
233
323
  observer.observe(el);
234
- 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
+ });
235
341
  },
236
342
  }, { injector: this.injector });
237
343
  }
238
- /** Measures the thead and exposes it as the group rows' sticky offset. */
239
- 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
+ }
240
356
  if (this.groupBy() == null)
241
357
  return;
242
- const thead = this.host.nativeElement.querySelector('thead');
243
- const h = this.stickyHeader() && thead ? thead.getBoundingClientRect().height : 0;
244
- 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`);
245
361
  }
246
362
  /** Column definitions (order = display order). */
247
363
  columns = input.required(/* @ts-ignore */
@@ -325,6 +441,55 @@ class MkTable {
325
441
  /** Formats a group header label; defaults to `String(value)`. */
326
442
  groupLabel = input(null, /* @ts-ignore */
327
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 });
328
493
  /** Emitted when the sort column/direction changes. */
329
494
  sortChange = output();
330
495
  /** Emitted when a row is clicked (enable via `clickableRows`). */
@@ -718,10 +883,227 @@ class MkTable {
718
883
  * re-resolves locale data on every call; one cached `Intl.Collator` makes
719
884
  * large-table sorts several-fold faster with the same default-locale order.
720
885
  */
721
- static sortCollator = new Intl.Collator();
722
886
  /** Data sorted by the active column, or the input order when unsorted. */
723
- sortedData = computed(() => this.sortRows(this.data()), /* @ts-ignore */
887
+ sortedData = computed(() => this.sortRows(this.filteredData()), /* @ts-ignore */
724
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 */ []));
725
1107
  /** Sort one sibling group by the active column (input order when unsorted). */
726
1108
  sortRows(rows) {
727
1109
  const key = this.sortKey();
@@ -739,7 +1121,7 @@ class MkTable {
739
1121
  return 1;
740
1122
  if (typeof av === 'number' && typeof bv === 'number')
741
1123
  return av - bv;
742
- return MkTable.sortCollator.compare(String(av), String(bv));
1124
+ return sortCollator().compare(String(av), String(bv));
743
1125
  };
744
1126
  // Negate the comparator for desc (instead of reversing) so the sort stays
745
1127
  // stable and null ordering is consistent in both directions.
@@ -842,10 +1224,10 @@ class MkTable {
842
1224
  const walk = (rows) => {
843
1225
  for (const row of this.sortRows(rows)) {
844
1226
  out.push(row);
845
- walk(this.childrenOf(row));
1227
+ walk(this.visibleChildrenOf(row));
846
1228
  }
847
1229
  };
848
- walk(this.data());
1230
+ walk(this.filteredData());
849
1231
  return out;
850
1232
  }, /* @ts-ignore */
851
1233
  ...(ngDevMode ? [{ debugName: "allRows" }] : /* istanbul ignore next */ []));
@@ -949,7 +1331,7 @@ class MkTable {
949
1331
  const tree = !!this.childrenKey();
950
1332
  const expandedKeys = this.treeExpanded();
951
1333
  for (const row of rows) {
952
- const children = tree ? this.childrenOf(row) : [];
1334
+ const children = tree ? this.visibleChildrenOf(row) : [];
953
1335
  const hasChildren = children.length > 0;
954
1336
  const expanded = hasChildren && expandedKeys.has(this.rowKey(row));
955
1337
  items.push({ kind: 'row', row, depth, hasChildren, expanded });
@@ -965,6 +1347,236 @@ class MkTable {
965
1347
  const value = row[key];
966
1348
  return Array.isArray(value) ? value : [];
967
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
+ }
968
1580
  // --- Tree rows ------------------------------------------------------------
969
1581
  /** Keys of parent rows whose children are shown. */
970
1582
  treeExpanded = signal(new Set(), /* @ts-ignore */
@@ -1003,7 +1615,8 @@ class MkTable {
1003
1615
  /**
1004
1616
  * The rows and columns an export writes — exactly what {@link exportCsv}
1005
1617
  * serialises, for other formats (XLSX, PDF, the clipboard, …): rows in
1006
- * 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
1007
1620
  * (`childrenKey`) flattened under their parent whether or not they are
1008
1621
  * expanded, optionally only the selected ones; columns in the table's
1009
1622
  * current order, restricted to `options.columns` when given, each carrying
@@ -1030,8 +1643,8 @@ class MkTable {
1030
1643
  }
1031
1644
  /**
1032
1645
  * The table's rows as CSV: current column order, column formatters applied,
1033
- * sorted the way they are shown, tree children flattened under their parent
1034
- * 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
1035
1648
  * `table.csv`) and returns the text. Built on {@link getExportRows}.
1036
1649
  */
1037
1650
  exportCsv(options = {}) {
@@ -1132,13 +1745,15 @@ class MkTable {
1132
1745
  this.expandedChange.emit(this.data().filter((r) => next.has(this.rowKey(r))));
1133
1746
  }
1134
1747
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
1135
- 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 });
1136
1749
  }
1137
1750
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTable, decorators: [{
1138
1751
  type: Component,
1139
1752
  args: [{ selector: 'mk-table', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MkCheckbox, NgTemplateOutlet], host: {
1140
1753
  class: 'mk-table',
1141
- '[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()',
1142
1757
  '[class.mk-table--zebra]': 'zebra()',
1143
1758
  '[class.mk-table--hover]': 'hover()',
1144
1759
  '[class.mk-table--compact]': "density() === 'compact'",
@@ -1147,8 +1762,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
1147
1762
  '[class.mk-table--expandable]': 'expandable()',
1148
1763
  '[class.mk-table--grouped]': 'groupBy() !== null',
1149
1764
  '[class.mk-table--stacked]': 'stacked()',
1150
- }, 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"] }]
1151
- }], 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 }] }] } });
1152
1767
 
1153
1768
  /**
1154
1769
  * Sort coordinator — apply `mkSort` to a table (or any container) to track
@@ -1318,6 +1933,14 @@ function sameSort(a, b) {
1318
1933
  return false;
1319
1934
  return a.active === b.active && a.direction === b.direction;
1320
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
+ }
1321
1944
  /** Duck-typed Observable check, so rxjs is a type-only dependency here. */
1322
1945
  function isSubscribable(value) {
1323
1946
  return typeof value.subscribe === 'function';
@@ -1422,6 +2045,8 @@ class MkTableDataSource {
1422
2045
  ...(ngDevMode ? [{ debugName: "_filter" }] : /* istanbul ignore next */ []));
1423
2046
  _query = signal(null, /* @ts-ignore */
1424
2047
  ...(ngDevMode ? [{ debugName: "_query" }] : /* istanbul ignore next */ []));
2048
+ _filters = signal(null, /* @ts-ignore */
2049
+ ...(ngDevMode ? [{ debugName: "_filters" }] : /* istanbul ignore next */ []));
1425
2050
  /** Rows of the current page (`[]` until the first load lands). */
1426
2051
  rows = this._rows.asReadonly();
1427
2052
  /** Total row count across all pages (feed to `mk-pagination`'s `total`). */
@@ -1440,6 +2065,8 @@ class MkTableDataSource {
1440
2065
  filter = this._filter.asReadonly();
1441
2066
  /** Current structured query, or `null`. */
1442
2067
  query = this._query.asReadonly();
2068
+ /** Current per-column filters, or `null`. */
2069
+ filters = this._filters.asReadonly();
1443
2070
  /** True when a settled load reported no rows at all. */
1444
2071
  empty = computed(() => !this._loading() && this._total() === 0, /* @ts-ignore */
1445
2072
  ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
@@ -1531,6 +2158,22 @@ class MkTableDataSource {
1531
2158
  this._page.set(1);
1532
2159
  this.load();
1533
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
+ }
1534
2177
  /**
1535
2178
  * Re-run the current request immediately (e.g. after a mutation). Flushes a
1536
2179
  * pending debounced filter, since the request reads the live filter value.
@@ -1578,6 +2221,7 @@ class MkTableDataSource {
1578
2221
  sort: this._sort(),
1579
2222
  filter: this._filter(),
1580
2223
  query: this._query(),
2224
+ filters: this._filters(),
1581
2225
  };
1582
2226
  let result;
1583
2227
  try {
@@ -1665,5 +2309,5 @@ class MkTableDataSource {
1665
2309
  * Generated bundle index. Do not edit.
1666
2310
  */
1667
2311
 
1668
- export { MkSort, MkSortHeader, MkTable, MkTableCell, MkTableDataSource, MkTableRowDetail, mkDownloadText, mkExportCsv, mkToCsv };
2312
+ export { MkSort, MkSortHeader, MkTable, MkTableCell, MkTableDataSource, MkTableRowDetail, mkCompactFilters, mkDownloadText, mkExportCsv, mkToCsv };
1669
2313
  //# sourceMappingURL=mk-kit-ui-table.mjs.map