@fusion.dev/helix-headless 0.177.1 → 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/types/vue.d.ts CHANGED
@@ -5540,10 +5540,22 @@ Empty is the reader's own. Dates are plain `YYYY-MM-DD` and carry no zone. */
5540
5540
  /** The message shown when `disable-dates` or `disable-days` bars a date. */
5541
5541
  unavailableMessage?: (HelixHeadlessDateInput["unavailableMessage"]) | undefined;
5542
5542
  /** The message for text that is no date, where `bad-input-message` is not
5543
- set. `{mode}` is `single`, `range`, or `multiple`. */
5543
+ set. `{mode}` is `single`, `range`, or `multiple`, and `{example}` is a
5544
+ date written as the field writes one, `August 24, 2026` in `en-US` and
5545
+ `24. August 2026` in `de-DE` at the default `date-style`.
5546
+
5547
+ **The default names a date the field reads back.** It said "Enter a date
5548
+ as YYYY-MM-DD." to a reader whose field showed `August 4, 2026`, and a
5549
+ pattern token is words for a developer. Time Input says the same. */
5544
5550
  "unreadable-message"?: (HelixHeadlessDateInput["unreadableMessage"]) | undefined;
5545
5551
  /** The message for text that is no date, where `bad-input-message` is not
5546
- set. `{mode}` is `single`, `range`, or `multiple`. */
5552
+ set. `{mode}` is `single`, `range`, or `multiple`, and `{example}` is a
5553
+ date written as the field writes one, `August 24, 2026` in `en-US` and
5554
+ `24. August 2026` in `de-DE` at the default `date-style`.
5555
+
5556
+ **The default names a date the field reads back.** It said "Enter a date
5557
+ as YYYY-MM-DD." to a reader whose field showed `August 4, 2026`, and a
5558
+ pattern token is words for a developer. Time Input says the same. */
5547
5559
  unreadableMessage?: (HelixHeadlessDateInput["unreadableMessage"]) | undefined;
5548
5560
  /** The name of the calendar's heading where it shows a page of years, with
5549
5561
  `{heading}` filled in. */
@@ -5834,16 +5846,24 @@ export type HelixHeadlessDatePickerVueProps = {
5834
5846
  **Every sentence a control raises in the browser's bubble is a property**,
5835
5847
  named for the validity flag it reports, so a form in another language
5836
5848
  speaks it. Unset is the sentence registered for the page's language, then
5837
- the English. `{mode}` is `single`, `range` or `multiple`, so each can have
5838
- its own sentence. */
5849
+ the English. `{example}` is a date written as the calendar names one,
5850
+ `August 24, 2026` in `en-US` and `24. August 2026` in `de-DE`, and `{mode}`
5851
+ is `single`, `range` or `multiple`, so each can have its own sentence.
5852
+
5853
+ **The default names a date in words.** It said "Enter a date as
5854
+ YYYY-MM-DD.", and a pattern token is words for a developer. */
5839
5855
  "bad-input-message"?: (HelixHeadlessDatePicker["badInputMessage"]) | undefined;
5840
5856
  /** The validation message for a value that is not a date.
5841
5857
 
5842
5858
  **Every sentence a control raises in the browser's bubble is a property**,
5843
5859
  named for the validity flag it reports, so a form in another language
5844
5860
  speaks it. Unset is the sentence registered for the page's language, then
5845
- the English. `{mode}` is `single`, `range` or `multiple`, so each can have
5846
- its own sentence. */
5861
+ the English. `{example}` is a date written as the calendar names one,
5862
+ `August 24, 2026` in `en-US` and `24. August 2026` in `de-DE`, and `{mode}`
5863
+ is `single`, `range` or `multiple`, so each can have its own sentence.
5864
+
5865
+ **The default names a date in words.** It said "Enter a date as
5866
+ YYYY-MM-DD.", and a pattern token is words for a developer. */
5847
5867
  badInputMessage?: (HelixHeadlessDatePicker["badInputMessage"]) | undefined;
5848
5868
  /** The calendar the heading, the days, and the months are written in.
5849
5869
 
@@ -7225,6 +7245,14 @@ an icon. */
7225
7245
  /** The name of the clear button, read by screen readers. The button shows only
7226
7246
  an icon. */
7227
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;
7228
7256
  /** Whether the panel narrows itself to the suggestions that hold the term
7229
7257
  being typed, as React Aria's Autocomplete filters. Off, the panel shows
7230
7258
  what you slot and you narrow it. */
@@ -7418,6 +7446,13 @@ imperatively later, and nothing is hydrating by then.
7418
7446
 
7419
7447
  Here rather than on each control. */
7420
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;
7421
7456
  };
7422
7457
 
7423
7458
  export type HelixHeadlessFilterInputVueEvents = {
@@ -7433,7 +7468,7 @@ export type HelixHeadlessFilterInputVueEvents = {
7433
7468
  "helix-dismiss": HelixHeadlessFilterInputHelixDismissElementEvent;
7434
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. */
7435
7470
  "helix-facet": HelixHeadlessFilterInputHelixFacetElementEvent;
7436
- /** 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. */
7437
7472
  "helix-select": HelixHeadlessFilterInputHelixSelectElementEvent;
7438
7473
  /** Fired when the text changes. */
7439
7474
  input: HelixHeadlessFilterInputElementEvent;
@@ -7665,6 +7700,14 @@ or hides its notes. Drawn where the notes have two groups or more. */
7665
7700
  "legend-label"?: (HelixHeadlessGraph["legendLabel"]) | undefined;
7666
7701
  /** The legend's name, read by screen readers. */
7667
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;
7668
7711
  /** How a note linked both to and from the selected one is described, with `{note}`. */
7669
7712
  "linked-both-label"?: (HelixHeadlessGraph["linkedBothLabel"]) | undefined;
7670
7713
  /** How a note linked both to and from the selected one is described, with `{note}`. */
@@ -7744,6 +7787,16 @@ the list then holds, written with `count-label`. */
7744
7787
  "using-label"?: (HelixHeadlessGraph["usingLabel"]) | undefined;
7745
7788
  /** The line shown while the graph, with `gestures="activate"`, has the gestures. */
7746
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;
7747
7800
  /** The hint shown when the wheel scrolls past the graph with
7748
7801
  `gestures="cooperative"`. `{key}` is ⌘ on a Mac and Ctrl elsewhere. */
7749
7802
  "wheel-hint-label"?: (HelixHeadlessGraph["wheelHintLabel"]) | undefined;
@@ -7936,7 +7989,7 @@ export type HelixHeadlessGraphVueEvents = {
7936
7989
  "helix-graph-layout": HelixHeadlessGraphHelixGraphLayoutElementEvent;
7937
7990
  /** A pan button or arrow key was pressed. `connectGraph` moves the drawing, then calls `moved()`. */
7938
7991
  "helix-graph-pan": HelixHeadlessGraphHelixGraphPanElementEvent;
7939
- /** 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. */
7940
7993
  "helix-graph-part-change": HelixHeadlessGraphHelixGraphPartChangeElementEvent;
7941
7994
  /** The graph has been drawn whole, laid out and settled. Sent each time `drawn()` says it is drawn after it was not. */
7942
7995
  "helix-graph-ready": HelixHeadlessGraphHelixGraphReadyElementEvent;
@@ -16581,18 +16634,28 @@ export type HelixHeadlessTimePickerVueProps = {
16581
16634
  **Every sentence a control raises in the browser's bubble is a property**,
16582
16635
  named for the validity flag it reports, so a form in another language
16583
16636
  speaks it. Unset is the sentence registered for the page's language, then
16584
- the English. `{shape}` is how a time is written, `HH:mm` or `HH:mm:ss`, and
16585
- `{mode}` is `single`, `range` or `multiple`, so each can have its own
16586
- sentence. */
16637
+ the English. `{example}` is a time written as the picker writes one,
16638
+ `2:30 PM` in `en-US` and `14:30` in `de-DE`, `{shape}` is `HH:mm` or
16639
+ `HH:mm:ss`, and `{mode}` is `single`, `range` or `multiple`, so each can
16640
+ have its own sentence.
16641
+
16642
+ **The default names a time as the picker shows one.** It said "Enter a
16643
+ time as HH:mm." to a reader whose list showed `2:30 PM`, and a pattern
16644
+ token is words for a developer. Time Input says the same. */
16587
16645
  "bad-input-message"?: (HelixHeadlessTimePicker["badInputMessage"]) | undefined;
16588
16646
  /** The validation message for a value that is not a time.
16589
16647
 
16590
16648
  **Every sentence a control raises in the browser's bubble is a property**,
16591
16649
  named for the validity flag it reports, so a form in another language
16592
16650
  speaks it. Unset is the sentence registered for the page's language, then
16593
- the English. `{shape}` is how a time is written, `HH:mm` or `HH:mm:ss`, and
16594
- `{mode}` is `single`, `range` or `multiple`, so each can have its own
16595
- sentence. */
16651
+ the English. `{example}` is a time written as the picker writes one,
16652
+ `2:30 PM` in `en-US` and `14:30` in `de-DE`, `{shape}` is `HH:mm` or
16653
+ `HH:mm:ss`, and `{mode}` is `single`, `range` or `multiple`, so each can
16654
+ have its own sentence.
16655
+
16656
+ **The default names a time as the picker shows one.** It said "Enter a
16657
+ time as HH:mm." to a reader whose list showed `2:30 PM`, and a pattern
16658
+ token is words for a developer. Time Input says the same. */
16596
16659
  badInputMessage?: (HelixHeadlessTimePicker["badInputMessage"]) | undefined;
16597
16660
  /** Times that cannot be chosen, as `HH:mm`, separated by commas or spaces.
16598
16661
 
@@ -19474,7 +19537,7 @@ export interface WcToolkitVueGlobalComponents {
19474
19537
  HelixHeadlessFilterOperatorVueProps,
19475
19538
  HelixHeadlessFilterOperatorVueEvents
19476
19539
  >;
19477
- /** 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. */
19478
19541
  "helix-headless-filter-input": DefineCustomElement<
19479
19542
  HelixHeadlessFilterInput,
19480
19543
  HelixHeadlessFilterInputVueProps,
@@ -19486,7 +19549,7 @@ export interface WcToolkitVueGlobalComponents {
19486
19549
  HelixHeadlessFormatDateVueProps,
19487
19550
  HelixHeadlessFormatDateVueEvents
19488
19551
  >;
19489
- /** 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. */
19490
19553
  "helix-headless-graph": DefineCustomElement<
19491
19554
  HelixHeadlessGraph,
19492
19555
  HelixHeadlessGraphVueProps,
package/types/words.d.ts CHANGED
@@ -1276,9 +1276,11 @@ declare module '@fusion.dev/helix-core' {
1276
1276
  unavailableMessage: Message
1277
1277
  /**
1278
1278
  * The message for text that is no date, where `bad-input-message` is not
1279
- * set. `{mode}` is `single`, `range`, or `multiple`.
1279
+ * set. `{mode}` is `single`, `range`, or `multiple`, and `{example}` is a
1280
+ * date written as the field writes one, `August 24, 2026` in `en-US` and
1281
+ * `24. August 2026` in `de-DE` at the default `date-style`.
1280
1282
  *
1281
- * English: `{mode, select, single {Enter a date as YYYY-MM-DD.} other {Enter dates as YYYY-MM-DD, separated by commas.}}`
1283
+ * English: `{mode, select, single {Enter a date such as {example}.} other {Enter dates such as {example}, separated by commas.}}`
1282
1284
  */
1283
1285
  unreadableMessage: Message
1284
1286
  /**
@@ -1293,7 +1295,7 @@ declare module '@fusion.dev/helix-core' {
1293
1295
  /**
1294
1296
  * The validation message for a value that is not a date.
1295
1297
  *
1296
- * English: `{mode, select, single {Enter a date as YYYY-MM-DD.} other {Enter dates as YYYY-MM-DD, separated by commas.}}`
1298
+ * English: `{mode, select, single {Enter a date such as {example}.} other {Enter dates such as {example}, separated by commas.}}`
1297
1299
  */
1298
1300
  badInputMessage: Message
1299
1301
  /**
@@ -1637,6 +1639,14 @@ declare module '@fusion.dev/helix-core' {
1637
1639
  * English: `Groups`
1638
1640
  */
1639
1641
  legendLabel: Message
1642
+ /**
1643
+ * What screen readers hear when a walk along the links reaches a note that
1644
+ * has no link to follow, or goes back to a note: `{note}` for its name and
1645
+ * `{count}` for how many notes the list holds that it links to or from.
1646
+ *
1647
+ * English: `{note}. {count, plural, =0 {No links} one {# link} other {# links}}`
1648
+ */
1649
+ linkCountLabel: Message
1640
1650
  /**
1641
1651
  * How a note linked both to and from the selected one is described, with `{note}`.
1642
1652
  *
@@ -1755,6 +1765,15 @@ declare module '@fusion.dev/helix-core' {
1755
1765
  * English: `Using the graph. Press Escape to scroll the page again.`
1756
1766
  */
1757
1767
  usingLabel: Message
1768
+ /**
1769
+ * What screen readers hear when a walk along the links moves to a note:
1770
+ * `{note}` for its name, `{relation}` for how it stands to the note the
1771
+ * step came from, such as "Linked from Roadmap", and `{index}` and `{count}`
1772
+ * for its place among that note's links.
1773
+ *
1774
+ * English: `{note}. {relation}. {index} of {count}`
1775
+ */
1776
+ walkLabel: Message
1758
1777
  /**
1759
1778
  * The hint shown when the wheel scrolls past the graph with
1760
1779
  * `gestures="cooperative"`. `{key}` is ⌘ on a Mac and Ctrl elsewhere.
@@ -3323,7 +3342,7 @@ declare module '@fusion.dev/helix-core' {
3323
3342
  /**
3324
3343
  * The validation message for a value that is not a time.
3325
3344
  *
3326
- * English: `{mode, select, single {Enter a time as {shape}.} other {Enter times as {shape}, separated by commas.}}`
3345
+ * English: `{mode, select, single {Enter a time such as {example}.} other {Enter times such as {example}, separated by commas.}}`
3327
3346
  */
3328
3347
  badInputMessage: Message
3329
3348
  /**