@fusion.dev/helix-headless 0.143.0 → 0.144.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 +8551 -375
- package/dist/area-chart/root/area-chart-root.d.ts +12 -4
- package/dist/area-chart/root/area-chart-root.js +24 -7
- package/dist/avatar/root/avatar-root.d.ts +14 -0
- package/dist/avatar/root/avatar-root.js +23 -9
- package/dist/badge/root/badge-root.d.ts +11 -7
- package/dist/badge/root/badge-root.js +1 -1
- package/dist/bar-chart/root/bar-chart-root.d.ts +12 -3
- package/dist/bar-chart/root/bar-chart-root.js +17 -10
- package/dist/button/root/button-root.d.ts +47 -1
- package/dist/button/root/button-root.js +38 -4
- package/dist/callout/root/callout-root.d.ts +18 -1
- package/dist/callout/root/callout-root.js +15 -4
- package/dist/card/root/card-root.d.ts +30 -1
- package/dist/card/root/card-root.js +38 -5
- package/dist/checkbox/root/checkbox-root.js +2 -0
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +53 -3
- package/dist/checkbox-group/root/checkbox-group-root.js +71 -22
- package/dist/code-block/lines.d.ts +13 -0
- package/dist/code-block/lines.js +2 -0
- package/dist/code-block/root/code-block-root.d.ts +30 -0
- package/dist/code-block/root/code-block-root.js +23 -2
- package/dist/color-input/root/color-input-root.d.ts +34 -1
- package/dist/color-input/root/color-input-root.js +23 -4
- package/dist/color-picker/channels.d.ts +26 -0
- package/dist/color-picker/channels.js +84 -0
- package/dist/color-picker/root/color-picker-root.d.ts +81 -5
- package/dist/color-picker/root/color-picker-root.js +103 -10
- package/dist/combobox/root/combobox-root.d.ts +38 -3
- package/dist/combobox/root/combobox-root.js +64 -2
- package/dist/command/root/command-root.d.ts +30 -4
- package/dist/command/root/command-root.js +33 -3
- package/dist/copy-button/from.d.ts +22 -0
- package/dist/copy-button/from.js +28 -0
- package/dist/copy-button/root/copy-button-root.d.ts +27 -1
- package/dist/copy-button/root/copy-button-root.js +43 -7
- package/dist/date-input/root/date-input-root.d.ts +73 -4
- package/dist/date-input/root/date-input-root.js +93 -43
- package/dist/date-picker/root/date-picker-root.d.ts +145 -1
- package/dist/date-picker/root/date-picker-root.js +239 -48
- package/dist/dialog/description.d.ts +9 -0
- package/dist/dialog/description.js +14 -0
- package/dist/dialog/root/dialog-root.d.ts +64 -1
- package/dist/dialog/root/dialog-root.js +49 -23
- package/dist/donut-chart/root/donut-chart-root.d.ts +25 -4
- package/dist/donut-chart/root/donut-chart-root.js +21 -6
- package/dist/drawer/root/drawer-root.d.ts +63 -2
- package/dist/drawer/root/drawer-root.js +95 -19
- package/dist/drawer/swipe-start.d.ts +9 -0
- package/dist/drawer/swipe-start.js +12 -0
- package/dist/element-events.generated.js +8 -5
- package/dist/empty/root/empty-root.d.ts +1 -1
- package/dist/file-input/root/file-input-root.d.ts +47 -0
- package/dist/file-input/root/file-input-root.js +60 -22
- package/dist/file-input/screen.d.ts +35 -0
- package/dist/file-input/screen.js +49 -0
- package/dist/filter-input/root/filter-input-root.d.ts +38 -4
- package/dist/filter-input/root/filter-input-root.js +67 -3
- package/dist/filter-input/suggest.d.ts +17 -0
- package/dist/filter-input/suggest.js +23 -0
- package/dist/format-date/options.d.ts +33 -0
- package/dist/format-date/options.js +42 -0
- package/dist/format-date/root/format-date-root.d.ts +51 -0
- package/dist/format-date/root/format-date-root.js +58 -38
- package/dist/heatmap/root/heatmap-root.d.ts +23 -6
- package/dist/heatmap/root/heatmap-root.js +24 -13
- package/dist/icon/index.d.ts +2 -0
- package/dist/icon/index.js +2 -0
- package/dist/icon/registry.d.ts +37 -0
- package/dist/icon/registry.js +83 -0
- package/dist/icon/root/icon-root.d.ts +30 -4
- package/dist/icon/root/icon-root.js +41 -6
- package/dist/icon/root/index.d.ts +2 -0
- package/dist/icon/root/index.js +2 -0
- package/dist/icon/set.d.ts +1 -0
- package/dist/icon/set.js +3 -0
- package/dist/image/root/image-root.d.ts +14 -0
- package/dist/image/root/image-root.js +15 -2
- package/dist/input/root/input-root.d.ts +11 -0
- package/dist/input/root/input-root.js +15 -0
- package/dist/kbd/root/kbd-root.d.ts +20 -13
- package/dist/kbd/root/kbd-root.js +35 -37
- package/dist/kbd/shortcut.d.ts +18 -0
- package/dist/kbd/shortcut.js +35 -0
- package/dist/legend/hidden-series.d.ts +8 -0
- package/dist/legend/hidden-series.js +13 -0
- package/dist/legend/root/legend-root.d.ts +56 -0
- package/dist/legend/root/legend-root.js +54 -5
- package/dist/lightbox/item/lightbox-item.d.ts +15 -0
- package/dist/lightbox/item/lightbox-item.js +10 -1
- package/dist/lightbox/root/lightbox-root.d.ts +61 -1
- package/dist/lightbox/root/lightbox-root.js +94 -4
- package/dist/line-chart/root/line-chart-root.d.ts +11 -3
- package/dist/line-chart/root/line-chart-root.js +19 -5
- package/dist/listbox/range.d.ts +17 -0
- package/dist/listbox/range.js +5 -0
- package/dist/listbox/root/listbox-root.d.ts +74 -3
- package/dist/listbox/root/listbox-root.js +87 -9
- package/dist/loader/busy.d.ts +17 -0
- package/dist/loader/busy.js +24 -0
- package/dist/loader/root/loader-root.d.ts +37 -0
- package/dist/loader/root/loader-root.js +52 -3
- package/dist/meter/root/meter-root.d.ts +7 -0
- package/dist/meter/root/meter-root.js +6 -3
- package/dist/notifications/feed-keys.d.ts +10 -0
- package/dist/notifications/feed-keys.js +14 -0
- package/dist/notifications/notification/notification.d.ts +17 -0
- package/dist/notifications/notification/notification.js +21 -2
- package/dist/notifications/root/notifications-root.d.ts +42 -0
- package/dist/notifications/root/notifications-root.js +60 -1
- package/dist/number-input/root/number-input-root.d.ts +47 -0
- package/dist/number-input/root/number-input-root.js +58 -22
- package/dist/object-inspector/root/object-inspector-root.d.ts +1 -0
- package/dist/object-inspector/root/object-inspector-root.js +1 -1
- package/dist/option/root/option-root.d.ts +54 -5
- package/dist/option/root/option-root.js +34 -5
- package/dist/pin-input/format.d.ts +7 -0
- package/dist/pin-input/format.js +16 -0
- package/dist/pin-input/root/pin-input-root.d.ts +21 -0
- package/dist/pin-input/root/pin-input-root.js +29 -2
- package/dist/popover/root/popover-root.d.ts +62 -4
- package/dist/popover/root/popover-root.js +53 -5
- package/dist/progress/root/progress-root.d.ts +7 -0
- package/dist/progress/root/progress-root.js +9 -3
- package/dist/qr-code/export.d.ts +31 -0
- package/dist/qr-code/export.js +10 -0
- package/dist/qr-code/root/qr-code-root.d.ts +26 -0
- package/dist/qr-code/root/qr-code-root.js +49 -2
- package/dist/radio/group/radio-group.d.ts +23 -0
- package/dist/radio/group/radio-group.js +18 -0
- package/dist/rating/root/rating-root.d.ts +25 -0
- package/dist/rating/root/rating-root.js +45 -3
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +17 -3
- package/dist/scatter-chart/root/scatter-chart-root.js +25 -7
- package/dist/select/root/select-root.d.ts +24 -0
- package/dist/select/root/select-root.js +20 -6
- package/dist/skeleton/root/skeleton-root.d.ts +25 -0
- package/dist/skeleton/root/skeleton-root.js +17 -2
- package/dist/slider/root/slider-root.d.ts +39 -0
- package/dist/slider/root/slider-root.js +45 -9
- package/dist/stat/root/stat-root.d.ts +2 -0
- package/dist/stat/root/stat-root.js +1 -1
- package/dist/steps/step/step.d.ts +1 -0
- package/dist/steps/step/step.js +1 -1
- package/dist/switch/root/switch-root.d.ts +12 -2
- package/dist/switch/root/switch-root.js +6 -1
- package/dist/tag/group/tag-group.d.ts +107 -0
- package/dist/tag/group/tag-group.js +157 -0
- package/dist/tag/index.d.ts +1 -0
- package/dist/tag/index.js +1 -0
- package/dist/tag/root/tag-root.d.ts +60 -0
- package/dist/tag/root/tag-root.js +74 -18
- package/dist/tag/selection.d.ts +10 -0
- package/dist/tag/selection.js +12 -0
- package/dist/textarea/grow.d.ts +22 -0
- package/dist/textarea/grow.js +20 -0
- package/dist/textarea/root/textarea-root.d.ts +9 -0
- package/dist/textarea/root/textarea-root.js +9 -1
- package/dist/time-input/root/time-input-root.d.ts +44 -5
- package/dist/time-input/root/time-input-root.js +69 -39
- package/dist/time-picker/root/time-picker-root.d.ts +54 -8
- package/dist/time-picker/root/time-picker-root.js +121 -91
- package/dist/toast/hotkey.d.ts +22 -0
- package/dist/toast/hotkey.js +23 -0
- package/dist/toast/item/toast-item.d.ts +16 -1
- package/dist/toast/item/toast-item.js +41 -13
- package/dist/toast/root/toast-root.d.ts +42 -8
- package/dist/toast/root/toast-root.js +64 -4
- package/dist/toggle/group/toggle-group.d.ts +57 -15
- package/dist/toggle/group/toggle-group.js +69 -9
- package/dist/toggle/root/toggle-root.d.ts +12 -1
- package/dist/toggle/root/toggle-root.js +11 -3
- package/dist/tooltip/focus-target.d.ts +6 -0
- package/dist/tooltip/focus-target.js +9 -0
- package/dist/tooltip/root/tooltip-root.d.ts +42 -17
- package/dist/tooltip/root/tooltip-root.js +50 -9
- package/dist/tour/naming.d.ts +30 -0
- package/dist/tour/naming.js +10 -0
- package/dist/tour/root/tour-root.d.ts +35 -0
- package/dist/tour/root/tour-root.js +56 -5
- package/dist/tour/stop/tour-stop.d.ts +53 -8
- package/dist/tour/stop/tour-stop.js +45 -15
- package/dist/tour/wait.d.ts +11 -0
- package/dist/tour/wait.js +19 -0
- package/dist/trend/root/trend-root.d.ts +13 -0
- package/dist/trend/root/trend-root.js +14 -9
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +4862 -254
- package/types/svelte.d.ts +1 -0
- package/types/vue.d.ts +1149 -67
|
@@ -4,13 +4,37 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, deepActiveElement, deepContains, focusableElements, HelixElement, hostAria, scrollHolders, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, deepActiveElement, deepContains, FocusScopeController, fillTemplate, focusableElements, HelixElement, hostAria, scrollHolders, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
|
+
import { matchesHotkey, parseHotkey } from '../hotkey.js';
|
|
10
11
|
let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
11
12
|
constructor() {
|
|
12
13
|
super(...arguments);
|
|
13
|
-
this.
|
|
14
|
+
this.hotkey = 'F8';
|
|
15
|
+
this.label = 'Notifications ({hotkey})';
|
|
16
|
+
this.scope = new FocusScopeController(this, {
|
|
17
|
+
contents: () => this.showing.flatMap((one) => {
|
|
18
|
+
const target = focusTarget(one);
|
|
19
|
+
return target ? [target] : [];
|
|
20
|
+
}),
|
|
21
|
+
surface: () => this.region,
|
|
22
|
+
});
|
|
23
|
+
this.handleHotkey = (event) => {
|
|
24
|
+
if (event.defaultPrevented || !matchesHotkey(event, parseHotkey(this.hotkey))) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (this.scope.holdsFocus) {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
this.scope.leave();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (this.showing.length > 0) {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
this.scope.enter();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
this.made = new WeakSet();
|
|
14
38
|
this.handleFocusIn = (event) => {
|
|
15
39
|
const from = event.relatedTarget;
|
|
16
40
|
if (from instanceof HTMLElement && !deepContains(this, from)) {
|
|
@@ -57,11 +81,41 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
|
57
81
|
}
|
|
58
82
|
willUpdate(changed) {
|
|
59
83
|
super.willUpdate(changed);
|
|
60
|
-
hostAria(this.internals, {
|
|
84
|
+
hostAria(this.internals, {
|
|
85
|
+
label: fillTemplate(this.label, { hotkey: this.hotkey }),
|
|
86
|
+
role: 'region',
|
|
87
|
+
});
|
|
61
88
|
this.internals.ariaLive = 'polite';
|
|
62
89
|
this.internals.ariaRelevant = 'additions';
|
|
63
90
|
this.internals.ariaAtomic = 'false';
|
|
64
91
|
}
|
|
92
|
+
add(message, options = {}) {
|
|
93
|
+
const tier = this.constructor.tier;
|
|
94
|
+
const item = document.createElement(`${tier}toast-item`);
|
|
95
|
+
item.textContent = message;
|
|
96
|
+
if (options.tone) {
|
|
97
|
+
item.setAttribute('tone', options.tone);
|
|
98
|
+
}
|
|
99
|
+
if (options.duration !== undefined) {
|
|
100
|
+
item.setAttribute('duration', String(options.duration));
|
|
101
|
+
}
|
|
102
|
+
if (options.dismissible ?? true) {
|
|
103
|
+
item.toggleAttribute('dismissible', true);
|
|
104
|
+
}
|
|
105
|
+
this.made.add(item);
|
|
106
|
+
this.append(item);
|
|
107
|
+
return item;
|
|
108
|
+
}
|
|
109
|
+
sweep() {
|
|
110
|
+
for (const one of [...this.children]) {
|
|
111
|
+
if (!(one instanceof HTMLElement) || !one.hidden || !this.made.has(one)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
this.made.delete(one);
|
|
115
|
+
const running = typeof one.getAnimations === 'function' ? one.getAnimations({ subtree: true }) : [];
|
|
116
|
+
void Promise.allSettled(running.map((animation) => animation.finished)).then(() => one.remove());
|
|
117
|
+
}
|
|
118
|
+
}
|
|
65
119
|
get showing() {
|
|
66
120
|
return [...this.children].filter((one) => one instanceof HTMLElement && !one.hidden);
|
|
67
121
|
}
|
|
@@ -129,8 +183,10 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
|
129
183
|
super.connectedCallback();
|
|
130
184
|
this.addEventListener('focusin', this.handleFocusIn);
|
|
131
185
|
this.addEventListener('helix-dismiss', this.handleDismiss);
|
|
186
|
+
document.addEventListener('keydown', this.handleHotkey);
|
|
132
187
|
this.watcher = new MutationObserver((records) => {
|
|
133
188
|
this.place(records.flatMap((record) => [...record.addedNodes]));
|
|
189
|
+
this.sweep();
|
|
134
190
|
});
|
|
135
191
|
this.watcher.observe(this, {
|
|
136
192
|
attributeFilter: ['hidden'],
|
|
@@ -146,6 +202,7 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
|
146
202
|
super.disconnectedCallback();
|
|
147
203
|
this.removeEventListener('focusin', this.handleFocusIn);
|
|
148
204
|
this.removeEventListener('helix-dismiss', this.handleDismiss);
|
|
205
|
+
document.removeEventListener('keydown', this.handleHotkey);
|
|
149
206
|
this.watcher?.disconnect();
|
|
150
207
|
this.watcher = undefined;
|
|
151
208
|
}
|
|
@@ -154,9 +211,12 @@ let HelixHeadlessToast = class HelixHeadlessToast extends HelixElement {
|
|
|
154
211
|
this.place();
|
|
155
212
|
}
|
|
156
213
|
render() {
|
|
157
|
-
return html `<div part="region" popover="manual"><slot></slot></div>`;
|
|
214
|
+
return html `<div part="region" popover="manual" tabindex="-1"><slot></slot></div>`;
|
|
158
215
|
}
|
|
159
216
|
};
|
|
217
|
+
__decorate([
|
|
218
|
+
property()
|
|
219
|
+
], HelixHeadlessToast.prototype, "hotkey", void 0);
|
|
160
220
|
__decorate([
|
|
161
221
|
property()
|
|
162
222
|
], HelixHeadlessToast.prototype, "label", void 0);
|
|
@@ -14,6 +14,17 @@ declare const HelixHeadlessToggleGroup_base: (new (...args: any[]) => import("@f
|
|
|
14
14
|
* Measurement happens on the client, so the server renders the indicator hidden
|
|
15
15
|
* and it fades in once measured.
|
|
16
16
|
*
|
|
17
|
+
* `selection-mode="multiple"` makes the group a toolbar of buttons that each
|
|
18
|
+
* say whether they are pressed with `aria-pressed`, as React Aria's
|
|
19
|
+
* ToggleButtonGroup and Radix's ToggleGroup do. A press presses or releases
|
|
20
|
+
* one item, the arrow keys move focus without pressing, and `value` is the
|
|
21
|
+
* pressed values, comma separated, which `values` also gives as a list. The
|
|
22
|
+
* form receives one entry per pressed value. The indicator stays hidden, and
|
|
23
|
+
* each pressed item is marked with `:state(pressed)`.
|
|
24
|
+
*
|
|
25
|
+
* `orientation="vertical"` runs the items down a column, says so to the
|
|
26
|
+
* accessibility tree, and the up and down arrows move between them.
|
|
27
|
+
*
|
|
17
28
|
* @since 0.1.0
|
|
18
29
|
* @status stable
|
|
19
30
|
* @aliases SegmentedControl, Segmented, Navigation, Switcher
|
|
@@ -98,6 +109,11 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
|
|
|
98
109
|
* goes to the first item that can be chosen, as in `Radio.Group`.
|
|
99
110
|
*/
|
|
100
111
|
private entry;
|
|
112
|
+
/** The item focus was last on, in a toolbar, which holds its tab stop. */
|
|
113
|
+
private lastFocused;
|
|
114
|
+
private readonly noteFocus;
|
|
115
|
+
/** Whether several items can be pressed at once. */
|
|
116
|
+
private get multiple();
|
|
101
117
|
/**
|
|
102
118
|
* Whether the group holds a tab stop of its own.
|
|
103
119
|
*
|
|
@@ -110,6 +126,37 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
|
|
|
110
126
|
* reason to set it.
|
|
111
127
|
*/
|
|
112
128
|
focusable: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Where the group's label sits. `start` puts it on the track's line.
|
|
131
|
+
*
|
|
132
|
+
* Absent is above, which is what a field looks like and what almost every
|
|
133
|
+
* group wants. `start` is for a row of controls that are labeled rather than
|
|
134
|
+
* a form of fields: a toolbar reads as one line, and a label stacked over
|
|
135
|
+
* every group in it turns one row into two.
|
|
136
|
+
*
|
|
137
|
+
* **A placement rather than a slot, and that is the whole point of it.** A
|
|
138
|
+
* `start` slot would let a consumer put the word there, and would also let
|
|
139
|
+
* them put it there *instead of* setting `label`, which is the group's
|
|
140
|
+
* accessible name: the set would then be named for nobody using a screen
|
|
141
|
+
* reader while looking correct to everybody else. Worse, setting both draws
|
|
142
|
+
* it twice. One property moves the label the consumer already has to give,
|
|
143
|
+
* and cannot be used to remove it.
|
|
144
|
+
*/
|
|
145
|
+
labelPlacement?: 'start';
|
|
146
|
+
/**
|
|
147
|
+
* The direction the items run. `vertical` puts them in a column, and the up
|
|
148
|
+
* and down arrows move between them.
|
|
149
|
+
*
|
|
150
|
+
* Optional, so the attribute is absent until a consumer writes it.
|
|
151
|
+
*/
|
|
152
|
+
orientation?: 'horizontal' | 'vertical';
|
|
153
|
+
/**
|
|
154
|
+
* Whether one item is chosen, as a radio group, or several can be pressed,
|
|
155
|
+
* as a toolbar of buttons with `aria-pressed`.
|
|
156
|
+
*
|
|
157
|
+
* Optional, so the attribute is absent until a consumer writes it.
|
|
158
|
+
*/
|
|
159
|
+
selectionMode?: 'multiple' | 'single';
|
|
113
160
|
/**
|
|
114
161
|
* Focus goes to the item that holds the stop, and to the item that would
|
|
115
162
|
* hold it while a toolbar holds the stop instead, so a label press or a
|
|
@@ -132,6 +179,11 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
|
|
|
132
179
|
protected firstUpdated(changed: PropertyValues): void;
|
|
133
180
|
protected updated(changed: PropertyValues): void;
|
|
134
181
|
protected willUpdate(changed: PropertyValues): void;
|
|
182
|
+
/**
|
|
183
|
+
* What the form receives: the value, or under `selection-mode="multiple"`
|
|
184
|
+
* one entry per pressed value, as a native `<select multiple>` sends.
|
|
185
|
+
*/
|
|
186
|
+
private formValue;
|
|
135
187
|
/**
|
|
136
188
|
* The verdict on the answer `required` asks for.
|
|
137
189
|
*
|
|
@@ -145,22 +197,12 @@ export declare class HelixHeadlessToggleGroup extends HelixHeadlessToggleGroup_b
|
|
|
145
197
|
*/
|
|
146
198
|
private validate;
|
|
147
199
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* group wants. `start` is for a row of controls that are labeled rather than
|
|
152
|
-
* a form of fields: a toolbar reads as one line, and a label stacked over
|
|
153
|
-
* every group in it turns one row into two.
|
|
154
|
-
*
|
|
155
|
-
* **A placement rather than a slot, and that is the whole point of it.** A
|
|
156
|
-
* `start` slot would let a consumer put the word there, and would also let
|
|
157
|
-
* them put it there *instead of* setting `label`, which is the group's
|
|
158
|
-
* accessible name: the set would then be named for nobody using a screen
|
|
159
|
-
* reader while looking correct to everybody else. Worse, setting both draws
|
|
160
|
-
* it twice. One property moves the label the consumer already has to give,
|
|
161
|
-
* and cannot be used to remove it.
|
|
200
|
+
* The pressed values as a list, under `selection-mode="multiple"`, in the
|
|
201
|
+
* order the items are written. Written, it presses exactly those items.
|
|
202
|
+
* Where one item is chosen, it is that one value, or none.
|
|
162
203
|
*/
|
|
163
|
-
|
|
204
|
+
get values(): string[];
|
|
205
|
+
set values(next: string[]);
|
|
164
206
|
/**
|
|
165
207
|
* The row the track sits in, so something can sit beside it.
|
|
166
208
|
*
|
|
@@ -4,21 +4,32 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { customElement, DisabledCascadeController, HelixFormElement, hostAria, IndicatorController, KeyboardNavigationController, MixinControllerValue, MixinField, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { commaList, customElement, DisabledCascadeController, HelixFormElement, hostAria, IndicatorController, KeyboardNavigationController, MixinControllerValue, MixinField, rovingStop, toggleValue, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { HelixHeadlessToggle } from '../root/toggle-root.js';
|
|
11
11
|
let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinControllerValue(MixinField(HelixFormElement)) {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments);
|
|
14
|
+
this.lastFocused = undefined;
|
|
15
|
+
this.noteFocus = (event) => {
|
|
16
|
+
const item = this.items.find((one) => one === event.target);
|
|
17
|
+
if (this.multiple && item && item !== this.lastFocused) {
|
|
18
|
+
this.lastFocused = item;
|
|
19
|
+
this.sync();
|
|
20
|
+
}
|
|
21
|
+
};
|
|
14
22
|
this.focusable = true;
|
|
15
23
|
this.indicator = new IndicatorController(this, {
|
|
16
|
-
active: () => this.items.find((item) => item.value === this.value),
|
|
24
|
+
active: () => this.multiple ? undefined : this.items.find((item) => item.value === this.value),
|
|
17
25
|
indicator: () => this.partNamed('indicator'),
|
|
18
26
|
observe: () => this.items,
|
|
19
27
|
surface: () => this.partNamed('track'),
|
|
20
28
|
});
|
|
21
|
-
this.keys = new KeyboardNavigationController(this, {
|
|
29
|
+
this.keys = new KeyboardNavigationController(this, {
|
|
30
|
+
items: () => this.items,
|
|
31
|
+
selects: () => !this.multiple,
|
|
32
|
+
});
|
|
22
33
|
this.cascade = new DisabledCascadeController(this, {
|
|
23
34
|
items: () => this.items,
|
|
24
35
|
});
|
|
@@ -27,7 +38,10 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
27
38
|
if (event.defaultPrevented) {
|
|
28
39
|
return;
|
|
29
40
|
}
|
|
30
|
-
|
|
41
|
+
const value = event.detail.value;
|
|
42
|
+
this.requestValue(this.multiple
|
|
43
|
+
? toggleValue(this.values, value, this.items.map((item) => item.value)).join(',')
|
|
44
|
+
: value);
|
|
31
45
|
});
|
|
32
46
|
};
|
|
33
47
|
}
|
|
@@ -41,16 +55,28 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
41
55
|
}
|
|
42
56
|
}
|
|
43
57
|
sync() {
|
|
44
|
-
const active = this.items.find((item) => item.value === this.value);
|
|
58
|
+
const active = this.multiple ? undefined : this.items.find((item) => item.value === this.value);
|
|
59
|
+
const pressed = new Set(this.values);
|
|
45
60
|
const entry = this.focusable ? this.entry() : undefined;
|
|
46
61
|
for (const item of this.items) {
|
|
47
62
|
item.selected = item === active;
|
|
63
|
+
item.pressed = this.multiple ? pressed.has(item.value) : undefined;
|
|
48
64
|
item.focusable = item === entry;
|
|
49
65
|
}
|
|
50
66
|
}
|
|
51
67
|
entry() {
|
|
52
|
-
const
|
|
53
|
-
|
|
68
|
+
const items = this.items;
|
|
69
|
+
if (this.multiple) {
|
|
70
|
+
return rovingStop(items, this.lastFocused, {
|
|
71
|
+
disabled: (item) => item.disabled,
|
|
72
|
+
selected: (item) => item.pressed === true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const active = items.find((item) => item.value === this.value);
|
|
76
|
+
return active && !active.disabled ? active : items.find((item) => !item.disabled);
|
|
77
|
+
}
|
|
78
|
+
get multiple() {
|
|
79
|
+
return this.selectionMode === 'multiple';
|
|
54
80
|
}
|
|
55
81
|
get focusTarget() {
|
|
56
82
|
return (super.focusTarget ??
|
|
@@ -64,6 +90,9 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
64
90
|
if (this.value === '') {
|
|
65
91
|
return false;
|
|
66
92
|
}
|
|
93
|
+
if (this.multiple) {
|
|
94
|
+
return this.children === undefined || this.values.length > 0;
|
|
95
|
+
}
|
|
67
96
|
if (this.children === undefined) {
|
|
68
97
|
return true;
|
|
69
98
|
}
|
|
@@ -73,10 +102,12 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
73
102
|
connectedCallback() {
|
|
74
103
|
super.connectedCallback();
|
|
75
104
|
this.addEventListener('helix-select', this.handleSelection);
|
|
105
|
+
this.addEventListener('focusin', this.noteFocus);
|
|
76
106
|
}
|
|
77
107
|
disconnectedCallback() {
|
|
78
108
|
super.disconnectedCallback();
|
|
79
109
|
this.removeEventListener('helix-select', this.handleSelection);
|
|
110
|
+
this.removeEventListener('focusin', this.noteFocus);
|
|
80
111
|
}
|
|
81
112
|
firstUpdated(changed) {
|
|
82
113
|
super.firstUpdated(changed);
|
|
@@ -92,12 +123,32 @@ let HelixHeadlessToggleGroup = class HelixHeadlessToggleGroup extends MixinContr
|
|
|
92
123
|
willUpdate(changed) {
|
|
93
124
|
super.willUpdate(changed);
|
|
94
125
|
this.adopt();
|
|
95
|
-
hostAria(this.internals, { label: this.label, role: 'radiogroup' });
|
|
96
|
-
this.internals.
|
|
126
|
+
hostAria(this.internals, { label: this.label, role: this.multiple ? 'toolbar' : 'radiogroup' });
|
|
127
|
+
this.internals.ariaOrientation = this.orientation ?? null;
|
|
128
|
+
this.internals.setFormValue(this.formValue());
|
|
129
|
+
}
|
|
130
|
+
formValue() {
|
|
131
|
+
if (!this.answered) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
if (!this.multiple || this.name === '') {
|
|
135
|
+
return this.value;
|
|
136
|
+
}
|
|
137
|
+
const data = new FormData();
|
|
138
|
+
for (const value of this.values) {
|
|
139
|
+
data.append(this.name, value);
|
|
140
|
+
}
|
|
141
|
+
return data;
|
|
97
142
|
}
|
|
98
143
|
validate() {
|
|
99
144
|
this.reportRequired(!this.answered, 'Choose one to continue.', 'track');
|
|
100
145
|
}
|
|
146
|
+
get values() {
|
|
147
|
+
return this.multiple ? commaList(this.value) : this.value === '' ? [] : [this.value];
|
|
148
|
+
}
|
|
149
|
+
set values(next) {
|
|
150
|
+
this.value = next.join(',');
|
|
151
|
+
}
|
|
101
152
|
render() {
|
|
102
153
|
const inside = this.labelPlacement === 'start';
|
|
103
154
|
const label = this.renderGroupLabel();
|
|
@@ -127,6 +178,15 @@ __decorate([
|
|
|
127
178
|
__decorate([
|
|
128
179
|
property({ attribute: 'label-placement', reflect: true })
|
|
129
180
|
], HelixHeadlessToggleGroup.prototype, "labelPlacement", void 0);
|
|
181
|
+
__decorate([
|
|
182
|
+
property({ reflect: true })
|
|
183
|
+
], HelixHeadlessToggleGroup.prototype, "orientation", void 0);
|
|
184
|
+
__decorate([
|
|
185
|
+
property({ attribute: 'selection-mode', reflect: true })
|
|
186
|
+
], HelixHeadlessToggleGroup.prototype, "selectionMode", void 0);
|
|
187
|
+
__decorate([
|
|
188
|
+
property({ attribute: false })
|
|
189
|
+
], HelixHeadlessToggleGroup.prototype, "values", null);
|
|
130
190
|
HelixHeadlessToggleGroup = __decorate([
|
|
131
191
|
customElement('helix-headless-toggle-group')
|
|
132
192
|
], HelixHeadlessToggleGroup);
|
|
@@ -5,7 +5,9 @@ declare const HelixHeadlessToggle_base: (new (...args: any[]) => import("@fusion
|
|
|
5
5
|
* @summary One option in a toggle group.
|
|
6
6
|
*
|
|
7
7
|
* @description
|
|
8
|
-
* Renders a `<button role="radio">` for use as a form control.
|
|
8
|
+
* Renders a `<button role="radio">` for use as a form control. In a group with
|
|
9
|
+
* `selection-mode="multiple"` it is a `<button>` that says whether it is
|
|
10
|
+
* pressed with `aria-pressed`.
|
|
9
11
|
*
|
|
10
12
|
* Do not use it for navigation. For navigation, use links marked with
|
|
11
13
|
* `aria-current` and styled by the `data-navigation-item` recipe in the base
|
|
@@ -23,6 +25,7 @@ declare const HelixHeadlessToggle_base: (new (...args: any[]) => import("@fusion
|
|
|
23
25
|
*
|
|
24
26
|
* @csspart item - The radio button.
|
|
25
27
|
*
|
|
28
|
+
* @cssstate pressed - The item is pressed, in a group where several can be.
|
|
26
29
|
* @cssstate selected - The item is chosen.
|
|
27
30
|
*/
|
|
28
31
|
export declare class HelixHeadlessToggle extends HelixHeadlessToggle_base {
|
|
@@ -69,6 +72,14 @@ export declare class HelixHeadlessToggle extends HelixHeadlessToggle_base {
|
|
|
69
72
|
* React consumer's hydration reports and does not patch up.
|
|
70
73
|
*/
|
|
71
74
|
focusable?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Whether the item is pressed, in a group where several can be, and
|
|
77
|
+
* `undefined` in a group where one is chosen.
|
|
78
|
+
*
|
|
79
|
+
* Written by the group, like `selected`, so no attribute.
|
|
80
|
+
*/
|
|
81
|
+
pressed?: boolean | undefined;
|
|
82
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
72
83
|
/**
|
|
73
84
|
* Asks for the render that follows a hydration, so the stop stops being every
|
|
74
85
|
* choice's.
|
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { customElement, HelixElement, HostClickController, MixinSelectableItem, } from '@fusion.dev/helix-core';
|
|
8
|
-
import { html } from 'lit';
|
|
8
|
+
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(HelixElement) {
|
|
11
11
|
constructor() {
|
|
@@ -18,6 +18,10 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
|
|
|
18
18
|
set defaultSelected(next) {
|
|
19
19
|
this.toggleAttribute('selected', next);
|
|
20
20
|
}
|
|
21
|
+
willUpdate(changed) {
|
|
22
|
+
super.willUpdate(changed);
|
|
23
|
+
this.toggleState('pressed', this.pressed === true);
|
|
24
|
+
}
|
|
21
25
|
firstUpdated(changed) {
|
|
22
26
|
super.firstUpdated(changed);
|
|
23
27
|
this.finishHydration();
|
|
@@ -26,8 +30,9 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
|
|
|
26
30
|
return html `<button
|
|
27
31
|
part="item"
|
|
28
32
|
type="button"
|
|
29
|
-
role
|
|
30
|
-
aria-checked=${this.selected ? 'true' : 'false'}
|
|
33
|
+
role=${this.pressed === undefined ? 'radio' : nothing}
|
|
34
|
+
aria-checked=${this.pressed === undefined ? (this.selected ? 'true' : 'false') : nothing}
|
|
35
|
+
aria-pressed=${this.pressed === undefined ? nothing : String(this.pressed)}
|
|
31
36
|
?disabled=${this.disabled}
|
|
32
37
|
tabindex=${(this.focusable ?? this.selected) || !this.childrenKnown ? '0' : '-1'}
|
|
33
38
|
@click=${() => this.select()}
|
|
@@ -39,6 +44,9 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
|
|
|
39
44
|
__decorate([
|
|
40
45
|
property({ attribute: false })
|
|
41
46
|
], HelixHeadlessToggle.prototype, "focusable", void 0);
|
|
47
|
+
__decorate([
|
|
48
|
+
property({ attribute: false })
|
|
49
|
+
], HelixHeadlessToggle.prototype, "pressed", void 0);
|
|
42
50
|
HelixHeadlessToggle = __decorate([
|
|
43
51
|
customElement('helix-headless-toggle')
|
|
44
52
|
], HelixHeadlessToggle);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The element focus lands on when a trigger is focused: the trigger itself,
|
|
3
|
+
* or, for a component that delegates focus, the control inside its shadow root,
|
|
4
|
+
* followed down through any component nested there.
|
|
5
|
+
*/
|
|
6
|
+
export declare const focusTarget: (trigger: Element) => Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const CONTROL = 'a[href], button, input, select, textarea, [contenteditable], [tabindex]:not([tabindex="-1"])';
|
|
2
|
+
export const focusTarget = (trigger) => {
|
|
3
|
+
const root = trigger.shadowRoot;
|
|
4
|
+
if (!root?.delegatesFocus) {
|
|
5
|
+
return trigger;
|
|
6
|
+
}
|
|
7
|
+
const inner = root.querySelector(CONTROL);
|
|
8
|
+
return inner ? focusTarget(inner) : trigger;
|
|
9
|
+
};
|
|
@@ -10,29 +10,32 @@ declare const HelixHeadlessTooltip_base: (new (...args: any[]) => import("@fusio
|
|
|
10
10
|
* does not clip it and no `z-index` is needed. `FloatingController` positions
|
|
11
11
|
* it and flips it to another side when the preferred side does not fit.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* trigger: an IDREF cannot cross a shadow boundary.
|
|
16
|
-
*
|
|
17
|
-
* To add a description, place the tooltip beside the trigger and name the
|
|
18
|
-
* trigger with `for`:
|
|
13
|
+
* **The tooltip describes its trigger itself, in both forms.** Wrap the
|
|
14
|
+
* trigger, or place the tooltip beside it and name the trigger with `for`:
|
|
19
15
|
*
|
|
20
16
|
* ```html
|
|
21
|
-
* <helix-headless-button id="save"
|
|
22
|
-
* <helix-headless-tooltip
|
|
17
|
+
* <helix-headless-button id="save">Save</helix-headless-button>
|
|
18
|
+
* <helix-headless-tooltip for="save" content="Save your work"></helix-headless-tooltip>
|
|
23
19
|
* ```
|
|
24
20
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
21
|
+
* The link is an element reference on the control that takes focus, and it is
|
|
22
|
+
* never written into your markup before a reader has acted. A trigger that is a
|
|
23
|
+
* Helix component is described at once, inside its own shadow root. A plain
|
|
24
|
+
* element in your markup is described when a reader first points at it or
|
|
25
|
+
* focuses it, which is after any hydration, and only where you have written no
|
|
26
|
+
* `aria-describedby` or `aria-labelledby` of your own. Write
|
|
27
|
+
* `aria-describedby` pointing at the tooltip yourself to have the link in the
|
|
28
|
+
* server's markup. Where the browser has no element references, write it
|
|
29
|
+
* yourself; the tooltip warns once on first open if nothing links the two.
|
|
28
30
|
*
|
|
29
31
|
* The tooltip shows on hover and on focus. It hides on pointer leave, on blur,
|
|
30
32
|
* on Escape, and when the trigger is activated. The pointer can move from the
|
|
31
33
|
* trigger onto the bubble and the bubble stays.
|
|
32
34
|
*
|
|
33
35
|
* Once one tooltip is showing, moving to another trigger shows its tooltip
|
|
34
|
-
* with no `delay`, and the first one hides. The same holds for
|
|
35
|
-
* milliseconds after a tooltip hides.
|
|
36
|
+
* with no `delay`, and the first one hides. The same holds for `skip-delay`
|
|
37
|
+
* milliseconds after a tooltip hides, 300 unless you set it. `show-on="focus"`
|
|
38
|
+
* shows the tooltip on keyboard focus only, for a hint a pointer does not need.
|
|
36
39
|
*
|
|
37
40
|
* @since 0.1.0
|
|
38
41
|
* @status stable
|
|
@@ -40,9 +43,8 @@ declare const HelixHeadlessTooltip_base: (new (...args: any[]) => import("@fusio
|
|
|
40
43
|
* @aliases Hint, Popup label
|
|
41
44
|
* @documentation https://fusion.dev/helix/components/tooltip
|
|
42
45
|
*
|
|
43
|
-
* @slot - The trigger, in the wrapping form.
|
|
44
|
-
*
|
|
45
|
-
* says, and use `for` otherwise.
|
|
46
|
+
* @slot - The trigger, in the wrapping form. The tooltip describes it, as it
|
|
47
|
+
* describes the element `for` names.
|
|
46
48
|
* @slot content - Rich content for the bubble. It replaces the `content`
|
|
47
49
|
* property.
|
|
48
50
|
*
|
|
@@ -149,6 +151,18 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
|
|
|
149
151
|
* It moves if that side does not fit.
|
|
150
152
|
*/
|
|
151
153
|
placement: Placement;
|
|
154
|
+
/**
|
|
155
|
+
* What shows the bubble. Unset, it is a pointer resting on the trigger and
|
|
156
|
+
* keyboard focus. `focus` is keyboard focus alone, for a hint a pointer does
|
|
157
|
+
* not need, such as a shortcut a mouse reader has no use for.
|
|
158
|
+
*/
|
|
159
|
+
showOn?: 'focus';
|
|
160
|
+
/**
|
|
161
|
+
* How long after a tooltip hides the next one still shows at once, with no
|
|
162
|
+
* `delay`, in milliseconds. The same skip delay Radix uses. Zero makes every
|
|
163
|
+
* tooltip wait.
|
|
164
|
+
*/
|
|
165
|
+
skipDelay: number;
|
|
152
166
|
/**
|
|
153
167
|
* The id of a box to keep the bubble inside. Defaults to the viewport.
|
|
154
168
|
*
|
|
@@ -260,7 +274,7 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
|
|
|
260
274
|
* Shows after the pointer has rested for `delay`, or at once while the
|
|
261
275
|
* tooltips are warm.
|
|
262
276
|
*
|
|
263
|
-
* **Warm is another tooltip showing, or one that hid within `
|
|
277
|
+
* **Warm is another tooltip showing, or one that hid within `skipDelay`.**
|
|
264
278
|
* A reader scanning a toolbar has already waited once, and waiting again on
|
|
265
279
|
* every control turns the scan into a series of pauses.
|
|
266
280
|
*/
|
|
@@ -344,6 +358,17 @@ export declare class HelixHeadlessTooltip extends HelixHeadlessTooltip_base {
|
|
|
344
358
|
* have upgraded, or existed, when this one connected.
|
|
345
359
|
*/
|
|
346
360
|
private bind;
|
|
361
|
+
/** The control this tooltip has described, so the link can be taken back. */
|
|
362
|
+
private linked;
|
|
363
|
+
/**
|
|
364
|
+
* Describes the control that takes the trigger's focus by this tooltip.
|
|
365
|
+
*
|
|
366
|
+
* `light` says a reader has just acted, which is what allows a write onto a
|
|
367
|
+
* plain element in the consumer's markup. See `linkDescription`.
|
|
368
|
+
*/
|
|
369
|
+
private link;
|
|
370
|
+
/** A reader arriving on the trigger, which is late enough to write the link. */
|
|
371
|
+
private readonly linkOnArrival;
|
|
347
372
|
private unbind;
|
|
348
373
|
/** Said once per element, however many times it opens. */
|
|
349
374
|
private audited;
|