@ionic/core 8.6.1-dev.11749562115.1e681558 → 8.6.1-dev.11749572912.152b544d

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.
@@ -185,7 +185,6 @@ const ItemSliding = /*@__PURE__*/ proxyCustomElement(class ItemSliding extends H
185
185
  }
186
186
  }
187
187
  async updateOptions() {
188
- var _a;
189
188
  const options = this.el.querySelectorAll('ion-item-options');
190
189
  let sides = 0;
191
190
  // Reset left and right options in case they were removed
@@ -199,7 +198,7 @@ const ItemSliding = /*@__PURE__*/ proxyCustomElement(class ItemSliding extends H
199
198
  */
200
199
  // eslint-disable-next-line custom-rules/no-component-on-ready-method
201
200
  const option = item.componentOnReady !== undefined ? await item.componentOnReady() : item;
202
- const side = isEndSide((_a = option.side) !== null && _a !== void 0 ? _a : option.getAttribute('side')) ? 'end' : 'start';
201
+ const side = isEndSide(option.side) ? 'end' : 'start';
203
202
  if (side === 'start') {
204
203
  this.leftOptions = option;
205
204
  sides |= 1 /* ItemSide.Start */;
@@ -397,7 +396,7 @@ const ItemSliding = /*@__PURE__*/ proxyCustomElement(class ItemSliding extends H
397
396
  }
398
397
  render() {
399
398
  const mode = getIonMode(this);
400
- return (h(Host, { key: 'd812322c9fb5da4ee16e99dc38bfb24cb4590d03', class: {
399
+ return (h(Host, { key: '9880396ad79e06117d572a27f92c4b753d1e26db', class: {
401
400
  [mode]: true,
402
401
  'item-sliding-active-slide': this.state !== 2 /* SlidingState.Disabled */,
403
402
  'item-sliding-active-options-end': (this.state & 8 /* SlidingState.End */) !== 0,
@@ -827,7 +827,7 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
827
827
  let offset = 0;
828
828
  let canDismissBlocksGesture = false;
829
829
  let cachedScrollEl = null;
830
- let cachedFooterEl = null;
830
+ let cachedFooterEls = null;
831
831
  let cachedFooterYPosition = null;
832
832
  let currentFooterState = null;
833
833
  const canDismissMaxStep = 0.95;
@@ -864,60 +864,81 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
864
864
  * @param newPosition Whether the footer is in a moving or stationary position.
865
865
  */
866
866
  const swapFooterPosition = (newPosition) => {
867
- if (!cachedFooterEl) {
868
- cachedFooterEl = baseEl.querySelector('ion-footer');
869
- if (!cachedFooterEl) {
867
+ if (!cachedFooterEls) {
868
+ cachedFooterEls = Array.from(baseEl.querySelectorAll('ion-footer'));
869
+ if (!cachedFooterEls.length) {
870
870
  return;
871
871
  }
872
872
  }
873
873
  const page = baseEl.querySelector('.ion-page');
874
874
  currentFooterState = newPosition;
875
875
  if (newPosition === 'stationary') {
876
- // Reset positioning styles to allow normal document flow
877
- cachedFooterEl.classList.remove('modal-footer-moving');
878
- cachedFooterEl.style.removeProperty('position');
879
- cachedFooterEl.style.removeProperty('width');
880
- cachedFooterEl.style.removeProperty('height');
881
- cachedFooterEl.style.removeProperty('top');
882
- cachedFooterEl.style.removeProperty('left');
883
- page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
884
- // Move to page
885
- page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
876
+ cachedFooterEls.forEach((cachedFooterEl) => {
877
+ // Reset positioning styles to allow normal document flow
878
+ cachedFooterEl.classList.remove('modal-footer-moving');
879
+ cachedFooterEl.style.removeProperty('position');
880
+ cachedFooterEl.style.removeProperty('width');
881
+ cachedFooterEl.style.removeProperty('height');
882
+ cachedFooterEl.style.removeProperty('top');
883
+ cachedFooterEl.style.removeProperty('left');
884
+ page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
885
+ // Move to page
886
+ page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
887
+ });
886
888
  }
887
889
  else {
888
- // Get both the footer and document body positions
889
- const cachedFooterElRect = cachedFooterEl.getBoundingClientRect();
890
- const bodyRect = document.body.getBoundingClientRect();
891
- // Add padding to the parent element to prevent content from being hidden
892
- // when the footer is positioned absolutely. This has to be done before we
893
- // make the footer absolutely positioned or we may accidentally cause the
894
- // sheet to scroll.
895
- const footerHeight = cachedFooterEl.clientHeight;
896
- page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeight}px`);
897
- // Apply positioning styles to keep footer at bottom
898
- cachedFooterEl.classList.add('modal-footer-moving');
899
- // Calculate absolute position relative to body
900
- // We need to subtract the body's offsetTop to get true position within document.body
901
- const absoluteTop = cachedFooterElRect.top - bodyRect.top;
902
- const absoluteLeft = cachedFooterElRect.left - bodyRect.left;
903
- // Capture the footer's current dimensions and hard code them during the drag
904
- cachedFooterEl.style.setProperty('position', 'absolute');
905
- cachedFooterEl.style.setProperty('width', `${cachedFooterEl.clientWidth}px`);
906
- cachedFooterEl.style.setProperty('height', `${cachedFooterEl.clientHeight}px`);
907
- cachedFooterEl.style.setProperty('top', `${absoluteTop}px`);
908
- cachedFooterEl.style.setProperty('left', `${absoluteLeft}px`);
909
- // Also cache the footer Y position, which we use to determine if the
910
- // sheet has been moved below the footer. When that happens, we need to swap
911
- // the position back so it will collapse correctly.
912
- cachedFooterYPosition = absoluteTop;
913
- // If there's a toolbar, we need to combine the toolbar height with the footer position
914
- // because the toolbar moves with the drag handle, so when it starts overlapping the footer,
915
- // we need to account for that.
916
- const toolbar = baseEl.querySelector('ion-toolbar');
917
- if (toolbar) {
918
- cachedFooterYPosition -= toolbar.clientHeight;
919
- }
920
- document.body.appendChild(cachedFooterEl);
890
+ let footerHeights = 0;
891
+ cachedFooterEls.forEach((cachedFooterEl, index) => {
892
+ // Get both the footer and document body positions
893
+ const cachedFooterElRect = cachedFooterEl.getBoundingClientRect();
894
+ const bodyRect = document.body.getBoundingClientRect();
895
+ // Calculate the total height of all footers
896
+ // so we can add padding to the page element
897
+ footerHeights += cachedFooterEl.clientHeight;
898
+ // Calculate absolute position relative to body
899
+ // We need to subtract the body's offsetTop to get true position within document.body
900
+ const absoluteTop = cachedFooterElRect.top - bodyRect.top;
901
+ const absoluteLeft = cachedFooterElRect.left - bodyRect.left;
902
+ // Capture the footer's current dimensions and store them in CSS variables for
903
+ // later use when applying absolute positioning.
904
+ cachedFooterEl.style.setProperty('--pinned-width', `${cachedFooterEl.clientWidth}px`);
905
+ cachedFooterEl.style.setProperty('--pinned-height', `${cachedFooterEl.clientHeight}px`);
906
+ cachedFooterEl.style.setProperty('--pinned-top', `${absoluteTop}px`);
907
+ cachedFooterEl.style.setProperty('--pinned-left', `${absoluteLeft}px`);
908
+ // Only cache the first footer's Y position
909
+ // This is used to determine if the sheet has been moved below the footer
910
+ // and needs to be swapped back to stationary so it collapses correctly.
911
+ if (index === 0) {
912
+ cachedFooterYPosition = absoluteTop;
913
+ // If there's a toolbar, we need to combine the toolbar height with the footer position
914
+ // because the toolbar moves with the drag handle, so when it starts overlapping the footer,
915
+ // we need to account for that.
916
+ const toolbar = baseEl.querySelector('ion-toolbar');
917
+ if (toolbar) {
918
+ cachedFooterYPosition -= toolbar.clientHeight;
919
+ }
920
+ }
921
+ });
922
+ // Apply the pinning of styles after we've calculated everything
923
+ // so that we don't cause layouts to shift while calculating the footer positions.
924
+ // Otherwise, with multiple footers we'll end up capturing the wrong positions.
925
+ cachedFooterEls.forEach((cachedFooterEl) => {
926
+ // Add padding to the parent element to prevent content from being hidden
927
+ // when the footer is positioned absolutely. This has to be done before we
928
+ // make the footer absolutely positioned or we may accidentally cause the
929
+ // sheet to scroll.
930
+ page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeights}px`);
931
+ // Apply positioning styles to keep footer at bottom
932
+ cachedFooterEl.classList.add('modal-footer-moving');
933
+ // Apply our preserved styles to pin the footer
934
+ cachedFooterEl.style.setProperty('position', 'absolute');
935
+ cachedFooterEl.style.setProperty('width', 'var(--pinned-width)');
936
+ cachedFooterEl.style.setProperty('height', 'var(--pinned-height)');
937
+ cachedFooterEl.style.setProperty('top', 'var(--pinned-top)');
938
+ cachedFooterEl.style.setProperty('left', 'var(--pinned-left)');
939
+ // Move the element to the body when everything else is done
940
+ document.body.appendChild(cachedFooterEl);
941
+ });
921
942
  }
922
943
  };
923
944
  /**
@@ -1109,6 +1130,16 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1109
1130
  * is not scrolled to the top.
1110
1131
  */
1111
1132
  if (!expandToScroll && detail.deltaY <= 0 && cachedScrollEl && cachedScrollEl.scrollTop > 0) {
1133
+ /**
1134
+ * If expand to scroll is disabled, we need to make sure we swap the footer position
1135
+ * back to stationary so that it will collapse correctly if the modal is dismissed without
1136
+ * dragging (e.g. through a dismiss button).
1137
+ * This can cause issues if the user has a modal with content that can be dragged, as we'll
1138
+ * swap to moving on drag and if we don't swap back here then the footer will get stuck.
1139
+ */
1140
+ if (!expandToScroll) {
1141
+ swapFooterPosition('stationary');
1142
+ }
1112
1143
  return;
1113
1144
  }
1114
1145
  /**
@@ -282,7 +282,6 @@ const ItemSliding = class {
282
282
  }
283
283
  }
284
284
  async updateOptions() {
285
- var _a;
286
285
  const options = this.el.querySelectorAll('ion-item-options');
287
286
  let sides = 0;
288
287
  // Reset left and right options in case they were removed
@@ -296,7 +295,7 @@ const ItemSliding = class {
296
295
  */
297
296
  // eslint-disable-next-line custom-rules/no-component-on-ready-method
298
297
  const option = item.componentOnReady !== undefined ? await item.componentOnReady() : item;
299
- const side = helpers.isEndSide((_a = option.side) !== null && _a !== void 0 ? _a : option.getAttribute('side')) ? 'end' : 'start';
298
+ const side = helpers.isEndSide(option.side) ? 'end' : 'start';
300
299
  if (side === 'start') {
301
300
  this.leftOptions = option;
302
301
  sides |= 1 /* ItemSide.Start */;
@@ -494,7 +493,7 @@ const ItemSliding = class {
494
493
  }
495
494
  render() {
496
495
  const mode = index.getIonMode(this);
497
- return (index.h(index.Host, { key: 'd812322c9fb5da4ee16e99dc38bfb24cb4590d03', class: {
496
+ return (index.h(index.Host, { key: '9880396ad79e06117d572a27f92c4b753d1e26db', class: {
498
497
  [mode]: true,
499
498
  'item-sliding-active-slide': this.state !== 2 /* SlidingState.Disabled */,
500
499
  'item-sliding-active-options-end': (this.state & 8 /* SlidingState.End */) !== 0,
@@ -829,7 +829,7 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
829
829
  let offset = 0;
830
830
  let canDismissBlocksGesture = false;
831
831
  let cachedScrollEl = null;
832
- let cachedFooterEl = null;
832
+ let cachedFooterEls = null;
833
833
  let cachedFooterYPosition = null;
834
834
  let currentFooterState = null;
835
835
  const canDismissMaxStep = 0.95;
@@ -866,60 +866,81 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
866
866
  * @param newPosition Whether the footer is in a moving or stationary position.
867
867
  */
868
868
  const swapFooterPosition = (newPosition) => {
869
- if (!cachedFooterEl) {
870
- cachedFooterEl = baseEl.querySelector('ion-footer');
871
- if (!cachedFooterEl) {
869
+ if (!cachedFooterEls) {
870
+ cachedFooterEls = Array.from(baseEl.querySelectorAll('ion-footer'));
871
+ if (!cachedFooterEls.length) {
872
872
  return;
873
873
  }
874
874
  }
875
875
  const page = baseEl.querySelector('.ion-page');
876
876
  currentFooterState = newPosition;
877
877
  if (newPosition === 'stationary') {
878
- // Reset positioning styles to allow normal document flow
879
- cachedFooterEl.classList.remove('modal-footer-moving');
880
- cachedFooterEl.style.removeProperty('position');
881
- cachedFooterEl.style.removeProperty('width');
882
- cachedFooterEl.style.removeProperty('height');
883
- cachedFooterEl.style.removeProperty('top');
884
- cachedFooterEl.style.removeProperty('left');
885
- page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
886
- // Move to page
887
- page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
878
+ cachedFooterEls.forEach((cachedFooterEl) => {
879
+ // Reset positioning styles to allow normal document flow
880
+ cachedFooterEl.classList.remove('modal-footer-moving');
881
+ cachedFooterEl.style.removeProperty('position');
882
+ cachedFooterEl.style.removeProperty('width');
883
+ cachedFooterEl.style.removeProperty('height');
884
+ cachedFooterEl.style.removeProperty('top');
885
+ cachedFooterEl.style.removeProperty('left');
886
+ page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
887
+ // Move to page
888
+ page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
889
+ });
888
890
  }
889
891
  else {
890
- // Get both the footer and document body positions
891
- const cachedFooterElRect = cachedFooterEl.getBoundingClientRect();
892
- const bodyRect = document.body.getBoundingClientRect();
893
- // Add padding to the parent element to prevent content from being hidden
894
- // when the footer is positioned absolutely. This has to be done before we
895
- // make the footer absolutely positioned or we may accidentally cause the
896
- // sheet to scroll.
897
- const footerHeight = cachedFooterEl.clientHeight;
898
- page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeight}px`);
899
- // Apply positioning styles to keep footer at bottom
900
- cachedFooterEl.classList.add('modal-footer-moving');
901
- // Calculate absolute position relative to body
902
- // We need to subtract the body's offsetTop to get true position within document.body
903
- const absoluteTop = cachedFooterElRect.top - bodyRect.top;
904
- const absoluteLeft = cachedFooterElRect.left - bodyRect.left;
905
- // Capture the footer's current dimensions and hard code them during the drag
906
- cachedFooterEl.style.setProperty('position', 'absolute');
907
- cachedFooterEl.style.setProperty('width', `${cachedFooterEl.clientWidth}px`);
908
- cachedFooterEl.style.setProperty('height', `${cachedFooterEl.clientHeight}px`);
909
- cachedFooterEl.style.setProperty('top', `${absoluteTop}px`);
910
- cachedFooterEl.style.setProperty('left', `${absoluteLeft}px`);
911
- // Also cache the footer Y position, which we use to determine if the
912
- // sheet has been moved below the footer. When that happens, we need to swap
913
- // the position back so it will collapse correctly.
914
- cachedFooterYPosition = absoluteTop;
915
- // If there's a toolbar, we need to combine the toolbar height with the footer position
916
- // because the toolbar moves with the drag handle, so when it starts overlapping the footer,
917
- // we need to account for that.
918
- const toolbar = baseEl.querySelector('ion-toolbar');
919
- if (toolbar) {
920
- cachedFooterYPosition -= toolbar.clientHeight;
921
- }
922
- document.body.appendChild(cachedFooterEl);
892
+ let footerHeights = 0;
893
+ cachedFooterEls.forEach((cachedFooterEl, index) => {
894
+ // Get both the footer and document body positions
895
+ const cachedFooterElRect = cachedFooterEl.getBoundingClientRect();
896
+ const bodyRect = document.body.getBoundingClientRect();
897
+ // Calculate the total height of all footers
898
+ // so we can add padding to the page element
899
+ footerHeights += cachedFooterEl.clientHeight;
900
+ // Calculate absolute position relative to body
901
+ // We need to subtract the body's offsetTop to get true position within document.body
902
+ const absoluteTop = cachedFooterElRect.top - bodyRect.top;
903
+ const absoluteLeft = cachedFooterElRect.left - bodyRect.left;
904
+ // Capture the footer's current dimensions and store them in CSS variables for
905
+ // later use when applying absolute positioning.
906
+ cachedFooterEl.style.setProperty('--pinned-width', `${cachedFooterEl.clientWidth}px`);
907
+ cachedFooterEl.style.setProperty('--pinned-height', `${cachedFooterEl.clientHeight}px`);
908
+ cachedFooterEl.style.setProperty('--pinned-top', `${absoluteTop}px`);
909
+ cachedFooterEl.style.setProperty('--pinned-left', `${absoluteLeft}px`);
910
+ // Only cache the first footer's Y position
911
+ // This is used to determine if the sheet has been moved below the footer
912
+ // and needs to be swapped back to stationary so it collapses correctly.
913
+ if (index === 0) {
914
+ cachedFooterYPosition = absoluteTop;
915
+ // If there's a toolbar, we need to combine the toolbar height with the footer position
916
+ // because the toolbar moves with the drag handle, so when it starts overlapping the footer,
917
+ // we need to account for that.
918
+ const toolbar = baseEl.querySelector('ion-toolbar');
919
+ if (toolbar) {
920
+ cachedFooterYPosition -= toolbar.clientHeight;
921
+ }
922
+ }
923
+ });
924
+ // Apply the pinning of styles after we've calculated everything
925
+ // so that we don't cause layouts to shift while calculating the footer positions.
926
+ // Otherwise, with multiple footers we'll end up capturing the wrong positions.
927
+ cachedFooterEls.forEach((cachedFooterEl) => {
928
+ // Add padding to the parent element to prevent content from being hidden
929
+ // when the footer is positioned absolutely. This has to be done before we
930
+ // make the footer absolutely positioned or we may accidentally cause the
931
+ // sheet to scroll.
932
+ page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeights}px`);
933
+ // Apply positioning styles to keep footer at bottom
934
+ cachedFooterEl.classList.add('modal-footer-moving');
935
+ // Apply our preserved styles to pin the footer
936
+ cachedFooterEl.style.setProperty('position', 'absolute');
937
+ cachedFooterEl.style.setProperty('width', 'var(--pinned-width)');
938
+ cachedFooterEl.style.setProperty('height', 'var(--pinned-height)');
939
+ cachedFooterEl.style.setProperty('top', 'var(--pinned-top)');
940
+ cachedFooterEl.style.setProperty('left', 'var(--pinned-left)');
941
+ // Move the element to the body when everything else is done
942
+ document.body.appendChild(cachedFooterEl);
943
+ });
923
944
  }
924
945
  };
925
946
  /**
@@ -1111,6 +1132,16 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
1111
1132
  * is not scrolled to the top.
1112
1133
  */
1113
1134
  if (!expandToScroll && detail.deltaY <= 0 && cachedScrollEl && cachedScrollEl.scrollTop > 0) {
1135
+ /**
1136
+ * If expand to scroll is disabled, we need to make sure we swap the footer position
1137
+ * back to stationary so that it will collapse correctly if the modal is dismissed without
1138
+ * dragging (e.g. through a dismiss button).
1139
+ * This can cause issues if the user has a modal with content that can be dragged, as we'll
1140
+ * swap to moving on drag and if we don't swap back here then the footer will get stuck.
1141
+ */
1142
+ if (!expandToScroll) {
1143
+ swapFooterPosition('stationary');
1144
+ }
1114
1145
  return;
1115
1146
  }
1116
1147
  /**
@@ -179,7 +179,6 @@ export class ItemSliding {
179
179
  }
180
180
  }
181
181
  async updateOptions() {
182
- var _a;
183
182
  const options = this.el.querySelectorAll('ion-item-options');
184
183
  let sides = 0;
185
184
  // Reset left and right options in case they were removed
@@ -193,7 +192,7 @@ export class ItemSliding {
193
192
  */
194
193
  // eslint-disable-next-line custom-rules/no-component-on-ready-method
195
194
  const option = item.componentOnReady !== undefined ? await item.componentOnReady() : item;
196
- const side = isEndSide((_a = option.side) !== null && _a !== void 0 ? _a : option.getAttribute('side')) ? 'end' : 'start';
195
+ const side = isEndSide(option.side) ? 'end' : 'start';
197
196
  if (side === 'start') {
198
197
  this.leftOptions = option;
199
198
  sides |= 1 /* ItemSide.Start */;
@@ -391,7 +390,7 @@ export class ItemSliding {
391
390
  }
392
391
  render() {
393
392
  const mode = getIonMode(this);
394
- return (h(Host, { key: 'd812322c9fb5da4ee16e99dc38bfb24cb4590d03', class: {
393
+ return (h(Host, { key: '9880396ad79e06117d572a27f92c4b753d1e26db', class: {
395
394
  [mode]: true,
396
395
  'item-sliding-active-slide': this.state !== 2 /* SlidingState.Disabled */,
397
396
  'item-sliding-active-options-end': (this.state & 8 /* SlidingState.End */) !== 0,
@@ -35,7 +35,7 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
35
35
  let offset = 0;
36
36
  let canDismissBlocksGesture = false;
37
37
  let cachedScrollEl = null;
38
- let cachedFooterEl = null;
38
+ let cachedFooterEls = null;
39
39
  let cachedFooterYPosition = null;
40
40
  let currentFooterState = null;
41
41
  const canDismissMaxStep = 0.95;
@@ -72,60 +72,81 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
72
72
  * @param newPosition Whether the footer is in a moving or stationary position.
73
73
  */
74
74
  const swapFooterPosition = (newPosition) => {
75
- if (!cachedFooterEl) {
76
- cachedFooterEl = baseEl.querySelector('ion-footer');
77
- if (!cachedFooterEl) {
75
+ if (!cachedFooterEls) {
76
+ cachedFooterEls = Array.from(baseEl.querySelectorAll('ion-footer'));
77
+ if (!cachedFooterEls.length) {
78
78
  return;
79
79
  }
80
80
  }
81
81
  const page = baseEl.querySelector('.ion-page');
82
82
  currentFooterState = newPosition;
83
83
  if (newPosition === 'stationary') {
84
- // Reset positioning styles to allow normal document flow
85
- cachedFooterEl.classList.remove('modal-footer-moving');
86
- cachedFooterEl.style.removeProperty('position');
87
- cachedFooterEl.style.removeProperty('width');
88
- cachedFooterEl.style.removeProperty('height');
89
- cachedFooterEl.style.removeProperty('top');
90
- cachedFooterEl.style.removeProperty('left');
91
- page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
92
- // Move to page
93
- page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
84
+ cachedFooterEls.forEach((cachedFooterEl) => {
85
+ // Reset positioning styles to allow normal document flow
86
+ cachedFooterEl.classList.remove('modal-footer-moving');
87
+ cachedFooterEl.style.removeProperty('position');
88
+ cachedFooterEl.style.removeProperty('width');
89
+ cachedFooterEl.style.removeProperty('height');
90
+ cachedFooterEl.style.removeProperty('top');
91
+ cachedFooterEl.style.removeProperty('left');
92
+ page === null || page === void 0 ? void 0 : page.style.removeProperty('padding-bottom');
93
+ // Move to page
94
+ page === null || page === void 0 ? void 0 : page.appendChild(cachedFooterEl);
95
+ });
94
96
  }
95
97
  else {
96
- // Get both the footer and document body positions
97
- const cachedFooterElRect = cachedFooterEl.getBoundingClientRect();
98
- const bodyRect = document.body.getBoundingClientRect();
99
- // Add padding to the parent element to prevent content from being hidden
100
- // when the footer is positioned absolutely. This has to be done before we
101
- // make the footer absolutely positioned or we may accidentally cause the
102
- // sheet to scroll.
103
- const footerHeight = cachedFooterEl.clientHeight;
104
- page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeight}px`);
105
- // Apply positioning styles to keep footer at bottom
106
- cachedFooterEl.classList.add('modal-footer-moving');
107
- // Calculate absolute position relative to body
108
- // We need to subtract the body's offsetTop to get true position within document.body
109
- const absoluteTop = cachedFooterElRect.top - bodyRect.top;
110
- const absoluteLeft = cachedFooterElRect.left - bodyRect.left;
111
- // Capture the footer's current dimensions and hard code them during the drag
112
- cachedFooterEl.style.setProperty('position', 'absolute');
113
- cachedFooterEl.style.setProperty('width', `${cachedFooterEl.clientWidth}px`);
114
- cachedFooterEl.style.setProperty('height', `${cachedFooterEl.clientHeight}px`);
115
- cachedFooterEl.style.setProperty('top', `${absoluteTop}px`);
116
- cachedFooterEl.style.setProperty('left', `${absoluteLeft}px`);
117
- // Also cache the footer Y position, which we use to determine if the
118
- // sheet has been moved below the footer. When that happens, we need to swap
119
- // the position back so it will collapse correctly.
120
- cachedFooterYPosition = absoluteTop;
121
- // If there's a toolbar, we need to combine the toolbar height with the footer position
122
- // because the toolbar moves with the drag handle, so when it starts overlapping the footer,
123
- // we need to account for that.
124
- const toolbar = baseEl.querySelector('ion-toolbar');
125
- if (toolbar) {
126
- cachedFooterYPosition -= toolbar.clientHeight;
127
- }
128
- document.body.appendChild(cachedFooterEl);
98
+ let footerHeights = 0;
99
+ cachedFooterEls.forEach((cachedFooterEl, index) => {
100
+ // Get both the footer and document body positions
101
+ const cachedFooterElRect = cachedFooterEl.getBoundingClientRect();
102
+ const bodyRect = document.body.getBoundingClientRect();
103
+ // Calculate the total height of all footers
104
+ // so we can add padding to the page element
105
+ footerHeights += cachedFooterEl.clientHeight;
106
+ // Calculate absolute position relative to body
107
+ // We need to subtract the body's offsetTop to get true position within document.body
108
+ const absoluteTop = cachedFooterElRect.top - bodyRect.top;
109
+ const absoluteLeft = cachedFooterElRect.left - bodyRect.left;
110
+ // Capture the footer's current dimensions and store them in CSS variables for
111
+ // later use when applying absolute positioning.
112
+ cachedFooterEl.style.setProperty('--pinned-width', `${cachedFooterEl.clientWidth}px`);
113
+ cachedFooterEl.style.setProperty('--pinned-height', `${cachedFooterEl.clientHeight}px`);
114
+ cachedFooterEl.style.setProperty('--pinned-top', `${absoluteTop}px`);
115
+ cachedFooterEl.style.setProperty('--pinned-left', `${absoluteLeft}px`);
116
+ // Only cache the first footer's Y position
117
+ // This is used to determine if the sheet has been moved below the footer
118
+ // and needs to be swapped back to stationary so it collapses correctly.
119
+ if (index === 0) {
120
+ cachedFooterYPosition = absoluteTop;
121
+ // If there's a toolbar, we need to combine the toolbar height with the footer position
122
+ // because the toolbar moves with the drag handle, so when it starts overlapping the footer,
123
+ // we need to account for that.
124
+ const toolbar = baseEl.querySelector('ion-toolbar');
125
+ if (toolbar) {
126
+ cachedFooterYPosition -= toolbar.clientHeight;
127
+ }
128
+ }
129
+ });
130
+ // Apply the pinning of styles after we've calculated everything
131
+ // so that we don't cause layouts to shift while calculating the footer positions.
132
+ // Otherwise, with multiple footers we'll end up capturing the wrong positions.
133
+ cachedFooterEls.forEach((cachedFooterEl) => {
134
+ // Add padding to the parent element to prevent content from being hidden
135
+ // when the footer is positioned absolutely. This has to be done before we
136
+ // make the footer absolutely positioned or we may accidentally cause the
137
+ // sheet to scroll.
138
+ page === null || page === void 0 ? void 0 : page.style.setProperty('padding-bottom', `${footerHeights}px`);
139
+ // Apply positioning styles to keep footer at bottom
140
+ cachedFooterEl.classList.add('modal-footer-moving');
141
+ // Apply our preserved styles to pin the footer
142
+ cachedFooterEl.style.setProperty('position', 'absolute');
143
+ cachedFooterEl.style.setProperty('width', 'var(--pinned-width)');
144
+ cachedFooterEl.style.setProperty('height', 'var(--pinned-height)');
145
+ cachedFooterEl.style.setProperty('top', 'var(--pinned-top)');
146
+ cachedFooterEl.style.setProperty('left', 'var(--pinned-left)');
147
+ // Move the element to the body when everything else is done
148
+ document.body.appendChild(cachedFooterEl);
149
+ });
129
150
  }
130
151
  };
131
152
  /**
@@ -317,6 +338,16 @@ export const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpo
317
338
  * is not scrolled to the top.
318
339
  */
319
340
  if (!expandToScroll && detail.deltaY <= 0 && cachedScrollEl && cachedScrollEl.scrollTop > 0) {
341
+ /**
342
+ * If expand to scroll is disabled, we need to make sure we swap the footer position
343
+ * back to stationary so that it will collapse correctly if the modal is dismissed without
344
+ * dragging (e.g. through a dismiss button).
345
+ * This can cause issues if the user has a modal with content that can be dragged, as we'll
346
+ * swap to moving on drag and if we don't swap back here then the footer will get stuck.
347
+ */
348
+ if (!expandToScroll) {
349
+ swapFooterPosition('stationary');
350
+ }
320
351
  return;
321
352
  }
322
353
  /**
@@ -30,20 +30,6 @@ export const setContent = async (page, html, testInfo, options) => {
30
30
  palette = options.palette;
31
31
  }
32
32
  const baseUrl = process.env.PLAYWRIGHT_TEST_BASE_URL;
33
- // The Ionic bundle is included locally by default unless the test
34
- // config passes in the importIonicFromCDN option. This is useful
35
- // when testing with the CDN version of Ionic.
36
- let ionicImports = `
37
- <link href="${baseUrl}/css/ionic.bundle.css" rel="stylesheet" />
38
- <script type="module" src="${baseUrl}/dist/ionic/ionic.esm.js"></script>
39
- `;
40
- if (options === null || options === void 0 ? void 0 : options.importIonicFromCDN) {
41
- ionicImports = `
42
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css" />
43
- <script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
44
- <script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
45
- `;
46
- }
47
33
  const output = `
48
34
  <!DOCTYPE html>
49
35
  <html dir="${direction}" lang="en">
@@ -51,10 +37,11 @@ export const setContent = async (page, html, testInfo, options) => {
51
37
  <title>Ionic Playwright Test</title>
52
38
  <meta charset="UTF-8" />
53
39
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
40
+ <link href="${baseUrl}/css/ionic.bundle.css" rel="stylesheet" />
54
41
  <link href="${baseUrl}/scripts/testing/styles.css" rel="stylesheet" />
55
42
  ${palette !== 'light' ? `<link href="${baseUrl}/css/palettes/${palette}.always.css" rel="stylesheet" />` : ''}
56
43
  <script src="${baseUrl}/scripts/testing/scripts.js"></script>
57
- ${ionicImports}
44
+ <script type="module" src="${baseUrl}/dist/ionic/ionic.esm.js"></script>
58
45
  <script>
59
46
  window.Ionic = {
60
47
  config: {
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2025-06-10T13:30:20",
2
+ "timestamp": "2025-06-10T16:30:13",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.33.1",