@aurodesignsystem-dev/auro-formkit 0.0.0-pr1492.5 → 0.0.0-pr1493.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/bibtemplate/dist/auro-bibtemplate.d.ts +7 -0
- package/components/bibtemplate/dist/index.js +9 -1
- package/components/bibtemplate/dist/registered.js +9 -1
- package/components/checkbox/demo/css-only.html +57 -0
- package/components/checkbox/demo/css-only.md +146 -0
- 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/demo/pages.json +1 -1
- package/components/checkbox/demo/styles.min.css +1 -1
- package/components/checkbox/demo/why-checkbox.html +57 -0
- package/components/checkbox/demo/why-checkbox.md +86 -0
- package/components/checkbox/dist/index.js +2 -2
- package/components/checkbox/dist/registered.js +2 -2
- package/components/combobox/demo/css-only.html +57 -0
- package/components/combobox/demo/css-only.md +209 -0
- package/components/combobox/demo/customize.md +7 -3
- package/components/combobox/demo/customize.min.js +1846 -1804
- package/components/combobox/demo/getting-started.min.js +1846 -1804
- package/components/combobox/demo/index.min.js +1846 -1804
- package/components/combobox/demo/pages.json +1 -1
- package/components/combobox/demo/styles.min.css +1 -1
- package/components/combobox/demo/why-combobox.html +57 -0
- package/components/combobox/demo/why-combobox.md +113 -0
- package/components/combobox/dist/auro-combobox.d.ts +5 -0
- package/components/combobox/dist/index.js +796 -179
- package/components/combobox/dist/registered.js +796 -179
- package/components/counter/demo/css-only.html +57 -0
- package/components/counter/demo/css-only.md +184 -0
- package/components/counter/demo/customize.min.js +276 -16
- package/components/counter/demo/index.min.js +276 -16
- package/components/counter/demo/keyboard-behavior.md +1 -0
- package/components/counter/demo/pages.json +1 -1
- package/components/counter/demo/styles.min.css +1 -1
- package/components/counter/demo/why-counter.html +57 -0
- package/components/counter/demo/why-counter.md +108 -0
- package/components/counter/dist/index.js +5457 -193
- package/components/counter/dist/registered.js +5457 -193
- package/components/datepicker/demo/accessibility.md +54 -3
- package/components/datepicker/demo/api.md +65 -55
- package/components/datepicker/demo/css-only.html +57 -0
- package/components/datepicker/demo/css-only.md +151 -0
- package/components/datepicker/demo/customize.html +2 -0
- package/components/datepicker/demo/customize.js +19 -0
- package/components/datepicker/demo/customize.md +474 -298
- package/components/datepicker/demo/customize.min.js +36243 -0
- package/components/datepicker/demo/design.md +5 -3
- package/components/datepicker/demo/getting-started.md +6 -6
- package/components/datepicker/demo/index.js +5 -1
- package/components/datepicker/demo/index.md +113 -16
- package/components/datepicker/demo/index.min.js +2735 -396
- package/components/datepicker/demo/keyboard-behavior.md +201 -2
- package/components/datepicker/demo/pages.json +1 -1
- package/components/datepicker/demo/styles.min.css +1 -1
- package/components/datepicker/demo/voiceover.md +21 -12
- package/components/datepicker/demo/why-datepicker.html +57 -0
- package/components/datepicker/demo/why-datepicker.md +133 -0
- package/components/datepicker/dist/index.js +2649 -397
- package/components/datepicker/dist/registered.js +2649 -397
- package/components/datepicker/dist/src/auro-calendar-cell.d.ts +116 -14
- package/components/datepicker/dist/src/auro-calendar-month.d.ts +54 -1
- package/components/datepicker/dist/src/auro-calendar.d.ts +264 -0
- package/components/datepicker/dist/src/auro-datepicker.d.ts +189 -53
- package/components/datepicker/dist/src/datepickerKeyboardStrategy.d.ts +5 -3
- package/components/datepicker/dist/src/utilities.d.ts +0 -14
- package/components/dropdown/demo/accessibility.md +11 -0
- package/components/dropdown/demo/api.md +1 -0
- package/components/dropdown/demo/css-only.html +57 -0
- package/components/dropdown/demo/css-only.md +219 -0
- package/components/dropdown/demo/customize.md +3 -0
- package/components/dropdown/demo/customize.min.js +265 -13
- package/components/dropdown/demo/getting-started.min.js +265 -13
- package/components/dropdown/demo/index.min.js +265 -13
- package/components/dropdown/demo/keyboard-behavior.md +1 -0
- package/components/dropdown/demo/pages.json +1 -1
- package/components/dropdown/demo/styles.min.css +1 -1
- package/components/dropdown/demo/why-dropdown.html +57 -0
- package/components/dropdown/demo/why-dropdown.md +97 -0
- package/components/dropdown/dist/auro-dropdown.d.ts +36 -1
- package/components/dropdown/dist/index.js +265 -13
- package/components/dropdown/dist/registered.js +265 -13
- package/components/form/demo/css-only.html +57 -0
- package/components/form/demo/css-only.md +156 -0
- package/components/form/demo/customize.min.js +11084 -8072
- package/components/form/demo/getting-started.min.js +11084 -8072
- package/components/form/demo/index.min.js +11084 -8072
- package/components/form/demo/pages.json +1 -1
- package/components/form/demo/registerDemoDeps.min.js +11083 -8071
- package/components/form/demo/styles.min.css +1 -1
- package/components/form/demo/why-form.html +57 -0
- package/components/form/demo/why-form.md +101 -0
- package/components/input/demo/css-only.html +57 -0
- package/components/input/demo/css-only.md +199 -0
- package/components/input/demo/customize.md +625 -636
- package/components/input/demo/customize.min.js +150 -75
- package/components/input/demo/getting-started.md +16 -6
- package/components/input/demo/getting-started.min.js +150 -75
- package/components/input/demo/index.min.js +150 -75
- package/components/input/demo/pages.json +1 -1
- package/components/input/demo/styles.min.css +1 -1
- package/components/input/demo/why-input.html +57 -0
- package/components/input/demo/why-input.md +121 -0
- package/components/input/dist/auro-input-util.d.ts +1 -1
- package/components/input/dist/base-input.d.ts +16 -0
- package/components/input/dist/index.js +150 -75
- package/components/input/dist/registered.js +150 -75
- package/components/input/dist/utilities.d.ts +1 -2
- package/components/menu/demo/api.md +41 -45
- package/components/menu/demo/css-only.html +57 -0
- package/components/menu/demo/css-only.md +166 -0
- package/components/menu/demo/customize.md +0 -28
- package/components/menu/demo/index.min.js +779 -1354
- package/components/menu/demo/pages.json +1 -1
- package/components/menu/demo/styles.min.css +1 -1
- package/components/menu/demo/why-menu.html +57 -0
- package/components/menu/demo/why-menu.md +104 -0
- 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/css-only.html +57 -0
- package/components/radio/demo/css-only.md +151 -0
- package/components/radio/demo/customize.min.js +1764 -0
- package/components/radio/demo/demo-support.min.js +55807 -0
- package/components/radio/demo/getting-started.js +1 -1
- package/components/radio/demo/getting-started.md +1 -1
- package/components/radio/demo/getting-started.min.js +1783 -0
- package/components/radio/demo/index.min.js +2 -2
- package/components/radio/demo/pages.json +1 -1
- package/components/radio/demo/styles.min.css +1 -1
- package/components/radio/demo/why-radio.html +57 -0
- package/components/radio/demo/why-radio.md +92 -0
- 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/css-only.html +57 -0
- package/components/select/demo/css-only.md +203 -0
- package/components/select/demo/customize.md +78 -3
- package/components/select/demo/customize.min.js +1341 -1533
- package/components/select/demo/getting-started.min.js +1341 -1533
- package/components/select/demo/index.min.js +1341 -1533
- package/components/select/demo/keyboard-behavior.md +19 -0
- package/components/select/demo/pages.json +1 -1
- package/components/select/demo/styles.min.css +1 -1
- package/components/select/demo/why-select.html +57 -0
- package/components/select/demo/why-select.md +128 -0
- package/components/select/dist/auro-select.d.ts +38 -2
- package/components/select/dist/index.js +435 -52
- package/components/select/dist/registered.js +435 -52
- package/components/select/dist/selectUtils.d.ts +12 -0
- package/custom-elements.json +4206 -3708
- package/package.json +8 -8
- 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
|
|
|
@@ -4158,7 +4175,7 @@ function applyKeyboardStrategy(component, strategy, options = {}) {
|
|
|
4158
4175
|
});
|
|
4159
4176
|
}
|
|
4160
4177
|
|
|
4161
|
-
var styleCss$2$1 = css`:host{position:fixed;z-index:var(--depth-tooltip, 400);display:none;isolation:isolate}:host dialog{width:auto;max-width:none;height:auto;max-height:none;padding:0;border:none;margin:0;outline:none;transform:translateZ(0)}:host dialog::backdrop{background:transparent}:host(:not([isfullscreen])) dialog{position:relative;inset:unset}:host(:not([isfullscreen])) .container.shape-box{border-radius:unset}:host(:not([isfullscreen])) .container[class*=shape-pill],:host(:not([isfullscreen])) .container[class*=shape-snowflake]{border-radius:30px}:host(:not([isfullscreen])) .container[class*=shape-rounded]{border-radius:16px}:host(:not([matchWidth])) .container{min-width:fit-content}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;margin-top:0;box-shadow:unset;overscroll-behavior:contain}:host([isfullscreen]) .container::backdrop{background:var(--ds-color-background-primary, #fff)}:host(:popover-open){position:fixed;overflow:visible;padding:0;border:none;margin:0;background:transparent;inset:unset;outline:none}:host([data-show]){display:flex}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}.container{display:inline-block;overflow:auto;box-sizing:border-box;border-radius:var(--ds-border-radius, 0.375rem);margin:var(--ds-size-50, 0.25rem) 0}.util_displayHiddenVisually{position:absolute;overflow:hidden;width:1px;height:1px;padding:0;border:0;margin:-1px;clip-path:inset(50%);white-space:nowrap}`;
|
|
4178
|
+
var styleCss$2$1 = css`:host{position:fixed;z-index:var(--depth-tooltip, 400);display:none;isolation:isolate}:host dialog{width:auto;max-width:none;height:auto;max-height:none;padding:0;border:none;margin:0;outline:none;transform:translateZ(0)}:host dialog::backdrop{background:transparent}:host(:not([isfullscreen])) dialog{position:relative;inset:unset}:host(:not([isfullscreen])) .container.shape-box{border-radius:unset}:host(:not([isfullscreen])) .container[class*=shape-pill],:host(:not([isfullscreen])) .container[class*=shape-snowflake]{border-radius:30px}:host(:not([isfullscreen])) .container[class*=shape-rounded]{border-radius:16px}:host([desktopmodal]:popover-open)::backdrop{background:transparent}:host(:not([matchWidth])) .container{min-width:fit-content}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;margin-top:0;box-shadow:unset;overscroll-behavior:contain}:host([isfullscreen]) .container::backdrop{background:var(--ds-color-background-primary, #fff)}:host(:popover-open){position:fixed;overflow:visible;padding:0;border:none;margin:0;background:transparent;inset:unset;outline:none}:host([data-show]){display:flex}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}.container{display:inline-block;overflow:auto;box-sizing:border-box;border-radius:var(--ds-border-radius, 0.375rem);margin:var(--ds-size-50, 0.25rem) 0}.util_displayHiddenVisually{position:absolute;overflow:hidden;width:1px;height:1px;padding:0;border:0;margin:-1px;clip-path:inset(50%);white-space:nowrap}`;
|
|
4162
4179
|
|
|
4163
4180
|
var colorCss$2$1 = css`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`;
|
|
4164
4181
|
|
|
@@ -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 = '202606181613';
|
|
4793
4810
|
|
|
4794
4811
|
let AuroElement$2 = class AuroElement extends LitElement {
|
|
4795
4812
|
static get properties() {
|
|
@@ -4969,6 +4986,7 @@ class AuroDropdown extends AuroElement$2 {
|
|
|
4969
4986
|
_intializeDefaults() {
|
|
4970
4987
|
this.appearance = 'default';
|
|
4971
4988
|
this.chevron = false;
|
|
4989
|
+
this.desktopModal = false;
|
|
4972
4990
|
this.disabled = false;
|
|
4973
4991
|
this.disableKeyboardHandling = false;
|
|
4974
4992
|
this.error = false;
|
|
@@ -5149,6 +5167,14 @@ class AuroDropdown extends AuroElement$2 {
|
|
|
5149
5167
|
reflect: true
|
|
5150
5168
|
},
|
|
5151
5169
|
|
|
5170
|
+
/**
|
|
5171
|
+
* If declared, the dropdown will behave as a modal dialog when in a desktop viewport size.
|
|
5172
|
+
*/
|
|
5173
|
+
desktopModal: {
|
|
5174
|
+
type: Boolean,
|
|
5175
|
+
reflect: true
|
|
5176
|
+
},
|
|
5177
|
+
|
|
5152
5178
|
/**
|
|
5153
5179
|
* If declared, the dropdown will only show by calling the API .show() public method.
|
|
5154
5180
|
*/
|
|
@@ -5436,6 +5462,15 @@ class AuroDropdown extends AuroElement$2 {
|
|
|
5436
5462
|
|
|
5437
5463
|
disconnectedCallback() {
|
|
5438
5464
|
super.disconnectedCallback();
|
|
5465
|
+
this._clearPageInert();
|
|
5466
|
+
if (this._bibTabHandler) {
|
|
5467
|
+
this.removeEventListener('keydown', this._bibTabHandler);
|
|
5468
|
+
this._bibTabHandler = undefined;
|
|
5469
|
+
}
|
|
5470
|
+
if (this.focusTrap) {
|
|
5471
|
+
this.focusTrap.disconnect();
|
|
5472
|
+
this.focusTrap = undefined;
|
|
5473
|
+
}
|
|
5439
5474
|
if (this.floater) {
|
|
5440
5475
|
this.floater.hideBib('disconnect');
|
|
5441
5476
|
this.floater.disconnect();
|
|
@@ -5463,19 +5498,45 @@ class AuroDropdown extends AuroElement$2 {
|
|
|
5463
5498
|
if (this.isPopoverVisible) {
|
|
5464
5499
|
// Fullscreen: use showModal() for native accessibility (inert outside, focus trap)
|
|
5465
5500
|
// Desktop: use show() for Floating UI positioning + FocusTrap for focus management
|
|
5466
|
-
|
|
5467
|
-
this.
|
|
5501
|
+
this.bibElement.value.open(this.isBibFullscreen);
|
|
5502
|
+
this.updateFocusTrap();
|
|
5503
|
+
|
|
5504
|
+
// Desktop modal: make siblings inert so content outside is not interactive
|
|
5505
|
+
if (this.desktopModal && !this.isBibFullscreen) {
|
|
5506
|
+
this._setPageInert();
|
|
5507
|
+
}
|
|
5468
5508
|
} else {
|
|
5469
5509
|
this.bibElement.value.close();
|
|
5510
|
+
this._clearPageInert();
|
|
5470
5511
|
}
|
|
5471
5512
|
}
|
|
5472
5513
|
|
|
5473
5514
|
// When fullscreen strategy changes while open, re-open dialog with correct mode
|
|
5474
5515
|
// (e.g. resizing from desktop → mobile while dropdown is open)
|
|
5475
5516
|
if (changedProperties.has('isBibFullscreen') && this.isPopoverVisible && this.bibElement.value) {
|
|
5476
|
-
const useModal = this.isBibFullscreen;
|
|
5477
5517
|
this.bibElement.value.close();
|
|
5478
|
-
this.bibElement.value.open(
|
|
5518
|
+
this.bibElement.value.open(this.isBibFullscreen);
|
|
5519
|
+
|
|
5520
|
+
// Re-initialize focus management for the new strategy
|
|
5521
|
+
this.updateFocusTrap();
|
|
5522
|
+
|
|
5523
|
+
// Toggle inert: desktop modal needs it, fullscreen showModal() handles it natively
|
|
5524
|
+
if (this.desktopModal && !this.isBibFullscreen) {
|
|
5525
|
+
this._setPageInert();
|
|
5526
|
+
} else {
|
|
5527
|
+
this._clearPageInert();
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
|
|
5531
|
+
// Handle desktopModal toggled while the dropdown is already open.
|
|
5532
|
+
// Re-initialize focus trapping and page inert state to match the new mode.
|
|
5533
|
+
if (changedProperties.has('desktopModal') && this.isPopoverVisible && !this.isBibFullscreen) {
|
|
5534
|
+
this.updateFocusTrap();
|
|
5535
|
+
if (this.desktopModal) {
|
|
5536
|
+
this._setPageInert();
|
|
5537
|
+
} else {
|
|
5538
|
+
this._clearPageInert();
|
|
5539
|
+
}
|
|
5479
5540
|
}
|
|
5480
5541
|
}
|
|
5481
5542
|
|
|
@@ -5485,8 +5546,14 @@ class AuroDropdown extends AuroElement$2 {
|
|
|
5485
5546
|
* @param {CustomEvent} event - The custom event that contains the dropdown toggle information.
|
|
5486
5547
|
*/
|
|
5487
5548
|
handleDropdownToggle(event) {
|
|
5488
|
-
this.updateFocusTrap();
|
|
5489
5549
|
this.isPopoverVisible = event.detail.expanded;
|
|
5550
|
+
|
|
5551
|
+
// Tear down FocusTrap when closing. Creation happens in updated()
|
|
5552
|
+
// after the dialog is open so getFocusableElements can find content.
|
|
5553
|
+
if (!this.isPopoverVisible) {
|
|
5554
|
+
this.updateFocusTrap();
|
|
5555
|
+
}
|
|
5556
|
+
|
|
5490
5557
|
const eventType = event.detail.eventType || "unknown";
|
|
5491
5558
|
if (!this.isPopoverVisible && this.hasFocus && eventType === "keydown") {
|
|
5492
5559
|
this.trigger.focus();
|
|
@@ -5585,19 +5652,220 @@ class AuroDropdown extends AuroElement$2 {
|
|
|
5585
5652
|
* @private
|
|
5586
5653
|
*/
|
|
5587
5654
|
updateFocusTrap() {
|
|
5655
|
+
// Always clean up existing handlers/traps before setting up new ones
|
|
5656
|
+
// to prevent duplicate listeners on repeated calls.
|
|
5657
|
+
if (this._bibTabHandler) {
|
|
5658
|
+
this.removeEventListener('keydown', this._bibTabHandler);
|
|
5659
|
+
this._bibTabHandler = undefined;
|
|
5660
|
+
}
|
|
5661
|
+
|
|
5662
|
+
if (this.focusTrap) {
|
|
5663
|
+
this.focusTrap.disconnect();
|
|
5664
|
+
this.focusTrap = undefined;
|
|
5665
|
+
}
|
|
5666
|
+
|
|
5667
|
+
// Restore the user-set noHideOnThisFocusLoss value, if we overrode it.
|
|
5668
|
+
if (this._noHideOverridden) {
|
|
5669
|
+
this.noHideOnThisFocusLoss = this._priorNoHide;
|
|
5670
|
+
this._noHideOverridden = false;
|
|
5671
|
+
this._priorNoHide = undefined;
|
|
5672
|
+
}
|
|
5673
|
+
|
|
5588
5674
|
if (this.isPopoverVisible) {
|
|
5589
5675
|
if (!this.isBibFullscreen) {
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5676
|
+
if (this.desktopModal) {
|
|
5677
|
+
// The floater's focus-loss check uses :focus-within, which does not
|
|
5678
|
+
// match the host when focus is on a slotted element projected through
|
|
5679
|
+
// multiple shadow roots. Without this override, focusing a button
|
|
5680
|
+
// inside the bib would be misread as focus leaving the dropdown and
|
|
5681
|
+
// close the bib immediately. desktopModal traps focus inside the
|
|
5682
|
+
// bib and inerts siblings, so suppressing focus-loss dismissal here
|
|
5683
|
+
// is safe — Escape and outside-click still close the bib.
|
|
5684
|
+
this._priorNoHide = this.noHideOnThisFocusLoss;
|
|
5685
|
+
this._noHideOverridden = true;
|
|
5686
|
+
this.noHideOnThisFocusLoss = true;
|
|
5687
|
+
|
|
5688
|
+
// Desktop modal: trap focus only within the bib content.
|
|
5689
|
+
// Can't use FocusTrap on the bib element because keydown events
|
|
5690
|
+
// from slotted content bubble through the dropdown host (light DOM),
|
|
5691
|
+
// not through the bib (shadow projection target). Using FocusTrap
|
|
5692
|
+
// on the dropdown would include the trigger in the tab cycle.
|
|
5693
|
+
// Instead, listen for Tab on the dropdown and manually wrap focus
|
|
5694
|
+
// within the bib's focusable elements.
|
|
5695
|
+
this._bibTabHandler = (event) => {
|
|
5696
|
+
if (event.key !== 'Tab') {
|
|
5697
|
+
return;
|
|
5698
|
+
}
|
|
5699
|
+
|
|
5700
|
+
// Collect focusable elements from the bib content.
|
|
5701
|
+
const focusables = getFocusableElements(this.bibContent);
|
|
5702
|
+
|
|
5703
|
+
// Fallback: try from slotted content directly
|
|
5704
|
+
if (!focusables.length) {
|
|
5705
|
+
const slot = this.shadowRoot.querySelector('.slotContent slot');
|
|
5706
|
+
const assignedNodes = slot.assignedNodes({ flatten: true });
|
|
5707
|
+
|
|
5708
|
+
for (const node of assignedNodes) {
|
|
5709
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
5710
|
+
focusables.push(...getFocusableElements(node));
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
}
|
|
5714
|
+
|
|
5715
|
+
if (!focusables.length) {
|
|
5716
|
+
return;
|
|
5717
|
+
}
|
|
5718
|
+
|
|
5719
|
+
event.preventDefault();
|
|
5720
|
+
|
|
5721
|
+
const direction = event.shiftKey ? -1 : 1; // eslint-disable-line no-magic-numbers
|
|
5722
|
+
|
|
5723
|
+
// Walk the active element chain through shadow roots
|
|
5724
|
+
const actives = this._getActiveElements();
|
|
5725
|
+
|
|
5726
|
+
let idx = focusables.findIndex((el) => actives.includes(el));
|
|
5727
|
+
|
|
5728
|
+
if (idx === -1) { // eslint-disable-line no-magic-numbers
|
|
5729
|
+
// Focus is not on a known element — move to first/last
|
|
5730
|
+
idx = direction === 1 ? -1 : focusables.length; // eslint-disable-line no-magic-numbers
|
|
5731
|
+
}
|
|
5732
|
+
|
|
5733
|
+
// Try each element in order, skipping any that can't receive focus
|
|
5734
|
+
// (e.g. hidden elements, elements in collapsed sections)
|
|
5735
|
+
for (let index = 0; index < focusables.length; index++) { // eslint-disable-line no-plusplus
|
|
5736
|
+
let nextIdx = idx + direction;
|
|
5737
|
+
|
|
5738
|
+
// Wrap around
|
|
5739
|
+
if (nextIdx < 0) {
|
|
5740
|
+
nextIdx = focusables.length - 1;
|
|
5741
|
+
} else if (nextIdx >= focusables.length) {
|
|
5742
|
+
nextIdx = 0;
|
|
5743
|
+
}
|
|
5744
|
+
|
|
5745
|
+
focusables[nextIdx].focus();
|
|
5746
|
+
|
|
5747
|
+
// Verify focus actually moved to the target
|
|
5748
|
+
const newActives = this._getActiveElements();
|
|
5749
|
+
|
|
5750
|
+
if (newActives.includes(focusables[nextIdx])) {
|
|
5751
|
+
return;
|
|
5752
|
+
}
|
|
5753
|
+
|
|
5754
|
+
// Focus didn't stick — skip this element and try the next
|
|
5755
|
+
idx = nextIdx;
|
|
5756
|
+
}
|
|
5757
|
+
};
|
|
5758
|
+
this.addEventListener('keydown', this._bibTabHandler);
|
|
5759
|
+
|
|
5760
|
+
// Move initial focus into the bib content, matching FocusTrap behavior
|
|
5761
|
+
requestAnimationFrame(() => {
|
|
5762
|
+
const focusables = getFocusableElements(this.bibContent);
|
|
5763
|
+
if (focusables.length) {
|
|
5764
|
+
focusables[0].focus();
|
|
5765
|
+
}
|
|
5766
|
+
});
|
|
5767
|
+
} else {
|
|
5768
|
+
// Normal desktop: use FocusTrap on the bib element.
|
|
5769
|
+
// Defer focusFirstElement to the next frame because the popover
|
|
5770
|
+
// is positioned asynchronously by Floating UI — calling focus()
|
|
5771
|
+
// synchronously here would target elements with zero dimensions
|
|
5772
|
+
// and the focus call would be silently ignored.
|
|
5773
|
+
this.focusTrap = new FocusTrap(this.bibContent);
|
|
5774
|
+
requestAnimationFrame(() => {
|
|
5775
|
+
if (this.focusTrap) {
|
|
5776
|
+
this.focusTrap.focusFirstElement();
|
|
5777
|
+
}
|
|
5778
|
+
});
|
|
5779
|
+
}
|
|
5593
5780
|
}
|
|
5594
5781
|
// Fullscreen: showModal() provides native focus trapping
|
|
5782
|
+
}
|
|
5783
|
+
}
|
|
5784
|
+
|
|
5785
|
+
/**
|
|
5786
|
+
* Returns the chain of active (focused) elements through shadow roots.
|
|
5787
|
+
* @private
|
|
5788
|
+
* @returns {Array<HTMLElement>}
|
|
5789
|
+
*/
|
|
5790
|
+
_getActiveElements() {
|
|
5791
|
+
let { activeElement } = document;
|
|
5792
|
+
const actives = [activeElement];
|
|
5793
|
+
|
|
5794
|
+
while (activeElement?.shadowRoot?.activeElement) {
|
|
5795
|
+
activeElement = activeElement.shadowRoot.activeElement;
|
|
5796
|
+
actives.push(activeElement);
|
|
5797
|
+
}
|
|
5798
|
+
|
|
5799
|
+
return actives;
|
|
5800
|
+
}
|
|
5801
|
+
|
|
5802
|
+
/**
|
|
5803
|
+
* Sets `inert` on sibling elements of the dropdown's top-level host
|
|
5804
|
+
* so that content outside the dropdown is not interactive while the modal is open.
|
|
5805
|
+
* Walks up through shadow DOM boundaries to find the outermost host element
|
|
5806
|
+
* in the light DOM, then sets `inert` on siblings at each ancestor level
|
|
5807
|
+
* to ensure all page content outside the host subtree is inert.
|
|
5808
|
+
* @private
|
|
5809
|
+
*/
|
|
5810
|
+
_setPageInert() {
|
|
5811
|
+
if (this._inertSiblings) {
|
|
5595
5812
|
return;
|
|
5596
5813
|
}
|
|
5597
5814
|
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5815
|
+
this._inertSiblings = [];
|
|
5816
|
+
|
|
5817
|
+
// Walk up through shadow DOM boundaries to find the topmost host
|
|
5818
|
+
// element in the light DOM. For example, if this dropdown is inside
|
|
5819
|
+
// auro-datepicker's shadow DOM, we walk up to the datepicker element
|
|
5820
|
+
// so we set inert on its siblings — not on the datepicker itself.
|
|
5821
|
+
let host = this;
|
|
5822
|
+
while (host.getRootNode() instanceof ShadowRoot) {
|
|
5823
|
+
host = host.getRootNode().host;
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5826
|
+
// Walk up the ancestor chain, inerting siblings at each level
|
|
5827
|
+
// to ensure the entire page outside the host subtree is inert.
|
|
5828
|
+
// Uses a reference counter (data-auro-inert-count) so multiple
|
|
5829
|
+
// simultaneous modal dropdowns share inert state safely.
|
|
5830
|
+
let current = host;
|
|
5831
|
+
while (current.parentElement) {
|
|
5832
|
+
const parent = current.parentElement;
|
|
5833
|
+
for (const sibling of parent.children) {
|
|
5834
|
+
if (sibling !== current) {
|
|
5835
|
+
const count = parseInt(sibling.dataset.auroInertCount || '0', 10);
|
|
5836
|
+
if (count === 0) {
|
|
5837
|
+
sibling.dataset.auroInertWas = sibling.inert ? 'true' : 'false';
|
|
5838
|
+
}
|
|
5839
|
+
sibling.dataset.auroInertCount = String(count + 1);
|
|
5840
|
+
sibling.inert = true;
|
|
5841
|
+
this._inertSiblings.push(sibling);
|
|
5842
|
+
}
|
|
5843
|
+
}
|
|
5844
|
+
current = parent;
|
|
5845
|
+
}
|
|
5846
|
+
}
|
|
5847
|
+
|
|
5848
|
+
/**
|
|
5849
|
+
* Restores `inert` state on siblings that were tracked by `_setPageInert`.
|
|
5850
|
+
* Uses reference counting so inert is only cleared when the last modal
|
|
5851
|
+
* dropdown releases a given element. Preserves the original inert state
|
|
5852
|
+
* so externally-inerted elements are not inadvertently re-enabled.
|
|
5853
|
+
* @private
|
|
5854
|
+
*/
|
|
5855
|
+
_clearPageInert() {
|
|
5856
|
+
if (this._inertSiblings) {
|
|
5857
|
+
for (const sibling of this._inertSiblings) {
|
|
5858
|
+
const count = parseInt(sibling.dataset.auroInertCount || '1', 10) - 1;
|
|
5859
|
+
if (count <= 0) {
|
|
5860
|
+
const wasInert = sibling.dataset.auroInertWas === 'true';
|
|
5861
|
+
delete sibling.dataset.auroInertCount;
|
|
5862
|
+
delete sibling.dataset.auroInertWas;
|
|
5863
|
+
sibling.inert = wasInert;
|
|
5864
|
+
} else {
|
|
5865
|
+
sibling.dataset.auroInertCount = String(count);
|
|
5866
|
+
}
|
|
5867
|
+
}
|
|
5868
|
+
this._inertSiblings = undefined;
|
|
5601
5869
|
}
|
|
5602
5870
|
}
|
|
5603
5871
|
|
|
@@ -5836,6 +6104,7 @@ class AuroDropdown extends AuroElement$2 {
|
|
|
5836
6104
|
shape="${this.shape}"
|
|
5837
6105
|
?data-show="${this.isPopoverVisible}"
|
|
5838
6106
|
?isfullscreen="${this.isBibFullscreen}"
|
|
6107
|
+
?desktopmodal="${this.desktopModal}"
|
|
5839
6108
|
.dialogLabel="${this.bibDialogLabel}"
|
|
5840
6109
|
${ref(this.bibElement)}
|
|
5841
6110
|
>
|
|
@@ -10410,7 +10679,7 @@ class AuroFormValidation {
|
|
|
10410
10679
|
} else if (this.runtimeUtils.elementMatch(elem, 'auro-input') && elem.errorMessage === '') {
|
|
10411
10680
|
const input = elem.renderRoot.querySelector('input');
|
|
10412
10681
|
|
|
10413
|
-
if (input.validationMessage.length > 0) {
|
|
10682
|
+
if (input && input.validationMessage.length > 0) {
|
|
10414
10683
|
elem.errorMessage = input.validationMessage;
|
|
10415
10684
|
}
|
|
10416
10685
|
} else if (this.runtimeUtils.elementMatch(elem, 'auro-combobox') && elem.errorMessage === '') {
|
|
@@ -16082,7 +16351,11 @@ class AuroInputUtilities {
|
|
|
16082
16351
|
return part.value;
|
|
16083
16352
|
}
|
|
16084
16353
|
})
|
|
16085
|
-
.join("")
|
|
16354
|
+
.join("")
|
|
16355
|
+
// Remove whitespace and leading/trailing special characters for IMask compatibility
|
|
16356
|
+
// for example, ko-KR's format is `YYYY. MM. DD.` which causes issues with IMask if we don't remove the trailing period.
|
|
16357
|
+
.replace(/\s/gu, '')
|
|
16358
|
+
.replace(/^[^A-Z0-9]+|[^A-Z0-9]+$/gu, '');
|
|
16086
16359
|
}
|
|
16087
16360
|
|
|
16088
16361
|
/**
|
|
@@ -16238,27 +16511,16 @@ class AuroInputUtilities {
|
|
|
16238
16511
|
* Converts a model value to a display value for the input element.
|
|
16239
16512
|
* For full date formats, converts an ISO model value to the configured display format.
|
|
16240
16513
|
* @private
|
|
16241
|
-
* @param {string} value - The model value (ISO string for date types).
|
|
16242
16514
|
* @param {Date|undefined} valueObject - Date object representation of value.
|
|
16243
16515
|
* @param {string} format - The date format string.
|
|
16244
|
-
* @returns {string}
|
|
16516
|
+
* @returns {string | undefined}
|
|
16245
16517
|
*/
|
|
16246
|
-
toFormattedValue(
|
|
16247
|
-
if (!this.isFullDateFormat('date', format) || !value) {
|
|
16248
|
-
return value;
|
|
16249
|
-
}
|
|
16250
|
-
|
|
16251
|
-
if (!dateFormatter.isValidDate(value)) {
|
|
16252
|
-
// For ISO-pattern strings that fail range validation (e.g. '2024-99-99'),
|
|
16253
|
-
// return '' so inputElement stays empty and format-based validation is not triggered.
|
|
16254
|
-
return (/^\d{4}-\d{2}-\d{2}$/u).test(value) ? '' : value;
|
|
16255
|
-
}
|
|
16256
|
-
|
|
16518
|
+
toFormattedValue(valueObject, format) {
|
|
16257
16519
|
const normalizedFormat = format.toLowerCase();
|
|
16258
16520
|
const maskOptions = this.getMaskOptions('date', normalizedFormat);
|
|
16259
16521
|
|
|
16260
16522
|
if (!(valueObject instanceof Date) || Number.isNaN(valueObject.getTime()) || !maskOptions || typeof maskOptions.format !== 'function') {
|
|
16261
|
-
return
|
|
16523
|
+
return undefined;
|
|
16262
16524
|
}
|
|
16263
16525
|
|
|
16264
16526
|
return maskOptions.format(valueObject);
|
|
@@ -16960,7 +17222,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
16960
17222
|
* @returns {Date|undefined}
|
|
16961
17223
|
*/
|
|
16962
17224
|
get valueObject() {
|
|
16963
|
-
return this._valueObject;
|
|
17225
|
+
return this._valueObject || this._computeDateObjectFallback(this.value);
|
|
16964
17226
|
}
|
|
16965
17227
|
|
|
16966
17228
|
/**
|
|
@@ -16968,7 +17230,7 @@ class BaseInput extends AuroElement$1 {
|
|
|
16968
17230
|
* @returns {Date|undefined}
|
|
16969
17231
|
*/
|
|
16970
17232
|
get minObject() {
|
|
16971
|
-
return this._minObject;
|
|
17233
|
+
return this._minObject || this._computeDateObjectFallback(this.min);
|
|
16972
17234
|
}
|
|
16973
17235
|
|
|
16974
17236
|
/**
|
|
@@ -16976,7 +17238,31 @@ class BaseInput extends AuroElement$1 {
|
|
|
16976
17238
|
* @returns {Date|undefined}
|
|
16977
17239
|
*/
|
|
16978
17240
|
get maxObject() {
|
|
16979
|
-
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);
|
|
16980
17266
|
}
|
|
16981
17267
|
|
|
16982
17268
|
/**
|
|
@@ -17001,8 +17287,30 @@ class BaseInput extends AuroElement$1 {
|
|
|
17001
17287
|
connectedCallback() {
|
|
17002
17288
|
super.connectedCallback();
|
|
17003
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
|
+
|
|
17004
17299
|
this.locale = this.domHandler.getLocale(this);
|
|
17005
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();
|
|
17006
17314
|
}
|
|
17007
17315
|
|
|
17008
17316
|
disconnectedCallback() {
|
|
@@ -17023,8 +17331,8 @@ class BaseInput extends AuroElement$1 {
|
|
|
17023
17331
|
}
|
|
17024
17332
|
|
|
17025
17333
|
// add attribute for query selectors when auro-input is registered under a custom name
|
|
17026
|
-
if (this.tagName.toLowerCase() !== 'auro-input') {
|
|
17027
|
-
this.setAttribute('auro-input',
|
|
17334
|
+
if (this.tagName.toLowerCase() !== 'auro-input' && !this.hasAttribute('auro-input')) {
|
|
17335
|
+
this.setAttribute('auro-input', '');
|
|
17028
17336
|
}
|
|
17029
17337
|
this.inputId = this.id ? `${this.id}-input` : window.crypto.randomUUID();
|
|
17030
17338
|
|
|
@@ -17072,6 +17380,12 @@ class BaseInput extends AuroElement$1 {
|
|
|
17072
17380
|
return;
|
|
17073
17381
|
}
|
|
17074
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
|
+
|
|
17075
17389
|
// If all guard clauses are passed, dispatch the event
|
|
17076
17390
|
const inputEvent = new InputEvent('input', {
|
|
17077
17391
|
bubbles: true,
|
|
@@ -17176,11 +17490,30 @@ class BaseInput extends AuroElement$1 {
|
|
|
17176
17490
|
this.hasValue = false;
|
|
17177
17491
|
}
|
|
17178
17492
|
|
|
17179
|
-
|
|
17493
|
+
let formattedValue = this.value;
|
|
17494
|
+
if (this.type === 'date') {
|
|
17495
|
+
const formattedDate = this.util.toFormattedValue(this.valueObject, this.format);
|
|
17496
|
+
if (!formattedDate) {
|
|
17497
|
+
// if user entered unrecognized date format that cannot be parsed into a Date object,
|
|
17498
|
+
// keep the raw value in the input so they can edit it instead of overwriting with an empty string
|
|
17499
|
+
formattedValue = this.value;
|
|
17500
|
+
} else {
|
|
17501
|
+
formattedValue = formattedDate;
|
|
17502
|
+
}
|
|
17503
|
+
}
|
|
17180
17504
|
|
|
17181
17505
|
if (formattedValue !== this.inputElement.value) {
|
|
17182
17506
|
this.skipNextProgrammaticInputEvent = true;
|
|
17183
|
-
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) {
|
|
17184
17517
|
this.inputElement.value = formattedValue;
|
|
17185
17518
|
} else {
|
|
17186
17519
|
this.inputElement.value = '';
|
|
@@ -17289,58 +17622,73 @@ class BaseInput extends AuroElement$1 {
|
|
|
17289
17622
|
* @returns {void}
|
|
17290
17623
|
*/
|
|
17291
17624
|
configureAutoFormatting() {
|
|
17292
|
-
|
|
17293
|
-
|
|
17294
|
-
|
|
17295
|
-
|
|
17296
|
-
|
|
17297
|
-
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
|
+
}
|
|
17298
17635
|
|
|
17299
|
-
|
|
17636
|
+
// Pass new format to util
|
|
17637
|
+
this.util.updateFormat(this.format);
|
|
17300
17638
|
|
|
17301
|
-
|
|
17639
|
+
const maskOptions = this.util.getMaskOptions(this.type, this.format);
|
|
17302
17640
|
|
|
17303
|
-
|
|
17304
|
-
// IMask's constructor processes the current input value which requires
|
|
17305
|
-
// selection state — clearing first avoids that scenario entirely.
|
|
17306
|
-
// When the format changes (e.g. locale switch) and we have a valid ISO
|
|
17307
|
-
// model value, compute the display string for the NEW format instead of
|
|
17308
|
-
// re-using the old display string, which may be invalid in the new mask.
|
|
17309
|
-
let existingValue = this.inputElement.value;
|
|
17310
|
-
if (
|
|
17311
|
-
this.util.isFullDateFormat(this.type, this.format) &&
|
|
17312
|
-
this.value &&
|
|
17313
|
-
dateFormatter.isValidDate(this.value) &&
|
|
17314
|
-
this.valueObject instanceof Date &&
|
|
17315
|
-
!Number.isNaN(this.valueObject.getTime()) &&
|
|
17316
|
-
typeof maskOptions.format === 'function'
|
|
17317
|
-
) {
|
|
17318
|
-
existingValue = maskOptions.format(this.valueObject);
|
|
17319
|
-
}
|
|
17641
|
+
if (this.inputElement && maskOptions.mask) {
|
|
17320
17642
|
|
|
17321
|
-
|
|
17322
|
-
|
|
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
|
+
}
|
|
17323
17660
|
|
|
17324
|
-
|
|
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
|
+
});
|
|
17325
17676
|
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
this.
|
|
17330
|
-
|
|
17331
|
-
|
|
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
|
+
});
|
|
17332
17684
|
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
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;
|
|
17337
17688
|
}
|
|
17338
|
-
});
|
|
17339
|
-
|
|
17340
|
-
// Restore the stashed value through IMask so it's properly masked
|
|
17341
|
-
if (existingValue) {
|
|
17342
|
-
this.maskInstance.value = existingValue;
|
|
17343
17689
|
}
|
|
17690
|
+
} finally {
|
|
17691
|
+
this._configuringMask = false;
|
|
17344
17692
|
}
|
|
17345
17693
|
}
|
|
17346
17694
|
|
|
@@ -17627,10 +17975,10 @@ class BaseInput extends AuroElement$1 {
|
|
|
17627
17975
|
},
|
|
17628
17976
|
{
|
|
17629
17977
|
name: 'Diners club',
|
|
17630
|
-
regex: /^(?<num>36|38)\d{0}/u,
|
|
17978
|
+
regex: /^(?<num>30[0-5]|36|38)\d{0}/u,
|
|
17631
17979
|
formatLength: 16,
|
|
17632
17980
|
errorMessage: CreditCardValidationMessage,
|
|
17633
|
-
cardIcon: '
|
|
17981
|
+
cardIcon: 'cc-dinersclub',
|
|
17634
17982
|
maskFormat: "0000 000000 0000"
|
|
17635
17983
|
},
|
|
17636
17984
|
{
|
|
@@ -18040,7 +18388,7 @@ let AuroHelpText$1 = class AuroHelpText extends LitElement {
|
|
|
18040
18388
|
}
|
|
18041
18389
|
};
|
|
18042
18390
|
|
|
18043
|
-
var formkitVersion$1 = '
|
|
18391
|
+
var formkitVersion$1 = '202606181613';
|
|
18044
18392
|
|
|
18045
18393
|
// Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
|
|
18046
18394
|
// See LICENSE in the project root for license information.
|
|
@@ -18378,10 +18726,6 @@ class AuroInput extends BaseInput {
|
|
|
18378
18726
|
return i18n(this.lang, this.type);
|
|
18379
18727
|
}
|
|
18380
18728
|
|
|
18381
|
-
if (this.type === 'date') {
|
|
18382
|
-
return i18n(this.lang, this.dateFormatMap[this.format] || 'dateMMDDYYYY');
|
|
18383
|
-
}
|
|
18384
|
-
|
|
18385
18729
|
return '';
|
|
18386
18730
|
}
|
|
18387
18731
|
|
|
@@ -19046,6 +19390,14 @@ class AuroBibtemplate extends LitElement {
|
|
|
19046
19390
|
large: {
|
|
19047
19391
|
type: Boolean,
|
|
19048
19392
|
reflect: true
|
|
19393
|
+
},
|
|
19394
|
+
|
|
19395
|
+
/**
|
|
19396
|
+
* If declared, the footer slot will be rendered even when not in fullscreen mode.
|
|
19397
|
+
*/
|
|
19398
|
+
showFooter: {
|
|
19399
|
+
type: Boolean,
|
|
19400
|
+
reflect: true
|
|
19049
19401
|
}
|
|
19050
19402
|
};
|
|
19051
19403
|
}
|
|
@@ -19148,7 +19500,7 @@ class AuroBibtemplate extends LitElement {
|
|
|
19148
19500
|
<slot></slot>
|
|
19149
19501
|
</div>
|
|
19150
19502
|
|
|
19151
|
-
${this.isFullscreen ? html`
|
|
19503
|
+
${this.isFullscreen || this.showFooter ? html`
|
|
19152
19504
|
<div id="footerContainer">
|
|
19153
19505
|
<slot name="footer"></slot>
|
|
19154
19506
|
</div>` : null}
|
|
@@ -19157,7 +19509,7 @@ class AuroBibtemplate extends LitElement {
|
|
|
19157
19509
|
}
|
|
19158
19510
|
}
|
|
19159
19511
|
|
|
19160
|
-
var formkitVersion = '
|
|
19512
|
+
var formkitVersion = '202606181613';
|
|
19161
19513
|
|
|
19162
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}`;
|
|
19163
19515
|
|
|
@@ -19514,6 +19866,25 @@ function normalizeFilterValue(value) {
|
|
|
19514
19866
|
return raw.trim() ? raw.trimEnd() : raw;
|
|
19515
19867
|
}
|
|
19516
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
|
+
|
|
19517
19888
|
// See https://git.io/JJ6SJ for "How to document your components using JSDoc"
|
|
19518
19889
|
/**
|
|
19519
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.
|
|
@@ -19641,7 +20012,19 @@ class AuroCombobox extends AuroElement {
|
|
|
19641
20012
|
availableOptions: {
|
|
19642
20013
|
state: true,
|
|
19643
20014
|
type: Array,
|
|
19644
|
-
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
|
+
}
|
|
19645
20028
|
},
|
|
19646
20029
|
|
|
19647
20030
|
/**
|
|
@@ -20072,18 +20455,15 @@ class AuroCombobox extends AuroElement {
|
|
|
20072
20455
|
matchString = `${matchString} ${option.getAttribute('suggest')}`.toLowerCase();
|
|
20073
20456
|
}
|
|
20074
20457
|
|
|
20075
|
-
|
|
20076
|
-
|
|
20077
|
-
|
|
20078
|
-
|
|
20079
|
-
|
|
20080
|
-
|
|
20081
|
-
} else if (matchString.includes(filterValue) && !option.hasAttribute('static')) {
|
|
20082
|
-
// 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')) {
|
|
20083
20464
|
option.removeAttribute('hidden');
|
|
20084
20465
|
this.availableOptions.push(option);
|
|
20085
20466
|
} else if (!option.hasAttribute('persistent')) {
|
|
20086
|
-
// Hide all other non-persistent options
|
|
20087
20467
|
option.setAttribute('hidden', '');
|
|
20088
20468
|
option.removeAttribute('aria-setsize');
|
|
20089
20469
|
option.removeAttribute('aria-posinset');
|
|
@@ -20091,7 +20471,9 @@ class AuroCombobox extends AuroElement {
|
|
|
20091
20471
|
});
|
|
20092
20472
|
|
|
20093
20473
|
if (this.availableOptions.length === 0) {
|
|
20094
|
-
|
|
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) {
|
|
20095
20477
|
this.noMatchOption.removeAttribute('hidden');
|
|
20096
20478
|
} else if (!this.menu.loading || this.isHiddenWhileLoading) {
|
|
20097
20479
|
this.hideBib();
|
|
@@ -20107,11 +20489,14 @@ class AuroCombobox extends AuroElement {
|
|
|
20107
20489
|
* @returns {void}
|
|
20108
20490
|
*/
|
|
20109
20491
|
syncValuesAndStates() {
|
|
20110
|
-
//
|
|
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.
|
|
20111
20496
|
if (this.menu) {
|
|
20112
20497
|
this.menu.matchWord = normalizeFilterValue(this.input.value);
|
|
20113
20498
|
}
|
|
20114
|
-
const label = this.menu.currentLabel;
|
|
20499
|
+
const label = getOptionLabel(this.menu.optionSelected) || this.menu.currentLabel;
|
|
20115
20500
|
this.updateTriggerTextDisplay(label || this.value);
|
|
20116
20501
|
}
|
|
20117
20502
|
|
|
@@ -20126,7 +20511,39 @@ class AuroCombobox extends AuroElement {
|
|
|
20126
20511
|
const isInputFocusedWithNoSelection = !this.menu.value && (this.input.matches(':focus-within') || (this.inputInBib && this.inputInBib.matches(':focus-within')));
|
|
20127
20512
|
|
|
20128
20513
|
if (!this.persistInput && !(this.behavior === 'suggestion' && isInputFocusedWithNoSelection)) {
|
|
20129
|
-
|
|
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
|
+
}
|
|
20130
20547
|
}
|
|
20131
20548
|
|
|
20132
20549
|
// update the displayValue in the trigger if displayValue slot content is present
|
|
@@ -20140,6 +20557,14 @@ class AuroCombobox extends AuroElement {
|
|
|
20140
20557
|
const displayValueEl = this.menu.optionSelected.querySelector("[slot='displayValue']");
|
|
20141
20558
|
if (displayValueEl) {
|
|
20142
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
|
+
}
|
|
20143
20568
|
}
|
|
20144
20569
|
}
|
|
20145
20570
|
|
|
@@ -20173,7 +20598,10 @@ class AuroCombobox extends AuroElement {
|
|
|
20173
20598
|
this.syncValuesAndStates();
|
|
20174
20599
|
}
|
|
20175
20600
|
|
|
20176
|
-
|
|
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) {
|
|
20177
20605
|
this.activateFirstEnabledAvailableOption();
|
|
20178
20606
|
}
|
|
20179
20607
|
}
|
|
@@ -20269,6 +20697,14 @@ class AuroCombobox extends AuroElement {
|
|
|
20269
20697
|
// keyboard navigation continues from the correct place in the page
|
|
20270
20698
|
if (this.dropdown.isBibFullscreen) {
|
|
20271
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
|
+
});
|
|
20272
20708
|
}
|
|
20273
20709
|
}
|
|
20274
20710
|
|
|
@@ -20298,12 +20734,27 @@ class AuroCombobox extends AuroElement {
|
|
|
20298
20734
|
|
|
20299
20735
|
guardTouchPassthrough(this.menu);
|
|
20300
20736
|
|
|
20301
|
-
//
|
|
20302
|
-
//
|
|
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
|
+
|
|
20303
20744
|
doubleRaf(() => {
|
|
20304
20745
|
this.setInputFocus();
|
|
20305
20746
|
this._inFullscreenTransition = false;
|
|
20306
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
|
+
});
|
|
20307
20758
|
}
|
|
20308
20759
|
}
|
|
20309
20760
|
});
|
|
@@ -20380,21 +20831,52 @@ class AuroCombobox extends AuroElement {
|
|
|
20380
20831
|
*/
|
|
20381
20832
|
setInputFocus() {
|
|
20382
20833
|
if (this.dropdown.isBibFullscreen && this.dropdown.isPopoverVisible) {
|
|
20383
|
-
|
|
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
|
+
}
|
|
20384
20856
|
|
|
20385
20857
|
// Place cursor at end of existing text so the user can continue editing
|
|
20386
|
-
const nativeInput = this.inputInBib.inputElement;
|
|
20387
20858
|
if (nativeInput && nativeInput.value) {
|
|
20388
20859
|
const len = nativeInput.value.length;
|
|
20389
20860
|
nativeInput.setSelectionRange(len, len);
|
|
20390
20861
|
}
|
|
20391
|
-
} else
|
|
20392
|
-
|
|
20393
|
-
|
|
20394
|
-
//
|
|
20395
|
-
|
|
20396
|
-
|
|
20397
|
-
|
|
20862
|
+
} else {
|
|
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);
|
|
20398
20880
|
}
|
|
20399
20881
|
}
|
|
20400
20882
|
}
|
|
@@ -20491,53 +20973,92 @@ class AuroCombobox extends AuroElement {
|
|
|
20491
20973
|
this.menu.setAttribute('nocheckmark', '');
|
|
20492
20974
|
}
|
|
20493
20975
|
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
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;
|
|
20497
20982
|
|
|
20498
|
-
|
|
20499
|
-
|
|
20500
|
-
// Update the optionSelected from the event details, not manually
|
|
20501
|
-
[this.optionSelected] = event.detail.options;
|
|
20983
|
+
if (this.menu.optionSelected) {
|
|
20984
|
+
const selected = this.menu.optionSelected;
|
|
20502
20985
|
|
|
20503
|
-
|
|
20504
|
-
|
|
20986
|
+
if (!this.optionSelected || this.optionSelected !== selected) {
|
|
20987
|
+
this.optionSelected = selected;
|
|
20988
|
+
}
|
|
20505
20989
|
|
|
20506
|
-
|
|
20507
|
-
|
|
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
|
+
}
|
|
20508
20997
|
|
|
20509
|
-
|
|
20510
|
-
|
|
20511
|
-
if (this.menu.matchWord !== trimmedInput) {
|
|
20512
|
-
this.menu.matchWord = trimmedInput;
|
|
20513
|
-
}
|
|
20998
|
+
// Update display
|
|
20999
|
+
this.updateTriggerTextDisplay(getOptionLabel(this.optionSelected) || this.menu.value);
|
|
20514
21000
|
|
|
20515
|
-
|
|
20516
|
-
|
|
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
|
+
}
|
|
20517
21007
|
|
|
20518
21008
|
// Hide dropdown on selection (except during slot changes)
|
|
20519
|
-
if (
|
|
20520
|
-
|
|
20521
|
-
|
|
20522
|
-
|
|
20523
|
-
|
|
20524
|
-
}
|
|
20525
|
-
}, 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
|
+
}
|
|
20526
21014
|
|
|
20527
21015
|
// Announce the selection after the dropdown closes so it isn't
|
|
20528
21016
|
// overridden by VoiceOver's "collapsed" announcement from aria-expanded.
|
|
20529
|
-
const selectedValue =
|
|
21017
|
+
const selectedValue = this.menu.value;
|
|
20530
21018
|
const announcementDelay = 300;
|
|
20531
21019
|
setTimeout(() => {
|
|
20532
21020
|
announceToScreenReader(this._getAnnouncementRoot(), `${selectedValue}, selected`);
|
|
20533
21021
|
}, announcementDelay);
|
|
20534
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
|
+
}
|
|
20535
21047
|
});
|
|
20536
21048
|
|
|
20537
21049
|
this.menu.addEventListener('auroMenu-customEventFired', () => {
|
|
20538
21050
|
this.hideBib();
|
|
20539
21051
|
});
|
|
20540
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
|
+
|
|
20541
21062
|
this.menu.addEventListener('auroMenu-activatedOption', (evt) => {
|
|
20542
21063
|
this.optionActive = evt.detail;
|
|
20543
21064
|
|
|
@@ -20638,11 +21159,12 @@ class AuroCombobox extends AuroElement {
|
|
|
20638
21159
|
// event from the trigger. The _syncingBibValue guard persists across the
|
|
20639
21160
|
// async boundary and prevents that re-entrant event from running the
|
|
20640
21161
|
// non-fullscreen path (which would call clear() → hideBib()).
|
|
20641
|
-
// When the event comes from the fullscreen bib input, sync the value to
|
|
20642
|
-
// the trigger and run filtering, but suppress the re-entrant input event
|
|
20643
|
-
// that the trigger fires (via Lit updated() → notifyValueChanged()) so
|
|
20644
|
-
// the non-fullscreen hide/clear logic doesn't close the dialog.
|
|
20645
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
|
+
}
|
|
20646
21168
|
this._syncingBibValue = true;
|
|
20647
21169
|
this.input.value = this.inputInBib.value;
|
|
20648
21170
|
this.input.updateComplete.then(() => {
|
|
@@ -20652,18 +21174,37 @@ class AuroCombobox extends AuroElement {
|
|
|
20652
21174
|
// Run filtering inline — the re-entrant event won't reach this code.
|
|
20653
21175
|
this.menu.matchWord = normalizeFilterValue(this.inputInBib.value);
|
|
20654
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
|
+
|
|
20655
21185
|
this.handleMenuOptions();
|
|
20656
21186
|
this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
|
|
20657
21187
|
return;
|
|
20658
21188
|
}
|
|
20659
21189
|
|
|
20660
|
-
// Ignore re-entrant input events caused by
|
|
20661
|
-
if (this._syncingBibValue) {
|
|
21190
|
+
// Ignore re-entrant input events caused by programmatic value sets.
|
|
21191
|
+
if (this._syncingBibValue || this._syncingDisplayValue) {
|
|
20662
21192
|
return;
|
|
20663
21193
|
}
|
|
20664
21194
|
|
|
20665
21195
|
this.inputInBib.value = this.input.value;
|
|
20666
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
|
+
|
|
20667
21208
|
this.menu.matchWord = normalizeFilterValue(this.input.value);
|
|
20668
21209
|
this.optionActive = null;
|
|
20669
21210
|
|
|
@@ -20671,7 +21212,7 @@ class AuroCombobox extends AuroElement {
|
|
|
20671
21212
|
this.value = this.input.value;
|
|
20672
21213
|
}
|
|
20673
21214
|
|
|
20674
|
-
if (!this.input.value) {
|
|
21215
|
+
if (!this.input.value && !this._clearing) {
|
|
20675
21216
|
this.clear();
|
|
20676
21217
|
}
|
|
20677
21218
|
this.handleMenuOptions();
|
|
@@ -20740,6 +21281,16 @@ class AuroCombobox extends AuroElement {
|
|
|
20740
21281
|
return;
|
|
20741
21282
|
}
|
|
20742
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
|
+
|
|
20743
21294
|
this.input.validity = evt.detail.validity;
|
|
20744
21295
|
this.input.errorMessage = evt.detail.message;
|
|
20745
21296
|
this.validity = evt.detail.validity;
|
|
@@ -20801,10 +21352,18 @@ class AuroCombobox extends AuroElement {
|
|
|
20801
21352
|
* @returns {void}
|
|
20802
21353
|
*/
|
|
20803
21354
|
setMenuValue(value) {
|
|
20804
|
-
if (!this.menu) {
|
|
21355
|
+
if (!this.menu || this.menu.value === value) {
|
|
20805
21356
|
return;
|
|
20806
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;
|
|
20807
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);
|
|
20808
21367
|
}
|
|
20809
21368
|
|
|
20810
21369
|
/**
|
|
@@ -20819,6 +21378,15 @@ class AuroCombobox extends AuroElement {
|
|
|
20819
21378
|
this.menu.value = undefined;
|
|
20820
21379
|
this.validation.reset(this);
|
|
20821
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 = '';
|
|
20822
21390
|
}
|
|
20823
21391
|
|
|
20824
21392
|
/**
|
|
@@ -20826,16 +21394,24 @@ class AuroCombobox extends AuroElement {
|
|
|
20826
21394
|
* @returns {void}
|
|
20827
21395
|
*/
|
|
20828
21396
|
clear() {
|
|
20829
|
-
//
|
|
20830
|
-
|
|
20831
|
-
this.
|
|
20832
|
-
|
|
20833
|
-
// Then clear input and menu
|
|
20834
|
-
if (this.input.value) {
|
|
20835
|
-
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;
|
|
20836
21401
|
}
|
|
20837
|
-
|
|
20838
|
-
|
|
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;
|
|
20839
21415
|
}
|
|
20840
21416
|
}
|
|
20841
21417
|
|
|
@@ -20863,29 +21439,64 @@ class AuroCombobox extends AuroElement {
|
|
|
20863
21439
|
this.input.value = this.value;
|
|
20864
21440
|
}
|
|
20865
21441
|
|
|
20866
|
-
|
|
20867
|
-
|
|
20868
|
-
|
|
20869
|
-
|
|
20870
|
-
|
|
20871
|
-
|
|
20872
|
-
|
|
20873
|
-
|
|
20874
|
-
|
|
20875
|
-
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.
|
|
20876
21451
|
this.setMenuValue(this.value);
|
|
20877
21452
|
} else {
|
|
20878
|
-
this.menu.value
|
|
20879
|
-
|
|
20880
|
-
|
|
20881
|
-
|
|
20882
|
-
|
|
20883
|
-
|
|
20884
|
-
|
|
20885
|
-
|
|
20886
|
-
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
|
+
}
|
|
20887
21495
|
}
|
|
20888
21496
|
}
|
|
21497
|
+
if (!this.value) {
|
|
21498
|
+
this.clear();
|
|
21499
|
+
}
|
|
20889
21500
|
if (this.value && !this.componentHasFocus) {
|
|
20890
21501
|
// If the value got set programmatically make sure we hide the bib
|
|
20891
21502
|
// when input is not taking the focus (input can be in dropdown.trigger or in bibtemplate)
|
|
@@ -20922,12 +21533,18 @@ class AuroCombobox extends AuroElement {
|
|
|
20922
21533
|
// branch from calling hideBib() when the dropdown was just opened but
|
|
20923
21534
|
// :focus-within hasn't propagated through the top-layer dialog's nested
|
|
20924
21535
|
// shadow DOM boundaries.
|
|
20925
|
-
|
|
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)) {
|
|
20926
21543
|
this.showBib();
|
|
20927
21544
|
if (!this.availableOptions.includes(this.menu.optionActive)) {
|
|
20928
21545
|
this.activateFirstEnabledAvailableOption();
|
|
20929
21546
|
}
|
|
20930
|
-
} else {
|
|
21547
|
+
} else if (this.dropdown && this.dropdown.isPopoverVisible) {
|
|
20931
21548
|
this.hideBib();
|
|
20932
21549
|
}
|
|
20933
21550
|
}
|