vira 33.0.0 → 33.2.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.
@@ -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
- /* Margins are not included in the percentage. */
169
- max-height: calc(100% - ${cssVars['vira-popover-trigger-offset-vertical'].value});
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,56 @@ export const ViraDropdown = defineViraElement()({
46
50
  white-space: nowrap;
47
51
  }
48
52
 
53
+ .option-label {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ gap: 8px;
57
+ max-width: 100%;
58
+ vertical-align: bottom;
59
+ }
60
+
61
+ .option-icon {
62
+ flex-shrink: 0;
63
+ }
64
+
65
+ .option-label-text {
66
+ overflow: hidden;
67
+ text-overflow: ellipsis;
68
+ white-space: nowrap;
69
+ }
70
+
71
+ .search-input {
72
+ ${noNativeFormStyles};
73
+ flex-grow: 1;
74
+ min-width: 0;
75
+ align-self: stretch;
76
+ text-overflow: ellipsis;
77
+ cursor: inherit;
78
+ user-select: text;
79
+ -webkit-user-select: text;
80
+ outline: none;
81
+
82
+ &::placeholder {
83
+ color: inherit;
84
+ opacity: 0.4;
85
+ }
86
+ }
87
+
88
+ .open .search-input {
89
+ cursor: text;
90
+ }
91
+
92
+ .dropdown-trigger:has(.search-input:focus-visible:not([disabled])) {
93
+ position: relative;
94
+
95
+ &::after {
96
+ ${createFocusStyles({
97
+ elementBorderSize: '1px',
98
+ noNesting: true,
99
+ })}
100
+ }
101
+ }
102
+
49
103
  .trigger-icon {
50
104
  width: 24px;
51
105
  aspect-ratio: 1;
@@ -155,9 +209,13 @@ export const ViraDropdown = defineViraElement()({
155
209
  * provided.
156
210
  */
157
211
  randomId: randomString(32),
212
+ /** The text typed into the search input while the dropdown is open. */
213
+ searchText: '',
214
+ navController: undefined,
215
+ popoverManager: undefined,
158
216
  };
159
217
  },
160
- render({ state, inputs, dispatch, events, updateState, testIds }) {
218
+ render({ state, inputs, dispatch, events, updateState, testIds, host }) {
161
219
  const flatOptions = inputs.options.flatMap((entry) => {
162
220
  return isViraDropdownOptionGroup(entry) ? entry.options : [entry];
163
221
  });
@@ -184,6 +242,29 @@ export const ViraDropdown = defineViraElement()({
184
242
  : inputs.isMultiSelect && selectedOptions.length > 1
185
243
  ? `${selectedOptions.length} Selected`
186
244
  : selectedOptions[0]?.label || '';
245
+ function renderOptionLabel(option) {
246
+ return option.icon || option.labelTemplate
247
+ ? html `
248
+ <span class="option-label">
249
+ ${option.icon
250
+ ? html `
251
+ <${ViraIcon.assign({
252
+ icon: option.icon,
253
+ })}
254
+ class="option-icon"
255
+ ></${ViraIcon}>
256
+ `
257
+ : nothing}
258
+ <span class="option-label-text">
259
+ ${option.labelTemplate ?? option.label}
260
+ </span>
261
+ </span>
262
+ `
263
+ : option.label;
264
+ }
265
+ const selectionTemplate = check.isLengthExactly(selectedOptions, 1)
266
+ ? renderOptionLabel(selectedOptions[0])
267
+ : selectionDisplay;
187
268
  function selectOption(option) {
188
269
  const newSelectedValues = inputs.isMultiSelect
189
270
  ? selectedOptions.includes(option)
@@ -197,6 +278,42 @@ export const ViraDropdown = defineViraElement()({
197
278
  detail: newSelectedValues,
198
279
  }));
199
280
  }
281
+ const filteredOptions = state.searchText
282
+ ? filterMap(inputs.options, (entry) => {
283
+ return isViraDropdownOptionGroup(entry)
284
+ ? {
285
+ ...entry,
286
+ options: entry.options.filter((option) => {
287
+ return fuzzyMatch({
288
+ search: state.searchText,
289
+ text: option.label,
290
+ });
291
+ }),
292
+ }
293
+ : entry;
294
+ }, (entry) => {
295
+ return isViraDropdownOptionGroup(entry)
296
+ ? !!entry.options.length
297
+ : fuzzyMatch({
298
+ search: state.searchText,
299
+ text: entry.label,
300
+ });
301
+ })
302
+ : inputs.options;
303
+ const filteredFlatOptions = filteredOptions.flatMap((entry) => {
304
+ return isViraDropdownOptionGroup(entry) ? entry.options : [entry];
305
+ });
306
+ function findSearchInput() {
307
+ const searchInput = host.shadowRoot.querySelector('.search-input');
308
+ return searchInput instanceof HTMLInputElement ? searchInput : undefined;
309
+ }
310
+ function clearSearch(searchInput) {
311
+ /** Set directly because the `.value` binding skips values it has already committed. */
312
+ searchInput.value = '';
313
+ updateState({
314
+ searchText: '',
315
+ });
316
+ }
200
317
  function toggleGroup(group) {
201
318
  const enabledGroupOptions = group.options.filter((option) => !option.disabled);
202
319
  const isGroupSelected = enabledGroupOptions.every((option) => {
@@ -220,6 +337,7 @@ export const ViraDropdown = defineViraElement()({
220
337
  disablePointerStyles: true,
221
338
  })}
222
339
  class="option-group-label"
340
+ ${state.navController ? nav(state.navController) : nothing}
223
341
  ${listen('click', () => {
224
342
  toggleGroup(group);
225
343
  })}
@@ -232,7 +350,7 @@ export const ViraDropdown = defineViraElement()({
232
350
  `;
233
351
  }
234
352
  /** Drag-select actions in the same order as the rendered menu items. */
235
- const menuItemActions = inputs.options.flatMap((entry) => {
353
+ const menuItemActions = filteredOptions.flatMap((entry) => {
236
354
  const options = isViraDropdownOptionGroup(entry) ? entry.options : [entry];
237
355
  const optionActions = options.map((option) => {
238
356
  return option.disabled
@@ -253,16 +371,16 @@ export const ViraDropdown = defineViraElement()({
253
371
  function renderOptionEntries(options) {
254
372
  return renderMenuItemEntries(options.map((option) => {
255
373
  return {
256
- content: option.label,
374
+ content: renderOptionLabel(option),
257
375
  onClick() {
258
376
  selectOption(option);
259
377
  },
260
378
  disabled: option.disabled,
261
379
  selected: selectedOptions.includes(option),
262
380
  };
263
- }));
381
+ }), state.navController);
264
382
  }
265
- const isMenuShown = state.isOpen && (!!flatOptions.length || !!inputs.noOptionsText);
383
+ const isMenuShown = state.isOpen && (!!filteredFlatOptions.length || !!inputs.noOptionsText);
266
384
  const menuTemplate = html `
267
385
  <${ViraMenu.assign({
268
386
  cornerStyle: ViraMenuCornerStyle.Round,
@@ -283,8 +401,8 @@ export const ViraDropdown = defineViraElement()({
283
401
  }
284
402
  })}
285
403
  >
286
- ${flatOptions.length
287
- ? inputs.options.map((entry) => {
404
+ ${filteredFlatOptions.length
405
+ ? filteredOptions.map((entry) => {
288
406
  return isViraDropdownOptionGroup(entry)
289
407
  ? html `
290
408
  ${renderGroupLabel(entry)} ${renderOptionEntries(entry.options)}
@@ -305,6 +423,12 @@ export const ViraDropdown = defineViraElement()({
305
423
  vertical: -1,
306
424
  right: 24,
307
425
  },
426
+ })}
427
+ ${listen(ViraPopoverTrigger.events.init, (event) => {
428
+ updateState({
429
+ navController: event.detail.navController,
430
+ popoverManager: event.detail.popoverManager,
431
+ });
308
432
  })}
309
433
  ${listen(ViraPopoverTrigger.events.openChange, (event) => {
310
434
  if (state.isOpen !== event.detail) {
@@ -315,6 +439,37 @@ export const ViraDropdown = defineViraElement()({
315
439
  updateState({
316
440
  isOpen: event.detail,
317
441
  });
442
+ const searchInput = findSearchInput();
443
+ if (!searchInput) {
444
+ return;
445
+ }
446
+ else if (event.detail) {
447
+ searchInput.focus();
448
+ }
449
+ else {
450
+ clearSearch(searchInput);
451
+ }
452
+ })}
453
+ ${listen('keydown', (event) => {
454
+ const searchInput = findSearchInput();
455
+ const isSearchKey = event.key === 'Backspace' ||
456
+ (event.key.length === 1 &&
457
+ event.key !== ' ' &&
458
+ !event.ctrlKey &&
459
+ !event.metaKey &&
460
+ !event.altKey);
461
+ if (!searchInput || !isSearchKey) {
462
+ return;
463
+ }
464
+ if (!state.isOpen) {
465
+ clearSearch(searchInput);
466
+ }
467
+ /**
468
+ * Focusing during keydown makes the browser type the pressed key into the
469
+ * search input, even when the key was pressed on the trigger or a menu item.
470
+ */
471
+ searchInput.focus();
472
+ state.popoverManager?.show();
318
473
  })}
319
474
  ${listen('mouseup', () => {
320
475
  if (state.shouldSelectOnMouseUp) {
@@ -342,14 +497,93 @@ export const ViraDropdown = defineViraElement()({
342
497
  })}
343
498
  >
344
499
  ${leadingIconTemplate}
345
- <span
346
- class="selection-display ${classMap({
347
- 'using-placeholder': shouldUsePlaceholder,
348
- })}"
349
- title=${ifDefined(shouldUsePlaceholder ? undefined : selectionDisplay)}
350
- >
351
- ${prefixTemplate} ${selectionDisplay}
352
- </span>
500
+ ${inputs.isSearchable
501
+ ? html `
502
+ ${state.isOpen ? nothing : prefixTemplate}
503
+ <input
504
+ class="search-input"
505
+ ${testId(testIds.searchInput)}
506
+ tabindex="-1"
507
+ autocomplete="off"
508
+ spellcheck="false"
509
+ aria-label=${ifDefined((check.isString(inputs.label) && inputs.label) || undefined)}
510
+ ?disabled=${!!inputs.isDisabled}
511
+ title=${ifDefined(shouldUsePlaceholder || state.isOpen
512
+ ? undefined
513
+ : selectionDisplay)}
514
+ placeholder=${state.isOpen && !shouldUsePlaceholder
515
+ ? selectionDisplay
516
+ : inputs.placeholder || ''}
517
+ .value=${state.isOpen
518
+ ? state.searchText
519
+ : shouldUsePlaceholder
520
+ ? ''
521
+ : selectionDisplay}
522
+ ${listen('mousedown', (event) => {
523
+ /** Keeps the popover open when clicking into the input. */
524
+ if (state.isOpen) {
525
+ event.stopPropagation();
526
+ }
527
+ })}
528
+ ${listen('input', (event) => {
529
+ updateState({
530
+ searchText: extractEventTarget(event, HTMLInputElement)
531
+ .value,
532
+ });
533
+ state.popoverManager?.show();
534
+ })}
535
+ ${listen('keydown', (event) => {
536
+ if (!state.isOpen) {
537
+ return;
538
+ }
539
+ else if (event.key === 'ArrowDown' ||
540
+ event.key === 'ArrowUp') {
541
+ event.preventDefault();
542
+ state.navController?.navigate({
543
+ direction: event.key === 'ArrowDown'
544
+ ? NavDirection.Down
545
+ : NavDirection.Up,
546
+ allowWrapping: false,
547
+ });
548
+ }
549
+ else if (event.key === 'Tab' && !event.shiftKey) {
550
+ /**
551
+ * Without this, browsers move focus from this input past
552
+ * the menu items (sometimes closing the menu).
553
+ */
554
+ const firstMenuItem = Array.from(host.shadowRoot.querySelectorAll(ViraMenuItem.tagName)).find((menuItem) => menuItem.tabIndex >= 0);
555
+ if (firstMenuItem) {
556
+ event.preventDefault();
557
+ firstMenuItem.focus();
558
+ }
559
+ }
560
+ else if (event.key === 'Enter') {
561
+ const firstOption = filteredFlatOptions.find((option) => !option.disabled);
562
+ if (!firstOption) {
563
+ return;
564
+ }
565
+ event.preventDefault();
566
+ selectOption(firstOption);
567
+ if (inputs.isMultiSelect) {
568
+ clearSearch(extractEventTarget(event, HTMLInputElement));
569
+ }
570
+ else {
571
+ state.popoverManager?.hide();
572
+ }
573
+ }
574
+ })}
575
+ />
576
+ `
577
+ : html `
578
+ <span
579
+ class="selection-display ${classMap({
580
+ 'using-placeholder': shouldUsePlaceholder,
581
+ })}"
582
+ title=${ifDefined(shouldUsePlaceholder ? undefined : selectionDisplay)}
583
+ >
584
+ ${prefixTemplate} ${selectionTemplate}
585
+ </span>
586
+ `}
353
587
 
354
588
  <span class="trigger-icon-wrapper">
355
589
  <${ViraIcon.assign({
@@ -364,7 +598,7 @@ export const ViraDropdown = defineViraElement()({
364
598
  `;
365
599
  const contentTemplate = inputs.isReadonly
366
600
  ? html `
367
- <span class="readonly-value">${selectionDisplay}</span>
601
+ <span class="readonly-value">${selectionTemplate}</span>
368
602
  `
369
603
  : triggerTemplate;
370
604
  if (inputs.label) {
@@ -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
+ }
@@ -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';
@@ -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>>): import("lit-html").HTMLTemplateResult[];
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 { html, listen } from 'element-vir';
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();
@@ -140,6 +140,8 @@ const anchorListeners = {
140
140
  })
141
141
  ?.focus({
142
142
  preventScroll: true,
143
+ /** Otherwise a mouse click shows the keyboard focus ring. */
144
+ focusVisible: false,
143
145
  });
144
146
  }
145
147
  popoverManager.toggle();
@@ -1,4 +1,6 @@
1
1
  import { type PartialWithUndefined } from '@augment-vir/common';
2
+ import { type HtmlInterpolation } from 'element-vir';
3
+ import { type ViraIconSvg } from '../icons/icon-svg.js';
2
4
  /**
3
5
  * Options for `ViraDropdown`.
4
6
  *
@@ -8,9 +10,17 @@ import { type PartialWithUndefined } from '@augment-vir/common';
8
10
  export type ViraDropdownOption = {
9
11
  /** A value or id, used to keep track of which option is selected. */
10
12
  value: string;
13
+ /**
14
+ * Plain text for the option. Still used for search filtering, tooltips, and the search input's
15
+ * text when `labelTemplate` is set.
16
+ */
11
17
  label: string;
12
18
  } & PartialWithUndefined<{
13
19
  disabled: boolean;
20
+ /** Drawn before the label. Not drawn inside a searchable dropdown's text input. */
21
+ icon: ViraIconSvg;
22
+ /** Rendered instead of `label`. Not rendered inside a searchable dropdown's text input. */
23
+ labelTemplate: HtmlInterpolation;
14
24
  }>;
15
25
  /**
16
26
  * A group of options for `ViraDropdown`, rendered under a group label.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vira",
3
- "version": "33.0.0",
3
+ "version": "33.2.0",
4
4
  "description": "A simple and highly versatile design system using element-vir.",
5
5
  "keywords": [
6
6
  "design",