@fusion.dev/helix-headless 0.22.0 → 0.24.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 +48397 -26631
- 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/carousel/root/carousel-root.d.ts +1 -0
- package/dist/carousel/root/carousel-root.js +18 -2
- 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/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 +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- 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 +54 -6
- package/dist/menu/item/menu-item.js +31 -6
- package/dist/menu/root/menu-root.d.ts +23 -0
- package/dist/menu/root/menu-root.js +13 -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/outline/root/outline-root.d.ts +16 -1
- package/dist/outline/root/outline-root.js +27 -9
- package/dist/radio/group/radio-group.d.ts +1 -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/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/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 +1 -0
- 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 +42 -22
- 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 +4535 -346
- package/types/svelte.d.ts +11 -0
- package/types/vue.d.ts +737 -39
|
@@ -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 = '';
|
|
@@ -24,6 +25,11 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
24
25
|
this.value = '';
|
|
25
26
|
this.isMenuItem = true;
|
|
26
27
|
this.submenu = false;
|
|
28
|
+
this.start = false;
|
|
29
|
+
this.readStart = () => {
|
|
30
|
+
const slot = this.renderRoot.querySelector('slot[name="start"]');
|
|
31
|
+
this.start = (slot?.assignedNodes().length ?? 0) > 0;
|
|
32
|
+
};
|
|
27
33
|
this.readSubmenu = () => {
|
|
28
34
|
const slot = this.renderRoot.querySelector('slot[name="submenu"]');
|
|
29
35
|
this.submenu = (slot?.assignedElements().length ?? 0) > 0;
|
|
@@ -50,9 +56,18 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
50
56
|
get hasSubmenu() {
|
|
51
57
|
return this.submenu;
|
|
52
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
|
+
}
|
|
53
68
|
firstUpdated(changed) {
|
|
54
69
|
super.firstUpdated(changed);
|
|
55
|
-
this.
|
|
70
|
+
this.finishHydration();
|
|
56
71
|
}
|
|
57
72
|
focus() {
|
|
58
73
|
this.renderRoot.querySelector('[part="item"]')?.focus();
|
|
@@ -81,9 +96,12 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
81
96
|
}
|
|
82
97
|
const submenu = this.hasSubmenu;
|
|
83
98
|
const linked = this.href !== '' && !this.disabled;
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
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;
|
|
102
|
+
const inside = html `<span part="start" ?data-empty=${!this.start}
|
|
103
|
+
><slot name="start" @slotchange=${this.readStart}></slot
|
|
104
|
+
></span>
|
|
87
105
|
<slot></slot>
|
|
88
106
|
${submenu
|
|
89
107
|
? html `<svg
|
|
@@ -104,7 +122,8 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
104
122
|
>
|
|
105
123
|
${unsafeSVG(check.content)}
|
|
106
124
|
</svg>`
|
|
107
|
-
:
|
|
125
|
+
:
|
|
126
|
+
nothing}`;
|
|
108
127
|
return html `${linked
|
|
109
128
|
? html `<a
|
|
110
129
|
part="item"
|
|
@@ -129,7 +148,7 @@ let HelixHeadlessMenuItem = class HelixHeadlessMenuItem extends HelixElement {
|
|
|
129
148
|
@click=${this.handleClick}
|
|
130
149
|
@keydown=${this.handleKeydown}
|
|
131
150
|
>${inside}</div>`}
|
|
132
|
-
<slot name="submenu" @slotchange=${this.readSubmenu}></slot>`;
|
|
151
|
+
<div hidden><slot name="submenu" @slotchange=${this.readSubmenu}></slot></div>`;
|
|
133
152
|
}
|
|
134
153
|
};
|
|
135
154
|
__decorate([
|
|
@@ -144,6 +163,9 @@ __decorate([
|
|
|
144
163
|
__decorate([
|
|
145
164
|
property({ type: Boolean })
|
|
146
165
|
], HelixHeadlessMenuItem.prototype, "download", void 0);
|
|
166
|
+
__decorate([
|
|
167
|
+
property({ reflect: true, type: Boolean })
|
|
168
|
+
], HelixHeadlessMenuItem.prototype, "exclusive", void 0);
|
|
147
169
|
__decorate([
|
|
148
170
|
property()
|
|
149
171
|
], HelixHeadlessMenuItem.prototype, "href", void 0);
|
|
@@ -165,6 +187,9 @@ __decorate([
|
|
|
165
187
|
__decorate([
|
|
166
188
|
state()
|
|
167
189
|
], HelixHeadlessMenuItem.prototype, "submenu", void 0);
|
|
190
|
+
__decorate([
|
|
191
|
+
state()
|
|
192
|
+
], HelixHeadlessMenuItem.prototype, "start", void 0);
|
|
168
193
|
HelixHeadlessMenuItem = __decorate([
|
|
169
194
|
customElement('helix-headless-menu-item')
|
|
170
195
|
], HelixHeadlessMenuItem);
|
|
@@ -126,6 +126,29 @@ export declare class HelixHeadlessMenu extends HelixHeadlessMenu_base {
|
|
|
126
126
|
connectedCallback(): void;
|
|
127
127
|
disconnectedCallback(): void;
|
|
128
128
|
protected updated(changed: PropertyValues): void;
|
|
129
|
+
/**
|
|
130
|
+
* Says whether the page showing reserves a column either side of its labels.
|
|
131
|
+
*
|
|
132
|
+
* **A fact no row can answer for itself**, which is why the columns were
|
|
133
|
+
* unconditional. A row knows about its own start slot and nothing about its
|
|
134
|
+
* neighbors, so a page where one row has an icon and the rest do not draws two
|
|
135
|
+
* different left edges for its text. Reserving on every row instead charged
|
|
136
|
+
* every menu for a column nothing in it was ever going to fill: measured on a
|
|
137
|
+
* page of two plain rows, the text started 36 pixels into the row, of which 12
|
|
138
|
+
* was the row's own padding and 24 a leading column and its seam. The same
|
|
139
|
+
* page now starts at 12, and a page where one row does have a mark still
|
|
140
|
+
* starts both of its rows at 36.
|
|
141
|
+
*
|
|
142
|
+
* **Per page rather than per menu**, because a submenu is a different set of
|
|
143
|
+
* rows: a page of checkable filters under a page of plain ones reserves on one
|
|
144
|
+
* and not on the other, and asking the whole tree would hand the plain page
|
|
145
|
+
* the filters' column. `Notifications` answers the same question for its media
|
|
146
|
+
* column the same way, and for the same reason.
|
|
147
|
+
*
|
|
148
|
+
* Read off the light DOM rather than off the rows, so it does not depend on
|
|
149
|
+
* which children have upgraded by the time this runs.
|
|
150
|
+
*/
|
|
151
|
+
private markColumns;
|
|
129
152
|
/**
|
|
130
153
|
* Walks the tree marking which rows are on the page showing.
|
|
131
154
|
*
|
|
@@ -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));
|
|
@@ -159,10 +163,17 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
159
163
|
}
|
|
160
164
|
this.mark(item, this.drilldown.atRoot);
|
|
161
165
|
}
|
|
166
|
+
this.markColumns();
|
|
167
|
+
}
|
|
168
|
+
markColumns() {
|
|
169
|
+
const rows = this.drilldown.rows;
|
|
170
|
+
this.toggleState('start', rows.some((row) => row.querySelector(':scope > [slot="start"]') !== null));
|
|
171
|
+
this.toggleState('mark', rows.some((row) => row.checkable || row.checked || this.drilldown.hasPage(row)));
|
|
162
172
|
}
|
|
163
173
|
mark(item, onPage) {
|
|
174
|
+
const page = this.drilldown.page;
|
|
164
175
|
item.onPage = onPage;
|
|
165
|
-
item.showingSubmenu =
|
|
176
|
+
item.showingSubmenu = page !== null && item.contains(page);
|
|
166
177
|
for (const child of item.children) {
|
|
167
178
|
const nested = child;
|
|
168
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
|
*
|