@ixfx/components 0.15.5 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/index.d.ts +986 -49
- package/bundle/index.js +765 -729
- package/dist/ac-token.js +1 -1
- package/dist/crumb-navigation-BbqfmkYs.d.ts +147 -0
- package/dist/crumb-navigation-YnFVFqrx.js +1290 -0
- package/dist/crumbs.d.ts +2 -115
- package/dist/crumbs.js +1 -1179
- package/dist/{data-grid-C1sZX6n9.js → data-grid-Cqxuwgbb.js} +615 -210
- package/dist/{data-grid-D7qzmMMp.d.ts → data-grid-Csh5EJW9.d.ts} +55 -4
- package/dist/data-grid.d.ts +3 -3
- package/dist/data-grid.js +1 -1
- package/dist/{detail-list-q8bs67BM.js → detail-list-Bo5ze3SN.js} +234 -9
- package/dist/{detail-list-CKIKlszv.d.ts → detail-list-DSZ1DDor.d.ts} +48 -4
- package/dist/detail-list.d.ts +4 -4
- package/dist/detail-list.js +1 -1
- package/dist/{flat-list-DXMyDiSH.d.ts → flat-list-CT9kX3TQ.d.ts} +27 -4
- package/dist/flat-list.d.ts +2 -2
- package/dist/flat-list.js +123 -4
- package/dist/{grid-list-y6dWJYu3.js → grid-list-D0dJNVEF.js} +278 -76
- package/dist/{grid-list-0e7snW6t.d.ts → grid-list-DDLrWRxm.d.ts} +128 -22
- package/dist/grid-list.d.ts +3 -3
- package/dist/grid-list.js +1 -1
- package/dist/{incr-search-JIhwB49y.js → incr-search-CgbL-OlS.js} +140 -10
- package/dist/incr-search.d.ts +1 -1
- package/dist/incr-search.js +1 -1
- package/dist/index-9ttcdwlL.d.ts +71 -0
- package/dist/index-Bnm-kKGC.d.ts +475 -0
- package/dist/{index-KN2MW_xD.d.ts → index-CUErGhCg.d.ts} +31 -4
- package/dist/{index-DEVyVIn8.d.ts → index-D-lZNlHa.d.ts} +52 -6
- package/dist/index.d.ts +341 -21
- package/dist/index.js +2286 -884
- package/dist/{list-native-drag-controller-O2Nruqpi.d.ts → list-native-drag-controller-D_UrXipS.d.ts} +1 -1
- package/dist/{list-selection-controller-BeE70lbB.js → list-selection-controller-5L0Nj9tR.js} +87 -2
- package/dist/miller-DUqiUAWQ.js +2230 -0
- package/dist/miller.d.ts +4 -437
- package/dist/miller.js +1 -1815
- package/dist/narrowed-text.js +1 -1
- package/dist/pending-BGidHppL.js +91 -0
- package/dist/scroll-hover-guard-Cjw6179Q.js +35 -0
- package/dist/{split-layout-JK3xWVX2.d.ts → split-layout-CophkNeU.d.ts} +41 -3
- package/dist/{split-layout-Deoxy47d.js → split-layout-DTY4PJRp.js} +251 -19
- package/dist/split-layout.d.ts +1 -1
- package/dist/split-layout.js +1 -1
- package/dist/sunburst-tree.d.ts +2 -2
- package/dist/sunburst-tree.js +1 -1
- package/dist/{sunbursttree-list-yXwS-uEI.js → sunbursttree-list-B_M0YTEA.js} +299 -80
- package/dist/{sunbursttree-list--Z0uyh8O.d.ts → sunbursttree-list-DePuFYlU.d.ts} +40 -3
- package/dist/tree-CftHwtEb.js +146 -0
- package/dist/{tree-D8VxvR9u.js → tree-DAlgvaHB.js} +276 -48
- package/dist/{tree-component-C6UHO-w9.d.ts → tree-component-DgfkxmVY.d.ts} +19 -8
- package/dist/tree.d.ts +3 -3
- package/dist/tree.js +1 -1
- package/dist/types-LU1u0CB8.d.ts +87 -0
- package/dist/vertical-list-DyKPKGxK.js +1993 -0
- package/dist/{vertical-list-D8MksoTp.d.ts → vertical-list-SAx56U-l.d.ts} +31 -3
- package/dist/vertical-list.d.ts +3 -3
- package/dist/vertical-list.js +1 -1789
- package/dist/view-state-controller-BdyXWUUM.js +305 -0
- package/docs-user/README.md +2 -0
- package/docs-user/crumbs.md +27 -0
- package/docs-user/data-grid.md +79 -5
- package/docs-user/detail-list.md +69 -0
- package/docs-user/dock.md +63 -12
- package/docs-user/flat-list.md +27 -0
- package/docs-user/grid-list.md +69 -13
- package/docs-user/hyperbolic-tree.md +31 -4
- package/docs-user/icicle-tree.md +36 -0
- package/docs-user/index.json +12 -2
- package/docs-user/llms.txt +2 -0
- package/docs-user/miller.md +52 -21
- package/docs-user/multicol-list.md +50 -8
- package/docs-user/split-layout.md +45 -17
- package/docs-user/sunburst-tree.md +32 -2
- package/docs-user/tree.md +45 -6
- package/docs-user/treemap.md +32 -0
- package/docs-user/twosplit-layout.md +22 -0
- package/docs-user/user-catalog.md +1 -1
- package/docs-user/vertical-list.md +55 -7
- package/llms.txt +2 -0
- package/package.json +2 -2
|
@@ -2,11 +2,14 @@ import { t as TickledController } from "./tickled-controller-D-HZymaZ.js";
|
|
|
2
2
|
import { n as tickledStyles, t as tickledItemStyles } from "./tickled-styles-BrAHEj1q.js";
|
|
3
3
|
import { t as themeFallbacks } from "./fallbacks-DQ_5OsmR.js";
|
|
4
4
|
import { n as safeCustomElement, t as __decorate } from "./decorate-VRK8rslU.js";
|
|
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-
|
|
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-CgbL-OlS.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
|
|
@@ -1907,6 +2007,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1907
2007
|
}
|
|
1908
2008
|
updated(changed) {
|
|
1909
2009
|
super.updated(changed);
|
|
2010
|
+
if (this.search?.isOpen) this.search.repaintHighlights();
|
|
1910
2011
|
const pending = this.#mutationObserver?.takeRecords();
|
|
1911
2012
|
const pendingAttrs = this.#itemAttrObserver?.takeRecords();
|
|
1912
2013
|
if (pending !== void 0 && pending.length > 0) this.#onChildrenChanged(pending);
|
|
@@ -1916,7 +2017,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1916
2017
|
this.selection.clearAnchor();
|
|
1917
2018
|
this.#syncAriaMultiselectable();
|
|
1918
2019
|
}
|
|
1919
|
-
if (changed.has(`
|
|
2020
|
+
if (changed.has(`scrollHoverTickle`) && this.scrollHoverTickle) this.#scrollHover.end();
|
|
1920
2021
|
if (changed.has(`itemsDraggable`)) this.#nativeDrag.refreshDraggable();
|
|
1921
2022
|
if (changed.has(`orientation`)) {
|
|
1922
2023
|
const vp = this.#viewportRef.value;
|
|
@@ -1936,6 +2037,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
1936
2037
|
if (Math.abs(vp.scrollLeft - this._scrollLeft) > .5) vp.scrollLeft = this._scrollLeft;
|
|
1937
2038
|
}
|
|
1938
2039
|
this.#applyZoomAnchor();
|
|
2040
|
+
this.#viewState.applyPendingScroll((scroll) => this.#applyScroll(scroll));
|
|
1939
2041
|
this.#activateTickledPan();
|
|
1940
2042
|
if (this.#groupByChanged) {
|
|
1941
2043
|
this.#groupByChanged = false;
|
|
@@ -2099,8 +2201,9 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2099
2201
|
}
|
|
2100
2202
|
/**
|
|
2101
2203
|
* 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
|
|
2204
|
+
* that is still present; keys that vanish are deselected silently and held
|
|
2205
|
+
* as pending view state, so they come back if the key reappears. A no-op in
|
|
2206
|
+
* element mode.
|
|
2104
2207
|
*/
|
|
2105
2208
|
setItems(items) {
|
|
2106
2209
|
if (this.itemAdapter) this.items = items;
|
|
@@ -2128,8 +2231,9 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2128
2231
|
this.items = next;
|
|
2129
2232
|
}
|
|
2130
2233
|
/**
|
|
2131
|
-
* Data mode: remove the items carrying these keys. Removed keys
|
|
2132
|
-
*
|
|
2234
|
+
* Data mode: remove the items carrying these keys. Removed keys leave the
|
|
2235
|
+
* selection and cursor silently (via the `items` change) and are held as
|
|
2236
|
+
* pending view state. A no-op in element mode.
|
|
2133
2237
|
*/
|
|
2134
2238
|
removeItems(keys) {
|
|
2135
2239
|
if (!this.itemAdapter) return;
|
|
@@ -2139,18 +2243,22 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2139
2243
|
}
|
|
2140
2244
|
/**
|
|
2141
2245
|
* Data mode: drop every item. Selection and cursor are reset and one
|
|
2142
|
-
* `list-select` with empty sets is emitted
|
|
2246
|
+
* `list-select` with empty sets is emitted; their keys are held as pending
|
|
2247
|
+
* view state, so re-adding the same keys restores them. A no-op in element
|
|
2248
|
+
* mode.
|
|
2143
2249
|
*/
|
|
2144
2250
|
clearItems() {
|
|
2145
2251
|
if (!this.itemAdapter) return;
|
|
2146
2252
|
this.selection.reset();
|
|
2147
|
-
this.tickled.
|
|
2253
|
+
const tickled = this.tickled.tickledId;
|
|
2254
|
+
if (tickled !== void 0) this.#demoteFocus(tickled);
|
|
2148
2255
|
this.items = [];
|
|
2149
2256
|
this.#emitSelect(/* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
|
|
2150
2257
|
}
|
|
2151
2258
|
/** Move the cursor to the item with this key and scroll it into view. */
|
|
2152
2259
|
focusKey(key) {
|
|
2153
2260
|
if (this.#source.indexOfKey(key) < 0) return;
|
|
2261
|
+
this.#pendingFocus = void 0;
|
|
2154
2262
|
this.tickled.focus(key);
|
|
2155
2263
|
this.#scrollItemIntoView(key);
|
|
2156
2264
|
}
|
|
@@ -2158,6 +2266,121 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2158
2266
|
get tickledKey() {
|
|
2159
2267
|
return this.tickled.tickledId;
|
|
2160
2268
|
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Snapshot selection, collapsed groups, keyboard cursor and scroll as plain
|
|
2271
|
+
* JSON, keyed by item / group key. Includes pending state for keys not
|
|
2272
|
+
* currently present unless `includePending: false`.
|
|
2273
|
+
*/
|
|
2274
|
+
getViewState(options) {
|
|
2275
|
+
const withPending = options?.includePending ?? true;
|
|
2276
|
+
const tickled = this.tickled.tickledId;
|
|
2277
|
+
const focus = this.tickled.inputMode === `keyboard` && tickled !== void 0 && !isSyntheticKey(tickled) ? tickled : void 0;
|
|
2278
|
+
return this.#viewState.shape({
|
|
2279
|
+
selected: keysToArray(this.selectedKeys, withPending ? [...this.selection.pendingKeys].filter((k) => !isSyntheticKey(k)) : []),
|
|
2280
|
+
expanded: expandedRecord(this._collapsedGroups, this.#pendingExpanded, withPending),
|
|
2281
|
+
focus: focus ?? (withPending ? this.#pendingFocus : void 0),
|
|
2282
|
+
scroll: (withPending ? this.#viewState.pendingScroll : void 0) ?? this.#captureScroll()
|
|
2283
|
+
}, options);
|
|
2284
|
+
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Apply saved view state. Fields present replace the current ones; keys
|
|
2287
|
+
* (items or groups) that aren't present yet are applied as they appear.
|
|
2288
|
+
* Restored selection fires `list-select`.
|
|
2289
|
+
*/
|
|
2290
|
+
setViewState(state) {
|
|
2291
|
+
if (!this.#viewState.accepts(state)) return;
|
|
2292
|
+
this.#viewState.restoring(() => {
|
|
2293
|
+
if (state.expanded) {
|
|
2294
|
+
this.#pendingExpanded = capFlags(flagsFromRecord(state.expanded));
|
|
2295
|
+
if (this._collapsedGroups.size > 0) this._collapsedGroups = /* @__PURE__ */ new Set();
|
|
2296
|
+
}
|
|
2297
|
+
if (state.focus !== void 0) this.#pendingFocus = state.focus;
|
|
2298
|
+
if (state.scroll) this.#viewState.pendingScroll = state.scroll;
|
|
2299
|
+
this.#reconcileGroups();
|
|
2300
|
+
if (state.selected) this.selection.restoreKeys(state.selected, this.#resolveKey);
|
|
2301
|
+
this.#reconcileViewState();
|
|
2302
|
+
});
|
|
2303
|
+
this.requestUpdate();
|
|
2304
|
+
}
|
|
2305
|
+
/** Drop view state still waiting for its keys to appear. */
|
|
2306
|
+
clearPendingViewState() {
|
|
2307
|
+
this.selection.clearPending();
|
|
2308
|
+
this.#pendingExpanded = EMPTY_FLAGS;
|
|
2309
|
+
this.#pendingFocus = void 0;
|
|
2310
|
+
this.#viewState.pendingScroll = void 0;
|
|
2311
|
+
}
|
|
2312
|
+
#resolveKey;
|
|
2313
|
+
/** Move state for missing keys to pending; apply pending state for keys now present. */
|
|
2314
|
+
#reconcileViewState() {
|
|
2315
|
+
this.#viewState.restoring(() => {
|
|
2316
|
+
this.#reconcileGroups();
|
|
2317
|
+
this.selection.resolvePending(this.#resolveKey);
|
|
2318
|
+
const focus = this.#pendingFocus;
|
|
2319
|
+
if (focus !== void 0 && this.#source.indexOfKey(focus) >= 0) {
|
|
2320
|
+
this.tickled.setInputMode(`keyboard`);
|
|
2321
|
+
this.tickled.focus(focus);
|
|
2322
|
+
this.#pendingFocus = void 0;
|
|
2323
|
+
}
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
#reconcileGroups() {
|
|
2327
|
+
if (this._collapsedGroups.size === 0 && this.#pendingExpanded.size === 0) return;
|
|
2328
|
+
const present = this.groupBy === null ? /* @__PURE__ */ new Set() : new Set(this.#source.groups().map((g) => g.key));
|
|
2329
|
+
const r = reconcileGroups(this._collapsedGroups, this.#pendingExpanded, (key) => present.has(key));
|
|
2330
|
+
if (!r.changed) return;
|
|
2331
|
+
this.#pendingExpanded = r.pending;
|
|
2332
|
+
if (r.collapsed !== this._collapsedGroups) {
|
|
2333
|
+
this._collapsedGroups = r.collapsed;
|
|
2334
|
+
this.#applyGroupingToSource();
|
|
2335
|
+
this.#dropUnreachableCursor();
|
|
2336
|
+
this.#viewState.notify(`data`);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
/** Items left the data: deselect them silently, keeping their keys pending. */
|
|
2340
|
+
#demoteGone(keys) {
|
|
2341
|
+
const synthetic = keys.filter(isSyntheticKey);
|
|
2342
|
+
if (this.selection.demote(keys.filter((k) => !isSyntheticKey(k)))) {
|
|
2343
|
+
this._syncItemStates();
|
|
2344
|
+
this.#viewState.notify(`data`);
|
|
2345
|
+
}
|
|
2346
|
+
if (synthetic.length > 0) this.selection.removeKeys(synthetic, (k) => k);
|
|
2347
|
+
}
|
|
2348
|
+
/** The cursor's item left the data: clear it, remembering a keyboard cursor. */
|
|
2349
|
+
#demoteFocus(key) {
|
|
2350
|
+
if (this.tickled.inputMode === `keyboard` && !isSyntheticKey(key)) this.#pendingFocus ??= key;
|
|
2351
|
+
this.tickled.focus(void 0);
|
|
2352
|
+
}
|
|
2353
|
+
#captureScroll() {
|
|
2354
|
+
const vp = this.#viewportRef.value;
|
|
2355
|
+
if (!vp) return void 0;
|
|
2356
|
+
const horizontal = this.orientation === `row`;
|
|
2357
|
+
const main = horizontal ? vp.scrollLeft : vp.scrollTop;
|
|
2358
|
+
const cross = horizontal ? vp.scrollTop : vp.scrollLeft;
|
|
2359
|
+
const keys = this.#source.keys();
|
|
2360
|
+
return captureGridScroll(this.#layout(), main, cross, (i) => {
|
|
2361
|
+
const key = keys[i];
|
|
2362
|
+
return key === void 0 || isSyntheticKey(key) ? void 0 : key;
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
/** Returns `true` once the saved position is fully reached. */
|
|
2366
|
+
#applyScroll(scroll) {
|
|
2367
|
+
const vp = this.#viewportRef.value;
|
|
2368
|
+
const horizontal = this.orientation === `row`;
|
|
2369
|
+
const extent = horizontal ? this._viewportWidth : this._viewportHeight;
|
|
2370
|
+
if (!vp || extent <= 0) return false;
|
|
2371
|
+
const target = gridScrollTarget(scroll, this.#layout(), (key) => this.#source.indexOfKey(key), extent);
|
|
2372
|
+
if (!target) return false;
|
|
2373
|
+
if (horizontal) {
|
|
2374
|
+
if (Math.abs(vp.scrollLeft - target.offset) > .5) this.#restoredScrollOffset = target.offset;
|
|
2375
|
+
vp.scrollLeft = target.offset;
|
|
2376
|
+
this._scrollLeft = target.offset;
|
|
2377
|
+
} else {
|
|
2378
|
+
if (Math.abs(vp.scrollTop - target.offset) > .5) this.#restoredScrollOffset = target.offset;
|
|
2379
|
+
vp.scrollTop = target.offset;
|
|
2380
|
+
this._scrollTop = target.offset;
|
|
2381
|
+
}
|
|
2382
|
+
return target.reached;
|
|
2383
|
+
}
|
|
2161
2384
|
/** Element mode: the light-DOM element carrying `key`, or `undefined`. */
|
|
2162
2385
|
getItem(key) {
|
|
2163
2386
|
return this.#source.resolveKeyToElement(key);
|
|
@@ -2296,6 +2519,7 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2296
2519
|
this.setAttribute(`data-input-mode`, this.tickled.inputMode);
|
|
2297
2520
|
}
|
|
2298
2521
|
#emitSelect(selected, previous) {
|
|
2522
|
+
this.#viewState.notify();
|
|
2299
2523
|
const selectedEls = /* @__PURE__ */ new Set();
|
|
2300
2524
|
const previousEls = /* @__PURE__ */ new Set();
|
|
2301
2525
|
for (const key of selected) {
|
|
@@ -2592,35 +2816,13 @@ let GridListElement = class GridListElement extends LitElement {
|
|
|
2592
2816
|
#onWheel;
|
|
2593
2817
|
/**
|
|
2594
2818
|
* Cell `pointerenter` funnel. While a scroll gesture moves cells beneath a
|
|
2595
|
-
* stationary pointer (`
|
|
2596
|
-
*
|
|
2819
|
+
* stationary pointer (see `scrollHoverTickle`), the freshly virtualised
|
|
2820
|
+
* cell's enter event is ignored so the cursor holds still.
|
|
2597
2821
|
*/
|
|
2598
2822
|
#handleCellPointerEnter(key) {
|
|
2599
|
-
if (this.#
|
|
2823
|
+
if (this.#scrollHover.suppressed()) return;
|
|
2600
2824
|
this.tickled.handlePointerEnter(key);
|
|
2601
2825
|
}
|
|
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
2826
|
#cancelScrollRaf() {
|
|
2625
2827
|
if (this.#scrollRaf !== 0) {
|
|
2626
2828
|
cancelAnimationFrame(this.#scrollRaf);
|
|
@@ -2718,9 +2920,9 @@ __decorate([property({
|
|
|
2718
2920
|
})], GridListElement.prototype, "disableHoverTickle", void 0);
|
|
2719
2921
|
__decorate([property({
|
|
2720
2922
|
type: Boolean,
|
|
2721
|
-
attribute: `
|
|
2923
|
+
attribute: `scroll-hover-tickle`,
|
|
2722
2924
|
reflect: true
|
|
2723
|
-
})], GridListElement.prototype, "
|
|
2925
|
+
})], GridListElement.prototype, "scrollHoverTickle", void 0);
|
|
2724
2926
|
__decorate([property({
|
|
2725
2927
|
type: Number,
|
|
2726
2928
|
attribute: `item-size`
|