@fusion.dev/helix-headless 0.109.3 → 0.110.1

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.
@@ -142,6 +142,16 @@ export declare class HelixHeadlessButton extends HelixFormElement {
142
142
  * "button" and nothing else.
143
143
  */
144
144
  label: string;
145
+ /**
146
+ * What kind of panel this button opens, where it opens one: `menu` for a
147
+ * `Menu`.
148
+ *
149
+ * Set by the component that owns the panel, as a property for the reason
150
+ * `expanded` gives above. It is what makes a reader say the button opens a
151
+ * menu. Read from Chromium's tree, a Menu's trigger was a plain button that
152
+ * reported collapsed and expanded and no popup.
153
+ */
154
+ popup?: 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree';
145
155
  /** Moves focus to the inner control, so the host is never the focus target. */
146
156
  static shadowRootOptions: ShadowRootInit;
147
157
  private readonly handleClick;
@@ -123,6 +123,7 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
123
123
  part="button"
124
124
  type="button"
125
125
  aria-expanded=${this.expanded === undefined ? nothing : String(this.expanded)}
126
+ aria-haspopup=${this.popup ?? nothing}
126
127
  aria-label=${this.label || nothing}
127
128
  command=${this.command || nothing}
128
129
  tabindex=${stop}
@@ -163,6 +164,9 @@ __decorate([
163
164
  __decorate([
164
165
  property()
165
166
  ], HelixHeadlessButton.prototype, "label", void 0);
167
+ __decorate([
168
+ property({ attribute: false })
169
+ ], HelixHeadlessButton.prototype, "popup", void 0);
166
170
  __decorate([
167
171
  property()
168
172
  ], HelixHeadlessButton.prototype, "type", void 0);
@@ -175,6 +175,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
175
175
  hint: channel-hint, label: channel-label, marker: channel-marker,
176
176
  marker-label: channel-marker-label, markers: channel-markers, output: channel-output"
177
177
  data-channel=${channel}
178
+ aria-invalid=${this.invalid ? 'true' : nothing}
178
179
  label=${label}
179
180
  max=${max}
180
181
  min="0"
@@ -254,6 +255,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
254
255
  min="0"
255
256
  max="100"
256
257
  aria-label=${this.saturationLabel}
258
+ aria-invalid=${this.invalid ? 'true' : nothing}
257
259
  .value=${String(color.saturation)}
258
260
  ?disabled=${this.isDisabled}
259
261
  @input=${(event) => this.takeArea('saturation', event)}
@@ -264,6 +266,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
264
266
  min="0"
265
267
  max="100"
266
268
  aria-label=${this.brightnessLabel}
269
+ aria-invalid=${this.invalid ? 'true' : nothing}
267
270
  .value=${String(color.brightness)}
268
271
  ?disabled=${this.isDisabled}
269
272
  @input=${(event) => this.takeArea('brightness', event)}
@@ -298,6 +301,7 @@ let HelixHeadlessColorPicker = class HelixHeadlessColorPicker extends MixinContr
298
301
  part="value"
299
302
  type="text"
300
303
  aria-label=${this.valueLabel}
304
+ aria-invalid=${this.invalid ? 'true' : nothing}
301
305
  autocomplete=${this.autocomplete || nothing}
302
306
  spellcheck="false"
303
307
  .value=${this.value}
@@ -127,6 +127,8 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
127
127
  * same reason.
128
128
  */
129
129
  static shadowRootOptions: ShadowRootInit;
130
+ /** The word for an ascending sort, in `sorted-label`. */
131
+ ascendingLabel: string;
130
132
  /**
131
133
  * The starting search query, used until `filter` is set.
132
134
  */
@@ -152,6 +154,8 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
152
154
  * The starting sort order, used until `sort` is set.
153
155
  */
154
156
  defaultSort: string;
157
+ /** The word for a descending sort, in `sorted-label`. */
158
+ descendingLabel: string;
155
159
  /**
156
160
  * The estimated height of a row before it is drawn, in pixels.
157
161
  *
@@ -280,6 +284,11 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
280
284
  */
281
285
  get selected(): string;
282
286
  set selected(next: string);
287
+ /**
288
+ * What screen readers hear when the chosen rows change. `{count}` is how many
289
+ * rows are chosen.
290
+ */
291
+ selectedLabel: string;
283
292
  /**
284
293
  * The sort order, as `field:direction`.
285
294
  *
@@ -289,6 +298,11 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
289
298
  */
290
299
  get sort(): string;
291
300
  set sort(next: string);
301
+ /**
302
+ * What screen readers hear when the table is sorted. `{column}` is the
303
+ * column's heading and `{order}` is `ascending-label` or `descending-label`.
304
+ */
305
+ sortedLabel: string;
292
306
  /**
293
307
  * The number of rows in the whole result, for when the table cannot count
294
308
  * them.
@@ -299,6 +313,8 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
299
313
  * one that cannot be stale.
300
314
  */
301
315
  total: number;
316
+ /** What screen readers hear when the table stops being sorted. */
317
+ unsortedLabel: string;
302
318
  /**
303
319
  * Draws only the rows in view, with empty space in place of the rest.
304
320
  *
@@ -582,6 +598,22 @@ export declare class HelixHeadlessDataTable extends HelixHeadlessDataTable_base
582
598
  * Manual assignment, so a consumer's own markup is never written to.
583
599
  */
584
600
  private assign;
601
+ /** The sort last heard, so a render that changed nothing says nothing. */
602
+ private heardSort;
603
+ /** The selection last heard, for the same reason. */
604
+ private heardSelected;
605
+ /**
606
+ * What the table says after a sort or a change to the chosen rows.
607
+ *
608
+ * **The state changed and nothing said so.** A sort moved `aria-sort` on the
609
+ * heading, and a reader on the heading's button is not told its column's
610
+ * state changed; a chosen row ticked one box, and nothing gave the count. So
611
+ * each is said in the collection's live region, once, after it has happened.
612
+ * Whoever set it, a reader or a consumer answering `helix-sort`, since either
613
+ * way the rows under the reader have moved. Not on the first render, which
614
+ * is the table arriving rather than anything changing.
615
+ */
616
+ private news;
585
617
  protected willUpdate(changed: PropertyValues): void;
586
618
  /**
587
619
  * Says on the toolbar and the footer whether anything is in them.
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  var HelixHeadlessDataTable_1;
8
- import { customElement, HelixElement, HelixFilterEvent, HelixNavigateEvent, HelixReorderEvent, HelixSelectEvent, HelixSortEvent, hostAria, inlineStep, isRtl, MixinCollection, markRegion, requestChange, VirtualizeController, writtenList, } from '@fusion.dev/helix-core';
8
+ import { customElement, fillTemplate, formatNumbers, HelixElement, HelixFilterEvent, HelixNavigateEvent, HelixReorderEvent, HelixSelectEvent, HelixSortEvent, hostAria, inlineStep, isRtl, MixinCollection, markRegion, requestChange, VirtualizeController, writtenList, } from '@fusion.dev/helix-core';
9
9
  import { nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
11
11
  import { repeat } from 'lit/directives/repeat.js';
@@ -17,12 +17,14 @@ import { arranged, cycle, format, ordered, parse, window_, } from './order.js';
17
17
  let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollection(HelixElement) {
18
18
  constructor() {
19
19
  super(...arguments);
20
+ this.ascendingLabel = 'ascending';
20
21
  this.defaultFilter = '';
21
22
  this.defaultOrder = '';
22
23
  this.defaultPage = 1;
23
24
  this.defaultPageSize = 0;
24
25
  this.defaultSelected = '';
25
26
  this.defaultSort = '';
27
+ this.descendingLabel = 'descending';
26
28
  this.estimate = 32;
27
29
  this.label = '';
28
30
  this.mode = 'client';
@@ -30,7 +32,10 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
30
32
  this.rowKey = '';
31
33
  this.rows = [];
32
34
  this.selectable = false;
35
+ this.selectedLabel = '{count} selected';
36
+ this.sortedLabel = 'Sorted by {column}, {order}';
33
37
  this.total = 0;
38
+ this.unsortedLabel = 'Not sorted';
34
39
  this.windowed = false;
35
40
  this.sortState = undefined;
36
41
  this.filterState = undefined;
@@ -76,6 +81,8 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
76
81
  }
77
82
  this.requestSelected(keys);
78
83
  };
84
+ this.heardSort = undefined;
85
+ this.heardSelected = undefined;
79
86
  this.handleSlotChange = () => {
80
87
  this.requestUpdate();
81
88
  };
@@ -508,8 +515,34 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
508
515
  }
509
516
  }
510
517
  }
518
+ news() {
519
+ const sort = this.sort;
520
+ const selected = this.selectable ? this.selected : '';
521
+ let said = this.announced;
522
+ if (this.hasUpdated && sort !== this.heardSort) {
523
+ const parsed = parse(sort);
524
+ const column = parsed
525
+ ? this.tableColumns.find((one) => one.field === parsed.field)
526
+ : undefined;
527
+ said = parsed
528
+ ? fillTemplate(this.sortedLabel, {
529
+ column: column?.textContent?.trim() || parsed.field,
530
+ order: parsed.direction === 'asc' ? this.ascendingLabel : this.descendingLabel,
531
+ })
532
+ : this.unsortedLabel;
533
+ }
534
+ else if (this.hasUpdated && selected !== this.heardSelected) {
535
+ said = fillTemplate(this.selectedLabel, {
536
+ count: formatNumbers(this.lang)(this.chosen.size),
537
+ });
538
+ }
539
+ this.heardSort = sort;
540
+ this.heardSelected = selected;
541
+ return said;
542
+ }
511
543
  willUpdate(changed) {
512
544
  super.willUpdate(changed);
545
+ this.announced = this.news();
513
546
  hostAria(this.internals, { label: this.label || 'Table', role: 'grid' });
514
547
  this.internals.ariaMultiSelectable = this.selectable ? 'true' : null;
515
548
  const placement = this.placement;
@@ -697,6 +730,9 @@ let HelixHeadlessDataTable = class HelixHeadlessDataTable extends MixinCollectio
697
730
  return undefined;
698
731
  }
699
732
  };
733
+ __decorate([
734
+ property({ attribute: 'ascending-label' })
735
+ ], HelixHeadlessDataTable.prototype, "ascendingLabel", void 0);
700
736
  __decorate([
701
737
  property({ attribute: 'default-filter' })
702
738
  ], HelixHeadlessDataTable.prototype, "defaultFilter", void 0);
@@ -715,6 +751,9 @@ __decorate([
715
751
  __decorate([
716
752
  property({ attribute: 'default-sort' })
717
753
  ], HelixHeadlessDataTable.prototype, "defaultSort", void 0);
754
+ __decorate([
755
+ property({ attribute: 'descending-label' })
756
+ ], HelixHeadlessDataTable.prototype, "descendingLabel", void 0);
718
757
  __decorate([
719
758
  property({ type: Number })
720
759
  ], HelixHeadlessDataTable.prototype, "estimate", void 0);
@@ -754,12 +793,21 @@ __decorate([
754
793
  __decorate([
755
794
  property()
756
795
  ], HelixHeadlessDataTable.prototype, "selected", null);
796
+ __decorate([
797
+ property({ attribute: 'selected-label' })
798
+ ], HelixHeadlessDataTable.prototype, "selectedLabel", void 0);
757
799
  __decorate([
758
800
  property()
759
801
  ], HelixHeadlessDataTable.prototype, "sort", null);
802
+ __decorate([
803
+ property({ attribute: 'sorted-label' })
804
+ ], HelixHeadlessDataTable.prototype, "sortedLabel", void 0);
760
805
  __decorate([
761
806
  property({ type: Number })
762
807
  ], HelixHeadlessDataTable.prototype, "total", void 0);
808
+ __decorate([
809
+ property({ attribute: 'unsorted-label' })
810
+ ], HelixHeadlessDataTable.prototype, "unsortedLabel", void 0);
763
811
  __decorate([
764
812
  property({ reflect: true, type: Boolean })
765
813
  ], HelixHeadlessDataTable.prototype, "windowed", void 0);
@@ -89,6 +89,7 @@ declare const HelixHeadlessMap_base: (new (...args: any[]) => import("@fusion.de
89
89
  * @csspart layers - The list of the map's shown layers, such as routes, areas, and points, read by screen readers.
90
90
  * @csspart layers-menu - The menu of the map's layers, which the layers button opens.
91
91
  * @csspart layers-toggle - The button that opens and closes the layers menu.
92
+ * @csspart live - What screen readers hear after the map moves: the zoom level, the way it faces, its tilt, or the way it moved. Visually hidden in the styled tier.
92
93
  * @csspart map-type - One map type's card in the layers menu: a radio button, its picture, and its name.
93
94
  * @csspart map-type-name - The name on a map type's card.
94
95
  * @csspart map-type-preview - The picture of the map on a map type's card.
@@ -124,6 +125,8 @@ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
124
125
  cluster: boolean;
125
126
  /** The Face north button's label. */
126
127
  faceNorthLabel: string;
128
+ /** What screen readers hear when the map is tilted back to looking straight down. */
129
+ flatLabel: string;
127
130
  /** The Hybrid map type's name. */
128
131
  hybridLabel: string;
129
132
  /**
@@ -149,6 +152,16 @@ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
149
152
  mapType?: 'hybrid' | 'satellite' | undefined;
150
153
  /** The name of the map types in the layers menu, read by screen readers. */
151
154
  mapTypeLabel: string;
155
+ /** What screen readers hear when the map has moved down. */
156
+ movedDownLabel: string;
157
+ /** What screen readers hear when the map has moved left. */
158
+ movedLeftLabel: string;
159
+ /** What screen readers hear when the map has moved right. */
160
+ movedRightLabel: string;
161
+ /** What screen readers hear when the map has moved up. */
162
+ movedUpLabel: string;
163
+ /** What screen readers hear when the map is turned back to face north. */
164
+ northLabel: string;
152
165
  /**
153
166
  * How far the map is tilted, in degrees from looking straight down.
154
167
  * `connectMap` keeps it current, and the 3D button is pressed while it is
@@ -163,8 +176,52 @@ export declare class HelixHeadlessMap extends HelixHeadlessMap_base {
163
176
  satelliteLabel: string;
164
177
  /** The Standard map type's name. */
165
178
  standardLabel: string;
179
+ /**
180
+ * What screen readers hear when the map is tilted. `{degrees}` is the tilt
181
+ * from looking straight down.
182
+ */
183
+ tiltedLabel: string;
166
184
  /** The 3D button's label. */
167
185
  tiltLabel: string;
186
+ /**
187
+ * What screen readers hear when the map is turned. `{degrees}` is the way the
188
+ * top of the map faces, clockwise from north.
189
+ */
190
+ turnedLabel: string;
191
+ /**
192
+ * What screen readers hear after a zoom. `{level}` is the zoom level.
193
+ */
194
+ zoomedLabel: string;
195
+ /**
196
+ * How far the map is zoomed, in its renderer's levels. `connectMap` keeps it
197
+ * current, and the map says it after a zoom.
198
+ */
199
+ zoomLevel?: number | undefined;
200
+ /** What the live region says now. */
201
+ private said;
202
+ /** What a reader last asked of the map, until the map has answered it. */
203
+ private asked;
204
+ /** The wait for the view to settle before it is said. */
205
+ private settling;
206
+ /**
207
+ * Says the map has moved, where a reader asked it to.
208
+ *
209
+ * **Called by whatever moves the map**, as `connectMap` does on every frame
210
+ * the view moves. The frame's keys and buttons only ask, and nothing moves
211
+ * until something answers, so the frame says nothing until it is told the
212
+ * view moved: an announcement for a move that never happened is worse than
213
+ * none. A page moving its own map in answer to `helix-map-pan` calls this
214
+ * once it has.
215
+ *
216
+ * What is said waits for the view to settle, so a held key or a long
217
+ * animation is one sentence, and it is the view's state where there is one:
218
+ * the zoom level, the way the map faces, its tilt. A pan has no state to
219
+ * say, so it says which way the map moved.
220
+ */
221
+ moved(): void;
222
+ private say;
223
+ /** Records what a reader asked, so the answer can be said. */
224
+ private ask;
168
225
  /**
169
226
  * The routes and areas among the frame's children, in the order they are
170
227
  * written, which is the order the list names them in.
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { customElement, HelixActivateEvent, HelixElement, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, hostAria, KeyboardNavigationController, MixinViewport, } from '@fusion.dev/helix-core';
7
+ import { customElement, fillTemplate, formatNumbers, HelixActivateEvent, HelixElement, HelixMapClusterEvent, HelixMapPanEvent, HelixMapPartChangeEvent, HelixMapRotateEvent, HelixMapTiltEvent, HelixMapZoomEvent, hostAria, KeyboardNavigationController, MixinViewport, } from '@fusion.dev/helix-core';
8
8
  import { buildings, cloudRain, dotsNine, leaf, mountains, path, polygon, stack, target, } from '@fusion.dev/helix-icons';
9
9
  import { html, nothing } from 'lit';
10
10
  import { property, state } from 'lit/decorators.js';
@@ -35,23 +35,37 @@ const iconOf = (layer) => {
35
35
  const ROTATE_STEP = 15;
36
36
  const TILT_STEP = 10;
37
37
  const TILTED = 50;
38
+ const ASK_LAPSES = 3000;
39
+ const SETTLES = 400;
38
40
  let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement) {
39
41
  constructor() {
40
42
  super(...arguments);
41
43
  this.bearing = 0;
42
44
  this.cluster = false;
43
45
  this.faceNorthLabel = 'Face north';
46
+ this.flatLabel = 'Looking straight down';
44
47
  this.hybridLabel = 'Hybrid';
45
48
  this.label = '';
46
49
  this.layersLabel = 'Map layers';
47
50
  this.mapPreviews = {};
48
51
  this.mapTypeLabel = 'Map type';
52
+ this.movedDownLabel = 'Moved down';
53
+ this.movedLeftLabel = 'Moved left';
54
+ this.movedRightLabel = 'Moved right';
55
+ this.movedUpLabel = 'Moved up';
56
+ this.northLabel = 'Facing north';
49
57
  this.pitch = 0;
50
58
  this.rotateLeftLabel = 'Rotate left';
51
59
  this.rotateRightLabel = 'Rotate right';
52
60
  this.satelliteLabel = 'Satellite';
53
61
  this.standardLabel = 'Standard';
62
+ this.tiltedLabel = 'Tilted {degrees} degrees';
54
63
  this.tiltLabel = 'Show in 3D';
64
+ this.turnedLabel = 'Facing {degrees} degrees';
65
+ this.zoomedLabel = 'Zoom level {level}';
66
+ this.said = '';
67
+ this.asked = undefined;
68
+ this.settling = undefined;
55
69
  this.layersOpen = false;
56
70
  this.handleOutside = (event) => {
57
71
  if (!event.composedPath().includes(this)) {
@@ -119,6 +133,46 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
119
133
  this.requestUpdate();
120
134
  };
121
135
  }
136
+ moved() {
137
+ if (!this.asked || performance.now() - this.asked.at > ASK_LAPSES) {
138
+ return;
139
+ }
140
+ clearTimeout(this.settling);
141
+ this.settling = setTimeout(() => this.say(), SETTLES);
142
+ }
143
+ say() {
144
+ const asked = this.asked;
145
+ this.asked = undefined;
146
+ if (!asked) {
147
+ return;
148
+ }
149
+ const numbers = formatNumbers(this.lang);
150
+ const degrees = (value) => numbers(Math.round(value));
151
+ const bearing = ((Math.round(this.bearing) % 360) + 360) % 360;
152
+ const words = {
153
+ down: this.movedDownLabel,
154
+ left: this.movedLeftLabel,
155
+ right: this.movedRightLabel,
156
+ up: this.movedUpLabel,
157
+ };
158
+ const next = asked.kind === 'pan'
159
+ ? (words[asked.side ?? ''] ?? '')
160
+ : asked.kind === 'zoom'
161
+ ? this.zoomLevel === undefined
162
+ ? ''
163
+ : fillTemplate(this.zoomedLabel, { level: numbers(this.zoomLevel) })
164
+ : asked.kind === 'tilt'
165
+ ? Math.round(this.pitch) === 0
166
+ ? this.flatLabel
167
+ : fillTemplate(this.tiltedLabel, { degrees: degrees(this.pitch) })
168
+ : bearing === 0
169
+ ? this.northLabel
170
+ : fillTemplate(this.turnedLabel, { degrees: degrees(bearing) });
171
+ this.said = next !== '' && next === this.said.trim() ? `${next}\u00a0` : next;
172
+ }
173
+ ask(kind, side) {
174
+ this.asked = { at: performance.now(), kind, ...(side ? { side } : {}) };
175
+ }
122
176
  get layers() {
123
177
  return this.knownChildren.filter((child) => child.isMapLayer === true);
124
178
  }
@@ -183,6 +237,8 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
183
237
  }
184
238
  disconnectedCallback() {
185
239
  super.disconnectedCallback();
240
+ clearTimeout(this.settling);
241
+ this.settling = undefined;
186
242
  this.hiding?.disconnect();
187
243
  document.removeEventListener('pointerdown', this.handleOutside);
188
244
  this.removeEventListener(HelixMapPartChangeEvent.eventName, this.handlePartChange);
@@ -221,15 +277,19 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
221
277
  }
222
278
  }
223
279
  rotate(by) {
280
+ this.ask('rotate');
224
281
  this.dispatchEvent(new HelixMapRotateEvent({ by }));
225
282
  }
226
283
  faceNorth() {
284
+ this.ask('north');
227
285
  this.dispatchEvent(new HelixMapRotateEvent({ to: 0 }));
228
286
  }
229
287
  tilt(by) {
288
+ this.ask('tilt');
230
289
  this.dispatchEvent(new HelixMapTiltEvent({ by }));
231
290
  }
232
291
  tiltTo(to) {
292
+ this.ask('tilt');
233
293
  this.dispatchEvent(new HelixMapTiltEvent({ to }));
234
294
  }
235
295
  renderLayersMenu() {
@@ -423,9 +483,11 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
423
483
  return false;
424
484
  }
425
485
  panEvent(detail) {
486
+ this.ask('pan', this.sideOf(detail.to));
426
487
  return new HelixMapPanEvent(detail);
427
488
  }
428
489
  zoomEvent(detail) {
490
+ this.ask('zoom');
429
491
  return new HelixMapZoomEvent(detail);
430
492
  }
431
493
  get restingControlsLabel() {
@@ -472,6 +534,7 @@ let HelixHeadlessMap = class HelixHeadlessMap extends MixinViewport(HelixElement
472
534
  <div part="attribution">
473
535
  <slot name="attribution"></slot>
474
536
  </div>
537
+ <span part="live" role="status">${this.said}</span>
475
538
  </div>`;
476
539
  }
477
540
  };
@@ -484,6 +547,9 @@ __decorate([
484
547
  __decorate([
485
548
  property({ attribute: 'face-north-label' })
486
549
  ], HelixHeadlessMap.prototype, "faceNorthLabel", void 0);
550
+ __decorate([
551
+ property({ attribute: 'flat-label' })
552
+ ], HelixHeadlessMap.prototype, "flatLabel", void 0);
487
553
  __decorate([
488
554
  property({ attribute: 'hybrid-label' })
489
555
  ], HelixHeadlessMap.prototype, "hybridLabel", void 0);
@@ -502,6 +568,21 @@ __decorate([
502
568
  __decorate([
503
569
  property({ attribute: 'map-type-label' })
504
570
  ], HelixHeadlessMap.prototype, "mapTypeLabel", void 0);
571
+ __decorate([
572
+ property({ attribute: 'moved-down-label' })
573
+ ], HelixHeadlessMap.prototype, "movedDownLabel", void 0);
574
+ __decorate([
575
+ property({ attribute: 'moved-left-label' })
576
+ ], HelixHeadlessMap.prototype, "movedLeftLabel", void 0);
577
+ __decorate([
578
+ property({ attribute: 'moved-right-label' })
579
+ ], HelixHeadlessMap.prototype, "movedRightLabel", void 0);
580
+ __decorate([
581
+ property({ attribute: 'moved-up-label' })
582
+ ], HelixHeadlessMap.prototype, "movedUpLabel", void 0);
583
+ __decorate([
584
+ property({ attribute: 'north-label' })
585
+ ], HelixHeadlessMap.prototype, "northLabel", void 0);
505
586
  __decorate([
506
587
  property({ type: Number })
507
588
  ], HelixHeadlessMap.prototype, "pitch", void 0);
@@ -517,9 +598,24 @@ __decorate([
517
598
  __decorate([
518
599
  property({ attribute: 'standard-label' })
519
600
  ], HelixHeadlessMap.prototype, "standardLabel", void 0);
601
+ __decorate([
602
+ property({ attribute: 'tilted-label' })
603
+ ], HelixHeadlessMap.prototype, "tiltedLabel", void 0);
520
604
  __decorate([
521
605
  property({ attribute: 'tilt-label' })
522
606
  ], HelixHeadlessMap.prototype, "tiltLabel", void 0);
607
+ __decorate([
608
+ property({ attribute: 'turned-label' })
609
+ ], HelixHeadlessMap.prototype, "turnedLabel", void 0);
610
+ __decorate([
611
+ property({ attribute: 'zoomed-label' })
612
+ ], HelixHeadlessMap.prototype, "zoomedLabel", void 0);
613
+ __decorate([
614
+ property({ attribute: 'zoom-level', type: Number })
615
+ ], HelixHeadlessMap.prototype, "zoomLevel", void 0);
616
+ __decorate([
617
+ state()
618
+ ], HelixHeadlessMap.prototype, "said", void 0);
523
619
  __decorate([
524
620
  state()
525
621
  ], HelixHeadlessMap.prototype, "layersOpen", void 0);
@@ -94,7 +94,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
94
94
  }
95
95
  tellTrigger() {
96
96
  const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
97
- tellDisclosure(slot?.assignedElements({ flatten: false })[0], this.open);
97
+ tellDisclosure(slot?.assignedElements({ flatten: false })[0], this.open, 'menu');
98
98
  }
99
99
  get trigger() {
100
100
  return anchorElement(this.renderRoot?.querySelector('slot[name="trigger"]'), this);
@@ -188,6 +188,20 @@ export declare class HelixHeadlessSelect extends HelixHeadlessSelect_base {
188
188
  * cannot change.
189
189
  */
190
190
  protected renderIndicator(): TemplateResult;
191
+ /**
192
+ * What names the trigger: the label, then the choice.
193
+ *
194
+ * **A button is named by its label alone**, and what it shows is not read:
195
+ * the `<label for>` wins over the button's own text. Read from Chromium's
196
+ * tree, a Select showing "Spanish" was a button named "Language", with the
197
+ * choice in no name or value, so a reader never heard what was chosen. A
198
+ * native `<select>` says both, and this names the trigger by both, the label
199
+ * and then the value, in that order.
200
+ *
201
+ * A consumer's own `aria-labelledby` on the host wins, carried to the trigger
202
+ * by `AriaForwardController`, so nothing is written here to fight it.
203
+ */
204
+ private get triggerNames();
191
205
  protected render(): TemplateResult;
192
206
  }
193
207
  declare global {
@@ -146,12 +146,19 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
146
146
  </svg>
147
147
  </slot>`;
148
148
  }
149
+ get triggerNames() {
150
+ if (this.hasAttribute('aria-labelledby')) {
151
+ return nothing;
152
+ }
153
+ return this.labeled ? `${this.labelId} value` : 'value';
154
+ }
149
155
  render() {
150
156
  return html `${this.renderLabel()}
151
157
  <button
152
158
  part="trigger"
153
159
  id=${this.controlId}
154
160
  type="button"
161
+ aria-labelledby=${this.triggerNames}
155
162
  aria-haspopup="listbox"
156
163
  aria-expanded=${this.open ? 'true' : 'false'}
157
164
  aria-describedby=${this.describedBy ?? nothing}
@@ -161,7 +168,7 @@ let HelixHeadlessSelect = class HelixHeadlessSelect extends MixinControllerOpen(
161
168
  @keydown=${this.handleTriggerKeydown}
162
169
  >
163
170
  <slot name="start"></slot>
164
- <span part="value">${this.display}</span>
171
+ <span part="value" id="value">${this.display}</span>
165
172
  ${''}
166
173
  <slot name="end"></slot>
167
174
  ${''}
@@ -202,6 +202,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
202
202
  return html `<${this.tag('listbox')}
203
203
  part="column"
204
204
  data-column=${part}
205
+ aria-invalid=${this.invalid ? 'true' : nothing}
205
206
  exportparts="empty: column-empty, feedback: column-feedback, filter-icon: column-filter-icon,
206
207
  filter-mark: column-filter-mark, hint: column-hint, items: column-items, label: column-label,
207
208
  live: column-live, loading: column-loading, search: column-search, search-field: column-search-field"
@@ -244,6 +245,7 @@ let HelixHeadlessTimePicker = class HelixHeadlessTimePicker extends MixinControl
244
245
  renderList() {
245
246
  return html `<${this.tag('listbox')}
246
247
  part="list"
248
+ aria-invalid=${this.invalid ? 'true' : nothing}
247
249
  exportparts="empty: list-empty, feedback: list-feedback, filter-icon: list-filter-icon,
248
250
  filter-mark: list-filter-mark, hint: list-hint, items: list-items, label: list-label,
249
251
  live: list-live, loading: list-loading, search: list-search, search-field: list-search-field"
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.109.3",
4
+ "version": "0.110.1",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -46,13 +46,13 @@
46
46
  },
47
47
  "bugs": "https://github.com/project-fusion/fusion/issues",
48
48
  "dependencies": {
49
- "@fusion.dev/helix-core": "0.109.3",
50
- "@fusion.dev/helix-icons": "0.109.3",
49
+ "@fusion.dev/helix-core": "0.110.1",
50
+ "@fusion.dev/helix-icons": "0.110.1",
51
51
  "uqr": "^0.1.3"
52
52
  },
53
53
  "devDependencies": {
54
- "@fusion.dev/helix-cem": "0.109.3",
55
- "@fusion.dev/helix-testing": "0.109.3",
54
+ "@fusion.dev/helix-cem": "0.110.1",
55
+ "@fusion.dev/helix-testing": "0.110.1",
56
56
  "@fusion.dev/scripts": "0.0.0-dev",
57
57
  "@happy-dom/global-registrator": "^20.0.0",
58
58
  "@lit-labs/ssr": "^4.1.0",