@ixfx/components 0.15.5 → 0.16.1
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 +986 -49
- package/bundle/index.js +765 -729
- package/dist/ac-token.js +1 -1
- package/dist/crumb-navigation-BbqfmkYs.d.ts +147 -0
- package/dist/crumb-navigation-YnFVFqrx.js +1290 -0
- package/dist/crumbs.d.ts +2 -115
- package/dist/crumbs.js +1 -1179
- package/dist/{data-grid-C1sZX6n9.js → data-grid-Cqxuwgbb.js} +615 -210
- package/dist/{data-grid-D7qzmMMp.d.ts → data-grid-Csh5EJW9.d.ts} +55 -4
- package/dist/data-grid.d.ts +3 -3
- package/dist/data-grid.js +1 -1
- package/dist/{detail-list-q8bs67BM.js → detail-list-Bo5ze3SN.js} +234 -9
- package/dist/{detail-list-CKIKlszv.d.ts → detail-list-DSZ1DDor.d.ts} +48 -4
- package/dist/detail-list.d.ts +4 -4
- package/dist/detail-list.js +1 -1
- package/dist/{flat-list-DXMyDiSH.d.ts → flat-list-CT9kX3TQ.d.ts} +27 -4
- package/dist/flat-list.d.ts +2 -2
- package/dist/flat-list.js +123 -4
- package/dist/{grid-list-y6dWJYu3.js → grid-list-D0dJNVEF.js} +278 -76
- package/dist/{grid-list-0e7snW6t.d.ts → grid-list-DDLrWRxm.d.ts} +128 -22
- package/dist/grid-list.d.ts +3 -3
- package/dist/grid-list.js +1 -1
- package/dist/{incr-search-JIhwB49y.js → incr-search-CgbL-OlS.js} +140 -10
- package/dist/incr-search.d.ts +1 -1
- package/dist/incr-search.js +1 -1
- package/dist/index-9ttcdwlL.d.ts +71 -0
- package/dist/index-Bnm-kKGC.d.ts +475 -0
- package/dist/{index-KN2MW_xD.d.ts → index-CUErGhCg.d.ts} +31 -4
- package/dist/{index-DEVyVIn8.d.ts → index-D-lZNlHa.d.ts} +52 -6
- package/dist/index.d.ts +341 -21
- package/dist/index.js +2286 -884
- package/dist/{list-native-drag-controller-O2Nruqpi.d.ts → list-native-drag-controller-D_UrXipS.d.ts} +1 -1
- package/dist/{list-selection-controller-BeE70lbB.js → list-selection-controller-5L0Nj9tR.js} +87 -2
- package/dist/miller-DUqiUAWQ.js +2230 -0
- package/dist/miller.d.ts +4 -437
- package/dist/miller.js +1 -1815
- package/dist/narrowed-text.js +1 -1
- package/dist/pending-BGidHppL.js +91 -0
- package/dist/scroll-hover-guard-Cjw6179Q.js +35 -0
- package/dist/{split-layout-JK3xWVX2.d.ts → split-layout-CophkNeU.d.ts} +41 -3
- package/dist/{split-layout-Deoxy47d.js → split-layout-DTY4PJRp.js} +251 -19
- package/dist/split-layout.d.ts +1 -1
- package/dist/split-layout.js +1 -1
- package/dist/sunburst-tree.d.ts +2 -2
- package/dist/sunburst-tree.js +1 -1
- package/dist/{sunbursttree-list-yXwS-uEI.js → sunbursttree-list-B_M0YTEA.js} +299 -80
- package/dist/{sunbursttree-list--Z0uyh8O.d.ts → sunbursttree-list-DePuFYlU.d.ts} +40 -3
- package/dist/tree-CftHwtEb.js +146 -0
- package/dist/{tree-D8VxvR9u.js → tree-DAlgvaHB.js} +276 -48
- package/dist/{tree-component-C6UHO-w9.d.ts → tree-component-DgfkxmVY.d.ts} +19 -8
- package/dist/tree.d.ts +3 -3
- package/dist/tree.js +1 -1
- package/dist/types-LU1u0CB8.d.ts +87 -0
- package/dist/vertical-list-DyKPKGxK.js +1993 -0
- package/dist/{vertical-list-D8MksoTp.d.ts → vertical-list-SAx56U-l.d.ts} +31 -3
- package/dist/vertical-list.d.ts +3 -3
- package/dist/vertical-list.js +1 -1789
- package/dist/view-state-controller-BdyXWUUM.js +305 -0
- package/docs-user/README.md +2 -0
- package/docs-user/crumbs.md +27 -0
- package/docs-user/data-grid.md +79 -5
- package/docs-user/detail-list.md +69 -0
- package/docs-user/dock.md +63 -12
- package/docs-user/flat-list.md +27 -0
- package/docs-user/grid-list.md +69 -13
- package/docs-user/hyperbolic-tree.md +31 -4
- package/docs-user/icicle-tree.md +36 -0
- package/docs-user/index.json +12 -2
- package/docs-user/llms.txt +2 -0
- package/docs-user/miller.md +52 -21
- package/docs-user/multicol-list.md +50 -8
- package/docs-user/split-layout.md +45 -17
- package/docs-user/sunburst-tree.md +32 -2
- package/docs-user/tree.md +45 -6
- package/docs-user/treemap.md +32 -0
- package/docs-user/twosplit-layout.md +22 -0
- package/docs-user/user-catalog.md +1 -1
- package/docs-user/vertical-list.md +55 -7
- package/llms.txt +2 -0
- package/package.json +2 -2
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { t as TickledController } from "./tickled-controller-D-HZymaZ.js";
|
|
2
2
|
import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
|
|
3
3
|
import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
|
|
4
|
-
import { i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, t as createTreeListSearchAdapter } from "./incr-search-
|
|
4
|
+
import { i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, t as createTreeListSearchAdapter } from "./incr-search-CgbL-OlS.js";
|
|
5
|
+
import { a as capKeys, l as keysToArray, n as EMPTY_KEYS } from "./pending-BGidHppL.js";
|
|
5
6
|
import { t as collectDataResult } from "./data-provider-35TyWclX.js";
|
|
7
|
+
import { t as ViewStateController } from "./view-state-controller-BdyXWUUM.js";
|
|
8
|
+
import { a as resolveKeyPath } from "./tree-CftHwtEb.js";
|
|
6
9
|
import { LitElement, css, html, nothing, svg } from "lit";
|
|
7
10
|
import { property, state } from "lit/decorators.js";
|
|
8
11
|
import { createCommandRegistry } from "@clinth/ui-commands";
|
|
@@ -313,6 +316,26 @@ function targetHues(layout, view, zoomId) {
|
|
|
313
316
|
function siblingShade(index, setSize) {
|
|
314
317
|
return setSize <= 1 ? 0 : (index + .5) / setSize - .5;
|
|
315
318
|
}
|
|
319
|
+
/** Settled visual for every arc to draw in `view`: the centre plus `maxDepth` rings. */
|
|
320
|
+
function computeTargets(input) {
|
|
321
|
+
const { layout, view, rings, zoomId, maxDepth } = input;
|
|
322
|
+
const targets = /* @__PURE__ */ new Map();
|
|
323
|
+
if (zoomId === void 0) return targets;
|
|
324
|
+
const hues = targetHues(layout, view, zoomId);
|
|
325
|
+
for (const entry of layout.values()) {
|
|
326
|
+
const p = projectArc(entry, view);
|
|
327
|
+
if (p.depth < -1 || p.depth >= maxDepth) continue;
|
|
328
|
+
if (p.depth === -1 ? entry.id !== zoomId : !hues.has(entry.id)) continue;
|
|
329
|
+
if (p.depth >= 0 && arcLength(p.a0, p.a1, arcRadii(p.depth, rings).outer) < input.minArcLength) continue;
|
|
330
|
+
targets.set(entry.id, {
|
|
331
|
+
...p,
|
|
332
|
+
hue: hues.get(entry.id) ?? input.displayed.get(entry.id)?.hue ?? 0,
|
|
333
|
+
shade: siblingShade(entry.index, entry.setSize),
|
|
334
|
+
opacity: 1
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return targets;
|
|
338
|
+
}
|
|
316
339
|
//#endregion
|
|
317
340
|
//#region src/sunburst-tree/keyboard.ts
|
|
318
341
|
/**
|
|
@@ -343,6 +366,45 @@ function sunburstKeyTarget(arcs, currentId, key) {
|
|
|
343
366
|
}
|
|
344
367
|
}
|
|
345
368
|
//#endregion
|
|
369
|
+
//#region src/sunburst-tree/render.ts
|
|
370
|
+
function renderSunburstLabels(input) {
|
|
371
|
+
const labels = [];
|
|
372
|
+
const guides = [];
|
|
373
|
+
for (const [id, visual] of input.displayed) {
|
|
374
|
+
const entry = input.layout.get(id);
|
|
375
|
+
if (!entry || visual.opacity < .05) continue;
|
|
376
|
+
const place = labelPlacement(visual, entry.node.item.label, input.rings, input.fontSize, { curved: input.curved });
|
|
377
|
+
if (!place) continue;
|
|
378
|
+
const style = `--arc-hue:${visual.hue.toFixed(2)};opacity:${visual.opacity.toFixed(3)}`;
|
|
379
|
+
if (place.kind === `curved`) {
|
|
380
|
+
const guideId = `${input.idPrefix}${guides.length}`;
|
|
381
|
+
guides.push(svg`<path id=${guideId} d=${place.path}></path>`);
|
|
382
|
+
labels.push(svg`<text class="sunburst-label curved" part="label" style=${style}
|
|
383
|
+
><textPath href="#${guideId}" startOffset="50%">${place.text}</textPath></text>`);
|
|
384
|
+
} else labels.push(svg`<text
|
|
385
|
+
class="sunburst-label"
|
|
386
|
+
part="label"
|
|
387
|
+
transform="translate(${place.x.toFixed(2)} ${place.y.toFixed(2)}) rotate(${place.rotate.toFixed(2)})"
|
|
388
|
+
style=${style}
|
|
389
|
+
>${place.text}</text>`);
|
|
390
|
+
}
|
|
391
|
+
return svg`<defs>${guides}</defs>${labels}`;
|
|
392
|
+
}
|
|
393
|
+
/** Label, value and share of the centre (`zoom`), or of the tickled arc when there is one. */
|
|
394
|
+
function renderSunburstReadout(zoom, tickled, rings, fontSize, formatter) {
|
|
395
|
+
if (!zoom) return nothing;
|
|
396
|
+
const shown = tickled ?? zoom;
|
|
397
|
+
const maxChars = Math.max(4, Math.floor(rings.center * 1.7 / (fontSize * .6)));
|
|
398
|
+
const label = shown.node.item.label;
|
|
399
|
+
const text = label.length <= maxChars ? label : `${label.slice(0, maxChars - 1)}…`;
|
|
400
|
+
const share = tickled && zoom.value > 0 ? ` · ${(tickled.value / zoom.value * 100).toFixed(tickled.value / zoom.value < .1 ? 1 : 0)}%` : ``;
|
|
401
|
+
const line = fontSize * 1.3;
|
|
402
|
+
return svg`<g class="center-readout" part="center-readout" aria-hidden="true">
|
|
403
|
+
<text class="center-label" y=${(-line / 2).toFixed(2)}>${text}</text>
|
|
404
|
+
<text class="center-meta" y=${(line / 2).toFixed(2)}>${formatter(shown.value, shown.node)}${share}</text>
|
|
405
|
+
</g>`;
|
|
406
|
+
}
|
|
407
|
+
//#endregion
|
|
346
408
|
//#region src/sunburst-tree/styles.ts
|
|
347
409
|
/**
|
|
348
410
|
* Shadow-DOM styles for `ixfx-sunbursttree-list`.
|
|
@@ -467,6 +529,98 @@ const sunburstTreeStyles = css`
|
|
|
467
529
|
}
|
|
468
530
|
`;
|
|
469
531
|
//#endregion
|
|
532
|
+
//#region src/sunburst-tree/sunburst-view-state.ts
|
|
533
|
+
/** Index the loaded tree by id, node and key. */
|
|
534
|
+
function indexSunburstTree(root) {
|
|
535
|
+
const byId = /* @__PURE__ */ new Map();
|
|
536
|
+
const idOf = /* @__PURE__ */ new Map();
|
|
537
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
538
|
+
const visit = (node, id) => {
|
|
539
|
+
byId.set(id, node);
|
|
540
|
+
idOf.set(node, id);
|
|
541
|
+
if (!byKey.has(node.item.key)) byKey.set(node.item.key, node);
|
|
542
|
+
for (const child of node.children ?? []) visit(child, sunburstChildId(id, child.item.key));
|
|
543
|
+
};
|
|
544
|
+
if (root) visit(root, root.item.key);
|
|
545
|
+
return {
|
|
546
|
+
byId,
|
|
547
|
+
idOf,
|
|
548
|
+
byKey
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* As `reconcileSelection` (tree view state), but live nodes are re-found by
|
|
553
|
+
* key path rather than by bare key:
|
|
554
|
+
* - selected nodes still in the tree stay; replaced ones (immutable rebuild)
|
|
555
|
+
* swap for the node now at their old path; ones whose path has gone go
|
|
556
|
+
* pending (their `item.key`)
|
|
557
|
+
* - pending keys that resolve (and pass `canSelect`) are selected
|
|
558
|
+
*/
|
|
559
|
+
function reconcileSunburstSelection(input) {
|
|
560
|
+
const { selected, pending, primary } = input;
|
|
561
|
+
const unchanged = {
|
|
562
|
+
selected,
|
|
563
|
+
pending,
|
|
564
|
+
primary,
|
|
565
|
+
restored: false,
|
|
566
|
+
changed: false
|
|
567
|
+
};
|
|
568
|
+
if (selected.size === 0 && pending.size === 0) return unchanged;
|
|
569
|
+
const previousIds = /* @__PURE__ */ new Map();
|
|
570
|
+
for (const entry of input.previousLayout.values()) previousIds.set(entry.node, entry.id);
|
|
571
|
+
if (pending.size === 0 && [...selected].every((node) => {
|
|
572
|
+
const id = previousIds.get(node);
|
|
573
|
+
return id !== void 0 && findSunburstNodeById(input.root, id) === node;
|
|
574
|
+
})) return unchanged;
|
|
575
|
+
const { byId, idOf, byKey } = indexSunburstTree(input.root);
|
|
576
|
+
const locate = (node) => {
|
|
577
|
+
if (idOf.has(node)) return node;
|
|
578
|
+
const id = previousIds.get(node);
|
|
579
|
+
return id === void 0 ? void 0 : byId.get(id);
|
|
580
|
+
};
|
|
581
|
+
const next = /* @__PURE__ */ new Set();
|
|
582
|
+
const nextPending = /* @__PURE__ */ new Set();
|
|
583
|
+
let changed = false;
|
|
584
|
+
for (const node of selected) {
|
|
585
|
+
const current = locate(node);
|
|
586
|
+
if (current) {
|
|
587
|
+
next.add(current);
|
|
588
|
+
changed ||= current !== node;
|
|
589
|
+
} else {
|
|
590
|
+
nextPending.add(node.item.key);
|
|
591
|
+
changed = true;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
let restored;
|
|
595
|
+
for (const key of pending) {
|
|
596
|
+
const node = byKey.get(key);
|
|
597
|
+
if (node && input.canSelect(node)) {
|
|
598
|
+
next.add(node);
|
|
599
|
+
restored = node;
|
|
600
|
+
changed = true;
|
|
601
|
+
} else nextPending.add(key);
|
|
602
|
+
}
|
|
603
|
+
if (!changed) return unchanged;
|
|
604
|
+
if (input.single && next.size > 0) {
|
|
605
|
+
const only = restored ?? [...next].at(-1);
|
|
606
|
+
return {
|
|
607
|
+
selected: /* @__PURE__ */ new Set([only]),
|
|
608
|
+
pending: EMPTY_KEYS,
|
|
609
|
+
primary: only,
|
|
610
|
+
restored: restored !== void 0,
|
|
611
|
+
changed: true
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
const located = primary && locate(primary);
|
|
615
|
+
return {
|
|
616
|
+
selected: next,
|
|
617
|
+
pending: capKeys(nextPending),
|
|
618
|
+
primary: located && next.has(located) ? located : [...next].at(-1),
|
|
619
|
+
restored: restored !== void 0,
|
|
620
|
+
changed: true
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
//#endregion
|
|
470
624
|
//#region src/sunburst-tree/tween.ts
|
|
471
625
|
function easeInOutCubic(t) {
|
|
472
626
|
return t < .5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
|
|
@@ -565,6 +719,12 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
565
719
|
this.disableKeyboardNavigation = false;
|
|
566
720
|
this.registry = void 0;
|
|
567
721
|
this._zoomId = void 0;
|
|
722
|
+
this.restoreLoads = true;
|
|
723
|
+
this.#viewState = new ViewStateController(this, {
|
|
724
|
+
kind: `sunbursttree-list`,
|
|
725
|
+
capture: () => this.getViewState()
|
|
726
|
+
});
|
|
727
|
+
this.#pendingSelected = EMPTY_KEYS;
|
|
568
728
|
this._size = {
|
|
569
729
|
width: 0,
|
|
570
730
|
height: 0
|
|
@@ -648,6 +808,12 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
648
808
|
if (this._zoomId === void 0) return void 0;
|
|
649
809
|
return findSunburstNodeById(this.#effectiveRoot, this._zoomId);
|
|
650
810
|
}
|
|
811
|
+
#viewState;
|
|
812
|
+
/** Saved selection for keys not (yet) loaded. */
|
|
813
|
+
#pendingSelected;
|
|
814
|
+
/** Saved zoom path not (yet) resolvable. */
|
|
815
|
+
#pendingZoom;
|
|
816
|
+
#pendingFocus;
|
|
651
817
|
#layout;
|
|
652
818
|
#view;
|
|
653
819
|
#rings;
|
|
@@ -759,14 +925,16 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
759
925
|
this.#unregisterSearchCommands = registerListSearchCommands(this.#ownedRegistry, this.search);
|
|
760
926
|
}
|
|
761
927
|
willUpdate(changed) {
|
|
762
|
-
if (changed.has(`root`) && changed.get(`root`) !== void 0) this.#
|
|
928
|
+
if (changed.has(`root`) && changed.get(`root`) !== void 0) this.#anchorIndex = void 0;
|
|
763
929
|
if (changed.has(`interactionMode`) && changed.get(`interactionMode`) !== void 0) {
|
|
764
930
|
this.#anchorIndex = void 0;
|
|
765
931
|
this.#clearSelectionInternal();
|
|
766
932
|
}
|
|
767
933
|
if (this._layoutDirty) {
|
|
768
934
|
this._layoutDirty = false;
|
|
935
|
+
this.#reconcileViewState();
|
|
769
936
|
this.#retarget();
|
|
937
|
+
this.#viewState.restoring(() => this.#reconcileFocus());
|
|
770
938
|
}
|
|
771
939
|
}
|
|
772
940
|
updated(changed) {
|
|
@@ -776,6 +944,7 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
776
944
|
}
|
|
777
945
|
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
778
946
|
this.#ensureVisibleChildren();
|
|
947
|
+
this.#continueRestoreLoads();
|
|
779
948
|
}
|
|
780
949
|
#outerRadius() {
|
|
781
950
|
const width = this._size.width || FALLBACK_SIZE;
|
|
@@ -797,7 +966,15 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
797
966
|
const zoomId = this.#zoomEntryId;
|
|
798
967
|
const view = sunburstViewFor(zoomId === void 0 ? void 0 : layout.get(zoomId));
|
|
799
968
|
const rings = computeRings(this.#outerRadius(), maxDepth, this.centerRatio);
|
|
800
|
-
const targets =
|
|
969
|
+
const targets = computeTargets({
|
|
970
|
+
layout,
|
|
971
|
+
view,
|
|
972
|
+
rings,
|
|
973
|
+
zoomId,
|
|
974
|
+
maxDepth,
|
|
975
|
+
minArcLength: this.minArcLength,
|
|
976
|
+
displayed: this.#displayed
|
|
977
|
+
});
|
|
801
978
|
this.#layout = layout;
|
|
802
979
|
this.#view = view;
|
|
803
980
|
this.#rings = rings;
|
|
@@ -839,24 +1016,6 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
839
1016
|
});
|
|
840
1017
|
this.#startTransition(transition);
|
|
841
1018
|
}
|
|
842
|
-
#computeTargets(layout, view, rings, zoomId, maxDepth) {
|
|
843
|
-
const targets = /* @__PURE__ */ new Map();
|
|
844
|
-
if (zoomId === void 0) return targets;
|
|
845
|
-
const hues = targetHues(layout, view, zoomId);
|
|
846
|
-
for (const entry of layout.values()) {
|
|
847
|
-
const p = projectArc(entry, view);
|
|
848
|
-
if (p.depth < -1 || p.depth >= maxDepth) continue;
|
|
849
|
-
if (p.depth === -1 ? entry.id !== zoomId : !hues.has(entry.id)) continue;
|
|
850
|
-
if (p.depth >= 0 && arcLength(p.a0, p.a1, arcRadii(p.depth, rings).outer) < this.minArcLength) continue;
|
|
851
|
-
targets.set(entry.id, {
|
|
852
|
-
...p,
|
|
853
|
-
hue: hues.get(entry.id) ?? this.#displayed.get(entry.id)?.hue ?? 0,
|
|
854
|
-
shade: siblingShade(entry.index, entry.setSize),
|
|
855
|
-
opacity: 1
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
return targets;
|
|
859
|
-
}
|
|
860
1019
|
#startTransition(transition) {
|
|
861
1020
|
if ((prefersReducedMotion() ? 0 : this.animationDuration) <= 0 || !this.isConnected) {
|
|
862
1021
|
this.#transition = void 0;
|
|
@@ -870,31 +1029,116 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
870
1029
|
this.#raf ??= requestAnimationFrame(this.#tick);
|
|
871
1030
|
}
|
|
872
1031
|
#tick;
|
|
873
|
-
/**
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1032
|
+
/**
|
|
1033
|
+
* Snapshot selection, zoom path and keyboard cursor as plain JSON, keyed by
|
|
1034
|
+
* `item.key`. Includes pending state for keys not currently loaded.
|
|
1035
|
+
*/
|
|
1036
|
+
getViewState(options) {
|
|
1037
|
+
const withPending = options?.includePending ?? true;
|
|
1038
|
+
const tickled = this.tickled.inputMode === `keyboard` ? this.tickled.tickledId : void 0;
|
|
1039
|
+
const focus = tickled === void 0 ? void 0 : sunburstIdToPath(tickled).at(-1);
|
|
1040
|
+
return this.#viewState.shape({
|
|
1041
|
+
selected: keysToArray([...this._selectedNodes].map((n) => n.item.key), withPending ? this.#pendingSelected : []),
|
|
1042
|
+
zoom: (withPending ? this.#pendingZoom : void 0) ?? this.#zoomPath(),
|
|
1043
|
+
focus: focus ?? (withPending ? this.#pendingFocus : void 0)
|
|
1044
|
+
}, options);
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Apply saved view state. Fields present replace the current ones; keys
|
|
1048
|
+
* that aren't loaded yet are applied as they appear — including via
|
|
1049
|
+
* `loadChildren` / `model` down a saved zoom path when `restoreLoads`.
|
|
1050
|
+
*/
|
|
1051
|
+
setViewState(state) {
|
|
1052
|
+
if (!this.#viewState.accepts(state)) return;
|
|
1053
|
+
this.#viewState.restoring(() => {
|
|
1054
|
+
if (state.selected) {
|
|
1055
|
+
this.#pendingSelected = capKeys(new Set(state.selected));
|
|
1056
|
+
if (this._selectedNodes.size > 0) this.#commitSelection(/* @__PURE__ */ new Set());
|
|
1057
|
+
}
|
|
1058
|
+
if (state.zoom) this.#pendingZoom = [...state.zoom];
|
|
1059
|
+
if (state.focus !== void 0) this.#pendingFocus = state.focus;
|
|
1060
|
+
this.#reconcileViewState();
|
|
1061
|
+
if (this.#pendingZoom !== void 0) this.#setZoom(void 0);
|
|
1062
|
+
});
|
|
1063
|
+
this.requestUpdate();
|
|
1064
|
+
}
|
|
1065
|
+
clearPendingViewState() {
|
|
1066
|
+
this.#pendingSelected = EMPTY_KEYS;
|
|
1067
|
+
this.#pendingZoom = void 0;
|
|
1068
|
+
this.#pendingFocus = void 0;
|
|
1069
|
+
}
|
|
1070
|
+
#zoomPath() {
|
|
1071
|
+
return this._zoomId === void 0 ? [] : sunburstIdToPath(this._zoomId).slice(1);
|
|
1072
|
+
}
|
|
1073
|
+
/** Move zoom / selection whose path has gone to pending; apply pending state now resolvable. */
|
|
1074
|
+
#reconcileViewState() {
|
|
878
1075
|
const root = this.#effectiveRoot;
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1076
|
+
this.#viewState.restoring(() => {
|
|
1077
|
+
if (this._zoomId !== void 0 && !findSunburstNodeById(root, this._zoomId)) {
|
|
1078
|
+
this.#pendingZoom ??= this.#zoomPath();
|
|
1079
|
+
this._zoomId = void 0;
|
|
1080
|
+
this.#viewState.notify(`data`);
|
|
1081
|
+
}
|
|
1082
|
+
const zoom = this.#pendingZoom;
|
|
1083
|
+
if (root && zoom !== void 0 && resolveKeyPath(root, zoom, () => false).status === `found`) {
|
|
1084
|
+
this.#pendingZoom = void 0;
|
|
1085
|
+
this.#setZoom(sunburstPathToId([root.item.key, ...zoom]));
|
|
1086
|
+
}
|
|
1087
|
+
if (this._selectedNodes.size > 0 || this.#pendingSelected.size > 0) this.#reconcileSelection(root);
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
#reconcileSelection(root) {
|
|
1091
|
+
const sel = reconcileSunburstSelection({
|
|
1092
|
+
root,
|
|
1093
|
+
previousLayout: this.#layout,
|
|
1094
|
+
selected: this._selectedNodes,
|
|
1095
|
+
primary: this.selectedNode,
|
|
1096
|
+
pending: this.#pendingSelected,
|
|
1097
|
+
canSelect: (node) => this.#canSelect(node),
|
|
1098
|
+
single: this.interactionMode === `implicit`
|
|
1099
|
+
});
|
|
1100
|
+
if (!sel.changed) return;
|
|
1101
|
+
this.#pendingSelected = sel.pending;
|
|
1102
|
+
if (sel.restored) this.#commitSelection(new Set(sel.selected), sel.primary);
|
|
1103
|
+
else {
|
|
1104
|
+
this._selectedNodes = new Set(sel.selected);
|
|
1105
|
+
this.selectedNode = sel.primary;
|
|
1106
|
+
this.#viewState.notify(`data`);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
/** Runs after layout: a cursor whose arc has gone goes pending; a pending one lands once its arc is drawn. */
|
|
1110
|
+
#reconcileFocus() {
|
|
1111
|
+
const tickled = this.tickled.tickledId;
|
|
1112
|
+
if (tickled !== void 0 && !this.#layout.has(tickled)) {
|
|
1113
|
+
if (this.tickled.inputMode === `keyboard`) this.#pendingFocus ??= sunburstIdToPath(tickled).at(-1);
|
|
1114
|
+
this.tickled.clearTickled();
|
|
1115
|
+
}
|
|
1116
|
+
const focus = this.#pendingFocus;
|
|
1117
|
+
const arc = focus === void 0 ? void 0 : this.#visibleArcs().find((a) => a.node.item.key === focus);
|
|
1118
|
+
if (!arc) return;
|
|
1119
|
+
this.#pendingFocus = void 0;
|
|
1120
|
+
this.tickled.setInputMode(`keyboard`);
|
|
1121
|
+
this.tickled.setTickled(arc.id);
|
|
1122
|
+
}
|
|
1123
|
+
/** Load the next unloaded node along a pending zoom path (one load at a time). */
|
|
1124
|
+
#continueRestoreLoads() {
|
|
1125
|
+
const root = this.#effectiveRoot;
|
|
1126
|
+
const zoom = this.#pendingZoom;
|
|
1127
|
+
if (!this.restoreLoads || !root || zoom === void 0 || !this.model && !this.loadChildren) return;
|
|
1128
|
+
const next = resolveKeyPath(root, zoom, (node) => node.item.isLeaf !== true);
|
|
1129
|
+
if (next.status !== `load`) return;
|
|
1130
|
+
const id = sunburstPathToId([root.item.key, ...zoom.slice(0, next.depth + 1)]);
|
|
1131
|
+
if (!this.#pendingIds.has(id)) this.#loadChildren(id, next.node, next.depth);
|
|
886
1132
|
}
|
|
887
1133
|
#ensureVisibleChildren() {
|
|
888
1134
|
if (!this.model && !this.loadChildren) return;
|
|
889
1135
|
for (const id of this.#targets.keys()) {
|
|
890
1136
|
const entry = this.#layout.get(id);
|
|
891
1137
|
if (!entry || entry.node.children !== void 0 || entry.node.item.isLeaf === true || this.#pendingIds.has(id)) continue;
|
|
892
|
-
this.#loadChildren(entry);
|
|
1138
|
+
this.#loadChildren(id, entry.node, entry.depth - 1);
|
|
893
1139
|
}
|
|
894
1140
|
}
|
|
895
|
-
async #loadChildren(
|
|
896
|
-
const { id, node } = entry;
|
|
897
|
-
const depth = entry.depth - 1;
|
|
1141
|
+
async #loadChildren(id, node, depth) {
|
|
898
1142
|
this.#pendingIds.add(id);
|
|
899
1143
|
const controller = new AbortController();
|
|
900
1144
|
this.#aborters.set(id, controller);
|
|
@@ -928,6 +1172,8 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
928
1172
|
const previous = this._selectedNodes;
|
|
929
1173
|
this._selectedNodes = next;
|
|
930
1174
|
this.selectedNode = primary;
|
|
1175
|
+
if (!this.#viewState.isRestoring) this.#pendingSelected = EMPTY_KEYS;
|
|
1176
|
+
this.#viewState.notify();
|
|
931
1177
|
this.dispatchEvent(new CustomEvent(`select`, {
|
|
932
1178
|
detail: {
|
|
933
1179
|
selected: new Set(next),
|
|
@@ -947,6 +1193,7 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
947
1193
|
this.#commitSelection(next, this.selectedNode === node ? [...next].at(-1) : this.selectedNode);
|
|
948
1194
|
}
|
|
949
1195
|
clearSelection() {
|
|
1196
|
+
this.#pendingSelected = EMPTY_KEYS;
|
|
950
1197
|
if (this._selectedNodes.size > 0) this.#commitSelection(/* @__PURE__ */ new Set());
|
|
951
1198
|
}
|
|
952
1199
|
selectAll() {
|
|
@@ -963,12 +1210,14 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
963
1210
|
return findSunburstId(this.#effectiveRoot, (n) => n === node);
|
|
964
1211
|
}
|
|
965
1212
|
#setZoom(id) {
|
|
1213
|
+
if (!this.#viewState.isRestoring) this.#pendingZoom = void 0;
|
|
966
1214
|
const zoomId = id === this.#effectiveRoot?.item.key ? void 0 : id;
|
|
967
1215
|
if (zoomId === this._zoomId) return;
|
|
968
1216
|
const previousId = this._zoomId;
|
|
969
1217
|
const previous = this.focusedNode;
|
|
970
1218
|
this._zoomId = zoomId;
|
|
971
1219
|
this.tickled.clearTickled();
|
|
1220
|
+
this.#viewState.notify();
|
|
972
1221
|
const node = this.focusedNode;
|
|
973
1222
|
const depthOf = (key) => key === void 0 ? -1 : sunburstIdToPath(key).length - 2;
|
|
974
1223
|
this.dispatchEvent(new CustomEvent(`zoom`, {
|
|
@@ -1151,6 +1400,8 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
1151
1400
|
event.preventDefault();
|
|
1152
1401
|
this.tickled.setInputMode(`keyboard`);
|
|
1153
1402
|
this.tickled.setTickled(target);
|
|
1403
|
+
this.#pendingFocus = void 0;
|
|
1404
|
+
this.#viewState.notify();
|
|
1154
1405
|
const entry = this.#layout.get(target);
|
|
1155
1406
|
if (entry && this.interactionMode === `implicit`) this.select(entry.node);
|
|
1156
1407
|
}
|
|
@@ -1202,53 +1453,13 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
1202
1453
|
@dblclick=${() => this.#handleArcDblClick(entry)}
|
|
1203
1454
|
@pointerenter=${() => this.tickled.handlePointerEnter(id)}
|
|
1204
1455
|
></path>`;
|
|
1205
|
-
}
|
|
1206
|
-
#renderLabels() {
|
|
1207
|
-
if (this.disableLabels) return nothing;
|
|
1208
|
-
const labels = [];
|
|
1209
|
-
const guides = [];
|
|
1210
|
-
for (const [id, visual] of this.#displayed) {
|
|
1211
|
-
const entry = this.#layout.get(id);
|
|
1212
|
-
if (!entry || visual.opacity < .05) continue;
|
|
1213
|
-
const place = labelPlacement(visual, entry.node.item.label, this.#rings, this.#fontSize, { curved: this.curvedLabels });
|
|
1214
|
-
if (!place) continue;
|
|
1215
|
-
const style = `--arc-hue:${visual.hue.toFixed(2)};opacity:${visual.opacity.toFixed(3)}`;
|
|
1216
|
-
if (place.kind === `curved`) {
|
|
1217
|
-
const guideId = `${this.#labelIdPrefix}${guides.length}`;
|
|
1218
|
-
guides.push(svg`<path id=${guideId} d=${place.path}></path>`);
|
|
1219
|
-
labels.push(svg`<text class="sunburst-label curved" part="label" style=${style}
|
|
1220
|
-
><textPath href="#${guideId}" startOffset="50%">${place.text}</textPath></text>`);
|
|
1221
|
-
} else labels.push(svg`<text
|
|
1222
|
-
class="sunburst-label"
|
|
1223
|
-
part="label"
|
|
1224
|
-
transform="translate(${place.x.toFixed(2)} ${place.y.toFixed(2)}) rotate(${place.rotate.toFixed(2)})"
|
|
1225
|
-
style=${style}
|
|
1226
|
-
>${place.text}</text>`);
|
|
1227
|
-
}
|
|
1228
|
-
return svg`<defs>${guides}</defs>${labels}`;
|
|
1229
|
-
}
|
|
1230
|
-
/** Label, value and share of the centre (or of the tickled arc). */
|
|
1231
|
-
#renderReadout(zoomId) {
|
|
1232
|
-
const zoom = zoomId === void 0 ? void 0 : this.#layout.get(zoomId);
|
|
1233
|
-
if (!zoom) return nothing;
|
|
1234
|
-
const tickledId = this.tickled.tickledId;
|
|
1235
|
-
const tickled = tickledId === void 0 ? void 0 : this.#layout.get(tickledId);
|
|
1236
|
-
const shown = tickled ?? zoom;
|
|
1237
|
-
const maxChars = Math.max(4, Math.floor(this.#rings.center * 1.7 / (this.#fontSize * .6)));
|
|
1238
|
-
const label = shown.node.item.label;
|
|
1239
|
-
const text = label.length <= maxChars ? label : `${label.slice(0, maxChars - 1)}…`;
|
|
1240
|
-
const share = tickled && zoom.value > 0 ? ` · ${(tickled.value / zoom.value * 100).toFixed(tickled.value / zoom.value < .1 ? 1 : 0)}%` : ``;
|
|
1241
|
-
const line = this.#fontSize * 1.3;
|
|
1242
|
-
return svg`<g class="center-readout" part="center-readout" aria-hidden="true">
|
|
1243
|
-
<text class="center-label" y=${(-line / 2).toFixed(2)}>${text}</text>
|
|
1244
|
-
<text class="center-meta" y=${(line / 2).toFixed(2)}>${this.valueFormatter(shown.value, shown.node)}${share}</text>
|
|
1245
|
-
</g>`;
|
|
1246
1456
|
}
|
|
1247
1457
|
render() {
|
|
1248
1458
|
if (!this.#effectiveRoot) return html`<div class="empty-state">No data</div>${this.search.renderField()}`;
|
|
1249
1459
|
const width = this._size.width || FALLBACK_SIZE;
|
|
1250
1460
|
const height = this._size.height || FALLBACK_SIZE;
|
|
1251
1461
|
const zoomId = this.#zoomEntryId;
|
|
1462
|
+
const tickledId = this.tickled.tickledId;
|
|
1252
1463
|
return html`
|
|
1253
1464
|
<svg
|
|
1254
1465
|
class="sunburst"
|
|
@@ -1261,8 +1472,15 @@ let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
|
1261
1472
|
@pointerleave=${() => this.tickled.clearTickled()}
|
|
1262
1473
|
>
|
|
1263
1474
|
<g class="arcs">${repeat(this.#displayed, ([id]) => id, ([id, visual]) => this.#renderArc(id, visual, zoomId))}</g>
|
|
1264
|
-
<g class="labels">${this
|
|
1265
|
-
|
|
1475
|
+
<g class="labels">${this.disableLabels ? nothing : renderSunburstLabels({
|
|
1476
|
+
displayed: this.#displayed,
|
|
1477
|
+
layout: this.#layout,
|
|
1478
|
+
rings: this.#rings,
|
|
1479
|
+
fontSize: this.#fontSize,
|
|
1480
|
+
curved: this.curvedLabels,
|
|
1481
|
+
idPrefix: this.#labelIdPrefix
|
|
1482
|
+
})}</g>
|
|
1483
|
+
${renderSunburstReadout(zoomId === void 0 ? void 0 : this.#layout.get(zoomId), tickledId === void 0 ? void 0 : this.#layout.get(tickledId), this.#rings, this.#fontSize, this.valueFormatter)}
|
|
1266
1484
|
</svg>
|
|
1267
1485
|
${this.search.renderField()}
|
|
1268
1486
|
`;
|
|
@@ -1331,6 +1549,7 @@ __decorate([property({
|
|
|
1331
1549
|
reflect: true
|
|
1332
1550
|
})], SunburstTreeListElement.prototype, "searchModes", void 0);
|
|
1333
1551
|
__decorate([state()], SunburstTreeListElement.prototype, "_zoomId", void 0);
|
|
1552
|
+
__decorate([property({ attribute: false })], SunburstTreeListElement.prototype, "restoreLoads", void 0);
|
|
1334
1553
|
__decorate([state()], SunburstTreeListElement.prototype, "_size", void 0);
|
|
1335
1554
|
SunburstTreeListElement = __decorate([safeCustomElement(`ixfx-sunbursttree-list`)], SunburstTreeListElement);
|
|
1336
1555
|
//#endregion
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as TreeItemDblClickEvent, E as TreeSearchHost, M as TreeDataModel, a as TreeComponent, i as TreeCollapseEvent, j as TreeTickleEvent, k as TreeSelectEvent, n as TreeActivateEvent, o as TreeComponentEventMap, x as TreeItemClickEvent, y as TreeExpandEvent } from "./tree-component-DgfkxmVY.js";
|
|
2
2
|
import { a as TreeItem, n as TreeExclusivity, o as TreeNode, r as TreeFilterPredicate, s as TreeSelectionFilter, t as LoadChildrenCallback } from "./types-Jky6oNAp.js";
|
|
3
|
-
import {
|
|
3
|
+
import { a as ViewStateBase, t as GetViewStateOptions } from "./types-LU1u0CB8.js";
|
|
4
|
+
import "./index-9ttcdwlL.js";
|
|
5
|
+
import { S as ListSearchController } from "./index-CUErGhCg.js";
|
|
4
6
|
import { LitElement, PropertyValues } from "lit";
|
|
5
7
|
import { CommandRegistry } from "@clinth/ui-commands";
|
|
6
8
|
//#region src/sunburst-tree/types.d.ts
|
|
@@ -120,6 +122,24 @@ type SunburstLabelPlacement = {
|
|
|
120
122
|
/** Ids are key paths; keys only need to be unique among siblings. */
|
|
121
123
|
declare const SUNBURST_PATH_SEPARATOR = "";
|
|
122
124
|
//#endregion
|
|
125
|
+
//#region src/sunburst-tree/sunburst-view-state.d.ts
|
|
126
|
+
/**
|
|
127
|
+
* Pure helpers behind `ixfx-sunbursttree-list` view state. Arcs are
|
|
128
|
+
* identified by key *path* (keys need only be unique among siblings), so live
|
|
129
|
+
* selection follows its path across data changes; saved keys — which are
|
|
130
|
+
* bare `item.key`s — resolve to the first node, depth-first, carrying them.
|
|
131
|
+
* See `docs/infra-view-state.md`.
|
|
132
|
+
*/
|
|
133
|
+
/** `ixfx-sunbursttree-list` view state: `selected`, `focus`, plus the zoom path. */
|
|
134
|
+
type SunburstViewState = ViewStateBase & {
|
|
135
|
+
/**
|
|
136
|
+
* Keys from the root's children down to the centre node; `[]` = the full
|
|
137
|
+
* tree. A path because keys are only unique among siblings, and so a
|
|
138
|
+
* lazily-loaded tree can be opened down to it, one load at a time.
|
|
139
|
+
*/
|
|
140
|
+
readonly zoom?: readonly string[];
|
|
141
|
+
};
|
|
142
|
+
//#endregion
|
|
123
143
|
//#region src/sunburst-tree/sunbursttree-list.d.ts
|
|
124
144
|
/** Fired when the zoom root changes. `node` is `undefined` on zoom-out to the full tree. */
|
|
125
145
|
type SunburstZoomDetail = {
|
|
@@ -209,6 +229,11 @@ declare class SunburstTreeListElement extends LitElement implements TreeComponen
|
|
|
209
229
|
get focusedNode(): TreeNode | undefined;
|
|
210
230
|
/** Id (key path) of the zoom node; `undefined` = the true root. */
|
|
211
231
|
private _zoomId;
|
|
232
|
+
/**
|
|
233
|
+
* Whether restoring a saved zoom may call `loadChildren` / `model` to load
|
|
234
|
+
* the nodes along its path (one at a time). Default `true`.
|
|
235
|
+
*/
|
|
236
|
+
restoreLoads: boolean;
|
|
212
237
|
private _size;
|
|
213
238
|
readonly search: ListSearchController<{
|
|
214
239
|
node: TreeNode;
|
|
@@ -223,6 +248,18 @@ declare class SunburstTreeListElement extends LitElement implements TreeComponen
|
|
|
223
248
|
disconnectedCallback(): void;
|
|
224
249
|
protected willUpdate(changed: PropertyValues): void;
|
|
225
250
|
protected updated(changed: PropertyValues): void;
|
|
251
|
+
/**
|
|
252
|
+
* Snapshot selection, zoom path and keyboard cursor as plain JSON, keyed by
|
|
253
|
+
* `item.key`. Includes pending state for keys not currently loaded.
|
|
254
|
+
*/
|
|
255
|
+
getViewState(options?: GetViewStateOptions<SunburstViewState>): SunburstViewState;
|
|
256
|
+
/**
|
|
257
|
+
* Apply saved view state. Fields present replace the current ones; keys
|
|
258
|
+
* that aren't loaded yet are applied as they appear — including via
|
|
259
|
+
* `loadChildren` / `model` down a saved zoom path when `restoreLoads`.
|
|
260
|
+
*/
|
|
261
|
+
setViewState(state: Partial<SunburstViewState>): void;
|
|
262
|
+
clearPendingViewState(): void;
|
|
226
263
|
select(node: TreeNode): void;
|
|
227
264
|
deselect(node: TreeNode): void;
|
|
228
265
|
clearSelection(): void;
|
|
@@ -263,4 +300,4 @@ interface SunburstTreeListElement {
|
|
|
263
300
|
removeEventListener: (<K extends keyof SunburstTreeListElementEventMap>(type: K, listener: (this: SunburstTreeListElement, event: SunburstTreeListElementEventMap[K]) => void, options?: boolean | EventListenerOptions) => void) & ((type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions) => void);
|
|
264
301
|
}
|
|
265
302
|
//#endregion
|
|
266
|
-
export {
|
|
303
|
+
export { SunburstViewState as a, SunburstArcVisual as c, SunburstLayout as d, SunburstLayoutNode as f, SunburstView as g, SunburstValueFormatter as h, SunburstZoomEvent as i, SunburstInteractionMode as l, SunburstValueAccessor as m, SunburstTreeListElementEventMap as n, ComputeSunburstLayoutOptions as o, SunburstRings as p, SunburstZoomDetail as r, SUNBURST_PATH_SEPARATOR as s, SunburstTreeListElement as t, SunburstLabelPlacement as u };
|