@aurodesignsystem-dev/auro-formkit 0.0.0-pr1544.0 → 0.0.0-pr1544.2
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/components/checkbox/demo/customize.min.js +1 -1
- package/components/checkbox/demo/getting-started.min.js +1 -1
- package/components/checkbox/demo/index.min.js +1 -1
- package/components/checkbox/dist/index.js +1 -1
- package/components/checkbox/dist/registered.js +1 -1
- package/components/combobox/demo/api.md +1 -1
- package/components/combobox/demo/customize.min.js +91 -22
- package/components/combobox/demo/getting-started.min.js +91 -22
- package/components/combobox/demo/index.md +1 -0
- package/components/combobox/demo/index.min.js +91 -22
- package/components/combobox/dist/auro-combobox.d.ts +1 -1
- package/components/combobox/dist/index.js +4 -4
- package/components/combobox/dist/registered.js +4 -4
- package/components/counter/demo/customize.min.js +2 -2
- package/components/counter/demo/index.min.js +2 -2
- package/components/counter/dist/index.js +2 -2
- package/components/counter/dist/registered.js +2 -2
- package/components/datepicker/demo/customize.md +29 -0
- package/components/datepicker/demo/customize.min.js +8 -4
- package/components/datepicker/demo/index.min.js +8 -4
- package/components/datepicker/dist/index.js +8 -4
- package/components/datepicker/dist/registered.js +8 -4
- package/components/dropdown/demo/customize.min.js +1 -1
- package/components/dropdown/demo/getting-started.min.js +1 -1
- package/components/dropdown/demo/index.min.js +1 -1
- package/components/dropdown/dist/index.js +1 -1
- package/components/dropdown/dist/registered.js +1 -1
- package/components/form/demo/customize.min.js +111 -34
- package/components/form/demo/getting-started.min.js +111 -34
- package/components/form/demo/index.min.js +111 -34
- package/components/form/demo/registerDemoDeps.min.js +111 -34
- package/components/input/demo/customize.min.js +1 -1
- package/components/input/demo/getting-started.min.js +1 -1
- package/components/input/demo/index.min.js +1 -1
- package/components/input/dist/index.js +1 -1
- package/components/input/dist/registered.js +1 -1
- package/components/menu/demo/accessibility.md +4 -4
- package/components/menu/demo/api.md +26 -19
- package/components/menu/demo/css-only.md +26 -19
- package/components/menu/demo/customize.md +307 -47
- package/components/menu/demo/design.md +1 -1
- package/components/menu/demo/getting-started.md +144 -7
- package/components/menu/demo/index.min.js +214 -19
- package/components/menu/demo/keyboard-behavior.md +83 -4
- package/components/menu/demo/voiceover.md +12 -8
- package/components/menu/demo/why-menu.md +8 -9
- package/components/menu/dist/auro-menu-utils.d.ts +19 -0
- package/components/menu/dist/auro-menu.d.ts +4 -8
- package/components/menu/dist/auro-menuoption.d.ts +12 -0
- package/components/menu/dist/index.js +87 -18
- package/components/menu/dist/registered.js +87 -18
- package/components/radio/demo/customize.min.js +1 -1
- package/components/radio/demo/getting-started.min.js +1 -1
- package/components/radio/demo/index.min.js +1 -1
- package/components/radio/dist/index.js +1 -1
- package/components/radio/dist/registered.js +1 -1
- package/components/select/demo/api.md +2 -2
- package/components/select/demo/customize.md +5 -2
- package/components/select/demo/customize.min.js +94 -21
- package/components/select/demo/design.md +10 -10
- package/components/select/demo/getting-started.md +1 -1
- package/components/select/demo/getting-started.min.js +94 -21
- package/components/select/demo/index.md +2 -2
- package/components/select/demo/index.min.js +94 -21
- package/components/select/demo/keyboard-behavior.md +49 -18
- package/components/select/dist/auro-select.d.ts +6 -2
- package/components/select/dist/index.js +7 -3
- package/components/select/dist/registered.js +7 -3
- package/custom-elements.json +106 -32
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<auro-header level="1" id="overview">Menu - VoiceOver Behavior</auro-header>
|
|
2
2
|
<p>This page documents the VoiceOver experience when using the <code><auro-menu></code> component. It covers announcements and interactions for menu navigation and option selection.</p>
|
|
3
|
+
<div class="note"><strong>Note:</strong> In the standard integrations (<code><auro-select></code>, <code><auro-combobox></code>), keyboard focus stays on the parent trigger/input — it does <strong>not</strong> move into the menu. The parent captures keys and drives the menu, and the active option is announced via <code>aria-activedescendant</code> on the trigger rather than by moving DOM focus between options. The focus-on-the-menu behavior described alongside the standalone case below only applies when an author makes <code><auro-menu></code> itself focusable (for example, <code>tabindex="0"</code>).</p>
|
|
3
4
|
<auro-header level="2" id="voiceOverInteractiveAnnouncements">Interactive Announcements</auro-header>
|
|
4
5
|
<auro-header level="3" id="voiceOverMenuContainer">Menu Container</auro-header>
|
|
5
|
-
<p>When
|
|
6
|
+
<p>When the listbox is exposed — the parent's dropdown opens, or a standalone focusable menu receives focus — VoiceOver announces:</p>
|
|
6
7
|
<ol>
|
|
7
8
|
<li><strong>Role:</strong> <em>"list box"</em> — set on the root <code><auro-menu></code>; nested menus use <code>role="group"</code> with <code>aria-label="submenu"</code>.</li>
|
|
8
9
|
<li><strong>Item count:</strong> The number of visible options (e.g., <em>"5 items"</em>)</li>
|
|
@@ -20,17 +21,20 @@
|
|
|
20
21
|
</ol>
|
|
21
22
|
<div class="note"><strong>Note:</strong> <code><auro-menuoption></code> is not directly focusable — <code>tabindex="-1"</code> keeps it out of the tab order. In a select or combobox context, focus stays on the parent trigger and the active option is pointed at via <code>aria-activedescendant</code> (using <code>ariaActiveDescendantElement</code> to cross the shadow-root boundary).</div>
|
|
22
23
|
<auro-header level="3" id="voiceOverSelection">Selection</auro-header>
|
|
23
|
-
<p>
|
|
24
|
+
<p>In a select or combobox context, the parent captures Enter (and Tab) on its trigger/input and commits the active option through the menu; the menu itself does not receive the key. When the selection is committed:</p>
|
|
24
25
|
<ul>
|
|
25
26
|
<li>VoiceOver announces the option label followed by <em>"selected"</em></li>
|
|
26
27
|
<li>In multi-select mode, previously selected options remain selected and their state is preserved</li>
|
|
27
|
-
</ul>
|
|
28
|
+
</ul>
|
|
29
|
+
<p>A standalone focusable menu (see the note at the top of this page) handles Enter and Tab on its own host, producing the same selection announcement.</p>
|
|
28
30
|
<auro-header level="2" id="voiceOverWorkflow">Navigation Workflow</auro-header>
|
|
31
|
+
<p>In the standard select/combobox integrations, focus remains on the parent trigger/input throughout — the menu is a visually presented listbox the parent points into, not a place focus travels to:</p>
|
|
29
32
|
<ol>
|
|
30
|
-
<li><strong>
|
|
31
|
-
<li><strong>Navigate options:</strong>
|
|
32
|
-
<li><strong>Select an option:</strong>
|
|
33
|
-
<li><strong>Exit:</strong> Tab
|
|
34
|
-
</ol>
|
|
33
|
+
<li><strong>Open the listbox:</strong> Activating the trigger (or typing in a combobox) exposes the menu; VoiceOver announces the listbox role and item count</li>
|
|
34
|
+
<li><strong>Navigate options:</strong> Arrow Down/Up keys pressed on the trigger move the active option; <code>aria-activedescendant</code> updates and VoiceOver announces each option's label, role, and position</li>
|
|
35
|
+
<li><strong>Select an option:</strong> Enter (captured by the parent) commits the active option; VoiceOver confirms the selection</li>
|
|
36
|
+
<li><strong>Exit:</strong> Escape and Tab are handled by the parent — Escape closes the listbox, and Tab commits the active option before moving focus to the next element in the tab sequence</li>
|
|
37
|
+
</ol>
|
|
38
|
+
<p>A standalone focusable menu follows the same active-option model, except focus is on the menu host and the menu handles Arrow, Enter, and Tab directly. See the menu's key events documentation for the full standalone key table.</p>
|
|
35
39
|
<auro-header level="2" id="voiceOverNestedMenus">Nested Menus</auro-header>
|
|
36
40
|
<p>When a nested <code>auro-menu</code> is encountered, VoiceOver announces a new listbox context. Each nested menu maintains its own option count and position tracking.</p>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<p>Custom list implementations often fail to provide correct ARIA semantics, keyboard navigation, or screen reader announcements.</p>
|
|
5
5
|
<p><code>auro-menu</code> provides:</p>
|
|
6
6
|
<ul>
|
|
7
|
-
<li><strong>Keyboard navigation</strong> — Arrow
|
|
7
|
+
<li><strong>Keyboard navigation</strong> — Arrow Up/Down move the active option; Enter selects it; Tab selects and moves focus out of the menu.</li>
|
|
8
8
|
<li><strong>Active option tracking</strong> — The currently highlighted option is tracked programmatically, enabling <code>aria-activedescendant</code> patterns in parent components like <code>auro-select</code> and <code>auro-combobox</code>.</li>
|
|
9
9
|
<li><strong>Multi-select semantics</strong> — When <code>multiSelect</code> is enabled, <code>aria-multiselectable</code> is set on the menu.</li>
|
|
10
10
|
<li><strong>Loading state</strong> — <code>aria-busy</code> is set during loading, with customizable loading icon and text slots.</li>
|
|
@@ -24,15 +24,14 @@
|
|
|
24
24
|
<ul>
|
|
25
25
|
<li>Checkmark indicators for each selected option</li>
|
|
26
26
|
<li>A <code>value</code> that returns a JSON-stringified array of selections</li>
|
|
27
|
-
<li
|
|
28
|
-
<li><code>
|
|
29
|
-
<li><code>clearSelection()</code> and <code>reset()</code> methods for programmatic control</li>
|
|
27
|
+
<li>Click-to-toggle behavior that deselects an already-selected option</li>
|
|
28
|
+
<li><code>reset()</code> method for programmatic control</li>
|
|
30
29
|
</ul>
|
|
31
30
|
<auro-header level="2" id="searchAndFiltering">Search and filtering</auro-header>
|
|
32
31
|
<p>Native menus have no concept of filtering or highlighting matched text.</p>
|
|
33
32
|
<p><code>auro-menu</code> supports:</p>
|
|
34
33
|
<ul>
|
|
35
|
-
<li><code>matchWord</code> to highlight matching portions of option text as the user types (used by <code>auro-combobox</code> for real-time filtering)</li>
|
|
34
|
+
<li><auro-hyperlink href="customize#matchWord"><code>matchWord</code></auro-hyperlink> to highlight matching portions of option text as the user types (used by <code>auro-combobox</code> for real-time filtering)</li>
|
|
36
35
|
<li>Programmatic <code>navigateOptions(direction)</code> for keyboard-driven traversal</li>
|
|
37
36
|
</ul>
|
|
38
37
|
<auro-header level="2" id="loadingState">Loading state</auro-header>
|
|
@@ -88,7 +87,7 @@
|
|
|
88
87
|
<tr>
|
|
89
88
|
<td>Keyboard navigation</td>
|
|
90
89
|
<td>Browser-dependent</td>
|
|
91
|
-
<td>
|
|
90
|
+
<td>Arrow Up/Down, Enter, Tab</td>
|
|
92
91
|
</tr>
|
|
93
92
|
<tr>
|
|
94
93
|
<td>Composable primitive</td>
|
|
@@ -96,9 +95,9 @@
|
|
|
96
95
|
<td>Foundation for select and combobox</td>
|
|
97
96
|
</tr>
|
|
98
97
|
<tr>
|
|
99
|
-
<td>
|
|
100
|
-
<td>
|
|
101
|
-
<td
|
|
98
|
+
<td>Multi-select deselect</td>
|
|
99
|
+
<td>Ctrl+click list box</td>
|
|
100
|
+
<td>Click-to-toggle in multi-select</td>
|
|
102
101
|
</tr>
|
|
103
102
|
</tbody>
|
|
104
103
|
</table>
|
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
* or if the value cannot be parsed into an array from a JSON string.
|
|
11
11
|
*/
|
|
12
12
|
export function arrayConverter(value: any): any[] | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Serializes a multi-select value array back into the String `value` property.
|
|
15
|
+
* An empty (or missing) array collapses to `undefined` so an emptied selection
|
|
16
|
+
* clears `value` rather than reflecting a `"[]"` attribute.
|
|
17
|
+
* @private
|
|
18
|
+
* @param {Array<string>|undefined} values - The selected values.
|
|
19
|
+
* @returns {string|undefined} JSON string of the values, or undefined when empty.
|
|
20
|
+
*/
|
|
21
|
+
export function serializeMultiSelectValue(values: Array<string> | undefined): string | undefined;
|
|
13
22
|
/**
|
|
14
23
|
* Compare two arrays for equality.
|
|
15
24
|
* @private
|
|
@@ -25,6 +34,16 @@ export function arraysAreEqual(arr1: any[], arr2: any[]): boolean;
|
|
|
25
34
|
* @returns {boolean} True if option is interactive.
|
|
26
35
|
*/
|
|
27
36
|
export function isOptionInteractive(option: HTMLElement): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Validates if an option may be selected by matching a programmatic value.
|
|
39
|
+
* Unlike `isOptionInteractive`, `hidden` is allowed: the combobox toggles
|
|
40
|
+
* `hidden` as its type-ahead filter, so a filtered-out option is still a
|
|
41
|
+
* valid programmatic selection. Only disabled and static options — which are
|
|
42
|
+
* never selectable — are rejected.
|
|
43
|
+
* @param {HTMLElement} option - The option to check.
|
|
44
|
+
* @returns {boolean} True if option can be selected by value.
|
|
45
|
+
*/
|
|
46
|
+
export function isSelectableByValue(option: HTMLElement): boolean;
|
|
28
47
|
/**
|
|
29
48
|
* Helper method to dispatch custom events.
|
|
30
49
|
* @param {HTMLElement} element - Element to dispatch event from.
|
|
@@ -60,21 +60,17 @@ export class AuroMenu extends AuroElement {
|
|
|
60
60
|
reflect: boolean;
|
|
61
61
|
attribute: string;
|
|
62
62
|
};
|
|
63
|
-
/**
|
|
64
|
-
* Specifies the current active menuOption.
|
|
65
|
-
*/
|
|
66
63
|
optionActive: {
|
|
67
64
|
type: ObjectConstructor;
|
|
68
|
-
attribute:
|
|
65
|
+
attribute: boolean;
|
|
69
66
|
};
|
|
70
|
-
/**
|
|
71
|
-
* An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements.
|
|
72
|
-
*/
|
|
73
67
|
optionSelected: {
|
|
74
68
|
type: ObjectConstructor;
|
|
69
|
+
attribute: boolean;
|
|
75
70
|
};
|
|
76
71
|
/**
|
|
77
72
|
* The value of the selected option. In multi-select mode, this is a JSON stringified array of selected option values.
|
|
73
|
+
* Options marked `disabled` or `static` are not selectable by value; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared (`optionSelected` becomes `undefined`) and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped from the value and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option.
|
|
78
74
|
*/
|
|
79
75
|
value: {
|
|
80
76
|
type: StringConstructor;
|
|
@@ -171,7 +167,7 @@ export class AuroMenu extends AuroElement {
|
|
|
171
167
|
*/
|
|
172
168
|
private get formattedValue();
|
|
173
169
|
/**
|
|
174
|
-
* Selects options by value.
|
|
170
|
+
* Selects options by value. Options marked `disabled` or `static` are not selectable; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option. Passing `undefined`, `null`, an empty string, or an empty array clears the selection without dispatching a failure.
|
|
175
171
|
* @param {string|string[]|undefined|null} value - The value(s) to select.
|
|
176
172
|
* @public
|
|
177
173
|
*/
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* The `auro-menuoption` element provides users a way to define a menu option.
|
|
3
3
|
* @customElement auro-menuoption
|
|
4
4
|
*
|
|
5
|
+
* @attr {Boolean} static - When present, marks the option as non-interactive — it renders but is skipped during keyboard navigation and cannot be selected. Useful for section headers, informational rows inside a menu, or attaching event listeners.
|
|
5
6
|
* @slot default - The default slot for the menu option text.
|
|
6
7
|
*
|
|
7
8
|
* @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
|
|
@@ -13,6 +14,11 @@ export class AuroMenuOption extends AuroElement {
|
|
|
13
14
|
type: BooleanConstructor;
|
|
14
15
|
reflect: boolean;
|
|
15
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* **Deprecated.** Use the `value` attribute on `auro-menu` to set the selected option when the menu renders (or call `menu.selectByValue(value)` programmatically). Support for the child-level `selected` attribute will be removed in a future major release.
|
|
19
|
+
*
|
|
20
|
+
* @deprecated Use the `value` attribute on `auro-menu` instead.
|
|
21
|
+
*/
|
|
16
22
|
selected: {
|
|
17
23
|
type: BooleanConstructor;
|
|
18
24
|
reflect: boolean;
|
|
@@ -45,6 +51,11 @@ export class AuroMenuOption extends AuroElement {
|
|
|
45
51
|
reflect: boolean;
|
|
46
52
|
attribute: string;
|
|
47
53
|
};
|
|
54
|
+
/** When true, this option is excluded from `matchWord` DOM rewriting — useful for utility rows (e.g., "Add new…") that must render identically regardless of the current filter. */
|
|
55
|
+
persistent: {
|
|
56
|
+
type: BooleanConstructor;
|
|
57
|
+
reflect: boolean;
|
|
58
|
+
};
|
|
48
59
|
layout: {
|
|
49
60
|
type: StringConstructor;
|
|
50
61
|
attribute: string;
|
|
@@ -74,6 +85,7 @@ export class AuroMenuOption extends AuroElement {
|
|
|
74
85
|
noCheckmark: boolean;
|
|
75
86
|
disabled: boolean;
|
|
76
87
|
noMatch: boolean;
|
|
88
|
+
persistent: boolean;
|
|
77
89
|
/**
|
|
78
90
|
* @private
|
|
79
91
|
*/
|
|
@@ -241,6 +241,18 @@ function arrayConverter(value) {
|
|
|
241
241
|
throw new Error('Invalid value: Input must be an array or undefined');
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Serializes a multi-select value array back into the String `value` property.
|
|
246
|
+
* An empty (or missing) array collapses to `undefined` so an emptied selection
|
|
247
|
+
* clears `value` rather than reflecting a `"[]"` attribute.
|
|
248
|
+
* @private
|
|
249
|
+
* @param {Array<string>|undefined} values - The selected values.
|
|
250
|
+
* @returns {string|undefined} JSON string of the values, or undefined when empty.
|
|
251
|
+
*/
|
|
252
|
+
function serializeMultiSelectValue(values) {
|
|
253
|
+
return values && values.length > 0 ? JSON.stringify(values) : undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
244
256
|
/**
|
|
245
257
|
* Validates if an option can be interacted with.
|
|
246
258
|
* @private
|
|
@@ -253,6 +265,20 @@ function isOptionInteractive(option) {
|
|
|
253
265
|
!option.hasAttribute('static');
|
|
254
266
|
}
|
|
255
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Validates if an option may be selected by matching a programmatic value.
|
|
270
|
+
* Unlike `isOptionInteractive`, `hidden` is allowed: the combobox toggles
|
|
271
|
+
* `hidden` as its type-ahead filter, so a filtered-out option is still a
|
|
272
|
+
* valid programmatic selection. Only disabled and static options — which are
|
|
273
|
+
* never selectable — are rejected.
|
|
274
|
+
* @param {HTMLElement} option - The option to check.
|
|
275
|
+
* @returns {boolean} True if option can be selected by value.
|
|
276
|
+
*/
|
|
277
|
+
function isSelectableByValue(option) {
|
|
278
|
+
return !option.hasAttribute('disabled') &&
|
|
279
|
+
!option.hasAttribute('static');
|
|
280
|
+
}
|
|
281
|
+
|
|
256
282
|
/**
|
|
257
283
|
* Helper method to dispatch custom events.
|
|
258
284
|
* @param {HTMLElement} element - Element to dispatch event from.
|
|
@@ -422,22 +448,26 @@ class AuroMenu extends AuroElement {
|
|
|
422
448
|
|
|
423
449
|
/**
|
|
424
450
|
* Specifies the current active menuOption.
|
|
451
|
+
* @readonly
|
|
425
452
|
*/
|
|
426
453
|
optionActive: {
|
|
427
454
|
type: Object,
|
|
428
|
-
attribute:
|
|
455
|
+
attribute: false
|
|
429
456
|
},
|
|
430
457
|
|
|
431
458
|
/**
|
|
432
|
-
*
|
|
459
|
+
* The currently selected menu option(s). In single-select mode this is a single `HTMLElement` (or `undefined` when nothing is selected). In multi-select mode this is an array of `HTMLElement`s.
|
|
460
|
+
* @readonly
|
|
433
461
|
*/
|
|
434
462
|
optionSelected: {
|
|
435
463
|
// Allow HTMLElement, HTMLElement[] arrays and undefined
|
|
436
|
-
type: Object
|
|
464
|
+
type: Object,
|
|
465
|
+
attribute: false
|
|
437
466
|
},
|
|
438
467
|
|
|
439
468
|
/**
|
|
440
469
|
* The value of the selected option. In multi-select mode, this is a JSON stringified array of selected option values.
|
|
470
|
+
* Options marked `disabled` or `static` are not selectable by value; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared (`optionSelected` becomes `undefined`) and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped from the value and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option.
|
|
441
471
|
*/
|
|
442
472
|
value: {
|
|
443
473
|
type: String,
|
|
@@ -561,7 +591,7 @@ class AuroMenu extends AuroElement {
|
|
|
561
591
|
}
|
|
562
592
|
|
|
563
593
|
/**
|
|
564
|
-
* Selects options by value.
|
|
594
|
+
* Selects options by value. Options marked `disabled` or `static` are not selectable; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option. Passing `undefined`, `null`, an empty string, or an empty array clears the selection without dispatching a failure.
|
|
565
595
|
* @param {string|string[]|undefined|null} value - The value(s) to select.
|
|
566
596
|
* @public
|
|
567
597
|
*/
|
|
@@ -646,6 +676,11 @@ class AuroMenu extends AuroElement {
|
|
|
646
676
|
this.initItems();
|
|
647
677
|
}
|
|
648
678
|
|
|
679
|
+
// Set when reconciliation reassigns `value` below. That reassignment schedules a
|
|
680
|
+
// second updated() cycle, so the `event`-attribute dispatch is deferred to that
|
|
681
|
+
// cycle to avoid firing option custom events twice on the same selection.
|
|
682
|
+
let valueReconciled = false;
|
|
683
|
+
|
|
649
684
|
// Handle null/undefined/empty case — empty/whitespace strings clear selection
|
|
650
685
|
// consistently with selectByValue(''), and avoid downstream `.includes('')` matches.
|
|
651
686
|
if (this.value === undefined || this.value === null || (typeof this.value === 'string' && this.value.trim() === '')) {
|
|
@@ -658,11 +693,31 @@ class AuroMenu extends AuroElement {
|
|
|
658
693
|
// Defensive default: `formattedValue` can be undefined for unexpected value types,
|
|
659
694
|
// and calling `.includes` on undefined would throw during reconciliation.
|
|
660
695
|
const valueArray = this.formattedValue || [];
|
|
661
|
-
const matchingOptions = this.items ? this.items.filter((item) => valueArray.includes(item.value)) : [];
|
|
696
|
+
const matchingOptions = this.items ? this.items.filter((item) => isSelectableByValue(item) && valueArray.includes(item.value)) : [];
|
|
662
697
|
newSelected = matchingOptions.length > 0 ? matchingOptions : undefined;
|
|
698
|
+
|
|
699
|
+
// Reconcile `value` with the selectable set. Drop only entries whose option is
|
|
700
|
+
// loaded but non-selectable (disabled/static) — leaving them would desync `value`
|
|
701
|
+
// from `optionSelected`, and the toggle handlers rebuild `value` from `formattedValue`,
|
|
702
|
+
// so the rejected entry would resurface on the next select/deselect. Entries with no
|
|
703
|
+
// matching item yet are preserved so async preselection still works once options render.
|
|
704
|
+
const rejectedValues = this.items
|
|
705
|
+
? this.items.filter((item) => !isSelectableByValue(item) && valueArray.includes(item.value)).map((item) => item.value)
|
|
706
|
+
: [];
|
|
707
|
+
if (rejectedValues.length > 0) {
|
|
708
|
+
const reconciled = valueArray.filter((val) => !rejectedValues.includes(val));
|
|
709
|
+
this.value = serializeMultiSelectValue(reconciled);
|
|
710
|
+
valueReconciled = true;
|
|
711
|
+
}
|
|
663
712
|
} else {
|
|
664
|
-
// In single-select mode, this.value should be a string
|
|
665
|
-
|
|
713
|
+
// In single-select mode, this.value should be a string. Reject
|
|
714
|
+
// disabled/static options so a programmatic value pointing at a
|
|
715
|
+
// non-selectable option falls through to the no-match path below
|
|
716
|
+
// (dispatching auroMenu-selectValueFailure) instead of pinning it.
|
|
717
|
+
// `hidden` is intentionally NOT excluded: the combobox toggles
|
|
718
|
+
// `hidden` as its type-ahead filter, so a filtered-out option is
|
|
719
|
+
// still a valid programmatic selection.
|
|
720
|
+
const matchingOption = this.items ? this.items.find((item) => isSelectableByValue(item) && item.value === this.value) : undefined;
|
|
666
721
|
|
|
667
722
|
if (matchingOption) {
|
|
668
723
|
newSelected = matchingOption;
|
|
@@ -704,8 +759,9 @@ class AuroMenu extends AuroElement {
|
|
|
704
759
|
]
|
|
705
760
|
]));
|
|
706
761
|
|
|
707
|
-
// Notify of changes
|
|
708
|
-
|
|
762
|
+
// Notify of changes. Skip when reconciliation just reassigned `value`: the
|
|
763
|
+
// follow-on update cycle re-runs this branch and fires the events exactly once.
|
|
764
|
+
if (this.optionSelected !== undefined && !valueReconciled) {
|
|
709
765
|
const selected = Array.isArray(this.optionSelected) ? this.optionSelected : [this.optionSelected];
|
|
710
766
|
selected.forEach((opt) => {
|
|
711
767
|
if (opt.hasAttribute('event')) {
|
|
@@ -918,7 +974,7 @@ class AuroMenu extends AuroElement {
|
|
|
918
974
|
const currentSelected = this.optionSelected || [];
|
|
919
975
|
|
|
920
976
|
if (!currentValue.includes(option.value)) {
|
|
921
|
-
this.value =
|
|
977
|
+
this.value = serializeMultiSelectValue([
|
|
922
978
|
...currentValue,
|
|
923
979
|
option.value
|
|
924
980
|
]);
|
|
@@ -944,15 +1000,9 @@ class AuroMenu extends AuroElement {
|
|
|
944
1000
|
*/
|
|
945
1001
|
handleDeselectState(option) {
|
|
946
1002
|
if (this.multiSelect) {
|
|
947
|
-
// Remove this option from array
|
|
1003
|
+
// Remove this option from array; an empty result collapses `value` to undefined.
|
|
948
1004
|
const newFormattedValue = (this.formattedValue || []).filter((val) => val !== option.value);
|
|
949
|
-
|
|
950
|
-
// If array is empty after removal, set back to undefined
|
|
951
|
-
if (newFormattedValue && newFormattedValue.length === 0) {
|
|
952
|
-
this.value = undefined;
|
|
953
|
-
} else {
|
|
954
|
-
this.value = JSON.stringify(newFormattedValue);
|
|
955
|
-
}
|
|
1005
|
+
this.value = serializeMultiSelectValue(newFormattedValue);
|
|
956
1006
|
|
|
957
1007
|
this.optionSelected = this.optionSelected.filter((val) => val !== option);
|
|
958
1008
|
if (this.optionSelected.length === 0) {
|
|
@@ -999,6 +1049,11 @@ class AuroMenu extends AuroElement {
|
|
|
999
1049
|
this.optionSelected = undefined;
|
|
1000
1050
|
this._index = -1;
|
|
1001
1051
|
|
|
1052
|
+
// Clear active option state so a follow-up open/navigation starts fresh
|
|
1053
|
+
// rather than reusing a stale reference from before the reset.
|
|
1054
|
+
this.items?.forEach((item) => item.classList.remove('active'));
|
|
1055
|
+
this.optionActive = undefined;
|
|
1056
|
+
|
|
1002
1057
|
// Reset UI state
|
|
1003
1058
|
this.updateItemsState(new Map([
|
|
1004
1059
|
[
|
|
@@ -1448,6 +1503,7 @@ let menuOptionIdCounter = 0;
|
|
|
1448
1503
|
* The `auro-menuoption` element provides users a way to define a menu option.
|
|
1449
1504
|
* @customElement auro-menuoption
|
|
1450
1505
|
*
|
|
1506
|
+
* @attr {Boolean} static - When present, marks the option as non-interactive — it renders but is skipped during keyboard navigation and cannot be selected. Useful for section headers, informational rows inside a menu, or attaching event listeners.
|
|
1451
1507
|
* @slot default - The default slot for the menu option text.
|
|
1452
1508
|
*
|
|
1453
1509
|
* @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
|
|
@@ -1477,6 +1533,7 @@ class AuroMenuOption extends AuroElement {
|
|
|
1477
1533
|
this.noCheckmark = false;
|
|
1478
1534
|
this.disabled = false;
|
|
1479
1535
|
this.noMatch = false;
|
|
1536
|
+
this.persistent = false;
|
|
1480
1537
|
|
|
1481
1538
|
/**
|
|
1482
1539
|
* @private
|
|
@@ -1498,6 +1555,12 @@ class AuroMenuOption extends AuroElement {
|
|
|
1498
1555
|
type: Boolean,
|
|
1499
1556
|
reflect: true
|
|
1500
1557
|
},
|
|
1558
|
+
|
|
1559
|
+
/**
|
|
1560
|
+
* **Deprecated.** Use the `value` attribute on `auro-menu` to set the selected option when the menu renders (or call `menu.selectByValue(value)` programmatically). Support for the child-level `selected` attribute will be removed in a future major release.
|
|
1561
|
+
*
|
|
1562
|
+
* @deprecated Use the `value` attribute on `auro-menu` instead.
|
|
1563
|
+
*/
|
|
1501
1564
|
selected: {
|
|
1502
1565
|
type: Boolean,
|
|
1503
1566
|
reflect: true
|
|
@@ -1532,6 +1595,12 @@ class AuroMenuOption extends AuroElement {
|
|
|
1532
1595
|
reflect: true,
|
|
1533
1596
|
attribute: 'nomatch'
|
|
1534
1597
|
},
|
|
1598
|
+
|
|
1599
|
+
/** When true, this option is excluded from `matchWord` DOM rewriting — useful for utility rows (e.g., "Add new…") that must render identically regardless of the current filter. */
|
|
1600
|
+
persistent: {
|
|
1601
|
+
type: Boolean,
|
|
1602
|
+
reflect: true
|
|
1603
|
+
},
|
|
1535
1604
|
};
|
|
1536
1605
|
}
|
|
1537
1606
|
|
|
@@ -200,6 +200,18 @@ class AuroLibraryRuntimeUtils {
|
|
|
200
200
|
// See LICENSE in the project root for license information.
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Serializes a multi-select value array back into the String `value` property.
|
|
205
|
+
* An empty (or missing) array collapses to `undefined` so an emptied selection
|
|
206
|
+
* clears `value` rather than reflecting a `"[]"` attribute.
|
|
207
|
+
* @private
|
|
208
|
+
* @param {Array<string>|undefined} values - The selected values.
|
|
209
|
+
* @returns {string|undefined} JSON string of the values, or undefined when empty.
|
|
210
|
+
*/
|
|
211
|
+
function serializeMultiSelectValue(values) {
|
|
212
|
+
return values && values.length > 0 ? JSON.stringify(values) : undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
203
215
|
/**
|
|
204
216
|
* Validates if an option can be interacted with.
|
|
205
217
|
* @private
|
|
@@ -212,6 +224,20 @@ function isOptionInteractive(option) {
|
|
|
212
224
|
!option.hasAttribute('static');
|
|
213
225
|
}
|
|
214
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Validates if an option may be selected by matching a programmatic value.
|
|
229
|
+
* Unlike `isOptionInteractive`, `hidden` is allowed: the combobox toggles
|
|
230
|
+
* `hidden` as its type-ahead filter, so a filtered-out option is still a
|
|
231
|
+
* valid programmatic selection. Only disabled and static options — which are
|
|
232
|
+
* never selectable — are rejected.
|
|
233
|
+
* @param {HTMLElement} option - The option to check.
|
|
234
|
+
* @returns {boolean} True if option can be selected by value.
|
|
235
|
+
*/
|
|
236
|
+
function isSelectableByValue(option) {
|
|
237
|
+
return !option.hasAttribute('disabled') &&
|
|
238
|
+
!option.hasAttribute('static');
|
|
239
|
+
}
|
|
240
|
+
|
|
215
241
|
/**
|
|
216
242
|
* Helper method to dispatch custom events.
|
|
217
243
|
* @param {HTMLElement} element - Element to dispatch event from.
|
|
@@ -381,22 +407,26 @@ class AuroMenu extends AuroElement {
|
|
|
381
407
|
|
|
382
408
|
/**
|
|
383
409
|
* Specifies the current active menuOption.
|
|
410
|
+
* @readonly
|
|
384
411
|
*/
|
|
385
412
|
optionActive: {
|
|
386
413
|
type: Object,
|
|
387
|
-
attribute:
|
|
414
|
+
attribute: false
|
|
388
415
|
},
|
|
389
416
|
|
|
390
417
|
/**
|
|
391
|
-
*
|
|
418
|
+
* The currently selected menu option(s). In single-select mode this is a single `HTMLElement` (or `undefined` when nothing is selected). In multi-select mode this is an array of `HTMLElement`s.
|
|
419
|
+
* @readonly
|
|
392
420
|
*/
|
|
393
421
|
optionSelected: {
|
|
394
422
|
// Allow HTMLElement, HTMLElement[] arrays and undefined
|
|
395
|
-
type: Object
|
|
423
|
+
type: Object,
|
|
424
|
+
attribute: false
|
|
396
425
|
},
|
|
397
426
|
|
|
398
427
|
/**
|
|
399
428
|
* The value of the selected option. In multi-select mode, this is a JSON stringified array of selected option values.
|
|
429
|
+
* Options marked `disabled` or `static` are not selectable by value; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared (`optionSelected` becomes `undefined`) and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped from the value and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option.
|
|
400
430
|
*/
|
|
401
431
|
value: {
|
|
402
432
|
type: String,
|
|
@@ -520,7 +550,7 @@ class AuroMenu extends AuroElement {
|
|
|
520
550
|
}
|
|
521
551
|
|
|
522
552
|
/**
|
|
523
|
-
* Selects options by value.
|
|
553
|
+
* Selects options by value. Options marked `disabled` or `static` are not selectable; `hidden` options remain selectable. In single-select mode, if the value matches a non-selectable option the selection is cleared and `auroMenu-selectValueFailure` is dispatched. In multi-select mode, non-selectable entries are dropped and the remaining selectable entries are selected; `auroMenu-selectValueFailure` is dispatched only when none of the entries match a selectable option. Passing `undefined`, `null`, an empty string, or an empty array clears the selection without dispatching a failure.
|
|
524
554
|
* @param {string|string[]|undefined|null} value - The value(s) to select.
|
|
525
555
|
* @public
|
|
526
556
|
*/
|
|
@@ -605,6 +635,11 @@ class AuroMenu extends AuroElement {
|
|
|
605
635
|
this.initItems();
|
|
606
636
|
}
|
|
607
637
|
|
|
638
|
+
// Set when reconciliation reassigns `value` below. That reassignment schedules a
|
|
639
|
+
// second updated() cycle, so the `event`-attribute dispatch is deferred to that
|
|
640
|
+
// cycle to avoid firing option custom events twice on the same selection.
|
|
641
|
+
let valueReconciled = false;
|
|
642
|
+
|
|
608
643
|
// Handle null/undefined/empty case — empty/whitespace strings clear selection
|
|
609
644
|
// consistently with selectByValue(''), and avoid downstream `.includes('')` matches.
|
|
610
645
|
if (this.value === undefined || this.value === null || (typeof this.value === 'string' && this.value.trim() === '')) {
|
|
@@ -617,11 +652,31 @@ class AuroMenu extends AuroElement {
|
|
|
617
652
|
// Defensive default: `formattedValue` can be undefined for unexpected value types,
|
|
618
653
|
// and calling `.includes` on undefined would throw during reconciliation.
|
|
619
654
|
const valueArray = this.formattedValue || [];
|
|
620
|
-
const matchingOptions = this.items ? this.items.filter((item) => valueArray.includes(item.value)) : [];
|
|
655
|
+
const matchingOptions = this.items ? this.items.filter((item) => isSelectableByValue(item) && valueArray.includes(item.value)) : [];
|
|
621
656
|
newSelected = matchingOptions.length > 0 ? matchingOptions : undefined;
|
|
657
|
+
|
|
658
|
+
// Reconcile `value` with the selectable set. Drop only entries whose option is
|
|
659
|
+
// loaded but non-selectable (disabled/static) — leaving them would desync `value`
|
|
660
|
+
// from `optionSelected`, and the toggle handlers rebuild `value` from `formattedValue`,
|
|
661
|
+
// so the rejected entry would resurface on the next select/deselect. Entries with no
|
|
662
|
+
// matching item yet are preserved so async preselection still works once options render.
|
|
663
|
+
const rejectedValues = this.items
|
|
664
|
+
? this.items.filter((item) => !isSelectableByValue(item) && valueArray.includes(item.value)).map((item) => item.value)
|
|
665
|
+
: [];
|
|
666
|
+
if (rejectedValues.length > 0) {
|
|
667
|
+
const reconciled = valueArray.filter((val) => !rejectedValues.includes(val));
|
|
668
|
+
this.value = serializeMultiSelectValue(reconciled);
|
|
669
|
+
valueReconciled = true;
|
|
670
|
+
}
|
|
622
671
|
} else {
|
|
623
|
-
// In single-select mode, this.value should be a string
|
|
624
|
-
|
|
672
|
+
// In single-select mode, this.value should be a string. Reject
|
|
673
|
+
// disabled/static options so a programmatic value pointing at a
|
|
674
|
+
// non-selectable option falls through to the no-match path below
|
|
675
|
+
// (dispatching auroMenu-selectValueFailure) instead of pinning it.
|
|
676
|
+
// `hidden` is intentionally NOT excluded: the combobox toggles
|
|
677
|
+
// `hidden` as its type-ahead filter, so a filtered-out option is
|
|
678
|
+
// still a valid programmatic selection.
|
|
679
|
+
const matchingOption = this.items ? this.items.find((item) => isSelectableByValue(item) && item.value === this.value) : undefined;
|
|
625
680
|
|
|
626
681
|
if (matchingOption) {
|
|
627
682
|
newSelected = matchingOption;
|
|
@@ -663,8 +718,9 @@ class AuroMenu extends AuroElement {
|
|
|
663
718
|
]
|
|
664
719
|
]));
|
|
665
720
|
|
|
666
|
-
// Notify of changes
|
|
667
|
-
|
|
721
|
+
// Notify of changes. Skip when reconciliation just reassigned `value`: the
|
|
722
|
+
// follow-on update cycle re-runs this branch and fires the events exactly once.
|
|
723
|
+
if (this.optionSelected !== undefined && !valueReconciled) {
|
|
668
724
|
const selected = Array.isArray(this.optionSelected) ? this.optionSelected : [this.optionSelected];
|
|
669
725
|
selected.forEach((opt) => {
|
|
670
726
|
if (opt.hasAttribute('event')) {
|
|
@@ -877,7 +933,7 @@ class AuroMenu extends AuroElement {
|
|
|
877
933
|
const currentSelected = this.optionSelected || [];
|
|
878
934
|
|
|
879
935
|
if (!currentValue.includes(option.value)) {
|
|
880
|
-
this.value =
|
|
936
|
+
this.value = serializeMultiSelectValue([
|
|
881
937
|
...currentValue,
|
|
882
938
|
option.value
|
|
883
939
|
]);
|
|
@@ -903,15 +959,9 @@ class AuroMenu extends AuroElement {
|
|
|
903
959
|
*/
|
|
904
960
|
handleDeselectState(option) {
|
|
905
961
|
if (this.multiSelect) {
|
|
906
|
-
// Remove this option from array
|
|
962
|
+
// Remove this option from array; an empty result collapses `value` to undefined.
|
|
907
963
|
const newFormattedValue = (this.formattedValue || []).filter((val) => val !== option.value);
|
|
908
|
-
|
|
909
|
-
// If array is empty after removal, set back to undefined
|
|
910
|
-
if (newFormattedValue && newFormattedValue.length === 0) {
|
|
911
|
-
this.value = undefined;
|
|
912
|
-
} else {
|
|
913
|
-
this.value = JSON.stringify(newFormattedValue);
|
|
914
|
-
}
|
|
964
|
+
this.value = serializeMultiSelectValue(newFormattedValue);
|
|
915
965
|
|
|
916
966
|
this.optionSelected = this.optionSelected.filter((val) => val !== option);
|
|
917
967
|
if (this.optionSelected.length === 0) {
|
|
@@ -958,6 +1008,11 @@ class AuroMenu extends AuroElement {
|
|
|
958
1008
|
this.optionSelected = undefined;
|
|
959
1009
|
this._index = -1;
|
|
960
1010
|
|
|
1011
|
+
// Clear active option state so a follow-up open/navigation starts fresh
|
|
1012
|
+
// rather than reusing a stale reference from before the reset.
|
|
1013
|
+
this.items?.forEach((item) => item.classList.remove('active'));
|
|
1014
|
+
this.optionActive = undefined;
|
|
1015
|
+
|
|
961
1016
|
// Reset UI state
|
|
962
1017
|
this.updateItemsState(new Map([
|
|
963
1018
|
[
|
|
@@ -1407,6 +1462,7 @@ let menuOptionIdCounter = 0;
|
|
|
1407
1462
|
* The `auro-menuoption` element provides users a way to define a menu option.
|
|
1408
1463
|
* @customElement auro-menuoption
|
|
1409
1464
|
*
|
|
1465
|
+
* @attr {Boolean} static - When present, marks the option as non-interactive — it renders but is skipped during keyboard navigation and cannot be selected. Useful for section headers, informational rows inside a menu, or attaching event listeners.
|
|
1410
1466
|
* @slot default - The default slot for the menu option text.
|
|
1411
1467
|
*
|
|
1412
1468
|
* @event auroMenuOption-mouseover - Notifies that this option has been hovered over.
|
|
@@ -1436,6 +1492,7 @@ class AuroMenuOption extends AuroElement {
|
|
|
1436
1492
|
this.noCheckmark = false;
|
|
1437
1493
|
this.disabled = false;
|
|
1438
1494
|
this.noMatch = false;
|
|
1495
|
+
this.persistent = false;
|
|
1439
1496
|
|
|
1440
1497
|
/**
|
|
1441
1498
|
* @private
|
|
@@ -1457,6 +1514,12 @@ class AuroMenuOption extends AuroElement {
|
|
|
1457
1514
|
type: Boolean,
|
|
1458
1515
|
reflect: true
|
|
1459
1516
|
},
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* **Deprecated.** Use the `value` attribute on `auro-menu` to set the selected option when the menu renders (or call `menu.selectByValue(value)` programmatically). Support for the child-level `selected` attribute will be removed in a future major release.
|
|
1520
|
+
*
|
|
1521
|
+
* @deprecated Use the `value` attribute on `auro-menu` instead.
|
|
1522
|
+
*/
|
|
1460
1523
|
selected: {
|
|
1461
1524
|
type: Boolean,
|
|
1462
1525
|
reflect: true
|
|
@@ -1491,6 +1554,12 @@ class AuroMenuOption extends AuroElement {
|
|
|
1491
1554
|
reflect: true,
|
|
1492
1555
|
attribute: 'nomatch'
|
|
1493
1556
|
},
|
|
1557
|
+
|
|
1558
|
+
/** When true, this option is excluded from `matchWord` DOM rewriting — useful for utility rows (e.g., "Add new…") that must render identically regardless of the current filter. */
|
|
1559
|
+
persistent: {
|
|
1560
|
+
type: Boolean,
|
|
1561
|
+
reflect: true
|
|
1562
|
+
},
|
|
1494
1563
|
};
|
|
1495
1564
|
}
|
|
1496
1565
|
|
|
@@ -1227,7 +1227,7 @@ class AuroHelpText extends i$2 {
|
|
|
1227
1227
|
}
|
|
1228
1228
|
}
|
|
1229
1229
|
|
|
1230
|
-
var formkitVersion = '
|
|
1230
|
+
var formkitVersion = '202607082328';
|
|
1231
1231
|
|
|
1232
1232
|
// Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
|
|
1233
1233
|
// See LICENSE in the project root for license information.
|
|
@@ -1227,7 +1227,7 @@ class AuroHelpText extends i$2 {
|
|
|
1227
1227
|
}
|
|
1228
1228
|
}
|
|
1229
1229
|
|
|
1230
|
-
var formkitVersion = '
|
|
1230
|
+
var formkitVersion = '202607082328';
|
|
1231
1231
|
|
|
1232
1232
|
// Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
|
|
1233
1233
|
// See LICENSE in the project root for license information.
|