@aurodesignsystem-dev/auro-formkit 0.0.0-pr1582.0 → 0.0.0-pr1582.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/checkbox/demo/customize.min.js +1 -1
- package/components/checkbox/demo/getting-started.min.js +1 -1
- package/components/checkbox/demo/index.min.js +1 -1
- package/components/checkbox/dist/index.js +1 -1
- package/components/checkbox/dist/registered.js +1 -1
- package/components/combobox/demo/customize.md +40 -0
- package/components/combobox/demo/customize.min.js +400 -62
- package/components/combobox/demo/getting-started.min.js +400 -62
- package/components/combobox/demo/index.html +1 -1
- package/components/combobox/demo/index.min.js +400 -62
- package/components/combobox/dist/index.js +78 -35
- package/components/combobox/dist/registered.js +78 -35
- package/components/counter/demo/customize.min.js +2 -2
- package/components/counter/demo/index.min.js +2 -2
- package/components/counter/dist/index.js +2 -2
- package/components/counter/dist/registered.js +2 -2
- package/components/datepicker/demo/customize.min.js +22 -9
- package/components/datepicker/demo/index.min.js +22 -9
- package/components/datepicker/dist/index.js +22 -9
- package/components/datepicker/dist/registered.js +22 -9
- package/components/dropdown/demo/customize.min.js +1 -1
- package/components/dropdown/demo/getting-started.min.js +21 -8
- package/components/dropdown/demo/index.min.js +1 -1
- package/components/dropdown/dist/index.js +1 -1
- package/components/dropdown/dist/registered.js +1 -1
- package/components/form/demo/customize.min.js +448 -84
- package/components/form/demo/getting-started.min.js +448 -84
- package/components/form/demo/index.min.js +448 -84
- package/components/form/demo/registerDemoDeps.min.js +448 -84
- package/components/input/demo/customize.min.js +20 -7
- package/components/input/demo/getting-started.min.js +20 -7
- package/components/input/demo/index.min.js +20 -7
- package/components/input/dist/auro-input.d.ts +8 -0
- package/components/input/dist/index.js +20 -7
- package/components/input/dist/registered.js +20 -7
- package/components/menu/demo/customize.md +50 -0
- package/components/menu/demo/index.min.js +322 -27
- package/components/menu/dist/auro-menu-utils.d.ts +30 -0
- package/components/menu/dist/auro-menu.d.ts +23 -0
- package/components/menu/dist/index.js +322 -27
- package/components/menu/dist/registered.js +322 -27
- package/components/radio/demo/customize.min.js +1 -1
- package/components/radio/demo/getting-started.min.js +1 -1
- package/components/radio/demo/index.min.js +1 -1
- package/components/radio/dist/index.js +1 -1
- package/components/radio/dist/registered.js +1 -1
- package/components/select/demo/customize.md +72 -0
- package/components/select/demo/customize.min.js +324 -29
- package/components/select/demo/getting-started.min.js +324 -29
- package/components/select/demo/index.min.js +324 -29
- package/components/select/dist/index.js +2 -2
- package/components/select/dist/registered.js +2 -2
- package/custom-elements.json +115 -0
- package/package.json +1 -1
|
@@ -4977,7 +4977,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$3 {
|
|
|
4977
4977
|
}
|
|
4978
4978
|
};
|
|
4979
4979
|
|
|
4980
|
-
var formkitVersion$2 = '
|
|
4980
|
+
var formkitVersion$2 = '202608111845';
|
|
4981
4981
|
|
|
4982
4982
|
let AuroElement$2 = class AuroElement extends i$3 {
|
|
4983
4983
|
static get properties() {
|
|
@@ -18677,7 +18677,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$3 {
|
|
|
18677
18677
|
}
|
|
18678
18678
|
};
|
|
18679
18679
|
|
|
18680
|
-
var formkitVersion$1 = '
|
|
18680
|
+
var formkitVersion$1 = '202608111845';
|
|
18681
18681
|
|
|
18682
18682
|
// Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
|
|
18683
18683
|
// See LICENSE in the project root for license information.
|
|
@@ -18778,22 +18778,35 @@ class AuroInput extends BaseInput {
|
|
|
18778
18778
|
* @private
|
|
18779
18779
|
*/
|
|
18780
18780
|
get inputHidden() {
|
|
18781
|
-
//
|
|
18782
|
-
// raised
|
|
18783
|
-
//
|
|
18784
|
-
|
|
18785
|
-
|
|
18786
|
-
|
|
18781
|
+
// In the classic/default layout, activeLabel keeps the input row visible so
|
|
18782
|
+
// the label stays raised rather than collapsing to the centered placeholder
|
|
18783
|
+
// position. Emphasized/snowflake always render the label inside the field,
|
|
18784
|
+
// so activeLabel does not apply there. This only overrides the empty/
|
|
18785
|
+
// unfocused clause — the display-value clause (combobox/datepicker) is
|
|
18786
|
+
// preserved regardless.
|
|
18787
|
+
const activeLabelRaisesInput = this.activeLabel && this.isClassicLayout;
|
|
18787
18788
|
|
|
18788
18789
|
return (
|
|
18789
18790
|
this.hasDisplayValueContent && !this.hasFocus && this.hasValue) ||
|
|
18790
18791
|
(
|
|
18792
|
+
!activeLabelRaisesInput &&
|
|
18791
18793
|
(!this.value || this.value.length === 0) &&
|
|
18792
18794
|
!this.hasFocus &&
|
|
18793
18795
|
(!this.placeholderStr || this.placeholderStr === '')
|
|
18794
18796
|
);
|
|
18795
18797
|
}
|
|
18796
18798
|
|
|
18799
|
+
/**
|
|
18800
|
+
* Whether the component is rendering the classic/default layout, meaning not
|
|
18801
|
+
* emphasized and not snowflake. Kept as a single source of truth so
|
|
18802
|
+
* layout-scoped behavior such as activeLabel stays consistent across getters.
|
|
18803
|
+
* @returns {boolean} - True for the classic/default layout.
|
|
18804
|
+
* @private
|
|
18805
|
+
*/
|
|
18806
|
+
get isClassicLayout() {
|
|
18807
|
+
return !this.layout.startsWith('emphasized') && this.layout !== 'snowflake';
|
|
18808
|
+
}
|
|
18809
|
+
|
|
18797
18810
|
/**
|
|
18798
18811
|
* Determines if the input should display in a state with no focus or value indication.
|
|
18799
18812
|
* Returns true when the input has display content without focus and has a value,
|
|
@@ -19840,7 +19853,7 @@ class AuroBibtemplate extends i$3 {
|
|
|
19840
19853
|
}
|
|
19841
19854
|
}
|
|
19842
19855
|
|
|
19843
|
-
var formkitVersion = '
|
|
19856
|
+
var formkitVersion = '202608111845';
|
|
19844
19857
|
|
|
19845
19858
|
var styleCss$3 = i$6`.util_displayInline{display:inline}.util_displayInlineBlock{display:inline-block}.util_displayBlock{display:block}.util_displayFlex{display:flex}.util_displayHidden{display:none}.util_displayHiddenVisually{position:absolute;overflow:hidden;clip:rect(1px, 1px, 1px, 1px);width:1px;height:1px;padding:0;border:0}:host{display:block;text-align:left}:host [auro-dropdown]{--ds-auro-dropdown-trigger-background-color: transparent}:host #inputInBib::part(wrapper){box-shadow:none}:host #inputInBib::part(accent-left){display:none}:host([layout*=classic]) [auro-input]{width:100%}:host([layout*=classic]) [auro-input]::part(helpText){display:none}:host([layout*=classic]) #slotHolder{display:none}`;
|
|
19846
19859
|
|
|
@@ -21646,7 +21659,17 @@ class AuroCombobox extends AuroElement {
|
|
|
21646
21659
|
return;
|
|
21647
21660
|
}
|
|
21648
21661
|
|
|
21649
|
-
this
|
|
21662
|
+
// Skip this write during a display-value sync: that path sets the input to
|
|
21663
|
+
// the selected option's LABEL, which is not its machine value. When an
|
|
21664
|
+
// option's label differs from its value (and especially when several
|
|
21665
|
+
// options share a value but render distinct labels — AB#1602086), writing
|
|
21666
|
+
// the label back into this.value clobbers the machine value the selection
|
|
21667
|
+
// listener just set (this.value = optionSelected.value), collapsing the
|
|
21668
|
+
// selection. _syncingBibValue still tracks (that mirrors user-typed text
|
|
21669
|
+
// from the fullscreen bib, where value should follow the input).
|
|
21670
|
+
if (!this._syncingDisplayValue) {
|
|
21671
|
+
this.value = this.input.value;
|
|
21672
|
+
}
|
|
21650
21673
|
|
|
21651
21674
|
// Ignore re-entrant input events caused by programmatic value sets.
|
|
21652
21675
|
if (this._syncingBibValue || this._syncingDisplayValue) {
|
|
@@ -21910,33 +21933,53 @@ class AuroCombobox extends AuroElement {
|
|
|
21910
21933
|
}
|
|
21911
21934
|
|
|
21912
21935
|
if (this.input.value !== this.value) {
|
|
21913
|
-
//
|
|
21914
|
-
//
|
|
21915
|
-
//
|
|
21916
|
-
//
|
|
21917
|
-
//
|
|
21918
|
-
|
|
21919
|
-
|
|
21920
|
-
|
|
21936
|
+
// A fresh user selection leaves the input showing the option's LABEL
|
|
21937
|
+
// while this.value holds its machine value, so input.value !== this.value
|
|
21938
|
+
// is expected whenever label ≠ value. In that case the menu's current
|
|
21939
|
+
// selection is authoritative — and when several options share a value it
|
|
21940
|
+
// is the ONLY thing that records WHICH same-value option the user picked
|
|
21941
|
+
// (the menu tracks it by element identity via `_selectedKey`). Clearing
|
|
21942
|
+
// it here drops that key; the ensuing value-only re-resolution then
|
|
21943
|
+
// collapses the selection onto the first same-value option (AB#1602086).
|
|
21944
|
+
// So only treat the divergence as a stale menu when the selected option
|
|
21945
|
+
// does NOT match the new value.
|
|
21946
|
+
const menuSelectionMatchesValue =
|
|
21947
|
+
this.menu.optionSelected &&
|
|
21948
|
+
// optionSelected is a single element here (scalar `.value`). A multiselect
|
|
21949
|
+
// menu exposes it as an ARRAY, which has no scalar `.value` to compare, so
|
|
21950
|
+
// that shape can't be a single-option match — fall through to the clear path.
|
|
21951
|
+
!Array.isArray(this.menu.optionSelected) &&
|
|
21952
|
+
this.menu.optionSelected.value === this.value;
|
|
21953
|
+
|
|
21954
|
+
if (!menuSelectionMatchesValue) {
|
|
21955
|
+
// Clear menu.value AND menu.optionSelected together. Clearing only
|
|
21956
|
+
// menu.value leaves the previously-selected option element pinned
|
|
21957
|
+
// as menu.optionSelected; a later auroMenu-selectedOption event
|
|
21958
|
+
// would then write its stale .value back into combobox.value
|
|
21959
|
+
// (e.g. Tab-after-Backspace re-selecting the prior option).
|
|
21960
|
+
if (this.menu.value || this.menu.optionSelected) {
|
|
21961
|
+
this.menu.clearSelection();
|
|
21962
|
+
}
|
|
21921
21963
|
|
|
21922
|
-
|
|
21923
|
-
|
|
21924
|
-
|
|
21964
|
+
if (!this.persistInput) {
|
|
21965
|
+
this.syncInputValuesAcrossTriggerAndBib(this.value || '');
|
|
21966
|
+
}
|
|
21925
21967
|
|
|
21926
|
-
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
|
|
21968
|
+
// Programmatic value with no matching option: updateFilter will close
|
|
21969
|
+
// the bib silently (see line 648 — no noMatchOption + 0 results
|
|
21970
|
+
// hides). Announce so screen-reader users hear the request was
|
|
21971
|
+
// dropped. Gated on `input.value !== this.value` so this never fires
|
|
21972
|
+
// for user typing — that path always reconciles input.value to
|
|
21973
|
+
// this.value before updated() runs.
|
|
21974
|
+
if (
|
|
21975
|
+
this.value &&
|
|
21976
|
+
this.menu &&
|
|
21977
|
+
this.menu.options &&
|
|
21978
|
+
this.menu.options.length > 0 &&
|
|
21979
|
+
!this.menu.options.some((opt) => opt.value === this.value)
|
|
21980
|
+
) {
|
|
21981
|
+
announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${this.value}`);
|
|
21982
|
+
}
|
|
21940
21983
|
}
|
|
21941
21984
|
}
|
|
21942
21985
|
|
|
@@ -22273,6 +22316,116 @@ function isSelectableByValue(option) {
|
|
|
22273
22316
|
!option.hasAttribute('static');
|
|
22274
22317
|
}
|
|
22275
22318
|
|
|
22319
|
+
/* eslint-disable no-underscore-dangle */
|
|
22320
|
+
/**
|
|
22321
|
+
* Resolves the single selected option for a given `value`, preferring the
|
|
22322
|
+
* option tracked by `selectedKey` (a user-initiated selection) over a
|
|
22323
|
+
* first-by-value match. When multiple options share the same `value`, matching
|
|
22324
|
+
* by `value` alone cannot distinguish which one the user picked; the key
|
|
22325
|
+
* disambiguates it.
|
|
22326
|
+
*
|
|
22327
|
+
* The key is trusted only when it still resolves to an option whose `value`
|
|
22328
|
+
* matches the requested `value`. If the key is stale (option removed) or the
|
|
22329
|
+
* value was changed programmatically, resolution falls back to value matching —
|
|
22330
|
+
* preserving backward-compatible behavior for preselection and `selectByValue`.
|
|
22331
|
+
* @private
|
|
22332
|
+
* @param {Array<HTMLElement>} items - The menu's flat option list.
|
|
22333
|
+
* @param {string} value - The value to resolve.
|
|
22334
|
+
* @param {string|undefined} selectedKey - The `_optionKey` of the user-selected option, if any.
|
|
22335
|
+
* @returns {HTMLElement|undefined} The resolved option, or undefined when none match.
|
|
22336
|
+
*/
|
|
22337
|
+
function resolveSelectedOption(items, value, selectedKey) {
|
|
22338
|
+
if (!items) {
|
|
22339
|
+
return undefined;
|
|
22340
|
+
}
|
|
22341
|
+
|
|
22342
|
+
if (selectedKey !== undefined) {
|
|
22343
|
+
const keyed = items.find((item) => item._optionKey === selectedKey);
|
|
22344
|
+
if (keyed && isSelectableByValue(keyed) && keyed.value === value) {
|
|
22345
|
+
return keyed;
|
|
22346
|
+
}
|
|
22347
|
+
// Key exists but the option is gone or its value no longer matches — fall
|
|
22348
|
+
// through to value-based matching.
|
|
22349
|
+
}
|
|
22350
|
+
|
|
22351
|
+
return items.find((item) => isSelectableByValue(item) && item.value === value);
|
|
22352
|
+
}
|
|
22353
|
+
|
|
22354
|
+
/**
|
|
22355
|
+
* Resolves the selected options for a multi-select `value` array, preferring
|
|
22356
|
+
* options tracked by `selectedKeys` (user-initiated selections) and falling
|
|
22357
|
+
* back to value matching for any values not resolved by key. The result is
|
|
22358
|
+
* always sorted into DOM order regardless of selection sequence.
|
|
22359
|
+
* @private
|
|
22360
|
+
* @param {Array<HTMLElement>} items - The menu's flat option list.
|
|
22361
|
+
* @param {Array<string>} valueArray - The selected values.
|
|
22362
|
+
* @param {Array<string>|undefined} selectedKeys - The `_optionKey`s of the user-selected options, if any.
|
|
22363
|
+
* @returns {Array<HTMLElement>} The resolved options in DOM order.
|
|
22364
|
+
*/
|
|
22365
|
+
function resolveSelectedOptions(items, valueArray, selectedKeys) {
|
|
22366
|
+
if (!items) {
|
|
22367
|
+
return [];
|
|
22368
|
+
}
|
|
22369
|
+
|
|
22370
|
+
const resolved = [];
|
|
22371
|
+
// Mirror `resolved` as a Set for O(1) membership checks below, matching the
|
|
22372
|
+
// indexMap optimization used for the sort rather than scanning `resolved`
|
|
22373
|
+
// on every candidate.
|
|
22374
|
+
const resolvedSet = new Set();
|
|
22375
|
+
|
|
22376
|
+
// Track how many of each value are still available to resolve. A value that
|
|
22377
|
+
// appears N times in `valueArray` may be satisfied at most N times total across
|
|
22378
|
+
// the key pass and the value fallback below — matching by count, not presence,
|
|
22379
|
+
// on BOTH passes. This stops a duplicate value from being over-resolved: e.g.
|
|
22380
|
+
// two keyed options that both carry `SEA` cannot both match a single requested
|
|
22381
|
+
// `SEA` (which happens when `value` is set directly without clearing
|
|
22382
|
+
// `_selectedKey`, so more keys survive than the value set now asks for).
|
|
22383
|
+
const remaining = new Map();
|
|
22384
|
+
valueArray.forEach((val) => remaining.set(val, (remaining.get(val) || 0) + 1));
|
|
22385
|
+
|
|
22386
|
+
// Resolve by key first: trust a key only when its option is still selectable
|
|
22387
|
+
// and there is still an unmatched occurrence of its value in the request set.
|
|
22388
|
+
if (Array.isArray(selectedKeys)) {
|
|
22389
|
+
selectedKeys.forEach((key) => {
|
|
22390
|
+
const keyed = items.find((item) => item._optionKey === key);
|
|
22391
|
+
if (keyed && isSelectableByValue(keyed) && (remaining.get(keyed.value) || 0) > 0 && !resolvedSet.has(keyed)) {
|
|
22392
|
+
resolved.push(keyed);
|
|
22393
|
+
resolvedSet.add(keyed);
|
|
22394
|
+
remaining.set(keyed.value, remaining.get(keyed.value) - 1);
|
|
22395
|
+
}
|
|
22396
|
+
});
|
|
22397
|
+
}
|
|
22398
|
+
|
|
22399
|
+
// Fall back to value matching for the occurrences not resolved by key. Iterate
|
|
22400
|
+
// the leftover per-value counts so a value that appears twice but was only
|
|
22401
|
+
// resolved once by key still matches its remaining occurrence(s).
|
|
22402
|
+
remaining.forEach((count, val) => {
|
|
22403
|
+
for (let occurrence = 0; occurrence < count; occurrence += 1) {
|
|
22404
|
+
const option = items.find((item) => isSelectableByValue(item) && item.value === val && !resolvedSet.has(item));
|
|
22405
|
+
if (option) {
|
|
22406
|
+
resolved.push(option);
|
|
22407
|
+
resolvedSet.add(option);
|
|
22408
|
+
}
|
|
22409
|
+
}
|
|
22410
|
+
});
|
|
22411
|
+
|
|
22412
|
+
// Always return in DOM order so display is consistent regardless of the order
|
|
22413
|
+
// keys/values were selected. Every resolved option came from `items`, so an
|
|
22414
|
+
// O(1) index lookup mirrors `_sortSelectedByDomOrder` and avoids the O(n)
|
|
22415
|
+
// `items.indexOf` per comparison for large combobox option sets. Any element
|
|
22416
|
+
// not in `items` (a stale snapshot from a future caller) sorts to the END via
|
|
22417
|
+
// `?? items.length`, matching `_sortSelectedByDomOrder` and avoiding NaN
|
|
22418
|
+
// comparisons.
|
|
22419
|
+
const indexMap = new Map(items.map((item, index) => [
|
|
22420
|
+
item,
|
|
22421
|
+
index
|
|
22422
|
+
]));
|
|
22423
|
+
resolved.sort((optionA, optionB) => (indexMap.get(optionA) ?? items.length) - (indexMap.get(optionB) ?? items.length));
|
|
22424
|
+
|
|
22425
|
+
return resolved;
|
|
22426
|
+
}
|
|
22427
|
+
/* eslint-enable no-underscore-dangle */
|
|
22428
|
+
|
|
22276
22429
|
/**
|
|
22277
22430
|
* Helper method to dispatch custom events.
|
|
22278
22431
|
* @param {HTMLElement} element - Element to dispatch event from.
|
|
@@ -22298,6 +22451,14 @@ function dispatchMenuEvent(element, eventName, detail = null) {
|
|
|
22298
22451
|
// See LICENSE in the project root for license information.
|
|
22299
22452
|
|
|
22300
22453
|
|
|
22454
|
+
/**
|
|
22455
|
+
* Monotonically increasing counter used to give each menu instance a unique
|
|
22456
|
+
* `_menuInstanceId` prefix. Auto-generating the id (rather than using a random
|
|
22457
|
+
* string) keeps option keys deterministic and collision-free across menus.
|
|
22458
|
+
* @private
|
|
22459
|
+
*/
|
|
22460
|
+
let menuInstanceIdCounter = 0;
|
|
22461
|
+
|
|
22301
22462
|
|
|
22302
22463
|
/**
|
|
22303
22464
|
* The `auro-menu` element provides users a way to select from a list of options.
|
|
@@ -22369,9 +22530,8 @@ class AuroMenu extends AuroElement {
|
|
|
22369
22530
|
|
|
22370
22531
|
// Instance properties (non-reactive)
|
|
22371
22532
|
|
|
22372
|
-
|
|
22373
|
-
|
|
22374
|
-
*/
|
|
22533
|
+
menuInstanceIdCounter += 1;
|
|
22534
|
+
|
|
22375
22535
|
Object.assign(this, {
|
|
22376
22536
|
// Root-level menu (true) or a nested submenu (false)
|
|
22377
22537
|
rootMenu: true,
|
|
@@ -22381,6 +22541,21 @@ class AuroMenu extends AuroElement {
|
|
|
22381
22541
|
nestingSpacer: '<span class="nestingSpacer"></span>',
|
|
22382
22542
|
// Loading indicator for slot elements
|
|
22383
22543
|
loadingSlots: null,
|
|
22544
|
+
// Unique id for this menu instance; prefixes every auto-generated option
|
|
22545
|
+
// key so keys never collide across menus in the same document.
|
|
22546
|
+
_menuInstanceId: `menu-${menuInstanceIdCounter}`,
|
|
22547
|
+
// Monotonically increasing counter for option key generation. Never
|
|
22548
|
+
// resets, so a key is never reused within this instance's lifetime.
|
|
22549
|
+
_optionKeyCounter: 0,
|
|
22550
|
+
// Key(s) of the option(s) the user has actively selected. A single string
|
|
22551
|
+
// in single-select, an array in multi-select, undefined when nothing is
|
|
22552
|
+
// user-selected. Used to disambiguate options that share a `value`.
|
|
22553
|
+
_selectedKey: undefined,
|
|
22554
|
+
// True only for the one updated() cycle following a user selection, so
|
|
22555
|
+
// reconciliation trusts `_selectedKey`. A `value` change from a consumer's
|
|
22556
|
+
// direct property assignment leaves this false, dropping the stale key so
|
|
22557
|
+
// reconciliation falls back to first-by-value (see updated()).
|
|
22558
|
+
_valueChangeFromSelection: false,
|
|
22384
22559
|
});
|
|
22385
22560
|
}
|
|
22386
22561
|
|
|
@@ -22600,6 +22775,13 @@ class AuroMenu extends AuroElement {
|
|
|
22600
22775
|
return;
|
|
22601
22776
|
}
|
|
22602
22777
|
|
|
22778
|
+
// A programmatic value set carries no positional intent, so drop any
|
|
22779
|
+
// `_selectedKey` left over from a prior user click. This makes reconciliation
|
|
22780
|
+
// in updated() fall back to first-by-value (single) / value-in-DOM-order
|
|
22781
|
+
// (multi), matching the documented contract for programmatic selection even
|
|
22782
|
+
// when a stale key would still resolve to a duplicate-value option.
|
|
22783
|
+
this._selectedKey = undefined;
|
|
22784
|
+
|
|
22603
22785
|
// `value` is a String property; stringify arrays so attribute reflection and `formattedValue` parsing stay correct.
|
|
22604
22786
|
this.value = Array.isArray(value) ? JSON.stringify(value) : value;
|
|
22605
22787
|
}
|
|
@@ -22647,6 +22829,17 @@ class AuroMenu extends AuroElement {
|
|
|
22647
22829
|
updated(changedProperties) {
|
|
22648
22830
|
super.updated(changedProperties);
|
|
22649
22831
|
|
|
22832
|
+
// Consume the selection-driven flag for THIS cycle up front. Clearing it
|
|
22833
|
+
// unconditionally — not only inside the `value` branch below — prevents it
|
|
22834
|
+
// from lingering `true` when a selection produces a serialized `value`
|
|
22835
|
+
// byte-identical to the current one, in which case Lit schedules no
|
|
22836
|
+
// `value`-change cycle to consume it. A lingering flag would misclassify a
|
|
22837
|
+
// later consumer's programmatic `value` set as selection-driven and keep a
|
|
22838
|
+
// stale `_selectedKey`. The reconcile path below re-sets the instance flag
|
|
22839
|
+
// after this point, so its intentional cross-cycle hand-off still works.
|
|
22840
|
+
const valueChangeFromSelection = this._valueChangeFromSelection;
|
|
22841
|
+
this._valueChangeFromSelection = false;
|
|
22842
|
+
|
|
22650
22843
|
// Single source of truth for 'auroMenu-selectedOption'. Selection handlers
|
|
22651
22844
|
// mutate optionSelected and let Lit's update cycle dispatch here; the prior
|
|
22652
22845
|
// .value comparison missed multi-select array changes and combined with the
|
|
@@ -22670,6 +22863,17 @@ class AuroMenu extends AuroElement {
|
|
|
22670
22863
|
this.initItems();
|
|
22671
22864
|
}
|
|
22672
22865
|
|
|
22866
|
+
// Distinguish a selection-driven `value` change (a user click, which set
|
|
22867
|
+
// the flag in handleSelectState / _sortSelectedByDomOrder) from a
|
|
22868
|
+
// programmatic assignment by a consumer. A programmatic set carries no
|
|
22869
|
+
// positional intent, so drop any leftover `_selectedKey` and let
|
|
22870
|
+
// reconciliation fall back to first-by-value (single) / value-in-DOM-order
|
|
22871
|
+
// (multi) — the same contract selectByValue() guarantees, even when a
|
|
22872
|
+
// stale key would otherwise still resolve to a duplicate-value option.
|
|
22873
|
+
if (!valueChangeFromSelection) {
|
|
22874
|
+
this._selectedKey = undefined;
|
|
22875
|
+
}
|
|
22876
|
+
|
|
22673
22877
|
// Set when reconciliation reassigns `value` below. That reassignment schedules a
|
|
22674
22878
|
// second updated() cycle, so the `event`-attribute dispatch is deferred to that
|
|
22675
22879
|
// cycle to avoid firing option custom events twice on the same selection.
|
|
@@ -22687,19 +22891,55 @@ class AuroMenu extends AuroElement {
|
|
|
22687
22891
|
// Defensive default: `formattedValue` can be undefined for unexpected value types,
|
|
22688
22892
|
// and calling `.includes` on undefined would throw during reconciliation.
|
|
22689
22893
|
const valueArray = this.formattedValue || [];
|
|
22690
|
-
|
|
22894
|
+
// Resolve by key first (the user's exact picks), then fall back to
|
|
22895
|
+
// value matching for any values not resolved by key — so pre-selection
|
|
22896
|
+
// and programmatic value sets keep working. Result is DOM-ordered.
|
|
22897
|
+
const matchingOptions = resolveSelectedOptions(this.items, valueArray, this._selectedKey);
|
|
22691
22898
|
newSelected = matchingOptions.length > 0 ? matchingOptions : undefined;
|
|
22692
22899
|
|
|
22693
|
-
// Reconcile `value` with the selectable set.
|
|
22694
|
-
// loaded but
|
|
22695
|
-
//
|
|
22696
|
-
//
|
|
22697
|
-
//
|
|
22698
|
-
|
|
22699
|
-
|
|
22700
|
-
|
|
22701
|
-
|
|
22702
|
-
|
|
22900
|
+
// Reconcile `value` with the selectable set. An occurrence is dropped
|
|
22901
|
+
// only when it is loaded but no selectable option can satisfy it —
|
|
22902
|
+
// every loaded item sharing that value is non-selectable, or the value
|
|
22903
|
+
// recurs more often than it has selectable options (a duplicate value
|
|
22904
|
+
// whose extra siblings are disabled/static). This is count-based, not
|
|
22905
|
+
// presence-based, so an enabled option is kept even when a disabled
|
|
22906
|
+
// sibling shares its value — mirroring how `resolveSelectedOptions`
|
|
22907
|
+
// resolves the same set. Entries with no matching item yet are
|
|
22908
|
+
// preserved so async preselection still works, and the toggle handlers
|
|
22909
|
+
// rebuild `value` from `formattedValue`, so a rejected entry cannot
|
|
22910
|
+
// resurface on the next select/deselect.
|
|
22911
|
+
const selectableByValue = new Map();
|
|
22912
|
+
const loadedValues = new Set();
|
|
22913
|
+
if (this.items) {
|
|
22914
|
+
this.items.forEach((item) => {
|
|
22915
|
+
loadedValues.add(item.value);
|
|
22916
|
+
if (isSelectableByValue(item)) {
|
|
22917
|
+
selectableByValue.set(item.value, (selectableByValue.get(item.value) || 0) + 1);
|
|
22918
|
+
}
|
|
22919
|
+
});
|
|
22920
|
+
}
|
|
22921
|
+
|
|
22922
|
+
const reconciled = valueArray.filter((val) => {
|
|
22923
|
+
// Not loaded yet (async preselection) — keep for a later cycle.
|
|
22924
|
+
if (!loadedValues.has(val)) {
|
|
22925
|
+
return true;
|
|
22926
|
+
}
|
|
22927
|
+
// Consume one selectable option per occurrence; drop once exhausted.
|
|
22928
|
+
const remaining = selectableByValue.get(val) || 0;
|
|
22929
|
+
if (remaining > 0) {
|
|
22930
|
+
selectableByValue.set(val, remaining - 1);
|
|
22931
|
+
return true;
|
|
22932
|
+
}
|
|
22933
|
+
return false;
|
|
22934
|
+
});
|
|
22935
|
+
|
|
22936
|
+
if (reconciled.length !== valueArray.length) {
|
|
22937
|
+
// This is an internal correction, not a consumer's programmatic set,
|
|
22938
|
+
// so preserve the selection-driven flag through the re-entrant
|
|
22939
|
+
// updated() cycle it schedules. Otherwise that cycle would treat the
|
|
22940
|
+
// reassignment as programmatic and drop `_selectedKey` mid-cascade,
|
|
22941
|
+
// flipping resolution and looping.
|
|
22942
|
+
this._valueChangeFromSelection = true;
|
|
22703
22943
|
this.value = serializeMultiSelectValue(reconciled);
|
|
22704
22944
|
valueReconciled = true;
|
|
22705
22945
|
}
|
|
@@ -22711,7 +22951,11 @@ class AuroMenu extends AuroElement {
|
|
|
22711
22951
|
// `hidden` is intentionally NOT excluded: the combobox toggles
|
|
22712
22952
|
// `hidden` as its type-ahead filter, so a filtered-out option is
|
|
22713
22953
|
// still a valid programmatic selection.
|
|
22714
|
-
|
|
22954
|
+
// Prefer the option the user actually selected (tracked by
|
|
22955
|
+
// `_selectedKey`) so a click on the second of two options sharing a
|
|
22956
|
+
// `value` resolves back to that exact element instead of the first
|
|
22957
|
+
// value match. Falls back to first-by-value for programmatic sets.
|
|
22958
|
+
const matchingOption = resolveSelectedOption(this.items, this.value, this._selectedKey);
|
|
22715
22959
|
|
|
22716
22960
|
if (matchingOption) {
|
|
22717
22961
|
newSelected = matchingOption;
|
|
@@ -22939,6 +23183,14 @@ class AuroMenu extends AuroElement {
|
|
|
22939
23183
|
}
|
|
22940
23184
|
});
|
|
22941
23185
|
|
|
23186
|
+
// Assign private keys once items are populated. Only the root menu assigns
|
|
23187
|
+
// keys: its `items` is a deep query that already includes nested submenu
|
|
23188
|
+
// options, so a single pass keys the entire tree. Nested menus skip this
|
|
23189
|
+
// and inherit keys from the root.
|
|
23190
|
+
if (this.rootMenu) {
|
|
23191
|
+
this._assignOptionKeys();
|
|
23192
|
+
}
|
|
23193
|
+
|
|
22942
23194
|
if (this.noCheckmark) {
|
|
22943
23195
|
this.updateItemsState(new Map([
|
|
22944
23196
|
[
|
|
@@ -22955,6 +23207,31 @@ class AuroMenu extends AuroElement {
|
|
|
22955
23207
|
}));
|
|
22956
23208
|
}
|
|
22957
23209
|
|
|
23210
|
+
/**
|
|
23211
|
+
* Assigns a private, auto-generated unique key (`_optionKey`) to each menu
|
|
23212
|
+
* option that does not already have one. Keys are internal state on the
|
|
23213
|
+
* element instance — never reflected as an attribute or exposed publicly —
|
|
23214
|
+
* and let selection tracking distinguish options that share the same `value`.
|
|
23215
|
+
*
|
|
23216
|
+
* The `_optionKey === undefined` guard makes this idempotent: options keep the
|
|
23217
|
+
* key they were first assigned across re-renders and slot changes, and if a
|
|
23218
|
+
* nested menu's lifecycle runs a pass before the root, options simply wait for
|
|
23219
|
+
* the root to key them (or keep whatever key they already hold).
|
|
23220
|
+
* @private
|
|
23221
|
+
*/
|
|
23222
|
+
_assignOptionKeys() {
|
|
23223
|
+
if (!this.items) {
|
|
23224
|
+
return;
|
|
23225
|
+
}
|
|
23226
|
+
|
|
23227
|
+
this.items.forEach((option) => {
|
|
23228
|
+
if (option._optionKey === undefined) {
|
|
23229
|
+
this._optionKeyCounter += 1;
|
|
23230
|
+
option._optionKey = `${this._menuInstanceId}-${this._optionKeyCounter}`;
|
|
23231
|
+
}
|
|
23232
|
+
});
|
|
23233
|
+
}
|
|
23234
|
+
|
|
22958
23235
|
// Logic Methods
|
|
22959
23236
|
|
|
22960
23237
|
/**
|
|
@@ -22964,24 +23241,28 @@ class AuroMenu extends AuroElement {
|
|
|
22964
23241
|
*/
|
|
22965
23242
|
handleSelectState(option) {
|
|
22966
23243
|
if (this.multiSelect) {
|
|
22967
|
-
const currentValue = this.formattedValue || [];
|
|
22968
23244
|
const currentSelected = this.optionSelected || [];
|
|
22969
23245
|
|
|
22970
|
-
if (!currentValue.includes(option.value)) {
|
|
22971
|
-
this.value = serializeMultiSelectValue([
|
|
22972
|
-
...currentValue,
|
|
22973
|
-
option.value
|
|
22974
|
-
]);
|
|
22975
|
-
}
|
|
22976
23246
|
if (!currentSelected.includes(option)) {
|
|
22977
23247
|
this.optionSelected = [
|
|
22978
23248
|
...currentSelected,
|
|
22979
23249
|
option
|
|
22980
23250
|
];
|
|
22981
23251
|
}
|
|
23252
|
+
|
|
23253
|
+
// Re-sort by DOM order and rebuild `_selectedKey`/`value` from the
|
|
23254
|
+
// selected set so display order stays consistent with the menu, not with
|
|
23255
|
+
// click order.
|
|
23256
|
+
this._sortSelectedByDomOrder();
|
|
22982
23257
|
} else {
|
|
22983
23258
|
this.value = option.value;
|
|
22984
23259
|
this.optionSelected = option;
|
|
23260
|
+
// Track the specific option the user selected so the value→option
|
|
23261
|
+
// reconciliation in updated() resolves back to this exact element even
|
|
23262
|
+
// when another option shares the same `value`.
|
|
23263
|
+
this._selectedKey = option._optionKey;
|
|
23264
|
+
// Mark this `value` change as selection-driven so updated() trusts the key.
|
|
23265
|
+
this._valueChangeFromSelection = true;
|
|
22985
23266
|
}
|
|
22986
23267
|
|
|
22987
23268
|
this._index = this.items.indexOf(option);
|
|
@@ -22994,18 +23275,22 @@ class AuroMenu extends AuroElement {
|
|
|
22994
23275
|
*/
|
|
22995
23276
|
handleDeselectState(option) {
|
|
22996
23277
|
if (this.multiSelect) {
|
|
22997
|
-
// Remove this
|
|
22998
|
-
|
|
22999
|
-
|
|
23000
|
-
|
|
23001
|
-
this.optionSelected = this.optionSelected.filter((val) => val !== option);
|
|
23278
|
+
// Remove this exact element from the selection (identity, not value — two
|
|
23279
|
+
// options can share a `value`), then rebuild `value`/`_selectedKey` from
|
|
23280
|
+
// the remaining set in DOM order. An empty result collapses to undefined.
|
|
23281
|
+
this.optionSelected = this.optionSelected.filter((selected) => selected !== option);
|
|
23002
23282
|
if (this.optionSelected.length === 0) {
|
|
23003
23283
|
this.optionSelected = undefined;
|
|
23284
|
+
this._selectedKey = undefined;
|
|
23285
|
+
this.value = undefined;
|
|
23286
|
+
} else {
|
|
23287
|
+
this._sortSelectedByDomOrder();
|
|
23004
23288
|
}
|
|
23005
23289
|
} else {
|
|
23006
23290
|
// For single-select: Back to undefined when deselected
|
|
23007
23291
|
this.value = undefined;
|
|
23008
23292
|
this.optionSelected = undefined;
|
|
23293
|
+
this._selectedKey = undefined;
|
|
23009
23294
|
}
|
|
23010
23295
|
|
|
23011
23296
|
// Update the index tracking
|
|
@@ -23029,9 +23314,46 @@ class AuroMenu extends AuroElement {
|
|
|
23029
23314
|
clearSelection() {
|
|
23030
23315
|
this.optionSelected = undefined;
|
|
23031
23316
|
this.value = undefined;
|
|
23317
|
+
this._selectedKey = undefined;
|
|
23032
23318
|
this._index = -1;
|
|
23033
23319
|
}
|
|
23034
23320
|
|
|
23321
|
+
/**
|
|
23322
|
+
* Re-sorts the multi-select selection into DOM order and rebuilds the derived
|
|
23323
|
+
* `_selectedKey` and `value` from `optionSelected`. Selection is always stored
|
|
23324
|
+
* and serialized in the order options appear in the menu, never in click
|
|
23325
|
+
* order — so selecting C then A yields `[A, C]`.
|
|
23326
|
+
* @private
|
|
23327
|
+
*/
|
|
23328
|
+
_sortSelectedByDomOrder() {
|
|
23329
|
+
if (!this.multiSelect || !Array.isArray(this.optionSelected) || !this.items) {
|
|
23330
|
+
return;
|
|
23331
|
+
}
|
|
23332
|
+
|
|
23333
|
+
const indexMap = new Map(this.items.map((item, index) => [
|
|
23334
|
+
item,
|
|
23335
|
+
index
|
|
23336
|
+
]));
|
|
23337
|
+
|
|
23338
|
+
// Sorting in place mutates `optionSelected` without a new array reference,
|
|
23339
|
+
// which Lit's `===` change-detection cannot see on its own — but that is
|
|
23340
|
+
// intentional and safe: both callers (handleSelectState / handleDeselectState)
|
|
23341
|
+
// assign a fresh `optionSelected` array immediately before calling, so Lit
|
|
23342
|
+
// already has a changed reference to react to, and the `value` write below
|
|
23343
|
+
// schedules the updated() cycle that re-derives `optionSelected` in DOM order
|
|
23344
|
+
// via resolveSelectedOptions. Do not "fix" this into a new-array assignment.
|
|
23345
|
+
//
|
|
23346
|
+
// Sort any element no longer in `items` (a stale selection left over from a
|
|
23347
|
+
// dynamic rebuild that the consumer has not cleared) to the END rather than
|
|
23348
|
+
// the front, so it never displaces a live option to the head of the
|
|
23349
|
+
// serialized order. Value reconciliation drops it on the next updated() cycle.
|
|
23350
|
+
this.optionSelected.sort((optionA, optionB) => (indexMap.get(optionA) ?? this.items.length) - (indexMap.get(optionB) ?? this.items.length));
|
|
23351
|
+
this._selectedKey = this.optionSelected.map((option) => option._optionKey);
|
|
23352
|
+
this.value = serializeMultiSelectValue(this.optionSelected.map((option) => option.value));
|
|
23353
|
+
// Mark this `value` change as selection-driven so updated() trusts the keys.
|
|
23354
|
+
this._valueChangeFromSelection = true;
|
|
23355
|
+
}
|
|
23356
|
+
|
|
23035
23357
|
/**
|
|
23036
23358
|
* Resets the menu to its initial state.
|
|
23037
23359
|
* This is the only way to return value to undefined.
|
|
@@ -23041,6 +23363,7 @@ class AuroMenu extends AuroElement {
|
|
|
23041
23363
|
// Reset to undefined - initial state
|
|
23042
23364
|
this.value = undefined;
|
|
23043
23365
|
this.optionSelected = undefined;
|
|
23366
|
+
this._selectedKey = undefined;
|
|
23044
23367
|
this._index = -1;
|
|
23045
23368
|
|
|
23046
23369
|
// Clear active option state so a follow-up open/navigation starts fresh
|
|
@@ -23102,6 +23425,21 @@ class AuroMenu extends AuroElement {
|
|
|
23102
23425
|
this.initItems();
|
|
23103
23426
|
}
|
|
23104
23427
|
|
|
23428
|
+
// Recover `_index` from the highlighted option when it has been reset to -1.
|
|
23429
|
+
// The updated() reconciliation resets `_index = -1` whenever the value
|
|
23430
|
+
// collapses to undefined while `optionActive` still points at the highlighted
|
|
23431
|
+
// option — e.g. deselecting the last remaining option in multi-select, or a
|
|
23432
|
+
// programmatic clearSelection() in single-select while keyboard focus is on an
|
|
23433
|
+
// option. Without this, reading `items[-1]` returns undefined and the re-select
|
|
23434
|
+
// no-ops until the highlight is moved away and back. Mirrors auro-combobox's
|
|
23435
|
+
// reconcileMenuIndex.
|
|
23436
|
+
if (this._index < 0 && this.optionActive && this.items) {
|
|
23437
|
+
const activeIndex = this.items.indexOf(this.optionActive);
|
|
23438
|
+
if (activeIndex >= 0) {
|
|
23439
|
+
this._index = activeIndex;
|
|
23440
|
+
}
|
|
23441
|
+
}
|
|
23442
|
+
|
|
23105
23443
|
// Get currently selected menu option based on index
|
|
23106
23444
|
const option = this.items ? this.items[this._index] : undefined;
|
|
23107
23445
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@aurodesignsystem/design-tokens@latest/dist/legacy/auro-classic/CSSCustomProperties.css"/>
|
|
26
26
|
|
|
27
27
|
<!-- Design Token Alaska Theme -->
|
|
28
|
-
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@aurodesignsystem/design-tokens@latest/dist/
|
|
28
|
+
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@aurodesignsystem/design-tokens@latest/dist/web/alaska.min.css"/>
|
|
29
29
|
|
|
30
30
|
<!-- Webcore Stylesheet Alaska Theme -->
|
|
31
31
|
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@aurodesignsystem/webcorestylesheets@latest/dist/bundled/themes/alaska.global.min.css" />
|