@fusion.dev/helix-headless 0.153.0 → 0.155.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 +1885 -140
- package/dist/accordion/item/accordion-item.d.ts +15 -0
- package/dist/accordion/item/accordion-item.js +12 -1
- package/dist/accordion/root/accordion-root.d.ts +2 -1
- package/dist/accordion/root/accordion-root.js +1 -0
- package/dist/badge/root/badge-root.d.ts +15 -2
- package/dist/badge/root/badge-root.js +20 -3
- package/dist/breadcrumbs/breadcrumb/breadcrumb.d.ts +36 -3
- package/dist/breadcrumbs/breadcrumb/breadcrumb.js +42 -2
- package/dist/breadcrumbs/root/breadcrumbs-root.d.ts +15 -5
- package/dist/breadcrumbs/root/breadcrumbs-root.js +29 -4
- package/dist/button-group/root/button-group-root.d.ts +2 -0
- package/dist/button-group/root/button-group-root.js +1 -0
- package/dist/card/root/card-root.d.ts +31 -11
- package/dist/card/root/card-root.js +25 -11
- package/dist/code/root/code-root.d.ts +7 -0
- package/dist/code/root/code-root.js +1 -1
- package/dist/code-block/root/code-block-root.d.ts +37 -1
- package/dist/code-block/root/code-block-root.js +38 -5
- package/dist/color-input/root/color-input-root.d.ts +9 -3
- package/dist/color-input/root/color-input-root.js +3 -1
- package/dist/color-picker/channels.d.ts +5 -3
- package/dist/color-picker/channels.js +79 -21
- package/dist/color-picker/root/color-picker-root.d.ts +58 -5
- package/dist/color-picker/root/color-picker-root.js +75 -13
- package/dist/date-input/root/date-input-root.d.ts +1 -0
- package/dist/date-input/root/date-input-root.js +2 -1
- package/dist/date-picker/root/date-picker-root.d.ts +43 -13
- package/dist/date-picker/root/date-picker-root.js +54 -31
- package/dist/element-events.generated.js +2 -1
- package/dist/empty/root/empty-root.d.ts +19 -1
- package/dist/empty/root/empty-root.js +13 -4
- package/dist/icon/fetch.d.ts +20 -0
- package/dist/icon/fetch.js +70 -0
- package/dist/icon/root/icon-root.d.ts +27 -5
- package/dist/icon/root/icon-root.js +41 -17
- package/dist/navigation/root/navigation-root.d.ts +26 -0
- package/dist/navigation/root/navigation-root.js +58 -1
- package/dist/quote/root/quote-root.d.ts +17 -1
- package/dist/quote/root/quote-root.js +15 -2
- package/dist/select/root/select-root.d.ts +29 -3
- package/dist/select/root/select-root.js +34 -9
- package/dist/skeleton/root/skeleton-root.d.ts +7 -0
- package/dist/skeleton/root/skeleton-root.js +4 -1
- package/dist/slider/root/slider-root.d.ts +71 -9
- package/dist/slider/root/slider-root.js +129 -42
- package/dist/speed-dial/item/speed-dial-item.d.ts +29 -1
- package/dist/speed-dial/item/speed-dial-item.js +48 -7
- package/dist/speed-dial/root/speed-dial-root.d.ts +32 -6
- package/dist/speed-dial/root/speed-dial-root.js +50 -5
- package/dist/splitter/root/splitter-root.d.ts +22 -0
- package/dist/splitter/root/splitter-root.js +36 -7
- package/dist/stat/group/stat-group.d.ts +23 -4
- package/dist/stat/group/stat-group.js +13 -1
- package/dist/stat/root/stat-root.d.ts +46 -1
- package/dist/stat/root/stat-root.js +29 -5
- package/dist/steps/reach.d.ts +5 -1
- package/dist/steps/reach.js +2 -2
- package/dist/steps/root/steps-root.d.ts +37 -2
- package/dist/steps/root/steps-root.js +49 -7
- package/dist/steps/step/step.d.ts +6 -0
- package/dist/steps/step/step.js +4 -0
- package/dist/tabs/root/tabs-root.d.ts +37 -0
- package/dist/tabs/root/tabs-root.js +92 -2
- package/dist/tabs/tab/tab.d.ts +38 -2
- package/dist/tabs/tab/tab.js +45 -4
- package/dist/tag/group/tag-group.d.ts +29 -3
- package/dist/tag/group/tag-group.js +42 -11
- package/dist/toggle/root/toggle-root.d.ts +2 -0
- package/dist/toggle/root/toggle-root.js +1 -1
- package/dist/tree/item/tree-item.d.ts +10 -0
- package/dist/tree/item/tree-item.js +12 -0
- package/dist/tree/root/tree-root.d.ts +40 -1
- package/dist/tree/root/tree-root.js +120 -4
- package/dist/tree/selection.d.ts +6 -0
- package/dist/tree/selection.js +11 -0
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +1412 -207
- package/types/vue.d.ts +294 -65
- package/types/words.d.ts +49 -1
|
@@ -50,6 +50,16 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
|
|
|
50
50
|
* `active` says so, with `helix-activate`. Where the removed tab had focus,
|
|
51
51
|
* focus moves to the tab now selected.
|
|
52
52
|
*
|
|
53
|
+
* **More tabs than fit.** With `scroll-controls`, a strip that scrolls shows a
|
|
54
|
+
* button at each end it can scroll toward, as Web Awesome's tab group does,
|
|
55
|
+
* and a press scrolls it most of its own length. The buttons are for the
|
|
56
|
+
* pointer, so they are out of the tab order and hidden from screen readers;
|
|
57
|
+
* the arrow keys already reach every tab, and the strip scrolls the selected
|
|
58
|
+
* tab into view. A strip scrolls once a stylesheet gives the `tabs` part an
|
|
59
|
+
* `overflow`, as the styled tier does.
|
|
60
|
+
*
|
|
61
|
+
* A tab with `dismissible` can be closed: see `Tabs.Tab`.
|
|
62
|
+
*
|
|
53
63
|
* The `indicator` part moves to the selected tab and carries its place and
|
|
54
64
|
* size, so a mark of your own can be drawn there: style the part, or slot an
|
|
55
65
|
* element into `indicator`, which sits inside the part and reads the same
|
|
@@ -74,6 +84,8 @@ declare const HelixHeadlessTabs_base: (new (...args: any[]) => import("@fusion.d
|
|
|
74
84
|
* `--indicator-block-size`, and `data-placed` once it has been placed, for a mark you draw
|
|
75
85
|
* and animate yourself.
|
|
76
86
|
* @csspart panels - The panel container.
|
|
87
|
+
* @csspart scroll-end - The button that scrolls the strip toward its end, shown under `scroll-controls` while there is more that way.
|
|
88
|
+
* @csspart scroll-start - The button that scrolls the strip toward its start, shown under `scroll-controls` while there is more that way.
|
|
77
89
|
* @csspart tabs - The tab list.
|
|
78
90
|
*/
|
|
79
91
|
export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
|
|
@@ -109,6 +121,11 @@ export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
|
|
|
109
121
|
* Optional, so the resting row writes no attribute onto the host.
|
|
110
122
|
*/
|
|
111
123
|
orientation?: 'horizontal' | 'vertical';
|
|
124
|
+
/**
|
|
125
|
+
* Whether a strip with more tabs than fit shows a button at each end it can
|
|
126
|
+
* scroll toward.
|
|
127
|
+
*/
|
|
128
|
+
scrollControls: boolean;
|
|
112
129
|
/**
|
|
113
130
|
* How the row of tabs is drawn.
|
|
114
131
|
*
|
|
@@ -139,6 +156,17 @@ export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
|
|
|
139
156
|
private get linked();
|
|
140
157
|
private get panels();
|
|
141
158
|
private readonly handleSelection;
|
|
159
|
+
/** Which ends the strip can scroll toward, under `scroll-controls`. */
|
|
160
|
+
private more;
|
|
161
|
+
/** Watches the strip's size for `scroll-controls`. */
|
|
162
|
+
private resizing;
|
|
163
|
+
/** Reads which ends of the strip have more tabs past them. */
|
|
164
|
+
private readonly measure;
|
|
165
|
+
/**
|
|
166
|
+
* Scrolls the strip most of its own length toward one end, smoothly unless
|
|
167
|
+
* the reader asked for less motion.
|
|
168
|
+
*/
|
|
169
|
+
private scrollStrip;
|
|
142
170
|
/** The values of the tabs the last pairing read, to tell a removed tab from one not yet here. */
|
|
143
171
|
private held;
|
|
144
172
|
/** Pushes the current value down to whichever tab and panel own it. */
|
|
@@ -155,6 +183,10 @@ export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
|
|
|
155
183
|
connectedCallback(): void;
|
|
156
184
|
disconnectedCallback(): void;
|
|
157
185
|
protected firstUpdated(changed: PropertyValues): void;
|
|
186
|
+
/** Watches the strip's size while `scroll-controls` asks, and reads it. */
|
|
187
|
+
private watchStrip;
|
|
188
|
+
/** Brings the selected tab into the strip's view, and moves nothing else. */
|
|
189
|
+
private reveal;
|
|
158
190
|
/**
|
|
159
191
|
* The set changed: pair it up and measure it.
|
|
160
192
|
*
|
|
@@ -172,6 +204,11 @@ export declare class HelixHeadlessTabs extends HelixHeadlessTabs_base {
|
|
|
172
204
|
*/
|
|
173
205
|
private readonly handleSlotChange;
|
|
174
206
|
protected updated(changed: PropertyValues): void;
|
|
207
|
+
/**
|
|
208
|
+
* One of the two scroll buttons, pointing the way it scrolls. A caret along
|
|
209
|
+
* the line is mirrored on a right-to-left page, and one down a column is not.
|
|
210
|
+
*/
|
|
211
|
+
private renderScroll;
|
|
175
212
|
protected render(): TemplateResult;
|
|
176
213
|
}
|
|
177
214
|
declare global {
|
|
@@ -4,9 +4,11 @@ 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 { AriaForwardController, customElement, deepActiveElement, HelixActivateEvent, HelixElement, IndicatorController, KeyboardNavigationController, MixinControllerActive, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, customElement, deepActiveElement, HelixActivateEvent, HelixElement, IndicatorController, isRtl, KeyboardNavigationController, MixinControllerActive, scrollItemIntoView, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { caretDown, caretLeft, caretRight, caretUp } from '@fusion.dev/helix-icons';
|
|
8
9
|
import { html, nothing } from 'lit';
|
|
9
|
-
import { property } from 'lit/decorators.js';
|
|
10
|
+
import { property, state } from 'lit/decorators.js';
|
|
11
|
+
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
10
12
|
import { HelixHeadlessTabPanel } from '../panel/tab-panel.js';
|
|
11
13
|
import { HelixHeadlessTab } from '../tab/tab.js';
|
|
12
14
|
let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(HelixElement) {
|
|
@@ -29,12 +31,32 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
29
31
|
});
|
|
30
32
|
this.label = 'Tabs';
|
|
31
33
|
this.manual = false;
|
|
34
|
+
this.scrollControls = false;
|
|
32
35
|
this.handleSelection = (event) => {
|
|
33
36
|
if (event.defaultPrevented || !this.tabs.includes(event.target)) {
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
36
39
|
this.requestActive(event.detail.value);
|
|
37
40
|
};
|
|
41
|
+
this.more = { end: false, start: false };
|
|
42
|
+
this.measure = () => {
|
|
43
|
+
const strip = this.partNamed('tabs');
|
|
44
|
+
if (!strip || !this.scrollControls) {
|
|
45
|
+
if (this.more.start || this.more.end) {
|
|
46
|
+
this.more = { end: false, start: false };
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const down = this.orientation === 'vertical';
|
|
51
|
+
const along = Math.abs(down ? strip.scrollTop : strip.scrollLeft);
|
|
52
|
+
const length = down ? strip.clientHeight : strip.clientWidth;
|
|
53
|
+
const whole = down ? strip.scrollHeight : strip.scrollWidth;
|
|
54
|
+
const start = along > 1;
|
|
55
|
+
const end = along + length < whole - 1;
|
|
56
|
+
if (start !== this.more.start || end !== this.more.end) {
|
|
57
|
+
this.more = { end, start };
|
|
58
|
+
}
|
|
59
|
+
};
|
|
38
60
|
this.held = [];
|
|
39
61
|
this.handleFocusIn = (event) => {
|
|
40
62
|
const path = event.composedPath();
|
|
@@ -44,6 +66,7 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
44
66
|
this.sync();
|
|
45
67
|
this.indicator.place();
|
|
46
68
|
this.refocus();
|
|
69
|
+
this.measure();
|
|
47
70
|
};
|
|
48
71
|
}
|
|
49
72
|
static { this.words = 'helix-tabs'; }
|
|
@@ -56,6 +79,16 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
56
79
|
get panels() {
|
|
57
80
|
return [...this.children].filter((child) => child instanceof HelixHeadlessTabPanel);
|
|
58
81
|
}
|
|
82
|
+
scrollStrip(toward) {
|
|
83
|
+
const strip = this.partNamed('tabs');
|
|
84
|
+
if (!strip) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const down = this.orientation === 'vertical';
|
|
88
|
+
const by = (down ? strip.clientHeight : strip.clientWidth) * 0.75 * toward;
|
|
89
|
+
const behavior = matchMedia('(prefers-reduced-motion: reduce)').matches ? 'auto' : 'smooth';
|
|
90
|
+
strip.scrollBy(down ? { behavior, top: by } : { behavior, left: isRtl(this) ? -by : by });
|
|
91
|
+
}
|
|
59
92
|
sync() {
|
|
60
93
|
const tabs = this.tabs;
|
|
61
94
|
const declared = this.active === '' ? tabs.find((tab) => tab.selected) : undefined;
|
|
@@ -106,11 +139,16 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
106
139
|
super.connectedCallback();
|
|
107
140
|
this.addEventListener('helix-select', this.handleSelection);
|
|
108
141
|
this.addEventListener('focusin', this.handleFocusIn);
|
|
142
|
+
if (this.hasUpdated) {
|
|
143
|
+
this.watchStrip();
|
|
144
|
+
}
|
|
109
145
|
}
|
|
110
146
|
disconnectedCallback() {
|
|
111
147
|
super.disconnectedCallback();
|
|
112
148
|
this.removeEventListener('helix-select', this.handleSelection);
|
|
113
149
|
this.removeEventListener('focusin', this.handleFocusIn);
|
|
150
|
+
this.resizing?.disconnect();
|
|
151
|
+
this.resizing = undefined;
|
|
114
152
|
}
|
|
115
153
|
firstUpdated(changed) {
|
|
116
154
|
super.firstUpdated(changed);
|
|
@@ -118,22 +156,68 @@ let HelixHeadlessTabs = class HelixHeadlessTabs extends MixinControllerActive(He
|
|
|
118
156
|
this.indicator.place();
|
|
119
157
|
requestAnimationFrame(() => this.indicator.place());
|
|
120
158
|
}
|
|
159
|
+
watchStrip() {
|
|
160
|
+
const strip = this.partNamed('tabs');
|
|
161
|
+
if (this.scrollControls && strip && !this.resizing) {
|
|
162
|
+
this.resizing = new ResizeObserver(this.measure);
|
|
163
|
+
this.resizing.observe(strip);
|
|
164
|
+
}
|
|
165
|
+
if (!this.scrollControls && this.resizing) {
|
|
166
|
+
this.resizing.disconnect();
|
|
167
|
+
this.resizing = undefined;
|
|
168
|
+
}
|
|
169
|
+
this.measure();
|
|
170
|
+
}
|
|
171
|
+
reveal() {
|
|
172
|
+
const strip = this.partNamed('tabs');
|
|
173
|
+
const tab = this.tabs.find((one) => one.selected);
|
|
174
|
+
if (strip && tab) {
|
|
175
|
+
scrollItemIntoView(tab, { padding: this.scrollControls ? 40 : 0, scroller: strip });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
121
178
|
updated(changed) {
|
|
122
179
|
super.updated(changed);
|
|
123
180
|
if (changed.has('active')) {
|
|
124
181
|
this.sync();
|
|
182
|
+
if (changed.get('active') !== undefined) {
|
|
183
|
+
void this.updateComplete.then(() => this.reveal());
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (changed.has('scrollControls') || changed.has('orientation')) {
|
|
187
|
+
this.watchStrip();
|
|
125
188
|
}
|
|
126
189
|
this.indicator.place();
|
|
127
190
|
}
|
|
191
|
+
renderScroll(toward) {
|
|
192
|
+
if (!this.scrollControls) {
|
|
193
|
+
return nothing;
|
|
194
|
+
}
|
|
195
|
+
const down = this.orientation === 'vertical';
|
|
196
|
+
const icon = down ? (toward > 0 ? caretDown : caretUp) : toward > 0 ? caretRight : caretLeft;
|
|
197
|
+
const glyph = html `<svg
|
|
198
|
+
data-mirror=${down ? nothing : ''}
|
|
199
|
+
aria-hidden="true"
|
|
200
|
+
fill="currentColor"
|
|
201
|
+
viewBox=${icon.viewBox}
|
|
202
|
+
>${unsafeSVG(icon.content)}</svg>`;
|
|
203
|
+
const shown = toward > 0 ? this.more.end : this.more.start;
|
|
204
|
+
const keepFocus = (event) => event.preventDefault();
|
|
205
|
+
return toward > 0
|
|
206
|
+
? html `<button part="scroll-end" type="button" tabindex="-1" aria-hidden="true" ?hidden=${!shown} @mousedown=${keepFocus} @click=${() => this.scrollStrip(1)}>${glyph}</button>`
|
|
207
|
+
: html `<button part="scroll-start" type="button" tabindex="-1" aria-hidden="true" ?hidden=${!shown} @mousedown=${keepFocus} @click=${() => this.scrollStrip(-1)}>${glyph}</button>`;
|
|
208
|
+
}
|
|
128
209
|
render() {
|
|
129
210
|
return html `<div
|
|
130
211
|
part="tabs"
|
|
131
212
|
role="tablist"
|
|
132
213
|
aria-label=${this.label}
|
|
133
214
|
aria-orientation=${this.orientation === 'vertical' ? 'vertical' : nothing}
|
|
215
|
+
@scroll=${this.scrollControls ? this.measure : nothing}
|
|
134
216
|
>
|
|
217
|
+
${this.renderScroll(-1)}
|
|
135
218
|
<slot @slotchange=${this.handleSlotChange}></slot>
|
|
136
219
|
<span part="indicator" aria-hidden="true"><slot name="indicator"></slot></span>
|
|
220
|
+
${this.renderScroll(1)}
|
|
137
221
|
</div>
|
|
138
222
|
<div part="panels"><slot name="panels" @slotchange=${this.handleSlotChange}></slot></div>`;
|
|
139
223
|
}
|
|
@@ -148,9 +232,15 @@ __decorate([
|
|
|
148
232
|
__decorate([
|
|
149
233
|
property({ reflect: true })
|
|
150
234
|
], HelixHeadlessTabs.prototype, "orientation", void 0);
|
|
235
|
+
__decorate([
|
|
236
|
+
property({ attribute: 'scroll-controls', reflect: true, type: Boolean })
|
|
237
|
+
], HelixHeadlessTabs.prototype, "scrollControls", void 0);
|
|
151
238
|
__decorate([
|
|
152
239
|
property({ reflect: true })
|
|
153
240
|
], HelixHeadlessTabs.prototype, "variant", void 0);
|
|
241
|
+
__decorate([
|
|
242
|
+
state()
|
|
243
|
+
], HelixHeadlessTabs.prototype, "more", void 0);
|
|
154
244
|
HelixHeadlessTabs = __decorate([
|
|
155
245
|
customElement('helix-headless-tabs')
|
|
156
246
|
], HelixHeadlessTabs);
|
package/dist/tabs/tab/tab.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
-
declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinSelectableItemInterface) & typeof HelixElement;
|
|
2
|
+
import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.dev/helix-core").DismissibleElement) & (new (...args: any[]) => import("@fusion.dev/helix-core").MixinSelectableItemInterface) & typeof HelixElement;
|
|
4
4
|
/**
|
|
5
5
|
* @summary One tab in a tab list.
|
|
6
6
|
*
|
|
@@ -12,6 +12,15 @@ declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.de
|
|
|
12
12
|
* first, and canceling it keeps the browser from following, so a client-side
|
|
13
13
|
* router follows it instead.
|
|
14
14
|
*
|
|
15
|
+
* With `dismissible`, the tab can be closed, as Web Awesome's closable tab
|
|
16
|
+
* can: a press on its `dismiss` mark, or Delete or Backspace while the tab
|
|
17
|
+
* has focus, sends `helix-dismiss`. The tab does not remove itself, because
|
|
18
|
+
* your code owns the set, so remove the tab and its panel in your handler.
|
|
19
|
+
* Focus moves to the tab beside it, and the tab set selects a tab that is
|
|
20
|
+
* left.
|
|
21
|
+
* The tab says the key through `aria-keyshortcuts`, and the mark is for the
|
|
22
|
+
* pointer, hidden from screen readers.
|
|
23
|
+
*
|
|
15
24
|
* @since 0.1.0
|
|
16
25
|
* @status stable
|
|
17
26
|
* @aliases Tab
|
|
@@ -19,13 +28,20 @@ declare const HelixHeadlessTab_base: (new (...args: any[]) => import("@fusion.de
|
|
|
19
28
|
*
|
|
20
29
|
* @slot - The tab's label.
|
|
21
30
|
*
|
|
31
|
+
* @event {HelixDismissEvent} helix-dismiss - A `dismissible` tab was asked to close. The tab does not remove itself, so remove it and its panel in response. Cancelable.
|
|
22
32
|
* @event {HelixSelectEvent} helix-select - Fired when the tab is chosen. Cancelable: canceling it stops the change and leaves the decision to you.
|
|
23
33
|
*
|
|
34
|
+
* @csspart dismiss - The mark a pointer presses to close a `dismissible` tab.
|
|
24
35
|
* @csspart tab - The tab itself.
|
|
25
36
|
*
|
|
26
37
|
* @cssstate selected - While it is the tab selected.
|
|
27
38
|
*/
|
|
28
39
|
export declare class HelixHeadlessTab extends HelixHeadlessTab_base {
|
|
40
|
+
/**
|
|
41
|
+
* The key this family's words are registered under, its styled tag:
|
|
42
|
+
* `registerWords('de', { 'helix-tab': { dismissLabel: 'Schließen' } })`.
|
|
43
|
+
*/
|
|
44
|
+
static words: string;
|
|
29
45
|
protected itemPart: string;
|
|
30
46
|
/**
|
|
31
47
|
* @internal The panel this tab shows, written by the tab set. A property,
|
|
@@ -34,6 +50,12 @@ export declare class HelixHeadlessTab extends HelixHeadlessTab_base {
|
|
|
34
50
|
* inside this shadow root can reach.
|
|
35
51
|
*/
|
|
36
52
|
controls: Element | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* The tooltip on the mark that closes a `dismissible` tab. The mark is for
|
|
55
|
+
* the pointer and hidden from screen readers, which hear the Delete key
|
|
56
|
+
* through `aria-keyshortcuts`.
|
|
57
|
+
*/
|
|
58
|
+
dismissLabel: string;
|
|
37
59
|
/**
|
|
38
60
|
* The URL the tab links to. Setting it renders the tab as a link, for a set
|
|
39
61
|
* of tabs that are pages of a site.
|
|
@@ -52,6 +74,20 @@ export declare class HelixHeadlessTab extends HelixHeadlessTab_base {
|
|
|
52
74
|
* script can focus the tab the way it focuses a native one.
|
|
53
75
|
*/
|
|
54
76
|
focus(options?: FocusOptions): void;
|
|
77
|
+
/**
|
|
78
|
+
* Sends `helix-dismiss` for a `dismissible` tab, and returns `false` where a
|
|
79
|
+
* listener refused it, or where the tab is not dismissible or is disabled.
|
|
80
|
+
* The tab stays where it is either way: removing it is yours. Where the tab
|
|
81
|
+
* had focus, focus moves to the tab beside it once you remove it.
|
|
82
|
+
*/
|
|
83
|
+
dismiss(): boolean;
|
|
84
|
+
/** Where focus goes when a tab beside this one that held it is removed: this tab. */
|
|
85
|
+
protected get returnFocusTo(): HTMLElement | null;
|
|
86
|
+
/** A press on the mark closes the tab, and neither selects it nor follows its link. */
|
|
87
|
+
private readonly handleDismissPress;
|
|
88
|
+
private readonly handleKeydown;
|
|
89
|
+
/** The mark that closes a `dismissible` tab, inside the control so it shares its box. */
|
|
90
|
+
protected renderDismiss(): TemplateResult | typeof nothing;
|
|
55
91
|
/**
|
|
56
92
|
* Chooses the tab before the browser follows its link, and keeps the browser
|
|
57
93
|
* from following where the choice was refused. A press with a modifier, or
|
package/dist/tabs/tab/tab.js
CHANGED
|
@@ -4,14 +4,28 @@ 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, HelixElement, MixinSelectableItem, plainClick, reflectElements, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, HelixElement, MixinDismissible, MixinSelectableItem, plainClick, reflectElements, words, } from '@fusion.dev/helix-core';
|
|
8
|
+
import { x } from '@fusion.dev/helix-icons';
|
|
8
9
|
import { html, nothing } from 'lit';
|
|
9
10
|
import { property } from 'lit/decorators.js';
|
|
10
|
-
|
|
11
|
+
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
|
+
let HelixHeadlessTab = class HelixHeadlessTab extends MixinDismissible(MixinSelectableItem(HelixElement)) {
|
|
11
13
|
constructor() {
|
|
12
14
|
super(...arguments);
|
|
13
15
|
this.itemPart = 'tab';
|
|
14
16
|
this.controls = undefined;
|
|
17
|
+
this.dismissLabel = 'Close';
|
|
18
|
+
this.handleDismissPress = (event) => {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
event.stopPropagation();
|
|
21
|
+
this.dismiss();
|
|
22
|
+
};
|
|
23
|
+
this.handleKeydown = (event) => {
|
|
24
|
+
if (this.dismissible && (event.key === 'Delete' || event.key === 'Backspace')) {
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
this.dismiss();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
15
29
|
this.handleLinkClick = (event) => {
|
|
16
30
|
if (!plainClick(event)) {
|
|
17
31
|
return;
|
|
@@ -21,9 +35,27 @@ let HelixHeadlessTab = class HelixHeadlessTab extends MixinSelectableItem(HelixE
|
|
|
21
35
|
}
|
|
22
36
|
};
|
|
23
37
|
}
|
|
38
|
+
static { this.words = 'helix-tab'; }
|
|
24
39
|
focus(options) {
|
|
25
40
|
this.partNamed('tab')?.focus(options);
|
|
26
41
|
}
|
|
42
|
+
dismiss() {
|
|
43
|
+
if (!this.dismissible || this.disabled) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return super.dismiss();
|
|
47
|
+
}
|
|
48
|
+
get returnFocusTo() {
|
|
49
|
+
return this.partNamed('tab');
|
|
50
|
+
}
|
|
51
|
+
renderDismiss() {
|
|
52
|
+
if (!this.dismissible) {
|
|
53
|
+
return nothing;
|
|
54
|
+
}
|
|
55
|
+
return html `<span part="dismiss" aria-hidden="true" title=${this.dismissLabel} @click=${this.handleDismissPress}
|
|
56
|
+
><svg fill="currentColor" viewBox=${x.viewBox} aria-hidden="true">${unsafeSVG(x.content)}</svg></span
|
|
57
|
+
>`;
|
|
58
|
+
}
|
|
27
59
|
updated(changed) {
|
|
28
60
|
super.updated(changed);
|
|
29
61
|
const control = this.renderRoot.querySelector('[part="tab"]');
|
|
@@ -35,6 +67,7 @@ let HelixHeadlessTab = class HelixHeadlessTab extends MixinSelectableItem(HelixE
|
|
|
35
67
|
}
|
|
36
68
|
render() {
|
|
37
69
|
const tabindex = this.selected || !this.childrenKnown ? '0' : '-1';
|
|
70
|
+
const shortcut = this.dismissible && !this.disabled ? 'Delete' : nothing;
|
|
38
71
|
if ((this.href ?? '') !== '') {
|
|
39
72
|
return html `<a
|
|
40
73
|
part="tab"
|
|
@@ -44,10 +77,12 @@ let HelixHeadlessTab = class HelixHeadlessTab extends MixinSelectableItem(HelixE
|
|
|
44
77
|
rel=${this.rel || nothing}
|
|
45
78
|
aria-selected=${this.selected ? 'true' : 'false'}
|
|
46
79
|
aria-disabled=${this.disabled ? 'true' : nothing}
|
|
80
|
+
aria-keyshortcuts=${shortcut}
|
|
47
81
|
tabindex=${this.disabled ? nothing : tabindex}
|
|
48
82
|
@click=${this.handleLinkClick}
|
|
83
|
+
@keydown=${this.handleKeydown}
|
|
49
84
|
>
|
|
50
|
-
<slot></slot
|
|
85
|
+
<slot></slot>${this.renderDismiss()}
|
|
51
86
|
</a>`;
|
|
52
87
|
}
|
|
53
88
|
return html `<button
|
|
@@ -55,17 +90,23 @@ let HelixHeadlessTab = class HelixHeadlessTab extends MixinSelectableItem(HelixE
|
|
|
55
90
|
role="tab"
|
|
56
91
|
type="button"
|
|
57
92
|
aria-selected=${this.selected ? 'true' : 'false'}
|
|
93
|
+
aria-keyshortcuts=${shortcut}
|
|
58
94
|
?disabled=${this.disabled}
|
|
59
95
|
tabindex=${tabindex}
|
|
60
96
|
@click=${() => this.select()}
|
|
97
|
+
@keydown=${this.handleKeydown}
|
|
61
98
|
>
|
|
62
|
-
<slot></slot
|
|
99
|
+
<slot></slot>${this.renderDismiss()}
|
|
63
100
|
</button>`;
|
|
64
101
|
}
|
|
65
102
|
};
|
|
66
103
|
__decorate([
|
|
67
104
|
property({ attribute: false })
|
|
68
105
|
], HelixHeadlessTab.prototype, "controls", void 0);
|
|
106
|
+
__decorate([
|
|
107
|
+
words,
|
|
108
|
+
property({ attribute: 'dismiss-label' })
|
|
109
|
+
], HelixHeadlessTab.prototype, "dismissLabel", void 0);
|
|
69
110
|
__decorate([
|
|
70
111
|
property()
|
|
71
112
|
], HelixHeadlessTab.prototype, "href", void 0);
|
|
@@ -17,8 +17,12 @@ export type TagSelectionMode = SelectionMode;
|
|
|
17
17
|
* `selection-mode` lets a reader select tags, with Space, Enter or a press:
|
|
18
18
|
* `single` selects one at a time and `multiple` any number. In `multiple`,
|
|
19
19
|
* Shift with an arrow, Home or End selects the range from the tag last
|
|
20
|
-
* pressed to the tag focus moves to,
|
|
21
|
-
*
|
|
20
|
+
* pressed to the tag focus moves to, Shift with a press or Space selects the
|
|
21
|
+
* range to that tag, and Control or Command with A selects every tag that is
|
|
22
|
+
* not disabled. `selection-behavior="replace"` selects the tag a reader
|
|
23
|
+
* presses or moves to alone, as a file list does, and Control or Command
|
|
24
|
+
* with a press or Space adds or takes out one tag. `disallow-empty-selection`
|
|
25
|
+
* keeps one tag selected once one is. `value` holds the
|
|
22
26
|
* selected tags' values, separated by commas, and a change asks with
|
|
23
27
|
* `helix-before-change` and tells with `change`. Enter on a tag with an
|
|
24
28
|
* `href` follows its link. A `disabled` tag is passed over. A selected tag
|
|
@@ -56,6 +60,12 @@ export declare class HelixHeadlessTagGroup extends HelixElement {
|
|
|
56
60
|
* `registerWords('de', { 'helix-tag-group': { emptyLabel: 'Keine Schlagwörter' } })`.
|
|
57
61
|
*/
|
|
58
62
|
static words: string;
|
|
63
|
+
/**
|
|
64
|
+
* Keeps at least one tag selected once one is, as React Aria's
|
|
65
|
+
* `disallowEmptySelection`: a press or a key that would leave none selected
|
|
66
|
+
* is refused. Removing the last selected tag still takes it out of `value`.
|
|
67
|
+
*/
|
|
68
|
+
disallowEmptySelection: boolean;
|
|
59
69
|
/**
|
|
60
70
|
* The words the group shows and says once its last tag is removed, where
|
|
61
71
|
* nothing is in the `empty` slot.
|
|
@@ -66,6 +76,15 @@ export declare class HelixHeadlessTagGroup extends HelixElement {
|
|
|
66
76
|
* before its tags.
|
|
67
77
|
*/
|
|
68
78
|
label: string;
|
|
79
|
+
/**
|
|
80
|
+
* What a press, Space, or a move does to the tags already selected.
|
|
81
|
+
* `toggle`, the default, keeps them: a press adds or takes out the tag, and
|
|
82
|
+
* the arrow keys move focus alone. `replace` selects the tag alone, as a
|
|
83
|
+
* file list does, and the arrow keys carry the selection with focus.
|
|
84
|
+
* Control or Command with a press or Space adds or takes out one tag in
|
|
85
|
+
* `multiple`, and with an arrow moves focus without selecting.
|
|
86
|
+
*/
|
|
87
|
+
selectionBehavior: 'replace' | 'toggle';
|
|
69
88
|
/**
|
|
70
89
|
* How many tags a reader can select: `single`, `multiple`, or none.
|
|
71
90
|
*
|
|
@@ -129,7 +148,14 @@ export declare class HelixHeadlessTagGroup extends HelixElement {
|
|
|
129
148
|
* listener that refuses it keeps the selection. `change` follows once it
|
|
130
149
|
* has moved.
|
|
131
150
|
*/
|
|
132
|
-
protected press(tag: HelixHeadlessTag): void;
|
|
151
|
+
protected press(tag: HelixHeadlessTag, cause?: KeyboardEvent | MouseEvent): void;
|
|
152
|
+
/** Whether a press or a move selects the tag alone. */
|
|
153
|
+
private get replaces();
|
|
154
|
+
/**
|
|
155
|
+
* Asks for `value` to become `next`, where it changes and where it leaves a
|
|
156
|
+
* tag selected under `disallow-empty-selection`.
|
|
157
|
+
*/
|
|
158
|
+
private ask;
|
|
133
159
|
/** The reachable tags from one to the other, either way round. */
|
|
134
160
|
private span;
|
|
135
161
|
/**
|
|
@@ -11,8 +11,10 @@ import { HelixHeadlessTag } from '../root/tag-root.js';
|
|
|
11
11
|
let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments);
|
|
14
|
+
this.disallowEmptySelection = false;
|
|
14
15
|
this.emptyLabel = 'No tags';
|
|
15
16
|
this.label = '';
|
|
17
|
+
this.selectionBehavior = 'toggle';
|
|
16
18
|
this.value = '';
|
|
17
19
|
this.keys = new KeyboardNavigationController(this, {
|
|
18
20
|
items: () => this.reachable.map((tag) => this.navigable(tag)),
|
|
@@ -34,6 +36,13 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
|
|
|
34
36
|
}
|
|
35
37
|
return;
|
|
36
38
|
}
|
|
39
|
+
if ((event.ctrlKey || event.metaKey) && event.key === ' ') {
|
|
40
|
+
if (this.replaces && multiple) {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
this.press(tag, event);
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
37
46
|
if (event.ctrlKey || event.metaKey) {
|
|
38
47
|
return;
|
|
39
48
|
}
|
|
@@ -45,6 +54,9 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
|
|
|
45
54
|
else if (!event.shiftKey) {
|
|
46
55
|
this.anchor = to;
|
|
47
56
|
this.reached = to;
|
|
57
|
+
if (this.replaces && to && !to.disabled) {
|
|
58
|
+
this.ask(this.valueFor(to));
|
|
59
|
+
}
|
|
48
60
|
}
|
|
49
61
|
return;
|
|
50
62
|
}
|
|
@@ -62,7 +74,7 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
|
|
|
62
74
|
}
|
|
63
75
|
if ((event.key === ' ' || event.key === 'Enter') && this.selectionMode) {
|
|
64
76
|
event.preventDefault();
|
|
65
|
-
this.press(tag);
|
|
77
|
+
this.press(tag, event);
|
|
66
78
|
}
|
|
67
79
|
};
|
|
68
80
|
this.handleClick = (event) => {
|
|
@@ -72,7 +84,7 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
|
|
|
72
84
|
.some((node) => node instanceof Element &&
|
|
73
85
|
/(^| )(dismiss|link)( |$)/.test(node.getAttribute('part') ?? ''));
|
|
74
86
|
if (tag && !control) {
|
|
75
|
-
this.press(tag);
|
|
87
|
+
this.press(tag, event);
|
|
76
88
|
}
|
|
77
89
|
};
|
|
78
90
|
this.handleDismiss = (event) => {
|
|
@@ -172,14 +184,33 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
|
|
|
172
184
|
.composedPath()
|
|
173
185
|
.find((node) => node instanceof HelixHeadlessTag);
|
|
174
186
|
}
|
|
175
|
-
press(tag) {
|
|
187
|
+
press(tag, cause) {
|
|
176
188
|
const mode = this.selectionMode ?? 'none';
|
|
177
189
|
if (mode === 'none' || tag.disabled) {
|
|
178
190
|
return;
|
|
179
191
|
}
|
|
192
|
+
if (mode === 'multiple' && cause?.shiftKey && this.anchor?.isConnected) {
|
|
193
|
+
this.extend(this.anchor, tag);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
180
196
|
this.anchor = tag;
|
|
181
197
|
this.reached = tag;
|
|
182
|
-
|
|
198
|
+
const adds = cause?.ctrlKey === true || cause?.metaKey === true;
|
|
199
|
+
this.ask(this.replaces && !(adds && mode === 'multiple')
|
|
200
|
+
? this.valueFor(tag)
|
|
201
|
+
: pressedValue(mode, this.written, this.valueFor(tag)));
|
|
202
|
+
}
|
|
203
|
+
get replaces() {
|
|
204
|
+
return this.selectionMode !== undefined && this.selectionBehavior === 'replace';
|
|
205
|
+
}
|
|
206
|
+
ask(next) {
|
|
207
|
+
if (next === this.written) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (this.disallowEmptySelection && commaList(next).length === 0) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
requestValue(this, next, (value) => {
|
|
183
214
|
this.value = value;
|
|
184
215
|
});
|
|
185
216
|
}
|
|
@@ -199,13 +230,7 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
|
|
|
199
230
|
this.choose(this.tags.filter((tag) => chosen.includes(tag)));
|
|
200
231
|
}
|
|
201
232
|
choose(tags) {
|
|
202
|
-
|
|
203
|
-
if (next === this.written) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
requestValue(this, next, (value) => {
|
|
207
|
-
this.value = value;
|
|
208
|
-
});
|
|
233
|
+
this.ask([...new Set(tags.map((tag) => this.valueFor(tag)))].join(', '));
|
|
209
234
|
}
|
|
210
235
|
connectedCallback() {
|
|
211
236
|
super.connectedCallback();
|
|
@@ -241,6 +266,9 @@ let HelixHeadlessTagGroup = class HelixHeadlessTagGroup extends HelixElement {
|
|
|
241
266
|
return html `<slot @slotchange=${this.handleSlotchange}></slot>${empty}`;
|
|
242
267
|
}
|
|
243
268
|
};
|
|
269
|
+
__decorate([
|
|
270
|
+
property({ attribute: 'disallow-empty-selection', type: Boolean })
|
|
271
|
+
], HelixHeadlessTagGroup.prototype, "disallowEmptySelection", void 0);
|
|
244
272
|
__decorate([
|
|
245
273
|
words,
|
|
246
274
|
property({ attribute: 'empty-label' })
|
|
@@ -248,6 +276,9 @@ __decorate([
|
|
|
248
276
|
__decorate([
|
|
249
277
|
property()
|
|
250
278
|
], HelixHeadlessTagGroup.prototype, "label", void 0);
|
|
279
|
+
__decorate([
|
|
280
|
+
property({ attribute: 'selection-behavior' })
|
|
281
|
+
], HelixHeadlessTagGroup.prototype, "selectionBehavior", void 0);
|
|
251
282
|
__decorate([
|
|
252
283
|
property({ attribute: 'selection-mode' })
|
|
253
284
|
], HelixHeadlessTagGroup.prototype, "selectionMode", void 0);
|
|
@@ -25,6 +25,8 @@ declare const HelixHeadlessToggle_base: (new (...args: any[]) => import("@fusion
|
|
|
25
25
|
* @documentation https://fusion.dev/helix/components/toggle
|
|
26
26
|
*
|
|
27
27
|
* @slot - The item's label.
|
|
28
|
+
* @slot end - Content after the label, such as a count or a shortcut.
|
|
29
|
+
* @slot start - Content before the label, such as an icon.
|
|
28
30
|
*
|
|
29
31
|
* @event {Event} change - Fired after a toggle button on its own is pressed or released.
|
|
30
32
|
* @event {HelixSelectEvent} helix-select - Fired when the item is chosen. Cancelable: call `preventDefault()` to keep the current choice.
|
|
@@ -53,7 +53,7 @@ let HelixHeadlessToggle = class HelixHeadlessToggle extends MixinSelectableItem(
|
|
|
53
53
|
?disabled=${this.disabled}
|
|
54
54
|
tabindex=${alone || (this.focusable ?? this.selected) || !this.childrenKnown ? '0' : '-1'}
|
|
55
55
|
@click=${() => this.select()}
|
|
56
|
-
><slot></slot></button>`;
|
|
56
|
+
><slot name="start"></slot><slot></slot><slot name="end"></slot></button>`;
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
__decorate([
|
|
@@ -146,6 +146,11 @@ export declare class HelixHeadlessTreeItem extends HelixHeadlessTreeItem_base {
|
|
|
146
146
|
* tree: the item being moved, or the one the drop indicator is beside.
|
|
147
147
|
*/
|
|
148
148
|
move?: TreeItemMove | undefined;
|
|
149
|
+
/**
|
|
150
|
+
* @internal The item's place among its siblings, counting from one, written
|
|
151
|
+
* by the tree for `aria-posinset`, for the reason `level` gives.
|
|
152
|
+
*/
|
|
153
|
+
position?: number | undefined;
|
|
149
154
|
/**
|
|
150
155
|
* The link's `rel` value, which describes how the linked page relates to this
|
|
151
156
|
* one.
|
|
@@ -168,6 +173,11 @@ export declare class HelixHeadlessTreeItem extends HelixHeadlessTreeItem_base {
|
|
|
168
173
|
* row is not part of the tree React hydrates.
|
|
169
174
|
*/
|
|
170
175
|
selected: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* @internal How many siblings the item has, itself included, written by the
|
|
178
|
+
* tree for `aria-setsize`, for the reason `level` gives.
|
|
179
|
+
*/
|
|
180
|
+
setSize?: number | undefined;
|
|
171
181
|
/**
|
|
172
182
|
* Where the link opens. Use it for a row that leaves the site.
|
|
173
183
|
*/
|