@ixfx/components 0.16.0 → 0.16.2
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 +290 -66
- package/bundle/index.js +412 -327
- package/dist/ac-text.d.ts +1 -1
- package/dist/{ac-token-qEs3FsQv.d.ts → ac-token-Cq7lsYf-.d.ts} +1 -1
- package/dist/ac-token.d.ts +1 -1
- package/dist/ac-token.js +1 -1
- package/dist/{button-N92DKoWb.d.ts → button-D1sPDUCM.d.ts} +1 -1
- package/dist/button.d.ts +1 -1
- package/dist/colour-picker.d.ts +1 -1
- package/dist/{crumb-navigation-YnFVFqrx.js → crumb-navigation-D3ndPgFg.js} +2 -2
- package/dist/{crumb-navigation-BbqfmkYs.d.ts → crumb-navigation-nnr881n-.d.ts} +1 -1
- package/dist/crumbs.d.ts +1 -1
- package/dist/crumbs.js +1 -1
- package/dist/{data-grid-CIG2DVip.d.ts → data-grid-BAEpfTkb.d.ts} +32 -5
- package/dist/{data-grid-rMkLX2qo.js → data-grid-CaPLWt5E.js} +159 -60
- package/dist/data-grid.d.ts +2 -2
- package/dist/data-grid.js +1 -1
- package/dist/{detail-list-C4pDSoI8.js → detail-list-C3XKTRmE.js} +86 -21
- package/dist/{detail-list-6iMTNP4N.d.ts → detail-list-DK49TsJd.d.ts} +26 -4
- package/dist/detail-list.d.ts +2 -2
- package/dist/detail-list.js +1 -1
- package/dist/{flat-list-JPgQSmLW.d.ts → flat-list-Bv4-qfjp.d.ts} +9 -4
- package/dist/flat-list.d.ts +1 -1
- package/dist/flat-list.js +6 -3
- package/dist/{grid-list-CdEkScn-.js → grid-list-51XsR2X2.js} +56 -18
- package/dist/{grid-list-D6n7WH8l.d.ts → grid-list-DaeSHv9P.d.ts} +58 -14
- package/dist/grid-list.d.ts +2 -2
- package/dist/grid-list.js +1 -1
- package/dist/{grouped-item-lister-DETte0Dh.d.ts → grouped-item-lister-DZIjo9nO.d.ts} +2 -2
- package/dist/grouped-item-lister.d.ts +1 -1
- package/dist/{icon-CDGprSdE.d.ts → icon-DR_3rSyZ.d.ts} +1 -1
- package/dist/icons.d.ts +2 -2
- package/dist/{incr-search-JIhwB49y.js → incr-search-2YaxUUA9.js} +150 -11
- package/dist/incr-search.d.ts +1 -1
- package/dist/incr-search.js +1 -1
- package/dist/{index-CoqtMSd9.d.ts → index-BUB5SICW.d.ts} +1 -1
- package/dist/{index-Dr80Zdl6.d.ts → index-BjRoPrwj.d.ts} +31 -4
- package/dist/{index-DN2BWctP.d.ts → index-Bz3loIAf.d.ts} +1 -1
- package/dist/{index-UvnoZUAo.d.ts → index-CJ5fshD3.d.ts} +1 -1
- package/dist/{index-DxyyVqzA.d.ts → index-DM9OLgTH.d.ts} +3 -2
- package/dist/{index-CYRc6Dah.d.ts → index-DXDz60XH.d.ts} +20 -1
- package/dist/{index-CYndvTWH.d.ts → index-garxxqgV.d.ts} +1 -1
- package/dist/{index-Bx0BXPxI.d.ts → index-vdbbGE7N.d.ts} +1 -1
- package/dist/index.d.ts +57 -28
- package/dist/index.js +58 -35
- package/dist/item-appearance-Bl6rMIID.js +60 -0
- package/dist/{labelled-input-base-DiTme1JO.d.ts → labelled-input-base-Dp_9KP0G.d.ts} +1 -1
- package/dist/labelled-radial-input.d.ts +2 -2
- package/dist/labelled-range-input.d.ts +1 -1
- package/dist/labelled-slider-input.d.ts +1 -1
- package/dist/list-group-header.d.ts +1 -1
- package/dist/{list-selection-controller-5L0Nj9tR.js → list-selection-controller-CJDdjH7E.js} +192 -54
- package/dist/list-selection-types-hj9Njei3.d.ts +83 -0
- package/dist/{menu-CU9KAm8s.js → menu-BX1FHxY8.js} +16 -1
- package/dist/{menu-item-bF6narIz.d.ts → menu-item-B0zqw2xp.d.ts} +1 -1
- package/dist/{menu-item-BUv8An2k.js → menu-item-oxU1VKiJ.js} +34 -9
- package/dist/menu.d.ts +2 -2
- package/dist/menu.js +2 -2
- package/dist/{miller-BFRfn0ve.js → miller-BfrTcujx.js} +142 -18
- package/dist/miller.d.ts +1 -1
- package/dist/miller.js +1 -1
- package/dist/narrowed-text.js +1 -1
- package/dist/panel.d.ts +1 -1
- package/dist/{split-layout-CophkNeU.d.ts → split-layout-ChZ0Glrs.d.ts} +1 -1
- package/dist/split-layout.d.ts +1 -1
- package/dist/sunburst-tree.d.ts +1 -1
- package/dist/sunburst-tree.js +1 -1
- package/dist/{sunbursttree-list-B_VxOHlo.js → sunbursttree-list-906G33ME.js} +1 -1
- package/dist/{sunbursttree-list-D6j5BxVQ.d.ts → sunbursttree-list-BAS0KrDG.d.ts} +1 -1
- package/dist/{tab-list-Df371rqw.d.ts → tab-list-wC0xNz1b.d.ts} +1 -1
- package/dist/tabs.d.ts +1 -1
- package/dist/{titlebar-Tqu-VvMU.d.ts → titlebar-B5zSTZnF.d.ts} +1 -1
- package/dist/titlebar.d.ts +1 -1
- package/dist/{tree-Crdpl4nc.js → tree-C_H1COTf.js} +1 -1
- package/dist/tree.d.ts +1 -1
- package/dist/tree.js +1 -1
- package/dist/{vertical-list-BPDmZx6T.d.ts → vertical-list-od4050Bo.d.ts} +11 -2
- package/dist/{vertical-list-1V1Vg2O5.js → vertical-list-qMRIxZIT.js} +54 -20
- package/dist/vertical-list.d.ts +2 -2
- package/dist/vertical-list.js +1 -1
- package/docs-user/data-grid.md +23 -7
- package/docs-user/detail-list.md +16 -1
- package/docs-user/dock.md +12 -3
- package/docs-user/grid-list.md +9 -3
- package/docs-user/index.json +2 -2
- package/docs-user/menu.md +11 -1
- package/docs-user/vertical-list.md +9 -1
- package/package.json +2 -2
- package/dist/item-appearance-BjcMyauw.js +0 -29
- package/dist/list-selection-types-PjKdhF-a.d.ts +0 -45
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region src/interaction/partial-keys.ts
|
|
2
|
+
/**
|
|
3
|
+
* Shared "partial" row state for list-like components: a row whose resource
|
|
4
|
+
* is only partly selected (a time range of an audio file, a region of an
|
|
5
|
+
* image). Set by the caller through a `partialKeys` property; independent of
|
|
6
|
+
* the selection, except that a key which is also selected counts as selected.
|
|
7
|
+
*/
|
|
8
|
+
/** Accessible description for a partial row outside checked mode (no `aria-checked="mixed"` there). */
|
|
9
|
+
const PARTIAL_DESCRIPTION = `Partially selected`;
|
|
10
|
+
/** Whether a row with this key shows the partial state: partial, and not selected (selected wins). */
|
|
11
|
+
function isPartialKey(key, partialKeys, selected) {
|
|
12
|
+
return !selected && key !== void 0 && partialKeys.has(key);
|
|
13
|
+
}
|
|
14
|
+
/** Whether two key sets hold the same keys. */
|
|
15
|
+
function sameKeySet(a, b) {
|
|
16
|
+
if (a.size !== b.size) return false;
|
|
17
|
+
for (const key of a) if (!b.has(key)) return false;
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Reflect the partial state onto a row element: `data-partial`, plus an
|
|
22
|
+
* accessible description. Never overwrites an `aria-description` it didn't
|
|
23
|
+
* set itself.
|
|
24
|
+
*/
|
|
25
|
+
function applyPartialAttributes(el, partial) {
|
|
26
|
+
el.toggleAttribute(`data-partial`, partial);
|
|
27
|
+
if (partial) {
|
|
28
|
+
if (!el.hasAttribute(`aria-description`)) el.setAttribute(`aria-description`, PARTIAL_DESCRIPTION);
|
|
29
|
+
} else if (el.getAttribute(`aria-description`) === PARTIAL_DESCRIPTION) el.removeAttribute(`aria-description`);
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/common/item-appearance.ts
|
|
33
|
+
/**
|
|
34
|
+
* Shared helpers for the list components' per-item appearance hooks — the
|
|
35
|
+
* per-item icon colour / text colour / badge that miller-list, detail-list,
|
|
36
|
+
* grid-list, data-grid and vertical-list all accept.
|
|
37
|
+
*
|
|
38
|
+
* Every component takes these three features in its own idiom (plain fields
|
|
39
|
+
* on a `TreeItem`, a property-name lookup, an adapter function, a nominated
|
|
40
|
+
* column) but they all agree on what "not supplied" means, and on never
|
|
41
|
+
* emitting an empty slot for an absent value.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* Whether a per-item appearance value counts as "not supplied". `false` and
|
|
45
|
+
* `''` are included so a consumer can clear a feature without rebuilding the
|
|
46
|
+
* collection — e.g. `badge: flags && tagChip(flags.colour)`.
|
|
47
|
+
*/
|
|
48
|
+
function appearanceAbsent(value) {
|
|
49
|
+
return value === void 0 || value === null || value === false || value === ``;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A usable CSS colour from a per-item value, or `undefined`. Non-strings and
|
|
53
|
+
* empty strings are ignored rather than stringified, so a stray number or
|
|
54
|
+
* `0` never lands in a `style` attribute.
|
|
55
|
+
*/
|
|
56
|
+
function appearanceColor(value) {
|
|
57
|
+
return typeof value === `string` && value.length > 0 ? value : void 0;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { isPartialKey as a, applyPartialAttributes as i, appearanceColor as n, sameKeySet as o, PARTIAL_DESCRIPTION as r, appearanceAbsent as t };
|
|
@@ -33,7 +33,7 @@ declare abstract class LabelledInputBase extends LitElement {
|
|
|
33
33
|
value: number;
|
|
34
34
|
}>) => void;
|
|
35
35
|
protected _syncValue(v: number): void;
|
|
36
|
-
protected _renderEditableNumber():
|
|
36
|
+
protected _renderEditableNumber(): import("lit").TemplateResult<1> | typeof nothing;
|
|
37
37
|
updated(changedProperties: Map<string, unknown>): void;
|
|
38
38
|
}
|
|
39
39
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as LabelledInputBase } from "./labelled-input-base-
|
|
1
|
+
import { t as LabelledInputBase } from "./labelled-input-base-Dp_9KP0G.js";
|
|
2
2
|
import { i as MarkerStyle, l as ValueMode } from "./radial-input-DU6t2hYO.js";
|
|
3
3
|
import { CSSResultGroup, nothing } from "lit";
|
|
4
4
|
//#region src/labelled-radial-input/labelled-radial-input.d.ts
|
|
@@ -19,7 +19,7 @@ declare class LabelledRadialInput extends LabelledInputBase {
|
|
|
19
19
|
private _isWide;
|
|
20
20
|
private _getEffectivePrecision;
|
|
21
21
|
protected _handleResize(entries: ResizeObserverEntry[]): void;
|
|
22
|
-
protected _renderEditableNumber():
|
|
22
|
+
protected _renderEditableNumber(): import("lit").TemplateResult<1> | typeof nothing;
|
|
23
23
|
render(): import("lit").TemplateResult<1>;
|
|
24
24
|
private _getTooltipValue;
|
|
25
25
|
static styles: CSSResultGroup;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as LabelledRangeInput } from "./index-
|
|
1
|
+
import { t as LabelledRangeInput } from "./index-BUB5SICW.js";
|
|
2
2
|
export { LabelledRangeInput };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as LabelledSliderInput } from "./index-
|
|
1
|
+
import { t as LabelledSliderInput } from "./index-CJ5fshD3.js";
|
|
2
2
|
export { LabelledSliderInput };
|
package/dist/{list-selection-controller-5L0Nj9tR.js → list-selection-controller-CJDdjH7E.js}
RENAMED
|
@@ -42,6 +42,7 @@ var ListDragSelectController = class {
|
|
|
42
42
|
/** Combine mode of the most recent preview (drives the commit flag). */
|
|
43
43
|
#lastMode = `replace`;
|
|
44
44
|
#preview = /* @__PURE__ */ new Set();
|
|
45
|
+
#covered = /* @__PURE__ */ new Set();
|
|
45
46
|
#raf = 0;
|
|
46
47
|
constructor(host, config) {
|
|
47
48
|
this.#host = host;
|
|
@@ -129,6 +130,7 @@ var ListDragSelectController = class {
|
|
|
129
130
|
this.#suppressClick = false;
|
|
130
131
|
this.#base = /* @__PURE__ */ new Set();
|
|
131
132
|
this.#lastMode = `replace`;
|
|
133
|
+
this.#covered = /* @__PURE__ */ new Set();
|
|
132
134
|
this.#lastModifiers = {
|
|
133
135
|
shiftKey: event.shiftKey,
|
|
134
136
|
ctrlKey: event.ctrlKey,
|
|
@@ -211,6 +213,7 @@ var ListDragSelectController = class {
|
|
|
211
213
|
preview = toggled;
|
|
212
214
|
} else preview = hitSet;
|
|
213
215
|
this.#preview = preview;
|
|
216
|
+
this.#covered = hitSet;
|
|
214
217
|
try {
|
|
215
218
|
this.#config.onPreview(preview, rect);
|
|
216
219
|
} catch {}
|
|
@@ -236,7 +239,10 @@ var ListDragSelectController = class {
|
|
|
236
239
|
this.#dragging = false;
|
|
237
240
|
const final = this.#preview;
|
|
238
241
|
try {
|
|
239
|
-
this.#config.onCommit(final, {
|
|
242
|
+
this.#config.onCommit(final, {
|
|
243
|
+
additive: this.#lastMode !== `replace`,
|
|
244
|
+
covered: this.#covered
|
|
245
|
+
});
|
|
240
246
|
} catch {}
|
|
241
247
|
try {
|
|
242
248
|
this.#config.onPreview(/* @__PURE__ */ new Set(), void 0);
|
|
@@ -296,10 +302,20 @@ var ListDragSelectController = class {
|
|
|
296
302
|
this.#base = /* @__PURE__ */ new Set();
|
|
297
303
|
this.#lastMode = `replace`;
|
|
298
304
|
this.#preview = /* @__PURE__ */ new Set();
|
|
305
|
+
this.#covered = /* @__PURE__ */ new Set();
|
|
299
306
|
}
|
|
300
307
|
};
|
|
301
308
|
//#endregion
|
|
302
309
|
//#region src/interaction/list-selection-controller.ts
|
|
310
|
+
/** Capture the modifier / pointer-type state of a click for `handleClick`. */
|
|
311
|
+
function gestureOf(e) {
|
|
312
|
+
return {
|
|
313
|
+
ctrlKey: e.ctrlKey,
|
|
314
|
+
metaKey: e.metaKey,
|
|
315
|
+
shiftKey: e.shiftKey,
|
|
316
|
+
pointerType: e.pointerType
|
|
317
|
+
};
|
|
318
|
+
}
|
|
303
319
|
/** Whether the interaction mode allows building a multi-item selection. */
|
|
304
320
|
function isMultiMode(mode) {
|
|
305
321
|
return mode === `checked` || mode === `sticky` || mode === `vscode` || mode === `standard`;
|
|
@@ -399,36 +415,36 @@ var ListSelectionController = class {
|
|
|
399
415
|
/** Select the contiguous ordered range from the anchor to `item`. Anchor unchanged. */
|
|
400
416
|
rangeTo(item) {
|
|
401
417
|
if (this.#config.getModes().selectionMode === `none`) return;
|
|
402
|
-
|
|
418
|
+
const range = this.#rangeItems(item);
|
|
419
|
+
if (range === void 0) {
|
|
403
420
|
this.replace(item);
|
|
404
421
|
return;
|
|
405
422
|
}
|
|
423
|
+
this.#apply(new Set(range));
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The selectable items between the anchor and `item` (inclusive), or
|
|
427
|
+
* `undefined` when there is no usable range (no anchor, an unresolved end,
|
|
428
|
+
* or a different group) and the gesture degrades to a plain `replace`.
|
|
429
|
+
*/
|
|
430
|
+
#rangeItems(item) {
|
|
431
|
+
if (this.#anchor === void 0) return void 0;
|
|
406
432
|
const ordered = this.#config.getOrderedItems();
|
|
407
433
|
const anchorIdx = ordered.indexOf(this.#anchor);
|
|
408
434
|
const targetIdx = ordered.indexOf(item);
|
|
409
|
-
if (anchorIdx === -1 || targetIdx === -1)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
435
|
+
if (anchorIdx === -1 || targetIdx === -1) return void 0;
|
|
436
|
+
let scope = ordered;
|
|
437
|
+
let a = anchorIdx;
|
|
438
|
+
let b = targetIdx;
|
|
413
439
|
if (this.#clamped()) {
|
|
414
440
|
const group = this.#groupOf(this.#anchor);
|
|
415
|
-
if (group !== this.#groupOf(item))
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const a = inGroup.indexOf(this.#anchor);
|
|
421
|
-
const b = inGroup.indexOf(item);
|
|
422
|
-
if (a === -1 || b === -1) {
|
|
423
|
-
this.replace(item);
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
this.#apply(new Set(inGroup.slice(Math.min(a, b), Math.max(a, b) + 1).filter((t) => this.#config.isSelectable(t))));
|
|
427
|
-
return;
|
|
441
|
+
if (group !== this.#groupOf(item)) return void 0;
|
|
442
|
+
scope = ordered.filter((t) => this.#groupOf(t) === group);
|
|
443
|
+
a = scope.indexOf(this.#anchor);
|
|
444
|
+
b = scope.indexOf(item);
|
|
445
|
+
if (a === -1 || b === -1) return void 0;
|
|
428
446
|
}
|
|
429
|
-
|
|
430
|
-
const end = Math.max(anchorIdx, targetIdx);
|
|
431
|
-
this.#apply(new Set(ordered.slice(start, end + 1).filter((t) => this.#config.isSelectable(t))));
|
|
447
|
+
return scope.slice(Math.min(a, b), Math.max(a, b) + 1).filter((t) => this.#config.isSelectable(t));
|
|
432
448
|
}
|
|
433
449
|
/** Clear the selection. No change event when the selection is already empty. */
|
|
434
450
|
clear() {
|
|
@@ -436,8 +452,16 @@ var ListSelectionController = class {
|
|
|
436
452
|
if (this.#selected.size === 0) return;
|
|
437
453
|
this.#apply(/* @__PURE__ */ new Set());
|
|
438
454
|
}
|
|
439
|
-
/**
|
|
440
|
-
|
|
455
|
+
/**
|
|
456
|
+
* Select every ordered item that passes `isSelectable`. Fires an `all`
|
|
457
|
+
* intent first; the caller may take over.
|
|
458
|
+
*/
|
|
459
|
+
selectAll(source = `api`) {
|
|
460
|
+
if (this.#intercepted({
|
|
461
|
+
kind: `all`,
|
|
462
|
+
keys: [],
|
|
463
|
+
source
|
|
464
|
+
})) return;
|
|
441
465
|
let ordered = this.#config.getOrderedItems();
|
|
442
466
|
if (ordered.length === 0) return;
|
|
443
467
|
if (this.#clamped()) {
|
|
@@ -478,9 +502,20 @@ var ListSelectionController = class {
|
|
|
478
502
|
* non-empty selection (e.g. a Cmd-drag that toggles everything off) but is
|
|
479
503
|
* a no-op when nothing is selected.
|
|
480
504
|
*/
|
|
481
|
-
applyDragSelection(items) {
|
|
505
|
+
applyDragSelection(items, info) {
|
|
482
506
|
if (!this.canDragSelect()) return;
|
|
483
|
-
const
|
|
507
|
+
const final = [...items];
|
|
508
|
+
const covered = info ? [...info.covered].filter((t) => this.#config.isSelectable(t)) : final;
|
|
509
|
+
if (this.#intercepted({
|
|
510
|
+
kind: `marquee`,
|
|
511
|
+
keys: this.#keysOf(covered),
|
|
512
|
+
additive: info?.additive ?? false,
|
|
513
|
+
source: `pointer`
|
|
514
|
+
})) {
|
|
515
|
+
this.#anchor = final.at(-1) ?? this.#anchor;
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const next = this.#clampToFirstGroup(new Set(final.filter((t) => this.#config.isSelectable(t))));
|
|
484
519
|
if (next.size === 0 && this.#selected.size === 0) return;
|
|
485
520
|
this.#anchor = [...next].at(-1);
|
|
486
521
|
this.#apply(next);
|
|
@@ -503,54 +538,67 @@ var ListSelectionController = class {
|
|
|
503
538
|
}
|
|
504
539
|
/**
|
|
505
540
|
* Replace the selection with the items resolved from `keys`. Keys that
|
|
506
|
-
* `resolve` cannot map
|
|
507
|
-
*
|
|
508
|
-
*
|
|
541
|
+
* `resolve` cannot map yet are kept pending and selected when their items
|
|
542
|
+
* appear (see `resolvePending()`); keys that resolve to a non-selectable
|
|
543
|
+
* item are ignored. `selectionMode="none"` is a no-op. When multi-select is
|
|
544
|
+
* not available, only the last key is kept. `onChange` fires once.
|
|
509
545
|
*/
|
|
510
546
|
selectKeys(keys, resolve) {
|
|
511
547
|
if (this.#config.getModes().selectionMode === `none`) return;
|
|
512
548
|
let next = /* @__PURE__ */ new Set();
|
|
549
|
+
const missing = /* @__PURE__ */ new Set();
|
|
513
550
|
for (const key of new Set(keys)) {
|
|
514
551
|
const item = resolve(key);
|
|
515
|
-
if (item
|
|
552
|
+
if (item === void 0) missing.add(key);
|
|
553
|
+
else if (this.#config.isSelectable(item)) next.add(item);
|
|
516
554
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
next
|
|
555
|
+
let pending = missing;
|
|
556
|
+
if (!this.#effectiveMulti()) {
|
|
557
|
+
if (next.size > 0) {
|
|
558
|
+
next = new Set([...next].slice(-1));
|
|
559
|
+
pending = EMPTY_KEYS;
|
|
560
|
+
} else pending = new Set([...missing].slice(-1));
|
|
520
561
|
}
|
|
521
562
|
this.#anchor = [...next].at(-1);
|
|
522
|
-
this.#
|
|
563
|
+
this.#pending = capKeys(pending);
|
|
564
|
+
this.#apply(next, true);
|
|
523
565
|
}
|
|
524
566
|
/**
|
|
525
567
|
* Add the items resolved from `keys` to the selection (multi-select only;
|
|
526
|
-
* a no-op otherwise).
|
|
568
|
+
* a no-op otherwise). Keys not present yet are kept pending. `onChange`
|
|
569
|
+
* fires once if anything changed.
|
|
527
570
|
*/
|
|
528
571
|
addKeys(keys, resolve) {
|
|
529
572
|
if (!this.#effectiveMulti()) return;
|
|
530
573
|
const next = new Set(this.#selected);
|
|
574
|
+
const pending = new Set(this.#pending);
|
|
531
575
|
let changed = false;
|
|
532
576
|
for (const key of new Set(keys)) {
|
|
533
577
|
const item = resolve(key);
|
|
534
|
-
if (item
|
|
578
|
+
if (item === void 0) pending.add(key);
|
|
579
|
+
else if (!next.has(item) && this.#config.isSelectable(item)) {
|
|
535
580
|
next.add(item);
|
|
536
581
|
changed = true;
|
|
537
582
|
}
|
|
538
583
|
}
|
|
539
|
-
if (changed) this.#apply(next);
|
|
584
|
+
if (changed) this.#apply(next, true);
|
|
585
|
+
this.#pending = capKeys(pending);
|
|
540
586
|
}
|
|
541
587
|
/**
|
|
542
|
-
* Remove the items resolved from `keys` from the selection
|
|
543
|
-
* fires once if anything changed.
|
|
588
|
+
* Remove the items resolved from `keys` from the selection, and drop them
|
|
589
|
+
* from the pending keys. `onChange` fires once if anything changed.
|
|
544
590
|
*/
|
|
545
591
|
removeKeys(keys, resolve) {
|
|
592
|
+
const wanted = new Set(keys);
|
|
593
|
+
if (this.#pending.size > 0) this.#pending = new Set([...this.#pending].filter((k) => !wanted.has(k)));
|
|
546
594
|
if (this.#selected.size === 0) return;
|
|
547
595
|
const next = new Set(this.#selected);
|
|
548
596
|
let changed = false;
|
|
549
|
-
for (const key of
|
|
597
|
+
for (const key of wanted) {
|
|
550
598
|
const item = resolve(key);
|
|
551
599
|
if (item !== void 0 && next.delete(item)) changed = true;
|
|
552
600
|
}
|
|
553
|
-
if (changed) this.#apply(next);
|
|
601
|
+
if (changed) this.#apply(next, true);
|
|
554
602
|
}
|
|
555
603
|
/** Selected keys waiting for their items to appear. */
|
|
556
604
|
get pendingKeys() {
|
|
@@ -625,32 +673,122 @@ var ListSelectionController = class {
|
|
|
625
673
|
const item = resolve(key);
|
|
626
674
|
return item !== void 0 && this.#config.isSelectable(item) ? item : void 0;
|
|
627
675
|
}
|
|
628
|
-
/**
|
|
676
|
+
/**
|
|
677
|
+
* Route a click on `item` according to the current interaction mode. In
|
|
678
|
+
* `manual` mode nothing is selected, but the intent is still reported so
|
|
679
|
+
* the caller can apply it.
|
|
680
|
+
*/
|
|
629
681
|
handleClick(item, gesture) {
|
|
630
682
|
const { selectionMode, interactionMode: mode } = this.#config.getModes();
|
|
631
|
-
if (mode === `manual`) return;
|
|
632
683
|
if (selectionMode === `none` && mode !== `checked` && mode !== `sticky`) return;
|
|
684
|
+
const touch = gesture.pointerType === `touch`;
|
|
685
|
+
const modified = gesture.ctrlKey || gesture.metaKey;
|
|
686
|
+
const multi = selectionMode === `multiple` || mode === `vscode`;
|
|
633
687
|
switch (mode) {
|
|
634
688
|
case `implicit`:
|
|
635
|
-
this
|
|
689
|
+
this.#replaceIntent(item, `pointer`);
|
|
690
|
+
break;
|
|
691
|
+
case `manual`:
|
|
692
|
+
if (modified) this.#toggleIntent(item, `pointer`);
|
|
693
|
+
else if (gesture.shiftKey) this.#extendIntent(item, `pointer`);
|
|
694
|
+
else this.#replaceIntent(item, `pointer`);
|
|
636
695
|
break;
|
|
637
696
|
case `standard`:
|
|
638
697
|
case `vscode`:
|
|
639
|
-
if (
|
|
640
|
-
if (
|
|
641
|
-
} else if (gesture.shiftKey &&
|
|
642
|
-
else this
|
|
698
|
+
if (modified) {
|
|
699
|
+
if (multi) this.#toggleIntent(item, `pointer`);
|
|
700
|
+
} else if (gesture.shiftKey && multi) this.#extendIntent(item, `pointer`);
|
|
701
|
+
else this.#replaceIntent(item, `pointer`);
|
|
643
702
|
break;
|
|
644
703
|
case `checked`: break;
|
|
645
|
-
case `sticky`: this
|
|
704
|
+
case `sticky`: this.#toggleIntent(item, touch ? `touch` : `pointer`);
|
|
646
705
|
}
|
|
647
706
|
}
|
|
648
707
|
/** Route a Space keypress on `item` according to the current interaction mode. */
|
|
649
708
|
handleSpace(item) {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
709
|
+
if (this.#config.getModes().interactionMode === `implicit`) this.#replaceIntent(item, `keyboard`);
|
|
710
|
+
else this.#toggleIntent(item, `keyboard`);
|
|
711
|
+
}
|
|
712
|
+
/** Route a checkbox (checked-mode) toggle on `item`. */
|
|
713
|
+
handleCheckbox(item, source = `pointer`) {
|
|
714
|
+
this.#toggleIntent(item, source);
|
|
715
|
+
}
|
|
716
|
+
/** Route a plain "select only this" gesture (Enter, double-click) on `item`. */
|
|
717
|
+
handleReplace(item, source = `pointer`) {
|
|
718
|
+
this.#replaceIntent(item, source);
|
|
719
|
+
}
|
|
720
|
+
/** Route Shift+Arrow: extend the range from the anchor to the cursor `item`. */
|
|
721
|
+
handleExtend(item) {
|
|
722
|
+
this.#extendIntent(item, `keyboard`);
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Route Escape / empty-space click: clear the selection. `reportOnly`
|
|
726
|
+
* suppresses the default action in `manual` mode (the caller owns it).
|
|
727
|
+
*/
|
|
728
|
+
handleClear(source = `keyboard`, reportOnly = false) {
|
|
729
|
+
if (this.#intercepted({
|
|
730
|
+
kind: `clear`,
|
|
731
|
+
keys: [],
|
|
732
|
+
source
|
|
733
|
+
})) return;
|
|
734
|
+
if (reportOnly && this.#config.getModes().interactionMode === `manual`) return;
|
|
735
|
+
this.clear();
|
|
736
|
+
}
|
|
737
|
+
#replaceIntent(item, source) {
|
|
738
|
+
if (this.#config.getModes().selectionMode === `none`) return;
|
|
739
|
+
if (!this.#config.isSelectable(item)) return;
|
|
740
|
+
if (this.#intercepted({
|
|
741
|
+
kind: `replace`,
|
|
742
|
+
keys: this.#keysOf([item]),
|
|
743
|
+
source
|
|
744
|
+
})) {
|
|
745
|
+
this.#anchor = item;
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (this.#config.getModes().interactionMode === `manual`) return;
|
|
749
|
+
this.replace(item);
|
|
750
|
+
}
|
|
751
|
+
#toggleIntent(item, source) {
|
|
752
|
+
if (!this.#config.isSelectable(item)) return;
|
|
753
|
+
if (this.#intercepted({
|
|
754
|
+
kind: `toggle`,
|
|
755
|
+
keys: this.#keysOf([item]),
|
|
756
|
+
source
|
|
757
|
+
})) return;
|
|
758
|
+
if (this.#config.getModes().interactionMode === `manual`) return;
|
|
759
|
+
this.toggle(item);
|
|
760
|
+
}
|
|
761
|
+
#extendIntent(item, source) {
|
|
762
|
+
if (this.#config.getModes().selectionMode === `none`) return;
|
|
763
|
+
const range = this.#anchor === void 0 ? void 0 : this.#rangeItems(item);
|
|
764
|
+
const anchorKey = this.#anchor === void 0 ? void 0 : this.#config.getKey?.(this.#anchor);
|
|
765
|
+
const targetKey = this.#config.getKey?.(item);
|
|
766
|
+
if (this.#intercepted({
|
|
767
|
+
kind: `extend`,
|
|
768
|
+
keys: this.#keysOf(range ?? [item]),
|
|
769
|
+
...anchorKey === void 0 ? {} : { anchorKey },
|
|
770
|
+
...targetKey === void 0 ? {} : { targetKey },
|
|
771
|
+
source
|
|
772
|
+
})) {
|
|
773
|
+
if (this.#anchor === void 0) this.#anchor = item;
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (this.#config.getModes().interactionMode === `manual`) return;
|
|
777
|
+
this.rangeTo(item);
|
|
778
|
+
}
|
|
779
|
+
#keysOf(items) {
|
|
780
|
+
const getKey = this.#config.getKey;
|
|
781
|
+
if (!getKey) return [];
|
|
782
|
+
const out = [];
|
|
783
|
+
for (const item of items) {
|
|
784
|
+
const key = getKey(item);
|
|
785
|
+
if (key !== void 0) out.push(key);
|
|
786
|
+
}
|
|
787
|
+
return out;
|
|
788
|
+
}
|
|
789
|
+
/** Report an intent; `true` when the caller took over. */
|
|
790
|
+
#intercepted(intent) {
|
|
791
|
+
return this.#config.onIntent?.(intent) ?? false;
|
|
654
792
|
}
|
|
655
793
|
/** Drop the range-selection anchor (e.g. when the interaction mode changes). */
|
|
656
794
|
clearAnchor() {
|
|
@@ -707,4 +845,4 @@ var ListSelectionController = class {
|
|
|
707
845
|
}
|
|
708
846
|
};
|
|
709
847
|
//#endregion
|
|
710
|
-
export {
|
|
848
|
+
export { ListDragSelectController as i, gestureOf as n, isMultiMode as r, ListSelectionController as t };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
//#region src/interaction/list-selection-types.d.ts
|
|
2
|
+
type ListSelectionMode = `none` | `single` | `multiple`;
|
|
3
|
+
/**
|
|
4
|
+
* Controls how pointer and keyboard gestures map to selection changes.
|
|
5
|
+
*
|
|
6
|
+
* - `implicit` — click always replaces selection (no modifier support)
|
|
7
|
+
* - `standard` — click replaces; Ctrl/Cmd+click toggles; Shift+click range-selects (multiple only)
|
|
8
|
+
* - `manual` — no auto-selection; drive state via select()/deselect()/clearSelection()
|
|
9
|
+
* - `checked` — every click toggles; implies multiple selection regardless of selectionMode
|
|
10
|
+
* - `sticky` — every tap/click toggles (no checkbox column); implies multiple selection, for touchscreens
|
|
11
|
+
* - `vscode` — like standard for clicks; Shift+Arrow extends range from the keyboard anchor
|
|
12
|
+
*
|
|
13
|
+
* Switching interactionMode clears the current selection.
|
|
14
|
+
*/
|
|
15
|
+
type ListInteractionMode = `implicit` | `standard` | `manual` | `checked` | `sticky` | `vscode`;
|
|
16
|
+
type ListSelectDetail = {
|
|
17
|
+
readonly selected: ReadonlySet<Element>;
|
|
18
|
+
readonly previous: ReadonlySet<Element>;
|
|
19
|
+
/**
|
|
20
|
+
* The selection as stable key strings (`data-key` / `data-value`). Empty
|
|
21
|
+
* when items carry no key. Additive to `selected`.
|
|
22
|
+
*/
|
|
23
|
+
readonly selectedKeys: ReadonlySet<string>;
|
|
24
|
+
/** The previous selection as keys. Additive to `previous`. */
|
|
25
|
+
readonly previousKeys: ReadonlySet<string>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The key-based subset of `list-select` detail common to every list-like
|
|
29
|
+
* component (including `ixfx-data-grid`), so one adapter can read any of them.
|
|
30
|
+
*/
|
|
31
|
+
type ListSelectKeysDetail = Pick<ListSelectDetail, `selectedKeys` | `previousKeys`>;
|
|
32
|
+
/**
|
|
33
|
+
* What the user (or API) is trying to do to the selection. See
|
|
34
|
+
* {@link ListSelectIntentDetail}.
|
|
35
|
+
*/
|
|
36
|
+
type ListSelectIntentKind = `replace` | `toggle` | `extend` | `all` | `marquee` | `clear`;
|
|
37
|
+
type ListSelectIntentSource = `pointer` | `keyboard` | `touch` | `api`;
|
|
38
|
+
/**
|
|
39
|
+
* Detail of the cancelable `list-select-intent` event: fired *before* a
|
|
40
|
+
* component changes its selection, in every interaction mode (including
|
|
41
|
+
* `manual`). Calling `preventDefault()` means "the caller applies this
|
|
42
|
+
* itself" — the component then changes nothing (cursor and range anchor still
|
|
43
|
+
* move). Without `preventDefault()` behaviour is unchanged.
|
|
44
|
+
*
|
|
45
|
+
* Only gestures and `selectAll()` fire it. The imperative `select()` /
|
|
46
|
+
* `selectKeys()` / `clearSelection()` family never does, so a handler can
|
|
47
|
+
* apply its decision through them without re-entering itself.
|
|
48
|
+
*/
|
|
49
|
+
type ListSelectIntentDetail = {
|
|
50
|
+
readonly kind: ListSelectIntentKind;
|
|
51
|
+
/** Keys directly affected. Empty for `all` / `clear`. For `extend`, the range (a convenience: callers should use the endpoints). */
|
|
52
|
+
readonly keys: readonly string[];
|
|
53
|
+
/** Range anchor, for `extend`. */
|
|
54
|
+
readonly anchorKey?: string;
|
|
55
|
+
/** Range end, for `extend`. */
|
|
56
|
+
readonly targetKey?: string;
|
|
57
|
+
/** Add to rather than replace the selection (a modifier was held). */
|
|
58
|
+
readonly additive?: boolean;
|
|
59
|
+
readonly source: ListSelectIntentSource;
|
|
60
|
+
};
|
|
61
|
+
type ListTickleDetail = {
|
|
62
|
+
readonly item: Element;
|
|
63
|
+
};
|
|
64
|
+
type ListActivateDetail = {
|
|
65
|
+
readonly item: Element;
|
|
66
|
+
};
|
|
67
|
+
type ListItemClickDetail = {
|
|
68
|
+
readonly item: Element;
|
|
69
|
+
};
|
|
70
|
+
type ListSelectEvent = CustomEvent<ListSelectDetail>;
|
|
71
|
+
type ListSelectIntentEvent = CustomEvent<ListSelectIntentDetail>;
|
|
72
|
+
type ListTickleEvent = CustomEvent<ListTickleDetail>;
|
|
73
|
+
type ListActivateEvent = CustomEvent<ListActivateDetail>;
|
|
74
|
+
type ListItemClickEvent = CustomEvent<ListItemClickDetail>;
|
|
75
|
+
interface ListElementEventMap {
|
|
76
|
+
'list-select': ListSelectEvent;
|
|
77
|
+
'list-select-intent': ListSelectIntentEvent;
|
|
78
|
+
'list-tickle': ListTickleEvent;
|
|
79
|
+
'list-activate': ListActivateEvent;
|
|
80
|
+
'list-item-click': ListItemClickEvent;
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { ListItemClickDetail as a, ListSelectEvent as c, ListSelectIntentKind as d, ListSelectIntentSource as f, ListTickleEvent as g, ListTickleDetail as h, ListInteractionMode as i, ListSelectIntentDetail as l, ListSelectionMode as m, ListActivateEvent as n, ListItemClickEvent as o, ListSelectKeysDetail as p, ListElementEventMap as r, ListSelectDetail as s, ListActivateDetail as t, ListSelectIntentEvent as u };
|
|
@@ -4,7 +4,7 @@ import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
|
|
|
4
4
|
import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
|
|
5
5
|
import "./icon-1gpwtPx8.js";
|
|
6
6
|
import "./interaction-E-uMGHkJ.js";
|
|
7
|
-
import { n as MenuTrigger, r as MenuContainer, t as MenuItem } from "./menu-item-
|
|
7
|
+
import { n as MenuTrigger, r as MenuContainer, t as MenuItem } from "./menu-item-oxU1VKiJ.js";
|
|
8
8
|
import { LitElement, css, html, nothing } from "lit";
|
|
9
9
|
import { property, query, state } from "lit/decorators.js";
|
|
10
10
|
//#region src/menu/menu-bar.ts
|
|
@@ -16,6 +16,15 @@ let MenuBar = class MenuBar extends LitElement {
|
|
|
16
16
|
this._handlePointerMove = () => {
|
|
17
17
|
this._setInputMode(`mouse`);
|
|
18
18
|
};
|
|
19
|
+
this._handlePointerOver = (e) => {
|
|
20
|
+
if (!this._openTrigger || e.pointerType === `touch`) return;
|
|
21
|
+
const triggers = this._getTriggers();
|
|
22
|
+
const path = e.composedPath();
|
|
23
|
+
const trigger = triggers.find((t) => path.includes(t));
|
|
24
|
+
if (!trigger || trigger === this._openTrigger) return;
|
|
25
|
+
this._openTrigger.closeMenu();
|
|
26
|
+
trigger.openMenu();
|
|
27
|
+
};
|
|
19
28
|
this._handleTriggerOpen = (e) => {
|
|
20
29
|
this._openTrigger = e.target;
|
|
21
30
|
};
|
|
@@ -53,6 +62,7 @@ let MenuBar = class MenuBar extends LitElement {
|
|
|
53
62
|
this.addEventListener(`focusin`, this._handleFocusIn);
|
|
54
63
|
this.addEventListener(`keydown`, this._handleKeydown);
|
|
55
64
|
this.addEventListener(`pointermove`, this._handlePointerMove);
|
|
65
|
+
this.addEventListener(`pointerover`, this._handlePointerOver);
|
|
56
66
|
}
|
|
57
67
|
disconnectedCallback() {
|
|
58
68
|
super.disconnectedCallback();
|
|
@@ -61,6 +71,7 @@ let MenuBar = class MenuBar extends LitElement {
|
|
|
61
71
|
this.removeEventListener(`focusin`, this._handleFocusIn);
|
|
62
72
|
this.removeEventListener(`keydown`, this._handleKeydown);
|
|
63
73
|
this.removeEventListener(`pointermove`, this._handlePointerMove);
|
|
74
|
+
this.removeEventListener(`pointerover`, this._handlePointerOver);
|
|
64
75
|
}
|
|
65
76
|
_setInputMode(mode) {
|
|
66
77
|
if (this._inputMode === mode) return;
|
|
@@ -75,6 +86,8 @@ let MenuBar = class MenuBar extends LitElement {
|
|
|
75
86
|
:host {
|
|
76
87
|
display: flex;
|
|
77
88
|
align-items: center;
|
|
89
|
+
gap: var(--menu-bar-gap, var(--space-xs));
|
|
90
|
+
padding: var(--menu-bar-padding, var(--space-xs));
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
/* Keyboard mode: suppress hover highlight on trigger buttons */
|
|
@@ -385,6 +398,7 @@ let MenuSubTrigger = class MenuSubTrigger extends LitElement {
|
|
|
385
398
|
}
|
|
386
399
|
_handleKeydown(e) {
|
|
387
400
|
if (this.disabled) return;
|
|
401
|
+
if (e.target !== this !== (e.key === `ArrowLeft` || e.key === `Escape`)) return;
|
|
388
402
|
switch (e.key) {
|
|
389
403
|
case `ArrowRight`:
|
|
390
404
|
case `Enter`:
|
|
@@ -408,6 +422,7 @@ let MenuSubTrigger = class MenuSubTrigger extends LitElement {
|
|
|
408
422
|
e.preventDefault();
|
|
409
423
|
e.stopPropagation();
|
|
410
424
|
this._hideSubmenu();
|
|
425
|
+
this._itemEl?.focus();
|
|
411
426
|
}
|
|
412
427
|
}
|
|
413
428
|
static {
|