@aurodesignsystem-dev/auro-formkit 0.0.0-pr1516.0 → 0.0.0-pr1519.0
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 +18 -10
- package/components/checkbox/demo/getting-started.min.js +18 -10
- package/components/checkbox/demo/index.min.js +18 -10
- package/components/checkbox/dist/index.js +18 -10
- package/components/checkbox/dist/registered.js +18 -10
- package/components/combobox/README.md +1 -1
- package/components/combobox/demo/customize.md +2 -10
- package/components/combobox/demo/customize.min.js +454 -467
- package/components/combobox/demo/getting-started.min.js +454 -463
- package/components/combobox/demo/index.md +1 -1
- package/components/combobox/demo/index.min.js +454 -463
- package/components/combobox/demo/keyboard-behavior.md +2 -142
- package/components/combobox/demo/readme.md +1 -1
- package/components/combobox/demo/why-combobox.md +2 -2
- package/components/combobox/dist/auro-combobox.d.ts +30 -14
- package/components/combobox/dist/index.js +454 -463
- package/components/combobox/dist/registered.js +454 -463
- package/components/counter/demo/customize.min.js +32 -12
- package/components/counter/demo/index.min.js +32 -12
- package/components/counter/dist/index.js +32 -12
- package/components/counter/dist/registered.js +32 -12
- package/components/datepicker/demo/customize.min.js +183 -167
- package/components/datepicker/demo/index.min.js +183 -167
- package/components/datepicker/dist/index.js +183 -167
- package/components/datepicker/dist/registered.js +183 -167
- package/components/dropdown/demo/customize.min.js +14 -2
- package/components/dropdown/demo/getting-started.min.js +14 -2
- package/components/dropdown/demo/index.min.js +14 -2
- package/components/dropdown/dist/index.js +14 -2
- package/components/dropdown/dist/registered.js +14 -2
- package/components/form/demo/customize.min.js +851 -829
- package/components/form/demo/getting-started.min.js +851 -829
- package/components/form/demo/index.min.js +851 -829
- package/components/form/demo/registerDemoDeps.min.js +851 -829
- package/components/input/demo/api.md +58 -57
- package/components/input/demo/customize.min.js +114 -155
- package/components/input/demo/getting-started.min.js +114 -155
- package/components/input/demo/index.min.js +114 -155
- package/components/input/dist/base-input.d.ts +9 -51
- package/components/input/dist/index.js +114 -155
- package/components/input/dist/registered.js +114 -155
- package/components/input/dist/utilities.d.ts +9 -0
- package/components/radio/demo/customize.min.js +18 -10
- package/components/radio/demo/getting-started.min.js +18 -10
- package/components/radio/demo/index.min.js +18 -10
- package/components/radio/dist/index.js +18 -10
- package/components/radio/dist/registered.js +18 -10
- package/components/select/demo/customize.min.js +32 -12
- package/components/select/demo/getting-started.min.js +32 -12
- package/components/select/demo/index.min.js +32 -12
- package/components/select/dist/index.js +32 -12
- package/components/select/dist/registered.js +32 -12
- package/custom-elements.json +142 -278
- package/package.json +1 -1
|
@@ -350,11 +350,17 @@ let AuroFormValidation$1 = class AuroFormValidation {
|
|
|
350
350
|
return;
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
// Validate that the date passed was the correct format and is a valid date
|
|
353
|
+
// Validate that the date passed was the correct format and is a valid date.
|
|
354
|
+
// For partial date formats, valueObject is never populated; validate them directly.
|
|
354
355
|
if (elem.value && !elem.valueObject) {
|
|
355
|
-
elem.
|
|
356
|
-
|
|
357
|
-
|
|
356
|
+
const isPartialDateFormat = elem.util && !elem.util.isFullDateFormat(elem.type, elem.format);
|
|
357
|
+
const isValidPartial = isPartialDateFormat && elem.util.isValidPartialDate(elem.value, elem.format);
|
|
358
|
+
|
|
359
|
+
if (!isValidPartial) {
|
|
360
|
+
elem.validity = 'patternMismatch';
|
|
361
|
+
elem.errorMessage = elem.setCustomValidityPatternMismatch || elem.setCustomValidity || 'Invalid Date Format Entered';
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
358
364
|
}
|
|
359
365
|
|
|
360
366
|
// Perform the rest of the validation
|
|
@@ -448,15 +454,17 @@ let AuroFormValidation$1 = class AuroFormValidation {
|
|
|
448
454
|
);
|
|
449
455
|
}
|
|
450
456
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
457
|
+
const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
|
|
458
|
+
|
|
459
|
+
// If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
|
|
460
|
+
// Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
|
|
461
|
+
// field (datepicker is the intended consumer — start/end are independently required).
|
|
462
|
+
if (this.auroInputElements?.length === 2 && !isCombobox) {
|
|
463
|
+
if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
|
|
454
464
|
hasValue = false;
|
|
455
465
|
}
|
|
456
466
|
}
|
|
457
467
|
|
|
458
|
-
const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
|
|
459
|
-
|
|
460
468
|
if (isCombobox) {
|
|
461
469
|
|
|
462
470
|
if (!elem.persistInput || elem.behavior === "filter") {
|
|
@@ -767,6 +775,8 @@ function navigateArrow(component, direction, options = {}) {
|
|
|
767
775
|
}
|
|
768
776
|
}
|
|
769
777
|
|
|
778
|
+
/* eslint-disable no-underscore-dangle */
|
|
779
|
+
|
|
770
780
|
/**
|
|
771
781
|
* Returns the clear button element from the active input's shadow
|
|
772
782
|
* DOM, if available.
|
|
@@ -802,11 +812,9 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
|
|
|
802
812
|
* @param {Object} menu - The menu component.
|
|
803
813
|
*/
|
|
804
814
|
function reconcileMenuIndex(menu) {
|
|
805
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
806
815
|
if (menu._index < 0 && menu.optionActive && menu.items) {
|
|
807
816
|
const idx = menu.items.indexOf(menu.optionActive);
|
|
808
817
|
if (idx >= 0) {
|
|
809
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
810
818
|
menu._index = idx;
|
|
811
819
|
}
|
|
812
820
|
}
|
|
@@ -825,11 +833,7 @@ const comboboxKeyboardStrategy = {
|
|
|
825
833
|
|
|
826
834
|
// navigate if bib is open otherwise open it
|
|
827
835
|
if (component.dropdown.isPopoverVisible) {
|
|
828
|
-
|
|
829
|
-
component.activateLastEnabledAvailableOption();
|
|
830
|
-
} else {
|
|
831
|
-
navigateArrow(component, 'down');
|
|
832
|
-
}
|
|
836
|
+
navigateArrow(component, 'down');
|
|
833
837
|
} else {
|
|
834
838
|
component.showBib();
|
|
835
839
|
}
|
|
@@ -848,11 +852,7 @@ const comboboxKeyboardStrategy = {
|
|
|
848
852
|
|
|
849
853
|
// navigate if bib is open otherwise open it
|
|
850
854
|
if (component.dropdown.isPopoverVisible) {
|
|
851
|
-
|
|
852
|
-
component.activateFirstEnabledAvailableOption();
|
|
853
|
-
} else {
|
|
854
|
-
navigateArrow(component, 'up');
|
|
855
|
-
}
|
|
855
|
+
navigateArrow(component, 'up');
|
|
856
856
|
} else {
|
|
857
857
|
component.showBib();
|
|
858
858
|
}
|
|
@@ -868,13 +868,18 @@ const comboboxKeyboardStrategy = {
|
|
|
868
868
|
},
|
|
869
869
|
|
|
870
870
|
Enter(component, evt, ctx) {
|
|
871
|
+
// Forms should not submit on Enter from a combobox, regardless of which
|
|
872
|
+
// child element (input, clear button, menu) is focused.
|
|
873
|
+
evt.stopPropagation();
|
|
874
|
+
|
|
871
875
|
if (isClearBtnFocused(ctx)) {
|
|
872
|
-
//
|
|
873
|
-
//
|
|
874
|
-
//
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
876
|
+
// Let the browser dispatch Enter to the focused clear button so its
|
|
877
|
+
// built-in activation fires and clears the selection. Do NOT call
|
|
878
|
+
// preventDefault — that would block the activation.
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (ctx.isExpanded && component.menu.optionActive) {
|
|
878
883
|
reconcileMenuIndex(component.menu);
|
|
879
884
|
component.menu.makeSelection();
|
|
880
885
|
|
|
@@ -883,14 +888,8 @@ const comboboxKeyboardStrategy = {
|
|
|
883
888
|
}
|
|
884
889
|
|
|
885
890
|
evt.preventDefault();
|
|
886
|
-
evt.stopPropagation();
|
|
887
891
|
} else {
|
|
888
|
-
// Prevent the keypress from bubbling to parent containers (e.g., forms)
|
|
889
|
-
// which could interpret Enter as a submit or trigger other unintended behavior.
|
|
890
|
-
// This is safe because showBib() opens the dialog programmatically,
|
|
891
|
-
// not via event propagation.
|
|
892
892
|
evt.preventDefault();
|
|
893
|
-
evt.stopPropagation();
|
|
894
893
|
component.showBib();
|
|
895
894
|
}
|
|
896
895
|
},
|
|
@@ -935,7 +934,7 @@ const comboboxKeyboardStrategy = {
|
|
|
935
934
|
component.setClearBtnFocus();
|
|
936
935
|
}
|
|
937
936
|
}
|
|
938
|
-
}
|
|
937
|
+
}
|
|
939
938
|
};
|
|
940
939
|
|
|
941
940
|
// Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
|
|
@@ -4806,7 +4805,7 @@ let AuroHelpText$2 = class AuroHelpText extends LitElement {
|
|
|
4806
4805
|
}
|
|
4807
4806
|
};
|
|
4808
4807
|
|
|
4809
|
-
var formkitVersion$2 = '
|
|
4808
|
+
var formkitVersion$2 = '202607011722';
|
|
4810
4809
|
|
|
4811
4810
|
let AuroElement$2 = class AuroElement extends LitElement {
|
|
4812
4811
|
static get properties() {
|
|
@@ -5558,7 +5557,19 @@ class AuroDropdown extends AuroElement$2 {
|
|
|
5558
5557
|
if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown") {
|
|
5559
5558
|
this.trigger.focus();
|
|
5560
5559
|
}
|
|
5561
|
-
|
|
5560
|
+
|
|
5561
|
+
|
|
5562
|
+
if (!this.isPopoverVisible) {
|
|
5563
|
+
// wait til the bib gets fully closed and rendered
|
|
5564
|
+
setTimeout(() => {
|
|
5565
|
+
// check if it's still closed and the focus is still within the dropdown, but not in the bib. If so, move focus to the trigger.
|
|
5566
|
+
if (!this.isPopoverVisible || !this.bib.matches(':focus-within')) {
|
|
5567
|
+
return;
|
|
5568
|
+
}
|
|
5569
|
+
// Move focus out of bib into trigger.
|
|
5570
|
+
this.trigger.focus();
|
|
5571
|
+
});
|
|
5572
|
+
} }
|
|
5562
5573
|
|
|
5563
5574
|
firstUpdated() {
|
|
5564
5575
|
// Configure the floater to, this will generate the ID for the bib
|
|
@@ -10483,11 +10494,17 @@ class AuroFormValidation {
|
|
|
10483
10494
|
return;
|
|
10484
10495
|
}
|
|
10485
10496
|
|
|
10486
|
-
// Validate that the date passed was the correct format and is a valid date
|
|
10497
|
+
// Validate that the date passed was the correct format and is a valid date.
|
|
10498
|
+
// For partial date formats, valueObject is never populated; validate them directly.
|
|
10487
10499
|
if (elem.value && !elem.valueObject) {
|
|
10488
|
-
elem.
|
|
10489
|
-
|
|
10490
|
-
|
|
10500
|
+
const isPartialDateFormat = elem.util && !elem.util.isFullDateFormat(elem.type, elem.format);
|
|
10501
|
+
const isValidPartial = isPartialDateFormat && elem.util.isValidPartialDate(elem.value, elem.format);
|
|
10502
|
+
|
|
10503
|
+
if (!isValidPartial) {
|
|
10504
|
+
elem.validity = 'patternMismatch';
|
|
10505
|
+
elem.errorMessage = elem.setCustomValidityPatternMismatch || elem.setCustomValidity || 'Invalid Date Format Entered';
|
|
10506
|
+
return;
|
|
10507
|
+
}
|
|
10491
10508
|
}
|
|
10492
10509
|
|
|
10493
10510
|
// Perform the rest of the validation
|
|
@@ -10581,15 +10598,17 @@ class AuroFormValidation {
|
|
|
10581
10598
|
);
|
|
10582
10599
|
}
|
|
10583
10600
|
|
|
10584
|
-
|
|
10585
|
-
|
|
10586
|
-
|
|
10601
|
+
const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
|
|
10602
|
+
|
|
10603
|
+
// If there is a second input in the elem and that value is undefined or an empty string set hasValue to false.
|
|
10604
|
+
// Skip for combobox: its second auro-input is the fullscreen-bib mirror of the same value, not an independent
|
|
10605
|
+
// field (datepicker is the intended consumer — start/end are independently required).
|
|
10606
|
+
if (this.auroInputElements?.length === 2 && !isCombobox) {
|
|
10607
|
+
if (!this.auroInputElements[1].value || this.auroInputElements[1].value.length === 0) {
|
|
10587
10608
|
hasValue = false;
|
|
10588
10609
|
}
|
|
10589
10610
|
}
|
|
10590
10611
|
|
|
10591
|
-
const isCombobox = this.runtimeUtils.elementMatch(elem, 'auro-combobox');
|
|
10592
|
-
|
|
10593
10612
|
if (isCombobox) {
|
|
10594
10613
|
|
|
10595
10614
|
if (!elem.persistInput || elem.behavior === "filter") {
|
|
@@ -16489,6 +16508,42 @@ class AuroInputUtilities {
|
|
|
16489
16508
|
return type === 'date' && normalizedFormat.includes('yy') && normalizedFormat.includes('mm') && normalizedFormat.includes('dd');
|
|
16490
16509
|
}
|
|
16491
16510
|
|
|
16511
|
+
/**
|
|
16512
|
+
* Validates a value against a partial date format (one that lacks yy/mm/dd all three).
|
|
16513
|
+
* Single-component formats are checked as integer ranges; multi-component formats use
|
|
16514
|
+
* a date-fns parse + round-trip to confirm both validity and exact formatting.
|
|
16515
|
+
* @param {string} value - The user-facing display value.
|
|
16516
|
+
* @param {string} format - The partial date format string (e.g. "mm/yyyy", "yyyy", "dd").
|
|
16517
|
+
* @returns {boolean}
|
|
16518
|
+
*/
|
|
16519
|
+
isValidPartialDate(value, format$1) {
|
|
16520
|
+
if (!value || !format$1) {
|
|
16521
|
+
return false;
|
|
16522
|
+
}
|
|
16523
|
+
const normalizedFormat = format$1.toLowerCase();
|
|
16524
|
+
|
|
16525
|
+
if (normalizedFormat === 'dd') {
|
|
16526
|
+
const num = Number(value);
|
|
16527
|
+
return (/^\d{2}$/u).test(value) && num >= 1 && num <= 31;
|
|
16528
|
+
}
|
|
16529
|
+
if (normalizedFormat === 'yy') {
|
|
16530
|
+
const num = Number(value);
|
|
16531
|
+
return (/^\d{2}$/u).test(value) && num >= 0 && num <= 99;
|
|
16532
|
+
}
|
|
16533
|
+
if (normalizedFormat === 'yyyy') {
|
|
16534
|
+
const num = Number(value);
|
|
16535
|
+
return (/^\d{4}$/u).test(value) && num >= 1 && num <= 9999;
|
|
16536
|
+
}
|
|
16537
|
+
|
|
16538
|
+
const dateFnsMask = this.toDateFnsMask(normalizedFormat);
|
|
16539
|
+
// Use the 1st of the current month as the reference so that formats
|
|
16540
|
+
// omitting a day (e.g. MM/yyyy) never roll over on days 29–31.
|
|
16541
|
+
const referenceDate = new Date();
|
|
16542
|
+
referenceDate.setDate(1);
|
|
16543
|
+
const parsed = parse(value, dateFnsMask, referenceDate);
|
|
16544
|
+
return isValid(parsed) && format(parsed, dateFnsMask) === value;
|
|
16545
|
+
}
|
|
16546
|
+
|
|
16492
16547
|
/**
|
|
16493
16548
|
* Converts a display string to its model value.
|
|
16494
16549
|
* For full date formats, converts the display string to an ISO date string.
|
|
@@ -16536,6 +16591,7 @@ class AuroInputUtilities {
|
|
|
16536
16591
|
const maskOptions = this.getMaskOptions('date', normalizedFormat);
|
|
16537
16592
|
|
|
16538
16593
|
if (!(valueObject instanceof Date) || Number.isNaN(valueObject.getTime()) || !maskOptions || typeof maskOptions.format !== 'function') {
|
|
16594
|
+
console.debug('Invalid date object or mask options for formatting', { valueObject, maskOptions }); // eslint-disable-line no-console
|
|
16539
16595
|
return undefined;
|
|
16540
16596
|
}
|
|
16541
16597
|
|
|
@@ -16693,6 +16749,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
16693
16749
|
// so a parent (datepicker/combobox) calling `validate()` synchronously
|
|
16694
16750
|
// during its own update cycle sees a populated util instance.
|
|
16695
16751
|
this.activeLabel = false;
|
|
16752
|
+
|
|
16696
16753
|
/** @private */
|
|
16697
16754
|
this.allowedInputTypes = [
|
|
16698
16755
|
"text",
|
|
@@ -16703,6 +16760,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
16703
16760
|
"tel"
|
|
16704
16761
|
];
|
|
16705
16762
|
this.appearance = "default";
|
|
16763
|
+
|
|
16706
16764
|
/** @private */
|
|
16707
16765
|
this.dateFormatMap = {
|
|
16708
16766
|
'mm/dd/yyyy': 'dateMMDDYYYY',
|
|
@@ -16721,23 +16779,24 @@ class BaseInput extends AuroElement$1 {
|
|
|
16721
16779
|
'mm/dd': 'dateMMDD'
|
|
16722
16780
|
};
|
|
16723
16781
|
this.disabled = false;
|
|
16782
|
+
|
|
16724
16783
|
/** @private */
|
|
16725
16784
|
this.domHandler = new DomHandler();
|
|
16726
16785
|
this.dvInputOnly = false;
|
|
16727
16786
|
this.hasValue = false;
|
|
16728
16787
|
this.hideLabelVisually = false;
|
|
16729
16788
|
this.icon = false;
|
|
16789
|
+
|
|
16730
16790
|
/** @private */
|
|
16731
16791
|
this.inputIconName = undefined;
|
|
16792
|
+
|
|
16732
16793
|
/** @private */
|
|
16733
16794
|
this.label = 'Input label is undefined';
|
|
16734
16795
|
this.layout = 'classic';
|
|
16735
16796
|
this.locale = 'en-US';
|
|
16736
16797
|
this.max = undefined;
|
|
16737
|
-
this._maxObject = undefined;
|
|
16738
16798
|
this.maxLength = undefined;
|
|
16739
16799
|
this.min = undefined;
|
|
16740
|
-
this._minObject = undefined;
|
|
16741
16800
|
this.minLength = undefined;
|
|
16742
16801
|
this.noValidate = false;
|
|
16743
16802
|
this.onDark = false;
|
|
@@ -16754,23 +16813,27 @@ class BaseInput extends AuroElement$1 {
|
|
|
16754
16813
|
"email"
|
|
16755
16814
|
];
|
|
16756
16815
|
this.shape = 'classic';
|
|
16816
|
+
|
|
16757
16817
|
/** @private */
|
|
16758
16818
|
this.showPassword = false;
|
|
16759
16819
|
this.size = 'lg';
|
|
16760
16820
|
this.touched = false;
|
|
16821
|
+
|
|
16761
16822
|
/** @private */
|
|
16762
16823
|
this.uniqueId = new UniqueId().create();
|
|
16824
|
+
|
|
16763
16825
|
/** @private */
|
|
16764
16826
|
this.util = new AuroInputUtilities({
|
|
16765
16827
|
locale: this.locale,
|
|
16766
16828
|
format: this.format
|
|
16767
16829
|
});
|
|
16830
|
+
|
|
16768
16831
|
/** @private */
|
|
16769
16832
|
this.validation = new AuroFormValidation();
|
|
16833
|
+
|
|
16770
16834
|
/** @private */
|
|
16771
16835
|
this.validationCCLength = undefined;
|
|
16772
16836
|
this.value = undefined;
|
|
16773
|
-
this._valueObject = undefined;
|
|
16774
16837
|
}
|
|
16775
16838
|
|
|
16776
16839
|
// function to define props used within the scope of this component
|
|
@@ -17100,6 +17163,13 @@ class BaseInput extends AuroElement$1 {
|
|
|
17100
17163
|
type: String
|
|
17101
17164
|
},
|
|
17102
17165
|
|
|
17166
|
+
/**
|
|
17167
|
+
* Custom help text message to display when validity = `patternMismatch`.
|
|
17168
|
+
*/
|
|
17169
|
+
setCustomValidityPatternMismatch: {
|
|
17170
|
+
type: String
|
|
17171
|
+
},
|
|
17172
|
+
|
|
17103
17173
|
/**
|
|
17104
17174
|
* Custom help text message to display when validity = `rangeOverflow`.
|
|
17105
17175
|
*/
|
|
@@ -17170,7 +17240,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
17170
17240
|
|
|
17171
17241
|
/**
|
|
17172
17242
|
* Populates the `type` attribute on the input.
|
|
17173
|
-
* @type {'text' | 'password' | 'email' | 'credit-card' | 'tel' | 'number'}
|
|
17243
|
+
* @type {'text' | 'password' | 'email' | 'credit-card' | 'tel' | 'number' | 'date'}
|
|
17174
17244
|
* @default 'text'
|
|
17175
17245
|
*/
|
|
17176
17246
|
type: {
|
|
@@ -17208,7 +17278,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
17208
17278
|
* @returns {Date|undefined}
|
|
17209
17279
|
*/
|
|
17210
17280
|
get valueObject() {
|
|
17211
|
-
return this.
|
|
17281
|
+
return this.value && dateFormatter.isValidDate(this.value) ? dateFormatter.stringToDateInstance(this.value) : undefined;
|
|
17212
17282
|
}
|
|
17213
17283
|
|
|
17214
17284
|
/**
|
|
@@ -17216,7 +17286,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
17216
17286
|
* @returns {Date|undefined}
|
|
17217
17287
|
*/
|
|
17218
17288
|
get minObject() {
|
|
17219
|
-
return this.
|
|
17289
|
+
return this.min && dateFormatter.isValidDate(this.min) ? dateFormatter.stringToDateInstance(this.min) : undefined;
|
|
17220
17290
|
}
|
|
17221
17291
|
|
|
17222
17292
|
/**
|
|
@@ -17224,50 +17294,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
17224
17294
|
* @returns {Date|undefined}
|
|
17225
17295
|
*/
|
|
17226
17296
|
get maxObject() {
|
|
17227
|
-
return this.
|
|
17228
|
-
}
|
|
17229
|
-
|
|
17230
|
-
/**
|
|
17231
|
-
* Parses a date string into a Date object when the corresponding `_*Object`
|
|
17232
|
-
* field hasn't been synced yet by `updated()`. Returns undefined when the
|
|
17233
|
-
* input type/format isn't a full date or the string is not a valid date.
|
|
17234
|
-
*
|
|
17235
|
-
* Why this exists: a parent (datepicker) can call `inputN.validate()` from
|
|
17236
|
-
* inside its own `updated()` before this input's `updated()` has run
|
|
17237
|
-
* `syncDateValues()` — so `_valueObject`/`_maxObject` are still `undefined`
|
|
17238
|
-
* and range checks would otherwise silently no-op (flipping the result to
|
|
17239
|
-
* `valid` or `patternMismatch`).
|
|
17240
|
-
* @private
|
|
17241
|
-
* @param {string|undefined} dateStr - ISO date string from `value`/`min`/`max`.
|
|
17242
|
-
* @returns {Date|undefined}
|
|
17243
|
-
*/
|
|
17244
|
-
_computeDateObjectFallback(dateStr) {
|
|
17245
|
-
if (!dateStr || !this.util || !this.util.isFullDateFormat(this.type, this.format)) {
|
|
17246
|
-
return undefined;
|
|
17247
|
-
}
|
|
17248
|
-
if (!dateFormatter.isValidDate(dateStr)) {
|
|
17249
|
-
return undefined;
|
|
17250
|
-
}
|
|
17251
|
-
return dateFormatter.stringToDateInstance(dateStr);
|
|
17252
|
-
}
|
|
17253
|
-
|
|
17254
|
-
/**
|
|
17255
|
-
* Internal setter for readonly date object properties.
|
|
17256
|
-
* @private
|
|
17257
|
-
* @param {'valueObject'|'minObject'|'maxObject'} propertyName - Public object property name.
|
|
17258
|
-
* @param {Date|undefined} propertyValue - Value to assign.
|
|
17259
|
-
* @returns {void}
|
|
17260
|
-
*/
|
|
17261
|
-
setDateObjectProperty(propertyName, propertyValue) {
|
|
17262
|
-
const internalPropertyName = `_${propertyName}`;
|
|
17263
|
-
const previousValue = this[internalPropertyName];
|
|
17264
|
-
|
|
17265
|
-
if (previousValue === propertyValue) {
|
|
17266
|
-
return;
|
|
17267
|
-
}
|
|
17268
|
-
|
|
17269
|
-
this[internalPropertyName] = propertyValue;
|
|
17270
|
-
this.requestUpdate(propertyName, previousValue);
|
|
17297
|
+
return this.max && dateFormatter.isValidDate(this.max) ? dateFormatter.stringToDateInstance(this.max) : undefined;
|
|
17271
17298
|
}
|
|
17272
17299
|
|
|
17273
17300
|
connectedCallback() {
|
|
@@ -17296,7 +17323,6 @@ class BaseInput extends AuroElement$1 {
|
|
|
17296
17323
|
format: this.format
|
|
17297
17324
|
});
|
|
17298
17325
|
this.configureDataForType();
|
|
17299
|
-
this.syncDateValues();
|
|
17300
17326
|
}
|
|
17301
17327
|
|
|
17302
17328
|
disconnectedCallback() {
|
|
@@ -17335,7 +17361,6 @@ class BaseInput extends AuroElement$1 {
|
|
|
17335
17361
|
this.setCustomHelpTextMessage();
|
|
17336
17362
|
this.configureAutoFormatting();
|
|
17337
17363
|
this.configureDataForType();
|
|
17338
|
-
this.syncDateValues();
|
|
17339
17364
|
}
|
|
17340
17365
|
|
|
17341
17366
|
/**
|
|
@@ -17479,8 +17504,6 @@ class BaseInput extends AuroElement$1 {
|
|
|
17479
17504
|
this.configureDataForType();
|
|
17480
17505
|
}
|
|
17481
17506
|
|
|
17482
|
-
this.syncDateValues(changedProperties);
|
|
17483
|
-
|
|
17484
17507
|
if (changedProperties.has('value')) {
|
|
17485
17508
|
if (this.value && this.value.length > 0) {
|
|
17486
17509
|
this.hasValue = true;
|
|
@@ -17502,14 +17525,14 @@ class BaseInput extends AuroElement$1 {
|
|
|
17502
17525
|
|
|
17503
17526
|
if (formattedValue !== this.inputElement.value) {
|
|
17504
17527
|
this.skipNextProgrammaticInputEvent = true;
|
|
17505
|
-
if (this.maskInstance && this.type
|
|
17528
|
+
if (this.maskInstance && this.type !== 'date') {
|
|
17506
17529
|
// Route through the mask so its _value and el.value stay in lock-step
|
|
17507
17530
|
// (set value calls updateControl which writes el.value = displayValue).
|
|
17508
17531
|
// Writing el.value directly leaves the mask thinking displayValue is
|
|
17509
|
-
// stale; _saveSelection on the next focus/click then warns.
|
|
17510
|
-
//
|
|
17511
|
-
//
|
|
17512
|
-
//
|
|
17532
|
+
// stale; _saveSelection on the next focus/click then warns. Date is
|
|
17533
|
+
// excluded because its formattedValue can be raw ISO when the calendar
|
|
17534
|
+
// is invalid, and re-masking through mm/dd/yyyy would truncate it and
|
|
17535
|
+
// flip validity from patternMismatch to tooShort.
|
|
17513
17536
|
this.maskInstance.value = formattedValue || '';
|
|
17514
17537
|
} else if (formattedValue) {
|
|
17515
17538
|
this.inputElement.value = formattedValue;
|
|
@@ -17551,120 +17574,65 @@ class BaseInput extends AuroElement$1 {
|
|
|
17551
17574
|
}));
|
|
17552
17575
|
}
|
|
17553
17576
|
|
|
17554
|
-
|
|
17555
|
-
/**
|
|
17556
|
-
* Synchronizes the ISO string values and Date object representations for date-related properties.
|
|
17557
|
-
* This keeps the model and display values aligned when either side changes.
|
|
17558
|
-
*
|
|
17559
|
-
* When a full date format is in use, this method updates `value`, `min`, and `max` from their corresponding
|
|
17560
|
-
* Date objects or vice versa, based on which properties have changed. It only runs when the current configuration
|
|
17561
|
-
* represents a full year/month/day date format.
|
|
17562
|
-
*
|
|
17563
|
-
* @param {Map<string, unknown>|undefined} [changedProperties=undefined] - Optional map of changed properties used to limit which values are synchronized.
|
|
17564
|
-
* @returns {void}
|
|
17565
|
-
* @private
|
|
17566
|
-
*/
|
|
17567
|
-
syncDateValues(changedProperties = undefined) {
|
|
17568
|
-
if (!this.util.isFullDateFormat(this.type, this.format)) {
|
|
17569
|
-
return;
|
|
17570
|
-
}
|
|
17571
|
-
|
|
17572
|
-
this.syncSingleDateValue(changedProperties, 'valueObject', 'value');
|
|
17573
|
-
this.syncSingleDateValue(changedProperties, 'minObject', 'min');
|
|
17574
|
-
this.syncSingleDateValue(changedProperties, 'maxObject', 'max');
|
|
17575
|
-
}
|
|
17576
|
-
|
|
17577
|
-
/**
|
|
17578
|
-
* Synchronizes one date object/string property pair.
|
|
17579
|
-
* @private
|
|
17580
|
-
* @param {Map<string, unknown>|undefined} changedProperties - Map of changed properties from Lit.
|
|
17581
|
-
* @param {string} objectProperty - Date object property name.
|
|
17582
|
-
* @param {string} valueProperty - ISO string property name.
|
|
17583
|
-
* @returns {void}
|
|
17584
|
-
*/
|
|
17585
|
-
syncSingleDateValue(changedProperties, objectProperty, valueProperty) {
|
|
17586
|
-
const objectPropertyChanged = !changedProperties || changedProperties.has(objectProperty);
|
|
17587
|
-
|
|
17588
|
-
// objectProperty wins over valueProperty when both changed
|
|
17589
|
-
if (objectPropertyChanged && this[objectProperty]) {
|
|
17590
|
-
this[valueProperty] = dateFormatter.toISOFormatString(this[objectProperty]);
|
|
17591
|
-
return;
|
|
17592
|
-
}
|
|
17593
|
-
|
|
17594
|
-
const valuePropertyChanged = !changedProperties || changedProperties.has(valueProperty);
|
|
17595
|
-
if (!valuePropertyChanged) {
|
|
17596
|
-
return;
|
|
17597
|
-
}
|
|
17598
|
-
|
|
17599
|
-
// when value is newly set to the same ISO string that corresponds to the existing Date object, do not clear the Date object (avoid unnecessary updates)
|
|
17600
|
-
if (
|
|
17601
|
-
changedProperties &&
|
|
17602
|
-
valueProperty === 'value' &&
|
|
17603
|
-
changedProperties.get('value') === undefined &&
|
|
17604
|
-
this[objectProperty] instanceof Date &&
|
|
17605
|
-
this[valueProperty] === dateFormatter.toISOFormatString(this[objectProperty])
|
|
17606
|
-
) {
|
|
17607
|
-
return;
|
|
17608
|
-
}
|
|
17609
|
-
|
|
17610
|
-
if (dateFormatter.isValidDate(this[valueProperty])) {
|
|
17611
|
-
this.setDateObjectProperty(objectProperty, dateFormatter.stringToDateInstance(this[valueProperty]));
|
|
17612
|
-
} else {
|
|
17613
|
-
this.setDateObjectProperty(objectProperty, undefined);
|
|
17614
|
-
}
|
|
17615
|
-
}
|
|
17616
|
-
|
|
17617
17577
|
/**
|
|
17618
17578
|
* Sets up IMasks and logic based on auto-formatting requirements.
|
|
17619
17579
|
* @private
|
|
17620
17580
|
* @returns {void}
|
|
17621
17581
|
*/
|
|
17622
17582
|
configureAutoFormatting() {
|
|
17623
|
-
//
|
|
17624
|
-
//
|
|
17625
|
-
//
|
|
17626
|
-
//
|
|
17583
|
+
// _configuringMask gates two things: external re-entry into this method
|
|
17584
|
+
// while setup is mid-flight (from property changes that call back here),
|
|
17585
|
+
// and the accept/complete listeners below — both need to ignore the mask
|
|
17586
|
+
// events fired by our own value-restore step.
|
|
17627
17587
|
if (this._configuringMask) return;
|
|
17628
17588
|
this._configuringMask = true;
|
|
17629
17589
|
try {
|
|
17590
|
+
// Destroy any prior mask so IMask can attach fresh under the new format.
|
|
17591
|
+
// Null the reference too — if the new maskOptions.mask is falsy (e.g.
|
|
17592
|
+
// type switched from credit-card to text) the IMask() reassignment
|
|
17593
|
+
// below is skipped, and downstream writes at line ~823 would otherwise
|
|
17594
|
+
// route through the destroyed instance.
|
|
17630
17595
|
if (this.maskInstance) {
|
|
17631
17596
|
this.maskInstance.destroy();
|
|
17597
|
+
this.maskInstance = null;
|
|
17632
17598
|
}
|
|
17633
17599
|
|
|
17634
|
-
// Pass new format to util
|
|
17635
17600
|
this.util.updateFormat(this.format);
|
|
17636
17601
|
|
|
17637
17602
|
const maskOptions = this.util.getMaskOptions(this.type, this.format);
|
|
17638
17603
|
|
|
17639
17604
|
if (this.inputElement && maskOptions.mask) {
|
|
17640
|
-
|
|
17641
|
-
//
|
|
17642
|
-
//
|
|
17643
|
-
//
|
|
17644
|
-
// When the format changes (e.g. locale switch) and we have a valid ISO
|
|
17645
|
-
// model value, compute the display string for the NEW format instead of
|
|
17646
|
-
// re-using the old display string, which may be invalid in the new mask.
|
|
17605
|
+
// Capture the current display so it can be re-applied after IMask
|
|
17606
|
+
// attaches. The restore at the bottom goes through maskInstance.value
|
|
17607
|
+
// (not inputElement.value directly) so the mask's internal state and
|
|
17608
|
+
// the input's displayed text stay in lock-step.
|
|
17647
17609
|
let existingValue = this.inputElement.value;
|
|
17610
|
+
|
|
17611
|
+
// Format-change case (e.g. locale switch): existingValue is the OLD
|
|
17612
|
+
// mask's display string and may not parse under the new mask. When
|
|
17613
|
+
// we have a valid date model, rebuild the display from valueObject
|
|
17614
|
+
// (the canonical source) using the new mask's format function.
|
|
17648
17615
|
if (
|
|
17649
17616
|
this.util.isFullDateFormat(this.type, this.format) &&
|
|
17650
17617
|
this.value &&
|
|
17651
|
-
|
|
17652
|
-
this.valueObject instanceof Date &&
|
|
17653
|
-
!Number.isNaN(this.valueObject.getTime()) &&
|
|
17618
|
+
this.valueObject &&
|
|
17654
17619
|
typeof maskOptions.format === 'function'
|
|
17655
17620
|
) {
|
|
17656
17621
|
existingValue = maskOptions.format(this.valueObject);
|
|
17657
17622
|
}
|
|
17658
17623
|
|
|
17624
|
+
// Clear before IMask attaches so the constructor seeds an empty
|
|
17625
|
+
// internal value. Otherwise IMask reads the stale unmasked string
|
|
17626
|
+
// and emits a spurious 'accept' before the restore below runs.
|
|
17659
17627
|
this.skipNextProgrammaticInputEvent = true;
|
|
17660
17628
|
this.inputElement.value = '';
|
|
17661
17629
|
|
|
17662
17630
|
this.maskInstance = IMask(this.inputElement, maskOptions);
|
|
17663
17631
|
|
|
17632
|
+
// Mask fires 'accept' on every value change, including the restore
|
|
17633
|
+
// step below. Skip events fired during configureAutoFormatting so
|
|
17634
|
+
// we don't overwrite a value the parent just pushed.
|
|
17664
17635
|
this.maskInstance.on('accept', () => {
|
|
17665
|
-
// Suppress propagation during configureAutoFormatting's own value-restoration
|
|
17666
|
-
// (line below) — the mask emits 'accept' on every value-set, including ours,
|
|
17667
|
-
// and we don't want to overwrite a value the parent just pushed.
|
|
17668
17636
|
if (this._configuringMask) return;
|
|
17669
17637
|
this.value = this.util.toModelValue(this.maskInstance.value, this.format);
|
|
17670
17638
|
if (this.type === "date") {
|
|
@@ -17672,6 +17640,8 @@ class BaseInput extends AuroElement$1 {
|
|
|
17672
17640
|
}
|
|
17673
17641
|
});
|
|
17674
17642
|
|
|
17643
|
+
// Mask fires 'complete' on the restore step below for any value that
|
|
17644
|
+
// happens to be a complete match. Same setup-suppression as 'accept'.
|
|
17675
17645
|
this.maskInstance.on('complete', () => {
|
|
17676
17646
|
if (this._configuringMask) return;
|
|
17677
17647
|
this.value = this.util.toModelValue(this.maskInstance.value, this.format);
|
|
@@ -17680,7 +17650,9 @@ class BaseInput extends AuroElement$1 {
|
|
|
17680
17650
|
}
|
|
17681
17651
|
});
|
|
17682
17652
|
|
|
17683
|
-
//
|
|
17653
|
+
// Write existingValue through the mask (not the input directly) so
|
|
17654
|
+
// the mask reformats it under the new rules and keeps its internal
|
|
17655
|
+
// _value aligned with the input's displayed text.
|
|
17684
17656
|
if (existingValue) {
|
|
17685
17657
|
this.maskInstance.value = existingValue;
|
|
17686
17658
|
}
|
|
@@ -17774,9 +17746,9 @@ class BaseInput extends AuroElement$1 {
|
|
|
17774
17746
|
// the gated types currently support it, since the list is a public
|
|
17775
17747
|
// property a consumer could mutate.
|
|
17776
17748
|
if (this.setSelectionInputTypes.includes(this.type)) {
|
|
17777
|
-
let selectionStart;
|
|
17749
|
+
let selectionStart = null;
|
|
17778
17750
|
try {
|
|
17779
|
-
selectionStart = this.inputElement
|
|
17751
|
+
({ selectionStart } = this.inputElement);
|
|
17780
17752
|
} catch (error) { // eslint-disable-line no-unused-vars
|
|
17781
17753
|
return;
|
|
17782
17754
|
}
|
|
@@ -17880,7 +17852,6 @@ class BaseInput extends AuroElement$1 {
|
|
|
17880
17852
|
*/
|
|
17881
17853
|
reset() {
|
|
17882
17854
|
this.value = undefined;
|
|
17883
|
-
this.setDateObjectProperty('valueObject', undefined);
|
|
17884
17855
|
this.validation.reset(this);
|
|
17885
17856
|
}
|
|
17886
17857
|
|
|
@@ -17889,7 +17860,6 @@ class BaseInput extends AuroElement$1 {
|
|
|
17889
17860
|
*/
|
|
17890
17861
|
clear() {
|
|
17891
17862
|
this.value = undefined;
|
|
17892
|
-
this.setDateObjectProperty('valueObject', undefined);
|
|
17893
17863
|
}
|
|
17894
17864
|
|
|
17895
17865
|
/**
|
|
@@ -18418,7 +18388,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
|
|
|
18418
18388
|
}
|
|
18419
18389
|
};
|
|
18420
18390
|
|
|
18421
|
-
var formkitVersion$1 = '
|
|
18391
|
+
var formkitVersion$1 = '202607011722';
|
|
18422
18392
|
|
|
18423
18393
|
// Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
|
|
18424
18394
|
// See LICENSE in the project root for license information.
|
|
@@ -19544,7 +19514,7 @@ class AuroBibtemplate extends LitElement {
|
|
|
19544
19514
|
}
|
|
19545
19515
|
}
|
|
19546
19516
|
|
|
19547
|
-
var formkitVersion = '
|
|
19517
|
+
var formkitVersion = '202607011722';
|
|
19548
19518
|
|
|
19549
19519
|
var styleCss$1 = css`.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}`;
|
|
19550
19520
|
|
|
@@ -19912,12 +19882,23 @@ function getOptionLabel(option) {
|
|
|
19912
19882
|
if (!option) {
|
|
19913
19883
|
return '';
|
|
19914
19884
|
}
|
|
19915
|
-
|
|
19916
|
-
|
|
19917
|
-
if (
|
|
19918
|
-
|
|
19885
|
+
|
|
19886
|
+
// Consumer-provided override: short-circuit the DOM walk entirely.
|
|
19887
|
+
if (option.dataset && option.dataset.label) {
|
|
19888
|
+
return option.dataset.label;
|
|
19889
|
+
}
|
|
19890
|
+
|
|
19891
|
+
// Walk direct children — the `slot` attribute only applies to direct children
|
|
19892
|
+
// of the slot host, so a shallow filter is sufficient. Avoids the cloneNode +
|
|
19893
|
+
// querySelector allocation that ran on every keystroke via syncValuesAndStates.
|
|
19894
|
+
let text = '';
|
|
19895
|
+
for (const node of option.childNodes) {
|
|
19896
|
+
const isDisplayValueSlot = node.nodeType === Node.ELEMENT_NODE && node.getAttribute('slot') === 'displayValue';
|
|
19897
|
+
if (!isDisplayValueSlot) {
|
|
19898
|
+
text += node.textContent || '';
|
|
19899
|
+
}
|
|
19919
19900
|
}
|
|
19920
|
-
return
|
|
19901
|
+
return text.replace(/\s+/gu, ' ').trim();
|
|
19921
19902
|
}
|
|
19922
19903
|
|
|
19923
19904
|
// See https://git.io/JJ6SJ for "How to document your components using JSDoc"
|
|
@@ -19992,8 +19973,6 @@ class AuroCombobox extends AuroElement {
|
|
|
19992
19973
|
this.availableOptions = [];
|
|
19993
19974
|
this.dropdownId = undefined;
|
|
19994
19975
|
this.dropdownOpen = false;
|
|
19995
|
-
this.triggerExpandedState = false;
|
|
19996
|
-
this._expandedTimeout = null;
|
|
19997
19976
|
this._inFullscreenTransition = false;
|
|
19998
19977
|
this.errorMessage = null;
|
|
19999
19978
|
this.isHiddenWhileLoading = false;
|
|
@@ -20004,6 +19983,30 @@ class AuroCombobox extends AuroElement {
|
|
|
20004
19983
|
this.touched = false;
|
|
20005
19984
|
this.validation = new AuroFormValidation$1();
|
|
20006
19985
|
this.validity = undefined;
|
|
19986
|
+
this._userTyped = false;
|
|
19987
|
+
// Tracks every setTimeout scheduled via _scheduleTimer so
|
|
19988
|
+
// disconnectedCallback can cancel them. Without this, a detached
|
|
19989
|
+
// combobox's pending timers still fire — most are no-ops, but
|
|
19990
|
+
// configureMenu's racing-condition retry would otherwise loop.
|
|
19991
|
+
this._pendingTimers = new Set();
|
|
19992
|
+
}
|
|
19993
|
+
|
|
19994
|
+
/**
|
|
19995
|
+
* setTimeout wrapper that records the timer id so disconnectedCallback
|
|
19996
|
+
* can cancel any outstanding callbacks. The id is removed from the set
|
|
19997
|
+
* once the callback fires so the set doesn't grow unbounded.
|
|
19998
|
+
* @param {Function} fn - Callback to run.
|
|
19999
|
+
* @param {number} ms - Delay in milliseconds.
|
|
20000
|
+
* @returns {number} The timer id.
|
|
20001
|
+
* @private
|
|
20002
|
+
*/
|
|
20003
|
+
_scheduleTimer(fn, ms) {
|
|
20004
|
+
const id = setTimeout(() => {
|
|
20005
|
+
this._pendingTimers.delete(id);
|
|
20006
|
+
fn();
|
|
20007
|
+
}, ms);
|
|
20008
|
+
this._pendingTimers.add(id);
|
|
20009
|
+
return id;
|
|
20007
20010
|
}
|
|
20008
20011
|
|
|
20009
20012
|
// This function is to define props used within the scope of this component
|
|
@@ -20365,17 +20368,6 @@ class AuroCombobox extends AuroElement {
|
|
|
20365
20368
|
attribute: false
|
|
20366
20369
|
},
|
|
20367
20370
|
|
|
20368
|
-
/**
|
|
20369
|
-
* Deferred aria-expanded state for the trigger input.
|
|
20370
|
-
* Delays the "true" transition so VoiceOver finishes its character echo
|
|
20371
|
-
* before announcing "expanded".
|
|
20372
|
-
* @private
|
|
20373
|
-
*/
|
|
20374
|
-
triggerExpandedState: {
|
|
20375
|
-
type: Boolean,
|
|
20376
|
-
reflect: false,
|
|
20377
|
-
attribute: false
|
|
20378
|
-
},
|
|
20379
20371
|
};
|
|
20380
20372
|
}
|
|
20381
20373
|
|
|
@@ -20398,13 +20390,6 @@ class AuroCombobox extends AuroElement {
|
|
|
20398
20390
|
return this.input.value;
|
|
20399
20391
|
}
|
|
20400
20392
|
|
|
20401
|
-
// /**
|
|
20402
|
-
// * Sets the value of the input element within the combobox.
|
|
20403
|
-
// */
|
|
20404
|
-
// set inputValue(value) {
|
|
20405
|
-
// this.input.value = value;
|
|
20406
|
-
// }
|
|
20407
|
-
|
|
20408
20393
|
/**
|
|
20409
20394
|
* Checks if the element is valid.
|
|
20410
20395
|
* @returns {boolean} - Returns true if the element is valid, false otherwise.
|
|
@@ -20531,7 +20516,7 @@ class AuroCombobox extends AuroElement {
|
|
|
20531
20516
|
if (this.menu) {
|
|
20532
20517
|
this.menu.matchWord = normalizeFilterValue(this.input.value);
|
|
20533
20518
|
}
|
|
20534
|
-
const label = getOptionLabel(this.menu.optionSelected)
|
|
20519
|
+
const label = getOptionLabel(this.menu.optionSelected);
|
|
20535
20520
|
this.updateTriggerTextDisplay(label || this.value);
|
|
20536
20521
|
}
|
|
20537
20522
|
|
|
@@ -20545,44 +20530,16 @@ class AuroCombobox extends AuroElement {
|
|
|
20545
20530
|
// in suggestion mode, do not override input value if no selection has been made and the input currently has focus
|
|
20546
20531
|
const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
|
|
20547
20532
|
|
|
20548
|
-
|
|
20549
|
-
|
|
20550
|
-
|
|
20551
|
-
// syncValuesAndStates re-enters here during typing when both inputs
|
|
20552
|
-
// already match, and a no-op flag flip would make the bib branch's
|
|
20553
|
-
// bail eat legitimate user-input events.
|
|
20554
|
-
const triggerNeedsSync = this.input.value !== nextValue;
|
|
20555
|
-
const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
|
|
20556
|
-
if (triggerNeedsSync || bibNeedsSync) {
|
|
20557
|
-
this._syncingDisplayValue = true;
|
|
20558
|
-
if (triggerNeedsSync) {
|
|
20559
|
-
this.input.value = nextValue;
|
|
20560
|
-
}
|
|
20561
|
-
if (bibNeedsSync) {
|
|
20562
|
-
this.inputInBib.value = nextValue;
|
|
20563
|
-
}
|
|
20564
|
-
const pending = [];
|
|
20565
|
-
if (triggerNeedsSync) {
|
|
20566
|
-
pending.push(this.input.updateComplete);
|
|
20567
|
-
}
|
|
20568
|
-
if (bibNeedsSync) {
|
|
20569
|
-
pending.push(this.inputInBib.updateComplete);
|
|
20570
|
-
}
|
|
20571
|
-
Promise.all(pending).then(() => {
|
|
20572
|
-
// imask reasserts otherwise, and handleBlur reverts to its stale value.
|
|
20573
|
-
if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
|
|
20574
|
-
this.input.maskInstance.updateValue();
|
|
20575
|
-
}
|
|
20576
|
-
if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
|
|
20577
|
-
this.inputInBib.maskInstance.updateValue();
|
|
20578
|
-
}
|
|
20579
|
-
this._syncingDisplayValue = false;
|
|
20580
|
-
});
|
|
20581
|
-
}
|
|
20533
|
+
const suppressed = this.persistInput || (this.behavior === 'suggestion' && isInputFocusedWithNoSelection);
|
|
20534
|
+
if (!suppressed) {
|
|
20535
|
+
this.syncInputValuesAcrossTriggerAndBib(label || this.value);
|
|
20582
20536
|
}
|
|
20583
20537
|
|
|
20584
|
-
//
|
|
20585
|
-
|
|
20538
|
+
// Replace any previously appended displayValue clone in the trigger.
|
|
20539
|
+
// :not(slot) excludes the template's <slot name="displayValue"
|
|
20540
|
+
// slot="displayValue"> forwarder, which also has slot="displayValue"
|
|
20541
|
+
// and would otherwise be matched first and removed.
|
|
20542
|
+
const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
|
|
20586
20543
|
|
|
20587
20544
|
if (displayValueInTrigger) {
|
|
20588
20545
|
displayValueInTrigger.remove();
|
|
@@ -20606,6 +20563,53 @@ class AuroCombobox extends AuroElement {
|
|
|
20606
20563
|
this.requestUpdate();
|
|
20607
20564
|
}
|
|
20608
20565
|
|
|
20566
|
+
/**
|
|
20567
|
+
* Writes nextValue to the trigger input and the bib input when their current
|
|
20568
|
+
* value differs, then re-asserts imask after Lit's update flushes.
|
|
20569
|
+
* @param {string} nextValue - The value to write to both inputs.
|
|
20570
|
+
* @returns {Promise<void>} Resolves after both inputs flush and imask
|
|
20571
|
+
* re-asserts; resolves immediately when no sync is needed.
|
|
20572
|
+
* @private
|
|
20573
|
+
*/
|
|
20574
|
+
async syncInputValuesAcrossTriggerAndBib(nextValue) {
|
|
20575
|
+
// Only set the flag when there's an actual write to suppress —
|
|
20576
|
+
// syncValuesAndStates re-enters here during typing when both inputs
|
|
20577
|
+
// already match, and a no-op flag flip would make the bib branch's
|
|
20578
|
+
// bail eat legitimate user-input events.
|
|
20579
|
+
const triggerNeedsSync = this.input.value !== nextValue;
|
|
20580
|
+
const bibNeedsSync = Boolean(this.inputInBib) && this.inputInBib.value !== nextValue;
|
|
20581
|
+
if (!triggerNeedsSync && !bibNeedsSync) {
|
|
20582
|
+
return;
|
|
20583
|
+
}
|
|
20584
|
+
|
|
20585
|
+
this._syncingDisplayValue = true;
|
|
20586
|
+
|
|
20587
|
+
const pending = [];
|
|
20588
|
+
if (triggerNeedsSync) {
|
|
20589
|
+
this.input.value = nextValue;
|
|
20590
|
+
pending.push(this.input.updateComplete);
|
|
20591
|
+
}
|
|
20592
|
+
if (bibNeedsSync) {
|
|
20593
|
+
this.inputInBib.value = nextValue;
|
|
20594
|
+
pending.push(this.inputInBib.updateComplete);
|
|
20595
|
+
}
|
|
20596
|
+
// finally — not a bare .then — so that an imask throw (see commit
|
|
20597
|
+
// d1857401c: imask can throw on credit-card format change) doesn't strand
|
|
20598
|
+
// _syncingDisplayValue=true and silently swallow every subsequent input.
|
|
20599
|
+
try {
|
|
20600
|
+
await Promise.all(pending);
|
|
20601
|
+
// imask reasserts otherwise, and handleBlur reverts to its stale value.
|
|
20602
|
+
if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
|
|
20603
|
+
this.input.maskInstance.updateValue();
|
|
20604
|
+
}
|
|
20605
|
+
if (bibNeedsSync && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
|
|
20606
|
+
this.inputInBib.maskInstance.updateValue();
|
|
20607
|
+
}
|
|
20608
|
+
} finally {
|
|
20609
|
+
this._syncingDisplayValue = false;
|
|
20610
|
+
}
|
|
20611
|
+
}
|
|
20612
|
+
|
|
20609
20613
|
/**
|
|
20610
20614
|
* Processes hidden state of all menu options and determines if there are any available options not hidden.
|
|
20611
20615
|
* @private
|
|
@@ -20614,6 +20618,13 @@ class AuroCombobox extends AuroElement {
|
|
|
20614
20618
|
handleMenuOptions() {
|
|
20615
20619
|
this.generateOptionsArray();
|
|
20616
20620
|
this.availableOptions = [];
|
|
20621
|
+
// Single source of truth for the menu's filter/highlight token per call.
|
|
20622
|
+
// syncValuesAndStates re-writes the same value on exact-match keystrokes —
|
|
20623
|
+
// Lit's hasChanged makes that a no-op — and the prior duplicate set inside
|
|
20624
|
+
// handleTriggerInputValueChange is gone.
|
|
20625
|
+
if (this.menu) {
|
|
20626
|
+
this.menu.matchWord = normalizeFilterValue(this.input.value);
|
|
20627
|
+
}
|
|
20617
20628
|
this.updateFilter();
|
|
20618
20629
|
|
|
20619
20630
|
// Set aria-setsize/aria-posinset on each visible option so screen readers
|
|
@@ -20625,17 +20636,14 @@ class AuroCombobox extends AuroElement {
|
|
|
20625
20636
|
option.setAttribute('aria-posinset', index + 1);
|
|
20626
20637
|
});
|
|
20627
20638
|
|
|
20628
|
-
if (this.value && this.
|
|
20629
|
-
|
|
20630
|
-
this.setMenuValue(this.value);
|
|
20631
|
-
}
|
|
20632
|
-
|
|
20639
|
+
if (this.input.value && this.menu.options && this.menu.options.some((opt) => opt.value === this.input.value)) {
|
|
20640
|
+
this.setMenuValue(this.input.value);
|
|
20633
20641
|
this.syncValuesAndStates();
|
|
20634
20642
|
}
|
|
20635
20643
|
|
|
20636
|
-
// Re-activate when optionActive is
|
|
20637
|
-
// been reset (e.g. by clearSelection in an async update)
|
|
20638
|
-
// makeSelection() can find the correct option.
|
|
20644
|
+
// Re-activate when optionActive is not in the current scrollable viewport,
|
|
20645
|
+
// or when _index has been reset (e.g. by clearSelection in an async update)
|
|
20646
|
+
// so that makeSelection() can find the correct option.
|
|
20639
20647
|
if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
|
|
20640
20648
|
this.activateFirstEnabledAvailableOption();
|
|
20641
20649
|
}
|
|
@@ -20708,18 +20716,6 @@ class AuroCombobox extends AuroElement {
|
|
|
20708
20716
|
this.dropdownOpen = ev.detail.expanded;
|
|
20709
20717
|
this.updateMenuShapeSize();
|
|
20710
20718
|
|
|
20711
|
-
// Defer aria-expanded "true" so VoiceOver finishes character echo
|
|
20712
|
-
// before announcing "expanded". Set "false" immediately on close.
|
|
20713
|
-
clearTimeout(this._expandedTimeout);
|
|
20714
|
-
if (this.dropdownOpen) {
|
|
20715
|
-
const expandedDelay = 150;
|
|
20716
|
-
this._expandedTimeout = setTimeout(() => {
|
|
20717
|
-
this.triggerExpandedState = true;
|
|
20718
|
-
}, expandedDelay);
|
|
20719
|
-
} else {
|
|
20720
|
-
this.triggerExpandedState = false;
|
|
20721
|
-
}
|
|
20722
|
-
|
|
20723
20719
|
// Clear aria-activedescendant when dropdown closes
|
|
20724
20720
|
if (!this.dropdownOpen && this.input) {
|
|
20725
20721
|
this.input.setActiveDescendant(null);
|
|
@@ -20769,28 +20765,25 @@ class AuroCombobox extends AuroElement {
|
|
|
20769
20765
|
|
|
20770
20766
|
guardTouchPassthrough(this.menu);
|
|
20771
20767
|
|
|
20772
|
-
//
|
|
20773
|
-
//
|
|
20774
|
-
//
|
|
20768
|
+
// showModal() takes focus away from the trigger. Early focus once
|
|
20769
|
+
// the dialog has painted; the shared doubleRaf below is the fallback
|
|
20770
|
+
// for when the dialog needs an extra frame and also clears the
|
|
20771
|
+
// validation guard.
|
|
20775
20772
|
requestAnimationFrame(() => {
|
|
20776
20773
|
this.setInputFocus();
|
|
20777
20774
|
});
|
|
20778
|
-
|
|
20779
|
-
doubleRaf(() => {
|
|
20780
|
-
this.setInputFocus();
|
|
20781
|
-
this._inFullscreenTransition = false;
|
|
20782
|
-
});
|
|
20783
|
-
} else {
|
|
20784
|
-
// Desktop popover-open: restore the trigger caret to end-of-text.
|
|
20785
|
-
// Clicking the trigger lands on auro-input's floating <label for="…">
|
|
20786
|
-
// overlay; Chrome resets the native input's selection to [0, 0] on
|
|
20787
|
-
// label-focus before any JS runs. setInputFocus()'s non-fullscreen
|
|
20788
|
-
// branch parks the caret at end. doubleRaf lets the dropdown layout
|
|
20789
|
-
// settle first, matching the fullscreen branch timing.
|
|
20790
|
-
doubleRaf(() => {
|
|
20791
|
-
this.setInputFocus();
|
|
20792
|
-
});
|
|
20793
20775
|
}
|
|
20776
|
+
// else (desktop popover-open): Chrome resets the trigger caret to
|
|
20777
|
+
// [0, 0] when its floating <label for="…"> overlay receives focus.
|
|
20778
|
+
// The shared doubleRaf below parks the caret back at end-of-text
|
|
20779
|
+
// after the dropdown layout settles.
|
|
20780
|
+
|
|
20781
|
+
doubleRaf(() => {
|
|
20782
|
+
this.setInputFocus();
|
|
20783
|
+
if (this._inFullscreenTransition) {
|
|
20784
|
+
this._inFullscreenTransition = false;
|
|
20785
|
+
}
|
|
20786
|
+
});
|
|
20794
20787
|
}
|
|
20795
20788
|
});
|
|
20796
20789
|
|
|
@@ -20827,7 +20820,7 @@ class AuroCombobox extends AuroElement {
|
|
|
20827
20820
|
this.dropdown.trigger.inert = false;
|
|
20828
20821
|
}
|
|
20829
20822
|
|
|
20830
|
-
|
|
20823
|
+
this._scheduleTimer(() => {
|
|
20831
20824
|
this.setInputFocus();
|
|
20832
20825
|
}, 0);
|
|
20833
20826
|
});
|
|
@@ -20971,7 +20964,7 @@ class AuroCombobox extends AuroElement {
|
|
|
20971
20964
|
|
|
20972
20965
|
// racing condition on custom-combobox with custom-menu
|
|
20973
20966
|
if (!this.menu) {
|
|
20974
|
-
|
|
20967
|
+
this._scheduleTimer(() => {
|
|
20975
20968
|
this.configureMenu();
|
|
20976
20969
|
}, 0);
|
|
20977
20970
|
return;
|
|
@@ -21001,7 +20994,7 @@ class AuroCombobox extends AuroElement {
|
|
|
21001
20994
|
if (this.menu.optionSelected) {
|
|
21002
20995
|
const selected = this.menu.optionSelected;
|
|
21003
20996
|
|
|
21004
|
-
if (
|
|
20997
|
+
if (this.optionSelected !== selected) {
|
|
21005
20998
|
this.optionSelected = selected;
|
|
21006
20999
|
}
|
|
21007
21000
|
|
|
@@ -21014,7 +21007,7 @@ class AuroCombobox extends AuroElement {
|
|
|
21014
21007
|
}
|
|
21015
21008
|
|
|
21016
21009
|
// Update display
|
|
21017
|
-
this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected)
|
|
21010
|
+
this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected));
|
|
21018
21011
|
|
|
21019
21012
|
// Update match word for filtering
|
|
21020
21013
|
const trimmedInput = normalizeFilterValue(this.input.value);
|
|
@@ -21026,17 +21019,24 @@ class AuroCombobox extends AuroElement {
|
|
|
21026
21019
|
// Hide dropdown on selection (except during slot changes)
|
|
21027
21020
|
if (evt.detail && evt.detail.source !== 'slotchange') {
|
|
21028
21021
|
// do not close while typing in suggestion mode with no value selected, to allow freeform input
|
|
21029
|
-
|
|
21030
|
-
|
|
21022
|
+
this.hideBib();
|
|
21023
|
+
|
|
21024
|
+
// Move focus to the clear button when the user makes a selection.
|
|
21025
|
+
if (!isEcho && this.menu.value !== undefined) {
|
|
21026
|
+
this.setClearBtnFocus();
|
|
21031
21027
|
}
|
|
21032
21028
|
|
|
21033
21029
|
// Announce the selection after the dropdown closes so it isn't
|
|
21034
21030
|
// overridden by VoiceOver's "collapsed" announcement from aria-expanded.
|
|
21031
|
+
// Skip when there's no selected value (e.g. menu.clearSelection() from
|
|
21032
|
+
// the unmatched-value path), otherwise VoiceOver reads "undefined".
|
|
21035
21033
|
const selectedValue = this.menu.value;
|
|
21036
|
-
|
|
21037
|
-
|
|
21038
|
-
|
|
21039
|
-
|
|
21034
|
+
if (selectedValue) {
|
|
21035
|
+
const announcementDelay = 300;
|
|
21036
|
+
this._scheduleTimer(() => {
|
|
21037
|
+
announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
|
|
21038
|
+
}, announcementDelay);
|
|
21039
|
+
}
|
|
21040
21040
|
}
|
|
21041
21041
|
|
|
21042
21042
|
// Programmatic value syncs leave availableOptions stale because
|
|
@@ -21045,22 +21045,6 @@ class AuroCombobox extends AuroElement {
|
|
|
21045
21045
|
// only — fresh user selections take the existing hideBib path.
|
|
21046
21046
|
if (isEcho && this.menu.optionSelected) {
|
|
21047
21047
|
this._programmaticFilterRefresh = true;
|
|
21048
|
-
this.handleMenuOptions();
|
|
21049
|
-
setTimeout(() => {
|
|
21050
|
-
this._programmaticFilterRefresh = false;
|
|
21051
|
-
}, 0);
|
|
21052
|
-
}
|
|
21053
|
-
|
|
21054
|
-
// base-input skips auto-validate when focus is inside its own shadow,
|
|
21055
|
-
// which it is after setTriggerInputFocus — re-run so prior tooShort
|
|
21056
|
-
// clears. processCreditCard reasserts errorMessage on every render.
|
|
21057
|
-
if (!isEcho && this.menu.optionSelected && this.input.validate) {
|
|
21058
|
-
this.input.updateComplete.then(() => {
|
|
21059
|
-
this.input.validate(true);
|
|
21060
|
-
if (this.input.validity === 'valid') {
|
|
21061
|
-
this.input.errorMessage = '';
|
|
21062
|
-
}
|
|
21063
|
-
});
|
|
21064
21048
|
}
|
|
21065
21049
|
});
|
|
21066
21050
|
|
|
@@ -21073,6 +21057,14 @@ class AuroCombobox extends AuroElement {
|
|
|
21073
21057
|
// stale option. Safe from re-entrancy because any resulting
|
|
21074
21058
|
// input.value changes dispatch isProgrammatic events.
|
|
21075
21059
|
this.menu.addEventListener('auroMenu-selectValueFailure', () => {
|
|
21060
|
+
// Announce the rejection BEFORE we clear `this.value` so the live
|
|
21061
|
+
// region carries the attempted value — without this the bib closes
|
|
21062
|
+
// silently and screen-reader users get no signal that their request
|
|
21063
|
+
// (e.g. a direct setMenuValue() call) was dropped.
|
|
21064
|
+
const attemptedValue = this.value;
|
|
21065
|
+
if (attemptedValue) {
|
|
21066
|
+
announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${attemptedValue}`);
|
|
21067
|
+
}
|
|
21076
21068
|
this.value = undefined;
|
|
21077
21069
|
this.optionSelected = undefined;
|
|
21078
21070
|
});
|
|
@@ -21084,10 +21076,13 @@ class AuroCombobox extends AuroElement {
|
|
|
21084
21076
|
this.input.setActiveDescendant(this.optionActive);
|
|
21085
21077
|
}
|
|
21086
21078
|
|
|
21087
|
-
//
|
|
21088
|
-
//
|
|
21089
|
-
//
|
|
21090
|
-
|
|
21079
|
+
// In fullscreen mode the menu sits inside a nested <dialog> shadow root,
|
|
21080
|
+
// and aria-activedescendant references across that boundary are lost —
|
|
21081
|
+
// VoiceOver/NVDA don't read the active option natively, so we mirror it
|
|
21082
|
+
// into the polite live region. In popover mode aria-activedescendant on
|
|
21083
|
+
// the trigger input is read natively; double-announcing would flood the
|
|
21084
|
+
// queue on arrow-key repeat.
|
|
21085
|
+
if (this.optionActive && this.dropdown.isBibFullscreen) {
|
|
21091
21086
|
const optionText = this.optionActive.textContent.trim();
|
|
21092
21087
|
const selectedState = this.optionActive.hasAttribute('selected') ? ', selected' : ', not selected';
|
|
21093
21088
|
const optionIndex = this.availableOptions.indexOf(this.optionActive) + 1;
|
|
@@ -21120,7 +21115,12 @@ class AuroCombobox extends AuroElement {
|
|
|
21120
21115
|
* Validate every time we remove focus from the combo box.
|
|
21121
21116
|
*/
|
|
21122
21117
|
this.addEventListener('focusout', () => {
|
|
21123
|
-
|
|
21118
|
+
// Skip while the dropdown is open — focus transits out briefly on
|
|
21119
|
+
// mousedown of a menu option (popover top-layer breaks :focus-within),
|
|
21120
|
+
// and validating against the pre-selection value flashes a stale error
|
|
21121
|
+
// between mousedown and mouseup. The next focusout fires after the
|
|
21122
|
+
// dropdown closes and validates against the post-selection value.
|
|
21123
|
+
if (!this.componentHasFocus && !this._inFullscreenTransition && !this.dropdownOpen) {
|
|
21124
21124
|
this.validate();
|
|
21125
21125
|
}
|
|
21126
21126
|
});
|
|
@@ -21183,49 +21183,40 @@ class AuroCombobox extends AuroElement {
|
|
|
21183
21183
|
if (this._syncingDisplayValue) {
|
|
21184
21184
|
return;
|
|
21185
21185
|
}
|
|
21186
|
-
this._syncingBibValue = true;
|
|
21187
|
-
this.input.value = this.inputInBib.value;
|
|
21188
|
-
this.input.updateComplete.then(() => {
|
|
21189
|
-
this._syncingBibValue = false;
|
|
21190
|
-
});
|
|
21191
21186
|
|
|
21192
|
-
//
|
|
21193
|
-
|
|
21194
|
-
|
|
21195
|
-
|
|
21196
|
-
|
|
21197
|
-
|
|
21198
|
-
|
|
21199
|
-
|
|
21200
|
-
|
|
21187
|
+
// Filtering runs via re-entrance: writing this.input.value below
|
|
21188
|
+
// dispatches an 'input' event that the trigger's listener routes to
|
|
21189
|
+
// handleTriggerInputValueChange, which refreshes the menu filter.
|
|
21190
|
+
if (this.input.value !== this.inputInBib.value) {
|
|
21191
|
+
this._syncingBibValue = true;
|
|
21192
|
+
this.input.value = this.inputInBib.value;
|
|
21193
|
+
this.input.updateComplete.then(() => {
|
|
21194
|
+
this._syncingBibValue = false;
|
|
21195
|
+
});
|
|
21201
21196
|
}
|
|
21202
21197
|
|
|
21203
|
-
this.handleMenuOptions();
|
|
21204
21198
|
this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
|
|
21205
|
-
|
|
21199
|
+
} else if (event.target === this.input) {
|
|
21200
|
+
|
|
21201
|
+
// Also sync the native input immediately so keystrokes arriving
|
|
21202
|
+
// before Lit's async update cycle (e.g. rapid Backspaces during a
|
|
21203
|
+
// fullscreen transition) operate on the correct content.
|
|
21204
|
+
// Skip the next programmatic input event to prevent the patched setter
|
|
21205
|
+
// from re-entering handleInputValueChange via the bib path.
|
|
21206
|
+
const bibNativeInput = this.inputInBib.inputElement;
|
|
21207
|
+
if (bibNativeInput && bibNativeInput.value !== this.input.value) {
|
|
21208
|
+
this.inputInBib.skipNextProgrammaticInputEvent = true;
|
|
21209
|
+
bibNativeInput.value = this.input.value || '';
|
|
21210
|
+
}
|
|
21206
21211
|
}
|
|
21207
21212
|
|
|
21213
|
+
this.value = this.input.value;
|
|
21214
|
+
|
|
21208
21215
|
// Ignore re-entrant input events caused by programmatic value sets.
|
|
21209
21216
|
if (this._syncingBibValue || this._syncingDisplayValue) {
|
|
21210
21217
|
return;
|
|
21211
21218
|
}
|
|
21212
21219
|
|
|
21213
|
-
this.inputInBib.value = this.input.value;
|
|
21214
|
-
|
|
21215
|
-
// Also sync the native input immediately so keystrokes arriving
|
|
21216
|
-
// before Lit's async update cycle (e.g. rapid Backspaces during a
|
|
21217
|
-
// fullscreen transition) operate on the correct content.
|
|
21218
|
-
// Skip the next programmatic input event to prevent the patched setter
|
|
21219
|
-
// from re-entering handleInputValueChange via the bib path.
|
|
21220
|
-
const bibNativeInput = this.inputInBib.inputElement;
|
|
21221
|
-
if (bibNativeInput && bibNativeInput.value !== this.input.value) {
|
|
21222
|
-
this.inputInBib.skipNextProgrammaticInputEvent = true;
|
|
21223
|
-
bibNativeInput.value = this.input.value || '';
|
|
21224
|
-
}
|
|
21225
|
-
|
|
21226
|
-
this.menu.matchWord = normalizeFilterValue(this.input.value);
|
|
21227
|
-
this.optionActive = null;
|
|
21228
|
-
|
|
21229
21220
|
if (this.behavior === 'suggestion') {
|
|
21230
21221
|
this.value = this.input.value;
|
|
21231
21222
|
}
|
|
@@ -21233,7 +21224,6 @@ class AuroCombobox extends AuroElement {
|
|
|
21233
21224
|
if (!this.input.value && !this._clearing) {
|
|
21234
21225
|
this.clear();
|
|
21235
21226
|
}
|
|
21236
|
-
this.handleMenuOptions();
|
|
21237
21227
|
|
|
21238
21228
|
// Validate only if the value was set programmatically (not during user
|
|
21239
21229
|
// interaction). In fullscreen dialog mode, componentHasFocus returns false
|
|
@@ -21243,34 +21233,44 @@ class AuroCombobox extends AuroElement {
|
|
|
21243
21233
|
this.validate();
|
|
21244
21234
|
}
|
|
21245
21235
|
|
|
21246
|
-
|
|
21247
|
-
|
|
21248
|
-
|
|
21249
|
-
|
|
21250
|
-
|
|
21251
|
-
|
|
21252
|
-
|
|
21253
|
-
|
|
21254
|
-
|
|
21236
|
+
this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
|
|
21237
|
+
}
|
|
21238
|
+
|
|
21239
|
+
/**
|
|
21240
|
+
* Handles input value changes originating from the trigger input.
|
|
21241
|
+
* Refreshes menu options and filtering, delegates to handleInputValueChange
|
|
21242
|
+
* for value synchronization, and manages fullscreen bib focus.
|
|
21243
|
+
* @private
|
|
21244
|
+
* @param {Event} event - The input event from the trigger input element.
|
|
21245
|
+
* @returns {void}
|
|
21246
|
+
*/
|
|
21247
|
+
handleTriggerInputValueChange(event) {
|
|
21248
|
+
// 'input' fires for every user-initiated value change — typing, paste,
|
|
21249
|
+
// IME composition end, dead-key composition, drag-drop. Flip _userTyped
|
|
21250
|
+
// here so updated('availableOptions') auto-opens the bib for sources
|
|
21251
|
+
// that keydown alone misses: paste fires no keydown, IME uses
|
|
21252
|
+
// key='Process', and dead keys produce multi-char keys (all bypass the
|
|
21253
|
+
// prior keydown.key.length===1 gate). Skip programmatic syncs.
|
|
21254
|
+
if (!this._syncingDisplayValue && !this._syncingBibValue && !this._programmaticFilterRefresh) {
|
|
21255
|
+
this._userTyped = true;
|
|
21255
21256
|
}
|
|
21256
21257
|
|
|
21257
|
-
|
|
21258
|
-
|
|
21259
|
-
|
|
21260
|
-
|
|
21261
|
-
|
|
21262
|
-
// dialog opens — the user then has to tap the input again to resume
|
|
21263
|
-
// typing.
|
|
21264
|
-
if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0) {
|
|
21265
|
-
if (!this.dropdown.isPopoverVisible) {
|
|
21266
|
-
this.showBib();
|
|
21267
|
-
}
|
|
21268
|
-
if (this.dropdown.isPopoverVisible) {
|
|
21269
|
-
this.setInputFocus();
|
|
21270
|
-
}
|
|
21258
|
+
this.handleMenuOptions();
|
|
21259
|
+
this.optionActive = null;
|
|
21260
|
+
|
|
21261
|
+
if (this.value === this.input.value) {
|
|
21262
|
+
return;
|
|
21271
21263
|
}
|
|
21272
21264
|
|
|
21273
|
-
this.
|
|
21265
|
+
this.handleInputValueChange(event);
|
|
21266
|
+
|
|
21267
|
+
if (this.dropdown.isBibFullscreen && this.input.value && this.input.value.length > 0 && this.dropdown.isPopoverVisible) {
|
|
21268
|
+
this.setInputFocus();
|
|
21269
|
+
}
|
|
21270
|
+
|
|
21271
|
+
if (this._programmaticFilterRefresh) {
|
|
21272
|
+
this._programmaticFilterRefresh = false;
|
|
21273
|
+
}
|
|
21274
21274
|
}
|
|
21275
21275
|
|
|
21276
21276
|
/**
|
|
@@ -21338,6 +21338,11 @@ class AuroCombobox extends AuroElement {
|
|
|
21338
21338
|
disconnectedCallback() {
|
|
21339
21339
|
super.disconnectedCallback();
|
|
21340
21340
|
this._inFullscreenTransition = false;
|
|
21341
|
+
// Cancel any outstanding timers so detached callbacks don't fire on
|
|
21342
|
+
// disposed DOM — most are no-ops, but configureMenu's racing-condition
|
|
21343
|
+
// retry would otherwise keep rescheduling itself indefinitely.
|
|
21344
|
+
this._pendingTimers.forEach((id) => clearTimeout(id));
|
|
21345
|
+
this._pendingTimers.clear();
|
|
21341
21346
|
}
|
|
21342
21347
|
|
|
21343
21348
|
firstUpdated() {
|
|
@@ -21379,7 +21384,7 @@ class AuroCombobox extends AuroElement {
|
|
|
21379
21384
|
this.menu.value = value;
|
|
21380
21385
|
// Backup clear: if menu.value === menu.optionSelected.value already, the
|
|
21381
21386
|
// listener won't fire and the flag would swallow the next user click.
|
|
21382
|
-
|
|
21387
|
+
this._scheduleTimer(() => {
|
|
21383
21388
|
this._pendingMenuValueSync = false;
|
|
21384
21389
|
}, 0);
|
|
21385
21390
|
}
|
|
@@ -21396,15 +21401,7 @@ class AuroCombobox extends AuroElement {
|
|
|
21396
21401
|
this.menu.value = undefined;
|
|
21397
21402
|
this.validation.reset(this);
|
|
21398
21403
|
this.touched = false;
|
|
21399
|
-
// Force validity back to the cleared state. validation.reset() calls
|
|
21400
|
-
// validate() at the end, and (post-credit-card-fix) the trigger input's
|
|
21401
|
-
// residual validity may still be copied into the combobox by the
|
|
21402
|
-
// auroInputElements loop. Explicitly clear here so reset() actually
|
|
21403
|
-
// leaves the component in a "no validity" state.
|
|
21404
21404
|
this.validity = undefined;
|
|
21405
|
-
this.errorMessage = '';
|
|
21406
|
-
this.input.validity = undefined;
|
|
21407
|
-
this.input.errorMessage = '';
|
|
21408
21405
|
}
|
|
21409
21406
|
|
|
21410
21407
|
/**
|
|
@@ -21422,6 +21419,17 @@ class AuroCombobox extends AuroElement {
|
|
|
21422
21419
|
this.optionSelected = undefined;
|
|
21423
21420
|
this.value = undefined;
|
|
21424
21421
|
|
|
21422
|
+
// Clear the appended displayValue clone in the trigger if present.
|
|
21423
|
+
// :not(slot) excludes the template's <slot name="displayValue"
|
|
21424
|
+
// slot="displayValue"> forwarder (line 1816), which also has
|
|
21425
|
+
// slot="displayValue" and would otherwise be matched first and removed,
|
|
21426
|
+
// permanently breaking the consumer-provided displayValue slot.
|
|
21427
|
+
const displayValueInTrigger = this.input.querySelector('[slot="displayValue"]:not(slot)');
|
|
21428
|
+
|
|
21429
|
+
if (displayValueInTrigger) {
|
|
21430
|
+
displayValueInTrigger.remove();
|
|
21431
|
+
}
|
|
21432
|
+
|
|
21425
21433
|
if (this.input.value) {
|
|
21426
21434
|
this.input.clear();
|
|
21427
21435
|
}
|
|
@@ -21445,81 +21453,50 @@ class AuroCombobox extends AuroElement {
|
|
|
21445
21453
|
}
|
|
21446
21454
|
|
|
21447
21455
|
updated(changedProperties) {
|
|
21448
|
-
// After the component is ready,
|
|
21456
|
+
// After the component is ready, propagate direct changes down to child components.
|
|
21449
21457
|
if (changedProperties.has('value')) {
|
|
21450
|
-
|
|
21451
|
-
|
|
21452
|
-
|
|
21453
|
-
|
|
21458
|
+
// Only flag programmatic refreshes — user-typed value changes must not
|
|
21459
|
+
// suppress the availableOptions branch's showBib(). Firefox batches
|
|
21460
|
+
// 'value' and 'availableOptions' into the same updated() call, so
|
|
21461
|
+
// setting the flag unconditionally here masks the user-typed open path.
|
|
21462
|
+
if (!this._userTyped) {
|
|
21463
|
+
this._programmaticFilterRefresh = true;
|
|
21454
21464
|
}
|
|
21455
21465
|
|
|
21456
|
-
if (this.
|
|
21457
|
-
|
|
21458
|
-
|
|
21466
|
+
if (this.input.value !== this.value) {
|
|
21467
|
+
// Clear menu.value AND menu.optionSelected together. Clearing only
|
|
21468
|
+
// menu.value leaves the previously-selected option element pinned
|
|
21469
|
+
// as menu.optionSelected; a later auroMenu-selectedOption event
|
|
21470
|
+
// would then write its stale .value back into combobox.value
|
|
21471
|
+
// (e.g. Tab-after-Backspace re-selecting the prior option).
|
|
21472
|
+
if (this.menu.value || this.menu.optionSelected) {
|
|
21473
|
+
this.menu.clearSelection();
|
|
21474
|
+
}
|
|
21459
21475
|
|
|
21460
|
-
|
|
21461
|
-
|
|
21462
|
-
|
|
21463
|
-
|
|
21464
|
-
|
|
21465
|
-
|
|
21466
|
-
|
|
21467
|
-
|
|
21468
|
-
|
|
21469
|
-
|
|
21470
|
-
|
|
21471
|
-
|
|
21472
|
-
|
|
21473
|
-
|
|
21474
|
-
|
|
21475
|
-
|
|
21476
|
-
|
|
21477
|
-
|
|
21478
|
-
// matches to avoid spurious events during the normal typing path.
|
|
21479
|
-
const nextValue = this.value || '';
|
|
21480
|
-
const triggerNeedsSync = this.input.value !== nextValue;
|
|
21481
|
-
const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
|
|
21482
|
-
if (triggerNeedsSync || bibNeedsSync) {
|
|
21483
|
-
this._syncingDisplayValue = true;
|
|
21484
|
-
if (triggerNeedsSync) {
|
|
21485
|
-
this.input.value = nextValue;
|
|
21486
|
-
}
|
|
21487
|
-
if (bibNeedsSync) {
|
|
21488
|
-
this.inputInBib.value = nextValue;
|
|
21489
|
-
}
|
|
21490
|
-
const pending = [];
|
|
21491
|
-
if (triggerNeedsSync) {
|
|
21492
|
-
pending.push(this.input.updateComplete);
|
|
21493
|
-
}
|
|
21494
|
-
if (bibNeedsSync) {
|
|
21495
|
-
pending.push(this.inputInBib.updateComplete);
|
|
21496
|
-
}
|
|
21497
|
-
Promise.all(pending).then(() => {
|
|
21498
|
-
if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
|
|
21499
|
-
this.input.maskInstance.updateValue();
|
|
21500
|
-
}
|
|
21501
|
-
if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
|
|
21502
|
-
this.inputInBib.maskInstance.updateValue();
|
|
21503
|
-
}
|
|
21504
|
-
this._syncingDisplayValue = false;
|
|
21505
|
-
// handleInputValueChange bailed on the flag — refresh the filter.
|
|
21506
|
-
this._programmaticFilterRefresh = true;
|
|
21507
|
-
this.handleMenuOptions();
|
|
21508
|
-
setTimeout(() => {
|
|
21509
|
-
this._programmaticFilterRefresh = false;
|
|
21510
|
-
}, 0);
|
|
21511
|
-
});
|
|
21512
|
-
}
|
|
21476
|
+
if (!this.persistInput) {
|
|
21477
|
+
this.syncInputValuesAcrossTriggerAndBib(this.value || '');
|
|
21478
|
+
}
|
|
21479
|
+
|
|
21480
|
+
// Programmatic value with no matching option: updateFilter will close
|
|
21481
|
+
// the bib silently (see line 648 — no noMatchOption + 0 results
|
|
21482
|
+
// hides). Announce so screen-reader users hear the request was
|
|
21483
|
+
// dropped. Gated on `input.value !== this.value` so this never fires
|
|
21484
|
+
// for user typing — that path always reconciles input.value to
|
|
21485
|
+
// this.value before updated() runs.
|
|
21486
|
+
if (
|
|
21487
|
+
this.value &&
|
|
21488
|
+
this.menu &&
|
|
21489
|
+
this.menu.options &&
|
|
21490
|
+
this.menu.options.length > 0 &&
|
|
21491
|
+
!this.menu.options.some((opt) => opt.value === this.value)
|
|
21492
|
+
) {
|
|
21493
|
+
announceToScreenReader(this._getAnnouncementRoot(), `No matching option for ${this.value}`);
|
|
21513
21494
|
}
|
|
21514
21495
|
}
|
|
21496
|
+
|
|
21515
21497
|
if (!this.value) {
|
|
21516
21498
|
this.clear();
|
|
21517
21499
|
}
|
|
21518
|
-
if (this.value && !this.componentHasFocus) {
|
|
21519
|
-
// If the value got set programmatically make sure we hide the bib
|
|
21520
|
-
// when input is not taking the focus (input can be in dropdown.trigger or in bibtemplate)
|
|
21521
|
-
this.hideBib();
|
|
21522
|
-
}
|
|
21523
21500
|
|
|
21524
21501
|
// Sync the input and match word, but don't directly set menu.value again
|
|
21525
21502
|
if (this.menu) {
|
|
@@ -21532,7 +21509,7 @@ class AuroCombobox extends AuroElement {
|
|
|
21532
21509
|
composed: true,
|
|
21533
21510
|
detail: {
|
|
21534
21511
|
optionSelected: this.menu.optionSelected,
|
|
21535
|
-
value: this.
|
|
21512
|
+
value: this.value
|
|
21536
21513
|
}
|
|
21537
21514
|
}));
|
|
21538
21515
|
|
|
@@ -21555,16 +21532,30 @@ class AuroCombobox extends AuroElement {
|
|
|
21555
21532
|
// from a programmatic filter refresh after a value swap — the user
|
|
21556
21533
|
// didn't interact, so we shouldn't auto-open the bib (especially for
|
|
21557
21534
|
// the no-match path where the existing condition unconditionally fires).
|
|
21558
|
-
if (this._programmaticFilterRefresh) {
|
|
21559
|
-
|
|
21560
|
-
|
|
21561
|
-
|
|
21535
|
+
if ((this.menu && !this._programmaticFilterRefresh)) {
|
|
21536
|
+
if (
|
|
21537
|
+
this.availableOptions.length > 0 ||
|
|
21538
|
+
this.menu.loading ||
|
|
21539
|
+
this.noMatchOption
|
|
21540
|
+
) {
|
|
21541
|
+
if (this._userTyped) {
|
|
21542
|
+
if (!this.dropdownOpen) {
|
|
21543
|
+
this.showBib();
|
|
21544
|
+
}
|
|
21545
|
+
this._userTyped = false;
|
|
21546
|
+
}
|
|
21547
|
+
}
|
|
21548
|
+
|
|
21562
21549
|
if (!this.availableOptions.includes(this.menu.optionActive)) {
|
|
21563
21550
|
this.activateFirstEnabledAvailableOption();
|
|
21564
21551
|
}
|
|
21565
|
-
} else if (this.dropdown
|
|
21552
|
+
} else if (!this.dropdown.isBibFullscreen) {
|
|
21566
21553
|
this.hideBib();
|
|
21567
21554
|
}
|
|
21555
|
+
|
|
21556
|
+
if (this._programmaticFilterRefresh) {
|
|
21557
|
+
this._programmaticFilterRefresh = false;
|
|
21558
|
+
}
|
|
21568
21559
|
}
|
|
21569
21560
|
|
|
21570
21561
|
if (changedProperties.has('error')) {
|
|
@@ -21706,10 +21697,10 @@ class AuroCombobox extends AuroElement {
|
|
|
21706
21697
|
shape="${this.shape}"
|
|
21707
21698
|
size="${this.size}">
|
|
21708
21699
|
<${this.inputTag}
|
|
21709
|
-
@input="${this.
|
|
21700
|
+
@input="${this.handleTriggerInputValueChange}"
|
|
21710
21701
|
appearance="${this.onDark ? 'inverse' : this.appearance}"
|
|
21711
21702
|
.a11yActivedescendant="${this.dropdownOpen && this.optionActive ? this.optionActive.id : undefined}"
|
|
21712
|
-
.a11yExpanded="${this.
|
|
21703
|
+
.a11yExpanded="${this.dropdownOpen}"
|
|
21713
21704
|
.a11yControls="${this.dropdownId}"
|
|
21714
21705
|
.autocomplete="${this.autocomplete}"
|
|
21715
21706
|
.inputmode="${this.inputmode}"
|