@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,79 +7,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { anchorElement, CommandController, customElement, deepActiveElement, HelixElement, MixinControllerOpen, PopoverController, tellDisclosure, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary A panel that opens against a trigger and holds anything.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* The general case the specific overlays are versions of. `Menu` holds
|
|
15
|
-
* commands, `Select` holds options, `Tooltip` holds a description; this holds
|
|
16
|
-
* whatever the consumer puts in it, which is why it has no keyboard of its own
|
|
17
|
-
* and no opinion about what is inside.
|
|
18
|
-
*
|
|
19
|
-
* **The trigger is slotted and stays the consumer's own element.** Nothing is
|
|
20
|
-
* written onto it: no `aria-expanded`, no `id`, no `tabindex`. A component that
|
|
21
|
-
* decorates a consumer's markup after upgrading is a component whose output a
|
|
22
|
-
* React consumer's hydration disagrees with, and the attribute they would care
|
|
23
|
-
* about most is the one that changes on every open.
|
|
24
|
-
*
|
|
25
|
-
* **The panel is in the top layer**, through the popover API, so it clears any
|
|
26
|
-
* ancestor with `overflow: hidden` without a `z-index` fight. `position: fixed`
|
|
27
|
-
* does not do that: it is still clipped by `overflow`, and re-anchored entirely
|
|
28
|
-
* by an ancestor carrying `transform`, `filter` or `contain`.
|
|
29
|
-
*
|
|
30
|
-
* **It opens on click, and from a command.** `command="--show"` and its
|
|
31
|
-
* siblings work here the way they do on `Dialog` and `Drawer`, so a trigger
|
|
32
|
-
* elsewhere on the page can open it without a line of script and without being
|
|
33
|
-
* slotted into it.
|
|
34
|
-
*
|
|
35
|
-
* @since 0.1.0
|
|
36
|
-
* @status stable
|
|
37
|
-
* @category overlays
|
|
38
|
-
* @aliases Popper, Flyout, Overlay
|
|
39
|
-
* @documentation https://helix.fusion.dev/components/popover
|
|
40
|
-
*
|
|
41
|
-
* @slot - The contents of the panel.
|
|
42
|
-
* @slot trigger - What opens it.
|
|
43
|
-
*
|
|
44
|
-
* @event {HelixBeforeCloseEvent} helix-before-close - Fired before it closes, and cancelable.
|
|
45
|
-
* @event {HelixBeforeOpenEvent} helix-before-open - Fired before it opens, and cancelable.
|
|
46
|
-
* @event {Event} toggle - Says the state moved, carrying `newState` and `oldState`.
|
|
47
|
-
*
|
|
48
|
-
* @csspart content - The scrolling region inside the panel.
|
|
49
|
-
* @csspart popover - The panel.
|
|
50
|
-
*/
|
|
51
10
|
let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpen(HelixElement) {
|
|
52
11
|
constructor() {
|
|
53
12
|
super(...arguments);
|
|
54
|
-
/**
|
|
55
|
-
* Accessible name for the panel.
|
|
56
|
-
*/
|
|
57
13
|
this.label = 'Popover';
|
|
58
|
-
/**
|
|
59
|
-
* Space between the trigger and the panel, in pixels.
|
|
60
|
-
*/
|
|
61
14
|
this.offset = 4;
|
|
62
|
-
/**
|
|
63
|
-
* Preferred side.
|
|
64
|
-
*
|
|
65
|
-
* It moves if that side does not fit.
|
|
66
|
-
*/
|
|
67
15
|
this.placement = 'bottom-start';
|
|
68
|
-
/* The panel, plus everything that has to happen around it.
|
|
69
|
-
*
|
|
70
|
-
* Called `panel` rather than `popover`, because `HTMLElement.popover` is the
|
|
71
|
-
* popover API's own IDL attribute and a field of that name shadows it with
|
|
72
|
-
* something of a different type. TypeScript objects; what it would have done
|
|
73
|
-
* at runtime is break the API this is built on. */
|
|
74
16
|
this.panel = new PopoverController(this, {
|
|
75
17
|
anchor: () => this.trigger,
|
|
76
|
-
/* The host *and* whatever invoked it, never the panel alone. An outside
|
|
77
|
-
press is measured against this, and the styled host is
|
|
78
|
-
`display: contents` with no box of its own, so a boundary of the panel
|
|
79
|
-
alone would close on the very press that opened it. A `--toggle` button
|
|
80
|
-
outside the host is part of the control by any reading a reader would
|
|
81
|
-
give it: pressing it while the panel is open should close it once, not
|
|
82
|
-
close it on `pointerdown` and reopen it on the `click` that follows. */
|
|
83
18
|
boundary: () => [this, this.invoker],
|
|
84
19
|
offset: () => this.offset,
|
|
85
20
|
onDismiss: () => this.hide(),
|
|
@@ -113,72 +48,34 @@ let HelixHeadlessPopover = class HelixHeadlessPopover extends MixinControllerOpe
|
|
|
113
48
|
}
|
|
114
49
|
};
|
|
115
50
|
}
|
|
116
|
-
/** The element the panel opens against. */
|
|
117
51
|
get trigger() {
|
|
118
|
-
/* The invoker first, where there is one: a command names the element the
|
|
119
|
-
reader actually pressed, and it is a better anchor than a slotted trigger
|
|
120
|
-
the consumer may not have provided at all. */
|
|
121
52
|
return (this.invoker ??
|
|
122
53
|
anchorElement(this.renderRoot?.querySelector('slot[name="trigger"]'), this));
|
|
123
54
|
}
|
|
124
|
-
/** Opens the panel. */
|
|
125
55
|
show() {
|
|
126
56
|
if (this.open || !this.requestOpen(true)) {
|
|
127
57
|
return;
|
|
128
58
|
}
|
|
129
|
-
// The raising happens in `updated()`, which `requestOpen` has just
|
|
130
|
-
// scheduled: one path for both the imperative and the declarative case.
|
|
131
59
|
}
|
|
132
|
-
/** Closes the panel and puts focus back on the trigger. */
|
|
133
60
|
hide() {
|
|
134
61
|
if (!this.open || !this.requestOpen(false)) {
|
|
135
62
|
return;
|
|
136
63
|
}
|
|
137
64
|
this.panel.hide();
|
|
138
|
-
/* Only when focus is inside the panel that is going away. A popover
|
|
139
|
-
dismissed by a press somewhere else has not taken focus with it, and
|
|
140
|
-
pulling focus back to the trigger would move a reader who was already
|
|
141
|
-
somewhere else on purpose. */
|
|
142
|
-
/* `deepActiveElement`, which walks into shadow roots. `document.activeElement`
|
|
143
|
-
stops at the outermost host, so a popover living inside another
|
|
144
|
-
component's shadow root never saw its own focus and never gave it back to
|
|
145
|
-
the trigger. The util exists for this. */
|
|
146
65
|
if (this.contains(deepActiveElement())) {
|
|
147
66
|
this.trigger.focus();
|
|
148
67
|
}
|
|
149
68
|
}
|
|
150
|
-
/** Tells a slotted trigger the disclosure state, by property. See `tellDisclosure`. */
|
|
151
69
|
tellTrigger() {
|
|
152
70
|
const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
|
|
153
71
|
tellDisclosure(slot?.assignedElements({ flatten: false })[0], this.open);
|
|
154
72
|
}
|
|
155
|
-
/**
|
|
156
|
-
* Puts the panel where `open` says it should be, however it got there.
|
|
157
|
-
*
|
|
158
|
-
* Driven from the resolved state rather than from `show()` and `hide()`,
|
|
159
|
-
* which is what makes the imperative and the declarative paths one path. Left
|
|
160
|
-
* to the two methods, a panel opened by *declaring* `open` (or `default-open`,
|
|
161
|
-
* or a consumer assigning the property) renders visible while never reaching
|
|
162
|
-
* the top layer and never being anchored: clipped by the first ancestor with
|
|
163
|
-
* `overflow`, and positioned wherever normal flow left it. The controller
|
|
164
|
-
* ignores a `show()` on something already raised, so this costs nothing when
|
|
165
|
-
* the methods did the work.
|
|
166
|
-
*/
|
|
167
73
|
updated(changed) {
|
|
168
74
|
super.updated(changed);
|
|
169
|
-
/* Every update, not only the ones that move `open`: a trigger slotted after
|
|
170
|
-
the first render has to be told too, and a slot change is not a property
|
|
171
|
-
change. Idempotent, so saying it twice costs an assignment Lit compares
|
|
172
|
-
and discards. */
|
|
173
75
|
this.tellTrigger();
|
|
174
76
|
if (!changed.has('open')) {
|
|
175
77
|
return;
|
|
176
78
|
}
|
|
177
|
-
/* Raised here rather than a microtask later, and `updated()` is what makes
|
|
178
|
-
that safe: the render has already been written, so the panel is no longer
|
|
179
|
-
`display: none` and `showPopover` will not throw on it. Anything raising
|
|
180
|
-
it before the render has to wait for `updateComplete`; from here there is
|
|
181
|
-
nothing left to wait for. */
|
|
182
79
|
if (this.open) {
|
|
183
80
|
this.panel.show();
|
|
184
81
|
}
|
|
@@ -7,139 +7,25 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { customElement, HelixElement, hostAria } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary Shows how far along a task is, when that is known.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* Determinate by default, and `indeterminate` for a task whose total is not
|
|
15
|
-
* known yet. `Loader` is still the right answer where the wait is short or has
|
|
16
|
-
* no place of its own on the page; a bar earns its width where the work is the
|
|
17
|
-
* subject of the region it sits in.
|
|
18
|
-
*
|
|
19
|
-
* A bar that fills at a made-up rate would be a bar that lies, so the
|
|
20
|
-
* indeterminate one does not fill: it runs a segment across the track, which
|
|
21
|
-
* says work is happening and claims no proportion at all. `aria-valuenow` is
|
|
22
|
-
* omitted with it, which is the platform's own signal for the same thing.
|
|
23
|
-
*
|
|
24
|
-
* **The fill is a fraction of the track, written as a custom property.**
|
|
25
|
-
* `--progress-fraction` is derived from `value` and `max` rather than measured,
|
|
26
|
-
* so it renders on a server with no layout and needs nothing after upgrade. That
|
|
27
|
-
* is what keeps it out of the class of geometry this library writes only on the
|
|
28
|
-
* client. A consumer never sets it: it carries a number this element computed
|
|
29
|
-
* and CSS cannot, which is what lets a stylesheet draw the thing without
|
|
30
|
-
* measuring anything itself.
|
|
31
|
-
*
|
|
32
|
-
* **`value` is clamped, and out-of-range input is not an error.** A count that
|
|
33
|
-
* runs past its own total is ordinary in real applications, and a bar that
|
|
34
|
-
* overflows its track or reports `aria-valuenow` above `aria-valuemax` is worse
|
|
35
|
-
* than one that reads as finished.
|
|
36
|
-
*
|
|
37
|
-
* Semantics are on the host through `ElementInternals`, the same as everywhere
|
|
38
|
-
* else: a `role` in the shadow root announces correctly and leaves the host
|
|
39
|
-
* anonymous to anything walking the light DOM, and a `role` written onto the
|
|
40
|
-
* host as an attribute is one a server's markup never had.
|
|
41
|
-
*
|
|
42
|
-
* @since 0.1.0
|
|
43
|
-
* @status stable
|
|
44
|
-
* @category feedback
|
|
45
|
-
* @aliases Progress bar, Loading bar
|
|
46
|
-
* @documentation https://helix.fusion.dev/components/progress
|
|
47
|
-
*
|
|
48
|
-
* @csspart groove - The unfilled circle, under the ring's own sweep.
|
|
49
|
-
* @csspart indicator - The part of it that is done.
|
|
50
|
-
* @csspart track - The full length the work is measured against.
|
|
51
|
-
* @csspart value - The reading beside the bar, under `show-value`.
|
|
52
|
-
*
|
|
53
|
-
* @cssproperty --progress-fraction - The value as a fraction of the range, 0 to 1. Pinned to 1 while indeterminate.
|
|
54
|
-
*/
|
|
55
10
|
let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
56
11
|
constructor() {
|
|
57
12
|
super(...arguments);
|
|
58
|
-
/**
|
|
59
|
-
* Whether the work has a knowable end.
|
|
60
|
-
*
|
|
61
|
-
* On the headless tier rather than as a treatment, because it is
|
|
62
|
-
* semantics before it is a picture: a progressbar with no `aria-valuenow` is
|
|
63
|
-
* how the platform says "running, and nobody knows how far", and a reader on
|
|
64
|
-
* a screen reader is told that rather than being read a number that means
|
|
65
|
-
* nothing. `value` and `max` are ignored while it is set.
|
|
66
|
-
*
|
|
67
|
-
* Not `Loader`, which is the other thing this could be. A loader says the
|
|
68
|
-
* page is busy; a bar says a named piece of work is running, and keeps its
|
|
69
|
-
* place in the layout when the answer arrives and it becomes determinate.
|
|
70
|
-
*/
|
|
71
13
|
this.indeterminate = false;
|
|
72
|
-
/**
|
|
73
|
-
* Accessible name for what is in progress.
|
|
74
|
-
*/
|
|
75
14
|
this.label = 'Progress';
|
|
76
|
-
/**
|
|
77
|
-
* The total the work is measured against.
|
|
78
|
-
*/
|
|
79
15
|
this.max = 100;
|
|
80
|
-
/**
|
|
81
|
-
* Whether the reading is shown beside the bar.
|
|
82
|
-
*
|
|
83
|
-
* The same name and shape `Slider` uses, because it is the same question:
|
|
84
|
-
* a control that holds a number can either show it or leave it to whatever is
|
|
85
|
-
* around it. Off by default, since most bars sit under a label that already
|
|
86
|
-
* says what is happening and a second number is noise.
|
|
87
|
-
*
|
|
88
|
-
* Nothing under `indeterminate`, where there is no reading to show: a percent
|
|
89
|
-
* that does not move is worse than none, and `valueText` is the property for
|
|
90
|
-
* saying "uploading" while the bar runs.
|
|
91
|
-
*/
|
|
92
16
|
this.showValue = false;
|
|
93
|
-
/**
|
|
94
|
-
* What has been done so far, in the same units as `max`.
|
|
95
|
-
*
|
|
96
|
-
* Clamped between zero and `max` for both the fill and the announcement, so
|
|
97
|
-
* the two can never disagree about what is on screen.
|
|
98
|
-
*/
|
|
99
17
|
this.value = 0;
|
|
100
|
-
/**
|
|
101
|
-
* A reading for the value, where the number alone does not say enough.
|
|
102
|
-
*
|
|
103
|
-
* "3 of 8 files" rather than "37". Announced instead of the percentage, and
|
|
104
|
-
* never instead of the value itself: `aria-valuenow` still carries the
|
|
105
|
-
* number, because assistive technology that ignores the text still has to
|
|
106
|
-
* report progress.
|
|
107
|
-
*/
|
|
108
18
|
this.valueText = '';
|
|
109
19
|
}
|
|
110
|
-
/** What is done, as a fraction of the whole, between 0 and 1. */
|
|
111
20
|
get fraction() {
|
|
112
21
|
const max = this.max > 0 ? this.max : 100;
|
|
113
|
-
/* `|| 0` catches the value that is not a number.
|
|
114
|
-
*
|
|
115
|
-
* `undefined / 100` is `NaN`, `Math.min(Math.max(NaN, 0), 1)` is `NaN`, and
|
|
116
|
-
* `NaN` reached CSS as `scale: NaN`, which is an invalid value the
|
|
117
|
-
* declaration is dropped for: the bar fell back to its unscaled width and
|
|
118
|
-
* painted COMPLETE for a value nobody had given. `aria-valuenow` announced
|
|
119
|
-
* the string "NaN" beside it. Empty means nothing done, which is the only
|
|
120
|
-
* reading that is not a claim. */
|
|
121
22
|
return Math.min(Math.max(this.value / max, 0), 1) || 0;
|
|
122
23
|
}
|
|
123
24
|
willUpdate(changed) {
|
|
124
25
|
super.willUpdate(changed);
|
|
125
26
|
hostAria(this.internals, { label: this.label, role: 'progressbar' });
|
|
126
|
-
/* The clamped value, not the given one, so what is announced is what is
|
|
127
|
-
drawn. Written as strings because that is what `ElementInternals` takes;
|
|
128
|
-
a number assigned here is stringified anyway and reads as though the
|
|
129
|
-
type were checked. */
|
|
130
27
|
this.internals.ariaValueMin = '0';
|
|
131
28
|
this.internals.ariaValueMax = String(this.max > 0 ? this.max : 100);
|
|
132
|
-
/* Absent while indeterminate, not zero. An omitted `aria-valuenow` is the
|
|
133
|
-
* platform's own signal for indeterminate, and it is the only one:
|
|
134
|
-
* `aria-busy` says the element is being updated, which is a different claim.
|
|
135
|
-
* Reporting 0 would announce "0 percent" for work that is running, which is
|
|
136
|
-
* worse than saying nothing because it is wrong rather than unknown.
|
|
137
|
-
*
|
|
138
|
-
* Otherwise the clamped value itself, never the fraction multiplied back up.
|
|
139
|
-
* Dividing by `max` and then multiplying by it is a round trip through a
|
|
140
|
-
* float, and for roughly one integer pair in sixteen it does not come back:
|
|
141
|
-
* `1/3*3` is `0.9999999999999998`, which is what the accessibility tree
|
|
142
|
-
* carried and what a screen reader read out. */
|
|
143
29
|
const ceiling = this.max > 0 ? this.max : 100;
|
|
144
30
|
this.internals.ariaValueNow = this.indeterminate
|
|
145
31
|
? null
|
|
@@ -147,20 +33,7 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
|
147
33
|
this.internals.ariaValueText = this.valueText || null;
|
|
148
34
|
}
|
|
149
35
|
render() {
|
|
150
|
-
/* A custom property rather than a width, so the styled tier decides what
|
|
151
|
-
the number means: a bar fills along it, and a ring would sweep. */
|
|
152
36
|
const fraction = this.indeterminate ? 1 : this.fraction;
|
|
153
|
-
/* Two circles and `pathLength`, which is what makes the arithmetic go away.
|
|
154
|
-
*
|
|
155
|
-
* Declaring the path 100 units long lets `stroke-dasharray` be written as
|
|
156
|
-
* the percentage itself, so nothing here or in the stylesheet needs the
|
|
157
|
-
* circumference, the radius or pi. The geometry is CSS as well: `r` and the
|
|
158
|
-
* stroke both read the ring's own custom properties, so a consumer changing
|
|
159
|
-
* the thickness moves the circle that carries it without the component
|
|
160
|
-
* recalculating anything.
|
|
161
|
-
*
|
|
162
|
-
* `aria-hidden`, because the host is the `progressbar` and carries the
|
|
163
|
-
* value. The drawing is a picture of what it already says. */
|
|
164
37
|
const ring = html `<svg part="track" aria-hidden="true">
|
|
165
38
|
<circle part="groove" />
|
|
166
39
|
<circle part="indicator" pathLength="100" style="--progress-fraction: ${fraction}" />
|
|
@@ -170,12 +43,7 @@ let HelixHeadlessProgress = class HelixHeadlessProgress extends HelixElement {
|
|
|
170
43
|
: html `<div part="track">
|
|
171
44
|
<div part="indicator" style="--progress-fraction: ${fraction}"></div>
|
|
172
45
|
</div>`}
|
|
173
|
-
${
|
|
174
|
-
/* `aria-hidden`, because the bar already reports this. The host carries
|
|
175
|
-
`role="progressbar"` with `aria-valuenow` and `aria-valuetext`, so a
|
|
176
|
-
readable copy of the same number is a second announcement of one
|
|
177
|
-
fact. It is here to be looked at, not to be read out. */
|
|
178
|
-
this.showValue && !this.indeterminate
|
|
46
|
+
${this.showValue && !this.indeterminate
|
|
179
47
|
? html `<span part="value" aria-hidden="true"
|
|
180
48
|
>${this.valueText || `${Math.round(this.fraction * 100)}%`}</span
|
|
181
49
|
>`
|
|
@@ -8,26 +8,10 @@ import { customElement, HelixElement } from '@fusion.dev/helix-core';
|
|
|
8
8
|
import { html, nothing, svg } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { encode } from 'uqr';
|
|
11
|
-
/** Whether a module is one of the three finder squares, or their separators. */
|
|
12
11
|
const isFinder = (x, y, size) => {
|
|
13
12
|
const near = (a, b) => a < 8 && b < 8;
|
|
14
13
|
return near(x, y) || near(size - 1 - x, y) || near(x, size - 1 - y);
|
|
15
14
|
};
|
|
16
|
-
/**
|
|
17
|
-
* The dark modules of a matrix, as one path.
|
|
18
|
-
*
|
|
19
|
-
* One `<path>` rather than a rect per module. A modest code is 33 by 33, so a
|
|
20
|
-
* rect apiece is a thousand elements the browser lays out and the accessibility
|
|
21
|
-
* tree walks, and every one of them is a one-unit square.
|
|
22
|
-
*
|
|
23
|
-
* **`square` joins runs and the other styles cannot.** A row of eight square
|
|
24
|
-
* modules is one `h8`; a row of eight *rounded* ones is eight shapes, because
|
|
25
|
-
* the corners are what is being drawn and a joined run has none in the middle.
|
|
26
|
-
*
|
|
27
|
-
* `only` splits the drawing in two without walking the matrix twice: the finder
|
|
28
|
-
* squares are structural, at three known corners, so a caller can ask for them
|
|
29
|
-
* alone and get a second path to paint differently.
|
|
30
|
-
*/
|
|
31
15
|
const pathFor = (matrix, style, only = 'all') => {
|
|
32
16
|
const size = matrix.length;
|
|
33
17
|
const wanted = (x, y) => {
|
|
@@ -56,10 +40,6 @@ const pathFor = (matrix, style, only = 'all') => {
|
|
|
56
40
|
}
|
|
57
41
|
return parts.join('');
|
|
58
42
|
}
|
|
59
|
-
/* A circle for `dots` and a squircle for `rounded`, both drawn as arcs on a
|
|
60
|
-
one-unit square. The radius is a little under half so neighboring dots do
|
|
61
|
-
not touch, which is what makes a dotted code read as dots rather than as a
|
|
62
|
-
square one with soft edges. */
|
|
63
43
|
const r = style === 'dots' ? 0.45 : 0.28;
|
|
64
44
|
for (let y = 0; y < size; y += 1) {
|
|
65
45
|
for (let x = 0; x < size; x += 1) {
|
|
@@ -79,77 +59,14 @@ const pathFor = (matrix, style, only = 'all') => {
|
|
|
79
59
|
}
|
|
80
60
|
return parts.join('');
|
|
81
61
|
};
|
|
82
|
-
/**
|
|
83
|
-
* @summary Encodes a short piece of text as a scannable square.
|
|
84
|
-
*
|
|
85
|
-
* @description
|
|
86
|
-
* **The picture is one `<path>`, not a grid of squares.** A code of any useful
|
|
87
|
-
* length is a thousand modules, and drawing each as its own rect is a thousand
|
|
88
|
-
* nodes for one image. Runs of adjacent dark modules are joined, so a row of
|
|
89
|
-
* eight is one horizontal move rather than eight squares.
|
|
90
|
-
*
|
|
91
|
-
* **It is an image and says so.** `role="img"` with a name, because what a
|
|
92
|
-
* reader with a screen reader needs is what the code is *for*, and no amount of
|
|
93
|
-
* describing the pattern gives them that. Where nothing is passed the value
|
|
94
|
-
* itself is the name, which is right far more often than it looks: most codes
|
|
95
|
-
* hold a URL, and a URL read aloud is the destination.
|
|
96
|
-
*
|
|
97
|
-
* **The quiet zone is part of the code.** Four modules of margin are required
|
|
98
|
-
* by the specification, not a design choice, and a scanner will refuse a code
|
|
99
|
-
* that is cropped to its edge. It is drawn inside the `viewBox` so it survives
|
|
100
|
-
* any size the component is given, including one set in CSS.
|
|
101
|
-
*
|
|
102
|
-
* `correction` is what buys the middle slot. A code at `H` can lose thirty
|
|
103
|
-
* percent of itself and still read, which is what makes covering the center
|
|
104
|
-
* with a mark safe; at `L` the same mark makes the code unscannable. That is
|
|
105
|
-
* why the slot exists on the same element as the property, and why the default
|
|
106
|
-
* moves when the slot is filled.
|
|
107
|
-
*
|
|
108
|
-
* @since 0.1.0
|
|
109
|
-
* @status stable
|
|
110
|
-
* @category media
|
|
111
|
-
* @aliases QR, Barcode, Scan code
|
|
112
|
-
* @documentation https://helix.fusion.dev/components/qr-code
|
|
113
|
-
*
|
|
114
|
-
* @slot logo - A mark for the center, which only reads at a high correction.
|
|
115
|
-
*
|
|
116
|
-
* @csspart code - The square itself.
|
|
117
|
-
* @csspart finders - The three corner squares, which can take their own color.
|
|
118
|
-
* @csspart logo - The box holding the center mark.
|
|
119
|
-
* @csspart modules - Everything but the three corner squares.
|
|
120
|
-
*/
|
|
121
62
|
let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
|
|
122
63
|
constructor() {
|
|
123
64
|
super(...arguments);
|
|
124
|
-
/**
|
|
125
|
-
* What the image is called.
|
|
126
|
-
*
|
|
127
|
-
* Falling back to the value is deliberate: a code almost always holds a URL,
|
|
128
|
-
* and a URL is a better name than "QR code" for a reader who cannot see it.
|
|
129
|
-
*/
|
|
130
65
|
this.label = '';
|
|
131
|
-
/**
|
|
132
|
-
* How the modules are drawn.
|
|
133
|
-
*
|
|
134
|
-
* `square` is the default and the only one that joins runs of adjacent
|
|
135
|
-
* modules into a single subpath, which is what keeps a thousand modules to a
|
|
136
|
-
* handful of nodes. `rounded` and `dots` draw each module on its own, because
|
|
137
|
-
* the corners are the thing being drawn and a joined run has none in the
|
|
138
|
-
* middle. That cost is paid only by a code that asks for them.
|
|
139
|
-
*
|
|
140
|
-
* A scanner reads contrast between cells, not their shape, so all three
|
|
141
|
-
* scan. `dots` sits a little under half a module so neighbors do not touch,
|
|
142
|
-
* which is what makes it read as dots rather than as a softened square code.
|
|
143
|
-
*/
|
|
144
66
|
this.shape = 'square';
|
|
145
|
-
/**
|
|
146
|
-
* What the code encodes, which is usually a URL.
|
|
147
|
-
*/
|
|
148
67
|
this.value = '';
|
|
149
|
-
/** Whether anything is slotted into the middle. */
|
|
150
68
|
this.logo = false;
|
|
151
69
|
}
|
|
152
|
-
/** The level actually used, which the slot can raise. */
|
|
153
70
|
get level() {
|
|
154
71
|
return this.correction ?? (this.logo ? 'H' : 'M');
|
|
155
72
|
}
|
|
@@ -157,23 +74,10 @@ let HelixHeadlessQrCode = class HelixHeadlessQrCode extends HelixElement {
|
|
|
157
74
|
if (this.value === '') {
|
|
158
75
|
return nothing;
|
|
159
76
|
}
|
|
160
|
-
/* Regenerated on every render rather than cached. It is a pure function of
|
|
161
|
-
two properties, Lit only renders when one of them moved, and a cache here
|
|
162
|
-
would be a second place for them to disagree. */
|
|
163
77
|
const result = encode(this.value, { border: 0, ecc: this.level });
|
|
164
78
|
const size = result.size;
|
|
165
|
-
/* The quiet zone, in the `viewBox` rather than in a wrapper's padding. Four
|
|
166
|
-
modules is what the specification requires and what a scanner looks for,
|
|
167
|
-
so it has to scale with the code and not with whatever box the component
|
|
168
|
-
was given. */
|
|
169
79
|
const quiet = 4;
|
|
170
80
|
const box = size + quiet * 2;
|
|
171
|
-
/* The three corner squares as their own path, so they can take their own
|
|
172
|
-
color. Their positions are structural rather than guessed: seven by
|
|
173
|
-
seven at each of three corners, plus the separator ring, which is what
|
|
174
|
-
`isFinder` describes. Drawn square whatever the body's style is, because
|
|
175
|
-
they are what a scanner locks onto first and rounding them costs
|
|
176
|
-
reliability for decoration. */
|
|
177
81
|
const finders = pathFor(result.data, 'square', 'finders');
|
|
178
82
|
return svg `<svg
|
|
179
83
|
part="code"
|
|
@@ -7,34 +7,9 @@ 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 } from 'lit/decorators.js';
|
|
10
|
-
/**
|
|
11
|
-
* @summary A short quotation, set inline with the text around it.
|
|
12
|
-
*
|
|
13
|
-
* @description
|
|
14
|
-
* Renders a real `<q>`, which is what supplies the quotation marks: the browser
|
|
15
|
-
* inserts the right ones for the document's language, and nests them correctly
|
|
16
|
-
* when one quotation sits inside another. Typing the marks into the content
|
|
17
|
-
* instead gets both of those wrong in every language but one.
|
|
18
|
-
*
|
|
19
|
-
* A passage set off from the prose rather than run inside it is a blockquote,
|
|
20
|
-
* which is a different element for a different job.
|
|
21
|
-
*
|
|
22
|
-
* @since 0.1.0
|
|
23
|
-
* @status stable
|
|
24
|
-
* @category typography
|
|
25
|
-
* @aliases Q, InlineQuote
|
|
26
|
-
* @documentation https://helix.fusion.dev/components/quote
|
|
27
|
-
*
|
|
28
|
-
* @slot - The quoted text.
|
|
29
|
-
*
|
|
30
|
-
* @csspart quote - The quotation.
|
|
31
|
-
*/
|
|
32
10
|
let HelixHeadlessQuote = class HelixHeadlessQuote extends HelixElement {
|
|
33
11
|
constructor() {
|
|
34
12
|
super(...arguments);
|
|
35
|
-
/**
|
|
36
|
-
* URL identifying where the quotation came from.
|
|
37
|
-
*/
|
|
38
13
|
this.cite = '';
|
|
39
14
|
}
|
|
40
15
|
render() {
|
|
@@ -8,70 +8,13 @@ import { customElement, DisabledCascadeController, HelixFormElement, hostAria, K
|
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { HelixHeadlessRadio } from '../root/radio-root.js';
|
|
11
|
-
/**
|
|
12
|
-
* @summary A set of choices where exactly one is the answer.
|
|
13
|
-
*
|
|
14
|
-
* @description
|
|
15
|
-
* **The group is the control, and the radios are its options.** This is the
|
|
16
|
-
* asymmetry with checkboxes and it comes from what a form submits: a set of
|
|
17
|
-
* checkboxes is several answers and each box submits its own, while a set of
|
|
18
|
-
* radios is one answer, so the group owns the `name`, the `value` and the
|
|
19
|
-
* validity, and a lone radio outside a group is not a control at all.
|
|
20
|
-
*
|
|
21
|
-
* Which is also why `MixinField` lands here and not on `Radio`: the label, the
|
|
22
|
-
* hint and the message are about the question, and the question is the set.
|
|
23
|
-
*
|
|
24
|
-
* Selection travels up and state travels down. An item announces that it was
|
|
25
|
-
* chosen, the group decides whether that stands, and the group writes
|
|
26
|
-
* `selected` onto every item. Nothing sets itself, so there is one answer to
|
|
27
|
-
* "which one is on" rather than one per item.
|
|
28
|
-
*
|
|
29
|
-
* `value` and `defaultValue` are separate for the reason `checked` and
|
|
30
|
-
* `defaultChecked` are: `value` is the answer now, `defaultValue` is what the
|
|
31
|
-
* markup asked for, which is where a reset returns and is gone the moment
|
|
32
|
-
* anything is chosen.
|
|
33
|
-
*
|
|
34
|
-
* @since 0.1.0
|
|
35
|
-
* @status experimental
|
|
36
|
-
* @aliases RadioButtons, Choice group
|
|
37
|
-
* @documentation https://helix.fusion.dev/components/radio-group
|
|
38
|
-
*
|
|
39
|
-
* @slot - The radios.
|
|
40
|
-
* @slot hint - Richer than a string.
|
|
41
|
-
* @slot label - Richer than a string, where the question is more than one.
|
|
42
|
-
*
|
|
43
|
-
* @event {Event} change - Announced once the value has moved.
|
|
44
|
-
* @event {HelixBeforeChangeEvent} helix-before-change - Fired when the answer changes, and cancelable.
|
|
45
|
-
* @event {Event} input - Announced once the value has moved.
|
|
46
|
-
*
|
|
47
|
-
* @csspart feedback - What is wrong with it.
|
|
48
|
-
* @csspart hint - Help under the set.
|
|
49
|
-
* @csspart items - The choices, which is what `orientation` lays out.
|
|
50
|
-
* @csspart label - The question, over the set.
|
|
51
|
-
*/
|
|
52
11
|
let HelixHeadlessRadioGroup = class HelixHeadlessRadioGroup extends MixinControllerValue(MixinField(HelixFormElement)) {
|
|
53
12
|
constructor() {
|
|
54
13
|
super(...arguments);
|
|
55
|
-
/* Moving and choosing are one act, which is the radio group pattern and is
|
|
56
|
-
what this controller exists to stop being written per component. */
|
|
57
|
-
/* Registered rather than held. A controller adds itself to the host, so there
|
|
58
|
-
is no reference to keep, and nothing here talks to it after construction.
|
|
59
|
-
`protected` rather than `private` because a private field nothing reads is
|
|
60
|
-
an unused one, and the subclass tier is where a consumer would reach for
|
|
61
|
-
it. */
|
|
62
14
|
this.keys = new KeyboardNavigationController(this, { items: () => this.items });
|
|
63
|
-
/* Stated once, and the group never thinks about it again. Hand calls from
|
|
64
|
-
each method that could disable an item reach different paths per
|
|
65
|
-
component; the controller is what stops that being written per group. */
|
|
66
15
|
this.cascade = new DisabledCascadeController(this, {
|
|
67
16
|
items: () => this.items,
|
|
68
17
|
});
|
|
69
|
-
/**
|
|
70
|
-
* Announces before it applies, so a listener can refuse the change.
|
|
71
|
-
*
|
|
72
|
-
* The item has not moved by the time this runs, unlike a native input, so
|
|
73
|
-
* refusing is simply not writing the value: there is nothing to put back.
|
|
74
|
-
*/
|
|
75
18
|
this.handleSelection = (event) => {
|
|
76
19
|
event.stopPropagation();
|
|
77
20
|
const next = event.detail.value;
|
|
@@ -81,25 +24,14 @@ let HelixHeadlessRadioGroup = class HelixHeadlessRadioGroup extends MixinControl
|
|
|
81
24
|
this.requestValue(next);
|
|
82
25
|
};
|
|
83
26
|
}
|
|
84
|
-
/** The radios, in light-DOM order. Client only: the server has no tree. */
|
|
85
27
|
get items() {
|
|
86
28
|
return [...this.children].filter((child) => child instanceof HelixHeadlessRadio);
|
|
87
29
|
}
|
|
88
|
-
/** Writes the answer onto the items, and nothing onto the host. */
|
|
89
30
|
place() {
|
|
90
31
|
const items = this.items;
|
|
91
32
|
for (const item of items) {
|
|
92
33
|
item.selected = item.value === this.value;
|
|
93
34
|
}
|
|
94
|
-
/* Nothing chosen yet, so the first item is the tab stop. A group where
|
|
95
|
-
every item is `tabindex="-1"` is a group the keyboard cannot enter, and
|
|
96
|
-
an answered group that keeps the entry stop carries two, so tabbing
|
|
97
|
-
through a form stops twice on one question and the first stop is on an
|
|
98
|
-
item that is not the answer.
|
|
99
|
-
|
|
100
|
-
`entered` rather than a bare toggle, because this also runs when the
|
|
101
|
-
group is answered and the attribute has to go whether or not this call is
|
|
102
|
-
the one that put it there. */
|
|
103
35
|
const entered = this.value === '' && items.length > 0 ? items[0] : undefined;
|
|
104
36
|
for (const item of items) {
|
|
105
37
|
if (item === entered) {
|
|
@@ -110,7 +42,6 @@ let HelixHeadlessRadioGroup = class HelixHeadlessRadioGroup extends MixinControl
|
|
|
110
42
|
}
|
|
111
43
|
}
|
|
112
44
|
}
|
|
113
|
-
/** Reports the answer and the verdict on it. */
|
|
114
45
|
sync() {
|
|
115
46
|
this.internals.setFormValue(this.value === '' ? null : this.value);
|
|
116
47
|
this.reportRequired(this.value === '', 'Choose one to continue.', 'items');
|
|
@@ -125,16 +56,7 @@ let HelixHeadlessRadioGroup = class HelixHeadlessRadioGroup extends MixinControl
|
|
|
125
56
|
}
|
|
126
57
|
willUpdate(changed) {
|
|
127
58
|
super.willUpdate(changed);
|
|
128
|
-
// Through internals, so nothing lands on the host as an attribute a React
|
|
129
|
-
// consumer's server markup never had.
|
|
130
|
-
/* The string half, which is what a server can write. `linkHost` adds the
|
|
131
|
-
element references on the client, and those win where a consumer slotted
|
|
132
|
-
a label richer than a string. */
|
|
133
59
|
hostAria(this.internals, { label: this.label, role: 'radiogroup' });
|
|
134
|
-
/* `required` reflects, sets validity and paints the asterisk the field
|
|
135
|
-
sheet draws, and tells the accessibility tree nothing on its own. Without
|
|
136
|
-
this a reader meets a mark beside the label they cannot see, and no
|
|
137
|
-
announcement of why the form would refuse the group. */
|
|
138
60
|
this.internals.ariaRequired = this.required ? 'true' : null;
|
|
139
61
|
}
|
|
140
62
|
updated(changed) {
|
|
@@ -144,9 +66,6 @@ let HelixHeadlessRadioGroup = class HelixHeadlessRadioGroup extends MixinControl
|
|
|
144
66
|
this.linkHost();
|
|
145
67
|
}
|
|
146
68
|
render() {
|
|
147
|
-
/* The choices sit in an element of their own, because `orientation` lays
|
|
148
|
-
out whatever is in the flex container and on the host that would be the
|
|
149
|
-
label and the hint as well. */
|
|
150
69
|
return html `${this.renderGroupLabel()}
|
|
151
70
|
<div part="items">
|
|
152
71
|
<slot @slotchange=${() => this.place()}></slot>
|