@fusion.dev/helix-core 0.0.0-dev → 0.0.2

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.
Files changed (89) hide show
  1. package/custom-elements.json +156 -124
  2. package/dist/controllers/aria-forward-controller/aria-forward-controller.d.ts +2 -0
  3. package/dist/controllers/aria-forward-controller/aria-forward-controller.js +44 -17
  4. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.d.ts +5 -6
  5. package/dist/controllers/chart-pointer-controller/chart-pointer-controller.js +5 -6
  6. package/dist/controllers/command-controller/command-controller.d.ts +5 -7
  7. package/dist/controllers/command-controller/command-controller.js +5 -7
  8. package/dist/controllers/copy-controller/copy-controller.d.ts +2 -4
  9. package/dist/controllers/copy-controller/copy-controller.js +7 -9
  10. package/dist/controllers/disabled-cascade-controller/disabled-cascade-controller.d.ts +10 -17
  11. package/dist/controllers/disabled-cascade-controller/disabled-cascade-controller.js +11 -18
  12. package/dist/controllers/dismiss-controller/dismiss-controller.d.ts +44 -6
  13. package/dist/controllers/dismiss-controller/dismiss-controller.js +77 -10
  14. package/dist/controllers/drilldown-controller/drilldown-controller.d.ts +3 -6
  15. package/dist/controllers/drilldown-controller/drilldown-controller.js +3 -6
  16. package/dist/controllers/floating-controller/floating-controller.d.ts +7 -8
  17. package/dist/controllers/floating-controller/floating-controller.js +69 -38
  18. package/dist/controllers/focus-trap-controller/focus-trap-controller.js +7 -10
  19. package/dist/controllers/indicator-controller/indicator-controller.d.ts +17 -29
  20. package/dist/controllers/indicator-controller/indicator-controller.js +11 -21
  21. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.d.ts +3 -13
  22. package/dist/controllers/keyboard-navigation-controller/keyboard-navigation-controller.js +11 -24
  23. package/dist/controllers/popover-controller/popover-controller.d.ts +2 -7
  24. package/dist/controllers/popover-controller/popover-controller.js +3 -9
  25. package/dist/controllers/source-controller/source-controller.d.ts +2 -5
  26. package/dist/controllers/source-controller/source-controller.js +2 -5
  27. package/dist/controllers/token-color-controller/token-color-controller.d.ts +2 -4
  28. package/dist/controllers/token-color-controller/token-color-controller.js +2 -4
  29. package/dist/controllers/virtualize-controller/virtualize-controller.d.ts +1 -1
  30. package/dist/controllers/virtualize-controller/virtualize-controller.js +15 -15
  31. package/dist/events/element-events.js +0 -5
  32. package/dist/events/events.d.ts +55 -93
  33. package/dist/events/events.js +68 -105
  34. package/dist/helix-element/helix-element.d.ts +26 -27
  35. package/dist/helix-element/helix-element.js +83 -100
  36. package/dist/helix-form-element/helix-form-element.d.ts +11 -17
  37. package/dist/helix-form-element/helix-form-element.js +15 -21
  38. package/dist/mixins/mixin-chart-source/mixin-chart-source.d.ts +6 -14
  39. package/dist/mixins/mixin-chart-source/mixin-chart-source.js +12 -25
  40. package/dist/mixins/mixin-collection/mixin-collection.d.ts +1 -7
  41. package/dist/mixins/mixin-collection/mixin-collection.js +16 -23
  42. package/dist/mixins/mixin-controller-active/mixin-controller-active.d.ts +15 -16
  43. package/dist/mixins/mixin-controller-active/mixin-controller-active.js +28 -42
  44. package/dist/mixins/mixin-controller-checked/mixin-controller-checked.d.ts +15 -17
  45. package/dist/mixins/mixin-controller-checked/mixin-controller-checked.js +33 -45
  46. package/dist/mixins/mixin-controller-open/mixin-controller-open.d.ts +4 -4
  47. package/dist/mixins/mixin-controller-open/mixin-controller-open.js +29 -45
  48. package/dist/mixins/mixin-controller-value/mixin-controller-value.d.ts +10 -18
  49. package/dist/mixins/mixin-controller-value/mixin-controller-value.js +14 -31
  50. package/dist/mixins/mixin-dismissible/mixin-dismissible.d.ts +10 -15
  51. package/dist/mixins/mixin-dismissible/mixin-dismissible.js +10 -15
  52. package/dist/mixins/mixin-field/mixin-field.d.ts +10 -12
  53. package/dist/mixins/mixin-field/mixin-field.js +48 -64
  54. package/dist/utils/capture-pointer/capture-pointer.d.ts +1 -4
  55. package/dist/utils/capture-pointer/capture-pointer.js +1 -4
  56. package/dist/utils/closest-instance/closest-instance.d.ts +6 -10
  57. package/dist/utils/closest-instance/closest-instance.js +6 -10
  58. package/dist/utils/create-sheet/create-sheet.d.ts +1 -1
  59. package/dist/utils/create-sheet/create-sheet.js +1 -1
  60. package/dist/utils/custom-element/custom-element.d.ts +0 -21
  61. package/dist/utils/custom-element/custom-element.js +1 -1
  62. package/dist/utils/draw-icon/draw-icon.d.ts +3 -3
  63. package/dist/utils/draw-icon/draw-icon.js +3 -3
  64. package/dist/utils/elapsed-time/elapsed-time.js +1 -3
  65. package/dist/utils/focusable-elements/focusable-elements.d.ts +3 -4
  66. package/dist/utils/focusable-elements/focusable-elements.js +9 -11
  67. package/dist/utils/format-numbers/format-numbers.js +1 -4
  68. package/dist/utils/host-aria/host-aria.d.ts +2 -2
  69. package/dist/utils/host-aria/host-aria.js +2 -2
  70. package/dist/utils/line-path/line-path.js +1 -1
  71. package/dist/utils/on-unless-false/on-unless-false.d.ts +0 -5
  72. package/dist/utils/on-unless-false/on-unless-false.js +0 -5
  73. package/dist/utils/read-series/read-series.d.ts +26 -0
  74. package/dist/utils/read-series/read-series.js +26 -29
  75. package/dist/utils/relative-time/relative-time.js +5 -6
  76. package/dist/utils/request-change/request-change.d.ts +9 -20
  77. package/dist/utils/resolve-active-item/resolve-active-item.d.ts +3 -3
  78. package/dist/utils/resolve-active-item/resolve-active-item.js +3 -3
  79. package/dist/utils/restore-value/restore-value.js +1 -6
  80. package/dist/utils/scroll-item-into-view/scroll-item-into-view.js +4 -5
  81. package/dist/utils/slot-filled/slot-filled.d.ts +2 -4
  82. package/dist/utils/slot-filled/slot-filled.js +2 -4
  83. package/dist/utils/tell-disclosure/tell-disclosure.d.ts +15 -18
  84. package/dist/utils/tell-disclosure/tell-disclosure.js +15 -18
  85. package/dist/utils/tone-marks/tone-marks.d.ts +10 -16
  86. package/dist/utils/tone-marks/tone-marks.js +6 -10
  87. package/dist/utils/value-as/value-as.d.ts +0 -28
  88. package/dist/utils/value-as/value-as.js +5 -10
  89. package/package.json +6 -6
@@ -17,9 +17,9 @@ export declare class MixinControllerValueInterface {
17
17
  /**
18
18
  * What the control holds now, and what its form submits.
19
19
  *
20
- * Reading gives the real value, in either mode. Writing hands ownership to
21
- * the consumer: from then on the control announces changes and applies none
22
- * of them, and waits to be told.
20
+ * Reading gives the real value. Writing always applies, and is a seed and a
21
+ * correction rather than a claim of ownership: a consumer who wants the
22
+ * decision refuses the `helix-before-change`.
23
23
  */
24
24
  value: string;
25
25
  /**
@@ -32,8 +32,7 @@ export declare class MixinControllerValueInterface {
32
32
  *
33
33
  * It selects the event rather than the timing. `requestValue` is called
34
34
  * whenever the value moves either way; this decides whether the control sends
35
- * `input` alone or `input` and `change` together, so a consumer can read the
36
- * name and know how often to expect it.
35
+ * `input` alone or `input` and `change` together.
37
36
  */
38
37
  protected readonly announcesInput: boolean;
39
38
  /** Requests a new value, and reports whether the request stood. */
@@ -46,23 +45,16 @@ export declare class MixinControllerValueInterface {
46
45
  /**
47
46
  * Grafts `value`, `defaultValue`, and the two callbacks that answer a form.
48
47
  *
49
- * Every control with a value had written this, and by the third it was three
50
- * copies of the same four members and the same seeding rule: `Input`, then
51
- * `Toggle.Group`, then `Radio.Group`. The copies had not diverged, which is the
52
- * argument for the mixin rather than against it, because the next one would
53
- * have.
54
- *
55
- * **Seeded in `willUpdate`, not `connectedCallback`.** SSR never calls the
56
- * latter, so a control with a `default-value` would serve empty and fill itself
57
- * at hydration, which is a flash for a reader and a mismatch for React. Seeding
58
- * once, before the first render, is what makes the server and the client agree.
48
+ * **Derived on read, never seeded in a lifecycle hook.** `value` falls back to
49
+ * `defaultValue`, so the server and the client agree without either running a
50
+ * hook. Seeding in `connectedCallback` is the trap: SSR never calls it, so a
51
+ * control with a `default-value` would serve empty and fill itself at
52
+ * hydration, a flash for a reader and a mismatch for React.
59
53
  *
60
54
  * **What it does not own is reporting.** `setFormValue` stays with the
61
55
  * component, because what an empty value means is the component's business: a
62
56
  * text field submits its emptiness, since `""` is an answer and the field was
63
- * on the form, while a group with nothing chosen submits nothing at all. One
64
- * mixin cannot hold both without a flag, and a flag would be this rule written
65
- * down twice.
57
+ * on the form, while a group with nothing chosen submits nothing at all.
66
58
  *
67
59
  * `Checkbox` deliberately does not take this. Its `value` is what it submits
68
60
  * *when it is on* rather than what it holds, so it is configuration rather than
@@ -10,23 +10,16 @@ import { requestChange } from '../../utils/request-change/request-change.js';
10
10
  /**
11
11
  * Grafts `value`, `defaultValue`, and the two callbacks that answer a form.
12
12
  *
13
- * Every control with a value had written this, and by the third it was three
14
- * copies of the same four members and the same seeding rule: `Input`, then
15
- * `Toggle.Group`, then `Radio.Group`. The copies had not diverged, which is the
16
- * argument for the mixin rather than against it, because the next one would
17
- * have.
18
- *
19
- * **Seeded in `willUpdate`, not `connectedCallback`.** SSR never calls the
20
- * latter, so a control with a `default-value` would serve empty and fill itself
21
- * at hydration, which is a flash for a reader and a mismatch for React. Seeding
22
- * once, before the first render, is what makes the server and the client agree.
13
+ * **Derived on read, never seeded in a lifecycle hook.** `value` falls back to
14
+ * `defaultValue`, so the server and the client agree without either running a
15
+ * hook. Seeding in `connectedCallback` is the trap: SSR never calls it, so a
16
+ * control with a `default-value` would serve empty and fill itself at
17
+ * hydration, a flash for a reader and a mismatch for React.
23
18
  *
24
19
  * **What it does not own is reporting.** `setFormValue` stays with the
25
20
  * component, because what an empty value means is the component's business: a
26
21
  * text field submits its emptiness, since `""` is an answer and the field was
27
- * on the form, while a group with nothing chosen submits nothing at all. One
28
- * mixin cannot hold both without a flag, and a flag would be this rule written
29
- * down twice.
22
+ * on the form, while a group with nothing chosen submits nothing at all.
30
23
  *
31
24
  * `Checkbox` deliberately does not take this. Its `value` is what it submits
32
25
  * *when it is on* rather than what it holds, so it is configuration rather than
@@ -54,12 +47,10 @@ export const MixinControllerValue = (Base) => {
54
47
  /**
55
48
  * Whether a reader edits this continuously, which decides the native name.
56
49
  *
57
- * **It used to pick between `helix-input` and `helix-change`**, which was
58
- * one mechanism under two names and said *phase* where it meant
59
- * *granularity*. The request event is `helix-before-change` whatever the
60
- * answer; this decides which native event announces the result, `input`
61
- * while a reader is editing against `change` when they are done, which is
62
- * the distinction the platform already draws. See 294.
50
+ * The request event is `helix-before-change` whatever the answer; this
51
+ * decides which native event announces the result, `input` while a reader
52
+ * is editing against `change` when they are done, which is the distinction
53
+ * the platform already draws.
63
54
  *
64
55
  * Discrete by default: only a control the reader edits continuously says
65
56
  * otherwise.
@@ -69,17 +60,10 @@ export const MixinControllerValue = (Base) => {
69
60
  /**
70
61
  * What the control holds now, and what its form submits.
71
62
  *
72
- * One property, not two. Reading gives the real value whichever mode the
73
- * control is in, so `input.value` answers the question anyone asking it
74
- * meant. Writing hands ownership to the consumer, which is what
75
- * `<helix-input value="x">` says too, and matches what a controlled input
76
- * means in React: the value is theirs and the control waits to be told.
77
- * `default-value` is how markup seeds one it does not want to own.
78
- *
79
- * The same shape as the other three mixins, which is the point of them
80
- * being four of a kind rather than four of three kinds.
81
- *
82
- * helix-input
63
+ * One property, not two, so `input.value` answers the question anyone
64
+ * asking it meant. Writing always applies; `<helix-input value="x">` is the
65
+ * same statement in markup, and `default-value` is how markup seeds a value
66
+ * a reset should return to.
83
67
  */
84
68
  get value() {
85
69
  return this.valueState ?? this.defaultValue;
@@ -117,7 +101,6 @@ export const MixinControllerValue = (Base) => {
117
101
  * itself, whether or not a consumer declared `value`: a declared property
118
102
  * is a seed and a correction, the way `<input value>` is, and a consumer
119
103
  * who wants the change for themselves refuses it and assigns their own.
120
- * See 294.
121
104
  */
122
105
  requestValue(next) {
123
106
  if (this.value === next) {
@@ -17,25 +17,20 @@ export declare class DismissibleElement {
17
17
  /**
18
18
  * Grafts `dismissible`, its label, and the event that says so.
19
19
  *
20
- * Three surfaces had written this: `Announcement`, `Tag` and `Toast.Item`, each
21
- * with the same two properties, the same event, and the same expression for the
22
- * label. They had not diverged, which is the argument for extracting rather than
23
- * against it, because the fourth would have.
24
- *
25
- * **The label reads the default slot, not the whole element**, and that is a bug
26
- * fix rather than a move. Every copy said `this.textContent`, which is every
27
- * light-DOM descendant including the ones assigned elsewhere. On `Announcement`
28
- * and `Tag` that is the same thing, because they have one slot. On `Toast.Item`
29
- * it swept in the actions: a message reading "Could not reach the server" with a
30
- * `Retry` button announced itself as "Could not reach the server Retry", and the
31
- * consumer removing it from their list got that string to match on.
20
+ * **The label reads the default slot, never `this.textContent`**, which is
21
+ * every light-DOM descendant including the ones assigned elsewhere. On
22
+ * `Announcement` and `Tag` the two are the same thing, because they have one
23
+ * slot. On `Toast.Item` the second sweeps in the actions: a message reading
24
+ * "Could not reach the server" with a `Retry` button announces itself as "Could
25
+ * not reach the server Retry", and the consumer removing it from their list
26
+ * gets that string to match on.
32
27
  *
33
28
  * **`dismissEvent()` is separate from `dismiss()` for the one that needs it.**
34
29
  * `Announcement` and `Tag` dispatch and are done: they own no state, so a
35
30
  * refusal is the whole of the consumer's answer. `Toast.Item` holds an `open`
36
- * it may or may not own, so its event has to go through the controlled-change
37
- * path with an `apply` beside it. Giving it the event rather than the method
38
- * lets it keep the contract and skip the default.
31
+ * and applies the change itself, so its event goes through `requestChange` with
32
+ * an `apply` beside it. Giving it the event rather than the method lets it keep
33
+ * the contract and skip the default.
39
34
  */
40
35
  export declare const MixinDismissible: <T extends Constructor<LitElement>>(Base: T) => Constructor<DismissibleElement> & T;
41
36
  export {};
@@ -9,25 +9,20 @@ import { HelixDismissEvent } from '../../events/events.js';
9
9
  /**
10
10
  * Grafts `dismissible`, its label, and the event that says so.
11
11
  *
12
- * Three surfaces had written this: `Announcement`, `Tag` and `Toast.Item`, each
13
- * with the same two properties, the same event, and the same expression for the
14
- * label. They had not diverged, which is the argument for extracting rather than
15
- * against it, because the fourth would have.
16
- *
17
- * **The label reads the default slot, not the whole element**, and that is a bug
18
- * fix rather than a move. Every copy said `this.textContent`, which is every
19
- * light-DOM descendant including the ones assigned elsewhere. On `Announcement`
20
- * and `Tag` that is the same thing, because they have one slot. On `Toast.Item`
21
- * it swept in the actions: a message reading "Could not reach the server" with a
22
- * `Retry` button announced itself as "Could not reach the server Retry", and the
23
- * consumer removing it from their list got that string to match on.
12
+ * **The label reads the default slot, never `this.textContent`**, which is
13
+ * every light-DOM descendant including the ones assigned elsewhere. On
14
+ * `Announcement` and `Tag` the two are the same thing, because they have one
15
+ * slot. On `Toast.Item` the second sweeps in the actions: a message reading
16
+ * "Could not reach the server" with a `Retry` button announces itself as "Could
17
+ * not reach the server Retry", and the consumer removing it from their list
18
+ * gets that string to match on.
24
19
  *
25
20
  * **`dismissEvent()` is separate from `dismiss()` for the one that needs it.**
26
21
  * `Announcement` and `Tag` dispatch and are done: they own no state, so a
27
22
  * refusal is the whole of the consumer's answer. `Toast.Item` holds an `open`
28
- * it may or may not own, so its event has to go through the controlled-change
29
- * path with an `apply` beside it. Giving it the event rather than the method
30
- * lets it keep the contract and skip the default.
23
+ * and applies the change itself, so its event goes through `requestChange` with
24
+ * an `apply` beside it. Giving it the event rather than the method lets it keep
25
+ * the contract and skip the default.
31
26
  */
32
27
  export const MixinDismissible = (Base) => {
33
28
  class Dismissible extends Base {
@@ -16,7 +16,7 @@ export declare class MixinFieldInterface {
16
16
  */
17
17
  hint: string;
18
18
  /**
19
- * Ids of the description nodes, for `aria-describedby` on the control.
19
+ * Id of the description node, for `aria-describedby` on the control.
20
20
  *
21
21
  * `undefined` when there is nothing to point at, so the attribute is absent
22
22
  * rather than pointing at an id that renders nothing.
@@ -68,7 +68,7 @@ export declare class MixinFieldInterface {
68
68
  *
69
69
  * `empty` is the control's own answer to whether it holds nothing, `message`
70
70
  * is what to say about it, and `anchor` names the part the browser should
71
- * point its bubble at. Seven families wrote this out by hand.
71
+ * point its bubble at.
72
72
  */
73
73
  protected reportRequired(empty: boolean, message: string, anchor: string): void;
74
74
  /** The message actually shown: the consumer's, or the platform's, or none. */
@@ -129,17 +129,15 @@ export declare class MixinFieldInterface {
129
129
  * uses. Measured the same way, and the failure is silent either way: the
130
130
  * description simply never reaches the accessibility tree.
131
131
  *
132
- *
133
132
  * **The label, the hint and the feedback are documented on each component that
134
- * applies this, rather than here.** Eight of the eighteen did and ten did not,
135
- * which is what `check:parts` now refuses in both directions. Declaring them
136
- * once on this mixin is the obvious fix and does not work: the analyzer reads
137
- * `@csspart` off a custom element class, so a class-level tag here reaches the
138
- * manifest as nothing at all, on this JSDoc and on the inner class alike.
139
- * Measured both ways, and the inner-class version is the dangerous one, since
140
- * it satisfies `check:parts` while every API page stays exactly as wrong as
141
- * before. Members are different and do inherit, which is what the note about
142
- * the inner class in the area rules is about.
133
+ * applies this, rather than here.** Declaring them once on this mixin is the
134
+ * obvious fix and does not work: the analyzer reads `@csspart` off a custom
135
+ * element class, so a class-level tag here reaches the manifest as nothing at
136
+ * all, on this JSDoc and on the inner class alike. Measured both ways, and the
137
+ * inner-class version is the dangerous one, since it satisfies the rule while
138
+ * every API page stays exactly as wrong as before. Members are different and
139
+ * do inherit, which is what the note about the inner class in the area rules
140
+ * is about.
143
141
  */
144
142
  export declare const MixinField: <T extends Constructor<HelixElement>>(Base: T) => Constructor<MixinFieldInterface> & T;
145
143
  export {};
@@ -65,17 +65,15 @@ const FEEDBACK_ID = 'helix-feedback';
65
65
  * uses. Measured the same way, and the failure is silent either way: the
66
66
  * description simply never reaches the accessibility tree.
67
67
  *
68
- *
69
68
  * **The label, the hint and the feedback are documented on each component that
70
- * applies this, rather than here.** Eight of the eighteen did and ten did not,
71
- * which is what `check:parts` now refuses in both directions. Declaring them
72
- * once on this mixin is the obvious fix and does not work: the analyzer reads
73
- * `@csspart` off a custom element class, so a class-level tag here reaches the
74
- * manifest as nothing at all, on this JSDoc and on the inner class alike.
75
- * Measured both ways, and the inner-class version is the dangerous one, since
76
- * it satisfies `check:parts` while every API page stays exactly as wrong as
77
- * before. Members are different and do inherit, which is what the note about
78
- * the inner class in the area rules is about.
69
+ * applies this, rather than here.** Declaring them once on this mixin is the
70
+ * obvious fix and does not work: the analyzer reads `@csspart` off a custom
71
+ * element class, so a class-level tag here reaches the manifest as nothing at
72
+ * all, on this JSDoc and on the inner class alike. Measured both ways, and the
73
+ * inner-class version is the dangerous one, since it satisfies the rule while
74
+ * every API page stays exactly as wrong as before. Members are different and
75
+ * do inherit, which is what the note about the inner class in the area rules
76
+ * is about.
79
77
  */
80
78
  export const MixinField = (Base) => {
81
79
  // JSDoc is duplicated from MixinFieldInterface deliberately. The interface is
@@ -84,14 +82,14 @@ export const MixinField = (Base) => {
84
82
  class FieldElement extends Base {
85
83
  constructor() {
86
84
  super(...arguments);
87
- /**
88
- * Help under the control, shown while there is nothing wrong.
89
- */
90
- this.hint = '';
91
85
  /**
92
86
  * What is wrong, in the reader's words rather than the browser's.
93
87
  */
94
88
  this.feedback = '';
89
+ /**
90
+ * Help under the control, shown while there is nothing wrong.
91
+ */
92
+ this.hint = '';
95
93
  /**
96
94
  * The accessible name, and the fallback for the `label` slot.
97
95
  */
@@ -103,15 +101,14 @@ export const MixinField = (Base) => {
103
101
  /**
104
102
  * Whether the reader has had a go at the field yet.
105
103
  *
106
- * This was `:user-invalid`, which is the platform's own answer to "is this
107
- * field wrong *yet*" and is the right idea. It does not work here, and the
108
- * reason is structural rather than a browser bug: `:user-invalid` tracks
109
- * interaction with the element it is on, and nobody interacts with this
110
- * element. The reader types into an `<input>` inside its shadow root, so
111
- * the host's interaction flag is never set and the selector never matches.
112
- * Measured after submitting an invalid required field and again after
113
- * editing one and leaving it: false both times, while `:invalid` matched
114
- * both times.
104
+ * **Not `:user-invalid`**, which is the platform's own answer to "is this
105
+ * field wrong *yet*" and cannot work here, for a structural reason rather
106
+ * than a browser bug: it tracks interaction with the element it is on, and
107
+ * nobody interacts with this element. The reader types into an `<input>`
108
+ * inside its shadow root, so the host's interaction flag is never set and
109
+ * the selector never matches. Measured after submitting an invalid required
110
+ * field and again after editing one and leaving it: false both times, while
111
+ * `:invalid` matched both times.
115
112
  *
116
113
  * So the moments are listened for instead, and both of them demonstrably
117
114
  * reach the host: `invalid` when a form is submitted with this control
@@ -122,10 +119,10 @@ export const MixinField = (Base) => {
122
119
  * **A composite control has to construct its own `change`, and this mixin
123
120
  * deliberately does not do it for them.** A native `change` is
124
121
  * `composed: false`, so a control built from several inner inputs never
125
- * relays one to its host: `Input` gets this free from its one control, and
126
- * `PinInput` had four boxes, none of whose `change` means the code is
127
- * entered, so a half-entered code was never `touched` and could never say
128
- * what was wrong with it. See 311.
122
+ * relays one to its host: `Input` gets this free from its one control,
123
+ * while a `PinInput` of four boxes has no inner `change` that means the
124
+ * code is entered, so a half-entered code is never `touched` and can never
125
+ * say what is wrong with it.
129
126
  *
130
127
  * **The obvious shared fix is for `touched` to take a `focusout`, and it is
131
128
  * the wrong one**, because it is the paragraph above undone: a `focusout`
@@ -134,15 +131,15 @@ export const MixinField = (Base) => {
134
131
  * focus moment, it is the native meaning of `change`, "edited, then left",
135
132
  * and the editing half is the part a mixin up here cannot see.
136
133
  *
137
- * **Nor is there one shape to extract yet.** The two composites in the
138
- * library solve it differently and both are right: `PinInput` is
139
- * focus-driven, taking a baseline when focus arrives from outside the row
140
- * and announcing on the way out if it moved, and `DateInput` is
141
- * value-driven, stopping its inner `change` and announcing once the date
142
- * itself moves. Generalizing from those two would be generalizing from one,
143
- * since only one of them has a leaving moment at all. The trigger for
144
- * lifting it is a third control that needs the focus-driven shape, at which
145
- * point what moves up is the baseline-and-commit pair rather than `touched`.
134
+ * **Nor is there one shape to extract yet.** The two composites solve it
135
+ * differently and both are right: `PinInput` is focus-driven, taking a
136
+ * baseline when focus arrives from outside the row and announcing on the
137
+ * way out if it moved, and `DateInput` is value-driven, stopping its inner
138
+ * `change` and announcing once the date itself moves. Only one of them has
139
+ * a leaving moment at all, so generalizing from the pair is generalizing
140
+ * from one. The trigger for lifting it is a third control that needs the
141
+ * focus-driven shape, at which point what moves up is the
142
+ * baseline-and-commit pair rather than `touched`.
146
143
  */
147
144
  this.touched = false;
148
145
  /** What the last render put on screen, so a change can be noticed. */
@@ -230,10 +227,8 @@ export const MixinField = (Base) => {
230
227
  * that the label is rendered and clipped, so the name arrives through the
231
228
  * same `<label for>` a visible one does.
232
229
  *
233
- * This replaced `hide-label`, which was a second attribute to say what the
234
- * absence of the first should have said. The rule is now `label` means a
235
- * label, `aria-label` means a name, and neither means an unnamed field a
236
- * screen reader announces as "edit text"
230
+ * The rule is that `label` means a label, `aria-label` means a name, and
231
+ * neither means an unnamed field a screen reader announces as "edit text".
237
232
  */
238
233
  renderLabel() {
239
234
  const hidden = this.label === '' ? this.getAttribute('aria-label') : null;
@@ -323,17 +318,10 @@ export const MixinField = (Base) => {
323
318
  /**
324
319
  * Reports "required and empty" and refreshes the message, or clears both.
325
320
  *
326
- * Seven families had this written out: the same `valueMissing`, the same
327
- * anchor lookup, the same `refreshFeedback` under the same comment about
328
- * validity moving without a property moving. Seven copies of a five-line
329
- * shape is the threshold at which this mixin itself was extracted, and two
330
- * of them had already drifted, `checkbox-group` having no validity at all
331
- * until yesterday.
332
- *
333
- * What differs between them is exactly two things, so those are the
321
+ * Two things differ between the controls that call this, so those are the
334
322
  * arguments: whether the control is empty, which only it can say, and the
335
323
  * sentence to say about it, which is different for a box, a switch and a
336
- * set. The anchor is looked up here because every one of them anchored on a
324
+ * set. The anchor is looked up here because every one of them anchors on a
337
325
  * rendered part and the browser needs somewhere to point the bubble.
338
326
  *
339
327
  * The message is not derived from the control's kind, deliberately. "Tick
@@ -354,13 +342,12 @@ export const MixinField = (Base) => {
354
342
  * Records "wrong, and the reader knows it" as the custom state `invalid`.
355
343
  *
356
344
  * The styled tier selects on this rather than on `:host(:user-invalid)`,
357
- * which cannot match for the reason above, so the invalid border never
358
- * arrived either. One state, and the message and the border agree by
359
- * construction.
345
+ * which cannot match for the reason above. One state, and the message and
346
+ * the border agree by construction.
360
347
  *
361
- * **A state and not `data-invalid`, since 27 Aug 2026.** A field served
362
- * with a `feedback` message already failing was written invalid on its
363
- * first update, which is an attribute the consumer's server never sent.
348
+ * **A state and not `data-invalid`.** A field served with a `feedback`
349
+ * message already failing is written invalid on its first update, which
350
+ * would be an attribute the consumer's server never sent.
364
351
  * `HelixElement.toggleState` carries the argument.
365
352
  *
366
353
  * In `willUpdate`, which is where the resolved message is first known.
@@ -369,11 +356,9 @@ export const MixinField = (Base) => {
369
356
  super.willUpdate(changed);
370
357
  this.shownFeedback = this.resolvedFeedback;
371
358
  this.toggleState('invalid', this.shownFeedback !== '');
372
- /* And say it to the accessibility tree, which nothing did.
373
- *
374
- * A field carrying a `feedback` message painted red, and was described by
375
- * that message through `aria-describedby`, and was never announced as
376
- * being *wrong*: a reader heard the message as though it were help text.
359
+ /* And say it to the accessibility tree. Describing a field with its
360
+ * `feedback` message through `aria-describedby` never announces it as
361
+ * being *wrong*: a reader hears the message as though it were help text.
377
362
  * `aria-invalid` is the one word for it, and this is the one place that
378
363
  * knows, so every field in the library gets it from here. */
379
364
  this.internals.ariaInvalid = this.shownFeedback === '' ? null : 'true';
@@ -395,10 +380,10 @@ export const MixinField = (Base) => {
395
380
  }
396
381
  __decorate([
397
382
  property()
398
- ], FieldElement.prototype, "hint", void 0);
383
+ ], FieldElement.prototype, "feedback", void 0);
399
384
  __decorate([
400
385
  property()
401
- ], FieldElement.prototype, "feedback", void 0);
386
+ ], FieldElement.prototype, "hint", void 0);
402
387
  __decorate([
403
388
  property()
404
389
  ], FieldElement.prototype, "label", void 0);
@@ -417,7 +402,6 @@ export const MixinField = (Base) => {
417
402
 
418
403
  The contract is enforced by use rather than by the cast. Every helper is
419
404
  called from `Input` or `Checkbox`, so an interface that drifts from the
420
- class fails their builds, which is exactly how the missing declarations
421
- were found. */
405
+ class fails their builds. */
422
406
  return FieldElement;
423
407
  };
@@ -11,10 +11,7 @@
11
11
  *
12
12
  * That is reachable outside a test: a pointer released between the event being
13
13
  * queued and the handler running is gone by the time this is called, and a
14
- * synthetic `pointerdown` has no active pointer at all. The browser spec run
15
- * reported four of those as uncaught page errors, and three splitter specs had
16
- * each stubbed the method to get past it, which is the shape of a workaround
17
- * that has been in place long enough to read as setup.
14
+ * synthetic `pointerdown` has no active pointer at all.
18
15
  *
19
16
  * Returns whether it took, for the callers that then ask `hasPointerCapture`
20
17
  * before believing a move belongs to them.
@@ -11,10 +11,7 @@
11
11
  *
12
12
  * That is reachable outside a test: a pointer released between the event being
13
13
  * queued and the handler running is gone by the time this is called, and a
14
- * synthetic `pointerdown` has no active pointer at all. The browser spec run
15
- * reported four of those as uncaught page errors, and three splitter specs had
16
- * each stubbed the method to get past it, which is the shape of a workaround
17
- * that has been in place long enough to read as setup.
14
+ * synthetic `pointerdown` has no active pointer at all.
18
15
  *
19
16
  * Returns whether it took, for the callers that then ask `hasPointerCapture`
20
17
  * before believing a move belongs to them.
@@ -1,19 +1,15 @@
1
1
  /**
2
2
  * The nearest ancestor that is an instance of a class, crossing shadow roots.
3
3
  *
4
- * **Identity rather than tag name, and the rule already existed.** `Accordion`'s
5
- * root states it two files from a violation of it: "Items are matched with
6
- * `instanceof`, never by tag name. Matching `helix-accordion-item` would tie
7
- * this tier to the styled one." Nine headless families resolve their
8
- * relationships that way; two did it by tag, and both named `helix-*` tags that
9
- * only `@fusion.dev/helix` registers, which is a headless package depending on a
10
- * styled one by string.
4
+ * **Identity rather than tag name.** Matching `helix-accordion-item` ties the
5
+ * headless tier to the styled one by string, and only `@fusion.dev/helix`
6
+ * registers a `helix-*` tag.
11
7
  *
12
8
  * The consequence is silent rather than loud. Subclassing a headless element under
13
9
  * a different tag is exactly what the headless tier exists to allow, and it is
14
- * what `helix/styled` itself does. Under a third tag the lookup returned `null`,
15
- * so an accordion's `mode` was ignored and every section behaved as `multiple`
16
- * with no error anywhere.
10
+ * what `helix/styled` itself does. Under a third tag a lookup by tag returns
11
+ * `null`, so an accordion's `mode` is ignored and every section behaves as
12
+ * `multiple` with no error anywhere.
17
13
  *
18
14
  * **`closest()` alone is not enough**, which is why this walks rather than
19
15
  * delegating. `closest()` stops at a shadow boundary: a component slotted into
@@ -1,19 +1,15 @@
1
1
  /**
2
2
  * The nearest ancestor that is an instance of a class, crossing shadow roots.
3
3
  *
4
- * **Identity rather than tag name, and the rule already existed.** `Accordion`'s
5
- * root states it two files from a violation of it: "Items are matched with
6
- * `instanceof`, never by tag name. Matching `helix-accordion-item` would tie
7
- * this tier to the styled one." Nine headless families resolve their
8
- * relationships that way; two did it by tag, and both named `helix-*` tags that
9
- * only `@fusion.dev/helix` registers, which is a headless package depending on a
10
- * styled one by string.
4
+ * **Identity rather than tag name.** Matching `helix-accordion-item` ties the
5
+ * headless tier to the styled one by string, and only `@fusion.dev/helix`
6
+ * registers a `helix-*` tag.
11
7
  *
12
8
  * The consequence is silent rather than loud. Subclassing a headless element under
13
9
  * a different tag is exactly what the headless tier exists to allow, and it is
14
- * what `helix/styled` itself does. Under a third tag the lookup returned `null`,
15
- * so an accordion's `mode` was ignored and every section behaved as `multiple`
16
- * with no error anywhere.
10
+ * what `helix/styled` itself does. Under a third tag a lookup by tag returns
11
+ * `null`, so an accordion's `mode` is ignored and every section behaves as
12
+ * `multiple` with no error anywhere.
17
13
  *
18
14
  * **`closest()` alone is not enough**, which is why this walks rather than
19
15
  * delegating. `closest()` stops at a shadow boundary: a component slotted into
@@ -4,7 +4,7 @@ import { type CSSResult } from 'lit';
4
4
  *
5
5
  * Component styles live in real `.css` files rather than `css` tagged
6
6
  * templates, so they get CSS tooling: syntax highlighting, formatters, the
7
- * token linter, and a diff that reads as CSS. `bun run styles` compiles each
7
+ * token linter, and a diff that reads as CSS. `turbo run styles` compiles each
8
8
  * one into a sibling `.css.ts` that calls this.
9
9
  *
10
10
  * The `unsafe` in `unsafeCSS` warns about interpolating untrusted input into a
@@ -4,7 +4,7 @@ import { unsafeCSS } from 'lit';
4
4
  *
5
5
  * Component styles live in real `.css` files rather than `css` tagged
6
6
  * templates, so they get CSS tooling: syntax highlighting, formatters, the
7
- * token linter, and a diff that reads as CSS. `bun run styles` compiles each
7
+ * token linter, and a diff that reads as CSS. `turbo run styles` compiles each
8
8
  * one into a sibling `.css.ts` that calls this.
9
9
  *
10
10
  * The `unsafe` in `unsafeCSS` warns about interpolating untrusted input into a
@@ -1,24 +1,3 @@
1
- /**
2
- * Registers a custom element, once.
3
- *
4
- * A drop-in replacement for Lit's `@customElement` that does not throw when the
5
- * tag is already registered.
6
- *
7
- * The duplicate is not hypothetical. Next.js evaluates a module in both the
8
- * server and client graphs, so a component imported by a server component and a
9
- * client component runs its registration twice against one registry, and
10
- * `customElements.define` throws:
11
- *
12
- * > Failed to execute 'define': the name "helix-card" has already been used
13
- *
14
- * That failure takes down the whole prerender, and it appears only once an app
15
- * mixes server and client usage of the same component, which is exactly what a
16
- * real app does.
17
- *
18
- * The analyzer detects `@customElement` **by decorator name**, not by where it
19
- * is imported from, so the manifest still records `tagName` and nothing
20
- * downstream changes.
21
- */
22
1
  /** Every tag registered twice by different classes, in registration order. */
23
2
  export declare const duplicateRegistrations: () => string[];
24
3
  /** Every tag registered through `customElement`, whoever imported it first. */
@@ -1,4 +1,4 @@
1
- /**
1
+ /*
2
2
  * Registers a custom element, once.
3
3
  *
4
4
  * A drop-in replacement for Lit's `@customElement` that does not throw when the
@@ -15,15 +15,15 @@ export interface DrawIconOptions {
15
15
  * every consumer of any component that renders one ships all of the generated
16
16
  * path data: measured at 50,814 bytes raw and 15,010 gzipped for 102 icons, and
17
17
  * growing about 147 gzipped bytes per icon added. A named import ships one path
18
- * and is checked when the package compiles rather than when `check:icons` runs.
18
+ * and is checked when the package compiles rather than by a name lookup later.
19
19
  *
20
20
  * That element still earns its place as *public* API, where a consumer writes a
21
21
  * name in markup and a lookup is the only thing that can work. What it should
22
22
  * not be is how this library draws its own marks.
23
23
  *
24
24
  * **There is no `part` option, and there cannot be.** A part name behind an
25
- * interpolation is a part the manifest cannot see, so `check:selectors` reports
26
- * the component's own stylesheet as matching nothing and the part stops being
25
+ * interpolation is a part the manifest cannot see, so the component's own
26
+ * stylesheet matches nothing and the part stops being
27
27
  * API. `Trend` needs one and therefore writes its `<svg>` out with a literal
28
28
  * `part="icon"`, which is the honest cost: a helper can carry everything except
29
29
  * the thing static analysis has to read.