@scion/components 22.3.0 → 22.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,15 @@
1
1
  import * as i0 from '@angular/core';
2
- import { isSignal, InjectionToken, makeEnvironmentProviders, linkedSignal, computed, signal, inject, Injector, runInInjectionContext, untracked, NgZone, resource, effect, assertNotInReactiveContext, assertInInjectionContext, DestroyRef, Injectable, input, Component, output, viewChild, Directive, afterNextRender, ElementRef, Pipe, debounced, viewChildren, ViewEncapsulation } from '@angular/core';
3
- import { coerceSignal, createDestroyableInjector, toLazyObservable, SciComponentOutletDirective, SciAttributesDirective } from '@scion/components/common';
2
+ import { computed, inject, DEFAULT_CURRENCY_CODE, signal, InjectionToken, makeEnvironmentProviders, Injector, DestroyRef, runInInjectionContext, onIdle, Injectable, untracked, linkedSignal, isSignal, NgZone, effect, resource, assertNotInReactiveContext, assertInInjectionContext, Pipe, input, Component, output, viewChild, Directive, afterNextRender, ElementRef, debounced, viewChildren, ViewEncapsulation } from '@angular/core';
3
+ import { coerceSignal, SCI_LOCALE, createDestroyableInjector, toLazyObservable, SciComponentOutletDirective, SciAttributesDirective } from '@scion/components/common';
4
+ import { formatDate, DATE_PIPE_DEFAULT_OPTIONS, NgTemplateOutlet } from '@angular/common';
4
5
  import { toObservable, rxResource, takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
- import { map, shareReplay, fromEvent, switchMap, concat, of, timer, firstValueFrom, defaultIfEmpty, throwError, skip } from 'rxjs';
6
6
  import { Objects, clamp, runSafe, Observables, Arrays } from '@scion/toolkit/util';
7
- import { map as map$1, startWith, first } from 'rxjs/operators';
7
+ import { LatestTaskExecutor } from '@scion/toolkit/common';
8
+ import { fromEvent, switchMap, concat, of, timer, firstValueFrom, defaultIfEmpty, throwError, combineLatestWith, tap, skip } from 'rxjs';
9
+ import { map, startWith, first } from 'rxjs/operators';
8
10
  import { subscribeIn } from '@scion/toolkit/operators';
9
11
  import { SciScrollbarComponent } from '@scion/components/viewport';
10
12
  import { dimension } from '@scion/components/dimension';
11
- import { NgTemplateOutlet } from '@angular/common';
12
13
  import { SciIconComponent } from '@scion/components/icon';
13
14
  import { SciToolbarComponent, contributeMenu, ɵSciMenuService as _SciMenuService } from '@scion/components/menu';
14
15
  import { UUID } from '@scion/toolkit/uuid';
@@ -18,6 +19,65 @@ import { SciThrobberComponent } from '@scion/components/throbber';
18
19
  import * as i1 from '@angular/forms';
19
20
  import { FormsModule } from '@angular/forms';
20
21
 
22
+ /**
23
+ * Creates a {@link SciDateValue} from the given date-like input.
24
+ *
25
+ * Calling this function is expensive due to date parsing and formatting.
26
+ */
27
+ function coerceDateValue(dateLike, options) {
28
+ const { format, locale, timezone } = options;
29
+ switch (typeof dateLike) {
30
+ case 'string': {
31
+ const isoString = ensureIso8601DatePrefix(dateLike);
32
+ return {
33
+ isoString,
34
+ millis: new Date(isoString).getTime(),
35
+ formatted: formatDate(isoString, format, locale, timezone),
36
+ };
37
+ }
38
+ case 'number': {
39
+ const millis = dateLike;
40
+ const isoString = new Date(millis).toISOString();
41
+ return {
42
+ isoString,
43
+ millis,
44
+ formatted: formatDate(isoString, format, locale, timezone),
45
+ };
46
+ }
47
+ case 'object': {
48
+ const date = dateLike;
49
+ const isoString = date.toISOString();
50
+ return {
51
+ isoString,
52
+ millis: date.getTime(),
53
+ formatted: formatDate(isoString, format, locale, timezone),
54
+ };
55
+ }
56
+ case 'undefined': {
57
+ return undefined;
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * Prepends '1970-01-01' if the date prefix is missing, as required by {@link formatDate}.
63
+ *
64
+ * Uses fast ASCII character code checks (`-`, `T`, `t`) to avoid string allocation.
65
+ */
66
+ function ensureIso8601DatePrefix(isoDate) {
67
+ // Check if iso string already starts with a full year date (YYYY-MM-DD)
68
+ if (isoDate.charCodeAt(4) === 45) { // '-'
69
+ return isoDate;
70
+ }
71
+ // Prepend epoch, adding 'T' separator if missing.
72
+ switch (isoDate.charCodeAt(0)) {
73
+ case 84: // 'T'
74
+ case 116: // 't'
75
+ return '1970-01-01' + isoDate;
76
+ default:
77
+ return '1970-01-01T' + isoDate;
78
+ }
79
+ }
80
+
21
81
  /*
22
82
  * Copyright (c) 2018-2026 Swiss Federal Railways
23
83
  *
@@ -28,54 +88,163 @@ import { FormsModule } from '@angular/forms';
28
88
  * SPDX-License-Identifier: EPL-2.0
29
89
  */
30
90
  class ɵSciTableColumnFactory {
31
- _descriptor;
91
+ _table;
32
92
  columns = new Array();
33
- constructor(_descriptor) {
34
- this._descriptor = _descriptor;
93
+ constructor(_table) {
94
+ this._table = _table;
95
+ }
96
+ addStringColumn(descriptorLike, value) {
97
+ const descriptor = coerceColumnDescriptor(descriptorLike, value);
98
+ const column = this.mapToColumn(descriptor);
99
+ const sortable = descriptor.sortable ?? true;
100
+ const filterable = descriptor.filterable ?? true;
101
+ this.columns.push({
102
+ ...column,
103
+ type: 'string',
104
+ value: item => coerceSignal(descriptor.value(item), { coerceUndefined: true }),
105
+ sortable: computed(() => this._table.sortable() && !!sortable),
106
+ filterable: computed(() => this._table.filterable() && !!filterable),
107
+ compare: typeof sortable === 'object' ? sortable.comparator : (a, b) => a.value.localeCompare(b.value),
108
+ matches: typeof filterable === 'object' ? filterable.matcher : (text, context) => context.value.toLowerCase().includes(text.toLowerCase()),
109
+ });
110
+ return this;
35
111
  }
36
- addBooleanColumn(valueHeaderDescriptor, value) {
37
- return this.addColumn('boolean', valueHeaderDescriptor, value);
112
+ addNumberColumn(descriptorLike, value) {
113
+ const descriptor = coerceColumnDescriptor(descriptorLike, value);
114
+ const column = this.mapToColumn(descriptor);
115
+ const locale = coerceSignal(descriptor.locale ?? inject(SCI_LOCALE));
116
+ const currency = inject(DEFAULT_CURRENCY_CODE, { optional: true }) ?? 'USD';
117
+ this.columns.push({
118
+ ...column,
119
+ type: 'number',
120
+ value: item => coerceSignal(descriptor.value(item), { coerceUndefined: true }),
121
+ sortable: computed(() => this._table.sortable() && (descriptor.sortable ?? true)),
122
+ filterable: computed(() => this._table.filterable() && (descriptor.filterable ?? true)),
123
+ compare: (a, b) => a.value - b.value,
124
+ matches: (text, context) => context.value === text,
125
+ format: computed(() => new Intl.NumberFormat(locale(), { currency, ...descriptor.format })),
126
+ });
127
+ return this;
38
128
  }
39
- addStringColumn(valueHeaderDescriptor, value) {
40
- return this.addColumn('string', valueHeaderDescriptor, value);
129
+ addBooleanColumn(descriptorLike, value) {
130
+ const descriptor = coerceColumnDescriptor(descriptorLike, value);
131
+ const column = this.mapToColumn(descriptor);
132
+ this.columns.push({
133
+ ...column,
134
+ type: 'boolean',
135
+ value: item => coerceSignal(descriptor.value(item), { coerceUndefined: true }),
136
+ sortable: computed(() => this._table.sortable() && (descriptor.sortable ?? true)),
137
+ filterable: computed(() => this._table.filterable() && (descriptor.filterable ?? true)),
138
+ compare: (a, b) => a.value === b.value ? 0 : (a.value ? 1 : -1),
139
+ matches: (text, context) => context.value === text,
140
+ });
141
+ return this;
41
142
  }
42
- addNumberColumn(valueHeaderDescriptor, value) {
43
- return this.addColumn('number', valueHeaderDescriptor, value);
143
+ addDateColumn(descriptorLike, value) {
144
+ const descriptor = coerceColumnDescriptor(descriptorLike, value);
145
+ const column = this.mapToColumn(descriptor);
146
+ const defaults = inject(DATE_PIPE_DEFAULT_OPTIONS, { optional: true });
147
+ const locale = coerceSignal(descriptor.locale ?? inject(SCI_LOCALE));
148
+ const format = descriptor.format ?? defaults?.dateFormat ?? 'mediumDate';
149
+ const timezone = descriptor.timezone ?? defaults?.timezone;
150
+ this.columns.push({
151
+ ...column,
152
+ type: 'date',
153
+ value: item => {
154
+ const dateLike = coerceSignal(descriptor.value(item), { coerceUndefined: true });
155
+ return computed(() => coerceDateValue(dateLike(), { locale: locale(), format, timezone }));
156
+ },
157
+ sortable: computed(() => this._table.sortable() && (descriptor.sortable ?? true)),
158
+ filterable: computed(() => this._table.filterable() && (descriptor.filterable ?? true)),
159
+ compare: (a, b) => a.value.millis - b.value.millis,
160
+ matches: (text, context) => context.value.formatted.toLowerCase().includes(text.toLowerCase()),
161
+ });
162
+ return this;
44
163
  }
45
- addComponentColumn(config) {
46
- // TODO [dwi] Normalize filterable and sortable and add default matcher/comparator to all columns
47
- if (isSignal(this._descriptor.ɵdatasource) && (config.filterable === true || config.sortable === true)) {
48
- throw Error('[ColumnDefinitionError] Component columns cannot have a auto filter or auto sort.');
49
- }
50
- return this.addColumn('component', config);
164
+ addComponentColumn(descriptor) {
165
+ // Require a filter matcher if filterable on an array datasource.
166
+ if (this._table.isArrayDatasource() && descriptor.filterable === true) {
167
+ throw Error('[ColumnDefinitionError] Component column requires a filter matcher in order to be filterable.');
168
+ }
169
+ // Require a sort comparator if sortable on an array datasource.
170
+ if (this._table.isArrayDatasource() && descriptor.sortable === true) {
171
+ throw Error('[ColumnDefinitionError] Component column requires a sort comparator in order to be sortable.');
172
+ }
173
+ const column = this.mapToColumn(descriptor);
174
+ this.columns.push({
175
+ ...column,
176
+ type: 'component',
177
+ component: descriptor.component,
178
+ sortable: computed(() => this._table.sortable() && !!descriptor.sortable),
179
+ filterable: computed(() => this._table.filterable() && !!descriptor.filterable),
180
+ compare: typeof descriptor.sortable === 'object' ? descriptor.sortable.comparator : () => 0,
181
+ matches: typeof descriptor.filterable === 'object' ? descriptor.filterable.matcher : () => true,
182
+ padding: descriptor.padding ?? true,
183
+ });
184
+ return this;
51
185
  }
52
- addTemplateColumn(config) {
53
- if (isSignal(this._descriptor.ɵdatasource) && (config.filterable === true || config.sortable === true)) {
54
- throw Error('[ColumnDefinitionError] Template columns cannot have a auto filter or auto sort.');
55
- }
56
- return this.addColumn('template', config);
186
+ addTemplateColumn(descriptor) {
187
+ // Require a filter matcher if filterable on an array datasource.
188
+ if (this._table.isArrayDatasource() && descriptor.filterable === true) {
189
+ throw Error('[ColumnDefinitionError] Template column requires a filter matcher in order to be filterable.');
190
+ }
191
+ // Require a sort comparator if sortable on an array datasource.
192
+ if (this._table.isArrayDatasource() && descriptor.sortable === true) {
193
+ throw Error('[ColumnDefinitionError] Template column requires a sort comparator in order to be sortable.');
194
+ }
195
+ const column = this.mapToColumn(descriptor);
196
+ this.columns.push({
197
+ ...column,
198
+ type: 'template',
199
+ template: descriptor.template,
200
+ sortable: computed(() => this._table.sortable() && !!descriptor.sortable),
201
+ filterable: computed(() => this._table.filterable() && !!descriptor.filterable),
202
+ compare: typeof descriptor.sortable === 'object' ? descriptor.sortable.comparator : () => 0,
203
+ matches: typeof descriptor.filterable === 'object' ? descriptor.filterable.matcher : () => true,
204
+ padding: descriptor.padding ?? true,
205
+ });
206
+ return this;
57
207
  }
58
- addColumn(type, valueHeaderDescriptor, value) {
59
- const config = (() => {
60
- switch (typeof valueHeaderDescriptor) {
61
- case 'string':
62
- return { header: valueHeaderDescriptor, value: value };
63
- case 'function':
64
- return { value: valueHeaderDescriptor };
65
- default:
66
- return valueHeaderDescriptor;
67
- }
68
- })();
69
- if (!isSignal(this._descriptor.ɵdatasource) && (typeof config.sortable === 'object' || typeof config.filterable === 'object')) {
70
- throw Error('[ColumnDefinitionError] Data sources with a loader function cannot define a custom sort or filter function. Sorting and filtering have to be done within the loader function.');
71
- }
72
- // Fallback to the column index as the column name.
73
- const name = config.name ?? `column:${this.columns.length}`;
208
+ /**
209
+ * Maps given column descriptor to a {@link SciTableColumn}.
210
+ */
211
+ mapToColumn(descriptor) {
212
+ // Require a unique column name.
213
+ const name = descriptor.name ?? `column:${this.columns.length}`;
74
214
  if (this.columns.find(column => column.name === name)) {
75
- throw Error(`[ColumnDefinitionError] Column names have to be unique. "${name}" is defined more than once.`);
215
+ throw Error(`[ColumnDefinitionError] Column names must be unique. "${name}" is defined more than once.`);
76
216
  }
77
- this.columns.push({ ...config, name, type });
78
- return this;
217
+ // Disallow a filter matcher if using a custom datasource.
218
+ if (!this._table.isArrayDatasource() && typeof descriptor.filterable === 'object') {
219
+ throw Error('[ColumnDefinitionError] Configuring a filter matcher is not supported for tables using a datasource. Filtering must be done by the datasource.');
220
+ }
221
+ // Disallow a sort comparator if using a custom datasource.
222
+ if (!this._table.isArrayDatasource() && typeof descriptor.sortable === 'object') {
223
+ throw Error('[ColumnDefinitionError] Configuring a sort comparator is not supported for tables using a datasource. Sorting must be done by the datasource.');
224
+ }
225
+ return {
226
+ name: name,
227
+ header: coerceSignal(descriptor.header ?? ''),
228
+ resizable: computed(() => this._table.resizable() && (descriptor.resizable ?? true)),
229
+ width: computed(() => {
230
+ const userSettings = this._table.userSettings().columns?.find(column => column.name === name);
231
+ return userSettings?.width ? `${userSettings.width}px` : descriptor.width ?? '1fr';
232
+ }),
233
+ minWidth: descriptor.minWidth ?? 100,
234
+ resizing: signal(false),
235
+ padding: true,
236
+ location: { x: 0, width: 0 }, // set in `SciTableColumnComponent`
237
+ };
238
+ }
239
+ }
240
+ function coerceColumnDescriptor(argument1, argument2) {
241
+ switch (typeof argument1) {
242
+ case 'string':
243
+ return { header: argument1, value: argument2 };
244
+ case 'function':
245
+ return { value: argument1 };
246
+ default:
247
+ return argument1;
79
248
  }
80
249
  }
81
250
 
@@ -122,6 +291,71 @@ function provideTableStorage(storage) {
122
291
  ]);
123
292
  }
124
293
 
294
+ /** @inheritDoc */
295
+ class ɵSciTableCellValuePreloader {
296
+ _executor = new LatestTaskExecutor();
297
+ _queue = new Array(); // PERF: Do not change to signal to avoid array re-allocation on enqueue (expensive); queue size is tracked via a separate signal.
298
+ _queueSize = signal(0, /* @ts-ignore */
299
+ ...(ngDevMode ? [{ debugName: "_queueSize" }] : /* istanbul ignore next */ []));
300
+ _injector = inject(Injector);
301
+ /** @inheritDoc */
302
+ loading = computed(() => this._queueSize() > 0, /* @ts-ignore */
303
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
304
+ constructor() {
305
+ inject(DestroyRef).onDestroy(() => {
306
+ this._executor.destroy();
307
+ this._queue.length = 0;
308
+ this._queueSize.set(0);
309
+ });
310
+ }
311
+ /** @inheritDoc */
312
+ queue(cellValueFn) {
313
+ this._queue.push(cellValueFn);
314
+ this._queueSize.update(size => size + 1);
315
+ this._executor.submit(() => this.whenIdle().then(() => this.preloadQueuedBatch(PRELOAD_BATCH_SIZE)));
316
+ }
317
+ /**
318
+ * Preloads the next batch of queued cell values, FIFO, then, if the queue is not empty, schedules the next batch.
319
+ */
320
+ async preloadQueuedBatch(batchSize) {
321
+ this._queue.splice(0, batchSize).forEach(fn => fn());
322
+ this._queueSize.set(this._queue.length);
323
+ // Preload next batch when idle again.
324
+ if (this._queue.length) {
325
+ this._executor.submit(() => this.whenIdle().then(() => this.preloadQueuedBatch(PRELOAD_BATCH_SIZE)));
326
+ }
327
+ }
328
+ /**
329
+ * Resolves when the browser becomes idle.
330
+ *
331
+ * @see onIdle
332
+ */
333
+ whenIdle() {
334
+ return runInInjectionContext(this._injector, onIdle);
335
+ }
336
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciTableCellValuePreloader, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
337
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciTableCellValuePreloader });
338
+ }
339
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciTableCellValuePreloader, decorators: [{
340
+ type: Injectable
341
+ }], ctorParameters: () => [] });
342
+ const PRELOAD_BATCH_SIZE = 50_000;
343
+
344
+ /**
345
+ * Preloads cell values in the background when the browser is idle. This prevents lag with large datasets, in particular
346
+ * for expensive computations like formatting dates and generating ISO 8601 strings, providing faster sort and filter performance.
347
+ *
348
+ * If a cell's value is requested before background preloading finishes, it is computed synchronously.
349
+ */
350
+ class SciTableCellValuePreloader {
351
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellValuePreloader, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
352
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellValuePreloader, providedIn: 'root', useClass: ɵSciTableCellValuePreloader });
353
+ }
354
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellValuePreloader, decorators: [{
355
+ type: Injectable,
356
+ args: [{ providedIn: 'root', useClass: ɵSciTableCellValuePreloader }]
357
+ }] });
358
+
125
359
  /*
126
360
  * Copyright (c) 2018-2026 Swiss Federal Railways
127
361
  *
@@ -132,124 +366,188 @@ function provideTableStorage(storage) {
132
366
  * SPDX-License-Identifier: EPL-2.0
133
367
  */
134
368
  function arrayDatasource(data, columns) {
135
- const cache = linkedSignal({ ...(ngDevMode ? { debugName: "cache" } : /* istanbul ignore next */ {}), source: () => ({ data: data(), columns: columns() }),
136
- computation: () => new Map() });
137
- const items$ = toObservable(computed(() => {
138
- const resolvedColumns = columns();
139
- const items = data().map(item => ({
140
- item,
141
- cells: resolvedColumns.reduce((acc, column) => acc.set(column.name, {
142
- column,
143
- value: column.type !== 'component' && column.type !== 'template' ? coerceSignal(column.value(item))() : undefined,
144
- }), new Map()),
145
- }));
146
- return {
147
- items,
148
- columns: resolvedColumns,
149
- };
150
- }));
151
- return (request) => {
152
- const sortHash = request.sortCriteria.map(sc => `${sc.columnName}_${sc.direction}`).join('-');
153
- const filterHash = request.columnFilters.map(fc => `${fc.columnName}_${fc.text}`).join('-');
154
- const hash = `${sortHash}-${filterHash}-${request.tableFilter ?? ''}`;
155
- if (!cache().has(hash)) {
156
- const sortedAndFiltered$ = items$.pipe(map(({ items, columns }) => {
157
- const sortCols = mapCriteria(request.sortCriteria, columns);
158
- const filterCols = mapCriteria(request.columnFilters, columns);
159
- return items
160
- .filter(item => columnFilter(item, filterCols) && globalFilter(item, request.tableFilter))
161
- .sort((a, b) => sort(a, b, sortCols));
162
- }), shareReplay({ bufferSize: 1, refCount: true }));
163
- // Only store one item in the cache.
164
- // The cache is used for scrolling and multipage selection.
165
- // It caches the data based on the current filter and sort.
166
- cache.set(new Map().set(hash, sortedAndFiltered$));
167
- }
168
- return cache().get(hash).pipe(map(items => ({
169
- totalCount: items.length,
170
- items: items.slice(request.start, request.end).map(i => i.item),
369
+ const dataset = new Dataset(data, columns);
370
+ return markAsArrayDatasource((request) => {
371
+ dataset.columnFilters.set(request.columnFilters);
372
+ dataset.tableFilter.set(request.tableFilter);
373
+ dataset.sortCriteria.set(request.sortCriteria);
374
+ const totalCount = dataset.count;
375
+ const items = dataset.slice(request.start, request.end);
376
+ return toObservable(computed(() => ({
377
+ totalCount: totalCount(),
378
+ items: items(),
171
379
  })));
172
- };
380
+ });
173
381
  }
174
- function mapCriteria(criteria, columns) {
175
- return criteria.map(sc => {
176
- const column = columns.find(c => sc.columnName === c.name);
177
- return ({
178
- ...sc,
179
- column,
180
- });
181
- }).filter((sc) => sc.column !== undefined);
382
+ /**
383
+ * Provides a filtered and sorted view on given data.
384
+ */
385
+ class Dataset {
386
+ _dataview;
387
+ columnFilters = signal([], { ...(ngDevMode ? { debugName: "columnFilters" } : /* istanbul ignore next */ {}), equal: Objects.isEqual });
388
+ sortCriteria = signal([], { ...(ngDevMode ? { debugName: "sortCriteria" } : /* istanbul ignore next */ {}), equal: Objects.isEqual });
389
+ tableFilter = signal(undefined, /* @ts-ignore */
390
+ ...(ngDevMode ? [{ debugName: "tableFilter" }] : /* istanbul ignore next */ []));
391
+ /**
392
+ * Returns the total count of items matching the current filters and search criteria.
393
+ */
394
+ count = computed(() => this._dataview().length, /* @ts-ignore */
395
+ ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
396
+ constructor(data, columns) {
397
+ const tableCellValuePreloader = inject(SciTableCellValuePreloader);
398
+ const dataset = computed(() => data().map(item => ({
399
+ item,
400
+ cells: columns().reduce((cells, column) => untracked(() => {
401
+ // PERF: Cell values are computed lazily (on idle or first use) to defer expensive computations like date formatting.
402
+ const tableCellValue = 'value' in column ? new eSciTableCellValue(tableCellValuePreloader, () => column.value(item)()) : undefined;
403
+ return cells.set(column.name, {
404
+ column: column,
405
+ value: () => tableCellValue?.value(),
406
+ });
407
+ }), new Map()),
408
+ })), /* @ts-ignore */
409
+ ...(ngDevMode ? [{ debugName: "dataset" }] : /* istanbul ignore next */ []));
410
+ // Memoize filtered/sorted view; recomputes only when dataset or criteria change, not when scrolling through the view, as sorting is an expensive operation.
411
+ this._dataview = computed(() => {
412
+ const rows = dataset();
413
+ const columnFilters = this.columnFilters();
414
+ const tableFilter = this.tableFilter();
415
+ const sortCriteria = this.sortCriteria();
416
+ // PERF: Do not track signals inside `Array.sort` to avoid Angular signal tracking overhead as the comparator runs repeatedly, degrading performance otherwise.
417
+ return untracked(() => rows
418
+ .filter(row => matchesRow(row, columnFilters) && matchesGlobalFilter(row, tableFilter))
419
+ .sort((a, b) => compareRows(a, b, sortCriteria))
420
+ .map(row => row.item));
421
+ }, /* @ts-ignore */
422
+ ...(ngDevMode ? [{ debugName: "_dataview" }] : /* istanbul ignore next */ []));
423
+ }
424
+ /**
425
+ * Returns a slice of the filtered and sorted data within the given range (start inclusive, end exclusive).
426
+ */
427
+ slice(start, end) {
428
+ return computed(() => this._dataview().slice(start, end));
429
+ }
182
430
  }
183
- function globalFilter(row, filter) {
431
+ /**
432
+ * Tests whether a row matches the given global filter.
433
+ */
434
+ function matchesGlobalFilter(row, filter) {
184
435
  if (!filter?.trim()) {
185
436
  return true;
186
437
  }
187
- for (const column of row.cells.values()) {
188
- const result = columnFilter(row, [{ text: filter, columnName: column.column.name, column: column.column }]);
189
- // If any value includes the filter, it matches the filter.
190
- if (result) {
438
+ for (const cell of row.cells.values()) {
439
+ const text = coerceFilterText(filter, { to: cell.column.type });
440
+ if (text === undefined) {
441
+ continue;
442
+ }
443
+ if (matchesRow(row, [{ text, columnName: cell.column.name }])) {
191
444
  return true;
192
445
  }
193
446
  }
194
447
  return false;
195
448
  }
196
- function columnFilter(row, filterCriteria) {
197
- if (filterCriteria.length === 0) {
449
+ /**
450
+ * Tests whether a row matches the given column filters.
451
+ */
452
+ function matchesRow(row, columnFilters) {
453
+ if (!columnFilters.length) {
198
454
  return true;
199
455
  }
200
- for (const criterion of filterCriteria) {
201
- const value = row.cells.get(criterion.columnName)?.value;
202
- const filter = (() => {
203
- switch (criterion.column.type) {
204
- case 'string':
205
- return criterion.column.filter(criterion.text, { item: row.item, value: value });
206
- case 'number':
207
- return criterion.column.filter(criterion.text, { item: row.item, value: value });
208
- case 'boolean':
209
- return criterion.column.filter(criterion.text, { item: row.item, value: value });
210
- case 'component':
211
- case 'template':
212
- return criterion.column.filter(criterion.text, { item: row.item, value: undefined });
213
- default:
214
- return true;
215
- }
216
- })();
217
- // all filters must match (for now)
218
- if (!filter) {
456
+ for (const columnFilter of columnFilters) {
457
+ const cell = row.cells.get(columnFilter.columnName);
458
+ if ('value' in cell.column && cell.value() === undefined) { // Component and template columns have no value.
459
+ return false;
460
+ }
461
+ if (!cell.column.matches(columnFilter.text, { item: row.item, value: cell.value() })) {
219
462
  return false;
220
463
  }
221
464
  }
222
465
  return true;
223
466
  }
224
- function sort(a, b, sortCriteria) {
225
- if (sortCriteria.length === 0) {
467
+ /**
468
+ * Compares two rows based on the given sort criteria.
469
+ */
470
+ function compareRows(row1, row2, sortCriteria) {
471
+ if (!sortCriteria.length) {
226
472
  return 0;
227
473
  }
228
474
  for (const criterion of sortCriteria) {
229
- const aValue = a.cells.get(criterion.columnName)?.value;
230
- const bValue = b.cells.get(criterion.columnName)?.value;
231
- const sort = (() => {
232
- switch (criterion.column.type) {
233
- case 'string':
234
- return criterion.column.sort({ item: a.item, value: aValue }, { item: b.item, value: bValue });
235
- case 'number':
236
- return criterion.column.sort({ item: a.item, value: aValue }, { item: b.item, value: bValue });
237
- case 'boolean':
238
- return criterion.column.sort({ item: a.item, value: aValue }, { item: b.item, value: bValue });
239
- case 'component':
240
- case 'template':
241
- return criterion.column.sort({ item: a.item, value: undefined }, { item: b.item, value: undefined });
242
- default:
243
- return 0;
244
- }
245
- })();
246
- if (sort !== 0) {
247
- const dir = criterion.direction === 'asc' ? 1 : -1;
248
- return sort * dir;
475
+ const cell1 = row1.cells.get(criterion.columnName);
476
+ const cell2 = row2.cells.get(criterion.columnName);
477
+ const value1 = cell1.value();
478
+ const value2 = cell2.value();
479
+ // Always place `undefined` values at the end, independent of the sort direction. Note that component and template columns have no value.
480
+ if (value1 === undefined && value2 !== undefined) {
481
+ return 1;
482
+ }
483
+ if (value2 === undefined && value1 !== undefined) {
484
+ return -1;
485
+ }
486
+ const comparison = cell1.column.compare({ item: row1.item, value: value1 }, { item: row2.item, value: value2 });
487
+ if (comparison !== 0) {
488
+ return criterion.direction === 'asc' ? comparison : -comparison;
249
489
  }
250
490
  }
251
491
  return 0;
252
492
  }
493
+ /**
494
+ * Checks whether given {@link SciTableDataLoaderFn} represents an {@link arrayDatasource}.
495
+ */
496
+ function isArrayDatasource(loader) {
497
+ return ARRAY_DATASOURCE_MARKER in loader;
498
+ }
499
+ /**
500
+ * Marks given {@link SciTableDataLoaderFn} as an array data source.
501
+ */
502
+ function markAsArrayDatasource(loader) {
503
+ Object.defineProperty(loader, ARRAY_DATASOURCE_MARKER, { value: true, writable: false, enumerable: false, configurable: false });
504
+ return loader;
505
+ }
506
+ /**
507
+ * Identifies a {@link SciTableDataLoaderFn} as an {@link arrayDatasource}.
508
+ */
509
+ const ARRAY_DATASOURCE_MARKER = Symbol('ARRAY_DATASOURCE_HINT');
510
+ /**
511
+ * Coerces the given filter text into the specified column data type.
512
+ */
513
+ function coerceFilterText(text, options) {
514
+ switch (options.to) {
515
+ case 'number': {
516
+ const number = Number.parseFloat(text);
517
+ return !Number.isNaN(number) ? number : undefined;
518
+ }
519
+ case 'boolean': {
520
+ if (text === 'true' || text === '1') {
521
+ return true;
522
+ }
523
+ if (text === 'false' || text === '0') {
524
+ return false;
525
+ }
526
+ return undefined;
527
+ }
528
+ default: {
529
+ return text;
530
+ }
531
+ }
532
+ }
533
+ /**
534
+ * Provides lazy loading for the cell's value, preloading it when idle for better sort/filter performance.
535
+ *
536
+ * The value is computed at most once, whether it gets preloaded during idle time or requested synchronously via {@link value()}.
537
+ */
538
+ class eSciTableCellValue {
539
+ _valueFn;
540
+ _value;
541
+ constructor(preloader, _valueFn) {
542
+ this._valueFn = _valueFn;
543
+ preloader.queue(() => {
544
+ this._value ??= this._valueFn();
545
+ });
546
+ }
547
+ value() {
548
+ return this._value ??= untracked(() => this._valueFn());
549
+ }
550
+ }
253
551
 
254
552
  /*
255
553
  * Copyright (c) 2018-2026 Swiss Federal Railways
@@ -409,11 +707,14 @@ class ɵSciTable {
409
707
  scrolling;
410
708
  resizing = computed(() => this.columns().some(column => column.resizing()), /* @ts-ignore */
411
709
  ...(ngDevMode ? [{ debugName: "resizing" }] : /* istanbul ignore next */ []));
412
- sortCriteria = signal([], /* @ts-ignore */
413
- ...(ngDevMode ? [{ debugName: "sortCriteria" }] : /* istanbul ignore next */ []));
414
- filterCriteria = signal([], /* @ts-ignore */
415
- ...(ngDevMode ? [{ debugName: "filterCriteria" }] : /* istanbul ignore next */ []));
710
+ sortCriteria = linkedSignal({ ...(ngDevMode ? { debugName: "sortCriteria" } : /* istanbul ignore next */ {}), source: () => this.columns(), // Discard stale criteria on column change.
711
+ computation: (columns, previous) => previous?.value.filter(criteria => columns.some(column => column.name === criteria.columnName)) ?? [],
712
+ equal: Objects.isEqual });
713
+ filterCriteria = linkedSignal({ ...(ngDevMode ? { debugName: "filterCriteria" } : /* istanbul ignore next */ {}), source: () => this.columns(), // Discard stale criteria on column change.
714
+ computation: (columns, previous) => previous?.value.filter(criteria => columns.some(column => column.name === criteria.columnName)) ?? [],
715
+ equal: Objects.isEqual });
416
716
  _cache = new SciTableCache();
717
+ _tableCellValuePreloader = inject(SciTableCellValuePreloader);
417
718
  _tableFilter = signal(null, /* @ts-ignore */
418
719
  ...(ngDevMode ? [{ debugName: "_tableFilter" }] : /* istanbul ignore next */ []));
419
720
  _selectedItems = signal(new Map(), /* @ts-ignore */
@@ -430,7 +731,8 @@ class ɵSciTable {
430
731
  computation: () => -1 });
431
732
  criteria = computed(() => ({ sort: this.sortCriteria(), filter: this.filterCriteria(), tableFilter: this._tableFilter() }), /* @ts-ignore */
432
733
  ...(ngDevMode ? [{ debugName: "criteria" }] : /* istanbul ignore next */ []));
433
- loading = this._cache.loading;
734
+ loading = computed(() => this._cache.loading() || this._tableCellValuePreloader.loading(), /* @ts-ignore */
735
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
434
736
  error = this._cache.error;
435
737
  activeRow;
436
738
  hoveredRow = computed(() => this.rowsByIndex().get(this.hoveredIndex()), /* @ts-ignore */
@@ -494,9 +796,9 @@ class ɵSciTable {
494
796
  injector?.destroy();
495
797
  injector = createDestroyableInjector({ parent: this._injector });
496
798
  return runInInjectionContext(injector, () => {
497
- const tableColumnFactory = new ɵSciTableColumnFactory(descriptor);
799
+ const tableColumnFactory = new ɵSciTableColumnFactory(this);
498
800
  descriptor.columns(tableColumnFactory);
499
- return untracked(() => tableColumnFactory.columns.map(column => this.initColumn(column.type, column)));
801
+ return tableColumnFactory.columns;
500
802
  });
501
803
  });
502
804
  }
@@ -573,7 +875,7 @@ class ɵSciTable {
573
875
  defaultValue: 0,
574
876
  params: () => this.tableViewRef()?.viewport,
575
877
  stream: ({ params: viewport }) => fromEvent(viewport, 'scroll', { passive: true })
576
- .pipe(map$1(() => viewport.scrollTop), startWith(viewport.scrollTop), subscribeIn(fn => zone.runOutsideAngular(fn))),
878
+ .pipe(map(() => viewport.scrollTop), startWith(viewport.scrollTop), subscribeIn(fn => zone.runOutsideAngular(fn))),
577
879
  }).value;
578
880
  }
579
881
  /**
@@ -585,7 +887,7 @@ class ɵSciTable {
585
887
  defaultValue: false,
586
888
  params: () => this.tableViewRef()?.viewport,
587
889
  stream: ({ params: viewport }) => fromEvent(viewport, 'scroll', { passive: true })
588
- .pipe(switchMap(() => concat(of(true), timer(150).pipe(map$1(() => false)))), startWith(false), subscribeIn(fn => zone.runOutsideAngular(fn))),
890
+ .pipe(switchMap(() => concat(of(true), timer(150).pipe(map(() => false)))), startWith(false), subscribeIn(fn => zone.runOutsideAngular(fn))),
589
891
  }).value;
590
892
  }
591
893
  /**
@@ -631,32 +933,22 @@ class ɵSciTable {
631
933
  tableFilter,
632
934
  columnFilters,
633
935
  })), error => throwError(() => error));
936
+ const columns$ = toObservable(this.columns);
634
937
  // Create a resource to track loading and error states.
635
- const tableResponse = rxResource({ stream: () => tableResponse$ });
636
- // Create a derived resource mapping the response to rows. Must be done in a separate resource to not fetch data anew on column change.
637
- const rows = resource({ ...(ngDevMode ? { debugName: "rows" } : /* istanbul ignore next */ {}), params: ({ chain }) => {
638
- const items = chain(tableResponse)?.items;
639
- return items && { items, columns: this.columns() };
640
- },
641
- loader: async ({ params }) => this.mapItemsToRow(params.items, params.columns, pageStart),
642
- defaultValue: [] });
643
- // Synchronize total row count based on resource state.
644
- effect(() => {
645
- switch (rows.status()) {
646
- case 'resolved': {
647
- // Update count only after rows resolve to prevent premature scroll invalidation.
648
- this.totalCount.set(tableResponse.value()?.totalCount);
649
- break;
650
- }
651
- case 'error': {
652
- console.error(rows.error().cause);
938
+ return rxResource({
939
+ stream: () => tableResponse$.pipe(combineLatestWith(columns$), map(([response, columns]) => ({
940
+ rows: this.mapItemsToRow(response.items, columns, pageStart),
941
+ totalCount: response.totalCount,
942
+ })), tap({
943
+ next: ({ totalCount }) => this.totalCount.set(totalCount),
944
+ error: error => {
945
+ console.error(error);
653
946
  // Reset count to scroll to the top so the user sees the error.
654
947
  this.totalCount.set(0);
655
- break;
656
- }
657
- }
948
+ },
949
+ }), map(({ rows }) => rows)),
950
+ defaultValue: [],
658
951
  });
659
- return rows;
660
952
  }),
661
953
  dispose: () => cacheEntryInjector.destroy(),
662
954
  start: pageStart,
@@ -715,6 +1007,14 @@ class ɵSciTable {
715
1007
  updateSelectedItems(updateFn) {
716
1008
  this._selectedItems.update(updateFn);
717
1009
  }
1010
+ /**
1011
+ * Indicates whether the built-in array datasource is used.
1012
+ *
1013
+ * Supports out-of-the-box filtering and sorting for built-in table columns.
1014
+ */
1015
+ isArrayDatasource() {
1016
+ return isArrayDatasource(this._dataLoaderFn);
1017
+ }
718
1018
  reset() {
719
1019
  this._cache.clear();
720
1020
  this.totalCount.set(undefined);
@@ -761,32 +1061,6 @@ class ɵSciTable {
761
1061
  this._cache.clear();
762
1062
  });
763
1063
  }
764
- // TODO [dwi] Consider moving into factory
765
- initColumn(type, config) {
766
- // Columns with a custom component or template must provide a sort function to be sortable, because the default sort function does not work.
767
- const sortable = type === 'component' || type === 'template' ? !!config.sortable : config.sortable !== false;
768
- // Columns with a custom component or template must provide a filter function to be filterable, because the default filter function does not work.
769
- const filterable = type === 'component' || type === 'template' ? !!config.filterable : config.filterable !== false;
770
- return {
771
- ...config,
772
- type,
773
- name: config.name,
774
- filter: typeof config.filterable === 'object' ? config.filterable.matcher : defaultFilter,
775
- sort: typeof config.sortable === 'object' ? config.sortable.comparator : defaultSort,
776
- sortable: computed(() => this.sortable() && sortable),
777
- filterable: computed(() => this.filterable() && filterable),
778
- resizable: computed(() => this.resizable() && (config.resizable ?? true)),
779
- padding: 'padding' in config ? (config.padding ?? true) : true,
780
- header: coerceSignal(config.header ?? ''),
781
- width: computed(() => {
782
- const userSettings = this.userSettings().columns?.find(column => column.name === config.name);
783
- return userSettings?.width ? `${userSettings.width}px` : config.width ?? '1fr';
784
- }),
785
- minWidth: config.minWidth ?? 100,
786
- resizing: signal(false),
787
- location: { x: 0, width: 0 }, // injected in `SciColumnComponent`
788
- };
789
- }
790
1064
  mapItemsToRow(items, columns, pageStart) {
791
1065
  return items.map((item, i) => {
792
1066
  const id = this.trackBy(item);
@@ -801,7 +1075,7 @@ class ɵSciTable {
801
1075
  hovered: computed(() => this.hoveredRow()?.index === index),
802
1076
  bindings: coerceTableRowBindings(this._rowBindings, item, pageStart + i),
803
1077
  cells: columns.map(column => ({
804
- value: column.type !== 'component' && column.type !== 'template' ? coerceSignal(column.value(item)) : undefined,
1078
+ value: 'value' in column ? column.value(item) : undefined,
805
1079
  component: column.type === 'component' ? column.component(item) : undefined,
806
1080
  template: column.type === 'template' ? column.template(item) : undefined,
807
1081
  type: column.type,
@@ -855,32 +1129,6 @@ class ɵSciTable {
855
1129
  });
856
1130
  }
857
1131
  }
858
- function defaultFilter(text, { value }) {
859
- if (typeof value !== typeof text) {
860
- return false;
861
- }
862
- switch (typeof value) {
863
- case 'string':
864
- return value.toLowerCase().includes(text.toLowerCase());
865
- default:
866
- return text === value;
867
- }
868
- }
869
- function defaultSort(a, b) {
870
- if (typeof a.value !== typeof b.value) {
871
- return 0;
872
- }
873
- switch (typeof a.value) {
874
- case 'string':
875
- return a.value.localeCompare(b.value);
876
- case 'number':
877
- return a.value - b.value;
878
- case 'boolean':
879
- return a.value === b.value ? 0 : (a.value ? 1 : -1);
880
- default:
881
- return 0;
882
- }
883
- }
884
1132
  /**
885
1133
  * Gets pages by range and page size, or `null` if empty. End is exclusive.
886
1134
  */
@@ -1383,6 +1631,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1383
1631
  type: Injectable
1384
1632
  }] });
1385
1633
 
1634
+ /*
1635
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1636
+ *
1637
+ * This program and the accompanying materials are made
1638
+ * available under the terms of the Eclipse Public License 2.0
1639
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1640
+ *
1641
+ * SPDX-License-Identifier: EPL-2.0
1642
+ */
1643
+ /**
1644
+ * Formats a given number based on the provided {@link Intl.NumberFormat}.
1645
+ */
1646
+ class SciNumberPipe {
1647
+ transform(number, format) {
1648
+ return number !== undefined ? format.format(number) : undefined;
1649
+ }
1650
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciNumberPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1651
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: SciNumberPipe, isStandalone: true, name: "sciNumber" });
1652
+ }
1653
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciNumberPipe, decorators: [{
1654
+ type: Pipe,
1655
+ args: [{ name: 'sciNumber' }]
1656
+ }] });
1657
+
1386
1658
  /*
1387
1659
  * Copyright (c) 2018-2026 Swiss Federal Railways
1388
1660
  *
@@ -1427,14 +1699,22 @@ class SciTableCellComponent {
1427
1699
  }
1428
1700
  computePartAttribute() {
1429
1701
  return computed(() => {
1430
- return [
1431
- ...Arrays.coerce(this.row().bindings?.part()),
1702
+ return Arrays.distinct([
1703
+ // Enable column styling based on column type.
1704
+ `column:${this.cell().column.type}`,
1705
+ // Enable column styling based on column name.
1432
1706
  this.cell().column.name,
1433
- ].join(' ');
1707
+ // Enable row styling based on selection state.
1708
+ this.row().selected() ? 'row:selected' : 'row:not:selected',
1709
+ // Enable row styling based on active state.
1710
+ this.row().active() ? 'row:active' : 'row:not:active',
1711
+ // Enable row styling based on row bindings.
1712
+ ...Arrays.coerce(this.row().bindings?.part()),
1713
+ ]).join(' ');
1434
1714
  });
1435
1715
  }
1436
1716
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1437
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciTableCellComponent, isStandalone: true, selector: "sci-table-cell", inputs: { cell: { classPropertyName: "cell", publicName: "cell", isSignal: true, isRequired: true, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-type": "cell().column.type", "attr.data-column": "cell().column.name", "attr.data-padding": "!cell().column.padding ? false : null", "attr.part": "row().selected() ? null : partAttribute()" } }, ngImport: i0, template: "@let cell = this.cell();\n\n@switch (cell.type) {\n @case ('template') {\n <ng-template *ngTemplateOutlet=\"template(); context: templateContext()\"/>\n }\n @case ('component') {\n <ng-container *sciComponentOutlet=\"cell.component\"/>\n }\n @case ('boolean') {\n <!-- TODO [dwi] Use another ligature for icon scion.clear -->\n <sci-icon class=\"checkbox\">{{cell.value() ? 'scion.checkmark' : 'scion.clear'}}</sci-icon>\n }\n @default {\n {{cell.value()}}\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;position:relative;border-inline:var(--\\275sci-table-cell-padding-inline) solid transparent;align-items:center;overflow:hidden}:host:is([data-type=string],[data-type=number]){display:inline;white-space:nowrap;text-overflow:ellipsis;line-height:var(--\\275sci-table-row-height)}:host[data-type=number]{text-align:end}:host[data-padding=false]{border-inline:none;align-items:stretch}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "directive", type: SciComponentOutletDirective, selector: "ng-template[sciComponentOutlet]", inputs: ["sciComponentOutlet"] }] });
1717
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciTableCellComponent, isStandalone: true, selector: "sci-table-cell", inputs: { cell: { classPropertyName: "cell", publicName: "cell", isSignal: true, isRequired: true, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-type": "cell().column.type", "attr.data-column": "cell().column.name", "attr.data-padding": "!cell().column.padding ? false : null", "attr.part": "partAttribute()" } }, ngImport: i0, template: "@let cell = this.cell();\n\n@switch (cell.type) {\n @case ('template') {\n <ng-template *ngTemplateOutlet=\"template(); context: templateContext()\"/>\n }\n @case ('component') {\n <ng-container *sciComponentOutlet=\"cell.component\"/>\n }\n @case ('boolean') {\n @if (cell.value() !== undefined) {\n <!-- TODO [dwi] Use another ligature for icon scion.clear -->\n <sci-icon class=\"checkbox\">{{cell.value() ? 'scion.checkmark' : 'scion.clear'}}</sci-icon>\n }\n }\n @case ('date') {\n {{cell.value()?.formatted}}\n }\n @case ('number') {\n {{cell.value() | sciNumber:cell.column.format()}}\n }\n @default {\n {{cell.value()}}\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;position:relative;border-inline:var(--\\275sci-table-cell-padding-inline) solid transparent;align-items:center;overflow:hidden}:host:is([data-type=string],[data-type=number],[data-type=date]){display:inline;white-space:nowrap;text-overflow:ellipsis;line-height:var(--\\275sci-table-row-height)}:host[data-type=number]{text-align:end}:host:is([data-type=number],[data-type=date]){font-variant-numeric:tabular-nums}:host[data-padding=false]{border-inline:none;align-items:stretch}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "directive", type: SciComponentOutletDirective, selector: "ng-template[sciComponentOutlet]", inputs: ["sciComponentOutlet"] }, { kind: "pipe", type: SciNumberPipe, name: "sciNumber" }] });
1438
1718
  }
1439
1719
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellComponent, decorators: [{
1440
1720
  type: Component,
@@ -1442,12 +1722,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1442
1722
  '[attr.data-type]': 'cell().column.type',
1443
1723
  '[attr.data-column]': 'cell().column.name',
1444
1724
  '[attr.data-padding]': '!cell().column.padding ? false : null',
1445
- '[attr.part]': 'row().selected() ? null : partAttribute()', // prevent styling selected rows
1725
+ '[attr.part]': 'partAttribute()',
1446
1726
  }, imports: [
1447
1727
  NgTemplateOutlet,
1448
1728
  SciIconComponent,
1449
1729
  SciComponentOutletDirective,
1450
- ], template: "@let cell = this.cell();\n\n@switch (cell.type) {\n @case ('template') {\n <ng-template *ngTemplateOutlet=\"template(); context: templateContext()\"/>\n }\n @case ('component') {\n <ng-container *sciComponentOutlet=\"cell.component\"/>\n }\n @case ('boolean') {\n <!-- TODO [dwi] Use another ligature for icon scion.clear -->\n <sci-icon class=\"checkbox\">{{cell.value() ? 'scion.checkmark' : 'scion.clear'}}</sci-icon>\n }\n @default {\n {{cell.value()}}\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;position:relative;border-inline:var(--\\275sci-table-cell-padding-inline) solid transparent;align-items:center;overflow:hidden}:host:is([data-type=string],[data-type=number]){display:inline;white-space:nowrap;text-overflow:ellipsis;line-height:var(--\\275sci-table-row-height)}:host[data-type=number]{text-align:end}:host[data-padding=false]{border-inline:none;align-items:stretch}\n"] }]
1730
+ SciNumberPipe,
1731
+ ], template: "@let cell = this.cell();\n\n@switch (cell.type) {\n @case ('template') {\n <ng-template *ngTemplateOutlet=\"template(); context: templateContext()\"/>\n }\n @case ('component') {\n <ng-container *sciComponentOutlet=\"cell.component\"/>\n }\n @case ('boolean') {\n @if (cell.value() !== undefined) {\n <!-- TODO [dwi] Use another ligature for icon scion.clear -->\n <sci-icon class=\"checkbox\">{{cell.value() ? 'scion.checkmark' : 'scion.clear'}}</sci-icon>\n }\n }\n @case ('date') {\n {{cell.value()?.formatted}}\n }\n @case ('number') {\n {{cell.value() | sciNumber:cell.column.format()}}\n }\n @default {\n {{cell.value()}}\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;position:relative;border-inline:var(--\\275sci-table-cell-padding-inline) solid transparent;align-items:center;overflow:hidden}:host:is([data-type=string],[data-type=number],[data-type=date]){display:inline;white-space:nowrap;text-overflow:ellipsis;line-height:var(--\\275sci-table-row-height)}:host[data-type=number]{text-align:end}:host:is([data-type=number],[data-type=date]){font-variant-numeric:tabular-nums}:host[data-padding=false]{border-inline:none;align-items:stretch}\n"] }]
1451
1732
  }], propDecorators: { cell: [{ type: i0.Input, args: [{ isSignal: true, alias: "cell", required: true }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: true }] }] } });
1452
1733
 
1453
1734
  /*
@@ -1489,7 +1770,7 @@ class SciTableRowComponent {
1489
1770
  onRowMouseLeave(event) {
1490
1771
  const next = event.relatedTarget;
1491
1772
  // Do not unset hovered row when hovering a column resize splitter.
1492
- if (next instanceof Element && next.closest('sci-column-splitter')) {
1773
+ if (next instanceof Element && next.closest('sci-splitter')) {
1493
1774
  return;
1494
1775
  }
1495
1776
  this.table().hoveredIndex.set(-1);
@@ -1507,7 +1788,7 @@ class SciTableRowComponent {
1507
1788
  });
1508
1789
  }
1509
1790
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableRowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1510
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciTableRowComponent, isStandalone: true, selector: "sci-table-row", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { primaryAction: "primaryAction" }, host: { listeners: { "click": "onRowClick($event)", "dblclick": "onRowDblClick($event)", "mouseenter": "onRowMouseEnter()", "mouseleave": "onRowMouseLeave($event)" }, properties: { "attr.data-active": "row().active() ? '' : null", "attr.data-selected": "row().selected() ? '' : null", "attr.data-hovered": "row().hovered() ? '' : null", "class": "row().bindings?.cssClass?.()" } }, viewQueries: [{ propertyName: "rowActionToolbar", first: true, predicate: SciToolbarComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@let row = this.row();\n@for (cell of row.cells; track cell.column.name) {\n <sci-table-cell [cell]=\"cell\" [row]=\"row\"/>\n} @empty {\n @for (column of table().columns(); track column.name) {\n <div class=\"skeleton e2e-skeleton\" [attr.data-column]=\"column.name\"></div>\n }\n}\n\n@let hasRowActions = !!table().rowActions;\n@let isRowHovered = table().hoveredRow() === row;\n@let isRowActionMenuOpen = rowActionToolbar()?.menuOpen();\n\n@if (hasRowActions && (isRowHovered || isRowActionMenuOpen)) {\n <!--eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <sci-toolbar [name]=\"rowActionsToolbarName\" (click)=\"onActionToolbarClick($event)\" class=\"row-actions\"/>\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;grid-template-columns:subgrid;grid-auto-rows:var(--sci-table-row-height);align-items:stretch;-webkit-user-select:none;user-select:none;border-bottom:var(--\\275sci-table-border-size) solid var(--sci-table-gridline-color)}:host[data-hovered]{background-color:var(--sci-table-row-background-color-hover)}:host[data-selected]{background-color:var(--sci-table-row-background-color-selected);--\\275sci-table-row-selected: true}:host[data-hovered]:not([data-selected]){border-radius:var(--sci-table-row-border-radius)}:host:is(:first-child,:not([data-selected])+sci-table-row)[data-selected]{border-top-left-radius:var(--sci-table-row-border-radius);border-top-right-radius:var(--sci-table-row-border-radius)}:host[data-selected]:not(:has(+sci-table-row[data-selected])){border-bottom-left-radius:var(--sci-table-row-border-radius);border-bottom-right-radius:var(--sci-table-row-border-radius)}@container (style(--\\275sci-table-scrolling: true)) or (style(--\\275sci-table-resizing: true)){:host{pointer-events:none}}:host:has(>div.skeleton){pointer-events:none;align-items:center}:host:has(>div.skeleton)>div.skeleton{display:inline-block;position:relative;overflow:hidden;border-radius:var(--sci-corner-small);background-color:var(--sci-color-skeleton-subtle)}:host:has(>div.skeleton)>div.skeleton:after{position:absolute;inset:0;transform:translate(-100%);background-image:linear-gradient(to right,transparent 0,color-mix(in srgb,light-dark(var(--sci-static-color-white),var(--sci-static-color-black)) 30%,transparent) 50%,transparent);animation:shimmer 2s infinite;content:\"\"}@keyframes shimmer{to{transform:translate(100%)}}:host:has(>div.skeleton)>div.skeleton{height:1em;margin-inline:var(--\\275sci-table-cell-padding-inline)}:host>sci-toolbar.row-actions{position:sticky;right:0;transform:translate(calc(-1 * var(--\\275sci-table-cell-padding-inline)));justify-self:end;align-self:center;border-radius:var(--sci-corner);padding:1px;background-color:var(--\\275sci-table-row-action-background-color);z-index:2;--sci-toolbar-item-background-color-hover: color-mix(in srgb, var(--\\275sci-table-row-action-background-color) 85%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)));--sci-toolbar-item-background-color-active: color-mix(in srgb, var(--\\275sci-table-row-action-background-color) 75%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)))}@container not style(--\\275sci-table-row-selected: true){:host>sci-toolbar.row-actions{--\\275sci-table-row-action-background-color: var(--sci-table-row-action-background-color)}}@container style(--\\275sci-table-row-selected: true){:host>sci-toolbar.row-actions{--\\275sci-table-row-action-background-color: var(--sci-table-row-action-background-color-selected)}}:host>sci-toolbar.row-actions:empty{display:none}\n"], dependencies: [{ kind: "component", type: SciTableCellComponent, selector: "sci-table-cell", inputs: ["cell", "row"] }, { kind: "component", type: SciToolbarComponent, selector: "sci-toolbar", inputs: ["name", "orientation", "context", "acceleratorTarget", "popoverViewContainerRef"] }] });
1791
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciTableRowComponent, isStandalone: true, selector: "sci-table-row", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { primaryAction: "primaryAction" }, host: { listeners: { "click": "onRowClick($event)", "dblclick": "onRowDblClick($event)", "mouseenter": "onRowMouseEnter()", "mouseleave": "onRowMouseLeave($event)" }, properties: { "attr.data-active": "row().active() ? '' : null", "attr.data-selected": "row().selected() ? '' : null", "attr.data-hovered": "row().hovered() ? '' : null", "class": "row().bindings?.cssClass?.()" } }, viewQueries: [{ propertyName: "rowActionToolbar", first: true, predicate: SciToolbarComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@let row = this.row();\n@for (cell of row.cells; track cell.column.name) {\n <sci-table-cell [cell]=\"cell\" [row]=\"row\"/>\n} @empty {\n @for (column of table().columns(); track column.name) {\n <div class=\"skeleton e2e-skeleton\" [attr.data-column]=\"column.name\"></div>\n }\n}\n\n@let hasRowActions = !!table().rowActions;\n@let isRowHovered = table().hoveredRow() === row;\n@let isRowActionMenuOpen = rowActionToolbar()?.menuOpen();\n\n@if (hasRowActions && (isRowHovered || isRowActionMenuOpen)) {\n <sci-toolbar [name]=\"rowActionsToolbarName\" (click)=\"onActionToolbarClick($event)\" class=\"row-actions\"/>\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;grid-template-columns:subgrid;grid-auto-rows:var(--sci-table-row-height);align-items:stretch;-webkit-user-select:none;user-select:none;border-bottom:var(--\\275sci-table-border-size) solid var(--sci-table-gridline-color)}:host[data-hovered]{background-color:var(--sci-table-row-background-color-hover)}:host[data-selected]{background-color:var(--sci-table-row-background-color-selected);--\\275sci-table-row-selected: true}:host[data-hovered]:not([data-selected]){border-radius:var(--sci-table-row-border-radius)}:host:is(:first-child,:not([data-selected])+sci-table-row)[data-selected]{border-top-left-radius:var(--sci-table-row-border-radius);border-top-right-radius:var(--sci-table-row-border-radius)}:host[data-selected]:not(:has(+sci-table-row[data-selected])){border-bottom-left-radius:var(--sci-table-row-border-radius);border-bottom-right-radius:var(--sci-table-row-border-radius)}@container (style(--\\275sci-table-scrolling: true)) or (style(--\\275sci-table-resizing: true)){:host{pointer-events:none}}:host:has(>div.skeleton){pointer-events:none;align-items:center}:host:has(>div.skeleton)>div.skeleton{display:inline-block;position:relative;overflow:hidden;border-radius:var(--sci-corner-small);background-color:var(--sci-color-skeleton-subtle);opacity:0;animation-name:visiblity;animation-duration:.5s;animation-timing-function:ease-in;animation-fill-mode:forwards}:host:has(>div.skeleton)>div.skeleton:after{position:absolute;inset:0;transform:translate(-100%);background-image:linear-gradient(to right,transparent 0,color-mix(in srgb,light-dark(var(--sci-static-color-white),var(--sci-static-color-black)) 30%,transparent) 50%,transparent);animation:shimmer 2s infinite;content:\"\"}@keyframes shimmer{to{transform:translate(100%)}}@keyframes visiblity{to{opacity:1}}:host:has(>div.skeleton)>div.skeleton{height:1em;margin-inline:var(--\\275sci-table-cell-padding-inline)}:host>sci-toolbar.row-actions{position:sticky;right:0;transform:translate(calc(-1 * var(--\\275sci-table-cell-padding-inline)));justify-self:end;align-self:center;border-radius:var(--sci-corner);padding:1px;background-color:var(--\\275sci-table-row-action-background-color);z-index:2;--sci-toolbar-item-background-color-hover: color-mix(in srgb, var(--\\275sci-table-row-action-background-color) 85%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)));--sci-toolbar-item-background-color-active: color-mix(in srgb, var(--\\275sci-table-row-action-background-color) 75%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)))}@container not style(--\\275sci-table-row-selected: true){:host>sci-toolbar.row-actions{--\\275sci-table-row-action-background-color: var(--sci-table-row-action-background-color)}}@container style(--\\275sci-table-row-selected: true){:host>sci-toolbar.row-actions{--\\275sci-table-row-action-background-color: var(--sci-table-row-action-background-color-selected)}}:host>sci-toolbar.row-actions:empty{display:none}\n"], dependencies: [{ kind: "component", type: SciTableCellComponent, selector: "sci-table-cell", inputs: ["cell", "row"] }, { kind: "component", type: SciToolbarComponent, selector: "sci-toolbar", inputs: ["name", "orientation", "context", "acceleratorTarget", "popoverViewContainerRef"] }] });
1511
1792
  }
1512
1793
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableRowComponent, decorators: [{
1513
1794
  type: Component,
@@ -1523,7 +1804,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
1523
1804
  '(dblclick)': 'onRowDblClick($event)',
1524
1805
  '(mouseenter)': 'onRowMouseEnter()',
1525
1806
  '(mouseleave)': 'onRowMouseLeave($event)',
1526
- }, template: "@let row = this.row();\n@for (cell of row.cells; track cell.column.name) {\n <sci-table-cell [cell]=\"cell\" [row]=\"row\"/>\n} @empty {\n @for (column of table().columns(); track column.name) {\n <div class=\"skeleton e2e-skeleton\" [attr.data-column]=\"column.name\"></div>\n }\n}\n\n@let hasRowActions = !!table().rowActions;\n@let isRowHovered = table().hoveredRow() === row;\n@let isRowActionMenuOpen = rowActionToolbar()?.menuOpen();\n\n@if (hasRowActions && (isRowHovered || isRowActionMenuOpen)) {\n <!--eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <sci-toolbar [name]=\"rowActionsToolbarName\" (click)=\"onActionToolbarClick($event)\" class=\"row-actions\"/>\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;grid-template-columns:subgrid;grid-auto-rows:var(--sci-table-row-height);align-items:stretch;-webkit-user-select:none;user-select:none;border-bottom:var(--\\275sci-table-border-size) solid var(--sci-table-gridline-color)}:host[data-hovered]{background-color:var(--sci-table-row-background-color-hover)}:host[data-selected]{background-color:var(--sci-table-row-background-color-selected);--\\275sci-table-row-selected: true}:host[data-hovered]:not([data-selected]){border-radius:var(--sci-table-row-border-radius)}:host:is(:first-child,:not([data-selected])+sci-table-row)[data-selected]{border-top-left-radius:var(--sci-table-row-border-radius);border-top-right-radius:var(--sci-table-row-border-radius)}:host[data-selected]:not(:has(+sci-table-row[data-selected])){border-bottom-left-radius:var(--sci-table-row-border-radius);border-bottom-right-radius:var(--sci-table-row-border-radius)}@container (style(--\\275sci-table-scrolling: true)) or (style(--\\275sci-table-resizing: true)){:host{pointer-events:none}}:host:has(>div.skeleton){pointer-events:none;align-items:center}:host:has(>div.skeleton)>div.skeleton{display:inline-block;position:relative;overflow:hidden;border-radius:var(--sci-corner-small);background-color:var(--sci-color-skeleton-subtle)}:host:has(>div.skeleton)>div.skeleton:after{position:absolute;inset:0;transform:translate(-100%);background-image:linear-gradient(to right,transparent 0,color-mix(in srgb,light-dark(var(--sci-static-color-white),var(--sci-static-color-black)) 30%,transparent) 50%,transparent);animation:shimmer 2s infinite;content:\"\"}@keyframes shimmer{to{transform:translate(100%)}}:host:has(>div.skeleton)>div.skeleton{height:1em;margin-inline:var(--\\275sci-table-cell-padding-inline)}:host>sci-toolbar.row-actions{position:sticky;right:0;transform:translate(calc(-1 * var(--\\275sci-table-cell-padding-inline)));justify-self:end;align-self:center;border-radius:var(--sci-corner);padding:1px;background-color:var(--\\275sci-table-row-action-background-color);z-index:2;--sci-toolbar-item-background-color-hover: color-mix(in srgb, var(--\\275sci-table-row-action-background-color) 85%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)));--sci-toolbar-item-background-color-active: color-mix(in srgb, var(--\\275sci-table-row-action-background-color) 75%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)))}@container not style(--\\275sci-table-row-selected: true){:host>sci-toolbar.row-actions{--\\275sci-table-row-action-background-color: var(--sci-table-row-action-background-color)}}@container style(--\\275sci-table-row-selected: true){:host>sci-toolbar.row-actions{--\\275sci-table-row-action-background-color: var(--sci-table-row-action-background-color-selected)}}:host>sci-toolbar.row-actions:empty{display:none}\n"] }]
1807
+ }, template: "@let row = this.row();\n@for (cell of row.cells; track cell.column.name) {\n <sci-table-cell [cell]=\"cell\" [row]=\"row\"/>\n} @empty {\n @for (column of table().columns(); track column.name) {\n <div class=\"skeleton e2e-skeleton\" [attr.data-column]=\"column.name\"></div>\n }\n}\n\n@let hasRowActions = !!table().rowActions;\n@let isRowHovered = table().hoveredRow() === row;\n@let isRowActionMenuOpen = rowActionToolbar()?.menuOpen();\n\n@if (hasRowActions && (isRowHovered || isRowActionMenuOpen)) {\n <sci-toolbar [name]=\"rowActionsToolbarName\" (click)=\"onActionToolbarClick($event)\" class=\"row-actions\"/>\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;grid-template-columns:subgrid;grid-auto-rows:var(--sci-table-row-height);align-items:stretch;-webkit-user-select:none;user-select:none;border-bottom:var(--\\275sci-table-border-size) solid var(--sci-table-gridline-color)}:host[data-hovered]{background-color:var(--sci-table-row-background-color-hover)}:host[data-selected]{background-color:var(--sci-table-row-background-color-selected);--\\275sci-table-row-selected: true}:host[data-hovered]:not([data-selected]){border-radius:var(--sci-table-row-border-radius)}:host:is(:first-child,:not([data-selected])+sci-table-row)[data-selected]{border-top-left-radius:var(--sci-table-row-border-radius);border-top-right-radius:var(--sci-table-row-border-radius)}:host[data-selected]:not(:has(+sci-table-row[data-selected])){border-bottom-left-radius:var(--sci-table-row-border-radius);border-bottom-right-radius:var(--sci-table-row-border-radius)}@container (style(--\\275sci-table-scrolling: true)) or (style(--\\275sci-table-resizing: true)){:host{pointer-events:none}}:host:has(>div.skeleton){pointer-events:none;align-items:center}:host:has(>div.skeleton)>div.skeleton{display:inline-block;position:relative;overflow:hidden;border-radius:var(--sci-corner-small);background-color:var(--sci-color-skeleton-subtle);opacity:0;animation-name:visiblity;animation-duration:.5s;animation-timing-function:ease-in;animation-fill-mode:forwards}:host:has(>div.skeleton)>div.skeleton:after{position:absolute;inset:0;transform:translate(-100%);background-image:linear-gradient(to right,transparent 0,color-mix(in srgb,light-dark(var(--sci-static-color-white),var(--sci-static-color-black)) 30%,transparent) 50%,transparent);animation:shimmer 2s infinite;content:\"\"}@keyframes shimmer{to{transform:translate(100%)}}@keyframes visiblity{to{opacity:1}}:host:has(>div.skeleton)>div.skeleton{height:1em;margin-inline:var(--\\275sci-table-cell-padding-inline)}:host>sci-toolbar.row-actions{position:sticky;right:0;transform:translate(calc(-1 * var(--\\275sci-table-cell-padding-inline)));justify-self:end;align-self:center;border-radius:var(--sci-corner);padding:1px;background-color:var(--\\275sci-table-row-action-background-color);z-index:2;--sci-toolbar-item-background-color-hover: color-mix(in srgb, var(--\\275sci-table-row-action-background-color) 85%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)));--sci-toolbar-item-background-color-active: color-mix(in srgb, var(--\\275sci-table-row-action-background-color) 75%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)))}@container not style(--\\275sci-table-row-selected: true){:host>sci-toolbar.row-actions{--\\275sci-table-row-action-background-color: var(--sci-table-row-action-background-color)}}@container style(--\\275sci-table-row-selected: true){:host>sci-toolbar.row-actions{--\\275sci-table-row-action-background-color: var(--sci-table-row-action-background-color-selected)}}:host>sci-toolbar.row-actions:empty{display:none}\n"] }]
1527
1808
  }], ctorParameters: () => [], propDecorators: { row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: true }] }], primaryAction: [{ type: i0.Output, args: ["primaryAction"] }], rowActionToolbar: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SciToolbarComponent), { isSignal: true }] }] } });
1528
1809
 
1529
1810
  /*
@@ -2214,7 +2495,7 @@ class SciTableComponent {
2214
2495
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciTableComponent, isStandalone: true, selector: "sci-table", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, table: { classPropertyName: "table", publicName: "table", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { primaryAction: "primaryAction" }, host: { properties: { "attr.name": "name()", "style.--\u0275sci-table-scrolling": "table().scrolling() ? `true` : null", "style.--\u0275sci-table-resizing": "table().resizing() ? `true` : null" } }, providers: [
2215
2496
  provideSciTable(),
2216
2497
  SciTableSelectionService,
2217
- ], viewQueries: [{ propertyName: "_viewport", first: true, predicate: SciTableViewportComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_viewportClient", first: true, predicate: SciTableGridComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_tableHeader", first: true, predicate: SciTableHeaderComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_tableBody", first: true, predicate: SciTableBodyComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_itemSizeSyntheticElement", first: true, predicate: ["item_size_synthetic_element"], descendants: true, isSignal: true }, { propertyName: "_cellPaddingSyntheticElement", first: true, predicate: ["cell_padding_synthetic_element"], descendants: true, isSignal: true }, { propertyName: "rows", predicate: SciTableRowComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@let table = this.table();\n\n<sci-table-viewport #viewport>\n <sci-table-grid>\n <!-- Table header -->\n @if (table.showHeader() || table.filterable()) {\n <sci-table-header/>\n }\n\n <!-- Table body -->\n <sci-table-body sciTableKeyboardNavigator>\n @if (!table.error()) {\n <!-- Track index to reuse the table row elements -->\n @for (row of table.rows(); track $index) {\n <sci-table-row [row]=\"row\" (primaryAction)=\"onRowPrimaryAction($event)\" [sciAttributes]=\"row.bindings?.attributes()\"/>\n } @empty {\n <span class=\"message e2e-no-data\">{{('%scion.components.no_items.message' | sciText)()}}</span>\n }\n } @else {\n <span class=\"message e2e-datasource\">\n {{('%scion.components.datasource_error.message' | sciText)()}}\n\n <button (click)=\"onRetry()\" class=\"action e2e-datasource-retry\">\n <!-- TODO [dwi] Replace with SCION icon -->\n <sci-icon>refresh</sci-icon>\n <span>{{('%scion.components.retry.label' | sciText)()}}</span>\n </button>\n </span>\n }\n\n <!-- Render active row outline separately to preserve row border-radius within block selection. -->\n @if (table.activeRow()) {\n <div class=\"active-row-outline e2e-active-row-outline\"></div>\n }\n </sci-table-body>\n\n <!-- Render column splitters. -->\n <sci-table-columns [rows]=\"rows()\"/>\n </sci-table-grid>\n</sci-table-viewport>\n\n<!-- Scrollbars -->\n<sci-scrollbar direction=\"hscroll\" [viewport]=\"viewport.host\"/>\n<sci-scrollbar direction=\"vscroll\" [viewport]=\"viewport.host\"/>\n\n@if (table.loading()) {\n <sci-throbber type=\"spinner\"/>\n}\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-row-height`, relative to the table's font size if em-based. -->\n<div class=\"synthetic item-size\" #item_size_synthetic_element></div>\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-cell-padding-inline`, relative to the table's font size if em-based. -->\n<div class=\"synthetic cell-padding\" #cell_padding_synthetic_element></div>\n", styles: ["@charset \"UTF-8\";:host{display:block;overflow:hidden;--\\275sci-table-border-size: 1px;--\\275sci-table-border-color: var(--sci-color-border)}:host:is(:not(:hover):not(:active),:host(:not(:hover):not(:active))) sci-scrollbar:not(.scrolling){opacity:0}:host>sci-table-viewport{display:block;position:relative;height:100%;overflow:auto;scrollbar-width:none;isolation:isolate;box-sizing:border-box;anchor-name:--sci-table-viewport}:host>sci-table-viewport:has(sci-table-header){--\\275sci-table-header-visible: true}:host>sci-table-viewport:has(sci-table-header>sci-column-header){--\\275sci-column-header-visible: true}:host>sci-table-viewport:has(~sci-scrollbar.scrolling){--\\275sci-table-scrolling: true}:host>sci-table-viewport>sci-table-grid{display:grid;grid-template-columns:var(--\\275sci-table-columns, repeat(auto-fit, 0)) 0;grid-auto-rows:min-content;grid-template-rows:min-content 1fr;width:min-content;min-width:100%;min-height:100%}@container not style(--\\275sci-table-header-visible: true){:host>sci-table-viewport>sci-table-grid{grid-template-rows:1fr}}:host>sci-table-viewport>sci-table-grid>sci-table-header{z-index:1;grid-row:1;grid-column:1/-1;position:sticky;top:0}:host>sci-table-viewport>sci-table-grid>sci-table-body{anchor-name:--sci-table-body;grid-row:2}@container not style(--\\275sci-table-header-visible: true){:host>sci-table-viewport>sci-table-grid>sci-table-body{grid-row:1}}:host>sci-table-viewport>sci-table-grid>sci-table-body{display:grid;grid-template-columns:subgrid;grid-column:1/-1;grid-auto-rows:var(--sci-table-row-height);outline:none;padding-top:var(--\\275sci-table-virtual-scroll-offset-top);padding-bottom:var(--\\275sci-table-virtual-scroll-offset-bottom)}:host>sci-table-viewport>sci-table-grid>sci-table-body>sci-table-row{grid-column:1/-1}:host>sci-table-viewport>sci-table-grid>sci-table-body>sci-table-row[data-active]{anchor-name:--sci-table-active-row}:host>sci-table-viewport>sci-table-grid>sci-table-body>div.active-row-outline{position:absolute;top:anchor(--sci-table-active-row top);right:anchor(--sci-table-active-row right);bottom:anchor(--sci-table-active-row bottom);left:anchor(--sci-table-active-row left);border:var(--sci-table-row-outline-width) var(--sci-table-row-outline-style) var(--sci-table-row-outline-color);border-radius:var(--sci-table-row-border-radius);pointer-events:none}:host>sci-table-viewport>sci-table-grid>sci-table-body:not(:has(sci-table-row[data-active]))>div.active-row-outline{display:none}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message{grid-column:1/-1;grid-row:auto/span 2;justify-self:center;align-self:center;display:flex;flex-direction:column;align-items:center;gap:1em;-webkit-user-select:none;user-select:none;white-space:nowrap;color:var(--sci-color-text-subtle)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message:has(button.action){grid-row:auto/span 4}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action{all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-button-border-radius);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-button-cursor);--sci-icon-size: var(--sci-button-icon-size)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:hover:where(:not(:disabled)){background-color:var(--sci-button-background-color-hover)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:active:where(:not(:disabled)){background-color:var(--sci-button-background-color-active)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:focus:not(:focus-visible){outline:none}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:focus-visible{outline:var(--sci-button-outline-width) solid var(--sci-color-accent)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:disabled{color:var(--sci-button-text-color-disabled)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action{border:1px solid var(--sci-color-border);padding:.25em 1em}@container style(--\\275sci-table-viewport-client-width > --\\275sci-table-viewport-width){:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message{position:sticky;left:50%;right:50%;width:0}}:host>sci-table-viewport>sci-table-grid>sci-table-body:has(>span.message){z-index:2}:host>sci-table-viewport>sci-table-grid>sci-table-columns{grid-area:1/1/-1/-1;z-index:1;position:sticky;top:0;max-height:var(--\\275sci-table-viewport-height)}:host>sci-scrollbar{position:absolute}:host>sci-scrollbar[direction=vscroll]{top:calc(anchor(--sci-table-body top) + 2px);right:calc(anchor(--sci-table-viewport right) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 2px)}:host>sci-scrollbar[direction=hscroll]{left:calc(anchor(--sci-table-viewport left) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 1px);right:calc(anchor(--sci-table-viewport right) + 2px)}@container style(--\\275sci-table-resizing: true){:host>sci-scrollbar{pointer-events:none}}:host>sci-throbber{--sci-throbber-size: 16px;position:absolute;top:calc(anchor(--sci-table-viewport top) + .5em);right:calc(anchor(--sci-table-viewport right) + .5em)}:host>div.synthetic{position:absolute;pointer-events:none;visibility:hidden}:host>div.synthetic.item-size{height:var(--sci-table-row-height)}:host>div.synthetic.cell-padding{width:var(--sci-table-cell-padding-inline)}\n"], dependencies: [{ kind: "component", type: SciTableViewportComponent, selector: "sci-table-viewport" }, { kind: "component", type: SciTableHeaderComponent, selector: "sci-table-header" }, { kind: "component", type: SciTableGridComponent, selector: "sci-table-grid" }, { kind: "component", type: SciTableBodyComponent, selector: "sci-table-body" }, { kind: "component", type: SciTableRowComponent, selector: "sci-table-row", inputs: ["row"], outputs: ["primaryAction"] }, { kind: "component", type: SciTableColumnsComponent, selector: "sci-table-columns", inputs: ["rows"] }, { kind: "directive", type: SciTableKeyboardNavigatorDirective, selector: "[sciTableKeyboardNavigator]" }, { kind: "directive", type: SciAttributesDirective, selector: "[sciAttributes]", inputs: ["sciAttributes"] }, { kind: "component", type: SciScrollbarComponent, selector: "sci-scrollbar", inputs: ["direction", "viewport"] }, { kind: "component", type: SciThrobberComponent, selector: "sci-throbber", inputs: ["type"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "pipe", type: SciTextPipe, name: "sciText" }], encapsulation: i0.ViewEncapsulation.ShadowDom });
2498
+ ], viewQueries: [{ propertyName: "_viewport", first: true, predicate: SciTableViewportComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_viewportClient", first: true, predicate: SciTableGridComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_tableHeader", first: true, predicate: SciTableHeaderComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_tableBody", first: true, predicate: SciTableBodyComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_itemSizeSyntheticElement", first: true, predicate: ["item_size_synthetic_element"], descendants: true, isSignal: true }, { propertyName: "_cellPaddingSyntheticElement", first: true, predicate: ["cell_padding_synthetic_element"], descendants: true, isSignal: true }, { propertyName: "rows", predicate: SciTableRowComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@let table = this.table();\n\n<sci-table-viewport #viewport>\n <sci-table-grid>\n <!-- Table header -->\n @if (table.showHeader() || table.filterable()) {\n <sci-table-header/>\n }\n\n <!-- Table body -->\n <sci-table-body sciTableKeyboardNavigator>\n @if (!table.error()) {\n <!-- Track index to reuse the table row elements -->\n @for (row of table.rows(); track $index) {\n <sci-table-row [row]=\"row\" (primaryAction)=\"onRowPrimaryAction($event)\" [sciAttributes]=\"row.bindings?.attributes()\"/>\n } @empty {\n <span class=\"message e2e-no-data\">{{('%scion.components.no_items.message' | sciText)()}}</span>\n }\n } @else {\n <span class=\"message e2e-datasource\">\n {{('%scion.components.datasource_error.message' | sciText)()}}\n\n <button (click)=\"onRetry()\" class=\"action e2e-datasource-retry\">\n <!-- TODO [dwi] Replace with SCION icon -->\n <sci-icon>refresh</sci-icon>\n <span>{{('%scion.components.retry.label' | sciText)()}}</span>\n </button>\n </span>\n }\n\n <!-- Render active row outline separately to preserve row border-radius within block selection. -->\n @if (table.activeRow()) {\n <div class=\"active-row-outline e2e-active-row-outline\"></div>\n }\n </sci-table-body>\n\n <!-- Render column splitters. -->\n <sci-table-columns [rows]=\"rows()\"/>\n </sci-table-grid>\n</sci-table-viewport>\n\n<!-- Scrollbars -->\n<sci-scrollbar direction=\"hscroll\" [viewport]=\"viewport.host\"/>\n<sci-scrollbar direction=\"vscroll\" [viewport]=\"viewport.host\"/>\n\n@if (table.loading()) {\n <sci-throbber type=\"spinner\" class=\"loading-indicator e2e-loading-indicator\"/>\n}\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-row-height`, relative to the table's font size if em-based. -->\n<div class=\"synthetic item-size\" #item_size_synthetic_element></div>\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-cell-padding-inline`, relative to the table's font size if em-based. -->\n<div class=\"synthetic cell-padding\" #cell_padding_synthetic_element></div>\n", styles: ["@charset \"UTF-8\";:host{display:block;overflow:hidden;--\\275sci-table-border-size: 1px;--\\275sci-table-border-color: var(--sci-color-border)}:host:is(:not(:hover):not(:active),:host(:not(:hover):not(:active))) sci-scrollbar:not(.scrolling){opacity:0}:host>sci-table-viewport{display:block;position:relative;height:100%;overflow:auto;scrollbar-width:none;isolation:isolate;box-sizing:border-box;anchor-name:--sci-table-viewport}:host>sci-table-viewport:has(sci-table-header){--\\275sci-table-header-visible: true}:host>sci-table-viewport:has(sci-table-header>sci-column-header){--\\275sci-column-header-visible: true}:host>sci-table-viewport:has(~sci-scrollbar.scrolling){--\\275sci-table-scrolling: true}:host>sci-table-viewport>sci-table-grid{display:grid;grid-template-columns:var(--\\275sci-table-columns, repeat(auto-fit, 100px)) 0;grid-auto-rows:min-content;grid-template-rows:min-content 1fr;width:min-content;min-width:100%;min-height:100%}@container not style(--\\275sci-table-header-visible: true){:host>sci-table-viewport>sci-table-grid{grid-template-rows:1fr}}:host>sci-table-viewport>sci-table-grid>sci-table-header{z-index:1;grid-row:1;grid-column:1/-1;position:sticky;top:0}:host>sci-table-viewport>sci-table-grid>sci-table-body{anchor-name:--sci-table-body;grid-row:2}@container not style(--\\275sci-table-header-visible: true){:host>sci-table-viewport>sci-table-grid>sci-table-body{grid-row:1}}:host>sci-table-viewport>sci-table-grid>sci-table-body{display:grid;grid-template-columns:subgrid;grid-column:1/-1;grid-auto-rows:var(--sci-table-row-height);outline:none;padding-top:var(--\\275sci-table-virtual-scroll-offset-top);padding-bottom:var(--\\275sci-table-virtual-scroll-offset-bottom)}:host>sci-table-viewport>sci-table-grid>sci-table-body>sci-table-row{grid-column:1/-1}:host>sci-table-viewport>sci-table-grid>sci-table-body>sci-table-row[data-active]{anchor-name:--sci-table-active-row}:host>sci-table-viewport>sci-table-grid>sci-table-body>div.active-row-outline{position:absolute;top:anchor(--sci-table-active-row top);right:anchor(--sci-table-active-row right);bottom:anchor(--sci-table-active-row bottom);left:anchor(--sci-table-active-row left);border:var(--sci-table-row-outline-width) var(--sci-table-row-outline-style) var(--sci-table-row-outline-color);border-radius:var(--sci-table-row-border-radius);pointer-events:none}:host>sci-table-viewport>sci-table-grid>sci-table-body:not(:has(sci-table-row[data-active]))>div.active-row-outline{display:none}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message{grid-column:1/-1;grid-row:auto/span 2;justify-self:center;align-self:center;display:flex;flex-direction:column;align-items:center;gap:1em;-webkit-user-select:none;user-select:none;white-space:nowrap;color:var(--sci-color-text-subtle)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message:has(button.action){grid-row:auto/span 4}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action{all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-button-border-radius);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-button-cursor);--sci-icon-size: var(--sci-button-icon-size)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:hover:where(:not(:disabled)){background-color:var(--sci-button-background-color-hover)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:active:where(:not(:disabled)){background-color:var(--sci-button-background-color-active)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:focus:not(:focus-visible){outline:none}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:focus-visible{outline:var(--sci-button-outline-width) solid var(--sci-color-accent)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:disabled{color:var(--sci-button-text-color-disabled)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action{border:1px solid var(--sci-color-border);padding:.25em 1em}@container style(--\\275sci-table-viewport-client-width > --\\275sci-table-viewport-width){:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message{position:sticky;left:50%;right:50%;width:0}}:host>sci-table-viewport>sci-table-grid>sci-table-body:has(>span.message){z-index:2}:host>sci-table-viewport>sci-table-grid>sci-table-columns{grid-area:1/1/-1/-1;z-index:1;position:sticky;top:0;max-height:var(--\\275sci-table-viewport-height)}:host>sci-scrollbar{position:absolute}:host>sci-scrollbar[direction=vscroll]{top:calc(anchor(--sci-table-body top) + 2px);right:calc(anchor(--sci-table-viewport right) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 2px)}:host>sci-scrollbar[direction=hscroll]{left:calc(anchor(--sci-table-viewport left) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 1px);right:calc(anchor(--sci-table-viewport right) + 2px)}@container style(--\\275sci-table-resizing: true){:host>sci-scrollbar{pointer-events:none}}:host>sci-throbber.loading-indicator{--sci-throbber-size: 16px;position:absolute;top:calc(anchor(--sci-table-viewport top) + .5em);right:calc(anchor(--sci-table-viewport right) + .5em)}:host>div.synthetic{position:absolute;pointer-events:none;visibility:hidden}:host>div.synthetic.item-size{height:var(--sci-table-row-height)}:host>div.synthetic.cell-padding{width:var(--sci-table-cell-padding-inline)}\n"], dependencies: [{ kind: "component", type: SciTableViewportComponent, selector: "sci-table-viewport" }, { kind: "component", type: SciTableHeaderComponent, selector: "sci-table-header" }, { kind: "component", type: SciTableGridComponent, selector: "sci-table-grid" }, { kind: "component", type: SciTableBodyComponent, selector: "sci-table-body" }, { kind: "component", type: SciTableRowComponent, selector: "sci-table-row", inputs: ["row"], outputs: ["primaryAction"] }, { kind: "component", type: SciTableColumnsComponent, selector: "sci-table-columns", inputs: ["rows"] }, { kind: "directive", type: SciTableKeyboardNavigatorDirective, selector: "[sciTableKeyboardNavigator]" }, { kind: "directive", type: SciAttributesDirective, selector: "[sciAttributes]", inputs: ["sciAttributes"] }, { kind: "component", type: SciScrollbarComponent, selector: "sci-scrollbar", inputs: ["direction", "viewport"] }, { kind: "component", type: SciThrobberComponent, selector: "sci-throbber", inputs: ["type"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "pipe", type: SciTextPipe, name: "sciText" }], encapsulation: i0.ViewEncapsulation.ShadowDom });
2218
2499
  }
2219
2500
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableComponent, decorators: [{
2220
2501
  type: Component,
@@ -2238,7 +2519,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
2238
2519
  ], providers: [
2239
2520
  provideSciTable(),
2240
2521
  SciTableSelectionService,
2241
- ], template: "@let table = this.table();\n\n<sci-table-viewport #viewport>\n <sci-table-grid>\n <!-- Table header -->\n @if (table.showHeader() || table.filterable()) {\n <sci-table-header/>\n }\n\n <!-- Table body -->\n <sci-table-body sciTableKeyboardNavigator>\n @if (!table.error()) {\n <!-- Track index to reuse the table row elements -->\n @for (row of table.rows(); track $index) {\n <sci-table-row [row]=\"row\" (primaryAction)=\"onRowPrimaryAction($event)\" [sciAttributes]=\"row.bindings?.attributes()\"/>\n } @empty {\n <span class=\"message e2e-no-data\">{{('%scion.components.no_items.message' | sciText)()}}</span>\n }\n } @else {\n <span class=\"message e2e-datasource\">\n {{('%scion.components.datasource_error.message' | sciText)()}}\n\n <button (click)=\"onRetry()\" class=\"action e2e-datasource-retry\">\n <!-- TODO [dwi] Replace with SCION icon -->\n <sci-icon>refresh</sci-icon>\n <span>{{('%scion.components.retry.label' | sciText)()}}</span>\n </button>\n </span>\n }\n\n <!-- Render active row outline separately to preserve row border-radius within block selection. -->\n @if (table.activeRow()) {\n <div class=\"active-row-outline e2e-active-row-outline\"></div>\n }\n </sci-table-body>\n\n <!-- Render column splitters. -->\n <sci-table-columns [rows]=\"rows()\"/>\n </sci-table-grid>\n</sci-table-viewport>\n\n<!-- Scrollbars -->\n<sci-scrollbar direction=\"hscroll\" [viewport]=\"viewport.host\"/>\n<sci-scrollbar direction=\"vscroll\" [viewport]=\"viewport.host\"/>\n\n@if (table.loading()) {\n <sci-throbber type=\"spinner\"/>\n}\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-row-height`, relative to the table's font size if em-based. -->\n<div class=\"synthetic item-size\" #item_size_synthetic_element></div>\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-cell-padding-inline`, relative to the table's font size if em-based. -->\n<div class=\"synthetic cell-padding\" #cell_padding_synthetic_element></div>\n", styles: ["@charset \"UTF-8\";:host{display:block;overflow:hidden;--\\275sci-table-border-size: 1px;--\\275sci-table-border-color: var(--sci-color-border)}:host:is(:not(:hover):not(:active),:host(:not(:hover):not(:active))) sci-scrollbar:not(.scrolling){opacity:0}:host>sci-table-viewport{display:block;position:relative;height:100%;overflow:auto;scrollbar-width:none;isolation:isolate;box-sizing:border-box;anchor-name:--sci-table-viewport}:host>sci-table-viewport:has(sci-table-header){--\\275sci-table-header-visible: true}:host>sci-table-viewport:has(sci-table-header>sci-column-header){--\\275sci-column-header-visible: true}:host>sci-table-viewport:has(~sci-scrollbar.scrolling){--\\275sci-table-scrolling: true}:host>sci-table-viewport>sci-table-grid{display:grid;grid-template-columns:var(--\\275sci-table-columns, repeat(auto-fit, 0)) 0;grid-auto-rows:min-content;grid-template-rows:min-content 1fr;width:min-content;min-width:100%;min-height:100%}@container not style(--\\275sci-table-header-visible: true){:host>sci-table-viewport>sci-table-grid{grid-template-rows:1fr}}:host>sci-table-viewport>sci-table-grid>sci-table-header{z-index:1;grid-row:1;grid-column:1/-1;position:sticky;top:0}:host>sci-table-viewport>sci-table-grid>sci-table-body{anchor-name:--sci-table-body;grid-row:2}@container not style(--\\275sci-table-header-visible: true){:host>sci-table-viewport>sci-table-grid>sci-table-body{grid-row:1}}:host>sci-table-viewport>sci-table-grid>sci-table-body{display:grid;grid-template-columns:subgrid;grid-column:1/-1;grid-auto-rows:var(--sci-table-row-height);outline:none;padding-top:var(--\\275sci-table-virtual-scroll-offset-top);padding-bottom:var(--\\275sci-table-virtual-scroll-offset-bottom)}:host>sci-table-viewport>sci-table-grid>sci-table-body>sci-table-row{grid-column:1/-1}:host>sci-table-viewport>sci-table-grid>sci-table-body>sci-table-row[data-active]{anchor-name:--sci-table-active-row}:host>sci-table-viewport>sci-table-grid>sci-table-body>div.active-row-outline{position:absolute;top:anchor(--sci-table-active-row top);right:anchor(--sci-table-active-row right);bottom:anchor(--sci-table-active-row bottom);left:anchor(--sci-table-active-row left);border:var(--sci-table-row-outline-width) var(--sci-table-row-outline-style) var(--sci-table-row-outline-color);border-radius:var(--sci-table-row-border-radius);pointer-events:none}:host>sci-table-viewport>sci-table-grid>sci-table-body:not(:has(sci-table-row[data-active]))>div.active-row-outline{display:none}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message{grid-column:1/-1;grid-row:auto/span 2;justify-self:center;align-self:center;display:flex;flex-direction:column;align-items:center;gap:1em;-webkit-user-select:none;user-select:none;white-space:nowrap;color:var(--sci-color-text-subtle)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message:has(button.action){grid-row:auto/span 4}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action{all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-button-border-radius);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-button-cursor);--sci-icon-size: var(--sci-button-icon-size)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:hover:where(:not(:disabled)){background-color:var(--sci-button-background-color-hover)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:active:where(:not(:disabled)){background-color:var(--sci-button-background-color-active)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:focus:not(:focus-visible){outline:none}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:focus-visible{outline:var(--sci-button-outline-width) solid var(--sci-color-accent)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:disabled{color:var(--sci-button-text-color-disabled)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action{border:1px solid var(--sci-color-border);padding:.25em 1em}@container style(--\\275sci-table-viewport-client-width > --\\275sci-table-viewport-width){:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message{position:sticky;left:50%;right:50%;width:0}}:host>sci-table-viewport>sci-table-grid>sci-table-body:has(>span.message){z-index:2}:host>sci-table-viewport>sci-table-grid>sci-table-columns{grid-area:1/1/-1/-1;z-index:1;position:sticky;top:0;max-height:var(--\\275sci-table-viewport-height)}:host>sci-scrollbar{position:absolute}:host>sci-scrollbar[direction=vscroll]{top:calc(anchor(--sci-table-body top) + 2px);right:calc(anchor(--sci-table-viewport right) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 2px)}:host>sci-scrollbar[direction=hscroll]{left:calc(anchor(--sci-table-viewport left) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 1px);right:calc(anchor(--sci-table-viewport right) + 2px)}@container style(--\\275sci-table-resizing: true){:host>sci-scrollbar{pointer-events:none}}:host>sci-throbber{--sci-throbber-size: 16px;position:absolute;top:calc(anchor(--sci-table-viewport top) + .5em);right:calc(anchor(--sci-table-viewport right) + .5em)}:host>div.synthetic{position:absolute;pointer-events:none;visibility:hidden}:host>div.synthetic.item-size{height:var(--sci-table-row-height)}:host>div.synthetic.cell-padding{width:var(--sci-table-cell-padding-inline)}\n"] }]
2522
+ ], template: "@let table = this.table();\n\n<sci-table-viewport #viewport>\n <sci-table-grid>\n <!-- Table header -->\n @if (table.showHeader() || table.filterable()) {\n <sci-table-header/>\n }\n\n <!-- Table body -->\n <sci-table-body sciTableKeyboardNavigator>\n @if (!table.error()) {\n <!-- Track index to reuse the table row elements -->\n @for (row of table.rows(); track $index) {\n <sci-table-row [row]=\"row\" (primaryAction)=\"onRowPrimaryAction($event)\" [sciAttributes]=\"row.bindings?.attributes()\"/>\n } @empty {\n <span class=\"message e2e-no-data\">{{('%scion.components.no_items.message' | sciText)()}}</span>\n }\n } @else {\n <span class=\"message e2e-datasource\">\n {{('%scion.components.datasource_error.message' | sciText)()}}\n\n <button (click)=\"onRetry()\" class=\"action e2e-datasource-retry\">\n <!-- TODO [dwi] Replace with SCION icon -->\n <sci-icon>refresh</sci-icon>\n <span>{{('%scion.components.retry.label' | sciText)()}}</span>\n </button>\n </span>\n }\n\n <!-- Render active row outline separately to preserve row border-radius within block selection. -->\n @if (table.activeRow()) {\n <div class=\"active-row-outline e2e-active-row-outline\"></div>\n }\n </sci-table-body>\n\n <!-- Render column splitters. -->\n <sci-table-columns [rows]=\"rows()\"/>\n </sci-table-grid>\n</sci-table-viewport>\n\n<!-- Scrollbars -->\n<sci-scrollbar direction=\"hscroll\" [viewport]=\"viewport.host\"/>\n<sci-scrollbar direction=\"vscroll\" [viewport]=\"viewport.host\"/>\n\n@if (table.loading()) {\n <sci-throbber type=\"spinner\" class=\"loading-indicator e2e-loading-indicator\"/>\n}\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-row-height`, relative to the table's font size if em-based. -->\n<div class=\"synthetic item-size\" #item_size_synthetic_element></div>\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-cell-padding-inline`, relative to the table's font size if em-based. -->\n<div class=\"synthetic cell-padding\" #cell_padding_synthetic_element></div>\n", styles: ["@charset \"UTF-8\";:host{display:block;overflow:hidden;--\\275sci-table-border-size: 1px;--\\275sci-table-border-color: var(--sci-color-border)}:host:is(:not(:hover):not(:active),:host(:not(:hover):not(:active))) sci-scrollbar:not(.scrolling){opacity:0}:host>sci-table-viewport{display:block;position:relative;height:100%;overflow:auto;scrollbar-width:none;isolation:isolate;box-sizing:border-box;anchor-name:--sci-table-viewport}:host>sci-table-viewport:has(sci-table-header){--\\275sci-table-header-visible: true}:host>sci-table-viewport:has(sci-table-header>sci-column-header){--\\275sci-column-header-visible: true}:host>sci-table-viewport:has(~sci-scrollbar.scrolling){--\\275sci-table-scrolling: true}:host>sci-table-viewport>sci-table-grid{display:grid;grid-template-columns:var(--\\275sci-table-columns, repeat(auto-fit, 100px)) 0;grid-auto-rows:min-content;grid-template-rows:min-content 1fr;width:min-content;min-width:100%;min-height:100%}@container not style(--\\275sci-table-header-visible: true){:host>sci-table-viewport>sci-table-grid{grid-template-rows:1fr}}:host>sci-table-viewport>sci-table-grid>sci-table-header{z-index:1;grid-row:1;grid-column:1/-1;position:sticky;top:0}:host>sci-table-viewport>sci-table-grid>sci-table-body{anchor-name:--sci-table-body;grid-row:2}@container not style(--\\275sci-table-header-visible: true){:host>sci-table-viewport>sci-table-grid>sci-table-body{grid-row:1}}:host>sci-table-viewport>sci-table-grid>sci-table-body{display:grid;grid-template-columns:subgrid;grid-column:1/-1;grid-auto-rows:var(--sci-table-row-height);outline:none;padding-top:var(--\\275sci-table-virtual-scroll-offset-top);padding-bottom:var(--\\275sci-table-virtual-scroll-offset-bottom)}:host>sci-table-viewport>sci-table-grid>sci-table-body>sci-table-row{grid-column:1/-1}:host>sci-table-viewport>sci-table-grid>sci-table-body>sci-table-row[data-active]{anchor-name:--sci-table-active-row}:host>sci-table-viewport>sci-table-grid>sci-table-body>div.active-row-outline{position:absolute;top:anchor(--sci-table-active-row top);right:anchor(--sci-table-active-row right);bottom:anchor(--sci-table-active-row bottom);left:anchor(--sci-table-active-row left);border:var(--sci-table-row-outline-width) var(--sci-table-row-outline-style) var(--sci-table-row-outline-color);border-radius:var(--sci-table-row-border-radius);pointer-events:none}:host>sci-table-viewport>sci-table-grid>sci-table-body:not(:has(sci-table-row[data-active]))>div.active-row-outline{display:none}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message{grid-column:1/-1;grid-row:auto/span 2;justify-self:center;align-self:center;display:flex;flex-direction:column;align-items:center;gap:1em;-webkit-user-select:none;user-select:none;white-space:nowrap;color:var(--sci-color-text-subtle)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message:has(button.action){grid-row:auto/span 4}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action{all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-button-border-radius);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-button-cursor);--sci-icon-size: var(--sci-button-icon-size)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:hover:where(:not(:disabled)){background-color:var(--sci-button-background-color-hover)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:active:where(:not(:disabled)){background-color:var(--sci-button-background-color-active)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:focus:not(:focus-visible){outline:none}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:focus-visible{outline:var(--sci-button-outline-width) solid var(--sci-color-accent)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action:disabled{color:var(--sci-button-text-color-disabled)}:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message>button.action{border:1px solid var(--sci-color-border);padding:.25em 1em}@container style(--\\275sci-table-viewport-client-width > --\\275sci-table-viewport-width){:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message{position:sticky;left:50%;right:50%;width:0}}:host>sci-table-viewport>sci-table-grid>sci-table-body:has(>span.message){z-index:2}:host>sci-table-viewport>sci-table-grid>sci-table-columns{grid-area:1/1/-1/-1;z-index:1;position:sticky;top:0;max-height:var(--\\275sci-table-viewport-height)}:host>sci-scrollbar{position:absolute}:host>sci-scrollbar[direction=vscroll]{top:calc(anchor(--sci-table-body top) + 2px);right:calc(anchor(--sci-table-viewport right) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 2px)}:host>sci-scrollbar[direction=hscroll]{left:calc(anchor(--sci-table-viewport left) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 1px);right:calc(anchor(--sci-table-viewport right) + 2px)}@container style(--\\275sci-table-resizing: true){:host>sci-scrollbar{pointer-events:none}}:host>sci-throbber.loading-indicator{--sci-throbber-size: 16px;position:absolute;top:calc(anchor(--sci-table-viewport top) + .5em);right:calc(anchor(--sci-table-viewport right) + .5em)}:host>div.synthetic{position:absolute;pointer-events:none;visibility:hidden}:host>div.synthetic.item-size{height:var(--sci-table-row-height)}:host>div.synthetic.cell-padding{width:var(--sci-table-cell-padding-inline)}\n"] }]
2242
2523
  }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], table: [{ type: i0.Input, args: [{ isSignal: true, alias: "table", required: true }] }], primaryAction: [{ type: i0.Output, args: ["primaryAction"] }], _viewport: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SciTableViewportComponent), { ...{ read: ElementRef }, isSignal: true }] }], _viewportClient: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SciTableGridComponent), { ...{ read: ElementRef }, isSignal: true }] }], _tableHeader: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SciTableHeaderComponent), { ...{ read: ElementRef }, isSignal: true }] }], _tableBody: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SciTableBodyComponent), { ...{ read: ElementRef }, isSignal: true }] }], _itemSizeSyntheticElement: [{ type: i0.ViewChild, args: ['item_size_synthetic_element', { isSignal: true }] }], _cellPaddingSyntheticElement: [{ type: i0.ViewChild, args: ['cell_padding_synthetic_element', { isSignal: true }] }], rows: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => SciTableRowComponent), { isSignal: true }] }] } });
2243
2524
  function provideSciTable() {
2244
2525
  return {