@adia-ai/web-components 0.6.33 → 0.6.34
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/CHANGELOG.md +22 -0
- package/components/accordion/accordion.css +2 -2
- package/components/action-list/action-list.css +2 -2
- package/components/agent-artifact/agent-artifact.css +31 -31
- package/components/agent-feedback-bar/agent-feedback-bar.css +10 -10
- package/components/agent-questions/agent-questions.css +57 -57
- package/components/agent-reasoning/agent-reasoning.css +62 -62
- package/components/agent-suggestions/agent-suggestions.css +4 -4
- package/components/agent-trace/agent-trace.css +53 -53
- package/components/alert/alert.css +41 -41
- package/components/avatar/avatar.css +27 -27
- package/components/badge/badge.css +27 -27
- package/components/block/block.css +16 -16
- package/components/breadcrumb/breadcrumb.css +23 -23
- package/components/button/button.css +101 -91
- package/components/calendar-grid/calendar-grid.a2ui.json +136 -0
- package/components/calendar-grid/calendar-grid.css +226 -0
- package/components/calendar-grid/calendar-grid.d.ts +37 -0
- package/components/calendar-grid/calendar-grid.js +17 -0
- package/components/calendar-grid/calendar-grid.yaml +116 -0
- package/components/calendar-grid/class.js +300 -0
- package/components/calendar-picker/calendar-picker.css +139 -139
- package/components/canvas/canvas.css +12 -12
- package/components/card/card.css +83 -83
- package/components/chart/chart.css +224 -224
- package/components/chart-legend/chart-legend.css +26 -26
- package/components/check/check.css +40 -40
- package/components/code/code.css +125 -125
- package/components/col/col.css +15 -15
- package/components/color-picker/color-picker.css +55 -55
- package/components/combobox/class.js +861 -0
- package/components/combobox/combobox.a2ui.json +363 -0
- package/components/combobox/combobox.css +244 -0
- package/components/combobox/combobox.d.ts +113 -0
- package/components/combobox/combobox.examples.md +59 -0
- package/components/combobox/combobox.js +17 -0
- package/components/combobox/combobox.test.js +181 -0
- package/components/combobox/combobox.yaml +369 -0
- package/components/command/command.css +90 -90
- package/components/date-range-picker/class.js +775 -0
- package/components/date-range-picker/date-range-picker.a2ui.json +300 -0
- package/components/date-range-picker/date-range-picker.css +178 -0
- package/components/date-range-picker/date-range-picker.d.ts +82 -0
- package/components/date-range-picker/date-range-picker.examples.md +37 -0
- package/components/date-range-picker/date-range-picker.js +17 -0
- package/components/date-range-picker/date-range-picker.test.js +387 -0
- package/components/date-range-picker/date-range-picker.yaml +285 -0
- package/components/datetime-picker/class.js +706 -0
- package/components/datetime-picker/datetime-picker.a2ui.json +334 -0
- package/components/datetime-picker/datetime-picker.css +150 -0
- package/components/datetime-picker/datetime-picker.d.ts +86 -0
- package/components/datetime-picker/datetime-picker.examples.md +46 -0
- package/components/datetime-picker/datetime-picker.js +17 -0
- package/components/datetime-picker/datetime-picker.test.js +454 -0
- package/components/datetime-picker/datetime-picker.yaml +332 -0
- package/components/demo-toggle/demo-toggle.css +27 -27
- package/components/description-list/description-list.css +18 -18
- package/components/divider/divider.css +24 -24
- package/components/embed/embed.css +6 -6
- package/components/empty-state/empty-state.css +27 -27
- package/components/feed/feed.css +12 -12
- package/components/field/field.css +28 -28
- package/components/fields/fields.css +5 -5
- package/components/grid/grid.css +5 -5
- package/components/heatmap/heatmap.css +63 -63
- package/components/icon/icon.css +12 -12
- package/components/image/image.css +14 -14
- package/components/index.js +8 -0
- package/components/input/input.css +66 -66
- package/components/inspector/inspector.css +6 -6
- package/components/integration-card/class.js +410 -0
- package/components/integration-card/integration-card.a2ui.json +268 -0
- package/components/integration-card/integration-card.css +169 -0
- package/components/integration-card/integration-card.d.ts +63 -0
- package/components/integration-card/integration-card.examples.md +41 -0
- package/components/integration-card/integration-card.js +17 -0
- package/components/integration-card/integration-card.test.js +306 -0
- package/components/integration-card/integration-card.yaml +280 -0
- package/components/kbd/kbd.css +32 -32
- package/components/link/link.css +12 -12
- package/components/list/list.css +8 -8
- package/components/list-window/class.js +688 -0
- package/components/list-window/list-window.a2ui.json +277 -0
- package/components/list-window/list-window.css +124 -0
- package/components/list-window/list-window.d.ts +84 -0
- package/components/list-window/list-window.examples.md +73 -0
- package/components/list-window/list-window.js +17 -0
- package/components/list-window/list-window.test.js +303 -0
- package/components/list-window/list-window.yaml +270 -0
- package/components/menu/menu.css +8 -8
- package/components/modal/modal.css +43 -43
- package/components/nav/nav.css +40 -40
- package/components/nav-group/nav-group.css +52 -52
- package/components/nav-item/nav-item.css +44 -44
- package/components/noodles/noodles.css +31 -31
- package/components/option-card/option-card.css +69 -69
- package/components/otp-input/otp-input.css +30 -30
- package/components/page/page.css +18 -18
- package/components/pagination/pagination.css +61 -61
- package/components/pane/pane.css +57 -57
- package/components/pipeline-status/pipeline-status.css +65 -65
- package/components/popover/popover.css +17 -17
- package/components/progress/progress.css +23 -23
- package/components/progress-row/progress-row.css +17 -17
- package/components/radio/radio.css +39 -39
- package/components/range/range.css +55 -55
- package/components/rating/rating.css +28 -28
- package/components/richtext/richtext.css +133 -133
- package/components/row/row.css +19 -19
- package/components/search/search.css +5 -5
- package/components/segment/segment.css +24 -24
- package/components/segmented/segmented.css +25 -25
- package/components/select/select.css +84 -84
- package/components/skeleton/skeleton.css +14 -14
- package/components/slider/slider.css +46 -46
- package/components/spinner/class.js +69 -0
- package/components/spinner/spinner.a2ui.json +197 -0
- package/components/spinner/spinner.css +165 -0
- package/components/spinner/spinner.d.ts +26 -0
- package/components/spinner/spinner.examples.md +26 -0
- package/components/spinner/spinner.js +17 -0
- package/components/spinner/spinner.test.js +234 -0
- package/components/spinner/spinner.yaml +230 -0
- package/components/stack/stack.css +11 -11
- package/components/stat/stat.css +25 -25
- package/components/step-progress/step-progress.css +20 -20
- package/components/stepper/stepper.css +29 -29
- package/components/stream/stream.css +12 -12
- package/components/swatch/swatch.css +68 -68
- package/components/swiper/swiper.css +57 -57
- package/components/switch/switch.css +52 -52
- package/components/table/table.css +162 -162
- package/components/table-toolbar/table-toolbar.css +32 -32
- package/components/tabs/tabs.css +51 -51
- package/components/tag/tag.css +48 -48
- package/components/text/text.css +44 -44
- package/components/textarea/textarea.css +46 -46
- package/components/time-picker/class.js +693 -0
- package/components/time-picker/time-picker.a2ui.json +267 -0
- package/components/time-picker/time-picker.css +122 -0
- package/components/time-picker/time-picker.d.ts +75 -0
- package/components/time-picker/time-picker.examples.md +35 -0
- package/components/time-picker/time-picker.js +17 -0
- package/components/time-picker/time-picker.test.js +287 -0
- package/components/time-picker/time-picker.yaml +256 -0
- package/components/timeline/timeline.css +50 -50
- package/components/toast/toast.css +58 -58
- package/components/toggle-group/toggle-group.css +6 -6
- package/components/toggle-scheme/toggle-scheme.css +2 -2
- package/components/toolbar/toolbar.css +17 -17
- package/components/tooltip/tooltip.css +2 -2
- package/components/tree/tree.css +37 -37
- package/components/upload/upload.css +49 -49
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +121 -83
- package/package.json +1 -1
- package/styles/components.css +8 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<combobox-ui>` — Typeahead-filterable single-select primitive — the constrained-choice
|
|
3
|
+
combobox per WAI-APG "Combobox With List Autocomplete (manual
|
|
4
|
+
selection)". The committed value MUST come from the options list
|
|
5
|
+
unless `[free-text]` is set. Distinct from `<select-ui searchable>`
|
|
6
|
+
(button-first dropdown) and `<autocomplete-input-ui>` (free-form).
|
|
7
|
+
Form-bearing via UIFormElement: `[name]`, `[value]`, `[required]`,
|
|
8
|
+
`[disabled]`, fires `change`. Options via native `<option>` /
|
|
9
|
+
`<optgroup>` children OR programmatic `.options` array.
|
|
10
|
+
|
|
11
|
+
*
|
|
12
|
+
* @see https://ui-kit.exe.xyz/site/components/combobox
|
|
13
|
+
*
|
|
14
|
+
* Type declarations generated by scripts/build/dts-codegen.mjs from
|
|
15
|
+
* the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
|
|
16
|
+
* run `npm run build:components`, then `npm run codegen:dts` to
|
|
17
|
+
* regenerate; or hand-author this file fully if rich event types are
|
|
18
|
+
* needed beyond what the yaml `events:` block can express.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { UIElement } from '../../core/element.js';
|
|
22
|
+
|
|
23
|
+
export interface ComboboxChangeEventDetail {
|
|
24
|
+
/** Resolved option object or null when free-text / cleared. */
|
|
25
|
+
option: Record<string, unknown>;
|
|
26
|
+
/** One of `click` / `keyboard` / `programmatic`. */
|
|
27
|
+
source: string;
|
|
28
|
+
/** Committed value. */
|
|
29
|
+
value: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ComboboxChangeEvent = CustomEvent<ComboboxChangeEventDetail>;
|
|
33
|
+
export interface ComboboxCloseEventDetail {
|
|
34
|
+
/** One of `escape` / `outside` / `select` / `blur`. */
|
|
35
|
+
reason: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type ComboboxCloseEvent = CustomEvent<ComboboxCloseEventDetail>;
|
|
39
|
+
export interface ComboboxCreateEventDetail {
|
|
40
|
+
/** The typed text the user is trying to create. */
|
|
41
|
+
value: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type ComboboxCreateEvent = CustomEvent<ComboboxCreateEventDetail>;
|
|
45
|
+
export interface ComboboxInputEventDetail {
|
|
46
|
+
/** Lowercased filter query. */
|
|
47
|
+
query: string;
|
|
48
|
+
/** Current input text (the user's typed query). */
|
|
49
|
+
value: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type ComboboxInputEvent = CustomEvent<ComboboxInputEventDetail>;
|
|
53
|
+
export interface ComboboxInvalidEventDetail {
|
|
54
|
+
/** The unmatched typed text. */
|
|
55
|
+
query: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type ComboboxInvalidEvent = CustomEvent<ComboboxInvalidEventDetail>;
|
|
59
|
+
export interface ComboboxOpenEventDetail {
|
|
60
|
+
/** One of `click` / `keyboard` / `typing`. */
|
|
61
|
+
trigger: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ComboboxOpenEvent = CustomEvent<ComboboxOpenEventDetail>;
|
|
65
|
+
|
|
66
|
+
export class UICombobox extends UIElement {
|
|
67
|
+
/** Marks the field as required for form validation */
|
|
68
|
+
required: boolean;
|
|
69
|
+
/** Renders an `x` clear button when value is set */
|
|
70
|
+
clearable: boolean;
|
|
71
|
+
/** Implies `[free-text]`; on Enter with unmatched typed text fires a
|
|
72
|
+
`create` event with detail.value so the consumer can run a
|
|
73
|
+
backend create-then-refresh-options flow. */
|
|
74
|
+
creatable: boolean;
|
|
75
|
+
/** Disables interaction and dims the control */
|
|
76
|
+
disabled: boolean;
|
|
77
|
+
/** Validation error message */
|
|
78
|
+
error: string;
|
|
79
|
+
/** Caption text below the field; wires `aria-describedby` on the host
|
|
80
|
+
so screen readers announce it as a description. */
|
|
81
|
+
hint: string;
|
|
82
|
+
/** Inline label rendered above the field. Wrap with <field-ui> for
|
|
83
|
+
proper `<label for>` association in form-row compositions. */
|
|
84
|
+
label: string;
|
|
85
|
+
/** Shows a loading spinner inside the popover; consumer drives async fetch */
|
|
86
|
+
loading: boolean;
|
|
87
|
+
/** Form control name for form data submission */
|
|
88
|
+
name: string;
|
|
89
|
+
/** Reflects popover open / closed state */
|
|
90
|
+
open: boolean;
|
|
91
|
+
/** Programmatic option list. Array of {value, label, disabled?, group?} or grouped {label, options:[…]}. Alternative to declarative <option> / <optgroup> children. */
|
|
92
|
+
options: unknown[];
|
|
93
|
+
/** Placeholder text in the input when value is empty */
|
|
94
|
+
placeholder: string;
|
|
95
|
+
/** Prevents selection changes while keeping the field focusable + popover-open allowed */
|
|
96
|
+
readonly: boolean;
|
|
97
|
+
/** Sizing scale via universal `[size]` attribute system. Matches Input + Select sizing tokens so a Combobox rendered alongside either feels coherent in a form row. */
|
|
98
|
+
size: 'sm' | 'md' | 'lg';
|
|
99
|
+
/** Currently selected option value. Must be a member of `options[].value` unless `[free-text]` is set. With declarative <option> children, a child carrying the native `selected` attribute sets the initial value when this attribute is absent. */
|
|
100
|
+
value: string;
|
|
101
|
+
|
|
102
|
+
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
103
|
+
type: K,
|
|
104
|
+
listener: (this: UICombobox, ev: HTMLElementEventMap[K]) => unknown,
|
|
105
|
+
options?: boolean | AddEventListenerOptions,
|
|
106
|
+
): void;
|
|
107
|
+
addEventListener(type: 'change', listener: (ev: ComboboxChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
108
|
+
addEventListener(type: 'close', listener: (ev: ComboboxCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
109
|
+
addEventListener(type: 'create', listener: (ev: ComboboxCreateEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
110
|
+
addEventListener(type: 'input', listener: (ev: ComboboxInputEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
111
|
+
addEventListener(type: 'invalid', listener: (ev: ComboboxInvalidEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
112
|
+
addEventListener(type: 'open', listener: (ev: ComboboxOpenEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
113
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# combobox — Examples
|
|
2
|
+
|
|
3
|
+
## basic
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<field-ui label="Country">
|
|
7
|
+
<combobox-ui name="country" placeholder="Select country…">
|
|
8
|
+
<option value="us">United States</option>
|
|
9
|
+
<option value="gb">United Kingdom</option>
|
|
10
|
+
<option value="de">Germany</option>
|
|
11
|
+
<option value="fr">France</option>
|
|
12
|
+
<option value="es">Spain</option>
|
|
13
|
+
<option value="it">Italy</option>
|
|
14
|
+
</combobox-ui>
|
|
15
|
+
</field-ui>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## grouped options
|
|
19
|
+
|
|
20
|
+
```html
|
|
21
|
+
<field-ui label="Location">
|
|
22
|
+
<combobox-ui placeholder="Choose a city">
|
|
23
|
+
<optgroup label="Americas">
|
|
24
|
+
<option value="nyc">New York</option>
|
|
25
|
+
<option value="sf">San Francisco</option>
|
|
26
|
+
</optgroup>
|
|
27
|
+
<optgroup label="Europe">
|
|
28
|
+
<option value="london">London</option>
|
|
29
|
+
<option value="paris">Paris</option>
|
|
30
|
+
</optgroup>
|
|
31
|
+
</combobox-ui>
|
|
32
|
+
</field-ui>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## creatable tag picker
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<field-ui label="Tag">
|
|
39
|
+
<combobox-ui name="tag" creatable placeholder="Pick or create a tag…">
|
|
40
|
+
<option value="bug">bug</option>
|
|
41
|
+
<option value="feature">feature</option>
|
|
42
|
+
<option value="chore">chore</option>
|
|
43
|
+
</combobox-ui>
|
|
44
|
+
</field-ui>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Typing "regression" + Enter fires the `create` event with `detail.value === "regression"`.
|
|
48
|
+
|
|
49
|
+
## clearable
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<field-ui label="Role">
|
|
53
|
+
<combobox-ui clearable value="user" placeholder="Pick a role…">
|
|
54
|
+
<option value="user">User</option>
|
|
55
|
+
<option value="admin">Admin</option>
|
|
56
|
+
<option value="guest">Guest</option>
|
|
57
|
+
</combobox-ui>
|
|
58
|
+
</field-ui>
|
|
59
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<combobox-ui>` — auto-registers the tag on import.
|
|
3
|
+
*
|
|
4
|
+
* For non-side-effect class import (test isolation, tag override), use
|
|
5
|
+
* the `class` subpath:
|
|
6
|
+
*
|
|
7
|
+
* import { UICombobox } from '@adia-ai/web-components/components/combobox/class';
|
|
8
|
+
*
|
|
9
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { defineIfFree } from '../../core/register.js';
|
|
13
|
+
import { UICombobox } from './class.js';
|
|
14
|
+
|
|
15
|
+
defineIfFree('combobox-ui', UICombobox);
|
|
16
|
+
|
|
17
|
+
export { UICombobox };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import '../../core/element.js';
|
|
3
|
+
import './combobox.js';
|
|
4
|
+
|
|
5
|
+
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
6
|
+
|
|
7
|
+
function mount(html) {
|
|
8
|
+
const wrap = document.createElement('div');
|
|
9
|
+
wrap.innerHTML = html;
|
|
10
|
+
document.body.appendChild(wrap);
|
|
11
|
+
return wrap.firstElementChild;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('combobox-ui', () => {
|
|
15
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
16
|
+
|
|
17
|
+
it('parses declarative <option> children into the options model', async () => {
|
|
18
|
+
const el = mount(`
|
|
19
|
+
<combobox-ui>
|
|
20
|
+
<option value="a">Alpha</option>
|
|
21
|
+
<option value="b">Beta</option>
|
|
22
|
+
<option value="c">Gamma</option>
|
|
23
|
+
</combobox-ui>
|
|
24
|
+
`);
|
|
25
|
+
await tick();
|
|
26
|
+
expect(el.options.length).toBe(3);
|
|
27
|
+
expect(el.options[0].value).toBe('a');
|
|
28
|
+
expect(el.options[0].label).toBe('Alpha');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('honours <option selected> as initial value', async () => {
|
|
32
|
+
const el = mount(`
|
|
33
|
+
<combobox-ui>
|
|
34
|
+
<option value="a">Alpha</option>
|
|
35
|
+
<option value="b" selected>Beta</option>
|
|
36
|
+
<option value="c">Gamma</option>
|
|
37
|
+
</combobox-ui>
|
|
38
|
+
`);
|
|
39
|
+
await tick();
|
|
40
|
+
expect(el.value).toBe('b');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('renders ARIA combobox + listbox wiring per WAI-APG', async () => {
|
|
44
|
+
const el = mount(`
|
|
45
|
+
<combobox-ui>
|
|
46
|
+
<option value="a">Alpha</option>
|
|
47
|
+
</combobox-ui>
|
|
48
|
+
`);
|
|
49
|
+
await tick();
|
|
50
|
+
// Per spec §5: role + aria-expanded live on the editable surface.
|
|
51
|
+
const input = el.querySelector('[data-input]');
|
|
52
|
+
expect(input).toBeTruthy();
|
|
53
|
+
expect(input.getAttribute('role')).toBe('combobox');
|
|
54
|
+
expect(input.getAttribute('aria-autocomplete')).toBe('list');
|
|
55
|
+
expect(input.getAttribute('aria-expanded')).toBe('false');
|
|
56
|
+
expect(input.hasAttribute('aria-controls')).toBe(true);
|
|
57
|
+
const listbox = el.querySelector('[data-listbox]');
|
|
58
|
+
expect(listbox).toBeTruthy();
|
|
59
|
+
expect(listbox.getAttribute('role')).toBe('listbox');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('programmatic .options = [...] re-renders the listbox', async () => {
|
|
63
|
+
const el = mount(`<combobox-ui></combobox-ui>`);
|
|
64
|
+
await tick();
|
|
65
|
+
el.options = [
|
|
66
|
+
{ value: 'a', label: 'Alpha' },
|
|
67
|
+
{ value: 'b', label: 'Beta' },
|
|
68
|
+
];
|
|
69
|
+
await tick();
|
|
70
|
+
const opts = el.querySelectorAll('[role="option"]');
|
|
71
|
+
expect(opts.length).toBe(2);
|
|
72
|
+
expect(opts[0].dataset.value).toBe('a');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('reflects [open] + aria-expanded on the input when opening', async () => {
|
|
76
|
+
const el = mount(`
|
|
77
|
+
<combobox-ui>
|
|
78
|
+
<option value="a">Alpha</option>
|
|
79
|
+
<option value="b">Beta</option>
|
|
80
|
+
</combobox-ui>
|
|
81
|
+
`);
|
|
82
|
+
await tick();
|
|
83
|
+
el.open = true;
|
|
84
|
+
await tick();
|
|
85
|
+
expect(el.hasAttribute('open')).toBe(true);
|
|
86
|
+
const input = el.querySelector('[data-input]');
|
|
87
|
+
expect(input.getAttribute('aria-expanded')).toBe('true');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('selectOption() commits + fires change with the option object', async () => {
|
|
91
|
+
const el = mount(`
|
|
92
|
+
<combobox-ui>
|
|
93
|
+
<option value="a">Alpha</option>
|
|
94
|
+
<option value="b">Beta</option>
|
|
95
|
+
</combobox-ui>
|
|
96
|
+
`);
|
|
97
|
+
await tick();
|
|
98
|
+
let changeDetail = null;
|
|
99
|
+
el.addEventListener('change', (e) => { changeDetail = e.detail; });
|
|
100
|
+
const hit = el.selectOption('b');
|
|
101
|
+
expect(hit).toBe(true);
|
|
102
|
+
await tick();
|
|
103
|
+
expect(el.value).toBe('b');
|
|
104
|
+
expect(changeDetail).not.toBeNull();
|
|
105
|
+
expect(changeDetail.value).toBe('b');
|
|
106
|
+
expect(changeDetail.option?.value).toBe('b');
|
|
107
|
+
expect(changeDetail.source).toBe('programmatic');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('selectOption() returns false for an unknown value (no commit)', async () => {
|
|
111
|
+
const el = mount(`
|
|
112
|
+
<combobox-ui>
|
|
113
|
+
<option value="a">Alpha</option>
|
|
114
|
+
</combobox-ui>
|
|
115
|
+
`);
|
|
116
|
+
await tick();
|
|
117
|
+
let changeCount = 0;
|
|
118
|
+
el.addEventListener('change', () => { changeCount += 1; });
|
|
119
|
+
const hit = el.selectOption('zzz');
|
|
120
|
+
expect(hit).toBe(false);
|
|
121
|
+
expect(el.value).toBe('');
|
|
122
|
+
expect(changeCount).toBe(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('clear() resets value + fires change with null option', async () => {
|
|
126
|
+
const el = mount(`
|
|
127
|
+
<combobox-ui value="a">
|
|
128
|
+
<option value="a">Alpha</option>
|
|
129
|
+
<option value="b">Beta</option>
|
|
130
|
+
</combobox-ui>
|
|
131
|
+
`);
|
|
132
|
+
await tick();
|
|
133
|
+
let changeDetail = null;
|
|
134
|
+
el.addEventListener('change', (e) => { changeDetail = e.detail; });
|
|
135
|
+
el.clear();
|
|
136
|
+
await tick();
|
|
137
|
+
expect(el.value).toBe('');
|
|
138
|
+
expect(changeDetail.option).toBeNull();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('renders aria-selected on the currently committed option', async () => {
|
|
142
|
+
const el = mount(`
|
|
143
|
+
<combobox-ui value="b">
|
|
144
|
+
<option value="a">Alpha</option>
|
|
145
|
+
<option value="b">Beta</option>
|
|
146
|
+
</combobox-ui>
|
|
147
|
+
`);
|
|
148
|
+
await tick();
|
|
149
|
+
const opts = el.querySelectorAll('[role="option"]');
|
|
150
|
+
const selected = Array.from(opts).filter((o) => o.getAttribute('aria-selected') === 'true');
|
|
151
|
+
expect(selected.length).toBe(1);
|
|
152
|
+
expect(selected[0].dataset.value).toBe('b');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('exposes UICombobox extending UIFormElement (form participation)', async () => {
|
|
156
|
+
const el = mount(`
|
|
157
|
+
<combobox-ui name="x">
|
|
158
|
+
<option value="a">Alpha</option>
|
|
159
|
+
</combobox-ui>
|
|
160
|
+
`);
|
|
161
|
+
await tick();
|
|
162
|
+
expect(typeof el.checkValidity).toBe('function');
|
|
163
|
+
expect(typeof el.syncValue).toBe('function');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('disabled options carry aria-disabled and are skipped by [role="option"][not aria-disabled]', async () => {
|
|
167
|
+
const el = mount(`
|
|
168
|
+
<combobox-ui>
|
|
169
|
+
<option value="a">Alpha</option>
|
|
170
|
+
<option value="b" disabled>Beta</option>
|
|
171
|
+
<option value="c">Gamma</option>
|
|
172
|
+
</combobox-ui>
|
|
173
|
+
`);
|
|
174
|
+
await tick();
|
|
175
|
+
const opts = el.querySelectorAll('[role="option"]');
|
|
176
|
+
expect(opts.length).toBe(3);
|
|
177
|
+
expect(opts[1].getAttribute('aria-disabled')).toBe('true');
|
|
178
|
+
const navigable = el.querySelectorAll('[role="option"]:not([aria-disabled="true"])');
|
|
179
|
+
expect(navigable.length).toBe(2);
|
|
180
|
+
});
|
|
181
|
+
});
|