@fusion.dev/helix-headless 0.1.0 → 0.3.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.
- package/custom-elements.json +234 -234
- package/dist/accordion/item/accordion-item.js +0 -108
- package/dist/accordion/root/accordion-root.js +0 -33
- package/dist/announcement/root/announcement-root.js +1 -76
- package/dist/app-header/root/app-header-root.js +0 -84
- package/dist/avatar/group/avatar-group.js +0 -77
- package/dist/avatar/root/avatar-root.js +0 -72
- package/dist/badge/root/badge-root.js +0 -67
- package/dist/bar-chart/root/bar-chart-root.js +3 -263
- package/dist/blockquote/root/blockquote-root.js +0 -46
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +0 -72
- package/dist/breadcrumbs/root/breadcrumbs-root.js +0 -52
- package/dist/button/root/button-root.js +0 -215
- package/dist/button-group/root/button-group-root.js +0 -30
- package/dist/callout/root/callout-root.js +0 -52
- package/dist/card/root/card-root.js +0 -41
- package/dist/carousel/root/carousel-root.d.ts +2 -2
- package/dist/carousel/root/carousel-root.js +2 -362
- package/dist/chat/composer/chat-composer.js +0 -131
- package/dist/chat/message/chat-message.js +0 -137
- package/dist/chat/root/chat-root.js +0 -193
- package/dist/checkbox/root/checkbox-root.js +0 -113
- package/dist/checkbox-group/root/checkbox-group-root.js +1 -157
- package/dist/code/root/code-root.js +0 -26
- package/dist/code-block/root/code-block-root.js +0 -100
- package/dist/color-input/root/color-input-root.js +0 -217
- package/dist/color-picker/color.js +0 -69
- package/dist/color-picker/root/color-picker-root.js +0 -272
- package/dist/combobox/root/combobox-root.js +0 -428
- package/dist/command/root/command-root.js +0 -177
- package/dist/copy-button/root/copy-button-root.js +0 -134
- package/dist/data-table/root/data-table-root.js +0 -336
- package/dist/data-table/root/order.js +0 -100
- package/dist/date-input/root/date-input-root.js +1 -398
- package/dist/date-picker/root/date-picker-root.js +0 -358
- package/dist/dialog/root/dialog-root.js +0 -189
- package/dist/divider/root/divider-root.js +0 -71
- package/dist/donut-chart/root/donut-chart-root.js +0 -214
- package/dist/drawer/root/drawer-root.js +0 -116
- package/dist/drilldown/item/drilldown-item.js +0 -82
- package/dist/drilldown/root/drilldown-root.js +0 -91
- package/dist/element-events.generated.js +0 -5
- package/dist/empty/root/empty-root.js +0 -35
- package/dist/file-input/root/file-input-root.js +0 -232
- package/dist/format-date/root/format-date-root.js +0 -95
- package/dist/heatmap/root/buckets.js +0 -57
- package/dist/heatmap/root/heatmap-root.js +0 -313
- package/dist/icon/root/icon-root.js +0 -48
- package/dist/icon-button/root/icon-button-root.js +0 -30
- package/dist/image/root/image-root.js +0 -174
- package/dist/index.js +0 -9
- package/dist/input/root/input-root.js +0 -228
- package/dist/kbd/root/kbd-root.js +0 -117
- package/dist/legend/root/legend-root.js +0 -73
- package/dist/line-chart/root/line-chart-root.js +0 -172
- package/dist/list/item/list-item.js +0 -92
- package/dist/list/root/list-root.js +0 -26
- package/dist/listbox/root/listbox-root.js +2 -429
- package/dist/loader/root/loader-root.js +0 -40
- package/dist/megamenu/root/megamenu-root.js +0 -284
- package/dist/menu/item/menu-item.js +1 -221
- package/dist/menu/root/menu-root.js +1 -171
- package/dist/meter/root/meter-root.js +1 -152
- package/dist/navigation/root/navigation-root.js +0 -56
- package/dist/notifications/notification/notification.js +0 -159
- package/dist/notifications/root/notifications-root.js +0 -148
- package/dist/number-input/root/number-input-root.js +1 -263
- package/dist/option/group/option-group.js +0 -26
- package/dist/option/root/option-root.js +0 -157
- package/dist/outline/root/outline-root.js +0 -292
- package/dist/pagination/root/pagination-root.js +0 -361
- package/dist/pagination/size/pagination-size.js +0 -104
- package/dist/pagination/summary/pagination-summary.js +0 -90
- package/dist/pin-input/root/pin-input-root.js +0 -276
- package/dist/popover/root/popover-root.js +0 -103
- package/dist/progress/root/progress-root.js +1 -133
- package/dist/qr-code/root/qr-code-root.js +0 -96
- package/dist/quote/root/quote-root.js +0 -25
- package/dist/radio/group/radio-group.js +0 -81
- package/dist/radio/root/radio-root.js +0 -85
- package/dist/rating/root/rating-root.js +0 -220
- package/dist/scroll-area/root/scroll-area-root.js +0 -100
- package/dist/select/root/select-root.js +2 -218
- package/dist/skeleton/root/skeleton-root.js +0 -20
- package/dist/slider/root/slider-root.js +1 -267
- package/dist/sparkline/root/sparkline-root.js +3 -164
- package/dist/speed-dial/item/speed-dial-item.js +0 -52
- package/dist/speed-dial/root/speed-dial-root.js +1 -116
- package/dist/splitter/root/splitter-root.js +0 -489
- package/dist/stat/group/stat-group.js +0 -18
- package/dist/stat/root/stat-root.js +0 -23
- package/dist/steps/root/steps-root.js +0 -62
- package/dist/steps/step/step.js +0 -131
- package/dist/switch/root/switch-root.js +0 -80
- package/dist/table/cell/table-cell.js +0 -23
- package/dist/table/column/table-column.js +0 -158
- package/dist/table/root/table-root.js +0 -82
- package/dist/table/row/table-row.js +0 -21
- package/dist/tabs/panel/tab-panel.js +0 -49
- package/dist/tabs/root/tabs-root.js +0 -98
- package/dist/tabs/tab/tab.js +0 -59
- package/dist/tag/root/tag-root.js +0 -31
- package/dist/textarea/root/textarea-root.js +0 -162
- package/dist/time-input/root/time-input-root.js +0 -283
- package/dist/time-picker/root/time-picker-root.js +0 -326
- package/dist/timeline/item/timeline-item.js +0 -66
- package/dist/timeline/root/timeline-root.js +0 -41
- package/dist/toast/item/toast-item.js +0 -165
- package/dist/toast/root/toast-root.js +0 -51
- package/dist/toggle/group/toggle-group.js +0 -150
- package/dist/toggle/root/toggle-root.js +0 -78
- package/dist/toolbar/root/toolbar-root.js +0 -116
- package/dist/tooltip/root/tooltip-root.js +0 -427
- package/dist/tour/root/tour-root.js +0 -130
- package/dist/tour/stop/tour-stop.js +1 -127
- package/dist/tree/item/tree-item.js +0 -198
- package/dist/tree/root/tree-root.js +0 -97
- package/dist/trend/root/trend-root.js +0 -53
- package/dist/video/root/video-root.d.ts +8 -12
- package/dist/video/root/video-root.js +5 -756
- package/dist/video-playlist/item/video-playlist-item.js +1 -90
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -132
- package/dist/video-playlist/root/video-playlist-root.js +0 -113
- package/dist/virtual-list/root/virtual-list-root.js +0 -105
- package/jsx.d.ts +1408 -2576
- package/package.json +6 -6
- 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));
|