@ixfx/components 0.7.3 → 0.7.5
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 +1355 -526
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +3277 -384
- package/bundle/index.js.map +1 -1
- package/dist/ac-text.d.ts +1 -1
- package/dist/ac-text.js +1 -1
- package/dist/{ac-token-TCLAZeVg.d.ts → ac-token--_vCHXWw.d.ts} +2 -2
- package/dist/{ac-token-TCLAZeVg.d.ts.map → ac-token--_vCHXWw.d.ts.map} +1 -1
- package/dist/ac-token.d.ts +1 -1
- package/dist/ac-token.js +2 -2
- package/dist/button.d.ts +1 -1
- package/dist/button.d.ts.map +1 -1
- package/dist/colour-picker.d.ts +1 -1
- package/dist/crumbs.d.ts +6 -2
- package/dist/crumbs.d.ts.map +1 -1
- package/dist/crumbs.js +13 -3
- package/dist/crumbs.js.map +1 -1
- package/dist/{data-grid-Db4QpnII.d.ts → data-grid-CtJ35v8L.d.ts} +59 -2
- package/dist/data-grid-CtJ35v8L.d.ts.map +1 -0
- package/dist/data-grid.d.ts +1 -1
- package/dist/data-grid.js +268 -14
- package/dist/data-grid.js.map +1 -1
- package/dist/detail-list-BS5dRDda.d.ts +175 -0
- package/dist/detail-list-BS5dRDda.d.ts.map +1 -0
- package/dist/detail-list.d.ts +3 -0
- package/dist/detail-list.js +1031 -0
- package/dist/detail-list.js.map +1 -0
- package/dist/grid-list-D-GbghSH.d.ts +321 -0
- package/dist/grid-list-D-GbghSH.d.ts.map +1 -0
- package/dist/grid-list.d.ts +3 -0
- package/dist/grid-list.js +994 -0
- package/dist/grid-list.js.map +1 -0
- package/dist/{grouped-item-lister-BWlC9S4e.d.ts → grouped-item-lister-fdZHVxFM.d.ts} +3 -3
- package/dist/{grouped-item-lister-BWlC9S4e.d.ts.map → grouped-item-lister-fdZHVxFM.d.ts.map} +1 -1
- package/dist/grouped-item-lister.d.ts +1 -1
- package/dist/{icon-DR_3rSyZ.d.ts → icon-CDGprSdE.d.ts} +2 -2
- package/dist/icon-CDGprSdE.d.ts.map +1 -0
- package/dist/icons.d.ts +2 -2
- package/dist/incr-search-DpbSJtgj.js +962 -0
- package/dist/incr-search-DpbSJtgj.js.map +1 -0
- package/dist/incr-search.d.ts +2 -2
- package/dist/incr-search.js +2 -3
- package/dist/{index-C59-JEPl.d.ts → index-9AhaHIT2.d.ts} +3 -3
- package/dist/{index-C59-JEPl.d.ts.map → index-9AhaHIT2.d.ts.map} +1 -1
- package/dist/index-CBS4z1v-.d.ts +365 -0
- package/dist/index-CBS4z1v-.d.ts.map +1 -0
- package/dist/{index-DsjpDksO.d.ts → index-CS9yM8L-.d.ts} +2 -2
- package/dist/{index-DsjpDksO.d.ts.map → index-CS9yM8L-.d.ts.map} +1 -1
- package/dist/{index-BUB5SICW.d.ts → index-CoqtMSd9.d.ts} +2 -2
- package/dist/{index-BUB5SICW.d.ts.map → index-CoqtMSd9.d.ts.map} +1 -1
- package/dist/{index-CJ5fshD3.d.ts → index-UvnoZUAo.d.ts} +2 -2
- package/dist/{index-CJ5fshD3.d.ts.map → index-UvnoZUAo.d.ts.map} +1 -1
- package/dist/{index-BOUaDIW-.d.ts → index-tq6f8007.d.ts} +2 -2
- package/dist/{index-BOUaDIW-.d.ts.map → index-tq6f8007.d.ts.map} +1 -1
- package/dist/index.d.ts +188 -243
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +376 -1004
- package/dist/index.js.map +1 -1
- package/dist/interaction-Cr13azbM.js +3 -0
- package/dist/{labelled-input-base-Dp_9KP0G.d.ts → labelled-input-base-DiTme1JO.d.ts} +2 -2
- package/dist/{labelled-input-base-Dp_9KP0G.d.ts.map → labelled-input-base-DiTme1JO.d.ts.map} +1 -1
- package/dist/labelled-radial-input.d.ts +2 -2
- package/dist/labelled-radial-input.d.ts.map +1 -1
- package/dist/labelled-range-input.d.ts +1 -1
- package/dist/labelled-slider-input.d.ts +1 -1
- package/dist/list-item-animation-DuiwgN3l.js +112 -0
- package/dist/list-item-animation-DuiwgN3l.js.map +1 -0
- package/dist/list-selection-controller-DTFI40li.js +264 -0
- package/dist/list-selection-controller-DTFI40li.js.map +1 -0
- package/dist/{list-selection-types-DSuRNWpx.d.ts → list-selection-types-B5ioL-dG.d.ts} +6 -1
- package/dist/list-selection-types-B5ioL-dG.d.ts.map +1 -0
- package/dist/{menu-DMzjkVnK.js → menu-Bg90Qr0B.js} +3 -3
- package/dist/{menu-DMzjkVnK.js.map → menu-Bg90Qr0B.js.map} +1 -1
- package/dist/{menu-item-D2S4i8u1.js → menu-item-Bx-Fs7jq.js} +3 -3
- package/dist/{menu-item-D2S4i8u1.js.map → menu-item-Bx-Fs7jq.js.map} +1 -1
- package/dist/{menu-item-ZmkXNjek.d.ts → menu-item-CZi9vy5O.d.ts} +2 -2
- package/dist/{menu-item-ZmkXNjek.d.ts.map → menu-item-CZi9vy5O.d.ts.map} +1 -1
- package/dist/menu.d.ts +2 -2
- package/dist/menu.js +2 -2
- package/dist/miller.d.ts +82 -4
- package/dist/miller.d.ts.map +1 -1
- package/dist/miller.js +329 -70
- package/dist/miller.js.map +1 -1
- package/dist/narrowed-text.js +3 -4
- package/dist/narrowed-text.js.map +1 -1
- package/dist/panel.d.ts +1 -1
- package/dist/plots.js +1 -1
- package/dist/select-horiz.js +2 -2
- package/dist/{split-layout-C5SYbDrq.d.ts → split-layout-JK3xWVX2.d.ts} +2 -2
- package/dist/{split-layout-C5SYbDrq.d.ts.map → split-layout-JK3xWVX2.d.ts.map} +1 -1
- package/dist/split-layout.d.ts +1 -1
- package/dist/{tab-list-DbMIL520.d.ts → tab-list-CGyPt07Z.d.ts} +2 -2
- package/dist/{tab-list-DbMIL520.d.ts.map → tab-list-CGyPt07Z.d.ts.map} +1 -1
- package/dist/tabs.d.ts +1 -1
- package/dist/{tickled-controller-Ds1DMyG7.d.ts → tickled-controller-Bn3YdQsM.d.ts} +12 -1
- package/dist/{tickled-controller-Ds1DMyG7.d.ts.map → tickled-controller-Bn3YdQsM.d.ts.map} +1 -1
- package/dist/{tickled-controller-h9GmJ_bU.js → tickled-controller-C66DgJHl.js} +16 -1
- package/dist/tickled-controller-C66DgJHl.js.map +1 -0
- package/dist/{titlebar-B23MrIbI.d.ts → titlebar-Bj5pfdw_.d.ts} +2 -2
- package/dist/{titlebar-B23MrIbI.d.ts.map → titlebar-Bj5pfdw_.d.ts.map} +1 -1
- package/dist/titlebar.d.ts +1 -1
- package/dist/{tree-Ch4W8qXQ.js → tree-DAii2A1g.js} +190 -29
- package/dist/tree-DAii2A1g.js.map +1 -0
- package/dist/{tree-component-BvV4muxx.d.ts → tree-component-F6qZ0Gdv.d.ts} +9 -1
- package/dist/{tree-component-BvV4muxx.d.ts.map → tree-component-F6qZ0Gdv.d.ts.map} +1 -1
- package/dist/tree.d.ts +28 -4
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +1 -1
- package/dist/vertical-list-DwtCPy1b.d.ts +143 -0
- package/dist/vertical-list-DwtCPy1b.d.ts.map +1 -0
- package/dist/vertical-list.d.ts +3 -121
- package/dist/vertical-list.js +642 -1
- package/dist/vertical-list.js.map +1 -0
- package/dist/{xy-axis-DWizs0bt.js → xy-axis-VrX8dmvS.js} +2 -2
- package/dist/{xy-axis-DWizs0bt.js.map → xy-axis-VrX8dmvS.js.map} +1 -1
- package/docs-user/README.md +1 -0
- package/docs-user/data-grid.md +70 -2
- package/docs-user/detail-list.md +39 -1
- package/docs-user/grid-list.md +298 -0
- package/docs-user/incr-search.md +12 -2
- package/docs-user/index.json +7 -2
- package/docs-user/llms.txt +1 -0
- package/docs-user/tree.md +35 -1
- package/docs-user/vertical-list.md +58 -2
- package/llms.txt +1 -0
- package/package.json +17 -2
- package/dist/data-grid-Db4QpnII.d.ts.map +0 -1
- package/dist/element-search-Y8SRfPGM.js +0 -185
- package/dist/element-search-Y8SRfPGM.js.map +0 -1
- package/dist/icon-DR_3rSyZ.d.ts.map +0 -1
- package/dist/incr-search-vCzuao-2.js +0 -137
- package/dist/incr-search-vCzuao-2.js.map +0 -1
- package/dist/index-Cu5i8zZY.d.ts +0 -160
- package/dist/index-Cu5i8zZY.d.ts.map +0 -1
- package/dist/interaction-D5XdKzbR.js +0 -2
- package/dist/list-selection-types-DSuRNWpx.d.ts.map +0 -1
- package/dist/tickled-controller-h9GmJ_bU.js.map +0 -1
- package/dist/tree-Ch4W8qXQ.js.map +0 -1
- package/dist/vertical-list-B4w_ksJk.js +0 -814
- package/dist/vertical-list-B4w_ksJk.js.map +0 -1
- package/dist/vertical-list.d.ts.map +0 -1
package/dist/vertical-list.js
CHANGED
|
@@ -1,2 +1,643 @@
|
|
|
1
|
-
import { t as
|
|
1
|
+
import { t as TickledController } from "./tickled-controller-C66DgJHl.js";
|
|
2
|
+
import { t as themeFallbacks } from "./fallbacks-DNI9xt0F.js";
|
|
3
|
+
import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
|
|
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-DpbSJtgj.js";
|
|
5
|
+
import { n as animateListItemIn, r as animateListItemOut } from "./list-item-animation-DuiwgN3l.js";
|
|
6
|
+
import { t as ListSelectionController } from "./list-selection-controller-DTFI40li.js";
|
|
7
|
+
import { LitElement, css, html, render } from "lit";
|
|
8
|
+
import { property } from "lit/decorators.js";
|
|
9
|
+
import { createCommandRegistry } from "@clinth/ui-commands";
|
|
10
|
+
//#region src/vertical-list/vertical-list.ts
|
|
11
|
+
const ITEM_SELECTOR = `li, [data-list-item]`;
|
|
12
|
+
let VerticalListElement = class VerticalListElement extends LitElement {
|
|
13
|
+
constructor(..._args) {
|
|
14
|
+
super(..._args);
|
|
15
|
+
this.selectionMode = `single`;
|
|
16
|
+
this.interactionMode = `standard`;
|
|
17
|
+
this.itemWrap = `stop`;
|
|
18
|
+
this.#customWrappers = /* @__PURE__ */ new Map();
|
|
19
|
+
this.#wrappedElements = /* @__PURE__ */ new Set();
|
|
20
|
+
this.#animatedItems = /* @__PURE__ */ new WeakSet();
|
|
21
|
+
this.selection = new ListSelectionController({
|
|
22
|
+
getOrderedItems: () => this._getVisibleItems(),
|
|
23
|
+
isSelectable: (el) => this._isCheckable(el),
|
|
24
|
+
getModes: () => ({
|
|
25
|
+
selectionMode: this.selectionMode,
|
|
26
|
+
interactionMode: this.interactionMode
|
|
27
|
+
}),
|
|
28
|
+
getKey: keyForElement,
|
|
29
|
+
onChange: (selected, previous) => {
|
|
30
|
+
this._syncItemStates();
|
|
31
|
+
this._dispatch(`list-select`, {
|
|
32
|
+
selected,
|
|
33
|
+
previous,
|
|
34
|
+
selectedKeys: this.selection.selectedKeys
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
this.tickled = new TickledController(this, {
|
|
39
|
+
getItems: () => this._getVisibleItems().map((el) => ({
|
|
40
|
+
id: el,
|
|
41
|
+
enabled: !el.hasAttribute(`not-checked`)
|
|
42
|
+
})),
|
|
43
|
+
onTickle: (id) => {
|
|
44
|
+
if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
|
|
45
|
+
},
|
|
46
|
+
onStateChange: () => this._syncItemStates(),
|
|
47
|
+
itemWrap: this.itemWrap
|
|
48
|
+
});
|
|
49
|
+
this.search = new ListSearchController(this, { adapter: createElementListSearchAdapter(this, {
|
|
50
|
+
itemSelector: ITEM_SELECTOR,
|
|
51
|
+
getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
52
|
+
highlightKey: `vertical-list-search`
|
|
53
|
+
}) });
|
|
54
|
+
this._onPointerOver = (e) => {
|
|
55
|
+
const item = this._findListItem(e.composedPath());
|
|
56
|
+
if (item) this.tickled.handlePointerEnter(item);
|
|
57
|
+
};
|
|
58
|
+
this._onPointerLeave = (e) => {
|
|
59
|
+
this.tickled.handlePointerLeave(e);
|
|
60
|
+
};
|
|
61
|
+
this._onKeyDown = (e) => {
|
|
62
|
+
if (this.search.isOpen) return;
|
|
63
|
+
if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
|
|
64
|
+
if (this.selection.canSelectAll()) {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
this.selection.selectAll();
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
switch (e.key) {
|
|
71
|
+
case `ArrowDown`:
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
this.tickled.handleKeyboardNavigation(`next`);
|
|
74
|
+
if (e.shiftKey && this.interactionMode === `vscode`) {
|
|
75
|
+
const cur = this.tickled.tickledId;
|
|
76
|
+
if (cur) this.selection.rangeTo(cur);
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
case `ArrowUp`:
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
this.tickled.handleKeyboardNavigation(`previous`);
|
|
82
|
+
if (e.shiftKey && this.interactionMode === `vscode`) {
|
|
83
|
+
const cur = this.tickled.tickledId;
|
|
84
|
+
if (cur) this.selection.rangeTo(cur);
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
case `Enter`: {
|
|
88
|
+
const id = this.tickled.tickledId;
|
|
89
|
+
if (id) {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
this._dispatch(`list-item-click`, { item: id });
|
|
92
|
+
this._activateItem(id);
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case ` `: {
|
|
97
|
+
const id = this.tickled.tickledId;
|
|
98
|
+
if (id) {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
this.selection.handleSpace(id);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case `Escape`: this.selection.clear();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
this._onClick = (e) => {
|
|
108
|
+
const item = this._findListItem(e.composedPath());
|
|
109
|
+
if (!item) return;
|
|
110
|
+
this._dispatch(`list-item-click`, { item });
|
|
111
|
+
this.selection.handleClick(item, e);
|
|
112
|
+
};
|
|
113
|
+
this._onDblClick = (e) => {
|
|
114
|
+
const item = this._findListItem(e.composedPath());
|
|
115
|
+
if (item) this._activateItem(item);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
#customWrappers;
|
|
119
|
+
#wrappedElements;
|
|
120
|
+
/** Watches for items added to the light DOM so they can be animated in. */
|
|
121
|
+
#itemObserver;
|
|
122
|
+
/** Items that have already played (or been skipped for) their enter animation. */
|
|
123
|
+
#animatedItems;
|
|
124
|
+
#searchRegistry;
|
|
125
|
+
#unregisterSearchCommands;
|
|
126
|
+
/** Command registry carrying this list's `list.search.*` commands. */
|
|
127
|
+
get registry() {
|
|
128
|
+
return this.#searchRegistry;
|
|
129
|
+
}
|
|
130
|
+
#effectiveSearchModes() {
|
|
131
|
+
const raw = this.searchModes?.trim();
|
|
132
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
133
|
+
const requested = new Set(raw.split(/\s+/));
|
|
134
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
135
|
+
}
|
|
136
|
+
#syncSearchCommands() {
|
|
137
|
+
if (!this.#searchRegistry) return;
|
|
138
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
139
|
+
this.#unregisterSearchCommands?.();
|
|
140
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#searchRegistry, this.search);
|
|
141
|
+
}
|
|
142
|
+
connectedCallback() {
|
|
143
|
+
super.connectedCallback();
|
|
144
|
+
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
145
|
+
this.addEventListener(`pointerover`, this._onPointerOver);
|
|
146
|
+
this.addEventListener(`pointerleave`, this._onPointerLeave);
|
|
147
|
+
this.addEventListener(`keydown`, this._onKeyDown);
|
|
148
|
+
this.addEventListener(`click`, this._onClick);
|
|
149
|
+
this.addEventListener(`dblclick`, this._onDblClick);
|
|
150
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR)) this.#animatedItems.add(el);
|
|
151
|
+
this.#itemObserver ??= new MutationObserver((records) => this.#onItemsMutated(records));
|
|
152
|
+
this.#itemObserver.observe(this, { childList: true });
|
|
153
|
+
this.#searchRegistry ??= createCommandRegistry();
|
|
154
|
+
this.#syncSearchCommands();
|
|
155
|
+
}
|
|
156
|
+
disconnectedCallback() {
|
|
157
|
+
super.disconnectedCallback();
|
|
158
|
+
this.removeEventListener(`pointerover`, this._onPointerOver);
|
|
159
|
+
this.removeEventListener(`pointerleave`, this._onPointerLeave);
|
|
160
|
+
this.removeEventListener(`keydown`, this._onKeyDown);
|
|
161
|
+
this.removeEventListener(`click`, this._onClick);
|
|
162
|
+
this.removeEventListener(`dblclick`, this._onDblClick);
|
|
163
|
+
this.#itemObserver?.disconnect();
|
|
164
|
+
this.#unregisterSearchCommands?.();
|
|
165
|
+
this.#unregisterSearchCommands = void 0;
|
|
166
|
+
this.#searchRegistry = void 0;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Animate in items appended to the light DOM (however they got there —
|
|
170
|
+
* {@link addItem}, direct `appendChild`, a framework binding). A bulk populate
|
|
171
|
+
* (roughly everything at once) is left alone, as are the wrapper `<li>`s the
|
|
172
|
+
* checked-mode plumbing shuffles around.
|
|
173
|
+
*/
|
|
174
|
+
#onItemsMutated(records) {
|
|
175
|
+
if (!this.isConnected) return;
|
|
176
|
+
const fresh = [];
|
|
177
|
+
for (const record of records) for (const node of record.addedNodes) {
|
|
178
|
+
if (!(node instanceof HTMLElement) || node.parentElement !== this) continue;
|
|
179
|
+
if (!node.matches(ITEM_SELECTOR)) continue;
|
|
180
|
+
if (this.#animatedItems.has(node) || this.#customWrappers.has(node) || this.#wrappedElements.has(node)) continue;
|
|
181
|
+
this.#animatedItems.add(node);
|
|
182
|
+
fresh.push(node);
|
|
183
|
+
}
|
|
184
|
+
if (fresh.length === 0) return;
|
|
185
|
+
const total = this.querySelectorAll(ITEM_SELECTOR).length;
|
|
186
|
+
if (fresh.length >= total) return;
|
|
187
|
+
for (const el of fresh) animateListItemIn(el);
|
|
188
|
+
}
|
|
189
|
+
updated(changed) {
|
|
190
|
+
super.updated(changed);
|
|
191
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
192
|
+
if (changed.has(`interactionMode`)) {
|
|
193
|
+
this.selection.clearAnchor();
|
|
194
|
+
const previousSelected = this.selection.selected;
|
|
195
|
+
const savedWrappers = changed.get(`interactionMode`) === `checked` ? new Map(this.#customWrappers) : null;
|
|
196
|
+
this._syncCheckboxes();
|
|
197
|
+
if (previousSelected.size > 0) {
|
|
198
|
+
if (savedWrappers && savedWrappers.size > 0) this.selection.remap((el) => savedWrappers.get(el) ?? el);
|
|
199
|
+
else if (this.#customWrappers.size > 0) {
|
|
200
|
+
const innerToWrapper = new Map([...this.#customWrappers].map(([w, i]) => [i, w]));
|
|
201
|
+
this.selection.remap((el) => innerToWrapper.get(el) ?? el);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
this._syncItemStates();
|
|
205
|
+
} else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) this._syncCheckboxes();
|
|
206
|
+
}
|
|
207
|
+
/** The most recently selected item, or `undefined` if nothing is selected. */
|
|
208
|
+
get selectedItem() {
|
|
209
|
+
return [...this.selection.selected].at(-1);
|
|
210
|
+
}
|
|
211
|
+
/** The full current selection set (read-only). */
|
|
212
|
+
get selectedItems() {
|
|
213
|
+
return this.selection.selected;
|
|
214
|
+
}
|
|
215
|
+
addItem(item, options) {
|
|
216
|
+
let el;
|
|
217
|
+
if (typeof item === `string`) {
|
|
218
|
+
const li = document.createElement(`li`);
|
|
219
|
+
li.textContent = item;
|
|
220
|
+
el = li;
|
|
221
|
+
} else el = item;
|
|
222
|
+
if (options?.notChecked) el.setAttribute(`not-checked`, ``);
|
|
223
|
+
this.appendChild(el);
|
|
224
|
+
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Collapse + fade `item` out, then detach it. The rows below slide up to
|
|
228
|
+
* close the gap. Resolves once the element has actually been removed.
|
|
229
|
+
* Under `prefers-reduced-motion: reduce` it's removed immediately.
|
|
230
|
+
*/
|
|
231
|
+
removeItem(item) {
|
|
232
|
+
this.selection.deselect(item);
|
|
233
|
+
return animateListItemOut(item).then(() => {
|
|
234
|
+
item.remove();
|
|
235
|
+
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** {@link removeItem} for several items at once. */
|
|
239
|
+
removeItems(items) {
|
|
240
|
+
return Promise.all([...items].map((item) => this.removeItem(item))).then(() => void 0);
|
|
241
|
+
}
|
|
242
|
+
clearItems() {
|
|
243
|
+
this.#customWrappers.clear();
|
|
244
|
+
this.replaceChildren();
|
|
245
|
+
this.selection.reset();
|
|
246
|
+
this._syncItemStates();
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Replace the entire item list in one call.
|
|
250
|
+
* Equivalent to `clearItems()` followed by `addItem()` for each element.
|
|
251
|
+
* Preserves `interactionMode` checkbox wrapping.
|
|
252
|
+
*/
|
|
253
|
+
setItems(items) {
|
|
254
|
+
this.clearItems();
|
|
255
|
+
for (const item of items) this.appendChild(item);
|
|
256
|
+
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Dispatch a command to this list — used by `ixfx-grouped-item-lister` to
|
|
260
|
+
* forward commands to all sub-components. Routes through the command registry
|
|
261
|
+
* (so `list.search.*` and any other registered commands work).
|
|
262
|
+
*/
|
|
263
|
+
dispatch(commandId, args) {
|
|
264
|
+
this.#searchRegistry?.invoke(commandId, args);
|
|
265
|
+
}
|
|
266
|
+
select(item) {
|
|
267
|
+
this.selection.replace(item);
|
|
268
|
+
}
|
|
269
|
+
selectMany(items) {
|
|
270
|
+
this.selection.selectMany(items);
|
|
271
|
+
}
|
|
272
|
+
deselect(item) {
|
|
273
|
+
this.selection.deselect(item);
|
|
274
|
+
}
|
|
275
|
+
clearSelection() {
|
|
276
|
+
this.selection.clear();
|
|
277
|
+
}
|
|
278
|
+
selectAll() {
|
|
279
|
+
if (this.selection.canSelectAll()) this.selection.selectAll();
|
|
280
|
+
}
|
|
281
|
+
/** The selection as stable `data-key` / `data-value` strings. */
|
|
282
|
+
get selectedKeys() {
|
|
283
|
+
return this.selection.selectedKeys;
|
|
284
|
+
}
|
|
285
|
+
/** The key of the item under the cursor, or `undefined`. */
|
|
286
|
+
get tickledKey() {
|
|
287
|
+
const id = this.tickled.tickledId;
|
|
288
|
+
return id ? keyForElement(id) : void 0;
|
|
289
|
+
}
|
|
290
|
+
/** Replace the selection with the items carrying these keys (unknown keys ignored). */
|
|
291
|
+
selectKeys(keys) {
|
|
292
|
+
this.selection.selectKeys(keys, (key) => this.#itemForKey(key));
|
|
293
|
+
}
|
|
294
|
+
/** Add items by key to the selection (multi-select modes only). */
|
|
295
|
+
addSelectedKeys(keys) {
|
|
296
|
+
this.selection.addKeys(keys, (key) => this.#itemForKey(key));
|
|
297
|
+
}
|
|
298
|
+
/** Remove items by key from the selection. */
|
|
299
|
+
removeSelectedKeys(keys) {
|
|
300
|
+
this.selection.removeKeys(keys, (key) => this.#itemForKey(key));
|
|
301
|
+
}
|
|
302
|
+
/** Move the cursor to the item with this key and scroll it into view. */
|
|
303
|
+
focusKey(key) {
|
|
304
|
+
const el = this.#itemForKey(key);
|
|
305
|
+
if (!el) return;
|
|
306
|
+
this.tickled.focus(el);
|
|
307
|
+
el.scrollIntoView({
|
|
308
|
+
block: `nearest`,
|
|
309
|
+
inline: `nearest`
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
#itemForKey(key) {
|
|
313
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
|
|
314
|
+
if (this.#wrappedElements.has(el)) continue;
|
|
315
|
+
if (keyForElement(el) === key) return el;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
*selectedFilter(kind) {
|
|
319
|
+
const wanted = (el) => {
|
|
320
|
+
switch (kind) {
|
|
321
|
+
case `selected`: return el.hasAttribute(`data-selected`);
|
|
322
|
+
case `checked`: return el.hasAttribute(`data-checked`);
|
|
323
|
+
case `either`: return el.hasAttribute(`data-selected`) || el.hasAttribute(`data-checked`);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
|
|
327
|
+
if (this.#wrappedElements.has(el)) continue;
|
|
328
|
+
if (el.hidden) continue;
|
|
329
|
+
if (wanted(el)) yield el;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
_getVisibleItems() {
|
|
333
|
+
return Array.from(this.querySelectorAll(ITEM_SELECTOR)).filter((el) => {
|
|
334
|
+
if (this.#wrappedElements.has(el)) return false;
|
|
335
|
+
if (el.hidden) return false;
|
|
336
|
+
if (this.filterPredicate && !this.filterPredicate(el)) return false;
|
|
337
|
+
return true;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
_isCheckable(item) {
|
|
341
|
+
if (this.interactionMode !== `checked`) return true;
|
|
342
|
+
return !(this.#customWrappers.get(item) ?? item).hasAttribute(`not-checked`);
|
|
343
|
+
}
|
|
344
|
+
_syncItemStates() {
|
|
345
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
|
|
346
|
+
if (this.#wrappedElements.has(el)) continue;
|
|
347
|
+
const item = this.#customWrappers.get(el) ?? el;
|
|
348
|
+
el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
|
|
349
|
+
el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
|
|
350
|
+
if (this.interactionMode === `checked`) el.toggleAttribute(`data-checked`, !item.hasAttribute(`not-checked`) && this.selection.selected.has(el));
|
|
351
|
+
else el.removeAttribute(`data-checked`);
|
|
352
|
+
}
|
|
353
|
+
this.dataset.inputMode = this.tickled.inputMode;
|
|
354
|
+
this._syncCheckboxes();
|
|
355
|
+
}
|
|
356
|
+
_syncCheckboxes() {
|
|
357
|
+
const inCheckedMode = this.interactionMode === `checked`;
|
|
358
|
+
const wantsCustom = typeof this.checkboxRenderer === `function`;
|
|
359
|
+
if (inCheckedMode) for (const inner of Array.from(this.children)) {
|
|
360
|
+
if (!inner.hasAttribute(`data-list-item`)) continue;
|
|
361
|
+
if (inner.parentElement !== this) continue;
|
|
362
|
+
const wrapper = document.createElement(`li`);
|
|
363
|
+
wrapper.className = `ixfx-custom-row`;
|
|
364
|
+
const key = inner.getAttribute(`data-key`);
|
|
365
|
+
const label = inner.getAttribute(`data-search-label`);
|
|
366
|
+
if (key !== null) wrapper.setAttribute(`data-key`, key);
|
|
367
|
+
if (label !== null) wrapper.setAttribute(`data-search-label`, label);
|
|
368
|
+
inner.removeAttribute(`data-list-item`);
|
|
369
|
+
this.insertBefore(wrapper, inner);
|
|
370
|
+
wrapper.appendChild(inner);
|
|
371
|
+
this.#customWrappers.set(wrapper, inner);
|
|
372
|
+
this.#wrappedElements.add(inner);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
for (const [wrapper, inner] of this.#customWrappers) {
|
|
376
|
+
if (wrapper.parentElement !== this) continue;
|
|
377
|
+
inner.setAttribute(`data-list-item`, ``);
|
|
378
|
+
this.insertBefore(inner, wrapper);
|
|
379
|
+
wrapper.remove();
|
|
380
|
+
}
|
|
381
|
+
this.#customWrappers.clear();
|
|
382
|
+
this.#wrappedElements.clear();
|
|
383
|
+
}
|
|
384
|
+
for (const el of this.querySelectorAll(`li`)) {
|
|
385
|
+
const existing = el.querySelector(`.ixfx-list-checkbox`);
|
|
386
|
+
if (!inCheckedMode) {
|
|
387
|
+
if (existing) existing.remove();
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if ((this.#customWrappers.get(el) ?? el).hasAttribute(`not-checked`)) {
|
|
391
|
+
if (existing) existing.remove();
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const existingIsCustom = existing?.classList.contains(`ixfx-list-checkbox-custom`) ?? false;
|
|
395
|
+
if (existing && existingIsCustom !== wantsCustom) existing.remove();
|
|
396
|
+
let cb = el.querySelector(`.ixfx-list-checkbox`);
|
|
397
|
+
if (!cb) {
|
|
398
|
+
if (wantsCustom) {
|
|
399
|
+
const opts = this.checkboxRenderer;
|
|
400
|
+
const margin = opts.margin ?? `0`;
|
|
401
|
+
const width = opts.width ?? `24px`;
|
|
402
|
+
cb = this._createCustomCheckbox(el, margin, width);
|
|
403
|
+
} else cb = this._createNativeCheckboxCell(el);
|
|
404
|
+
el.prepend(cb);
|
|
405
|
+
}
|
|
406
|
+
const checked = this.selection.selected.has(el);
|
|
407
|
+
if (wantsCustom) {
|
|
408
|
+
cb.setAttribute(`aria-checked`, String(checked));
|
|
409
|
+
cb.toggleAttribute(`data-checked`, checked);
|
|
410
|
+
if (this.checkboxRenderer) {
|
|
411
|
+
const renderer = typeof this.checkboxRenderer === `function` ? this.checkboxRenderer : this.checkboxRenderer.renderer;
|
|
412
|
+
render(renderer(checked), cb);
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
const input = cb.querySelector(`input.ixfx-list-checkbox`);
|
|
416
|
+
if (input) input.checked = checked;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Native checkbox cell: a span that stretches to the row's full cross-axis
|
|
422
|
+
* (so the entire column height is a click target) with a real `<input>`
|
|
423
|
+
* centred inside. The span owns the click; the input is purely visual and
|
|
424
|
+
* has `pointer-events:none` so a single click produces one toggle.
|
|
425
|
+
*/
|
|
426
|
+
_createNativeCheckboxCell(el) {
|
|
427
|
+
const cell = document.createElement(`span`);
|
|
428
|
+
cell.className = `ixfx-list-checkbox ixfx-list-checkbox-native`;
|
|
429
|
+
cell.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:18px;margin:0 6px 0 0;cursor:pointer;pointer-events:auto`;
|
|
430
|
+
cell.tabIndex = -1;
|
|
431
|
+
cell.setAttribute(`role`, `checkbox`);
|
|
432
|
+
cell.setAttribute(`part`, `checkbox`);
|
|
433
|
+
cell.addEventListener(`click`, (e) => {
|
|
434
|
+
e.stopPropagation();
|
|
435
|
+
this.selection.toggle(el);
|
|
436
|
+
});
|
|
437
|
+
const input = document.createElement(`input`);
|
|
438
|
+
input.type = `checkbox`;
|
|
439
|
+
input.className = `ixfx-list-checkbox`;
|
|
440
|
+
input.style.cssText = `pointer-events:none;cursor:pointer;margin:0`;
|
|
441
|
+
cell.appendChild(input);
|
|
442
|
+
return cell;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Custom checkbox cell: same shape as the native cell (a stretching span)
|
|
446
|
+
* so behaviour is identical. The renderer's content goes inside.
|
|
447
|
+
*/
|
|
448
|
+
_createCustomCheckbox(el, margin, width) {
|
|
449
|
+
const span = document.createElement(`span`);
|
|
450
|
+
span.className = `ixfx-list-checkbox ixfx-list-checkbox-custom`;
|
|
451
|
+
span.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:${width};margin:${margin};cursor:pointer;pointer-events:auto`;
|
|
452
|
+
span.tabIndex = -1;
|
|
453
|
+
span.setAttribute(`role`, `checkbox`);
|
|
454
|
+
span.setAttribute(`part`, `checkbox`);
|
|
455
|
+
span.addEventListener(`click`, (e) => {
|
|
456
|
+
e.stopPropagation();
|
|
457
|
+
this.selection.toggle(el);
|
|
458
|
+
});
|
|
459
|
+
return span;
|
|
460
|
+
}
|
|
461
|
+
_findListItem(path) {
|
|
462
|
+
for (const target of path) {
|
|
463
|
+
if (!(target instanceof Element)) continue;
|
|
464
|
+
if (target === this) break;
|
|
465
|
+
if (target.parentElement === this && target.matches(ITEM_SELECTOR)) return target;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
_dispatch(name, detail) {
|
|
469
|
+
this.dispatchEvent(new CustomEvent(name, {
|
|
470
|
+
detail,
|
|
471
|
+
bubbles: true,
|
|
472
|
+
composed: true
|
|
473
|
+
}));
|
|
474
|
+
}
|
|
475
|
+
_activateItem(item) {
|
|
476
|
+
if (this.interactionMode !== `manual`) this.selection.replace(item);
|
|
477
|
+
this._dispatch(`list-activate`, { item });
|
|
478
|
+
}
|
|
479
|
+
static {
|
|
480
|
+
this.styles = [
|
|
481
|
+
themeFallbacks,
|
|
482
|
+
listSearchStyles,
|
|
483
|
+
css`
|
|
484
|
+
:host {
|
|
485
|
+
display: block;
|
|
486
|
+
position: relative;
|
|
487
|
+
background: var(--list-bg, var(--surface-3));
|
|
488
|
+
color: var(--list-text, var(--surface-3-text));
|
|
489
|
+
border-radius: var(--list-radius, var(--radius-s));
|
|
490
|
+
border: 1px solid var(--list-border, var(--border));
|
|
491
|
+
width: 100%;
|
|
492
|
+
height: 100%;
|
|
493
|
+
overflow: hidden;
|
|
494
|
+
box-sizing: border-box;
|
|
495
|
+
outline: none;
|
|
496
|
+
--ixfx-list-item-padding: var(--ixfx-list-item-padding, var(--list-item-padding, 4px var(--space-m, 8px)));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
:host(:focus-visible) {
|
|
500
|
+
outline: 2px solid var(--accent);
|
|
501
|
+
outline-offset: 2px;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.container {
|
|
505
|
+
position: relative;
|
|
506
|
+
width: 100%;
|
|
507
|
+
height: 100%;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
ul {
|
|
511
|
+
list-style: none;
|
|
512
|
+
margin: 0;
|
|
513
|
+
padding: 0;
|
|
514
|
+
width: 100%;
|
|
515
|
+
height: 100%;
|
|
516
|
+
overflow-x: hidden;
|
|
517
|
+
overflow-y: auto;
|
|
518
|
+
box-sizing: border-box;
|
|
519
|
+
/* Scrollbar theming: end users override these CSS variables to control
|
|
520
|
+
the native scrollbar (scrollbar-width, scrollbar-gutter, scrollbar-color). */
|
|
521
|
+
scrollbar-width: var(--list-scrollbar-width, auto);
|
|
522
|
+
scrollbar-gutter: var(--list-scrollbar-gutter, auto);
|
|
523
|
+
scrollbar-color: var(--list-scrollbar-color, auto);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
::slotted([hidden]) {
|
|
527
|
+
display: none !important;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
::slotted(li) {
|
|
531
|
+
display: flex;
|
|
532
|
+
align-items: center;
|
|
533
|
+
padding: var(--ixfx-list-item-padding);
|
|
534
|
+
/* Inset each row from the scroll container's edges (margin, not padding:
|
|
535
|
+
padding is inside the border box, so the tickle/selection outlines
|
|
536
|
+
would still sit flush against the edge and be clipped). */
|
|
537
|
+
margin: 0 var(--list-gutter, 4px);
|
|
538
|
+
min-height: var(--list-item-height, 28px);
|
|
539
|
+
cursor: pointer;
|
|
540
|
+
user-select: none;
|
|
541
|
+
transition: background var(--transition, 0.1s ease-out),
|
|
542
|
+
outline-color var(--transition, 0.1s ease-out);
|
|
543
|
+
box-sizing: border-box;
|
|
544
|
+
outline: 1px solid transparent;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* Custom shadow-DOM components handle their own internal layout/padding */
|
|
548
|
+
::slotted([data-list-item]) {
|
|
549
|
+
display: block;
|
|
550
|
+
margin: 0 var(--list-gutter, 4px);
|
|
551
|
+
cursor: pointer;
|
|
552
|
+
user-select: none;
|
|
553
|
+
transition: background var(--transition, 0.1s ease-out),
|
|
554
|
+
outline-color var(--transition, 0.1s ease-out);
|
|
555
|
+
outline: 1px solid transparent;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/* In checked mode, only the checkbox column is clickable; the row body
|
|
559
|
+
gets a default cursor so it doesn't suggest otherwise. */
|
|
560
|
+
:host([interaction-mode="checked"]) ::slotted(li),
|
|
561
|
+
:host([interaction-mode="checked"]) ::slotted([data-list-item]) {
|
|
562
|
+
cursor: default;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/* Wrapper inserted around custom [data-list-item] elements in checked mode.
|
|
566
|
+
Provides the flex layout so the injected checkbox can stretch to the
|
|
567
|
+
row's full height via align-self:stretch. */
|
|
568
|
+
.ixfx-custom-row {
|
|
569
|
+
display: flex;
|
|
570
|
+
align-items: center;
|
|
571
|
+
min-height: var(--list-item-height, 28px);
|
|
572
|
+
box-sizing: border-box;
|
|
573
|
+
cursor: default;
|
|
574
|
+
user-select: none;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
::slotted([data-tickled]) {
|
|
578
|
+
background: var(--item-bg-tickled);
|
|
579
|
+
outline: var(--item-border-tickled);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
::slotted([data-selected]) {
|
|
583
|
+
background: var(--item-bg-selected);
|
|
584
|
+
color: var(--item-text-selected);
|
|
585
|
+
outline: 1px solid transparent;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
::slotted([data-selected][data-tickled]) {
|
|
589
|
+
background: var(--item-bg-selected-tickled);
|
|
590
|
+
outline: 2px dashed var(--surface-3);
|
|
591
|
+
outline-offset: -2px;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/* checked mode: checkbox (injected into li) communicates selection — suppress bg highlight */
|
|
595
|
+
:host([interaction-mode="checked"]) ::slotted([data-selected]) {
|
|
596
|
+
background: transparent;
|
|
597
|
+
color: inherit;
|
|
598
|
+
outline: 1px solid transparent;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
:host([interaction-mode="checked"]) ::slotted([data-selected][data-tickled]) {
|
|
602
|
+
background: var(--item-bg-tickled);
|
|
603
|
+
color: inherit;
|
|
604
|
+
outline: var(--item-border-tickled);
|
|
605
|
+
}
|
|
606
|
+
`
|
|
607
|
+
];
|
|
608
|
+
}
|
|
609
|
+
render() {
|
|
610
|
+
return html`
|
|
611
|
+
<div class="container">
|
|
612
|
+
<ul part="list">
|
|
613
|
+
<slot></slot>
|
|
614
|
+
</ul>
|
|
615
|
+
${this.search.renderField()}
|
|
616
|
+
</div>
|
|
617
|
+
`;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
__decorate([property({
|
|
621
|
+
type: String,
|
|
622
|
+
attribute: `selection-mode`
|
|
623
|
+
})], VerticalListElement.prototype, "selectionMode", void 0);
|
|
624
|
+
__decorate([property({
|
|
625
|
+
type: String,
|
|
626
|
+
attribute: `interaction-mode`
|
|
627
|
+
})], VerticalListElement.prototype, "interactionMode", void 0);
|
|
628
|
+
__decorate([property({ attribute: false })], VerticalListElement.prototype, "filterPredicate", void 0);
|
|
629
|
+
__decorate([property({
|
|
630
|
+
type: String,
|
|
631
|
+
attribute: `item-wrap`
|
|
632
|
+
})], VerticalListElement.prototype, "itemWrap", void 0);
|
|
633
|
+
__decorate([property({ attribute: false })], VerticalListElement.prototype, "checkboxRenderer", void 0);
|
|
634
|
+
__decorate([property({
|
|
635
|
+
type: String,
|
|
636
|
+
attribute: `search-modes`,
|
|
637
|
+
reflect: true
|
|
638
|
+
})], VerticalListElement.prototype, "searchModes", void 0);
|
|
639
|
+
VerticalListElement = __decorate([safeCustomElement(`ixfx-vertical-list`)], VerticalListElement);
|
|
640
|
+
//#endregion
|
|
2
641
|
export { VerticalListElement };
|
|
642
|
+
|
|
643
|
+
//# sourceMappingURL=vertical-list.js.map
|