@aurodesignsystem-dev/auro-formkit 0.0.0-pr1503.2 → 0.0.0-pr1505.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 +2 -2
- package/components/checkbox/demo/getting-started.min.js +2 -2
- package/components/checkbox/demo/index.min.js +2 -2
- package/components/checkbox/dist/index.js +2 -2
- package/components/checkbox/dist/registered.js +2 -2
- package/components/combobox/demo/customize.md +7 -3
- package/components/combobox/demo/customize.min.js +1450 -1662
- package/components/combobox/demo/getting-started.min.js +1450 -1662
- package/components/combobox/demo/index.min.js +1450 -1662
- package/components/combobox/dist/auro-combobox.d.ts +5 -0
- package/components/combobox/dist/index.js +504 -141
- package/components/combobox/dist/registered.js +504 -141
- package/components/counter/demo/customize.min.js +3 -3
- package/components/counter/demo/index.min.js +3 -3
- package/components/counter/dist/index.js +3 -3
- package/components/counter/dist/registered.js +3 -3
- package/components/datepicker/demo/api.md +2 -0
- package/components/datepicker/demo/customize.md +195 -33
- package/components/datepicker/demo/customize.min.js +145 -60
- package/components/datepicker/demo/index.min.js +137 -59
- package/components/datepicker/dist/index.js +137 -59
- package/components/datepicker/dist/registered.js +137 -59
- package/components/datepicker/dist/src/auro-datepicker.d.ts +2 -0
- package/components/dropdown/demo/customize.min.js +1 -1
- package/components/dropdown/demo/getting-started.min.js +1 -1
- package/components/dropdown/demo/index.min.js +1 -1
- package/components/dropdown/dist/index.js +1 -1
- package/components/dropdown/dist/registered.js +1 -1
- package/components/form/demo/customize.min.js +2209 -2237
- package/components/form/demo/getting-started.min.js +2209 -2237
- package/components/form/demo/index.min.js +2209 -2237
- package/components/form/demo/registerDemoDeps.min.js +2207 -2235
- package/components/input/demo/customize.md +56 -55
- package/components/input/demo/customize.min.js +131 -56
- package/components/input/demo/getting-started.min.js +131 -56
- package/components/input/demo/index.md +2 -2
- package/components/input/demo/index.min.js +131 -56
- package/components/input/dist/base-input.d.ts +16 -0
- package/components/input/dist/index.js +131 -56
- package/components/input/dist/registered.js +131 -56
- package/components/menu/demo/api.md +41 -45
- package/components/menu/demo/customize.md +0 -28
- package/components/menu/demo/index.min.js +779 -1354
- package/components/menu/dist/auro-menu.d.ts +95 -110
- package/components/menu/dist/auro-menuoption.d.ts +32 -138
- package/components/menu/dist/index.js +753 -1308
- package/components/menu/dist/registered.js +765 -1308
- package/components/radio/demo/customize.min.js +2 -2
- package/components/radio/demo/getting-started.min.js +2 -2
- package/components/radio/demo/index.min.js +2 -2
- package/components/radio/dist/index.js +2 -2
- package/components/radio/dist/registered.js +2 -2
- package/components/select/demo/customize.md +7 -3
- package/components/select/demo/customize.min.js +1072 -1616
- package/components/select/demo/getting-started.min.js +1072 -1616
- package/components/select/demo/index.min.js +1072 -1616
- package/components/select/dist/index.js +46 -15
- package/components/select/dist/registered.js +46 -15
- package/components/select/dist/selectUtils.d.ts +12 -0
- package/custom-elements.json +384 -1022
- package/package.json +1 -3
- package/components/menu/dist/auro-menu.context.d.ts +0 -238
|
@@ -555,7 +555,7 @@ let AuroFormValidation$1 = class AuroFormValidation {
|
|
|
555
555
|
} else if (this.runtimeUtils.elementMatch(elem, 'auro-input') && elem.errorMessage === '') {
|
|
556
556
|
const input = elem.renderRoot.querySelector('input');
|
|
557
557
|
|
|
558
|
-
if (input.validationMessage.length > 0) {
|
|
558
|
+
if (input && input.validationMessage.length > 0) {
|
|
559
559
|
elem.errorMessage = input.validationMessage;
|
|
560
560
|
}
|
|
561
561
|
} else if (this.runtimeUtils.elementMatch(elem, 'auro-combobox') && elem.errorMessage === '') {
|
|
@@ -797,6 +797,21 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
|
|
|
797
797
|
return isFocused;
|
|
798
798
|
}
|
|
799
799
|
|
|
800
|
+
/**
|
|
801
|
+
* Reconcile the menu `_index` from `optionActive` so subsequent `makeSelection` calls find the correct option after async clearSelection has reset it.
|
|
802
|
+
* @param {Object} menu - The menu component.
|
|
803
|
+
*/
|
|
804
|
+
function reconcileMenuIndex(menu) {
|
|
805
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
806
|
+
if (menu._index < 0 && menu.optionActive && menu.items) {
|
|
807
|
+
const idx = menu.items.indexOf(menu.optionActive);
|
|
808
|
+
if (idx >= 0) {
|
|
809
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
810
|
+
menu._index = idx;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
800
815
|
const comboboxKeyboardStrategy = {
|
|
801
816
|
ArrowDown(component, evt, ctx) {
|
|
802
817
|
// If the clear button has focus, let the browser handle ArrowDown normally.
|
|
@@ -860,6 +875,7 @@ const comboboxKeyboardStrategy = {
|
|
|
860
875
|
// block the browser's built-in "Enter activates focused button" behavior.
|
|
861
876
|
evt.stopPropagation();
|
|
862
877
|
} else if (ctx.isExpanded && component.menu.optionActive) {
|
|
878
|
+
reconcileMenuIndex(component.menu);
|
|
863
879
|
component.menu.makeSelection();
|
|
864
880
|
|
|
865
881
|
if (ctx.isModal) {
|
|
@@ -907,6 +923,7 @@ const comboboxKeyboardStrategy = {
|
|
|
907
923
|
// When the clear button is focused, Tab events do not bubble out of
|
|
908
924
|
// its shadow DOM, so this handler only fires when the clear button
|
|
909
925
|
// is NOT focused. In that case, select the active option and close.
|
|
926
|
+
reconcileMenuIndex(component.menu);
|
|
910
927
|
component.menu.makeSelection();
|
|
911
928
|
component.hideBib();
|
|
912
929
|
|
|
@@ -4789,7 +4806,7 @@ let AuroHelpText$2 = class AuroHelpText extends LitElement {
|
|
|
4789
4806
|
}
|
|
4790
4807
|
};
|
|
4791
4808
|
|
|
4792
|
-
var formkitVersion$2 = '
|
|
4809
|
+
var formkitVersion$2 = '202606171946';
|
|
4793
4810
|
|
|
4794
4811
|
let AuroElement$2 = class AuroElement extends LitElement {
|
|
4795
4812
|
static get properties() {
|
|
@@ -10662,7 +10679,7 @@ class AuroFormValidation {
|
|
|
10662
10679
|
} else if (this.runtimeUtils.elementMatch(elem, 'auro-input') && elem.errorMessage === '') {
|
|
10663
10680
|
const input = elem.renderRoot.querySelector('input');
|
|
10664
10681
|
|
|
10665
|
-
if (input.validationMessage.length > 0) {
|
|
10682
|
+
if (input && input.validationMessage.length > 0) {
|
|
10666
10683
|
elem.errorMessage = input.validationMessage;
|
|
10667
10684
|
}
|
|
10668
10685
|
} else if (this.runtimeUtils.elementMatch(elem, 'auro-combobox') && elem.errorMessage === '') {
|
|
@@ -16503,7 +16520,6 @@ class AuroInputUtilities {
|
|
|
16503
16520
|
const maskOptions = this.getMaskOptions('date', normalizedFormat);
|
|
16504
16521
|
|
|
16505
16522
|
if (!(valueObject instanceof Date) || Number.isNaN(valueObject.getTime()) || !maskOptions || typeof maskOptions.format !== 'function') {
|
|
16506
|
-
console.debug('Invalid date object or mask options for formatting', { valueObject, maskOptions }); // eslint-disable-line no-console
|
|
16507
16523
|
return undefined;
|
|
16508
16524
|
}
|
|
16509
16525
|
|
|
@@ -17206,7 +17222,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
17206
17222
|
* @returns {Date|undefined}
|
|
17207
17223
|
*/
|
|
17208
17224
|
get valueObject() {
|
|
17209
|
-
return this._valueObject;
|
|
17225
|
+
return this._valueObject || this._computeDateObjectFallback(this.value);
|
|
17210
17226
|
}
|
|
17211
17227
|
|
|
17212
17228
|
/**
|
|
@@ -17214,7 +17230,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
17214
17230
|
* @returns {Date|undefined}
|
|
17215
17231
|
*/
|
|
17216
17232
|
get minObject() {
|
|
17217
|
-
return this._minObject;
|
|
17233
|
+
return this._minObject || this._computeDateObjectFallback(this.min);
|
|
17218
17234
|
}
|
|
17219
17235
|
|
|
17220
17236
|
/**
|
|
@@ -17222,7 +17238,31 @@ class BaseInput extends AuroElement$1 {
|
|
|
17222
17238
|
* @returns {Date|undefined}
|
|
17223
17239
|
*/
|
|
17224
17240
|
get maxObject() {
|
|
17225
|
-
return this._maxObject;
|
|
17241
|
+
return this._maxObject || this._computeDateObjectFallback(this.max);
|
|
17242
|
+
}
|
|
17243
|
+
|
|
17244
|
+
/**
|
|
17245
|
+
* Parses a date string into a Date object when the corresponding `_*Object`
|
|
17246
|
+
* field hasn't been synced yet by `updated()`. Returns undefined when the
|
|
17247
|
+
* input type/format isn't a full date or the string is not a valid date.
|
|
17248
|
+
*
|
|
17249
|
+
* Why this exists: a parent (datepicker) can call `inputN.validate()` from
|
|
17250
|
+
* inside its own `updated()` before this input's `updated()` has run
|
|
17251
|
+
* `syncDateValues()` — so `_valueObject`/`_maxObject` are still `undefined`
|
|
17252
|
+
* and range checks would otherwise silently no-op (flipping the result to
|
|
17253
|
+
* `valid` or `patternMismatch`).
|
|
17254
|
+
* @private
|
|
17255
|
+
* @param {string|undefined} dateStr - ISO date string from `value`/`min`/`max`.
|
|
17256
|
+
* @returns {Date|undefined}
|
|
17257
|
+
*/
|
|
17258
|
+
_computeDateObjectFallback(dateStr) {
|
|
17259
|
+
if (!dateStr || !this.util || !this.util.isFullDateFormat(this.type, this.format)) {
|
|
17260
|
+
return undefined;
|
|
17261
|
+
}
|
|
17262
|
+
if (!dateFormatter.isValidDate(dateStr)) {
|
|
17263
|
+
return undefined;
|
|
17264
|
+
}
|
|
17265
|
+
return dateFormatter.stringToDateInstance(dateStr);
|
|
17226
17266
|
}
|
|
17227
17267
|
|
|
17228
17268
|
/**
|
|
@@ -17247,8 +17287,30 @@ class BaseInput extends AuroElement$1 {
|
|
|
17247
17287
|
connectedCallback() {
|
|
17248
17288
|
super.connectedCallback();
|
|
17249
17289
|
|
|
17290
|
+
// Mark for query selectors when registered under a versioned tag (e.g. inside
|
|
17291
|
+
// a datepicker/combobox). Must run before parent components call validate() on
|
|
17292
|
+
// their inner inputs — the validation framework matches via tag-or-attribute,
|
|
17293
|
+
// and parents trigger validation during their own updated() cycle, which can
|
|
17294
|
+
// precede this input's firstUpdated().
|
|
17295
|
+
if (this.tagName.toLowerCase() !== 'auro-input' && !this.hasAttribute('auro-input')) {
|
|
17296
|
+
this.setAttribute('auro-input', '');
|
|
17297
|
+
}
|
|
17298
|
+
|
|
17250
17299
|
this.locale = this.domHandler.getLocale(this);
|
|
17251
17300
|
notifyOnLangChange(this);
|
|
17301
|
+
|
|
17302
|
+
// Pre-compute lengthForType and date-object fields so a parent (e.g. datepicker)
|
|
17303
|
+
// calling our validate() synchronously during its own updated() cycle sees
|
|
17304
|
+
// populated values. Without this, range validation silently no-ops because
|
|
17305
|
+
// `max.length === lengthForType` fails when lengthForType is still undefined.
|
|
17306
|
+
// Rebuild util here (constructor seeded with en-US default) so configureDataForType's
|
|
17307
|
+
// locale-derived format lookup uses the actual locale just resolved above.
|
|
17308
|
+
this.util = new AuroInputUtilities({
|
|
17309
|
+
locale: this.locale,
|
|
17310
|
+
format: this.format
|
|
17311
|
+
});
|
|
17312
|
+
this.configureDataForType();
|
|
17313
|
+
this.syncDateValues();
|
|
17252
17314
|
}
|
|
17253
17315
|
|
|
17254
17316
|
disconnectedCallback() {
|
|
@@ -17269,8 +17331,8 @@ class BaseInput extends AuroElement$1 {
|
|
|
17269
17331
|
}
|
|
17270
17332
|
|
|
17271
17333
|
// add attribute for query selectors when auro-input is registered under a custom name
|
|
17272
|
-
if (this.tagName.toLowerCase() !== 'auro-input') {
|
|
17273
|
-
this.setAttribute('auro-input',
|
|
17334
|
+
if (this.tagName.toLowerCase() !== 'auro-input' && !this.hasAttribute('auro-input')) {
|
|
17335
|
+
this.setAttribute('auro-input', '');
|
|
17274
17336
|
}
|
|
17275
17337
|
this.inputId = this.id ? `${this.id}-input` : window.crypto.randomUUID();
|
|
17276
17338
|
|
|
@@ -17318,6 +17380,12 @@ class BaseInput extends AuroElement$1 {
|
|
|
17318
17380
|
return;
|
|
17319
17381
|
}
|
|
17320
17382
|
|
|
17383
|
+
// While configureAutoFormatting is running, imask's internal updateControl
|
|
17384
|
+
// writes el.value to align display with the masked value. A synthetic input
|
|
17385
|
+
// event from that write would re-enter handleInput → processCreditCard mid-setup
|
|
17386
|
+
// and clobber a Lit value that was just pushed by the parent combobox.
|
|
17387
|
+
if (component._configuringMask) return;
|
|
17388
|
+
|
|
17321
17389
|
// If all guard clauses are passed, dispatch the event
|
|
17322
17390
|
const inputEvent = new InputEvent('input', {
|
|
17323
17391
|
bubbles: true,
|
|
@@ -17436,7 +17504,16 @@ class BaseInput extends AuroElement$1 {
|
|
|
17436
17504
|
|
|
17437
17505
|
if (formattedValue !== this.inputElement.value) {
|
|
17438
17506
|
this.skipNextProgrammaticInputEvent = true;
|
|
17439
|
-
if (
|
|
17507
|
+
if (this.maskInstance && this.type === 'credit-card') {
|
|
17508
|
+
// Route through the mask so its _value and el.value stay in lock-step
|
|
17509
|
+
// (set value calls updateControl which writes el.value = displayValue).
|
|
17510
|
+
// Writing el.value directly leaves the mask thinking displayValue is
|
|
17511
|
+
// stale; _saveSelection on the next focus/click then warns. Scoped to
|
|
17512
|
+
// credit-card so date's own formattedValue (raw ISO when calendar-invalid)
|
|
17513
|
+
// isn't re-masked through the mm/dd/yyyy mask, which would truncate it
|
|
17514
|
+
// and flip validity from patternMismatch to tooShort.
|
|
17515
|
+
this.maskInstance.value = formattedValue || '';
|
|
17516
|
+
} else if (formattedValue) {
|
|
17440
17517
|
this.inputElement.value = formattedValue;
|
|
17441
17518
|
} else {
|
|
17442
17519
|
this.inputElement.value = '';
|
|
@@ -17545,58 +17622,73 @@ class BaseInput extends AuroElement$1 {
|
|
|
17545
17622
|
* @returns {void}
|
|
17546
17623
|
*/
|
|
17547
17624
|
configureAutoFormatting() {
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
this.
|
|
17625
|
+
// Re-entrancy guard. The patched-setter's synthetic input event (suppressed
|
|
17626
|
+
// by _configuringMask above) could otherwise trigger handleInput →
|
|
17627
|
+
// processCreditCard → configureAutoFormatting before the outer call's
|
|
17628
|
+
// set value has finished its alignCursor pass.
|
|
17629
|
+
if (this._configuringMask) return;
|
|
17630
|
+
this._configuringMask = true;
|
|
17631
|
+
try {
|
|
17632
|
+
if (this.maskInstance) {
|
|
17633
|
+
this.maskInstance.destroy();
|
|
17634
|
+
}
|
|
17554
17635
|
|
|
17555
|
-
|
|
17636
|
+
// Pass new format to util
|
|
17637
|
+
this.util.updateFormat(this.format);
|
|
17556
17638
|
|
|
17557
|
-
|
|
17639
|
+
const maskOptions = this.util.getMaskOptions(this.type, this.format);
|
|
17558
17640
|
|
|
17559
|
-
|
|
17560
|
-
// IMask's constructor processes the current input value which requires
|
|
17561
|
-
// selection state — clearing first avoids that scenario entirely.
|
|
17562
|
-
// When the format changes (e.g. locale switch) and we have a valid ISO
|
|
17563
|
-
// model value, compute the display string for the NEW format instead of
|
|
17564
|
-
// re-using the old display string, which may be invalid in the new mask.
|
|
17565
|
-
let existingValue = this.inputElement.value;
|
|
17566
|
-
if (
|
|
17567
|
-
this.util.isFullDateFormat(this.type, this.format) &&
|
|
17568
|
-
this.value &&
|
|
17569
|
-
dateFormatter.isValidDate(this.value) &&
|
|
17570
|
-
this.valueObject instanceof Date &&
|
|
17571
|
-
!Number.isNaN(this.valueObject.getTime()) &&
|
|
17572
|
-
typeof maskOptions.format === 'function'
|
|
17573
|
-
) {
|
|
17574
|
-
existingValue = maskOptions.format(this.valueObject);
|
|
17575
|
-
}
|
|
17641
|
+
if (this.inputElement && maskOptions.mask) {
|
|
17576
17642
|
|
|
17577
|
-
|
|
17578
|
-
|
|
17643
|
+
// Stash and clear any existing value before IMask init.
|
|
17644
|
+
// IMask's constructor processes the current input value which requires
|
|
17645
|
+
// selection state — clearing first avoids that scenario entirely.
|
|
17646
|
+
// When the format changes (e.g. locale switch) and we have a valid ISO
|
|
17647
|
+
// model value, compute the display string for the NEW format instead of
|
|
17648
|
+
// re-using the old display string, which may be invalid in the new mask.
|
|
17649
|
+
let existingValue = this.inputElement.value;
|
|
17650
|
+
if (
|
|
17651
|
+
this.util.isFullDateFormat(this.type, this.format) &&
|
|
17652
|
+
this.value &&
|
|
17653
|
+
dateFormatter.isValidDate(this.value) &&
|
|
17654
|
+
this.valueObject instanceof Date &&
|
|
17655
|
+
!Number.isNaN(this.valueObject.getTime()) &&
|
|
17656
|
+
typeof maskOptions.format === 'function'
|
|
17657
|
+
) {
|
|
17658
|
+
existingValue = maskOptions.format(this.valueObject);
|
|
17659
|
+
}
|
|
17579
17660
|
|
|
17580
|
-
|
|
17661
|
+
this.skipNextProgrammaticInputEvent = true;
|
|
17662
|
+
this.inputElement.value = '';
|
|
17663
|
+
|
|
17664
|
+
this.maskInstance = IMask(this.inputElement, maskOptions);
|
|
17665
|
+
|
|
17666
|
+
this.maskInstance.on('accept', () => {
|
|
17667
|
+
// Suppress propagation during configureAutoFormatting's own value-restoration
|
|
17668
|
+
// (line below) — the mask emits 'accept' on every value-set, including ours,
|
|
17669
|
+
// and we don't want to overwrite a value the parent just pushed.
|
|
17670
|
+
if (this._configuringMask) return;
|
|
17671
|
+
this.value = this.util.toModelValue(this.maskInstance.value, this.format);
|
|
17672
|
+
if (this.type === "date") {
|
|
17673
|
+
this._rawMaskValue = this.maskInstance.value;
|
|
17674
|
+
}
|
|
17675
|
+
});
|
|
17581
17676
|
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
this.
|
|
17586
|
-
|
|
17587
|
-
|
|
17677
|
+
this.maskInstance.on('complete', () => {
|
|
17678
|
+
if (this._configuringMask) return;
|
|
17679
|
+
this.value = this.util.toModelValue(this.maskInstance.value, this.format);
|
|
17680
|
+
if (this.type === "date") {
|
|
17681
|
+
this._rawMaskValue = this.maskInstance.value;
|
|
17682
|
+
}
|
|
17683
|
+
});
|
|
17588
17684
|
|
|
17589
|
-
|
|
17590
|
-
|
|
17591
|
-
|
|
17592
|
-
this._rawMaskValue = this.maskInstance.value;
|
|
17685
|
+
// Restore the stashed value through IMask so it's properly masked
|
|
17686
|
+
if (existingValue) {
|
|
17687
|
+
this.maskInstance.value = existingValue;
|
|
17593
17688
|
}
|
|
17594
|
-
});
|
|
17595
|
-
|
|
17596
|
-
// Restore the stashed value through IMask so it's properly masked
|
|
17597
|
-
if (existingValue) {
|
|
17598
|
-
this.maskInstance.value = existingValue;
|
|
17599
17689
|
}
|
|
17690
|
+
} finally {
|
|
17691
|
+
this._configuringMask = false;
|
|
17600
17692
|
}
|
|
17601
17693
|
}
|
|
17602
17694
|
|
|
@@ -17883,10 +17975,10 @@ class BaseInput extends AuroElement$1 {
|
|
|
17883
17975
|
},
|
|
17884
17976
|
{
|
|
17885
17977
|
name: 'Diners club',
|
|
17886
|
-
regex: /^(?<num>36|38)\d{0}/u,
|
|
17978
|
+
regex: /^(?<num>30[0-5]|36|38)\d{0}/u,
|
|
17887
17979
|
formatLength: 16,
|
|
17888
17980
|
errorMessage: CreditCardValidationMessage,
|
|
17889
|
-
cardIcon: '
|
|
17981
|
+
cardIcon: 'cc-dinersclub',
|
|
17890
17982
|
maskFormat: "0000 000000 0000"
|
|
17891
17983
|
},
|
|
17892
17984
|
{
|
|
@@ -18296,7 +18388,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
|
|
|
18296
18388
|
}
|
|
18297
18389
|
};
|
|
18298
18390
|
|
|
18299
|
-
var formkitVersion$1 = '
|
|
18391
|
+
var formkitVersion$1 = '202606171946';
|
|
18300
18392
|
|
|
18301
18393
|
// Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
|
|
18302
18394
|
// See LICENSE in the project root for license information.
|
|
@@ -19417,7 +19509,7 @@ class AuroBibtemplate extends LitElement {
|
|
|
19417
19509
|
}
|
|
19418
19510
|
}
|
|
19419
19511
|
|
|
19420
|
-
var formkitVersion = '
|
|
19512
|
+
var formkitVersion = '202606171946';
|
|
19421
19513
|
|
|
19422
19514
|
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}`;
|
|
19423
19515
|
|
|
@@ -19774,6 +19866,25 @@ function normalizeFilterValue(value) {
|
|
|
19774
19866
|
return raw.trim() ? raw.trimEnd() : raw;
|
|
19775
19867
|
}
|
|
19776
19868
|
|
|
19869
|
+
/**
|
|
19870
|
+
* Returns the option's plain-text label suitable for input.value, with
|
|
19871
|
+
* HTML-indentation whitespace collapsed and the displayValue slot text
|
|
19872
|
+
* removed (the slot renders separately in the trigger).
|
|
19873
|
+
* @param {HTMLElement} option - An auro-menuoption element.
|
|
19874
|
+
* @returns {string} Normalized label.
|
|
19875
|
+
*/
|
|
19876
|
+
function getOptionLabel(option) {
|
|
19877
|
+
if (!option) {
|
|
19878
|
+
return '';
|
|
19879
|
+
}
|
|
19880
|
+
const clone = option.cloneNode(true);
|
|
19881
|
+
const displayValueEl = clone.querySelector('[slot="displayValue"]');
|
|
19882
|
+
if (displayValueEl) {
|
|
19883
|
+
displayValueEl.remove();
|
|
19884
|
+
}
|
|
19885
|
+
return (clone.textContent || '').replace(/\s+/gu, ' ').trim();
|
|
19886
|
+
}
|
|
19887
|
+
|
|
19777
19888
|
// See https://git.io/JJ6SJ for "How to document your components using JSDoc"
|
|
19778
19889
|
/**
|
|
19779
19890
|
* The `auro-combobox` element provides users with a way to select an option from a list of filtered or suggested options based on user input.
|
|
@@ -19901,7 +20012,19 @@ class AuroCombobox extends AuroElement {
|
|
|
19901
20012
|
availableOptions: {
|
|
19902
20013
|
state: true,
|
|
19903
20014
|
type: Array,
|
|
19904
|
-
reflect: false
|
|
20015
|
+
reflect: false,
|
|
20016
|
+
hasChanged(newVal, oldVal) {
|
|
20017
|
+
if (!oldVal && !newVal) {
|
|
20018
|
+
return false;
|
|
20019
|
+
}
|
|
20020
|
+
if (!oldVal || !newVal) {
|
|
20021
|
+
return true;
|
|
20022
|
+
}
|
|
20023
|
+
if (newVal.length !== oldVal.length) {
|
|
20024
|
+
return true;
|
|
20025
|
+
}
|
|
20026
|
+
return newVal.some((opt, idx) => opt !== oldVal[idx]);
|
|
20027
|
+
}
|
|
19905
20028
|
},
|
|
19906
20029
|
|
|
19907
20030
|
/**
|
|
@@ -20332,18 +20455,15 @@ class AuroCombobox extends AuroElement {
|
|
|
20332
20455
|
matchString = `${matchString} ${option.getAttribute('suggest')}`.toLowerCase();
|
|
20333
20456
|
}
|
|
20334
20457
|
|
|
20335
|
-
|
|
20336
|
-
|
|
20337
|
-
|
|
20338
|
-
|
|
20339
|
-
|
|
20340
|
-
|
|
20341
|
-
} else if (matchString.includes(filterValue) && !option.hasAttribute('static')) {
|
|
20342
|
-
// only count options that match the typed input value AND are not static
|
|
20458
|
+
if (!filterValue && option.hasAttribute('static')) {
|
|
20459
|
+
// Static options are shown when the input is empty so consumers can
|
|
20460
|
+
// surface headers / "add new" rows when no filter is active.
|
|
20461
|
+
option.removeAttribute('hidden');
|
|
20462
|
+
this.availableOptions.push(option);
|
|
20463
|
+
} else if (filterValue && matchString.includes(filterValue) && !option.hasAttribute('static')) {
|
|
20343
20464
|
option.removeAttribute('hidden');
|
|
20344
20465
|
this.availableOptions.push(option);
|
|
20345
20466
|
} else if (!option.hasAttribute('persistent')) {
|
|
20346
|
-
// Hide all other non-persistent options
|
|
20347
20467
|
option.setAttribute('hidden', '');
|
|
20348
20468
|
option.removeAttribute('aria-setsize');
|
|
20349
20469
|
option.removeAttribute('aria-posinset');
|
|
@@ -20351,7 +20471,9 @@ class AuroCombobox extends AuroElement {
|
|
|
20351
20471
|
});
|
|
20352
20472
|
|
|
20353
20473
|
if (this.availableOptions.length === 0) {
|
|
20354
|
-
|
|
20474
|
+
// Only surface the nomatch option once the user has actually typed
|
|
20475
|
+
// something. Empty input is handled by the bib being closed.
|
|
20476
|
+
if (this.noMatchOption && filterValue) {
|
|
20355
20477
|
this.noMatchOption.removeAttribute('hidden');
|
|
20356
20478
|
} else if (!this.menu.loading || this.isHiddenWhileLoading) {
|
|
20357
20479
|
this.hideBib();
|
|
@@ -20367,11 +20489,14 @@ class AuroCombobox extends AuroElement {
|
|
|
20367
20489
|
* @returns {void}
|
|
20368
20490
|
*/
|
|
20369
20491
|
syncValuesAndStates() {
|
|
20370
|
-
//
|
|
20492
|
+
// Do NOT call setMenuValue here — the caller (handleMenuOptions) already
|
|
20493
|
+
// guards setMenuValue with an option-match check. Calling it unconditionally
|
|
20494
|
+
// sets free-form values on the menu, triggering clearSelection which resets
|
|
20495
|
+
// _index and breaks subsequent makeSelection calls.
|
|
20371
20496
|
if (this.menu) {
|
|
20372
20497
|
this.menu.matchWord = normalizeFilterValue(this.input.value);
|
|
20373
20498
|
}
|
|
20374
|
-
const label = this.menu.currentLabel;
|
|
20499
|
+
const label = getOptionLabel(this.menu.optionSelected) || this.menu.currentLabel;
|
|
20375
20500
|
this.updateTriggerTextDisplay(label || this.value);
|
|
20376
20501
|
}
|
|
20377
20502
|
|
|
@@ -20386,7 +20511,39 @@ class AuroCombobox extends AuroElement {
|
|
|
20386
20511
|
const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
|
|
20387
20512
|
|
|
20388
20513
|
if (!this.persistInput && !(this.behavior === 'suggestion' && isInputFocusedWithNoSelection)) {
|
|
20389
|
-
|
|
20514
|
+
const nextValue = label || this.value;
|
|
20515
|
+
// Only set the flag when there's an actual write to suppress —
|
|
20516
|
+
// syncValuesAndStates re-enters here during typing when both inputs
|
|
20517
|
+
// already match, and a no-op flag flip would make the bib branch's
|
|
20518
|
+
// bail eat legitimate user-input events.
|
|
20519
|
+
const triggerNeedsSync = this.input.value !== nextValue;
|
|
20520
|
+
const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
|
|
20521
|
+
if (triggerNeedsSync || bibNeedsSync) {
|
|
20522
|
+
this._syncingDisplayValue = true;
|
|
20523
|
+
if (triggerNeedsSync) {
|
|
20524
|
+
this.input.value = nextValue;
|
|
20525
|
+
}
|
|
20526
|
+
if (bibNeedsSync) {
|
|
20527
|
+
this.inputInBib.value = nextValue;
|
|
20528
|
+
}
|
|
20529
|
+
const pending = [];
|
|
20530
|
+
if (triggerNeedsSync) {
|
|
20531
|
+
pending.push(this.input.updateComplete);
|
|
20532
|
+
}
|
|
20533
|
+
if (bibNeedsSync) {
|
|
20534
|
+
pending.push(this.inputInBib.updateComplete);
|
|
20535
|
+
}
|
|
20536
|
+
Promise.all(pending).then(() => {
|
|
20537
|
+
// imask reasserts otherwise, and handleBlur reverts to its stale value.
|
|
20538
|
+
if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
|
|
20539
|
+
this.input.maskInstance.updateValue();
|
|
20540
|
+
}
|
|
20541
|
+
if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
|
|
20542
|
+
this.inputInBib.maskInstance.updateValue();
|
|
20543
|
+
}
|
|
20544
|
+
this._syncingDisplayValue = false;
|
|
20545
|
+
});
|
|
20546
|
+
}
|
|
20390
20547
|
}
|
|
20391
20548
|
|
|
20392
20549
|
// update the displayValue in the trigger if displayValue slot content is present
|
|
@@ -20400,6 +20557,14 @@ class AuroCombobox extends AuroElement {
|
|
|
20400
20557
|
const displayValueEl = this.menu.optionSelected.querySelector("[slot='displayValue']");
|
|
20401
20558
|
if (displayValueEl) {
|
|
20402
20559
|
this.input.appendChild(displayValueEl.cloneNode(true));
|
|
20560
|
+
// auro-input's hasDisplayValueContent is non-reactive; nudge it to
|
|
20561
|
+
// re-evaluate the slot and re-render so the displayValue wrapper
|
|
20562
|
+
// gets its hasContent class. Without this, the apple/icon stays
|
|
20563
|
+
// hidden when input.value is set in the same tick as the append.
|
|
20564
|
+
if (typeof this.input.checkDisplayValueSlotChange === 'function') {
|
|
20565
|
+
this.input.checkDisplayValueSlotChange();
|
|
20566
|
+
this.input.requestUpdate();
|
|
20567
|
+
}
|
|
20403
20568
|
}
|
|
20404
20569
|
}
|
|
20405
20570
|
|
|
@@ -20433,7 +20598,10 @@ class AuroCombobox extends AuroElement {
|
|
|
20433
20598
|
this.syncValuesAndStates();
|
|
20434
20599
|
}
|
|
20435
20600
|
|
|
20436
|
-
|
|
20601
|
+
// Re-activate when optionActive is no longer visible, or when _index has
|
|
20602
|
+
// been reset (e.g. by clearSelection in an async update) so that
|
|
20603
|
+
// makeSelection() can find the correct option.
|
|
20604
|
+
if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
|
|
20437
20605
|
this.activateFirstEnabledAvailableOption();
|
|
20438
20606
|
}
|
|
20439
20607
|
}
|
|
@@ -20529,6 +20697,14 @@ class AuroCombobox extends AuroElement {
|
|
|
20529
20697
|
// keyboard navigation continues from the correct place in the page
|
|
20530
20698
|
if (this.dropdown.isBibFullscreen) {
|
|
20531
20699
|
restoreTriggerAfterClose(this.dropdown, this.input);
|
|
20700
|
+
|
|
20701
|
+
// After the rAF inside restoreTriggerAfterClose() refocuses the
|
|
20702
|
+
// trigger, park the caret at end-of-text. Without this, the trigger
|
|
20703
|
+
// shows the new value (e.g. "Peaches") but its caret sits at [0, 0]
|
|
20704
|
+
// because nothing wrote to the native input between close and focus.
|
|
20705
|
+
doubleRaf(() => {
|
|
20706
|
+
this.setInputFocus();
|
|
20707
|
+
});
|
|
20532
20708
|
}
|
|
20533
20709
|
}
|
|
20534
20710
|
|
|
@@ -20558,12 +20734,27 @@ class AuroCombobox extends AuroElement {
|
|
|
20558
20734
|
|
|
20559
20735
|
guardTouchPassthrough(this.menu);
|
|
20560
20736
|
|
|
20561
|
-
//
|
|
20562
|
-
//
|
|
20737
|
+
// The dialog's showModal() steals focus from the trigger.
|
|
20738
|
+
// A single rAF is enough for the dialog DOM to be painted and
|
|
20739
|
+
// focusable, then doubleRaf finalizes the transition.
|
|
20740
|
+
requestAnimationFrame(() => {
|
|
20741
|
+
this.setInputFocus();
|
|
20742
|
+
});
|
|
20743
|
+
|
|
20563
20744
|
doubleRaf(() => {
|
|
20564
20745
|
this.setInputFocus();
|
|
20565
20746
|
this._inFullscreenTransition = false;
|
|
20566
20747
|
});
|
|
20748
|
+
} else {
|
|
20749
|
+
// Desktop popover-open: restore the trigger caret to end-of-text.
|
|
20750
|
+
// Clicking the trigger lands on auro-input's floating <label for="…">
|
|
20751
|
+
// overlay; Chrome resets the native input's selection to [0, 0] on
|
|
20752
|
+
// label-focus before any JS runs. setInputFocus()'s non-fullscreen
|
|
20753
|
+
// branch parks the caret at end. doubleRaf lets the dropdown layout
|
|
20754
|
+
// settle first, matching the fullscreen branch timing.
|
|
20755
|
+
doubleRaf(() => {
|
|
20756
|
+
this.setInputFocus();
|
|
20757
|
+
});
|
|
20567
20758
|
}
|
|
20568
20759
|
}
|
|
20569
20760
|
});
|
|
@@ -20640,16 +20831,53 @@ class AuroCombobox extends AuroElement {
|
|
|
20640
20831
|
*/
|
|
20641
20832
|
setInputFocus() {
|
|
20642
20833
|
if (this.dropdown.isBibFullscreen && this.dropdown.isPopoverVisible) {
|
|
20643
|
-
|
|
20834
|
+
// Sync the native input value synchronously before focusing.
|
|
20835
|
+
// Lit's property-to-attribute sync is async (microtask), so the
|
|
20836
|
+
// native <input> inside inputInBib may still hold a stale value
|
|
20837
|
+
// when focus moves here during the fullscreen transition. Without
|
|
20838
|
+
// this, keystrokes (e.g. Backspace) operate on the wrong content.
|
|
20839
|
+
const nativeInput = this.inputInBib.inputElement;
|
|
20840
|
+
const triggerNativeInput = this.input.inputElement;
|
|
20841
|
+
if (nativeInput && triggerNativeInput && nativeInput.value !== triggerNativeInput.value) {
|
|
20842
|
+
this.inputInBib.skipNextProgrammaticInputEvent = true;
|
|
20843
|
+
nativeInput.value = triggerNativeInput.value || '';
|
|
20844
|
+
}
|
|
20845
|
+
|
|
20846
|
+
// Focus the native input directly to ensure it receives keystrokes
|
|
20847
|
+
// after the fullscreen dialog opens. The dialog's showModal() may
|
|
20848
|
+
// have moved focus to the dialog element itself; focusing the
|
|
20849
|
+
// auro-input custom element doesn't always reach the native input
|
|
20850
|
+
// when the dialog DOM isn't fully painted.
|
|
20851
|
+
if (nativeInput) {
|
|
20852
|
+
nativeInput.focus();
|
|
20853
|
+
} else {
|
|
20854
|
+
this.inputInBib.focus();
|
|
20855
|
+
}
|
|
20644
20856
|
|
|
20645
20857
|
// Place cursor at end of existing text so the user can continue editing
|
|
20646
|
-
const nativeInput = this.inputInBib.inputElement;
|
|
20647
20858
|
if (nativeInput && nativeInput.value) {
|
|
20648
20859
|
const len = nativeInput.value.length;
|
|
20649
20860
|
nativeInput.setSelectionRange(len, len);
|
|
20650
20861
|
}
|
|
20651
20862
|
} else {
|
|
20652
|
-
|
|
20863
|
+
// Safety net for the strategy-change setTimeout: a viewport-crossing
|
|
20864
|
+
// fullscreen→floating switch can close the dialog and leave focus on
|
|
20865
|
+
// body. Every other Branch 2 callsite already has focus on the input
|
|
20866
|
+
// via the focusin → this.focus() redirect (L1448-1452), so this is a
|
|
20867
|
+
// no-op in the common case.
|
|
20868
|
+
if (!this.input.componentHasFocus) {
|
|
20869
|
+
this.input.focus();
|
|
20870
|
+
}
|
|
20871
|
+
|
|
20872
|
+
// Park the trigger native input's caret at end-of-text every time the
|
|
20873
|
+
// popover opens. Chrome focuses the native via the floating <label for="…">
|
|
20874
|
+
// overlay on click and resets selection to [0, 0]; no JS fires between
|
|
20875
|
+
// mousedown and selectionchange, so we restore the caret here.
|
|
20876
|
+
const triggerNativeInput = this.input.inputElement;
|
|
20877
|
+
if (triggerNativeInput && triggerNativeInput.value) {
|
|
20878
|
+
const len = triggerNativeInput.value.length;
|
|
20879
|
+
triggerNativeInput.setSelectionRange(len, len);
|
|
20880
|
+
}
|
|
20653
20881
|
}
|
|
20654
20882
|
}
|
|
20655
20883
|
|
|
@@ -20745,53 +20973,92 @@ class AuroCombobox extends AuroElement {
|
|
|
20745
20973
|
this.menu.setAttribute('nocheckmark', '');
|
|
20746
20974
|
}
|
|
20747
20975
|
|
|
20748
|
-
|
|
20749
|
-
|
|
20750
|
-
|
|
20976
|
+
// Handle menu option selection
|
|
20977
|
+
this.menu.addEventListener('auroMenu-selectedOption', (evt) => {
|
|
20978
|
+
// Capture before the consumption below — true means this event is the
|
|
20979
|
+
// echo of our own setMenuValue (programmatic value sync), false means
|
|
20980
|
+
// a fresh user selection.
|
|
20981
|
+
const isEcho = this._pendingMenuValueSync === true;
|
|
20751
20982
|
|
|
20752
|
-
|
|
20753
|
-
|
|
20754
|
-
// Update the optionSelected from the event details, not manually
|
|
20755
|
-
[this.optionSelected] = event.detail.options;
|
|
20983
|
+
if (this.menu.optionSelected) {
|
|
20984
|
+
const selected = this.menu.optionSelected;
|
|
20756
20985
|
|
|
20757
|
-
|
|
20758
|
-
|
|
20986
|
+
if (!this.optionSelected || this.optionSelected !== selected) {
|
|
20987
|
+
this.optionSelected = selected;
|
|
20988
|
+
}
|
|
20759
20989
|
|
|
20760
|
-
|
|
20761
|
-
|
|
20990
|
+
// Skip writeback when this event is the echo of our own setMenuValue —
|
|
20991
|
+
// otherwise it cascades against handleInputValueChange in suggestion mode.
|
|
20992
|
+
if (this._pendingMenuValueSync) {
|
|
20993
|
+
this._pendingMenuValueSync = false;
|
|
20994
|
+
} else if (!this.value || this.value !== this.optionSelected.value) {
|
|
20995
|
+
this.value = this.optionSelected.value;
|
|
20996
|
+
}
|
|
20762
20997
|
|
|
20763
|
-
|
|
20764
|
-
|
|
20765
|
-
if (this.menu.matchWord !== trimmedInput) {
|
|
20766
|
-
this.menu.matchWord = trimmedInput;
|
|
20767
|
-
}
|
|
20998
|
+
// Update display
|
|
20999
|
+
this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected) || this.menu.value);
|
|
20768
21000
|
|
|
20769
|
-
|
|
20770
|
-
|
|
21001
|
+
// Update match word for filtering
|
|
21002
|
+
const trimmedInput = normalizeFilterValue(this.input.value);
|
|
21003
|
+
if (this.menu.matchWord !== trimmedInput) {
|
|
21004
|
+
this.menu.matchWord = trimmedInput;
|
|
21005
|
+
}
|
|
21006
|
+
}
|
|
20771
21007
|
|
|
20772
21008
|
// Hide dropdown on selection (except during slot changes)
|
|
20773
|
-
if (
|
|
20774
|
-
|
|
20775
|
-
|
|
20776
|
-
|
|
20777
|
-
|
|
20778
|
-
}
|
|
20779
|
-
}, 0);
|
|
21009
|
+
if (evt.detail && evt.detail.source !== 'slotchange') {
|
|
21010
|
+
// do not close while typing in suggestion mode with no value selected, to allow freeform input
|
|
21011
|
+
if (this.menu.value || this.behavior !== 'suggestion') {
|
|
21012
|
+
this.hideBib();
|
|
21013
|
+
}
|
|
20780
21014
|
|
|
20781
21015
|
// Announce the selection after the dropdown closes so it isn't
|
|
20782
21016
|
// overridden by VoiceOver's "collapsed" announcement from aria-expanded.
|
|
20783
|
-
const selectedValue =
|
|
21017
|
+
const selectedValue = this.menu.value;
|
|
20784
21018
|
const announcementDelay = 300;
|
|
20785
21019
|
setTimeout(() => {
|
|
20786
21020
|
announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
|
|
20787
21021
|
}, announcementDelay);
|
|
20788
21022
|
}
|
|
21023
|
+
|
|
21024
|
+
// Programmatic value syncs leave availableOptions stale because
|
|
21025
|
+
// updateTriggerTextDisplay sets input.value with _syncingDisplayValue
|
|
21026
|
+
// and handleInputValueChange bails. Refresh the filter on echo events
|
|
21027
|
+
// only — fresh user selections take the existing hideBib path.
|
|
21028
|
+
if (isEcho && this.menu.optionSelected) {
|
|
21029
|
+
this._programmaticFilterRefresh = true;
|
|
21030
|
+
this.handleMenuOptions();
|
|
21031
|
+
setTimeout(() => {
|
|
21032
|
+
this._programmaticFilterRefresh = false;
|
|
21033
|
+
}, 0);
|
|
21034
|
+
}
|
|
21035
|
+
|
|
21036
|
+
// base-input skips auto-validate when focus is inside its own shadow,
|
|
21037
|
+
// which it is after setTriggerInputFocus — re-run so prior tooShort
|
|
21038
|
+
// clears. processCreditCard reasserts errorMessage on every render.
|
|
21039
|
+
if (!isEcho && this.menu.optionSelected && this.input.validate) {
|
|
21040
|
+
this.input.updateComplete.then(() => {
|
|
21041
|
+
this.input.validate(true);
|
|
21042
|
+
if (this.input.validity === 'valid') {
|
|
21043
|
+
this.input.errorMessage = '';
|
|
21044
|
+
}
|
|
21045
|
+
});
|
|
21046
|
+
}
|
|
20789
21047
|
});
|
|
20790
21048
|
|
|
20791
21049
|
this.menu.addEventListener('auroMenu-customEventFired', () => {
|
|
20792
21050
|
this.hideBib();
|
|
20793
21051
|
});
|
|
20794
21052
|
|
|
21053
|
+
// When the menu cannot match a programmatic value to any option,
|
|
21054
|
+
// clear the combobox selection state so it doesn't reference a
|
|
21055
|
+
// stale option. Safe from re-entrancy because any resulting
|
|
21056
|
+
// input.value changes dispatch isProgrammatic events.
|
|
21057
|
+
this.menu.addEventListener('auroMenu-selectValueFailure', () => {
|
|
21058
|
+
this.value = undefined;
|
|
21059
|
+
this.optionSelected = undefined;
|
|
21060
|
+
});
|
|
21061
|
+
|
|
20795
21062
|
this.menu.addEventListener('auroMenu-activatedOption', (evt) => {
|
|
20796
21063
|
this.optionActive = evt.detail;
|
|
20797
21064
|
|
|
@@ -20892,11 +21159,12 @@ class AuroCombobox extends AuroElement {
|
|
|
20892
21159
|
// event from the trigger. The _syncingBibValue guard persists across the
|
|
20893
21160
|
// async boundary and prevents that re-entrant event from running the
|
|
20894
21161
|
// non-fullscreen path (which would call clear() → hideBib()).
|
|
20895
|
-
// When the event comes from the fullscreen bib input, sync the value to
|
|
20896
|
-
// the trigger and run filtering, but suppress the re-entrant input event
|
|
20897
|
-
// that the trigger fires (via Lit updated() → notifyValueChanged()) so
|
|
20898
|
-
// the non-fullscreen hide/clear logic doesn't close the dialog.
|
|
20899
21162
|
if (event.target === this.inputInBib) {
|
|
21163
|
+
// Mirror the trigger-branch bail below — programmatic inputInBib writes
|
|
21164
|
+
// notify back into this branch via Lit's notifyValueChanged.
|
|
21165
|
+
if (this._syncingDisplayValue) {
|
|
21166
|
+
return;
|
|
21167
|
+
}
|
|
20900
21168
|
this._syncingBibValue = true;
|
|
20901
21169
|
this.input.value = this.inputInBib.value;
|
|
20902
21170
|
this.input.updateComplete.then(() => {
|
|
@@ -20906,18 +21174,37 @@ class AuroCombobox extends AuroElement {
|
|
|
20906
21174
|
// Run filtering inline — the re-entrant event won't reach this code.
|
|
20907
21175
|
this.menu.matchWord = normalizeFilterValue(this.inputInBib.value);
|
|
20908
21176
|
this.optionActive = null;
|
|
21177
|
+
|
|
21178
|
+
// In suggestion mode, keep the combobox value in sync with the typed
|
|
21179
|
+
// text so that freeform values are captured (mirroring the non-fullscreen
|
|
21180
|
+
// path). Clear the selection when the input is emptied.
|
|
21181
|
+
if (this.behavior === 'suggestion') {
|
|
21182
|
+
this.value = this.inputInBib.value || undefined;
|
|
21183
|
+
}
|
|
21184
|
+
|
|
20909
21185
|
this.handleMenuOptions();
|
|
20910
21186
|
this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
|
|
20911
21187
|
return;
|
|
20912
21188
|
}
|
|
20913
21189
|
|
|
20914
|
-
// Ignore re-entrant input events caused by
|
|
20915
|
-
if (this._syncingBibValue) {
|
|
21190
|
+
// Ignore re-entrant input events caused by programmatic value sets.
|
|
21191
|
+
if (this._syncingBibValue || this._syncingDisplayValue) {
|
|
20916
21192
|
return;
|
|
20917
21193
|
}
|
|
20918
21194
|
|
|
20919
21195
|
this.inputInBib.value = this.input.value;
|
|
20920
21196
|
|
|
21197
|
+
// Also sync the native input immediately so keystrokes arriving
|
|
21198
|
+
// before Lit's async update cycle (e.g. rapid Backspaces during a
|
|
21199
|
+
// fullscreen transition) operate on the correct content.
|
|
21200
|
+
// Skip the next programmatic input event to prevent the patched setter
|
|
21201
|
+
// from re-entering handleInputValueChange via the bib path.
|
|
21202
|
+
const bibNativeInput = this.inputInBib.inputElement;
|
|
21203
|
+
if (bibNativeInput && bibNativeInput.value !== this.input.value) {
|
|
21204
|
+
this.inputInBib.skipNextProgrammaticInputEvent = true;
|
|
21205
|
+
bibNativeInput.value = this.input.value || '';
|
|
21206
|
+
}
|
|
21207
|
+
|
|
20921
21208
|
this.menu.matchWord = normalizeFilterValue(this.input.value);
|
|
20922
21209
|
this.optionActive = null;
|
|
20923
21210
|
|
|
@@ -20925,7 +21212,7 @@ class AuroCombobox extends AuroElement {
|
|
|
20925
21212
|
this.value = this.input.value;
|
|
20926
21213
|
}
|
|
20927
21214
|
|
|
20928
|
-
if (!this.input.value) {
|
|
21215
|
+
if (!this.input.value && !this._clearing) {
|
|
20929
21216
|
this.clear();
|
|
20930
21217
|
}
|
|
20931
21218
|
this.handleMenuOptions();
|
|
@@ -20994,6 +21281,16 @@ class AuroCombobox extends AuroElement {
|
|
|
20994
21281
|
return;
|
|
20995
21282
|
}
|
|
20996
21283
|
|
|
21284
|
+
// Ignore dispatches from the bib (fullscreen) input. It's re-validated
|
|
21285
|
+
// inside this.validate()'s auroInputElements loop with its own
|
|
21286
|
+
// (often undefined) validity, and the event is composed/bubbles up to
|
|
21287
|
+
// this listener with `target` retargeted to the combobox. Letting it
|
|
21288
|
+
// through would overwrite the trigger input's correct validity with
|
|
21289
|
+
// the bib input's stale one (e.g. wiping `tooShort` during typing).
|
|
21290
|
+
if (this.inputInBib && evt.composedPath()[0] === this.inputInBib && this.inputInBib !== this.input) {
|
|
21291
|
+
return;
|
|
21292
|
+
}
|
|
21293
|
+
|
|
20997
21294
|
this.input.validity = evt.detail.validity;
|
|
20998
21295
|
this.input.errorMessage = evt.detail.message;
|
|
20999
21296
|
this.validity = evt.detail.validity;
|
|
@@ -21055,10 +21352,18 @@ class AuroCombobox extends AuroElement {
|
|
|
21055
21352
|
* @returns {void}
|
|
21056
21353
|
*/
|
|
21057
21354
|
setMenuValue(value) {
|
|
21058
|
-
if (!this.menu) {
|
|
21355
|
+
if (!this.menu || this.menu.value === value) {
|
|
21059
21356
|
return;
|
|
21060
21357
|
}
|
|
21358
|
+
// One-shot flag consumed by the auroMenu-selectedOption listener so the
|
|
21359
|
+
// echo of this sync doesn't propagate back to this.value.
|
|
21360
|
+
this._pendingMenuValueSync = true;
|
|
21061
21361
|
this.menu.value = value;
|
|
21362
|
+
// Backup clear: if menu.value === menu.optionSelected.value already, the
|
|
21363
|
+
// listener won't fire and the flag would swallow the next user click.
|
|
21364
|
+
setTimeout(() => {
|
|
21365
|
+
this._pendingMenuValueSync = false;
|
|
21366
|
+
}, 0);
|
|
21062
21367
|
}
|
|
21063
21368
|
|
|
21064
21369
|
/**
|
|
@@ -21073,6 +21378,15 @@ class AuroCombobox extends AuroElement {
|
|
|
21073
21378
|
this.menu.value = undefined;
|
|
21074
21379
|
this.validation.reset(this);
|
|
21075
21380
|
this.touched = false;
|
|
21381
|
+
// Force validity back to the cleared state. validation.reset() calls
|
|
21382
|
+
// validate() at the end, and (post-credit-card-fix) the trigger input's
|
|
21383
|
+
// residual validity may still be copied into the combobox by the
|
|
21384
|
+
// auroInputElements loop. Explicitly clear here so reset() actually
|
|
21385
|
+
// leaves the component in a "no validity" state.
|
|
21386
|
+
this.validity = undefined;
|
|
21387
|
+
this.errorMessage = '';
|
|
21388
|
+
this.input.validity = undefined;
|
|
21389
|
+
this.input.errorMessage = '';
|
|
21076
21390
|
}
|
|
21077
21391
|
|
|
21078
21392
|
/**
|
|
@@ -21080,16 +21394,24 @@ class AuroCombobox extends AuroElement {
|
|
|
21080
21394
|
* @returns {void}
|
|
21081
21395
|
*/
|
|
21082
21396
|
clear() {
|
|
21083
|
-
//
|
|
21084
|
-
|
|
21085
|
-
this.
|
|
21086
|
-
|
|
21087
|
-
// Then clear input and menu
|
|
21088
|
-
if (this.input.value) {
|
|
21089
|
-
this.input.clear();
|
|
21397
|
+
// input.clear() fires an input event that re-enters handleInputValueChange
|
|
21398
|
+
// → which calls clear() again on empty input. Guard against that loop.
|
|
21399
|
+
if (this._clearing) {
|
|
21400
|
+
return;
|
|
21090
21401
|
}
|
|
21091
|
-
|
|
21092
|
-
|
|
21402
|
+
this._clearing = true;
|
|
21403
|
+
try {
|
|
21404
|
+
this.optionSelected = undefined;
|
|
21405
|
+
this.value = undefined;
|
|
21406
|
+
|
|
21407
|
+
if (this.input.value) {
|
|
21408
|
+
this.input.clear();
|
|
21409
|
+
}
|
|
21410
|
+
if (this.menu.value || this.menu.optionSelected) {
|
|
21411
|
+
this.menu.reset();
|
|
21412
|
+
}
|
|
21413
|
+
} finally {
|
|
21414
|
+
this._clearing = false;
|
|
21093
21415
|
}
|
|
21094
21416
|
}
|
|
21095
21417
|
|
|
@@ -21117,29 +21439,64 @@ class AuroCombobox extends AuroElement {
|
|
|
21117
21439
|
this.input.value = this.value;
|
|
21118
21440
|
}
|
|
21119
21441
|
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
|
|
21123
|
-
|
|
21124
|
-
|
|
21125
|
-
|
|
21126
|
-
|
|
21127
|
-
|
|
21128
|
-
|
|
21129
|
-
if (!this.menu.options || this.menu.options.length === 0) ; else if (this.menu.options.filter((opt) => opt.value === this.value).length > 0) {
|
|
21442
|
+
// Sync menu.value only when an option actually matches; otherwise clear it.
|
|
21443
|
+
if (this.menu.options && this.menu.options.length > 0) {
|
|
21444
|
+
if (this.menu.options.some((opt) => opt.value === this.value)) {
|
|
21445
|
+
this.setMenuValue(this.value);
|
|
21446
|
+
} else if (this.behavior === 'filter') {
|
|
21447
|
+
// In filter mode, freeform values aren't allowed. Push the unmatched
|
|
21448
|
+
// value through setMenuValue so menu dispatches auroMenu-selectValueFailure
|
|
21449
|
+
// and the listener at line 1206 clears combobox.value (mirrors select's
|
|
21450
|
+
// pattern). Bypassing setMenuValue here would skip the failure cascade.
|
|
21130
21451
|
this.setMenuValue(this.value);
|
|
21131
21452
|
} else {
|
|
21132
|
-
this.menu.value
|
|
21133
|
-
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
|
|
21140
|
-
this.
|
|
21453
|
+
if (this.menu.value) {
|
|
21454
|
+
this.menu.value = undefined;
|
|
21455
|
+
}
|
|
21456
|
+
// Sync the display for programmatic freeform value changes (e.g. the
|
|
21457
|
+
// swap-values pattern). Guard with _syncingDisplayValue so that the
|
|
21458
|
+
// re-entrant input event from base-input.notifyValueChanged() is
|
|
21459
|
+
// ignored by handleInputValueChange. Skip the sync when input already
|
|
21460
|
+
// matches to avoid spurious events during the normal typing path.
|
|
21461
|
+
const nextValue = this.value || '';
|
|
21462
|
+
const triggerNeedsSync = this.input.value !== nextValue;
|
|
21463
|
+
const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
|
|
21464
|
+
if (triggerNeedsSync || bibNeedsSync) {
|
|
21465
|
+
this._syncingDisplayValue = true;
|
|
21466
|
+
if (triggerNeedsSync) {
|
|
21467
|
+
this.input.value = nextValue;
|
|
21468
|
+
}
|
|
21469
|
+
if (bibNeedsSync) {
|
|
21470
|
+
this.inputInBib.value = nextValue;
|
|
21471
|
+
}
|
|
21472
|
+
const pending = [];
|
|
21473
|
+
if (triggerNeedsSync) {
|
|
21474
|
+
pending.push(this.input.updateComplete);
|
|
21475
|
+
}
|
|
21476
|
+
if (bibNeedsSync) {
|
|
21477
|
+
pending.push(this.inputInBib.updateComplete);
|
|
21478
|
+
}
|
|
21479
|
+
Promise.all(pending).then(() => {
|
|
21480
|
+
if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
|
|
21481
|
+
this.input.maskInstance.updateValue();
|
|
21482
|
+
}
|
|
21483
|
+
if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
|
|
21484
|
+
this.inputInBib.maskInstance.updateValue();
|
|
21485
|
+
}
|
|
21486
|
+
this._syncingDisplayValue = false;
|
|
21487
|
+
// handleInputValueChange bailed on the flag — refresh the filter.
|
|
21488
|
+
this._programmaticFilterRefresh = true;
|
|
21489
|
+
this.handleMenuOptions();
|
|
21490
|
+
setTimeout(() => {
|
|
21491
|
+
this._programmaticFilterRefresh = false;
|
|
21492
|
+
}, 0);
|
|
21493
|
+
});
|
|
21494
|
+
}
|
|
21141
21495
|
}
|
|
21142
21496
|
}
|
|
21497
|
+
if (!this.value) {
|
|
21498
|
+
this.clear();
|
|
21499
|
+
}
|
|
21143
21500
|
if (this.value && !this.componentHasFocus) {
|
|
21144
21501
|
// If the value got set programmatically make sure we hide the bib
|
|
21145
21502
|
// when input is not taking the focus (input can be in dropdown.trigger or in bibtemplate)
|
|
@@ -21176,12 +21533,18 @@ class AuroCombobox extends AuroElement {
|
|
|
21176
21533
|
// branch from calling hideBib() when the dropdown was just opened but
|
|
21177
21534
|
// :focus-within hasn't propagated through the top-layer dialog's nested
|
|
21178
21535
|
// shadow DOM boundaries.
|
|
21179
|
-
|
|
21536
|
+
// Skip the show/hide side effects when the availableOptions change came
|
|
21537
|
+
// from a programmatic filter refresh after a value swap — the user
|
|
21538
|
+
// didn't interact, so we shouldn't auto-open the bib (especially for
|
|
21539
|
+
// the no-match path where the existing condition unconditionally fires).
|
|
21540
|
+
if (this._programmaticFilterRefresh) {
|
|
21541
|
+
this._programmaticFilterRefresh = false;
|
|
21542
|
+
} else if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
|
|
21180
21543
|
this.showBib();
|
|
21181
21544
|
if (!this.availableOptions.includes(this.menu.optionActive)) {
|
|
21182
21545
|
this.activateFirstEnabledAvailableOption();
|
|
21183
21546
|
}
|
|
21184
|
-
} else {
|
|
21547
|
+
} else if (this.dropdown && this.dropdown.isPopoverVisible) {
|
|
21185
21548
|
this.hideBib();
|
|
21186
21549
|
}
|
|
21187
21550
|
}
|