@ixfx/components 0.10.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/index.d.ts +233 -21
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +2101 -150
- package/bundle/index.js.map +1 -1
- package/bundle/style.css +38 -85
- package/dist/ac-text.js +1 -1
- package/dist/ac-token.js +2 -2
- package/dist/{colour-picker-R73i0QTP.js → colour-picker-WDrPuqoO.js} +2 -2
- package/dist/{colour-picker-R73i0QTP.js.map → colour-picker-WDrPuqoO.js.map} +1 -1
- package/dist/colour-picker.js +1 -1
- package/dist/crumbs.d.ts +1 -1
- package/dist/crumbs.js +2 -2
- package/dist/{data-grid-C5hk_pzK.d.ts → data-grid-pv6eaJCZ.d.ts} +4 -3
- package/dist/data-grid-pv6eaJCZ.d.ts.map +1 -0
- package/dist/data-grid.d.ts +2 -2
- package/dist/data-grid.js +28 -10
- package/dist/data-grid.js.map +1 -1
- package/dist/defaults-kVl1t2tT.js +59 -0
- package/dist/defaults-kVl1t2tT.js.map +1 -0
- package/dist/{detail-list-CxPpv89T.d.ts → detail-list-BA3RVVCB.d.ts} +4 -4
- package/dist/{detail-list-CxPpv89T.d.ts.map → detail-list-BA3RVVCB.d.ts.map} +1 -1
- package/dist/detail-list.d.ts +3 -3
- package/dist/detail-list.js +4 -4
- package/dist/{grid-list-DSnYOGh7.js → grid-list-CIc4jo-x.js} +34 -7
- package/dist/grid-list-CIc4jo-x.js.map +1 -0
- package/dist/{grid-list-BGvoWbZz.d.ts → grid-list-DWXJoRnK.d.ts} +4 -4
- package/dist/{grid-list-BGvoWbZz.d.ts.map → grid-list-DWXJoRnK.d.ts.map} +1 -1
- package/dist/grid-list.d.ts +2 -2
- package/dist/grid-list.js +1 -1
- package/dist/handle-DAXs7kbJ.js +656 -0
- package/dist/handle-DAXs7kbJ.js.map +1 -0
- package/dist/{icon-BCJJYEpm.js → icon-1gpwtPx8.js} +10 -1
- package/dist/icon-1gpwtPx8.js.map +1 -0
- package/dist/icon-CDGprSdE.d.ts.map +1 -1
- package/dist/icons.d.ts +1 -1
- package/dist/icons.js +2 -2
- package/dist/{incr-search-C05WjbZh.js → incr-search-D91M4Ozq.js} +5 -4
- package/dist/incr-search-D91M4Ozq.js.map +1 -0
- package/dist/incr-search.d.ts +1 -1
- package/dist/incr-search.js +1 -1
- package/dist/index-9AhaHIT2.d.ts.map +1 -1
- package/dist/index-CYndvTWH.d.ts +38 -0
- package/dist/index-CYndvTWH.d.ts.map +1 -0
- package/dist/{index--5BSdbnL.d.ts → index-CsaQwNkz.d.ts} +3 -2
- package/dist/index-CsaQwNkz.d.ts.map +1 -0
- package/dist/index.d.ts +207 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1295 -40
- package/dist/index.js.map +1 -1
- package/dist/{list-native-drag-controller-D97TYjn1.js → list-native-drag-controller-B9xO0XWn.js} +6 -4
- package/dist/list-native-drag-controller-B9xO0XWn.js.map +1 -0
- package/dist/{list-native-drag-controller-DWiXQZou.d.ts → list-native-drag-controller-BAuWAtyQ.d.ts} +2 -2
- package/dist/{list-native-drag-controller-DWiXQZou.d.ts.map → list-native-drag-controller-BAuWAtyQ.d.ts.map} +1 -1
- package/dist/{menu-C8a-84S5.js → menu-B2Qv3Oiz.js} +6 -5
- package/dist/menu-B2Qv3Oiz.js.map +1 -0
- package/dist/{menu-item-DzizkMH-.js → menu-item-Woc8jJc1.js} +4 -4
- package/dist/menu-item-Woc8jJc1.js.map +1 -0
- package/dist/menu.js +2 -2
- package/dist/miller.d.ts +2 -2
- package/dist/miller.js +1 -1
- package/dist/narrowed-text.js +1 -1
- package/dist/{scroll-fade-CAs1EabA.js → scroll-fade-BrNUIYnm.js} +16 -3
- package/dist/scroll-fade-BrNUIYnm.js.map +1 -0
- package/dist/select-horiz.js +1 -1
- package/dist/{split-layout-3yBOEVZW.js → split-layout-BJEU1W0d.js} +2 -2
- package/dist/{split-layout-3yBOEVZW.js.map → split-layout-BJEU1W0d.js.map} +1 -1
- package/dist/split-layout.js +1 -1
- package/dist/style.css +38 -85
- package/dist/tabs.js +1 -1
- package/dist/{timeline-CFlBFFGZ.js → timeline-BP1Ux2EV.js} +2 -2
- package/dist/{timeline-CFlBFFGZ.js.map → timeline-BP1Ux2EV.js.map} +1 -1
- package/dist/timeline.js +1 -1
- package/dist/titlebar.js +1 -1
- package/dist/{tree-C25xYUa6.js → tree-Bm2TzTfe.js} +234 -15
- package/dist/tree-Bm2TzTfe.js.map +1 -0
- package/dist/{tree-component-mw8x-n2Y.d.ts → tree-component-DGmKBbpy.d.ts} +14 -1
- package/dist/tree-component-DGmKBbpy.d.ts.map +1 -0
- package/dist/tree.d.ts +15 -3
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +1 -1
- package/dist/{vertical-list--tFanQwM.d.ts → vertical-list-BWLqy3PJ.d.ts} +3 -3
- package/dist/{vertical-list--tFanQwM.d.ts.map → vertical-list-BWLqy3PJ.d.ts.map} +1 -1
- package/dist/vertical-list.d.ts +2 -2
- package/dist/vertical-list.js +2 -2
- package/docs-user/README.md +1 -0
- package/docs-user/icons.md +24 -10
- package/docs-user/index.json +8 -3
- package/docs-user/llms.txt +2 -1
- package/docs-user/multicol-list.md +128 -0
- package/docs-user/tree.md +12 -0
- package/docs-user/vertical-list.md +65 -9
- package/llms.txt +2 -1
- package/package.json +1 -1
- package/dist/data-grid-C5hk_pzK.d.ts.map +0 -1
- package/dist/defaults-BJwDsMH4.js +0 -86
- package/dist/defaults-BJwDsMH4.js.map +0 -1
- package/dist/grid-list-DSnYOGh7.js.map +0 -1
- package/dist/handle-BPOxBOKf.js +0 -219
- package/dist/handle-BPOxBOKf.js.map +0 -1
- package/dist/icon-BCJJYEpm.js.map +0 -1
- package/dist/incr-search-C05WjbZh.js.map +0 -1
- package/dist/index--5BSdbnL.d.ts.map +0 -1
- package/dist/index-CS9yM8L-.d.ts +0 -47
- package/dist/index-CS9yM8L-.d.ts.map +0 -1
- package/dist/list-native-drag-controller-D97TYjn1.js.map +0 -1
- package/dist/menu-C8a-84S5.js.map +0 -1
- package/dist/menu-item-DzizkMH-.js.map +0 -1
- package/dist/scroll-fade-CAs1EabA.js.map +0 -1
- package/dist/tree-C25xYUa6.js.map +0 -1
- package/dist/tree-component-mw8x-n2Y.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -5,42 +5,42 @@ import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
|
|
|
5
5
|
import { t as popupSurfaceStyles } from "./popup-D80sj2QC.js";
|
|
6
6
|
import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
|
|
7
7
|
import { a as iconBus, c as setIconFont, i as hasIcon, l as svgToDataUri, n as getIconEntry, o as registerFontIcon, r as getIconFont, s as registerIcon, t as getIcon } from "./registry-BEtb8_wB.js";
|
|
8
|
-
import { t as IxfxIconElement } from "./icon-
|
|
8
|
+
import { t as IxfxIconElement } from "./icon-1gpwtPx8.js";
|
|
9
9
|
import { AcTextElement } from "./ac-text.js";
|
|
10
10
|
import { t as DataController } from "./data-controller-DOlmqNKb.js";
|
|
11
|
-
import { a as LIST_SEARCH_COMMAND_IDS, c as createElementListSearchAdapter, d as createDataListSearchAdapter, f as IncrSearchTreeController, h as createFuzzySearch, i as ListSearchController, l as KeyedElementIndex, m as createAcCompletionProvider, n as listSearchStyles, o as registerListSearchCommands, p as createIncrSearch, r as LIST_SEARCH_MODES, s as createElementIncrSearch, t as createTreeListSearchAdapter, u as keyForElement } from "./incr-search-
|
|
11
|
+
import { a as LIST_SEARCH_COMMAND_IDS, c as createElementListSearchAdapter, d as createDataListSearchAdapter, f as IncrSearchTreeController, h as createFuzzySearch, i as ListSearchController, l as KeyedElementIndex, m as createAcCompletionProvider, n as listSearchStyles, o as registerListSearchCommands, p as createIncrSearch, r as LIST_SEARCH_MODES, s as createElementIncrSearch, t as createTreeListSearchAdapter, u as keyForElement } from "./incr-search-D91M4Ozq.js";
|
|
12
12
|
import { AcTokenElement } from "./ac-token.js";
|
|
13
|
-
import { t as handleStyles } from "./handle-
|
|
13
|
+
import { t as handleStyles } from "./handle-DAXs7kbJ.js";
|
|
14
14
|
import { TitlebarElement } from "./titlebar.js";
|
|
15
|
-
import { i as ICON_CLOSE, n as ICON_CHECK, r as ICON_CHEVRON_DOWN, t as ICON_CARET_RIGHT } from "./defaults-
|
|
15
|
+
import { i as ICON_CLOSE, n as ICON_CHECK, r as ICON_CHEVRON_DOWN, t as ICON_CARET_RIGHT } from "./defaults-kVl1t2tT.js";
|
|
16
16
|
import "./icons.js";
|
|
17
17
|
import { t as prominenceStyles } from "./prominence-DlFShic3.js";
|
|
18
18
|
import { n as buttonStyles, t as ButtonElement } from "./button-DsFbV4XD.js";
|
|
19
|
-
import { n as captionForEntry, r as createDefaultListDragPreview, t as ListNativeDragController } from "./list-native-drag-controller-
|
|
19
|
+
import { n as captionForEntry, r as createDefaultListDragPreview, t as ListNativeDragController } from "./list-native-drag-controller-B9xO0XWn.js";
|
|
20
20
|
import { n as isMultiMode, r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-SA4piSBi.js";
|
|
21
21
|
import { a as prefersReducedMotion, i as listItemFadeKeyframes, n as animateListItemIn, o as startListItemOutAnimation, r as animateListItemOut, t as LIST_ITEM_MOTION_MS } from "./list-item-animation-BUoEKi5s.js";
|
|
22
22
|
import "./interaction-BskmlA9t.js";
|
|
23
|
-
import "./menu-item-
|
|
23
|
+
import "./menu-item-Woc8jJc1.js";
|
|
24
24
|
import { Checkbox } from "./checkbox.js";
|
|
25
|
-
import { n as ColourPicker, t as ColourPickerPopup } from "./colour-picker-
|
|
25
|
+
import { n as ColourPicker, t as ColourPickerPopup } from "./colour-picker-WDrPuqoO.js";
|
|
26
26
|
import { LabelledRangeInput } from "./labelled-range-input.js";
|
|
27
27
|
import { t as collectDataResult } from "./data-provider-35TyWclX.js";
|
|
28
|
-
import { a as TreeDataModel, c as pruneDraggedDescendants, i as TreeDropController, n as TreeListElement, o as applyTreeDrop, r as TreeBaseElement, s as dropTargetFor, t as TreeController } from "./tree-
|
|
29
|
-
import { t as scrollFadeStyles } from "./scroll-fade-
|
|
28
|
+
import { a as TreeDataModel, c as pruneDraggedDescendants, i as TreeDropController, n as TreeListElement, o as applyTreeDrop, r as TreeBaseElement, s as dropTargetFor, t as TreeController } from "./tree-Bm2TzTfe.js";
|
|
29
|
+
import { t as scrollFadeStyles } from "./scroll-fade-BrNUIYnm.js";
|
|
30
30
|
import { NarrowedTextElement } from "./narrowed-text.js";
|
|
31
31
|
import { CrumbNavigationElement } from "./crumbs.js";
|
|
32
32
|
import { DataDisplayComponent } from "./data-display.js";
|
|
33
33
|
import { t as createDragGhost } from "./drag-ghost-DtSDvjri.js";
|
|
34
|
-
import { t as menu_exports } from "./menu-
|
|
34
|
+
import { a as menuFromTreeToElements, t as menu_exports } from "./menu-B2Qv3Oiz.js";
|
|
35
35
|
import { t as PanTextElement } from "./pan-text-CqtvjPyK.js";
|
|
36
36
|
import { DataGridElement } from "./data-grid.js";
|
|
37
37
|
import { DetailListElement } from "./detail-list.js";
|
|
38
|
-
import { a as distribute, c as toSnapshot, i as buildModel, l as walk, n as SplitPanelElement, o as normalise, r as applySnapshot, s as resolveTracks, t as SplitLayoutElement } from "./split-layout-
|
|
38
|
+
import { a as distribute, c as toSnapshot, i as buildModel, l as walk, n as SplitPanelElement, o as normalise, r as applySnapshot, s as resolveTracks, t as SplitLayoutElement } from "./split-layout-BJEU1W0d.js";
|
|
39
39
|
import { TabListElement } from "./tabs.js";
|
|
40
40
|
import { EditableLabel } from "./editable-label.js";
|
|
41
41
|
import { n as EditableNumber, t as LabelledSliderInput } from "./labelled-slider-input-CF2RtzgG.js";
|
|
42
42
|
import { t as FormElement } from "./form-CsXJX15p.js";
|
|
43
|
-
import { t as GridListElement } from "./grid-list-
|
|
43
|
+
import { t as GridListElement } from "./grid-list-CIc4jo-x.js";
|
|
44
44
|
import { PanelElement } from "./panel.js";
|
|
45
45
|
import { GroupedItemListerElement } from "./grouped-item-lister.js";
|
|
46
46
|
import { VerticalListElement } from "./vertical-list.js";
|
|
@@ -60,7 +60,7 @@ import { SelectHorizElement } from "./select-horiz.js";
|
|
|
60
60
|
import { SnackbarElement } from "./snackbar.js";
|
|
61
61
|
import { SnapContainer } from "./snap-container.js";
|
|
62
62
|
import { SwipeElement } from "./swipe.js";
|
|
63
|
-
import { a as resolveCssVars, i as attachTimelineInteractions, n as TimelineTrackTabElement, o as resolveGrid, r as timelineStyles, s as TimelineController, t as IxfxTimelineElement } from "./timeline-
|
|
63
|
+
import { a as resolveCssVars, i as attachTimelineInteractions, n as TimelineTrackTabElement, o as resolveGrid, r as timelineStyles, s as TimelineController, t as IxfxTimelineElement } from "./timeline-BP1Ux2EV.js";
|
|
64
64
|
import { TransitoryLabelElement } from "./transitory-label.js";
|
|
65
65
|
import { XyPad } from "./xy-pad.js";
|
|
66
66
|
import { LitElement, css, html, nothing } from "lit";
|
|
@@ -6487,6 +6487,974 @@ __decorate([state()], IcicleTreeListElement.prototype, "_zoomKey", void 0);
|
|
|
6487
6487
|
__decorate([query(`#icicle-tooltip`)], IcicleTreeListElement.prototype, "_tooltipEl", void 0);
|
|
6488
6488
|
IcicleTreeListElement = __decorate([safeCustomElement(`ixfx-icicletree-list`)], IcicleTreeListElement);
|
|
6489
6489
|
//#endregion
|
|
6490
|
+
//#region src/multicol-list/types.ts
|
|
6491
|
+
const ALL_KEY = `__all__`;
|
|
6492
|
+
//#endregion
|
|
6493
|
+
//#region src/multicol-list/column.ts
|
|
6494
|
+
/** Sorts by label, honouring direction. 'All' lives outside the list, so it is not special-cased. */
|
|
6495
|
+
function sortItems(items, direction) {
|
|
6496
|
+
const dir = direction === `desc` ? -1 : 1;
|
|
6497
|
+
return [...items].sort((a, b) => dir * a.label.localeCompare(b.label, void 0, {
|
|
6498
|
+
sensitivity: `base`,
|
|
6499
|
+
numeric: true
|
|
6500
|
+
}));
|
|
6501
|
+
}
|
|
6502
|
+
/**
|
|
6503
|
+
* Nearest-gap math for header drag-reorder (per data-grid's `_beginHeaderDrag`).
|
|
6504
|
+
*
|
|
6505
|
+
* `offsets` is a prefix-sum of column widths (length = count + 1). `x` is the
|
|
6506
|
+
* pointer position in content space (already adjusted for scroll). Returns the
|
|
6507
|
+
* final insertion index once the source column is removed (`insert`), plus the
|
|
6508
|
+
* un-normalised gap offset for the drop caret (`caretOffset`).
|
|
6509
|
+
*/
|
|
6510
|
+
function computeDropGap(offsets, x, count, from) {
|
|
6511
|
+
if (count === 0) return {
|
|
6512
|
+
insert: 0,
|
|
6513
|
+
caretOffset: 0
|
|
6514
|
+
};
|
|
6515
|
+
let bestDist = Number.MAX_SAFE_INTEGER;
|
|
6516
|
+
let caretGap = from;
|
|
6517
|
+
for (let i = 0; i < count; i++) {
|
|
6518
|
+
if (i === from) continue;
|
|
6519
|
+
const left = offsets[i];
|
|
6520
|
+
const right = offsets[i + 1] ?? offsets[offsets.length - 1];
|
|
6521
|
+
const mid = (left + right) / 2;
|
|
6522
|
+
const dist = Math.abs(x - (x < mid ? left : right));
|
|
6523
|
+
if (dist < bestDist) {
|
|
6524
|
+
bestDist = dist;
|
|
6525
|
+
caretGap = x < mid ? i : i + 1;
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
const insert = caretGap > from ? caretGap - 1 : caretGap;
|
|
6529
|
+
return {
|
|
6530
|
+
insert: Math.max(0, Math.min(count - 1, insert)),
|
|
6531
|
+
caretOffset: offsets[caretGap] ?? offsets[offsets.length - 1]
|
|
6532
|
+
};
|
|
6533
|
+
}
|
|
6534
|
+
var MulticolColumnController = class {
|
|
6535
|
+
#category;
|
|
6536
|
+
#categoryDefaults;
|
|
6537
|
+
#selectionMode;
|
|
6538
|
+
#dataProvider;
|
|
6539
|
+
#onSelectionChange;
|
|
6540
|
+
#onRequestData;
|
|
6541
|
+
#abortController = void 0;
|
|
6542
|
+
#host;
|
|
6543
|
+
#allRow;
|
|
6544
|
+
#listerEl;
|
|
6545
|
+
#selectedValues = [];
|
|
6546
|
+
#allSelected = true;
|
|
6547
|
+
#lastItems = [];
|
|
6548
|
+
#syncingSelection = false;
|
|
6549
|
+
#state;
|
|
6550
|
+
constructor(options) {
|
|
6551
|
+
this.id = options.id;
|
|
6552
|
+
this.#category = options.category;
|
|
6553
|
+
this.#categoryDefaults = options.categoryDefaults;
|
|
6554
|
+
this.#selectionMode = options.selectionMode;
|
|
6555
|
+
this.#dataProvider = options.dataProvider;
|
|
6556
|
+
this.#onSelectionChange = options.onSelectionChange;
|
|
6557
|
+
this.#onRequestData = options.onRequestData;
|
|
6558
|
+
this.#state = {
|
|
6559
|
+
id: options.id,
|
|
6560
|
+
categoryKey: options.category.key,
|
|
6561
|
+
sort: options.categoryDefaults?.sort ?? `asc`,
|
|
6562
|
+
grouped: options.categoryDefaults?.grouped ?? false,
|
|
6563
|
+
width: 220
|
|
6564
|
+
};
|
|
6565
|
+
}
|
|
6566
|
+
get state() {
|
|
6567
|
+
return this.getState();
|
|
6568
|
+
}
|
|
6569
|
+
/** Always derived from the live category so a swap is visible everywhere. */
|
|
6570
|
+
get categoryKey() {
|
|
6571
|
+
return this.#category.key;
|
|
6572
|
+
}
|
|
6573
|
+
getState() {
|
|
6574
|
+
return { ...this.#state };
|
|
6575
|
+
}
|
|
6576
|
+
updateState(partial) {
|
|
6577
|
+
const sortChanged = partial.sort !== void 0 && partial.sort !== this.#state.sort;
|
|
6578
|
+
const groupedChanged = partial.grouped !== void 0 && partial.grouped !== this.#state.grouped;
|
|
6579
|
+
this.#state = {
|
|
6580
|
+
...this.#state,
|
|
6581
|
+
...partial
|
|
6582
|
+
};
|
|
6583
|
+
if (sortChanged) this.#repush();
|
|
6584
|
+
if (groupedChanged) this.#syncGrouping();
|
|
6585
|
+
}
|
|
6586
|
+
setWidth(width) {
|
|
6587
|
+
this.#state = {
|
|
6588
|
+
...this.#state,
|
|
6589
|
+
width: Math.max(80, Math.min(480, Math.round(width)))
|
|
6590
|
+
};
|
|
6591
|
+
}
|
|
6592
|
+
setSort(sort) {
|
|
6593
|
+
if (sort === this.#state.sort) return;
|
|
6594
|
+
this.#state = {
|
|
6595
|
+
...this.#state,
|
|
6596
|
+
sort
|
|
6597
|
+
};
|
|
6598
|
+
this.#repush();
|
|
6599
|
+
}
|
|
6600
|
+
setGrouped(grouped) {
|
|
6601
|
+
if (grouped === this.#state.grouped) return;
|
|
6602
|
+
this.#state = {
|
|
6603
|
+
...this.#state,
|
|
6604
|
+
grouped
|
|
6605
|
+
};
|
|
6606
|
+
this.#syncGrouping();
|
|
6607
|
+
}
|
|
6608
|
+
setSelectionMode(mode) {
|
|
6609
|
+
if (mode === this.#selectionMode) return;
|
|
6610
|
+
this.#selectionMode = mode;
|
|
6611
|
+
for (const sub of this.#subComponents()) sub.selectionMode = mode;
|
|
6612
|
+
}
|
|
6613
|
+
setCategory(category) {
|
|
6614
|
+
this.#category = category;
|
|
6615
|
+
this.#categoryDefaults = category.defaults;
|
|
6616
|
+
this.#state = {
|
|
6617
|
+
...this.#state,
|
|
6618
|
+
categoryKey: category.key,
|
|
6619
|
+
sort: category.defaults?.sort ?? `asc`,
|
|
6620
|
+
grouped: category.defaults?.grouped ?? false
|
|
6621
|
+
};
|
|
6622
|
+
this.clearSelection();
|
|
6623
|
+
this.#syncGrouping();
|
|
6624
|
+
}
|
|
6625
|
+
get listerHost() {
|
|
6626
|
+
return this.#host;
|
|
6627
|
+
}
|
|
6628
|
+
get isMounted() {
|
|
6629
|
+
return this.#host !== void 0;
|
|
6630
|
+
}
|
|
6631
|
+
setDataProvider(provider) {
|
|
6632
|
+
this.#dataProvider = provider;
|
|
6633
|
+
}
|
|
6634
|
+
mount(host) {
|
|
6635
|
+
if (this.#host) return;
|
|
6636
|
+
this.#host = host;
|
|
6637
|
+
const allRow = document.createElement(`div`);
|
|
6638
|
+
allRow.className = `all-row`;
|
|
6639
|
+
allRow.dataset.key = ALL_KEY;
|
|
6640
|
+
allRow.setAttribute(`role`, `button`);
|
|
6641
|
+
allRow.setAttribute(`tabindex`, `0`);
|
|
6642
|
+
const allLabel = document.createElement(`span`);
|
|
6643
|
+
allLabel.className = `all-label`;
|
|
6644
|
+
allRow.appendChild(allLabel);
|
|
6645
|
+
const applyAll = () => {
|
|
6646
|
+
if (this.#allSelected) return;
|
|
6647
|
+
this.#setAllSelection();
|
|
6648
|
+
};
|
|
6649
|
+
allRow.addEventListener(`click`, applyAll);
|
|
6650
|
+
allRow.addEventListener(`keydown`, (e) => {
|
|
6651
|
+
if (e.key === `Enter` || e.key === ` `) {
|
|
6652
|
+
e.preventDefault();
|
|
6653
|
+
applyAll();
|
|
6654
|
+
}
|
|
6655
|
+
});
|
|
6656
|
+
this.#allRow = allRow;
|
|
6657
|
+
const lister = document.createElement(`ixfx-grouped-item-lister`);
|
|
6658
|
+
lister.crossGroupSelection = true;
|
|
6659
|
+
lister.factory = () => createVerticalListAdapter(renderItem, {
|
|
6660
|
+
selectionMode: this.#selectionMode,
|
|
6661
|
+
interactionMode: `standard`
|
|
6662
|
+
});
|
|
6663
|
+
lister.addEventListener(`select`, this.#handleSelect);
|
|
6664
|
+
this.#listerEl = lister;
|
|
6665
|
+
this.#syncGrouping();
|
|
6666
|
+
this.#syncAllRow();
|
|
6667
|
+
host.append(allRow, lister);
|
|
6668
|
+
if (this.#lastItems.length > 0) {
|
|
6669
|
+
this.#pushItems();
|
|
6670
|
+
this.#restoreAfterSync();
|
|
6671
|
+
}
|
|
6672
|
+
}
|
|
6673
|
+
unmount() {
|
|
6674
|
+
this.#abortPending();
|
|
6675
|
+
this.#allRow?.remove();
|
|
6676
|
+
this.#listerEl?.remove();
|
|
6677
|
+
this.#allRow = void 0;
|
|
6678
|
+
this.#listerEl = void 0;
|
|
6679
|
+
this.#host = void 0;
|
|
6680
|
+
}
|
|
6681
|
+
requestData(query) {
|
|
6682
|
+
if (!this.#dataProvider) return;
|
|
6683
|
+
this.#abortPending();
|
|
6684
|
+
const controller = new AbortController();
|
|
6685
|
+
this.#abortController = controller;
|
|
6686
|
+
const { signal } = controller;
|
|
6687
|
+
this.#onRequestData(this.id, query, signal);
|
|
6688
|
+
Promise.resolve().then(async () => {
|
|
6689
|
+
const result = this.#dataProvider(query, signal);
|
|
6690
|
+
const items = await collectDataResult(result, signal);
|
|
6691
|
+
if (signal.aborted) return;
|
|
6692
|
+
this.#applyItems(items);
|
|
6693
|
+
}).catch(() => {});
|
|
6694
|
+
}
|
|
6695
|
+
#applyItems(items) {
|
|
6696
|
+
this.#lastItems = items;
|
|
6697
|
+
const valid = this.#selectedValues.filter((v) => items.some((i) => i.key === v));
|
|
6698
|
+
const changed = valid.length !== this.#selectedValues.length;
|
|
6699
|
+
this.#selectedValues = valid;
|
|
6700
|
+
if (valid.length === 0) this.#allSelected = true;
|
|
6701
|
+
if (!this.#listerEl) {
|
|
6702
|
+
if (changed) this.#onSelectionChange(this.id, valid);
|
|
6703
|
+
return;
|
|
6704
|
+
}
|
|
6705
|
+
this.#pushItems();
|
|
6706
|
+
this.#restoreAfterSync();
|
|
6707
|
+
if (changed) this.#onSelectionChange(this.id, valid);
|
|
6708
|
+
}
|
|
6709
|
+
#pushItems() {
|
|
6710
|
+
if (!this.#listerEl) return;
|
|
6711
|
+
const sorted = sortItems(this.#lastItems, this.#state.sort);
|
|
6712
|
+
this.#listerEl.provider = () => sorted;
|
|
6713
|
+
}
|
|
6714
|
+
#repush() {
|
|
6715
|
+
if (!this.#listerEl || this.#lastItems.length === 0) return;
|
|
6716
|
+
this.#pushItems();
|
|
6717
|
+
this.#restoreAfterSync();
|
|
6718
|
+
}
|
|
6719
|
+
/** Grouping config from category defaults; grouping without groupBy is a no-op. */
|
|
6720
|
+
#syncGrouping() {
|
|
6721
|
+
if (!this.#listerEl) return;
|
|
6722
|
+
const groupBy = this.#categoryDefaults?.groupBy;
|
|
6723
|
+
this.#listerEl.groupBy = groupBy;
|
|
6724
|
+
this.#listerEl.grouped = this.#state.grouped && groupBy !== void 0;
|
|
6725
|
+
if (this.#lastItems.length > 0) this.#restoreAfterSync();
|
|
6726
|
+
}
|
|
6727
|
+
#subComponents() {
|
|
6728
|
+
if (!this.#listerEl) return [];
|
|
6729
|
+
return this.#listerEl.querySelectorAll(`:scope > .ungrouped-host > *, :scope > ixfx-panel > *`);
|
|
6730
|
+
}
|
|
6731
|
+
#handleSelect = () => {
|
|
6732
|
+
if (this.#syncingSelection) return;
|
|
6733
|
+
const values = [...this.#listerEl?.selectedItems ?? []].map((i) => i.key).filter((k) => k !== ALL_KEY);
|
|
6734
|
+
if (values.length === this.#selectedValues.length && values.every((v) => this.#selectedValues.includes(v))) {
|
|
6735
|
+
this.#syncAllRow();
|
|
6736
|
+
return;
|
|
6737
|
+
}
|
|
6738
|
+
this.#selectedValues = values;
|
|
6739
|
+
this.#allSelected = values.length === 0;
|
|
6740
|
+
this.#syncAllRow();
|
|
6741
|
+
this.#onSelectionChange(this.id, values);
|
|
6742
|
+
};
|
|
6743
|
+
#setAllSelection() {
|
|
6744
|
+
const previous = this.#selectedValues;
|
|
6745
|
+
this.#syncingSelection = true;
|
|
6746
|
+
try {
|
|
6747
|
+
this.#selectedValues = [];
|
|
6748
|
+
this.#allSelected = true;
|
|
6749
|
+
for (const sub of this.#subComponents()) sub.clearSelection();
|
|
6750
|
+
} finally {
|
|
6751
|
+
this.#syncingSelection = false;
|
|
6752
|
+
}
|
|
6753
|
+
this.#syncAllRow();
|
|
6754
|
+
if (previous.length > 0) this.#onSelectionChange(this.id, []);
|
|
6755
|
+
}
|
|
6756
|
+
clearSelection() {
|
|
6757
|
+
this.#setAllSelection();
|
|
6758
|
+
}
|
|
6759
|
+
get selectedValues() {
|
|
6760
|
+
return this.#selectedValues;
|
|
6761
|
+
}
|
|
6762
|
+
#restoreAfterSync() {
|
|
6763
|
+
if (!this.#listerEl) return;
|
|
6764
|
+
this.#listerEl.updateComplete.then(() => {
|
|
6765
|
+
if (!this.#listerEl || !this.#host) return;
|
|
6766
|
+
this.#syncingSelection = true;
|
|
6767
|
+
try {
|
|
6768
|
+
const wanted = this.#selectedValues;
|
|
6769
|
+
for (const list of this.#listerEl.querySelectorAll(`ixfx-vertical-list`)) list.selectKeys(wanted);
|
|
6770
|
+
const selected = [...this.#listerEl.selectedItems].map((i) => i.key).filter((k) => k !== ALL_KEY);
|
|
6771
|
+
this.#selectedValues = selected;
|
|
6772
|
+
this.#allSelected = selected.length === 0;
|
|
6773
|
+
} finally {
|
|
6774
|
+
this.#syncingSelection = false;
|
|
6775
|
+
}
|
|
6776
|
+
this.#syncAllRow();
|
|
6777
|
+
});
|
|
6778
|
+
}
|
|
6779
|
+
#syncAllRow() {
|
|
6780
|
+
if (!this.#allRow) return;
|
|
6781
|
+
const label = this.#categoryDefaults?.allLabel ?? `All`;
|
|
6782
|
+
const labelEl = this.#allRow.querySelector(`.all-label`);
|
|
6783
|
+
if (labelEl) labelEl.textContent = label;
|
|
6784
|
+
this.#allRow.toggleAttribute(`data-selected`, this.#allSelected);
|
|
6785
|
+
}
|
|
6786
|
+
#abortPending() {
|
|
6787
|
+
if (this.#abortController) {
|
|
6788
|
+
this.#abortController.abort();
|
|
6789
|
+
this.#abortController = void 0;
|
|
6790
|
+
}
|
|
6791
|
+
}
|
|
6792
|
+
};
|
|
6793
|
+
function renderItem(item) {
|
|
6794
|
+
const li = document.createElement(`li`);
|
|
6795
|
+
li.dataset.key = item.key;
|
|
6796
|
+
const labelSpan = document.createElement(`span`);
|
|
6797
|
+
labelSpan.className = `item-label`;
|
|
6798
|
+
labelSpan.textContent = item.label;
|
|
6799
|
+
li.appendChild(labelSpan);
|
|
6800
|
+
if (item.count !== void 0) {
|
|
6801
|
+
const countSpan = document.createElement(`span`);
|
|
6802
|
+
countSpan.className = `count`;
|
|
6803
|
+
countSpan.textContent = String(item.count);
|
|
6804
|
+
li.appendChild(countSpan);
|
|
6805
|
+
}
|
|
6806
|
+
return li;
|
|
6807
|
+
}
|
|
6808
|
+
//#endregion
|
|
6809
|
+
//#region src/multicol-list/multicol-list.ts
|
|
6810
|
+
registerIcon(`menu`, `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12h16"/><path d="M4 18h16"/><path d="M4 6h16"/></svg>`);
|
|
6811
|
+
const DEFAULT_COLUMN_WIDTH = 220;
|
|
6812
|
+
const MIN_COLUMN_WIDTH = 80;
|
|
6813
|
+
const MAX_COLUMN_WIDTH = 480;
|
|
6814
|
+
const DRAG_THRESHOLD = 5;
|
|
6815
|
+
let MulticolListElement = class MulticolListElement extends LitElement {
|
|
6816
|
+
constructor(..._args) {
|
|
6817
|
+
super(..._args);
|
|
6818
|
+
this.categories = [];
|
|
6819
|
+
this.selectionMode = `multiple`;
|
|
6820
|
+
this.minColumns = 1;
|
|
6821
|
+
this.maxColumns = 5;
|
|
6822
|
+
this._columns = [];
|
|
6823
|
+
this._colOffsets = [0];
|
|
6824
|
+
this._resizingColumnId = void 0;
|
|
6825
|
+
this._draggingColumnId = void 0;
|
|
6826
|
+
this._dropCaretX = void 0;
|
|
6827
|
+
this._idCounter = 0;
|
|
6828
|
+
this._autoSeeded = false;
|
|
6829
|
+
this._drag = {
|
|
6830
|
+
pointerId: -1,
|
|
6831
|
+
from: -1,
|
|
6832
|
+
startX: 0,
|
|
6833
|
+
startY: 0,
|
|
6834
|
+
active: false,
|
|
6835
|
+
ghost: null,
|
|
6836
|
+
insert: -1
|
|
6837
|
+
};
|
|
6838
|
+
}
|
|
6839
|
+
static {
|
|
6840
|
+
this.styles = [
|
|
6841
|
+
themeFallbacks,
|
|
6842
|
+
handleStyles,
|
|
6843
|
+
css`
|
|
6844
|
+
:host {
|
|
6845
|
+
--multicol-list-bg: var(--surface-3);
|
|
6846
|
+
--multicol-list-header-bg: var(--surface-2);
|
|
6847
|
+
--multicol-list-header-text: var(--surface-3-text);
|
|
6848
|
+
--multicol-list-border: var(--border);
|
|
6849
|
+
--multicol-list-min-column-width: ${MIN_COLUMN_WIDTH}px;
|
|
6850
|
+
--multicol-list-max-column-width: ${MAX_COLUMN_WIDTH}px;
|
|
6851
|
+
|
|
6852
|
+
display: block;
|
|
6853
|
+
overflow: hidden;
|
|
6854
|
+
background: var(--multicol-list-bg);
|
|
6855
|
+
color: var(--surface-3-text);
|
|
6856
|
+
font-family: var(--font-family);
|
|
6857
|
+
}
|
|
6858
|
+
#container {
|
|
6859
|
+
display: flex;
|
|
6860
|
+
flex-direction: row;
|
|
6861
|
+
align-items: stretch;
|
|
6862
|
+
overflow-x: auto;
|
|
6863
|
+
overflow-y: hidden;
|
|
6864
|
+
position: relative;
|
|
6865
|
+
height: 100%;
|
|
6866
|
+
}
|
|
6867
|
+
.column {
|
|
6868
|
+
flex-shrink: 0;
|
|
6869
|
+
display: flex;
|
|
6870
|
+
flex-direction: column;
|
|
6871
|
+
border-right: var(--multicol-list-border);
|
|
6872
|
+
min-width: var(--multicol-list-min-column-width);
|
|
6873
|
+
max-width: var(--multicol-list-max-column-width);
|
|
6874
|
+
background: var(--surface-3);
|
|
6875
|
+
}
|
|
6876
|
+
.column-header {
|
|
6877
|
+
display: flex;
|
|
6878
|
+
align-items: center;
|
|
6879
|
+
gap: var(--space-xs);
|
|
6880
|
+
height: 32px;
|
|
6881
|
+
background: var(--multicol-list-header-bg);
|
|
6882
|
+
color: var(--multicol-list-header-text);
|
|
6883
|
+
border-bottom: var(--border);
|
|
6884
|
+
position: relative;
|
|
6885
|
+
user-select: none;
|
|
6886
|
+
min-width: var(--multicol-list-min-column-width);
|
|
6887
|
+
box-sizing: border-box;
|
|
6888
|
+
}
|
|
6889
|
+
.column-header[data-dragging] {
|
|
6890
|
+
opacity: 0.4;
|
|
6891
|
+
}
|
|
6892
|
+
.reorder-handle {
|
|
6893
|
+
cursor: grab;
|
|
6894
|
+
opacity: 0;
|
|
6895
|
+
display: inline-flex;
|
|
6896
|
+
align-items: center;
|
|
6897
|
+
flex-shrink: 0;
|
|
6898
|
+
padding-left: var(--space-xs);
|
|
6899
|
+
touch-action: none;
|
|
6900
|
+
transition: opacity var(--transition, 0.15s ease-out);
|
|
6901
|
+
}
|
|
6902
|
+
.column-header:hover .reorder-handle, .column-header:focus-within .reorder-handle, .column-header[data-dragging] .reorder-handle { opacity: var(--ixfx-handle-opacity, 0.5); }
|
|
6903
|
+
.reorder-handle:active {
|
|
6904
|
+
cursor: grabbing;
|
|
6905
|
+
}
|
|
6906
|
+
.reorder-handle .ixfx-handle {
|
|
6907
|
+
--ixfx-handle-length: 14px;
|
|
6908
|
+
--ixfx-handle-thickness: var(--multicol-list-reorder-handle-width, 10px);
|
|
6909
|
+
--ixfx-handle-dot-size: 3px;
|
|
6910
|
+
}
|
|
6911
|
+
.column-label {
|
|
6912
|
+
flex: 1;
|
|
6913
|
+
overflow: hidden;
|
|
6914
|
+
text-overflow: ellipsis;
|
|
6915
|
+
white-space: nowrap;
|
|
6916
|
+
padding: 0 var(--space-s);
|
|
6917
|
+
font-size: var(--text-m);
|
|
6918
|
+
font-weight: 600;
|
|
6919
|
+
background: transparent;
|
|
6920
|
+
border: none;
|
|
6921
|
+
color: inherit;
|
|
6922
|
+
cursor: pointer;
|
|
6923
|
+
text-align: left;
|
|
6924
|
+
min-width: 0;
|
|
6925
|
+
}
|
|
6926
|
+
.column-label:hover {
|
|
6927
|
+
text-decoration: underline;
|
|
6928
|
+
}
|
|
6929
|
+
.burger-btn {
|
|
6930
|
+
flex-shrink: 0;
|
|
6931
|
+
width: 28px;
|
|
6932
|
+
height: 28px;
|
|
6933
|
+
display: flex;
|
|
6934
|
+
align-items: center;
|
|
6935
|
+
justify-content: center;
|
|
6936
|
+
background: transparent;
|
|
6937
|
+
border: none;
|
|
6938
|
+
cursor: pointer;
|
|
6939
|
+
color: var(--surface-3-text);
|
|
6940
|
+
opacity: 0;
|
|
6941
|
+
transition: opacity var(--transition);
|
|
6942
|
+
border-radius: var(--radius-s);
|
|
6943
|
+
}
|
|
6944
|
+
.column:hover .burger-btn,
|
|
6945
|
+
.column-header:focus-within .burger-btn {
|
|
6946
|
+
opacity: 1;
|
|
6947
|
+
}
|
|
6948
|
+
.burger-btn:hover {
|
|
6949
|
+
background: var(--surface-4);
|
|
6950
|
+
}
|
|
6951
|
+
.resize-handle {
|
|
6952
|
+
position: absolute;
|
|
6953
|
+
right: -4px;
|
|
6954
|
+
top: 0;
|
|
6955
|
+
bottom: 0;
|
|
6956
|
+
width: 8px;
|
|
6957
|
+
cursor: col-resize;
|
|
6958
|
+
z-index: 1;
|
|
6959
|
+
display: flex;
|
|
6960
|
+
align-items: stretch;
|
|
6961
|
+
justify-content: center;
|
|
6962
|
+
touch-action: none;
|
|
6963
|
+
}
|
|
6964
|
+
.resize-handle::before {
|
|
6965
|
+
content: '';
|
|
6966
|
+
width: 2px;
|
|
6967
|
+
margin: 2px 0;
|
|
6968
|
+
border-radius: 1px;
|
|
6969
|
+
background: transparent;
|
|
6970
|
+
transition: background-color var(--transition);
|
|
6971
|
+
}
|
|
6972
|
+
.resize-handle:hover::before,
|
|
6973
|
+
.resize-handle[data-active]::before {
|
|
6974
|
+
background: var(--accent);
|
|
6975
|
+
}
|
|
6976
|
+
.lister-host {
|
|
6977
|
+
flex: 1;
|
|
6978
|
+
overflow-y: auto;
|
|
6979
|
+
overflow-x: hidden;
|
|
6980
|
+
position: relative;
|
|
6981
|
+
display: flex;
|
|
6982
|
+
flex-direction: column;
|
|
6983
|
+
}
|
|
6984
|
+
.all-row {
|
|
6985
|
+
display: flex;
|
|
6986
|
+
align-items: center;
|
|
6987
|
+
padding: var(--space-xs) var(--space-s);
|
|
6988
|
+
cursor: pointer;
|
|
6989
|
+
user-select: none;
|
|
6990
|
+
min-height: 28px;
|
|
6991
|
+
box-sizing: border-box;
|
|
6992
|
+
outline: 1px solid transparent;
|
|
6993
|
+
background: var(--surface-3);
|
|
6994
|
+
}
|
|
6995
|
+
.all-row:hover,
|
|
6996
|
+
.all-row[data-selected] {
|
|
6997
|
+
background: var(--surface-5);
|
|
6998
|
+
}
|
|
6999
|
+
.all-row[data-selected] {
|
|
7000
|
+
outline-color: var(--accent);
|
|
7001
|
+
font-weight: 600;
|
|
7002
|
+
}
|
|
7003
|
+
ixfx-grouped-item-lister {
|
|
7004
|
+
display: block;
|
|
7005
|
+
}
|
|
7006
|
+
.drop-caret {
|
|
7007
|
+
position: absolute;
|
|
7008
|
+
top: 0;
|
|
7009
|
+
bottom: 0;
|
|
7010
|
+
width: 2px;
|
|
7011
|
+
background: var(--accent);
|
|
7012
|
+
border-radius: 1px;
|
|
7013
|
+
pointer-events: none;
|
|
7014
|
+
z-index: 10;
|
|
7015
|
+
}
|
|
7016
|
+
.item-label {
|
|
7017
|
+
flex: 1;
|
|
7018
|
+
}
|
|
7019
|
+
.count {
|
|
7020
|
+
opacity: 0.6;
|
|
7021
|
+
margin-left: var(--space-s);
|
|
7022
|
+
font-size: var(--text-s);
|
|
7023
|
+
}
|
|
7024
|
+
`
|
|
7025
|
+
];
|
|
7026
|
+
}
|
|
7027
|
+
get columns() {
|
|
7028
|
+
return this._columns.map((c) => c.getState());
|
|
7029
|
+
}
|
|
7030
|
+
setColumns(states) {
|
|
7031
|
+
const existingMap = new Map(this._columns.map((c) => [c.id, c]));
|
|
7032
|
+
const newColumns = [];
|
|
7033
|
+
for (const colState of states) {
|
|
7034
|
+
const existing = existingMap.get(colState.id);
|
|
7035
|
+
if (existing) {
|
|
7036
|
+
if (existing.categoryKey !== colState.categoryKey) {
|
|
7037
|
+
const newCategory = this.categories.find((c) => c.key === colState.categoryKey);
|
|
7038
|
+
if (newCategory) existing.setCategory(newCategory);
|
|
7039
|
+
}
|
|
7040
|
+
existing.updateState(colState);
|
|
7041
|
+
newColumns.push(existing);
|
|
7042
|
+
existingMap.delete(colState.id);
|
|
7043
|
+
continue;
|
|
7044
|
+
}
|
|
7045
|
+
const category = this.categories.find((c) => c.key === colState.categoryKey);
|
|
7046
|
+
if (!category) continue;
|
|
7047
|
+
const controller = this.#createController(colState.id, category);
|
|
7048
|
+
controller.updateState(colState);
|
|
7049
|
+
newColumns.push(controller);
|
|
7050
|
+
}
|
|
7051
|
+
for (const col of existingMap.values()) col.unmount();
|
|
7052
|
+
this._columns = newColumns;
|
|
7053
|
+
this._updateColOffsets();
|
|
7054
|
+
this._emitColumnsChange(`add`);
|
|
7055
|
+
this._requeryAllColumns();
|
|
7056
|
+
this.requestUpdate();
|
|
7057
|
+
}
|
|
7058
|
+
addColumn(categoryKey, position) {
|
|
7059
|
+
if (this.maxColumns > 0 && this._columns.length >= this.maxColumns) return;
|
|
7060
|
+
const category = this.categories.find((c) => c.key === categoryKey);
|
|
7061
|
+
if (!category) return;
|
|
7062
|
+
const id = `col-${++this._idCounter}`;
|
|
7063
|
+
const controller = this.#createController(id, category);
|
|
7064
|
+
if (position !== void 0 && position >= 0 && position < this._columns.length) this._columns.splice(position, 0, controller);
|
|
7065
|
+
else this._columns.push(controller);
|
|
7066
|
+
this._updateColOffsets();
|
|
7067
|
+
this._emitColumnsChange(`add`);
|
|
7068
|
+
this._requeryAllColumns();
|
|
7069
|
+
this.requestUpdate();
|
|
7070
|
+
}
|
|
7071
|
+
removeColumn(columnId) {
|
|
7072
|
+
if (this._columns.length <= this.minColumns) return;
|
|
7073
|
+
const idx = this._columns.findIndex((c) => c.id === columnId);
|
|
7074
|
+
if (idx < 0) return;
|
|
7075
|
+
this._columns[idx].unmount();
|
|
7076
|
+
this._columns.splice(idx, 1);
|
|
7077
|
+
this._updateColOffsets();
|
|
7078
|
+
this._emitColumnsChange(`remove`);
|
|
7079
|
+
this._requeryAllColumns();
|
|
7080
|
+
this.requestUpdate();
|
|
7081
|
+
}
|
|
7082
|
+
getSelection() {
|
|
7083
|
+
return this._columns.map((c) => ({
|
|
7084
|
+
columnId: c.id,
|
|
7085
|
+
categoryKey: c.categoryKey,
|
|
7086
|
+
values: c.selectedValues
|
|
7087
|
+
}));
|
|
7088
|
+
}
|
|
7089
|
+
clearSelection() {
|
|
7090
|
+
for (const col of this._columns) col.clearSelection();
|
|
7091
|
+
this.requestUpdate();
|
|
7092
|
+
}
|
|
7093
|
+
render() {
|
|
7094
|
+
return html`
|
|
7095
|
+
<div id="container">
|
|
7096
|
+
${repeat(this._columns, (col) => col.id, (col, idx) => this._renderColumn(col, idx))}
|
|
7097
|
+
${this._dropCaretX !== void 0 ? html`<div class="drop-caret" style="left: ${this._dropCaretX}px"></div>` : nothing}
|
|
7098
|
+
</div>
|
|
7099
|
+
`;
|
|
7100
|
+
}
|
|
7101
|
+
updated(changedProperties) {
|
|
7102
|
+
if (!this._autoSeeded && this._columns.length === 0 && this.categories.length > 0) {
|
|
7103
|
+
this._autoSeeded = true;
|
|
7104
|
+
this.addColumn(this.categories[0].key);
|
|
7105
|
+
}
|
|
7106
|
+
if (changedProperties.has(`selectionMode`)) for (const col of this._columns) col.setSelectionMode(this.selectionMode);
|
|
7107
|
+
if (changedProperties.has(`dataProvider`)) for (const col of this._columns) col.setDataProvider(this.dataProvider);
|
|
7108
|
+
for (const col of this._columns) if (!col.isMounted) {
|
|
7109
|
+
const hostEl = this.shadowRoot?.querySelector(`[data-col-id="${col.id}"] .lister-host`);
|
|
7110
|
+
if (hostEl) col.mount(hostEl);
|
|
7111
|
+
}
|
|
7112
|
+
if (changedProperties.has(`categories`)) this._syncColumnsWithCategories();
|
|
7113
|
+
if (changedProperties.has(`categories`) || changedProperties.has(`dataProvider`)) this._requeryAllColumns();
|
|
7114
|
+
this._updateColOffsets();
|
|
7115
|
+
}
|
|
7116
|
+
_renderColumn(col, idx) {
|
|
7117
|
+
const width = col.getState().width ?? DEFAULT_COLUMN_WIDTH;
|
|
7118
|
+
return html`
|
|
7119
|
+
<div
|
|
7120
|
+
class="column"
|
|
7121
|
+
data-col-id="${col.id}"
|
|
7122
|
+
style="width: ${width}px"
|
|
7123
|
+
>
|
|
7124
|
+
<div
|
|
7125
|
+
class="column-header"
|
|
7126
|
+
?data-dragging=${this._draggingColumnId === col.id}
|
|
7127
|
+
data-col-idx=${idx}
|
|
7128
|
+
>
|
|
7129
|
+
<span
|
|
7130
|
+
class="reorder-handle"
|
|
7131
|
+
data-tooltip="Drag to reorder"
|
|
7132
|
+
@pointerdown=${(e) => this._onHeaderPointerDown(e, col, idx)}
|
|
7133
|
+
><span class="ixfx-handle" data-handle="dotted" data-shape="vertical" part="reorder-handle"></span></span>
|
|
7134
|
+
<button
|
|
7135
|
+
class="column-label"
|
|
7136
|
+
data-tooltip="Change category"
|
|
7137
|
+
@click=${(e) => {
|
|
7138
|
+
e.stopPropagation();
|
|
7139
|
+
this._openCategoryPicker(col, e.currentTarget);
|
|
7140
|
+
}}
|
|
7141
|
+
>${this._getCategoryLabel(col.categoryKey)}</button>
|
|
7142
|
+
<button
|
|
7143
|
+
class="burger-btn"
|
|
7144
|
+
data-tooltip="Column options"
|
|
7145
|
+
@click=${(e) => {
|
|
7146
|
+
e.stopPropagation();
|
|
7147
|
+
this._openBurgerMenu(col, e.currentTarget);
|
|
7148
|
+
}}
|
|
7149
|
+
>
|
|
7150
|
+
<ixfx-icon name="menu" size="14"></ixfx-icon>
|
|
7151
|
+
</button>
|
|
7152
|
+
<span
|
|
7153
|
+
class="resize-handle"
|
|
7154
|
+
?data-active=${this._resizingColumnId === col.id}
|
|
7155
|
+
@pointerdown=${(e) => this._onResizePointerDown(e, col)}
|
|
7156
|
+
@click=${(e) => e.stopPropagation()}
|
|
7157
|
+
></span>
|
|
7158
|
+
</div>
|
|
7159
|
+
<div class="lister-host"></div>
|
|
7160
|
+
</div>
|
|
7161
|
+
`;
|
|
7162
|
+
}
|
|
7163
|
+
_getCategoryLabel(key) {
|
|
7164
|
+
return this.categories.find((c) => c.key === key)?.label ?? key;
|
|
7165
|
+
}
|
|
7166
|
+
#createController(id, category) {
|
|
7167
|
+
return new MulticolColumnController({
|
|
7168
|
+
id,
|
|
7169
|
+
category,
|
|
7170
|
+
categoryDefaults: category.defaults,
|
|
7171
|
+
selectionMode: this.selectionMode,
|
|
7172
|
+
dataProvider: this.dataProvider,
|
|
7173
|
+
onSelectionChange: this._onColumnSelectionChange.bind(this),
|
|
7174
|
+
onRequestData: this._onColumnRequestData.bind(this)
|
|
7175
|
+
});
|
|
7176
|
+
}
|
|
7177
|
+
_onHeaderPointerDown(e, col, idx) {
|
|
7178
|
+
e.stopPropagation();
|
|
7179
|
+
this._drag = {
|
|
7180
|
+
pointerId: e.pointerId,
|
|
7181
|
+
from: idx,
|
|
7182
|
+
startX: e.clientX,
|
|
7183
|
+
startY: e.clientY,
|
|
7184
|
+
active: false,
|
|
7185
|
+
ghost: null,
|
|
7186
|
+
insert: idx
|
|
7187
|
+
};
|
|
7188
|
+
const onMove = (ev) => {
|
|
7189
|
+
if (ev.pointerId !== this._drag.pointerId) return;
|
|
7190
|
+
const dx = ev.clientX - this._drag.startX;
|
|
7191
|
+
const dy = ev.clientY - this._drag.startY;
|
|
7192
|
+
if (!this._drag.active) {
|
|
7193
|
+
if (Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
7194
|
+
this._drag.active = true;
|
|
7195
|
+
this._draggingColumnId = col.id;
|
|
7196
|
+
const headerEl = this.shadowRoot?.querySelector(`[data-col-id="${col.id}"] .column-header`);
|
|
7197
|
+
const content = document.createElement(`span`);
|
|
7198
|
+
content.textContent = this._getCategoryLabel(col.categoryKey);
|
|
7199
|
+
try {
|
|
7200
|
+
if (headerEl) {
|
|
7201
|
+
this._drag.ghost = createDragGhost({
|
|
7202
|
+
anchor: headerEl,
|
|
7203
|
+
content
|
|
7204
|
+
});
|
|
7205
|
+
this._drag.ghost.move(ev.clientX, ev.clientY);
|
|
7206
|
+
}
|
|
7207
|
+
} catch {}
|
|
7208
|
+
try {
|
|
7209
|
+
this._containerEl?.setPointerCapture(ev.pointerId);
|
|
7210
|
+
} catch {}
|
|
7211
|
+
}
|
|
7212
|
+
this._drag.ghost?.move(ev.clientX, ev.clientY);
|
|
7213
|
+
const containerEl = this._containerEl;
|
|
7214
|
+
if (!containerEl) return;
|
|
7215
|
+
const rect = containerEl.getBoundingClientRect();
|
|
7216
|
+
const x = ev.clientX - rect.left + containerEl.scrollLeft;
|
|
7217
|
+
const gap = computeDropGap(this._colOffsets, x, this._columns.length, this._drag.from);
|
|
7218
|
+
this._drag.insert = gap.insert;
|
|
7219
|
+
this._dropCaretX = gap.caretOffset;
|
|
7220
|
+
this.requestUpdate();
|
|
7221
|
+
};
|
|
7222
|
+
const finish = (ev) => {
|
|
7223
|
+
if (ev.pointerId !== this._drag.pointerId) return;
|
|
7224
|
+
const wasActive = this._drag.active;
|
|
7225
|
+
const from = this._drag.from;
|
|
7226
|
+
const insert = this._drag.insert;
|
|
7227
|
+
this._drag.ghost?.destroy();
|
|
7228
|
+
this._drag.ghost = null;
|
|
7229
|
+
this._draggingColumnId = void 0;
|
|
7230
|
+
this._dropCaretX = void 0;
|
|
7231
|
+
try {
|
|
7232
|
+
this._containerEl?.releasePointerCapture(ev.pointerId);
|
|
7233
|
+
} catch {}
|
|
7234
|
+
window.removeEventListener(`pointermove`, onMove);
|
|
7235
|
+
window.removeEventListener(`pointerup`, finish);
|
|
7236
|
+
window.removeEventListener(`pointercancel`, finish);
|
|
7237
|
+
if (wasActive && insert !== from) {
|
|
7238
|
+
const [moved] = this._columns.splice(from, 1);
|
|
7239
|
+
this._columns.splice(insert, 0, moved);
|
|
7240
|
+
this._updateColOffsets();
|
|
7241
|
+
this._emitColumnsChange(`reorder`);
|
|
7242
|
+
this.requestUpdate();
|
|
7243
|
+
}
|
|
7244
|
+
};
|
|
7245
|
+
window.addEventListener(`pointermove`, onMove);
|
|
7246
|
+
window.addEventListener(`pointerup`, finish);
|
|
7247
|
+
window.addEventListener(`pointercancel`, finish);
|
|
7248
|
+
}
|
|
7249
|
+
_onResizePointerDown(e, col) {
|
|
7250
|
+
e.preventDefault();
|
|
7251
|
+
e.stopPropagation();
|
|
7252
|
+
const startX = e.clientX;
|
|
7253
|
+
const startW = col.getState().width ?? DEFAULT_COLUMN_WIDTH;
|
|
7254
|
+
this._resizingColumnId = col.id;
|
|
7255
|
+
const applyWidth = (clientX) => {
|
|
7256
|
+
const w = Math.max(MIN_COLUMN_WIDTH, Math.min(MAX_COLUMN_WIDTH, startW + clientX - startX));
|
|
7257
|
+
col.setWidth(w);
|
|
7258
|
+
return w;
|
|
7259
|
+
};
|
|
7260
|
+
const onMove = (ev) => {
|
|
7261
|
+
if (ev.pointerId !== e.pointerId) return;
|
|
7262
|
+
applyWidth(ev.clientX);
|
|
7263
|
+
this._updateColOffsets();
|
|
7264
|
+
this.requestUpdate();
|
|
7265
|
+
};
|
|
7266
|
+
const finish = (ev) => {
|
|
7267
|
+
window.removeEventListener(`pointermove`, onMove);
|
|
7268
|
+
window.removeEventListener(`pointerup`, finish);
|
|
7269
|
+
window.removeEventListener(`pointercancel`, finish);
|
|
7270
|
+
applyWidth(ev.type === `pointercancel` ? startX : ev.clientX);
|
|
7271
|
+
this._resizingColumnId = void 0;
|
|
7272
|
+
this._updateColOffsets();
|
|
7273
|
+
this._emitColumnsChange(`resize`);
|
|
7274
|
+
this.requestUpdate();
|
|
7275
|
+
};
|
|
7276
|
+
window.addEventListener(`pointermove`, onMove);
|
|
7277
|
+
window.addEventListener(`pointerup`, finish);
|
|
7278
|
+
window.addEventListener(`pointercancel`, finish);
|
|
7279
|
+
}
|
|
7280
|
+
/** Floats a dropdown menu trigger over `anchorEl` and opens it immediately. */
|
|
7281
|
+
#openMenuAt(anchorEl, placement, buildMenu) {
|
|
7282
|
+
const rect = anchorEl.getBoundingClientRect();
|
|
7283
|
+
const trigger = document.createElement(`ixfx-menu-trigger`);
|
|
7284
|
+
trigger.mode = `dropdown`;
|
|
7285
|
+
trigger.tabStop = false;
|
|
7286
|
+
trigger.placement = placement;
|
|
7287
|
+
trigger.style.position = `fixed`;
|
|
7288
|
+
trigger.style.left = `${rect.left}px`;
|
|
7289
|
+
trigger.style.top = `${rect.bottom}px`;
|
|
7290
|
+
const container = document.createElement(`ixfx-menu-container`);
|
|
7291
|
+
container.setAttribute(`slot`, `menu`);
|
|
7292
|
+
buildMenu(container);
|
|
7293
|
+
trigger.appendChild(container);
|
|
7294
|
+
trigger.addEventListener(`menu-trigger-close`, () => trigger.remove(), { once: true });
|
|
7295
|
+
document.body.appendChild(trigger);
|
|
7296
|
+
trigger.updateComplete.then(() => trigger.openMenu());
|
|
7297
|
+
}
|
|
7298
|
+
#categoryToNode(cat) {
|
|
7299
|
+
return {
|
|
7300
|
+
item: {
|
|
7301
|
+
key: cat.key,
|
|
7302
|
+
label: cat.label
|
|
7303
|
+
},
|
|
7304
|
+
children: cat.children && cat.children.length > 0 ? cat.children.map((c) => this.#categoryToNode(c)) : void 0
|
|
7305
|
+
};
|
|
7306
|
+
}
|
|
7307
|
+
_openCategoryPicker(col, anchorEl) {
|
|
7308
|
+
this.#openMenuAt(anchorEl, `bottom-start`, (container) => {
|
|
7309
|
+
const items = menuFromTreeToElements(this.categories.map((c) => this.#categoryToNode(c)), { onCommand: (detail) => {
|
|
7310
|
+
if (detail.command === void 0) return;
|
|
7311
|
+
this._changeColumnCategory(col, detail.command);
|
|
7312
|
+
} });
|
|
7313
|
+
for (const el of items) container.appendChild(el);
|
|
7314
|
+
for (const item of container.querySelectorAll(`ixfx-menu-item`)) if (item.getAttribute(`command`) === col.categoryKey) item.setAttribute(`disabled`, ``);
|
|
7315
|
+
});
|
|
7316
|
+
}
|
|
7317
|
+
#menuItem(label, command, onCommand, iconName) {
|
|
7318
|
+
const el = document.createElement(`ixfx-menu-item`);
|
|
7319
|
+
el.setAttribute(`command`, command);
|
|
7320
|
+
el.setAttribute(`label`, label);
|
|
7321
|
+
if (iconName) el.setAttribute(`icon-name`, iconName);
|
|
7322
|
+
if (onCommand) el.addEventListener(`command`, () => onCommand());
|
|
7323
|
+
return el;
|
|
7324
|
+
}
|
|
7325
|
+
_openBurgerMenu(col, anchorEl) {
|
|
7326
|
+
this.#openMenuAt(anchorEl, `bottom-end`, (container) => {
|
|
7327
|
+
const removeItem = this.#menuItem(`Remove this column`, `remove`, () => this.removeColumn(col.id));
|
|
7328
|
+
removeItem.toggleAttribute(`disabled`, this._columns.length <= this.minColumns);
|
|
7329
|
+
container.appendChild(removeItem);
|
|
7330
|
+
const separator = document.createElement(`ixfx-menu-separator`);
|
|
7331
|
+
container.appendChild(separator);
|
|
7332
|
+
const atMax = this.maxColumns > 0 && this._columns.length >= this.maxColumns;
|
|
7333
|
+
const usedCats = new Set(this._columns.map((c) => c.categoryKey));
|
|
7334
|
+
const unused = this.categories.filter((c) => !usedCats.has(c.key));
|
|
7335
|
+
const canAdd = !atMax && unused.length > 0;
|
|
7336
|
+
const addSub = document.createElement(`ixfx-menu-sub-trigger`);
|
|
7337
|
+
addSub.setAttribute(`label`, `Add column`);
|
|
7338
|
+
const addContainer = document.createElement(`ixfx-menu-container`);
|
|
7339
|
+
addContainer.setAttribute(`slot`, `submenu`);
|
|
7340
|
+
const idx = this._columns.findIndex((c) => c.id === col.id);
|
|
7341
|
+
for (const [label, offset] of [[`Left`, -1], [`Right`, 1]]) {
|
|
7342
|
+
const item = this.#menuItem(label, `add-${label.toLowerCase()}`, () => {
|
|
7343
|
+
this.#insertColumn(unused[0].key, Math.max(0, idx + Math.max(0, offset)));
|
|
7344
|
+
});
|
|
7345
|
+
item.toggleAttribute(`disabled`, !canAdd);
|
|
7346
|
+
addContainer.appendChild(item);
|
|
7347
|
+
}
|
|
7348
|
+
addSub.appendChild(addContainer);
|
|
7349
|
+
container.appendChild(addSub);
|
|
7350
|
+
const sortSub = document.createElement(`ixfx-menu-sub-trigger`);
|
|
7351
|
+
sortSub.setAttribute(`label`, `Sort`);
|
|
7352
|
+
const sortContainer = document.createElement(`ixfx-menu-container`);
|
|
7353
|
+
sortContainer.setAttribute(`slot`, `submenu`);
|
|
7354
|
+
sortContainer.appendChild(this.#menuItem(`Ascending`, `sort-asc`, () => {
|
|
7355
|
+
col.setSort(`asc`);
|
|
7356
|
+
this._emitColumnsChange(`sort`);
|
|
7357
|
+
this.requestUpdate();
|
|
7358
|
+
}, col.getState().sort === `asc` ? `check` : void 0));
|
|
7359
|
+
sortContainer.appendChild(this.#menuItem(`Descending`, `sort-desc`, () => {
|
|
7360
|
+
col.setSort(`desc`);
|
|
7361
|
+
this._emitColumnsChange(`sort`);
|
|
7362
|
+
this.requestUpdate();
|
|
7363
|
+
}, col.getState().sort === `desc` ? `check` : void 0));
|
|
7364
|
+
sortSub.appendChild(sortContainer);
|
|
7365
|
+
container.appendChild(sortSub);
|
|
7366
|
+
const grouped = col.getState().grouped;
|
|
7367
|
+
container.appendChild(this.#menuItem(grouped ? `Ungroup` : `Group`, `toggle-group`, () => {
|
|
7368
|
+
col.setGrouped(!grouped);
|
|
7369
|
+
this._emitColumnsChange(`grouping`);
|
|
7370
|
+
this.requestUpdate();
|
|
7371
|
+
}, grouped ? `check` : void 0));
|
|
7372
|
+
});
|
|
7373
|
+
}
|
|
7374
|
+
#insertColumn(categoryKey, position) {
|
|
7375
|
+
this.addColumn(categoryKey, position);
|
|
7376
|
+
}
|
|
7377
|
+
_changeColumnCategory(col, categoryKey) {
|
|
7378
|
+
const category = this.categories.find((c) => c.key === categoryKey);
|
|
7379
|
+
if (!category) return;
|
|
7380
|
+
col.setCategory(category);
|
|
7381
|
+
this._emitColumnsChange(`category`);
|
|
7382
|
+
this._requeryAllColumns();
|
|
7383
|
+
this.requestUpdate();
|
|
7384
|
+
}
|
|
7385
|
+
_onColumnSelectionChange(columnId, _values) {
|
|
7386
|
+
if (!this._columns.find((c) => c.id === columnId)) return;
|
|
7387
|
+
for (const otherCol of this._columns) if (otherCol.id !== columnId) otherCol.requestData(this._buildQuery(otherCol));
|
|
7388
|
+
const detail = {
|
|
7389
|
+
columnId,
|
|
7390
|
+
columns: this.getSelection()
|
|
7391
|
+
};
|
|
7392
|
+
this.dispatchEvent(new CustomEvent(`multicol-select`, {
|
|
7393
|
+
detail,
|
|
7394
|
+
bubbles: true,
|
|
7395
|
+
composed: true
|
|
7396
|
+
}));
|
|
7397
|
+
this.requestUpdate();
|
|
7398
|
+
}
|
|
7399
|
+
_onColumnRequestData(_columnId, _query, _signal) {}
|
|
7400
|
+
_buildQuery(col) {
|
|
7401
|
+
const otherSelections = /* @__PURE__ */ new Map();
|
|
7402
|
+
for (const c of this._columns) if (c.id !== col.id) otherSelections.set(c.id, c.selectedValues);
|
|
7403
|
+
return {
|
|
7404
|
+
columnId: col.id,
|
|
7405
|
+
categoryKey: col.categoryKey,
|
|
7406
|
+
otherSelections
|
|
7407
|
+
};
|
|
7408
|
+
}
|
|
7409
|
+
_requeryAllColumns() {
|
|
7410
|
+
for (const col of this._columns) col.requestData(this._buildQuery(col));
|
|
7411
|
+
}
|
|
7412
|
+
_syncColumnsWithCategories() {
|
|
7413
|
+
let changed = false;
|
|
7414
|
+
for (const col of this._columns) {
|
|
7415
|
+
if (this.categories.some((c) => c.key === col.categoryKey)) continue;
|
|
7416
|
+
if (this.categories.length === 0) continue;
|
|
7417
|
+
col.setCategory(this.categories[0]);
|
|
7418
|
+
changed = true;
|
|
7419
|
+
}
|
|
7420
|
+
if (changed) this._emitColumnsChange(`category`);
|
|
7421
|
+
}
|
|
7422
|
+
_updateColOffsets() {
|
|
7423
|
+
const offsets = [0];
|
|
7424
|
+
let sum = 0;
|
|
7425
|
+
for (const col of this._columns) {
|
|
7426
|
+
sum += col.getState().width ?? DEFAULT_COLUMN_WIDTH;
|
|
7427
|
+
offsets.push(sum);
|
|
7428
|
+
}
|
|
7429
|
+
if (!(offsets.length === this._colOffsets.length && offsets.every((v, i) => v === this._colOffsets[i]))) this._colOffsets = offsets;
|
|
7430
|
+
}
|
|
7431
|
+
_emitColumnsChange(reason) {
|
|
7432
|
+
this.dispatchEvent(new CustomEvent(`multicol-columns-change`, {
|
|
7433
|
+
detail: {
|
|
7434
|
+
columns: this.columns,
|
|
7435
|
+
reason
|
|
7436
|
+
},
|
|
7437
|
+
bubbles: true,
|
|
7438
|
+
composed: true
|
|
7439
|
+
}));
|
|
7440
|
+
}
|
|
7441
|
+
};
|
|
7442
|
+
__decorate([property({ attribute: false })], MulticolListElement.prototype, "categories", void 0);
|
|
7443
|
+
__decorate([property({ attribute: false })], MulticolListElement.prototype, "dataProvider", void 0);
|
|
7444
|
+
__decorate([property({
|
|
7445
|
+
type: String,
|
|
7446
|
+
attribute: `selection-mode`
|
|
7447
|
+
})], MulticolListElement.prototype, "selectionMode", void 0);
|
|
7448
|
+
__decorate([property({ type: Number })], MulticolListElement.prototype, "minColumns", void 0);
|
|
7449
|
+
__decorate([property({ type: Number })], MulticolListElement.prototype, "maxColumns", void 0);
|
|
7450
|
+
__decorate([state()], MulticolListElement.prototype, "_columns", void 0);
|
|
7451
|
+
__decorate([state()], MulticolListElement.prototype, "_colOffsets", void 0);
|
|
7452
|
+
__decorate([state()], MulticolListElement.prototype, "_resizingColumnId", void 0);
|
|
7453
|
+
__decorate([state()], MulticolListElement.prototype, "_draggingColumnId", void 0);
|
|
7454
|
+
__decorate([state()], MulticolListElement.prototype, "_dropCaretX", void 0);
|
|
7455
|
+
__decorate([query(`#container`)], MulticolListElement.prototype, "_containerEl", void 0);
|
|
7456
|
+
MulticolListElement = __decorate([customElement(`ixfx-multicol-list`)], MulticolListElement);
|
|
7457
|
+
//#endregion
|
|
6490
7458
|
//#region src/notifier/pill-renderer.ts
|
|
6491
7459
|
const ROW_SPACING = 8;
|
|
6492
7460
|
const DEFAULT_ROW_HEIGHT = 40;
|
|
@@ -9655,12 +10623,25 @@ function squarifyInto(items, box) {
|
|
|
9655
10623
|
}
|
|
9656
10624
|
return out;
|
|
9657
10625
|
}
|
|
10626
|
+
/** Clamp a raw cascade offset fraction to a sane range. */
|
|
10627
|
+
function normaliseOffset(raw) {
|
|
10628
|
+
return typeof raw === `number` && Number.isFinite(raw) && raw > 0 ? Math.min(raw, 1) : 0;
|
|
10629
|
+
}
|
|
9658
10630
|
/**
|
|
9659
10631
|
* Subdivide `box` among `weights` (same length as the child list) using
|
|
9660
10632
|
* `method` at `relativeDepth`. Returns one absolute box per weight, in order.
|
|
9661
10633
|
* Zero-share children get degenerate (zero-area) boxes at the box origin.
|
|
10634
|
+
* Degenerate (zero/negative-extent) containers also yield zero-area boxes
|
|
10635
|
+
* rather than `NaN` — this matters for cascaded layouts where deep insets
|
|
10636
|
+
* can consume the whole box.
|
|
9662
10637
|
*/
|
|
9663
10638
|
function subdivide(box, weights, method, relativeDepth) {
|
|
10639
|
+
if (box.width <= 0 || box.height <= 0) return weights.map(() => ({
|
|
10640
|
+
x: box.x,
|
|
10641
|
+
y: box.y,
|
|
10642
|
+
width: 0,
|
|
10643
|
+
height: 0
|
|
10644
|
+
}));
|
|
9664
10645
|
const total = weights.reduce((a, b) => a + b, 0);
|
|
9665
10646
|
const shares = total > 0 ? weights.map((w) => w / total) : weights.map(() => 1 / weights.length);
|
|
9666
10647
|
const dice = method === `dice` || method === `slice-dice` && relativeDepth % 2 === 0;
|
|
@@ -9727,16 +10708,24 @@ function subdivide(box, weights, method, relativeDepth) {
|
|
|
9727
10708
|
* @param options `valueAccessor` (default equal weights), `maxDepth` (nesting
|
|
9728
10709
|
* levels, default unlimited), `zoomKey` (visual root, default true root;
|
|
9729
10710
|
* unknown keys fall back to the true root), `tilingMethod` (default
|
|
9730
|
-
* `'squarify'`)
|
|
10711
|
+
* `'squarify'`), `presentation` (default `'nested'`) plus `cascadeOffsetX/Y`
|
|
10712
|
+
* (per-level shift as a viewport fraction, cascaded only).
|
|
9731
10713
|
*/
|
|
9732
10714
|
function computeTreemapLayout(root, options = {}) {
|
|
9733
10715
|
if (!root?.children?.length) return [];
|
|
9734
|
-
const { maxDepth = Number.POSITIVE_INFINITY, valueAccessor = () => 1, zoomKey, tilingMethod = `squarify` } = options;
|
|
10716
|
+
const { maxDepth = Number.POSITIVE_INFINITY, valueAccessor = () => 1, zoomKey, tilingMethod = `squarify`, hideLeaves = false, presentation = `nested`, cascadeOffsetX: rawOx, cascadeOffsetY: rawOy } = options;
|
|
9735
10717
|
if (maxDepth <= 0) return [];
|
|
10718
|
+
const cascaded = presentation === `cascaded`;
|
|
10719
|
+
const ox = cascaded ? normaliseOffset(rawOx) : 0;
|
|
10720
|
+
const oy = cascaded ? normaliseOffset(rawOy) : 0;
|
|
10721
|
+
const visibleChildren = (node) => {
|
|
10722
|
+
const children = node.children ?? [];
|
|
10723
|
+
return hideLeaves ? children.filter((n) => n.item.isLeaf !== true) : children;
|
|
10724
|
+
};
|
|
9736
10725
|
const zoomNode = zoomKey === void 0 ? root : findTreemapNode(root, zoomKey) ?? root;
|
|
9737
10726
|
const zoomPath = findTreemapPath(root, zoomNode.item.key);
|
|
9738
10727
|
const zoomDepth = zoomPath ? zoomPath.length - 2 : -1;
|
|
9739
|
-
const siblings = zoomNode
|
|
10728
|
+
const siblings = visibleChildren(zoomNode);
|
|
9740
10729
|
if (siblings.length === 0) return [];
|
|
9741
10730
|
const rects = [];
|
|
9742
10731
|
const layoutLevel = (nodes, box, depth, relativeDepth, parentKey) => {
|
|
@@ -9745,7 +10734,7 @@ function computeTreemapLayout(root, options = {}) {
|
|
|
9745
10734
|
const setSize = nodes.length;
|
|
9746
10735
|
nodes.forEach((node, index) => {
|
|
9747
10736
|
const childBox = boxes[index];
|
|
9748
|
-
const children = node
|
|
10737
|
+
const children = visibleChildren(node);
|
|
9749
10738
|
rects.push({
|
|
9750
10739
|
node,
|
|
9751
10740
|
depth,
|
|
@@ -9759,7 +10748,15 @@ function computeTreemapLayout(root, options = {}) {
|
|
|
9759
10748
|
setSize,
|
|
9760
10749
|
hasChildren: children.length > 0 || node.children === void 0 && node.item.isLeaf !== true
|
|
9761
10750
|
});
|
|
9762
|
-
if (children.length > 0)
|
|
10751
|
+
if (children.length > 0) {
|
|
10752
|
+
const contentBox = ox <= 0 && oy <= 0 ? childBox : {
|
|
10753
|
+
x: childBox.x + ox,
|
|
10754
|
+
y: childBox.y + oy,
|
|
10755
|
+
width: Math.max(0, childBox.width - ox),
|
|
10756
|
+
height: Math.max(0, childBox.height - oy)
|
|
10757
|
+
};
|
|
10758
|
+
layoutLevel(children, contentBox, depth + 1, relativeDepth + 1, node.item.key);
|
|
10759
|
+
}
|
|
9763
10760
|
});
|
|
9764
10761
|
};
|
|
9765
10762
|
layoutLevel(siblings, {
|
|
@@ -9779,9 +10776,14 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
9779
10776
|
this.selectionFilter = `all`;
|
|
9780
10777
|
this.interactionMode = `standard`;
|
|
9781
10778
|
this.tilingMethod = `squarify`;
|
|
10779
|
+
this.presentation = `nested`;
|
|
10780
|
+
this.cascadeOffset = 6;
|
|
9782
10781
|
this.maxDepth = 4;
|
|
10782
|
+
this.hideLeaves = false;
|
|
9783
10783
|
this.minLabelArea = 1600;
|
|
10784
|
+
this.minTileWidth = 24;
|
|
9784
10785
|
this.padding = 4;
|
|
10786
|
+
this.minChildArea = 400;
|
|
9785
10787
|
this.valueAccessor = () => 1;
|
|
9786
10788
|
this._selectedNodes = /* @__PURE__ */ new Set();
|
|
9787
10789
|
this.disableKeyboardNavigation = false;
|
|
@@ -9815,6 +10817,7 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
9815
10817
|
});
|
|
9816
10818
|
this.#pendingKeys = /* @__PURE__ */ new Set();
|
|
9817
10819
|
this.#aborters = /* @__PURE__ */ new Map();
|
|
10820
|
+
this.#wheelCooldownUntil = 0;
|
|
9818
10821
|
}
|
|
9819
10822
|
get selectedNodes() {
|
|
9820
10823
|
return this._selectedNodes;
|
|
@@ -9831,17 +10834,43 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
9831
10834
|
#ownedRegistry;
|
|
9832
10835
|
#unregisterSearchCommands;
|
|
9833
10836
|
#resizeObserver;
|
|
10837
|
+
#pendingFlip;
|
|
10838
|
+
/** `performance.now()` timestamp before which further Cmd+wheel zoom steps are ignored. */
|
|
10839
|
+
#wheelCooldownUntil;
|
|
9834
10840
|
get #effectiveRoot() {
|
|
9835
10841
|
return this.model?.root ?? this.root;
|
|
9836
10842
|
}
|
|
9837
10843
|
#rects() {
|
|
10844
|
+
return this.#rectsForZoom(this._zoomKey);
|
|
10845
|
+
}
|
|
10846
|
+
#rectsForZoom(zoomKey) {
|
|
9838
10847
|
return computeTreemapLayout(this.#effectiveRoot, {
|
|
9839
10848
|
valueAccessor: this.valueAccessor,
|
|
9840
10849
|
maxDepth: this.maxDepth,
|
|
9841
|
-
zoomKey
|
|
9842
|
-
tilingMethod: this.tilingMethod
|
|
10850
|
+
zoomKey,
|
|
10851
|
+
tilingMethod: this.tilingMethod,
|
|
10852
|
+
hideLeaves: this.hideLeaves,
|
|
10853
|
+
presentation: this.presentation,
|
|
10854
|
+
...this.#cascadeFractions()
|
|
9843
10855
|
});
|
|
9844
10856
|
}
|
|
10857
|
+
/**
|
|
10858
|
+
* Pixel `cascadeOffset` as viewport fractions for the current host size.
|
|
10859
|
+
* Returns `{}` when nested, unmeasured, or non-positive — layout then falls
|
|
10860
|
+
* back to classic nesting for that pass (the `ResizeObserver` re-renders
|
|
10861
|
+
* once measured).
|
|
10862
|
+
*/
|
|
10863
|
+
#cascadeFractions() {
|
|
10864
|
+
if (this.presentation !== `cascaded`) return {};
|
|
10865
|
+
if (!Number.isFinite(this.cascadeOffset) || this.cascadeOffset <= 0) return {};
|
|
10866
|
+
const w = this.clientWidth || 0;
|
|
10867
|
+
const h = this.clientHeight || 0;
|
|
10868
|
+
if (w <= 0 || h <= 0) return {};
|
|
10869
|
+
return {
|
|
10870
|
+
cascadeOffsetX: this.cascadeOffset / w,
|
|
10871
|
+
cascadeOffsetY: this.cascadeOffset / h
|
|
10872
|
+
};
|
|
10873
|
+
}
|
|
9845
10874
|
connectedCallback() {
|
|
9846
10875
|
super.connectedCallback();
|
|
9847
10876
|
this.setAttribute(`data-input-mode`, `mouse`);
|
|
@@ -9915,8 +10944,14 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
9915
10944
|
if (changedProps.has(`searchModes`)) this.#syncSearchCommands();
|
|
9916
10945
|
if (this._zoomKey !== void 0 && !findTreemapNode(this.#effectiveRoot, this._zoomKey)) this._zoomKey = void 0;
|
|
9917
10946
|
this.#ensureVisibleChildren();
|
|
10947
|
+
if (this.#pendingFlip) {
|
|
10948
|
+
const flip = this.#pendingFlip;
|
|
10949
|
+
this.#pendingFlip = void 0;
|
|
10950
|
+
this.#applyFlip(flip);
|
|
10951
|
+
}
|
|
9918
10952
|
}
|
|
9919
10953
|
#resetViewState() {
|
|
10954
|
+
this.#pendingFlip = void 0;
|
|
9920
10955
|
this.#clearSelectionInternal();
|
|
9921
10956
|
this.tickled.clearTickled();
|
|
9922
10957
|
this._zoomKey = void 0;
|
|
@@ -10029,6 +11064,7 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10029
11064
|
*/
|
|
10030
11065
|
zoomTo(node) {
|
|
10031
11066
|
if (this._zoomKey === node.item.key) return;
|
|
11067
|
+
this.#pendingFlip = this.#planFlip(node.item.key);
|
|
10032
11068
|
this._zoomKey = node.item.key;
|
|
10033
11069
|
this.tickled.clearTickled();
|
|
10034
11070
|
const depth = findTreemapPath(this.#effectiveRoot, node.item.key)?.length;
|
|
@@ -10057,6 +11093,7 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10057
11093
|
*/
|
|
10058
11094
|
zoomOut() {
|
|
10059
11095
|
if (this._zoomKey === void 0) return;
|
|
11096
|
+
this.#pendingFlip = this.#planFlip(void 0);
|
|
10060
11097
|
const previous = this.focusedNode;
|
|
10061
11098
|
const path = this._zoomKey === void 0 ? void 0 : findTreemapPath(this.#effectiveRoot, this._zoomKey);
|
|
10062
11099
|
const absoluteDepth = path === void 0 ? -1 : path.length - 2;
|
|
@@ -10080,9 +11117,66 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10080
11117
|
}));
|
|
10081
11118
|
this.requestUpdate();
|
|
10082
11119
|
}
|
|
11120
|
+
/**
|
|
11121
|
+
* Work out (before `_zoomKey` changes) whether the upcoming zoom should
|
|
11122
|
+
* animate, and how — see `FlipPlan`. Returns `undefined` to skip the
|
|
11123
|
+
* animation: reduced-motion is requested, or the node needed to anchor it
|
|
11124
|
+
* isn't in either the old or new layout's visible window.
|
|
11125
|
+
*/
|
|
11126
|
+
#planFlip(targetKey) {
|
|
11127
|
+
if (typeof matchMedia === `function` && matchMedia(`(prefers-reduced-motion: reduce)`).matches) return void 0;
|
|
11128
|
+
if (targetKey !== void 0) {
|
|
11129
|
+
const visible = this.#rects().find((r) => r.node.item.key === targetKey);
|
|
11130
|
+
if (visible) return {
|
|
11131
|
+
direction: `in`,
|
|
11132
|
+
rect: {
|
|
11133
|
+
x: visible.x,
|
|
11134
|
+
y: visible.y,
|
|
11135
|
+
width: visible.width,
|
|
11136
|
+
height: visible.height
|
|
11137
|
+
}
|
|
11138
|
+
};
|
|
11139
|
+
}
|
|
11140
|
+
const fromKey = this._zoomKey;
|
|
11141
|
+
if (fromKey === void 0) return void 0;
|
|
11142
|
+
const landing = this.#rectsForZoom(targetKey).find((r) => r.node.item.key === fromKey);
|
|
11143
|
+
if (!landing) return void 0;
|
|
11144
|
+
return {
|
|
11145
|
+
direction: `out`,
|
|
11146
|
+
rect: {
|
|
11147
|
+
x: landing.x,
|
|
11148
|
+
y: landing.y,
|
|
11149
|
+
width: landing.width,
|
|
11150
|
+
height: landing.height
|
|
11151
|
+
}
|
|
11152
|
+
};
|
|
11153
|
+
}
|
|
11154
|
+
/** Apply a planned FLIP animation once the new layout has rendered — see `FlipPlan`. */
|
|
11155
|
+
#applyFlip(flip) {
|
|
11156
|
+
const inner = this.renderRoot.querySelector(`.viewport-inner`);
|
|
11157
|
+
const viewport = this.renderRoot.querySelector(`.viewport`);
|
|
11158
|
+
if (!(inner instanceof HTMLElement) || !(viewport instanceof HTMLElement)) return;
|
|
11159
|
+
const vw = viewport.clientWidth;
|
|
11160
|
+
const vh = viewport.clientHeight;
|
|
11161
|
+
if (vw <= 0 || vh <= 0) return;
|
|
11162
|
+
const { rect, direction } = flip;
|
|
11163
|
+
const startTransform = direction === `in` ? `translate(${rect.x * vw}px, ${rect.y * vh}px) scale(${Math.max(rect.width, 1e-4)}, ${Math.max(rect.height, 1e-4)})` : (() => {
|
|
11164
|
+
const sx = rect.width > 0 ? 1 / rect.width : 1;
|
|
11165
|
+
const sy = rect.height > 0 ? 1 / rect.height : 1;
|
|
11166
|
+
return `translate(${-rect.x * sx * vw}px, ${-rect.y * sy * vh}px) scale(${sx}, ${sy})`;
|
|
11167
|
+
})();
|
|
11168
|
+
inner.classList.remove(`flipping`);
|
|
11169
|
+
inner.style.transform = startTransform;
|
|
11170
|
+
inner.offsetWidth;
|
|
11171
|
+
requestAnimationFrame(() => {
|
|
11172
|
+
inner.classList.add(`flipping`);
|
|
11173
|
+
inner.style.transform = `none`;
|
|
11174
|
+
});
|
|
11175
|
+
inner.addEventListener(`transitionend`, () => inner.classList.remove(`flipping`), { once: true });
|
|
11176
|
+
}
|
|
10083
11177
|
#canZoom(node) {
|
|
10084
11178
|
if (node.item.isLeaf === true) return false;
|
|
10085
|
-
if (node.children !== void 0) return node.children.length > 0;
|
|
11179
|
+
if (node.children !== void 0) return this.hideLeaves ? node.children.some((n) => n.item.isLeaf !== true) : node.children.length > 0;
|
|
10086
11180
|
return this.model !== void 0 || this.loadChildren !== void 0;
|
|
10087
11181
|
}
|
|
10088
11182
|
/**
|
|
@@ -10309,6 +11403,39 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10309
11403
|
}
|
|
10310
11404
|
}
|
|
10311
11405
|
}
|
|
11406
|
+
/**
|
|
11407
|
+
* Cmd+wheel zooms: scrolling backward (deltaY < 0, towards the user) zooms
|
|
11408
|
+
* into the tile under the pointer — the same as clicking it; scrolling
|
|
11409
|
+
* forward (deltaY > 0, away from the user) zooms out one level at a time
|
|
11410
|
+
* (see `#zoomOutOneStep`), not straight to the root. Plain wheel (no Cmd)
|
|
11411
|
+
* is left alone for normal page scroll. A short cooldown after each step
|
|
11412
|
+
* keeps a single trackpad gesture (which fires many small wheel events)
|
|
11413
|
+
* from firing a cascade of zoom steps.
|
|
11414
|
+
*/
|
|
11415
|
+
#handleWheel(event) {
|
|
11416
|
+
if (!event.metaKey || event.deltaY === 0) return;
|
|
11417
|
+
event.preventDefault();
|
|
11418
|
+
if (performance.now() < this.#wheelCooldownUntil) return;
|
|
11419
|
+
if (event.deltaY > 0) {
|
|
11420
|
+
if (this._zoomKey === void 0) return;
|
|
11421
|
+
this.#wheelCooldownUntil = performance.now() + 260;
|
|
11422
|
+
this.#zoomOutOneStep();
|
|
11423
|
+
return;
|
|
11424
|
+
}
|
|
11425
|
+
const key = (event.target?.closest(`.tile`))?.getAttribute(`data-key`) ?? void 0;
|
|
11426
|
+
if (key === void 0) return;
|
|
11427
|
+
const rect = this.#rects().find((r) => r.node.item.key === key);
|
|
11428
|
+
if (!rect || !this.#canZoom(rect.node)) return;
|
|
11429
|
+
this.#wheelCooldownUntil = performance.now() + 260;
|
|
11430
|
+
this.zoomTo(rect.node);
|
|
11431
|
+
}
|
|
11432
|
+
/** Zoom to the current zoom node's parent, or fully out when it's a direct child of the root. */
|
|
11433
|
+
#zoomOutOneStep() {
|
|
11434
|
+
const path = this._zoomKey === void 0 ? void 0 : findTreemapPath(this.#effectiveRoot, this._zoomKey);
|
|
11435
|
+
const parent = path && path.length > 2 ? path[path.length - 2] : void 0;
|
|
11436
|
+
if (parent) this.zoomTo(parent);
|
|
11437
|
+
else this.zoomOut();
|
|
11438
|
+
}
|
|
10312
11439
|
#renderBreadcrumb() {
|
|
10313
11440
|
const zoomNode = this.focusedNode;
|
|
10314
11441
|
if (zoomNode === void 0) return nothing;
|
|
@@ -10338,25 +11465,64 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10338
11465
|
if (node.item.key === this.#effectiveRoot?.item.key) this.zoomOut();
|
|
10339
11466
|
else this.zoomTo(node);
|
|
10340
11467
|
}
|
|
10341
|
-
|
|
11468
|
+
/** On-screen size (px) of `rect`, or `undefined` before the host has been measured. */
|
|
11469
|
+
#pixelSize(rect) {
|
|
10342
11470
|
const hostWidth = this.clientWidth || 0;
|
|
10343
11471
|
const hostHeight = this.clientHeight || 0;
|
|
10344
|
-
if (hostWidth <= 0 || hostHeight <= 0) return
|
|
10345
|
-
return
|
|
11472
|
+
if (hostWidth <= 0 || hostHeight <= 0) return void 0;
|
|
11473
|
+
return {
|
|
11474
|
+
width: rect.width * hostWidth,
|
|
11475
|
+
height: rect.height * hostHeight
|
|
11476
|
+
};
|
|
10346
11477
|
}
|
|
10347
|
-
|
|
11478
|
+
/** Hide when too small by area, or too narrow to fit any readable text even truncated. */
|
|
11479
|
+
#hideLabel(rect) {
|
|
11480
|
+
const size = this.#pixelSize(rect);
|
|
11481
|
+
if (size === void 0) return false;
|
|
11482
|
+
return size.width * size.height < this.minLabelArea || size.width < this.minTileWidth;
|
|
11483
|
+
}
|
|
11484
|
+
/** Whether `rect` is large enough on-screen for its own children to be subdivided and drawn. */
|
|
11485
|
+
#canDescend(rect) {
|
|
11486
|
+
const size = this.#pixelSize(rect);
|
|
11487
|
+
if (size === void 0) return true;
|
|
11488
|
+
const offset = this.presentation === `cascaded` && Number.isFinite(this.cascadeOffset) && this.cascadeOffset > 0 ? this.cascadeOffset : 0;
|
|
11489
|
+
const w = size.width - offset;
|
|
11490
|
+
const h = size.height - offset;
|
|
11491
|
+
if (w <= 0 || h <= 0) return false;
|
|
11492
|
+
return w * h >= this.minChildArea && w >= this.minTileWidth;
|
|
11493
|
+
}
|
|
11494
|
+
/** Slugify a category name into a CSS custom-property-safe token. */
|
|
11495
|
+
#categorySlug(category) {
|
|
11496
|
+
return category.replace(/[^\w-]/g, `-`);
|
|
11497
|
+
}
|
|
11498
|
+
/**
|
|
11499
|
+
* Inline colour overrides for `node`'s category, or `undefined` for none.
|
|
11500
|
+
* Set as real `background-color`/`color` (not custom properties) so they
|
|
11501
|
+
* simply win over `.tile`'s CSS-variable-driven defaults by specificity —
|
|
11502
|
+
* no risk of an unresolvable `var()` chain leaving the property invalid.
|
|
11503
|
+
*/
|
|
11504
|
+
#categoryStyle(node) {
|
|
11505
|
+
const category = this.categoryAccessor?.(node.item);
|
|
11506
|
+
if (!category) return void 0;
|
|
11507
|
+
const slug = this.#categorySlug(category);
|
|
11508
|
+
return `background-color:var(--treemap-category-${slug}-bg, var(--treemap-tile-bg));color:var(--treemap-category-${slug}-text, var(--treemap-tile-text));`;
|
|
11509
|
+
}
|
|
11510
|
+
#renderTileBox(rect, positionStyle, hasVisibleChildren, childrenContent) {
|
|
10348
11511
|
const { node } = rect;
|
|
10349
11512
|
const selected = this._selectedNodes.has(node);
|
|
10350
11513
|
const tickled = this.tickled.isTickled(node.item.key);
|
|
10351
11514
|
const filtered = !this.#matchesFilter(node, rect.depth);
|
|
10352
11515
|
const pending = this.#pendingKeys.has(node.item.key);
|
|
10353
11516
|
const hideLabel = this.#hideLabel(rect);
|
|
10354
|
-
const showHeader =
|
|
11517
|
+
const showHeader = hasVisibleChildren && !hideLabel;
|
|
11518
|
+
const category = this.categoryAccessor?.(node.item);
|
|
11519
|
+
const categoryStyle = this.#categoryStyle(node);
|
|
10355
11520
|
return html`
|
|
10356
11521
|
<div
|
|
10357
|
-
class="item tile ${
|
|
11522
|
+
class="item tile ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${filtered ? `filtered` : ``} ${pending ? `loading` : ``} ${rect.width <= 0 || rect.height <= 0 ? `empty` : ``}"
|
|
10358
11523
|
data-key=${node.item.key}
|
|
10359
11524
|
data-depth=${rect.relativeDepth}
|
|
11525
|
+
data-category=${category ?? nothing}
|
|
10360
11526
|
role="treeitem"
|
|
10361
11527
|
tabindex="-1"
|
|
10362
11528
|
aria-level=${rect.depth + 1}
|
|
@@ -10366,7 +11532,7 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10366
11532
|
aria-label=${node.item.label}
|
|
10367
11533
|
title=${node.item.label}
|
|
10368
11534
|
part="tile"
|
|
10369
|
-
style=${positionStyle}
|
|
11535
|
+
style=${categoryStyle ? `${positionStyle};${categoryStyle}` : positionStyle}
|
|
10370
11536
|
@click=${(e) => {
|
|
10371
11537
|
e.stopPropagation();
|
|
10372
11538
|
this.#handleItemClick(rect, this.#rects().findIndex((r) => r.node.item.key === node.item.key), e);
|
|
@@ -10378,23 +11544,46 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10378
11544
|
@pointerenter=${() => this.tickled.handlePointerEnter(node.item.key)}
|
|
10379
11545
|
>
|
|
10380
11546
|
${showHeader ? html`<div class="tile-header" part="tile-header"><span class="treemap-label" part="label">${node.item.label}</span></div>` : html`<span class="treemap-label ${hideLabel ? `hide-label` : ``}" part="label">${node.item.label}</span>`}
|
|
10381
|
-
${
|
|
10382
|
-
<div class="tile-children" role="group">
|
|
10383
|
-
${repeat(children, (r) => r.node.item.key, (r) => this.#renderNestedTile(r, rect, byParent))}
|
|
10384
|
-
</div>
|
|
10385
|
-
` : nothing}
|
|
11547
|
+
${childrenContent}
|
|
10386
11548
|
</div>
|
|
10387
11549
|
`;
|
|
10388
11550
|
}
|
|
11551
|
+
/**
|
|
11552
|
+
* Render `rect` plus (if it's large enough to descend into — see
|
|
11553
|
+
* `#canDescend`) a `.tile-children` holding its own children, each
|
|
11554
|
+
* positioned relative to `rect` itself and recursing the same way.
|
|
11555
|
+
*
|
|
11556
|
+
* Each level gets its own real `.tile-header` + `.tile-children` box, so a
|
|
11557
|
+
* header reserves its own space via normal box-model layout (flex column:
|
|
11558
|
+
* header, then the remaining space for children) at *every* level — not
|
|
11559
|
+
* just the top one. That matters because layout rects are purely
|
|
11560
|
+
* geometric (no allowance for header height), so a folder whose one child
|
|
11561
|
+
* fills ~100% of its area gets a child rect nearly identical to its own;
|
|
11562
|
+
* positioning that child against its direct parent (not some flattened
|
|
11563
|
+
* shared ancestor) is what keeps the parent's header from being painted
|
|
11564
|
+
* over by an opaque, near-identically-sized child box.
|
|
11565
|
+
*/
|
|
11566
|
+
#renderTile(rect, positionStyle, byParent) {
|
|
11567
|
+
const children = rect.hasChildren && this.#canDescend(rect) ? byParent.get(rect.node.item.key) ?? [] : [];
|
|
11568
|
+
const childrenContent = children.length > 0 ? html`
|
|
11569
|
+
<div class="tile-children" role="group">
|
|
11570
|
+
${repeat(children, (r) => r.node.item.key, (r) => this.#renderNestedTile(r, rect, byParent))}
|
|
11571
|
+
</div>
|
|
11572
|
+
` : nothing;
|
|
11573
|
+
return this.#renderTileBox(rect, positionStyle, children.length > 0, childrenContent);
|
|
11574
|
+
}
|
|
11575
|
+
/** A child tile, positioned as a percentage of `parent`'s own box. */
|
|
10389
11576
|
#renderNestedTile(rect, parent, byParent) {
|
|
10390
11577
|
const relX = parent.width > 0 ? (rect.x - parent.x) / parent.width : 0;
|
|
10391
11578
|
const relY = parent.height > 0 ? (rect.y - parent.y) / parent.height : 0;
|
|
10392
11579
|
const relW = parent.width > 0 ? rect.width / parent.width : 0;
|
|
10393
11580
|
const relH = parent.height > 0 ? rect.height / parent.height : 0;
|
|
10394
|
-
|
|
11581
|
+
const style = `left:${(relX * 100).toFixed(4)}%;top:${(relY * 100).toFixed(4)}%;width:${(relW * 100).toFixed(4)}%;height:${(relH * 100).toFixed(4)}%`;
|
|
11582
|
+
return this.#renderTile(rect, style, byParent);
|
|
10395
11583
|
}
|
|
10396
11584
|
#renderTopTile(rect, byParent) {
|
|
10397
|
-
|
|
11585
|
+
const style = `left:${(rect.x * 100).toFixed(4)}%;top:${(rect.y * 100).toFixed(4)}%;width:${(rect.width * 100).toFixed(4)}%;height:${(rect.height * 100).toFixed(4)}%`;
|
|
11586
|
+
return this.#renderTile(rect, style, byParent);
|
|
10398
11587
|
}
|
|
10399
11588
|
render() {
|
|
10400
11589
|
const rects = this.#rects();
|
|
@@ -10412,15 +11601,19 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10412
11601
|
class="treemap"
|
|
10413
11602
|
role="tree"
|
|
10414
11603
|
tabindex="0"
|
|
11604
|
+
data-presentation=${this.presentation}
|
|
10415
11605
|
data-input-mode=${this.tickled.inputMode}
|
|
10416
11606
|
style=${`--treemap-padding:${this.padding}px`}
|
|
10417
11607
|
@keydown=${(e) => this.#handleKeyDown(e)}
|
|
10418
11608
|
@pointerleave=${() => this.tickled.clearTickled()}
|
|
11609
|
+
@wheel=${(e) => this.#handleWheel(e)}
|
|
10419
11610
|
>
|
|
10420
11611
|
${this.#renderBreadcrumb()}
|
|
10421
11612
|
${rects.length === 0 ? html`<div class="empty-state">No data</div>` : html`
|
|
10422
11613
|
<div class="viewport" part="viewport">
|
|
10423
|
-
|
|
11614
|
+
<div class="viewport-inner">
|
|
11615
|
+
${repeat(topLevel, (r) => r.node.item.key, (r) => this.#renderTopTile(r, byParent))}
|
|
11616
|
+
</div>
|
|
10424
11617
|
</div>
|
|
10425
11618
|
`}
|
|
10426
11619
|
</div>
|
|
@@ -10443,11 +11636,18 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10443
11636
|
--treemap-tile-border: var(--border-subtle);
|
|
10444
11637
|
--treemap-header-bg: rgb(0 0 0 / 0.12);
|
|
10445
11638
|
--treemap-font-size: var(--text-s);
|
|
11639
|
+
--treemap-zoom-duration: 220ms;
|
|
10446
11640
|
|
|
10447
11641
|
display: block;
|
|
10448
|
-
overflow:
|
|
11642
|
+
overflow: hidden;
|
|
10449
11643
|
}
|
|
10450
11644
|
|
|
11645
|
+
/* Per-category tile colours (set categoryAccessor to opt in): define
|
|
11646
|
+
--treemap-category-SLUG-bg / -text at whatever scope suits (:host
|
|
11647
|
+
here, or globally) for each category your accessor can return —
|
|
11648
|
+
e.g. --treemap-category-urgent-bg: oklch(55% 0.18 25). A category
|
|
11649
|
+
with no matching pair falls back to --treemap-tile-bg/-text. */
|
|
11650
|
+
|
|
10451
11651
|
.treemap {
|
|
10452
11652
|
display: flex;
|
|
10453
11653
|
flex-direction: column;
|
|
@@ -10476,18 +11676,44 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10476
11676
|
.viewport {
|
|
10477
11677
|
position: relative;
|
|
10478
11678
|
width: 100%;
|
|
10479
|
-
|
|
10480
|
-
|
|
11679
|
+
/* flex-basis of --treemap-height (its size when nothing forces a
|
|
11680
|
+
different one — the common case of an auto-height host) but
|
|
11681
|
+
allowed to grow/shrink like any flex item, so it shares the
|
|
11682
|
+
.treemap column's height with the breadcrumb instead of a plain
|
|
11683
|
+
fixed height always claiming that full amount regardless of what
|
|
11684
|
+
else needs space — which is what let the breadcrumb push the
|
|
11685
|
+
host past its own height and scroll once a zoom made it appear. */
|
|
11686
|
+
flex: 1 1 var(--treemap-height);
|
|
11687
|
+
min-height: 0;
|
|
10481
11688
|
overflow: hidden;
|
|
10482
11689
|
border-radius: var(--radius-m);
|
|
10483
11690
|
}
|
|
10484
11691
|
|
|
11692
|
+
/* Transform target for the zoom-in/zoom-out FLIP animation (see
|
|
11693
|
+
#planFlip/#applyFlip) — kept separate from .viewport so the
|
|
11694
|
+
transform never fights that element's own sizing/overflow. */
|
|
11695
|
+
.viewport-inner {
|
|
11696
|
+
position: absolute;
|
|
11697
|
+
inset: 0;
|
|
11698
|
+
transform-origin: 0 0;
|
|
11699
|
+
}
|
|
11700
|
+
|
|
11701
|
+
.viewport-inner.flipping {
|
|
11702
|
+
transition: transform var(--treemap-zoom-duration, 220ms) ease-out;
|
|
11703
|
+
}
|
|
11704
|
+
|
|
10485
11705
|
.tile {
|
|
10486
11706
|
position: absolute;
|
|
10487
11707
|
box-sizing: border-box;
|
|
10488
11708
|
overflow: hidden;
|
|
10489
11709
|
display: flex;
|
|
10490
11710
|
flex-direction: column;
|
|
11711
|
+
/* Override the shared .item class's align-items:center (built for
|
|
11712
|
+
icon+label rows) — tiles need their flex children to stretch to
|
|
11713
|
+
full width, or .tile-children collapses to its (empty) content
|
|
11714
|
+
width and every nested tile collapses with it. */
|
|
11715
|
+
align-items: stretch;
|
|
11716
|
+
white-space: normal;
|
|
10491
11717
|
background: var(--treemap-tile-bg);
|
|
10492
11718
|
color: var(--treemap-tile-text);
|
|
10493
11719
|
border: var(--treemap-tile-border);
|
|
@@ -10506,6 +11732,14 @@ let TreemapListElement = class TreemapListElement extends LitElement {
|
|
|
10506
11732
|
margin-top: 0;
|
|
10507
11733
|
}
|
|
10508
11734
|
|
|
11735
|
+
/* Cascaded presentation (Lü & Fogarty 2008): the geometric inset is
|
|
11736
|
+
already in the layout rects — this adds the stack-of-cards depth
|
|
11737
|
+
cue on top. Parent tiles read as cards sitting behind their
|
|
11738
|
+
children. */
|
|
11739
|
+
.treemap[data-presentation="cascaded"] .tile {
|
|
11740
|
+
box-shadow: 2px 2px 0 rgb(0 0 0 / 0.25);
|
|
11741
|
+
}
|
|
11742
|
+
|
|
10509
11743
|
.tile-header {
|
|
10510
11744
|
flex-shrink: 0;
|
|
10511
11745
|
padding: 1px var(--space-xs);
|
|
@@ -10576,16 +11810,37 @@ __decorate([property({
|
|
|
10576
11810
|
type: String,
|
|
10577
11811
|
attribute: `tiling-method`
|
|
10578
11812
|
})], TreemapListElement.prototype, "tilingMethod", void 0);
|
|
11813
|
+
__decorate([property({
|
|
11814
|
+
type: String,
|
|
11815
|
+
attribute: `presentation`
|
|
11816
|
+
})], TreemapListElement.prototype, "presentation", void 0);
|
|
11817
|
+
__decorate([property({
|
|
11818
|
+
type: Number,
|
|
11819
|
+
attribute: `cascade-offset`
|
|
11820
|
+
})], TreemapListElement.prototype, "cascadeOffset", void 0);
|
|
10579
11821
|
__decorate([property({
|
|
10580
11822
|
type: Number,
|
|
10581
11823
|
attribute: `max-depth`
|
|
10582
11824
|
})], TreemapListElement.prototype, "maxDepth", void 0);
|
|
11825
|
+
__decorate([property({
|
|
11826
|
+
type: Boolean,
|
|
11827
|
+
attribute: `hide-leaves`
|
|
11828
|
+
})], TreemapListElement.prototype, "hideLeaves", void 0);
|
|
10583
11829
|
__decorate([property({
|
|
10584
11830
|
type: Number,
|
|
10585
11831
|
attribute: `min-label-area`
|
|
10586
11832
|
})], TreemapListElement.prototype, "minLabelArea", void 0);
|
|
11833
|
+
__decorate([property({
|
|
11834
|
+
type: Number,
|
|
11835
|
+
attribute: `min-tile-width`
|
|
11836
|
+
})], TreemapListElement.prototype, "minTileWidth", void 0);
|
|
10587
11837
|
__decorate([property({ type: Number })], TreemapListElement.prototype, "padding", void 0);
|
|
11838
|
+
__decorate([property({
|
|
11839
|
+
type: Number,
|
|
11840
|
+
attribute: `min-child-area`
|
|
11841
|
+
})], TreemapListElement.prototype, "minChildArea", void 0);
|
|
10588
11842
|
__decorate([property({ attribute: false })], TreemapListElement.prototype, "valueAccessor", void 0);
|
|
11843
|
+
__decorate([property({ attribute: false })], TreemapListElement.prototype, "categoryAccessor", void 0);
|
|
10589
11844
|
__decorate([property({ attribute: false })], TreemapListElement.prototype, "selectedNode", void 0);
|
|
10590
11845
|
__decorate([state()], TreemapListElement.prototype, "_selectedNodes", void 0);
|
|
10591
11846
|
__decorate([property({ attribute: false })], TreemapListElement.prototype, "filterPredicate", void 0);
|
|
@@ -11244,6 +12499,6 @@ function init() {
|
|
|
11244
12499
|
console.log(`Init`);
|
|
11245
12500
|
}
|
|
11246
12501
|
//#endregion
|
|
11247
|
-
export { AcTextElement, AcTokenElement, BottomDrawerElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IcicleTreeListElement, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListNativeDragController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeDropController, TreeKeyboardNavController, TreeListElement, TreemapListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, applyTreeDrop, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeIcicleLayout, computeLeafGeoms, computeTreemapLayout, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, dropTargetFor, extractStack, findIcicleNode, findIciclePath, findNode, findParentOf, findStackOf, findStackPathByName, findTreemapNode, findTreemapPath, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normaliseValue, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, toolbarGapIndex, walk, wrapRoot };
|
|
12502
|
+
export { AcTextElement, AcTokenElement, BottomDrawerElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DataTableElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GridListElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IcicleTreeListElement, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, ListDragSelectController, ListNativeDragController, ListSearchController, ListSelectionController, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, MulticolListElement, NarrowedTextElement, NotificationManager, NotificationPillElement, PanTextElement, PanelElement, PanelGroupElement, PlotDataSeries, PlotHistogram, PlotMultiAxis, PlotSingleAxis, PlotXyAxis, PolarPad, RadialInputElement, RadioGroup, RangeElement, RangeInput, RangeMultiElement, RatingElement, SelectHorizElement, SliderInputElement, SlidingNumberElement, SnackbarElement, SnapContainer, SplitButton, SplitLayoutElement, SplitPanelElement, SwipeElement, TabController, TabListElement, TabListItemElement, TabPanelElement, TabPanelsElement, TextShimmerElement, TickledController, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeDropController, TreeKeyboardNavController, TreeListElement, TreemapListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, applyTreeDrop, attachTimelineInteractions, bottomDrawerCommandId, buildHighlightTargets, buildModel, captionForEntry, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeIcicleLayout, computeLeafGeoms, computeTreemapLayout, createAcCompletionProvider, createDataListSearchAdapter, createDefaultListDragPreview, createElementIncrSearch, createElementListSearchAdapter, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createTreeListSearchAdapter, createVerticalListAdapter, distribute, dropTargetFor, extractStack, findIcicleNode, findIciclePath, findNode, findParentOf, findStackOf, findStackPathByName, findTreemapNode, findTreemapPath, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, listSearchStyles, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normaliseValue, normalize, panelGapIndex, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, pruneDraggedDescendants, readBaseColour, registerFontIcon, registerIcon, registerListSearchCommands, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, startListItemOutAnimation, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, toolbarGapIndex, walk, wrapRoot };
|
|
11248
12503
|
|
|
11249
12504
|
//# sourceMappingURL=index.js.map
|