@aurodesignsystem-dev/auro-formkit 0.0.0-pr1398.2 → 0.0.0-pr1398.3

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 (61) 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/demo/keyboardBehavior.md +0 -0
  4. package/components/checkbox/dist/index.js +1 -1
  5. package/components/checkbox/dist/registered.js +1 -1
  6. package/components/combobox/demo/api.min.js +129 -166
  7. package/components/combobox/demo/index.min.js +129 -166
  8. package/components/combobox/demo/keyboardBehavior.html +81 -0
  9. package/components/combobox/demo/keyboardBehavior.md +308 -0
  10. package/components/combobox/dist/index.js +87 -134
  11. package/components/combobox/dist/registered.js +87 -134
  12. package/components/counter/demo/api.min.js +157 -160
  13. package/components/counter/demo/index.min.js +157 -160
  14. package/components/counter/demo/keyboardBehavior.html +81 -0
  15. package/components/counter/demo/keyboardBehavior.md +127 -0
  16. package/components/counter/dist/auro-counter.d.ts +0 -7
  17. package/components/counter/dist/index.js +157 -160
  18. package/components/counter/dist/keyboardStrategy.d.ts +4 -0
  19. package/components/counter/dist/registered.js +157 -160
  20. package/components/datepicker/demo/api.min.js +89 -137
  21. package/components/datepicker/demo/index.min.js +89 -137
  22. package/components/datepicker/demo/keyboardBehavior.html +81 -0
  23. package/components/datepicker/demo/keyboardBehavior.md +24 -0
  24. package/components/datepicker/dist/index.js +84 -132
  25. package/components/datepicker/dist/registered.js +84 -132
  26. package/components/dropdown/demo/api.md +0 -1
  27. package/components/dropdown/demo/api.min.js +99 -140
  28. package/components/dropdown/demo/index.md +2 -2
  29. package/components/dropdown/demo/index.min.js +99 -140
  30. package/components/dropdown/demo/keyboardBehavior.html +81 -0
  31. package/components/dropdown/demo/keyboardBehavior.md +77 -0
  32. package/components/dropdown/dist/auro-dropdown.d.ts +0 -8
  33. package/components/dropdown/dist/auro-dropdownBib.d.ts +1 -50
  34. package/components/dropdown/dist/dropdownBibKeyboardStrategy.d.ts +7 -0
  35. package/components/dropdown/dist/index.js +83 -128
  36. package/components/dropdown/dist/registered.js +83 -128
  37. package/components/form/demo/api.min.js +466 -599
  38. package/components/form/demo/index.min.js +466 -599
  39. package/components/form/demo/keyboardBehavior.md +0 -0
  40. package/components/input/demo/api.min.js +1 -1
  41. package/components/input/demo/index.min.js +1 -1
  42. package/components/input/demo/keyboardBehavior.md +0 -0
  43. package/components/input/dist/index.js +1 -1
  44. package/components/input/dist/registered.js +1 -1
  45. package/components/menu/demo/api.min.js +42 -32
  46. package/components/menu/demo/index.min.js +42 -32
  47. package/components/menu/dist/auro-menu.d.ts +3 -11
  48. package/components/menu/dist/index.js +42 -32
  49. package/components/menu/dist/registered.js +42 -32
  50. package/components/radio/demo/api.min.js +1 -1
  51. package/components/radio/demo/index.min.js +1 -1
  52. package/components/radio/dist/index.js +1 -1
  53. package/components/radio/dist/registered.js +1 -1
  54. package/components/select/demo/api.min.js +132 -167
  55. package/components/select/demo/index.min.js +132 -167
  56. package/components/select/demo/keyboardBehavior.html +81 -0
  57. package/components/select/demo/keyboardBehavior.md +246 -0
  58. package/components/select/dist/index.js +90 -135
  59. package/components/select/dist/registered.js +90 -135
  60. package/custom-elements.json +61 -89
  61. package/package.json +2 -2
@@ -3,6 +3,8 @@ import { classMap } from 'lit/directives/class-map.js';
3
3
  import { createRef, ref } from 'lit/directives/ref.js';
4
4
  import { css, html, LitElement } from 'lit';
5
5
  import { ifDefined } from 'lit/directives/if-defined.js';
6
+ import 'lit-html';
7
+ import 'lit-html/directives/unsafe-html.js';
6
8
 
7
9
  // Copyright (c) Alaska Air. All right reserved. Licensed under the Apache-2.0 license
8
10
  // See LICENSE in the project root for license information.
@@ -2053,11 +2055,10 @@ class AuroFloatingUI {
2053
2055
  return;
2054
2056
  }
2055
2057
 
2056
- const { activeElement } = document;
2057
2058
  // if focus is still inside of trigger or bib, do not close
2058
2059
  if (
2059
- this.element.contains(activeElement) ||
2060
- this.element.bib?.contains(activeElement)
2060
+ this.element.matches(":focus") ||
2061
+ this.element.matches(":focus-within")
2061
2062
  ) {
2062
2063
  return;
2063
2064
  }
@@ -2896,12 +2897,83 @@ class p{registerComponent(t,a){customElements.get(t)||customElements.define(t,cl
2896
2897
 
2897
2898
  var iconVersion = '9.1.2';
2898
2899
 
2900
+ /**
2901
+ * Computes display state once per keydown event.
2902
+ * Centralizes null-safety checks and makes the shared/modal/popover branching explicit.
2903
+ *
2904
+ * @param {HTMLElement} component - The component with a dropdown reference.
2905
+ * @param {Object} [options] - Optional config.
2906
+ * @param {HTMLElement} [options.dropdown] - Explicit dropdown reference. Falls back to component.dropdown.
2907
+ * @param {Function} [options.inputResolver] - Called with (component, ctx) to resolve the active input element.
2908
+ * @returns {{isExpanded: boolean, isModal: boolean, isPopover: boolean, activeInput: HTMLElement|null}}
2909
+ * isModal and isPopover reflect the display mode (fullscreen vs not) regardless of expanded state.
2910
+ */
2911
+ function createDisplayContext(component, options = {}) {
2912
+ const dd = options.dropdown || component.dropdown;
2913
+ // isPopoverVisible reflects as the `open` attribute.
2914
+ // It reports whether the bib is open in any mode (popover or modal).
2915
+ const isExpanded = Boolean(dd && dd.isPopoverVisible);
2916
+ const isFullscreen = Boolean(dd && dd.isBibFullscreen);
2917
+
2918
+ const ctx = {
2919
+ isExpanded,
2920
+ isModal: isFullscreen,
2921
+ isPopover: !isFullscreen,
2922
+ activeInput: null,
2923
+ };
2924
+
2925
+ if (options.inputResolver) {
2926
+ const resolvedInput = options.inputResolver(component, ctx);
2927
+ // Guard against resolvers returning undefined or non-HTMLElement values.
2928
+ ctx.activeInput = resolvedInput instanceof HTMLElement ? resolvedInput : null;
2929
+ }
2930
+
2931
+ return ctx;
2932
+ }
2933
+
2934
+ /**
2935
+ * Wires up a keydown listener that dispatches to strategy[evt.key] or strategy.default.
2936
+ * Handles both sync and async handlers.
2937
+ * @param {HTMLElement} component - The component to attach the listener to.
2938
+ * @param {Object} strategy - Map of key names to handler functions.
2939
+ * @param {Object} [options] - Optional config passed to createDisplayContext.
2940
+ */
2941
+ function applyKeyboardStrategy(component, strategy, options = {}) {
2942
+ component.addEventListener('keydown', async (evt) => {
2943
+ const handler = strategy[evt.key] || strategy.default;
2944
+ if (typeof handler === 'function') {
2945
+ const ctx = createDisplayContext(component, options);
2946
+ await handler(component, evt, ctx);
2947
+ }
2948
+ });
2949
+ }
2950
+
2899
2951
  var styleCss$2 = 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}`;
2900
2952
 
2901
2953
  var colorCss$2 = 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)}`;
2902
2954
 
2903
2955
  var tokensCss$1 = css`:host(:not([ondark])),:host(:not([appearance=inverse])){--ds-auro-dropdown-label-text-color: var(--ds-basic-color-texticon-muted, #676767);--ds-auro-dropdown-trigger-background-color: var(--ds-basic-color-surface-default, #ffffff);--ds-auro-dropdown-trigger-hover-background-color: var(--ds-basic-color-surface-default, #ffffff);--ds-auro-dropdown-trigger-container-color: var(--ds-basic-color-surface-default, #ffffff);--ds-auro-dropdown-trigger-border-color: var(--ds-basic-color-border-bold, #585e67);--ds-auro-dropdown-trigger-outline-color: transparent;--ds-auro-dropdown-trigger-text-color: var(--ds-basic-color-texticon-default, #2a2a2a);--ds-auro-dropdownbib-boxshadow-color: var(--ds-elevation-200, 0px 0px 10px rgba(0, 0, 0, 0.15));--ds-auro-dropdownbib-background-color: var(--ds-basic-color-surface-default, #ffffff);--ds-auro-dropdownbib-container-color: var(--ds-basic-color-surface-default, #ffffff);--ds-auro-dropdownbib-text-color: var(--ds-basic-color-texticon-default, #2a2a2a)}:host([ondark]),:host([appearance=inverse]){--ds-auro-dropdown-label-text-color: var(--ds-basic-color-texticon-inverse-muted, #ccd2db);--ds-auro-dropdown-trigger-background-color: var(--ds-advanced-color-shared-background-inverse, rgba(255, 255, 255, 0.15));--ds-auro-dropdown-trigger-hover-background-color: var(--ds-advanced-color-shared-background-inverse, rgba(255, 255, 255, 0.15));--ds-auro-dropdown-trigger-container-color: var(--ds-advanced-color-shared-background-inverse, rgba(255, 255, 255, 0.15));--ds-auro-dropdown-trigger-border-color: var(--ds-basic-color-border-inverse, #ffffff);--ds-auro-dropdown-trigger-outline-color: transparent;--ds-auro-dropdown-trigger-text-color: var(--ds-basic-color-texticon-inverse, #ffffff);--ds-auro-dropdownbib-boxshadow-color: var(--ds-elevation-200, 0px 0px 10px rgba(0, 0, 0, 0.15));--ds-auro-dropdownbib-background-color: var(--ds-advanced-color-shared-background-inverse, rgba(255, 255, 255, 0.15));--ds-auro-dropdownbib-container-color: var(--ds-advanced-color-shared-background-inverse, rgba(255, 255, 255, 0.15));--ds-auro-dropdownbib-text-color: var(--ds-basic-color-texticon-inverse, #ffffff)}`;
2904
2956
 
2957
+ /**
2958
+ * Creates a keyboard strategy for dialog-specific key handling.
2959
+ * All other keydown behavior is left to the browser's native bubbling path.
2960
+ * @param {HTMLElement} bib - The dropdown bib element.
2961
+ * @returns {Object} Keyboard handlers keyed by `event.key`.
2962
+ */
2963
+ // eslint-disable-next-line no-unused-vars
2964
+ function createDropdownBibKeyboardStrategy(bib) {
2965
+ return {
2966
+ // eslint-disable-next-line no-unused-vars
2967
+ Enter(_dialog, event) {
2968
+ // Floating UI handles Enter key to open the dropdown
2969
+ },
2970
+ // eslint-disable-next-line no-unused-vars
2971
+ Escape(_dialog, event) {
2972
+ // Floating UI handles Escape key to close the dropdown
2973
+ }
2974
+ };
2975
+ }
2976
+
2905
2977
  // Copyright (c) 2020 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
2906
2978
  // See LICENSE in the project root for license information.
2907
2979
  /* eslint-disable max-lines */
@@ -3026,26 +3098,11 @@ class AuroDropdownBib extends LitElement {
3026
3098
  },
3027
3099
 
3028
3100
  /**
3029
- * Set by auro-dropdown when a menu option is highlighted via
3030
- * aria-activedescendant. The dialog keyboard bridge checks this
3031
- * flag so that Enter selects the highlighted option instead of
3032
- * activating the focused interactive element (e.g. the trigger
3033
- * button, or the bibtemplate close button in fullscreen).
3101
+ * Tracks whether a menu option is currently highlighted.
3034
3102
  * @private
3035
3103
  */
3036
3104
  hasActiveDescendant: {
3037
3105
  type: Boolean
3038
- },
3039
-
3040
- /**
3041
- * When true, the keyboard bridge allows native Tab behavior
3042
- * instead of intercepting it. Set this for bib consumers
3043
- * (e.g. counter) whose content contains real focusable elements
3044
- * that need native Tab navigation.
3045
- * @private
3046
- */
3047
- nativeFocusableContent: {
3048
- type: Boolean
3049
3106
  }
3050
3107
  };
3051
3108
  }
@@ -3115,7 +3172,7 @@ class AuroDropdownBib extends LitElement {
3115
3172
 
3116
3173
  const dialog = this.shadowRoot.querySelector('dialog');
3117
3174
  this._setupCancelHandler(dialog);
3118
- this._setupKeyboardBridge(dialog);
3175
+ applyKeyboardStrategy(dialog, createDropdownBibKeyboardStrategy());
3119
3176
 
3120
3177
  this.dispatchEvent(new CustomEvent('auro-dropdownbib-connected', {
3121
3178
  bubbles: true,
@@ -3142,98 +3199,6 @@ class AuroDropdownBib extends LitElement {
3142
3199
  });
3143
3200
  }
3144
3201
 
3145
- /**
3146
- * showModal() creates a closed focus scope — keyboard events inside
3147
- * the dialog's shadow DOM do NOT bubble out to the combobox/select
3148
- * keydown handlers in the parent shadow DOM. This handler bridges
3149
- * that gap by re-dispatching navigation keys so they cross the
3150
- * shadow boundary and reach the menu navigation logic in the parent
3151
- * component.
3152
- *
3153
- * The trade-off: intercepting these keys means native keyboard
3154
- * behaviors that would normally "just work" must be manually
3155
- * re-implemented here:
3156
- *
3157
- * - Enter on buttons: Custom elements (auro-button) don't get the
3158
- * native Enter→click that <button> provides, so we call .click()
3159
- * directly when Enter is pressed on a button-like element.
3160
- *
3161
- * - Tab: Intercepted and re-dispatched so parent components
3162
- * (select/combobox) can select the active option and close the
3163
- * dialog. The <dialog> provides containment and isolation
3164
- * (inert background, VoiceOver focus trapping, top layer), while
3165
- * the content inside is a role="listbox" navigated via
3166
- * aria-activedescendant (options are not focusable). Tab keyboard
3167
- * behavior follows listbox conventions (select + close) because
3168
- * the dialog's native Tab trap only cycles between the close
3169
- * button and browser chrome.
3170
- *
3171
- * - Escape: The native <dialog> fires a `cancel` event on ESC
3172
- * (handled by _setupCancelHandler), so the re-dispatched Escape
3173
- * is a secondary path for parent components that also listen for
3174
- * Escape keydown.
3175
- *
3176
- * @param {HTMLDialogElement} dialog - The dialog element to attach the keyboard bridge to.
3177
- * @private
3178
- */
3179
- _setupKeyboardBridge(dialog) {
3180
- const navKeys = new Set([
3181
- 'ArrowUp',
3182
- 'ArrowDown',
3183
- 'Enter',
3184
- 'Escape',
3185
- 'Tab'
3186
- ]);
3187
-
3188
- dialog.addEventListener('keydown', (event) => {
3189
- if (!navKeys.has(event.key)) {
3190
- return;
3191
- }
3192
-
3193
- // Custom elements (auro-button) don't get the native Enter→click
3194
- // behavior that <button> has. Find the button in the composed path
3195
- // and click it directly — but only when no menu option is
3196
- // highlighted. In fullscreen mode focus stays on the close button
3197
- // while arrow keys move the active-descendant highlight through
3198
- // the listbox. If the user presses Enter with an option
3199
- // highlighted, the intent is to select that option, not to click
3200
- // the close button. In that case we fall through and bridge the
3201
- // Enter key to the parent component's keyboard strategy.
3202
- if (event.key === 'Enter') {
3203
- if (!this.hasActiveDescendant) {
3204
- const buttonSelector = 'button, [role="button"], auro-button, [auro-button]';
3205
- const btn = event.composedPath().find((el) => el.matches && el.matches(buttonSelector));
3206
- if (btn) {
3207
- event.preventDefault();
3208
- event.stopPropagation();
3209
- btn.click();
3210
- return;
3211
- }
3212
- }
3213
- }
3214
-
3215
- // Allow native Tab when the bib contains focusable content
3216
- // (e.g. counter buttons) that needs normal Tab navigation.
3217
- if (event.key === 'Tab' && this.nativeFocusableContent) {
3218
- return;
3219
- }
3220
-
3221
- event.preventDefault();
3222
- event.stopPropagation();
3223
- const newEvent = new KeyboardEvent('keydown', {
3224
- key: event.key,
3225
- code: event.code,
3226
- shiftKey: event.shiftKey,
3227
- altKey: event.altKey,
3228
- ctrlKey: event.ctrlKey,
3229
- metaKey: event.metaKey,
3230
- bubbles: true,
3231
- composed: true,
3232
- cancelable: true
3233
- });
3234
- this.dispatchEvent(newEvent);
3235
- });
3236
- }
3237
3202
 
3238
3203
  /**
3239
3204
  * Blocks touch-driven page scroll while a fullscreen modal dialog is open.
@@ -3608,7 +3573,7 @@ class AuroHelpText extends LitElement {
3608
3573
  }
3609
3574
  }
3610
3575
 
3611
- var formkitVersion = '202603271519';
3576
+ var formkitVersion = '202604012043';
3612
3577
 
3613
3578
  class AuroElement extends LitElement {
3614
3579
  static get properties() {
@@ -3789,7 +3754,6 @@ class AuroDropdown extends AuroElement {
3789
3754
  this.appearance = 'default';
3790
3755
  this.chevron = false;
3791
3756
  this.disabled = false;
3792
- this.disableFocusTrap = false;
3793
3757
  this.error = false;
3794
3758
  this.tabIndex = 0;
3795
3759
  this.noToggle = false;
@@ -3887,9 +3851,8 @@ class AuroDropdown extends AuroElement {
3887
3851
  // showModal() fires asynchronously via Lit's update cycle, which
3888
3852
  // falls outside the user activation window and causes iOS to
3889
3853
  // dismiss the keyboard.
3890
- if (this.isBibFullscreen && this.bibElement && this.bibElement.value) {
3891
- const useModal = !this.disableFocusTrap;
3892
- this.bibElement.value.open(useModal);
3854
+ if (this.bibElement && this.bibElement.value) {
3855
+ this.bibElement.value.open(this.isBibFullscreen);
3893
3856
  }
3894
3857
  }
3895
3858
 
@@ -4002,14 +3965,6 @@ class AuroDropdown extends AuroElement {
4002
3965
  reflect: true
4003
3966
  },
4004
3967
 
4005
- /**
4006
- * If declared, the focus trap inside of bib will be turned off.
4007
- */
4008
- disableFocusTrap: {
4009
- type: Boolean,
4010
- reflect: true
4011
- },
4012
-
4013
3968
  /**
4014
3969
  * @private
4015
3970
  */
@@ -4283,7 +4238,7 @@ class AuroDropdown extends AuroElement {
4283
4238
  if (this.isPopoverVisible) {
4284
4239
  // Fullscreen: use showModal() for native accessibility (inert outside, focus trap)
4285
4240
  // Desktop: use show() for Floating UI positioning + FocusTrap for focus management
4286
- const useModal = this.isBibFullscreen && !this.disableFocusTrap;
4241
+ const useModal = this.isBibFullscreen;
4287
4242
  this.bibElement.value.open(useModal);
4288
4243
  } else {
4289
4244
  this.bibElement.value.close();
@@ -4293,7 +4248,7 @@ class AuroDropdown extends AuroElement {
4293
4248
  // When fullscreen strategy changes while open, re-open dialog with correct mode
4294
4249
  // (e.g. resizing from desktop → mobile while dropdown is open)
4295
4250
  if (changedProperties.has('isBibFullscreen') && this.isPopoverVisible && this.bibElement.value) {
4296
- const useModal = this.isBibFullscreen && !this.disableFocusTrap;
4251
+ const useModal = this.isBibFullscreen;
4297
4252
  this.bibElement.value.close();
4298
4253
  this.bibElement.value.open(useModal);
4299
4254
  }
@@ -4405,7 +4360,7 @@ class AuroDropdown extends AuroElement {
4405
4360
  * @private
4406
4361
  */
4407
4362
  updateFocusTrap() {
4408
- if (this.isPopoverVisible && !this.disableFocusTrap) {
4363
+ if (this.isPopoverVisible) {
4409
4364
  if (!this.isBibFullscreen) {
4410
4365
  // Desktop: show() doesn't trap focus, so use FocusTrap
4411
4366
  this.focusTrap = new FocusTrap(this.bibContent);