@aurodesignsystem/auro-formkit 2.0.0-beta.10 → 2.0.0-beta.12
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/.turbo/cache/0c8124a987c1cc05-meta.json +1 -1
- package/.turbo/cache/0cd512cdf86242c7-meta.json +1 -0
- package/.turbo/cache/0cd512cdf86242c7.tar.zst +0 -0
- package/.turbo/cache/123c83cd8727dff3-meta.json +1 -0
- package/.turbo/cache/123c83cd8727dff3.tar.zst +0 -0
- package/.turbo/cache/18129dba20f51b6b-meta.json +1 -1
- package/.turbo/cache/2787020e69f50af2-meta.json +1 -1
- package/.turbo/cache/2a5295c8f561ed84-meta.json +1 -0
- package/.turbo/cache/2c0d681132c153dd-meta.json +1 -1
- package/.turbo/cache/4006a206400d5c7b-meta.json +1 -1
- package/.turbo/cache/492dda333b8d15f1-meta.json +1 -1
- package/.turbo/cache/4e3619d9dfc86809-meta.json +1 -0
- package/.turbo/cache/4e3619d9dfc86809.tar.zst +0 -0
- package/.turbo/cache/50993de942ec15a9-meta.json +1 -1
- package/.turbo/cache/50cd7dcfc9f820c5-meta.json +1 -0
- package/.turbo/cache/50cd7dcfc9f820c5.tar.zst +0 -0
- package/.turbo/cache/51eaa58d5c167de8-meta.json +1 -1
- package/.turbo/cache/5a0d3e26da304c62-meta.json +1 -0
- package/.turbo/cache/5a0d3e26da304c62.tar.zst +0 -0
- package/.turbo/cache/5c960af698582835-meta.json +1 -0
- package/.turbo/cache/5c960af698582835.tar.zst +0 -0
- package/.turbo/cache/5dbbb71dffc3f542-meta.json +1 -0
- package/.turbo/cache/5dbbb71dffc3f542.tar.zst +0 -0
- package/.turbo/cache/6081837e8943b62e-meta.json +1 -1
- package/.turbo/cache/60ad74320c682a2b-meta.json +1 -1
- package/.turbo/cache/61e218aba69cff58-meta.json +1 -1
- package/.turbo/cache/77da375a012de9d0-meta.json +1 -1
- package/.turbo/cache/7964d1656e9e702a-meta.json +1 -0
- package/.turbo/cache/7964d1656e9e702a.tar.zst +0 -0
- package/.turbo/cache/7bf2b06a479d0b30-meta.json +1 -1
- package/.turbo/cache/7c9ca6163e61285c-meta.json +1 -1
- package/.turbo/cache/80aca269cd346fb4-meta.json +1 -0
- package/.turbo/cache/80aca269cd346fb4.tar.zst +0 -0
- package/.turbo/cache/8602fc2bb737a5cf-meta.json +1 -0
- package/.turbo/cache/89e0e7a6148e854f-meta.json +1 -0
- package/.turbo/cache/{83a167e135cb431a.tar.zst → 89e0e7a6148e854f.tar.zst} +0 -0
- package/.turbo/cache/8bb856bd31b5b479-meta.json +1 -1
- package/.turbo/cache/93c887fb93a10daa-meta.json +1 -0
- package/.turbo/cache/93c887fb93a10daa.tar.zst +0 -0
- package/.turbo/cache/94dae2a64e9d8356-meta.json +1 -0
- package/.turbo/cache/97f6fe83b54acf09-meta.json +1 -0
- package/.turbo/cache/97f6fe83b54acf09.tar.zst +0 -0
- package/.turbo/cache/98317b0d14d94df7-meta.json +1 -0
- package/.turbo/cache/98317b0d14d94df7.tar.zst +0 -0
- package/.turbo/cache/98fc8a56e3e13790-meta.json +1 -0
- package/.turbo/cache/98fc8a56e3e13790.tar.zst +0 -0
- package/.turbo/cache/9ae99e8e7bd83d06-meta.json +1 -1
- package/.turbo/cache/9cbcd13b1d031f63-meta.json +1 -0
- package/.turbo/cache/{8af27c076dc010c3.tar.zst → 9cbcd13b1d031f63.tar.zst} +0 -0
- package/.turbo/cache/afbbd49ed1a558b9-meta.json +1 -0
- package/.turbo/cache/b353ce8f6da43dea-meta.json +1 -0
- package/.turbo/cache/b5e6dc7fb9ae1a2f-meta.json +1 -1
- package/.turbo/cache/b6a202cc85cb61a0-meta.json +1 -1
- package/.turbo/cache/b8db059a9b9ccb5d-meta.json +1 -0
- package/.turbo/cache/bc24a38aa1b1a102-meta.json +1 -0
- package/.turbo/cache/be0b95293ea517cc-meta.json +1 -1
- package/.turbo/cache/c3a4f7a3565d6706-meta.json +1 -0
- package/.turbo/cache/c3a4f7a3565d6706.tar.zst +0 -0
- package/.turbo/cache/c44efc9e4ddd8a0e-meta.json +1 -1
- package/.turbo/cache/c6c6411199b68170-meta.json +1 -1
- package/.turbo/cache/c97b043e748e3580-meta.json +1 -0
- package/.turbo/cache/d5db503b2eaf239c-meta.json +1 -1
- package/.turbo/cache/d775555355d6b8fc-meta.json +1 -1
- package/.turbo/cache/d7c3007be148d2a1-meta.json +1 -1
- package/.turbo/cache/dad3d78b33edd9e4-meta.json +1 -1
- package/.turbo/cache/dc597b3ea4f61ec8-meta.json +1 -0
- package/.turbo/cache/dc597b3ea4f61ec8.tar.zst +0 -0
- package/.turbo/cache/df40b180126e5351-meta.json +1 -0
- package/.turbo/cache/{50a29c70b93c57dd.tar.zst → df40b180126e5351.tar.zst} +0 -0
- package/.turbo/cache/e5f217f77c32c93b-meta.json +1 -0
- package/.turbo/cache/e5f217f77c32c93b.tar.zst +0 -0
- package/.turbo/cache/e62cfee068e3ef36-meta.json +1 -1
- package/.turbo/cache/e9e36823f6c98f07-meta.json +1 -1
- package/.turbo/cache/ee1a3c1fe389da51-meta.json +1 -0
- package/.turbo/cache/f3c7b40f2c6a4094-meta.json +1 -0
- package/.turbo/cache/{b22ca87b2f7f9cc2.tar.zst → f3c7b40f2c6a4094.tar.zst} +0 -0
- package/.turbo/cache/f5958c3acb889631-meta.json +1 -0
- package/.turbo/cache/fb3809ac3f90e3b2-meta.json +1 -0
- package/.turbo/cache/{eb1dbe885532c1dc.tar.zst → fb3809ac3f90e3b2.tar.zst} +0 -0
- package/.turbo/cache/fd5ddfa43ebd8e5c-meta.json +1 -0
- package/.turbo/cache/fd5ddfa43ebd8e5c.tar.zst +0 -0
- package/CHANGELOG.md +25 -0
- package/components/checkbox/.turbo/turbo-build.log +3 -3
- package/components/checkbox/.turbo/turbo-bundler.log +3 -3
- package/components/checkbox/README.md +1 -1
- package/components/combobox/.turbo/turbo-build.log +3 -3
- package/components/combobox/README.md +4 -4
- package/components/combobox/demo/api.md +22 -21
- package/components/combobox/demo/api.min.js +2532 -655
- package/components/combobox/demo/index.min.js +2529 -652
- package/components/combobox/dist/auro-combobox.d.ts +70 -80
- package/components/combobox/dist/auro-combobox.d.ts.map +1 -1
- package/components/combobox/dist/index.js +1836 -274
- package/components/combobox/src/auro-combobox.js +156 -101
- package/components/counter/.turbo/turbo-build.log +3 -3
- package/components/counter/.turbo/turbo-bundler.log +3 -3
- package/components/counter/README.md +1 -1
- package/components/datepicker/.turbo/turbo-build.log +3 -3
- package/components/datepicker/README.md +4 -4
- package/components/dropdown/.turbo/turbo-build.log +3 -3
- package/components/dropdown/.turbo/turbo-bundler.log +3 -3
- package/components/dropdown/README.md +1 -1
- package/components/form/.turbo/turbo-build.log +3 -3
- package/components/form/.turbo/turbo-bundler.log +3 -3
- package/components/form/README.md +1 -1
- package/components/input/.turbo/turbo-build.log +3 -3
- package/components/input/.turbo/turbo-bundler.log +3 -3
- package/components/input/README.md +1 -1
- package/components/menu/.turbo/turbo-build.log +5 -3
- package/components/menu/.turbo/turbo-bundler.log +3 -3
- package/components/menu/README.md +1 -1
- package/components/menu/demo/api.md +57 -20
- package/components/menu/demo/api.min.js +620 -305
- package/components/menu/demo/index.min.js +618 -303
- package/components/menu/dist/auro-menu-utils.d.ts +43 -0
- package/components/menu/dist/auro-menu-utils.d.ts.map +1 -0
- package/components/menu/dist/auro-menu.d.ts +97 -81
- package/components/menu/dist/auro-menu.d.ts.map +1 -1
- package/components/menu/dist/index.d.ts +1 -0
- package/components/menu/dist/index.js +619 -304
- package/components/menu/src/auro-menu-utils.js +131 -0
- package/components/menu/src/auro-menu.js +493 -303
- package/components/menu/src/index.js +7 -0
- package/components/radio/.turbo/turbo-build.log +3 -3
- package/components/radio/.turbo/turbo-bundler.log +3 -3
- package/components/radio/README.md +1 -1
- package/components/select/.turbo/turbo-build.log +5 -3
- package/components/select/README.md +3 -3
- package/components/select/demo/api.md +46 -11
- package/components/select/demo/api.min.js +2336 -485
- package/components/select/demo/index.min.js +2337 -486
- package/components/select/dist/auro-select.d.ts +17 -6
- package/components/select/dist/auro-select.d.ts.map +1 -1
- package/components/select/dist/index.js +1706 -170
- package/components/select/src/auro-select.js +53 -24
- package/components/select/src/styles/style-css.js +1 -1
- package/components/select/src/styles/style.css +7 -0
- package/components/select/src/styles/style.scss +13 -0
- package/package.json +1 -1
- package/.turbo/cache/026e4d886ba97e63-meta.json +0 -1
- package/.turbo/cache/026e4d886ba97e63.tar.zst +0 -0
- package/.turbo/cache/080ca6155e637d5d-meta.json +0 -1
- package/.turbo/cache/080ca6155e637d5d.tar.zst +0 -0
- package/.turbo/cache/0b115e30ff606299-meta.json +0 -1
- package/.turbo/cache/0b115e30ff606299.tar.zst +0 -0
- package/.turbo/cache/1c630fb3411e4a41-meta.json +0 -1
- package/.turbo/cache/24b19ac5895e5dd6-meta.json +0 -1
- package/.turbo/cache/24b19ac5895e5dd6.tar.zst +0 -0
- package/.turbo/cache/29b72c73cbccb53d-meta.json +0 -1
- package/.turbo/cache/29b72c73cbccb53d.tar.zst +0 -0
- package/.turbo/cache/3e647c5863d32e6f-meta.json +0 -1
- package/.turbo/cache/3e647c5863d32e6f.tar.zst +0 -0
- package/.turbo/cache/43f5206cc4e69b44-meta.json +0 -1
- package/.turbo/cache/4a85ec226b585fd5-meta.json +0 -1
- package/.turbo/cache/50a29c70b93c57dd-meta.json +0 -1
- package/.turbo/cache/56455145cd768755-meta.json +0 -1
- package/.turbo/cache/56455145cd768755.tar.zst +0 -0
- package/.turbo/cache/5c06332cf9f132da-meta.json +0 -1
- package/.turbo/cache/5e613afc6868d0e2-meta.json +0 -1
- package/.turbo/cache/5e613afc6868d0e2.tar.zst +0 -0
- package/.turbo/cache/639dac15b979bedc-meta.json +0 -1
- package/.turbo/cache/664c2e08614fd212-meta.json +0 -1
- package/.turbo/cache/6c51b0ebfc086faa-meta.json +0 -1
- package/.turbo/cache/6c51b0ebfc086faa.tar.zst +0 -0
- package/.turbo/cache/7216d994164825fb-meta.json +0 -1
- package/.turbo/cache/7216d994164825fb.tar.zst +0 -0
- package/.turbo/cache/83a167e135cb431a-meta.json +0 -1
- package/.turbo/cache/8af27c076dc010c3-meta.json +0 -1
- package/.turbo/cache/953c8216249d3509-meta.json +0 -1
- package/.turbo/cache/95a5e76ffd8c5110-meta.json +0 -1
- package/.turbo/cache/95a5e76ffd8c5110.tar.zst +0 -0
- package/.turbo/cache/a8b0fa0a9aa707c5-meta.json +0 -1
- package/.turbo/cache/a8b0fa0a9aa707c5.tar.zst +0 -0
- package/.turbo/cache/b22ca87b2f7f9cc2-meta.json +0 -1
- package/.turbo/cache/b7bbe2e7d44b77f0-meta.json +0 -1
- package/.turbo/cache/b7bbe2e7d44b77f0.tar.zst +0 -0
- package/.turbo/cache/c2b51643f886a493-meta.json +0 -1
- package/.turbo/cache/c2b51643f886a493.tar.zst +0 -0
- package/.turbo/cache/c74d369a0475b124-meta.json +0 -1
- package/.turbo/cache/c7f5a276ddb73cf7-meta.json +0 -1
- package/.turbo/cache/c96933d40404e4c8-meta.json +0 -1
- package/.turbo/cache/c96933d40404e4c8.tar.zst +0 -0
- package/.turbo/cache/eb1dbe885532c1dc-meta.json +0 -1
- package/.turbo/cache/f1f6744948f1b18f-meta.json +0 -1
- package/.turbo/cache/f1f6744948f1b18f.tar.zst +0 -0
- package/.turbo/cache/feefbc25d550c1cd-meta.json +0 -1
- package/.turbo/cache/ff4dbfffc29255ab-meta.json +0 -1
- package/.turbo/cache/ff4dbfffc29255ab.tar.zst +0 -0
- /package/.turbo/cache/{639dac15b979bedc.tar.zst → 2a5295c8f561ed84.tar.zst} +0 -0
- /package/.turbo/cache/{1c630fb3411e4a41.tar.zst → 8602fc2bb737a5cf.tar.zst} +0 -0
- /package/.turbo/cache/{664c2e08614fd212.tar.zst → 94dae2a64e9d8356.tar.zst} +0 -0
- /package/.turbo/cache/{c7f5a276ddb73cf7.tar.zst → afbbd49ed1a558b9.tar.zst} +0 -0
- /package/.turbo/cache/{43f5206cc4e69b44.tar.zst → b353ce8f6da43dea.tar.zst} +0 -0
- /package/.turbo/cache/{c74d369a0475b124.tar.zst → b8db059a9b9ccb5d.tar.zst} +0 -0
- /package/.turbo/cache/{4a85ec226b585fd5.tar.zst → bc24a38aa1b1a102.tar.zst} +0 -0
- /package/.turbo/cache/{5c06332cf9f132da.tar.zst → c97b043e748e3580.tar.zst} +0 -0
- /package/.turbo/cache/{953c8216249d3509.tar.zst → ee1a3c1fe389da51.tar.zst} +0 -0
- /package/.turbo/cache/{feefbc25d550c1cd.tar.zst → f5958c3acb889631.tar.zst} +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
function auroMenuResetExample() {
|
|
2
2
|
const resetExampleBtnElem = document.querySelector('#resetExampleBtn');
|
|
3
3
|
const resetExampleElem = document.querySelector('#resetExample');
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
if (resetExampleElem && resetExampleBtnElem) {
|
|
6
6
|
resetExampleBtnElem.addEventListener('click', () => {
|
|
7
|
-
resetExampleElem.
|
|
7
|
+
resetExampleElem.reset();
|
|
8
8
|
});
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -143,23 +143,158 @@ class AuroLibraryRuntimeUtils {
|
|
|
143
143
|
// Copyright (c) 2021 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
|
|
144
144
|
// See LICENSE in the project root for license information.
|
|
145
145
|
|
|
146
|
+
// ---------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Converts value to an array.
|
|
150
|
+
* If the value is a JSON string representing an array, it will be parsed.
|
|
151
|
+
* If the value is already an array, it is returned.
|
|
152
|
+
* If the value is undefined, it returns undefined.
|
|
153
|
+
* @private
|
|
154
|
+
* @param {any} value - The value to be converted. Can be a string, array, or undefined.
|
|
155
|
+
* @returns {Array|undefined} - The converted array or undefined.
|
|
156
|
+
* @throws {Error} - Throws an error if the value is not an array, undefined,
|
|
157
|
+
* or if the value cannot be parsed into an array from a JSON string.
|
|
158
|
+
*/
|
|
159
|
+
function arrayConverter(value) {
|
|
160
|
+
// Allow undefined
|
|
161
|
+
if (value === undefined) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Return the value if it is already an array
|
|
166
|
+
if (Array.isArray(value)) {
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
// If value is a JSON string, parse it
|
|
172
|
+
const parsed = typeof value === 'string' ? JSON.parse(value) : value;
|
|
173
|
+
|
|
174
|
+
// Check if the parsed value is an array
|
|
175
|
+
if (Array.isArray(parsed)) {
|
|
176
|
+
return parsed;
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
// If JSON parsing fails, continue to throw an error below
|
|
180
|
+
/* eslint-disable no-console */
|
|
181
|
+
console.error('JSON parsing failed:', error);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Throw error if the input is not an array or undefined
|
|
185
|
+
throw new Error('Invalid value: Input must be an array or undefined');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Compare two arrays for equality.
|
|
190
|
+
* @private
|
|
191
|
+
* @param {Array} arr1 - First array to compare.
|
|
192
|
+
* @param {Array} arr2 - Second array to compare.
|
|
193
|
+
* @returns {boolean} True if arrays are equal.
|
|
194
|
+
*/
|
|
195
|
+
function arraysAreEqual(arr1, arr2) {
|
|
196
|
+
// If both arrays undefined, they are equal (true)
|
|
197
|
+
if (arr1 === undefined || arr2 === undefined) {
|
|
198
|
+
return arr1 === arr2;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// If arrays have different lengths, they are not equal
|
|
202
|
+
if (arr1.length !== arr2.length) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// If every item at each index is the same, return true
|
|
207
|
+
for (let index = 0; index < arr1.length; index += 1) {
|
|
208
|
+
if (arr1[index] !== arr2[index]) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Compares array for changes.
|
|
217
|
+
* @private
|
|
218
|
+
* @param {Array|any} newVal - New value to compare.
|
|
219
|
+
* @param {Array|any} oldVal - Old value to compare.
|
|
220
|
+
* @returns {boolean} True if arrays have changed.
|
|
221
|
+
*/
|
|
222
|
+
function arrayOrUndefinedHasChanged(newVal, oldVal) {
|
|
223
|
+
try {
|
|
224
|
+
// Check if values are undefined or arrays
|
|
225
|
+
const isArrayOrUndefined = (val) => val === undefined || Array.isArray(val);
|
|
226
|
+
|
|
227
|
+
// If non-array or non-undefined, throw error
|
|
228
|
+
if (!isArrayOrUndefined(newVal) || !isArrayOrUndefined(oldVal)) {
|
|
229
|
+
const invalidValue = isArrayOrUndefined(newVal) ? oldVal : newVal;
|
|
230
|
+
throw new Error(`Value must be an array or undefined, received ${typeof invalidValue}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Return true if arrays have changed, false if they are the same
|
|
234
|
+
return !arraysAreEqual(newVal, oldVal);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
/* eslint-disable no-console */
|
|
237
|
+
console.error(error);
|
|
238
|
+
// If validation fails, it has changed
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Validates if an option can be interacted with.
|
|
245
|
+
* @private
|
|
246
|
+
* @param {HTMLElement} option - The option to check.
|
|
247
|
+
* @returns {boolean} True if option is interactive.
|
|
248
|
+
*/
|
|
249
|
+
function isOptionInteractive(option) {
|
|
250
|
+
return !option.hasAttribute('hidden') &&
|
|
251
|
+
!option.hasAttribute('disabled') &&
|
|
252
|
+
!option.hasAttribute('static');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Helper method to dispatch custom events.
|
|
257
|
+
* @param {HTMLElement} element - Element to dispatch event from.
|
|
258
|
+
* @param {string} eventName - Name of the event to dispatch.
|
|
259
|
+
* @param {Object} [detail] - Optional detail object to include with the event.
|
|
260
|
+
*/
|
|
261
|
+
function dispatchMenuEvent(element, eventName, detail = null) {
|
|
262
|
+
const eventConfig = {
|
|
263
|
+
bubbles: true,
|
|
264
|
+
cancelable: false,
|
|
265
|
+
composed: true
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
if (detail !== null) {
|
|
269
|
+
eventConfig.detail = detail;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
element.dispatchEvent(new CustomEvent(eventName, eventConfig));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Copyright (c) 2021 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
|
|
276
|
+
// See LICENSE in the project root for license information.
|
|
277
|
+
|
|
278
|
+
|
|
146
279
|
|
|
147
280
|
// See https://git.io/JJ6SJ for "How to document your components using JSDoc"
|
|
148
281
|
/**
|
|
149
282
|
* The auro-menu element provides users a way to select from a list of options.
|
|
150
|
-
* @attr {
|
|
151
|
-
* @attr {
|
|
152
|
-
* @attr {
|
|
153
|
-
* @attr {
|
|
154
|
-
* @attr {
|
|
155
|
-
* @attr {
|
|
156
|
-
* @
|
|
157
|
-
* @
|
|
158
|
-
* @
|
|
159
|
-
* @event auroMenu-
|
|
160
|
-
* @event auroMenu-
|
|
161
|
-
* @event auroMenu-
|
|
162
|
-
* @event auroMenu-
|
|
283
|
+
* @attr {Array<HTMLElement>|undefined} optionselected - An array of currently selected menu options. In single-select mode, the array will contain only one HTMLElement. `undefined` when no options are selected.
|
|
284
|
+
* @attr {object} optionactive - Specifies the current active menuOption.
|
|
285
|
+
* @attr {string} matchword - Specifies a string used to highlight matched string parts in options.
|
|
286
|
+
* @attr {boolean} disabled - When true, the entire menu and all options are disabled;
|
|
287
|
+
* @attr {boolean} nocheckmark - When true, selected option will not show the checkmark.
|
|
288
|
+
* @attr {boolean} loading - When true, displays a loading state using the loadingIcon and loadingText slots if provided.
|
|
289
|
+
* @attr {boolean} multiselect - When true, the selected option can be multiple options.
|
|
290
|
+
* @attr {Array<string>|undefined} value - Value selected for the menu. `undefined` when no selection has been made, otherwise an array of strings. In single-select mode, the array will contain only one value.
|
|
291
|
+
* @prop {boolean} hasLoadingPlaceholder - Indicates whether the menu has a loadingIcon or loadingText to render when in a loading state.
|
|
292
|
+
* @event {CustomEvent<Element>} auroMenu-activatedOption - Notifies that a menuoption has been made `active`.
|
|
293
|
+
* @event {CustomEvent<any>} auroMenu-customEventFired - Notifies that a custom event has been fired.
|
|
294
|
+
* @event {CustomEvent<{ loading: boolean; hasLoadingPlaceholder: boolean; }>} auroMenu-loadingChange - Notifies when the loading attribute is changed.
|
|
295
|
+
* @event {CustomEvent<any>} auroMenu-selectValueFailure - Notifies that an attempt to select a menuoption by matching a value has failed.
|
|
296
|
+
* @event {CustomEvent<any>} auroMenu-selectValueReset - Notifies that the component value has been reset.
|
|
297
|
+
* @event {CustomEvent<any>} auroMenu-selectedOption - Notifies that a new menuoption selection has been made.
|
|
163
298
|
* @slot loadingText - Text to show while loading attribute is set
|
|
164
299
|
* @slot loadingIcon - Icon to show while loading attribute is set
|
|
165
300
|
* @slot - Slot for insertion of menu options.
|
|
@@ -170,52 +305,104 @@ class AuroLibraryRuntimeUtils {
|
|
|
170
305
|
class AuroMenu extends r {
|
|
171
306
|
constructor() {
|
|
172
307
|
super();
|
|
308
|
+
|
|
309
|
+
// State properties (reactive)
|
|
310
|
+
|
|
311
|
+
// Value of the selected options
|
|
173
312
|
this.value = undefined;
|
|
313
|
+
// Currently selected option
|
|
174
314
|
this.optionSelected = undefined;
|
|
315
|
+
// String used for highlighting/filtering
|
|
175
316
|
this.matchWord = undefined;
|
|
317
|
+
// Hide the checkmark icon on selected options
|
|
176
318
|
this.noCheckmark = false;
|
|
319
|
+
// Currently active option
|
|
177
320
|
this.optionActive = undefined;
|
|
321
|
+
// Loading state
|
|
178
322
|
this.loading = false;
|
|
323
|
+
// Multi-select mode
|
|
324
|
+
this.multiSelect = false;
|
|
325
|
+
|
|
326
|
+
// Event Bindings
|
|
179
327
|
|
|
180
328
|
/**
|
|
181
329
|
* @private
|
|
182
330
|
*/
|
|
183
|
-
this.
|
|
331
|
+
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
184
332
|
|
|
185
333
|
/**
|
|
186
334
|
* @private
|
|
187
335
|
*/
|
|
188
|
-
this.
|
|
336
|
+
this.handleMouseSelect = this.handleMouseSelect.bind(this);
|
|
189
337
|
|
|
190
338
|
/**
|
|
191
339
|
* @private
|
|
192
340
|
*/
|
|
193
|
-
this.
|
|
341
|
+
this.handleOptionHover = this.handleOptionHover.bind(this);
|
|
194
342
|
|
|
195
343
|
/**
|
|
196
344
|
* @private
|
|
197
345
|
*/
|
|
198
|
-
this.
|
|
346
|
+
this.handleSlotChange = this.handleSlotChange.bind(this);
|
|
347
|
+
|
|
348
|
+
// Instance properties (non-reactive)
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* @private
|
|
352
|
+
*/
|
|
353
|
+
Object.assign(this, {
|
|
354
|
+
// Root-level menu (true) or a nested submenu (false)
|
|
355
|
+
rootMenu: true,
|
|
356
|
+
// Currently focused/active menu item index
|
|
357
|
+
index: -1,
|
|
358
|
+
// Nested menu spacer
|
|
359
|
+
nestingSpacer: '<span class="nestingSpacer"></span>',
|
|
360
|
+
// Loading indicator for slot elements
|
|
361
|
+
loadingSlots: null,
|
|
362
|
+
// Store for menu items
|
|
363
|
+
items: [],
|
|
364
|
+
});
|
|
199
365
|
}
|
|
200
366
|
|
|
201
367
|
static get properties() {
|
|
202
368
|
return {
|
|
203
|
-
noCheckmark:
|
|
369
|
+
noCheckmark: {
|
|
204
370
|
type: Boolean,
|
|
205
|
-
reflect: true
|
|
371
|
+
reflect: true,
|
|
372
|
+
attribute: 'nocheckmark'
|
|
206
373
|
},
|
|
207
|
-
disabled:
|
|
374
|
+
disabled: {
|
|
208
375
|
type: Boolean,
|
|
209
376
|
reflect: true
|
|
210
377
|
},
|
|
211
|
-
loading:
|
|
378
|
+
loading: {
|
|
212
379
|
type: Boolean,
|
|
213
380
|
reflect: true
|
|
214
381
|
},
|
|
215
|
-
optionSelected: {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
382
|
+
optionSelected: {
|
|
383
|
+
// Allow HTMLElement[] arrays and undefined
|
|
384
|
+
converter: arrayConverter,
|
|
385
|
+
hasChanged: arrayOrUndefinedHasChanged
|
|
386
|
+
},
|
|
387
|
+
optionActive: {
|
|
388
|
+
type: Object,
|
|
389
|
+
attribute: 'optionactive'
|
|
390
|
+
},
|
|
391
|
+
matchWord: {
|
|
392
|
+
type: String,
|
|
393
|
+
attribute: 'matchword'
|
|
394
|
+
},
|
|
395
|
+
multiSelect: {
|
|
396
|
+
type: Boolean,
|
|
397
|
+
reflect: true,
|
|
398
|
+
attribute: 'multiselect'
|
|
399
|
+
},
|
|
400
|
+
value: {
|
|
401
|
+
// Allow string[] arrays and undefined
|
|
402
|
+
type: Object,
|
|
403
|
+
converter: arrayConverter,
|
|
404
|
+
hasChanged: arrayOrUndefinedHasChanged
|
|
405
|
+
}
|
|
219
406
|
};
|
|
220
407
|
}
|
|
221
408
|
|
|
@@ -239,198 +426,329 @@ class AuroMenu extends r {
|
|
|
239
426
|
AuroLibraryRuntimeUtils.prototype.registerComponent(name, AuroMenu);
|
|
240
427
|
}
|
|
241
428
|
|
|
242
|
-
|
|
243
|
-
* Passes the noCheckmark attribute to all nested auro-menuoptions.
|
|
244
|
-
* @private
|
|
245
|
-
* @returns {void}
|
|
246
|
-
*/
|
|
247
|
-
handleNoCheckmarkAttr() {
|
|
248
|
-
if (this.noCheckmark) {
|
|
249
|
-
const menus = this.querySelectorAll('auro-menu, [auro-menu]');
|
|
429
|
+
// Lifecycle Methods
|
|
250
430
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
});
|
|
431
|
+
connectedCallback() {
|
|
432
|
+
super.connectedCallback();
|
|
254
433
|
|
|
255
|
-
|
|
434
|
+
this.addEventListener('keydown', this.handleKeyDown);
|
|
435
|
+
this.addEventListener('mousedown', this.handleMouseSelect);
|
|
436
|
+
this.addEventListener('auroMenuOption-mouseover', this.handleOptionHover);
|
|
437
|
+
this.addEventListener('slotchange', this.handleSlotChange);
|
|
438
|
+
}
|
|
256
439
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
440
|
+
disconnectedCallback() {
|
|
441
|
+
this.removeEventListener('keydown', this.handleKeyDown);
|
|
442
|
+
this.removeEventListener('mousedown', this.handleMouseSelect);
|
|
443
|
+
this.removeEventListener('auroMenuOption-mouseover', this.handleOptionHover);
|
|
444
|
+
this.removeEventListener('slotchange', this.handleSlotChange);
|
|
445
|
+
|
|
446
|
+
super.disconnectedCallback();
|
|
261
447
|
}
|
|
262
448
|
|
|
263
449
|
firstUpdated() {
|
|
264
|
-
|
|
265
|
-
this.runtimeUtils.handleComponentTagRename(this, 'auro-menu');
|
|
266
|
-
|
|
267
|
-
this.addEventListener('keydown', this.handleKeyDown);
|
|
450
|
+
AuroLibraryRuntimeUtils.prototype.handleComponentTagRename(this, 'auro-menu');
|
|
268
451
|
|
|
269
452
|
this.loadingSlots = this.querySelectorAll("[slot='loadingText'], [slot='loadingIcon']");
|
|
453
|
+
this.initializeMenu();
|
|
270
454
|
}
|
|
271
455
|
|
|
272
456
|
updated(changedProperties) {
|
|
273
|
-
if (changedProperties.has('
|
|
274
|
-
|
|
457
|
+
if (changedProperties.has('value')) {
|
|
458
|
+
// Handle null/undefined case
|
|
459
|
+
if (this.value === undefined || this.value === null) {
|
|
460
|
+
this.optionSelected = undefined;
|
|
461
|
+
// Reset index tracking
|
|
462
|
+
this.index = -1;
|
|
463
|
+
} else {
|
|
464
|
+
// Convert single values to arrays
|
|
465
|
+
const valueArray = Array.isArray(this.value) ? this.value : [this.value];
|
|
466
|
+
|
|
467
|
+
// Find all matching options
|
|
468
|
+
const matchingOptions = this.items.filter((item) => valueArray.includes(item.value));
|
|
469
|
+
|
|
470
|
+
if (matchingOptions.length > 0) {
|
|
471
|
+
if (this.multiSelect) {
|
|
472
|
+
// For multiselect, keep all matching options
|
|
473
|
+
this.optionSelected = matchingOptions;
|
|
474
|
+
} else {
|
|
475
|
+
// For single select, only use the first match
|
|
476
|
+
this.optionSelected = [matchingOptions[0]];
|
|
477
|
+
this.index = this.items.indexOf(matchingOptions[0]);
|
|
478
|
+
}
|
|
479
|
+
} else {
|
|
480
|
+
// No matches found - trigger failure event
|
|
481
|
+
dispatchMenuEvent(this, 'auroMenu-selectValueFailure');
|
|
482
|
+
this.optionSelected = undefined;
|
|
483
|
+
this.index = -1;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Update UI state
|
|
488
|
+
this.updateItemsState(new Map([
|
|
489
|
+
[
|
|
490
|
+
'optionSelected',
|
|
491
|
+
true
|
|
492
|
+
]
|
|
493
|
+
]));
|
|
494
|
+
|
|
495
|
+
// Notify of changes
|
|
496
|
+
if (this.optionSelected !== undefined) {
|
|
497
|
+
this.notifySelectionChange();
|
|
498
|
+
}
|
|
275
499
|
}
|
|
276
500
|
|
|
277
|
-
|
|
278
|
-
|
|
501
|
+
// Process all other UI updates
|
|
502
|
+
this.updateItemsState(changedProperties);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Updates the UI state and appearance of menu items based on changed properties.
|
|
507
|
+
* @private
|
|
508
|
+
* @param {Map<string, boolean>} changedProperties - LitElement's changed properties map.
|
|
509
|
+
*/
|
|
510
|
+
updateItemsState(changedProperties) {
|
|
511
|
+
if (!this.items) {
|
|
512
|
+
return;
|
|
279
513
|
}
|
|
280
514
|
|
|
281
|
-
|
|
282
|
-
|
|
515
|
+
// Handle noCheckmark propagation to all menus and options
|
|
516
|
+
if (changedProperties.has('noCheckmark') && this.noCheckmark) {
|
|
517
|
+
// Update both menus and options
|
|
518
|
+
this.querySelectorAll('auro-menu, [auro-menu], auro-menuoption, [auro-menuoption]').forEach((element) => element.setAttribute('noCheckmark', ''));
|
|
519
|
+
}
|
|
283
520
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
521
|
+
// Regex for matchWord if needed
|
|
522
|
+
let regexWord = null;
|
|
523
|
+
|
|
524
|
+
if (changedProperties.has('matchWord') && this.matchWord && this.matchWord.length) {
|
|
525
|
+
const escapedWord = this.matchWord.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
526
|
+
regexWord = new RegExp(escapedWord, 'giu');
|
|
287
527
|
}
|
|
288
528
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
529
|
+
// Handle direct item updates
|
|
530
|
+
this.items.forEach((option) => {
|
|
531
|
+
// Update selection if option or value changed
|
|
532
|
+
if (changedProperties.has('optionSelected') || changedProperties.has('value')) {
|
|
533
|
+
const isSelected = this.isOptionSelected(option);
|
|
534
|
+
option.classList.toggle('active', isSelected);
|
|
535
|
+
option.setAttribute('aria-selected', isSelected ? 'true' : 'false');
|
|
536
|
+
|
|
537
|
+
// Add/remove selected attribute based on state
|
|
538
|
+
if (isSelected) {
|
|
539
|
+
option.setAttribute('selected', '');
|
|
540
|
+
} else {
|
|
541
|
+
option.removeAttribute('selected');
|
|
295
542
|
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Update text highlighting if matchWord changed
|
|
546
|
+
if (changedProperties.has('matchWord') && regexWord &&
|
|
547
|
+
isOptionInteractive(option) && !option.hasAttribute('persistent')) {
|
|
548
|
+
const nested = option.querySelectorAll('.nestingSpacer');
|
|
549
|
+
// Create nested spacers
|
|
550
|
+
const nestingSpacerBundle = [...nested].map(() => this.nestingSpacer).join('');
|
|
551
|
+
|
|
552
|
+
// Update with spacers and matchWord
|
|
553
|
+
option.innerHTML = nestingSpacerBundle +
|
|
554
|
+
option.textContent.replace(
|
|
555
|
+
regexWord,
|
|
556
|
+
(match) => `<strong>${match}</strong>`
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Update disabled state
|
|
561
|
+
if (changedProperties.has('disabled')) {
|
|
562
|
+
option.disabled = this.disabled;
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// Handle loading state changes
|
|
567
|
+
if (changedProperties.has('loading')) {
|
|
568
|
+
this.setAttribute("aria-busy", this.loading);
|
|
569
|
+
dispatchMenuEvent(this, "auroMenu-loadingChange", {
|
|
570
|
+
loading: this.loading,
|
|
571
|
+
hasLoadingPlaceholder: this.hasLoadingPlaceholder
|
|
296
572
|
});
|
|
297
|
-
this.setAttribute("aria-busy", this.hasAttribute("loading"));
|
|
298
|
-
this.dispatchEvent(event);
|
|
299
573
|
}
|
|
300
574
|
}
|
|
301
575
|
|
|
576
|
+
// Init Methods
|
|
577
|
+
|
|
302
578
|
/**
|
|
579
|
+
* Initializes the menu's state and structure.
|
|
303
580
|
* @private
|
|
304
|
-
* @param {Object} option - The menuoption to check for interactive state.
|
|
305
|
-
* @returns {Boolean} Returns true if the option is interactive.
|
|
306
581
|
*/
|
|
307
|
-
|
|
308
|
-
|
|
582
|
+
initializeMenu() {
|
|
583
|
+
this.initItems();
|
|
584
|
+
if (this.rootMenu) {
|
|
585
|
+
this.setAttribute('role', 'listbox');
|
|
586
|
+
this.setAttribute('root', '');
|
|
587
|
+
this.handleNestedMenus(this);
|
|
588
|
+
}
|
|
309
589
|
}
|
|
310
590
|
|
|
311
591
|
/**
|
|
592
|
+
* Initializes menu items and their attributes.
|
|
312
593
|
* @private
|
|
313
|
-
* @returns {void} When called will update the DOM with visible suggest text matches.
|
|
314
594
|
*/
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
this.items.forEach((item) => {
|
|
325
|
-
if (this.optionInteractive(item) && !item.hasAttribute('persistent')) {
|
|
326
|
-
const nested = item.querySelectorAll('.nestingSpacer');
|
|
327
|
-
const nestingSpacerBundle = [...nested].map(() => this.nestingSpacer).join('');
|
|
328
|
-
|
|
329
|
-
item.innerHTML = nestingSpacerBundle + item.textContent.replace(regexWord, (match) => `<strong>${match}</strong>`);
|
|
330
|
-
}
|
|
331
|
-
});
|
|
595
|
+
initItems() {
|
|
596
|
+
this.items = Array.from(this.querySelectorAll('auro-menuoption, [auro-menuoption]'));
|
|
597
|
+
if (this.noCheckmark) {
|
|
598
|
+
this.updateItemsState(new Map([
|
|
599
|
+
[
|
|
600
|
+
'noCheckmark',
|
|
601
|
+
true
|
|
602
|
+
]
|
|
603
|
+
]));
|
|
332
604
|
}
|
|
333
605
|
}
|
|
334
606
|
|
|
607
|
+
// Logic Methods
|
|
608
|
+
|
|
335
609
|
/**
|
|
336
|
-
*
|
|
610
|
+
* Updates menu state when an option is selected.
|
|
611
|
+
* @private
|
|
612
|
+
* @param {HTMLElement} option - The option element to select.
|
|
337
613
|
*/
|
|
338
|
-
|
|
339
|
-
this.
|
|
340
|
-
|
|
341
|
-
this.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
614
|
+
handleSelectState(option) {
|
|
615
|
+
if (this.multiSelect) {
|
|
616
|
+
const currentValue = this.value || [];
|
|
617
|
+
const currentSelected = this.optionSelected || [];
|
|
618
|
+
|
|
619
|
+
if (!currentValue.includes(option.value)) {
|
|
620
|
+
this.value = [
|
|
621
|
+
...currentValue,
|
|
622
|
+
option.value
|
|
623
|
+
];
|
|
624
|
+
}
|
|
625
|
+
if (!currentSelected.includes(option)) {
|
|
626
|
+
this.optionSelected = [
|
|
627
|
+
...currentSelected,
|
|
628
|
+
option
|
|
629
|
+
];
|
|
630
|
+
}
|
|
631
|
+
} else {
|
|
632
|
+
// Single select - use arrays with single values
|
|
633
|
+
this.value = [option.value];
|
|
634
|
+
this.optionSelected = [option];
|
|
345
635
|
}
|
|
636
|
+
|
|
637
|
+
this.index = this.items.indexOf(option);
|
|
346
638
|
}
|
|
347
639
|
|
|
348
640
|
/**
|
|
349
|
-
*
|
|
350
|
-
* @param {Object} option - The menuoption to be selected.
|
|
641
|
+
* Deselects a menu option and updates related state.
|
|
351
642
|
* @private
|
|
643
|
+
* @param {HTMLElement} option - The menuoption to be deselected.
|
|
352
644
|
*/
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
645
|
+
handleDeselectState(option) {
|
|
646
|
+
if (this.multiSelect && Array.isArray(this.value)) {
|
|
647
|
+
// Remove this option from array
|
|
648
|
+
this.value = this.value.filter((val) => val !== option.value);
|
|
649
|
+
|
|
650
|
+
// If array is empty after removal, set back to undefined
|
|
651
|
+
if (this.value.length === 0) {
|
|
652
|
+
this.value = undefined;
|
|
653
|
+
}
|
|
357
654
|
|
|
358
|
-
|
|
359
|
-
|
|
655
|
+
this.optionSelected = this.optionSelected.filter((val) => val !== option);
|
|
656
|
+
if (this.optionSelected.length === 0) {
|
|
657
|
+
this.optionSelected = undefined;
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
// For single-select: Back to undefined when deselected
|
|
661
|
+
this.value = undefined;
|
|
662
|
+
this.optionSelected = undefined;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Update the index tracking
|
|
360
666
|
this.index = this.items.indexOf(option);
|
|
667
|
+
|
|
668
|
+
// Update UI to reflect changes
|
|
669
|
+
this.updateItemsState(new Map([
|
|
670
|
+
[
|
|
671
|
+
'optionSelected',
|
|
672
|
+
true
|
|
673
|
+
]
|
|
674
|
+
]));
|
|
675
|
+
|
|
676
|
+
// Notify of selection change
|
|
677
|
+
this.notifySelectionChange();
|
|
361
678
|
}
|
|
362
679
|
|
|
363
680
|
/**
|
|
364
|
-
*
|
|
681
|
+
* Resets all options to their default state.
|
|
365
682
|
* @private
|
|
366
|
-
* @return {void}
|
|
367
683
|
*/
|
|
368
|
-
|
|
369
|
-
this.
|
|
370
|
-
|
|
371
|
-
cancelable: false,
|
|
372
|
-
composed: true,
|
|
373
|
-
}));
|
|
684
|
+
clearSelection() {
|
|
685
|
+
this.optionSelected = undefined;
|
|
686
|
+
this.value = undefined;
|
|
374
687
|
}
|
|
375
688
|
|
|
376
689
|
/**
|
|
377
|
-
*
|
|
690
|
+
* Resets the menu to its initial state.
|
|
691
|
+
* This is the only way to return value to undefined.
|
|
692
|
+
* @public
|
|
378
693
|
*/
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
694
|
+
reset() {
|
|
695
|
+
// Reset to undefined - initial state
|
|
696
|
+
this.value = undefined;
|
|
697
|
+
this.optionSelected = undefined;
|
|
698
|
+
this.index = -1;
|
|
699
|
+
|
|
700
|
+
// Reset UI state
|
|
701
|
+
this.updateItemsState(new Map([
|
|
702
|
+
[
|
|
703
|
+
'optionSelected',
|
|
704
|
+
true
|
|
705
|
+
]
|
|
706
|
+
]));
|
|
707
|
+
|
|
708
|
+
// Dispatch reset event
|
|
709
|
+
dispatchMenuEvent(this, 'auroMenu-selectValueReset');
|
|
710
|
+
}
|
|
383
711
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
composed: true,
|
|
398
|
-
}));
|
|
399
|
-
|
|
400
|
-
this.dispatchEvent(new CustomEvent('auroMenu-customEventFired', {
|
|
401
|
-
bubbles: true,
|
|
402
|
-
cancelable: false,
|
|
403
|
-
composed: true,
|
|
404
|
-
}));
|
|
405
|
-
} else {
|
|
406
|
-
this.handleLocalSelectState(option);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
712
|
+
/**
|
|
713
|
+
* Handles nested menu structure.
|
|
714
|
+
* @private
|
|
715
|
+
* @param {HTMLElement} menu - Root menu element.
|
|
716
|
+
*/
|
|
717
|
+
handleNestedMenus(menu) {
|
|
718
|
+
const nestedMenus = menu.querySelectorAll('auro-menu, [auro-menu]');
|
|
719
|
+
|
|
720
|
+
nestedMenus.forEach((nestedMenu) => {
|
|
721
|
+
// role="listbox" only allows "role=group" for children.
|
|
722
|
+
nestedMenu.setAttribute('role', 'group');
|
|
723
|
+
if (!nestedMenu.hasAttribute('aria-label')) {
|
|
724
|
+
nestedMenu.setAttribute('aria-label', 'submenu');
|
|
409
725
|
}
|
|
410
|
-
}
|
|
411
726
|
|
|
412
|
-
|
|
727
|
+
const options = nestedMenu.querySelectorAll(':scope > auro-menuoption, :scope > [auro-menuoption]');
|
|
728
|
+
options.forEach((option) => {
|
|
729
|
+
option.innerHTML = this.nestingSpacer + option.innerHTML;
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
this.handleNestedMenus(nestedMenu);
|
|
733
|
+
});
|
|
413
734
|
}
|
|
414
735
|
|
|
736
|
+
// Event Handlers
|
|
737
|
+
|
|
415
738
|
/**
|
|
416
|
-
*
|
|
739
|
+
* Handles keyboard navigation.
|
|
417
740
|
* @private
|
|
418
|
-
* @param {
|
|
741
|
+
* @param {KeyboardEvent} event - Event object from the browser.
|
|
419
742
|
*/
|
|
420
743
|
handleKeyDown(event) {
|
|
421
744
|
event.preventDefault();
|
|
422
|
-
|
|
423
|
-
// With ArrowDown/ArrowUp events, pass new value to selectNextItem()
|
|
424
|
-
// With Enter event, set value and apply attrs
|
|
425
745
|
switch (event.key) {
|
|
426
746
|
case "ArrowDown":
|
|
427
|
-
this.
|
|
747
|
+
this.navigateOptions('down');
|
|
428
748
|
break;
|
|
429
|
-
|
|
430
749
|
case "ArrowUp":
|
|
431
|
-
this.
|
|
750
|
+
this.navigateOptions('up');
|
|
432
751
|
break;
|
|
433
|
-
|
|
434
752
|
case "Enter":
|
|
435
753
|
this.makeSelection();
|
|
436
754
|
break;
|
|
@@ -438,222 +756,218 @@ class AuroMenu extends r {
|
|
|
438
756
|
}
|
|
439
757
|
|
|
440
758
|
/**
|
|
441
|
-
*
|
|
759
|
+
* Makes a selection based on the current index or clicked option.
|
|
442
760
|
* @private
|
|
443
761
|
*/
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
762
|
+
makeSelection() {
|
|
763
|
+
if (!this.items) {
|
|
764
|
+
this.initItems();
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Get currently selected menu option based on index
|
|
768
|
+
const option = this.items[this.index];
|
|
769
|
+
|
|
770
|
+
// Return early if option is not interactive
|
|
771
|
+
if (!option || !isOptionInteractive(option)) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Handle custom events first
|
|
776
|
+
if (option.hasAttribute('event')) {
|
|
777
|
+
this.handleCustomEvent(option);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (this.multiSelect) {
|
|
782
|
+
// In multiselect, toggle individual selections
|
|
783
|
+
this.toggleOption(option);
|
|
784
|
+
// In single select, only handle selection of new options
|
|
785
|
+
} else if (!this.isOptionSelected(option)) {
|
|
786
|
+
this.clearSelection();
|
|
787
|
+
this.handleSelectState(option);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
this.notifySelectionChange();
|
|
447
791
|
}
|
|
448
792
|
|
|
449
793
|
/**
|
|
450
|
-
*
|
|
794
|
+
* Toggle the selection state of the menuoption.
|
|
451
795
|
* @private
|
|
796
|
+
* @param {HTMLElement} option - The menuoption to toggle.
|
|
452
797
|
*/
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const index = this.items.findIndex((option) => option.hasAttribute('selected') && this.optionInteractive(option));
|
|
798
|
+
toggleOption(option) {
|
|
799
|
+
const isCurrentlySelected = this.isOptionSelected(option);
|
|
456
800
|
|
|
457
|
-
if (
|
|
458
|
-
this.
|
|
459
|
-
|
|
801
|
+
if (isCurrentlySelected) {
|
|
802
|
+
this.handleDeselectState(option);
|
|
803
|
+
} else if (option.value === undefined || option.value === '') {
|
|
804
|
+
dispatchMenuEvent(this, 'auroMenu-selectValueFailure');
|
|
805
|
+
} else {
|
|
806
|
+
this.handleSelectState(option);
|
|
460
807
|
}
|
|
461
808
|
}
|
|
462
809
|
|
|
463
810
|
/**
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
* The event.target is not used as the function needs to know where to go,
|
|
469
|
-
* versus knowing where it is.
|
|
470
|
-
* @param {String} moveDirection - Up or Down based on keyboard event.
|
|
811
|
+
* Handles option selection via mouse.
|
|
812
|
+
* @private
|
|
813
|
+
* @param {MouseEvent} event - Event object from the browser.
|
|
471
814
|
*/
|
|
472
|
-
|
|
473
|
-
if (
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
// calculate which is the selection we should focus next
|
|
477
|
-
let increment = 0;
|
|
478
|
-
|
|
479
|
-
if (moveDirection === 'down') {
|
|
480
|
-
increment = 1;
|
|
481
|
-
} else if (moveDirection === 'up') {
|
|
482
|
-
increment = -1;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
this.index += increment;
|
|
486
|
-
|
|
487
|
-
// keep looping inside the array of options
|
|
488
|
-
if (this.index > this.items.length - 1) {
|
|
489
|
-
this.index = 0;
|
|
490
|
-
} else if (this.index < 0) {
|
|
491
|
-
this.index = this.items.length - 1;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// check if new index is disabled, static or hidden, if so, execute again
|
|
495
|
-
if (!this.optionInteractive(this.items[this.index])) {
|
|
496
|
-
this.selectNextItem(moveDirection);
|
|
497
|
-
} else {
|
|
498
|
-
// apply focus to new index
|
|
499
|
-
this.updateActiveOption(this.index);
|
|
500
|
-
}
|
|
501
|
-
} else {
|
|
502
|
-
this.index = 0;
|
|
815
|
+
handleMouseSelect(event) {
|
|
816
|
+
if (event.target === this) {
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
503
819
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
820
|
+
const option = event.target.closest('auro-menuoption, [auro-menuoption]');
|
|
821
|
+
if (option) {
|
|
822
|
+
this.index = this.items.indexOf(option);
|
|
823
|
+
this.makeSelection();
|
|
509
824
|
}
|
|
510
825
|
}
|
|
511
826
|
|
|
512
827
|
/**
|
|
513
|
-
*
|
|
828
|
+
* Handles option hover events.
|
|
514
829
|
* @private
|
|
515
|
-
* @param {
|
|
830
|
+
* @param {CustomEvent} event - Event object from the browser.
|
|
516
831
|
*/
|
|
517
|
-
|
|
518
|
-
const
|
|
832
|
+
handleOptionHover(event) {
|
|
833
|
+
const option = event.target;
|
|
834
|
+
this.index = this.items.indexOf(option);
|
|
835
|
+
this.updateActiveOption(this.index);
|
|
836
|
+
}
|
|
519
837
|
|
|
520
|
-
|
|
521
|
-
|
|
838
|
+
/**
|
|
839
|
+
* Handles slot change events.
|
|
840
|
+
* @private
|
|
841
|
+
*/
|
|
842
|
+
handleSlotChange() {
|
|
843
|
+
if (this.parentElement && this.parentElement.closest('auro-menu, [auro-menu]')) {
|
|
844
|
+
this.rootMenu = false;
|
|
522
845
|
}
|
|
523
846
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
847
|
+
if (this.rootMenu) {
|
|
848
|
+
this.initializeMenu();
|
|
849
|
+
} else if (this.noCheckmark) {
|
|
850
|
+
this.updateItemsState(new Map([
|
|
851
|
+
[
|
|
852
|
+
'noCheckmark',
|
|
853
|
+
true
|
|
854
|
+
]
|
|
855
|
+
]));
|
|
856
|
+
}
|
|
533
857
|
}
|
|
534
858
|
|
|
535
859
|
/**
|
|
536
|
-
*
|
|
860
|
+
* Navigates through options using keyboard.
|
|
537
861
|
* @private
|
|
538
|
-
* @param {
|
|
862
|
+
* @param {string} direction - 'up' or 'down'.
|
|
539
863
|
*/
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (!this.items) {
|
|
543
|
-
|
|
864
|
+
navigateOptions(direction) {
|
|
865
|
+
// Return early if no items exist
|
|
866
|
+
if (!this.items || !this.items.length) {
|
|
867
|
+
return;
|
|
544
868
|
}
|
|
545
869
|
|
|
546
|
-
this.index
|
|
870
|
+
let newIndex = this.index;
|
|
871
|
+
const increment = direction === 'down' ? 1 : -1;
|
|
872
|
+
const maxIterations = this.items.length;
|
|
873
|
+
let iterations = 0;
|
|
874
|
+
let foundInteractiveOption = false;
|
|
547
875
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
valueMatch = true;
|
|
552
|
-
this.index = index;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
876
|
+
do {
|
|
877
|
+
newIndex = (newIndex + increment + this.items.length) % this.items.length;
|
|
878
|
+
iterations += 1;
|
|
555
879
|
|
|
556
|
-
if
|
|
557
|
-
|
|
558
|
-
|
|
880
|
+
// Check if current option is interactive
|
|
881
|
+
const currentOption = this.items[newIndex];
|
|
882
|
+
if (isOptionInteractive(currentOption)) {
|
|
883
|
+
foundInteractiveOption = true;
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
559
886
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
composed: true,
|
|
564
|
-
}));
|
|
565
|
-
} else {
|
|
566
|
-
this.makeSelection();
|
|
887
|
+
// Break if all options were checked
|
|
888
|
+
if (iterations >= maxIterations) {
|
|
889
|
+
break;
|
|
567
890
|
}
|
|
568
|
-
}
|
|
569
|
-
this.resetOptionsStates();
|
|
891
|
+
} while (iterations < maxIterations);
|
|
570
892
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
893
|
+
// Handle the results of the search
|
|
894
|
+
if (foundInteractiveOption) {
|
|
895
|
+
// Update only if an interactive option was found
|
|
896
|
+
this.index = newIndex;
|
|
897
|
+
this.updateActiveOption(this.index);
|
|
898
|
+
} else {
|
|
899
|
+
// All options are disabled or non-interactive
|
|
900
|
+
// Keep the current index unchanged
|
|
901
|
+
dispatchMenuEvent(this, 'auroMenu-navigateFailure', {
|
|
902
|
+
reason: 'No interactive options available',
|
|
903
|
+
direction,
|
|
904
|
+
currentIndex: this.index
|
|
905
|
+
});
|
|
576
906
|
}
|
|
577
907
|
}
|
|
578
908
|
|
|
579
909
|
/**
|
|
580
|
-
*
|
|
581
|
-
* @param {Number} index - Index of the menuoption that will be made active.
|
|
910
|
+
* Updates the active option state and dispatches events.
|
|
582
911
|
* @private
|
|
912
|
+
* @param {number} index - Index of the option to make active.
|
|
583
913
|
*/
|
|
584
914
|
updateActiveOption(index) {
|
|
585
|
-
this.items.
|
|
586
|
-
|
|
587
|
-
}
|
|
915
|
+
if (!this.items || !this.items[index]) {
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
this.items.forEach((item) => item.classList.remove('active'));
|
|
588
920
|
this.items[index].classList.add('active');
|
|
589
921
|
this.optionActive = this.items[index];
|
|
590
922
|
|
|
591
|
-
this
|
|
592
|
-
bubbles: true,
|
|
593
|
-
cancelable: false,
|
|
594
|
-
composed: true,
|
|
595
|
-
detail: this.items[index]
|
|
596
|
-
}));
|
|
923
|
+
dispatchMenuEvent(this, 'auroMenu-activatedOption', this.items[index]);
|
|
597
924
|
}
|
|
598
925
|
|
|
599
926
|
/**
|
|
600
|
-
*
|
|
601
|
-
* @param {Event} evt - Mousedown event.
|
|
927
|
+
* Handles custom events defined on options.
|
|
602
928
|
* @private
|
|
929
|
+
* @param {HTMLElement} option - Option with custom event.
|
|
603
930
|
*/
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
931
|
+
handleCustomEvent(option) {
|
|
932
|
+
const eventName = option.getAttribute('event');
|
|
933
|
+
dispatchMenuEvent(this, eventName);
|
|
934
|
+
dispatchMenuEvent(this, 'auroMenu-customEventFired');
|
|
608
935
|
}
|
|
609
936
|
|
|
610
937
|
/**
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
* This getter evaluates the `loadingSlots` collection to determine if it contains any items.
|
|
614
|
-
* If the size of the collection is greater than zero, it indicates the presence of loading
|
|
615
|
-
* placeholders, returning true; otherwise, it returns false.
|
|
616
|
-
*
|
|
617
|
-
* @getter hasLoadingPlaceholder
|
|
618
|
-
* @type {boolean}
|
|
619
|
-
* @returns {boolean} Returns true if loading placeholders exist; false otherwise.
|
|
938
|
+
* Notifies selection change to parent components.
|
|
939
|
+
* @private
|
|
620
940
|
*/
|
|
621
|
-
|
|
622
|
-
|
|
941
|
+
notifySelectionChange() {
|
|
942
|
+
dispatchMenuEvent(this, 'auroMenu-selectedOption');
|
|
623
943
|
}
|
|
624
944
|
|
|
625
945
|
/**
|
|
626
|
-
*
|
|
946
|
+
* Checks if an option is currently selected.
|
|
627
947
|
* @private
|
|
948
|
+
* @param {HTMLElement} option - The option to check.
|
|
949
|
+
* @returns {boolean}
|
|
628
950
|
*/
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
this.rootMenu = false;
|
|
951
|
+
isOptionSelected(option) {
|
|
952
|
+
if (!this.optionSelected) {
|
|
953
|
+
return false;
|
|
633
954
|
}
|
|
955
|
+
// Always treat as array for both single and multi-select
|
|
956
|
+
return Array.isArray(this.optionSelected) && this.optionSelected.includes(option);
|
|
957
|
+
}
|
|
634
958
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
this.markOptions();
|
|
642
|
-
this.index = -1;
|
|
643
|
-
this.getSelectedIndex();
|
|
644
|
-
|
|
645
|
-
this.addEventListener('keydown', this.handleKeyDown);
|
|
646
|
-
this.addEventListener('mousedown', this.handleMenuMouseDown);
|
|
647
|
-
this.addEventListener('auroMenuOption-mouseover', (evt) => {
|
|
648
|
-
this.index = this.items.indexOf(evt.target);
|
|
649
|
-
this.updateActiveOption(this.index);
|
|
650
|
-
});
|
|
651
|
-
} else {
|
|
652
|
-
// make sure to update all menuoption noCheckmark attributes when the menu is dynamically changed
|
|
653
|
-
this.handleNoCheckmarkAttr();
|
|
654
|
-
}
|
|
959
|
+
/**
|
|
960
|
+
* Getter for loading placeholder state.
|
|
961
|
+
* @returns {boolean} - True if loading slots are present and non-empty.
|
|
962
|
+
*/
|
|
963
|
+
get hasLoadingPlaceholder() {
|
|
964
|
+
return this.loadingSlots && this.loadingSlots.length > 0;
|
|
655
965
|
}
|
|
656
966
|
|
|
967
|
+
/**
|
|
968
|
+
* Renders the component.
|
|
969
|
+
* @returns {boolean} - True if loading slots are present and non-empty.
|
|
970
|
+
*/
|
|
657
971
|
render() {
|
|
658
972
|
if (this.loading) {
|
|
659
973
|
return x`
|
|
@@ -665,7 +979,8 @@ class AuroMenu extends r {
|
|
|
665
979
|
</auro-menuoption>
|
|
666
980
|
`;
|
|
667
981
|
}
|
|
668
|
-
|
|
982
|
+
|
|
983
|
+
return x`<slot @slotchange=${this.handleSlotChange}></slot>`;
|
|
669
984
|
}
|
|
670
985
|
}
|
|
671
986
|
|