@fusion.dev/helix-headless 0.164.0 → 0.166.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 +837 -64
- package/dist/callout/root/callout-root.d.ts +8 -2
- package/dist/callout/root/callout-root.js +14 -1
- package/dist/code-block/root/code-block-root.d.ts +63 -0
- package/dist/code-block/root/code-block-root.js +117 -6
- package/dist/date-input/root/date-input-root.d.ts +3 -3
- package/dist/date-input/root/date-input-root.js +8 -5
- package/dist/element-events.generated.js +15 -2
- package/dist/filter-input/menu/filter-menu.d.ts +1 -0
- package/dist/filter-input/menu/filter-menu.js +1 -1
- package/dist/format-date/root/format-date-root.d.ts +3 -1
- package/dist/format-date/root/format-date-root.js +4 -1
- package/dist/lightbox/item/lightbox-item.js +3 -1
- package/dist/lightbox/root/lightbox-root.d.ts +28 -0
- package/dist/lightbox/root/lightbox-root.js +21 -2
- package/dist/listbox/root/listbox-root.js +2 -5
- package/dist/menu/item/menu-item.d.ts +20 -0
- package/dist/menu/item/menu-item.js +53 -20
- package/dist/menu/root/menu-root.d.ts +84 -1
- package/dist/menu/root/menu-root.js +187 -44
- package/dist/number-input/root/number-input-root.d.ts +32 -9
- package/dist/number-input/root/number-input-root.js +53 -9
- package/dist/object-inspector/root/object-inspector-root.d.ts +5 -0
- package/dist/object-inspector/root/object-inspector-root.js +5 -0
- package/dist/option/root/index.d.ts +1 -0
- package/dist/option/root/index.js +1 -0
- package/dist/steps/root/steps-root.d.ts +7 -0
- package/dist/steps/root/steps-root.js +7 -0
- package/dist/tag/root/index.d.ts +1 -0
- package/dist/tag/root/index.js +1 -0
- package/dist/time-input/root/time-input-root.d.ts +4 -3
- package/dist/time-input/root/time-input-root.js +5 -4
- package/dist/time-picker/root/time-picker-root.d.ts +1 -1
- package/dist/time-picker/root/time-picker-root.js +5 -2
- package/dist/video/root/video-root.d.ts +2 -0
- package/dist/video/root/video-root.js +4 -2
- 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.js +15 -1
- package/lint.json +1 -1
- package/package.json +5 -5
- package/types/jsx.d.ts +310 -44
- package/types/vue.d.ts +68 -11
- package/types/words.d.ts +18 -0
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { AriaForwardController, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, focusTarget, HelixElement, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, onUnlessFalse, PopoverController, scrollItemIntoView, sideOf, slottedWords, tellDisclosure, typeaheadBy, unlinkDescription, words, } from '@fusion.dev/helix-core';
|
|
7
|
+
import { AriaForwardController, anchorElement, customElement, DrilldownController, deepActiveElement, deepContains, filterKeeps, focusTarget, HelixElement, HelixFilterEvent, inlineStep, KeyboardNavigationController, linkDescription, MixinCollection, MixinControllerOpen, markMatches, onUnlessFalse, PopoverController, scrollItemIntoView, sideOf, 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';
|
|
@@ -33,8 +33,12 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
33
33
|
this.backLabel = 'Back';
|
|
34
34
|
this.closeOnSelect = true;
|
|
35
35
|
this.context = false;
|
|
36
|
+
this.filter = '';
|
|
37
|
+
this.filterable = false;
|
|
38
|
+
this.filterLabel = 'Filter';
|
|
36
39
|
this.label = '';
|
|
37
40
|
this.longPressLabel = 'Long press or press Alt+Down to show the menu';
|
|
41
|
+
this.noResultsText = 'No matches';
|
|
38
42
|
this.offset = 4;
|
|
39
43
|
this.longPress = new LongPress(this, {
|
|
40
44
|
onLongPress: () => {
|
|
@@ -68,7 +72,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
68
72
|
});
|
|
69
73
|
this.aria = new AriaForwardController(this, {
|
|
70
74
|
label: true,
|
|
71
|
-
target: () => this.
|
|
75
|
+
target: () => this.menuBox,
|
|
72
76
|
});
|
|
73
77
|
this.panel = new PopoverController(this, {
|
|
74
78
|
anchor: () => this.context && this.point
|
|
@@ -89,6 +93,11 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
89
93
|
surface: () => this.partNamed('surface'),
|
|
90
94
|
});
|
|
91
95
|
this.settledOnce = false;
|
|
96
|
+
this.filterPage = null;
|
|
97
|
+
this.handleFilterInput = (event) => {
|
|
98
|
+
event.stopPropagation();
|
|
99
|
+
this.filterBy(event.target.value);
|
|
100
|
+
};
|
|
92
101
|
this.handleContextMenu = (event) => {
|
|
93
102
|
if (!this.context) {
|
|
94
103
|
return;
|
|
@@ -129,14 +138,9 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
129
138
|
};
|
|
130
139
|
this.handleFocusIn = (event) => {
|
|
131
140
|
const item = this.items.find((row) => row.contains(event.target));
|
|
132
|
-
if (
|
|
133
|
-
|
|
141
|
+
if (item) {
|
|
142
|
+
this.revealRow(item);
|
|
134
143
|
}
|
|
135
|
-
const holder = this.flies ? this.holderOf(item) : null;
|
|
136
|
-
const surface = holder
|
|
137
|
-
? holder.shadowRoot?.querySelector('[part="surface"]')
|
|
138
|
-
: this.partNamed('surface');
|
|
139
|
-
scrollItemIntoView(item, { padding: 4, ...(surface ? { scroller: surface } : {}) });
|
|
140
144
|
};
|
|
141
145
|
this.handleKeydown = (event) => {
|
|
142
146
|
if (!this.open) {
|
|
@@ -146,9 +150,23 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
146
150
|
const items = this.items;
|
|
147
151
|
const at = items.findIndex((item) => item.active);
|
|
148
152
|
const along = inlineStep(this, event.key);
|
|
153
|
+
const typing = this.filterable && event.composedPath()[0] === this.field;
|
|
154
|
+
const step = event.key === 'ArrowDown' ? 1 : event.key === 'ArrowUp' ? -1 : 0;
|
|
149
155
|
if (event.key === 'Escape') {
|
|
150
156
|
event.stopPropagation();
|
|
151
|
-
this.
|
|
157
|
+
if (!this.filterable || this.filter === '') {
|
|
158
|
+
this.leave();
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
this.filterBy('');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (typing && step !== 0) {
|
|
165
|
+
const count = items.length;
|
|
166
|
+
this.reach(items[at < 0 ? (step > 0 ? 0 : count - 1) : (at + step + count) % count]);
|
|
167
|
+
}
|
|
168
|
+
else if (typing && event.key === 'Enter') {
|
|
169
|
+
items[at]?.renderRoot.querySelector('[part="item"]')?.click();
|
|
152
170
|
}
|
|
153
171
|
else if (along === 1 && items[at]?.hasSubmenu) {
|
|
154
172
|
this.enter(items[at]);
|
|
@@ -188,6 +206,14 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
188
206
|
this.longPress.down(event);
|
|
189
207
|
}
|
|
190
208
|
};
|
|
209
|
+
this.handleMouseDown = (event) => {
|
|
210
|
+
if (this.filterable &&
|
|
211
|
+
this.open &&
|
|
212
|
+
event.composedPath()[0] !== this.field &&
|
|
213
|
+
!this.fromTrigger(event)) {
|
|
214
|
+
event.preventDefault();
|
|
215
|
+
}
|
|
216
|
+
};
|
|
191
217
|
this.handlePointerMove = (event) => {
|
|
192
218
|
this.longPress.move(event);
|
|
193
219
|
};
|
|
@@ -209,7 +235,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
209
235
|
}
|
|
210
236
|
};
|
|
211
237
|
this.handleHover = (event) => {
|
|
212
|
-
if (!this.flies || !this.open || event.pointerType !== 'mouse') {
|
|
238
|
+
if (!(this.flies || this.filterable) || !this.open || event.pointerType !== 'mouse') {
|
|
213
239
|
return;
|
|
214
240
|
}
|
|
215
241
|
const row = event
|
|
@@ -221,7 +247,10 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
221
247
|
this.hovered = row;
|
|
222
248
|
this.grace = undefined;
|
|
223
249
|
if (!row.disabled) {
|
|
224
|
-
|
|
250
|
+
this.reach(row);
|
|
251
|
+
}
|
|
252
|
+
if (!this.flies) {
|
|
253
|
+
return;
|
|
225
254
|
}
|
|
226
255
|
clearTimeout(this.hoverTimer);
|
|
227
256
|
this.hoverTimer = setTimeout(() => {
|
|
@@ -334,7 +363,53 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
334
363
|
}
|
|
335
364
|
get items() {
|
|
336
365
|
const rows = this.flies ? this.rowsOf(this.keyPage) : this.drilldown.rows;
|
|
337
|
-
return rows.filter((row) => !row.disabled);
|
|
366
|
+
return rows.filter((row) => !row.disabled && this.kept(row));
|
|
367
|
+
}
|
|
368
|
+
get menuBox() {
|
|
369
|
+
return this.filterable
|
|
370
|
+
? (this.renderRoot?.querySelector('#items') ?? null)
|
|
371
|
+
: this.partNamed('surface');
|
|
372
|
+
}
|
|
373
|
+
get field() {
|
|
374
|
+
return this.partNamed('search-field');
|
|
375
|
+
}
|
|
376
|
+
get filtering() {
|
|
377
|
+
return this.filterable && this.filter.trim() !== '' && this.filter !== this.refused;
|
|
378
|
+
}
|
|
379
|
+
kept(row) {
|
|
380
|
+
return (!this.filtering ||
|
|
381
|
+
this.holderOf(row) !== this.filterPage ||
|
|
382
|
+
filterKeeps(slottedWords(row, ['']), this.filter.trim(), this.filterMatch));
|
|
383
|
+
}
|
|
384
|
+
requestFilter(value) {
|
|
385
|
+
if (value === this.filter) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
this.filterPage = this.flies ? this.keyPage : this.drilldown.page;
|
|
389
|
+
this.refused = this.dispatchEvent(new HelixFilterEvent({ value })) ? undefined : value;
|
|
390
|
+
this.filter = value;
|
|
391
|
+
}
|
|
392
|
+
filterBy(value) {
|
|
393
|
+
this.requestFilter(value);
|
|
394
|
+
void this.updateComplete.then(() => this.reach(this.items[0]));
|
|
395
|
+
}
|
|
396
|
+
reach(row) {
|
|
397
|
+
if (!this.filterable) {
|
|
398
|
+
row?.focus();
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (this.highlighted && this.highlighted !== row) {
|
|
402
|
+
this.highlighted.highlighted = false;
|
|
403
|
+
}
|
|
404
|
+
this.highlighted = row;
|
|
405
|
+
const field = this.field;
|
|
406
|
+
if (field && 'ariaActiveDescendantElement' in field) {
|
|
407
|
+
field.ariaActiveDescendantElement = row ?? null;
|
|
408
|
+
}
|
|
409
|
+
if (row) {
|
|
410
|
+
row.highlighted = true;
|
|
411
|
+
this.revealRow(row);
|
|
412
|
+
}
|
|
338
413
|
}
|
|
339
414
|
show() {
|
|
340
415
|
this.openAt('first');
|
|
@@ -345,7 +420,8 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
345
420
|
}
|
|
346
421
|
this.updateComplete.then(() => {
|
|
347
422
|
const items = this.items;
|
|
348
|
-
|
|
423
|
+
this.field?.focus();
|
|
424
|
+
this.reach(end === 'first' ? items[0] : items[items.length - 1]);
|
|
349
425
|
});
|
|
350
426
|
}
|
|
351
427
|
hide() {
|
|
@@ -355,6 +431,8 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
355
431
|
this.panel.hide();
|
|
356
432
|
this.drilldown.reset();
|
|
357
433
|
this.stopHover();
|
|
434
|
+
this.requestFilter('');
|
|
435
|
+
this.reach(undefined);
|
|
358
436
|
if (deepContains(this, deepActiveElement())) {
|
|
359
437
|
;
|
|
360
438
|
(this.context ? (this.returnTo ?? this.trigger) : this.trigger).focus();
|
|
@@ -377,18 +455,20 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
377
455
|
if (!this.drilldown.enter(item)) {
|
|
378
456
|
return;
|
|
379
457
|
}
|
|
458
|
+
this.requestFilter('');
|
|
380
459
|
void this.updateComplete.then(async () => {
|
|
381
460
|
if (!this.flies) {
|
|
382
|
-
this.items[0]
|
|
461
|
+
this.reach(this.items[0]);
|
|
383
462
|
return;
|
|
384
463
|
}
|
|
385
464
|
await item.updateComplete;
|
|
386
465
|
const first = this.rowsOf(item).find((row) => !row.disabled);
|
|
387
466
|
await first?.updateComplete;
|
|
388
|
-
first
|
|
467
|
+
this.reach(first);
|
|
389
468
|
});
|
|
390
469
|
}
|
|
391
470
|
leave() {
|
|
471
|
+
this.requestFilter('');
|
|
392
472
|
if (this.flies) {
|
|
393
473
|
const holder = this.keyPage;
|
|
394
474
|
if (!holder) {
|
|
@@ -396,7 +476,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
396
476
|
return;
|
|
397
477
|
}
|
|
398
478
|
this.openPage(this.holderOf(holder));
|
|
399
|
-
this.updateComplete.then(() =>
|
|
479
|
+
this.updateComplete.then(() => this.reach(holder));
|
|
400
480
|
return;
|
|
401
481
|
}
|
|
402
482
|
if (this.drilldown.atRoot) {
|
|
@@ -405,7 +485,14 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
405
485
|
}
|
|
406
486
|
const leaving = this.drilldown.page;
|
|
407
487
|
this.drilldown.back();
|
|
408
|
-
this.updateComplete.then(() => leaving
|
|
488
|
+
this.updateComplete.then(() => this.reach(leaving ?? undefined));
|
|
489
|
+
}
|
|
490
|
+
revealRow(item) {
|
|
491
|
+
const holder = this.flies ? this.holderOf(item) : null;
|
|
492
|
+
const surface = holder
|
|
493
|
+
? holder.shadowRoot?.querySelector('[part="surface"]')
|
|
494
|
+
: this.partNamed('surface');
|
|
495
|
+
scrollItemIntoView(item, { padding: 4, ...(surface ? { scroller: surface } : {}) });
|
|
409
496
|
}
|
|
410
497
|
handleClosedKeydown(event) {
|
|
411
498
|
if (this.context && event.shiftKey && event.key === 'F10' && this.fromTrigger(event)) {
|
|
@@ -462,6 +549,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
462
549
|
this.addEventListener('focusin', this.handleFocusIn);
|
|
463
550
|
this.addEventListener('contextmenu', this.handleContextMenu);
|
|
464
551
|
this.addEventListener('pointerdown', this.handlePointerDown);
|
|
552
|
+
this.addEventListener('mousedown', this.handleMouseDown);
|
|
465
553
|
this.addEventListener('pointermove', this.handlePointerMove);
|
|
466
554
|
this.addEventListener('pointerup', this.handlePointerUp);
|
|
467
555
|
this.addEventListener('pointercancel', this.handlePointerCancel);
|
|
@@ -484,6 +572,10 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
484
572
|
this.removeEventListener('focusin', this.handleFocusIn);
|
|
485
573
|
this.removeEventListener('contextmenu', this.handleContextMenu);
|
|
486
574
|
this.removeEventListener('pointerdown', this.handlePointerDown);
|
|
575
|
+
this.removeEventListener('mousedown', this.handleMouseDown);
|
|
576
|
+
if (this.filterable) {
|
|
577
|
+
markMatches(this, [], '');
|
|
578
|
+
}
|
|
487
579
|
this.removeEventListener('pointermove', this.handlePointerMove);
|
|
488
580
|
this.removeEventListener('pointerup', this.handlePointerUp);
|
|
489
581
|
this.removeEventListener('pointercancel', this.handlePointerCancel);
|
|
@@ -505,7 +597,9 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
505
597
|
const top = this.flies || this.drilldown.atRoot;
|
|
506
598
|
for (const child of this.children) {
|
|
507
599
|
if (isSection(child)) {
|
|
508
|
-
child.onPage =
|
|
600
|
+
child.onPage =
|
|
601
|
+
top &&
|
|
602
|
+
(!this.filtering || [...child.children].some((row) => isItem(row) && this.kept(row)));
|
|
509
603
|
for (const row of child.children) {
|
|
510
604
|
if (row.isMenuItem === true) {
|
|
511
605
|
this.mark(row, top);
|
|
@@ -522,9 +616,18 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
522
616
|
this.toggleState('drilled', !this.flies && !this.drilldown.atRoot);
|
|
523
617
|
this.markColumns();
|
|
524
618
|
this.nameSurface();
|
|
619
|
+
if (this.filterable) {
|
|
620
|
+
markMatches(this, this.rowsOf(this.filterPage).filter((row) => this.kept(row)), this.filtering ? this.filter.trim() : '');
|
|
621
|
+
const items = this.items;
|
|
622
|
+
if (changed.has('filter') &&
|
|
623
|
+
this.open &&
|
|
624
|
+
!items.includes(this.highlighted)) {
|
|
625
|
+
this.reach(items[0]);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
525
628
|
}
|
|
526
629
|
nameSurface() {
|
|
527
|
-
const surface = this.
|
|
630
|
+
const surface = this.menuBox;
|
|
528
631
|
if (!surface || !('ariaLabelledByElements' in surface)) {
|
|
529
632
|
return;
|
|
530
633
|
}
|
|
@@ -544,7 +647,8 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
544
647
|
}
|
|
545
648
|
mark(item, onPage) {
|
|
546
649
|
const page = this.drilldown.page;
|
|
547
|
-
item.onPage = onPage;
|
|
650
|
+
item.onPage = onPage && this.kept(item);
|
|
651
|
+
item.virtual = this.filterable;
|
|
548
652
|
const holds = page !== null && item.contains(page);
|
|
549
653
|
item.flyout = this.flies;
|
|
550
654
|
item.expanded = this.flies && holds;
|
|
@@ -553,7 +657,7 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
553
657
|
for (const child of item.children) {
|
|
554
658
|
const nested = child;
|
|
555
659
|
if (nested.isMenuItem === true && child.slot === 'submenu') {
|
|
556
|
-
this.mark(nested, onPage && holds);
|
|
660
|
+
this.mark(nested, item.onPage && holds);
|
|
557
661
|
}
|
|
558
662
|
}
|
|
559
663
|
return;
|
|
@@ -568,6 +672,32 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
568
672
|
}
|
|
569
673
|
render() {
|
|
570
674
|
const point = this.context && this.point;
|
|
675
|
+
const filtering = this.filtering;
|
|
676
|
+
const none = filtering && !this.rowsOf(this.filterPage).some((row) => this.kept(row));
|
|
677
|
+
this.announced = none ? this.noResultsText : '';
|
|
678
|
+
const rows = html `${this.flies || this.drilldown.atRoot
|
|
679
|
+
? nothing
|
|
680
|
+
: html `<div part="header">
|
|
681
|
+
<button
|
|
682
|
+
part="back"
|
|
683
|
+
type="button"
|
|
684
|
+
role="menuitem"
|
|
685
|
+
tabindex="-1"
|
|
686
|
+
aria-label=${this.backLabel}
|
|
687
|
+
@click=${() => this.leave()}
|
|
688
|
+
>
|
|
689
|
+
<svg part="back-mark" data-mirror fill="currentColor" viewBox=${caretLeft.viewBox} aria-hidden="true">
|
|
690
|
+
${unsafeSVG(caretLeft.content)}
|
|
691
|
+
</svg>
|
|
692
|
+
</button>
|
|
693
|
+
<span part="title">${this.drilldown.label}</span>
|
|
694
|
+
</div>`}
|
|
695
|
+
<slot @slotchange=${(event) => this.countRows(event)}></slot>
|
|
696
|
+
${none
|
|
697
|
+
? html `<p part="empty"><slot name="no-results">${this.noResultsText}</slot></p>`
|
|
698
|
+
: this.renderStatus(this.hasRows)}`;
|
|
699
|
+
const busy = this.loading ? 'true' : nothing;
|
|
700
|
+
const label = this.label || nothing;
|
|
571
701
|
return html `<slot name="trigger" @click=${this.handleTriggerClick}></slot>${point
|
|
572
702
|
? html `<span
|
|
573
703
|
id="point"
|
|
@@ -578,31 +708,27 @@ let HelixHeadlessMenu = class HelixHeadlessMenu extends MixinCollection(MixinCon
|
|
|
578
708
|
<div
|
|
579
709
|
part="surface"
|
|
580
710
|
popover="manual"
|
|
581
|
-
role
|
|
582
|
-
aria-busy=${this.
|
|
583
|
-
aria-label=${this.
|
|
711
|
+
role=${this.filterable ? nothing : 'menu'}
|
|
712
|
+
aria-busy=${this.filterable ? nothing : busy}
|
|
713
|
+
aria-label=${this.filterable ? nothing : label}
|
|
584
714
|
data-drilled=${this.flies || this.drilldown.atRoot ? nothing : ''}
|
|
715
|
+
data-filtered=${filtering ? '' : nothing}
|
|
585
716
|
?hidden=${!this.open}
|
|
586
717
|
>
|
|
587
|
-
${this.
|
|
588
|
-
?
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
aria-label=${this.
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
</button>
|
|
602
|
-
<span part="title">${this.drilldown.label}</span>
|
|
603
|
-
</div>`}
|
|
604
|
-
<slot @slotchange=${(event) => this.countRows(event)}></slot>
|
|
605
|
-
${this.renderStatus(this.hasRows)}
|
|
718
|
+
${this.filterable
|
|
719
|
+
? html `<input
|
|
720
|
+
part="search-field"
|
|
721
|
+
type="text"
|
|
722
|
+
autocomplete="off"
|
|
723
|
+
spellcheck="false"
|
|
724
|
+
.value=${this.filter}
|
|
725
|
+
placeholder=${this.filterLabel}
|
|
726
|
+
aria-label=${this.filterLabel}
|
|
727
|
+
aria-autocomplete="list"
|
|
728
|
+
aria-controls="items"
|
|
729
|
+
@input=${this.handleFilterInput}
|
|
730
|
+
/><div id="items" role="menu" aria-busy=${busy} aria-label=${label}>${rows}</div>`
|
|
731
|
+
: rows}
|
|
606
732
|
</div>${this.renderLive(this.hasRows)}`;
|
|
607
733
|
}
|
|
608
734
|
};
|
|
@@ -616,6 +742,19 @@ __decorate([
|
|
|
616
742
|
__decorate([
|
|
617
743
|
property({ reflect: true, type: Boolean })
|
|
618
744
|
], HelixHeadlessMenu.prototype, "context", void 0);
|
|
745
|
+
__decorate([
|
|
746
|
+
property()
|
|
747
|
+
], HelixHeadlessMenu.prototype, "filter", void 0);
|
|
748
|
+
__decorate([
|
|
749
|
+
property({ type: Boolean })
|
|
750
|
+
], HelixHeadlessMenu.prototype, "filterable", void 0);
|
|
751
|
+
__decorate([
|
|
752
|
+
words,
|
|
753
|
+
property({ attribute: 'filter-label' })
|
|
754
|
+
], HelixHeadlessMenu.prototype, "filterLabel", void 0);
|
|
755
|
+
__decorate([
|
|
756
|
+
property({ attribute: false })
|
|
757
|
+
], HelixHeadlessMenu.prototype, "filterMatch", void 0);
|
|
619
758
|
__decorate([
|
|
620
759
|
property()
|
|
621
760
|
], HelixHeadlessMenu.prototype, "label", void 0);
|
|
@@ -623,6 +762,10 @@ __decorate([
|
|
|
623
762
|
words,
|
|
624
763
|
property({ attribute: 'long-press-label' })
|
|
625
764
|
], HelixHeadlessMenu.prototype, "longPressLabel", void 0);
|
|
765
|
+
__decorate([
|
|
766
|
+
words,
|
|
767
|
+
property({ attribute: 'no-results-text' })
|
|
768
|
+
], HelixHeadlessMenu.prototype, "noResultsText", void 0);
|
|
626
769
|
__decorate([
|
|
627
770
|
property({ type: Number })
|
|
628
771
|
], HelixHeadlessMenu.prototype, "offset", void 0);
|
|
@@ -8,7 +8,7 @@ declare const HelixHeadlessNumberInput_base: (new (...args: any[]) => import("@f
|
|
|
8
8
|
* A separate family from `Input`'s `type="number"`. It renders two buttons,
|
|
9
9
|
* binds four keys `Input` does not, and handles clamping and stepping.
|
|
10
10
|
*
|
|
11
|
-
* The field is `type="text"` with `inputmode
|
|
11
|
+
* The field is `type="text"` with an `inputmode` for a number. Tested in a real
|
|
12
12
|
* browser, `type="number"` fails in three ways:
|
|
13
13
|
* - Typing `12e` gives a `.value` of `""`, so the element cannot see what you
|
|
14
14
|
* typed. A `type="text"` field keeps `"12e"`.
|
|
@@ -16,7 +16,11 @@ declare const HelixHeadlessNumberInput_base: (new (...args: any[]) => import("@f
|
|
|
16
16
|
* to 4, when you only meant to scroll the page.
|
|
17
17
|
* - `selectionStart` is `null`, so the caret cannot be managed.
|
|
18
18
|
*
|
|
19
|
-
* `inputmode
|
|
19
|
+
* `inputmode` keeps a numeric keypad on phones, chosen as React Aria's
|
|
20
|
+
* NumberField chooses it: a keypad with a minus key where the number can be
|
|
21
|
+
* negative, and one with a decimal separator where the format shows a
|
|
22
|
+
* fraction. An iPhone has no keypad with a minus key, so a field that can be
|
|
23
|
+
* negative takes the full keyboard there.
|
|
20
24
|
*
|
|
21
25
|
* `format-options` shows the number with `Intl.NumberFormat` in `locale`, as a
|
|
22
26
|
* currency, a percent or a unit, while the field is not being edited. Focus
|
|
@@ -85,6 +89,8 @@ declare const HelixHeadlessNumberInput_base: (new (...args: any[]) => import("@f
|
|
|
85
89
|
* @csspart increment-mark - The icon on the step up button.
|
|
86
90
|
* @csspart label - The label above the field. It is also the grip you drag when `scrub` is set.
|
|
87
91
|
*
|
|
92
|
+
* @cssstate at-max - The number is at `max`, so it cannot step up, as React Aria's NumberField says with `canIncrement`.
|
|
93
|
+
* @cssstate at-min - The number is at `min`, so it cannot step down.
|
|
88
94
|
* @cssstate invalid - The control has an error to show, from the browser's validation or from your `feedback`.
|
|
89
95
|
* @cssstate scrubbing - The label is being dragged to change the value, under `scrub`, as Zag's scrubber says while it is held.
|
|
90
96
|
*/
|
|
@@ -209,12 +215,13 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
209
215
|
* invalid, as it is in a native number field.
|
|
210
216
|
*
|
|
211
217
|
* Unset, a step moves by one, or by one percent under a percent format, and
|
|
212
|
-
* a typed number is rounded to the smallest amount
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* the
|
|
218
|
+
* a typed number is rounded to the smallest amount the format shows, as
|
|
219
|
+
* React Aria's NumberField does: a field showing dollars keeps the cents
|
|
220
|
+
* typed into it, and one with no `format-options` keeps three decimal
|
|
221
|
+
* places. `any` puts the number on no grid, as it does in a native number
|
|
222
|
+
* field: a step moves by one, and any number in range is valid.
|
|
216
223
|
*/
|
|
217
|
-
step?: number;
|
|
224
|
+
step?: number | 'any';
|
|
218
225
|
/**
|
|
219
226
|
* The message the browser shows when the number is off the step grid, with
|
|
220
227
|
* `{below}` and `{above}` filled in with the nearest valid values, written in
|
|
@@ -251,16 +258,30 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
251
258
|
* has no navigator and renders the spinbutton.
|
|
252
259
|
*/
|
|
253
260
|
private readonly touchApple;
|
|
261
|
+
/** The phone the field is on, read once on the client, for the keypad it offers. */
|
|
262
|
+
private readonly phone;
|
|
263
|
+
/**
|
|
264
|
+
* The keypad a phone shows, as React Aria's NumberField chooses it.
|
|
265
|
+
* `numeric` on Android has a minus key and a decimal separator, and
|
|
266
|
+
* `decimal` has no minus key. On an iPhone neither has a minus key, so a
|
|
267
|
+
* number that can be negative takes the full keyboard. Measured before
|
|
268
|
+
* this: `decimal` with `min="-10"`, so a phone could not type the minus.
|
|
269
|
+
*/
|
|
270
|
+
private get keypad();
|
|
254
271
|
private get control();
|
|
255
272
|
/** The value as a number, or `null` where the field holds nothing usable. */
|
|
256
273
|
private get numeric();
|
|
257
274
|
/** The grain last read off the format, with the locale and options it was read for. */
|
|
258
275
|
private grainOf?;
|
|
276
|
+
/** The step as a number, or zero where it is unset or `any`. */
|
|
277
|
+
private get stepSize();
|
|
259
278
|
/** How far one press moves: `step`, or the stride the format implies. */
|
|
260
279
|
private get stride();
|
|
261
280
|
/**
|
|
262
|
-
* The grid a typed number is rounded to: `step` where it is set,
|
|
263
|
-
* otherwise the smallest amount the format shows
|
|
281
|
+
* The grid a typed number is rounded to: `step` where it is set, nothing
|
|
282
|
+
* where it is `any`, and otherwise the smallest amount the format shows.
|
|
283
|
+
* Measured before this: with no step and no format, `2,75` typed in German
|
|
284
|
+
* became 3 on blur.
|
|
264
285
|
*/
|
|
265
286
|
private get grid();
|
|
266
287
|
/**
|
|
@@ -323,6 +344,8 @@ export declare class HelixHeadlessNumberInput extends HelixHeadlessNumberInput_b
|
|
|
323
344
|
* `handleStepperClick`.
|
|
324
345
|
*/
|
|
325
346
|
private readonly handleStepperDown;
|
|
347
|
+
/** Keeps a pressed stepper from taking focus from the field. */
|
|
348
|
+
private readonly handleStepperMouseDown;
|
|
326
349
|
/**
|
|
327
350
|
* Ends a hold. The click that follows a release arrives in the same task,
|
|
328
351
|
* so the press is forgotten after it.
|
|
@@ -12,6 +12,20 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
|
12
12
|
import { decimals, formatGrain, strideOf } from '../grain.js';
|
|
13
13
|
const SCRUB_PITCH = 4;
|
|
14
14
|
const SCRUB_FINE = 16;
|
|
15
|
+
const PLAIN_FORMAT = {};
|
|
16
|
+
const stepConverter = {
|
|
17
|
+
fromAttribute: (text) => {
|
|
18
|
+
if (text === null) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return text.trim().toLowerCase() === 'any' ? 'any' : Number(text);
|
|
22
|
+
},
|
|
23
|
+
toAttribute: (step) => step === undefined ? undefined : String(step),
|
|
24
|
+
};
|
|
25
|
+
const phoneOf = (platform) => {
|
|
26
|
+
const agent = platform?.userAgent ?? '';
|
|
27
|
+
return /iPhone|iPod/u.test(agent) ? 'iphone' : /android/iu.test(agent) ? 'android' : '';
|
|
28
|
+
};
|
|
15
29
|
const PLAIN = /^-?(\d+(\.\d*)?|\.\d+)$/;
|
|
16
30
|
const REPEAT_DELAY = 400;
|
|
17
31
|
const REPEAT_EVERY = 60;
|
|
@@ -53,6 +67,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
53
67
|
this.editing = true;
|
|
54
68
|
};
|
|
55
69
|
this.touchApple = appleTouch(typeof navigator === 'undefined' ? undefined : navigator);
|
|
70
|
+
this.phone = phoneOf(typeof navigator === 'undefined' ? undefined : navigator);
|
|
56
71
|
this.pressStepped = false;
|
|
57
72
|
this.handleStepperDown = (event) => {
|
|
58
73
|
if (event.button !== 0) {
|
|
@@ -60,6 +75,9 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
60
75
|
}
|
|
61
76
|
const button = event.currentTarget;
|
|
62
77
|
const up = button.getAttribute('part') === 'increment';
|
|
78
|
+
if (event.pointerType === 'mouse' && this.control && !this.control.matches(':focus')) {
|
|
79
|
+
this.control.focus({ preventScroll: true });
|
|
80
|
+
}
|
|
63
81
|
clearTimeout(this.repeat);
|
|
64
82
|
this.pressStepped = true;
|
|
65
83
|
button.setPointerCapture?.(event.pointerId);
|
|
@@ -79,6 +97,9 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
79
97
|
this.repeat = setTimeout(step, REPEAT_DELAY);
|
|
80
98
|
}
|
|
81
99
|
};
|
|
100
|
+
this.handleStepperMouseDown = (event) => {
|
|
101
|
+
event.preventDefault();
|
|
102
|
+
};
|
|
82
103
|
this.stopRepeat = () => {
|
|
83
104
|
clearTimeout(this.repeat);
|
|
84
105
|
this.repeat = undefined;
|
|
@@ -217,6 +238,18 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
217
238
|
}
|
|
218
239
|
return formatNumber(parsed, this.locale, this.formatOptions);
|
|
219
240
|
}
|
|
241
|
+
get keypad() {
|
|
242
|
+
const negative = this.min === undefined || this.min < 0;
|
|
243
|
+
const grid = this.grid;
|
|
244
|
+
const fraction = grid === 0 || !Number.isInteger(grid);
|
|
245
|
+
if (this.phone === 'iphone') {
|
|
246
|
+
return negative ? 'text' : fraction ? 'decimal' : 'numeric';
|
|
247
|
+
}
|
|
248
|
+
if (this.phone === 'android') {
|
|
249
|
+
return negative ? 'numeric' : fraction ? 'decimal' : 'numeric';
|
|
250
|
+
}
|
|
251
|
+
return 'numeric';
|
|
252
|
+
}
|
|
220
253
|
get control() {
|
|
221
254
|
return this.partNamed('control');
|
|
222
255
|
}
|
|
@@ -227,15 +260,18 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
227
260
|
}
|
|
228
261
|
return parseNumber(text, this.locale, this.formatOptions);
|
|
229
262
|
}
|
|
263
|
+
get stepSize() {
|
|
264
|
+
return typeof this.step === 'number' ? Math.abs(this.step) || 0 : 0;
|
|
265
|
+
}
|
|
230
266
|
get stride() {
|
|
231
|
-
return
|
|
267
|
+
return this.stepSize || strideOf(this.formatOptions);
|
|
232
268
|
}
|
|
233
269
|
get grid() {
|
|
234
|
-
const step =
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return step || 1;
|
|
270
|
+
const step = this.stepSize;
|
|
271
|
+
if (step || this.step === 'any') {
|
|
272
|
+
return step;
|
|
238
273
|
}
|
|
274
|
+
const options = this.formatOptions ?? PLAIN_FORMAT;
|
|
239
275
|
const [locale, read, grain] = this.grainOf ?? [];
|
|
240
276
|
if (locale === this.locale && read === options && grain !== undefined) {
|
|
241
277
|
return grain;
|
|
@@ -252,6 +288,9 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
252
288
|
}
|
|
253
289
|
onStep(value) {
|
|
254
290
|
const step = this.grid;
|
|
291
|
+
if (!step) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
255
294
|
const anchor = this.min ?? 0;
|
|
256
295
|
const steps = (value - anchor) / step;
|
|
257
296
|
return Math.abs(steps - Math.round(steps)) < 1e-9;
|
|
@@ -259,11 +298,11 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
259
298
|
clamp(value) {
|
|
260
299
|
const step = this.grid;
|
|
261
300
|
const anchor = this.min ?? 0;
|
|
262
|
-
const stepped = anchor + Math.round((value - anchor) / step) * step;
|
|
301
|
+
const stepped = step ? anchor + Math.round((value - anchor) / step) * step : value;
|
|
263
302
|
const floored = this.min === undefined ? stepped : Math.max(this.min, stepped);
|
|
264
303
|
const capped = this.max === undefined ? floored : Math.min(this.max, floored);
|
|
265
304
|
const places = Math.max(decimals(step), decimals(anchor));
|
|
266
|
-
return Number(capped.toFixed(places));
|
|
305
|
+
return step ? Number(capped.toFixed(places)) : capped;
|
|
267
306
|
}
|
|
268
307
|
nudge(steps) {
|
|
269
308
|
if (this.isDisabled || this.readOnly) {
|
|
@@ -387,6 +426,9 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
387
426
|
this.releaseCaret?.();
|
|
388
427
|
this.releaseCaret = undefined;
|
|
389
428
|
this.sync();
|
|
429
|
+
const parsed = this.numeric;
|
|
430
|
+
this.toggleState('at-min', parsed !== null && this.min !== undefined && parsed <= this.min);
|
|
431
|
+
this.toggleState('at-max', parsed !== null && this.max !== undefined && parsed >= this.max);
|
|
390
432
|
}
|
|
391
433
|
get blocksImplicitSubmission() {
|
|
392
434
|
return true;
|
|
@@ -419,6 +461,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
419
461
|
?disabled=${this.isDisabled || this.readOnly || atMin}
|
|
420
462
|
@click=${this.handleStepperClick}
|
|
421
463
|
@lostpointercapture=${this.stopRepeat}
|
|
464
|
+
@mousedown=${this.handleStepperMouseDown}
|
|
422
465
|
@pointercancel=${this.stopRepeat}
|
|
423
466
|
@pointerdown=${this.handleStepperDown}
|
|
424
467
|
@pointerup=${this.stopRepeat}
|
|
@@ -434,7 +477,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
434
477
|
type="text"
|
|
435
478
|
role=${this.touchApple ? nothing : 'spinbutton'}
|
|
436
479
|
aria-roledescription=${this.touchApple || this.roleLabel === '' ? nothing : this.roleLabel}
|
|
437
|
-
inputmode
|
|
480
|
+
inputmode=${this.keypad}
|
|
438
481
|
autocomplete=${this.autocomplete || nothing}
|
|
439
482
|
name=${this.name || nothing}
|
|
440
483
|
placeholder=${this.placeholder || nothing}
|
|
@@ -474,6 +517,7 @@ let HelixHeadlessNumberInput = class HelixHeadlessNumberInput extends MixinContr
|
|
|
474
517
|
?disabled=${this.isDisabled || this.readOnly || atMax}
|
|
475
518
|
@click=${this.handleStepperClick}
|
|
476
519
|
@lostpointercapture=${this.stopRepeat}
|
|
520
|
+
@mousedown=${this.handleStepperMouseDown}
|
|
477
521
|
@pointercancel=${this.stopRepeat}
|
|
478
522
|
@pointerdown=${this.handleStepperDown}
|
|
479
523
|
@pointerup=${this.stopRepeat}
|
|
@@ -542,7 +586,7 @@ __decorate([
|
|
|
542
586
|
property({ reflect: true, type: Boolean })
|
|
543
587
|
], HelixHeadlessNumberInput.prototype, "scrub", void 0);
|
|
544
588
|
__decorate([
|
|
545
|
-
property({
|
|
589
|
+
property({ converter: stepConverter })
|
|
546
590
|
], HelixHeadlessNumberInput.prototype, "step", void 0);
|
|
547
591
|
__decorate([
|
|
548
592
|
words,
|