@ixfx/components 0.8.2 → 0.8.4
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 +274 -93
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +2135 -637
- package/bundle/index.js.map +1 -1
- package/dist/ac-text.js +2 -2
- package/dist/ac-token.js +2 -2
- package/dist/checkbox.js +1 -1
- package/dist/{colour-picker-Ddsk-7E_.js → colour-picker-CSxwUmtE.js} +2 -2
- package/dist/{colour-picker-Ddsk-7E_.js.map → colour-picker-CSxwUmtE.js.map} +1 -1
- package/dist/colour-picker.js +1 -1
- package/dist/crumbs.js +2 -2
- package/dist/data-display.js +1 -1
- package/dist/data-grid-D-z7bx5-.d.ts.map +1 -1
- package/dist/data-grid.js +7 -4
- package/dist/data-grid.js.map +1 -1
- package/dist/detail-list-_ZJHYEBr.d.ts.map +1 -1
- package/dist/detail-list.js +67 -4
- package/dist/detail-list.js.map +1 -1
- package/dist/editable-label.js +1 -1
- package/dist/{fallbacks-DNI9xt0F.js → fallbacks-BcKGq-2F.js} +17 -1
- package/dist/{fallbacks-DNI9xt0F.js.map → fallbacks-BcKGq-2F.js.map} +1 -1
- package/dist/{grid-list-bmBWq6P-.js → grid-list-BXfY3XXX.js} +486 -13
- package/dist/grid-list-BXfY3XXX.js.map +1 -0
- package/dist/{grid-list-C1T6WlEp.d.ts → grid-list-x550wJ0X.d.ts} +97 -2
- package/dist/grid-list-x550wJ0X.d.ts.map +1 -0
- package/dist/grid-list.d.ts +2 -2
- package/dist/grid-list.js +1 -1
- package/dist/handle-BPOxBOKf.js +219 -0
- package/dist/handle-BPOxBOKf.js.map +1 -0
- package/dist/{icon-DcEEGQa_.js → icon-Dy33gu4Z.js} +2 -2
- package/dist/{icon-DcEEGQa_.js.map → icon-Dy33gu4Z.js.map} +1 -1
- package/dist/icons.js +1 -1
- package/dist/{index-BeuCXtiO.d.ts → index-DV3iaUBW.d.ts} +2 -2
- package/dist/{index-BeuCXtiO.d.ts.map → index-DV3iaUBW.d.ts.map} +1 -1
- package/dist/index.d.ts +130 -48
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +551 -203
- package/dist/index.js.map +1 -1
- package/dist/labelled-radial-input.js +2 -2
- package/dist/{labelled-slider-input-BInB5GKL.js → labelled-slider-input-BCH9968c.js} +3 -3
- package/dist/{labelled-slider-input-BInB5GKL.js.map → labelled-slider-input-BCH9968c.js.map} +1 -1
- package/dist/labelled-slider-input.js +1 -1
- package/dist/list-selection-controller-SA4piSBi.js +589 -0
- package/dist/list-selection-controller-SA4piSBi.js.map +1 -0
- package/dist/{menu-BDrTh6oo.js → menu-HMoxCPY2.js} +4 -4
- package/dist/{menu-BDrTh6oo.js.map → menu-HMoxCPY2.js.map} +1 -1
- package/dist/{menu-item-B6pKg2Qq.js → menu-item-BQDYxk5A.js} +3 -3
- package/dist/{menu-item-B6pKg2Qq.js.map → menu-item-BQDYxk5A.js.map} +1 -1
- package/dist/menu.js +2 -2
- package/dist/miller.js +1 -1
- package/dist/narrowed-text.js +1 -1
- package/dist/notifier.js +1 -1
- package/dist/panel.d.ts +1 -1
- package/dist/panel.js +1 -1
- package/dist/polar-pad.js +1 -1
- package/dist/{radial-input-BfA3GO0H.js → radial-input-DyxfbNzD.js} +2 -2
- package/dist/{radial-input-BfA3GO0H.js.map → radial-input-DyxfbNzD.js.map} +1 -1
- package/dist/radial-input.js +1 -1
- package/dist/range-input.js +1 -1
- package/dist/range.js +1 -1
- package/dist/select-horiz.js +1 -1
- package/dist/{slider-input-Bl1hFE89.js → slider-input-WR8_RwHg.js} +2 -2
- package/dist/{slider-input-Bl1hFE89.js.map → slider-input-WR8_RwHg.js.map} +1 -1
- package/dist/slider-input.js +1 -1
- package/dist/snackbar.js +1 -1
- package/dist/{split-layout-BFSbUZQv.js → split-layout-CA5jbNbG.js} +22 -33
- package/dist/split-layout-CA5jbNbG.js.map +1 -0
- package/dist/split-layout-JK3xWVX2.d.ts.map +1 -1
- package/dist/split-layout.js +1 -1
- package/dist/swipe.js +1 -1
- package/dist/tabs.js +1 -1
- package/dist/{timeline-B2fgDL4U.js → timeline-THyvH8xy.js} +14 -8
- package/dist/timeline-THyvH8xy.js.map +1 -0
- package/dist/timeline.d.ts +1 -1
- package/dist/timeline.js +1 -1
- package/dist/{titlebar-YQ-jVQoS.d.ts → titlebar-Tqu-VvMU.d.ts} +5 -1
- package/dist/titlebar-Tqu-VvMU.d.ts.map +1 -0
- package/dist/titlebar.d.ts +1 -1
- package/dist/titlebar.js +5 -3
- package/dist/titlebar.js.map +1 -1
- package/dist/{tree-4Jv28Nig.js → tree-DTQon838.js} +2 -2
- package/dist/{tree-4Jv28Nig.js.map → tree-DTQon838.js.map} +1 -1
- package/dist/tree.js +1 -1
- package/dist/vertical-list-CFuFDygi.d.ts.map +1 -1
- package/dist/vertical-list.js +67 -3
- package/dist/vertical-list.js.map +1 -1
- package/dist/xy-pad.js +1 -1
- package/docs-user/data-grid.md +1 -1
- package/docs-user/detail-list.md +4 -0
- package/docs-user/dock.md +33 -8
- package/docs-user/grid-list.md +5 -0
- package/docs-user/index.json +2 -2
- package/docs-user/split-layout.md +2 -4
- package/docs-user/twosplit-layout.md +14 -16
- package/docs-user/vertical-list.md +4 -0
- package/package.json +1 -1
- package/dist/grid-list-C1T6WlEp.d.ts.map +0 -1
- package/dist/grid-list-bmBWq6P-.js.map +0 -1
- package/dist/list-selection-controller-DTFI40li.js +0 -264
- package/dist/list-selection-controller-DTFI40li.js.map +0 -1
- package/dist/split-layout-BFSbUZQv.js.map +0 -1
- package/dist/timeline-B2fgDL4U.js.map +0 -1
- package/dist/titlebar-YQ-jVQoS.d.ts.map +0 -1
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
//#region src/interaction/list-drag-select-controller.ts
|
|
2
|
+
const DEFAULT_POINTER_TYPES = [`mouse`, `pen`];
|
|
3
|
+
const DEFAULT_THRESHOLD = 4;
|
|
4
|
+
/** Pointer distance (px) from an edge that triggers auto-scroll. */
|
|
5
|
+
const EDGE_ZONE = 28;
|
|
6
|
+
/** Max auto-scroll speed in px per frame. */
|
|
7
|
+
const MAX_SCROLL_SPEED = 16;
|
|
8
|
+
/**
|
|
9
|
+
* Shared pointer-drag (marquee / rubber-band) selection for list components.
|
|
10
|
+
*
|
|
11
|
+
* Owns all pointer plumbing and rectangle state; knows nothing about DOM
|
|
12
|
+
* geometry or the selection model — those are host callbacks. Mirrors the
|
|
13
|
+
* `TickledController` attach pattern (`new ListDragSelectController(this, …)`).
|
|
14
|
+
*
|
|
15
|
+
* Behaviour: `pointerdown` on the surface records a start point; once the
|
|
16
|
+
* pointer moves past `threshold`, the surface captures the pointer and every
|
|
17
|
+
* move recomputes the rect, hit-tests, and calls `onPreview`. On release the
|
|
18
|
+
* final set is delivered once via `onCommit`. A press that never crosses the
|
|
19
|
+
* threshold does nothing (the normal `@click` path runs).
|
|
20
|
+
*/
|
|
21
|
+
var ListDragSelectController = class {
|
|
22
|
+
#host;
|
|
23
|
+
#config;
|
|
24
|
+
#boundSurface;
|
|
25
|
+
#pointerId;
|
|
26
|
+
#startClientX = 0;
|
|
27
|
+
#startClientY = 0;
|
|
28
|
+
#startX = 0;
|
|
29
|
+
#startY = 0;
|
|
30
|
+
#currentClientX = 0;
|
|
31
|
+
#currentClientY = 0;
|
|
32
|
+
#lastModifiers = {
|
|
33
|
+
shiftKey: false,
|
|
34
|
+
ctrlKey: false,
|
|
35
|
+
metaKey: false
|
|
36
|
+
};
|
|
37
|
+
#dragging = false;
|
|
38
|
+
#suppressClick = false;
|
|
39
|
+
/** Selection as it was when the drag started — the combine baseline. */
|
|
40
|
+
#base = /* @__PURE__ */ new Set();
|
|
41
|
+
/** Combine mode of the most recent preview (drives the commit flag). */
|
|
42
|
+
#lastMode = `replace`;
|
|
43
|
+
#preview = /* @__PURE__ */ new Set();
|
|
44
|
+
#raf = 0;
|
|
45
|
+
constructor(host, config) {
|
|
46
|
+
this.#host = host;
|
|
47
|
+
this.#config = config;
|
|
48
|
+
host.addController(this);
|
|
49
|
+
}
|
|
50
|
+
/** `true` while a drag is in progress or its trailing click is still pending. */
|
|
51
|
+
get isDragging() {
|
|
52
|
+
return this.#dragging || this.#suppressClick;
|
|
53
|
+
}
|
|
54
|
+
/** `true` once the press has crossed the threshold and a rect is live. */
|
|
55
|
+
get isActive() {
|
|
56
|
+
return this.#dragging;
|
|
57
|
+
}
|
|
58
|
+
hostConnected() {
|
|
59
|
+
this.#bindSurface();
|
|
60
|
+
window.addEventListener(`pointermove`, this.#onPointerMove);
|
|
61
|
+
window.addEventListener(`pointerup`, this.#onPointerUp);
|
|
62
|
+
window.addEventListener(`pointercancel`, this.#onPointerCancel);
|
|
63
|
+
}
|
|
64
|
+
hostDisconnected() {
|
|
65
|
+
this.#unbindSurface();
|
|
66
|
+
window.removeEventListener(`pointermove`, this.#onPointerMove);
|
|
67
|
+
window.removeEventListener(`pointerup`, this.#onPointerUp);
|
|
68
|
+
window.removeEventListener(`pointercancel`, this.#onPointerCancel);
|
|
69
|
+
this.#stopAutoScroll();
|
|
70
|
+
this.#reset();
|
|
71
|
+
}
|
|
72
|
+
/** Re-bind when the surface element appears/changes (e.g. first render). */
|
|
73
|
+
hostUpdated() {
|
|
74
|
+
if ((this.#safeSurface() ?? this.#host) !== this.#boundSurface) this.#bindSurface();
|
|
75
|
+
}
|
|
76
|
+
#safeSurface() {
|
|
77
|
+
try {
|
|
78
|
+
return this.#config.getSurface();
|
|
79
|
+
} catch {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
#bindSurface() {
|
|
84
|
+
const next = this.#safeSurface() ?? this.#host;
|
|
85
|
+
if (next === this.#boundSurface) return;
|
|
86
|
+
this.#unbindSurface();
|
|
87
|
+
this.#boundSurface = next;
|
|
88
|
+
next.addEventListener(`pointerdown`, this.#onPointerDown);
|
|
89
|
+
}
|
|
90
|
+
#unbindSurface() {
|
|
91
|
+
this.#boundSurface?.removeEventListener(`pointerdown`, this.#onPointerDown);
|
|
92
|
+
this.#boundSurface = void 0;
|
|
93
|
+
}
|
|
94
|
+
#enabledTypes() {
|
|
95
|
+
return this.#config.pointerTypes ?? DEFAULT_POINTER_TYPES;
|
|
96
|
+
}
|
|
97
|
+
#threshold() {
|
|
98
|
+
return this.#config.threshold ?? DEFAULT_THRESHOLD;
|
|
99
|
+
}
|
|
100
|
+
#onPointerDown = (event) => {
|
|
101
|
+
if (event.button !== 0) return;
|
|
102
|
+
if (!this.#enabledTypes().includes(event.pointerType)) return;
|
|
103
|
+
let enabled = false;
|
|
104
|
+
try {
|
|
105
|
+
enabled = this.#config.isEnabled();
|
|
106
|
+
} catch {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!enabled) return;
|
|
110
|
+
const target = event.target;
|
|
111
|
+
if (target instanceof Element && typeof target.closest === `function`) {
|
|
112
|
+
if (target.closest(`input,button,a,select,textarea,[contenteditable]`)) return;
|
|
113
|
+
}
|
|
114
|
+
if (this.#config.shouldStart) try {
|
|
115
|
+
if (!this.#config.shouldStart(event)) return;
|
|
116
|
+
} catch {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const box = (this.#safeSurface() ?? this.#host).getBoundingClientRect();
|
|
120
|
+
this.#pointerId = event.pointerId;
|
|
121
|
+
this.#startClientX = event.clientX;
|
|
122
|
+
this.#startClientY = event.clientY;
|
|
123
|
+
this.#currentClientX = event.clientX;
|
|
124
|
+
this.#currentClientY = event.clientY;
|
|
125
|
+
this.#startX = Math.min(Math.max(event.clientX - box.left, 0), Math.max(0, box.width));
|
|
126
|
+
this.#startY = Math.min(Math.max(event.clientY - box.top, 0), Math.max(0, box.height));
|
|
127
|
+
this.#dragging = false;
|
|
128
|
+
this.#suppressClick = false;
|
|
129
|
+
this.#base = /* @__PURE__ */ new Set();
|
|
130
|
+
this.#lastMode = `replace`;
|
|
131
|
+
this.#lastModifiers = {
|
|
132
|
+
shiftKey: event.shiftKey,
|
|
133
|
+
ctrlKey: event.ctrlKey,
|
|
134
|
+
metaKey: event.metaKey
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
/** Combine mode for the current modifiers: Shift adds, Cmd/Ctrl inverts. */
|
|
138
|
+
#modeFor(modifiers) {
|
|
139
|
+
if (modifiers.metaKey || modifiers.ctrlKey) return `invert`;
|
|
140
|
+
if (modifiers.shiftKey) return `add`;
|
|
141
|
+
return `replace`;
|
|
142
|
+
}
|
|
143
|
+
#onPointerMove = (event) => {
|
|
144
|
+
if (this.#pointerId === void 0 || event.pointerId !== this.#pointerId) return;
|
|
145
|
+
this.#currentClientX = event.clientX;
|
|
146
|
+
this.#currentClientY = event.clientY;
|
|
147
|
+
this.#lastModifiers = {
|
|
148
|
+
shiftKey: event.shiftKey,
|
|
149
|
+
ctrlKey: event.ctrlKey,
|
|
150
|
+
metaKey: event.metaKey
|
|
151
|
+
};
|
|
152
|
+
const moved = Math.hypot(event.clientX - this.#startClientX, event.clientY - this.#startClientY);
|
|
153
|
+
if (!this.#dragging) {
|
|
154
|
+
if (moved < this.#threshold()) return;
|
|
155
|
+
this.#dragging = true;
|
|
156
|
+
try {
|
|
157
|
+
this.#base = new Set(this.#config.getBaseSelection());
|
|
158
|
+
} catch {
|
|
159
|
+
this.#base = /* @__PURE__ */ new Set();
|
|
160
|
+
}
|
|
161
|
+
const surface = this.#safeSurface() ?? this.#host;
|
|
162
|
+
try {
|
|
163
|
+
if (typeof surface.setPointerCapture === `function`) surface.setPointerCapture(event.pointerId);
|
|
164
|
+
} catch {}
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
event.preventDefault?.();
|
|
168
|
+
} catch {}
|
|
169
|
+
this.#updatePreview();
|
|
170
|
+
this.#kickAutoScroll();
|
|
171
|
+
};
|
|
172
|
+
#surfaceBox() {
|
|
173
|
+
const surface = this.#safeSurface() ?? this.#host;
|
|
174
|
+
try {
|
|
175
|
+
return surface.getBoundingClientRect();
|
|
176
|
+
} catch {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
#currentRect() {
|
|
181
|
+
const box = this.#surfaceBox();
|
|
182
|
+
if (!box) return void 0;
|
|
183
|
+
const cx = Math.min(Math.max(this.#currentClientX - box.left, 0), Math.max(0, box.width));
|
|
184
|
+
const cy = Math.min(Math.max(this.#currentClientY - box.top, 0), Math.max(0, box.height));
|
|
185
|
+
return {
|
|
186
|
+
x: Math.min(this.#startX, cx),
|
|
187
|
+
y: Math.min(this.#startY, cy),
|
|
188
|
+
width: Math.abs(cx - this.#startX),
|
|
189
|
+
height: Math.abs(cy - this.#startY)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
#updatePreview() {
|
|
193
|
+
const rect = this.#currentRect();
|
|
194
|
+
if (!rect) return;
|
|
195
|
+
let hit = [];
|
|
196
|
+
try {
|
|
197
|
+
hit = this.#config.hitTest(rect);
|
|
198
|
+
} catch {
|
|
199
|
+
hit = [];
|
|
200
|
+
}
|
|
201
|
+
const mode = this.#modeFor(this.#lastModifiers);
|
|
202
|
+
this.#lastMode = mode;
|
|
203
|
+
const hitSet = new Set(hit);
|
|
204
|
+
let preview;
|
|
205
|
+
if (mode === `add`) preview = /* @__PURE__ */ new Set([...this.#base, ...hitSet]);
|
|
206
|
+
else if (mode === `invert`) {
|
|
207
|
+
const toggled = new Set(this.#base);
|
|
208
|
+
for (const item of hitSet) if (toggled.has(item)) toggled.delete(item);
|
|
209
|
+
else toggled.add(item);
|
|
210
|
+
preview = toggled;
|
|
211
|
+
} else preview = hitSet;
|
|
212
|
+
this.#preview = preview;
|
|
213
|
+
try {
|
|
214
|
+
this.#config.onPreview(preview, rect);
|
|
215
|
+
} catch {}
|
|
216
|
+
}
|
|
217
|
+
#onPointerUp = (event) => {
|
|
218
|
+
if (this.#pointerId === void 0 || event.pointerId !== this.#pointerId) return;
|
|
219
|
+
this.#lastModifiers = {
|
|
220
|
+
shiftKey: event.shiftKey,
|
|
221
|
+
ctrlKey: event.ctrlKey,
|
|
222
|
+
metaKey: event.metaKey
|
|
223
|
+
};
|
|
224
|
+
const wasDragging = this.#dragging;
|
|
225
|
+
const surface = this.#safeSurface() ?? this.#host;
|
|
226
|
+
try {
|
|
227
|
+
if (typeof surface.hasPointerCapture === `function` && typeof surface.releasePointerCapture === `function` && surface.hasPointerCapture(event.pointerId)) surface.releasePointerCapture(event.pointerId);
|
|
228
|
+
} catch {}
|
|
229
|
+
this.#stopAutoScroll();
|
|
230
|
+
this.#pointerId = void 0;
|
|
231
|
+
if (!wasDragging) {
|
|
232
|
+
this.#reset();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
this.#dragging = false;
|
|
236
|
+
const final = this.#preview;
|
|
237
|
+
try {
|
|
238
|
+
this.#config.onCommit(final, { additive: this.#lastMode !== `replace` });
|
|
239
|
+
} catch {}
|
|
240
|
+
try {
|
|
241
|
+
this.#config.onPreview(/* @__PURE__ */ new Set(), void 0);
|
|
242
|
+
} catch {}
|
|
243
|
+
this.#preview = /* @__PURE__ */ new Set();
|
|
244
|
+
this.#suppressClick = true;
|
|
245
|
+
window.setTimeout(() => {
|
|
246
|
+
this.#suppressClick = false;
|
|
247
|
+
}, 0);
|
|
248
|
+
};
|
|
249
|
+
#onPointerCancel = (event) => {
|
|
250
|
+
if (this.#pointerId === void 0 || event.pointerId !== this.#pointerId) return;
|
|
251
|
+
this.#stopAutoScroll();
|
|
252
|
+
this.#pointerId = void 0;
|
|
253
|
+
this.#dragging = false;
|
|
254
|
+
this.#preview = /* @__PURE__ */ new Set();
|
|
255
|
+
try {
|
|
256
|
+
this.#config.onPreview(/* @__PURE__ */ new Set(), void 0);
|
|
257
|
+
} catch {}
|
|
258
|
+
this.#suppressClick = false;
|
|
259
|
+
};
|
|
260
|
+
#kickAutoScroll() {
|
|
261
|
+
if (!this.#config.autoScroll || this.#raf !== 0) return;
|
|
262
|
+
this.#raf = requestAnimationFrame(this.#autoScrollTick);
|
|
263
|
+
}
|
|
264
|
+
#autoScrollTick = () => {
|
|
265
|
+
this.#raf = 0;
|
|
266
|
+
if (!this.#dragging || this.#pointerId === void 0) return;
|
|
267
|
+
const box = this.#surfaceBox();
|
|
268
|
+
if (!box) return;
|
|
269
|
+
const px = this.#currentClientX - box.left;
|
|
270
|
+
const py = this.#currentClientY - box.top;
|
|
271
|
+
let dx = 0;
|
|
272
|
+
let dy = 0;
|
|
273
|
+
if (px < EDGE_ZONE) dx = -Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, px) / EDGE_ZONE));
|
|
274
|
+
else if (px > box.width - EDGE_ZONE) dx = Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, box.width - px) / EDGE_ZONE));
|
|
275
|
+
if (py < EDGE_ZONE) dy = -Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, py) / EDGE_ZONE));
|
|
276
|
+
else if (py > box.height - EDGE_ZONE) dy = Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, box.height - py) / EDGE_ZONE));
|
|
277
|
+
if (dx === 0 && dy === 0) return;
|
|
278
|
+
const autoScroll = this.#config.autoScroll;
|
|
279
|
+
if (!autoScroll) return;
|
|
280
|
+
try {
|
|
281
|
+
autoScroll(dx, dy);
|
|
282
|
+
} catch {}
|
|
283
|
+
this.#updatePreview();
|
|
284
|
+
this.#raf = requestAnimationFrame(this.#autoScrollTick);
|
|
285
|
+
};
|
|
286
|
+
#stopAutoScroll() {
|
|
287
|
+
if (this.#raf !== 0) {
|
|
288
|
+
cancelAnimationFrame(this.#raf);
|
|
289
|
+
this.#raf = 0;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
#reset() {
|
|
293
|
+
this.#dragging = false;
|
|
294
|
+
this.#suppressClick = false;
|
|
295
|
+
this.#base = /* @__PURE__ */ new Set();
|
|
296
|
+
this.#lastMode = `replace`;
|
|
297
|
+
this.#preview = /* @__PURE__ */ new Set();
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/interaction/list-selection-controller.ts
|
|
302
|
+
/** Whether the interaction mode allows building a multi-item selection. */
|
|
303
|
+
function isMultiMode(mode) {
|
|
304
|
+
return mode === `checked` || mode === `sticky` || mode === `vscode` || mode === `standard`;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Generic selection state machine shared by list components.
|
|
308
|
+
*
|
|
309
|
+
* Owns the selected set plus the range-selection anchor, the selection
|
|
310
|
+
* primitives (`replace` / `toggle` / `rangeTo` / `clear` / `selectAll` /
|
|
311
|
+
* `selectMany` / `deselect`) and the per-interactionMode gesture routers
|
|
312
|
+
* (`handleClick` / `handleSpace`).
|
|
313
|
+
*
|
|
314
|
+
* Identity is generic: both `ixfx-vertical-list` and `ixfx-detail-list` use
|
|
315
|
+
* `ListSelectionController<Element>`.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```ts
|
|
319
|
+
* const selection = new ListSelectionController<Element>({
|
|
320
|
+
* getOrderedItems: () => this.getVisibleItems(),
|
|
321
|
+
* isSelectable: el => !el.hasAttribute(`not-checked`),
|
|
322
|
+
* getModes: () => ({ selectionMode: this.selectionMode, interactionMode: this.interactionMode }),
|
|
323
|
+
* onChange: (selected, previous) => {
|
|
324
|
+
* this.syncItemStates();
|
|
325
|
+
* this.dispatchEvent(new CustomEvent(`list-select`, { detail: { selected, previous } }));
|
|
326
|
+
* },
|
|
327
|
+
* });
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
var ListSelectionController = class {
|
|
331
|
+
#config;
|
|
332
|
+
#selected = /* @__PURE__ */ new Set();
|
|
333
|
+
#anchor;
|
|
334
|
+
constructor(config) {
|
|
335
|
+
this.#config = config;
|
|
336
|
+
}
|
|
337
|
+
/** The current selection set (read-only). */
|
|
338
|
+
get selected() {
|
|
339
|
+
return this.#selected;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* The current selection as stable string keys (read-only). Empty unless a
|
|
343
|
+
* `getKey` was supplied in config. Items whose `getKey` returns `undefined`
|
|
344
|
+
* are omitted.
|
|
345
|
+
*/
|
|
346
|
+
get selectedKeys() {
|
|
347
|
+
const getKey = this.#config.getKey;
|
|
348
|
+
const out = /* @__PURE__ */ new Set();
|
|
349
|
+
if (getKey) for (const item of this.#selected) {
|
|
350
|
+
const key = getKey(item);
|
|
351
|
+
if (key !== void 0) out.add(key);
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
355
|
+
/** Anchor for range (shift) selection — the last plainly-selected item. */
|
|
356
|
+
get anchor() {
|
|
357
|
+
return this.#anchor;
|
|
358
|
+
}
|
|
359
|
+
/** Whether "select all" (Cmd/Ctrl+A) is currently permitted. */
|
|
360
|
+
canSelectAll() {
|
|
361
|
+
return this.canSelectMultiple() && this.#config.getModes().selectionMode !== `none`;
|
|
362
|
+
}
|
|
363
|
+
/** Whether a multi-item selection can be built in the current modes. */
|
|
364
|
+
canSelectMultiple() {
|
|
365
|
+
const { selectionMode, interactionMode } = this.#config.getModes();
|
|
366
|
+
return selectionMode === `multiple` || isMultiMode(interactionMode);
|
|
367
|
+
}
|
|
368
|
+
/** Replace the entire selection with a single item; updates the anchor. */
|
|
369
|
+
replace(item) {
|
|
370
|
+
if (this.#config.getModes().selectionMode === `none`) return;
|
|
371
|
+
if (!this.#config.isSelectable(item)) return;
|
|
372
|
+
this.#anchor = item;
|
|
373
|
+
this.#apply(/* @__PURE__ */ new Set([item]));
|
|
374
|
+
}
|
|
375
|
+
/** Toggle a single item in/out of the selection without replacing it. Anchor unchanged. */
|
|
376
|
+
toggle(item) {
|
|
377
|
+
const { selectionMode, interactionMode } = this.#config.getModes();
|
|
378
|
+
if (!this.#config.isSelectable(item)) return;
|
|
379
|
+
if (selectionMode === `none` && interactionMode !== `checked` && interactionMode !== `sticky`) return;
|
|
380
|
+
const next = new Set(this.#selected);
|
|
381
|
+
if (next.has(item)) next.delete(item);
|
|
382
|
+
else {
|
|
383
|
+
if (!this.canSelectMultiple()) next.clear();
|
|
384
|
+
next.add(item);
|
|
385
|
+
}
|
|
386
|
+
this.#apply(next);
|
|
387
|
+
}
|
|
388
|
+
/** Select the contiguous ordered range from the anchor to `item`. Anchor unchanged. */
|
|
389
|
+
rangeTo(item) {
|
|
390
|
+
if (this.#config.getModes().selectionMode === `none`) return;
|
|
391
|
+
if (this.#anchor === void 0) {
|
|
392
|
+
this.replace(item);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const ordered = this.#config.getOrderedItems();
|
|
396
|
+
const anchorIdx = ordered.indexOf(this.#anchor);
|
|
397
|
+
const targetIdx = ordered.indexOf(item);
|
|
398
|
+
if (anchorIdx === -1 || targetIdx === -1) {
|
|
399
|
+
this.replace(item);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const start = Math.min(anchorIdx, targetIdx);
|
|
403
|
+
const end = Math.max(anchorIdx, targetIdx);
|
|
404
|
+
this.#apply(new Set(ordered.slice(start, end + 1).filter((t) => this.#config.isSelectable(t))));
|
|
405
|
+
}
|
|
406
|
+
/** Clear the selection. No change event when the selection is already empty. */
|
|
407
|
+
clear() {
|
|
408
|
+
if (this.#selected.size === 0) return;
|
|
409
|
+
this.#apply(/* @__PURE__ */ new Set());
|
|
410
|
+
}
|
|
411
|
+
/** Select every ordered item that passes `isSelectable`. */
|
|
412
|
+
selectAll() {
|
|
413
|
+
const ordered = this.#config.getOrderedItems();
|
|
414
|
+
if (ordered.length === 0) return;
|
|
415
|
+
const next = new Set(ordered.filter((t) => this.#config.isSelectable(t)));
|
|
416
|
+
if (next.size === 0) return;
|
|
417
|
+
this.#apply(next);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Replace the selection with many items at once. Requires
|
|
421
|
+
* `selectionMode="multiple"`, throws otherwise. Updates the anchor.
|
|
422
|
+
*/
|
|
423
|
+
selectMany(items) {
|
|
424
|
+
const { selectionMode } = this.#config.getModes();
|
|
425
|
+
if (selectionMode !== `multiple`) throw new Error(`selectMany requires selectionMode="multiple" (current: "${selectionMode}")`);
|
|
426
|
+
const next = new Set([...items].filter((t) => this.#config.isSelectable(t)));
|
|
427
|
+
if (next.size === 0) return;
|
|
428
|
+
this.#anchor = [...next].at(-1);
|
|
429
|
+
this.#apply(next);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Whether a drag / marquee gesture may build a selection in the current
|
|
433
|
+
* modes: `selectionMode="multiple"`, or a multi interaction mode
|
|
434
|
+
* (`checked` / `sticky` / `vscode`) outside `selectionMode="none"`.
|
|
435
|
+
* Stricter than `canSelectMultiple()` — `single` + `standard` does not
|
|
436
|
+
* qualify (a drag there would be a surprising multi-select).
|
|
437
|
+
*/
|
|
438
|
+
canDragSelect() {
|
|
439
|
+
return this.#config.getModes().selectionMode !== `none` && this.#effectiveMulti();
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Replace the selection with `items` from a drag / marquee gesture. Like
|
|
443
|
+
* `selectMany` but gated on `canDragSelect()` instead of throwing, and a
|
|
444
|
+
* no-op in single/none modes. Non-selectable items are dropped; the anchor
|
|
445
|
+
* moves to the last item. Fires `onChange` once. An empty result clears a
|
|
446
|
+
* non-empty selection (e.g. a Cmd-drag that toggles everything off) but is
|
|
447
|
+
* a no-op when nothing is selected.
|
|
448
|
+
*/
|
|
449
|
+
applyDragSelection(items) {
|
|
450
|
+
if (!this.canDragSelect()) return;
|
|
451
|
+
const next = new Set([...items].filter((t) => this.#config.isSelectable(t)));
|
|
452
|
+
if (next.size === 0 && this.#selected.size === 0) return;
|
|
453
|
+
this.#anchor = [...next].at(-1);
|
|
454
|
+
this.#apply(next);
|
|
455
|
+
}
|
|
456
|
+
/** Remove a single item from the selection. */
|
|
457
|
+
deselect(item) {
|
|
458
|
+
if (!this.#selected.has(item)) return;
|
|
459
|
+
const next = new Set(this.#selected);
|
|
460
|
+
next.delete(item);
|
|
461
|
+
this.#apply(next);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Whether the current modes permit a selection of more than one item.
|
|
465
|
+
* Stricter than `canSelectMultiple()`: `standard` / `implicit` only count
|
|
466
|
+
* as multi when `selectionMode="multiple"`.
|
|
467
|
+
*/
|
|
468
|
+
#effectiveMulti() {
|
|
469
|
+
const { selectionMode, interactionMode } = this.#config.getModes();
|
|
470
|
+
return selectionMode === `multiple` || interactionMode === `checked` || interactionMode === `sticky` || interactionMode === `vscode`;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Replace the selection with the items resolved from `keys`. Keys that
|
|
474
|
+
* `resolve` cannot map, or that resolve to a non-selectable item, are
|
|
475
|
+
* ignored. `selectionMode="none"` is a no-op. When multi-select is not
|
|
476
|
+
* available, only the last resolvable key is kept. `onChange` fires once.
|
|
477
|
+
*/
|
|
478
|
+
selectKeys(keys, resolve) {
|
|
479
|
+
if (this.#config.getModes().selectionMode === `none`) return;
|
|
480
|
+
let next = /* @__PURE__ */ new Set();
|
|
481
|
+
for (const key of new Set(keys)) {
|
|
482
|
+
const item = resolve(key);
|
|
483
|
+
if (item !== void 0 && this.#config.isSelectable(item)) next.add(item);
|
|
484
|
+
}
|
|
485
|
+
if (!this.#effectiveMulti() && next.size > 1) {
|
|
486
|
+
const last = [...next].at(-1);
|
|
487
|
+
next = last === void 0 ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([last]);
|
|
488
|
+
}
|
|
489
|
+
this.#anchor = [...next].at(-1);
|
|
490
|
+
this.#apply(next);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Add the items resolved from `keys` to the selection (multi-select only;
|
|
494
|
+
* a no-op otherwise). `onChange` fires once if anything changed.
|
|
495
|
+
*/
|
|
496
|
+
addKeys(keys, resolve) {
|
|
497
|
+
if (!this.#effectiveMulti()) return;
|
|
498
|
+
const next = new Set(this.#selected);
|
|
499
|
+
let changed = false;
|
|
500
|
+
for (const key of new Set(keys)) {
|
|
501
|
+
const item = resolve(key);
|
|
502
|
+
if (item !== void 0 && !next.has(item) && this.#config.isSelectable(item)) {
|
|
503
|
+
next.add(item);
|
|
504
|
+
changed = true;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (changed) this.#apply(next);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Remove the items resolved from `keys` from the selection. `onChange`
|
|
511
|
+
* fires once if anything changed.
|
|
512
|
+
*/
|
|
513
|
+
removeKeys(keys, resolve) {
|
|
514
|
+
if (this.#selected.size === 0) return;
|
|
515
|
+
const next = new Set(this.#selected);
|
|
516
|
+
let changed = false;
|
|
517
|
+
for (const key of new Set(keys)) {
|
|
518
|
+
const item = resolve(key);
|
|
519
|
+
if (item !== void 0 && next.delete(item)) changed = true;
|
|
520
|
+
}
|
|
521
|
+
if (changed) this.#apply(next);
|
|
522
|
+
}
|
|
523
|
+
/** Route a click on `item` according to the current interaction mode. */
|
|
524
|
+
handleClick(item, gesture) {
|
|
525
|
+
const { selectionMode, interactionMode: mode } = this.#config.getModes();
|
|
526
|
+
if (mode === `manual`) return;
|
|
527
|
+
if (selectionMode === `none` && mode !== `checked` && mode !== `sticky`) return;
|
|
528
|
+
switch (mode) {
|
|
529
|
+
case `implicit`:
|
|
530
|
+
this.replace(item);
|
|
531
|
+
break;
|
|
532
|
+
case `standard`:
|
|
533
|
+
case `vscode`:
|
|
534
|
+
if (gesture.ctrlKey || gesture.metaKey) {
|
|
535
|
+
if (selectionMode === `multiple` || mode === `vscode`) this.toggle(item);
|
|
536
|
+
} else if (gesture.shiftKey && (selectionMode === `multiple` || mode === `vscode`)) this.rangeTo(item);
|
|
537
|
+
else this.replace(item);
|
|
538
|
+
break;
|
|
539
|
+
case `checked`: break;
|
|
540
|
+
case `sticky`: this.toggle(item);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/** Route a Space keypress on `item` according to the current interaction mode. */
|
|
544
|
+
handleSpace(item) {
|
|
545
|
+
const mode = this.#config.getModes().interactionMode;
|
|
546
|
+
if (mode === `manual`) return;
|
|
547
|
+
if (mode === `implicit`) this.replace(item);
|
|
548
|
+
else this.toggle(item);
|
|
549
|
+
}
|
|
550
|
+
/** Drop the range-selection anchor (e.g. when the interaction mode changes). */
|
|
551
|
+
clearAnchor() {
|
|
552
|
+
this.#anchor = void 0;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Re-key the selection across an identity boundary — e.g. when items are
|
|
556
|
+
* wrapped/unwrapped and the selected Element references change. Silent:
|
|
557
|
+
* no `onChange` is fired.
|
|
558
|
+
*/
|
|
559
|
+
remap(fn) {
|
|
560
|
+
if (this.#selected.size === 0) return;
|
|
561
|
+
this.#selected = new Set([...this.#selected].map(fn));
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Hard reset: empty the selection and clear the anchor without firing
|
|
565
|
+
* `onChange` (e.g. when all items are removed).
|
|
566
|
+
*/
|
|
567
|
+
reset() {
|
|
568
|
+
this.#selected = /* @__PURE__ */ new Set();
|
|
569
|
+
this.#anchor = void 0;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Replace the selection and anchor silently (no `onChange`). Used to
|
|
573
|
+
* re-key the selection when items are rebuilt from data — e.g. matching
|
|
574
|
+
* new elements to previously selected keys.
|
|
575
|
+
*/
|
|
576
|
+
seed(items, anchor) {
|
|
577
|
+
this.#selected = new Set(items);
|
|
578
|
+
this.#anchor = anchor;
|
|
579
|
+
}
|
|
580
|
+
#apply(next) {
|
|
581
|
+
const previous = this.#selected;
|
|
582
|
+
this.#selected = next;
|
|
583
|
+
this.#config.onChange(next, previous);
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
//#endregion
|
|
587
|
+
export { isMultiMode as n, ListDragSelectController as r, ListSelectionController as t };
|
|
588
|
+
|
|
589
|
+
//# sourceMappingURL=list-selection-controller-SA4piSBi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-selection-controller-SA4piSBi.js","names":["#host","#config","#dragging","#suppressClick","#bindSurface","#onPointerMove","#onPointerUp","#onPointerCancel","#unbindSurface","#stopAutoScroll","#reset","#safeSurface","#boundSurface","#onPointerDown","#enabledTypes","#pointerId","#startClientX","#startClientY","#currentClientX","#currentClientY","#startX","#startY","#base","#lastMode","#lastModifiers","#threshold","#updatePreview","#kickAutoScroll","#surfaceBox","#currentRect","#modeFor","#preview","#raf","#autoScrollTick","#config","#selected","#anchor","#apply","#effectiveMulti"],"sources":["../src/interaction/list-drag-select-controller.ts","../src/interaction/list-selection-controller.ts"],"sourcesContent":["import type { ReactiveController, ReactiveControllerHost } from 'lit';\n\n/** Surface-relative rectangle: origin at the scroll container's top-left. */\nexport type DragSelectRect = {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n};\n\nexport type ListDragSelectControllerConfig<T> = {\n /** Element the marquee is drawn in and pointer geometry is measured against. */\n getSurface: () => HTMLElement | undefined;\n /** Is drag-select currently allowed? (multi-capable, has items, not disabled) */\n isEnabled: () => boolean;\n /** Pointer types that may start a drag. Default ['mouse', 'pen'] — touch still scrolls. */\n pointerTypes?: readonly string[];\n /** Movement in px before a press becomes a drag. Default 4. */\n threshold?: number;\n /** Map a surface-relative rect to covered items. */\n hitTest: (rect: DragSelectRect) => Iterable<T>;\n /**\n * Selection snapshotted when the drag starts, combined with the hit set\n * while modifiers are held: Shift adds (`base + hit`), Cmd / Ctrl inverts\n * (covered items toggle, the rest is untouched).\n */\n getBaseSelection: () => ReadonlySet<T>;\n /** Continuous visual feedback: the preview set + current rect (undefined when idle). */\n onPreview: (preview: ReadonlySet<T>, rect: DragSelectRect | undefined) => void;\n /**\n * Fired once on release with the final set. Host applies it to its\n * selection. `additive` is `true` when the drag ran with Shift (add) or\n * Cmd / Ctrl (invert) held.\n */\n onCommit: (finalSet: ReadonlySet<T>, info: { additive: boolean }) => void;\n /** Optional: scroll the surface when the pointer nears an edge during a drag. */\n autoScroll?: (dx: number, dy: number) => void;\n /**\n * Optional predicate called on pointerdown before the controller records its\n * pointer id or later calls setPointerCapture/preventDefault. Return false\n * to yield to another gesture (e.g. native item drag).\n */\n shouldStart?: (event: PointerEvent) => boolean;\n};\n\ntype DragHost = ReactiveControllerHost & HTMLElement;\n\nconst DEFAULT_POINTER_TYPES = [`mouse`, `pen`] as const;\nconst DEFAULT_THRESHOLD = 4;\n/** Pointer distance (px) from an edge that triggers auto-scroll. */\nconst EDGE_ZONE = 28;\n/** Max auto-scroll speed in px per frame. */\nconst MAX_SCROLL_SPEED = 16;\n\n/**\n * Shared pointer-drag (marquee / rubber-band) selection for list components.\n *\n * Owns all pointer plumbing and rectangle state; knows nothing about DOM\n * geometry or the selection model — those are host callbacks. Mirrors the\n * `TickledController` attach pattern (`new ListDragSelectController(this, …)`).\n *\n * Behaviour: `pointerdown` on the surface records a start point; once the\n * pointer moves past `threshold`, the surface captures the pointer and every\n * move recomputes the rect, hit-tests, and calls `onPreview`. On release the\n * final set is delivered once via `onCommit`. A press that never crosses the\n * threshold does nothing (the normal `@click` path runs).\n */\nexport class ListDragSelectController<T> implements ReactiveController {\n readonly #host: DragHost;\n readonly #config: ListDragSelectControllerConfig<T>;\n\n #boundSurface: EventTarget | undefined;\n\n #pointerId: number | undefined;\n #startClientX = 0;\n #startClientY = 0;\n #startX = 0;\n #startY = 0;\n #currentClientX = 0;\n #currentClientY = 0;\n #lastModifiers = { shiftKey: false, ctrlKey: false, metaKey: false };\n #dragging = false;\n #suppressClick = false;\n /** Selection as it was when the drag started — the combine baseline. */\n #base: ReadonlySet<T> = new Set();\n /** Combine mode of the most recent preview (drives the commit flag). */\n #lastMode: `replace` | `add` | `invert` = `replace`;\n #preview: ReadonlySet<T> = new Set();\n #raf = 0;\n\n constructor(host: DragHost, config: ListDragSelectControllerConfig<T>) {\n this.#host = host;\n this.#config = config;\n host.addController(this);\n }\n\n /** `true` while a drag is in progress or its trailing click is still pending. */\n get isDragging(): boolean {\n return this.#dragging || this.#suppressClick;\n }\n\n /** `true` once the press has crossed the threshold and a rect is live. */\n get isActive(): boolean {\n return this.#dragging;\n }\n\n hostConnected(): void {\n this.#bindSurface();\n window.addEventListener(`pointermove`, this.#onPointerMove);\n window.addEventListener(`pointerup`, this.#onPointerUp);\n window.addEventListener(`pointercancel`, this.#onPointerCancel);\n }\n\n hostDisconnected(): void {\n this.#unbindSurface();\n window.removeEventListener(`pointermove`, this.#onPointerMove);\n window.removeEventListener(`pointerup`, this.#onPointerUp);\n window.removeEventListener(`pointercancel`, this.#onPointerCancel);\n this.#stopAutoScroll();\n this.#reset();\n }\n\n /** Re-bind when the surface element appears/changes (e.g. first render). */\n hostUpdated(): void {\n const next = this.#safeSurface() ?? this.#host;\n if (next !== this.#boundSurface) {\n this.#bindSurface();\n }\n }\n\n #safeSurface(): HTMLElement | undefined {\n try {\n return this.#config.getSurface();\n } catch {\n return undefined;\n }\n }\n\n #bindSurface(): void {\n const next = this.#safeSurface() ?? this.#host;\n if (next === this.#boundSurface)\n return;\n this.#unbindSurface();\n this.#boundSurface = next;\n next.addEventListener(`pointerdown`, this.#onPointerDown as EventListener);\n }\n\n #unbindSurface(): void {\n this.#boundSurface?.removeEventListener(`pointerdown`, this.#onPointerDown as EventListener);\n this.#boundSurface = undefined;\n }\n\n #enabledTypes(): readonly string[] {\n return this.#config.pointerTypes ?? DEFAULT_POINTER_TYPES;\n }\n\n #threshold(): number {\n return this.#config.threshold ?? DEFAULT_THRESHOLD;\n }\n\n #onPointerDown = (event: PointerEvent): void => {\n if (event.button !== 0)\n return;\n if (!this.#enabledTypes().includes(event.pointerType))\n return;\n let enabled = false;\n try {\n enabled = this.#config.isEnabled();\n } catch {\n return;\n }\n if (!enabled)\n return;\n const target = event.target as Element | null;\n if (target instanceof Element && typeof target.closest === `function`) {\n if (target.closest(`input,button,a,select,textarea,[contenteditable]`))\n return;\n }\n if (this.#config.shouldStart) {\n try {\n if (!this.#config.shouldStart(event))\n return;\n } catch {\n return;\n }\n }\n const surface = this.#safeSurface() ?? this.#host;\n const box = surface.getBoundingClientRect();\n this.#pointerId = event.pointerId;\n this.#startClientX = event.clientX;\n this.#startClientY = event.clientY;\n this.#currentClientX = event.clientX;\n this.#currentClientY = event.clientY;\n this.#startX = Math.min(Math.max(event.clientX - box.left, 0), Math.max(0, box.width));\n this.#startY = Math.min(Math.max(event.clientY - box.top, 0), Math.max(0, box.height));\n this.#dragging = false;\n this.#suppressClick = false;\n this.#base = new Set();\n this.#lastMode = `replace`;\n this.#lastModifiers = { shiftKey: event.shiftKey, ctrlKey: event.ctrlKey, metaKey: event.metaKey };\n // Do not capture yet — a sub-threshold press is a plain click.\n };\n\n /** Combine mode for the current modifiers: Shift adds, Cmd/Ctrl inverts. */\n #modeFor(modifiers: { shiftKey: boolean; ctrlKey: boolean; metaKey: boolean }): `replace` | `add` | `invert` {\n if (modifiers.metaKey || modifiers.ctrlKey)\n return `invert`;\n if (modifiers.shiftKey)\n return `add`;\n return `replace`;\n }\n\n #onPointerMove = (event: PointerEvent): void => {\n if (this.#pointerId === undefined || event.pointerId !== this.#pointerId)\n return;\n this.#currentClientX = event.clientX;\n this.#currentClientY = event.clientY;\n this.#lastModifiers = { shiftKey: event.shiftKey, ctrlKey: event.ctrlKey, metaKey: event.metaKey };\n const moved = Math.hypot(event.clientX - this.#startClientX, event.clientY - this.#startClientY);\n if (!this.#dragging) {\n if (moved < this.#threshold())\n return;\n this.#dragging = true;\n try {\n this.#base = new Set(this.#config.getBaseSelection());\n } catch {\n this.#base = new Set();\n }\n const surface = this.#safeSurface() ?? this.#host;\n try {\n if (typeof surface.setPointerCapture === `function`)\n surface.setPointerCapture(event.pointerId);\n } catch {\n // happy-dom and some environments lack setPointerCapture — non-fatal.\n }\n }\n try {\n event.preventDefault?.();\n } catch {\n // Ignore — prevents native image drag where supported.\n }\n this.#updatePreview();\n this.#kickAutoScroll();\n };\n\n #surfaceBox(): DOMRect | undefined {\n const surface = this.#safeSurface() ?? this.#host;\n try {\n return surface.getBoundingClientRect();\n } catch {\n return undefined;\n }\n }\n\n #currentRect(): DragSelectRect | undefined {\n const box = this.#surfaceBox();\n if (!box)\n return undefined;\n const cx = Math.min(Math.max(this.#currentClientX - box.left, 0), Math.max(0, box.width));\n const cy = Math.min(Math.max(this.#currentClientY - box.top, 0), Math.max(0, box.height));\n return {\n x: Math.min(this.#startX, cx),\n y: Math.min(this.#startY, cy),\n width: Math.abs(cx - this.#startX),\n height: Math.abs(cy - this.#startY),\n };\n }\n\n #updatePreview(): void {\n const rect = this.#currentRect();\n if (!rect)\n return;\n let hit: Iterable<T> = [];\n try {\n hit = this.#config.hitTest(rect);\n } catch {\n hit = [];\n }\n const mode = this.#modeFor(this.#lastModifiers);\n this.#lastMode = mode;\n const hitSet = new Set(hit);\n let preview: ReadonlySet<T>;\n if (mode === `add`) {\n preview = new Set([...this.#base, ...hitSet]);\n } else if (mode === `invert`) {\n const toggled = new Set(this.#base);\n for (const item of hitSet) {\n if (toggled.has(item))\n toggled.delete(item);\n else\n toggled.add(item);\n }\n preview = toggled;\n } else {\n preview = hitSet;\n }\n this.#preview = preview;\n try {\n this.#config.onPreview(preview, rect);\n } catch {\n // Host preview must never break the gesture.\n }\n }\n\n #onPointerUp = (event: PointerEvent): void => {\n if (this.#pointerId === undefined || event.pointerId !== this.#pointerId)\n return;\n this.#lastModifiers = { shiftKey: event.shiftKey, ctrlKey: event.ctrlKey, metaKey: event.metaKey };\n const wasDragging = this.#dragging;\n const surface = this.#safeSurface() ?? this.#host;\n try {\n if (typeof surface.hasPointerCapture === `function`\n && typeof surface.releasePointerCapture === `function`\n && surface.hasPointerCapture(event.pointerId)) {\n surface.releasePointerCapture(event.pointerId);\n }\n } catch {\n // Non-fatal (see pointermove).\n }\n this.#stopAutoScroll();\n this.#pointerId = undefined;\n if (!wasDragging) {\n this.#reset();\n return;\n }\n this.#dragging = false;\n const final = this.#preview;\n try {\n this.#config.onCommit(final, { additive: this.#lastMode !== `replace` });\n } catch {\n // Host commit must never break state reset.\n }\n try {\n this.#config.onPreview(new Set(), undefined);\n } catch {\n // Ignore.\n }\n this.#preview = new Set();\n // Keep `isDragging` true until the trailing synthetic `click` has been\n // dispatched so hosts can swallow it; clicks dispatch before timers fire.\n this.#suppressClick = true;\n window.setTimeout(() => {\n this.#suppressClick = false;\n }, 0);\n };\n\n #onPointerCancel = (event: PointerEvent): void => {\n if (this.#pointerId === undefined || event.pointerId !== this.#pointerId)\n return;\n this.#stopAutoScroll();\n this.#pointerId = undefined;\n this.#dragging = false;\n this.#preview = new Set();\n try {\n this.#config.onPreview(new Set(), undefined);\n } catch {\n // Ignore.\n }\n this.#suppressClick = false;\n };\n\n #kickAutoScroll(): void {\n if (!this.#config.autoScroll || this.#raf !== 0)\n return;\n this.#raf = requestAnimationFrame(this.#autoScrollTick);\n }\n\n #autoScrollTick = (): void => {\n this.#raf = 0;\n if (!this.#dragging || this.#pointerId === undefined)\n return;\n const box = this.#surfaceBox();\n if (!box)\n return;\n const px = this.#currentClientX - box.left;\n const py = this.#currentClientY - box.top;\n let dx = 0;\n let dy = 0;\n if (px < EDGE_ZONE)\n dx = -Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, px) / EDGE_ZONE));\n else if (px > box.width - EDGE_ZONE)\n dx = Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, box.width - px) / EDGE_ZONE));\n if (py < EDGE_ZONE)\n dy = -Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, py) / EDGE_ZONE));\n else if (py > box.height - EDGE_ZONE)\n dy = Math.ceil(MAX_SCROLL_SPEED * (1 - Math.max(0, box.height - py) / EDGE_ZONE));\n if (dx === 0 && dy === 0)\n return;\n const autoScroll = this.#config.autoScroll;\n if (!autoScroll)\n return;\n try {\n autoScroll(dx, dy);\n } catch {\n // Ignore.\n }\n // Scrolling changes which items a fixed surface-relative rect covers\n // (hosts add the scroll offset in hitTest), so re-run the preview.\n this.#updatePreview();\n this.#raf = requestAnimationFrame(this.#autoScrollTick);\n };\n\n #stopAutoScroll(): void {\n if (this.#raf !== 0) {\n cancelAnimationFrame(this.#raf);\n this.#raf = 0;\n }\n }\n\n #reset(): void {\n this.#dragging = false;\n this.#suppressClick = false;\n this.#base = new Set();\n this.#lastMode = `replace`;\n this.#preview = new Set();\n }\n}\n","import type { ListInteractionMode, ListSelectionMode } from './list-selection-types.js';\n\n/** Modifier state needed to route a click gesture per interaction mode. */\nexport type ListSelectionGesture = {\n readonly ctrlKey: boolean;\n readonly metaKey: boolean;\n readonly shiftKey: boolean;\n};\n\nexport type ListSelectionModes = {\n readonly selectionMode: ListSelectionMode;\n readonly interactionMode: ListInteractionMode;\n};\n\n/**\n * Configuration supplied by the host component.\n *\n * The controller owns selection *state* only — everything DOM- or\n * component-specific is expressed through these callbacks.\n */\nexport type ListSelectionControllerConfig<T> = {\n /** Visible items in navigation order — used for range selection and select-all. */\n readonly getOrderedItems: () => readonly T[];\n /** Whether an item may join the selection (e.g. reject `not-checked` rows). */\n readonly isSelectable: (item: T) => boolean;\n /** Current modes; read on every gesture so property changes apply immediately. */\n readonly getModes: () => ListSelectionModes;\n /**\n * Fired after every selection change. The host syncs DOM state\n * (`data-selected` / `data-checked`), dispatches `list-select`, and\n * requests an update.\n */\n readonly onChange: (selected: ReadonlySet<T>, previous: ReadonlySet<T>) => void;\n /**\n * Optional: derive a stable string key for an item. Enables `selectedKeys`\n * and `selectKeys()`. When omitted, `selectedKeys` is always empty.\n */\n readonly getKey?: (item: T) => string | undefined;\n};\n\n/** Whether the interaction mode allows building a multi-item selection. */\nexport function isMultiMode(mode: ListInteractionMode): boolean {\n return mode === `checked` || mode === `sticky` || mode === `vscode` || mode === `standard`;\n}\n\n/**\n * Generic selection state machine shared by list components.\n *\n * Owns the selected set plus the range-selection anchor, the selection\n * primitives (`replace` / `toggle` / `rangeTo` / `clear` / `selectAll` /\n * `selectMany` / `deselect`) and the per-interactionMode gesture routers\n * (`handleClick` / `handleSpace`).\n *\n * Identity is generic: both `ixfx-vertical-list` and `ixfx-detail-list` use\n * `ListSelectionController<Element>`.\n *\n * @example\n * ```ts\n * const selection = new ListSelectionController<Element>({\n * getOrderedItems: () => this.getVisibleItems(),\n * isSelectable: el => !el.hasAttribute(`not-checked`),\n * getModes: () => ({ selectionMode: this.selectionMode, interactionMode: this.interactionMode }),\n * onChange: (selected, previous) => {\n * this.syncItemStates();\n * this.dispatchEvent(new CustomEvent(`list-select`, { detail: { selected, previous } }));\n * },\n * });\n * ```\n */\nexport class ListSelectionController<T> {\n readonly #config: ListSelectionControllerConfig<T>;\n #selected: ReadonlySet<T> = new Set();\n #anchor: T | undefined;\n\n constructor(config: ListSelectionControllerConfig<T>) {\n this.#config = config;\n }\n\n /** The current selection set (read-only). */\n get selected(): ReadonlySet<T> {\n return this.#selected;\n }\n\n /**\n * The current selection as stable string keys (read-only). Empty unless a\n * `getKey` was supplied in config. Items whose `getKey` returns `undefined`\n * are omitted.\n */\n get selectedKeys(): ReadonlySet<string> {\n const getKey = this.#config.getKey;\n const out = new Set<string>();\n if (getKey) {\n for (const item of this.#selected) {\n const key = getKey(item);\n if (key !== undefined) {\n out.add(key);\n }\n }\n }\n return out;\n }\n\n /** Anchor for range (shift) selection — the last plainly-selected item. */\n get anchor(): T | undefined {\n return this.#anchor;\n }\n\n /** Whether \"select all\" (Cmd/Ctrl+A) is currently permitted. */\n canSelectAll(): boolean {\n return this.canSelectMultiple() && this.#config.getModes().selectionMode !== `none`;\n }\n\n /** Whether a multi-item selection can be built in the current modes. */\n canSelectMultiple(): boolean {\n const { selectionMode, interactionMode } = this.#config.getModes();\n return selectionMode === `multiple` || isMultiMode(interactionMode);\n }\n\n // --- Selection primitives ---\n\n /** Replace the entire selection with a single item; updates the anchor. */\n replace(item: T): void {\n if (this.#config.getModes().selectionMode === `none`)\n return;\n if (!this.#config.isSelectable(item))\n return;\n this.#anchor = item;\n this.#apply(new Set([item]));\n }\n\n /** Toggle a single item in/out of the selection without replacing it. Anchor unchanged. */\n toggle(item: T): void {\n const { selectionMode, interactionMode } = this.#config.getModes();\n if (!this.#config.isSelectable(item))\n return;\n // checked/vscode/sticky modes imply multiple selection capacity\n if (selectionMode === `none` && interactionMode !== `checked` && interactionMode !== `sticky`)\n return;\n\n const next = new Set(this.#selected);\n if (next.has(item)) {\n next.delete(item);\n } else {\n if (!this.canSelectMultiple())\n next.clear();\n next.add(item);\n }\n this.#apply(next);\n }\n\n /** Select the contiguous ordered range from the anchor to `item`. Anchor unchanged. */\n rangeTo(item: T): void {\n if (this.#config.getModes().selectionMode === `none`)\n return;\n if (this.#anchor === undefined) {\n this.replace(item);\n return;\n }\n const ordered = this.#config.getOrderedItems();\n const anchorIdx = ordered.indexOf(this.#anchor);\n const targetIdx = ordered.indexOf(item);\n if (anchorIdx === -1 || targetIdx === -1) {\n this.replace(item);\n return;\n }\n const start = Math.min(anchorIdx, targetIdx);\n const end = Math.max(anchorIdx, targetIdx);\n this.#apply(new Set(ordered.slice(start, end + 1).filter(t => this.#config.isSelectable(t))));\n }\n\n /** Clear the selection. No change event when the selection is already empty. */\n clear(): void {\n if (this.#selected.size === 0)\n return;\n this.#apply(new Set());\n }\n\n /** Select every ordered item that passes `isSelectable`. */\n selectAll(): void {\n const ordered = this.#config.getOrderedItems();\n if (ordered.length === 0)\n return;\n const next = new Set(ordered.filter(t => this.#config.isSelectable(t)));\n if (next.size === 0)\n return;\n this.#apply(next);\n }\n\n /**\n * Replace the selection with many items at once. Requires\n * `selectionMode=\"multiple\"`, throws otherwise. Updates the anchor.\n */\n selectMany(items: Iterable<T>): void {\n const { selectionMode } = this.#config.getModes();\n if (selectionMode !== `multiple`) {\n throw new Error(`selectMany requires selectionMode=\"multiple\" (current: \"${selectionMode}\")`);\n }\n const next = new Set([...items].filter(t => this.#config.isSelectable(t)));\n if (next.size === 0)\n return;\n this.#anchor = [...next].at(-1);\n this.#apply(next);\n }\n\n /**\n * Whether a drag / marquee gesture may build a selection in the current\n * modes: `selectionMode=\"multiple\"`, or a multi interaction mode\n * (`checked` / `sticky` / `vscode`) outside `selectionMode=\"none\"`.\n * Stricter than `canSelectMultiple()` — `single` + `standard` does not\n * qualify (a drag there would be a surprising multi-select).\n */\n canDragSelect(): boolean {\n return this.#config.getModes().selectionMode !== `none` && this.#effectiveMulti();\n }\n\n /**\n * Replace the selection with `items` from a drag / marquee gesture. Like\n * `selectMany` but gated on `canDragSelect()` instead of throwing, and a\n * no-op in single/none modes. Non-selectable items are dropped; the anchor\n * moves to the last item. Fires `onChange` once. An empty result clears a\n * non-empty selection (e.g. a Cmd-drag that toggles everything off) but is\n * a no-op when nothing is selected.\n */\n applyDragSelection(items: Iterable<T>): void {\n if (!this.canDragSelect())\n return;\n const next = new Set([...items].filter(t => this.#config.isSelectable(t)));\n if (next.size === 0 && this.#selected.size === 0)\n return;\n this.#anchor = [...next].at(-1);\n this.#apply(next);\n }\n\n /** Remove a single item from the selection. */\n deselect(item: T): void {\n if (!this.#selected.has(item))\n return;\n const next = new Set(this.#selected);\n next.delete(item);\n this.#apply(next);\n }\n\n // --- Key-based selection ---\n\n /**\n * Whether the current modes permit a selection of more than one item.\n * Stricter than `canSelectMultiple()`: `standard` / `implicit` only count\n * as multi when `selectionMode=\"multiple\"`.\n */\n #effectiveMulti(): boolean {\n const { selectionMode, interactionMode } = this.#config.getModes();\n return selectionMode === `multiple`\n || interactionMode === `checked`\n || interactionMode === `sticky`\n || interactionMode === `vscode`;\n }\n\n /**\n * Replace the selection with the items resolved from `keys`. Keys that\n * `resolve` cannot map, or that resolve to a non-selectable item, are\n * ignored. `selectionMode=\"none\"` is a no-op. When multi-select is not\n * available, only the last resolvable key is kept. `onChange` fires once.\n */\n selectKeys(keys: Iterable<string>, resolve: (key: string) => T | undefined): void {\n if (this.#config.getModes().selectionMode === `none`)\n return;\n let next = new Set<T>();\n for (const key of new Set(keys)) {\n const item = resolve(key);\n if (item !== undefined && this.#config.isSelectable(item)) {\n next.add(item);\n }\n }\n if (!this.#effectiveMulti() && next.size > 1) {\n const last = [...next].at(-1);\n next = last === undefined ? new Set() : new Set([last]);\n }\n this.#anchor = [...next].at(-1);\n this.#apply(next);\n }\n\n /**\n * Add the items resolved from `keys` to the selection (multi-select only;\n * a no-op otherwise). `onChange` fires once if anything changed.\n */\n addKeys(keys: Iterable<string>, resolve: (key: string) => T | undefined): void {\n if (!this.#effectiveMulti())\n return;\n const next = new Set(this.#selected);\n let changed = false;\n for (const key of new Set(keys)) {\n const item = resolve(key);\n if (item !== undefined && !next.has(item) && this.#config.isSelectable(item)) {\n next.add(item);\n changed = true;\n }\n }\n if (changed) {\n this.#apply(next);\n }\n }\n\n /**\n * Remove the items resolved from `keys` from the selection. `onChange`\n * fires once if anything changed.\n */\n removeKeys(keys: Iterable<string>, resolve: (key: string) => T | undefined): void {\n if (this.#selected.size === 0)\n return;\n const next = new Set(this.#selected);\n let changed = false;\n for (const key of new Set(keys)) {\n const item = resolve(key);\n if (item !== undefined && next.delete(item)) {\n changed = true;\n }\n }\n if (changed) {\n this.#apply(next);\n }\n }\n\n // --- Gesture routers ---\n\n /** Route a click on `item` according to the current interaction mode. */\n handleClick(item: T, gesture: ListSelectionGesture): void {\n const { selectionMode, interactionMode: mode } = this.#config.getModes();\n if (mode === `manual`)\n return;\n if (selectionMode === `none` && mode !== `checked` && mode !== `sticky`)\n return;\n\n switch (mode) {\n case `implicit`:\n // Single-select only; modifier keys have no effect\n this.replace(item);\n break;\n\n case `standard`:\n case `vscode`:\n if (gesture.ctrlKey || gesture.metaKey) {\n // Ctrl/Cmd+click: toggle item; requires effective multi-select\n if (selectionMode === `multiple` || mode === `vscode`) {\n this.toggle(item);\n }\n } else if (gesture.shiftKey && (selectionMode === `multiple` || mode === `vscode`)) {\n // Shift+click: range-select from anchor\n this.rangeTo(item);\n } else {\n // Plain click: replace selection, update anchor\n this.replace(item);\n }\n break;\n\n case `checked`:\n // Body clicks do nothing — only the checkbox column toggles.\n break;\n\n case `sticky`:\n // Every tap/click toggles — multi-select without modifier keys (touchscreens).\n this.toggle(item);\n break;\n }\n }\n\n /** Route a Space keypress on `item` according to the current interaction mode. */\n handleSpace(item: T): void {\n const mode = this.#config.getModes().interactionMode;\n if (mode === `manual`)\n return;\n if (mode === `implicit`) {\n // Space selects (same as click) in implicit mode\n this.replace(item);\n } else {\n // standard / vscode / checked / sticky: Space toggles\n this.toggle(item);\n }\n }\n\n // --- Maintenance ---\n\n /** Drop the range-selection anchor (e.g. when the interaction mode changes). */\n clearAnchor(): void {\n this.#anchor = undefined;\n }\n\n /**\n * Re-key the selection across an identity boundary — e.g. when items are\n * wrapped/unwrapped and the selected Element references change. Silent:\n * no `onChange` is fired.\n */\n remap(fn: (item: T) => T): void {\n if (this.#selected.size === 0)\n return;\n this.#selected = new Set([...this.#selected].map(fn));\n }\n\n /**\n * Hard reset: empty the selection and clear the anchor without firing\n * `onChange` (e.g. when all items are removed).\n */\n reset(): void {\n this.#selected = new Set();\n this.#anchor = undefined;\n }\n\n /**\n * Replace the selection and anchor silently (no `onChange`). Used to\n * re-key the selection when items are rebuilt from data — e.g. matching\n * new elements to previously selected keys.\n */\n seed(items: Iterable<T>, anchor?: T): void {\n this.#selected = new Set(items);\n this.#anchor = anchor;\n }\n\n #apply(next: ReadonlySet<T>): void {\n const previous = this.#selected;\n this.#selected = next;\n this.#config.onChange(next, previous);\n }\n}\n"],"mappings":";AA+CA,MAAM,wBAAwB,CAAC,SAAS,KAAK;AAC7C,MAAM,oBAAoB;;AAE1B,MAAM,YAAY;;AAElB,MAAM,mBAAmB;;;;;;;;;;;;;;AAezB,IAAa,2BAAb,MAAuE;CACrE;CACA;CAEA;CAEA;CACA,gBAAgB;CAChB,gBAAgB;CAChB,UAAU;CACV,UAAU;CACV,kBAAkB;CAClB,kBAAkB;CAClB,iBAAiB;EAAE,UAAU;EAAO,SAAS;EAAO,SAAS;CAAM;CACnE,YAAY;CACZ,iBAAiB;;CAEjB,wBAAwB,IAAI,IAAI;;CAEhC,YAA0C;CAC1C,2BAA2B,IAAI,IAAI;CACnC,OAAO;CAEP,YAAY,MAAgB,QAA2C;EACrE,KAAKA,QAAQ;EACb,KAAKC,UAAU;EACf,KAAK,cAAc,IAAI;CACzB;;CAGA,IAAI,aAAsB;EACxB,OAAO,KAAKC,aAAa,KAAKC;CAChC;;CAGA,IAAI,WAAoB;EACtB,OAAO,KAAKD;CACd;CAEA,gBAAsB;EACpB,KAAKE,aAAa;EAClB,OAAO,iBAAiB,eAAe,KAAKC,cAAc;EAC1D,OAAO,iBAAiB,aAAa,KAAKC,YAAY;EACtD,OAAO,iBAAiB,iBAAiB,KAAKC,gBAAgB;CAChE;CAEA,mBAAyB;EACvB,KAAKC,eAAe;EACpB,OAAO,oBAAoB,eAAe,KAAKH,cAAc;EAC7D,OAAO,oBAAoB,aAAa,KAAKC,YAAY;EACzD,OAAO,oBAAoB,iBAAiB,KAAKC,gBAAgB;EACjE,KAAKE,gBAAgB;EACrB,KAAKC,OAAO;CACd;;CAGA,cAAoB;EAElB,KADa,KAAKC,aAAa,KAAK,KAAKX,WAC5B,KAAKY,eAChB,KAAKR,aAAa;CAEtB;CAEA,eAAwC;EACtC,IAAI;GACF,OAAO,KAAKH,QAAQ,WAAW;EACjC,QAAQ;GACN;EACF;CACF;CAEA,eAAqB;EACnB,MAAM,OAAO,KAAKU,aAAa,KAAK,KAAKX;EACzC,IAAI,SAAS,KAAKY,eAChB;EACF,KAAKJ,eAAe;EACpB,KAAKI,gBAAgB;EACrB,KAAK,iBAAiB,eAAe,KAAKC,cAA+B;CAC3E;CAEA,iBAAuB;EACrB,KAAKD,eAAe,oBAAoB,eAAe,KAAKC,cAA+B;EAC3F,KAAKD,gBAAgB,KAAA;CACvB;CAEA,gBAAmC;EACjC,OAAO,KAAKX,QAAQ,gBAAgB;CACtC;CAEA,aAAqB;EACnB,OAAO,KAAKA,QAAQ,aAAa;CACnC;CAEA,kBAAkB,UAA8B;EAC9C,IAAI,MAAM,WAAW,GACnB;EACF,IAAI,CAAC,KAAKa,cAAc,CAAC,CAAC,SAAS,MAAM,WAAW,GAClD;EACF,IAAI,UAAU;EACd,IAAI;GACF,UAAU,KAAKb,QAAQ,UAAU;EACnC,QAAQ;GACN;EACF;EACA,IAAI,CAAC,SACH;EACF,MAAM,SAAS,MAAM;EACrB,IAAI,kBAAkB,WAAW,OAAO,OAAO,YAAY,YACrD;OAAA,OAAO,QAAQ,kDAAkD,GACnE;EAAA;EAEJ,IAAI,KAAKA,QAAQ,aACf,IAAI;GACF,IAAI,CAAC,KAAKA,QAAQ,YAAY,KAAK,GACjC;EACJ,QAAQ;GACN;EACF;EAGF,MAAM,OADU,KAAKU,aAAa,KAAK,KAAKX,MAAAA,CACxB,sBAAsB;EAC1C,KAAKe,aAAa,MAAM;EACxB,KAAKC,gBAAgB,MAAM;EAC3B,KAAKC,gBAAgB,MAAM;EAC3B,KAAKC,kBAAkB,MAAM;EAC7B,KAAKC,kBAAkB,MAAM;EAC7B,KAAKC,UAAU,KAAK,IAAI,KAAK,IAAI,MAAM,UAAU,IAAI,MAAM,CAAC,GAAG,KAAK,IAAI,GAAG,IAAI,KAAK,CAAC;EACrF,KAAKC,UAAU,KAAK,IAAI,KAAK,IAAI,MAAM,UAAU,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,GAAG,IAAI,MAAM,CAAC;EACrF,KAAKnB,YAAY;EACjB,KAAKC,iBAAiB;EACtB,KAAKmB,wBAAQ,IAAI,IAAI;EACrB,KAAKC,YAAY;EACjB,KAAKC,iBAAiB;GAAE,UAAU,MAAM;GAAU,SAAS,MAAM;GAAS,SAAS,MAAM;EAAQ;CAEnG;;CAGA,SAAS,WAAoG;EAC3G,IAAI,UAAU,WAAW,UAAU,SACjC,OAAO;EACT,IAAI,UAAU,UACZ,OAAO;EACT,OAAO;CACT;CAEA,kBAAkB,UAA8B;EAC9C,IAAI,KAAKT,eAAe,KAAA,KAAa,MAAM,cAAc,KAAKA,YAC5D;EACF,KAAKG,kBAAkB,MAAM;EAC7B,KAAKC,kBAAkB,MAAM;EAC7B,KAAKK,iBAAiB;GAAE,UAAU,MAAM;GAAU,SAAS,MAAM;GAAS,SAAS,MAAM;EAAQ;EACjG,MAAM,QAAQ,KAAK,MAAM,MAAM,UAAU,KAAKR,eAAe,MAAM,UAAU,KAAKC,aAAa;EAC/F,IAAI,CAAC,KAAKf,WAAW;GACnB,IAAI,QAAQ,KAAKuB,WAAW,GAC1B;GACF,KAAKvB,YAAY;GACjB,IAAI;IACF,KAAKoB,QAAQ,IAAI,IAAI,KAAKrB,QAAQ,iBAAiB,CAAC;GACtD,QAAQ;IACN,KAAKqB,wBAAQ,IAAI,IAAI;GACvB;GACA,MAAM,UAAU,KAAKX,aAAa,KAAK,KAAKX;GAC5C,IAAI;IACF,IAAI,OAAO,QAAQ,sBAAsB,YACvC,QAAQ,kBAAkB,MAAM,SAAS;GAC7C,QAAQ,CAER;EACF;EACA,IAAI;GACF,MAAM,iBAAiB;EACzB,QAAQ,CAER;EACA,KAAK0B,eAAe;EACpB,KAAKC,gBAAgB;CACvB;CAEA,cAAmC;EACjC,MAAM,UAAU,KAAKhB,aAAa,KAAK,KAAKX;EAC5C,IAAI;GACF,OAAO,QAAQ,sBAAsB;EACvC,QAAQ;GACN;EACF;CACF;CAEA,eAA2C;EACzC,MAAM,MAAM,KAAK4B,YAAY;EAC7B,IAAI,CAAC,KACH,OAAO,KAAA;EACT,MAAM,KAAK,KAAK,IAAI,KAAK,IAAI,KAAKV,kBAAkB,IAAI,MAAM,CAAC,GAAG,KAAK,IAAI,GAAG,IAAI,KAAK,CAAC;EACxF,MAAM,KAAK,KAAK,IAAI,KAAK,IAAI,KAAKC,kBAAkB,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,GAAG,IAAI,MAAM,CAAC;EACxF,OAAO;GACL,GAAG,KAAK,IAAI,KAAKC,SAAS,EAAE;GAC5B,GAAG,KAAK,IAAI,KAAKC,SAAS,EAAE;GAC5B,OAAO,KAAK,IAAI,KAAK,KAAKD,OAAO;GACjC,QAAQ,KAAK,IAAI,KAAK,KAAKC,OAAO;EACpC;CACF;CAEA,iBAAuB;EACrB,MAAM,OAAO,KAAKQ,aAAa;EAC/B,IAAI,CAAC,MACH;EACF,IAAI,MAAmB,CAAC;EACxB,IAAI;GACF,MAAM,KAAK5B,QAAQ,QAAQ,IAAI;EACjC,QAAQ;GACN,MAAM,CAAC;EACT;EACA,MAAM,OAAO,KAAK6B,SAAS,KAAKN,cAAc;EAC9C,KAAKD,YAAY;EACjB,MAAM,SAAS,IAAI,IAAI,GAAG;EAC1B,IAAI;EACJ,IAAI,SAAS,OACX,0BAAU,IAAI,IAAI,CAAC,GAAG,KAAKD,OAAO,GAAG,MAAM,CAAC;OACvC,IAAI,SAAS,UAAU;GAC5B,MAAM,UAAU,IAAI,IAAI,KAAKA,KAAK;GAClC,KAAK,MAAM,QAAQ,QACjB,IAAI,QAAQ,IAAI,IAAI,GAClB,QAAQ,OAAO,IAAI;QAEnB,QAAQ,IAAI,IAAI;GAEpB,UAAU;EACZ,OACE,UAAU;EAEZ,KAAKS,WAAW;EAChB,IAAI;GACF,KAAK9B,QAAQ,UAAU,SAAS,IAAI;EACtC,QAAQ,CAER;CACF;CAEA,gBAAgB,UAA8B;EAC5C,IAAI,KAAKc,eAAe,KAAA,KAAa,MAAM,cAAc,KAAKA,YAC5D;EACF,KAAKS,iBAAiB;GAAE,UAAU,MAAM;GAAU,SAAS,MAAM;GAAS,SAAS,MAAM;EAAQ;EACjG,MAAM,cAAc,KAAKtB;EACzB,MAAM,UAAU,KAAKS,aAAa,KAAK,KAAKX;EAC5C,IAAI;GACF,IAAI,OAAO,QAAQ,sBAAsB,cACpC,OAAO,QAAQ,0BAA0B,cACzC,QAAQ,kBAAkB,MAAM,SAAS,GAC5C,QAAQ,sBAAsB,MAAM,SAAS;EAEjD,QAAQ,CAER;EACA,KAAKS,gBAAgB;EACrB,KAAKM,aAAa,KAAA;EAClB,IAAI,CAAC,aAAa;GAChB,KAAKL,OAAO;GACZ;EACF;EACA,KAAKR,YAAY;EACjB,MAAM,QAAQ,KAAK6B;EACnB,IAAI;GACF,KAAK9B,QAAQ,SAAS,OAAO,EAAE,UAAU,KAAKsB,cAAc,UAAU,CAAC;EACzE,QAAQ,CAER;EACA,IAAI;GACF,KAAKtB,QAAQ,0BAAU,IAAI,IAAI,GAAG,KAAA,CAAS;EAC7C,QAAQ,CAER;EACA,KAAK8B,2BAAW,IAAI,IAAI;EAGxB,KAAK5B,iBAAiB;EACtB,OAAO,iBAAiB;GACtB,KAAKA,iBAAiB;EACxB,GAAG,CAAC;CACN;CAEA,oBAAoB,UAA8B;EAChD,IAAI,KAAKY,eAAe,KAAA,KAAa,MAAM,cAAc,KAAKA,YAC5D;EACF,KAAKN,gBAAgB;EACrB,KAAKM,aAAa,KAAA;EAClB,KAAKb,YAAY;EACjB,KAAK6B,2BAAW,IAAI,IAAI;EACxB,IAAI;GACF,KAAK9B,QAAQ,0BAAU,IAAI,IAAI,GAAG,KAAA,CAAS;EAC7C,QAAQ,CAER;EACA,KAAKE,iBAAiB;CACxB;CAEA,kBAAwB;EACtB,IAAI,CAAC,KAAKF,QAAQ,cAAc,KAAK+B,SAAS,GAC5C;EACF,KAAKA,OAAO,sBAAsB,KAAKC,eAAe;CACxD;CAEA,wBAA8B;EAC5B,KAAKD,OAAO;EACZ,IAAI,CAAC,KAAK9B,aAAa,KAAKa,eAAe,KAAA,GACzC;EACF,MAAM,MAAM,KAAKa,YAAY;EAC7B,IAAI,CAAC,KACH;EACF,MAAM,KAAK,KAAKV,kBAAkB,IAAI;EACtC,MAAM,KAAK,KAAKC,kBAAkB,IAAI;EACtC,IAAI,KAAK;EACT,IAAI,KAAK;EACT,IAAI,KAAK,WACP,KAAK,CAAC,KAAK,KAAK,oBAAoB,IAAI,KAAK,IAAI,GAAG,EAAE,IAAI,UAAU;OACjE,IAAI,KAAK,IAAI,QAAQ,WACxB,KAAK,KAAK,KAAK,oBAAoB,IAAI,KAAK,IAAI,GAAG,IAAI,QAAQ,EAAE,IAAI,UAAU;EACjF,IAAI,KAAK,WACP,KAAK,CAAC,KAAK,KAAK,oBAAoB,IAAI,KAAK,IAAI,GAAG,EAAE,IAAI,UAAU;OACjE,IAAI,KAAK,IAAI,SAAS,WACzB,KAAK,KAAK,KAAK,oBAAoB,IAAI,KAAK,IAAI,GAAG,IAAI,SAAS,EAAE,IAAI,UAAU;EAClF,IAAI,OAAO,KAAK,OAAO,GACrB;EACF,MAAM,aAAa,KAAKlB,QAAQ;EAChC,IAAI,CAAC,YACH;EACF,IAAI;GACF,WAAW,IAAI,EAAE;EACnB,QAAQ,CAER;EAGA,KAAKyB,eAAe;EACpB,KAAKM,OAAO,sBAAsB,KAAKC,eAAe;CACxD;CAEA,kBAAwB;EACtB,IAAI,KAAKD,SAAS,GAAG;GACnB,qBAAqB,KAAKA,IAAI;GAC9B,KAAKA,OAAO;EACd;CACF;CAEA,SAAe;EACb,KAAK9B,YAAY;EACjB,KAAKC,iBAAiB;EACtB,KAAKmB,wBAAQ,IAAI,IAAI;EACrB,KAAKC,YAAY;EACjB,KAAKQ,2BAAW,IAAI,IAAI;CAC1B;AACF;;;;ACvXA,SAAgB,YAAY,MAAoC;CAC9D,OAAO,SAAS,aAAa,SAAS,YAAY,SAAS,YAAY,SAAS;AAClF;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,IAAa,0BAAb,MAAwC;CACtC;CACA,4BAA4B,IAAI,IAAI;CACpC;CAEA,YAAY,QAA0C;EACpD,KAAKG,UAAU;CACjB;;CAGA,IAAI,WAA2B;EAC7B,OAAO,KAAKC;CACd;;;;;;CAOA,IAAI,eAAoC;EACtC,MAAM,SAAS,KAAKD,QAAQ;EAC5B,MAAM,sBAAM,IAAI,IAAY;EAC5B,IAAI,QACF,KAAK,MAAM,QAAQ,KAAKC,WAAW;GACjC,MAAM,MAAM,OAAO,IAAI;GACvB,IAAI,QAAQ,KAAA,GACV,IAAI,IAAI,GAAG;EAEf;EAEF,OAAO;CACT;;CAGA,IAAI,SAAwB;EAC1B,OAAO,KAAKC;CACd;;CAGA,eAAwB;EACtB,OAAO,KAAK,kBAAkB,KAAK,KAAKF,QAAQ,SAAS,CAAC,CAAC,kBAAkB;CAC/E;;CAGA,oBAA6B;EAC3B,MAAM,EAAE,eAAe,oBAAoB,KAAKA,QAAQ,SAAS;EACjE,OAAO,kBAAkB,cAAc,YAAY,eAAe;CACpE;;CAKA,QAAQ,MAAe;EACrB,IAAI,KAAKA,QAAQ,SAAS,CAAC,CAAC,kBAAkB,QAC5C;EACF,IAAI,CAAC,KAAKA,QAAQ,aAAa,IAAI,GACjC;EACF,KAAKE,UAAU;EACf,KAAKC,uBAAO,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;CAC7B;;CAGA,OAAO,MAAe;EACpB,MAAM,EAAE,eAAe,oBAAoB,KAAKH,QAAQ,SAAS;EACjE,IAAI,CAAC,KAAKA,QAAQ,aAAa,IAAI,GACjC;EAEF,IAAI,kBAAkB,UAAU,oBAAoB,aAAa,oBAAoB,UACnF;EAEF,MAAM,OAAO,IAAI,IAAI,KAAKC,SAAS;EACnC,IAAI,KAAK,IAAI,IAAI,GACf,KAAK,OAAO,IAAI;OACX;GACL,IAAI,CAAC,KAAK,kBAAkB,GAC1B,KAAK,MAAM;GACb,KAAK,IAAI,IAAI;EACf;EACA,KAAKE,OAAO,IAAI;CAClB;;CAGA,QAAQ,MAAe;EACrB,IAAI,KAAKH,QAAQ,SAAS,CAAC,CAAC,kBAAkB,QAC5C;EACF,IAAI,KAAKE,YAAY,KAAA,GAAW;GAC9B,KAAK,QAAQ,IAAI;GACjB;EACF;EACA,MAAM,UAAU,KAAKF,QAAQ,gBAAgB;EAC7C,MAAM,YAAY,QAAQ,QAAQ,KAAKE,OAAO;EAC9C,MAAM,YAAY,QAAQ,QAAQ,IAAI;EACtC,IAAI,cAAc,MAAM,cAAc,IAAI;GACxC,KAAK,QAAQ,IAAI;GACjB;EACF;EACA,MAAM,QAAQ,KAAK,IAAI,WAAW,SAAS;EAC3C,MAAM,MAAM,KAAK,IAAI,WAAW,SAAS;EACzC,KAAKC,OAAO,IAAI,IAAI,QAAQ,MAAM,OAAO,MAAM,CAAC,CAAC,CAAC,QAAO,MAAK,KAAKH,QAAQ,aAAa,CAAC,CAAC,CAAC,CAAC;CAC9F;;CAGA,QAAc;EACZ,IAAI,KAAKC,UAAU,SAAS,GAC1B;EACF,KAAKE,uBAAO,IAAI,IAAI,CAAC;CACvB;;CAGA,YAAkB;EAChB,MAAM,UAAU,KAAKH,QAAQ,gBAAgB;EAC7C,IAAI,QAAQ,WAAW,GACrB;EACF,MAAM,OAAO,IAAI,IAAI,QAAQ,QAAO,MAAK,KAAKA,QAAQ,aAAa,CAAC,CAAC,CAAC;EACtE,IAAI,KAAK,SAAS,GAChB;EACF,KAAKG,OAAO,IAAI;CAClB;;;;;CAMA,WAAW,OAA0B;EACnC,MAAM,EAAE,kBAAkB,KAAKH,QAAQ,SAAS;EAChD,IAAI,kBAAkB,YACpB,MAAM,IAAI,MAAM,2DAA2D,cAAc,GAAG;EAE9F,MAAM,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,QAAO,MAAK,KAAKA,QAAQ,aAAa,CAAC,CAAC,CAAC;EACzE,IAAI,KAAK,SAAS,GAChB;EACF,KAAKE,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,EAAE;EAC9B,KAAKC,OAAO,IAAI;CAClB;;;;;;;;CASA,gBAAyB;EACvB,OAAO,KAAKH,QAAQ,SAAS,CAAC,CAAC,kBAAkB,UAAU,KAAKI,gBAAgB;CAClF;;;;;;;;;CAUA,mBAAmB,OAA0B;EAC3C,IAAI,CAAC,KAAK,cAAc,GACtB;EACF,MAAM,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,QAAO,MAAK,KAAKJ,QAAQ,aAAa,CAAC,CAAC,CAAC;EACzE,IAAI,KAAK,SAAS,KAAK,KAAKC,UAAU,SAAS,GAC7C;EACF,KAAKC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,EAAE;EAC9B,KAAKC,OAAO,IAAI;CAClB;;CAGA,SAAS,MAAe;EACtB,IAAI,CAAC,KAAKF,UAAU,IAAI,IAAI,GAC1B;EACF,MAAM,OAAO,IAAI,IAAI,KAAKA,SAAS;EACnC,KAAK,OAAO,IAAI;EAChB,KAAKE,OAAO,IAAI;CAClB;;;;;;CASA,kBAA2B;EACzB,MAAM,EAAE,eAAe,oBAAoB,KAAKH,QAAQ,SAAS;EACjE,OAAO,kBAAkB,cACpB,oBAAoB,aACpB,oBAAoB,YACpB,oBAAoB;CAC3B;;;;;;;CAQA,WAAW,MAAwB,SAA+C;EAChF,IAAI,KAAKA,QAAQ,SAAS,CAAC,CAAC,kBAAkB,QAC5C;EACF,IAAI,uBAAO,IAAI,IAAO;EACtB,KAAK,MAAM,OAAO,IAAI,IAAI,IAAI,GAAG;GAC/B,MAAM,OAAO,QAAQ,GAAG;GACxB,IAAI,SAAS,KAAA,KAAa,KAAKA,QAAQ,aAAa,IAAI,GACtD,KAAK,IAAI,IAAI;EAEjB;EACA,IAAI,CAAC,KAAKI,gBAAgB,KAAK,KAAK,OAAO,GAAG;GAC5C,MAAM,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,EAAE;GAC5B,OAAO,SAAS,KAAA,oBAAY,IAAI,IAAI,oBAAI,IAAI,IAAI,CAAC,IAAI,CAAC;EACxD;EACA,KAAKF,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,EAAE;EAC9B,KAAKC,OAAO,IAAI;CAClB;;;;;CAMA,QAAQ,MAAwB,SAA+C;EAC7E,IAAI,CAAC,KAAKC,gBAAgB,GACxB;EACF,MAAM,OAAO,IAAI,IAAI,KAAKH,SAAS;EACnC,IAAI,UAAU;EACd,KAAK,MAAM,OAAO,IAAI,IAAI,IAAI,GAAG;GAC/B,MAAM,OAAO,QAAQ,GAAG;GACxB,IAAI,SAAS,KAAA,KAAa,CAAC,KAAK,IAAI,IAAI,KAAK,KAAKD,QAAQ,aAAa,IAAI,GAAG;IAC5E,KAAK,IAAI,IAAI;IACb,UAAU;GACZ;EACF;EACA,IAAI,SACF,KAAKG,OAAO,IAAI;CAEpB;;;;;CAMA,WAAW,MAAwB,SAA+C;EAChF,IAAI,KAAKF,UAAU,SAAS,GAC1B;EACF,MAAM,OAAO,IAAI,IAAI,KAAKA,SAAS;EACnC,IAAI,UAAU;EACd,KAAK,MAAM,OAAO,IAAI,IAAI,IAAI,GAAG;GAC/B,MAAM,OAAO,QAAQ,GAAG;GACxB,IAAI,SAAS,KAAA,KAAa,KAAK,OAAO,IAAI,GACxC,UAAU;EAEd;EACA,IAAI,SACF,KAAKE,OAAO,IAAI;CAEpB;;CAKA,YAAY,MAAS,SAAqC;EACxD,MAAM,EAAE,eAAe,iBAAiB,SAAS,KAAKH,QAAQ,SAAS;EACvE,IAAI,SAAS,UACX;EACF,IAAI,kBAAkB,UAAU,SAAS,aAAa,SAAS,UAC7D;EAEF,QAAQ,MAAR;GACE,KAAK;IAEH,KAAK,QAAQ,IAAI;IACjB;GAEF,KAAK;GACL,KAAK;IACH,IAAI,QAAQ,WAAW,QAAQ,SAEzB;SAAA,kBAAkB,cAAc,SAAS,UAC3C,KAAK,OAAO,IAAI;IAAA,OAEb,IAAI,QAAQ,aAAa,kBAAkB,cAAc,SAAS,WAEvE,KAAK,QAAQ,IAAI;SAGjB,KAAK,QAAQ,IAAI;IAEnB;GAEF,KAAK,WAEH;GAEF,KAAK,UAEH,KAAK,OAAO,IAAI;EAEpB;CACF;;CAGA,YAAY,MAAe;EACzB,MAAM,OAAO,KAAKA,QAAQ,SAAS,CAAC,CAAC;EACrC,IAAI,SAAS,UACX;EACF,IAAI,SAAS,YAEX,KAAK,QAAQ,IAAI;OAGjB,KAAK,OAAO,IAAI;CAEpB;;CAKA,cAAoB;EAClB,KAAKE,UAAU,KAAA;CACjB;;;;;;CAOA,MAAM,IAA0B;EAC9B,IAAI,KAAKD,UAAU,SAAS,GAC1B;EACF,KAAKA,YAAY,IAAI,IAAI,CAAC,GAAG,KAAKA,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;CACtD;;;;;CAMA,QAAc;EACZ,KAAKA,4BAAY,IAAI,IAAI;EACzB,KAAKC,UAAU,KAAA;CACjB;;;;;;CAOA,KAAK,OAAoB,QAAkB;EACzC,KAAKD,YAAY,IAAI,IAAI,KAAK;EAC9B,KAAKC,UAAU;CACjB;CAEA,OAAO,MAA4B;EACjC,MAAM,WAAW,KAAKD;EACtB,KAAKA,YAAY;EACjB,KAAKD,QAAQ,SAAS,MAAM,QAAQ;CACtC;AACF"}
|