@fusion.dev/helix-headless 0.44.0 → 0.45.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.
@@ -38128,7 +38128,7 @@
38128
38128
  "helix": {
38129
38129
  "category": "forms",
38130
38130
  "since": "0.23.0",
38131
- "status": "experimental",
38131
+ "status": "stable",
38132
38132
  "documentation": "https://fusion.dev/helix/components/filter-input",
38133
38133
  "aliases": [
38134
38134
  "FilterToken",
@@ -38596,7 +38596,7 @@
38596
38596
  "helix": {
38597
38597
  "category": "forms",
38598
38598
  "since": "0.23.0",
38599
- "status": "experimental",
38599
+ "status": "stable",
38600
38600
  "documentation": "https://fusion.dev/helix/components/filter-input",
38601
38601
  "aliases": [
38602
38602
  "Facet",
@@ -39325,7 +39325,7 @@
39325
39325
  "helix": {
39326
39326
  "category": "forms",
39327
39327
  "since": "0.25.0",
39328
- "status": "experimental",
39328
+ "status": "stable",
39329
39329
  "documentation": "https://fusion.dev/helix/components/filter-input",
39330
39330
  "aliases": [
39331
39331
  "FacetPicker",
@@ -39789,7 +39789,7 @@
39789
39789
  "helix": {
39790
39790
  "category": "forms",
39791
39791
  "since": "0.23.0",
39792
- "status": "experimental",
39792
+ "status": "stable",
39793
39793
  "documentation": "https://fusion.dev/helix/components/filter-input",
39794
39794
  "aliases": [
39795
39795
  "Comparator",
@@ -41812,7 +41812,7 @@
41812
41812
  "helix": {
41813
41813
  "category": "forms",
41814
41814
  "since": "0.1.0",
41815
- "status": "experimental",
41815
+ "status": "stable",
41816
41816
  "documentation": "https://fusion.dev/helix/components/filter-input",
41817
41817
  "aliases": [
41818
41818
  "SearchField",
@@ -62513,6 +62513,17 @@
62513
62513
  "description": "The heading over the run.",
62514
62514
  "attribute": "label"
62515
62515
  },
62516
+ {
62517
+ "kind": "method",
62518
+ "name": "nameFromHeading",
62519
+ "privacy": "private",
62520
+ "return": {
62521
+ "type": {
62522
+ "text": "void"
62523
+ }
62524
+ },
62525
+ "description": "Names the run after the heading it drew, whatever the heading is made of.\n\n**The string alone was not the name of the heading, it was the name of one\nway of writing it.** `label` reached `ariaLabel` and the slot reached the\nscreen, so a consumer who took the documented `label` slot for a heading\nricher than a string got a `role=\"group\"` with no accessible name at all,\nand the words they wrote were the one thing a reader could not hear: the\nheading carries `aria-hidden`, because the name was supposed to be on the\nhost already. Set both and they disagreed, the host saying the string and\nthe panel showing the slot.\n\nSo the host points at the heading, which is the one thing that is always\nwhat a reader sees. `ElementInternals` and not an IDREF, because the host\nis in the light DOM and the heading is in this shadow root, which is the\nboundary `aria-labelledby` cannot cross. `MixinField.linkHost` does the\nsame for a field's label and records the measurement.\n\nClient only. Element references need the nodes to exist, and the server has\nthe string."
62526
+ },
62516
62527
  {
62517
62528
  "kind": "field",
62518
62529
  "name": "tier",
@@ -62856,7 +62867,7 @@
62856
62867
  "summary": "A named run of options inside a listbox.",
62857
62868
  "helix": {
62858
62869
  "since": "0.1.0",
62859
- "status": "experimental",
62870
+ "status": "stable",
62860
62871
  "documentation": "https://fusion.dev/helix/components/option",
62861
62872
  "aliases": [
62862
62873
  "OptGroup",
@@ -63532,7 +63543,7 @@
63532
63543
  "helix": {
63533
63544
  "category": "forms",
63534
63545
  "since": "0.1.0",
63535
- "status": "experimental",
63546
+ "status": "stable",
63536
63547
  "documentation": "https://fusion.dev/helix/components/option",
63537
63548
  "aliases": [
63538
63549
  "Item",
@@ -71390,7 +71401,7 @@
71390
71401
  "summary": "A set of choices where exactly one is the answer.",
71391
71402
  "helix": {
71392
71403
  "since": "0.1.0",
71393
- "status": "experimental",
71404
+ "status": "stable",
71394
71405
  "documentation": "https://fusion.dev/helix/components/radio",
71395
71406
  "aliases": [
71396
71407
  "RadioButtons",
@@ -71504,6 +71515,15 @@
71504
71515
  },
71505
71516
  "description": "Whether the markup declared this one chosen, which is what a reset returns to.\n\n`<option selected>` reflects to `defaultSelected` and `option.selected` is\nlive state that does not reflect, and those are two different facts. This\nelement has one `selected` doing both jobs, so the declaration is the\nattribute and the state is the property.\n\n**The attribute, read when it is asked for, and never a snapshot taken in\nthe constructor.** A field initializer runs there, and an element the HTML\nparser created has none of its attributes yet at that moment: measured on\nthis element, on `Option` and on `Toggle`, `<helix-radio value=\"a\"\nselected>` reported `defaultSelected` as `false` both from parsed markup\nand from `createElement` followed by `setAttribute`, which is every way a\nconsumer writes one. The snapshot could only ever be true for an element\nalready in the document when `customElements.define` ran.\n\nNothing overwrites it either, which is what makes reading it late correct\nrather than merely later: `selected` does not reflect, for the reason\n`willUpdate` records, so the group's writes never reach the attribute and\nwhat it says is what the markup said.\n\n**The deeper half is not this.** State reflecting under the declaration's\nname is the fault `TreeItem` avoids by mirroring to `data-selected`.\nSplitting them means moving what every stylesheet selects on, which is a\nchange of its own rather than a property being added."
71506
71517
  },
71518
+ {
71519
+ "kind": "field",
71520
+ "name": "focusable",
71521
+ "type": {
71522
+ "text": "boolean"
71523
+ },
71524
+ "default": "false",
71525
+ "description": "Whether this choice holds the group's tab stop while nothing is chosen.\n\nThe stop rides on the selection, and an unanswered group has no selection,\nso without this every choice is `tabindex=\"-1\"` and the keyboard cannot\nenter the set at all.\n\n**Written by the group, with no attribute, and it reaches the control\nrather than the host.** A `tabindex` on the host is two faults at once: it\nis markup a consumer rendered and their server never sent, which React\nreports as a mismatch and does not patch up, and it puts the tab stop on\nthe element rather than on the `<button role=\"radio\">` inside it, so a\nscreen reader meets an unnamed box where a native group would have given\nthem an unchecked radio. `Toggle.Root` carries the same property for the\nsame reason."
71526
+ },
71507
71527
  {
71508
71528
  "kind": "field",
71509
71529
  "name": "selected",
@@ -71936,7 +71956,7 @@
71936
71956
  "helix": {
71937
71957
  "category": "forms",
71938
71958
  "since": "0.1.0",
71939
- "status": "experimental",
71959
+ "status": "stable",
71940
71960
  "documentation": "https://fusion.dev/helix/components/radio",
71941
71961
  "aliases": [
71942
71962
  "Option",
@@ -77274,7 +77294,7 @@
77274
77294
  "helix": {
77275
77295
  "category": "navigation",
77276
77296
  "since": "0.1.0",
77277
- "status": "experimental",
77297
+ "status": "stable",
77278
77298
  "documentation": "https://fusion.dev/helix/components/skip-link",
77279
77299
  "aliases": [
77280
77300
  "Skip navigation",
@@ -37,7 +37,7 @@ declare const HelixHeadlessFilterChip_base: (new (...args: any[]) => import("@fu
37
37
  * gives: what the words mean is the domain's.
38
38
  *
39
39
  * @since 0.23.0
40
- * @status experimental
40
+ * @status stable
41
41
  * @category forms
42
42
  * @aliases FilterToken, FacetChip
43
43
  * @documentation https://fusion.dev/helix/components/filter-input
@@ -43,7 +43,7 @@ import type { HelixHeadlessFilterOperator } from '../operator/filter-operator.js
43
43
  * what fills a chip then is whatever the consumer types.
44
44
  *
45
45
  * @since 0.23.0
46
- * @status experimental
46
+ * @status stable
47
47
  * @category forms
48
48
  * @aliases Facet, Dimension
49
49
  * @documentation https://fusion.dev/helix/components/filter-input
@@ -50,7 +50,7 @@ import '../../menu/index.js';
50
50
  * rather than inventing one. Refusing `helix-facet` refuses the change.
51
51
  *
52
52
  * @since 0.25.0
53
- * @status experimental
53
+ * @status stable
54
54
  * @category forms
55
55
  * @aliases FacetPicker, FilterBuilder
56
56
  * @documentation https://fusion.dev/helix/components/filter-input
@@ -23,7 +23,7 @@ import { nothing, type PropertyValues } from 'lit';
23
23
  * consumer bug and the field reports it rather than guessing.
24
24
  *
25
25
  * @since 0.23.0
26
- * @status experimental
26
+ * @status stable
27
27
  * @category forms
28
28
  * @aliases Comparator, Predicate
29
29
  * @documentation https://fusion.dev/helix/components/filter-input
@@ -83,7 +83,7 @@ declare const HelixHeadlessFilterInput_base: (new (...args: any[]) => import("@f
83
83
  * and the chip goes because the filter went.
84
84
  *
85
85
  * @since 0.1.0
86
- * @status experimental
86
+ * @status stable
87
87
  * @category forms
88
88
  * @aliases SearchField, FacetInput, TokenInput
89
89
  * @documentation https://fusion.dev/helix/components/filter-input
@@ -13,7 +13,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
13
13
  * an option is in without the heading becoming something to land on.
14
14
  *
15
15
  * @since 0.1.0
16
- * @status experimental
16
+ * @status stable
17
17
  * @aliases OptGroup, Section
18
18
  * @documentation https://fusion.dev/helix/components/option
19
19
  *
@@ -29,6 +29,29 @@ export declare class HelixHeadlessOptionGroup extends HelixElement {
29
29
  */
30
30
  label: string;
31
31
  protected willUpdate(changed: PropertyValues): void;
32
+ protected updated(changed: PropertyValues): void;
33
+ /**
34
+ * Names the run after the heading it drew, whatever the heading is made of.
35
+ *
36
+ * **The string alone was not the name of the heading, it was the name of one
37
+ * way of writing it.** `label` reached `ariaLabel` and the slot reached the
38
+ * screen, so a consumer who took the documented `label` slot for a heading
39
+ * richer than a string got a `role="group"` with no accessible name at all,
40
+ * and the words they wrote were the one thing a reader could not hear: the
41
+ * heading carries `aria-hidden`, because the name was supposed to be on the
42
+ * host already. Set both and they disagreed, the host saying the string and
43
+ * the panel showing the slot.
44
+ *
45
+ * So the host points at the heading, which is the one thing that is always
46
+ * what a reader sees. `ElementInternals` and not an IDREF, because the host
47
+ * is in the light DOM and the heading is in this shadow root, which is the
48
+ * boundary `aria-labelledby` cannot cross. `MixinField.linkHost` does the
49
+ * same for a field's label and records the measurement.
50
+ *
51
+ * Client only. Element references need the nodes to exist, and the server has
52
+ * the string.
53
+ */
54
+ private nameFromHeading;
32
55
  protected render(): TemplateResult;
33
56
  }
34
57
  declare global {
@@ -16,6 +16,14 @@ let HelixHeadlessOptionGroup = class HelixHeadlessOptionGroup extends HelixEleme
16
16
  super.willUpdate(changed);
17
17
  hostAria(this.internals, { label: this.label, role: 'group' });
18
18
  }
19
+ updated(changed) {
20
+ super.updated(changed);
21
+ this.nameFromHeading();
22
+ }
23
+ nameFromHeading() {
24
+ const heading = this.partNamed('label');
25
+ hostAria(this.internals, { labelledBy: heading ? [heading] : [] });
26
+ }
19
27
  render() {
20
28
  return html `<span part="label" aria-hidden="true">
21
29
  <slot name="label">${this.label}</slot>
@@ -41,7 +41,7 @@ export declare const optionsAmong: (elements: readonly Element[]) => HelixHeadle
41
41
  * question and differs between a select and a command palette.
42
42
  *
43
43
  * @since 0.1.0
44
- * @status experimental
44
+ * @status stable
45
45
  * @category forms
46
46
  * @aliases Item, Choice
47
47
  * @documentation https://fusion.dev/helix/components/option
@@ -26,7 +26,7 @@ declare const HelixHeadlessRadioGroup_base: (new (...args: any[]) => import("@fu
26
26
  * anything is chosen.
27
27
  *
28
28
  * @since 0.1.0
29
- * @status experimental
29
+ * @status stable
30
30
  * @aliases RadioButtons, Choice group
31
31
  * @documentation https://fusion.dev/helix/components/radio
32
32
  *
@@ -29,17 +29,13 @@ let HelixHeadlessRadioGroup = class HelixHeadlessRadioGroup extends MixinControl
29
29
  }
30
30
  place() {
31
31
  const items = this.items;
32
+ const answer = items.find((item) => item.value === this.value);
32
33
  for (const item of items) {
33
- item.selected = item.value === this.value;
34
+ item.selected = item === answer;
34
35
  }
35
- const entered = this.value === '' && items.length > 0 ? items[0] : undefined;
36
+ const entry = answer ? undefined : items.find((item) => !item.disabled);
36
37
  for (const item of items) {
37
- if (item === entered) {
38
- item.setAttribute('tabindex', '0');
39
- }
40
- else {
41
- item.removeAttribute('tabindex');
42
- }
38
+ item.focusable = item === entry;
43
39
  }
44
40
  }
45
41
  sync() {
@@ -20,7 +20,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
20
20
  * be a second source of truth for which one is on.
21
21
  *
22
22
  * @since 0.1.0
23
- * @status experimental
23
+ * @status stable
24
24
  * @category forms
25
25
  * @aliases Option, Choice
26
26
  * @documentation https://fusion.dev/helix/components/radio
@@ -75,6 +75,23 @@ export declare class HelixHeadlessRadio extends HelixElement {
75
75
  */
76
76
  get defaultSelected(): boolean;
77
77
  set defaultSelected(next: boolean);
78
+ /**
79
+ * Whether this choice holds the group's tab stop while nothing is chosen.
80
+ *
81
+ * The stop rides on the selection, and an unanswered group has no selection,
82
+ * so without this every choice is `tabindex="-1"` and the keyboard cannot
83
+ * enter the set at all.
84
+ *
85
+ * **Written by the group, with no attribute, and it reaches the control
86
+ * rather than the host.** A `tabindex` on the host is two faults at once: it
87
+ * is markup a consumer rendered and their server never sent, which React
88
+ * reports as a mismatch and does not patch up, and it puts the tab stop on
89
+ * the element rather than on the `<button role="radio">` inside it, so a
90
+ * screen reader meets an unnamed box where a native group would have given
91
+ * them an unchecked radio. `Toggle.Root` carries the same property for the
92
+ * same reason.
93
+ */
94
+ focusable: boolean;
78
95
  /**
79
96
  * Whether this is the current choice.
80
97
  *
@@ -11,6 +11,7 @@ let HelixHeadlessRadio = class HelixHeadlessRadio extends HelixElement {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.disabled = false;
14
+ this.focusable = false;
14
15
  this.selected = false;
15
16
  this.value = '';
16
17
  }
@@ -40,7 +41,7 @@ let HelixHeadlessRadio = class HelixHeadlessRadio extends HelixElement {
40
41
  role="radio"
41
42
  aria-checked=${this.selected ? 'true' : 'false'}
42
43
  ?disabled=${this.disabled}
43
- tabindex=${this.selected ? '0' : '-1'}
44
+ tabindex=${this.selected || this.focusable ? '0' : '-1'}
44
45
  @click=${() => this.select()}
45
46
  >
46
47
  <span part="box"><span part="mark" aria-hidden="true"></span></span>
@@ -52,6 +53,9 @@ let HelixHeadlessRadio = class HelixHeadlessRadio extends HelixElement {
52
53
  __decorate([
53
54
  property({ reflect: true, type: Boolean })
54
55
  ], HelixHeadlessRadio.prototype, "disabled", void 0);
56
+ __decorate([
57
+ property({ attribute: false })
58
+ ], HelixHeadlessRadio.prototype, "focusable", void 0);
55
59
  __decorate([
56
60
  property({ type: Boolean })
57
61
  ], HelixHeadlessRadio.prototype, "selected", void 0);
@@ -32,7 +32,7 @@ import { type TemplateResult } from 'lit';
32
32
  * broken.
33
33
  *
34
34
  * @since 0.1.0
35
- * @status experimental
35
+ * @status stable
36
36
  * @category navigation
37
37
  * @aliases Skip navigation, Skip to content
38
38
  * @documentation https://fusion.dev/helix/components/skip-link
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.44.0",
4
+ "version": "0.45.0",
5
5
  "license": "MIT",
6
6
  "author": "Ross Sheppard <ross.sheppard@fusion.dev>",
7
7
  "repository": {
@@ -46,14 +46,14 @@
46
46
  },
47
47
  "bugs": "https://github.com/project-fusion/fusion/issues",
48
48
  "dependencies": {
49
- "@fusion.dev/helix-core": "0.44.0",
50
- "@fusion.dev/helix-icons": "0.44.0",
49
+ "@fusion.dev/helix-core": "0.45.0",
50
+ "@fusion.dev/helix-icons": "0.45.0",
51
51
  "uqr": "^0.1.3"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@custom-elements-manifest/analyzer": "^0.11.0",
55
- "@fusion.dev/helix-cem": "0.44.0",
56
- "@fusion.dev/helix-testing": "0.44.0",
55
+ "@fusion.dev/helix-cem": "0.45.0",
56
+ "@fusion.dev/helix-testing": "0.45.0",
57
57
  "@fusion.dev/scripts": "0.0.0-dev",
58
58
  "@happy-dom/global-registrator": "^20.0.0",
59
59
  "@lit-labs/ssr": "^4.1.0",
package/types/jsx.d.ts CHANGED
@@ -11715,6 +11715,21 @@ name is the fault `TreeItem` avoids by mirroring to `data-selected`.
11715
11715
  Splitting them means moving what every stylesheet selects on, which is a
11716
11716
  change of its own rather than a property being added. */
11717
11717
  defaultSelected?: (HelixHeadlessRadio["defaultSelected"]) | undefined;
11718
+ /** Whether this choice holds the group's tab stop while nothing is chosen.
11719
+
11720
+ The stop rides on the selection, and an unanswered group has no selection,
11721
+ so without this every choice is `tabindex="-1"` and the keyboard cannot
11722
+ enter the set at all.
11723
+
11724
+ **Written by the group, with no attribute, and it reaches the control
11725
+ rather than the host.** A `tabindex` on the host is two faults at once: it
11726
+ is markup a consumer rendered and their server never sent, which React
11727
+ reports as a mismatch and does not patch up, and it puts the tab stop on
11728
+ the element rather than on the `<button role="radio">` inside it, so a
11729
+ screen reader meets an unnamed box where a native group would have given
11730
+ them an unchecked radio. `Toggle.Root` carries the same property for the
11731
+ same reason. */
11732
+ focusable?: (HelixHeadlessRadio["focusable"]) | undefined;
11718
11733
 
11719
11734
  /** Fired when this choice is made. Cancelable: refusing it takes the decision and nothing moves. */
11720
11735
  "onhelix-select"?: ((e: HelixHeadlessRadioHelixSelectElementEvent) => void) | undefined;
@@ -11755,6 +11770,21 @@ name is the fault `TreeItem` avoids by mirroring to `data-selected`.
11755
11770
  Splitting them means moving what every stylesheet selects on, which is a
11756
11771
  change of its own rather than a property being added. */
11757
11772
  "prop:defaultSelected"?: (HelixHeadlessRadio["defaultSelected"]) | undefined;
11773
+ /** Whether this choice holds the group's tab stop while nothing is chosen.
11774
+
11775
+ The stop rides on the selection, and an unanswered group has no selection,
11776
+ so without this every choice is `tabindex="-1"` and the keyboard cannot
11777
+ enter the set at all.
11778
+
11779
+ **Written by the group, with no attribute, and it reaches the control
11780
+ rather than the host.** A `tabindex` on the host is two faults at once: it
11781
+ is markup a consumer rendered and their server never sent, which React
11782
+ reports as a mismatch and does not patch up, and it puts the tab stop on
11783
+ the element rather than on the `<button role="radio">` inside it, so a
11784
+ screen reader meets an unnamed box where a native group would have given
11785
+ them an unchecked radio. `Toggle.Root` carries the same property for the
11786
+ same reason. */
11787
+ "prop:focusable"?: (HelixHeadlessRadio["focusable"]) | undefined;
11758
11788
  /** Fired when this choice is made. Cancelable: refusing it takes the decision and nothing moves. */
11759
11789
  "on:helix-select"?: ((e: HelixHeadlessRadioHelixSelectElementEvent) => void) | undefined;
11760
11790
 
@@ -29054,6 +29084,20 @@ export type CustomElements = {
29054
29084
  * name is the fault `TreeItem` avoids by mirroring to `data-selected`.
29055
29085
  * Splitting them means moving what every stylesheet selects on, which is a
29056
29086
  * change of its own rather than a property being added. (property only)
29087
+ * - `focusable`: Whether this choice holds the group's tab stop while nothing is chosen.
29088
+ *
29089
+ * The stop rides on the selection, and an unanswered group has no selection,
29090
+ * so without this every choice is `tabindex="-1"` and the keyboard cannot
29091
+ * enter the set at all.
29092
+ *
29093
+ * **Written by the group, with no attribute, and it reaches the control
29094
+ * rather than the host.** A `tabindex` on the host is two faults at once: it
29095
+ * is markup a consumer rendered and their server never sent, which React
29096
+ * reports as a mismatch and does not patch up, and it puts the tab stop on
29097
+ * the element rather than on the `<button role="radio">` inside it, so a
29098
+ * screen reader meets an unnamed box where a native group would have given
29099
+ * them an unchecked radio. `Toggle.Root` carries the same property for the
29100
+ * same reason. (property only)
29057
29101
  * - `whenSettled`: Resolves a frame after the first update, when transitions are switched on.
29058
29102
  *
29059
29103
  * **Named `whenSettled` because `settled` is taken four times over**, by
@@ -46914,6 +46958,20 @@ export type CustomElementsSolidJs = {
46914
46958
  * name is the fault `TreeItem` avoids by mirroring to `data-selected`.
46915
46959
  * Splitting them means moving what every stylesheet selects on, which is a
46916
46960
  * change of its own rather than a property being added. (property only)
46961
+ * - `focusable`: Whether this choice holds the group's tab stop while nothing is chosen.
46962
+ *
46963
+ * The stop rides on the selection, and an unanswered group has no selection,
46964
+ * so without this every choice is `tabindex="-1"` and the keyboard cannot
46965
+ * enter the set at all.
46966
+ *
46967
+ * **Written by the group, with no attribute, and it reaches the control
46968
+ * rather than the host.** A `tabindex` on the host is two faults at once: it
46969
+ * is markup a consumer rendered and their server never sent, which React
46970
+ * reports as a mismatch and does not patch up, and it puts the tab stop on
46971
+ * the element rather than on the `<button role="radio">` inside it, so a
46972
+ * screen reader meets an unnamed box where a native group would have given
46973
+ * them an unchecked radio. `Toggle.Root` carries the same property for the
46974
+ * same reason. (property only)
46917
46975
  * - `whenSettled`: Resolves a frame after the first update, when transitions are switched on.
46918
46976
  *
46919
46977
  * **Named `whenSettled` because `settled` is taken four times over**, by
package/types/vue.d.ts CHANGED
@@ -6021,6 +6021,21 @@ name is the fault `TreeItem` avoids by mirroring to `data-selected`.
6021
6021
  Splitting them means moving what every stylesheet selects on, which is a
6022
6022
  change of its own rather than a property being added. */
6023
6023
  defaultSelected?: (HelixHeadlessRadio["defaultSelected"]) | undefined;
6024
+ /** Whether this choice holds the group's tab stop while nothing is chosen.
6025
+
6026
+ The stop rides on the selection, and an unanswered group has no selection,
6027
+ so without this every choice is `tabindex="-1"` and the keyboard cannot
6028
+ enter the set at all.
6029
+
6030
+ **Written by the group, with no attribute, and it reaches the control
6031
+ rather than the host.** A `tabindex` on the host is two faults at once: it
6032
+ is markup a consumer rendered and their server never sent, which React
6033
+ reports as a mismatch and does not patch up, and it puts the tab stop on
6034
+ the element rather than on the `<button role="radio">` inside it, so a
6035
+ screen reader meets an unnamed box where a native group would have given
6036
+ them an unchecked radio. `Toggle.Root` carries the same property for the
6037
+ same reason. */
6038
+ focusable?: (HelixHeadlessRadio["focusable"]) | undefined;
6024
6039
  };
6025
6040
 
6026
6041
  export type HelixHeadlessRadioVueEvents = {