vira 33.0.0 → 33.1.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/dist/elements/popover/vira-menu-item.element.js +9 -0
- package/dist/elements/popover/vira-popover-trigger.element.d.ts +1 -0
- package/dist/elements/popover/vira-popover-trigger.element.js +8 -2
- package/dist/elements/vira-dropdown.element.d.ts +12 -1
- package/dist/elements/vira-dropdown.element.js +208 -15
- package/dist/util/fuzzy-match.d.ts +16 -0
- package/dist/util/fuzzy-match.js +24 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/menu-helpers.d.ts +4 -1
- package/dist/util/menu-helpers.js +10 -2
- package/dist/util/popover-manager.js +2 -0
- package/package.json +1 -1
|
@@ -207,6 +207,15 @@ export const ViraMenuItem = defineViraElement()({
|
|
|
207
207
|
}, {
|
|
208
208
|
capture: true,
|
|
209
209
|
}),
|
|
210
|
+
listenTo(host, 'keydown', (event) => {
|
|
211
|
+
if (event instanceof KeyboardEvent &&
|
|
212
|
+
event.target === host &&
|
|
213
|
+
(event.key === 'Enter' || event.key === ' ')) {
|
|
214
|
+
/** Prevents Space from scrolling the menu. */
|
|
215
|
+
event.preventDefault();
|
|
216
|
+
host.click();
|
|
217
|
+
}
|
|
218
|
+
}),
|
|
210
219
|
listenTo(host, 'mouseenter', () => {
|
|
211
220
|
if (!inputs.disabled) {
|
|
212
221
|
host.focus();
|
|
@@ -45,5 +45,6 @@ export declare const ViraPopoverTrigger: import("element-vir").DeclarativeElemen
|
|
|
45
45
|
openChange: import("element-vir").DefineElementEvent<boolean>;
|
|
46
46
|
init: import("element-vir").DefineElementEvent<{
|
|
47
47
|
navController: NavController;
|
|
48
|
+
popoverManager: PopoverManager;
|
|
48
49
|
}>;
|
|
49
50
|
}, "vira-popover-trigger-disabled" | "vira-popover-trigger-inside-focus" | "vira-popover-trigger-outside-focus", "vira-popover-trigger-offset-vertical" | "vira-popover-trigger-offset-left" | "vira-popover-trigger-offset-right", readonly ["vira-popover-trigger-trigger", "vira-popover-trigger-popover"], readonly []>;
|
|
@@ -165,8 +165,13 @@ export const ViraPopoverTrigger = defineViraElement()({
|
|
|
165
165
|
it flip upwards when there is not much room below the trigger.
|
|
166
166
|
*/
|
|
167
167
|
min-height: 200px;
|
|
168
|
-
/*
|
|
169
|
-
|
|
168
|
+
/*
|
|
169
|
+
Margins are not included in the percentage. The extra 4px keeps the popover off
|
|
170
|
+
the window's top or bottom edge.
|
|
171
|
+
*/
|
|
172
|
+
max-height: calc(
|
|
173
|
+
100% - ${cssVars['vira-popover-trigger-offset-vertical'].value} - 4px
|
|
174
|
+
);
|
|
170
175
|
width: max-content;
|
|
171
176
|
min-width: calc(
|
|
172
177
|
anchor-size(width) - ${cssVars['vira-popover-trigger-offset-left'].value} -
|
|
@@ -225,6 +230,7 @@ export const ViraPopoverTrigger = defineViraElement()({
|
|
|
225
230
|
dispatch(new events.init({
|
|
226
231
|
detail: {
|
|
227
232
|
navController: state.navController,
|
|
233
|
+
popoverManager: state.popoverManager,
|
|
228
234
|
},
|
|
229
235
|
}));
|
|
230
236
|
},
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type NavController } from 'device-navigation';
|
|
2
3
|
import { type AttributeValues, type HtmlInterpolation } from 'element-vir';
|
|
3
4
|
import { type ViraIconSvg } from '../icons/icon-svg.js';
|
|
5
|
+
import { type PopoverManager } from '../util/popover-manager.js';
|
|
4
6
|
import { type ViraDropdownOption, type ViraDropdownOptionGroup } from '../util/vira-dropdown-option.js';
|
|
5
7
|
/**
|
|
6
8
|
* A dropdown element that uses popover menus.
|
|
@@ -26,6 +28,11 @@ export declare const ViraDropdown: import("element-vir").DeclarativeElementDefin
|
|
|
26
28
|
* multiple.
|
|
27
29
|
*/
|
|
28
30
|
isMultiSelect: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Replaces the selection text with a text input. Opening the dropdown focuses it and typing
|
|
33
|
+
* into it filters the options to those whose labels fuzzy match the typed text.
|
|
34
|
+
*/
|
|
35
|
+
isSearchable: boolean;
|
|
29
36
|
icon: ViraIconSvg;
|
|
30
37
|
selectionPrefix: string;
|
|
31
38
|
isDisabled: boolean;
|
|
@@ -53,9 +60,13 @@ export declare const ViraDropdown: import("element-vir").DeclarativeElementDefin
|
|
|
53
60
|
* provided.
|
|
54
61
|
*/
|
|
55
62
|
randomId: string;
|
|
63
|
+
/** The text typed into the search input while the dropdown is open. */
|
|
64
|
+
searchText: string;
|
|
65
|
+
navController: undefined | NavController;
|
|
66
|
+
popoverManager: undefined | PopoverManager;
|
|
56
67
|
}, {
|
|
57
68
|
/** Emits all currently selected values. */
|
|
58
69
|
selectedValuesChange: import("element-vir").DefineElementEvent<string[]>;
|
|
59
70
|
/** `true` when the popover just opened, `false` when it just closed. */
|
|
60
71
|
openChange: import("element-vir").DefineElementEvent<boolean>;
|
|
61
|
-
}, "vira-dropdown-", "vira-dropdown-", readonly [], readonly ["leadingIcon", "prefixText", "trigger"]>;
|
|
72
|
+
}, "vira-dropdown-", "vira-dropdown-", readonly [], readonly ["leadingIcon", "prefixText", "searchInput", "trigger"]>;
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { check } from '@augment-vir/assert';
|
|
2
2
|
import { filterMap, randomString } from '@augment-vir/common';
|
|
3
3
|
import { extractEventTarget } from '@augment-vir/web';
|
|
4
|
+
import { nav, NavDirection } from 'device-navigation';
|
|
4
5
|
import { attributes, classMap, css, defineElementEvent, html, ifDefined, listen, nothing, testId, } from 'element-vir';
|
|
5
6
|
import { ChevronUp16Icon } from '../icons/index.js';
|
|
7
|
+
import { createFocusStyles } from '../styles/focus.js';
|
|
6
8
|
import { viraFormCssVars } from '../styles/form-styles.js';
|
|
7
9
|
import { ViraSize, viraSizeHeights } from '../styles/form-variants.js';
|
|
8
|
-
import { noUserSelect, viraAnimationDurations } from '../styles/index.js';
|
|
10
|
+
import { noNativeFormStyles, noUserSelect, viraAnimationDurations } from '../styles/index.js';
|
|
9
11
|
import { defineViraElement } from '../util/define-vira-element.js';
|
|
12
|
+
import { fuzzyMatch } from '../util/fuzzy-match.js';
|
|
10
13
|
import { renderMenuItemEntries } from '../util/menu-helpers.js';
|
|
11
14
|
import { isViraDropdownOptionGroup, } from '../util/vira-dropdown-option.js';
|
|
12
15
|
import { ViraMenuItem } from './popover/vira-menu-item.element.js';
|
|
@@ -25,6 +28,7 @@ export const ViraDropdown = defineViraElement()({
|
|
|
25
28
|
testIds: [
|
|
26
29
|
'leadingIcon',
|
|
27
30
|
'prefixText',
|
|
31
|
+
'searchInput',
|
|
28
32
|
'trigger',
|
|
29
33
|
],
|
|
30
34
|
styles: css `
|
|
@@ -46,6 +50,38 @@ export const ViraDropdown = defineViraElement()({
|
|
|
46
50
|
white-space: nowrap;
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
.search-input {
|
|
54
|
+
${noNativeFormStyles};
|
|
55
|
+
flex-grow: 1;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
align-self: stretch;
|
|
58
|
+
text-overflow: ellipsis;
|
|
59
|
+
cursor: inherit;
|
|
60
|
+
user-select: text;
|
|
61
|
+
-webkit-user-select: text;
|
|
62
|
+
outline: none;
|
|
63
|
+
|
|
64
|
+
&::placeholder {
|
|
65
|
+
color: inherit;
|
|
66
|
+
opacity: 0.4;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.open .search-input {
|
|
71
|
+
cursor: text;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.dropdown-trigger:has(.search-input:focus-visible:not([disabled])) {
|
|
75
|
+
position: relative;
|
|
76
|
+
|
|
77
|
+
&::after {
|
|
78
|
+
${createFocusStyles({
|
|
79
|
+
elementBorderSize: '1px',
|
|
80
|
+
noNesting: true,
|
|
81
|
+
})}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
49
85
|
.trigger-icon {
|
|
50
86
|
width: 24px;
|
|
51
87
|
aspect-ratio: 1;
|
|
@@ -155,9 +191,13 @@ export const ViraDropdown = defineViraElement()({
|
|
|
155
191
|
* provided.
|
|
156
192
|
*/
|
|
157
193
|
randomId: randomString(32),
|
|
194
|
+
/** The text typed into the search input while the dropdown is open. */
|
|
195
|
+
searchText: '',
|
|
196
|
+
navController: undefined,
|
|
197
|
+
popoverManager: undefined,
|
|
158
198
|
};
|
|
159
199
|
},
|
|
160
|
-
render({ state, inputs, dispatch, events, updateState, testIds }) {
|
|
200
|
+
render({ state, inputs, dispatch, events, updateState, testIds, host }) {
|
|
161
201
|
const flatOptions = inputs.options.flatMap((entry) => {
|
|
162
202
|
return isViraDropdownOptionGroup(entry) ? entry.options : [entry];
|
|
163
203
|
});
|
|
@@ -197,6 +237,42 @@ export const ViraDropdown = defineViraElement()({
|
|
|
197
237
|
detail: newSelectedValues,
|
|
198
238
|
}));
|
|
199
239
|
}
|
|
240
|
+
const filteredOptions = state.searchText
|
|
241
|
+
? filterMap(inputs.options, (entry) => {
|
|
242
|
+
return isViraDropdownOptionGroup(entry)
|
|
243
|
+
? {
|
|
244
|
+
...entry,
|
|
245
|
+
options: entry.options.filter((option) => {
|
|
246
|
+
return fuzzyMatch({
|
|
247
|
+
search: state.searchText,
|
|
248
|
+
text: option.label,
|
|
249
|
+
});
|
|
250
|
+
}),
|
|
251
|
+
}
|
|
252
|
+
: entry;
|
|
253
|
+
}, (entry) => {
|
|
254
|
+
return isViraDropdownOptionGroup(entry)
|
|
255
|
+
? !!entry.options.length
|
|
256
|
+
: fuzzyMatch({
|
|
257
|
+
search: state.searchText,
|
|
258
|
+
text: entry.label,
|
|
259
|
+
});
|
|
260
|
+
})
|
|
261
|
+
: inputs.options;
|
|
262
|
+
const filteredFlatOptions = filteredOptions.flatMap((entry) => {
|
|
263
|
+
return isViraDropdownOptionGroup(entry) ? entry.options : [entry];
|
|
264
|
+
});
|
|
265
|
+
function findSearchInput() {
|
|
266
|
+
const searchInput = host.shadowRoot.querySelector('.search-input');
|
|
267
|
+
return searchInput instanceof HTMLInputElement ? searchInput : undefined;
|
|
268
|
+
}
|
|
269
|
+
function clearSearch(searchInput) {
|
|
270
|
+
/** Set directly because the `.value` binding skips values it has already committed. */
|
|
271
|
+
searchInput.value = '';
|
|
272
|
+
updateState({
|
|
273
|
+
searchText: '',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
200
276
|
function toggleGroup(group) {
|
|
201
277
|
const enabledGroupOptions = group.options.filter((option) => !option.disabled);
|
|
202
278
|
const isGroupSelected = enabledGroupOptions.every((option) => {
|
|
@@ -220,6 +296,7 @@ export const ViraDropdown = defineViraElement()({
|
|
|
220
296
|
disablePointerStyles: true,
|
|
221
297
|
})}
|
|
222
298
|
class="option-group-label"
|
|
299
|
+
${state.navController ? nav(state.navController) : nothing}
|
|
223
300
|
${listen('click', () => {
|
|
224
301
|
toggleGroup(group);
|
|
225
302
|
})}
|
|
@@ -232,7 +309,7 @@ export const ViraDropdown = defineViraElement()({
|
|
|
232
309
|
`;
|
|
233
310
|
}
|
|
234
311
|
/** Drag-select actions in the same order as the rendered menu items. */
|
|
235
|
-
const menuItemActions =
|
|
312
|
+
const menuItemActions = filteredOptions.flatMap((entry) => {
|
|
236
313
|
const options = isViraDropdownOptionGroup(entry) ? entry.options : [entry];
|
|
237
314
|
const optionActions = options.map((option) => {
|
|
238
315
|
return option.disabled
|
|
@@ -260,9 +337,9 @@ export const ViraDropdown = defineViraElement()({
|
|
|
260
337
|
disabled: option.disabled,
|
|
261
338
|
selected: selectedOptions.includes(option),
|
|
262
339
|
};
|
|
263
|
-
}));
|
|
340
|
+
}), state.navController);
|
|
264
341
|
}
|
|
265
|
-
const isMenuShown = state.isOpen && (!!
|
|
342
|
+
const isMenuShown = state.isOpen && (!!filteredFlatOptions.length || !!inputs.noOptionsText);
|
|
266
343
|
const menuTemplate = html `
|
|
267
344
|
<${ViraMenu.assign({
|
|
268
345
|
cornerStyle: ViraMenuCornerStyle.Round,
|
|
@@ -283,8 +360,8 @@ export const ViraDropdown = defineViraElement()({
|
|
|
283
360
|
}
|
|
284
361
|
})}
|
|
285
362
|
>
|
|
286
|
-
${
|
|
287
|
-
?
|
|
363
|
+
${filteredFlatOptions.length
|
|
364
|
+
? filteredOptions.map((entry) => {
|
|
288
365
|
return isViraDropdownOptionGroup(entry)
|
|
289
366
|
? html `
|
|
290
367
|
${renderGroupLabel(entry)} ${renderOptionEntries(entry.options)}
|
|
@@ -305,6 +382,12 @@ export const ViraDropdown = defineViraElement()({
|
|
|
305
382
|
vertical: -1,
|
|
306
383
|
right: 24,
|
|
307
384
|
},
|
|
385
|
+
})}
|
|
386
|
+
${listen(ViraPopoverTrigger.events.init, (event) => {
|
|
387
|
+
updateState({
|
|
388
|
+
navController: event.detail.navController,
|
|
389
|
+
popoverManager: event.detail.popoverManager,
|
|
390
|
+
});
|
|
308
391
|
})}
|
|
309
392
|
${listen(ViraPopoverTrigger.events.openChange, (event) => {
|
|
310
393
|
if (state.isOpen !== event.detail) {
|
|
@@ -315,6 +398,37 @@ export const ViraDropdown = defineViraElement()({
|
|
|
315
398
|
updateState({
|
|
316
399
|
isOpen: event.detail,
|
|
317
400
|
});
|
|
401
|
+
const searchInput = findSearchInput();
|
|
402
|
+
if (!searchInput) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
else if (event.detail) {
|
|
406
|
+
searchInput.focus();
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
clearSearch(searchInput);
|
|
410
|
+
}
|
|
411
|
+
})}
|
|
412
|
+
${listen('keydown', (event) => {
|
|
413
|
+
const searchInput = findSearchInput();
|
|
414
|
+
const isSearchKey = event.key === 'Backspace' ||
|
|
415
|
+
(event.key.length === 1 &&
|
|
416
|
+
event.key !== ' ' &&
|
|
417
|
+
!event.ctrlKey &&
|
|
418
|
+
!event.metaKey &&
|
|
419
|
+
!event.altKey);
|
|
420
|
+
if (!searchInput || !isSearchKey) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (!state.isOpen) {
|
|
424
|
+
clearSearch(searchInput);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Focusing during keydown makes the browser type the pressed key into the
|
|
428
|
+
* search input, even when the key was pressed on the trigger or a menu item.
|
|
429
|
+
*/
|
|
430
|
+
searchInput.focus();
|
|
431
|
+
state.popoverManager?.show();
|
|
318
432
|
})}
|
|
319
433
|
${listen('mouseup', () => {
|
|
320
434
|
if (state.shouldSelectOnMouseUp) {
|
|
@@ -342,14 +456,93 @@ export const ViraDropdown = defineViraElement()({
|
|
|
342
456
|
})}
|
|
343
457
|
>
|
|
344
458
|
${leadingIconTemplate}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
459
|
+
${inputs.isSearchable
|
|
460
|
+
? html `
|
|
461
|
+
${state.isOpen ? nothing : prefixTemplate}
|
|
462
|
+
<input
|
|
463
|
+
class="search-input"
|
|
464
|
+
${testId(testIds.searchInput)}
|
|
465
|
+
tabindex="-1"
|
|
466
|
+
autocomplete="off"
|
|
467
|
+
spellcheck="false"
|
|
468
|
+
aria-label=${ifDefined((check.isString(inputs.label) && inputs.label) || undefined)}
|
|
469
|
+
?disabled=${!!inputs.isDisabled}
|
|
470
|
+
title=${ifDefined(shouldUsePlaceholder || state.isOpen
|
|
471
|
+
? undefined
|
|
472
|
+
: selectionDisplay)}
|
|
473
|
+
placeholder=${state.isOpen && !shouldUsePlaceholder
|
|
474
|
+
? selectionDisplay
|
|
475
|
+
: inputs.placeholder || ''}
|
|
476
|
+
.value=${state.isOpen
|
|
477
|
+
? state.searchText
|
|
478
|
+
: shouldUsePlaceholder
|
|
479
|
+
? ''
|
|
480
|
+
: selectionDisplay}
|
|
481
|
+
${listen('mousedown', (event) => {
|
|
482
|
+
/** Keeps the popover open when clicking into the input. */
|
|
483
|
+
if (state.isOpen) {
|
|
484
|
+
event.stopPropagation();
|
|
485
|
+
}
|
|
486
|
+
})}
|
|
487
|
+
${listen('input', (event) => {
|
|
488
|
+
updateState({
|
|
489
|
+
searchText: extractEventTarget(event, HTMLInputElement)
|
|
490
|
+
.value,
|
|
491
|
+
});
|
|
492
|
+
state.popoverManager?.show();
|
|
493
|
+
})}
|
|
494
|
+
${listen('keydown', (event) => {
|
|
495
|
+
if (!state.isOpen) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
else if (event.key === 'ArrowDown' ||
|
|
499
|
+
event.key === 'ArrowUp') {
|
|
500
|
+
event.preventDefault();
|
|
501
|
+
state.navController?.navigate({
|
|
502
|
+
direction: event.key === 'ArrowDown'
|
|
503
|
+
? NavDirection.Down
|
|
504
|
+
: NavDirection.Up,
|
|
505
|
+
allowWrapping: false,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
else if (event.key === 'Tab' && !event.shiftKey) {
|
|
509
|
+
/**
|
|
510
|
+
* Without this, browsers move focus from this input past
|
|
511
|
+
* the menu items (sometimes closing the menu).
|
|
512
|
+
*/
|
|
513
|
+
const firstMenuItem = Array.from(host.shadowRoot.querySelectorAll(ViraMenuItem.tagName)).find((menuItem) => menuItem.tabIndex >= 0);
|
|
514
|
+
if (firstMenuItem) {
|
|
515
|
+
event.preventDefault();
|
|
516
|
+
firstMenuItem.focus();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else if (event.key === 'Enter') {
|
|
520
|
+
const firstOption = filteredFlatOptions.find((option) => !option.disabled);
|
|
521
|
+
if (!firstOption) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
event.preventDefault();
|
|
525
|
+
selectOption(firstOption);
|
|
526
|
+
if (inputs.isMultiSelect) {
|
|
527
|
+
clearSearch(extractEventTarget(event, HTMLInputElement));
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
state.popoverManager?.hide();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
})}
|
|
534
|
+
/>
|
|
535
|
+
`
|
|
536
|
+
: html `
|
|
537
|
+
<span
|
|
538
|
+
class="selection-display ${classMap({
|
|
539
|
+
'using-placeholder': shouldUsePlaceholder,
|
|
540
|
+
})}"
|
|
541
|
+
title=${ifDefined(shouldUsePlaceholder ? undefined : selectionDisplay)}
|
|
542
|
+
>
|
|
543
|
+
${prefixTemplate} ${selectionDisplay}
|
|
544
|
+
</span>
|
|
545
|
+
`}
|
|
353
546
|
|
|
354
547
|
<span class="trigger-icon-wrapper">
|
|
355
548
|
<${ViraIcon.assign({
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if every non-whitespace character of `search` appears in `text`, in order, ignoring case.
|
|
3
|
+
* An empty `search` matches everything.
|
|
4
|
+
*
|
|
5
|
+
* @category Internal
|
|
6
|
+
* @example
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* fuzzyMatch({search: 'opa', text: 'Option A'}); // true
|
|
10
|
+
* fuzzyMatch({search: 'apo', text: 'Option A'}); // false
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function fuzzyMatch({ search, text, }: Readonly<{
|
|
14
|
+
search: string;
|
|
15
|
+
text: string;
|
|
16
|
+
}>): boolean;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if every non-whitespace character of `search` appears in `text`, in order, ignoring case.
|
|
3
|
+
* An empty `search` matches everything.
|
|
4
|
+
*
|
|
5
|
+
* @category Internal
|
|
6
|
+
* @example
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* fuzzyMatch({search: 'opa', text: 'Option A'}); // true
|
|
10
|
+
* fuzzyMatch({search: 'apo', text: 'Option A'}); // false
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function fuzzyMatch({ search, text, }) {
|
|
14
|
+
const lowerText = text.toLowerCase();
|
|
15
|
+
return (search
|
|
16
|
+
.replaceAll(/\s/g, '')
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.split('')
|
|
19
|
+
.reduce((textIndex, character) => {
|
|
20
|
+
return textIndex < 0
|
|
21
|
+
? textIndex
|
|
22
|
+
: lowerText.indexOf(character, textIndex) + 1 || -1;
|
|
23
|
+
}, 0) >= 0);
|
|
24
|
+
}
|
package/dist/util/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './anchored-popover.js';
|
|
|
2
2
|
export * from './define-table.js';
|
|
3
3
|
export * from './define-vira-element.js';
|
|
4
4
|
export * from './dynamic-element.js';
|
|
5
|
+
export * from './fuzzy-match.js';
|
|
5
6
|
export * from './menu-helpers.js';
|
|
6
7
|
export * from './overflow-observer.js';
|
|
7
8
|
export * from './popover-manager.js';
|
package/dist/util/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export * from './anchored-popover.js';
|
|
|
2
2
|
export * from './define-table.js';
|
|
3
3
|
export * from './define-vira-element.js';
|
|
4
4
|
export * from './dynamic-element.js';
|
|
5
|
+
export * from './fuzzy-match.js';
|
|
5
6
|
export * from './menu-helpers.js';
|
|
6
7
|
export * from './overflow-observer.js';
|
|
7
8
|
export * from './popover-manager.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type MaybePromise, type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type NavController } from 'device-navigation';
|
|
2
3
|
import { type HtmlInterpolation } from 'element-vir';
|
|
3
4
|
import { ViraMenuItem } from '../elements/popover/vira-menu-item.element.js';
|
|
4
5
|
/**
|
|
@@ -42,4 +43,6 @@ export type MenuItemClickCallback = (params: Readonly<{
|
|
|
42
43
|
*
|
|
43
44
|
* @category Popover
|
|
44
45
|
*/
|
|
45
|
-
export declare function renderMenuItemEntries(items: ReadonlyArray<Readonly<ViraMenuItemEntry
|
|
46
|
+
export declare function renderMenuItemEntries(items: ReadonlyArray<Readonly<ViraMenuItemEntry>>,
|
|
47
|
+
/** Enables arrow key navigation between the menu items. */
|
|
48
|
+
navController?: NavController | undefined): import("lit-html").HTMLTemplateResult[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { filterMap, joinWithFinalConjunction, } from '@augment-vir/common';
|
|
2
|
-
import {
|
|
2
|
+
import { nav } from 'device-navigation';
|
|
3
|
+
import { html, listen, nothing } from 'element-vir';
|
|
3
4
|
import { ViraMenuItem } from '../elements/popover/vira-menu-item.element.js';
|
|
4
5
|
/**
|
|
5
6
|
* Verifies that all items have unique ids.
|
|
@@ -26,12 +27,19 @@ export function assertUniqueIdProps(items) {
|
|
|
26
27
|
*
|
|
27
28
|
* @category Popover
|
|
28
29
|
*/
|
|
29
|
-
export function renderMenuItemEntries(items
|
|
30
|
+
export function renderMenuItemEntries(items,
|
|
31
|
+
/** Enables arrow key navigation between the menu items. */
|
|
32
|
+
navController) {
|
|
30
33
|
return filterMap(items, (item, index) => {
|
|
31
34
|
return html `
|
|
32
35
|
<${ViraMenuItem.assign({
|
|
33
36
|
...item,
|
|
34
37
|
})}
|
|
38
|
+
${navController
|
|
39
|
+
? nav(navController, {
|
|
40
|
+
disabled: item.disabled,
|
|
41
|
+
})
|
|
42
|
+
: nothing}
|
|
35
43
|
${listen('click', async (event) => {
|
|
36
44
|
if (item.disabled) {
|
|
37
45
|
event.stopImmediatePropagation();
|