@ixfx/components 0.16.2 → 0.16.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.
Files changed (111) hide show
  1. package/bundle/index.d.ts +61 -7
  2. package/bundle/index.js +151 -143
  3. package/dist/ac-text.d.ts +1 -1
  4. package/dist/{ac-token-Cq7lsYf-.d.ts → ac-token-qEs3FsQv.d.ts} +1 -1
  5. package/dist/ac-token.d.ts +1 -1
  6. package/dist/ac-token.js +1 -1
  7. package/dist/{button-D1sPDUCM.d.ts → button-N92DKoWb.d.ts} +1 -1
  8. package/dist/button.d.ts +1 -1
  9. package/dist/colour-picker.d.ts +1 -1
  10. package/dist/{crumb-navigation-nnr881n-.d.ts → crumb-navigation-BbqfmkYs.d.ts} +1 -1
  11. package/dist/crumbs.d.ts +1 -1
  12. package/dist/{data-grid-CaPLWt5E.js → data-grid-8TsiF2ES.js} +7 -6
  13. package/dist/{data-grid-BAEpfTkb.d.ts → data-grid-CslzF16R.d.ts} +4 -4
  14. package/dist/data-grid.d.ts +1 -1
  15. package/dist/data-grid.js +1 -1
  16. package/dist/{detail-list-DK49TsJd.d.ts → detail-list-n1MbNbhF.d.ts} +4 -4
  17. package/dist/{detail-list-C3XKTRmE.js → detail-list-w3GXKO2k.js} +4 -3
  18. package/dist/detail-list.d.ts +2 -2
  19. package/dist/detail-list.js +1 -1
  20. package/dist/{flat-list-Bv4-qfjp.d.ts → flat-list-D_4y3wgn.d.ts} +4 -4
  21. package/dist/flat-list.d.ts +1 -1
  22. package/dist/flat-list.js +2 -2
  23. package/dist/{grid-list-DaeSHv9P.d.ts → grid-list-CqYS28NY.d.ts} +4 -2
  24. package/dist/{grid-list-51XsR2X2.js → grid-list-Cz2tKcNZ.js} +4 -3
  25. package/dist/grid-list.d.ts +2 -2
  26. package/dist/grid-list.js +1 -1
  27. package/dist/{grouped-item-lister-DZIjo9nO.d.ts → grouped-item-lister-CS4yInuw.d.ts} +2 -2
  28. package/dist/grouped-item-lister.d.ts +1 -1
  29. package/dist/{icon-DR_3rSyZ.d.ts → icon-CDGprSdE.d.ts} +1 -1
  30. package/dist/icons.d.ts +2 -2
  31. package/dist/{incr-search-2YaxUUA9.js → incr-search-Dc6gYpJG.js} +24 -4
  32. package/dist/incr-search.d.ts +1 -1
  33. package/dist/incr-search.js +1 -1
  34. package/dist/{index-BjRoPrwj.d.ts → index-Bm9a9S3_.d.ts} +3 -2
  35. package/dist/{index-garxxqgV.d.ts → index-CYndvTWH.d.ts} +1 -1
  36. package/dist/{index-BUB5SICW.d.ts → index-CoqtMSd9.d.ts} +1 -1
  37. package/dist/{index-Bz3loIAf.d.ts → index-DN2BWctP.d.ts} +1 -1
  38. package/dist/{index-CJ5fshD3.d.ts → index-UvnoZUAo.d.ts} +1 -1
  39. package/dist/{index-vdbbGE7N.d.ts → index-VAs9akt4.d.ts} +18 -2
  40. package/dist/{index-DXDz60XH.d.ts → index-YCDugjdu.d.ts} +4 -4
  41. package/dist/{index-DM9OLgTH.d.ts → index-ZaA90u09.d.ts} +2 -2
  42. package/dist/index.d.ts +37 -26
  43. package/dist/index.js +88 -13
  44. package/dist/item-appearance-BjcMyauw.js +29 -0
  45. package/dist/{labelled-input-base-Dp_9KP0G.d.ts → labelled-input-base-DiTme1JO.d.ts} +1 -1
  46. package/dist/labelled-radial-input.d.ts +2 -2
  47. package/dist/labelled-range-input.d.ts +1 -1
  48. package/dist/labelled-slider-input.d.ts +1 -1
  49. package/dist/list-group-header.d.ts +1 -1
  50. package/dist/{list-selection-controller-CJDdjH7E.js → list-selection-controller-D4iIrvJH.js} +12 -8
  51. package/dist/{list-selection-types-hj9Njei3.d.ts → list-selection-types-Dnamh2Lu.d.ts} +29 -3
  52. package/dist/{menu-item-B0zqw2xp.d.ts → menu-item-bF6narIz.d.ts} +1 -1
  53. package/dist/menu.d.ts +2 -2
  54. package/dist/{miller-BfrTcujx.js → miller-BrmBywNZ.js} +20 -9
  55. package/dist/miller.d.ts +1 -1
  56. package/dist/miller.js +1 -1
  57. package/dist/narrowed-text.js +1 -1
  58. package/dist/panel.d.ts +1 -1
  59. package/dist/{item-appearance-Bl6rMIID.js → partial-keys-Ck6CiprB.js} +1 -29
  60. package/dist/{split-layout-ChZ0Glrs.d.ts → split-layout-CophkNeU.d.ts} +1 -1
  61. package/dist/split-layout.d.ts +1 -1
  62. package/dist/sunburst-tree.d.ts +1 -1
  63. package/dist/sunburst-tree.js +1 -1
  64. package/dist/{sunbursttree-list-BAS0KrDG.d.ts → sunbursttree-list-Bk20giTF.d.ts} +1 -1
  65. package/dist/{sunbursttree-list-906G33ME.js → sunbursttree-list-DQF6DcsS.js} +1 -1
  66. package/dist/{tab-list-wC0xNz1b.d.ts → tab-list-Df371rqw.d.ts} +1 -1
  67. package/dist/tabs.d.ts +1 -1
  68. package/dist/timeline.d.ts +1 -1
  69. package/dist/{titlebar-B5zSTZnF.d.ts → titlebar-Tqu-VvMU.d.ts} +1 -1
  70. package/dist/titlebar.d.ts +1 -1
  71. package/dist/{tree-C_H1COTf.js → tree-BaXYyt5U.js} +185 -69
  72. package/dist/tree.d.ts +1 -1
  73. package/dist/tree.js +1 -1
  74. package/dist/{vertical-list-od4050Bo.d.ts → vertical-list-DtHX26JO.d.ts} +2 -2
  75. package/dist/{vertical-list-qMRIxZIT.js → vertical-list-o5hfqH87.js} +4 -3
  76. package/dist/vertical-list.d.ts +2 -2
  77. package/dist/vertical-list.js +1 -1
  78. package/docs-user/README.md +6 -0
  79. package/docs-user/bottom-drawer.md +2 -2
  80. package/docs-user/crumbs.md +1 -1
  81. package/docs-user/data-grid.md +3 -3
  82. package/docs-user/detail-list.md +3 -3
  83. package/docs-user/dock.md +3 -3
  84. package/docs-user/flat-list.md +7 -7
  85. package/docs-user/grid-list.md +4 -4
  86. package/docs-user/hyperbolic-tree.md +6 -6
  87. package/docs-user/icicle-tree.md +2 -2
  88. package/docs-user/incr-search.md +1 -1
  89. package/docs-user/index.json +26 -2
  90. package/docs-user/infra-handle.md +122 -0
  91. package/docs-user/infra-incremental.md +154 -0
  92. package/docs-user/infra-theming.md +510 -0
  93. package/docs-user/infra-view-state.md +210 -0
  94. package/docs-user/list-state.md +656 -0
  95. package/docs-user/llms.txt +6 -0
  96. package/docs-user/miller.md +5 -1
  97. package/docs-user/multicol-list.md +6 -0
  98. package/docs-user/panel.md +3 -3
  99. package/docs-user/select-horiz.md +1 -1
  100. package/docs-user/split-layout.md +2 -2
  101. package/docs-user/sunburst-tree.md +3 -3
  102. package/docs-user/titlebar.md +2 -2
  103. package/docs-user/tree.md +8 -2
  104. package/docs-user/treemap.md +2 -2
  105. package/docs-user/twosplit-layout.md +3 -3
  106. package/docs-user/user-selection-intents.md +86 -0
  107. package/docs-user/user-theming.md +1 -1
  108. package/docs-user/vertical-list.md +3 -3
  109. package/llms.txt +6 -0
  110. package/package.json +1 -1
  111. /package/dist/{index-DV3iaUBW.d.ts → index-DV3iaUBW2.d.ts} +0 -0
@@ -194,7 +194,7 @@ grid.addEventListener('list-select', ({ detail }) => {
194
194
  ```
195
195
 
196
196
  Caller-owned selection: a cancelable `list-select-intent` fires before every gesture, and `partialKeys` marks partially selected cells (`data-partial`, `part="item partial"`).
197
- See [Selection intents, partial rows, unloaded keys](../../docs/infra-selection-intents.md): `list-select-intent`, `partialKeys`, and keys that arrive later.
197
+ See [Selection intents, partial rows, unloaded keys](./user-selection-intents.md): `list-select-intent`, `partialKeys`, and keys that arrive later.
198
198
 
199
199
  Restore across a reload:
200
200
 
@@ -329,7 +329,7 @@ items directly, independent of search; the data-mode equivalent is
329
329
  `itemFilterPredicate` (`(item, index) => boolean`). Filtering removes items from
330
330
  layout and navigation without materialising anything in data mode. Limit modes
331
331
  with `search-modes="navigate filter"`. Full model:
332
- [`docs/infra-incremental.md`](../../docs/infra-incremental.md).
332
+ [`docs/infra-incremental.md`](./infra-incremental.md).
333
333
 
334
334
  ---
335
335
 
@@ -428,7 +428,7 @@ operations.
428
428
 
429
429
  ## Grouping
430
430
 
431
- Group items under collapsible headers, following `ixfx-data-grid` (see its [Grouping](../data-grid/README.md#grouping) section).
431
+ Group items under collapsible headers, following `ixfx-data-grid` (see its [Grouping](./data-grid.md#grouping) section).
432
432
 
433
433
  ```ts
434
434
  grid.groupBy = 'kind'; // data mode: item property · element mode: data-kind attribute
@@ -463,7 +463,7 @@ Differences from data-grid: group state is reset on every `groupBy` change, the
463
463
 
464
464
  Snapshot and restore how the grid is showing its data, separately from the
465
465
  data. The contract is shared with the other list components; see
466
- [`docs/infra-view-state.md`](../../docs/infra-view-state.md).
466
+ [`docs/infra-view-state.md`](./infra-view-state.md).
467
467
 
468
468
  ```ts
469
469
  import { bindViewState } from '@ixfx/components';
@@ -2,7 +2,7 @@
2
2
 
3
3
  `<ixfx-hyperbolictree-list>` — a hyperbolic (Poincaré disk) browser over tree data. The focused node sits at the disk's center; its neighbourhood fans outward, shrinking toward the edge with tree-graph distance. Clicking a node re-centers the disk on it with an animated Möbius transform — the underlying layout never changes, only the projection.
4
4
 
5
- It implements the shared `TreeComponent` interface (see [`src/tree/README.md`](../tree/README.md)), so `TreeDataModel`, `IncrSearchTreeController`, and any `TreeComponent`-typed code work unmodified.
5
+ It implements the shared `TreeComponent` interface (see [`src/tree/README.md`](./tree.md)), so `TreeDataModel`, `IncrSearchTreeController`, and any `TreeComponent`-typed code work unmodified.
6
6
 
7
7
  ---
8
8
 
@@ -36,7 +36,7 @@ It implements the shared `TreeComponent` interface (see [`src/tree/README.md`](.
36
36
 
37
37
  ## Data model
38
38
 
39
- Same `TreeNode` / `TreeItem` shapes as the rest of the tree family (see [`src/tree/README.md`](../tree/README.md#data-model)), including `root`, `model` (`TreeDataModel`), and `loadChildren` for lazy loading.
39
+ Same `TreeNode` / `TreeItem` shapes as the rest of the tree family (see [`src/tree/README.md`](./tree.md#data-model)), including `root`, `model` (`TreeDataModel`), and `loadChildren` for lazy loading.
40
40
 
41
41
  Keys only need to be unique **among siblings**: internally each node is identified by its key *path* from the root (`HyperbolicLayoutNode.id`), so data like a file system — where `readme.md` recurs in many folders — lays out and focuses correctly. Key-based lookups that can't know the path (`focusedKey` when given a bare key, `revealKey`, `::part(node-<key>)`) match the first node, depth-first, carrying that key.
42
42
 
@@ -58,7 +58,7 @@ el.root = {
58
58
  | `model` | `TreeDataModel \| undefined` | `undefined` | Shared data model; takes precedence over `loadChildren` |
59
59
  | `loadChildren` | `LoadChildrenCallback \| undefined` | `undefined` | Callback for lazy child loading |
60
60
  | `restoreLoads` | `boolean` | `true` | Whether restoring a saved `center` may load its unloaded ancestors. See [View state](#view-state) |
61
- | `interactionMode` (`interaction-mode`) | `'implicit' \| 'standard' \| 'manual'` | `'standard'` | Gesture mapping — see [`src/tree/README.md`](../tree/README.md#interactionmode-ixfx-tree-list--ixfx-miller-list) (`'checked'` is unsupported here) |
61
+ | `interactionMode` (`interaction-mode`) | `'implicit' \| 'standard' \| 'manual'` | `'standard'` | Gesture mapping — see [`src/tree/README.md`](./tree.md#interactionmode-ixfx-tree-list--ixfx-miller-list) (`'checked'` is unsupported here) |
62
62
  | `selectionFilter` (`selection-filter`) | `'none' \| 'leaf' \| 'branch' \| 'all'` | `'all'` | Which node kinds can be selected |
63
63
  | `exclusivity` | `TreeExclusivity` | `'none'` | Accepted for interface compatibility, ignored |
64
64
  | `filterPredicate` | `TreeFilterPredicate \| undefined` | `undefined` | Non-matching nodes (and branches with no matching descendant) are dimmed, not hidden |
@@ -80,7 +80,7 @@ el.root = {
80
80
  | `focusAnimationDuration` (`focus-animation-duration`) | `number` | `400` | Duration (ms) of the animated re-center on focus change; `0` snaps instantly. Respects `prefers-reduced-motion` |
81
81
  | `edgeRenderer` | `HyperbolicEdgeRenderer \| undefined` | `undefined` | Custom renderer for edges, in place of the default geodesic-arc path |
82
82
  | `disableKeyboardNavigation` (`disable-keyboard-navigation`) | `boolean` | `false` | Disables the keyboard handlers below |
83
- | `searchModes` (`search-modes`) | `string \| undefined` | `undefined` | Space-separated subset of `navigate select filter` — see [`src/tree/README.md`](../tree/README.md#incremental-search) |
83
+ | `searchModes` (`search-modes`) | `string \| undefined` | `undefined` | Space-separated subset of `navigate select filter` — see [`src/tree/README.md`](./tree.md#incremental-search) |
84
84
  | `selectedNode` | `TreeNode \| undefined` | `undefined` | Primary (most recent) selected node |
85
85
  | `selectedNodes` | `ReadonlySet<TreeNode>` | `{}` | Full selection set (read-only) |
86
86
  | `focusedNode` | `TreeNode \| undefined` (read-only) | true root | The node currently centered on the disk. Pure view-state — `root` is never mutated by focus changes |
@@ -256,7 +256,7 @@ const nodes = el.getNodes(); // ReadonlyArray<{ node: TreeNode; depth: number }>
256
256
 
257
257
  ## View state
258
258
 
259
- Implements the shared view-state API (`getViewState()` / `setViewState()` / `clearPendingViewState()` / `view-state-change`) — see [`docs/infra-view-state.md`](../../docs/infra-view-state.md). `kind` is `hyperbolictree-list`.
259
+ Implements the shared view-state API (`getViewState()` / `setViewState()` / `clearPendingViewState()` / `view-state-change`) — see [`docs/infra-view-state.md`](./infra-view-state.md). `kind` is `hyperbolictree-list`.
260
260
 
261
261
  ```typescript
262
262
  type HyperbolicTreeListViewState = ViewStateBase & {
@@ -386,4 +386,4 @@ type HyperbolicLayoutEdge = {
386
386
  };
387
387
  ```
388
388
 
389
- See [`src/tree/README.md`](../tree/README.md) for `TreeNode`, `TreeItem` (including `weight`), `TreeDataModel`, `TreeComponent`, and incremental search — all shared with the rest of the tree family.
389
+ See [`src/tree/README.md`](./tree.md) for `TreeNode`, `TreeItem` (including `weight`), `TreeDataModel`, `TreeComponent`, and incremental search — all shared with the rest of the tree family.
@@ -2,13 +2,13 @@
2
2
 
3
3
  `<ixfx-icicletree-list>` — an icicle (partition) view over tree data. Each depth is a band; each node a rectangle sized by its `valueAccessor` share of its parent. Clicking a branch zooms so it becomes the visual root, with its ancestors in a breadcrumb strip.
4
4
 
5
- It implements the shared `TreeComponent` interface (see [`src/tree/README.md`](../tree/README.md)). The [sunburst](../sunburst-tree/) is its polar counterpart.
5
+ It implements the shared `TreeComponent` interface (see [`src/tree/README.md`](./tree.md)). The [sunburst](./sunburst-tree.md) is its polar counterpart.
6
6
 
7
7
  ---
8
8
 
9
9
  ## View state
10
10
 
11
- Implements the shared view-state API (`getViewState()` / `setViewState()` / `clearPendingViewState()` / `view-state-change`) — see [`docs/infra-view-state.md`](../../docs/infra-view-state.md). `kind` is `icicletree-list`.
11
+ Implements the shared view-state API (`getViewState()` / `setViewState()` / `clearPendingViewState()` / `view-state-change`) — see [`docs/infra-view-state.md`](./infra-view-state.md). `kind` is `icicletree-list`.
12
12
 
13
13
  | Field | Type | Meaning |
14
14
  |---|---|---|
@@ -6,7 +6,7 @@ Utilities for incremental (type-ahead) search over data collections or DOM-backe
6
6
  popover in `ixfx-grid-list` / `ixfx-vertical-list` / `ixfx-detail-list` /
7
7
  `ixfx-data-grid` / `ixfx-tree-list` / `ixfx-miller-list`. Three modes
8
8
  (navigate / select / filter), driven by `list.search.*` commands. See
9
- [`docs/infra-incremental.md`](../../docs/infra-incremental.md) — start there
9
+ [`docs/infra-incremental.md`](./infra-incremental.md) — start there
10
10
  for anything component-facing.
11
11
  - **`createIncrSearch`** — the low-level fuzzy engine (fzf) both the controller
12
12
  and the helpers below are built on.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
- "version": "0.16.2",
4
- "generated": "2026-11-02T19:16:08.207Z",
3
+ "version": "0.16.4",
4
+ "generated": "2026-11-02T21:02:52.405Z",
5
5
  "components": [
6
6
  {
7
7
  "name": "ac-text",
@@ -305,14 +305,38 @@
305
305
  }
306
306
  ],
307
307
  "overviews": [
308
+ {
309
+ "name": "infra-handle.md",
310
+ "path": "docs-user/infra-handle.md"
311
+ },
312
+ {
313
+ "name": "infra-incremental.md",
314
+ "path": "docs-user/infra-incremental.md"
315
+ },
316
+ {
317
+ "name": "infra-theming.md",
318
+ "path": "docs-user/infra-theming.md"
319
+ },
308
320
  {
309
321
  "name": "infra-tickled.md",
310
322
  "path": "docs-user/infra-tickled.md"
311
323
  },
324
+ {
325
+ "name": "infra-view-state.md",
326
+ "path": "docs-user/infra-view-state.md"
327
+ },
328
+ {
329
+ "name": "list-state.md",
330
+ "path": "docs-user/list-state.md"
331
+ },
312
332
  {
313
333
  "name": "user-catalog.md",
314
334
  "path": "docs-user/user-catalog.md"
315
335
  },
336
+ {
337
+ "name": "user-selection-intents.md",
338
+ "path": "docs-user/user-selection-intents.md"
339
+ },
316
340
  {
317
341
  "name": "user-theming.md",
318
342
  "path": "docs-user/user-theming.md"
@@ -0,0 +1,122 @@
1
+ # Drag / resize handles
2
+
3
+ A **handle** is the grab affordance for moving or resizing an object — a
4
+ splitter grip, a resize edge, a reorder handle. `handleStyles`
5
+ (`src/styles/handle.js`) is the one shared look, so every draggable component
6
+ reads the same and themes through the same variables.
7
+
8
+ Before this module the library had six unrelated handle idioms (two of them
9
+ near-duplicate 3-dot implementations, two hard-coded Unicode glyphs). Don't add a
10
+ seventh.
11
+
12
+ ---
13
+
14
+ ## Usage
15
+
16
+ ```ts
17
+ import { themeFallbacks } from '../styles/fallbacks.js';
18
+ import { handleStyles } from '../styles/handle.js';
19
+
20
+ class MyComponent extends LitElement {
21
+ static styles = [themeFallbacks, handleStyles, css`
22
+ .hit-zone { /* your resize / drag target */ }
23
+ `];
24
+
25
+ render() {
26
+ return html`
27
+ <div class="hit-zone" @pointerdown=${this.#onDown}>
28
+ <span class="ixfx-handle" data-handle="flat" data-shape="horizontal" part="handle"></span>
29
+ </div>`;
30
+ }
31
+ }
32
+ ```
33
+
34
+ Bind pointer handlers to the **hit-zone**, not the handle — the handle is
35
+ `pointer-events: none`.
36
+
37
+ ---
38
+
39
+ ## Two independent axes
40
+
41
+ | Attribute | Values | Meaning |
42
+ |---|---|---|
43
+ | `data-handle` | `flat` (default) · `dotted` · `dimpled` · `grated` | **texture** — how the mark looks |
44
+ | `data-shape` | `horizontal` (default) · `vertical` · `spot` | **topology** — a wide bar, a tall bar, or a small centred point handle |
45
+
46
+ Any texture combines with any shape.
47
+
48
+ - **flat** — a solid rounded capsule (a bar) or dot (`spot`), filled with
49
+ `--ixfx-handle-color`. The `ixfx-bottom-drawer` resize handle.
50
+ - **dotted** — marks in the [Lucide *grip*](https://lucide.dev/icons/grip)
51
+ arrangement: **three in a row** (`horizontal`), **three in a column**
52
+ (`vertical`), or a **3×3 block** (`spot`). Drawn as one `::before` mark plus
53
+ `box-shadow` copies, so each mark's corner rounding is `--ixfx-handle-radius`
54
+ (→ the theme `--radius-l`): **round** normally, **square** under a flat theme
55
+ (`--radius-l: 0`, e.g. `theme-cyberpunk`). Tinted by `--ixfx-handle-color`. The
56
+ `ixfx-data-grid` column reorder handle, `ixfx-timeline-track-tab` reorder
57
+ handle, `ixfx-dock-host` junction handles.
58
+ - **dimpled** — that same layout, but each **bar** mark is pressed in: a lighter
59
+ copy peeking out above-left and a darker copy below-right (`spot` stays plain —
60
+ the 3D is imperceptible at that size). Splitter grips (`ixfx-split-layout`,
61
+ `ixfx-twosplit-layout`).
62
+ - **grated** — fine 45° knurling: parallel ridges, each a **lit edge, the base
63
+ colour, then a shaded edge**. `spot` is the same hatch cropped square.
64
+
65
+ `dotted`, `dimpled` and `grated` are texture only — no plate, box or border; the
66
+ gaps are transparent, so the surface behind shows through. The base is
67
+ `--ixfx-handle-color` (`currentColor`); `--ixfx-handle-highlight` /
68
+ `--ixfx-handle-shadow` default to that colour lightened / darkened, so `dimpled`
69
+ stays as prominent as `dotted` and re-tones with the theme.
70
+
71
+ ---
72
+
73
+ ## Active state
74
+
75
+ The handle goes to `--ixfx-handle-opacity-active` (and, for `flat` / `dotted`,
76
+ `--ixfx-handle-color-active`) when:
77
+
78
+ - its **immediate parent** (the hit-zone) is `:hover` or `:focus-visible`, or
79
+ - the hit-zone or the handle carries `[data-active]`.
80
+
81
+ While a drag is live the pointer may leave the hit-zone, so set `[data-active]`
82
+ on the hit-zone yourself for the duration and the handle stays lit.
83
+
84
+ To suppress the reveal entirely, set `--ixfx-handle-opacity-active` equal to
85
+ `--ixfx-handle-opacity`.
86
+
87
+ ---
88
+
89
+ ## CSS variables
90
+
91
+ Defaults resolve through `themeFallbacks`. Override on the handle element or any
92
+ ancestor (`:host`, the hit-zone, …).
93
+
94
+ | Variable | Default | Purpose |
95
+ |---|---|---|
96
+ | `--ixfx-handle-color` | `var(--surface-muted-text)` | `flat` / `dotted` fill |
97
+ | `--ixfx-handle-color-active` | `var(--accent)` | `flat` / `dotted` hover / focus / `[data-active]` |
98
+ | `--ixfx-handle-thickness` | `6px` | cross-axis size of a bar |
99
+ | `--ixfx-handle-length` | `44px` | long-axis size of a bar (`100%` fills) |
100
+ | `--ixfx-handle-spot-size` | `14px` | width = height of a `spot` |
101
+ | `--ixfx-handle-radius` | `var(--radius-l)` | `flat` capsule + `dotted` / `dimpled` mark rounding (0 ⇒ squares) |
102
+ | `--ixfx-handle-opacity` | `0.55` | rest opacity |
103
+ | `--ixfx-handle-opacity-active` | `1` | active opacity |
104
+ | `--ixfx-handle-dot-size` | `4px` | mark size (dotted / dimpled) |
105
+ | `--ixfx-handle-dot-gap` | `length / 6` (bar) · `spot / 3` | mark centre-to-centre step |
106
+ | `--ixfx-handle-tile` | `5px` | knurl period (grated) |
107
+ | `--ixfx-handle-highlight` | `color-mix(currentColor 35%, #fff)` | lit deboss edge (dimpled / grated) |
108
+ | `--ixfx-handle-shadow` | `color-mix(currentColor 60%, #000)` | shaded deboss edge (dimpled / grated) |
109
+
110
+ ---
111
+
112
+ ## Notes & limits
113
+
114
+ - **Host-as-handle** (`:host(.ixfx-handle)`) is not supported yet — always wrap
115
+ the class on a child element inside the hit-zone.
116
+ - The handle exposes `part="handle"` (rename per component where a more specific
117
+ part name already exists, e.g. `part="grip"` / `part="reorder-handle"`).
118
+ - No motion beyond the colour / opacity transition; nothing to gate for reduced
119
+ motion (the module still drops the transition under
120
+ `prefers-reduced-motion: reduce`).
121
+
122
+ See it live: `demo/handle.html` (4 textures × 3 shapes + variable knobs).
@@ -0,0 +1,154 @@
1
+ # Incremental search
2
+
3
+ Type-ahead search shared by the list and tree components. One popover, one
4
+ `ReactiveController`, three behaviours ("modes"), triggered by
5
+ `@clinth/ui-commands` commands. Built on the fuzzy engine in
6
+ `src/incr-search/incr-search.ts` (fzf).
7
+
8
+ Supported today: `ixfx-grid-list`, `ixfx-vertical-list`, `ixfx-detail-list`,
9
+ `ixfx-data-grid`, `ixfx-tree-list`, `ixfx-miller-list`.
10
+
11
+ ---
12
+
13
+ ## The three modes
14
+
15
+ | Mode | As you type… | Enter | On close |
16
+ |---|---|---|---|
17
+ | **navigate** | the cursor moves to the best match; ↑/↓ cycle between matches | activates the current match, closes | cursor stays |
18
+ | **select** | the selection becomes the set of matching items, live | closes | selection stays |
19
+ | **filter** | items that don't match are hidden | closes (keeps the filter with `stickyFilter`) | filter is removed |
20
+
21
+ All modes highlight the matched characters (CSS Custom Highlights) where the
22
+ component supports it.
23
+
24
+ ### Conversions
25
+
26
+ The popover is interactive. While it's open you can, without retyping:
27
+
28
+ - **switch mode** — buttons for each other enabled mode, or **Tab** /
29
+ **Shift+Tab** in the input to cycle forward / back (`cycleMode(±1)`). The old
30
+ mode's effect is undone first (e.g. leaving *filter* un-hides), then the new
31
+ mode is applied to the current query. Example: start in *filter*, hit
32
+ **navigate** to stop hiding and just move the cursor.
33
+ - **re-open** — running a search command while the popover is open keeps the
34
+ query: it switches to that command's mode and refocuses the input with the
35
+ text selected. `focus()` refocuses without changing mode.
36
+ - **Select matches** — a one-shot button (shown in *navigate* / *filter* when
37
+ *select* is enabled) that turns the current match set into the selection while
38
+ staying in the current mode.
39
+
40
+ ### Sticky filter
41
+
42
+ By default a filter lasts only while the popover is open. Set
43
+ `list.search.stickyFilter = true` (or the `stickyFilter` controller option) so
44
+ that Enter in *filter* mode closes the popover but keeps the filter. A
45
+ "Filtered: …" chip (part `search-kept`) stays in the popover's place, and the
46
+ filter re-applies as items change. Clear it with the chip's ×, Escape on the
47
+ list, or `search.clearFilter()`. Clicking the chip, or running any search
48
+ command, reopens the popover with the kept query. `search.keptFilter` is the
49
+ kept query (or `undefined`).
50
+
51
+ ### Which modes are offered
52
+
53
+ The effective set is **component capability ∩ the `search-modes` attribute**:
54
+
55
+ - **capability** — most components offer all three. A component with no
56
+ selection concept would omit `select`, etc.
57
+ - **`search-modes`** attribute — a space-separated subset of
58
+ `navigate select filter`. Unset ⇒ the component default. Empty (`search-modes=""`)
59
+ ⇒ search disabled: no commands registered, no popover.
60
+
61
+ ```html
62
+ <ixfx-grid-list search-modes="navigate filter"></ixfx-grid-list>
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Commands
68
+
69
+ Each component owns a `@clinth/ui-commands` `CommandRegistry` (exposed as
70
+ `el.registry`) and registers one command per enabled mode:
71
+
72
+ | id | opens |
73
+ |---|---|
74
+ | `list.search.navigate` | popover in navigate mode |
75
+ | `list.search.select` | popover in select mode |
76
+ | `list.search.filter` | popover in filter mode |
77
+
78
+ There is **no default keybinding** — a consumer binds a key or button:
79
+
80
+ ```ts
81
+ el.registry.invoke('list.search.filter');
82
+ // or via @clinth/ui-commands' InputManager / a data-command button
83
+ ```
84
+
85
+ `ixfx-grouped-item-lister.dispatch('list.search.filter')` fans the command out to
86
+ every sub-list.
87
+
88
+ ---
89
+
90
+ ## Adding search to a new component
91
+
92
+ 1. **Adapter.** Implement `ListSearchAdapter<TItem>` (`src/incr-search/list-search-controller.ts`):
93
+ `getItems` / `getLabel` / `getKey`, then `focusItem` (navigate),
94
+ `selectItems` (select), `applyFilter` (filter; `undefined` clears).
95
+ Optional: `activateItem`, the `highlight*` hooks, `container` (an element
96
+ whose child-list mutations should re-`refresh()` the item set).
97
+ - Element-based lists: `createElementListSearchAdapter(this, { itemSelector })`.
98
+ - Tree-based: `createTreeListSearchAdapter(this, { labelSelector })`.
99
+
100
+ 2. **Controller.** One field:
101
+
102
+ ```ts
103
+ readonly search = new ListSearchController<TItem>(this, {
104
+ adapter: /* from step 1 */,
105
+ });
106
+ ```
107
+
108
+ 3. **Styles + field.** Add `listSearchStyles` to `static styles` and
109
+ `${this.search.renderField()}` to `render()`, inside a `position: relative`
110
+ ancestor that is **not** the scroll container (so the popover doesn't scroll
111
+ away — put `overflow:auto` on an inner element, `position:relative` on `:host`).
112
+
113
+ 4. **Modes + commands.** Keep a `search-modes` reflected property; on connect and
114
+ whenever it changes:
115
+
116
+ ```ts
117
+ this.search.setModes(/* capability ∩ search-modes */);
118
+ this.#unregister?.();
119
+ this.#unregister = registerListSearchCommands(this.registry, this.search);
120
+ ```
121
+
122
+ Own the registry (`createCommandRegistry()`), expose it as `registry`,
123
+ unregister on disconnect. Route any `dispatch(commandId, args)` to
124
+ `registry.invoke(commandId, args)`.
125
+
126
+ 5. **Keyboard.** `if (this.search.isOpen) return;` at the top of the host's
127
+ keydown handler so list navigation doesn't fight the field.
128
+
129
+ ---
130
+
131
+ ## Notes
132
+
133
+ - **Virtualised lists** (`ixfx-grid-list`, `ixfx-data-grid`) — filter genuinely
134
+ drops items from layout/virtualisation. Character highlights are best-effort:
135
+ applied to whatever is in the rendered window (data-grid does no highlighting —
136
+ a match position indexes into the whole-row text, which spans cells).
137
+ - **Trees** snapshot the whole node set when the popover opens (`getNodes()`
138
+ returns every node regardless of expansion or an active filter), so refining a
139
+ query can always re-surface a node. `navigate` expands the match's ancestors;
140
+ `filter` prunes via `filterPredicate` but does **not** auto-expand collapsed
141
+ ancestors of a deep match.
142
+ - **Highlights** painted inside a host's shadow root are styled by
143
+ `listSearchStyles` (keys `incr-search-highlight`, `grid-list-search`,
144
+ `data-grid-search`; tokens `--search-highlight-bg` / `--search-highlight-color`).
145
+ A host using another key must add its own rule. Text in the light DOM (the
146
+ element lists paint onto the item elements) needs a page-level
147
+ `::highlight(<key>)` rule:
148
+ `::highlight(vertical-list-search) { background: var(--accent); color: var(--accent-text); }`.
149
+ - Everything is inert when the effective mode set is empty.
150
+
151
+ ## Deprecated
152
+
153
+ `createElementIncrSearch` and `IncrSearchTreeController` (the external-`<input>`
154
+ filter-only controllers) still work but are superseded by the built-in popover.