@ionic/core 8.6.5-nightly.20250714 → 8.6.5-nightly.20250716

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.
@@ -23,6 +23,7 @@ const Item = /*@__PURE__*/ proxyCustomElement(class Item extends HTMLElement {
23
23
  this.inheritedAriaAttributes = {};
24
24
  this.multipleInputs = false;
25
25
  this.focusable = true;
26
+ this.isInteractive = false;
26
27
  /**
27
28
  * If `true`, a button tag will be rendered and the item will be tappable.
28
29
  */
@@ -44,6 +45,11 @@ const Item = /*@__PURE__*/ proxyCustomElement(class Item extends HTMLElement {
44
45
  * The type of the button. Only used when an `onclick` or `button` property is present.
45
46
  */
46
47
  this.type = 'button';
48
+ // slot change listener updates state to reflect how/if item should be interactive
49
+ this.updateInteractivityOnSlotChange = () => {
50
+ this.setIsInteractive();
51
+ this.setMultipleInputs();
52
+ };
47
53
  }
48
54
  buttonChanged() {
49
55
  // Update the focusable option when the button option is changed
@@ -91,13 +97,11 @@ const Item = /*@__PURE__*/ proxyCustomElement(class Item extends HTMLElement {
91
97
  componentDidLoad() {
92
98
  raf(() => {
93
99
  this.setMultipleInputs();
100
+ this.setIsInteractive();
94
101
  this.focusable = this.isFocusable();
95
102
  });
96
103
  }
97
- // If the item contains multiple clickable elements and/or inputs, then the item
98
- // should not have a clickable input cover over the entire item to prevent
99
- // interfering with their individual click events
100
- setMultipleInputs() {
104
+ totalNestedInputs() {
101
105
  // The following elements have a clickable cover that is relative to the entire item
102
106
  const covers = this.el.querySelectorAll('ion-checkbox, ion-datetime, ion-select, ion-radio');
103
107
  // The following elements can accept focus alongside the previous elements
@@ -106,6 +110,17 @@ const Item = /*@__PURE__*/ proxyCustomElement(class Item extends HTMLElement {
106
110
  const inputs = this.el.querySelectorAll('ion-input, ion-range, ion-searchbar, ion-segment, ion-textarea, ion-toggle');
107
111
  // The following elements should also stay clickable when an input with cover is present
108
112
  const clickables = this.el.querySelectorAll('ion-router-link, ion-button, a, button');
113
+ return {
114
+ covers,
115
+ inputs,
116
+ clickables,
117
+ };
118
+ }
119
+ // If the item contains multiple clickable elements and/or inputs, then the item
120
+ // should not have a clickable input cover over the entire item to prevent
121
+ // interfering with their individual click events
122
+ setMultipleInputs() {
123
+ const { covers, inputs, clickables } = this.totalNestedInputs();
109
124
  // Check for multiple inputs to change the position of the input cover to relative
110
125
  // for all of the covered inputs above
111
126
  this.multipleInputs =
@@ -113,6 +128,11 @@ const Item = /*@__PURE__*/ proxyCustomElement(class Item extends HTMLElement {
113
128
  covers.length + clickables.length > 1 ||
114
129
  (covers.length > 0 && this.isClickable());
115
130
  }
131
+ setIsInteractive() {
132
+ // If item contains any interactive children, set isInteractive to `true`
133
+ const { covers, inputs, clickables } = this.totalNestedInputs();
134
+ this.isInteractive = covers.length > 0 || inputs.length > 0 || clickables.length > 0;
135
+ }
116
136
  // If the item contains an input including a checkbox, datetime, select, or radio
117
137
  // then the item will have a clickable input cover that covers the item
118
138
  // that should get the hover, focused and activated states UNLESS it has multiple
@@ -214,7 +234,7 @@ const Item = /*@__PURE__*/ proxyCustomElement(class Item extends HTMLElement {
214
234
  * However, other form controls such as checkboxes and radios do.
215
235
  */
216
236
  const firstInteractiveNeedsPointerCursor = firstInteractive !== undefined && !['ION-INPUT', 'ION-TEXTAREA'].includes(firstInteractive.tagName);
217
- return (h(Host, { key: '15e7d3b674c25232bc2d51573b291c72548690e5', "aria-disabled": ariaDisabled, class: Object.assign(Object.assign(Object.assign({}, childStyles), labelColorStyles), createColorClasses(this.color, {
237
+ return (h(Host, { key: '24b59935bd8db8b0b7f940582455a42b82cbf762', "aria-disabled": ariaDisabled, class: Object.assign(Object.assign(Object.assign({}, childStyles), labelColorStyles), createColorClasses(this.color, {
218
238
  item: true,
219
239
  [mode]: true,
220
240
  'item-lines-default': lines === undefined,
@@ -226,7 +246,7 @@ const Item = /*@__PURE__*/ proxyCustomElement(class Item extends HTMLElement {
226
246
  'ion-activatable': canActivate,
227
247
  'ion-focusable': this.focusable,
228
248
  'item-rtl': document.dir === 'rtl',
229
- })), role: inList ? 'listitem' : null }, h(TagType, Object.assign({ key: 'f27ae647501e29db554efe8f12f498b165d1dbf6' }, attrs, inheritedAriaAttributes, { class: "item-native", part: "native", disabled: disabled }, clickFn), h("slot", { key: 'ba039a321df0e4bd5ce11e7a6e7e174c8623b112', name: "start" }), h("div", { key: 'e67fdf4ad9086d855da8402ca04ffe292cf6ce67', class: "item-inner" }, h("div", { key: '2434348a0b3f10052a340c57a7ac127ddc851729', class: "input-wrapper" }, h("slot", { key: '3e27090ae4dab734541b3fd2c94de0f98c0f0030' })), h("slot", { key: '52dc633b60fe612d48d2efffbf2c4c62692bef85', name: "end" }), showDetail && (h("ion-icon", { key: '4fb7397dc8cf4cecb82f19a6151f85112b73992b', icon: detailIcon, lazy: false, class: "item-detail-icon", part: "detail-icon", "aria-hidden": "true", "flip-rtl": detailIcon === chevronForward }))), canActivate && mode === 'md' && h("ion-ripple-effect", { key: '09b2413a7a484ecd8ffb00805223727da4d4e344' }))));
249
+ })), role: inList ? 'listitem' : null }, h(TagType, Object.assign({ key: 'fd77b6e5f3eb2e1857a0cdd45562d71eabd30255' }, attrs, inheritedAriaAttributes, { class: "item-native", part: "native", disabled: disabled }, clickFn), h("slot", { key: '8824ac8395aafa3d63c92f2128e947cac8393ac4', name: "start", onSlotchange: this.updateInteractivityOnSlotChange }), h("div", { key: '5c9127e388a432687766d86a9db91fd1663abf03', class: "item-inner" }, h("div", { key: '9dc2d2f58c4067c0143b3963334c346c3c7f77df', class: "input-wrapper" }, h("slot", { key: '8377d9e56dc4b1913f1346111b706e7f14c24d30', onSlotchange: this.updateInteractivityOnSlotChange })), h("slot", { key: 'bc771e106174f4a84ee12e92d14df81ad7ed177d', name: "end", onSlotchange: this.updateInteractivityOnSlotChange }), showDetail && (h("ion-icon", { key: '45336d121a097cbf71ee8a3f6b554745ba5e0bbf', icon: detailIcon, lazy: false, class: "item-detail-icon", part: "detail-icon", "aria-hidden": "true", "flip-rtl": detailIcon === chevronForward }))), canActivate && mode === 'md' && h("ion-ripple-effect", { key: '197e244ae3bffebfa6ac9bfe7658d12e1af0ecb1' }))));
230
250
  }
231
251
  get el() { return this; }
232
252
  static get watchers() { return {
@@ -251,7 +271,8 @@ const Item = /*@__PURE__*/ proxyCustomElement(class Item extends HTMLElement {
251
271
  "target": [1],
252
272
  "type": [1],
253
273
  "multipleInputs": [32],
254
- "focusable": [32]
274
+ "focusable": [32],
275
+ "isInteractive": [32]
255
276
  }, [[0, "ionColor", "labelColorChanged"], [0, "ionStyle", "itemStyle"]], {
256
277
  "button": ["buttonChanged"]
257
278
  }]);
@@ -793,20 +793,20 @@ const portraitToLandscapeTransition = (baseEl, opts, duration = 300) => {
793
793
  // need to care about layering and modal-specific styles.
794
794
  const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
795
795
  const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
796
- const toTransform = `translateY(-10px) scale(${toPresentingScale})`;
796
+ const toTransform = `translateY(0px) scale(1)`;
797
797
  presentingAnimation
798
- .addElement(presentingElRoot.querySelector('.modal-wrapper'))
798
+ .addElement(presentingEl)
799
799
  .afterStyles({
800
800
  transform: toTransform,
801
801
  })
802
802
  .fromTo('transform', fromTransform, toTransform)
803
- .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
803
+ .fromTo('filter', 'contrast(0.85)', 'contrast(1)');
804
804
  const shadowAnimation = createAnimation()
805
805
  .addElement(presentingElRoot.querySelector('.modal-shadow'))
806
806
  .afterStyles({
807
807
  transform: toTransform,
808
+ opacity: '0',
808
809
  })
809
- .fromTo('opacity', '0', '0') // Shadow stays hidden in landscape for card modals
810
810
  .fromTo('transform', fromTransform, toTransform);
811
811
  baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
812
812
  }
@@ -851,17 +851,8 @@ const landscapeToPortraitTransition = (baseEl, opts, duration = 300) => {
851
851
  const toTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
852
852
  presentingAnimation
853
853
  .addElement(presentingEl)
854
- .beforeStyles({
855
- transform: 'translateY(0px) scale(1)',
856
- 'transform-origin': 'top center',
857
- overflow: 'hidden',
858
- })
859
854
  .afterStyles({
860
855
  transform: toTransform,
861
- 'border-radius': '10px 10px 0 0',
862
- filter: 'contrast(0.85)',
863
- overflow: 'hidden',
864
- 'transform-origin': 'top center',
865
856
  })
866
857
  .beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black'))
867
858
  .keyframes([
@@ -876,20 +867,19 @@ const landscapeToPortraitTransition = (baseEl, opts, duration = 300) => {
876
867
  // to handle layering and modal-specific styles.
877
868
  const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
878
869
  const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
879
- const toTransform = `translateY(-10px) scale(${toPresentingScale})`;
870
+ const toTransform = `translateY(0) scale(1)`;
880
871
  presentingAnimation
881
- .addElement(presentingElRoot.querySelector('.modal-wrapper'))
872
+ .addElement(presentingEl)
882
873
  .afterStyles({
883
874
  transform: toTransform,
884
875
  })
885
- .fromTo('transform', fromTransform, toTransform)
886
- .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
876
+ .fromTo('transform', fromTransform, toTransform);
887
877
  const shadowAnimation = createAnimation()
888
878
  .addElement(presentingElRoot.querySelector('.modal-shadow'))
889
879
  .afterStyles({
890
880
  transform: toTransform,
881
+ opacity: '0',
891
882
  })
892
- .fromTo('opacity', '0', '0') // Shadow stays hidden
893
883
  .fromTo('transform', fromTransform, toTransform);
894
884
  baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
895
885
  }
@@ -1710,6 +1700,7 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
1710
1700
  disconnectedCallback() {
1711
1701
  this.triggerController.removeClickListener();
1712
1702
  this.cleanupViewTransitionListener();
1703
+ this.cleanupParentRemovalObserver();
1713
1704
  }
1714
1705
  componentWillLoad() {
1715
1706
  var _a;
@@ -1717,6 +1708,10 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
1717
1708
  const isSheetModal = (this.isSheetModal = breakpoints !== undefined && initialBreakpoint !== undefined);
1718
1709
  const attributesToInherit = ['aria-label', 'role'];
1719
1710
  this.inheritedAttributes = inheritAttributes(el, attributesToInherit);
1711
+ // Cache original parent before modal gets moved to body during presentation
1712
+ if (el.parentNode) {
1713
+ this.cachedOriginalParent = el.parentNode;
1714
+ }
1720
1715
  /**
1721
1716
  * When using a controller modal you can set attributes
1722
1717
  * using the htmlAttributes property. Since the above attributes
@@ -1922,6 +1917,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
1922
1917
  }
1923
1918
  // Initialize view transition listener for iOS card modals
1924
1919
  this.initViewTransitionListener();
1920
+ // Initialize parent removal observer
1921
+ this.initParentRemovalObserver();
1925
1922
  unlock();
1926
1923
  }
1927
1924
  initSwipeToClose() {
@@ -2082,6 +2079,7 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
2082
2079
  this.gesture.destroy();
2083
2080
  }
2084
2081
  this.cleanupViewTransitionListener();
2082
+ this.cleanupParentRemovalObserver();
2085
2083
  }
2086
2084
  this.currentBreakpoint = undefined;
2087
2085
  this.animation = undefined;
@@ -2245,7 +2243,7 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
2245
2243
  wrapperEl.style.transform = 'translateY(0vh)';
2246
2244
  wrapperEl.style.opacity = '1';
2247
2245
  }
2248
- if (presentingElement) {
2246
+ if ((presentingElement === null || presentingElement === void 0 ? void 0 : presentingElement.tagName) === 'ION-MODAL') {
2249
2247
  const isPortrait = window.innerWidth < 768;
2250
2248
  if (isPortrait) {
2251
2249
  const transformOffset = !CSS.supports('width', 'max(0px, 1px)')
@@ -2265,6 +2263,53 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
2265
2263
  await modal.dismiss(undefined, 'parent-dismissed');
2266
2264
  });
2267
2265
  }
2266
+ initParentRemovalObserver() {
2267
+ if (typeof MutationObserver === 'undefined') {
2268
+ return;
2269
+ }
2270
+ // Only observe if we have a cached parent and are in browser environment
2271
+ if (typeof window === 'undefined' || !this.cachedOriginalParent) {
2272
+ return;
2273
+ }
2274
+ // Don't observe document or fragment nodes as they can't be "removed"
2275
+ if (this.cachedOriginalParent.nodeType === Node.DOCUMENT_NODE ||
2276
+ this.cachedOriginalParent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
2277
+ return;
2278
+ }
2279
+ this.parentRemovalObserver = new MutationObserver((mutations) => {
2280
+ mutations.forEach((mutation) => {
2281
+ if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
2282
+ // Check if our cached original parent was removed
2283
+ const cachedParentWasRemoved = Array.from(mutation.removedNodes).some((node) => {
2284
+ var _a, _b;
2285
+ const isDirectMatch = node === this.cachedOriginalParent;
2286
+ const isContainedMatch = this.cachedOriginalParent
2287
+ ? (_b = (_a = node).contains) === null || _b === void 0 ? void 0 : _b.call(_a, this.cachedOriginalParent)
2288
+ : false;
2289
+ return isDirectMatch || isContainedMatch;
2290
+ });
2291
+ // Also check if parent is no longer connected to DOM
2292
+ const cachedParentDisconnected = this.cachedOriginalParent && !this.cachedOriginalParent.isConnected;
2293
+ if (cachedParentWasRemoved || cachedParentDisconnected) {
2294
+ this.dismiss(undefined, 'parent-removed');
2295
+ // Release the reference to the cached original parent
2296
+ // so we don't have a memory leak
2297
+ this.cachedOriginalParent = undefined;
2298
+ }
2299
+ }
2300
+ });
2301
+ });
2302
+ // Observe document body with subtree to catch removals at any level
2303
+ this.parentRemovalObserver.observe(document.body, {
2304
+ childList: true,
2305
+ subtree: true,
2306
+ });
2307
+ }
2308
+ cleanupParentRemovalObserver() {
2309
+ var _a;
2310
+ (_a = this.parentRemovalObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
2311
+ this.parentRemovalObserver = undefined;
2312
+ }
2268
2313
  render() {
2269
2314
  const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
2270
2315
  const showHandle = handle !== false && isSheetModal;
@@ -2272,20 +2317,20 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
2272
2317
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2273
2318
  const isHandleCycle = handleBehavior === 'cycle';
2274
2319
  const isSheetModalWithHandle = isSheetModal && showHandle;
2275
- return (h(Host, Object.assign({ key: '4cb27cda1c331191ccc623e15a999e524d053397', "no-router": true,
2320
+ return (h(Host, Object.assign({ key: '9e9a7bd591eb17a225a00b4fa2e379e94601d17f', "no-router": true,
2276
2321
  // Allow the modal to be navigable when the handle is focusable
2277
2322
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2278
2323
  zIndex: `${20000 + this.overlayIndex}`,
2279
- }, 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: '5de598f6aaad693da61a8cdfd8ea73e3d9cc762e', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '2e74d2ed93c7884cbb1e3561d7296bba46e10426', class: "modal-shadow" }), h("div", Object.assign({ key: '314a7f8f669639b2d3f3a61a86e3b9de31a3ec78',
2324
+ }, 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: 'e5eae2c14f830f75e308fcd7f4c10c86fac5b962', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: 'e268f9cd310c3cf4e051b5b92524ce4fb70d005e', class: "modal-shadow" }), h("div", Object.assign({ key: '9c380f36c18144c153077b15744d1c3346bce63e',
2280
2325
  /*
2281
2326
  role and aria-modal must be used on the
2282
2327
  same element. They must also be set inside the
2283
2328
  shadow DOM otherwise ion-button will not be highlighted
2284
2329
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2285
2330
  */
2286
- role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'fc16ab670165f5ecc6b27b026637094da16d7af4', class: "modal-handle",
2331
+ role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '2d5ee6d5959d97309c306e8ce72eb0f2c19be144', class: "modal-handle",
2287
2332
  // Prevents the handle from receiving keyboard focus when it does not cycle
2288
- 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: 'e3634c590f877dc34d39d651c059fa3f701cfb4d', onSlotchange: this.onSlotChange }))));
2333
+ 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: '5590434c35ea04c42fc006498bc189038e15a298', onSlotchange: this.onSlotChange }))));
2289
2334
  }
2290
2335
  get el() { return this; }
2291
2336
  static get watchers() { return {
@@ -20,6 +20,7 @@ const Item = class {
20
20
  this.inheritedAriaAttributes = {};
21
21
  this.multipleInputs = false;
22
22
  this.focusable = true;
23
+ this.isInteractive = false;
23
24
  /**
24
25
  * If `true`, a button tag will be rendered and the item will be tappable.
25
26
  */
@@ -41,6 +42,11 @@ const Item = class {
41
42
  * The type of the button. Only used when an `onclick` or `button` property is present.
42
43
  */
43
44
  this.type = 'button';
45
+ // slot change listener updates state to reflect how/if item should be interactive
46
+ this.updateInteractivityOnSlotChange = () => {
47
+ this.setIsInteractive();
48
+ this.setMultipleInputs();
49
+ };
44
50
  }
45
51
  buttonChanged() {
46
52
  // Update the focusable option when the button option is changed
@@ -88,13 +94,11 @@ const Item = class {
88
94
  componentDidLoad() {
89
95
  helpers.raf(() => {
90
96
  this.setMultipleInputs();
97
+ this.setIsInteractive();
91
98
  this.focusable = this.isFocusable();
92
99
  });
93
100
  }
94
- // If the item contains multiple clickable elements and/or inputs, then the item
95
- // should not have a clickable input cover over the entire item to prevent
96
- // interfering with their individual click events
97
- setMultipleInputs() {
101
+ totalNestedInputs() {
98
102
  // The following elements have a clickable cover that is relative to the entire item
99
103
  const covers = this.el.querySelectorAll('ion-checkbox, ion-datetime, ion-select, ion-radio');
100
104
  // The following elements can accept focus alongside the previous elements
@@ -103,6 +107,17 @@ const Item = class {
103
107
  const inputs = this.el.querySelectorAll('ion-input, ion-range, ion-searchbar, ion-segment, ion-textarea, ion-toggle');
104
108
  // The following elements should also stay clickable when an input with cover is present
105
109
  const clickables = this.el.querySelectorAll('ion-router-link, ion-button, a, button');
110
+ return {
111
+ covers,
112
+ inputs,
113
+ clickables,
114
+ };
115
+ }
116
+ // If the item contains multiple clickable elements and/or inputs, then the item
117
+ // should not have a clickable input cover over the entire item to prevent
118
+ // interfering with their individual click events
119
+ setMultipleInputs() {
120
+ const { covers, inputs, clickables } = this.totalNestedInputs();
106
121
  // Check for multiple inputs to change the position of the input cover to relative
107
122
  // for all of the covered inputs above
108
123
  this.multipleInputs =
@@ -110,6 +125,11 @@ const Item = class {
110
125
  covers.length + clickables.length > 1 ||
111
126
  (covers.length > 0 && this.isClickable());
112
127
  }
128
+ setIsInteractive() {
129
+ // If item contains any interactive children, set isInteractive to `true`
130
+ const { covers, inputs, clickables } = this.totalNestedInputs();
131
+ this.isInteractive = covers.length > 0 || inputs.length > 0 || clickables.length > 0;
132
+ }
113
133
  // If the item contains an input including a checkbox, datetime, select, or radio
114
134
  // then the item will have a clickable input cover that covers the item
115
135
  // that should get the hover, focused and activated states UNLESS it has multiple
@@ -211,7 +231,7 @@ const Item = class {
211
231
  * However, other form controls such as checkboxes and radios do.
212
232
  */
213
233
  const firstInteractiveNeedsPointerCursor = firstInteractive !== undefined && !['ION-INPUT', 'ION-TEXTAREA'].includes(firstInteractive.tagName);
214
- return (index.h(index.Host, { key: '15e7d3b674c25232bc2d51573b291c72548690e5', "aria-disabled": ariaDisabled, class: Object.assign(Object.assign(Object.assign({}, childStyles), labelColorStyles), theme.createColorClasses(this.color, {
234
+ return (index.h(index.Host, { key: '24b59935bd8db8b0b7f940582455a42b82cbf762', "aria-disabled": ariaDisabled, class: Object.assign(Object.assign(Object.assign({}, childStyles), labelColorStyles), theme.createColorClasses(this.color, {
215
235
  item: true,
216
236
  [mode]: true,
217
237
  'item-lines-default': lines === undefined,
@@ -223,7 +243,7 @@ const Item = class {
223
243
  'ion-activatable': canActivate,
224
244
  'ion-focusable': this.focusable,
225
245
  'item-rtl': document.dir === 'rtl',
226
- })), role: inList ? 'listitem' : null }, index.h(TagType, Object.assign({ key: 'f27ae647501e29db554efe8f12f498b165d1dbf6' }, attrs, inheritedAriaAttributes, { class: "item-native", part: "native", disabled: disabled }, clickFn), index.h("slot", { key: 'ba039a321df0e4bd5ce11e7a6e7e174c8623b112', name: "start" }), index.h("div", { key: 'e67fdf4ad9086d855da8402ca04ffe292cf6ce67', class: "item-inner" }, index.h("div", { key: '2434348a0b3f10052a340c57a7ac127ddc851729', class: "input-wrapper" }, index.h("slot", { key: '3e27090ae4dab734541b3fd2c94de0f98c0f0030' })), index.h("slot", { key: '52dc633b60fe612d48d2efffbf2c4c62692bef85', name: "end" }), showDetail && (index.h("ion-icon", { key: '4fb7397dc8cf4cecb82f19a6151f85112b73992b', icon: detailIcon, lazy: false, class: "item-detail-icon", part: "detail-icon", "aria-hidden": "true", "flip-rtl": detailIcon === index$1.chevronForward }))), canActivate && mode === 'md' && index.h("ion-ripple-effect", { key: '09b2413a7a484ecd8ffb00805223727da4d4e344' }))));
246
+ })), role: inList ? 'listitem' : null }, index.h(TagType, Object.assign({ key: 'fd77b6e5f3eb2e1857a0cdd45562d71eabd30255' }, attrs, inheritedAriaAttributes, { class: "item-native", part: "native", disabled: disabled }, clickFn), index.h("slot", { key: '8824ac8395aafa3d63c92f2128e947cac8393ac4', name: "start", onSlotchange: this.updateInteractivityOnSlotChange }), index.h("div", { key: '5c9127e388a432687766d86a9db91fd1663abf03', class: "item-inner" }, index.h("div", { key: '9dc2d2f58c4067c0143b3963334c346c3c7f77df', class: "input-wrapper" }, index.h("slot", { key: '8377d9e56dc4b1913f1346111b706e7f14c24d30', onSlotchange: this.updateInteractivityOnSlotChange })), index.h("slot", { key: 'bc771e106174f4a84ee12e92d14df81ad7ed177d', name: "end", onSlotchange: this.updateInteractivityOnSlotChange }), showDetail && (index.h("ion-icon", { key: '45336d121a097cbf71ee8a3f6b554745ba5e0bbf', icon: detailIcon, lazy: false, class: "item-detail-icon", part: "detail-icon", "aria-hidden": "true", "flip-rtl": detailIcon === index$1.chevronForward }))), canActivate && mode === 'md' && index.h("ion-ripple-effect", { key: '197e244ae3bffebfa6ac9bfe7658d12e1af0ecb1' }))));
227
247
  }
228
248
  get el() { return index.getElement(this); }
229
249
  static get watchers() { return {
@@ -795,20 +795,20 @@ const portraitToLandscapeTransition = (baseEl, opts, duration = 300) => {
795
795
  // need to care about layering and modal-specific styles.
796
796
  const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
797
797
  const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
798
- const toTransform = `translateY(-10px) scale(${toPresentingScale})`;
798
+ const toTransform = `translateY(0px) scale(1)`;
799
799
  presentingAnimation
800
- .addElement(presentingElRoot.querySelector('.modal-wrapper'))
800
+ .addElement(presentingEl)
801
801
  .afterStyles({
802
802
  transform: toTransform,
803
803
  })
804
804
  .fromTo('transform', fromTransform, toTransform)
805
- .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
805
+ .fromTo('filter', 'contrast(0.85)', 'contrast(1)');
806
806
  const shadowAnimation = animation.createAnimation()
807
807
  .addElement(presentingElRoot.querySelector('.modal-shadow'))
808
808
  .afterStyles({
809
809
  transform: toTransform,
810
+ opacity: '0',
810
811
  })
811
- .fromTo('opacity', '0', '0') // Shadow stays hidden in landscape for card modals
812
812
  .fromTo('transform', fromTransform, toTransform);
813
813
  baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
814
814
  }
@@ -853,17 +853,8 @@ const landscapeToPortraitTransition = (baseEl, opts, duration = 300) => {
853
853
  const toTransform = `translateY(${transformOffset}) scale(${toPresentingScale})`;
854
854
  presentingAnimation
855
855
  .addElement(presentingEl)
856
- .beforeStyles({
857
- transform: 'translateY(0px) scale(1)',
858
- 'transform-origin': 'top center',
859
- overflow: 'hidden',
860
- })
861
856
  .afterStyles({
862
857
  transform: toTransform,
863
- 'border-radius': '10px 10px 0 0',
864
- filter: 'contrast(0.85)',
865
- overflow: 'hidden',
866
- 'transform-origin': 'top center',
867
858
  })
868
859
  .beforeAddWrite(() => bodyEl.style.setProperty('background-color', 'black'))
869
860
  .keyframes([
@@ -878,20 +869,19 @@ const landscapeToPortraitTransition = (baseEl, opts, duration = 300) => {
878
869
  // to handle layering and modal-specific styles.
879
870
  const toPresentingScale = SwipeToCloseDefaults.MIN_PRESENTING_SCALE;
880
871
  const fromTransform = `translateY(-10px) scale(${toPresentingScale})`;
881
- const toTransform = `translateY(-10px) scale(${toPresentingScale})`;
872
+ const toTransform = `translateY(0) scale(1)`;
882
873
  presentingAnimation
883
- .addElement(presentingElRoot.querySelector('.modal-wrapper'))
874
+ .addElement(presentingEl)
884
875
  .afterStyles({
885
876
  transform: toTransform,
886
877
  })
887
- .fromTo('transform', fromTransform, toTransform)
888
- .fromTo('filter', 'contrast(0.85)', 'contrast(0.85)'); // Keep same contrast for card
878
+ .fromTo('transform', fromTransform, toTransform);
889
879
  const shadowAnimation = animation.createAnimation()
890
880
  .addElement(presentingElRoot.querySelector('.modal-shadow'))
891
881
  .afterStyles({
892
882
  transform: toTransform,
883
+ opacity: '0',
893
884
  })
894
- .fromTo('opacity', '0', '0') // Shadow stays hidden
895
885
  .fromTo('transform', fromTransform, toTransform);
896
886
  baseAnimation.addAnimation([presentingAnimation, shadowAnimation]);
897
887
  }
@@ -1710,6 +1700,7 @@ const Modal = class {
1710
1700
  disconnectedCallback() {
1711
1701
  this.triggerController.removeClickListener();
1712
1702
  this.cleanupViewTransitionListener();
1703
+ this.cleanupParentRemovalObserver();
1713
1704
  }
1714
1705
  componentWillLoad() {
1715
1706
  var _a;
@@ -1717,6 +1708,10 @@ const Modal = class {
1717
1708
  const isSheetModal = (this.isSheetModal = breakpoints !== undefined && initialBreakpoint !== undefined);
1718
1709
  const attributesToInherit = ['aria-label', 'role'];
1719
1710
  this.inheritedAttributes = helpers.inheritAttributes(el, attributesToInherit);
1711
+ // Cache original parent before modal gets moved to body during presentation
1712
+ if (el.parentNode) {
1713
+ this.cachedOriginalParent = el.parentNode;
1714
+ }
1720
1715
  /**
1721
1716
  * When using a controller modal you can set attributes
1722
1717
  * using the htmlAttributes property. Since the above attributes
@@ -1922,6 +1917,8 @@ const Modal = class {
1922
1917
  }
1923
1918
  // Initialize view transition listener for iOS card modals
1924
1919
  this.initViewTransitionListener();
1920
+ // Initialize parent removal observer
1921
+ this.initParentRemovalObserver();
1925
1922
  unlock();
1926
1923
  }
1927
1924
  initSwipeToClose() {
@@ -2082,6 +2079,7 @@ const Modal = class {
2082
2079
  this.gesture.destroy();
2083
2080
  }
2084
2081
  this.cleanupViewTransitionListener();
2082
+ this.cleanupParentRemovalObserver();
2085
2083
  }
2086
2084
  this.currentBreakpoint = undefined;
2087
2085
  this.animation = undefined;
@@ -2245,7 +2243,7 @@ const Modal = class {
2245
2243
  wrapperEl.style.transform = 'translateY(0vh)';
2246
2244
  wrapperEl.style.opacity = '1';
2247
2245
  }
2248
- if (presentingElement) {
2246
+ if ((presentingElement === null || presentingElement === void 0 ? void 0 : presentingElement.tagName) === 'ION-MODAL') {
2249
2247
  const isPortrait = window.innerWidth < 768;
2250
2248
  if (isPortrait) {
2251
2249
  const transformOffset = !CSS.supports('width', 'max(0px, 1px)')
@@ -2265,6 +2263,53 @@ const Modal = class {
2265
2263
  await modal.dismiss(undefined, 'parent-dismissed');
2266
2264
  });
2267
2265
  }
2266
+ initParentRemovalObserver() {
2267
+ if (typeof MutationObserver === 'undefined') {
2268
+ return;
2269
+ }
2270
+ // Only observe if we have a cached parent and are in browser environment
2271
+ if (typeof window === 'undefined' || !this.cachedOriginalParent) {
2272
+ return;
2273
+ }
2274
+ // Don't observe document or fragment nodes as they can't be "removed"
2275
+ if (this.cachedOriginalParent.nodeType === Node.DOCUMENT_NODE ||
2276
+ this.cachedOriginalParent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
2277
+ return;
2278
+ }
2279
+ this.parentRemovalObserver = new MutationObserver((mutations) => {
2280
+ mutations.forEach((mutation) => {
2281
+ if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
2282
+ // Check if our cached original parent was removed
2283
+ const cachedParentWasRemoved = Array.from(mutation.removedNodes).some((node) => {
2284
+ var _a, _b;
2285
+ const isDirectMatch = node === this.cachedOriginalParent;
2286
+ const isContainedMatch = this.cachedOriginalParent
2287
+ ? (_b = (_a = node).contains) === null || _b === void 0 ? void 0 : _b.call(_a, this.cachedOriginalParent)
2288
+ : false;
2289
+ return isDirectMatch || isContainedMatch;
2290
+ });
2291
+ // Also check if parent is no longer connected to DOM
2292
+ const cachedParentDisconnected = this.cachedOriginalParent && !this.cachedOriginalParent.isConnected;
2293
+ if (cachedParentWasRemoved || cachedParentDisconnected) {
2294
+ this.dismiss(undefined, 'parent-removed');
2295
+ // Release the reference to the cached original parent
2296
+ // so we don't have a memory leak
2297
+ this.cachedOriginalParent = undefined;
2298
+ }
2299
+ }
2300
+ });
2301
+ });
2302
+ // Observe document body with subtree to catch removals at any level
2303
+ this.parentRemovalObserver.observe(document.body, {
2304
+ childList: true,
2305
+ subtree: true,
2306
+ });
2307
+ }
2308
+ cleanupParentRemovalObserver() {
2309
+ var _a;
2310
+ (_a = this.parentRemovalObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
2311
+ this.parentRemovalObserver = undefined;
2312
+ }
2268
2313
  render() {
2269
2314
  const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
2270
2315
  const showHandle = handle !== false && isSheetModal;
@@ -2272,20 +2317,20 @@ const Modal = class {
2272
2317
  const isCardModal = presentingElement !== undefined && mode === 'ios';
2273
2318
  const isHandleCycle = handleBehavior === 'cycle';
2274
2319
  const isSheetModalWithHandle = isSheetModal && showHandle;
2275
- return (index$3.h(index$3.Host, Object.assign({ key: '4cb27cda1c331191ccc623e15a999e524d053397', "no-router": true,
2320
+ return (index$3.h(index$3.Host, Object.assign({ key: '9e9a7bd591eb17a225a00b4fa2e379e94601d17f', "no-router": true,
2276
2321
  // Allow the modal to be navigable when the handle is focusable
2277
2322
  tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
2278
2323
  zIndex: `${20000 + this.overlayIndex}`,
2279
- }, 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: '5de598f6aaad693da61a8cdfd8ea73e3d9cc762e', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && index$3.h("div", { key: '2e74d2ed93c7884cbb1e3561d7296bba46e10426', class: "modal-shadow" }), index$3.h("div", Object.assign({ key: '314a7f8f669639b2d3f3a61a86e3b9de31a3ec78',
2324
+ }, 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: 'e5eae2c14f830f75e308fcd7f4c10c86fac5b962', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && index$3.h("div", { key: 'e268f9cd310c3cf4e051b5b92524ce4fb70d005e', class: "modal-shadow" }), index$3.h("div", Object.assign({ key: '9c380f36c18144c153077b15744d1c3346bce63e',
2280
2325
  /*
2281
2326
  role and aria-modal must be used on the
2282
2327
  same element. They must also be set inside the
2283
2328
  shadow DOM otherwise ion-button will not be highlighted
2284
2329
  when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
2285
2330
  */
2286
- 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: 'fc16ab670165f5ecc6b27b026637094da16d7af4', class: "modal-handle",
2331
+ 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: '2d5ee6d5959d97309c306e8ce72eb0f2c19be144', class: "modal-handle",
2287
2332
  // Prevents the handle from receiving keyboard focus when it does not cycle
2288
- 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: 'e3634c590f877dc34d39d651c059fa3f701cfb4d', onSlotchange: this.onSlotChange }))));
2333
+ 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: '5590434c35ea04c42fc006498bc189038e15a298', onSlotchange: this.onSlotChange }))));
2289
2334
  }
2290
2335
  get el() { return index$3.getElement(this); }
2291
2336
  static get watchers() { return {