@fusion.dev/helix-headless 0.209.0 → 0.210.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.
@@ -50,6 +50,9 @@ declare const HelixHeadlessCanvas_base: (new (...args: any[]) => import("@fusion
50
50
  * board, where the arrow keys pan it.
51
51
  * - Plus and minus zoom the board, on the board and on a card or a
52
52
  * connection.
53
+ * - The pan and zoom buttons are a toolbar and one tab stop. The arrow keys
54
+ * move between them by where they sit, Right from the up arrow to zoom in,
55
+ * and Enter or Space presses one.
53
56
  * - The arrow keys move through the cards in reading order. The card you land
54
57
  * on scrolls into view, and its connections are announced with it.
55
58
  * - The card you land on is selected. Control or Command with an arrow key,
@@ -162,7 +165,7 @@ declare const HelixHeadlessCanvas_base: (new (...args: any[]) => import("@fusion
162
165
  *
163
166
  * @csspart arrow - The arrowhead at an end of a connection that has one.
164
167
  * @csspart board - The plane the cards are placed on, which pans and zooms.
165
- * @csspart controls - The group the pan and zoom buttons sit in.
168
+ * @csspart controls - The toolbar the pan and zoom buttons sit in, one tab stop.
166
169
  * @csspart draft - The connection being drawn, before `helix-connect` is sent.
167
170
  * @csspart edge - One connector between two cards.
168
171
  * @csspart edge-end - An end of a selected connection. Drag it, or click it to pick it up.
@@ -1,4 +1,4 @@
1
- import { type GraphSignalDetail, HelixElement, KeyboardNavigationController, type ViewportPanDetail, type ViewportZoomDetail } from '@fusion.dev/helix-core';
1
+ import { type GraphSignalDetail, HelixElement, ToolbarController, type ViewportPanDetail, type ViewportZoomDetail } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  import { type GraphLink, type GraphNode } from './graph-model.js';
4
4
  declare const HelixHeadlessGraph_base: (new (...args: any[]) => import("@fusion.dev/helix-core").ViewportElement) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerActiveInterface) & typeof HelixElement;
@@ -43,7 +43,9 @@ declare const HelixHeadlessGraph_base: (new (...args: any[]) => import("@fusion.
43
43
  * or hides its notes. `hiddenGroups` holds the groups turned off, and the
44
44
  * list holds only what the drawing shows. The legend is a toolbar and one tab
45
45
  * stop: the arrow keys, Home, End and a group's first letters move between
46
- * its buttons, and Space or Enter presses one.
46
+ * its buttons, and Space or Enter presses one. The pan and zoom buttons are
47
+ * a toolbar and one tab stop too, the arrow keys moving between them by where
48
+ * they sit on screen.
47
49
  *
48
50
  * `nodes` and `links` are properties, never markup: a graph of ten thousand
49
51
  * notes as ten thousand elements would cost what the renderer exists to save.
@@ -86,7 +88,7 @@ declare const HelixHeadlessGraph_base: (new (...args: any[]) => import("@fusion.
86
88
  * @csspart after - The room the notes after those drawn would take, so the list scrolls its whole length.
87
89
  * @csspart before - The room the notes before those drawn would take.
88
90
  * @csspart body - The drawing beside the list, or above it where the frame is narrow.
89
- * @csspart controls - The group the pan and zoom buttons sit in.
91
+ * @csspart controls - The toolbar the pan and zoom buttons sit in, one tab stop.
90
92
  * @csspart count - How many notes the list holds, which a search narrows, at the end of the search bar.
91
93
  * @csspart empty - The box in place of the list when it holds no notes, holding the `empty` slot.
92
94
  * @csspart filter-icon - The icon beside the search field.
@@ -343,15 +345,14 @@ export declare class HelixHeadlessGraph extends HelixHeadlessGraph_base {
343
345
  * and wrote `active` itself.
344
346
  */
345
347
  private selectionAsked;
346
- /** The legend's button that holds its one tab stop, by place: the one a reader was last on, or the first. */
347
- private legendStop;
348
348
  /**
349
- * The arrow keys, Home, End and typed letters among the legend's buttons,
350
- * so a legend of twelve groups is one tab stop, as the `Legend` family's
351
- * toolbar is. Only a press that starts on a legend button moves: the frame's
352
- * own arrows pan the drawing, and the list's walk the notes.
349
+ * The legend's buttons as one tab stop: the arrow keys, Home, End and typed
350
+ * letters move among them, so a legend of twelve groups is one tab stop, as
351
+ * the `Legend` family's toolbar is. Only a press that starts on a legend
352
+ * button moves: the frame's own arrows pan the drawing, and the list's walk
353
+ * the notes. Both axes, the buttons wrapping onto more lines.
353
354
  */
354
- protected readonly legendKeys: KeyboardNavigationController;
355
+ protected readonly legendKeys: ToolbarController;
355
356
  private readonly virtualize;
356
357
  /**
357
358
  * @internal The notes the drawing shows, which `connectGraph` reads: those
@@ -1,5 +1,5 @@
1
1
  import { __decorate } from "tslib";
2
- import { auditName, customElement, focusItemOf, formatWords, HelixElement, HelixGraphFilterEvent, HelixGraphLayoutEvent, HelixGraphPanEvent, HelixGraphPartChangeEvent, HelixGraphReadyEvent, HelixGraphSignalEvent, HelixGraphZoomEvent, isRtl, KeyboardNavigationController, MixinControllerActive, MixinViewport, numberConverter, SCROLLER_RULE, scrollRoom, scrollWithin, Typeahead, typeaheadBy, VIRTUAL_RULE, VirtualizeController, words, wordsLocale, } from '@fusion.dev/helix-core';
2
+ import { auditName, customElement, formatWords, HelixElement, HelixGraphFilterEvent, HelixGraphLayoutEvent, HelixGraphPanEvent, HelixGraphPartChangeEvent, HelixGraphReadyEvent, HelixGraphSignalEvent, HelixGraphZoomEvent, isRtl, MixinControllerActive, MixinViewport, numberConverter, SCROLLER_RULE, scrollRoom, scrollWithin, ToolbarController, Typeahead, VIRTUAL_RULE, VirtualizeController, words, wordsLocale, } from '@fusion.dev/helix-core';
3
3
  import { list, magnifyingGlass } from '@fusion.dev/helix-icons';
4
4
  import { html, nothing } from 'lit';
5
5
  import { property, state } from 'lit/decorators.js';
@@ -59,14 +59,12 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
59
59
  this.whole = false;
60
60
  this.said = '';
61
61
  this.countAsked = false;
62
- this.legendStop = 0;
63
- this.legendKeys = new KeyboardNavigationController(this, {
64
- items: () => [
65
- ...(this.renderRoot?.querySelectorAll?.('[part~="legend-item"]') ?? []),
66
- ].map((button) => focusItemOf(button, { words: button.textContent?.trim() ?? '' })),
67
- origin: 'focus',
68
- selects: () => false,
69
- typeahead: typeaheadBy((item) => item.words ?? ''),
62
+ this.legendKeys = new ToolbarController(this, {
63
+ arrows: () => 'both',
64
+ members: () => [...(this.partNamed('legend')?.children ?? [])],
65
+ own: true,
66
+ watch: () => this.renderRoot,
67
+ words: (button) => button.textContent?.trim() ?? '',
70
68
  });
71
69
  this.virtualize = new VirtualizeController(this, {
72
70
  container: () => this.partNamed('list'),
@@ -373,20 +371,15 @@ let HelixHeadlessGraph = class HelixHeadlessGraph extends MixinViewport(MixinCon
373
371
  return nothing;
374
372
  }
375
373
  const hidden = new Set(this.hiddenGroups);
376
- const stop = Math.min(this.legendStop, this.groups.length - 1);
377
374
  return html `<div part="legend" role="toolbar" aria-label=${this.legendLabel}>
378
- ${this.groups.map(({ name, tone }, index) => {
375
+ ${this.groups.map(({ name, tone }) => {
379
376
  const shown = !hidden.has(name);
380
377
  return html `<button
381
378
  part="legend-item${shown ? '' : ' legend-item-hidden'}"
382
379
  type="button"
383
- tabindex=${index === stop ? '0' : '-1'}
384
380
  aria-pressed=${shown ? 'true' : 'false'}
385
381
  data-tone=${tone ?? nothing}
386
382
  @click=${() => this.toggleGroup(name)}
387
- @focus=${() => {
388
- this.legendStop = index;
389
- }}
390
383
  >
391
384
  <span part="legend-swatch" aria-hidden="true"></span>${name}
392
385
  </button>`;
@@ -882,9 +875,6 @@ __decorate([
882
875
  __decorate([
883
876
  state()
884
877
  ], HelixHeadlessGraph.prototype, "said", void 0);
885
- __decorate([
886
- state()
887
- ], HelixHeadlessGraph.prototype, "legendStop", void 0);
888
878
  HelixHeadlessGraph = __decorate([
889
879
  customElement('helix-headless-graph')
890
880
  ], HelixHeadlessGraph);
@@ -1,4 +1,4 @@
1
- import { HelixElement, KeyboardNavigationController, SourceController } from '@fusion.dev/helix-core';
1
+ import { HelixElement, SourceController, ToolbarController } from '@fusion.dev/helix-core';
2
2
  import { nothing, type PropertyValues, type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary A list of the series in a chart, each name beside the color it is drawn in.
@@ -108,17 +108,12 @@ export declare class HelixHeadlessLegend extends HelixElement {
108
108
  toggles: boolean;
109
109
  protected readonly source: SourceController;
110
110
  /**
111
- * The toggle that holds the one tab stop, by place: the one a reader was
112
- * last on, or the first.
111
+ * The toggles as one tab stop: the arrow keys, Home, End and typed letters
112
+ * move among them, so a legend of twelve series is one tab stop, as a
113
+ * toolbar of toggle buttons is. Both axes, the entries wrapping onto more
114
+ * lines. Nothing without `toggles`, where there is nothing to press.
113
115
  */
114
- private stop;
115
- /**
116
- * The arrow keys, Home, End and typed letters among the toggles, so a
117
- * legend of twelve series is one tab stop, as a toolbar of
118
- * toggle buttons is. Nothing without `toggles`, where there is nothing to
119
- * press.
120
- */
121
- protected readonly keys: KeyboardNavigationController;
116
+ protected readonly keys: ToolbarController;
122
117
  /** The table this legend last wrote its `hidden-series` to, so leaving undoes it. */
123
118
  private wroteTo;
124
119
  /** Whether this update writes `hidden-series` to the table, decided before the render. */
@@ -198,11 +193,10 @@ export declare class HelixHeadlessLegend extends HelixElement {
198
193
  * The button around a name under `toggles`, behind a seam a tier can move.
199
194
  *
200
195
  * Pressed while its series is shown, which is the state a reader changes by
201
- * pressing it: a toggle button says whether its thing is on. `holds` says
202
- * whether it is the one tab stop, which is the last toggle a reader was on,
203
- * the toggle at `index`.
196
+ * pressing it: a toggle button says whether its thing is on. Which toggle
197
+ * holds the one tab stop is `keys`'s to say, so the button names none.
204
198
  */
205
- protected renderToggle(name: string, off: boolean, index?: number, holds?: boolean): TemplateResult;
199
+ protected renderToggle(name: string, off: boolean, index?: number): TemplateResult;
206
200
  }
207
201
  declare global {
208
202
  interface HTMLElementTagNameMap {
@@ -1,7 +1,7 @@
1
1
  import { __decorate } from "tslib";
2
- import { customElement, focusItemOf, HelixElement, KeyboardNavigationController, requestValue, SourceController, seriesIndex, seriesKey, typeaheadBy, } from '@fusion.dev/helix-core';
2
+ import { customElement, HelixElement, requestValue, SourceController, seriesIndex, seriesKey, ToolbarController, } from '@fusion.dev/helix-core';
3
3
  import { html, nothing } from 'lit';
4
- import { property, state } from 'lit/decorators.js';
4
+ import { property } from 'lit/decorators.js';
5
5
  import { isHidden, namesIn, toggledNames } from '../hidden-series.js';
6
6
  let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
7
7
  constructor() {
@@ -11,14 +11,12 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
11
11
  this.label = '';
12
12
  this.toggles = false;
13
13
  this.source = new SourceController(this);
14
- this.stop = 0;
15
- this.keys = new KeyboardNavigationController(this, {
16
- items: () => this.toggles
17
- ? [...this.renderRoot.querySelectorAll('[part="toggle"]')].map((button) => focusItemOf(button, { words: button.textContent?.trim() ?? '' }))
18
- : [],
19
- origin: 'focus',
20
- selects: () => false,
21
- typeahead: typeaheadBy((item) => item.words ?? ''),
14
+ this.keys = new ToolbarController(this, {
15
+ arrows: () => 'both',
16
+ members: () => (this.toggles ? [...(this.partNamed('list')?.children ?? [])] : []),
17
+ own: true,
18
+ watch: () => this.renderRoot,
19
+ words: (button) => button.textContent?.trim() ?? '',
22
20
  });
23
21
  this.wroteTo = null;
24
22
  this.writes = false;
@@ -75,7 +73,6 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
75
73
  render() {
76
74
  const named = this.label || this.source.element?.querySelector('caption')?.textContent?.trim();
77
75
  const names = this.names();
78
- const stop = Math.min(this.stop, names.length - 1);
79
76
  const toolbar = this.toggles;
80
77
  return html `<ul
81
78
  aria-label=${named || nothing}
@@ -90,7 +87,7 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
90
87
  style="--chart-series: var(--chart-series-${seriesIndex(index)})"
91
88
  role=${toolbar ? 'none' : 'listitem'}
92
89
  >
93
- ${this.renderSwatch(name, index)}${toolbar ? this.renderToggle(name, off, index, index === stop) : this.renderName(name, index)}
90
+ ${this.renderSwatch(name, index)}${toolbar ? this.renderToggle(name, off, index) : this.renderName(name, index)}
94
91
  </li>`;
95
92
  })}
96
93
  </ul>`;
@@ -101,16 +98,12 @@ let HelixHeadlessLegend = class HelixHeadlessLegend extends HelixElement {
101
98
  renderName(name, _index) {
102
99
  return html `<span part="name">${name}</span>`;
103
100
  }
104
- renderToggle(name, off, index = 0, holds = true) {
101
+ renderToggle(name, off, index = 0) {
105
102
  return html `<button
106
103
  aria-pressed=${off ? 'false' : 'true'}
107
104
  part="toggle"
108
- tabindex=${holds ? '0' : '-1'}
109
105
  type="button"
110
106
  @click=${() => this.toggle(name)}
111
- @focus=${() => {
112
- this.stop = index;
113
- }}
114
107
  >
115
108
  ${this.renderName(name, index)}
116
109
  </button>`;
@@ -131,9 +124,6 @@ __decorate([
131
124
  __decorate([
132
125
  property({ reflect: true, type: Boolean })
133
126
  ], HelixHeadlessLegend.prototype, "toggles", void 0);
134
- __decorate([
135
- state()
136
- ], HelixHeadlessLegend.prototype, "stop", void 0);
137
127
  HelixHeadlessLegend = __decorate([
138
128
  customElement('helix-headless-legend')
139
129
  ], HelixHeadlessLegend);
@@ -27,7 +27,9 @@ declare const HelixHeadlessMap_base: (new (...args: any[]) => import("@fusion.de
27
27
  * map. Dragging is otherwise the only way a pointer moves the map, so add them
28
28
  * where the map has to be usable without dragging, as WCAG 2.2 asks at level
29
29
  * AA. The arrow keys pan the map and Shift with Left or Right turns it either
30
- * way.
30
+ * way. The buttons are a toolbar and one tab stop: on them, the arrow keys
31
+ * move between the buttons by where they sit, and Shift with an arrow still
32
+ * turns or tilts the map.
31
33
  *
32
34
  * The pan and zoom buttons and the arrow keys send `helix-map-pan` and
33
35
  * `helix-map-zoom`. Move your map in response, or connect it with
@@ -98,7 +100,7 @@ declare const HelixHeadlessMap_base: (new (...args: any[]) => import("@fusion.de
98
100
  *
99
101
  * @csspart attribution - The strip under the map that holds the credit.
100
102
  * @csspart compass - The compass and the pan and rotate buttons around it.
101
- * @csspart controls - The column of buttons that move the map.
103
+ * @csspart controls - The column of buttons that move the map, a toolbar and one tab stop.
102
104
  * @csspart face-north - The compass: its needle points north as the map turns, and pressing it turns the map to face north.
103
105
  * @csspart footer - The strip below the map.
104
106
  * @csspart fullscreen - The button that makes the map fill the screen.
@@ -525,7 +525,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
525
525
  if (!pan && !zoom && !rotate && !tilt && !fullscreen && menu === nothing) {
526
526
  return nothing;
527
527
  }
528
- return html `<div part="controls" role="group" aria-label=${this.controlsLabel}>
528
+ return html `<div part="controls" role="toolbar" aria-label=${this.controlsLabel}>
529
529
  ${fullscreen
530
530
  ? html `<button
531
531
  part="fullscreen"
@@ -1,4 +1,4 @@
1
- import { AriaForwardController, HelixElement } from '@fusion.dev/helix-core';
1
+ import { AriaForwardController, HelixElement, ToolbarController } from '@fusion.dev/helix-core';
2
2
  import { type PropertyValues, type TemplateResult } from 'lit';
3
3
  /**
4
4
  * @summary A group of controls that share one tab stop.
@@ -94,13 +94,6 @@ export declare class HelixHeadlessToolbar extends HelixElement {
94
94
  label: string;
95
95
  /** Carries a host `aria-label` and `aria-labelledby` to the toolbar. */
96
96
  protected readonly aria: AriaForwardController;
97
- /**
98
- * Whether the stop has been written onto controls this package does not
99
- * know, which happens on the first focus. From then on, every move of the
100
- * stop has to write them too, or the one at `0` can be removed or disabled
101
- * and leave every other one at `-1`.
102
- */
103
- private wroteForeign;
104
97
  /**
105
98
  * The direction the arrow keys move focus in.
106
99
  */
@@ -110,77 +103,12 @@ export declare class HelixHeadlessToolbar extends HelixElement {
110
103
  * wrap around from one end to the other.
111
104
  */
112
105
  stopAtEnds: boolean;
113
- private watcher;
114
- /**
115
- * The children that take part in the toolbar, in document order, each with
116
- * the elements in it that take focus.
117
- *
118
- * A test on the element rather than a list of tag names: a toolbar holds
119
- * anything, including elements this package has never heard of. A child
120
- * that takes no focus and holds nothing that does is skipped. Client only,
121
- * there being no tree to walk on the server.
122
- */
123
- private get controls();
124
- /**
125
- * The toolbar's children, a slot among them read as the elements assigned
126
- * to it.
127
- *
128
- * **For a toolbar a component renders in its own shadow root** and passes a
129
- * page's controls into through a slot, as Terminal's header does with its
130
- * `actions`: read as a child, the slot holds only its fallback, and the
131
- * page's controls kept tab stops of their own beside the toolbar's one.
132
- */
133
- private get members();
134
- /** The elements in a child's light DOM that take focus. */
135
- private within;
136
- /**
137
- * The stops of a child that takes focus itself: the child, or the members of
138
- * a composite that owns its tab stop, such as a toggle group, which the
139
- * toolbar enters at a member and moves the stop of through `focusable`.
140
- */
141
- private inside;
142
- /**
143
- * Where focus lands on arriving at a control. A nested composite is entered
144
- * at the stop it holds, which for a toggle group is the chosen toggle.
145
- */
146
- private entry;
147
- /** The control a press or a focus came from, by its path. */
148
- private origin;
149
106
  /**
150
- * Moves the single tab stop to one control.
151
- *
152
- * Prefers the control's own `focusable` property to writing `tabindex` on it.
153
- * The attribute is a change to markup the consumer rendered, which a React
154
- * consumer's hydration reports and does not patch up; a control that offers
155
- * the property moves the stop inside its own shadow root instead.
156
- *
157
- * `tabindex` stays as the fallback, because a toolbar has to hold controls
158
- * this package has never heard of.
159
- */
160
- private setTabStop;
161
- /**
162
- * Sets the first tab stop once every custom control has upgraded.
163
- *
164
- * A custom element has no properties at all before it upgrades, so a pass
165
- * that ran first would find no `focusable`, take the `tabindex` fallback, and
166
- * write the very attribute this exists to avoid.
167
- */
168
- private claimTabStop;
169
- private readonly handleFocusIn;
170
- /**
171
- * Gives the tab stop back to a control that still exists.
172
- *
173
- * `controls` excludes anything disabled or hidden, so disabling the control
174
- * holding the stop drops it out of the list while every other control is
175
- * already at `-1`, leaving a toolbar the keyboard cannot reach at all. A Save
176
- * button disabling itself once the form is clean is enough to do it.
177
- *
178
- * `foreign` stays false, the same as the first claim, until a focus has
179
- * written `tabindex="-1"` onto foreign controls. From then on `setTabStop`
180
- * writes them as well, so removing or disabling the native button that held
181
- * the stop hands it to the first one left.
107
+ * The one tab stop and the arrow keys, shared with every toolbar a
108
+ * component draws: the controls are the children, a slot among them read as
109
+ * the elements assigned to it.
182
110
  */
183
- private readonly restoreTabStop;
111
+ protected readonly tabStop: ToolbarController;
184
112
  /**
185
113
  * Whether this toolbar sits inside another, which then owns the keys and the
186
114
  * tab stop: a toolbar inside a toolbar held the reader's arrows inside it.
@@ -195,18 +123,7 @@ export declare class HelixHeadlessToolbar extends HelixElement {
195
123
  * so a reflected default cannot appear on the host after hydration.
196
124
  */
197
125
  private get axis();
198
- /**
199
- * The toolbar's own keys, from inside a nested composite.
200
- *
201
- * In capture, ahead of the composite, which answers every arrow itself and
202
- * would otherwise hold the reader inside it. Only the keys along the
203
- * toolbar's axis and Home and End are taken, so the arrows across it still
204
- * move inside the composite.
205
- */
206
- private readonly handleCompositeKeydown;
207
- private readonly handleKeydown;
208
126
  connectedCallback(): void;
209
- disconnectedCallback(): void;
210
127
  protected firstUpdated(changed: PropertyValues): void;
211
128
  protected willUpdate(changed: PropertyValues): void;
212
129
  protected render(): TemplateResult;