@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.
- package/bundle/index.d.ts +61 -7
- package/bundle/index.js +151 -143
- package/dist/ac-text.d.ts +1 -1
- package/dist/{ac-token-Cq7lsYf-.d.ts → ac-token-qEs3FsQv.d.ts} +1 -1
- package/dist/ac-token.d.ts +1 -1
- package/dist/ac-token.js +1 -1
- package/dist/{button-D1sPDUCM.d.ts → button-N92DKoWb.d.ts} +1 -1
- package/dist/button.d.ts +1 -1
- package/dist/colour-picker.d.ts +1 -1
- package/dist/{crumb-navigation-nnr881n-.d.ts → crumb-navigation-BbqfmkYs.d.ts} +1 -1
- package/dist/crumbs.d.ts +1 -1
- package/dist/{data-grid-CaPLWt5E.js → data-grid-8TsiF2ES.js} +7 -6
- package/dist/{data-grid-BAEpfTkb.d.ts → data-grid-CslzF16R.d.ts} +4 -4
- package/dist/data-grid.d.ts +1 -1
- package/dist/data-grid.js +1 -1
- package/dist/{detail-list-DK49TsJd.d.ts → detail-list-n1MbNbhF.d.ts} +4 -4
- package/dist/{detail-list-C3XKTRmE.js → detail-list-w3GXKO2k.js} +4 -3
- package/dist/detail-list.d.ts +2 -2
- package/dist/detail-list.js +1 -1
- package/dist/{flat-list-Bv4-qfjp.d.ts → flat-list-D_4y3wgn.d.ts} +4 -4
- package/dist/flat-list.d.ts +1 -1
- package/dist/flat-list.js +2 -2
- package/dist/{grid-list-DaeSHv9P.d.ts → grid-list-CqYS28NY.d.ts} +4 -2
- package/dist/{grid-list-51XsR2X2.js → grid-list-Cz2tKcNZ.js} +4 -3
- package/dist/grid-list.d.ts +2 -2
- package/dist/grid-list.js +1 -1
- package/dist/{grouped-item-lister-DZIjo9nO.d.ts → grouped-item-lister-CS4yInuw.d.ts} +2 -2
- package/dist/grouped-item-lister.d.ts +1 -1
- package/dist/{icon-DR_3rSyZ.d.ts → icon-CDGprSdE.d.ts} +1 -1
- package/dist/icons.d.ts +2 -2
- package/dist/{incr-search-2YaxUUA9.js → incr-search-Dc6gYpJG.js} +24 -4
- package/dist/incr-search.d.ts +1 -1
- package/dist/incr-search.js +1 -1
- package/dist/{index-BjRoPrwj.d.ts → index-Bm9a9S3_.d.ts} +3 -2
- package/dist/{index-garxxqgV.d.ts → index-CYndvTWH.d.ts} +1 -1
- package/dist/{index-BUB5SICW.d.ts → index-CoqtMSd9.d.ts} +1 -1
- package/dist/{index-Bz3loIAf.d.ts → index-DN2BWctP.d.ts} +1 -1
- package/dist/{index-CJ5fshD3.d.ts → index-UvnoZUAo.d.ts} +1 -1
- package/dist/{index-vdbbGE7N.d.ts → index-VAs9akt4.d.ts} +18 -2
- package/dist/{index-DXDz60XH.d.ts → index-YCDugjdu.d.ts} +4 -4
- package/dist/{index-DM9OLgTH.d.ts → index-ZaA90u09.d.ts} +2 -2
- package/dist/index.d.ts +37 -26
- package/dist/index.js +88 -13
- package/dist/item-appearance-BjcMyauw.js +29 -0
- package/dist/{labelled-input-base-Dp_9KP0G.d.ts → labelled-input-base-DiTme1JO.d.ts} +1 -1
- package/dist/labelled-radial-input.d.ts +2 -2
- package/dist/labelled-range-input.d.ts +1 -1
- package/dist/labelled-slider-input.d.ts +1 -1
- package/dist/list-group-header.d.ts +1 -1
- package/dist/{list-selection-controller-CJDdjH7E.js → list-selection-controller-D4iIrvJH.js} +12 -8
- package/dist/{list-selection-types-hj9Njei3.d.ts → list-selection-types-Dnamh2Lu.d.ts} +29 -3
- package/dist/{menu-item-B0zqw2xp.d.ts → menu-item-bF6narIz.d.ts} +1 -1
- package/dist/menu.d.ts +2 -2
- package/dist/{miller-BfrTcujx.js → miller-BrmBywNZ.js} +20 -9
- package/dist/miller.d.ts +1 -1
- package/dist/miller.js +1 -1
- package/dist/narrowed-text.js +1 -1
- package/dist/panel.d.ts +1 -1
- package/dist/{item-appearance-Bl6rMIID.js → partial-keys-Ck6CiprB.js} +1 -29
- package/dist/{split-layout-ChZ0Glrs.d.ts → split-layout-CophkNeU.d.ts} +1 -1
- package/dist/split-layout.d.ts +1 -1
- package/dist/sunburst-tree.d.ts +1 -1
- package/dist/sunburst-tree.js +1 -1
- package/dist/{sunbursttree-list-BAS0KrDG.d.ts → sunbursttree-list-Bk20giTF.d.ts} +1 -1
- package/dist/{sunbursttree-list-906G33ME.js → sunbursttree-list-DQF6DcsS.js} +1 -1
- package/dist/{tab-list-wC0xNz1b.d.ts → tab-list-Df371rqw.d.ts} +1 -1
- package/dist/tabs.d.ts +1 -1
- package/dist/timeline.d.ts +1 -1
- package/dist/{titlebar-B5zSTZnF.d.ts → titlebar-Tqu-VvMU.d.ts} +1 -1
- package/dist/titlebar.d.ts +1 -1
- package/dist/{tree-C_H1COTf.js → tree-BaXYyt5U.js} +185 -69
- package/dist/tree.d.ts +1 -1
- package/dist/tree.js +1 -1
- package/dist/{vertical-list-od4050Bo.d.ts → vertical-list-DtHX26JO.d.ts} +2 -2
- package/dist/{vertical-list-qMRIxZIT.js → vertical-list-o5hfqH87.js} +4 -3
- package/dist/vertical-list.d.ts +2 -2
- package/dist/vertical-list.js +1 -1
- package/docs-user/README.md +6 -0
- package/docs-user/bottom-drawer.md +2 -2
- package/docs-user/crumbs.md +1 -1
- package/docs-user/data-grid.md +3 -3
- package/docs-user/detail-list.md +3 -3
- package/docs-user/dock.md +3 -3
- package/docs-user/flat-list.md +7 -7
- package/docs-user/grid-list.md +4 -4
- package/docs-user/hyperbolic-tree.md +6 -6
- package/docs-user/icicle-tree.md +2 -2
- package/docs-user/incr-search.md +1 -1
- package/docs-user/index.json +26 -2
- package/docs-user/infra-handle.md +122 -0
- package/docs-user/infra-incremental.md +154 -0
- package/docs-user/infra-theming.md +510 -0
- package/docs-user/infra-view-state.md +210 -0
- package/docs-user/list-state.md +656 -0
- package/docs-user/llms.txt +6 -0
- package/docs-user/miller.md +5 -1
- package/docs-user/multicol-list.md +6 -0
- package/docs-user/panel.md +3 -3
- package/docs-user/select-horiz.md +1 -1
- package/docs-user/split-layout.md +2 -2
- package/docs-user/sunburst-tree.md +3 -3
- package/docs-user/titlebar.md +2 -2
- package/docs-user/tree.md +8 -2
- package/docs-user/treemap.md +2 -2
- package/docs-user/twosplit-layout.md +3 -3
- package/docs-user/user-selection-intents.md +86 -0
- package/docs-user/user-theming.md +1 -1
- package/docs-user/vertical-list.md +3 -3
- package/llms.txt +6 -0
- package/package.json +1 -1
- /package/dist/{index-DV3iaUBW.d.ts → index-DV3iaUBW2.d.ts} +0 -0
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
# Saving and restoring list state
|
|
2
|
+
|
|
3
|
+
The list, tree, grid and layout components can give you a snapshot of their
|
|
4
|
+
*view state*: what the user has done to them, as opposed to the data you gave
|
|
5
|
+
them. That covers:
|
|
6
|
+
|
|
7
|
+
- which items are selected (or checked)
|
|
8
|
+
- which tree branches and groups are open or collapsed
|
|
9
|
+
- where the list is scrolled to
|
|
10
|
+
- where the keyboard cursor is
|
|
11
|
+
- column widths, column order, column visibility and sort, in grids
|
|
12
|
+
- panel sizes and arrangement, in layouts
|
|
13
|
+
|
|
14
|
+
You give the component data. The component keeps track of the view state.
|
|
15
|
+
When you want that state to survive (a reload, a route change, a data
|
|
16
|
+
refresh), ask for a snapshot and hand it back later.
|
|
17
|
+
|
|
18
|
+
The components never store anything themselves. Where the snapshot goes
|
|
19
|
+
(localStorage, a URL, a server, nowhere) is up to you.
|
|
20
|
+
|
|
21
|
+
Internal design notes for component authors are in
|
|
22
|
+
[`infra-view-state.md`](./infra-view-state.md).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { bindViewState } from '@ixfx/components';
|
|
30
|
+
|
|
31
|
+
const tree = document.querySelector(`ixfx-tree-list`)!;
|
|
32
|
+
|
|
33
|
+
bindViewState(tree, {
|
|
34
|
+
load: () => JSON.parse(localStorage.getItem(`files-tree`) ?? `null`),
|
|
35
|
+
save: state => localStorage.setItem(`files-tree`, JSON.stringify(state)),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
tree.root = await fetchTree(); // before or after, it doesn't matter
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's all. `bindViewState`:
|
|
42
|
+
|
|
43
|
+
1. calls `load()` once and applies the result,
|
|
44
|
+
2. calls `save()` after each change (debounced, about 150 ms after the user stops),
|
|
45
|
+
3. calls `save()` again when the page is hidden (`pagehide`), so the last change isn't lost.
|
|
46
|
+
|
|
47
|
+
It returns a function that stops saving:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const stop = bindViewState(el, { load, save });
|
|
51
|
+
// later
|
|
52
|
+
stop();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If `load()` throws (bad JSON, say), it logs a warning and carries on with no
|
|
56
|
+
saved state.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Doing it by hand
|
|
61
|
+
|
|
62
|
+
Every view-stateful component has the same three methods and one event:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const state = el.getViewState(); // plain JSON object
|
|
66
|
+
el.setViewState(state); // apply it, any time
|
|
67
|
+
|
|
68
|
+
el.addEventListener(`view-state-change`, (e) => {
|
|
69
|
+
e.detail.state; // the full current state
|
|
70
|
+
e.detail.reason; // 'user' | 'data' | 'restore'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
el.clearPendingViewState(); // see "Pending state" below
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `getViewState(options?)`
|
|
77
|
+
|
|
78
|
+
Returns a plain object you can pass straight to `JSON.stringify`. It contains
|
|
79
|
+
only arrays, records, strings, numbers and booleans.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
el.getViewState(); // everything
|
|
83
|
+
el.getViewState({ include: [`columns`, `sort`] }); // only some fields
|
|
84
|
+
el.getViewState({ includePending: false }); // only what's live now
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`includePending` defaults to `true`, which is almost always what you want; see
|
|
88
|
+
[Pending state](#pending-state).
|
|
89
|
+
|
|
90
|
+
### `setViewState(state)`
|
|
91
|
+
|
|
92
|
+
- Can be called before the component has any data, before it's connected, or
|
|
93
|
+
at any point afterwards.
|
|
94
|
+
- Accepts a **partial** state. Fields you include replace the current value,
|
|
95
|
+
and fields you leave out are untouched:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
el.setViewState({ selected: [`invoice-42`] }); // only changes the selection
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- Ignores state meant for a different component, or from a different schema
|
|
102
|
+
version, and logs a warning. Feeding a data-grid's saved state into a tree
|
|
103
|
+
does nothing.
|
|
104
|
+
|
|
105
|
+
### `view-state-change`
|
|
106
|
+
|
|
107
|
+
Fired after changes, debounced. It bubbles, but is **not** composed: it won't
|
|
108
|
+
escape the shadow root of a component that contains it. Listen on the
|
|
109
|
+
component itself.
|
|
110
|
+
|
|
111
|
+
`reason` tells you what caused the change:
|
|
112
|
+
|
|
113
|
+
| `reason` | Meaning |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `user` | The user clicked, typed, dragged or scrolled. |
|
|
116
|
+
| `data` | You changed the data and the component adjusted. |
|
|
117
|
+
| `restore` | Saved state was applied, either by `setViewState()` or because a matching item just arrived. |
|
|
118
|
+
|
|
119
|
+
When several things happen inside one debounce window, the strongest reason
|
|
120
|
+
wins (`user` > `data` > `restore`). `bindViewState` skips `restore` events by
|
|
121
|
+
default, because they only echo what you just loaded. Pass
|
|
122
|
+
`saveOnRestore: true` if you want them saved too.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Key ideas
|
|
127
|
+
|
|
128
|
+
### Everything is keyed
|
|
129
|
+
|
|
130
|
+
State refers to items by **key**, never by position: `item.key` for trees,
|
|
131
|
+
`data-key` (or `data-value`) for element lists, the row key in data-grid, the
|
|
132
|
+
column `key` or `id`, the group key, the panel id. That's what lets saved state
|
|
133
|
+
survive sorting, filtering, inserting, and rebuilding the data from scratch.
|
|
134
|
+
|
|
135
|
+
**Your items need stable keys.** An item without a key can't be restored. In
|
|
136
|
+
data-grid, rows without a `key` or `id` fall back to their row index, and those
|
|
137
|
+
index keys are deliberately never saved. Give rows real keys, or a stable
|
|
138
|
+
`getRowKey`.
|
|
139
|
+
|
|
140
|
+
### Pending state
|
|
141
|
+
|
|
142
|
+
When saved state names a key that isn't in the current data, the component
|
|
143
|
+
**keeps it pending** and applies it as soon as an item with that key appears.
|
|
144
|
+
|
|
145
|
+
This means you don't have to wait until all your data has loaded before
|
|
146
|
+
restoring:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
tree.setViewState({ expanded: { A: true, B: true, C: true } });
|
|
150
|
+
|
|
151
|
+
tree.root = rootWith(A); // A opens
|
|
152
|
+
// … later
|
|
153
|
+
addChild(A, B); tree.invalidateStructure(); // B opens
|
|
154
|
+
// … later
|
|
155
|
+
addChild(B, C); tree.invalidateStructure(); // C opens
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
It also works the other way. When your data changes and an item disappears,
|
|
159
|
+
its state isn't thrown away; it goes pending. If the item comes back, so does
|
|
160
|
+
its state:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
list.setItems(page1); // user selects 'x'
|
|
164
|
+
list.clearItems(); // 'x' is gone, so its selection is pending
|
|
165
|
+
list.setItems(page1); // 'x' is selected again
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`getViewState()` includes pending state by default. If you save mid-reload,
|
|
169
|
+
you get the full saved state, not just the part that has loaded so far.
|
|
170
|
+
|
|
171
|
+
Pending state is dropped when:
|
|
172
|
+
|
|
173
|
+
| State | Pending entries are dropped when… |
|
|
174
|
+
|---|---|
|
|
175
|
+
| selection | the selection changes in any other way: a click, a key, `select…()`, `clearSelection()`. An old saved selection never merges into a new one. |
|
|
176
|
+
| expanded / collapsed | never on their own. Each key is tracked separately, so collapsing `A` doesn't forget that `C`, below it, was open. |
|
|
177
|
+
| scroll | the user scrolls (wheel, touch, drag, arrow or page keys), or the saved position has been reached. |
|
|
178
|
+
| keyboard cursor | the cursor moves. |
|
|
179
|
+
| anything | you call `setViewState()` with that field, or `clearPendingViewState()`. |
|
|
180
|
+
|
|
181
|
+
Each kind of pending state is capped at 2,000 keys, and the oldest are dropped
|
|
182
|
+
first.
|
|
183
|
+
|
|
184
|
+
### Selection events
|
|
185
|
+
|
|
186
|
+
When pending selection is applied because its item arrived, the component
|
|
187
|
+
fires its **normal selection event** (`select`, `list-select`,
|
|
188
|
+
`datagrid-select`, and so on), so your selection handlers run as usual. Where
|
|
189
|
+
the event has an `origin` field, it is `'programmatic'`.
|
|
190
|
+
|
|
191
|
+
When a selected item *leaves* the data, it is deselected **silently**, with no
|
|
192
|
+
event, and its key goes pending.
|
|
193
|
+
|
|
194
|
+
### Scroll
|
|
195
|
+
|
|
196
|
+
Scroll is saved as the **key of the first visible item plus a pixel offset**,
|
|
197
|
+
along with the raw pixel position as a fallback:
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{ "top": 4820, "anchor": { "key": "row-311", "offset": 12 } }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
When restored, the component waits until that item exists and there's enough
|
|
204
|
+
content to scroll to, then scrolls there. If the user scrolls first, the saved
|
|
205
|
+
position is dropped, because the user's scroll wins.
|
|
206
|
+
|
|
207
|
+
### Keyboard cursor
|
|
208
|
+
|
|
209
|
+
`focus` is only recorded when the cursor was moved with the keyboard. Mouse
|
|
210
|
+
hover isn't saved. Restoring `focus` puts the component in keyboard mode, so
|
|
211
|
+
the cursor is visible.
|
|
212
|
+
|
|
213
|
+
### Lazy trees
|
|
214
|
+
|
|
215
|
+
`ixfx-tree-list` has a `restoreLoads` property, which defaults to `true`. If a
|
|
216
|
+
saved-open branch hasn't loaded its children, the tree loads them through
|
|
217
|
+
`loadChildren` / `model`, one branch at a time, so a saved deep path opens all
|
|
218
|
+
the way down. That can mean several requests in a row. Set
|
|
219
|
+
`tree.restoreLoads = false` if you'd rather only re-open branches as you load
|
|
220
|
+
them yourself.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Component reference
|
|
225
|
+
|
|
226
|
+
| Component | `kind` | Fields |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `ixfx-tree-list` | `tree-list` | `selected`, `expanded`, `focus`, `scroll` |
|
|
229
|
+
| `ixfx-vertical-list` | `vertical-list` | `selected`, `focus`, `scroll` |
|
|
230
|
+
| `ixfx-detail-list` | `detail-list` | `selected`, `expanded` (groups), `focus`, `scroll` |
|
|
231
|
+
| `ixfx-grid-list` | `grid-list` | `selected`, `expanded` (groups), `focus`, `scroll` |
|
|
232
|
+
| `ixfx-data-grid` | `data-grid` | `selected`, `expanded` (groups), `focus`, `scroll`, `columns`, `sort`, `groupBy` |
|
|
233
|
+
| `ixfx-multicol-list` | `multicol-list` | `columns`, `selectedByColumn`, `scrollByColumn` |
|
|
234
|
+
| `ixfx-miller-list` | `miller-list` | `selected`, `focus`, `path`, `columnWidths`, `columnScroll` |
|
|
235
|
+
| `ixfx-crumb-navigation` | `crumb-navigation` | `path` |
|
|
236
|
+
| `ixfx-flat-list` | `flat-list` | `path`, `selected`, `focus`, `scroll` |
|
|
237
|
+
| `ixfx-sunbursttree-list` | `sunbursttree-list` | `selected`, `focus`, `zoom` |
|
|
238
|
+
| `ixfx-icicletree-list` | `icicletree-list` | `selected`, `focus`, `zoom`, `scroll` |
|
|
239
|
+
| `ixfx-treemap-list` | `treemap-list` | `selected`, `focus`, `zoom` |
|
|
240
|
+
| `ixfx-hyperbolictree-list` | `hyperbolictree-list` | `selected`, `focus`, `center`, `zoom` |
|
|
241
|
+
| `ixfx-split-layout` | `split-layout` | `layout` |
|
|
242
|
+
| `ixfx-twosplit-layout` | `twosplit-layout` | `layout` |
|
|
243
|
+
| `ixfx-dock-host` | `dock-host` | `layout` |
|
|
244
|
+
|
|
245
|
+
Each component's README has a "View state" section with details.
|
|
246
|
+
|
|
247
|
+
### Shared fields
|
|
248
|
+
|
|
249
|
+
| Field | Type | Notes |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| `v` | `1` | Schema version. |
|
|
252
|
+
| `kind` | `string` | Which component the state is for. |
|
|
253
|
+
| `selected` | `string[]` | Selected keys. In `checked` interaction mode this is the checked set. |
|
|
254
|
+
| `expanded` | `Record<string, boolean>` | Only explicit choices are recorded, and a missing key means "default". Tree branches default to closed, so open ones are `true`. Groups default to open, so collapsed ones are `false`. |
|
|
255
|
+
| `focus` | `string` | Keyboard cursor key. |
|
|
256
|
+
| `scroll` | `{ top, left?, anchor?: { key, offset } }` | See [Scroll](#scroll). |
|
|
257
|
+
|
|
258
|
+
### `ixfx-data-grid` extras
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
{
|
|
262
|
+
columns: { [columnKey]: { width?: number; visible?: boolean; order?: number } },
|
|
263
|
+
sort: { key: string; direction: 'asc' | 'desc' } | null,
|
|
264
|
+
groupBy: string | null, // only when allowGrouping is on; a function groupBy is never saved
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- A saved width or visibility is applied when the column first appears. If you
|
|
269
|
+
redefine that column afterwards, your definition wins.
|
|
270
|
+
- A column you remove keeps its saved width and visibility pending, ready for
|
|
271
|
+
when it comes back.
|
|
272
|
+
- A saved `order` places the columns it names; other columns stay where they are.
|
|
273
|
+
|
|
274
|
+
### `ixfx-multicol-list` extras
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
{
|
|
278
|
+
columns: MulticolColumnState[], // display order: { id, categoryKey, sort, grouped, menu?, width? }
|
|
279
|
+
selectedByColumn: { [columnId]: string[] }, // missing or [] means 'All'
|
|
280
|
+
scrollByColumn: { [columnId]: ScrollState },
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
A saved column whose category isn't in `categories` yet is inserted at its
|
|
285
|
+
saved position once that category appears.
|
|
286
|
+
|
|
287
|
+
### `ixfx-miller-list` extras
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
{
|
|
291
|
+
path: string[], // key of the open branch at each depth, column 0 first
|
|
292
|
+
columnWidths: { [depth: string]: number }, // px
|
|
293
|
+
columnScroll: { [parentKey: string]: ScrollState }, // only scrolled columns; column 0 uses the view root's key
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
- `path` is relative to the current view root, which isn't saved. A saved path
|
|
298
|
+
only matches when restored under the same view root.
|
|
299
|
+
- `restoreLoads` (default `true`) opens a saved path through unloaded branches
|
|
300
|
+
one load at a time.
|
|
301
|
+
- Any explicit navigation (click, arrow keys, Escape, `navigateTo`,
|
|
302
|
+
`revealKey`, rebasing) drops a pending path.
|
|
303
|
+
|
|
304
|
+
### `ixfx-crumb-navigation` extras
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
{
|
|
308
|
+
path: string[], // keys from the root down to the current location
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
- The location *is* the selection, so there's no separate `selected`.
|
|
313
|
+
- With a pending path, the crumb moves to the deepest loaded ancestor and keeps
|
|
314
|
+
going as data arrives, firing `select` at each step. With `restoreLoads` it
|
|
315
|
+
calls `loadChildren` itself.
|
|
316
|
+
- Setting `selectedNode` counts as an explicit move and drops a pending path.
|
|
317
|
+
**Set the initial `selectedNode` before calling `setViewState` /
|
|
318
|
+
`bindViewState`**, or it will override what you restore.
|
|
319
|
+
|
|
320
|
+
### `ixfx-flat-list` extras
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
{
|
|
324
|
+
path: string[], // the folder being shown
|
|
325
|
+
// selected / focus / scroll apply to the level at `path`
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
A saved path through lazy levels loads one level at a time, because showing a
|
|
330
|
+
level loads it. If new data removes the level being shown, the list steps up
|
|
331
|
+
and that level, with its selection, goes pending.
|
|
332
|
+
|
|
333
|
+
### `ixfx-sunbursttree-list` and `ixfx-icicletree-list` extras
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
{
|
|
337
|
+
zoom: string[], // key path from the root's children to the zoomed-in node; [] = whole tree
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
- `zoom` is a path so a lazy tree can be loaded down to it. `restoreLoads`
|
|
342
|
+
(default `true`) loads the path one level at a time.
|
|
343
|
+
- A restored zoom fires `zoom` and `expand`, just like a user's zoom.
|
|
344
|
+
- Any explicit zoom (click, `zoomTo`, `navigateTo`, Escape, crumbs) drops a
|
|
345
|
+
pending one.
|
|
346
|
+
- The icicle also saves `scroll`, as plain `top` / `left` pixels with no anchor.
|
|
347
|
+
- The sunburst only needs keys to be unique among siblings, so a restored
|
|
348
|
+
`selected` key that repeats in several folders matches the first one,
|
|
349
|
+
depth-first.
|
|
350
|
+
|
|
351
|
+
### `ixfx-treemap-list` extras
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
{
|
|
355
|
+
zoom: string[], // key path from the root's children to the zoomed-in node; [] = whole tree
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
There's no `expanded`, because zooming takes its place, and no `scroll`,
|
|
360
|
+
because the treemap fills its box. Like the tree, it has `restoreLoads`
|
|
361
|
+
(default `true`): a deep saved zoom loads each ancestor in turn. A restored zoom
|
|
362
|
+
fires `zoom`, not `expand`, and doesn't animate.
|
|
363
|
+
|
|
364
|
+
### `ixfx-hyperbolictree-list` extras
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
{
|
|
368
|
+
center: string[], // key path from the root's children to the centred node; [] = root
|
|
369
|
+
zoom: number, // magnification
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
- `center` is a **path**, not a single key, because keys may repeat across
|
|
374
|
+
branches in this component. Saving the path keeps the right node.
|
|
375
|
+
- Free panning and disk rotation **aren't saved**. They're raw geometry that
|
|
376
|
+
stops making sense when the data changes, so a restored view sits exactly on
|
|
377
|
+
`center`.
|
|
378
|
+
- `restoreLoads` (default `true`) loads the path one level at a time.
|
|
379
|
+
- A restored centre fires `focus`, not `expand`, and snaps instead of animating.
|
|
380
|
+
|
|
381
|
+
### `ixfx-split-layout` extras
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
{
|
|
385
|
+
layout: { [childId]: { weight: number; collapsed?: boolean } },
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
- `weight` is the child's share (0 to 1) of the resizable space. For a
|
|
390
|
+
collapsed child it's the share it expands back to.
|
|
391
|
+
- **Only direct children with an `id` you set are saved.** Generated ids
|
|
392
|
+
(`p-1`, `g-2`) depend on position, so they'd apply sizes to the wrong child.
|
|
393
|
+
Give every child you want remembered an `id`.
|
|
394
|
+
- A nested `ixfx-split-layout` is saved as one entry. Its own children are in
|
|
395
|
+
its own view state, so bind it separately.
|
|
396
|
+
- Sizes for a child that isn't slotted yet are applied when a child with that
|
|
397
|
+
id appears. A child that's removed keeps its size pending, so re-adding it
|
|
398
|
+
restores the size.
|
|
399
|
+
- Set the `forget-absent` attribute to turn pending off, so children that leave
|
|
400
|
+
are forgotten.
|
|
401
|
+
|
|
402
|
+
### `ixfx-twosplit-layout` extras
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
{
|
|
406
|
+
layout: { a?: { size: string; collapsed?: boolean }, b?: { size: string; collapsed?: boolean } },
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
This view-state `layout` field has nothing to do with the `layout`
|
|
411
|
+
(orientation) attribute.
|
|
412
|
+
|
|
413
|
+
### `ixfx-dock-host` extras
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
{
|
|
417
|
+
layout: DockLayout, // the whole tree: splits, stacks, sizes, active tabs, collapse
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
- A panel id that's saved in the layout but hasn't been added yet keeps its
|
|
422
|
+
place. It shows as a placeholder tab until the panel arrives, and
|
|
423
|
+
`addPanel()` without a target then puts it there.
|
|
424
|
+
- Closing or removing a panel takes it out of the layout. Its place isn't
|
|
425
|
+
remembered.
|
|
426
|
+
- `clearPendingViewState()` does nothing on the dock, because placeholders are
|
|
427
|
+
part of the layout. Use `removePanel(id)` to drop one.
|
|
428
|
+
- `setViewState()` before the dock is connected wins over a `layout` attribute.
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## Porting from the old APIs
|
|
433
|
+
|
|
434
|
+
### Removed methods
|
|
435
|
+
|
|
436
|
+
| Component | Old | New |
|
|
437
|
+
|---|---|---|
|
|
438
|
+
| `ixfx-multicol-list` | `el.columns` (getter) | `el.getViewState().columns` |
|
|
439
|
+
| `ixfx-multicol-list` | `el.setColumns(states)` | `el.setViewState({ columns: states })` |
|
|
440
|
+
| `ixfx-miller-list` | `el.getConfig()` / `el.setConfig({ columnWidths })` | `el.getViewState().columnWidths` / `el.setViewState({ columnWidths })`. Same shape. The `MillerConfig` type is gone too. |
|
|
441
|
+
| `ixfx-split-layout` | `el.getLayout()` / `el.setLayout(snapshot)` | `el.getViewState().layout` / `el.setViewState({ layout })`. **The shape changed**; see below. |
|
|
442
|
+
| `ixfx-dock-host` | `el.saveLayout()` / `el.loadLayout(layout)` | `el.getViewState().layout` / `el.setViewState({ layout })`. Same `DockLayout` shape as before. |
|
|
443
|
+
|
|
444
|
+
`ixfx-tree-list`'s `getConfig()` / `setConfig()` is **not** removed. It only
|
|
445
|
+
holds display configuration (`indent`), not view state.
|
|
446
|
+
|
|
447
|
+
**Saved data from the old APIs is not read by the new ones.** If you have
|
|
448
|
+
stored layouts or configs you need to keep, convert them once on load:
|
|
449
|
+
|
|
450
|
+
```ts
|
|
451
|
+
// multicol-list: an old saved `columns` array
|
|
452
|
+
const old = JSON.parse(localStorage.getItem(`multicol-columns`) ?? `null`);
|
|
453
|
+
if (Array.isArray(old))
|
|
454
|
+
el.setViewState({ columns: old }); // same shape as before
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
// miller-list: an old saved MillerConfig
|
|
459
|
+
const oldMiller = JSON.parse(localStorage.getItem(`miller-config`) ?? `null`);
|
|
460
|
+
if (oldMiller?.columnWidths)
|
|
461
|
+
miller.setViewState({ columnWidths: oldMiller.columnWidths });
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
// dock-host: an old saved DockLayout is already the right shape
|
|
466
|
+
const oldDock = JSON.parse(localStorage.getItem(`dock-layout`) ?? `null`);
|
|
467
|
+
if (oldDock)
|
|
468
|
+
dock.setViewState({ layout: oldDock });
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
// split-layout: an old LayoutSnapshot ({ root: { kind, id, weight, collapsed, children } })
|
|
473
|
+
// becomes a record of the layout's direct children
|
|
474
|
+
const snap = JSON.parse(localStorage.getItem(`split-layout`) ?? `null`);
|
|
475
|
+
if (snap?.root?.children) {
|
|
476
|
+
const kids = snap.root.children;
|
|
477
|
+
const total = kids.reduce((sum, c) => sum + (c.weight ?? 1), 0) || 1;
|
|
478
|
+
const layout = Object.fromEntries(kids.map(c =>
|
|
479
|
+
[c.id, { weight: (c.weight ?? 1) / total, ...(c.collapsed ? { collapsed: true } : {}) }]));
|
|
480
|
+
split.setViewState({ layout });
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Check the result for split-layout: the new state keeps only children that have
|
|
485
|
+
an `id` you gave them (see below), and weights are shares of the resizable space.
|
|
486
|
+
|
|
487
|
+
### Replacing hand-rolled persistence
|
|
488
|
+
|
|
489
|
+
The common old pattern was to save on some event and restore with a setter:
|
|
490
|
+
|
|
491
|
+
```ts
|
|
492
|
+
// Before
|
|
493
|
+
const saved = localStorage.getItem(`miller-config`);
|
|
494
|
+
if (saved) el.setConfig(JSON.parse(saved));
|
|
495
|
+
el.addEventListener(`pointerup`, () =>
|
|
496
|
+
localStorage.setItem(`miller-config`, JSON.stringify(el.getConfig())));
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
// After
|
|
501
|
+
bindViewState(el, {
|
|
502
|
+
load: () => JSON.parse(localStorage.getItem(`miller-state`) ?? `null`),
|
|
503
|
+
save: s => localStorage.setItem(`miller-state`, JSON.stringify(s)),
|
|
504
|
+
});
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
The new version also saves selection, expansion, cursor and scroll, and it
|
|
508
|
+
saves after keyboard changes and on `pagehide`, which `pointerup` missed.
|
|
509
|
+
|
|
510
|
+
### Re-selecting after a data refresh
|
|
511
|
+
|
|
512
|
+
You no longer need to remember the selection yourself across a data refresh:
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
// Before
|
|
516
|
+
const keep = [...list.selectedKeys];
|
|
517
|
+
list.setItems(fresh);
|
|
518
|
+
list.selectKeys(keep);
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
// After
|
|
523
|
+
list.setItems(fresh); // selection for keys still present (or returning later) is kept
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
The same applies to replacing `tree.root`. Selection, expansion and cursor
|
|
527
|
+
carry over for every key the new root still has.
|
|
528
|
+
|
|
529
|
+
If you want the *old* behaviour, where a refresh clears everything, ask for it
|
|
530
|
+
explicitly:
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
list.setItems(fresh);
|
|
534
|
+
list.clearPendingViewState();
|
|
535
|
+
list.clearSelection();
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Behaviour changes to check
|
|
539
|
+
|
|
540
|
+
These used to work differently. If your code relied on the old behaviour,
|
|
541
|
+
adjust it.
|
|
542
|
+
|
|
543
|
+
| Component | Before | Now |
|
|
544
|
+
|---|---|---|
|
|
545
|
+
| `ixfx-tree-list` | Assigning a new `root` cleared the selection and keyboard cursor. | Selection and cursor are kept for every key the new root still contains. Others go pending. |
|
|
546
|
+
| `ixfx-tree-list` | Expansion for a key that disappeared and came back was forgotten. | It comes back open. |
|
|
547
|
+
| `ixfx-tree-list` | Selection held references to the old root's nodes after `root` was replaced. | Selection is re-matched by key onto the new root's nodes. |
|
|
548
|
+
| `ixfx-vertical-list` | `removeItem()` on a selected item fired `list-select`. | It deselects silently, and the key goes pending. |
|
|
549
|
+
| `ixfx-vertical-list`, `ixfx-detail-list` | A selected element removed straight from the DOM stayed in the selection as a stale reference. | It's dropped from the selection, and its key goes pending. |
|
|
550
|
+
| `ixfx-detail-list`, `ixfx-vertical-list` | `clearItems()` followed by `setItems()` lost the selection. | Matching keys are re-selected. |
|
|
551
|
+
| `ixfx-grid-list` | Items leaving `items` / `removeItems()` fired one `list-select`. | They're deselected silently, and their keys go pending. `clearItems()` still fires its empty `list-select`. |
|
|
552
|
+
| `ixfx-grid-list` | | A saved selection for an item that's filtered out or inside a collapsed group stays pending until the item is shown. |
|
|
553
|
+
| `ixfx-data-grid` | Keys of rows removed from `rows` stayed in `selectedKeys`. | They're deselected silently and go pending, and they're re-selected (with `datagrid-select`) if the rows return. |
|
|
554
|
+
| `ixfx-data-grid`, `ixfx-tree-list`, lists | A cursor on a removed item was left dangling. | The cursor is cleared. If it was a keyboard cursor, it comes back when the item does. |
|
|
555
|
+
| `ixfx-miller-list` | Assigning a new `root` cleared the selection and the open columns. | The open path, selection and cursor carry over by key; missing keys go pending. |
|
|
556
|
+
| `ixfx-miller-list` | `invalidateStructure()` left removed nodes selected or open. | Removed parts close or deselect, and come back if their keys reappear. |
|
|
557
|
+
| `ixfx-crumb-navigation` | A new `root` without the current location left it pointing at a missing node. | It steps back to the closest ancestor silently, and returns if the key reappears. |
|
|
558
|
+
| `ixfx-flat-list` | A `path` set before its data loaded was cut short for good. | It waits, and extends as the data arrives. |
|
|
559
|
+
| `ixfx-icicletree-list` | Assigning a new `root` reset selection, zoom and cursor. | They're kept by key; missing ones go pending. |
|
|
560
|
+
| `ixfx-sunbursttree-list`, `ixfx-icicletree-list` | A zoom node that disappeared fell back to the whole tree for good. | It still falls back, but the zoom comes back if the node does. |
|
|
561
|
+
| `ixfx-treemap-list` | Assigning a new `root` cleared selection, zoom and cursor. | They're kept by key; missing ones go pending. |
|
|
562
|
+
| `ixfx-hyperbolictree-list` | Assigning a new `root` / `model` reset selection, cursor and centre. | They're kept by key. If the centred node survives, so do pan and rotation; if not, the view returns to the root and the centre goes pending. |
|
|
563
|
+
| `ixfx-split-layout` | Adding, removing or collapsing a child re-read every size from the DOM attributes, which reset sizes the user had dragged. | Sizes the user dragged are kept. The DOM only wins when a `size` / `weight` / `sizes` / `fixed` / `collapsed` attribute actually changes. |
|
|
564
|
+
| `ixfx-split-layout` | (README described `storage-key` persistence.) | That was never implemented and has been removed from the README. Use `bindViewState`. |
|
|
565
|
+
| `ixfx-dock-host` | `addPanel(el)` with no target always added to the first stack. | If the panel's id has a saved place, it goes there. |
|
|
566
|
+
| `ixfx-multicol-list` | `setColumns()` dropped columns whose category didn't exist. | They stay pending and appear when the category does. |
|
|
567
|
+
|
|
568
|
+
"Deselected silently" means your selection handler won't run when an item
|
|
569
|
+
leaves the data. If you mirror the selection elsewhere, read `selectedKeys`
|
|
570
|
+
after changing the data, or listen for `view-state-change`.
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## Recipes
|
|
575
|
+
|
|
576
|
+
### Several components, one storage entry
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
const KEY = `workspace`;
|
|
580
|
+
const saved = JSON.parse(localStorage.getItem(KEY) ?? `{}`);
|
|
581
|
+
const parts = { tree, grid, layout };
|
|
582
|
+
|
|
583
|
+
for (const [name, el] of Object.entries(parts)) {
|
|
584
|
+
bindViewState(el, {
|
|
585
|
+
load: () => saved[name],
|
|
586
|
+
save: (s) => {
|
|
587
|
+
saved[name] = s;
|
|
588
|
+
localStorage.setItem(KEY, JSON.stringify(saved));
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### State per dataset
|
|
595
|
+
|
|
596
|
+
Keys are only meaningful within one dataset, so include the dataset in your
|
|
597
|
+
storage key:
|
|
598
|
+
|
|
599
|
+
```ts
|
|
600
|
+
bindViewState(grid, {
|
|
601
|
+
load: () => JSON.parse(localStorage.getItem(`grid:${projectId}`) ?? `null`),
|
|
602
|
+
save: s => localStorage.setItem(`grid:${projectId}`, JSON.stringify(s)),
|
|
603
|
+
});
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Saving only some of it
|
|
607
|
+
|
|
608
|
+
```ts
|
|
609
|
+
bindViewState(grid, {
|
|
610
|
+
load: () => JSON.parse(localStorage.getItem(`grid-columns`) ?? `null`),
|
|
611
|
+
save: () => localStorage.setItem(`grid-columns`,
|
|
612
|
+
JSON.stringify(grid.getViewState({ include: [`columns`, `sort`] }))),
|
|
613
|
+
});
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### State in the URL
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
const fromUrl = new URLSearchParams(location.search).get(`sel`);
|
|
620
|
+
if (fromUrl)
|
|
621
|
+
list.setViewState({ selected: fromUrl.split(`,`) });
|
|
622
|
+
|
|
623
|
+
list.addEventListener(`view-state-change`, (e) => {
|
|
624
|
+
const url = new URL(location.href);
|
|
625
|
+
url.searchParams.set(`sel`, (e.detail.state.selected ?? []).join(`,`));
|
|
626
|
+
history.replaceState(null, ``, url);
|
|
627
|
+
});
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Reset to defaults
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
el.clearPendingViewState();
|
|
634
|
+
el.setViewState({ selected: [], expanded: {} });
|
|
635
|
+
localStorage.removeItem(`files-tree`);
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
## Caveats
|
|
641
|
+
|
|
642
|
+
- **Stable keys are required.** Items without a key (or with index-based keys)
|
|
643
|
+
can't have their state restored.
|
|
644
|
+
- **No `origin` on some events.** The treemap's and hyperbolic tree's `select`
|
|
645
|
+
events have no `origin` field, so a restored selection can't be told apart
|
|
646
|
+
from a user's there. Use `view-state-change`'s `reason` instead.
|
|
647
|
+
- **Key collisions.** Tree keys must be unique across the whole tree. If a node
|
|
648
|
+
moves to a different parent with the same key, its open/selected state moves
|
|
649
|
+
with it.
|
|
650
|
+
- **Lazy restore makes requests.** With `restoreLoads`, restoring a deep tree
|
|
651
|
+
makes one `loadChildren` call per saved-open branch, one after another.
|
|
652
|
+
- **Hidden items.** A cursor or scroll anchor on an item that's filtered out or
|
|
653
|
+
inside a collapsed group stays pending until the item is visible again. Some
|
|
654
|
+
components only retry when items are added, not when a filter is lifted.
|
|
655
|
+
- **Uniform row heights.** The tree's scroll anchor assumes rows are all the
|
|
656
|
+
same height, as its virtualisation already does.
|
package/docs-user/llms.txt
CHANGED
|
@@ -73,6 +73,12 @@ import '@ixfx/components/data-grid';
|
|
|
73
73
|
- [xy-pad](docs-user/xy-pad.md): A 2D touch-friendly XY pad for controlling two parameters simultaneously. Each axis supports independent linear/logarithmic scaling.
|
|
74
74
|
|
|
75
75
|
## Overviews
|
|
76
|
+
- [infra-handle.md](docs-user/infra-handle.md)
|
|
77
|
+
- [infra-incremental.md](docs-user/infra-incremental.md)
|
|
78
|
+
- [infra-theming.md](docs-user/infra-theming.md)
|
|
76
79
|
- [infra-tickled.md](docs-user/infra-tickled.md)
|
|
80
|
+
- [infra-view-state.md](docs-user/infra-view-state.md)
|
|
81
|
+
- [list-state.md](docs-user/list-state.md)
|
|
77
82
|
- [user-catalog.md](docs-user/user-catalog.md)
|
|
83
|
+
- [user-selection-intents.md](docs-user/user-selection-intents.md)
|
|
78
84
|
- [user-theming.md](docs-user/user-theming.md)
|
package/docs-user/miller.md
CHANGED
|
@@ -149,6 +149,10 @@ The `change` event is fired on the component after every checkbox toggle.
|
|
|
149
149
|
|
|
150
150
|
---
|
|
151
151
|
|
|
152
|
+
## Selection intents and partial rows
|
|
153
|
+
|
|
154
|
+
`ixfx-miller-list` fires a cancelable `list-select-intent` before a click (all interaction modes, including `manual`), a checkbox toggle or a Shift+Arrow range changes the selection; `preventDefault()` means "I'll apply it". Navigation (opening the node's column) still happens. It also fires `list-select` `{ selectedKeys, previousKeys, previous }` next to `select`, and supports `partialKeys` (indeterminate checkbox in `checked` mode, `.partial` otherwise; selected wins; in `checked` mode a node is indeterminate if either the loaded descendants or `partialKeys` say so, as in the tree). Ticking a branch in `checked` mode is one `toggle` intent with `descendants: true` and `checked` (the state it is being set to), exactly as in `ixfx-tree-list`: `keys` lists the node and every loaded descendant, and the caller can express it as a rule ("this folder and everything under it"). See [Selection intents, partial rows, unloaded keys](./user-selection-intents.md).
|
|
155
|
+
|
|
152
156
|
## Selection API
|
|
153
157
|
|
|
154
158
|
```typescript
|
|
@@ -347,7 +351,7 @@ el.setViewState({ columnWidths: { 0: 150, 1: 200, 2: 180 } });
|
|
|
347
351
|
`getViewState()` / `setViewState()` snapshot and restore how the columns are
|
|
348
352
|
showing the data, as plain JSON keyed by `item.key`. The component never
|
|
349
353
|
stores it; `bindViewState()` wires it to storage. See
|
|
350
|
-
[docs/infra-view-state.md](
|
|
354
|
+
[docs/infra-view-state.md](./infra-view-state.md).
|
|
351
355
|
|
|
352
356
|
```javascript
|
|
353
357
|
import { bindViewState } from '@ixfx/components';
|