@fusion.dev/helix-headless 0.23.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 +48376 -26638
- 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 +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/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 = '';
|
|
@@ -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
|
*
|
|
@@ -32,7 +32,33 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
32
32
|
return this.items.filter((item) => !item.read).length;
|
|
33
33
|
}
|
|
34
34
|
get items() {
|
|
35
|
-
|
|
35
|
+
const isItem = (child) => 'isNotification' in child && child.isNotification === true;
|
|
36
|
+
return this.knownChildren.flatMap((child) => {
|
|
37
|
+
if (isItem(child)) {
|
|
38
|
+
return [child];
|
|
39
|
+
}
|
|
40
|
+
const grouped = 'isNotificationGroup' in child ? [...(child.children ?? [])] : [];
|
|
41
|
+
return grouped.filter(isItem);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
get rows() {
|
|
45
|
+
return this.knownChildren.filter((child) => ('isNotification' in child && child.isNotification === true) ||
|
|
46
|
+
'isNotificationGroup' in child);
|
|
47
|
+
}
|
|
48
|
+
get groups() {
|
|
49
|
+
return this.knownChildren.filter((child) => 'isNotificationGroup' in child);
|
|
50
|
+
}
|
|
51
|
+
foldEveryGroup(folded = true) {
|
|
52
|
+
let moved = 0;
|
|
53
|
+
for (const group of this.groups) {
|
|
54
|
+
if (!group.collapsible || group.open !== folded) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (group.requestOpen(!folded)) {
|
|
58
|
+
moved += 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return moved;
|
|
36
62
|
}
|
|
37
63
|
markEveryoneRead() {
|
|
38
64
|
if (!this.dispatchEvent(new HelixReadEvent({ read: true }))) {
|
|
@@ -53,8 +79,13 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
53
79
|
if (!list?.assign) {
|
|
54
80
|
return;
|
|
55
81
|
}
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
if (this.assignsByName) {
|
|
83
|
+
for (const row of this.rows) {
|
|
84
|
+
this.route(row, 'items');
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
list.assign(...this.rows);
|
|
58
89
|
for (const name of ['header', 'footer']) {
|
|
59
90
|
const slot = root?.querySelector?.(`slot[name="${name}"]`);
|
|
60
91
|
slot?.assign?.(...[...this.children].filter((child) => child.getAttribute('slot') === name));
|
|
@@ -101,7 +132,7 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
101
132
|
}
|
|
102
133
|
}
|
|
103
134
|
render() {
|
|
104
|
-
const hasItems = this.items.length > 0;
|
|
135
|
+
const hasItems = this.items.length > 0 || !this.childrenKnown;
|
|
105
136
|
return html `<div part="header">
|
|
106
137
|
<slot name="header"></slot>
|
|
107
138
|
${this.markAll && this.unread > 0
|
|
@@ -142,6 +142,18 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
142
142
|
* stepping to 0 would be out of range before the reader had typed anything.
|
|
143
143
|
*/
|
|
144
144
|
private nudge;
|
|
145
|
+
/**
|
|
146
|
+
* A value the reader moved to in one gesture, applied and committed.
|
|
147
|
+
*
|
|
148
|
+
* **A step is finished when it lands, so it commits.** The field sends
|
|
149
|
+
* `change` on blur for what the reader typed, and a step is written by
|
|
150
|
+
* script, so the browser never sends one for it: three presses of the plus
|
|
151
|
+
* and a move elsewhere reached a form listening on `change` as nothing at
|
|
152
|
+
* all. A native number field commits on every step, and so does this, for
|
|
153
|
+
* the arrows, the buttons, and `Home` and `End`. Only when the value moved: a
|
|
154
|
+
* refused step, or one pinned at a bound, finished nothing.
|
|
155
|
+
*/
|
|
156
|
+
private land;
|
|
145
157
|
/** One step up, or `PageUp`'s larger one. */
|
|
146
158
|
stepUp(large?: boolean): void;
|
|
147
159
|
/**
|
|
@@ -173,6 +185,8 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
173
185
|
/** Where the pointer went down, and what the value was there. */
|
|
174
186
|
private scrubFrom;
|
|
175
187
|
private scrubValue;
|
|
188
|
+
/** The value when the press began, which is what a release is compared to. */
|
|
189
|
+
private scrubStart;
|
|
176
190
|
/**
|
|
177
191
|
* Press, and the value follows the pointer.
|
|
178
192
|
*
|
|
@@ -193,7 +207,13 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
193
207
|
* the value can only be where the pointer says it is.
|
|
194
208
|
*/
|
|
195
209
|
private readonly handleScrubMove;
|
|
196
|
-
/**
|
|
210
|
+
/**
|
|
211
|
+
* Let go, and the capture goes with it, and the drag commits.
|
|
212
|
+
*
|
|
213
|
+
* The same reason a step does: every frame of the drag was written by script,
|
|
214
|
+
* so the browser sends no `change` for any of it, and a drag that moved the
|
|
215
|
+
* value is finished when the pointer lets go.
|
|
216
|
+
*/
|
|
197
217
|
private readonly handleScrubEnd;
|
|
198
218
|
/**
|
|
199
219
|
* Every keystroke, cancelable, and put back where it is refused.
|