@ionic/core 8.6.1-dev.11749648223.169ce853 → 8.6.1-dev.11749649767.15f41148

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.
@@ -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 header, we need to combine the header height with the footer position
122
+ // because the header moves with the drag handle, so when it starts overlapping the footer,
123
+ // we need to account for that.
124
+ const header = baseEl.querySelector('ion-header');
125
+ if (header) {
126
+ cachedFooterYPosition -= header.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,14 @@ 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
+ swapFooterPosition('stationary');
320
349
  return;
321
350
  }
322
351
  /**
@@ -53,7 +53,7 @@ export class ReorderGroup {
53
53
  }
54
54
  }
55
55
  /**
56
- * Completes the reorder operation. Must be called by the `ionReorderEnd` event.
56
+ * Completes the reorder operation. Must be called by the `ionItemReorder` event.
57
57
  *
58
58
  * If a list of items is passed, the list will be reordered and returned in the
59
59
  * proper order.
@@ -120,7 +120,6 @@ export class ReorderGroup {
120
120
  this.state = 1 /* ReorderGroupState.Active */;
121
121
  item.classList.add(ITEM_REORDER_SELECTED);
122
122
  hapticSelectionStart();
123
- this.ionReorderStart.emit();
124
123
  }
125
124
  onMove(ev) {
126
125
  const selectedItem = this.selectedItemEl;
@@ -135,7 +134,6 @@ export class ReorderGroup {
135
134
  const currentY = Math.max(top, Math.min(ev.currentY, bottom));
136
135
  const deltaY = scroll + currentY - ev.startY;
137
136
  const normalizedY = currentY - top;
138
- const fromIndex = this.lastToIndex;
139
137
  const toIndex = this.itemIndexForTop(normalizedY);
140
138
  if (toIndex !== this.lastToIndex) {
141
139
  const fromIndex = indexForItem(selectedItem);
@@ -145,10 +143,6 @@ export class ReorderGroup {
145
143
  }
146
144
  // Update selected item position
147
145
  selectedItem.style.transform = `translateY(${deltaY}px)`;
148
- this.ionReorderMove.emit({
149
- from: fromIndex,
150
- to: toIndex,
151
- });
152
146
  }
153
147
  onEnd() {
154
148
  const selectedItemEl = this.selectedItemEl;
@@ -160,23 +154,14 @@ export class ReorderGroup {
160
154
  const toIndex = this.lastToIndex;
161
155
  const fromIndex = indexForItem(selectedItemEl);
162
156
  if (toIndex === fromIndex) {
163
- this.ionReorderEnd.emit({
164
- complete: this.completeReorder.bind(this),
165
- });
166
157
  this.completeReorder();
167
158
  }
168
159
  else {
169
- // TODO(FW-6590): Remove this once the deprecated event is removed
170
160
  this.ionItemReorder.emit({
171
161
  from: fromIndex,
172
162
  to: toIndex,
173
163
  complete: this.completeReorder.bind(this),
174
164
  });
175
- this.ionReorderEnd.emit({
176
- from: fromIndex,
177
- to: toIndex,
178
- complete: this.completeReorder.bind(this),
179
- });
180
165
  }
181
166
  hapticSelectionEnd();
182
167
  }
@@ -256,7 +241,7 @@ export class ReorderGroup {
256
241
  }
257
242
  render() {
258
243
  const mode = getIonMode(this);
259
- return (h(Host, { key: '8f47e24dfc7b8ef53ad3f5816fa13a8fa07de2a6', class: {
244
+ return (h(Host, { key: 'f30613b361c5c3095b7928a92fb4b1e8d6eff600', class: {
260
245
  [mode]: true,
261
246
  'reorder-enabled': !this.disabled,
262
247
  'reorder-list-active': this.state !== 0 /* ReorderGroupState.Idle */,
@@ -310,11 +295,8 @@ export class ReorderGroup {
310
295
  "cancelable": true,
311
296
  "composed": true,
312
297
  "docs": {
313
- "tags": [{
314
- "name": "deprecated",
315
- "text": "Use `ionReorderEnd` instead. The new event is emitted\nat the end of every reorder gesture, even if the positions do not\nchange. If you were accessing `event.detail.from` or `event.detail.to`,\nyou should now add `undefined` checks as they can be `undefined` in\n`ionReorderEnd`."
316
- }],
317
- "text": "TODO(FW-6590): Remove this in a major release."
298
+ "tags": [],
299
+ "text": "Event that needs to be listened to in order to complete the reorder action.\nOnce the event has been emitted, the `complete()` method then needs\nto be called in order to finalize the reorder action."
318
300
  },
319
301
  "complexType": {
320
302
  "original": "ItemReorderEventDetail",
@@ -327,63 +309,6 @@ export class ReorderGroup {
327
309
  }
328
310
  }
329
311
  }
330
- }, {
331
- "method": "ionReorderStart",
332
- "name": "ionReorderStart",
333
- "bubbles": true,
334
- "cancelable": true,
335
- "composed": true,
336
- "docs": {
337
- "tags": [],
338
- "text": "Event that is emitted when the reorder gesture starts."
339
- },
340
- "complexType": {
341
- "original": "void",
342
- "resolved": "void",
343
- "references": {}
344
- }
345
- }, {
346
- "method": "ionReorderMove",
347
- "name": "ionReorderMove",
348
- "bubbles": true,
349
- "cancelable": true,
350
- "composed": true,
351
- "docs": {
352
- "tags": [],
353
- "text": "Event that is emitted as the reorder gesture moves."
354
- },
355
- "complexType": {
356
- "original": "ReorderMoveEventDetail",
357
- "resolved": "ReorderMoveEventDetail",
358
- "references": {
359
- "ReorderMoveEventDetail": {
360
- "location": "import",
361
- "path": "./reorder-group-interface",
362
- "id": "src/components/reorder-group/reorder-group-interface.ts::ReorderMoveEventDetail"
363
- }
364
- }
365
- }
366
- }, {
367
- "method": "ionReorderEnd",
368
- "name": "ionReorderEnd",
369
- "bubbles": true,
370
- "cancelable": true,
371
- "composed": true,
372
- "docs": {
373
- "tags": [],
374
- "text": "Event that is emitted when the reorder gesture ends.\nThe from and to properties are only available if the reorder gesture\nmoved the item. If the item did not move, the from and to properties\nwill be undefined.\nOnce the event has been emitted, the `complete()` method then needs\nto be called in order to finalize the reorder action."
375
- },
376
- "complexType": {
377
- "original": "ReorderEndEventDetail",
378
- "resolved": "ReorderEndEventDetail",
379
- "references": {
380
- "ReorderEndEventDetail": {
381
- "location": "import",
382
- "path": "./reorder-group-interface",
383
- "id": "src/components/reorder-group/reorder-group-interface.ts::ReorderEndEventDetail"
384
- }
385
- }
386
- }
387
312
  }];
388
313
  }
389
314
  static get methods() {
@@ -405,7 +330,7 @@ export class ReorderGroup {
405
330
  "return": "Promise<any>"
406
331
  },
407
332
  "docs": {
408
- "text": "Completes the reorder operation. Must be called by the `ionReorderEnd` event.\n\nIf a list of items is passed, the list will be reordered and returned in the\nproper order.\n\nIf no parameters are passed or if `true` is passed in, the reorder will complete\nand the item will remain in the position it was dragged to. If `false` is passed,\nthe reorder will complete and the item will bounce back to its original position.",
333
+ "text": "Completes the reorder operation. Must be called by the `ionItemReorder` event.\n\nIf a list of items is passed, the list will be reordered and returned in the\nproper order.\n\nIf no parameters are passed or if `true` is passed in, the reorder will complete\nand the item will remain in the position it was dragged to. If `false` is passed,\nthe reorder will complete and the item will bounce back to its original position.",
409
334
  "tags": [{
410
335
  "name": "param",
411
336
  "text": "listOrReorder A list of items to be sorted and returned in the new order or a\nboolean of whether or not the reorder should reposition the item."
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2025-06-11T13:25:15",
2
+ "timestamp": "2025-06-11T13:51:12",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.33.1",
@@ -27019,7 +27019,7 @@
27019
27019
  "docs": "A list of items to be sorted and returned in the new order or a\nboolean of whether or not the reorder should reposition the item."
27020
27020
  }
27021
27021
  ],
27022
- "docs": "Completes the reorder operation. Must be called by the `ionReorderEnd` event.\n\nIf a list of items is passed, the list will be reordered and returned in the\nproper order.\n\nIf no parameters are passed or if `true` is passed in, the reorder will complete\nand the item will remain in the position it was dragged to. If `false` is passed,\nthe reorder will complete and the item will bounce back to its original position.",
27022
+ "docs": "Completes the reorder operation. Must be called by the `ionItemReorder` event.\n\nIf a list of items is passed, the list will be reordered and returned in the\nproper order.\n\nIf no parameters are passed or if `true` is passed in, the reorder will complete\nand the item will remain in the position it was dragged to. If `false` is passed,\nthe reorder will complete and the item will bounce back to its original position.",
27023
27023
  "docsTags": [
27024
27024
  {
27025
27025
  "name": "param",
@@ -27046,67 +27046,7 @@
27046
27046
  },
27047
27047
  "cancelable": true,
27048
27048
  "composed": true,
27049
- "docs": "TODO(FW-6590): Remove this in a major release.",
27050
- "docsTags": [
27051
- {
27052
- "name": "deprecated",
27053
- "text": "Use `ionReorderEnd` instead. The new event is emitted\nat the end of every reorder gesture, even if the positions do not\nchange. If you were accessing `event.detail.from` or `event.detail.to`,\nyou should now add `undefined` checks as they can be `undefined` in\n`ionReorderEnd`."
27054
- }
27055
- ],
27056
- "deprecation": "Use `ionReorderEnd` instead. The new event is emitted\nat the end of every reorder gesture, even if the positions do not\nchange. If you were accessing `event.detail.from` or `event.detail.to`,\nyou should now add `undefined` checks as they can be `undefined` in\n`ionReorderEnd`."
27057
- },
27058
- {
27059
- "event": "ionReorderEnd",
27060
- "detail": "ReorderEndEventDetail",
27061
- "bubbles": true,
27062
- "complexType": {
27063
- "original": "ReorderEndEventDetail",
27064
- "resolved": "ReorderEndEventDetail",
27065
- "references": {
27066
- "ReorderEndEventDetail": {
27067
- "location": "import",
27068
- "path": "./reorder-group-interface",
27069
- "id": "src/components/reorder-group/reorder-group-interface.ts::ReorderEndEventDetail"
27070
- }
27071
- }
27072
- },
27073
- "cancelable": true,
27074
- "composed": true,
27075
- "docs": "Event that is emitted when the reorder gesture ends.\nThe from and to properties are only available if the reorder gesture\nmoved the item. If the item did not move, the from and to properties\nwill be undefined.\nOnce the event has been emitted, the `complete()` method then needs\nto be called in order to finalize the reorder action.",
27076
- "docsTags": []
27077
- },
27078
- {
27079
- "event": "ionReorderMove",
27080
- "detail": "ReorderMoveEventDetail",
27081
- "bubbles": true,
27082
- "complexType": {
27083
- "original": "ReorderMoveEventDetail",
27084
- "resolved": "ReorderMoveEventDetail",
27085
- "references": {
27086
- "ReorderMoveEventDetail": {
27087
- "location": "import",
27088
- "path": "./reorder-group-interface",
27089
- "id": "src/components/reorder-group/reorder-group-interface.ts::ReorderMoveEventDetail"
27090
- }
27091
- }
27092
- },
27093
- "cancelable": true,
27094
- "composed": true,
27095
- "docs": "Event that is emitted as the reorder gesture moves.",
27096
- "docsTags": []
27097
- },
27098
- {
27099
- "event": "ionReorderStart",
27100
- "detail": "void",
27101
- "bubbles": true,
27102
- "complexType": {
27103
- "original": "void",
27104
- "resolved": "void",
27105
- "references": {}
27106
- },
27107
- "cancelable": true,
27108
- "composed": true,
27109
- "docs": "Event that is emitted when the reorder gesture starts.",
27049
+ "docs": "Event that needs to be listened to in order to complete the reorder action.\nOnce the event has been emitted, the `complete()` method then needs\nto be called in order to finalize the reorder action.",
27110
27050
  "docsTags": []
27111
27051
  }
27112
27052
  ],
@@ -37526,16 +37466,6 @@
37526
37466
  "docstring": "",
37527
37467
  "path": "src/components/reorder-group/reorder-group-interface.ts"
37528
37468
  },
37529
- "src/components/reorder-group/reorder-group-interface.ts::ReorderMoveEventDetail": {
37530
- "declaration": "export interface ReorderMoveEventDetail {\n from: number;\n to: number;\n}",
37531
- "docstring": "",
37532
- "path": "src/components/reorder-group/reorder-group-interface.ts"
37533
- },
37534
- "src/components/reorder-group/reorder-group-interface.ts::ReorderEndEventDetail": {
37535
- "declaration": "export interface ReorderEndEventDetail {\n from?: number;\n to?: number;\n complete: (data?: boolean | any[]) => any;\n}",
37536
- "docstring": "",
37537
- "path": "src/components/reorder-group/reorder-group-interface.ts"
37538
- },
37539
37469
  "src/components/route/route-interface.ts::NavigationHookCallback": {
37540
37470
  "declaration": "() => NavigationHookResult | Promise<NavigationHookResult>",
37541
37471
  "docstring": "",
@@ -280,6 +280,7 @@ const ItemSliding = class {
280
280
  }
281
281
  }
282
282
  async updateOptions() {
283
+ var _a;
283
284
  const options = this.el.querySelectorAll('ion-item-options');
284
285
  let sides = 0;
285
286
  // Reset left and right options in case they were removed
@@ -293,7 +294,7 @@ const ItemSliding = class {
293
294
  */
294
295
  // eslint-disable-next-line custom-rules/no-component-on-ready-method
295
296
  const option = item.componentOnReady !== undefined ? await item.componentOnReady() : item;
296
- const side = isEndSide(option.side) ? 'end' : 'start';
297
+ const side = isEndSide((_a = option.side) !== null && _a !== void 0 ? _a : option.getAttribute('side')) ? 'end' : 'start';
297
298
  if (side === 'start') {
298
299
  this.leftOptions = option;
299
300
  sides |= 1 /* ItemSide.Start */;
@@ -491,7 +492,7 @@ const ItemSliding = class {
491
492
  }
492
493
  render() {
493
494
  const mode = getIonMode(this);
494
- return (h(Host, { key: '9880396ad79e06117d572a27f92c4b753d1e26db', class: {
495
+ return (h(Host, { key: 'd812322c9fb5da4ee16e99dc38bfb24cb4590d03', class: {
495
496
  [mode]: true,
496
497
  'item-sliding-active-slide': this.state !== 2 /* SlidingState.Disabled */,
497
498
  '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 header, we need to combine the header height with the footer position
914
+ // because the header moves with the drag handle, so when it starts overlapping the footer,
915
+ // we need to account for that.
916
+ const header = baseEl.querySelector('ion-header');
917
+ if (header) {
918
+ cachedFooterYPosition -= header.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,14 @@ 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
+ swapFooterPosition('stationary');
1112
1141
  return;
1113
1142
  }
1114
1143
  /**