@ixfx/components 0.15.4 → 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 +1223 -48
- package/bundle/index.js +875 -700
- package/dist/ac-text.d.ts +1 -1
- package/dist/{ac-token-Cq7lsYf-.d.ts → ac-token-qEs3FsQv.d.ts} +1 -1
- package/dist/ac-token.d.ts +1 -1
- package/dist/colour-picker.d.ts +1 -1
- 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-CbiubdG2.d.ts → data-grid-CIG2DVip.d.ts} +58 -9
- package/dist/{data-grid-Dx3l3dxw.js → data-grid-rMkLX2qo.js} +630 -215
- package/dist/data-grid.d.ts +3 -3
- package/dist/data-grid.js +1 -1
- package/dist/{detail-list-ClkMV0SN.d.ts → detail-list-6iMTNP4N.d.ts} +35 -5
- 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-0xTZdXzh.d.ts → flat-list-JPgQSmLW.d.ts} +28 -5
- 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-BnWJUIBC.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/{grouped-item-lister-Bhyo-dkH.d.ts → grouped-item-lister-DETte0Dh.d.ts} +1 -1
- package/dist/grouped-item-lister.d.ts +1 -1
- package/dist/{icon-DR_3rSyZ.d.ts → icon-CDGprSdE.d.ts} +1 -1
- package/dist/icons.d.ts +2 -2
- package/dist/incr-search.d.ts +1 -1
- package/dist/index-9ttcdwlL.d.ts +71 -0
- package/dist/{index-BAuqR9El.d.ts → index-Bx0BXPxI.d.ts} +52 -6
- package/dist/index-CYRc6Dah.d.ts +475 -0
- package/dist/{index-garxxqgV.d.ts → index-CYndvTWH.d.ts} +1 -1
- package/dist/{index-BUB5SICW.d.ts → index-CoqtMSd9.d.ts} +1 -1
- package/dist/{index-Bz3loIAf.d.ts → index-DN2BWctP.d.ts} +1 -1
- package/dist/{index-BPTuCRGb.d.ts → index-Dr80Zdl6.d.ts} +2 -2
- package/dist/{index-cnaRPQPp.d.ts → index-DxyyVqzA.d.ts} +2 -2
- package/dist/{index-CJ5fshD3.d.ts → index-UvnoZUAo.d.ts} +1 -1
- package/dist/index.d.ts +380 -33
- package/dist/index.js +2247 -866
- package/dist/{labelled-input-base-Dp_9KP0G.d.ts → labelled-input-base-DiTme1JO.d.ts} +1 -1
- package/dist/labelled-radial-input.d.ts +2 -2
- package/dist/labelled-range-input.d.ts +1 -1
- package/dist/labelled-slider-input.d.ts +1 -1
- package/dist/list-group-header.d.ts +1 -1
- 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/{menu-item-B0zqw2xp.d.ts → menu-item-bF6narIz.d.ts} +1 -1
- package/dist/menu.d.ts +2 -2
- package/dist/miller-BFRfn0ve.js +2237 -0
- package/dist/miller.d.ts +4 -437
- package/dist/miller.js +1 -1815
- package/dist/panel.d.ts +1 -1
- package/dist/pending-BGidHppL.js +91 -0
- package/dist/scroll-hover-guard-Cjw6179Q.js +35 -0
- package/dist/{split-layout-C5SYbDrq.d.ts → split-layout-CophkNeU.d.ts} +42 -4
- 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 -0
- package/dist/sunburst-tree.js +2 -0
- package/dist/sunbursttree-list-B_VxOHlo.js +1556 -0
- package/dist/sunbursttree-list-D6j5BxVQ.d.ts +303 -0
- package/dist/{tab-list-wC0xNz1b.d.ts → tab-list-Df371rqw.d.ts} +1 -1
- package/dist/tabs.d.ts +1 -1
- package/dist/{titlebar-B5zSTZnF.d.ts → titlebar-Tqu-VvMU.d.ts} +1 -1
- package/dist/titlebar.d.ts +1 -1
- 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-B8FoU-pU.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 +3 -0
- package/docs-user/crumbs.md +27 -0
- package/docs-user/data-grid.md +77 -5
- 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 +17 -2
- package/docs-user/llms.txt +3 -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 +223 -0
- package/docs-user/tree.md +46 -7
- 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 +3 -0
- package/package.json +5 -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;
|
|
@@ -1589,11 +1799,16 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1589
1799
|
ctx.font = font;
|
|
1590
1800
|
return Math.ceil(ctx.measureText(text).width);
|
|
1591
1801
|
};
|
|
1802
|
+
const extra = col.fixedExtraWidth ?? 0;
|
|
1592
1803
|
let maxW = measure(col.label) + headerExtra;
|
|
1593
1804
|
for (const r of this.rows) {
|
|
1805
|
+
if (col.measure) {
|
|
1806
|
+
maxW = Math.max(maxW, col.measure(r, col) + cellPad);
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1594
1809
|
const v = r[col.key];
|
|
1595
1810
|
if (v === null || v === void 0 || v === ``) continue;
|
|
1596
|
-
const w = measure(String(this._formatValue(v, col, r))) + cellPad;
|
|
1811
|
+
const w = measure(String(this._formatValue(v, col, r))) + cellPad + extra;
|
|
1597
1812
|
if (w > maxW) maxW = w;
|
|
1598
1813
|
}
|
|
1599
1814
|
const min = col.minWidth ?? 40;
|
|
@@ -1604,6 +1819,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1604
1819
|
const w = this._measureAutoWidth(col);
|
|
1605
1820
|
col.width = w;
|
|
1606
1821
|
this.requestUpdate();
|
|
1822
|
+
this.#viewState.notify();
|
|
1607
1823
|
this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
|
|
1608
1824
|
detail: {
|
|
1609
1825
|
key: col.key,
|
|
@@ -1616,20 +1832,22 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1616
1832
|
/**
|
|
1617
1833
|
* Resize a single column, by key, to fit its contents (header label + every
|
|
1618
1834
|
* row's formatted value), animating the width change smoothly. Only works
|
|
1619
|
-
* for plain columns
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1835
|
+
* for plain columns, or `renderer` columns that supply `measure` or
|
|
1836
|
+
* `fixedExtraWidth` - otherwise a renderer's output can't be measured
|
|
1837
|
+
* reliably from the raw value, so it's left untouched. Returns `false` (and
|
|
1838
|
+
* does nothing) if the column doesn't exist, isn't resizable, or has an
|
|
1839
|
+
* unmeasurable `renderer`.
|
|
1623
1840
|
*/
|
|
1624
1841
|
autoSizeColumn(key) {
|
|
1625
1842
|
const col = this.columns.find((c) => c.key === key);
|
|
1626
|
-
if (!col || col.resizable === false || col.renderer) return false;
|
|
1843
|
+
if (!col || col.resizable === false || col.renderer && !col.measure && col.fixedExtraWidth === void 0) return false;
|
|
1627
1844
|
const target = this._measureAutoWidth(col);
|
|
1628
1845
|
if ((col.width ?? 150) === target) return true;
|
|
1629
1846
|
this._animatingColKeys = new Set(this._animatingColKeys).add(key);
|
|
1630
1847
|
requestAnimationFrame(() => {
|
|
1631
1848
|
col.width = target;
|
|
1632
1849
|
this.requestUpdate();
|
|
1850
|
+
this.#viewState.notify();
|
|
1633
1851
|
this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
|
|
1634
1852
|
detail: {
|
|
1635
1853
|
key,
|
|
@@ -1666,6 +1884,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1666
1884
|
window.removeEventListener(`pointerup`, onUp);
|
|
1667
1885
|
this._resizingColKey = void 0;
|
|
1668
1886
|
const w = Math.max(col.minWidth ?? 40, Math.min(col.maxWidth ?? 600, startW + ev.clientX - startX));
|
|
1887
|
+
this.#viewState.notify();
|
|
1669
1888
|
this.dispatchEvent(new CustomEvent(`datagrid-column-resize`, {
|
|
1670
1889
|
detail: {
|
|
1671
1890
|
key: col.key,
|
|
@@ -1678,6 +1897,179 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1678
1897
|
window.addEventListener(`pointermove`, onMove);
|
|
1679
1898
|
window.addEventListener(`pointerup`, onUp);
|
|
1680
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
|
+
}
|
|
1681
2073
|
/** Height of the row area: the viewport minus the sticky header row inside it. */
|
|
1682
2074
|
#bodyHeight() {
|
|
1683
2075
|
return this._viewportEl.clientHeight - (this._headerRowEl?.offsetHeight ?? 0);
|
|
@@ -1962,6 +2354,30 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1962
2354
|
${this.search.renderField()}
|
|
1963
2355
|
</div>
|
|
1964
2356
|
`;
|
|
2357
|
+
}
|
|
2358
|
+
#renderColumnCheckItem(col) {
|
|
2359
|
+
return html`<ixfx-menu-check-item command="col:${col.key}" label=${col.label} ?checked=${col.visible !== false}></ixfx-menu-check-item>`;
|
|
2360
|
+
}
|
|
2361
|
+
/** Ungrouped columns first, then one submenu per `group` (in first-seen order). */
|
|
2362
|
+
#renderColumnMenuItems() {
|
|
2363
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2364
|
+
const ungrouped = [];
|
|
2365
|
+
for (const col of this.columns) {
|
|
2366
|
+
if (col.group === void 0 || col.group === ``) {
|
|
2367
|
+
ungrouped.push(col);
|
|
2368
|
+
continue;
|
|
2369
|
+
}
|
|
2370
|
+
groups.set(col.group, [...groups.get(col.group) ?? [], col]);
|
|
2371
|
+
}
|
|
2372
|
+
return html`
|
|
2373
|
+
${repeat(ungrouped, (c) => c.key, (c) => this.#renderColumnCheckItem(c))}
|
|
2374
|
+
${repeat([...groups], ([name]) => name, ([name, cols]) => html`
|
|
2375
|
+
<ixfx-menu-sub-trigger label=${name}>
|
|
2376
|
+
<ixfx-menu-container slot="submenu">
|
|
2377
|
+
${repeat(cols, (c) => c.key, (c) => this.#renderColumnCheckItem(c))}
|
|
2378
|
+
</ixfx-menu-container>
|
|
2379
|
+
</ixfx-menu-sub-trigger>
|
|
2380
|
+
`)}`;
|
|
1965
2381
|
}
|
|
1966
2382
|
/** Built-in column/group menu, pinned over the header row's end. Only for `columnMenu: auto`. */
|
|
1967
2383
|
#renderColumnMenu() {
|
|
@@ -1983,6 +2399,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1983
2399
|
const v = value ?? ``;
|
|
1984
2400
|
this.groupBy = v || null;
|
|
1985
2401
|
this._collapsedGroups = /* @__PURE__ */ new Set();
|
|
2402
|
+
this.#viewState.notify();
|
|
1986
2403
|
this.dispatchEvent(new CustomEvent(`datagrid-group-change`, {
|
|
1987
2404
|
detail: { groupBy: this.groupBy },
|
|
1988
2405
|
bubbles: true,
|
|
@@ -1993,9 +2410,7 @@ let DataGridElement = class DataGridElement extends LitElement {
|
|
|
1993
2410
|
}}>
|
|
1994
2411
|
<ixfx-menu-container slot="menu">
|
|
1995
2412
|
<ixfx-menu-header label="Columns"></ixfx-menu-header>
|
|
1996
|
-
${
|
|
1997
|
-
<ixfx-menu-check-item command="col:${col.key}" label=${col.label} ?checked=${col.visible !== false}></ixfx-menu-check-item>
|
|
1998
|
-
`)}
|
|
2413
|
+
${this.#renderColumnMenuItems()}
|
|
1999
2414
|
${this.allowGrouping ? html`
|
|
2000
2415
|
<ixfx-menu-separator></ixfx-menu-separator>
|
|
2001
2416
|
<ixfx-menu-header label="Group by"></ixfx-menu-header>
|