@aurodesignsystem-dev/auro-formkit 0.0.0-pr1389.0 → 0.0.0-pr1390.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.
Files changed (40) hide show
  1. package/components/checkbox/demo/api.min.js +1 -1
  2. package/components/checkbox/demo/index.min.js +1 -1
  3. package/components/checkbox/dist/index.js +1 -1
  4. package/components/checkbox/dist/registered.js +1 -1
  5. package/components/combobox/demo/api.min.js +116 -46
  6. package/components/combobox/demo/index.min.js +116 -46
  7. package/components/combobox/dist/comboboxKeyboardStrategy.d.ts +4 -4
  8. package/components/combobox/dist/index.js +116 -46
  9. package/components/combobox/dist/registered.js +116 -46
  10. package/components/counter/demo/api.min.js +4 -22
  11. package/components/counter/demo/index.min.js +4 -22
  12. package/components/counter/dist/auro-counter.d.ts +0 -7
  13. package/components/counter/dist/index.js +4 -22
  14. package/components/counter/dist/registered.js +4 -22
  15. package/components/datepicker/demo/api.min.js +3 -3
  16. package/components/datepicker/demo/index.min.js +3 -3
  17. package/components/datepicker/dist/index.js +3 -3
  18. package/components/datepicker/dist/registered.js +3 -3
  19. package/components/dropdown/demo/api.min.js +1 -1
  20. package/components/dropdown/demo/index.min.js +1 -1
  21. package/components/dropdown/dist/index.js +1 -1
  22. package/components/dropdown/dist/registered.js +1 -1
  23. package/components/form/demo/api.min.js +182 -86
  24. package/components/form/demo/index.min.js +182 -86
  25. package/components/input/demo/api.min.js +1 -1
  26. package/components/input/demo/index.min.js +1 -1
  27. package/components/input/dist/index.js +1 -1
  28. package/components/input/dist/registered.js +1 -1
  29. package/components/radio/demo/api.min.js +1 -1
  30. package/components/radio/demo/index.min.js +1 -1
  31. package/components/radio/dist/index.js +1 -1
  32. package/components/radio/dist/registered.js +1 -1
  33. package/components/select/demo/api.min.js +56 -12
  34. package/components/select/demo/index.min.js +56 -12
  35. package/components/select/dist/index.js +56 -12
  36. package/components/select/dist/registered.js +56 -12
  37. package/components/select/dist/selectKeyboardStrategy.d.ts +3 -3
  38. package/custom-elements.json +1407 -1497
  39. package/package.json +1 -1
  40. package/components/dropdown/dist/keyboardUtils.d.ts +0 -18
@@ -1687,7 +1687,7 @@ class AuroHelpText extends i$2 {
1687
1687
  }
1688
1688
  }
1689
1689
 
1690
- var formkitVersion = '202603191747';
1690
+ var formkitVersion = '202603201627';
1691
1691
 
1692
1692
  // Copyright (c) 2026 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1693
1693
  // See LICENSE in the project root for license information.
@@ -1679,7 +1679,7 @@ class AuroHelpText extends i$2 {
1679
1679
  }
1680
1680
  }
1681
1681
 
1682
- var formkitVersion = '202603191747';
1682
+ var formkitVersion = '202603201627';
1683
1683
 
1684
1684
  // Copyright (c) 2026 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1685
1685
  // See LICENSE in the project root for license information.
@@ -1632,7 +1632,7 @@ class AuroHelpText extends LitElement {
1632
1632
  }
1633
1633
  }
1634
1634
 
1635
- var formkitVersion = '202603191747';
1635
+ var formkitVersion = '202603201627';
1636
1636
 
1637
1637
  // Copyright (c) 2026 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1638
1638
  // See LICENSE in the project root for license information.
@@ -1632,7 +1632,7 @@ class AuroHelpText extends LitElement {
1632
1632
  }
1633
1633
  }
1634
1634
 
1635
- var formkitVersion = '202603191747';
1635
+ var formkitVersion = '202603201627';
1636
1636
 
1637
1637
  // Copyright (c) 2026 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
1638
1638
  // See LICENSE in the project root for license information.
@@ -1231,17 +1231,53 @@ function restoreTriggerAfterClose(dropdown, focusTarget) {
1231
1231
  * SPDX-License-Identifier: BSD-3-Clause
1232
1232
  */let e$2 = class e extends i$2{constructor(i){if(super(i),this.it=A$2,i.type!==t$1.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===A$2||null==r)return this._t=void 0,this.it=r;if(r===E)return r;if("string"!=typeof r)throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this._t;this.it=r;const s=[r];return s.raw=s,this._t={_$litType$:this.constructor.resultType,strings:s,values:[]}}};e$2.directiveName="unsafeHTML",e$2.resultType=1;
1233
1233
 
1234
+ /**
1235
+ * Computes display state once per keydown event.
1236
+ * Centralizes null-safety checks and makes the shared/modal/popover branching explicit.
1237
+ *
1238
+ * @param {HTMLElement} component - The component with a dropdown reference.
1239
+ * @param {Object} [options] - Optional config.
1240
+ * @param {HTMLElement} [options.dropdown] - Explicit dropdown reference. Falls back to component.dropdown.
1241
+ * @param {Function} [options.inputResolver] - Called with (component, ctx) to resolve the active input element.
1242
+ * @returns {{isExpanded: boolean, isModal: boolean, isPopover: boolean, activeInput: HTMLElement|null}}
1243
+ * isModal and isPopover reflect the display mode (fullscreen vs not) regardless of expanded state.
1244
+ */
1245
+ function createDisplayContext(component, options = {}) {
1246
+ const dd = options.dropdown || component.dropdown;
1247
+ // isPopoverVisible reflects as the `open` attribute.
1248
+ // It reports whether the bib is open in any mode (popover or modal).
1249
+ const isExpanded = Boolean(dd && dd.isPopoverVisible);
1250
+ const isFullscreen = Boolean(dd && dd.isBibFullscreen);
1251
+
1252
+ const ctx = {
1253
+ isExpanded,
1254
+ isModal: isFullscreen,
1255
+ isPopover: !isFullscreen,
1256
+ activeInput: null,
1257
+ };
1258
+
1259
+ if (options.inputResolver) {
1260
+ const resolvedInput = options.inputResolver(component, ctx);
1261
+ // Guard against resolvers returning undefined or non-HTMLElement values.
1262
+ ctx.activeInput = resolvedInput instanceof HTMLElement ? resolvedInput : null;
1263
+ }
1264
+
1265
+ return ctx;
1266
+ }
1267
+
1234
1268
  /**
1235
1269
  * Wires up a keydown listener that dispatches to strategy[evt.key] or strategy.default.
1236
1270
  * Handles both sync and async handlers.
1237
1271
  * @param {HTMLElement} component - The component to attach the listener to.
1238
1272
  * @param {Object} strategy - Map of key names to handler functions.
1273
+ * @param {Object} [options] - Optional config passed to createDisplayContext.
1239
1274
  */
1240
- function applyKeyboardStrategy(component, strategy) {
1275
+ function applyKeyboardStrategy(component, strategy, options = {}) {
1241
1276
  component.addEventListener('keydown', async (evt) => {
1242
1277
  const handler = strategy[evt.key] || strategy.default;
1243
- if (handler) {
1244
- await handler(component, evt);
1278
+ if (typeof handler === 'function') {
1279
+ const ctx = createDisplayContext(component, options);
1280
+ await handler(component, evt, ctx);
1245
1281
  }
1246
1282
  });
1247
1283
  }
@@ -1253,39 +1289,58 @@ function applyKeyboardStrategy(component, strategy) {
1253
1289
  * @param {string} direction - 'up' or 'down'.
1254
1290
  * @param {Object} [options] - Optional config.
1255
1291
  * @param {Function} [options.showFn] - Called to open the dropdown when closed.
1292
+ * @param {Object} [options.ctx] - Display context to avoid re-checking visibility.
1256
1293
  */
1257
1294
  function navigateArrow(component, direction, options = {}) {
1258
- if (component.dropdown.isPopoverVisible) {
1295
+ const visible = options.ctx ? options.ctx.isExpanded : component.dropdown.isPopoverVisible;
1296
+ if (visible) {
1259
1297
  component.menu.navigateOptions(direction);
1260
1298
  } else if (options.showFn) {
1261
1299
  options.showFn();
1262
1300
  }
1263
1301
  }
1264
1302
 
1303
+ /**
1304
+ * Returns the clear button element from the active input's shadow
1305
+ * DOM, if available.
1306
+ * @param {Object} ctx - Display context with activeInput.
1307
+ * @returns {Element|null}
1308
+ */
1309
+ function getClearBtn(ctx) {
1310
+ const root = ctx && ctx.activeInput && ctx.activeInput.shadowRoot;
1311
+ if (!root) {
1312
+ return null;
1313
+ }
1314
+ return root.querySelector('.clearBtn');
1315
+ }
1316
+
1317
+ /**
1318
+ * Returns true when the clear button inside the active input's shadow DOM has focus.
1319
+ * Uses shadowRoot.activeElement to detect focus inside auro-button,
1320
+ * since Safari does not propagate :focus-within through shadow DOM.
1321
+ * @param {Object} ctx - Display context with activeInput.
1322
+ * @param {Element} [clearBtn=getClearBtn(ctx)] - Pre-fetched clear button element.
1323
+ * @returns {boolean}
1324
+ */
1325
+ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
1326
+ if (!clearBtn) {
1327
+ return false;
1328
+ }
1329
+ return Boolean(clearBtn.shadowRoot && clearBtn.shadowRoot.activeElement !== null);
1330
+ }
1331
+
1265
1332
  const comboboxKeyboardStrategy = {
1266
- async Enter(component, evt) {
1333
+ async Enter(component, evt, ctx) {
1267
1334
  // If the clear button has focus, let the browser activate it normally.
1268
1335
  // stopPropagation prevents parent containers (e.g., forms) from treating
1269
1336
  // Enter as a submit, but we must NOT call preventDefault — that would
1270
1337
  // block the browser's built-in "Enter activates focused button" behavior.
1271
- const isBibFullscreenActive =
1272
- component.dropdown &&
1273
- component.dropdown.isBibFullscreen &&
1274
- component.dropdown.isPopoverVisible;
1275
- const activeInput =
1276
- isBibFullscreenActive && component.inputInBib
1277
- ? component.inputInBib
1278
- : component.input;
1279
- const clearBtn =
1280
- activeInput && activeInput.shadowRoot
1281
- ? activeInput.shadowRoot.querySelector('.clearBtn')
1282
- : null;
1283
- if (clearBtn && clearBtn.shadowRoot && clearBtn.shadowRoot.activeElement !== null) {
1338
+ if (isClearBtnFocused(ctx)) {
1284
1339
  evt.stopPropagation();
1285
1340
  return;
1286
1341
  }
1287
1342
 
1288
- if (component.dropdown.isPopoverVisible && component.optionActive) {
1343
+ if (ctx.isExpanded && component.optionActive) {
1289
1344
  component.menu.makeSelection();
1290
1345
  await component.updateComplete;
1291
1346
  evt.preventDefault();
@@ -1302,20 +1357,20 @@ const comboboxKeyboardStrategy = {
1302
1357
  }
1303
1358
  },
1304
1359
 
1305
- Tab(component) {
1306
- if (!component.dropdown.isPopoverVisible) {
1360
+ Tab(component, _evt, ctx) {
1361
+ if (!ctx.isExpanded) {
1307
1362
  return;
1308
1363
  }
1309
1364
 
1310
- if (component.dropdown.isBibFullscreen) {
1311
- const clearBtn = component.inputInBib.shadowRoot.querySelector('.clearBtn');
1312
-
1313
- // Use shadowRoot.activeElement to detect focus inside auro-button,
1314
- // since Safari does not propagate :focus-within through shadow DOM.
1315
- const clearBtnHasFocus = clearBtn && clearBtn.shadowRoot && clearBtn.shadowRoot.activeElement !== null;
1365
+ if (ctx.isModal) {
1366
+ if (!ctx.activeInput) {
1367
+ return;
1368
+ }
1369
+ const clearBtn = getClearBtn(ctx);
1370
+ const clearBtnHasFocus = isClearBtnFocused(ctx, clearBtn);
1316
1371
 
1317
1372
  // Tab from input: if clear button exists and doesn't have focus, focus it
1318
- if (clearBtn && !clearBtnHasFocus && component.inputInBib.value) {
1373
+ if (clearBtn && !clearBtnHasFocus && ctx.activeInput.value) {
1319
1374
  // Force clear button container visible to work around Safari not
1320
1375
  // propagating :focus-within through shadow DOM boundaries, which
1321
1376
  // causes .wrapper:not(:focus-within) to hide .notification.clear.
@@ -1358,30 +1413,34 @@ const comboboxKeyboardStrategy = {
1358
1413
  component.hideBib();
1359
1414
  },
1360
1415
 
1361
- ArrowUp(component, evt) {
1362
- // If the clear button has focus, let the browser handle ArrowUp normally
1363
- if (component.clearBtnFocused) {
1416
+ ArrowUp(component, evt, ctx) {
1417
+ // If the clear button has focus, let the browser handle ArrowUp normally.
1418
+ if (isClearBtnFocused(ctx)) {
1364
1419
  return;
1365
1420
  }
1366
1421
 
1367
1422
  if (component.availableOptions.length > 0) {
1368
1423
  component.showBib();
1369
1424
  }
1425
+ // Read live visibility — ctx.isExpanded was computed before showBib() above,
1426
+ // so it wouldn't reflect the state change.
1370
1427
  if (component.dropdown.isPopoverVisible) {
1371
1428
  evt.preventDefault();
1372
1429
  navigateArrow(component, 'up');
1373
1430
  }
1374
1431
  },
1375
1432
 
1376
- ArrowDown(component, evt) {
1377
- // If the clear button has focus, let the browser handle ArrowDown normally
1378
- if (component.clearBtnFocused) {
1433
+ ArrowDown(component, evt, ctx) {
1434
+ // If the clear button has focus, let the browser handle ArrowDown normally.
1435
+ if (isClearBtnFocused(ctx)) {
1379
1436
  return;
1380
1437
  }
1381
1438
 
1382
1439
  if (component.availableOptions.length > 0) {
1383
1440
  component.showBib();
1384
1441
  }
1442
+ // Read live visibility — ctx.isExpanded was computed before showBib() above,
1443
+ // so it wouldn't reflect the state change.
1385
1444
  if (component.dropdown.isPopoverVisible) {
1386
1445
  evt.preventDefault();
1387
1446
  navigateArrow(component, 'down');
@@ -4977,7 +5036,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$4 {
4977
5036
  }
4978
5037
  };
4979
5038
 
4980
- var formkitVersion$2 = '202603191747';
5039
+ var formkitVersion$2 = '202603201627';
4981
5040
 
4982
5041
  let AuroElement$2 = class AuroElement extends i$4 {
4983
5042
  static get properties() {
@@ -12732,7 +12791,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$4 {
12732
12791
  }
12733
12792
  };
12734
12793
 
12735
- var formkitVersion$1 = '202603191747';
12794
+ var formkitVersion$1 = '202603201627';
12736
12795
 
12737
12796
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
12738
12797
  // See LICENSE in the project root for license information.
@@ -13771,7 +13830,7 @@ class AuroBibtemplate extends i$4 {
13771
13830
  }
13772
13831
  }
13773
13832
 
13774
- var formkitVersion = '202603191747';
13833
+ var formkitVersion = '202603201627';
13775
13834
 
13776
13835
  var styleCss$3 = i$7`.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}`;
13777
13836
 
@@ -14628,6 +14687,13 @@ class AuroCombobox extends AuroElement {
14628
14687
 
14629
14688
  this.noMatchOption = undefined;
14630
14689
 
14690
+ // Remove trailing whitespace only when the input contains
14691
+ // non-whitespace characters, so residual spaces from cursor
14692
+ // editing don't break matching. Whitespace-only input is
14693
+ // preserved as-is so a lone space doesn't show all options.
14694
+ const raw = this.input.value || '';
14695
+ const filterValue = (raw.trim() ? raw.trimEnd() : raw).toLowerCase();
14696
+
14631
14697
  this.options.forEach((option) => {
14632
14698
  let matchString = option.textContent.toLowerCase();
14633
14699
 
@@ -14644,12 +14710,12 @@ class AuroCombobox extends AuroElement {
14644
14710
  }
14645
14711
 
14646
14712
  // If input is empty, show all options (except static ones)
14647
- if (!this.input.value || this.input.value.length === 0) {
14713
+ if (!filterValue) {
14648
14714
  if (!option.hasAttribute('static')) {
14649
14715
  option.removeAttribute('hidden');
14650
14716
  this.availableOptions.push(option);
14651
14717
  }
14652
- } else if (matchString.includes(this.input.value.toLowerCase()) && !option.hasAttribute('static')) {
14718
+ } else if (matchString.includes(filterValue) && !option.hasAttribute('static')) {
14653
14719
  // only count options that match the typed input value AND are not static
14654
14720
  option.removeAttribute('hidden');
14655
14721
  this.availableOptions.push(option);
@@ -14680,7 +14746,7 @@ class AuroCombobox extends AuroElement {
14680
14746
  syncValuesAndStates() {
14681
14747
  // Only sync matchWord, don't set menu.value here since setMenuValue should handle that
14682
14748
  if (this.menu) {
14683
- this.menu.matchWord = this.input.value;
14749
+ this.menu.matchWord = (this.input.value || '').trimEnd();
14684
14750
  }
14685
14751
  const label = this.menu.currentLabel;
14686
14752
  this.updateTriggerTextDisplay(label || this.value);
@@ -15044,8 +15110,9 @@ class AuroCombobox extends AuroElement {
15044
15110
  this.updateTriggerTextDisplay(event.detail.label || event.detail.value);
15045
15111
 
15046
15112
  // Update match word for filtering
15047
- if (this.menu.matchWord !== this.input.value) {
15048
- this.menu.matchWord = this.input.value;
15113
+ const trimmedInput = (this.input.value || '').trimEnd();
15114
+ if (this.menu.matchWord !== trimmedInput) {
15115
+ this.menu.matchWord = trimmedInput;
15049
15116
  }
15050
15117
 
15051
15118
  // Update available options based on selection
@@ -15183,7 +15250,7 @@ class AuroCombobox extends AuroElement {
15183
15250
  });
15184
15251
 
15185
15252
  // Run filtering inline — the re-entrant event won't reach this code.
15186
- this.menu.matchWord = this.inputInBib.value;
15253
+ this.menu.matchWord = (this.inputInBib.value || '').trimEnd();
15187
15254
  this.optionActive = null;
15188
15255
  this.handleMenuOptions();
15189
15256
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
@@ -15197,7 +15264,7 @@ class AuroCombobox extends AuroElement {
15197
15264
 
15198
15265
  this.inputInBib.value = this.input.value;
15199
15266
 
15200
- this.menu.matchWord = this.input.value;
15267
+ this.menu.matchWord = (this.input.value || '').trimEnd();
15201
15268
  this.optionActive = null;
15202
15269
 
15203
15270
  if (!this.input.value) {
@@ -15249,7 +15316,10 @@ class AuroCombobox extends AuroElement {
15249
15316
  * @returns {void}
15250
15317
  */
15251
15318
  configureCombobox() {
15252
- applyKeyboardStrategy(this, comboboxKeyboardStrategy);
15319
+ applyKeyboardStrategy(this, comboboxKeyboardStrategy, {
15320
+ // In modal mode the input moves into the bib; route keyboard events to that element instead.
15321
+ inputResolver: (comp, ctx) => (ctx.isModal && comp.inputInBib ? comp.inputInBib : comp.input),
15322
+ });
15253
15323
 
15254
15324
  this.addEventListener('focusin', () => {
15255
15325
  this.touched = true;
@@ -15408,7 +15478,7 @@ class AuroCombobox extends AuroElement {
15408
15478
 
15409
15479
  // Sync the input and match word, but don't directly set menu.value again
15410
15480
  if (this.menu) {
15411
- this.menu.matchWord = this.input.value;
15481
+ this.menu.matchWord = (this.input.value || '').trimEnd();
15412
15482
  }
15413
15483
 
15414
15484
  this.dispatchEvent(new CustomEvent('input', {
@@ -1154,17 +1154,53 @@ function restoreTriggerAfterClose(dropdown, focusTarget) {
1154
1154
  * SPDX-License-Identifier: BSD-3-Clause
1155
1155
  */let e$2 = class e extends i$2{constructor(i){if(super(i),this.it=A$2,i.type!==t$1.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===A$2||null==r)return this._t=void 0,this.it=r;if(r===E)return r;if("string"!=typeof r)throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this._t;this.it=r;const s=[r];return s.raw=s,this._t={_$litType$:this.constructor.resultType,strings:s,values:[]}}};e$2.directiveName="unsafeHTML",e$2.resultType=1;
1156
1156
 
1157
+ /**
1158
+ * Computes display state once per keydown event.
1159
+ * Centralizes null-safety checks and makes the shared/modal/popover branching explicit.
1160
+ *
1161
+ * @param {HTMLElement} component - The component with a dropdown reference.
1162
+ * @param {Object} [options] - Optional config.
1163
+ * @param {HTMLElement} [options.dropdown] - Explicit dropdown reference. Falls back to component.dropdown.
1164
+ * @param {Function} [options.inputResolver] - Called with (component, ctx) to resolve the active input element.
1165
+ * @returns {{isExpanded: boolean, isModal: boolean, isPopover: boolean, activeInput: HTMLElement|null}}
1166
+ * isModal and isPopover reflect the display mode (fullscreen vs not) regardless of expanded state.
1167
+ */
1168
+ function createDisplayContext(component, options = {}) {
1169
+ const dd = options.dropdown || component.dropdown;
1170
+ // isPopoverVisible reflects as the `open` attribute.
1171
+ // It reports whether the bib is open in any mode (popover or modal).
1172
+ const isExpanded = Boolean(dd && dd.isPopoverVisible);
1173
+ const isFullscreen = Boolean(dd && dd.isBibFullscreen);
1174
+
1175
+ const ctx = {
1176
+ isExpanded,
1177
+ isModal: isFullscreen,
1178
+ isPopover: !isFullscreen,
1179
+ activeInput: null,
1180
+ };
1181
+
1182
+ if (options.inputResolver) {
1183
+ const resolvedInput = options.inputResolver(component, ctx);
1184
+ // Guard against resolvers returning undefined or non-HTMLElement values.
1185
+ ctx.activeInput = resolvedInput instanceof HTMLElement ? resolvedInput : null;
1186
+ }
1187
+
1188
+ return ctx;
1189
+ }
1190
+
1157
1191
  /**
1158
1192
  * Wires up a keydown listener that dispatches to strategy[evt.key] or strategy.default.
1159
1193
  * Handles both sync and async handlers.
1160
1194
  * @param {HTMLElement} component - The component to attach the listener to.
1161
1195
  * @param {Object} strategy - Map of key names to handler functions.
1196
+ * @param {Object} [options] - Optional config passed to createDisplayContext.
1162
1197
  */
1163
- function applyKeyboardStrategy(component, strategy) {
1198
+ function applyKeyboardStrategy(component, strategy, options = {}) {
1164
1199
  component.addEventListener('keydown', async (evt) => {
1165
1200
  const handler = strategy[evt.key] || strategy.default;
1166
- if (handler) {
1167
- await handler(component, evt);
1201
+ if (typeof handler === 'function') {
1202
+ const ctx = createDisplayContext(component, options);
1203
+ await handler(component, evt, ctx);
1168
1204
  }
1169
1205
  });
1170
1206
  }
@@ -1176,39 +1212,58 @@ function applyKeyboardStrategy(component, strategy) {
1176
1212
  * @param {string} direction - 'up' or 'down'.
1177
1213
  * @param {Object} [options] - Optional config.
1178
1214
  * @param {Function} [options.showFn] - Called to open the dropdown when closed.
1215
+ * @param {Object} [options.ctx] - Display context to avoid re-checking visibility.
1179
1216
  */
1180
1217
  function navigateArrow(component, direction, options = {}) {
1181
- if (component.dropdown.isPopoverVisible) {
1218
+ const visible = options.ctx ? options.ctx.isExpanded : component.dropdown.isPopoverVisible;
1219
+ if (visible) {
1182
1220
  component.menu.navigateOptions(direction);
1183
1221
  } else if (options.showFn) {
1184
1222
  options.showFn();
1185
1223
  }
1186
1224
  }
1187
1225
 
1226
+ /**
1227
+ * Returns the clear button element from the active input's shadow
1228
+ * DOM, if available.
1229
+ * @param {Object} ctx - Display context with activeInput.
1230
+ * @returns {Element|null}
1231
+ */
1232
+ function getClearBtn(ctx) {
1233
+ const root = ctx && ctx.activeInput && ctx.activeInput.shadowRoot;
1234
+ if (!root) {
1235
+ return null;
1236
+ }
1237
+ return root.querySelector('.clearBtn');
1238
+ }
1239
+
1240
+ /**
1241
+ * Returns true when the clear button inside the active input's shadow DOM has focus.
1242
+ * Uses shadowRoot.activeElement to detect focus inside auro-button,
1243
+ * since Safari does not propagate :focus-within through shadow DOM.
1244
+ * @param {Object} ctx - Display context with activeInput.
1245
+ * @param {Element} [clearBtn=getClearBtn(ctx)] - Pre-fetched clear button element.
1246
+ * @returns {boolean}
1247
+ */
1248
+ function isClearBtnFocused(ctx, clearBtn = getClearBtn(ctx)) {
1249
+ if (!clearBtn) {
1250
+ return false;
1251
+ }
1252
+ return Boolean(clearBtn.shadowRoot && clearBtn.shadowRoot.activeElement !== null);
1253
+ }
1254
+
1188
1255
  const comboboxKeyboardStrategy = {
1189
- async Enter(component, evt) {
1256
+ async Enter(component, evt, ctx) {
1190
1257
  // If the clear button has focus, let the browser activate it normally.
1191
1258
  // stopPropagation prevents parent containers (e.g., forms) from treating
1192
1259
  // Enter as a submit, but we must NOT call preventDefault — that would
1193
1260
  // block the browser's built-in "Enter activates focused button" behavior.
1194
- const isBibFullscreenActive =
1195
- component.dropdown &&
1196
- component.dropdown.isBibFullscreen &&
1197
- component.dropdown.isPopoverVisible;
1198
- const activeInput =
1199
- isBibFullscreenActive && component.inputInBib
1200
- ? component.inputInBib
1201
- : component.input;
1202
- const clearBtn =
1203
- activeInput && activeInput.shadowRoot
1204
- ? activeInput.shadowRoot.querySelector('.clearBtn')
1205
- : null;
1206
- if (clearBtn && clearBtn.shadowRoot && clearBtn.shadowRoot.activeElement !== null) {
1261
+ if (isClearBtnFocused(ctx)) {
1207
1262
  evt.stopPropagation();
1208
1263
  return;
1209
1264
  }
1210
1265
 
1211
- if (component.dropdown.isPopoverVisible && component.optionActive) {
1266
+ if (ctx.isExpanded && component.optionActive) {
1212
1267
  component.menu.makeSelection();
1213
1268
  await component.updateComplete;
1214
1269
  evt.preventDefault();
@@ -1225,20 +1280,20 @@ const comboboxKeyboardStrategy = {
1225
1280
  }
1226
1281
  },
1227
1282
 
1228
- Tab(component) {
1229
- if (!component.dropdown.isPopoverVisible) {
1283
+ Tab(component, _evt, ctx) {
1284
+ if (!ctx.isExpanded) {
1230
1285
  return;
1231
1286
  }
1232
1287
 
1233
- if (component.dropdown.isBibFullscreen) {
1234
- const clearBtn = component.inputInBib.shadowRoot.querySelector('.clearBtn');
1235
-
1236
- // Use shadowRoot.activeElement to detect focus inside auro-button,
1237
- // since Safari does not propagate :focus-within through shadow DOM.
1238
- const clearBtnHasFocus = clearBtn && clearBtn.shadowRoot && clearBtn.shadowRoot.activeElement !== null;
1288
+ if (ctx.isModal) {
1289
+ if (!ctx.activeInput) {
1290
+ return;
1291
+ }
1292
+ const clearBtn = getClearBtn(ctx);
1293
+ const clearBtnHasFocus = isClearBtnFocused(ctx, clearBtn);
1239
1294
 
1240
1295
  // Tab from input: if clear button exists and doesn't have focus, focus it
1241
- if (clearBtn && !clearBtnHasFocus && component.inputInBib.value) {
1296
+ if (clearBtn && !clearBtnHasFocus && ctx.activeInput.value) {
1242
1297
  // Force clear button container visible to work around Safari not
1243
1298
  // propagating :focus-within through shadow DOM boundaries, which
1244
1299
  // causes .wrapper:not(:focus-within) to hide .notification.clear.
@@ -1281,30 +1336,34 @@ const comboboxKeyboardStrategy = {
1281
1336
  component.hideBib();
1282
1337
  },
1283
1338
 
1284
- ArrowUp(component, evt) {
1285
- // If the clear button has focus, let the browser handle ArrowUp normally
1286
- if (component.clearBtnFocused) {
1339
+ ArrowUp(component, evt, ctx) {
1340
+ // If the clear button has focus, let the browser handle ArrowUp normally.
1341
+ if (isClearBtnFocused(ctx)) {
1287
1342
  return;
1288
1343
  }
1289
1344
 
1290
1345
  if (component.availableOptions.length > 0) {
1291
1346
  component.showBib();
1292
1347
  }
1348
+ // Read live visibility — ctx.isExpanded was computed before showBib() above,
1349
+ // so it wouldn't reflect the state change.
1293
1350
  if (component.dropdown.isPopoverVisible) {
1294
1351
  evt.preventDefault();
1295
1352
  navigateArrow(component, 'up');
1296
1353
  }
1297
1354
  },
1298
1355
 
1299
- ArrowDown(component, evt) {
1300
- // If the clear button has focus, let the browser handle ArrowDown normally
1301
- if (component.clearBtnFocused) {
1356
+ ArrowDown(component, evt, ctx) {
1357
+ // If the clear button has focus, let the browser handle ArrowDown normally.
1358
+ if (isClearBtnFocused(ctx)) {
1302
1359
  return;
1303
1360
  }
1304
1361
 
1305
1362
  if (component.availableOptions.length > 0) {
1306
1363
  component.showBib();
1307
1364
  }
1365
+ // Read live visibility — ctx.isExpanded was computed before showBib() above,
1366
+ // so it wouldn't reflect the state change.
1308
1367
  if (component.dropdown.isPopoverVisible) {
1309
1368
  evt.preventDefault();
1310
1369
  navigateArrow(component, 'down');
@@ -4900,7 +4959,7 @@ let AuroHelpText$2 = class AuroHelpText extends i$4 {
4900
4959
  }
4901
4960
  };
4902
4961
 
4903
- var formkitVersion$2 = '202603191747';
4962
+ var formkitVersion$2 = '202603201627';
4904
4963
 
4905
4964
  let AuroElement$2 = class AuroElement extends i$4 {
4906
4965
  static get properties() {
@@ -12655,7 +12714,7 @@ let AuroHelpText$1 = class AuroHelpText extends i$4 {
12655
12714
  }
12656
12715
  };
12657
12716
 
12658
- var formkitVersion$1 = '202603191747';
12717
+ var formkitVersion$1 = '202603201627';
12659
12718
 
12660
12719
  // Copyright (c) 2025 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
12661
12720
  // See LICENSE in the project root for license information.
@@ -13694,7 +13753,7 @@ class AuroBibtemplate extends i$4 {
13694
13753
  }
13695
13754
  }
13696
13755
 
13697
- var formkitVersion = '202603191747';
13756
+ var formkitVersion = '202603201627';
13698
13757
 
13699
13758
  var styleCss$3 = i$7`.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}`;
13700
13759
 
@@ -14551,6 +14610,13 @@ class AuroCombobox extends AuroElement {
14551
14610
 
14552
14611
  this.noMatchOption = undefined;
14553
14612
 
14613
+ // Remove trailing whitespace only when the input contains
14614
+ // non-whitespace characters, so residual spaces from cursor
14615
+ // editing don't break matching. Whitespace-only input is
14616
+ // preserved as-is so a lone space doesn't show all options.
14617
+ const raw = this.input.value || '';
14618
+ const filterValue = (raw.trim() ? raw.trimEnd() : raw).toLowerCase();
14619
+
14554
14620
  this.options.forEach((option) => {
14555
14621
  let matchString = option.textContent.toLowerCase();
14556
14622
 
@@ -14567,12 +14633,12 @@ class AuroCombobox extends AuroElement {
14567
14633
  }
14568
14634
 
14569
14635
  // If input is empty, show all options (except static ones)
14570
- if (!this.input.value || this.input.value.length === 0) {
14636
+ if (!filterValue) {
14571
14637
  if (!option.hasAttribute('static')) {
14572
14638
  option.removeAttribute('hidden');
14573
14639
  this.availableOptions.push(option);
14574
14640
  }
14575
- } else if (matchString.includes(this.input.value.toLowerCase()) && !option.hasAttribute('static')) {
14641
+ } else if (matchString.includes(filterValue) && !option.hasAttribute('static')) {
14576
14642
  // only count options that match the typed input value AND are not static
14577
14643
  option.removeAttribute('hidden');
14578
14644
  this.availableOptions.push(option);
@@ -14603,7 +14669,7 @@ class AuroCombobox extends AuroElement {
14603
14669
  syncValuesAndStates() {
14604
14670
  // Only sync matchWord, don't set menu.value here since setMenuValue should handle that
14605
14671
  if (this.menu) {
14606
- this.menu.matchWord = this.input.value;
14672
+ this.menu.matchWord = (this.input.value || '').trimEnd();
14607
14673
  }
14608
14674
  const label = this.menu.currentLabel;
14609
14675
  this.updateTriggerTextDisplay(label || this.value);
@@ -14967,8 +15033,9 @@ class AuroCombobox extends AuroElement {
14967
15033
  this.updateTriggerTextDisplay(event.detail.label || event.detail.value);
14968
15034
 
14969
15035
  // Update match word for filtering
14970
- if (this.menu.matchWord !== this.input.value) {
14971
- this.menu.matchWord = this.input.value;
15036
+ const trimmedInput = (this.input.value || '').trimEnd();
15037
+ if (this.menu.matchWord !== trimmedInput) {
15038
+ this.menu.matchWord = trimmedInput;
14972
15039
  }
14973
15040
 
14974
15041
  // Update available options based on selection
@@ -15106,7 +15173,7 @@ class AuroCombobox extends AuroElement {
15106
15173
  });
15107
15174
 
15108
15175
  // Run filtering inline — the re-entrant event won't reach this code.
15109
- this.menu.matchWord = this.inputInBib.value;
15176
+ this.menu.matchWord = (this.inputInBib.value || '').trimEnd();
15110
15177
  this.optionActive = null;
15111
15178
  this.handleMenuOptions();
15112
15179
  this.dispatchEvent(new CustomEvent('inputValue', { detail: { value: this.inputValue } }));
@@ -15120,7 +15187,7 @@ class AuroCombobox extends AuroElement {
15120
15187
 
15121
15188
  this.inputInBib.value = this.input.value;
15122
15189
 
15123
- this.menu.matchWord = this.input.value;
15190
+ this.menu.matchWord = (this.input.value || '').trimEnd();
15124
15191
  this.optionActive = null;
15125
15192
 
15126
15193
  if (!this.input.value) {
@@ -15172,7 +15239,10 @@ class AuroCombobox extends AuroElement {
15172
15239
  * @returns {void}
15173
15240
  */
15174
15241
  configureCombobox() {
15175
- applyKeyboardStrategy(this, comboboxKeyboardStrategy);
15242
+ applyKeyboardStrategy(this, comboboxKeyboardStrategy, {
15243
+ // In modal mode the input moves into the bib; route keyboard events to that element instead.
15244
+ inputResolver: (comp, ctx) => (ctx.isModal && comp.inputInBib ? comp.inputInBib : comp.input),
15245
+ });
15176
15246
 
15177
15247
  this.addEventListener('focusin', () => {
15178
15248
  this.touched = true;
@@ -15331,7 +15401,7 @@ class AuroCombobox extends AuroElement {
15331
15401
 
15332
15402
  // Sync the input and match word, but don't directly set menu.value again
15333
15403
  if (this.menu) {
15334
- this.menu.matchWord = this.input.value;
15404
+ this.menu.matchWord = (this.input.value || '').trimEnd();
15335
15405
  }
15336
15406
 
15337
15407
  this.dispatchEvent(new CustomEvent('input', {