@ixfx/components 0.15.5 → 0.16.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/bundle/index.d.ts +917 -44
- package/bundle/index.js +702 -696
- package/dist/crumb-navigation-BbqfmkYs.d.ts +147 -0
- package/dist/crumb-navigation-YnFVFqrx.js +1290 -0
- package/dist/crumbs.d.ts +2 -115
- package/dist/crumbs.js +1 -1179
- package/dist/{data-grid-D7qzmMMp.d.ts → data-grid-CIG2DVip.d.ts} +45 -3
- package/dist/{data-grid-C1sZX6n9.js → data-grid-rMkLX2qo.js} +593 -206
- package/dist/data-grid.d.ts +3 -3
- package/dist/data-grid.js +1 -1
- package/dist/{detail-list-CKIKlszv.d.ts → detail-list-6iMTNP4N.d.ts} +34 -4
- package/dist/{detail-list-q8bs67BM.js → detail-list-C4pDSoI8.js} +218 -7
- package/dist/detail-list.d.ts +4 -4
- package/dist/detail-list.js +1 -1
- package/dist/{flat-list-DXMyDiSH.d.ts → flat-list-JPgQSmLW.d.ts} +27 -4
- package/dist/flat-list.d.ts +2 -2
- package/dist/flat-list.js +123 -4
- package/dist/{grid-list-y6dWJYu3.js → grid-list-CdEkScn-.js} +276 -75
- package/dist/{grid-list-0e7snW6t.d.ts → grid-list-D6n7WH8l.d.ts} +128 -22
- package/dist/grid-list.d.ts +3 -3
- package/dist/grid-list.js +1 -1
- package/dist/incr-search.d.ts +1 -1
- package/dist/index-9ttcdwlL.d.ts +71 -0
- package/dist/{index-DEVyVIn8.d.ts → index-Bx0BXPxI.d.ts} +52 -6
- package/dist/index-CYRc6Dah.d.ts +475 -0
- package/dist/{index-KN2MW_xD.d.ts → index-Dr80Zdl6.d.ts} +1 -1
- package/dist/index.d.ts +334 -23
- package/dist/index.js +2247 -867
- package/dist/{list-native-drag-controller-O2Nruqpi.d.ts → list-native-drag-controller-D_UrXipS.d.ts} +1 -1
- package/dist/{list-selection-controller-BeE70lbB.js → list-selection-controller-5L0Nj9tR.js} +87 -2
- package/dist/miller-BFRfn0ve.js +2237 -0
- package/dist/miller.d.ts +4 -437
- package/dist/miller.js +1 -1815
- package/dist/pending-BGidHppL.js +91 -0
- package/dist/scroll-hover-guard-Cjw6179Q.js +35 -0
- package/dist/{split-layout-JK3xWVX2.d.ts → split-layout-CophkNeU.d.ts} +41 -3
- package/dist/{split-layout-Deoxy47d.js → split-layout-DTY4PJRp.js} +251 -19
- package/dist/split-layout.d.ts +1 -1
- package/dist/split-layout.js +1 -1
- package/dist/sunburst-tree.d.ts +2 -2
- package/dist/sunburst-tree.js +1 -1
- package/dist/{sunbursttree-list-yXwS-uEI.js → sunbursttree-list-B_VxOHlo.js} +298 -79
- package/dist/{sunbursttree-list--Z0uyh8O.d.ts → sunbursttree-list-D6j5BxVQ.d.ts} +40 -3
- package/dist/tree-CftHwtEb.js +146 -0
- package/dist/{tree-D8VxvR9u.js → tree-Crdpl4nc.js} +275 -47
- package/dist/{tree-component-C6UHO-w9.d.ts → tree-component-DgfkxmVY.d.ts} +19 -8
- package/dist/tree.d.ts +3 -3
- package/dist/tree.js +1 -1
- package/dist/types-LU1u0CB8.d.ts +87 -0
- package/dist/vertical-list-1V1Vg2O5.js +1993 -0
- package/dist/{vertical-list-D8MksoTp.d.ts → vertical-list-BPDmZx6T.d.ts} +31 -3
- package/dist/vertical-list.d.ts +3 -3
- package/dist/vertical-list.js +1 -1789
- package/dist/view-state-controller-BdyXWUUM.js +305 -0
- package/docs-user/README.md +2 -0
- package/docs-user/crumbs.md +27 -0
- package/docs-user/data-grid.md +66 -1
- package/docs-user/detail-list.md +54 -0
- package/docs-user/dock.md +54 -12
- package/docs-user/flat-list.md +27 -0
- package/docs-user/grid-list.md +64 -11
- package/docs-user/hyperbolic-tree.md +31 -4
- package/docs-user/icicle-tree.md +36 -0
- package/docs-user/index.json +12 -2
- package/docs-user/llms.txt +2 -0
- package/docs-user/miller.md +52 -21
- package/docs-user/multicol-list.md +50 -8
- package/docs-user/split-layout.md +45 -17
- package/docs-user/sunburst-tree.md +32 -2
- package/docs-user/tree.md +45 -6
- package/docs-user/treemap.md +32 -0
- package/docs-user/twosplit-layout.md +22 -0
- package/docs-user/user-catalog.md +1 -1
- package/docs-user/vertical-list.md +55 -7
- package/llms.txt +2 -0
- package/package.json +1 -1
|
@@ -6,7 +6,9 @@ import "./icon-1gpwtPx8.js";
|
|
|
6
6
|
import { i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES } from "./incr-search-JIhwB49y.js";
|
|
7
7
|
import { t as handleStyles } from "./handle-DAXs7kbJ.js";
|
|
8
8
|
import { a as listItemFadeKeyframes, t as ListNativeDragController } from "./list-native-drag-controller-CcXoS0YX.js";
|
|
9
|
+
import { a as capKeys, l as keysToArray, n as EMPTY_KEYS, o as flagsFromRecord, r as MAX_PENDING_KEYS } from "./pending-BGidHppL.js";
|
|
9
10
|
import "./interaction-E-uMGHkJ.js";
|
|
11
|
+
import { n as anchorFromRow, o as scrollToOffset, t as ViewStateController } from "./view-state-controller-BdyXWUUM.js";
|
|
10
12
|
import { t as scrollFadeStyles } from "./scroll-fade-Xx28D7mH.js";
|
|
11
13
|
import { n as appearanceColor, t as appearanceAbsent } from "./item-appearance-BjcMyauw.js";
|
|
12
14
|
import { t as createDragGhost } from "./drag-ghost-DtSDvjri.js";
|
|
@@ -16,6 +18,191 @@ import { LitElement, css, html, nothing } from "lit";
|
|
|
16
18
|
import { property, query, state } from "lit/decorators.js";
|
|
17
19
|
import { createCommandRegistry } from "@clinth/ui-commands";
|
|
18
20
|
import { repeat } from "lit/directives/repeat.js";
|
|
21
|
+
//#region src/data-grid/data-grid-view-state.ts
|
|
22
|
+
const EMPTY_COLUMNS = /* @__PURE__ */ new Map();
|
|
23
|
+
const EMPTY_ORDER = /* @__PURE__ */ new Map();
|
|
24
|
+
/**
|
|
25
|
+
* Selected keys whose row is gone go pending (deselected silently); pending
|
|
26
|
+
* keys whose row now exists are selected unless `canRestore` is false (e.g.
|
|
27
|
+
* `selectionMode="none"`), in which case they stay pending. With `single`, at
|
|
28
|
+
* most one key stays selected (a restored key wins) and nothing more stays
|
|
29
|
+
* pending.
|
|
30
|
+
*/
|
|
31
|
+
function reconcileSelectedKeys(selected, pending, has, single, canRestore = true) {
|
|
32
|
+
if (selected.size === 0 && pending.size === 0) return {
|
|
33
|
+
selected,
|
|
34
|
+
pending,
|
|
35
|
+
restored: false,
|
|
36
|
+
changed: false
|
|
37
|
+
};
|
|
38
|
+
let next;
|
|
39
|
+
let nextPending;
|
|
40
|
+
for (const key of selected) {
|
|
41
|
+
if (has(key)) continue;
|
|
42
|
+
next ??= new Set(selected);
|
|
43
|
+
next.delete(key);
|
|
44
|
+
nextPending ??= new Set(pending);
|
|
45
|
+
nextPending.add(key);
|
|
46
|
+
}
|
|
47
|
+
const present = canRestore ? [...pending].filter(has) : [];
|
|
48
|
+
const resolved = resolvePendingKeys(next ?? selected, nextPending ?? pending, present, single);
|
|
49
|
+
return {
|
|
50
|
+
...resolved,
|
|
51
|
+
changed: resolved.changed || next !== void 0
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Select the `present` pending keys (already known to exist). Used on its own
|
|
56
|
+
* for streamed appends, where only the new slice can match — so the cost is
|
|
57
|
+
* proportional to the slice, not the row count.
|
|
58
|
+
*/
|
|
59
|
+
function resolvePendingKeys(selected, pending, present, single) {
|
|
60
|
+
if (present.length === 0) return {
|
|
61
|
+
selected,
|
|
62
|
+
pending: capKeys(pending),
|
|
63
|
+
restored: false,
|
|
64
|
+
changed: false
|
|
65
|
+
};
|
|
66
|
+
const next = new Set(selected);
|
|
67
|
+
const nextPending = new Set(pending);
|
|
68
|
+
for (const key of present) {
|
|
69
|
+
next.add(key);
|
|
70
|
+
nextPending.delete(key);
|
|
71
|
+
}
|
|
72
|
+
if (single) return {
|
|
73
|
+
selected: /* @__PURE__ */ new Set([present.at(-1)]),
|
|
74
|
+
pending: EMPTY_KEYS,
|
|
75
|
+
restored: true,
|
|
76
|
+
changed: true
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
selected: next,
|
|
80
|
+
pending: capKeys(nextPending),
|
|
81
|
+
restored: true,
|
|
82
|
+
changed: true
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Pending keys that appear in `sliceKeys` — iterates the slice, not the pending set. */
|
|
86
|
+
function matchSlice(pending, sliceKeys) {
|
|
87
|
+
if (pending.size === 0) return [];
|
|
88
|
+
return sliceKeys.filter((k) => pending.has(k));
|
|
89
|
+
}
|
|
90
|
+
/** Collapsed group keys as an `expanded` record (`false` = collapsed). */
|
|
91
|
+
function collapsedToRecord(collapsed, include = () => true) {
|
|
92
|
+
const out = {};
|
|
93
|
+
for (const key of collapsed) if (include(key)) out[key] = false;
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
/** `expanded` record → collapsed group keys. Groups default open, so only `false` matters. */
|
|
97
|
+
function collapsedFromRecord(record) {
|
|
98
|
+
const keys = [...flagsFromRecord(record)].filter(([, open]) => !open).map(([k]) => k);
|
|
99
|
+
return new Set(capKeys(new Set(keys)));
|
|
100
|
+
}
|
|
101
|
+
/** Parse a saved `columns` record into pending layout and a saved order. Invalid values are dropped. */
|
|
102
|
+
function columnStateFromRecord(record) {
|
|
103
|
+
const pending = /* @__PURE__ */ new Map();
|
|
104
|
+
const order = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const [key, s] of Object.entries(record)) {
|
|
106
|
+
if (s === null || typeof s !== `object`) continue;
|
|
107
|
+
const width = typeof s.width === `number` && Number.isFinite(s.width) && s.width > 0 ? s.width : void 0;
|
|
108
|
+
const visible = typeof s.visible === `boolean` ? s.visible : void 0;
|
|
109
|
+
if (width !== void 0 || visible !== void 0) pending.set(key, {
|
|
110
|
+
...width === void 0 ? {} : { width },
|
|
111
|
+
...visible === void 0 ? {} : { visible }
|
|
112
|
+
});
|
|
113
|
+
if (typeof s.order === `number` && Number.isFinite(s.order)) order.set(key, s.order);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
pending,
|
|
117
|
+
order
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* - columns in `prev` but not in `columns` → their width / visibility go pending
|
|
122
|
+
* - pending layout for columns now defined → applied (on a copy of the column)
|
|
123
|
+
* - a saved `order` reorders the columns it names within the slots they
|
|
124
|
+
* occupy, leaving other columns where they are; it is dropped once every
|
|
125
|
+
* column it names is defined (so the host can reorder freely afterwards)
|
|
126
|
+
*
|
|
127
|
+
* Returns the input array when nothing changed, so `columns` identity (a sort
|
|
128
|
+
* input) only changes when it must.
|
|
129
|
+
*/
|
|
130
|
+
function reconcileColumns(prev, columns, pending, order) {
|
|
131
|
+
const keys = new Set(columns.map((c) => c.key));
|
|
132
|
+
let nextPending;
|
|
133
|
+
for (const col of prev) {
|
|
134
|
+
if (keys.has(col.key) || pending.has(col.key)) continue;
|
|
135
|
+
nextPending ??= new Map(pending);
|
|
136
|
+
nextPending.set(col.key, layoutOf(col));
|
|
137
|
+
}
|
|
138
|
+
let next;
|
|
139
|
+
columns.forEach((col, i) => {
|
|
140
|
+
const saved = (nextPending ?? pending).get(col.key);
|
|
141
|
+
if (!saved) return;
|
|
142
|
+
nextPending ??= new Map(pending);
|
|
143
|
+
nextPending.delete(col.key);
|
|
144
|
+
if ((saved.width === void 0 || saved.width === col.width) && (saved.visible === void 0 || saved.visible === (col.visible !== false))) return;
|
|
145
|
+
next ??= [...columns];
|
|
146
|
+
next[i] = {
|
|
147
|
+
...col,
|
|
148
|
+
...saved
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
let nextOrder = order;
|
|
152
|
+
if (order.size > 0) {
|
|
153
|
+
const working = next ?? columns;
|
|
154
|
+
const slots = working.flatMap((c, i) => order.has(c.key) ? [i] : []);
|
|
155
|
+
const ranked = slots.map((i) => working[i]).sort((a, b) => order.get(a.key) - order.get(b.key));
|
|
156
|
+
if (ranked.some((c, j) => c !== working[slots[j]])) {
|
|
157
|
+
next ??= [...columns];
|
|
158
|
+
slots.forEach((slot, j) => {
|
|
159
|
+
next[slot] = ranked[j];
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if ([...order.keys()].every((k) => keys.has(k))) nextOrder = EMPTY_ORDER;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
columns: next ?? columns,
|
|
166
|
+
pending: nextPending ? capMap(nextPending) : pending,
|
|
167
|
+
order: nextOrder,
|
|
168
|
+
changed: next !== void 0 || nextPending !== void 0 || nextOrder !== order
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** Defined columns (and, with `pending`, saved layout for undefined ones) as a JSON record. */
|
|
172
|
+
function captureColumns(columns, pending, order, includePending) {
|
|
173
|
+
const out = {};
|
|
174
|
+
if (includePending) {
|
|
175
|
+
for (const [key, layout] of pending) out[key] = withOrder(layout, order.get(key));
|
|
176
|
+
for (const [key, rank] of order) if (!(key in out) && !columns.some((c) => c.key === key)) out[key] = { order: rank };
|
|
177
|
+
}
|
|
178
|
+
columns.forEach((col, i) => {
|
|
179
|
+
out[col.key] = withOrder(layoutOf(col), order.size > 0 ? order.get(col.key) : i);
|
|
180
|
+
});
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
/** Whether `value` is a usable sort state (`null` = unsorted). */
|
|
184
|
+
function isSortState(value) {
|
|
185
|
+
if (value === null) return true;
|
|
186
|
+
if (typeof value !== `object`) return false;
|
|
187
|
+
const s = value;
|
|
188
|
+
return typeof s.key === `string` && (s.direction === `asc` || s.direction === `desc`);
|
|
189
|
+
}
|
|
190
|
+
function layoutOf(col) {
|
|
191
|
+
return {
|
|
192
|
+
...col.width === void 0 ? {} : { width: col.width },
|
|
193
|
+
visible: col.visible !== false
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function withOrder(layout, order) {
|
|
197
|
+
return order === void 0 ? { ...layout } : {
|
|
198
|
+
...layout,
|
|
199
|
+
order
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function capMap(map, max = MAX_PENDING_KEYS) {
|
|
203
|
+
return map.size <= max ? map : new Map([...map].slice(map.size - max));
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
19
206
|
//#region src/data-grid/shift-wheel.ts
|
|
20
207
|
/**
|
|
21
208
|
* Shift + vertical mouse wheel → horizontal scroll.
|
|
@@ -72,12 +259,20 @@ const EMPTY_CELL_LOOK = {
|
|
|
72
259
|
iconColor: void 0,
|
|
73
260
|
badge: void 0
|
|
74
261
|
};
|
|
262
|
+
/**
|
|
263
|
+
* Default `getRowKey`: `row.key`, then `row.id`, else the row's sorted index.
|
|
264
|
+
* Index keys aren't stable, so view state never records them.
|
|
265
|
+
*/
|
|
266
|
+
const defaultGetRowKey = (r, i) => r.key ?? r.id ?? String(i);
|
|
75
267
|
/** Shallow key-array equality, treating `undefined` (never dispatched) as different. */
|
|
76
268
|
function sameKeys(a, b) {
|
|
77
269
|
if (a === void 0 || a.length !== b.length) return false;
|
|
78
270
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
79
271
|
return true;
|
|
80
272
|
}
|
|
273
|
+
function sameSet(a, b) {
|
|
274
|
+
return a.size === b.size && [...a].every((k) => b.has(k));
|
|
275
|
+
}
|
|
81
276
|
/** Element-wise reference equality, treating `undefined` (never computed) as different. */
|
|
82
277
|
function sameRefs(a, b) {
|
|
83
278
|
return a !== void 0 && a.length === b.length && a.every((v, i) => v === b[i]);
|
|
@@ -91,7 +286,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
91
286
|
this.interactionMode = `standard`;
|
|
92
287
|
this.sortState = null;
|
|
93
288
|
this.rowHeight = 28;
|
|
94
|
-
this.getRowKey =
|
|
289
|
+
this.getRowKey = defaultGetRowKey;
|
|
95
290
|
this.editable = false;
|
|
96
291
|
this.editability = `user`;
|
|
97
292
|
this.cellOverflow = `ellipsis`;
|
|
@@ -185,6 +380,15 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
185
380
|
this.#viewportChangeRaf = 0;
|
|
186
381
|
this.#resortRaf = 0;
|
|
187
382
|
this._drag = null;
|
|
383
|
+
this.#viewState = new ViewStateController(this, {
|
|
384
|
+
kind: `data-grid`,
|
|
385
|
+
capture: () => this.getViewState()
|
|
386
|
+
});
|
|
387
|
+
this.#pendingSelected = EMPTY_KEYS;
|
|
388
|
+
this.#pendingColumns = EMPTY_COLUMNS;
|
|
389
|
+
this.#columnOrder = EMPTY_ORDER;
|
|
390
|
+
this.#reconciledColumns = [];
|
|
391
|
+
this.#groupKeys = /* @__PURE__ */ new Set();
|
|
188
392
|
this._onFocusOut = (e) => {
|
|
189
393
|
if (this._suppressBlurCommit) return;
|
|
190
394
|
if (this.editability === `immediate`) return;
|
|
@@ -204,180 +408,18 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
204
408
|
this._scrollLeft = el.scrollLeft;
|
|
205
409
|
const after = this.#windowRows(2);
|
|
206
410
|
if (before.start !== after.start || before.end !== after.end) this.requestUpdate();
|
|
411
|
+
if (!this.#viewState.pendingScroll) this.#viewState.notify();
|
|
207
412
|
cancelAnimationFrame(this.#viewportChangeRaf);
|
|
208
413
|
this.#viewportChangeRaf = 0;
|
|
209
414
|
this.#emitViewportChangeIfChanged();
|
|
210
415
|
});
|
|
211
416
|
};
|
|
212
417
|
this._onKeyDown = (e) => {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const g = this._getGroupKey(this._getRowForKey(this._anchorKey) ?? { key: this._anchorKey });
|
|
219
|
-
this._selectedKeys = new Set(this._flat.filter((f) => f.type === `row` && this._getGroupKey(f.row) === g).map((f) => f.key));
|
|
220
|
-
} else this._selectedKeys = new Set(this._flat.filter((f) => f.type === `row`).map((f) => f.key));
|
|
221
|
-
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
222
|
-
detail: {
|
|
223
|
-
selectedKeys: new Set(this._selectedKeys),
|
|
224
|
-
previous: prev
|
|
225
|
-
},
|
|
226
|
-
bubbles: true,
|
|
227
|
-
composed: true
|
|
228
|
-
}));
|
|
229
|
-
}
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
if (this._editing && (e.key === `ArrowDown` || e.key === `ArrowUp`)) {
|
|
233
|
-
if ((this.shadowRoot?.activeElement)?.tagName === `SELECT`) return;
|
|
234
|
-
e.preventDefault();
|
|
235
|
-
const cur = this._editing;
|
|
236
|
-
const dir = e.key === `ArrowDown` ? 1 : -1;
|
|
237
|
-
const colKey = cur.colKey;
|
|
238
|
-
this._suppressBlurCommit = true;
|
|
239
|
-
this._commitEdit();
|
|
240
|
-
const rowKeys = this._flat.filter((f) => f.type === `row`).map((f) => f.key);
|
|
241
|
-
const idx = rowKeys.indexOf(cur.rowKey);
|
|
242
|
-
let nextIdx = idx + dir;
|
|
243
|
-
while (nextIdx >= 0 && nextIdx < rowKeys.length) {
|
|
244
|
-
const nk = rowKeys[nextIdx];
|
|
245
|
-
const row = this.#rowEntry(nk)?.row;
|
|
246
|
-
const col = this._visibleColumns.find((c) => c.key === colKey);
|
|
247
|
-
if (row && col && this._isEditable(row, col)) {
|
|
248
|
-
this._startEdit(nk, colKey);
|
|
249
|
-
this._tickled.handleKeyboardNavigation(nextIdx > idx ? `next` : `previous`);
|
|
250
|
-
this._scrollRowIntoView(nk);
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
nextIdx += dir;
|
|
254
|
-
}
|
|
255
|
-
if (nextIdx < 0 || nextIdx >= rowKeys.length) {
|
|
256
|
-
this._tickled.handleKeyboardNavigation(dir > 0 ? `next` : `previous`);
|
|
257
|
-
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
258
|
-
}
|
|
259
|
-
requestAnimationFrame(() => {
|
|
260
|
-
this._suppressBlurCommit = false;
|
|
261
|
-
});
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (e.key === `ArrowDown` || e.key === `ArrowUp` || e.key === `Home` || e.key === `End`) {
|
|
265
|
-
e.preventDefault();
|
|
266
|
-
this._tickled.setInputMode(`keyboard`);
|
|
267
|
-
}
|
|
268
|
-
switch (e.key) {
|
|
269
|
-
case `ArrowDown`:
|
|
270
|
-
this._tickled.handleKeyboardNavigation(`next`);
|
|
271
|
-
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
272
|
-
if (this.interactionMode === `implicit` && this._tickled.tickledId) this._replaceSelection(this._tickled.tickledId);
|
|
273
|
-
if (e.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`) && this._tickled.tickledId) this._rangeSelectTo(this._tickled.tickledId);
|
|
274
|
-
break;
|
|
275
|
-
case `ArrowUp`:
|
|
276
|
-
this._tickled.handleKeyboardNavigation(`previous`);
|
|
277
|
-
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
278
|
-
if (this.interactionMode === `implicit` && this._tickled.tickledId) this._replaceSelection(this._tickled.tickledId);
|
|
279
|
-
if (e.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`) && this._tickled.tickledId) this._rangeSelectTo(this._tickled.tickledId);
|
|
280
|
-
break;
|
|
281
|
-
case `Home`:
|
|
282
|
-
this._tickled.handleKeyboardNavigation(`first`);
|
|
283
|
-
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
284
|
-
break;
|
|
285
|
-
case `End`:
|
|
286
|
-
this._tickled.handleKeyboardNavigation(`last`);
|
|
287
|
-
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
288
|
-
break;
|
|
289
|
-
case `Enter`: {
|
|
290
|
-
const key = this._tickled.tickledId;
|
|
291
|
-
if (!key) break;
|
|
292
|
-
e.preventDefault();
|
|
293
|
-
if (!this._editing && this.editability === `user`) {
|
|
294
|
-
const entry = this.#rowEntry(key);
|
|
295
|
-
if (entry) {
|
|
296
|
-
const row = entry.row;
|
|
297
|
-
if (this._lastClickedCell?.rowKey === key) {
|
|
298
|
-
const col = this._visibleColumns.find((c) => c.key === this._lastClickedCell.colKey);
|
|
299
|
-
if (col && this._isEditable(row, col)) {
|
|
300
|
-
this._startEdit(key, col.key);
|
|
301
|
-
break;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const firstEditable = this._visibleColumns.find((c) => this._isEditable(row, c));
|
|
305
|
-
if (firstEditable) {
|
|
306
|
-
this._startEdit(key, firstEditable.key);
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
if (!this._editing) {
|
|
312
|
-
const entry = this.#rowEntry(key);
|
|
313
|
-
const idx = entry?.idx ?? -1;
|
|
314
|
-
if (this._selectedKeys.has(key)) {
|
|
315
|
-
this._activateRow(key, entry?.row, idx);
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
if (this.interactionMode !== `manual` && this.interactionMode !== `checked`) this._replaceSelection(key);
|
|
319
|
-
this.dispatchEvent(new CustomEvent(`datagrid-cell-click`, {
|
|
320
|
-
detail: {
|
|
321
|
-
rowKey: key,
|
|
322
|
-
colKey: ``,
|
|
323
|
-
rowIndex: idx,
|
|
324
|
-
colIndex: -1
|
|
325
|
-
},
|
|
326
|
-
bubbles: true,
|
|
327
|
-
composed: true
|
|
328
|
-
}));
|
|
329
|
-
}
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
case ` `: {
|
|
333
|
-
if (this._editing) {
|
|
334
|
-
if ((this.columns.find((c) => c.key === this._editing.colKey)?.dataType ?? `text`) === `boolean`) {
|
|
335
|
-
e.preventDefault();
|
|
336
|
-
const isImmediate = this.editability === `immediate`;
|
|
337
|
-
const rowKey = this._editing.rowKey;
|
|
338
|
-
const colKey = this._editing.colKey;
|
|
339
|
-
const active = this.shadowRoot?.activeElement;
|
|
340
|
-
let next;
|
|
341
|
-
if (active?.type === `checkbox`) {
|
|
342
|
-
next = !active.checked;
|
|
343
|
-
active.checked = next;
|
|
344
|
-
} else {
|
|
345
|
-
const row = this.#rowEntry(rowKey)?.row;
|
|
346
|
-
next = !(this._editValue !== `` ? Boolean(this._editValue) : Boolean(row?.[colKey]));
|
|
347
|
-
}
|
|
348
|
-
if (isImmediate) this._commitImmediate(rowKey, colKey, next);
|
|
349
|
-
else {
|
|
350
|
-
this._editValue = next;
|
|
351
|
-
this._commitEdit();
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
const key = this._tickled.tickledId;
|
|
357
|
-
if (!key) break;
|
|
358
|
-
e.preventDefault();
|
|
359
|
-
if (this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `standard` || this.interactionMode === `vscode` || this.interactionMode === `manual`) this._toggleSelection(key);
|
|
360
|
-
else this._replaceSelection(key);
|
|
361
|
-
break;
|
|
362
|
-
}
|
|
363
|
-
case `Escape`: if (this._editing || this._editingRow) {
|
|
364
|
-
e.preventDefault();
|
|
365
|
-
this._editing = null;
|
|
366
|
-
this._editingRow = null;
|
|
367
|
-
this.requestUpdate();
|
|
368
|
-
} else if (this._selectedKeys.size) {
|
|
369
|
-
e.preventDefault();
|
|
370
|
-
const prev = new Set(this._selectedKeys);
|
|
371
|
-
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
372
|
-
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
373
|
-
detail: {
|
|
374
|
-
selectedKeys: /* @__PURE__ */ new Set(),
|
|
375
|
-
previous: prev
|
|
376
|
-
},
|
|
377
|
-
bubbles: true,
|
|
378
|
-
composed: true
|
|
379
|
-
}));
|
|
380
|
-
}
|
|
418
|
+
const before = this._tickled.tickledId;
|
|
419
|
+
this.#handleKeyDown(e);
|
|
420
|
+
if (this._tickled.tickledId !== before) {
|
|
421
|
+
this.#pendingFocus = void 0;
|
|
422
|
+
this.#viewState.notify();
|
|
381
423
|
}
|
|
382
424
|
};
|
|
383
425
|
this._suppressBlurCommit = false;
|
|
@@ -711,6 +753,21 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
711
753
|
#lastViewportVisibleKeys;
|
|
712
754
|
#resortRaf;
|
|
713
755
|
#detachShiftWheel;
|
|
756
|
+
#viewState;
|
|
757
|
+
/** Saved selection for row keys not (yet) in `rows`. */
|
|
758
|
+
#pendingSelected;
|
|
759
|
+
#pendingFocus;
|
|
760
|
+
/** Saved width / visibility for column keys not (yet) in `columns`. */
|
|
761
|
+
#pendingColumns;
|
|
762
|
+
/** Saved column order, kept until every column it names is defined. */
|
|
763
|
+
#columnOrder;
|
|
764
|
+
/** `columns` as of the last column reconcile, to spot removed columns. */
|
|
765
|
+
#reconciledColumns;
|
|
766
|
+
/** Every row by key, built lazily (only when a key isn't in `_flat`); reset on resort. */
|
|
767
|
+
#rowIndex;
|
|
768
|
+
#rowIndexKeyFn;
|
|
769
|
+
/** Group keys in the current flat model. */
|
|
770
|
+
#groupKeys;
|
|
714
771
|
connectedCallback() {
|
|
715
772
|
super.connectedCallback();
|
|
716
773
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
@@ -754,6 +811,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
754
811
|
this.#viewportResizeObserver = new ResizeObserver(() => this.#measureViewport());
|
|
755
812
|
this.#viewportResizeObserver.observe(this._viewportEl);
|
|
756
813
|
this.#measureViewport();
|
|
814
|
+
this.#viewState.observeScrollTarget(this._viewportEl);
|
|
757
815
|
}
|
|
758
816
|
/**
|
|
759
817
|
* Re-read the viewport's size and update `_viewportH`/`_viewportW` if it
|
|
@@ -788,6 +846,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
788
846
|
}
|
|
789
847
|
updated(changed) {
|
|
790
848
|
super.updated(changed);
|
|
849
|
+
this.#viewState.applyPendingScroll((s) => this.#applyScroll(s));
|
|
791
850
|
if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`) || changed.has(`filterPredicate`) || changed.has(`rowHeight`)) this.#scheduleViewportChangeEmit();
|
|
792
851
|
if (changed.has(`_editing`) || changed.has(`_editingRow`)) this.updateComplete.then(() => {
|
|
793
852
|
let sel = null;
|
|
@@ -803,7 +862,8 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
803
862
|
super.willUpdate(changed);
|
|
804
863
|
this.setAttribute(`data-input-mode`, this._tickled.inputMode);
|
|
805
864
|
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
806
|
-
this.#
|
|
865
|
+
if (changed.has(`columns`)) this.#reconcileColumns();
|
|
866
|
+
if (this.#syncDerivedRows()) this.#reconcileRows();
|
|
807
867
|
this._visibleColumns = this.columns.filter((c) => c.visible !== false);
|
|
808
868
|
let off = 0;
|
|
809
869
|
this._colOffsets = this._visibleColumns.map((c) => {
|
|
@@ -840,11 +900,14 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
840
900
|
if (resort) {
|
|
841
901
|
this.#sortInputs = sortInputs;
|
|
842
902
|
this._sortedRows = this._applySort([...this.rows]);
|
|
903
|
+
this.#rowIndex = void 0;
|
|
843
904
|
}
|
|
844
905
|
if (resort || !sameRefs(this.#flatInputs, flatInputs)) {
|
|
845
906
|
this.#flatInputs = flatInputs;
|
|
846
907
|
this._rebuildFlat();
|
|
908
|
+
return true;
|
|
847
909
|
}
|
|
910
|
+
return false;
|
|
848
911
|
}
|
|
849
912
|
/** Mark the derived rows as matching the current inputs (after an incremental update). */
|
|
850
913
|
#markDerivedRowsSynced() {
|
|
@@ -904,6 +967,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
904
967
|
for (let i = 0; i < this._sortedRows.length; i++) if (!pred || pred(this._sortedRows[i])) sourceIdx.push(i);
|
|
905
968
|
const flat = [];
|
|
906
969
|
const keys = [];
|
|
970
|
+
this.#groupKeys = /* @__PURE__ */ new Set();
|
|
907
971
|
const pushRow = (idx) => {
|
|
908
972
|
const row = this._sortedRows[idx];
|
|
909
973
|
const key = this.getRowKey(row, idx);
|
|
@@ -924,6 +988,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
924
988
|
if (arr) arr.push(idx);
|
|
925
989
|
else groups.set(k, [idx]);
|
|
926
990
|
}
|
|
991
|
+
this.#groupKeys = new Set(groups.keys());
|
|
927
992
|
for (const [gKey, idxs] of groups) {
|
|
928
993
|
const label = this.groupLabel ? this.groupLabel(gKey, idxs.map((i) => this._sortedRows[i])) : gKey;
|
|
929
994
|
const gId = `__group__${gKey}`;
|
|
@@ -963,6 +1028,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
963
1028
|
if (next.has(gKey)) next.delete(gKey);
|
|
964
1029
|
else next.add(gKey);
|
|
965
1030
|
this._collapsedGroups = next;
|
|
1031
|
+
this.#viewState.notify();
|
|
966
1032
|
}
|
|
967
1033
|
_applySort(rows) {
|
|
968
1034
|
if (!this.sortState) return rows;
|
|
@@ -1001,6 +1067,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1001
1067
|
};
|
|
1002
1068
|
else next = null;
|
|
1003
1069
|
this.sortState = next;
|
|
1070
|
+
this.#viewState.notify();
|
|
1004
1071
|
this.dispatchEvent(new CustomEvent(`datagrid-sort`, {
|
|
1005
1072
|
detail: {
|
|
1006
1073
|
sort: next,
|
|
@@ -1071,6 +1138,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1071
1138
|
_onVisibilityToggle(col, checked) {
|
|
1072
1139
|
col.visible = checked;
|
|
1073
1140
|
this.columns = [...this.columns];
|
|
1141
|
+
this.#viewState.notify();
|
|
1074
1142
|
this.dispatchEvent(new CustomEvent(`datagrid-column-visibility`, {
|
|
1075
1143
|
detail: {
|
|
1076
1144
|
key: col.key,
|
|
@@ -1175,6 +1243,8 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1175
1243
|
const [moved] = vis.splice(fromIdx, 1);
|
|
1176
1244
|
vis.splice(toIdx, 0, moved);
|
|
1177
1245
|
this.columns = [...vis, ...hidden];
|
|
1246
|
+
this.#columnOrder = EMPTY_ORDER;
|
|
1247
|
+
this.#viewState.notify();
|
|
1178
1248
|
this.dispatchEvent(new CustomEvent(`datagrid-column-reorder`, {
|
|
1179
1249
|
detail: {
|
|
1180
1250
|
from: fromIdx,
|
|
@@ -1222,10 +1292,10 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1222
1292
|
* set when the selection actually changes.
|
|
1223
1293
|
*/
|
|
1224
1294
|
selectKeys(keys) {
|
|
1295
|
+
this.#pendingSelected = EMPTY_KEYS;
|
|
1225
1296
|
let next = new Set([...keys].filter((k) => this._getRowForKey(k) !== void 0));
|
|
1226
|
-
const multi = this.selectionMode === `multiple` || this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `vscode`;
|
|
1227
1297
|
if (this.selectionMode === `none`) next = /* @__PURE__ */ new Set();
|
|
1228
|
-
else if (!
|
|
1298
|
+
else if (!this.#isMultiSelect() && next.size > 1) {
|
|
1229
1299
|
const last = [...next].at(-1);
|
|
1230
1300
|
next = last === void 0 ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([last]);
|
|
1231
1301
|
}
|
|
@@ -1233,10 +1303,18 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1233
1303
|
if (prev.size === next.size && [...next].every((k) => prev.has(k))) return;
|
|
1234
1304
|
this._selectedKeys = next;
|
|
1235
1305
|
this._anchorKey = [...next].at(-1);
|
|
1306
|
+
this.#dispatchSelect(prev);
|
|
1307
|
+
}
|
|
1308
|
+
#isMultiSelect() {
|
|
1309
|
+
return this.selectionMode === `multiple` || this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `vscode`;
|
|
1310
|
+
}
|
|
1311
|
+
#dispatchSelect(previous) {
|
|
1312
|
+
if (!this.#viewState.isRestoring) this.#pendingSelected = EMPTY_KEYS;
|
|
1313
|
+
this.#viewState.notify();
|
|
1236
1314
|
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
1237
1315
|
detail: {
|
|
1238
|
-
selectedKeys: new Set(
|
|
1239
|
-
previous
|
|
1316
|
+
selectedKeys: new Set(this._selectedKeys),
|
|
1317
|
+
previous
|
|
1240
1318
|
},
|
|
1241
1319
|
bubbles: true,
|
|
1242
1320
|
composed: true
|
|
@@ -1251,6 +1329,8 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1251
1329
|
if (!this.#flatIndexByKey.has(key) || key.startsWith(`__group__`)) return;
|
|
1252
1330
|
this._tickled.focus(key);
|
|
1253
1331
|
this._scrollRowIntoView(key);
|
|
1332
|
+
this.#pendingFocus = void 0;
|
|
1333
|
+
this.#viewState.notify();
|
|
1254
1334
|
}
|
|
1255
1335
|
/**
|
|
1256
1336
|
* Append rows without the full O(n log n) rebuild that reassigning `rows`
|
|
@@ -1273,6 +1353,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1273
1353
|
this._sortedRows = [...this._sortedRows, ...appended];
|
|
1274
1354
|
this.#appendToFlat(appended, startIdx);
|
|
1275
1355
|
this.#markDerivedRowsSynced();
|
|
1356
|
+
this.#resolveAppended(appended, startIdx);
|
|
1276
1357
|
if (this.sortState != null) {
|
|
1277
1358
|
cancelAnimationFrame(this.#resortRaf);
|
|
1278
1359
|
this.#resortRaf = requestAnimationFrame(() => {
|
|
@@ -1311,14 +1392,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1311
1392
|
this._selectedKeys = /* @__PURE__ */ new Set([key]);
|
|
1312
1393
|
} else this._selectedKeys = /* @__PURE__ */ new Set([key]);
|
|
1313
1394
|
this._anchorKey = key;
|
|
1314
|
-
this
|
|
1315
|
-
detail: {
|
|
1316
|
-
selectedKeys: new Set(this._selectedKeys),
|
|
1317
|
-
previous: prev
|
|
1318
|
-
},
|
|
1319
|
-
bubbles: true,
|
|
1320
|
-
composed: true
|
|
1321
|
-
}));
|
|
1395
|
+
this.#dispatchSelect(prev);
|
|
1322
1396
|
}
|
|
1323
1397
|
_toggleSelection(key) {
|
|
1324
1398
|
const prev = new Set(this._selectedKeys);
|
|
@@ -1334,14 +1408,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1334
1408
|
}
|
|
1335
1409
|
this._selectedKeys = next;
|
|
1336
1410
|
this._anchorKey = key;
|
|
1337
|
-
this
|
|
1338
|
-
detail: {
|
|
1339
|
-
selectedKeys: new Set(this._selectedKeys),
|
|
1340
|
-
previous: prev
|
|
1341
|
-
},
|
|
1342
|
-
bubbles: true,
|
|
1343
|
-
composed: true
|
|
1344
|
-
}));
|
|
1411
|
+
this.#dispatchSelect(prev);
|
|
1345
1412
|
}
|
|
1346
1413
|
_rangeSelectTo(key) {
|
|
1347
1414
|
if (!this._anchorKey) {
|
|
@@ -1372,28 +1439,171 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1372
1439
|
const hi = Math.max(ga, gb);
|
|
1373
1440
|
const prev = new Set(this._selectedKeys);
|
|
1374
1441
|
this._selectedKeys = new Set(groupKeys.slice(lo, hi + 1));
|
|
1375
|
-
this
|
|
1376
|
-
detail: {
|
|
1377
|
-
selectedKeys: new Set(this._selectedKeys),
|
|
1378
|
-
previous: prev
|
|
1379
|
-
},
|
|
1380
|
-
bubbles: true,
|
|
1381
|
-
composed: true
|
|
1382
|
-
}));
|
|
1442
|
+
this.#dispatchSelect(prev);
|
|
1383
1443
|
return;
|
|
1384
1444
|
}
|
|
1385
1445
|
const lo = Math.min(a, b);
|
|
1386
1446
|
const hi = Math.max(a, b);
|
|
1387
1447
|
const prev = new Set(this._selectedKeys);
|
|
1388
1448
|
this._selectedKeys = new Set(keys.slice(lo, hi + 1));
|
|
1389
|
-
this
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1449
|
+
this.#dispatchSelect(prev);
|
|
1450
|
+
}
|
|
1451
|
+
#handleKeyDown(e) {
|
|
1452
|
+
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === `a`) {
|
|
1453
|
+
if (this.selectionMode === `multiple` || this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `vscode` || this.interactionMode === `standard`) {
|
|
1454
|
+
e.preventDefault();
|
|
1455
|
+
const prev = new Set(this._selectedKeys);
|
|
1456
|
+
if (this.groupBy && !this.crossGroupSelection && this._anchorKey) {
|
|
1457
|
+
const g = this._getGroupKey(this._getRowForKey(this._anchorKey) ?? { key: this._anchorKey });
|
|
1458
|
+
this._selectedKeys = new Set(this._flat.filter((f) => f.type === `row` && this._getGroupKey(f.row) === g).map((f) => f.key));
|
|
1459
|
+
} else this._selectedKeys = new Set(this._flat.filter((f) => f.type === `row`).map((f) => f.key));
|
|
1460
|
+
this.#dispatchSelect(prev);
|
|
1461
|
+
}
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
if (this._editing && (e.key === `ArrowDown` || e.key === `ArrowUp`)) {
|
|
1465
|
+
if ((this.shadowRoot?.activeElement)?.tagName === `SELECT`) return;
|
|
1466
|
+
e.preventDefault();
|
|
1467
|
+
const cur = this._editing;
|
|
1468
|
+
const dir = e.key === `ArrowDown` ? 1 : -1;
|
|
1469
|
+
const colKey = cur.colKey;
|
|
1470
|
+
this._suppressBlurCommit = true;
|
|
1471
|
+
this._commitEdit();
|
|
1472
|
+
const rowKeys = this._flat.filter((f) => f.type === `row`).map((f) => f.key);
|
|
1473
|
+
const idx = rowKeys.indexOf(cur.rowKey);
|
|
1474
|
+
let nextIdx = idx + dir;
|
|
1475
|
+
while (nextIdx >= 0 && nextIdx < rowKeys.length) {
|
|
1476
|
+
const nk = rowKeys[nextIdx];
|
|
1477
|
+
const row = this.#rowEntry(nk)?.row;
|
|
1478
|
+
const col = this._visibleColumns.find((c) => c.key === colKey);
|
|
1479
|
+
if (row && col && this._isEditable(row, col)) {
|
|
1480
|
+
this._startEdit(nk, colKey);
|
|
1481
|
+
this._tickled.handleKeyboardNavigation(nextIdx > idx ? `next` : `previous`);
|
|
1482
|
+
this._scrollRowIntoView(nk);
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
nextIdx += dir;
|
|
1486
|
+
}
|
|
1487
|
+
if (nextIdx < 0 || nextIdx >= rowKeys.length) {
|
|
1488
|
+
this._tickled.handleKeyboardNavigation(dir > 0 ? `next` : `previous`);
|
|
1489
|
+
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
1490
|
+
}
|
|
1491
|
+
requestAnimationFrame(() => {
|
|
1492
|
+
this._suppressBlurCommit = false;
|
|
1493
|
+
});
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
if (e.key === `ArrowDown` || e.key === `ArrowUp` || e.key === `Home` || e.key === `End`) {
|
|
1497
|
+
e.preventDefault();
|
|
1498
|
+
this._tickled.setInputMode(`keyboard`);
|
|
1499
|
+
}
|
|
1500
|
+
switch (e.key) {
|
|
1501
|
+
case `ArrowDown`:
|
|
1502
|
+
this._tickled.handleKeyboardNavigation(`next`);
|
|
1503
|
+
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
1504
|
+
if (this.interactionMode === `implicit` && this._tickled.tickledId) this._replaceSelection(this._tickled.tickledId);
|
|
1505
|
+
if (e.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`) && this._tickled.tickledId) this._rangeSelectTo(this._tickled.tickledId);
|
|
1506
|
+
break;
|
|
1507
|
+
case `ArrowUp`:
|
|
1508
|
+
this._tickled.handleKeyboardNavigation(`previous`);
|
|
1509
|
+
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
1510
|
+
if (this.interactionMode === `implicit` && this._tickled.tickledId) this._replaceSelection(this._tickled.tickledId);
|
|
1511
|
+
if (e.shiftKey && (this.interactionMode === `standard` || this.interactionMode === `vscode`) && this._tickled.tickledId) this._rangeSelectTo(this._tickled.tickledId);
|
|
1512
|
+
break;
|
|
1513
|
+
case `Home`:
|
|
1514
|
+
this._tickled.handleKeyboardNavigation(`first`);
|
|
1515
|
+
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
1516
|
+
break;
|
|
1517
|
+
case `End`:
|
|
1518
|
+
this._tickled.handleKeyboardNavigation(`last`);
|
|
1519
|
+
if (this._tickled.tickledId) this._scrollRowIntoView(this._tickled.tickledId);
|
|
1520
|
+
break;
|
|
1521
|
+
case `Enter`: {
|
|
1522
|
+
const key = this._tickled.tickledId;
|
|
1523
|
+
if (!key) break;
|
|
1524
|
+
e.preventDefault();
|
|
1525
|
+
if (!this._editing && this.editability === `user`) {
|
|
1526
|
+
const entry = this.#rowEntry(key);
|
|
1527
|
+
if (entry) {
|
|
1528
|
+
const row = entry.row;
|
|
1529
|
+
if (this._lastClickedCell?.rowKey === key) {
|
|
1530
|
+
const col = this._visibleColumns.find((c) => c.key === this._lastClickedCell.colKey);
|
|
1531
|
+
if (col && this._isEditable(row, col)) {
|
|
1532
|
+
this._startEdit(key, col.key);
|
|
1533
|
+
break;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
const firstEditable = this._visibleColumns.find((c) => this._isEditable(row, c));
|
|
1537
|
+
if (firstEditable) {
|
|
1538
|
+
this._startEdit(key, firstEditable.key);
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
if (!this._editing) {
|
|
1544
|
+
const entry = this.#rowEntry(key);
|
|
1545
|
+
const idx = entry?.idx ?? -1;
|
|
1546
|
+
if (this._selectedKeys.has(key)) {
|
|
1547
|
+
this._activateRow(key, entry?.row, idx);
|
|
1548
|
+
break;
|
|
1549
|
+
}
|
|
1550
|
+
if (this.interactionMode !== `manual` && this.interactionMode !== `checked`) this._replaceSelection(key);
|
|
1551
|
+
this.dispatchEvent(new CustomEvent(`datagrid-cell-click`, {
|
|
1552
|
+
detail: {
|
|
1553
|
+
rowKey: key,
|
|
1554
|
+
colKey: ``,
|
|
1555
|
+
rowIndex: idx,
|
|
1556
|
+
colIndex: -1
|
|
1557
|
+
},
|
|
1558
|
+
bubbles: true,
|
|
1559
|
+
composed: true
|
|
1560
|
+
}));
|
|
1561
|
+
}
|
|
1562
|
+
break;
|
|
1563
|
+
}
|
|
1564
|
+
case ` `: {
|
|
1565
|
+
if (this._editing) {
|
|
1566
|
+
if ((this.columns.find((c) => c.key === this._editing.colKey)?.dataType ?? `text`) === `boolean`) {
|
|
1567
|
+
e.preventDefault();
|
|
1568
|
+
const isImmediate = this.editability === `immediate`;
|
|
1569
|
+
const rowKey = this._editing.rowKey;
|
|
1570
|
+
const colKey = this._editing.colKey;
|
|
1571
|
+
const active = this.shadowRoot?.activeElement;
|
|
1572
|
+
let next;
|
|
1573
|
+
if (active?.type === `checkbox`) {
|
|
1574
|
+
next = !active.checked;
|
|
1575
|
+
active.checked = next;
|
|
1576
|
+
} else {
|
|
1577
|
+
const row = this.#rowEntry(rowKey)?.row;
|
|
1578
|
+
next = !(this._editValue !== `` ? Boolean(this._editValue) : Boolean(row?.[colKey]));
|
|
1579
|
+
}
|
|
1580
|
+
if (isImmediate) this._commitImmediate(rowKey, colKey, next);
|
|
1581
|
+
else {
|
|
1582
|
+
this._editValue = next;
|
|
1583
|
+
this._commitEdit();
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
break;
|
|
1587
|
+
}
|
|
1588
|
+
const key = this._tickled.tickledId;
|
|
1589
|
+
if (!key) break;
|
|
1590
|
+
e.preventDefault();
|
|
1591
|
+
if (this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `standard` || this.interactionMode === `vscode` || this.interactionMode === `manual`) this._toggleSelection(key);
|
|
1592
|
+
else this._replaceSelection(key);
|
|
1593
|
+
break;
|
|
1594
|
+
}
|
|
1595
|
+
case `Escape`: if (this._editing || this._editingRow) {
|
|
1596
|
+
e.preventDefault();
|
|
1597
|
+
this._editing = null;
|
|
1598
|
+
this._editingRow = null;
|
|
1599
|
+
this.requestUpdate();
|
|
1600
|
+
} else if (this._selectedKeys.size) {
|
|
1601
|
+
e.preventDefault();
|
|
1602
|
+
const prev = new Set(this._selectedKeys);
|
|
1603
|
+
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
1604
|
+
this.#dispatchSelect(prev);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1397
1607
|
}
|
|
1398
1608
|
_isEditable(row, col) {
|
|
1399
1609
|
if (this.editability === `disabled`) return false;
|
|
@@ -1609,6 +1819,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1609
1819
|
const w = this._measureAutoWidth(col);
|
|
1610
1820
|
col.width = w;
|
|
1611
1821
|
this.requestUpdate();
|
|
1822
|
+
this.#viewState.notify();
|
|
1612
1823
|
this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
|
|
1613
1824
|
detail: {
|
|
1614
1825
|
key: col.key,
|
|
@@ -1636,6 +1847,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1636
1847
|
requestAnimationFrame(() => {
|
|
1637
1848
|
col.width = target;
|
|
1638
1849
|
this.requestUpdate();
|
|
1850
|
+
this.#viewState.notify();
|
|
1639
1851
|
this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
|
|
1640
1852
|
detail: {
|
|
1641
1853
|
key,
|
|
@@ -1672,6 +1884,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1672
1884
|
window.removeEventListener(`pointerup`, onUp);
|
|
1673
1885
|
this._resizingColKey = void 0;
|
|
1674
1886
|
const w = Math.max(col.minWidth ?? 40, Math.min(col.maxWidth ?? 600, startW + ev.clientX - startX));
|
|
1887
|
+
this.#viewState.notify();
|
|
1675
1888
|
this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
|
|
1676
1889
|
detail: {
|
|
1677
1890
|
key: col.key,
|
|
@@ -1684,6 +1897,179 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1684
1897
|
window.addEventListener(`pointermove`, onMove);
|
|
1685
1898
|
window.addEventListener(`pointerup`, onUp);
|
|
1686
1899
|
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Snapshot selection, cursor, scroll, collapsed groups, column layout, sort
|
|
1902
|
+
* and (with `allowGrouping`) grouping as plain JSON, keyed by row / group /
|
|
1903
|
+
* column key. Includes pending state for keys not currently present. Rows
|
|
1904
|
+
* keyed by index (no `key` / `id` under the default `getRowKey`) are never
|
|
1905
|
+
* recorded.
|
|
1906
|
+
*/
|
|
1907
|
+
getViewState(options) {
|
|
1908
|
+
const withPending = options?.includePending ?? true;
|
|
1909
|
+
const cursor = this._tickled.inputMode === `keyboard` ? this._tickled.tickledId : void 0;
|
|
1910
|
+
const focus = cursor !== void 0 && this.#isStableKey(cursor) ? cursor : void 0;
|
|
1911
|
+
const groupBy = this.groupBy === null || typeof this.groupBy === `string` ? this.groupBy : void 0;
|
|
1912
|
+
return this.#viewState.shape({
|
|
1913
|
+
selected: keysToArray([...this._selectedKeys].filter((k) => this.#isStableKey(k)), withPending ? this.#pendingSelected : []),
|
|
1914
|
+
focus: focus ?? (withPending ? this.#pendingFocus : void 0),
|
|
1915
|
+
scroll: (withPending ? this.#viewState.pendingScroll : void 0) ?? this.#captureScroll(),
|
|
1916
|
+
expanded: collapsedToRecord(this._collapsedGroups, (k) => withPending || this.#groupKeys.has(k)),
|
|
1917
|
+
columns: captureColumns(this.columns, this.#pendingColumns, this.#columnOrder, withPending),
|
|
1918
|
+
sort: this.sortState,
|
|
1919
|
+
groupBy: this.allowGrouping ? groupBy : void 0
|
|
1920
|
+
}, options);
|
|
1921
|
+
}
|
|
1922
|
+
/**
|
|
1923
|
+
* Apply saved view state. Fields present replace the current ones; row and
|
|
1924
|
+
* column keys that aren't defined yet are applied as they appear (including
|
|
1925
|
+
* rows streamed in with `appendRows()`).
|
|
1926
|
+
*/
|
|
1927
|
+
setViewState(state) {
|
|
1928
|
+
if (!this.#viewState.accepts(state)) return;
|
|
1929
|
+
this.#viewState.restoring(() => {
|
|
1930
|
+
if (state.columns && typeof state.columns === `object`) {
|
|
1931
|
+
const { pending, order } = columnStateFromRecord(state.columns);
|
|
1932
|
+
this.#pendingColumns = pending;
|
|
1933
|
+
this.#columnOrder = order;
|
|
1934
|
+
this.#reconcileColumns();
|
|
1935
|
+
}
|
|
1936
|
+
if (state.sort !== void 0 && isSortState(state.sort)) this.sortState = state.sort;
|
|
1937
|
+
if (state.groupBy === null || typeof state.groupBy === `string`) this.groupBy = state.groupBy;
|
|
1938
|
+
if (state.expanded && typeof state.expanded === `object`) this._collapsedGroups = collapsedFromRecord(state.expanded);
|
|
1939
|
+
if (state.focus !== void 0) this.#pendingFocus = state.focus;
|
|
1940
|
+
if (state.scroll) this.#viewState.pendingScroll = state.scroll;
|
|
1941
|
+
if (Array.isArray(state.selected)) {
|
|
1942
|
+
const prev = this._selectedKeys;
|
|
1943
|
+
this.#pendingSelected = capKeys(new Set(state.selected.filter((k) => typeof k === `string`)));
|
|
1944
|
+
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
1945
|
+
this.#reconcileRows(prev);
|
|
1946
|
+
} else this.#reconcileRows();
|
|
1947
|
+
this.#viewState.notify();
|
|
1948
|
+
});
|
|
1949
|
+
this.requestUpdate();
|
|
1950
|
+
}
|
|
1951
|
+
clearPendingViewState() {
|
|
1952
|
+
this.#pendingSelected = EMPTY_KEYS;
|
|
1953
|
+
this.#pendingFocus = void 0;
|
|
1954
|
+
this.#pendingColumns = EMPTY_COLUMNS;
|
|
1955
|
+
this.#columnOrder = EMPTY_ORDER;
|
|
1956
|
+
this.#viewState.pendingScroll = void 0;
|
|
1957
|
+
if ([...this._collapsedGroups].some((k) => !this.#groupKeys.has(k))) this._collapsedGroups = new Set([...this._collapsedGroups].filter((k) => this.#groupKeys.has(k)));
|
|
1958
|
+
}
|
|
1959
|
+
/** Apply pending column layout to defined columns; demote layout of removed ones. */
|
|
1960
|
+
#reconcileColumns() {
|
|
1961
|
+
const r = reconcileColumns(this.#reconciledColumns, this.columns, this.#pendingColumns, this.#columnOrder);
|
|
1962
|
+
this.#pendingColumns = r.pending;
|
|
1963
|
+
this.#columnOrder = r.order;
|
|
1964
|
+
this.#reconciledColumns = r.columns;
|
|
1965
|
+
if (r.columns !== this.columns) {
|
|
1966
|
+
this.columns = r.columns;
|
|
1967
|
+
this.#viewState.restoring(() => this.#viewState.notify());
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Re-key selection and cursor onto the current rows: state for rows that
|
|
1972
|
+
* left goes pending (silently), pending state for rows now present is
|
|
1973
|
+
* applied. `baseline` is the selection to report as `previous` when the
|
|
1974
|
+
* caller already replaced it (`setViewState`).
|
|
1975
|
+
*/
|
|
1976
|
+
#reconcileRows(baseline) {
|
|
1977
|
+
this.#viewState.restoring(() => {
|
|
1978
|
+
const sel = reconcileSelectedKeys(this._selectedKeys, this.#pendingSelected, (k) => this.#hasRow(k), !this.#isMultiSelect(), this.selectionMode !== `none`);
|
|
1979
|
+
this.#pendingSelected = sel.pending;
|
|
1980
|
+
if (sel.changed) this.#setRestoredSelection(sel.selected);
|
|
1981
|
+
const before = baseline ?? this._selectedKeys;
|
|
1982
|
+
if (baseline ? !sameSet(baseline, this._selectedKeys) : sel.restored) this.#dispatchSelect(before);
|
|
1983
|
+
const cursor = this._tickled.tickledId;
|
|
1984
|
+
if (cursor !== void 0 && !this.#flatIndexByKey.has(cursor)) {
|
|
1985
|
+
if (this._tickled.inputMode === `keyboard` && this.#isStableKey(cursor)) this.#pendingFocus ??= cursor;
|
|
1986
|
+
this._tickled.clearTickled();
|
|
1987
|
+
}
|
|
1988
|
+
this.#applyPendingFocus();
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
/** Streamed append: match pending state against the new slice only (never the whole row set). */
|
|
1992
|
+
#resolveAppended(appended, startIdx) {
|
|
1993
|
+
const wantSelection = this.#pendingSelected.size > 0 && this.selectionMode !== `none`;
|
|
1994
|
+
if (wantSelection || this.#rowIndex !== void 0) {
|
|
1995
|
+
const keys = appended.map((r, j) => this.getRowKey(r, startIdx + j));
|
|
1996
|
+
keys.forEach((k, j) => this.#rowIndex?.set(k, appended[j]));
|
|
1997
|
+
const present = wantSelection ? matchSlice(this.#pendingSelected, keys) : [];
|
|
1998
|
+
if (present.length > 0) this.#viewState.restoring(() => {
|
|
1999
|
+
const prev = this._selectedKeys;
|
|
2000
|
+
const sel = resolvePendingKeys(prev, this.#pendingSelected, present, !this.#isMultiSelect());
|
|
2001
|
+
this.#pendingSelected = sel.pending;
|
|
2002
|
+
this.#setRestoredSelection(sel.selected);
|
|
2003
|
+
this.#dispatchSelect(prev);
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
if (this.#pendingFocus !== void 0) this.#viewState.restoring(() => this.#applyPendingFocus());
|
|
2007
|
+
}
|
|
2008
|
+
#setRestoredSelection(selected) {
|
|
2009
|
+
const lastKey = this._anchorKey;
|
|
2010
|
+
this._selectedKeys = new Set(selected);
|
|
2011
|
+
this._anchorKey = lastKey !== void 0 && selected.has(lastKey) ? lastKey : [...selected].at(-1);
|
|
2012
|
+
}
|
|
2013
|
+
#applyPendingFocus() {
|
|
2014
|
+
const key = this.#pendingFocus;
|
|
2015
|
+
if (key === void 0 || this._getRowForKey(key) === void 0) return;
|
|
2016
|
+
this.#pendingFocus = void 0;
|
|
2017
|
+
this._tickled.setInputMode(`keyboard`);
|
|
2018
|
+
this._tickled.setTickled(key);
|
|
2019
|
+
this.#viewState.notify();
|
|
2020
|
+
}
|
|
2021
|
+
/** Whether a row with this key is in `rows` (visible, filtered out or in a collapsed group). */
|
|
2022
|
+
#hasRow(key) {
|
|
2023
|
+
const i = this.#flatIndexByKey.get(key);
|
|
2024
|
+
if (i !== void 0) return this._flat[i].type === `row`;
|
|
2025
|
+
return this.#rowsByKey().has(key);
|
|
2026
|
+
}
|
|
2027
|
+
/** Every row by key — built only when a key isn't in `_flat`, then reused until the next resort. */
|
|
2028
|
+
#rowsByKey() {
|
|
2029
|
+
if (this.#rowIndex === void 0 || this.#rowIndexKeyFn !== this.getRowKey) {
|
|
2030
|
+
this.#rowIndexKeyFn = this.getRowKey;
|
|
2031
|
+
this.#rowIndex = new Map(this._sortedRows.map((r, j) => [this.getRowKey(r, j), r]));
|
|
2032
|
+
}
|
|
2033
|
+
return this.#rowIndex;
|
|
2034
|
+
}
|
|
2035
|
+
/** Index-derived keys (default `getRowKey`, row without `key` / `id`) can't be restored, so aren't recorded. */
|
|
2036
|
+
#isStableKey(key) {
|
|
2037
|
+
if (this.getRowKey !== defaultGetRowKey) return true;
|
|
2038
|
+
const row = this._getRowForKey(key) ?? this.#rowsByKey().get(key);
|
|
2039
|
+
return row !== void 0 && (row.key ?? row.id) !== void 0;
|
|
2040
|
+
}
|
|
2041
|
+
#captureScroll() {
|
|
2042
|
+
const vp = this._viewportEl;
|
|
2043
|
+
if (!vp) return void 0;
|
|
2044
|
+
const top = vp.scrollTop;
|
|
2045
|
+
const rh = this.rowHeight;
|
|
2046
|
+
const first = Math.max(0, Math.floor(top / rh));
|
|
2047
|
+
const end = Math.min(this._flat.length, first + 64);
|
|
2048
|
+
for (let i = first; i < end; i++) {
|
|
2049
|
+
const entry = this._flat[i];
|
|
2050
|
+
if (entry.type === `row`) return anchorFromRow(top, this.#isStableKey(entry.key) ? entry.key : void 0, i * rh, vp.scrollLeft);
|
|
2051
|
+
}
|
|
2052
|
+
return anchorFromRow(top, void 0, 0, vp.scrollLeft);
|
|
2053
|
+
}
|
|
2054
|
+
/** Returns `true` once the saved position is fully reached. */
|
|
2055
|
+
#applyScroll(scroll) {
|
|
2056
|
+
const vp = this._viewportEl;
|
|
2057
|
+
if (!vp) return false;
|
|
2058
|
+
let top = scroll.top;
|
|
2059
|
+
if (scroll.anchor) {
|
|
2060
|
+
const i = this.#flatIndexByKey.get(scroll.anchor.key);
|
|
2061
|
+
if (i === void 0) return false;
|
|
2062
|
+
top = i * this.rowHeight + scroll.anchor.offset;
|
|
2063
|
+
}
|
|
2064
|
+
const reachedY = scrollToOffset(vp, top);
|
|
2065
|
+
const reachedX = scroll.left === void 0 || scrollToOffset(vp, scroll.left, `horizontal`);
|
|
2066
|
+
const before = this.#windowRows(2);
|
|
2067
|
+
this._scrollTop = vp.scrollTop;
|
|
2068
|
+
this._scrollLeft = vp.scrollLeft;
|
|
2069
|
+
const after = this.#windowRows(2);
|
|
2070
|
+
if (before.start !== after.start || before.end !== after.end) this.requestUpdate();
|
|
2071
|
+
return reachedY && reachedX;
|
|
2072
|
+
}
|
|
1687
2073
|
/** Height of the row area: the viewport minus the sticky header row inside it. */
|
|
1688
2074
|
#bodyHeight() {
|
|
1689
2075
|
return this._viewportEl.clientHeight - (this._headerRowEl?.offsetHeight ?? 0);
|
|
@@ -2013,6 +2399,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
2013
2399
|
const v = value ?? ``;
|
|
2014
2400
|
this.groupBy = v || null;
|
|
2015
2401
|
this._collapsedGroups = /* @__PURE__ */ new Set();
|
|
2402
|
+
this.#viewState.notify();
|
|
2016
2403
|
this.dispatchEvent(new CustomEvent(`datagrid-group-change`, {
|
|
2017
2404
|
detail: { groupBy: this.groupBy },
|
|
2018
2405
|
bubbles: true,
|