@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
@@ -19,9 +19,8 @@ const BOX_SIZING = css `
19
19
  *
20
20
  * The UA stylesheet's `[hidden] { display: none }` is user-agent origin, so any
21
21
  * author declaration beats it, and a component whose `:host` sets a `display`
22
- * is doing exactly that. `<helix-grid hidden>` stayed visible; so did
23
- * `<helix-empty hidden>`, `<helix-flex hidden>` and fifty-four others. The
24
- * audit found three of them and the real count was fifty-seven, because every
22
+ * is doing exactly that. Fifty-seven components stayed visible while hidden,
23
+ * `<helix-grid>`, `<helix-empty>` and `<helix-flex>` among them, because every
25
24
  * component that lays itself out has the same fault and nothing connects them.
26
25
  *
27
26
  * Here rather than in each sheet, for the reason `box-sizing` is here: it is a
@@ -29,14 +28,13 @@ const BOX_SIZING = css `
29
28
  * forget the fifty-eighth.
30
29
  *
31
30
  * **The selector is (0,3,0) on purpose, and the obvious (0,2,0) is not enough.**
32
- * A bare `:host([hidden])` beats a bare `:host`, which is what the first version
33
- * of this rule assumed and it was wrong: six components set their `display` from
34
- * an attribute selector at the *same* (0,2,0) and win on source order because
35
- * they are injected after this. Measured, all five still rendered while hidden:
36
- * `flex[inline]` at `inline-flex`, `divider[orientation="vertical"]` at
37
- * `inline-block`, and `text[as]`, `checkbox[appearance="card"]` and
38
- * `radio[appearance="card"]` at `block`. The `:not()` lifts it a step and it
39
- * wins from first position, so the rule can stay prepended.
31
+ * A bare `:host([hidden])` beats a bare `:host`, but it only ties with the
32
+ * `:host([attr])` rules a component uses to set its own `display`, and those win
33
+ * on source order because they are injected after this. Measured, elements
34
+ * styled that way still rendered while hidden: `flex[inline]` at `inline-flex`,
35
+ * `divider[orientation="vertical"]` at `inline-block`, `text[as]` at `block`.
36
+ * The `:not()` lifts it a step and it wins from first position, so the rule can
37
+ * stay prepended.
40
38
  *
41
39
  * **And the `:not()` is load-bearing beyond specificity.** `hidden="until-found"`
42
40
  * is how the platform lets find-in-page reveal collapsed content: the browser
@@ -60,30 +58,25 @@ const HIDDEN = css `
60
58
  * so.** `overflow` is what creates a scrollport: without it `scrollTop` is
61
59
  * permanently zero and every scroll API on the element is inert. So a component
62
60
  * that reads `scrollTop` and cannot declare `overflow` is one whose behavior
63
- * lives in somebody else's stylesheet, and six of them do exactly that.
61
+ * lives in somebody else's stylesheet.
64
62
  *
65
63
  * Measured before deciding: `helix-headless-scroll-area`, the component whose
66
64
  * entire purpose is scrolling, computes `overflow-y: visible` on its viewport
67
- * with `scrollHeight` equal to `clientHeight`. Standing alone it is a div. The
68
- * virtual list was worse, because it *acts* on the reading: #174 is the day it
69
- * pegged a renderer measuring a box that could not scroll.
70
- *
71
- * **It goes here rather than in a stylesheet, because this is where the other
72
- * three already are.** `BOX_SIZING`, `HIDDEN` and `SETTLED` are token-free
73
- * structural rules `core` ships into every shadow root, and `HIDDEN` is the
74
- * exact same category: the rules argue `hidden` is base-tier state because the
75
- * platform acts on it, and the way the headless tier makes the platform act is a
76
- * CSS rule shipped from here. "The headless tier has no CSS" has never meant
77
- * `core` has none; it means `helix/headless` must not depend on
65
+ * with `scrollHeight` equal to `clientHeight`. Standing alone it is a div, and
66
+ * the virtual list is worse because it *acts* on the reading.
67
+ *
68
+ * **It goes here rather than in a stylesheet**, beside `BOX_SIZING`, `HIDDEN`
69
+ * and `SETTLED`, which are the token-free structural rules `core` ships into
70
+ * every shadow root. "The headless tier has no CSS" has never meant `core` has
71
+ * none; it means `helix/headless` must not depend on
78
72
  * `@fusion.dev/helix-styles`, and a rule with no token in it does not.
79
73
  *
80
74
  * **And a rule rather than an inline style, which was the first idea and is
81
75
  * wrong.** An inline `style` on a rendered part outranks every author rule
82
76
  * short of `!important`, so a consumer wanting `overflow: hidden` on that part
83
- * would have to fight it, and this repository's own notes say what that is: it
84
- * moves the fight onto consumers, who then need `!important` of their own. A
85
- * shadow rule loses to a document rule, so `::part(viewport) { overflow: … }`
86
- * from a consumer simply wins, which is the right way round.
77
+ * would have to fight it and would need `!important` of their own. A shadow rule
78
+ * loses to a document rule, so `::part(viewport) { overflow: … }` from a
79
+ * consumer simply wins, which is the right way round.
87
80
  *
88
81
  * Both axes, and nothing else. Which *kind* of scrolling, how much room,
89
82
  * `overscroll-behavior`, `scroll-snap`, a gutter, a scrollbar treatment: those
@@ -91,14 +84,13 @@ const HIDDEN = css `
91
84
  * styled sheet is prepended-after so it overrides at equal specificity anyway.
92
85
  *
93
86
  * **A component marks a box whose scroll state it reads, and no other**, which
94
- * is narrower than "every box that ends up scrolling" and is the line that took
95
- * a measurement to find. `Megamenu`'s panel scrolls, and only because the
96
- * styled tier gives it a `max-block-size`: that is a decision, the `overflow`
97
- * that serves it belongs with it, and the headless tier never asks the panel
98
- * where it is scrolled to. Marking it anyway moved a heading four pixels,
99
- * because `overflow` establishes a block formatting context and margins that
100
- * had been collapsing stopped. A mechanism with a layout consequence is one to
101
- * apply exactly where it is needed.
87
+ * is narrower than "every box that ends up scrolling". `Megamenu`'s panel
88
+ * scrolls, and only because the styled tier gives it a `max-block-size`: that is
89
+ * a decision, the `overflow` that serves it belongs with it, and the headless
90
+ * tier never asks the panel where it is scrolled to. Marking it anyway moved a
91
+ * heading four pixels, because `overflow` establishes a block formatting context
92
+ * and margins that had been collapsing stopped. A mechanism with a layout
93
+ * consequence is one to apply exactly where it is needed.
102
94
  *
103
95
  * Three qualify: `ScrollArea`, `VirtualList` and `Carousel`, each of which
104
96
  * reads `scrollTop` or `scrollLeft` and acts on the answer.
@@ -149,20 +141,13 @@ const SCROLLER = css `
149
141
  * resolved with transitions off, so enabling them changes nothing and there is
150
142
  * nothing left to animate.
151
143
  *
152
- * **`:state()` and not an attribute, and that was a bug for three routes before
153
- * it was a preference.** This was `[data-settled]`, written onto the host with
154
- * `setAttribute`, and the host is the element React owns. A definition that
155
- * lands during module evaluation has a frame to spend before React arrives, so
156
- * the element wrote its own bookkeeping onto itself and React then hydrated a
157
- * DOM that had gained an attribute since the server wrote it. Measured across
158
- * three routes at the time: `/helix` clean, `/helix/theme` one diff and
159
- * `/helix/sandbox` three, every one of them a `data-settled=""` no server sent.
160
- *
161
- * It was worked around three times, in `use-sandbox-editor.ts`, in the sandbox
162
- * barrel and again on the reference pages, each deferring a registration so the
163
- * frame fell the other side of hydration. Every one of those is a fix for the
164
- * timing rather than for the cause: the cause is that a component's private
165
- * bookkeeping was being kept somewhere React reconciles.
144
+ * **`:state()` and never an attribute.** The host is the element React owns, and
145
+ * a definition that lands during module evaluation has a frame to spend before
146
+ * React arrives, so an element writing `[data-settled]` onto its own host leaves
147
+ * React hydrating a DOM that gained an attribute since the server wrote it.
148
+ * Deferring a registration so the frame falls the other side of hydration is a
149
+ * fix for the timing rather than for the cause, which is that a component's
150
+ * private bookkeeping was being kept somewhere React reconciles.
166
151
  *
167
152
  * A custom state is not in the DOM. It carries the fact to this stylesheet and
168
153
  * nothing to the markup, so a server's output and a hydrated client's agree by
@@ -189,25 +174,25 @@ const REACT_PROPS = '__reactProps$';
189
174
  * would be a scan per keystroke. Re-scanned whenever the cached key comes back
190
175
  * empty, so a second React root with a suffix of its own still resolves.
191
176
  *
192
- * **A miss must not erase it, and it did.** The re-scan wrote its own result
193
- * back unconditionally, so any dispatch from a node React does not own found
194
- * nothing and set the key to `undefined`, and the next React-owned node paid
195
- * for a full scan again. Most elements in this library are rendered by Lit
196
- * inside a shadow root and are exactly that kind of node, so on a real page the
197
- * cache was being cleared constantly by the components least able to benefit
198
- * from it. Measured, 20,000 dispatches: 5.3ms with a warm cache against 20.3ms
199
- * alternating one owned node with one that is not, which is roughly twice the
200
- * per-dispatch cost for the half that should have been free.
177
+ * **A miss must not erase it.** A re-scan that writes its own result back
178
+ * unconditionally sets the key to `undefined` on every dispatch from a node
179
+ * React does not own, and the next React-owned node pays for a full scan again.
180
+ * Most elements in this library are rendered by Lit inside a shadow root and are
181
+ * exactly that kind of node, so the cache is cleared constantly by the
182
+ * components least able to benefit from it. Measured, 20,000 dispatches: 5.3ms
183
+ * with a warm cache against 20.3ms alternating one owned node with one that is
184
+ * not, roughly twice the per-dispatch cost for the half that should have been
185
+ * free.
201
186
  *
202
- * So a miss now leaves the cache alone and only a *different* key replaces it,
203
- * which keeps the second-React-root case the note above is about.
187
+ * So a miss leaves the cache alone and only a *different* key replaces it, which
188
+ * keeps the second-React-root case the note above is about.
204
189
  *
205
190
  * **What is left is a scan per dispatch on a page with no React at all**, since
206
191
  * there is no key to find and no honest way to conclude one will never appear:
207
192
  * a root can mount at any time, and memoizing "this node is not React's" would
208
193
  * silently disable the binding for any element that dispatches before React
209
194
  * commits, which is the exact failure this whole mechanism exists to repair.
210
- * It is around half a microsecond and it is filed rather than hidden. See #320.
195
+ * It is around half a microsecond and it is filed rather than hidden.
211
196
  */
212
197
  let reactPropsKey;
213
198
  /** The props React is holding for a node, if React is holding any. */
@@ -244,9 +229,9 @@ const REMOVED = new Set(['onActiveChange', 'onCheckedChange', 'onOpenChange', 'o
244
229
  * do reject a wrong *value* on a normal attribute. Measured with `tsc`.
245
230
  *
246
231
  * That leaves runtime as the only place a typo can be caught, and React's props
247
- * bag as the only place to look, which the binding above already reads. Once
248
- * per element, like the controlled-change audit, and for the same reason: a
249
- * warning that repeats on every keystroke is one a consumer silences.
232
+ * bag as the only place to look, which the binding above already reads. Once per
233
+ * element, because a warning that repeats on every keystroke is one a consumer
234
+ * silences.
250
235
  *
251
236
  * `on:helix-open` gets its own message because it is not a mistake so much as
252
237
  * the wrong dialect. It is what the Solid types offer, correctly, and React
@@ -284,7 +269,7 @@ const auditReactProps = (host, props) => {
284
269
  bubbling, but React binds an `on` prop by reading it as the element itself
285
270
  dispatches: one merely passing through never calls this host's own
286
271
  `dispatchEvent`, so the prop is recorded and never wired.
287
-
272
+
288
273
  Silent when nothing registered the tag, which is a component from outside
289
274
  this library or a package built before the map existed. */
290
275
  const mine = host instanceof Element ? eventsForTag(host.localName) : undefined;
@@ -353,7 +338,7 @@ const REACT_WIRES = new Set(['scroll', 'scrollend']);
353
338
  * React binds a hyphenated prop directly on the element, so our own events are
354
339
  * reached by name and are safe to bind again: same type, same callback, same
355
340
  * capture flag is a no-op by specification. A native name is different. React
356
- * recognises `onChange` and `onLoad` as its own registration names and attaches
341
+ * recognizes `onChange` and `onLoad` as its own registration names and attaches
357
342
  * nothing to the element for either; it relies on a delegated listener at the
358
343
  * root, which sees an event only if it **bubbles**, and on a non-delegated list
359
344
  * it sets up for `<video>`, `<audio>` and `<img>` and never for a custom
@@ -364,8 +349,7 @@ const REACT_WIRES = new Set(['scroll', 'scrollend']);
364
349
  * it and the consumer's handler runs twice. A non-bubbling relay arrives
365
350
  * nowhere, and is the bug this exists to close: `<helix-avatar>` relays `load`
366
351
  * and `error`, `<helix-video>` relays `ended`, `play` and `pause`, all of them
367
- * declared public API, all of them silent in React until now. An avatar
368
- * fallback is a shipped feature that did nothing.
352
+ * declared public API and all of them otherwise silent in React.
369
353
  *
370
354
  * The name is React's own casing, `onError` rather than `onerror`, because that
371
355
  * is what a consumer writes and therefore what is in the props bag. Every
@@ -422,9 +406,8 @@ const bindReactHandler = (host, event) => {
422
406
  * the element; the second is what keeps this to the properties that are actually
423
407
  * broken, rather than re-applying a string that is already correct. Three
424
408
  * qualify today, `rows` on the data table and `cell` and `compare` on a table
425
- * column, and the other ten `attribute: false` properties in the library are
426
- * booleans and strings a parent writes at runtime, which no consumer passes and
427
- * this never touches.
409
+ * column; the remaining `attribute: false` properties are booleans and strings a
410
+ * parent writes at runtime, which no consumer passes and this never touches.
428
411
  *
429
412
  * **`null` is the one place this does not reproduce a client mount, and it is
430
413
  * deliberate.** `typeof null` is `'object'`, and React assigns a null prop on a
@@ -488,13 +471,11 @@ export class HelixElement extends LitElement {
488
471
  * `helix-pagination-size` composes `helix-select`. One template says
489
472
  * `${this.tag('select')}` and each tier answers with its own name.
490
473
  *
491
- * It was the public tag in both tiers, on the argument that each imports its
492
- * own element under that name. That works and costs three things: the name
493
- * means two different things depending on what a consumer imported, the
494
- * headless tier composes an element it does not ship, and the composition
495
- * cannot be tested there at all, because there is no `helix-select` on that
496
- * tier to test it against. Thirty-nine specs had to be moved back to
497
- * `helix/styled` for exactly that reason.
474
+ * **The public tag may not be the composed one, in either tier.** Using it
475
+ * costs three things: the name means two different things depending on what a
476
+ * consumer imported, the headless tier composes an element it does not ship,
477
+ * and the composition cannot be tested there at all, because there is no
478
+ * `helix-select` on that tier to test it against.
498
479
  *
499
480
  * **A prefix rather than an override per composition**, which was the first
500
481
  * plan and does not compile. An override re-renders the fragment from the
@@ -527,25 +508,27 @@ export class HelixElement extends LitElement {
527
508
  * fact to a stylesheet through `:state()` and leaves the markup alone, so the
528
509
  * server's output and the client's agree by construction.
529
510
  *
530
- * `SETTLED` carries the long version of that argument and was the first to
531
- * move. `checked`, `open`, `invalid` and `has-label` followed it on 27 Aug
532
- * 2026, after the same fault was reported once more from the landing page's
533
- * showcase: a `default-checked` switch, a `default-checked` checkbox and a
534
- * `default-open` accordion item, each writing its own resolved state onto
535
- * itself before React reached the node.
511
+ * `SETTLED` carries the long version of that argument; `checked`, `open`,
512
+ * `invalid` and `has-label` are recorded the same way, each of them a state a
513
+ * `default-*` attribute resolves before React reaches the node.
536
514
  *
537
- * **The deferral that had been holding it is not a fix and could not be.**
538
- * The site imports the definitions from an effect so the upgrades fall the
539
- * other side of hydration, which works only while hydration is one pass:
540
- * anything that splits it, a streamed segment or a suspended boundary, puts
541
- * an upgrade back inside the window. Timing cannot be the guarantee when the
542
- * fact is kept somewhere React reconciles.
515
+ * **Deferring the definitions is not a fix and could not be.** Importing them
516
+ * from an effect so the upgrades fall the other side of hydration works only
517
+ * while hydration is one pass: anything that splits it, a streamed segment or
518
+ * a suspended boundary, puts an upgrade back inside the window. Timing cannot
519
+ * be the guarantee when the fact is kept somewhere React reconciles.
543
520
  *
544
521
  * Feature-tested rather than assumed, the way `hostAria` tests element
545
522
  * references. An engine without `CustomStateSet` records nothing and keeps
546
- * whatever the resting rule draws, which is the safe direction to fail, and
547
- * Lit's server shim is one of them: a custom state is a fact about a running
548
- * element, and a server has nothing to say about it.
523
+ * whatever the resting rule draws, which is the safe direction to fail.
524
+ *
525
+ * **Lit's server shim is no longer one of them**, and this said it was.
526
+ * `@lit-labs/ssr-dom-shim@1.6.0` sets `this.states = new Set()` in its
527
+ * `ElementInternals` constructor, so the guard passes on the server and a
528
+ * custom state is recorded where nothing reads it, which is harmless. The
529
+ * engines the test is for are browsers: Safari below 17.4 and Firefox below
530
+ * 126. There the write is a `TypeError`, so a component that bypasses this
531
+ * fails to render rather than rendering without whatever the state draws.
549
532
  */
550
533
  toggleState(name, on) {
551
534
  if ('states' in this.internals) {
@@ -643,9 +626,9 @@ export class HelixElement extends LitElement {
643
626
  * suppressing transitions.
644
627
  *
645
628
  * A frame later, not now: see `SETTLED` for why `firstUpdated` itself is too
646
- * early. SSR never calls this, which is correct, because the attribute
647
- * describes something that happened in a browser and the server has no frame
648
- * to wait for.
629
+ * early. SSR never calls this, which is correct, because the state describes
630
+ * something that happened in a browser and the server has no frame to wait
631
+ * for.
649
632
  */
650
633
  firstUpdated(changed) {
651
634
  super.firstUpdated(changed);
@@ -663,10 +646,10 @@ export class HelixElement extends LitElement {
663
646
  this.toggleState('settled', true);
664
647
  this.resolveSettled();
665
648
  /* **Audited here as well as at dispatch, and this is the half that catches
666
- the real mistake.** The audit ran only from inside `dispatchEvent`, so an
667
- element holding a handler prop and sending nothing was never looked at.
668
- A prop on the wrong element is exactly the case where that element has no
669
- reason to dispatch, so the check could not reach the thing it is for.
649
+ the real mistake.** From inside `dispatchEvent` alone, an element holding
650
+ a handler prop and sending nothing is never looked at, and a prop on the
651
+ wrong element is exactly the case where that element has no reason to
652
+ dispatch.
670
653
 
671
654
  A frame late, alongside the settled state, because React has to have
672
655
  committed its props for there to be anything to read. Once per element
@@ -30,25 +30,21 @@ export declare class HelixFormElement extends HelixElement {
30
30
  * `aria-disabled` rather than removing the element from the accessibility
31
31
  * tree, so a screen reader can still announce that the control exists.
32
32
  *
33
- * The mirror is below and was for a while only a sentence: this paragraph
34
- * described it, four components had their own copies of it, and the class
35
- * itself did nothing. Here is where it belongs, because here is where
36
- * `disabled` is.
33
+ * The mirror itself is in `willUpdate` below, here rather than in each
34
+ * control, because here is where `disabled` is.
37
35
  */
38
36
  disabled: boolean;
39
37
  /**
40
38
  * Whether an ancestor `<fieldset disabled>` has switched this control off.
41
39
  *
42
40
  * The platform tells a form-associated element through `formDisabledCallback`
43
- * and nothing here implemented it, so a control inside a disabled fieldset
44
- * stayed fully typeable and announced itself as enabled. Measured: a
45
- * `helix-input` in one accepted text, reported `disabled` as false, and
46
- * carried no `aria-disabled`.
41
+ * and nothing else does, so without this a control inside a disabled fieldset
42
+ * stays fully typeable and announces itself as enabled.
47
43
  *
48
- * What the platform already did on its own is the submission half. The same
49
- * measurement showed the fieldset's controls absent from the form's entry
50
- * list and the host matching `:disabled`, so the value never went anywhere.
51
- * What was missing is everything a reader can see or touch.
44
+ * The submission half is already the platform's: measured, the fieldset's
45
+ * controls are absent from the form's entry list and the host matches
46
+ * `:disabled`, so the value never goes anywhere. What is missing without this
47
+ * is everything a reader can see or touch.
52
48
  *
53
49
  * State rather than the `disabled` property. Writing that would reflect an
54
50
  * attribute the consumer's server markup does not have, which React reports
@@ -63,10 +59,9 @@ export declare class HelixFormElement extends HelixElement {
63
59
  * half of it the consumer wrote.
64
60
  *
65
61
  * @internal Read by the component itself and by nothing outside it: every
66
- * call in the library is `this.isDisabled`. Public, it printed a row on
62
+ * call in the library is `this.isDisabled`. Public, it prints a row on
67
63
  * twenty-four families in both tiers, forty-eight in all, on every form
68
- * control's reference page, and it is the largest single entry in those
69
- * tables.
64
+ * control's reference page.
70
65
  *
71
66
  * **A consumer loses nothing, which was measured rather than assumed.**
72
67
  * `element.matches(':disabled')` answers the same question, is the platform's
@@ -133,8 +128,7 @@ export declare class HelixFormElement extends HelixElement {
133
128
  * there. It is only a change to the markup when a consumer assigns it
134
129
  * imperatively later, and nothing is hydrating by then.
135
130
  *
136
- * Here rather than on each control, where it was declared eight times and
137
- * reflected none of them.
131
+ * Here rather than on each control.
138
132
  */
139
133
  name: string;
140
134
  /**
@@ -36,25 +36,21 @@ export class HelixFormElement extends HelixElement {
36
36
  * `aria-disabled` rather than removing the element from the accessibility
37
37
  * tree, so a screen reader can still announce that the control exists.
38
38
  *
39
- * The mirror is below and was for a while only a sentence: this paragraph
40
- * described it, four components had their own copies of it, and the class
41
- * itself did nothing. Here is where it belongs, because here is where
42
- * `disabled` is.
39
+ * The mirror itself is in `willUpdate` below, here rather than in each
40
+ * control, because here is where `disabled` is.
43
41
  */
44
42
  this.disabled = false;
45
43
  /**
46
44
  * Whether an ancestor `<fieldset disabled>` has switched this control off.
47
45
  *
48
46
  * The platform tells a form-associated element through `formDisabledCallback`
49
- * and nothing here implemented it, so a control inside a disabled fieldset
50
- * stayed fully typeable and announced itself as enabled. Measured: a
51
- * `helix-input` in one accepted text, reported `disabled` as false, and
52
- * carried no `aria-disabled`.
47
+ * and nothing else does, so without this a control inside a disabled fieldset
48
+ * stays fully typeable and announces itself as enabled.
53
49
  *
54
- * What the platform already did on its own is the submission half. The same
55
- * measurement showed the fieldset's controls absent from the form's entry
56
- * list and the host matching `:disabled`, so the value never went anywhere.
57
- * What was missing is everything a reader can see or touch.
50
+ * The submission half is already the platform's: measured, the fieldset's
51
+ * controls are absent from the form's entry list and the host matches
52
+ * `:disabled`, so the value never goes anywhere. What is missing without this
53
+ * is everything a reader can see or touch.
58
54
  *
59
55
  * State rather than the `disabled` property. Writing that would reflect an
60
56
  * attribute the consumer's server markup does not have, which React reports
@@ -78,8 +74,7 @@ export class HelixFormElement extends HelixElement {
78
74
  * there. It is only a change to the markup when a consumer assigns it
79
75
  * imperatively later, and nothing is hydrating by then.
80
76
  *
81
- * Here rather than on each control, where it was declared eight times and
82
- * reflected none of them.
77
+ * Here rather than on each control.
83
78
  */
84
79
  this.name = '';
85
80
  }
@@ -92,10 +87,9 @@ export class HelixFormElement extends HelixElement {
92
87
  * half of it the consumer wrote.
93
88
  *
94
89
  * @internal Read by the component itself and by nothing outside it: every
95
- * call in the library is `this.isDisabled`. Public, it printed a row on
90
+ * call in the library is `this.isDisabled`. Public, it prints a row on
96
91
  * twenty-four families in both tiers, forty-eight in all, on every form
97
- * control's reference page, and it is the largest single entry in those
98
- * tables.
92
+ * control's reference page.
99
93
  *
100
94
  * **A consumer loses nothing, which was measured rather than assumed.**
101
95
  * `element.matches(':disabled')` answers the same question, is the platform's
@@ -181,10 +175,10 @@ __decorate([
181
175
  property({
182
176
  /* Empty means absent, which plain reflection cannot say. `name = ''` is the
183
177
  default and Lit reflects it as `name=""`, so every button, switch and
184
- calendar in the library shipped an empty name attribute the consumer
185
- never wrote, and six SSR specs said so. Returning `null` removes the
186
- attribute instead. Same trap `hostAria` exists for: a name of no
187
- characters is not the absence of one. */
178
+ calendar in the library would ship an empty name attribute the consumer
179
+ never wrote. Returning `null` removes the attribute instead. Same trap
180
+ `hostAria` exists for: a name of no characters is not the absence of
181
+ one. */
188
182
  converter: {
189
183
  fromAttribute: (value) => value ?? '',
190
184
  toAttribute: (value) => (value === '' ? null : value),
@@ -3,13 +3,12 @@ import type { HelixElement } from '../../helix-element/helix-element.js';
3
3
  import { type Row, type Series } from '../../utils/read-series/read-series.js';
4
4
  type Constructor<T> = new (...args: any[]) => T;
5
5
  /**
6
- * How many colours the series palette has, and so where it starts over.
6
+ * How many colors the series palette has, and so where it starts over.
7
7
  *
8
- * Here rather than in each chart, which is what it was. Four families held the
9
- * same `const PALETTE = 24` and the legend held a fifth: when the palette went
10
- * from eight to twenty-four, one of them was missed and drew every ninth series
11
- * in the first color again while the legend beside it named the ninth. A
12
- * number two files have to agree on is a number that will stop agreeing.
8
+ * Here rather than in each chart, because a number two files have to agree on
9
+ * is a number that will stop agreeing: when the palette went from eight to
10
+ * twenty-four, one of five copies was missed and drew every ninth series in the
11
+ * first color while the legend beside it named the ninth.
13
12
  */
14
13
  export declare const PALETTE = 24;
15
14
  /** The palette slot for the nth series, counting from one and wrapping. */
@@ -41,8 +40,7 @@ export declare const seriesIndex: (depth: number) => number;
41
40
  *
42
41
  * Declared here because this is where `seriesIndex` and `PALETTE` live, and
43
42
  * because a hook repeated on four families is a hook four families have to keep
44
- * agreeing about, which is the mistake the `PALETTE` comment above already
45
- * records somebody making.
43
+ * agreeing about.
46
44
  *
47
45
  * The numbered twenty-four are the seam between the tiers: `chart/series.css`
48
46
  * points each at a semantic token, which is a decision and so belongs to the
@@ -118,12 +116,6 @@ export declare class MixinChartSourceInterface {
118
116
  /**
119
117
  * The half of a chart that is about finding its numbers rather than drawing.
120
118
  *
121
- * Four families had written this out separately, and the row that asked for it
122
- * set the bar: two copies is a coincidence and three is a pattern. There were
123
- * five copies of `for`, four of `label` and three of the caption fallback by the
124
- * time the line and donut charts landed, which is why this arrives now and not
125
- * when the second chart did.
126
- *
127
119
  * **A mixin rather than a controller**, by the rule the tier notes give: it
128
120
  * introduces `for` and `label`, which a consumer writes. `SourceController` is
129
121
  * the other half and stays a controller, because resolving an id and watching
@@ -9,13 +9,12 @@ import { ChartPointerController } from '../../controllers/chart-pointer-controll
9
9
  import { SourceController } from '../../controllers/source-controller/source-controller.js';
10
10
  import { readRows, readSeries } from '../../utils/read-series/read-series.js';
11
11
  /**
12
- * How many colours the series palette has, and so where it starts over.
12
+ * How many colors the series palette has, and so where it starts over.
13
13
  *
14
- * Here rather than in each chart, which is what it was. Four families held the
15
- * same `const PALETTE = 24` and the legend held a fifth: when the palette went
16
- * from eight to twenty-four, one of them was missed and drew every ninth series
17
- * in the first color again while the legend beside it named the ninth. A
18
- * number two files have to agree on is a number that will stop agreeing.
14
+ * Here rather than in each chart, because a number two files have to agree on
15
+ * is a number that will stop agreeing: when the palette went from eight to
16
+ * twenty-four, one of five copies was missed and drew every ninth series in the
17
+ * first color while the legend beside it named the ninth.
19
18
  */
20
19
  export const PALETTE = 24;
21
20
  /** The palette slot for the nth series, counting from one and wrapping. */
@@ -23,12 +22,6 @@ export const seriesIndex = (depth) => (depth % PALETTE) + 1;
23
22
  /**
24
23
  * The half of a chart that is about finding its numbers rather than drawing.
25
24
  *
26
- * Four families had written this out separately, and the row that asked for it
27
- * set the bar: two copies is a coincidence and three is a pattern. There were
28
- * five copies of `for`, four of `label` and three of the caption fallback by the
29
- * time the line and donut charts landed, which is why this arrives now and not
30
- * when the second chart did.
31
- *
32
25
  * **A mixin rather than a controller**, by the rule the tier notes give: it
33
26
  * introduces `for` and `label`, which a consumer writes. `SourceController` is
34
27
  * the other half and stays a controller, because resolving an id and watching
@@ -76,11 +69,8 @@ export const MixinChartSource = (Base) => {
76
69
  * Re-resolves when the chart is pointed at a different table.
77
70
  *
78
71
  * `SourceController` watches whatever it resolved on connect, and nothing
79
- * tells it the id changed. `BarChart` had this and `LineChart` and
80
- * `DonutChart` did not, which is the kind of difference that survives
81
- * precisely because each file looked complete on its own: pointing a bar
82
- * chart at a second table worked and pointing a line chart at one left it
83
- * drawing the first. Extracting the four found it.
72
+ * tells it the id changed. Without this, pointing a chart at a second table
73
+ * leaves it drawing the first, silently.
84
74
  */
85
75
  willUpdate(changed) {
86
76
  super.willUpdate(changed);
@@ -112,14 +102,11 @@ export const MixinChartSource = (Base) => {
112
102
  * what the table says, which is `$128k` and not `128000`, and often wants
113
103
  * the column beside it that nothing plotted at all.
114
104
  *
115
- * **Not `rows`, and the rename is the whole point of this paragraph.** It
116
- * is the source table's rows, and `rows` on a chart is a name a chart
117
- * wants for itself: `helix-heatmap` takes `rows="7"` to mean a calendar
118
- * week, and declaring that against a base class whose `rows` is a `Row[]`
119
- * getter is a type error at best. The same fault `MixinCollection` had,
120
- * where a slot counter called `rows` would have shared one field with a
121
- * consumer's data property. Name the member for what it holds rather than
122
- * for the shortest word available.
105
+ * **Not `rows`.** It is the source table's rows, and `rows` on a chart is a
106
+ * name a chart wants for itself: `helix-heatmap` takes `rows="7"` to mean a
107
+ * calendar week, and declaring that against a base class whose `rows` is a
108
+ * `Row[]` getter is a type error at best. Name the member for what it holds
109
+ * rather than for the shortest word available.
123
110
  */
124
111
  get sourceRows() {
125
112
  const element = this.source.element;
@@ -32,9 +32,7 @@ export declare class CollectionElement {
32
32
  * **Empty and loading are different answers and a reader can tell them apart.**
33
33
  * "No results" under a spinner is a lie for as long as the request is in
34
34
  * flight, and a blank box is not an answer at all: a reader cannot know whether
35
- * a list is empty, still arriving, or broken. Six components each rendered rows
36
- * and none of them said either thing, so every consumer wrote their own and
37
- * wrote it differently.
35
+ * a list is empty, still arriving, or broken.
38
36
  *
39
37
  * **`aria-busy` on the collection, not on the message.** A reader with a screen
40
38
  * reader is told the region is changing and then told once, politely, what it
@@ -46,10 +44,6 @@ export declare class CollectionElement {
46
44
  * which question it is answering, and this is the point in a component's life
47
45
  * when a consumer is most likely to be looking at stale content.
48
46
  *
49
- * Listbox had `empty-text` and nothing else, and Combobox forwarded it. Both
50
- * keep the name, so nothing a consumer wrote has moved.
51
- *
52
- *
53
47
  * **`empty` and `loading` are documented on each of the five families that
54
48
  * apply this, rather than here.** See `MixinField` for why a class-level tag on
55
49
  * a mixin reaches no manifest.