@aurodesignsystem-dev/auro-formkit 0.0.0-pr1504.0 → 0.0.0-pr1506.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 +1435 -1666
- package/components/combobox/demo/getting-started.min.js +1435 -1666
- package/components/combobox/demo/index.min.js +1435 -1666
- package/components/combobox/dist/auro-combobox.d.ts +14 -0
- package/components/combobox/dist/index.js +489 -145
- package/components/combobox/dist/registered.js +489 -145
- 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/customize.min.js +140 -61
- package/components/datepicker/demo/index.min.js +140 -61
- package/components/datepicker/dist/index.js +140 -61
- package/components/datepicker/dist/registered.js +140 -61
- 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 +2336 -2269
- package/components/form/demo/getting-started.min.js +2336 -2269
- package/components/form/demo/index.min.js +2336 -2269
- package/components/form/demo/registerDemoDeps.min.js +2334 -2267
- package/components/input/demo/customize.min.js +138 -57
- package/components/input/demo/getting-started.min.js +138 -57
- package/components/input/demo/index.min.js +138 -57
- package/components/input/dist/base-input.d.ts +16 -0
- package/components/input/dist/index.js +138 -57
- package/components/input/dist/registered.js +138 -57
- 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/api.md +1 -0
- package/components/select/demo/customize.md +78 -3
- package/components/select/demo/customize.min.js +1140 -1577
- package/components/select/demo/getting-started.min.js +1140 -1577
- package/components/select/demo/index.min.js +1140 -1577
- package/components/select/demo/keyboard-behavior.md +18 -0
- package/components/select/dist/auro-select.d.ts +38 -2
- package/components/select/dist/index.js +177 -39
- package/components/select/dist/registered.js +177 -39
- package/components/select/dist/selectUtils.d.ts +24 -0
- package/custom-elements.json +503 -1037
- package/package.json +1 -1
- 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 === '') {
|
|
@@ -767,6 +767,8 @@ function navigateArrow(component, direction, options = {}) {
|
|
|
767
767
|
}
|
|
768
768
|
}
|
|
769
769
|
|
|
770
|
+
/* eslint-disable no-underscore-dangle */
|
|
771
|
+
|
|
770
772
|
/**
|
|
771
773
|
* Returns the clear button element from the active input's shadow
|
|
772
774
|
* DOM, if available.
|
|
@@ -797,6 +799,19 @@ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
|
|
|
797
799
|
return isFocused;
|
|
798
800
|
}
|
|
799
801
|
|
|
802
|
+
/**
|
|
803
|
+
* Reconcile the menu `_index` from `optionActive` so subsequent `makeSelection` calls find the correct option after async clearSelection has reset it.
|
|
804
|
+
* @param {Object} menu - The menu component.
|
|
805
|
+
*/
|
|
806
|
+
function reconcileMenuIndex(menu) {
|
|
807
|
+
if (menu._index < 0 && menu.optionActive && menu.items) {
|
|
808
|
+
const idx = menu.items.indexOf(menu.optionActive);
|
|
809
|
+
if (idx >= 0) {
|
|
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 = '202606190840';
|
|
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 === '') {
|
|
@@ -17206,7 +17223,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
17206
17223
|
* @returns {Date|undefined}
|
|
17207
17224
|
*/
|
|
17208
17225
|
get valueObject() {
|
|
17209
|
-
return this._valueObject;
|
|
17226
|
+
return this._valueObject || this._computeDateObjectFallback(this.value);
|
|
17210
17227
|
}
|
|
17211
17228
|
|
|
17212
17229
|
/**
|
|
@@ -17214,7 +17231,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
17214
17231
|
* @returns {Date|undefined}
|
|
17215
17232
|
*/
|
|
17216
17233
|
get minObject() {
|
|
17217
|
-
return this._minObject;
|
|
17234
|
+
return this._minObject || this._computeDateObjectFallback(this.min);
|
|
17218
17235
|
}
|
|
17219
17236
|
|
|
17220
17237
|
/**
|
|
@@ -17222,7 +17239,31 @@ class BaseInput extends AuroElement$1 {
|
|
|
17222
17239
|
* @returns {Date|undefined}
|
|
17223
17240
|
*/
|
|
17224
17241
|
get maxObject() {
|
|
17225
|
-
return this._maxObject;
|
|
17242
|
+
return this._maxObject || this._computeDateObjectFallback(this.max);
|
|
17243
|
+
}
|
|
17244
|
+
|
|
17245
|
+
/**
|
|
17246
|
+
* Parses a date string into a Date object when the corresponding `_*Object`
|
|
17247
|
+
* field hasn't been synced yet by `updated()`. Returns undefined when the
|
|
17248
|
+
* input type/format isn't a full date or the string is not a valid date.
|
|
17249
|
+
*
|
|
17250
|
+
* Why this exists: a parent (datepicker) can call `inputN.validate()` from
|
|
17251
|
+
* inside its own `updated()` before this input's `updated()` has run
|
|
17252
|
+
* `syncDateValues()` — so `_valueObject`/`_maxObject` are still `undefined`
|
|
17253
|
+
* and range checks would otherwise silently no-op (flipping the result to
|
|
17254
|
+
* `valid` or `patternMismatch`).
|
|
17255
|
+
* @private
|
|
17256
|
+
* @param {string|undefined} dateStr - ISO date string from `value`/`min`/`max`.
|
|
17257
|
+
* @returns {Date|undefined}
|
|
17258
|
+
*/
|
|
17259
|
+
_computeDateObjectFallback(dateStr) {
|
|
17260
|
+
if (!dateStr || !this.util || !this.util.isFullDateFormat(this.type, this.format)) {
|
|
17261
|
+
return undefined;
|
|
17262
|
+
}
|
|
17263
|
+
if (!dateFormatter.isValidDate(dateStr)) {
|
|
17264
|
+
return undefined;
|
|
17265
|
+
}
|
|
17266
|
+
return dateFormatter.stringToDateInstance(dateStr);
|
|
17226
17267
|
}
|
|
17227
17268
|
|
|
17228
17269
|
/**
|
|
@@ -17247,8 +17288,30 @@ class BaseInput extends AuroElement$1 {
|
|
|
17247
17288
|
connectedCallback() {
|
|
17248
17289
|
super.connectedCallback();
|
|
17249
17290
|
|
|
17291
|
+
// Mark for query selectors when registered under a versioned tag (e.g. inside
|
|
17292
|
+
// a datepicker/combobox). Must run before parent components call validate() on
|
|
17293
|
+
// their inner inputs — the validation framework matches via tag-or-attribute,
|
|
17294
|
+
// and parents trigger validation during their own updated() cycle, which can
|
|
17295
|
+
// precede this input's firstUpdated().
|
|
17296
|
+
if (this.tagName.toLowerCase() !== 'auro-input' && !this.hasAttribute('auro-input')) {
|
|
17297
|
+
this.setAttribute('auro-input', '');
|
|
17298
|
+
}
|
|
17299
|
+
|
|
17250
17300
|
this.locale = this.domHandler.getLocale(this);
|
|
17251
17301
|
notifyOnLangChange(this);
|
|
17302
|
+
|
|
17303
|
+
// Pre-compute lengthForType and date-object fields so a parent (e.g. datepicker)
|
|
17304
|
+
// calling our validate() synchronously during its own updated() cycle sees
|
|
17305
|
+
// populated values. Without this, range validation silently no-ops because
|
|
17306
|
+
// `max.length === lengthForType` fails when lengthForType is still undefined.
|
|
17307
|
+
// Rebuild util here (constructor seeded with en-US default) so configureDataForType's
|
|
17308
|
+
// locale-derived format lookup uses the actual locale just resolved above.
|
|
17309
|
+
this.util = new AuroInputUtilities({
|
|
17310
|
+
locale: this.locale,
|
|
17311
|
+
format: this.format
|
|
17312
|
+
});
|
|
17313
|
+
this.configureDataForType();
|
|
17314
|
+
this.syncDateValues();
|
|
17252
17315
|
}
|
|
17253
17316
|
|
|
17254
17317
|
disconnectedCallback() {
|
|
@@ -17268,10 +17331,6 @@ class BaseInput extends AuroElement$1 {
|
|
|
17268
17331
|
this.wrapperElement.addEventListener('click', this.handleClick);
|
|
17269
17332
|
}
|
|
17270
17333
|
|
|
17271
|
-
// 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', true);
|
|
17274
|
-
}
|
|
17275
17334
|
this.inputId = this.id ? `${this.id}-input` : window.crypto.randomUUID();
|
|
17276
17335
|
|
|
17277
17336
|
// use validity message override if declared when initializing the component
|
|
@@ -17318,6 +17377,12 @@ class BaseInput extends AuroElement$1 {
|
|
|
17318
17377
|
return;
|
|
17319
17378
|
}
|
|
17320
17379
|
|
|
17380
|
+
// While configureAutoFormatting is running, imask's internal updateControl
|
|
17381
|
+
// writes el.value to align display with the masked value. A synthetic input
|
|
17382
|
+
// event from that write would re-enter handleInput → processCreditCard mid-setup
|
|
17383
|
+
// and clobber a Lit value that was just pushed by the parent combobox.
|
|
17384
|
+
if (component._configuringMask) return;
|
|
17385
|
+
|
|
17321
17386
|
// If all guard clauses are passed, dispatch the event
|
|
17322
17387
|
const inputEvent = new InputEvent('input', {
|
|
17323
17388
|
bubbles: true,
|
|
@@ -17436,7 +17501,16 @@ class BaseInput extends AuroElement$1 {
|
|
|
17436
17501
|
|
|
17437
17502
|
if (formattedValue !== this.inputElement.value) {
|
|
17438
17503
|
this.skipNextProgrammaticInputEvent = true;
|
|
17439
|
-
if (
|
|
17504
|
+
if (this.maskInstance && this.type !== 'date') {
|
|
17505
|
+
// Route through the mask so its _value and el.value stay in lock-step
|
|
17506
|
+
// (set value calls updateControl which writes el.value = displayValue).
|
|
17507
|
+
// Writing el.value directly leaves the mask thinking displayValue is
|
|
17508
|
+
// stale; _saveSelection on the next focus/click then warns. Date is
|
|
17509
|
+
// excluded because its formattedValue can be raw ISO when the calendar
|
|
17510
|
+
// is invalid, and re-masking through mm/dd/yyyy would truncate it and
|
|
17511
|
+
// flip validity from patternMismatch to tooShort.
|
|
17512
|
+
this.maskInstance.value = formattedValue || '';
|
|
17513
|
+
} else if (formattedValue) {
|
|
17440
17514
|
this.inputElement.value = formattedValue;
|
|
17441
17515
|
} else {
|
|
17442
17516
|
this.inputElement.value = '';
|
|
@@ -17545,58 +17619,82 @@ class BaseInput extends AuroElement$1 {
|
|
|
17545
17619
|
* @returns {void}
|
|
17546
17620
|
*/
|
|
17547
17621
|
configureAutoFormatting() {
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
|
|
17553
|
-
this.
|
|
17622
|
+
// _configuringMask gates two things: external re-entry into this method
|
|
17623
|
+
// while setup is mid-flight (from property changes that call back here),
|
|
17624
|
+
// and the accept/complete listeners below — both need to ignore the mask
|
|
17625
|
+
// events fired by our own value-restore step.
|
|
17626
|
+
if (this._configuringMask) return;
|
|
17627
|
+
this._configuringMask = true;
|
|
17628
|
+
try {
|
|
17629
|
+
// Destroy any prior mask so IMask can attach fresh under the new format.
|
|
17630
|
+
if (this.maskInstance) {
|
|
17631
|
+
this.maskInstance.destroy();
|
|
17632
|
+
}
|
|
17554
17633
|
|
|
17555
|
-
|
|
17634
|
+
this.util.updateFormat(this.format);
|
|
17556
17635
|
|
|
17557
|
-
|
|
17636
|
+
const maskOptions = this.util.getMaskOptions(this.type, this.format);
|
|
17558
17637
|
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
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
|
-
}
|
|
17638
|
+
if (this.inputElement && maskOptions.mask) {
|
|
17639
|
+
// Capture the current display so it can be re-applied after IMask
|
|
17640
|
+
// attaches. The restore at the bottom goes through maskInstance.value
|
|
17641
|
+
// (not inputElement.value directly) so the mask's internal state and
|
|
17642
|
+
// the input's displayed text stay in lock-step.
|
|
17643
|
+
let existingValue = this.inputElement.value;
|
|
17576
17644
|
|
|
17577
|
-
|
|
17578
|
-
|
|
17645
|
+
// Format-change case (e.g. locale switch): existingValue is the OLD
|
|
17646
|
+
// mask's display string and may not parse under the new mask. When
|
|
17647
|
+
// we have a valid date model, rebuild the display from valueObject
|
|
17648
|
+
// (the canonical source) using the new mask's format function.
|
|
17649
|
+
if (
|
|
17650
|
+
this.util.isFullDateFormat(this.type, this.format) &&
|
|
17651
|
+
this.value &&
|
|
17652
|
+
dateFormatter.isValidDate(this.value) &&
|
|
17653
|
+
this.valueObject instanceof Date &&
|
|
17654
|
+
!Number.isNaN(this.valueObject.getTime()) &&
|
|
17655
|
+
typeof maskOptions.format === 'function'
|
|
17656
|
+
) {
|
|
17657
|
+
existingValue = maskOptions.format(this.valueObject);
|
|
17658
|
+
}
|
|
17579
17659
|
|
|
17580
|
-
|
|
17660
|
+
// Clear before IMask attaches so the constructor seeds an empty
|
|
17661
|
+
// internal value. Otherwise IMask reads the stale unmasked string
|
|
17662
|
+
// and emits a spurious 'accept' before the restore below runs.
|
|
17663
|
+
this.skipNextProgrammaticInputEvent = true;
|
|
17664
|
+
this.inputElement.value = '';
|
|
17665
|
+
|
|
17666
|
+
this.maskInstance = IMask(this.inputElement, maskOptions);
|
|
17667
|
+
|
|
17668
|
+
// Mask fires 'accept' on every value change, including the restore
|
|
17669
|
+
// step below. Skip events fired during configureAutoFormatting so
|
|
17670
|
+
// we don't overwrite a value the parent just pushed.
|
|
17671
|
+
this.maskInstance.on('accept', () => {
|
|
17672
|
+
if (this._configuringMask) return;
|
|
17673
|
+
this.value = this.util.toModelValue(this.maskInstance.value, this.format);
|
|
17674
|
+
if (this.type === "date") {
|
|
17675
|
+
this._rawMaskValue = this.maskInstance.value;
|
|
17676
|
+
}
|
|
17677
|
+
});
|
|
17581
17678
|
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17585
|
-
this.
|
|
17586
|
-
|
|
17587
|
-
|
|
17679
|
+
// Mask fires 'complete' on the restore step below for any value that
|
|
17680
|
+
// happens to be a complete match. Same setup-suppression as 'accept'.
|
|
17681
|
+
this.maskInstance.on('complete', () => {
|
|
17682
|
+
if (this._configuringMask) return;
|
|
17683
|
+
this.value = this.util.toModelValue(this.maskInstance.value, this.format);
|
|
17684
|
+
if (this.type === "date") {
|
|
17685
|
+
this._rawMaskValue = this.maskInstance.value;
|
|
17686
|
+
}
|
|
17687
|
+
});
|
|
17588
17688
|
|
|
17589
|
-
|
|
17590
|
-
|
|
17591
|
-
|
|
17592
|
-
|
|
17689
|
+
// Write existingValue through the mask (not the input directly) so
|
|
17690
|
+
// the mask reformats it under the new rules and keeps its internal
|
|
17691
|
+
// _value aligned with the input's displayed text.
|
|
17692
|
+
if (existingValue) {
|
|
17693
|
+
this.maskInstance.value = existingValue;
|
|
17593
17694
|
}
|
|
17594
|
-
});
|
|
17595
|
-
|
|
17596
|
-
// Restore the stashed value through IMask so it's properly masked
|
|
17597
|
-
if (existingValue) {
|
|
17598
|
-
this.maskInstance.value = existingValue;
|
|
17599
17695
|
}
|
|
17696
|
+
} finally {
|
|
17697
|
+
this._configuringMask = false;
|
|
17600
17698
|
}
|
|
17601
17699
|
}
|
|
17602
17700
|
|
|
@@ -17883,10 +17981,10 @@ class BaseInput extends AuroElement$1 {
|
|
|
17883
17981
|
},
|
|
17884
17982
|
{
|
|
17885
17983
|
name: 'Diners club',
|
|
17886
|
-
regex: /^(?<num>36|38)\d{0}/u,
|
|
17984
|
+
regex: /^(?<num>30[0-5]|36|38)\d{0}/u,
|
|
17887
17985
|
formatLength: 16,
|
|
17888
17986
|
errorMessage: CreditCardValidationMessage,
|
|
17889
|
-
cardIcon: '
|
|
17987
|
+
cardIcon: 'cc-dinersclub',
|
|
17890
17988
|
maskFormat: "0000 000000 0000"
|
|
17891
17989
|
},
|
|
17892
17990
|
{
|
|
@@ -18296,7 +18394,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
|
|
|
18296
18394
|
}
|
|
18297
18395
|
};
|
|
18298
18396
|
|
|
18299
|
-
var formkitVersion$1 = '
|
|
18397
|
+
var formkitVersion$1 = '202606190840';
|
|
18300
18398
|
|
|
18301
18399
|
// Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
|
|
18302
18400
|
// See LICENSE in the project root for license information.
|
|
@@ -19417,7 +19515,7 @@ class AuroBibtemplate extends LitElement {
|
|
|
19417
19515
|
}
|
|
19418
19516
|
}
|
|
19419
19517
|
|
|
19420
|
-
var formkitVersion = '
|
|
19518
|
+
var formkitVersion = '202606190840';
|
|
19421
19519
|
|
|
19422
19520
|
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
19521
|
|
|
@@ -19774,6 +19872,25 @@ function normalizeFilterValue(value) {
|
|
|
19774
19872
|
return raw.trim() ? raw.trimEnd() : raw;
|
|
19775
19873
|
}
|
|
19776
19874
|
|
|
19875
|
+
/**
|
|
19876
|
+
* Returns the option's plain-text label suitable for input.value, with
|
|
19877
|
+
* HTML-indentation whitespace collapsed and the displayValue slot text
|
|
19878
|
+
* removed (the slot renders separately in the trigger).
|
|
19879
|
+
* @param {HTMLElement} option - An auro-menuoption element.
|
|
19880
|
+
* @returns {string} Normalized label.
|
|
19881
|
+
*/
|
|
19882
|
+
function getOptionLabel(option) {
|
|
19883
|
+
if (!option) {
|
|
19884
|
+
return '';
|
|
19885
|
+
}
|
|
19886
|
+
const clone = option.cloneNode(true);
|
|
19887
|
+
const displayValueEl = clone.querySelector('[slot="displayValue"]');
|
|
19888
|
+
if (displayValueEl) {
|
|
19889
|
+
displayValueEl.remove();
|
|
19890
|
+
}
|
|
19891
|
+
return (clone.textContent || '').replace(/\s+/gu, ' ').trim();
|
|
19892
|
+
}
|
|
19893
|
+
|
|
19777
19894
|
// See https://git.io/JJ6SJ for "How to document your components using JSDoc"
|
|
19778
19895
|
/**
|
|
19779
19896
|
* 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 +20018,19 @@ class AuroCombobox extends AuroElement {
|
|
|
19901
20018
|
availableOptions: {
|
|
19902
20019
|
state: true,
|
|
19903
20020
|
type: Array,
|
|
19904
|
-
reflect: false
|
|
20021
|
+
reflect: false,
|
|
20022
|
+
hasChanged(newVal, oldVal) {
|
|
20023
|
+
if (!oldVal && !newVal) {
|
|
20024
|
+
return false;
|
|
20025
|
+
}
|
|
20026
|
+
if (!oldVal || !newVal) {
|
|
20027
|
+
return true;
|
|
20028
|
+
}
|
|
20029
|
+
if (newVal.length !== oldVal.length) {
|
|
20030
|
+
return true;
|
|
20031
|
+
}
|
|
20032
|
+
return newVal.some((opt, idx) => opt !== oldVal[idx]);
|
|
20033
|
+
}
|
|
19905
20034
|
},
|
|
19906
20035
|
|
|
19907
20036
|
/**
|
|
@@ -20332,18 +20461,15 @@ class AuroCombobox extends AuroElement {
|
|
|
20332
20461
|
matchString = `${matchString} ${option.getAttribute('suggest')}`.toLowerCase();
|
|
20333
20462
|
}
|
|
20334
20463
|
|
|
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
|
|
20464
|
+
if (!filterValue && option.hasAttribute('static')) {
|
|
20465
|
+
// Static options are shown when the input is empty so consumers can
|
|
20466
|
+
// surface headers / "add new" rows when no filter is active.
|
|
20467
|
+
option.removeAttribute('hidden');
|
|
20468
|
+
this.availableOptions.push(option);
|
|
20469
|
+
} else if (filterValue && matchString.includes(filterValue) && !option.hasAttribute('static')) {
|
|
20343
20470
|
option.removeAttribute('hidden');
|
|
20344
20471
|
this.availableOptions.push(option);
|
|
20345
20472
|
} else if (!option.hasAttribute('persistent')) {
|
|
20346
|
-
// Hide all other non-persistent options
|
|
20347
20473
|
option.setAttribute('hidden', '');
|
|
20348
20474
|
option.removeAttribute('aria-setsize');
|
|
20349
20475
|
option.removeAttribute('aria-posinset');
|
|
@@ -20351,7 +20477,9 @@ class AuroCombobox extends AuroElement {
|
|
|
20351
20477
|
});
|
|
20352
20478
|
|
|
20353
20479
|
if (this.availableOptions.length === 0) {
|
|
20354
|
-
|
|
20480
|
+
// Only surface the nomatch option once the user has actually typed
|
|
20481
|
+
// something. Empty input is handled by the bib being closed.
|
|
20482
|
+
if (this.noMatchOption && filterValue) {
|
|
20355
20483
|
this.noMatchOption.removeAttribute('hidden');
|
|
20356
20484
|
} else if (!this.menu.loading || this.isHiddenWhileLoading) {
|
|
20357
20485
|
this.hideBib();
|
|
@@ -20367,11 +20495,14 @@ class AuroCombobox extends AuroElement {
|
|
|
20367
20495
|
* @returns {void}
|
|
20368
20496
|
*/
|
|
20369
20497
|
syncValuesAndStates() {
|
|
20370
|
-
//
|
|
20498
|
+
// Do NOT call setMenuValue here — the caller (handleMenuOptions) already
|
|
20499
|
+
// guards setMenuValue with an option-match check. Calling it unconditionally
|
|
20500
|
+
// sets free-form values on the menu, triggering clearSelection which resets
|
|
20501
|
+
// _index and breaks subsequent makeSelection calls.
|
|
20371
20502
|
if (this.menu) {
|
|
20372
20503
|
this.menu.matchWord = normalizeFilterValue(this.input.value);
|
|
20373
20504
|
}
|
|
20374
|
-
const label = this.menu.
|
|
20505
|
+
const label = getOptionLabel(this.menu.optionSelected);
|
|
20375
20506
|
this.updateTriggerTextDisplay(label || this.value);
|
|
20376
20507
|
}
|
|
20377
20508
|
|
|
@@ -20385,8 +20516,9 @@ class AuroCombobox extends AuroElement {
|
|
|
20385
20516
|
// in suggestion mode, do not override input value if no selection has been made and the input currently has focus
|
|
20386
20517
|
const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
|
|
20387
20518
|
|
|
20388
|
-
|
|
20389
|
-
|
|
20519
|
+
const suppressed = this.persistInput || (this.behavior === 'suggestion' && isInputFocusedWithNoSelection);
|
|
20520
|
+
if (!suppressed) {
|
|
20521
|
+
this.syncInputValuesAcrossTriggerAndBib(label || this.value);
|
|
20390
20522
|
}
|
|
20391
20523
|
|
|
20392
20524
|
// update the displayValue in the trigger if displayValue slot content is present
|
|
@@ -20400,12 +20532,66 @@ class AuroCombobox extends AuroElement {
|
|
|
20400
20532
|
const displayValueEl = this.menu.optionSelected.querySelector("[slot='displayValue']");
|
|
20401
20533
|
if (displayValueEl) {
|
|
20402
20534
|
this.input.appendChild(displayValueEl.cloneNode(true));
|
|
20535
|
+
// auro-input's hasDisplayValueContent is non-reactive; nudge it to
|
|
20536
|
+
// re-evaluate the slot and re-render so the displayValue wrapper
|
|
20537
|
+
// gets its hasContent class. Without this, the apple/icon stays
|
|
20538
|
+
// hidden when input.value is set in the same tick as the append.
|
|
20539
|
+
if (typeof this.input.checkDisplayValueSlotChange === 'function') {
|
|
20540
|
+
this.input.checkDisplayValueSlotChange();
|
|
20541
|
+
this.input.requestUpdate();
|
|
20542
|
+
}
|
|
20403
20543
|
}
|
|
20404
20544
|
}
|
|
20405
20545
|
|
|
20406
20546
|
this.requestUpdate();
|
|
20407
20547
|
}
|
|
20408
20548
|
|
|
20549
|
+
/**
|
|
20550
|
+
* Writes nextValue to the trigger input and the bib input when their current
|
|
20551
|
+
* value differs, then re-asserts imask after Lit's update flushes.
|
|
20552
|
+
* @param {string} nextValue - The value to write to both inputs.
|
|
20553
|
+
* @returns {Promise<void> | null} Promise that resolves after the inputs flush,
|
|
20554
|
+
* or null when no sync was needed (so callers can skip post-flush work).
|
|
20555
|
+
* @private
|
|
20556
|
+
*/
|
|
20557
|
+
syncInputValuesAcrossTriggerAndBib(nextValue) {
|
|
20558
|
+
// Only set the flag when there's an actual write to suppress —
|
|
20559
|
+
// syncValuesAndStates re-enters here during typing when both inputs
|
|
20560
|
+
// already match, and a no-op flag flip would make the bib branch's
|
|
20561
|
+
// bail eat legitimate user-input events.
|
|
20562
|
+
const triggerNeedsSync = this.input.value !== nextValue;
|
|
20563
|
+
const bibNeedsSync = Boolean(this.inputInBib && this.inputInBib !== this.input && this.inputInBib.value !== nextValue);
|
|
20564
|
+
if (!triggerNeedsSync && !bibNeedsSync) {
|
|
20565
|
+
return null;
|
|
20566
|
+
}
|
|
20567
|
+
|
|
20568
|
+
this._syncingDisplayValue = true;
|
|
20569
|
+
if (triggerNeedsSync) {
|
|
20570
|
+
this.input.value = nextValue;
|
|
20571
|
+
}
|
|
20572
|
+
if (bibNeedsSync) {
|
|
20573
|
+
this.inputInBib.value = nextValue;
|
|
20574
|
+
}
|
|
20575
|
+
|
|
20576
|
+
const pending = [];
|
|
20577
|
+
if (triggerNeedsSync) {
|
|
20578
|
+
pending.push(this.input.updateComplete);
|
|
20579
|
+
}
|
|
20580
|
+
if (bibNeedsSync) {
|
|
20581
|
+
pending.push(this.inputInBib.updateComplete);
|
|
20582
|
+
}
|
|
20583
|
+
return Promise.all(pending).then(() => {
|
|
20584
|
+
// imask reasserts otherwise, and handleBlur reverts to its stale value.
|
|
20585
|
+
if (triggerNeedsSync && this.input.maskInstance && typeof this.input.maskInstance.updateValue === 'function') {
|
|
20586
|
+
this.input.maskInstance.updateValue();
|
|
20587
|
+
}
|
|
20588
|
+
if (bibNeedsSync && this.inputInBib && this.inputInBib.maskInstance && typeof this.inputInBib.maskInstance.updateValue === 'function') {
|
|
20589
|
+
this.inputInBib.maskInstance.updateValue();
|
|
20590
|
+
}
|
|
20591
|
+
this._syncingDisplayValue = false;
|
|
20592
|
+
});
|
|
20593
|
+
}
|
|
20594
|
+
|
|
20409
20595
|
/**
|
|
20410
20596
|
* Processes hidden state of all menu options and determines if there are any available options not hidden.
|
|
20411
20597
|
* @private
|
|
@@ -20433,7 +20619,10 @@ class AuroCombobox extends AuroElement {
|
|
|
20433
20619
|
this.syncValuesAndStates();
|
|
20434
20620
|
}
|
|
20435
20621
|
|
|
20436
|
-
|
|
20622
|
+
// Re-activate when optionActive is not in the current scrollable viewport,
|
|
20623
|
+
// or when _index has been reset (e.g. by clearSelection in an async update)
|
|
20624
|
+
// so that makeSelection() can find the correct option.
|
|
20625
|
+
if (!this.availableOptions.includes(this.menu.optionActive) || this.menu._index < 0) {
|
|
20437
20626
|
this.activateFirstEnabledAvailableOption();
|
|
20438
20627
|
}
|
|
20439
20628
|
}
|
|
@@ -20529,6 +20718,14 @@ class AuroCombobox extends AuroElement {
|
|
|
20529
20718
|
// keyboard navigation continues from the correct place in the page
|
|
20530
20719
|
if (this.dropdown.isBibFullscreen) {
|
|
20531
20720
|
restoreTriggerAfterClose(this.dropdown, this.input);
|
|
20721
|
+
|
|
20722
|
+
// After the rAF inside restoreTriggerAfterClose() refocuses the
|
|
20723
|
+
// trigger, park the caret at end-of-text. Without this, the trigger
|
|
20724
|
+
// shows the new value (e.g. "Peaches") but its caret sits at [0, 0]
|
|
20725
|
+
// because nothing wrote to the native input between close and focus.
|
|
20726
|
+
doubleRaf(() => {
|
|
20727
|
+
this.setInputFocus();
|
|
20728
|
+
});
|
|
20532
20729
|
}
|
|
20533
20730
|
}
|
|
20534
20731
|
|
|
@@ -20558,13 +20755,25 @@ class AuroCombobox extends AuroElement {
|
|
|
20558
20755
|
|
|
20559
20756
|
guardTouchPassthrough(this.menu);
|
|
20560
20757
|
|
|
20561
|
-
//
|
|
20562
|
-
//
|
|
20563
|
-
|
|
20758
|
+
// showModal() takes focus away from the trigger. Early focus once
|
|
20759
|
+
// the dialog has painted; the shared doubleRaf below is the fallback
|
|
20760
|
+
// for when the dialog needs an extra frame and also clears the
|
|
20761
|
+
// validation guard.
|
|
20762
|
+
requestAnimationFrame(() => {
|
|
20564
20763
|
this.setInputFocus();
|
|
20565
|
-
this._inFullscreenTransition = false;
|
|
20566
20764
|
});
|
|
20567
20765
|
}
|
|
20766
|
+
// else (desktop popover-open): Chrome resets the trigger caret to
|
|
20767
|
+
// [0, 0] when its floating <label for="…"> overlay receives focus.
|
|
20768
|
+
// The shared doubleRaf below parks the caret back at end-of-text
|
|
20769
|
+
// after the dropdown layout settles.
|
|
20770
|
+
|
|
20771
|
+
doubleRaf(() => {
|
|
20772
|
+
this.setInputFocus();
|
|
20773
|
+
if (this._inFullscreenTransition) {
|
|
20774
|
+
this._inFullscreenTransition = false;
|
|
20775
|
+
}
|
|
20776
|
+
});
|
|
20568
20777
|
}
|
|
20569
20778
|
});
|
|
20570
20779
|
|
|
@@ -20640,16 +20849,53 @@ class AuroCombobox extends AuroElement {
|
|
|
20640
20849
|
*/
|
|
20641
20850
|
setInputFocus() {
|
|
20642
20851
|
if (this.dropdown.isBibFullscreen && this.dropdown.isPopoverVisible) {
|
|
20643
|
-
|
|
20852
|
+
// Sync the native input value synchronously before focusing.
|
|
20853
|
+
// Lit's property-to-attribute sync is async (microtask), so the
|
|
20854
|
+
// native <input> inside inputInBib may still hold a stale value
|
|
20855
|
+
// when focus moves here during the fullscreen transition. Without
|
|
20856
|
+
// this, keystrokes (e.g. Backspace) operate on the wrong content.
|
|
20857
|
+
const nativeInput = this.inputInBib.inputElement;
|
|
20858
|
+
const triggerNativeInput = this.input.inputElement;
|
|
20859
|
+
if (nativeInput && triggerNativeInput && nativeInput.value !== triggerNativeInput.value) {
|
|
20860
|
+
this.inputInBib.skipNextProgrammaticInputEvent = true;
|
|
20861
|
+
nativeInput.value = triggerNativeInput.value || '';
|
|
20862
|
+
}
|
|
20863
|
+
|
|
20864
|
+
// Focus the native input directly to ensure it receives keystrokes
|
|
20865
|
+
// after the fullscreen dialog opens. The dialog's showModal() may
|
|
20866
|
+
// have moved focus to the dialog element itself; focusing the
|
|
20867
|
+
// auro-input custom element doesn't always reach the native input
|
|
20868
|
+
// when the dialog DOM isn't fully painted.
|
|
20869
|
+
if (nativeInput) {
|
|
20870
|
+
nativeInput.focus();
|
|
20871
|
+
} else {
|
|
20872
|
+
this.inputInBib.focus();
|
|
20873
|
+
}
|
|
20644
20874
|
|
|
20645
20875
|
// Place cursor at end of existing text so the user can continue editing
|
|
20646
|
-
const nativeInput = this.inputInBib.inputElement;
|
|
20647
20876
|
if (nativeInput && nativeInput.value) {
|
|
20648
20877
|
const len = nativeInput.value.length;
|
|
20649
20878
|
nativeInput.setSelectionRange(len, len);
|
|
20650
20879
|
}
|
|
20651
20880
|
} else {
|
|
20652
|
-
|
|
20881
|
+
// Safety net for the strategy-change setTimeout: a viewport-crossing
|
|
20882
|
+
// fullscreen→floating switch can close the dialog and leave focus on
|
|
20883
|
+
// body. Every other call into this branch already has focus on the
|
|
20884
|
+
// input via the combobox-level focusin → this.focus() redirect, so
|
|
20885
|
+
// this is a no-op in the common case.
|
|
20886
|
+
if (!this.input.componentHasFocus) {
|
|
20887
|
+
this.input.focus();
|
|
20888
|
+
}
|
|
20889
|
+
|
|
20890
|
+
// Park the trigger native input's caret at end-of-text every time the
|
|
20891
|
+
// popover opens. Chrome focuses the native via the floating <label for="…">
|
|
20892
|
+
// overlay on click and resets selection to [0, 0]; no JS fires between
|
|
20893
|
+
// mousedown and selectionchange, so we restore the caret here.
|
|
20894
|
+
const triggerNativeInput = this.input.inputElement;
|
|
20895
|
+
if (triggerNativeInput && triggerNativeInput.value) {
|
|
20896
|
+
const len = triggerNativeInput.value.length;
|
|
20897
|
+
triggerNativeInput.setSelectionRange(len, len);
|
|
20898
|
+
}
|
|
20653
20899
|
}
|
|
20654
20900
|
}
|
|
20655
20901
|
|
|
@@ -20745,53 +20991,92 @@ class AuroCombobox extends AuroElement {
|
|
|
20745
20991
|
this.menu.setAttribute('nocheckmark', '');
|
|
20746
20992
|
}
|
|
20747
20993
|
|
|
20748
|
-
|
|
20749
|
-
|
|
20750
|
-
|
|
20994
|
+
// Handle menu option selection
|
|
20995
|
+
this.menu.addEventListener('auroMenu-selectedOption', (evt) => {
|
|
20996
|
+
// Capture before the consumption below — true means this event is the
|
|
20997
|
+
// echo of our own setMenuValue (programmatic value sync), false means
|
|
20998
|
+
// a fresh user selection.
|
|
20999
|
+
const isEcho = this._pendingMenuValueSync === true;
|
|
20751
21000
|
|
|
20752
|
-
|
|
20753
|
-
|
|
20754
|
-
|
|
20755
|
-
|
|
21001
|
+
if (this.menu.optionSelected) {
|
|
21002
|
+
const selected = this.menu.optionSelected;
|
|
21003
|
+
|
|
21004
|
+
if (this.optionSelected !== selected) {
|
|
21005
|
+
this.optionSelected = selected;
|
|
21006
|
+
}
|
|
20756
21007
|
|
|
20757
|
-
|
|
20758
|
-
|
|
21008
|
+
// Skip writeback when this event is the echo of our own setMenuValue —
|
|
21009
|
+
// otherwise it cascades against handleInputValueChange in suggestion mode.
|
|
21010
|
+
if (this._pendingMenuValueSync) {
|
|
21011
|
+
this._pendingMenuValueSync = false;
|
|
21012
|
+
} else if (!this.value || this.value !== this.optionSelected.value) {
|
|
21013
|
+
this.value = this.optionSelected.value;
|
|
21014
|
+
}
|
|
20759
21015
|
|
|
20760
|
-
|
|
20761
|
-
|
|
21016
|
+
// Update display
|
|
21017
|
+
this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected));
|
|
20762
21018
|
|
|
20763
|
-
|
|
20764
|
-
|
|
20765
|
-
|
|
20766
|
-
|
|
21019
|
+
// Update match word for filtering
|
|
21020
|
+
const trimmedInput = normalizeFilterValue(this.input.value);
|
|
21021
|
+
if (this.menu.matchWord !== trimmedInput) {
|
|
21022
|
+
this.menu.matchWord = trimmedInput;
|
|
21023
|
+
}
|
|
20767
21024
|
}
|
|
20768
21025
|
|
|
20769
|
-
// Update available options based on selection
|
|
20770
|
-
this.handleMenuOptions();
|
|
20771
|
-
|
|
20772
21026
|
// Hide dropdown on selection (except during slot changes)
|
|
20773
|
-
if (
|
|
20774
|
-
|
|
20775
|
-
|
|
20776
|
-
|
|
20777
|
-
|
|
20778
|
-
}
|
|
20779
|
-
}, 0);
|
|
21027
|
+
if (evt.detail && evt.detail.source !== 'slotchange') {
|
|
21028
|
+
// do not close while typing in suggestion mode with no value selected, to allow freeform input
|
|
21029
|
+
if (this.menu.value || this.behavior !== 'suggestion') {
|
|
21030
|
+
this.hideBib();
|
|
21031
|
+
}
|
|
20780
21032
|
|
|
20781
21033
|
// Announce the selection after the dropdown closes so it isn't
|
|
20782
21034
|
// overridden by VoiceOver's "collapsed" announcement from aria-expanded.
|
|
20783
|
-
const selectedValue =
|
|
21035
|
+
const selectedValue = this.menu.value;
|
|
20784
21036
|
const announcementDelay = 300;
|
|
20785
21037
|
setTimeout(() => {
|
|
20786
21038
|
announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
|
|
20787
21039
|
}, announcementDelay);
|
|
20788
21040
|
}
|
|
21041
|
+
|
|
21042
|
+
// Programmatic value syncs leave availableOptions stale because
|
|
21043
|
+
// updateTriggerTextDisplay sets input.value with _syncingDisplayValue
|
|
21044
|
+
// and handleInputValueChange bails. Refresh the filter on echo events
|
|
21045
|
+
// only — fresh user selections take the existing hideBib path.
|
|
21046
|
+
if (isEcho && this.menu.optionSelected) {
|
|
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
|
+
}
|
|
20789
21065
|
});
|
|
20790
21066
|
|
|
20791
21067
|
this.menu.addEventListener('auroMenu-customEventFired', () => {
|
|
20792
21068
|
this.hideBib();
|
|
20793
21069
|
});
|
|
20794
21070
|
|
|
21071
|
+
// When the menu cannot match a programmatic value to any option,
|
|
21072
|
+
// clear the combobox selection state so it doesn't reference a
|
|
21073
|
+
// stale option. Safe from re-entrancy because any resulting
|
|
21074
|
+
// input.value changes dispatch isProgrammatic events.
|
|
21075
|
+
this.menu.addEventListener('auroMenu-selectValueFailure', () => {
|
|
21076
|
+
this.value = undefined;
|
|
21077
|
+
this.optionSelected = undefined;
|
|
21078
|
+
});
|
|
21079
|
+
|
|
20795
21080
|
this.menu.addEventListener('auroMenu-activatedOption', (evt) => {
|
|
20796
21081
|
this.optionActive = evt.detail;
|
|
20797
21082
|
|
|
@@ -20892,11 +21177,12 @@ class AuroCombobox extends AuroElement {
|
|
|
20892
21177
|
// event from the trigger. The _syncingBibValue guard persists across the
|
|
20893
21178
|
// async boundary and prevents that re-entrant event from running the
|
|
20894
21179
|
// 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
21180
|
if (event.target === this.inputInBib) {
|
|
21181
|
+
// Mirror the trigger-branch bail below — programmatic inputInBib writes
|
|
21182
|
+
// notify back into this branch via Lit's notifyValueChanged.
|
|
21183
|
+
if (this._syncingDisplayValue) {
|
|
21184
|
+
return;
|
|
21185
|
+
}
|
|
20900
21186
|
this._syncingBibValue = true;
|
|
20901
21187
|
this.input.value = this.inputInBib.value;
|
|
20902
21188
|
this.input.updateComplete.then(() => {
|
|
@@ -20906,18 +21192,37 @@ class AuroCombobox extends AuroElement {
|
|
|
20906
21192
|
// Run filtering inline — the re-entrant event won't reach this code.
|
|
20907
21193
|
this.menu.matchWord = normalizeFilterValue(this.inputInBib.value);
|
|
20908
21194
|
this.optionActive = null;
|
|
21195
|
+
|
|
21196
|
+
// In suggestion mode, keep the combobox value in sync with the typed
|
|
21197
|
+
// text so that freeform values are captured (mirroring the non-fullscreen
|
|
21198
|
+
// path). Clear the selection when the input is emptied.
|
|
21199
|
+
if (this.behavior === 'suggestion') {
|
|
21200
|
+
this.value = this.inputInBib.value || undefined;
|
|
21201
|
+
}
|
|
21202
|
+
|
|
20909
21203
|
this.handleMenuOptions();
|
|
20910
21204
|
this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
|
|
20911
21205
|
return;
|
|
20912
21206
|
}
|
|
20913
21207
|
|
|
20914
|
-
// Ignore re-entrant input events caused by
|
|
20915
|
-
if (this._syncingBibValue) {
|
|
21208
|
+
// Ignore re-entrant input events caused by programmatic value sets.
|
|
21209
|
+
if (this._syncingBibValue || this._syncingDisplayValue) {
|
|
20916
21210
|
return;
|
|
20917
21211
|
}
|
|
20918
21212
|
|
|
20919
21213
|
this.inputInBib.value = this.input.value;
|
|
20920
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
|
+
|
|
20921
21226
|
this.menu.matchWord = normalizeFilterValue(this.input.value);
|
|
20922
21227
|
this.optionActive = null;
|
|
20923
21228
|
|
|
@@ -20925,7 +21230,7 @@ class AuroCombobox extends AuroElement {
|
|
|
20925
21230
|
this.value = this.input.value;
|
|
20926
21231
|
}
|
|
20927
21232
|
|
|
20928
|
-
if (!this.input.value) {
|
|
21233
|
+
if (!this.input.value && !this._clearing) {
|
|
20929
21234
|
this.clear();
|
|
20930
21235
|
}
|
|
20931
21236
|
this.handleMenuOptions();
|
|
@@ -20994,6 +21299,16 @@ class AuroCombobox extends AuroElement {
|
|
|
20994
21299
|
return;
|
|
20995
21300
|
}
|
|
20996
21301
|
|
|
21302
|
+
// Ignore dispatches from the bib (fullscreen) input. It's re-validated
|
|
21303
|
+
// inside this.validate()'s auroInputElements loop with its own
|
|
21304
|
+
// (often undefined) validity, and the event is composed/bubbles up to
|
|
21305
|
+
// this listener with `target` retargeted to the combobox. Letting it
|
|
21306
|
+
// through would overwrite the trigger input's correct validity with
|
|
21307
|
+
// the bib input's stale one (e.g. wiping `tooShort` during typing).
|
|
21308
|
+
if (this.inputInBib && evt.composedPath()[0] === this.inputInBib && this.inputInBib !== this.input) {
|
|
21309
|
+
return;
|
|
21310
|
+
}
|
|
21311
|
+
|
|
20997
21312
|
this.input.validity = evt.detail.validity;
|
|
20998
21313
|
this.input.errorMessage = evt.detail.message;
|
|
20999
21314
|
this.validity = evt.detail.validity;
|
|
@@ -21055,10 +21370,18 @@ class AuroCombobox extends AuroElement {
|
|
|
21055
21370
|
* @returns {void}
|
|
21056
21371
|
*/
|
|
21057
21372
|
setMenuValue(value) {
|
|
21058
|
-
if (!this.menu) {
|
|
21373
|
+
if (!this.menu || this.menu.value === value) {
|
|
21059
21374
|
return;
|
|
21060
21375
|
}
|
|
21376
|
+
// One-shot flag consumed by the auroMenu-selectedOption listener so the
|
|
21377
|
+
// echo of this sync doesn't propagate back to this.value.
|
|
21378
|
+
this._pendingMenuValueSync = true;
|
|
21061
21379
|
this.menu.value = value;
|
|
21380
|
+
// Backup clear: if menu.value === menu.optionSelected.value already, the
|
|
21381
|
+
// listener won't fire and the flag would swallow the next user click.
|
|
21382
|
+
setTimeout(() => {
|
|
21383
|
+
this._pendingMenuValueSync = false;
|
|
21384
|
+
}, 0);
|
|
21062
21385
|
}
|
|
21063
21386
|
|
|
21064
21387
|
/**
|
|
@@ -21073,6 +21396,7 @@ class AuroCombobox extends AuroElement {
|
|
|
21073
21396
|
this.menu.value = undefined;
|
|
21074
21397
|
this.validation.reset(this);
|
|
21075
21398
|
this.touched = false;
|
|
21399
|
+
this.validity = undefined;
|
|
21076
21400
|
}
|
|
21077
21401
|
|
|
21078
21402
|
/**
|
|
@@ -21080,16 +21404,24 @@ class AuroCombobox extends AuroElement {
|
|
|
21080
21404
|
* @returns {void}
|
|
21081
21405
|
*/
|
|
21082
21406
|
clear() {
|
|
21083
|
-
//
|
|
21084
|
-
|
|
21085
|
-
this.
|
|
21086
|
-
|
|
21087
|
-
// Then clear input and menu
|
|
21088
|
-
if (this.input.value) {
|
|
21089
|
-
this.input.clear();
|
|
21407
|
+
// input.clear() fires an input event that re-enters handleInputValueChange
|
|
21408
|
+
// → which calls clear() again on empty input. Guard against that loop.
|
|
21409
|
+
if (this._clearing) {
|
|
21410
|
+
return;
|
|
21090
21411
|
}
|
|
21091
|
-
|
|
21092
|
-
|
|
21412
|
+
this._clearing = true;
|
|
21413
|
+
try {
|
|
21414
|
+
this.optionSelected = undefined;
|
|
21415
|
+
this.value = undefined;
|
|
21416
|
+
|
|
21417
|
+
if (this.input.value) {
|
|
21418
|
+
this.input.clear();
|
|
21419
|
+
}
|
|
21420
|
+
if (this.menu.value || this.menu.optionSelected) {
|
|
21421
|
+
this.menu.reset();
|
|
21422
|
+
}
|
|
21423
|
+
} finally {
|
|
21424
|
+
this._clearing = false;
|
|
21093
21425
|
}
|
|
21094
21426
|
}
|
|
21095
21427
|
|
|
@@ -21117,29 +21449,35 @@ class AuroCombobox extends AuroElement {
|
|
|
21117
21449
|
this.input.value = this.value;
|
|
21118
21450
|
}
|
|
21119
21451
|
|
|
21120
|
-
|
|
21121
|
-
|
|
21122
|
-
|
|
21123
|
-
|
|
21124
|
-
|
|
21125
|
-
|
|
21126
|
-
}
|
|
21127
|
-
|
|
21128
|
-
if (this.behavior === 'suggestion') {
|
|
21129
|
-
if (!this.menu.options || this.menu.options.length === 0) ; else if (this.menu.options.filter((opt) => opt.value === this.value).length > 0) {
|
|
21452
|
+
// Sync menu.value only when an option actually matches, or when filter
|
|
21453
|
+
// mode needs setMenuValue to dispatch auroMenu-selectValueFailure (the
|
|
21454
|
+
// listener at line 1206 clears combobox.value, mirroring select's
|
|
21455
|
+
// pattern). Otherwise sync the input display for freeform values.
|
|
21456
|
+
if (this.menu.options && this.menu.options.length > 0) {
|
|
21457
|
+
if (this.menu.options.some((opt) => opt.value === this.value) || this.behavior === 'filter') {
|
|
21130
21458
|
this.setMenuValue(this.value);
|
|
21131
21459
|
} else {
|
|
21132
|
-
this.menu.value
|
|
21133
|
-
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
|
|
21140
|
-
|
|
21460
|
+
if (this.menu.value) {
|
|
21461
|
+
this.menu.value = undefined;
|
|
21462
|
+
}
|
|
21463
|
+
// Suggestion-mode freeform value: sync the trigger + bib to show it,
|
|
21464
|
+
// then refresh the filter once the inputs flush (handleInputValueChange
|
|
21465
|
+
// bailed on _syncingDisplayValue).
|
|
21466
|
+
const syncPromise = this.syncInputValuesAcrossTriggerAndBib(this.value || '');
|
|
21467
|
+
if (syncPromise) {
|
|
21468
|
+
syncPromise.then(() => {
|
|
21469
|
+
this._programmaticFilterRefresh = true;
|
|
21470
|
+
this.handleMenuOptions();
|
|
21471
|
+
setTimeout(() => {
|
|
21472
|
+
this._programmaticFilterRefresh = false;
|
|
21473
|
+
}, 0);
|
|
21474
|
+
});
|
|
21475
|
+
}
|
|
21141
21476
|
}
|
|
21142
21477
|
}
|
|
21478
|
+
if (!this.value) {
|
|
21479
|
+
this.clear();
|
|
21480
|
+
}
|
|
21143
21481
|
if (this.value && !this.componentHasFocus) {
|
|
21144
21482
|
// If the value got set programmatically make sure we hide the bib
|
|
21145
21483
|
// when input is not taking the focus (input can be in dropdown.trigger or in bibtemplate)
|
|
@@ -21176,12 +21514,18 @@ class AuroCombobox extends AuroElement {
|
|
|
21176
21514
|
// branch from calling hideBib() when the dropdown was just opened but
|
|
21177
21515
|
// :focus-within hasn't propagated through the top-layer dialog's nested
|
|
21178
21516
|
// shadow DOM boundaries.
|
|
21179
|
-
|
|
21517
|
+
// Skip the show/hide side effects when the availableOptions change came
|
|
21518
|
+
// from a programmatic filter refresh after a value swap — the user
|
|
21519
|
+
// didn't interact, so we shouldn't auto-open the bib (especially for
|
|
21520
|
+
// the no-match path where the existing condition unconditionally fires).
|
|
21521
|
+
if (this._programmaticFilterRefresh) {
|
|
21522
|
+
this._programmaticFilterRefresh = false;
|
|
21523
|
+
} else if ((this.availableOptions.length > 0 && (this.componentHasFocus || this.dropdownOpen)) || (this.menu && this.menu.loading) || (this.availableOptions.length === 0 && this.noMatchOption)) {
|
|
21180
21524
|
this.showBib();
|
|
21181
21525
|
if (!this.availableOptions.includes(this.menu.optionActive)) {
|
|
21182
21526
|
this.activateFirstEnabledAvailableOption();
|
|
21183
21527
|
}
|
|
21184
|
-
} else {
|
|
21528
|
+
} else if (this.dropdown && this.dropdown.isPopoverVisible) {
|
|
21185
21529
|
this.hideBib();
|
|
21186
21530
|
}
|
|
21187
21531
|
}
|