@ixfx/components 0.15.5 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/bundle/index.d.ts +986 -49
  2. package/bundle/index.js +765 -729
  3. package/dist/ac-token.js +1 -1
  4. package/dist/crumb-navigation-BbqfmkYs.d.ts +147 -0
  5. package/dist/crumb-navigation-YnFVFqrx.js +1290 -0
  6. package/dist/crumbs.d.ts +2 -115
  7. package/dist/crumbs.js +1 -1179
  8. package/dist/{data-grid-C1sZX6n9.js → data-grid-Cqxuwgbb.js} +615 -210
  9. package/dist/{data-grid-D7qzmMMp.d.ts → data-grid-Csh5EJW9.d.ts} +55 -4
  10. package/dist/data-grid.d.ts +3 -3
  11. package/dist/data-grid.js +1 -1
  12. package/dist/{detail-list-q8bs67BM.js → detail-list-Bo5ze3SN.js} +234 -9
  13. package/dist/{detail-list-CKIKlszv.d.ts → detail-list-DSZ1DDor.d.ts} +48 -4
  14. package/dist/detail-list.d.ts +4 -4
  15. package/dist/detail-list.js +1 -1
  16. package/dist/{flat-list-DXMyDiSH.d.ts → flat-list-CT9kX3TQ.d.ts} +27 -4
  17. package/dist/flat-list.d.ts +2 -2
  18. package/dist/flat-list.js +123 -4
  19. package/dist/{grid-list-y6dWJYu3.js → grid-list-D0dJNVEF.js} +278 -76
  20. package/dist/{grid-list-0e7snW6t.d.ts → grid-list-DDLrWRxm.d.ts} +128 -22
  21. package/dist/grid-list.d.ts +3 -3
  22. package/dist/grid-list.js +1 -1
  23. package/dist/{incr-search-JIhwB49y.js → incr-search-CgbL-OlS.js} +140 -10
  24. package/dist/incr-search.d.ts +1 -1
  25. package/dist/incr-search.js +1 -1
  26. package/dist/index-9ttcdwlL.d.ts +71 -0
  27. package/dist/index-Bnm-kKGC.d.ts +475 -0
  28. package/dist/{index-KN2MW_xD.d.ts → index-CUErGhCg.d.ts} +31 -4
  29. package/dist/{index-DEVyVIn8.d.ts → index-D-lZNlHa.d.ts} +52 -6
  30. package/dist/index.d.ts +341 -21
  31. package/dist/index.js +2286 -884
  32. package/dist/{list-native-drag-controller-O2Nruqpi.d.ts → list-native-drag-controller-D_UrXipS.d.ts} +1 -1
  33. package/dist/{list-selection-controller-BeE70lbB.js → list-selection-controller-5L0Nj9tR.js} +87 -2
  34. package/dist/miller-DUqiUAWQ.js +2230 -0
  35. package/dist/miller.d.ts +4 -437
  36. package/dist/miller.js +1 -1815
  37. package/dist/narrowed-text.js +1 -1
  38. package/dist/pending-BGidHppL.js +91 -0
  39. package/dist/scroll-hover-guard-Cjw6179Q.js +35 -0
  40. package/dist/{split-layout-JK3xWVX2.d.ts → split-layout-CophkNeU.d.ts} +41 -3
  41. package/dist/{split-layout-Deoxy47d.js → split-layout-DTY4PJRp.js} +251 -19
  42. package/dist/split-layout.d.ts +1 -1
  43. package/dist/split-layout.js +1 -1
  44. package/dist/sunburst-tree.d.ts +2 -2
  45. package/dist/sunburst-tree.js +1 -1
  46. package/dist/{sunbursttree-list-yXwS-uEI.js → sunbursttree-list-B_M0YTEA.js} +299 -80
  47. package/dist/{sunbursttree-list--Z0uyh8O.d.ts → sunbursttree-list-DePuFYlU.d.ts} +40 -3
  48. package/dist/tree-CftHwtEb.js +146 -0
  49. package/dist/{tree-D8VxvR9u.js → tree-DAlgvaHB.js} +276 -48
  50. package/dist/{tree-component-C6UHO-w9.d.ts → tree-component-DgfkxmVY.d.ts} +19 -8
  51. package/dist/tree.d.ts +3 -3
  52. package/dist/tree.js +1 -1
  53. package/dist/types-LU1u0CB8.d.ts +87 -0
  54. package/dist/vertical-list-DyKPKGxK.js +1993 -0
  55. package/dist/{vertical-list-D8MksoTp.d.ts → vertical-list-SAx56U-l.d.ts} +31 -3
  56. package/dist/vertical-list.d.ts +3 -3
  57. package/dist/vertical-list.js +1 -1789
  58. package/dist/view-state-controller-BdyXWUUM.js +305 -0
  59. package/docs-user/README.md +2 -0
  60. package/docs-user/crumbs.md +27 -0
  61. package/docs-user/data-grid.md +79 -5
  62. package/docs-user/detail-list.md +69 -0
  63. package/docs-user/dock.md +63 -12
  64. package/docs-user/flat-list.md +27 -0
  65. package/docs-user/grid-list.md +69 -13
  66. package/docs-user/hyperbolic-tree.md +31 -4
  67. package/docs-user/icicle-tree.md +36 -0
  68. package/docs-user/index.json +12 -2
  69. package/docs-user/llms.txt +2 -0
  70. package/docs-user/miller.md +52 -21
  71. package/docs-user/multicol-list.md +50 -8
  72. package/docs-user/split-layout.md +45 -17
  73. package/docs-user/sunburst-tree.md +32 -2
  74. package/docs-user/tree.md +45 -6
  75. package/docs-user/treemap.md +32 -0
  76. package/docs-user/twosplit-layout.md +22 -0
  77. package/docs-user/user-catalog.md +1 -1
  78. package/docs-user/vertical-list.md +55 -7
  79. package/llms.txt +2 -0
  80. package/package.json +2 -2
@@ -1,13 +1,61 @@
1
- import { B as ListNativeDragStartEvent, I as ListDragPreviewRenderer, L as ListDragPreviewSourceResolver } from "./tree-component-C6UHO-w9.js";
2
- import { S as ListSearchController } from "./index-KN2MW_xD.js";
1
+ import { L as ListDragPreviewRenderer, R as ListDragPreviewSourceResolver, V as ListNativeDragStartEvent } from "./tree-component-DgfkxmVY.js";
2
+ import { a as ViewStateBase, t as GetViewStateOptions } from "./types-LU1u0CB8.js";
3
+ import "./index-9ttcdwlL.js";
4
+ import { S as ListSearchController } from "./index-CUErGhCg.js";
3
5
  import "./pan-text-CoYmt2qd.js";
4
6
  import { i as ListGroupChangeEvent, n as ListGroupBy, o as ListGroupInfo, s as ListGroupLabel, u as ListGroupToggleEvent } from "./grouping-CUKgmkMm.js";
5
7
  import { i as ListInteractionMode, l as ListSelectionMode } from "./list-selection-types-PjKdhF-a.js";
6
8
  import "./list-group-header.js";
7
- import "./list-native-drag-controller-O2Nruqpi.js";
9
+ import "./list-native-drag-controller-D_UrXipS.js";
8
10
  import { i as TickledItem, n as TickledController } from "./tickled-controller-DodxD3eQ.js";
9
11
  import { LitElement, PropertyValues, ReactiveController, ReactiveControllerHost, html } from "lit";
10
12
  import { CommandRegistry } from "@clinth/ui-commands";
13
+ //#region src/common/view-state/pending.d.ts
14
+ /**
15
+ * Pure helpers for *pending* view state: keyed intent that is waiting for a
16
+ * matching item to appear in the data.
17
+ *
18
+ * Pending sets / maps are insertion-ordered (oldest first) and capped so a
19
+ * long-running app with churning data can't grow them without bound.
20
+ */
21
+ /** Default cap on pending keys per facet. Oldest entries are dropped first. */
22
+ declare const MAX_PENDING_KEYS = 2000;
23
+ type PendingKeys = ReadonlySet<string>;
24
+ type PendingFlags = ReadonlyMap<string, boolean>;
25
+ declare const EMPTY_KEYS: PendingKeys;
26
+ declare const EMPTY_FLAGS: PendingFlags;
27
+ /**
28
+ * Split `keys` into those `has` reports present (to apply now) and those
29
+ * still missing (to keep pending). Returns the input set untouched as
30
+ * `missing` when nothing matched, so callers can cheaply detect "no change".
31
+ */
32
+ declare function partitionKeys(keys: PendingKeys, has: (key: string) => boolean): {
33
+ readonly present: readonly string[];
34
+ readonly missing: PendingKeys;
35
+ };
36
+ /** As {@link partitionKeys}, for keyed boolean flags (e.g. expanded / collapsed). */
37
+ declare function partitionFlags(flags: PendingFlags, has: (key: string) => boolean): {
38
+ readonly present: ReadonlyArray<readonly [string, boolean]>;
39
+ readonly missing: PendingFlags;
40
+ };
41
+ /** Keep at most `max` keys, dropping the oldest. */
42
+ declare function capKeys(keys: PendingKeys, max?: number): PendingKeys;
43
+ /** Keep at most `max` flags, dropping the oldest. */
44
+ declare function capFlags(flags: PendingFlags, max?: number): PendingFlags;
45
+ /** `flags` without `key` (same instance when absent). */
46
+ declare function withoutFlag(flags: PendingFlags, key: string): PendingFlags;
47
+ /** JSON record → flags map. Non-boolean values are ignored. */
48
+ declare function flagsFromRecord(record: Readonly<Record<string, boolean>> | undefined): PendingFlags;
49
+ /**
50
+ * Combine pending and live flags into one JSON record. Live entries win when
51
+ * a key is in both (it shouldn't be — matched keys leave pending).
52
+ */
53
+ declare function flagsToRecord(...sources: Array<Iterable<readonly [string, boolean]>>): Record<string, boolean>;
54
+ /** Pending keys followed by live keys, de-duplicated, as a JSON array. */
55
+ declare function keysToArray(...sources: Array<Iterable<string>>): string[];
56
+ /** Map `keys` to `true` flags — for components that record a plain key set. */
57
+ declare function keysAsFlags(keys: Iterable<string>, value?: boolean): Iterable<readonly [string, boolean]>;
58
+ //#endregion
11
59
  //#region src/interaction/list-drag-select-controller.d.ts
12
60
  /** Surface-relative rectangle: origin at the scroll container's top-left. */
13
61
  type DragSelectRect = {
@@ -220,6 +268,29 @@ declare class ListSelectionController<T> {
220
268
  * fires once if anything changed.
221
269
  */
222
270
  removeKeys(keys: Iterable<string>, resolve: (key: string) => T | undefined): void;
271
+ /** Selected keys waiting for their items to appear. */
272
+ get pendingKeys(): PendingKeys;
273
+ /**
274
+ * Restore a selection by key: keys that resolve are selected now (one
275
+ * `onChange`), the rest are kept pending for `resolvePending()`. Replaces
276
+ * any previous selection and pending keys. Unlike `selectKeys`, this works
277
+ * in `selectionMode="none"` only to the extent of holding keys pending.
278
+ */
279
+ restoreKeys(keys: Iterable<string>, resolve: (key: string) => T | undefined): void;
280
+ /**
281
+ * Select pending keys whose items now resolve (call after items are added
282
+ * or rebuilt). Fires `onChange` once when anything was selected; returns
283
+ * whether it did.
284
+ */
285
+ resolvePending(resolve: (key: string) => T | undefined): boolean;
286
+ /**
287
+ * Silently deselect `items` that left the data, keeping their keys pending
288
+ * so they are re-selected if the same keys come back (data reset, re-fetch).
289
+ * No `onChange`; returns whether the selection changed.
290
+ */
291
+ demote(items: Iterable<T>): boolean;
292
+ /** Drop pending keys without touching the live selection. */
293
+ clearPending(): void;
223
294
  /** Route a click on `item` according to the current interaction mode. */
224
295
  handleClick(item: T, gesture: ListSelectionGesture): void;
225
296
  /** Route a Space keypress on `item` according to the current interaction mode. */
@@ -234,7 +305,8 @@ declare class ListSelectionController<T> {
234
305
  remap(fn: (item: T) => T): void;
235
306
  /**
236
307
  * Hard reset: empty the selection and clear the anchor without firing
237
- * `onChange` (e.g. when all items are removed).
308
+ * `onChange` (e.g. when all items are removed). Keyed selected items become
309
+ * pending, so a rebuilt item set re-selects them via `resolvePending()`.
238
310
  */
239
311
  reset(): void;
240
312
  /**
@@ -404,6 +476,23 @@ interface GridListFullElementEventMap extends GridListElementEventMap {
404
476
  'list-native-drag-start': ListNativeDragStartEvent;
405
477
  }
406
478
  //#endregion
479
+ //#region src/grid-list/grid-list-view-state.d.ts
480
+ /**
481
+ * Pure view-state helpers for `ixfx-grid-list`. The component runs
482
+ * {@link reconcileGroups} whenever its item set or grouping changes, so a
483
+ * group's collapsed state survives the group disappearing (data reset,
484
+ * filter, streaming) and comes back when the group does. Scroll is captured
485
+ * and restored arithmetically from the virtual layout, along the main axis
486
+ * (`scrollLeft` in `row` orientation, `scrollTop` otherwise).
487
+ */
488
+ /**
489
+ * `ixfx-grid-list` view state. All fields are the shared base fields:
490
+ * `selected` (item keys), `focus` (keyboard cursor key), `scroll`, and
491
+ * `expanded` (group key → open; groups default open, so only `false` entries
492
+ * are ever recorded).
493
+ */
494
+ type GridListViewState = ViewStateBase;
495
+ //#endregion
407
496
  //#region src/grid-list/grid-list.d.ts
408
497
  declare class GridListElement<T = unknown> extends LitElement {
409
498
  #private;
@@ -417,18 +506,17 @@ declare class GridListElement<T = unknown> extends LitElement {
417
506
  */
418
507
  disableHoverTickle: boolean;
419
508
  /**
420
- * Ignore hover-target changes caused by scrolling. With virtualisation, a
421
- * wheel / trackpad gesture replaces the cell beneath a stationary pointer,
422
- * and the freshly rendered cell's `pointerenter` would move the cursor and
423
- * emit `list-tickle` even though the pointer never moved. With this set,
424
- * `pointerenter`-driven tickling is suppressed while a scroll gesture is
425
- * active — the host also carries a `data-scroll-hover-suppress` attribute
426
- * that gates the CSS `:hover` highlight, so the visual cursor holds still
427
- * too. Hover resumes on the next real pointer movement, or after the pointer
428
- * leaves and re-enters the list. Click selection, keyboard navigation, and
429
- * `focusKey()` are unaffected.
430
- */
431
- disableScrollHoverTickle: boolean;
509
+ * Let hover follow cells that scroll beneath a stationary pointer. Off by
510
+ * default: with virtualisation, a wheel / trackpad gesture replaces the cell
511
+ * beneath the pointer, and following its `pointerenter` would move the
512
+ * cursor and emit `list-tickle` even though the pointer never moved. While a
513
+ * scroll gesture is active those enters are ignored and the host carries a
514
+ * `data-scroll-hover-suppress` attribute that gates the CSS `:hover`
515
+ * highlight, so the visual cursor holds still too. Hover resumes on the next
516
+ * real pointer movement, or after the pointer leaves the list. Click
517
+ * selection, keyboard navigation, and `focusKey()` are unaffected.
518
+ */
519
+ scrollHoverTickle: boolean;
432
520
  itemSize: number;
433
521
  gap: number;
434
522
  /**
@@ -618,8 +706,9 @@ declare class GridListElement<T = unknown> extends LitElement {
618
706
  removeSelectedKeys(keys: Iterable<string>): void;
619
707
  /**
620
708
  * Data mode: replace the whole item array. Selection and cursor keep any key
621
- * that is still present; keys that vanish are dropped (one `list-select`).
622
- * A no-op in element mode.
709
+ * that is still present; keys that vanish are deselected silently and held
710
+ * as pending view state, so they come back if the key reappears. A no-op in
711
+ * element mode.
623
712
  */
624
713
  setItems(items: readonly T[]): void;
625
714
  /**
@@ -636,19 +725,36 @@ declare class GridListElement<T = unknown> extends LitElement {
636
725
  */
637
726
  updateItem(key: string, item: T): void;
638
727
  /**
639
- * Data mode: remove the items carrying these keys. Removed keys are dropped
640
- * from selection and cursor (via the `items` change). A no-op in element mode.
728
+ * Data mode: remove the items carrying these keys. Removed keys leave the
729
+ * selection and cursor silently (via the `items` change) and are held as
730
+ * pending view state. A no-op in element mode.
641
731
  */
642
732
  removeItems(keys: Iterable<string>): void;
643
733
  /**
644
734
  * Data mode: drop every item. Selection and cursor are reset and one
645
- * `list-select` with empty sets is emitted. A no-op in element mode.
735
+ * `list-select` with empty sets is emitted; their keys are held as pending
736
+ * view state, so re-adding the same keys restores them. A no-op in element
737
+ * mode.
646
738
  */
647
739
  clearItems(): void;
648
740
  /** Move the cursor to the item with this key and scroll it into view. */
649
741
  focusKey(key: string): void;
650
742
  /** The key of the item under the cursor, or `undefined`. */
651
743
  get tickledKey(): string | undefined;
744
+ /**
745
+ * Snapshot selection, collapsed groups, keyboard cursor and scroll as plain
746
+ * JSON, keyed by item / group key. Includes pending state for keys not
747
+ * currently present unless `includePending: false`.
748
+ */
749
+ getViewState(options?: GetViewStateOptions<GridListViewState>): GridListViewState;
750
+ /**
751
+ * Apply saved view state. Fields present replace the current ones; keys
752
+ * (items or groups) that aren't present yet are applied as they appear.
753
+ * Restored selection fires `list-select`.
754
+ */
755
+ setViewState(state: Partial<GridListViewState>): void;
756
+ /** Drop view state still waiting for its keys to appear. */
757
+ clearPendingViewState(): void;
652
758
  /** Element mode: the light-DOM element carrying `key`, or `undefined`. */
653
759
  getItem(key: string): Element | undefined;
654
760
  /** Element mode: the light-DOM element at effective index `index`. */
@@ -689,4 +795,4 @@ interface GridListElement {
689
795
  addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
690
796
  }
691
797
  //#endregion
692
- export { DragSelectRect as A, TreeKeyboardNavControllerConfig as C, ListSelectionGesture as D, ListSelectionControllerConfig as E, ListDragSelectControllerConfig as M, ListSelectionModes as O, TreeKeyboardNavController as S, ListSelectionController as T, GridListTickleDetail as _, GridListCaptionBackdrop as a, GridListViewportChangeEvent as b, GridListDataAdapter as c, GridListItemClickDetail as d, GridListItemClickEvent as f, GridListSelectEvent as g, GridListSelectDetail as h, GridListCaptionAlign as i, ListDragSelectController as j, isMultiMode as k, GridListElementEventMap as l, GridListOrientation as m, GridListActivateDetail as n, GridListCaptionPosition as o, GridListItemEventDetail as p, GridListActivateEvent as r, GridListCaptionStyle as s, GridListElement as t, GridListFullElementEventMap as u, GridListTickleEvent as v, TreeKeyboardNavItemId as w, TreeKeyboardNavCallbacks as x, GridListViewportChangeDetail as y };
798
+ export { isMultiMode as A, capKeys as B, TreeKeyboardNavController as C, ListSelectionControllerConfig as D, ListSelectionController as E, EMPTY_KEYS as F, partitionFlags as G, flagsToRecord as H, MAX_PENDING_KEYS as I, partitionKeys as K, PendingFlags as L, ListDragSelectController as M, ListDragSelectControllerConfig as N, ListSelectionGesture as O, EMPTY_FLAGS as P, PendingKeys as R, TreeKeyboardNavCallbacks as S, TreeKeyboardNavItemId as T, keysAsFlags as U, flagsFromRecord as V, keysToArray as W, GridListSelectEvent as _, GridListCaptionAlign as a, GridListViewportChangeDetail as b, GridListCaptionStyle as c, GridListFullElementEventMap as d, GridListItemClickDetail as f, GridListSelectDetail as g, GridListOrientation as h, GridListActivateEvent as i, DragSelectRect as j, ListSelectionModes as k, GridListDataAdapter as l, GridListItemEventDetail as m, GridListViewState as n, GridListCaptionBackdrop as o, GridListItemClickEvent as p, withoutFlag as q, GridListActivateDetail as r, GridListCaptionPosition as s, GridListElement as t, GridListElementEventMap as u, GridListTickleDetail as v, TreeKeyboardNavControllerConfig as w, GridListViewportChangeEvent as x, GridListTickleEvent as y, capFlags as z };
@@ -1,5 +1,5 @@
1
- import { B as ListNativeDragStartEvent, F as ListDragPreviewContext, I as ListDragPreviewRenderer, N as ListDragEntry, P as ListDragPreview, R as ListDragSource, z as ListNativeDragStartDetail } from "./tree-component-C6UHO-w9.js";
1
+ import { B as ListNativeDragStartDetail, F as ListDragPreview, I as ListDragPreviewContext, L as ListDragPreviewRenderer, P as ListDragEntry, V as ListNativeDragStartEvent, z as ListDragSource } from "./tree-component-DgfkxmVY.js";
2
+ import { _ as GridListSelectEvent, a as GridListCaptionAlign, b as GridListViewportChangeDetail, c as GridListCaptionStyle, d as GridListFullElementEventMap, f as GridListItemClickDetail, g as GridListSelectDetail, h as GridListOrientation, i as GridListActivateEvent, l as GridListDataAdapter, m as GridListItemEventDetail, n as GridListViewState, o as GridListCaptionBackdrop, p as GridListItemClickEvent, r as GridListActivateDetail, s as GridListCaptionPosition, t as GridListElement, u as GridListElementEventMap, v as GridListTickleDetail, x as GridListViewportChangeEvent, y as GridListTickleEvent } from "./grid-list-DDLrWRxm.js";
2
3
  import { i as ListGroupChangeEvent, l as ListGroupToggleDetail, n as ListGroupBy, o as ListGroupInfo, r as ListGroupChangeDetail, s as ListGroupLabel, u as ListGroupToggleEvent } from "./grouping-CUKgmkMm.js";
3
4
  import { i as ListInteractionMode, l as ListSelectionMode } from "./list-selection-types-PjKdhF-a.js";
4
- import { _ as GridListTickleDetail, a as GridListCaptionBackdrop, b as GridListViewportChangeEvent, c as GridListDataAdapter, d as GridListItemClickDetail, f as GridListItemClickEvent, g as GridListSelectEvent, h as GridListSelectDetail, i as GridListCaptionAlign, l as GridListElementEventMap, m as GridListOrientation, n as GridListActivateDetail, o as GridListCaptionPosition, p as GridListItemEventDetail, r as GridListActivateEvent, s as GridListCaptionStyle, t as GridListElement, u as GridListFullElementEventMap, v as GridListTickleEvent, y as GridListViewportChangeDetail } from "./grid-list-0e7snW6t.js";
5
- export { type GridListActivateDetail, type GridListActivateEvent, type GridListCaptionAlign, type GridListCaptionBackdrop, type GridListCaptionPosition, type GridListCaptionStyle, type GridListDataAdapter, GridListElement, type GridListElementEventMap, type GridListFullElementEventMap, type GridListItemClickDetail, type GridListItemClickEvent, type GridListItemEventDetail, type GridListOrientation, type GridListSelectDetail, type GridListSelectEvent, type GridListTickleDetail, type GridListTickleEvent, type GridListViewportChangeDetail, type GridListViewportChangeEvent, type ListDragEntry, type ListDragPreview, type ListDragPreviewContext, type ListDragPreviewRenderer, type ListDragSource, type ListGroupBy, type ListGroupChangeDetail, type ListGroupChangeEvent, type ListGroupInfo, type ListGroupLabel, type ListGroupToggleDetail, type ListGroupToggleEvent, type ListInteractionMode, type ListNativeDragStartDetail, type ListNativeDragStartEvent, type ListSelectionMode };
5
+ export { type GridListActivateDetail, type GridListActivateEvent, type GridListCaptionAlign, type GridListCaptionBackdrop, type GridListCaptionPosition, type GridListCaptionStyle, type GridListDataAdapter, GridListElement, type GridListElementEventMap, type GridListFullElementEventMap, type GridListItemClickDetail, type GridListItemClickEvent, type GridListItemEventDetail, type GridListOrientation, type GridListSelectDetail, type GridListSelectEvent, type GridListTickleDetail, type GridListTickleEvent, type GridListViewState, type GridListViewportChangeDetail, type GridListViewportChangeEvent, type ListDragEntry, type ListDragPreview, type ListDragPreviewContext, type ListDragPreviewRenderer, type ListDragSource, type ListGroupBy, type ListGroupChangeDetail, type ListGroupChangeEvent, type ListGroupInfo, type ListGroupLabel, type ListGroupToggleDetail, type ListGroupToggleEvent, type ListInteractionMode, type ListNativeDragStartDetail, type ListNativeDragStartEvent, type ListSelectionMode };
package/dist/grid-list.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import "./list-group-header.js";
2
- import { t as GridListElement } from "./grid-list-y6dWJYu3.js";
2
+ import { t as GridListElement } from "./grid-list-D0dJNVEF.js";
3
3
  export { GridListElement };
@@ -572,6 +572,8 @@ var ListSearchController = class {
572
572
  #matches = [];
573
573
  /** Whether a filter has been pushed to the adapter and still needs undoing. */
574
574
  #filterActive = false;
575
+ /** A filter committed with Enter (sticky) that stays applied while the popover is closed. */
576
+ #kept = false;
575
577
  constructor(host, options) {
576
578
  this.#host = host;
577
579
  this.#adapter = options.adapter;
@@ -579,6 +581,7 @@ var ListSearchController = class {
579
581
  this.#modes = LIST_SEARCH_MODES.filter((m) => requested.has(m));
580
582
  this.#mode = this.#modes[0] ?? `filter`;
581
583
  this.#placeholder = options.placeholder ?? `Search…`;
584
+ this.stickyFilter = options.stickyFilter ?? false;
582
585
  this.#search = createIncrSearch({
583
586
  selector: (i) => i.label,
584
587
  fzf: options.fzf
@@ -605,6 +608,7 @@ var ListSearchController = class {
605
608
  this.#modes = LIST_SEARCH_MODES.filter((m) => set.has(m));
606
609
  if (!this.#modes.includes(this.#mode)) {
607
610
  if (this.#open) this.close();
611
+ this.clearFilter();
608
612
  this.#mode = this.#modes[0] ?? `filter`;
609
613
  }
610
614
  this.#host.requestUpdate();
@@ -624,6 +628,10 @@ var ListSearchController = class {
624
628
  get matchIndex() {
625
629
  return this.#matchIndex;
626
630
  }
631
+ /** The query of a filter kept applied after the popover closed (see {@link stickyFilter}). */
632
+ get keptFilter() {
633
+ return this.#kept ? this.#query : void 0;
634
+ }
627
635
  hostConnected() {
628
636
  const container = this.#adapter.container?.();
629
637
  if (container) {
@@ -633,29 +641,47 @@ var ListSearchController = class {
633
641
  subtree: false
634
642
  });
635
643
  }
644
+ this.#host.addEventListener(`keydown`, this.#onHostKeydown);
636
645
  this.refresh();
637
646
  }
638
647
  hostDisconnected() {
639
648
  this.#observer?.disconnect();
640
649
  this.#observer = void 0;
641
- if (this.#open) this.#teardownEffects();
650
+ this.#host.removeEventListener(`keydown`, this.#onHostKeydown);
651
+ if (this.#open || this.#kept) this.#teardownEffects();
642
652
  this.#open = false;
653
+ this.#kept = false;
643
654
  this.#unsub?.();
644
655
  this.#unsub = void 0;
645
656
  this.#search.dispose();
646
657
  }
647
- /** Open the popover in `mode` (clamped to the available set). */
658
+ /**
659
+ * Open the popover in `mode` (clamped to the available set). When it is
660
+ * already open the query is kept: the mode switches if it differs and the
661
+ * input is refocused with its text selected.
662
+ */
648
663
  open(mode) {
649
664
  if (!this.enabled) return;
665
+ if (this.#open) {
666
+ this.switchMode(this.#modes.includes(mode) ? mode : this.#mode);
667
+ this.focus();
668
+ return;
669
+ }
650
670
  this.#mode = this.#modes.includes(mode) ? mode : this.#modes[0];
651
671
  this.#open = true;
652
- this.#query = ``;
672
+ const resume = this.#kept;
673
+ this.#kept = false;
674
+ if (!resume) this.#query = ``;
653
675
  this.#matchIndex = 0;
654
676
  this.#matches = [];
677
+ if (resume && this.#mode !== `filter` && this.#filterActive) {
678
+ this.#adapter.applyFilter(void 0);
679
+ this.#filterActive = false;
680
+ }
655
681
  this.refresh();
656
- this.#search.setQuery(``);
682
+ this.#search.setQuery(this.#query);
657
683
  this.#host.requestUpdate();
658
- this.#host.updateComplete.then(() => this.#focusInput());
684
+ this.#host.updateComplete.then(() => this.#focusInput(resume));
659
685
  }
660
686
  /** Close the popover, restoring any live filter. Selection / cursor stay put. */
661
687
  close() {
@@ -668,6 +694,16 @@ var ListSearchController = class {
668
694
  this.#host.requestUpdate();
669
695
  this.#host.focus();
670
696
  }
697
+ /** Remove a filter kept by {@link stickyFilter}. No-op when none is kept. */
698
+ clearFilter() {
699
+ if (!this.#kept) return;
700
+ this.#kept = false;
701
+ this.#teardownEffects();
702
+ this.#query = ``;
703
+ this.#matches = [];
704
+ this.#search.clearQuery();
705
+ this.#host.requestUpdate();
706
+ }
671
707
  setQuery(query) {
672
708
  this.#query = query;
673
709
  this.#matchIndex = 0;
@@ -685,6 +721,21 @@ var ListSearchController = class {
685
721
  this.#host.requestUpdate();
686
722
  this.#host.updateComplete.then(() => this.#focusInput());
687
723
  }
724
+ /**
725
+ * Step through {@link registeredModes} (wrapping) and switch to the result.
726
+ * Bound to Tab / Shift+Tab in the popover input.
727
+ */
728
+ cycleMode(delta) {
729
+ const n = this.#modes.length;
730
+ if (n < 2) return;
731
+ const index = this.#modes.indexOf(this.#mode);
732
+ this.switchMode(this.#modes[(index + delta + n) % n]);
733
+ }
734
+ /** Refocus the popover input (text selected) without touching the query. No-op when closed. */
735
+ focus() {
736
+ if (!this.#open) return;
737
+ this.#host.updateComplete.then(() => this.#focusInput(true));
738
+ }
688
739
  /** One-shot: turn the current match set into the selection, staying in the current mode. */
689
740
  selectMatches() {
690
741
  if (!this.#modes.includes(`select`)) return;
@@ -697,8 +748,19 @@ var ListSearchController = class {
697
748
  prevMatch() {
698
749
  this.#cycle(-1);
699
750
  }
700
- /** Enter: in `navigate` activate the current match; then close. */
751
+ /**
752
+ * Enter: in `navigate` activate the current match; then close. In `filter`
753
+ * with {@link stickyFilter} and a query, close but keep the filter.
754
+ */
701
755
  commit() {
756
+ if (this.#mode === `filter` && this.stickyFilter && this.#filterActive) {
757
+ this.#open = false;
758
+ this.#kept = true;
759
+ clearHighlights({ key: this.#highlightKey });
760
+ this.#host.requestUpdate();
761
+ this.#host.focus();
762
+ return;
763
+ }
702
764
  if (this.#mode === `navigate`) {
703
765
  const item = this.#itemFor(this.#matches[this.#matchIndex]);
704
766
  if (item) {
@@ -729,6 +791,7 @@ var ListSearchController = class {
729
791
  /** Popover markup. Pair with `listSearchStyles` in the host's `static styles`. */
730
792
  renderField() {
731
793
  if (!this.enabled) return nothing;
794
+ if (this.#kept) return this.#renderKeptFilter();
732
795
  const hasQuery = this.#query.trim().length > 0;
733
796
  const others = this.#modes.filter((m) => m !== this.#mode);
734
797
  const showSelectMatches = this.#modes.includes(`select`) && this.#mode !== `select`;
@@ -754,15 +817,33 @@ var ListSearchController = class {
754
817
  </div>
755
818
  ${others.length > 0 || showSelectMatches ? html`
756
819
  <div class="list-search-switch">
757
- ${others.map((m) => html`<button class="list-search-btn" @click=${() => this.switchMode(m)}>${m}</button>`)}
820
+ ${others.map((m) => html`<button class="list-search-btn" title=${this.#switchTitle(m)} @click=${() => this.switchMode(m)}>${m}</button>`)}
758
821
  ${showSelectMatches ? html`<button class="list-search-btn" @click=${() => this.selectMatches()}>Select matches</button>` : nothing}
759
822
  </div>
760
823
  ` : nothing}
761
824
  </div>
762
825
  `;
763
826
  }
764
- #focusInput() {
765
- this.#host.renderRoot?.querySelector(`.${INPUT_CLASS}`)?.focus();
827
+ #renderKeptFilter() {
828
+ return html`
829
+ <div class="list-search list-search-kept" part="search-kept">
830
+ <button class="list-search-kept-label" title="Edit filter" @click=${() => this.open(`filter`)}>
831
+ Filtered: <strong>${this.#query}</strong> <span class="list-search-count">(${this.#matches.length})</span>
832
+ </button>
833
+ <button class="list-search-icon-btn" title="Clear filter (Esc)" @click=${() => this.clearFilter()}><ixfx-icon name="close" size="14"></ixfx-icon></button>
834
+ </div>
835
+ `;
836
+ }
837
+ #focusInput(selectText = false) {
838
+ const input = this.#host.renderRoot?.querySelector(`.${INPUT_CLASS}`);
839
+ input?.focus();
840
+ if (selectText) input?.select();
841
+ }
842
+ /** Switch-row button title, naming the Tab chord that reaches `mode`. */
843
+ #switchTitle(mode) {
844
+ const n = this.#modes.length;
845
+ const index = this.#modes.indexOf(this.#mode);
846
+ return `Switch to ${mode} (${[this.#modes[(index + 1) % n] === mode ? `Tab` : void 0, this.#modes[(index - 1 + n) % n] === mode ? `Shift+Tab` : void 0].filter((k) => k !== void 0).join(` / `)})`;
766
847
  }
767
848
  #onInput = (e) => {
768
849
  this.setQuery(e.target.value);
@@ -779,6 +860,13 @@ var ListSearchController = class {
779
860
  e.stopPropagation();
780
861
  this.commit();
781
862
  break;
863
+ case `Tab`:
864
+ if (this.#modes.length > 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
865
+ e.preventDefault();
866
+ e.stopPropagation();
867
+ this.cycleMode(e.shiftKey ? -1 : 1);
868
+ }
869
+ break;
782
870
  case `ArrowDown`:
783
871
  if (this.#mode === `navigate`) {
784
872
  e.preventDefault();
@@ -791,6 +879,13 @@ var ListSearchController = class {
791
879
  }
792
880
  }
793
881
  };
882
+ /** Escape on the host clears a kept filter (the open popover's input handles its own Escape). */
883
+ #onHostKeydown = (e) => {
884
+ if (!this.#kept || this.#open || e.key !== `Escape` || e.defaultPrevented) return;
885
+ e.preventDefault();
886
+ e.stopPropagation();
887
+ this.clearFilter();
888
+ };
794
889
  #cycle(delta) {
795
890
  if (this.#matches.length === 0) return;
796
891
  const n = this.#matches.length;
@@ -808,9 +903,14 @@ var ListSearchController = class {
808
903
  return this.#adapter.highlightKey ?? DEFAULT_HIGHLIGHT_KEY;
809
904
  }
810
905
  #onState(state) {
811
- if (!this.#open) return;
906
+ if (!this.#open && !this.#kept) return;
812
907
  const hasQuery = state.query.trim().length > 0;
813
908
  this.#matches = hasQuery ? [...state.results] : [];
909
+ if (!this.#open) {
910
+ this.#adapter.applyFilter(new Set(this.#matches.map((m) => m.item.key)));
911
+ this.#host.requestUpdate();
912
+ return;
913
+ }
814
914
  if (this.#matchIndex >= this.#matches.length) this.#matchIndex = 0;
815
915
  this.#applyMode();
816
916
  this.#host.requestUpdate();
@@ -970,6 +1070,36 @@ const listSearchStyles = css`
970
1070
  .list-search-icon-btn:hover {
971
1071
  color: inherit;
972
1072
  }
1073
+
1074
+ .list-search-kept {
1075
+ flex-direction: row;
1076
+ align-items: center;
1077
+ padding: 2px 4px 2px 6px;
1078
+ }
1079
+
1080
+ .list-search-kept-label {
1081
+ padding: 0;
1082
+ background: transparent;
1083
+ border: none;
1084
+ color: inherit;
1085
+ cursor: pointer;
1086
+ font: inherit;
1087
+ white-space: nowrap;
1088
+ }
1089
+
1090
+ .list-search-kept-label:hover strong {
1091
+ text-decoration: underline;
1092
+ }
1093
+
1094
+ /* Matched characters for hosts that paint highlights inside their own shadow
1095
+ root, which a page-level ::highlight() rule cannot reach. Light-DOM items
1096
+ (e.g. detail-list rows) still need a page-level rule. */
1097
+ ::highlight(incr-search-highlight),
1098
+ ::highlight(grid-list-search),
1099
+ ::highlight(data-grid-search) {
1100
+ background: var(--search-highlight-bg, var(--accent, #60a5fa));
1101
+ color: var(--search-highlight-color, var(--accent-text, #000));
1102
+ }
973
1103
  `;
974
1104
  //#endregion
975
1105
  //#region src/incr-search/tree-list-search-adapter.ts
@@ -1,2 +1,2 @@
1
- import { A as FuzzySearch, C as ListSearchControllerOptions, D as IncrSearchTreeOptions, E as IncrSearchTreeController, F as IncrSearchChangeCallback, I as IncrSearchOptions, L as IncrSearchResult, M as HighlightOptions, N as HighlightTarget, O as createAcCompletionProvider, P as IncrSearch, R as IncrSearchState, S as ListSearchController, T as ListSearchMode, _ as DataListSearchAdapterOptions, a as RegisterListSearchCommandsOptions, b as LIST_SEARCH_MODES, c as applyHighlights, d as createFuzzySearch, f as ElementIncrSearch, g as createElementListSearchAdapter, h as ElementListSearchHost, i as LIST_SEARCH_COMMAND_IDS, j as FuzzySearchOptions, k as ElementIncrSearchOptions, l as buildHighlightTargets, m as ElementListSearchAdapterOptions, n as createTreeListSearchAdapter, o as registerListSearchCommands, p as createElementIncrSearch, r as listSearchStyles, s as createIncrSearch, t as TreeListSearchAdapterOptions, u as clearHighlights, v as DataListSearchItem, w as ListSearchHost, x as ListSearchAdapter, y as createDataListSearchAdapter } from "./index-KN2MW_xD.js";
1
+ import { A as FuzzySearch, C as ListSearchControllerOptions, D as IncrSearchTreeOptions, E as IncrSearchTreeController, F as IncrSearchChangeCallback, I as IncrSearchOptions, L as IncrSearchResult, M as HighlightOptions, N as HighlightTarget, O as createAcCompletionProvider, P as IncrSearch, R as IncrSearchState, S as ListSearchController, T as ListSearchMode, _ as DataListSearchAdapterOptions, a as RegisterListSearchCommandsOptions, b as LIST_SEARCH_MODES, c as applyHighlights, d as createFuzzySearch, f as ElementIncrSearch, g as createElementListSearchAdapter, h as ElementListSearchHost, i as LIST_SEARCH_COMMAND_IDS, j as FuzzySearchOptions, k as ElementIncrSearchOptions, l as buildHighlightTargets, m as ElementListSearchAdapterOptions, n as createTreeListSearchAdapter, o as registerListSearchCommands, p as createElementIncrSearch, r as listSearchStyles, s as createIncrSearch, t as TreeListSearchAdapterOptions, u as clearHighlights, v as DataListSearchItem, w as ListSearchHost, x as ListSearchAdapter, y as createDataListSearchAdapter } from "./index-CUErGhCg.js";
2
2
  export { type DataListSearchAdapterOptions, type DataListSearchItem, type ElementIncrSearch, type ElementIncrSearchOptions, type ElementListSearchAdapterOptions, type ElementListSearchHost, type FuzzySearch, type FuzzySearchOptions, type HighlightOptions, type HighlightTarget, type IncrSearch, type IncrSearchChangeCallback, IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, type IncrSearchTreeOptions as IncrSearchMillerOptions, type IncrSearchTreeOptions, type IncrSearchOptions, type IncrSearchResult, type IncrSearchState, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, type ListSearchAdapter, ListSearchController, type ListSearchControllerOptions, type ListSearchHost, type ListSearchMode, type RegisterListSearchCommandsOptions, type TreeListSearchAdapterOptions, applyHighlights, buildHighlightTargets, clearHighlights, createAcCompletionProvider, createDataListSearchAdapter, createElementIncrSearch, createElementListSearchAdapter, createFuzzySearch, createIncrSearch, createTreeListSearchAdapter, listSearchStyles, registerListSearchCommands };
@@ -1,3 +1,3 @@
1
1
  import { n as buildHighlightTargets, r as clearHighlights, t as applyHighlights } from "./highlight-B-eQrhhb.js";
2
- import { a as LIST_SEARCH_COMMAND_IDS, c as createElementListSearchAdapter, d as createDataListSearchAdapter, f as IncrSearchTreeController, h as createFuzzySearch, i as ListSearchController, m as createAcCompletionProvider, n as listSearchStyles, o as registerListSearchCommands, p as createIncrSearch, r as LIST_SEARCH_MODES, s as createElementIncrSearch, t as createTreeListSearchAdapter } from "./incr-search-JIhwB49y.js";
2
+ import { a as LIST_SEARCH_COMMAND_IDS, c as createElementListSearchAdapter, d as createDataListSearchAdapter, f as IncrSearchTreeController, h as createFuzzySearch, i as ListSearchController, m as createAcCompletionProvider, n as listSearchStyles, o as registerListSearchCommands, p as createIncrSearch, r as LIST_SEARCH_MODES, s as createElementIncrSearch, t as createTreeListSearchAdapter } from "./incr-search-CgbL-OlS.js";
3
3
  export { IncrSearchTreeController as IncrSearchMillerController, IncrSearchTreeController, LIST_SEARCH_COMMAND_IDS, LIST_SEARCH_MODES, ListSearchController, applyHighlights, buildHighlightTargets, clearHighlights, createAcCompletionProvider, createDataListSearchAdapter, createElementIncrSearch, createElementListSearchAdapter, createFuzzySearch, createIncrSearch, createTreeListSearchAdapter, listSearchStyles, registerListSearchCommands };
@@ -0,0 +1,71 @@
1
+ import { a as ViewStateBase, c as ViewStateChangeReason, n as ScrollState, t as GetViewStateOptions } from "./types-LU1u0CB8.js";
2
+ import { ReactiveController, ReactiveControllerHost } from "lit";
3
+ //#region src/common/view-state/view-state-controller.d.ts
4
+ type ViewStateControllerOptions<S extends ViewStateBase> = {
5
+ /** Component kind written into every state, e.g. `tree-list`. */
6
+ readonly kind: string;
7
+ /** Build the full current state (pending included). Called when the change event fires. */
8
+ readonly capture: () => S;
9
+ /** Trailing debounce for `view-state-change`, ms. Default 150. */
10
+ readonly debounceMs?: number;
11
+ };
12
+ /**
13
+ * Plumbing shared by every view-stateful component:
14
+ *
15
+ * - **change events** — `notify(reason)` coalesces changes into one debounced
16
+ * `view-state-change` event (strongest reason wins: user > data > restore),
17
+ * flushed immediately on disconnect so the last change is never lost.
18
+ * - **restore marking** — changes made inside `restoring(fn)` are reported as
19
+ * `restore`, so persistence code can skip writing back what it just loaded.
20
+ * - **validation** — `accepts(state)` rejects a state for another kind / version.
21
+ * - **pending scroll** — a scroll position waiting for its anchor item (or for
22
+ * enough content). Cancelled by the user's own scroll intent on the target
23
+ * given to `observeScrollTarget()`.
24
+ *
25
+ * The component owns its facets (selection, expansion, columns) and decides
26
+ * what's pending; this controller only carries the cross-cutting parts.
27
+ */
28
+ declare class ViewStateController<S extends ViewStateBase> implements ReactiveController {
29
+ #private;
30
+ constructor(host: ReactiveControllerHost & EventTarget, options: ViewStateControllerOptions<S>);
31
+ get kind(): string;
32
+ hostConnected(): void;
33
+ hostDisconnected(): void;
34
+ /** Record a change. Inside `restoring()` the reason is forced to `restore`. */
35
+ notify(reason?: ViewStateChangeReason): void;
36
+ /** Fire any pending change event now. */
37
+ flush(): void;
38
+ /** Run `fn` with every `notify()` inside it reported as `restore`. */
39
+ restoring<T>(fn: () => T): T;
40
+ /** True while inside `restoring()`. */
41
+ get isRestoring(): boolean;
42
+ /** Whether `state` is for this component kind and schema version. Warns when not. */
43
+ accepts(state: Partial<S> | null | undefined): state is Partial<S>;
44
+ /**
45
+ * Stamp `v` / `kind` onto `fields` and apply `include`. Components build
46
+ * their full field set and pass it through here from `getViewState()`.
47
+ */
48
+ shape(fields: Omit<S, `v` | `kind`>, options?: GetViewStateOptions<S>): S;
49
+ /**
50
+ * Watch `target` for user scroll intent (wheel / touch / pointer / nav key);
51
+ * any intent drops the pending scroll. Call again if the scroll container
52
+ * is replaced.
53
+ */
54
+ observeScrollTarget(target: EventTarget): void;
55
+ get pendingScroll(): ScrollState | undefined;
56
+ set pendingScroll(scroll: ScrollState | undefined);
57
+ /**
58
+ * Try the pending scroll with `apply`; it returns `true` once the position
59
+ * was fully reached (anchor present, content long enough), which clears it.
60
+ * Call after each render that may have added items.
61
+ */
62
+ applyPendingScroll(apply: (scroll: ScrollState) => boolean): void;
63
+ /**
64
+ * Call from the viewport's `scroll` listener. Ignored while a scroll restore
65
+ * is still pending; the event caused by a restore's own write is reported
66
+ * as `restore`, anything else as `user`.
67
+ */
68
+ notifyScroll(): void;
69
+ }
70
+ //#endregion
71
+ export { ViewStateControllerOptions as n, ViewStateController as t };