@ionic/core 8.8.7-dev.11779385275.161a641b → 8.8.7-dev.11779467048.1641d05e

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 (72) hide show
  1. package/components/index.js +1 -1
  2. package/components/ion-action-sheet.js +1 -1
  3. package/components/ion-alert.js +1 -1
  4. package/components/ion-datetime.js +1 -1
  5. package/components/ion-gallery.js +1 -1
  6. package/components/ion-loading.js +1 -1
  7. package/components/ion-menu.js +1 -1
  8. package/components/ion-modal.js +1 -1
  9. package/components/ion-picker-legacy.js +1 -1
  10. package/components/ion-popover.js +1 -1
  11. package/components/ion-select-modal.js +1 -1
  12. package/components/ion-select-popover.js +1 -1
  13. package/components/ion-select.js +1 -1
  14. package/components/ion-toast.js +1 -1
  15. package/components/{p-B2rpt1JV.js → p-C38HUpU5.js} +1 -1
  16. package/components/{p-Dmuy6xyk.js → p-C4G6C9fP.js} +1 -1
  17. package/components/{p-B6zr9RZN.js → p-CVRxImH6.js} +1 -1
  18. package/components/{p-B71c6yUH.js → p-CoFDGTFO.js} +1 -1
  19. package/components/{p-DAv9P_LE.js → p-CykCvfXQ.js} +1 -1
  20. package/components/p-DHTe6lDL.js +4 -0
  21. package/components/{p-Di5rHO3q.js → p-qZr7hBPz.js} +1 -1
  22. package/dist/cjs/index.cjs.js +1 -1
  23. package/dist/cjs/ion-action-sheet.cjs.entry.js +1 -1
  24. package/dist/cjs/ion-alert.cjs.entry.js +1 -1
  25. package/dist/cjs/ion-datetime_3.cjs.entry.js +1 -1
  26. package/dist/cjs/ion-gallery.cjs.entry.js +7 -28
  27. package/dist/cjs/ion-loading.cjs.entry.js +1 -1
  28. package/dist/cjs/ion-menu_3.cjs.entry.js +1 -1
  29. package/dist/cjs/ion-modal.cjs.entry.js +1 -1
  30. package/dist/cjs/ion-popover.cjs.entry.js +1 -1
  31. package/dist/cjs/ion-select-modal.cjs.entry.js +1 -1
  32. package/dist/cjs/ion-select_3.cjs.entry.js +1 -1
  33. package/dist/cjs/ion-toast.cjs.entry.js +1 -1
  34. package/dist/cjs/{overlays-Hci_7vw_.js → overlays-C54DhaTC.js} +187 -10
  35. package/dist/collection/components/gallery/gallery.js +7 -28
  36. package/dist/collection/utils/overlays.js +187 -10
  37. package/dist/docs.json +1 -1
  38. package/dist/esm/index.js +1 -1
  39. package/dist/esm/ion-action-sheet.entry.js +1 -1
  40. package/dist/esm/ion-alert.entry.js +1 -1
  41. package/dist/esm/ion-datetime_3.entry.js +1 -1
  42. package/dist/esm/ion-gallery.entry.js +7 -28
  43. package/dist/esm/ion-loading.entry.js +1 -1
  44. package/dist/esm/ion-menu_3.entry.js +1 -1
  45. package/dist/esm/ion-modal.entry.js +1 -1
  46. package/dist/esm/ion-popover.entry.js +1 -1
  47. package/dist/esm/ion-select-modal.entry.js +1 -1
  48. package/dist/esm/ion-select_3.entry.js +1 -1
  49. package/dist/esm/ion-toast.entry.js +1 -1
  50. package/dist/esm/{overlays-rwDDzEs4.js → overlays-ttYCMKRp.js} +187 -10
  51. package/dist/ionic/index.esm.js +1 -1
  52. package/dist/ionic/ionic.esm.js +1 -1
  53. package/dist/ionic/p-06bd033b.entry.js +4 -0
  54. package/dist/ionic/{p-c10fa162.entry.js → p-1f74b8d4.entry.js} +1 -1
  55. package/dist/ionic/{p-a9fb086b.entry.js → p-2f8aa0ac.entry.js} +1 -1
  56. package/dist/ionic/{p-2f0073af.entry.js → p-3331cfa9.entry.js} +1 -1
  57. package/dist/ionic/{p-35b144f5.entry.js → p-33c34361.entry.js} +1 -1
  58. package/dist/ionic/{p-15e3e8f5.entry.js → p-5061a8d4.entry.js} +1 -1
  59. package/dist/ionic/{p-4a0260e6.entry.js → p-8f04bd89.entry.js} +1 -1
  60. package/dist/ionic/{p-bf972309.entry.js → p-967576f8.entry.js} +1 -1
  61. package/dist/ionic/p-DdyNaGpi.js +4 -0
  62. package/dist/ionic/{p-71b6014c.entry.js → p-bb898d47.entry.js} +1 -1
  63. package/dist/ionic/p-dea52cb3.entry.js +4 -0
  64. package/dist/ionic/{p-432c5888.entry.js → p-fc796d48.entry.js} +1 -1
  65. package/dist/types/components/gallery/gallery.d.ts +2 -5
  66. package/hydrate/index.js +194 -38
  67. package/hydrate/index.mjs +194 -38
  68. package/package.json +1 -1
  69. package/components/p-CtiqM786.js +0 -4
  70. package/dist/ionic/p-0f3b4262.entry.js +0 -4
  71. package/dist/ionic/p-4079cee3.entry.js +0 -4
  72. package/dist/ionic/p-C4uUM9DM.js +0 -4
package/hydrate/index.js CHANGED
@@ -8085,8 +8085,101 @@ const CoreDelegate = () => {
8085
8085
  let lastOverlayIndex = 0;
8086
8086
  let lastId = 0;
8087
8087
  const activeAnimations = new WeakMap();
8088
+ const OVERLAY_FOCUS_TRAP_SELECTOR = 'ion-alert,ion-action-sheet,ion-loading,ion-modal,ion-picker-legacy,ion-popover';
8089
+ const ION_SELECT_MODAL_SELECTOR = 'ion-select-modal';
8090
+ const isSelectModalOptionControl = (el) => el.tagName === 'ION-RADIO' || el.tagName === 'ION-CHECKBOX';
8088
8091
  /**
8089
- * Determines if the overlay's backdrop is always blocking (no background interaction).
8092
+ * Returns the currently focused element for keyboard focus checks.
8093
+ *
8094
+ * Starts from `document.activeElement` (non-shadow / light DOM focus).
8095
+ * If focus is inside one or more open shadow roots
8096
+ * (e.g. native control inside `ion-radio`), walks through nested
8097
+ * `shadowRoot.activeElement` values until the innermost focused node is reached.
8098
+ */
8099
+ const getActiveElement = (ownerDoc) => {
8100
+ var _a;
8101
+ let active = ownerDoc.activeElement;
8102
+ if (!active) {
8103
+ return null;
8104
+ }
8105
+ while ((_a = active.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement) {
8106
+ active = active.shadowRoot.activeElement;
8107
+ }
8108
+ return active;
8109
+ };
8110
+ /**
8111
+ * Walks from a focused node (possibly deep inside shadow roots)
8112
+ * up to the nearest `ion-radio` / `ion-checkbox` host.
8113
+ */
8114
+ const getOptionControlHost = (active) => {
8115
+ let n = active;
8116
+ while (n) {
8117
+ if (isSelectModalOptionControl(n)) {
8118
+ return n;
8119
+ }
8120
+ const root = n.getRootNode();
8121
+ if (root instanceof ShadowRoot && root.host instanceof HTMLElement) {
8122
+ n = root.host;
8123
+ }
8124
+ else {
8125
+ return null;
8126
+ }
8127
+ }
8128
+ return null;
8129
+ };
8130
+ /**
8131
+ * Sheet modals can have visual order that differs from DOM order.
8132
+ * Without sorting, Tab can skip the handle after option traversal.
8133
+ *
8134
+ * Order: option controls (radio/checkbox) → sheet handle → end-slot
8135
+ * header button. Any other focusables are appended after.
8136
+ */
8137
+ const sortSheetModalFocusables = (overlay, elements) => {
8138
+ const optionControls = elements.filter((el) => {
8139
+ return overlay.contains(el) && isSelectModalOptionControl(el) && el.tabIndex >= 0;
8140
+ });
8141
+ const cancelControl = elements.find((el) => overlay.contains(el) &&
8142
+ el.tagName === 'ION-BUTTON' &&
8143
+ el.closest('ion-header ion-buttons[slot="end"]') !== null);
8144
+ const handleControl = elements.find((el) => el.classList.contains('modal-handle'));
8145
+ const sortByGeometry = (els) => [...els].sort((a, b) => {
8146
+ const ra = a.getBoundingClientRect();
8147
+ const rb = b.getBoundingClientRect();
8148
+ const topDiff = ra.top - rb.top;
8149
+ if (Math.abs(topDiff) > 1) {
8150
+ return topDiff;
8151
+ }
8152
+ return ra.left - rb.left;
8153
+ });
8154
+ const ordered = [];
8155
+ ordered.push(...sortByGeometry(optionControls));
8156
+ if (handleControl) {
8157
+ ordered.push(handleControl);
8158
+ }
8159
+ if (cancelControl) {
8160
+ ordered.push(cancelControl);
8161
+ }
8162
+ const used = new Set(ordered);
8163
+ for (const el of elements) {
8164
+ if (!used.has(el)) {
8165
+ ordered.push(el);
8166
+ }
8167
+ }
8168
+ return ordered;
8169
+ };
8170
+ /**
8171
+ * Option controls in groups use a tabindex pattern where only one
8172
+ * option is tabbable (`tabIndex="0"`) while the others are `-1`.
8173
+ * This returns the index of that current tabbable option.
8174
+ */
8175
+ const getTabbableOptionControlIndex = (elements, overlay) => {
8176
+ return elements.findIndex((el) => {
8177
+ return overlay.contains(el) && isSelectModalOptionControl(el) && el.tabIndex >= 0;
8178
+ });
8179
+ };
8180
+ /**
8181
+ * Determines if the overlay's backdrop is always blocking
8182
+ * (no background interaction).
8090
8183
  * Returns false if showBackdrop=false or backdropBreakpoint > 0.
8091
8184
  */
8092
8185
  const isBackdropAlwaysBlocking = (el) => {
@@ -8201,7 +8294,7 @@ const focusElementInOverlay = (hostToFocus, overlay) => {
8201
8294
  * Should NOT include: Toast
8202
8295
  */
8203
8296
  const trapKeyboardFocus = (ev, doc) => {
8204
- const lastOverlay = getPresentedOverlay(doc, 'ion-alert,ion-action-sheet,ion-loading,ion-modal,ion-picker-legacy,ion-popover');
8297
+ const lastOverlay = getPresentedOverlay(doc, OVERLAY_FOCUS_TRAP_SELECTOR);
8205
8298
  const target = ev.target;
8206
8299
  /**
8207
8300
  * If no active overlay, ignore this event.
@@ -8384,6 +8477,30 @@ const connectListeners = (doc) => {
8384
8477
  doc.addEventListener('focus', (ev) => {
8385
8478
  trapKeyboardFocus(ev, doc);
8386
8479
  }, true);
8480
+ /**
8481
+ * Remember which option control last received focus
8482
+ * (arrows, click, or Tab). This pattern keeps `tabIndex=0` on the
8483
+ * checked/first radio, so the Tab trap uses this when wrapping back
8484
+ * into the list or focusing the option-group slot.
8485
+ */
8486
+ doc.addEventListener('focusin', (ev) => {
8487
+ const lastOverlay = getPresentedOverlay(doc, OVERLAY_FOCUS_TRAP_SELECTOR);
8488
+ if (!lastOverlay || lastOverlay.classList.contains(FOCUS_TRAP_DISABLE_CLASS)) {
8489
+ return;
8490
+ }
8491
+ const isSheetModal = lastOverlay.classList.contains('modal-sheet');
8492
+ if (!isSheetModal) {
8493
+ return;
8494
+ }
8495
+ const target = ev.target;
8496
+ if (!(target instanceof HTMLElement)) {
8497
+ return;
8498
+ }
8499
+ const optionHost = getOptionControlHost(target);
8500
+ if (optionHost && lastOverlay.contains(optionHost)) {
8501
+ lastOverlay.trapLastSheetOptionControl = optionHost;
8502
+ }
8503
+ }, true);
8387
8504
  // Listen for keydown events to intercept Tab navigation.
8388
8505
  // This is needed for Safari and Firefox which may skip focusable
8389
8506
  // elements or allow focus to escape the overlay.
@@ -8393,10 +8510,10 @@ const connectListeners = (doc) => {
8393
8510
  var _a, _b, _c;
8394
8511
  if (ev.key !== 'Tab' && ev.key !== 'Alt+Tab')
8395
8512
  return;
8396
- const lastOverlay = getPresentedOverlay(doc, 'ion-alert,ion-action-sheet,ion-loading,ion-modal,ion-picker-legacy,ion-popover');
8513
+ const lastOverlay = getPresentedOverlay(doc, OVERLAY_FOCUS_TRAP_SELECTOR);
8397
8514
  if (!lastOverlay || lastOverlay.classList.contains(FOCUS_TRAP_DISABLE_CLASS))
8398
8515
  return;
8399
- const activeElement = doc.activeElement;
8516
+ const activeElement = getActiveElement(doc);
8400
8517
  if (activeElement === lastOverlay) {
8401
8518
  ev.preventDefault();
8402
8519
  focusFirstDescendant(lastOverlay);
@@ -8412,16 +8529,32 @@ const connectListeners = (doc) => {
8412
8529
  if (!isInsideOverlay)
8413
8530
  return;
8414
8531
  // Get all focusable elements from both light and shadow DOM
8415
- const allFocusable = [
8532
+ let allFocusable = [
8416
8533
  ...lastOverlay.querySelectorAll(focusableQueryString),
8417
8534
  ...(((_c = lastOverlay.shadowRoot) === null || _c === void 0 ? void 0 : _c.querySelectorAll(focusableQueryString)) || []),
8418
8535
  ];
8536
+ const selectModalEl = lastOverlay.querySelector(ION_SELECT_MODAL_SELECTOR);
8537
+ const isSheetModal = lastOverlay.classList.contains('modal-sheet');
8538
+ /**
8539
+ * Some sheet modal content, including ion-select-modal,
8540
+ * renders option containers as `ion-item.select-interface-option`.
8541
+ * These can match focusable selectors in some builds but
8542
+ * are not intended tab stops. Keep only true interactive
8543
+ * controls so Tab can move from options to header
8544
+ * controls/handle.
8545
+ */
8546
+ if (selectModalEl) {
8547
+ allFocusable = allFocusable.filter((el) => !el.matches('ion-item.select-interface-option'));
8548
+ }
8549
+ if (isSheetModal) {
8550
+ allFocusable = sortSheetModalFocusables(lastOverlay, allFocusable);
8551
+ }
8419
8552
  if (allFocusable.length === 0) {
8420
8553
  ev.preventDefault();
8421
8554
  return;
8422
8555
  }
8423
8556
  // Find current element's index (accounting for shadow DOM)
8424
- const currentIndex = activeElement
8557
+ let currentIndex = activeElement
8425
8558
  ? allFocusable.findIndex((el) => {
8426
8559
  var _a;
8427
8560
  if (el === activeElement)
@@ -8432,6 +8565,31 @@ const connectListeners = (doc) => {
8432
8565
  return rootNode instanceof ShadowRoot && rootNode.host === el;
8433
8566
  })
8434
8567
  : -1;
8568
+ /**
8569
+ * Radio/checkbox groups can move focus onto an option with
8570
+ * `tabIndex=-1`, while another option still has `tabIndex=0`
8571
+ * in the list. `findIndex` then yields -1 and Tab incorrectly
8572
+ * wraps to `allFocusable[0]`.
8573
+ * Treat focus on any option control inside the sheet modal
8574
+ * as the same trap slot as the listed tabbable option
8575
+ * (`tabIndex >= 0`) so Tab goes handle → Cancel, not back
8576
+ * to the first radio.
8577
+ */
8578
+ if (currentIndex < 0 && isSheetModal && activeElement) {
8579
+ const optionHost = getOptionControlHost(activeElement);
8580
+ if (optionHost && lastOverlay.contains(optionHost)) {
8581
+ const directIndex = allFocusable.indexOf(optionHost);
8582
+ if (directIndex >= 0) {
8583
+ currentIndex = directIndex;
8584
+ }
8585
+ else {
8586
+ const tabbableOptionIndex = getTabbableOptionControlIndex(allFocusable, lastOverlay);
8587
+ if (tabbableOptionIndex >= 0) {
8588
+ currentIndex = tabbableOptionIndex;
8589
+ }
8590
+ }
8591
+ }
8592
+ }
8435
8593
  ev.preventDefault();
8436
8594
  // Helper to focus an element, handling shadow DOM properly
8437
8595
  const focusElement = (element) => {
@@ -8445,24 +8603,43 @@ const connectListeners = (doc) => {
8445
8603
  }
8446
8604
  focusVisibleElement(element);
8447
8605
  };
8606
+ let nextIndex;
8448
8607
  if (ev.shiftKey) {
8449
8608
  // Shift+Tab: previous element, wrap to last if at first
8450
8609
  if (currentIndex <= 0) {
8451
- focusLastDescendant(lastOverlay);
8610
+ nextIndex = allFocusable.length - 1;
8452
8611
  }
8453
8612
  else {
8454
- focusElement(allFocusable[currentIndex - 1]);
8613
+ nextIndex = currentIndex - 1;
8455
8614
  }
8456
8615
  }
8457
8616
  else {
8458
8617
  // Tab: next element, wrap to first if at last
8459
8618
  if (currentIndex < 0 || currentIndex >= allFocusable.length - 1) {
8460
- focusFirstDescendant(lastOverlay);
8619
+ nextIndex = 0;
8461
8620
  }
8462
8621
  else {
8463
- focusElement(allFocusable[currentIndex + 1]);
8622
+ nextIndex = currentIndex + 1;
8623
+ }
8624
+ }
8625
+ const nextEl = allFocusable[nextIndex];
8626
+ const overlayTrap = lastOverlay;
8627
+ const tabbableOptionIndex = isSheetModal ? getTabbableOptionControlIndex(allFocusable, lastOverlay) : -1;
8628
+ /**
8629
+ * The trap list only includes one tabbable option host
8630
+ * (`tabIndex >= 0`), usually the checked/first radio.
8631
+ * `focusin` tracks the real last-focused option; use it
8632
+ * whenever Tab would focus that slot (wrap from Cancel,
8633
+ * Shift+Tab from handle, etc.).
8634
+ */
8635
+ let focusTarget = nextEl;
8636
+ if (isSheetModal && tabbableOptionIndex >= 0 && nextIndex === tabbableOptionIndex) {
8637
+ const saved = overlayTrap.trapLastSheetOptionControl;
8638
+ if ((saved === null || saved === void 0 ? void 0 : saved.isConnected) && lastOverlay.contains(saved) && saved !== nextEl) {
8639
+ focusTarget = saved;
8464
8640
  }
8465
8641
  }
8642
+ focusElement(focusTarget);
8466
8643
  }, true);
8467
8644
  // handle back-button click
8468
8645
  doc.addEventListener('ionBackButton', (ev) => {
@@ -18747,7 +18924,6 @@ const BREAKPOINT_ORDER = ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'];
18747
18924
  class Gallery {
18748
18925
  constructor(hostRef) {
18749
18926
  registerInstance(this, hostRef);
18750
- this.itemWrapperSelector = '[data-gallery-group]';
18751
18927
  // Keep track of whether we've warned about invalid columns, invalid gap,
18752
18928
  // and unused order properties to avoid duplicate warnings on screen resize.
18753
18929
  this.hasWarnedInvalidColumns = false;
@@ -18802,9 +18978,8 @@ class Gallery {
18802
18978
  const styles = getComputedStyle(this.el);
18803
18979
  const rowHeight = parseFloat(styles.getPropertyValue('grid-auto-rows')) || 0;
18804
18980
  const rowGap = parseFloat(styles.getPropertyValue('row-gap')) || parseFloat(styles.getPropertyValue('gap')) || 0;
18805
- const itemGap = parseFloat(styles.getPropertyValue('column-gap')) || parseFloat(styles.getPropertyValue('gap')) || 0;
18806
18981
  const items = this.getItems();
18807
- this.layoutMasonry(items, rowHeight, rowGap, itemGap, columns);
18982
+ this.layoutMasonry(items, rowHeight, rowGap, columns);
18808
18983
  };
18809
18984
  }
18810
18985
  onColumnsOrGapChanged() {
@@ -19061,28 +19236,12 @@ class Gallery {
19061
19236
  const gap = this.getGapForWidth(width);
19062
19237
  this.el.style.setProperty('--internal-gallery-gap', `${gap}`);
19063
19238
  }
19064
- isGalleryItemElement(element) {
19065
- var _a;
19066
- return typeof ((_a = element.style) === null || _a === void 0 ? void 0 : _a.setProperty) === 'function';
19067
- }
19068
19239
  /**
19069
- * Return all gallery items that can be grid items with inline placement styles.
19070
- * Direct children marked with `data-gallery-group` are ignored and replaced
19071
- * with their element children.
19240
+ * Return all directly slotted children of the gallery that can be grid items
19241
+ * with inline placement styles (HTML elements and SVG elements).
19072
19242
  */
19073
19243
  getItems() {
19074
- const items = Array.from(this.el.children).filter((child) => this.isGalleryItemElement(child));
19075
- const flattenedItems = [];
19076
- items.forEach((itemEl) => {
19077
- if (!itemEl.matches(this.itemWrapperSelector)) {
19078
- flattenedItems.push(itemEl);
19079
- return;
19080
- }
19081
- itemEl.style.display = 'contents';
19082
- const wrappedItems = Array.from(itemEl.children).filter((child) => this.isGalleryItemElement(child));
19083
- flattenedItems.push(...wrappedItems);
19084
- });
19085
- return flattenedItems;
19244
+ return Array.from(this.el.children).filter((child) => { var _a; return typeof ((_a = child.style) === null || _a === void 0 ? void 0 : _a.setProperty) === 'function'; });
19086
19245
  }
19087
19246
  /**
19088
19247
  * Clear the item styles for the given item element.
@@ -19139,14 +19298,11 @@ class Gallery {
19139
19298
  /**
19140
19299
  * Apply masonry placement by assigning each item a column and row span.
19141
19300
  */
19142
- layoutMasonry(items, rowHeight, rowGap, itemGap, columns) {
19301
+ layoutMasonry(items, rowHeight, rowGap, columns) {
19143
19302
  const columnHeights = new Array(columns).fill(0);
19144
19303
  const lastItemsByColumn = new Array(columns).fill(undefined);
19145
19304
  items.forEach((itemEl, i) => {
19146
19305
  itemEl.style.marginBottom = '';
19147
- if (itemEl.parentElement !== this.el) {
19148
- itemEl.style.marginBottom = `${itemGap}px`;
19149
- }
19150
19306
  const span = this.calculateRowSpan(itemEl, rowHeight, rowGap);
19151
19307
  if (span === undefined) {
19152
19308
  this.clearItemStyles(itemEl);
@@ -19195,11 +19351,11 @@ class Gallery {
19195
19351
  const { layout } = this;
19196
19352
  const order = this.getOrder();
19197
19353
  const theme = getIonTheme(this);
19198
- return (hAsync(Host, { key: '10b550a9cc0c6b6994a86ec95bc6dbfadb3e8c58', class: {
19354
+ return (hAsync(Host, { key: '1bf2973d22835c0dbddf3214b602f8c08b95e421', class: {
19199
19355
  [theme]: true,
19200
19356
  [`gallery-layout-${layout}`]: true,
19201
19357
  [`gallery-order-${order}`]: layout === 'masonry' && order !== undefined,
19202
- } }, hAsync("slot", { key: '1ac472f867053973aa90975cd61901a2e8ff20aa', onSlotchange: this.onSlotChange })));
19358
+ } }, hAsync("slot", { key: '0dea31f609f6afdb1d73ecb2d873909ffe49203f', onSlotchange: this.onSlotChange })));
19203
19359
  }
19204
19360
  get el() { return getElement(this); }
19205
19361
  static get watchers() { return {