@ixfx/components 0.11.7 → 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 +153 -2
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +1129 -164
- package/bundle/index.js.map +1 -1
- package/dist/{detail-list-CgkOHYOa.d.ts → detail-list-lE_5UWVl.d.ts} +34 -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 +271 -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/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- 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/vertical-list.js +1 -1
- package/docs-user/README.md +1 -0
- package/docs-user/detail-list.md +28 -0
- package/docs-user/grid-list.md +35 -3
- package/docs-user/index.json +7 -2
- package/docs-user/list-group-header.md +14 -0
- package/docs-user/llms.txt +1 -0
- package/llms.txt +1 -0
- package/package.json +5 -1
- package/dist/detail-list-CgkOHYOa.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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "./icon-CDGprSdE.js";
|
|
2
|
+
import { LitElement } from "lit";
|
|
3
|
+
//#region src/list-group-header/list-group-header.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Collapsible group header used by `ixfx-grid-list` and `ixfx-detail-list`.
|
|
6
|
+
* Look matches the group rows of `ixfx-data-grid`: caret, bold label, muted
|
|
7
|
+
* count, and a hairline divider. The host list owns the click handling.
|
|
8
|
+
*/
|
|
9
|
+
declare class ListGroupHeaderElement extends LitElement {
|
|
10
|
+
static styles: import("lit").CSSResult;
|
|
11
|
+
label: string;
|
|
12
|
+
count: number;
|
|
13
|
+
collapsed: boolean;
|
|
14
|
+
orientation: `horizontal` | `vertical`;
|
|
15
|
+
connectedCallback(): void;
|
|
16
|
+
protected willUpdate(): void;
|
|
17
|
+
protected render(): import("lit").TemplateResult<1>;
|
|
18
|
+
}
|
|
19
|
+
declare global {
|
|
20
|
+
interface HTMLElementTagNameMap {
|
|
21
|
+
'ixfx-list-group-header': ListGroupHeaderElement;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { ListGroupHeaderElement };
|
|
26
|
+
//# sourceMappingURL=list-group-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-group-header.d.ts","names":[],"sources":["../src/list-group-header/list-group-header.ts"],"mappings":";;;;;;;;cAWa,+BAA+B;SAC1B,sBAAM;EAuEtB;EAGA;EAGA;EAGA;EAES;YAKU;YAKA,wBAAM;;QAUnB;YACI;IACR,0BAA0B"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
|
|
2
|
+
import "./icon-1gpwtPx8.js";
|
|
3
|
+
import { LitElement, css, html } from "lit";
|
|
4
|
+
import { property } from "lit/decorators.js";
|
|
5
|
+
//#region src/list-group-header/list-group-header.ts
|
|
6
|
+
let ListGroupHeaderElement = class ListGroupHeaderElement extends LitElement {
|
|
7
|
+
constructor(..._args) {
|
|
8
|
+
super(..._args);
|
|
9
|
+
this.label = ``;
|
|
10
|
+
this.count = 0;
|
|
11
|
+
this.collapsed = false;
|
|
12
|
+
this.orientation = `horizontal`;
|
|
13
|
+
}
|
|
14
|
+
static {
|
|
15
|
+
this.styles = css`
|
|
16
|
+
:host {
|
|
17
|
+
--_height: var(--list-group-header-height, 28px);
|
|
18
|
+
--_border: var(--list-group-header-border, var(--border));
|
|
19
|
+
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: var(--space-s);
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
min-height: var(--_height);
|
|
25
|
+
padding: 0 var(--space-s);
|
|
26
|
+
background: transparent;
|
|
27
|
+
color: var(--list-group-header-text, var(--surface-3-text));
|
|
28
|
+
border-bottom: 1px solid var(--_border);
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
user-select: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.caret {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
transition: transform 0.15s;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
:host([collapsed]) .caret {
|
|
40
|
+
transform: rotate(-90deg);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.count {
|
|
44
|
+
opacity: 0.6;
|
|
45
|
+
font-weight: 400;
|
|
46
|
+
font-size: 0.9em;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.label {
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
text-overflow: ellipsis;
|
|
52
|
+
white-space: nowrap;
|
|
53
|
+
min-width: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.divider {
|
|
57
|
+
flex: 1;
|
|
58
|
+
height: 1px;
|
|
59
|
+
background: var(--_border);
|
|
60
|
+
margin-left: var(--space-s);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Single-row (grid-list \`row\` orientation) layout: a narrow column cell. */
|
|
64
|
+
:host([orientation="vertical"]) {
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
justify-content: flex-start;
|
|
67
|
+
padding: var(--space-s) 0;
|
|
68
|
+
border-bottom: 0;
|
|
69
|
+
border-right: 1px solid var(--_border);
|
|
70
|
+
min-height: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
:host([orientation="vertical"]) .divider {
|
|
74
|
+
width: 1px;
|
|
75
|
+
height: auto;
|
|
76
|
+
margin-left: 0;
|
|
77
|
+
margin-top: var(--space-s);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@media (prefers-reduced-motion: reduce) {
|
|
81
|
+
.caret { transition: none; }
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
connectedCallback() {
|
|
86
|
+
super.connectedCallback();
|
|
87
|
+
this.setAttribute(`role`, `button`);
|
|
88
|
+
}
|
|
89
|
+
willUpdate() {
|
|
90
|
+
this.setAttribute(`aria-expanded`, this.collapsed ? `false` : `true`);
|
|
91
|
+
this.setAttribute(`aria-label`, `${this.label}, ${this.count} ${this.count === 1 ? `item` : `items`}`);
|
|
92
|
+
}
|
|
93
|
+
render() {
|
|
94
|
+
return html`
|
|
95
|
+
<span class="caret"><ixfx-icon name="chevron-down" size="12"></ixfx-icon></span>
|
|
96
|
+
<span class="label" part="label">${this.label}</span>
|
|
97
|
+
<span class="count" part="count">(${this.count})</span>
|
|
98
|
+
<span class="divider" part="divider"></span>
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
__decorate([property({
|
|
103
|
+
type: String,
|
|
104
|
+
reflect: true
|
|
105
|
+
})], ListGroupHeaderElement.prototype, "label", void 0);
|
|
106
|
+
__decorate([property({
|
|
107
|
+
type: Number,
|
|
108
|
+
reflect: true
|
|
109
|
+
})], ListGroupHeaderElement.prototype, "count", void 0);
|
|
110
|
+
__decorate([property({
|
|
111
|
+
type: Boolean,
|
|
112
|
+
reflect: true
|
|
113
|
+
})], ListGroupHeaderElement.prototype, "collapsed", void 0);
|
|
114
|
+
__decorate([property({
|
|
115
|
+
type: String,
|
|
116
|
+
reflect: true
|
|
117
|
+
})], ListGroupHeaderElement.prototype, "orientation", void 0);
|
|
118
|
+
ListGroupHeaderElement = __decorate([safeCustomElement(`ixfx-list-group-header`)], ListGroupHeaderElement);
|
|
119
|
+
//#endregion
|
|
120
|
+
export { ListGroupHeaderElement };
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=list-group-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-group-header.js","names":["customElement"],"sources":["../src/list-group-header/list-group-header.ts"],"sourcesContent":["import { css, html, LitElement } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { safeCustomElement as customElement } from '../util/custom-elements.js';\nimport '../icons/icon.js';\n\n/**\n * Collapsible group header used by `ixfx-grid-list` and `ixfx-detail-list`.\n * Look matches the group rows of `ixfx-data-grid`: caret, bold label, muted\n * count, and a hairline divider. The host list owns the click handling.\n */\n@customElement(`ixfx-list-group-header`)\nexport class ListGroupHeaderElement extends LitElement {\n static override styles = css`\n :host {\n --_height: var(--list-group-header-height, 28px);\n --_border: var(--list-group-header-border, var(--border));\n\n display: flex;\n align-items: center;\n gap: var(--space-s);\n box-sizing: border-box;\n min-height: var(--_height);\n padding: 0 var(--space-s);\n background: transparent;\n color: var(--list-group-header-text, var(--surface-3-text));\n border-bottom: 1px solid var(--_border);\n font-weight: 600;\n cursor: pointer;\n user-select: none;\n }\n\n .caret {\n display: inline-flex;\n transition: transform 0.15s;\n }\n\n :host([collapsed]) .caret {\n transform: rotate(-90deg);\n }\n\n .count {\n opacity: 0.6;\n font-weight: 400;\n font-size: 0.9em;\n }\n\n .label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n }\n\n .divider {\n flex: 1;\n height: 1px;\n background: var(--_border);\n margin-left: var(--space-s);\n }\n\n /* Single-row (grid-list \\`row\\` orientation) layout: a narrow column cell. */\n :host([orientation=\"vertical\"]) {\n flex-direction: column;\n justify-content: flex-start;\n padding: var(--space-s) 0;\n border-bottom: 0;\n border-right: 1px solid var(--_border);\n min-height: 0;\n }\n\n :host([orientation=\"vertical\"]) .divider {\n width: 1px;\n height: auto;\n margin-left: 0;\n margin-top: var(--space-s);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .caret { transition: none; }\n }\n `;\n\n @property({ type: String, reflect: true })\n label = ``;\n\n @property({ type: Number, reflect: true })\n count = 0;\n\n @property({ type: Boolean, reflect: true })\n collapsed = false;\n\n @property({ type: String, reflect: true })\n orientation: `horizontal` | `vertical` = `horizontal`;\n\n override connectedCallback(): void {\n super.connectedCallback();\n this.setAttribute(`role`, `button`);\n }\n\n protected override willUpdate(): void {\n this.setAttribute(`aria-expanded`, this.collapsed ? `false` : `true`);\n this.setAttribute(`aria-label`, `${this.label}, ${this.count} ${this.count === 1 ? `item` : `items`}`);\n }\n\n protected override render() {\n return html`\n <span class=\"caret\"><ixfx-icon name=\"chevron-down\" size=\"12\"></ixfx-icon></span>\n <span class=\"label\" part=\"label\">${this.label}</span>\n <span class=\"count\" part=\"count\">(${this.count})</span>\n <span class=\"divider\" part=\"divider\"></span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ixfx-list-group-header': ListGroupHeaderElement;\n }\n}\n"],"mappings":";;;;;AAWO,IAAM,yBAAN,MAAM,+BAA+B,WAAW;;;EAwE7C,KAAA,QAAA;EAGA,KAAA,QAAA;EAGI,KAAA,YAAA;EAG6B,KAAA,cAAA;;;EAhFhB,KAAA,SAAA,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkF5B,oBAAmC;EACjC,MAAM,kBAAkB;EACxB,KAAK,aAAa,QAAQ,QAAQ;CACpC;CAEA,aAAsC;EACpC,KAAK,aAAa,iBAAiB,KAAK,YAAY,UAAU,MAAM;EACpE,KAAK,aAAa,cAAc,GAAG,KAAK,MAAM,IAAI,KAAK,MAAM,GAAG,KAAK,UAAU,IAAI,SAAS,SAAS;CACvG;CAEA,SAA4B;EAC1B,OAAO,IAAI;;yCAE0B,KAAK,MAAM;0CACV,KAAK,MAAM;;;CAGnD;AACF;AA9BG,WAAA,CAAA,SAAS;CAAE,MAAM;CAAQ,SAAS;AAAK,CAAC,CAAA,GAAA,uBAAA,WAAA,SAAA,KAAA,CAAA;AAGxC,WAAA,CAAA,SAAS;CAAE,MAAM;CAAQ,SAAS;AAAK,CAAC,CAAA,GAAA,uBAAA,WAAA,SAAA,KAAA,CAAA;AAGxC,WAAA,CAAA,SAAS;CAAE,MAAM;CAAS,SAAS;AAAK,CAAC,CAAA,GAAA,uBAAA,WAAA,aAAA,KAAA,CAAA;AAGzC,WAAA,CAAA,SAAS;CAAE,MAAM;CAAQ,SAAS;AAAK,CAAC,CAAA,GAAA,uBAAA,WAAA,eAAA,KAAA,CAAA;AAjF1CA,yBAAAA,WAAAA,CAAAA,kBAAc,wBAAwB,CAAA,GAAA,sBAAA"}
|
package/dist/{list-selection-controller-SA4piSBi.js → list-selection-controller-BeE70lbB.js}
RENAMED
|
@@ -381,6 +381,10 @@ var ListSelectionController = class {
|
|
|
381
381
|
if (next.has(item)) next.delete(item);
|
|
382
382
|
else {
|
|
383
383
|
if (!this.canSelectMultiple()) next.clear();
|
|
384
|
+
if (this.#clamped()) {
|
|
385
|
+
const g = this.#groupOf(item);
|
|
386
|
+
for (const other of [...next]) if (this.#groupOf(other) !== g) next.delete(other);
|
|
387
|
+
}
|
|
384
388
|
next.add(item);
|
|
385
389
|
}
|
|
386
390
|
this.#apply(next);
|
|
@@ -399,6 +403,22 @@ var ListSelectionController = class {
|
|
|
399
403
|
this.replace(item);
|
|
400
404
|
return;
|
|
401
405
|
}
|
|
406
|
+
if (this.#clamped()) {
|
|
407
|
+
const group = this.#groupOf(this.#anchor);
|
|
408
|
+
if (group !== this.#groupOf(item)) {
|
|
409
|
+
this.replace(item);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const inGroup = ordered.filter((t) => this.#groupOf(t) === group);
|
|
413
|
+
const a = inGroup.indexOf(this.#anchor);
|
|
414
|
+
const b = inGroup.indexOf(item);
|
|
415
|
+
if (a === -1 || b === -1) {
|
|
416
|
+
this.replace(item);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
this.#apply(new Set(inGroup.slice(Math.min(a, b), Math.max(a, b) + 1).filter((t) => this.#config.isSelectable(t))));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
402
422
|
const start = Math.min(anchorIdx, targetIdx);
|
|
403
423
|
const end = Math.max(anchorIdx, targetIdx);
|
|
404
424
|
this.#apply(new Set(ordered.slice(start, end + 1).filter((t) => this.#config.isSelectable(t))));
|
|
@@ -410,8 +430,12 @@ var ListSelectionController = class {
|
|
|
410
430
|
}
|
|
411
431
|
/** Select every ordered item that passes `isSelectable`. */
|
|
412
432
|
selectAll() {
|
|
413
|
-
|
|
433
|
+
let ordered = this.#config.getOrderedItems();
|
|
414
434
|
if (ordered.length === 0) return;
|
|
435
|
+
if (this.#clamped()) {
|
|
436
|
+
const group = (this.#anchor === void 0 ? null : this.#groupOf(this.#anchor)) ?? this.#config.getFallbackGroupKey?.() ?? null;
|
|
437
|
+
if (group !== null) ordered = ordered.filter((t) => this.#groupOf(t) === group);
|
|
438
|
+
}
|
|
415
439
|
const next = new Set(ordered.filter((t) => this.#config.isSelectable(t)));
|
|
416
440
|
if (next.size === 0) return;
|
|
417
441
|
this.#apply(next);
|
|
@@ -423,7 +447,7 @@ var ListSelectionController = class {
|
|
|
423
447
|
selectMany(items) {
|
|
424
448
|
const { selectionMode } = this.#config.getModes();
|
|
425
449
|
if (selectionMode !== `multiple`) throw new Error(`selectMany requires selectionMode="multiple" (current: "${selectionMode}")`);
|
|
426
|
-
const next = new Set([...items].filter((t) => this.#config.isSelectable(t)));
|
|
450
|
+
const next = this.#clampToFirstGroup(new Set([...items].filter((t) => this.#config.isSelectable(t))));
|
|
427
451
|
if (next.size === 0) return;
|
|
428
452
|
this.#anchor = [...next].at(-1);
|
|
429
453
|
this.#apply(next);
|
|
@@ -448,7 +472,7 @@ var ListSelectionController = class {
|
|
|
448
472
|
*/
|
|
449
473
|
applyDragSelection(items) {
|
|
450
474
|
if (!this.canDragSelect()) return;
|
|
451
|
-
const next = new Set([...items].filter((t) => this.#config.isSelectable(t)));
|
|
475
|
+
const next = this.#clampToFirstGroup(new Set([...items].filter((t) => this.#config.isSelectable(t))));
|
|
452
476
|
if (next.size === 0 && this.#selected.size === 0) return;
|
|
453
477
|
this.#anchor = [...next].at(-1);
|
|
454
478
|
this.#apply(next);
|
|
@@ -577,6 +601,20 @@ var ListSelectionController = class {
|
|
|
577
601
|
this.#selected = new Set(items);
|
|
578
602
|
this.#anchor = anchor;
|
|
579
603
|
}
|
|
604
|
+
/** Whether gestures must stay inside one group. */
|
|
605
|
+
#clamped() {
|
|
606
|
+
return this.#config.getGroupKey !== void 0 && !(this.#config.crossGroupSelection?.() ?? false);
|
|
607
|
+
}
|
|
608
|
+
#groupOf(item) {
|
|
609
|
+
return this.#config.getGroupKey?.(item) ?? null;
|
|
610
|
+
}
|
|
611
|
+
/** Keep only items in the group of the first item (in display order) when clamped. */
|
|
612
|
+
#clampToFirstGroup(items) {
|
|
613
|
+
if (!this.#clamped() || items.size === 0) return new Set(items);
|
|
614
|
+
const first = this.#config.getOrderedItems().find((t) => items.has(t)) ?? [...items][0];
|
|
615
|
+
const group = this.#groupOf(first);
|
|
616
|
+
return new Set([...items].filter((t) => this.#groupOf(t) === group));
|
|
617
|
+
}
|
|
580
618
|
#apply(next) {
|
|
581
619
|
const previous = this.#selected;
|
|
582
620
|
this.#selected = next;
|
|
@@ -586,4 +624,4 @@ var ListSelectionController = class {
|
|
|
586
624
|
//#endregion
|
|
587
625
|
export { isMultiMode as n, ListDragSelectController as r, ListSelectionController as t };
|
|
588
626
|
|
|
589
|
-
//# sourceMappingURL=list-selection-controller-
|
|
627
|
+
//# sourceMappingURL=list-selection-controller-BeE70lbB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-selection-controller-BeE70lbB.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","#clamped","#groupOf","#clampToFirstGroup","#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 * Optional: the group an item belongs to (`null` when ungrouped or the item\n * cannot be resolved). With this set and `crossGroupSelection` off, gestures\n * are confined to a single group — same rules as `ixfx-data-grid` and\n * `ixfx-grouped-item-lister`. Programmatic key APIs are never clamped.\n */\n readonly getGroupKey?: (item: T) => string | null;\n /** Whether selection may span groups. Read on every gesture; defaults to `false`. */\n readonly crossGroupSelection?: () => boolean;\n /** Group used by `selectAll` when there is no anchor (e.g. the cursor's group). */\n readonly getFallbackGroupKey?: () => string | null;\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 if (this.#clamped()) {\n const g = this.#groupOf(item);\n for (const other of [...next]) {\n if (this.#groupOf(other) !== g)\n next.delete(other);\n }\n }\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 if (this.#clamped()) {\n const group = this.#groupOf(this.#anchor);\n if (group !== this.#groupOf(item)) {\n this.replace(item);\n return;\n }\n const inGroup = ordered.filter(t => this.#groupOf(t) === group);\n const a = inGroup.indexOf(this.#anchor);\n const b = inGroup.indexOf(item);\n if (a === -1 || b === -1) {\n this.replace(item);\n return;\n }\n this.#apply(new Set(inGroup.slice(Math.min(a, b), Math.max(a, b) + 1).filter(t => this.#config.isSelectable(t))));\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 let ordered = this.#config.getOrderedItems();\n if (ordered.length === 0)\n return;\n if (this.#clamped()) {\n const group = (this.#anchor === undefined ? null : this.#groupOf(this.#anchor))\n ?? this.#config.getFallbackGroupKey?.()\n ?? null;\n if (group !== null)\n ordered = ordered.filter(t => this.#groupOf(t) === group);\n }\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 = this.#clampToFirstGroup(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 = this.#clampToFirstGroup(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 // --- Grouping ---\n\n /** Whether gestures must stay inside one group. */\n #clamped(): boolean {\n return this.#config.getGroupKey !== undefined && !(this.#config.crossGroupSelection?.() ?? false);\n }\n\n #groupOf(item: T): string | null {\n return this.#config.getGroupKey?.(item) ?? null;\n }\n\n /** Keep only items in the group of the first item (in display order) when clamped. */\n #clampToFirstGroup(items: ReadonlySet<T>): Set<T> {\n if (!this.#clamped() || items.size === 0)\n return new Set(items);\n const first = this.#config.getOrderedItems().find(t => items.has(t)) ?? [...items][0];\n const group = this.#groupOf(first);\n return new Set([...items].filter(t => this.#groupOf(t) === group));\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":"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"}
|
package/dist/vertical-list.js
CHANGED
|
@@ -3,7 +3,7 @@ import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
|
|
|
3
3
|
import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
|
|
4
4
|
import { c as createElementListSearchAdapter, i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, u as keyForElement } from "./incr-search-D91M4Ozq.js";
|
|
5
5
|
import { t as ListNativeDragController } from "./list-native-drag-controller-B9xO0XWn.js";
|
|
6
|
-
import { r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-
|
|
6
|
+
import { r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-BeE70lbB.js";
|
|
7
7
|
import { n as animateListItemIn, r as animateListItemOut } from "./list-item-animation-BUoEKi5s.js";
|
|
8
8
|
import { t as scrollFadeStyles } from "./scroll-fade-BrNUIYnm.js";
|
|
9
9
|
import { n as appearanceColor, t as appearanceAbsent } from "./item-appearance-BjcMyauw.js";
|
package/docs-user/README.md
CHANGED
|
@@ -28,6 +28,7 @@ This directory contains documentation for each component and some architectural
|
|
|
28
28
|
- [labelled-range-input](./labelled-range-input.md)
|
|
29
29
|
- [labelled-slider-input](./labelled-slider-input.md)
|
|
30
30
|
- [led](./led.md)
|
|
31
|
+
- [list-group-header](./list-group-header.md)
|
|
31
32
|
- [menu](./menu.md)
|
|
32
33
|
- [miller](./miller.md)
|
|
33
34
|
- [multicol-list](./multicol-list.md)
|
package/docs-user/detail-list.md
CHANGED
|
@@ -29,6 +29,7 @@ Selection semantics match `ixfx-vertical-list` — same modes, same `list-*` eve
|
|
|
29
29
|
- [Wrapping](#wrapping)
|
|
30
30
|
- [Incremental search](#incremental-search)
|
|
31
31
|
- [Highlight styling](#highlight-styling)
|
|
32
|
+
- [Grouping](#grouping)
|
|
32
33
|
- [Filter predicate](#filter-predicate)
|
|
33
34
|
- [Sorting](#sorting)
|
|
34
35
|
- [Simple sort](#simple-sort)
|
|
@@ -462,6 +463,33 @@ Matched characters are highlighted with the `detail-list-search` highlight key.
|
|
|
462
463
|
|
|
463
464
|
---
|
|
464
465
|
|
|
466
|
+
## Grouping
|
|
467
|
+
|
|
468
|
+
Group rows under collapsible headers, following `ixfx-data-grid` (see its [Grouping](../data-grid/README.md#grouping) section).
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
list.groupBy = 'kind'; // object items: property · declarative <li>: data-kind
|
|
472
|
+
list.groupBy = (data, li) => li.textContent![0]; // or a function of (data, element)
|
|
473
|
+
list.groupLabel = (key, items) => `${key} (${items.length})`;
|
|
474
|
+
list.collapseAllGroups();
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
| Member | Notes |
|
|
478
|
+
|---|---|
|
|
479
|
+
| `groupBy` / `group-by` | Property name (object items), else `data-<name>` attribute, or a function `(data, li) => string`. `null` ungroups and restores the original order. |
|
|
480
|
+
| `groupLabel` | `(groupKey, items) => string`. Default: the key, or `(none)`. |
|
|
481
|
+
| `cross-group-selection` | Default `false`: selecting in one group clears the others, and Shift-range, Ctrl+A and marquee stay in one group. Programmatic key APIs are never clamped. |
|
|
482
|
+
| `groups` | Read-only `{ key, label, count, collapsed }[]`. |
|
|
483
|
+
| `toggleGroup(key)`, `setGroupCollapsed(key, bool)`, `collapseAllGroups()`, `expandAllGroups()` | Collapse state. |
|
|
484
|
+
|
|
485
|
+
- **Layout:** headers are ordinary rows in the column flow — a header, then its items, then the next group's header, wrapping into the next column like any row. Groups do not start new columns, so a header can land at the bottom of a column.
|
|
486
|
+
- Order: original order → `sort` → partition. Groups appear in first-encounter order of the sorted, filtered list. Rows added later join their group; a row added out of order counts as last in the original order.
|
|
487
|
+
- Filtering (`filterPredicate`, filter-mode search) runs first; an emptied group loses its header.
|
|
488
|
+
- **Collapsed groups** hide their rows (`data-group-collapsed`); they leave navigation, select-all, range, marquee and drag-out but keep their selection.
|
|
489
|
+
- **Keyboard:** Up/Down skip headers, even mid-column. Left/Right pick the row in the neighbouring column nearest the current one vertically, since headers shift rows. Headers are never focused; collapse is by click or the API.
|
|
490
|
+
- Headers are `<ixfx-list-group-header data-ixfx-group-header>` children managed by the list — do not add or remove them yourself. Style through `--detail-list-group-header-height`, `--list-group-header-text` and `--list-group-header-border`.
|
|
491
|
+
- Events: `list-group-toggle` `{ groupKey, collapsed }`, `list-group-change` `{ groupBy }`.
|
|
492
|
+
|
|
465
493
|
## Filter predicate
|
|
466
494
|
|
|
467
495
|
Use `filterPredicate` to programmatically hide items:
|
package/docs-user/grid-list.md
CHANGED
|
@@ -19,9 +19,10 @@ single vertical column, or a horizontally-scrolling strip.
|
|
|
19
19
|
8. [Events](#events)
|
|
20
20
|
9. [Native drag-out](#native-drag-out)
|
|
21
21
|
10. [Keyboard](#keyboard)
|
|
22
|
-
11. [
|
|
23
|
-
12. [
|
|
24
|
-
13. [
|
|
22
|
+
11. [Grouping](#grouping)
|
|
23
|
+
12. [Virtualisation](#virtualisation)
|
|
24
|
+
13. [CSS parts](#css-parts)
|
|
25
|
+
14. [Choosing a list component](#choosing-a-list-component)
|
|
25
26
|
|
|
26
27
|
---
|
|
27
28
|
|
|
@@ -415,6 +416,37 @@ operations.
|
|
|
415
416
|
|
|
416
417
|
---
|
|
417
418
|
|
|
419
|
+
## Grouping
|
|
420
|
+
|
|
421
|
+
Group items under collapsible headers, following `ixfx-data-grid` (see its [Grouping](../data-grid/README.md#grouping) section).
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
grid.groupBy = 'kind'; // data mode: item property · element mode: data-kind attribute
|
|
425
|
+
grid.groupBy = item => item.locked ? 'Locked' : 'Open'; // or a function (Element in element mode)
|
|
426
|
+
grid.groupLabel = (key, items) => `${key} (${items.length})`;
|
|
427
|
+
grid.crossGroupSelection = false; // default
|
|
428
|
+
grid.collapseAllGroups();
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
| Member | Notes |
|
|
432
|
+
|---|---|
|
|
433
|
+
| `groupBy` / `group-by` | `string` property (data mode) or `data-<name>` attribute (element mode), or a function. `null` ungroups. |
|
|
434
|
+
| `groupLabel` | `(groupKey, items) => string`. Default: the key, or `(none)` for the empty key. |
|
|
435
|
+
| `cross-group-selection` | Default `false`: selecting in one group clears the others, and Shift-range, Ctrl+A and marquee stay inside one group. Programmatic `selectKeys` / `addSelectedKeys` are never clamped. |
|
|
436
|
+
| `group-header-height` / `group-header-width` | Header size in px (`grid` / `column` height, `row` width). Defaults 28 / 120. |
|
|
437
|
+
| `groups` | Read-only `{ key, label, count, collapsed }[]` in display order. |
|
|
438
|
+
| `toggleGroup(key)`, `setGroupCollapsed(key, bool)`, `collapseAllGroups()`, `expandAllGroups()` | Collapse state. |
|
|
439
|
+
|
|
440
|
+
- Groups appear in first-encounter order. Filters (`filterPredicate`, `itemFilterPredicate`, incremental filter) run first, so an emptied group loses its header and counts follow the filter.
|
|
441
|
+
- **Collapsed groups** leave the effective order: not navigable, not rendered, not in select-all, range, marquee or drag-out. Their selection state is kept. A collapsed key is not auto-expanded by `focusKey()`.
|
|
442
|
+
- Changing `groupBy` (from any path) clears the collapse state.
|
|
443
|
+
- **Layout:** in `grid` / `column` a header spans the full width and its items restart at column 0 beneath it. In `row` orientation the header is an inline cell of `group-header-width`.
|
|
444
|
+
- **Keyboard:** Left/Right step through items, crossing groups. Up/Down keep the column, hop across headers and clamp on a short last row. Headers are never focused; collapsing is by click or the API.
|
|
445
|
+
- Events: `list-group-toggle` `{ groupKey, collapsed }`, `list-group-change` `{ groupBy }`.
|
|
446
|
+
- Headers are `ixfx-list-group-header` elements (`part="group-header"`), themeable through `--list-group-header-text` / `--list-group-header-border`.
|
|
447
|
+
|
|
448
|
+
Differences from data-grid: group state is reset on every `groupBy` change, the empty key is labelled `(none)`, headers are skipped by arrow keys, and the collapse API and events are exposed.
|
|
449
|
+
|
|
418
450
|
## Virtualisation
|
|
419
451
|
|
|
420
452
|
Only the cells in (or near) the viewport are in the DOM — in both element and
|
package/docs-user/index.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixfx/components",
|
|
3
|
-
"version": "0.11.
|
|
4
|
-
"generated": "2026-09-
|
|
3
|
+
"version": "0.11.8",
|
|
4
|
+
"generated": "2026-09-30T06:11:13.150Z",
|
|
5
5
|
"components": [
|
|
6
6
|
{
|
|
7
7
|
"name": "ac-text",
|
|
@@ -123,6 +123,11 @@
|
|
|
123
123
|
"summary": "An LED indicator component with color mixing and brightness, blink, and pulse modes.",
|
|
124
124
|
"path": "docs-user/led.md"
|
|
125
125
|
},
|
|
126
|
+
{
|
|
127
|
+
"name": "list-group-header",
|
|
128
|
+
"summary": "Collapsible group header shared by `ixfx-grid-list` and `ixfx-detail-list`. It is a building block: the lists create and position it themselves, and handle its clicks. Its look matches the group rows ",
|
|
129
|
+
"path": "docs-user/list-group-header.md"
|
|
130
|
+
},
|
|
126
131
|
{
|
|
127
132
|
"name": "menu",
|
|
128
133
|
"summary": "A custom menu system built on native HTML popovers with keyboard navigation, tickled focus management, and command routing.",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# `ixfx-list-group-header`
|
|
2
|
+
|
|
3
|
+
Collapsible group header shared by `ixfx-grid-list` and `ixfx-detail-list`. It is a building block: the lists create and position it themselves, and handle its clicks. Its look matches the group rows of `ixfx-data-grid`.
|
|
4
|
+
|
|
5
|
+
| Attribute | Type | Default | Notes |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| `label` | string | `''` | Header text; reflected |
|
|
8
|
+
| `count` | number | `0` | Shown as `(n)` |
|
|
9
|
+
| `collapsed` | boolean | `false` | Rotates the caret; reflected |
|
|
10
|
+
| `orientation` | `horizontal` \| `vertical` | `horizontal` | `vertical` is a narrow column cell for single-row layouts |
|
|
11
|
+
|
|
12
|
+
CSS variables: `--list-group-header-height` (28px), `--list-group-header-text`, `--list-group-header-border`.
|
|
13
|
+
|
|
14
|
+
Accessibility: `role="button"`, `aria-expanded`, and an `aria-label` of `"<label>, <n> items"`. It is not focusable; the host list owns focus.
|
package/docs-user/llms.txt
CHANGED
|
@@ -35,6 +35,7 @@ import '@ixfx/components/data-grid';
|
|
|
35
35
|
- [labelled-range-input](docs-user/labelled-range-input.md): A composite range input that pairs an editable label (`ixfx-editable-number`) with a slider (`ixfx-range-input`). The two controls are always in sync — dragging the slider updates the number, and edit
|
|
36
36
|
- [labelled-slider-input](docs-user/labelled-slider-input.md): A composite slider input that pairs an editable label (`ixfx-editable-number`) with a slider (`ixfx-slider-input`). The two controls are always in sync — dragging the slider updates the number, and ed
|
|
37
37
|
- [led](docs-user/led.md): An LED indicator component with color mixing and brightness, blink, and pulse modes.
|
|
38
|
+
- [list-group-header](docs-user/list-group-header.md): Collapsible group header shared by `ixfx-grid-list` and `ixfx-detail-list`. It is a building block: the lists create and position it themselves, and handle its clicks. Its look matches the group rows
|
|
38
39
|
- [menu](docs-user/menu.md): A custom menu system built on native HTML popovers with keyboard navigation, tickled focus management, and command routing.
|
|
39
40
|
- [miller](docs-user/miller.md): A Miller column component displays hierarchical data as multiple vertical columns, like macOS Finder or email clients. Each column shows the children of the selected item in the previous column.
|
|
40
41
|
- [multicol-list](docs-user/multicol-list.md): A faceted-filtering component: each column shows one property (facet) of the *same*
|
package/llms.txt
CHANGED
|
@@ -35,6 +35,7 @@ import '@ixfx/components/data-grid';
|
|
|
35
35
|
- [labelled-range-input](docs-user/labelled-range-input.md): A composite range input that pairs an editable label (`ixfx-editable-number`) with a slider (`ixfx-range-input`). The two controls are always in sync — dragging the slider updates the number, and edit
|
|
36
36
|
- [labelled-slider-input](docs-user/labelled-slider-input.md): A composite slider input that pairs an editable label (`ixfx-editable-number`) with a slider (`ixfx-slider-input`). The two controls are always in sync — dragging the slider updates the number, and ed
|
|
37
37
|
- [led](docs-user/led.md): An LED indicator component with color mixing and brightness, blink, and pulse modes.
|
|
38
|
+
- [list-group-header](docs-user/list-group-header.md): Collapsible group header shared by `ixfx-grid-list` and `ixfx-detail-list`. It is a building block: the lists create and position it themselves, and handle its clicks. Its look matches the group rows
|
|
38
39
|
- [menu](docs-user/menu.md): A custom menu system built on native HTML popovers with keyboard navigation, tickled focus management, and command routing.
|
|
39
40
|
- [miller](docs-user/miller.md): A Miller column component displays hierarchical data as multiple vertical columns, like macOS Finder or email clients. Each column shows the children of the selected item in the previous column.
|
|
40
41
|
- [multicol-list](docs-user/multicol-list.md): A faceted-filtering component: each column shows one property (facet) of the *same*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ixfx/components",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.8",
|
|
5
5
|
"packageManager": "pnpm@10.0.0",
|
|
6
6
|
"description": "Web components for ixfx — Lit-based UI toolkit",
|
|
7
7
|
"author": "Clint Heyer",
|
|
@@ -168,6 +168,10 @@
|
|
|
168
168
|
"types": "./dist/grid-list.d.ts",
|
|
169
169
|
"import": "./dist/grid-list.js"
|
|
170
170
|
},
|
|
171
|
+
"./list-group-header": {
|
|
172
|
+
"types": "./dist/list-group-header.d.ts",
|
|
173
|
+
"import": "./dist/list-group-header.js"
|
|
174
|
+
},
|
|
171
175
|
"./form": {
|
|
172
176
|
"types": "./dist/form.d.ts",
|
|
173
177
|
"import": "./dist/form.js"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"detail-list-CgkOHYOa.d.ts","names":[],"sources":["../src/detail-list/types.ts","../src/detail-list/detail-list.ts"],"mappings":";;;;;;;;;;;KACY;;KAGA;WACD,SAAS;WACT;WACA;;;KAIC,wBAAwB,GAAG,gBAAgB,GAAG;;KAG9C,mBAAmB,eAAe,2BAA2B;;;;;;;;;;;;;;;;;;;;cCgI5D,0BAA0B;;EAErC,eAAe;EAGf,iBAAiB;EAGjB,MAAM;EAGN;EAGA,gBAAgB;EAGhB;EAGA,UAAU;EAGV;EAGA,WAAW;EAGX;;;;;EAOA;;;;;EAOA;;;;;;EAQA;EAGA;EAGA,mBAAmB,IAAI;;;;;;EAQvB;;EAIA,oBAAoB,gCAAgC;;;;;;EAQpD;;;;;;EAQA;;;;;EAOA,qBAAqB,wBAAwB;EAG7C,mBAAmB,8BAA8B;UAGzC;mBAoBS;UAeT;UAyFA;;WAkGC,QAAM,qBAAA;;MAYX,YAAY;EAoBP;EAgEA;EA6BA;EASA,QAAQ,SAAS;;MAuDtB,gBAAgB;;MAKhB,iBAAiB,YAAY;;;;;;EASjC;EAIA,QAAQ,MAAM,kBAAkB,UAAU;;;;;EAuB1C,WAAW,MAAM,UAAU;;EAW3B,YAAY,OAAO,SAAS,WAAW;EAIvC;;;;;EAYA,SAAS,gBAAgB;;;;;EAgBzB,YAAY,MAAM;;;;;;;;;;EAalB;;;;;;EAsBA,SAAS,WAAW,WAAW,OAAO;EAItC,OAAO,MAAM;EAIb,WAAW,OAAO,SAAS;EAI3B,SAAS,MAAM;EAIf;EAIA;;MAQI,gBAAgB;;MAKhB;;EAMJ,WAAW,MAAM;;EAKjB,gBAAgB,MAAM;;EAKtB,mBAAmB,MAAM;;EAKzB,SAAS;EAgBP,eAAe,0CAA0C,UAAU;UA0b7D;;;;;;;;;EAkBF,kBAAkB;UAwBhB;;UAOA;UA2DA;UAiBA;;;;;;;UA2DA;;UAoBA;UAcA;UAYA;UAQA;UA6DA;UAMA;;UAKA;UAIA;UA+EA;UAWA;UAMA;UAUA;SAUQ,sBAAM;EA2Jb,wBAAM;;QAsBT;YACI;IACR,oBAAoB;;YAGZ,4BAA4B;;UAGvB,kCAAkC;EACjD,+BAA+B;EAC/B,0BAA0B,yBAAyB"}
|