@aurodesignsystem-dev/auro-formkit 0.0.0-pr1395.0 → 0.0.0-pr1395.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/checkbox/demo/api.min.js +1 -1
- package/components/checkbox/demo/index.min.js +1 -1
- package/components/checkbox/dist/index.js +1 -1
- package/components/checkbox/dist/registered.js +1 -1
- package/components/combobox/demo/api.min.js +151 -40
- package/components/combobox/demo/index.min.js +151 -40
- package/components/combobox/dist/index.js +3 -3
- package/components/combobox/dist/registered.js +3 -3
- package/components/counter/demo/api.min.js +2 -2
- package/components/counter/demo/index.min.js +2 -2
- package/components/counter/dist/index.js +2 -2
- package/components/counter/dist/registered.js +2 -2
- package/components/datepicker/demo/api.min.js +74 -13
- package/components/datepicker/demo/index.min.js +74 -13
- package/components/datepicker/dist/datepickerKeyboardStrategy.d.ts +4 -0
- package/components/datepicker/dist/index.js +74 -13
- package/components/datepicker/dist/registered.js +74 -13
- package/components/dropdown/demo/api.min.js +1 -1
- package/components/dropdown/demo/index.min.js +1 -1
- package/components/dropdown/dist/index.js +1 -1
- package/components/dropdown/dist/registered.js +1 -1
- package/components/form/demo/api.min.js +240 -63
- package/components/form/demo/index.min.js +240 -63
- package/components/input/demo/api.min.js +1 -1
- package/components/input/demo/index.min.js +1 -1
- package/components/input/dist/index.js +1 -1
- package/components/input/dist/registered.js +1 -1
- package/components/menu/demo/api.md +1 -1
- package/components/menu/demo/api.min.js +148 -37
- package/components/menu/demo/index.min.js +148 -37
- package/components/menu/dist/auro-menu.context.d.ts +15 -3
- package/components/menu/dist/auro-menu.d.ts +1 -1
- package/components/menu/dist/index.js +148 -37
- package/components/menu/dist/registered.js +148 -37
- package/components/radio/demo/api.min.js +1 -1
- package/components/radio/demo/index.min.js +1 -1
- package/components/radio/dist/index.js +1 -1
- package/components/radio/dist/registered.js +1 -1
- package/components/select/demo/api.min.js +158 -42
- package/components/select/demo/index.min.js +158 -42
- package/components/select/dist/index.js +10 -5
- package/components/select/dist/registered.js +10 -5
- package/custom-elements.json +1514 -1429
- package/package.json +11 -4
|
@@ -21,7 +21,7 @@ The `auro-menu` element provides users a way to select from a list of options.
|
|
|
21
21
|
| [noCheckmark](#noCheckmark) | `nocheckmark` | | `boolean` | false | When true, selected option will not show the checkmark. |
|
|
22
22
|
| [optionActive](#optionActive) | `optionactive` | | `object` | "undefined" | Specifies the current active menuOption. |
|
|
23
23
|
| [optionSelected](#optionSelected) | `optionSelected` | | `object` | "undefined" | An array of currently selected menu options, type `HTMLElement` by default. In multi-select mode, `optionSelected` is an array of HTML elements. |
|
|
24
|
-
| [options](#options) | | readonly | `array` | | Available menu options
|
|
24
|
+
| [options](#options) | | readonly | `array` | | Available menu options. |
|
|
25
25
|
| [selectAllMatchingOptions](#selectAllMatchingOptions) | `selectAllMatchingOptions` | | `boolean` | false | When true, selects all options that match the provided value/key when setting value and multiselect is enabled. |
|
|
26
26
|
| [selectedOption](#selectedOption) | | readonly | `HTMLElement \| null` | | Gets the first selected option, or null if none. |
|
|
27
27
|
| [selectedOptions](#selectedOptions) | | readonly | `HTMLElement[]` | | Gets the currently selected options. |
|
|
@@ -650,10 +650,20 @@ class AuroMenuOption extends AuroElement {
|
|
|
650
650
|
subscribe: true
|
|
651
651
|
});
|
|
652
652
|
|
|
653
|
-
// Establish the key property as early as possible
|
|
653
|
+
// Establish the key property as early as possible.
|
|
654
|
+
// When a framework (e.g. Svelte) inserts the element into the DOM before
|
|
655
|
+
// setting its `value` property, both `getAttribute('value')` and
|
|
656
|
+
// `getAttribute('key')` return null here. Setting `this.key = null`
|
|
657
|
+
// would block the fallback in `updated()` that assigns key from the
|
|
658
|
+
// value property (the guard checked `=== undefined`). Only assign key
|
|
659
|
+
// if at least one source attribute is actually present so that the
|
|
660
|
+
// `updated()` fallback can run when the value property arrives later.
|
|
654
661
|
const valueAttr = this.getAttribute('value');
|
|
655
662
|
const keyAttr = this.getAttribute('key');
|
|
656
|
-
|
|
663
|
+
const resolvedKey = keyAttr !== null ? keyAttr : valueAttr;
|
|
664
|
+
if (resolvedKey !== null) {
|
|
665
|
+
this.key = resolvedKey;
|
|
666
|
+
}
|
|
657
667
|
}
|
|
658
668
|
|
|
659
669
|
firstUpdated() {
|
|
@@ -703,8 +713,14 @@ class AuroMenuOption extends AuroElement {
|
|
|
703
713
|
this.updateTextHighlight();
|
|
704
714
|
}
|
|
705
715
|
|
|
706
|
-
// Set the key to be the passed value if no key is provided
|
|
707
|
-
|
|
716
|
+
// Set the key to be the passed value if no key is provided.
|
|
717
|
+
// Loose equality (== null) is intentional: it catches both null AND
|
|
718
|
+
// undefined. When a framework (e.g. Svelte, React) inserts the element
|
|
719
|
+
// before setting its value property, connectedCallback skips key
|
|
720
|
+
// assignment because both attributes are null at that point. The Lit
|
|
721
|
+
// property default for `key` is undefined (not null), so strict
|
|
722
|
+
// === null would miss the case and the fallback would never run.
|
|
723
|
+
if (changedProperties.has('value') && this.key == null) { // eslint-disable-line eqeqeq, no-eq-null
|
|
708
724
|
this.key = this.value;
|
|
709
725
|
}
|
|
710
726
|
}
|
|
@@ -1073,6 +1089,9 @@ class MenuService {
|
|
|
1073
1089
|
this._subscribers = [];
|
|
1074
1090
|
this.internalUpdateInProgress = false;
|
|
1075
1091
|
this.selectedOptions = [];
|
|
1092
|
+
this._pendingValue = null;
|
|
1093
|
+
this._pendingRetryScheduled = false;
|
|
1094
|
+
this._pendingRetryCount = 0;
|
|
1076
1095
|
}
|
|
1077
1096
|
|
|
1078
1097
|
/**
|
|
@@ -1112,6 +1131,9 @@ class MenuService {
|
|
|
1112
1131
|
hostDisconnected() {
|
|
1113
1132
|
this._subscribers = [];
|
|
1114
1133
|
this._menuOptions = [];
|
|
1134
|
+
this._pendingValue = null;
|
|
1135
|
+
this._pendingRetryScheduled = false;
|
|
1136
|
+
this._pendingRetryCount = 0;
|
|
1115
1137
|
}
|
|
1116
1138
|
|
|
1117
1139
|
/**
|
|
@@ -1314,17 +1336,22 @@ class MenuService {
|
|
|
1314
1336
|
* @param {string|number|Array<string|number>} value - The value(s) to select.
|
|
1315
1337
|
*/
|
|
1316
1338
|
selectByValue(value) {
|
|
1317
|
-
|
|
1318
|
-
if (this.internalUpdateInProgress ||
|
|
1319
|
-
this.host.internalUpdateInProgress ||
|
|
1320
|
-
value === undefined ||
|
|
1339
|
+
const isEmptyValue = value === undefined ||
|
|
1321
1340
|
value === null ||
|
|
1322
1341
|
(Array.isArray(value) && value.length === 0) ||
|
|
1323
|
-
(typeof value === 'string' && value.trim() === '')
|
|
1342
|
+
(typeof value === 'string' && value.trim() === '');
|
|
1343
|
+
|
|
1344
|
+
// Early exit for invalid/empty values
|
|
1345
|
+
if (isEmptyValue) {
|
|
1324
1346
|
return;
|
|
1325
1347
|
}
|
|
1326
1348
|
|
|
1327
|
-
|
|
1349
|
+
// If an internal update cycle is still in progress, defer value application
|
|
1350
|
+
// rather than dropping it.
|
|
1351
|
+
if (this.internalUpdateInProgress || this.host.internalUpdateInProgress) {
|
|
1352
|
+
this.queuePendingValue(value);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1328
1355
|
|
|
1329
1356
|
// Normalize values to array of strings
|
|
1330
1357
|
const normalizedValues = this._getNormalizedValues(value);
|
|
@@ -1336,33 +1363,100 @@ class MenuService {
|
|
|
1336
1363
|
validatedValues = [normalizedValues[0]];
|
|
1337
1364
|
}
|
|
1338
1365
|
|
|
1366
|
+
if (this._menuOptions.length === 0) {
|
|
1367
|
+
this.queuePendingValue(value);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1339
1371
|
// Find matching options by comparing available options to validated values
|
|
1340
1372
|
const trackedKeys = new Set();
|
|
1341
1373
|
const optionsToSelect = this._menuOptions.filter(option => {
|
|
1342
1374
|
const passesFilter = validatedValues.includes(option.key);
|
|
1343
1375
|
const alreadyTracked = trackedKeys.has(option.key);
|
|
1376
|
+
const isActive = option.isActive;
|
|
1344
1377
|
|
|
1345
1378
|
trackedKeys.add(option.key);
|
|
1346
1379
|
|
|
1347
1380
|
// Include the option in the options to be selected if it passes the filter check and
|
|
1348
1381
|
// either hasn't been tracked yet or selectAllMatchingOptions is true
|
|
1349
|
-
return passesFilter && (!alreadyTracked || (alreadyTracked && this.selectAllMatchingOptions));
|
|
1382
|
+
return isActive && passesFilter && (!alreadyTracked || (alreadyTracked && this.selectAllMatchingOptions));
|
|
1350
1383
|
});
|
|
1351
1384
|
|
|
1352
|
-
// Handle selection
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1385
|
+
// Handle no matches: clear existing selection, but do not dispatch an intermediate
|
|
1386
|
+
// undefined value that can overwrite the host value in parent components.
|
|
1387
|
+
if (!optionsToSelect.length) {
|
|
1388
|
+
const hasUnresolvedKeys = this._menuOptions.some((option) => option.isActive && option.key == null);
|
|
1389
|
+
|
|
1390
|
+
if (hasUnresolvedKeys) {
|
|
1391
|
+
this.queuePendingValue(value);
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
this.clearPendingValue();
|
|
1396
|
+
|
|
1397
|
+
if (this.selectedOptions.length > 0) {
|
|
1398
|
+
this.selectedOptions = [];
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// Always notify so the host resets any stale invalid value, even when
|
|
1402
|
+
// selectedOptions was already empty (e.g. double-clicking set-invalid).
|
|
1403
|
+
this.stageUpdate({ reason: 'no-match' });
|
|
1404
|
+
|
|
1405
|
+
// Dispatch failure event if no matches found
|
|
1406
|
+
if (validatedValues.length) {
|
|
1407
|
+
this.dispatchChangeEvent('auroMenu-selectValueFailure', {
|
|
1408
|
+
message: 'No matching options found for the provided value(s).',
|
|
1409
|
+
values: validatedValues
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
return;
|
|
1357
1414
|
}
|
|
1358
1415
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1416
|
+
this.clearPendingValue();
|
|
1417
|
+
|
|
1418
|
+
if (this.optionsArraysMatch(optionsToSelect, this.selectedOptions)) {
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Apply programmatic selection as a single transaction and emit one final state.
|
|
1423
|
+
this.selectedOptions = optionsToSelect;
|
|
1424
|
+
this.stageUpdate();
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* Queues a pending value and schedules a bounded retry.
|
|
1429
|
+
* @param {string|number|Array<string|number>} value - The value to retry.
|
|
1430
|
+
*/
|
|
1431
|
+
queuePendingValue(value) {
|
|
1432
|
+
this._pendingValue = value;
|
|
1433
|
+
|
|
1434
|
+
if (this._pendingRetryScheduled || this._pendingRetryCount >= 5) {
|
|
1435
|
+
return;
|
|
1365
1436
|
}
|
|
1437
|
+
|
|
1438
|
+
this._pendingRetryScheduled = true;
|
|
1439
|
+
this._pendingRetryCount += 1;
|
|
1440
|
+
|
|
1441
|
+
setTimeout(() => {
|
|
1442
|
+
this._pendingRetryScheduled = false;
|
|
1443
|
+
|
|
1444
|
+
if (this._pendingValue == null) {
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
const pendingValue = this._pendingValue;
|
|
1449
|
+
this.selectByValue(pendingValue);
|
|
1450
|
+
}, 0);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* Clears pending retry state.
|
|
1455
|
+
*/
|
|
1456
|
+
clearPendingValue() {
|
|
1457
|
+
this._pendingValue = null;
|
|
1458
|
+
this._pendingRetryScheduled = false;
|
|
1459
|
+
this._pendingRetryCount = 0;
|
|
1366
1460
|
}
|
|
1367
1461
|
|
|
1368
1462
|
/**
|
|
@@ -1401,9 +1495,9 @@ class MenuService {
|
|
|
1401
1495
|
/**
|
|
1402
1496
|
* Stages an update to notify subscribers of state and value changes.
|
|
1403
1497
|
*/
|
|
1404
|
-
stageUpdate() {
|
|
1405
|
-
this.notifyStateChange();
|
|
1406
|
-
this.notifyValueChange();
|
|
1498
|
+
stageUpdate(meta = {}) {
|
|
1499
|
+
this.notifyStateChange(meta);
|
|
1500
|
+
this.notifyValueChange(meta);
|
|
1407
1501
|
}
|
|
1408
1502
|
|
|
1409
1503
|
/**
|
|
@@ -1418,14 +1512,18 @@ class MenuService {
|
|
|
1418
1512
|
/**
|
|
1419
1513
|
* Notifies subscribers of a state change (selected options has changed).
|
|
1420
1514
|
*/
|
|
1421
|
-
notifyStateChange() {
|
|
1422
|
-
this.notify({
|
|
1515
|
+
notifyStateChange(meta = {}) {
|
|
1516
|
+
this.notify({
|
|
1517
|
+
type: 'stateChange',
|
|
1518
|
+
selectedOptions: this.selectedOptions,
|
|
1519
|
+
...meta
|
|
1520
|
+
});
|
|
1423
1521
|
}
|
|
1424
1522
|
|
|
1425
1523
|
/**
|
|
1426
1524
|
* Notifies subscribers of a value change (current value has changed).
|
|
1427
1525
|
*/
|
|
1428
|
-
notifyValueChange() {
|
|
1526
|
+
notifyValueChange(meta = {}) {
|
|
1429
1527
|
|
|
1430
1528
|
// Prepare details for the event
|
|
1431
1529
|
const details = {
|
|
@@ -1441,10 +1539,9 @@ class MenuService {
|
|
|
1441
1539
|
|
|
1442
1540
|
this.notify({
|
|
1443
1541
|
type: 'valueChange',
|
|
1542
|
+
...meta,
|
|
1444
1543
|
...details
|
|
1445
1544
|
});
|
|
1446
|
-
|
|
1447
|
-
this.dispatchChangeEvent('auroMenu-selectedOption', details);
|
|
1448
1545
|
}
|
|
1449
1546
|
|
|
1450
1547
|
/**
|
|
@@ -1472,6 +1569,10 @@ class MenuService {
|
|
|
1472
1569
|
addMenuOption(option) {
|
|
1473
1570
|
this._menuOptions.push(option);
|
|
1474
1571
|
this.notify({ type: 'optionsChange', options: this._menuOptions });
|
|
1572
|
+
|
|
1573
|
+
if (this._pendingValue != null) {
|
|
1574
|
+
this.queuePendingValue(this._pendingValue);
|
|
1575
|
+
}
|
|
1475
1576
|
}
|
|
1476
1577
|
|
|
1477
1578
|
/**
|
|
@@ -1481,6 +1582,10 @@ class MenuService {
|
|
|
1481
1582
|
removeMenuOption(option) {
|
|
1482
1583
|
this._menuOptions = this._menuOptions.filter(opt => opt !== option);
|
|
1483
1584
|
this.notify({ type: 'optionsChange', options: this._menuOptions });
|
|
1585
|
+
|
|
1586
|
+
if (this._menuOptions.length === 0) {
|
|
1587
|
+
this.clearPendingValue();
|
|
1588
|
+
}
|
|
1484
1589
|
}
|
|
1485
1590
|
|
|
1486
1591
|
/**
|
|
@@ -1754,7 +1859,7 @@ class AuroMenu extends AuroElement {
|
|
|
1754
1859
|
},
|
|
1755
1860
|
|
|
1756
1861
|
/**
|
|
1757
|
-
* Available menu options
|
|
1862
|
+
* Available menu options.
|
|
1758
1863
|
* @readonly
|
|
1759
1864
|
*/
|
|
1760
1865
|
options: {
|
|
@@ -1821,7 +1926,7 @@ class AuroMenu extends AuroElement {
|
|
|
1821
1926
|
/**
|
|
1822
1927
|
* @readonly
|
|
1823
1928
|
* @returns {Array<HTMLElement>} - Returns the array of available menu options.
|
|
1824
|
-
* @deprecated
|
|
1929
|
+
* @deprecated Use `options` property instead.
|
|
1825
1930
|
*/
|
|
1826
1931
|
get items() {
|
|
1827
1932
|
return this.options;
|
|
@@ -1929,7 +2034,7 @@ class AuroMenu extends AuroElement {
|
|
|
1929
2034
|
const newValue = event.stringValue;
|
|
1930
2035
|
|
|
1931
2036
|
// Check if the option or value has actually changed
|
|
1932
|
-
if (
|
|
2037
|
+
if (this.optionSelected !== newOption || this.stringValue !== newValue) {
|
|
1933
2038
|
this.optionSelected = newOption;
|
|
1934
2039
|
this.setInternalValue(newValue);
|
|
1935
2040
|
}
|
|
@@ -2003,8 +2108,13 @@ class AuroMenu extends AuroElement {
|
|
|
2003
2108
|
updated(changedProperties) {
|
|
2004
2109
|
super.updated(changedProperties);
|
|
2005
2110
|
|
|
2006
|
-
//
|
|
2007
|
-
|
|
2111
|
+
// Apply value selection synchronously so that static-HTML fixtures
|
|
2112
|
+
// resolve within a single update cycle. The refactored selectByValue
|
|
2113
|
+
// no longer calls reset() first, so the destructive intermediate-event
|
|
2114
|
+
// cascade that originally required deferral is eliminated. If option
|
|
2115
|
+
// keys are not yet resolved (framework mount-order race), selectByValue
|
|
2116
|
+
// queues a bounded retry automatically via queuePendingValue.
|
|
2117
|
+
if (changedProperties.has('value') && !this.internalUpdateInProgress) {
|
|
2008
2118
|
this.menuService.selectByValue(this.value);
|
|
2009
2119
|
}
|
|
2010
2120
|
|
|
@@ -2184,12 +2294,13 @@ class AuroMenu extends AuroElement {
|
|
|
2184
2294
|
* @param {any} source - The source that triggers this event.
|
|
2185
2295
|
* @private
|
|
2186
2296
|
*/
|
|
2187
|
-
notifySelectionChange({value, stringValue, keys, options} = {}) {
|
|
2297
|
+
notifySelectionChange({value, stringValue, keys, options, reason} = {}) {
|
|
2188
2298
|
dispatchMenuEvent(this, 'auroMenu-selectedOption', {
|
|
2189
2299
|
value,
|
|
2190
2300
|
stringValue,
|
|
2191
2301
|
keys,
|
|
2192
|
-
options
|
|
2302
|
+
options,
|
|
2303
|
+
reason
|
|
2193
2304
|
});
|
|
2194
2305
|
}
|
|
2195
2306
|
|
|
@@ -558,10 +558,20 @@ class AuroMenuOption extends AuroElement {
|
|
|
558
558
|
subscribe: true
|
|
559
559
|
});
|
|
560
560
|
|
|
561
|
-
// Establish the key property as early as possible
|
|
561
|
+
// Establish the key property as early as possible.
|
|
562
|
+
// When a framework (e.g. Svelte) inserts the element into the DOM before
|
|
563
|
+
// setting its `value` property, both `getAttribute('value')` and
|
|
564
|
+
// `getAttribute('key')` return null here. Setting `this.key = null`
|
|
565
|
+
// would block the fallback in `updated()` that assigns key from the
|
|
566
|
+
// value property (the guard checked `=== undefined`). Only assign key
|
|
567
|
+
// if at least one source attribute is actually present so that the
|
|
568
|
+
// `updated()` fallback can run when the value property arrives later.
|
|
562
569
|
const valueAttr = this.getAttribute('value');
|
|
563
570
|
const keyAttr = this.getAttribute('key');
|
|
564
|
-
|
|
571
|
+
const resolvedKey = keyAttr !== null ? keyAttr : valueAttr;
|
|
572
|
+
if (resolvedKey !== null) {
|
|
573
|
+
this.key = resolvedKey;
|
|
574
|
+
}
|
|
565
575
|
}
|
|
566
576
|
|
|
567
577
|
firstUpdated() {
|
|
@@ -611,8 +621,14 @@ class AuroMenuOption extends AuroElement {
|
|
|
611
621
|
this.updateTextHighlight();
|
|
612
622
|
}
|
|
613
623
|
|
|
614
|
-
// Set the key to be the passed value if no key is provided
|
|
615
|
-
|
|
624
|
+
// Set the key to be the passed value if no key is provided.
|
|
625
|
+
// Loose equality (== null) is intentional: it catches both null AND
|
|
626
|
+
// undefined. When a framework (e.g. Svelte, React) inserts the element
|
|
627
|
+
// before setting its value property, connectedCallback skips key
|
|
628
|
+
// assignment because both attributes are null at that point. The Lit
|
|
629
|
+
// property default for `key` is undefined (not null), so strict
|
|
630
|
+
// === null would miss the case and the fallback would never run.
|
|
631
|
+
if (changedProperties.has('value') && this.key == null) { // eslint-disable-line eqeqeq, no-eq-null
|
|
616
632
|
this.key = this.value;
|
|
617
633
|
}
|
|
618
634
|
}
|
|
@@ -981,6 +997,9 @@ class MenuService {
|
|
|
981
997
|
this._subscribers = [];
|
|
982
998
|
this.internalUpdateInProgress = false;
|
|
983
999
|
this.selectedOptions = [];
|
|
1000
|
+
this._pendingValue = null;
|
|
1001
|
+
this._pendingRetryScheduled = false;
|
|
1002
|
+
this._pendingRetryCount = 0;
|
|
984
1003
|
}
|
|
985
1004
|
|
|
986
1005
|
/**
|
|
@@ -1020,6 +1039,9 @@ class MenuService {
|
|
|
1020
1039
|
hostDisconnected() {
|
|
1021
1040
|
this._subscribers = [];
|
|
1022
1041
|
this._menuOptions = [];
|
|
1042
|
+
this._pendingValue = null;
|
|
1043
|
+
this._pendingRetryScheduled = false;
|
|
1044
|
+
this._pendingRetryCount = 0;
|
|
1023
1045
|
}
|
|
1024
1046
|
|
|
1025
1047
|
/**
|
|
@@ -1222,17 +1244,22 @@ class MenuService {
|
|
|
1222
1244
|
* @param {string|number|Array<string|number>} value - The value(s) to select.
|
|
1223
1245
|
*/
|
|
1224
1246
|
selectByValue(value) {
|
|
1225
|
-
|
|
1226
|
-
if (this.internalUpdateInProgress ||
|
|
1227
|
-
this.host.internalUpdateInProgress ||
|
|
1228
|
-
value === undefined ||
|
|
1247
|
+
const isEmptyValue = value === undefined ||
|
|
1229
1248
|
value === null ||
|
|
1230
1249
|
(Array.isArray(value) && value.length === 0) ||
|
|
1231
|
-
(typeof value === 'string' && value.trim() === '')
|
|
1250
|
+
(typeof value === 'string' && value.trim() === '');
|
|
1251
|
+
|
|
1252
|
+
// Early exit for invalid/empty values
|
|
1253
|
+
if (isEmptyValue) {
|
|
1232
1254
|
return;
|
|
1233
1255
|
}
|
|
1234
1256
|
|
|
1235
|
-
|
|
1257
|
+
// If an internal update cycle is still in progress, defer value application
|
|
1258
|
+
// rather than dropping it.
|
|
1259
|
+
if (this.internalUpdateInProgress || this.host.internalUpdateInProgress) {
|
|
1260
|
+
this.queuePendingValue(value);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1236
1263
|
|
|
1237
1264
|
// Normalize values to array of strings
|
|
1238
1265
|
const normalizedValues = this._getNormalizedValues(value);
|
|
@@ -1244,33 +1271,100 @@ class MenuService {
|
|
|
1244
1271
|
validatedValues = [normalizedValues[0]];
|
|
1245
1272
|
}
|
|
1246
1273
|
|
|
1274
|
+
if (this._menuOptions.length === 0) {
|
|
1275
|
+
this.queuePendingValue(value);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1247
1279
|
// Find matching options by comparing available options to validated values
|
|
1248
1280
|
const trackedKeys = new Set();
|
|
1249
1281
|
const optionsToSelect = this._menuOptions.filter(option => {
|
|
1250
1282
|
const passesFilter = validatedValues.includes(option.key);
|
|
1251
1283
|
const alreadyTracked = trackedKeys.has(option.key);
|
|
1284
|
+
const isActive = option.isActive;
|
|
1252
1285
|
|
|
1253
1286
|
trackedKeys.add(option.key);
|
|
1254
1287
|
|
|
1255
1288
|
// Include the option in the options to be selected if it passes the filter check and
|
|
1256
1289
|
// either hasn't been tracked yet or selectAllMatchingOptions is true
|
|
1257
|
-
return passesFilter && (!alreadyTracked || (alreadyTracked && this.selectAllMatchingOptions));
|
|
1290
|
+
return isActive && passesFilter && (!alreadyTracked || (alreadyTracked && this.selectAllMatchingOptions));
|
|
1258
1291
|
});
|
|
1259
1292
|
|
|
1260
|
-
// Handle selection
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1293
|
+
// Handle no matches: clear existing selection, but do not dispatch an intermediate
|
|
1294
|
+
// undefined value that can overwrite the host value in parent components.
|
|
1295
|
+
if (!optionsToSelect.length) {
|
|
1296
|
+
const hasUnresolvedKeys = this._menuOptions.some((option) => option.isActive && option.key == null);
|
|
1297
|
+
|
|
1298
|
+
if (hasUnresolvedKeys) {
|
|
1299
|
+
this.queuePendingValue(value);
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
this.clearPendingValue();
|
|
1304
|
+
|
|
1305
|
+
if (this.selectedOptions.length > 0) {
|
|
1306
|
+
this.selectedOptions = [];
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// Always notify so the host resets any stale invalid value, even when
|
|
1310
|
+
// selectedOptions was already empty (e.g. double-clicking set-invalid).
|
|
1311
|
+
this.stageUpdate({ reason: 'no-match' });
|
|
1312
|
+
|
|
1313
|
+
// Dispatch failure event if no matches found
|
|
1314
|
+
if (validatedValues.length) {
|
|
1315
|
+
this.dispatchChangeEvent('auroMenu-selectValueFailure', {
|
|
1316
|
+
message: 'No matching options found for the provided value(s).',
|
|
1317
|
+
values: validatedValues
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
return;
|
|
1265
1322
|
}
|
|
1266
1323
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1324
|
+
this.clearPendingValue();
|
|
1325
|
+
|
|
1326
|
+
if (this.optionsArraysMatch(optionsToSelect, this.selectedOptions)) {
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// Apply programmatic selection as a single transaction and emit one final state.
|
|
1331
|
+
this.selectedOptions = optionsToSelect;
|
|
1332
|
+
this.stageUpdate();
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
/**
|
|
1336
|
+
* Queues a pending value and schedules a bounded retry.
|
|
1337
|
+
* @param {string|number|Array<string|number>} value - The value to retry.
|
|
1338
|
+
*/
|
|
1339
|
+
queuePendingValue(value) {
|
|
1340
|
+
this._pendingValue = value;
|
|
1341
|
+
|
|
1342
|
+
if (this._pendingRetryScheduled || this._pendingRetryCount >= 5) {
|
|
1343
|
+
return;
|
|
1273
1344
|
}
|
|
1345
|
+
|
|
1346
|
+
this._pendingRetryScheduled = true;
|
|
1347
|
+
this._pendingRetryCount += 1;
|
|
1348
|
+
|
|
1349
|
+
setTimeout(() => {
|
|
1350
|
+
this._pendingRetryScheduled = false;
|
|
1351
|
+
|
|
1352
|
+
if (this._pendingValue == null) {
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
const pendingValue = this._pendingValue;
|
|
1357
|
+
this.selectByValue(pendingValue);
|
|
1358
|
+
}, 0);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* Clears pending retry state.
|
|
1363
|
+
*/
|
|
1364
|
+
clearPendingValue() {
|
|
1365
|
+
this._pendingValue = null;
|
|
1366
|
+
this._pendingRetryScheduled = false;
|
|
1367
|
+
this._pendingRetryCount = 0;
|
|
1274
1368
|
}
|
|
1275
1369
|
|
|
1276
1370
|
/**
|
|
@@ -1309,9 +1403,9 @@ class MenuService {
|
|
|
1309
1403
|
/**
|
|
1310
1404
|
* Stages an update to notify subscribers of state and value changes.
|
|
1311
1405
|
*/
|
|
1312
|
-
stageUpdate() {
|
|
1313
|
-
this.notifyStateChange();
|
|
1314
|
-
this.notifyValueChange();
|
|
1406
|
+
stageUpdate(meta = {}) {
|
|
1407
|
+
this.notifyStateChange(meta);
|
|
1408
|
+
this.notifyValueChange(meta);
|
|
1315
1409
|
}
|
|
1316
1410
|
|
|
1317
1411
|
/**
|
|
@@ -1326,14 +1420,18 @@ class MenuService {
|
|
|
1326
1420
|
/**
|
|
1327
1421
|
* Notifies subscribers of a state change (selected options has changed).
|
|
1328
1422
|
*/
|
|
1329
|
-
notifyStateChange() {
|
|
1330
|
-
this.notify({
|
|
1423
|
+
notifyStateChange(meta = {}) {
|
|
1424
|
+
this.notify({
|
|
1425
|
+
type: 'stateChange',
|
|
1426
|
+
selectedOptions: this.selectedOptions,
|
|
1427
|
+
...meta
|
|
1428
|
+
});
|
|
1331
1429
|
}
|
|
1332
1430
|
|
|
1333
1431
|
/**
|
|
1334
1432
|
* Notifies subscribers of a value change (current value has changed).
|
|
1335
1433
|
*/
|
|
1336
|
-
notifyValueChange() {
|
|
1434
|
+
notifyValueChange(meta = {}) {
|
|
1337
1435
|
|
|
1338
1436
|
// Prepare details for the event
|
|
1339
1437
|
const details = {
|
|
@@ -1349,10 +1447,9 @@ class MenuService {
|
|
|
1349
1447
|
|
|
1350
1448
|
this.notify({
|
|
1351
1449
|
type: 'valueChange',
|
|
1450
|
+
...meta,
|
|
1352
1451
|
...details
|
|
1353
1452
|
});
|
|
1354
|
-
|
|
1355
|
-
this.dispatchChangeEvent('auroMenu-selectedOption', details);
|
|
1356
1453
|
}
|
|
1357
1454
|
|
|
1358
1455
|
/**
|
|
@@ -1380,6 +1477,10 @@ class MenuService {
|
|
|
1380
1477
|
addMenuOption(option) {
|
|
1381
1478
|
this._menuOptions.push(option);
|
|
1382
1479
|
this.notify({ type: 'optionsChange', options: this._menuOptions });
|
|
1480
|
+
|
|
1481
|
+
if (this._pendingValue != null) {
|
|
1482
|
+
this.queuePendingValue(this._pendingValue);
|
|
1483
|
+
}
|
|
1383
1484
|
}
|
|
1384
1485
|
|
|
1385
1486
|
/**
|
|
@@ -1389,6 +1490,10 @@ class MenuService {
|
|
|
1389
1490
|
removeMenuOption(option) {
|
|
1390
1491
|
this._menuOptions = this._menuOptions.filter(opt => opt !== option);
|
|
1391
1492
|
this.notify({ type: 'optionsChange', options: this._menuOptions });
|
|
1493
|
+
|
|
1494
|
+
if (this._menuOptions.length === 0) {
|
|
1495
|
+
this.clearPendingValue();
|
|
1496
|
+
}
|
|
1392
1497
|
}
|
|
1393
1498
|
|
|
1394
1499
|
/**
|
|
@@ -1662,7 +1767,7 @@ class AuroMenu extends AuroElement {
|
|
|
1662
1767
|
},
|
|
1663
1768
|
|
|
1664
1769
|
/**
|
|
1665
|
-
* Available menu options
|
|
1770
|
+
* Available menu options.
|
|
1666
1771
|
* @readonly
|
|
1667
1772
|
*/
|
|
1668
1773
|
options: {
|
|
@@ -1729,7 +1834,7 @@ class AuroMenu extends AuroElement {
|
|
|
1729
1834
|
/**
|
|
1730
1835
|
* @readonly
|
|
1731
1836
|
* @returns {Array<HTMLElement>} - Returns the array of available menu options.
|
|
1732
|
-
* @deprecated
|
|
1837
|
+
* @deprecated Use `options` property instead.
|
|
1733
1838
|
*/
|
|
1734
1839
|
get items() {
|
|
1735
1840
|
return this.options;
|
|
@@ -1837,7 +1942,7 @@ class AuroMenu extends AuroElement {
|
|
|
1837
1942
|
const newValue = event.stringValue;
|
|
1838
1943
|
|
|
1839
1944
|
// Check if the option or value has actually changed
|
|
1840
|
-
if (
|
|
1945
|
+
if (this.optionSelected !== newOption || this.stringValue !== newValue) {
|
|
1841
1946
|
this.optionSelected = newOption;
|
|
1842
1947
|
this.setInternalValue(newValue);
|
|
1843
1948
|
}
|
|
@@ -1911,8 +2016,13 @@ class AuroMenu extends AuroElement {
|
|
|
1911
2016
|
updated(changedProperties) {
|
|
1912
2017
|
super.updated(changedProperties);
|
|
1913
2018
|
|
|
1914
|
-
//
|
|
1915
|
-
|
|
2019
|
+
// Apply value selection synchronously so that static-HTML fixtures
|
|
2020
|
+
// resolve within a single update cycle. The refactored selectByValue
|
|
2021
|
+
// no longer calls reset() first, so the destructive intermediate-event
|
|
2022
|
+
// cascade that originally required deferral is eliminated. If option
|
|
2023
|
+
// keys are not yet resolved (framework mount-order race), selectByValue
|
|
2024
|
+
// queues a bounded retry automatically via queuePendingValue.
|
|
2025
|
+
if (changedProperties.has('value') && !this.internalUpdateInProgress) {
|
|
1916
2026
|
this.menuService.selectByValue(this.value);
|
|
1917
2027
|
}
|
|
1918
2028
|
|
|
@@ -2092,12 +2202,13 @@ class AuroMenu extends AuroElement {
|
|
|
2092
2202
|
* @param {any} source - The source that triggers this event.
|
|
2093
2203
|
* @private
|
|
2094
2204
|
*/
|
|
2095
|
-
notifySelectionChange({value, stringValue, keys, options} = {}) {
|
|
2205
|
+
notifySelectionChange({value, stringValue, keys, options, reason} = {}) {
|
|
2096
2206
|
dispatchMenuEvent(this, 'auroMenu-selectedOption', {
|
|
2097
2207
|
value,
|
|
2098
2208
|
stringValue,
|
|
2099
2209
|
keys,
|
|
2100
|
-
options
|
|
2210
|
+
options,
|
|
2211
|
+
reason
|
|
2101
2212
|
});
|
|
2102
2213
|
}
|
|
2103
2214
|
|