@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.
- package/README.md +5 -3
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/fesm2022/mk-kit-ui-block-editor.mjs +4 -2
- package/fesm2022/mk-kit-ui-block-editor.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-checkbox.mjs +4 -2
- package/fesm2022/mk-kit-ui-checkbox.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-core.mjs +136 -1
- package/fesm2022/mk-kit-ui-core.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-data.mjs +438 -8
- package/fesm2022/mk-kit-ui-data.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-datetime.mjs +88 -50
- package/fesm2022/mk-kit-ui-datetime.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-directives.mjs +27 -8
- package/fesm2022/mk-kit-ui-directives.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-dnd.mjs +43 -8
- package/fesm2022/mk-kit-ui-dnd.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-dynamic-form.mjs +149 -2
- package/fesm2022/mk-kit-ui-dynamic-form.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-feedback.mjs +1 -1
- package/fesm2022/mk-kit-ui-feedback.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-forms.mjs +326 -78
- package/fesm2022/mk-kit-ui-forms.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-icon-extended.mjs +425 -0
- package/fesm2022/mk-kit-ui-icon-extended.mjs.map +1 -0
- package/fesm2022/mk-kit-ui-icon.mjs +296 -459
- package/fesm2022/mk-kit-ui-icon.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-media.mjs +1 -1
- package/fesm2022/mk-kit-ui-media.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-query-builder.mjs +1 -1
- package/fesm2022/mk-kit-ui-query-builder.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-rich-text.mjs +4 -2
- package/fesm2022/mk-kit-ui-rich-text.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-table.mjs +668 -24
- package/fesm2022/mk-kit-ui-table.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-testing.mjs +54 -0
- package/fesm2022/mk-kit-ui-testing.mjs.map +1 -1
- package/fesm2022/mk-kit-ui.mjs +1 -0
- package/fesm2022/mk-kit-ui.mjs.map +1 -1
- package/package.json +5 -1
- package/schematics/migrate-primeng/transform.js +4 -1
- package/schematics/ng-add/index.js +30 -2
- package/schematics/ng-add/schema.json +6 -0
- package/types/mk-kit-ui-block-editor.d.ts +2 -0
- package/types/mk-kit-ui-checkbox.d.ts +2 -0
- package/types/mk-kit-ui-core.d.ts +82 -2
- package/types/mk-kit-ui-data.d.ts +203 -2
- package/types/mk-kit-ui-datetime.d.ts +41 -19
- package/types/mk-kit-ui-directives.d.ts +12 -6
- package/types/mk-kit-ui-dnd.d.ts +32 -4
- package/types/mk-kit-ui-dynamic-form.d.ts +44 -1
- package/types/mk-kit-ui-forms.d.ts +154 -16
- package/types/mk-kit-ui-icon-extended.d.ts +83 -0
- package/types/mk-kit-ui-icon.d.ts +100 -17
- package/types/mk-kit-ui-rich-text.d.ts +2 -0
- package/types/mk-kit-ui-table.d.ts +234 -25
- package/types/mk-kit-ui-testing.d.ts +16 -0
- 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
|
|
212
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
239
|
-
|
|
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 =
|
|
243
|
-
const h =
|
|
244
|
-
|
|
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.
|
|
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
|
|
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.
|
|
1227
|
+
walk(this.visibleChildrenOf(row));
|
|
846
1228
|
}
|
|
847
1229
|
};
|
|
848
|
-
walk(this.
|
|
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.
|
|
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
|
|
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
|
|
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
|