@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
|
@@ -25,6 +25,10 @@ declare const HelixHeadlessAccordionItem_base: (new (...args: any[]) => import("
|
|
|
25
25
|
* and a closed section is `hidden` and `inert` instead, and find in page skips
|
|
26
26
|
* it.
|
|
27
27
|
*
|
|
28
|
+
* A section whose trigger has no words, with nothing in its `summary` slot,
|
|
29
|
+
* is announced as a bare button, and a development build warns once with the
|
|
30
|
+
* kind `missing-name`.
|
|
31
|
+
*
|
|
28
32
|
* While it is open, the content part carries the content's height as
|
|
29
33
|
* `--accordion-content-height`, as Radix's accordion does, for a stylesheet
|
|
30
34
|
* that animates the section open with keyframes of its own.
|
|
@@ -51,6 +55,7 @@ declare const HelixHeadlessAccordionItem_base: (new (...args: any[]) => import("
|
|
|
51
55
|
*
|
|
52
56
|
* @cssproperty --accordion-content-height - The height of the content, in pixels, written on the `content` part while the section is open.
|
|
53
57
|
*
|
|
58
|
+
* @cssstate closing - While the item has closed and its content is still drawn, until the content part's animations end.
|
|
54
59
|
* @cssstate open - While the item is open.
|
|
55
60
|
*/
|
|
56
61
|
export declare class HelixHeadlessAccordionItem extends HelixHeadlessAccordionItem_base {
|
|
@@ -88,6 +93,11 @@ export declare class HelixHeadlessAccordionItem extends HelixHeadlessAccordionIt
|
|
|
88
93
|
* for the reason `groupHeadingLevel` is a property.
|
|
89
94
|
*/
|
|
90
95
|
groupHiddenUntilFound: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* @internal The accordion's `mode`, written by the accordion, so the trigger
|
|
98
|
+
* of the open section says it cannot close under `single`.
|
|
99
|
+
*/
|
|
100
|
+
groupMode: 'single' | 'single-collapsible' | undefined;
|
|
91
101
|
/**
|
|
92
102
|
* The level of the heading around the trigger, from 1 to 6.
|
|
93
103
|
*
|
|
@@ -155,6 +165,7 @@ export declare class HelixHeadlessAccordionItem extends HelixHeadlessAccordionIt
|
|
|
155
165
|
* and runs before the next paint or key press.
|
|
156
166
|
*/
|
|
157
167
|
private readonly handleBeforeMatch;
|
|
168
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
158
169
|
/** Whether the item was open at the last update, and `undefined` before one. */
|
|
159
170
|
private wasOpen;
|
|
160
171
|
/**
|
|
@@ -239,6 +250,10 @@ export declare class HelixHeadlessAccordionItem extends HelixHeadlessAccordionIt
|
|
|
239
250
|
* the accessibility tree by itself, and an inert region is never searched.
|
|
240
251
|
* It leaves the region's own box in the tree, so a closed findable section
|
|
241
252
|
* drops `role="region"` and its name until it opens.
|
|
253
|
+
*
|
|
254
|
+
* Under `mode="single"` the open section's trigger is `aria-disabled`, as in
|
|
255
|
+
* Radix, because a press cannot close the last open section. It stays
|
|
256
|
+
* focusable, so the arrow keys still reach it.
|
|
242
257
|
*/
|
|
243
258
|
protected render(): TemplateResult;
|
|
244
259
|
}
|
|
@@ -4,7 +4,7 @@ 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 { animationsFinished, closestInstance, customElement, HelixElement, headingLevel, MixinControllerOpen, onUnlessFalse, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { animationsFinished, auditName, closestInstance, customElement, HelixElement, headingLevel, MixinControllerOpen, onUnlessFalse, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretDown } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property, state } from 'lit/decorators.js';
|
|
@@ -18,6 +18,7 @@ let HelixHeadlessAccordionItem = class HelixHeadlessAccordionItem extends MixinC
|
|
|
18
18
|
this.disabled = false;
|
|
19
19
|
this.groupHeadingLevel = undefined;
|
|
20
20
|
this.groupHiddenUntilFound = true;
|
|
21
|
+
this.groupMode = undefined;
|
|
21
22
|
this.closing = false;
|
|
22
23
|
this.handleBeforeMatch = (event) => {
|
|
23
24
|
if (this.open || this.requestOpen(true)) {
|
|
@@ -76,6 +77,7 @@ let HelixHeadlessAccordionItem = class HelixHeadlessAccordionItem extends MixinC
|
|
|
76
77
|
if (changed.has('open')) {
|
|
77
78
|
this.closing = !this.open && changed.get('open') === true && this.findable;
|
|
78
79
|
}
|
|
80
|
+
this.toggleState('closing', this.closing);
|
|
79
81
|
}
|
|
80
82
|
finishClosing() {
|
|
81
83
|
void animationsFinished(this.partNamed('content')).then(() => {
|
|
@@ -84,6 +86,10 @@ let HelixHeadlessAccordionItem = class HelixHeadlessAccordionItem extends MixinC
|
|
|
84
86
|
}
|
|
85
87
|
});
|
|
86
88
|
}
|
|
89
|
+
firstUpdated(changed) {
|
|
90
|
+
super.firstUpdated(changed);
|
|
91
|
+
auditName(this, () => this.renderRoot.querySelector(`#${TRIGGER_ID}`), 'Write its title in the `summary` slot.');
|
|
92
|
+
}
|
|
87
93
|
get accordion() {
|
|
88
94
|
return closestInstance(this, HelixHeadlessAccordion);
|
|
89
95
|
}
|
|
@@ -129,12 +135,14 @@ let HelixHeadlessAccordionItem = class HelixHeadlessAccordionItem extends MixinC
|
|
|
129
135
|
const shown = open || this.closing;
|
|
130
136
|
const landmark = shown || !this.findable;
|
|
131
137
|
const level = headingLevel(this.headingLevel ?? this.groupHeadingLevel);
|
|
138
|
+
const locked = open && this.groupMode === 'single';
|
|
132
139
|
return html `
|
|
133
140
|
<div part="container">
|
|
134
141
|
<div part="heading" role="heading" aria-level=${level}>
|
|
135
142
|
<button
|
|
136
143
|
id=${TRIGGER_ID}
|
|
137
144
|
aria-controls=${CONTENT_ID}
|
|
145
|
+
aria-disabled=${locked ? 'true' : nothing}
|
|
138
146
|
aria-expanded=${open ? 'true' : 'false'}
|
|
139
147
|
part="trigger"
|
|
140
148
|
type="button"
|
|
@@ -175,6 +183,9 @@ __decorate([
|
|
|
175
183
|
__decorate([
|
|
176
184
|
property({ attribute: false })
|
|
177
185
|
], HelixHeadlessAccordionItem.prototype, "groupHiddenUntilFound", void 0);
|
|
186
|
+
__decorate([
|
|
187
|
+
property({ attribute: false })
|
|
188
|
+
], HelixHeadlessAccordionItem.prototype, "groupMode", void 0);
|
|
178
189
|
__decorate([
|
|
179
190
|
property({ attribute: 'heading-level', type: Number })
|
|
180
191
|
], HelixHeadlessAccordionItem.prototype, "headingLevel", void 0);
|
|
@@ -20,7 +20,8 @@ import { HelixHeadlessAccordionItem } from '../item/accordion-item.js';
|
|
|
20
20
|
* Keys, on a trigger: Up and Down Arrow move to the previous and the next
|
|
21
21
|
* trigger, and so do Left and Right, wrapping at the ends; Home and End go to
|
|
22
22
|
* the first and the last. Enter and Space open and close the section. Disabled
|
|
23
|
-
* sections are passed over.
|
|
23
|
+
* sections are passed over. Under `single` the open section's trigger is
|
|
24
|
+
* `aria-disabled`, because a press cannot close it.
|
|
24
25
|
*
|
|
25
26
|
* Each trigger is a button with `aria-expanded` and `aria-controls`, inside a
|
|
26
27
|
* heading, level 3 unless `heading-level` says otherwise, so the sections are
|
|
@@ -32,6 +32,7 @@ let HelixHeadlessAccordion = class HelixHeadlessAccordion extends HelixElement {
|
|
|
32
32
|
whenHydrated(item, () => {
|
|
33
33
|
item.groupHeadingLevel = this.headingLevel;
|
|
34
34
|
item.groupHiddenUntilFound = this.hiddenUntilFound;
|
|
35
|
+
item.groupMode = this.mode;
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
if (this.mode === undefined) {
|
|
@@ -35,6 +35,12 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
35
35
|
* button "Inbox 8 unread", and stays current as the count changes. The styled
|
|
36
36
|
* tier's `placement="inline"` draws the mark in the line for that case.
|
|
37
37
|
*
|
|
38
|
+
* **An icon goes inside the mark** through the `icon` slot, before the count,
|
|
39
|
+
* as Web Awesome's badge takes one at its start. With no `value` the icon is
|
|
40
|
+
* the whole mark, such as a check on an avatar, drawn where the dot would be.
|
|
41
|
+
* The icon is hidden from screen readers: the count and `label` are what the
|
|
42
|
+
* mark says.
|
|
43
|
+
*
|
|
38
44
|
* @since 0.1.0
|
|
39
45
|
* @status stable
|
|
40
46
|
* @category markers
|
|
@@ -42,12 +48,15 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
42
48
|
* @documentation https://fusion.dev/helix/components/badge
|
|
43
49
|
*
|
|
44
50
|
* @slot - The element being decorated.
|
|
51
|
+
* @slot icon - An icon inside the mark, before the count, or the whole mark when there is no value. Hidden from screen readers.
|
|
45
52
|
*
|
|
46
|
-
* @csspart
|
|
53
|
+
* @csspart icon - The slot the icon arrives through, inside the indicator. A slot draws no box, so it passes inherited properties, such as color, to the icon.
|
|
54
|
+
* @csspart indicator - The count, or the dot when there is no value. Carries `data-dot` when there is no value, `data-single` when the count is one character, and `data-blank` when the value is zero.
|
|
47
55
|
*
|
|
48
56
|
* @cssstate blank - The value is zero, so there is no mark.
|
|
49
57
|
* @cssstate capped - The count is past `max`, so the mark shows `{max}+`.
|
|
50
|
-
* @cssstate dot - There is no value, so the mark is a dot.
|
|
58
|
+
* @cssstate dot - There is no value and no icon, so the mark is a dot.
|
|
59
|
+
* @cssstate has-icon - Something is slotted into `icon`, so it is drawn inside the mark.
|
|
51
60
|
*/
|
|
52
61
|
export declare class HelixHeadlessBadge extends HelixElement {
|
|
53
62
|
/**
|
|
@@ -98,6 +107,10 @@ export declare class HelixHeadlessBadge extends HelixElement {
|
|
|
98
107
|
* is showing a count must not lose the count for being handed one.
|
|
99
108
|
*/
|
|
100
109
|
value: number | string;
|
|
110
|
+
/** Whether something is slotted into `icon`, read from the children. */
|
|
111
|
+
private hasIcon;
|
|
112
|
+
/** Reads whether an icon is slotted, from the children where they can be known. */
|
|
113
|
+
private readonly readIcon;
|
|
101
114
|
/** The value as text, whatever it was set to. */
|
|
102
115
|
private get given();
|
|
103
116
|
/** Whether the value counts nothing, in which case there is no mark. */
|
|
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
6
6
|
};
|
|
7
7
|
import { customElement, focusTarget, HelixElement, wordsLocale } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
|
-
import { property } from 'lit/decorators.js';
|
|
9
|
+
import { property, state } from 'lit/decorators.js';
|
|
10
10
|
import { badgeBlank, badgeCapped, badgeText, badgeWords } from '../count.js';
|
|
11
11
|
let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
|
|
12
12
|
constructor() {
|
|
@@ -14,6 +14,10 @@ let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
|
|
|
14
14
|
this.label = '';
|
|
15
15
|
this.locale = '';
|
|
16
16
|
this.value = '';
|
|
17
|
+
this.hasIcon = false;
|
|
18
|
+
this.readIcon = () => {
|
|
19
|
+
this.hasIcon = this.knownChildren.some((child) => child.slot === 'icon');
|
|
20
|
+
};
|
|
17
21
|
this.handleSlotchange = () => {
|
|
18
22
|
this.describe();
|
|
19
23
|
};
|
|
@@ -81,9 +85,13 @@ let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
|
|
|
81
85
|
}
|
|
82
86
|
willUpdate(changed) {
|
|
83
87
|
super.willUpdate(changed);
|
|
88
|
+
if (!this.hasUpdated) {
|
|
89
|
+
this.readIcon();
|
|
90
|
+
}
|
|
84
91
|
this.toggleState('capped', this.capped);
|
|
85
|
-
this.toggleState('dot', this.given.trim() === '');
|
|
92
|
+
this.toggleState('dot', this.given.trim() === '' && !this.hasIcon);
|
|
86
93
|
this.toggleState('blank', this.blank);
|
|
94
|
+
this.toggleState('has-icon', this.hasIcon);
|
|
87
95
|
}
|
|
88
96
|
updated(changed) {
|
|
89
97
|
super.updated(changed);
|
|
@@ -97,10 +105,16 @@ let HelixHeadlessBadge = class HelixHeadlessBadge extends HelixElement {
|
|
|
97
105
|
<span
|
|
98
106
|
part="indicator"
|
|
99
107
|
data-blank=${blank ? '' : nothing}
|
|
108
|
+
data-dot=${this.given.trim() === '' ? '' : nothing}
|
|
100
109
|
data-single=${text.length <= 1 ? '' : nothing}
|
|
101
110
|
role="status"
|
|
102
111
|
aria-label=${announced ? this.words : nothing}
|
|
103
|
-
|
|
112
|
+
><slot
|
|
113
|
+
aria-hidden="true"
|
|
114
|
+
name="icon"
|
|
115
|
+
part="icon"
|
|
116
|
+
@slotchange=${this.readIcon}
|
|
117
|
+
></slot>${blank ? html `<span aria-hidden="true">${text}</span>` : text}</span
|
|
104
118
|
>`;
|
|
105
119
|
}
|
|
106
120
|
};
|
|
@@ -119,6 +133,9 @@ __decorate([
|
|
|
119
133
|
__decorate([
|
|
120
134
|
property()
|
|
121
135
|
], HelixHeadlessBadge.prototype, "value", void 0);
|
|
136
|
+
__decorate([
|
|
137
|
+
state()
|
|
138
|
+
], HelixHeadlessBadge.prototype, "hasIcon", void 0);
|
|
122
139
|
HelixHeadlessBadge = __decorate([
|
|
123
140
|
customElement('helix-headless-badge')
|
|
124
141
|
], HelixHeadlessBadge);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
-
import { type CSSResultGroup, type PropertyValues, type TemplateResult } from 'lit';
|
|
2
|
+
import { type CSSResultGroup, nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
3
3
|
/**
|
|
4
4
|
* @summary One step in a breadcrumb trail.
|
|
5
5
|
*
|
|
@@ -11,8 +11,11 @@ import { type CSSResultGroup, type PropertyValues, type TemplateResult } from 'l
|
|
|
11
11
|
* Set `href` to make the whole crumb an anchor, which works with Enter, middle
|
|
12
12
|
* click, and the status bar, and before any JavaScript runs. A router cannot
|
|
13
13
|
* reach an anchor inside a shadow root, so intercept the click for client-side
|
|
14
|
-
* navigation, as with `Tree.Item` and `List.Item
|
|
15
|
-
*
|
|
14
|
+
* navigation, as with `Tree.Item` and `List.Item`: a plain press on the link
|
|
15
|
+
* sends `helix-select` with the `href` as its value first, and canceling it
|
|
16
|
+
* keeps the browser from following, so a client-side router follows it
|
|
17
|
+
* instead, as React Aria's `onAction` lets a router do. A press with a
|
|
18
|
+
* modifier opens the link where the browser opens it and sends nothing.
|
|
16
19
|
*
|
|
17
20
|
* Without `href`, the crumb renders a plain `<span>` around what you slot,
|
|
18
21
|
* including your own anchor.
|
|
@@ -37,12 +40,18 @@ import { type CSSResultGroup, type PropertyValues, type TemplateResult } from 'l
|
|
|
37
40
|
* @slot separator - Replaces the chevron shown before this item.
|
|
38
41
|
* @slot start - Content before the label, such as an icon.
|
|
39
42
|
*
|
|
43
|
+
* @event {HelixSelectEvent} helix-select - A plain press on the crumb's link, with its `href` as the value, before the browser follows it. Cancelable: cancel it to keep the browser from following, as a client-side router does.
|
|
44
|
+
*
|
|
45
|
+
* @csspart expand - The button after the first crumb of a folded slotted trail, which shows the folded crumbs.
|
|
46
|
+
* @csspart fold-separator - The chevron before that button.
|
|
40
47
|
* @csspart link - The crumb itself, which is an `<a>` where `href` is set.
|
|
41
48
|
* @csspart separator - The separator preceding the item.
|
|
42
49
|
* @csspart separator-mark - The chevron between crumbs, shown when no separator is slotted.
|
|
43
50
|
*
|
|
51
|
+
* @cssstate current - While the crumb is the current page, marked `current` or the last of a trail with none marked.
|
|
44
52
|
* @cssstate end - While the `end` slot holds something, so a stylesheet can join
|
|
45
53
|
* the label and what follows it into one control.
|
|
54
|
+
* @cssstate folded - While a trail longer than its `max-items` has folded the crumb away. It takes no room.
|
|
46
55
|
*/
|
|
47
56
|
export declare class HelixHeadlessBreadcrumb extends HelixElement {
|
|
48
57
|
static styles: CSSResultGroup;
|
|
@@ -50,6 +59,18 @@ export declare class HelixHeadlessBreadcrumb extends HelixElement {
|
|
|
50
59
|
* Marks this item as the page you are on.
|
|
51
60
|
*/
|
|
52
61
|
current: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* @internal Set by the trail: how many crumbs after this one a slotted trail
|
|
64
|
+
* longer than its `max-items` has folded, so this crumb draws the button
|
|
65
|
+
* that shows them. 0 draws none.
|
|
66
|
+
*/
|
|
67
|
+
fold: number;
|
|
68
|
+
/**
|
|
69
|
+
* @internal Set by the trail: whether this crumb is folded away.
|
|
70
|
+
*/
|
|
71
|
+
folded: boolean;
|
|
72
|
+
/** @internal Set by the trail: the name of the button that shows the folded crumbs. */
|
|
73
|
+
foldLabel: string;
|
|
53
74
|
/**
|
|
54
75
|
* The URL the crumb links to. The current page has none.
|
|
55
76
|
*
|
|
@@ -74,6 +95,8 @@ export declare class HelixHeadlessBreadcrumb extends HelixElement {
|
|
|
74
95
|
* `current`, so this one is said to be the current page.
|
|
75
96
|
*/
|
|
76
97
|
implied: boolean;
|
|
98
|
+
/** @internal Set by the trail: what a press on the fold's button does. */
|
|
99
|
+
onFold: (() => void) | undefined;
|
|
77
100
|
/**
|
|
78
101
|
* The link's `rel` value, which describes how the linked page relates to this
|
|
79
102
|
* one.
|
|
@@ -86,6 +109,16 @@ export declare class HelixHeadlessBreadcrumb extends HelixElement {
|
|
|
86
109
|
*/
|
|
87
110
|
target: string;
|
|
88
111
|
protected willUpdate(changed: PropertyValues): void;
|
|
112
|
+
/**
|
|
113
|
+
* Sends `helix-select` for a plain press on the link, and keeps the browser
|
|
114
|
+
* from following where it was canceled.
|
|
115
|
+
*/
|
|
116
|
+
private readonly handleLinkClick;
|
|
117
|
+
/**
|
|
118
|
+
* The button that shows the crumbs a slotted trail folded after this one,
|
|
119
|
+
* with a chevron before it, drawn after the crumb's own label.
|
|
120
|
+
*/
|
|
121
|
+
protected renderFold(): TemplateResult | typeof nothing;
|
|
89
122
|
protected updated(changed: PropertyValues): void;
|
|
90
123
|
/**
|
|
91
124
|
* Says whether anything sits after the label.
|
|
@@ -4,7 +4,7 @@ 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, hostAria, slotFilled, slotNamed, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { customElement, HelixElement, HelixSelectEvent, hostAria, plainClick, slotFilled, slotNamed, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretRight } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { css, html, nothing, } from 'lit';
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
@@ -13,9 +13,21 @@ let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement
|
|
|
13
13
|
constructor() {
|
|
14
14
|
super(...arguments);
|
|
15
15
|
this.current = false;
|
|
16
|
+
this.fold = 0;
|
|
17
|
+
this.folded = false;
|
|
18
|
+
this.foldLabel = '';
|
|
16
19
|
this.implied = false;
|
|
20
|
+
this.onFold = undefined;
|
|
17
21
|
this.rel = '';
|
|
18
22
|
this.target = '';
|
|
23
|
+
this.handleLinkClick = (event) => {
|
|
24
|
+
if (!plainClick(event)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!this.dispatchEvent(new HelixSelectEvent({ value: this.href ?? '' }))) {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
19
31
|
this.handleEnd = () => {
|
|
20
32
|
this.markEnd();
|
|
21
33
|
};
|
|
@@ -24,11 +36,29 @@ let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement
|
|
|
24
36
|
:host(:first-child) [part='separator'] {
|
|
25
37
|
display: none;
|
|
26
38
|
}
|
|
39
|
+
|
|
40
|
+
:host(:state(folded)) {
|
|
41
|
+
display: none;
|
|
42
|
+
}
|
|
27
43
|
`; }
|
|
28
44
|
willUpdate(changed) {
|
|
29
45
|
super.willUpdate(changed);
|
|
30
46
|
hostAria(this.internals, { role: 'listitem' });
|
|
31
47
|
this.internals.ariaCurrent = this.current || this.implied ? 'page' : null;
|
|
48
|
+
this.toggleState('current', this.current || this.implied);
|
|
49
|
+
this.toggleState('folded', this.folded);
|
|
50
|
+
}
|
|
51
|
+
renderFold() {
|
|
52
|
+
if (this.fold === 0) {
|
|
53
|
+
return nothing;
|
|
54
|
+
}
|
|
55
|
+
return html `<span part="fold-separator" aria-hidden="true"
|
|
56
|
+
><svg data-mirror fill="currentColor" viewBox=${caretRight.viewBox}>
|
|
57
|
+
${unsafeSVG(caretRight.content)}
|
|
58
|
+
</svg></span
|
|
59
|
+
><button part="expand" type="button" aria-label=${this.foldLabel} @click=${() => this.onFold?.()}>
|
|
60
|
+
…
|
|
61
|
+
</button>`;
|
|
32
62
|
}
|
|
33
63
|
updated(changed) {
|
|
34
64
|
super.updated(changed);
|
|
@@ -55,14 +85,24 @@ let HelixHeadlessBreadcrumb = class HelixHeadlessBreadcrumb extends HelixElement
|
|
|
55
85
|
href=${this.href ?? ''}
|
|
56
86
|
target=${this.target || nothing}
|
|
57
87
|
rel=${this.rel || nothing}
|
|
88
|
+
@click=${this.handleLinkClick}
|
|
58
89
|
>${label}</a
|
|
59
90
|
>`
|
|
60
|
-
: html `<span part="link">${label}</span>`}<slot name="end" @slotchange=${this.handleEnd}></slot
|
|
91
|
+
: html `<span part="link">${label}</span>`}<slot name="end" @slotchange=${this.handleEnd}></slot>${this.renderFold()}`;
|
|
61
92
|
}
|
|
62
93
|
};
|
|
63
94
|
__decorate([
|
|
64
95
|
property({ reflect: true, type: Boolean })
|
|
65
96
|
], HelixHeadlessBreadcrumb.prototype, "current", void 0);
|
|
97
|
+
__decorate([
|
|
98
|
+
property({ attribute: false })
|
|
99
|
+
], HelixHeadlessBreadcrumb.prototype, "fold", void 0);
|
|
100
|
+
__decorate([
|
|
101
|
+
property({ attribute: false })
|
|
102
|
+
], HelixHeadlessBreadcrumb.prototype, "folded", void 0);
|
|
103
|
+
__decorate([
|
|
104
|
+
property({ attribute: false })
|
|
105
|
+
], HelixHeadlessBreadcrumb.prototype, "foldLabel", void 0);
|
|
66
106
|
__decorate([
|
|
67
107
|
property({ reflect: true })
|
|
68
108
|
], HelixHeadlessBreadcrumb.prototype, "href", void 0);
|
|
@@ -35,11 +35,13 @@ export interface BreadcrumbItem {
|
|
|
35
35
|
* array of `{ label, href }`, drawn as crumbs in order, the last one as the
|
|
36
36
|
* current page. Slotted crumbs follow them. Use one way or the other in a trail.
|
|
37
37
|
*
|
|
38
|
-
* With `max-items`, a trail
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
38
|
+
* With `max-items`, a trail that is longer folds its middle into one place:
|
|
39
|
+
* the first crumb, a button, then the last `max-items` less one. The button is
|
|
40
|
+
* named by `expand-label` and shows the whole trail, moving focus to the first
|
|
41
|
+
* crumb it brought back. A trail drawn from `items` draws the button as a
|
|
42
|
+
* crumb of its own, and a new `items` folds again. A slotted trail hides the
|
|
43
|
+
* crumbs it folds, which take no room, and its first crumb draws the button
|
|
44
|
+
* after its link; a change to the crumbs folds again.
|
|
43
45
|
*
|
|
44
46
|
* **Keyboard.** Each crumb with an `href` is a link and one tab stop, and
|
|
45
47
|
* Enter follows it. The current page and the separators are not in the tab
|
|
@@ -58,6 +60,8 @@ export interface BreadcrumbItem {
|
|
|
58
60
|
* @csspart crumb - Each crumb drawn from `items`.
|
|
59
61
|
* @csspart crumb-collapsed - The crumb that holds the folded middle of a trail longer than `max-items`.
|
|
60
62
|
* @csspart crumb-current - The crumb drawn from `items` for the current page.
|
|
63
|
+
* @csspart crumb-expand - The fold button a crumb draws after the first crumb of a folded slotted trail. A trail drawn from `items` draws its fold as a crumb of its own, `crumb-collapsed`, so this one is never drawn there.
|
|
64
|
+
* @csspart crumb-fold-separator - The chevron before that button.
|
|
61
65
|
* @csspart crumb-link - The link, or the current page's words, of a crumb drawn from `items`.
|
|
62
66
|
* @csspart crumb-separator - The separator before a crumb drawn from `items`.
|
|
63
67
|
* @csspart crumb-separator-mark - The chevron before a crumb drawn from `items`.
|
|
@@ -98,6 +102,10 @@ export declare class HelixHeadlessBreadcrumbs extends HelixElement {
|
|
|
98
102
|
maxItems?: number;
|
|
99
103
|
/** Whether a reader has asked to see the whole of a folded trail. */
|
|
100
104
|
private expanded;
|
|
105
|
+
/** The crumbs slotted into the trail, in order. */
|
|
106
|
+
private get slotted();
|
|
107
|
+
/** Shows the whole of a folded slotted trail and moves focus to the first crumb it brought back. */
|
|
108
|
+
private readonly unfold;
|
|
101
109
|
/** Shows the whole trail and moves focus to the first crumb it brought back. */
|
|
102
110
|
private readonly expand;
|
|
103
111
|
protected willUpdate(changed: PropertyValues): void;
|
|
@@ -110,6 +118,8 @@ export declare class HelixHeadlessBreadcrumbs extends HelixElement {
|
|
|
110
118
|
* not reflect, so nothing is written onto your markup.
|
|
111
119
|
*/
|
|
112
120
|
private readonly markLast;
|
|
121
|
+
/** New crumbs fold again, as a new `items` does. */
|
|
122
|
+
private readonly handleSlotChange;
|
|
113
123
|
/** The crumbs `items` describes, each a crumb of this tier. */
|
|
114
124
|
protected renderItems(): TemplateResult | typeof nothing;
|
|
115
125
|
protected render(): TemplateResult;
|
|
@@ -17,6 +17,15 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
|
|
|
17
17
|
this.items = [];
|
|
18
18
|
this.label = 'Breadcrumb';
|
|
19
19
|
this.expanded = false;
|
|
20
|
+
this.unfold = () => {
|
|
21
|
+
this.expanded = true;
|
|
22
|
+
this.updateComplete.then(() => {
|
|
23
|
+
const crumb = this.slotted[1];
|
|
24
|
+
const link = crumb?.shadowRoot?.querySelector('a[part="link"]') ??
|
|
25
|
+
crumb?.querySelector('a[href]');
|
|
26
|
+
link?.focus();
|
|
27
|
+
});
|
|
28
|
+
};
|
|
20
29
|
this.expand = () => {
|
|
21
30
|
this.expanded = true;
|
|
22
31
|
this.updateComplete.then(() => {
|
|
@@ -25,16 +34,32 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
|
|
|
25
34
|
});
|
|
26
35
|
};
|
|
27
36
|
this.markLast = () => {
|
|
28
|
-
const crumbs =
|
|
37
|
+
const crumbs = this.slotted;
|
|
29
38
|
const marked = crumbs.some((one) => one.current);
|
|
39
|
+
const most = this.maxItems ?? 0;
|
|
40
|
+
const fold = !this.expanded && this.items.length === 0 && most >= 2 && crumbs.length > most
|
|
41
|
+
? crumbs.length - most
|
|
42
|
+
: 0;
|
|
43
|
+
const label = fold > 0 ? formatWords(this, 'expandLabel', { count: fold }) : '';
|
|
30
44
|
crumbs.forEach((crumb, index) => {
|
|
31
45
|
whenHydrated(crumb, () => {
|
|
32
46
|
crumb.implied = !marked && this.items.length === 0 && index === crumbs.length - 1;
|
|
47
|
+
crumb.folded = index > 0 && index <= fold;
|
|
48
|
+
crumb.fold = index === 0 ? fold : 0;
|
|
49
|
+
crumb.foldLabel = index === 0 ? label : '';
|
|
50
|
+
crumb.onFold = index === 0 && fold > 0 ? this.unfold : undefined;
|
|
33
51
|
});
|
|
34
52
|
});
|
|
35
53
|
};
|
|
54
|
+
this.handleSlotChange = () => {
|
|
55
|
+
this.expanded = false;
|
|
56
|
+
this.markLast();
|
|
57
|
+
};
|
|
36
58
|
}
|
|
37
59
|
static { this.words = 'helix-breadcrumbs'; }
|
|
60
|
+
get slotted() {
|
|
61
|
+
return (this.renderRoot?.querySelector('slot:not([name])')?.assignedElements() ?? []).filter((one) => one instanceof HelixHeadlessBreadcrumb);
|
|
62
|
+
}
|
|
38
63
|
willUpdate(changed) {
|
|
39
64
|
super.willUpdate(changed);
|
|
40
65
|
hostAria(this.internals, { label: this.label, role: 'navigation' });
|
|
@@ -65,7 +90,7 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
|
|
|
65
90
|
? nothing
|
|
66
91
|
: html `<${crumb}
|
|
67
92
|
part="crumb crumb-collapsed"
|
|
68
|
-
exportparts="link: crumb-link, separator: crumb-separator, separator-mark: crumb-separator-mark"
|
|
93
|
+
exportparts="expand: crumb-expand, fold-separator: crumb-fold-separator, link: crumb-link, separator: crumb-separator, separator-mark: crumb-separator-mark"
|
|
69
94
|
><button
|
|
70
95
|
part="expand"
|
|
71
96
|
type="button"
|
|
@@ -79,7 +104,7 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
|
|
|
79
104
|
const rest = shown.slice(1);
|
|
80
105
|
const draw = ({ index, item }) => html `<${crumb}
|
|
81
106
|
part="crumb${index === last ? ' crumb-current' : ''}"
|
|
82
|
-
exportparts="link: crumb-link, separator: crumb-separator, separator-mark: crumb-separator-mark"
|
|
107
|
+
exportparts="expand: crumb-expand, fold-separator: crumb-fold-separator, link: crumb-link, separator: crumb-separator, separator-mark: crumb-separator-mark"
|
|
83
108
|
href=${index === last ? nothing : (item.href ?? nothing)}
|
|
84
109
|
rel=${item.rel ?? nothing}
|
|
85
110
|
target=${item.target ?? nothing}
|
|
@@ -90,7 +115,7 @@ let HelixHeadlessBreadcrumbs = class HelixHeadlessBreadcrumbs extends HelixEleme
|
|
|
90
115
|
}
|
|
91
116
|
render() {
|
|
92
117
|
return html `<ol part="list" role="list">
|
|
93
|
-
${this.renderItems()}<slot @slotchange=${this.
|
|
118
|
+
${this.renderItems()}<slot @slotchange=${this.handleSlotChange}></slot>
|
|
94
119
|
</ol>`;
|
|
95
120
|
}
|
|
96
121
|
};
|
|
@@ -65,6 +65,8 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
65
65
|
* @event {HelixBeforeChangeEvent} helix-before-change - Fired before a press changes `value`, with the value it would leave in `detail.value`. Cancelable: call `preventDefault()` to keep the buttons as they are.
|
|
66
66
|
*
|
|
67
67
|
* @cssproperty --button-group-pressed - Written on the control inside each toggle button of a group with `selection-mode`: `1` while it is pressed and `0` while it is not. Read it in a `::part(button)` rule to draw the pressed look.
|
|
68
|
+
*
|
|
69
|
+
* @cssstate pressed - While a group with `selection-mode` has a button pressed.
|
|
68
70
|
*/
|
|
69
71
|
export declare class HelixHeadlessButtonGroup extends HelixElement {
|
|
70
72
|
/**
|
|
@@ -145,6 +145,7 @@ let HelixHeadlessButtonGroup = class HelixHeadlessButtonGroup extends HelixEleme
|
|
|
145
145
|
const toolbar = this.roving;
|
|
146
146
|
hostAria(this.internals, { label: this.label, role: toolbar ? 'toolbar' : 'group' });
|
|
147
147
|
this.internals.ariaOrientation = toolbar ? (this.orientation ?? null) : null;
|
|
148
|
+
this.toggleState('pressed', Boolean(this.selectionMode) && commaList(this.value).length > 0);
|
|
148
149
|
}
|
|
149
150
|
firstUpdated(changed) {
|
|
150
151
|
super.firstUpdated(changed);
|
|
@@ -13,13 +13,17 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
13
13
|
* through with `::part`.
|
|
14
14
|
*
|
|
15
15
|
* Name the card with `label`, as a Stat Group is named, or with `aria-label`
|
|
16
|
-
* or `aria-labelledby`.
|
|
17
|
-
*
|
|
18
|
-
* server has none. `label` is set through `ElementInternals`, so it writes no
|
|
19
|
-
* attribute onto the card. **A named card is a group**, so a
|
|
16
|
+
* or `aria-labelledby`. `label` is set through `ElementInternals`, so it
|
|
17
|
+
* writes no attribute onto the card. **A named card is a group**, so a
|
|
20
18
|
* reader entering it hears its name; an unnamed one has no role, since a group
|
|
21
19
|
* with no name only adds "group" to what a reader says.
|
|
22
20
|
*
|
|
21
|
+
* **With none of those, a heading in `header` names the card**, such as the
|
|
22
|
+
* `<h2>` of its title, by reference, so the name follows the heading's words.
|
|
23
|
+
* Reading a heading needs a DOM, so a served card is named by it once the
|
|
24
|
+
* script runs; `label`, `aria-label` and `aria-labelledby` are the names a
|
|
25
|
+
* server writes.
|
|
26
|
+
*
|
|
23
27
|
* Each region's slot is a part of the same name, `body` for the main content,
|
|
24
28
|
* as Web Awesome's card names its regions. A slot draws no box, so a part
|
|
25
29
|
* passes inherited properties to what is slotted, and a page wanting a box
|
|
@@ -29,7 +33,9 @@ import { type PropertyValues, type TemplateResult } from 'lit';
|
|
|
29
33
|
* header's and the footer's line, as Web Awesome's card does. While one holds
|
|
30
34
|
* something, its line is a `header-row` or `footer-row` part; otherwise there
|
|
31
35
|
* is no wrapper and every region is a bare slot. A server cannot see slotted
|
|
32
|
-
* content, so served
|
|
36
|
+
* content, so a served card has no row; the styled tier sets served actions at
|
|
37
|
+
* the end of their line from the stylesheet alone, so they do not move when
|
|
38
|
+
* the script runs.
|
|
33
39
|
*
|
|
34
40
|
* **The card says which regions it holds**, as `:state(has-media)`,
|
|
35
41
|
* `:state(has-header)` and `:state(has-footer)`, so a sheet can draw a card
|
|
@@ -87,20 +93,34 @@ export declare class HelixHeadlessCard extends HelixElement {
|
|
|
87
93
|
*/
|
|
88
94
|
mediaPlacement?: 'end';
|
|
89
95
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
96
|
+
* Hears a name written on the card, which no property mirrors.
|
|
97
|
+
*
|
|
98
|
+
* Lit observes the attributes of its own properties only, and `ariaLabel`
|
|
99
|
+
* and `ariaLabelledByElements` belong to the platform. Observed here rather
|
|
100
|
+
* than through a property, so the manifest lists no attribute of the card's
|
|
101
|
+
* own for them.
|
|
92
102
|
*/
|
|
93
|
-
|
|
103
|
+
static get observedAttributes(): string[];
|
|
94
104
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
105
|
+
* Called by the browser when an observed attribute changes. A name written
|
|
106
|
+
* or taken off asks for an update, because it decides whether the card is a
|
|
107
|
+
* group and whether its heading names it.
|
|
97
108
|
*/
|
|
98
|
-
|
|
109
|
+
attributeChangedCallback(name: string, old: string | null, value: string | null): void;
|
|
99
110
|
/** Whether `footer-actions` holds something, read from the slot. */
|
|
100
111
|
private footerActions;
|
|
101
112
|
/** Whether `header-actions` holds something, read from the slot. */
|
|
102
113
|
private headerActions;
|
|
103
114
|
protected willUpdate(changed: PropertyValues): void;
|
|
115
|
+
/**
|
|
116
|
+
* Says what the card is and what names it. On the host, since the host is
|
|
117
|
+
* the box, and from `willUpdate`, which the server calls too.
|
|
118
|
+
*
|
|
119
|
+
* **A heading in `header` is the name only where nothing else is**, and it
|
|
120
|
+
* names the card by reference, so a renamed heading renames the card. An
|
|
121
|
+
* engine without element references hears its words.
|
|
122
|
+
*/
|
|
123
|
+
private writeName;
|
|
104
124
|
protected updated(changed: PropertyValues): void;
|
|
105
125
|
protected firstUpdated(changed: PropertyValues): void;
|
|
106
126
|
private readonly readRegions;
|