@ixfx/components 0.15.4 → 0.16.0
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 +1223 -48
- package/bundle/index.js +875 -700
- package/dist/ac-text.d.ts +1 -1
- package/dist/{ac-token-Cq7lsYf-.d.ts → ac-token-qEs3FsQv.d.ts} +1 -1
- package/dist/ac-token.d.ts +1 -1
- package/dist/colour-picker.d.ts +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-CbiubdG2.d.ts → data-grid-CIG2DVip.d.ts} +58 -9
- package/dist/{data-grid-Dx3l3dxw.js → data-grid-rMkLX2qo.js} +630 -215
- package/dist/data-grid.d.ts +3 -3
- package/dist/data-grid.js +1 -1
- package/dist/{detail-list-ClkMV0SN.d.ts → detail-list-6iMTNP4N.d.ts} +35 -5
- package/dist/{detail-list-q8bs67BM.js → detail-list-C4pDSoI8.js} +218 -7
- package/dist/detail-list.d.ts +4 -4
- package/dist/detail-list.js +1 -1
- package/dist/{flat-list-0xTZdXzh.d.ts → flat-list-JPgQSmLW.d.ts} +28 -5
- package/dist/flat-list.d.ts +2 -2
- package/dist/flat-list.js +123 -4
- package/dist/{grid-list-y6dWJYu3.js → grid-list-CdEkScn-.js} +276 -75
- package/dist/{grid-list-BnWJUIBC.d.ts → grid-list-D6n7WH8l.d.ts} +128 -22
- package/dist/grid-list.d.ts +3 -3
- package/dist/grid-list.js +1 -1
- package/dist/{grouped-item-lister-Bhyo-dkH.d.ts → grouped-item-lister-DETte0Dh.d.ts} +1 -1
- package/dist/grouped-item-lister.d.ts +1 -1
- package/dist/{icon-DR_3rSyZ.d.ts → icon-CDGprSdE.d.ts} +1 -1
- package/dist/icons.d.ts +2 -2
- package/dist/incr-search.d.ts +1 -1
- package/dist/index-9ttcdwlL.d.ts +71 -0
- package/dist/{index-BAuqR9El.d.ts → index-Bx0BXPxI.d.ts} +52 -6
- package/dist/index-CYRc6Dah.d.ts +475 -0
- package/dist/{index-garxxqgV.d.ts → index-CYndvTWH.d.ts} +1 -1
- package/dist/{index-BUB5SICW.d.ts → index-CoqtMSd9.d.ts} +1 -1
- package/dist/{index-Bz3loIAf.d.ts → index-DN2BWctP.d.ts} +1 -1
- package/dist/{index-BPTuCRGb.d.ts → index-Dr80Zdl6.d.ts} +2 -2
- package/dist/{index-cnaRPQPp.d.ts → index-DxyyVqzA.d.ts} +2 -2
- package/dist/{index-CJ5fshD3.d.ts → index-UvnoZUAo.d.ts} +1 -1
- package/dist/index.d.ts +380 -33
- package/dist/index.js +2247 -866
- package/dist/{labelled-input-base-Dp_9KP0G.d.ts → labelled-input-base-DiTme1JO.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-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/{menu-item-B0zqw2xp.d.ts → menu-item-bF6narIz.d.ts} +1 -1
- package/dist/menu.d.ts +2 -2
- package/dist/miller-BFRfn0ve.js +2237 -0
- package/dist/miller.d.ts +4 -437
- package/dist/miller.js +1 -1815
- package/dist/panel.d.ts +1 -1
- package/dist/pending-BGidHppL.js +91 -0
- package/dist/scroll-hover-guard-Cjw6179Q.js +35 -0
- package/dist/{split-layout-C5SYbDrq.d.ts → split-layout-CophkNeU.d.ts} +42 -4
- 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 -0
- package/dist/sunburst-tree.js +2 -0
- package/dist/sunbursttree-list-B_VxOHlo.js +1556 -0
- package/dist/sunbursttree-list-D6j5BxVQ.d.ts +303 -0
- package/dist/{tab-list-wC0xNz1b.d.ts → tab-list-Df371rqw.d.ts} +1 -1
- package/dist/tabs.d.ts +1 -1
- package/dist/{titlebar-B5zSTZnF.d.ts → titlebar-Tqu-VvMU.d.ts} +1 -1
- package/dist/titlebar.d.ts +1 -1
- package/dist/tree-CftHwtEb.js +146 -0
- package/dist/{tree-D8VxvR9u.js → tree-Crdpl4nc.js} +275 -47
- 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-1V1Vg2O5.js +1993 -0
- package/dist/{vertical-list-B8FoU-pU.d.ts → vertical-list-BPDmZx6T.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 +3 -0
- package/docs-user/crumbs.md +27 -0
- package/docs-user/data-grid.md +77 -5
- package/docs-user/detail-list.md +54 -0
- package/docs-user/dock.md +54 -12
- package/docs-user/flat-list.md +27 -0
- package/docs-user/grid-list.md +64 -11
- package/docs-user/hyperbolic-tree.md +31 -4
- package/docs-user/icicle-tree.md +36 -0
- package/docs-user/index.json +17 -2
- package/docs-user/llms.txt +3 -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 +223 -0
- package/docs-user/tree.md +46 -7
- 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 +3 -0
- package/package.json +5 -1
|
@@ -0,0 +1,1556 @@
|
|
|
1
|
+
import { t as TickledController } from "./tickled-controller-D-HZymaZ.js";
|
|
2
|
+
import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
|
|
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-JIhwB49y.js";
|
|
5
|
+
import { a as capKeys, l as keysToArray, n as EMPTY_KEYS } from "./pending-BGidHppL.js";
|
|
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";
|
|
9
|
+
import { LitElement, css, html, nothing, svg } from "lit";
|
|
10
|
+
import { property, state } from "lit/decorators.js";
|
|
11
|
+
import { createCommandRegistry } from "@clinth/ui-commands";
|
|
12
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
13
|
+
//#region src/sunburst-tree/types.ts
|
|
14
|
+
/** Ids are key paths; keys only need to be unique among siblings. */
|
|
15
|
+
const SUNBURST_PATH_SEPARATOR = `\u001F`;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/sunburst-tree/layout.ts
|
|
18
|
+
/** Default accessor: the shared `TreeItem.weight` field. */
|
|
19
|
+
const defaultSunburstValueAccessor = (item) => item.weight;
|
|
20
|
+
/** Id of the child `key` under `parentId`. */
|
|
21
|
+
function sunburstChildId(parentId, key) {
|
|
22
|
+
return `${parentId}${SUNBURST_PATH_SEPARATOR}${key}`;
|
|
23
|
+
}
|
|
24
|
+
/** Join a key path (root key first) into an id. */
|
|
25
|
+
function sunburstPathToId(path) {
|
|
26
|
+
return path.join(SUNBURST_PATH_SEPARATOR);
|
|
27
|
+
}
|
|
28
|
+
/** True when `ancestorId` is a strict ancestor of `id`. */
|
|
29
|
+
function isSunburstAncestorId(ancestorId, id) {
|
|
30
|
+
return id.startsWith(`${ancestorId}${SUNBURST_PATH_SEPARATOR}`);
|
|
31
|
+
}
|
|
32
|
+
/** Split an id back into its key path. */
|
|
33
|
+
function sunburstIdToPath(id) {
|
|
34
|
+
return id.split(SUNBURST_PATH_SEPARATOR);
|
|
35
|
+
}
|
|
36
|
+
/** Parent id, or `undefined` for the root id. */
|
|
37
|
+
function sunburstParentId(id) {
|
|
38
|
+
const at = id.lastIndexOf(SUNBURST_PATH_SEPARATOR);
|
|
39
|
+
return at < 0 ? void 0 : id.slice(0, at);
|
|
40
|
+
}
|
|
41
|
+
/** Resolve an id (key path from the root, root key included) to a node. */
|
|
42
|
+
function findSunburstNodeById(root, id) {
|
|
43
|
+
if (!root) return void 0;
|
|
44
|
+
const [rootKey, ...rest] = sunburstIdToPath(id);
|
|
45
|
+
if (rootKey !== root.item.key) return void 0;
|
|
46
|
+
let current = root;
|
|
47
|
+
for (const key of rest) {
|
|
48
|
+
current = current.children?.find((c) => c.item.key === key);
|
|
49
|
+
if (!current) return void 0;
|
|
50
|
+
}
|
|
51
|
+
return current;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Depth-first search for the first node matching `predicate`. Returns its id
|
|
55
|
+
* (root key included), or `undefined`.
|
|
56
|
+
*/
|
|
57
|
+
function findSunburstId(root, predicate) {
|
|
58
|
+
if (!root) return void 0;
|
|
59
|
+
const visit = (node, id) => {
|
|
60
|
+
if (predicate(node)) return id;
|
|
61
|
+
for (const child of node.children ?? []) {
|
|
62
|
+
const hit = visit(child, sunburstChildId(id, child.item.key));
|
|
63
|
+
if (hit !== void 0) return hit;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
return visit(root, root.item.key);
|
|
67
|
+
}
|
|
68
|
+
/** Valid explicit value: finite and non-negative. */
|
|
69
|
+
function explicitValue(raw) {
|
|
70
|
+
return typeof raw === `number` && Number.isFinite(raw) && raw >= 0 ? raw : void 0;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Returns a memoised resolver for node values: the accessor's value when
|
|
74
|
+
* valid, else the sum of loaded children, else `1`.
|
|
75
|
+
*/
|
|
76
|
+
function createValueResolver(accessor) {
|
|
77
|
+
const cache = /* @__PURE__ */ new Map();
|
|
78
|
+
const resolve = (node) => {
|
|
79
|
+
const cached = cache.get(node);
|
|
80
|
+
if (cached !== void 0) return cached;
|
|
81
|
+
let value = explicitValue(accessor(node.item, node));
|
|
82
|
+
if (value === void 0) {
|
|
83
|
+
const children = node.children ?? [];
|
|
84
|
+
value = children.length > 0 ? children.reduce((sum, c) => sum + resolve(c), 0) : 1;
|
|
85
|
+
}
|
|
86
|
+
cache.set(node, value);
|
|
87
|
+
return value;
|
|
88
|
+
};
|
|
89
|
+
return resolve;
|
|
90
|
+
}
|
|
91
|
+
/** True when `node` may have children (loaded or loadable). */
|
|
92
|
+
function sunburstHasChildren(node) {
|
|
93
|
+
if (node.children !== void 0) return node.children.length > 0;
|
|
94
|
+
return node.item.isLeaf !== true;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Compute the absolute layout of the whole (loaded) tree. The root spans the
|
|
98
|
+
* full turn at depth `0`; each child spans
|
|
99
|
+
* `parentSpan × value / max(parentValue, Σ siblingValues)`, packed from the
|
|
100
|
+
* parent's start angle in sibling order. Returned in ring-major order.
|
|
101
|
+
*/
|
|
102
|
+
function computeSunburstLayout(root, options = {}) {
|
|
103
|
+
const out = /* @__PURE__ */ new Map();
|
|
104
|
+
if (!root) return out;
|
|
105
|
+
const { valueAccessor = defaultSunburstValueAccessor, maxDepth = Number.POSITIVE_INFINITY } = options;
|
|
106
|
+
const valueOf = createValueResolver(valueAccessor);
|
|
107
|
+
const rootEntry = {
|
|
108
|
+
id: root.item.key,
|
|
109
|
+
node: root,
|
|
110
|
+
parentId: void 0,
|
|
111
|
+
depth: 0,
|
|
112
|
+
x0: 0,
|
|
113
|
+
x1: 1,
|
|
114
|
+
value: valueOf(root),
|
|
115
|
+
index: 0,
|
|
116
|
+
setSize: 1,
|
|
117
|
+
hasChildren: sunburstHasChildren(root)
|
|
118
|
+
};
|
|
119
|
+
out.set(rootEntry.id, rootEntry);
|
|
120
|
+
let ring = [rootEntry];
|
|
121
|
+
while (ring.length > 0) {
|
|
122
|
+
const next = [];
|
|
123
|
+
for (const parent of ring) {
|
|
124
|
+
const children = parent.node.children ?? [];
|
|
125
|
+
if (children.length === 0 || parent.depth + 1 > maxDepth) continue;
|
|
126
|
+
const values = children.map(valueOf);
|
|
127
|
+
const total = values.reduce((a, b) => a + b, 0);
|
|
128
|
+
const denominator = Math.max(parent.value, total);
|
|
129
|
+
const span = parent.x1 - parent.x0;
|
|
130
|
+
let cursor = parent.x0;
|
|
131
|
+
children.forEach((node, index) => {
|
|
132
|
+
const width = denominator > 0 ? span * values[index] / denominator : 0;
|
|
133
|
+
const entry = {
|
|
134
|
+
id: sunburstChildId(parent.id, node.item.key),
|
|
135
|
+
node,
|
|
136
|
+
parentId: parent.id,
|
|
137
|
+
depth: parent.depth + 1,
|
|
138
|
+
x0: cursor,
|
|
139
|
+
x1: cursor + width,
|
|
140
|
+
value: values[index],
|
|
141
|
+
index,
|
|
142
|
+
setSize: children.length,
|
|
143
|
+
hasChildren: sunburstHasChildren(node)
|
|
144
|
+
};
|
|
145
|
+
cursor += width;
|
|
146
|
+
out.set(entry.id, entry);
|
|
147
|
+
next.push(entry);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
ring = next;
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/sunburst-tree/geometry.ts
|
|
156
|
+
const TAU = Math.PI * 2;
|
|
157
|
+
/** Spans at least this close to a full turn are drawn as full rings. */
|
|
158
|
+
const FULL_TURN = 1 - 1e-6;
|
|
159
|
+
const clamp01 = (v) => Math.min(1, Math.max(0, v));
|
|
160
|
+
/** View that makes `zoom` the centre and its span the full circle. */
|
|
161
|
+
function sunburstViewFor(zoom) {
|
|
162
|
+
if (!zoom || zoom.x1 - zoom.x0 <= 0) return {
|
|
163
|
+
x0: 0,
|
|
164
|
+
x1: 1,
|
|
165
|
+
depth: 0
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
x0: zoom.x0,
|
|
169
|
+
x1: zoom.x1,
|
|
170
|
+
depth: zoom.depth
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** Screen angles + relative ring for `node` seen through `view`. */
|
|
174
|
+
function projectArc(node, view) {
|
|
175
|
+
const span = view.x1 - view.x0;
|
|
176
|
+
return {
|
|
177
|
+
a0: clamp01((node.x0 - view.x0) / span),
|
|
178
|
+
a1: clamp01((node.x1 - view.x0) / span),
|
|
179
|
+
depth: node.depth - view.depth - 1
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Radius of ring boundary `d`. Boundary `0` is the centre disc's edge;
|
|
184
|
+
* negative boundaries shrink linearly into the centre, so the zoom node
|
|
185
|
+
* (ring `-1`) is the centre disc and its ancestors collapse to nothing.
|
|
186
|
+
*/
|
|
187
|
+
function ringBoundary(d, rings) {
|
|
188
|
+
return d >= 0 ? rings.center + d * rings.ring : Math.max(0, rings.center * (1 + d));
|
|
189
|
+
}
|
|
190
|
+
/** Inner/outer radius of an arc sitting in (possibly fractional) ring `depth`. */
|
|
191
|
+
function arcRadii(depth, rings) {
|
|
192
|
+
return {
|
|
193
|
+
inner: ringBoundary(depth, rings),
|
|
194
|
+
outer: ringBoundary(depth + 1, rings)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** Split `outerRadius` into a centre disc and `maxDepth` equal rings. */
|
|
198
|
+
function computeRings(outerRadius, maxDepth, centerRatio) {
|
|
199
|
+
const radius = Math.max(0, outerRadius);
|
|
200
|
+
const center = radius * Math.min(.9, Math.max(0, centerRatio));
|
|
201
|
+
return {
|
|
202
|
+
center,
|
|
203
|
+
ring: (radius - center) / Math.max(1, maxDepth)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function point(angle, r) {
|
|
207
|
+
const theta = angle * TAU;
|
|
208
|
+
return `${(r * Math.sin(theta)).toFixed(2)} ${(-r * Math.cos(theta)).toFixed(2)}`;
|
|
209
|
+
}
|
|
210
|
+
function circle(r) {
|
|
211
|
+
const d = (r * 2).toFixed(2);
|
|
212
|
+
return `M0 ${(-r).toFixed(2)}a${r.toFixed(2)} ${r.toFixed(2)} 0 1 1 0 ${d}a${r.toFixed(2)} ${r.toFixed(2)} 0 1 1 0 -${d}Z`;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* SVG path for an annular sector. Full turns become a ring (two circles,
|
|
216
|
+
* drawn with `fill-rule: evenodd`) or a disc when `inner` is `0`. Returns
|
|
217
|
+
* an empty string for degenerate arcs.
|
|
218
|
+
*/
|
|
219
|
+
function arcPath(a0, a1, inner, outer) {
|
|
220
|
+
const span = a1 - a0;
|
|
221
|
+
if (span <= 0 || outer <= 0 || outer <= inner) return ``;
|
|
222
|
+
if (span >= FULL_TURN) return inner > 0 ? `${circle(outer)}${circle(inner)}` : circle(outer);
|
|
223
|
+
const large = span > .5 ? 1 : 0;
|
|
224
|
+
const o = outer.toFixed(2);
|
|
225
|
+
if (inner <= 0) return `M0 0L${point(a0, outer)}A${o} ${o} 0 ${large} 1 ${point(a1, outer)}Z`;
|
|
226
|
+
const i = inner.toFixed(2);
|
|
227
|
+
return `M${point(a0, outer)}A${o} ${o} 0 ${large} 1 ${point(a1, outer)}L${point(a1, inner)}A${i} ${i} 0 ${large} 0 ${point(a0, inner)}Z`;
|
|
228
|
+
}
|
|
229
|
+
/** On-screen length (px) of the outer edge of an arc. */
|
|
230
|
+
function arcLength(a0, a1, radius) {
|
|
231
|
+
return Math.max(0, a1 - a0) * TAU * radius;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Guide path for curved text: an arc of radius `r` from `a0` to `a1`,
|
|
235
|
+
* clockwise — or, when `flip`, counter-clockwise from `a1` to `a0` so text
|
|
236
|
+
* in the bottom half reads left-to-right rather than upside-down.
|
|
237
|
+
*/
|
|
238
|
+
function labelArcPath(a0, a1, r, flip) {
|
|
239
|
+
const end = Math.min(a1, a0 + .9999);
|
|
240
|
+
const large = end - a0 > .5 ? 1 : 0;
|
|
241
|
+
const rr = r.toFixed(2);
|
|
242
|
+
return flip ? `M${point(end, r)}A${rr} ${rr} 0 ${large} 0 ${point(a0, r)}` : `M${point(a0, r)}A${rr} ${rr} 0 ${large} 1 ${point(end, r)}`;
|
|
243
|
+
}
|
|
244
|
+
function truncate(label, fits) {
|
|
245
|
+
return label.length <= fits ? label : `${label.slice(0, fits - 1)}…`;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Where to draw `label` inside an arc, or `undefined` if it can't fit
|
|
249
|
+
* legibly. Prefers tangential text (following the ring) when the arc is long
|
|
250
|
+
* enough — curved along the ring with `curved`, else straight — otherwise
|
|
251
|
+
* radial text truncated to the ring width. All are oriented to read upright.
|
|
252
|
+
*/
|
|
253
|
+
function labelPlacement(visual, label, rings, fontSize, options = {}) {
|
|
254
|
+
if (visual.depth < 0 || label.length === 0) return void 0;
|
|
255
|
+
const { inner, outer } = arcRadii(visual.depth, rings);
|
|
256
|
+
const thickness = outer - inner;
|
|
257
|
+
const mid = (inner + outer) / 2;
|
|
258
|
+
const span = visual.a1 - visual.a0;
|
|
259
|
+
const chord = arcLength(visual.a0, visual.a1, mid);
|
|
260
|
+
const charWidth = fontSize * .6;
|
|
261
|
+
const pad = fontSize * .5;
|
|
262
|
+
const midAngle = (visual.a0 + visual.a1) / 2;
|
|
263
|
+
const degrees = midAngle * 360;
|
|
264
|
+
const bottomHalf = degrees > 90 && degrees < 270;
|
|
265
|
+
const lineFits = thickness >= fontSize * 1.3;
|
|
266
|
+
if (options.curved && lineFits) {
|
|
267
|
+
const fits = Math.floor((chord - pad) / charWidth);
|
|
268
|
+
if (fits >= Math.min(label.length, 4)) return {
|
|
269
|
+
kind: `curved`,
|
|
270
|
+
path: span >= FULL_TURN ? labelArcPath(.5, 1.5, mid, false) : labelArcPath(visual.a0, visual.a1, mid, bottomHalf),
|
|
271
|
+
text: truncate(label, fits)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const theta = midAngle * TAU;
|
|
275
|
+
const x = mid * Math.sin(theta);
|
|
276
|
+
const y = -mid * Math.cos(theta);
|
|
277
|
+
if (!options.curved && lineFits && span < .5 && chord >= label.length * charWidth + pad) return {
|
|
278
|
+
kind: `straight`,
|
|
279
|
+
x,
|
|
280
|
+
y,
|
|
281
|
+
rotate: degrees + (bottomHalf ? 180 : 0),
|
|
282
|
+
text: label
|
|
283
|
+
};
|
|
284
|
+
if (chord < fontSize * 1.1) return void 0;
|
|
285
|
+
const fits = Math.floor((thickness - pad) / charWidth);
|
|
286
|
+
if (fits < 3) return void 0;
|
|
287
|
+
return {
|
|
288
|
+
kind: `straight`,
|
|
289
|
+
x,
|
|
290
|
+
y,
|
|
291
|
+
rotate: degrees - 90 + (degrees > 180 ? 180 : 0),
|
|
292
|
+
text: truncate(label, fits)
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Target hue offset (degrees) per arc: the angular midpoint of its
|
|
297
|
+
* first-ring ancestor (the zoom node's child it descends from), so the ring
|
|
298
|
+
* around the centre spans the colour wheel and everything outside it takes a
|
|
299
|
+
* tone of its ancestor's hue. Arcs outside the zoomed subtree get no entry.
|
|
300
|
+
*/
|
|
301
|
+
function targetHues(layout, view, zoomId) {
|
|
302
|
+
const hues = /* @__PURE__ */ new Map();
|
|
303
|
+
for (const entry of layout.values()) {
|
|
304
|
+
if (entry.depth <= view.depth) continue;
|
|
305
|
+
if (entry.parentId === zoomId) {
|
|
306
|
+
const { a0, a1 } = projectArc(entry, view);
|
|
307
|
+
hues.set(entry.id, (a0 + a1) / 2 * 360);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const parentHue = entry.parentId === void 0 ? void 0 : hues.get(entry.parentId);
|
|
311
|
+
if (parentHue !== void 0) hues.set(entry.id, parentHue);
|
|
312
|
+
}
|
|
313
|
+
return hues;
|
|
314
|
+
}
|
|
315
|
+
/** Tone offset from sibling position, so neighbouring same-hue arcs differ. */
|
|
316
|
+
function siblingShade(index, setSize) {
|
|
317
|
+
return setSize <= 1 ? 0 : (index + .5) / setSize - .5;
|
|
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
|
+
}
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/sunburst-tree/keyboard.ts
|
|
341
|
+
/**
|
|
342
|
+
* Id the cursor moves to for `key`, or `undefined` when the key isn't a
|
|
343
|
+
* navigation key or there's nowhere to go. With no current arc, any
|
|
344
|
+
* navigation key lands on the first arc.
|
|
345
|
+
*/
|
|
346
|
+
function sunburstKeyTarget(arcs, currentId, key) {
|
|
347
|
+
if (arcs.length === 0) return void 0;
|
|
348
|
+
const current = currentId === void 0 ? void 0 : arcs.find((a) => a.id === currentId);
|
|
349
|
+
if (key === `Home`) return arcs[0].id;
|
|
350
|
+
if (key === `End`) return arcs.at(-1).id;
|
|
351
|
+
if (![
|
|
352
|
+
`ArrowLeft`,
|
|
353
|
+
`ArrowRight`,
|
|
354
|
+
`ArrowUp`,
|
|
355
|
+
`ArrowDown`
|
|
356
|
+
].includes(key)) return void 0;
|
|
357
|
+
if (!current) return arcs[0].id;
|
|
358
|
+
switch (key) {
|
|
359
|
+
case `ArrowLeft`:
|
|
360
|
+
case `ArrowRight`: {
|
|
361
|
+
const siblings = arcs.filter((a) => a.parentId === current.parentId);
|
|
362
|
+
return siblings[(siblings.findIndex((a) => a.id === current.id) + (key === `ArrowRight` ? 1 : -1) + siblings.length) % siblings.length].id;
|
|
363
|
+
}
|
|
364
|
+
case `ArrowUp`: return arcs.find((a) => a.id === current.parentId)?.id;
|
|
365
|
+
default: return arcs.find((a) => a.parentId === current.id)?.id;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
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
|
|
408
|
+
//#region src/sunburst-tree/styles.ts
|
|
409
|
+
/**
|
|
410
|
+
* Shadow-DOM styles for `ixfx-sunbursttree-list`.
|
|
411
|
+
*
|
|
412
|
+
* Arc colour is computed here, not in JS: each arc carries its (animated)
|
|
413
|
+
* `--arc-hue`, `--arc-depth`, `--arc-shade` and `--arc-centre`, and the
|
|
414
|
+
* `--sunbursttree-*` knobs turn those into a fill — so the palette is
|
|
415
|
+
* themeable without touching the component.
|
|
416
|
+
*/
|
|
417
|
+
const sunburstTreeStyles = css`
|
|
418
|
+
:host {
|
|
419
|
+
--sunbursttree-hue-start: 250;
|
|
420
|
+
--sunbursttree-chroma: 0.14;
|
|
421
|
+
--sunbursttree-chroma-step: 0.015;
|
|
422
|
+
--sunbursttree-lightness: 60%;
|
|
423
|
+
--sunbursttree-lightness-step: 5%;
|
|
424
|
+
--sunbursttree-shade-step: 10%;
|
|
425
|
+
--sunbursttree-center-color: var(--surface-4);
|
|
426
|
+
--sunbursttree-center-text: var(--surface-4-text);
|
|
427
|
+
--sunbursttree-gap-color: var(--surface-2);
|
|
428
|
+
--sunbursttree-gap: 1px;
|
|
429
|
+
--sunbursttree-font-size: 11px;
|
|
430
|
+
--sunbursttree-label-lightness: 22%;
|
|
431
|
+
|
|
432
|
+
display: block;
|
|
433
|
+
position: relative;
|
|
434
|
+
min-height: 200px;
|
|
435
|
+
aspect-ratio: 1;
|
|
436
|
+
outline: none;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
:host(:focus),
|
|
440
|
+
:host(:focus-visible) {
|
|
441
|
+
outline: none;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.sunburst {
|
|
445
|
+
display: block;
|
|
446
|
+
width: 100%;
|
|
447
|
+
height: 100%;
|
|
448
|
+
outline: none;
|
|
449
|
+
overflow: hidden;
|
|
450
|
+
font-size: var(--sunbursttree-font-size);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.arc {
|
|
454
|
+
--_l: clamp(
|
|
455
|
+
10%,
|
|
456
|
+
calc(
|
|
457
|
+
var(--sunbursttree-lightness)
|
|
458
|
+
+ var(--arc-depth, 0) * var(--sunbursttree-lightness-step)
|
|
459
|
+
+ var(--arc-shade, 0) * var(--sunbursttree-shade-step)
|
|
460
|
+
+ var(--_boost, 0%)
|
|
461
|
+
),
|
|
462
|
+
95%
|
|
463
|
+
);
|
|
464
|
+
--_c: max(0, calc(var(--sunbursttree-chroma) - var(--arc-depth, 0) * var(--sunbursttree-chroma-step)));
|
|
465
|
+
--_arc-color: oklch(var(--_l) var(--_c) calc(var(--sunbursttree-hue-start) + var(--arc-hue, 0)));
|
|
466
|
+
|
|
467
|
+
fill: color-mix(in oklch, var(--_arc-color), var(--sunbursttree-center-color) calc(var(--arc-centre, 0) * 100%));
|
|
468
|
+
fill-rule: evenodd;
|
|
469
|
+
stroke: var(--sunbursttree-gap-color);
|
|
470
|
+
stroke-width: var(--sunbursttree-gap);
|
|
471
|
+
stroke-linejoin: round;
|
|
472
|
+
cursor: pointer;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.arc.tickled {
|
|
476
|
+
--_boost: 10%;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.arc.selected {
|
|
480
|
+
stroke: var(--accent);
|
|
481
|
+
stroke-width: calc(var(--sunbursttree-gap) + 1.5px);
|
|
482
|
+
paint-order: fill;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.arc.filtered {
|
|
486
|
+
fill-opacity: 0.3;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.arc.loading {
|
|
490
|
+
cursor: progress;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.arc.exiting {
|
|
494
|
+
pointer-events: none;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.arc.center.leaf {
|
|
498
|
+
cursor: default;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.sunburst-label {
|
|
502
|
+
fill: oklch(var(--sunbursttree-label-lightness) 0.03 calc(var(--sunbursttree-hue-start) + var(--arc-hue, 0)));
|
|
503
|
+
pointer-events: none;
|
|
504
|
+
user-select: none;
|
|
505
|
+
dominant-baseline: central;
|
|
506
|
+
text-anchor: middle;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.center-readout {
|
|
510
|
+
fill: var(--sunbursttree-center-text);
|
|
511
|
+
pointer-events: none;
|
|
512
|
+
user-select: none;
|
|
513
|
+
text-anchor: middle;
|
|
514
|
+
dominant-baseline: central;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.center-readout .center-label {
|
|
518
|
+
font-weight: var(--font-weight-bold, 600);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.center-readout .center-meta {
|
|
522
|
+
fill-opacity: 0.75;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
.empty-state {
|
|
526
|
+
padding: var(--space-l);
|
|
527
|
+
color: var(--surface-muted-text);
|
|
528
|
+
font-size: var(--text-s);
|
|
529
|
+
}
|
|
530
|
+
`;
|
|
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
|
|
624
|
+
//#region src/sunburst-tree/tween.ts
|
|
625
|
+
function easeInOutCubic(t) {
|
|
626
|
+
return t < .5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
|
|
627
|
+
}
|
|
628
|
+
function easeOutCubic(t) {
|
|
629
|
+
return 1 - (1 - t) ** 3;
|
|
630
|
+
}
|
|
631
|
+
function prefersReducedMotion() {
|
|
632
|
+
return typeof matchMedia === `function` && matchMedia(`(prefers-reduced-motion: reduce)`).matches;
|
|
633
|
+
}
|
|
634
|
+
const lerp = (a, b, t) => a + (b - a) * t;
|
|
635
|
+
/** Interpolate hue (degrees) along the shorter way round the wheel. */
|
|
636
|
+
function lerpHue(a, b, t) {
|
|
637
|
+
return a + (((b - a) % 360 + 540) % 360 - 180) * t;
|
|
638
|
+
}
|
|
639
|
+
function lerpVisual(a, b, t) {
|
|
640
|
+
return {
|
|
641
|
+
a0: lerp(a.a0, b.a0, t),
|
|
642
|
+
a1: lerp(a.a1, b.a1, t),
|
|
643
|
+
depth: lerp(a.depth, b.depth, t),
|
|
644
|
+
hue: lerpHue(a.hue, b.hue, t),
|
|
645
|
+
shade: lerp(a.shade, b.shade, t),
|
|
646
|
+
opacity: lerp(a.opacity, b.opacity, t)
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function planTransition(options) {
|
|
650
|
+
const { current, targets, enter, exit } = options;
|
|
651
|
+
const from = /* @__PURE__ */ new Map();
|
|
652
|
+
const to = /* @__PURE__ */ new Map();
|
|
653
|
+
const exiting = /* @__PURE__ */ new Set();
|
|
654
|
+
for (const [id, target] of targets) {
|
|
655
|
+
from.set(id, current.get(id) ?? enter(id, target));
|
|
656
|
+
to.set(id, target);
|
|
657
|
+
}
|
|
658
|
+
for (const [id, shown] of current) {
|
|
659
|
+
if (targets.has(id)) continue;
|
|
660
|
+
from.set(id, shown);
|
|
661
|
+
to.set(id, exit(id, shown));
|
|
662
|
+
exiting.add(id);
|
|
663
|
+
}
|
|
664
|
+
return {
|
|
665
|
+
from,
|
|
666
|
+
to,
|
|
667
|
+
exiting
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
/** Displayed state at eased progress `t` (0–1). */
|
|
671
|
+
function sampleTransition(transition, t) {
|
|
672
|
+
const out = /* @__PURE__ */ new Map();
|
|
673
|
+
for (const [id, target] of transition.to) {
|
|
674
|
+
const start = transition.from.get(id) ?? target;
|
|
675
|
+
out.set(id, t >= 1 ? target : lerpVisual(start, target, t));
|
|
676
|
+
}
|
|
677
|
+
return out;
|
|
678
|
+
}
|
|
679
|
+
/** Settled state: the targets, without the arcs that animated out. */
|
|
680
|
+
function settleTransition(transition) {
|
|
681
|
+
const out = /* @__PURE__ */ new Map();
|
|
682
|
+
for (const [id, target] of transition.to) if (!transition.exiting.has(id)) out.set(id, target);
|
|
683
|
+
return out;
|
|
684
|
+
}
|
|
685
|
+
const EPSILON = 1e-4;
|
|
686
|
+
function sameVisual(a, b) {
|
|
687
|
+
return Math.abs(a.a0 - b.a0) < EPSILON && Math.abs(a.a1 - b.a1) < EPSILON && Math.abs(a.depth - b.depth) < EPSILON && Math.abs(lerpHue(a.hue, b.hue, 1) - a.hue) < EPSILON * 360 && Math.abs(a.shade - b.shade) < EPSILON && Math.abs(a.opacity - b.opacity) < EPSILON;
|
|
688
|
+
}
|
|
689
|
+
/** True when two target sets would render identically. */
|
|
690
|
+
function sameTargets(a, b) {
|
|
691
|
+
if (a.size !== b.size) return false;
|
|
692
|
+
for (const [id, va] of a) {
|
|
693
|
+
const vb = b.get(id);
|
|
694
|
+
if (!vb || !sameVisual(va, vb)) return false;
|
|
695
|
+
}
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
//#endregion
|
|
699
|
+
//#region src/sunburst-tree/sunbursttree-list.ts
|
|
700
|
+
const defaultFormatter = (value) => value.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
701
|
+
let instanceCount = 0;
|
|
702
|
+
/** Size used before the element has been measured (and in DOM-less tests). */
|
|
703
|
+
const FALLBACK_SIZE = 320;
|
|
704
|
+
let SunburstTreeListElement = class SunburstTreeListElement extends LitElement {
|
|
705
|
+
constructor(..._args) {
|
|
706
|
+
super(..._args);
|
|
707
|
+
this.exclusivity = `none`;
|
|
708
|
+
this.selectionFilter = `all`;
|
|
709
|
+
this.interactionMode = `standard`;
|
|
710
|
+
this.maxDepth = 5;
|
|
711
|
+
this.centerRatio = .22;
|
|
712
|
+
this.minArcLength = 1.5;
|
|
713
|
+
this.disableLabels = false;
|
|
714
|
+
this.curvedLabels = false;
|
|
715
|
+
this.animationDuration = 600;
|
|
716
|
+
this.valueAccessor = defaultSunburstValueAccessor;
|
|
717
|
+
this.valueFormatter = defaultFormatter;
|
|
718
|
+
this._selectedNodes = /* @__PURE__ */ new Set();
|
|
719
|
+
this.disableKeyboardNavigation = false;
|
|
720
|
+
this.registry = void 0;
|
|
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;
|
|
728
|
+
this._size = {
|
|
729
|
+
width: 0,
|
|
730
|
+
height: 0
|
|
731
|
+
};
|
|
732
|
+
this.search = new ListSearchController(this, { adapter: createTreeListSearchAdapter(this, {
|
|
733
|
+
labelSelector: `.sunburst-label`,
|
|
734
|
+
highlightKey: `incr-search-highlight`
|
|
735
|
+
}) });
|
|
736
|
+
this.tickled = new TickledController(this, {
|
|
737
|
+
getItems: () => this.#visibleArcs().map((a) => ({
|
|
738
|
+
id: a.id,
|
|
739
|
+
enabled: true
|
|
740
|
+
})),
|
|
741
|
+
onStateChange: () => {
|
|
742
|
+
this.setAttribute(`data-input-mode`, this.tickled.inputMode);
|
|
743
|
+
},
|
|
744
|
+
onTickle: (id) => {
|
|
745
|
+
const entry = id === void 0 ? void 0 : this.#layout.get(id);
|
|
746
|
+
if (!entry) return;
|
|
747
|
+
this.dispatchEvent(new CustomEvent(`tickle`, {
|
|
748
|
+
detail: {
|
|
749
|
+
node: entry.node,
|
|
750
|
+
depth: entry.depth - 1
|
|
751
|
+
},
|
|
752
|
+
bubbles: true,
|
|
753
|
+
composed: true
|
|
754
|
+
}));
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
this._layoutDirty = true;
|
|
758
|
+
this._ticking = false;
|
|
759
|
+
this.#layout = /* @__PURE__ */ new Map();
|
|
760
|
+
this.#view = {
|
|
761
|
+
x0: 0,
|
|
762
|
+
x1: 1,
|
|
763
|
+
depth: 0
|
|
764
|
+
};
|
|
765
|
+
this.#rings = {
|
|
766
|
+
center: 0,
|
|
767
|
+
ring: 0
|
|
768
|
+
};
|
|
769
|
+
this.#targets = /* @__PURE__ */ new Map();
|
|
770
|
+
this.#displayed = /* @__PURE__ */ new Map();
|
|
771
|
+
this.#animStart = 0;
|
|
772
|
+
this.#easing = easeInOutCubic;
|
|
773
|
+
this.#fontSize = 11;
|
|
774
|
+
this.#labelIdPrefix = `sunburst-${++instanceCount}-label-`;
|
|
775
|
+
this.#pendingIds = /* @__PURE__ */ new Set();
|
|
776
|
+
this.#aborters = /* @__PURE__ */ new Map();
|
|
777
|
+
this.#tick = (now) => {
|
|
778
|
+
this.#raf = void 0;
|
|
779
|
+
const transition = this.#transition;
|
|
780
|
+
if (!transition) return;
|
|
781
|
+
const t = (now - this.#animStart) / Math.max(1, this.animationDuration);
|
|
782
|
+
if (t >= 1) {
|
|
783
|
+
this.#displayed = settleTransition(transition);
|
|
784
|
+
this.#transition = void 0;
|
|
785
|
+
} else {
|
|
786
|
+
this.#displayed = sampleTransition(transition, this.#easing(Math.max(0, t)));
|
|
787
|
+
this.#raf = requestAnimationFrame(this.#tick);
|
|
788
|
+
}
|
|
789
|
+
this._ticking = true;
|
|
790
|
+
try {
|
|
791
|
+
this.requestUpdate();
|
|
792
|
+
} finally {
|
|
793
|
+
this._ticking = false;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
static {
|
|
798
|
+
this.shadowRootOptions = {
|
|
799
|
+
...LitElement.shadowRootOptions,
|
|
800
|
+
delegatesFocus: true
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
get selectedNodes() {
|
|
804
|
+
return this._selectedNodes;
|
|
805
|
+
}
|
|
806
|
+
/** Current zoom root. `undefined` when showing the full tree. Pure view-state. */
|
|
807
|
+
get focusedNode() {
|
|
808
|
+
if (this._zoomId === void 0) return void 0;
|
|
809
|
+
return findSunburstNodeById(this.#effectiveRoot, this._zoomId);
|
|
810
|
+
}
|
|
811
|
+
#viewState;
|
|
812
|
+
/** Saved selection for keys not (yet) loaded. */
|
|
813
|
+
#pendingSelected;
|
|
814
|
+
/** Saved zoom path not (yet) resolvable. */
|
|
815
|
+
#pendingZoom;
|
|
816
|
+
#pendingFocus;
|
|
817
|
+
#layout;
|
|
818
|
+
#view;
|
|
819
|
+
#rings;
|
|
820
|
+
#targets;
|
|
821
|
+
#displayed;
|
|
822
|
+
#transition;
|
|
823
|
+
#animStart;
|
|
824
|
+
#easing;
|
|
825
|
+
#raf;
|
|
826
|
+
#fontSize;
|
|
827
|
+
/** Unique per instance — guide-path ids are referenced by `<textPath href>`. */
|
|
828
|
+
#labelIdPrefix;
|
|
829
|
+
#filtered;
|
|
830
|
+
#modelUnsubscribe;
|
|
831
|
+
#pendingIds;
|
|
832
|
+
#aborters;
|
|
833
|
+
#anchorIndex;
|
|
834
|
+
#ownedRegistry;
|
|
835
|
+
#unregisterSearchCommands;
|
|
836
|
+
#resizeObserver;
|
|
837
|
+
get #effectiveRoot() {
|
|
838
|
+
return this.model?.root ?? this.root;
|
|
839
|
+
}
|
|
840
|
+
get #zoomEntryId() {
|
|
841
|
+
return this._zoomId ?? this.#effectiveRoot?.item.key;
|
|
842
|
+
}
|
|
843
|
+
/** Settled, interactive arcs (the centre excluded), in ring-major order. */
|
|
844
|
+
#visibleArcs() {
|
|
845
|
+
const out = [];
|
|
846
|
+
for (const [id, target] of this.#targets) {
|
|
847
|
+
const entry = this.#layout.get(id);
|
|
848
|
+
if (entry && target.depth >= 0) out.push(entry);
|
|
849
|
+
}
|
|
850
|
+
return out;
|
|
851
|
+
}
|
|
852
|
+
requestUpdate(...args) {
|
|
853
|
+
if (!this._ticking) this._layoutDirty = true;
|
|
854
|
+
super.requestUpdate(...args);
|
|
855
|
+
}
|
|
856
|
+
connectedCallback() {
|
|
857
|
+
super.connectedCallback();
|
|
858
|
+
this.setAttribute(`data-input-mode`, `mouse`);
|
|
859
|
+
const registry = this.#ownedRegistry = createCommandRegistry();
|
|
860
|
+
registry.register({
|
|
861
|
+
id: `select-all`,
|
|
862
|
+
label: `Select All`,
|
|
863
|
+
description: `Select all visible items`,
|
|
864
|
+
execute: () => this.selectAll()
|
|
865
|
+
});
|
|
866
|
+
registry.register({
|
|
867
|
+
id: `deselect-all`,
|
|
868
|
+
label: `Deselect All`,
|
|
869
|
+
description: `Clear all selection`,
|
|
870
|
+
execute: () => this.deselectAll()
|
|
871
|
+
});
|
|
872
|
+
registry.register({
|
|
873
|
+
id: `zoom-up`,
|
|
874
|
+
label: `Zoom Out One Level`,
|
|
875
|
+
description: `Zoom to the parent of the centre`,
|
|
876
|
+
execute: () => this.zoomUp()
|
|
877
|
+
});
|
|
878
|
+
this.registry = registry;
|
|
879
|
+
this.#syncSearchCommands();
|
|
880
|
+
if (typeof ResizeObserver !== `undefined`) {
|
|
881
|
+
this.#resizeObserver = new ResizeObserver(([entry]) => {
|
|
882
|
+
const { width, height } = entry.contentRect;
|
|
883
|
+
this.#fontSize = Number.parseFloat(getComputedStyle(this).getPropertyValue(`--sunbursttree-font-size`)) || 11;
|
|
884
|
+
if (width !== this._size.width || height !== this._size.height) this._size = {
|
|
885
|
+
width,
|
|
886
|
+
height
|
|
887
|
+
};
|
|
888
|
+
});
|
|
889
|
+
this.#resizeObserver.observe(this);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
disconnectedCallback() {
|
|
893
|
+
super.disconnectedCallback();
|
|
894
|
+
this.#modelUnsubscribe?.();
|
|
895
|
+
this.#modelUnsubscribe = void 0;
|
|
896
|
+
for (const aborter of this.#aborters.values()) aborter.abort();
|
|
897
|
+
this.#aborters.clear();
|
|
898
|
+
this.#pendingIds.clear();
|
|
899
|
+
this.#resizeObserver?.disconnect();
|
|
900
|
+
this.#resizeObserver = void 0;
|
|
901
|
+
if (this.#raf !== void 0) cancelAnimationFrame(this.#raf);
|
|
902
|
+
this.#raf = void 0;
|
|
903
|
+
if (this.#transition) this.#displayed = settleTransition(this.#transition);
|
|
904
|
+
this.#transition = void 0;
|
|
905
|
+
if (this.#ownedRegistry) {
|
|
906
|
+
for (const id of [
|
|
907
|
+
`select-all`,
|
|
908
|
+
`deselect-all`,
|
|
909
|
+
`zoom-up`
|
|
910
|
+
]) this.#ownedRegistry.unregister(id);
|
|
911
|
+
this.#ownedRegistry = void 0;
|
|
912
|
+
this.registry = void 0;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
#effectiveSearchModes() {
|
|
916
|
+
const raw = this.searchModes?.trim();
|
|
917
|
+
if (raw === void 0) return [...LIST_SEARCH_MODES];
|
|
918
|
+
const requested = new Set(raw.split(/\s+/));
|
|
919
|
+
return LIST_SEARCH_MODES.filter((m) => requested.has(m));
|
|
920
|
+
}
|
|
921
|
+
#syncSearchCommands() {
|
|
922
|
+
if (!this.#ownedRegistry) return;
|
|
923
|
+
this.search.setModes(this.#effectiveSearchModes());
|
|
924
|
+
this.#unregisterSearchCommands?.();
|
|
925
|
+
this.#unregisterSearchCommands = registerListSearchCommands(this.#ownedRegistry, this.search);
|
|
926
|
+
}
|
|
927
|
+
willUpdate(changed) {
|
|
928
|
+
if (changed.has(`root`) && changed.get(`root`) !== void 0) this.#anchorIndex = void 0;
|
|
929
|
+
if (changed.has(`interactionMode`) && changed.get(`interactionMode`) !== void 0) {
|
|
930
|
+
this.#anchorIndex = void 0;
|
|
931
|
+
this.#clearSelectionInternal();
|
|
932
|
+
}
|
|
933
|
+
if (this._layoutDirty) {
|
|
934
|
+
this._layoutDirty = false;
|
|
935
|
+
this.#reconcileViewState();
|
|
936
|
+
this.#retarget();
|
|
937
|
+
this.#viewState.restoring(() => this.#reconcileFocus());
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
updated(changed) {
|
|
941
|
+
if (changed.has(`model`)) {
|
|
942
|
+
this.#modelUnsubscribe?.();
|
|
943
|
+
this.#modelUnsubscribe = this.model?._subscribe(() => this.requestUpdate());
|
|
944
|
+
}
|
|
945
|
+
if (changed.has(`searchModes`)) this.#syncSearchCommands();
|
|
946
|
+
this.#ensureVisibleChildren();
|
|
947
|
+
this.#continueRestoreLoads();
|
|
948
|
+
}
|
|
949
|
+
#outerRadius() {
|
|
950
|
+
const width = this._size.width || FALLBACK_SIZE;
|
|
951
|
+
const height = this._size.height || FALLBACK_SIZE;
|
|
952
|
+
return Math.max(0, Math.min(width, height) / 2 - 2);
|
|
953
|
+
}
|
|
954
|
+
/** Recompute the layout and start animating towards it (if anything changed). */
|
|
955
|
+
#retarget() {
|
|
956
|
+
const root = this.#effectiveRoot;
|
|
957
|
+
const prevLayout = this.#layout;
|
|
958
|
+
const prevView = this.#view;
|
|
959
|
+
const maxDepth = Math.max(1, Math.floor(this.maxDepth));
|
|
960
|
+
const zoomDepth = this._zoomId === void 0 ? 0 : sunburstIdToPath(this._zoomId).length - 1;
|
|
961
|
+
const layout = computeSunburstLayout(root, {
|
|
962
|
+
valueAccessor: this.valueAccessor,
|
|
963
|
+
maxDepth: Math.max(zoomDepth, prevView.depth) + maxDepth
|
|
964
|
+
});
|
|
965
|
+
if (this._zoomId !== void 0 && !layout.has(this._zoomId)) this._zoomId = void 0;
|
|
966
|
+
const zoomId = this.#zoomEntryId;
|
|
967
|
+
const view = sunburstViewFor(zoomId === void 0 ? void 0 : layout.get(zoomId));
|
|
968
|
+
const rings = computeRings(this.#outerRadius(), maxDepth, this.centerRatio);
|
|
969
|
+
const targets = computeTargets({
|
|
970
|
+
layout,
|
|
971
|
+
view,
|
|
972
|
+
rings,
|
|
973
|
+
zoomId,
|
|
974
|
+
maxDepth,
|
|
975
|
+
minArcLength: this.minArcLength,
|
|
976
|
+
displayed: this.#displayed
|
|
977
|
+
});
|
|
978
|
+
this.#layout = layout;
|
|
979
|
+
this.#view = view;
|
|
980
|
+
this.#rings = rings;
|
|
981
|
+
if (sameTargets(targets, this.#targets)) return;
|
|
982
|
+
this.#targets = targets;
|
|
983
|
+
const transition = planTransition({
|
|
984
|
+
current: this.#displayed,
|
|
985
|
+
targets,
|
|
986
|
+
enter: (id, target) => {
|
|
987
|
+
const before = prevLayout.get(id);
|
|
988
|
+
if (before) {
|
|
989
|
+
const p = projectArc(before, prevView);
|
|
990
|
+
return {
|
|
991
|
+
...target,
|
|
992
|
+
...p,
|
|
993
|
+
opacity: 0
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
...target,
|
|
998
|
+
a1: target.a0
|
|
999
|
+
};
|
|
1000
|
+
},
|
|
1001
|
+
exit: (id, shown) => {
|
|
1002
|
+
const now = layout.get(id);
|
|
1003
|
+
if (now) return {
|
|
1004
|
+
...shown,
|
|
1005
|
+
...projectArc(now, view),
|
|
1006
|
+
opacity: 0
|
|
1007
|
+
};
|
|
1008
|
+
const mid = (shown.a0 + shown.a1) / 2;
|
|
1009
|
+
return {
|
|
1010
|
+
...shown,
|
|
1011
|
+
a0: mid,
|
|
1012
|
+
a1: mid,
|
|
1013
|
+
opacity: 0
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
this.#startTransition(transition);
|
|
1018
|
+
}
|
|
1019
|
+
#startTransition(transition) {
|
|
1020
|
+
if ((prefersReducedMotion() ? 0 : this.animationDuration) <= 0 || !this.isConnected) {
|
|
1021
|
+
this.#transition = void 0;
|
|
1022
|
+
this.#displayed = settleTransition(transition);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
this.#easing = this.#transition ? easeOutCubic : easeInOutCubic;
|
|
1026
|
+
this.#transition = transition;
|
|
1027
|
+
this.#displayed = sampleTransition(transition, 0);
|
|
1028
|
+
this.#animStart = performance.now();
|
|
1029
|
+
this.#raf ??= requestAnimationFrame(this.#tick);
|
|
1030
|
+
}
|
|
1031
|
+
#tick;
|
|
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() {
|
|
1075
|
+
const root = this.#effectiveRoot;
|
|
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);
|
|
1132
|
+
}
|
|
1133
|
+
#ensureVisibleChildren() {
|
|
1134
|
+
if (!this.model && !this.loadChildren) return;
|
|
1135
|
+
for (const id of this.#targets.keys()) {
|
|
1136
|
+
const entry = this.#layout.get(id);
|
|
1137
|
+
if (!entry || entry.node.children !== void 0 || entry.node.item.isLeaf === true || this.#pendingIds.has(id)) continue;
|
|
1138
|
+
this.#loadChildren(id, entry.node, entry.depth - 1);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
async #loadChildren(id, node, depth) {
|
|
1142
|
+
this.#pendingIds.add(id);
|
|
1143
|
+
const controller = new AbortController();
|
|
1144
|
+
this.#aborters.set(id, controller);
|
|
1145
|
+
try {
|
|
1146
|
+
const children = this.model ? await this.model.getChildren(node, depth, controller.signal) : [...await collectDataResult(this.loadChildren({
|
|
1147
|
+
node,
|
|
1148
|
+
depth
|
|
1149
|
+
}, controller.signal), controller.signal)];
|
|
1150
|
+
if (controller.signal.aborted) return;
|
|
1151
|
+
node.children = children;
|
|
1152
|
+
} catch {
|
|
1153
|
+
if (controller.signal.aborted) return;
|
|
1154
|
+
node.children = [];
|
|
1155
|
+
} finally {
|
|
1156
|
+
this.#pendingIds.delete(id);
|
|
1157
|
+
this.#aborters.delete(id);
|
|
1158
|
+
if (!controller.signal.aborted) this.requestUpdate();
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
#canSelect(node) {
|
|
1162
|
+
if (this.selectionFilter === `none`) return false;
|
|
1163
|
+
if (this.selectionFilter === `leaf`) return node.item.isLeaf === true;
|
|
1164
|
+
if (this.selectionFilter === `branch`) return node.item.isLeaf === false || node.children !== void 0 && node.children.length > 0;
|
|
1165
|
+
return true;
|
|
1166
|
+
}
|
|
1167
|
+
#clearSelectionInternal() {
|
|
1168
|
+
this._selectedNodes = /* @__PURE__ */ new Set();
|
|
1169
|
+
this.selectedNode = void 0;
|
|
1170
|
+
}
|
|
1171
|
+
#commitSelection(next, primary = [...next].at(-1)) {
|
|
1172
|
+
const previous = this._selectedNodes;
|
|
1173
|
+
this._selectedNodes = next;
|
|
1174
|
+
this.selectedNode = primary;
|
|
1175
|
+
if (!this.#viewState.isRestoring) this.#pendingSelected = EMPTY_KEYS;
|
|
1176
|
+
this.#viewState.notify();
|
|
1177
|
+
this.dispatchEvent(new CustomEvent(`select`, {
|
|
1178
|
+
detail: {
|
|
1179
|
+
selected: new Set(next),
|
|
1180
|
+
previous
|
|
1181
|
+
},
|
|
1182
|
+
bubbles: true,
|
|
1183
|
+
composed: true
|
|
1184
|
+
}));
|
|
1185
|
+
}
|
|
1186
|
+
select(node) {
|
|
1187
|
+
if (this.#canSelect(node)) this.#commitSelection(/* @__PURE__ */ new Set([node]), node);
|
|
1188
|
+
}
|
|
1189
|
+
deselect(node) {
|
|
1190
|
+
if (!this._selectedNodes.has(node)) return;
|
|
1191
|
+
const next = new Set(this._selectedNodes);
|
|
1192
|
+
next.delete(node);
|
|
1193
|
+
this.#commitSelection(next, this.selectedNode === node ? [...next].at(-1) : this.selectedNode);
|
|
1194
|
+
}
|
|
1195
|
+
clearSelection() {
|
|
1196
|
+
this.#pendingSelected = EMPTY_KEYS;
|
|
1197
|
+
if (this._selectedNodes.size > 0) this.#commitSelection(/* @__PURE__ */ new Set());
|
|
1198
|
+
}
|
|
1199
|
+
selectAll() {
|
|
1200
|
+
this.#commitSelection(/* @__PURE__ */ new Set([...this._selectedNodes, ...this.#visibleArcs().map((a) => a.node).filter((n) => this.#canSelect(n))]));
|
|
1201
|
+
}
|
|
1202
|
+
deselectAll() {
|
|
1203
|
+
this.#commitSelection(/* @__PURE__ */ new Set());
|
|
1204
|
+
}
|
|
1205
|
+
selectMany(nodes) {
|
|
1206
|
+
this.#commitSelection(new Set([...nodes].filter((n) => this.#canSelect(n))));
|
|
1207
|
+
}
|
|
1208
|
+
#idOf(node) {
|
|
1209
|
+
for (const entry of this.#layout.values()) if (entry.node === node) return entry.id;
|
|
1210
|
+
return findSunburstId(this.#effectiveRoot, (n) => n === node);
|
|
1211
|
+
}
|
|
1212
|
+
#setZoom(id) {
|
|
1213
|
+
if (!this.#viewState.isRestoring) this.#pendingZoom = void 0;
|
|
1214
|
+
const zoomId = id === this.#effectiveRoot?.item.key ? void 0 : id;
|
|
1215
|
+
if (zoomId === this._zoomId) return;
|
|
1216
|
+
const previousId = this._zoomId;
|
|
1217
|
+
const previous = this.focusedNode;
|
|
1218
|
+
this._zoomId = zoomId;
|
|
1219
|
+
this.tickled.clearTickled();
|
|
1220
|
+
this.#viewState.notify();
|
|
1221
|
+
const node = this.focusedNode;
|
|
1222
|
+
const depthOf = (key) => key === void 0 ? -1 : sunburstIdToPath(key).length - 2;
|
|
1223
|
+
this.dispatchEvent(new CustomEvent(`zoom`, {
|
|
1224
|
+
detail: {
|
|
1225
|
+
node,
|
|
1226
|
+
depth: depthOf(zoomId)
|
|
1227
|
+
},
|
|
1228
|
+
bubbles: true,
|
|
1229
|
+
composed: true
|
|
1230
|
+
}));
|
|
1231
|
+
if (zoomId !== void 0 && (previousId === void 0 || isSunburstAncestorId(previousId, zoomId)) && node) this.dispatchEvent(new CustomEvent(`expand`, {
|
|
1232
|
+
detail: {
|
|
1233
|
+
node,
|
|
1234
|
+
depth: depthOf(zoomId)
|
|
1235
|
+
},
|
|
1236
|
+
bubbles: true,
|
|
1237
|
+
composed: true
|
|
1238
|
+
}));
|
|
1239
|
+
else if (previous) this.dispatchEvent(new CustomEvent(`collapse`, {
|
|
1240
|
+
detail: {
|
|
1241
|
+
node: previous,
|
|
1242
|
+
depth: depthOf(previousId)
|
|
1243
|
+
},
|
|
1244
|
+
bubbles: true,
|
|
1245
|
+
composed: true
|
|
1246
|
+
}));
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Zoom so `node` becomes the centre, its children the first ring. Fires
|
|
1250
|
+
* `zoom` and `expand` (or `collapse` when `node` is an ancestor of the
|
|
1251
|
+
* current centre). Pure view-state — `root` is never mutated.
|
|
1252
|
+
*/
|
|
1253
|
+
zoomTo(node) {
|
|
1254
|
+
const id = this.#idOf(node);
|
|
1255
|
+
if (id !== void 0) this.#setZoom(id);
|
|
1256
|
+
}
|
|
1257
|
+
/** Zoom out one level: the centre's parent becomes the centre. */
|
|
1258
|
+
zoomUp() {
|
|
1259
|
+
if (this._zoomId !== void 0) this.#setZoom(sunburstParentId(this._zoomId));
|
|
1260
|
+
}
|
|
1261
|
+
/** Zoom all the way out to the full tree. */
|
|
1262
|
+
zoomOut() {
|
|
1263
|
+
this.#setZoom(void 0);
|
|
1264
|
+
}
|
|
1265
|
+
#canZoom(entry) {
|
|
1266
|
+
if (entry.x1 - entry.x0 <= 0 || entry.node.item.isLeaf === true) return false;
|
|
1267
|
+
if (entry.node.children !== void 0) return entry.node.children.length > 0;
|
|
1268
|
+
return this.model !== void 0 || this.loadChildren !== void 0;
|
|
1269
|
+
}
|
|
1270
|
+
/** Zoom so `id` is in the first ring (its parent at the centre). */
|
|
1271
|
+
#reveal(id) {
|
|
1272
|
+
const root = this.#effectiveRoot;
|
|
1273
|
+
const node = findSunburstNodeById(root, id);
|
|
1274
|
+
if (!node) return void 0;
|
|
1275
|
+
const parent = sunburstParentId(id);
|
|
1276
|
+
if (parent !== void 0) this.#setZoom(parent);
|
|
1277
|
+
this.tickled.setTickled(id);
|
|
1278
|
+
return node;
|
|
1279
|
+
}
|
|
1280
|
+
/** Zoom to reveal the node at `keys` (path from the root's children), then select it. */
|
|
1281
|
+
navigateTo(keys) {
|
|
1282
|
+
const root = this.#effectiveRoot;
|
|
1283
|
+
if (!root || keys.length === 0) return false;
|
|
1284
|
+
const node = this.#reveal(sunburstPathToId([root.item.key, ...keys]));
|
|
1285
|
+
if (!node) return false;
|
|
1286
|
+
this.select(node);
|
|
1287
|
+
return true;
|
|
1288
|
+
}
|
|
1289
|
+
/** Zoom + select the first node (depth-first) matching `predicate`. */
|
|
1290
|
+
navigateToNode(predicate) {
|
|
1291
|
+
const root = this.#effectiveRoot;
|
|
1292
|
+
const id = findSunburstId(root, (n) => n !== root && predicate(n));
|
|
1293
|
+
if (id === void 0) return false;
|
|
1294
|
+
return this.navigateTo(sunburstIdToPath(id).slice(1));
|
|
1295
|
+
}
|
|
1296
|
+
/** Zoom to reveal the first node with `key` and move the cursor to it, without selecting. */
|
|
1297
|
+
revealKey(key) {
|
|
1298
|
+
const root = this.#effectiveRoot;
|
|
1299
|
+
const id = findSunburstId(root, (n) => n !== root && n.item.key === key);
|
|
1300
|
+
return id !== void 0 && this.#reveal(id) !== void 0;
|
|
1301
|
+
}
|
|
1302
|
+
/** Arcs currently drawn (centre excluded), ring-major, depth from the root's children. */
|
|
1303
|
+
getNodes() {
|
|
1304
|
+
return this.#visibleArcs().map((a) => ({
|
|
1305
|
+
node: a.node,
|
|
1306
|
+
depth: a.depth - 1
|
|
1307
|
+
}));
|
|
1308
|
+
}
|
|
1309
|
+
#isFilteredOut(id) {
|
|
1310
|
+
const predicate = this.filterPredicate;
|
|
1311
|
+
if (!predicate) return false;
|
|
1312
|
+
if (this.#filtered?.predicate !== predicate || this.#filtered.layout !== this.#layout) {
|
|
1313
|
+
const matches = (node, depth) => predicate(node, depth) || (node.children?.some((c) => matches(c, depth + 1)) ?? false);
|
|
1314
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1315
|
+
for (const entry of this.#layout.values()) if (entry.depth > 0 && !matches(entry.node, entry.depth - 1)) ids.add(entry.id);
|
|
1316
|
+
this.#filtered = {
|
|
1317
|
+
predicate,
|
|
1318
|
+
layout: this.#layout,
|
|
1319
|
+
ids
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
return this.#filtered.ids.has(id);
|
|
1323
|
+
}
|
|
1324
|
+
#handleArcClick(entry, event) {
|
|
1325
|
+
this.dispatchEvent(new CustomEvent(`item-click`, {
|
|
1326
|
+
detail: {
|
|
1327
|
+
node: entry.node,
|
|
1328
|
+
depth: entry.depth - 1
|
|
1329
|
+
},
|
|
1330
|
+
bubbles: true,
|
|
1331
|
+
composed: true
|
|
1332
|
+
}));
|
|
1333
|
+
const { node } = entry;
|
|
1334
|
+
const arcs = this.#visibleArcs();
|
|
1335
|
+
const index = arcs.findIndex((a) => a.id === entry.id);
|
|
1336
|
+
if (this.interactionMode === `standard` && (event.ctrlKey || event.metaKey)) {
|
|
1337
|
+
if (this._selectedNodes.has(node)) this.deselect(node);
|
|
1338
|
+
else if (this.#canSelect(node)) this.#commitSelection(/* @__PURE__ */ new Set([...this._selectedNodes, node]), node);
|
|
1339
|
+
this.#anchorIndex = index;
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
if (this.interactionMode === `standard` && event.shiftKey && this.#anchorIndex !== void 0) {
|
|
1343
|
+
const from = Math.min(this.#anchorIndex, index);
|
|
1344
|
+
const to = Math.max(this.#anchorIndex, index);
|
|
1345
|
+
this.#commitSelection(new Set(arcs.slice(from, to + 1).map((a) => a.node).filter((n) => this.#canSelect(n))));
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
if (this.interactionMode !== `manual`) {
|
|
1349
|
+
this.select(node);
|
|
1350
|
+
this.#anchorIndex = index;
|
|
1351
|
+
}
|
|
1352
|
+
if (this.#canZoom(entry)) this.#setZoom(entry.id);
|
|
1353
|
+
else this.tickled.setTickled(entry.id);
|
|
1354
|
+
}
|
|
1355
|
+
#handleArcDblClick(entry) {
|
|
1356
|
+
const detail = {
|
|
1357
|
+
node: entry.node,
|
|
1358
|
+
depth: entry.depth - 1
|
|
1359
|
+
};
|
|
1360
|
+
this.dispatchEvent(new CustomEvent(`item-dblclick`, {
|
|
1361
|
+
detail,
|
|
1362
|
+
bubbles: true,
|
|
1363
|
+
composed: true
|
|
1364
|
+
}));
|
|
1365
|
+
this.dispatchEvent(new CustomEvent(`activate`, {
|
|
1366
|
+
detail,
|
|
1367
|
+
bubbles: true,
|
|
1368
|
+
composed: true
|
|
1369
|
+
}));
|
|
1370
|
+
}
|
|
1371
|
+
#handleKeyDown(event) {
|
|
1372
|
+
if (this.disableKeyboardNavigation) return;
|
|
1373
|
+
if (event.key === `Escape` || event.key === `Backspace`) {
|
|
1374
|
+
if (this._zoomId !== void 0) {
|
|
1375
|
+
event.preventDefault();
|
|
1376
|
+
this.zoomUp();
|
|
1377
|
+
}
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
const arcs = this.#visibleArcs();
|
|
1381
|
+
const current = this.tickled.tickledId;
|
|
1382
|
+
if (event.key === `Enter`) {
|
|
1383
|
+
const entry = current === void 0 ? void 0 : this.#layout.get(current);
|
|
1384
|
+
if (!entry) return;
|
|
1385
|
+
event.preventDefault();
|
|
1386
|
+
if (this.interactionMode !== `manual`) this.select(entry.node);
|
|
1387
|
+
this.dispatchEvent(new CustomEvent(`activate`, {
|
|
1388
|
+
detail: {
|
|
1389
|
+
node: entry.node,
|
|
1390
|
+
depth: entry.depth - 1
|
|
1391
|
+
},
|
|
1392
|
+
bubbles: true,
|
|
1393
|
+
composed: true
|
|
1394
|
+
}));
|
|
1395
|
+
if (this.#canZoom(entry)) this.#setZoom(entry.id);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
const target = sunburstKeyTarget(arcs, current, event.key);
|
|
1399
|
+
if (target === void 0) return;
|
|
1400
|
+
event.preventDefault();
|
|
1401
|
+
this.tickled.setInputMode(`keyboard`);
|
|
1402
|
+
this.tickled.setTickled(target);
|
|
1403
|
+
this.#pendingFocus = void 0;
|
|
1404
|
+
this.#viewState.notify();
|
|
1405
|
+
const entry = this.#layout.get(target);
|
|
1406
|
+
if (entry && this.interactionMode === `implicit`) this.select(entry.node);
|
|
1407
|
+
}
|
|
1408
|
+
#renderArc(id, visual, zoomId) {
|
|
1409
|
+
const entry = this.#layout.get(id);
|
|
1410
|
+
const { inner, outer } = arcRadii(visual.depth, this.#rings);
|
|
1411
|
+
const d = arcPath(visual.a0, visual.a1, inner, outer);
|
|
1412
|
+
const exiting = !this.#targets.has(id);
|
|
1413
|
+
const centre = id === zoomId;
|
|
1414
|
+
const style = `--arc-hue:${visual.hue.toFixed(2)};--arc-depth:${Math.max(0, visual.depth).toFixed(3)};--arc-shade:${visual.shade.toFixed(3)};--arc-centre:${Math.min(1, Math.max(0, -visual.depth)).toFixed(3)};opacity:${visual.opacity.toFixed(3)}`;
|
|
1415
|
+
if (!entry || exiting) return svg`<path class="arc exiting" d=${d} style=${style} aria-hidden="true"></path>`;
|
|
1416
|
+
if (centre) {
|
|
1417
|
+
const canGoUp = this._zoomId !== void 0;
|
|
1418
|
+
return svg`<path
|
|
1419
|
+
class="arc center ${canGoUp ? `` : `leaf`}"
|
|
1420
|
+
part="center"
|
|
1421
|
+
d=${d}
|
|
1422
|
+
style=${style}
|
|
1423
|
+
role=${canGoUp ? `button` : `presentation`}
|
|
1424
|
+
aria-label=${canGoUp ? `Zoom out from ${entry.node.item.label}` : nothing}
|
|
1425
|
+
@click=${() => this.zoomUp()}
|
|
1426
|
+
@pointerenter=${() => this.tickled.clearTickled()}
|
|
1427
|
+
></path>`;
|
|
1428
|
+
}
|
|
1429
|
+
const { node } = entry;
|
|
1430
|
+
const selected = this._selectedNodes.has(node);
|
|
1431
|
+
const tickled = this.tickled.isTickled(id);
|
|
1432
|
+
const classes = [
|
|
1433
|
+
`arc`,
|
|
1434
|
+
selected ? `selected` : ``,
|
|
1435
|
+
tickled ? `tickled` : ``,
|
|
1436
|
+
this.#isFilteredOut(id) ? `filtered` : ``,
|
|
1437
|
+
this.#pendingIds.has(id) ? `loading` : ``
|
|
1438
|
+
].join(` `);
|
|
1439
|
+
return svg`<path
|
|
1440
|
+
class=${classes}
|
|
1441
|
+
part="arc"
|
|
1442
|
+
d=${d}
|
|
1443
|
+
style=${style}
|
|
1444
|
+
data-id=${id}
|
|
1445
|
+
data-key=${node.item.key}
|
|
1446
|
+
role="treeitem"
|
|
1447
|
+
aria-level=${entry.depth}
|
|
1448
|
+
aria-selected=${selected}
|
|
1449
|
+
aria-setsize=${entry.setSize}
|
|
1450
|
+
aria-posinset=${entry.index + 1}
|
|
1451
|
+
aria-label=${node.item.label}
|
|
1452
|
+
@click=${(e) => this.#handleArcClick(entry, e)}
|
|
1453
|
+
@dblclick=${() => this.#handleArcDblClick(entry)}
|
|
1454
|
+
@pointerenter=${() => this.tickled.handlePointerEnter(id)}
|
|
1455
|
+
></path>`;
|
|
1456
|
+
}
|
|
1457
|
+
render() {
|
|
1458
|
+
if (!this.#effectiveRoot) return html`<div class="empty-state">No data</div>${this.search.renderField()}`;
|
|
1459
|
+
const width = this._size.width || FALLBACK_SIZE;
|
|
1460
|
+
const height = this._size.height || FALLBACK_SIZE;
|
|
1461
|
+
const zoomId = this.#zoomEntryId;
|
|
1462
|
+
const tickledId = this.tickled.tickledId;
|
|
1463
|
+
return html`
|
|
1464
|
+
<svg
|
|
1465
|
+
class="sunburst"
|
|
1466
|
+
viewBox="${-width / 2} ${-height / 2} ${width} ${height}"
|
|
1467
|
+
role="tree"
|
|
1468
|
+
tabindex="0"
|
|
1469
|
+
aria-label=${this.#effectiveRoot.item.label}
|
|
1470
|
+
data-input-mode=${this.tickled.inputMode}
|
|
1471
|
+
@keydown=${(e) => this.#handleKeyDown(e)}
|
|
1472
|
+
@pointerleave=${() => this.tickled.clearTickled()}
|
|
1473
|
+
>
|
|
1474
|
+
<g class="arcs">${repeat(this.#displayed, ([id]) => id, ([id, visual]) => this.#renderArc(id, visual, zoomId))}</g>
|
|
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)}
|
|
1484
|
+
</svg>
|
|
1485
|
+
${this.search.renderField()}
|
|
1486
|
+
`;
|
|
1487
|
+
}
|
|
1488
|
+
static {
|
|
1489
|
+
this.styles = [
|
|
1490
|
+
themeFallbacks,
|
|
1491
|
+
listSearchStyles,
|
|
1492
|
+
sunburstTreeStyles
|
|
1493
|
+
];
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
__decorate([property({ type: Object })], SunburstTreeListElement.prototype, "root", void 0);
|
|
1497
|
+
__decorate([property({ attribute: false })], SunburstTreeListElement.prototype, "model", void 0);
|
|
1498
|
+
__decorate([property({ attribute: false })], SunburstTreeListElement.prototype, "loadChildren", void 0);
|
|
1499
|
+
__decorate([property({
|
|
1500
|
+
reflect: true,
|
|
1501
|
+
attribute: `exclusivity`
|
|
1502
|
+
})], SunburstTreeListElement.prototype, "exclusivity", void 0);
|
|
1503
|
+
__decorate([property({
|
|
1504
|
+
reflect: true,
|
|
1505
|
+
attribute: `selection-filter`
|
|
1506
|
+
})], SunburstTreeListElement.prototype, "selectionFilter", void 0);
|
|
1507
|
+
__decorate([property({
|
|
1508
|
+
type: String,
|
|
1509
|
+
attribute: `interaction-mode`
|
|
1510
|
+
})], SunburstTreeListElement.prototype, "interactionMode", void 0);
|
|
1511
|
+
__decorate([property({
|
|
1512
|
+
type: Number,
|
|
1513
|
+
attribute: `max-depth`
|
|
1514
|
+
})], SunburstTreeListElement.prototype, "maxDepth", void 0);
|
|
1515
|
+
__decorate([property({
|
|
1516
|
+
type: Number,
|
|
1517
|
+
attribute: `center-ratio`
|
|
1518
|
+
})], SunburstTreeListElement.prototype, "centerRatio", void 0);
|
|
1519
|
+
__decorate([property({
|
|
1520
|
+
type: Number,
|
|
1521
|
+
attribute: `min-arc-length`
|
|
1522
|
+
})], SunburstTreeListElement.prototype, "minArcLength", void 0);
|
|
1523
|
+
__decorate([property({
|
|
1524
|
+
type: Boolean,
|
|
1525
|
+
attribute: `disable-labels`
|
|
1526
|
+
})], SunburstTreeListElement.prototype, "disableLabels", void 0);
|
|
1527
|
+
__decorate([property({
|
|
1528
|
+
type: Boolean,
|
|
1529
|
+
attribute: `curved-labels`,
|
|
1530
|
+
reflect: true
|
|
1531
|
+
})], SunburstTreeListElement.prototype, "curvedLabels", void 0);
|
|
1532
|
+
__decorate([property({
|
|
1533
|
+
type: Number,
|
|
1534
|
+
attribute: `animation-duration`
|
|
1535
|
+
})], SunburstTreeListElement.prototype, "animationDuration", void 0);
|
|
1536
|
+
__decorate([property({ attribute: false })], SunburstTreeListElement.prototype, "valueAccessor", void 0);
|
|
1537
|
+
__decorate([property({ attribute: false })], SunburstTreeListElement.prototype, "valueFormatter", void 0);
|
|
1538
|
+
__decorate([property({ attribute: false })], SunburstTreeListElement.prototype, "selectedNode", void 0);
|
|
1539
|
+
__decorate([state()], SunburstTreeListElement.prototype, "_selectedNodes", void 0);
|
|
1540
|
+
__decorate([property({ attribute: false })], SunburstTreeListElement.prototype, "filterPredicate", void 0);
|
|
1541
|
+
__decorate([property({
|
|
1542
|
+
type: Boolean,
|
|
1543
|
+
attribute: `disable-keyboard-navigation`
|
|
1544
|
+
})], SunburstTreeListElement.prototype, "disableKeyboardNavigation", void 0);
|
|
1545
|
+
__decorate([property({ attribute: false })], SunburstTreeListElement.prototype, "registry", void 0);
|
|
1546
|
+
__decorate([property({
|
|
1547
|
+
type: String,
|
|
1548
|
+
attribute: `search-modes`,
|
|
1549
|
+
reflect: true
|
|
1550
|
+
})], SunburstTreeListElement.prototype, "searchModes", void 0);
|
|
1551
|
+
__decorate([state()], SunburstTreeListElement.prototype, "_zoomId", void 0);
|
|
1552
|
+
__decorate([property({ attribute: false })], SunburstTreeListElement.prototype, "restoreLoads", void 0);
|
|
1553
|
+
__decorate([state()], SunburstTreeListElement.prototype, "_size", void 0);
|
|
1554
|
+
SunburstTreeListElement = __decorate([safeCustomElement(`ixfx-sunbursttree-list`)], SunburstTreeListElement);
|
|
1555
|
+
//#endregion
|
|
1556
|
+
export { findSunburstId as a, sunburstChildId as c, sunburstParentId as d, sunburstPathToId as f, defaultSunburstValueAccessor as i, sunburstHasChildren as l, computeSunburstLayout as n, findSunburstNodeById as o, SUNBURST_PATH_SEPARATOR as p, createValueResolver as r, isSunburstAncestorId as s, SunburstTreeListElement as t, sunburstIdToPath as u };
|