@aurodesignsystem-dev/auro-formkit 0.0.0-pr1574.0 → 0.0.0-pr1576.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.
Files changed (51) hide show
  1. package/components/checkbox/demo/customize.min.js +1 -1
  2. package/components/checkbox/demo/getting-started.min.js +1 -1
  3. package/components/checkbox/demo/index.min.js +1 -1
  4. package/components/checkbox/dist/index.js +1 -1
  5. package/components/checkbox/dist/registered.js +1 -1
  6. package/components/combobox/demo/customize.md +40 -0
  7. package/components/combobox/demo/customize.min.js +256 -17
  8. package/components/combobox/demo/getting-started.min.js +256 -17
  9. package/components/combobox/demo/index.min.js +256 -17
  10. package/components/combobox/dist/index.js +3 -3
  11. package/components/combobox/dist/registered.js +3 -3
  12. package/components/counter/demo/customize.min.js +3 -3
  13. package/components/counter/demo/index.min.js +3 -3
  14. package/components/counter/dist/index.js +3 -3
  15. package/components/counter/dist/registered.js +3 -3
  16. package/components/datepicker/demo/customize.min.js +3 -3
  17. package/components/datepicker/demo/index.min.js +3 -3
  18. package/components/datepicker/dist/index.js +3 -3
  19. package/components/datepicker/dist/registered.js +3 -3
  20. package/components/dropdown/demo/customize.min.js +1 -1
  21. package/components/dropdown/demo/getting-started.min.js +1 -1
  22. package/components/dropdown/demo/index.min.js +1 -1
  23. package/components/dropdown/dist/index.js +1 -1
  24. package/components/dropdown/dist/registered.js +1 -1
  25. package/components/form/demo/customize.min.js +267 -28
  26. package/components/form/demo/getting-started.min.js +267 -28
  27. package/components/form/demo/index.min.js +267 -28
  28. package/components/form/demo/registerDemoDeps.min.js +267 -28
  29. package/components/input/demo/customize.min.js +1 -1
  30. package/components/input/demo/getting-started.min.js +1 -1
  31. package/components/input/demo/index.min.js +1 -1
  32. package/components/input/dist/index.js +1 -1
  33. package/components/input/dist/registered.js +1 -1
  34. package/components/menu/demo/index.min.js +253 -14
  35. package/components/menu/dist/auro-menu-utils.d.ts +30 -0
  36. package/components/menu/dist/auro-menu.d.ts +23 -0
  37. package/components/menu/dist/index.js +253 -14
  38. package/components/menu/dist/registered.js +253 -14
  39. package/components/radio/demo/customize.min.js +1 -1
  40. package/components/radio/demo/getting-started.min.js +1 -1
  41. package/components/radio/demo/index.min.js +1 -1
  42. package/components/radio/dist/index.js +1 -1
  43. package/components/radio/dist/registered.js +1 -1
  44. package/components/select/demo/customize.md +72 -0
  45. package/components/select/demo/customize.min.js +255 -16
  46. package/components/select/demo/getting-started.min.js +255 -16
  47. package/components/select/demo/index.min.js +255 -16
  48. package/components/select/dist/index.js +2 -2
  49. package/components/select/dist/registered.js +2 -2
  50. package/custom-elements.json +1617 -1513
  51. package/package.json +1 -1
@@ -238,6 +238,107 @@ function isSelectableByValue(option) {
238
238
  !option.hasAttribute('static');
239
239
  }
240
240
 
241
+ /* eslint-disable no-underscore-dangle */
242
+ /**
243
+ * Resolves the single selected option for a given `value`, preferring the
244
+ * option tracked by `selectedKey` (a user-initiated selection) over a
245
+ * first-by-value match. When multiple options share the same `value`, matching
246
+ * by `value` alone cannot distinguish which one the user picked; the key
247
+ * disambiguates it.
248
+ *
249
+ * The key is trusted only when it still resolves to an option whose `value`
250
+ * matches the requested `value`. If the key is stale (option removed) or the
251
+ * value was changed programmatically, resolution falls back to value matching —
252
+ * preserving backward-compatible behavior for preselection and `selectByValue`.
253
+ * @private
254
+ * @param {Array<HTMLElement>} items - The menu's flat option list.
255
+ * @param {string} value - The value to resolve.
256
+ * @param {string|undefined} selectedKey - The `_optionKey` of the user-selected option, if any.
257
+ * @returns {HTMLElement|undefined} The resolved option, or undefined when none match.
258
+ */
259
+ function resolveSelectedOption(items, value, selectedKey) {
260
+ if (!items) {
261
+ return undefined;
262
+ }
263
+
264
+ if (selectedKey !== undefined) {
265
+ const keyed = items.find((item) => item._optionKey === selectedKey);
266
+ if (keyed && isSelectableByValue(keyed) && keyed.value === value) {
267
+ return keyed;
268
+ }
269
+ // Key exists but the option is gone or its value no longer matches — fall
270
+ // through to value-based matching.
271
+ }
272
+
273
+ return items.find((item) => isSelectableByValue(item) && item.value === value);
274
+ }
275
+
276
+ /**
277
+ * Resolves the selected options for a multi-select `value` array, preferring
278
+ * options tracked by `selectedKeys` (user-initiated selections) and falling
279
+ * back to value matching for any values not resolved by key. The result is
280
+ * always sorted into DOM order regardless of selection sequence.
281
+ * @private
282
+ * @param {Array<HTMLElement>} items - The menu's flat option list.
283
+ * @param {Array<string>} valueArray - The selected values.
284
+ * @param {Array<string>|undefined} selectedKeys - The `_optionKey`s of the user-selected options, if any.
285
+ * @returns {Array<HTMLElement>} The resolved options in DOM order.
286
+ */
287
+ function resolveSelectedOptions(items, valueArray, selectedKeys) {
288
+ if (!items) {
289
+ return [];
290
+ }
291
+
292
+ const resolved = [];
293
+
294
+ // Track how many of each value are still available to resolve. A value that
295
+ // appears N times in `valueArray` may be satisfied at most N times total across
296
+ // the key pass and the value fallback below — matching by count, not presence,
297
+ // on BOTH passes. This stops a duplicate value from being over-resolved: e.g.
298
+ // two keyed options that both carry `SEA` cannot both match a single requested
299
+ // `SEA` (which happens when `value` is set directly without clearing
300
+ // `_selectedKey`, so more keys survive than the value set now asks for).
301
+ const remaining = new Map();
302
+ valueArray.forEach((val) => remaining.set(val, (remaining.get(val) || 0) + 1));
303
+
304
+ // Resolve by key first: trust a key only when its option is still selectable
305
+ // and there is still an unmatched occurrence of its value in the request set.
306
+ if (Array.isArray(selectedKeys)) {
307
+ selectedKeys.forEach((key) => {
308
+ const keyed = items.find((item) => item._optionKey === key);
309
+ if (keyed && isSelectableByValue(keyed) && (remaining.get(keyed.value) || 0) > 0 && !resolved.includes(keyed)) {
310
+ resolved.push(keyed);
311
+ remaining.set(keyed.value, remaining.get(keyed.value) - 1);
312
+ }
313
+ });
314
+ }
315
+
316
+ // Fall back to value matching for the occurrences not resolved by key. Iterate
317
+ // the leftover per-value counts so a value that appears twice but was only
318
+ // resolved once by key still matches its remaining occurrence(s).
319
+ remaining.forEach((count, val) => {
320
+ for (let occurrence = 0; occurrence < count; occurrence += 1) {
321
+ const option = items.find((item) => isSelectableByValue(item) && item.value === val && !resolved.includes(item));
322
+ if (option) {
323
+ resolved.push(option);
324
+ }
325
+ }
326
+ });
327
+
328
+ // Always return in DOM order so display is consistent regardless of the order
329
+ // keys/values were selected. Every resolved option came from `items`, so an
330
+ // O(1) index lookup mirrors `_sortSelectedByDomOrder` and avoids the O(n)
331
+ // `items.indexOf` per comparison for large combobox option sets.
332
+ const indexMap = new Map(items.map((item, index) => [
333
+ item,
334
+ index
335
+ ]));
336
+ resolved.sort((optionA, optionB) => indexMap.get(optionA) - indexMap.get(optionB));
337
+
338
+ return resolved;
339
+ }
340
+ /* eslint-enable no-underscore-dangle */
341
+
241
342
  /**
242
343
  * Helper method to dispatch custom events.
243
344
  * @param {HTMLElement} element - Element to dispatch event from.
@@ -263,6 +364,14 @@ function dispatchMenuEvent(element, eventName, detail = null) {
263
364
  // See LICENSE in the project root for license information.
264
365
 
265
366
 
367
+ /**
368
+ * Monotonically increasing counter used to give each menu instance a unique
369
+ * `_menuInstanceId` prefix. Auto-generating the id (rather than using a random
370
+ * string) keeps option keys deterministic and collision-free across menus.
371
+ * @private
372
+ */
373
+ let menuInstanceIdCounter = 0;
374
+
266
375
 
267
376
  /**
268
377
  * The `auro-menu` element provides users a way to select from a list of options.
@@ -337,6 +446,8 @@ class AuroMenu extends AuroElement {
337
446
  /**
338
447
  * @private
339
448
  */
449
+ menuInstanceIdCounter += 1;
450
+
340
451
  Object.assign(this, {
341
452
  // Root-level menu (true) or a nested submenu (false)
342
453
  rootMenu: true,
@@ -346,6 +457,21 @@ class AuroMenu extends AuroElement {
346
457
  nestingSpacer: '<span class="nestingSpacer"></span>',
347
458
  // Loading indicator for slot elements
348
459
  loadingSlots: null,
460
+ // Unique id for this menu instance; prefixes every auto-generated option
461
+ // key so keys never collide across menus in the same document.
462
+ _menuInstanceId: `menu-${menuInstanceIdCounter}`,
463
+ // Monotonically increasing counter for option key generation. Never
464
+ // resets, so a key is never reused within this instance's lifetime.
465
+ _optionKeyCounter: 0,
466
+ // Key(s) of the option(s) the user has actively selected. A single string
467
+ // in single-select, an array in multi-select, undefined when nothing is
468
+ // user-selected. Used to disambiguate options that share a `value`.
469
+ _selectedKey: undefined,
470
+ // True only for the one updated() cycle following a user selection, so
471
+ // reconciliation trusts `_selectedKey`. A `value` change from a consumer's
472
+ // direct property assignment leaves this false, dropping the stale key so
473
+ // reconciliation falls back to first-by-value (see updated()).
474
+ _valueChangeFromSelection: false,
349
475
  });
350
476
  }
351
477
 
@@ -565,6 +691,13 @@ class AuroMenu extends AuroElement {
565
691
  return;
566
692
  }
567
693
 
694
+ // A programmatic value set carries no positional intent, so drop any
695
+ // `_selectedKey` left over from a prior user click. This makes reconciliation
696
+ // in updated() fall back to first-by-value (single) / value-in-DOM-order
697
+ // (multi), matching the documented contract for programmatic selection even
698
+ // when a stale key would still resolve to a duplicate-value option.
699
+ this._selectedKey = undefined;
700
+
568
701
  // `value` is a String property; stringify arrays so attribute reflection and `formattedValue` parsing stay correct.
569
702
  this.value = Array.isArray(value) ? JSON.stringify(value) : value;
570
703
  }
@@ -612,6 +745,17 @@ class AuroMenu extends AuroElement {
612
745
  updated(changedProperties) {
613
746
  super.updated(changedProperties);
614
747
 
748
+ // Consume the selection-driven flag for THIS cycle up front. Clearing it
749
+ // unconditionally — not only inside the `value` branch below — prevents it
750
+ // from lingering `true` when a selection produces a serialized `value`
751
+ // byte-identical to the current one, in which case Lit schedules no
752
+ // `value`-change cycle to consume it. A lingering flag would misclassify a
753
+ // later consumer's programmatic `value` set as selection-driven and keep a
754
+ // stale `_selectedKey`. The reconcile path below re-sets the instance flag
755
+ // after this point, so its intentional cross-cycle hand-off still works.
756
+ const valueChangeFromSelection = this._valueChangeFromSelection;
757
+ this._valueChangeFromSelection = false;
758
+
615
759
  // Single source of truth for 'auroMenu-selectedOption'. Selection handlers
616
760
  // mutate optionSelected and let Lit's update cycle dispatch here; the prior
617
761
  // .value comparison missed multi-select array changes and combined with the
@@ -635,6 +779,17 @@ class AuroMenu extends AuroElement {
635
779
  this.initItems();
636
780
  }
637
781
 
782
+ // Distinguish a selection-driven `value` change (a user click, which set
783
+ // the flag in handleSelectState / _sortSelectedByDomOrder) from a
784
+ // programmatic assignment by a consumer. A programmatic set carries no
785
+ // positional intent, so drop any leftover `_selectedKey` and let
786
+ // reconciliation fall back to first-by-value (single) / value-in-DOM-order
787
+ // (multi) — the same contract selectByValue() guarantees, even when a
788
+ // stale key would otherwise still resolve to a duplicate-value option.
789
+ if (!valueChangeFromSelection) {
790
+ this._selectedKey = undefined;
791
+ }
792
+
638
793
  // Set when reconciliation reassigns `value` below. That reassignment schedules a
639
794
  // second updated() cycle, so the `event`-attribute dispatch is deferred to that
640
795
  // cycle to avoid firing option custom events twice on the same selection.
@@ -652,7 +807,10 @@ class AuroMenu extends AuroElement {
652
807
  // Defensive default: `formattedValue` can be undefined for unexpected value types,
653
808
  // and calling `.includes` on undefined would throw during reconciliation.
654
809
  const valueArray = this.formattedValue || [];
655
- const matchingOptions = this.items ? this.items.filter((item) => isSelectableByValue(item) && valueArray.includes(item.value)) : [];
810
+ // Resolve by key first (the user's exact picks), then fall back to
811
+ // value matching for any values not resolved by key — so pre-selection
812
+ // and programmatic value sets keep working. Result is DOM-ordered.
813
+ const matchingOptions = resolveSelectedOptions(this.items, valueArray, this._selectedKey);
656
814
  newSelected = matchingOptions.length > 0 ? matchingOptions : undefined;
657
815
 
658
816
  // Reconcile `value` with the selectable set. Drop only entries whose option is
@@ -665,6 +823,12 @@ class AuroMenu extends AuroElement {
665
823
  : [];
666
824
  if (rejectedValues.length > 0) {
667
825
  const reconciled = valueArray.filter((val) => !rejectedValues.includes(val));
826
+ // This is an internal correction, not a consumer's programmatic set,
827
+ // so preserve the selection-driven flag through the re-entrant
828
+ // updated() cycle it schedules. Otherwise that cycle would treat the
829
+ // reassignment as programmatic and drop `_selectedKey` mid-cascade,
830
+ // flipping resolution and looping.
831
+ this._valueChangeFromSelection = true;
668
832
  this.value = serializeMultiSelectValue(reconciled);
669
833
  valueReconciled = true;
670
834
  }
@@ -676,7 +840,11 @@ class AuroMenu extends AuroElement {
676
840
  // `hidden` is intentionally NOT excluded: the combobox toggles
677
841
  // `hidden` as its type-ahead filter, so a filtered-out option is
678
842
  // still a valid programmatic selection.
679
- const matchingOption = this.items ? this.items.find((item) => isSelectableByValue(item) && item.value === this.value) : undefined;
843
+ // Prefer the option the user actually selected (tracked by
844
+ // `_selectedKey`) so a click on the second of two options sharing a
845
+ // `value` resolves back to that exact element instead of the first
846
+ // value match. Falls back to first-by-value for programmatic sets.
847
+ const matchingOption = resolveSelectedOption(this.items, this.value, this._selectedKey);
680
848
 
681
849
  if (matchingOption) {
682
850
  newSelected = matchingOption;
@@ -904,6 +1072,14 @@ class AuroMenu extends AuroElement {
904
1072
  }
905
1073
  });
906
1074
 
1075
+ // Assign private keys once items are populated. Only the root menu assigns
1076
+ // keys: its `items` is a deep query that already includes nested submenu
1077
+ // options, so a single pass keys the entire tree. Nested menus skip this
1078
+ // and inherit keys from the root.
1079
+ if (this.rootMenu) {
1080
+ this._assignOptionKeys();
1081
+ }
1082
+
907
1083
  if (this.noCheckmark) {
908
1084
  this.updateItemsState(new Map([
909
1085
  [
@@ -920,6 +1096,31 @@ class AuroMenu extends AuroElement {
920
1096
  }));
921
1097
  }
922
1098
 
1099
+ /**
1100
+ * Assigns a private, auto-generated unique key (`_optionKey`) to each menu
1101
+ * option that does not already have one. Keys are internal state on the
1102
+ * element instance — never reflected as an attribute or exposed publicly —
1103
+ * and let selection tracking distinguish options that share the same `value`.
1104
+ *
1105
+ * The `_optionKey === undefined` guard makes this idempotent: options keep the
1106
+ * key they were first assigned across re-renders and slot changes, and if a
1107
+ * nested menu's lifecycle runs a pass before the root, options simply wait for
1108
+ * the root to key them (or keep whatever key they already hold).
1109
+ * @private
1110
+ */
1111
+ _assignOptionKeys() {
1112
+ if (!this.items) {
1113
+ return;
1114
+ }
1115
+
1116
+ this.items.forEach((option) => {
1117
+ if (option._optionKey === undefined) {
1118
+ this._optionKeyCounter += 1;
1119
+ option._optionKey = `${this._menuInstanceId}-${this._optionKeyCounter}`;
1120
+ }
1121
+ });
1122
+ }
1123
+
923
1124
  // Logic Methods
924
1125
 
925
1126
  /**
@@ -929,24 +1130,28 @@ class AuroMenu extends AuroElement {
929
1130
  */
930
1131
  handleSelectState(option) {
931
1132
  if (this.multiSelect) {
932
- const currentValue = this.formattedValue || [];
933
1133
  const currentSelected = this.optionSelected || [];
934
1134
 
935
- if (!currentValue.includes(option.value)) {
936
- this.value = serializeMultiSelectValue([
937
- ...currentValue,
938
- option.value
939
- ]);
940
- }
941
1135
  if (!currentSelected.includes(option)) {
942
1136
  this.optionSelected = [
943
1137
  ...currentSelected,
944
1138
  option
945
1139
  ];
946
1140
  }
1141
+
1142
+ // Re-sort by DOM order and rebuild `_selectedKey`/`value` from the
1143
+ // selected set so display order stays consistent with the menu, not with
1144
+ // click order.
1145
+ this._sortSelectedByDomOrder();
947
1146
  } else {
948
1147
  this.value = option.value;
949
1148
  this.optionSelected = option;
1149
+ // Track the specific option the user selected so the value→option
1150
+ // reconciliation in updated() resolves back to this exact element even
1151
+ // when another option shares the same `value`.
1152
+ this._selectedKey = option._optionKey;
1153
+ // Mark this `value` change as selection-driven so updated() trusts the key.
1154
+ this._valueChangeFromSelection = true;
950
1155
  }
951
1156
 
952
1157
  this._index = this.items.indexOf(option);
@@ -959,18 +1164,22 @@ class AuroMenu extends AuroElement {
959
1164
  */
960
1165
  handleDeselectState(option) {
961
1166
  if (this.multiSelect) {
962
- // Remove this option from array; an empty result collapses `value` to undefined.
963
- const newFormattedValue = (this.formattedValue || []).filter((val) => val !== option.value);
964
- this.value = serializeMultiSelectValue(newFormattedValue);
965
-
966
- this.optionSelected = this.optionSelected.filter((val) => val !== option);
1167
+ // Remove this exact element from the selection (identity, not value — two
1168
+ // options can share a `value`), then rebuild `value`/`_selectedKey` from
1169
+ // the remaining set in DOM order. An empty result collapses to undefined.
1170
+ this.optionSelected = this.optionSelected.filter((selected) => selected !== option);
967
1171
  if (this.optionSelected.length === 0) {
968
1172
  this.optionSelected = undefined;
1173
+ this._selectedKey = undefined;
1174
+ this.value = undefined;
1175
+ } else {
1176
+ this._sortSelectedByDomOrder();
969
1177
  }
970
1178
  } else {
971
1179
  // For single-select: Back to undefined when deselected
972
1180
  this.value = undefined;
973
1181
  this.optionSelected = undefined;
1182
+ this._selectedKey = undefined;
974
1183
  }
975
1184
 
976
1185
  // Update the index tracking
@@ -994,9 +1203,38 @@ class AuroMenu extends AuroElement {
994
1203
  clearSelection() {
995
1204
  this.optionSelected = undefined;
996
1205
  this.value = undefined;
1206
+ this._selectedKey = undefined;
997
1207
  this._index = -1;
998
1208
  }
999
1209
 
1210
+ /**
1211
+ * Re-sorts the multi-select selection into DOM order and rebuilds the derived
1212
+ * `_selectedKey` and `value` from `optionSelected`. Selection is always stored
1213
+ * and serialized in the order options appear in the menu, never in click
1214
+ * order — so selecting C then A yields `[A, C]`.
1215
+ * @private
1216
+ */
1217
+ _sortSelectedByDomOrder() {
1218
+ if (!this.multiSelect || !Array.isArray(this.optionSelected) || !this.items) {
1219
+ return;
1220
+ }
1221
+
1222
+ const indexMap = new Map(this.items.map((item, index) => [
1223
+ item,
1224
+ index
1225
+ ]));
1226
+
1227
+ // Sort any element no longer in `items` (a stale selection left over from a
1228
+ // dynamic rebuild that the consumer has not cleared) to the END rather than
1229
+ // the front, so it never displaces a live option to the head of the
1230
+ // serialized order. Value reconciliation drops it on the next updated() cycle.
1231
+ this.optionSelected.sort((optionA, optionB) => (indexMap.get(optionA) ?? this.items.length) - (indexMap.get(optionB) ?? this.items.length));
1232
+ this._selectedKey = this.optionSelected.map((option) => option._optionKey);
1233
+ this.value = serializeMultiSelectValue(this.optionSelected.map((option) => option.value));
1234
+ // Mark this `value` change as selection-driven so updated() trusts the keys.
1235
+ this._valueChangeFromSelection = true;
1236
+ }
1237
+
1000
1238
  /**
1001
1239
  * Resets the menu to its initial state.
1002
1240
  * This is the only way to return value to undefined.
@@ -1006,6 +1244,7 @@ class AuroMenu extends AuroElement {
1006
1244
  // Reset to undefined - initial state
1007
1245
  this.value = undefined;
1008
1246
  this.optionSelected = undefined;
1247
+ this._selectedKey = undefined;
1009
1248
  this._index = -1;
1010
1249
 
1011
1250
  // Clear active option state so a follow-up open/navigation starts fresh
@@ -1227,7 +1227,7 @@ class AuroHelpText extends i$2 {
1227
1227
  }
1228
1228
  }
1229
1229
 
1230
- var formkitVersion = '202607272111';
1230
+ var formkitVersion = '202608040326';
1231
1231
 
1232
1232
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1233
1233
  // See LICENSE in the project root for license information.
@@ -1227,7 +1227,7 @@ class AuroHelpText extends i$2 {
1227
1227
  }
1228
1228
  }
1229
1229
 
1230
- var formkitVersion = '202607272111';
1230
+ var formkitVersion = '202608040326';
1231
1231
 
1232
1232
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1233
1233
  // See LICENSE in the project root for license information.
@@ -1227,7 +1227,7 @@ class AuroHelpText extends i$2 {
1227
1227
  }
1228
1228
  }
1229
1229
 
1230
- var formkitVersion = '202607272111';
1230
+ var formkitVersion = '202608040326';
1231
1231
 
1232
1232
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1233
1233
  // See LICENSE in the project root for license information.
@@ -1166,7 +1166,7 @@ class AuroHelpText extends LitElement {
1166
1166
  }
1167
1167
  }
1168
1168
 
1169
- var formkitVersion = '202607272111';
1169
+ var formkitVersion = '202608040326';
1170
1170
 
1171
1171
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1172
1172
  // See LICENSE in the project root for license information.
@@ -1166,7 +1166,7 @@ class AuroHelpText extends LitElement {
1166
1166
  }
1167
1167
  }
1168
1168
 
1169
- var formkitVersion = '202607272111';
1169
+ var formkitVersion = '202608040326';
1170
1170
 
1171
1171
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
1172
1172
  // See LICENSE in the project root for license information.
@@ -33,6 +33,7 @@
33
33
  <auro-anchorlink fluid href="#noValidate" class="level2 body-xs">No Validation</auro-anchorlink>
34
34
  <auro-anchorlink fluid href="#placeholder" class="level2 body-xs">Placeholder</auro-anchorlink>
35
35
  <auro-anchorlink fluid href="#loading" class="level2 body-xs">Loading</auro-anchorlink>
36
+ <auro-anchorlink fluid href="#nonUniqueValues" class="level2 body-xs">Non-Unique Option Values</auro-anchorlink>
36
37
  </auro-nav>
37
38
  </nav>
38
39
  <div class="mainContent">
@@ -1297,6 +1298,77 @@
1297
1298
  &lt;/auro-select&gt;</code></pre>
1298
1299
  <!-- AURO-GENERATED-CONTENT:END -->
1299
1300
  </auro-accordion>
1301
+ <auro-header level="3" id="nonUniqueValues">Non-Unique Option Values</auro-header>
1302
+ <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 &mdash; for example, several airports that all serve the same city. The component tracks the specific option a user selects, so the correct label is displayed even when the underlying <code>value</code> is duplicated.</p>
1303
+ <div class="exampleWrapper">
1304
+ <!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/duplicate-values.html) -->
1305
+ <!-- The below content is automatically added from ./../apiExamples/duplicate-values.html -->
1306
+ <auro-select>
1307
+ <span slot="ariaLabel.bib.close">Close Popup</span>
1308
+ <span slot="bib.fullscreen.headline">Choose an airport</span>
1309
+ <span slot="label">Departure airport</span>
1310
+ <auro-menu>
1311
+ <auro-menuoption value="seattle">Seattle&ndash;Tacoma International (SEA)</auro-menuoption>
1312
+ <auro-menuoption value="seattle">Seattle Paine Field (PAE)</auro-menuoption>
1313
+ <auro-menuoption value="portland">Portland International (PDX)</auro-menuoption>
1314
+ <auro-menuoption value="spokane">Spokane International (GEG)</auro-menuoption>
1315
+ </auro-menu>
1316
+ </auro-select>
1317
+ <!-- AURO-GENERATED-CONTENT:END -->
1318
+ </div>
1319
+ <auro-accordion alignRight>
1320
+ <span slot="trigger">See code</span>
1321
+ <!-- AURO-GENERATED-CONTENT:START (CODE:src=./../apiExamples/duplicate-values.html) -->
1322
+ <!-- The below code snippet is automatically added from ./../apiExamples/duplicate-values.html -->
1323
+ <pre class="language-html"><code class="language-html">&lt;auro-select&gt;
1324
+ &lt;span slot="ariaLabel.bib.close"&gt;Close Popup&lt;/span&gt;
1325
+ &lt;span slot="bib.fullscreen.headline"&gt;Choose an airport&lt;/span&gt;
1326
+ &lt;span slot="label"&gt;Departure airport&lt;/span&gt;
1327
+ &lt;auro-menu&gt;
1328
+ &lt;auro-menuoption value="seattle"&gt;Seattle&amp;ndash;Tacoma International (SEA)&lt;/auro-menuoption&gt;
1329
+ &lt;auro-menuoption value="seattle"&gt;Seattle Paine Field (PAE)&lt;/auro-menuoption&gt;
1330
+ &lt;auro-menuoption value="portland"&gt;Portland International (PDX)&lt;/auro-menuoption&gt;
1331
+ &lt;auro-menuoption value="spokane"&gt;Spokane International (GEG)&lt;/auro-menuoption&gt;
1332
+ &lt;/auro-menu&gt;
1333
+ &lt;/auro-select&gt;</code></pre>
1334
+ <!-- AURO-GENERATED-CONTENT:END -->
1335
+ </auro-accordion>
1336
+ <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>
1337
+ <div class="exampleWrapper">
1338
+ <!-- AURO-GENERATED-CONTENT:START (FILE:src=./../apiExamples/duplicate-values-multiselect.html) -->
1339
+ <!-- The below content is automatically added from ./../apiExamples/duplicate-values-multiselect.html -->
1340
+ <auro-select multiselect>
1341
+ <span slot="ariaLabel.bib.close">Close Popup</span>
1342
+ <span slot="bib.fullscreen.headline">Choose airports</span>
1343
+ <label slot="placeholder">Select one or more airports</label>
1344
+ <span slot="label">Airports served</span>
1345
+ <auro-menu>
1346
+ <auro-menuoption value="seattle">Seattle&ndash;Tacoma International (SEA)</auro-menuoption>
1347
+ <auro-menuoption value="seattle">Seattle Paine Field (PAE)</auro-menuoption>
1348
+ <auro-menuoption value="portland">Portland International (PDX)</auro-menuoption>
1349
+ <auro-menuoption value="spokane">Spokane International (GEG)</auro-menuoption>
1350
+ </auro-menu>
1351
+ </auro-select>
1352
+ <!-- AURO-GENERATED-CONTENT:END -->
1353
+ </div>
1354
+ <auro-accordion alignRight>
1355
+ <span slot="trigger">See code</span>
1356
+ <!-- AURO-GENERATED-CONTENT:START (CODE:src=./../apiExamples/duplicate-values-multiselect.html) -->
1357
+ <!-- The below code snippet is automatically added from ./../apiExamples/duplicate-values-multiselect.html -->
1358
+ <pre class="language-html"><code class="language-html">&lt;auro-select multiselect&gt;
1359
+ &lt;span slot="ariaLabel.bib.close"&gt;Close Popup&lt;/span&gt;
1360
+ &lt;span slot="bib.fullscreen.headline"&gt;Choose airports&lt;/span&gt;
1361
+ &lt;label slot="placeholder"&gt;Select one or more airports&lt;/label&gt;
1362
+ &lt;span slot="label"&gt;Airports served&lt;/span&gt;
1363
+ &lt;auro-menu&gt;
1364
+ &lt;auro-menuoption value="seattle"&gt;Seattle&amp;ndash;Tacoma International (SEA)&lt;/auro-menuoption&gt;
1365
+ &lt;auro-menuoption value="seattle"&gt;Seattle Paine Field (PAE)&lt;/auro-menuoption&gt;
1366
+ &lt;auro-menuoption value="portland"&gt;Portland International (PDX)&lt;/auro-menuoption&gt;
1367
+ &lt;auro-menuoption value="spokane"&gt;Spokane International (GEG)&lt;/auro-menuoption&gt;
1368
+ &lt;/auro-menu&gt;
1369
+ &lt;/auro-select&gt;</code></pre>
1370
+ <!-- AURO-GENERATED-CONTENT:END -->
1371
+ </auro-accordion>
1300
1372
  </section>
1301
1373
  </div>
1302
1374
  </div>