@scion/components 22.5.0 → 22.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_index.scss +32 -88
- package/design/components/_scion-component-dark-tokens.scss +15 -0
- package/design/components/_scion-component-light-tokens.scss +15 -0
- package/fesm2022/scion-components-common.mjs +156 -5
- package/fesm2022/scion-components-common.mjs.map +1 -1
- package/fesm2022/scion-components-dimension.mjs +3 -3
- package/fesm2022/scion-components-dimension.mjs.map +1 -1
- package/fesm2022/scion-components-icon.mjs +134 -98
- package/fesm2022/scion-components-icon.mjs.map +1 -1
- package/fesm2022/scion-components-menu.mjs +58 -51
- package/fesm2022/scion-components-menu.mjs.map +1 -1
- package/fesm2022/scion-components-sashbox.mjs +1 -1
- package/fesm2022/scion-components-sashbox.mjs.map +1 -1
- package/fesm2022/scion-components-splitter.mjs +1 -1
- package/fesm2022/scion-components-splitter.mjs.map +1 -1
- package/fesm2022/scion-components-table.mjs +1544 -1008
- package/fesm2022/scion-components-table.mjs.map +1 -1
- package/fesm2022/scion-components-text.mjs +83 -40
- package/fesm2022/scion-components-text.mjs.map +1 -1
- package/fesm2022/scion-components-throbber.mjs +1 -1
- package/fesm2022/scion-components-throbber.mjs.map +1 -1
- package/fesm2022/scion-components-tree.mjs +306 -0
- package/fesm2022/scion-components-tree.mjs.map +1 -0
- package/fesm2022/scion-components-viewport.mjs +31 -18
- package/fesm2022/scion-components-viewport.mjs.map +1 -1
- package/package.json +6 -2
- package/tree/package.json +4 -0
- package/types/scion-components-common.d.ts +111 -4
- package/types/scion-components-icon.d.ts +42 -15
- package/types/scion-components-menu.d.ts +7 -2
- package/types/scion-components-sashbox.d.ts +1 -1
- package/types/scion-components-splitter.d.ts +1 -1
- package/types/scion-components-table.d.ts +1112 -300
- package/types/scion-components-text.d.ts +6 -5
- package/types/scion-components-throbber.d.ts +1 -1
- package/types/scion-components-tree.d.ts +559 -0
- package/types/scion-components-viewport.d.ts +20 -11
- package/viewport/scrollbar/_scrollbar.scss +12 -0
- package/design/icons/_scion-icon-font.scss +0 -50
|
@@ -1,24 +1,328 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { formatDate, DATE_PIPE_DEFAULT_OPTIONS, NgTemplateOutlet } from '@angular/common';
|
|
5
|
-
import { toObservable, rxResource, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
|
-
import { Objects, clamp, runSafe, Observables, Arrays } from '@scion/toolkit/util';
|
|
2
|
+
import { signal, inject, Injector, computed, DestroyRef, untracked, runInInjectionContext, onIdle, Injectable, DEFAULT_CURRENCY_CODE, InjectionToken, linkedSignal, isSignal, NgZone, effect, resource, assertNotInReactiveContext, assertInInjectionContext, Pipe, input, Component, output, viewChild, Directive, afterNextRender, ElementRef, debounced, viewChildren, ViewEncapsulation } from '@angular/core';
|
|
3
|
+
import { toObservable, rxResource } from '@angular/core/rxjs-interop';
|
|
7
4
|
import { LatestTaskExecutor } from '@scion/toolkit/common';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
5
|
+
import { Arrays, Observables, Objects, clamp } from '@scion/toolkit/util';
|
|
6
|
+
import * as i1 from '@scion/components/common';
|
|
7
|
+
import { ɵSingleActiveInjectorFactory as _SingleActiveInjectorFactory, coerceSignal, SCI_LOCALE, SCI_COMPONENTS_STORAGE, ɵcreateDestroyableInjector as _createDestroyableInjector, toLazyObservable, SciComponentOutletDirective, SciTooltipOnOverflowDirective, TOOLTIP_ON_OVERFLOW_DIRECTIVE_ADDITIONAL_TEXT, TOOLTIP_ON_OVERFLOW_DIRECTIVE_SOURCE_ELEMENT, SciAttributesDirective } from '@scion/components/common';
|
|
8
|
+
import { mapArray, subscribeIn } from '@scion/toolkit/operators';
|
|
9
|
+
import { formatDate, DATE_PIPE_DEFAULT_OPTIONS, NgTemplateOutlet } from '@angular/common';
|
|
10
|
+
import { text, SciTextPipe } from '@scion/components/text';
|
|
11
|
+
import { catchError, fromEvent, switchMap, concat, of, timer, combineLatestWith, finalize, firstValueFrom, skip } from 'rxjs';
|
|
12
|
+
import { map, startWith } from 'rxjs/operators';
|
|
11
13
|
import { SciScrollbarComponent } from '@scion/components/viewport';
|
|
12
14
|
import { dimension } from '@scion/components/dimension';
|
|
13
15
|
import { SciIconComponent } from '@scion/components/icon';
|
|
14
16
|
import { SciToolbarComponent, contributeMenu, ɵSciMenuService as _SciMenuService } from '@scion/components/menu';
|
|
15
17
|
import { UUID } from '@scion/toolkit/uuid';
|
|
16
18
|
import { SciSplitterComponent } from '@scion/components/splitter';
|
|
17
|
-
import { text, SciTextPipe } from '@scion/components/text';
|
|
18
19
|
import { SciThrobberComponent } from '@scion/components/throbber';
|
|
19
|
-
import * as
|
|
20
|
+
import * as i2 from '@angular/forms';
|
|
20
21
|
import { FormsModule } from '@angular/forms';
|
|
21
22
|
|
|
23
|
+
/** @inheritDoc */
|
|
24
|
+
class ɵSciTableCellValuePreloader {
|
|
25
|
+
_executor = new LatestTaskExecutor();
|
|
26
|
+
_queue = new Array(); // PERF: Do not change to signal to avoid array re-allocation on enqueue (expensive); queue size is tracked via a separate signal.
|
|
27
|
+
_queueSize = signal(0, /* @ts-ignore */
|
|
28
|
+
...(ngDevMode ? [{ debugName: "_queueSize" }] : /* istanbul ignore next */ []));
|
|
29
|
+
_injector = inject(Injector);
|
|
30
|
+
/** @inheritDoc */
|
|
31
|
+
loading = computed(() => this._queueSize() > 0, /* @ts-ignore */
|
|
32
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
33
|
+
constructor() {
|
|
34
|
+
inject(DestroyRef).onDestroy(() => {
|
|
35
|
+
this._executor.destroy();
|
|
36
|
+
this.clear();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** @inheritDoc */
|
|
40
|
+
queue(cellValueFn) {
|
|
41
|
+
this._queue.push(cellValueFn);
|
|
42
|
+
this._queueSize.update(size => size + 1);
|
|
43
|
+
this._executor.submit(() => this.whenIdle().then(() => this.preloadQueuedBatch(PRELOAD_BATCH_SIZE)));
|
|
44
|
+
}
|
|
45
|
+
/** @inheritDoc */
|
|
46
|
+
clear() {
|
|
47
|
+
this._queue.length = 0;
|
|
48
|
+
untracked(() => this._queueSize.set(0));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Preloads the next batch of queued cell values, FIFO, then, if the queue is not empty, schedules the next batch.
|
|
52
|
+
*/
|
|
53
|
+
async preloadQueuedBatch(batchSize) {
|
|
54
|
+
this._queue.splice(0, batchSize).forEach(fn => fn());
|
|
55
|
+
this._queueSize.set(this._queue.length);
|
|
56
|
+
// Preload next batch when idle again.
|
|
57
|
+
if (this._queue.length) {
|
|
58
|
+
this._executor.submit(() => this.whenIdle().then(() => this.preloadQueuedBatch(PRELOAD_BATCH_SIZE)));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolves when the browser becomes idle.
|
|
63
|
+
*
|
|
64
|
+
* @see onIdle
|
|
65
|
+
*/
|
|
66
|
+
whenIdle() {
|
|
67
|
+
return runInInjectionContext(this._injector, onIdle);
|
|
68
|
+
}
|
|
69
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciTableCellValuePreloader, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
70
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciTableCellValuePreloader });
|
|
71
|
+
}
|
|
72
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciTableCellValuePreloader, decorators: [{
|
|
73
|
+
type: Injectable
|
|
74
|
+
}], ctorParameters: () => [] });
|
|
75
|
+
const PRELOAD_BATCH_SIZE = 50_000;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Preloads cell values in the background when the browser is idle. This prevents lag with large datasets, in particular
|
|
79
|
+
* for expensive computations like formatting dates and generating ISO 8601 strings, providing faster sort and filter performance.
|
|
80
|
+
*
|
|
81
|
+
* If a cell's value is requested before background preloading finishes, it is computed synchronously.
|
|
82
|
+
*/
|
|
83
|
+
class SciTableCellValuePreloader {
|
|
84
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellValuePreloader, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
85
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellValuePreloader, providedIn: 'root', useClass: ɵSciTableCellValuePreloader });
|
|
86
|
+
}
|
|
87
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellValuePreloader, decorators: [{
|
|
88
|
+
type: Injectable,
|
|
89
|
+
args: [{ providedIn: 'root', useClass: ɵSciTableCellValuePreloader }]
|
|
90
|
+
}] });
|
|
91
|
+
|
|
92
|
+
/*
|
|
93
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
94
|
+
*
|
|
95
|
+
* This program and the accompanying materials are made
|
|
96
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
97
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
98
|
+
*
|
|
99
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
100
|
+
*/
|
|
101
|
+
class SciInMemoryTableDatasource {
|
|
102
|
+
_rows;
|
|
103
|
+
constructor(data, table, options) {
|
|
104
|
+
const tableCellValuePreloader = inject(SciTableCellValuePreloader);
|
|
105
|
+
const injectorFactory = new _SingleActiveInjectorFactory();
|
|
106
|
+
this._rows = computed(() => {
|
|
107
|
+
// Create new value function injector, releasing resources allocated in column value functions.
|
|
108
|
+
const injector = injectorFactory.create();
|
|
109
|
+
// Cancel preloading of previous data.
|
|
110
|
+
tableCellValuePreloader.clear();
|
|
111
|
+
return data()
|
|
112
|
+
.flatMap(item => createRow(item))
|
|
113
|
+
.reduce((map, row) => map.set(row.id, row), new Map());
|
|
114
|
+
function createRow(item, parent) {
|
|
115
|
+
const id = table.trackBy(item);
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
id,
|
|
119
|
+
parentId: parent?.id,
|
|
120
|
+
item,
|
|
121
|
+
cells: table.columns().reduce((cells, column) => untracked(() => {
|
|
122
|
+
// PERF: Cell values are computed lazily (on idle or first use) to defer expensive computations like date formatting.
|
|
123
|
+
const tableCellValue = 'value' in column ? new eSciTableCellValue(tableCellValuePreloader, () => runInInjectionContext(injector, () => column.value(item)())) : undefined;
|
|
124
|
+
return cells.set(column.name, {
|
|
125
|
+
column: column,
|
|
126
|
+
value: () => tableCellValue?.value(),
|
|
127
|
+
});
|
|
128
|
+
}), new Map()),
|
|
129
|
+
},
|
|
130
|
+
// Child rows if a tree structure.
|
|
131
|
+
...options?.children?.(item).flatMap(child => createRow(child, { id })) ?? [],
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
}, /* @ts-ignore */
|
|
135
|
+
...(ngDevMode ? [{ debugName: "_rows" }] : /* istanbul ignore next */ []));
|
|
136
|
+
}
|
|
137
|
+
getTreeTableNodes(query) {
|
|
138
|
+
return toObservable(computed(() => {
|
|
139
|
+
const rows = this._rows();
|
|
140
|
+
// PERF: Do not track signals inside `Array.sort` to avoid Angular signal tracking overhead as the comparator runs repeatedly, degrading performance otherwise.
|
|
141
|
+
return untracked(() => [...rows.values()]
|
|
142
|
+
.filter(row => matchesRow(row, query.columnFilters, { coerceEnumValueToArray: true }) && matchesGlobalFilter(row, query.tableFilter, { coerceEnumValueToArray: false }))
|
|
143
|
+
.sort((a, b) => compareRows(a, b, query.sortCriteria))
|
|
144
|
+
.map(row => ({ id: row.id, parentId: row.parentId })));
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
getItems(ids) {
|
|
148
|
+
return toObservable(computed(() => ids.flatMap(id => this._rows().has(id) ? this._rows().get(id).item : [])));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Tests whether a row matches the given global filter.
|
|
153
|
+
*/
|
|
154
|
+
function matchesGlobalFilter(row, filter, options) {
|
|
155
|
+
if (!filter?.trim()) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
for (const cell of row.cells.values()) {
|
|
159
|
+
const text = cell.column.type === 'number' ? toNumber(filter) : filter;
|
|
160
|
+
if (text === undefined) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (matchesRow(row, [{ text, columnName: cell.column.name }], options)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Tests whether a row matches the given column filters.
|
|
171
|
+
*/
|
|
172
|
+
function matchesRow(row, columnFilters, options) {
|
|
173
|
+
if (!columnFilters.length) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
for (const columnFilter of columnFilters) {
|
|
177
|
+
const cell = row.cells.get(columnFilter.columnName);
|
|
178
|
+
if ('value' in cell.column && cell.value() === undefined) { // Component and template columns have no value.
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
const filter = cell.column.type === 'enum' && options.coerceEnumValueToArray ? Arrays.coerce(columnFilter.text) : columnFilter.text;
|
|
182
|
+
if (!cell.column.matches(filter, { item: row.item, value: cell.value() })) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Compares two rows based on the given sort criteria.
|
|
190
|
+
*/
|
|
191
|
+
function compareRows(row1, row2, sortCriteria) {
|
|
192
|
+
if (!sortCriteria.length) {
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
for (const criterion of sortCriteria) {
|
|
196
|
+
const cell1 = row1.cells.get(criterion.columnName);
|
|
197
|
+
const cell2 = row2.cells.get(criterion.columnName);
|
|
198
|
+
const value1 = cell1.value();
|
|
199
|
+
const value2 = cell2.value();
|
|
200
|
+
// Always place `undefined` values at the end, independent of the sort direction. Note that component and template columns have no value.
|
|
201
|
+
if (value1 === undefined && value2 !== undefined) {
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
204
|
+
if (value2 === undefined && value1 !== undefined) {
|
|
205
|
+
return -1;
|
|
206
|
+
}
|
|
207
|
+
// Prevent calling comparator for value columns where both values are undefined. Note that component and template columns have no value.
|
|
208
|
+
if (value1 === undefined && value2 === undefined && 'value' in cell1.column) {
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
const comparison = cell1.column.compare({ item: row1.item, value: value1 }, { item: row2.item, value: value2 });
|
|
212
|
+
if (comparison !== 0) {
|
|
213
|
+
return criterion.direction === 'asc' ? comparison : -comparison;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Converts the given string to a number, or `undefined` if it cannot be parsed.
|
|
220
|
+
*/
|
|
221
|
+
function toNumber(numberLike) {
|
|
222
|
+
const number = Number.parseFloat(numberLike);
|
|
223
|
+
return !Number.isNaN(number) ? number : undefined;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Provides lazy loading for the cell's value, preloading it when idle for better sort/filter performance.
|
|
227
|
+
*
|
|
228
|
+
* The value is computed at most once, whether it gets preloaded during idle time or requested synchronously via {@link value()}.
|
|
229
|
+
*/
|
|
230
|
+
class eSciTableCellValue {
|
|
231
|
+
_valueFn;
|
|
232
|
+
_value;
|
|
233
|
+
constructor(preloader, _valueFn) {
|
|
234
|
+
this._valueFn = _valueFn;
|
|
235
|
+
preloader.queue(() => {
|
|
236
|
+
this._value ??= this._valueFn();
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
value() {
|
|
240
|
+
return this._value ??= untracked(() => this._valueFn());
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/*
|
|
245
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
246
|
+
*
|
|
247
|
+
* This program and the accompanying materials are made
|
|
248
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
249
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
250
|
+
*
|
|
251
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
252
|
+
*/
|
|
253
|
+
/**
|
|
254
|
+
* Provides an in-memory datasource for {@link SciTable} with built-in filtering and sorting.
|
|
255
|
+
*
|
|
256
|
+
* A datasource provides data to {@link SciTable}, passing each item to columns for display. For a tree structure, additionally pass a child selector to the datasource.
|
|
257
|
+
*
|
|
258
|
+
* This datasource requires all data to be loaded upfront. The datasource filters and sorts data in memory.
|
|
259
|
+
*
|
|
260
|
+
* Custom columns must configure a filter matcher and sort comparator to be filterable and sortable.
|
|
261
|
+
*
|
|
262
|
+
* For large datasets requiring pagination or server-side filtering and sorting, prefer implementing a custom datasource using {@link ɵprovideTableDatasource}.
|
|
263
|
+
*
|
|
264
|
+
* @example - Usage
|
|
265
|
+
* ```ts
|
|
266
|
+
* import {table, provideTableDatasource} from '@scion/components/table';
|
|
267
|
+
* import {httpResource} from '@angular/common/http';
|
|
268
|
+
*
|
|
269
|
+
* const users = httpResource<User[]>(() => '/api/users', {defaultValue: []});
|
|
270
|
+
*
|
|
271
|
+
* table({
|
|
272
|
+
* datasource: provideTableDatasource(users.value),
|
|
273
|
+
* columns: table => table.addStringColumn('Name', user => user.name),
|
|
274
|
+
* });
|
|
275
|
+
* ```
|
|
276
|
+
*
|
|
277
|
+
* @example - Displaying hierarchical data
|
|
278
|
+
* ```ts
|
|
279
|
+
* import {table, provideTableDatasource} from '@scion/components/table';
|
|
280
|
+
* import {httpResource} from '@angular/common/http';
|
|
281
|
+
*
|
|
282
|
+
* const users = httpResource<User[]>(() => '/api/users', {defaultValue: []});
|
|
283
|
+
*
|
|
284
|
+
* table({
|
|
285
|
+
* datasource: provideTableDatasource(users.value, user => user.children),
|
|
286
|
+
* columns: table => table.addStringColumn('Name', user => user.name),
|
|
287
|
+
* });
|
|
288
|
+
* ```
|
|
289
|
+
*
|
|
290
|
+
* @param data - Provides items to display in the table, or root items if displaying a tree structure.
|
|
291
|
+
* @param children - Function required to display a tree structure, returning an item's children or an empty array if the item has no children or is a leaf.
|
|
292
|
+
* Called eagerly for each item when initializing the table, inside a reactive context that re-evaluates when tracked signals change.
|
|
293
|
+
*
|
|
294
|
+
* @see SciTableDescriptor.datasource
|
|
295
|
+
*/
|
|
296
|
+
function provideTableDatasource(data, children) {
|
|
297
|
+
return {
|
|
298
|
+
[eSCI_TABLE_DATASOURCE_PROVIDER]: (table) => new SciInMemoryTableDatasource(data, table, { children }),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function ɵprovideTableDatasource(datasource) {
|
|
302
|
+
const isTreeTableDatesource = 'getTreeTableNodes' in datasource;
|
|
303
|
+
return {
|
|
304
|
+
[eSCI_TABLE_DATASOURCE_PROVIDER]: () => ({
|
|
305
|
+
getTreeTableNodes: (query) => isTreeTableDatesource ? datasource.getTreeTableNodes(query) : Observables.coerce(datasource.getIds(query)).pipe(mapArray(id => ({ id }))),
|
|
306
|
+
getItems: (ids) => datasource.getItems(ids),
|
|
307
|
+
}),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Symbol for providing a {@link SciTreeTableDatasource}.
|
|
312
|
+
*/
|
|
313
|
+
const eSCI_TABLE_DATASOURCE_PROVIDER = Symbol('eSCI_TABLE_DATASOURCE_PROVIDER');
|
|
314
|
+
|
|
315
|
+
/*
|
|
316
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
317
|
+
*
|
|
318
|
+
* This program and the accompanying materials are made
|
|
319
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
320
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
321
|
+
*
|
|
322
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
323
|
+
*/
|
|
324
|
+
const skeletonRowId = Symbol('SKELETON_ROW');
|
|
325
|
+
|
|
22
326
|
/**
|
|
23
327
|
* Creates a {@link SciDateValue} from the given date-like input.
|
|
24
328
|
*
|
|
@@ -78,6 +382,39 @@ function ensureIso8601DatePrefix(isoDate) {
|
|
|
78
382
|
}
|
|
79
383
|
}
|
|
80
384
|
|
|
385
|
+
/*
|
|
386
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
387
|
+
*
|
|
388
|
+
* This program and the accompanying materials are made
|
|
389
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
390
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
391
|
+
*
|
|
392
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
393
|
+
*/
|
|
394
|
+
/**
|
|
395
|
+
* Creates a {@link SciEnumValue} from the given enum-like input.
|
|
396
|
+
*/
|
|
397
|
+
function coerceEnumValue(enumLike, options) {
|
|
398
|
+
const value = coerceSignal(enumLike, { coerceUndefined: true });
|
|
399
|
+
const enumeration = coerceSignal(options.enumeration);
|
|
400
|
+
const enumEntry = computed(() => value() !== undefined ? enumeration().find(entry => entry.value === value()) : undefined, /* @ts-ignore */
|
|
401
|
+
...(ngDevMode ? [{ debugName: "enumEntry" }] : /* istanbul ignore next */ []));
|
|
402
|
+
const label = text(computed(() => enumEntry()?.label), { injector: options.injector ?? inject(Injector) });
|
|
403
|
+
const tooltip = text(computed(() => enumEntry()?.tooltip), { injector: options.injector ?? inject(Injector) });
|
|
404
|
+
return computed(() => {
|
|
405
|
+
if (!enumEntry()) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
value: enumEntry().value,
|
|
410
|
+
label: label(),
|
|
411
|
+
icon: enumEntry().icon,
|
|
412
|
+
// Use tooltip if provided, or fall back to label, but only if it is not configured for display.
|
|
413
|
+
tooltip: tooltip() ?? (tooltip() === null || options.format.label ? undefined : label()),
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
81
418
|
/*
|
|
82
419
|
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
83
420
|
*
|
|
@@ -101,7 +438,7 @@ class ɵSciTableColumnFactory {
|
|
|
101
438
|
this.columns.push({
|
|
102
439
|
...column,
|
|
103
440
|
type: 'string',
|
|
104
|
-
value: item =>
|
|
441
|
+
value: item => text(descriptor.value(item)),
|
|
105
442
|
sortable: computed(() => this._table.sortable() && !!sortable),
|
|
106
443
|
filterable: computed(() => this._table.filterable() && !!filterable),
|
|
107
444
|
compare: typeof sortable === 'object' ? sortable.comparator : (a, b) => a.value.localeCompare(b.value),
|
|
@@ -128,15 +465,16 @@ class ɵSciTableColumnFactory {
|
|
|
128
465
|
}
|
|
129
466
|
addBooleanColumn(descriptorLike, value) {
|
|
130
467
|
const descriptor = coerceColumnDescriptor(descriptorLike, value);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
...column,
|
|
134
|
-
type: 'boolean',
|
|
468
|
+
this.addEnumColumn({
|
|
469
|
+
...descriptor,
|
|
135
470
|
value: item => coerceSignal(descriptor.value(item), { coerceUndefined: true }),
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
471
|
+
values: [
|
|
472
|
+
{ value: true, icon: 'scion:true', label: '%scion:components:yes.value', tooltip: null },
|
|
473
|
+
{ value: false, icon: 'scion:false', label: '%scion:components:no.value', tooltip: null },
|
|
474
|
+
],
|
|
475
|
+
format: { icon: true },
|
|
476
|
+
filterable: descriptor.filterable === false ? false : 'single',
|
|
477
|
+
referenceType: 'boolean',
|
|
140
478
|
});
|
|
141
479
|
return this;
|
|
142
480
|
}
|
|
@@ -161,392 +499,141 @@ class ɵSciTableColumnFactory {
|
|
|
161
499
|
});
|
|
162
500
|
return this;
|
|
163
501
|
}
|
|
164
|
-
|
|
165
|
-
// Require a filter matcher if filterable on an array datasource.
|
|
166
|
-
if (this._table.isArrayDatasource() && descriptor.filterable === true) {
|
|
167
|
-
throw Error('[ColumnDefinitionError] Component column requires a filter matcher in order to be filterable.');
|
|
168
|
-
}
|
|
169
|
-
// Require a sort comparator if sortable on an array datasource.
|
|
170
|
-
if (this._table.isArrayDatasource() && descriptor.sortable === true) {
|
|
171
|
-
throw Error('[ColumnDefinitionError] Component column requires a sort comparator in order to be sortable.');
|
|
172
|
-
}
|
|
502
|
+
addEnumColumn(descriptor) {
|
|
173
503
|
const column = this.mapToColumn(descriptor);
|
|
504
|
+
const enumeration = coerceSignal(descriptor.values);
|
|
505
|
+
const positionByEnumValue = computed(() => enumeration().reduce((acc, entry, index) => acc.set(entry.value, index), new Map()), /* @ts-ignore */
|
|
506
|
+
...(ngDevMode ? [{ debugName: "positionByEnumValue" }] : /* istanbul ignore next */ []));
|
|
507
|
+
const format = descriptor.format
|
|
508
|
+
? { icon: descriptor.format.icon ?? false, label: descriptor.format.label ?? false, filter: { icon: descriptor.format.filter?.icon ?? false } } // eslint-disable-line @stylistic/operator-linebreak
|
|
509
|
+
: { icon: false, label: true, filter: { icon: true } }; // eslint-disable-line @stylistic/operator-linebreak
|
|
174
510
|
this.columns.push({
|
|
175
511
|
...column,
|
|
176
|
-
type: '
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
512
|
+
type: 'enum',
|
|
513
|
+
referenceType: descriptor.referenceType,
|
|
514
|
+
value: item => coerceEnumValue(descriptor.value(item), { enumeration, format }),
|
|
515
|
+
sortable: computed(() => this._table.sortable() && (descriptor.sortable ?? true)),
|
|
516
|
+
filterable: computed(() => this._table.filterable() && (descriptor.filterable ?? 'multi')),
|
|
517
|
+
enumeration,
|
|
518
|
+
compare: (a, b) => untracked(() => (positionByEnumValue().get(a.value.value) ?? 0) - (positionByEnumValue().get(b.value.value) ?? 0)),
|
|
519
|
+
matches: (filter, context) => {
|
|
520
|
+
if (typeof filter === 'string') { // table filter
|
|
521
|
+
return context.value.label.toLowerCase().includes(filter.toLowerCase());
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
return filter.includes(context.value.value); // column filter
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
format,
|
|
183
528
|
});
|
|
184
529
|
return this;
|
|
185
530
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.columns.push({
|
|
197
|
-
...column,
|
|
198
|
-
type: 'template',
|
|
199
|
-
template: descriptor.template,
|
|
200
|
-
sortable: computed(() => this._table.sortable() && !!descriptor.sortable),
|
|
201
|
-
filterable: computed(() => this._table.filterable() && !!descriptor.filterable),
|
|
202
|
-
compare: typeof descriptor.sortable === 'object' ? descriptor.sortable.comparator : () => 0,
|
|
203
|
-
matches: typeof descriptor.filterable === 'object' ? descriptor.filterable.matcher : () => true,
|
|
204
|
-
padding: descriptor.padding ?? true,
|
|
531
|
+
addIconColumn(descriptor) {
|
|
532
|
+
const resizable = typeof descriptor.header === 'string' || !!descriptor.header?.label?.length;
|
|
533
|
+
this.addEnumColumn({
|
|
534
|
+
...descriptor,
|
|
535
|
+
value: item => coerceSignal(descriptor.value(item), { coerceUndefined: true }),
|
|
536
|
+
format: { icon: true, filter: { icon: true } },
|
|
537
|
+
resizable: descriptor.resizable ?? resizable,
|
|
538
|
+
width: descriptor.width ?? (resizable ? undefined : 'max-content'), // adapt width to intrinsic size
|
|
539
|
+
minWidth: descriptor.minWidth ?? (resizable ? undefined : 32),
|
|
540
|
+
referenceType: 'icon',
|
|
205
541
|
});
|
|
206
542
|
return this;
|
|
207
543
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
// Require a unique column name.
|
|
213
|
-
const name = descriptor.name ?? `column:${this.columns.length}`;
|
|
214
|
-
if (this.columns.find(column => column.name === name)) {
|
|
215
|
-
throw Error(`[ColumnDefinitionError] Column names must be unique. "${name}" is defined more than once.`);
|
|
216
|
-
}
|
|
217
|
-
// Disallow a filter matcher if using a custom datasource.
|
|
218
|
-
if (!this._table.isArrayDatasource() && typeof descriptor.filterable === 'object') {
|
|
219
|
-
throw Error('[ColumnDefinitionError] Configuring a filter matcher is not supported for tables using a datasource. Filtering must be done by the datasource.');
|
|
220
|
-
}
|
|
221
|
-
// Disallow a sort comparator if using a custom datasource.
|
|
222
|
-
if (!this._table.isArrayDatasource() && typeof descriptor.sortable === 'object') {
|
|
223
|
-
throw Error('[ColumnDefinitionError] Configuring a sort comparator is not supported for tables using a datasource. Sorting must be done by the datasource.');
|
|
224
|
-
}
|
|
225
|
-
return {
|
|
226
|
-
name: name,
|
|
227
|
-
header: coerceSignal(descriptor.header ?? ''),
|
|
228
|
-
resizable: computed(() => this._table.resizable() && (descriptor.resizable ?? true)),
|
|
229
|
-
width: computed(() => {
|
|
230
|
-
const userSettings = this._table.userSettings().columns?.find(column => column.name === name);
|
|
231
|
-
return userSettings?.width ? `${userSettings.width}px` : descriptor.width ?? '1fr';
|
|
232
|
-
}),
|
|
233
|
-
minWidth: descriptor.minWidth ?? 100,
|
|
234
|
-
resizing: signal(false),
|
|
235
|
-
padding: true,
|
|
236
|
-
location: { x: 0, width: 0 }, // set in `SciTableColumnComponent`
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
function coerceColumnDescriptor(argument1, argument2) {
|
|
241
|
-
switch (typeof argument1) {
|
|
242
|
-
case 'string':
|
|
243
|
-
return { header: argument1, value: argument2 };
|
|
244
|
-
case 'function':
|
|
245
|
-
return { value: argument1 };
|
|
246
|
-
default:
|
|
247
|
-
return argument1;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/*
|
|
252
|
-
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
253
|
-
*
|
|
254
|
-
* This program and the accompanying materials are made
|
|
255
|
-
* available under the terms of the Eclipse Public License 2.0
|
|
256
|
-
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
257
|
-
*
|
|
258
|
-
* SPDX-License-Identifier: EPL-2.0
|
|
259
|
-
*/
|
|
260
|
-
/**
|
|
261
|
-
* Creates a range of integers starting at `start` and ending at `end` (including `end`).
|
|
262
|
-
*/
|
|
263
|
-
function rangeInclusive(start, end) {
|
|
264
|
-
const range = [];
|
|
265
|
-
for (let i = start; i <= end; i++) {
|
|
266
|
-
range.push(i);
|
|
267
|
-
}
|
|
268
|
-
return range;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/*
|
|
272
|
-
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
273
|
-
*
|
|
274
|
-
* This program and the accompanying materials are made
|
|
275
|
-
* available under the terms of the Eclipse Public License 2.0
|
|
276
|
-
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
277
|
-
*
|
|
278
|
-
* SPDX-License-Identifier: EPL-2.0
|
|
279
|
-
*/
|
|
280
|
-
const SCI_TABLE_STORAGE = new InjectionToken('SCI_TABLE_STORAGE', { factory: () => ({
|
|
281
|
-
load: (name) => localStorage.getItem(name),
|
|
282
|
-
store: (name, value) => localStorage.setItem(name, value),
|
|
283
|
-
}) });
|
|
284
|
-
/**
|
|
285
|
-
* Defaults to localStorage persistence.
|
|
286
|
-
* @param storage
|
|
287
|
-
*/
|
|
288
|
-
function provideTableStorage(storage) {
|
|
289
|
-
return makeEnvironmentProviders([
|
|
290
|
-
{ provide: SCI_TABLE_STORAGE, useClass: storage },
|
|
291
|
-
]);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/** @inheritDoc */
|
|
295
|
-
class ɵSciTableCellValuePreloader {
|
|
296
|
-
_executor = new LatestTaskExecutor();
|
|
297
|
-
_queue = new Array(); // PERF: Do not change to signal to avoid array re-allocation on enqueue (expensive); queue size is tracked via a separate signal.
|
|
298
|
-
_queueSize = signal(0, /* @ts-ignore */
|
|
299
|
-
...(ngDevMode ? [{ debugName: "_queueSize" }] : /* istanbul ignore next */ []));
|
|
300
|
-
_injector = inject(Injector);
|
|
301
|
-
/** @inheritDoc */
|
|
302
|
-
loading = computed(() => this._queueSize() > 0, /* @ts-ignore */
|
|
303
|
-
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
304
|
-
constructor() {
|
|
305
|
-
inject(DestroyRef).onDestroy(() => {
|
|
306
|
-
this._executor.destroy();
|
|
307
|
-
this._queue.length = 0;
|
|
308
|
-
this._queueSize.set(0);
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
/** @inheritDoc */
|
|
312
|
-
queue(cellValueFn) {
|
|
313
|
-
this._queue.push(cellValueFn);
|
|
314
|
-
this._queueSize.update(size => size + 1);
|
|
315
|
-
this._executor.submit(() => this.whenIdle().then(() => this.preloadQueuedBatch(PRELOAD_BATCH_SIZE)));
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Preloads the next batch of queued cell values, FIFO, then, if the queue is not empty, schedules the next batch.
|
|
319
|
-
*/
|
|
320
|
-
async preloadQueuedBatch(batchSize) {
|
|
321
|
-
this._queue.splice(0, batchSize).forEach(fn => fn());
|
|
322
|
-
this._queueSize.set(this._queue.length);
|
|
323
|
-
// Preload next batch when idle again.
|
|
324
|
-
if (this._queue.length) {
|
|
325
|
-
this._executor.submit(() => this.whenIdle().then(() => this.preloadQueuedBatch(PRELOAD_BATCH_SIZE)));
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Resolves when the browser becomes idle.
|
|
330
|
-
*
|
|
331
|
-
* @see onIdle
|
|
332
|
-
*/
|
|
333
|
-
whenIdle() {
|
|
334
|
-
return runInInjectionContext(this._injector, onIdle);
|
|
335
|
-
}
|
|
336
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciTableCellValuePreloader, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
337
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciTableCellValuePreloader });
|
|
338
|
-
}
|
|
339
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciTableCellValuePreloader, decorators: [{
|
|
340
|
-
type: Injectable
|
|
341
|
-
}], ctorParameters: () => [] });
|
|
342
|
-
const PRELOAD_BATCH_SIZE = 50_000;
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Preloads cell values in the background when the browser is idle. This prevents lag with large datasets, in particular
|
|
346
|
-
* for expensive computations like formatting dates and generating ISO 8601 strings, providing faster sort and filter performance.
|
|
347
|
-
*
|
|
348
|
-
* If a cell's value is requested before background preloading finishes, it is computed synchronously.
|
|
349
|
-
*/
|
|
350
|
-
class SciTableCellValuePreloader {
|
|
351
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellValuePreloader, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
352
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellValuePreloader, providedIn: 'root', useClass: ɵSciTableCellValuePreloader });
|
|
353
|
-
}
|
|
354
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellValuePreloader, decorators: [{
|
|
355
|
-
type: Injectable,
|
|
356
|
-
args: [{ providedIn: 'root', useClass: ɵSciTableCellValuePreloader }]
|
|
357
|
-
}] });
|
|
358
|
-
|
|
359
|
-
/*
|
|
360
|
-
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
361
|
-
*
|
|
362
|
-
* This program and the accompanying materials are made
|
|
363
|
-
* available under the terms of the Eclipse Public License 2.0
|
|
364
|
-
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
365
|
-
*
|
|
366
|
-
* SPDX-License-Identifier: EPL-2.0
|
|
367
|
-
*/
|
|
368
|
-
function arrayDatasource(data, columns) {
|
|
369
|
-
const dataset = new Dataset(data, columns);
|
|
370
|
-
return markAsArrayDatasource((request) => {
|
|
371
|
-
dataset.columnFilters.set(request.columnFilters);
|
|
372
|
-
dataset.tableFilter.set(request.tableFilter);
|
|
373
|
-
dataset.sortCriteria.set(request.sortCriteria);
|
|
374
|
-
const totalCount = dataset.count;
|
|
375
|
-
const items = dataset.slice(request.start, request.end);
|
|
376
|
-
return toObservable(computed(() => ({
|
|
377
|
-
totalCount: totalCount(),
|
|
378
|
-
items: items(),
|
|
379
|
-
})));
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Provides a filtered and sorted view on given data.
|
|
384
|
-
*/
|
|
385
|
-
class Dataset {
|
|
386
|
-
_dataview;
|
|
387
|
-
columnFilters = signal([], { ...(ngDevMode ? { debugName: "columnFilters" } : /* istanbul ignore next */ {}), equal: Objects.isEqual });
|
|
388
|
-
sortCriteria = signal([], { ...(ngDevMode ? { debugName: "sortCriteria" } : /* istanbul ignore next */ {}), equal: Objects.isEqual });
|
|
389
|
-
tableFilter = signal(undefined, /* @ts-ignore */
|
|
390
|
-
...(ngDevMode ? [{ debugName: "tableFilter" }] : /* istanbul ignore next */ []));
|
|
391
|
-
/**
|
|
392
|
-
* Returns the total count of items matching the current filters and search criteria.
|
|
393
|
-
*/
|
|
394
|
-
count = computed(() => this._dataview().length, /* @ts-ignore */
|
|
395
|
-
...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
|
|
396
|
-
constructor(data, columns) {
|
|
397
|
-
const tableCellValuePreloader = inject(SciTableCellValuePreloader);
|
|
398
|
-
const dataset = computed(() => data().map(item => ({
|
|
399
|
-
item,
|
|
400
|
-
cells: columns().reduce((cells, column) => untracked(() => {
|
|
401
|
-
// PERF: Cell values are computed lazily (on idle or first use) to defer expensive computations like date formatting.
|
|
402
|
-
const tableCellValue = 'value' in column ? new eSciTableCellValue(tableCellValuePreloader, () => column.value(item)()) : undefined;
|
|
403
|
-
return cells.set(column.name, {
|
|
404
|
-
column: column,
|
|
405
|
-
value: () => tableCellValue?.value(),
|
|
406
|
-
});
|
|
407
|
-
}), new Map()),
|
|
408
|
-
})), /* @ts-ignore */
|
|
409
|
-
...(ngDevMode ? [{ debugName: "dataset" }] : /* istanbul ignore next */ []));
|
|
410
|
-
// Memoize filtered/sorted view; recomputes only when dataset or criteria change, not when scrolling through the view, as sorting is an expensive operation.
|
|
411
|
-
this._dataview = computed(() => {
|
|
412
|
-
const rows = dataset();
|
|
413
|
-
const columnFilters = this.columnFilters();
|
|
414
|
-
const tableFilter = this.tableFilter();
|
|
415
|
-
const sortCriteria = this.sortCriteria();
|
|
416
|
-
// PERF: Do not track signals inside `Array.sort` to avoid Angular signal tracking overhead as the comparator runs repeatedly, degrading performance otherwise.
|
|
417
|
-
return untracked(() => rows
|
|
418
|
-
.filter(row => matchesRow(row, columnFilters) && matchesGlobalFilter(row, tableFilter))
|
|
419
|
-
.sort((a, b) => compareRows(a, b, sortCriteria))
|
|
420
|
-
.map(row => row.item));
|
|
421
|
-
}, /* @ts-ignore */
|
|
422
|
-
...(ngDevMode ? [{ debugName: "_dataview" }] : /* istanbul ignore next */ []));
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Returns a slice of the filtered and sorted data within the given range (start inclusive, end exclusive).
|
|
426
|
-
*/
|
|
427
|
-
slice(start, end) {
|
|
428
|
-
return computed(() => this._dataview().slice(start, end));
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Tests whether a row matches the given global filter.
|
|
433
|
-
*/
|
|
434
|
-
function matchesGlobalFilter(row, filter) {
|
|
435
|
-
if (!filter?.trim()) {
|
|
436
|
-
return true;
|
|
437
|
-
}
|
|
438
|
-
for (const cell of row.cells.values()) {
|
|
439
|
-
const text = coerceFilterText(filter, { to: cell.column.type });
|
|
440
|
-
if (text === undefined) {
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
if (matchesRow(row, [{ text, columnName: cell.column.name }])) {
|
|
444
|
-
return true;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
return false;
|
|
448
|
-
}
|
|
449
|
-
/**
|
|
450
|
-
* Tests whether a row matches the given column filters.
|
|
451
|
-
*/
|
|
452
|
-
function matchesRow(row, columnFilters) {
|
|
453
|
-
if (!columnFilters.length) {
|
|
454
|
-
return true;
|
|
455
|
-
}
|
|
456
|
-
for (const columnFilter of columnFilters) {
|
|
457
|
-
const cell = row.cells.get(columnFilter.columnName);
|
|
458
|
-
if ('value' in cell.column && cell.value() === undefined) { // Component and template columns have no value.
|
|
459
|
-
return false;
|
|
544
|
+
addComponentColumn(descriptor) {
|
|
545
|
+
// Require a filter matcher if filterable using the built-in in-memory datasource.
|
|
546
|
+
if (this._table.isInMemoryDatasource() && descriptor.filterable === true) {
|
|
547
|
+
throw Error('[ColumnDefinitionError] Component column requires a filter matcher in order to be filterable.');
|
|
460
548
|
}
|
|
461
|
-
if
|
|
462
|
-
|
|
549
|
+
// Require a sort comparator if sortable using the built-in in-memory datasource.
|
|
550
|
+
if (this._table.isInMemoryDatasource() && descriptor.sortable === true) {
|
|
551
|
+
throw Error('[ColumnDefinitionError] Component column requires a sort comparator in order to be sortable.');
|
|
463
552
|
}
|
|
553
|
+
const column = this.mapToColumn(descriptor);
|
|
554
|
+
this.columns.push({
|
|
555
|
+
...column,
|
|
556
|
+
type: 'component',
|
|
557
|
+
component: descriptor.component,
|
|
558
|
+
sortable: computed(() => this._table.sortable() && !!descriptor.sortable),
|
|
559
|
+
filterable: computed(() => this._table.filterable() && !!descriptor.filterable),
|
|
560
|
+
compare: typeof descriptor.sortable === 'object' ? descriptor.sortable.comparator : () => 0,
|
|
561
|
+
matches: typeof descriptor.filterable === 'object' ? descriptor.filterable.matcher : () => false,
|
|
562
|
+
padding: descriptor.padding ?? true,
|
|
563
|
+
});
|
|
564
|
+
return this;
|
|
464
565
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
*/
|
|
470
|
-
function compareRows(row1, row2, sortCriteria) {
|
|
471
|
-
if (!sortCriteria.length) {
|
|
472
|
-
return 0;
|
|
473
|
-
}
|
|
474
|
-
for (const criterion of sortCriteria) {
|
|
475
|
-
const cell1 = row1.cells.get(criterion.columnName);
|
|
476
|
-
const cell2 = row2.cells.get(criterion.columnName);
|
|
477
|
-
const value1 = cell1.value();
|
|
478
|
-
const value2 = cell2.value();
|
|
479
|
-
// Always place `undefined` values at the end, independent of the sort direction. Note that component and template columns have no value.
|
|
480
|
-
if (value1 === undefined && value2 !== undefined) {
|
|
481
|
-
return 1;
|
|
482
|
-
}
|
|
483
|
-
if (value2 === undefined && value1 !== undefined) {
|
|
484
|
-
return -1;
|
|
566
|
+
addTemplateColumn(descriptor) {
|
|
567
|
+
// Require a filter matcher if filterable using the built-in in-memory datasource.
|
|
568
|
+
if (this._table.isInMemoryDatasource() && descriptor.filterable === true) {
|
|
569
|
+
throw Error('[ColumnDefinitionError] Template column requires a filter matcher in order to be filterable.');
|
|
485
570
|
}
|
|
486
|
-
|
|
487
|
-
if (
|
|
488
|
-
|
|
571
|
+
// Require a sort comparator if sortable using the built-in in-memory datasource.
|
|
572
|
+
if (this._table.isInMemoryDatasource() && descriptor.sortable === true) {
|
|
573
|
+
throw Error('[ColumnDefinitionError] Template column requires a sort comparator in order to be sortable.');
|
|
489
574
|
}
|
|
575
|
+
const column = this.mapToColumn(descriptor);
|
|
576
|
+
this.columns.push({
|
|
577
|
+
...column,
|
|
578
|
+
type: 'template',
|
|
579
|
+
template: descriptor.template,
|
|
580
|
+
sortable: computed(() => this._table.sortable() && !!descriptor.sortable),
|
|
581
|
+
filterable: computed(() => this._table.filterable() && !!descriptor.filterable),
|
|
582
|
+
compare: typeof descriptor.sortable === 'object' ? descriptor.sortable.comparator : () => 0,
|
|
583
|
+
matches: typeof descriptor.filterable === 'object' ? descriptor.filterable.matcher : () => false,
|
|
584
|
+
padding: descriptor.padding ?? true,
|
|
585
|
+
});
|
|
586
|
+
return this;
|
|
490
587
|
}
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* Marks given {@link SciTableDataLoaderFn} as an array data source.
|
|
501
|
-
*/
|
|
502
|
-
function markAsArrayDatasource(loader) {
|
|
503
|
-
Object.defineProperty(loader, ARRAY_DATASOURCE_MARKER, { value: true, writable: false, enumerable: false, configurable: false });
|
|
504
|
-
return loader;
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Identifies a {@link SciTableDataLoaderFn} as an {@link arrayDatasource}.
|
|
508
|
-
*/
|
|
509
|
-
const ARRAY_DATASOURCE_MARKER = Symbol('ARRAY_DATASOURCE_HINT');
|
|
510
|
-
/**
|
|
511
|
-
* Coerces the given filter text into the specified column data type.
|
|
512
|
-
*/
|
|
513
|
-
function coerceFilterText(text, options) {
|
|
514
|
-
switch (options.to) {
|
|
515
|
-
case 'number': {
|
|
516
|
-
const number = Number.parseFloat(text);
|
|
517
|
-
return !Number.isNaN(number) ? number : undefined;
|
|
588
|
+
/**
|
|
589
|
+
* Maps given column descriptor to a {@link SciTableColumn}.
|
|
590
|
+
*/
|
|
591
|
+
mapToColumn(descriptor) {
|
|
592
|
+
// Require a unique column name.
|
|
593
|
+
const name = descriptor.name ?? `column:${this.columns.length}`;
|
|
594
|
+
if (this.columns.find(column => column.name === name)) {
|
|
595
|
+
throw Error(`[ColumnDefinitionError] Column names must be unique. "${name}" is defined more than once.`);
|
|
518
596
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
523
|
-
if (text === 'false' || text === '0') {
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
return undefined;
|
|
597
|
+
// Ensure filter matcher is only configured if using the built-in in-memory datasource.
|
|
598
|
+
if (!this._table.isInMemoryDatasource() && typeof descriptor.filterable === 'object') {
|
|
599
|
+
throw Error('[ColumnDefinitionError] Configuring a filter matcher is not supported for tables using a datasource. Filtering must be done by the datasource.');
|
|
527
600
|
}
|
|
528
|
-
|
|
529
|
-
|
|
601
|
+
// Ensure sort comparator is only configured if using the built-in in-memory datasource.
|
|
602
|
+
if (!this._table.isInMemoryDatasource() && typeof descriptor.sortable === 'object') {
|
|
603
|
+
throw Error('[ColumnDefinitionError] Configuring a sort comparator is not supported for tables using a datasource. Sorting must be done by the datasource.');
|
|
530
604
|
}
|
|
605
|
+
return {
|
|
606
|
+
name: name,
|
|
607
|
+
header: untracked(() => {
|
|
608
|
+
if (typeof descriptor.header === 'string') {
|
|
609
|
+
return { label: text(descriptor.header), icon: undefined, tooltip: undefined };
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
label: descriptor.header?.label !== undefined ? text(descriptor.header.label) : undefined,
|
|
613
|
+
icon: descriptor.header?.icon,
|
|
614
|
+
tooltip: descriptor.header?.tooltip !== undefined ? text(descriptor.header.tooltip) : undefined,
|
|
615
|
+
};
|
|
616
|
+
}),
|
|
617
|
+
resizable: computed(() => this._table.resizable() && (descriptor.resizable ?? true)),
|
|
618
|
+
width: computed(() => {
|
|
619
|
+
const userSettings = this._table.userSettings().columns?.find(column => column.name === name);
|
|
620
|
+
return userSettings?.width ? `${userSettings.width}px` : descriptor.width ?? '1fr';
|
|
621
|
+
}),
|
|
622
|
+
minWidth: descriptor.minWidth ?? 100,
|
|
623
|
+
resizing: signal(false),
|
|
624
|
+
padding: true,
|
|
625
|
+
location: { x: 0, width: 0 }, // set in `SciTableColumnComponent`
|
|
626
|
+
};
|
|
531
627
|
}
|
|
532
628
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
* The value is computed at most once, whether it gets preloaded during idle time or requested synchronously via {@link value()}.
|
|
537
|
-
*/
|
|
538
|
-
class eSciTableCellValue {
|
|
539
|
-
_valueFn;
|
|
540
|
-
_value;
|
|
541
|
-
constructor(preloader, _valueFn) {
|
|
542
|
-
this._valueFn = _valueFn;
|
|
543
|
-
preloader.queue(() => {
|
|
544
|
-
this._value ??= this._valueFn();
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
value() {
|
|
548
|
-
return this._value ??= untracked(() => this._valueFn());
|
|
629
|
+
function coerceColumnDescriptor(argument1, argument2) {
|
|
630
|
+
if (argument2 === undefined) {
|
|
631
|
+
return typeof argument1 === 'function' ? { value: argument1 } : argument1;
|
|
549
632
|
}
|
|
633
|
+
return {
|
|
634
|
+
header: argument1,
|
|
635
|
+
value: argument2,
|
|
636
|
+
};
|
|
550
637
|
}
|
|
551
638
|
|
|
552
639
|
/*
|
|
@@ -559,57 +646,68 @@ class eSciTableCellValue {
|
|
|
559
646
|
* SPDX-License-Identifier: EPL-2.0
|
|
560
647
|
*/
|
|
561
648
|
class SciTableCache {
|
|
562
|
-
|
|
563
|
-
...(ngDevMode ? [{ debugName: "
|
|
564
|
-
|
|
649
|
+
_entriesById = signal(new Map(), /* @ts-ignore */
|
|
650
|
+
...(ngDevMode ? [{ debugName: "_entriesById" }] : /* istanbul ignore next */ []));
|
|
651
|
+
_idsByEntry = computed(() => [...this._entriesById().entries()].reduce((map, [id, entry]) => {
|
|
652
|
+
const ids = map.get(entry) ?? new Set();
|
|
653
|
+
ids.add(id);
|
|
654
|
+
return map.set(entry, ids);
|
|
655
|
+
}, new Map()), /* @ts-ignore */
|
|
656
|
+
...(ngDevMode ? [{ debugName: "_idsByEntry" }] : /* istanbul ignore next */ []));
|
|
657
|
+
rowsById = computed(() => this._entries()
|
|
658
|
+
.flatMap(entry => entry.status() === 'error' ? [] : entry.value())
|
|
659
|
+
.reduce((acc, row) => row.id !== skeletonRowId ? acc.set(row.id, row) : acc, new Map()), { ...(ngDevMode ? { debugName: "rowsById" } : /* istanbul ignore next */ {}), equal: Objects.isEqual });
|
|
660
|
+
_entries = computed(() => [...new Set(this._entriesById().values())], /* @ts-ignore */
|
|
661
|
+
...(ngDevMode ? [{ debugName: "_entries" }] : /* istanbul ignore next */ []));
|
|
662
|
+
loading = computed(() => this._entries().some(entry => entry.isLoading()), /* @ts-ignore */
|
|
565
663
|
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
566
|
-
error = computed(() => this.
|
|
664
|
+
error = computed(() => this._entries().some(entry => entry.status() === 'error'), /* @ts-ignore */
|
|
567
665
|
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
568
|
-
values = computed(() => [...this._cache().values()], /* @ts-ignore */
|
|
569
|
-
...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
|
|
570
|
-
rowsByIndex = computed(() => this.values()
|
|
571
|
-
.flatMap(page => page.rows.value())
|
|
572
|
-
.reduce((acc, row) => acc.set(row.index, row), new Map()), { ...(ngDevMode ? { debugName: "rowsByIndex" } : /* istanbul ignore next */ {}), equal: Objects.isEqual });
|
|
573
|
-
rowsById = computed(() => this.values()
|
|
574
|
-
.flatMap(page => page.rows.value())
|
|
575
|
-
.reduce((acc, row) => row.id !== undefined ? acc.set(row.id, row) : acc, new Map()), { ...(ngDevMode ? { debugName: "rowsById" } : /* istanbul ignore next */ {}), equal: Objects.isEqual });
|
|
576
666
|
has(key) {
|
|
577
|
-
return this.
|
|
667
|
+
return this._entriesById().has(key);
|
|
578
668
|
}
|
|
579
|
-
|
|
580
|
-
|
|
669
|
+
set(ids, entry) {
|
|
670
|
+
this._entriesById.update(cache => {
|
|
671
|
+
const newCache = new Map(cache);
|
|
672
|
+
for (const id of ids) {
|
|
673
|
+
newCache.set(id, entry);
|
|
674
|
+
}
|
|
675
|
+
return newCache;
|
|
676
|
+
});
|
|
581
677
|
}
|
|
582
|
-
|
|
583
|
-
this.
|
|
678
|
+
deleteIfLoading(ids) {
|
|
679
|
+
this._entriesById.update(cache => {
|
|
584
680
|
const cacheCopy = new Map(cache);
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
cacheCopy.
|
|
588
|
-
|
|
681
|
+
const entries = new Set(ids.map(id => cacheCopy.get(id)));
|
|
682
|
+
for (const id of ids) {
|
|
683
|
+
if (cacheCopy.get(id)?.isLoading()) {
|
|
684
|
+
cacheCopy.delete(id);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Dispose entries, which have no ids left pointing to them.
|
|
688
|
+
for (const entry of entries) {
|
|
689
|
+
if (!entry) {
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
const idsByEntry = this._idsByEntry().get(entry);
|
|
693
|
+
if (entry.isLoading() && [...(idsByEntry ?? [])].every(id => ids.includes(id))) {
|
|
694
|
+
entry.destroy();
|
|
695
|
+
}
|
|
589
696
|
}
|
|
590
|
-
cacheCopy.set(key, entry);
|
|
591
697
|
return cacheCopy;
|
|
592
698
|
});
|
|
593
699
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (!cacheEntry?.rows.isLoading()) {
|
|
600
|
-
return;
|
|
700
|
+
reloadFailed() {
|
|
701
|
+
for (const entry of this._entries()) {
|
|
702
|
+
if (entry.status() === 'error') {
|
|
703
|
+
entry.reload();
|
|
704
|
+
}
|
|
601
705
|
}
|
|
602
|
-
this._cache.update(cache => {
|
|
603
|
-
const cacheCopy = new Map(cache);
|
|
604
|
-
cacheCopy.delete(key);
|
|
605
|
-
cacheEntry.dispose();
|
|
606
|
-
return cacheCopy;
|
|
607
|
-
});
|
|
608
706
|
}
|
|
609
707
|
clear() {
|
|
610
|
-
this.
|
|
611
|
-
for (const entry of cache.values()) {
|
|
612
|
-
entry.
|
|
708
|
+
this._entriesById.update(cache => {
|
|
709
|
+
for (const entry of new Set(cache.values())) {
|
|
710
|
+
entry.destroy();
|
|
613
711
|
}
|
|
614
712
|
return new Map();
|
|
615
713
|
});
|
|
@@ -625,32 +723,57 @@ class SciTableCache {
|
|
|
625
723
|
*
|
|
626
724
|
* SPDX-License-Identifier: EPL-2.0
|
|
627
725
|
*/
|
|
726
|
+
/**
|
|
727
|
+
* Provides row bindings to tables in the scope of the injector providing the bindings.
|
|
728
|
+
*
|
|
729
|
+
* Multiple providers can be registered. Providers at different injector tree levels form a hierarchy.
|
|
730
|
+
*/
|
|
628
731
|
function provideTableRowBinding(bindings) {
|
|
629
|
-
return
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
732
|
+
return [
|
|
733
|
+
{
|
|
734
|
+
provide: SCI_TABLE_ROW_BINDING,
|
|
735
|
+
useValue: bindings,
|
|
736
|
+
multi: true,
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
provide: SCI_TABLE_ROW_BINDINGS,
|
|
740
|
+
useFactory: () => {
|
|
741
|
+
return [
|
|
742
|
+
// Collect bindings registered in parent injectors.
|
|
743
|
+
...inject(SCI_TABLE_ROW_BINDINGS, { skipSelf: true, optional: true }) ?? [],
|
|
744
|
+
// Collect bindings registered in this injector.
|
|
745
|
+
...inject(SCI_TABLE_ROW_BINDING),
|
|
746
|
+
];
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
];
|
|
634
750
|
}
|
|
635
751
|
class ɵSciTableRowBindingFactory {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
752
|
+
icons = new Array();
|
|
753
|
+
parts = new Array();
|
|
754
|
+
attributes = new Array();
|
|
755
|
+
classes = new Array();
|
|
756
|
+
addIconBinding(icon) {
|
|
757
|
+
if (icon) {
|
|
758
|
+
this.icons.push(icon);
|
|
642
759
|
}
|
|
643
760
|
return this;
|
|
644
761
|
}
|
|
645
|
-
|
|
762
|
+
addPartBinding(name) {
|
|
646
763
|
if (name) {
|
|
647
|
-
this.
|
|
764
|
+
this.parts.push(name);
|
|
648
765
|
}
|
|
649
766
|
return this;
|
|
650
767
|
}
|
|
651
|
-
|
|
768
|
+
addAttributeBinding(name, value) {
|
|
769
|
+
if (value !== undefined) {
|
|
770
|
+
this.attributes.push([name, value]);
|
|
771
|
+
}
|
|
772
|
+
return this;
|
|
773
|
+
}
|
|
774
|
+
addClassBinding(name) {
|
|
652
775
|
if (name) {
|
|
653
|
-
this.
|
|
776
|
+
this.classes.push(name);
|
|
654
777
|
}
|
|
655
778
|
return this;
|
|
656
779
|
}
|
|
@@ -663,12 +786,341 @@ function coerceTableRowBindings(bindingsFn, item, index) {
|
|
|
663
786
|
}, /* @ts-ignore */
|
|
664
787
|
...(ngDevMode ? [{ debugName: "factory" }] : /* istanbul ignore next */ []));
|
|
665
788
|
return {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
789
|
+
icons: computed(() => factory().icons, { equal: Objects.isEqual }),
|
|
790
|
+
parts: computed(() => factory().parts, { equal: Objects.isEqual }),
|
|
791
|
+
attributes: computed(() => Object.fromEntries(factory().attributes), { equal: Objects.isEqual }),
|
|
792
|
+
classes: computed(() => factory().classes, { equal: Objects.isEqual }),
|
|
669
793
|
};
|
|
670
794
|
}
|
|
795
|
+
/**
|
|
796
|
+
* Multi-DI token for registering a {@link SciTableRowBindingFactory}.
|
|
797
|
+
*/
|
|
671
798
|
const SCI_TABLE_ROW_BINDING = new InjectionToken('SCI_TABLE_ROW_BINDING');
|
|
799
|
+
/**
|
|
800
|
+
* DI token to inject {@link SciTableRowBindingFactory} functions provided in the current and parent injectors.
|
|
801
|
+
*/
|
|
802
|
+
const SCI_TABLE_ROW_BINDINGS = new InjectionToken('SCI_TABLE_ROW_BINDINGS');
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Loads and builds the table's hierarchy using item IDs and parent relationships, without loading full items.
|
|
806
|
+
* Only operates on IDs, never full objects and regardless of the viewport / virtual scrolling state.
|
|
807
|
+
* For non-hierarchical datasources, treats each item as a root node and preserves the order from the unfiltered response.
|
|
808
|
+
*
|
|
809
|
+
* Uses two independent requests:
|
|
810
|
+
* - An unfiltered request loads all nodes initially and whenever the sort criteria change.
|
|
811
|
+
* - A filtered request loads matching nodes when a filter is present and whenever the filters change.
|
|
812
|
+
*
|
|
813
|
+
* Combines both responses to include matching nodes and their parents and children, preserving the order from the unfiltered response.
|
|
814
|
+
* When no filter is present, uses the unfiltered response directly.
|
|
815
|
+
*
|
|
816
|
+
* @example
|
|
817
|
+
* ```ts
|
|
818
|
+
* const unfilteredNodes = [
|
|
819
|
+
* {item: 'Downloads'},
|
|
820
|
+
* {item: 'Desktop'},
|
|
821
|
+
* {item: 'New Folder', parent: 'Desktop'},
|
|
822
|
+
* {item: 'notes.txt', parent: 'New Folder'},
|
|
823
|
+
* ];
|
|
824
|
+
*
|
|
825
|
+
* // Filtered by 'notes'
|
|
826
|
+
* const filteredNodes = [{item: 'notes.txt', parent: 'New Folder'}];
|
|
827
|
+
*
|
|
828
|
+
* // Result includes the matching node and its ancestors.
|
|
829
|
+
* const tree = [
|
|
830
|
+
* {item: 'Desktop'},
|
|
831
|
+
* {item: 'New Folder', parent: 'Desktop'},
|
|
832
|
+
* {item: 'notes.txt', parent: 'New Folder'},
|
|
833
|
+
* ];
|
|
834
|
+
* ```
|
|
835
|
+
*/
|
|
836
|
+
class SciTableTree {
|
|
837
|
+
_injector = inject(Injector);
|
|
838
|
+
_datasource;
|
|
839
|
+
_sortCriteria;
|
|
840
|
+
_columnFilters;
|
|
841
|
+
_tableFilter;
|
|
842
|
+
_activeNode = this.computeActiveNode();
|
|
843
|
+
_selectedNodes = this.computeSelectedNodes();
|
|
844
|
+
_expandedNodes = this.computeExpandedNodes();
|
|
845
|
+
_unfilteredNodes = this.computeUnfilteredNodes();
|
|
846
|
+
_filteredNodes = this.computeFilteredNodes();
|
|
847
|
+
_tree = this.computeTree();
|
|
848
|
+
expandedNodes = this._expandedNodes.asReadonly();
|
|
849
|
+
selectedNodes = this._selectedNodes.asReadonly();
|
|
850
|
+
activeNode = this._activeNode.asReadonly();
|
|
851
|
+
error = computed(() => this._unfilteredNodes.status() === 'error' || this._filteredNodes.status() === 'error', /* @ts-ignore */
|
|
852
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
853
|
+
loading = computed(() => this._unfilteredNodes.isLoading() || this._filteredNodes.isLoading(), /* @ts-ignore */
|
|
854
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
855
|
+
unfilteredNodesById = computed(() => this._unfilteredNodes.status() === 'error' ? new Map() : this._unfilteredNodes.value().reduce((acc, node) => acc.set(node.id, node), new Map()), /* @ts-ignore */
|
|
856
|
+
...(ngDevMode ? [{ debugName: "unfilteredNodesById" }] : /* istanbul ignore next */ []));
|
|
857
|
+
nodes = computed(() => this._tree().nodes, /* @ts-ignore */
|
|
858
|
+
...(ngDevMode ? [{ debugName: "nodes" }] : /* istanbul ignore next */ []));
|
|
859
|
+
nodesById = computed(() => this._tree().nodesById, /* @ts-ignore */
|
|
860
|
+
...(ngDevMode ? [{ debugName: "nodesById" }] : /* istanbul ignore next */ []));
|
|
861
|
+
childrenIdsByParent = computed(() => this._tree().childrenIdsByParent, /* @ts-ignore */
|
|
862
|
+
...(ngDevMode ? [{ debugName: "childrenIdsByParent" }] : /* istanbul ignore next */ []));
|
|
863
|
+
visibleNodes = this.computeVisibleNodes();
|
|
864
|
+
nodeIndexById = computed(() => this.visibleNodes().reduce((map, node, index) => map.set(node.id, index), new Map()), /* @ts-ignore */
|
|
865
|
+
...(ngDevMode ? [{ debugName: "nodeIndexById" }] : /* istanbul ignore next */ []));
|
|
866
|
+
visibleNodeCount = computed(() => this.visibleNodes().length, /* @ts-ignore */
|
|
867
|
+
...(ngDevMode ? [{ debugName: "visibleNodeCount" }] : /* istanbul ignore next */ []));
|
|
868
|
+
constructor(options) {
|
|
869
|
+
this._datasource = options.datasource;
|
|
870
|
+
this._columnFilters = options.columnFilters;
|
|
871
|
+
this._tableFilter = options.tableFilter;
|
|
872
|
+
this._sortCriteria = options.sortCriteria;
|
|
873
|
+
}
|
|
874
|
+
reload() {
|
|
875
|
+
this._unfilteredNodes.reload();
|
|
876
|
+
}
|
|
877
|
+
reloadFailed() {
|
|
878
|
+
if (this._unfilteredNodes.status() === 'error') {
|
|
879
|
+
this._unfilteredNodes.reload();
|
|
880
|
+
}
|
|
881
|
+
if (this._filteredNodes.status() === 'error') {
|
|
882
|
+
this._filteredNodes.reload();
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
expand(id) {
|
|
886
|
+
const ids = Arrays.coerce(id);
|
|
887
|
+
this._expandedNodes.update(toggledRows => new Set([...toggledRows, ...ids]));
|
|
888
|
+
}
|
|
889
|
+
expandAll() {
|
|
890
|
+
this._expandedNodes.set(new Set([...this.childrenIdsByParent().keys()].filter((id) => id !== undefined)));
|
|
891
|
+
}
|
|
892
|
+
collapse(id) {
|
|
893
|
+
const ids = Arrays.coerce(id);
|
|
894
|
+
this._expandedNodes.update(toggledRows => {
|
|
895
|
+
const newToggledRows = new Set(toggledRows);
|
|
896
|
+
ids.forEach(id => newToggledRows.delete(id));
|
|
897
|
+
return newToggledRows;
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
collapseAll() {
|
|
901
|
+
this._expandedNodes.set(new Set());
|
|
902
|
+
}
|
|
903
|
+
toggleRow(id) {
|
|
904
|
+
this._expandedNodes.update(toggledRows => {
|
|
905
|
+
if (toggledRows.has(id)) {
|
|
906
|
+
const newToggledRows = new Set(toggledRows);
|
|
907
|
+
newToggledRows.delete(id);
|
|
908
|
+
return newToggledRows;
|
|
909
|
+
}
|
|
910
|
+
else {
|
|
911
|
+
return new Set(toggledRows).add(id);
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
setActiveNode(id) {
|
|
916
|
+
this._activeNode.set(id);
|
|
917
|
+
}
|
|
918
|
+
updateSelectedNodes(updateFn) {
|
|
919
|
+
this._selectedNodes.update(updateFn);
|
|
920
|
+
}
|
|
921
|
+
select(ids) {
|
|
922
|
+
// Prevents selecting nodes which do not exist.
|
|
923
|
+
// Check against unfiltered nodes, to allow selecting nodes, which are filtered out.
|
|
924
|
+
const nodesById = this.unfilteredNodesById();
|
|
925
|
+
const nodeIds = Arrays.coerce(ids).filter(id => nodesById.has(id));
|
|
926
|
+
// Expand nodes.
|
|
927
|
+
nodeIds.forEach(id => {
|
|
928
|
+
const node = nodesById.get(id);
|
|
929
|
+
if (node?.parentId !== undefined && nodesById.has(node.parentId)) {
|
|
930
|
+
// Expand all nodes before the selected ones.
|
|
931
|
+
this.expand(getPathFromRoot(nodesById.get(node.parentId), nodesById));
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
// Wait for viewport height to update after expanding nodes.
|
|
935
|
+
// Else scrollActiveRowIntoViewport operates on an old viewport height.
|
|
936
|
+
requestAnimationFrame(() => {
|
|
937
|
+
this._selectedNodes.set(new Set(nodeIds));
|
|
938
|
+
// Find and activate last item based on the visual index.
|
|
939
|
+
const lastItemIndex = Math.max(...nodeIds.map(id => this.nodeIndexById().get(id) ?? -1));
|
|
940
|
+
const lastItem = this.visibleNodes()[lastItemIndex]?.id;
|
|
941
|
+
this._activeNode.set(lastItem);
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
getIds$(query, options) {
|
|
945
|
+
return runInInjectionContext(options.injector, () => Observables.coerce(this._datasource.getTreeTableNodes({
|
|
946
|
+
columnFilters: query.columnFilters ?? [],
|
|
947
|
+
sortCriteria: query.sortCriteria ?? [],
|
|
948
|
+
tableFilter: query.tableFilter,
|
|
949
|
+
})).pipe(catchError(error => {
|
|
950
|
+
console.error(error);
|
|
951
|
+
throw error;
|
|
952
|
+
})));
|
|
953
|
+
}
|
|
954
|
+
computeUnfilteredNodes() {
|
|
955
|
+
// Create separate injection context per resource invocation to clean up allocated resources.
|
|
956
|
+
const injectorFactory = new _SingleActiveInjectorFactory({ parent: this._injector });
|
|
957
|
+
return rxResource({
|
|
958
|
+
params: () => this._sortCriteria(),
|
|
959
|
+
stream: ({ params: sortCriteria }) => this.getIds$({ sortCriteria }, { injector: injectorFactory.create() }),
|
|
960
|
+
defaultValue: [],
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
computeFilteredNodes() {
|
|
964
|
+
// Create separate injection context per resource invocation to clean up allocated resources.
|
|
965
|
+
const injectorFactory = new _SingleActiveInjectorFactory({ parent: this._injector });
|
|
966
|
+
return rxResource({
|
|
967
|
+
params: () => ({
|
|
968
|
+
columnFilters: this._columnFilters(),
|
|
969
|
+
tableFilter: this._tableFilter() ?? undefined,
|
|
970
|
+
}),
|
|
971
|
+
stream: ({ params }) => {
|
|
972
|
+
const injector = injectorFactory.create();
|
|
973
|
+
if (params.columnFilters.length === 0 && params.tableFilter === undefined) {
|
|
974
|
+
return runInInjectionContext(injector, () => toObservable(this._unfilteredNodes.value));
|
|
975
|
+
}
|
|
976
|
+
return this.getIds$({ columnFilters: params.columnFilters, tableFilter: params.tableFilter }, { injector });
|
|
977
|
+
},
|
|
978
|
+
defaultValue: [],
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
computeTree() {
|
|
982
|
+
return computed(() => {
|
|
983
|
+
if (this.error()) {
|
|
984
|
+
return {
|
|
985
|
+
nodes: [],
|
|
986
|
+
nodesById: new Map(),
|
|
987
|
+
childrenIdsByParent: new Map(),
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
const filteredNodes = this._filteredNodes.value();
|
|
991
|
+
const unfilteredNodes = this._unfilteredNodes.value();
|
|
992
|
+
return untracked(() => createTree(filteredNodes, unfilteredNodes));
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
computeExpandedNodes() {
|
|
996
|
+
return linkedSignal({
|
|
997
|
+
source: () => ({ unfilteredNodesById: this.unfilteredNodesById(), loading: this._unfilteredNodes.isLoading(), columnFilters: this._columnFilters(), tableFilter: this._tableFilter() }),
|
|
998
|
+
computation: ({ unfilteredNodesById, loading, tableFilter, columnFilters }, previous) => {
|
|
999
|
+
// Wait until resource finished loading before changing the expanded nodes.
|
|
1000
|
+
if (loading) {
|
|
1001
|
+
return previous?.value ?? new Set();
|
|
1002
|
+
}
|
|
1003
|
+
// Expand all items if filter is set.
|
|
1004
|
+
if (columnFilters.length > 0 || tableFilter?.trim()) {
|
|
1005
|
+
return new Set(unfilteredNodesById.keys());
|
|
1006
|
+
}
|
|
1007
|
+
const expandedNodeIds = previous?.value ?? new Set();
|
|
1008
|
+
return new Set([...expandedNodeIds].filter(id => unfilteredNodesById.has(id)));
|
|
1009
|
+
},
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
computeSelectedNodes() {
|
|
1013
|
+
return linkedSignal({
|
|
1014
|
+
source: () => ({ unfilteredNodesById: this.unfilteredNodesById(), loading: this._unfilteredNodes.isLoading() }),
|
|
1015
|
+
computation: ({ unfilteredNodesById, loading }, previous) => {
|
|
1016
|
+
// Wait until resource finished loading before changing the selected nodes.
|
|
1017
|
+
if (loading) {
|
|
1018
|
+
return previous?.value ?? new Set();
|
|
1019
|
+
}
|
|
1020
|
+
const selectedNodeIds = previous?.value ?? new Set();
|
|
1021
|
+
return new Set([...selectedNodeIds].filter(id => unfilteredNodesById.has(id)));
|
|
1022
|
+
},
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
computeActiveNode() {
|
|
1026
|
+
return linkedSignal({
|
|
1027
|
+
source: () => ({ unfilteredNodesById: this.unfilteredNodesById(), loading: this._unfilteredNodes.isLoading() }),
|
|
1028
|
+
computation: ({ unfilteredNodesById, loading }, previous) => {
|
|
1029
|
+
// Wait until resource finished loading before changing the active nodes.
|
|
1030
|
+
if (loading) {
|
|
1031
|
+
return previous?.value;
|
|
1032
|
+
}
|
|
1033
|
+
return previous?.value !== undefined && unfilteredNodesById.has(previous.value) ? previous.value : undefined;
|
|
1034
|
+
},
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
computeVisibleNodes() {
|
|
1038
|
+
return computed(() => {
|
|
1039
|
+
if (this.error()) {
|
|
1040
|
+
return [];
|
|
1041
|
+
}
|
|
1042
|
+
const expandedNodes = this._expandedNodes();
|
|
1043
|
+
const nodesById = this.nodesById();
|
|
1044
|
+
const nodes = this.nodes();
|
|
1045
|
+
const allAncestorsExpanded = (node) => {
|
|
1046
|
+
if (node?.parentId === undefined) {
|
|
1047
|
+
return true;
|
|
1048
|
+
}
|
|
1049
|
+
if (!expandedNodes.has(node.parentId)) {
|
|
1050
|
+
return false;
|
|
1051
|
+
}
|
|
1052
|
+
return allAncestorsExpanded(nodesById.get(node.parentId));
|
|
1053
|
+
};
|
|
1054
|
+
return untracked(() => nodes.filter(allAncestorsExpanded));
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
function createTree(filteredNodes, unfilteredNodes) {
|
|
1059
|
+
const nodesById = includeParentsAndChildren(unfilteredNodes, filteredNodes);
|
|
1060
|
+
const sortedNodes = unfilteredNodes.filter(node => nodesById.has(node.id));
|
|
1061
|
+
const childrenIdsByParent = groupChildrenByParent(sortedNodes);
|
|
1062
|
+
const nodes = flattenTree(nodesById, childrenIdsByParent);
|
|
1063
|
+
return ({ nodes, nodesById, childrenIdsByParent });
|
|
1064
|
+
}
|
|
1065
|
+
function includeParentsAndChildren(unfilteredNodes, filteredNodes) {
|
|
1066
|
+
const nodesById = new Map(unfilteredNodes.map(node => [node.id, node]));
|
|
1067
|
+
const filteredIds = new Set(filteredNodes.map(node => node.id));
|
|
1068
|
+
return unfilteredNodes.reduce((acc, node) => {
|
|
1069
|
+
const path = getPathFromRoot(node, nodesById);
|
|
1070
|
+
// Include the full path (parents and children) of any filtered node.
|
|
1071
|
+
// This ensures that all parents and children are displayed, even if they don't match the filter.
|
|
1072
|
+
if (path.some(id => filteredIds.has(id))) {
|
|
1073
|
+
path.forEach(id => acc.set(id, nodesById.get(id)));
|
|
1074
|
+
}
|
|
1075
|
+
return acc;
|
|
1076
|
+
}, new Map());
|
|
1077
|
+
}
|
|
1078
|
+
function groupChildrenByParent(nodes) {
|
|
1079
|
+
return nodes.reduce((acc, node) => {
|
|
1080
|
+
const values = acc.get(node.parentId) ?? [];
|
|
1081
|
+
values.push(node.id);
|
|
1082
|
+
return acc.set(node.parentId, values);
|
|
1083
|
+
}, new Map());
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Flattens the tree in depth-first order, placing each parent before its descendants and preserving sibling order.
|
|
1087
|
+
* Includes all nodes regardless of expansion state.
|
|
1088
|
+
*
|
|
1089
|
+
* Example:
|
|
1090
|
+
* ```
|
|
1091
|
+
* [
|
|
1092
|
+
* {id: '1'},
|
|
1093
|
+
* {id: '1.1', parentId: '1'},
|
|
1094
|
+
* {id: '1.2', parentId: '1'},
|
|
1095
|
+
* {id: '1.2.1', parentId: '1.2'},
|
|
1096
|
+
* {id: '1.3', parentId: '1'},
|
|
1097
|
+
* {id: '2'},
|
|
1098
|
+
* ]
|
|
1099
|
+
* ```
|
|
1100
|
+
*/
|
|
1101
|
+
function flattenTree(nodesById, childrenIdsByParent) {
|
|
1102
|
+
const nodes = [];
|
|
1103
|
+
const roots = childrenIdsByParent.get(undefined) ?? [];
|
|
1104
|
+
const stack = roots.slice().reverse();
|
|
1105
|
+
// Don't use recursion, to avoid stack overflow on large amount of root nodes (e.g. flat table).
|
|
1106
|
+
while (stack.length > 0) {
|
|
1107
|
+
const id = stack.pop();
|
|
1108
|
+
nodes.push(nodesById.get(id));
|
|
1109
|
+
// Push children in reverse order so the first child is visited next.
|
|
1110
|
+
const children = childrenIdsByParent.get(id) ?? [];
|
|
1111
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
1112
|
+
if (children[i] !== undefined) {
|
|
1113
|
+
stack.push(children[i]);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return nodes;
|
|
1118
|
+
}
|
|
1119
|
+
function getPathFromRoot(node, nodesById) {
|
|
1120
|
+
const parent = node.parentId !== undefined ? nodesById.get(node.parentId) : undefined;
|
|
1121
|
+
const pathFromRoot = parent ? getPathFromRoot(parent, nodesById) : [];
|
|
1122
|
+
return pathFromRoot.concat(node.id);
|
|
1123
|
+
}
|
|
672
1124
|
|
|
673
1125
|
/*
|
|
674
1126
|
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
@@ -679,19 +1131,23 @@ const SCI_TABLE_ROW_BINDING = new InjectionToken('SCI_TABLE_ROW_BINDING');
|
|
|
679
1131
|
*
|
|
680
1132
|
* SPDX-License-Identifier: EPL-2.0
|
|
681
1133
|
*/
|
|
1134
|
+
/**
|
|
1135
|
+
* @docs-private Not public API. For internal use only.
|
|
1136
|
+
*/
|
|
682
1137
|
class ɵSciTable {
|
|
683
|
-
_tableStorage = inject(
|
|
1138
|
+
_tableStorage = inject(SCI_COMPONENTS_STORAGE);
|
|
684
1139
|
_injector = inject(Injector);
|
|
685
1140
|
name = signal(undefined, /* @ts-ignore */
|
|
686
1141
|
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
687
1142
|
columns;
|
|
688
1143
|
rowActions;
|
|
689
1144
|
_rowBindings;
|
|
690
|
-
|
|
1145
|
+
_datasource;
|
|
691
1146
|
_trackBy;
|
|
692
1147
|
tableViewRef = signal(undefined, /* @ts-ignore */
|
|
693
1148
|
...(ngDevMode ? [{ debugName: "tableViewRef" }] : /* istanbul ignore next */ []));
|
|
694
1149
|
userSettings;
|
|
1150
|
+
type;
|
|
695
1151
|
bufferSize;
|
|
696
1152
|
pageSize;
|
|
697
1153
|
filterable;
|
|
@@ -710,42 +1166,38 @@ class ɵSciTable {
|
|
|
710
1166
|
sortCriteria = linkedSignal({ ...(ngDevMode ? { debugName: "sortCriteria" } : /* istanbul ignore next */ {}), source: () => this.columns(), // Discard stale criteria on column change.
|
|
711
1167
|
computation: (columns, previous) => previous?.value.filter(criteria => columns.some(column => column.name === criteria.columnName)) ?? [],
|
|
712
1168
|
equal: Objects.isEqual });
|
|
713
|
-
|
|
1169
|
+
columnFilters = linkedSignal({ ...(ngDevMode ? { debugName: "columnFilters" } : /* istanbul ignore next */ {}), source: () => this.columns(), // Discard stale criteria on column change.
|
|
714
1170
|
computation: (columns, previous) => previous?.value.filter(criteria => columns.some(column => column.name === criteria.columnName)) ?? [],
|
|
715
1171
|
equal: Objects.isEqual });
|
|
716
1172
|
_cache = new SciTableCache();
|
|
717
1173
|
_tableCellValuePreloader = inject(SciTableCellValuePreloader);
|
|
718
1174
|
_tableFilter = signal(null, /* @ts-ignore */
|
|
719
1175
|
...(ngDevMode ? [{ debugName: "_tableFilter" }] : /* istanbul ignore next */ []));
|
|
720
|
-
|
|
721
|
-
...(ngDevMode ? [{ debugName: "_selectedItems" }] : /* istanbul ignore next */ []));
|
|
722
|
-
// Reset total count on criteria change to show skeletons instead of stale data while loading.
|
|
723
|
-
totalCount = linkedSignal({ ...(ngDevMode ? { debugName: "totalCount" } : /* istanbul ignore next */ {}), source: () => this.criteria(),
|
|
724
|
-
computation: () => undefined });
|
|
725
|
-
activeItem = linkedSignal({ ...(ngDevMode ? { debugName: "activeItem" } : /* istanbul ignore next */ {}), source: () => this.criteria(),
|
|
726
|
-
computation: () => undefined });
|
|
1176
|
+
_tableTree;
|
|
727
1177
|
hoveredIndex = linkedSignal({ ...(ngDevMode ? { debugName: "hoveredIndex" } : /* istanbul ignore next */ {}), source: () => {
|
|
728
1178
|
this.criteria(); // reset on criteria change
|
|
729
1179
|
this.scrolling(); // reset when start scrolling
|
|
730
1180
|
},
|
|
731
1181
|
computation: () => -1 });
|
|
732
|
-
criteria = computed(() => ({ sort: this.sortCriteria(), filter: this.
|
|
1182
|
+
criteria = computed(() => ({ sort: this.sortCriteria(), filter: this.columnFilters(), tableFilter: this._tableFilter() }), /* @ts-ignore */
|
|
733
1183
|
...(ngDevMode ? [{ debugName: "criteria" }] : /* istanbul ignore next */ []));
|
|
734
|
-
loading = computed(() => this._cache.loading() || this._tableCellValuePreloader.loading(), /* @ts-ignore */
|
|
1184
|
+
loading = computed(() => this._tableTree.loading() || this._cache.loading() || this._tableCellValuePreloader.loading(), /* @ts-ignore */
|
|
735
1185
|
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
736
|
-
error = this._cache.error
|
|
1186
|
+
error = computed(() => this._cache.error() || this._tableTree.error(), /* @ts-ignore */
|
|
1187
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
737
1188
|
activeRow;
|
|
738
|
-
|
|
739
|
-
...(ngDevMode ? [{ debugName: "hoveredRow" }] : /* istanbul ignore next */ []));
|
|
740
|
-
selectedItems = computed(() => [...this._selectedItems().values()], /* @ts-ignore */
|
|
1189
|
+
selectedItems = computed(() => [...this._tableTree.selectedNodes().values()], /* @ts-ignore */
|
|
741
1190
|
...(ngDevMode ? [{ debugName: "selectedItems" }] : /* istanbul ignore next */ []));
|
|
742
|
-
|
|
743
|
-
...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
|
|
744
|
-
rowsByIndex = this._cache.rowsByIndex;
|
|
1191
|
+
rowsById = this._cache.rowsById;
|
|
745
1192
|
rows = this.computeRows();
|
|
1193
|
+
visibleRowCount;
|
|
1194
|
+
visibleRows;
|
|
1195
|
+
rowIndexById;
|
|
1196
|
+
activeItem;
|
|
1197
|
+
activeIndex = computed(() => this.activeItem() !== undefined ? (this.rowIndexById().get(this.activeItem()) ?? -1) : -1, /* @ts-ignore */
|
|
1198
|
+
...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
746
1199
|
constructor(descriptor) {
|
|
747
|
-
|
|
748
|
-
descriptor = { ...descriptor, ɵdatasource: descriptor.ɵdatasource ?? descriptor.datasource };
|
|
1200
|
+
this.type = descriptor.type ?? 'table';
|
|
749
1201
|
this.bufferSize = descriptor.bufferSize ?? 10;
|
|
750
1202
|
this.pageSize = descriptor.pageSize ?? 50;
|
|
751
1203
|
this.sortable = signal(descriptor.sortable ?? true, /* @ts-ignore */
|
|
@@ -771,11 +1223,21 @@ class ɵSciTable {
|
|
|
771
1223
|
this.rowActions = descriptor.rowActions;
|
|
772
1224
|
this._rowBindings = [
|
|
773
1225
|
...descriptor.rowBindings ? [descriptor.rowBindings] : [],
|
|
774
|
-
...inject(
|
|
1226
|
+
...inject(SCI_TABLE_ROW_BINDINGS, { optional: true }) ?? [],
|
|
775
1227
|
];
|
|
776
1228
|
this._trackBy = descriptor.trackBy;
|
|
777
|
-
|
|
778
|
-
this.
|
|
1229
|
+
const datasourceProvider = isSignal(descriptor.datasource) ? provideTableDatasource(descriptor.datasource) : descriptor.datasource;
|
|
1230
|
+
this._datasource = datasourceProvider[eSCI_TABLE_DATASOURCE_PROVIDER](this);
|
|
1231
|
+
this._tableTree = new SciTableTree({
|
|
1232
|
+
datasource: this._datasource,
|
|
1233
|
+
sortCriteria: this.sortCriteria,
|
|
1234
|
+
columnFilters: this.columnFilters,
|
|
1235
|
+
tableFilter: this._tableFilter,
|
|
1236
|
+
});
|
|
1237
|
+
this.visibleRowCount = this._tableTree.visibleNodeCount;
|
|
1238
|
+
this.rowIndexById = this._tableTree.nodeIndexById;
|
|
1239
|
+
this.visibleRows = this._tableTree.visibleNodes;
|
|
1240
|
+
this.activeItem = this._tableTree.activeNode;
|
|
779
1241
|
this.installPageLoader();
|
|
780
1242
|
}
|
|
781
1243
|
/**
|
|
@@ -790,17 +1252,13 @@ class ɵSciTable {
|
|
|
790
1252
|
this.tableViewRef.set(undefined);
|
|
791
1253
|
}
|
|
792
1254
|
computeColumns(descriptor) {
|
|
793
|
-
// Create
|
|
794
|
-
|
|
795
|
-
return computed(() => {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
return
|
|
799
|
-
|
|
800
|
-
descriptor.columns(tableColumnFactory);
|
|
801
|
-
return tableColumnFactory.columns;
|
|
802
|
-
});
|
|
803
|
-
});
|
|
1255
|
+
// Create a factory to instantiate a new injector per column factory execution, releasing resources allocated in the previous execution.
|
|
1256
|
+
const injectorFactory = new _SingleActiveInjectorFactory();
|
|
1257
|
+
return computed(() => runInInjectionContext(injectorFactory.create(), () => {
|
|
1258
|
+
const tableColumnFactory = new ɵSciTableColumnFactory(this);
|
|
1259
|
+
descriptor.columns(tableColumnFactory);
|
|
1260
|
+
return tableColumnFactory.columns;
|
|
1261
|
+
}));
|
|
804
1262
|
}
|
|
805
1263
|
/**
|
|
806
1264
|
* Computes the rows currently visible in the viewport (+buffer).
|
|
@@ -811,15 +1269,29 @@ class ɵSciTable {
|
|
|
811
1269
|
if (!scrollRange) {
|
|
812
1270
|
return [];
|
|
813
1271
|
}
|
|
814
|
-
const
|
|
815
|
-
const
|
|
1272
|
+
const visibleNodes = this._tableTree.visibleNodes();
|
|
1273
|
+
const rowsById = this.rowsById();
|
|
1274
|
+
const rangeCount = scrollRange.end - scrollRange.start;
|
|
1275
|
+
const rowCount = Math.min(rangeCount, this._tableTree.loading() ? rangeCount : visibleNodes.length);
|
|
816
1276
|
// Populate rows with cached rows in the range window, fallback to row shell to show skeleton.
|
|
817
|
-
return untracked(() => Array.from({ length: rowCount }, (_, i) =>
|
|
1277
|
+
return untracked(() => Array.from({ length: rowCount }, (_, i) => {
|
|
1278
|
+
const node = visibleNodes[scrollRange.start + i];
|
|
1279
|
+
if (node && rowsById.has(node.id)) {
|
|
1280
|
+
return rowsById.get(node.id);
|
|
1281
|
+
}
|
|
1282
|
+
return createSkeletonRow(i);
|
|
1283
|
+
}));
|
|
818
1284
|
});
|
|
819
|
-
function
|
|
1285
|
+
function createSkeletonRow(index) {
|
|
820
1286
|
return {
|
|
821
1287
|
index,
|
|
822
|
-
|
|
1288
|
+
id: skeletonRowId,
|
|
1289
|
+
item: undefined,
|
|
1290
|
+
cells: [],
|
|
1291
|
+
bindings: coerceTableRowBindings([], undefined, index),
|
|
1292
|
+
level: 0,
|
|
1293
|
+
hasChildren: signal(false).asReadonly(),
|
|
1294
|
+
expanded: signal(false).asReadonly(),
|
|
823
1295
|
active: signal(false).asReadonly(),
|
|
824
1296
|
selected: signal(false).asReadonly(),
|
|
825
1297
|
hovered: signal(false).asReadonly(),
|
|
@@ -841,7 +1313,8 @@ class ɵSciTable {
|
|
|
841
1313
|
const start = Math.floor(scrollTop / itemHeight);
|
|
842
1314
|
const viewportRowCount = Math.ceil(viewportHeight / itemHeight);
|
|
843
1315
|
const end = Math.min(start + viewportRowCount);
|
|
844
|
-
|
|
1316
|
+
// fill viewport if no data loaded yet
|
|
1317
|
+
const totalCount = this._tableTree.loading() ? viewportRowCount : this.visibleRowCount();
|
|
845
1318
|
return {
|
|
846
1319
|
start: clamp(start - this.bufferSize, { min: 0, max: Math.max(0, totalCount - viewportRowCount) }),
|
|
847
1320
|
end: clamp(end + this.bufferSize, { max: totalCount }),
|
|
@@ -850,9 +1323,13 @@ class ɵSciTable {
|
|
|
850
1323
|
}
|
|
851
1324
|
computeVirtualScrollOffset() {
|
|
852
1325
|
return computed(() => {
|
|
1326
|
+
// Remove virtual-scroll padding so the error message and retry button are in view.
|
|
1327
|
+
if (this.error()) {
|
|
1328
|
+
return { top: 0, bottom: 0 };
|
|
1329
|
+
}
|
|
853
1330
|
const itemHeight = this.tableViewRef()?.itemHeight() ?? 0;
|
|
854
|
-
const totalCount = this.
|
|
855
|
-
const rangeEnd = Math.min(this.scrollRange()?.end ?? 0,
|
|
1331
|
+
const totalCount = this.visibleRowCount();
|
|
1332
|
+
const rangeEnd = Math.min(this.scrollRange()?.end ?? 0, totalCount);
|
|
856
1333
|
return {
|
|
857
1334
|
top: (this.scrollRange()?.start ?? 0) * itemHeight,
|
|
858
1335
|
bottom: (totalCount - rangeEnd) * itemHeight,
|
|
@@ -890,74 +1367,44 @@ class ɵSciTable {
|
|
|
890
1367
|
.pipe(switchMap(() => concat(of(true), timer(150).pipe(map(() => false)))), startWith(false), subscribeIn(fn => zone.runOutsideAngular(fn))),
|
|
891
1368
|
}).value;
|
|
892
1369
|
}
|
|
893
|
-
/**
|
|
894
|
-
* Loads a range of rows, based on the current sort and filter criteria, into the cache.
|
|
895
|
-
*/
|
|
896
|
-
async loadRange(start, end) {
|
|
897
|
-
const sortCriteria = this.sortCriteria();
|
|
898
|
-
const columnFilters = this.filterCriteria();
|
|
899
|
-
const tableFilter = this._tableFilter() ?? undefined;
|
|
900
|
-
// Calculate pages based on row indices and page size.
|
|
901
|
-
const pages = pagesByRange(start, end, this.pageSize);
|
|
902
|
-
if (!pages) {
|
|
903
|
-
return;
|
|
904
|
-
}
|
|
905
|
-
// Load pages and wait until completed loading.
|
|
906
|
-
await Promise.all(pages
|
|
907
|
-
.map(page => this.loadPage({ page, pageSize: this.pageSize, columnFilters, tableFilter, sortCriteria }))
|
|
908
|
-
.map(page => firstValueFrom(toLazyObservable(page.loading, { injector: this._injector }).pipe(first(loading => !loading), defaultIfEmpty(true)))));
|
|
909
|
-
}
|
|
910
1370
|
/**
|
|
911
1371
|
* Loads the requested page from the cache or datasource and returns its loading state with a cancelation handler.
|
|
912
1372
|
*/
|
|
913
|
-
loadPage(
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
const cacheKey = `${pageStart}-${pageEnd}`;
|
|
917
|
-
if (this._cache.has(cacheKey)) {
|
|
1373
|
+
loadPage(nodeIds) {
|
|
1374
|
+
const nodeIdsToLoad = nodeIds.filter(id => !this._cache.has(id));
|
|
1375
|
+
if (nodeIdsToLoad.length === 0) {
|
|
918
1376
|
return {
|
|
919
|
-
|
|
920
|
-
cancel: () => this._cache.deleteIfLoading(cacheKey),
|
|
1377
|
+
cancel: () => this._cache.deleteIfLoading(nodeIds),
|
|
921
1378
|
};
|
|
922
1379
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
}), map(({ rows }) => rows)),
|
|
950
|
-
defaultValue: [],
|
|
951
|
-
});
|
|
952
|
-
}),
|
|
953
|
-
dispose: () => cacheEntryInjector.destroy(),
|
|
954
|
-
start: pageStart,
|
|
955
|
-
end: pageEnd,
|
|
956
|
-
};
|
|
957
|
-
this._cache.set(cacheKey, cacheEntry);
|
|
1380
|
+
// Track consistent snapshot of columns and node Ids.
|
|
1381
|
+
// If the access happens directly in `mapItemsToRow` they can be out of sync because `nodesById` depends on `columns`.
|
|
1382
|
+
// Don't use the rowMapping data as resource params, since it should not refetch from the datasource on change.
|
|
1383
|
+
const snapshot = computed(() => {
|
|
1384
|
+
const columns = this.columns();
|
|
1385
|
+
const nodesById = this._tableTree.nodesById();
|
|
1386
|
+
const nodeIndexById = this._tableTree.nodeIndexById();
|
|
1387
|
+
return { columns, nodesById, nodeIndexById };
|
|
1388
|
+
}, /* @ts-ignore */
|
|
1389
|
+
...(ngDevMode ? [{ debugName: "snapshot" }] : /* istanbul ignore next */ []));
|
|
1390
|
+
// Create a resource to track loading and error states.
|
|
1391
|
+
this._cache.set(nodeIdsToLoad, rxResource({
|
|
1392
|
+
stream: () => {
|
|
1393
|
+
// Injector for the datasource call; destroyed on stream completion to clean up allocated resources.
|
|
1394
|
+
const datasourceInjector = _createDestroyableInjector({ parent: this._injector });
|
|
1395
|
+
// Create separate injection context per items emission to clean up allocated resources.
|
|
1396
|
+
const itemsInjectorFactory = new _SingleActiveInjectorFactory({ parent: datasourceInjector });
|
|
1397
|
+
return runInInjectionContext(datasourceInjector, () => Observables.coerce(this._datasource.getItems(nodeIdsToLoad)))
|
|
1398
|
+
.pipe(combineLatestWith(toLazyObservable(snapshot, { injector: datasourceInjector })), map(([response, { columns, nodesById, nodeIndexById }]) => runInInjectionContext(itemsInjectorFactory.create(), () => this.mapItemsToRow(response, columns, nodesById, nodeIndexById))), catchError(error => {
|
|
1399
|
+
console.error(error);
|
|
1400
|
+
throw error;
|
|
1401
|
+
}), finalize(() => datasourceInjector.destroy()));
|
|
1402
|
+
},
|
|
1403
|
+
injector: this._injector,
|
|
1404
|
+
defaultValue: [],
|
|
1405
|
+
}));
|
|
958
1406
|
return {
|
|
959
|
-
|
|
960
|
-
cancel: () => this._cache.deleteIfLoading(cacheKey),
|
|
1407
|
+
cancel: () => this._cache.deleteIfLoading(nodeIdsToLoad),
|
|
961
1408
|
};
|
|
962
1409
|
}
|
|
963
1410
|
/**
|
|
@@ -996,28 +1443,30 @@ class ɵSciTable {
|
|
|
996
1443
|
if (!column) {
|
|
997
1444
|
throw Error(`[NullColumnError] Column '${options.columnName}' not found in table '${this.name()}'.`);
|
|
998
1445
|
}
|
|
999
|
-
this.
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
return otherFilterCriteria;
|
|
1003
|
-
}
|
|
1004
|
-
return otherFilterCriteria.concat({ columnName: column.name, text });
|
|
1446
|
+
this.columnFilters.update(filterCriteria => {
|
|
1447
|
+
const newFilterCriteria = filterCriteria.filter(filterCriterion => filterCriterion.columnName !== options.columnName);
|
|
1448
|
+
return text === null ? newFilterCriteria : newFilterCriteria.concat({ columnName: column.name, text });
|
|
1005
1449
|
});
|
|
1006
1450
|
}
|
|
1451
|
+
setActiveItem(id) {
|
|
1452
|
+
this._tableTree.setActiveNode(id);
|
|
1453
|
+
}
|
|
1007
1454
|
updateSelectedItems(updateFn) {
|
|
1008
|
-
this.
|
|
1455
|
+
this._tableTree.updateSelectedNodes(updateFn);
|
|
1009
1456
|
}
|
|
1010
1457
|
/**
|
|
1011
|
-
* Indicates whether the built-in
|
|
1012
|
-
*
|
|
1013
|
-
* Supports out-of-the-box filtering and sorting for built-in table columns.
|
|
1458
|
+
* Indicates whether the built-in in-memory datasource is used.
|
|
1014
1459
|
*/
|
|
1015
|
-
|
|
1016
|
-
return
|
|
1460
|
+
isInMemoryDatasource() {
|
|
1461
|
+
return this._datasource instanceof SciInMemoryTableDatasource;
|
|
1017
1462
|
}
|
|
1018
|
-
|
|
1463
|
+
reload() {
|
|
1019
1464
|
this._cache.clear();
|
|
1020
|
-
this.
|
|
1465
|
+
this._tableTree.reload();
|
|
1466
|
+
}
|
|
1467
|
+
reloadFailed() {
|
|
1468
|
+
this._cache.reloadFailed();
|
|
1469
|
+
this._tableTree.reloadFailed();
|
|
1021
1470
|
}
|
|
1022
1471
|
dispose() {
|
|
1023
1472
|
this._cache.clear();
|
|
@@ -1029,51 +1478,45 @@ class ɵSciTable {
|
|
|
1029
1478
|
installPageLoader() {
|
|
1030
1479
|
effect(onCleanup => {
|
|
1031
1480
|
const scrollRange = this.scrollRange();
|
|
1032
|
-
const
|
|
1033
|
-
const columnFilters = this.filterCriteria();
|
|
1034
|
-
const tableFilter = this._tableFilter() ?? undefined;
|
|
1481
|
+
const visibleNodes = this._tableTree.visibleNodes();
|
|
1035
1482
|
if (!scrollRange) {
|
|
1036
1483
|
return;
|
|
1037
1484
|
}
|
|
1038
1485
|
untracked(() => {
|
|
1039
|
-
//
|
|
1040
|
-
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
const
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
sortCriteria,
|
|
1047
|
-
tableFilter,
|
|
1048
|
-
columnFilters,
|
|
1049
|
-
});
|
|
1486
|
+
// Calculate start and end pages that contain the range.
|
|
1487
|
+
const startPage = Math.floor(scrollRange.start / this.pageSize);
|
|
1488
|
+
const endPage = Math.floor((scrollRange.end - 1) / this.pageSize);
|
|
1489
|
+
for (let page = startPage; page <= endPage; page++) {
|
|
1490
|
+
const index = page * this.pageSize;
|
|
1491
|
+
const nodes = visibleNodes.slice(index, index + this.pageSize).map(node => node.id);
|
|
1492
|
+
const pageRef = this.loadPage(nodes);
|
|
1050
1493
|
onCleanup(() => pageRef.cancel());
|
|
1051
|
-
}
|
|
1494
|
+
}
|
|
1052
1495
|
});
|
|
1053
1496
|
});
|
|
1054
1497
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
toObservable(this.criteria).pipe(skip(1), // skip first emission to avoid race condition with loader on initialization.
|
|
1060
|
-
takeUntilDestroyed()).subscribe(() => {
|
|
1061
|
-
this._cache.clear();
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
mapItemsToRow(items, columns, pageStart) {
|
|
1065
|
-
return items.map((item, i) => {
|
|
1498
|
+
mapItemsToRow(items, columns, nodesById, nodeIndexById) {
|
|
1499
|
+
// Indicates whether there are nodes with children.
|
|
1500
|
+
const isTree = Array.from(nodesById.values()).some(node => node.parentId !== undefined);
|
|
1501
|
+
return items.map((item) => {
|
|
1066
1502
|
const id = this.trackBy(item);
|
|
1067
|
-
const index =
|
|
1503
|
+
const index = nodeIndexById.get(id);
|
|
1504
|
+
const node = nodesById.get(id);
|
|
1505
|
+
if (node === undefined || index === undefined) {
|
|
1506
|
+
return undefined;
|
|
1507
|
+
}
|
|
1068
1508
|
return {
|
|
1069
1509
|
id,
|
|
1070
1510
|
index,
|
|
1071
1511
|
item,
|
|
1072
|
-
|
|
1073
|
-
active: computed(() =>
|
|
1074
|
-
selected: computed(() => this.
|
|
1075
|
-
|
|
1076
|
-
|
|
1512
|
+
hovered: computed(() => this.hoveredIndex() === index),
|
|
1513
|
+
active: computed(() => this.activeItem() === id),
|
|
1514
|
+
selected: computed(() => this._tableTree.selectedNodes().has(id)),
|
|
1515
|
+
expanded: computed(() => this._tableTree.expandedNodes().has(id)),
|
|
1516
|
+
level: isTree ? calculateLevel(node) : undefined,
|
|
1517
|
+
parentId: node.parentId,
|
|
1518
|
+
hasChildren: computed(() => (this._tableTree.childrenIdsByParent().get(id)?.length ?? 0) > 0),
|
|
1519
|
+
bindings: coerceTableRowBindings(this._rowBindings, item, index),
|
|
1077
1520
|
cells: columns.map(column => ({
|
|
1078
1521
|
value: 'value' in column ? column.value(item) : undefined,
|
|
1079
1522
|
component: column.type === 'component' ? column.component(item) : undefined,
|
|
@@ -1082,7 +1525,13 @@ class ɵSciTable {
|
|
|
1082
1525
|
column,
|
|
1083
1526
|
})),
|
|
1084
1527
|
};
|
|
1085
|
-
});
|
|
1528
|
+
}).filter((node) => !!node);
|
|
1529
|
+
function calculateLevel(node) {
|
|
1530
|
+
if (node.parentId === undefined) {
|
|
1531
|
+
return 0;
|
|
1532
|
+
}
|
|
1533
|
+
return calculateLevel(nodesById.get(node.parentId)) + 1;
|
|
1534
|
+
}
|
|
1086
1535
|
}
|
|
1087
1536
|
/**
|
|
1088
1537
|
* Creates a signal to read and write table user settings.
|
|
@@ -1092,7 +1541,7 @@ class ɵSciTable {
|
|
|
1092
1541
|
const userSettings = resource({ ...(ngDevMode ? { debugName: "userSettings" } : /* istanbul ignore next */ {}), params: () => this.name(),
|
|
1093
1542
|
loader: async ({ params: tableName }) => {
|
|
1094
1543
|
try {
|
|
1095
|
-
const serialized = await this._tableStorage.load(`scion.components
|
|
1544
|
+
const serialized = await this._tableStorage.load(`scion.components:${tableName}`);
|
|
1096
1545
|
return serialized ? JSON.parse(serialized) : { columns: [] };
|
|
1097
1546
|
}
|
|
1098
1547
|
catch (error) {
|
|
@@ -1104,7 +1553,7 @@ class ɵSciTable {
|
|
|
1104
1553
|
// Persist settings to storage.
|
|
1105
1554
|
effect(() => {
|
|
1106
1555
|
if (userSettings.status() === 'local' && this.name()) {
|
|
1107
|
-
untracked(() => void this._tableStorage.store(`scion.components
|
|
1556
|
+
untracked(() => void this._tableStorage.store(`scion.components:${this.name()}`, JSON.stringify(userSettings.value())));
|
|
1108
1557
|
}
|
|
1109
1558
|
});
|
|
1110
1559
|
return userSettings.value;
|
|
@@ -1113,30 +1562,39 @@ class ɵSciTable {
|
|
|
1113
1562
|
* Call trackBy function or fallback to track by object reference.
|
|
1114
1563
|
*/
|
|
1115
1564
|
trackBy(item) {
|
|
1116
|
-
|
|
1565
|
+
if (this._trackBy !== undefined) {
|
|
1566
|
+
return this._trackBy(item);
|
|
1567
|
+
}
|
|
1568
|
+
return item;
|
|
1117
1569
|
}
|
|
1118
1570
|
computeActiveRow() {
|
|
1119
1571
|
return computed(() => {
|
|
1120
1572
|
const activeItem = this.activeItem();
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
}
|
|
1124
|
-
const id = this.trackBy(activeItem);
|
|
1125
|
-
if (id === undefined) {
|
|
1126
|
-
return undefined;
|
|
1127
|
-
}
|
|
1128
|
-
return this._cache.rowsById().get(id);
|
|
1573
|
+
const rowsById = this._cache.rowsById();
|
|
1574
|
+
return rowsById.has(activeItem) ? rowsById.get(activeItem) : undefined;
|
|
1129
1575
|
});
|
|
1130
1576
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1577
|
+
select(ids) {
|
|
1578
|
+
this._tableTree.select(ids);
|
|
1579
|
+
}
|
|
1580
|
+
expand(ids) {
|
|
1581
|
+
this._tableTree.expand(ids);
|
|
1582
|
+
}
|
|
1583
|
+
isExpanded(id) {
|
|
1584
|
+
return this._tableTree.expandedNodes().has(id);
|
|
1585
|
+
}
|
|
1586
|
+
expandAll() {
|
|
1587
|
+
this._tableTree.expandAll();
|
|
1588
|
+
}
|
|
1589
|
+
collapse(ids) {
|
|
1590
|
+
this._tableTree.collapse(ids);
|
|
1591
|
+
}
|
|
1592
|
+
collapseAll() {
|
|
1593
|
+
this._tableTree.collapseAll();
|
|
1594
|
+
}
|
|
1595
|
+
toggleRow(id) {
|
|
1596
|
+
this._tableTree.toggleRow(id);
|
|
1597
|
+
}
|
|
1140
1598
|
}
|
|
1141
1599
|
const ɵSCI_TABLE = new InjectionToken('ɵSciTable');
|
|
1142
1600
|
|
|
@@ -1149,16 +1607,16 @@ const ɵSCI_TABLE = new InjectionToken('ɵSciTable');
|
|
|
1149
1607
|
*
|
|
1150
1608
|
* SPDX-License-Identifier: EPL-2.0
|
|
1151
1609
|
*/
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1610
|
+
function table(dataOrDescriptor, columnFactoryFn, options) {
|
|
1611
|
+
assertNotInReactiveContext(table, 'Call table in a non-reactive (non-tracking) context, such as within the untracked() function.');
|
|
1612
|
+
const descriptor = isSignal(dataOrDescriptor) ? ({ datasource: dataOrDescriptor, columns: columnFactoryFn, injector: options?.injector }) : dataOrDescriptor;
|
|
1613
|
+
if (!descriptor.injector) {
|
|
1614
|
+
assertInInjectionContext(table);
|
|
1615
|
+
}
|
|
1616
|
+
const injector = descriptor.injector ?? inject(Injector);
|
|
1617
|
+
const sciTable = runInInjectionContext(injector, () => new ɵSciTable(descriptor));
|
|
1618
|
+
injector.get(DestroyRef).onDestroy(() => sciTable.dispose());
|
|
1619
|
+
return sciTable;
|
|
1162
1620
|
}
|
|
1163
1621
|
|
|
1164
1622
|
/*
|
|
@@ -1170,16 +1628,33 @@ function ɵillegaldatasource() {
|
|
|
1170
1628
|
*
|
|
1171
1629
|
* SPDX-License-Identifier: EPL-2.0
|
|
1172
1630
|
*/
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1631
|
+
/**
|
|
1632
|
+
* Creates a range of integers starting at `start` and ending at `end` (including `end`).
|
|
1633
|
+
*/
|
|
1634
|
+
function rangeInclusive(start, end) {
|
|
1635
|
+
const range = [];
|
|
1636
|
+
for (let i = start; i <= end; i++) {
|
|
1637
|
+
range.push(i);
|
|
1178
1638
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1639
|
+
return range;
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Indicates whether the given event was triggered from an input element.
|
|
1643
|
+
*/
|
|
1644
|
+
function isFromInput(event) {
|
|
1645
|
+
if (event.target instanceof Element && event.target.closest('input, textarea, select') !== null) {
|
|
1646
|
+
return true;
|
|
1647
|
+
}
|
|
1648
|
+
if (event.target instanceof HTMLElement && event.target.isContentEditable) {
|
|
1649
|
+
return true;
|
|
1650
|
+
}
|
|
1651
|
+
return false;
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Indicates whether the given keyboard event was triggered from a button.
|
|
1655
|
+
*/
|
|
1656
|
+
function isFromButton(event) {
|
|
1657
|
+
return event.target instanceof Element && event.target.closest('button') !== null;
|
|
1183
1658
|
}
|
|
1184
1659
|
|
|
1185
1660
|
/*
|
|
@@ -1195,218 +1670,114 @@ class SciTableSelectionService {
|
|
|
1195
1670
|
_table = inject(ɵSCI_TABLE);
|
|
1196
1671
|
async onRowClick(index, event) {
|
|
1197
1672
|
const table = this._table();
|
|
1198
|
-
const
|
|
1199
|
-
const
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
table.activeItem.set(item);
|
|
1203
|
-
if (item === undefined || !table.selectable()) {
|
|
1673
|
+
const rowId = table.visibleRows()?.[index]?.id;
|
|
1674
|
+
const previousActiveIndex = table.activeIndex();
|
|
1675
|
+
table.setActiveItem(rowId);
|
|
1676
|
+
if (rowId === undefined || !table.selectable()) {
|
|
1204
1677
|
return;
|
|
1205
1678
|
}
|
|
1206
1679
|
if (table.selectable() === 'single') {
|
|
1207
|
-
table.updateSelectedItems(() => new
|
|
1680
|
+
table.updateSelectedItems(() => new Set().add(rowId));
|
|
1208
1681
|
return;
|
|
1209
1682
|
}
|
|
1210
|
-
if (event.shiftKey &&
|
|
1211
|
-
const start = Math.min(
|
|
1212
|
-
const end = Math.max(
|
|
1683
|
+
if (event.shiftKey && previousActiveIndex >= 0) {
|
|
1684
|
+
const start = Math.min(previousActiveIndex, index);
|
|
1685
|
+
const end = Math.max(previousActiveIndex, index);
|
|
1213
1686
|
await this.selectItems(start, end, { add: true });
|
|
1214
1687
|
}
|
|
1215
1688
|
else if (event.ctrlKey || event.metaKey) {
|
|
1216
|
-
this.toggleSelectedItem(
|
|
1689
|
+
this.toggleSelectedItem(rowId);
|
|
1217
1690
|
}
|
|
1218
1691
|
else {
|
|
1219
1692
|
// If no modifier is pressed set the selection to the clicked row.
|
|
1220
|
-
table.updateSelectedItems(() => new
|
|
1693
|
+
table.updateSelectedItems(() => new Set().add(rowId));
|
|
1221
1694
|
}
|
|
1222
1695
|
}
|
|
1223
1696
|
async onArrowUp(event) {
|
|
1224
1697
|
event.preventDefault();
|
|
1225
1698
|
const table = this._table();
|
|
1226
|
-
const activeIndex =
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
}
|
|
1230
|
-
// Set active item.
|
|
1231
|
-
const startIndex = activeIndex - 1;
|
|
1232
|
-
await this.loadMissingItems(startIndex, startIndex);
|
|
1233
|
-
const startItem = table.rowsByIndex().get(startIndex)?.item;
|
|
1234
|
-
table.activeItem.set(startItem);
|
|
1235
|
-
if (!table.selectable()) {
|
|
1236
|
-
return;
|
|
1237
|
-
}
|
|
1238
|
-
const keyboardEvent = event;
|
|
1239
|
-
const shift = keyboardEvent.shiftKey;
|
|
1240
|
-
const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
|
|
1241
|
-
if (shift && ctrlOrMeta) {
|
|
1242
|
-
await this.ctrlShiftUp({ startIndex, activeIndex });
|
|
1243
|
-
}
|
|
1244
|
-
else if (shift) {
|
|
1245
|
-
await this.shiftUp({ startIndex, activeIndex });
|
|
1246
|
-
}
|
|
1247
|
-
else if (ctrlOrMeta) {
|
|
1248
|
-
// Selection remains unchanged.
|
|
1249
|
-
}
|
|
1250
|
-
else {
|
|
1251
|
-
await this.selectItems(startIndex, startIndex);
|
|
1252
|
-
}
|
|
1699
|
+
const activeIndex = table.activeIndex();
|
|
1700
|
+
const targetIndex = Math.max(activeIndex - 1, 0);
|
|
1701
|
+
await this.selectOnKeyboardNavigation(targetIndex, event);
|
|
1253
1702
|
}
|
|
1254
1703
|
async onArrowDown(event) {
|
|
1255
1704
|
event.preventDefault();
|
|
1256
1705
|
const table = this._table();
|
|
1257
|
-
const activeIndex =
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1266
|
-
if (!table.selectable()) {
|
|
1706
|
+
const activeIndex = table.activeIndex();
|
|
1707
|
+
const targetIndex = Math.min(activeIndex + 1, table.visibleRowCount() - 1);
|
|
1708
|
+
await this.selectOnKeyboardNavigation(targetIndex, event);
|
|
1709
|
+
}
|
|
1710
|
+
async onArrowLeft(event) {
|
|
1711
|
+
event.preventDefault();
|
|
1712
|
+
const table = this._table();
|
|
1713
|
+
const activeRow = table.activeRow();
|
|
1714
|
+
if (activeRow?.item === undefined || activeRow.id === skeletonRowId) {
|
|
1267
1715
|
return;
|
|
1268
1716
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
|
|
1272
|
-
if (shift && ctrlOrMeta) {
|
|
1273
|
-
await this.ctrlShiftDown({ endIndex, activeIndex });
|
|
1274
|
-
}
|
|
1275
|
-
else if (shift) {
|
|
1276
|
-
await this.shiftDown({ endIndex, activeIndex });
|
|
1717
|
+
if (activeRow.hasChildren() && activeRow.expanded()) {
|
|
1718
|
+
table.collapse(activeRow.id);
|
|
1277
1719
|
}
|
|
1278
|
-
else if (
|
|
1279
|
-
|
|
1720
|
+
else if (activeRow.parentId !== undefined) {
|
|
1721
|
+
const parentIndex = table.rowIndexById().get(activeRow.parentId) ?? -1;
|
|
1722
|
+
await this.selectOnKeyboardNavigation(parentIndex, { shiftKey: false, ctrlKey: false, metaKey: false });
|
|
1280
1723
|
}
|
|
1724
|
+
// Root case
|
|
1281
1725
|
else {
|
|
1282
|
-
|
|
1726
|
+
const rows = table.visibleRows();
|
|
1727
|
+
const row = findPreviousExpandedRow(activeRow.index - 1);
|
|
1728
|
+
if (row !== undefined) {
|
|
1729
|
+
const index = table.rowIndexById().get(row.id) ?? -1;
|
|
1730
|
+
await this.selectOnKeyboardNavigation(index, { shiftKey: false, ctrlKey: false, metaKey: false });
|
|
1731
|
+
}
|
|
1732
|
+
function findPreviousExpandedRow(index) {
|
|
1733
|
+
while (index > 0) {
|
|
1734
|
+
const row = rows?.[index];
|
|
1735
|
+
// Get the row expansion state whether the row is already loaded or not.
|
|
1736
|
+
if (row !== undefined && table.isExpanded(row.id)) {
|
|
1737
|
+
return row;
|
|
1738
|
+
}
|
|
1739
|
+
index--;
|
|
1740
|
+
}
|
|
1741
|
+
// Return the first row if no previous row is expanded.
|
|
1742
|
+
return rows?.[index];
|
|
1743
|
+
}
|
|
1283
1744
|
}
|
|
1284
1745
|
}
|
|
1285
|
-
async
|
|
1746
|
+
async onArrowRight(event) {
|
|
1286
1747
|
event.preventDefault();
|
|
1287
1748
|
const table = this._table();
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
const startIndex = Math.max(activeIndex - table.viewportPageSize() + 1, 0);
|
|
1294
|
-
await this.loadMissingItems(startIndex, startIndex);
|
|
1295
|
-
const startItem = table.rowsByIndex().get(startIndex)?.item;
|
|
1296
|
-
table.activeItem.set(startItem);
|
|
1297
|
-
if (!table.selectable()) {
|
|
1749
|
+
const activeRow = table.activeRow();
|
|
1750
|
+
const activeIndex = table.activeIndex();
|
|
1751
|
+
const endIndex = Math.min(activeIndex + 1, table.visibleRowCount() - 1);
|
|
1752
|
+
if (activeRow?.id !== skeletonRowId && activeRow?.hasChildren() && !activeRow.expanded()) {
|
|
1753
|
+
table.expand(activeRow.id);
|
|
1298
1754
|
return;
|
|
1299
1755
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
}
|
|
1309
|
-
else if (ctrlOrMeta) {
|
|
1310
|
-
// Selection remains unchanged.
|
|
1311
|
-
}
|
|
1312
|
-
else {
|
|
1313
|
-
await this.selectItems(startIndex, startIndex);
|
|
1314
|
-
}
|
|
1756
|
+
await this.selectOnKeyboardNavigation(endIndex, { shiftKey: false, ctrlKey: false, metaKey: false });
|
|
1757
|
+
}
|
|
1758
|
+
async onPageUp(event) {
|
|
1759
|
+
event.preventDefault();
|
|
1760
|
+
const table = this._table();
|
|
1761
|
+
const activeIndex = table.activeIndex();
|
|
1762
|
+
const targetIndex = Math.max(activeIndex - table.viewportPageSize() + 1, 0);
|
|
1763
|
+
await this.selectOnKeyboardNavigation(targetIndex, event);
|
|
1315
1764
|
}
|
|
1316
1765
|
async onPageDown(event) {
|
|
1317
1766
|
event.preventDefault();
|
|
1318
1767
|
const table = this._table();
|
|
1319
|
-
const activeIndex =
|
|
1320
|
-
const
|
|
1321
|
-
|
|
1322
|
-
return;
|
|
1323
|
-
}
|
|
1324
|
-
// Set active item.
|
|
1325
|
-
await this.loadMissingItems(endIndex, endIndex);
|
|
1326
|
-
const endItem = table.rowsByIndex().get(endIndex)?.item;
|
|
1327
|
-
table.activeItem.set(endItem);
|
|
1328
|
-
if (!table.selectable()) {
|
|
1329
|
-
return;
|
|
1330
|
-
}
|
|
1331
|
-
const keyboardEvent = event;
|
|
1332
|
-
const shift = keyboardEvent.shiftKey;
|
|
1333
|
-
const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
|
|
1334
|
-
if (shift && ctrlOrMeta) {
|
|
1335
|
-
await this.ctrlShiftDown({ endIndex, activeIndex });
|
|
1336
|
-
}
|
|
1337
|
-
else if (shift) {
|
|
1338
|
-
await this.shiftDown({ endIndex, activeIndex });
|
|
1339
|
-
}
|
|
1340
|
-
else if (ctrlOrMeta) {
|
|
1341
|
-
// Selection remains unchanged.
|
|
1342
|
-
}
|
|
1343
|
-
else {
|
|
1344
|
-
await this.selectItems(endIndex, endIndex);
|
|
1345
|
-
}
|
|
1768
|
+
const activeIndex = table.activeIndex();
|
|
1769
|
+
const targetIndex = Math.min(activeIndex + table.viewportPageSize() - 1, table.visibleRowCount() - 1);
|
|
1770
|
+
await this.selectOnKeyboardNavigation(targetIndex, event);
|
|
1346
1771
|
}
|
|
1347
1772
|
async onHome(event) {
|
|
1348
1773
|
event.preventDefault();
|
|
1349
|
-
|
|
1350
|
-
const activeIndex = this._table().activeRow()?.index ?? -1;
|
|
1351
|
-
if (activeIndex <= 0) {
|
|
1352
|
-
return;
|
|
1353
|
-
}
|
|
1354
|
-
// Set active item.
|
|
1355
|
-
const startIndex = 0;
|
|
1356
|
-
await this.loadMissingItems(startIndex, startIndex);
|
|
1357
|
-
const startItem = table.rowsByIndex().get(startIndex)?.item;
|
|
1358
|
-
table.activeItem.set(startItem);
|
|
1359
|
-
if (!table.selectable()) {
|
|
1360
|
-
return;
|
|
1361
|
-
}
|
|
1362
|
-
const keyboardEvent = event;
|
|
1363
|
-
const shift = keyboardEvent.shiftKey;
|
|
1364
|
-
const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
|
|
1365
|
-
if (shift && ctrlOrMeta) {
|
|
1366
|
-
await this.ctrlShiftUp({ startIndex, activeIndex });
|
|
1367
|
-
}
|
|
1368
|
-
else if (shift) {
|
|
1369
|
-
await this.shiftUp({ startIndex, activeIndex });
|
|
1370
|
-
}
|
|
1371
|
-
else if (ctrlOrMeta) {
|
|
1372
|
-
// Selection remains unchanged.
|
|
1373
|
-
}
|
|
1374
|
-
else {
|
|
1375
|
-
await this.selectItems(startIndex, startIndex);
|
|
1376
|
-
}
|
|
1774
|
+
await this.selectOnKeyboardNavigation(0, event);
|
|
1377
1775
|
}
|
|
1378
1776
|
async onEnd(event) {
|
|
1379
1777
|
event.preventDefault();
|
|
1380
1778
|
const table = this._table();
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
if (endIndex === activeIndex) {
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1386
|
-
// TODO [mmu] should active item be set before selection? this way, further methods cant rely on table.activeItem.
|
|
1387
|
-
// But otherwise, loading and setting the active item needs to be done for each case, or once at the end.
|
|
1388
|
-
// Set active item.
|
|
1389
|
-
await this.loadMissingItems(endIndex, endIndex);
|
|
1390
|
-
const endItem = table.rowsByIndex().get(endIndex)?.item;
|
|
1391
|
-
table.activeItem.set(endItem);
|
|
1392
|
-
if (!table.selectable()) {
|
|
1393
|
-
return;
|
|
1394
|
-
}
|
|
1395
|
-
const keyboardEvent = event;
|
|
1396
|
-
const shift = keyboardEvent.shiftKey;
|
|
1397
|
-
const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
|
|
1398
|
-
if (shift && ctrlOrMeta) {
|
|
1399
|
-
await this.ctrlShiftDown({ endIndex, activeIndex });
|
|
1400
|
-
}
|
|
1401
|
-
else if (shift) {
|
|
1402
|
-
await this.shiftDown({ endIndex, activeIndex });
|
|
1403
|
-
}
|
|
1404
|
-
else if (ctrlOrMeta) {
|
|
1405
|
-
// Selection remains unchanged.
|
|
1406
|
-
}
|
|
1407
|
-
else {
|
|
1408
|
-
await this.selectItems(endIndex, endIndex);
|
|
1409
|
-
}
|
|
1779
|
+
const targetIndex = table.visibleRowCount() - 1;
|
|
1780
|
+
await this.selectOnKeyboardNavigation(targetIndex, event);
|
|
1410
1781
|
}
|
|
1411
1782
|
onSpace(event) {
|
|
1412
1783
|
event.preventDefault();
|
|
@@ -1414,168 +1785,126 @@ class SciTableSelectionService {
|
|
|
1414
1785
|
if (!table.selectable()) {
|
|
1415
1786
|
return;
|
|
1416
1787
|
}
|
|
1417
|
-
const
|
|
1418
|
-
if (
|
|
1788
|
+
const activeItemId = table.activeItem();
|
|
1789
|
+
if (activeItemId === undefined) {
|
|
1419
1790
|
return;
|
|
1420
1791
|
}
|
|
1421
1792
|
const keyboardEvent = event;
|
|
1422
1793
|
const ctrlOrMeta = keyboardEvent.ctrlKey || keyboardEvent.metaKey;
|
|
1423
|
-
const activeItemId = table.trackBy(activeItem);
|
|
1424
1794
|
if (ctrlOrMeta) {
|
|
1425
|
-
this.toggleSelectedItem(
|
|
1795
|
+
this.toggleSelectedItem(activeItemId);
|
|
1426
1796
|
}
|
|
1427
1797
|
else {
|
|
1428
|
-
table.updateSelectedItems(() => new
|
|
1798
|
+
table.updateSelectedItems(() => new Set().add(activeItemId));
|
|
1429
1799
|
}
|
|
1430
1800
|
}
|
|
1431
1801
|
async onControlA(event) {
|
|
1432
1802
|
event.preventDefault();
|
|
1433
1803
|
const table = this._table();
|
|
1434
|
-
const totalCount = table.
|
|
1804
|
+
const totalCount = table.visibleRowCount();
|
|
1435
1805
|
if (table.selectable() === 'multi' && totalCount >= 0) {
|
|
1436
1806
|
await this.selectItems(0, totalCount - 1);
|
|
1437
1807
|
}
|
|
1438
1808
|
}
|
|
1439
|
-
|
|
1440
|
-
// shiftArrowDown
|
|
1441
|
-
// shiftPageDown
|
|
1442
|
-
// ShiftEnd
|
|
1443
|
-
async shiftDown(options) {
|
|
1809
|
+
async selectOnKeyboardNavigation(targetIndex, event) {
|
|
1444
1810
|
const table = this._table();
|
|
1445
|
-
const
|
|
1446
|
-
const activeIndex =
|
|
1447
|
-
const
|
|
1448
|
-
const endIndex =
|
|
1449
|
-
const
|
|
1450
|
-
const
|
|
1451
|
-
if (table.selectable()
|
|
1452
|
-
|
|
1811
|
+
const targetItem = table.visibleRows()?.[targetIndex]?.id;
|
|
1812
|
+
const activeIndex = table.activeIndex();
|
|
1813
|
+
const direction = activeIndex > targetIndex ? 'up' : 'down';
|
|
1814
|
+
const [startIndex, endIndex] = direction === 'up' ? [targetIndex, activeIndex] : [activeIndex, targetIndex];
|
|
1815
|
+
const shift = event.shiftKey;
|
|
1816
|
+
const ctrlOrMeta = event.ctrlKey || event.metaKey;
|
|
1817
|
+
if (table.selectable() !== 'multi') {
|
|
1818
|
+
// Don't modify selection when only ctrlOrMeta is pressed.
|
|
1819
|
+
if (table.selectable() === 'single' && (shift || !ctrlOrMeta)) {
|
|
1820
|
+
await this.selectItems(targetIndex, targetIndex);
|
|
1821
|
+
}
|
|
1822
|
+
table.setActiveItem(targetItem);
|
|
1453
1823
|
return;
|
|
1454
1824
|
}
|
|
1455
|
-
if (
|
|
1456
|
-
await this.selectItems(startIndex, endIndex);
|
|
1457
|
-
return;
|
|
1825
|
+
if (shift && ctrlOrMeta) {
|
|
1826
|
+
await this.selectItems(startIndex, endIndex, { add: true });
|
|
1458
1827
|
}
|
|
1459
|
-
if (
|
|
1460
|
-
await this.
|
|
1461
|
-
return;
|
|
1828
|
+
else if (shift) {
|
|
1829
|
+
await this.selectOnShiftNavigation(direction, startIndex, endIndex);
|
|
1462
1830
|
}
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
async ctrlShiftDown(options) {
|
|
1466
|
-
const table = this._table();
|
|
1467
|
-
const startIndex = options.activeIndex;
|
|
1468
|
-
const endIndex = options.endIndex;
|
|
1469
|
-
if (table.selectable() === 'single') {
|
|
1470
|
-
await this.selectItems(endIndex, endIndex);
|
|
1831
|
+
else if (ctrlOrMeta) {
|
|
1832
|
+
// Selection remains unchanged.
|
|
1471
1833
|
}
|
|
1472
1834
|
else {
|
|
1473
|
-
await this.selectItems(
|
|
1835
|
+
await this.selectItems(targetIndex, targetIndex);
|
|
1474
1836
|
}
|
|
1837
|
+
table.setActiveItem(targetItem);
|
|
1475
1838
|
}
|
|
1476
|
-
|
|
1839
|
+
selectOnShiftNavigation(direction, startIndex, endIndex) {
|
|
1477
1840
|
const table = this._table();
|
|
1478
|
-
const
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
const activeItemId = table.trackBy(activeItem);
|
|
1484
|
-
if (table.selectable() === 'single') {
|
|
1485
|
-
await this.selectItems(startIndex, startIndex);
|
|
1486
|
-
return;
|
|
1487
|
-
}
|
|
1488
|
-
if (!selectedIds.has(activeItemId)) {
|
|
1489
|
-
await this.selectItems(startIndex, endIndex);
|
|
1490
|
-
return;
|
|
1491
|
-
}
|
|
1492
|
-
if (this.isSelectionAtBlockEnd(activeIndex)) {
|
|
1493
|
-
await this.selectFromBlockEnd(startIndex, endIndex);
|
|
1494
|
-
return;
|
|
1841
|
+
const activeIndex = table.activeIndex();
|
|
1842
|
+
const selectedIds = table.selectedItems();
|
|
1843
|
+
// If the active item is outside the selection, start a new selection.
|
|
1844
|
+
if (!selectedIds.includes(table.activeItem())) {
|
|
1845
|
+
return this.selectItems(startIndex, endIndex);
|
|
1495
1846
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
const table = this._table();
|
|
1500
|
-
const startIndex = options.startIndex;
|
|
1501
|
-
const endIndex = options.activeIndex;
|
|
1502
|
-
if (table.selectable() === 'single') {
|
|
1503
|
-
await this.selectItems(startIndex, startIndex);
|
|
1847
|
+
// Moving down from the block start shrinks the selection until the block end, then extends it.
|
|
1848
|
+
if (direction === 'down' && this.isSelectionAtBlockStart(activeIndex)) {
|
|
1849
|
+
return this.selectFromBlockStart(startIndex, endIndex);
|
|
1504
1850
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1851
|
+
// Moving up from the block end shrinks the selection until the block start, then extends it.
|
|
1852
|
+
if (direction === 'up' && this.isSelectionAtBlockEnd(activeIndex)) {
|
|
1853
|
+
return this.selectFromBlockEnd(startIndex, endIndex);
|
|
1507
1854
|
}
|
|
1855
|
+
// In other cases, e.g. going down at the end of a selection block, select all items in the range.
|
|
1856
|
+
return this.selectItems(startIndex, endIndex, { add: true });
|
|
1508
1857
|
}
|
|
1509
1858
|
isSelectionAtBlockStart(activeIndex) {
|
|
1510
1859
|
const table = this._table();
|
|
1511
|
-
const selectedIds = table.
|
|
1512
|
-
const
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
const nextItemId = nextItem ? table.trackBy(nextItem) : undefined;
|
|
1516
|
-
return !selectedIds.has(previousItemId) && selectedIds.has(nextItemId);
|
|
1860
|
+
const selectedIds = table.selectedItems();
|
|
1861
|
+
const previousItemId = table.visibleRows()?.[activeIndex - 1]?.id;
|
|
1862
|
+
const nextItemId = table.visibleRows()?.[activeIndex + 1]?.id;
|
|
1863
|
+
return (previousItemId === undefined || !selectedIds.includes(previousItemId)) && nextItemId !== undefined && selectedIds.includes(nextItemId);
|
|
1517
1864
|
}
|
|
1518
1865
|
isSelectionAtBlockEnd(activeIndex) {
|
|
1519
1866
|
const table = this._table();
|
|
1520
|
-
const selectedIds = table.
|
|
1521
|
-
const
|
|
1522
|
-
const
|
|
1523
|
-
|
|
1524
|
-
const nextItemId = nextItem ? table.trackBy(nextItem) : undefined;
|
|
1525
|
-
return selectedIds.has(previousItemId) && !selectedIds.has(nextItemId);
|
|
1526
|
-
}
|
|
1527
|
-
async loadMissingItems(startIndex, endIndex) {
|
|
1528
|
-
const table = this._table();
|
|
1529
|
-
const indices = rangeInclusive(startIndex, endIndex + 1);
|
|
1530
|
-
const rows = indices.map(i => table.rowsByIndex().get(i));
|
|
1531
|
-
if (rows.some(row => row?.id === undefined)) {
|
|
1532
|
-
await Promise.race([
|
|
1533
|
-
table.loadRange(startIndex, endIndex + 1),
|
|
1534
|
-
firstValueFrom(timer(5_000)),
|
|
1535
|
-
]);
|
|
1536
|
-
}
|
|
1867
|
+
const selectedIds = table.selectedItems();
|
|
1868
|
+
const previousItemId = table.visibleRows()?.[activeIndex - 1]?.id;
|
|
1869
|
+
const nextItemId = table.visibleRows()?.[activeIndex + 1]?.id;
|
|
1870
|
+
return previousItemId !== undefined && selectedIds.includes(previousItemId) && (nextItemId === undefined || !selectedIds.includes(nextItemId));
|
|
1537
1871
|
}
|
|
1538
1872
|
async selectItems(startIndex, endIndex, options) {
|
|
1539
1873
|
const table = this._table();
|
|
1874
|
+
if (!table.selectable()) {
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1540
1877
|
const indices = rangeInclusive(startIndex, endIndex);
|
|
1541
1878
|
const add = options?.add ?? false;
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
// If not all id's could be found load the missing items.
|
|
1545
|
-
await Promise.race([
|
|
1546
|
-
table.loadRange(startIndex, endIndex),
|
|
1547
|
-
firstValueFrom(timer(5_000)),
|
|
1548
|
-
]);
|
|
1549
|
-
rows = indices.map(i => table.rowsByIndex().get(i));
|
|
1550
|
-
}
|
|
1551
|
-
table.updateSelectedItems(existing => new Map([
|
|
1879
|
+
const rows = indices.map(i => table.visibleRows()?.[i]);
|
|
1880
|
+
table.updateSelectedItems(existing => new Set([
|
|
1552
1881
|
...(add ? existing : []),
|
|
1553
1882
|
...rows
|
|
1554
|
-
.filter((row) => row
|
|
1555
|
-
.map(
|
|
1883
|
+
.filter((row) => !!row)
|
|
1884
|
+
.map(row => row.id),
|
|
1556
1885
|
]));
|
|
1557
1886
|
}
|
|
1558
1887
|
async selectFromBlockStart(startIndex, endIndex) {
|
|
1559
1888
|
const table = this._table();
|
|
1560
|
-
await this.loadMissingItems(startIndex, endIndex);
|
|
1561
1889
|
const indices = rangeInclusive(startIndex, endIndex);
|
|
1562
1890
|
table.updateSelectedItems(selection => {
|
|
1563
|
-
const newSelection = new
|
|
1891
|
+
const newSelection = new Set(selection);
|
|
1892
|
+
let shrinking = true;
|
|
1564
1893
|
for (let i = 0; i < indices.length; i++) {
|
|
1565
1894
|
const currentIndex = indices[i];
|
|
1566
1895
|
const nextIndex = indices[i + 1];
|
|
1567
|
-
const
|
|
1568
|
-
if (
|
|
1896
|
+
const itemId = table.visibleRows()?.[currentIndex]?.id;
|
|
1897
|
+
if (itemId === undefined) {
|
|
1569
1898
|
continue;
|
|
1570
1899
|
}
|
|
1571
|
-
const
|
|
1572
|
-
|
|
1573
|
-
const nextItemId = nextItem ? table.trackBy(nextItem) : undefined;
|
|
1574
|
-
if (selection.has(nextItemId)) {
|
|
1900
|
+
const nextItemId = table.visibleRows()?.[nextIndex]?.id;
|
|
1901
|
+
if (shrinking && nextItemId !== undefined && selection.has(nextItemId)) {
|
|
1575
1902
|
newSelection.delete(itemId);
|
|
1576
1903
|
}
|
|
1577
1904
|
else {
|
|
1578
|
-
|
|
1905
|
+
// Once outside the active block, only add items, even when crossing another selected block.
|
|
1906
|
+
shrinking = false;
|
|
1907
|
+
newSelection.add(itemId);
|
|
1579
1908
|
}
|
|
1580
1909
|
}
|
|
1581
1910
|
return newSelection;
|
|
@@ -1583,35 +1912,34 @@ class SciTableSelectionService {
|
|
|
1583
1912
|
}
|
|
1584
1913
|
async selectFromBlockEnd(startIndex, endIndex) {
|
|
1585
1914
|
const table = this._table();
|
|
1586
|
-
await this.loadMissingItems(startIndex, endIndex);
|
|
1587
1915
|
const indices = rangeInclusive(startIndex, endIndex);
|
|
1588
1916
|
table.updateSelectedItems(selection => {
|
|
1589
|
-
const newSelection = new
|
|
1917
|
+
const newSelection = new Set(selection);
|
|
1918
|
+
let shrinking = true;
|
|
1590
1919
|
for (let i = indices.length - 1; i >= 0; i--) {
|
|
1591
1920
|
const currentIndex = indices[i];
|
|
1592
1921
|
const previousIndex = indices[i - 1];
|
|
1593
|
-
const
|
|
1594
|
-
if (
|
|
1922
|
+
const itemId = table.visibleRows()?.[currentIndex]?.id;
|
|
1923
|
+
if (itemId === undefined) {
|
|
1595
1924
|
continue;
|
|
1596
1925
|
}
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
const previousItemId = previousItem ? table.trackBy(previousItem) : undefined;
|
|
1600
|
-
if (selection.has(previousItemId)) {
|
|
1926
|
+
const previousItemId = table.visibleRows()?.[previousIndex]?.id;
|
|
1927
|
+
if (shrinking && previousItemId !== undefined && selection.has(previousItemId)) {
|
|
1601
1928
|
newSelection.delete(itemId);
|
|
1602
1929
|
}
|
|
1603
1930
|
else {
|
|
1604
|
-
|
|
1931
|
+
// Once outside the active block, only add items, even when crossing another selected block.
|
|
1932
|
+
shrinking = false;
|
|
1933
|
+
newSelection.add(itemId);
|
|
1605
1934
|
}
|
|
1606
1935
|
}
|
|
1607
1936
|
return newSelection;
|
|
1608
1937
|
});
|
|
1609
1938
|
}
|
|
1610
|
-
toggleSelectedItem(
|
|
1939
|
+
toggleSelectedItem(id) {
|
|
1611
1940
|
const table = this._table();
|
|
1612
|
-
const id = table.trackBy(item);
|
|
1613
1941
|
table.updateSelectedItems(selection => {
|
|
1614
|
-
const next = new
|
|
1942
|
+
const next = new Set(selection);
|
|
1615
1943
|
if (next.has(id)) {
|
|
1616
1944
|
next.delete(id);
|
|
1617
1945
|
}
|
|
@@ -1619,7 +1947,7 @@ class SciTableSelectionService {
|
|
|
1619
1947
|
if (table.selectable() === 'single') {
|
|
1620
1948
|
next.clear();
|
|
1621
1949
|
}
|
|
1622
|
-
next.
|
|
1950
|
+
next.add(id);
|
|
1623
1951
|
}
|
|
1624
1952
|
return next;
|
|
1625
1953
|
});
|
|
@@ -1669,9 +1997,11 @@ class SciTableCellComponent {
|
|
|
1669
1997
|
...(ngDevMode ? [{ debugName: "cell" }] : /* istanbul ignore next */ []));
|
|
1670
1998
|
row = input.required(/* @ts-ignore */
|
|
1671
1999
|
...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
|
|
2000
|
+
_table = inject(ɵSCI_TABLE);
|
|
1672
2001
|
template = this.computeTemplate();
|
|
1673
2002
|
templateContext = this.computeTemplateContext();
|
|
1674
2003
|
partAttribute = this.computePartAttribute();
|
|
2004
|
+
value = this.computeValueAttribute();
|
|
1675
2005
|
computeTemplate() {
|
|
1676
2006
|
return computed(() => {
|
|
1677
2007
|
const cell = this.cell();
|
|
@@ -1699,28 +2029,51 @@ class SciTableCellComponent {
|
|
|
1699
2029
|
}
|
|
1700
2030
|
computePartAttribute() {
|
|
1701
2031
|
return computed(() => {
|
|
2032
|
+
const cell = this.cell();
|
|
2033
|
+
const partType = this._table().type === 'tree' ? 'node' : 'row';
|
|
2034
|
+
const referenceType = cell.type === 'enum' ? cell.column.referenceType : undefined;
|
|
1702
2035
|
return Arrays.distinct([
|
|
1703
|
-
// Enable column styling based on column type.
|
|
1704
|
-
`column:${
|
|
2036
|
+
// Enable column styling based on column type. For enum columns, resolves to the reference type, if any, i.e., boolean or icon.
|
|
2037
|
+
`column:${referenceType ?? cell.column.type}`,
|
|
1705
2038
|
// Enable column styling based on column name.
|
|
1706
|
-
|
|
2039
|
+
cell.column.name,
|
|
1707
2040
|
// Enable row styling based on selection state.
|
|
1708
|
-
this.row().selected() ?
|
|
2041
|
+
this.row().selected() ? `${partType}:selected` : `${partType}:not:selected`,
|
|
1709
2042
|
// Enable row styling based on active state.
|
|
1710
|
-
this.row().active() ?
|
|
2043
|
+
this.row().active() ? `${partType}:active` : `${partType}:not:active`,
|
|
1711
2044
|
// Enable row styling based on row bindings.
|
|
1712
|
-
...Arrays.coerce(this.row().bindings
|
|
2045
|
+
...Arrays.coerce(this.row().bindings.parts()),
|
|
1713
2046
|
]).join(' ');
|
|
1714
2047
|
});
|
|
1715
2048
|
}
|
|
2049
|
+
computeValueAttribute() {
|
|
2050
|
+
return computed(() => {
|
|
2051
|
+
const cell = this.cell();
|
|
2052
|
+
const value = (() => {
|
|
2053
|
+
switch (cell.type) {
|
|
2054
|
+
case 'string':
|
|
2055
|
+
case 'number':
|
|
2056
|
+
return cell.value();
|
|
2057
|
+
case 'enum':
|
|
2058
|
+
return cell.value()?.value;
|
|
2059
|
+
case 'date':
|
|
2060
|
+
return cell.value()?.isoString;
|
|
2061
|
+
default:
|
|
2062
|
+
return undefined;
|
|
2063
|
+
}
|
|
2064
|
+
})();
|
|
2065
|
+
return value !== undefined ? `${value}` : undefined;
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
1716
2068
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1717
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciTableCellComponent, isStandalone: true, selector: "sci-table-cell", inputs: { cell: { classPropertyName: "cell", publicName: "cell", isSignal: true, isRequired: true, transformFunction: null }, row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-type": "cell().column.type", "attr.data-column": "cell().column.name", "attr.data-padding": "!cell().column.padding ? false : null", "attr.part": "partAttribute()" } }, ngImport: i0, template: "@let cell = this.cell();\n\n@switch (cell.type) {\n @case ('template') {\n <ng-template *ngTemplateOutlet=\"template(); context: templateContext()\"/>\n }\n @case ('component') {\n <ng-container *sciComponentOutlet=\"cell.component\"/>\n }\n @case ('
|
|
2069
|
+
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-value": "value()", "attr.data-padding": "!cell().column.padding ? false : null", "attr.part": "partAttribute()" } }, hostDirectives: [{ directive: i1.SciTooltipOnOverflowDirective }], 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 ('date') {\n {{cell.value()?.formatted}}\n }\n @case ('number') {\n {{cell.value() | sciNumber:cell.column.format()}}\n }\n @case ('enum') {\n @let value = cell.value();\n @if (value !== undefined) {\n @if (cell.column.format.icon && value.icon !== undefined) {\n <sci-icon [attr.title]=\"value.tooltip\">{{value.icon}}</sci-icon>\n }\n @if (cell.column.format.label) {\n <span>{{value.label}}</span>\n }\n }\n }\n @default {\n {{cell.value()}}\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;position:relative;border-inline:var(--\\275sci-table-cell-padding-inline) solid transparent;align-items:center;overflow:hidden}:host:is([data-type=string],[data-type=number],[data-type=date],[data-type=enum]){display:inline;white-space:nowrap;text-overflow:ellipsis;line-height:var(--\\275sci-table-row-height)}:host[data-type=number]{text-align:end}:host:is([data-type=number],[data-type=date]){font-variant-numeric:tabular-nums}:host[data-padding=false]{border-inline:none;align-items:stretch}:host>sci-icon:not(:last-child){margin-right:.75em}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "directive", type: SciComponentOutletDirective, selector: "ng-template[sciComponentOutlet]", inputs: ["sciComponentOutlet"] }, { kind: "pipe", type: SciNumberPipe, name: "sciNumber" }] });
|
|
1718
2070
|
}
|
|
1719
2071
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableCellComponent, decorators: [{
|
|
1720
2072
|
type: Component,
|
|
1721
2073
|
args: [{ selector: 'sci-table-cell', host: {
|
|
1722
2074
|
'[attr.data-type]': 'cell().column.type',
|
|
1723
2075
|
'[attr.data-column]': 'cell().column.name',
|
|
2076
|
+
'[attr.data-value]': 'value()',
|
|
1724
2077
|
'[attr.data-padding]': '!cell().column.padding ? false : null',
|
|
1725
2078
|
'[attr.part]': 'partAttribute()',
|
|
1726
2079
|
}, imports: [
|
|
@@ -1728,7 +2081,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
1728
2081
|
SciIconComponent,
|
|
1729
2082
|
SciComponentOutletDirective,
|
|
1730
2083
|
SciNumberPipe,
|
|
1731
|
-
],
|
|
2084
|
+
], hostDirectives: [
|
|
2085
|
+
SciTooltipOnOverflowDirective,
|
|
2086
|
+
], 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 ('date') {\n {{cell.value()?.formatted}}\n }\n @case ('number') {\n {{cell.value() | sciNumber:cell.column.format()}}\n }\n @case ('enum') {\n @let value = cell.value();\n @if (value !== undefined) {\n @if (cell.column.format.icon && value.icon !== undefined) {\n <sci-icon [attr.title]=\"value.tooltip\">{{value.icon}}</sci-icon>\n }\n @if (cell.column.format.label) {\n <span>{{value.label}}</span>\n }\n }\n }\n @default {\n {{cell.value()}}\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;position:relative;border-inline:var(--\\275sci-table-cell-padding-inline) solid transparent;align-items:center;overflow:hidden}:host:is([data-type=string],[data-type=number],[data-type=date],[data-type=enum]){display:inline;white-space:nowrap;text-overflow:ellipsis;line-height:var(--\\275sci-table-row-height)}:host[data-type=number]{text-align:end}:host:is([data-type=number],[data-type=date]){font-variant-numeric:tabular-nums}:host[data-padding=false]{border-inline:none;align-items:stretch}:host>sci-icon:not(:last-child){margin-right:.75em}\n"] }]
|
|
1732
2087
|
}], propDecorators: { cell: [{ type: i0.Input, args: [{ isSignal: true, alias: "cell", required: true }] }], row: [{ type: i0.Input, args: [{ isSignal: true, alias: "row", required: true }] }] } });
|
|
1733
2088
|
|
|
1734
2089
|
/*
|
|
@@ -1745,27 +2100,35 @@ class SciTableRowComponent {
|
|
|
1745
2100
|
...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
|
|
1746
2101
|
primaryAction = output();
|
|
1747
2102
|
table = inject(ɵSCI_TABLE);
|
|
2103
|
+
_selectionService = inject(SciTableSelectionService);
|
|
1748
2104
|
rowActionToolbar = viewChild(SciToolbarComponent, /* @ts-ignore */
|
|
1749
2105
|
...(ngDevMode ? [{ debugName: "rowActionToolbar" }] : /* istanbul ignore next */ []));
|
|
1750
2106
|
rowActionsToolbarName = `toolbar:${UUID.randomUUID()}`;
|
|
1751
|
-
_selectionService = inject(SciTableSelectionService);
|
|
1752
2107
|
constructor() {
|
|
1753
2108
|
this.contributeRowActions();
|
|
1754
2109
|
}
|
|
1755
2110
|
onRowClick(event) {
|
|
1756
|
-
|
|
2111
|
+
const row = this.row();
|
|
2112
|
+
if (row.id === skeletonRowId) {
|
|
1757
2113
|
return;
|
|
1758
2114
|
}
|
|
1759
|
-
|
|
2115
|
+
const index = this.table().rowIndexById().get(row.id) ?? -1;
|
|
2116
|
+
void this._selectionService.onRowClick(index, event);
|
|
1760
2117
|
}
|
|
1761
2118
|
onRowDblClick(event) {
|
|
1762
|
-
|
|
2119
|
+
const row = this.row();
|
|
2120
|
+
if (row.id === skeletonRowId || isFromInput(event) || isFromButton(event)) {
|
|
1763
2121
|
return;
|
|
1764
2122
|
}
|
|
1765
|
-
this.primaryAction.emit({ sourceEvent: event, items: [
|
|
2123
|
+
this.primaryAction.emit({ sourceEvent: event, items: [row.id] });
|
|
1766
2124
|
}
|
|
1767
2125
|
onRowMouseEnter() {
|
|
1768
|
-
this.
|
|
2126
|
+
const row = this.row();
|
|
2127
|
+
if (row.id === skeletonRowId) {
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
const index = this.table().rowIndexById().get(row.id) ?? -1;
|
|
2131
|
+
this.table().hoveredIndex.set(index);
|
|
1769
2132
|
}
|
|
1770
2133
|
onRowMouseLeave(event) {
|
|
1771
2134
|
const next = event.relatedTarget;
|
|
@@ -1776,35 +2139,49 @@ class SciTableRowComponent {
|
|
|
1776
2139
|
this.table().hoveredIndex.set(-1);
|
|
1777
2140
|
}
|
|
1778
2141
|
onActionToolbarClick(event) {
|
|
2142
|
+
const row = this.row();
|
|
2143
|
+
if (row.id === skeletonRowId) {
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
event.stopPropagation(); // prevent selecting the row
|
|
2147
|
+
this.table().setActiveItem(row.id);
|
|
2148
|
+
}
|
|
2149
|
+
onTreeNodeHandle(event) {
|
|
2150
|
+
const row = this.row();
|
|
2151
|
+
if (row.id === skeletonRowId) {
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
1779
2154
|
event.stopPropagation(); // prevent selecting the row
|
|
1780
|
-
this.table().
|
|
2155
|
+
this.table().toggleRow(row.id);
|
|
1781
2156
|
}
|
|
1782
2157
|
contributeRowActions() {
|
|
1783
2158
|
contributeMenu(this.rowActionsToolbarName, toolbar => {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
rowActionsFactoryFn(toolbar, this.row().item, this.row().index);
|
|
2159
|
+
if (this.row().id !== skeletonRowId) {
|
|
2160
|
+
this.table().rowActions?.(toolbar, this.row().item, this.row().index);
|
|
1787
2161
|
}
|
|
1788
2162
|
});
|
|
1789
2163
|
}
|
|
1790
2164
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableRowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1791
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciTableRowComponent, isStandalone: true, selector: "sci-table-row", inputs: { row: { classPropertyName: "row", publicName: "row", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { primaryAction: "primaryAction" }, host: { listeners: { "click": "onRowClick($event)", "dblclick": "onRowDblClick($event)", "mouseenter": "onRowMouseEnter()", "mouseleave": "onRowMouseLeave($event)" }, properties: { "attr.data-active": "row().active() ? '' : null", "attr.data-selected": "row().selected() ? '' : null", "attr.data-hovered": "row().hovered() ? '' : null", "
|
|
2165
|
+
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", "attr.data-level": "row().level", "attr.data-state": "row().level !== undefined && row().hasChildren() ? (row().expanded() ? 'expanded' : 'collapsed') : null", "style.--\u0275sci-table-row-level": "row().level" } }, viewQueries: [{ propertyName: "rowActionToolbar", first: true, predicate: SciToolbarComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@let row = this.row();\n\n<!-- Handle to expand or collapse a tree node -->\n@if (row.level !== undefined) {\n <button class=\"tree-node-handle e2e-tree-node-handle\" (click)=\"onTreeNodeHandle($event)\" [attr.disabled]=\"!row.hasChildren() || null\">\n @if (row.expanded()) {\n <sci-icon class=\"e2e-expanded\">scion:chevron_down</sci-icon>\n } @else {\n <sci-icon class=\"e2e-collapsed\">scion:chevron_right</sci-icon>\n }\n </button>\n}\n\n<!-- Row Icons -->\n@if (row.cells.length) {\n @for (icon of row.bindings.icons(); track $index) {\n <sci-icon>{{icon}}</sci-icon>\n }\n}\n\n<!-- Table Cells -->\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<!--Row Actions -->\n@let hasRowActions = !!table().rowActions;\n@let isRowHovered = table().hoveredIndex() === row.index;\n@let isRowActionMenuOpen = rowActionToolbar()?.menuOpen();\n\n@if (hasRowActions && (isRowHovered || isRowActionMenuOpen)) {\n <sci-toolbar [name]=\"rowActionsToolbarName\" (click)=\"onActionToolbarClick($event)\" class=\"row-actions\"/>\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;grid-template-columns:subgrid;grid-auto-rows:var(--sci-table-row-height);align-items:stretch;-webkit-user-select:none;user-select:none;border-bottom:var(--\\275sci-table-border-size) solid var(--sci-table-gridline-color)}:host[data-hovered]{background-color:var(--sci-table-row-background-color-hover)}:host[data-selected]{background-color:var(--sci-table-row-background-color-selected);--\\275sci-table-row-selected: true}:host[data-hovered]:not([data-selected]){border-radius:var(--sci-table-row-border-radius)}:host:is(:first-child,:not([data-selected])+sci-table-row)[data-selected]{border-top-left-radius:var(--sci-table-row-border-radius);border-top-right-radius:var(--sci-table-row-border-radius)}:host[data-selected]:not(:has(+sci-table-row[data-selected])){border-bottom-left-radius:var(--sci-table-row-border-radius);border-bottom-right-radius:var(--sci-table-row-border-radius)}@container (style(--\\275sci-table-scrolling: true)) or (style(--\\275sci-table-resizing: true)){:host{pointer-events:none}}:host:has(>div.skeleton){pointer-events:none;align-items:center}:host:has(>div.skeleton)>div.skeleton{display:inline-block;position:relative;overflow:hidden;border-radius:var(--sci-corner-small);background-color:var(--sci-color-skeleton-subtle);opacity:0;animation-name:visiblity;animation-duration:.5s;animation-timing-function:ease-in;animation-fill-mode:forwards}:host:has(>div.skeleton)>div.skeleton:after{position:absolute;inset:0;transform:translate(-100%);background-image:linear-gradient(to right,transparent 0,color-mix(in srgb,light-dark(var(--sci-static-color-white),var(--sci-static-color-black)) 30%,transparent) 50%,transparent);animation:shimmer 2s infinite;content:\"\"}@keyframes shimmer{to{transform:translate(100%)}}@keyframes visiblity{to{opacity:1}}:host:has(>div.skeleton)>div.skeleton{height:1em;margin-inline:var(--\\275sci-table-cell-padding-inline)}:host>button.tree-node-handle{all:unset;display:inline-grid;position:relative;order:1}:host>button.tree-node-handle:disabled{display:none}:host>:is(sci-icon,button.tree-node-handle):has(~sci-table-cell:first-of-type){grid-column:1;grid-row:1;align-self:center;margin-left:calc(var(--\\275sci-table-cell-padding-inline) + (sibling-index() - 1) * 24px + var(--\\275sci-table-row-level, 0) * var(--sci-table-row-indent));--sci-icon-size: 16px;width:auto;max-width:var(--sci-icon-size)}:host>:is(sci-icon,button.tree-node-handle):has(~sci-table-cell:first-of-type)+sci-table-cell{grid-column:1;grid-row:1;border-left-width:calc(var(--\\275sci-table-cell-padding-inline) + (sibling-index() - 1) * 24px + var(--\\275sci-table-row-level, 0) * var(--sci-table-row-indent))}:host>:is(sci-icon,button.tree-node-handle):has(~sci-table-cell:first-of-type)+sci-table-cell[data-padding=false]{border-left-width:calc(var(--\\275sci-table-cell-padding-inline) + (sibling-index() - 1) * 24px + var(--\\275sci-table-row-level, 0) * var(--sci-table-row-indent) - 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"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }] });
|
|
1792
2166
|
}
|
|
1793
2167
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableRowComponent, decorators: [{
|
|
1794
2168
|
type: Component,
|
|
1795
2169
|
args: [{ selector: 'sci-table-row', imports: [
|
|
1796
2170
|
SciTableCellComponent,
|
|
1797
2171
|
SciToolbarComponent,
|
|
2172
|
+
SciIconComponent,
|
|
1798
2173
|
], host: {
|
|
1799
2174
|
'[attr.data-active]': `row().active() ? '' : null`,
|
|
1800
2175
|
'[attr.data-selected]': `row().selected() ? '' : null`,
|
|
1801
2176
|
'[attr.data-hovered]': `row().hovered() ? '' : null`,
|
|
1802
|
-
'[
|
|
2177
|
+
'[attr.data-level]': 'row().level',
|
|
2178
|
+
'[attr.data-state]': `row().level !== undefined && row().hasChildren() ? (row().expanded() ? 'expanded' : 'collapsed') : null`,
|
|
2179
|
+
'[style.--ɵsci-table-row-level]': 'row().level', // only set if hierarchical
|
|
1803
2180
|
'(click)': 'onRowClick($event)',
|
|
1804
2181
|
'(dblclick)': 'onRowDblClick($event)',
|
|
1805
2182
|
'(mouseenter)': 'onRowMouseEnter()',
|
|
1806
2183
|
'(mouseleave)': 'onRowMouseLeave($event)',
|
|
1807
|
-
}, template: "@let row = this.row();\n@for (cell of row.cells; track cell.column.name) {\n <sci-table-cell [cell]=\"cell\" [row]=\"row\"/>\n} @empty {\n @for (column of table().columns(); track column.name) {\n <div class=\"skeleton e2e-skeleton\" [attr.data-column]=\"column.name\"></div>\n }\n}\n\n@let hasRowActions = !!table().rowActions;\n@let isRowHovered = table().
|
|
2184
|
+
}, template: "@let row = this.row();\n\n<!-- Handle to expand or collapse a tree node -->\n@if (row.level !== undefined) {\n <button class=\"tree-node-handle e2e-tree-node-handle\" (click)=\"onTreeNodeHandle($event)\" [attr.disabled]=\"!row.hasChildren() || null\">\n @if (row.expanded()) {\n <sci-icon class=\"e2e-expanded\">scion:chevron_down</sci-icon>\n } @else {\n <sci-icon class=\"e2e-collapsed\">scion:chevron_right</sci-icon>\n }\n </button>\n}\n\n<!-- Row Icons -->\n@if (row.cells.length) {\n @for (icon of row.bindings.icons(); track $index) {\n <sci-icon>{{icon}}</sci-icon>\n }\n}\n\n<!-- Table Cells -->\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<!--Row Actions -->\n@let hasRowActions = !!table().rowActions;\n@let isRowHovered = table().hoveredIndex() === row.index;\n@let isRowActionMenuOpen = rowActionToolbar()?.menuOpen();\n\n@if (hasRowActions && (isRowHovered || isRowActionMenuOpen)) {\n <sci-toolbar [name]=\"rowActionsToolbarName\" (click)=\"onActionToolbarClick($event)\" class=\"row-actions\"/>\n}\n", styles: ["@charset \"UTF-8\";:host{display:inline-grid;grid-template-columns:subgrid;grid-auto-rows:var(--sci-table-row-height);align-items:stretch;-webkit-user-select:none;user-select:none;border-bottom:var(--\\275sci-table-border-size) solid var(--sci-table-gridline-color)}:host[data-hovered]{background-color:var(--sci-table-row-background-color-hover)}:host[data-selected]{background-color:var(--sci-table-row-background-color-selected);--\\275sci-table-row-selected: true}:host[data-hovered]:not([data-selected]){border-radius:var(--sci-table-row-border-radius)}:host:is(:first-child,:not([data-selected])+sci-table-row)[data-selected]{border-top-left-radius:var(--sci-table-row-border-radius);border-top-right-radius:var(--sci-table-row-border-radius)}:host[data-selected]:not(:has(+sci-table-row[data-selected])){border-bottom-left-radius:var(--sci-table-row-border-radius);border-bottom-right-radius:var(--sci-table-row-border-radius)}@container (style(--\\275sci-table-scrolling: true)) or (style(--\\275sci-table-resizing: true)){:host{pointer-events:none}}:host:has(>div.skeleton){pointer-events:none;align-items:center}:host:has(>div.skeleton)>div.skeleton{display:inline-block;position:relative;overflow:hidden;border-radius:var(--sci-corner-small);background-color:var(--sci-color-skeleton-subtle);opacity:0;animation-name:visiblity;animation-duration:.5s;animation-timing-function:ease-in;animation-fill-mode:forwards}:host:has(>div.skeleton)>div.skeleton:after{position:absolute;inset:0;transform:translate(-100%);background-image:linear-gradient(to right,transparent 0,color-mix(in srgb,light-dark(var(--sci-static-color-white),var(--sci-static-color-black)) 30%,transparent) 50%,transparent);animation:shimmer 2s infinite;content:\"\"}@keyframes shimmer{to{transform:translate(100%)}}@keyframes visiblity{to{opacity:1}}:host:has(>div.skeleton)>div.skeleton{height:1em;margin-inline:var(--\\275sci-table-cell-padding-inline)}:host>button.tree-node-handle{all:unset;display:inline-grid;position:relative;order:1}:host>button.tree-node-handle:disabled{display:none}:host>:is(sci-icon,button.tree-node-handle):has(~sci-table-cell:first-of-type){grid-column:1;grid-row:1;align-self:center;margin-left:calc(var(--\\275sci-table-cell-padding-inline) + (sibling-index() - 1) * 24px + var(--\\275sci-table-row-level, 0) * var(--sci-table-row-indent));--sci-icon-size: 16px;width:auto;max-width:var(--sci-icon-size)}:host>:is(sci-icon,button.tree-node-handle):has(~sci-table-cell:first-of-type)+sci-table-cell{grid-column:1;grid-row:1;border-left-width:calc(var(--\\275sci-table-cell-padding-inline) + (sibling-index() - 1) * 24px + var(--\\275sci-table-row-level, 0) * var(--sci-table-row-indent))}:host>:is(sci-icon,button.tree-node-handle):has(~sci-table-cell:first-of-type)+sci-table-cell[data-padding=false]{border-left-width:calc(var(--\\275sci-table-cell-padding-inline) + (sibling-index() - 1) * 24px + var(--\\275sci-table-row-level, 0) * var(--sci-table-row-indent) - 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"] }]
|
|
1808
2185
|
}], 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 }] }] } });
|
|
1809
2186
|
|
|
1810
2187
|
/*
|
|
@@ -1817,27 +2194,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
1817
2194
|
* SPDX-License-Identifier: EPL-2.0
|
|
1818
2195
|
*/
|
|
1819
2196
|
class SciTableKeyboardNavigatorDirective {
|
|
2197
|
+
_table = inject(ɵSCI_TABLE);
|
|
1820
2198
|
selectionService = inject(SciTableSelectionService);
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
return true;
|
|
2199
|
+
isFromInput = isFromInput;
|
|
2200
|
+
isFromButton = isFromButton;
|
|
2201
|
+
async onKeydown(event) {
|
|
2202
|
+
if (event.key === '+' && event.location === 3) { // NumpadAdd
|
|
2203
|
+
void this.onNumpadAdd(event);
|
|
1827
2204
|
}
|
|
1828
|
-
if (event.
|
|
1829
|
-
|
|
2205
|
+
else if (event.key === '-' && event.location === 3) { // NumpadSubtract
|
|
2206
|
+
void this.onNumpadSubtract(event);
|
|
1830
2207
|
}
|
|
1831
|
-
return false;
|
|
1832
2208
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2209
|
+
async onNumpadAdd(event) {
|
|
2210
|
+
event.preventDefault();
|
|
2211
|
+
const table = this._table();
|
|
2212
|
+
if (event.ctrlKey || event.metaKey) {
|
|
2213
|
+
table.expandAll();
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
const activeRow = table.activeRow();
|
|
2217
|
+
if (activeRow?.id !== skeletonRowId && activeRow?.hasChildren()) {
|
|
2218
|
+
table.expand(activeRow.id);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
async onNumpadSubtract(event) {
|
|
2222
|
+
event.preventDefault();
|
|
2223
|
+
const table = this._table();
|
|
2224
|
+
if (event.ctrlKey || event.metaKey) {
|
|
2225
|
+
table.collapseAll();
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
const activeRow = table.activeRow();
|
|
2229
|
+
if (activeRow?.id !== skeletonRowId && activeRow?.hasChildren()) {
|
|
2230
|
+
table.collapse(activeRow.id);
|
|
2231
|
+
}
|
|
1838
2232
|
}
|
|
1839
2233
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableKeyboardNavigatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1840
|
-
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 });
|
|
2234
|
+
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.arrowLeft": "isFromInput($event) || selectionService.onArrowLeft($event)", "keydown.arrowRight": "isFromInput($event) || selectionService.onArrowRight($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.meta.pageUp": "isFromInput($event) || selectionService.onPageUp($event)", "keydown.shift.control.pageUp": "isFromInput($event) || selectionService.onPageUp($event)", "keydown.shift.meta.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.meta.pageDown": "isFromInput($event) || selectionService.onPageDown($event)", "keydown.shift.control.pageDown": "isFromInput($event) || selectionService.onPageDown($event)", "keydown.shift.meta.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.meta.home": "isFromInput($event) || selectionService.onHome($event)", "keydown.shift.control.home": "isFromInput($event) || selectionService.onHome($event)", "keydown.shift.meta.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.meta.end": "isFromInput($event) || selectionService.onEnd($event)", "keydown.shift.control.end": "isFromInput($event) || selectionService.onEnd($event)", "keydown.shift.meta.end": "isFromInput($event) || selectionService.onEnd($event)", "keydown": "isFromInput($event) || onKeydown($event)" }, properties: { "attr.tabindex": "0" } }, ngImport: i0 });
|
|
1841
2235
|
}
|
|
1842
2236
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableKeyboardNavigatorDirective, decorators: [{
|
|
1843
2237
|
type: Directive,
|
|
@@ -1857,6 +2251,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
1857
2251
|
'(keydown.meta.arrowDown)': 'isFromInput($event) || selectionService.onArrowDown($event)',
|
|
1858
2252
|
'(keydown.shift.control.arrowDown)': 'isFromInput($event) || selectionService.onArrowDown($event)',
|
|
1859
2253
|
'(keydown.shift.meta.arrowDown)': 'isFromInput($event) || selectionService.onArrowDown($event)',
|
|
2254
|
+
'(keydown.arrowLeft)': 'isFromInput($event) || selectionService.onArrowLeft($event)',
|
|
2255
|
+
'(keydown.arrowRight)': 'isFromInput($event) || selectionService.onArrowRight($event)',
|
|
1860
2256
|
'(keydown.space)': 'isFromInput($event) || isFromButton($event) || selectionService.onSpace($event)',
|
|
1861
2257
|
'(keydown.control.space)': 'isFromInput($event) || isFromButton($event) || selectionService.onSpace($event)',
|
|
1862
2258
|
'(keydown.meta.space)': 'isFromInput($event) || isFromButton($event) || selectionService.onSpace($event)',
|
|
@@ -1865,19 +2261,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
1865
2261
|
'(keydown.pageUp)': 'isFromInput($event) || selectionService.onPageUp($event)',
|
|
1866
2262
|
'(keydown.shift.pageUp)': 'isFromInput($event) || selectionService.onPageUp($event)',
|
|
1867
2263
|
'(keydown.control.pageUp)': 'isFromInput($event) || selectionService.onPageUp($event)',
|
|
2264
|
+
'(keydown.meta.pageUp)': 'isFromInput($event) || selectionService.onPageUp($event)',
|
|
1868
2265
|
'(keydown.shift.control.pageUp)': 'isFromInput($event) || selectionService.onPageUp($event)',
|
|
2266
|
+
'(keydown.shift.meta.pageUp)': 'isFromInput($event) || selectionService.onPageUp($event)',
|
|
1869
2267
|
'(keydown.pageDown)': 'isFromInput($event) || selectionService.onPageDown($event)',
|
|
1870
2268
|
'(keydown.shift.pageDown)': 'isFromInput($event) || selectionService.onPageDown($event)',
|
|
1871
2269
|
'(keydown.control.pageDown)': 'isFromInput($event) || selectionService.onPageDown($event)',
|
|
2270
|
+
'(keydown.meta.pageDown)': 'isFromInput($event) || selectionService.onPageDown($event)',
|
|
1872
2271
|
'(keydown.shift.control.pageDown)': 'isFromInput($event) || selectionService.onPageDown($event)',
|
|
2272
|
+
'(keydown.shift.meta.pageDown)': 'isFromInput($event) || selectionService.onPageDown($event)',
|
|
1873
2273
|
'(keydown.home)': 'isFromInput($event) || selectionService.onHome($event)',
|
|
1874
2274
|
'(keydown.shift.home)': 'isFromInput($event) || selectionService.onHome($event)',
|
|
1875
2275
|
'(keydown.control.home)': 'isFromInput($event) || selectionService.onHome($event)',
|
|
2276
|
+
'(keydown.meta.home)': 'isFromInput($event) || selectionService.onHome($event)',
|
|
1876
2277
|
'(keydown.shift.control.home)': 'isFromInput($event) || selectionService.onHome($event)',
|
|
2278
|
+
'(keydown.shift.meta.home)': 'isFromInput($event) || selectionService.onHome($event)',
|
|
1877
2279
|
'(keydown.end)': 'isFromInput($event) || selectionService.onEnd($event)',
|
|
1878
2280
|
'(keydown.shift.end)': 'isFromInput($event) || selectionService.onEnd($event)',
|
|
1879
2281
|
'(keydown.control.end)': 'isFromInput($event) || selectionService.onEnd($event)',
|
|
2282
|
+
'(keydown.meta.end)': 'isFromInput($event) || selectionService.onEnd($event)',
|
|
1880
2283
|
'(keydown.shift.control.end)': 'isFromInput($event) || selectionService.onEnd($event)',
|
|
2284
|
+
'(keydown.shift.meta.end)': 'isFromInput($event) || selectionService.onEnd($event)',
|
|
2285
|
+
'(keydown)': 'isFromInput($event) || onKeydown($event)',
|
|
1881
2286
|
},
|
|
1882
2287
|
}]
|
|
1883
2288
|
}] });
|
|
@@ -2092,14 +2497,14 @@ class SciTableColumnsComponent {
|
|
|
2092
2497
|
}
|
|
2093
2498
|
}
|
|
2094
2499
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableColumnsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2095
|
-
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
|
|
2500
|
+
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 splitters, also for non-resizable columns, as non-interactive column header dividers. -->\n@for (column of table().columns(); track column.name; let i = $index) {\n <sci-splitter orientation=\"vertical\"\n [class.resizing]=\"column.resizing()\"\n [attr.data-column]=\"column.name\"\n [attr.data-non-interactive]=\"!column.resizable() ? '' : null\"\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<!-- 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}:host>sci-splitter:where(:not([data-non-interactive])){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-start: .5em;--sci-splitter-cross-axis-size: auto;--sci-splitter-cross-axis-end: 0}@container not style(--\\275sci-table-header-column-divider-visible: true){:host>sci-splitter:is(:hover,.resizing){--sci-splitter-cross-axis-start: 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"] }] });
|
|
2096
2501
|
}
|
|
2097
2502
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableColumnsComponent, decorators: [{
|
|
2098
2503
|
type: Component,
|
|
2099
2504
|
args: [{ selector: 'sci-table-columns', imports: [
|
|
2100
2505
|
SciSplitterComponent,
|
|
2101
2506
|
SciTableColumnComponent,
|
|
2102
|
-
], template: "<!-- Render
|
|
2507
|
+
], template: "<!-- Render splitters, also for non-resizable columns, as non-interactive column header dividers. -->\n@for (column of table().columns(); track column.name; let i = $index) {\n <sci-splitter orientation=\"vertical\"\n [class.resizing]=\"column.resizing()\"\n [attr.data-column]=\"column.name\"\n [attr.data-non-interactive]=\"!column.resizable() ? '' : null\"\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<!-- 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}:host>sci-splitter:where(:not([data-non-interactive])){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-start: .5em;--sci-splitter-cross-axis-size: auto;--sci-splitter-cross-axis-end: 0}@container not style(--\\275sci-table-header-column-divider-visible: true){:host>sci-splitter:is(:hover,.resizing){--sci-splitter-cross-axis-start: 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"] }]
|
|
2103
2508
|
}], propDecorators: { rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }] } });
|
|
2104
2509
|
|
|
2105
2510
|
/*
|
|
@@ -2215,15 +2620,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
2215
2620
|
class SciColumnHeaderComponent {
|
|
2216
2621
|
column = input.required(/* @ts-ignore */
|
|
2217
2622
|
...(ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []));
|
|
2623
|
+
labelElement = viewChild('label', /* @ts-ignore */
|
|
2624
|
+
...(ngDevMode ? [{ debugName: "labelElement" }] : /* istanbul ignore next */ [])); // public visibility required to be accessible in `configureTooltip` function
|
|
2218
2625
|
table = inject(ɵSCI_TABLE);
|
|
2219
|
-
header = text(computed(() => this.column().header()));
|
|
2220
2626
|
sortDirection = computed(() => this.table().sortCriteria().find(criteria => criteria.columnName === this.column().name)?.direction, /* @ts-ignore */
|
|
2221
2627
|
...(ngDevMode ? [{ debugName: "sortDirection" }] : /* istanbul ignore next */ []));
|
|
2222
2628
|
onSort(event) {
|
|
2223
2629
|
this.table().sort(this.column().name, event.ctrlKey || event.metaKey);
|
|
2224
2630
|
}
|
|
2225
2631
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciColumnHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2226
|
-
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"
|
|
2632
|
+
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" } }, providers: [
|
|
2633
|
+
configureTooltip$1(),
|
|
2634
|
+
], viewQueries: [{ propertyName: "labelElement", first: true, predicate: ["label"], descendants: true, isSignal: true }], hostDirectives: [{ directive: i1.SciTooltipOnOverflowDirective }], 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 @if (column().header.icon) {\n <sci-icon class=\"icon e2e-icon\">{{column().header.icon}}</sci-icon>\n }\n\n @if (column().header.label) {\n <span [class.wrap]=\"table().wrapHeader()\" class=\"label\" #label>\n {{column().header.label!()}}\n </span>\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:grid;align-items:center;gap:.25em}: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:has(>sci-icon.icon):has(>span.label){grid-template-columns:auto 1fr}:host>button.sort{all:unset;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.25em;padding-inline:var(--\\275sci-table-cell-padding-inline);cursor:var(--sci-table-header-cursor);align-self:stretch}:host>button.sort:has(>sci-icon.icon):has(>span.label){grid-template-columns:auto minmax(0,1fr) auto}:host>button.sort:not(:has(>:is(sci-icon.icon,span.label))){grid-template-columns:1fr;justify-items:end}@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.label{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.label.wrap{white-space:wrap}\n"], dependencies: [{ kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
|
|
2227
2635
|
}
|
|
2228
2636
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciColumnHeaderComponent, decorators: [{
|
|
2229
2637
|
type: Component,
|
|
@@ -2232,9 +2640,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
2232
2640
|
NgTemplateOutlet,
|
|
2233
2641
|
], host: {
|
|
2234
2642
|
'[attr.data-column]': 'column().name',
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2643
|
+
}, hostDirectives: [
|
|
2644
|
+
SciTooltipOnOverflowDirective,
|
|
2645
|
+
], providers: [
|
|
2646
|
+
configureTooltip$1(),
|
|
2647
|
+
], 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 @if (column().header.icon) {\n <sci-icon class=\"icon e2e-icon\">{{column().header.icon}}</sci-icon>\n }\n\n @if (column().header.label) {\n <span [class.wrap]=\"table().wrapHeader()\" class=\"label\" #label>\n {{column().header.label!()}}\n </span>\n }\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:grid;align-items:center;gap:.25em}: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:has(>sci-icon.icon):has(>span.label){grid-template-columns:auto 1fr}:host>button.sort{all:unset;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.25em;padding-inline:var(--\\275sci-table-cell-padding-inline);cursor:var(--sci-table-header-cursor);align-self:stretch}:host>button.sort:has(>sci-icon.icon):has(>span.label){grid-template-columns:auto minmax(0,1fr) auto}:host>button.sort:not(:has(>:is(sci-icon.icon,span.label))){grid-template-columns:1fr;justify-items:end}@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.label{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.label.wrap{white-space:wrap}\n"] }]
|
|
2648
|
+
}], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], labelElement: [{ type: i0.ViewChild, args: ['label', { isSignal: true }] }] } });
|
|
2649
|
+
function configureTooltip$1() {
|
|
2650
|
+
return [
|
|
2651
|
+
{
|
|
2652
|
+
provide: TOOLTIP_ON_OVERFLOW_DIRECTIVE_ADDITIONAL_TEXT,
|
|
2653
|
+
useFactory: () => {
|
|
2654
|
+
const host = inject(SciColumnHeaderComponent);
|
|
2655
|
+
return computed(() => host.column().header.tooltip?.());
|
|
2656
|
+
},
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
provide: TOOLTIP_ON_OVERFLOW_DIRECTIVE_SOURCE_ELEMENT,
|
|
2660
|
+
useFactory: () => inject(SciColumnHeaderComponent).labelElement,
|
|
2661
|
+
},
|
|
2662
|
+
];
|
|
2663
|
+
}
|
|
2238
2664
|
|
|
2239
2665
|
/*
|
|
2240
2666
|
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
@@ -2245,22 +2671,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
2245
2671
|
*
|
|
2246
2672
|
* SPDX-License-Identifier: EPL-2.0
|
|
2247
2673
|
*/
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2674
|
+
/**
|
|
2675
|
+
* Creates a comma-separated string with localized labels of the given enum values.
|
|
2676
|
+
*/
|
|
2677
|
+
class SciEnumLabelPipe {
|
|
2678
|
+
_injector = inject(Injector);
|
|
2679
|
+
transform(values, enumeration) {
|
|
2680
|
+
return computed(() => Arrays.coerce(values)
|
|
2681
|
+
.map(value => enumeration.find(entry => entry.value === value)?.label)
|
|
2682
|
+
.map(label => untracked(() => text(label, { injector: this._injector }))())
|
|
2683
|
+
.filter(Boolean)
|
|
2684
|
+
.join(', ') || undefined);
|
|
2685
|
+
}
|
|
2686
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciEnumLabelPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
2687
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: SciEnumLabelPipe, isStandalone: true, name: "sciEnumLabel" });
|
|
2260
2688
|
}
|
|
2261
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type:
|
|
2689
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciEnumLabelPipe, decorators: [{
|
|
2262
2690
|
type: Pipe,
|
|
2263
|
-
args: [{ name: '
|
|
2691
|
+
args: [{ name: 'sciEnumLabel' }]
|
|
2264
2692
|
}] });
|
|
2265
2693
|
|
|
2266
2694
|
/*
|
|
@@ -2275,33 +2703,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
2275
2703
|
class SciColumnFilterComponent {
|
|
2276
2704
|
column = input.required(/* @ts-ignore */
|
|
2277
2705
|
...(ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []));
|
|
2706
|
+
inputElement = viewChild('input', /* @ts-ignore */
|
|
2707
|
+
...(ngDevMode ? [{ debugName: "inputElement" }] : /* istanbul ignore next */ [])); // public visibility required to be accessible in `configureTooltip` function
|
|
2278
2708
|
filter = signal(null, /* @ts-ignore */
|
|
2279
2709
|
...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
2280
|
-
|
|
2281
|
-
...(ngDevMode ? [{ debugName: "input" }] : /* istanbul ignore next */ []));
|
|
2710
|
+
dimension = dimension(inject(ElementRef));
|
|
2282
2711
|
_table = inject(ɵSCI_TABLE);
|
|
2283
2712
|
_menuService = inject(_SciMenuService);
|
|
2284
2713
|
_injector = inject(Injector);
|
|
2285
2714
|
_host = inject(ElementRef).nativeElement;
|
|
2286
|
-
|
|
2715
|
+
menu;
|
|
2287
2716
|
constructor() {
|
|
2288
2717
|
this.bindToModel();
|
|
2289
2718
|
}
|
|
2290
2719
|
onFilterClick() {
|
|
2291
|
-
if (this.column().type === '
|
|
2292
|
-
this.
|
|
2720
|
+
if (this.column().type === 'enum') {
|
|
2721
|
+
this.toggleEnumChooser();
|
|
2722
|
+
}
|
|
2723
|
+
else {
|
|
2724
|
+
this.inputElement().nativeElement.focus();
|
|
2293
2725
|
}
|
|
2294
|
-
this.input().nativeElement.focus();
|
|
2295
2726
|
}
|
|
2296
2727
|
onReset(event) {
|
|
2297
2728
|
this.filter.set(null);
|
|
2298
|
-
this.
|
|
2729
|
+
this.inputElement().nativeElement.focus();
|
|
2299
2730
|
event.stopPropagation(); // prevent opening menu
|
|
2300
2731
|
}
|
|
2301
2732
|
bindToModel() {
|
|
2302
2733
|
const filterDebounced = debounced(computed(() => trim(this.filter())), async (value) => {
|
|
2303
|
-
// Debounce only if not empty.
|
|
2304
|
-
if (value !== null &&
|
|
2734
|
+
// Debounce only if not empty and not an enum.
|
|
2735
|
+
if (value !== null && this.column().type !== 'enum') {
|
|
2305
2736
|
await firstValueFrom(timer(200));
|
|
2306
2737
|
}
|
|
2307
2738
|
});
|
|
@@ -2314,56 +2745,93 @@ class SciColumnFilterComponent {
|
|
|
2314
2745
|
});
|
|
2315
2746
|
});
|
|
2316
2747
|
}
|
|
2317
|
-
|
|
2748
|
+
toggleEnumChooser() {
|
|
2318
2749
|
// Close menu if already opened.
|
|
2319
|
-
if (this.
|
|
2320
|
-
this.
|
|
2750
|
+
if (this.menu) {
|
|
2751
|
+
this.menu.close();
|
|
2321
2752
|
return;
|
|
2322
2753
|
}
|
|
2323
|
-
const
|
|
2754
|
+
const column = this.column();
|
|
2755
|
+
const injector = _createDestroyableInjector({ parent: this._injector });
|
|
2324
2756
|
runInInjectionContext(injector, () => {
|
|
2757
|
+
const menuId = `menu:column-filter-${UUID.randomUUID()}`;
|
|
2325
2758
|
// Contribute menu items.
|
|
2326
|
-
contributeMenu(
|
|
2327
|
-
|
|
2328
|
-
.
|
|
2759
|
+
contributeMenu(menuId, menu => {
|
|
2760
|
+
const multi = column.filterable() === 'multi';
|
|
2761
|
+
column.enumeration().forEach(entry => menu.addMenuItem({
|
|
2762
|
+
label: entry.label,
|
|
2763
|
+
checked: multi ? computed(() => this.filter()?.includes(entry.value) ?? false) : undefined,
|
|
2764
|
+
icon: column.format.filter.icon ? entry.icon : undefined,
|
|
2765
|
+
attributes: { 'data-option': `${entry.value}` },
|
|
2766
|
+
onSelect: () => this.filter.update(filter => {
|
|
2767
|
+
if (multi) {
|
|
2768
|
+
const values = new Set(filter ?? []);
|
|
2769
|
+
values.has(entry.value) ? values.delete(entry.value) : values.add(entry.value);
|
|
2770
|
+
return values.size ? Array.from(values) : null;
|
|
2771
|
+
}
|
|
2772
|
+
else {
|
|
2773
|
+
return entry.value;
|
|
2774
|
+
}
|
|
2775
|
+
}),
|
|
2776
|
+
}));
|
|
2777
|
+
});
|
|
2329
2778
|
// Open menu.
|
|
2330
|
-
this.
|
|
2779
|
+
this.menu = this._menuService.open(menuId, {
|
|
2331
2780
|
anchor: this._host,
|
|
2332
2781
|
minWidth: `${this._host.offsetWidth}px`,
|
|
2782
|
+
maxHeight: '300px',
|
|
2783
|
+
cssClass: 'e2e-filter',
|
|
2784
|
+
filter: column.enumeration().length >= 8 ? { focus: true } : false,
|
|
2785
|
+
maxWidth: `max(300px, ${this._host.offsetWidth}px)`, // overflow long enumeration labels
|
|
2333
2786
|
offset: { y: -1 },
|
|
2334
2787
|
});
|
|
2335
2788
|
// Close menu when start resizing a column. By default, closes the menu only on mouse up.
|
|
2336
2789
|
toObservable(this._table().resizing)
|
|
2337
2790
|
.pipe(skip(1))
|
|
2338
|
-
.subscribe(() => this.
|
|
2339
|
-
this.
|
|
2791
|
+
.subscribe(() => this.menu?.close());
|
|
2792
|
+
this.menu.onClose(() => {
|
|
2340
2793
|
injector.destroy();
|
|
2341
2794
|
// Restore focus manually as it is not restored automatically since opening the popover inside Shadow DOM.
|
|
2342
2795
|
// TODO [menu] Consider restore focus manually in Menu API due to restriction in shadow DOM.
|
|
2343
|
-
this.
|
|
2344
|
-
this.
|
|
2796
|
+
this.inputElement().nativeElement.focus();
|
|
2797
|
+
this.menu = undefined;
|
|
2345
2798
|
});
|
|
2346
2799
|
});
|
|
2347
2800
|
}
|
|
2348
2801
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciColumnFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2349
|
-
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"
|
|
2802
|
+
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", "class.collapsed": "dimension().clientWidth < 50" } }, providers: [
|
|
2803
|
+
configureTooltip(),
|
|
2804
|
+
], viewQueries: [{ propertyName: "inputElement", first: true, predicate: ["input"], descendants: true, isSignal: true }], hostDirectives: [{ directive: i1.SciTooltipOnOverflowDirective }], ngImport: i0, template: "@let column = this.column();\n@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 ('enum') {\n <input [ngModel]=\"(filter() | sciEnumLabel:column.enumeration())()\" type=\"button\" #input>\n <sci-icon class=\"chevron\">{{menu ? 'scion:chevron_up' : '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)\" [attr.title]=\"('%scion:components:clear.tooltip' | sciText)()\">\n <sci-icon>scion:clear</sci-icon>\n </button>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;gap:.25em;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:has(input[type=button]){cursor:var(--sci-button-cursor)}:host:has(input[type=button]):has(button.clear)>sci-icon.chevron{display:none}:host.collapsed{gap:0}: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>input[type=button]{white-space:nowrap;text-overflow:ellipsis}: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: i2.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: i2.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.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: SciEnumLabelPipe, name: "sciEnumLabel" }, { kind: "pipe", type: SciTextPipe, name: "sciText" }] });
|
|
2350
2805
|
}
|
|
2351
2806
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciColumnFilterComponent, decorators: [{
|
|
2352
2807
|
type: Component,
|
|
2353
2808
|
args: [{ selector: 'sci-column-filter', imports: [
|
|
2354
2809
|
FormsModule,
|
|
2355
2810
|
SciIconComponent,
|
|
2811
|
+
SciEnumLabelPipe,
|
|
2356
2812
|
SciTextPipe,
|
|
2357
|
-
|
|
2813
|
+
], hostDirectives: [
|
|
2814
|
+
SciTooltipOnOverflowDirective,
|
|
2358
2815
|
], host: {
|
|
2359
2816
|
'[attr.data-column]': 'column().name',
|
|
2360
2817
|
'[attr.data-disabled]': `!column().filterable() ? '' : null`,
|
|
2818
|
+
'[class.collapsed]': 'dimension().clientWidth < 50',
|
|
2361
2819
|
'(click)': 'column().filterable() && onFilterClick()',
|
|
2362
|
-
},
|
|
2363
|
-
|
|
2820
|
+
}, providers: [
|
|
2821
|
+
configureTooltip(),
|
|
2822
|
+
], template: "@let column = this.column();\n@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 ('enum') {\n <input [ngModel]=\"(filter() | sciEnumLabel:column.enumeration())()\" type=\"button\" #input>\n <sci-icon class=\"chevron\">{{menu ? 'scion:chevron_up' : '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)\" [attr.title]=\"('%scion:components:clear.tooltip' | sciText)()\">\n <sci-icon>scion:clear</sci-icon>\n </button>\n }\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;gap:.25em;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:has(input[type=button]){cursor:var(--sci-button-cursor)}:host:has(input[type=button]):has(button.clear)>sci-icon.chevron{display:none}:host.collapsed{gap:0}: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>input[type=button]{white-space:nowrap;text-overflow:ellipsis}: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"] }]
|
|
2823
|
+
}], ctorParameters: () => [], propDecorators: { column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], inputElement: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }] } });
|
|
2364
2824
|
function trim(value) {
|
|
2365
2825
|
return typeof value === 'string' ? value.trim() || null : value;
|
|
2366
2826
|
}
|
|
2827
|
+
function configureTooltip() {
|
|
2828
|
+
return [
|
|
2829
|
+
{
|
|
2830
|
+
provide: TOOLTIP_ON_OVERFLOW_DIRECTIVE_SOURCE_ELEMENT,
|
|
2831
|
+
useFactory: () => inject(SciColumnFilterComponent).inputElement,
|
|
2832
|
+
},
|
|
2833
|
+
];
|
|
2834
|
+
}
|
|
2367
2835
|
|
|
2368
2836
|
/*
|
|
2369
2837
|
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
@@ -2405,6 +2873,72 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
2405
2873
|
* SPDX-License-Identifier: EPL-2.0
|
|
2406
2874
|
*/
|
|
2407
2875
|
/**
|
|
2876
|
+
* Provides a table to display tabular or hierarchical data.
|
|
2877
|
+
*
|
|
2878
|
+
* ### Usage
|
|
2879
|
+
*
|
|
2880
|
+
* Create a table using the `table()` function, passing a signal containing the items to display in the table and a factory function to define the columns.
|
|
2881
|
+
* Alternatively, pass a descriptor to display a tree structure, configure a custom datasource, or set additional options.
|
|
2882
|
+
*
|
|
2883
|
+
* ```ts
|
|
2884
|
+
* import {SciTableComponent, table} from '@scion/components/table';
|
|
2885
|
+
* import {httpResource} from '@angular/common/http';
|
|
2886
|
+
*
|
|
2887
|
+
* // Load data to display in the table.
|
|
2888
|
+
* const users = httpResource<User[]>(() => '/api/users', {defaultValue: []});
|
|
2889
|
+
*
|
|
2890
|
+
* // Create the table and pass data.
|
|
2891
|
+
* const userTable = table(users.value, table => table
|
|
2892
|
+
* .addNumberColumn('ID', user => user.id)
|
|
2893
|
+
* .addStringColumn('Name', user => user.name)
|
|
2894
|
+
* .addBooleanColumn('Inactive', user => user.inactive)
|
|
2895
|
+
* .addDateColumn('Expiration', user => user.expiration),
|
|
2896
|
+
* );
|
|
2897
|
+
* ```
|
|
2898
|
+
*
|
|
2899
|
+
* In the HTML, bind the created table instance to `<sci-table>` and assign it a name.
|
|
2900
|
+
* The name must start with the `table:` prefix and is used to store user settings.
|
|
2901
|
+
*
|
|
2902
|
+
* ```html
|
|
2903
|
+
* <sci-table name="table:users" [table]="userTable"/>
|
|
2904
|
+
* ```
|
|
2905
|
+
*
|
|
2906
|
+
* ### Fixed Row Height
|
|
2907
|
+
*
|
|
2908
|
+
* All rows in the table must have equal height, which is `2em` by default. The height can be changed per table using the CSS variable `--sci-table-row-height`.
|
|
2909
|
+
*
|
|
2910
|
+
* ```scss
|
|
2911
|
+
* sci-table {
|
|
2912
|
+
* --sci-table-row-height: 30px;
|
|
2913
|
+
* }
|
|
2914
|
+
* ```
|
|
2915
|
+
*
|
|
2916
|
+
* ### Styling
|
|
2917
|
+
*
|
|
2918
|
+
* The appearance of the table can be customized using the following CSS variables, either globally in the `:root` or at the table level.
|
|
2919
|
+
*
|
|
2920
|
+
* - `--sci-table-gridline-color`
|
|
2921
|
+
* - `--sci-table-header-height`
|
|
2922
|
+
* - `--sci-table-header-cursor`
|
|
2923
|
+
* - `--sci-table-header-background-color`
|
|
2924
|
+
* - `--sci-table-header-background-color-hover`
|
|
2925
|
+
* - `--sci-table-header-font-family`
|
|
2926
|
+
* - `--sci-table-header-font-size`
|
|
2927
|
+
* - `--sci-table-header-font-weight`
|
|
2928
|
+
* - `--sci-table-header-text-color`
|
|
2929
|
+
* - `--sci-table-header-column-divider`
|
|
2930
|
+
* - `--sci-table-row-height`
|
|
2931
|
+
* - `--sci-table-row-indent`
|
|
2932
|
+
* - `--sci-table-row-background-color-hover`
|
|
2933
|
+
* - `--sci-table-row-background-color-selected`
|
|
2934
|
+
* - `--sci-table-row-border-radius`
|
|
2935
|
+
* - `--sci-table-row-outline-color`
|
|
2936
|
+
* - `--sci-table-row-outline-width`
|
|
2937
|
+
* - `--sci-table-row-outline-style`
|
|
2938
|
+
* - `--sci-table-row-action-background-color`
|
|
2939
|
+
* - `--sci-table-row-action-background-color-selected`
|
|
2940
|
+
* - `--sci-table-cell-padding-inline`
|
|
2941
|
+
*
|
|
2408
2942
|
* @experimental since 22.3.0; API and behavior may change in any version without notice.
|
|
2409
2943
|
*/
|
|
2410
2944
|
class SciTableComponent {
|
|
@@ -2437,9 +2971,10 @@ class SciTableComponent {
|
|
|
2437
2971
|
}
|
|
2438
2972
|
onRowPrimaryAction(event) {
|
|
2439
2973
|
this.primaryAction.emit(event);
|
|
2974
|
+
event.items.forEach(item => this.table().toggleRow(item));
|
|
2440
2975
|
}
|
|
2441
2976
|
onRetry() {
|
|
2442
|
-
this.table().
|
|
2977
|
+
this.table().reloadFailed();
|
|
2443
2978
|
}
|
|
2444
2979
|
connectToModel() {
|
|
2445
2980
|
const viewportDimension = dimension(this._viewport);
|
|
@@ -2470,15 +3005,16 @@ class SciTableComponent {
|
|
|
2470
3005
|
}
|
|
2471
3006
|
scrollActiveRowIntoViewport() {
|
|
2472
3007
|
effect(() => {
|
|
2473
|
-
const
|
|
2474
|
-
if (
|
|
3008
|
+
const activeItem = this.table().activeItem();
|
|
3009
|
+
if (activeItem === undefined) {
|
|
2475
3010
|
return;
|
|
2476
3011
|
}
|
|
2477
3012
|
untracked(() => {
|
|
2478
3013
|
const viewport = this._viewport().nativeElement;
|
|
2479
3014
|
const viewportHeight = this.table().tableViewRef()?.viewportHeight() ?? 0;
|
|
2480
3015
|
const itemHeight = this.table().tableViewRef()?.itemHeight() ?? 0;
|
|
2481
|
-
const
|
|
3016
|
+
const activeIndex = this.table().rowIndexById().get(activeItem) ?? -1;
|
|
3017
|
+
const activeRowTop = activeIndex * itemHeight;
|
|
2482
3018
|
const activeRowBottom = activeRowTop + itemHeight;
|
|
2483
3019
|
const scrollTop = viewport.scrollTop;
|
|
2484
3020
|
const scrollBottom = scrollTop + viewportHeight;
|
|
@@ -2495,7 +3031,7 @@ class SciTableComponent {
|
|
|
2495
3031
|
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: [
|
|
2496
3032
|
provideSciTable(),
|
|
2497
3033
|
SciTableSelectionService,
|
|
2498
|
-
], viewQueries: [{ propertyName: "_viewport", first: true, predicate: SciTableViewportComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_viewportClient", first: true, predicate: SciTableGridComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_tableHeader", first: true, predicate: SciTableHeaderComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_tableBody", first: true, predicate: SciTableBodyComponent, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "_itemSizeSyntheticElement", first: true, predicate: ["item_size_synthetic_element"], descendants: true, isSignal: true }, { propertyName: "_cellPaddingSyntheticElement", first: true, predicate: ["cell_padding_synthetic_element"], descendants: true, isSignal: true }, { propertyName: "rows", predicate: SciTableRowComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@let table = this.table();\n\n<sci-table-viewport #viewport>\n <sci-table-grid>\n <!-- Table header -->\n @if (table.showHeader() || table.filterable()) {\n <sci-table-header/>\n }\n\n <!-- Table body -->\n <sci-table-body sciTableKeyboardNavigator>\n @if (!table.error()) {\n <!-- Track index to reuse the table row elements -->\n @for (row of table.rows(); track $index) {\n <sci-table-row [row]=\"row\"
|
|
3034
|
+
], 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<!-- TODO [#384] Replace with sci-viewport -->\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\" [sciAttributes]=\"row.bindings.attributes()\" [class]=\"row.bindings.classes()\" (primaryAction)=\"onRowPrimaryAction($event)\"/>\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 <sci-icon>scion:reload</sci-icon>\n <span>{{('%scion:components:retry.action' | sciText)()}}</span>\n </button>\n </span>\n }\n\n <!-- Render active row outline separately to preserve row border-radius within block selection. -->\n @if (table.activeRow()) {\n <div class=\"active-row-outline e2e-active-row-outline\"></div>\n }\n </sci-table-body>\n\n <!-- Render column splitters. -->\n <sci-table-columns [rows]=\"rows()\"/>\n </sci-table-grid>\n</sci-table-viewport>\n\n<!-- Scrollbars -->\n<sci-scrollbar direction=\"hscroll\" [viewport]=\"viewport.host\"/>\n<sci-scrollbar direction=\"vscroll\" [viewport]=\"viewport.host\"/>\n\n@if (table.loading()) {\n <sci-throbber type=\"spinner\" class=\"loading-indicator e2e-loading-indicator\"/>\n}\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-row-height`, relative to the table's font size if em-based. -->\n<div class=\"synthetic item-size\" #item_size_synthetic_element></div>\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-cell-padding-inline`, relative to the table's font size if em-based. -->\n<div class=\"synthetic cell-padding\" #cell_padding_synthetic_element></div>\n", styles: ["@charset \"UTF-8\";:host{display:block;overflow:hidden;--\\275sci-table-border-size: 1px;--\\275sci-table-border-color: var(--sci-color-border)}:host>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:is(:not(:hover),:has(sci-table-header:hover))~sci-scrollbar{opacity:0}: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{display:flex;gap:.5em;border:1px solid var(--sci-color-border);padding:.35em 1em}@container style(--\\275sci-table-viewport-client-width > --\\275sci-table-viewport-width){:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message{position:sticky;left:50%;right:50%;width:0}}:host>sci-table-viewport>sci-table-grid>sci-table-body:has(>span.message){z-index:2}:host>sci-table-viewport>sci-table-grid>sci-table-columns{grid-area:1/1/-1/-1;z-index:1;position:sticky;top:0;max-height:var(--\\275sci-table-viewport-height)}:host>sci-scrollbar{position:absolute}:host>sci-scrollbar[direction=vscroll]{top:calc(anchor(--sci-table-body top) + 2px);right:calc(anchor(--sci-table-viewport right) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 2px)}:host>sci-scrollbar[direction=hscroll]{left:calc(anchor(--sci-table-viewport left) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 1px);right:calc(anchor(--sci-table-viewport right) + 2px)}@container style(--\\275sci-table-resizing: true){:host>sci-scrollbar{pointer-events:none}}:host>sci-throbber.loading-indicator{--sci-throbber-size: 16px;position:absolute;top:calc(anchor(--sci-table-viewport top) + .5em);right:calc(anchor(--sci-table-viewport right) + .5em)}:host>div.synthetic{position:absolute;pointer-events:none;visibility:hidden}:host>div.synthetic.item-size{height:var(--sci-table-row-height)}:host>div.synthetic.cell-padding{width:var(--sci-table-cell-padding-inline)}\n"], dependencies: [{ kind: "component", type: SciTableViewportComponent, selector: "sci-table-viewport" }, { kind: "component", type: SciTableHeaderComponent, selector: "sci-table-header" }, { kind: "component", type: SciTableGridComponent, selector: "sci-table-grid" }, { kind: "component", type: SciTableBodyComponent, selector: "sci-table-body" }, { kind: "component", type: SciTableRowComponent, selector: "sci-table-row", inputs: ["row"], outputs: ["primaryAction"] }, { kind: "component", type: SciTableColumnsComponent, selector: "sci-table-columns", inputs: ["rows"] }, { kind: "directive", type: SciTableKeyboardNavigatorDirective, selector: "[sciTableKeyboardNavigator]" }, { kind: "directive", type: SciAttributesDirective, selector: "[sciAttributes]", inputs: ["sciAttributes"] }, { kind: "component", type: SciScrollbarComponent, selector: "sci-scrollbar", inputs: ["direction", "viewport"] }, { kind: "component", type: SciThrobberComponent, selector: "sci-throbber", inputs: ["type"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "pipe", type: SciTextPipe, name: "sciText" }], encapsulation: i0.ViewEncapsulation.ShadowDom });
|
|
2499
3035
|
}
|
|
2500
3036
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciTableComponent, decorators: [{
|
|
2501
3037
|
type: Component,
|
|
@@ -2519,7 +3055,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
2519
3055
|
], providers: [
|
|
2520
3056
|
provideSciTable(),
|
|
2521
3057
|
SciTableSelectionService,
|
|
2522
|
-
], template: "@let table = this.table();\n\n<sci-table-viewport #viewport>\n <sci-table-grid>\n <!-- Table header -->\n @if (table.showHeader() || table.filterable()) {\n <sci-table-header/>\n }\n\n <!-- Table body -->\n <sci-table-body sciTableKeyboardNavigator>\n @if (!table.error()) {\n <!-- Track index to reuse the table row elements -->\n @for (row of table.rows(); track $index) {\n <sci-table-row [row]=\"row\"
|
|
3058
|
+
], template: "@let table = this.table();\n\n<!-- TODO [#384] Replace with sci-viewport -->\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\" [sciAttributes]=\"row.bindings.attributes()\" [class]=\"row.bindings.classes()\" (primaryAction)=\"onRowPrimaryAction($event)\"/>\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 <sci-icon>scion:reload</sci-icon>\n <span>{{('%scion:components:retry.action' | sciText)()}}</span>\n </button>\n </span>\n }\n\n <!-- Render active row outline separately to preserve row border-radius within block selection. -->\n @if (table.activeRow()) {\n <div class=\"active-row-outline e2e-active-row-outline\"></div>\n }\n </sci-table-body>\n\n <!-- Render column splitters. -->\n <sci-table-columns [rows]=\"rows()\"/>\n </sci-table-grid>\n</sci-table-viewport>\n\n<!-- Scrollbars -->\n<sci-scrollbar direction=\"hscroll\" [viewport]=\"viewport.host\"/>\n<sci-scrollbar direction=\"vscroll\" [viewport]=\"viewport.host\"/>\n\n@if (table.loading()) {\n <sci-throbber type=\"spinner\" class=\"loading-indicator e2e-loading-indicator\"/>\n}\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-row-height`, relative to the table's font size if em-based. -->\n<div class=\"synthetic item-size\" #item_size_synthetic_element></div>\n\n<!-- Synthetic element to compute the absolute pixel value of the `--sci-table-cell-padding-inline`, relative to the table's font size if em-based. -->\n<div class=\"synthetic cell-padding\" #cell_padding_synthetic_element></div>\n", styles: ["@charset \"UTF-8\";:host{display:block;overflow:hidden;--\\275sci-table-border-size: 1px;--\\275sci-table-border-color: var(--sci-color-border)}:host>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:is(:not(:hover),:has(sci-table-header:hover))~sci-scrollbar{opacity:0}: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{display:flex;gap:.5em;border:1px solid var(--sci-color-border);padding:.35em 1em}@container style(--\\275sci-table-viewport-client-width > --\\275sci-table-viewport-width){:host>sci-table-viewport>sci-table-grid>sci-table-body>span.message{position:sticky;left:50%;right:50%;width:0}}:host>sci-table-viewport>sci-table-grid>sci-table-body:has(>span.message){z-index:2}:host>sci-table-viewport>sci-table-grid>sci-table-columns{grid-area:1/1/-1/-1;z-index:1;position:sticky;top:0;max-height:var(--\\275sci-table-viewport-height)}:host>sci-scrollbar{position:absolute}:host>sci-scrollbar[direction=vscroll]{top:calc(anchor(--sci-table-body top) + 2px);right:calc(anchor(--sci-table-viewport right) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 2px)}:host>sci-scrollbar[direction=hscroll]{left:calc(anchor(--sci-table-viewport left) + 2px);bottom:calc(anchor(--sci-table-viewport bottom) + 1px);right:calc(anchor(--sci-table-viewport right) + 2px)}@container style(--\\275sci-table-resizing: true){:host>sci-scrollbar{pointer-events:none}}:host>sci-throbber.loading-indicator{--sci-throbber-size: 16px;position:absolute;top:calc(anchor(--sci-table-viewport top) + .5em);right:calc(anchor(--sci-table-viewport right) + .5em)}:host>div.synthetic{position:absolute;pointer-events:none;visibility:hidden}:host>div.synthetic.item-size{height:var(--sci-table-row-height)}:host>div.synthetic.cell-padding{width:var(--sci-table-cell-padding-inline)}\n"] }]
|
|
2523
3059
|
}], 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 }] }] } });
|
|
2524
3060
|
function provideSciTable() {
|
|
2525
3061
|
return {
|
|
@@ -2542,5 +3078,5 @@ function provideSciTable() {
|
|
|
2542
3078
|
* Generated bundle index. Do not edit.
|
|
2543
3079
|
*/
|
|
2544
3080
|
|
|
2545
|
-
export { SciTableComponent,
|
|
3081
|
+
export { SciTableComponent, provideTableDatasource, provideTableRowBinding, table, ɵSciTable, ɵSciTableRowBindingFactory, ɵprovideTableDatasource };
|
|
2546
3082
|
//# sourceMappingURL=scion-components-table.mjs.map
|