@fusion.dev/helix-headless 0.158.0 → 0.160.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 +1925 -174
- package/dist/app-header/root/app-header-root.d.ts +59 -12
- package/dist/app-header/root/app-header-root.js +68 -29
- package/dist/button/root/button-root.d.ts +8 -0
- package/dist/button/root/button-root.js +4 -0
- package/dist/button-group/root/button-group-root.d.ts +20 -7
- package/dist/button-group/root/button-group-root.js +30 -5
- package/dist/date-input/root/date-input-root.d.ts +108 -19
- package/dist/date-input/root/date-input-root.js +112 -13
- package/dist/date-picker/root/date-picker-root.d.ts +81 -16
- package/dist/date-picker/root/date-picker-root.js +140 -36
- package/dist/element-events.generated.js +9 -1
- package/dist/format-date/fields.js +1 -0
- package/dist/format-date/options.d.ts +3 -1
- package/dist/format-date/options.js +4 -1
- package/dist/format-date/root/format-date-root.d.ts +27 -11
- package/dist/format-date/root/format-date-root.js +6 -0
- package/dist/lightbox/item/lightbox-item.d.ts +7 -1
- package/dist/lightbox/item/lightbox-item.js +15 -2
- package/dist/lightbox/root/lightbox-root.d.ts +8 -4
- package/dist/lightbox/root/lightbox-root.js +9 -1
- package/dist/lightbox/zoom-controller.d.ts +13 -4
- package/dist/lightbox/zoom-controller.js +17 -0
- package/dist/listbox/root/listbox-root.js +5 -0
- package/dist/notifications/notification/notification.d.ts +1 -0
- package/dist/notifications/notification/notification.js +2 -1
- package/dist/slider/root/slider-root.d.ts +54 -3
- package/dist/slider/root/slider-root.js +124 -2
- package/dist/steps/root/steps-root.js +8 -6
- package/dist/steps/step/step.d.ts +9 -0
- package/dist/steps/step/step.js +19 -2
- package/dist/tabs/panel/tab-panel.d.ts +22 -1
- package/dist/tabs/panel/tab-panel.js +22 -1
- package/dist/tabs/root/tabs-root.d.ts +21 -5
- package/dist/tabs/root/tabs-root.js +24 -13
- package/dist/tabs/tab/tab.d.ts +4 -2
- package/dist/tabs/tab/tab.js +3 -1
- package/dist/time-input/root/time-input-root.d.ts +55 -7
- package/dist/time-input/root/time-input-root.js +70 -10
- package/dist/time-picker/root/time-picker-root.d.ts +27 -5
- package/dist/time-picker/root/time-picker-root.js +52 -13
- package/dist/toast/item/toast-item.d.ts +27 -8
- package/dist/toast/item/toast-item.js +47 -11
- package/dist/toast/root/toast-root.d.ts +24 -1
- package/dist/toast/root/toast-root.js +54 -0
- package/dist/toggle/group/toggle-group.d.ts +18 -1
- package/dist/toggle/group/toggle-group.js +8 -2
- package/dist/toggle/root/toggle-root.d.ts +2 -2
- package/dist/toggle/root/toggle-root.js +2 -1
- package/dist/tour/root/tour-root.d.ts +34 -0
- package/dist/tour/root/tour-root.js +92 -3
- package/dist/tour/stop/tour-stop.d.ts +33 -1
- package/dist/tour/stop/tour-stop.js +12 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +1119 -218
- package/types/vue.d.ts +278 -51
- package/types/words.d.ts +72 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
declare const HelixHeadlessAppHeader_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
|
|
3
4
|
/**
|
|
4
5
|
* @summary Page-level banner holding brand, navigation, and actions.
|
|
5
6
|
*
|
|
@@ -26,12 +27,21 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
26
27
|
*
|
|
27
28
|
* **Set `fold-below` to fold the middle region on a narrow screen.** Below that
|
|
28
29
|
* many pixels of the bar's own width, the middle region is hidden behind a
|
|
29
|
-
* toggle at the end of the bar, named by `menu-label
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
* toggle at the end of the bar, named by `menu-label`, or at its start with
|
|
31
|
+
* `toggle-placement="start"`. The toggle says whether the region is open with
|
|
32
|
+
* `aria-expanded`, and the region opens below the row. The region follows the
|
|
33
|
+
* toggle's region in focus order, so Tab from the toggle at the end goes into
|
|
34
|
+
* the open region. The custom state `folded` is on the host while the bar is
|
|
35
|
+
* that narrow, and `open` while the folded region is open. A server serves the
|
|
36
|
+
* bar unfolded, and the browser folds it once it has measured it.
|
|
37
|
+
*
|
|
38
|
+
* **`open` says whether the folded region is open, and setting it opens or
|
|
39
|
+
* closes the region.** `requestOpen(true)` and `requestOpen(false)` do the same
|
|
40
|
+
* and send `helix-before-open` or `helix-before-close` first, which you can
|
|
41
|
+
* cancel, and `toggleOpen()` asks for the opposite. `toggle` follows every
|
|
42
|
+
* change. The region closes on Escape, which puts focus back on the toggle,
|
|
43
|
+
* when focus moves to something outside the header, and on a press outside
|
|
44
|
+
* it. A bar that widens past `fold-below` closes its region.
|
|
35
45
|
*
|
|
36
46
|
* The host is the page's `banner` landmark. Placed inside `main`, `article`,
|
|
37
47
|
* `aside`, `nav` or `section`, it is a plain box instead, as a native
|
|
@@ -53,11 +63,14 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
53
63
|
* @slot menu-icon - Replaces the glyph inside the toggle that opens a folded middle region.
|
|
54
64
|
* @slot start - Leading content, usually a wordmark.
|
|
55
65
|
*
|
|
66
|
+
* @event {HelixBeforeCloseEvent} helix-before-close - The folded region is about to close. Cancelable: cancel it to keep it open.
|
|
67
|
+
* @event {HelixBeforeOpenEvent} helix-before-open - The folded region is about to open. Cancelable: cancel it to keep it closed.
|
|
56
68
|
* @event {HelixScrolledEvent} helix-scrolled - Fired when the page crosses the
|
|
57
69
|
* scroll threshold, with the new state. A custom state cannot be observed the
|
|
58
70
|
* way an attribute can, so listen for this event to follow it. The event uses
|
|
59
71
|
* the header's own threshold and hysteresis, so you do not need a second scroll
|
|
60
72
|
* listener.
|
|
73
|
+
* @event {ToggleEvent} toggle - The folded region opened or closed. The event carries `newState` and `oldState`.
|
|
61
74
|
*
|
|
62
75
|
* @csspart content - The middle region.
|
|
63
76
|
* @csspart end - The trailing region.
|
|
@@ -71,7 +84,7 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
71
84
|
* @cssstate open - While the folded middle region is open.
|
|
72
85
|
* @cssstate scrolled - While the page is scrolled down from the top, which is when the bar lifts off it.
|
|
73
86
|
*/
|
|
74
|
-
export declare class HelixHeadlessAppHeader extends
|
|
87
|
+
export declare class HelixHeadlessAppHeader extends HelixHeadlessAppHeader_base {
|
|
75
88
|
/**
|
|
76
89
|
* The key this family's words are registered under, its styled tag:
|
|
77
90
|
* `registerWords('de', { 'helix-app-header': { menuLabel: 'Menü' } })`.
|
|
@@ -103,20 +116,37 @@ export declare class HelixHeadlessAppHeader extends HelixElement {
|
|
|
103
116
|
* page scrolls up.
|
|
104
117
|
*/
|
|
105
118
|
hideOnScroll: boolean;
|
|
119
|
+
/** The name of the toggle that opens a folded middle region. */
|
|
120
|
+
menuLabel: string;
|
|
106
121
|
/** Whether the bar is narrower than `fold-below`, which only the observer sets. */
|
|
107
122
|
private folded;
|
|
108
|
-
/** Whether the folded middle region is open. */
|
|
109
|
-
private unfolded;
|
|
123
|
+
/** Whether the folded middle region is open and showing. */
|
|
124
|
+
private get unfolded();
|
|
110
125
|
/** Watches the bar's own width while `fold-below` is set. */
|
|
111
126
|
private widths;
|
|
112
127
|
/** Starts or stops watching the width, according to `fold-below`. */
|
|
113
128
|
private watchWidth;
|
|
114
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* Folds the middle region where the bar is narrower than `fold-below`. A bar
|
|
131
|
+
* that widens closes its region, so it starts closed when it folds again;
|
|
132
|
+
* one that narrows keeps an `open` you set.
|
|
133
|
+
*/
|
|
115
134
|
private fold;
|
|
116
135
|
/** Opens or closes the folded middle region. */
|
|
117
136
|
private readonly handleToggle;
|
|
118
137
|
/** Escape in the open region closes it and puts focus back on the toggle. */
|
|
119
138
|
private readonly handleKeydown;
|
|
139
|
+
/**
|
|
140
|
+
* Closes the open region when focus moves to something outside the header.
|
|
141
|
+
* Focus leaving the window has no `relatedTarget`, and closes nothing.
|
|
142
|
+
*/
|
|
143
|
+
private readonly handleFocusOut;
|
|
144
|
+
/** Closes the open region on a press outside the header. */
|
|
145
|
+
private readonly handleOutsidePress;
|
|
146
|
+
/** Whether the outside press is listened for, which is while the region is open. */
|
|
147
|
+
private pressing;
|
|
148
|
+
/** Listens for a press outside while the folded region is open, and only then. */
|
|
149
|
+
private watchPresses;
|
|
120
150
|
/** Whether the bar is out of the way, under `hide-on-scroll`. */
|
|
121
151
|
private away;
|
|
122
152
|
/** Where the page was last scrolled to, to tell down from up. */
|
|
@@ -132,13 +162,19 @@ export declare class HelixHeadlessAppHeader extends HelixElement {
|
|
|
132
162
|
* or its folded region is open, which a reader is using.
|
|
133
163
|
*/
|
|
134
164
|
private follow;
|
|
135
|
-
/** The name of the toggle that opens a folded middle region. */
|
|
136
|
-
menuLabel: string;
|
|
137
165
|
/**
|
|
138
166
|
* The id of the element whose scrolling lifts the bar, for an app shell that
|
|
139
167
|
* scrolls a container instead of the window. Unset is the window.
|
|
140
168
|
*/
|
|
141
169
|
scroller?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Where the toggle that opens a folded middle region sits: at the end of the
|
|
172
|
+
* bar, after the trailing content, or with `start`, before the leading
|
|
173
|
+
* content.
|
|
174
|
+
*
|
|
175
|
+
* Optional, so the resting bar writes no attribute onto the host.
|
|
176
|
+
*/
|
|
177
|
+
togglePlacement?: 'start';
|
|
142
178
|
private landmark;
|
|
143
179
|
private scrolled;
|
|
144
180
|
/** What scroll is listened to now, so a change of `scroller` moves it. */
|
|
@@ -187,6 +223,16 @@ export declare class HelixHeadlessAppHeader extends HelixElement {
|
|
|
187
223
|
* tier above replaces it by overriding.
|
|
188
224
|
*/
|
|
189
225
|
protected renderMenuIcon(): TemplateResult;
|
|
226
|
+
/**
|
|
227
|
+
* The row, with the middle region between the leading and the trailing
|
|
228
|
+
* content while the bar is wide, and after both while it is folded.
|
|
229
|
+
*
|
|
230
|
+
* **Folded, the region comes last in the markup**, because it opens below
|
|
231
|
+
* the row and focus order follows the markup. Between the two, as it is
|
|
232
|
+
* drawn on a wide bar, Tab from the toggle at the end left the header and
|
|
233
|
+
* the links it had just revealed were reached only by going back. The grid
|
|
234
|
+
* puts it on its own row either way.
|
|
235
|
+
*/
|
|
190
236
|
protected render(): TemplateResult;
|
|
191
237
|
}
|
|
192
238
|
declare global {
|
|
@@ -194,3 +240,4 @@ declare global {
|
|
|
194
240
|
'helix-headless-app-header': HelixHeadlessAppHeader;
|
|
195
241
|
}
|
|
196
242
|
}
|
|
243
|
+
export {};
|
|
@@ -4,35 +4,47 @@ 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, elementById, HelixElement, HelixScrolledEvent, hostAria, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, deepActiveElement, deepContains, elementById, HelixElement, HelixScrolledEvent, hostAria, MixinControllerOpen, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { list } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
12
|
import { inSectioningContent } from '../sectioning.js';
|
|
13
|
-
let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
|
|
13
|
+
let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends MixinControllerOpen(HelixElement) {
|
|
14
14
|
constructor() {
|
|
15
15
|
super(...arguments);
|
|
16
16
|
this.hideOnScroll = false;
|
|
17
|
+
this.menuLabel = 'Menu';
|
|
17
18
|
this.folded = false;
|
|
18
|
-
this.unfolded = false;
|
|
19
19
|
this.handleToggle = () => {
|
|
20
|
-
this.
|
|
20
|
+
this.toggleOpen();
|
|
21
21
|
};
|
|
22
22
|
this.handleKeydown = (event) => {
|
|
23
23
|
if (event.key !== 'Escape' || !this.unfolded || event.defaultPrevented) {
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
event.preventDefault();
|
|
27
|
-
this.
|
|
28
|
-
|
|
27
|
+
if (this.requestOpen(false)) {
|
|
28
|
+
void this.updateComplete.then(() => this.partNamed('toggle')?.focus());
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
this.handleFocusOut = (event) => {
|
|
32
|
+
const next = event.relatedTarget;
|
|
33
|
+
if (this.unfolded && next instanceof Node && !deepContains(this, next)) {
|
|
34
|
+
this.requestOpen(false);
|
|
35
|
+
}
|
|
29
36
|
};
|
|
37
|
+
this.handleOutsidePress = (event) => {
|
|
38
|
+
if (this.unfolded && !event.composedPath().includes(this)) {
|
|
39
|
+
this.requestOpen(false);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
this.pressing = false;
|
|
30
43
|
this.away = false;
|
|
31
44
|
this.lastY = 0;
|
|
32
45
|
this.handleFocusIn = () => {
|
|
33
46
|
this.hide(false);
|
|
34
47
|
};
|
|
35
|
-
this.menuLabel = 'Menu';
|
|
36
48
|
this.landmark = 'banner';
|
|
37
49
|
this.scrolled = false;
|
|
38
50
|
this.handleScroll = () => {
|
|
@@ -49,6 +61,9 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
|
|
|
49
61
|
};
|
|
50
62
|
}
|
|
51
63
|
static { this.words = 'helix-app-header'; }
|
|
64
|
+
get unfolded() {
|
|
65
|
+
return this.folded && this.open;
|
|
66
|
+
}
|
|
52
67
|
watchWidth() {
|
|
53
68
|
if (!this.foldBelow || typeof ResizeObserver === 'undefined') {
|
|
54
69
|
this.widths?.disconnect();
|
|
@@ -71,7 +86,22 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
|
|
|
71
86
|
const folded = this.foldBelow !== undefined && width < this.foldBelow;
|
|
72
87
|
if (folded !== this.folded) {
|
|
73
88
|
this.folded = folded;
|
|
74
|
-
this.
|
|
89
|
+
if (!folded && this.open) {
|
|
90
|
+
this.open = false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
watchPresses() {
|
|
95
|
+
const want = this.unfolded && this.isConnected;
|
|
96
|
+
if (want === this.pressing) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.pressing = want;
|
|
100
|
+
if (want) {
|
|
101
|
+
document.addEventListener('pointerdown', this.handleOutsidePress, true);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
document.removeEventListener('pointerdown', this.handleOutsidePress, true);
|
|
75
105
|
}
|
|
76
106
|
}
|
|
77
107
|
hide(away) {
|
|
@@ -119,7 +149,7 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
|
|
|
119
149
|
super.willUpdate(changed);
|
|
120
150
|
hostAria(this.internals, { role: this.landmark });
|
|
121
151
|
this.toggleState('folded', this.folded);
|
|
122
|
-
this.toggleState('open', this.
|
|
152
|
+
this.toggleState('open', this.unfolded);
|
|
123
153
|
}
|
|
124
154
|
connectedCallback() {
|
|
125
155
|
super.connectedCallback();
|
|
@@ -131,9 +161,11 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
|
|
|
131
161
|
});
|
|
132
162
|
this.listen();
|
|
133
163
|
this.addEventListener('focusin', this.handleFocusIn);
|
|
164
|
+
this.addEventListener('focusout', this.handleFocusOut);
|
|
134
165
|
this.addEventListener('keydown', this.handleKeydown);
|
|
135
166
|
if (this.hasUpdated) {
|
|
136
167
|
this.watchWidth();
|
|
168
|
+
this.watchPresses();
|
|
137
169
|
}
|
|
138
170
|
}
|
|
139
171
|
readLandmark() {
|
|
@@ -145,9 +177,11 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
|
|
|
145
177
|
this.source?.removeEventListener('scroll', this.handleScroll);
|
|
146
178
|
this.source = undefined;
|
|
147
179
|
this.removeEventListener('focusin', this.handleFocusIn);
|
|
180
|
+
this.removeEventListener('focusout', this.handleFocusOut);
|
|
148
181
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
149
182
|
this.widths?.disconnect();
|
|
150
183
|
this.widths = undefined;
|
|
184
|
+
this.watchPresses();
|
|
151
185
|
}
|
|
152
186
|
firstUpdated(changed) {
|
|
153
187
|
super.firstUpdated(changed);
|
|
@@ -170,6 +204,7 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
|
|
|
170
204
|
if (changed.has('hideOnScroll') && !this.hideOnScroll) {
|
|
171
205
|
this.hide(false);
|
|
172
206
|
}
|
|
207
|
+
this.watchPresses();
|
|
173
208
|
}
|
|
174
209
|
renderMenuIcon() {
|
|
175
210
|
return html `<slot name="menu-icon">
|
|
@@ -179,22 +214,26 @@ let HelixHeadlessAppHeader = class HelixHeadlessAppHeader extends HelixElement {
|
|
|
179
214
|
</slot>`;
|
|
180
215
|
}
|
|
181
216
|
render() {
|
|
217
|
+
const toggle = this.folded
|
|
218
|
+
? html `<button
|
|
219
|
+
part="toggle"
|
|
220
|
+
type="button"
|
|
221
|
+
aria-controls="content"
|
|
222
|
+
aria-expanded=${this.unfolded ? 'true' : 'false'}
|
|
223
|
+
aria-label=${this.menuLabel}
|
|
224
|
+
@click=${this.handleToggle}
|
|
225
|
+
>
|
|
226
|
+
${this.renderMenuIcon()}
|
|
227
|
+
</button>`
|
|
228
|
+
: nothing;
|
|
229
|
+
const first = this.togglePlacement === 'start';
|
|
230
|
+
const content = html `<div part="content" id="content" ?hidden=${this.folded && !this.unfolded}><slot></slot></div>`;
|
|
182
231
|
return html `
|
|
183
232
|
<div part="row">
|
|
184
|
-
<div part="start"
|
|
185
|
-
|
|
186
|
-
<div part="end"><slot name="end"></slot>${
|
|
187
|
-
|
|
188
|
-
part="toggle"
|
|
189
|
-
type="button"
|
|
190
|
-
aria-controls="content"
|
|
191
|
-
aria-expanded=${this.unfolded ? 'true' : 'false'}
|
|
192
|
-
aria-label=${this.menuLabel}
|
|
193
|
-
@click=${this.handleToggle}
|
|
194
|
-
>
|
|
195
|
-
${this.renderMenuIcon()}
|
|
196
|
-
</button>`
|
|
197
|
-
: nothing}</div>
|
|
233
|
+
<div part="start">${first ? toggle : nothing}<slot name="start"></slot></div>
|
|
234
|
+
${this.folded ? nothing : content}
|
|
235
|
+
<div part="end"><slot name="end"></slot>${first ? nothing : toggle}</div>
|
|
236
|
+
${this.folded ? content : nothing}
|
|
198
237
|
</div>
|
|
199
238
|
`;
|
|
200
239
|
}
|
|
@@ -208,19 +247,19 @@ __decorate([
|
|
|
208
247
|
__decorate([
|
|
209
248
|
property({ attribute: 'hide-on-scroll', type: Boolean })
|
|
210
249
|
], HelixHeadlessAppHeader.prototype, "hideOnScroll", void 0);
|
|
211
|
-
__decorate([
|
|
212
|
-
state()
|
|
213
|
-
], HelixHeadlessAppHeader.prototype, "folded", void 0);
|
|
214
|
-
__decorate([
|
|
215
|
-
state()
|
|
216
|
-
], HelixHeadlessAppHeader.prototype, "unfolded", void 0);
|
|
217
250
|
__decorate([
|
|
218
251
|
words,
|
|
219
252
|
property({ attribute: 'menu-label' })
|
|
220
253
|
], HelixHeadlessAppHeader.prototype, "menuLabel", void 0);
|
|
254
|
+
__decorate([
|
|
255
|
+
state()
|
|
256
|
+
], HelixHeadlessAppHeader.prototype, "folded", void 0);
|
|
221
257
|
__decorate([
|
|
222
258
|
property()
|
|
223
259
|
], HelixHeadlessAppHeader.prototype, "scroller", void 0);
|
|
260
|
+
__decorate([
|
|
261
|
+
property({ attribute: 'toggle-placement', reflect: true })
|
|
262
|
+
], HelixHeadlessAppHeader.prototype, "togglePlacement", void 0);
|
|
224
263
|
HelixHeadlessAppHeader = __decorate([
|
|
225
264
|
customElement('helix-headless-app-header')
|
|
226
265
|
], HelixHeadlessAppHeader);
|
|
@@ -57,6 +57,7 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
|
57
57
|
* @csspart text - The button's words, which the styled button trims to their capitals and baseline so the letters sit at the middle in any font.
|
|
58
58
|
*
|
|
59
59
|
* @cssstate pending - The button is pending: `pending` is set.
|
|
60
|
+
* @cssstate pressed - A Button Group in a selection mode has this button pressed.
|
|
60
61
|
*/
|
|
61
62
|
export declare class HelixHeadlessButton extends HelixFormElement {
|
|
62
63
|
/**
|
|
@@ -206,6 +207,13 @@ export declare class HelixHeadlessButton extends HelixFormElement {
|
|
|
206
207
|
* reported collapsed and expanded and no popup.
|
|
207
208
|
*/
|
|
208
209
|
popup?: 'dialog' | 'grid' | 'listbox' | 'menu' | 'tree';
|
|
210
|
+
/**
|
|
211
|
+
* @internal Set by a Button Group in a selection mode: whether the group has
|
|
212
|
+
* this button pressed. Recorded as `:state(pressed)`, so a page can style a
|
|
213
|
+
* pressed button from its own element. The group says it to screen readers
|
|
214
|
+
* on the control.
|
|
215
|
+
*/
|
|
216
|
+
pressed?: boolean | undefined;
|
|
209
217
|
/**
|
|
210
218
|
* The status region, rendered from the first time the button is pending, so
|
|
211
219
|
* a button that never waits carries no live region.
|
|
@@ -140,6 +140,7 @@ let HelixHeadlessButton = class HelixHeadlessButton extends HelixFormElement {
|
|
|
140
140
|
willUpdate(changed) {
|
|
141
141
|
super.willUpdate(changed);
|
|
142
142
|
this.toggleState('pending', this.pending);
|
|
143
|
+
this.toggleState('pressed', this.pressed === true);
|
|
143
144
|
}
|
|
144
145
|
updated(changed) {
|
|
145
146
|
super.updated(changed);
|
|
@@ -238,6 +239,9 @@ __decorate([
|
|
|
238
239
|
__decorate([
|
|
239
240
|
property({ attribute: false })
|
|
240
241
|
], HelixHeadlessButton.prototype, "popup", void 0);
|
|
242
|
+
__decorate([
|
|
243
|
+
property({ attribute: false })
|
|
244
|
+
], HelixHeadlessButton.prototype, "pressed", void 0);
|
|
241
245
|
__decorate([
|
|
242
246
|
property()
|
|
243
247
|
], HelixHeadlessButton.prototype, "type", void 0);
|
|
@@ -16,11 +16,15 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
16
16
|
* appearance and size.
|
|
17
17
|
*
|
|
18
18
|
* **Buttons that stay pressed.** With `selection-mode`, each button with a
|
|
19
|
-
* `value` is a toggle button: a press presses or releases it
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* `value` is a toggle button: a press presses or releases it. `multiple` lets
|
|
20
|
+
* any number be pressed, and each button says so with `aria-pressed`. `single`
|
|
21
|
+
* keeps one pressed at a time: the group is a `radiogroup` and each button a
|
|
22
|
+
* `radio` that says so with `aria-checked`, as React Aria's toggle button
|
|
23
|
+
* group is. A press on the pressed one releases it, unless
|
|
24
|
+
* `disallow-empty-selection` keeps one pressed. `value` is the pressed values,
|
|
25
|
+
* comma separated. A press sends a cancelable `helix-before-change` with the
|
|
26
|
+
* value it would leave, then `change`. A pressed Helix button has
|
|
27
|
+
* `:state(pressed)` on its own element.
|
|
24
28
|
*
|
|
25
29
|
* The pressed state is written on the control inside each button's shadow
|
|
26
30
|
* root, so the markup you rendered is never changed and a framework hydrating
|
|
@@ -48,10 +52,10 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
48
52
|
* | Space, Enter | Presses or releases the focused button. |
|
|
49
53
|
*
|
|
50
54
|
* Left and Right swap on a right-to-left page, and a disabled button is
|
|
51
|
-
* passed over.
|
|
55
|
+
* passed over. Under `multiple` the group is a `toolbar`, with
|
|
52
56
|
* `aria-orientation` under `orientation="vertical"`. Inside a toolbar it stays
|
|
53
57
|
* a named `group`, and the toolbar's own arrow keys walk its buttons, so a
|
|
54
|
-
* reader meets one toolbar.
|
|
58
|
+
* reader meets one toolbar. Under `single` it is a `radiogroup` either way.
|
|
55
59
|
*
|
|
56
60
|
* @since 0.1.0
|
|
57
61
|
* @status stable
|
|
@@ -69,6 +73,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
69
73
|
* @cssstate pressed - While a group with `selection-mode` has a button pressed.
|
|
70
74
|
*/
|
|
71
75
|
export declare class HelixHeadlessButtonGroup extends HelixElement {
|
|
76
|
+
/**
|
|
77
|
+
* Keeps at least one button pressed once one is, as React Aria's
|
|
78
|
+
* `disallowEmptySelection`: a press that would leave none pressed is
|
|
79
|
+
* refused.
|
|
80
|
+
*/
|
|
81
|
+
disallowEmptySelection: boolean;
|
|
72
82
|
/**
|
|
73
83
|
* The group's name, read by screen readers.
|
|
74
84
|
*
|
|
@@ -172,6 +182,9 @@ export declare class HelixHeadlessButtonGroup extends HelixElement {
|
|
|
172
182
|
* Says on a button's control whether it is pressed, or that it is no toggle
|
|
173
183
|
* at all where `pressed` is undefined. A styled tier draws the look here.
|
|
174
184
|
*
|
|
185
|
+
* Under `single` the control is a `radio` that says so with `aria-checked`,
|
|
186
|
+
* and under `multiple` a toggle button that says so with `aria-pressed`.
|
|
187
|
+
*
|
|
175
188
|
* @param control The focusable control inside the button's shadow root.
|
|
176
189
|
* @param pressed Whether the button is pressed, or undefined where it is not a toggle.
|
|
177
190
|
*/
|
|
@@ -12,6 +12,7 @@ const isGroupButton = (child) => child instanceof HTMLElement && 'focusable' in
|
|
|
12
12
|
let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixElement {
|
|
13
13
|
constructor() {
|
|
14
14
|
super(...arguments);
|
|
15
|
+
this.disallowEmptySelection = false;
|
|
15
16
|
this.label = '';
|
|
16
17
|
this.value = '';
|
|
17
18
|
this.lastFocused = undefined;
|
|
@@ -21,8 +22,12 @@ let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixEleme
|
|
|
21
22
|
const mode = this.selectionMode;
|
|
22
23
|
const button = this.buttons.find((one) => event.composedPath().includes(one));
|
|
23
24
|
if (mode && button && button.value !== '') {
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
const next = pressedValue(mode, this.value, button.value);
|
|
26
|
+
if (this.disallowEmptySelection && commaList(next).length === 0) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
requestValue(this, next, (value) => {
|
|
30
|
+
this.value = value;
|
|
26
31
|
});
|
|
27
32
|
}
|
|
28
33
|
};
|
|
@@ -107,17 +112,30 @@ let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixEleme
|
|
|
107
112
|
const control = button.shadowRoot?.querySelector('button');
|
|
108
113
|
if (control && !button.hasAttribute('aria-pressed')) {
|
|
109
114
|
const toggle = this.told.has(button) && button.value !== '';
|
|
110
|
-
|
|
115
|
+
const pressed = toggle ? this.pressed(button) : undefined;
|
|
116
|
+
if ('pressed' in button && button.pressed !== pressed) {
|
|
117
|
+
button.pressed = pressed;
|
|
118
|
+
}
|
|
119
|
+
this.markPressed(control, pressed);
|
|
111
120
|
}
|
|
112
121
|
});
|
|
113
122
|
}
|
|
114
123
|
markPressed(control, pressed) {
|
|
115
|
-
|
|
124
|
+
const radio = pressed !== undefined && this.selectionMode === 'single';
|
|
125
|
+
if (pressed === undefined || radio) {
|
|
116
126
|
control.removeAttribute('aria-pressed');
|
|
117
127
|
}
|
|
118
128
|
else {
|
|
119
129
|
control.setAttribute('aria-pressed', String(pressed));
|
|
120
130
|
}
|
|
131
|
+
if (radio) {
|
|
132
|
+
control.setAttribute('role', 'radio');
|
|
133
|
+
control.setAttribute('aria-checked', String(pressed));
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
control.removeAttribute('role');
|
|
137
|
+
control.removeAttribute('aria-checked');
|
|
138
|
+
}
|
|
121
139
|
control.style.setProperty('--button-group-pressed', pressed === undefined ? '' : pressed ? '1' : '0');
|
|
122
140
|
}
|
|
123
141
|
connectedCallback() {
|
|
@@ -143,7 +161,11 @@ let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixEleme
|
|
|
143
161
|
willUpdate(changed) {
|
|
144
162
|
super.willUpdate(changed);
|
|
145
163
|
const toolbar = this.roving;
|
|
146
|
-
|
|
164
|
+
const radios = this.selectionMode === 'single';
|
|
165
|
+
hostAria(this.internals, {
|
|
166
|
+
label: this.label,
|
|
167
|
+
role: radios ? 'radiogroup' : toolbar ? 'toolbar' : 'group',
|
|
168
|
+
});
|
|
147
169
|
this.internals.ariaOrientation = toolbar ? (this.orientation ?? null) : null;
|
|
148
170
|
this.toggleState('pressed', Boolean(this.selectionMode) && commaList(this.value).length > 0);
|
|
149
171
|
}
|
|
@@ -159,6 +181,9 @@ let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixEleme
|
|
|
159
181
|
return html `<slot></slot>`;
|
|
160
182
|
}
|
|
161
183
|
};
|
|
184
|
+
__decorate([
|
|
185
|
+
property({ attribute: 'disallow-empty-selection', type: Boolean })
|
|
186
|
+
], HelixHeadlessButtonGroup.prototype, "disallowEmptySelection", void 0);
|
|
162
187
|
__decorate([
|
|
163
188
|
property()
|
|
164
189
|
], HelixHeadlessButtonGroup.prototype, "label", void 0);
|