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