@fusion.dev/helix-headless 0.23.0 → 0.24.1
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 +50579 -26954
- package/dist/announcement/root/announcement-root.d.ts +19 -5
- package/dist/area-chart/index.d.ts +1 -0
- package/dist/area-chart/index.js +1 -0
- package/dist/area-chart/root/area-chart-root.d.ts +182 -0
- package/dist/area-chart/root/area-chart-root.js +205 -0
- package/dist/avatar/group/avatar-group.d.ts +1 -0
- package/dist/avatar/group/avatar-group.js +5 -1
- package/dist/avatar/root/avatar-root.d.ts +1 -0
- package/dist/avatar/root/avatar-root.js +11 -0
- package/dist/carousel/root/carousel-root.d.ts +3 -0
- package/dist/carousel/root/carousel-root.js +23 -4
- package/dist/chat/index.d.ts +1 -1
- package/dist/chat/message/chat-message.d.ts +7 -0
- package/dist/chat/message/chat-message.js +2 -1
- package/dist/chat/root/chat-root.d.ts +195 -4
- package/dist/chat/root/chat-root.js +316 -23
- package/dist/checkbox-group/root/checkbox-group-root.d.ts +2 -0
- package/dist/checkbox-group/root/checkbox-group-root.js +12 -2
- package/dist/code-block/index.d.ts +1 -0
- package/dist/code-block/index.js +1 -0
- package/dist/code-block/patch.d.ts +62 -0
- package/dist/code-block/patch.js +93 -0
- package/dist/code-block/root/code-block-root.d.ts +82 -17
- package/dist/code-block/root/code-block-root.js +83 -6
- package/dist/color-picker/root/color-picker-root.d.ts +4 -3
- package/dist/color-picker/root/color-picker-root.js +4 -1
- package/dist/combobox/root/combobox-root.d.ts +1 -0
- package/dist/combobox/root/combobox-root.js +6 -1
- package/dist/command/root/command-root.d.ts +7 -0
- package/dist/command/root/command-root.js +10 -0
- package/dist/data-table/root/data-table-root.d.ts +195 -23
- package/dist/data-table/root/data-table-root.js +308 -27
- package/dist/description-list/index.d.ts +2 -0
- package/dist/description-list/index.js +2 -0
- package/dist/description-list/item/description-list-item.d.ts +39 -0
- package/dist/description-list/item/description-list-item.js +26 -0
- package/dist/description-list/root/description-list-root.d.ts +56 -0
- package/dist/description-list/root/description-list-root.js +21 -0
- package/dist/element-events.generated.js +27 -6
- package/dist/filter-input/chip/filter-chip.d.ts +114 -0
- package/dist/filter-input/chip/filter-chip.js +61 -0
- package/dist/filter-input/field/filter-field.d.ts +128 -0
- package/dist/filter-input/field/filter-field.js +70 -0
- package/dist/filter-input/grammar.d.ts +96 -0
- package/dist/filter-input/grammar.js +133 -0
- package/dist/filter-input/index.d.ts +5 -0
- package/dist/filter-input/index.js +5 -0
- package/dist/filter-input/menu/filter-menu.d.ts +153 -0
- package/dist/filter-input/menu/filter-menu.js +164 -0
- package/dist/filter-input/operator/filter-operator.d.ts +105 -0
- package/dist/filter-input/operator/filter-operator.js +39 -0
- package/dist/filter-input/root/filter-input-root.d.ts +118 -9
- package/dist/filter-input/root/filter-input-root.js +96 -2
- package/dist/format-date/root/format-date-root.js +5 -2
- package/dist/image/root/image-root.d.ts +9 -0
- package/dist/image/root/image-root.js +16 -3
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/kbd/root/kbd-root.d.ts +9 -2
- package/dist/kbd/root/kbd-root.js +7 -2
- package/dist/line-chart/root/line-chart-root.d.ts +9 -2
- package/dist/listbox/root/listbox-root.d.ts +8 -7
- package/dist/listbox/root/listbox-root.js +7 -2
- package/dist/menu/item/menu-item.d.ts +31 -6
- package/dist/menu/item/menu-item.js +18 -5
- package/dist/menu/root/menu-root.js +7 -2
- package/dist/notifications/group/notification-group.d.ts +129 -0
- package/dist/notifications/group/notification-group.js +106 -0
- package/dist/notifications/index.d.ts +1 -0
- package/dist/notifications/index.js +1 -0
- package/dist/notifications/notification/notification.d.ts +39 -13
- package/dist/notifications/notification/notification.js +11 -8
- package/dist/notifications/root/notifications-root.d.ts +57 -1
- package/dist/notifications/root/notifications-root.js +35 -4
- package/dist/number-input/root/number-input-root.d.ts +21 -1
- package/dist/number-input/root/number-input-root.js +18 -5
- package/dist/object-inspector/index.d.ts +1 -0
- package/dist/object-inspector/index.js +1 -0
- package/dist/object-inspector/root/object-inspector-root.d.ts +183 -0
- package/dist/object-inspector/root/object-inspector-root.js +348 -0
- package/dist/option/root/option-root.d.ts +17 -0
- package/dist/option/root/option-root.js +4 -0
- package/dist/outline/root/outline-root.d.ts +16 -1
- package/dist/outline/root/outline-root.js +27 -9
- package/dist/qr-code/root/qr-code-root.d.ts +3 -1
- package/dist/qr-code/root/qr-code-root.js +11 -1
- package/dist/radio/group/radio-group.d.ts +1 -0
- package/dist/rating/root/rating-root.d.ts +3 -0
- package/dist/rating/root/rating-root.js +9 -0
- package/dist/scatter-chart/index.d.ts +1 -0
- package/dist/scatter-chart/index.js +1 -0
- package/dist/scatter-chart/root/scatter-chart-root.d.ts +144 -0
- package/dist/scatter-chart/root/scatter-chart-root.js +205 -0
- package/dist/select/root/select-root.d.ts +8 -0
- package/dist/select/root/select-root.js +8 -6
- package/dist/skip-link/index.d.ts +1 -0
- package/dist/skip-link/index.js +1 -0
- package/dist/skip-link/root/skip-link-root.d.ts +81 -0
- package/dist/skip-link/root/skip-link-root.js +58 -0
- package/dist/speed-dial/root/speed-dial-root.d.ts +1 -0
- package/dist/speed-dial/root/speed-dial-root.js +6 -2
- package/dist/steps/root/steps-root.d.ts +1 -0
- package/dist/steps/root/steps-root.js +5 -2
- package/dist/table/column/table-column.d.ts +79 -0
- package/dist/table/column/table-column.js +125 -5
- package/dist/table/root/table-root.d.ts +2 -0
- package/dist/table/root/table-root.js +12 -3
- package/dist/time-picker/root/time-picker-root.d.ts +9 -0
- package/dist/time-picker/root/time-picker-root.js +5 -0
- package/dist/toast/root/toast-root.d.ts +2 -0
- package/dist/tooltip/root/tooltip-root.js +1 -1
- package/dist/tour/root/tour-root.d.ts +14 -0
- package/dist/tour/root/tour-root.js +43 -18
- package/dist/tree/item/tree-item.d.ts +7 -4
- package/dist/tree/item/tree-item.js +4 -1
- package/dist/video/root/video-root.d.ts +23 -1
- package/dist/video/root/video-root.js +46 -23
- package/dist/virtual-list/root/virtual-list-root.d.ts +10 -6
- package/dist/virtual-list/root/virtual-list-root.js +10 -4
- package/package.json +5 -5
- package/types/jsx.d.ts +4536 -346
- package/types/svelte.d.ts +11 -0
- package/types/vue.d.ts +738 -39
|
@@ -12,8 +12,9 @@ import { nothing, type PropertyValues, type TemplateResult } from 'lit';
|
|
|
12
12
|
*
|
|
13
13
|
* **The role follows the shape.** A row that can be ticked is
|
|
14
14
|
* `menuitemcheckbox` and carries `aria-checked`, because a reader is told "one
|
|
15
|
-
* of these is on" rather than "here is a button"
|
|
16
|
-
* `
|
|
15
|
+
* of these is on" rather than "here is a button"; `exclusive` makes it
|
|
16
|
+
* `menuitemradio`, for a set where only one can be. A row that opens a page
|
|
17
|
+
* keeps `menuitem` and says `aria-haspopup`, since the page it opens is a menu.
|
|
17
18
|
*
|
|
18
19
|
* Whether it is on the page showing is set by the menu as a property, never as
|
|
19
20
|
* an attribute: an attribute written onto a consumer's element after it
|
|
@@ -64,6 +65,19 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
64
65
|
* about who may name a file on somebody's disk rather than a gap here.
|
|
65
66
|
*/
|
|
66
67
|
download: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether only one row of the set it belongs to can be on.
|
|
70
|
+
*
|
|
71
|
+
* Announcement and nothing else, the way `checkable` is: this element applies
|
|
72
|
+
* no choice and polices no group, so what it does is tell a reader which kind
|
|
73
|
+
* of set they are in. `menuitemcheckbox` says every row here is independent,
|
|
74
|
+
* which for a run of comparisons, or the values of a filter that compares
|
|
75
|
+
* against one, is the opposite of true.
|
|
76
|
+
*
|
|
77
|
+
* Meaningless without `checkable`, since a row that cannot be ticked is not
|
|
78
|
+
* one of a set of anything.
|
|
79
|
+
*/
|
|
80
|
+
exclusive: boolean;
|
|
67
81
|
/**
|
|
68
82
|
* Where the row goes, which makes it a real link.
|
|
69
83
|
*
|
|
@@ -98,13 +112,14 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
98
112
|
*/
|
|
99
113
|
rel: string;
|
|
100
114
|
/**
|
|
101
|
-
* Whether this row's submenu
|
|
115
|
+
* Whether the page showing is this row's submenu, or one below it.
|
|
102
116
|
*
|
|
103
117
|
* Separate from `onPage`, and it has to be. A row that opened a page is not
|
|
104
118
|
* itself on that page, so both are false for it, and rendering nothing on
|
|
105
119
|
* that basis takes its `slot[name="submenu"]` with it: the rows inside have
|
|
106
|
-
* nowhere to be assigned and are never laid out.
|
|
107
|
-
* page has to keep
|
|
120
|
+
* nowhere to be assigned and are never laid out. Every row on the way down to
|
|
121
|
+
* the open page has to keep its slot, not only the one directly above it.
|
|
122
|
+
* Three levels deep, the topmost row holds the slot the middle one lives in.
|
|
108
123
|
*/
|
|
109
124
|
showingSubmenu: boolean;
|
|
110
125
|
/**
|
|
@@ -131,7 +146,7 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
131
146
|
*/
|
|
132
147
|
get hasSubmenu(): boolean;
|
|
133
148
|
/**
|
|
134
|
-
* Whether anything is slotted into `submenu`.
|
|
149
|
+
* Whether anything is slotted into `submenu`.
|
|
135
150
|
*
|
|
136
151
|
* State rather than a getter over `children`. A getter is read during
|
|
137
152
|
* `render()` with nothing to re-read it, so a submenu arriving after the
|
|
@@ -158,6 +173,16 @@ export declare class HelixHeadlessMenuItem extends HelixElement {
|
|
|
158
173
|
* exactly the thing that is.
|
|
159
174
|
*/
|
|
160
175
|
private start;
|
|
176
|
+
/**
|
|
177
|
+
* Both answers, from the children, before each render.
|
|
178
|
+
*
|
|
179
|
+
* The slots exist only once a render has made them: read through them, the
|
|
180
|
+
* first render drew no caret and no leading box and a second render had to
|
|
181
|
+
* follow it. `slotchange` still reads them, for a child that arrives later.
|
|
182
|
+
* Not on the server, where `children` is undefined, nor while hydrating what
|
|
183
|
+
* one sent.
|
|
184
|
+
*/
|
|
185
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
161
186
|
protected firstUpdated(changed: PropertyValues): void;
|
|
162
187
|
/**
|
|
163
188
|
* Re-reads whether anything is before the label.
|
|
@@ -16,6 +16,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
16
16
|
this.checked = false;
|
|
17
17
|
this.disabled = false;
|
|
18
18
|
this.download = false;
|
|
19
|
+
this.exclusive = false;
|
|
19
20
|
this.href = '';
|
|
20
21
|
this.onPage = true;
|
|
21
22
|
this.rel = '';
|
|
@@ -55,10 +56,18 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
55
56
|
get hasSubmenu() {
|
|
56
57
|
return this.submenu;
|
|
57
58
|
}
|
|
59
|
+
willUpdate(changed) {
|
|
60
|
+
super.willUpdate(changed);
|
|
61
|
+
if (this.children === undefined || this.hydrating) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const named = (name) => [...this.children].some((child) => child.slot === name);
|
|
65
|
+
this.submenu = named('submenu');
|
|
66
|
+
this.start = named('start');
|
|
67
|
+
}
|
|
58
68
|
firstUpdated(changed) {
|
|
59
69
|
super.firstUpdated(changed);
|
|
60
|
-
this.
|
|
61
|
-
this.readStart();
|
|
70
|
+
this.finishHydration();
|
|
62
71
|
}
|
|
63
72
|
focus() {
|
|
64
73
|
this.renderRoot.querySelector('[part="item"]')?.focus();
|
|
@@ -87,8 +96,9 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
87
96
|
}
|
|
88
97
|
const submenu = this.hasSubmenu;
|
|
89
98
|
const linked = this.href !== '' && !this.disabled;
|
|
90
|
-
const
|
|
91
|
-
const
|
|
99
|
+
const ticks = this.checkable || this.checked;
|
|
100
|
+
const role = ticks ? (this.exclusive ? 'menuitemradio' : 'menuitemcheckbox') : 'menuitem';
|
|
101
|
+
const checkedState = ticks ? (this.checked ? 'true' : 'false') : nothing;
|
|
92
102
|
const inside = html `<span part="start" ?data-empty=${!this.start}
|
|
93
103
|
><slot name="start" @slotchange=${this.readStart}></slot
|
|
94
104
|
></span>
|
|
@@ -138,7 +148,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
138
148
|
@click=${this.handleClick}
|
|
139
149
|
@keydown=${this.handleKeydown}
|
|
140
150
|
>${inside}</div>`}
|
|
141
|
-
<slot name="submenu" @slotchange=${this.readSubmenu}></slot>`;
|
|
151
|
+
<div hidden><slot name="submenu" @slotchange=${this.readSubmenu}></slot></div>`;
|
|
142
152
|
}
|
|
143
153
|
};
|
|
144
154
|
__decorate([
|
|
@@ -153,6 +163,9 @@ __decorate([
|
|
|
153
163
|
__decorate([
|
|
154
164
|
property({ type: Boolean })
|
|
155
165
|
], HelixHeadlessMenuItem.prototype, "download", void 0);
|
|
166
|
+
__decorate([
|
|
167
|
+
property({ reflect: true, type: Boolean })
|
|
168
|
+
], HelixHeadlessMenuItem.prototype, "exclusive", void 0);
|
|
156
169
|
__decorate([
|
|
157
170
|
property()
|
|
158
171
|
], HelixHeadlessMenuItem.prototype, "href", void 0);
|
|
@@ -45,7 +45,11 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
45
45
|
this.enter(chosen);
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
-
|
|
48
|
+
queueMicrotask(() => {
|
|
49
|
+
if (!event.defaultPrevented) {
|
|
50
|
+
this.hide();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
49
53
|
};
|
|
50
54
|
this.handleFocusIn = (event) => {
|
|
51
55
|
const item = this.items.find((row) => row.contains(event.target));
|
|
@@ -167,8 +171,9 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
167
171
|
this.toggleState('mark', rows.some((row) => row.checkable || row.checked || this.drilldown.hasPage(row)));
|
|
168
172
|
}
|
|
169
173
|
mark(item, onPage) {
|
|
174
|
+
const page = this.drilldown.page;
|
|
170
175
|
item.onPage = onPage;
|
|
171
|
-
item.showingSubmenu =
|
|
176
|
+
item.showingSubmenu = page !== null && item.contains(page);
|
|
172
177
|
for (const child of item.children) {
|
|
173
178
|
const nested = child;
|
|
174
179
|
if (nested.isMenuItem === true && child.slot === 'submenu') {
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
import type { HelixHeadlessNotification } from '../notification/notification.js';
|
|
4
|
+
declare const HelixHeadlessNotificationGroup_base: (new (...args: any[]) => import("@fusion.dev/helix-core").MixinControllerOpenInterface) & typeof HelixElement;
|
|
5
|
+
/**
|
|
6
|
+
* @summary A named set of notifications, which can be collapsed to the first of
|
|
7
|
+
* them.
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* **What groups with what is the consumer's, and this never guesses.** A feed
|
|
11
|
+
* can be cut by day, by the thing being talked about, by the person doing the
|
|
12
|
+
* talking, or by nothing at all, and which of those is right is a question about
|
|
13
|
+
* the product. So a group is written by hand around the rows that belong
|
|
14
|
+
* together and told what to call itself, exactly as `FilterField` is told what
|
|
15
|
+
* its operators are.
|
|
16
|
+
*
|
|
17
|
+
* **Collapsed shows the first row and assigns the rest nowhere.** Not `hidden`,
|
|
18
|
+
* not `inert`, not a stylesheet: the slot is assigned imperatively, so a
|
|
19
|
+
* collapsed group's later rows are absent from the layout and from the
|
|
20
|
+
* accessibility tree with no CSS at all, which is what the headless tier claims
|
|
21
|
+
* for itself. `Notifications` already assigns its list this way and this borrows
|
|
22
|
+
* the mechanism.
|
|
23
|
+
*
|
|
24
|
+
* The count is separate from how many rows are written, because it usually is: a
|
|
25
|
+
* group saying five with one row under it and a link to the rest is the common
|
|
26
|
+
* shape, and computing the number from the children would make that unsayable.
|
|
27
|
+
* Left off, it is the number of rows.
|
|
28
|
+
*
|
|
29
|
+
* @since 0.24.0
|
|
30
|
+
* @status experimental
|
|
31
|
+
* @category feedback
|
|
32
|
+
* @aliases NotificationSection, FeedGroup
|
|
33
|
+
* @documentation https://helix.fusion.dev/components/notifications
|
|
34
|
+
*
|
|
35
|
+
* @slot - The notifications in this group.
|
|
36
|
+
* @slot action - Beside the count, for a link to the rest of them.
|
|
37
|
+
* @slot label - Replaces the group's name, for a heading that needs a mark in it.
|
|
38
|
+
*
|
|
39
|
+
* @event {HelixBeforeCloseEvent} helix-before-close - Fired before the group folds, and cancelable.
|
|
40
|
+
* @event {HelixBeforeOpenEvent} helix-before-open - Fired before the group unfolds. Cancelable: refusing it takes the decision and nothing moves.
|
|
41
|
+
*
|
|
42
|
+
* @csspart action - The trailing region of the header.
|
|
43
|
+
* @csspart count - How many are in the group.
|
|
44
|
+
* @csspart header - The label, the count and the action together.
|
|
45
|
+
* @csspart items - The region holding the notifications.
|
|
46
|
+
* @csspart label - What the group is called.
|
|
47
|
+
* @csspart trigger - The control that collapses and expands, where collapsible.
|
|
48
|
+
*/
|
|
49
|
+
export declare class HelixHeadlessNotificationGroup extends HelixHeadlessNotificationGroup_base {
|
|
50
|
+
/**
|
|
51
|
+
* Manual, which is what lets a fold remove rows rather than hide them.
|
|
52
|
+
*
|
|
53
|
+
* Under automatic assignment every unnamed child lands in the default slot and
|
|
54
|
+
* the only ways to fold are `hidden`, `inert` or a stylesheet: the first two
|
|
55
|
+
* leave the rows in the layout or in the tree, and the third puts the state
|
|
56
|
+
* somewhere a headless consumer has to re-implement. Assigning by hand, a
|
|
57
|
+
* folded group's later rows are assigned nowhere and render nothing at all.
|
|
58
|
+
*
|
|
59
|
+
* `Notifications` takes the same option for a neighboring reason, and its
|
|
60
|
+
* note records the other half of it: the rows are ordinary children a consumer
|
|
61
|
+
* writes with no `slot`, and wrapping them any other way means editing their
|
|
62
|
+
* markup.
|
|
63
|
+
*/
|
|
64
|
+
static shadowRootOptions: ShadowRootInit;
|
|
65
|
+
/**
|
|
66
|
+
* Whether the group can be collapsed to its first row.
|
|
67
|
+
*
|
|
68
|
+
* Off by default, because a group is a heading before it is a control: a feed
|
|
69
|
+
* cut by day wants its days named and rarely wants them folded, and a control
|
|
70
|
+
* that does nothing anybody asked for is still a tab stop.
|
|
71
|
+
*/
|
|
72
|
+
collapsible: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* How many notifications this group stands for.
|
|
75
|
+
*
|
|
76
|
+
* **Not the number of rows, and that is the point.** A group saying five with
|
|
77
|
+
* one row under it and a link to the rest is the ordinary shape of a feed that
|
|
78
|
+
* has been trimmed, and deriving this from the children would make it
|
|
79
|
+
* unsayable. Left off, it is the number of rows.
|
|
80
|
+
*/
|
|
81
|
+
count?: number;
|
|
82
|
+
/**
|
|
83
|
+
* What the group is called: a day, a subject, a person.
|
|
84
|
+
*
|
|
85
|
+
* The slot's fallback rather than its replacement, so a consumer who wants a
|
|
86
|
+
* mark beside the name slots `label` and this goes unused.
|
|
87
|
+
*/
|
|
88
|
+
label: string;
|
|
89
|
+
/**
|
|
90
|
+
* What this is, for the feed above it to recognize without importing it.
|
|
91
|
+
*
|
|
92
|
+
* The same seam `Notification` uses: a root reads a child's markers rather
|
|
93
|
+
* than its constructor, so the two tiers never have to agree about which class
|
|
94
|
+
* a consumer happened to instantiate.
|
|
95
|
+
*/
|
|
96
|
+
readonly isNotificationGroup = true;
|
|
97
|
+
/** The notifications written into this group, in the order they were given. */
|
|
98
|
+
get items(): HelixHeadlessNotification[];
|
|
99
|
+
/** How many the group says it stands for, which is the count or the rows. */
|
|
100
|
+
get total(): number;
|
|
101
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
102
|
+
protected firstUpdated(changed: PropertyValues): void;
|
|
103
|
+
protected updated(changed: PropertyValues): void;
|
|
104
|
+
/**
|
|
105
|
+
* Puts the rows the group is showing into the slot, and the rest nowhere.
|
|
106
|
+
*
|
|
107
|
+
* **Assignment rather than hiding, which is what makes this work with no
|
|
108
|
+
* stylesheet.** A row that is not assigned to a slot renders nothing at all:
|
|
109
|
+
* it is out of the layout, out of the tab order and out of the accessibility
|
|
110
|
+
* tree, in both tiers, without a rule anywhere. `hidden` would do the first
|
|
111
|
+
* three and `inert` only the last two, and either would put the collapse in a
|
|
112
|
+
* place a headless consumer has to re-implement.
|
|
113
|
+
*
|
|
114
|
+
* Collapsed keeps the first row rather than none, because a group folded to
|
|
115
|
+
* nothing is a heading with a number after it and no reason to believe there
|
|
116
|
+
* is anything behind it. The one row is the evidence.
|
|
117
|
+
*/
|
|
118
|
+
private assign;
|
|
119
|
+
private readonly handleToggle;
|
|
120
|
+
/** How many rows the collapse is hiding, for the control to say so. */
|
|
121
|
+
private get folded();
|
|
122
|
+
protected render(): TemplateResult;
|
|
123
|
+
}
|
|
124
|
+
declare global {
|
|
125
|
+
interface HTMLElementTagNameMap {
|
|
126
|
+
'helix-headless-notification-group': HelixHeadlessNotificationGroup;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { customElement, HelixElement, hostAria, MixinControllerOpen } from '@fusion.dev/helix-core';
|
|
8
|
+
import { html, nothing } from 'lit';
|
|
9
|
+
import { property } from 'lit/decorators.js';
|
|
10
|
+
const TRIGGER_ID = 'trigger';
|
|
11
|
+
const ITEMS_ID = 'items';
|
|
12
|
+
let HelixHeadlessNotificationGroup = class HelixHeadlessNotificationGroup extends MixinControllerOpen(HelixElement) {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
this.collapsible = false;
|
|
16
|
+
this.label = '';
|
|
17
|
+
this.isNotificationGroup = true;
|
|
18
|
+
this.handleToggle = () => {
|
|
19
|
+
this.toggleOpen();
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
static { this.shadowRootOptions = {
|
|
23
|
+
...HelixElement.shadowRootOptions,
|
|
24
|
+
slotAssignment: 'manual',
|
|
25
|
+
}; }
|
|
26
|
+
get items() {
|
|
27
|
+
return this.knownChildren.filter((child) => child.isNotification === true);
|
|
28
|
+
}
|
|
29
|
+
get total() {
|
|
30
|
+
return this.count ?? this.items.length;
|
|
31
|
+
}
|
|
32
|
+
willUpdate(changed) {
|
|
33
|
+
super.willUpdate(changed);
|
|
34
|
+
hostAria(this.internals, this.label === '' ? { role: 'group' } : { label: this.label, role: 'group' });
|
|
35
|
+
}
|
|
36
|
+
firstUpdated(changed) {
|
|
37
|
+
super.firstUpdated(changed);
|
|
38
|
+
this.finishHydration();
|
|
39
|
+
}
|
|
40
|
+
updated(changed) {
|
|
41
|
+
super.updated(changed);
|
|
42
|
+
this.assign();
|
|
43
|
+
}
|
|
44
|
+
assign() {
|
|
45
|
+
const slot = this.renderRoot?.querySelector?.('slot:not([name])');
|
|
46
|
+
if (!slot?.assign) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const items = this.items;
|
|
50
|
+
const showing = this.collapsible && !this.open ? items.slice(0, 1) : items;
|
|
51
|
+
if (this.assignsByName) {
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
this.route(item, showing.includes(item) ? undefined : 'folded');
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
slot.assign(...showing);
|
|
58
|
+
const action = this.renderRoot?.querySelector?.('slot[name="action"]');
|
|
59
|
+
action?.assign?.(...[...this.children].filter((child) => child.getAttribute('slot') === 'action'));
|
|
60
|
+
const label = this.renderRoot?.querySelector?.('slot[name="label"]');
|
|
61
|
+
label?.assign?.(...[...this.children].filter((child) => child.getAttribute('slot') === 'label'));
|
|
62
|
+
}
|
|
63
|
+
get folded() {
|
|
64
|
+
return Math.max(this.items.length - 1, 0);
|
|
65
|
+
}
|
|
66
|
+
render() {
|
|
67
|
+
const collapsed = this.collapsible && !this.open;
|
|
68
|
+
return html `<div part="header">
|
|
69
|
+
${this.collapsible
|
|
70
|
+
? html `<button
|
|
71
|
+
id=${TRIGGER_ID}
|
|
72
|
+
aria-controls=${ITEMS_ID}
|
|
73
|
+
aria-expanded=${this.open ? 'true' : 'false'}
|
|
74
|
+
part="trigger"
|
|
75
|
+
type="button"
|
|
76
|
+
@click=${this.handleToggle}
|
|
77
|
+
>
|
|
78
|
+
<span part="label"><slot name="label">${this.label}</slot></span>
|
|
79
|
+
<span part="count">${this.total}</span>
|
|
80
|
+
</button>`
|
|
81
|
+
: html `<span part="label"><slot name="label">${this.label}</slot></span>
|
|
82
|
+
<span part="count">${this.total}</span>`}
|
|
83
|
+
<span part="action"><slot name="action"></slot></span>
|
|
84
|
+
</div>
|
|
85
|
+
<div
|
|
86
|
+
id=${ITEMS_ID}
|
|
87
|
+
part="items"
|
|
88
|
+
aria-label=${collapsed && this.folded > 0 ? `${this.folded} more` : nothing}
|
|
89
|
+
>
|
|
90
|
+
<slot></slot>
|
|
91
|
+
</div>`;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
__decorate([
|
|
95
|
+
property({ reflect: true, type: Boolean })
|
|
96
|
+
], HelixHeadlessNotificationGroup.prototype, "collapsible", void 0);
|
|
97
|
+
__decorate([
|
|
98
|
+
property({ type: Number })
|
|
99
|
+
], HelixHeadlessNotificationGroup.prototype, "count", void 0);
|
|
100
|
+
__decorate([
|
|
101
|
+
property()
|
|
102
|
+
], HelixHeadlessNotificationGroup.prototype, "label", void 0);
|
|
103
|
+
HelixHeadlessNotificationGroup = __decorate([
|
|
104
|
+
customElement('helix-headless-notification-group')
|
|
105
|
+
], HelixHeadlessNotificationGroup);
|
|
106
|
+
export { HelixHeadlessNotificationGroup };
|
|
@@ -36,10 +36,14 @@ import '../../format-date/index.js';
|
|
|
36
36
|
* @documentation https://helix.fusion.dev/components/notifications
|
|
37
37
|
*
|
|
38
38
|
* @slot - What happened, in the consumer's own markup.
|
|
39
|
-
* @slot actions - What a reader can do about it.
|
|
39
|
+
* @slot actions - What a reader can do about it, under the words it is about. Drawn always
|
|
40
|
+
* rather than on hover, because an action is the point of the row and not chrome around it.
|
|
40
41
|
* @slot caption - A second line qualifying it, quieter than the first. A bare
|
|
41
42
|
* slot rather than a box, so an item without one pays nothing for it; style it
|
|
42
43
|
* through `::slotted()`.
|
|
44
|
+
* @slot detail - A block belonging to the row rather than a line about it: a quoted
|
|
45
|
+
* comment, a status moving from one value to another, a preview. Unlike `caption` nothing
|
|
46
|
+
* here sets a size or an ink, because whatever fills it usually brings its own.
|
|
43
47
|
* @slot media - An avatar or icon identifying who or what it came from.
|
|
44
48
|
*
|
|
45
49
|
* @event {HelixDismissEvent} helix-dismiss - Fired when a reader asks to remove it, and cancelable.
|
|
@@ -121,14 +125,30 @@ export declare class HelixHeadlessNotification extends HelixElement {
|
|
|
121
125
|
/**
|
|
122
126
|
* Says whether there is anything for the time to make way for.
|
|
123
127
|
*
|
|
124
|
-
* The trailing column holds the time and the
|
|
128
|
+
* The trailing column holds the time and the dismiss in one place and shows
|
|
125
129
|
* one of them, so a row with nothing to do about it would hide its timestamp
|
|
126
|
-
* on hover and put nothing there.
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
+
* on hover and put nothing there.
|
|
131
|
+
*
|
|
132
|
+
* **`dismissible` alone, where this once also counted a filled actions slot.**
|
|
133
|
+
* An action is not chrome. A row offering "Respond" is a row whose whole point
|
|
134
|
+
* is that button, and one nobody sees until a pointer arrives is one most
|
|
135
|
+
* readers never find: on a coarse pointer it was drawn and on a desktop it was
|
|
136
|
+
* not, so the two disagreed about whether the row had a button at all. Actions
|
|
137
|
+
* now sit in the body under the words they are about, and the swap is left to
|
|
138
|
+
* the one control that really is chrome.
|
|
139
|
+
*
|
|
140
|
+
* This element knows `dismissible` on the server, so unlike the answer it
|
|
141
|
+
* replaces this one needs no DOM and is right from the first render.
|
|
130
142
|
*/
|
|
131
143
|
private markControls;
|
|
144
|
+
/**
|
|
145
|
+
* Says whether a consumer put anything in the actions slot.
|
|
146
|
+
*
|
|
147
|
+
* Only so the stylesheet can space them from the words above: a row with no
|
|
148
|
+
* actions pays nothing for the room they would have taken. Answered from a
|
|
149
|
+
* DOM, so the resting answer is the one the server writes.
|
|
150
|
+
*/
|
|
151
|
+
private markActions;
|
|
132
152
|
/**
|
|
133
153
|
* The control that removes the item, with the mark a consumer can replace.
|
|
134
154
|
*
|
|
@@ -148,13 +168,19 @@ export declare class HelixHeadlessNotification extends HelixElement {
|
|
|
148
168
|
* on its own viewBox, so it lands where the box says and takes the icon step
|
|
149
169
|
* the row is already set in.
|
|
150
170
|
*
|
|
151
|
-
* **A
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
171
|
+
* **A cross, and not the trash this used to draw.** The argument for the trash
|
|
172
|
+
* was that a notification is the only copy of something that happened, so
|
|
173
|
+
* removing it is destruction and should be marked as such. That reads the row
|
|
174
|
+
* as the event. It is not: the deploy still deployed and the build still
|
|
175
|
+
* failed, and the feed this sits in has `See everything` under it, which says
|
|
176
|
+
* plainly that the row is a view of a longer list. What dismissing ends is the
|
|
177
|
+
* notice.
|
|
178
|
+
*
|
|
179
|
+
* The component cannot know better than that either. `helix-dismiss` is a
|
|
180
|
+
* request the consumer answers, so whether anything is destroyed is theirs to
|
|
181
|
+
* decide, and a default that shouts danger asserts a permanence this element
|
|
182
|
+
* does not control. `--notifications-dismiss-color` is still there for a
|
|
183
|
+
* consumer whose control really does destroy something.
|
|
158
184
|
*/
|
|
159
185
|
protected renderDismiss(): TemplateResult | typeof nothing;
|
|
160
186
|
/**
|
|
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { customElement, HelixDismissEvent, HelixElement, HelixReadEvent, hostAria, requestChange, slotFilled, } from '@fusion.dev/helix-core';
|
|
8
|
-
import {
|
|
8
|
+
import { x } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { nothing } from 'lit';
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
@@ -62,10 +62,14 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends HelixEle
|
|
|
62
62
|
updated(changed) {
|
|
63
63
|
super.updated(changed);
|
|
64
64
|
this.markControls();
|
|
65
|
+
this.markActions();
|
|
65
66
|
}
|
|
66
67
|
markControls() {
|
|
68
|
+
this.toggleState('controls', this.dismissible);
|
|
69
|
+
}
|
|
70
|
+
markActions() {
|
|
67
71
|
const slot = this.renderRoot?.querySelector('slot[name="actions"]');
|
|
68
|
-
this.toggleState('
|
|
72
|
+
this.toggleState('actions', slotFilled(slot));
|
|
69
73
|
}
|
|
70
74
|
renderDismiss() {
|
|
71
75
|
if (!this.dismissible) {
|
|
@@ -83,9 +87,9 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends HelixEle
|
|
|
83
87
|
data-glyph
|
|
84
88
|
fill="currentColor"
|
|
85
89
|
part="dismiss-mark"
|
|
86
|
-
viewBox=${
|
|
90
|
+
viewBox=${x.viewBox}
|
|
87
91
|
>
|
|
88
|
-
${unsafeSVG(
|
|
92
|
+
${unsafeSVG(x.content)}
|
|
89
93
|
</svg>
|
|
90
94
|
</slot>
|
|
91
95
|
</button>`;
|
|
@@ -105,13 +109,12 @@ let HelixHeadlessNotification = class HelixHeadlessNotification extends HelixEle
|
|
|
105
109
|
<div part="body">
|
|
106
110
|
<div part="label"><slot></slot></div>
|
|
107
111
|
<slot name="caption"></slot>
|
|
112
|
+
<slot name="detail"></slot>
|
|
113
|
+
<span part="actions"><slot name="actions" @slotchange=${this.handleActions}></slot></span>
|
|
108
114
|
</div>
|
|
109
115
|
<div part="trailing">
|
|
110
116
|
${this.renderTime()}
|
|
111
|
-
<div part="controls">
|
|
112
|
-
<span part="actions"><slot name="actions" @slotchange=${this.handleActions}></slot></span>
|
|
113
|
-
${this.renderDismiss()}
|
|
114
|
-
</div>
|
|
117
|
+
<div part="controls">${this.renderDismiss()}</div>
|
|
115
118
|
</div>
|
|
116
119
|
</div>`;
|
|
117
120
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HelixElement } from '@fusion.dev/helix-core';
|
|
2
2
|
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
3
|
+
import type { HelixHeadlessNotificationGroup } from '../group/notification-group.js';
|
|
3
4
|
import type { HelixHeadlessNotification } from '../notification/notification.js';
|
|
4
5
|
declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("@fusion.dev/helix-core").CollectionElement) & typeof HelixElement;
|
|
5
6
|
/**
|
|
@@ -33,6 +34,7 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
|
|
|
33
34
|
* @slot footer - Below the list, for a link to everything.
|
|
34
35
|
* @slot header - Above the list, for a title and the mark-all control.
|
|
35
36
|
*
|
|
37
|
+
* @event {HelixDismissEvent} helix-dismiss - A row asking to be removed, dispatched by the row and heard here because the feed is what a consumer holds. Cancelable: refusing it takes the decision and the row stays.
|
|
36
38
|
* @event {HelixReadEvent} helix-read - Fired by an item, and by this element when everything is marked read. Cancelable.
|
|
37
39
|
*
|
|
38
40
|
* @csspart empty - Shown when there is nothing in the feed.
|
|
@@ -79,8 +81,62 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
|
|
|
79
81
|
* attribute showed a count for an item the reader had just opened.
|
|
80
82
|
*/
|
|
81
83
|
get unread(): number;
|
|
82
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* The notifications in the feed, in the order they are written.
|
|
86
|
+
*
|
|
87
|
+
* **Through a group as well as beside one**, because a feed may be cut into
|
|
88
|
+
* named piles and the rows inside them are still the feed's rows: `unread`
|
|
89
|
+
* counts them and `markEveryoneRead` moves them. A group that renders only its
|
|
90
|
+
* first row while folded is still holding the rest, and a badge that dropped
|
|
91
|
+
* to what was on screen would fall every time somebody folded something.
|
|
92
|
+
*
|
|
93
|
+
* One level, not a walk. A group inside a group is not a thing this family
|
|
94
|
+
* has, and a recursive read here would invent one.
|
|
95
|
+
*
|
|
96
|
+
* `?? []` is the server. Lit's element shim carries attributes and not a tree,
|
|
97
|
+
* so `children` is undefined there, and this getter is reached from `unread`
|
|
98
|
+
* during the first update.
|
|
99
|
+
*/
|
|
83
100
|
get items(): HelixHeadlessNotification[];
|
|
101
|
+
/**
|
|
102
|
+
* What the list slot is given: the rows and the groups, as written.
|
|
103
|
+
*
|
|
104
|
+
* Not `items`, which reaches through a group to the rows inside it. Assigning
|
|
105
|
+
* those would lift them out of the group that is arranging them and hand the
|
|
106
|
+
* feed a flat list again.
|
|
107
|
+
*/
|
|
108
|
+
get rows(): Element[];
|
|
109
|
+
/**
|
|
110
|
+
* The groups written into the feed, in the order they are written.
|
|
111
|
+
*
|
|
112
|
+
* Only the ones that can fold answer to `foldEveryGroup`, but this is every
|
|
113
|
+
* group: a consumer counting their sections should not have to know which of
|
|
114
|
+
* them happen to be collapsible.
|
|
115
|
+
*/
|
|
116
|
+
get groups(): HelixHeadlessNotificationGroup[];
|
|
117
|
+
/**
|
|
118
|
+
* Folds every group that can fold, and reports how many actually moved.
|
|
119
|
+
*
|
|
120
|
+
* **A command, not a second place the state lives.** A group owns whether it
|
|
121
|
+
* is folded; this asks all of them at once. The alternative, a property on the
|
|
122
|
+
* feed that groups read, gives two answers to one question the moment a reader
|
|
123
|
+
* unfolds one of them, and then neither is wrong and neither is right.
|
|
124
|
+
*
|
|
125
|
+
* **Through each group's own request**, so one refusal refuses one group: a
|
|
126
|
+
* `helix-before-open` or `helix-before-close` prevented on a group leaves that
|
|
127
|
+
* group where it was and the rest still move. `markEveryoneRead` takes the
|
|
128
|
+
* same route for the same reason.
|
|
129
|
+
*
|
|
130
|
+
* **And unlike that one, this announces nothing of its own.** Marking read is
|
|
131
|
+
* usually a round trip, so a feed-level `helix-read` lets a consumer refuse the
|
|
132
|
+
* whole operation before any of it starts. Folding is not: it is where the
|
|
133
|
+
* reader is looking, it reaches no server, and an event nobody can meaningfully
|
|
134
|
+
* refuse is a channel that exists to be ignored.
|
|
135
|
+
*
|
|
136
|
+
* A group that is not `collapsible` is left alone rather than counted as
|
|
137
|
+
* refusing, because it was never asked.
|
|
138
|
+
*/
|
|
139
|
+
foldEveryGroup(folded?: boolean): number;
|
|
84
140
|
/**
|
|
85
141
|
* Marks every unread item read, and reports how many actually moved.
|
|
86
142
|
*
|