@fusion.dev/helix-headless 0.177.2 → 0.178.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fusion.dev/helix-headless",
3
3
  "description": "Headless component tier. Structure, accessibility, state, and form participation. No styling.",
4
- "version": "0.177.2",
4
+ "version": "0.178.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -49,13 +49,13 @@
49
49
  },
50
50
  "bugs": "https://github.com/project-fusion/fusion/issues",
51
51
  "dependencies": {
52
- "@fusion.dev/helix-core": "0.177.2",
53
- "@fusion.dev/helix-icons": "0.177.2",
52
+ "@fusion.dev/helix-core": "0.178.0",
53
+ "@fusion.dev/helix-icons": "0.178.0",
54
54
  "uqr": "^0.1.3"
55
55
  },
56
56
  "devDependencies": {
57
- "@fusion.dev/helix-cem": "0.177.2",
58
- "@fusion.dev/helix-testing": "0.177.2",
57
+ "@fusion.dev/helix-cem": "0.178.0",
58
+ "@fusion.dev/helix-testing": "0.178.0",
59
59
  "@fusion.dev/scripts": "0.0.0-dev",
60
60
  "@happy-dom/global-registrator": "^20.14.5",
61
61
  "@lit-labs/ssr": "^4.1.0",
package/types/jsx.d.ts CHANGED
@@ -14098,6 +14098,14 @@ an icon. */
14098
14098
  /** The name of the clear button, read by screen readers. The button shows only
14099
14099
  an icon. */
14100
14100
  clearLabel?: (HelixHeadlessFilterInput["clearLabel"]) | undefined;
14101
+ /** How long typing rests before `source` is asked, in milliseconds. A delay
14102
+ sends one request for a word in place of one for each letter. The arrow
14103
+ keys and Enter ask at once. `0` asks on every keystroke. */
14104
+ "filter-delay"?: (HelixHeadlessFilterInput["filterDelay"] | `${number}`) | undefined;
14105
+ /** How long typing rests before `source` is asked, in milliseconds. A delay
14106
+ sends one request for a word in place of one for each letter. The arrow
14107
+ keys and Enter ask at once. `0` asks on every keystroke. */
14108
+ filterDelay?: (HelixHeadlessFilterInput["filterDelay"]) | undefined;
14101
14109
  /** Whether the panel narrows itself to the suggestions that hold the term
14102
14110
  being typed, as React Aria's Autocomplete filters. Off, the panel shows
14103
14111
  what you slot and you narrow it. */
@@ -14291,6 +14299,13 @@ imperatively later, and nothing is hydrating by then.
14291
14299
 
14292
14300
  Here rather than on each control. */
14293
14301
  name?: (HelixHeadlessFilterInput["name"]) | undefined;
14302
+ /** Where the suggestions come from: a function given the term at the caret,
14303
+ the field it is a value of, the whole text, and an `AbortSignal`, which
14304
+ returns the suggestions or a promise of them. Each is a string, or a
14305
+ `value` with a `label` and `disabled`. The field draws them as options in
14306
+ the panel, beside any you slot. A property only, there being no way to
14307
+ write a function in markup. See the class description. */
14308
+ source?: (HelixHeadlessFilterInput["source"]) | undefined;
14294
14309
 
14295
14310
  /** Fired from the input when the user finishes editing the text. */
14296
14311
  onchange?: ((e: HelixHeadlessFilterInputElementEvent) => void) | undefined;
@@ -14312,7 +14327,7 @@ Here rather than on each control. */
14312
14327
  ) => void) | undefined;
14313
14328
  /** Fired when a filter changes, whether it was typed, picked, or removed. Cancelable: call `preventDefault()` to leave the text and the chip as they were. */
14314
14329
  "onhelix-facet"?: ((e: HelixHeadlessFilterInputHelixFacetElementEvent) => void) | undefined;
14315
- /** Fired by an option in `suggestions` when it is chosen, and bubbles out of the field. Cancelable: call `preventDefault()` to stop other listeners acting on it. The field takes no action on a choice; you decide what it does. */
14330
+ /** Fired by an option in `suggestions` when it is chosen, and bubbles out of the field. Cancelable: call `preventDefault()` to stop other listeners acting on it. The field takes no action on an option you slotted, and you decide what it does. A suggestion from `source` is written over the term at the caret unless you cancel it. */
14316
14331
  "onhelix-select"?: ((
14317
14332
  e: HelixHeadlessFilterInputHelixSelectElementEvent,
14318
14333
  ) => void) | undefined;
@@ -14348,6 +14363,14 @@ an icon. */
14348
14363
  /** The name of the clear button, read by screen readers. The button shows only
14349
14364
  an icon. */
14350
14365
  "prop:clearLabel"?: (HelixHeadlessFilterInput["clearLabel"]) | undefined;
14366
+ /** How long typing rests before `source` is asked, in milliseconds. A delay
14367
+ sends one request for a word in place of one for each letter. The arrow
14368
+ keys and Enter ask at once. `0` asks on every keystroke. */
14369
+ "attr:filter-delay"?: (HelixHeadlessFilterInput["filterDelay"] | `${number}`) | undefined;
14370
+ /** How long typing rests before `source` is asked, in milliseconds. A delay
14371
+ sends one request for a word in place of one for each letter. The arrow
14372
+ keys and Enter ask at once. `0` asks on every keystroke. */
14373
+ "prop:filterDelay"?: (HelixHeadlessFilterInput["filterDelay"]) | undefined;
14351
14374
  /** Whether the panel narrows itself to the suggestions that hold the term
14352
14375
  being typed, as React Aria's Autocomplete filters. Off, the panel shows
14353
14376
  what you slot and you narrow it. */
@@ -14541,6 +14564,13 @@ imperatively later, and nothing is hydrating by then.
14541
14564
 
14542
14565
  Here rather than on each control. */
14543
14566
  "prop:name"?: (HelixHeadlessFilterInput["name"]) | undefined;
14567
+ /** Where the suggestions come from: a function given the term at the caret,
14568
+ the field it is a value of, the whole text, and an `AbortSignal`, which
14569
+ returns the suggestions or a promise of them. Each is a string, or a
14570
+ `value` with a `label` and `disabled`. The field draws them as options in
14571
+ the panel, beside any you slot. A property only, there being no way to
14572
+ write a function in markup. See the class description. */
14573
+ "prop:source"?: (HelixHeadlessFilterInput["source"]) | undefined;
14544
14574
  /** Fired from the input when the user finishes editing the text. */
14545
14575
  "on:change"?: ((e: HelixHeadlessFilterInputElementEvent) => void) | undefined;
14546
14576
  /** Fired as the text changes. Cancelable: call `preventDefault()` to keep the current text. */
@@ -14563,7 +14593,7 @@ Here rather than on each control. */
14563
14593
  "on:helix-facet"?: ((
14564
14594
  e: HelixHeadlessFilterInputHelixFacetElementEvent,
14565
14595
  ) => void) | undefined;
14566
- /** Fired by an option in `suggestions` when it is chosen, and bubbles out of the field. Cancelable: call `preventDefault()` to stop other listeners acting on it. The field takes no action on a choice; you decide what it does. */
14596
+ /** Fired by an option in `suggestions` when it is chosen, and bubbles out of the field. Cancelable: call `preventDefault()` to stop other listeners acting on it. The field takes no action on an option you slotted, and you decide what it does. A suggestion from `source` is written over the term at the caret unless you cancel it. */
14567
14597
  "on:helix-select"?: ((
14568
14598
  e: HelixHeadlessFilterInputHelixSelectElementEvent,
14569
14599
  ) => void) | undefined;
@@ -14950,6 +14980,14 @@ or hides its notes. Drawn where the notes have two groups or more. */
14950
14980
  "legend-label"?: (HelixHeadlessGraph["legendLabel"]) | undefined;
14951
14981
  /** The legend's name, read by screen readers. */
14952
14982
  legendLabel?: (HelixHeadlessGraph["legendLabel"]) | undefined;
14983
+ /** What screen readers hear when a walk along the links reaches a note that
14984
+ has no link to follow, or goes back to a note: `{note}` for its name and
14985
+ `{count}` for how many notes the list holds that it links to or from. */
14986
+ "link-count-label"?: (HelixHeadlessGraph["linkCountLabel"]) | undefined;
14987
+ /** What screen readers hear when a walk along the links reaches a note that
14988
+ has no link to follow, or goes back to a note: `{note}` for its name and
14989
+ `{count}` for how many notes the list holds that it links to or from. */
14990
+ linkCountLabel?: (HelixHeadlessGraph["linkCountLabel"]) | undefined;
14953
14991
  /** How a note linked both to and from the selected one is described, with `{note}`. */
14954
14992
  "linked-both-label"?: (HelixHeadlessGraph["linkedBothLabel"]) | undefined;
14955
14993
  /** How a note linked both to and from the selected one is described, with `{note}`. */
@@ -15029,6 +15067,16 @@ the list then holds, written with `count-label`. */
15029
15067
  "using-label"?: (HelixHeadlessGraph["usingLabel"]) | undefined;
15030
15068
  /** The line shown while the graph, with `gestures="activate"`, has the gestures. */
15031
15069
  usingLabel?: (HelixHeadlessGraph["usingLabel"]) | undefined;
15070
+ /** What screen readers hear when a walk along the links moves to a note:
15071
+ `{note}` for its name, `{relation}` for how it stands to the note the
15072
+ step came from, such as "Linked from Roadmap", and `{index}` and `{count}`
15073
+ for its place among that note's links. */
15074
+ "walk-label"?: (HelixHeadlessGraph["walkLabel"]) | undefined;
15075
+ /** What screen readers hear when a walk along the links moves to a note:
15076
+ `{note}` for its name, `{relation}` for how it stands to the note the
15077
+ step came from, such as "Linked from Roadmap", and `{index}` and `{count}`
15078
+ for its place among that note's links. */
15079
+ walkLabel?: (HelixHeadlessGraph["walkLabel"]) | undefined;
15032
15080
  /** The hint shown when the wheel scrolls past the graph with
15033
15081
  `gestures="cooperative"`. `{key}` is ⌘ on a Mac and Ctrl elsewhere. */
15034
15082
  "wheel-hint-label"?: (HelixHeadlessGraph["wheelHintLabel"]) | undefined;
@@ -15227,7 +15275,7 @@ markup. */
15227
15275
  "onhelix-graph-pan"?: ((
15228
15276
  e: HelixHeadlessGraphHelixGraphPanElementEvent,
15229
15277
  ) => void) | undefined;
15230
- /** The notes, the links, the selection, the depth or the gestures changed. Used by `connectGraph`; pages do not need to listen for it. */
15278
+ /** The notes, the links, the selection, the depth, the note the keys are on, or the gestures changed. Used by `connectGraph`; pages do not need to listen for it. */
15231
15279
  "onhelix-graph-part-change"?: ((
15232
15280
  e: HelixHeadlessGraphHelixGraphPartChangeElementEvent,
15233
15281
  ) => void) | undefined;
@@ -15289,6 +15337,14 @@ or hides its notes. Drawn where the notes have two groups or more. */
15289
15337
  "attr:legend-label"?: (HelixHeadlessGraph["legendLabel"]) | undefined;
15290
15338
  /** The legend's name, read by screen readers. */
15291
15339
  "prop:legendLabel"?: (HelixHeadlessGraph["legendLabel"]) | undefined;
15340
+ /** What screen readers hear when a walk along the links reaches a note that
15341
+ has no link to follow, or goes back to a note: `{note}` for its name and
15342
+ `{count}` for how many notes the list holds that it links to or from. */
15343
+ "attr:link-count-label"?: (HelixHeadlessGraph["linkCountLabel"]) | undefined;
15344
+ /** What screen readers hear when a walk along the links reaches a note that
15345
+ has no link to follow, or goes back to a note: `{note}` for its name and
15346
+ `{count}` for how many notes the list holds that it links to or from. */
15347
+ "prop:linkCountLabel"?: (HelixHeadlessGraph["linkCountLabel"]) | undefined;
15292
15348
  /** How a note linked both to and from the selected one is described, with `{note}`. */
15293
15349
  "attr:linked-both-label"?: (HelixHeadlessGraph["linkedBothLabel"]) | undefined;
15294
15350
  /** How a note linked both to and from the selected one is described, with `{note}`. */
@@ -15368,6 +15424,16 @@ the list then holds, written with `count-label`. */
15368
15424
  "attr:using-label"?: (HelixHeadlessGraph["usingLabel"]) | undefined;
15369
15425
  /** The line shown while the graph, with `gestures="activate"`, has the gestures. */
15370
15426
  "prop:usingLabel"?: (HelixHeadlessGraph["usingLabel"]) | undefined;
15427
+ /** What screen readers hear when a walk along the links moves to a note:
15428
+ `{note}` for its name, `{relation}` for how it stands to the note the
15429
+ step came from, such as "Linked from Roadmap", and `{index}` and `{count}`
15430
+ for its place among that note's links. */
15431
+ "attr:walk-label"?: (HelixHeadlessGraph["walkLabel"]) | undefined;
15432
+ /** What screen readers hear when a walk along the links moves to a note:
15433
+ `{note}` for its name, `{relation}` for how it stands to the note the
15434
+ step came from, such as "Linked from Roadmap", and `{index}` and `{count}`
15435
+ for its place among that note's links. */
15436
+ "prop:walkLabel"?: (HelixHeadlessGraph["walkLabel"]) | undefined;
15371
15437
  /** The hint shown when the wheel scrolls past the graph with
15372
15438
  `gestures="cooperative"`. `{key}` is ⌘ on a Mac and Ctrl elsewhere. */
15373
15439
  "attr:wheel-hint-label"?: (HelixHeadlessGraph["wheelHintLabel"]) | undefined;
@@ -15567,7 +15633,7 @@ markup. */
15567
15633
  "on:helix-graph-pan"?: ((
15568
15634
  e: HelixHeadlessGraphHelixGraphPanElementEvent,
15569
15635
  ) => void) | undefined;
15570
- /** The notes, the links, the selection, the depth or the gestures changed. Used by `connectGraph`; pages do not need to listen for it. */
15636
+ /** The notes, the links, the selection, the depth, the note the keys are on, or the gestures changed. Used by `connectGraph`; pages do not need to listen for it. */
15571
15637
  "on:helix-graph-part-change"?: ((
15572
15638
  e: HelixHeadlessGraphHelixGraphPartChangeElementEvent,
15573
15639
  ) => void) | undefined;
@@ -47722,9 +47788,15 @@ export type CustomElements = {
47722
47788
  *
47723
47789
  * **The picker has an entry of its own.** `FilterMenu` draws its pages with
47724
47790
  * `Menu`, which a field without a picker does not need, so this family's entry
47725
- * leaves it out. A page that writes one imports
47726
- * `@fusion.dev/helix/filter-input/menu` as well, and a picker written without it
47727
- * is reported once the page has loaded, with the entry to add.
47791
+ * leaves it out. A page that writes a `FilterMenu` imports both entries:
47792
+ *
47793
+ * ```js
47794
+ * import '@fusion.dev/helix/filter-input'
47795
+ * import '@fusion.dev/helix/filter-input/menu'
47796
+ * ```
47797
+ *
47798
+ * A picker written without the second is reported once the page has loaded,
47799
+ * with the entry to add.
47728
47800
  *
47729
47801
  * **Suggestions.** Slot options into `suggestions` and the field shows them,
47730
47802
  * moves through them with the arrow keys, and presses one on Enter. The option
@@ -47740,6 +47812,24 @@ export type CustomElements = {
47740
47812
  * completion back and put the caret after it, or the next parse reads the wrong
47741
47813
  * term.
47742
47814
  *
47815
+ * **Suggestions from a source.** Set `source` to a function, and the field asks
47816
+ * it for suggestions as the text changes and draws them as options in the
47817
+ * panel. It is given the term at the caret, the field the term is a value of,
47818
+ * the whole text, and an `AbortSignal`, and returns the suggestions or a
47819
+ * promise of them. `filter-delay` waits for typing to rest before asking, and
47820
+ * a later keystroke aborts a request still out. While a promise is pending, the
47821
+ * panel shows `loading-text` and a screen reader hears it, then the count.
47822
+ * Choosing a suggestion writes its value over the term at the caret, and a
47823
+ * finished `field:value` is read into a chip as Enter would. Cancel its
47824
+ * `helix-select` to write it yourself.
47825
+ *
47826
+ * ```js
47827
+ * filter.source = async ({ field, term, signal }) => {
47828
+ * const response = await fetch(`/values?field=${field}&q=${term}`, { signal })
47829
+ * return response.json()
47830
+ * }
47831
+ * ```
47832
+ *
47743
47833
  * **State.** Three things have three different owners:
47744
47834
  *
47745
47835
  * - `value` is the text. The user types, the element applies it, and `input`
@@ -47781,6 +47871,9 @@ export type CustomElements = {
47781
47871
  * them.
47782
47872
  * - `clear-label`/`clearLabel`: The name of the clear button, read by screen readers. The button shows only
47783
47873
  * an icon.
47874
+ * - `filter-delay`/`filterDelay`: How long typing rests before `source` is asked, in milliseconds. A delay
47875
+ * sends one request for a word in place of one for each letter. The arrow
47876
+ * keys and Enter ask at once. `0` asks on every keystroke.
47784
47877
  * - `filter-suggestions`/`filterSuggestions`: Whether the panel narrows itself to the suggestions that hold the term
47785
47878
  * being typed, as React Aria's Autocomplete filters. Off, the panel shows
47786
47879
  * what you slot and you narrow it.
@@ -47888,6 +47981,12 @@ export type CustomElements = {
47888
47981
  * imperatively later, and nothing is hydrating by then.
47889
47982
  *
47890
47983
  * Here rather than on each control.
47984
+ * - `source`: Where the suggestions come from: a function given the term at the caret,
47985
+ * the field it is a value of, the whole text, and an `AbortSignal`, which
47986
+ * returns the suggestions or a promise of them. Each is a string, or a
47987
+ * `value` with a `label` and `disabled`. The field draws them as options in
47988
+ * the panel, beside any you slot. A property only, there being no way to
47989
+ * write a function in markup. See the class description. (property only)
47891
47990
  * - `selectionDirection`: The direction of the selection, named as on `<input>`.
47892
47991
  *
47893
47992
  * **Published for the reason `Input` publishes them, and this element needs
@@ -47944,7 +48043,7 @@ export type CustomElements = {
47944
48043
  * - `helix-before-open`: Fired before the suggestions open. Cancelable: call `preventDefault()` to keep them closed.
47945
48044
  * - `helix-dismiss`: Fired when the user removes a token with its remove button or with Backspace in an empty field. A `FilterChip` that names a declared field sends `helix-facet` instead. Cancelable: call `preventDefault()` to keep the token.
47946
48045
  * - `helix-facet`: Fired when a filter changes, whether it was typed, picked, or removed. Cancelable: call `preventDefault()` to leave the text and the chip as they were.
47947
- * - `helix-select`: Fired by an option in `suggestions` when it is chosen, and bubbles out of the field. Cancelable: call `preventDefault()` to stop other listeners acting on it. The field takes no action on a choice; you decide what it does.
48046
+ * - `helix-select`: Fired by an option in `suggestions` when it is chosen, and bubbles out of the field. Cancelable: call `preventDefault()` to stop other listeners acting on it. The field takes no action on an option you slotted, and you decide what it does. A suggestion from `source` is written over the term at the caret unless you cancel it.
47948
48047
  * - `input`: Fired when the text changes.
47949
48048
  * - `toggle`: Fired when the suggestions panel opens or closes, with `newState` and `oldState`.
47950
48049
  *
@@ -48051,7 +48150,7 @@ export type CustomElements = {
48051
48150
  * - `label`: The label above the field.
48052
48151
  * - `lead`: The box before the text that holds the `start` slot.
48053
48152
  * - `live`: The live region that says how many suggestions the open panel offers. It is rendered from the first time the panel opens with suggestions.
48054
- * - `loading`: Shown in the panel while the suggestions load.
48153
+ * - `loading`: Shown in the panel while the suggestions load: while `loading` is set, or a request to `source` is out.
48055
48154
  * - `rest`: The count of tokens that did not fit. It is a `<button>` with `expandable` and a `<span>` without it.
48056
48155
  * - `surface`: The floating panel the suggestions sit in.
48057
48156
  * - `tags`: The row the tokens sit in.
@@ -48061,7 +48160,7 @@ export type CustomElements = {
48061
48160
  * These can be used to apply styling when a component is in a given state.
48062
48161
  *
48063
48162
  * - `invalid`: The field has an error to show, from the browser's validation or from your `feedback`.
48064
- * - `loading`: While `loading` is set.
48163
+ * - `loading`: While `loading` is set, or a request to `source` is out.
48065
48164
  * - `no-results`: `filter-suggestions` hid every suggestion, and `no-results-text` or the `no-results` slot says so.
48066
48165
  * - `open`: The suggestions are open.
48067
48166
  * - `settled`: Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
@@ -48281,6 +48380,23 @@ export type CustomElements = {
48281
48380
  * Set `depth` to show the local graph: only the notes within that many links
48282
48381
  * of the selected one.
48283
48382
  *
48383
+ * The search field is a combobox over the list. Down and Up move through the
48384
+ * notes while the focus stays in the field, and Enter selects one.
48385
+ *
48386
+ * **Walk the links with Alt and the arrow keys**, in the list or in the
48387
+ * search field once its keys are on a note. The note the keys are on is
48388
+ * marked in the drawing.
48389
+ *
48390
+ * | Key | What it does |
48391
+ * |---|---|
48392
+ * | Alt+Right | Follows a link, to the first note the one the keys are on links to or from. |
48393
+ * | Alt+Down, Alt+Up | Moves to the next or the previous note linked to the one the step came from, wrapping. |
48394
+ * | Alt+Left | Goes back along the links walked. |
48395
+ *
48396
+ * Right and Left swap on a right-to-left page. Each step says the note, how it
48397
+ * stands to the note it came from, and its place among that note's links,
48398
+ * such as "Hiring plan. Linked from Roadmap. 2 of 3". Enter selects the note.
48399
+ *
48284
48400
  * Set `legend` to name the groups over the drawing, each a button that shows
48285
48401
  * or hides its notes. `hiddenGroups` holds the groups turned off, and the
48286
48402
  * list holds only what the drawing shows. The legend is a toolbar and one tab
@@ -48316,6 +48432,9 @@ export type CustomElements = {
48316
48432
  * - `legend`: Names the groups over the drawing, each a button in its color that shows
48317
48433
  * or hides its notes. Drawn where the notes have two groups or more.
48318
48434
  * - `legend-label`/`legendLabel`: The legend's name, read by screen readers.
48435
+ * - `link-count-label`/`linkCountLabel`: What screen readers hear when a walk along the links reaches a note that
48436
+ * has no link to follow, or goes back to a note: `{note}` for its name and
48437
+ * `{count}` for how many notes the list holds that it links to or from.
48319
48438
  * - `linked-both-label`/`linkedBothLabel`: How a note linked both to and from the selected one is described, with `{note}`.
48320
48439
  * - `linked-from-label`/`linkedFromLabel`: How a note the selected one links to is described, with `{note}`, the selected note's name.
48321
48440
  * - `links-to-label`/`linksToLabel`: How a note that links to the selected one is described, with `{note}`.
@@ -48340,6 +48459,10 @@ export type CustomElements = {
48340
48459
  * the list then holds, written with `count-label`.
48341
48460
  * - `two-finger-hint-label`/`twoFingerHintLabel`: The hint shown when one finger swipes over the graph with `gestures="cooperative"`.
48342
48461
  * - `using-label`/`usingLabel`: The line shown while the graph, with `gestures="activate"`, has the gestures.
48462
+ * - `walk-label`/`walkLabel`: What screen readers hear when a walk along the links moves to a note:
48463
+ * `{note}` for its name, `{relation}` for how it stands to the note the
48464
+ * step came from, such as "Linked from Roadmap", and `{index}` and `{count}`
48465
+ * for its place among that note's links.
48343
48466
  * - `wheel-hint-label`/`wheelHintLabel`: The hint shown when the wheel scrolls past the graph with
48344
48467
  * `gestures="cooperative"`. `{key}` is ⌘ on a Mac and Ctrl elsewhere.
48345
48468
  * - `zoomed-in-label`/`zoomedInLabel`: What screen readers hear once the drawing has zoomed in, after `moved()`.
@@ -48473,7 +48596,7 @@ export type CustomElements = {
48473
48596
  * - `helix-graph-filter`: A group in the legend was pressed, to show or hide its notes. The detail's `hidden` is every group that would be hidden. Cancelable: cancel it to keep the groups as they were, or to set `hiddenGroups` yourself.
48474
48597
  * - `helix-graph-layout`: A layout settled, with where it put each note, sent by `laidOut()`. Keep the positions and pass them back as each note's `x` and `y`, and `z` where a renderer draws in three dimensions.
48475
48598
  * - `helix-graph-pan`: A pan button or arrow key was pressed. `connectGraph` moves the drawing, then calls `moved()`.
48476
- * - `helix-graph-part-change`: The notes, the links, the selection, the depth or the gestures changed. Used by `connectGraph`; pages do not need to listen for it.
48599
+ * - `helix-graph-part-change`: The notes, the links, the selection, the depth, the note the keys are on, or the gestures changed. Used by `connectGraph`; pages do not need to listen for it.
48477
48600
  * - `helix-graph-ready`: The graph has been drawn whole, laid out and settled. Sent each time `drawn()` says it is drawn after it was not.
48478
48601
  * - `helix-graph-signal`: Something passed between two notes, sent by `signal()`. Used by `connectGraph`; pages do not need to listen for it.
48479
48602
  * - `helix-graph-zoom`: A zoom button or key was pressed. `connectGraph` zooms the drawing, then calls `moved()`.
@@ -77257,9 +77380,15 @@ export type CustomElementsSolidJs = {
77257
77380
  *
77258
77381
  * **The picker has an entry of its own.** `FilterMenu` draws its pages with
77259
77382
  * `Menu`, which a field without a picker does not need, so this family's entry
77260
- * leaves it out. A page that writes one imports
77261
- * `@fusion.dev/helix/filter-input/menu` as well, and a picker written without it
77262
- * is reported once the page has loaded, with the entry to add.
77383
+ * leaves it out. A page that writes a `FilterMenu` imports both entries:
77384
+ *
77385
+ * ```js
77386
+ * import '@fusion.dev/helix/filter-input'
77387
+ * import '@fusion.dev/helix/filter-input/menu'
77388
+ * ```
77389
+ *
77390
+ * A picker written without the second is reported once the page has loaded,
77391
+ * with the entry to add.
77263
77392
  *
77264
77393
  * **Suggestions.** Slot options into `suggestions` and the field shows them,
77265
77394
  * moves through them with the arrow keys, and presses one on Enter. The option
@@ -77275,6 +77404,24 @@ export type CustomElementsSolidJs = {
77275
77404
  * completion back and put the caret after it, or the next parse reads the wrong
77276
77405
  * term.
77277
77406
  *
77407
+ * **Suggestions from a source.** Set `source` to a function, and the field asks
77408
+ * it for suggestions as the text changes and draws them as options in the
77409
+ * panel. It is given the term at the caret, the field the term is a value of,
77410
+ * the whole text, and an `AbortSignal`, and returns the suggestions or a
77411
+ * promise of them. `filter-delay` waits for typing to rest before asking, and
77412
+ * a later keystroke aborts a request still out. While a promise is pending, the
77413
+ * panel shows `loading-text` and a screen reader hears it, then the count.
77414
+ * Choosing a suggestion writes its value over the term at the caret, and a
77415
+ * finished `field:value` is read into a chip as Enter would. Cancel its
77416
+ * `helix-select` to write it yourself.
77417
+ *
77418
+ * ```js
77419
+ * filter.source = async ({ field, term, signal }) => {
77420
+ * const response = await fetch(`/values?field=${field}&q=${term}`, { signal })
77421
+ * return response.json()
77422
+ * }
77423
+ * ```
77424
+ *
77278
77425
  * **State.** Three things have three different owners:
77279
77426
  *
77280
77427
  * - `value` is the text. The user types, the element applies it, and `input`
@@ -77316,6 +77463,9 @@ export type CustomElementsSolidJs = {
77316
77463
  * them.
77317
77464
  * - `clear-label`/`clearLabel`: The name of the clear button, read by screen readers. The button shows only
77318
77465
  * an icon.
77466
+ * - `filter-delay`/`filterDelay`: How long typing rests before `source` is asked, in milliseconds. A delay
77467
+ * sends one request for a word in place of one for each letter. The arrow
77468
+ * keys and Enter ask at once. `0` asks on every keystroke.
77319
77469
  * - `filter-suggestions`/`filterSuggestions`: Whether the panel narrows itself to the suggestions that hold the term
77320
77470
  * being typed, as React Aria's Autocomplete filters. Off, the panel shows
77321
77471
  * what you slot and you narrow it.
@@ -77423,6 +77573,12 @@ export type CustomElementsSolidJs = {
77423
77573
  * imperatively later, and nothing is hydrating by then.
77424
77574
  *
77425
77575
  * Here rather than on each control.
77576
+ * - `source`: Where the suggestions come from: a function given the term at the caret,
77577
+ * the field it is a value of, the whole text, and an `AbortSignal`, which
77578
+ * returns the suggestions or a promise of them. Each is a string, or a
77579
+ * `value` with a `label` and `disabled`. The field draws them as options in
77580
+ * the panel, beside any you slot. A property only, there being no way to
77581
+ * write a function in markup. See the class description. (property only)
77426
77582
  * - `selectionDirection`: The direction of the selection, named as on `<input>`.
77427
77583
  *
77428
77584
  * **Published for the reason `Input` publishes them, and this element needs
@@ -77479,7 +77635,7 @@ export type CustomElementsSolidJs = {
77479
77635
  * - `helix-before-open`: Fired before the suggestions open. Cancelable: call `preventDefault()` to keep them closed.
77480
77636
  * - `helix-dismiss`: Fired when the user removes a token with its remove button or with Backspace in an empty field. A `FilterChip` that names a declared field sends `helix-facet` instead. Cancelable: call `preventDefault()` to keep the token.
77481
77637
  * - `helix-facet`: Fired when a filter changes, whether it was typed, picked, or removed. Cancelable: call `preventDefault()` to leave the text and the chip as they were.
77482
- * - `helix-select`: Fired by an option in `suggestions` when it is chosen, and bubbles out of the field. Cancelable: call `preventDefault()` to stop other listeners acting on it. The field takes no action on a choice; you decide what it does.
77638
+ * - `helix-select`: Fired by an option in `suggestions` when it is chosen, and bubbles out of the field. Cancelable: call `preventDefault()` to stop other listeners acting on it. The field takes no action on an option you slotted, and you decide what it does. A suggestion from `source` is written over the term at the caret unless you cancel it.
77483
77639
  * - `input`: Fired when the text changes.
77484
77640
  * - `toggle`: Fired when the suggestions panel opens or closes, with `newState` and `oldState`.
77485
77641
  *
@@ -77586,7 +77742,7 @@ export type CustomElementsSolidJs = {
77586
77742
  * - `label`: The label above the field.
77587
77743
  * - `lead`: The box before the text that holds the `start` slot.
77588
77744
  * - `live`: The live region that says how many suggestions the open panel offers. It is rendered from the first time the panel opens with suggestions.
77589
- * - `loading`: Shown in the panel while the suggestions load.
77745
+ * - `loading`: Shown in the panel while the suggestions load: while `loading` is set, or a request to `source` is out.
77590
77746
  * - `rest`: The count of tokens that did not fit. It is a `<button>` with `expandable` and a `<span>` without it.
77591
77747
  * - `surface`: The floating panel the suggestions sit in.
77592
77748
  * - `tags`: The row the tokens sit in.
@@ -77596,7 +77752,7 @@ export type CustomElementsSolidJs = {
77596
77752
  * These can be used to apply styling when a component is in a given state.
77597
77753
  *
77598
77754
  * - `invalid`: The field has an error to show, from the browser's validation or from your `feedback`.
77599
- * - `loading`: While `loading` is set.
77755
+ * - `loading`: While `loading` is set, or a request to `source` is out.
77600
77756
  * - `no-results`: `filter-suggestions` hid every suggestion, and `no-results-text` or the `no-results` slot says so.
77601
77757
  * - `open`: The suggestions are open.
77602
77758
  * - `settled`: Set one frame after the first render. Transitions wait for it, so an element does not animate its own arrival.
@@ -77818,6 +77974,23 @@ export type CustomElementsSolidJs = {
77818
77974
  * Set `depth` to show the local graph: only the notes within that many links
77819
77975
  * of the selected one.
77820
77976
  *
77977
+ * The search field is a combobox over the list. Down and Up move through the
77978
+ * notes while the focus stays in the field, and Enter selects one.
77979
+ *
77980
+ * **Walk the links with Alt and the arrow keys**, in the list or in the
77981
+ * search field once its keys are on a note. The note the keys are on is
77982
+ * marked in the drawing.
77983
+ *
77984
+ * | Key | What it does |
77985
+ * |---|---|
77986
+ * | Alt+Right | Follows a link, to the first note the one the keys are on links to or from. |
77987
+ * | Alt+Down, Alt+Up | Moves to the next or the previous note linked to the one the step came from, wrapping. |
77988
+ * | Alt+Left | Goes back along the links walked. |
77989
+ *
77990
+ * Right and Left swap on a right-to-left page. Each step says the note, how it
77991
+ * stands to the note it came from, and its place among that note's links,
77992
+ * such as "Hiring plan. Linked from Roadmap. 2 of 3". Enter selects the note.
77993
+ *
77821
77994
  * Set `legend` to name the groups over the drawing, each a button that shows
77822
77995
  * or hides its notes. `hiddenGroups` holds the groups turned off, and the
77823
77996
  * list holds only what the drawing shows. The legend is a toolbar and one tab
@@ -77853,6 +78026,9 @@ export type CustomElementsSolidJs = {
77853
78026
  * - `legend`: Names the groups over the drawing, each a button in its color that shows
77854
78027
  * or hides its notes. Drawn where the notes have two groups or more.
77855
78028
  * - `legend-label`/`legendLabel`: The legend's name, read by screen readers.
78029
+ * - `link-count-label`/`linkCountLabel`: What screen readers hear when a walk along the links reaches a note that
78030
+ * has no link to follow, or goes back to a note: `{note}` for its name and
78031
+ * `{count}` for how many notes the list holds that it links to or from.
77856
78032
  * - `linked-both-label`/`linkedBothLabel`: How a note linked both to and from the selected one is described, with `{note}`.
77857
78033
  * - `linked-from-label`/`linkedFromLabel`: How a note the selected one links to is described, with `{note}`, the selected note's name.
77858
78034
  * - `links-to-label`/`linksToLabel`: How a note that links to the selected one is described, with `{note}`.
@@ -77877,6 +78053,10 @@ export type CustomElementsSolidJs = {
77877
78053
  * the list then holds, written with `count-label`.
77878
78054
  * - `two-finger-hint-label`/`twoFingerHintLabel`: The hint shown when one finger swipes over the graph with `gestures="cooperative"`.
77879
78055
  * - `using-label`/`usingLabel`: The line shown while the graph, with `gestures="activate"`, has the gestures.
78056
+ * - `walk-label`/`walkLabel`: What screen readers hear when a walk along the links moves to a note:
78057
+ * `{note}` for its name, `{relation}` for how it stands to the note the
78058
+ * step came from, such as "Linked from Roadmap", and `{index}` and `{count}`
78059
+ * for its place among that note's links.
77880
78060
  * - `wheel-hint-label`/`wheelHintLabel`: The hint shown when the wheel scrolls past the graph with
77881
78061
  * `gestures="cooperative"`. `{key}` is ⌘ on a Mac and Ctrl elsewhere.
77882
78062
  * - `zoomed-in-label`/`zoomedInLabel`: What screen readers hear once the drawing has zoomed in, after `moved()`.
@@ -78010,7 +78190,7 @@ export type CustomElementsSolidJs = {
78010
78190
  * - `helix-graph-filter`: A group in the legend was pressed, to show or hide its notes. The detail's `hidden` is every group that would be hidden. Cancelable: cancel it to keep the groups as they were, or to set `hiddenGroups` yourself.
78011
78191
  * - `helix-graph-layout`: A layout settled, with where it put each note, sent by `laidOut()`. Keep the positions and pass them back as each note's `x` and `y`, and `z` where a renderer draws in three dimensions.
78012
78192
  * - `helix-graph-pan`: A pan button or arrow key was pressed. `connectGraph` moves the drawing, then calls `moved()`.
78013
- * - `helix-graph-part-change`: The notes, the links, the selection, the depth or the gestures changed. Used by `connectGraph`; pages do not need to listen for it.
78193
+ * - `helix-graph-part-change`: The notes, the links, the selection, the depth, the note the keys are on, or the gestures changed. Used by `connectGraph`; pages do not need to listen for it.
78014
78194
  * - `helix-graph-ready`: The graph has been drawn whole, laid out and settled. Sent each time `drawn()` says it is drawn after it was not.
78015
78195
  * - `helix-graph-signal`: Something passed between two notes, sent by `signal()`. Used by `connectGraph`; pages do not need to listen for it.
78016
78196
  * - `helix-graph-zoom`: A zoom button or key was pressed. `connectGraph` zooms the drawing, then calls `moved()`.
package/types/vue.d.ts CHANGED
@@ -7245,6 +7245,14 @@ an icon. */
7245
7245
  /** The name of the clear button, read by screen readers. The button shows only
7246
7246
  an icon. */
7247
7247
  clearLabel?: (HelixHeadlessFilterInput["clearLabel"]) | undefined;
7248
+ /** How long typing rests before `source` is asked, in milliseconds. A delay
7249
+ sends one request for a word in place of one for each letter. The arrow
7250
+ keys and Enter ask at once. `0` asks on every keystroke. */
7251
+ "filter-delay"?: (HelixHeadlessFilterInput["filterDelay"] | `${number}`) | undefined;
7252
+ /** How long typing rests before `source` is asked, in milliseconds. A delay
7253
+ sends one request for a word in place of one for each letter. The arrow
7254
+ keys and Enter ask at once. `0` asks on every keystroke. */
7255
+ filterDelay?: (HelixHeadlessFilterInput["filterDelay"]) | undefined;
7248
7256
  /** Whether the panel narrows itself to the suggestions that hold the term
7249
7257
  being typed, as React Aria's Autocomplete filters. Off, the panel shows
7250
7258
  what you slot and you narrow it. */
@@ -7438,6 +7446,13 @@ imperatively later, and nothing is hydrating by then.
7438
7446
 
7439
7447
  Here rather than on each control. */
7440
7448
  name?: (HelixHeadlessFilterInput["name"]) | undefined;
7449
+ /** Where the suggestions come from: a function given the term at the caret,
7450
+ the field it is a value of, the whole text, and an `AbortSignal`, which
7451
+ returns the suggestions or a promise of them. Each is a string, or a
7452
+ `value` with a `label` and `disabled`. The field draws them as options in
7453
+ the panel, beside any you slot. A property only, there being no way to
7454
+ write a function in markup. See the class description. */
7455
+ source?: (HelixHeadlessFilterInput["source"]) | undefined;
7441
7456
  };
7442
7457
 
7443
7458
  export type HelixHeadlessFilterInputVueEvents = {
@@ -7453,7 +7468,7 @@ export type HelixHeadlessFilterInputVueEvents = {
7453
7468
  "helix-dismiss": HelixHeadlessFilterInputHelixDismissElementEvent;
7454
7469
  /** Fired when a filter changes, whether it was typed, picked, or removed. Cancelable: call `preventDefault()` to leave the text and the chip as they were. */
7455
7470
  "helix-facet": HelixHeadlessFilterInputHelixFacetElementEvent;
7456
- /** Fired by an option in `suggestions` when it is chosen, and bubbles out of the field. Cancelable: call `preventDefault()` to stop other listeners acting on it. The field takes no action on a choice; you decide what it does. */
7471
+ /** Fired by an option in `suggestions` when it is chosen, and bubbles out of the field. Cancelable: call `preventDefault()` to stop other listeners acting on it. The field takes no action on an option you slotted, and you decide what it does. A suggestion from `source` is written over the term at the caret unless you cancel it. */
7457
7472
  "helix-select": HelixHeadlessFilterInputHelixSelectElementEvent;
7458
7473
  /** Fired when the text changes. */
7459
7474
  input: HelixHeadlessFilterInputElementEvent;
@@ -7685,6 +7700,14 @@ or hides its notes. Drawn where the notes have two groups or more. */
7685
7700
  "legend-label"?: (HelixHeadlessGraph["legendLabel"]) | undefined;
7686
7701
  /** The legend's name, read by screen readers. */
7687
7702
  legendLabel?: (HelixHeadlessGraph["legendLabel"]) | undefined;
7703
+ /** What screen readers hear when a walk along the links reaches a note that
7704
+ has no link to follow, or goes back to a note: `{note}` for its name and
7705
+ `{count}` for how many notes the list holds that it links to or from. */
7706
+ "link-count-label"?: (HelixHeadlessGraph["linkCountLabel"]) | undefined;
7707
+ /** What screen readers hear when a walk along the links reaches a note that
7708
+ has no link to follow, or goes back to a note: `{note}` for its name and
7709
+ `{count}` for how many notes the list holds that it links to or from. */
7710
+ linkCountLabel?: (HelixHeadlessGraph["linkCountLabel"]) | undefined;
7688
7711
  /** How a note linked both to and from the selected one is described, with `{note}`. */
7689
7712
  "linked-both-label"?: (HelixHeadlessGraph["linkedBothLabel"]) | undefined;
7690
7713
  /** How a note linked both to and from the selected one is described, with `{note}`. */
@@ -7764,6 +7787,16 @@ the list then holds, written with `count-label`. */
7764
7787
  "using-label"?: (HelixHeadlessGraph["usingLabel"]) | undefined;
7765
7788
  /** The line shown while the graph, with `gestures="activate"`, has the gestures. */
7766
7789
  usingLabel?: (HelixHeadlessGraph["usingLabel"]) | undefined;
7790
+ /** What screen readers hear when a walk along the links moves to a note:
7791
+ `{note}` for its name, `{relation}` for how it stands to the note the
7792
+ step came from, such as "Linked from Roadmap", and `{index}` and `{count}`
7793
+ for its place among that note's links. */
7794
+ "walk-label"?: (HelixHeadlessGraph["walkLabel"]) | undefined;
7795
+ /** What screen readers hear when a walk along the links moves to a note:
7796
+ `{note}` for its name, `{relation}` for how it stands to the note the
7797
+ step came from, such as "Linked from Roadmap", and `{index}` and `{count}`
7798
+ for its place among that note's links. */
7799
+ walkLabel?: (HelixHeadlessGraph["walkLabel"]) | undefined;
7767
7800
  /** The hint shown when the wheel scrolls past the graph with
7768
7801
  `gestures="cooperative"`. `{key}` is ⌘ on a Mac and Ctrl elsewhere. */
7769
7802
  "wheel-hint-label"?: (HelixHeadlessGraph["wheelHintLabel"]) | undefined;
@@ -7956,7 +7989,7 @@ export type HelixHeadlessGraphVueEvents = {
7956
7989
  "helix-graph-layout": HelixHeadlessGraphHelixGraphLayoutElementEvent;
7957
7990
  /** A pan button or arrow key was pressed. `connectGraph` moves the drawing, then calls `moved()`. */
7958
7991
  "helix-graph-pan": HelixHeadlessGraphHelixGraphPanElementEvent;
7959
- /** The notes, the links, the selection, the depth or the gestures changed. Used by `connectGraph`; pages do not need to listen for it. */
7992
+ /** The notes, the links, the selection, the depth, the note the keys are on, or the gestures changed. Used by `connectGraph`; pages do not need to listen for it. */
7960
7993
  "helix-graph-part-change": HelixHeadlessGraphHelixGraphPartChangeElementEvent;
7961
7994
  /** The graph has been drawn whole, laid out and settled. Sent each time `drawn()` says it is drawn after it was not. */
7962
7995
  "helix-graph-ready": HelixHeadlessGraphHelixGraphReadyElementEvent;
@@ -19504,7 +19537,7 @@ export interface WcToolkitVueGlobalComponents {
19504
19537
  HelixHeadlessFilterOperatorVueProps,
19505
19538
  HelixHeadlessFilterOperatorVueEvents
19506
19539
  >;
19507
- /** Tokens are your own elements, slotted in. A filter can be a facet with a prefix, a saved query, or a range, and these belong to your model, so the element does not render tokens from a list of strings. Removing a token is a request the element fires, and you apply it. Combobox works the other way: its tokens show a selection it owns, drawn from the options it was given. 'value' is the typed text. Every keystroke fires 'input' with the text on the element, so a table can narrow as the user types. Backspace in an empty field fires 'helix-dismiss' on the last token, the same event its remove control fires, so one handler covers the pointer and the keyboard. **What a form receives.** The text under 'name', and then one more entry under 'name' for each token, in the order the tokens stand: | Token | Entry | |---|---| | Any element with a 'value' attribute | The value as written: 'saved:mine' | | 'FilterChip' with 'name' and values | 'status:open,pending' | | The same, with 'operator="none"' (what a typed '-status:' makes) | '-status:open,pending' | | The same, with any other 'operator' except 'any' | 'created[after]:2024' | | A token with neither | Nothing | The values are the chip's text outside its 'field' and 'operator' slots, with the spaces around each comma removed. A server splits an entry at its first colon, reads a leading '-' as the negation, and reads a word in brackets before the colon as the comparison. **Keyboard.** Focus stays in the text, which is the field's one tab stop. The text names the highlighted suggestion as its 'aria-activedescendant', and the arrow keys reach the tokens. | Key | What it does | |---|---| | Enter | Presses the highlighted suggestion. With none highlighted, reads the typed filters into chips, or submits the form when there are none. | | Down, Up | Moves the highlight through the suggestions, wrapping, and opens the panel. | | Home, End | Highlights the first or the last suggestion while the panel is open. | | Escape | Closes the panel and stops there, so a dialog around the field stays open. With the panel closed, empties the text. In an empty field, does nothing, so the page hears it. | | Backspace | In an empty field, asks the last token to go. | | Left | With the caret at the start of the text, moves to the last token's remove button. Right on a right-to-left page. | | Left, Right on a token | Move between the tokens, and past the last back to the text. | | Delete, Backspace on a token | Asks the token to go. | **Typed filters.** The field parses only the fields you declare. Write 'FilterField' elements, usually inside the 'FilterMenu' slotted into 'start', so the picker and the text share one set. On Enter, the field reads the text against them, and a paste is read the same way as soon as it lands. 'disposition:refunded,settled' and '-disposition:refunded' fire 'helix-facet' exactly as ticking those values in the picker would, and the parsed text is removed. Everything else stays, so 'refund tuesday disposition:settled' is a filter and a search at once. Helix defines the punctuation and you define every word. 'grammar.ts' has the full rule, so read it before you rely on it. A field with no 'FilterField' in it parses nothing. A field whose values cannot be listed, such as 'assignee:ada', goes directly inside this element, outside the menu. It is not assigned to a slot, so nothing draws it and the menu has no page for it, and the grammar still reads it. **The picker has an entry of its own.** 'FilterMenu' draws its pages with 'Menu', which a field without a picker does not need, so this family's entry leaves it out. A page that writes one imports '@fusion.dev/helix/filter-input/menu' as well, and a picker written without it is reported once the page has loaded, with the entry to add. **Suggestions.** Slot options into 'suggestions' and the field shows them, moves through them with the arrow keys, and presses one on Enter. The option fires its own 'helix-select', and you decide what choosing it means. Keys before a colon and values after it use the same panel with different contents. 'filter-suggestions' narrows the panel to the options whose words hold the term at the caret, after its colon, ignoring case and accents, and hides the rest. 'readFilterText' is exported so you can narrow it yourself against partly typed text. While the panel is open, a reader hears how many suggestions it offers. The panel opens when it has options and closes when it has none, and a choice does not close it, so after picking a key you see its values next. Use 'selectionStart' and 'setSelectionRange' to write a completion back and put the caret after it, or the next parse reads the wrong term. **State.** Three things have three different owners: - 'value' is the text. The user types, the element applies it, and 'input' reports it. Setting 'value' seeds or corrects the text, and the next keystroke overwrites it, even in React. To control the value, cancel 'helix-before-change' and write the value you want, as with every field in this library. 'default-value' seeds the field. - 'open' is the suggestions panel. Cancel 'helix-before-open' or 'helix-before-close' to control it, and use 'default-open' to seed it. - Tokens are your elements in a slot and hold no state. A token goes when you remove it from your markup after 'helix-dismiss'. A 'FilterChip' naming a declared field works differently, because the picker holds what is ticked and removing the chip alone would leave its value ticked in the hidden menu. Its dismissal is treated as the field being filtered by nothing, so it fires 'helix-facet' like every other change, and the chip goes when the filter goes. Set 'chips' and the field adds, updates, and removes those chips itself, from every 'helix-facet' you do not cancel. */
19540
+ /** Tokens are your own elements, slotted in. A filter can be a facet with a prefix, a saved query, or a range, and these belong to your model, so the element does not render tokens from a list of strings. Removing a token is a request the element fires, and you apply it. Combobox works the other way: its tokens show a selection it owns, drawn from the options it was given. 'value' is the typed text. Every keystroke fires 'input' with the text on the element, so a table can narrow as the user types. Backspace in an empty field fires 'helix-dismiss' on the last token, the same event its remove control fires, so one handler covers the pointer and the keyboard. **What a form receives.** The text under 'name', and then one more entry under 'name' for each token, in the order the tokens stand: | Token | Entry | |---|---| | Any element with a 'value' attribute | The value as written: 'saved:mine' | | 'FilterChip' with 'name' and values | 'status:open,pending' | | The same, with 'operator="none"' (what a typed '-status:' makes) | '-status:open,pending' | | The same, with any other 'operator' except 'any' | 'created[after]:2024' | | A token with neither | Nothing | The values are the chip's text outside its 'field' and 'operator' slots, with the spaces around each comma removed. A server splits an entry at its first colon, reads a leading '-' as the negation, and reads a word in brackets before the colon as the comparison. **Keyboard.** Focus stays in the text, which is the field's one tab stop. The text names the highlighted suggestion as its 'aria-activedescendant', and the arrow keys reach the tokens. | Key | What it does | |---|---| | Enter | Presses the highlighted suggestion. With none highlighted, reads the typed filters into chips, or submits the form when there are none. | | Down, Up | Moves the highlight through the suggestions, wrapping, and opens the panel. | | Home, End | Highlights the first or the last suggestion while the panel is open. | | Escape | Closes the panel and stops there, so a dialog around the field stays open. With the panel closed, empties the text. In an empty field, does nothing, so the page hears it. | | Backspace | In an empty field, asks the last token to go. | | Left | With the caret at the start of the text, moves to the last token's remove button. Right on a right-to-left page. | | Left, Right on a token | Move between the tokens, and past the last back to the text. | | Delete, Backspace on a token | Asks the token to go. | **Typed filters.** The field parses only the fields you declare. Write 'FilterField' elements, usually inside the 'FilterMenu' slotted into 'start', so the picker and the text share one set. On Enter, the field reads the text against them, and a paste is read the same way as soon as it lands. 'disposition:refunded,settled' and '-disposition:refunded' fire 'helix-facet' exactly as ticking those values in the picker would, and the parsed text is removed. Everything else stays, so 'refund tuesday disposition:settled' is a filter and a search at once. Helix defines the punctuation and you define every word. 'grammar.ts' has the full rule, so read it before you rely on it. A field with no 'FilterField' in it parses nothing. A field whose values cannot be listed, such as 'assignee:ada', goes directly inside this element, outside the menu. It is not assigned to a slot, so nothing draws it and the menu has no page for it, and the grammar still reads it. **The picker has an entry of its own.** 'FilterMenu' draws its pages with 'Menu', which a field without a picker does not need, so this family's entry leaves it out. A page that writes a 'FilterMenu' imports both entries: '''js import '@fusion.dev/helix/filter-input' import '@fusion.dev/helix/filter-input/menu' ''' A picker written without the second is reported once the page has loaded, with the entry to add. **Suggestions.** Slot options into 'suggestions' and the field shows them, moves through them with the arrow keys, and presses one on Enter. The option fires its own 'helix-select', and you decide what choosing it means. Keys before a colon and values after it use the same panel with different contents. 'filter-suggestions' narrows the panel to the options whose words hold the term at the caret, after its colon, ignoring case and accents, and hides the rest. 'readFilterText' is exported so you can narrow it yourself against partly typed text. While the panel is open, a reader hears how many suggestions it offers. The panel opens when it has options and closes when it has none, and a choice does not close it, so after picking a key you see its values next. Use 'selectionStart' and 'setSelectionRange' to write a completion back and put the caret after it, or the next parse reads the wrong term. **Suggestions from a source.** Set 'source' to a function, and the field asks it for suggestions as the text changes and draws them as options in the panel. It is given the term at the caret, the field the term is a value of, the whole text, and an 'AbortSignal', and returns the suggestions or a promise of them. 'filter-delay' waits for typing to rest before asking, and a later keystroke aborts a request still out. While a promise is pending, the panel shows 'loading-text' and a screen reader hears it, then the count. Choosing a suggestion writes its value over the term at the caret, and a finished 'field:value' is read into a chip as Enter would. Cancel its 'helix-select' to write it yourself. '''js filter.source = async ({ field, term, signal }) => { const response = await fetch('/values?field=${field}&q=${term}', { signal }) return response.json() } ''' **State.** Three things have three different owners: - 'value' is the text. The user types, the element applies it, and 'input' reports it. Setting 'value' seeds or corrects the text, and the next keystroke overwrites it, even in React. To control the value, cancel 'helix-before-change' and write the value you want, as with every field in this library. 'default-value' seeds the field. - 'open' is the suggestions panel. Cancel 'helix-before-open' or 'helix-before-close' to control it, and use 'default-open' to seed it. - Tokens are your elements in a slot and hold no state. A token goes when you remove it from your markup after 'helix-dismiss'. A 'FilterChip' naming a declared field works differently, because the picker holds what is ticked and removing the chip alone would leave its value ticked in the hidden menu. Its dismissal is treated as the field being filtered by nothing, so it fires 'helix-facet' like every other change, and the chip goes when the filter goes. Set 'chips' and the field adds, updates, and removes those chips itself, from every 'helix-facet' you do not cancel. */
19508
19541
  "helix-headless-filter-input": DefineCustomElement<
19509
19542
  HelixHeadlessFilterInput,
19510
19543
  HelixHeadlessFilterInputVueProps,
@@ -19516,7 +19549,7 @@ export interface WcToolkitVueGlobalComponents {
19516
19549
  HelixHeadlessFormatDateVueProps,
19517
19550
  HelixHeadlessFormatDateVueEvents
19518
19551
  >;
19519
- /** Helix draws no graph. A force layout of thousands of notes is physics and WebGL, which a renderer such as Sigma.js does. Put the renderer's box in the frame, and 'connectGraph' from '@fusion.dev/helix-graph' draws 'nodes' and 'links' in it and keeps it in step with the frame. What the frame adds is what a drawing of a graph cannot do: a list of every note beside it, which is the graph for a screen reader and a quick way to find a note for anyone. The selected note comes first, then the notes it links to and from, then every other note, and the search field narrows it. Selecting a note in the list or in the drawing is the same selection. Set 'depth' to show the local graph: only the notes within that many links of the selected one. Set 'legend' to name the groups over the drawing, each a button that shows or hides its notes. 'hiddenGroups' holds the groups turned off, and the list holds only what the drawing shows. The legend is a toolbar and one tab stop: the arrow keys, Home, End and a group's first letters move between its buttons, and Space or Enter presses one. 'nodes' and 'links' are properties, never markup: a graph of ten thousand notes as ten thousand elements would cost what the renderer exists to save. The list draws only the rows in view. The list scrolls on this tier. It carries 'data-scroller', and 'core' ships '[data-scroller] { overflow: auto }' into every shadow root. You still need to set a block size on the list, or it grows to hold every row. */
19552
+ /** Helix draws no graph. A force layout of thousands of notes is physics and WebGL, which a renderer such as Sigma.js does. Put the renderer's box in the frame, and 'connectGraph' from '@fusion.dev/helix-graph' draws 'nodes' and 'links' in it and keeps it in step with the frame. What the frame adds is what a drawing of a graph cannot do: a list of every note beside it, which is the graph for a screen reader and a quick way to find a note for anyone. The selected note comes first, then the notes it links to and from, then every other note, and the search field narrows it. Selecting a note in the list or in the drawing is the same selection. Set 'depth' to show the local graph: only the notes within that many links of the selected one. The search field is a combobox over the list. Down and Up move through the notes while the focus stays in the field, and Enter selects one. **Walk the links with Alt and the arrow keys**, in the list or in the search field once its keys are on a note. The note the keys are on is marked in the drawing. | Key | What it does | |---|---| | Alt+Right | Follows a link, to the first note the one the keys are on links to or from. | | Alt+Down, Alt+Up | Moves to the next or the previous note linked to the one the step came from, wrapping. | | Alt+Left | Goes back along the links walked. | Right and Left swap on a right-to-left page. Each step says the note, how it stands to the note it came from, and its place among that note's links, such as "Hiring plan. Linked from Roadmap. 2 of 3". Enter selects the note. Set 'legend' to name the groups over the drawing, each a button that shows or hides its notes. 'hiddenGroups' holds the groups turned off, and the list holds only what the drawing shows. The legend is a toolbar and one tab stop: the arrow keys, Home, End and a group's first letters move between its buttons, and Space or Enter presses one. 'nodes' and 'links' are properties, never markup: a graph of ten thousand notes as ten thousand elements would cost what the renderer exists to save. The list draws only the rows in view. The list scrolls on this tier. It carries 'data-scroller', and 'core' ships '[data-scroller] { overflow: auto }' into every shadow root. You still need to set a block size on the list, or it grows to hold every row. */
19520
19553
  "helix-headless-graph": DefineCustomElement<
19521
19554
  HelixHeadlessGraph,
19522
19555
  HelixHeadlessGraphVueProps,