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