@ixfx/components 0.7.2 → 0.7.4
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 +805 -137
- package/bundle/index.d.ts.map +1 -1
- package/bundle/index.js +2168 -42
- package/bundle/index.js.map +1 -1
- package/dist/ac-text.js +1 -1
- package/dist/ac-token.js +1 -1
- package/dist/crumbs.js +3 -3
- package/dist/{data-grid-Bh4YrQGK.d.ts → data-grid-BddUjAti.d.ts} +33 -1
- package/dist/data-grid-BddUjAti.d.ts.map +1 -0
- package/dist/data-grid.d.ts +1 -1
- package/dist/data-grid.js +180 -7
- package/dist/data-grid.js.map +1 -1
- package/dist/detail-list-Br0WAnry.d.ts +167 -0
- package/dist/detail-list-Br0WAnry.d.ts.map +1 -0
- package/dist/detail-list.d.ts +3 -0
- package/dist/detail-list.js +1115 -0
- package/dist/detail-list.js.map +1 -0
- package/dist/grid-list-B_orPsot.d.ts +304 -0
- package/dist/grid-list-B_orPsot.d.ts.map +1 -0
- package/dist/grid-list.d.ts +3 -0
- package/dist/grid-list.js +933 -0
- package/dist/grid-list.js.map +1 -0
- package/dist/{grouped-item-lister-DnWB-eFT.d.ts → grouped-item-lister-fdZHVxFM.d.ts} +2 -2
- package/dist/{grouped-item-lister-DnWB-eFT.d.ts.map → grouped-item-lister-fdZHVxFM.d.ts.map} +1 -1
- package/dist/grouped-item-lister.d.ts +1 -1
- package/dist/index.d.ts +267 -124
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +469 -979
- package/dist/index.js.map +1 -1
- package/dist/interaction-Cr13azbM.js +3 -0
- package/dist/list-item-animation-DuiwgN3l.js +112 -0
- package/dist/list-item-animation-DuiwgN3l.js.map +1 -0
- package/dist/list-selection-controller-CUo-Hhcm.js +338 -0
- package/dist/list-selection-controller-CUo-Hhcm.js.map +1 -0
- package/dist/{list-selection-types-DSuRNWpx.d.ts → list-selection-types-B5ioL-dG.d.ts} +6 -1
- package/dist/list-selection-types-B5ioL-dG.d.ts.map +1 -0
- package/dist/{menu-CXDFJxNm.js → menu-Bg90Qr0B.js} +7 -7
- package/dist/menu-Bg90Qr0B.js.map +1 -0
- package/dist/{menu-item-D2S4i8u1.js → menu-item-Bx-Fs7jq.js} +3 -3
- package/dist/{menu-item-D2S4i8u1.js.map → menu-item-Bx-Fs7jq.js.map} +1 -1
- package/dist/menu.js +2 -2
- package/dist/miller.d.ts +1 -1
- package/dist/miller.js +2 -2
- package/dist/narrowed-text.js +2 -2
- package/dist/plots.js +1 -1
- package/dist/select-horiz.js +2 -2
- package/dist/{tickled-controller-CRzdIEjJ.d.ts → tickled-controller-Bn3YdQsM.d.ts} +13 -2
- package/dist/{tickled-controller-CRzdIEjJ.d.ts.map → tickled-controller-Bn3YdQsM.d.ts.map} +1 -1
- package/dist/{tickled-controller-h9GmJ_bU.js → tickled-controller-C66DgJHl.js} +16 -1
- package/dist/tickled-controller-C66DgJHl.js.map +1 -0
- package/dist/{tree-Ch4W8qXQ.js → tree-B74zdI60.js} +116 -17
- package/dist/tree-B74zdI60.js.map +1 -0
- package/dist/tree.d.ts +6 -3
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +1 -1
- package/dist/vertical-list-CQSEVeOT.d.ts +140 -0
- package/dist/vertical-list-CQSEVeOT.d.ts.map +1 -0
- package/dist/vertical-list.d.ts +3 -121
- package/dist/vertical-list.js +710 -1
- package/dist/vertical-list.js.map +1 -0
- package/dist/{xy-axis-DWizs0bt.js → xy-axis-VrX8dmvS.js} +2 -2
- package/dist/{xy-axis-DWizs0bt.js.map → xy-axis-VrX8dmvS.js.map} +1 -1
- package/docs-user/README.md +2 -0
- package/docs-user/data-grid.md +51 -2
- package/docs-user/detail-list.md +80 -15
- package/docs-user/grid-list.md +274 -0
- package/docs-user/index.json +11 -2
- package/docs-user/infra-tickled.md +163 -0
- package/docs-user/llms.txt +2 -0
- package/docs-user/tree.md +13 -0
- package/docs-user/vertical-list.md +51 -1
- package/llms.txt +2 -0
- package/package.json +17 -2
- package/dist/data-grid-Bh4YrQGK.d.ts.map +0 -1
- package/dist/interaction-D5XdKzbR.js +0 -2
- package/dist/list-selection-types-DSuRNWpx.d.ts.map +0 -1
- package/dist/menu-CXDFJxNm.js.map +0 -1
- package/dist/tickled-controller-h9GmJ_bU.js.map +0 -1
- package/dist/tree-Ch4W8qXQ.js.map +0 -1
- package/dist/vertical-list-ClnqTkF3.js +0 -814
- package/dist/vertical-list-ClnqTkF3.js.map +0 -1
- package/dist/vertical-list.d.ts.map +0 -1
package/bundle/index.js
CHANGED
|
@@ -8,8 +8,9 @@ import { createCommandRegistry } from "@clinth/ui-commands";
|
|
|
8
8
|
import { Trees } from "@ixfx/collections";
|
|
9
9
|
import { clamp, clamper, rangeStream, round, scaler } from "@ixfx/numbers";
|
|
10
10
|
import { repeat } from "lit/directives/repeat.js";
|
|
11
|
-
import {
|
|
11
|
+
import { createRef, ref } from "lit/directives/ref.js";
|
|
12
12
|
import { classMap } from "lit/directives/class-map.js";
|
|
13
|
+
import { Easings } from "@ixfx/modulation";
|
|
13
14
|
import "@clinth/vcanvas";
|
|
14
15
|
import { keyed } from "lit/directives/keyed.js";
|
|
15
16
|
//#region node_modules/.pnpm/@lit+reactive-element@2.1.2/node_modules/@lit/reactive-element/css-tag.js
|
|
@@ -752,6 +753,21 @@ var TickledController = class TickledController {
|
|
|
752
753
|
return this._tickledId;
|
|
753
754
|
}
|
|
754
755
|
/**
|
|
756
|
+
* The item currently under the cursor, or `undefined`. Alias of `tickledId`
|
|
757
|
+
* for parity with other controllers.
|
|
758
|
+
*/
|
|
759
|
+
get current() {
|
|
760
|
+
return this._tickledId;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Move the cursor programmatically (e.g. to reveal a match or re-seat it
|
|
764
|
+
* after the item set changed). Does not change the input mode. Alias of
|
|
765
|
+
* `setTickled`.
|
|
766
|
+
*/
|
|
767
|
+
focus(id) {
|
|
768
|
+
this.setTickled(id);
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
755
771
|
* Check if a specific item is tickled
|
|
756
772
|
*/
|
|
757
773
|
isTickled(id) {
|
|
@@ -3861,6 +3877,188 @@ __decorate([state()], RadioGroup.prototype, "_icons", void 0);
|
|
|
3861
3877
|
__decorate([query(`slot`)], RadioGroup.prototype, "_slot", void 0);
|
|
3862
3878
|
RadioGroup = __decorate([safeCustomElement(`ixfx-radio-group`)], RadioGroup);
|
|
3863
3879
|
//#endregion
|
|
3880
|
+
//#region src/interaction/keyed-list.ts
|
|
3881
|
+
/**
|
|
3882
|
+
* Helpers for key-based selection on the Element-flavoured list components
|
|
3883
|
+
* (`ixfx-vertical-list`, `ixfx-detail-list`, `ixfx-grid-list`).
|
|
3884
|
+
*
|
|
3885
|
+
* A "key" is a stable string the host puts on each item element — `data-key`
|
|
3886
|
+
* (preferred) or `data-value`. It survives the host rebuilding the item set
|
|
3887
|
+
* (re-fetch, re-sort, filter) where raw `Element` references do not.
|
|
3888
|
+
*/
|
|
3889
|
+
/**
|
|
3890
|
+
* The stable key for an item element: `data-key`, then `data-value`, else
|
|
3891
|
+
* `undefined`.
|
|
3892
|
+
*/
|
|
3893
|
+
function keyForElement(el) {
|
|
3894
|
+
return el.getAttribute(`data-key`) ?? el.getAttribute(`data-value`) ?? void 0;
|
|
3895
|
+
}
|
|
3896
|
+
/**
|
|
3897
|
+
* A lazily-built key → element index over a container. Rebuilt on first use
|
|
3898
|
+
* and after any child-list mutation under `root`; `invalidate()` forces a
|
|
3899
|
+
* rebuild on the next `get()`.
|
|
3900
|
+
*
|
|
3901
|
+
* ```ts
|
|
3902
|
+
* #keyIndex = new KeyedElementIndex(this);
|
|
3903
|
+
* // ...
|
|
3904
|
+
* const el = this.#keyIndex.get(`row-42`);
|
|
3905
|
+
* // on disconnect:
|
|
3906
|
+
* this.#keyIndex.dispose();
|
|
3907
|
+
* ```
|
|
3908
|
+
*/
|
|
3909
|
+
var KeyedElementIndex = class {
|
|
3910
|
+
#root;
|
|
3911
|
+
#selector;
|
|
3912
|
+
#map = /* @__PURE__ */ new Map();
|
|
3913
|
+
#dirty = true;
|
|
3914
|
+
#observer;
|
|
3915
|
+
/**
|
|
3916
|
+
* @param root Container whose descendants are indexed.
|
|
3917
|
+
* @param itemSelector Which descendants count as items (default: any `[data-key]`).
|
|
3918
|
+
*/
|
|
3919
|
+
constructor(root, itemSelector = `[data-key]`) {
|
|
3920
|
+
this.#root = root;
|
|
3921
|
+
this.#selector = itemSelector;
|
|
3922
|
+
this.#observer = new MutationObserver(() => {
|
|
3923
|
+
this.#dirty = true;
|
|
3924
|
+
});
|
|
3925
|
+
this.#observer.observe(root, {
|
|
3926
|
+
childList: true,
|
|
3927
|
+
subtree: true
|
|
3928
|
+
});
|
|
3929
|
+
}
|
|
3930
|
+
/** The element with this key, or `undefined`. First match wins on duplicates. */
|
|
3931
|
+
get(key) {
|
|
3932
|
+
if (this.#dirty) this.#rebuild();
|
|
3933
|
+
return this.#map.get(key);
|
|
3934
|
+
}
|
|
3935
|
+
/** Force a rebuild on the next `get()` — e.g. after a `data-key` attribute change. */
|
|
3936
|
+
invalidate() {
|
|
3937
|
+
this.#dirty = true;
|
|
3938
|
+
}
|
|
3939
|
+
/** Stop observing and drop the cache. */
|
|
3940
|
+
dispose() {
|
|
3941
|
+
this.#observer.disconnect();
|
|
3942
|
+
this.#map.clear();
|
|
3943
|
+
}
|
|
3944
|
+
#rebuild() {
|
|
3945
|
+
this.#map.clear();
|
|
3946
|
+
for (const el of this.#root.querySelectorAll(this.#selector)) {
|
|
3947
|
+
const key = keyForElement(el);
|
|
3948
|
+
if (key !== void 0 && !this.#map.has(key)) this.#map.set(key, el);
|
|
3949
|
+
}
|
|
3950
|
+
this.#dirty = false;
|
|
3951
|
+
}
|
|
3952
|
+
};
|
|
3953
|
+
//#endregion
|
|
3954
|
+
//#region src/interaction/list-item-animation.ts
|
|
3955
|
+
/**
|
|
3956
|
+
* Shared insertion / removal animation for the list & tree components.
|
|
3957
|
+
*
|
|
3958
|
+
* Two flavours, because the components split into two layout families:
|
|
3959
|
+
*
|
|
3960
|
+
* - **Flow layouts** (`ixfx-vertical-list`, `ixfx-detail-list`, `ixfx-tree-*`) —
|
|
3961
|
+
* rows sit in normal flow, so a new row can collapse in from zero height and a
|
|
3962
|
+
* leaving row can collapse back to zero, and the rows around it slide to make
|
|
3963
|
+
* or close the gap for free. Use {@link animateListItemIn} /
|
|
3964
|
+
* {@link animateListItemOut} (Web Animations API, exact measured sizes).
|
|
3965
|
+
*
|
|
3966
|
+
* - **Virtualised layouts** (`ixfx-grid-list`, `ixfx-data-grid`) — rows are
|
|
3967
|
+
* absolutely positioned (`transform` / `top`), so collapsing a row's box moves
|
|
3968
|
+
* nothing. Those components instead fade the entering / leaving row with the
|
|
3969
|
+
* {@link listItemFadeKeyframes} keyframes and slide the survivors by
|
|
3970
|
+
* transitioning their position for a short window after the change.
|
|
3971
|
+
*
|
|
3972
|
+
* Everything here is a no-op under `prefers-reduced-motion: reduce`.
|
|
3973
|
+
*/
|
|
3974
|
+
/** Default duration (ms) for both flavours; overridable per call / per component. */
|
|
3975
|
+
const LIST_ITEM_MOTION_MS = 200;
|
|
3976
|
+
function prefersReducedMotion() {
|
|
3977
|
+
return typeof window !== `undefined` && typeof window.matchMedia === `function` && window.matchMedia(`(prefers-reduced-motion: reduce)`).matches;
|
|
3978
|
+
}
|
|
3979
|
+
/**
|
|
3980
|
+
* Opacity keyframes for the virtualised components. Each one adds its own
|
|
3981
|
+
* one-line rule binding `.<row>.ixfx-item-entering` / `.ixfx-item-leaving` to
|
|
3982
|
+
* these, plus a `prefers-reduced-motion` guard on its own selectors.
|
|
3983
|
+
*/
|
|
3984
|
+
const listItemFadeKeyframes = i$3`
|
|
3985
|
+
@keyframes ixfx-list-item-in {
|
|
3986
|
+
from { opacity: 0; }
|
|
3987
|
+
to { opacity: 1; }
|
|
3988
|
+
}
|
|
3989
|
+
@keyframes ixfx-list-item-out {
|
|
3990
|
+
from { opacity: 1; }
|
|
3991
|
+
to { opacity: 0; }
|
|
3992
|
+
}
|
|
3993
|
+
`;
|
|
3994
|
+
function measure(el, axis) {
|
|
3995
|
+
const rect = el.getBoundingClientRect();
|
|
3996
|
+
return axis === `inline` ? rect.width : rect.height;
|
|
3997
|
+
}
|
|
3998
|
+
function resolveDuration(el, explicit) {
|
|
3999
|
+
if (explicit !== void 0) return explicit;
|
|
4000
|
+
const raw = getComputedStyle(el).getPropertyValue(`--ixfx-list-item-move-duration`).trim();
|
|
4001
|
+
if (raw) {
|
|
4002
|
+
const n = Number.parseFloat(raw);
|
|
4003
|
+
if (!Number.isNaN(n)) return raw.endsWith(`ms`) ? n : raw.endsWith(`s`) ? n * 1e3 : n;
|
|
4004
|
+
}
|
|
4005
|
+
return 200;
|
|
4006
|
+
}
|
|
4007
|
+
function collapsedFrame(axis) {
|
|
4008
|
+
const prop = axis === `inline` ? `width` : `height`;
|
|
4009
|
+
const minProp = axis === `inline` ? `minWidth` : `minHeight`;
|
|
4010
|
+
return {
|
|
4011
|
+
[prop]: `0px`,
|
|
4012
|
+
[minProp]: `0px`,
|
|
4013
|
+
opacity: 0,
|
|
4014
|
+
overflow: `hidden`
|
|
4015
|
+
};
|
|
4016
|
+
}
|
|
4017
|
+
function fullFrame(size, axis) {
|
|
4018
|
+
const prop = axis === `inline` ? `width` : `height`;
|
|
4019
|
+
const minProp = axis === `inline` ? `minWidth` : `minHeight`;
|
|
4020
|
+
return {
|
|
4021
|
+
[prop]: `${size}px`,
|
|
4022
|
+
[minProp]: `0px`,
|
|
4023
|
+
opacity: 1,
|
|
4024
|
+
overflow: `hidden`
|
|
4025
|
+
};
|
|
4026
|
+
}
|
|
4027
|
+
/**
|
|
4028
|
+
* Collapse + fade `el` in from nothing to its natural size. Call it just after
|
|
4029
|
+
* the element has been inserted (it must already be laid out at full size so it
|
|
4030
|
+
* can be measured). No-op under reduced motion.
|
|
4031
|
+
*/
|
|
4032
|
+
function animateListItemIn(el, options = {}) {
|
|
4033
|
+
if (prefersReducedMotion()) return void 0;
|
|
4034
|
+
const { axis = `block`, easing = `ease` } = options;
|
|
4035
|
+
const duration = resolveDuration(el, options.duration);
|
|
4036
|
+
const size = measure(el, axis);
|
|
4037
|
+
if (size <= 0) return void 0;
|
|
4038
|
+
return el.animate([collapsedFrame(axis), fullFrame(size, axis)], {
|
|
4039
|
+
duration,
|
|
4040
|
+
easing
|
|
4041
|
+
});
|
|
4042
|
+
}
|
|
4043
|
+
/**
|
|
4044
|
+
* Collapse + fade `el` out. Resolves once the animation finishes (or straight
|
|
4045
|
+
* away under reduced motion); the caller detaches the element after. The final
|
|
4046
|
+
* collapsed state is held (`fill: forwards`) so there's no flash back to full
|
|
4047
|
+
* size in the gap before removal.
|
|
4048
|
+
*/
|
|
4049
|
+
function animateListItemOut(el, options = {}) {
|
|
4050
|
+
if (prefersReducedMotion()) return Promise.resolve();
|
|
4051
|
+
const { axis = `block`, easing = `ease` } = options;
|
|
4052
|
+
const duration = resolveDuration(el, options.duration);
|
|
4053
|
+
const size = measure(el, axis);
|
|
4054
|
+
if (size <= 0) return Promise.resolve();
|
|
4055
|
+
return el.animate([fullFrame(size, axis), collapsedFrame(axis)], {
|
|
4056
|
+
duration,
|
|
4057
|
+
easing,
|
|
4058
|
+
fill: `forwards`
|
|
4059
|
+
}).finished.then(() => void 0, () => void 0);
|
|
4060
|
+
}
|
|
4061
|
+
//#endregion
|
|
3864
4062
|
//#region src/interaction/list-selection-controller.ts
|
|
3865
4063
|
/** Whether the interaction mode allows building a multi-item selection. */
|
|
3866
4064
|
function isMultiMode(mode) {
|
|
@@ -3901,6 +4099,20 @@ var ListSelectionController = class {
|
|
|
3901
4099
|
get selected() {
|
|
3902
4100
|
return this.#selected;
|
|
3903
4101
|
}
|
|
4102
|
+
/**
|
|
4103
|
+
* The current selection as stable string keys (read-only). Empty unless a
|
|
4104
|
+
* `getKey` was supplied in config. Items whose `getKey` returns `undefined`
|
|
4105
|
+
* are omitted.
|
|
4106
|
+
*/
|
|
4107
|
+
get selectedKeys() {
|
|
4108
|
+
const getKey = this.#config.getKey;
|
|
4109
|
+
const out = /* @__PURE__ */ new Set();
|
|
4110
|
+
if (getKey) for (const item of this.#selected) {
|
|
4111
|
+
const key = getKey(item);
|
|
4112
|
+
if (key !== void 0) out.add(key);
|
|
4113
|
+
}
|
|
4114
|
+
return out;
|
|
4115
|
+
}
|
|
3904
4116
|
/** Anchor for range (shift) selection — the last plainly-selected item. */
|
|
3905
4117
|
get anchor() {
|
|
3906
4118
|
return this.#anchor;
|
|
@@ -3984,6 +4196,66 @@ var ListSelectionController = class {
|
|
|
3984
4196
|
next.delete(item);
|
|
3985
4197
|
this.#apply(next);
|
|
3986
4198
|
}
|
|
4199
|
+
/**
|
|
4200
|
+
* Whether the current modes permit a selection of more than one item.
|
|
4201
|
+
* Stricter than `canSelectMultiple()`: `standard` / `implicit` only count
|
|
4202
|
+
* as multi when `selectionMode="multiple"`.
|
|
4203
|
+
*/
|
|
4204
|
+
#effectiveMulti() {
|
|
4205
|
+
const { selectionMode, interactionMode } = this.#config.getModes();
|
|
4206
|
+
return selectionMode === `multiple` || interactionMode === `checked` || interactionMode === `sticky` || interactionMode === `vscode`;
|
|
4207
|
+
}
|
|
4208
|
+
/**
|
|
4209
|
+
* Replace the selection with the items resolved from `keys`. Keys that
|
|
4210
|
+
* `resolve` cannot map, or that resolve to a non-selectable item, are
|
|
4211
|
+
* ignored. `selectionMode="none"` is a no-op. When multi-select is not
|
|
4212
|
+
* available, only the last resolvable key is kept. `onChange` fires once.
|
|
4213
|
+
*/
|
|
4214
|
+
selectKeys(keys, resolve) {
|
|
4215
|
+
if (this.#config.getModes().selectionMode === `none`) return;
|
|
4216
|
+
let next = /* @__PURE__ */ new Set();
|
|
4217
|
+
for (const key of new Set(keys)) {
|
|
4218
|
+
const item = resolve(key);
|
|
4219
|
+
if (item !== void 0 && this.#config.isSelectable(item)) next.add(item);
|
|
4220
|
+
}
|
|
4221
|
+
if (!this.#effectiveMulti() && next.size > 1) {
|
|
4222
|
+
const last = [...next].at(-1);
|
|
4223
|
+
next = last === void 0 ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([last]);
|
|
4224
|
+
}
|
|
4225
|
+
this.#anchor = [...next].at(-1);
|
|
4226
|
+
this.#apply(next);
|
|
4227
|
+
}
|
|
4228
|
+
/**
|
|
4229
|
+
* Add the items resolved from `keys` to the selection (multi-select only;
|
|
4230
|
+
* a no-op otherwise). `onChange` fires once if anything changed.
|
|
4231
|
+
*/
|
|
4232
|
+
addKeys(keys, resolve) {
|
|
4233
|
+
if (!this.#effectiveMulti()) return;
|
|
4234
|
+
const next = new Set(this.#selected);
|
|
4235
|
+
let changed = false;
|
|
4236
|
+
for (const key of new Set(keys)) {
|
|
4237
|
+
const item = resolve(key);
|
|
4238
|
+
if (item !== void 0 && !next.has(item) && this.#config.isSelectable(item)) {
|
|
4239
|
+
next.add(item);
|
|
4240
|
+
changed = true;
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
if (changed) this.#apply(next);
|
|
4244
|
+
}
|
|
4245
|
+
/**
|
|
4246
|
+
* Remove the items resolved from `keys` from the selection. `onChange`
|
|
4247
|
+
* fires once if anything changed.
|
|
4248
|
+
*/
|
|
4249
|
+
removeKeys(keys, resolve) {
|
|
4250
|
+
if (this.#selected.size === 0) return;
|
|
4251
|
+
const next = new Set(this.#selected);
|
|
4252
|
+
let changed = false;
|
|
4253
|
+
for (const key of new Set(keys)) {
|
|
4254
|
+
const item = resolve(key);
|
|
4255
|
+
if (item !== void 0 && next.delete(item)) changed = true;
|
|
4256
|
+
}
|
|
4257
|
+
if (changed) this.#apply(next);
|
|
4258
|
+
}
|
|
3987
4259
|
/** Route a click on `item` according to the current interaction mode. */
|
|
3988
4260
|
handleClick(item, gesture) {
|
|
3989
4261
|
const { selectionMode, interactionMode: mode } = this.#config.getModes();
|
|
@@ -4048,6 +4320,99 @@ var ListSelectionController = class {
|
|
|
4048
4320
|
}
|
|
4049
4321
|
};
|
|
4050
4322
|
//#endregion
|
|
4323
|
+
//#region src/interaction/tree-keyboard-controller.ts
|
|
4324
|
+
var TreeKeyboardNavController = class {
|
|
4325
|
+
constructor(host, tickled, config) {
|
|
4326
|
+
this.handleKeyDown = (event) => {
|
|
4327
|
+
if (this.disableKeyboardNavigation()) return;
|
|
4328
|
+
const { key } = event;
|
|
4329
|
+
if ([
|
|
4330
|
+
`ArrowDown`,
|
|
4331
|
+
`ArrowUp`,
|
|
4332
|
+
`ArrowLeft`,
|
|
4333
|
+
`ArrowRight`,
|
|
4334
|
+
`Enter`,
|
|
4335
|
+
`Escape`
|
|
4336
|
+
].includes(key)) this.tickled.setInputMode(`keyboard`);
|
|
4337
|
+
switch (key) {
|
|
4338
|
+
case `ArrowDown`:
|
|
4339
|
+
event.preventDefault();
|
|
4340
|
+
this.#handleArrowDown();
|
|
4341
|
+
break;
|
|
4342
|
+
case `ArrowUp`:
|
|
4343
|
+
event.preventDefault();
|
|
4344
|
+
this.#handleArrowUp();
|
|
4345
|
+
break;
|
|
4346
|
+
case `ArrowRight`:
|
|
4347
|
+
event.preventDefault();
|
|
4348
|
+
this.#handleArrowRight();
|
|
4349
|
+
break;
|
|
4350
|
+
case `ArrowLeft`:
|
|
4351
|
+
event.preventDefault();
|
|
4352
|
+
this.#handleArrowLeft();
|
|
4353
|
+
break;
|
|
4354
|
+
case `Enter`:
|
|
4355
|
+
event.preventDefault();
|
|
4356
|
+
this.#handleEnter();
|
|
4357
|
+
break;
|
|
4358
|
+
case `Escape`:
|
|
4359
|
+
event.preventDefault();
|
|
4360
|
+
this.callbacks.onEscape?.();
|
|
4361
|
+
}
|
|
4362
|
+
};
|
|
4363
|
+
this.host = host;
|
|
4364
|
+
this.tickled = tickled;
|
|
4365
|
+
this.callbacks = config.callbacks;
|
|
4366
|
+
this.disableKeyboardNavigation = config.disableKeyboardNavigation ?? (() => false);
|
|
4367
|
+
host.addController(this);
|
|
4368
|
+
}
|
|
4369
|
+
hostConnected() {}
|
|
4370
|
+
hostDisconnected() {}
|
|
4371
|
+
#handleArrowDown() {
|
|
4372
|
+
this.tickled.handleKeyboardNavigation(`next`);
|
|
4373
|
+
}
|
|
4374
|
+
#handleArrowUp() {
|
|
4375
|
+
this.tickled.handleKeyboardNavigation(`previous`);
|
|
4376
|
+
}
|
|
4377
|
+
#handleArrowRight() {
|
|
4378
|
+
const currentId = this.tickled.tickledId;
|
|
4379
|
+
if (!currentId) return;
|
|
4380
|
+
if (this.callbacks.isLeaf(currentId)) return;
|
|
4381
|
+
if (!this.callbacks.isExpanded(currentId)) {
|
|
4382
|
+
this.callbacks.onExpand(currentId);
|
|
4383
|
+
this.tickled.setTickled(currentId);
|
|
4384
|
+
} else {
|
|
4385
|
+
const items = this.callbacks.getItems();
|
|
4386
|
+
const currentIdx = items.findIndex((item) => item.id === currentId);
|
|
4387
|
+
if (currentIdx !== -1 && currentIdx + 1 < items.length) {
|
|
4388
|
+
const nextItem = items[currentIdx + 1];
|
|
4389
|
+
if (nextItem.enabled !== false) this.tickled.setTickled(nextItem.id);
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
4393
|
+
#handleArrowLeft() {
|
|
4394
|
+
const currentId = this.tickled.tickledId;
|
|
4395
|
+
if (!currentId) return;
|
|
4396
|
+
if (this.callbacks.isLeaf(currentId)) {
|
|
4397
|
+
const parentId = this.callbacks.getParentId?.(currentId);
|
|
4398
|
+
if (parentId) {
|
|
4399
|
+
this.tickled.setTickled(parentId);
|
|
4400
|
+
if (this.callbacks.isExpanded(parentId)) this.callbacks.onCollapse(parentId);
|
|
4401
|
+
}
|
|
4402
|
+
} else if (this.callbacks.isExpanded(currentId)) this.callbacks.onCollapse(currentId);
|
|
4403
|
+
else {
|
|
4404
|
+
const parentId = this.callbacks.getParentId?.(currentId);
|
|
4405
|
+
if (parentId) this.tickled.setTickled(parentId);
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
#handleEnter() {
|
|
4409
|
+
const currentId = this.tickled.tickledId;
|
|
4410
|
+
if (!currentId) return;
|
|
4411
|
+
this.callbacks.onActivate(currentId);
|
|
4412
|
+
this.callbacks.onSelect?.(currentId);
|
|
4413
|
+
}
|
|
4414
|
+
};
|
|
4415
|
+
//#endregion
|
|
4051
4416
|
//#region src/menu/menu-container.ts
|
|
4052
4417
|
let MenuContainer = class MenuContainer extends i {
|
|
4053
4418
|
constructor(..._args) {
|
|
@@ -10018,8 +10383,8 @@ let MenuCheckItem = class MenuCheckItem extends i {
|
|
|
10018
10383
|
}
|
|
10019
10384
|
|
|
10020
10385
|
.check {
|
|
10021
|
-
width:
|
|
10022
|
-
margin-
|
|
10386
|
+
width: 1em;
|
|
10387
|
+
margin-left: var(--space-xs);
|
|
10023
10388
|
font-size: var(--text-m);
|
|
10024
10389
|
color: var(--accent);
|
|
10025
10390
|
}
|
|
@@ -10539,8 +10904,8 @@ let MenuRadioItem = class MenuRadioItem extends i {
|
|
|
10539
10904
|
}
|
|
10540
10905
|
|
|
10541
10906
|
.radio {
|
|
10542
|
-
width:
|
|
10543
|
-
margin-
|
|
10907
|
+
width: 1em;
|
|
10908
|
+
margin-left: var(--space-xs);
|
|
10544
10909
|
font-size: var(--text-m);
|
|
10545
10910
|
color: var(--accent);
|
|
10546
10911
|
}
|
|
@@ -10679,6 +11044,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
10679
11044
|
this.groupBy = null;
|
|
10680
11045
|
this.allowGrouping = false;
|
|
10681
11046
|
this.crossGroupSelection = false;
|
|
11047
|
+
this.columnMenu = `auto`;
|
|
10682
11048
|
this._columnFormatters = /* @__PURE__ */ new Map();
|
|
10683
11049
|
this._selectedKeys = /* @__PURE__ */ new Set();
|
|
10684
11050
|
this._scrollTop = 0;
|
|
@@ -10701,10 +11067,16 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
10701
11067
|
this._sortedRows = [];
|
|
10702
11068
|
this._flat = [];
|
|
10703
11069
|
this._flatKeys = [];
|
|
11070
|
+
this.#prevFlatKeys = [];
|
|
11071
|
+
this.#enteringRowKeys = /* @__PURE__ */ new Set();
|
|
11072
|
+
this.#rowReflowTimer = 0;
|
|
11073
|
+
this.#dataGridRenderedOnce = false;
|
|
10704
11074
|
this._visibleColumns = [];
|
|
10705
11075
|
this._colOffsets = [];
|
|
10706
11076
|
this._colWidthSum = 0;
|
|
10707
11077
|
this._raf = 0;
|
|
11078
|
+
this.#appendInProgress = false;
|
|
11079
|
+
this.#resortRaf = 0;
|
|
10708
11080
|
this._drag = null;
|
|
10709
11081
|
this._onFocusOut = (e) => {
|
|
10710
11082
|
if (this._suppressBlurCommit) return;
|
|
@@ -10899,6 +11271,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
10899
11271
|
themeFallbacks,
|
|
10900
11272
|
tickledStyles,
|
|
10901
11273
|
tickledItemStyles,
|
|
11274
|
+
listItemFadeKeyframes,
|
|
10902
11275
|
i$3`
|
|
10903
11276
|
:host {
|
|
10904
11277
|
--data-grid-row-height: 28px;
|
|
@@ -10955,6 +11328,22 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
10955
11328
|
.row.item.tickled { background: var(--item-bg-tickled); border: var(--item-border-tickled); color: var(--item-text-tickled); }
|
|
10956
11329
|
.row.item.selected { background: var(--item-bg-selected); border: var(--item-border-selected); color: var(--item-text-selected); }
|
|
10957
11330
|
.row.item.selected.tickled { background: var(--item-bg-selected-tickled); }
|
|
11331
|
+
|
|
11332
|
+
/* Insertion / removal animation. Only while [reflowing] — the ~0.3s after
|
|
11333
|
+
the row set changes — do rows slide (their top transitions) to make or
|
|
11334
|
+
close a gap; a freshly inserted row also fades in. Kept scoped so it
|
|
11335
|
+
never fires while scrolling the virtualised list. */
|
|
11336
|
+
:host([reflowing]) .row.item,
|
|
11337
|
+
:host([reflowing]) .group-row {
|
|
11338
|
+
transition: top var(--data-grid-row-move-duration, 200ms) cubic-bezier(0.2, 0, 0, 1);
|
|
11339
|
+
}
|
|
11340
|
+
.row.item.entering {
|
|
11341
|
+
animation: ixfx-list-item-in var(--data-grid-row-move-duration, 200ms) ease both;
|
|
11342
|
+
}
|
|
11343
|
+
@media (prefers-reduced-motion: reduce) {
|
|
11344
|
+
:host([reflowing]) .row.item, :host([reflowing]) .group-row { transition: none; }
|
|
11345
|
+
.row.item.entering { animation: none; }
|
|
11346
|
+
}
|
|
10958
11347
|
.cell {
|
|
10959
11348
|
flex-shrink: 0; display: flex; align-items: center; padding: 0 var(--space-s);
|
|
10960
11349
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border-right: var(--border-subtle);
|
|
@@ -10984,6 +11373,13 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
10984
11373
|
`
|
|
10985
11374
|
];
|
|
10986
11375
|
}
|
|
11376
|
+
/** Row-key snapshot from the previous update, for the insertion / removal animation. */
|
|
11377
|
+
#prevFlatKeys;
|
|
11378
|
+
#enteringRowKeys;
|
|
11379
|
+
#rowReflowTimer;
|
|
11380
|
+
#dataGridRenderedOnce;
|
|
11381
|
+
#appendInProgress;
|
|
11382
|
+
#resortRaf;
|
|
10987
11383
|
connectedCallback() {
|
|
10988
11384
|
super.connectedCallback();
|
|
10989
11385
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
@@ -10995,6 +11391,10 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
10995
11391
|
this.removeEventListener(`keydown`, this._onKeyDown);
|
|
10996
11392
|
this.removeEventListener(`focusout`, this._onFocusOut);
|
|
10997
11393
|
cancelAnimationFrame(this._raf);
|
|
11394
|
+
if (this.#rowReflowTimer) {
|
|
11395
|
+
clearTimeout(this.#rowReflowTimer);
|
|
11396
|
+
this.#rowReflowTimer = 0;
|
|
11397
|
+
}
|
|
10998
11398
|
}
|
|
10999
11399
|
firstUpdated() {
|
|
11000
11400
|
new ResizeObserver(() => {
|
|
@@ -11021,8 +11421,10 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11021
11421
|
willUpdate(changed) {
|
|
11022
11422
|
super.willUpdate(changed);
|
|
11023
11423
|
this.setAttribute(`data-input-mode`, this._tickled.inputMode);
|
|
11024
|
-
if (
|
|
11025
|
-
|
|
11424
|
+
if (!this.#appendInProgress) {
|
|
11425
|
+
if (changed.has(`rows`) || changed.has(`sortState`) || changed.has(`columns`) || changed.has(`groupBy`) || changed.has(`groupLabel`)) this._sortedRows = this._applySort([...this.rows]);
|
|
11426
|
+
this._rebuildFlat();
|
|
11427
|
+
}
|
|
11026
11428
|
this._visibleColumns = this.columns.filter((c) => c.visible !== false);
|
|
11027
11429
|
let off = 0;
|
|
11028
11430
|
this._colOffsets = this._visibleColumns.map((c) => {
|
|
@@ -11032,6 +11434,40 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11032
11434
|
});
|
|
11033
11435
|
this._colWidthSum = off;
|
|
11034
11436
|
if (this.interactionMode === `checked`) this._colWidthSum += 28;
|
|
11437
|
+
this.#noteFlatMutation();
|
|
11438
|
+
}
|
|
11439
|
+
/**
|
|
11440
|
+
* Diff the row-key list against the previous update. Rows that appeared get an
|
|
11441
|
+
* enter fade; any add/remove opens a short `[reflowing]` window so the rows
|
|
11442
|
+
* that shift slide (their `top` transitions) instead of snapping. Bounded in
|
|
11443
|
+
* time so it never bleeds into scroll virtualisation.
|
|
11444
|
+
*/
|
|
11445
|
+
#noteFlatMutation() {
|
|
11446
|
+
const now = this._flatKeys;
|
|
11447
|
+
const prev = this.#prevFlatKeys;
|
|
11448
|
+
this.#prevFlatKeys = now.slice();
|
|
11449
|
+
if (!this.#dataGridRenderedOnce) {
|
|
11450
|
+
this.#dataGridRenderedOnce = true;
|
|
11451
|
+
return;
|
|
11452
|
+
}
|
|
11453
|
+
if (prev.length === 0 || now.length === 0) {
|
|
11454
|
+
this.#enteringRowKeys.clear();
|
|
11455
|
+
return;
|
|
11456
|
+
}
|
|
11457
|
+
const before = new Set(prev);
|
|
11458
|
+
const after = new Set(now);
|
|
11459
|
+
const added = now.filter((k) => !before.has(k));
|
|
11460
|
+
const removed = prev.filter((k) => !after.has(k));
|
|
11461
|
+
if (added.length === now.length || added.length === 0 && removed.length === 0) return;
|
|
11462
|
+
this.#enteringRowKeys = new Set(added);
|
|
11463
|
+
this.toggleAttribute(`reflowing`, true);
|
|
11464
|
+
if (this.#rowReflowTimer) clearTimeout(this.#rowReflowTimer);
|
|
11465
|
+
this.#rowReflowTimer = window.setTimeout(() => {
|
|
11466
|
+
this.#rowReflowTimer = 0;
|
|
11467
|
+
this.#enteringRowKeys.clear();
|
|
11468
|
+
this.toggleAttribute(`reflowing`, false);
|
|
11469
|
+
this.requestUpdate();
|
|
11470
|
+
}, 320);
|
|
11035
11471
|
}
|
|
11036
11472
|
_getGroupKey(row) {
|
|
11037
11473
|
if (!this.groupBy) return null;
|
|
@@ -11279,6 +11715,102 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11279
11715
|
_getRowForKey(key) {
|
|
11280
11716
|
return this._flat.find((f) => f.key === key)?.row;
|
|
11281
11717
|
}
|
|
11718
|
+
/** The selected row keys (read-only). */
|
|
11719
|
+
get selectedKeys() {
|
|
11720
|
+
return this._selectedKeys;
|
|
11721
|
+
}
|
|
11722
|
+
/** Replace the selection with these row keys (clamped to known rows). */
|
|
11723
|
+
set selectedKeys(keys) {
|
|
11724
|
+
this.selectKeys(keys);
|
|
11725
|
+
}
|
|
11726
|
+
/**
|
|
11727
|
+
* Replace the selection with the given row keys. Unknown keys are dropped;
|
|
11728
|
+
* in a single-select mode only the last surviving key is kept;
|
|
11729
|
+
* `selectionMode="none"` clears. Fires `datagrid-select` with the previous
|
|
11730
|
+
* set when the selection actually changes.
|
|
11731
|
+
*/
|
|
11732
|
+
selectKeys(keys) {
|
|
11733
|
+
const known = new Set(this._flat.filter((f) => f.type === `row`).map((f) => f.key));
|
|
11734
|
+
let next = new Set([...keys].filter((k) => known.has(k)));
|
|
11735
|
+
const multi = this.selectionMode === `multiple` || this.interactionMode === `checked` || this.interactionMode === `sticky` || this.interactionMode === `vscode`;
|
|
11736
|
+
if (this.selectionMode === `none`) next = /* @__PURE__ */ new Set();
|
|
11737
|
+
else if (!multi && next.size > 1) {
|
|
11738
|
+
const last = [...next].at(-1);
|
|
11739
|
+
next = last === void 0 ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([last]);
|
|
11740
|
+
}
|
|
11741
|
+
const prev = new Set(this._selectedKeys);
|
|
11742
|
+
if (prev.size === next.size && [...next].every((k) => prev.has(k))) return;
|
|
11743
|
+
this._selectedKeys = next;
|
|
11744
|
+
this._anchorKey = [...next].at(-1);
|
|
11745
|
+
this.dispatchEvent(new CustomEvent(`datagrid-select`, {
|
|
11746
|
+
detail: {
|
|
11747
|
+
selectedKeys: new Set(next),
|
|
11748
|
+
previous: prev
|
|
11749
|
+
},
|
|
11750
|
+
bubbles: true,
|
|
11751
|
+
composed: true
|
|
11752
|
+
}));
|
|
11753
|
+
}
|
|
11754
|
+
/** The row key under the cursor, or `undefined`. */
|
|
11755
|
+
get tickledKey() {
|
|
11756
|
+
return this._tickled.tickledId;
|
|
11757
|
+
}
|
|
11758
|
+
/** Move the cursor to this row key and scroll it into view. */
|
|
11759
|
+
tickleRow(key) {
|
|
11760
|
+
if (!this._flatKeys.includes(key)) return;
|
|
11761
|
+
this._tickled.focus(key);
|
|
11762
|
+
this._scrollRowIntoView(key);
|
|
11763
|
+
}
|
|
11764
|
+
/**
|
|
11765
|
+
* Append rows without the full O(n log n) rebuild that reassigning `rows`
|
|
11766
|
+
* incurs. `rows` still gets a new array reference (reactivity is preserved),
|
|
11767
|
+
* but the derived arrays are extended over the appended slice only. When a
|
|
11768
|
+
* sort is active the new rows show unsorted for one frame, then a single
|
|
11769
|
+
* coalesced re-sort runs. Scroll position, selection and cursor are kept.
|
|
11770
|
+
*/
|
|
11771
|
+
appendRows(rows) {
|
|
11772
|
+
if (rows.length === 0) return;
|
|
11773
|
+
const startIdx = this.rows.length;
|
|
11774
|
+
this.#appendInProgress = true;
|
|
11775
|
+
this.rows = [...this.rows, ...rows];
|
|
11776
|
+
const appended = this.rows.slice(startIdx);
|
|
11777
|
+
if (this._sortedRows.length === startIdx) this._sortedRows = [...this._sortedRows, ...appended];
|
|
11778
|
+
else {
|
|
11779
|
+
this._sortedRows = this._applySort([...this.rows]);
|
|
11780
|
+
this._rebuildFlat();
|
|
11781
|
+
this.#appendInProgress = false;
|
|
11782
|
+
this.requestUpdate();
|
|
11783
|
+
return;
|
|
11784
|
+
}
|
|
11785
|
+
this.#appendToFlat(appended, startIdx);
|
|
11786
|
+
this.#appendInProgress = false;
|
|
11787
|
+
this.requestUpdate();
|
|
11788
|
+
if (this.sortState != null) {
|
|
11789
|
+
cancelAnimationFrame(this.#resortRaf);
|
|
11790
|
+
this.#resortRaf = requestAnimationFrame(() => {
|
|
11791
|
+
this._sortedRows = this._applySort([...this.rows]);
|
|
11792
|
+
this._rebuildFlat();
|
|
11793
|
+
this.requestUpdate();
|
|
11794
|
+
});
|
|
11795
|
+
}
|
|
11796
|
+
}
|
|
11797
|
+
#appendToFlat(appended, startIdx) {
|
|
11798
|
+
if (this.groupBy) {
|
|
11799
|
+
this._rebuildFlat();
|
|
11800
|
+
return;
|
|
11801
|
+
}
|
|
11802
|
+
for (let j = 0; j < appended.length; j++) {
|
|
11803
|
+
const idx = startIdx + j;
|
|
11804
|
+
const key = this.getRowKey(appended[j], idx);
|
|
11805
|
+
this._flat.push({
|
|
11806
|
+
type: `row`,
|
|
11807
|
+
key,
|
|
11808
|
+
row: appended[j],
|
|
11809
|
+
idx
|
|
11810
|
+
});
|
|
11811
|
+
this._flatKeys.push(key);
|
|
11812
|
+
}
|
|
11813
|
+
}
|
|
11282
11814
|
_replaceSelection(key) {
|
|
11283
11815
|
if (this.selectionMode === `none`) return;
|
|
11284
11816
|
const prev = new Set(this._selectedKeys);
|
|
@@ -11634,6 +12166,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11634
12166
|
return last >= 0 ? this._colOffsets[last] + (this._visibleColumns[last].width ?? 150) : 0;
|
|
11635
12167
|
})()}px"></div>` : A}
|
|
11636
12168
|
</div>
|
|
12169
|
+
${this.columnMenu === `none` ? A : b`
|
|
11637
12170
|
<ixfx-menu-trigger label="⚙" placement="bottom-end" @menu-command=${(e) => {
|
|
11638
12171
|
const cmd = e.detail.command;
|
|
11639
12172
|
const checked = e.detail.checked;
|
|
@@ -11673,7 +12206,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11673
12206
|
</ixfx-menu-radio-group>
|
|
11674
12207
|
` : A}
|
|
11675
12208
|
</ixfx-menu-container>
|
|
11676
|
-
</ixfx-menu-trigger
|
|
12209
|
+
</ixfx-menu-trigger>`}
|
|
11677
12210
|
</div>
|
|
11678
12211
|
<div class="viewport" @scroll=${this._onScroll}>
|
|
11679
12212
|
<div class="spacer" style="height:${this._flat.length * rh}px; width:${this._colWidthSum}px">
|
|
@@ -11690,7 +12223,7 @@ let DataGridElement = class DataGridElement extends i {
|
|
|
11690
12223
|
const selected = this._selectedKeys.has(rowKey);
|
|
11691
12224
|
const tickled = this._tickled.isTickled(rowKey);
|
|
11692
12225
|
return b`
|
|
11693
|
-
<div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
|
|
12226
|
+
<div class="row item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${this.#enteringRowKeys.has(rowKey) ? `entering` : ``}" style="top:${absIdx * rh}px; height:${rh}px"
|
|
11694
12227
|
data-key=${rowKey}
|
|
11695
12228
|
@click=${(e) => {
|
|
11696
12229
|
this._onRowClick(rowKey, null, e);
|
|
@@ -11861,6 +12394,10 @@ __decorate([property({
|
|
|
11861
12394
|
attribute: `cross-group-selection`,
|
|
11862
12395
|
reflect: true
|
|
11863
12396
|
})], DataGridElement.prototype, "crossGroupSelection", void 0);
|
|
12397
|
+
__decorate([property({
|
|
12398
|
+
type: String,
|
|
12399
|
+
attribute: `column-menu`
|
|
12400
|
+
})], DataGridElement.prototype, "columnMenu", void 0);
|
|
11864
12401
|
__decorate([property({ attribute: false })], DataGridElement.prototype, "globalFormatter", void 0);
|
|
11865
12402
|
__decorate([state()], DataGridElement.prototype, "_selectedKeys", void 0);
|
|
11866
12403
|
__decorate([state()], DataGridElement.prototype, "_scrollTop", void 0);
|
|
@@ -11877,8 +12414,273 @@ __decorate([query(`.viewport`)], DataGridElement.prototype, "_viewportEl", void
|
|
|
11877
12414
|
__decorate([query(`.header-scroll`)], DataGridElement.prototype, "_headerScrollEl", void 0);
|
|
11878
12415
|
DataGridElement = __decorate([safeCustomElement(`ixfx-data-grid`)], DataGridElement);
|
|
11879
12416
|
//#endregion
|
|
12417
|
+
//#region src/data-table/data-table.ts
|
|
12418
|
+
let DataTableElement = class DataTableElement extends i {
|
|
12419
|
+
constructor(..._args) {
|
|
12420
|
+
super(..._args);
|
|
12421
|
+
this.rows = [];
|
|
12422
|
+
this.tickleDelay = 400;
|
|
12423
|
+
this.layout = `auto`;
|
|
12424
|
+
this.narrowBreakpoint = 420;
|
|
12425
|
+
this._tickled = new TickledController(this, { getItems: () => this.rows.map((_, i) => ({
|
|
12426
|
+
id: i,
|
|
12427
|
+
enabled: true
|
|
12428
|
+
})) });
|
|
12429
|
+
this._stacked = false;
|
|
12430
|
+
this.#lastWidth = 0;
|
|
12431
|
+
}
|
|
12432
|
+
static {
|
|
12433
|
+
this.styles = [
|
|
12434
|
+
tickledStyles,
|
|
12435
|
+
tickledItemStyles,
|
|
12436
|
+
i$3`
|
|
12437
|
+
:host {
|
|
12438
|
+
display: block;
|
|
12439
|
+
container-type: inline-size;
|
|
12440
|
+
}
|
|
12441
|
+
|
|
12442
|
+
.properties {
|
|
12443
|
+
display: grid;
|
|
12444
|
+
grid-template-columns: var(--data-table-name-width, max-content)
|
|
12445
|
+
var(--data-table-value-width, minmax(0, 1fr));
|
|
12446
|
+
/* No column gap: the tickled highlight reads as one solid block.
|
|
12447
|
+
Separation comes from cell padding, which the highlight covers. */
|
|
12448
|
+
gap: 2px 0;
|
|
12449
|
+
}
|
|
12450
|
+
|
|
12451
|
+
/* Widening: 2 then 3 property-pairs per row on roomy containers.
|
|
12452
|
+
Suppressed when stacked, or when locked to 'columns'. */
|
|
12453
|
+
@container (min-width: 650px) {
|
|
12454
|
+
:host(:not([layout='columns'])) .properties:not(.stacked) {
|
|
12455
|
+
grid-template-columns:
|
|
12456
|
+
var(--data-table-name-width, max-content) var(--data-table-value-width, minmax(0, 1fr))
|
|
12457
|
+
var(--data-table-name-width, max-content) var(--data-table-value-width, minmax(0, 1fr));
|
|
12458
|
+
}
|
|
12459
|
+
}
|
|
12460
|
+
|
|
12461
|
+
@container (min-width: 1000px) {
|
|
12462
|
+
:host(:not([layout='columns'])) .properties:not(.stacked) {
|
|
12463
|
+
grid-template-columns:
|
|
12464
|
+
var(--data-table-name-width, max-content) var(--data-table-value-width, minmax(0, 1fr))
|
|
12465
|
+
var(--data-table-name-width, max-content) var(--data-table-value-width, minmax(0, 1fr))
|
|
12466
|
+
var(--data-table-name-width, max-content) var(--data-table-value-width, minmax(0, 1fr));
|
|
12467
|
+
}
|
|
12468
|
+
}
|
|
12469
|
+
|
|
12470
|
+
/* Stacked (key above value). Threshold is JS-driven via 'narrowBreakpoint'.
|
|
12471
|
+
minmax(0, 1fr) — not 1fr — so a long value's min-content size can't
|
|
12472
|
+
blow the column out past the container; the value then ellipsises. */
|
|
12473
|
+
.properties.stacked {
|
|
12474
|
+
grid-template-columns: minmax(0, 1fr);
|
|
12475
|
+
}
|
|
12476
|
+
|
|
12477
|
+
/* A pair is its own subgrid so the whole row is one element:
|
|
12478
|
+
the tickled highlight can't split, and key/value share a baseline. */
|
|
12479
|
+
.pair {
|
|
12480
|
+
display: grid;
|
|
12481
|
+
grid-column: span 2;
|
|
12482
|
+
grid-template-columns: subgrid;
|
|
12483
|
+
align-items: var(--data-table-cell-align, baseline);
|
|
12484
|
+
cursor: pointer;
|
|
12485
|
+
}
|
|
12486
|
+
|
|
12487
|
+
.pair[data-full] {
|
|
12488
|
+
grid-column: 1 / -1;
|
|
12489
|
+
}
|
|
12490
|
+
|
|
12491
|
+
.pair[data-full] > .value {
|
|
12492
|
+
grid-column: 2 / -1;
|
|
12493
|
+
}
|
|
12494
|
+
|
|
12495
|
+
.properties.stacked .pair {
|
|
12496
|
+
display: block;
|
|
12497
|
+
grid-column: 1 / -1;
|
|
12498
|
+
/* Let the block shrink below its content so .value-text can clip. */
|
|
12499
|
+
min-width: 0;
|
|
12500
|
+
}
|
|
12501
|
+
|
|
12502
|
+
.properties.stacked .pair:not(:last-child) {
|
|
12503
|
+
margin-bottom: var(--space-s);
|
|
12504
|
+
}
|
|
12505
|
+
|
|
12506
|
+
/* Stacked keys get their own line, so a long label must wrap rather than
|
|
12507
|
+
run off the edge (nowrap is only wanted for the max-content column). */
|
|
12508
|
+
.properties.stacked .key {
|
|
12509
|
+
white-space: normal;
|
|
12510
|
+
overflow-wrap: break-word;
|
|
12511
|
+
}
|
|
12512
|
+
|
|
12513
|
+
.key,
|
|
12514
|
+
.value {
|
|
12515
|
+
min-width: 0;
|
|
12516
|
+
/* Row rhythm comes from this padding (tunable), not line-height. */
|
|
12517
|
+
padding: var(--data-table-row-padding, 0.35em) 0;
|
|
12518
|
+
}
|
|
12519
|
+
|
|
12520
|
+
.key {
|
|
12521
|
+
padding-right: var(--space-m);
|
|
12522
|
+
opacity: 0.6;
|
|
12523
|
+
white-space: nowrap;
|
|
12524
|
+
text-align: var(--data-table-name-align, left);
|
|
12525
|
+
font-size: 10px;
|
|
12526
|
+
text-transform: uppercase;
|
|
12527
|
+
letter-spacing: 0.05em;
|
|
12528
|
+
/* Uppercase keys have no descenders, so both edges can be trimmed to
|
|
12529
|
+
the glyphs for tight baseline / centre alignment. */
|
|
12530
|
+
text-box: trim-both cap alphabetic;
|
|
12531
|
+
}
|
|
12532
|
+
|
|
12533
|
+
.value {
|
|
12534
|
+
position: relative;
|
|
12535
|
+
padding-right: var(--space-l);
|
|
12536
|
+
font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
12537
|
+
}
|
|
12538
|
+
|
|
12539
|
+
/* The value text is clipped here — never on .value itself — so a long
|
|
12540
|
+
value stays within its cell even while tickled, and the detail
|
|
12541
|
+
popover (a child of .value) can still escape the cell. */
|
|
12542
|
+
.value-text {
|
|
12543
|
+
display: block;
|
|
12544
|
+
overflow: hidden;
|
|
12545
|
+
text-overflow: ellipsis;
|
|
12546
|
+
white-space: nowrap;
|
|
12547
|
+
/* No text-box trim here: the natural line box gives descenders the room
|
|
12548
|
+
overflow:hidden would otherwise clip. Baseline alignment of key and
|
|
12549
|
+
value comes from align-items on .pair, which needs no trimming. */
|
|
12550
|
+
}
|
|
12551
|
+
|
|
12552
|
+
.pair.tickled {
|
|
12553
|
+
background: var(--item-bg-tickled, color-mix(in oklch, var(--surface-2), var(--accent) 10%));
|
|
12554
|
+
}
|
|
12555
|
+
|
|
12556
|
+
/* Lift the row whose detail is open above the rows it slides over. */
|
|
12557
|
+
.pair.show-detail {
|
|
12558
|
+
position: relative;
|
|
12559
|
+
z-index: 1;
|
|
12560
|
+
}
|
|
12561
|
+
|
|
12562
|
+
.detail {
|
|
12563
|
+
position: absolute;
|
|
12564
|
+
z-index: 2;
|
|
12565
|
+
/* Flush with the bottom of the value (no gap) so the pointer can travel
|
|
12566
|
+
from value to panel without crossing a dead zone that would dismiss it.
|
|
12567
|
+
Left-aligned with the value; only as wide as its content, capped at
|
|
12568
|
+
the table width. */
|
|
12569
|
+
top: 100%;
|
|
12570
|
+
left: 0;
|
|
12571
|
+
right: auto;
|
|
12572
|
+
width: max-content;
|
|
12573
|
+
max-width: 100cqi;
|
|
12574
|
+
padding: var(--space-xs);
|
|
12575
|
+
background: var(--surface-2);
|
|
12576
|
+
visibility: hidden;
|
|
12577
|
+
transform: translateY(-4px);
|
|
12578
|
+
pointer-events: none;
|
|
12579
|
+
transition: transform var(--transition);
|
|
12580
|
+
}
|
|
12581
|
+
|
|
12582
|
+
.pair.show-detail .detail {
|
|
12583
|
+
visibility: visible;
|
|
12584
|
+
transform: translateY(0);
|
|
12585
|
+
pointer-events: auto;
|
|
12586
|
+
}
|
|
12587
|
+
`
|
|
12588
|
+
];
|
|
12589
|
+
}
|
|
12590
|
+
#detailTimer;
|
|
12591
|
+
#resizeObserver;
|
|
12592
|
+
#lastWidth;
|
|
12593
|
+
connectedCallback() {
|
|
12594
|
+
super.connectedCallback();
|
|
12595
|
+
this.#resizeObserver = new ResizeObserver((entries) => {
|
|
12596
|
+
const entry = entries[0];
|
|
12597
|
+
if (!entry) return;
|
|
12598
|
+
this.#lastWidth = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;
|
|
12599
|
+
this.#updateStacked();
|
|
12600
|
+
});
|
|
12601
|
+
this.#resizeObserver.observe(this);
|
|
12602
|
+
}
|
|
12603
|
+
disconnectedCallback() {
|
|
12604
|
+
super.disconnectedCallback();
|
|
12605
|
+
clearTimeout(this.#detailTimer);
|
|
12606
|
+
this.#resizeObserver?.disconnect();
|
|
12607
|
+
this.#resizeObserver = void 0;
|
|
12608
|
+
}
|
|
12609
|
+
willUpdate(changed) {
|
|
12610
|
+
if (changed.has(`layout`) || changed.has(`narrowBreakpoint`)) this.#updateStacked();
|
|
12611
|
+
}
|
|
12612
|
+
#updateStacked() {
|
|
12613
|
+
const stacked = this.layout === `rows` ? true : this.layout === `columns` ? false : this.#lastWidth > 0 && this.#lastWidth <= this.narrowBreakpoint;
|
|
12614
|
+
if (stacked !== this._stacked) this._stacked = stacked;
|
|
12615
|
+
}
|
|
12616
|
+
#handlePointerEnter(i) {
|
|
12617
|
+
this._tickled.handlePointerEnter(i);
|
|
12618
|
+
clearTimeout(this.#detailTimer);
|
|
12619
|
+
if (this._detailIndex !== void 0) this._detailIndex = void 0;
|
|
12620
|
+
this.#detailTimer = setTimeout(() => {
|
|
12621
|
+
if (this._tickled.isTickled(i)) this._detailIndex = i;
|
|
12622
|
+
}, this.tickleDelay);
|
|
12623
|
+
}
|
|
12624
|
+
#handlePointerLeave(e) {
|
|
12625
|
+
clearTimeout(this.#detailTimer);
|
|
12626
|
+
this._detailIndex = void 0;
|
|
12627
|
+
this._tickled.handlePointerLeave(e);
|
|
12628
|
+
}
|
|
12629
|
+
#select(i) {
|
|
12630
|
+
clearTimeout(this.#detailTimer);
|
|
12631
|
+
this.selectedIndex = this.selectedIndex === i ? void 0 : i;
|
|
12632
|
+
this._tickled.setTickled(i);
|
|
12633
|
+
this._detailIndex = i;
|
|
12634
|
+
this.dispatchEvent(new CustomEvent(`data-table-row-click`, {
|
|
12635
|
+
bubbles: true,
|
|
12636
|
+
composed: true,
|
|
12637
|
+
detail: {
|
|
12638
|
+
row: this.rows[i],
|
|
12639
|
+
index: i,
|
|
12640
|
+
selected: this.selectedIndex === i
|
|
12641
|
+
}
|
|
12642
|
+
}));
|
|
12643
|
+
}
|
|
12644
|
+
render() {
|
|
12645
|
+
return b`<div
|
|
12646
|
+
class="properties ${this._stacked ? `stacked` : ``}"
|
|
12647
|
+
@pointerleave=${(e) => this.#handlePointerLeave(e)}
|
|
12648
|
+
>
|
|
12649
|
+
${this.rows.map((r, i) => b`<div
|
|
12650
|
+
class="pair ${this._tickled.isTickled(i) ? `tickled` : ``} ${this._detailIndex === i ? `show-detail` : ``}"
|
|
12651
|
+
?data-full=${r.fullWidth}
|
|
12652
|
+
@pointerenter=${() => this.#handlePointerEnter(i)}
|
|
12653
|
+
@click=${() => this.#select(i)}
|
|
12654
|
+
><div class="key">${r.label}</div><div class="value"><span class="value-text">${typeof r.value === `boolean` ? r.value ? `Yes` : `No` : r.value}</span>${r.detail === void 0 ? A : b`<div class="detail" part="detail">${r.detail}</div>`}</div></div>`)}</div>`;
|
|
12655
|
+
}
|
|
12656
|
+
};
|
|
12657
|
+
__decorate([property({
|
|
12658
|
+
type: Array,
|
|
12659
|
+
attribute: false
|
|
12660
|
+
})], DataTableElement.prototype, "rows", void 0);
|
|
12661
|
+
__decorate([property({
|
|
12662
|
+
type: Number,
|
|
12663
|
+
attribute: false
|
|
12664
|
+
})], DataTableElement.prototype, "selectedIndex", void 0);
|
|
12665
|
+
__decorate([property({
|
|
12666
|
+
type: Number,
|
|
12667
|
+
attribute: `tickle-delay`
|
|
12668
|
+
})], DataTableElement.prototype, "tickleDelay", void 0);
|
|
12669
|
+
__decorate([property({
|
|
12670
|
+
type: String,
|
|
12671
|
+
reflect: true
|
|
12672
|
+
})], DataTableElement.prototype, "layout", void 0);
|
|
12673
|
+
__decorate([property({
|
|
12674
|
+
type: Number,
|
|
12675
|
+
attribute: `narrow-breakpoint`
|
|
12676
|
+
})], DataTableElement.prototype, "narrowBreakpoint", void 0);
|
|
12677
|
+
__decorate([state()], DataTableElement.prototype, "_tickled", void 0);
|
|
12678
|
+
__decorate([state()], DataTableElement.prototype, "_detailIndex", void 0);
|
|
12679
|
+
__decorate([state()], DataTableElement.prototype, "_stacked", void 0);
|
|
12680
|
+
DataTableElement = __decorate([safeCustomElement(`ixfx-data-table`)], DataTableElement);
|
|
12681
|
+
//#endregion
|
|
11880
12682
|
//#region src/detail-list/detail-list.ts
|
|
11881
|
-
const ITEM_SELECTOR$
|
|
12683
|
+
const ITEM_SELECTOR$2 = `:scope > li`;
|
|
11882
12684
|
const SEARCH_HIGHLIGHT_KEY = `detail-list-search`;
|
|
11883
12685
|
const LABEL_SPAN_CSS = `overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0`;
|
|
11884
12686
|
let DetailListElement = class DetailListElement extends i {
|
|
@@ -11899,6 +12701,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
11899
12701
|
this.#grid = [];
|
|
11900
12702
|
this.#positions = /* @__PURE__ */ new Map();
|
|
11901
12703
|
this.#geometryScheduled = false;
|
|
12704
|
+
this.#animatedItems = /* @__PURE__ */ new WeakSet();
|
|
11902
12705
|
this.#builtFromProvider = [];
|
|
11903
12706
|
this.#providerBuilt = 0;
|
|
11904
12707
|
this.#data = new DataController(this);
|
|
@@ -11909,11 +12712,13 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
11909
12712
|
selectionMode: this.selectionMode,
|
|
11910
12713
|
interactionMode: this.interactionMode
|
|
11911
12714
|
}),
|
|
12715
|
+
getKey: keyForElement,
|
|
11912
12716
|
onChange: (selected, previous) => {
|
|
11913
12717
|
this._syncItemStates();
|
|
11914
12718
|
this._dispatch(`list-select`, {
|
|
11915
12719
|
selected,
|
|
11916
|
-
previous
|
|
12720
|
+
previous,
|
|
12721
|
+
selectedKeys: this.selection.selectedKeys
|
|
11917
12722
|
});
|
|
11918
12723
|
}
|
|
11919
12724
|
});
|
|
@@ -12065,6 +12870,8 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12065
12870
|
#geometryScheduled;
|
|
12066
12871
|
#resizeObserver;
|
|
12067
12872
|
#mutationObserver;
|
|
12873
|
+
/** Items that have already played (or been skipped for) their enter animation. */
|
|
12874
|
+
#animatedItems;
|
|
12068
12875
|
#builtFromProvider;
|
|
12069
12876
|
#providerBuilt;
|
|
12070
12877
|
#data;
|
|
@@ -12079,8 +12886,9 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12079
12886
|
this.addEventListener(`wheel`, this._onWheel);
|
|
12080
12887
|
this.#resizeObserver = new ResizeObserver(() => this.#scheduleGeometry());
|
|
12081
12888
|
this.#resizeObserver.observe(this);
|
|
12082
|
-
this.#mutationObserver = new MutationObserver(() => {
|
|
12889
|
+
this.#mutationObserver = new MutationObserver((records) => {
|
|
12083
12890
|
this.#processChildren();
|
|
12891
|
+
this.#animateChildMutations(records);
|
|
12084
12892
|
this.#scheduleGeometry();
|
|
12085
12893
|
});
|
|
12086
12894
|
this.#mutationObserver.observe(this, {
|
|
@@ -12088,6 +12896,27 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12088
12896
|
subtree: false,
|
|
12089
12897
|
attributeFilter: [`hidden`]
|
|
12090
12898
|
});
|
|
12899
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) this.#animatedItems.add(el);
|
|
12900
|
+
}
|
|
12901
|
+
/**
|
|
12902
|
+
* Collapse-in items appended to the light DOM (however they got there). A bulk
|
|
12903
|
+
* populate is left alone. Geometry is re-read once the animations settle so
|
|
12904
|
+
* keyboard navigation lines back up with the final layout.
|
|
12905
|
+
*/
|
|
12906
|
+
#animateChildMutations(records) {
|
|
12907
|
+
if (!this.isConnected) return;
|
|
12908
|
+
const fresh = [];
|
|
12909
|
+
for (const record of records) for (const node of record.addedNodes) {
|
|
12910
|
+
if (!(node instanceof HTMLElement) || node.parentElement !== this || node.localName !== `li`) continue;
|
|
12911
|
+
if (this.#animatedItems.has(node)) continue;
|
|
12912
|
+
this.#animatedItems.add(node);
|
|
12913
|
+
fresh.push(node);
|
|
12914
|
+
}
|
|
12915
|
+
if (fresh.length === 0) return;
|
|
12916
|
+
const total = this.querySelectorAll(ITEM_SELECTOR$2).length;
|
|
12917
|
+
if (fresh.length >= total) return;
|
|
12918
|
+
for (const el of fresh) animateListItemIn(el);
|
|
12919
|
+
window.setTimeout(() => this.#scheduleGeometry(), 230);
|
|
12091
12920
|
}
|
|
12092
12921
|
disconnectedCallback() {
|
|
12093
12922
|
super.disconnectedCallback();
|
|
@@ -12109,7 +12938,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12109
12938
|
if (this.filterPredicate) this.#applyFilterPredicate();
|
|
12110
12939
|
this.#search = createElementIncrSearch({
|
|
12111
12940
|
container: this,
|
|
12112
|
-
itemSelector: ITEM_SELECTOR$
|
|
12941
|
+
itemSelector: ITEM_SELECTOR$2,
|
|
12113
12942
|
getLabel: (el) => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
12114
12943
|
getKey: (el) => el.getAttribute(`data-key`) ?? el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,
|
|
12115
12944
|
highlightKey: SEARCH_HIGHLIGHT_KEY
|
|
@@ -12168,8 +12997,21 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12168
12997
|
if (this.sort !== `none`) this.#applySort();
|
|
12169
12998
|
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
12170
12999
|
}
|
|
13000
|
+
/**
|
|
13001
|
+
* Collapse + fade `item` out, then detach it; the rows after it slide up to
|
|
13002
|
+
* close the gap. Resolves once removed. Immediate under reduced motion.
|
|
13003
|
+
*/
|
|
12171
13004
|
removeItem(item) {
|
|
12172
|
-
|
|
13005
|
+
this.selection.deselect(item);
|
|
13006
|
+
return animateListItemOut(item).then(() => {
|
|
13007
|
+
item.remove();
|
|
13008
|
+
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
13009
|
+
this.#recomputeGeometry();
|
|
13010
|
+
});
|
|
13011
|
+
}
|
|
13012
|
+
/** {@link removeItem} for several items at once. */
|
|
13013
|
+
removeItems(items) {
|
|
13014
|
+
return Promise.all([...items].map((item) => this.removeItem(item))).then(() => void 0);
|
|
12173
13015
|
}
|
|
12174
13016
|
clearItems() {
|
|
12175
13017
|
this.replaceChildren();
|
|
@@ -12215,6 +13057,40 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12215
13057
|
selectAll() {
|
|
12216
13058
|
if (this.selection.canSelectAll()) this.selection.selectAll();
|
|
12217
13059
|
}
|
|
13060
|
+
/** The selection as stable `data-key` / `data-value` strings. */
|
|
13061
|
+
get selectedKeys() {
|
|
13062
|
+
return this.selection.selectedKeys;
|
|
13063
|
+
}
|
|
13064
|
+
/** The key of the item under the cursor, or `undefined`. */
|
|
13065
|
+
get tickledKey() {
|
|
13066
|
+
const id = this.tickled.tickledId;
|
|
13067
|
+
return id ? keyForElement(id) : void 0;
|
|
13068
|
+
}
|
|
13069
|
+
/** Replace the selection with the items carrying these keys (unknown keys ignored). */
|
|
13070
|
+
selectKeys(keys) {
|
|
13071
|
+
this.selection.selectKeys(keys, (key) => this.#itemForKey(key));
|
|
13072
|
+
}
|
|
13073
|
+
/** Add items by key to the selection (multi-select modes only). */
|
|
13074
|
+
addSelectedKeys(keys) {
|
|
13075
|
+
this.selection.addKeys(keys, (key) => this.#itemForKey(key));
|
|
13076
|
+
}
|
|
13077
|
+
/** Remove items by key from the selection. */
|
|
13078
|
+
removeSelectedKeys(keys) {
|
|
13079
|
+
this.selection.removeKeys(keys, (key) => this.#itemForKey(key));
|
|
13080
|
+
}
|
|
13081
|
+
/** Move the cursor to the item with this key and scroll it into view. */
|
|
13082
|
+
focusKey(key) {
|
|
13083
|
+
const el = this.#itemForKey(key);
|
|
13084
|
+
if (!el) return;
|
|
13085
|
+
this.tickled.focus(el);
|
|
13086
|
+
el.scrollIntoView({
|
|
13087
|
+
block: `nearest`,
|
|
13088
|
+
inline: `nearest`
|
|
13089
|
+
});
|
|
13090
|
+
}
|
|
13091
|
+
#itemForKey(key) {
|
|
13092
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) if (keyForElement(el) === key) return el;
|
|
13093
|
+
}
|
|
12218
13094
|
*selectedFilter(kind) {
|
|
12219
13095
|
const wanted = (el) => {
|
|
12220
13096
|
switch (kind) {
|
|
@@ -12223,7 +13099,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12223
13099
|
case `either`: return el.hasAttribute(`data-selected`) || el.hasAttribute(`data-checked`);
|
|
12224
13100
|
}
|
|
12225
13101
|
};
|
|
12226
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13102
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) {
|
|
12227
13103
|
if (el.hidden) continue;
|
|
12228
13104
|
if (wanted(el)) yield el;
|
|
12229
13105
|
}
|
|
@@ -12236,7 +13112,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12236
13112
|
if (previousKeys.size > 0 || anchorKey !== null) {
|
|
12237
13113
|
const next = [];
|
|
12238
13114
|
let anchor;
|
|
12239
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13115
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) {
|
|
12240
13116
|
const key = keyOf(el);
|
|
12241
13117
|
if (key !== null && previousKeys.has(key)) next.push(el);
|
|
12242
13118
|
if (key !== null && key === anchorKey) anchor = el;
|
|
@@ -12291,15 +13167,15 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12291
13167
|
}
|
|
12292
13168
|
#applyFilterPredicate() {
|
|
12293
13169
|
if (!this.filterPredicate) {
|
|
12294
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13170
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) el.removeAttribute(`hidden`);
|
|
12295
13171
|
return;
|
|
12296
13172
|
}
|
|
12297
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13173
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) if (this.filterPredicate(el)) el.removeAttribute(`hidden`);
|
|
12298
13174
|
else el.setAttribute(`hidden`, ``);
|
|
12299
13175
|
}
|
|
12300
13176
|
#applySort() {
|
|
12301
13177
|
if (this.sort === `none`) return;
|
|
12302
|
-
const rows = [...this.querySelectorAll(ITEM_SELECTOR$
|
|
13178
|
+
const rows = [...this.querySelectorAll(ITEM_SELECTOR$2)];
|
|
12303
13179
|
if (rows.length < 2) return;
|
|
12304
13180
|
const dir = this.sort === `descending` ? -1 : 1;
|
|
12305
13181
|
const meta = (el) => ({
|
|
@@ -12491,12 +13367,34 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12491
13367
|
else if (this.itemWrap === `wrap`) this.#navigateTo(this.#findLastCheckable());
|
|
12492
13368
|
}
|
|
12493
13369
|
_getVisibleItems() {
|
|
12494
|
-
return Array.from(this.querySelectorAll(ITEM_SELECTOR$
|
|
13370
|
+
return Array.from(this.querySelectorAll(ITEM_SELECTOR$2)).filter((el) => {
|
|
12495
13371
|
if (el.hidden) return false;
|
|
12496
13372
|
if (this.filterPredicate && !this.filterPredicate(el)) return false;
|
|
12497
13373
|
return true;
|
|
12498
13374
|
});
|
|
12499
13375
|
}
|
|
13376
|
+
/**
|
|
13377
|
+
* Set the column width to fit the widest visible item.
|
|
13378
|
+
*
|
|
13379
|
+
* This is opt-in; detail lists continue to use `--detail-list-column-width`
|
|
13380
|
+
* (or its 220px fallback) until this method is called. The measured value is
|
|
13381
|
+
* set directly on this element, so it takes precedence over an inherited
|
|
13382
|
+
* value. Returns the applied width in pixels, or 0 when there are no items.
|
|
13383
|
+
*/
|
|
13384
|
+
async fitColumnWidth() {
|
|
13385
|
+
await this.updateComplete;
|
|
13386
|
+
const items = this._getVisibleItems();
|
|
13387
|
+
if (items.length === 0) return 0;
|
|
13388
|
+
const previousWidth = this.style.getPropertyValue(`--detail-list-column-width`);
|
|
13389
|
+
const previousPriority = this.style.getPropertyPriority(`--detail-list-column-width`);
|
|
13390
|
+
this.style.setProperty(`--detail-list-column-width`, `max-content`);
|
|
13391
|
+
const width = Math.ceil(Math.max(...items.map((item) => item.getBoundingClientRect().width)));
|
|
13392
|
+
if (previousWidth) this.style.setProperty(`--detail-list-column-width`, previousWidth, previousPriority);
|
|
13393
|
+
else this.style.removeProperty(`--detail-list-column-width`);
|
|
13394
|
+
this.style.setProperty(`--detail-list-column-width`, `${width}px`);
|
|
13395
|
+
this.#scheduleGeometry();
|
|
13396
|
+
return width;
|
|
13397
|
+
}
|
|
12500
13398
|
_isCheckable(item) {
|
|
12501
13399
|
if (this.interactionMode !== `checked`) return true;
|
|
12502
13400
|
return !item.hasAttribute(`not-checked`);
|
|
@@ -12524,10 +13422,10 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12524
13422
|
}
|
|
12525
13423
|
/** Scan light-DOM children for unprocessed `data-icon` items. */
|
|
12526
13424
|
#processChildren() {
|
|
12527
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13425
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) this.#ensureIcon(el);
|
|
12528
13426
|
}
|
|
12529
13427
|
_syncItemStates() {
|
|
12530
|
-
for (const el of this.querySelectorAll(ITEM_SELECTOR$
|
|
13428
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR$2)) {
|
|
12531
13429
|
el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
|
|
12532
13430
|
el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
|
|
12533
13431
|
if (this.interactionMode === `checked`) el.toggleAttribute(`data-checked`, !el.hasAttribute(`not-checked`) && this.selection.selected.has(el));
|
|
@@ -12658,8 +13556,8 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12658
13556
|
overflow: hidden;
|
|
12659
13557
|
box-sizing: border-box;
|
|
12660
13558
|
outline: none;
|
|
12661
|
-
--
|
|
12662
|
-
var(--ixfx-detail-list-item-padding, var(--detail-list-item-padding, 0 var(--space-
|
|
13559
|
+
--_detail-list-item-padding:
|
|
13560
|
+
var(--ixfx-detail-list-item-padding, var(--detail-list-item-padding, 0 var(--space-xs, 3px)));
|
|
12663
13561
|
}
|
|
12664
13562
|
|
|
12665
13563
|
:host(:focus-visible) {
|
|
@@ -12706,7 +13604,7 @@ let DetailListElement = class DetailListElement extends i {
|
|
|
12706
13604
|
flex: none;
|
|
12707
13605
|
width: var(--detail-list-column-width, 220px);
|
|
12708
13606
|
height: var(--detail-list-item-height, 28px);
|
|
12709
|
-
padding: var(--
|
|
13607
|
+
padding: var(--_detail-list-item-padding);
|
|
12710
13608
|
cursor: pointer;
|
|
12711
13609
|
user-select: none;
|
|
12712
13610
|
transition: background var(--transition, 0.1s ease-out),
|
|
@@ -18586,6 +19484,924 @@ let FormElement = class FormElement extends i {
|
|
|
18586
19484
|
};
|
|
18587
19485
|
FormElement = __decorate([safeCustomElement(`ixfx-form`)], FormElement);
|
|
18588
19486
|
//#endregion
|
|
19487
|
+
//#region src/grid-list/grid-list.ts
|
|
19488
|
+
const ITEM_SELECTOR$1 = `:scope > *`;
|
|
19489
|
+
const MIN_ITEM_SIZE = 16;
|
|
19490
|
+
/**
|
|
19491
|
+
* How long the host keeps the `[reflowing]` attribute after items are inserted
|
|
19492
|
+
* mid-list — long enough for the cell move-transition and the enter animation
|
|
19493
|
+
* to finish. Kept a touch above the default `--grid-list-item-move-duration`.
|
|
19494
|
+
*/
|
|
19495
|
+
const REFLOW_WINDOW_MS = 320;
|
|
19496
|
+
function computeGridWindow(count, itemWidth, rowHeight, gap, viewportWidth, viewportHeight, scrollOffset, orientation, overscan = 2) {
|
|
19497
|
+
const w = Math.max(1, itemWidth);
|
|
19498
|
+
const h = Math.max(1, rowHeight);
|
|
19499
|
+
const g = Math.max(0, gap);
|
|
19500
|
+
const colStride = w + g;
|
|
19501
|
+
const rowStride = h + g;
|
|
19502
|
+
const offset = Math.max(0, scrollOffset);
|
|
19503
|
+
if (orientation === `row`) {
|
|
19504
|
+
const rows = count === 0 ? 0 : 1;
|
|
19505
|
+
const totalWidth = count === 0 ? 0 : (count - 1) * colStride + w;
|
|
19506
|
+
if (count === 0 || viewportWidth <= 0) return {
|
|
19507
|
+
cols: count,
|
|
19508
|
+
rows,
|
|
19509
|
+
totalHeight: 0,
|
|
19510
|
+
totalWidth,
|
|
19511
|
+
startIndex: 0,
|
|
19512
|
+
endIndex: 0,
|
|
19513
|
+
colStride,
|
|
19514
|
+
rowStride,
|
|
19515
|
+
horizontal: true
|
|
19516
|
+
};
|
|
19517
|
+
const firstVisible = Math.max(0, Math.floor(offset / colStride));
|
|
19518
|
+
const lastVisible = Math.ceil((offset + viewportWidth) / colStride);
|
|
19519
|
+
return {
|
|
19520
|
+
cols: count,
|
|
19521
|
+
rows,
|
|
19522
|
+
totalHeight: 0,
|
|
19523
|
+
totalWidth,
|
|
19524
|
+
startIndex: Math.max(0, firstVisible - overscan),
|
|
19525
|
+
endIndex: Math.min(count, lastVisible + overscan),
|
|
19526
|
+
colStride,
|
|
19527
|
+
rowStride,
|
|
19528
|
+
horizontal: true
|
|
19529
|
+
};
|
|
19530
|
+
}
|
|
19531
|
+
const cols = orientation === `column` ? 1 : Math.max(1, Math.floor((Math.max(0, viewportWidth) + g) / colStride));
|
|
19532
|
+
const rows = count === 0 ? 0 : Math.ceil(count / cols);
|
|
19533
|
+
const totalHeight = rows === 0 ? 0 : (rows - 1) * rowStride + h;
|
|
19534
|
+
if (count === 0 || viewportHeight <= 0) return {
|
|
19535
|
+
cols,
|
|
19536
|
+
rows,
|
|
19537
|
+
totalHeight,
|
|
19538
|
+
totalWidth: 0,
|
|
19539
|
+
startIndex: 0,
|
|
19540
|
+
endIndex: 0,
|
|
19541
|
+
colStride,
|
|
19542
|
+
rowStride,
|
|
19543
|
+
horizontal: false
|
|
19544
|
+
};
|
|
19545
|
+
const firstVisibleRow = Math.max(0, Math.floor(offset / rowStride));
|
|
19546
|
+
const lastVisibleRow = Math.ceil((offset + viewportHeight) / rowStride);
|
|
19547
|
+
const rowStart = Math.max(0, firstVisibleRow - overscan);
|
|
19548
|
+
const rowEnd = Math.min(rows, lastVisibleRow + overscan);
|
|
19549
|
+
return {
|
|
19550
|
+
cols,
|
|
19551
|
+
rows,
|
|
19552
|
+
totalHeight,
|
|
19553
|
+
totalWidth: 0,
|
|
19554
|
+
startIndex: rowStart * cols,
|
|
19555
|
+
endIndex: Math.min(count, rowEnd * cols),
|
|
19556
|
+
colStride,
|
|
19557
|
+
rowStride,
|
|
19558
|
+
horizontal: false
|
|
19559
|
+
};
|
|
19560
|
+
}
|
|
19561
|
+
function columnOf(index, cols) {
|
|
19562
|
+
return cols <= 1 ? 0 : index % cols;
|
|
19563
|
+
}
|
|
19564
|
+
function rowOf(index, cols) {
|
|
19565
|
+
return cols <= 1 ? index : Math.floor(index / cols);
|
|
19566
|
+
}
|
|
19567
|
+
let GridListElement = class GridListElement extends i {
|
|
19568
|
+
constructor(..._args) {
|
|
19569
|
+
super(..._args);
|
|
19570
|
+
this.selectionMode = `single`;
|
|
19571
|
+
this.interactionMode = `standard`;
|
|
19572
|
+
this.itemSize = 160;
|
|
19573
|
+
this.gap = 8;
|
|
19574
|
+
this.orientation = `grid`;
|
|
19575
|
+
this.captionStyle = `under`;
|
|
19576
|
+
this.captionAlign = `middle`;
|
|
19577
|
+
this.captionPosition = `bottom`;
|
|
19578
|
+
this.captionBackdrop = `scrim`;
|
|
19579
|
+
this.captionHeight = 24;
|
|
19580
|
+
this.itemWrap = `stop`;
|
|
19581
|
+
this._scrollTop = 0;
|
|
19582
|
+
this._scrollLeft = 0;
|
|
19583
|
+
this._viewportHeight = 0;
|
|
19584
|
+
this._viewportWidth = 0;
|
|
19585
|
+
this.#viewportRef = createRef();
|
|
19586
|
+
this.#scrollRaf = 0;
|
|
19587
|
+
this.#items = [];
|
|
19588
|
+
this.#itemWrap = `stop`;
|
|
19589
|
+
this.#entering = /* @__PURE__ */ new Set();
|
|
19590
|
+
this.#reflowTimer = 0;
|
|
19591
|
+
this.#slotNames = /* @__PURE__ */ new WeakMap();
|
|
19592
|
+
this.#slotSeq = 0;
|
|
19593
|
+
this.selection = new ListSelectionController({
|
|
19594
|
+
getOrderedItems: () => this._getVisibleItems(),
|
|
19595
|
+
isSelectable: () => true,
|
|
19596
|
+
getModes: () => ({
|
|
19597
|
+
selectionMode: this.selectionMode,
|
|
19598
|
+
interactionMode: this.interactionMode
|
|
19599
|
+
}),
|
|
19600
|
+
getKey: keyForElement,
|
|
19601
|
+
onChange: (selected, previous) => {
|
|
19602
|
+
this._syncItemStates();
|
|
19603
|
+
this.requestUpdate();
|
|
19604
|
+
this._dispatch(`list-select`, {
|
|
19605
|
+
selected,
|
|
19606
|
+
previous,
|
|
19607
|
+
selectedKeys: this.selection.selectedKeys
|
|
19608
|
+
});
|
|
19609
|
+
}
|
|
19610
|
+
});
|
|
19611
|
+
this.tickled = new TickledController(this, {
|
|
19612
|
+
getItems: () => this.#items.map((el) => ({
|
|
19613
|
+
id: el,
|
|
19614
|
+
enabled: !el.hasAttribute(`not-checked`)
|
|
19615
|
+
})),
|
|
19616
|
+
onTickle: (id) => {
|
|
19617
|
+
if (id !== void 0) this._dispatch(`list-tickle`, { item: id });
|
|
19618
|
+
},
|
|
19619
|
+
onStateChange: () => this._syncItemStates(),
|
|
19620
|
+
itemWrap: this.#itemWrap
|
|
19621
|
+
});
|
|
19622
|
+
this.#onScroll = (e) => {
|
|
19623
|
+
const el = e.target;
|
|
19624
|
+
this.#cancelScrollRaf();
|
|
19625
|
+
this.#scrollRaf = requestAnimationFrame(() => {
|
|
19626
|
+
this.#scrollRaf = 0;
|
|
19627
|
+
this._scrollTop = el.scrollTop;
|
|
19628
|
+
this._scrollLeft = el.scrollLeft;
|
|
19629
|
+
this.#emitViewportChange();
|
|
19630
|
+
});
|
|
19631
|
+
};
|
|
19632
|
+
this._onKeyDown = (e) => {
|
|
19633
|
+
if ((e.ctrlKey || e.metaKey) && e.key === `f`) return;
|
|
19634
|
+
if ((e.ctrlKey || e.metaKey) && e.key === `a`) {
|
|
19635
|
+
if (this.selection.canSelectAll()) {
|
|
19636
|
+
e.preventDefault();
|
|
19637
|
+
this.selection.selectAll();
|
|
19638
|
+
}
|
|
19639
|
+
return;
|
|
19640
|
+
}
|
|
19641
|
+
const count = this.#items.length;
|
|
19642
|
+
if (count === 0) return;
|
|
19643
|
+
const win = this.#currentWindow();
|
|
19644
|
+
const move = (delta) => {
|
|
19645
|
+
const from = this.tickled.tickledId ? this.#items.indexOf(this.tickled.tickledId) : -1;
|
|
19646
|
+
const nextIndex = Math.max(0, Math.min(count - 1, from + delta));
|
|
19647
|
+
const next = this.#items[nextIndex];
|
|
19648
|
+
if (next) this.tickled.handleKeyboardNavigation(next);
|
|
19649
|
+
};
|
|
19650
|
+
const perLine = this.orientation === `grid` ? win.cols : 1;
|
|
19651
|
+
switch (e.key) {
|
|
19652
|
+
case `ArrowLeft`:
|
|
19653
|
+
move(-1);
|
|
19654
|
+
break;
|
|
19655
|
+
case `ArrowRight`:
|
|
19656
|
+
move(1);
|
|
19657
|
+
break;
|
|
19658
|
+
case `ArrowUp`:
|
|
19659
|
+
move(-perLine);
|
|
19660
|
+
break;
|
|
19661
|
+
case `ArrowDown`:
|
|
19662
|
+
move(perLine);
|
|
19663
|
+
break;
|
|
19664
|
+
case `Home`:
|
|
19665
|
+
this.tickled.handleKeyboardNavigation(`first`);
|
|
19666
|
+
break;
|
|
19667
|
+
case `End`:
|
|
19668
|
+
this.tickled.handleKeyboardNavigation(`last`);
|
|
19669
|
+
break;
|
|
19670
|
+
case `PageUp`:
|
|
19671
|
+
case `PageDown`: {
|
|
19672
|
+
const mainExtent = win.horizontal ? this._viewportWidth : this._viewportHeight;
|
|
19673
|
+
const mainStride = win.horizontal ? win.colStride : win.rowStride;
|
|
19674
|
+
const visibleLines = Math.max(1, Math.floor(mainExtent / mainStride));
|
|
19675
|
+
move((e.key === `PageDown` ? 1 : -1) * visibleLines * perLine);
|
|
19676
|
+
break;
|
|
19677
|
+
}
|
|
19678
|
+
case `Enter`: {
|
|
19679
|
+
const id = this.tickled.tickledId;
|
|
19680
|
+
if (id) {
|
|
19681
|
+
e.preventDefault();
|
|
19682
|
+
this._dispatch(`list-item-click`, { item: id });
|
|
19683
|
+
this.#onCellActivate(id);
|
|
19684
|
+
}
|
|
19685
|
+
break;
|
|
19686
|
+
}
|
|
19687
|
+
case ` `: {
|
|
19688
|
+
const id = this.tickled.tickledId;
|
|
19689
|
+
if (id) {
|
|
19690
|
+
e.preventDefault();
|
|
19691
|
+
this.selection.handleSpace(id);
|
|
19692
|
+
}
|
|
19693
|
+
break;
|
|
19694
|
+
}
|
|
19695
|
+
case `Escape`:
|
|
19696
|
+
this.selection.clear();
|
|
19697
|
+
break;
|
|
19698
|
+
default: return;
|
|
19699
|
+
}
|
|
19700
|
+
e.preventDefault();
|
|
19701
|
+
};
|
|
19702
|
+
}
|
|
19703
|
+
static {
|
|
19704
|
+
this.styles = [
|
|
19705
|
+
themeFallbacks,
|
|
19706
|
+
tickledStyles,
|
|
19707
|
+
tickledItemStyles,
|
|
19708
|
+
scrollFadeStyles(`x`),
|
|
19709
|
+
scrollFadeStyles(`y`),
|
|
19710
|
+
listItemFadeKeyframes,
|
|
19711
|
+
i$3`
|
|
19712
|
+
:host {
|
|
19713
|
+
--grid-list-item-bg: var(--item-bg);
|
|
19714
|
+
--grid-list-radius: var(--radius-m);
|
|
19715
|
+
--grid-list-gap: var(--space-xs);
|
|
19716
|
+
display: block;
|
|
19717
|
+
height: 100%;
|
|
19718
|
+
min-height: 2em;
|
|
19719
|
+
overflow: hidden;
|
|
19720
|
+
user-select: none;
|
|
19721
|
+
background: var(--surface-3);
|
|
19722
|
+
color: var(--surface-3-text);
|
|
19723
|
+
border-radius: var(--radius-s);
|
|
19724
|
+
border: 1px solid var(--border);
|
|
19725
|
+
}
|
|
19726
|
+
:host(:focus-visible) {
|
|
19727
|
+
outline: 2px solid var(--accent);
|
|
19728
|
+
outline-offset: -2px;
|
|
19729
|
+
}
|
|
19730
|
+
.viewport {
|
|
19731
|
+
position: relative;
|
|
19732
|
+
height: 100%;
|
|
19733
|
+
overflow-y: auto;
|
|
19734
|
+
overflow-x: hidden;
|
|
19735
|
+
}
|
|
19736
|
+
:host([orientation="row"]) .viewport {
|
|
19737
|
+
overflow-x: auto;
|
|
19738
|
+
overflow-y: hidden;
|
|
19739
|
+
}
|
|
19740
|
+
.spacer {
|
|
19741
|
+
position: relative;
|
|
19742
|
+
width: 100%;
|
|
19743
|
+
}
|
|
19744
|
+
.cell {
|
|
19745
|
+
position: absolute;
|
|
19746
|
+
inset: 0 auto auto 0;
|
|
19747
|
+
box-sizing: border-box;
|
|
19748
|
+
display: flex;
|
|
19749
|
+
flex-direction: column;
|
|
19750
|
+
align-items: stretch;
|
|
19751
|
+
contain: layout style;
|
|
19752
|
+
border-radius: var(--grid-list-radius);
|
|
19753
|
+
}
|
|
19754
|
+
|
|
19755
|
+
/* The square that holds the slotted content. */
|
|
19756
|
+
.thumb {
|
|
19757
|
+
position: relative;
|
|
19758
|
+
flex: 1;
|
|
19759
|
+
min-height: 0;
|
|
19760
|
+
display: flex;
|
|
19761
|
+
align-items: stretch;
|
|
19762
|
+
box-sizing: border-box;
|
|
19763
|
+
overflow: hidden;
|
|
19764
|
+
border-radius: 0;
|
|
19765
|
+
background: transparent;
|
|
19766
|
+
border: 1px solid transparent;
|
|
19767
|
+
transition: background var(--transition), border-color var(--transition);
|
|
19768
|
+
}
|
|
19769
|
+
.thumb ::slotted(*) {
|
|
19770
|
+
flex: 1;
|
|
19771
|
+
min-width: 0;
|
|
19772
|
+
min-height: 0;
|
|
19773
|
+
width: 100%;
|
|
19774
|
+
display: flex;
|
|
19775
|
+
align-items: center;
|
|
19776
|
+
justify-content: center;
|
|
19777
|
+
overflow: hidden;
|
|
19778
|
+
}
|
|
19779
|
+
.thumb ::slotted(*:empty)::after {
|
|
19780
|
+
content: "Empty";
|
|
19781
|
+
color: var(--surface-muted-text);
|
|
19782
|
+
font-size: var(--text-s);
|
|
19783
|
+
}
|
|
19784
|
+
::slotted([hidden]) {
|
|
19785
|
+
display: none !important;
|
|
19786
|
+
}
|
|
19787
|
+
|
|
19788
|
+
/* Selection / cursor wash — sits over the slotted content so it reads
|
|
19789
|
+
on photos and arbitrary DOM alike. */
|
|
19790
|
+
.thumb::after {
|
|
19791
|
+
content: "";
|
|
19792
|
+
position: absolute;
|
|
19793
|
+
inset: 0;
|
|
19794
|
+
pointer-events: none;
|
|
19795
|
+
border-radius: inherit;
|
|
19796
|
+
background: var(--accent);
|
|
19797
|
+
opacity: 0;
|
|
19798
|
+
transition: opacity var(--transition, 0.1s ease-out);
|
|
19799
|
+
}
|
|
19800
|
+
.cell.selected .thumb::after {
|
|
19801
|
+
opacity: 0.18;
|
|
19802
|
+
}
|
|
19803
|
+
|
|
19804
|
+
/* ---- Caption (base) ---- */
|
|
19805
|
+
.caption {
|
|
19806
|
+
box-sizing: border-box;
|
|
19807
|
+
flex: none;
|
|
19808
|
+
padding: 3px 6px;
|
|
19809
|
+
font-size: var(--text-s, 0.8rem);
|
|
19810
|
+
line-height: 1.3;
|
|
19811
|
+
text-align: center;
|
|
19812
|
+
white-space: nowrap;
|
|
19813
|
+
overflow: hidden;
|
|
19814
|
+
text-overflow: ellipsis;
|
|
19815
|
+
color: var(--surface-3-text);
|
|
19816
|
+
}
|
|
19817
|
+
:host([caption-align="left"]) .caption { text-align: left; }
|
|
19818
|
+
:host([caption-align="right"]) .caption { text-align: right; }
|
|
19819
|
+
|
|
19820
|
+
/* ---- Box modes (hidden / under / overlaid): the cell is one box with a
|
|
19821
|
+
single selection outline ---- */
|
|
19822
|
+
:host(:not([caption-style="separate"])) .cell {
|
|
19823
|
+
background: var(--grid-list-item-bg);
|
|
19824
|
+
border: 1px solid transparent;
|
|
19825
|
+
overflow: hidden;
|
|
19826
|
+
transition: background var(--transition), border-color var(--transition);
|
|
19827
|
+
}
|
|
19828
|
+
:host(:not([caption-style="separate"])) .cell:hover {
|
|
19829
|
+
background: var(--item-bg-tickled);
|
|
19830
|
+
border-color: var(--item-border-tickled);
|
|
19831
|
+
}
|
|
19832
|
+
:host(:not([caption-style="separate"])) .cell.selected {
|
|
19833
|
+
outline: 2px solid var(--accent);
|
|
19834
|
+
outline-offset: -2px;
|
|
19835
|
+
}
|
|
19836
|
+
:host(:not([caption-style="separate"])) .cell.tickled {
|
|
19837
|
+
box-shadow: inset 0 0 0 2px var(--accent);
|
|
19838
|
+
}
|
|
19839
|
+
|
|
19840
|
+
/* under: caption is a strip inside the box, below the content */
|
|
19841
|
+
:host([caption-style="under"]) .caption {
|
|
19842
|
+
border-top: 1px solid var(--border, rgba(128, 128, 128, 0.3));
|
|
19843
|
+
background: var(--surface-4, transparent);
|
|
19844
|
+
}
|
|
19845
|
+
|
|
19846
|
+
/* overlaid: caption floats over the content */
|
|
19847
|
+
:host([caption-style="overlaid"]) .caption {
|
|
19848
|
+
position: absolute;
|
|
19849
|
+
left: 0;
|
|
19850
|
+
right: 0;
|
|
19851
|
+
bottom: 0;
|
|
19852
|
+
color: #fff;
|
|
19853
|
+
}
|
|
19854
|
+
:host([caption-style="overlaid"][caption-position="top"]) .caption {
|
|
19855
|
+
top: 0;
|
|
19856
|
+
bottom: auto;
|
|
19857
|
+
}
|
|
19858
|
+
:host([caption-style="overlaid"][caption-position="middle"]) .caption {
|
|
19859
|
+
top: 50%;
|
|
19860
|
+
bottom: auto;
|
|
19861
|
+
transform: translateY(-50%);
|
|
19862
|
+
}
|
|
19863
|
+
/* overlaid backdrop */
|
|
19864
|
+
:host([caption-style="overlaid"][caption-backdrop="scrim"]) .caption {
|
|
19865
|
+
background: rgba(0, 0, 0, 0.55);
|
|
19866
|
+
}
|
|
19867
|
+
:host([caption-style="overlaid"][caption-backdrop="gradient"]) .caption {
|
|
19868
|
+
background: linear-gradient(to top, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0));
|
|
19869
|
+
padding-block: 10px 4px;
|
|
19870
|
+
}
|
|
19871
|
+
:host([caption-style="overlaid"][caption-backdrop="gradient"][caption-position="top"]) .caption {
|
|
19872
|
+
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0));
|
|
19873
|
+
padding-block: 4px 10px;
|
|
19874
|
+
}
|
|
19875
|
+
:host([caption-style="overlaid"][caption-backdrop="gradient"][caption-position="middle"]) .caption {
|
|
19876
|
+
background: rgba(0, 0, 0, 0.5);
|
|
19877
|
+
padding-block: 3px;
|
|
19878
|
+
}
|
|
19879
|
+
:host([caption-style="overlaid"][caption-backdrop="blur"]) .caption {
|
|
19880
|
+
background: rgba(20, 20, 20, 0.3);
|
|
19881
|
+
backdrop-filter: blur(6px);
|
|
19882
|
+
-webkit-backdrop-filter: blur(6px);
|
|
19883
|
+
}
|
|
19884
|
+
:host([caption-style="overlaid"][caption-backdrop="none"]) .caption {
|
|
19885
|
+
background: none;
|
|
19886
|
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 0 6px rgba(0, 0, 0, 0.7);
|
|
19887
|
+
}
|
|
19888
|
+
|
|
19889
|
+
/* ---- separate mode: square + detached caption, each highlighted ---- */
|
|
19890
|
+
:host([caption-style="separate"]) .thumb {
|
|
19891
|
+
flex: none;
|
|
19892
|
+
border-radius: var(--grid-list-radius);
|
|
19893
|
+
background: var(--grid-list-item-bg);
|
|
19894
|
+
border: 1px solid transparent;
|
|
19895
|
+
}
|
|
19896
|
+
:host([caption-style="separate"]) .cell:hover .thumb {
|
|
19897
|
+
background: var(--item-bg-tickled);
|
|
19898
|
+
border-color: var(--item-border-tickled);
|
|
19899
|
+
}
|
|
19900
|
+
:host([caption-style="separate"]) .caption {
|
|
19901
|
+
display: flex;
|
|
19902
|
+
align-items: center;
|
|
19903
|
+
align-self: center;
|
|
19904
|
+
max-width: 100%;
|
|
19905
|
+
margin-top: 4px;
|
|
19906
|
+
border-radius: var(--radius-s, 4px);
|
|
19907
|
+
}
|
|
19908
|
+
:host([caption-style="separate"][caption-align="left"]) .caption { align-self: flex-start; }
|
|
19909
|
+
:host([caption-style="separate"][caption-align="right"]) .caption { align-self: flex-end; }
|
|
19910
|
+
:host([caption-style="separate"]) .cell.selected .thumb,
|
|
19911
|
+
:host([caption-style="separate"]) .cell.tickled .thumb {
|
|
19912
|
+
outline: 2px solid var(--accent);
|
|
19913
|
+
outline-offset: 2px;
|
|
19914
|
+
}
|
|
19915
|
+
:host([caption-style="separate"]) .cell.tickled .thumb {
|
|
19916
|
+
outline-style: dashed;
|
|
19917
|
+
}
|
|
19918
|
+
:host([caption-style="separate"]) .cell.selected .caption {
|
|
19919
|
+
background: var(--accent);
|
|
19920
|
+
color: var(--accent-text, #000);
|
|
19921
|
+
}
|
|
19922
|
+
:host([caption-style="separate"]) .cell.tickled .caption {
|
|
19923
|
+
outline: 1px dashed var(--accent);
|
|
19924
|
+
}
|
|
19925
|
+
|
|
19926
|
+
/* ---- Insertion / removal animation ----
|
|
19927
|
+
Active only while the host carries [reflowing] — the ~0.3s window after
|
|
19928
|
+
the child list changes mid-list (see #noteChildMutation). Kept out of
|
|
19929
|
+
the base rules so it never fires during scroll virtualisation, where
|
|
19930
|
+
cells are recycled to wildly different positions every frame. Placed
|
|
19931
|
+
last so it wins over the earlier cell transition rule regardless of
|
|
19932
|
+
specificity. Survivors slide to their new slot; a freshly inserted cell
|
|
19933
|
+
also fades in. (A removed item's element is already gone, so there's
|
|
19934
|
+
nothing left to fade — the gap simply closes as the others slide.) */
|
|
19935
|
+
:host([reflowing]) .cell {
|
|
19936
|
+
transition:
|
|
19937
|
+
transform var(--grid-list-item-move-duration, 200ms) cubic-bezier(0.2, 0, 0, 1),
|
|
19938
|
+
background var(--transition),
|
|
19939
|
+
border-color var(--transition);
|
|
19940
|
+
}
|
|
19941
|
+
.cell.entering {
|
|
19942
|
+
animation: ixfx-list-item-in var(--grid-list-item-move-duration, 200ms) ease both;
|
|
19943
|
+
}
|
|
19944
|
+
@media (prefers-reduced-motion: reduce) {
|
|
19945
|
+
:host([reflowing]) .cell { transition: background var(--transition), border-color var(--transition); }
|
|
19946
|
+
.cell.entering { animation: none; }
|
|
19947
|
+
}
|
|
19948
|
+
`
|
|
19949
|
+
];
|
|
19950
|
+
}
|
|
19951
|
+
#viewportRef;
|
|
19952
|
+
#resizeObserver;
|
|
19953
|
+
#mutationObserver;
|
|
19954
|
+
#scrollRaf;
|
|
19955
|
+
#items;
|
|
19956
|
+
#itemWrap;
|
|
19957
|
+
/** Items added mid-list that are still playing their enter animation. */
|
|
19958
|
+
#entering;
|
|
19959
|
+
/** Timer that ends the `[reflowing]` window opened by a mid-list insertion. */
|
|
19960
|
+
#reflowTimer;
|
|
19961
|
+
/**
|
|
19962
|
+
* When cell geometry changes (zoom), the item nearest the viewport centre is
|
|
19963
|
+
* captured here in `willUpdate` and the scroll position is corrected in
|
|
19964
|
+
* `updated` so that item stays put — rather than the grid anchoring to its
|
|
19965
|
+
* top-left corner.
|
|
19966
|
+
*/
|
|
19967
|
+
#zoomAnchor;
|
|
19968
|
+
/**
|
|
19969
|
+
* Per-item slot name. Each cell renders its own named `<slot>` and the
|
|
19970
|
+
* matching light-DOM child carries `slot="<name>"`, so a child is projected
|
|
19971
|
+
* into exactly one cell. (A single unnamed slot would collapse every child
|
|
19972
|
+
* into the first cell.)
|
|
19973
|
+
*/
|
|
19974
|
+
#slotNames;
|
|
19975
|
+
#slotSeq;
|
|
19976
|
+
connectedCallback() {
|
|
19977
|
+
super.connectedCallback();
|
|
19978
|
+
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
19979
|
+
for (const [attr, value] of [
|
|
19980
|
+
[`caption-style`, this.captionStyle],
|
|
19981
|
+
[`caption-align`, this.captionAlign],
|
|
19982
|
+
[`caption-position`, this.captionPosition],
|
|
19983
|
+
[`caption-backdrop`, this.captionBackdrop]
|
|
19984
|
+
]) if (!this.hasAttribute(attr)) this.setAttribute(attr, value);
|
|
19985
|
+
this.addEventListener(`keydown`, this._onKeyDown);
|
|
19986
|
+
this.#mutationObserver ??= new MutationObserver((records) => this.#onChildrenChanged(records));
|
|
19987
|
+
this.#mutationObserver.observe(this, { childList: true });
|
|
19988
|
+
}
|
|
19989
|
+
disconnectedCallback() {
|
|
19990
|
+
super.disconnectedCallback();
|
|
19991
|
+
this.removeEventListener(`keydown`, this._onKeyDown);
|
|
19992
|
+
this.#resizeObserver?.disconnect();
|
|
19993
|
+
this.#resizeObserver = void 0;
|
|
19994
|
+
this.#mutationObserver?.disconnect();
|
|
19995
|
+
this.#cancelScrollRaf();
|
|
19996
|
+
if (this.#reflowTimer) {
|
|
19997
|
+
clearTimeout(this.#reflowTimer);
|
|
19998
|
+
this.#reflowTimer = 0;
|
|
19999
|
+
}
|
|
20000
|
+
}
|
|
20001
|
+
#onChildrenChanged(records) {
|
|
20002
|
+
const previousLength = this.#items.length;
|
|
20003
|
+
this.#syncItems();
|
|
20004
|
+
this._syncItemStates();
|
|
20005
|
+
this.#noteChildMutation(previousLength, records);
|
|
20006
|
+
this.requestUpdate();
|
|
20007
|
+
this.updateComplete.then(() => this.#emitViewportChange());
|
|
20008
|
+
}
|
|
20009
|
+
/**
|
|
20010
|
+
* When the child list changes mid-list — not a first populate or a wholesale
|
|
20011
|
+
* replace — mark any newcomers for an enter animation and open a short
|
|
20012
|
+
* `[reflowing]` window so the cells that shift slide to their new position
|
|
20013
|
+
* instead of snapping. Removals get the same window (the survivors slide to
|
|
20014
|
+
* close the gap). The window is bounded in time so the transform transition
|
|
20015
|
+
* never bleeds into scroll virtualisation.
|
|
20016
|
+
*
|
|
20017
|
+
* Reads the `MutationRecord`s directly rather than diffing `#items`, because a
|
|
20018
|
+
* `requestUpdate()` between the DOM change and this callback (e.g. a caller
|
|
20019
|
+
* that clears the selection before removing) lets `updated()` re-sync `#items`
|
|
20020
|
+
* first, which would hide the change from a naive diff.
|
|
20021
|
+
*/
|
|
20022
|
+
#noteChildMutation(previousLength, records) {
|
|
20023
|
+
if (!this.hasUpdated || !records || records.length === 0) return;
|
|
20024
|
+
const currentSet = new Set(this.#items);
|
|
20025
|
+
const added = [];
|
|
20026
|
+
let removedCount = 0;
|
|
20027
|
+
for (const record of records) {
|
|
20028
|
+
for (const node of record.addedNodes) if (node.nodeType === Node.ELEMENT_NODE && currentSet.has(node)) added.push(node);
|
|
20029
|
+
for (const node of record.removedNodes) if (node.nodeType === Node.ELEMENT_NODE) removedCount++;
|
|
20030
|
+
}
|
|
20031
|
+
if (added.length === 0 && removedCount === 0) return;
|
|
20032
|
+
if (previousLength === 0 && removedCount === 0) return;
|
|
20033
|
+
if (added.length > 0 && added.length === this.#items.length) return;
|
|
20034
|
+
for (const el of added) this.#entering.add(el);
|
|
20035
|
+
this.toggleAttribute(`reflowing`, true);
|
|
20036
|
+
if (this.#reflowTimer) clearTimeout(this.#reflowTimer);
|
|
20037
|
+
this.#reflowTimer = window.setTimeout(() => {
|
|
20038
|
+
this.#reflowTimer = 0;
|
|
20039
|
+
this.#entering.clear();
|
|
20040
|
+
this.toggleAttribute(`reflowing`, false);
|
|
20041
|
+
this.requestUpdate();
|
|
20042
|
+
}, REFLOW_WINDOW_MS);
|
|
20043
|
+
}
|
|
20044
|
+
#emitViewportChange() {
|
|
20045
|
+
this._dispatch(`grid-list-viewport-change`, { visibleKeys: this.visibleKeys() });
|
|
20046
|
+
}
|
|
20047
|
+
firstUpdated() {
|
|
20048
|
+
const el = this.#viewportRef.value;
|
|
20049
|
+
if (!el) return;
|
|
20050
|
+
this._viewportWidth = el.clientWidth;
|
|
20051
|
+
this._viewportHeight = el.clientHeight;
|
|
20052
|
+
this.#resizeObserver = new ResizeObserver((entries) => {
|
|
20053
|
+
for (const entry of entries) {
|
|
20054
|
+
this._viewportWidth = entry.contentRect.width;
|
|
20055
|
+
this._viewportHeight = entry.contentRect.height;
|
|
20056
|
+
}
|
|
20057
|
+
});
|
|
20058
|
+
this.#resizeObserver.observe(el);
|
|
20059
|
+
this.#syncItems();
|
|
20060
|
+
this.requestUpdate();
|
|
20061
|
+
}
|
|
20062
|
+
willUpdate(changed) {
|
|
20063
|
+
if (changed.has(`itemSize`) || changed.has(`gap`)) {
|
|
20064
|
+
this.itemSize = Math.max(MIN_ITEM_SIZE, Math.round(this.itemSize));
|
|
20065
|
+
this.gap = Math.max(0, Math.round(this.gap));
|
|
20066
|
+
}
|
|
20067
|
+
if (changed.has(`captionHeight`)) this.captionHeight = Math.max(0, Math.round(this.captionHeight));
|
|
20068
|
+
if (changed.has(`itemWrap`)) this.#itemWrap = this.itemWrap;
|
|
20069
|
+
if ((changed.has(`itemSize`) || changed.has(`gap`) || changed.has(`captionStyle`) || changed.has(`captionHeight`) || changed.has(`orientation`)) && this.hasUpdated && this.#items.length > 0) {
|
|
20070
|
+
const prevOrientation = changed.get(`orientation`) ?? this.orientation;
|
|
20071
|
+
const horizontal = prevOrientation === `row`;
|
|
20072
|
+
const prevOffset = horizontal ? this._scrollLeft : this._scrollTop;
|
|
20073
|
+
const prevExtent = horizontal ? this._viewportWidth : this._viewportHeight;
|
|
20074
|
+
if (prevExtent > 0 && prevOffset > 1) {
|
|
20075
|
+
const prevItemSize = changed.get(`itemSize`) ?? this.itemSize;
|
|
20076
|
+
const prevGap = changed.get(`gap`) ?? this.gap;
|
|
20077
|
+
const prevCaptionStyle = changed.get(`captionStyle`) ?? this.captionStyle;
|
|
20078
|
+
const prevCaptionHeight = changed.get(`captionHeight`) ?? this.captionHeight;
|
|
20079
|
+
const prevRowHeight = prevItemSize + (prevCaptionStyle === `separate` || prevCaptionStyle === `under` ? prevCaptionHeight : 0);
|
|
20080
|
+
const prev = computeGridWindow(this.#items.length, prevItemSize, prevRowHeight, prevGap, this._viewportWidth, this._viewportHeight, prevOffset, prevOrientation);
|
|
20081
|
+
const mainStride = horizontal ? prev.colStride : prev.rowStride;
|
|
20082
|
+
const centerLine = (prevOffset + prevExtent / 2) / mainStride;
|
|
20083
|
+
const lineFloor = Math.max(0, Math.floor(centerLine));
|
|
20084
|
+
const index = horizontal ? Math.min(this.#items.length - 1, lineFloor) : Math.min(this.#items.length - 1, lineFloor * prev.cols + Math.max(0, Math.floor((prev.cols - 1) / 2)));
|
|
20085
|
+
this.#zoomAnchor = {
|
|
20086
|
+
index,
|
|
20087
|
+
lineFrac: centerLine - lineFloor
|
|
20088
|
+
};
|
|
20089
|
+
}
|
|
20090
|
+
}
|
|
20091
|
+
}
|
|
20092
|
+
/** Full cell height: the square, plus a caption band in `separate` / `under`. */
|
|
20093
|
+
#rowHeight() {
|
|
20094
|
+
const captionBand = this.captionStyle === `separate` || this.captionStyle === `under`;
|
|
20095
|
+
return this.itemSize + (captionBand ? this.captionHeight : 0);
|
|
20096
|
+
}
|
|
20097
|
+
#captionFor(el) {
|
|
20098
|
+
return el.getAttribute(`data-label`) ?? el.querySelector(`[data-grid-list-caption]`)?.textContent?.trim() ?? ``;
|
|
20099
|
+
}
|
|
20100
|
+
updated(changed) {
|
|
20101
|
+
super.updated(changed);
|
|
20102
|
+
this.#syncItems();
|
|
20103
|
+
if (changed.has(`selectionMode`) || changed.has(`interactionMode`)) this.selection.clearAnchor();
|
|
20104
|
+
if (changed.has(`orientation`)) {
|
|
20105
|
+
const vp = this.#viewportRef.value;
|
|
20106
|
+
if (vp) {
|
|
20107
|
+
if (this.orientation === `row`) {
|
|
20108
|
+
vp.scrollTop = 0;
|
|
20109
|
+
this._scrollTop = 0;
|
|
20110
|
+
} else {
|
|
20111
|
+
vp.scrollLeft = 0;
|
|
20112
|
+
this._scrollLeft = 0;
|
|
20113
|
+
}
|
|
20114
|
+
}
|
|
20115
|
+
}
|
|
20116
|
+
this.#applyZoomAnchor();
|
|
20117
|
+
}
|
|
20118
|
+
/** Re-anchor the scroll so the item captured in `willUpdate` stays centred. */
|
|
20119
|
+
#applyZoomAnchor() {
|
|
20120
|
+
if (!this.#zoomAnchor) return;
|
|
20121
|
+
const { index, lineFrac } = this.#zoomAnchor;
|
|
20122
|
+
this.#zoomAnchor = void 0;
|
|
20123
|
+
const vp = this.#viewportRef.value;
|
|
20124
|
+
if (!vp) return;
|
|
20125
|
+
const win = this.#currentWindow();
|
|
20126
|
+
if (win.horizontal) {
|
|
20127
|
+
if (this._viewportWidth <= 0) return;
|
|
20128
|
+
const targetCenter = (index + lineFrac) * win.colStride;
|
|
20129
|
+
const maxScroll = Math.max(0, win.totalWidth - this._viewportWidth);
|
|
20130
|
+
const next = Math.min(maxScroll, Math.max(0, targetCenter - this._viewportWidth / 2));
|
|
20131
|
+
if (Math.abs(vp.scrollLeft - next) > .5) {
|
|
20132
|
+
vp.scrollLeft = next;
|
|
20133
|
+
this._scrollLeft = next;
|
|
20134
|
+
}
|
|
20135
|
+
return;
|
|
20136
|
+
}
|
|
20137
|
+
if (this._viewportHeight <= 0) return;
|
|
20138
|
+
const targetCenter = ((win.cols > 0 ? Math.floor(index / win.cols) : 0) + lineFrac) * win.rowStride;
|
|
20139
|
+
const maxScroll = Math.max(0, win.totalHeight - this._viewportHeight);
|
|
20140
|
+
const next = Math.min(maxScroll, Math.max(0, targetCenter - this._viewportHeight / 2));
|
|
20141
|
+
if (Math.abs(vp.scrollTop - next) > .5) {
|
|
20142
|
+
vp.scrollTop = next;
|
|
20143
|
+
this._scrollTop = next;
|
|
20144
|
+
}
|
|
20145
|
+
}
|
|
20146
|
+
#syncItems() {
|
|
20147
|
+
this.#items = Array.from(this.querySelectorAll(ITEM_SELECTOR$1));
|
|
20148
|
+
for (const el of this.#items) {
|
|
20149
|
+
const name = this.#slotNameFor(el);
|
|
20150
|
+
if (el.getAttribute(`slot`) !== name) el.setAttribute(`slot`, name);
|
|
20151
|
+
}
|
|
20152
|
+
}
|
|
20153
|
+
#slotNameFor(el) {
|
|
20154
|
+
let name = this.#slotNames.get(el);
|
|
20155
|
+
if (name === void 0) {
|
|
20156
|
+
this.#slotSeq += 1;
|
|
20157
|
+
name = `gl-item-${this.#slotSeq}`;
|
|
20158
|
+
this.#slotNames.set(el, name);
|
|
20159
|
+
}
|
|
20160
|
+
return name;
|
|
20161
|
+
}
|
|
20162
|
+
_getVisibleItems() {
|
|
20163
|
+
return this.#items.filter((el) => {
|
|
20164
|
+
if (el.hidden) return false;
|
|
20165
|
+
return true;
|
|
20166
|
+
});
|
|
20167
|
+
}
|
|
20168
|
+
get count() {
|
|
20169
|
+
return this.#items.length;
|
|
20170
|
+
}
|
|
20171
|
+
/**
|
|
20172
|
+
* Number of cells currently rendered in the shadow DOM. With virtualization
|
|
20173
|
+
* this is roughly the visible rows plus overscan, regardless of how large
|
|
20174
|
+
* `count` is — useful for demos/diagnostics.
|
|
20175
|
+
*/
|
|
20176
|
+
get renderedCount() {
|
|
20177
|
+
return this.renderRoot.querySelectorAll(`.cell`).length;
|
|
20178
|
+
}
|
|
20179
|
+
get selectedItems() {
|
|
20180
|
+
return this.selection.selected;
|
|
20181
|
+
}
|
|
20182
|
+
/** The selection as stable `data-key` / `data-value` strings. */
|
|
20183
|
+
get selectedKeys() {
|
|
20184
|
+
return this.selection.selectedKeys;
|
|
20185
|
+
}
|
|
20186
|
+
select(item) {
|
|
20187
|
+
this.selection.replace(item);
|
|
20188
|
+
}
|
|
20189
|
+
selectMany(items) {
|
|
20190
|
+
this.selection.selectMany(items);
|
|
20191
|
+
}
|
|
20192
|
+
deselect(item) {
|
|
20193
|
+
this.selection.deselect(item);
|
|
20194
|
+
}
|
|
20195
|
+
clearSelection() {
|
|
20196
|
+
this.selection.clear();
|
|
20197
|
+
}
|
|
20198
|
+
selectAll() {
|
|
20199
|
+
if (this.selection.canSelectAll()) this.selection.selectAll();
|
|
20200
|
+
}
|
|
20201
|
+
/**
|
|
20202
|
+
* Replace the selection with the items carrying these keys. Unknown keys are
|
|
20203
|
+
* ignored; survives the host rebuilding the item set. Requires each item to
|
|
20204
|
+
* have a `data-key` (or `data-value`).
|
|
20205
|
+
*/
|
|
20206
|
+
selectKeys(keys) {
|
|
20207
|
+
this.selection.selectKeys(keys, (key) => this.getItem(key));
|
|
20208
|
+
}
|
|
20209
|
+
/** Add items by key to the selection (multi-select modes only). */
|
|
20210
|
+
addSelectedKeys(keys) {
|
|
20211
|
+
this.selection.addKeys(keys, (key) => this.getItem(key));
|
|
20212
|
+
}
|
|
20213
|
+
/** Remove items by key from the selection. */
|
|
20214
|
+
removeSelectedKeys(keys) {
|
|
20215
|
+
this.selection.removeKeys(keys, (key) => this.getItem(key));
|
|
20216
|
+
}
|
|
20217
|
+
/** Move the cursor to the item with this key and scroll it into view. */
|
|
20218
|
+
focusKey(key) {
|
|
20219
|
+
const el = this.getItem(key);
|
|
20220
|
+
if (!el) return;
|
|
20221
|
+
this.tickled.focus(el);
|
|
20222
|
+
this.#scrollItemIntoView(el);
|
|
20223
|
+
}
|
|
20224
|
+
/** The key of the item under the cursor, or `undefined`. */
|
|
20225
|
+
get tickledKey() {
|
|
20226
|
+
const id = this.tickled.tickledId;
|
|
20227
|
+
return id ? keyForElement(id) : void 0;
|
|
20228
|
+
}
|
|
20229
|
+
getItem(key) {
|
|
20230
|
+
return this.#items.find((el) => keyForElement(el) === key);
|
|
20231
|
+
}
|
|
20232
|
+
getItemByIndex(index) {
|
|
20233
|
+
return this.#items[index];
|
|
20234
|
+
}
|
|
20235
|
+
getKey(item) {
|
|
20236
|
+
return keyForElement(item);
|
|
20237
|
+
}
|
|
20238
|
+
visibleKeys() {
|
|
20239
|
+
const win = this.#currentWindow();
|
|
20240
|
+
return this.#items.slice(win.startIndex, win.endIndex).map((el) => keyForElement(el)).filter((k) => k !== void 0);
|
|
20241
|
+
}
|
|
20242
|
+
/** Adjust the scroll offset so `el` is fully within the viewport. */
|
|
20243
|
+
#scrollItemIntoView(el) {
|
|
20244
|
+
const index = this.#items.indexOf(el);
|
|
20245
|
+
const vp = this.#viewportRef.value;
|
|
20246
|
+
if (index < 0 || !vp) return;
|
|
20247
|
+
const win = this.#currentWindow();
|
|
20248
|
+
if (win.horizontal) {
|
|
20249
|
+
const left = index * win.colStride;
|
|
20250
|
+
const right = left + this.itemSize;
|
|
20251
|
+
const viewLeft = vp.scrollLeft;
|
|
20252
|
+
let next = viewLeft;
|
|
20253
|
+
if (left < viewLeft) next = left;
|
|
20254
|
+
else if (right > viewLeft + this._viewportWidth) next = right - this._viewportWidth;
|
|
20255
|
+
if (next !== viewLeft) {
|
|
20256
|
+
vp.scrollLeft = next;
|
|
20257
|
+
this._scrollLeft = next;
|
|
20258
|
+
}
|
|
20259
|
+
return;
|
|
20260
|
+
}
|
|
20261
|
+
const top = (win.cols > 0 ? Math.floor(index / win.cols) : 0) * win.rowStride;
|
|
20262
|
+
const bottom = top + this.#rowHeight();
|
|
20263
|
+
const viewTop = vp.scrollTop;
|
|
20264
|
+
let next = viewTop;
|
|
20265
|
+
if (top < viewTop) next = top;
|
|
20266
|
+
else if (bottom > viewTop + this._viewportHeight) next = bottom - this._viewportHeight;
|
|
20267
|
+
if (next !== viewTop) {
|
|
20268
|
+
vp.scrollTop = next;
|
|
20269
|
+
this._scrollTop = next;
|
|
20270
|
+
}
|
|
20271
|
+
}
|
|
20272
|
+
_syncItemStates() {
|
|
20273
|
+
for (const el of this.#items) {
|
|
20274
|
+
el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));
|
|
20275
|
+
el.toggleAttribute(`data-selected`, this.selection.selected.has(el));
|
|
20276
|
+
}
|
|
20277
|
+
this.setAttribute(`data-input-mode`, this.tickled.inputMode);
|
|
20278
|
+
}
|
|
20279
|
+
_dispatch(name, detail) {
|
|
20280
|
+
this.dispatchEvent(new CustomEvent(name, {
|
|
20281
|
+
detail,
|
|
20282
|
+
bubbles: true,
|
|
20283
|
+
composed: true
|
|
20284
|
+
}));
|
|
20285
|
+
}
|
|
20286
|
+
render() {
|
|
20287
|
+
const win = this.#currentWindow();
|
|
20288
|
+
const slice = this.#items.slice(win.startIndex, win.endIndex);
|
|
20289
|
+
const spacerStyle = win.horizontal ? `width:${win.totalWidth}px;height:100%` : `height:${win.totalHeight}px`;
|
|
20290
|
+
const scrollFade = win.horizontal ? `scroll-fade-x` : `scroll-fade-y`;
|
|
20291
|
+
return b`
|
|
20292
|
+
<div
|
|
20293
|
+
class="viewport ${scrollFade}"
|
|
20294
|
+
part="viewport"
|
|
20295
|
+
${ref(this.#viewportRef)}
|
|
20296
|
+
data-input-mode=${this.tickled.inputMode}
|
|
20297
|
+
@scroll=${this.#onScroll}
|
|
20298
|
+
>
|
|
20299
|
+
<div class="spacer" style=${spacerStyle}>
|
|
20300
|
+
${repeat(slice, (el) => this.#slotNameFor(el), (el, i) => this.#renderCell(el, win.startIndex + i, win))}
|
|
20301
|
+
</div>
|
|
20302
|
+
</div>
|
|
20303
|
+
`;
|
|
20304
|
+
}
|
|
20305
|
+
#renderCell(el, index, win) {
|
|
20306
|
+
const selected = this.selection.selected.has(el);
|
|
20307
|
+
const tickled = this.tickled.isTickled(el);
|
|
20308
|
+
const entering = this.#entering.has(el);
|
|
20309
|
+
const style = this.captionStyle;
|
|
20310
|
+
const separate = style === `separate`;
|
|
20311
|
+
const caption = style === `hidden` ? `` : this.#captionFor(el);
|
|
20312
|
+
const showCaption = caption !== ``;
|
|
20313
|
+
const cellHeight = win.rowStride - this.gap;
|
|
20314
|
+
const x = win.horizontal ? index * win.colStride : columnOf(index, win.cols) * win.colStride;
|
|
20315
|
+
const y = win.horizontal ? Math.max(0, (this._viewportHeight - cellHeight) / 2) : rowOf(index, win.cols) * win.rowStride;
|
|
20316
|
+
const thumbStyle = separate ? `height:${this.itemSize}px` : ``;
|
|
20317
|
+
const captionCss = separate ? `height:${Math.max(0, this.captionHeight - 4)}px` : style === `under` ? `height:${this.captionHeight}px` : ``;
|
|
20318
|
+
return b`
|
|
20319
|
+
<div
|
|
20320
|
+
class="cell${selected ? ` selected` : ``}${tickled ? ` tickled` : ``}${entering ? ` entering` : ``}"
|
|
20321
|
+
part="item"
|
|
20322
|
+
style="width:${this.itemSize}px;height:${cellHeight}px;transform:translate(${x}px,${y}px)"
|
|
20323
|
+
data-index=${index}
|
|
20324
|
+
@click=${(e) => this.#onCellClick(e, el)}
|
|
20325
|
+
@dblclick=${() => this.#onCellActivate(el)}
|
|
20326
|
+
@pointerenter=${() => this.tickled.handlePointerEnter(el)}
|
|
20327
|
+
>
|
|
20328
|
+
<div class="thumb" part="thumb" style=${thumbStyle}>
|
|
20329
|
+
<slot name=${this.#slotNameFor(el)}></slot>
|
|
20330
|
+
</div>
|
|
20331
|
+
${showCaption ? b`<div class="caption" part="caption" style=${captionCss}>${caption}</div>` : ``}
|
|
20332
|
+
</div>
|
|
20333
|
+
`;
|
|
20334
|
+
}
|
|
20335
|
+
#onScroll;
|
|
20336
|
+
#cancelScrollRaf() {
|
|
20337
|
+
if (this.#scrollRaf !== 0) {
|
|
20338
|
+
cancelAnimationFrame(this.#scrollRaf);
|
|
20339
|
+
this.#scrollRaf = 0;
|
|
20340
|
+
}
|
|
20341
|
+
}
|
|
20342
|
+
#onCellClick(e, item) {
|
|
20343
|
+
this.selection.handleClick(item, e);
|
|
20344
|
+
this._dispatch(`list-item-click`, { item });
|
|
20345
|
+
}
|
|
20346
|
+
#onCellActivate(item) {
|
|
20347
|
+
if (this.interactionMode !== `manual`) this.selection.replace(item);
|
|
20348
|
+
this._dispatch(`list-activate`, { item });
|
|
20349
|
+
}
|
|
20350
|
+
#currentWindow() {
|
|
20351
|
+
return computeGridWindow(this.#items.length, this.itemSize, this.#rowHeight(), this.gap, this._viewportWidth, this._viewportHeight, this.orientation === `row` ? this._scrollLeft : this._scrollTop, this.orientation);
|
|
20352
|
+
}
|
|
20353
|
+
};
|
|
20354
|
+
__decorate([property({
|
|
20355
|
+
type: String,
|
|
20356
|
+
attribute: `selection-mode`
|
|
20357
|
+
})], GridListElement.prototype, "selectionMode", void 0);
|
|
20358
|
+
__decorate([property({
|
|
20359
|
+
type: String,
|
|
20360
|
+
attribute: `interaction-mode`
|
|
20361
|
+
})], GridListElement.prototype, "interactionMode", void 0);
|
|
20362
|
+
__decorate([property({
|
|
20363
|
+
type: Number,
|
|
20364
|
+
attribute: `item-size`
|
|
20365
|
+
})], GridListElement.prototype, "itemSize", void 0);
|
|
20366
|
+
__decorate([property({ type: Number })], GridListElement.prototype, "gap", void 0);
|
|
20367
|
+
__decorate([property({
|
|
20368
|
+
type: String,
|
|
20369
|
+
reflect: true
|
|
20370
|
+
})], GridListElement.prototype, "orientation", void 0);
|
|
20371
|
+
__decorate([property({
|
|
20372
|
+
type: String,
|
|
20373
|
+
attribute: `caption-style`,
|
|
20374
|
+
reflect: true
|
|
20375
|
+
})], GridListElement.prototype, "captionStyle", void 0);
|
|
20376
|
+
__decorate([property({
|
|
20377
|
+
type: String,
|
|
20378
|
+
attribute: `caption-align`,
|
|
20379
|
+
reflect: true
|
|
20380
|
+
})], GridListElement.prototype, "captionAlign", void 0);
|
|
20381
|
+
__decorate([property({
|
|
20382
|
+
type: String,
|
|
20383
|
+
attribute: `caption-position`,
|
|
20384
|
+
reflect: true
|
|
20385
|
+
})], GridListElement.prototype, "captionPosition", void 0);
|
|
20386
|
+
__decorate([property({
|
|
20387
|
+
type: String,
|
|
20388
|
+
attribute: `caption-backdrop`,
|
|
20389
|
+
reflect: true
|
|
20390
|
+
})], GridListElement.prototype, "captionBackdrop", void 0);
|
|
20391
|
+
__decorate([property({
|
|
20392
|
+
type: Number,
|
|
20393
|
+
attribute: `caption-height`
|
|
20394
|
+
})], GridListElement.prototype, "captionHeight", void 0);
|
|
20395
|
+
__decorate([property({
|
|
20396
|
+
type: String,
|
|
20397
|
+
attribute: `item-wrap`
|
|
20398
|
+
})], GridListElement.prototype, "itemWrap", void 0);
|
|
20399
|
+
__decorate([state()], GridListElement.prototype, "_scrollTop", void 0);
|
|
20400
|
+
__decorate([state()], GridListElement.prototype, "_scrollLeft", void 0);
|
|
20401
|
+
__decorate([state()], GridListElement.prototype, "_viewportHeight", void 0);
|
|
20402
|
+
__decorate([state()], GridListElement.prototype, "_viewportWidth", void 0);
|
|
20403
|
+
GridListElement = __decorate([safeCustomElement(`ixfx-grid-list`)], GridListElement);
|
|
20404
|
+
//#endregion
|
|
18589
20405
|
//#region src/panel/panel.ts
|
|
18590
20406
|
let PanelElement = class PanelElement extends i {
|
|
18591
20407
|
constructor(..._args) {
|
|
@@ -18938,6 +20754,8 @@ __decorate([property({
|
|
|
18938
20754
|
GroupedItemListerElement = __decorate([safeCustomElement(`ixfx-grouped-item-lister`)], GroupedItemListerElement);
|
|
18939
20755
|
//#endregion
|
|
18940
20756
|
//#region src/tree/tree-base.ts
|
|
20757
|
+
/** Above this many rows entering / leaving at once, skip the animation entirely. */
|
|
20758
|
+
const MAX_ANIMATED_ROWS = 50;
|
|
18941
20759
|
let TreeBaseElement = class TreeBaseElement extends i {
|
|
18942
20760
|
constructor(..._args) {
|
|
18943
20761
|
super(..._args);
|
|
@@ -18961,6 +20779,12 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
18961
20779
|
});
|
|
18962
20780
|
this._focusedDepth = 0;
|
|
18963
20781
|
this.#config = { indent: 20 };
|
|
20782
|
+
this.#renderedKeys = [];
|
|
20783
|
+
this.#lastNodeByKey = /* @__PURE__ */ new Map();
|
|
20784
|
+
this.#enteringKeys = /* @__PURE__ */ new Set();
|
|
20785
|
+
this.#leaving = /* @__PURE__ */ new Map();
|
|
20786
|
+
this.#leavingAnimating = /* @__PURE__ */ new Set();
|
|
20787
|
+
this.#treeAnimReady = false;
|
|
18964
20788
|
this.#onKeyDown = (event) => {
|
|
18965
20789
|
const nodes = this.#getAllNodes();
|
|
18966
20790
|
if (nodes.length === 0) return;
|
|
@@ -19022,7 +20846,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
19022
20846
|
bubbles: true,
|
|
19023
20847
|
composed: true
|
|
19024
20848
|
}));
|
|
19025
|
-
} else if (current.depth >
|
|
20849
|
+
} else if (current.depth > 0) {
|
|
19026
20850
|
const parentDepth = current.depth - 1;
|
|
19027
20851
|
for (let i = currentIdx - 1; i >= 0; i--) if (nodes[i].depth === parentDepth) {
|
|
19028
20852
|
this.#setFocusedIndex(i);
|
|
@@ -19088,6 +20912,85 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
19088
20912
|
#anchor;
|
|
19089
20913
|
#ownedRegistry;
|
|
19090
20914
|
#config;
|
|
20915
|
+
#renderedKeys;
|
|
20916
|
+
#lastNodeByKey;
|
|
20917
|
+
#enteringKeys;
|
|
20918
|
+
#leaving;
|
|
20919
|
+
#leavingAnimating;
|
|
20920
|
+
#treeAnimReady;
|
|
20921
|
+
willUpdate(changedProps) {
|
|
20922
|
+
super.willUpdate(changedProps);
|
|
20923
|
+
this.#diffVisibleNodes();
|
|
20924
|
+
}
|
|
20925
|
+
#diffVisibleNodes() {
|
|
20926
|
+
const nodes = this.root ? this.#getAllNodes() : [];
|
|
20927
|
+
const nowKeys = new Set(nodes.map((n) => n.node.item.key));
|
|
20928
|
+
this.#enteringKeys.clear();
|
|
20929
|
+
if (this.#treeAnimReady && this.#renderedKeys.length > 0) {
|
|
20930
|
+
const prevSet = new Set(this.#renderedKeys);
|
|
20931
|
+
for (const { node } of nodes) if (!prevSet.has(node.item.key)) this.#enteringKeys.add(node.item.key);
|
|
20932
|
+
if (this.#enteringKeys.size > MAX_ANIMATED_ROWS) this.#enteringKeys.clear();
|
|
20933
|
+
for (const key of this.#renderedKeys) if (!nowKeys.has(key) && !this.#leaving.has(key)) {
|
|
20934
|
+
const last = this.#lastNodeByKey.get(key);
|
|
20935
|
+
if (last) this.#leaving.set(key, last);
|
|
20936
|
+
}
|
|
20937
|
+
}
|
|
20938
|
+
for (const key of [...this.#leaving.keys()]) if (nowKeys.has(key)) {
|
|
20939
|
+
this.#leaving.delete(key);
|
|
20940
|
+
this.#leavingAnimating.delete(key);
|
|
20941
|
+
}
|
|
20942
|
+
if (this.#leaving.size > MAX_ANIMATED_ROWS) {
|
|
20943
|
+
this.#leaving.clear();
|
|
20944
|
+
this.#leavingAnimating.clear();
|
|
20945
|
+
}
|
|
20946
|
+
}
|
|
20947
|
+
/** True while `node` is a fading ghost row (removed / collapsed, still animating out). */
|
|
20948
|
+
_isLeaving(node) {
|
|
20949
|
+
return this.#leaving.has(node.item.key);
|
|
20950
|
+
}
|
|
20951
|
+
/** Splice the fading ghost rows back into the visible list near where they were. */
|
|
20952
|
+
#weaveLeaving(nodes) {
|
|
20953
|
+
if (this.#leaving.size === 0) return nodes;
|
|
20954
|
+
const result = [...nodes];
|
|
20955
|
+
const indexOfKey = (key) => result.findIndex((e) => e.node.item.key === key);
|
|
20956
|
+
for (const [key, entry] of this.#leaving) {
|
|
20957
|
+
const prevIdx = this.#renderedKeys.indexOf(key);
|
|
20958
|
+
let insertAt = result.length;
|
|
20959
|
+
for (let j = prevIdx + 1; j < this.#renderedKeys.length; j++) {
|
|
20960
|
+
const at = indexOfKey(this.#renderedKeys[j]);
|
|
20961
|
+
if (at !== -1) {
|
|
20962
|
+
insertAt = at;
|
|
20963
|
+
break;
|
|
20964
|
+
}
|
|
20965
|
+
}
|
|
20966
|
+
result.splice(insertAt, 0, entry);
|
|
20967
|
+
}
|
|
20968
|
+
return result;
|
|
20969
|
+
}
|
|
20970
|
+
#runItemAnimations() {
|
|
20971
|
+
if (this.#enteringKeys.size > 0) {
|
|
20972
|
+
for (const el of this.renderRoot.querySelectorAll(`.item`)) {
|
|
20973
|
+
const key = el.dataset.key;
|
|
20974
|
+
if (key !== void 0 && this.#enteringKeys.has(key)) animateListItemIn(el);
|
|
20975
|
+
}
|
|
20976
|
+
this.#enteringKeys.clear();
|
|
20977
|
+
}
|
|
20978
|
+
if (this.#leaving.size > 0) for (const el of this.renderRoot.querySelectorAll(`.item.leaving`)) {
|
|
20979
|
+
const key = el.dataset.key;
|
|
20980
|
+
if (key === void 0 || this.#leavingAnimating.has(key)) continue;
|
|
20981
|
+
this.#leavingAnimating.add(key);
|
|
20982
|
+
animateListItemOut(el).then(() => {
|
|
20983
|
+
this.#leaving.delete(key);
|
|
20984
|
+
this.#leavingAnimating.delete(key);
|
|
20985
|
+
this.requestUpdate();
|
|
20986
|
+
});
|
|
20987
|
+
}
|
|
20988
|
+
const visible = this.root ? this.#getAllNodes() : [];
|
|
20989
|
+
this.#renderedKeys = visible.map((n) => n.node.item.key);
|
|
20990
|
+
this.#lastNodeByKey.clear();
|
|
20991
|
+
for (const entry of visible) this.#lastNodeByKey.set(entry.node.item.key, entry);
|
|
20992
|
+
this.#treeAnimReady = true;
|
|
20993
|
+
}
|
|
19091
20994
|
updated(changedProps) {
|
|
19092
20995
|
if (changedProps.has(`root`) && this.root) {
|
|
19093
20996
|
this.#abortPending();
|
|
@@ -19122,6 +21025,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
19122
21025
|
if (changedProps.has(`selectedNode`) && this.selectedNode) {
|
|
19123
21026
|
if (changedProps.get(`selectedNode`) !== this.selectedNode) this.tickled.setTickled(this.selectedNode.item.key);
|
|
19124
21027
|
}
|
|
21028
|
+
this.#runItemAnimations();
|
|
19125
21029
|
}
|
|
19126
21030
|
connectedCallback() {
|
|
19127
21031
|
super.connectedCallback();
|
|
@@ -19286,9 +21190,9 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
19286
21190
|
}
|
|
19287
21191
|
}
|
|
19288
21192
|
}
|
|
19289
|
-
renderNodeContent(node,
|
|
21193
|
+
renderNodeContent(node, depth) {
|
|
19290
21194
|
const isBranch = node.item.isLeaf !== true;
|
|
19291
|
-
const isExpanded = this._isExpanded(node);
|
|
21195
|
+
const isExpanded = this._isExpanded(node, depth);
|
|
19292
21196
|
return b`
|
|
19293
21197
|
<span class="tree-caret">
|
|
19294
21198
|
${isBranch ? b`<ixfx-icon class="caret-icon" name="chevron-down" .rotate=${isExpanded ? A : `-90deg`}></ixfx-icon>` : ``}
|
|
@@ -19569,9 +21473,9 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
19569
21473
|
_isFocused(node) {
|
|
19570
21474
|
return this.tickled.isTickled(node.item.key);
|
|
19571
21475
|
}
|
|
19572
|
-
_isExpanded(node) {
|
|
21476
|
+
_isExpanded(node, depth = 0) {
|
|
19573
21477
|
if (this._expandedKeys.has(node.item.key)) return true;
|
|
19574
|
-
if (this.filterPredicate && node.children) return node.children.some((child) => this.#matchesFilter(child,
|
|
21478
|
+
if (this.filterPredicate && node.children) return node.children.some((child) => this.#matchesFilter(child, depth + 1));
|
|
19575
21479
|
return false;
|
|
19576
21480
|
}
|
|
19577
21481
|
#matchesFilter(node, depth) {
|
|
@@ -19652,7 +21556,7 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
19652
21556
|
}
|
|
19653
21557
|
#getAllNodes() {
|
|
19654
21558
|
if (!this.root) return [];
|
|
19655
|
-
return this.#getVisibleNodes(this.root,
|
|
21559
|
+
return this.#getVisibleNodes(this.root, -1);
|
|
19656
21560
|
}
|
|
19657
21561
|
#getFocusedIndex() {
|
|
19658
21562
|
const nodes = this.#getAllNodes();
|
|
@@ -19706,14 +21610,14 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
19706
21610
|
}
|
|
19707
21611
|
#onDblClick;
|
|
19708
21612
|
#renderNodes(nodes) {
|
|
19709
|
-
if (nodes.length === 0 && this.#pendingNode) return b`
|
|
21613
|
+
if (nodes.length === 0 && this.#pendingNode && this.#leaving.size === 0) return b`
|
|
19710
21614
|
<div class="tree-loading">
|
|
19711
21615
|
${this.renderLoadingIndicator()}
|
|
19712
21616
|
</div>
|
|
19713
21617
|
`;
|
|
19714
21618
|
return b`
|
|
19715
21619
|
<ul class="tree-list" role="tree">
|
|
19716
|
-
${nodes.map(({ node, depth }) => this._renderNode(node, depth))}
|
|
21620
|
+
${this.#weaveLeaving(nodes).map(({ node, depth }) => this._renderNode(node, depth))}
|
|
19717
21621
|
</ul>
|
|
19718
21622
|
`;
|
|
19719
21623
|
}
|
|
@@ -19721,15 +21625,17 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
19721
21625
|
const selected = this._isSelected(node);
|
|
19722
21626
|
const tickled = this._isFocused(node);
|
|
19723
21627
|
const isBranch = node.item.isLeaf !== true;
|
|
19724
|
-
const isExpanded = this._isExpanded(node);
|
|
21628
|
+
const isExpanded = this._isExpanded(node, depth);
|
|
19725
21629
|
const isPending = this.#pendingNode === node;
|
|
19726
21630
|
const isFiltered = this.filterPredicate !== void 0 && !this.#matchesFilter(node, depth);
|
|
21631
|
+
const isLeaving = this._isLeaving(node);
|
|
19727
21632
|
return b`
|
|
19728
21633
|
<li
|
|
19729
|
-
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${isFiltered ? `filtered` : ``}"
|
|
21634
|
+
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${isFiltered ? `filtered` : ``} ${isLeaving ? `leaving` : ``}"
|
|
19730
21635
|
role="treeitem"
|
|
19731
21636
|
aria-selected=${selected}
|
|
19732
21637
|
aria-expanded=${isBranch ? isExpanded : A}
|
|
21638
|
+
aria-hidden=${isLeaving ? `true` : A}
|
|
19733
21639
|
aria-level=${depth + 1}
|
|
19734
21640
|
data-key=${node.item.key}
|
|
19735
21641
|
style=${`--depth: ${depth}`}
|
|
@@ -19850,6 +21756,12 @@ let TreeBaseElement = class TreeBaseElement extends i {
|
|
|
19850
21756
|
display: none;
|
|
19851
21757
|
}
|
|
19852
21758
|
|
|
21759
|
+
/* Fading-out ghost row (see #runItemAnimations) — inert while it collapses. */
|
|
21760
|
+
.item.leaving {
|
|
21761
|
+
pointer-events: none;
|
|
21762
|
+
overflow: hidden;
|
|
21763
|
+
}
|
|
21764
|
+
|
|
19853
21765
|
::highlight(incr-search-highlight) {
|
|
19854
21766
|
background: var(--search-highlight-bg, var(--accent));
|
|
19855
21767
|
color: var(--search-highlight-color, var(--accent-text, #fff));
|
|
@@ -19909,11 +21821,11 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
|
19909
21821
|
if (node.item.icon) this.#depthsWithIcons.add(depth);
|
|
19910
21822
|
if (node.children) for (const child of node.children) visit(child, depth + 1);
|
|
19911
21823
|
};
|
|
19912
|
-
if (this.root) visit(this.root,
|
|
21824
|
+
if (this.root) visit(this.root, -1);
|
|
19913
21825
|
}
|
|
19914
21826
|
renderNodeContent(node, depth) {
|
|
19915
21827
|
const isBranch = node.item.isLeaf !== true;
|
|
19916
|
-
const isExpanded = this._isExpanded(node);
|
|
21828
|
+
const isExpanded = this._isExpanded(node, depth);
|
|
19917
21829
|
const icon = node.item.icon;
|
|
19918
21830
|
const tooltip = node.item.tooltip;
|
|
19919
21831
|
const caretAndLabel = b`
|
|
@@ -19959,16 +21871,18 @@ let TreeListElement = class TreeListElement extends TreeBaseElement {
|
|
|
19959
21871
|
const selected = this._isSelected(node);
|
|
19960
21872
|
const tickled = this._isFocused(node);
|
|
19961
21873
|
const isBranch = node.item.isLeaf !== true;
|
|
19962
|
-
const isExpanded = this._isExpanded(node);
|
|
21874
|
+
const isExpanded = this._isExpanded(node, depth);
|
|
19963
21875
|
const isPending = this._pendingNode === node;
|
|
19964
21876
|
const hasIconAtDepth = this.#depthsWithIcons.has(depth);
|
|
21877
|
+
const isLeaving = this._isLeaving(node);
|
|
19965
21878
|
return b`
|
|
19966
21879
|
<li
|
|
19967
|
-
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``}"
|
|
21880
|
+
class="item ${selected ? `selected` : ``} ${tickled ? `tickled` : ``} ${isBranch ? `branch` : `leaf`} ${hasIconAtDepth ? `has-icon-at-depth` : ``} ${isLeaving ? `leaving` : ``}"
|
|
19968
21881
|
data-key=${node.item.key}
|
|
19969
21882
|
role="treeitem"
|
|
19970
21883
|
aria-selected=${selected}
|
|
19971
21884
|
aria-expanded=${isBranch ? isExpanded : A}
|
|
21885
|
+
aria-hidden=${isLeaving ? `true` : A}
|
|
19972
21886
|
aria-level=${depth + 1}
|
|
19973
21887
|
style=${`--depth: ${depth}`}
|
|
19974
21888
|
@click=${(e) => this._handleItemClick(node, depth, e)}
|
|
@@ -20241,6 +22155,7 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
20241
22155
|
this._searchQuery = ``;
|
|
20242
22156
|
this.#customWrappers = /* @__PURE__ */ new Map();
|
|
20243
22157
|
this.#wrappedElements = /* @__PURE__ */ new Set();
|
|
22158
|
+
this.#animatedItems = /* @__PURE__ */ new WeakSet();
|
|
20244
22159
|
this.selection = new ListSelectionController({
|
|
20245
22160
|
getOrderedItems: () => this._getVisibleItems(),
|
|
20246
22161
|
isSelectable: (el) => this._isCheckable(el),
|
|
@@ -20248,11 +22163,13 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
20248
22163
|
selectionMode: this.selectionMode,
|
|
20249
22164
|
interactionMode: this.interactionMode
|
|
20250
22165
|
}),
|
|
22166
|
+
getKey: keyForElement,
|
|
20251
22167
|
onChange: (selected, previous) => {
|
|
20252
22168
|
this._syncItemStates();
|
|
20253
22169
|
this._dispatch(`list-select`, {
|
|
20254
22170
|
selected,
|
|
20255
|
-
previous
|
|
22171
|
+
previous,
|
|
22172
|
+
selectedKeys: this.selection.selectedKeys
|
|
20256
22173
|
});
|
|
20257
22174
|
}
|
|
20258
22175
|
});
|
|
@@ -20365,6 +22282,10 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
20365
22282
|
#search;
|
|
20366
22283
|
#customWrappers;
|
|
20367
22284
|
#wrappedElements;
|
|
22285
|
+
/** Watches for items added to the light DOM so they can be animated in. */
|
|
22286
|
+
#itemObserver;
|
|
22287
|
+
/** Items that have already played (or been skipped for) their enter animation. */
|
|
22288
|
+
#animatedItems;
|
|
20368
22289
|
connectedCallback() {
|
|
20369
22290
|
super.connectedCallback();
|
|
20370
22291
|
if (!this.hasAttribute(`tabindex`)) this.tabIndex = 0;
|
|
@@ -20373,6 +22294,9 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
20373
22294
|
this.addEventListener(`keydown`, this._onKeyDown);
|
|
20374
22295
|
this.addEventListener(`click`, this._onClick);
|
|
20375
22296
|
this.addEventListener(`dblclick`, this._onDblClick);
|
|
22297
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR)) this.#animatedItems.add(el);
|
|
22298
|
+
this.#itemObserver ??= new MutationObserver((records) => this.#onItemsMutated(records));
|
|
22299
|
+
this.#itemObserver.observe(this, { childList: true });
|
|
20376
22300
|
}
|
|
20377
22301
|
disconnectedCallback() {
|
|
20378
22302
|
super.disconnectedCallback();
|
|
@@ -20381,9 +22305,31 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
20381
22305
|
this.removeEventListener(`keydown`, this._onKeyDown);
|
|
20382
22306
|
this.removeEventListener(`click`, this._onClick);
|
|
20383
22307
|
this.removeEventListener(`dblclick`, this._onDblClick);
|
|
22308
|
+
this.#itemObserver?.disconnect();
|
|
20384
22309
|
this.#search?.dispose();
|
|
20385
22310
|
this.#search = void 0;
|
|
20386
22311
|
}
|
|
22312
|
+
/**
|
|
22313
|
+
* Animate in items appended to the light DOM (however they got there —
|
|
22314
|
+
* {@link addItem}, direct `appendChild`, a framework binding). A bulk populate
|
|
22315
|
+
* (roughly everything at once) is left alone, as are the wrapper `<li>`s the
|
|
22316
|
+
* checked-mode plumbing shuffles around.
|
|
22317
|
+
*/
|
|
22318
|
+
#onItemsMutated(records) {
|
|
22319
|
+
if (!this.isConnected) return;
|
|
22320
|
+
const fresh = [];
|
|
22321
|
+
for (const record of records) for (const node of record.addedNodes) {
|
|
22322
|
+
if (!(node instanceof HTMLElement) || node.parentElement !== this) continue;
|
|
22323
|
+
if (!node.matches(ITEM_SELECTOR)) continue;
|
|
22324
|
+
if (this.#animatedItems.has(node) || this.#customWrappers.has(node) || this.#wrappedElements.has(node)) continue;
|
|
22325
|
+
this.#animatedItems.add(node);
|
|
22326
|
+
fresh.push(node);
|
|
22327
|
+
}
|
|
22328
|
+
if (fresh.length === 0) return;
|
|
22329
|
+
const total = this.querySelectorAll(ITEM_SELECTOR).length;
|
|
22330
|
+
if (fresh.length >= total) return;
|
|
22331
|
+
for (const el of fresh) animateListItemIn(el);
|
|
22332
|
+
}
|
|
20387
22333
|
firstUpdated() {
|
|
20388
22334
|
this.#search = createElementIncrSearch({
|
|
20389
22335
|
container: this,
|
|
@@ -20429,8 +22375,21 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
20429
22375
|
this.appendChild(el);
|
|
20430
22376
|
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
20431
22377
|
}
|
|
22378
|
+
/**
|
|
22379
|
+
* Collapse + fade `item` out, then detach it. The rows below slide up to
|
|
22380
|
+
* close the gap. Resolves once the element has actually been removed.
|
|
22381
|
+
* Under `prefers-reduced-motion: reduce` it's removed immediately.
|
|
22382
|
+
*/
|
|
20432
22383
|
removeItem(item) {
|
|
20433
|
-
|
|
22384
|
+
this.selection.deselect(item);
|
|
22385
|
+
return animateListItemOut(item).then(() => {
|
|
22386
|
+
item.remove();
|
|
22387
|
+
if (this.interactionMode === `checked`) this._syncCheckboxes();
|
|
22388
|
+
});
|
|
22389
|
+
}
|
|
22390
|
+
/** {@link removeItem} for several items at once. */
|
|
22391
|
+
removeItems(items) {
|
|
22392
|
+
return Promise.all([...items].map((item) => this.removeItem(item))).then(() => void 0);
|
|
20434
22393
|
}
|
|
20435
22394
|
clearItems() {
|
|
20436
22395
|
this.#customWrappers.clear();
|
|
@@ -20469,6 +22428,43 @@ let VerticalListElement = class VerticalListElement extends i {
|
|
|
20469
22428
|
selectAll() {
|
|
20470
22429
|
if (this.selection.canSelectAll()) this.selection.selectAll();
|
|
20471
22430
|
}
|
|
22431
|
+
/** The selection as stable `data-key` / `data-value` strings. */
|
|
22432
|
+
get selectedKeys() {
|
|
22433
|
+
return this.selection.selectedKeys;
|
|
22434
|
+
}
|
|
22435
|
+
/** The key of the item under the cursor, or `undefined`. */
|
|
22436
|
+
get tickledKey() {
|
|
22437
|
+
const id = this.tickled.tickledId;
|
|
22438
|
+
return id ? keyForElement(id) : void 0;
|
|
22439
|
+
}
|
|
22440
|
+
/** Replace the selection with the items carrying these keys (unknown keys ignored). */
|
|
22441
|
+
selectKeys(keys) {
|
|
22442
|
+
this.selection.selectKeys(keys, (key) => this.#itemForKey(key));
|
|
22443
|
+
}
|
|
22444
|
+
/** Add items by key to the selection (multi-select modes only). */
|
|
22445
|
+
addSelectedKeys(keys) {
|
|
22446
|
+
this.selection.addKeys(keys, (key) => this.#itemForKey(key));
|
|
22447
|
+
}
|
|
22448
|
+
/** Remove items by key from the selection. */
|
|
22449
|
+
removeSelectedKeys(keys) {
|
|
22450
|
+
this.selection.removeKeys(keys, (key) => this.#itemForKey(key));
|
|
22451
|
+
}
|
|
22452
|
+
/** Move the cursor to the item with this key and scroll it into view. */
|
|
22453
|
+
focusKey(key) {
|
|
22454
|
+
const el = this.#itemForKey(key);
|
|
22455
|
+
if (!el) return;
|
|
22456
|
+
this.tickled.focus(el);
|
|
22457
|
+
el.scrollIntoView({
|
|
22458
|
+
block: `nearest`,
|
|
22459
|
+
inline: `nearest`
|
|
22460
|
+
});
|
|
22461
|
+
}
|
|
22462
|
+
#itemForKey(key) {
|
|
22463
|
+
for (const el of this.querySelectorAll(ITEM_SELECTOR)) {
|
|
22464
|
+
if (this.#wrappedElements.has(el)) continue;
|
|
22465
|
+
if (keyForElement(el) === key) return el;
|
|
22466
|
+
}
|
|
22467
|
+
}
|
|
20472
22468
|
*selectedFilter(kind) {
|
|
20473
22469
|
const wanted = (el) => {
|
|
20474
22470
|
switch (kind) {
|
|
@@ -27113,6 +29109,136 @@ function dismiss(id) {
|
|
|
27113
29109
|
syncRenderer();
|
|
27114
29110
|
}
|
|
27115
29111
|
//#endregion
|
|
29112
|
+
//#region src/pan-text/pan-text.ts
|
|
29113
|
+
const SPEED = 45;
|
|
29114
|
+
const END_PADDING = 4;
|
|
29115
|
+
const START_DELAY = 300;
|
|
29116
|
+
const PAN_EASING = `cubic-bezier(0.25, 0.46, 0.45, 0.94)`;
|
|
29117
|
+
let PanTextElement = class PanTextElement extends i {
|
|
29118
|
+
constructor(..._args) {
|
|
29119
|
+
super(..._args);
|
|
29120
|
+
this.direction = `end`;
|
|
29121
|
+
this.disabled = false;
|
|
29122
|
+
this._isOverflowing = false;
|
|
29123
|
+
this._isHovering = false;
|
|
29124
|
+
this._overflow = 0;
|
|
29125
|
+
this._resizeObserver = null;
|
|
29126
|
+
this._onMouseEnter = () => {
|
|
29127
|
+
this._isHovering = true;
|
|
29128
|
+
};
|
|
29129
|
+
this._onMouseLeave = () => {
|
|
29130
|
+
this._isHovering = false;
|
|
29131
|
+
};
|
|
29132
|
+
this._measure = () => {
|
|
29133
|
+
if (this._isHovering) return;
|
|
29134
|
+
const el = this._textEl;
|
|
29135
|
+
if (!el) return;
|
|
29136
|
+
this._overflow = Math.max(0, el.scrollWidth - el.clientWidth);
|
|
29137
|
+
const overflowing = this._overflow > 0;
|
|
29138
|
+
this.classList.toggle(`is-overflowing`, overflowing);
|
|
29139
|
+
if (overflowing !== this._isOverflowing) this._isOverflowing = overflowing;
|
|
29140
|
+
};
|
|
29141
|
+
}
|
|
29142
|
+
connectedCallback() {
|
|
29143
|
+
super.connectedCallback();
|
|
29144
|
+
this._resizeObserver = new ResizeObserver(() => this._measure());
|
|
29145
|
+
this.addEventListener(`mouseenter`, this._onMouseEnter);
|
|
29146
|
+
this.addEventListener(`mouseleave`, this._onMouseLeave);
|
|
29147
|
+
}
|
|
29148
|
+
disconnectedCallback() {
|
|
29149
|
+
super.disconnectedCallback();
|
|
29150
|
+
this._resizeObserver?.disconnect();
|
|
29151
|
+
this._resizeObserver = null;
|
|
29152
|
+
this.removeEventListener(`mouseenter`, this._onMouseEnter);
|
|
29153
|
+
this.removeEventListener(`mouseleave`, this._onMouseLeave);
|
|
29154
|
+
}
|
|
29155
|
+
firstUpdated() {
|
|
29156
|
+
this._measure();
|
|
29157
|
+
this._resizeObserver?.observe(this);
|
|
29158
|
+
document.fonts?.ready.then(() => this._measure());
|
|
29159
|
+
}
|
|
29160
|
+
updated() {
|
|
29161
|
+
this._measure();
|
|
29162
|
+
}
|
|
29163
|
+
_textStyle() {
|
|
29164
|
+
if (!this._isOverflowing) return ``;
|
|
29165
|
+
const distance = this._overflow + END_PADDING;
|
|
29166
|
+
const duration = Math.max(1.5, distance / SPEED);
|
|
29167
|
+
const revealEnd = this.direction === `end`;
|
|
29168
|
+
const restX = revealEnd ? 0 : -distance;
|
|
29169
|
+
const hoverX = revealEnd ? -distance : 0;
|
|
29170
|
+
if (this._isHovering) return [
|
|
29171
|
+
`transform: translateX(${hoverX}px)`,
|
|
29172
|
+
`transition: transform ${duration}s ${PAN_EASING} ${START_DELAY}ms`,
|
|
29173
|
+
`overflow: visible`,
|
|
29174
|
+
`text-overflow: clip`
|
|
29175
|
+
].join(`; `);
|
|
29176
|
+
return [
|
|
29177
|
+
`transform: translateX(${restX}px)`,
|
|
29178
|
+
`transition: transform 400ms ${PAN_EASING}`,
|
|
29179
|
+
revealEnd ? `` : `overflow: visible; text-overflow: clip`
|
|
29180
|
+
].filter(Boolean).join(`; `);
|
|
29181
|
+
}
|
|
29182
|
+
render() {
|
|
29183
|
+
const textClasses = {
|
|
29184
|
+
"is-overflowing": this._isOverflowing,
|
|
29185
|
+
"is-hovering": this._isHovering
|
|
29186
|
+
};
|
|
29187
|
+
return b`
|
|
29188
|
+
<span class="text ${classMap(textClasses)}" part="text" style=${this._textStyle()}>
|
|
29189
|
+
<slot @slotchange=${this._measure}></slot>
|
|
29190
|
+
</span>
|
|
29191
|
+
`;
|
|
29192
|
+
}
|
|
29193
|
+
static {
|
|
29194
|
+
this.styles = [themeFallbacks, i$3`
|
|
29195
|
+
/* Renders like plain inline text: no colours, border, padding or margin
|
|
29196
|
+
of its own — it only adds the clipping needed to pan on hover. */
|
|
29197
|
+
:host {
|
|
29198
|
+
display: inline-block;
|
|
29199
|
+
overflow: hidden;
|
|
29200
|
+
white-space: nowrap;
|
|
29201
|
+
max-width: 100%;
|
|
29202
|
+
box-sizing: border-box;
|
|
29203
|
+
font: inherit;
|
|
29204
|
+
color: inherit;
|
|
29205
|
+
}
|
|
29206
|
+
|
|
29207
|
+
:host([disabled]) {
|
|
29208
|
+
opacity: var(--disabled-opacity, 0.5);
|
|
29209
|
+
pointer-events: none;
|
|
29210
|
+
}
|
|
29211
|
+
|
|
29212
|
+
.text {
|
|
29213
|
+
display: block;
|
|
29214
|
+
overflow: hidden;
|
|
29215
|
+
text-overflow: ellipsis;
|
|
29216
|
+
white-space: nowrap;
|
|
29217
|
+
will-change: transform;
|
|
29218
|
+
}
|
|
29219
|
+
|
|
29220
|
+
@media (prefers-reduced-motion: reduce) {
|
|
29221
|
+
.text {
|
|
29222
|
+
transition: none !important;
|
|
29223
|
+
transform: none !important;
|
|
29224
|
+
}
|
|
29225
|
+
}
|
|
29226
|
+
`];
|
|
29227
|
+
}
|
|
29228
|
+
};
|
|
29229
|
+
__decorate([property({
|
|
29230
|
+
type: String,
|
|
29231
|
+
attribute: `direction`
|
|
29232
|
+
})], PanTextElement.prototype, "direction", void 0);
|
|
29233
|
+
__decorate([property({
|
|
29234
|
+
type: Boolean,
|
|
29235
|
+
attribute: `disabled`
|
|
29236
|
+
})], PanTextElement.prototype, "disabled", void 0);
|
|
29237
|
+
__decorate([state()], PanTextElement.prototype, "_isOverflowing", void 0);
|
|
29238
|
+
__decorate([state()], PanTextElement.prototype, "_isHovering", void 0);
|
|
29239
|
+
__decorate([query(`.text`)], PanTextElement.prototype, "_textEl", void 0);
|
|
29240
|
+
PanTextElement = __decorate([safeCustomElement(`ixfx-pan-text`)], PanTextElement);
|
|
29241
|
+
//#endregion
|
|
27116
29242
|
//#region src/panel/panel-group.ts
|
|
27117
29243
|
let PanelGroupElement = class PanelGroupElement extends i {
|
|
27118
29244
|
constructor(..._args) {
|
|
@@ -36516,6 +38642,6 @@ function init() {
|
|
|
36516
38642
|
console.log(`Init`);
|
|
36517
38643
|
}
|
|
36518
38644
|
//#endregion
|
|
36519
|
-
export { AcTextElement, AcTokenElement, ButtonElement, Checkbox, ColourPicker, ColourPickerPopup, CrumbNavigationElement, CrumbPathController, CrumbPathElement, DataController, DataDisplayComponent, DataGridElement, DetailListElement, DockHostElement, DockPanelElement, EditableLabel, EditableNumber, FormElement, FormGroupElement, FormPartElement, GroupedItemListerElement, ICON_CARET_RIGHT, ICON_CHECK, ICON_CHEVRON_DOWN, ICON_CLOSE, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, menu_exports as Menu, MillerBaseElement, MillerListElement, MillerTreeController, NarrowedTextElement, NotificationManager, NotificationPillElement, 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, TimelineController, TimelineTrackTabElement, TitlebarElement, ToolbarElement, ToolbarGroupElement, TooltipElement, TransitoryLabelElement, TreeBaseElement, TreeController, TreeDataModel, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, buildHighlightTargets, buildModel, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createElementIncrSearch, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isSplit, isStack, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, parseColourScaleAttr, parseLayout, pointerOverAnyHost, readBaseColour, registerFontIcon, registerIcon, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, svgToDataUri, swapLeaves, timelineStyles, toSnapshot, walk, wrapRoot };
|
|
38645
|
+
export { AcTextElement, AcTokenElement, 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, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, IxfxHexEditorElement, IxfxIconElement, IxfxTimelineElement, KeyedElementIndex, LIST_ITEM_MOTION_MS, LabelledRadialInput, LabelledRangeInput, LabelledSliderInput, LedElement, 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, TreeKeyboardNavController, TreeListElement, TwoSplitLayoutElement, VerticalListElement, XyPad, addPanelToStack, animateListItemIn, animateListItemOut, applyAgeModification, applyHighlights, applySnapshot, attachTimelineInteractions, buildHighlightTargets, buildModel, clearHighlights, collectContainerNames, collectDataResult, collectPanelIds, collectStackPaths, computeColour, computeLeafGeoms, createAcCompletionProvider, createElementIncrSearch, createEmptyLayout, createEmptyStack, createFuzzySearch, createIncrSearch, createPillRenderer, createSplit, createStack, createTreeListAdapter, createVerticalListAdapter, distribute, extractStack, findNode, findParentOf, findStackOf, findStackPathByName, getActiveDragPanelId, getIcon, getIconEntry, getIconFont, getLeafNeighbours, hasIcon, iconBus, init, insertBeside, isDragging, isMultiMode, isSplit, isStack, keyForElement, listItemFadeKeyframes, monitorIndeterminate, moveLeafNodeToEdge, moveLeafStep, normalise, normalize, parseColourScaleAttr, parseLayout, pointerOverAnyHost, prefersReducedMotion, readBaseColour, registerFontIcon, registerIcon, removePanelFromStack, resolveCssVars, resolveGrid, resolveLeafPath, resolveTracks, serializeLayout, setActive, setIconFont, svgToDataUri, swapLeaves, tickledItemStyles, tickledStyles, timelineStyles, toSnapshot, walk, wrapRoot };
|
|
36520
38646
|
|
|
36521
38647
|
//# sourceMappingURL=index.js.map
|