@fusion.dev/helix-headless 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/custom-elements.json +234 -234
  2. package/dist/accordion/item/accordion-item.js +0 -108
  3. package/dist/accordion/root/accordion-root.js +0 -33
  4. package/dist/announcement/root/announcement-root.js +1 -76
  5. package/dist/app-header/root/app-header-root.js +0 -84
  6. package/dist/avatar/group/avatar-group.js +0 -77
  7. package/dist/avatar/root/avatar-root.js +0 -72
  8. package/dist/badge/root/badge-root.js +0 -67
  9. package/dist/bar-chart/root/bar-chart-root.js +3 -263
  10. package/dist/blockquote/root/blockquote-root.js +0 -46
  11. package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
  12. package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
  13. package/dist/button/root/button-root.js +0 -215
  14. package/dist/button-group/root/button-group-root.js +0 -30
  15. package/dist/callout/root/callout-root.js +0 -52
  16. package/dist/card/root/card-root.js +0 -41
  17. package/dist/carousel/root/carousel-root.d.ts +2 -2
  18. package/dist/carousel/root/carousel-root.js +2 -362
  19. package/dist/chat/composer/chat-composer.js +0 -131
  20. package/dist/chat/message/chat-message.js +0 -137
  21. package/dist/chat/root/chat-root.js +0 -193
  22. package/dist/checkbox/root/checkbox-root.js +0 -113
  23. package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
  24. package/dist/code/root/code-root.js +0 -26
  25. package/dist/code-block/root/code-block-root.js +0 -100
  26. package/dist/color-input/root/color-input-root.js +0 -217
  27. package/dist/color-picker/color.js +0 -69
  28. package/dist/color-picker/root/color-picker-root.js +0 -272
  29. package/dist/combobox/root/combobox-root.js +0 -428
  30. package/dist/command/root/command-root.js +0 -177
  31. package/dist/copy-button/root/copy-button-root.js +0 -134
  32. package/dist/data-table/root/data-table-root.js +0 -336
  33. package/dist/data-table/root/order.js +0 -100
  34. package/dist/date-input/root/date-input-root.js +1 -398
  35. package/dist/date-picker/root/date-picker-root.js +0 -358
  36. package/dist/dialog/root/dialog-root.js +0 -189
  37. package/dist/divider/root/divider-root.js +0 -71
  38. package/dist/donut-chart/root/donut-chart-root.js +0 -214
  39. package/dist/drawer/root/drawer-root.js +0 -116
  40. package/dist/drilldown/item/drilldown-item.js +0 -82
  41. package/dist/drilldown/root/drilldown-root.js +0 -91
  42. package/dist/element-events.generated.js +0 -5
  43. package/dist/empty/root/empty-root.js +0 -35
  44. package/dist/file-input/root/file-input-root.js +0 -232
  45. package/dist/format-date/root/format-date-root.js +0 -95
  46. package/dist/heatmap/root/buckets.js +0 -57
  47. package/dist/heatmap/root/heatmap-root.js +0 -313
  48. package/dist/icon/root/icon-root.js +0 -48
  49. package/dist/icon-button/root/icon-button-root.js +0 -30
  50. package/dist/image/root/image-root.js +0 -174
  51. package/dist/index.js +0 -9
  52. package/dist/input/root/input-root.js +0 -228
  53. package/dist/kbd/root/kbd-root.js +0 -117
  54. package/dist/legend/root/legend-root.js +0 -73
  55. package/dist/line-chart/root/line-chart-root.js +0 -172
  56. package/dist/list/item/list-item.js +0 -92
  57. package/dist/list/root/list-root.js +0 -26
  58. package/dist/listbox/root/listbox-root.js +2 -429
  59. package/dist/loader/root/loader-root.js +0 -40
  60. package/dist/megamenu/root/megamenu-root.js +0 -284
  61. package/dist/menu/item/menu-item.js +1 -221
  62. package/dist/menu/root/menu-root.js +1 -171
  63. package/dist/meter/root/meter-root.js +1 -152
  64. package/dist/navigation/root/navigation-root.js +0 -56
  65. package/dist/notifications/notification/notification.js +0 -159
  66. package/dist/notifications/root/notifications-root.js +0 -148
  67. package/dist/number-input/root/number-input-root.js +1 -263
  68. package/dist/option/group/option-group.js +0 -26
  69. package/dist/option/root/option-root.js +0 -157
  70. package/dist/outline/root/outline-root.js +0 -292
  71. package/dist/pagination/root/pagination-root.js +0 -361
  72. package/dist/pagination/size/pagination-size.js +0 -104
  73. package/dist/pagination/summary/pagination-summary.js +0 -90
  74. package/dist/pin-input/root/pin-input-root.js +0 -276
  75. package/dist/popover/root/popover-root.js +0 -103
  76. package/dist/progress/root/progress-root.js +1 -133
  77. package/dist/qr-code/root/qr-code-root.js +0 -96
  78. package/dist/quote/root/quote-root.js +0 -25
  79. package/dist/radio/group/radio-group.js +0 -81
  80. package/dist/radio/root/radio-root.js +0 -85
  81. package/dist/rating/root/rating-root.js +0 -220
  82. package/dist/scroll-area/root/scroll-area-root.js +0 -100
  83. package/dist/select/root/select-root.js +2 -218
  84. package/dist/skeleton/root/skeleton-root.js +0 -20
  85. package/dist/slider/root/slider-root.js +1 -267
  86. package/dist/sparkline/root/sparkline-root.js +3 -164
  87. package/dist/speed-dial/item/speed-dial-item.js +0 -52
  88. package/dist/speed-dial/root/speed-dial-root.js +1 -116
  89. package/dist/splitter/root/splitter-root.js +0 -489
  90. package/dist/stat/group/stat-group.js +0 -18
  91. package/dist/stat/root/stat-root.js +0 -23
  92. package/dist/steps/root/steps-root.js +0 -62
  93. package/dist/steps/step/step.js +0 -131
  94. package/dist/switch/root/switch-root.js +0 -80
  95. package/dist/table/cell/table-cell.js +0 -23
  96. package/dist/table/column/table-column.js +0 -158
  97. package/dist/table/root/table-root.js +0 -82
  98. package/dist/table/row/table-row.js +0 -21
  99. package/dist/tabs/panel/tab-panel.js +0 -49
  100. package/dist/tabs/root/tabs-root.js +0 -98
  101. package/dist/tabs/tab/tab.js +0 -59
  102. package/dist/tag/root/tag-root.js +0 -31
  103. package/dist/textarea/root/textarea-root.js +0 -162
  104. package/dist/time-input/root/time-input-root.js +0 -283
  105. package/dist/time-picker/root/time-picker-root.js +0 -326
  106. package/dist/timeline/item/timeline-item.js +0 -66
  107. package/dist/timeline/root/timeline-root.js +0 -41
  108. package/dist/toast/item/toast-item.js +0 -165
  109. package/dist/toast/root/toast-root.js +0 -51
  110. package/dist/toggle/group/toggle-group.js +0 -150
  111. package/dist/toggle/root/toggle-root.js +0 -78
  112. package/dist/toolbar/root/toolbar-root.js +0 -116
  113. package/dist/tooltip/root/tooltip-root.js +0 -427
  114. package/dist/tour/root/tour-root.js +0 -130
  115. package/dist/tour/stop/tour-stop.js +1 -127
  116. package/dist/tree/item/tree-item.js +0 -198
  117. package/dist/tree/root/tree-root.js +0 -97
  118. package/dist/trend/root/trend-root.js +0 -53
  119. package/dist/video/root/video-root.d.ts +8 -12
  120. package/dist/video/root/video-root.js +5 -756
  121. package/dist/video-playlist/item/video-playlist-item.js +1 -90
  122. package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
  123. package/dist/video-playlist/root/video-playlist-root.js +0 -113
  124. package/dist/virtual-list/root/virtual-list-root.js +0 -105
  125. package/jsx.d.ts +1408 -2576
  126. package/package.json +6 -6
  127. package/vue.d.ts +1 -1
@@ -7,119 +7,34 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement, HelixSelectEvent } from '@fusion.dev/helix-core';
8
8
  import { html } from 'lit';
9
9
  import { property } from 'lit/decorators.js';
10
- /**
11
- * @summary One choice in a radio group.
12
- *
13
- * @description
14
- * Renders a `<button role="radio">`, not an `<input type="radio">`, and the
15
- * reason is the shadow boundary rather than preference: native radios group by
16
- * sharing a `name` within a form, and a radio inside a shadow root cannot join
17
- * a group on the other side of it. The group is the form control here, so the
18
- * option only has to say what it is and announce when it is chosen.
19
- *
20
- * **It carries no label of its own.** A radio is named by the words beside it,
21
- * which is its own content, and the label, hint and message of the *set* belong
22
- * to the group. This is the one control in the family that takes nothing from
23
- * `MixinField`, because a lone radio is not a control: it is an answer to one.
24
- *
25
- * Selection is announced rather than applied. The item dispatches, the group
26
- * decides, and the group writes `selected` back. An item that set itself would
27
- * be a second source of truth for which one is on.
28
- *
29
- * @since 0.1.0
30
- * @status experimental
31
- * @category forms
32
- * @aliases Option, Choice
33
- * @documentation https://helix.fusion.dev/components/radio
34
- *
35
- * @slot - The words naming this choice.
36
- * @slot icon - A mark shown beside them, or instead of them under
37
- * `appearance="chip"`, where the words become the accessible name and the mark
38
- * is what a reader picks between. Separate from the default slot because the
39
- * chip has to hide one and show the other, and they are both in the same place
40
- * otherwise.
41
- *
42
- * @event {HelixSelectEvent} helix-select - Fired when this choice is made. Cancelable: refusing it takes the decision and nothing moves.
43
- *
44
- * @csspart box - The circle.
45
- * @csspart item - The button, which is the whole row.
46
- * @csspart label - The words beside it.
47
- * @csspart mark - The dot inside it.
48
- */
49
10
  let HelixHeadlessRadio = class HelixHeadlessRadio extends HelixElement {
50
11
  constructor() {
51
12
  super(...arguments);
52
- /**
53
- * Whether this choice can be made.
54
- */
55
13
  this.disabled = false;
56
- /** What the markup said, read once at construction for the reason above. */
57
14
  this.seeded = this.hasAttribute('selected');
58
- /**
59
- * Whether this is the current choice.
60
- *
61
- * Written by the group.
62
- */
63
15
  this.selected = false;
64
- /**
65
- * Identifies this choice to the group, and is what the form submits.
66
- */
67
16
  this.value = '';
68
17
  }
69
- /**
70
- * Whether the markup declared this one chosen, which is what a reset returns to.
71
- *
72
- * `<option selected>` reflects to `defaultSelected` and `option.selected` is
73
- * live state that does not reflect, and those are two different facts. This
74
- * element has one `selected` doing both jobs, so what is captured here is what
75
- * the markup said at upgrade, before anything could overwrite it: read the
76
- * attribute later and the listbox's own writes answer instead.
77
- *
78
- * **The deeper half is not this.** State reflecting under the declaration's
79
- * name is the fault `TreeItem` avoids by mirroring to `data-selected`.
80
- * Splitting them means moving what every stylesheet selects on, which is a
81
- * change of its own rather than a property being added.
82
- */
83
18
  get defaultSelected() {
84
19
  return this.seeded;
85
20
  }
86
21
  set defaultSelected(next) {
87
22
  this.seeded = next;
88
23
  }
89
- /** Moves focus to this choice, for the group's arrow keys. */
90
24
  focusItem() {
91
25
  this.renderRoot.querySelector('[part="item"]')?.focus();
92
26
  }
93
- /** Makes this choice and announces it. */
94
27
  select() {
95
28
  if (this.disabled) {
96
29
  return;
97
30
  }
98
31
  this.dispatchEvent(new HelixSelectEvent({ value: this.value }));
99
32
  }
100
- /**
101
- * Mirrors the chosen state where a stylesheet can select on it.
102
- *
103
- * **A custom state rather than the `selected` attribute.** The parent writes
104
- * this: a listbox choosing a row, a tab strip moving the tab, a radio group
105
- * answering for its set. Reflected, that puts the element's own bookkeeping
106
- * onto markup a consumer wrote and a consumer's server never sent, which
107
- * React reconciles and reports as a mismatch, and which a serialized element
108
- * pasted back declares as a choice nobody made. `ElementInternals` holds it
109
- * instead, where it is not in the DOM at all.
110
- *
111
- * Reading `selected` is unaffected and so is writing it: the attribute still
112
- * seeds the property, which is what `<helix-radio selected>` in a consumer's
113
- * markup means. There is no write back.
114
- */
115
33
  willUpdate(changed) {
116
34
  super.willUpdate(changed);
117
35
  this.toggleState('selected', this.selected);
118
36
  }
119
37
  render() {
120
- /* Roving `tabindex`: one stop for the whole set, which is what a radio
121
- group is in the tab order, and the arrow keys move within it. The group
122
- owns the keys, because moving between items is a fact about the set. */
123
38
  return html `<button
124
39
  part="item"
125
40
  type="button"
@@ -7,188 +7,29 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { valueAsNumber as asNumber, customElement, numberAsValue as fromNumber, HelixFormElement, KeyboardNavigationController, MixinControllerValue, MixinField, } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
- /**
11
- * @summary A score out of a few marks, given or shown.
12
- *
13
- * @description
14
- * **Half marks decide the shape.** Supporting them means the value is not an
15
- * integer and the targets are not the marks: five stars at a half step is ten
16
- * places to press, and a mark that is filled to the middle. The pointer handling
17
- * and `step` are both built around that rather than added over it.
18
- *
19
- * **A partial mark is the whole mark, clipped.** Not a second glyph filled to
20
- * the middle: that needs a matching pair drawn for every mark anybody uses, and
21
- * the moment a consumer wants a heart or a circle they need to draw one too. The
22
- * fill is the same marks over the same marks, cut off partway, so any mark works
23
- * and any fraction does.
24
- *
25
- * **Read-only is not this control grayed out, it is a different thing.** The
26
- * commoner use is showing a score nobody is being asked for, a product's
27
- * average, and that is not a control at all: it takes no focus, answers no keys,
28
- * has no options to choose between, and reads as one thing rather than as five.
29
- * So `readonly` is `role="img"` with the score in its name, where the control is
30
- * a radio group. A disabled control is still a control and still says so; this
31
- * is not one.
32
- *
33
- * **Moving is choosing here**, which is the radio group's reading and the
34
- * opposite of `Menu`'s: arriving at four stars *is* rating it four, because
35
- * nothing else follows and there is nothing to confirm. That is what
36
- * `KeyboardNavigationController` takes `selects` for, and why this can use it
37
- * where a menu cannot.
38
- *
39
- * **The mark is a stylesheet's, not a slot's.** A slot projects its content
40
- * once, and there are two layers of marks here: slotting a star would put it in
41
- * the track or the fill and leave the other empty. So the mark is a shape the
42
- * styled tier masks with, which is also what lets a partial one be a clip of the
43
- * whole rather than a second drawing of it.
44
- *
45
- * The properties below are written by this component and read by whoever draws
46
- * it. A consumer sets none of them: they carry a number this element computed
47
- * and CSS cannot, which is what lets a stylesheet on this tier draw the thing
48
- * without measuring anything itself.
49
- *
50
- * @since 0.1.0
51
- * @status experimental
52
- * @category forms
53
- * @aliases Stars, Score, Review score
54
- * @documentation https://helix.fusion.dev/components/rating
55
- *
56
- * @event {Event} change - Announced with it, the choice being the commit.
57
- * @event {HelixBeforeChangeEvent} helix-before-change - Fired before the score changes, and cancelable.
58
- * @event {Event} input - Announced once the value has moved.
59
- *
60
- * @csspart feedback - What is wrong with it.
61
- * @csspart fill - The filled marks, clipped to the score.
62
- * @csspart hint - Help under the marks, while nothing is wrong.
63
- * @csspart label - The question, over the marks.
64
- * @csspart mark - One mark, in either layer.
65
- * @csspart marks - The row of marks.
66
- * @csspart option - One place a reader can press, which is one step.
67
- * @csspart track - The unfilled marks.
68
- * @csspart value - The score written out, under `show-value`.
69
- *
70
- * @cssproperty --rating-full - How many marks are wholly filled.
71
- * @cssproperty --rating-part - How much of the next mark is filled, 0 to 1.
72
- * @cssproperty --rating-step - The width of one step, as a fraction of a mark.
73
- * @cssproperty --rating-steps - How many steps one mark is divided into.
74
- */
75
10
  let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue(MixinField(HelixFormElement)) {
76
11
  constructor() {
77
12
  super(...arguments);
78
- /*
79
- * Discrete. `Input`, `Slider`, `NumberInput`, `Textarea` and `PinInput` all
80
- * announce input, and every one of them is edited continuously: a value passes
81
- * through every keystroke or pixel on its way to the one meant. A rating does
82
- * not. A press picks a score, an arrow picks the next one, and the drag
83
- * deliberately asks for nothing until it is let go, so there is exactly one
84
- * value and it is the chosen one. That is `helix-before-change`, the same as
85
- * the radio group this announces itself as.
86
- */
87
13
  this.announcesInput = false;
88
- /**
89
- * How many marks there are.
90
- */
91
14
  this.max = 5;
92
- /**
93
- * The lowest score a reader can give, which is what clearing returns to.
94
- *
95
- * Zero rather than one, because "no rating" and "the worst rating" are
96
- * different answers and a control that cannot express the first has to
97
- * pretend the second. A survey that requires an answer says so with
98
- * `required`, not by making one unreachable.
99
- */
100
15
  this.min = 0;
101
- /**
102
- * Whether this shows a score rather than asks for one.
103
- *
104
- * A readout is not a control that has been switched off. It takes no focus,
105
- * answers no keys, and reads as one thing: see the note on the class.
106
- */
107
16
  this.readOnly = false;
108
- /**
109
- * Whether the score is written out beside the marks.
110
- *
111
- * Marks say roughly, and a number says exactly. Four and a bit stars is not a
112
- * reading anyone can repeat, which is why a product's average is nearly always
113
- * printed next to them. `Meter` spells its reading the same way and under the
114
- * same attribute.
115
- *
116
- * It follows the pointer, because it labels what the marks are showing rather
117
- * than what the form holds: a label that disagreed with the marks under the
118
- * reader's own cursor would be worse than no label.
119
- */
120
17
  this.showValue = false;
121
- /**
122
- * The smallest part of a mark that can be chosen.
123
- *
124
- * `0.5` is the half star, and it is the reason the value is a number rather
125
- * than an index: with halves, five marks are ten answers.
126
- */
127
18
  this.step = 1;
128
- /**
129
- * How the score is read out, where "3.5 out of 5" is not what it should say.
130
- *
131
- * A rating out of five is one of the few places a screen reader gets a worse
132
- * answer from the value than from a sentence: "3.5" alone is a number without
133
- * a subject. This names the readout, where a control is named by its `label`
134
- * and each of its options by the score that option is.
135
- */
136
19
  this.valueText = '';
137
- /**
138
- * The shape a consumer slotted, if they slotted one.
139
- *
140
- * Read rather than projected. A `<slot>` shows its content once and a rating
141
- * needs the shape as many times as there are marks, twice over because there
142
- * are two layers, so the slot here is a way of being handed the shape rather
143
- * than a place the shape is drawn. What is handed over gets cloned into every
144
- * position.
145
- *
146
- * **One shape, not one per position.** A rating is a quantity of identical
147
- * marks and the fill is a clip across all of them, so a row whose third mark
148
- * is a different drawing from its first is not a rating: it is a set of
149
- * choices, which is `Radio.Group` and already carries a value and a name on
150
- * each of its own options. Anything past the first slotted element is
151
- * ignored.
152
- */
153
20
  this.markSource = undefined;
154
- /**
155
- * What the pointer is over, which is shown instead of the score while it is.
156
- */
157
21
  this.previewing = undefined;
158
- /* The radio group's reading, and `selects` is what makes it one: arriving at
159
- a mark rates it. `Menu` passes false to the same option for the opposite
160
- reason, which is the whole of the difference between a set of choices and a
161
- set of commands. */
162
22
  this.keys = new KeyboardNavigationController(this, {
163
23
  items: () => this.options,
164
24
  selects: () => true,
165
25
  wrap: false,
166
26
  });
167
- /** The pointer drawing a score, while one is. */
168
27
  this.dragging = undefined;
169
- /**
170
- * One clone per place a mark goes, kept so a render does not remake them.
171
- *
172
- * A drag renders on every pointer move, and handing Lit a fresh node each
173
- * time would swap every mark in the row on every frame of a gesture whose
174
- * whole point is that only the fill moves.
175
- */
176
28
  this.clones = new Map();
177
29
  this.handleMarkSlot = (event) => {
178
30
  this.clones.clear();
179
31
  this.markSource = event.target.assignedElements({ flatten: true })[0];
180
32
  };
181
- /**
182
- * Dragging across the marks draws a score, and letting go chooses it.
183
- *
184
- * One press and a slide is how a rating is given on a phone, and on a desktop
185
- * it is how somebody who overshoots corrects without lifting. Pointer capture
186
- * is what makes it one gesture: without it the events go to whichever target
187
- * the pointer crossed into, and a drag that leaves the row stops reporting.
188
- *
189
- * Nothing is chosen until release, so a drag that ends back where it started,
190
- * or off the element entirely, has asked for nothing.
191
- */
192
33
  this.handleDragStart = (event) => {
193
34
  if (this.isDisabled || this.readOnly || event.button !== 0) {
194
35
  return;
@@ -202,9 +43,6 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
202
43
  event.currentTarget.setPointerCapture(event.pointerId);
203
44
  }
204
45
  catch {
205
- /* A synthesized pointer has no capture to take, and the drag works
206
- without it right up until the pointer leaves the row. Worth having the
207
- gesture testable rather than making the specs drive a real device. */
208
46
  }
209
47
  this.dragging = event.pointerId;
210
48
  this.previewing = Number.parseFloat(score);
@@ -229,8 +67,6 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
229
67
  this.requestValue(score);
230
68
  }
231
69
  };
232
- /* A canceled pointer is the system taking the gesture away, which is not the
233
- reader choosing. Put the marks back to the value they already held. */
234
70
  this.handleDragCancel = (event) => {
235
71
  if (this.dragging !== event.pointerId) {
236
72
  return;
@@ -253,7 +89,6 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
253
89
  this.previewing = undefined;
254
90
  };
255
91
  }
256
- /** The score, as a number, clamped to what the marks can show. */
257
92
  get score() {
258
93
  const parsed = Number.parseFloat(this.value);
259
94
  if (!Number.isFinite(parsed)) {
@@ -261,19 +96,10 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
261
96
  }
262
97
  return Math.min(Math.max(parsed, 0), this.max);
263
98
  }
264
- /** Every score a reader can choose, which is `max / step` of them. */
265
99
  get steps() {
266
100
  const count = Math.max(1, Math.round(this.max / this.step));
267
101
  return Array.from({ length: count }, (_, index) => Number(((index + 1) * this.step).toFixed(2)));
268
102
  }
269
- /**
270
- * The options, as the shared controller wants them.
271
- *
272
- * Adapters rather than elements, because these are buttons this element drew
273
- * rather than a consumer's custom elements: the interface asks for five
274
- * things and a `<button>` in a shadow root can answer all five without being
275
- * dressed up as a component to do it.
276
- */
277
103
  get options() {
278
104
  const found = [...(this.renderRoot?.querySelectorAll('[part="option"]') ?? [])];
279
105
  return found.map((element) => ({
@@ -286,7 +112,6 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
286
112
  selected: element.getAttribute('aria-checked') === 'true',
287
113
  }));
288
114
  }
289
- /** The shape for one place, or nothing when the stylesheet is drawing it. */
290
115
  markFor(layer, index) {
291
116
  if (!this.markSource) {
292
117
  return undefined;
@@ -300,18 +125,6 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
300
125
  this.clones.set(key, clone);
301
126
  return clone;
302
127
  }
303
- /**
304
- * The step under a point on the row, or the nearest one to it.
305
- *
306
- * Asked of the boxes rather than worked out from `max` and `step`, because
307
- * where a step sits is the stylesheet's arithmetic and this would be a second
308
- * copy of it that could disagree. The browser has already laid the targets
309
- * out; this reads them back.
310
- *
311
- * Nearest rather than only a hit, so a drag that runs off the end of the row
312
- * pins to the last mark instead of stopping at whatever it last touched, and
313
- * a pointer in the half-gap outside the first mark still answers.
314
- */
315
128
  scoreAt(clientX) {
316
129
  let nearest;
317
130
  let best = Number.POSITIVE_INFINITY;
@@ -328,23 +141,6 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
328
141
  }
329
142
  return nearest;
330
143
  }
331
- /**
332
- * Reports the value, and whether the form may go without one.
333
- *
334
- * **`required` arrives from the mixin, so it prints on the reference page
335
- * whether or not anything here enforces it.** Without a `setValidity` call a
336
- * form holding an unrated required rating submits, `:valid` true and the
337
- * entry present but empty. `reportRequired` is what makes the property real.
338
- *
339
- * **Empty is `value === ''` rather than a score of zero**, because zero is a
340
- * rating a reader can deliberately give when `min` allows it, and refusing to
341
- * submit it would be the control disagreeing with the property that permits
342
- * it. What `required` asks is whether they answered at all.
343
- *
344
- * `Slider` is deliberately not given the same treatment: a native
345
- * `<input type="range" required>` is always valid, because a range always
346
- * holds a number, and matching the platform there is the point.
347
- */
348
144
  sync() {
349
145
  this.internals.setFormValue(this.value);
350
146
  this.reportRequired(this.value === '', 'Choose a rating to continue.', 'marks');
@@ -359,8 +155,6 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
359
155
  }
360
156
  updated(changed) {
361
157
  super.updated?.(changed);
362
- // Every update, not only the ones that name `value`: see the note on
363
- // `ColorInput.updated`.
364
158
  this.sync();
365
159
  }
366
160
  firstUpdated(changed) {
@@ -373,13 +167,6 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
373
167
  const count = Math.max(0, Math.round(this.max));
374
168
  const first = this.steps[0];
375
169
  const mark = (layer, index) => html `<span part="mark">${this.markFor(layer, index)}</span>`;
376
- /* Both layers hold every mark, because the fill is a clip of the whole row
377
- rather than a count of the filled ones.
378
-
379
- Written out twice rather than through one helper taking the part name:
380
- a part is read from the source as a literal, and a `part=${name}`
381
- binding is invisible. The stylesheet's `[part="track"]` and
382
- `[part="fill"]` read as dead. */
383
170
  const marksIn = (layer) => Array.from({ length: count }, (_, index) => mark(layer, index));
384
171
  return html `<div
385
172
  part="marks"
@@ -433,13 +220,6 @@ let HelixHeadlessRating = class HelixHeadlessRating extends MixinControllerValue
433
220
  >`
434
221
  : nothing}`;
435
222
  }
436
- /**
437
- * The value as a number, or `NaN` while the control is empty.
438
- *
439
- * `NaN` and not zero, which is the trap the platform already avoids: `Number('')`
440
- * is zero, so an empty control would report a number it does not hold and a
441
- * form would submit it.
442
- */
443
223
  get valueAsNumber() {
444
224
  return asNumber(this.value);
445
225
  }
@@ -7,69 +7,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { customElement, HelixElement } from '@fusion.dev/helix-core';
8
8
  import { html, nothing } from 'lit';
9
9
  import { property, state } from 'lit/decorators.js';
10
- /** Which edges have something past them, as a set of tokens. */
11
10
  const EDGES = ['block-start', 'block-end', 'inline-start', 'inline-end'];
12
- /**
13
- * @summary A scrolling box that says there is more.
14
- *
15
- * @description
16
- * **The whole component is one fact a stylesheet cannot work out for itself:
17
- * which edges have content past them.** A fade at the bottom of a list is easy
18
- * to draw and impossible to decide, because CSS has no reading of scroll
19
- * position. So this measures and says, in a custom state per edge, and the
20
- * styled tier draws whatever it likes on the edges named.
21
- *
22
- * **States rather than attributes**, so an element that upgrades after a
23
- * framework rendered its markup does not hand that framework markup it did not
24
- * write. A stylesheet asks `:host(:state(more-block-end))`.
25
- *
26
- * **A scrolling region takes the keyboard when it scrolls, and not before.**
27
- * A box a reader can only reach with a pointer is a box some readers cannot
28
- * read at all, which is what `tabindex` answers here. Only while it overflows:
29
- * a tab stop on a box with nothing to scroll is a stop that does nothing, and a
30
- * form full of them is worse than the problem.
31
- *
32
- * `role="region"` only with a `label`, because an unnamed region is an
33
- * announcement with no content: a screen reader says "region" and the reader
34
- * has learned nothing they did not already know.
35
- *
36
- * @since 0.1.0
37
- * @status stable
38
- * @category layout
39
- * @aliases Scroller, Overflow, Scroll container
40
- * @documentation https://helix.fusion.dev/components/scroll-area
41
- *
42
- * @slot - What scrolls.
43
- *
44
- * @csspart edges - Where the sideways edge cues are drawn.
45
- * @csspart viewport - The scrolling box.
46
- */
47
11
  let HelixHeadlessScrollArea = class HelixHeadlessScrollArea extends HelixElement {
48
12
  constructor() {
49
13
  super(...arguments);
50
- /**
51
- * Names the region, for a reader arriving at it out of context.
52
- *
53
- * Without one there is no region: see the note on the class.
54
- */
55
14
  this.label = '';
56
- /**
57
- * Which edges have content past them, measured rather than guessed.
58
- */
59
15
  this.more = [];
60
- /**
61
- * Whether there is anything to scroll at all, which decides the tab stop.
62
- */
63
16
  this.scrolls = false;
64
- /**
65
- * Reads the four edges off the box.
66
- *
67
- * A pixel of slack on each, because a scroller at its end is routinely a
68
- * fraction short of it: browsers round scroll positions differently under
69
- * zoom and on a display that is not at one to one, and a fade that never
70
- * quite goes out at the bottom of a fully scrolled list is the bug that
71
- * costs.
72
- */
73
17
  this.measure = () => {
74
18
  const box = this.scroller;
75
19
  if (!box) {
@@ -100,35 +44,12 @@ let HelixHeadlessScrollArea = class HelixHeadlessScrollArea extends HelixElement
100
44
  this.measure();
101
45
  };
102
46
  }
103
- /**
104
- * The box that actually scrolls, for anything that needs to move it.
105
- *
106
- * **Public because the alternative is silence.** The scrolling box is inside
107
- * this shadow root: a consumer holding a reference to this element holds the
108
- * host, which does not scroll, and `scrollBy` on it is a no-op that reports
109
- * nothing. That is how `scrollItemIntoView`'s `offsetParent` default fails
110
- * here.
111
- *
112
- * The element rather than a method of our own, because the utilities that
113
- * want it already take one.
114
- */
115
47
  get scroller() {
116
48
  return this.renderRoot?.querySelector('[part="viewport"]') ?? null;
117
49
  }
118
50
  connectedCallback() {
119
51
  super.connectedCallback();
120
- /* Both the box and what is in it. The box changing size is a layout change
121
- and the content changing size is a data change, and a fade that only
122
- answered one of them was wrong every time a list was filtered. */
123
52
  this.watcher = new ResizeObserver(this.measure);
124
- /* And what it watches, which rebuilding the observer does not restore. A
125
- * fresh observer watches nothing until something calls `observe`, so a
126
- * scroll area moved in the DOM comes back registered on no targets: the
127
- * fades stop answering a resize or a filtered list, and the component goes
128
- * on rendering.
129
- *
130
- * Guarded on `hasUpdated`, because the first connection happens before the
131
- * first render and there is no scroller to find yet. */
132
53
  if (this.hasUpdated) {
133
54
  this.watchContent();
134
55
  }
@@ -143,7 +64,6 @@ let HelixHeadlessScrollArea = class HelixHeadlessScrollArea extends HelixElement
143
64
  this.watchContent();
144
65
  this.measure();
145
66
  }
146
- /** Registers the box and everything slotted into it, which is what resizes. */
147
67
  watchContent() {
148
68
  const box = this.scroller;
149
69
  if (!box || !this.watcher) {
@@ -158,26 +78,6 @@ let HelixHeadlessScrollArea = class HelixHeadlessScrollArea extends HelixElement
158
78
  }
159
79
  updated(changed) {
160
80
  super.updated?.(changed);
161
- /* On the host rather than the viewport, so a consumer can style from the
162
- outside without reaching through a part.
163
-
164
- **Custom states and never attributes, which is what keeps hydration
165
- clean.** These are a reading of a laid-out box, so they are written on
166
- upgrade, and upgrade happens before a framework hydrates the subtree
167
- around it. Written as attributes they are markup on the host that the
168
- framework did not render: React reported a hydration mismatch naming
169
- `data-scrolls` and `data-more` on every load of a page holding one.
170
- `states` is not in the DOM at all, so this element may upgrade whenever it
171
- likes and a framework finds the markup it rendered. That is the rule the
172
- rest of the library already keeps, `app-header`'s `scrolled` being the
173
- clearest statement of it.
174
-
175
- One state per edge rather than one holding tokens, because a custom state
176
- is a name and not a value: there is no `~=` to ask with. The four
177
- selectors are what the styled tier writes either way. */
178
- /* Through `toggleState` for the feature test it carries. This is `updated`
179
- rather than `willUpdate`, so the server never reaches it, but Safari
180
- below 17.4 and Firefox below 126 do and have no `CustomStateSet`. */
181
81
  this.toggleState('scrolls', this.scrolls);
182
82
  for (const edge of EDGES) {
183
83
  this.toggleState(`more-${edge}`, this.more.includes(edge));