@fusion.dev/helix-headless 0.155.0 → 0.157.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 +2695 -137
- package/dist/canvas/node/canvas-node.js +1 -1
- package/dist/canvas/root/canvas-root.d.ts +17 -2
- package/dist/canvas/root/canvas-root.js +23 -6
- package/dist/chat/composer/chat-composer.js +1 -1
- package/dist/chat/root/chat-root.d.ts +14 -1
- package/dist/chat/root/chat-root.js +14 -4
- package/dist/combobox/root/combobox-root.d.ts +10 -0
- package/dist/combobox/root/combobox-root.js +18 -3
- package/dist/command/root/command-root.d.ts +116 -1
- package/dist/command/root/command-root.js +124 -15
- package/dist/data-table/root/data-table-root.d.ts +25 -0
- package/dist/data-table/root/data-table-root.js +13 -3
- package/dist/date-input/root/date-input-root.d.ts +32 -1
- package/dist/date-input/root/date-input-root.js +7 -1
- package/dist/drilldown/item/drilldown-item.d.ts +49 -1
- package/dist/drilldown/item/drilldown-item.js +77 -17
- package/dist/drilldown/root/drilldown-root.d.ts +45 -5
- package/dist/drilldown/root/drilldown-root.js +51 -5
- package/dist/element-events.generated.js +2 -1
- package/dist/filter-input/menu/filter-menu.d.ts +1 -0
- package/dist/filter-input/menu/filter-menu.js +4 -4
- package/dist/graph/root/graph-root.d.ts +13 -0
- package/dist/graph/root/graph-root.js +16 -0
- package/dist/list/root/list-root.d.ts +74 -1
- package/dist/list/root/list-root.js +92 -4
- package/dist/list/selection.js +3 -3
- package/dist/map/root/map-root.d.ts +6 -0
- package/dist/map/root/map-root.js +8 -1
- package/dist/megamenu/root/megamenu-root.d.ts +50 -13
- package/dist/megamenu/root/megamenu-root.js +44 -6
- package/dist/menu/item/menu-item.d.ts +24 -1
- package/dist/menu/item/menu-item.js +42 -2
- package/dist/menu/root/menu-root.d.ts +120 -9
- package/dist/menu/root/menu-root.js +329 -26
- package/dist/notifications/root/notifications-root.d.ts +20 -0
- package/dist/notifications/root/notifications-root.js +32 -1
- package/dist/number-input/root/number-input-root.d.ts +63 -6
- package/dist/number-input/root/number-input-root.js +96 -11
- package/dist/object-inspector/root/object-inspector-root.d.ts +36 -1
- package/dist/object-inspector/root/object-inspector-root.js +26 -2
- package/dist/option/root/option-root.d.ts +32 -0
- package/dist/option/root/option-root.js +44 -5
- package/dist/outline/root/outline-root.d.ts +6 -2
- package/dist/outline/root/outline-root.js +7 -3
- package/dist/table/root/table-root.d.ts +20 -0
- package/dist/table/root/table-root.js +13 -3
- package/dist/time-input/root/time-input-root.d.ts +32 -1
- package/dist/time-input/root/time-input-root.js +9 -2
- package/dist/time-picker/root/time-picker-root.d.ts +63 -1
- package/dist/time-picker/root/time-picker-root.js +58 -7
- package/dist/timeline/item/timeline-item.d.ts +25 -0
- package/dist/timeline/item/timeline-item.js +27 -2
- package/dist/toast/item/toast-item.d.ts +26 -0
- package/dist/toast/item/toast-item.js +55 -3
- package/dist/toast/root/toast-root.d.ts +32 -0
- package/dist/toast/root/toast-root.js +47 -2
- package/dist/video/root/keys.d.ts +6 -2
- package/dist/video/root/keys.js +18 -1
- package/dist/video/root/video-root.d.ts +61 -1
- package/dist/video/root/video-root.js +79 -11
- package/dist/video-playlist/item/video-playlist-item.d.ts +15 -1
- package/dist/video-playlist/item/video-playlist-item.js +7 -0
- package/dist/video-playlist/menu/video-playlist-menu.d.ts +1 -0
- package/dist/video-playlist/menu/video-playlist-menu.js +1 -1
- package/dist/video-playlist/root/video-playlist-root.d.ts +43 -4
- package/dist/video-playlist/root/video-playlist-root.js +69 -2
- package/dist/virtual-list/root/virtual-list-root.d.ts +8 -0
- package/dist/virtual-list/root/virtual-list-root.js +3 -0
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +3346 -266
- package/types/vue.d.ts +910 -66
- package/types/words.d.ts +124 -0
|
@@ -4,11 +4,28 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, HelixElement, inlineStep, KeyboardNavigationController, MixinCollection, MixinControllerOpen, PopoverController, scrollItemIntoView, slottedWords, tellDisclosure, typeaheadBy, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, focusTarget, HelixElement, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, PopoverController, scrollItemIntoView, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { caretLeft } from '@fusion.dev/helix-icons';
|
|
9
9
|
import { html, nothing } from 'lit';
|
|
10
10
|
import { property } from 'lit/decorators.js';
|
|
11
11
|
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
12
|
+
import { LongPress } from '../../speed-dial/long-press.js';
|
|
13
|
+
const isItem = (element) => element !== null &&
|
|
14
|
+
typeof element === 'object' &&
|
|
15
|
+
'isMenuItem' in element &&
|
|
16
|
+
element.isMenuItem === true;
|
|
17
|
+
const HOVER_DELAY = 100;
|
|
18
|
+
const GRACE = 300;
|
|
19
|
+
const inTriangle = (x, y, [a, b, c]) => {
|
|
20
|
+
if (!a || !b || !c) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const side = (p, q) => (q[0] - p[0]) * (y - p[1]) - (q[1] - p[1]) * (x - p[0]);
|
|
24
|
+
const one = side(a, b);
|
|
25
|
+
const two = side(b, c);
|
|
26
|
+
const three = side(c, a);
|
|
27
|
+
return (one >= 0 && two >= 0 && three >= 0) || (one <= 0 && two <= 0 && three <= 0);
|
|
28
|
+
};
|
|
12
29
|
const isSection = (element) => 'isMenuSection' in element && element.isMenuSection === true;
|
|
13
30
|
let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinControllerOpen(HelixElement)) {
|
|
14
31
|
constructor() {
|
|
@@ -16,8 +33,25 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
16
33
|
this.backLabel = 'Back';
|
|
17
34
|
this.context = false;
|
|
18
35
|
this.label = '';
|
|
36
|
+
this.longPressLabel = 'Long press or press Alt+Down to show the menu';
|
|
19
37
|
this.offset = 4;
|
|
38
|
+
this.longPress = new LongPress(this, {
|
|
39
|
+
onLongPress: () => {
|
|
40
|
+
if (this.context) {
|
|
41
|
+
const at = this.heldAt;
|
|
42
|
+
if (at) {
|
|
43
|
+
this.openContextAt(at);
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!this.open) {
|
|
48
|
+
this.show();
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
this.openOn = 'click';
|
|
20
53
|
this.placement = 'bottom-start';
|
|
54
|
+
this.submenus = 'drill';
|
|
21
55
|
this.drilldown = new DrilldownController(this, {
|
|
22
56
|
isRow: (element) => 'isMenuItem' in element && element.isMenuItem === true,
|
|
23
57
|
root: () => [...this.children]
|
|
@@ -46,6 +80,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
46
80
|
placement: () => this.placement,
|
|
47
81
|
surface: () => this.partNamed('surface'),
|
|
48
82
|
});
|
|
83
|
+
this.settledOnce = false;
|
|
49
84
|
this.handleContextMenu = (event) => {
|
|
50
85
|
if (!this.context) {
|
|
51
86
|
return;
|
|
@@ -56,21 +91,19 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
56
91
|
return;
|
|
57
92
|
}
|
|
58
93
|
event.preventDefault();
|
|
59
|
-
|
|
60
|
-
const keyed = event.clientX === 0 && event.clientY === 0;
|
|
61
|
-
this.point = keyed ? { x: box.left, y: box.top } : { x: event.clientX, y: event.clientY };
|
|
62
|
-
if (this.open) {
|
|
63
|
-
this.requestUpdate();
|
|
64
|
-
this.updateComplete.then(() => this.panel.reposition());
|
|
94
|
+
if (this.open && this.longPress.takeClick()) {
|
|
65
95
|
return;
|
|
66
96
|
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
this.
|
|
97
|
+
const box = this.trigger.getBoundingClientRect();
|
|
98
|
+
const keyed = event.clientX === 0 && event.clientY === 0;
|
|
99
|
+
this.openContextAt(keyed ? { x: box.left, y: box.top } : { x: event.clientX, y: event.clientY });
|
|
70
100
|
};
|
|
71
101
|
this.handleSelect = (event) => {
|
|
72
102
|
const item = event.target;
|
|
73
|
-
|
|
103
|
+
const reachable = this.flies
|
|
104
|
+
? isItem(item) && item.onPage
|
|
105
|
+
: this.items.includes(item);
|
|
106
|
+
if (!reachable) {
|
|
74
107
|
return;
|
|
75
108
|
}
|
|
76
109
|
const chosen = item;
|
|
@@ -90,7 +123,10 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
90
123
|
if (!item) {
|
|
91
124
|
return;
|
|
92
125
|
}
|
|
93
|
-
const
|
|
126
|
+
const holder = this.flies ? this.holderOf(item) : null;
|
|
127
|
+
const surface = holder
|
|
128
|
+
? holder.shadowRoot?.querySelector('[part="surface"]')
|
|
129
|
+
: this.partNamed('surface');
|
|
94
130
|
scrollItemIntoView(item, { padding: 4, ...(surface ? { scroller: surface } : {}) });
|
|
95
131
|
};
|
|
96
132
|
this.handleKeydown = (event) => {
|
|
@@ -109,6 +145,9 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
109
145
|
this.enter(items[at]);
|
|
110
146
|
}
|
|
111
147
|
else if (along === -1) {
|
|
148
|
+
if (this.flies && this.keyPage === null) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
112
151
|
this.leave();
|
|
113
152
|
}
|
|
114
153
|
else if (event.key === 'Tab') {
|
|
@@ -127,21 +166,166 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
127
166
|
if (this.open) {
|
|
128
167
|
this.hide();
|
|
129
168
|
}
|
|
130
|
-
else {
|
|
169
|
+
else if (!this.pressHeld) {
|
|
131
170
|
this.show();
|
|
132
171
|
}
|
|
133
172
|
};
|
|
173
|
+
this.handlePointerDown = (event) => {
|
|
174
|
+
if (this.open || !this.fromTrigger(event)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (this.pressHeld || (this.context && event.pointerType !== 'mouse')) {
|
|
178
|
+
this.heldAt = { x: event.clientX, y: event.clientY };
|
|
179
|
+
this.longPress.down(event);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
this.handlePointerMove = (event) => {
|
|
183
|
+
this.longPress.move(event);
|
|
184
|
+
};
|
|
185
|
+
this.handlePointerUp = () => {
|
|
186
|
+
this.longPress.up();
|
|
187
|
+
};
|
|
188
|
+
this.handlePointerCancel = () => {
|
|
189
|
+
this.longPress.cancel();
|
|
190
|
+
};
|
|
191
|
+
this.handleHeldClick = (event) => {
|
|
192
|
+
if (this.pressHeld && this.longPress.takeClick()) {
|
|
193
|
+
event.stopImmediatePropagation();
|
|
194
|
+
event.preventDefault();
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
this.handleTriggerFocusIn = (event) => {
|
|
198
|
+
if (this.pressHeld && this.fromTrigger(event)) {
|
|
199
|
+
this.describeTrigger(true);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
this.handleHover = (event) => {
|
|
203
|
+
if (!this.flies || !this.open || event.pointerType !== 'mouse') {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const row = event
|
|
207
|
+
.composedPath()
|
|
208
|
+
.find((node) => isItem(node));
|
|
209
|
+
if (!row?.onPage || row === this.hovered || this.inGrace(event)) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
this.hovered = row;
|
|
213
|
+
this.grace = undefined;
|
|
214
|
+
if (!row.disabled) {
|
|
215
|
+
row.focus();
|
|
216
|
+
}
|
|
217
|
+
clearTimeout(this.hoverTimer);
|
|
218
|
+
this.hoverTimer = setTimeout(() => {
|
|
219
|
+
if (!this.open || this.hovered !== row) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (row.hasSubmenu && !row.disabled) {
|
|
223
|
+
if (this.drilldown.page !== row) {
|
|
224
|
+
this.drilldown.enter(row);
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const holder = this.holderOf(row);
|
|
229
|
+
if (this.drilldown.page !== holder) {
|
|
230
|
+
this.openPage(holder);
|
|
231
|
+
}
|
|
232
|
+
}, HOVER_DELAY);
|
|
233
|
+
};
|
|
234
|
+
this.handleRowOut = (event) => {
|
|
235
|
+
if (!this.flies || event.pointerType !== 'mouse') {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const row = event
|
|
239
|
+
.composedPath()
|
|
240
|
+
.find((node) => isItem(node));
|
|
241
|
+
const panel = row?.expanded
|
|
242
|
+
? row.shadowRoot?.querySelector('[part="surface"]')
|
|
243
|
+
: undefined;
|
|
244
|
+
if (!panel) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const box = panel.getBoundingClientRect();
|
|
248
|
+
const edge = box.left >= event.clientX ? box.left : box.right;
|
|
249
|
+
this.grace = {
|
|
250
|
+
points: [
|
|
251
|
+
[event.clientX, event.clientY],
|
|
252
|
+
[edge, box.top],
|
|
253
|
+
[edge, box.bottom],
|
|
254
|
+
],
|
|
255
|
+
until: Date.now() + GRACE,
|
|
256
|
+
};
|
|
257
|
+
};
|
|
134
258
|
}
|
|
135
259
|
static { this.words = 'helix-menu'; }
|
|
260
|
+
get flies() {
|
|
261
|
+
return this.submenus === 'flyout';
|
|
262
|
+
}
|
|
263
|
+
get topRows() {
|
|
264
|
+
return [...this.children]
|
|
265
|
+
.filter((child) => child.slot !== 'trigger')
|
|
266
|
+
.flatMap((child) => (isSection(child) ? [...child.children] : [child]))
|
|
267
|
+
.filter(isItem);
|
|
268
|
+
}
|
|
269
|
+
rowsOf(holder) {
|
|
270
|
+
return holder
|
|
271
|
+
? [...holder.children].filter((child) => child.slot === 'submenu' && isItem(child))
|
|
272
|
+
: this.topRows;
|
|
273
|
+
}
|
|
274
|
+
holderOf(row) {
|
|
275
|
+
const parent = row.parentElement;
|
|
276
|
+
return row.slot === 'submenu' && parent && isItem(parent) ? parent : null;
|
|
277
|
+
}
|
|
278
|
+
get openPages() {
|
|
279
|
+
const pages = [];
|
|
280
|
+
for (let page = this.drilldown.page; page; page = this.holderOf(page)) {
|
|
281
|
+
pages.unshift(page);
|
|
282
|
+
}
|
|
283
|
+
return [null, ...pages];
|
|
284
|
+
}
|
|
285
|
+
get keyPage() {
|
|
286
|
+
for (const page of this.openPages) {
|
|
287
|
+
const active = this.rowsOf(page).find((row) => row.active);
|
|
288
|
+
if (active) {
|
|
289
|
+
return page;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return this.drilldown.page;
|
|
293
|
+
}
|
|
294
|
+
openPage(holder) {
|
|
295
|
+
if (holder) {
|
|
296
|
+
this.drilldown.enter(holder);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
this.drilldown.reset();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
get pressHeld() {
|
|
303
|
+
return !this.context && this.openOn === 'long-press';
|
|
304
|
+
}
|
|
136
305
|
tellTrigger() {
|
|
137
306
|
const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
|
|
138
|
-
|
|
307
|
+
const trigger = slot?.assignedElements({ flatten: true })[0];
|
|
308
|
+
if (!trigger || trigger.localName.includes('-')) {
|
|
309
|
+
tellDisclosure(trigger, this.open, 'menu');
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (trigger !== this.told) {
|
|
313
|
+
if (!this.settledOnce || trigger.hasAttribute('aria-expanded')) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
this.told = trigger;
|
|
317
|
+
if (!trigger.hasAttribute('aria-haspopup')) {
|
|
318
|
+
trigger.setAttribute('aria-haspopup', 'menu');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
trigger.setAttribute('aria-expanded', String(this.open));
|
|
139
322
|
}
|
|
140
323
|
get trigger() {
|
|
141
324
|
return anchorElement(this.renderRoot?.querySelector('slot[name="trigger"]'), this);
|
|
142
325
|
}
|
|
143
326
|
get items() {
|
|
144
|
-
|
|
327
|
+
const rows = this.flies ? this.rowsOf(this.keyPage) : this.drilldown.rows;
|
|
328
|
+
return rows.filter((row) => !row.disabled);
|
|
145
329
|
}
|
|
146
330
|
show() {
|
|
147
331
|
this.openAt('first');
|
|
@@ -161,6 +345,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
161
345
|
}
|
|
162
346
|
this.panel.hide();
|
|
163
347
|
this.drilldown.reset();
|
|
348
|
+
this.stopHover();
|
|
164
349
|
if (deepContains(this, deepActiveElement())) {
|
|
165
350
|
;
|
|
166
351
|
(this.context ? (this.returnTo ?? this.trigger) : this.trigger).focus();
|
|
@@ -168,13 +353,43 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
168
353
|
this.point = undefined;
|
|
169
354
|
this.returnTo = undefined;
|
|
170
355
|
}
|
|
356
|
+
openContextAt(point) {
|
|
357
|
+
this.point = point;
|
|
358
|
+
if (this.open) {
|
|
359
|
+
this.requestUpdate();
|
|
360
|
+
this.updateComplete.then(() => this.panel.reposition());
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const active = deepActiveElement();
|
|
364
|
+
this.returnTo = active instanceof HTMLElement ? active : undefined;
|
|
365
|
+
this.show();
|
|
366
|
+
}
|
|
171
367
|
enter(item) {
|
|
172
368
|
if (!this.drilldown.enter(item)) {
|
|
173
369
|
return;
|
|
174
370
|
}
|
|
175
|
-
this.updateComplete.then(() =>
|
|
371
|
+
void this.updateComplete.then(async () => {
|
|
372
|
+
if (!this.flies) {
|
|
373
|
+
this.items[0]?.focusItem();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
await item.updateComplete;
|
|
377
|
+
const first = this.rowsOf(item).find((row) => !row.disabled);
|
|
378
|
+
await first?.updateComplete;
|
|
379
|
+
first?.focusItem();
|
|
380
|
+
});
|
|
176
381
|
}
|
|
177
382
|
leave() {
|
|
383
|
+
if (this.flies) {
|
|
384
|
+
const holder = this.keyPage;
|
|
385
|
+
if (!holder) {
|
|
386
|
+
this.hide();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
this.openPage(this.holderOf(holder));
|
|
390
|
+
this.updateComplete.then(() => holder.focus());
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
178
393
|
if (this.drilldown.atRoot) {
|
|
179
394
|
this.hide();
|
|
180
395
|
return;
|
|
@@ -184,42 +399,107 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
184
399
|
this.updateComplete.then(() => leaving?.focus());
|
|
185
400
|
}
|
|
186
401
|
handleClosedKeydown(event) {
|
|
402
|
+
if (this.context && event.shiftKey && event.key === 'F10' && this.fromTrigger(event)) {
|
|
403
|
+
event.preventDefault();
|
|
404
|
+
const box = this.trigger.getBoundingClientRect();
|
|
405
|
+
this.openContextAt({ x: box.left, y: box.top });
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
187
408
|
if (this.context || (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')) {
|
|
188
409
|
return;
|
|
189
410
|
}
|
|
190
|
-
|
|
191
|
-
const triggers = slot?.assignedElements({ flatten: true }) ?? [];
|
|
192
|
-
if (!event.composedPath().some((node) => triggers.includes(node))) {
|
|
411
|
+
if (this.pressHeld !== event.altKey || !this.fromTrigger(event)) {
|
|
193
412
|
return;
|
|
194
413
|
}
|
|
195
414
|
event.preventDefault();
|
|
196
415
|
this.openAt(event.key === 'ArrowDown' ? 'first' : 'last');
|
|
197
416
|
}
|
|
417
|
+
fromTrigger(event) {
|
|
418
|
+
const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
|
|
419
|
+
const triggers = slot?.assignedElements({ flatten: true }) ?? [];
|
|
420
|
+
return event.composedPath().some((node) => triggers.includes(node));
|
|
421
|
+
}
|
|
422
|
+
describeTrigger(light = false) {
|
|
423
|
+
const slot = this.renderRoot?.querySelector('slot[name="trigger"]');
|
|
424
|
+
const trigger = slot?.assignedElements({ flatten: true })[0];
|
|
425
|
+
const control = this.pressHeld && trigger instanceof HTMLElement ? focusTarget(trigger) : undefined;
|
|
426
|
+
if (this.described && this.described !== control) {
|
|
427
|
+
unlinkDescription(this.described, this);
|
|
428
|
+
this.described = undefined;
|
|
429
|
+
}
|
|
430
|
+
if (control && control !== this.described && linkDescription(control, this, light)) {
|
|
431
|
+
this.described = control;
|
|
432
|
+
}
|
|
433
|
+
this.internals.ariaLabel = this.described ? this.longPressLabel : null;
|
|
434
|
+
}
|
|
435
|
+
stopHover() {
|
|
436
|
+
clearTimeout(this.hoverTimer);
|
|
437
|
+
this.hoverTimer = undefined;
|
|
438
|
+
this.hovered = undefined;
|
|
439
|
+
this.grace = undefined;
|
|
440
|
+
}
|
|
441
|
+
inGrace(event) {
|
|
442
|
+
const grace = this.grace;
|
|
443
|
+
return (grace !== undefined &&
|
|
444
|
+
Date.now() < grace.until &&
|
|
445
|
+
inTriangle(event.clientX, event.clientY, grace.points));
|
|
446
|
+
}
|
|
198
447
|
connectedCallback() {
|
|
199
448
|
super.connectedCallback();
|
|
449
|
+
this.addEventListener('pointermove', this.handleHover);
|
|
450
|
+
this.addEventListener('pointerout', this.handleRowOut);
|
|
200
451
|
this.addEventListener('helix-select', this.handleSelect, true);
|
|
201
452
|
this.addEventListener('keydown', this.handleKeydown);
|
|
202
453
|
this.addEventListener('focusin', this.handleFocusIn);
|
|
203
454
|
this.addEventListener('contextmenu', this.handleContextMenu);
|
|
455
|
+
this.addEventListener('pointerdown', this.handlePointerDown);
|
|
456
|
+
this.addEventListener('pointermove', this.handlePointerMove);
|
|
457
|
+
this.addEventListener('pointerup', this.handlePointerUp);
|
|
458
|
+
this.addEventListener('pointercancel', this.handlePointerCancel);
|
|
459
|
+
this.addEventListener('click', this.handleHeldClick, true);
|
|
460
|
+
this.addEventListener('focusin', this.handleTriggerFocusIn);
|
|
461
|
+
void this.whenSettled.then(() => {
|
|
462
|
+
this.settledOnce = true;
|
|
463
|
+
if (!this.context) {
|
|
464
|
+
this.tellTrigger();
|
|
465
|
+
}
|
|
466
|
+
});
|
|
204
467
|
}
|
|
205
468
|
disconnectedCallback() {
|
|
206
469
|
super.disconnectedCallback();
|
|
470
|
+
this.removeEventListener('pointermove', this.handleHover);
|
|
471
|
+
this.removeEventListener('pointerout', this.handleRowOut);
|
|
472
|
+
this.stopHover();
|
|
207
473
|
this.removeEventListener('helix-select', this.handleSelect, true);
|
|
208
474
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
209
475
|
this.removeEventListener('focusin', this.handleFocusIn);
|
|
210
476
|
this.removeEventListener('contextmenu', this.handleContextMenu);
|
|
477
|
+
this.removeEventListener('pointerdown', this.handlePointerDown);
|
|
478
|
+
this.removeEventListener('pointermove', this.handlePointerMove);
|
|
479
|
+
this.removeEventListener('pointerup', this.handlePointerUp);
|
|
480
|
+
this.removeEventListener('pointercancel', this.handlePointerCancel);
|
|
481
|
+
this.removeEventListener('click', this.handleHeldClick, true);
|
|
482
|
+
this.removeEventListener('focusin', this.handleTriggerFocusIn);
|
|
483
|
+
if (this.described) {
|
|
484
|
+
unlinkDescription(this.described, this);
|
|
485
|
+
this.described = undefined;
|
|
486
|
+
}
|
|
211
487
|
}
|
|
212
488
|
updated(changed) {
|
|
213
489
|
super.updated(changed);
|
|
214
490
|
if (!this.context) {
|
|
215
491
|
this.tellTrigger();
|
|
216
492
|
}
|
|
493
|
+
if (changed.has('openOn') || changed.has('context') || this.described) {
|
|
494
|
+
this.describeTrigger();
|
|
495
|
+
}
|
|
496
|
+
const top = this.flies || this.drilldown.atRoot;
|
|
217
497
|
for (const child of this.children) {
|
|
218
498
|
if (isSection(child)) {
|
|
219
|
-
child.onPage =
|
|
499
|
+
child.onPage = top;
|
|
220
500
|
for (const row of child.children) {
|
|
221
501
|
if (row.isMenuItem === true) {
|
|
222
|
-
this.mark(row,
|
|
502
|
+
this.mark(row, top);
|
|
223
503
|
}
|
|
224
504
|
}
|
|
225
505
|
continue;
|
|
@@ -228,9 +508,9 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
228
508
|
if (item.isMenuItem !== true) {
|
|
229
509
|
continue;
|
|
230
510
|
}
|
|
231
|
-
this.mark(item,
|
|
511
|
+
this.mark(item, top);
|
|
232
512
|
}
|
|
233
|
-
this.toggleState('drilled', !this.drilldown.atRoot);
|
|
513
|
+
this.toggleState('drilled', !this.flies && !this.drilldown.atRoot);
|
|
234
514
|
this.markColumns();
|
|
235
515
|
this.nameSurface();
|
|
236
516
|
}
|
|
@@ -249,13 +529,26 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
249
529
|
surface.ariaLabelledByElements = trigger && !named ? [trigger] : null;
|
|
250
530
|
}
|
|
251
531
|
markColumns() {
|
|
252
|
-
const rows = this.drilldown.rows;
|
|
532
|
+
const rows = this.flies ? this.topRows : this.drilldown.rows;
|
|
253
533
|
this.toggleState('start', rows.some((row) => row.querySelector(':scope > [slot="start"]') !== null));
|
|
254
534
|
this.toggleState('mark', rows.some((row) => row.checkable || row.checked || this.drilldown.hasPage(row)));
|
|
255
535
|
}
|
|
256
536
|
mark(item, onPage) {
|
|
257
537
|
const page = this.drilldown.page;
|
|
258
538
|
item.onPage = onPage;
|
|
539
|
+
const holds = page !== null && item.contains(page);
|
|
540
|
+
item.flyout = this.flies;
|
|
541
|
+
item.expanded = this.flies && holds;
|
|
542
|
+
if (this.flies) {
|
|
543
|
+
item.showingSubmenu = false;
|
|
544
|
+
for (const child of item.children) {
|
|
545
|
+
const nested = child;
|
|
546
|
+
if (nested.isMenuItem === true && child.slot === 'submenu') {
|
|
547
|
+
this.mark(nested, onPage && holds);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
259
552
|
item.showingSubmenu = page !== null && item.contains(page);
|
|
260
553
|
for (const child of item.children) {
|
|
261
554
|
const nested = child;
|
|
@@ -279,10 +572,10 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
279
572
|
role="menu"
|
|
280
573
|
aria-busy=${this.loading ? 'true' : nothing}
|
|
281
574
|
aria-label=${this.label || nothing}
|
|
282
|
-
data-drilled=${this.drilldown.atRoot ? nothing : ''}
|
|
575
|
+
data-drilled=${this.flies || this.drilldown.atRoot ? nothing : ''}
|
|
283
576
|
?hidden=${!this.open}
|
|
284
577
|
>
|
|
285
|
-
${this.drilldown.atRoot
|
|
578
|
+
${this.flies || this.drilldown.atRoot
|
|
286
579
|
? nothing
|
|
287
580
|
: html `<div part="header">
|
|
288
581
|
<button
|
|
@@ -314,12 +607,22 @@ __decorate([
|
|
|
314
607
|
__decorate([
|
|
315
608
|
property()
|
|
316
609
|
], HelixHeadlessMenu.prototype, "label", void 0);
|
|
610
|
+
__decorate([
|
|
611
|
+
words,
|
|
612
|
+
property({ attribute: 'long-press-label' })
|
|
613
|
+
], HelixHeadlessMenu.prototype, "longPressLabel", void 0);
|
|
317
614
|
__decorate([
|
|
318
615
|
property({ type: Number })
|
|
319
616
|
], HelixHeadlessMenu.prototype, "offset", void 0);
|
|
617
|
+
__decorate([
|
|
618
|
+
property({ attribute: 'open-on' })
|
|
619
|
+
], HelixHeadlessMenu.prototype, "openOn", void 0);
|
|
320
620
|
__decorate([
|
|
321
621
|
property()
|
|
322
622
|
], HelixHeadlessMenu.prototype, "placement", void 0);
|
|
623
|
+
__decorate([
|
|
624
|
+
property()
|
|
625
|
+
], HelixHeadlessMenu.prototype, "submenus", void 0);
|
|
323
626
|
HelixHeadlessMenu = __decorate([
|
|
324
627
|
customElement('helix-headless-menu')
|
|
325
628
|
], HelixHeadlessMenu);
|
|
@@ -40,6 +40,11 @@ declare const HelixHeadlessNotifications_base: (new (...args: any[]) => import("
|
|
|
40
40
|
* | Control+Home | From anywhere in the feed, moves to the nearest control before it on the page. |
|
|
41
41
|
* | Tab | Moves through the controls in a row, and on out of the feed. |
|
|
42
42
|
*
|
|
43
|
+
* **A notification that arrives says so.** Unread rows added above the rows
|
|
44
|
+
* there were are counted in the live region,
|
|
45
|
+
* "1 new notification", in `arrived-label`. Rows that arrive while `loading`
|
|
46
|
+
* is set say nothing, so set it while a page is on its way.
|
|
47
|
+
*
|
|
43
48
|
* **`load-more` adds a button after the list** that sends `helix-load-more`,
|
|
44
49
|
* for a feed that holds one page of a longer list. Append the next page, set
|
|
45
50
|
* `loading` while it is on its way, and take `load-more` off once there is
|
|
@@ -100,6 +105,11 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
|
|
|
100
105
|
* A notification's own words are under `helix-notification`.
|
|
101
106
|
*/
|
|
102
107
|
static words: string;
|
|
108
|
+
/**
|
|
109
|
+
* What the feed says when unread notifications arrive, with `{count}` the
|
|
110
|
+
* number of them. Empty it to say nothing.
|
|
111
|
+
*/
|
|
112
|
+
arrivedLabel: string;
|
|
103
113
|
/** The region's name where `label` is empty. */
|
|
104
114
|
fallbackLabel: string;
|
|
105
115
|
/**
|
|
@@ -272,6 +282,16 @@ export declare class HelixHeadlessNotifications extends HelixHeadlessNotificatio
|
|
|
272
282
|
private rove;
|
|
273
283
|
connectedCallback(): void;
|
|
274
284
|
disconnectedCallback(): void;
|
|
285
|
+
/** The rows the feed has met, so a row that arrives later is told apart. */
|
|
286
|
+
private seen;
|
|
287
|
+
/** Empties the arrival a moment after it is said, so the next one is news. */
|
|
288
|
+
private readonly hush;
|
|
289
|
+
/**
|
|
290
|
+
* Counts the unread rows that arrived above the rows there were, and says
|
|
291
|
+
* how many. Rows that fill an empty feed are a page, which says nothing. The rows there are at the first update are
|
|
292
|
+
* the feed's to begin with, and say nothing.
|
|
293
|
+
*/
|
|
294
|
+
private noteArrivals;
|
|
275
295
|
protected willUpdate(changed: PropertyValues): void;
|
|
276
296
|
/**
|
|
277
297
|
* The error, the empty or the loading line, each around a slot of the same
|
|
@@ -4,13 +4,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { assignSlots, ChildrenController, customElement, deepContains, focusableElements, HelixElement, HelixLoadMoreEvent, HelixReadEvent, hostAria, MixinCollection, markRegion, rovingStop, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { assignSlots, ChildrenController, customElement, DelayController, deepContains, focusableElements, formatWords, HelixElement, HelixLoadMoreEvent, HelixReadEvent, hostAria, MixinCollection, markRegion, rovingStop, whenHydrated, words, } from '@fusion.dev/helix-core';
|
|
8
8
|
import { html, nothing } from 'lit';
|
|
9
9
|
import { property } from 'lit/decorators.js';
|
|
10
10
|
import { feedStep } from '../feed-keys.js';
|
|
11
11
|
let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinCollection(HelixElement) {
|
|
12
12
|
constructor() {
|
|
13
13
|
super(...arguments);
|
|
14
|
+
this.arrivedLabel = '{count, plural, one {# new notification} other {# new notifications}}';
|
|
14
15
|
this.fallbackLabel = 'Notifications';
|
|
15
16
|
this.label = '';
|
|
16
17
|
this.loadError = '';
|
|
@@ -60,6 +61,7 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
60
61
|
rowBox(next)?.focus();
|
|
61
62
|
}
|
|
62
63
|
};
|
|
64
|
+
this.hush = new DelayController(this, { renders: false });
|
|
63
65
|
this.askForMore = () => {
|
|
64
66
|
if (!this.loading) {
|
|
65
67
|
this.dispatchEvent(new HelixLoadMoreEvent());
|
|
@@ -196,8 +198,33 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
196
198
|
this.removeEventListener('focusin', this.handleFocusIn);
|
|
197
199
|
this.removeEventListener('keydown', this.handleKeydown);
|
|
198
200
|
}
|
|
201
|
+
noteArrivals() {
|
|
202
|
+
if (!this.childrenKnown) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const items = this.items;
|
|
206
|
+
const seen = this.seen;
|
|
207
|
+
if (!seen) {
|
|
208
|
+
this.seen = new WeakSet(items);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const firstKnown = items.findIndex((item) => seen.has(item));
|
|
212
|
+
const fresh = items.filter((item, at) => !seen.has(item) && at < firstKnown && !item.read);
|
|
213
|
+
for (const item of items) {
|
|
214
|
+
seen.add(item);
|
|
215
|
+
}
|
|
216
|
+
if (fresh.length === 0 || this.loading || this.arrivedLabel === '') {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
this.announced = formatWords(this, 'arrivedLabel', { count: fresh.length });
|
|
220
|
+
this.hush.start(1000, () => {
|
|
221
|
+
this.announced = '';
|
|
222
|
+
this.requestUpdate();
|
|
223
|
+
});
|
|
224
|
+
}
|
|
199
225
|
willUpdate(changed) {
|
|
200
226
|
super.willUpdate(changed);
|
|
227
|
+
this.noteArrivals();
|
|
201
228
|
hostAria(this.internals, { label: this.label || this.fallbackLabel, role: 'region' });
|
|
202
229
|
const failed = this.failed;
|
|
203
230
|
this.toggleState('error', failed);
|
|
@@ -284,6 +311,10 @@ let HelixHeadlessNotifications = class HelixHeadlessNotifications extends MixinC
|
|
|
284
311
|
<div part="footer"><slot name="footer"></slot></div>`;
|
|
285
312
|
}
|
|
286
313
|
};
|
|
314
|
+
__decorate([
|
|
315
|
+
words,
|
|
316
|
+
property({ attribute: 'arrived-label' })
|
|
317
|
+
], HelixHeadlessNotifications.prototype, "arrivedLabel", void 0);
|
|
287
318
|
__decorate([
|
|
288
319
|
words,
|
|
289
320
|
property({ attribute: 'fallback-label' })
|