@ixfx/components 0.15.4 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/index.d.ts +1223 -48
- package/bundle/index.js +875 -700
- package/dist/ac-text.d.ts +1 -1
- package/dist/{ac-token-Cq7lsYf-.d.ts → ac-token-qEs3FsQv.d.ts} +1 -1
- package/dist/ac-token.d.ts +1 -1
- package/dist/colour-picker.d.ts +1 -1
- package/dist/crumb-navigation-BbqfmkYs.d.ts +147 -0
- package/dist/crumb-navigation-YnFVFqrx.js +1290 -0
- package/dist/crumbs.d.ts +2 -115
- package/dist/crumbs.js +1 -1179
- package/dist/{data-grid-CbiubdG2.d.ts → data-grid-CIG2DVip.d.ts} +58 -9
- package/dist/{data-grid-Dx3l3dxw.js → data-grid-rMkLX2qo.js} +630 -215
- package/dist/data-grid.d.ts +3 -3
- package/dist/data-grid.js +1 -1
- package/dist/{detail-list-ClkMV0SN.d.ts → detail-list-6iMTNP4N.d.ts} +35 -5
- package/dist/{detail-list-q8bs67BM.js → detail-list-C4pDSoI8.js} +218 -7
- package/dist/detail-list.d.ts +4 -4
- package/dist/detail-list.js +1 -1
- package/dist/{flat-list-0xTZdXzh.d.ts → flat-list-JPgQSmLW.d.ts} +28 -5
- package/dist/flat-list.d.ts +2 -2
- package/dist/flat-list.js +123 -4
- package/dist/{grid-list-y6dWJYu3.js → grid-list-CdEkScn-.js} +276 -75
- package/dist/{grid-list-BnWJUIBC.d.ts → grid-list-D6n7WH8l.d.ts} +128 -22
- package/dist/grid-list.d.ts +3 -3
- package/dist/grid-list.js +1 -1
- package/dist/{grouped-item-lister-Bhyo-dkH.d.ts → grouped-item-lister-DETte0Dh.d.ts} +1 -1
- package/dist/grouped-item-lister.d.ts +1 -1
- package/dist/{icon-DR_3rSyZ.d.ts → icon-CDGprSdE.d.ts} +1 -1
- package/dist/icons.d.ts +2 -2
- package/dist/incr-search.d.ts +1 -1
- package/dist/index-9ttcdwlL.d.ts +71 -0
- package/dist/{index-BAuqR9El.d.ts → index-Bx0BXPxI.d.ts} +52 -6
- package/dist/index-CYRc6Dah.d.ts +475 -0
- package/dist/{index-garxxqgV.d.ts → index-CYndvTWH.d.ts} +1 -1
- package/dist/{index-BUB5SICW.d.ts → index-CoqtMSd9.d.ts} +1 -1
- package/dist/{index-Bz3loIAf.d.ts → index-DN2BWctP.d.ts} +1 -1
- package/dist/{index-BPTuCRGb.d.ts → index-Dr80Zdl6.d.ts} +2 -2
- package/dist/{index-cnaRPQPp.d.ts → index-DxyyVqzA.d.ts} +2 -2
- package/dist/{index-CJ5fshD3.d.ts → index-UvnoZUAo.d.ts} +1 -1
- package/dist/index.d.ts +380 -33
- package/dist/index.js +2247 -866
- package/dist/{labelled-input-base-Dp_9KP0G.d.ts → labelled-input-base-DiTme1JO.d.ts} +1 -1
- package/dist/labelled-radial-input.d.ts +2 -2
- package/dist/labelled-range-input.d.ts +1 -1
- package/dist/labelled-slider-input.d.ts +1 -1
- package/dist/list-group-header.d.ts +1 -1
- package/dist/{list-native-drag-controller-O2Nruqpi.d.ts → list-native-drag-controller-D_UrXipS.d.ts} +1 -1
- package/dist/{list-selection-controller-BeE70lbB.js → list-selection-controller-5L0Nj9tR.js} +87 -2
- package/dist/{menu-item-B0zqw2xp.d.ts → menu-item-bF6narIz.d.ts} +1 -1
- package/dist/menu.d.ts +2 -2
- package/dist/miller-BFRfn0ve.js +2237 -0
- package/dist/miller.d.ts +4 -437
- package/dist/miller.js +1 -1815
- package/dist/panel.d.ts +1 -1
- package/dist/pending-BGidHppL.js +91 -0
- package/dist/scroll-hover-guard-Cjw6179Q.js +35 -0
- package/dist/{split-layout-C5SYbDrq.d.ts → split-layout-CophkNeU.d.ts} +42 -4
- package/dist/{split-layout-Deoxy47d.js → split-layout-DTY4PJRp.js} +251 -19
- package/dist/split-layout.d.ts +1 -1
- package/dist/split-layout.js +1 -1
- package/dist/sunburst-tree.d.ts +2 -0
- package/dist/sunburst-tree.js +2 -0
- package/dist/sunbursttree-list-B_VxOHlo.js +1556 -0
- package/dist/sunbursttree-list-D6j5BxVQ.d.ts +303 -0
- package/dist/{tab-list-wC0xNz1b.d.ts → tab-list-Df371rqw.d.ts} +1 -1
- package/dist/tabs.d.ts +1 -1
- package/dist/{titlebar-B5zSTZnF.d.ts → titlebar-Tqu-VvMU.d.ts} +1 -1
- package/dist/titlebar.d.ts +1 -1
- package/dist/tree-CftHwtEb.js +146 -0
- package/dist/{tree-D8VxvR9u.js → tree-Crdpl4nc.js} +275 -47
- package/dist/{tree-component-C6UHO-w9.d.ts → tree-component-DgfkxmVY.d.ts} +19 -8
- package/dist/tree.d.ts +3 -3
- package/dist/tree.js +1 -1
- package/dist/types-LU1u0CB8.d.ts +87 -0
- package/dist/vertical-list-1V1Vg2O5.js +1993 -0
- package/dist/{vertical-list-B8FoU-pU.d.ts → vertical-list-BPDmZx6T.d.ts} +31 -3
- package/dist/vertical-list.d.ts +3 -3
- package/dist/vertical-list.js +1 -1789
- package/dist/view-state-controller-BdyXWUUM.js +305 -0
- package/docs-user/README.md +3 -0
- package/docs-user/crumbs.md +27 -0
- package/docs-user/data-grid.md +77 -5
- package/docs-user/detail-list.md +54 -0
- package/docs-user/dock.md +54 -12
- package/docs-user/flat-list.md +27 -0
- package/docs-user/grid-list.md +64 -11
- package/docs-user/hyperbolic-tree.md +31 -4
- package/docs-user/icicle-tree.md +36 -0
- package/docs-user/index.json +17 -2
- package/docs-user/llms.txt +3 -0
- package/docs-user/miller.md +52 -21
- package/docs-user/multicol-list.md +50 -8
- package/docs-user/split-layout.md +45 -17
- package/docs-user/sunburst-tree.md +223 -0
- package/docs-user/tree.md +46 -7
- package/docs-user/treemap.md +32 -0
- package/docs-user/twosplit-layout.md +22 -0
- package/docs-user/user-catalog.md +1 -1
- package/docs-user/vertical-list.md +55 -7
- package/llms.txt +3 -0
- package/package.json +5 -1
|
@@ -5,8 +5,11 @@ import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js"
|
|
|
5
5
|
import { c as createElementListSearchAdapter, d as createDataListSearchAdapter, i as ListSearchController, n as listSearchStyles, o as registerListSearchCommands, r as LIST_SEARCH_MODES, u as keyForElement } from "./incr-search-JIhwB49y.js";
|
|
6
6
|
import { i as resolveGroupKey, r as partitionByGroup } from "./grouping-RYPnHkrS.js";
|
|
7
7
|
import { a as listItemFadeKeyframes, t as ListNativeDragController } from "./list-native-drag-controller-CcXoS0YX.js";
|
|
8
|
-
import { r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-
|
|
8
|
+
import { r as ListDragSelectController, t as ListSelectionController } from "./list-selection-controller-5L0Nj9tR.js";
|
|
9
|
+
import { c as keysAsFlags, f as withoutFlag, i as capFlags, l as keysToArray, o as flagsFromRecord, s as flagsToRecord, t as EMPTY_FLAGS } from "./pending-BGidHppL.js";
|
|
10
|
+
import { n as createScrollHoverGuard } from "./scroll-hover-guard-Cjw6179Q.js";
|
|
9
11
|
import "./interaction-E-uMGHkJ.js";
|
|
12
|
+
import { t as ViewStateController } from "./view-state-controller-BdyXWUUM.js";
|
|
10
13
|
import { t as scrollFadeStyles } from "./scroll-fade-Xx28D7mH.js";
|
|
11
14
|
import { n as appearanceColor, t as appearanceAbsent } from "./item-appearance-BjcMyauw.js";
|
|
12
15
|
import "./pan-text-CqtvjPyK.js";
|
|
@@ -669,6 +672,90 @@ var DataGridListSource = class {
|
|
|
669
672
|
}
|
|
670
673
|
};
|
|
671
674
|
//#endregion
|
|
675
|
+
//#region src/grid-list/grid-list-view-state.ts
|
|
676
|
+
/**
|
|
677
|
+
* - live collapsed groups that no longer exist → pending `false`
|
|
678
|
+
* - pending groups that now exist → applied (`false` collapses, `true` opens)
|
|
679
|
+
*
|
|
680
|
+
* Returns the inputs untouched (and `changed: false`) when nothing moved.
|
|
681
|
+
*/
|
|
682
|
+
function reconcileGroups(collapsed, pending, has) {
|
|
683
|
+
let nextCollapsed;
|
|
684
|
+
let nextPending;
|
|
685
|
+
for (const key of collapsed) {
|
|
686
|
+
if (has(key)) continue;
|
|
687
|
+
nextCollapsed ??= new Set(collapsed);
|
|
688
|
+
nextCollapsed.delete(key);
|
|
689
|
+
nextPending ??= new Map(pending);
|
|
690
|
+
nextPending.set(key, false);
|
|
691
|
+
}
|
|
692
|
+
for (const [key, open] of pending) {
|
|
693
|
+
if (!has(key)) continue;
|
|
694
|
+
nextPending ??= new Map(pending);
|
|
695
|
+
nextPending.delete(key);
|
|
696
|
+
if (open === (nextCollapsed ?? collapsed).has(key)) {
|
|
697
|
+
nextCollapsed ??= new Set(collapsed);
|
|
698
|
+
if (open) nextCollapsed.delete(key);
|
|
699
|
+
else nextCollapsed.add(key);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
collapsed: nextCollapsed ?? collapsed,
|
|
704
|
+
pending: nextPending ? capFlags(nextPending) : pending,
|
|
705
|
+
changed: nextCollapsed !== void 0 || nextPending !== void 0
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
/** `expanded` record: collapsed groups as `false`, plus pending choices when asked. */
|
|
709
|
+
function expandedRecord(collapsed, pending, withPending) {
|
|
710
|
+
return flagsToRecord(withPending ? pending : [], keysAsFlags(collapsed, false));
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Capture the scroll position against the virtual layout. The anchor is the
|
|
714
|
+
* first item of the first line intersecting the viewport, and the offset is
|
|
715
|
+
* how far the viewport start sits past that line's start. `keyAt` returns
|
|
716
|
+
* `undefined` for keys that can't anchor (auto-assigned keyless items).
|
|
717
|
+
*/
|
|
718
|
+
function captureGridScroll(layout, mainOffset, crossOffset, keyAt) {
|
|
719
|
+
const base = layout.horizontal ? {
|
|
720
|
+
top: crossOffset,
|
|
721
|
+
left: mainOffset
|
|
722
|
+
} : crossOffset === 0 ? { top: mainOffset } : {
|
|
723
|
+
top: mainOffset,
|
|
724
|
+
left: crossOffset
|
|
725
|
+
};
|
|
726
|
+
const win = layout.window(mainOffset, 0);
|
|
727
|
+
if (win.ranges.length === 0) return base;
|
|
728
|
+
const index = win.startIndex;
|
|
729
|
+
const key = keyAt(index);
|
|
730
|
+
if (key === void 0) return base;
|
|
731
|
+
const lineStart = layout.offsetOfAnchor(index, 0);
|
|
732
|
+
return {
|
|
733
|
+
...base,
|
|
734
|
+
anchor: {
|
|
735
|
+
key,
|
|
736
|
+
offset: Math.round(mainOffset - lineStart)
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Where to scroll for a saved {@link ScrollState}, or `undefined` while the
|
|
742
|
+
* anchor key isn't in the effective order yet. `extent` is the viewport's
|
|
743
|
+
* main-axis size.
|
|
744
|
+
*/
|
|
745
|
+
function gridScrollTarget(scroll, layout, indexOfKey, extent) {
|
|
746
|
+
let target = layout.horizontal ? scroll.left ?? 0 : scroll.top;
|
|
747
|
+
if (scroll.anchor) {
|
|
748
|
+
const index = indexOfKey(scroll.anchor.key);
|
|
749
|
+
if (index < 0) return void 0;
|
|
750
|
+
target = layout.offsetOfAnchor(index, 0) + scroll.anchor.offset;
|
|
751
|
+
}
|
|
752
|
+
const max = Math.max(0, layout.totalExtent - extent);
|
|
753
|
+
return {
|
|
754
|
+
offset: Math.max(0, Math.min(target, max)),
|
|
755
|
+
reached: target <= max + 1
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
//#endregion
|
|
672
759
|
//#region src/grid-list/grid-list.ts
|
|
673
760
|
const ITEM_SELECTOR = `:scope > *`;
|
|
674
761
|
const MIN_ITEM_SIZE = 16;
|
|
@@ -707,7 +794,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
707
794
|
this.selectionMode = `single`;
|
|
708
795
|
this.interactionMode = `standard`;
|
|
709
796
|
this.disableHoverTickle = false;
|
|
710
|
-
this.
|
|
797
|
+
this.scrollHoverTickle = false;
|
|
711
798
|
this.itemSize = 160;
|
|
712
799
|
this.gap = 8;
|
|
713
800
|
this.orientation = `grid`;
|
|
@@ -730,6 +817,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
730
817
|
this._scrollLeft = 0;
|
|
731
818
|
this._viewportHeight = 0;
|
|
732
819
|
this._viewportWidth = 0;
|
|
820
|
+
this.#viewState = new ViewStateController(this, {
|
|
821
|
+
kind: `grid-list`,
|
|
822
|
+
capture: () => this.getViewState()
|
|
823
|
+
});
|
|
824
|
+
this.#pendingExpanded = EMPTY_FLAGS;
|
|
733
825
|
this.#viewportRef = createRef();
|
|
734
826
|
this.#scrollRaf = 0;
|
|
735
827
|
this.#snapTimer = 0;
|
|
@@ -775,14 +867,24 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
775
867
|
onTickle: (k) => {
|
|
776
868
|
this.#stopActiveCaptionPan();
|
|
777
869
|
if (k !== void 0) this.#emitItemEvent(`list-tickle`, k);
|
|
870
|
+
if (this.tickled.inputMode === `keyboard`) {
|
|
871
|
+
if (k !== void 0 && !this.#viewState.isRestoring) this.#pendingFocus = void 0;
|
|
872
|
+
this.#viewState.notify();
|
|
873
|
+
}
|
|
778
874
|
},
|
|
779
875
|
onStateChange: () => this._syncItemStates(),
|
|
780
876
|
itemWrap: this.#itemWrap,
|
|
781
877
|
hoverDisabled: () => this.disableHoverTickle || this.#dragSelect.isDragging || this.#nativeDrag.isDragging
|
|
782
878
|
});
|
|
783
|
-
this.#
|
|
784
|
-
|
|
785
|
-
|
|
879
|
+
this.#scrollHover = createScrollHoverGuard({
|
|
880
|
+
host: this,
|
|
881
|
+
enabled: () => !this.scrollHoverTickle,
|
|
882
|
+
onResume: (x, y) => {
|
|
883
|
+
if (this.disableHoverTickle) return;
|
|
884
|
+
const key = this.shadowRoot?.elementFromPoint?.(x, y)?.closest(`.cell`)?.getAttribute(`data-key`);
|
|
885
|
+
if (key) this.tickled.handlePointerEnter(key);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
786
888
|
this.#marqueePreview = /* @__PURE__ */ new Set();
|
|
787
889
|
this.#nativeDragging = false;
|
|
788
890
|
this.#dragOriginKey = void 0;
|
|
@@ -863,12 +965,13 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
863
965
|
}
|
|
864
966
|
});
|
|
865
967
|
this.#groupByChanged = false;
|
|
968
|
+
this.#resolveKey = (key) => this.#source.indexOfKey(key) >= 0 ? key : void 0;
|
|
866
969
|
this.#markedTickled = /* @__PURE__ */ new Set();
|
|
867
970
|
this.#markedSelected = /* @__PURE__ */ new Set();
|
|
868
971
|
this.#selectedSnapshot = /* @__PURE__ */ new Set();
|
|
869
972
|
this.#onScroll = (e) => {
|
|
870
973
|
const el = e.target;
|
|
871
|
-
this.#
|
|
974
|
+
this.#scrollHover.onScroll();
|
|
872
975
|
this.#cancelScrollRaf();
|
|
873
976
|
this.#scrollRaf = requestAnimationFrame(() => {
|
|
874
977
|
this.#scrollRaf = 0;
|
|
@@ -877,6 +980,10 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
877
980
|
this.#emitViewportChange();
|
|
878
981
|
});
|
|
879
982
|
this.#scheduleSnap();
|
|
983
|
+
const offset = this.orientation === `row` ? el.scrollLeft : el.scrollTop;
|
|
984
|
+
const echo = this.#restoredScrollOffset !== void 0 && Math.abs(offset - this.#restoredScrollOffset) <= 1;
|
|
985
|
+
this.#restoredScrollOffset = void 0;
|
|
986
|
+
if (!echo && !this.#viewState.pendingScroll) this.#viewState.notify();
|
|
880
987
|
};
|
|
881
988
|
this.#onWheel = (e) => {
|
|
882
989
|
if (this.orientation !== `row`) return;
|
|
@@ -885,23 +992,10 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
885
992
|
const vp = this.#viewportRef.value ?? e.currentTarget;
|
|
886
993
|
if (!vp) return;
|
|
887
994
|
if (vp.scrollWidth <= vp.clientWidth + 1) return;
|
|
888
|
-
this.#
|
|
995
|
+
this.#scrollHover.onScroll();
|
|
889
996
|
vp.scrollLeft += e.deltaX + e.deltaY;
|
|
890
997
|
e.preventDefault();
|
|
891
998
|
};
|
|
892
|
-
this.#onHostPointerMove = (e) => {
|
|
893
|
-
const moved = !Number.isNaN(this.#lastPointerX) && (e.clientX !== this.#lastPointerX || e.clientY !== this.#lastPointerY);
|
|
894
|
-
this.#lastPointerX = e.clientX;
|
|
895
|
-
this.#lastPointerY = e.clientY;
|
|
896
|
-
if (!moved || !this.#scrollHoverSuppress) return;
|
|
897
|
-
this.#endScrollHoverSuppress();
|
|
898
|
-
if (this.disableHoverTickle) return;
|
|
899
|
-
const key = this.shadowRoot?.elementFromPoint?.(e.clientX, e.clientY)?.closest(`.cell`)?.getAttribute(`data-key`);
|
|
900
|
-
if (key) this.tickled.handlePointerEnter(key);
|
|
901
|
-
};
|
|
902
|
-
this.#onHostPointerLeave = () => {
|
|
903
|
-
this.#endScrollHoverSuppress();
|
|
904
|
-
};
|
|
905
999
|
this._onKeyDown = (e) => {
|
|
906
1000
|
if ((e.ctrlKey || e.metaKey) && e.key === `f`) return;
|
|
907
1001
|
if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
|
|
@@ -1210,7 +1304,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1210
1304
|
}
|
|
1211
1305
|
:host(:not([caption-style="separate"])) .cell.tickled,
|
|
1212
1306
|
/* [data-scroll-hover-suppress] is set while a scroll gesture moves
|
|
1213
|
-
cells beneath a stationary pointer (see
|
|
1307
|
+
cells beneath a stationary pointer (see scroll-hover-tickle):
|
|
1214
1308
|
the :hover highlight is gated so the visual cursor holds still too. */
|
|
1215
1309
|
:host(:not([caption-style="separate"]):not([disable-hover-tickle]):not([data-scroll-hover-suppress])) .cell:hover {
|
|
1216
1310
|
background: var(--item-bg-tickled);
|
|
@@ -1390,6 +1484,13 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1390
1484
|
`
|
|
1391
1485
|
];
|
|
1392
1486
|
}
|
|
1487
|
+
#viewState;
|
|
1488
|
+
/** Saved open / closed choices for groups not currently present. */
|
|
1489
|
+
#pendingExpanded;
|
|
1490
|
+
/** Saved keyboard-cursor key, waiting for its item to appear. */
|
|
1491
|
+
#pendingFocus;
|
|
1492
|
+
/** Main-axis offset the last scroll restore wrote, so its `scroll` echo isn't reported as `user`. */
|
|
1493
|
+
#restoredScrollOffset;
|
|
1393
1494
|
#viewportRef;
|
|
1394
1495
|
#resizeObserver;
|
|
1395
1496
|
#mutationObserver;
|
|
@@ -1449,16 +1550,8 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1449
1550
|
*/
|
|
1450
1551
|
#sourceElement;
|
|
1451
1552
|
#source;
|
|
1452
|
-
/**
|
|
1453
|
-
|
|
1454
|
-
* just-finished scroll gesture (`disable-scroll-hover-tickle`). While set,
|
|
1455
|
-
* the host carries `[data-scroll-hover-suppress]`, which gates the CSS
|
|
1456
|
-
* `:hover` highlight so the visual cursor does not churn either.
|
|
1457
|
-
*/
|
|
1458
|
-
#scrollHoverSuppress;
|
|
1459
|
-
/** Last pointer position seen over the host; real movement ends suppression. */
|
|
1460
|
-
#lastPointerX;
|
|
1461
|
-
#lastPointerY;
|
|
1553
|
+
/** Holds the cursor still while a scroll gesture moves cells beneath a stationary pointer. */
|
|
1554
|
+
#scrollHover;
|
|
1462
1555
|
/** Live drag-select preview: covered keys + the surface-relative rect. */
|
|
1463
1556
|
#marqueePreview;
|
|
1464
1557
|
#marqueeRect;
|
|
@@ -1610,8 +1703,9 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1610
1703
|
[`caption-backdrop`, this.captionBackdrop]
|
|
1611
1704
|
]) if (!this.hasAttribute(attr)) this.setAttribute(attr, value);
|
|
1612
1705
|
this.addEventListener(`keydown`, this._onKeyDown);
|
|
1613
|
-
this.addEventListener(`pointermove`, this.#
|
|
1614
|
-
this.addEventListener(`pointerleave`, this.#
|
|
1706
|
+
this.addEventListener(`pointermove`, this.#scrollHover.onPointerMove, { passive: true });
|
|
1707
|
+
this.addEventListener(`pointerleave`, this.#scrollHover.onPointerLeave);
|
|
1708
|
+
this.#viewState.observeScrollTarget(this);
|
|
1615
1709
|
this.#mutationObserver ??= new MutationObserver((records) => this.#onChildrenChanged(records));
|
|
1616
1710
|
this.#mutationObserver.observe(this, { childList: true });
|
|
1617
1711
|
this.#itemAttrObserver ??= new MutationObserver((records) => this.#onItemAttributesChanged(records));
|
|
@@ -1631,9 +1725,9 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1631
1725
|
super.disconnectedCallback();
|
|
1632
1726
|
this.#stopActiveCaptionPan();
|
|
1633
1727
|
this.removeEventListener(`keydown`, this._onKeyDown);
|
|
1634
|
-
this.removeEventListener(`pointermove`, this.#
|
|
1635
|
-
this.removeEventListener(`pointerleave`, this.#
|
|
1636
|
-
this.#
|
|
1728
|
+
this.removeEventListener(`pointermove`, this.#scrollHover.onPointerMove);
|
|
1729
|
+
this.removeEventListener(`pointerleave`, this.#scrollHover.onPointerLeave);
|
|
1730
|
+
this.#scrollHover.end();
|
|
1637
1731
|
this.#resizeObserver?.disconnect();
|
|
1638
1732
|
this.#resizeObserver = void 0;
|
|
1639
1733
|
this.#mutationObserver?.disconnect();
|
|
@@ -1665,10 +1759,11 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1665
1759
|
if (removedKeys.length > 0) {
|
|
1666
1760
|
const present = new Set(this.#source.keys());
|
|
1667
1761
|
const gone = removedKeys.filter((k) => !present.has(k));
|
|
1668
|
-
if (gone.length > 0) this
|
|
1762
|
+
if (gone.length > 0) this.#demoteGone(gone);
|
|
1669
1763
|
const tickledId = this.tickled.tickledId;
|
|
1670
|
-
if (tickledId && gone.includes(tickledId)) this
|
|
1764
|
+
if (tickledId && gone.includes(tickledId)) this.#demoteFocus(tickledId);
|
|
1671
1765
|
}
|
|
1766
|
+
this.#reconcileViewState();
|
|
1672
1767
|
this._syncItemStates(true);
|
|
1673
1768
|
this.#noteChildMutation(previousLength, records);
|
|
1674
1769
|
this.requestUpdate();
|
|
@@ -1753,6 +1848,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1753
1848
|
this.#resizeObserver.observe(el);
|
|
1754
1849
|
this.#measureViewport(false);
|
|
1755
1850
|
this.#syncItems();
|
|
1851
|
+
this.#reconcileViewState();
|
|
1756
1852
|
this.requestUpdate();
|
|
1757
1853
|
this.#nativeDrag.refreshDraggable();
|
|
1758
1854
|
}
|
|
@@ -1788,7 +1884,8 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1788
1884
|
this.#applyFilters();
|
|
1789
1885
|
this.updateComplete.then(() => this.#emitViewportChange());
|
|
1790
1886
|
}
|
|
1791
|
-
|
|
1887
|
+
const dataModeChanged = changed.has(`items`) || changed.has(`itemAdapter`);
|
|
1888
|
+
if (dataModeChanged) {
|
|
1792
1889
|
this.#zoomAnchor = void 0;
|
|
1793
1890
|
const prevKeys = this.#source?.keys() ?? [];
|
|
1794
1891
|
const modeFlipped = this.itemAdapter !== void 0 !== this.#source instanceof DataGridListSource;
|
|
@@ -1796,9 +1893,9 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1796
1893
|
const nextKeys = this.#source.keys();
|
|
1797
1894
|
const nextKeySet = new Set(nextKeys);
|
|
1798
1895
|
const removedKeys = prevKeys.filter((k) => !nextKeySet.has(k));
|
|
1799
|
-
if (removedKeys.length > 0) this
|
|
1896
|
+
if (removedKeys.length > 0) this.#demoteGone(removedKeys);
|
|
1800
1897
|
const tickledId = this.tickled.tickledId;
|
|
1801
|
-
if (tickledId && !nextKeySet.has(tickledId)) this
|
|
1898
|
+
if (tickledId && !nextKeySet.has(tickledId)) this.#demoteFocus(tickledId);
|
|
1802
1899
|
this._syncItemStates();
|
|
1803
1900
|
this.updateComplete.then(() => this.#emitViewportChange());
|
|
1804
1901
|
if (modeFlipped) {
|
|
@@ -1837,6 +1934,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1837
1934
|
this.#captureCentreAnchor(prevOffset, prevExtent, prevItemSize, prevGap, prevCaptionStyle, prevCaptionHeight, prevOrientation);
|
|
1838
1935
|
}
|
|
1839
1936
|
}
|
|
1937
|
+
if (dataModeChanged || groupsChanged || changed.has(`filterPredicate`) || changed.has(`itemFilterPredicate`)) this.#reconcileViewState();
|
|
1840
1938
|
}
|
|
1841
1939
|
#groupByChanged;
|
|
1842
1940
|
/** Grouping config for the active source, or `undefined` when ungrouped. */
|
|
@@ -1880,6 +1978,8 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1880
1978
|
if (collapsed) next.add(groupKey);
|
|
1881
1979
|
else next.delete(groupKey);
|
|
1882
1980
|
this._collapsedGroups = next;
|
|
1981
|
+
this.#pendingExpanded = withoutFlag(this.#pendingExpanded, groupKey);
|
|
1982
|
+
this.#viewState.notify();
|
|
1883
1983
|
this._dispatch(`list-group-toggle`, {
|
|
1884
1984
|
groupKey,
|
|
1885
1985
|
collapsed
|
|
@@ -1916,7 +2016,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1916
2016
|
this.selection.clearAnchor();
|
|
1917
2017
|
this.#syncAriaMultiselectable();
|
|
1918
2018
|
}
|
|
1919
|
-
if (changed.has(`
|
|
2019
|
+
if (changed.has(`scrollHoverTickle`) && this.scrollHoverTickle) this.#scrollHover.end();
|
|
1920
2020
|
if (changed.has(`itemsDraggable`)) this.#nativeDrag.refreshDraggable();
|
|
1921
2021
|
if (changed.has(`orientation`)) {
|
|
1922
2022
|
const vp = this.#viewportRef.value;
|
|
@@ -1936,6 +2036,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1936
2036
|
if (Math.abs(vp.scrollLeft - this._scrollLeft) > .5) vp.scrollLeft = this._scrollLeft;
|
|
1937
2037
|
}
|
|
1938
2038
|
this.#applyZoomAnchor();
|
|
2039
|
+
this.#viewState.applyPendingScroll((scroll) => this.#applyScroll(scroll));
|
|
1939
2040
|
this.#activateTickledPan();
|
|
1940
2041
|
if (this.#groupByChanged) {
|
|
1941
2042
|
this.#groupByChanged = false;
|
|
@@ -2099,8 +2200,9 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2099
2200
|
}
|
|
2100
2201
|
/**
|
|
2101
2202
|
* Data mode: replace the whole item array. Selection and cursor keep any key
|
|
2102
|
-
* that is still present; keys that vanish are
|
|
2103
|
-
* A no-op in
|
|
2203
|
+
* that is still present; keys that vanish are deselected silently and held
|
|
2204
|
+
* as pending view state, so they come back if the key reappears. A no-op in
|
|
2205
|
+
* element mode.
|
|
2104
2206
|
*/
|
|
2105
2207
|
setItems(items) {
|
|
2106
2208
|
if (this.itemAdapter) this.items = items;
|
|
@@ -2128,8 +2230,9 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2128
2230
|
this.items = next;
|
|
2129
2231
|
}
|
|
2130
2232
|
/**
|
|
2131
|
-
* Data mode: remove the items carrying these keys. Removed keys
|
|
2132
|
-
*
|
|
2233
|
+
* Data mode: remove the items carrying these keys. Removed keys leave the
|
|
2234
|
+
* selection and cursor silently (via the `items` change) and are held as
|
|
2235
|
+
* pending view state. A no-op in element mode.
|
|
2133
2236
|
*/
|
|
2134
2237
|
removeItems(keys) {
|
|
2135
2238
|
if (!this.itemAdapter) return;
|
|
@@ -2139,18 +2242,22 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2139
2242
|
}
|
|
2140
2243
|
/**
|
|
2141
2244
|
* Data mode: drop every item. Selection and cursor are reset and one
|
|
2142
|
-
* `list-select` with empty sets is emitted
|
|
2245
|
+
* `list-select` with empty sets is emitted; their keys are held as pending
|
|
2246
|
+
* view state, so re-adding the same keys restores them. A no-op in element
|
|
2247
|
+
* mode.
|
|
2143
2248
|
*/
|
|
2144
2249
|
clearItems() {
|
|
2145
2250
|
if (!this.itemAdapter) return;
|
|
2146
2251
|
this.selection.reset();
|
|
2147
|
-
this.tickled.
|
|
2252
|
+
const tickled = this.tickled.tickledId;
|
|
2253
|
+
if (tickled !== void 0) this.#demoteFocus(tickled);
|
|
2148
2254
|
this.items = [];
|
|
2149
2255
|
this.#emitSelect(/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
|
|
2150
2256
|
}
|
|
2151
2257
|
/** Move the cursor to the item with this key and scroll it into view. */
|
|
2152
2258
|
focusKey(key) {
|
|
2153
2259
|
if (this.#source.indexOfKey(key) < 0) return;
|
|
2260
|
+
this.#pendingFocus = void 0;
|
|
2154
2261
|
this.tickled.focus(key);
|
|
2155
2262
|
this.#scrollItemIntoView(key);
|
|
2156
2263
|
}
|
|
@@ -2158,6 +2265,121 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2158
2265
|
get tickledKey() {
|
|
2159
2266
|
return this.tickled.tickledId;
|
|
2160
2267
|
}
|
|
2268
|
+
/**
|
|
2269
|
+
* Snapshot selection, collapsed groups, keyboard cursor and scroll as plain
|
|
2270
|
+
* JSON, keyed by item / group key. Includes pending state for keys not
|
|
2271
|
+
* currently present unless `includePending: false`.
|
|
2272
|
+
*/
|
|
2273
|
+
getViewState(options) {
|
|
2274
|
+
const withPending = options?.includePending ?? true;
|
|
2275
|
+
const tickled = this.tickled.tickledId;
|
|
2276
|
+
const focus = this.tickled.inputMode === `keyboard` && tickled !== void 0 && !isSyntheticKey(tickled) ? tickled : void 0;
|
|
2277
|
+
return this.#viewState.shape({
|
|
2278
|
+
selected: keysToArray(this.selectedKeys, withPending ? [...this.selection.pendingKeys].filter((k) => !isSyntheticKey(k)) : []),
|
|
2279
|
+
expanded: expandedRecord(this._collapsedGroups, this.#pendingExpanded, withPending),
|
|
2280
|
+
focus: focus ?? (withPending ? this.#pendingFocus : void 0),
|
|
2281
|
+
scroll: (withPending ? this.#viewState.pendingScroll : void 0) ?? this.#captureScroll()
|
|
2282
|
+
}, options);
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Apply saved view state. Fields present replace the current ones; keys
|
|
2286
|
+
* (items or groups) that aren't present yet are applied as they appear.
|
|
2287
|
+
* Restored selection fires `list-select`.
|
|
2288
|
+
*/
|
|
2289
|
+
setViewState(state) {
|
|
2290
|
+
if (!this.#viewState.accepts(state)) return;
|
|
2291
|
+
this.#viewState.restoring(() => {
|
|
2292
|
+
if (state.expanded) {
|
|
2293
|
+
this.#pendingExpanded = capFlags(flagsFromRecord(state.expanded));
|
|
2294
|
+
if (this._collapsedGroups.size > 0) this._collapsedGroups = /* @__PURE__ */ new Set();
|
|
2295
|
+
}
|
|
2296
|
+
if (state.focus !== void 0) this.#pendingFocus = state.focus;
|
|
2297
|
+
if (state.scroll) this.#viewState.pendingScroll = state.scroll;
|
|
2298
|
+
this.#reconcileGroups();
|
|
2299
|
+
if (state.selected) this.selection.restoreKeys(state.selected, this.#resolveKey);
|
|
2300
|
+
this.#reconcileViewState();
|
|
2301
|
+
});
|
|
2302
|
+
this.requestUpdate();
|
|
2303
|
+
}
|
|
2304
|
+
/** Drop view state still waiting for its keys to appear. */
|
|
2305
|
+
clearPendingViewState() {
|
|
2306
|
+
this.selection.clearPending();
|
|
2307
|
+
this.#pendingExpanded = EMPTY_FLAGS;
|
|
2308
|
+
this.#pendingFocus = void 0;
|
|
2309
|
+
this.#viewState.pendingScroll = void 0;
|
|
2310
|
+
}
|
|
2311
|
+
#resolveKey;
|
|
2312
|
+
/** Move state for missing keys to pending; apply pending state for keys now present. */
|
|
2313
|
+
#reconcileViewState() {
|
|
2314
|
+
this.#viewState.restoring(() => {
|
|
2315
|
+
this.#reconcileGroups();
|
|
2316
|
+
this.selection.resolvePending(this.#resolveKey);
|
|
2317
|
+
const focus = this.#pendingFocus;
|
|
2318
|
+
if (focus !== void 0 && this.#source.indexOfKey(focus) >= 0) {
|
|
2319
|
+
this.tickled.setInputMode(`keyboard`);
|
|
2320
|
+
this.tickled.focus(focus);
|
|
2321
|
+
this.#pendingFocus = void 0;
|
|
2322
|
+
}
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
#reconcileGroups() {
|
|
2326
|
+
if (this._collapsedGroups.size === 0 && this.#pendingExpanded.size === 0) return;
|
|
2327
|
+
const present = this.groupBy === null ? /* @__PURE__ */ new Set() : new Set(this.#source.groups().map((g) => g.key));
|
|
2328
|
+
const r = reconcileGroups(this._collapsedGroups, this.#pendingExpanded, (key) => present.has(key));
|
|
2329
|
+
if (!r.changed) return;
|
|
2330
|
+
this.#pendingExpanded = r.pending;
|
|
2331
|
+
if (r.collapsed !== this._collapsedGroups) {
|
|
2332
|
+
this._collapsedGroups = r.collapsed;
|
|
2333
|
+
this.#applyGroupingToSource();
|
|
2334
|
+
this.#dropUnreachableCursor();
|
|
2335
|
+
this.#viewState.notify(`data`);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
/** Items left the data: deselect them silently, keeping their keys pending. */
|
|
2339
|
+
#demoteGone(keys) {
|
|
2340
|
+
const synthetic = keys.filter(isSyntheticKey);
|
|
2341
|
+
if (this.selection.demote(keys.filter((k) => !isSyntheticKey(k)))) {
|
|
2342
|
+
this._syncItemStates();
|
|
2343
|
+
this.#viewState.notify(`data`);
|
|
2344
|
+
}
|
|
2345
|
+
if (synthetic.length > 0) this.selection.removeKeys(synthetic, (k) => k);
|
|
2346
|
+
}
|
|
2347
|
+
/** The cursor's item left the data: clear it, remembering a keyboard cursor. */
|
|
2348
|
+
#demoteFocus(key) {
|
|
2349
|
+
if (this.tickled.inputMode === `keyboard` && !isSyntheticKey(key)) this.#pendingFocus ??= key;
|
|
2350
|
+
this.tickled.focus(void 0);
|
|
2351
|
+
}
|
|
2352
|
+
#captureScroll() {
|
|
2353
|
+
const vp = this.#viewportRef.value;
|
|
2354
|
+
if (!vp) return void 0;
|
|
2355
|
+
const horizontal = this.orientation === `row`;
|
|
2356
|
+
const main = horizontal ? vp.scrollLeft : vp.scrollTop;
|
|
2357
|
+
const cross = horizontal ? vp.scrollTop : vp.scrollLeft;
|
|
2358
|
+
const keys = this.#source.keys();
|
|
2359
|
+
return captureGridScroll(this.#layout(), main, cross, (i) => {
|
|
2360
|
+
const key = keys[i];
|
|
2361
|
+
return key === void 0 || isSyntheticKey(key) ? void 0 : key;
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
/** Returns `true` once the saved position is fully reached. */
|
|
2365
|
+
#applyScroll(scroll) {
|
|
2366
|
+
const vp = this.#viewportRef.value;
|
|
2367
|
+
const horizontal = this.orientation === `row`;
|
|
2368
|
+
const extent = horizontal ? this._viewportWidth : this._viewportHeight;
|
|
2369
|
+
if (!vp || extent <= 0) return false;
|
|
2370
|
+
const target = gridScrollTarget(scroll, this.#layout(), (key) => this.#source.indexOfKey(key), extent);
|
|
2371
|
+
if (!target) return false;
|
|
2372
|
+
if (horizontal) {
|
|
2373
|
+
if (Math.abs(vp.scrollLeft - target.offset) > .5) this.#restoredScrollOffset = target.offset;
|
|
2374
|
+
vp.scrollLeft = target.offset;
|
|
2375
|
+
this._scrollLeft = target.offset;
|
|
2376
|
+
} else {
|
|
2377
|
+
if (Math.abs(vp.scrollTop - target.offset) > .5) this.#restoredScrollOffset = target.offset;
|
|
2378
|
+
vp.scrollTop = target.offset;
|
|
2379
|
+
this._scrollTop = target.offset;
|
|
2380
|
+
}
|
|
2381
|
+
return target.reached;
|
|
2382
|
+
}
|
|
2161
2383
|
/** Element mode: the light-DOM element carrying `key`, or `undefined`. */
|
|
2162
2384
|
getItem(key) {
|
|
2163
2385
|
return this.#source.resolveKeyToElement(key);
|
|
@@ -2296,6 +2518,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2296
2518
|
this.setAttribute(`data-input-mode`, this.tickled.inputMode);
|
|
2297
2519
|
}
|
|
2298
2520
|
#emitSelect(selected, previous) {
|
|
2521
|
+
this.#viewState.notify();
|
|
2299
2522
|
const selectedEls = /* @__PURE__ */ new Set();
|
|
2300
2523
|
const previousEls = /* @__PURE__ */ new Set();
|
|
2301
2524
|
for (const key of selected) {
|
|
@@ -2592,35 +2815,13 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2592
2815
|
#onWheel;
|
|
2593
2816
|
/**
|
|
2594
2817
|
* Cell `pointerenter` funnel. While a scroll gesture moves cells beneath a
|
|
2595
|
-
* stationary pointer (`
|
|
2596
|
-
*
|
|
2818
|
+
* stationary pointer (see `scrollHoverTickle`), the freshly virtualised
|
|
2819
|
+
* cell's enter event is ignored so the cursor holds still.
|
|
2597
2820
|
*/
|
|
2598
2821
|
#handleCellPointerEnter(key) {
|
|
2599
|
-
if (this.#
|
|
2822
|
+
if (this.#scrollHover.suppressed()) return;
|
|
2600
2823
|
this.tickled.handlePointerEnter(key);
|
|
2601
2824
|
}
|
|
2602
|
-
/** Open the scroll-hover suppression window (no-op unless opted in and not already open). */
|
|
2603
|
-
#markScrollHoverSuppress() {
|
|
2604
|
-
if (!this.disableScrollHoverTickle || this.#scrollHoverSuppress) return;
|
|
2605
|
-
this.#scrollHoverSuppress = true;
|
|
2606
|
-
this.toggleAttribute(`data-scroll-hover-suppress`, true);
|
|
2607
|
-
}
|
|
2608
|
-
#endScrollHoverSuppress() {
|
|
2609
|
-
if (!this.#scrollHoverSuppress) return;
|
|
2610
|
-
this.#scrollHoverSuppress = false;
|
|
2611
|
-
this.toggleAttribute(`data-scroll-hover-suppress`, false);
|
|
2612
|
-
}
|
|
2613
|
-
/**
|
|
2614
|
-
* A real pointer movement ends scroll-hover suppression. The first
|
|
2615
|
-
* `pointerenter` on a real move is swallowed by design (it can be
|
|
2616
|
-
* indistinguishable from a virtualisation enter), so the cell already under
|
|
2617
|
-
* the pointer is re-tickled here — otherwise the cursor would lag one cell
|
|
2618
|
-
* boundary behind the pointer. The very first tracked `pointermove` only
|
|
2619
|
-
* establishes the baseline; it cannot know the pointer moved.
|
|
2620
|
-
*/
|
|
2621
|
-
#onHostPointerMove;
|
|
2622
|
-
/** The pointer leaving the list ends suppression, so re-entry hovers normally. */
|
|
2623
|
-
#onHostPointerLeave;
|
|
2624
2825
|
#cancelScrollRaf() {
|
|
2625
2826
|
if (this.#scrollRaf !== 0) {
|
|
2626
2827
|
cancelAnimationFrame(this.#scrollRaf);
|
|
@@ -2718,9 +2919,9 @@ __decorate([property({
|
|
|
2718
2919
|
})], GridListElement.prototype, "disableHoverTickle", void 0);
|
|
2719
2920
|
__decorate([property({
|
|
2720
2921
|
type: Boolean,
|
|
2721
|
-
attribute: `
|
|
2922
|
+
attribute: `scroll-hover-tickle`,
|
|
2722
2923
|
reflect: true
|
|
2723
|
-
})], GridListElement.prototype, "
|
|
2924
|
+
})], GridListElement.prototype, "scrollHoverTickle", void 0);
|
|
2724
2925
|
__decorate([property({
|
|
2725
2926
|
type: Number,
|
|
2726
2927
|
attribute: `item-size`
|