@scion/components 22.1.0 → 22.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/_index.scss +75 -5
  2. package/common/package.json +4 -0
  3. package/design/_theming.scss +25 -10
  4. package/design/components/_scion-component-dark-tokens.scss +153 -0
  5. package/design/components/_scion-component-light-tokens.scss +153 -0
  6. package/design/icons/_scion-icon-font.scss +50 -0
  7. package/design/themes/_scion-dark-theme.scss +2 -7
  8. package/design/themes/_scion-light-theme.scss +3 -8
  9. package/fesm2022/scion-components-common.mjs +251 -0
  10. package/fesm2022/scion-components-common.mjs.map +1 -0
  11. package/fesm2022/scion-components-dimension.mjs +7 -6
  12. package/fesm2022/scion-components-dimension.mjs.map +1 -1
  13. package/fesm2022/scion-components-icon.mjs +380 -0
  14. package/fesm2022/scion-components-icon.mjs.map +1 -0
  15. package/fesm2022/scion-components-menu.mjs +3271 -0
  16. package/fesm2022/scion-components-menu.mjs.map +1 -0
  17. package/fesm2022/scion-components-sashbox.mjs +21 -21
  18. package/fesm2022/scion-components-sashbox.mjs.map +1 -1
  19. package/fesm2022/scion-components-splitter.mjs +16 -13
  20. package/fesm2022/scion-components-splitter.mjs.map +1 -1
  21. package/fesm2022/scion-components-table.mjs +2265 -0
  22. package/fesm2022/scion-components-table.mjs.map +1 -0
  23. package/fesm2022/scion-components-text.mjs +322 -0
  24. package/fesm2022/scion-components-text.mjs.map +1 -0
  25. package/fesm2022/scion-components-throbber.mjs +15 -15
  26. package/fesm2022/scion-components-throbber.mjs.map +1 -1
  27. package/fesm2022/scion-components-viewport.mjs +142 -125
  28. package/fesm2022/scion-components-viewport.mjs.map +1 -1
  29. package/icon/package.json +4 -0
  30. package/menu/package.json +4 -0
  31. package/package.json +22 -2
  32. package/table/package.json +4 -0
  33. package/text/package.json +4 -0
  34. package/types/scion-components-common.d.ts +185 -0
  35. package/types/scion-components-icon.d.ts +146 -0
  36. package/types/scion-components-menu.d.ts +1922 -0
  37. package/types/scion-components-sashbox.d.ts +4 -2
  38. package/types/scion-components-splitter.d.ts +4 -3
  39. package/types/scion-components-table.d.ts +577 -0
  40. package/types/scion-components-text.d.ts +131 -0
  41. package/types/scion-components-viewport.d.ts +32 -39
  42. package/viewport/scrollbar/_scrollbar.scss +4 -4
  43. package/design/components/_scion-component-tokens.scss +0 -48
@@ -0,0 +1,2265 @@
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';
4
+ 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
+ import { Objects, clamp, runSafe, Observables, Arrays } from '@scion/toolkit/util';
7
+ import { map as map$1, startWith, first } from 'rxjs/operators';
8
+ import { subscribeIn } from '@scion/toolkit/operators';
9
+ import { SciScrollbarComponent } from '@scion/components/viewport';
10
+ import { dimension } from '@scion/components/dimension';
11
+ import { NgTemplateOutlet } from '@angular/common';
12
+ import { SciIconComponent } from '@scion/components/icon';
13
+ import { SciToolbarComponent, contributeMenu, ɵSciMenuService as _SciMenuService } from '@scion/components/menu';
14
+ import { UUID } from '@scion/toolkit/uuid';
15
+ import { SciSplitterComponent } from '@scion/components/splitter';
16
+ import { text, SciTextPipe } from '@scion/components/text';
17
+ import { SciThrobberComponent } from '@scion/components/throbber';
18
+ import * as i1 from '@angular/forms';
19
+ import { FormsModule } from '@angular/forms';
20
+
21
+ /*
22
+ * Copyright (c) 2018-2026 Swiss Federal Railways
23
+ *
24
+ * This program and the accompanying materials are made
25
+ * available under the terms of the Eclipse Public License 2.0
26
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
27
+ *
28
+ * SPDX-License-Identifier: EPL-2.0
29
+ */
30
+ class ɵSciTableColumnFactory {
31
+ _descriptor;
32
+ columns = new Array();
33
+ constructor(_descriptor) {
34
+ this._descriptor = _descriptor;
35
+ }
36
+ addBooleanColumn(valueHeaderDescriptor, value) {
37
+ return this.addColumn('boolean', valueHeaderDescriptor, value);
38
+ }
39
+ addStringColumn(valueHeaderDescriptor, value) {
40
+ return this.addColumn('string', valueHeaderDescriptor, value);
41
+ }
42
+ addNumberColumn(valueHeaderDescriptor, value) {
43
+ return this.addColumn('number', valueHeaderDescriptor, value);
44
+ }
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);
51
+ }
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);
57
+ }
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}`;
74
+ if (this.columns.find(column => column.name === name)) {
75
+ throw Error(`[ColumnDefinitionError] Column names have to be unique. "${name}" is defined more than once.`);
76
+ }
77
+ this.columns.push({ ...config, name, type });
78
+ return this;
79
+ }
80
+ }
81
+
82
+ /*
83
+ * Copyright (c) 2018-2026 Swiss Federal Railways
84
+ *
85
+ * This program and the accompanying materials are made
86
+ * available under the terms of the Eclipse Public License 2.0
87
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
88
+ *
89
+ * SPDX-License-Identifier: EPL-2.0
90
+ */
91
+ /**
92
+ * Creates a range of integers starting at `start` and ending at `end` (including `end`).
93
+ */
94
+ function rangeInclusive(start, end) {
95
+ const range = [];
96
+ for (let i = start; i <= end; i++) {
97
+ range.push(i);
98
+ }
99
+ return range;
100
+ }
101
+
102
+ /*
103
+ * Copyright (c) 2018-2026 Swiss Federal Railways
104
+ *
105
+ * This program and the accompanying materials are made
106
+ * available under the terms of the Eclipse Public License 2.0
107
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
108
+ *
109
+ * SPDX-License-Identifier: EPL-2.0
110
+ */
111
+ const SCI_TABLE_STORAGE = new InjectionToken('SCI_TABLE_STORAGE', { factory: () => ({
112
+ load: (name) => localStorage.getItem(name),
113
+ store: (name, value) => localStorage.setItem(name, value),
114
+ }) });
115
+ /**
116
+ * Defaults to localStorage persistence.
117
+ * @param storage
118
+ */
119
+ function provideTableStorage(storage) {
120
+ return makeEnvironmentProviders([
121
+ { provide: SCI_TABLE_STORAGE, useClass: storage },
122
+ ]);
123
+ }
124
+
125
+ /*
126
+ * Copyright (c) 2018-2026 Swiss Federal Railways
127
+ *
128
+ * This program and the accompanying materials are made
129
+ * available under the terms of the Eclipse Public License 2.0
130
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
131
+ *
132
+ * SPDX-License-Identifier: EPL-2.0
133
+ */
134
+ 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),
171
+ })));
172
+ };
173
+ }
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);
182
+ }
183
+ function globalFilter(row, filter) {
184
+ if (!filter?.trim()) {
185
+ return true;
186
+ }
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) {
191
+ return true;
192
+ }
193
+ }
194
+ return false;
195
+ }
196
+ function columnFilter(row, filterCriteria) {
197
+ if (filterCriteria.length === 0) {
198
+ return true;
199
+ }
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) {
219
+ return false;
220
+ }
221
+ }
222
+ return true;
223
+ }
224
+ function sort(a, b, sortCriteria) {
225
+ if (sortCriteria.length === 0) {
226
+ return 0;
227
+ }
228
+ 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;
249
+ }
250
+ }
251
+ return 0;
252
+ }
253
+
254
+ /*
255
+ * Copyright (c) 2018-2026 Swiss Federal Railways
256
+ *
257
+ * This program and the accompanying materials are made
258
+ * available under the terms of the Eclipse Public License 2.0
259
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
260
+ *
261
+ * SPDX-License-Identifier: EPL-2.0
262
+ */
263
+ class SciTableCache {
264
+ _cache = signal(new Map(), /* @ts-ignore */
265
+ ...(ngDevMode ? [{ debugName: "_cache" }] : /* istanbul ignore next */ []));
266
+ loading = computed(() => this.values().some(entry => entry.rows.isLoading()), /* @ts-ignore */
267
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
268
+ error = computed(() => this.values().find(entry => entry.rows.error())?.rows.error(), /* @ts-ignore */
269
+ ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
270
+ values = computed(() => [...this._cache().values()], /* @ts-ignore */
271
+ ...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
272
+ rowsByIndex = computed(() => this.values()
273
+ .flatMap(page => page.rows.value())
274
+ .reduce((acc, row) => acc.set(row.index, row), new Map()), { ...(ngDevMode ? { debugName: "rowsByIndex" } : /* istanbul ignore next */ {}), equal: Objects.isEqual });
275
+ rowsById = computed(() => this.values()
276
+ .flatMap(page => page.rows.value())
277
+ .reduce((acc, row) => row.id !== undefined ? acc.set(row.id, row) : acc, new Map()), { ...(ngDevMode ? { debugName: "rowsById" } : /* istanbul ignore next */ {}), equal: Objects.isEqual });
278
+ has(key) {
279
+ return this._cache().has(key);
280
+ }
281
+ get(key) {
282
+ return this._cache().get(key);
283
+ }
284
+ set(key, entry) {
285
+ this._cache.update(cache => {
286
+ const cacheCopy = new Map(cache);
287
+ const existing = cacheCopy.get(key);
288
+ if (existing) {
289
+ cacheCopy.delete(key);
290
+ existing.dispose();
291
+ }
292
+ cacheCopy.set(key, entry);
293
+ return cacheCopy;
294
+ });
295
+ }
296
+ /**
297
+ * Deletes the specified page from the cache, but only if still loading.
298
+ */
299
+ deleteIfLoading(key) {
300
+ const cacheEntry = this._cache().get(key);
301
+ if (!cacheEntry?.rows.isLoading()) {
302
+ return;
303
+ }
304
+ this._cache.update(cache => {
305
+ const cacheCopy = new Map(cache);
306
+ cacheCopy.delete(key);
307
+ cacheEntry.dispose();
308
+ return cacheCopy;
309
+ });
310
+ }
311
+ clear() {
312
+ this._cache.update(cache => {
313
+ for (const entry of cache.values()) {
314
+ entry.dispose();
315
+ }
316
+ return new Map();
317
+ });
318
+ }
319
+ }
320
+
321
+ /*
322
+ * Copyright (c) 2018-2026 Swiss Federal Railways
323
+ *
324
+ * This program and the accompanying materials are made
325
+ * available under the terms of the Eclipse Public License 2.0
326
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
327
+ *
328
+ * SPDX-License-Identifier: EPL-2.0
329
+ */
330
+ function provideTableRowBinding(bindings) {
331
+ return {
332
+ provide: SCI_TABLE_ROW_BINDING,
333
+ useValue: bindings,
334
+ multi: true,
335
+ };
336
+ }
337
+ class ɵSciTableRowBindingFactory {
338
+ attributeBindings = new Array();
339
+ classBindings = new Array();
340
+ partBindings = new Array();
341
+ addAttributeBinding(name, value) {
342
+ if (value !== undefined) {
343
+ this.attributeBindings.push([name, value]);
344
+ }
345
+ return this;
346
+ }
347
+ addClassBinding(name) {
348
+ if (name) {
349
+ this.classBindings.push(name);
350
+ }
351
+ return this;
352
+ }
353
+ addPartBinding(name) {
354
+ if (name) {
355
+ this.partBindings.push(name);
356
+ }
357
+ return this;
358
+ }
359
+ }
360
+ function coerceTableRowBindings(bindingsFn, item, index) {
361
+ const factory = computed(() => {
362
+ const factory = new ɵSciTableRowBindingFactory();
363
+ bindingsFn.forEach(fn => fn(factory, item, index));
364
+ return factory;
365
+ }, /* @ts-ignore */
366
+ ...(ngDevMode ? [{ debugName: "factory" }] : /* istanbul ignore next */ []));
367
+ return {
368
+ attributes: computed(() => Object.fromEntries(factory().attributeBindings), { equal: Objects.isEqual }),
369
+ cssClass: computed(() => factory().classBindings, { equal: Objects.isEqual }),
370
+ part: computed(() => factory().partBindings, { equal: Objects.isEqual }),
371
+ };
372
+ }
373
+ const SCI_TABLE_ROW_BINDING = new InjectionToken('SCI_TABLE_ROW_BINDING');
374
+
375
+ /*
376
+ * Copyright (c) 2018-2026 Swiss Federal Railways
377
+ *
378
+ * This program and the accompanying materials are made
379
+ * available under the terms of the Eclipse Public License 2.0
380
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
381
+ *
382
+ * SPDX-License-Identifier: EPL-2.0
383
+ */
384
+ class ɵSciTable {
385
+ _tableStorage = inject(SCI_TABLE_STORAGE);
386
+ _injector = inject(Injector);
387
+ name = signal(undefined, /* @ts-ignore */
388
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
389
+ columns;
390
+ rowActions;
391
+ _rowBindings;
392
+ _dataLoaderFn;
393
+ _trackBy;
394
+ tableViewRef = signal(undefined, /* @ts-ignore */
395
+ ...(ngDevMode ? [{ debugName: "tableViewRef" }] : /* istanbul ignore next */ []));
396
+ userSettings;
397
+ bufferSize;
398
+ pageSize;
399
+ filterable;
400
+ showHeader;
401
+ wrapHeader;
402
+ sortable;
403
+ resizable;
404
+ selectable;
405
+ scrollRange;
406
+ scrollTop;
407
+ virtualScrollOffset;
408
+ viewportPageSize;
409
+ scrolling;
410
+ resizing = computed(() => this.columns().some(column => column.resizing()), /* @ts-ignore */
411
+ ...(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 */ []));
416
+ _cache = new SciTableCache();
417
+ _tableFilter = signal(null, /* @ts-ignore */
418
+ ...(ngDevMode ? [{ debugName: "_tableFilter" }] : /* istanbul ignore next */ []));
419
+ _selectedItems = signal(new Map(), /* @ts-ignore */
420
+ ...(ngDevMode ? [{ debugName: "_selectedItems" }] : /* istanbul ignore next */ []));
421
+ // Reset total count on criteria change to show skeletons instead of stale data while loading.
422
+ totalCount = linkedSignal({ ...(ngDevMode ? { debugName: "totalCount" } : /* istanbul ignore next */ {}), source: () => this.criteria(),
423
+ computation: () => undefined });
424
+ activeItem = linkedSignal({ ...(ngDevMode ? { debugName: "activeItem" } : /* istanbul ignore next */ {}), source: () => this.criteria(),
425
+ computation: () => undefined });
426
+ hoveredIndex = linkedSignal({ ...(ngDevMode ? { debugName: "hoveredIndex" } : /* istanbul ignore next */ {}), source: () => {
427
+ this.criteria(); // reset on criteria change
428
+ this.scrolling(); // reset when start scrolling
429
+ },
430
+ computation: () => -1 });
431
+ criteria = computed(() => ({ sort: this.sortCriteria(), filter: this.filterCriteria(), tableFilter: this._tableFilter() }), /* @ts-ignore */
432
+ ...(ngDevMode ? [{ debugName: "criteria" }] : /* istanbul ignore next */ []));
433
+ loading = this._cache.loading;
434
+ error = this._cache.error;
435
+ activeRow;
436
+ hoveredRow = computed(() => this.rowsByIndex().get(this.hoveredIndex()), /* @ts-ignore */
437
+ ...(ngDevMode ? [{ debugName: "hoveredRow" }] : /* istanbul ignore next */ []));
438
+ selectedItems = computed(() => [...this._selectedItems().values()], /* @ts-ignore */
439
+ ...(ngDevMode ? [{ debugName: "selectedItems" }] : /* istanbul ignore next */ []));
440
+ selectedIds = computed(() => new Set([...this._selectedItems().keys()]), /* @ts-ignore */
441
+ ...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
442
+ rowsByIndex = this._cache.rowsByIndex;
443
+ rows = this.computeRows();
444
+ constructor(descriptor) {
445
+ // TODO [table] Remove after datasource is final. See ɵillegaldatasource.
446
+ descriptor = { ...descriptor, ɵdatasource: descriptor.ɵdatasource ?? descriptor.datasource };
447
+ this.bufferSize = descriptor.bufferSize ?? 10;
448
+ this.pageSize = descriptor.pageSize ?? 50;
449
+ this.sortable = signal(descriptor.sortable ?? true, /* @ts-ignore */
450
+ ...(ngDevMode ? [{ debugName: "sortable" }] : /* istanbul ignore next */ []));
451
+ this.filterable = signal(descriptor.filterable ?? false, /* @ts-ignore */
452
+ ...(ngDevMode ? [{ debugName: "filterable" }] : /* istanbul ignore next */ []));
453
+ this.showHeader = signal(descriptor.showHeader ?? true, /* @ts-ignore */
454
+ ...(ngDevMode ? [{ debugName: "showHeader" }] : /* istanbul ignore next */ []));
455
+ this.wrapHeader = signal(descriptor.wrapHeader ?? false, /* @ts-ignore */
456
+ ...(ngDevMode ? [{ debugName: "wrapHeader" }] : /* istanbul ignore next */ []));
457
+ this.resizable = signal(descriptor.resizable ?? true, /* @ts-ignore */
458
+ ...(ngDevMode ? [{ debugName: "resizable" }] : /* istanbul ignore next */ []));
459
+ this.selectable = signal(descriptor.selectable ?? 'multi', /* @ts-ignore */
460
+ ...(ngDevMode ? [{ debugName: "selectable" }] : /* istanbul ignore next */ []));
461
+ this.userSettings = this.computeUserSettings();
462
+ this.scrollRange = this.computeScrollRange();
463
+ this.scrollTop = this.computeScrollTop();
464
+ this.scrolling = this.computeScrolling();
465
+ this.virtualScrollOffset = this.computeVirtualScrollOffset();
466
+ this.viewportPageSize = this.computeViewportPageSize();
467
+ this.activeRow = this.computeActiveRow();
468
+ this.columns = this.computeColumns(descriptor);
469
+ this.rowActions = descriptor.rowActions;
470
+ this._rowBindings = [
471
+ ...descriptor.rowBindings ? [descriptor.rowBindings] : [],
472
+ ...inject(SCI_TABLE_ROW_BINDING, { optional: true }) ?? [],
473
+ ];
474
+ this._trackBy = descriptor.trackBy;
475
+ this._dataLoaderFn = isSignal(descriptor.ɵdatasource) ? arrayDatasource(descriptor.ɵdatasource, this.columns) : descriptor.ɵdatasource;
476
+ this.installCriteriaWatcher();
477
+ this.installPageLoader();
478
+ }
479
+ /**
480
+ * Connects {@link SciTableComponent} to the model.
481
+ */
482
+ connect(name, viewRef) {
483
+ this.name.set(name);
484
+ this.tableViewRef.set(viewRef);
485
+ }
486
+ disconnect() {
487
+ this.name.set(undefined);
488
+ this.tableViewRef.set(undefined);
489
+ }
490
+ computeColumns(descriptor) {
491
+ // Create separate injection context per factory invocation to clean up allocated resources, like RxJS subscriptions.
492
+ let injector;
493
+ return computed(() => {
494
+ injector?.destroy();
495
+ injector = createDestroyableInjector({ parent: this._injector });
496
+ return runInInjectionContext(injector, () => {
497
+ const tableColumnFactory = new ɵSciTableColumnFactory(descriptor);
498
+ descriptor.columns(tableColumnFactory);
499
+ return untracked(() => tableColumnFactory.columns.map(column => this.initColumn(column.type, column)));
500
+ });
501
+ });
502
+ }
503
+ /**
504
+ * Computes the rows currently visible in the viewport (+buffer).
505
+ */
506
+ computeRows() {
507
+ return computed(() => {
508
+ const scrollRange = this.scrollRange();
509
+ if (!scrollRange) {
510
+ return [];
511
+ }
512
+ const rowsByIndex = this.rowsByIndex();
513
+ const rowCount = scrollRange.end - scrollRange.start;
514
+ // Populate rows with cached rows in the range window, fallback to row shell to show skeleton.
515
+ return untracked(() => Array.from({ length: rowCount }, (_, i) => rowsByIndex.get(scrollRange.start + i) ?? createSyntheticRow(i)));
516
+ });
517
+ function createSyntheticRow(index) {
518
+ return {
519
+ index,
520
+ loading: true,
521
+ active: signal(false).asReadonly(),
522
+ selected: signal(false).asReadonly(),
523
+ hovered: signal(false).asReadonly(),
524
+ };
525
+ }
526
+ }
527
+ /**
528
+ * Computes the visible row count based on the viewport size.
529
+ */
530
+ computeScrollRange() {
531
+ return computed(() => {
532
+ const tableViewRef = this.tableViewRef();
533
+ if (!tableViewRef) {
534
+ return undefined;
535
+ }
536
+ const viewportHeight = tableViewRef.viewportHeight();
537
+ const itemHeight = tableViewRef.itemHeight();
538
+ const scrollTop = this.scrollTop();
539
+ const start = Math.floor(scrollTop / itemHeight);
540
+ const viewportRowCount = Math.ceil(viewportHeight / itemHeight);
541
+ const end = Math.min(start + viewportRowCount);
542
+ const totalCount = this.totalCount() ?? viewportRowCount; // fill viewport if no data loaded yet
543
+ return {
544
+ start: clamp(start - this.bufferSize, { min: 0, max: Math.max(0, totalCount - viewportRowCount) }),
545
+ end: clamp(end + this.bufferSize, { max: totalCount }),
546
+ };
547
+ }, { equal: Objects.isEqual });
548
+ }
549
+ computeVirtualScrollOffset() {
550
+ return computed(() => {
551
+ const itemHeight = this.tableViewRef()?.itemHeight() ?? 0;
552
+ const totalCount = this.totalCount() ?? 0;
553
+ const rangeEnd = Math.min(this.scrollRange()?.end ?? 0, this.totalCount() ?? 0);
554
+ return {
555
+ top: (this.scrollRange()?.start ?? 0) * itemHeight,
556
+ bottom: (totalCount - rangeEnd) * itemHeight,
557
+ };
558
+ }, { equal: Objects.isEqual });
559
+ }
560
+ computeViewportPageSize() {
561
+ return computed(() => {
562
+ const itemHeight = this.tableViewRef()?.itemHeight() ?? 0;
563
+ const viewportHeight = this.tableViewRef()?.viewportHeight() ?? 0;
564
+ return Math.ceil(viewportHeight / itemHeight);
565
+ });
566
+ }
567
+ /**
568
+ * Tracks {@link HTMLElement.scrollTop} of the viewport.
569
+ */
570
+ computeScrollTop() {
571
+ const zone = inject(NgZone);
572
+ return rxResource({
573
+ defaultValue: 0,
574
+ params: () => this.tableViewRef()?.viewport,
575
+ stream: ({ params: viewport }) => fromEvent(viewport, 'scroll', { passive: true })
576
+ .pipe(map$1(() => viewport.scrollTop), startWith(viewport.scrollTop), subscribeIn(fn => zone.runOutsideAngular(fn))),
577
+ }).value;
578
+ }
579
+ /**
580
+ * Tracks whether currently scrolling the viewport.
581
+ */
582
+ computeScrolling() {
583
+ const zone = inject(NgZone);
584
+ return rxResource({
585
+ defaultValue: false,
586
+ params: () => this.tableViewRef()?.viewport,
587
+ 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))),
589
+ }).value;
590
+ }
591
+ /**
592
+ * Loads a range of rows, based on the current sort and filter criteria, into the cache.
593
+ */
594
+ async loadRange(start, end) {
595
+ const sortCriteria = this.sortCriteria();
596
+ const columnFilters = this.filterCriteria();
597
+ const tableFilter = this._tableFilter() ?? undefined;
598
+ // Calculate pages based on row indices and page size.
599
+ const pages = pagesByRange(start, end, this.pageSize);
600
+ if (!pages) {
601
+ return;
602
+ }
603
+ // Load pages and wait until completed loading.
604
+ await Promise.all(pages
605
+ .map(page => this.loadPage({ page, pageSize: this.pageSize, columnFilters, tableFilter, sortCriteria }))
606
+ .map(page => firstValueFrom(toLazyObservable(page.loading, { injector: this._injector }).pipe(first(loading => !loading), defaultIfEmpty(true)))));
607
+ }
608
+ /**
609
+ * Loads the requested page from the cache or datasource and returns its loading state with a cancelation handler.
610
+ */
611
+ loadPage({ page, pageSize, sortCriteria, columnFilters, tableFilter }) {
612
+ const pageStart = page * pageSize;
613
+ const pageEnd = pageStart + pageSize;
614
+ const cacheKey = `${pageStart}-${pageEnd}`;
615
+ if (this._cache.has(cacheKey)) {
616
+ return {
617
+ loading: this._cache.get(cacheKey).rows.isLoading,
618
+ cancel: () => this._cache.deleteIfLoading(cacheKey),
619
+ };
620
+ }
621
+ const cacheEntryInjector = createDestroyableInjector({ parent: this._injector });
622
+ const cacheEntry = {
623
+ rows: runInInjectionContext(cacheEntryInjector, () => {
624
+ // Fetch data.
625
+ const tableResponse$ = runSafe(() => Observables.coerce(this._dataLoaderFn({
626
+ start: pageStart,
627
+ end: pageEnd,
628
+ pageSize,
629
+ page,
630
+ sortCriteria,
631
+ tableFilter,
632
+ columnFilters,
633
+ })), error => throwError(() => error));
634
+ // 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);
653
+ // Reset count to scroll to the top so the user sees the error.
654
+ this.totalCount.set(0);
655
+ break;
656
+ }
657
+ }
658
+ });
659
+ return rows;
660
+ }),
661
+ dispose: () => cacheEntryInjector.destroy(),
662
+ start: pageStart,
663
+ end: pageEnd,
664
+ };
665
+ this._cache.set(cacheKey, cacheEntry);
666
+ return {
667
+ loading: cacheEntry.rows.isLoading,
668
+ cancel: () => this._cache.deleteIfLoading(cacheKey),
669
+ };
670
+ }
671
+ /**
672
+ * Toggles sort on a column. ASC -> DESC -> No sort
673
+ *
674
+ * TODO [ego] Revisit API, do we really want a three-state and implicit toggling logic?
675
+ */
676
+ sort(columnName, multi) {
677
+ // Scroll to the top as soon as sort changes.
678
+ // Done here explicitly to avoid race-conditions and pages being loaded unnecessarily.
679
+ this.tableViewRef()?.scrollToTop();
680
+ this.sortCriteria.update(sortCriteria => {
681
+ const column = this.columns().find(column => column.name === columnName);
682
+ if (!column) {
683
+ throw Error(`[NullColumnError] Column '${columnName}' not found in table '${this.name()}'.`);
684
+ }
685
+ const currentSortCriterion = sortCriteria.find(sortCriterion => sortCriterion.columnName === column.name);
686
+ const otherSortCriteria = sortCriteria.filter(sortCriterion => sortCriterion !== currentSortCriterion);
687
+ const direction = currentSortCriterion ? (currentSortCriterion.direction === 'asc' ? 'desc' : undefined) : 'asc';
688
+ if (!direction) {
689
+ return multi ? otherSortCriteria : [];
690
+ }
691
+ const newSortCriterion = { columnName: column.name, direction };
692
+ return multi ? [...otherSortCriteria, newSortCriterion] : [newSortCriterion];
693
+ });
694
+ }
695
+ filter(text, options) {
696
+ // Scroll to the top as soon as filter changes.
697
+ // Done here explicitly to avoid race-conditions and pages being loaded unnecessarily.
698
+ this.tableViewRef()?.scrollToTop();
699
+ if (!options) {
700
+ this._tableFilter.set(text);
701
+ return;
702
+ }
703
+ const column = this.columns().find(column => column.name === options.columnName);
704
+ if (!column) {
705
+ throw Error(`[NullColumnError] Column '${options.columnName}' not found in table '${this.name()}'.`);
706
+ }
707
+ this.filterCriteria.update(filterCriterion => {
708
+ const otherFilterCriteria = filterCriterion.filter(filterCriterion => filterCriterion.columnName !== options.columnName);
709
+ if (text === null) {
710
+ return otherFilterCriteria;
711
+ }
712
+ return otherFilterCriteria.concat({ columnName: column.name, text });
713
+ });
714
+ }
715
+ updateSelectedItems(updateFn) {
716
+ this._selectedItems.update(updateFn);
717
+ }
718
+ reset() {
719
+ this._cache.clear();
720
+ this.totalCount.set(undefined);
721
+ }
722
+ dispose() {
723
+ this._cache.clear();
724
+ this.disconnect();
725
+ }
726
+ /**
727
+ * Loads a set of pages on criteria or viewport changes.
728
+ */
729
+ installPageLoader() {
730
+ effect(onCleanup => {
731
+ const scrollRange = this.scrollRange();
732
+ const sortCriteria = this.sortCriteria();
733
+ const columnFilters = this.filterCriteria();
734
+ const tableFilter = this._tableFilter() ?? undefined;
735
+ if (!scrollRange) {
736
+ return;
737
+ }
738
+ untracked(() => {
739
+ // Fall back to load the initial page if range is empty. Otherwise, if the table grows with its content,
740
+ // no data would ever load because the scroll range remains 0.
741
+ const pages = pagesByRange(scrollRange.start, scrollRange.end, this.pageSize) ?? [0];
742
+ pages.forEach(page => {
743
+ const pageRef = this.loadPage({
744
+ pageSize: this.pageSize,
745
+ page,
746
+ sortCriteria,
747
+ tableFilter,
748
+ columnFilters,
749
+ });
750
+ onCleanup(() => pageRef.cancel());
751
+ });
752
+ });
753
+ });
754
+ }
755
+ /**
756
+ * Resets cache on criteria changes.
757
+ */
758
+ installCriteriaWatcher() {
759
+ toObservable(this.criteria).pipe(skip(1), // skip first emission to avoid race condition with loader on initialization.
760
+ takeUntilDestroyed()).subscribe(() => {
761
+ this._cache.clear();
762
+ });
763
+ }
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
+ mapItemsToRow(items, columns, pageStart) {
791
+ return items.map((item, i) => {
792
+ const id = this.trackBy(item);
793
+ const index = pageStart + i;
794
+ return {
795
+ id,
796
+ index,
797
+ item,
798
+ loading: false,
799
+ active: computed(() => item === this.activeItem()),
800
+ selected: computed(() => this.selectedIds().has(id)),
801
+ hovered: computed(() => this.hoveredRow()?.index === index),
802
+ bindings: coerceTableRowBindings(this._rowBindings, item, pageStart + i),
803
+ cells: columns.map(column => ({
804
+ value: column.type !== 'component' && column.type !== 'template' ? coerceSignal(column.value(item)) : undefined,
805
+ component: column.type === 'component' ? column.component(item) : undefined,
806
+ template: column.type === 'template' ? column.template(item) : undefined,
807
+ type: column.type,
808
+ column,
809
+ })),
810
+ };
811
+ });
812
+ }
813
+ /**
814
+ * Creates a signal to read and write table user settings.
815
+ */
816
+ computeUserSettings() {
817
+ // Load user settings from storage.
818
+ const userSettings = resource({ ...(ngDevMode ? { debugName: "userSettings" } : /* istanbul ignore next */ {}), params: () => this.name(),
819
+ loader: async ({ params: tableName }) => {
820
+ try {
821
+ const serialized = await this._tableStorage.load(`scion.components.${tableName}`);
822
+ return serialized ? JSON.parse(serialized) : { columns: [] };
823
+ }
824
+ catch (error) {
825
+ console.warn(`[SciTable] Failed to load user settings for '${tableName}'.`, error);
826
+ return { columns: [] };
827
+ }
828
+ },
829
+ defaultValue: { columns: [] } });
830
+ // Persist settings to storage.
831
+ effect(() => {
832
+ if (userSettings.status() === 'local' && this.name()) {
833
+ untracked(() => void this._tableStorage.store(`scion.components.${this.name()}`, JSON.stringify(userSettings.value())));
834
+ }
835
+ });
836
+ return userSettings.value;
837
+ }
838
+ /**
839
+ * Call trackBy function or fallback to track by object reference.
840
+ */
841
+ trackBy(item) {
842
+ return this._trackBy?.(item) ?? item;
843
+ }
844
+ computeActiveRow() {
845
+ return computed(() => {
846
+ const activeItem = this.activeItem();
847
+ if (activeItem === undefined) {
848
+ return undefined;
849
+ }
850
+ const id = this.trackBy(activeItem);
851
+ if (id === undefined) {
852
+ return undefined;
853
+ }
854
+ return this._cache.rowsById().get(id);
855
+ });
856
+ }
857
+ }
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
+ /**
885
+ * Gets pages by range and page size, or `null` if empty. End is exclusive.
886
+ */
887
+ function pagesByRange(start, end, pageSize) {
888
+ const startPage = Math.floor(start / pageSize);
889
+ const endPage = Math.floor((end - 1) / pageSize); // `end` is exclusive, so use the last included index (`end - 1`) for page calculation.
890
+ const pages = rangeInclusive(startPage, endPage);
891
+ return pages.length ? pages : null;
892
+ }
893
+ const ɵSCI_TABLE = new InjectionToken('ɵSciTable');
894
+
895
+ /*
896
+ * Copyright (c) 2018-2026 Swiss Federal Railways
897
+ *
898
+ * This program and the accompanying materials are made
899
+ * available under the terms of the Eclipse Public License 2.0
900
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
901
+ *
902
+ * SPDX-License-Identifier: EPL-2.0
903
+ */
904
+ /**
905
+ * TODO [table] Remove after datasource is final.
906
+ *
907
+ * @docs-private Not public API. For internal use only.
908
+ * @experimental since 22.3.0; API and behavior may change in any version without notice.
909
+ */
910
+ function ɵillegaldatasource() {
911
+ return computed(() => {
912
+ throw Error('[SciTableError] Illegal state. ɵillegaldatasource should not be used.');
913
+ });
914
+ }
915
+
916
+ /*
917
+ * Copyright (c) 2018-2026 Swiss Federal Railways
918
+ *
919
+ * This program and the accompanying materials are made
920
+ * available under the terms of the Eclipse Public License 2.0
921
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
922
+ *
923
+ * SPDX-License-Identifier: EPL-2.0
924
+ */
925
+ function table(dataOrDescriptor, columnFactoryFn, options) {
926
+ assertNotInReactiveContext(table, 'Call table in a non-reactive (non-tracking) context, such as within the untracked() function.');
927
+ const descriptor = isSignal(dataOrDescriptor) ? ({ ɵdatasource: dataOrDescriptor, datasource: ɵillegaldatasource(), columns: columnFactoryFn, injector: options?.injector }) : dataOrDescriptor;
928
+ if (!descriptor.injector) {
929
+ assertInInjectionContext(table);
930
+ }
931
+ const injector = descriptor.injector ?? inject(Injector);
932
+ const sciTable = runInInjectionContext(injector, () => new ɵSciTable(descriptor));
933
+ injector.get(DestroyRef).onDestroy(() => sciTable.dispose());
934
+ return sciTable;
935
+ }
936
+
937
+ /*
938
+ * Copyright (c) 2018-2026 Swiss Federal Railways
939
+ *
940
+ * This program and the accompanying materials are made
941
+ * available under the terms of the Eclipse Public License 2.0
942
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
943
+ *
944
+ * SPDX-License-Identifier: EPL-2.0
945
+ */
946
+ class SciTableSelectionService {
947
+ _table = inject(ɵSCI_TABLE);
948
+ async onRowClick(index, event) {
949
+ const table = this._table();
950
+ const rowsByIndex = table.rowsByIndex();
951
+ const row = rowsByIndex.get(index);
952
+ const item = rowsByIndex.get(index)?.item;
953
+ const previousFocusedIndex = table.activeRow()?.index ?? -1;
954
+ table.activeItem.set(item);
955
+ if (item === undefined || !table.selectable()) {
956
+ return;
957
+ }
958
+ if (table.selectable() === 'single') {
959
+ table.updateSelectedItems(() => new Map().set(row.id, item));
960
+ return;
961
+ }
962
+ if (event.shiftKey && previousFocusedIndex >= 0) {
963
+ const start = Math.min(previousFocusedIndex, index);
964
+ const end = Math.max(previousFocusedIndex, index);
965
+ await this.selectItems(start, end, { add: true });
966
+ }
967
+ else if (event.ctrlKey || event.metaKey) {
968
+ this.toggleSelectedItem(item);
969
+ }
970
+ else {
971
+ // If no modifier is pressed set the selection to the clicked row.
972
+ table.updateSelectedItems(() => new Map().set(row.id, item));
973
+ }
974
+ }
975
+ async onArrowUp(event) {
976
+ event.preventDefault();
977
+ const table = this._table();
978
+ const activeIndex = this._table().activeRow()?.index ?? -1;
979
+ if (activeIndex <= 0) {
980
+ return;
981
+ }
982
+ // Set active item.
983
+ const startIndex = activeIndex - 1;
984
+ await this.loadMissingItems(startIndex, startIndex);
985
+ const startItem = table.rowsByIndex().get(startIndex)?.item;
986
+ table.activeItem.set(startItem);
987
+ if (!table.selectable()) {
988
+ return;
989
+ }
990
+ const keyboardEvent = event;
991
+ const shift = keyboardEvent.shiftKey;
992
+ const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
993
+ if (shift && ctrlOrMeta) {
994
+ await this.ctrlShiftUp({ startIndex, activeIndex });
995
+ }
996
+ else if (shift) {
997
+ await this.shiftUp({ startIndex, activeIndex });
998
+ }
999
+ else if (ctrlOrMeta) {
1000
+ // Selection remains unchanged.
1001
+ }
1002
+ else {
1003
+ await this.selectItems(startIndex, startIndex);
1004
+ }
1005
+ }
1006
+ async onArrowDown(event) {
1007
+ event.preventDefault();
1008
+ const table = this._table();
1009
+ const activeIndex = this._table().activeRow()?.index ?? -1;
1010
+ const endIndex = Math.min(activeIndex + 1, table.totalCount() - 1);
1011
+ if (endIndex === activeIndex) {
1012
+ return;
1013
+ }
1014
+ // Set active item.
1015
+ await this.loadMissingItems(endIndex, endIndex);
1016
+ const endItem = table.rowsByIndex().get(endIndex)?.item;
1017
+ table.activeItem.set(endItem);
1018
+ if (!table.selectable()) {
1019
+ return;
1020
+ }
1021
+ const keyboardEvent = event;
1022
+ const shift = keyboardEvent.shiftKey;
1023
+ const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
1024
+ if (shift && ctrlOrMeta) {
1025
+ await this.ctrlShiftDown({ endIndex, activeIndex });
1026
+ }
1027
+ else if (shift) {
1028
+ await this.shiftDown({ endIndex, activeIndex });
1029
+ }
1030
+ else if (ctrlOrMeta) {
1031
+ // Selection remains unchanged.
1032
+ }
1033
+ else {
1034
+ await this.selectItems(endIndex, endIndex);
1035
+ }
1036
+ }
1037
+ async onPageUp(event) {
1038
+ event.preventDefault();
1039
+ const table = this._table();
1040
+ const activeIndex = this._table().activeRow()?.index ?? -1;
1041
+ if (activeIndex === 0) {
1042
+ return;
1043
+ }
1044
+ // Set active item.
1045
+ const startIndex = Math.max(activeIndex - table.viewportPageSize() + 1, 0);
1046
+ await this.loadMissingItems(startIndex, startIndex);
1047
+ const startItem = table.rowsByIndex().get(startIndex)?.item;
1048
+ table.activeItem.set(startItem);
1049
+ if (!table.selectable()) {
1050
+ return;
1051
+ }
1052
+ const keyboardEvent = event;
1053
+ const shift = keyboardEvent.shiftKey;
1054
+ const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
1055
+ if (shift && ctrlOrMeta) {
1056
+ await this.ctrlShiftUp({ startIndex, activeIndex });
1057
+ }
1058
+ else if (shift) {
1059
+ await this.shiftUp({ startIndex, activeIndex });
1060
+ }
1061
+ else if (ctrlOrMeta) {
1062
+ // Selection remains unchanged.
1063
+ }
1064
+ else {
1065
+ await this.selectItems(startIndex, startIndex);
1066
+ }
1067
+ }
1068
+ async onPageDown(event) {
1069
+ event.preventDefault();
1070
+ const table = this._table();
1071
+ const activeIndex = this._table().activeRow()?.index ?? -1;
1072
+ const endIndex = Math.min(activeIndex + table.viewportPageSize() - 1, table.totalCount() - 1);
1073
+ if (endIndex === activeIndex) {
1074
+ return;
1075
+ }
1076
+ // Set active item.
1077
+ await this.loadMissingItems(endIndex, endIndex);
1078
+ const endItem = table.rowsByIndex().get(endIndex)?.item;
1079
+ table.activeItem.set(endItem);
1080
+ if (!table.selectable()) {
1081
+ return;
1082
+ }
1083
+ const keyboardEvent = event;
1084
+ const shift = keyboardEvent.shiftKey;
1085
+ const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
1086
+ if (shift && ctrlOrMeta) {
1087
+ await this.ctrlShiftDown({ endIndex, activeIndex });
1088
+ }
1089
+ else if (shift) {
1090
+ await this.shiftDown({ endIndex, activeIndex });
1091
+ }
1092
+ else if (ctrlOrMeta) {
1093
+ // Selection remains unchanged.
1094
+ }
1095
+ else {
1096
+ await this.selectItems(endIndex, endIndex);
1097
+ }
1098
+ }
1099
+ async onHome(event) {
1100
+ event.preventDefault();
1101
+ const table = this._table();
1102
+ const activeIndex = this._table().activeRow()?.index ?? -1;
1103
+ if (activeIndex <= 0) {
1104
+ return;
1105
+ }
1106
+ // Set active item.
1107
+ const startIndex = 0;
1108
+ await this.loadMissingItems(startIndex, startIndex);
1109
+ const startItem = table.rowsByIndex().get(startIndex)?.item;
1110
+ table.activeItem.set(startItem);
1111
+ if (!table.selectable()) {
1112
+ return;
1113
+ }
1114
+ const keyboardEvent = event;
1115
+ const shift = keyboardEvent.shiftKey;
1116
+ const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
1117
+ if (shift && ctrlOrMeta) {
1118
+ await this.ctrlShiftUp({ startIndex, activeIndex });
1119
+ }
1120
+ else if (shift) {
1121
+ await this.shiftUp({ startIndex, activeIndex });
1122
+ }
1123
+ else if (ctrlOrMeta) {
1124
+ // Selection remains unchanged.
1125
+ }
1126
+ else {
1127
+ await this.selectItems(startIndex, startIndex);
1128
+ }
1129
+ }
1130
+ async onEnd(event) {
1131
+ event.preventDefault();
1132
+ const table = this._table();
1133
+ const activeIndex = this._table().activeRow()?.index ?? -1;
1134
+ const endIndex = table.totalCount() - 1;
1135
+ if (endIndex === activeIndex) {
1136
+ return;
1137
+ }
1138
+ // TODO [mmu] should active item be set before selection? this way, further methods cant rely on table.activeItem.
1139
+ // But otherwise, loading and setting the active item needs to be done for each case, or once at the end.
1140
+ // Set active item.
1141
+ await this.loadMissingItems(endIndex, endIndex);
1142
+ const endItem = table.rowsByIndex().get(endIndex)?.item;
1143
+ table.activeItem.set(endItem);
1144
+ if (!table.selectable()) {
1145
+ return;
1146
+ }
1147
+ const keyboardEvent = event;
1148
+ const shift = keyboardEvent.shiftKey;
1149
+ const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
1150
+ if (shift && ctrlOrMeta) {
1151
+ await this.ctrlShiftDown({ endIndex, activeIndex });
1152
+ }
1153
+ else if (shift) {
1154
+ await this.shiftDown({ endIndex, activeIndex });
1155
+ }
1156
+ else if (ctrlOrMeta) {
1157
+ // Selection remains unchanged.
1158
+ }
1159
+ else {
1160
+ await this.selectItems(endIndex, endIndex);
1161
+ }
1162
+ }
1163
+ onSpace(event) {
1164
+ event.preventDefault();
1165
+ const table = this._table();
1166
+ if (!table.selectable()) {
1167
+ return;
1168
+ }
1169
+ const activeItem = table.activeItem();
1170
+ if (!activeItem) {
1171
+ return;
1172
+ }
1173
+ const keyboardEvent = event;
1174
+ const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
1175
+ const activeItemId = table.trackBy(activeItem);
1176
+ if (ctrlOrMeta) {
1177
+ this.toggleSelectedItem(activeItem);
1178
+ }
1179
+ else {
1180
+ table.updateSelectedItems(() => new Map().set(activeItemId, activeItem));
1181
+ }
1182
+ }
1183
+ async onControlA(event) {
1184
+ event.preventDefault();
1185
+ const table = this._table();
1186
+ const totalCount = table.totalCount() ?? 0;
1187
+ if (table.selectable() === 'multi' && totalCount >= 0) {
1188
+ await this.selectItems(0, totalCount - 1);
1189
+ }
1190
+ }
1191
+ // TODO [mmu] Should we add overloads for all variants for better readability, but same logic?
1192
+ // shiftArrowDown
1193
+ // shiftPageDown
1194
+ // ShiftEnd
1195
+ async shiftDown(options) {
1196
+ const table = this._table();
1197
+ const selectedIds = table.selectedIds();
1198
+ const activeIndex = options.activeIndex;
1199
+ const startIndex = activeIndex;
1200
+ const endIndex = options.endIndex;
1201
+ const activeItem = table.rowsByIndex().get(activeIndex).item;
1202
+ const activeItemId = table.trackBy(activeItem);
1203
+ if (table.selectable() === 'single') {
1204
+ await this.selectItems(endIndex, endIndex);
1205
+ return;
1206
+ }
1207
+ if (!selectedIds.has(activeItemId)) {
1208
+ await this.selectItems(startIndex, endIndex);
1209
+ return;
1210
+ }
1211
+ if (this.isSelectionAtBlockStart(activeIndex)) {
1212
+ await this.selectFromBlockStart(startIndex, endIndex);
1213
+ return;
1214
+ }
1215
+ await this.selectItems(startIndex, endIndex, { add: true });
1216
+ }
1217
+ async ctrlShiftDown(options) {
1218
+ const table = this._table();
1219
+ const startIndex = options.activeIndex;
1220
+ const endIndex = options.endIndex;
1221
+ if (table.selectable() === 'single') {
1222
+ await this.selectItems(endIndex, endIndex);
1223
+ }
1224
+ else {
1225
+ await this.selectItems(startIndex, endIndex, { add: true });
1226
+ }
1227
+ }
1228
+ async shiftUp(options) {
1229
+ const table = this._table();
1230
+ const selectedIds = table.selectedIds();
1231
+ const activeIndex = options.activeIndex;
1232
+ const startIndex = options.startIndex;
1233
+ const endIndex = activeIndex;
1234
+ const activeItem = table.rowsByIndex().get(activeIndex).item;
1235
+ const activeItemId = table.trackBy(activeItem);
1236
+ if (table.selectable() === 'single') {
1237
+ await this.selectItems(startIndex, startIndex);
1238
+ return;
1239
+ }
1240
+ if (!selectedIds.has(activeItemId)) {
1241
+ await this.selectItems(startIndex, endIndex);
1242
+ return;
1243
+ }
1244
+ if (this.isSelectionAtBlockEnd(activeIndex)) {
1245
+ await this.selectFromBlockEnd(startIndex, endIndex);
1246
+ return;
1247
+ }
1248
+ await this.selectItems(startIndex, endIndex, { add: true });
1249
+ }
1250
+ async ctrlShiftUp(options) {
1251
+ const table = this._table();
1252
+ const startIndex = options.startIndex;
1253
+ const endIndex = options.activeIndex;
1254
+ if (table.selectable() === 'single') {
1255
+ await this.selectItems(startIndex, startIndex);
1256
+ }
1257
+ else {
1258
+ await this.selectItems(startIndex, endIndex, { add: true });
1259
+ }
1260
+ }
1261
+ isSelectionAtBlockStart(activeIndex) {
1262
+ const table = this._table();
1263
+ const selectedIds = table.selectedIds();
1264
+ const previousItem = table.rowsByIndex().get(activeIndex - 1)?.item;
1265
+ const previousItemId = previousItem ? table.trackBy(previousItem) : undefined;
1266
+ const nextItem = table.rowsByIndex().get(activeIndex + 1)?.item;
1267
+ const nextItemId = nextItem ? table.trackBy(nextItem) : undefined;
1268
+ return !selectedIds.has(previousItemId) && selectedIds.has(nextItemId);
1269
+ }
1270
+ isSelectionAtBlockEnd(activeIndex) {
1271
+ const table = this._table();
1272
+ const selectedIds = table.selectedIds();
1273
+ const previousItem = table.rowsByIndex().get(activeIndex - 1)?.item;
1274
+ const previousItemId = previousItem ? table.trackBy(previousItem) : undefined;
1275
+ const nextItem = table.rowsByIndex().get(activeIndex + 1)?.item;
1276
+ const nextItemId = nextItem ? table.trackBy(nextItem) : undefined;
1277
+ return selectedIds.has(previousItemId) && !selectedIds.has(nextItemId);
1278
+ }
1279
+ async loadMissingItems(startIndex, endIndex) {
1280
+ const table = this._table();
1281
+ const indices = rangeInclusive(startIndex, endIndex + 1);
1282
+ const rows = indices.map(i => table.rowsByIndex().get(i));
1283
+ if (rows.some(row => row?.id === undefined)) {
1284
+ await Promise.race([
1285
+ table.loadRange(startIndex, endIndex + 1),
1286
+ firstValueFrom(timer(5_000)),
1287
+ ]);
1288
+ }
1289
+ }
1290
+ async selectItems(startIndex, endIndex, options) {
1291
+ const table = this._table();
1292
+ const indices = rangeInclusive(startIndex, endIndex);
1293
+ const add = options?.add ?? false;
1294
+ let rows = indices.map(i => table.rowsByIndex().get(i));
1295
+ if (rows.some(row => row?.id === undefined)) {
1296
+ // If not all id's could be found load the missing items.
1297
+ await Promise.race([
1298
+ table.loadRange(startIndex, endIndex),
1299
+ firstValueFrom(timer(5_000)),
1300
+ ]);
1301
+ rows = indices.map(i => table.rowsByIndex().get(i));
1302
+ }
1303
+ table.updateSelectedItems(existing => new Map([
1304
+ ...(add ? existing : []),
1305
+ ...rows
1306
+ .filter((row) => row?.id !== undefined && row.item !== undefined)
1307
+ .map((row) => [row.id, row.item]),
1308
+ ]));
1309
+ }
1310
+ async selectFromBlockStart(startIndex, endIndex) {
1311
+ const table = this._table();
1312
+ await this.loadMissingItems(startIndex, endIndex);
1313
+ const indices = rangeInclusive(startIndex, endIndex);
1314
+ table.updateSelectedItems(selection => {
1315
+ const newSelection = new Map(selection);
1316
+ for (let i = 0; i < indices.length; i++) {
1317
+ const currentIndex = indices[i];
1318
+ const nextIndex = indices[i + 1];
1319
+ const item = table.rowsByIndex().get(currentIndex)?.item;
1320
+ if (!item) {
1321
+ continue;
1322
+ }
1323
+ const itemId = table.trackBy(item);
1324
+ const nextItem = table.rowsByIndex().get(nextIndex)?.item;
1325
+ const nextItemId = nextItem ? table.trackBy(nextItem) : undefined;
1326
+ if (selection.has(nextItemId)) {
1327
+ newSelection.delete(itemId);
1328
+ }
1329
+ else {
1330
+ newSelection.set(itemId, item);
1331
+ }
1332
+ }
1333
+ return newSelection;
1334
+ });
1335
+ }
1336
+ async selectFromBlockEnd(startIndex, endIndex) {
1337
+ const table = this._table();
1338
+ await this.loadMissingItems(startIndex, endIndex);
1339
+ const indices = rangeInclusive(startIndex, endIndex);
1340
+ table.updateSelectedItems(selection => {
1341
+ const newSelection = new Map(selection);
1342
+ for (let i = indices.length - 1; i >= 0; i--) {
1343
+ const currentIndex = indices[i];
1344
+ const previousIndex = indices[i - 1];
1345
+ const item = table.rowsByIndex().get(currentIndex)?.item;
1346
+ if (!item) {
1347
+ continue;
1348
+ }
1349
+ const itemId = table.trackBy(item);
1350
+ const previousItem = table.rowsByIndex().get(previousIndex)?.item;
1351
+ const previousItemId = previousItem ? table.trackBy(previousItem) : undefined;
1352
+ if (selection.has(previousItemId)) {
1353
+ newSelection.delete(itemId);
1354
+ }
1355
+ else {
1356
+ newSelection.set(itemId, item);
1357
+ }
1358
+ }
1359
+ return newSelection;
1360
+ });
1361
+ }
1362
+ toggleSelectedItem(item) {
1363
+ const table = this._table();
1364
+ const id = table.trackBy(item);
1365
+ table.updateSelectedItems(selection => {
1366
+ const next = new Map(selection);
1367
+ if (next.has(id)) {
1368
+ next.delete(id);
1369
+ }
1370
+ else {
1371
+ if (table.selectable() === 'single') {
1372
+ next.clear();
1373
+ }
1374
+ next.set(id, item);
1375
+ }
1376
+ return next;
1377
+ });
1378
+ }
1379
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1380
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableSelectionService });
1381
+ }
1382
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableSelectionService, decorators: [{
1383
+ type: Injectable
1384
+ }] });
1385
+
1386
+ /*
1387
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1388
+ *
1389
+ * This program and the accompanying materials are made
1390
+ * available under the terms of the Eclipse Public License 2.0
1391
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1392
+ *
1393
+ * SPDX-License-Identifier: EPL-2.0
1394
+ */
1395
+ class SciTableCellComponent {
1396
+ cell = input.required(/* @ts-ignore */
1397
+ ...(ngDevMode ? [{ debugName: "cell" }] : /* istanbul ignore next */ []));
1398
+ row = input.required(/* @ts-ignore */
1399
+ ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
1400
+ template = this.computeTemplate();
1401
+ templateContext = this.computeTemplateContext();
1402
+ partAttribute = this.computePartAttribute();
1403
+ computeTemplate() {
1404
+ return computed(() => {
1405
+ const cell = this.cell();
1406
+ if (cell.type !== 'template') {
1407
+ return null;
1408
+ }
1409
+ return coerceSignal(cell.template.template)();
1410
+ });
1411
+ }
1412
+ computeTemplateContext() {
1413
+ return computed(() => {
1414
+ const cell = this.cell();
1415
+ const item = this.row().item;
1416
+ if (cell.type !== 'template') {
1417
+ return null;
1418
+ }
1419
+ return {
1420
+ $implicit: item,
1421
+ ...Objects.entries(cell.template.context ?? {}).reduce((context, [key, value]) => ({
1422
+ ...context,
1423
+ [key]: coerceSignal(value)?.(), // eslint-disable-line @typescript-eslint/no-unnecessary-condition
1424
+ }), {}),
1425
+ };
1426
+ });
1427
+ }
1428
+ computePartAttribute() {
1429
+ return computed(() => {
1430
+ return [
1431
+ ...Arrays.coerce(this.row().bindings?.part()),
1432
+ this.cell().column.name,
1433
+ ].join(' ');
1434
+ });
1435
+ }
1436
+ 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"] }] });
1438
+ }
1439
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellComponent, decorators: [{
1440
+ type: Component,
1441
+ args: [{ selector: 'sci-table-cell', host: {
1442
+ '[attr.data-type]': 'cell().column.type',
1443
+ '[attr.data-column]': 'cell().column.name',
1444
+ '[attr.data-padding]': '!cell().column.padding ? false : null',
1445
+ '[attr.part]': 'row().selected() ? null : partAttribute()', // prevent styling selected rows
1446
+ }, imports: [
1447
+ NgTemplateOutlet,
1448
+ SciIconComponent,
1449
+ 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"] }]
1451
+ }], propDecorators: { cell: [{ type: i0.Input, args: [{ isSignal: true, alias: "cell", required: true }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: true }] }] } });
1452
+
1453
+ /*
1454
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1455
+ *
1456
+ * This program and the accompanying materials are made
1457
+ * available under the terms of the Eclipse Public License 2.0
1458
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1459
+ *
1460
+ * SPDX-License-Identifier: EPL-2.0
1461
+ */
1462
+ class SciTableRowComponent {
1463
+ row = input.required(/* @ts-ignore */
1464
+ ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
1465
+ primaryAction = output();
1466
+ table = inject(ɵSCI_TABLE);
1467
+ rowActionToolbar = viewChild(SciToolbarComponent, /* @ts-ignore */
1468
+ ...(ngDevMode ? [{ debugName: "rowActionToolbar" }] : /* istanbul ignore next */ []));
1469
+ rowActionsToolbarName = `toolbar:${UUID.randomUUID()}`;
1470
+ _selectionService = inject(SciTableSelectionService);
1471
+ constructor() {
1472
+ this.contributeRowActions();
1473
+ }
1474
+ onRowClick(event) {
1475
+ if (this.row().loading) {
1476
+ return;
1477
+ }
1478
+ void this._selectionService.onRowClick(this.row().index, event);
1479
+ }
1480
+ onRowDblClick(event) {
1481
+ if (this.row().loading) {
1482
+ return;
1483
+ }
1484
+ this.primaryAction.emit({ sourceEvent: event, items: [this.row().item] });
1485
+ }
1486
+ onRowMouseEnter() {
1487
+ this.table().hoveredIndex.set(this.row().index);
1488
+ }
1489
+ onRowMouseLeave(event) {
1490
+ const next = event.relatedTarget;
1491
+ // Do not unset hovered row when hovering a column resize splitter.
1492
+ if (next instanceof Element && next.closest('sci-column-splitter')) {
1493
+ return;
1494
+ }
1495
+ this.table().hoveredIndex.set(-1);
1496
+ }
1497
+ onActionToolbarClick(event) {
1498
+ event.stopPropagation(); // prevent selecting the row
1499
+ this.table().activeItem.set(this.row().item);
1500
+ }
1501
+ contributeRowActions() {
1502
+ contributeMenu(this.rowActionsToolbarName, toolbar => {
1503
+ const rowActionsFactoryFn = this.table().rowActions;
1504
+ if (!this.row().loading && rowActionsFactoryFn) {
1505
+ rowActionsFactoryFn(toolbar, this.row().item, this.row().index);
1506
+ }
1507
+ });
1508
+ }
1509
+ 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"] }] });
1511
+ }
1512
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableRowComponent, decorators: [{
1513
+ type: Component,
1514
+ args: [{ selector: 'sci-table-row', imports: [
1515
+ SciTableCellComponent,
1516
+ SciToolbarComponent,
1517
+ ], host: {
1518
+ '[attr.data-active]': `row().active() ? '' : null`,
1519
+ '[attr.data-selected]': `row().selected() ? '' : null`,
1520
+ '[attr.data-hovered]': `row().hovered() ? '' : null`,
1521
+ '[class]': 'row().bindings?.cssClass?.()',
1522
+ '(click)': 'onRowClick($event)',
1523
+ '(dblclick)': 'onRowDblClick($event)',
1524
+ '(mouseenter)': 'onRowMouseEnter()',
1525
+ '(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"] }]
1527
+ }], 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
+
1529
+ /*
1530
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1531
+ *
1532
+ * This program and the accompanying materials are made
1533
+ * available under the terms of the Eclipse Public License 2.0
1534
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1535
+ *
1536
+ * SPDX-License-Identifier: EPL-2.0
1537
+ */
1538
+ class SciTableKeyboardNavigatorDirective {
1539
+ selectionService = inject(SciTableSelectionService);
1540
+ /**
1541
+ * Indicates whether the given keyboard event was triggered from an input element.
1542
+ */
1543
+ isFromInput(event) {
1544
+ if (event.target instanceof Element && event.target.closest('input, textarea, select') !== null) {
1545
+ return true;
1546
+ }
1547
+ if (event.target instanceof HTMLElement && event.target.isContentEditable) {
1548
+ return true;
1549
+ }
1550
+ return false;
1551
+ }
1552
+ /**
1553
+ * Indicates whether the given keyboard event was triggered from a button.
1554
+ */
1555
+ isFromButton(event) {
1556
+ return event.target instanceof Element && event.target.closest('button') !== null;
1557
+ }
1558
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableKeyboardNavigatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1559
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: SciTableKeyboardNavigatorDirective, isStandalone: true, selector: "[sciTableKeyboardNavigator]", host: { listeners: { "keydown.arrowUp": "isFromInput($event) || selectionService.onArrowUp($event)", "keydown.shift.arrowUp": "isFromInput($event) || selectionService.onArrowUp($event)", "keydown.control.arrowUp": "isFromInput($event) || selectionService.onArrowUp($event)", "keydown.meta.arrowUp": "isFromInput($event) || selectionService.onArrowUp($event)", "keydown.shift.control.arrowUp": "isFromInput($event) || selectionService.onArrowUp($event)", "keydown.shift.meta.arrowUp": "isFromInput($event) || selectionService.onArrowUp($event)", "keydown.arrowDown": "isFromInput($event) || selectionService.onArrowDown($event)", "keydown.shift.arrowDown": "isFromInput($event) || selectionService.onArrowDown($event)", "keydown.control.arrowDown": "isFromInput($event) || selectionService.onArrowDown($event)", "keydown.meta.arrowDown": "isFromInput($event) || selectionService.onArrowDown($event)", "keydown.shift.control.arrowDown": "isFromInput($event) || selectionService.onArrowDown($event)", "keydown.shift.meta.arrowDown": "isFromInput($event) || selectionService.onArrowDown($event)", "keydown.space": "isFromInput($event) || isFromButton($event) || selectionService.onSpace($event)", "keydown.control.space": "isFromInput($event) || isFromButton($event) || selectionService.onSpace($event)", "keydown.meta.space": "isFromInput($event) || isFromButton($event) || selectionService.onSpace($event)", "keydown.control.a": "isFromInput($event) || selectionService.onControlA($event)", "keydown.meta.a": "isFromInput($event) || selectionService.onControlA($event)", "keydown.pageUp": "isFromInput($event) || selectionService.onPageUp($event)", "keydown.shift.pageUp": "isFromInput($event) || selectionService.onPageUp($event)", "keydown.control.pageUp": "isFromInput($event) || selectionService.onPageUp($event)", "keydown.shift.control.pageUp": "isFromInput($event) || selectionService.onPageUp($event)", "keydown.pageDown": "isFromInput($event) || selectionService.onPageDown($event)", "keydown.shift.pageDown": "isFromInput($event) || selectionService.onPageDown($event)", "keydown.control.pageDown": "isFromInput($event) || selectionService.onPageDown($event)", "keydown.shift.control.pageDown": "isFromInput($event) || selectionService.onPageDown($event)", "keydown.home": "isFromInput($event) || selectionService.onHome($event)", "keydown.shift.home": "isFromInput($event) || selectionService.onHome($event)", "keydown.control.home": "isFromInput($event) || selectionService.onHome($event)", "keydown.shift.control.home": "isFromInput($event) || selectionService.onHome($event)", "keydown.end": "isFromInput($event) || selectionService.onEnd($event)", "keydown.shift.end": "isFromInput($event) || selectionService.onEnd($event)", "keydown.control.end": "isFromInput($event) || selectionService.onEnd($event)", "keydown.shift.control.end": "isFromInput($event) || selectionService.onEnd($event)" }, properties: { "attr.tabindex": "0" } }, ngImport: i0 });
1560
+ }
1561
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableKeyboardNavigatorDirective, decorators: [{
1562
+ type: Directive,
1563
+ args: [{
1564
+ selector: '[sciTableKeyboardNavigator]',
1565
+ host: {
1566
+ '[attr.tabindex]': '0', // required for keyboard navigation
1567
+ '(keydown.arrowUp)': 'isFromInput($event) || selectionService.onArrowUp($event)',
1568
+ '(keydown.shift.arrowUp)': 'isFromInput($event) || selectionService.onArrowUp($event)',
1569
+ '(keydown.control.arrowUp)': 'isFromInput($event) || selectionService.onArrowUp($event)',
1570
+ '(keydown.meta.arrowUp)': 'isFromInput($event) || selectionService.onArrowUp($event)',
1571
+ '(keydown.shift.control.arrowUp)': 'isFromInput($event) || selectionService.onArrowUp($event)',
1572
+ '(keydown.shift.meta.arrowUp)': 'isFromInput($event) || selectionService.onArrowUp($event)',
1573
+ '(keydown.arrowDown)': 'isFromInput($event) || selectionService.onArrowDown($event)',
1574
+ '(keydown.shift.arrowDown)': 'isFromInput($event) || selectionService.onArrowDown($event)',
1575
+ '(keydown.control.arrowDown)': 'isFromInput($event) || selectionService.onArrowDown($event)',
1576
+ '(keydown.meta.arrowDown)': 'isFromInput($event) || selectionService.onArrowDown($event)',
1577
+ '(keydown.shift.control.arrowDown)': 'isFromInput($event) || selectionService.onArrowDown($event)',
1578
+ '(keydown.shift.meta.arrowDown)': 'isFromInput($event) || selectionService.onArrowDown($event)',
1579
+ '(keydown.space)': 'isFromInput($event) || isFromButton($event) || selectionService.onSpace($event)',
1580
+ '(keydown.control.space)': 'isFromInput($event) || isFromButton($event) || selectionService.onSpace($event)',
1581
+ '(keydown.meta.space)': 'isFromInput($event) || isFromButton($event) || selectionService.onSpace($event)',
1582
+ '(keydown.control.a)': 'isFromInput($event) || selectionService.onControlA($event)',
1583
+ '(keydown.meta.a)': 'isFromInput($event) || selectionService.onControlA($event)',
1584
+ '(keydown.pageUp)': 'isFromInput($event) || selectionService.onPageUp($event)',
1585
+ '(keydown.shift.pageUp)': 'isFromInput($event) || selectionService.onPageUp($event)',
1586
+ '(keydown.control.pageUp)': 'isFromInput($event) || selectionService.onPageUp($event)',
1587
+ '(keydown.shift.control.pageUp)': 'isFromInput($event) || selectionService.onPageUp($event)',
1588
+ '(keydown.pageDown)': 'isFromInput($event) || selectionService.onPageDown($event)',
1589
+ '(keydown.shift.pageDown)': 'isFromInput($event) || selectionService.onPageDown($event)',
1590
+ '(keydown.control.pageDown)': 'isFromInput($event) || selectionService.onPageDown($event)',
1591
+ '(keydown.shift.control.pageDown)': 'isFromInput($event) || selectionService.onPageDown($event)',
1592
+ '(keydown.home)': 'isFromInput($event) || selectionService.onHome($event)',
1593
+ '(keydown.shift.home)': 'isFromInput($event) || selectionService.onHome($event)',
1594
+ '(keydown.control.home)': 'isFromInput($event) || selectionService.onHome($event)',
1595
+ '(keydown.shift.control.home)': 'isFromInput($event) || selectionService.onHome($event)',
1596
+ '(keydown.end)': 'isFromInput($event) || selectionService.onEnd($event)',
1597
+ '(keydown.shift.end)': 'isFromInput($event) || selectionService.onEnd($event)',
1598
+ '(keydown.control.end)': 'isFromInput($event) || selectionService.onEnd($event)',
1599
+ '(keydown.shift.control.end)': 'isFromInput($event) || selectionService.onEnd($event)',
1600
+ },
1601
+ }]
1602
+ }] });
1603
+
1604
+ class SciTableColumnService {
1605
+ _table = inject(ɵSCI_TABLE);
1606
+ _injector = inject(Injector);
1607
+ /**
1608
+ * State while resizing a column.
1609
+ */
1610
+ _resizingState = signal(undefined, /* @ts-ignore */
1611
+ ...(ngDevMode ? [{ debugName: "_resizingState" }] : /* istanbul ignore next */ []));
1612
+ /**
1613
+ * Provides the calculated CSS `grid-template-columns` value based on configured and resized column widths.
1614
+ */
1615
+ gridTemplateColumns = this.computeGridTemplateColumns();
1616
+ /**
1617
+ * Computes the CSS `grid-template-columns` value.
1618
+ *
1619
+ * Columns are layed out according to their configured widths. While resizing a column, absolute pixel widths are used.
1620
+ */
1621
+ computeGridTemplateColumns() {
1622
+ return computed(() => {
1623
+ return this._table().columns()
1624
+ .map(column => {
1625
+ const width = this._resizingState()?.columnWidths()?.get(column.name) ?? column.width();
1626
+ if (typeof width === 'number') {
1627
+ return `${width}px`;
1628
+ }
1629
+ if (width.endsWith('fr')) {
1630
+ return cssMinmax({ min: column.minWidth, max: width });
1631
+ }
1632
+ return width;
1633
+ })
1634
+ .join(' ') || '100%'; // fill full width if no column
1635
+ });
1636
+ }
1637
+ startResize(column) {
1638
+ column.resizing.set(true);
1639
+ this._resizingState.set({ column, columnWidths: signal(undefined) });
1640
+ }
1641
+ resize(columnWidth) {
1642
+ const state = this._resizingState();
1643
+ const clampedWidth = typeof columnWidth === 'number' ? clamp(columnWidth, { min: state.column.minWidth }) : columnWidth;
1644
+ if (clampedWidth !== state.columnWidths()?.get(state.column.name)) {
1645
+ const columnWidths = this.calculateColumnWidths(state.column, clampedWidth);
1646
+ state.columnWidths.set(columnWidths);
1647
+ }
1648
+ }
1649
+ endResize() {
1650
+ const { column } = this._resizingState();
1651
+ // Run on next render to update user settings with effective column widths read from the DOM.
1652
+ afterNextRender(() => {
1653
+ this.updateUserSettings(column);
1654
+ column.resizing.set(false);
1655
+ this._resizingState.set(undefined);
1656
+ }, { injector: this._injector });
1657
+ }
1658
+ /**
1659
+ * Updates user settings with current column widths.
1660
+ */
1661
+ updateUserSettings(resizedColumn) {
1662
+ const resizedColumnIndex = this._table().columns().indexOf(resizedColumn);
1663
+ this._table().userSettings.update(userSettings => ({
1664
+ ...userSettings,
1665
+ columns: this._table().columns().flatMap((column, index) => {
1666
+ const columnSettings = userSettings.columns?.find(it => it.name === column.name);
1667
+ // Store the width of the resized column and all columns to its left.
1668
+ if (index <= resizedColumnIndex) {
1669
+ return {
1670
+ ...columnSettings ?? { name: column.name },
1671
+ width: column.location.width,
1672
+ };
1673
+ }
1674
+ return columnSettings ?? [];
1675
+ }),
1676
+ }));
1677
+ }
1678
+ /**
1679
+ * Calculates absolute column widths based on the resized column's new width.
1680
+ *
1681
+ * Any remaining viewport space is distributed proportionally among flex-sized columns using their current width ratio.
1682
+ */
1683
+ calculateColumnWidths(columnToResize, newColumnWidth) {
1684
+ const columns = this._table().columns();
1685
+ const columnIndex = columns.indexOf(columnToResize);
1686
+ return columns.reduce((map, column, index) => {
1687
+ if (index < columnIndex) {
1688
+ return map.set(column.name, column.location.width); // columns to the left are fixed
1689
+ }
1690
+ if (index === columnIndex) {
1691
+ return map.set(column.name, newColumnWidth);
1692
+ }
1693
+ return map;
1694
+ }, new Map());
1695
+ }
1696
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableColumnService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1697
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableColumnService });
1698
+ }
1699
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableColumnService, decorators: [{
1700
+ type: Injectable
1701
+ }] });
1702
+ /**
1703
+ * Creates a minmax() CSS function with the given min/max for use in a CSS grid.
1704
+ */
1705
+ function cssMinmax(minmax) {
1706
+ const min = `${minmax.min}px`;
1707
+ const max = typeof minmax.max === 'number' ? `${minmax.max}px` : minmax.max;
1708
+ return `minmax(${min}, ${max})`;
1709
+ }
1710
+
1711
+ /*
1712
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1713
+ *
1714
+ * This program and the accompanying materials are made
1715
+ * available under the terms of the Eclipse Public License 2.0
1716
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1717
+ *
1718
+ * SPDX-License-Identifier: EPL-2.0
1719
+ */
1720
+ class SciTableColumnComponent {
1721
+ column = input.required(/* @ts-ignore */
1722
+ ...(ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []));
1723
+ constructor() {
1724
+ this.injectLocationToColumn();
1725
+ }
1726
+ injectLocationToColumn() {
1727
+ const element = inject(ElementRef).nativeElement;
1728
+ effect(onCleanup => {
1729
+ const column = this.column();
1730
+ untracked(() => {
1731
+ const location = column.location = {
1732
+ get x() {
1733
+ return element.getBoundingClientRect().left;
1734
+ },
1735
+ get width() {
1736
+ return element.offsetWidth;
1737
+ },
1738
+ };
1739
+ onCleanup(() => {
1740
+ if (column.location === location) {
1741
+ column.location = { x: 0, width: 0 };
1742
+ }
1743
+ });
1744
+ });
1745
+ });
1746
+ }
1747
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableColumnComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1748
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: SciTableColumnComponent, isStandalone: true, selector: "sci-table-column", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-column": "column().name", "style.min-width.px": "column().minWidth" } }, ngImport: i0, template: '', isInline: true });
1749
+ }
1750
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableColumnComponent, decorators: [{
1751
+ type: Component,
1752
+ args: [{
1753
+ selector: 'sci-table-column',
1754
+ template: '',
1755
+ host: {
1756
+ '[attr.data-column]': 'column().name',
1757
+ '[style.min-width.px]': 'column().minWidth', // required when resizing the column to its preferred size to not drop below min-width.
1758
+ },
1759
+ }]
1760
+ }], ctorParameters: () => [], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }] } });
1761
+
1762
+ /*
1763
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1764
+ *
1765
+ * This program and the accompanying materials are made
1766
+ * available under the terms of the Eclipse Public License 2.0
1767
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1768
+ *
1769
+ * SPDX-License-Identifier: EPL-2.0
1770
+ */
1771
+ class SciTableColumnsComponent {
1772
+ rows = input.required(/* @ts-ignore */
1773
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
1774
+ table = inject(ɵSCI_TABLE);
1775
+ _hovered = signal(false, /* @ts-ignore */
1776
+ ...(ngDevMode ? [{ debugName: "_hovered" }] : /* istanbul ignore next */ []));
1777
+ _columnService = inject(SciTableColumnService);
1778
+ onResizeStart(column) {
1779
+ this._columnService.startResize(column);
1780
+ }
1781
+ onResize(column, event) {
1782
+ const pointerPosition = event.position.clientPos; // TODO [dwi] Change to screen coordinates to work with microfrontends
1783
+ const columnStart = column.location.x - 1; // -1 because splitters are positioned at the end of cell content
1784
+ const newColumnWidth = pointerPosition - columnStart;
1785
+ this._columnService.resize(newColumnWidth);
1786
+ }
1787
+ onResizeEnd() {
1788
+ this._columnService.endResize();
1789
+ // Clear the hovered row when resizing ends outside the splitter (for example, above or below it).
1790
+ if (!this._hovered()) {
1791
+ this.table().hoveredIndex.set(-1);
1792
+ }
1793
+ }
1794
+ /**
1795
+ * Resizes the column to its preferred size.
1796
+ */
1797
+ onPack(column) {
1798
+ this._columnService.startResize(column);
1799
+ this._columnService.resize('min-content');
1800
+ this._columnService.endResize();
1801
+ }
1802
+ onMouseEnter() {
1803
+ this._hovered.set(true);
1804
+ }
1805
+ onMouseLeave() {
1806
+ this._hovered.set(false);
1807
+ // Do not clear the hovered row while dragging the resize handle, or quick movements will accidentally clear it.
1808
+ // If the mouse leaves the splitter during drag (above or below), the hovered row is unset when resizing ends.
1809
+ if (!this.table().resizing()) {
1810
+ this.table().hoveredIndex.set(-1);
1811
+ }
1812
+ }
1813
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableColumnsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1814
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciTableColumnsComponent, isStandalone: true, selector: "sci-table-columns", inputs: { rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<!-- Render a resize splitter per resizable column. -->\n@for (column of table().columns(); track column.name; let i = $index) {\n @if (column.resizable()) {\n <sci-splitter orientation=\"vertical\"\n [class.resizing]=\"column.resizing()\"\n [attr.data-column]=\"column.name\"\n [style.grid-column]=\"i + 1\"\n (start)=\"onResizeStart(column)\"\n (move)=\"onResize(column, $event)\"\n (end)=\"onResizeEnd()\"\n (reset)=\"onPack(column)\"\n (mouseenter)=\"onMouseEnter()\"\n (mouseleave)=\"onMouseLeave()\"/>\n }\n}\n\n<!-- Render an invisible synthetic column per column to read current widths and set minimum widths. -->\n@for (column of table().columns(); track column.name; let i = $index) {\n <sci-table-column [column]=\"column\" [style.grid-column]=\"i + 1\"/>\n}\n", styles: ["@charset \"UTF-8\";:host{display:grid;grid-template-columns:subgrid;overflow-x:clip;pointer-events:none}@container (style(--sci-table-header-column-divider: visible)) and (style(--\\275sci-table-header-visible: true)){:host{--\\275sci-table-header-column-divider-visible: true}}:host:has(>sci-splitter:hover){--\\275sci-column-splitter-hover: true}:host>sci-splitter,:host>sci-table-column{grid-row:1}:host>sci-splitter{--sci-splitter-size: var(--\\275sci-table-border-size);--sci-splitter-size-hover: var(--\\275sci-table-border-size);--sci-splitter-cross-axis-size: 0;--sci-splitter-background-color: var(--\\275sci-table-border-color);justify-self:end;pointer-events:auto}@container style(--\\275sci-table-header-column-divider-visible: true){:host>sci-splitter{--sci-splitter-cross-axis-start: .5em;--sci-splitter-cross-axis-size: calc(var(--\\275sci-table-header-height) - 2 * var(--sci-splitter-cross-axis-start))}@container style(--\\275sci-table-viewport-client-width >= --\\275sci-table-viewport-width){:host>sci-splitter:last-of-type:not(.resizing):not(:hover){--sci-splitter-cross-axis-size: 0}}}:host>sci-splitter:is(:hover,.resizing){--sci-splitter-background-color: rgb(from var(--\\275sci-table-border-color) r g b / .5);--sci-splitter-cross-axis-size: 100%}@container style(--\\275sci-table-header-column-divider-visible: true){:host>sci-splitter:is(:hover,.resizing){--sci-splitter-cross-axis-size: auto;--sci-splitter-cross-axis-end: 0}}@container style(--\\275sci-table-resizing: true){:host>sci-splitter:not(.resizing){pointer-events:none}}@container (style(--\\275sci-table-scrolling: true)) and (not style(--\\275sci-table-resizing: true)) and (not style(--\\275sci-column-splitter-hover: true)){:host>sci-splitter{pointer-events:none}}:host>sci-table-column{height:0}\n"], dependencies: [{ kind: "component", type: SciSplitterComponent, selector: "sci-splitter", inputs: ["orientation"], outputs: ["start", "move", "end", "reset"] }, { kind: "component", type: SciTableColumnComponent, selector: "sci-table-column", inputs: ["column"] }] });
1815
+ }
1816
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableColumnsComponent, decorators: [{
1817
+ type: Component,
1818
+ args: [{ selector: 'sci-table-columns', imports: [
1819
+ SciSplitterComponent,
1820
+ SciTableColumnComponent,
1821
+ ], template: "<!-- Render a resize splitter per resizable column. -->\n@for (column of table().columns(); track column.name; let i = $index) {\n @if (column.resizable()) {\n <sci-splitter orientation=\"vertical\"\n [class.resizing]=\"column.resizing()\"\n [attr.data-column]=\"column.name\"\n [style.grid-column]=\"i + 1\"\n (start)=\"onResizeStart(column)\"\n (move)=\"onResize(column, $event)\"\n (end)=\"onResizeEnd()\"\n (reset)=\"onPack(column)\"\n (mouseenter)=\"onMouseEnter()\"\n (mouseleave)=\"onMouseLeave()\"/>\n }\n}\n\n<!-- Render an invisible synthetic column per column to read current widths and set minimum widths. -->\n@for (column of table().columns(); track column.name; let i = $index) {\n <sci-table-column [column]=\"column\" [style.grid-column]=\"i + 1\"/>\n}\n", styles: ["@charset \"UTF-8\";:host{display:grid;grid-template-columns:subgrid;overflow-x:clip;pointer-events:none}@container (style(--sci-table-header-column-divider: visible)) and (style(--\\275sci-table-header-visible: true)){:host{--\\275sci-table-header-column-divider-visible: true}}:host:has(>sci-splitter:hover){--\\275sci-column-splitter-hover: true}:host>sci-splitter,:host>sci-table-column{grid-row:1}:host>sci-splitter{--sci-splitter-size: var(--\\275sci-table-border-size);--sci-splitter-size-hover: var(--\\275sci-table-border-size);--sci-splitter-cross-axis-size: 0;--sci-splitter-background-color: var(--\\275sci-table-border-color);justify-self:end;pointer-events:auto}@container style(--\\275sci-table-header-column-divider-visible: true){:host>sci-splitter{--sci-splitter-cross-axis-start: .5em;--sci-splitter-cross-axis-size: calc(var(--\\275sci-table-header-height) - 2 * var(--sci-splitter-cross-axis-start))}@container style(--\\275sci-table-viewport-client-width >= --\\275sci-table-viewport-width){:host>sci-splitter:last-of-type:not(.resizing):not(:hover){--sci-splitter-cross-axis-size: 0}}}:host>sci-splitter:is(:hover,.resizing){--sci-splitter-background-color: rgb(from var(--\\275sci-table-border-color) r g b / .5);--sci-splitter-cross-axis-size: 100%}@container style(--\\275sci-table-header-column-divider-visible: true){:host>sci-splitter:is(:hover,.resizing){--sci-splitter-cross-axis-size: auto;--sci-splitter-cross-axis-end: 0}}@container style(--\\275sci-table-resizing: true){:host>sci-splitter:not(.resizing){pointer-events:none}}@container (style(--\\275sci-table-scrolling: true)) and (not style(--\\275sci-table-resizing: true)) and (not style(--\\275sci-column-splitter-hover: true)){:host>sci-splitter{pointer-events:none}}:host>sci-table-column{height:0}\n"] }]
1822
+ }], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }] } });
1823
+
1824
+ /*
1825
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1826
+ *
1827
+ * This program and the accompanying materials are made
1828
+ * available under the terms of the Eclipse Public License 2.0
1829
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1830
+ *
1831
+ * SPDX-License-Identifier: EPL-2.0
1832
+ */
1833
+ /**
1834
+ * Represents a semantic element for the table viewport provided as slotted content.
1835
+ */
1836
+ class SciTableViewportComponent {
1837
+ host = inject(ElementRef).nativeElement;
1838
+ table = inject(ɵSCI_TABLE);
1839
+ viewportHeight = computed(() => {
1840
+ const viewportHeight = this.table().tableViewRef()?.viewportHeight() ?? 0;
1841
+ const headerHeight = this.table().tableViewRef()?.headerHeight() ?? 0;
1842
+ return viewportHeight + headerHeight;
1843
+ }, /* @ts-ignore */
1844
+ ...(ngDevMode ? [{ debugName: "viewportHeight" }] : /* istanbul ignore next */ []));
1845
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableViewportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1846
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: SciTableViewportComponent, isStandalone: true, selector: "sci-table-viewport", host: { properties: { "style.--\u0275sci-table-viewport-width": "`${table().tableViewRef()?.viewportWidth() ?? 0}px`", "style.--\u0275sci-table-viewport-height": "`${viewportHeight() ?? 0}px`", "style.--\u0275sci-table-viewport-client-width": "`${table().tableViewRef()?.viewportClientWidth() ?? 0}px`", "style.--\u0275sci-table-viewport-client-height": "`${table().tableViewRef()?.viewportClientHeight() ?? 0}px`", "style.--\u0275sci-table-header-height": "`${table().tableViewRef()?.headerHeight() ?? 0}px`", "style.--\u0275sci-table-cell-padding-inline": "`${table().tableViewRef()?.cellPadding() ?? 0}px`", "style.--\u0275sci-table-row-height": "`${table().tableViewRef()?.itemHeight() ?? 0}px`" } }, ngImport: i0, template: '<ng-content/>', isInline: true });
1847
+ }
1848
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableViewportComponent, decorators: [{
1849
+ type: Component,
1850
+ args: [{
1851
+ selector: 'sci-table-viewport',
1852
+ template: '<ng-content/>',
1853
+ host: {
1854
+ '[style.--ɵsci-table-viewport-width]': '`${table().tableViewRef()?.viewportWidth() ?? 0}px`',
1855
+ '[style.--ɵsci-table-viewport-height]': '`${viewportHeight() ?? 0}px`',
1856
+ '[style.--ɵsci-table-viewport-client-width]': '`${table().tableViewRef()?.viewportClientWidth() ?? 0}px`',
1857
+ '[style.--ɵsci-table-viewport-client-height]': '`${table().tableViewRef()?.viewportClientHeight() ?? 0}px`',
1858
+ '[style.--ɵsci-table-header-height]': '`${table().tableViewRef()?.headerHeight() ?? 0}px`',
1859
+ '[style.--ɵsci-table-cell-padding-inline]': '`${table().tableViewRef()?.cellPadding() ?? 0}px`',
1860
+ '[style.--ɵsci-table-row-height]': '`${table().tableViewRef()?.itemHeight() ?? 0}px`',
1861
+ },
1862
+ }]
1863
+ }] });
1864
+
1865
+ /*
1866
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1867
+ *
1868
+ * This program and the accompanying materials are made
1869
+ * available under the terms of the Eclipse Public License 2.0
1870
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1871
+ *
1872
+ * SPDX-License-Identifier: EPL-2.0
1873
+ */
1874
+ /**
1875
+ * Represents a semantic element for the table body provided as slotted content.
1876
+ */
1877
+ class SciTableGridComponent {
1878
+ table = inject(ɵSCI_TABLE);
1879
+ gridTemplateColumns = inject(SciTableColumnService).gridTemplateColumns;
1880
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1881
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: SciTableGridComponent, isStandalone: true, selector: "sci-table-grid", host: { properties: { "style.--\u0275sci-table-columns": "gridTemplateColumns()", "style.--\u0275sci-table-virtual-scroll-offset-top": "`${table().virtualScrollOffset().top}px`", "style.--\u0275sci-table-virtual-scroll-offset-bottom": "`${table().virtualScrollOffset().bottom}px`" } }, providers: [
1882
+ SciTableColumnService,
1883
+ ], ngImport: i0, template: '<ng-content/>', isInline: true });
1884
+ }
1885
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableGridComponent, decorators: [{
1886
+ type: Component,
1887
+ args: [{
1888
+ selector: 'sci-table-grid',
1889
+ template: '<ng-content/>',
1890
+ providers: [
1891
+ SciTableColumnService,
1892
+ ],
1893
+ host: {
1894
+ '[style.--ɵsci-table-columns]': 'gridTemplateColumns()',
1895
+ '[style.--ɵsci-table-virtual-scroll-offset-top]': '`${table().virtualScrollOffset().top}px`',
1896
+ '[style.--ɵsci-table-virtual-scroll-offset-bottom]': '`${table().virtualScrollOffset().bottom}px`',
1897
+ },
1898
+ }]
1899
+ }] });
1900
+
1901
+ /*
1902
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1903
+ *
1904
+ * This program and the accompanying materials are made
1905
+ * available under the terms of the Eclipse Public License 2.0
1906
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1907
+ *
1908
+ * SPDX-License-Identifier: EPL-2.0
1909
+ */
1910
+ /**
1911
+ * Represents a semantic element for the table body provided as slotted content.
1912
+ */
1913
+ class SciTableBodyComponent {
1914
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableBodyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1915
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: SciTableBodyComponent, isStandalone: true, selector: "sci-table-body", ngImport: i0, template: '<ng-content/>', isInline: true });
1916
+ }
1917
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableBodyComponent, decorators: [{
1918
+ type: Component,
1919
+ args: [{
1920
+ selector: 'sci-table-body',
1921
+ template: '<ng-content/>',
1922
+ }]
1923
+ }] });
1924
+
1925
+ /*
1926
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1927
+ *
1928
+ * This program and the accompanying materials are made
1929
+ * available under the terms of the Eclipse Public License 2.0
1930
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1931
+ *
1932
+ * SPDX-License-Identifier: EPL-2.0
1933
+ */
1934
+ class SciColumnHeaderComponent {
1935
+ column = input.required(/* @ts-ignore */
1936
+ ...(ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []));
1937
+ table = inject(ɵSCI_TABLE);
1938
+ header = text(computed(() => this.column().header()));
1939
+ sortDirection = computed(() => this.table().sortCriteria().find(criteria => criteria.columnName === this.column().name)?.direction, /* @ts-ignore */
1940
+ ...(ngDevMode ? [{ debugName: "sortDirection" }] : /* istanbul ignore next */ []));
1941
+ onSort(event) {
1942
+ this.table().sort(this.column().name, event.ctrlKey || event.metaKey);
1943
+ }
1944
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciColumnHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1945
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciColumnHeaderComponent, isStandalone: true, selector: "sci-column-header", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-column": "column().name", "attr.title": "header()" } }, ngImport: i0, template: "@if (column().sortable()) {\n <button class=\"sort e2e-sort\"\n [attr.data-sort-direction]=\"sortDirection()\"\n (click)=\"onSort($event)\">\n <!-- Header -->\n <ng-container *ngTemplateOutlet=\"header_template\"/>\n\n <!-- Sort Indicator -->\n @switch (sortDirection()) {\n @case ('asc') {\n <sci-icon>scion.sort_ascending</sci-icon>\n }\n @case ('desc') {\n <sci-icon>scion.sort_descending</sci-icon>\n }\n @default {\n <sci-icon class=\"no-sort\">scion.sort_indicator</sci-icon>\n }\n }\n </button>\n} @else {\n <ng-container *ngTemplateOutlet=\"header_template\"/>\n}\n\n<ng-template #header_template>\n <span class=\"header e2e-header\" [class.wrap]=\"table().wrapHeader()\">{{header()}}</span>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:grid;align-items:center}:host:not(:has(button.sort)){padding-inline:var(--\\275sci-table-cell-padding-inline)}:host:is(:focus-visible,:hover){--\\275sci-table-column-header-hover: true}:host>button.sort{all:unset;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.5em;padding-inline:var(--\\275sci-table-cell-padding-inline);cursor:var(--sci-table-header-cursor);align-self:stretch}@container not style(--\\275sci-table-column-header-hover: true){:host>button.sort>sci-icon.no-sort{visibility:hidden}}@container style(--\\275sci-table-column-header-hover: true){:host>button.sort{background-color:var(--sci-table-header-background-color-hover)}}:host span.header{display:inline;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--sci-table-header-text-color);font-family:var(--sci-table-header-font-family),sans-serif;font-weight:var(--sci-table-header-font-weight);font-size:var(--sci-table-header-font-size);padding-block:.25em}:host span.header.wrap{white-space:wrap}\n"], dependencies: [{ kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
1946
+ }
1947
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciColumnHeaderComponent, decorators: [{
1948
+ type: Component,
1949
+ args: [{ selector: 'sci-column-header', imports: [
1950
+ SciIconComponent,
1951
+ NgTemplateOutlet,
1952
+ ], host: {
1953
+ '[attr.data-column]': 'column().name',
1954
+ '[attr.title]': 'header()',
1955
+ }, template: "@if (column().sortable()) {\n <button class=\"sort e2e-sort\"\n [attr.data-sort-direction]=\"sortDirection()\"\n (click)=\"onSort($event)\">\n <!-- Header -->\n <ng-container *ngTemplateOutlet=\"header_template\"/>\n\n <!-- Sort Indicator -->\n @switch (sortDirection()) {\n @case ('asc') {\n <sci-icon>scion.sort_ascending</sci-icon>\n }\n @case ('desc') {\n <sci-icon>scion.sort_descending</sci-icon>\n }\n @default {\n <sci-icon class=\"no-sort\">scion.sort_indicator</sci-icon>\n }\n }\n </button>\n} @else {\n <ng-container *ngTemplateOutlet=\"header_template\"/>\n}\n\n<ng-template #header_template>\n <span class=\"header e2e-header\" [class.wrap]=\"table().wrapHeader()\">{{header()}}</span>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:grid;align-items:center}:host:not(:has(button.sort)){padding-inline:var(--\\275sci-table-cell-padding-inline)}:host:is(:focus-visible,:hover){--\\275sci-table-column-header-hover: true}:host>button.sort{all:unset;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.5em;padding-inline:var(--\\275sci-table-cell-padding-inline);cursor:var(--sci-table-header-cursor);align-self:stretch}@container not style(--\\275sci-table-column-header-hover: true){:host>button.sort>sci-icon.no-sort{visibility:hidden}}@container style(--\\275sci-table-column-header-hover: true){:host>button.sort{background-color:var(--sci-table-header-background-color-hover)}}:host span.header{display:inline;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--sci-table-header-text-color);font-family:var(--sci-table-header-font-family),sans-serif;font-weight:var(--sci-table-header-font-weight);font-size:var(--sci-table-header-font-size);padding-block:.25em}:host span.header.wrap{white-space:wrap}\n"] }]
1956
+ }], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }] } });
1957
+
1958
+ /*
1959
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1960
+ *
1961
+ * This program and the accompanying materials are made
1962
+ * available under the terms of the Eclipse Public License 2.0
1963
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1964
+ *
1965
+ * SPDX-License-Identifier: EPL-2.0
1966
+ */
1967
+ class SciColumnFilterBooleanLabelPipe {
1968
+ transform(value) {
1969
+ if (value === true) {
1970
+ return '%scion.components.yes.value';
1971
+ }
1972
+ if (value === false) {
1973
+ return '%scion.components.no.value';
1974
+ }
1975
+ return null;
1976
+ }
1977
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciColumnFilterBooleanLabelPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
1978
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: SciColumnFilterBooleanLabelPipe, isStandalone: true, name: "sciColumnFilterBooleanLabel" });
1979
+ }
1980
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciColumnFilterBooleanLabelPipe, decorators: [{
1981
+ type: Pipe,
1982
+ args: [{ name: 'sciColumnFilterBooleanLabel' }]
1983
+ }] });
1984
+
1985
+ /*
1986
+ * Copyright (c) 2018-2026 Swiss Federal Railways
1987
+ *
1988
+ * This program and the accompanying materials are made
1989
+ * available under the terms of the Eclipse Public License 2.0
1990
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
1991
+ *
1992
+ * SPDX-License-Identifier: EPL-2.0
1993
+ */
1994
+ class SciColumnFilterComponent {
1995
+ column = input.required(/* @ts-ignore */
1996
+ ...(ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []));
1997
+ filter = signal(null, /* @ts-ignore */
1998
+ ...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
1999
+ input = viewChild('input', /* @ts-ignore */
2000
+ ...(ngDevMode ? [{ debugName: "input" }] : /* istanbul ignore next */ []));
2001
+ _table = inject(ɵSCI_TABLE);
2002
+ _menuService = inject(_SciMenuService);
2003
+ _injector = inject(Injector);
2004
+ _host = inject(ElementRef).nativeElement;
2005
+ _menu;
2006
+ constructor() {
2007
+ this.bindToModel();
2008
+ }
2009
+ onFilterClick() {
2010
+ if (this.column().type === 'boolean') {
2011
+ this.toggleBooleanMenu();
2012
+ }
2013
+ this.input().nativeElement.focus();
2014
+ }
2015
+ onReset(event) {
2016
+ this.filter.set(null);
2017
+ this.input().nativeElement.focus();
2018
+ event.stopPropagation(); // prevent opening menu
2019
+ }
2020
+ bindToModel() {
2021
+ const filterDebounced = debounced(computed(() => trim(this.filter())), async (value) => {
2022
+ // Debounce only if not empty.
2023
+ if (value !== null && typeof value !== 'boolean') {
2024
+ await firstValueFrom(timer(200));
2025
+ }
2026
+ });
2027
+ effect(() => {
2028
+ const filter = filterDebounced.value();
2029
+ const table = this._table();
2030
+ const column = this.column();
2031
+ untracked(() => {
2032
+ table.filter(filter, { columnName: column.name });
2033
+ });
2034
+ });
2035
+ }
2036
+ toggleBooleanMenu() {
2037
+ // Close menu if already opened.
2038
+ if (this._menu) {
2039
+ this._menu.close();
2040
+ return;
2041
+ }
2042
+ const injector = createDestroyableInjector({ parent: this._injector });
2043
+ runInInjectionContext(injector, () => {
2044
+ // Contribute menu items.
2045
+ contributeMenu('menu:column-filter', menu => menu
2046
+ .addMenuItem({ label: '%scion.components.yes.value', attributes: { 'data-option': 'true' }, onSelect: () => this.filter.set(true) })
2047
+ .addMenuItem({ label: '%scion.components.no.value', attributes: { 'data-option': 'false' }, onSelect: () => this.filter.set(false) }));
2048
+ // Open menu.
2049
+ this._menu = this._menuService.open('menu:column-filter', {
2050
+ anchor: this._host,
2051
+ minWidth: `${this._host.offsetWidth}px`,
2052
+ offset: { y: -1 },
2053
+ });
2054
+ // Close menu when start resizing a column. By default, closes the menu only on mouse up.
2055
+ toObservable(this._table().resizing)
2056
+ .pipe(skip(1))
2057
+ .subscribe(() => this._menu?.close());
2058
+ this._menu.onClose(() => {
2059
+ injector.destroy();
2060
+ // Restore focus manually as it is not restored automatically since opening the popover inside Shadow DOM.
2061
+ // TODO [menu] Consider restore focus manually in Menu API due to restriction in shadow DOM.
2062
+ this.input().nativeElement.focus();
2063
+ this._menu = undefined;
2064
+ });
2065
+ });
2066
+ }
2067
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciColumnFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2068
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciColumnFilterComponent, isStandalone: true, selector: "sci-column-filter", inputs: { column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "click": "column().filterable() && onFilterClick()" }, properties: { "attr.data-column": "column().name", "attr.data-disabled": "!column().filterable() ? '' : null" } }, viewQueries: [{ propertyName: "input", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (column().filterable()) {\n @let active = filter() !== null && filter() !== '';\n\n <sci-icon class=\"filter e2e-filter\" [class.active]=\"active\">scion.filter</sci-icon>\n\n @switch (column().type) {\n @case ('number') {\n <input [(ngModel)]=\"filter\" type=\"number\" #input/>\n }\n @case ('boolean') {\n <input [ngModel]=\"(filter() | sciColumnFilterBooleanLabel | sciText)()\" type=\"button\" #input>\n <sci-icon class=\"chevron\">scion.chevron_down</sci-icon>\n }\n @default {\n <input [(ngModel)]=\"filter\" type=\"text\" #input/>\n }\n }\n\n @if (active) {\n <button class=\"clear e2e-clear\" (click)=\"onReset($event)\">\n <sci-icon>scion.clear</sci-icon>\n </button>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;gap:.5em;align-items:center;padding-inline:var(--\\275sci-table-cell-padding-inline);border-bottom:var(--\\275sci-table-border-size) solid transparent;margin-bottom:-1px;transition:border-bottom-color ease-in-out .15s,color ease-in-out .15s}:host:focus-within:not(:disabled):not(:has(input:disabled)){border-bottom-color:var(--sci-color-accent)}:host>sci-icon.filter.active{color:var(--sci-color-accent)}:host>sci-icon.chevron{--sci-icon-size: var(--sci-button-icon-size)}:host:has(input[type=button]){cursor:var(--sci-button-cursor)}:host:has(input[type=button]):has(button.clear)>sci-icon.chevron{display:none}:host>input{all:unset;flex:auto;align-self:stretch;width:0;min-width:0;color:var(--sci-color-text);appearance:auto;box-sizing:border-box}:host>input[type=number]::-webkit-outer-spin-button,:host>input[type=number]::-webkit-inner-spin-button{appearance:none;margin:0}:host>button.clear{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>button.clear:hover:where(:not(:disabled)){background-color:var(--sci-button-background-color-hover)}:host>button.clear:active:where(:not(:disabled)){background-color:var(--sci-button-background-color-active)}:host>button.clear:focus:not(:focus-visible){outline:none}:host>button.clear:focus-visible{outline:var(--sci-button-outline-width) solid var(--sci-color-accent)}:host>button.clear:disabled{color:var(--sci-button-text-color-disabled)}:host>button.clear{padding:1px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "pipe", type: SciTextPipe, name: "sciText" }, { kind: "pipe", type: SciColumnFilterBooleanLabelPipe, name: "sciColumnFilterBooleanLabel" }] });
2069
+ }
2070
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciColumnFilterComponent, decorators: [{
2071
+ type: Component,
2072
+ args: [{ selector: 'sci-column-filter', imports: [
2073
+ FormsModule,
2074
+ SciIconComponent,
2075
+ SciTextPipe,
2076
+ SciColumnFilterBooleanLabelPipe,
2077
+ ], host: {
2078
+ '[attr.data-column]': 'column().name',
2079
+ '[attr.data-disabled]': `!column().filterable() ? '' : null`,
2080
+ '(click)': 'column().filterable() && onFilterClick()',
2081
+ }, template: "@if (column().filterable()) {\n @let active = filter() !== null && filter() !== '';\n\n <sci-icon class=\"filter e2e-filter\" [class.active]=\"active\">scion.filter</sci-icon>\n\n @switch (column().type) {\n @case ('number') {\n <input [(ngModel)]=\"filter\" type=\"number\" #input/>\n }\n @case ('boolean') {\n <input [ngModel]=\"(filter() | sciColumnFilterBooleanLabel | sciText)()\" type=\"button\" #input>\n <sci-icon class=\"chevron\">scion.chevron_down</sci-icon>\n }\n @default {\n <input [(ngModel)]=\"filter\" type=\"text\" #input/>\n }\n }\n\n @if (active) {\n <button class=\"clear e2e-clear\" (click)=\"onReset($event)\">\n <sci-icon>scion.clear</sci-icon>\n </button>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;gap:.5em;align-items:center;padding-inline:var(--\\275sci-table-cell-padding-inline);border-bottom:var(--\\275sci-table-border-size) solid transparent;margin-bottom:-1px;transition:border-bottom-color ease-in-out .15s,color ease-in-out .15s}:host:focus-within:not(:disabled):not(:has(input:disabled)){border-bottom-color:var(--sci-color-accent)}:host>sci-icon.filter.active{color:var(--sci-color-accent)}:host>sci-icon.chevron{--sci-icon-size: var(--sci-button-icon-size)}:host:has(input[type=button]){cursor:var(--sci-button-cursor)}:host:has(input[type=button]):has(button.clear)>sci-icon.chevron{display:none}:host>input{all:unset;flex:auto;align-self:stretch;width:0;min-width:0;color:var(--sci-color-text);appearance:auto;box-sizing:border-box}:host>input[type=number]::-webkit-outer-spin-button,:host>input[type=number]::-webkit-inner-spin-button{appearance:none;margin:0}:host>button.clear{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>button.clear:hover:where(:not(:disabled)){background-color:var(--sci-button-background-color-hover)}:host>button.clear:active:where(:not(:disabled)){background-color:var(--sci-button-background-color-active)}:host>button.clear:focus:not(:focus-visible){outline:none}:host>button.clear:focus-visible{outline:var(--sci-button-outline-width) solid var(--sci-color-accent)}:host>button.clear:disabled{color:var(--sci-button-text-color-disabled)}:host>button.clear{padding:1px}\n"] }]
2082
+ }], ctorParameters: () => [], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], input: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }] } });
2083
+ function trim(value) {
2084
+ return typeof value === 'string' ? value.trim() || null : value;
2085
+ }
2086
+
2087
+ /*
2088
+ * Copyright (c) 2018-2026 Swiss Federal Railways
2089
+ *
2090
+ * This program and the accompanying materials are made
2091
+ * available under the terms of the Eclipse Public License 2.0
2092
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
2093
+ *
2094
+ * SPDX-License-Identifier: EPL-2.0
2095
+ */
2096
+ class SciTableHeaderComponent {
2097
+ table = inject(ɵSCI_TABLE);
2098
+ onMouseWheel(event) {
2099
+ // Prevent vertical scrolling on header, but not horizontal scrolling.
2100
+ if (!event.shiftKey) {
2101
+ event.preventDefault();
2102
+ }
2103
+ }
2104
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2105
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciTableHeaderComponent, isStandalone: true, selector: "sci-table-header", host: { listeners: { "wheel": "onMouseWheel($event)" } }, ngImport: i0, template: "@let table = this.table();\n\n@if (table.showHeader()) {\n @for (column of table.columns(); track column.name) {\n <sci-column-header [column]=\"column\"/>\n }\n}\n@if (table.filterable()) {\n @for (column of table.columns(); track column.name) {\n <sci-column-filter [column]=\"column\"/>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:grid;grid-template-columns:subgrid;grid-auto-rows:var(--sci-table-header-height);background-color:var(--sci-table-header-background-color);border-bottom:var(--\\275sci-table-border-size) solid var(--\\275sci-table-border-color);-webkit-user-select:none;user-select:none}@container style(--\\275sci-column-header-visible: true){:host{grid-template-rows:minmax(var(--sci-table-header-height),auto)}}:host>sci-column-filter:first-of-type{grid-column:1}@container style(--\\275sci-table-resizing: true){:host{pointer-events:none}}:host:empty{display:none}\n"], dependencies: [{ kind: "component", type: SciColumnHeaderComponent, selector: "sci-column-header", inputs: ["column"] }, { kind: "component", type: SciColumnFilterComponent, selector: "sci-column-filter", inputs: ["column"] }] });
2106
+ }
2107
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableHeaderComponent, decorators: [{
2108
+ type: Component,
2109
+ args: [{ selector: 'sci-table-header', host: {
2110
+ '(wheel)': 'onMouseWheel($event)',
2111
+ }, imports: [
2112
+ SciColumnHeaderComponent,
2113
+ SciColumnFilterComponent,
2114
+ ], template: "@let table = this.table();\n\n@if (table.showHeader()) {\n @for (column of table.columns(); track column.name) {\n <sci-column-header [column]=\"column\"/>\n }\n}\n@if (table.filterable()) {\n @for (column of table.columns(); track column.name) {\n <sci-column-filter [column]=\"column\"/>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:grid;grid-template-columns:subgrid;grid-auto-rows:var(--sci-table-header-height);background-color:var(--sci-table-header-background-color);border-bottom:var(--\\275sci-table-border-size) solid var(--\\275sci-table-border-color);-webkit-user-select:none;user-select:none}@container style(--\\275sci-column-header-visible: true){:host{grid-template-rows:minmax(var(--sci-table-header-height),auto)}}:host>sci-column-filter:first-of-type{grid-column:1}@container style(--\\275sci-table-resizing: true){:host{pointer-events:none}}:host:empty{display:none}\n"] }]
2115
+ }] });
2116
+
2117
+ /*
2118
+ * Copyright (c) 2018-2026 Swiss Federal Railways
2119
+ *
2120
+ * This program and the accompanying materials are made
2121
+ * available under the terms of the Eclipse Public License 2.0
2122
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
2123
+ *
2124
+ * SPDX-License-Identifier: EPL-2.0
2125
+ */
2126
+ /**
2127
+ * @experimental since 22.3.0; API and behavior may change in any version without notice.
2128
+ */
2129
+ class SciTableComponent {
2130
+ /**
2131
+ * Specifies a unique table identifier, used as the key for storing user preferences.
2132
+ */
2133
+ name = input.required(/* @ts-ignore */
2134
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
2135
+ /**
2136
+ * Specifies the table definition and datasource.
2137
+ */
2138
+ table = input.required({ ...(ngDevMode ? { debugName: "table" } : /* istanbul ignore next */ {}), transform: (table) => table });
2139
+ /**
2140
+ * Emits when the user double-clicks a row or presses `Enter` on selected rows.
2141
+ */
2142
+ primaryAction = output();
2143
+ _viewport = viewChild.required(SciTableViewportComponent, { ...(ngDevMode ? { debugName: "_viewport" } : /* istanbul ignore next */ {}), read: ElementRef });
2144
+ _viewportClient = viewChild.required(SciTableGridComponent, { ...(ngDevMode ? { debugName: "_viewportClient" } : /* istanbul ignore next */ {}), read: ElementRef });
2145
+ _tableHeader = viewChild(SciTableHeaderComponent, { ...(ngDevMode ? { debugName: "_tableHeader" } : /* istanbul ignore next */ {}), read: ElementRef });
2146
+ _tableBody = viewChild.required(SciTableBodyComponent, { ...(ngDevMode ? { debugName: "_tableBody" } : /* istanbul ignore next */ {}), read: ElementRef });
2147
+ _itemSizeSyntheticElement = viewChild.required('item_size_synthetic_element', /* @ts-ignore */
2148
+ ...(ngDevMode ? [{ debugName: "_itemSizeSyntheticElement" }] : /* istanbul ignore next */ []));
2149
+ _cellPaddingSyntheticElement = viewChild.required('cell_padding_synthetic_element', /* @ts-ignore */
2150
+ ...(ngDevMode ? [{ debugName: "_cellPaddingSyntheticElement" }] : /* istanbul ignore next */ []));
2151
+ rows = viewChildren(SciTableRowComponent, /* @ts-ignore */
2152
+ ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
2153
+ constructor() {
2154
+ this.connectToModel();
2155
+ this.scrollActiveRowIntoViewport();
2156
+ }
2157
+ onRowPrimaryAction(event) {
2158
+ this.primaryAction.emit(event);
2159
+ }
2160
+ onRetry() {
2161
+ this.table().reset();
2162
+ }
2163
+ connectToModel() {
2164
+ const viewportDimension = dimension(this._viewport);
2165
+ const viewportClientDimension = dimension(this._viewportClient);
2166
+ const tableHeaderDimension = dimension(this._tableHeader);
2167
+ const tableBodyDimension = dimension(this._tableBody);
2168
+ const cellPadding = dimension(this._cellPaddingSyntheticElement);
2169
+ const itemSizeDimension = dimension(this._itemSizeSyntheticElement);
2170
+ effect(onCleanup => {
2171
+ const name = this.name();
2172
+ const table = this.table();
2173
+ const viewport = this._viewport().nativeElement;
2174
+ untracked(() => {
2175
+ table.connect(name, {
2176
+ viewport: viewport,
2177
+ viewportHeight: computed(() => viewportDimension().clientHeight - (tableHeaderDimension()?.offsetHeight ?? 0)),
2178
+ viewportWidth: computed(() => viewportDimension().clientWidth),
2179
+ viewportClientHeight: computed(() => viewportClientDimension().offsetHeight),
2180
+ viewportClientWidth: computed(() => tableBodyDimension().offsetWidth), // Use `table-body` instead of `table-grid` because `table-grid` has `min-width: 100%`, thus never shrinking below viewport width.
2181
+ cellPadding: computed(() => cellPadding().clientWidth),
2182
+ headerHeight: computed(() => tableHeaderDimension()?.offsetHeight ?? 0),
2183
+ itemHeight: computed(() => itemSizeDimension().offsetHeight),
2184
+ scrollToTop: () => viewport.scrollTo({ top: 0 }),
2185
+ });
2186
+ onCleanup(() => table.disconnect());
2187
+ });
2188
+ });
2189
+ }
2190
+ scrollActiveRowIntoViewport() {
2191
+ effect(() => {
2192
+ const activeRow = this.table().activeRow();
2193
+ if (!activeRow) {
2194
+ return;
2195
+ }
2196
+ untracked(() => {
2197
+ const viewport = this._viewport().nativeElement;
2198
+ const viewportHeight = this.table().tableViewRef()?.viewportHeight() ?? 0;
2199
+ const itemHeight = this.table().tableViewRef()?.itemHeight() ?? 0;
2200
+ const activeRowTop = activeRow.index * itemHeight;
2201
+ const activeRowBottom = activeRowTop + itemHeight;
2202
+ const scrollTop = viewport.scrollTop;
2203
+ const scrollBottom = scrollTop + viewportHeight;
2204
+ if (activeRowTop < scrollTop) {
2205
+ viewport.scrollTop = activeRowTop;
2206
+ }
2207
+ else if (activeRowBottom > scrollBottom) {
2208
+ viewport.scrollTop = activeRowBottom - viewportHeight;
2209
+ }
2210
+ });
2211
+ });
2212
+ }
2213
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2214
+ 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
+ provideSciTable(),
2216
+ 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 });
2218
+ }
2219
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableComponent, decorators: [{
2220
+ type: Component,
2221
+ args: [{ selector: 'sci-table', encapsulation: ViewEncapsulation.ShadowDom, host: {
2222
+ '[attr.name]': 'name()', // Public API: Enables selecting the table by name in CSS (also if the table has a dynamic name input binding)
2223
+ '[style.--ɵsci-table-scrolling]': 'table().scrolling() ? `true` : null',
2224
+ '[style.--ɵsci-table-resizing]': 'table().resizing() ? `true` : null',
2225
+ }, imports: [
2226
+ SciTableViewportComponent,
2227
+ SciTableHeaderComponent,
2228
+ SciTableGridComponent,
2229
+ SciTableBodyComponent,
2230
+ SciTableRowComponent,
2231
+ SciTableColumnsComponent,
2232
+ SciTableKeyboardNavigatorDirective,
2233
+ SciAttributesDirective,
2234
+ SciScrollbarComponent,
2235
+ SciTextPipe,
2236
+ SciThrobberComponent,
2237
+ SciIconComponent,
2238
+ ], providers: [
2239
+ provideSciTable(),
2240
+ 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"] }]
2242
+ }], 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
+ function provideSciTable() {
2244
+ return {
2245
+ provide: ɵSCI_TABLE,
2246
+ useFactory: () => inject(SciTableComponent).table,
2247
+ };
2248
+ }
2249
+
2250
+ /*
2251
+ * Copyright (c) 2018-2026 Swiss Federal Railways
2252
+ *
2253
+ * This program and the accompanying materials are made
2254
+ * available under the terms of the Eclipse Public License 2.0
2255
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
2256
+ *
2257
+ * SPDX-License-Identifier: EPL-2.0
2258
+ */
2259
+
2260
+ /**
2261
+ * Generated bundle index. Do not edit.
2262
+ */
2263
+
2264
+ export { SciTableComponent, provideTableRowBinding, provideTableStorage, table, ɵillegaldatasource };
2265
+ //# sourceMappingURL=scion-components-table.mjs.map