@ionic/core 8.8.5-dev.11776871786.1e73ab78 → 8.8.5-dev.11777476461.1fd79771

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 (43) hide show
  1. package/components/ion-modal.js +1 -1
  2. package/components/ion-radio-group.js +1 -1
  3. package/components/ion-select-modal.js +1 -1
  4. package/components/ion-select-popover.js +1 -1
  5. package/components/ion-select.js +1 -1
  6. package/components/p-BlNv564p.js +4 -0
  7. package/components/p-D-cP12ZN.js +4 -0
  8. package/components/{p-BZfgPT2N.js → p-DUqnmRFi.js} +1 -1
  9. package/components/{p-Bk2zuNWT.js → p-DvOO1fxp.js} +1 -1
  10. package/dist/cjs/ion-modal.cjs.entry.js +10 -4
  11. package/dist/cjs/ion-radio_2.cjs.entry.js +13 -1
  12. package/dist/cjs/ion-select-modal.cjs.entry.js +18 -7
  13. package/dist/cjs/ion-select_3.cjs.entry.js +18 -7
  14. package/dist/collection/components/content/content.css +0 -6
  15. package/dist/collection/components/modal/modal.js +10 -4
  16. package/dist/collection/components/radio-group/radio-group.js +13 -1
  17. package/dist/collection/components/radio-group/test/fixtures.js +2 -2
  18. package/dist/collection/components/select-modal/select-modal.js +18 -7
  19. package/dist/collection/components/select-modal/test/fixtures.js +4 -0
  20. package/dist/collection/components/select-popover/select-popover.js +18 -7
  21. package/dist/collection/components/select-popover/test/fixtures.js +4 -0
  22. package/dist/docs.json +1 -6
  23. package/dist/esm/ion-modal.entry.js +10 -4
  24. package/dist/esm/ion-radio_2.entry.js +13 -1
  25. package/dist/esm/ion-select-modal.entry.js +18 -7
  26. package/dist/esm/ion-select_3.entry.js +18 -7
  27. package/dist/ionic/ionic.esm.js +1 -1
  28. package/dist/ionic/p-28a9e720.entry.js +4 -0
  29. package/dist/ionic/{p-4dd5e8e0.entry.js → p-8fda6a62.entry.js} +1 -1
  30. package/dist/ionic/{p-9eac4eb1.entry.js → p-aa812c4b.entry.js} +1 -1
  31. package/dist/ionic/{p-4eedd78a.entry.js → p-e0287f41.entry.js} +1 -1
  32. package/dist/types/components/modal/modal.d.ts +6 -0
  33. package/dist/types/components/radio-group/test/fixtures.d.ts +1 -1
  34. package/dist/types/components/select-modal/select-modal.d.ts +1 -0
  35. package/dist/types/components/select-modal/test/fixtures.d.ts +1 -0
  36. package/dist/types/components/select-popover/select-popover.d.ts +1 -0
  37. package/dist/types/components/select-popover/test/fixtures.d.ts +1 -0
  38. package/hydrate/index.js +59 -19
  39. package/hydrate/index.mjs +59 -19
  40. package/package.json +1 -1
  41. package/components/p-EK4xUz-q.js +0 -4
  42. package/components/p-MlJRD6E1.js +0 -4
  43. package/dist/ionic/p-e6c5f060.entry.js +0 -4
@@ -2890,6 +2890,12 @@ const Modal = class {
2890
2890
  * when no footer is present, so ion-content's .inner-scroll includes
2891
2891
  * safe-area-bottom in its scroll padding. This keeps the modal background
2892
2892
  * edge-to-edge while ensuring content scrolls clear of the system nav bar.
2893
+ *
2894
+ * --ion-content-safe-area-padding-bottom is an internal CSS property used
2895
+ * only by this code path. It is not part of ion-content's public API and
2896
+ * should not be set by consumers. The default of 0px makes it a no-op
2897
+ * when unset, which is the expected state for ion-content used outside of
2898
+ * a fullscreen modal without a footer.
2893
2899
  */
2894
2900
  applyFullscreenSafeAreaTo(contentEl, hasFooter) {
2895
2901
  // Only apply for standard Ionic layouts (has ion-content but no
@@ -2954,20 +2960,20 @@ const Modal = class {
2954
2960
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2955
2961
  const isHandleCycle = handleBehavior === 'cycle';
2956
2962
  const isSheetModalWithHandle = isSheetModal && showHandle;
2957
- return (index$3.h(index$3.Host, Object.assign({ key: 'b665328614ae3a0d27ec15ecb8334d14e0d517e7', "no-router": true,
2963
+ return (index$3.h(index$3.Host, Object.assign({ key: '4bf38aa67df9a3f977163bba5423960bbafd16de', "no-router": true,
2958
2964
  // Allow the modal to be navigable when the handle is focusable
2959
2965
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2960
2966
  zIndex: `${20000 + this.overlayIndex}`,
2961
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, theme.getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), index$3.h("ion-backdrop", { key: '263b41858dc0ad44e5a84cd83cf6eaaf32a804d2', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && index$3.h("div", { key: '65eb2a58f20576941e49f5499de644b4c45ad50e', class: "modal-shadow" }), index$3.h("div", Object.assign({ key: '2817d6cc8015ad013204941fea5ee96c331841f5',
2967
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, theme.getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), index$3.h("ion-backdrop", { key: '866da40cc5fc8d3e36637098fb3066a5bc9f4e0f', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && index$3.h("div", { key: '5a2a05514ea8592c8feb0465e504aa7c7af17963', class: "modal-shadow" }), index$3.h("div", Object.assign({ key: '4d327115306451f57d190b06ab8cbb6191a6f1d7',
2962
2968
  /*
2963
2969
  role and aria-modal must be used on the
2964
2970
  same element. They must also be set inside the
2965
2971
  shadow DOM otherwise ion-button will not be highlighted
2966
2972
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2967
2973
  */
2968
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (index$3.h("button", { key: '318f2c1e903cb41c977fbcce529f509c82378079', class: "modal-handle",
2974
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (index$3.h("button", { key: 'd1882835cc049232c0d957e3ba1e79676a07d179', class: "modal-handle",
2969
2975
  // Prevents the handle from receiving keyboard focus when it does not cycle
2970
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), index$3.h("slot", { key: 'de8e6b0d4126820cc6a02be0af229a3e62afa1dd', onSlotchange: this.onSlotChange }))));
2976
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), index$3.h("slot", { key: '81dc58b09cf7d7022b04cd170f53113604364d5e', onSlotchange: this.onSlotChange }))));
2971
2977
  }
2972
2978
  get el() { return index$3.getElement(this); }
2973
2979
  static get watchers() { return {
@@ -375,6 +375,18 @@ const RadioGroup = class {
375
375
  // to the bottom of the screen
376
376
  ev.preventDefault();
377
377
  }
378
+ // Inside a select interface, Enter commits the focused radio
379
+ // value (matching native <select>). The !ev.repeat guard stops
380
+ // a held Enter on the triggering ion-select from re-committing
381
+ // once focus lands in the opened popover/modal.
382
+ if (ev.key === 'Enter' && inSelectInterface && !ev.repeat) {
383
+ const previousValue = this.value;
384
+ this.value = current.value;
385
+ if (previousValue !== this.value) {
386
+ this.emitValueChange(ev);
387
+ }
388
+ ev.preventDefault();
389
+ }
378
390
  }
379
391
  }
380
392
  /** @internal */
@@ -407,7 +419,7 @@ const RadioGroup = class {
407
419
  const { label, labelId, el, name, value } = this;
408
420
  const mode = ionicGlobal.getIonMode(this);
409
421
  helpers.renderHiddenInput(true, el, name, value, false);
410
- return (index.h(index.Host, { key: 'db593b3ed511e9395e3c7bfd91b787328692cd6d', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, class: mode }, this.renderHintText(), index.h("slot", { key: 'd683b01c1ba34fe843c4b320bce4661a117472a5' })));
422
+ return (index.h(index.Host, { key: '377e4aa3a656cc84b742f9d7a7d4be65d20c69f5', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, class: mode }, this.renderHintText(), index.h("slot", { key: 'c3187a2497773b4f15cea3b413b036502bcec8c0' })));
411
423
  }
412
424
  get el() { return index.getElement(this); }
413
425
  static get watchers() { return {
@@ -22,6 +22,10 @@ const selectModalMdCss = () => `.sc-ion-select-modal-md-h{height:100%}ion-list.s
22
22
  const SelectModal = class {
23
23
  constructor(hostRef) {
24
24
  index.registerInstance(this, hostRef);
25
+ // Tracks the option that received Enter-keydown so keyup only
26
+ // dismisses when the press started on the same option. Prevents
27
+ // Enter on the triggering ion-select from auto-dismissing.
28
+ this.pendingEnterTarget = null;
25
29
  /**
26
30
  * The text to display on the cancel button.
27
31
  */
@@ -71,15 +75,22 @@ const SelectModal = class {
71
75
  return (index.h("ion-radio-group", { value: checked, onIonChange: (ev) => this.callOptionHandler(ev) }, this.options.map((option) => (index.h("ion-item", { lines: "none", class: Object.assign({
72
76
  // TODO FW-4784
73
77
  'item-radio-checked': option.value === checked
74
- }, theme.getClassMap(option.cssClass)) }, index.h("ion-radio", { value: option.value, disabled: option.disabled, justify: "start", labelPlacement: "end", onClick: () => this.closeModal(), onKeyUp: (ev) => {
78
+ }, theme.getClassMap(option.cssClass)) }, index.h("ion-radio", { value: option.value, disabled: option.disabled, justify: "start", labelPlacement: "end", onClick: () => this.closeModal(), onKeyDown: (ev) => {
79
+ if (ev.key === 'Enter' && !ev.repeat) {
80
+ this.pendingEnterTarget = ev.currentTarget;
81
+ }
82
+ }, onKeyUp: (ev) => {
75
83
  if (ev.key === ' ') {
76
- /**
77
- * Selecting a radio option with keyboard navigation,
78
- * either through the Enter or Space keys, should
79
- * dismiss the modal.
80
- */
84
+ // Space selects and dismisses in one press.
81
85
  this.closeModal();
82
86
  }
87
+ else if (ev.key === 'Enter') {
88
+ const shouldClose = this.pendingEnterTarget === ev.currentTarget;
89
+ this.pendingEnterTarget = null;
90
+ if (shouldClose) {
91
+ this.closeModal();
92
+ }
93
+ }
83
94
  } }, option.text))))));
84
95
  }
85
96
  renderCheckboxOptions() {
@@ -94,7 +105,7 @@ const SelectModal = class {
94
105
  } }, option.text))));
95
106
  }
96
107
  render() {
97
- return (index.h(index.Host, { key: 'f8a4cd6ff23ff01eaa1bdaf3c046814e7b30b23b', class: ionicGlobal.getIonMode(this) }, index.h("ion-header", { key: '9e29a7e57ad5cf332641111882f16852187ec8ba' }, index.h("ion-toolbar", { key: 'e6af5d6eabbf4b10799fc8a0b8f91d29b12d41f5' }, this.header !== undefined && index.h("ion-title", { key: '6056e52d15dbf307571d25e0305d67228a79237d' }, this.header), index.h("ion-buttons", { key: 'c9aa4fb2e21a93f3a95c5a8f0ba8b7d5553c5a72', slot: "end" }, index.h("ion-button", { key: '5ffbf512719bcb053b652fc96b1b6154d0593095', onClick: () => this.closeModal() }, this.cancelText)))), index.h("ion-content", { key: '0ec9098798a4e6de7a83a0a7e9d10bdcd7c98a78' }, index.h("ion-list", { key: 'd60b1700d3c2f8655951632de810900707a101f0' }, this.multiple === true ? this.renderCheckboxOptions() : this.renderRadioOptions()))));
108
+ return (index.h(index.Host, { key: 'fda0bf6f93cd5ec9f3c64f88a52de849e0e140a2', class: ionicGlobal.getIonMode(this) }, index.h("ion-header", { key: '27c0b17175a53db9ff159feeeb96451a3f011dab' }, index.h("ion-toolbar", { key: '91a4155ebc317fbc9f1bb3e26a7e94754b953c9b' }, this.header !== undefined && index.h("ion-title", { key: 'f6dae8e4e381f322cc90efefd9bb6ef81d4d2f3e' }, this.header), index.h("ion-buttons", { key: 'e7760532fb2e7e7385ed6e62097d92d96ff20148', slot: "end" }, index.h("ion-button", { key: '4999b6fc46cba138186546dca67b7950855e6fb7', onClick: () => this.closeModal() }, this.cancelText)))), index.h("ion-content", { key: 'c73f80a4bc25b9061ea65cf11e5d811c1a4d8704' }, index.h("ion-list", { key: 'b21905d15b36ad5eb45845e768918d2763cf48b1' }, this.multiple === true ? this.renderCheckboxOptions() : this.renderRadioOptions()))));
98
109
  }
99
110
  get el() { return index.getElement(this); }
100
111
  };
@@ -892,6 +892,10 @@ const selectPopoverMdCss = () => `.sc-ion-select-popover-md-h ion-list.sc-ion-se
892
892
  const SelectPopover = class {
893
893
  constructor(hostRef) {
894
894
  index.registerInstance(this, hostRef);
895
+ // Tracks the option that received Enter-keydown so keyup only
896
+ // dismisses when the press started on the same option. Prevents
897
+ // Enter on the triggering ion-select from auto-dismissing.
898
+ this.pendingEnterTarget = null;
895
899
  /**
896
900
  * An array of options for the popover
897
901
  */
@@ -969,21 +973,28 @@ const SelectPopover = class {
969
973
  return (index.h("ion-radio-group", { value: checked, onIonChange: (ev) => this.callOptionHandler(ev) }, options.map((option) => (index.h("ion-item", { class: Object.assign({
970
974
  // TODO FW-4784
971
975
  'item-radio-checked': option.value === checked
972
- }, theme.getClassMap(option.cssClass)) }, index.h("ion-radio", { value: option.value, disabled: option.disabled, onClick: () => this.dismissParentPopover(), onKeyUp: (ev) => {
976
+ }, theme.getClassMap(option.cssClass)) }, index.h("ion-radio", { value: option.value, disabled: option.disabled, onClick: () => this.dismissParentPopover(), onKeyDown: (ev) => {
977
+ if (ev.key === 'Enter' && !ev.repeat) {
978
+ this.pendingEnterTarget = ev.currentTarget;
979
+ }
980
+ }, onKeyUp: (ev) => {
973
981
  if (ev.key === ' ') {
974
- /**
975
- * Selecting a radio option with keyboard navigation,
976
- * either through the Enter or Space keys, should
977
- * dismiss the popover.
978
- */
982
+ // Space selects and dismisses in one press.
979
983
  this.dismissParentPopover();
980
984
  }
985
+ else if (ev.key === 'Enter') {
986
+ const shouldDismiss = this.pendingEnterTarget === ev.currentTarget;
987
+ this.pendingEnterTarget = null;
988
+ if (shouldDismiss) {
989
+ this.dismissParentPopover();
990
+ }
991
+ }
981
992
  } }, option.text))))));
982
993
  }
983
994
  render() {
984
995
  const { header, message, options, subHeader } = this;
985
996
  const hasSubHeaderOrMessage = subHeader !== undefined || message !== undefined;
986
- return (index.h(index.Host, { key: '0c9845a40d3fc392b0a7d64e2a6ed27d94bb7634', class: ionicGlobal.getIonMode(this) }, index.h("ion-list", { key: '84a30f6661b0f8c00e6fa199658ed2adbcf27358' }, header !== undefined && index.h("ion-list-header", { key: '13f5f56bbfbc06751fa516291a2da72629b60ece' }, header), hasSubHeaderOrMessage && (index.h("ion-item", { key: '3d39d18e720e798bbde334e79e6832091c7dfb81' }, index.h("ion-label", { key: 'd3051b0d140120b44bf5e79572f6f287e7cfb03a', class: "ion-text-wrap" }, subHeader !== undefined && index.h("h3", { key: 'b16805956f3316f8ec703c123b76f717488e8637' }, subHeader), message !== undefined && index.h("p", { key: '2215ac4ab4146a14e75a79192e319a8016286b5f' }, message)))), this.renderOptions(options))));
997
+ return (index.h(index.Host, { key: 'e7449a1ecfcdbf45a79f8e26a00253c4e146448a', class: ionicGlobal.getIonMode(this) }, index.h("ion-list", { key: '52abdfc8668c3429a0dcefef8ddedb6647fdd894' }, header !== undefined && index.h("ion-list-header", { key: '978e5c03728756feafcc60a0e10e6ec59bf2ae11' }, header), hasSubHeaderOrMessage && (index.h("ion-item", { key: 'e93c44e7f07a76def16e4b11f0fb4780d84ed402' }, index.h("ion-label", { key: 'bba1aac43b0bc7f4f00978dd8301985233f3725c', class: "ion-text-wrap" }, subHeader !== undefined && index.h("h3", { key: 'ad96f6017cf2cc5219540bded2c4f1ca3b532de2' }, subHeader), message !== undefined && index.h("p", { key: '3fd038921dc40c4d0c29734433984b279ccaeec3' }, message)))), this.renderOptions(options))));
987
998
  }
988
999
  get el() { return index.getElement(this); }
989
1000
  };
@@ -106,12 +106,6 @@
106
106
  background: var(--background);
107
107
  }
108
108
 
109
- /**
110
- * --ion-content-safe-area-padding-bottom is an internal property set by
111
- * modal.tsx for fullscreen modals without an ion-footer. This decouples
112
- * safe-area-bottom scroll padding from --padding-bottom (which is a
113
- * public property consumers may override).
114
- */
115
109
  .inner-scroll {
116
110
  left: 0px;
117
111
  right: 0px;
@@ -1092,6 +1092,12 @@ export class Modal {
1092
1092
  * when no footer is present, so ion-content's .inner-scroll includes
1093
1093
  * safe-area-bottom in its scroll padding. This keeps the modal background
1094
1094
  * edge-to-edge while ensuring content scrolls clear of the system nav bar.
1095
+ *
1096
+ * --ion-content-safe-area-padding-bottom is an internal CSS property used
1097
+ * only by this code path. It is not part of ion-content's public API and
1098
+ * should not be set by consumers. The default of 0px makes it a no-op
1099
+ * when unset, which is the expected state for ion-content used outside of
1100
+ * a fullscreen modal without a footer.
1095
1101
  */
1096
1102
  applyFullscreenSafeAreaTo(contentEl, hasFooter) {
1097
1103
  // Only apply for standard Ionic layouts (has ion-content but no
@@ -1156,20 +1162,20 @@ export class Modal {
1156
1162
  const isCardModal = presentingElement !== undefined && mode === 'ios';
1157
1163
  const isHandleCycle = handleBehavior === 'cycle';
1158
1164
  const isSheetModalWithHandle = isSheetModal && showHandle;
1159
- return (h(Host, Object.assign({ key: 'b665328614ae3a0d27ec15ecb8334d14e0d517e7', "no-router": true,
1165
+ return (h(Host, Object.assign({ key: '4bf38aa67df9a3f977163bba5423960bbafd16de', "no-router": true,
1160
1166
  // Allow the modal to be navigable when the handle is focusable
1161
1167
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
1162
1168
  zIndex: `${20000 + this.overlayIndex}`,
1163
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '263b41858dc0ad44e5a84cd83cf6eaaf32a804d2', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '65eb2a58f20576941e49f5499de644b4c45ad50e', class: "modal-shadow" }), h("div", Object.assign({ key: '2817d6cc8015ad013204941fea5ee96c331841f5',
1169
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '866da40cc5fc8d3e36637098fb3066a5bc9f4e0f', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '5a2a05514ea8592c8feb0465e504aa7c7af17963', class: "modal-shadow" }), h("div", Object.assign({ key: '4d327115306451f57d190b06ab8cbb6191a6f1d7',
1164
1170
  /*
1165
1171
  role and aria-modal must be used on the
1166
1172
  same element. They must also be set inside the
1167
1173
  shadow DOM otherwise ion-button will not be highlighted
1168
1174
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
1169
1175
  */
1170
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '318f2c1e903cb41c977fbcce529f509c82378079', class: "modal-handle",
1176
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'd1882835cc049232c0d957e3ba1e79676a07d179', class: "modal-handle",
1171
1177
  // Prevents the handle from receiving keyboard focus when it does not cycle
1172
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: 'de8e6b0d4126820cc6a02be0af229a3e62afa1dd', onSlotchange: this.onSlotChange }))));
1178
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '81dc58b09cf7d7022b04cd170f53113604364d5e', onSlotchange: this.onSlotChange }))));
1173
1179
  }
1174
1180
  static get is() { return "ion-modal"; }
1175
1181
  static get encapsulation() { return "shadow"; }
@@ -206,6 +206,18 @@ export class RadioGroup {
206
206
  // to the bottom of the screen
207
207
  ev.preventDefault();
208
208
  }
209
+ // Inside a select interface, Enter commits the focused radio
210
+ // value (matching native <select>). The !ev.repeat guard stops
211
+ // a held Enter on the triggering ion-select from re-committing
212
+ // once focus lands in the opened popover/modal.
213
+ if (ev.key === 'Enter' && inSelectInterface && !ev.repeat) {
214
+ const previousValue = this.value;
215
+ this.value = current.value;
216
+ if (previousValue !== this.value) {
217
+ this.emitValueChange(ev);
218
+ }
219
+ ev.preventDefault();
220
+ }
209
221
  }
210
222
  }
211
223
  /** @internal */
@@ -238,7 +250,7 @@ export class RadioGroup {
238
250
  const { label, labelId, el, name, value } = this;
239
251
  const mode = getIonMode(this);
240
252
  renderHiddenInput(true, el, name, value, false);
241
- return (h(Host, { key: 'db593b3ed511e9395e3c7bfd91b787328692cd6d', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, class: mode }, this.renderHintText(), h("slot", { key: 'd683b01c1ba34fe843c4b320bce4661a117472a5' })));
253
+ return (h(Host, { key: '377e4aa3a656cc84b742f9d7a7d4be65d20c69f5', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, class: mode }, this.renderHintText(), h("slot", { key: 'c3187a2497773b4f15cea3b413b036502bcec8c0' })));
242
254
  }
243
255
  static get is() { return "ion-radio-group"; }
244
256
  static get originalStyleUrls() {
@@ -6,12 +6,12 @@ export class RadioFixture {
6
6
  constructor(page) {
7
7
  this.page = page;
8
8
  }
9
- async checkRadio(method, selector = 'ion-radio') {
9
+ async checkRadio(method, selector = 'ion-radio', key = 'Space') {
10
10
  const { page } = this;
11
11
  const radio = (this.radio = page.locator(selector));
12
12
  if (method === 'keyboard') {
13
13
  await radio.focus();
14
- await page.keyboard.press('Space');
14
+ await page.keyboard.press(key);
15
15
  }
16
16
  else {
17
17
  await radio.click();
@@ -7,6 +7,10 @@ import { safeCall } from "../../utils/overlays";
7
7
  import { getClassMap } from "../../utils/theme";
8
8
  export class SelectModal {
9
9
  constructor() {
10
+ // Tracks the option that received Enter-keydown so keyup only
11
+ // dismisses when the press started on the same option. Prevents
12
+ // Enter on the triggering ion-select from auto-dismissing.
13
+ this.pendingEnterTarget = null;
10
14
  /**
11
15
  * The text to display on the cancel button.
12
16
  */
@@ -56,15 +60,22 @@ export class SelectModal {
56
60
  return (h("ion-radio-group", { value: checked, onIonChange: (ev) => this.callOptionHandler(ev) }, this.options.map((option) => (h("ion-item", { lines: "none", class: Object.assign({
57
61
  // TODO FW-4784
58
62
  'item-radio-checked': option.value === checked
59
- }, getClassMap(option.cssClass)) }, h("ion-radio", { value: option.value, disabled: option.disabled, justify: "start", labelPlacement: "end", onClick: () => this.closeModal(), onKeyUp: (ev) => {
63
+ }, getClassMap(option.cssClass)) }, h("ion-radio", { value: option.value, disabled: option.disabled, justify: "start", labelPlacement: "end", onClick: () => this.closeModal(), onKeyDown: (ev) => {
64
+ if (ev.key === 'Enter' && !ev.repeat) {
65
+ this.pendingEnterTarget = ev.currentTarget;
66
+ }
67
+ }, onKeyUp: (ev) => {
60
68
  if (ev.key === ' ') {
61
- /**
62
- * Selecting a radio option with keyboard navigation,
63
- * either through the Enter or Space keys, should
64
- * dismiss the modal.
65
- */
69
+ // Space selects and dismisses in one press.
66
70
  this.closeModal();
67
71
  }
72
+ else if (ev.key === 'Enter') {
73
+ const shouldClose = this.pendingEnterTarget === ev.currentTarget;
74
+ this.pendingEnterTarget = null;
75
+ if (shouldClose) {
76
+ this.closeModal();
77
+ }
78
+ }
68
79
  } }, option.text))))));
69
80
  }
70
81
  renderCheckboxOptions() {
@@ -79,7 +90,7 @@ export class SelectModal {
79
90
  } }, option.text))));
80
91
  }
81
92
  render() {
82
- return (h(Host, { key: 'f8a4cd6ff23ff01eaa1bdaf3c046814e7b30b23b', class: getIonMode(this) }, h("ion-header", { key: '9e29a7e57ad5cf332641111882f16852187ec8ba' }, h("ion-toolbar", { key: 'e6af5d6eabbf4b10799fc8a0b8f91d29b12d41f5' }, this.header !== undefined && h("ion-title", { key: '6056e52d15dbf307571d25e0305d67228a79237d' }, this.header), h("ion-buttons", { key: 'c9aa4fb2e21a93f3a95c5a8f0ba8b7d5553c5a72', slot: "end" }, h("ion-button", { key: '5ffbf512719bcb053b652fc96b1b6154d0593095', onClick: () => this.closeModal() }, this.cancelText)))), h("ion-content", { key: '0ec9098798a4e6de7a83a0a7e9d10bdcd7c98a78' }, h("ion-list", { key: 'd60b1700d3c2f8655951632de810900707a101f0' }, this.multiple === true ? this.renderCheckboxOptions() : this.renderRadioOptions()))));
93
+ return (h(Host, { key: 'fda0bf6f93cd5ec9f3c64f88a52de849e0e140a2', class: getIonMode(this) }, h("ion-header", { key: '27c0b17175a53db9ff159feeeb96451a3f011dab' }, h("ion-toolbar", { key: '91a4155ebc317fbc9f1bb3e26a7e94754b953c9b' }, this.header !== undefined && h("ion-title", { key: 'f6dae8e4e381f322cc90efefd9bb6ef81d4d2f3e' }, this.header), h("ion-buttons", { key: 'e7760532fb2e7e7385ed6e62097d92d96ff20148', slot: "end" }, h("ion-button", { key: '4999b6fc46cba138186546dca67b7950855e6fb7', onClick: () => this.closeModal() }, this.cancelText)))), h("ion-content", { key: 'c73f80a4bc25b9061ea65cf11e5d811c1a4d8704' }, h("ion-list", { key: 'b21905d15b36ad5eb45845e768918d2763cf48b1' }, this.multiple === true ? this.renderCheckboxOptions() : this.renderRadioOptions()))));
83
94
  }
84
95
  static get is() { return "ion-select-modal"; }
85
96
  static get encapsulation() { return "scoped"; }
@@ -39,6 +39,10 @@ export class SelectModalPage {
39
39
  const option = this.getOption(value);
40
40
  await option.press('Space');
41
41
  }
42
+ async pressEnterOnOption(value) {
43
+ const option = this.getOption(value);
44
+ await option.press('Enter');
45
+ }
42
46
  getOption(value) {
43
47
  const { multiple, selectModal } = this;
44
48
  const selector = multiple ? 'ion-checkbox' : 'ion-radio';
@@ -10,6 +10,10 @@ import { getIonMode } from "../../global/ionic-global";
10
10
  */
11
11
  export class SelectPopover {
12
12
  constructor() {
13
+ // Tracks the option that received Enter-keydown so keyup only
14
+ // dismisses when the press started on the same option. Prevents
15
+ // Enter on the triggering ion-select from auto-dismissing.
16
+ this.pendingEnterTarget = null;
13
17
  /**
14
18
  * An array of options for the popover
15
19
  */
@@ -87,21 +91,28 @@ export class SelectPopover {
87
91
  return (h("ion-radio-group", { value: checked, onIonChange: (ev) => this.callOptionHandler(ev) }, options.map((option) => (h("ion-item", { class: Object.assign({
88
92
  // TODO FW-4784
89
93
  'item-radio-checked': option.value === checked
90
- }, getClassMap(option.cssClass)) }, h("ion-radio", { value: option.value, disabled: option.disabled, onClick: () => this.dismissParentPopover(), onKeyUp: (ev) => {
94
+ }, getClassMap(option.cssClass)) }, h("ion-radio", { value: option.value, disabled: option.disabled, onClick: () => this.dismissParentPopover(), onKeyDown: (ev) => {
95
+ if (ev.key === 'Enter' && !ev.repeat) {
96
+ this.pendingEnterTarget = ev.currentTarget;
97
+ }
98
+ }, onKeyUp: (ev) => {
91
99
  if (ev.key === ' ') {
92
- /**
93
- * Selecting a radio option with keyboard navigation,
94
- * either through the Enter or Space keys, should
95
- * dismiss the popover.
96
- */
100
+ // Space selects and dismisses in one press.
97
101
  this.dismissParentPopover();
98
102
  }
103
+ else if (ev.key === 'Enter') {
104
+ const shouldDismiss = this.pendingEnterTarget === ev.currentTarget;
105
+ this.pendingEnterTarget = null;
106
+ if (shouldDismiss) {
107
+ this.dismissParentPopover();
108
+ }
109
+ }
99
110
  } }, option.text))))));
100
111
  }
101
112
  render() {
102
113
  const { header, message, options, subHeader } = this;
103
114
  const hasSubHeaderOrMessage = subHeader !== undefined || message !== undefined;
104
- return (h(Host, { key: '0c9845a40d3fc392b0a7d64e2a6ed27d94bb7634', class: getIonMode(this) }, h("ion-list", { key: '84a30f6661b0f8c00e6fa199658ed2adbcf27358' }, header !== undefined && h("ion-list-header", { key: '13f5f56bbfbc06751fa516291a2da72629b60ece' }, header), hasSubHeaderOrMessage && (h("ion-item", { key: '3d39d18e720e798bbde334e79e6832091c7dfb81' }, h("ion-label", { key: 'd3051b0d140120b44bf5e79572f6f287e7cfb03a', class: "ion-text-wrap" }, subHeader !== undefined && h("h3", { key: 'b16805956f3316f8ec703c123b76f717488e8637' }, subHeader), message !== undefined && h("p", { key: '2215ac4ab4146a14e75a79192e319a8016286b5f' }, message)))), this.renderOptions(options))));
115
+ return (h(Host, { key: 'e7449a1ecfcdbf45a79f8e26a00253c4e146448a', class: getIonMode(this) }, h("ion-list", { key: '52abdfc8668c3429a0dcefef8ddedb6647fdd894' }, header !== undefined && h("ion-list-header", { key: '978e5c03728756feafcc60a0e10e6ec59bf2ae11' }, header), hasSubHeaderOrMessage && (h("ion-item", { key: 'e93c44e7f07a76def16e4b11f0fb4780d84ed402' }, h("ion-label", { key: 'bba1aac43b0bc7f4f00978dd8301985233f3725c', class: "ion-text-wrap" }, subHeader !== undefined && h("h3", { key: 'ad96f6017cf2cc5219540bded2c4f1ca3b532de2' }, subHeader), message !== undefined && h("p", { key: '3fd038921dc40c4d0c29734433984b279ccaeec3' }, message)))), this.renderOptions(options))));
105
116
  }
106
117
  static get is() { return "ion-select-popover"; }
107
118
  static get encapsulation() { return "scoped"; }
@@ -39,6 +39,10 @@ export class SelectPopoverPage {
39
39
  const option = this.getOption(value);
40
40
  await option.press('Space');
41
41
  }
42
+ async pressEnterOnOption(value) {
43
+ const option = this.getOption(value);
44
+ await option.press('Enter');
45
+ }
42
46
  getOption(value) {
43
47
  const { multiple, selectPopover } = this;
44
48
  const selector = multiple ? 'ion-checkbox' : 'ion-radio';
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-04-22T15:31:51",
2
+ "timestamp": "2026-04-29T15:29:39",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.43.0",
@@ -8379,11 +8379,6 @@
8379
8379
  "annotation": "prop",
8380
8380
  "docs": "Color of the content"
8381
8381
  },
8382
- {
8383
- "name": "--ion-content-safe-area-padding-bottom is an internal property set by modal.tsx for fullscreen modals without an ion-footer. This decouples safe-area-bottom scroll padding from --padding-bottom (which is a public property consumers may override).",
8384
- "annotation": "prop",
8385
- "docs": ""
8386
- },
8387
8382
  {
8388
8383
  "name": "--keyboard-offset",
8389
8384
  "annotation": "prop",
@@ -2888,6 +2888,12 @@ const Modal = class {
2888
2888
  * when no footer is present, so ion-content's .inner-scroll includes
2889
2889
  * safe-area-bottom in its scroll padding. This keeps the modal background
2890
2890
  * edge-to-edge while ensuring content scrolls clear of the system nav bar.
2891
+ *
2892
+ * --ion-content-safe-area-padding-bottom is an internal CSS property used
2893
+ * only by this code path. It is not part of ion-content's public API and
2894
+ * should not be set by consumers. The default of 0px makes it a no-op
2895
+ * when unset, which is the expected state for ion-content used outside of
2896
+ * a fullscreen modal without a footer.
2891
2897
  */
2892
2898
  applyFullscreenSafeAreaTo(contentEl, hasFooter) {
2893
2899
  // Only apply for standard Ionic layouts (has ion-content but no
@@ -2952,20 +2958,20 @@ const Modal = class {
2952
2958
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2953
2959
  const isHandleCycle = handleBehavior === 'cycle';
2954
2960
  const isSheetModalWithHandle = isSheetModal && showHandle;
2955
- return (h(Host, Object.assign({ key: 'b665328614ae3a0d27ec15ecb8334d14e0d517e7', "no-router": true,
2961
+ return (h(Host, Object.assign({ key: '4bf38aa67df9a3f977163bba5423960bbafd16de', "no-router": true,
2956
2962
  // Allow the modal to be navigable when the handle is focusable
2957
2963
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2958
2964
  zIndex: `${20000 + this.overlayIndex}`,
2959
- }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '263b41858dc0ad44e5a84cd83cf6eaaf32a804d2', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '65eb2a58f20576941e49f5499de644b4c45ad50e', class: "modal-shadow" }), h("div", Object.assign({ key: '2817d6cc8015ad013204941fea5ee96c331841f5',
2965
+ }, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '866da40cc5fc8d3e36637098fb3066a5bc9f4e0f', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '5a2a05514ea8592c8feb0465e504aa7c7af17963', class: "modal-shadow" }), h("div", Object.assign({ key: '4d327115306451f57d190b06ab8cbb6191a6f1d7',
2960
2966
  /*
2961
2967
  role and aria-modal must be used on the
2962
2968
  same element. They must also be set inside the
2963
2969
  shadow DOM otherwise ion-button will not be highlighted
2964
2970
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2965
2971
  */
2966
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '318f2c1e903cb41c977fbcce529f509c82378079', class: "modal-handle",
2972
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'd1882835cc049232c0d957e3ba1e79676a07d179', class: "modal-handle",
2967
2973
  // Prevents the handle from receiving keyboard focus when it does not cycle
2968
- tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: 'de8e6b0d4126820cc6a02be0af229a3e62afa1dd', onSlotchange: this.onSlotChange }))));
2974
+ tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '81dc58b09cf7d7022b04cd170f53113604364d5e', onSlotchange: this.onSlotChange }))));
2969
2975
  }
2970
2976
  get el() { return getElement(this); }
2971
2977
  static get watchers() { return {
@@ -373,6 +373,18 @@ const RadioGroup = class {
373
373
  // to the bottom of the screen
374
374
  ev.preventDefault();
375
375
  }
376
+ // Inside a select interface, Enter commits the focused radio
377
+ // value (matching native <select>). The !ev.repeat guard stops
378
+ // a held Enter on the triggering ion-select from re-committing
379
+ // once focus lands in the opened popover/modal.
380
+ if (ev.key === 'Enter' && inSelectInterface && !ev.repeat) {
381
+ const previousValue = this.value;
382
+ this.value = current.value;
383
+ if (previousValue !== this.value) {
384
+ this.emitValueChange(ev);
385
+ }
386
+ ev.preventDefault();
387
+ }
376
388
  }
377
389
  }
378
390
  /** @internal */
@@ -405,7 +417,7 @@ const RadioGroup = class {
405
417
  const { label, labelId, el, name, value } = this;
406
418
  const mode = getIonMode(this);
407
419
  renderHiddenInput(true, el, name, value, false);
408
- return (h(Host, { key: 'db593b3ed511e9395e3c7bfd91b787328692cd6d', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, class: mode }, this.renderHintText(), h("slot", { key: 'd683b01c1ba34fe843c4b320bce4661a117472a5' })));
420
+ return (h(Host, { key: '377e4aa3a656cc84b742f9d7a7d4be65d20c69f5', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, class: mode }, this.renderHintText(), h("slot", { key: 'c3187a2497773b4f15cea3b413b036502bcec8c0' })));
409
421
  }
410
422
  get el() { return getElement(this); }
411
423
  static get watchers() { return {
@@ -20,6 +20,10 @@ const selectModalMdCss = () => `.sc-ion-select-modal-md-h{height:100%}ion-list.s
20
20
  const SelectModal = class {
21
21
  constructor(hostRef) {
22
22
  registerInstance(this, hostRef);
23
+ // Tracks the option that received Enter-keydown so keyup only
24
+ // dismisses when the press started on the same option. Prevents
25
+ // Enter on the triggering ion-select from auto-dismissing.
26
+ this.pendingEnterTarget = null;
23
27
  /**
24
28
  * The text to display on the cancel button.
25
29
  */
@@ -69,15 +73,22 @@ const SelectModal = class {
69
73
  return (h("ion-radio-group", { value: checked, onIonChange: (ev) => this.callOptionHandler(ev) }, this.options.map((option) => (h("ion-item", { lines: "none", class: Object.assign({
70
74
  // TODO FW-4784
71
75
  'item-radio-checked': option.value === checked
72
- }, getClassMap(option.cssClass)) }, h("ion-radio", { value: option.value, disabled: option.disabled, justify: "start", labelPlacement: "end", onClick: () => this.closeModal(), onKeyUp: (ev) => {
76
+ }, getClassMap(option.cssClass)) }, h("ion-radio", { value: option.value, disabled: option.disabled, justify: "start", labelPlacement: "end", onClick: () => this.closeModal(), onKeyDown: (ev) => {
77
+ if (ev.key === 'Enter' && !ev.repeat) {
78
+ this.pendingEnterTarget = ev.currentTarget;
79
+ }
80
+ }, onKeyUp: (ev) => {
73
81
  if (ev.key === ' ') {
74
- /**
75
- * Selecting a radio option with keyboard navigation,
76
- * either through the Enter or Space keys, should
77
- * dismiss the modal.
78
- */
82
+ // Space selects and dismisses in one press.
79
83
  this.closeModal();
80
84
  }
85
+ else if (ev.key === 'Enter') {
86
+ const shouldClose = this.pendingEnterTarget === ev.currentTarget;
87
+ this.pendingEnterTarget = null;
88
+ if (shouldClose) {
89
+ this.closeModal();
90
+ }
91
+ }
81
92
  } }, option.text))))));
82
93
  }
83
94
  renderCheckboxOptions() {
@@ -92,7 +103,7 @@ const SelectModal = class {
92
103
  } }, option.text))));
93
104
  }
94
105
  render() {
95
- return (h(Host, { key: 'f8a4cd6ff23ff01eaa1bdaf3c046814e7b30b23b', class: getIonMode(this) }, h("ion-header", { key: '9e29a7e57ad5cf332641111882f16852187ec8ba' }, h("ion-toolbar", { key: 'e6af5d6eabbf4b10799fc8a0b8f91d29b12d41f5' }, this.header !== undefined && h("ion-title", { key: '6056e52d15dbf307571d25e0305d67228a79237d' }, this.header), h("ion-buttons", { key: 'c9aa4fb2e21a93f3a95c5a8f0ba8b7d5553c5a72', slot: "end" }, h("ion-button", { key: '5ffbf512719bcb053b652fc96b1b6154d0593095', onClick: () => this.closeModal() }, this.cancelText)))), h("ion-content", { key: '0ec9098798a4e6de7a83a0a7e9d10bdcd7c98a78' }, h("ion-list", { key: 'd60b1700d3c2f8655951632de810900707a101f0' }, this.multiple === true ? this.renderCheckboxOptions() : this.renderRadioOptions()))));
106
+ return (h(Host, { key: 'fda0bf6f93cd5ec9f3c64f88a52de849e0e140a2', class: getIonMode(this) }, h("ion-header", { key: '27c0b17175a53db9ff159feeeb96451a3f011dab' }, h("ion-toolbar", { key: '91a4155ebc317fbc9f1bb3e26a7e94754b953c9b' }, this.header !== undefined && h("ion-title", { key: 'f6dae8e4e381f322cc90efefd9bb6ef81d4d2f3e' }, this.header), h("ion-buttons", { key: 'e7760532fb2e7e7385ed6e62097d92d96ff20148', slot: "end" }, h("ion-button", { key: '4999b6fc46cba138186546dca67b7950855e6fb7', onClick: () => this.closeModal() }, this.cancelText)))), h("ion-content", { key: 'c73f80a4bc25b9061ea65cf11e5d811c1a4d8704' }, h("ion-list", { key: 'b21905d15b36ad5eb45845e768918d2763cf48b1' }, this.multiple === true ? this.renderCheckboxOptions() : this.renderRadioOptions()))));
96
107
  }
97
108
  get el() { return getElement(this); }
98
109
  };
@@ -890,6 +890,10 @@ const selectPopoverMdCss = () => `.sc-ion-select-popover-md-h ion-list.sc-ion-se
890
890
  const SelectPopover = class {
891
891
  constructor(hostRef) {
892
892
  registerInstance(this, hostRef);
893
+ // Tracks the option that received Enter-keydown so keyup only
894
+ // dismisses when the press started on the same option. Prevents
895
+ // Enter on the triggering ion-select from auto-dismissing.
896
+ this.pendingEnterTarget = null;
893
897
  /**
894
898
  * An array of options for the popover
895
899
  */
@@ -967,21 +971,28 @@ const SelectPopover = class {
967
971
  return (h("ion-radio-group", { value: checked, onIonChange: (ev) => this.callOptionHandler(ev) }, options.map((option) => (h("ion-item", { class: Object.assign({
968
972
  // TODO FW-4784
969
973
  'item-radio-checked': option.value === checked
970
- }, getClassMap(option.cssClass)) }, h("ion-radio", { value: option.value, disabled: option.disabled, onClick: () => this.dismissParentPopover(), onKeyUp: (ev) => {
974
+ }, getClassMap(option.cssClass)) }, h("ion-radio", { value: option.value, disabled: option.disabled, onClick: () => this.dismissParentPopover(), onKeyDown: (ev) => {
975
+ if (ev.key === 'Enter' && !ev.repeat) {
976
+ this.pendingEnterTarget = ev.currentTarget;
977
+ }
978
+ }, onKeyUp: (ev) => {
971
979
  if (ev.key === ' ') {
972
- /**
973
- * Selecting a radio option with keyboard navigation,
974
- * either through the Enter or Space keys, should
975
- * dismiss the popover.
976
- */
980
+ // Space selects and dismisses in one press.
977
981
  this.dismissParentPopover();
978
982
  }
983
+ else if (ev.key === 'Enter') {
984
+ const shouldDismiss = this.pendingEnterTarget === ev.currentTarget;
985
+ this.pendingEnterTarget = null;
986
+ if (shouldDismiss) {
987
+ this.dismissParentPopover();
988
+ }
989
+ }
979
990
  } }, option.text))))));
980
991
  }
981
992
  render() {
982
993
  const { header, message, options, subHeader } = this;
983
994
  const hasSubHeaderOrMessage = subHeader !== undefined || message !== undefined;
984
- return (h(Host, { key: '0c9845a40d3fc392b0a7d64e2a6ed27d94bb7634', class: getIonMode(this) }, h("ion-list", { key: '84a30f6661b0f8c00e6fa199658ed2adbcf27358' }, header !== undefined && h("ion-list-header", { key: '13f5f56bbfbc06751fa516291a2da72629b60ece' }, header), hasSubHeaderOrMessage && (h("ion-item", { key: '3d39d18e720e798bbde334e79e6832091c7dfb81' }, h("ion-label", { key: 'd3051b0d140120b44bf5e79572f6f287e7cfb03a', class: "ion-text-wrap" }, subHeader !== undefined && h("h3", { key: 'b16805956f3316f8ec703c123b76f717488e8637' }, subHeader), message !== undefined && h("p", { key: '2215ac4ab4146a14e75a79192e319a8016286b5f' }, message)))), this.renderOptions(options))));
995
+ return (h(Host, { key: 'e7449a1ecfcdbf45a79f8e26a00253c4e146448a', class: getIonMode(this) }, h("ion-list", { key: '52abdfc8668c3429a0dcefef8ddedb6647fdd894' }, header !== undefined && h("ion-list-header", { key: '978e5c03728756feafcc60a0e10e6ec59bf2ae11' }, header), hasSubHeaderOrMessage && (h("ion-item", { key: 'e93c44e7f07a76def16e4b11f0fb4780d84ed402' }, h("ion-label", { key: 'bba1aac43b0bc7f4f00978dd8301985233f3725c', class: "ion-text-wrap" }, subHeader !== undefined && h("h3", { key: 'ad96f6017cf2cc5219540bded2c4f1ca3b532de2' }, subHeader), message !== undefined && h("p", { key: '3fd038921dc40c4d0c29734433984b279ccaeec3' }, message)))), this.renderOptions(options))));
985
996
  }
986
997
  get el() { return getElement(this); }
987
998
  };