@ixfx/components 0.11.6 → 0.11.8
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 +213 -2
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +1766 -165
- package/bundle/index.js.map +1 -1
- package/dist/checkbox.d.ts +3 -0
- package/dist/checkbox.d.ts.map +1 -1
- package/dist/checkbox.js +24 -1
- package/dist/checkbox.js.map +1 -1
- package/dist/checked-select-CglRukbP.d.ts +59 -0
- package/dist/checked-select-CglRukbP.d.ts.map +1 -0
- package/dist/checked-select-Dejl13qd.js +604 -0
- package/dist/checked-select-Dejl13qd.js.map +1 -0
- package/dist/checked-select.d.ts +2 -0
- package/dist/checked-select.js +3 -0
- package/dist/{detail-list-CmdCxddo.d.ts → detail-list-lE_5UWVl.d.ts} +36 -3
- package/dist/detail-list-lE_5UWVl.d.ts.map +1 -0
- package/dist/detail-list.d.ts +3 -2
- package/dist/detail-list.js +293 -6
- package/dist/detail-list.js.map +1 -1
- package/dist/{grid-list-D6AfWfwn.d.ts → grid-list-Bc-RkDoO.d.ts} +42 -1
- package/dist/grid-list-Bc-RkDoO.d.ts.map +1 -0
- package/dist/{grid-list-3fGhdaFA.js → grid-list-Bwaf61is.js} +659 -158
- package/dist/grid-list-Bwaf61is.js.map +1 -0
- package/dist/grid-list.d.ts +3 -2
- package/dist/grid-list.js +2 -1
- package/dist/grouping-CUKgmkMm.d.ts +63 -0
- package/dist/grouping-CUKgmkMm.d.ts.map +1 -0
- package/dist/grouping-RYPnHkrS.js +53 -0
- package/dist/grouping-RYPnHkrS.js.map +1 -0
- package/dist/{hex-editor-BiRfwTz1.js → hex-editor-DiYpgIKs.js} +2 -2
- package/dist/{hex-editor-BiRfwTz1.js.map → hex-editor-DiYpgIKs.js.map} +1 -1
- package/dist/hex.js +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -6
- package/dist/index.js.map +1 -1
- package/dist/list-group-header.d.ts +26 -0
- package/dist/list-group-header.d.ts.map +1 -0
- package/dist/list-group-header.js +122 -0
- package/dist/list-group-header.js.map +1 -0
- package/dist/{list-selection-controller-SA4piSBi.js → list-selection-controller-BeE70lbB.js} +42 -4
- package/dist/list-selection-controller-BeE70lbB.js.map +1 -0
- package/dist/plots.js +1 -1
- package/dist/vertical-list.js +1 -1
- package/dist/{xy-axis-COKw338Q.js → xy-axis-CyMd_Bn1.js} +2 -2
- package/dist/{xy-axis-COKw338Q.js.map → xy-axis-CyMd_Bn1.js.map} +1 -1
- package/docs-user/README.md +2 -0
- package/docs-user/checked-select.md +63 -0
- package/docs-user/detail-list.md +28 -0
- package/docs-user/grid-list.md +35 -3
- package/docs-user/index.json +12 -2
- package/docs-user/list-group-header.md +14 -0
- package/docs-user/llms.txt +2 -0
- package/llms.txt +2 -0
- package/package.json +9 -1
- package/dist/detail-list-CmdCxddo.d.ts.map +0 -1
- package/dist/grid-list-3fGhdaFA.js.map +0 -1
- package/dist/grid-list-D6AfWfwn.d.ts.map +0 -1
- package/dist/list-selection-controller-SA4piSBi.js.map +0 -1
|
@@ -3,19 +3,388 @@ import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrA
|
|
|
3
3
|
import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
|
|
4
4
|
import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
|
|
5
5
|
import { c as createElementListSearchAdapter, d as createDataListSearchAdapter, i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, u as keyForElement } from "./incr-search-D91M4Ozq.js";
|
|
6
|
+
import { i as resolveGroupKey, r as partitionByGroup } from "./grouping-RYPnHkrS.js";
|
|
6
7
|
import { t as ListNativeDragController } from "./list-native-drag-controller-B9xO0XWn.js";
|
|
7
|
-
import { r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-
|
|
8
|
+
import { r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-BeE70lbB.js";
|
|
8
9
|
import { i as listItemFadeKeyframes } from "./list-item-animation-BUoEKi5s.js";
|
|
9
10
|
import "./interaction-BskmlA9t.js";
|
|
10
11
|
import { t as scrollFadeStyles } from "./scroll-fade-BrNUIYnm.js";
|
|
11
12
|
import { n as appearanceColor, t as appearanceAbsent } from "./item-appearance-BjcMyauw.js";
|
|
12
13
|
import "./pan-text-CqtvjPyK.js";
|
|
14
|
+
import "./list-group-header.js";
|
|
13
15
|
import { LitElement, css, html, nothing } from "lit";
|
|
14
16
|
import { property, state } from "lit/decorators.js";
|
|
15
17
|
import { createCommandRegistry } from "@clinth/ui-commands";
|
|
16
18
|
import { repeat } from "lit/directives/repeat.js";
|
|
17
19
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
20
|
+
//#region src/grid-list/grid-list-layout.ts
|
|
21
|
+
/**
|
|
22
|
+
* `itemWidth` is the square's width; `rowHeight` is the full cell height
|
|
23
|
+
* (square, plus the caption band in `separate` / `under` styles).
|
|
24
|
+
* `scrollOffset` is `scrollTop` for `grid`/`column` and `scrollLeft` for `row`.
|
|
25
|
+
*/
|
|
26
|
+
function computeGridWindow(count, itemWidth, rowHeight, gap, viewportWidth, viewportHeight, scrollOffset, orientation, overscan = 2) {
|
|
27
|
+
const w = Math.max(1, itemWidth);
|
|
28
|
+
const h = Math.max(1, rowHeight);
|
|
29
|
+
const g = Math.max(0, gap);
|
|
30
|
+
const colStride = w + g;
|
|
31
|
+
const rowStride = h + g;
|
|
32
|
+
const offset = Math.max(0, scrollOffset);
|
|
33
|
+
if (orientation === `row`) {
|
|
34
|
+
const rows = count === 0 ? 0 : 1;
|
|
35
|
+
const totalWidth = count === 0 ? 0 : (count - 1) * colStride + w;
|
|
36
|
+
if (count === 0 || viewportWidth <= 0) return {
|
|
37
|
+
cols: count,
|
|
38
|
+
rows,
|
|
39
|
+
totalHeight: 0,
|
|
40
|
+
totalWidth,
|
|
41
|
+
startIndex: 0,
|
|
42
|
+
endIndex: 0,
|
|
43
|
+
colStride,
|
|
44
|
+
rowStride,
|
|
45
|
+
horizontal: true
|
|
46
|
+
};
|
|
47
|
+
const firstVisible = Math.max(0, Math.floor(offset / colStride));
|
|
48
|
+
const lastVisible = Math.ceil((offset + viewportWidth) / colStride);
|
|
49
|
+
return {
|
|
50
|
+
cols: count,
|
|
51
|
+
rows,
|
|
52
|
+
totalHeight: 0,
|
|
53
|
+
totalWidth,
|
|
54
|
+
startIndex: Math.max(0, firstVisible - overscan),
|
|
55
|
+
endIndex: Math.min(count, lastVisible + overscan),
|
|
56
|
+
colStride,
|
|
57
|
+
rowStride,
|
|
58
|
+
horizontal: true
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const cols = orientation === `column` ? 1 : Math.max(1, Math.floor((Math.max(0, viewportWidth) + g) / colStride));
|
|
62
|
+
const rows = count === 0 ? 0 : Math.ceil(count / cols);
|
|
63
|
+
const totalHeight = rows === 0 ? 0 : (rows - 1) * rowStride + h;
|
|
64
|
+
if (count === 0 || viewportHeight <= 0) return {
|
|
65
|
+
cols,
|
|
66
|
+
rows,
|
|
67
|
+
totalHeight,
|
|
68
|
+
totalWidth: 0,
|
|
69
|
+
startIndex: 0,
|
|
70
|
+
endIndex: 0,
|
|
71
|
+
colStride,
|
|
72
|
+
rowStride,
|
|
73
|
+
horizontal: false
|
|
74
|
+
};
|
|
75
|
+
const firstVisibleRow = Math.max(0, Math.floor(offset / rowStride));
|
|
76
|
+
const lastVisibleRow = Math.ceil((offset + viewportHeight) / rowStride);
|
|
77
|
+
const rowStart = Math.max(0, firstVisibleRow - overscan);
|
|
78
|
+
const rowEnd = Math.min(rows, lastVisibleRow + overscan);
|
|
79
|
+
return {
|
|
80
|
+
cols,
|
|
81
|
+
rows,
|
|
82
|
+
totalHeight,
|
|
83
|
+
totalWidth: 0,
|
|
84
|
+
startIndex: rowStart * cols,
|
|
85
|
+
endIndex: Math.min(count, rowEnd * cols),
|
|
86
|
+
colStride,
|
|
87
|
+
rowStride,
|
|
88
|
+
horizontal: false
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function buildSections(p, cols, stride, itemExtent) {
|
|
92
|
+
const g = Math.max(0, p.gap);
|
|
93
|
+
const horizontal = p.orientation === `row`;
|
|
94
|
+
const lineCount = (n) => horizontal ? n : Math.ceil(n / cols);
|
|
95
|
+
const extentFor = (n) => n === 0 ? 0 : (lineCount(n) - 1) * stride + itemExtent;
|
|
96
|
+
if (!p.groups) {
|
|
97
|
+
const end = extentFor(p.count);
|
|
98
|
+
return {
|
|
99
|
+
sections: [{
|
|
100
|
+
group: void 0,
|
|
101
|
+
first: 0,
|
|
102
|
+
count: p.count,
|
|
103
|
+
headerOffset: 0,
|
|
104
|
+
itemsOffset: 0,
|
|
105
|
+
end
|
|
106
|
+
}],
|
|
107
|
+
total: end,
|
|
108
|
+
count: p.count
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const headerSize = horizontal ? p.headerWidth : p.headerHeight;
|
|
112
|
+
const sections = [];
|
|
113
|
+
let cursor = 0;
|
|
114
|
+
let first = 0;
|
|
115
|
+
for (const group of p.groups) {
|
|
116
|
+
const count = group.collapsed ? 0 : group.count;
|
|
117
|
+
const itemsOffset = cursor + headerSize + g;
|
|
118
|
+
const end = count === 0 ? cursor + headerSize : itemsOffset + extentFor(count);
|
|
119
|
+
sections.push({
|
|
120
|
+
group,
|
|
121
|
+
first,
|
|
122
|
+
count,
|
|
123
|
+
headerOffset: cursor,
|
|
124
|
+
itemsOffset,
|
|
125
|
+
end
|
|
126
|
+
});
|
|
127
|
+
first += count;
|
|
128
|
+
cursor = end + g;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
sections,
|
|
132
|
+
total: sections.length === 0 ? 0 : sections[sections.length - 1].end,
|
|
133
|
+
count: first
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** Index of the last section whose `headerOffset` is <= `offset` (0 when before all). */
|
|
137
|
+
function sectionAtOffset(sections, offset) {
|
|
138
|
+
let lo = 0;
|
|
139
|
+
let hi = sections.length - 1;
|
|
140
|
+
while (lo < hi) {
|
|
141
|
+
const mid = lo + hi + 1 >> 1;
|
|
142
|
+
if (sections[mid].headerOffset <= offset) lo = mid;
|
|
143
|
+
else hi = mid - 1;
|
|
144
|
+
}
|
|
145
|
+
return lo;
|
|
146
|
+
}
|
|
147
|
+
/** Index of the section holding effective item `index` (skips empty sections). */
|
|
148
|
+
function sectionOfIndex(sections, index) {
|
|
149
|
+
let lo = 0;
|
|
150
|
+
let hi = sections.length - 1;
|
|
151
|
+
while (lo < hi) {
|
|
152
|
+
const mid = lo + hi + 1 >> 1;
|
|
153
|
+
if (sections[mid].first <= index) lo = mid;
|
|
154
|
+
else hi = mid - 1;
|
|
155
|
+
}
|
|
156
|
+
let s = lo;
|
|
157
|
+
while (s < sections.length - 1 && sections[s].count === 0) s++;
|
|
158
|
+
return s;
|
|
159
|
+
}
|
|
160
|
+
function buildLayout(p) {
|
|
161
|
+
const horizontal = p.orientation === `row`;
|
|
162
|
+
const w = Math.max(1, p.itemWidth);
|
|
163
|
+
const h = Math.max(1, p.rowHeight);
|
|
164
|
+
const g = Math.max(0, p.gap);
|
|
165
|
+
const colStride = w + g;
|
|
166
|
+
const rowStride = h + g;
|
|
167
|
+
const cols = horizontal ? 1 : p.orientation === `column` ? 1 : Math.max(1, Math.floor((Math.max(0, p.viewportWidth) + g) / colStride));
|
|
168
|
+
const stride = horizontal ? colStride : rowStride;
|
|
169
|
+
const itemExtent = horizontal ? w : h;
|
|
170
|
+
const { sections, total, count } = buildSections(p, cols, stride, itemExtent);
|
|
171
|
+
const grouped = p.groups !== void 0;
|
|
172
|
+
const headerSize = horizontal ? p.headerWidth : p.headerHeight;
|
|
173
|
+
const viewportMain = horizontal ? p.viewportWidth : p.viewportHeight;
|
|
174
|
+
const cellHeight = rowStride - g;
|
|
175
|
+
const rowY = Math.max(0, (p.viewportHeight - cellHeight) / 2);
|
|
176
|
+
const positionOf = (index) => {
|
|
177
|
+
const si = sectionOfIndex(sections, index);
|
|
178
|
+
const s = sections[si];
|
|
179
|
+
const local = Math.max(0, index - s.first);
|
|
180
|
+
if (horizontal) return {
|
|
181
|
+
x: s.itemsOffset + local * colStride,
|
|
182
|
+
y: rowY,
|
|
183
|
+
col: 0,
|
|
184
|
+
line: local,
|
|
185
|
+
section: si
|
|
186
|
+
};
|
|
187
|
+
const col = cols <= 1 ? 0 : local % cols;
|
|
188
|
+
const line = cols <= 1 ? local : Math.floor(local / cols);
|
|
189
|
+
return {
|
|
190
|
+
x: col * colStride,
|
|
191
|
+
y: s.itemsOffset + line * rowStride,
|
|
192
|
+
col,
|
|
193
|
+
line,
|
|
194
|
+
section: si
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
const window = (offset, overscan = 2) => {
|
|
198
|
+
const off = Math.max(0, offset);
|
|
199
|
+
const headers = [];
|
|
200
|
+
const ranges = [];
|
|
201
|
+
const lo = off - overscan * stride;
|
|
202
|
+
const hi = off + viewportMain + overscan * stride;
|
|
203
|
+
let firstSection = 0;
|
|
204
|
+
if (sections.length > 1) {
|
|
205
|
+
let a = 0;
|
|
206
|
+
let b = sections.length - 1;
|
|
207
|
+
while (a < b) {
|
|
208
|
+
const mid = a + b >> 1;
|
|
209
|
+
if (sections[mid].end >= lo) b = mid;
|
|
210
|
+
else a = mid + 1;
|
|
211
|
+
}
|
|
212
|
+
firstSection = a;
|
|
213
|
+
}
|
|
214
|
+
if (viewportMain > 0) for (let si = firstSection; si < sections.length; si++) {
|
|
215
|
+
const s = sections[si];
|
|
216
|
+
if (s.headerOffset > hi) break;
|
|
217
|
+
if (s.group && s.headerOffset + headerSize >= lo) headers.push({
|
|
218
|
+
key: s.group.key,
|
|
219
|
+
label: s.group.label,
|
|
220
|
+
count: s.group.count,
|
|
221
|
+
collapsed: s.group.collapsed,
|
|
222
|
+
offset: s.headerOffset,
|
|
223
|
+
size: headerSize
|
|
224
|
+
});
|
|
225
|
+
if (s.count === 0) continue;
|
|
226
|
+
const rel = off - s.itemsOffset;
|
|
227
|
+
const viewport = rel < 0 ? viewportMain + rel : viewportMain;
|
|
228
|
+
if (viewport <= 0) continue;
|
|
229
|
+
const sw = computeGridWindow(s.count, w, h, g, horizontal ? viewport : p.viewportWidth, horizontal ? p.viewportHeight : viewport, Math.max(0, rel), p.orientation, overscan);
|
|
230
|
+
if (sw.endIndex > sw.startIndex) ranges.push([s.first + sw.startIndex, s.first + sw.endIndex]);
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
cols: horizontal ? count : cols,
|
|
234
|
+
rows: 0,
|
|
235
|
+
totalHeight: horizontal ? 0 : total,
|
|
236
|
+
totalWidth: horizontal ? total : 0,
|
|
237
|
+
startIndex: ranges.length > 0 ? ranges[0][0] : 0,
|
|
238
|
+
endIndex: ranges.length > 0 ? ranges[ranges.length - 1][1] : 0,
|
|
239
|
+
colStride,
|
|
240
|
+
rowStride,
|
|
241
|
+
horizontal,
|
|
242
|
+
headers,
|
|
243
|
+
ranges
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
const extentOf = (index) => {
|
|
247
|
+
const { line, section } = positionOf(index);
|
|
248
|
+
const s = sections[section];
|
|
249
|
+
const start = s.itemsOffset + line * stride;
|
|
250
|
+
return [line === 0 && s.group ? s.headerOffset : start, start + itemExtent];
|
|
251
|
+
};
|
|
252
|
+
const neighbour = (index, dLine) => {
|
|
253
|
+
if (count === 0) return index;
|
|
254
|
+
const si = sectionOfIndex(sections, index);
|
|
255
|
+
const s = sections[si];
|
|
256
|
+
const local = index - s.first;
|
|
257
|
+
const col = local % cols;
|
|
258
|
+
if (dLine > 0) {
|
|
259
|
+
const t = local + cols;
|
|
260
|
+
if (t <= s.count - 1) return s.first + t;
|
|
261
|
+
const lastLine = Math.floor((s.count - 1) / cols);
|
|
262
|
+
if (Math.floor(local / cols) < lastLine) return s.first + s.count - 1;
|
|
263
|
+
for (let n = si + 1; n < sections.length; n++) if (sections[n].count > 0) return sections[n].first + Math.min(col, sections[n].count - 1);
|
|
264
|
+
return s.first + s.count - 1;
|
|
265
|
+
}
|
|
266
|
+
const t = local - cols;
|
|
267
|
+
if (t >= 0) return s.first + t;
|
|
268
|
+
for (let n = si - 1; n >= 0; n--) {
|
|
269
|
+
const prev = sections[n];
|
|
270
|
+
if (prev.count > 0) {
|
|
271
|
+
const line = Math.floor((prev.count - 1) / cols);
|
|
272
|
+
return prev.first + Math.min(line * cols + col, prev.count - 1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return s.first;
|
|
276
|
+
};
|
|
277
|
+
const hitTest = (x0, x1, y0, y1) => {
|
|
278
|
+
const out = [];
|
|
279
|
+
if (count === 0) return out;
|
|
280
|
+
const main0 = horizontal ? x0 : y0;
|
|
281
|
+
const main1 = horizontal ? x1 : y1;
|
|
282
|
+
const start = sectionAtOffset(sections, main0 - stride);
|
|
283
|
+
for (let si = Math.max(0, start); si < sections.length; si++) {
|
|
284
|
+
const s = sections[si];
|
|
285
|
+
if (s.itemsOffset > main1) break;
|
|
286
|
+
if (s.count === 0 || s.end < main0) continue;
|
|
287
|
+
if (horizontal) {
|
|
288
|
+
const a = Math.max(0, Math.floor((x0 - s.itemsOffset) / colStride));
|
|
289
|
+
const b = Math.min(s.count - 1, Math.floor((x1 - s.itemsOffset) / colStride));
|
|
290
|
+
for (let i = a; i <= b; i++) out.push(s.first + i);
|
|
291
|
+
} else {
|
|
292
|
+
const colLo = Math.max(0, Math.floor(x0 / colStride));
|
|
293
|
+
const colHi = Math.min(cols - 1, Math.floor(x1 / colStride));
|
|
294
|
+
const rowLo = Math.max(0, Math.floor((y0 - s.itemsOffset) / rowStride));
|
|
295
|
+
const rowHi = Math.floor((y1 - s.itemsOffset) / rowStride);
|
|
296
|
+
for (let row = rowLo; row <= rowHi; row++) for (let col = colLo; col <= colHi; col++) {
|
|
297
|
+
const local = row * cols + col;
|
|
298
|
+
if (local >= 0 && local < s.count) out.push(s.first + local);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return out;
|
|
303
|
+
};
|
|
304
|
+
const anchorAt = (mainOffset) => {
|
|
305
|
+
if (count === 0) return {
|
|
306
|
+
index: 0,
|
|
307
|
+
lineFrac: 0
|
|
308
|
+
};
|
|
309
|
+
let si = sectionAtOffset(sections, mainOffset);
|
|
310
|
+
while (si < sections.length - 1 && sections[si].count === 0) si++;
|
|
311
|
+
const s = sections[si];
|
|
312
|
+
if (s.count === 0) return {
|
|
313
|
+
index: 0,
|
|
314
|
+
lineFrac: 0
|
|
315
|
+
};
|
|
316
|
+
const centreLine = Math.max(0, mainOffset - s.itemsOffset) / stride;
|
|
317
|
+
const lineFloor = Math.max(0, Math.floor(centreLine));
|
|
318
|
+
const local = horizontal ? Math.min(s.count - 1, lineFloor) : Math.min(s.count - 1, lineFloor * cols + Math.max(0, Math.floor((cols - 1) / 2)));
|
|
319
|
+
return {
|
|
320
|
+
index: s.first + local,
|
|
321
|
+
lineFrac: centreLine - lineFloor
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
const offsetOfAnchor = (index, lineFrac) => {
|
|
325
|
+
const { line, section } = positionOf(index);
|
|
326
|
+
return sections[section].itemsOffset + (line + lineFrac) * stride;
|
|
327
|
+
};
|
|
328
|
+
const snapTarget = (offset) => {
|
|
329
|
+
const si = sectionAtOffset(sections, offset);
|
|
330
|
+
const s = sections[si];
|
|
331
|
+
let rel = Math.round((offset - s.itemsOffset) / stride);
|
|
332
|
+
if (grouped) {
|
|
333
|
+
const lines = s.count === 0 ? 0 : horizontal ? s.count : Math.ceil(s.count / cols);
|
|
334
|
+
rel = Math.max(0, Math.min(Math.max(0, lines - 1), rel));
|
|
335
|
+
}
|
|
336
|
+
const candidates = [s.itemsOffset + rel * stride];
|
|
337
|
+
if (grouped) {
|
|
338
|
+
candidates.push(s.headerOffset);
|
|
339
|
+
if (si + 1 < sections.length) candidates.push(sections[si + 1].headerOffset);
|
|
340
|
+
}
|
|
341
|
+
return candidates.reduce((best, c) => Math.abs(c - offset) < Math.abs(best - offset) ? c : best);
|
|
342
|
+
};
|
|
343
|
+
return {
|
|
344
|
+
horizontal,
|
|
345
|
+
cols,
|
|
346
|
+
colStride,
|
|
347
|
+
rowStride,
|
|
348
|
+
grouped,
|
|
349
|
+
count,
|
|
350
|
+
totalExtent: total,
|
|
351
|
+
window,
|
|
352
|
+
positionOf,
|
|
353
|
+
extentOf,
|
|
354
|
+
neighbour,
|
|
355
|
+
hitTest,
|
|
356
|
+
anchorAt,
|
|
357
|
+
offsetOfAnchor,
|
|
358
|
+
snapTarget
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
18
362
|
//#region src/grid-list/grid-list-source.ts
|
|
363
|
+
/** Reorder `effective` raw indices into contiguous groups, dropping collapsed ones. */
|
|
364
|
+
function applyGrouping(effective, cfg, keyOfRaw) {
|
|
365
|
+
const parts = partitionByGroup(effective, (raw) => cfg.groupKeyOf(raw), cfg.labelOf ? (key, raws) => cfg.labelOf(key, raws.map((r) => cfg.itemAt(r))) : void 0);
|
|
366
|
+
const order = [];
|
|
367
|
+
const groups = [];
|
|
368
|
+
const groupByKey = /* @__PURE__ */ new Map();
|
|
369
|
+
for (const part of parts) {
|
|
370
|
+
const collapsed = cfg.collapsed.has(part.key);
|
|
371
|
+
groups.push({
|
|
372
|
+
key: part.key,
|
|
373
|
+
label: part.label,
|
|
374
|
+
count: part.items.length,
|
|
375
|
+
collapsed
|
|
376
|
+
});
|
|
377
|
+
for (const raw of part.items) {
|
|
378
|
+
groupByKey.set(keyOfRaw(raw), part.key);
|
|
379
|
+
if (!collapsed) order.push(raw);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
order,
|
|
384
|
+
groups,
|
|
385
|
+
groupByKey
|
|
386
|
+
};
|
|
387
|
+
}
|
|
19
388
|
const ITEM_SELECTOR$1 = `:scope > *`;
|
|
20
389
|
const SYNTHETIC_PREFIX = `gl-auto-`;
|
|
21
390
|
/** Whether `key` is an auto-assigned key for a keyless light-DOM item. */
|
|
@@ -42,6 +411,9 @@ var ElementGridListSource = class {
|
|
|
42
411
|
#searchKeys;
|
|
43
412
|
#effectiveIndices = [];
|
|
44
413
|
#keysCache;
|
|
414
|
+
#grouping;
|
|
415
|
+
#groups = [];
|
|
416
|
+
#groupByKey = /* @__PURE__ */ new Map();
|
|
45
417
|
constructor(host) {
|
|
46
418
|
this.#host = host;
|
|
47
419
|
this.refresh();
|
|
@@ -94,6 +466,16 @@ var ElementGridListSource = class {
|
|
|
94
466
|
if (`searchKeys` in opts) this.#searchKeys = opts.searchKeys;
|
|
95
467
|
this.#recomputeEffective();
|
|
96
468
|
}
|
|
469
|
+
setGrouping(config) {
|
|
470
|
+
this.#grouping = config;
|
|
471
|
+
this.#recomputeEffective();
|
|
472
|
+
}
|
|
473
|
+
groups() {
|
|
474
|
+
return this.#groups;
|
|
475
|
+
}
|
|
476
|
+
groupKeyOfKey(key) {
|
|
477
|
+
return this.#groupByKey.get(key) ?? null;
|
|
478
|
+
}
|
|
97
479
|
refresh() {
|
|
98
480
|
this.#items = Array.from(this.#host.querySelectorAll(ITEM_SELECTOR$1)).filter((el) => !el.hidden);
|
|
99
481
|
for (const el of this.#items) {
|
|
@@ -136,9 +518,21 @@ var ElementGridListSource = class {
|
|
|
136
518
|
}
|
|
137
519
|
effective.push(i);
|
|
138
520
|
}
|
|
139
|
-
this.#
|
|
521
|
+
this.#applyGroupingTo(effective);
|
|
140
522
|
this.#keysCache = void 0;
|
|
141
523
|
}
|
|
524
|
+
#applyGroupingTo(effective) {
|
|
525
|
+
if (this.#grouping === void 0) {
|
|
526
|
+
this.#effectiveIndices = effective;
|
|
527
|
+
this.#groups = [];
|
|
528
|
+
this.#groupByKey = /* @__PURE__ */ new Map();
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const grouped = applyGrouping(effective, this.#grouping, (raw) => this.#itemKey(this.#items[raw]));
|
|
532
|
+
this.#effectiveIndices = grouped.order;
|
|
533
|
+
this.#groups = grouped.groups;
|
|
534
|
+
this.#groupByKey = grouped.groupByKey;
|
|
535
|
+
}
|
|
142
536
|
#itemKey(el) {
|
|
143
537
|
const userKey = keyForElement(el);
|
|
144
538
|
if (userKey !== void 0) return userKey;
|
|
@@ -161,6 +555,9 @@ var DataGridListSource = class {
|
|
|
161
555
|
#searchKeys;
|
|
162
556
|
#effectiveIndices = [];
|
|
163
557
|
#keysCache;
|
|
558
|
+
#grouping;
|
|
559
|
+
#groups = [];
|
|
560
|
+
#groupByKey = /* @__PURE__ */ new Map();
|
|
164
561
|
constructor(items, adapter) {
|
|
165
562
|
this.#items = items;
|
|
166
563
|
this.#adapter = adapter;
|
|
@@ -226,6 +623,16 @@ var DataGridListSource = class {
|
|
|
226
623
|
if (`searchKeys` in opts) this.#searchKeys = opts.searchKeys;
|
|
227
624
|
this.#recomputeEffective();
|
|
228
625
|
}
|
|
626
|
+
setGrouping(config) {
|
|
627
|
+
this.#grouping = config;
|
|
628
|
+
this.#recomputeEffective();
|
|
629
|
+
}
|
|
630
|
+
groups() {
|
|
631
|
+
return this.#groups;
|
|
632
|
+
}
|
|
633
|
+
groupKeyOfKey(key) {
|
|
634
|
+
return this.#groupByKey.get(key) ?? null;
|
|
635
|
+
}
|
|
229
636
|
refresh() {
|
|
230
637
|
this.#recomputeEffective();
|
|
231
638
|
}
|
|
@@ -250,7 +657,16 @@ var DataGridListSource = class {
|
|
|
250
657
|
if (this.#searchKeys !== void 0 && !this.#searchKeys.has(this.#adapter.key(this.#items[i], i))) continue;
|
|
251
658
|
effective.push(i);
|
|
252
659
|
}
|
|
253
|
-
this.#
|
|
660
|
+
if (this.#grouping === void 0) {
|
|
661
|
+
this.#effectiveIndices = effective;
|
|
662
|
+
this.#groups = [];
|
|
663
|
+
this.#groupByKey = /* @__PURE__ */ new Map();
|
|
664
|
+
} else {
|
|
665
|
+
const grouped = applyGrouping(effective, this.#grouping, (raw) => this.#adapter.key(this.#items[raw], raw));
|
|
666
|
+
this.#effectiveIndices = grouped.order;
|
|
667
|
+
this.#groups = grouped.groups;
|
|
668
|
+
this.#groupByKey = grouped.groupByKey;
|
|
669
|
+
}
|
|
254
670
|
this.#keysCache = void 0;
|
|
255
671
|
}
|
|
256
672
|
};
|
|
@@ -266,76 +682,11 @@ const SNAP_DEBOUNCE_MS = 120;
|
|
|
266
682
|
* to finish. Kept a touch above the default `--grid-list-item-move-duration`.
|
|
267
683
|
*/
|
|
268
684
|
const REFLOW_WINDOW_MS = 320;
|
|
269
|
-
function
|
|
270
|
-
|
|
271
|
-
const h = Math.max(1, rowHeight);
|
|
272
|
-
const g = Math.max(0, gap);
|
|
273
|
-
const colStride = w + g;
|
|
274
|
-
const rowStride = h + g;
|
|
275
|
-
const offset = Math.max(0, scrollOffset);
|
|
276
|
-
if (orientation === `row`) {
|
|
277
|
-
const rows = count === 0 ? 0 : 1;
|
|
278
|
-
const totalWidth = count === 0 ? 0 : (count - 1) * colStride + w;
|
|
279
|
-
if (count === 0 || viewportWidth <= 0) return {
|
|
280
|
-
cols: count,
|
|
281
|
-
rows,
|
|
282
|
-
totalHeight: 0,
|
|
283
|
-
totalWidth,
|
|
284
|
-
startIndex: 0,
|
|
285
|
-
endIndex: 0,
|
|
286
|
-
colStride,
|
|
287
|
-
rowStride,
|
|
288
|
-
horizontal: true
|
|
289
|
-
};
|
|
290
|
-
const firstVisible = Math.max(0, Math.floor(offset / colStride));
|
|
291
|
-
const lastVisible = Math.ceil((offset + viewportWidth) / colStride);
|
|
292
|
-
return {
|
|
293
|
-
cols: count,
|
|
294
|
-
rows,
|
|
295
|
-
totalHeight: 0,
|
|
296
|
-
totalWidth,
|
|
297
|
-
startIndex: Math.max(0, firstVisible - overscan),
|
|
298
|
-
endIndex: Math.min(count, lastVisible + overscan),
|
|
299
|
-
colStride,
|
|
300
|
-
rowStride,
|
|
301
|
-
horizontal: true
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
const cols = orientation === `column` ? 1 : Math.max(1, Math.floor((Math.max(0, viewportWidth) + g) / colStride));
|
|
305
|
-
const rows = count === 0 ? 0 : Math.ceil(count / cols);
|
|
306
|
-
const totalHeight = rows === 0 ? 0 : (rows - 1) * rowStride + h;
|
|
307
|
-
if (count === 0 || viewportHeight <= 0) return {
|
|
308
|
-
cols,
|
|
309
|
-
rows,
|
|
310
|
-
totalHeight,
|
|
311
|
-
totalWidth: 0,
|
|
312
|
-
startIndex: 0,
|
|
313
|
-
endIndex: 0,
|
|
314
|
-
colStride,
|
|
315
|
-
rowStride,
|
|
316
|
-
horizontal: false
|
|
317
|
-
};
|
|
318
|
-
const firstVisibleRow = Math.max(0, Math.floor(offset / rowStride));
|
|
319
|
-
const lastVisibleRow = Math.ceil((offset + viewportHeight) / rowStride);
|
|
320
|
-
const rowStart = Math.max(0, firstVisibleRow - overscan);
|
|
321
|
-
const rowEnd = Math.min(rows, lastVisibleRow + overscan);
|
|
322
|
-
return {
|
|
323
|
-
cols,
|
|
324
|
-
rows,
|
|
325
|
-
totalHeight,
|
|
326
|
-
totalWidth: 0,
|
|
327
|
-
startIndex: rowStart * cols,
|
|
328
|
-
endIndex: Math.min(count, rowEnd * cols),
|
|
329
|
-
colStride,
|
|
330
|
-
rowStride,
|
|
331
|
-
horizontal: false
|
|
332
|
-
};
|
|
685
|
+
function readProperty(item, name) {
|
|
686
|
+
return typeof item === `object` && item !== null ? item[name] : void 0;
|
|
333
687
|
}
|
|
334
|
-
function
|
|
335
|
-
return
|
|
336
|
-
}
|
|
337
|
-
function rowOf(index, cols) {
|
|
338
|
-
return cols <= 1 ? index : Math.floor(index / cols);
|
|
688
|
+
function readAttribute(el, name) {
|
|
689
|
+
return el.getAttribute(`data-${name}`);
|
|
339
690
|
}
|
|
340
691
|
/** Shallow key-array equality, treating `undefined` (never dispatched) as different. */
|
|
341
692
|
function sameKeys(a, b) {
|
|
@@ -363,6 +714,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
363
714
|
this.marqueeFromCellBlank = false;
|
|
364
715
|
this.snapToItem = false;
|
|
365
716
|
this.items = [];
|
|
717
|
+
this.groupBy = null;
|
|
718
|
+
this.crossGroupSelection = false;
|
|
719
|
+
this.groupHeaderHeight = 28;
|
|
720
|
+
this.groupHeaderWidth = 120;
|
|
721
|
+
this._collapsedGroups = /* @__PURE__ */ new Set();
|
|
366
722
|
this._scrollTop = 0;
|
|
367
723
|
this._scrollLeft = 0;
|
|
368
724
|
this._viewportHeight = 0;
|
|
@@ -387,6 +743,12 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
387
743
|
interactionMode: this.interactionMode
|
|
388
744
|
}),
|
|
389
745
|
getKey: (k) => k,
|
|
746
|
+
getGroupKey: (k) => this.groupBy === null ? null : this.#source.groupKeyOfKey(k),
|
|
747
|
+
crossGroupSelection: () => this.crossGroupSelection,
|
|
748
|
+
getFallbackGroupKey: () => {
|
|
749
|
+
const k = this.tickled.tickledId;
|
|
750
|
+
return k !== void 0 && this.groupBy !== null ? this.#source.groupKeyOfKey(k) : null;
|
|
751
|
+
},
|
|
390
752
|
onChange: (selected, previous) => {
|
|
391
753
|
this._syncItemStates();
|
|
392
754
|
this.requestUpdate();
|
|
@@ -484,10 +846,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
484
846
|
},
|
|
485
847
|
shouldStart: (event) => {
|
|
486
848
|
if (this.marqueeFromCellBlank && this.#isCellBlankPress(event)) return true;
|
|
487
|
-
for (const node of event.composedPath()) if (node instanceof Element && node.classList.contains(`cell`)) return false;
|
|
849
|
+
for (const node of event.composedPath()) if (node instanceof Element && (node.classList.contains(`cell`) || node.classList.contains(`group-header`))) return false;
|
|
488
850
|
return true;
|
|
489
851
|
}
|
|
490
852
|
});
|
|
853
|
+
this.#groupByChanged = false;
|
|
491
854
|
this.#selectedSnapshot = /* @__PURE__ */ new Set();
|
|
492
855
|
this.#onScroll = (e) => {
|
|
493
856
|
const el = e.target;
|
|
@@ -537,15 +900,25 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
537
900
|
const count = this.#source.count;
|
|
538
901
|
if (count === 0) return;
|
|
539
902
|
const win = this.#currentWindow();
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
903
|
+
const layout = this.#layout();
|
|
904
|
+
const current = () => this.tickled.tickledId ? this.#source.indexOfKey(this.tickled.tickledId) : -1;
|
|
905
|
+
const goto = (nextIndex) => {
|
|
543
906
|
const nextKey = this.#source.keyAt(nextIndex);
|
|
544
907
|
if (nextKey) {
|
|
545
908
|
this.tickled.handleKeyboardNavigation(nextKey);
|
|
546
909
|
this.#scrollItemIntoView(nextKey);
|
|
547
910
|
}
|
|
548
911
|
};
|
|
912
|
+
const move = (delta) => goto(Math.max(0, Math.min(count - 1, current() + delta)));
|
|
913
|
+
const moveLines = (dir, lines = 1) => {
|
|
914
|
+
let index = current();
|
|
915
|
+
if (index < 0) {
|
|
916
|
+
move(dir * lines * win.cols);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
for (let i = 0; i < lines; i++) index = layout.neighbour(index, dir);
|
|
920
|
+
goto(index);
|
|
921
|
+
};
|
|
549
922
|
const perLine = this.orientation === `grid` ? win.cols : 1;
|
|
550
923
|
switch (e.key) {
|
|
551
924
|
case `ArrowLeft`:
|
|
@@ -555,10 +928,12 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
555
928
|
move(1);
|
|
556
929
|
break;
|
|
557
930
|
case `ArrowUp`:
|
|
558
|
-
|
|
931
|
+
if (this.orientation === `grid`) moveLines(-1);
|
|
932
|
+
else move(-perLine);
|
|
559
933
|
break;
|
|
560
934
|
case `ArrowDown`:
|
|
561
|
-
|
|
935
|
+
if (this.orientation === `grid`) moveLines(1);
|
|
936
|
+
else move(perLine);
|
|
562
937
|
break;
|
|
563
938
|
case `Home`:
|
|
564
939
|
this.tickled.handleKeyboardNavigation(`first`);
|
|
@@ -573,7 +948,8 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
573
948
|
const mainExtent = win.horizontal ? this._viewportWidth : this._viewportHeight;
|
|
574
949
|
const mainStride = win.horizontal ? win.colStride : win.rowStride;
|
|
575
950
|
const visibleLines = Math.max(1, Math.floor(mainExtent / mainStride));
|
|
576
|
-
|
|
951
|
+
if (this.orientation === `grid`) moveLines(e.key === `PageDown` ? 1 : -1, visibleLines);
|
|
952
|
+
else move((e.key === `PageDown` ? 1 : -1) * visibleLines * perLine);
|
|
577
953
|
break;
|
|
578
954
|
}
|
|
579
955
|
case `Enter`: {
|
|
@@ -654,6 +1030,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
654
1030
|
position: relative;
|
|
655
1031
|
width: 100%;
|
|
656
1032
|
}
|
|
1033
|
+
.group-header {
|
|
1034
|
+
position: absolute;
|
|
1035
|
+
inset: 0 auto auto 0;
|
|
1036
|
+
z-index: 1;
|
|
1037
|
+
}
|
|
657
1038
|
.cell {
|
|
658
1039
|
position: absolute;
|
|
659
1040
|
inset: 0 auto auto 0;
|
|
@@ -947,6 +1328,9 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
947
1328
|
specificity. Survivors slide to their new slot; a freshly inserted cell
|
|
948
1329
|
also fades in. (A removed item's element is already gone, so there's
|
|
949
1330
|
nothing left to fade — the gap simply closes as the others slide.) */
|
|
1331
|
+
:host([reflowing]) .group-header {
|
|
1332
|
+
transition: transform var(--grid-list-item-move-duration, 200ms) cubic-bezier(0.2, 0, 0, 1);
|
|
1333
|
+
}
|
|
950
1334
|
:host([reflowing]) .cell {
|
|
951
1335
|
transition:
|
|
952
1336
|
transform var(--grid-list-item-move-duration, 200ms) cubic-bezier(0.2, 0, 0, 1),
|
|
@@ -957,6 +1341,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
957
1341
|
animation: ixfx-list-item-in var(--grid-list-item-move-duration, 200ms) ease both;
|
|
958
1342
|
}
|
|
959
1343
|
@media (prefers-reduced-motion: reduce) {
|
|
1344
|
+
:host([reflowing]) .group-header { transition: none; }
|
|
960
1345
|
:host([reflowing]) .cell { transition: background var(--transition), border-color var(--transition); }
|
|
961
1346
|
.cell.entering { animation: none; }
|
|
962
1347
|
}
|
|
@@ -1098,31 +1483,13 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1098
1483
|
* so virtualized off-screen items are covered without DOM measurement.
|
|
1099
1484
|
*/
|
|
1100
1485
|
#marqueeHitTest(rect) {
|
|
1101
|
-
|
|
1102
|
-
if (count === 0) return [];
|
|
1486
|
+
if (this.#source.count === 0) return [];
|
|
1103
1487
|
const horizontal = this.orientation === `row`;
|
|
1104
|
-
const
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
const lo = Math.max(0, Math.floor(x0 / win.colStride));
|
|
1110
|
-
const hi = Math.min(count - 1, Math.floor(x1 / win.colStride));
|
|
1111
|
-
for (let i = lo; i <= hi; i++) out.push(i);
|
|
1112
|
-
} else {
|
|
1113
|
-
const x0 = rect.x + this._scrollLeft;
|
|
1114
|
-
const x1 = x0 + rect.width;
|
|
1115
|
-
const y0 = rect.y + this._scrollTop;
|
|
1116
|
-
const y1 = y0 + rect.height;
|
|
1117
|
-
const colLo = Math.max(0, Math.floor(x0 / win.colStride));
|
|
1118
|
-
const colHi = Math.min(win.cols - 1, Math.floor(x1 / win.colStride));
|
|
1119
|
-
const rowLo = Math.max(0, Math.floor(y0 / win.rowStride));
|
|
1120
|
-
const rowHi = Math.floor(y1 / win.rowStride);
|
|
1121
|
-
for (let row = rowLo; row <= rowHi; row++) for (let col = colLo; col <= colHi; col++) {
|
|
1122
|
-
const index = row * win.cols + col;
|
|
1123
|
-
if (index >= 0 && index < count) out.push(index);
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1488
|
+
const x0 = rect.x + this._scrollLeft;
|
|
1489
|
+
const x1 = x0 + rect.width;
|
|
1490
|
+
const y0 = horizontal ? 0 : rect.y + this._scrollTop;
|
|
1491
|
+
const y1 = horizontal ? 0 : y0 + rect.height;
|
|
1492
|
+
const out = this.#layout().hitTest(x0, x1, y0, y1);
|
|
1126
1493
|
const keys = [];
|
|
1127
1494
|
for (const index of out) {
|
|
1128
1495
|
const key = this.#source.keyAt(index);
|
|
@@ -1272,6 +1639,10 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1272
1639
|
if (previousLength === 0 && removedCount === 0) return;
|
|
1273
1640
|
if (added.length > 0 && added.length === this.#items.length) return;
|
|
1274
1641
|
for (const el of added) this.#entering.add(el);
|
|
1642
|
+
this.#openReflowWindow();
|
|
1643
|
+
}
|
|
1644
|
+
/** Open (or extend) the `[reflowing]` window so moved cells and headers slide. */
|
|
1645
|
+
#openReflowWindow() {
|
|
1275
1646
|
this.toggleAttribute(`reflowing`, true);
|
|
1276
1647
|
if (this.#reflowTimer) clearTimeout(this.#reflowTimer);
|
|
1277
1648
|
this.#reflowTimer = window.setTimeout(() => {
|
|
@@ -1328,6 +1699,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1328
1699
|
this.#sourceElement = new ElementGridListSource(this);
|
|
1329
1700
|
this.#source = this.#sourceElement;
|
|
1330
1701
|
}
|
|
1702
|
+
this.#source.setGrouping(this.#groupingConfig());
|
|
1331
1703
|
this.#applyFilters();
|
|
1332
1704
|
if (this.#searchFilterKeys) this.#source.setFilters({ searchKeys: this.#searchFilterKeys });
|
|
1333
1705
|
}
|
|
@@ -1369,6 +1741,17 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1369
1741
|
}
|
|
1370
1742
|
this.search?.refresh();
|
|
1371
1743
|
}
|
|
1744
|
+
const groupsChanged = changed.has(`groupBy`) || changed.has(`groupLabel`) || changed.has(`_collapsedGroups`);
|
|
1745
|
+
if (groupsChanged && this.hasUpdated) {
|
|
1746
|
+
if (changed.has(`groupBy`)) {
|
|
1747
|
+
if (this._collapsedGroups.size > 0) this._collapsedGroups = /* @__PURE__ */ new Set();
|
|
1748
|
+
this.#groupByChanged = true;
|
|
1749
|
+
}
|
|
1750
|
+
this.#applyGroupingToSource();
|
|
1751
|
+
this.#dropUnreachableCursor();
|
|
1752
|
+
this.#openReflowWindow();
|
|
1753
|
+
this.updateComplete.then(() => this.#emitViewportChange());
|
|
1754
|
+
} else if (groupsChanged) this.#applyGroupingToSource();
|
|
1372
1755
|
if (changed.has(`itemSize`) || changed.has(`gap`)) {
|
|
1373
1756
|
this.itemSize = Math.max(MIN_ITEM_SIZE, Math.round(this.itemSize));
|
|
1374
1757
|
this.gap = Math.max(0, Math.round(this.gap));
|
|
@@ -1389,6 +1772,62 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1389
1772
|
}
|
|
1390
1773
|
}
|
|
1391
1774
|
}
|
|
1775
|
+
#groupByChanged;
|
|
1776
|
+
/** Grouping config for the active source, or `undefined` when ungrouped. */
|
|
1777
|
+
#groupingConfig() {
|
|
1778
|
+
const by = this.groupBy;
|
|
1779
|
+
if (by === null || by === void 0 || by === ``) return void 0;
|
|
1780
|
+
const collapsed = this._collapsedGroups;
|
|
1781
|
+
const src = this.#source;
|
|
1782
|
+
if (src instanceof DataGridListSource) return {
|
|
1783
|
+
groupKeyOf: (raw) => resolveGroupKey(by, src.rawItemAt(raw), raw, readProperty),
|
|
1784
|
+
itemAt: (raw) => src.rawItemAt(raw),
|
|
1785
|
+
labelOf: this.groupLabel,
|
|
1786
|
+
collapsed
|
|
1787
|
+
};
|
|
1788
|
+
const elements = src;
|
|
1789
|
+
return {
|
|
1790
|
+
groupKeyOf: (raw) => resolveGroupKey(by, elements.rawElementAt(raw), raw, readAttribute),
|
|
1791
|
+
itemAt: (raw) => elements.rawElementAt(raw),
|
|
1792
|
+
labelOf: this.groupLabel,
|
|
1793
|
+
collapsed
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
/** Push the current grouping into the source and refresh the element-mode item cache. */
|
|
1797
|
+
#applyGroupingToSource() {
|
|
1798
|
+
this.#source.setGrouping(this.#groupingConfig());
|
|
1799
|
+
if (this.#source instanceof ElementGridListSource) this.#items = this.#source.effectiveElements();
|
|
1800
|
+
}
|
|
1801
|
+
/** The cursor can't stay on an item that a collapsed group just removed. */
|
|
1802
|
+
#dropUnreachableCursor() {
|
|
1803
|
+
const id = this.tickled.tickledId;
|
|
1804
|
+
if (id !== void 0 && this.#source.indexOfKey(id) < 0) this.tickled.focus(void 0);
|
|
1805
|
+
}
|
|
1806
|
+
/** Groups in display order (empty when ungrouped). */
|
|
1807
|
+
get groups() {
|
|
1808
|
+
return this.groupBy === null ? [] : this.#source.groups();
|
|
1809
|
+
}
|
|
1810
|
+
/** Collapse or expand one group. Fires `list-group-toggle` when the state changes. */
|
|
1811
|
+
setGroupCollapsed(groupKey, collapsed) {
|
|
1812
|
+
if (this._collapsedGroups.has(groupKey) === collapsed) return;
|
|
1813
|
+
const next = new Set(this._collapsedGroups);
|
|
1814
|
+
if (collapsed) next.add(groupKey);
|
|
1815
|
+
else next.delete(groupKey);
|
|
1816
|
+
this._collapsedGroups = next;
|
|
1817
|
+
this._dispatch(`list-group-toggle`, {
|
|
1818
|
+
groupKey,
|
|
1819
|
+
collapsed
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
toggleGroup(groupKey) {
|
|
1823
|
+
this.setGroupCollapsed(groupKey, !this._collapsedGroups.has(groupKey));
|
|
1824
|
+
}
|
|
1825
|
+
collapseAllGroups() {
|
|
1826
|
+
for (const g of this.groups) this.setGroupCollapsed(g.key, true);
|
|
1827
|
+
}
|
|
1828
|
+
expandAllGroups() {
|
|
1829
|
+
for (const key of [...this._collapsedGroups]) this.setGroupCollapsed(key, false);
|
|
1830
|
+
}
|
|
1392
1831
|
#syncAriaMultiselectable() {
|
|
1393
1832
|
this.setAttribute(`aria-multiselectable`, this.selection.canSelectMultiple() ? `true` : `false`);
|
|
1394
1833
|
}
|
|
@@ -1428,6 +1867,10 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1428
1867
|
}
|
|
1429
1868
|
this.#applyZoomAnchor();
|
|
1430
1869
|
this.#activateTickledPan();
|
|
1870
|
+
if (this.#groupByChanged) {
|
|
1871
|
+
this.#groupByChanged = false;
|
|
1872
|
+
this._dispatch(`list-group-change`, { groupBy: this.groupBy });
|
|
1873
|
+
}
|
|
1431
1874
|
}
|
|
1432
1875
|
/** Re-anchor the scroll so the item captured in `willUpdate` stays centred. */
|
|
1433
1876
|
#applyZoomAnchor() {
|
|
@@ -1436,41 +1879,33 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1436
1879
|
this.#zoomAnchor = void 0;
|
|
1437
1880
|
const vp = this.#viewportRef.value;
|
|
1438
1881
|
if (!vp) return;
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1882
|
+
const layout = this.#layout();
|
|
1883
|
+
const horizontal = layout.horizontal;
|
|
1884
|
+
const extent = horizontal ? this._viewportWidth : this._viewportHeight;
|
|
1885
|
+
if (extent <= 0) return;
|
|
1886
|
+
const targetCenter = layout.offsetOfAnchor(index, lineFrac);
|
|
1887
|
+
const maxScroll = Math.max(0, layout.totalExtent - extent);
|
|
1888
|
+
const next = Math.min(maxScroll, Math.max(0, targetCenter - extent / 2));
|
|
1889
|
+
if (horizontal) {
|
|
1445
1890
|
if (Math.abs(vp.scrollLeft - next) > .5) {
|
|
1446
1891
|
vp.scrollLeft = next;
|
|
1447
1892
|
this._scrollLeft = next;
|
|
1448
1893
|
}
|
|
1449
|
-
|
|
1450
|
-
}
|
|
1451
|
-
if (this._viewportHeight <= 0) return;
|
|
1452
|
-
const targetCenter = ((win.cols > 0 ? Math.floor(index / win.cols) : 0) + lineFrac) * win.rowStride;
|
|
1453
|
-
const maxScroll = Math.max(0, win.totalHeight - this._viewportHeight);
|
|
1454
|
-
const next = Math.min(maxScroll, Math.max(0, targetCenter - this._viewportHeight / 2));
|
|
1455
|
-
if (Math.abs(vp.scrollTop - next) > .5) {
|
|
1894
|
+
} else if (Math.abs(vp.scrollTop - next) > .5) {
|
|
1456
1895
|
vp.scrollTop = next;
|
|
1457
1896
|
this._scrollTop = next;
|
|
1458
1897
|
}
|
|
1459
1898
|
}
|
|
1460
1899
|
#captureCentreAnchor(prevOffset, prevExtent, prevItemSize, prevGap, prevCaptionStyle, prevCaptionHeight, prevOrientation) {
|
|
1461
|
-
|
|
1462
|
-
if (count === 0) return;
|
|
1900
|
+
if ((this.#source?.count ?? 0) === 0) return;
|
|
1463
1901
|
const prevRowHeight = prevItemSize + (prevCaptionStyle === `separate` || prevCaptionStyle === `under` ? prevCaptionHeight : 0);
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
this.#zoomAnchor =
|
|
1471
|
-
index,
|
|
1472
|
-
lineFrac: centerLine - lineFloor
|
|
1473
|
-
};
|
|
1902
|
+
const prev = this.#buildLayout({
|
|
1903
|
+
itemWidth: prevItemSize,
|
|
1904
|
+
rowHeight: prevRowHeight,
|
|
1905
|
+
gap: prevGap,
|
|
1906
|
+
orientation: prevOrientation
|
|
1907
|
+
});
|
|
1908
|
+
this.#zoomAnchor = prev.anchorAt(prevOffset + prevExtent / 2);
|
|
1474
1909
|
}
|
|
1475
1910
|
/**
|
|
1476
1911
|
* Re-read the viewport after an ancestor's visibility/layout changed.
|
|
@@ -1684,37 +2119,75 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1684
2119
|
return this.#windowKeys(this.#currentWindow(true));
|
|
1685
2120
|
}
|
|
1686
2121
|
#windowKeys(win) {
|
|
1687
|
-
|
|
2122
|
+
const keys = this.#source.keys();
|
|
2123
|
+
const out = [];
|
|
2124
|
+
for (const [start, end] of win.ranges) for (let i = start; i < end; i++) if (!isSyntheticKey(keys[i])) out.push(keys[i]);
|
|
2125
|
+
return out;
|
|
2126
|
+
}
|
|
2127
|
+
/** Layout params for the current geometry, with optional overrides (used for zoom anchoring). */
|
|
2128
|
+
#layoutParams(over = {}) {
|
|
2129
|
+
return {
|
|
2130
|
+
count: this.#source.count,
|
|
2131
|
+
groups: this.groupBy === null ? void 0 : this.#source.groups(),
|
|
2132
|
+
itemWidth: this.itemSize,
|
|
2133
|
+
rowHeight: this.#rowHeight(),
|
|
2134
|
+
gap: this.gap,
|
|
2135
|
+
viewportWidth: this._viewportWidth,
|
|
2136
|
+
viewportHeight: this._viewportHeight,
|
|
2137
|
+
orientation: this.orientation,
|
|
2138
|
+
headerHeight: this.groupHeaderHeight,
|
|
2139
|
+
headerWidth: this.groupHeaderWidth,
|
|
2140
|
+
...over
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
#buildLayout(over = {}) {
|
|
2144
|
+
return buildLayout(this.#layoutParams(over));
|
|
2145
|
+
}
|
|
2146
|
+
#layoutCache;
|
|
2147
|
+
/** The current layout, rebuilt only when its inputs change (never per scroll). */
|
|
2148
|
+
#layout() {
|
|
2149
|
+
const params = this.#layoutParams();
|
|
2150
|
+
const cached = this.#layoutCache;
|
|
2151
|
+
if (cached) {
|
|
2152
|
+
const a = cached.params;
|
|
2153
|
+
let same = a.groups === params.groups;
|
|
2154
|
+
for (const k of Object.keys(params)) {
|
|
2155
|
+
if (!same) break;
|
|
2156
|
+
same = a[k] === params[k];
|
|
2157
|
+
}
|
|
2158
|
+
if (same) return cached.layout;
|
|
2159
|
+
}
|
|
2160
|
+
const layout = buildLayout(params);
|
|
2161
|
+
this.#layoutCache = {
|
|
2162
|
+
params,
|
|
2163
|
+
layout
|
|
2164
|
+
};
|
|
2165
|
+
return layout;
|
|
1688
2166
|
}
|
|
1689
2167
|
#currentWindow(noOverscan = false) {
|
|
1690
|
-
|
|
1691
|
-
return computeGridWindow(this.#source.count, this.itemSize, this.#rowHeight(), this.gap, this._viewportWidth, this._viewportHeight, this.orientation === `row` ? this._scrollLeft : this._scrollTop, this.orientation, overscan);
|
|
2168
|
+
return this.#layout().window(this.orientation === `row` ? this._scrollLeft : this._scrollTop, noOverscan ? 0 : 2);
|
|
1692
2169
|
}
|
|
1693
2170
|
/** Adjust the scroll offset so `key` is fully within the viewport. */
|
|
1694
2171
|
#scrollItemIntoView(key) {
|
|
1695
2172
|
const index = this.#source.indexOfKey(key);
|
|
1696
2173
|
const vp = this.#viewportRef.value;
|
|
1697
2174
|
if (index < 0 || !vp) return;
|
|
1698
|
-
const
|
|
1699
|
-
if (
|
|
1700
|
-
const left = index * win.colStride;
|
|
1701
|
-
const right = left + this.itemSize;
|
|
2175
|
+
const [start, end] = this.#layout().extentOf(index);
|
|
2176
|
+
if (this.orientation === `row`) {
|
|
1702
2177
|
const viewLeft = vp.scrollLeft;
|
|
1703
2178
|
let next = viewLeft;
|
|
1704
|
-
if (
|
|
1705
|
-
else if (
|
|
2179
|
+
if (start < viewLeft) next = start;
|
|
2180
|
+
else if (end > viewLeft + this._viewportWidth) next = end - this._viewportWidth;
|
|
1706
2181
|
if (next !== viewLeft) {
|
|
1707
2182
|
vp.scrollLeft = next;
|
|
1708
2183
|
this._scrollLeft = next;
|
|
1709
2184
|
}
|
|
1710
2185
|
return;
|
|
1711
2186
|
}
|
|
1712
|
-
const top = (win.cols > 0 ? Math.floor(index / win.cols) : 0) * win.rowStride;
|
|
1713
|
-
const bottom = top + this.#rowHeight();
|
|
1714
2187
|
const viewTop = vp.scrollTop;
|
|
1715
2188
|
let next = viewTop;
|
|
1716
|
-
if (
|
|
1717
|
-
else if (
|
|
2189
|
+
if (start < viewTop) next = start;
|
|
2190
|
+
else if (end > viewTop + this._viewportHeight) next = end - this._viewportHeight;
|
|
1718
2191
|
if (next !== viewTop) {
|
|
1719
2192
|
vp.scrollTop = next;
|
|
1720
2193
|
this._scrollTop = next;
|
|
@@ -1788,6 +2261,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1788
2261
|
>
|
|
1789
2262
|
<div class="spacer" style=${spacerStyle}>
|
|
1790
2263
|
${this.#source instanceof DataGridListSource ? this.#renderDataCells(win) : this.#renderElementCells(win)}
|
|
2264
|
+
${this.#renderHeaders(win)}
|
|
1791
2265
|
</div>
|
|
1792
2266
|
${this.#marqueeRect ? html`<div class="marquee" part="marquee" style=${this.#marqueeStyle(this.#marqueeRect)}></div>` : ``}
|
|
1793
2267
|
</div>
|
|
@@ -1795,7 +2269,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1795
2269
|
`;
|
|
1796
2270
|
}
|
|
1797
2271
|
#renderDataCells(win) {
|
|
1798
|
-
const slice = Array.from({ length:
|
|
2272
|
+
const slice = win.ranges.flatMap(([start, end]) => Array.from({ length: end - start }, (_, i) => start + i));
|
|
1799
2273
|
return repeat(slice, (index) => this.#source.keyAt(index), (index) => this.#renderDataCell(index, win));
|
|
1800
2274
|
}
|
|
1801
2275
|
#renderDataCell(index, win) {
|
|
@@ -1810,8 +2284,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1810
2284
|
const caption = style === `hidden` ? `` : this.#source.labelAt(index);
|
|
1811
2285
|
const showCaption = caption !== ``;
|
|
1812
2286
|
const cellHeight = win.rowStride - this.gap;
|
|
1813
|
-
const x
|
|
1814
|
-
const y = win.horizontal ? Math.max(0, (this._viewportHeight - cellHeight) / 2) : rowOf(index, win.cols) * win.rowStride;
|
|
2287
|
+
const { x, y } = this.#layout().positionOf(index);
|
|
1815
2288
|
const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
|
|
1816
2289
|
const rawIcon = this.#source.iconAt(index);
|
|
1817
2290
|
const icon = appearanceAbsent(rawIcon) ? void 0 : rawIcon;
|
|
@@ -1897,8 +2370,25 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1897
2370
|
current.startLoop?.();
|
|
1898
2371
|
}
|
|
1899
2372
|
#renderElementCells(win) {
|
|
1900
|
-
const slice =
|
|
1901
|
-
return repeat(slice, (
|
|
2373
|
+
const slice = win.ranges.flatMap(([start, end]) => Array.from({ length: end - start }, (_, i) => start + i));
|
|
2374
|
+
return repeat(slice, (index) => this.#source.keyAt(index), (index) => this.#renderCell(this.#items[index], index, win));
|
|
2375
|
+
}
|
|
2376
|
+
#renderHeaders(win) {
|
|
2377
|
+
if (win.headers.length === 0) return nothing;
|
|
2378
|
+
const horizontal = win.horizontal;
|
|
2379
|
+
return repeat(win.headers, (h) => `__group__${h.key}`, (h) => html`
|
|
2380
|
+
<ixfx-list-group-header
|
|
2381
|
+
class="group-header"
|
|
2382
|
+
part="group-header"
|
|
2383
|
+
data-group-key=${h.key}
|
|
2384
|
+
label=${h.label}
|
|
2385
|
+
count=${h.count}
|
|
2386
|
+
orientation=${horizontal ? `vertical` : `horizontal`}
|
|
2387
|
+
?collapsed=${h.collapsed}
|
|
2388
|
+
style=${horizontal ? `width:${h.size}px;height:100%;transform:translate(${h.offset}px,0)` : `width:100%;height:${h.size}px;transform:translate(0,${h.offset}px)`}
|
|
2389
|
+
@click=${() => this.toggleGroup(h.key)}
|
|
2390
|
+
></ixfx-list-group-header>
|
|
2391
|
+
`);
|
|
1902
2392
|
}
|
|
1903
2393
|
#renderCell(el, index, win) {
|
|
1904
2394
|
const key = this.#source.keyAt(index);
|
|
@@ -1913,8 +2403,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1913
2403
|
const caption = style === `hidden` ? `` : this.#captionFor(el);
|
|
1914
2404
|
const showCaption = caption !== ``;
|
|
1915
2405
|
const cellHeight = win.rowStride - this.gap;
|
|
1916
|
-
const x
|
|
1917
|
-
const y = win.horizontal ? Math.max(0, (this._viewportHeight - cellHeight) / 2) : rowOf(index, win.cols) * win.rowStride;
|
|
2406
|
+
const { x, y } = this.#layout().positionOf(index);
|
|
1918
2407
|
const thumbStyle = separate ? `height:${this.itemSize}px` : ``;
|
|
1919
2408
|
const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
|
|
1920
2409
|
return html`
|
|
@@ -2013,16 +2502,12 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2013
2502
|
#snapToNearestLine() {
|
|
2014
2503
|
const vp = this.#viewportRef.value;
|
|
2015
2504
|
if (!vp) return;
|
|
2016
|
-
const
|
|
2017
|
-
const horizontal =
|
|
2018
|
-
const stride = horizontal ? win.colStride : win.rowStride;
|
|
2019
|
-
if (stride <= 0) return;
|
|
2505
|
+
const layout = this.#layout();
|
|
2506
|
+
const horizontal = layout.horizontal;
|
|
2020
2507
|
const offset = horizontal ? vp.scrollLeft : vp.scrollTop;
|
|
2021
|
-
const extent = horizontal ? win.totalWidth : win.totalHeight;
|
|
2022
2508
|
const viewportExtent = horizontal ? this._viewportWidth : this._viewportHeight;
|
|
2023
|
-
const maxScroll = Math.max(0,
|
|
2024
|
-
const
|
|
2025
|
-
const target = Math.min(maxScroll, Math.max(0, index * stride));
|
|
2509
|
+
const maxScroll = Math.max(0, layout.totalExtent - viewportExtent);
|
|
2510
|
+
const target = Math.min(maxScroll, Math.max(0, layout.snapTarget(offset)));
|
|
2026
2511
|
if (Math.abs(target - offset) < .5) return;
|
|
2027
2512
|
vp.scrollTo(horizontal ? {
|
|
2028
2513
|
left: target,
|
|
@@ -2050,7 +2535,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2050
2535
|
if (mode === `manual`) return;
|
|
2051
2536
|
if (this.selectionMode === `none` && mode !== `checked` && mode !== `sticky`) return;
|
|
2052
2537
|
if (this.#dragSelect.isDragging || this.#nativeDragging || this.#nativeDrag.isDragging || this.#postDragSuppressTimer !== 0) return;
|
|
2053
|
-
for (const node of e.composedPath()) if (node instanceof Element && node.classList.contains(`cell`)) return;
|
|
2538
|
+
for (const node of e.composedPath()) if (node instanceof Element && (node.classList.contains(`cell`) || node.classList.contains(`group-header`))) return;
|
|
2054
2539
|
this.selection.clear();
|
|
2055
2540
|
}
|
|
2056
2541
|
#startPostDragSuppressTimer() {
|
|
@@ -2151,6 +2636,22 @@ __decorate([property({
|
|
|
2151
2636
|
})], GridListElement.prototype, "snapToItem", void 0);
|
|
2152
2637
|
__decorate([property({ attribute: false })], GridListElement.prototype, "items", void 0);
|
|
2153
2638
|
__decorate([property({ attribute: false })], GridListElement.prototype, "itemAdapter", void 0);
|
|
2639
|
+
__decorate([property({ attribute: `group-by` })], GridListElement.prototype, "groupBy", void 0);
|
|
2640
|
+
__decorate([property({ attribute: false })], GridListElement.prototype, "groupLabel", void 0);
|
|
2641
|
+
__decorate([property({
|
|
2642
|
+
type: Boolean,
|
|
2643
|
+
attribute: `cross-group-selection`,
|
|
2644
|
+
reflect: true
|
|
2645
|
+
})], GridListElement.prototype, "crossGroupSelection", void 0);
|
|
2646
|
+
__decorate([property({
|
|
2647
|
+
type: Number,
|
|
2648
|
+
attribute: `group-header-height`
|
|
2649
|
+
})], GridListElement.prototype, "groupHeaderHeight", void 0);
|
|
2650
|
+
__decorate([property({
|
|
2651
|
+
type: Number,
|
|
2652
|
+
attribute: `group-header-width`
|
|
2653
|
+
})], GridListElement.prototype, "groupHeaderWidth", void 0);
|
|
2654
|
+
__decorate([state()], GridListElement.prototype, "_collapsedGroups", void 0);
|
|
2154
2655
|
__decorate([state()], GridListElement.prototype, "_scrollTop", void 0);
|
|
2155
2656
|
__decorate([state()], GridListElement.prototype, "_scrollLeft", void 0);
|
|
2156
2657
|
__decorate([state()], GridListElement.prototype, "_viewportHeight", void 0);
|
|
@@ -2159,4 +2660,4 @@ GridListElement = __decorate([safeCustomElement(`ixfx-grid-list`)], GridListElem
|
|
|
2159
2660
|
//#endregion
|
|
2160
2661
|
export { GridListElement as t };
|
|
2161
2662
|
|
|
2162
|
-
//# sourceMappingURL=grid-list-
|
|
2663
|
+
//# sourceMappingURL=grid-list-Bwaf61is.js.map
|