@api-client/ui 0.5.24 → 0.5.26

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.
Files changed (83) hide show
  1. package/.cursor/rules/lit-best-practices.mdc +12 -1
  2. package/.github/instructions/lit-best-practices.instructions.md +2 -0
  3. package/build/src/elements/navigation/internals/AppNavigationElement.d.ts.map +1 -1
  4. package/build/src/elements/navigation/internals/AppNavigationElement.js +1 -6
  5. package/build/src/elements/navigation/internals/AppNavigationElement.js.map +1 -1
  6. package/build/src/elements/navigation/internals/NavigationItem.d.ts +6 -0
  7. package/build/src/elements/navigation/internals/NavigationItem.d.ts.map +1 -1
  8. package/build/src/elements/navigation/internals/NavigationItem.js +26 -7
  9. package/build/src/elements/navigation/internals/NavigationItem.js.map +1 -1
  10. package/build/src/elements/navigation/internals/NavigationItem.styles.d.ts.map +1 -1
  11. package/build/src/elements/navigation/internals/NavigationItem.styles.js +18 -1
  12. package/build/src/elements/navigation/internals/NavigationItem.styles.js.map +1 -1
  13. package/build/src/md/dropdown-list/internals/UiDropdownList.d.ts.map +1 -1
  14. package/build/src/md/dropdown-list/internals/UiDropdownList.js +4 -3
  15. package/build/src/md/dropdown-list/internals/UiDropdownList.js.map +1 -1
  16. package/build/src/md/input/Input.d.ts +8 -4
  17. package/build/src/md/input/Input.d.ts.map +1 -1
  18. package/build/src/md/input/Input.js +8 -36
  19. package/build/src/md/input/Input.js.map +1 -1
  20. package/build/src/md/list/internals/List.d.ts +3 -1
  21. package/build/src/md/list/internals/List.d.ts.map +1 -1
  22. package/build/src/md/list/internals/List.js +9 -4
  23. package/build/src/md/list/internals/List.js.map +1 -1
  24. package/build/src/md/menu/internal/Menu.d.ts +8 -7
  25. package/build/src/md/menu/internal/Menu.d.ts.map +1 -1
  26. package/build/src/md/menu/internal/Menu.js +26 -29
  27. package/build/src/md/menu/internal/Menu.js.map +1 -1
  28. package/build/src/md/select/index.d.ts +4 -0
  29. package/build/src/md/select/index.d.ts.map +1 -0
  30. package/build/src/md/select/index.js +3 -0
  31. package/build/src/md/select/index.js.map +1 -0
  32. package/build/src/md/select/internals/Option.d.ts +125 -0
  33. package/build/src/md/select/internals/Option.d.ts.map +1 -0
  34. package/build/src/md/select/internals/Option.js +242 -0
  35. package/build/src/md/select/internals/Option.js.map +1 -0
  36. package/build/src/md/select/internals/Option.styles.d.ts +3 -0
  37. package/build/src/md/select/internals/Option.styles.d.ts.map +1 -0
  38. package/build/src/md/select/internals/Option.styles.js +139 -0
  39. package/build/src/md/select/internals/Option.styles.js.map +1 -0
  40. package/build/src/md/select/internals/Select.d.ts +250 -0
  41. package/build/src/md/select/internals/Select.d.ts.map +1 -0
  42. package/build/src/md/select/internals/Select.js +606 -0
  43. package/build/src/md/select/internals/Select.js.map +1 -0
  44. package/build/src/md/select/internals/Select.styles.d.ts +3 -0
  45. package/build/src/md/select/internals/Select.styles.d.ts.map +1 -0
  46. package/build/src/md/select/internals/Select.styles.js +22 -0
  47. package/build/src/md/select/internals/Select.styles.js.map +1 -0
  48. package/build/src/md/select/ui-option.d.ts +12 -0
  49. package/build/src/md/select/ui-option.d.ts.map +1 -0
  50. package/build/src/md/select/ui-option.js +29 -0
  51. package/build/src/md/select/ui-option.js.map +1 -0
  52. package/build/src/md/select/ui-select.d.ts +12 -0
  53. package/build/src/md/select/ui-select.d.ts.map +1 -0
  54. package/build/src/md/select/ui-select.js +27 -0
  55. package/build/src/md/select/ui-select.js.map +1 -0
  56. package/build/src/md/text-field/internals/TextField.d.ts.map +1 -1
  57. package/build/src/md/text-field/internals/TextField.js +1 -0
  58. package/build/src/md/text-field/internals/TextField.js.map +1 -1
  59. package/demo/elements/index.html +7 -4
  60. package/demo/elements/navigation/navigation-item.html +45 -0
  61. package/demo/elements/navigation/navigation-item.ts +112 -0
  62. package/demo/md/index.html +2 -0
  63. package/demo/md/inputs/input.ts +4 -0
  64. package/demo/md/select/index.html +16 -0
  65. package/demo/md/select/index.ts +202 -0
  66. package/package.json +1 -1
  67. package/src/elements/navigation/internals/AppNavigationElement.ts +1 -6
  68. package/src/elements/navigation/internals/NavigationItem.styles.ts +18 -1
  69. package/src/elements/navigation/internals/NavigationItem.ts +11 -5
  70. package/src/md/dropdown-list/internals/UiDropdownList.ts +4 -3
  71. package/src/md/input/Input.ts +8 -37
  72. package/src/md/list/internals/List.ts +12 -5
  73. package/src/md/menu/internal/Menu.ts +27 -18
  74. package/src/md/select/index.ts +3 -0
  75. package/src/md/select/internals/Option.styles.ts +139 -0
  76. package/src/md/select/internals/Option.ts +210 -0
  77. package/src/md/select/internals/Select.styles.ts +22 -0
  78. package/src/md/select/internals/Select.ts +534 -0
  79. package/src/md/select/ui-option.ts +18 -0
  80. package/src/md/select/ui-select.ts +17 -0
  81. package/src/md/text-field/internals/TextField.ts +1 -0
  82. package/test/md/menu/SubMenu.test.ts +2 -3
  83. package/test/md/select/Select.test.ts +667 -0
@@ -0,0 +1,22 @@
1
+ import { css } from 'lit'
2
+
3
+ export default css`
4
+ :host {
5
+ display: inline-block;
6
+ position: relative;
7
+ outline: none;
8
+ --md-focus-ring-shape-end-end: var(--md-sys-shape-corner-extra-small);
9
+ --md-focus-ring-shape-end-start: var(--md-sys-shape-corner-extra-small);
10
+ --md-focus-ring-shape-start-end: var(--md-sys-shape-corner-extra-small);
11
+ --md-focus-ring-shape-start-start: var(--md-sys-shape-corner-extra-small);
12
+ }
13
+
14
+ .input {
15
+ anchor-name: --input;
16
+ cursor: default;
17
+ }
18
+
19
+ .menu {
20
+ position-anchor: --input;
21
+ }
22
+ `
@@ -0,0 +1,534 @@
1
+ import { html, LitElement, PropertyValues, TemplateResult } from 'lit'
2
+ import { property, query, state } from 'lit/decorators.js'
3
+ import { classMap } from 'lit/directives/class-map.js'
4
+ import { setDisabled } from '../../../lib/disabled.js'
5
+ import type UiOption from './Option.js'
6
+ import type { UiMenuElement } from '../../menu/ui-menu.js'
7
+
8
+ import '../../text-field/ui-outlined-text-field.js'
9
+ import '../../menu/ui-menu.js'
10
+ import '../../icons/ui-icon.js'
11
+ import '@material/web/focus/md-focus-ring.js'
12
+
13
+ export interface UiSelectChangeEvent {
14
+ value: string | undefined
15
+ item: UiOption | null
16
+ }
17
+
18
+ /**
19
+ * Material Design 3 Select component that behaves like an outlined text field with dropdown.
20
+ *
21
+ * @fires change - Dispatched when the selection changes. The event is non-bubbling and non-cancelable.
22
+ * The `event.detail` object contains the `value` and `item` properties.
23
+ * @fires open - Dispatched when the dropdown opens
24
+ * @fires close - Dispatched when the dropdown closes
25
+ */
26
+ export default class UiSelect extends LitElement {
27
+ static readonly formAssociated = true
28
+ #internals = this.attachInternals()
29
+
30
+ /**
31
+ * The value has a private member so that we can set the value without triggering
32
+ * the side effects.
33
+ */
34
+ #value: string | undefined
35
+
36
+ /**
37
+ * The currently selected value. Corresponds to the `value` attribute of the selected `ui-option`.
38
+ * When set programmatically, it will update the selected option if a matching option exists.
39
+ *
40
+ * @example
41
+ * ```html
42
+ * <ui-select value="apple">
43
+ * <ui-option value="apple">Apple</ui-option>
44
+ * <ui-option value="banana">Banana</ui-option>
45
+ * </ui-select>
46
+ * ```
47
+ */
48
+ get value(): string | undefined {
49
+ return this.#value
50
+ }
51
+
52
+ @property({ type: String })
53
+ set value(newValue: string | undefined) {
54
+ const oldValue = this.#value
55
+ if (newValue === oldValue) return
56
+ this.#value = newValue
57
+ this.requestUpdate()
58
+ }
59
+
60
+ /**
61
+ * The name attribute for form submission. This value will be used as the key
62
+ * when the form is submitted.
63
+ *
64
+ * @example
65
+ * ```html
66
+ * <ui-select name="country" value="us">
67
+ * <ui-option value="us">United States</ui-option>
68
+ * </ui-select>
69
+ * ```
70
+ */
71
+ @property({ type: String }) accessor name: string | undefined
72
+
73
+ /**
74
+ * The label text displayed in the select field. Provides accessible labeling
75
+ * and is shown as the floating label in the outlined text field.
76
+ *
77
+ * @example
78
+ * ```html
79
+ * <ui-select label="Select a country">
80
+ * <ui-option value="us">United States</ui-option>
81
+ * </ui-select>
82
+ * ```
83
+ */
84
+ @property({ type: String }) accessor label: string | undefined
85
+
86
+ /**
87
+ * Whether the select is required for form validation. When true, the select
88
+ * must have a value selected for the form to be valid.
89
+ *
90
+ * @default false
91
+ * @example
92
+ * ```html
93
+ * <ui-select required label="Required field">
94
+ * <ui-option value="option1">Option 1</ui-option>
95
+ * </ui-select>
96
+ * ```
97
+ */
98
+ @property({ type: Boolean }) accessor required = false
99
+
100
+ /**
101
+ * Whether the select is in an invalid state. This is typically set automatically
102
+ * during validation, but can be set manually to indicate validation errors.
103
+ *
104
+ * @example
105
+ * ```html
106
+ * <ui-select invalid invalidText="Please select a valid option">
107
+ * <ui-option value="option1">Option 1</ui-option>
108
+ * </ui-select>
109
+ * ```
110
+ */
111
+ @property({ type: Boolean }) accessor invalid: boolean | undefined
112
+
113
+ /**
114
+ * The error message to display when the select is invalid. This text is shown
115
+ * below the select field when `invalid` is true.
116
+ *
117
+ * @example
118
+ * ```html
119
+ * <ui-select invalid invalidText="This field is required">
120
+ * <ui-option value="option1">Option 1</ui-option>
121
+ * </ui-select>
122
+ * ```
123
+ */
124
+ @property({ type: String }) accessor invalidText: string | undefined
125
+
126
+ /**
127
+ * Whether the select is disabled. When disabled, the select cannot be interacted
128
+ * with and will not receive focus or respond to user input.
129
+ *
130
+ * @default false
131
+ * @example
132
+ * ```html
133
+ * <ui-select disabled label="Disabled select">
134
+ * <ui-option value="option1">Option 1</ui-option>
135
+ * </ui-select>
136
+ * ```
137
+ */
138
+ @property({ type: Boolean, reflect: true }) accessor disabled = false
139
+
140
+ /**
141
+ * Whether the dropdown menu is currently open. This property reflects the
142
+ * current state of the dropdown and can be set programmatically to open/close it.
143
+ *
144
+ * @default false
145
+ * @example
146
+ * ```javascript
147
+ * // Open the dropdown programmatically
148
+ * selectElement.open = true;
149
+ *
150
+ * // Close the dropdown
151
+ * selectElement.open = false;
152
+ * ```
153
+ */
154
+ @property({ type: Boolean, reflect: true }) accessor open = false
155
+
156
+ @state() accessor selectedOption: UiOption | null = null
157
+ @state() accessor ariaActiveDescendant: string | undefined
158
+ @query('.menu') accessor menu!: UiMenuElement
159
+
160
+ /**
161
+ * Returns the currently selected option element. This provides access to the
162
+ * full `ui-option` element, not just its value.
163
+ *
164
+ * @readonly
165
+ * @example
166
+ * ```javascript
167
+ * const select = document.querySelector('ui-select');
168
+ * const selectedItem = select.selectedItem;
169
+ * if (selectedItem) {
170
+ * console.log('Selected option:', selectedItem.textContent);
171
+ * }
172
+ * ```
173
+ */
174
+ get selectedItem(): UiOption | null {
175
+ return this.selectedOption
176
+ }
177
+
178
+ /**
179
+ * Returns the text content that should be displayed in the select field.
180
+ * This is the rendered value of the currently selected option.
181
+ *
182
+ * @readonly
183
+ * @example
184
+ * ```javascript
185
+ * const select = document.querySelector('ui-select');
186
+ * console.log('Display text:', select.renderValue);
187
+ * ```
188
+ */
189
+ get renderValue(): string {
190
+ const item = this.selectedOption
191
+ return item ? item.renderValue : ''
192
+ }
193
+
194
+ /**
195
+ * Returns the form element that contains this select, if any.
196
+ * Part of the form-associated custom element API.
197
+ *
198
+ * @readonly
199
+ */
200
+ get form(): HTMLFormElement | null {
201
+ return this.#internals.form
202
+ }
203
+
204
+ /**
205
+ * Returns the validity state of the select element.
206
+ * Part of the form-associated custom element API.
207
+ *
208
+ * @readonly
209
+ */
210
+ get validity(): ValidityState {
211
+ return this.#internals.validity
212
+ }
213
+
214
+ /**
215
+ * Returns the validation message for the select element.
216
+ * Part of the form-associated custom element API.
217
+ *
218
+ * @readonly
219
+ */
220
+ get validationMessage(): string {
221
+ return this.#internals.validationMessage
222
+ }
223
+
224
+ /**
225
+ * Returns whether the select element will be validated when the form is submitted.
226
+ * Part of the form-associated custom element API.
227
+ *
228
+ * @readonly
229
+ */
230
+ get willValidate(): boolean {
231
+ return this.#internals.willValidate
232
+ }
233
+
234
+ /**
235
+ * Checks the validity of the select element and returns true if valid.
236
+ * Part of the form-associated custom element API.
237
+ *
238
+ * @returns {boolean} True if the element is valid, false otherwise
239
+ * @example
240
+ * ```javascript
241
+ * const select = document.querySelector('ui-select');
242
+ * if (!select.checkValidity()) {
243
+ * console.log('Select is invalid:', select.validationMessage);
244
+ * }
245
+ * ```
246
+ */
247
+ checkValidity(): boolean {
248
+ return this.#internals.checkValidity()
249
+ }
250
+
251
+ constructor() {
252
+ super()
253
+ this.addEventListener('click', this.handleClick.bind(this))
254
+ this.addEventListener('blur', this.handleBlur.bind(this))
255
+ this.addEventListener('keydown', this.handleKeydown.bind(this))
256
+ }
257
+
258
+ override connectedCallback(): void {
259
+ super.connectedCallback()
260
+ this.setAttribute('role', 'combobox')
261
+ this.setAttribute('aria-haspopup', 'listbox')
262
+ this.setAttribute('aria-controls', 'menu')
263
+ if (!this.disabled) {
264
+ this.setAttribute('tabindex', '0')
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Resets the select to its initial state. Called automatically when the parent
270
+ * form is reset. Part of the form-associated custom element API.
271
+ *
272
+ * @example
273
+ * ```javascript
274
+ * const select = document.querySelector('ui-select');
275
+ * select.formResetCallback(); // Clears the selection
276
+ * ```
277
+ */
278
+ formResetCallback(): void {
279
+ this.value = undefined
280
+ }
281
+
282
+ /**
283
+ * Restores the select's state from saved form data. Called automatically when
284
+ * the browser restores form state. Part of the form-associated custom element API.
285
+ *
286
+ * @param {string | null} state - The saved state to restore
287
+ */
288
+ formStateRestoreCallback(state: string | null): void {
289
+ this.value = state ?? undefined
290
+ }
291
+
292
+ /**
293
+ * Validates the select element and updates its validity state. This is called
294
+ * automatically during property changes, but can be called manually to trigger validation.
295
+ *
296
+ * @example
297
+ * ```javascript
298
+ * const select = document.querySelector('ui-select');
299
+ * select.validate();
300
+ * if (select.invalid) {
301
+ * console.log('Validation failed:', select.invalidText);
302
+ * }
303
+ * ```
304
+ */
305
+ validate(): void {
306
+ let message = ''
307
+ if (this.required && !this.value) {
308
+ message = 'Please select an item.'
309
+ this.#internals.setValidity({ valueMissing: true }, message)
310
+ } else {
311
+ this.#internals.setValidity({})
312
+ }
313
+ this.invalid = !this.#internals.validity.valid
314
+ this.invalidText = message
315
+ }
316
+
317
+ protected override willUpdate(changedProperties: PropertyValues<this>): void {
318
+ super.willUpdate(changedProperties)
319
+ if (changedProperties.has('disabled')) {
320
+ setDisabled(this, this.disabled)
321
+ }
322
+ if (changedProperties.has('open')) {
323
+ this.handleOpenChange()
324
+ }
325
+ if (changedProperties.has('value')) {
326
+ this.setCurrentOption()
327
+ this.#internals.setFormValue(this.value ?? null)
328
+ this.validate()
329
+ }
330
+ if (changedProperties.has('label')) {
331
+ if (this.label) {
332
+ this.setAttribute('aria-label', this.label)
333
+ } else {
334
+ this.removeAttribute('aria-label')
335
+ }
336
+ }
337
+ }
338
+
339
+ protected async setCurrentOption(): Promise<void> {
340
+ await this.updateComplete
341
+ if (this.value) {
342
+ const options = this.querySelectorAll<UiOption>('ui-option')
343
+ this.selectedOption = Array.from(options).find((option) => option.value === this.value) || null
344
+ } else {
345
+ this.selectedOption = null
346
+ }
347
+ }
348
+
349
+ protected handleKeydown(e: KeyboardEvent): void {
350
+ if (this.disabled || e.defaultPrevented) return
351
+ if (this.open) {
352
+ switch (e.key) {
353
+ case 'Tab': {
354
+ // If menu is open and Tab is pressed, close it and allow normal tab navigation
355
+ if (this.open) {
356
+ this.open = false
357
+ }
358
+ break
359
+ }
360
+ case 'Escape': {
361
+ if (this.open) {
362
+ e.preventDefault()
363
+ this.open = false
364
+ this.focus() // Return focus to the select element
365
+ }
366
+ break
367
+ }
368
+ case 'ArrowDown':
369
+ e.preventDefault()
370
+ this.menu.highlightNext()
371
+ return
372
+ case 'ArrowUp':
373
+ e.preventDefault()
374
+ this.menu.highlightPrevious()
375
+ return
376
+ case 'Home':
377
+ e.preventDefault()
378
+ this.menu.highlightFirst()
379
+ return
380
+ case 'End':
381
+ e.preventDefault()
382
+ this.menu.highlightLast()
383
+ return
384
+ case 'Enter':
385
+ case ' ':
386
+ if (this.menu.highlightListItem) {
387
+ e.preventDefault()
388
+ this.menu.notifySelect(this.menu.highlightListItem as UiOption)
389
+ }
390
+ return
391
+ }
392
+ } else {
393
+ switch (e.key) {
394
+ case 'Enter':
395
+ case ' ': {
396
+ if (!this.open) {
397
+ e.preventDefault()
398
+ this.open = true
399
+ }
400
+ break
401
+ }
402
+ case 'ArrowDown':
403
+ case 'ArrowUp': {
404
+ if (!this.open) {
405
+ e.preventDefault()
406
+ this.open = true
407
+ }
408
+ // If menu is open, let the menu handle arrow keys
409
+ break
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ protected handleBlur(e: FocusEvent): void {
416
+ if (this.disabled) return
417
+
418
+ // Check if focus is moving to the menu or one of its children
419
+ const relatedTarget = e.relatedTarget as HTMLElement
420
+
421
+ if (relatedTarget && this.contains(relatedTarget)) {
422
+ // Focus is moving to the menu, keep it open
423
+ return
424
+ }
425
+
426
+ // Close the menu when focus leaves the component
427
+ this.open = false
428
+ }
429
+
430
+ protected handleClick(e: Event): void {
431
+ if (this.disabled || e.defaultPrevented) return
432
+ e.preventDefault()
433
+ e.stopPropagation()
434
+ this.open = true
435
+ }
436
+
437
+ protected async handleOpenChange(): Promise<void> {
438
+ const menu = this.menu
439
+ if (!menu) {
440
+ // The status can be set before the menu is rendered
441
+ return
442
+ }
443
+ this.setAttribute('aria-expanded', String(this.open))
444
+ if (this.open) {
445
+ menu.showPopover()
446
+ // menu.focus()
447
+ if (this.selectedOption) {
448
+ this.menu.highlightItem(this.selectedOption)
449
+ } else {
450
+ this.menu.highlightFirst()
451
+ }
452
+ this.dispatchEvent(new CustomEvent('open', { bubbles: false, composed: true }))
453
+ this.focus()
454
+ } else {
455
+ menu.hidePopover()
456
+ this.dispatchEvent(new CustomEvent('close', { bubbles: false, composed: true }))
457
+ }
458
+ }
459
+
460
+ handleSelect(e: CustomEvent<{ item: UiOption }>): void {
461
+ e.stopPropagation()
462
+ const item = e.detail.item
463
+ this.selectedOption = item
464
+ this.#value = item.value
465
+ this.open = false
466
+
467
+ // Dispatch change event
468
+ const changeEvent = new CustomEvent<UiSelectChangeEvent>('change', {
469
+ detail: { value: this.value, item: item },
470
+ bubbles: false,
471
+ composed: true,
472
+ })
473
+ this.dispatchEvent(changeEvent)
474
+ this.focus()
475
+ }
476
+
477
+ handleHighlightChange(e: CustomEvent<{ item: UiOption | null }>): void {
478
+ this.ariaActiveDescendant = e.detail.item?.id
479
+ }
480
+
481
+ handleMenuClose(): void {
482
+ this.open = false
483
+ this.focus()
484
+ }
485
+
486
+ override render(): TemplateResult {
487
+ const classes = classMap({
488
+ 'ui-select': true,
489
+ 'open': this.open,
490
+ 'disabled': this.disabled,
491
+ })
492
+ return html`${this.renderFocusRing()}
493
+ <div class="${classes}" aria-activedescendant=${this.ariaActiveDescendant || ''}>
494
+ ${this.renderInput()} ${this.renderMenu()}
495
+ </div> `
496
+ }
497
+
498
+ protected renderInput(): TemplateResult {
499
+ return html`<ui-outlined-text-field
500
+ .name=${this.name}
501
+ .label=${this.label}
502
+ .value=${this.renderValue}
503
+ .disabled=${this.disabled}
504
+ .required=${this.required}
505
+ readonly
506
+ tabindex="-1"
507
+ inert
508
+ aria-hidden="true"
509
+ .invalid=${this.invalid}
510
+ .invalidText=${this.invalidText || ''}
511
+ class="input"
512
+ >
513
+ <ui-icon slot="suffix">arrow_drop_down</ui-icon>
514
+ </ui-outlined-text-field>`
515
+ }
516
+
517
+ protected renderMenu(): TemplateResult {
518
+ return html`<ui-menu
519
+ id="menu"
520
+ class="menu"
521
+ popover="auto"
522
+ selector="ui-option"
523
+ @select="${this.handleSelect}"
524
+ @close="${this.handleMenuClose}"
525
+ @highlightchange="${this.handleHighlightChange}"
526
+ >
527
+ <slot></slot>
528
+ </ui-menu>`
529
+ }
530
+
531
+ protected renderFocusRing(): TemplateResult {
532
+ return html`<md-focus-ring part="focus-ring" class="focus-ring" .control="${this as HTMLElement}"></md-focus-ring>`
533
+ }
534
+ }
@@ -0,0 +1,18 @@
1
+ import type { CSSResultOrNative } from 'lit'
2
+ import { customElement } from 'lit/decorators.js'
3
+ import Element from './internals/Option.js'
4
+ import styles from './internals/Option.styles.js'
5
+ import listStyles from '../list/internals/ListItem.styles.js'
6
+
7
+ @customElement('ui-option')
8
+ export class UiOptionElement extends Element {
9
+ static override styles: CSSResultOrNative[] = [styles, listStyles]
10
+ }
11
+
12
+ declare global {
13
+ interface HTMLElementTagNameMap {
14
+ 'ui-option': Element
15
+ }
16
+ }
17
+
18
+ export { ListItemImage, ListItemLines } from '../list/internals/ListItem.js'
@@ -0,0 +1,17 @@
1
+ import type { CSSResultOrNative } from 'lit'
2
+ import { customElement } from 'lit/decorators.js'
3
+ import Element from './internals/Select.js'
4
+ import styles from './internals/Select.styles.js'
5
+
6
+ @customElement('ui-select')
7
+ export class UiSelectElement extends Element {
8
+ static override styles: CSSResultOrNative[] = [styles]
9
+ }
10
+
11
+ declare global {
12
+ interface HTMLElementTagNameMap {
13
+ 'ui-select': Element
14
+ }
15
+ }
16
+
17
+ export type { UiSelectChangeEvent } from './internals/Select.js'
@@ -54,6 +54,7 @@ export default class TextField extends Input {
54
54
  ?multiple="${this.multiple}"
55
55
  spellcheck="${ifDefined(this.spellcheck)}"
56
56
  list="input-list"
57
+ tabindex="${ifDefined(this.tabIndex)}"
57
58
  @change=${this.retargetEvent}
58
59
  @input=${this.handleInput}
59
60
  @select=${this.retargetEvent}
@@ -166,14 +166,13 @@ describe('md', () => {
166
166
  it('should dispatch open event when shown', async () => {
167
167
  const container = await withAnchorFixture()
168
168
  const submenu = container.querySelector('#test-submenu') as UiSubMenu
169
- await nextFrame()
170
169
 
171
170
  setTimeout(() => submenu.show())
172
171
  const event = await oneEvent(submenu, 'open')
173
172
 
174
173
  assert.instanceOf(event, CustomEvent)
175
- assert.isTrue(event.bubbles)
176
- assert.isTrue(event.composed)
174
+ assert.isFalse(event.bubbles, 'the event does not bubble')
175
+ assert.isTrue(event.composed, 'the event is composed')
177
176
  })
178
177
 
179
178
  it('should dispatch close event when hidden', async () => {