Skip to content

Commit 7db572c

Browse files
Merge branch 'feature-8.7' into FW-6539
2 parents 7f7675e + d52b253 commit 7db572c

28 files changed

+255
-144
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
All notable changes to this project will be documented in this file.
44
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
55

6+
## [8.6.1](https://github.com/ionic-team/ionic-framework/compare/v8.6.0...v8.6.1) (2025-06-11)
7+
8+
9+
### Bug Fixes
10+
11+
* **item-sliding:** check for side attribute to avoid an `undefined` value ([#29845](https://github.com/ionic-team/ionic-framework/issues/29845)) ([c38aa07](https://github.com/ionic-team/ionic-framework/commit/c38aa07cf8bfab200b3c071328d893bd1627cde7)), closes [#29499](https://github.com/ionic-team/ionic-framework/issues/29499)
12+
* **modal:** reset footer positioning after content drag and multi-footer support ([#30470](https://github.com/ionic-team/ionic-framework/issues/30470)) ([071b414](https://github.com/ionic-team/ionic-framework/commit/071b414a00f4497ed0baa1431f0bee4b3c7c13fb)), closes [#30468](https://github.com/ionic-team/ionic-framework/issues/30468)
13+
14+
15+
16+
17+
618
# [8.6.0](https://github.com/ionic-team/ionic-framework/compare/v8.5.9...v8.6.0) (2025-06-04)
719

820

core/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@
33
All notable changes to this project will be documented in this file.
44
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
55

6+
## [8.6.1](https://github.com/ionic-team/ionic-framework/compare/v8.6.0...v8.6.1) (2025-06-11)
7+
8+
9+
### Bug Fixes
10+
11+
* **item-sliding:** check for side attribute to avoid an `undefined` value ([#29845](https://github.com/ionic-team/ionic-framework/issues/29845)) ([c38aa07](https://github.com/ionic-team/ionic-framework/commit/c38aa07cf8bfab200b3c071328d893bd1627cde7)), closes [#29499](https://github.com/ionic-team/ionic-framework/issues/29499)
12+
* **modal:** reset footer positioning after content drag and multi-footer support ([#30470](https://github.com/ionic-team/ionic-framework/issues/30470)) ([071b414](https://github.com/ionic-team/ionic-framework/commit/071b414a00f4497ed0baa1431f0bee4b3c7c13fb)), closes [#30468](https://github.com/ionic-team/ionic-framework/issues/30468)
13+
14+
15+
16+
17+
618
# [8.6.0](https://github.com/ionic-team/ionic-framework/compare/v8.5.9...v8.6.0) (2025-06-04)
719

820

core/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ionic/core",
3-
"version": "8.6.0",
3+
"version": "8.6.1",
44
"description": "Base components for Ionic",
55
"keywords": [
66
"ionic",

core/src/components/item-sliding/item-sliding.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ export class ItemSliding implements ComponentInterface {
263263
// eslint-disable-next-line custom-rules/no-component-on-ready-method
264264
const option = (item as any).componentOnReady !== undefined ? await item.componentOnReady() : item;
265265

266-
const side = isEndSide(option.side) ? 'end' : 'start';
266+
const side = isEndSide(option.side ?? option.getAttribute('side')) ? 'end' : 'start';
267267

268268
if (side === 'start') {
269269
this.leftOptions = option;

core/src/components/modal/gestures/sheet.ts

Lines changed: 85 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const createSheetGesture = (
8484
let offset = 0;
8585
let canDismissBlocksGesture = false;
8686
let cachedScrollEl: HTMLElement | null = null;
87-
let cachedFooterEl: HTMLIonFooterElement | null = null;
87+
let cachedFooterEls: HTMLIonFooterElement[] | null = null;
8888
let cachedFooterYPosition: number | null = null;
8989
let currentFooterState: 'moving' | 'stationary' | null = null;
9090
const canDismissMaxStep = 0.95;
@@ -126,9 +126,9 @@ export const createSheetGesture = (
126126
* @param newPosition Whether the footer is in a moving or stationary position.
127127
*/
128128
const swapFooterPosition = (newPosition: 'moving' | 'stationary') => {
129-
if (!cachedFooterEl) {
130-
cachedFooterEl = baseEl.querySelector('ion-footer') as HTMLIonFooterElement | null;
131-
if (!cachedFooterEl) {
129+
if (!cachedFooterEls) {
130+
cachedFooterEls = Array.from(baseEl.querySelectorAll('ion-footer'));
131+
if (!cachedFooterEls.length) {
132132
return;
133133
}
134134
}
@@ -137,57 +137,80 @@ export const createSheetGesture = (
137137

138138
currentFooterState = newPosition;
139139
if (newPosition === 'stationary') {
140-
// Reset positioning styles to allow normal document flow
141-
cachedFooterEl.classList.remove('modal-footer-moving');
142-
cachedFooterEl.style.removeProperty('position');
143-
cachedFooterEl.style.removeProperty('width');
144-
cachedFooterEl.style.removeProperty('height');
145-
cachedFooterEl.style.removeProperty('top');
146-
cachedFooterEl.style.removeProperty('left');
147-
page?.style.removeProperty('padding-bottom');
148-
149-
// Move to page
150-
page?.appendChild(cachedFooterEl);
140+
cachedFooterEls.forEach((cachedFooterEl) => {
141+
// Reset positioning styles to allow normal document flow
142+
cachedFooterEl.classList.remove('modal-footer-moving');
143+
cachedFooterEl.style.removeProperty('position');
144+
cachedFooterEl.style.removeProperty('width');
145+
cachedFooterEl.style.removeProperty('height');
146+
cachedFooterEl.style.removeProperty('top');
147+
cachedFooterEl.style.removeProperty('left');
148+
page?.style.removeProperty('padding-bottom');
149+
150+
// Move to page
151+
page?.appendChild(cachedFooterEl);
152+
});
151153
} else {
152-
// Get both the footer and document body positions
153-
const cachedFooterElRect = cachedFooterEl.getBoundingClientRect();
154-
const bodyRect = document.body.getBoundingClientRect();
155-
156-
// Add padding to the parent element to prevent content from being hidden
157-
// when the footer is positioned absolutely. This has to be done before we
158-
// make the footer absolutely positioned or we may accidentally cause the
159-
// sheet to scroll.
160-
const footerHeight = cachedFooterEl.clientHeight;
161-
page?.style.setProperty('padding-bottom', `${footerHeight}px`);
162-
163-
// Apply positioning styles to keep footer at bottom
164-
cachedFooterEl.classList.add('modal-footer-moving');
165-
166-
// Calculate absolute position relative to body
167-
// We need to subtract the body's offsetTop to get true position within document.body
168-
const absoluteTop = cachedFooterElRect.top - bodyRect.top;
169-
const absoluteLeft = cachedFooterElRect.left - bodyRect.left;
170-
171-
// Capture the footer's current dimensions and hard code them during the drag
172-
cachedFooterEl.style.setProperty('position', 'absolute');
173-
cachedFooterEl.style.setProperty('width', `${cachedFooterEl.clientWidth}px`);
174-
cachedFooterEl.style.setProperty('height', `${cachedFooterEl.clientHeight}px`);
175-
cachedFooterEl.style.setProperty('top', `${absoluteTop}px`);
176-
cachedFooterEl.style.setProperty('left', `${absoluteLeft}px`);
177-
178-
// Also cache the footer Y position, which we use to determine if the
179-
// sheet has been moved below the footer. When that happens, we need to swap
180-
// the position back so it will collapse correctly.
181-
cachedFooterYPosition = absoluteTop;
182-
// If there's a toolbar, we need to combine the toolbar height with the footer position
183-
// because the toolbar moves with the drag handle, so when it starts overlapping the footer,
184-
// we need to account for that.
185-
const toolbar = baseEl.querySelector('ion-toolbar') as HTMLIonToolbarElement | null;
186-
if (toolbar) {
187-
cachedFooterYPosition -= toolbar.clientHeight;
188-
}
189-
190-
document.body.appendChild(cachedFooterEl);
154+
let footerHeights = 0;
155+
cachedFooterEls.forEach((cachedFooterEl, index) => {
156+
// Get both the footer and document body positions
157+
const cachedFooterElRect = cachedFooterEl.getBoundingClientRect();
158+
const bodyRect = document.body.getBoundingClientRect();
159+
160+
// Calculate the total height of all footers
161+
// so we can add padding to the page element
162+
footerHeights += cachedFooterEl.clientHeight;
163+
164+
// Calculate absolute position relative to body
165+
// We need to subtract the body's offsetTop to get true position within document.body
166+
const absoluteTop = cachedFooterElRect.top - bodyRect.top;
167+
const absoluteLeft = cachedFooterElRect.left - bodyRect.left;
168+
169+
// Capture the footer's current dimensions and store them in CSS variables for
170+
// later use when applying absolute positioning.
171+
cachedFooterEl.style.setProperty('--pinned-width', `${cachedFooterEl.clientWidth}px`);
172+
cachedFooterEl.style.setProperty('--pinned-height', `${cachedFooterEl.clientHeight}px`);
173+
cachedFooterEl.style.setProperty('--pinned-top', `${absoluteTop}px`);
174+
cachedFooterEl.style.setProperty('--pinned-left', `${absoluteLeft}px`);
175+
176+
// Only cache the first footer's Y position
177+
// This is used to determine if the sheet has been moved below the footer
178+
// and needs to be swapped back to stationary so it collapses correctly.
179+
if (index === 0) {
180+
cachedFooterYPosition = absoluteTop;
181+
// If there's a header, we need to combine the header height with the footer position
182+
// because the header moves with the drag handle, so when it starts overlapping the footer,
183+
// we need to account for that.
184+
const header = baseEl.querySelector('ion-header') as HTMLIonHeaderElement | null;
185+
if (header) {
186+
cachedFooterYPosition -= header.clientHeight;
187+
}
188+
}
189+
});
190+
191+
// Apply the pinning of styles after we've calculated everything
192+
// so that we don't cause layouts to shift while calculating the footer positions.
193+
// Otherwise, with multiple footers we'll end up capturing the wrong positions.
194+
cachedFooterEls.forEach((cachedFooterEl) => {
195+
// Add padding to the parent element to prevent content from being hidden
196+
// when the footer is positioned absolutely. This has to be done before we
197+
// make the footer absolutely positioned or we may accidentally cause the
198+
// sheet to scroll.
199+
page?.style.setProperty('padding-bottom', `${footerHeights}px`);
200+
201+
// Apply positioning styles to keep footer at bottom
202+
cachedFooterEl.classList.add('modal-footer-moving');
203+
204+
// Apply our preserved styles to pin the footer
205+
cachedFooterEl.style.setProperty('position', 'absolute');
206+
cachedFooterEl.style.setProperty('width', 'var(--pinned-width)');
207+
cachedFooterEl.style.setProperty('height', 'var(--pinned-height)');
208+
cachedFooterEl.style.setProperty('top', 'var(--pinned-top)');
209+
cachedFooterEl.style.setProperty('left', 'var(--pinned-left)');
210+
211+
// Move the element to the body when everything else is done
212+
document.body.appendChild(cachedFooterEl);
213+
});
191214
}
192215
};
193216

@@ -400,6 +423,14 @@ export const createSheetGesture = (
400423
* is not scrolled to the top.
401424
*/
402425
if (!expandToScroll && detail.deltaY <= 0 && cachedScrollEl && cachedScrollEl.scrollTop > 0) {
426+
/**
427+
* If expand to scroll is disabled, we need to make sure we swap the footer position
428+
* back to stationary so that it will collapse correctly if the modal is dismissed without
429+
* dragging (e.g. through a dismiss button).
430+
* This can cause issues if the user has a modal with content that can be dragged, as we'll
431+
* swap to moving on drag and if we don't swap back here then the footer will get stuck.
432+
*/
433+
swapFooterPosition('stationary');
403434
return;
404435
}
405436

lerna.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
"core",
44
"packages/*"
55
],
6-
"version": "8.6.0"
6+
"version": "8.6.1"
77
}

packages/angular-server/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
All notable changes to this project will be documented in this file.
44
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
55

6+
## [8.6.1](https://github.com/ionic-team/ionic-framework/compare/v8.6.0...v8.6.1) (2025-06-11)
7+
8+
**Note:** Version bump only for package @ionic/angular-server
9+
10+
11+
12+
13+
614
# [8.6.0](https://github.com/ionic-team/ionic-framework/compare/v8.5.9...v8.6.0) (2025-06-04)
715

816
**Note:** Version bump only for package @ionic/angular-server

packages/angular-server/package-lock.json

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/angular-server/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ionic/angular-server",
3-
"version": "8.6.0",
3+
"version": "8.6.1",
44
"description": "Angular SSR Module for Ionic",
55
"keywords": [
66
"ionic",
@@ -62,6 +62,6 @@
6262
},
6363
"prettier": "@ionic/prettier-config",
6464
"dependencies": {
65-
"@ionic/core": "^8.6.0"
65+
"@ionic/core": "^8.6.1"
6666
}
6767
}

0 commit comments

Comments
 (0)