apex-grid 3.5.0 → 3.6.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/CHANGELOG.md +338 -0
- package/LICENSE.md +125 -0
- package/README.md +64 -10
- package/components/cell.d.ts +30 -4
- package/components/cell.js +199 -53
- package/components/cell.js.map +1 -1
- package/components/filter-row.d.ts +2 -0
- package/components/filter-row.js +82 -28
- package/components/filter-row.js.map +1 -1
- package/components/grid.d.ts +146 -36
- package/components/grid.js +442 -103
- package/components/grid.js.map +1 -1
- package/components/group-header-row.js +28 -25
- package/components/group-header-row.js.map +1 -1
- package/components/header-row.d.ts +7 -5
- package/components/header-row.js +11 -9
- package/components/header-row.js.map +1 -1
- package/components/header.js +32 -20
- package/components/header.js.map +1 -1
- package/components/row.d.ts +19 -3
- package/components/row.js +69 -23
- package/components/row.js.map +1 -1
- package/components/toolbar.js +2 -1
- package/components/toolbar.js.map +1 -1
- package/components/virtualizer.d.ts +40 -0
- package/components/virtualizer.js +116 -5
- package/components/virtualizer.js.map +1 -1
- package/controllers/dom.d.ts +4 -4
- package/controllers/dom.js +40 -22
- package/controllers/dom.js.map +1 -1
- package/controllers/editing.d.ts +104 -37
- package/controllers/editing.js +230 -78
- package/controllers/editing.js.map +1 -1
- package/controllers/expansion.d.ts +14 -5
- package/controllers/expansion.js +24 -1
- package/controllers/expansion.js.map +1 -1
- package/controllers/filter.d.ts +10 -2
- package/controllers/filter.js +62 -17
- package/controllers/filter.js.map +1 -1
- package/controllers/history.d.ts +45 -8
- package/controllers/history.js +58 -20
- package/controllers/history.js.map +1 -1
- package/controllers/navigation.d.ts +67 -13
- package/controllers/navigation.js +191 -51
- package/controllers/navigation.js.map +1 -1
- package/controllers/pagination.d.ts +6 -0
- package/controllers/pagination.js +10 -2
- package/controllers/pagination.js.map +1 -1
- package/controllers/reorder.js +38 -22
- package/controllers/reorder.js.map +1 -1
- package/controllers/resize.d.ts +17 -2
- package/controllers/resize.js +16 -6
- package/controllers/resize.js.map +1 -1
- package/controllers/row-pin.d.ts +20 -2
- package/controllers/row-pin.js +16 -9
- package/controllers/row-pin.js.map +1 -1
- package/controllers/row-reorder.d.ts +31 -11
- package/controllers/row-reorder.js +117 -37
- package/controllers/row-reorder.js.map +1 -1
- package/controllers/selection.d.ts +11 -4
- package/controllers/selection.js +26 -11
- package/controllers/selection.js.map +1 -1
- package/controllers/sort.d.ts +22 -2
- package/controllers/sort.js +29 -23
- package/controllers/sort.js.map +1 -1
- package/controllers/state.d.ts +56 -7
- package/controllers/state.js +76 -6
- package/controllers/state.js.map +1 -1
- package/controllers/tree.d.ts +15 -10
- package/controllers/tree.js +0 -0
- package/controllers/tree.js.map +1 -1
- package/custom-elements.json +3156 -277
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/highlight.css +15 -8
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/ApexGrid.html +155 -81
- package/docs/classes/ApexGridPaginator.html +1 -1
- package/docs/classes/ApexGridToolbar.html +3 -3
- package/docs/functions/localize.html +2 -2
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +74 -11
- package/docs/interfaces/ApexCellValidationFailedEvent.html +3 -2
- package/docs/interfaces/ApexCellValueChangedEvent.html +4 -2
- package/docs/interfaces/ApexCellValueChangingEvent.html +3 -2
- package/docs/interfaces/ApexGridEventMap.html +5 -2
- package/docs/interfaces/ApexRowEditEndedEvent.html +2 -1
- package/docs/interfaces/ApexRowEditStartedEvent.html +2 -1
- package/docs/interfaces/ApexRowMovingEvent.html +9 -3
- package/docs/interfaces/ApexRowPinningEvent.html +1 -1
- package/docs/interfaces/ApexTreeRowExpandedEvent.html +4 -1
- package/docs/interfaces/ApexTreeRowExpandingEvent.html +4 -1
- package/docs/interfaces/BaseApexCellContext.html +1 -1
- package/docs/interfaces/BaseApexEditorContext.html +1 -1
- package/docs/interfaces/BaseFilterExpression.html +2 -2
- package/docs/interfaces/CSVExportOptions.html +12 -4
- package/docs/interfaces/DataPipelineConfiguration.html +1 -1
- package/docs/interfaces/ExportOptions.html +5 -3
- package/docs/interfaces/GridExpansionConfiguration.html +6 -2
- package/docs/interfaces/GridRowReorderingConfiguration.html +1 -1
- package/docs/interfaces/PaginationState.html +1 -1
- package/docs/interfaces/SetStateResult.html +3 -2
- package/docs/interfaces/ValidatorContext.html +7 -2
- package/docs/media/_token-values.scss +118 -0
- package/docs/media/package.json +3 -2
- package/docs/modules.html +1 -1
- package/docs/types/ExportSource.html +6 -4
- package/docs/types/OperandKeys.html +9 -0
- package/docs/variables/EN_LOCALE.html +2 -2
- package/i18n/en.d.ts +5 -0
- package/i18n/en.js +5 -0
- package/i18n/en.js.map +1 -1
- package/i18n/es.js +5 -0
- package/i18n/es.js.map +1 -1
- package/internal/column-types.d.ts +27 -1
- package/internal/column-types.js +75 -49
- package/internal/column-types.js.map +1 -1
- package/internal/constants.d.ts +1 -2
- package/internal/constants.js +0 -4
- package/internal/constants.js.map +1 -1
- package/internal/document-styles.d.ts +30 -0
- package/internal/document-styles.js +48 -0
- package/internal/document-styles.js.map +1 -0
- package/internal/export.d.ts +39 -7
- package/internal/export.js +35 -34
- package/internal/export.js.map +1 -1
- package/internal/feature-module.d.ts +60 -3
- package/internal/feature-module.js +6 -0
- package/internal/feature-module.js.map +1 -1
- package/internal/index.d.ts +6 -4
- package/internal/index.js +5 -3
- package/internal/index.js.map +1 -1
- package/internal/normalize-case.d.ts +8 -1
- package/internal/normalize-case.js +3 -2
- package/internal/normalize-case.js.map +1 -1
- package/internal/state-snapshot.d.ts +60 -8
- package/internal/state-snapshot.js +87 -19
- package/internal/state-snapshot.js.map +1 -1
- package/internal/types.d.ts +17 -2
- package/internal/types.js.map +1 -1
- package/internal/utils.d.ts +34 -6
- package/internal/utils.js +30 -21
- package/internal/utils.js.map +1 -1
- package/internal/validators.js +1 -0
- package/internal/validators.js.map +1 -1
- package/operations/filter/operands/number.js +9 -6
- package/operations/filter/operands/number.js.map +1 -1
- package/operations/filter/operands/string.js +2 -3
- package/operations/filter/operands/string.js.map +1 -1
- package/operations/filter/types.d.ts +13 -1
- package/operations/filter/types.js.map +1 -1
- package/operations/filter.d.ts +6 -0
- package/operations/filter.js +23 -7
- package/operations/filter.js.map +1 -1
- package/operations/quick-filter.js +2 -5
- package/operations/quick-filter.js.map +1 -1
- package/operations/sort.d.ts +21 -2
- package/operations/sort.js +41 -14
- package/operations/sort.js.map +1 -1
- package/package.json +8 -3
- package/setup.d.ts +11 -0
- package/setup.js +9 -19
- package/setup.js.map +1 -1
- package/styles/_token-values.css.d.ts +1 -0
- package/styles/_token-values.css.js +3 -0
- package/styles/_token-values.css.js.map +1 -0
- package/styles/_tokens.css.js +1 -1
- package/styles/_tokens.css.js.map +1 -1
- package/styles/body-cell/body-cell.css.js +1 -1
- package/styles/body-cell/body-cell.css.js.map +1 -1
- package/styles/body-cell/cell-renderers.css.js +1 -1
- package/styles/body-cell/cell-renderers.css.js.map +1 -1
- package/styles/body-row/body-row.css.js +1 -1
- package/styles/body-row/body-row.css.js.map +1 -1
- package/styles/document-tokens.css.d.ts +1 -0
- package/styles/document-tokens.css.js +3 -0
- package/styles/document-tokens.css.js.map +1 -0
- package/styles/filter-row/filter-row.css.js +1 -1
- package/styles/filter-row/filter-row.css.js.map +1 -1
- package/styles/grid/_grid.base.css.js +1 -1
- package/styles/grid/_grid.base.css.js.map +1 -1
- package/styles/grid/grid.css.js +1 -1
- package/styles/grid/grid.css.js.map +1 -1
- package/styles/group-header-row/group-header-row.css.js +1 -1
- package/styles/group-header-row/group-header-row.css.js.map +1 -1
- package/styles/header-cell/header-cell.css.js +1 -1
- package/styles/header-cell/header-cell.css.js.map +1 -1
- package/styles/header-row/header-row.base.css.js +1 -1
- package/styles/header-row/header-row.base.css.js.map +1 -1
- package/styles/paginator/paginator.css.js +1 -1
- package/styles/paginator/paginator.css.js.map +1 -1
- package/styles/toolbar/toolbar.css.js +1 -1
- package/styles/toolbar/toolbar.css.js.map +1 -1
- package/tokens.css +78 -0
- package/LICENSE +0 -80
- package/docs/media/LICENSE +0 -80
- package/docs/media/_tokens.scss +0 -214
package/components/grid.js
CHANGED
|
@@ -10,16 +10,18 @@ import { eventOptions, property, query, queryAll, state } from 'lit/decorators.j
|
|
|
10
10
|
import { styleMap } from 'lit/directives/style-map.js';
|
|
11
11
|
import { DataOperationsController } from '../controllers/data-operation.js';
|
|
12
12
|
import { GridDOMController } from '../controllers/dom.js';
|
|
13
|
+
import { viewIndexer } from '../controllers/history.js';
|
|
13
14
|
import { gridStateContext, StateController } from '../controllers/state.js';
|
|
14
15
|
import { localize as resolveLocaleText, } from '../i18n/index.js';
|
|
15
16
|
import { DEFAULT_COLUMN_CONFIG, PIPELINE, SENTINEL_NODE } from '../internal/constants.js';
|
|
17
|
+
import { adoptGridDocumentStyles } from '../internal/document-styles.js';
|
|
16
18
|
import { buildCSV, downloadBlob, } from '../internal/export.js';
|
|
17
19
|
import { EventEmitterBase } from '../internal/mixins/event-emitter.js';
|
|
18
20
|
import { registerComponent } from '../internal/register.js';
|
|
19
21
|
import { columnSchema, operandsByType } from '../internal/state-schema.js';
|
|
20
|
-
import { applyColumnLayout, deserializeFilter, resolveRowRefs, serializeColumnLayout, serializeFilter, serializeRowRefs, serializeSort, } from '../internal/state-snapshot.js';
|
|
22
|
+
import { applyColumnLayout, deserializeFilter, resolveFilterOperand, resolveRowRefs, SET_STATE_SLICES, serializeColumnLayout, serializeFilter, serializeRowRefs, serializeSort, } from '../internal/state-snapshot.js';
|
|
21
23
|
import { GRID_TAG } from '../internal/tags.js';
|
|
22
|
-
import { asArray, autoGenerateColumns, getDisplayColumns
|
|
24
|
+
import { asArray, autoGenerateColumns, getDisplayColumns } from '../internal/utils.js';
|
|
23
25
|
import { watch } from '../internal/watch.js';
|
|
24
26
|
import { styles as gridStyles } from '../styles/grid/grid.css.js';
|
|
25
27
|
import ApexGridCell from './cell.js';
|
|
@@ -30,19 +32,33 @@ import ApexGridPaginator from './paginator.js';
|
|
|
30
32
|
import ApexGridRow from './row.js';
|
|
31
33
|
import ApexGridToolbar from './toolbar.js';
|
|
32
34
|
import ApexVirtualizer from './virtualizer.js';
|
|
33
|
-
const
|
|
35
|
+
const SORT_DIRECTIONS = ['ascending', 'descending', 'none'];
|
|
36
|
+
const VIEW_SHAPING_SLICES = new Set([
|
|
34
37
|
'columns',
|
|
35
|
-
'sort',
|
|
36
38
|
'filter',
|
|
37
39
|
'quickFilter',
|
|
38
40
|
'modules',
|
|
39
|
-
'rowOrder',
|
|
40
41
|
'tree',
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
]
|
|
42
|
+
]);
|
|
43
|
+
const VIEW_SHAPING_PROPERTIES = new Map([
|
|
44
|
+
['data', null],
|
|
45
|
+
['quickFilter', null],
|
|
46
|
+
['pagination', ['enabled', 'mode', 'pageSize', 'totalItems']],
|
|
47
|
+
['tree', ['enabled', 'getDataPath', 'defaultExpanded']],
|
|
48
|
+
['dataPipelineConfiguration', ['sort', 'filter', 'pagination', 'quickFilter']],
|
|
49
|
+
]);
|
|
50
|
+
function viewShapeChanged(fields, before, after) {
|
|
51
|
+
if (Object.is(before, after))
|
|
52
|
+
return false;
|
|
53
|
+
if (!fields)
|
|
54
|
+
return true;
|
|
55
|
+
const a = (before ?? {});
|
|
56
|
+
const b = (after ?? {});
|
|
57
|
+
return fields.some((field) => !Object.is(a[field], b[field]));
|
|
58
|
+
}
|
|
59
|
+
function warnDropped(message) {
|
|
60
|
+
console.warn(`[apex-grid] ${message}`);
|
|
61
|
+
}
|
|
46
62
|
export class ApexGrid extends EventEmitterBase {
|
|
47
63
|
constructor() {
|
|
48
64
|
super(...arguments);
|
|
@@ -55,21 +71,27 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
55
71
|
});
|
|
56
72
|
this.dataState = [];
|
|
57
73
|
this.columns = [];
|
|
58
|
-
this
|
|
74
|
+
this.#data = [];
|
|
59
75
|
this.autoGenerate = false;
|
|
60
76
|
this.sortConfiguration = {
|
|
61
77
|
multiple: true,
|
|
62
78
|
triState: true,
|
|
63
79
|
};
|
|
64
|
-
this
|
|
80
|
+
this.#quickFilter = '';
|
|
81
|
+
this.#quickFilterWrites = 0;
|
|
65
82
|
this.showQuickFilter = false;
|
|
66
83
|
this.showExport = false;
|
|
67
84
|
this.columnReordering = false;
|
|
68
85
|
this.columnSeparator = true;
|
|
69
86
|
this.coordinateHints = false;
|
|
70
87
|
this.columnMenu = true;
|
|
88
|
+
this.#viewChangePending = false;
|
|
89
|
+
this.#shapingPending = false;
|
|
90
|
+
this.#pipelineRun = 0;
|
|
91
|
+
this.#pipelineSettled = Promise.resolve();
|
|
92
|
+
this.#settlePipeline = null;
|
|
71
93
|
this.#autoAriaLabel = null;
|
|
72
|
-
this.#stateChangedListeners =
|
|
94
|
+
this.#stateChangedListeners = new Map();
|
|
73
95
|
this.#lastStateJson = null;
|
|
74
96
|
}
|
|
75
97
|
static get tagName() {
|
|
@@ -82,6 +104,24 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
82
104
|
createStateController() {
|
|
83
105
|
return new StateController(this);
|
|
84
106
|
}
|
|
107
|
+
#data;
|
|
108
|
+
get data() {
|
|
109
|
+
return this.#data;
|
|
110
|
+
}
|
|
111
|
+
set data(value) {
|
|
112
|
+
if (value == null && !this.#data.length)
|
|
113
|
+
return;
|
|
114
|
+
this.#data = value ?? [];
|
|
115
|
+
}
|
|
116
|
+
get quickFilter() {
|
|
117
|
+
return this.#quickFilter;
|
|
118
|
+
}
|
|
119
|
+
set quickFilter(value) {
|
|
120
|
+
this.#quickFilter = value;
|
|
121
|
+
this.#quickFilterWrites++;
|
|
122
|
+
}
|
|
123
|
+
#quickFilter;
|
|
124
|
+
#quickFilterWrites;
|
|
85
125
|
localize(key, params, fallback) {
|
|
86
126
|
return resolveLocaleText(this.localeText, key, params, fallback);
|
|
87
127
|
}
|
|
@@ -160,26 +200,50 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
160
200
|
isTreeRowExpanded(row) {
|
|
161
201
|
return this.stateController.tree.isExpanded(row);
|
|
162
202
|
}
|
|
203
|
+
#pendingSort;
|
|
204
|
+
#pendingFilter;
|
|
163
205
|
set sortExpressions(expressions) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
206
|
+
this.#pendingSort = [...(expressions ?? [])];
|
|
207
|
+
this.requestUpdate(PIPELINE);
|
|
167
208
|
}
|
|
168
209
|
get sortExpressions() {
|
|
210
|
+
if (this.#pendingSort)
|
|
211
|
+
return [...this.#pendingSort];
|
|
169
212
|
return Array.from(this.stateController.sorting.state.values());
|
|
170
213
|
}
|
|
171
214
|
set filterExpressions(expressions) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
215
|
+
this.#pendingFilter = [...(expressions ?? [])];
|
|
216
|
+
this.#markViewChange();
|
|
175
217
|
}
|
|
176
218
|
get filterExpressions() {
|
|
219
|
+
if (this.#pendingFilter) {
|
|
220
|
+
return this.#pendingFilter.map((each) => {
|
|
221
|
+
const resolved = this.#resolveFilter(each);
|
|
222
|
+
return typeof resolved === 'string' ? each : resolved;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
177
225
|
const expressions = [];
|
|
178
226
|
for (const each of this.stateController.filtering.state.values) {
|
|
179
227
|
expressions.push(...each.all);
|
|
180
228
|
}
|
|
181
229
|
return expressions;
|
|
182
230
|
}
|
|
231
|
+
#resolveFilter(each) {
|
|
232
|
+
const column = each ? this.getColumn(each.key) : undefined;
|
|
233
|
+
if (!column)
|
|
234
|
+
return `filter: unknown column "${String(each?.key)}"; dropped`;
|
|
235
|
+
const defaults = this.stateController.filtering.getDefaultExpression(column);
|
|
236
|
+
const named = each.condition ?? defaults.condition;
|
|
237
|
+
const condition = typeof named === 'string' ? resolveFilterOperand(column, named) : named;
|
|
238
|
+
if (!condition)
|
|
239
|
+
return `filter: unknown operand "${named}" for "${String(each.key)}"; dropped`;
|
|
240
|
+
return {
|
|
241
|
+
...defaults,
|
|
242
|
+
...each,
|
|
243
|
+
condition,
|
|
244
|
+
criteria: each.criteria ?? 'and',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
183
247
|
get rows() {
|
|
184
248
|
return Array.from(this._rows);
|
|
185
249
|
}
|
|
@@ -197,7 +261,7 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
197
261
|
}
|
|
198
262
|
get pageItems() {
|
|
199
263
|
const rowPin = this.stateController.rowPin;
|
|
200
|
-
const base = rowPin?.
|
|
264
|
+
const base = rowPin?.hasPinnedRows
|
|
201
265
|
? this.dataState.filter((row) => !rowPin.isPinned(row))
|
|
202
266
|
: this.dataState;
|
|
203
267
|
if (!this.stateController.pagination.enabled)
|
|
@@ -214,7 +278,33 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
214
278
|
return this.stateController.pagination.page;
|
|
215
279
|
}
|
|
216
280
|
set page(value) {
|
|
217
|
-
this
|
|
281
|
+
if (this.#viewStale) {
|
|
282
|
+
this.stateController.pagination.page = Number.isFinite(value)
|
|
283
|
+
? Math.max(0, Math.trunc(value))
|
|
284
|
+
: 0;
|
|
285
|
+
this.requestUpdate(PIPELINE);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
this.stateController.pagination.gotoPage(value);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
#viewChangePending;
|
|
292
|
+
#shapingPending;
|
|
293
|
+
get #viewStale() {
|
|
294
|
+
return !this.hasUpdated || this.#viewChangePending || this.#shapingPending;
|
|
295
|
+
}
|
|
296
|
+
#markViewChange() {
|
|
297
|
+
this.#viewChangePending = true;
|
|
298
|
+
this.requestUpdate(PIPELINE);
|
|
299
|
+
}
|
|
300
|
+
requestUpdate(name, oldValue, options, useNewValue, newValue) {
|
|
301
|
+
const fields = name === undefined ? undefined : VIEW_SHAPING_PROPERTIES.get(name);
|
|
302
|
+
if (this.hasUpdated &&
|
|
303
|
+
fields !== undefined &&
|
|
304
|
+
viewShapeChanged(fields, oldValue, useNewValue ? newValue : this[name])) {
|
|
305
|
+
this.#viewChangePending = true;
|
|
306
|
+
}
|
|
307
|
+
super.requestUpdate(name, oldValue, options, useNewValue, newValue);
|
|
218
308
|
}
|
|
219
309
|
get pageSize() {
|
|
220
310
|
return this.stateController.pagination.pageSize;
|
|
@@ -228,21 +318,31 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
228
318
|
watchColumns(_, newConfig = []) {
|
|
229
319
|
this.columns = newConfig.map((config) => ({ ...DEFAULT_COLUMN_CONFIG, ...config }));
|
|
230
320
|
}
|
|
321
|
+
historyRecords() {
|
|
322
|
+
return this.data;
|
|
323
|
+
}
|
|
231
324
|
dataChanged() {
|
|
232
325
|
let next = [...this.data];
|
|
233
326
|
autoGenerateColumns(this);
|
|
327
|
+
this.stateController?.history?.reconcile(() => this.historyRecords());
|
|
234
328
|
this.stateController?.tree?.resetForDataChange();
|
|
329
|
+
const carry = (rows) => this.#carryRows(rows);
|
|
330
|
+
this.stateController?.rowReorder.resetForDataChange(carry);
|
|
331
|
+
this.stateController?.rowPin.resetForDataChange(carry);
|
|
235
332
|
if (this.stateController?.tree?.enabled) {
|
|
236
333
|
next = this.stateController.tree.process(next);
|
|
237
334
|
}
|
|
238
335
|
this.dataState = this.stateController ? this.stateController.applyModuleTransforms(next) : next;
|
|
239
|
-
if (this.hasUpdated)
|
|
240
|
-
this.
|
|
241
|
-
}
|
|
336
|
+
if (this.hasUpdated)
|
|
337
|
+
this.requestUpdate(PIPELINE);
|
|
242
338
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
this.
|
|
339
|
+
#carryRows(rows) {
|
|
340
|
+
const rowId = this.rowId;
|
|
341
|
+
const records = [...this.historyRecords()];
|
|
342
|
+
if (rowId)
|
|
343
|
+
return resolveRowRefs(serializeRowRefs(rows, [], rowId), records, rowId);
|
|
344
|
+
const present = new Set(records);
|
|
345
|
+
return rows.filter((row) => present.has(row));
|
|
246
346
|
}
|
|
247
347
|
paginationChanged() {
|
|
248
348
|
const ctrl = this.stateController?.pagination;
|
|
@@ -255,16 +355,103 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
255
355
|
if (typeof next.page === 'number') {
|
|
256
356
|
ctrl.page = next.page;
|
|
257
357
|
}
|
|
258
|
-
if (this.hasUpdated)
|
|
259
|
-
this.
|
|
260
|
-
}
|
|
358
|
+
if (this.hasUpdated)
|
|
359
|
+
this.requestUpdate(PIPELINE);
|
|
261
360
|
}
|
|
262
361
|
quickFilterChanged() {
|
|
263
|
-
if (this.hasUpdated)
|
|
264
|
-
this.
|
|
362
|
+
if (this.hasUpdated || this.quickFilter)
|
|
363
|
+
this.requestUpdate(PIPELINE);
|
|
364
|
+
}
|
|
365
|
+
treeChanged(previous) {
|
|
366
|
+
if (!viewShapeChanged(VIEW_SHAPING_PROPERTIES.get('tree'), previous, this.tree))
|
|
367
|
+
return;
|
|
368
|
+
this.stateController.tree.resetForDataChange();
|
|
369
|
+
this.requestUpdate(PIPELINE);
|
|
370
|
+
}
|
|
371
|
+
#pipelineRun;
|
|
372
|
+
#pipelineSettled;
|
|
373
|
+
#settlePipeline;
|
|
374
|
+
pipeline() {
|
|
375
|
+
if (!this.#settlePipeline) {
|
|
376
|
+
this.#pipelineSettled = new Promise((settle) => {
|
|
377
|
+
this.#settlePipeline = settle;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return this.#runPipeline(++this.#pipelineRun);
|
|
381
|
+
}
|
|
382
|
+
async #runPipeline(run) {
|
|
383
|
+
try {
|
|
384
|
+
this.#flushPendingExpressions();
|
|
385
|
+
if (this.#viewChangePending || !this.hasUpdated)
|
|
386
|
+
this.#shapingPending = true;
|
|
387
|
+
this.#viewChangePending = false;
|
|
388
|
+
const next = await this.dataController.apply([...this.data], this.stateController);
|
|
389
|
+
if (run !== this.#pipelineRun)
|
|
390
|
+
return;
|
|
391
|
+
this.dataState = next;
|
|
392
|
+
this.stateController.dataVersion++;
|
|
393
|
+
this.stateController.pagination.reclamp();
|
|
394
|
+
}
|
|
395
|
+
finally {
|
|
396
|
+
if (run === this.#pipelineRun) {
|
|
397
|
+
this.#settlePipeline?.();
|
|
398
|
+
this.#settlePipeline = null;
|
|
399
|
+
if (this.#shapingPending) {
|
|
400
|
+
this.#shapingPending = false;
|
|
401
|
+
this.#baselineStateIfUnset();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
265
404
|
}
|
|
266
405
|
}
|
|
406
|
+
async pipelineSettled() {
|
|
407
|
+
await this.updateComplete;
|
|
408
|
+
let settled;
|
|
409
|
+
do {
|
|
410
|
+
settled = this.#pipelineSettled;
|
|
411
|
+
await settled;
|
|
412
|
+
await this.updateComplete;
|
|
413
|
+
} while (settled !== this.#pipelineSettled);
|
|
414
|
+
}
|
|
415
|
+
#flushPendingExpressions() {
|
|
416
|
+
const sort = this.#pendingSort;
|
|
417
|
+
const filter = this.#pendingFilter;
|
|
418
|
+
this.#pendingSort = undefined;
|
|
419
|
+
this.#pendingFilter = undefined;
|
|
420
|
+
if (sort)
|
|
421
|
+
this.#replaceSort(sort, warnDropped);
|
|
422
|
+
if (filter)
|
|
423
|
+
this.#replaceFilter(filter);
|
|
424
|
+
}
|
|
425
|
+
#replaceSort(entries, warn) {
|
|
426
|
+
this.stateController.sorting.reset();
|
|
427
|
+
const valid = [];
|
|
428
|
+
for (const entry of entries) {
|
|
429
|
+
const { direction, ...rest } = (entry ?? {});
|
|
430
|
+
if (typeof entry !== 'object' || entry === null || !this.getColumn(rest.key)) {
|
|
431
|
+
warn(`sort: unknown column "${String(rest.key)}"; dropped`);
|
|
432
|
+
}
|
|
433
|
+
else if (direction === undefined) {
|
|
434
|
+
valid.push(rest);
|
|
435
|
+
}
|
|
436
|
+
else if (SORT_DIRECTIONS.includes(direction)) {
|
|
437
|
+
valid.push(entry);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
warn(`sort: invalid direction "${String(direction)}"; dropped`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (valid.length)
|
|
444
|
+
this.sort(valid);
|
|
445
|
+
this.requestUpdate(PIPELINE);
|
|
446
|
+
}
|
|
447
|
+
#replaceFilter(entries) {
|
|
448
|
+
this.#markViewChange();
|
|
449
|
+
this.stateController.filtering.reset();
|
|
450
|
+
if (entries.length)
|
|
451
|
+
this.filter([...entries]);
|
|
452
|
+
}
|
|
267
453
|
updated() {
|
|
454
|
+
this.#viewChangePending = false;
|
|
268
455
|
this.setAttribute('role', this.stateController.tree.enabled ? 'treegrid' : 'grid');
|
|
269
456
|
const pinned = this.stateController.rowPin.pinnedRows;
|
|
270
457
|
this.setAttribute('aria-rowcount', String(this.headerRowCount + pinned.top.length + this.pageItems.length + pinned.bottom.length));
|
|
@@ -279,9 +466,7 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
279
466
|
else {
|
|
280
467
|
this.removeAttribute('aria-multiselectable');
|
|
281
468
|
}
|
|
282
|
-
|
|
283
|
-
this.scrollContainer.tabIndex = this.stateController.active === SENTINEL_NODE ? 0 : -1;
|
|
284
|
-
}
|
|
469
|
+
this.#syncBodyTabStop();
|
|
285
470
|
const currentLabel = this.getAttribute('aria-label');
|
|
286
471
|
const authorLabeled = this.hasAttribute('aria-labelledby') ||
|
|
287
472
|
(currentLabel !== null && currentLabel !== this.#autoAriaLabel);
|
|
@@ -297,21 +482,54 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
297
482
|
#stateChangedListeners;
|
|
298
483
|
#lastStateJson;
|
|
299
484
|
addEventListener(type, listener, options) {
|
|
300
|
-
if (type === 'stateChanged')
|
|
301
|
-
this.#
|
|
302
|
-
this.#lastStateJson = this.hasUpdated ? JSON.stringify(this.getState()) : null;
|
|
303
|
-
}
|
|
485
|
+
if (type === 'stateChanged' && listener)
|
|
486
|
+
this.#trackStateChangedListener(listener, options);
|
|
304
487
|
super.addEventListener(type, listener, options);
|
|
305
488
|
}
|
|
489
|
+
#trackStateChangedListener(listener, options) {
|
|
490
|
+
const capture = typeof options === 'boolean' ? options : Boolean(options?.capture);
|
|
491
|
+
const captures = this.#stateChangedListeners.get(listener) ?? new Map();
|
|
492
|
+
if (!captures.has(capture) && !options?.signal?.aborted) {
|
|
493
|
+
if (!this.#stateChangedListeners.size) {
|
|
494
|
+
this.#lastStateJson = this.hasUpdated ? JSON.stringify(this.getState()) : null;
|
|
495
|
+
}
|
|
496
|
+
const token = {};
|
|
497
|
+
captures.set(capture, token);
|
|
498
|
+
this.#stateChangedListeners.set(listener, captures);
|
|
499
|
+
const forget = () => this.#forgetStateChangedListener(listener, capture, token);
|
|
500
|
+
if (typeof options === 'object') {
|
|
501
|
+
if (options.once) {
|
|
502
|
+
HTMLElement.prototype.addEventListener.call(this, 'stateChanged', forget, {
|
|
503
|
+
capture,
|
|
504
|
+
once: true,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
options.signal?.addEventListener('abort', forget, { once: true });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
#forgetStateChangedListener(listener, capture, token) {
|
|
512
|
+
const captures = this.#stateChangedListeners.get(listener);
|
|
513
|
+
if (!captures || (token && captures.get(capture) !== token))
|
|
514
|
+
return;
|
|
515
|
+
captures.delete(capture);
|
|
516
|
+
if (!captures.size)
|
|
517
|
+
this.#stateChangedListeners.delete(listener);
|
|
518
|
+
}
|
|
306
519
|
removeEventListener(type, listener, options) {
|
|
307
|
-
if (type === 'stateChanged') {
|
|
308
|
-
|
|
520
|
+
if (type === 'stateChanged' && listener) {
|
|
521
|
+
const capture = typeof options === 'boolean' ? options : Boolean(options?.capture);
|
|
522
|
+
this.#forgetStateChangedListener(listener, capture);
|
|
309
523
|
}
|
|
310
524
|
super.removeEventListener(type, listener, options);
|
|
311
525
|
}
|
|
312
526
|
#emitStateChangedIfNeeded() {
|
|
313
|
-
if (this.#stateChangedListeners
|
|
527
|
+
if (!this.#stateChangedListeners.size)
|
|
528
|
+
return;
|
|
529
|
+
if (this.#lastStateJson === null) {
|
|
530
|
+
this.#baselineStateIfUnset();
|
|
314
531
|
return;
|
|
532
|
+
}
|
|
315
533
|
const snapshot = this.getState();
|
|
316
534
|
const json = JSON.stringify(snapshot);
|
|
317
535
|
if (json === this.#lastStateJson)
|
|
@@ -319,6 +537,15 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
319
537
|
this.#lastStateJson = json;
|
|
320
538
|
this.emitEvent('stateChanged', { detail: { state: snapshot } });
|
|
321
539
|
}
|
|
540
|
+
#baselineStateIfUnset() {
|
|
541
|
+
if (this.#lastStateJson === null && this.#stateChangedListeners.size && !this.#shapingPending) {
|
|
542
|
+
this.#lastStateJson = JSON.stringify(this.getState());
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
connectedCallback() {
|
|
546
|
+
super.connectedCallback();
|
|
547
|
+
adoptGridDocumentStyles(this);
|
|
548
|
+
}
|
|
322
549
|
firstUpdated() {
|
|
323
550
|
if (typeof getComputedStyle === 'function' && getComputedStyle(this).display !== 'grid') {
|
|
324
551
|
console.warn('[apex-grid] Host `display` has been overridden. The grid requires ' +
|
|
@@ -328,26 +555,44 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
328
555
|
}
|
|
329
556
|
}
|
|
330
557
|
filter(config) {
|
|
331
|
-
this
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
558
|
+
if (this.#pendingFilter) {
|
|
559
|
+
const added = asArray(config);
|
|
560
|
+
this.#pendingFilter.push(...added);
|
|
561
|
+
if (added.length)
|
|
562
|
+
this.#markViewChange();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const expressions = [];
|
|
566
|
+
for (const each of asArray(config)) {
|
|
567
|
+
const resolved = this.#resolveFilter(each);
|
|
568
|
+
if (typeof resolved === 'string')
|
|
569
|
+
warnDropped(resolved);
|
|
570
|
+
else
|
|
571
|
+
expressions.push(resolved);
|
|
572
|
+
}
|
|
573
|
+
if (!expressions.length)
|
|
574
|
+
return;
|
|
575
|
+
this.#markViewChange();
|
|
576
|
+
this.stateController.filtering.filter(expressions);
|
|
337
577
|
}
|
|
338
578
|
sort(expressions) {
|
|
579
|
+
if (this.#pendingSort) {
|
|
580
|
+
this.#pendingSort.push(...asArray(expressions));
|
|
581
|
+
this.requestUpdate(PIPELINE);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
339
584
|
this.stateController.sorting.sort(expressions);
|
|
340
585
|
}
|
|
341
586
|
getState(options) {
|
|
342
587
|
const rowId = options?.rowId ?? this.rowId;
|
|
343
|
-
const pinned = this.stateController.rowPin.
|
|
588
|
+
const pinned = this.stateController.rowPin.pins;
|
|
344
589
|
const manualOrder = this.stateController.rowReorder.getManualOrder();
|
|
345
590
|
return {
|
|
346
591
|
version: 1,
|
|
347
592
|
columns: serializeColumnLayout(this.columns),
|
|
348
593
|
sort: serializeSort(this.sortExpressions),
|
|
349
594
|
filter: serializeFilter(this.filterExpressions),
|
|
350
|
-
quickFilter: this.quickFilter,
|
|
595
|
+
quickFilter: this.quickFilter ?? '',
|
|
351
596
|
pagination: { page: this.page, pageSize: this.pageSize },
|
|
352
597
|
selection: serializeRowRefs(this.selectedRows, this.data, rowId),
|
|
353
598
|
expansion: serializeRowRefs(this.expandedRows, this.data, rowId),
|
|
@@ -367,7 +612,7 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
367
612
|
columns: this.columns.map((column) => columnSchema(column)),
|
|
368
613
|
capabilities: {
|
|
369
614
|
sort: {
|
|
370
|
-
directions:
|
|
615
|
+
directions: SORT_DIRECTIONS.filter((direction) => direction !== 'none'),
|
|
371
616
|
multi: Boolean(this.sortConfiguration?.multiple),
|
|
372
617
|
},
|
|
373
618
|
filter: { operandsByType: operandsByType(this.columns) },
|
|
@@ -384,6 +629,7 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
384
629
|
const strict = options?.strict ?? false;
|
|
385
630
|
const applied = [];
|
|
386
631
|
const warnings = [];
|
|
632
|
+
const viewSettled = !this.#viewStale;
|
|
387
633
|
const warn = (message) => {
|
|
388
634
|
if (strict)
|
|
389
635
|
throw new Error(`[apex-grid] setState: ${message}`);
|
|
@@ -410,31 +656,23 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
410
656
|
}
|
|
411
657
|
const columns = asArray(state.columns, 'columns');
|
|
412
658
|
if (columns) {
|
|
659
|
+
const unkeyed = columns.filter((entry) => typeof entry?.key !== 'string').length;
|
|
660
|
+
if (unkeyed)
|
|
661
|
+
warn(`columns: ${unkeyed} entr${unkeyed === 1 ? 'y' : 'ies'} without a key; dropped`);
|
|
413
662
|
this.columns = applyColumnLayout(this.columns, columns);
|
|
414
663
|
applied.push('columns');
|
|
415
664
|
}
|
|
416
665
|
const sort = asArray(state.sort, 'sort');
|
|
417
666
|
if (sort) {
|
|
418
|
-
this
|
|
419
|
-
|
|
420
|
-
for (const entry of sort) {
|
|
421
|
-
if (this.getColumn(entry.key)) {
|
|
422
|
-
valid.push(entry);
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
warn(`sort: unknown column "${entry.key}"; dropped`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (valid.length)
|
|
429
|
-
this.sort(valid);
|
|
667
|
+
this.#pendingSort = undefined;
|
|
668
|
+
this.#replaceSort(sort, warn);
|
|
430
669
|
applied.push('sort');
|
|
431
670
|
}
|
|
432
671
|
const filter = asArray(state.filter, 'filter');
|
|
433
672
|
if (filter) {
|
|
434
|
-
this
|
|
673
|
+
this.#pendingFilter = undefined;
|
|
435
674
|
const expressions = deserializeFilter(filter, (key) => this.getColumn(key), (snapshot, reason) => warn(`filter: "${snapshot.key}" dropped (${reason})`));
|
|
436
|
-
|
|
437
|
-
this.filter(expressions);
|
|
675
|
+
this.#replaceFilter(expressions);
|
|
438
676
|
applied.push('filter');
|
|
439
677
|
}
|
|
440
678
|
if (state.quickFilter !== undefined) {
|
|
@@ -449,6 +687,7 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
449
687
|
if (state.modules !== undefined) {
|
|
450
688
|
if (state.modules && typeof state.modules === 'object') {
|
|
451
689
|
this.stateController.restoreModuleState(state.modules);
|
|
690
|
+
this.#markViewChange();
|
|
452
691
|
applied.push('modules');
|
|
453
692
|
}
|
|
454
693
|
else {
|
|
@@ -476,13 +715,22 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
476
715
|
if (state.treeExpanded !== undefined || state.treeExpandedKeys !== undefined) {
|
|
477
716
|
const refs = asArray(state.treeExpanded, 'treeExpanded') ?? [];
|
|
478
717
|
const keys = asArray(state.treeExpandedKeys, 'treeExpandedKeys') ?? [];
|
|
479
|
-
this.stateController.tree.restoreExpansion(resolveCounted(refs, 'treeExpanded'), keys);
|
|
718
|
+
this.stateController.tree.restoreExpansion(resolveCounted(refs, 'treeExpanded'), keys.filter((key) => typeof key === 'string'));
|
|
719
|
+
this.#markViewChange();
|
|
480
720
|
applied.push('tree');
|
|
481
721
|
}
|
|
482
722
|
const expansion = asArray(state.expansion, 'expansion');
|
|
483
723
|
if (expansion) {
|
|
484
|
-
|
|
485
|
-
|
|
724
|
+
const ctrl = this.stateController.expansion;
|
|
725
|
+
const predicate = this.expansion?.isExpandable;
|
|
726
|
+
const rows = resolveCounted(expansion, 'expansion');
|
|
727
|
+
const expected = predicate ? rows.filter((row) => predicate(row)) : rows;
|
|
728
|
+
if (this.#replaceRows('expansion', ctrl.enabled, expected, warn, () => {
|
|
729
|
+
void ctrl.replaceExpansion(expected);
|
|
730
|
+
return ctrl.expandedRows();
|
|
731
|
+
})) {
|
|
732
|
+
applied.push('expansion');
|
|
733
|
+
}
|
|
486
734
|
}
|
|
487
735
|
if (state.rowPinning !== undefined) {
|
|
488
736
|
const pinning = state.rowPinning;
|
|
@@ -496,8 +744,15 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
496
744
|
}
|
|
497
745
|
const selection = asArray(state.selection, 'selection');
|
|
498
746
|
if (selection) {
|
|
499
|
-
|
|
500
|
-
|
|
747
|
+
const ctrl = this.stateController.selection;
|
|
748
|
+
const rows = resolveCounted(selection, 'selection');
|
|
749
|
+
const expected = ctrl.mode === 'single' ? rows.slice(0, 1) : rows;
|
|
750
|
+
if (this.#replaceRows('selection', ctrl.enabled, expected, warn, () => {
|
|
751
|
+
void ctrl.replaceSelection(expected);
|
|
752
|
+
return ctrl.selectedRows();
|
|
753
|
+
})) {
|
|
754
|
+
applied.push('selection');
|
|
755
|
+
}
|
|
501
756
|
}
|
|
502
757
|
if (state.pagination !== undefined) {
|
|
503
758
|
const pagination = state.pagination;
|
|
@@ -514,10 +769,13 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
514
769
|
if (page !== undefined) {
|
|
515
770
|
if (typeof page === 'number' && Number.isFinite(page)) {
|
|
516
771
|
const requested = Math.trunc(page);
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
772
|
+
const reshaped = applied.some((slice) => VIEW_SHAPING_SLICES.has(slice));
|
|
773
|
+
const ctrl = this.stateController.pagination;
|
|
774
|
+
const next = viewSettled && !reshaped ? ctrl.clamp(requested) : Math.max(0, requested);
|
|
775
|
+
if (next !== requested) {
|
|
776
|
+
warn(`pagination: page ${requested} out of range; clamped to ${next}`);
|
|
520
777
|
}
|
|
778
|
+
ctrl.page = next;
|
|
521
779
|
}
|
|
522
780
|
else {
|
|
523
781
|
warn(`pagination: invalid page ${String(page)}; ignored`);
|
|
@@ -533,13 +791,37 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
533
791
|
const skipped = SET_STATE_SLICES.filter((slice) => !applied.includes(slice));
|
|
534
792
|
return { applied, skipped, warnings };
|
|
535
793
|
}
|
|
794
|
+
#replaceRows(label, enabled, expected, warn, replace) {
|
|
795
|
+
if (!enabled) {
|
|
796
|
+
if (expected.length)
|
|
797
|
+
warn(`${label}: the feature is disabled on this grid; skipped`);
|
|
798
|
+
return !expected.length;
|
|
799
|
+
}
|
|
800
|
+
const actual = new Set(replace());
|
|
801
|
+
const took = actual.size === new Set(expected).size && expected.every((row) => actual.has(row));
|
|
802
|
+
if (!took)
|
|
803
|
+
warn(`${label}: the change was cancelled by a listener; skipped`);
|
|
804
|
+
return took;
|
|
805
|
+
}
|
|
536
806
|
clearSort(key) {
|
|
807
|
+
if (this.#pendingSort && key !== undefined) {
|
|
808
|
+
this.#pendingSort = this.#pendingSort.filter((each) => each.key !== key);
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
this.#pendingSort = undefined;
|
|
812
|
+
}
|
|
537
813
|
this.stateController.sorting.reset(key);
|
|
538
814
|
this.requestUpdate(PIPELINE);
|
|
539
815
|
}
|
|
540
816
|
clearFilter(key) {
|
|
817
|
+
if (this.#pendingFilter && key !== undefined) {
|
|
818
|
+
this.#pendingFilter = this.#pendingFilter.filter((each) => each.key !== key);
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
this.#pendingFilter = undefined;
|
|
822
|
+
}
|
|
823
|
+
this.#markViewChange();
|
|
541
824
|
this.stateController.filtering.reset(key);
|
|
542
|
-
this.requestUpdate(PIPELINE);
|
|
543
825
|
}
|
|
544
826
|
gotoPage(page) {
|
|
545
827
|
return this.stateController.pagination.gotoPage(page);
|
|
@@ -563,15 +845,21 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
563
845
|
const next = value ?? '';
|
|
564
846
|
if (next === this.quickFilter)
|
|
565
847
|
return false;
|
|
848
|
+
const previous = this.quickFilter;
|
|
849
|
+
const writes = this.#quickFilterWrites;
|
|
566
850
|
const proceed = this.emitEvent('quickFilterChanging', {
|
|
567
|
-
detail: { value:
|
|
851
|
+
detail: { value: previous, nextValue: next },
|
|
568
852
|
cancelable: true,
|
|
569
853
|
});
|
|
570
854
|
if (!proceed)
|
|
571
855
|
return false;
|
|
572
|
-
this
|
|
573
|
-
|
|
574
|
-
|
|
856
|
+
if (this.#quickFilterWrites === writes)
|
|
857
|
+
this.quickFilter = next;
|
|
858
|
+
if (this.quickFilter === previous)
|
|
859
|
+
return false;
|
|
860
|
+
const pageReset = this.stateController.pagination.resetPage();
|
|
861
|
+
await this.pipelineSettled();
|
|
862
|
+
await pageReset;
|
|
575
863
|
this.emitEvent('quickFilterChanged', { detail: { value: this.quickFilter } });
|
|
576
864
|
return true;
|
|
577
865
|
}
|
|
@@ -643,16 +931,22 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
643
931
|
return tally;
|
|
644
932
|
const editing = this.stateController.editing;
|
|
645
933
|
const items = this.pageItems;
|
|
934
|
+
const indexOf = viewIndexer(items, edits.length);
|
|
935
|
+
const version = editing.validationVersion;
|
|
646
936
|
this.stateController.history.beginBatch();
|
|
647
937
|
try {
|
|
648
|
-
for (const { rowIndex, column, value } of edits) {
|
|
649
|
-
const record = items
|
|
938
|
+
for (const { row, rowIndex, column, value } of edits) {
|
|
939
|
+
const record = row ?? items[rowIndex];
|
|
650
940
|
const config = this.getColumn(column);
|
|
651
|
-
if (!record ||
|
|
941
|
+
if (!record ||
|
|
942
|
+
!config ||
|
|
943
|
+
!editing.isEditable(config) ||
|
|
944
|
+
!this.stateController.isDataRow(record)) {
|
|
652
945
|
tally.skipped += 1;
|
|
653
946
|
continue;
|
|
654
947
|
}
|
|
655
|
-
|
|
948
|
+
const index = row ? indexOf(row) : rowIndex;
|
|
949
|
+
tally[editing.applyCellEdit(index, column, record, value)] += 1;
|
|
656
950
|
}
|
|
657
951
|
}
|
|
658
952
|
finally {
|
|
@@ -660,6 +954,8 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
660
954
|
}
|
|
661
955
|
if (tally.applied > 0)
|
|
662
956
|
this.requestUpdate(PIPELINE);
|
|
957
|
+
else if (editing.validationVersion !== version)
|
|
958
|
+
this.requestUpdate();
|
|
663
959
|
return tally;
|
|
664
960
|
}
|
|
665
961
|
cancelEdit() {
|
|
@@ -731,22 +1027,20 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
731
1027
|
const target = this.columns[toIndex];
|
|
732
1028
|
if ((source.pinned ?? null) !== (target.pinned ?? null))
|
|
733
1029
|
return false;
|
|
734
|
-
const proceed = this.emitEvent('columnMoving', {
|
|
735
|
-
detail: { key: fromKey, fromIndex, toKey, position },
|
|
736
|
-
cancelable: true,
|
|
737
|
-
});
|
|
738
|
-
if (!proceed)
|
|
739
|
-
return false;
|
|
740
1030
|
const next = this.columns.slice();
|
|
741
1031
|
const [moved] = next.splice(fromIndex, 1);
|
|
742
1032
|
let insertIndex = next.findIndex((column) => column.key === toKey);
|
|
743
|
-
if (insertIndex < 0)
|
|
744
|
-
return false;
|
|
745
1033
|
if (position === 'after')
|
|
746
1034
|
insertIndex += 1;
|
|
747
1035
|
next.splice(insertIndex, 0, moved);
|
|
748
1036
|
if (next.every((column, index) => column === this.columns[index]))
|
|
749
1037
|
return false;
|
|
1038
|
+
const proceed = this.emitEvent('columnMoving', {
|
|
1039
|
+
detail: { key: fromKey, fromIndex, toKey, position },
|
|
1040
|
+
cancelable: true,
|
|
1041
|
+
});
|
|
1042
|
+
if (!proceed)
|
|
1043
|
+
return false;
|
|
750
1044
|
this.columns = next;
|
|
751
1045
|
await this.updateComplete;
|
|
752
1046
|
const resolvedIndex = this.columns.findIndex((column) => column.key === fromKey);
|
|
@@ -755,8 +1049,42 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
755
1049
|
});
|
|
756
1050
|
return true;
|
|
757
1051
|
}
|
|
1052
|
+
ownsEvent(event) {
|
|
1053
|
+
for (const node of event.composedPath()) {
|
|
1054
|
+
if (node === this || node === this.scrollContainer)
|
|
1055
|
+
return true;
|
|
1056
|
+
if (node instanceof ApexGrid || node instanceof ApexVirtualizer)
|
|
1057
|
+
return false;
|
|
1058
|
+
}
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
#syncBodyTabStop() {
|
|
1062
|
+
const body = this.scrollContainer;
|
|
1063
|
+
if (!body)
|
|
1064
|
+
return;
|
|
1065
|
+
const active = this.stateController.active;
|
|
1066
|
+
body.tabIndex = active !== SENTINEL_NODE && body.isRendered(active.row) ? -1 : 0;
|
|
1067
|
+
}
|
|
1068
|
+
bodyRangeChanged(event) {
|
|
1069
|
+
const { first, last } = event;
|
|
1070
|
+
this.#syncBodyTabStop();
|
|
1071
|
+
const focused = this.renderRoot instanceof ShadowRoot ? this.renderRoot.activeElement : null;
|
|
1072
|
+
if (focused instanceof ApexGridRow &&
|
|
1073
|
+
focused.parentElement === this.scrollContainer &&
|
|
1074
|
+
(focused.index < first || focused.index > last)) {
|
|
1075
|
+
this.scrollContainer.focus({ preventScroll: true });
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
#ownCell(event) {
|
|
1079
|
+
if (!this.ownsEvent(event))
|
|
1080
|
+
return undefined;
|
|
1081
|
+
return event.composedPath().find((el) => el instanceof ApexGridCell);
|
|
1082
|
+
}
|
|
758
1083
|
bodyClickHandler(event) {
|
|
759
|
-
|
|
1084
|
+
if (!this.ownsEvent(event))
|
|
1085
|
+
return;
|
|
1086
|
+
const path = event.composedPath();
|
|
1087
|
+
const target = path.find((el) => el instanceof ApexGridCell);
|
|
760
1088
|
if (target) {
|
|
761
1089
|
this.stateController.active = {
|
|
762
1090
|
column: target.column.key,
|
|
@@ -765,11 +1093,22 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
765
1093
|
if (!target.editing)
|
|
766
1094
|
target.focus({ preventScroll: true });
|
|
767
1095
|
}
|
|
1096
|
+
else {
|
|
1097
|
+
const row = path.find((el) => el instanceof ApexGridRow);
|
|
1098
|
+
const cell = row?.presentedCell;
|
|
1099
|
+
if (row && cell && path.includes(cell)) {
|
|
1100
|
+
const { column } = this.stateController.active;
|
|
1101
|
+
this.stateController.active = { column, row: row.index };
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
768
1104
|
}
|
|
769
1105
|
bodyKeydownHandler(event) {
|
|
1106
|
+
if (!this.ownsEvent(event))
|
|
1107
|
+
return;
|
|
770
1108
|
const origin = event.composedPath()[0];
|
|
771
1109
|
const fromBody = this.scrollContainer.isSameNode(event.target);
|
|
772
|
-
const fromCell = origin instanceof ApexGridCell && !origin.editing
|
|
1110
|
+
const fromCell = (origin instanceof ApexGridCell && !origin.editing) ||
|
|
1111
|
+
(event.target instanceof ApexGridRow && event.target.presentedCell === origin);
|
|
773
1112
|
if (fromBody || fromCell) {
|
|
774
1113
|
this.stateController.navigation.navigate(event);
|
|
775
1114
|
}
|
|
@@ -784,7 +1123,7 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
784
1123
|
return;
|
|
785
1124
|
if (event.type === 'pointermove' && event.buttons === 0)
|
|
786
1125
|
return;
|
|
787
|
-
const target = event
|
|
1126
|
+
const target = this.#ownCell(event);
|
|
788
1127
|
if (!target?.row)
|
|
789
1128
|
return;
|
|
790
1129
|
const kind = event.type === 'pointerdown' ? 'down' : event.type === 'pointerup' ? 'up' : 'over';
|
|
@@ -829,6 +1168,7 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
829
1168
|
@click=${this.bodyClickHandler}
|
|
830
1169
|
@keydown=${this.bodyKeydownHandler}
|
|
831
1170
|
@focusin=${this.bodyFocusHandler}
|
|
1171
|
+
@rangeChanged=${this.bodyRangeChanged}
|
|
832
1172
|
@pointerdown=${this.bodyPointerHandler}
|
|
833
1173
|
@pointermove=${this.bodyPointerHandler}
|
|
834
1174
|
@pointerup=${this.bodyPointerHandler}
|
|
@@ -840,8 +1180,6 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
840
1180
|
}
|
|
841
1181
|
renderPinnedTop() {
|
|
842
1182
|
const rowPin = this.stateController.rowPin;
|
|
843
|
-
if (!rowPin.enabled)
|
|
844
|
-
return nothing;
|
|
845
1183
|
const rows = rowPin.pinnedRows.top;
|
|
846
1184
|
if (rows.length === 0)
|
|
847
1185
|
return nothing;
|
|
@@ -851,8 +1189,6 @@ export class ApexGrid extends EventEmitterBase {
|
|
|
851
1189
|
}
|
|
852
1190
|
renderPinnedBottom() {
|
|
853
1191
|
const rowPin = this.stateController.rowPin;
|
|
854
|
-
if (!rowPin.enabled)
|
|
855
|
-
return nothing;
|
|
856
1192
|
const rows = rowPin.pinnedRows.bottom;
|
|
857
1193
|
if (rows.length === 0)
|
|
858
1194
|
return nothing;
|
|
@@ -973,7 +1309,7 @@ __decorate([
|
|
|
973
1309
|
], ApexGrid.prototype, "columns", void 0);
|
|
974
1310
|
__decorate([
|
|
975
1311
|
property({ attribute: false })
|
|
976
|
-
], ApexGrid.prototype, "data",
|
|
1312
|
+
], ApexGrid.prototype, "data", null);
|
|
977
1313
|
__decorate([
|
|
978
1314
|
property({ attribute: false })
|
|
979
1315
|
], ApexGrid.prototype, "rowId", void 0);
|
|
@@ -991,7 +1327,7 @@ __decorate([
|
|
|
991
1327
|
], ApexGrid.prototype, "pagination", void 0);
|
|
992
1328
|
__decorate([
|
|
993
1329
|
property({ type: String, attribute: 'quick-filter' })
|
|
994
|
-
], ApexGrid.prototype, "quickFilter",
|
|
1330
|
+
], ApexGrid.prototype, "quickFilter", null);
|
|
995
1331
|
__decorate([
|
|
996
1332
|
property({ type: Boolean, attribute: 'show-quick-filter' })
|
|
997
1333
|
], ApexGrid.prototype, "showQuickFilter", void 0);
|
|
@@ -1052,15 +1388,18 @@ __decorate([
|
|
|
1052
1388
|
__decorate([
|
|
1053
1389
|
watch('data')
|
|
1054
1390
|
], ApexGrid.prototype, "dataChanged", null);
|
|
1055
|
-
__decorate([
|
|
1056
|
-
watch(PIPELINE)
|
|
1057
|
-
], ApexGrid.prototype, "pipeline", null);
|
|
1058
1391
|
__decorate([
|
|
1059
1392
|
watch('pagination')
|
|
1060
1393
|
], ApexGrid.prototype, "paginationChanged", null);
|
|
1061
1394
|
__decorate([
|
|
1062
1395
|
watch('quickFilter')
|
|
1063
1396
|
], ApexGrid.prototype, "quickFilterChanged", null);
|
|
1397
|
+
__decorate([
|
|
1398
|
+
watch('tree', { waitUntilFirstUpdate: true })
|
|
1399
|
+
], ApexGrid.prototype, "treeChanged", null);
|
|
1400
|
+
__decorate([
|
|
1401
|
+
watch(PIPELINE)
|
|
1402
|
+
], ApexGrid.prototype, "pipeline", null);
|
|
1064
1403
|
__decorate([
|
|
1065
1404
|
eventOptions({ capture: true })
|
|
1066
1405
|
], ApexGrid.prototype, "bodyClickHandler", null);
|