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

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
@@ -130,11 +130,10 @@ export interface TourAskDetail {
130
130
  * Composed and bubbling, because a stop is slotted into the root and the root
131
131
  * has to hear it across that boundary.
132
132
  *
133
- * **A class rather than the raw `CustomEvent` this was.** It dispatched, it
134
- * bubbled, and it was invisible: `check:events` matches `new HelixSomethingEvent(`,
135
- * so a hand-rolled `CustomEvent` was not held to being declared, and nothing
136
- * carried it into the manifest. Consumers could hear it and could not find it,
137
- * which is the trap #75 is about, in its purest form.
133
+ * **A class rather than a hand-rolled `CustomEvent`.** A dispatch is matched as
134
+ * `new HelixSomethingEvent(`, so a raw `CustomEvent` is not held to being
135
+ * declared and nothing carries it into the manifest: consumers can hear it and
136
+ * cannot find it.
138
137
  */
139
138
  export declare class HelixTourAskEvent extends CustomEvent<TourAskDetail> {
140
139
  static readonly eventName = "helix-tour-ask";
@@ -148,11 +147,9 @@ export declare class HelixTourAskEvent extends CustomEvent<TourAskDetail> {
148
147
  * way for the sample and come back afterwards, and those are two moments with
149
148
  * different work in each.
150
149
  *
151
- * **A class rather than the bare `Event` these were.** They dispatched, they
152
- * bubbled, `ColorInput` bound both, and `check:events` matched
153
- * `new CustomEvent(` and so held neither to being declared: two pieces of
154
- * public API that a consumer could hear and could not find. The check reads
155
- * `new Event(` now as well.
150
+ * **A class rather than a bare `Event`.** The check that holds an event to being
151
+ * declared reads `new Event(` as well as `new HelixSomethingEvent(`, so a
152
+ * hand-rolled one is public API a consumer can hear and cannot find.
156
153
  *
157
154
  * No detail, because there is nothing to say beyond that it happened.
158
155
  */
@@ -196,18 +193,10 @@ export declare class HelixDismissEvent extends CustomEvent<DismissDetail> {
196
193
  * whose state is a boolean wants an event with a typed detail, not a `String()`
197
194
  * call here.
198
195
  *
199
- * **There is no `property` field, and there was one until 27 Aug 2026.** It named
200
- * which property a request was about, which every verb in this file now says by
201
- * itself: `helix-before-pin` carried `'pinned'`, `helix-before-collapse` carried
202
- * `'collapsed'`, the active mixin carried `'active'`. Two families sharing a verb
203
- * are told apart by the element rather than by a field, `composedPath()[0]`
204
- * surviving the retarget that `event.target` does not.
205
- *
206
- * The one element that sent one verb for two facts was the sandbox root, whose
207
- * split and whose panel both settled through `helix-before-resize`. They have a
208
- * verb each now, `helix-before-split` and `helix-before-panel-resize`, which is
209
- * 294's argument one level down: a name says what moved, and a field that repeats
210
- * the name is eleven dispatch sites paying for one that needed it. See 306.
196
+ * **There is no `property` field.** Every verb in this file says by itself which
197
+ * property a request is about, and two families sharing a verb are told apart by
198
+ * the element rather than by a field: `composedPath()[0]` survives the retarget
199
+ * that `event.target` does not.
211
200
  */
212
201
  export interface ChangeDetail {
213
202
  /** The value after the change. */
@@ -216,35 +205,21 @@ export interface ChangeDetail {
216
205
  /**
217
206
  * A set is about to make a different child current, and can be refused.
218
207
  *
219
- * **The generic `helix-change` covered this and should not have.** Eleven
220
- * families that are not form controls announced their state under the word the
221
- * platform uses for a committed value, which is 294's whole complaint read from
222
- * the other end: `Tabs` moving to another tab, `Tree` marking another row and
223
- * `Steps` advancing are not a value changing, they are a set choosing. One word
224
- * for four questions is what `appearance` was before `check:axes`.
225
- *
226
- * `active` names which child is current, so `activate` is the verb, and unlike
227
- * `change` the platform has no claim on it.
228
- *
229
- * **It has a notification half now, and this said it did not.** *Reversed 28 Aug
230
- * 2026.* The paragraph here read "there is no notification half: a native
231
- * `change` would be a lie about what moved, and no other native event means
232
- * this. The before event is the whole channel." The first half of that is still
233
- * exactly right, which is why the pair is `helix-activate` and not `change`. The
234
- * conclusion did not follow: that no *native* word fits is an argument against
235
- * borrowing one, not an argument against having a notification at all.
236
- *
237
- * What the missing half cost is the thing 294 fixed for values and left here.
238
- * A consumer who only wants to know which child is current now had one channel,
239
- * and it fires *before* `apply`: `currentTarget.active` still holds the previous
240
- * value inside the handler, and `detail.value` is a proposal another listener may
241
- * refuse. Measured across the four families taking this mixin, nothing at all was
242
- * announced afterwards. `Tabs` and `Tree` looked covered because `helix-select`
243
- * bubbles up from a child, which fires when an item is *chosen* and not when
244
- * `active` is assigned; `Steps` and `Video Playlist` had nothing.
245
- *
246
- * So the shape is the one `MixinControllerOpen` already has, a cancelable
247
- * request and a plain notification after the state has moved. See #325.
208
+ * **Not a generic `helix-change`.** `Tabs` moving to another tab, `Tree` marking
209
+ * another row and `Steps` advancing are not a value changing, they are a set
210
+ * choosing, and the platform's word for a committed value would say the wrong
211
+ * thing. `active` names which child is current, so `activate` is the verb, and
212
+ * unlike `change` the platform has no claim on it.
213
+ *
214
+ * **This fires before the state moves.** Inside a handler `currentTarget.active`
215
+ * still holds the previous value, and `detail.value` is a proposal another
216
+ * listener may refuse, so a consumer who only wants to know which child is
217
+ * current listens for `helix-activate` instead. `helix-select` is not that
218
+ * notification either: it fires when an item is *chosen*, not when `active` is
219
+ * assigned.
220
+ *
221
+ * The shape is the one `MixinControllerOpen` already has, a cancelable request
222
+ * and a plain notification after the state has moved.
248
223
  */
249
224
  export declare class HelixBeforeActivateEvent extends CustomEvent<ChangeDetail> {
250
225
  static readonly eventName = "helix-before-activate";
@@ -264,8 +239,7 @@ export declare class HelixBeforeActivateEvent extends CustomEvent<ChangeDetail>
264
239
  * **Prefixed, and it has to be.** `activate` unprefixed is not in any
265
240
  * `*EventMap` in the DOM library, checked rather than assumed, but the prefix is
266
241
  * what stops an event dispatched inside a shadow root and retargeted to the host
267
- * being mistaken for something the host's own contents sent. That is the same
268
- * argument `helix-select` and `helix-resize` are excused under.
242
+ * being mistaken for something the host's own contents sent.
269
243
  */
270
244
  export declare class HelixActivateEvent extends CustomEvent<ChangeDetail> {
271
245
  static readonly eventName = "helix-activate";
@@ -286,7 +260,7 @@ export declare class HelixBeforeResizeEvent extends CustomEvent<ChangeDetail> {
286
260
  *
287
261
  * Its own verb rather than a `helix-before-resize` carrying which divider moved:
288
262
  * a sandbox has two, and they were told apart by a field on a detail every other
289
- * request in this file also carries. See 306.
263
+ * request in this file also carries.
290
264
  */
291
265
  export declare class HelixBeforeSplitEvent extends CustomEvent<ChangeDetail> {
292
266
  static readonly eventName = "helix-before-split";
@@ -331,13 +305,9 @@ export declare class HelixBeforeEditEvent extends CustomEvent<ChangeDetail> {
331
305
  * carries the detail because at the moment it fires the value is not on the
332
306
  * element yet.
333
307
  *
334
- * **It replaced `helix-change` and `helix-input` on the value controls**, which
335
- * were one mechanism under two names chosen by `announcesInput`. The name said
336
- * *phase* and the flag meant *granularity*, so a consumer reading "change" as
337
- * "the reader is done" wired a request to `onhelix-change` and got one per
338
- * character. Granularity is now what the platform already says it is, `input`
339
- * against `change`, and `announcesInput` picks which native one fires rather
340
- * than which name this carries. See 294.
308
+ * **`announcesInput` picks which native notification fires, not which name this
309
+ * carries.** Granularity is the platform's own distinction, `input` against
310
+ * `change`; the request is always this one event.
341
311
  */
342
312
  export declare class HelixBeforeChangeEvent extends CustomEvent<ChangeDetail> {
343
313
  static readonly eventName = "helix-before-change";
@@ -346,22 +316,20 @@ export declare class HelixBeforeChangeEvent extends CustomEvent<ChangeDetail> {
346
316
  /**
347
317
  * A checked state is about to change, and the change can be refused.
348
318
  *
349
- * **Its own event rather than a `helix-before-change` carrying a boolean**, and
350
- * the reason is recorded rather than aesthetic. Reported through the value
351
- * event the state arrived as `String(checked)`, so a consumer received the
352
- * string `"false"`, which is truthy, and this library's own checkbox group
353
- * decoded it with `detail.value === 'true'`. A test asserting the value would
354
- * have passed on that bug. A control whose state is a boolean wants an event
355
- * with a typed boolean in it.
319
+ * **Its own event rather than a `helix-before-change` carrying a boolean.**
320
+ * Through the value event the state arrives as `String(checked)`, so a consumer
321
+ * receives the string `"false"`, which is truthy, and a test asserting the value
322
+ * passes on that bug. A control whose state is a boolean wants an event with a
323
+ * typed boolean in it.
356
324
  *
357
325
  * The notification is native, as it is for a value: the control relays `change`
358
326
  * from the inner input and announces `input` beside it, so `onChange` reads
359
327
  * `currentTarget.checked` and gets a boolean from the platform.
360
328
  *
361
- * It also ends a collision. Checkbox Group listened for its boxes' change and
362
- * dispatched its own under the same name from the same host, so it had to call
363
- * `stopImmediatePropagation` to keep a consumer from receiving one box's
364
- * boolean alongside the whole set. Two names, two facts.
329
+ * It also ends a collision: Checkbox Group listened for its boxes' change and
330
+ * dispatched its own under the same name from the same host, so it needed
331
+ * `stopImmediatePropagation` to keep a consumer from receiving one box's boolean
332
+ * alongside the whole set. Two names, two facts.
365
333
  */
366
334
  export declare class HelixBeforeToggleEvent extends CustomEvent<ToggleDetail> {
367
335
  static readonly eventName = "helix-before-toggle";
@@ -464,15 +432,14 @@ export declare class HelixResizeEvent extends CustomEvent<ResizeDetail> {
464
432
  * Fired when a header crosses the threshold between resting at the top of the
465
433
  * page and floating over it.
466
434
  *
467
- * **State a consumer used to read off an attribute, said out loud instead.**
468
- * The header reflected this as `data-scrolled` on its own host, which is an
469
- * attribute nobody rendered: a server sends the element without it and the
470
- * element adds it on upgrade, so a framework hydrating the page finds markup
471
- * that no longer matches what it sent. The threshold now lives in a custom
472
- * state, which is deliberately not in the DOM and therefore not something a
435
+ * **The threshold lives in a custom state, and is announced rather than
436
+ * reflected.** A `data-scrolled` on the host is an attribute nobody rendered: a
437
+ * server sends the element without it and the element adds it on upgrade, so a
438
+ * framework hydrating the page finds markup that no longer matches what it sent.
439
+ * A custom state is deliberately not in the DOM and therefore not something a
473
440
  * hydration pass can disagree with.
474
441
  *
475
- * That closes the observability the attribute accidentally provided. A
442
+ * That closes the observability an attribute would accidentally provide. A
476
443
  * `MutationObserver` can watch an attribute and there is no equivalent for
477
444
  * `ElementInternals.states`, so a component holding a boolean nothing can read
478
445
  * is a component with a private opinion its consumers demonstrably need: the
@@ -611,8 +578,8 @@ export declare class HelixReadEvent extends CustomEvent<ReadDetail> {
611
578
  *
612
579
  * Cancelable, and refusing it leaves the order alone, which is what makes a
613
580
  * server-side sort expressible: the consumer refuses the local reorder, fetches,
614
- * and hands back a new set. In controlled mode nothing is written whatever the
615
- * answer, and this is the element reporting that a heading was clicked.
581
+ * and hands back a new set. This is the element reporting that a heading was
582
+ * clicked, before it acts on it.
616
583
  *
617
584
  * Not `helix-change`: nothing about the data changed, only the order it is read
618
585
  * in, and a consumer listening for changes to a value should not hear a sort.
@@ -669,18 +636,13 @@ export declare class HelixSelectEvent extends CustomEvent<SelectDetail> {
669
636
  /**
670
637
  * Every event this file publishes, and the one place a new one is added.
671
638
  *
672
- * The map below is derived from this rather than written beside it. Kept by
673
- * hand, the two drifted: `helix-filter` and `helix-navigate` were dispatched by
674
- * the calendar, the listbox and the pagination and were missing from the map
675
- * for weeks, so the one thing the augmentation exists to do was not done for
676
- * them and a consumer writing `addEventListener('helix-navigate', …)` got
677
- * `Event` and had to reach into `detail` through a cast. Nothing failed, which
678
- * is why two of the nine went missing.
679
- *
680
- * Derived, a class that is in here is typed and a class that is not is absent
681
- * from both halves at once, so the two cannot disagree. `check:events` fails
682
- * the build on an exported event class left out of this object, which is the
683
- * only step left that can be forgotten.
639
+ * The map below is derived from this rather than written beside it, so a class
640
+ * that is in here is typed and one that is not is absent from both halves at
641
+ * once. Kept by hand the two drifted, and nothing failed when they did: a
642
+ * consumer writing `addEventListener('helix-navigate', …)` got `Event` and had
643
+ * to reach into `detail` through a cast. The build fails on an exported event
644
+ * class left out of this object, which is the only step left that can be
645
+ * forgotten.
684
646
  */
685
647
  declare const registry: {
686
648
  readonly HelixActivateEvent: typeof HelixActivateEvent;
@@ -94,35 +94,29 @@ export class HelixPreviousEvent extends CustomEvent {
94
94
  * Composed and bubbling, because a stop is slotted into the root and the root
95
95
  * has to hear it across that boundary.
96
96
  *
97
- * **A class rather than the raw `CustomEvent` this was.** It dispatched, it
98
- * bubbled, and it was invisible: `check:events` matches `new HelixSomethingEvent(`,
99
- * so a hand-rolled `CustomEvent` was not held to being declared, and nothing
100
- * carried it into the manifest. Consumers could hear it and could not find it,
101
- * which is the trap #75 is about, in its purest form.
97
+ * **A class rather than a hand-rolled `CustomEvent`.** A dispatch is matched as
98
+ * `new HelixSomethingEvent(`, so a raw `CustomEvent` is not held to being
99
+ * declared and nothing carries it into the manifest: consumers can hear it and
100
+ * cannot find it.
102
101
  */
103
102
  export class HelixTourAskEvent extends CustomEvent {
104
103
  static { this.eventName = 'helix-tour-ask'; }
105
104
  constructor(detail) {
106
105
  super(HelixTourAskEvent.eventName, {
107
106
  bubbles: true,
108
- /* Not cancelable, and this is the one of the four where that was a real
109
- promise rather than an idle flag. The name says *ask*, so a consumer had
110
- every reason to think refusing it held the tour, and the root's
111
- `handleAsk` never read `defaultPrevented`: the tour moved anyway.
112
- Honoring it there would not have worked either, and the reason is
113
- ordering. The root registers its listener in `connectedCallback` and a
114
- consumer registers later, so on a bubbling event both are on the same
115
- target and the root's runs first: it would read `defaultPrevented`
116
- before the consumer had a chance to set it. `helix-shortcut` gets this
117
- right by reading the flag on the element that dispatched, where every
118
- listener has already run.
107
+ /* Not cancelable. The name says *ask*, so refusing it looks like it should
108
+ hold the tour, and honoring it here could not work: the root registers
109
+ its listener in `connectedCallback` and a consumer registers later, so on
110
+ a bubbling event both are on the same target and the root's runs first,
111
+ reading `defaultPrevented` before the consumer has had a chance to set
112
+ it. `helix-shortcut` gets this right by reading the flag on the element
113
+ that dispatched, where every listener has already run.
119
114
 
120
- **The veto exists and is elsewhere**, which is why this is a
121
- downgrade rather than a gap. Moving between stops goes through
115
+ **The veto exists and is elsewhere.** Moving between stops goes through
122
116
  `requestChange` and announces `helix-before-activate`; leaving early
123
117
  dispatches `helix-before-close` and reads the flag before writing
124
- `open`. Both are honored. This event is the stop telling the root a
125
- button was pressed, and the decision is taken one step later. */
118
+ `open`. This event is the stop telling the root a button was pressed,
119
+ and the decision is taken one step later. */
126
120
  cancelable: false,
127
121
  composed: true,
128
122
  detail,
@@ -137,11 +131,9 @@ export class HelixTourAskEvent extends CustomEvent {
137
131
  * way for the sample and come back afterwards, and those are two moments with
138
132
  * different work in each.
139
133
  *
140
- * **A class rather than the bare `Event` these were.** They dispatched, they
141
- * bubbled, `ColorInput` bound both, and `check:events` matched
142
- * `new CustomEvent(` and so held neither to being declared: two pieces of
143
- * public API that a consumer could hear and could not find. The check reads
144
- * `new Event(` now as well.
134
+ * **A class rather than a bare `Event`.** The check that holds an event to being
135
+ * declared reads `new Event(` as well as `new HelixSomethingEvent(`, so a
136
+ * hand-rolled one is public API a consumer can hear and cannot find.
145
137
  *
146
138
  * No detail, because there is nothing to say beyond that it happened.
147
139
  */
@@ -183,35 +175,21 @@ export class HelixDismissEvent extends CustomEvent {
183
175
  /**
184
176
  * A set is about to make a different child current, and can be refused.
185
177
  *
186
- * **The generic `helix-change` covered this and should not have.** Eleven
187
- * families that are not form controls announced their state under the word the
188
- * platform uses for a committed value, which is 294's whole complaint read from
189
- * the other end: `Tabs` moving to another tab, `Tree` marking another row and
190
- * `Steps` advancing are not a value changing, they are a set choosing. One word
191
- * for four questions is what `appearance` was before `check:axes`.
192
- *
193
- * `active` names which child is current, so `activate` is the verb, and unlike
194
- * `change` the platform has no claim on it.
195
- *
196
- * **It has a notification half now, and this said it did not.** *Reversed 28 Aug
197
- * 2026.* The paragraph here read "there is no notification half: a native
198
- * `change` would be a lie about what moved, and no other native event means
199
- * this. The before event is the whole channel." The first half of that is still
200
- * exactly right, which is why the pair is `helix-activate` and not `change`. The
201
- * conclusion did not follow: that no *native* word fits is an argument against
202
- * borrowing one, not an argument against having a notification at all.
203
- *
204
- * What the missing half cost is the thing 294 fixed for values and left here.
205
- * A consumer who only wants to know which child is current now had one channel,
206
- * and it fires *before* `apply`: `currentTarget.active` still holds the previous
207
- * value inside the handler, and `detail.value` is a proposal another listener may
208
- * refuse. Measured across the four families taking this mixin, nothing at all was
209
- * announced afterwards. `Tabs` and `Tree` looked covered because `helix-select`
210
- * bubbles up from a child, which fires when an item is *chosen* and not when
211
- * `active` is assigned; `Steps` and `Video Playlist` had nothing.
212
- *
213
- * So the shape is the one `MixinControllerOpen` already has, a cancelable
214
- * request and a plain notification after the state has moved. See #325.
178
+ * **Not a generic `helix-change`.** `Tabs` moving to another tab, `Tree` marking
179
+ * another row and `Steps` advancing are not a value changing, they are a set
180
+ * choosing, and the platform's word for a committed value would say the wrong
181
+ * thing. `active` names which child is current, so `activate` is the verb, and
182
+ * unlike `change` the platform has no claim on it.
183
+ *
184
+ * **This fires before the state moves.** Inside a handler `currentTarget.active`
185
+ * still holds the previous value, and `detail.value` is a proposal another
186
+ * listener may refuse, so a consumer who only wants to know which child is
187
+ * current listens for `helix-activate` instead. `helix-select` is not that
188
+ * notification either: it fires when an item is *chosen*, not when `active` is
189
+ * assigned.
190
+ *
191
+ * The shape is the one `MixinControllerOpen` already has, a cancelable request
192
+ * and a plain notification after the state has moved.
215
193
  */
216
194
  export class HelixBeforeActivateEvent extends CustomEvent {
217
195
  static { this.eventName = 'helix-before-activate'; }
@@ -238,8 +216,7 @@ export class HelixBeforeActivateEvent extends CustomEvent {
238
216
  * **Prefixed, and it has to be.** `activate` unprefixed is not in any
239
217
  * `*EventMap` in the DOM library, checked rather than assumed, but the prefix is
240
218
  * what stops an event dispatched inside a shadow root and retargeted to the host
241
- * being mistaken for something the host's own contents sent. That is the same
242
- * argument `helix-select` and `helix-resize` are excused under.
219
+ * being mistaken for something the host's own contents sent.
243
220
  */
244
221
  export class HelixActivateEvent extends CustomEvent {
245
222
  static { this.eventName = 'helix-activate'; }
@@ -281,7 +258,7 @@ export class HelixBeforeResizeEvent extends CustomEvent {
281
258
  *
282
259
  * Its own verb rather than a `helix-before-resize` carrying which divider moved:
283
260
  * a sandbox has two, and they were told apart by a field on a detail every other
284
- * request in this file also carries. See 306.
261
+ * request in this file also carries.
285
262
  */
286
263
  export class HelixBeforeSplitEvent extends CustomEvent {
287
264
  static { this.eventName = 'helix-before-split'; }
@@ -354,13 +331,9 @@ export class HelixBeforeEditEvent extends CustomEvent {
354
331
  * carries the detail because at the moment it fires the value is not on the
355
332
  * element yet.
356
333
  *
357
- * **It replaced `helix-change` and `helix-input` on the value controls**, which
358
- * were one mechanism under two names chosen by `announcesInput`. The name said
359
- * *phase* and the flag meant *granularity*, so a consumer reading "change" as
360
- * "the reader is done" wired a request to `onhelix-change` and got one per
361
- * character. Granularity is now what the platform already says it is, `input`
362
- * against `change`, and `announcesInput` picks which native one fires rather
363
- * than which name this carries. See 294.
334
+ * **`announcesInput` picks which native notification fires, not which name this
335
+ * carries.** Granularity is the platform's own distinction, `input` against
336
+ * `change`; the request is always this one event.
364
337
  */
365
338
  export class HelixBeforeChangeEvent extends CustomEvent {
366
339
  static { this.eventName = 'helix-before-change'; }
@@ -376,22 +349,20 @@ export class HelixBeforeChangeEvent extends CustomEvent {
376
349
  /**
377
350
  * A checked state is about to change, and the change can be refused.
378
351
  *
379
- * **Its own event rather than a `helix-before-change` carrying a boolean**, and
380
- * the reason is recorded rather than aesthetic. Reported through the value
381
- * event the state arrived as `String(checked)`, so a consumer received the
382
- * string `"false"`, which is truthy, and this library's own checkbox group
383
- * decoded it with `detail.value === 'true'`. A test asserting the value would
384
- * have passed on that bug. A control whose state is a boolean wants an event
385
- * with a typed boolean in it.
352
+ * **Its own event rather than a `helix-before-change` carrying a boolean.**
353
+ * Through the value event the state arrives as `String(checked)`, so a consumer
354
+ * receives the string `"false"`, which is truthy, and a test asserting the value
355
+ * passes on that bug. A control whose state is a boolean wants an event with a
356
+ * typed boolean in it.
386
357
  *
387
358
  * The notification is native, as it is for a value: the control relays `change`
388
359
  * from the inner input and announces `input` beside it, so `onChange` reads
389
360
  * `currentTarget.checked` and gets a boolean from the platform.
390
361
  *
391
- * It also ends a collision. Checkbox Group listened for its boxes' change and
392
- * dispatched its own under the same name from the same host, so it had to call
393
- * `stopImmediatePropagation` to keep a consumer from receiving one box's
394
- * boolean alongside the whole set. Two names, two facts.
362
+ * It also ends a collision: Checkbox Group listened for its boxes' change and
363
+ * dispatched its own under the same name from the same host, so it needed
364
+ * `stopImmediatePropagation` to keep a consumer from receiving one box's boolean
365
+ * alongside the whole set. Two names, two facts.
395
366
  */
396
367
  export class HelixBeforeToggleEvent extends CustomEvent {
397
368
  static { this.eventName = 'helix-before-toggle'; }
@@ -459,9 +430,8 @@ export class HelixConsoleEvent extends CustomEvent {
459
430
  super(HelixConsoleEvent.eventName, {
460
431
  bubbles: true,
461
432
  /* Not cancelable, for the reason `helix-resize` is not: a log line the
462
- frame already wrote is a fact being relayed, and this element's own
463
- comment at the dispatch says "relayed rather than acted on". Nothing
464
- read the result. */
433
+ frame already wrote is a fact being relayed rather than a proposal, and
434
+ nothing reads the result. */
465
435
  cancelable: false,
466
436
  composed: true,
467
437
  detail,
@@ -487,12 +457,11 @@ export class HelixResizeEvent extends CustomEvent {
487
457
  constructor(detail) {
488
458
  super(HelixResizeEvent.eventName, {
489
459
  bubbles: true,
490
- /* Not cancelable, and it was until 27 Aug 2026. A measurement is not a
491
- proposal: the content is that tall whether or not anybody wants it to
492
- be, and neither dispatcher, `Heatmap` nor `Sandbox.Preview`, ever read
493
- the result. So `preventDefault()` on it was accepted and ignored, which
494
- is worse than refusing the call: a consumer had every reason to believe
495
- they had stopped something. */
460
+ /* Not cancelable. A measurement is not a proposal: the content is that
461
+ tall whether or not anybody wants it to be, and neither dispatcher,
462
+ `Heatmap` nor `Sandbox.Preview`, reads the result. Accepting
463
+ `preventDefault()` and ignoring it is worse than refusing the call, a
464
+ consumer having every reason to believe they had stopped something. */
496
465
  cancelable: false,
497
466
  composed: true,
498
467
  detail,
@@ -503,15 +472,14 @@ export class HelixResizeEvent extends CustomEvent {
503
472
  * Fired when a header crosses the threshold between resting at the top of the
504
473
  * page and floating over it.
505
474
  *
506
- * **State a consumer used to read off an attribute, said out loud instead.**
507
- * The header reflected this as `data-scrolled` on its own host, which is an
508
- * attribute nobody rendered: a server sends the element without it and the
509
- * element adds it on upgrade, so a framework hydrating the page finds markup
510
- * that no longer matches what it sent. The threshold now lives in a custom
511
- * state, which is deliberately not in the DOM and therefore not something a
475
+ * **The threshold lives in a custom state, and is announced rather than
476
+ * reflected.** A `data-scrolled` on the host is an attribute nobody rendered: a
477
+ * server sends the element without it and the element adds it on upgrade, so a
478
+ * framework hydrating the page finds markup that no longer matches what it sent.
479
+ * A custom state is deliberately not in the DOM and therefore not something a
512
480
  * hydration pass can disagree with.
513
481
  *
514
- * That closes the observability the attribute accidentally provided. A
482
+ * That closes the observability an attribute would accidentally provide. A
515
483
  * `MutationObserver` can watch an attribute and there is no equivalent for
516
484
  * `ElementInternals.states`, so a component holding a boolean nothing can read
517
485
  * is a component with a private opinion its consumers demonstrably need: the
@@ -666,8 +634,8 @@ export class HelixReadEvent extends CustomEvent {
666
634
  *
667
635
  * Cancelable, and refusing it leaves the order alone, which is what makes a
668
636
  * server-side sort expressible: the consumer refuses the local reorder, fetches,
669
- * and hands back a new set. In controlled mode nothing is written whatever the
670
- * answer, and this is the element reporting that a heading was clicked.
637
+ * and hands back a new set. This is the element reporting that a heading was
638
+ * clicked, before it acts on it.
671
639
  *
672
640
  * Not `helix-change`: nothing about the data changed, only the order it is read
673
641
  * in, and a consumer listening for changes to a value should not hear a sort.
@@ -742,18 +710,13 @@ export class HelixSelectEvent extends CustomEvent {
742
710
  /**
743
711
  * Every event this file publishes, and the one place a new one is added.
744
712
  *
745
- * The map below is derived from this rather than written beside it. Kept by
746
- * hand, the two drifted: `helix-filter` and `helix-navigate` were dispatched by
747
- * the calendar, the listbox and the pagination and were missing from the map
748
- * for weeks, so the one thing the augmentation exists to do was not done for
749
- * them and a consumer writing `addEventListener('helix-navigate', …)` got
750
- * `Event` and had to reach into `detail` through a cast. Nothing failed, which
751
- * is why two of the nine went missing.
752
- *
753
- * Derived, a class that is in here is typed and a class that is not is absent
754
- * from both halves at once, so the two cannot disagree. `check:events` fails
755
- * the build on an exported event class left out of this object, which is the
756
- * only step left that can be forgotten.
713
+ * The map below is derived from this rather than written beside it, so a class
714
+ * that is in here is typed and one that is not is absent from both halves at
715
+ * once. Kept by hand the two drifted, and nothing failed when they did: a
716
+ * consumer writing `addEventListener('helix-navigate', …)` got `Event` and had
717
+ * to reach into `detail` through a cast. The build fails on an exported event
718
+ * class left out of this object, which is the only step left that can be
719
+ * forgotten.
757
720
  */
758
721
  const registry = {
759
722
  HelixActivateEvent,
@@ -26,13 +26,11 @@ export declare class HelixElement extends LitElement {
26
26
  * `helix-pagination-size` composes `helix-select`. One template says
27
27
  * `${this.tag('select')}` and each tier answers with its own name.
28
28
  *
29
- * It was the public tag in both tiers, on the argument that each imports its
30
- * own element under that name. That works and costs three things: the name
31
- * means two different things depending on what a consumer imported, the
32
- * headless tier composes an element it does not ship, and the composition
33
- * cannot be tested there at all, because there is no `helix-select` on that
34
- * tier to test it against. Thirty-nine specs had to be moved back to
35
- * `helix/styled` for exactly that reason.
29
+ * **The public tag may not be the composed one, in either tier.** Using it
30
+ * costs three things: the name means two different things depending on what a
31
+ * consumer imported, the headless tier composes an element it does not ship,
32
+ * and the composition cannot be tested there at all, because there is no
33
+ * `helix-select` on that tier to test it against.
36
34
  *
37
35
  * **A prefix rather than an override per composition**, which was the first
38
36
  * plan and does not compile. An override re-renders the fragment from the
@@ -76,25 +74,27 @@ export declare class HelixElement extends LitElement {
76
74
  * fact to a stylesheet through `:state()` and leaves the markup alone, so the
77
75
  * server's output and the client's agree by construction.
78
76
  *
79
- * `SETTLED` carries the long version of that argument and was the first to
80
- * move. `checked`, `open`, `invalid` and `has-label` followed it on 27 Aug
81
- * 2026, after the same fault was reported once more from the landing page's
82
- * showcase: a `default-checked` switch, a `default-checked` checkbox and a
83
- * `default-open` accordion item, each writing its own resolved state onto
84
- * itself before React reached the node.
77
+ * `SETTLED` carries the long version of that argument; `checked`, `open`,
78
+ * `invalid` and `has-label` are recorded the same way, each of them a state a
79
+ * `default-*` attribute resolves before React reaches the node.
85
80
  *
86
- * **The deferral that had been holding it is not a fix and could not be.**
87
- * The site imports the definitions from an effect so the upgrades fall the
88
- * other side of hydration, which works only while hydration is one pass:
89
- * anything that splits it, a streamed segment or a suspended boundary, puts
90
- * an upgrade back inside the window. Timing cannot be the guarantee when the
91
- * fact is kept somewhere React reconciles.
81
+ * **Deferring the definitions is not a fix and could not be.** Importing them
82
+ * from an effect so the upgrades fall the other side of hydration works only
83
+ * while hydration is one pass: anything that splits it, a streamed segment or
84
+ * a suspended boundary, puts an upgrade back inside the window. Timing cannot
85
+ * be the guarantee when the fact is kept somewhere React reconciles.
92
86
  *
93
87
  * Feature-tested rather than assumed, the way `hostAria` tests element
94
88
  * references. An engine without `CustomStateSet` records nothing and keeps
95
- * whatever the resting rule draws, which is the safe direction to fail, and
96
- * Lit's server shim is one of them: a custom state is a fact about a running
97
- * element, and a server has nothing to say about it.
89
+ * whatever the resting rule draws, which is the safe direction to fail.
90
+ *
91
+ * **Lit's server shim is no longer one of them**, and this said it was.
92
+ * `@lit-labs/ssr-dom-shim@1.6.0` sets `this.states = new Set()` in its
93
+ * `ElementInternals` constructor, so the guard passes on the server and a
94
+ * custom state is recorded where nothing reads it, which is harmless. The
95
+ * engines the test is for are browsers: Safari below 17.4 and Firefox below
96
+ * 126. There the write is a `TypeError`, so a component that bypasses this
97
+ * fails to render rather than rendering without whatever the state draws.
98
98
  */
99
99
  protected toggleState(name: string, on: boolean): void;
100
100
  /**
@@ -116,8 +116,7 @@ export declare class HelixElement extends LitElement {
116
116
  *
117
117
  * Separate from `updateComplete`, which resolves in the microtask checkpoint
118
118
  * the suppression is still active in. That is the distinction `readyToMove`
119
- * in `@fusion.dev/helix-testing` was written around and the reason two checkbox specs
120
- * were misread as a token fault for days.
119
+ * in `@fusion.dev/helix-testing` is written around.
121
120
  *
122
121
  * It never resolves where `requestAnimationFrame` never fires, which is a
123
122
  * server, a hidden tab, and happy-dom. Callers that must not hang race it
@@ -193,9 +192,9 @@ export declare class HelixElement extends LitElement {
193
192
  * suppressing transitions.
194
193
  *
195
194
  * A frame later, not now: see `SETTLED` for why `firstUpdated` itself is too
196
- * early. SSR never calls this, which is correct, because the attribute
197
- * describes something that happened in a browser and the server has no frame
198
- * to wait for.
195
+ * early. SSR never calls this, which is correct, because the state describes
196
+ * something that happened in a browser and the server has no frame to wait
197
+ * for.
199
198
  */
200
199
  protected firstUpdated(changed: PropertyValues): void;
201
200
  }