@aurodesignsystem-dev/auro-formkit 0.0.0-pr1576.8 → 0.0.0-pr1577.1
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/combobox/demo/customize.md +0 -40
- package/components/combobox/demo/customize.min.js +62 -365
- package/components/combobox/demo/getting-started.min.js +62 -365
- package/components/combobox/demo/index.min.js +62 -365
- package/components/combobox/dist/index.js +35 -53
- package/components/combobox/dist/registered.js +35 -53
- package/components/counter/demo/customize.min.js +9 -0
- package/components/counter/demo/index.min.js +9 -0
- package/components/counter/dist/index.js +9 -0
- package/components/counter/dist/registered.js +9 -0
- package/components/datepicker/demo/customize.min.js +9 -0
- package/components/datepicker/demo/index.min.js +9 -0
- package/components/datepicker/dist/index.js +9 -0
- package/components/datepicker/dist/registered.js +9 -0
- package/components/dropdown/demo/customize.min.js +9 -0
- package/components/dropdown/demo/getting-started.min.js +9 -0
- package/components/dropdown/demo/index.min.js +9 -0
- package/components/dropdown/dist/index.js +9 -0
- package/components/dropdown/dist/registered.js +9 -0
- package/components/form/demo/customize.min.js +89 -365
- package/components/form/demo/getting-started.min.js +89 -365
- package/components/form/demo/index.min.js +89 -365
- package/components/form/demo/registerDemoDeps.min.js +89 -365
- package/components/menu/demo/customize.md +0 -50
- package/components/menu/demo/index.min.js +27 -312
- package/components/menu/dist/auro-menu-utils.d.ts +0 -30
- package/components/menu/dist/auro-menu.d.ts +0 -23
- package/components/menu/dist/index.js +27 -312
- package/components/menu/dist/registered.js +27 -312
- package/components/select/demo/customize.md +0 -72
- package/components/select/demo/customize.min.js +36 -312
- package/components/select/demo/getting-started.min.js +36 -312
- package/components/select/demo/index.min.js +36 -312
- package/components/select/dist/index.js +9 -0
- package/components/select/dist/registered.js +9 -0
- package/custom-elements.json +1510 -1613
- package/package.json +1 -1
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
<auro-anchorlink fluid href="#multiselect" class="level2 body-xs">Multi-Select</auro-anchorlink>
|
|
24
24
|
<auro-anchorlink fluid href="#presetValue" class="level2 body-xs">Preset Value</auro-anchorlink>
|
|
25
25
|
<auro-anchorlink fluid href="#presetValueMultiselect" class="level2 body-xs">Preset Value (Multi)</auro-anchorlink>
|
|
26
|
-
<auro-anchorlink fluid href="#nonUniqueValues" class="level2 body-xs">Non-Unique Option Values</auro-anchorlink>
|
|
27
26
|
</auro-nav>
|
|
28
27
|
</nav>
|
|
29
28
|
<div class="mainContent">
|
|
@@ -864,55 +863,6 @@
|
|
|
864
863
|
</auro-menu></code></pre>
|
|
865
864
|
<!-- AURO-GENERATED-CONTENT:END -->
|
|
866
865
|
</auro-accordion>
|
|
867
|
-
<auro-header level="3" id="nonUniqueValues">Non-Unique Option Values</auro-header>
|
|
868
|
-
<p>Two or more <code>auro-menuoption</code> elements may share the same <code>value</code>. This is common when the <code>value</code> represents a coarser grouping than the option label — for example, several airports that all serve the same city. The menu tracks the specific option a user selects rather than resolving by <code>value</code> alone, so selecting one of several options that share a <code>value</code> highlights exactly the option that was chosen.</p>
|
|
869
|
-
<div class="exampleWrapper">
|
|
870
|
-
<!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/duplicate-values.html) -->
|
|
871
|
-
<!-- The below content is automatically added from ./../apiExamples/duplicate-values.html -->
|
|
872
|
-
<auro-menu>
|
|
873
|
-
<auro-menuoption value="seattle">Seattle–Tacoma International (SEA)</auro-menuoption>
|
|
874
|
-
<auro-menuoption value="seattle">Seattle Paine Field (PAE)</auro-menuoption>
|
|
875
|
-
<auro-menuoption value="portland">Portland International (PDX)</auro-menuoption>
|
|
876
|
-
<auro-menuoption value="spokane">Spokane International (GEG)</auro-menuoption>
|
|
877
|
-
</auro-menu>
|
|
878
|
-
<!-- AURO-GENERATED-CONTENT:END -->
|
|
879
|
-
</div>
|
|
880
|
-
<auro-accordion alignRight>
|
|
881
|
-
<span slot="trigger">See code</span>
|
|
882
|
-
<!-- AURO-GENERATED-CONTENT:START (CODE:src=./../apiExamples/duplicate-values.html) -->
|
|
883
|
-
<!-- The below code snippet is automatically added from ./../apiExamples/duplicate-values.html -->
|
|
884
|
-
<pre class="language-html"><code class="language-html"><auro-menu>
|
|
885
|
-
<auro-menuoption value="seattle">Seattle&ndash;Tacoma International (SEA)</auro-menuoption>
|
|
886
|
-
<auro-menuoption value="seattle">Seattle Paine Field (PAE)</auro-menuoption>
|
|
887
|
-
<auro-menuoption value="portland">Portland International (PDX)</auro-menuoption>
|
|
888
|
-
<auro-menuoption value="spokane">Spokane International (GEG)</auro-menuoption>
|
|
889
|
-
</auro-menu></code></pre>
|
|
890
|
-
<!-- AURO-GENERATED-CONTENT:END -->
|
|
891
|
-
</auro-accordion>
|
|
892
|
-
<p>In <code>multiselect</code> mode, options that share a <code>value</code> are tracked independently, so each can be selected and removed on its own. Selections are stored in DOM order regardless of the order in which they were chosen.</p>
|
|
893
|
-
<div class="exampleWrapper">
|
|
894
|
-
<!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/duplicate-values-multiselect.html) -->
|
|
895
|
-
<!-- The below content is automatically added from ./../apiExamples/duplicate-values-multiselect.html -->
|
|
896
|
-
<auro-menu multiselect>
|
|
897
|
-
<auro-menuoption value="seattle">Seattle–Tacoma International (SEA)</auro-menuoption>
|
|
898
|
-
<auro-menuoption value="seattle">Seattle Paine Field (PAE)</auro-menuoption>
|
|
899
|
-
<auro-menuoption value="portland">Portland International (PDX)</auro-menuoption>
|
|
900
|
-
<auro-menuoption value="spokane">Spokane International (GEG)</auro-menuoption>
|
|
901
|
-
</auro-menu>
|
|
902
|
-
<!-- AURO-GENERATED-CONTENT:END -->
|
|
903
|
-
</div>
|
|
904
|
-
<auro-accordion alignRight>
|
|
905
|
-
<span slot="trigger">See code</span>
|
|
906
|
-
<!-- AURO-GENERATED-CONTENT:START (CODE:src=./../apiExamples/duplicate-values-multiselect.html) -->
|
|
907
|
-
<!-- The below code snippet is automatically added from ./../apiExamples/duplicate-values-multiselect.html -->
|
|
908
|
-
<pre class="language-html"><code class="language-html"><auro-menu multiselect>
|
|
909
|
-
<auro-menuoption value="seattle">Seattle&ndash;Tacoma International (SEA)</auro-menuoption>
|
|
910
|
-
<auro-menuoption value="seattle">Seattle Paine Field (PAE)</auro-menuoption>
|
|
911
|
-
<auro-menuoption value="portland">Portland International (PDX)</auro-menuoption>
|
|
912
|
-
<auro-menuoption value="spokane">Spokane International (GEG)</auro-menuoption>
|
|
913
|
-
</auro-menu></code></pre>
|
|
914
|
-
<!-- AURO-GENERATED-CONTENT:END -->
|
|
915
|
-
</auro-accordion>
|
|
916
866
|
</section>
|
|
917
867
|
</div>
|
|
918
868
|
</div>
|
|
@@ -259,116 +259,6 @@ function isSelectableByValue(option) {
|
|
|
259
259
|
!option.hasAttribute('static');
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
/* eslint-disable no-underscore-dangle */
|
|
263
|
-
/**
|
|
264
|
-
* Resolves the single selected option for a given `value`, preferring the
|
|
265
|
-
* option tracked by `selectedKey` (a user-initiated selection) over a
|
|
266
|
-
* first-by-value match. When multiple options share the same `value`, matching
|
|
267
|
-
* by `value` alone cannot distinguish which one the user picked; the key
|
|
268
|
-
* disambiguates it.
|
|
269
|
-
*
|
|
270
|
-
* The key is trusted only when it still resolves to an option whose `value`
|
|
271
|
-
* matches the requested `value`. If the key is stale (option removed) or the
|
|
272
|
-
* value was changed programmatically, resolution falls back to value matching —
|
|
273
|
-
* preserving backward-compatible behavior for preselection and `selectByValue`.
|
|
274
|
-
* @private
|
|
275
|
-
* @param {Array<HTMLElement>} items - The menu's flat option list.
|
|
276
|
-
* @param {string} value - The value to resolve.
|
|
277
|
-
* @param {string|undefined} selectedKey - The `_optionKey` of the user-selected option, if any.
|
|
278
|
-
* @returns {HTMLElement|undefined} The resolved option, or undefined when none match.
|
|
279
|
-
*/
|
|
280
|
-
function resolveSelectedOption(items, value, selectedKey) {
|
|
281
|
-
if (!items) {
|
|
282
|
-
return undefined;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (selectedKey !== undefined) {
|
|
286
|
-
const keyed = items.find((item) => item._optionKey === selectedKey);
|
|
287
|
-
if (keyed && isSelectableByValue(keyed) && keyed.value === value) {
|
|
288
|
-
return keyed;
|
|
289
|
-
}
|
|
290
|
-
// Key exists but the option is gone or its value no longer matches — fall
|
|
291
|
-
// through to value-based matching.
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return items.find((item) => isSelectableByValue(item) && item.value === value);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Resolves the selected options for a multi-select `value` array, preferring
|
|
299
|
-
* options tracked by `selectedKeys` (user-initiated selections) and falling
|
|
300
|
-
* back to value matching for any values not resolved by key. The result is
|
|
301
|
-
* always sorted into DOM order regardless of selection sequence.
|
|
302
|
-
* @private
|
|
303
|
-
* @param {Array<HTMLElement>} items - The menu's flat option list.
|
|
304
|
-
* @param {Array<string>} valueArray - The selected values.
|
|
305
|
-
* @param {Array<string>|undefined} selectedKeys - The `_optionKey`s of the user-selected options, if any.
|
|
306
|
-
* @returns {Array<HTMLElement>} The resolved options in DOM order.
|
|
307
|
-
*/
|
|
308
|
-
function resolveSelectedOptions(items, valueArray, selectedKeys) {
|
|
309
|
-
if (!items) {
|
|
310
|
-
return [];
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const resolved = [];
|
|
314
|
-
// Mirror `resolved` as a Set for O(1) membership checks below, matching the
|
|
315
|
-
// indexMap optimization used for the sort rather than scanning `resolved`
|
|
316
|
-
// on every candidate.
|
|
317
|
-
const resolvedSet = new Set();
|
|
318
|
-
|
|
319
|
-
// Track how many of each value are still available to resolve. A value that
|
|
320
|
-
// appears N times in `valueArray` may be satisfied at most N times total across
|
|
321
|
-
// the key pass and the value fallback below — matching by count, not presence,
|
|
322
|
-
// on BOTH passes. This stops a duplicate value from being over-resolved: e.g.
|
|
323
|
-
// two keyed options that both carry `SEA` cannot both match a single requested
|
|
324
|
-
// `SEA` (which happens when `value` is set directly without clearing
|
|
325
|
-
// `_selectedKey`, so more keys survive than the value set now asks for).
|
|
326
|
-
const remaining = new Map();
|
|
327
|
-
valueArray.forEach((val) => remaining.set(val, (remaining.get(val) || 0) + 1));
|
|
328
|
-
|
|
329
|
-
// Resolve by key first: trust a key only when its option is still selectable
|
|
330
|
-
// and there is still an unmatched occurrence of its value in the request set.
|
|
331
|
-
if (Array.isArray(selectedKeys)) {
|
|
332
|
-
selectedKeys.forEach((key) => {
|
|
333
|
-
const keyed = items.find((item) => item._optionKey === key);
|
|
334
|
-
if (keyed && isSelectableByValue(keyed) && (remaining.get(keyed.value) || 0) > 0 && !resolvedSet.has(keyed)) {
|
|
335
|
-
resolved.push(keyed);
|
|
336
|
-
resolvedSet.add(keyed);
|
|
337
|
-
remaining.set(keyed.value, remaining.get(keyed.value) - 1);
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Fall back to value matching for the occurrences not resolved by key. Iterate
|
|
343
|
-
// the leftover per-value counts so a value that appears twice but was only
|
|
344
|
-
// resolved once by key still matches its remaining occurrence(s).
|
|
345
|
-
remaining.forEach((count, val) => {
|
|
346
|
-
for (let occurrence = 0; occurrence < count; occurrence += 1) {
|
|
347
|
-
const option = items.find((item) => isSelectableByValue(item) && item.value === val && !resolvedSet.has(item));
|
|
348
|
-
if (option) {
|
|
349
|
-
resolved.push(option);
|
|
350
|
-
resolvedSet.add(option);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
// Always return in DOM order so display is consistent regardless of the order
|
|
356
|
-
// keys/values were selected. Every resolved option came from `items`, so an
|
|
357
|
-
// O(1) index lookup mirrors `_sortSelectedByDomOrder` and avoids the O(n)
|
|
358
|
-
// `items.indexOf` per comparison for large combobox option sets. Any element
|
|
359
|
-
// not in `items` (a stale snapshot from a future caller) sorts to the END via
|
|
360
|
-
// `?? items.length`, matching `_sortSelectedByDomOrder` and avoiding NaN
|
|
361
|
-
// comparisons.
|
|
362
|
-
const indexMap = new Map(items.map((item, index) => [
|
|
363
|
-
item,
|
|
364
|
-
index
|
|
365
|
-
]));
|
|
366
|
-
resolved.sort((optionA, optionB) => (indexMap.get(optionA) ?? items.length) - (indexMap.get(optionB) ?? items.length));
|
|
367
|
-
|
|
368
|
-
return resolved;
|
|
369
|
-
}
|
|
370
|
-
/* eslint-enable no-underscore-dangle */
|
|
371
|
-
|
|
372
262
|
/**
|
|
373
263
|
* Helper method to dispatch custom events.
|
|
374
264
|
* @param {HTMLElement} element - Element to dispatch event from.
|
|
@@ -407,14 +297,6 @@ const t={ATTRIBUTE:1},e$1=t=>(...e)=>({_$litDirective$:t,values:e});let i$1 = cl
|
|
|
407
297
|
// See LICENSE in the project root for license information.
|
|
408
298
|
|
|
409
299
|
|
|
410
|
-
/**
|
|
411
|
-
* Monotonically increasing counter used to give each menu instance a unique
|
|
412
|
-
* `_menuInstanceId` prefix. Auto-generating the id (rather than using a random
|
|
413
|
-
* string) keeps option keys deterministic and collision-free across menus.
|
|
414
|
-
* @private
|
|
415
|
-
*/
|
|
416
|
-
let menuInstanceIdCounter = 0;
|
|
417
|
-
|
|
418
300
|
|
|
419
301
|
/**
|
|
420
302
|
* The `auro-menu` element provides users a way to select from a list of options.
|
|
@@ -486,8 +368,9 @@ class AuroMenu extends AuroElement {
|
|
|
486
368
|
|
|
487
369
|
// Instance properties (non-reactive)
|
|
488
370
|
|
|
489
|
-
|
|
490
|
-
|
|
371
|
+
/**
|
|
372
|
+
* @private
|
|
373
|
+
*/
|
|
491
374
|
Object.assign(this, {
|
|
492
375
|
// Root-level menu (true) or a nested submenu (false)
|
|
493
376
|
rootMenu: true,
|
|
@@ -497,21 +380,6 @@ class AuroMenu extends AuroElement {
|
|
|
497
380
|
nestingSpacer: '<span class="nestingSpacer"></span>',
|
|
498
381
|
// Loading indicator for slot elements
|
|
499
382
|
loadingSlots: null,
|
|
500
|
-
// Unique id for this menu instance; prefixes every auto-generated option
|
|
501
|
-
// key so keys never collide across menus in the same document.
|
|
502
|
-
_menuInstanceId: `menu-${menuInstanceIdCounter}`,
|
|
503
|
-
// Monotonically increasing counter for option key generation. Never
|
|
504
|
-
// resets, so a key is never reused within this instance's lifetime.
|
|
505
|
-
_optionKeyCounter: 0,
|
|
506
|
-
// Key(s) of the option(s) the user has actively selected. A single string
|
|
507
|
-
// in single-select, an array in multi-select, undefined when nothing is
|
|
508
|
-
// user-selected. Used to disambiguate options that share a `value`.
|
|
509
|
-
_selectedKey: undefined,
|
|
510
|
-
// True only for the one updated() cycle following a user selection, so
|
|
511
|
-
// reconciliation trusts `_selectedKey`. A `value` change from a consumer's
|
|
512
|
-
// direct property assignment leaves this false, dropping the stale key so
|
|
513
|
-
// reconciliation falls back to first-by-value (see updated()).
|
|
514
|
-
_valueChangeFromSelection: false,
|
|
515
383
|
});
|
|
516
384
|
}
|
|
517
385
|
|
|
@@ -731,13 +599,6 @@ class AuroMenu extends AuroElement {
|
|
|
731
599
|
return;
|
|
732
600
|
}
|
|
733
601
|
|
|
734
|
-
// A programmatic value set carries no positional intent, so drop any
|
|
735
|
-
// `_selectedKey` left over from a prior user click. This makes reconciliation
|
|
736
|
-
// in updated() fall back to first-by-value (single) / value-in-DOM-order
|
|
737
|
-
// (multi), matching the documented contract for programmatic selection even
|
|
738
|
-
// when a stale key would still resolve to a duplicate-value option.
|
|
739
|
-
this._selectedKey = undefined;
|
|
740
|
-
|
|
741
602
|
// `value` is a String property; stringify arrays so attribute reflection and `formattedValue` parsing stay correct.
|
|
742
603
|
this.value = Array.isArray(value) ? JSON.stringify(value) : value;
|
|
743
604
|
}
|
|
@@ -785,17 +646,6 @@ class AuroMenu extends AuroElement {
|
|
|
785
646
|
updated(changedProperties) {
|
|
786
647
|
super.updated(changedProperties);
|
|
787
648
|
|
|
788
|
-
// Consume the selection-driven flag for THIS cycle up front. Clearing it
|
|
789
|
-
// unconditionally — not only inside the `value` branch below — prevents it
|
|
790
|
-
// from lingering `true` when a selection produces a serialized `value`
|
|
791
|
-
// byte-identical to the current one, in which case Lit schedules no
|
|
792
|
-
// `value`-change cycle to consume it. A lingering flag would misclassify a
|
|
793
|
-
// later consumer's programmatic `value` set as selection-driven and keep a
|
|
794
|
-
// stale `_selectedKey`. The reconcile path below re-sets the instance flag
|
|
795
|
-
// after this point, so its intentional cross-cycle hand-off still works.
|
|
796
|
-
const valueChangeFromSelection = this._valueChangeFromSelection;
|
|
797
|
-
this._valueChangeFromSelection = false;
|
|
798
|
-
|
|
799
649
|
// Single source of truth for 'auroMenu-selectedOption'. Selection handlers
|
|
800
650
|
// mutate optionSelected and let Lit's update cycle dispatch here; the prior
|
|
801
651
|
// .value comparison missed multi-select array changes and combined with the
|
|
@@ -819,17 +669,6 @@ class AuroMenu extends AuroElement {
|
|
|
819
669
|
this.initItems();
|
|
820
670
|
}
|
|
821
671
|
|
|
822
|
-
// Distinguish a selection-driven `value` change (a user click, which set
|
|
823
|
-
// the flag in handleSelectState / _sortSelectedByDomOrder) from a
|
|
824
|
-
// programmatic assignment by a consumer. A programmatic set carries no
|
|
825
|
-
// positional intent, so drop any leftover `_selectedKey` and let
|
|
826
|
-
// reconciliation fall back to first-by-value (single) / value-in-DOM-order
|
|
827
|
-
// (multi) — the same contract selectByValue() guarantees, even when a
|
|
828
|
-
// stale key would otherwise still resolve to a duplicate-value option.
|
|
829
|
-
if (!valueChangeFromSelection) {
|
|
830
|
-
this._selectedKey = undefined;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
672
|
// Set when reconciliation reassigns `value` below. That reassignment schedules a
|
|
834
673
|
// second updated() cycle, so the `event`-attribute dispatch is deferred to that
|
|
835
674
|
// cycle to avoid firing option custom events twice on the same selection.
|
|
@@ -847,55 +686,19 @@ class AuroMenu extends AuroElement {
|
|
|
847
686
|
// Defensive default: `formattedValue` can be undefined for unexpected value types,
|
|
848
687
|
// and calling `.includes` on undefined would throw during reconciliation.
|
|
849
688
|
const valueArray = this.formattedValue || [];
|
|
850
|
-
|
|
851
|
-
// value matching for any values not resolved by key — so pre-selection
|
|
852
|
-
// and programmatic value sets keep working. Result is DOM-ordered.
|
|
853
|
-
const matchingOptions = resolveSelectedOptions(this.items, valueArray, this._selectedKey);
|
|
689
|
+
const matchingOptions = this.items ? this.items.filter((item) => isSelectableByValue(item) && valueArray.includes(item.value)) : [];
|
|
854
690
|
newSelected = matchingOptions.length > 0 ? matchingOptions : undefined;
|
|
855
691
|
|
|
856
|
-
// Reconcile `value` with the selectable set.
|
|
857
|
-
//
|
|
858
|
-
//
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
// resurface on the next select/deselect.
|
|
867
|
-
const selectableByValue = new Map();
|
|
868
|
-
const loadedValues = new Set();
|
|
869
|
-
if (this.items) {
|
|
870
|
-
this.items.forEach((item) => {
|
|
871
|
-
loadedValues.add(item.value);
|
|
872
|
-
if (isSelectableByValue(item)) {
|
|
873
|
-
selectableByValue.set(item.value, (selectableByValue.get(item.value) || 0) + 1);
|
|
874
|
-
}
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
const reconciled = valueArray.filter((val) => {
|
|
879
|
-
// Not loaded yet (async preselection) — keep for a later cycle.
|
|
880
|
-
if (!loadedValues.has(val)) {
|
|
881
|
-
return true;
|
|
882
|
-
}
|
|
883
|
-
// Consume one selectable option per occurrence; drop once exhausted.
|
|
884
|
-
const remaining = selectableByValue.get(val) || 0;
|
|
885
|
-
if (remaining > 0) {
|
|
886
|
-
selectableByValue.set(val, remaining - 1);
|
|
887
|
-
return true;
|
|
888
|
-
}
|
|
889
|
-
return false;
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
if (reconciled.length !== valueArray.length) {
|
|
893
|
-
// This is an internal correction, not a consumer's programmatic set,
|
|
894
|
-
// so preserve the selection-driven flag through the re-entrant
|
|
895
|
-
// updated() cycle it schedules. Otherwise that cycle would treat the
|
|
896
|
-
// reassignment as programmatic and drop `_selectedKey` mid-cascade,
|
|
897
|
-
// flipping resolution and looping.
|
|
898
|
-
this._valueChangeFromSelection = true;
|
|
692
|
+
// Reconcile `value` with the selectable set. Drop only entries whose option is
|
|
693
|
+
// loaded but non-selectable (disabled/static) — leaving them would desync `value`
|
|
694
|
+
// from `optionSelected`, and the toggle handlers rebuild `value` from `formattedValue`,
|
|
695
|
+
// so the rejected entry would resurface on the next select/deselect. Entries with no
|
|
696
|
+
// matching item yet are preserved so async preselection still works once options render.
|
|
697
|
+
const rejectedValues = this.items
|
|
698
|
+
? this.items.filter((item) => !isSelectableByValue(item) && valueArray.includes(item.value)).map((item) => item.value)
|
|
699
|
+
: [];
|
|
700
|
+
if (rejectedValues.length > 0) {
|
|
701
|
+
const reconciled = valueArray.filter((val) => !rejectedValues.includes(val));
|
|
899
702
|
this.value = serializeMultiSelectValue(reconciled);
|
|
900
703
|
valueReconciled = true;
|
|
901
704
|
}
|
|
@@ -907,11 +710,7 @@ class AuroMenu extends AuroElement {
|
|
|
907
710
|
// `hidden` is intentionally NOT excluded: the combobox toggles
|
|
908
711
|
// `hidden` as its type-ahead filter, so a filtered-out option is
|
|
909
712
|
// still a valid programmatic selection.
|
|
910
|
-
|
|
911
|
-
// `_selectedKey`) so a click on the second of two options sharing a
|
|
912
|
-
// `value` resolves back to that exact element instead of the first
|
|
913
|
-
// value match. Falls back to first-by-value for programmatic sets.
|
|
914
|
-
const matchingOption = resolveSelectedOption(this.items, this.value, this._selectedKey);
|
|
713
|
+
const matchingOption = this.items ? this.items.find((item) => isSelectableByValue(item) && item.value === this.value) : undefined;
|
|
915
714
|
|
|
916
715
|
if (matchingOption) {
|
|
917
716
|
newSelected = matchingOption;
|
|
@@ -1139,14 +938,6 @@ class AuroMenu extends AuroElement {
|
|
|
1139
938
|
}
|
|
1140
939
|
});
|
|
1141
940
|
|
|
1142
|
-
// Assign private keys once items are populated. Only the root menu assigns
|
|
1143
|
-
// keys: its `items` is a deep query that already includes nested submenu
|
|
1144
|
-
// options, so a single pass keys the entire tree. Nested menus skip this
|
|
1145
|
-
// and inherit keys from the root.
|
|
1146
|
-
if (this.rootMenu) {
|
|
1147
|
-
this._assignOptionKeys();
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
941
|
if (this.noCheckmark) {
|
|
1151
942
|
this.updateItemsState(new Map([
|
|
1152
943
|
[
|
|
@@ -1163,31 +954,6 @@ class AuroMenu extends AuroElement {
|
|
|
1163
954
|
}));
|
|
1164
955
|
}
|
|
1165
956
|
|
|
1166
|
-
/**
|
|
1167
|
-
* Assigns a private, auto-generated unique key (`_optionKey`) to each menu
|
|
1168
|
-
* option that does not already have one. Keys are internal state on the
|
|
1169
|
-
* element instance — never reflected as an attribute or exposed publicly —
|
|
1170
|
-
* and let selection tracking distinguish options that share the same `value`.
|
|
1171
|
-
*
|
|
1172
|
-
* The `_optionKey === undefined` guard makes this idempotent: options keep the
|
|
1173
|
-
* key they were first assigned across re-renders and slot changes, and if a
|
|
1174
|
-
* nested menu's lifecycle runs a pass before the root, options simply wait for
|
|
1175
|
-
* the root to key them (or keep whatever key they already hold).
|
|
1176
|
-
* @private
|
|
1177
|
-
*/
|
|
1178
|
-
_assignOptionKeys() {
|
|
1179
|
-
if (!this.items) {
|
|
1180
|
-
return;
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
this.items.forEach((option) => {
|
|
1184
|
-
if (option._optionKey === undefined) {
|
|
1185
|
-
this._optionKeyCounter += 1;
|
|
1186
|
-
option._optionKey = `${this._menuInstanceId}-${this._optionKeyCounter}`;
|
|
1187
|
-
}
|
|
1188
|
-
});
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
957
|
// Logic Methods
|
|
1192
958
|
|
|
1193
959
|
/**
|
|
@@ -1197,28 +963,24 @@ class AuroMenu extends AuroElement {
|
|
|
1197
963
|
*/
|
|
1198
964
|
handleSelectState(option) {
|
|
1199
965
|
if (this.multiSelect) {
|
|
966
|
+
const currentValue = this.formattedValue || [];
|
|
1200
967
|
const currentSelected = this.optionSelected || [];
|
|
1201
968
|
|
|
969
|
+
if (!currentValue.includes(option.value)) {
|
|
970
|
+
this.value = serializeMultiSelectValue([
|
|
971
|
+
...currentValue,
|
|
972
|
+
option.value
|
|
973
|
+
]);
|
|
974
|
+
}
|
|
1202
975
|
if (!currentSelected.includes(option)) {
|
|
1203
976
|
this.optionSelected = [
|
|
1204
977
|
...currentSelected,
|
|
1205
978
|
option
|
|
1206
979
|
];
|
|
1207
980
|
}
|
|
1208
|
-
|
|
1209
|
-
// Re-sort by DOM order and rebuild `_selectedKey`/`value` from the
|
|
1210
|
-
// selected set so display order stays consistent with the menu, not with
|
|
1211
|
-
// click order.
|
|
1212
|
-
this._sortSelectedByDomOrder();
|
|
1213
981
|
} else {
|
|
1214
982
|
this.value = option.value;
|
|
1215
983
|
this.optionSelected = option;
|
|
1216
|
-
// Track the specific option the user selected so the value→option
|
|
1217
|
-
// reconciliation in updated() resolves back to this exact element even
|
|
1218
|
-
// when another option shares the same `value`.
|
|
1219
|
-
this._selectedKey = option._optionKey;
|
|
1220
|
-
// Mark this `value` change as selection-driven so updated() trusts the key.
|
|
1221
|
-
this._valueChangeFromSelection = true;
|
|
1222
984
|
}
|
|
1223
985
|
|
|
1224
986
|
this._index = this.items.indexOf(option);
|
|
@@ -1231,22 +993,18 @@ class AuroMenu extends AuroElement {
|
|
|
1231
993
|
*/
|
|
1232
994
|
handleDeselectState(option) {
|
|
1233
995
|
if (this.multiSelect) {
|
|
1234
|
-
// Remove this
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
996
|
+
// Remove this option from array; an empty result collapses `value` to undefined.
|
|
997
|
+
const newFormattedValue = (this.formattedValue || []).filter((val) => val !== option.value);
|
|
998
|
+
this.value = serializeMultiSelectValue(newFormattedValue);
|
|
999
|
+
|
|
1000
|
+
this.optionSelected = this.optionSelected.filter((val) => val !== option);
|
|
1238
1001
|
if (this.optionSelected.length === 0) {
|
|
1239
1002
|
this.optionSelected = undefined;
|
|
1240
|
-
this._selectedKey = undefined;
|
|
1241
|
-
this.value = undefined;
|
|
1242
|
-
} else {
|
|
1243
|
-
this._sortSelectedByDomOrder();
|
|
1244
1003
|
}
|
|
1245
1004
|
} else {
|
|
1246
1005
|
// For single-select: Back to undefined when deselected
|
|
1247
1006
|
this.value = undefined;
|
|
1248
1007
|
this.optionSelected = undefined;
|
|
1249
|
-
this._selectedKey = undefined;
|
|
1250
1008
|
}
|
|
1251
1009
|
|
|
1252
1010
|
// Update the index tracking
|
|
@@ -1270,38 +1028,9 @@ class AuroMenu extends AuroElement {
|
|
|
1270
1028
|
clearSelection() {
|
|
1271
1029
|
this.optionSelected = undefined;
|
|
1272
1030
|
this.value = undefined;
|
|
1273
|
-
this._selectedKey = undefined;
|
|
1274
1031
|
this._index = -1;
|
|
1275
1032
|
}
|
|
1276
1033
|
|
|
1277
|
-
/**
|
|
1278
|
-
* Re-sorts the multi-select selection into DOM order and rebuilds the derived
|
|
1279
|
-
* `_selectedKey` and `value` from `optionSelected`. Selection is always stored
|
|
1280
|
-
* and serialized in the order options appear in the menu, never in click
|
|
1281
|
-
* order — so selecting C then A yields `[A, C]`.
|
|
1282
|
-
* @private
|
|
1283
|
-
*/
|
|
1284
|
-
_sortSelectedByDomOrder() {
|
|
1285
|
-
if (!this.multiSelect || !Array.isArray(this.optionSelected) || !this.items) {
|
|
1286
|
-
return;
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
const indexMap = new Map(this.items.map((item, index) => [
|
|
1290
|
-
item,
|
|
1291
|
-
index
|
|
1292
|
-
]));
|
|
1293
|
-
|
|
1294
|
-
// Sort any element no longer in `items` (a stale selection left over from a
|
|
1295
|
-
// dynamic rebuild that the consumer has not cleared) to the END rather than
|
|
1296
|
-
// the front, so it never displaces a live option to the head of the
|
|
1297
|
-
// serialized order. Value reconciliation drops it on the next updated() cycle.
|
|
1298
|
-
this.optionSelected.sort((optionA, optionB) => (indexMap.get(optionA) ?? this.items.length) - (indexMap.get(optionB) ?? this.items.length));
|
|
1299
|
-
this._selectedKey = this.optionSelected.map((option) => option._optionKey);
|
|
1300
|
-
this.value = serializeMultiSelectValue(this.optionSelected.map((option) => option.value));
|
|
1301
|
-
// Mark this `value` change as selection-driven so updated() trusts the keys.
|
|
1302
|
-
this._valueChangeFromSelection = true;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
1034
|
/**
|
|
1306
1035
|
* Resets the menu to its initial state.
|
|
1307
1036
|
* This is the only way to return value to undefined.
|
|
@@ -1311,7 +1040,6 @@ class AuroMenu extends AuroElement {
|
|
|
1311
1040
|
// Reset to undefined - initial state
|
|
1312
1041
|
this.value = undefined;
|
|
1313
1042
|
this.optionSelected = undefined;
|
|
1314
|
-
this._selectedKey = undefined;
|
|
1315
1043
|
this._index = -1;
|
|
1316
1044
|
|
|
1317
1045
|
// Clear active option state so a follow-up open/navigation starts fresh
|
|
@@ -1373,19 +1101,6 @@ class AuroMenu extends AuroElement {
|
|
|
1373
1101
|
this.initItems();
|
|
1374
1102
|
}
|
|
1375
1103
|
|
|
1376
|
-
// Recover `_index` from the highlighted option when it has been reset to -1.
|
|
1377
|
-
// In multi-select, deselecting the last remaining option collapses the value
|
|
1378
|
-
// to undefined, and the updated() reconciliation resets `_index = -1` even
|
|
1379
|
-
// though `optionActive` still points at the highlighted option. Without this,
|
|
1380
|
-
// reading `items[-1]` returns undefined and the re-select no-ops until the
|
|
1381
|
-
// highlight is moved away and back. Mirrors auro-combobox's reconcileMenuIndex.
|
|
1382
|
-
if (this._index < 0 && this.optionActive && this.items) {
|
|
1383
|
-
const activeIndex = this.items.indexOf(this.optionActive);
|
|
1384
|
-
if (activeIndex >= 0) {
|
|
1385
|
-
this._index = activeIndex;
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
1104
|
// Get currently selected menu option based on index
|
|
1390
1105
|
const option = this.items ? this.items[this._index] : undefined;
|
|
1391
1106
|
|
|
@@ -44,36 +44,6 @@ export function isOptionInteractive(option: HTMLElement): boolean;
|
|
|
44
44
|
* @returns {boolean} True if option can be selected by value.
|
|
45
45
|
*/
|
|
46
46
|
export function isSelectableByValue(option: HTMLElement): boolean;
|
|
47
|
-
/**
|
|
48
|
-
* Resolves the single selected option for a given `value`, preferring the
|
|
49
|
-
* option tracked by `selectedKey` (a user-initiated selection) over a
|
|
50
|
-
* first-by-value match. When multiple options share the same `value`, matching
|
|
51
|
-
* by `value` alone cannot distinguish which one the user picked; the key
|
|
52
|
-
* disambiguates it.
|
|
53
|
-
*
|
|
54
|
-
* The key is trusted only when it still resolves to an option whose `value`
|
|
55
|
-
* matches the requested `value`. If the key is stale (option removed) or the
|
|
56
|
-
* value was changed programmatically, resolution falls back to value matching —
|
|
57
|
-
* preserving backward-compatible behavior for preselection and `selectByValue`.
|
|
58
|
-
* @private
|
|
59
|
-
* @param {Array<HTMLElement>} items - The menu's flat option list.
|
|
60
|
-
* @param {string} value - The value to resolve.
|
|
61
|
-
* @param {string|undefined} selectedKey - The `_optionKey` of the user-selected option, if any.
|
|
62
|
-
* @returns {HTMLElement|undefined} The resolved option, or undefined when none match.
|
|
63
|
-
*/
|
|
64
|
-
export function resolveSelectedOption(items: Array<HTMLElement>, value: string, selectedKey: string | undefined): HTMLElement | undefined;
|
|
65
|
-
/**
|
|
66
|
-
* Resolves the selected options for a multi-select `value` array, preferring
|
|
67
|
-
* options tracked by `selectedKeys` (user-initiated selections) and falling
|
|
68
|
-
* back to value matching for any values not resolved by key. The result is
|
|
69
|
-
* always sorted into DOM order regardless of selection sequence.
|
|
70
|
-
* @private
|
|
71
|
-
* @param {Array<HTMLElement>} items - The menu's flat option list.
|
|
72
|
-
* @param {Array<string>} valueArray - The selected values.
|
|
73
|
-
* @param {Array<string>|undefined} selectedKeys - The `_optionKey`s of the user-selected options, if any.
|
|
74
|
-
* @returns {Array<HTMLElement>} The resolved options in DOM order.
|
|
75
|
-
*/
|
|
76
|
-
export function resolveSelectedOptions(items: Array<HTMLElement>, valueArray: Array<string>, selectedKeys: Array<string> | undefined): Array<HTMLElement>;
|
|
77
47
|
/**
|
|
78
48
|
* Helper method to dispatch custom events.
|
|
79
49
|
* @param {HTMLElement} element - Element to dispatch event from.
|
|
@@ -172,7 +172,6 @@ export class AuroMenu extends AuroElement {
|
|
|
172
172
|
* @public
|
|
173
173
|
*/
|
|
174
174
|
public selectByValue(value: string | string[] | undefined | null): void;
|
|
175
|
-
_selectedKey: any;
|
|
176
175
|
firstUpdated(): void;
|
|
177
176
|
loadingSlots: NodeListOf<Element> | undefined;
|
|
178
177
|
/**
|
|
@@ -182,7 +181,6 @@ export class AuroMenu extends AuroElement {
|
|
|
182
181
|
*/
|
|
183
182
|
private setTagAttribute;
|
|
184
183
|
updated(changedProperties: any): void;
|
|
185
|
-
_valueChangeFromSelection: boolean | undefined;
|
|
186
184
|
_index: number | undefined;
|
|
187
185
|
/**
|
|
188
186
|
* Updates the UI state and appearance of menu items based on changed properties.
|
|
@@ -201,19 +199,6 @@ export class AuroMenu extends AuroElement {
|
|
|
201
199
|
*/
|
|
202
200
|
private initItems;
|
|
203
201
|
items: Element[] | undefined;
|
|
204
|
-
/**
|
|
205
|
-
* Assigns a private, auto-generated unique key (`_optionKey`) to each menu
|
|
206
|
-
* option that does not already have one. Keys are internal state on the
|
|
207
|
-
* element instance — never reflected as an attribute or exposed publicly —
|
|
208
|
-
* and let selection tracking distinguish options that share the same `value`.
|
|
209
|
-
*
|
|
210
|
-
* The `_optionKey === undefined` guard makes this idempotent: options keep the
|
|
211
|
-
* key they were first assigned across re-renders and slot changes, and if a
|
|
212
|
-
* nested menu's lifecycle runs a pass before the root, options simply wait for
|
|
213
|
-
* the root to key them (or keep whatever key they already hold).
|
|
214
|
-
* @private
|
|
215
|
-
*/
|
|
216
|
-
private _assignOptionKeys;
|
|
217
202
|
/**
|
|
218
203
|
* Updates menu state when an option is selected.
|
|
219
204
|
* @private
|
|
@@ -231,14 +216,6 @@ export class AuroMenu extends AuroElement {
|
|
|
231
216
|
* @private
|
|
232
217
|
*/
|
|
233
218
|
private clearSelection;
|
|
234
|
-
/**
|
|
235
|
-
* Re-sorts the multi-select selection into DOM order and rebuilds the derived
|
|
236
|
-
* `_selectedKey` and `value` from `optionSelected`. Selection is always stored
|
|
237
|
-
* and serialized in the order options appear in the menu, never in click
|
|
238
|
-
* order — so selecting C then A yields `[A, C]`.
|
|
239
|
-
* @private
|
|
240
|
-
*/
|
|
241
|
-
private _sortSelectedByDomOrder;
|
|
242
219
|
/**
|
|
243
220
|
* Resets the menu to its initial state.
|
|
244
221
|
* This is the only way to return value to undefined.
|