Skip to content

Commit b5e009b

Browse files
merge release-8.5.9 (#30453)
v8.5.9
2 parents 3c6555a + 2a22181 commit b5e009b

36 files changed

+347
-346
lines changed

.github/ionic-issue-bot.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ stale:
9393
- "triage"
9494
- "type: bug"
9595
- "type: feature request"
96+
- "needs: investigation"
9697
exemptAssigned: true
9798
exemptProjects: true
9899
exemptMilestones: true

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.5.9](https://github.com/ionic-team/ionic-framework/compare/v8.5.8...v8.5.9) (2025-06-04)
7+
8+
9+
### Bug Fixes
10+
11+
* **datetime:** display the correct month when multiple values are set ([#29610](https://github.com/ionic-team/ionic-framework/issues/29610)) ([14f32f8](https://github.com/ionic-team/ionic-framework/commit/14f32f8feea7b3880367868ff0a2134b0c28cc07)), closes [#29094](https://github.com/ionic-team/ionic-framework/issues/29094)
12+
* **modal:** move sheet footers instead of cloning while dragging ([#30433](https://github.com/ionic-team/ionic-framework/issues/30433)) ([4cbbbb0](https://github.com/ionic-team/ionic-framework/commit/4cbbbb053ad36d176f1d79ad09777f94ca8076d2)), closes [#30315](https://github.com/ionic-team/ionic-framework/issues/30315) [#30341](https://github.com/ionic-team/ionic-framework/issues/30341) [#30312](https://github.com/ionic-team/ionic-framework/issues/30312)
13+
14+
15+
16+
17+
618
## [8.5.8](https://github.com/ionic-team/ionic-framework/compare/v8.5.7...v8.5.8) (2025-05-28)
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.5.9](https://github.com/ionic-team/ionic-framework/compare/v8.5.8...v8.5.9) (2025-06-04)
7+
8+
9+
### Bug Fixes
10+
11+
* **datetime:** display the correct month when multiple values are set ([#29610](https://github.com/ionic-team/ionic-framework/issues/29610)) ([14f32f8](https://github.com/ionic-team/ionic-framework/commit/14f32f8feea7b3880367868ff0a2134b0c28cc07)), closes [#29094](https://github.com/ionic-team/ionic-framework/issues/29094)
12+
* **modal:** move sheet footers instead of cloning while dragging ([#30433](https://github.com/ionic-team/ionic-framework/issues/30433)) ([4cbbbb0](https://github.com/ionic-team/ionic-framework/commit/4cbbbb053ad36d176f1d79ad09777f94ca8076d2)), closes [#30315](https://github.com/ionic-team/ionic-framework/issues/30315) [#30341](https://github.com/ionic-team/ionic-framework/issues/30341) [#30312](https://github.com/ionic-team/ionic-framework/issues/30312)
13+
14+
15+
16+
17+
618
## [8.5.8](https://github.com/ionic-team/ionic-framework/compare/v8.5.7...v8.5.8) (2025-05-28)
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.5.8",
3+
"version": "8.5.9",
44
"description": "Base components for Ionic",
55
"keywords": [
66
"ionic",

core/src/components/datetime/datetime.tsx

Lines changed: 25 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,21 +1263,20 @@ export class Datetime implements ComponentInterface {
12631263
}
12641264

12651265
/**
1266-
* If there are multiple values, pick an arbitrary one to clamp to. This way,
1267-
* if the values are across months, we always show at least one of them. Note
1268-
* that the values don't necessarily have to be in order.
1266+
* If there are multiple values, clamp to the last one.
1267+
* This is because the last value is the one that the user
1268+
* has most recently interacted with.
12691269
*/
1270-
const singleValue = Array.isArray(valueToProcess) ? valueToProcess[0] : valueToProcess;
1270+
const singleValue = Array.isArray(valueToProcess) ? valueToProcess[valueToProcess.length - 1] : valueToProcess;
12711271
const targetValue = clampDate(singleValue, minParts, maxParts);
12721272

12731273
const { month, day, year, hour, minute } = targetValue;
12741274
const ampm = parseAmPm(hour!);
12751275

12761276
/**
1277-
* Since `activeParts` indicates a value that
1278-
* been explicitly selected either by the
1279-
* user or the app, only update `activeParts`
1280-
* if the `value` property is set.
1277+
* Since `activeParts` indicates a value that been explicitly selected
1278+
* either by the user or the app, only update `activeParts` if the
1279+
* `value` property is set.
12811280
*/
12821281
if (hasValue) {
12831282
if (Array.isArray(valueToProcess)) {
@@ -1301,53 +1300,29 @@ export class Datetime implements ComponentInterface {
13011300
this.activeParts = [];
13021301
}
13031302

1304-
/**
1305-
* Only animate if:
1306-
* 1. We're using grid style (wheel style pickers should just jump to new value)
1307-
* 2. The month and/or year actually changed, and both are defined (otherwise there's nothing to animate to)
1308-
* 3. The calendar body is visible (prevents animation when in collapsed datetime-button, for example)
1309-
* 4. The month/year picker is not open (since you wouldn't see the animation anyway)
1310-
*/
13111303
const didChangeMonth =
13121304
(month !== undefined && month !== workingParts.month) || (year !== undefined && year !== workingParts.year);
13131305
const bodyIsVisible = el.classList.contains('datetime-ready');
13141306
const { isGridStyle, showMonthAndYear } = this;
13151307

1316-
let areAllSelectedDatesInSameMonth = true;
1317-
if (Array.isArray(valueToProcess)) {
1318-
const firstMonth = valueToProcess[0].month;
1319-
for (const date of valueToProcess) {
1320-
if (date.month !== firstMonth) {
1321-
areAllSelectedDatesInSameMonth = false;
1322-
break;
1323-
}
1324-
}
1325-
}
1326-
1327-
/**
1328-
* If there is more than one date selected
1329-
* and the dates aren't all in the same month,
1330-
* then we should neither animate to the date
1331-
* nor update the working parts because we do
1332-
* not know which date the user wants to view.
1333-
*/
1334-
if (areAllSelectedDatesInSameMonth) {
1335-
if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
1336-
this.animateToDate(targetValue);
1337-
} else {
1338-
/**
1339-
* We only need to do this if we didn't just animate to a new month,
1340-
* since that calls prevMonth/nextMonth which calls setWorkingParts for us.
1341-
*/
1342-
this.setWorkingParts({
1343-
month,
1344-
day,
1345-
year,
1346-
hour,
1347-
minute,
1348-
ampm,
1349-
});
1350-
}
1308+
if (isGridStyle && didChangeMonth && bodyIsVisible && !showMonthAndYear) {
1309+
/**
1310+
* Only animate if:
1311+
* 1. We're using grid style (wheel style pickers should just jump to new value)
1312+
* 2. The month and/or year actually changed, and both are defined (otherwise there's nothing to animate to)
1313+
* 3. The calendar body is visible (prevents animation when in collapsed datetime-button, for example)
1314+
* 4. The month/year picker is not open (since you wouldn't see the animation anyway)
1315+
*/
1316+
this.animateToDate(targetValue);
1317+
} else {
1318+
this.setWorkingParts({
1319+
month,
1320+
day,
1321+
year,
1322+
hour,
1323+
minute,
1324+
ampm,
1325+
});
13511326
}
13521327
};
13531328

core/src/components/datetime/test/multiple/datetime.e2e.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -174,18 +174,6 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
174174
await expect(monthYear).toHaveText(/June 2022/);
175175
});
176176

177-
test('should not scroll to new month when value is updated with dates in different months', async ({ page }) => {
178-
const datetime = await datetimeFixture.goto(config, MULTIPLE_DATES);
179-
await datetime.evaluate((el: HTMLIonDatetimeElement, dates: string[]) => {
180-
el.value = dates;
181-
}, MULTIPLE_DATES_SEPARATE_MONTHS);
182-
183-
await page.waitForChanges();
184-
185-
const monthYear = datetime.locator('.calendar-month-year');
186-
await expect(monthYear).toHaveText(/June 2022/);
187-
});
188-
189177
test('with buttons, should only update value when confirm is called', async ({ page }) => {
190178
const datetime = await datetimeFixture.goto(config, SINGLE_DATE, { showDefaultButtons: true });
191179
const june2Button = datetime.locator('[data-month="6"][data-day="2"]');
@@ -311,4 +299,41 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
311299
await expect(header).toHaveText('Mon, Oct 10');
312300
});
313301
});
302+
303+
test.describe('with selected days in different months', () => {
304+
test(`set the active month view to the latest value's month`, async ({ page }, testInfo) => {
305+
testInfo.annotations.push({
306+
type: 'issue',
307+
description: 'https://github.com/ionic-team/ionic-framework/issues/29094',
308+
});
309+
310+
const datetime = await new DatetimeMultipleFixture(page).goto(config, MULTIPLE_DATES_SEPARATE_MONTHS);
311+
const calendarMonthYear = datetime.locator('.calendar-month-year');
312+
313+
await expect(calendarMonthYear).toHaveText(/May 2022/);
314+
});
315+
316+
test('does not change the active month view when selecting a day in a different month', async ({
317+
page,
318+
}, testInfo) => {
319+
testInfo.annotations.push({
320+
type: 'issue',
321+
description: 'https://github.com/ionic-team/ionic-framework/issues/29094',
322+
});
323+
324+
const datetime = await new DatetimeMultipleFixture(page).goto(config, MULTIPLE_DATES_SEPARATE_MONTHS);
325+
const nextButton = page.locator('.calendar-next-prev ion-button:nth-child(2)');
326+
const calendarMonthYear = datetime.locator('.calendar-month-year');
327+
328+
await nextButton.click();
329+
330+
await expect(calendarMonthYear).toHaveText(/June 2022/);
331+
332+
const june8Button = datetime.locator('[data-month="6"][data-day="8"]');
333+
334+
await june8Button.click();
335+
336+
await expect(calendarMonthYear).toHaveText(/June 2022/);
337+
});
338+
});
314339
});

core/src/components/modal/animations/ios.enter.ts

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -41,50 +41,7 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
4141
.addElement(baseEl)
4242
.easing('cubic-bezier(0.32,0.72,0,1)')
4343
.duration(500)
44-
.addAnimation([wrapperAnimation])
45-
.beforeAddWrite(() => {
46-
if (expandToScroll) {
47-
// Scroll can only be done when the modal is fully expanded.
48-
return;
49-
}
50-
51-
/**
52-
* There are some browsers that causes flickering when
53-
* dragging the content when scroll is enabled at every
54-
* breakpoint. This is due to the wrapper element being
55-
* transformed off the screen and having a snap animation.
56-
*
57-
* A workaround is to clone the footer element and append
58-
* it outside of the wrapper element. This way, the footer
59-
* is still visible and the drag can be done without
60-
* flickering. The original footer is hidden until the modal
61-
* is dismissed. This maintains the animation of the footer
62-
* when the modal is dismissed.
63-
*
64-
* The workaround needs to be done before the animation starts
65-
* so there are no flickering issues.
66-
*/
67-
const ionFooter = baseEl.querySelector('ion-footer');
68-
/**
69-
* This check is needed to prevent more than one footer
70-
* from being appended to the shadow root.
71-
* Otherwise, iOS and MD enter animations would append
72-
* the footer twice.
73-
*/
74-
const ionFooterAlreadyAppended = baseEl.shadowRoot!.querySelector('ion-footer');
75-
if (ionFooter && !ionFooterAlreadyAppended) {
76-
const footerHeight = ionFooter.clientHeight;
77-
const clonedFooter = ionFooter.cloneNode(true) as HTMLIonFooterElement;
78-
79-
baseEl.shadowRoot!.appendChild(clonedFooter);
80-
ionFooter.style.setProperty('display', 'none');
81-
ionFooter.setAttribute('aria-hidden', 'true');
82-
83-
// Padding is added to prevent some content from being hidden.
84-
const page = baseEl.querySelector('.ion-page') as HTMLElement;
85-
page.style.setProperty('padding-bottom', `${footerHeight}px`);
86-
}
87-
});
44+
.addAnimation([wrapperAnimation]);
8845

8946
if (contentAnimation) {
9047
baseAnimation.addAnimation(contentAnimation);

core/src/components/modal/animations/ios.leave.ts

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const createLeaveAnimation = () => {
1919
* iOS Modal Leave Animation
2020
*/
2121
export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions, duration = 500): Animation => {
22-
const { presentingEl, currentBreakpoint, expandToScroll } = opts;
22+
const { presentingEl, currentBreakpoint } = opts;
2323
const root = getElementRoot(baseEl);
2424
const { wrapperAnimation, backdropAnimation } =
2525
currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation();
@@ -32,33 +32,7 @@ export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
3232
.addElement(baseEl)
3333
.easing('cubic-bezier(0.32,0.72,0,1)')
3434
.duration(duration)
35-
.addAnimation(wrapperAnimation)
36-
.beforeAddWrite(() => {
37-
if (expandToScroll) {
38-
// Scroll can only be done when the modal is fully expanded.
39-
return;
40-
}
41-
42-
/**
43-
* If expandToScroll is disabled, we need to swap
44-
* the visibility to the original, so the footer
45-
* dismisses with the modal and doesn't stay
46-
* until the modal is removed from the DOM.
47-
*/
48-
const ionFooter = baseEl.querySelector('ion-footer');
49-
if (ionFooter) {
50-
const clonedFooter = baseEl.shadowRoot!.querySelector('ion-footer')!;
51-
52-
ionFooter.style.removeProperty('display');
53-
ionFooter.removeAttribute('aria-hidden');
54-
55-
clonedFooter.style.setProperty('display', 'none');
56-
clonedFooter.setAttribute('aria-hidden', 'true');
57-
58-
const page = baseEl.querySelector('.ion-page') as HTMLElement;
59-
page.style.removeProperty('padding-bottom');
60-
}
61-
});
35+
.addAnimation(wrapperAnimation);
6236

6337
if (presentingEl) {
6438
const isMobile = window.innerWidth < 768;

core/src/components/modal/animations/md.enter.ts

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -37,56 +37,13 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOption
3737

3838
// The content animation is only added if scrolling is enabled for
3939
// all the breakpoints.
40-
expandToScroll && contentAnimation?.addElement(baseEl.querySelector('.ion-page')!);
40+
!expandToScroll && contentAnimation?.addElement(baseEl.querySelector('.ion-page')!);
4141

4242
const baseAnimation = createAnimation()
4343
.addElement(baseEl)
4444
.easing('cubic-bezier(0.36,0.66,0.04,1)')
4545
.duration(280)
46-
.addAnimation([backdropAnimation, wrapperAnimation])
47-
.beforeAddWrite(() => {
48-
if (expandToScroll) {
49-
// Scroll can only be done when the modal is fully expanded.
50-
return;
51-
}
52-
53-
/**
54-
* There are some browsers that causes flickering when
55-
* dragging the content when scroll is enabled at every
56-
* breakpoint. This is due to the wrapper element being
57-
* transformed off the screen and having a snap animation.
58-
*
59-
* A workaround is to clone the footer element and append
60-
* it outside of the wrapper element. This way, the footer
61-
* is still visible and the drag can be done without
62-
* flickering. The original footer is hidden until the modal
63-
* is dismissed. This maintains the animation of the footer
64-
* when the modal is dismissed.
65-
*
66-
* The workaround needs to be done before the animation starts
67-
* so there are no flickering issues.
68-
*/
69-
const ionFooter = baseEl.querySelector('ion-footer');
70-
/**
71-
* This check is needed to prevent more than one footer
72-
* from being appended to the shadow root.
73-
* Otherwise, iOS and MD enter animations would append
74-
* the footer twice.
75-
*/
76-
const ionFooterAlreadyAppended = baseEl.shadowRoot!.querySelector('ion-footer');
77-
if (ionFooter && !ionFooterAlreadyAppended) {
78-
const footerHeight = ionFooter.clientHeight;
79-
const clonedFooter = ionFooter.cloneNode(true) as HTMLIonFooterElement;
80-
81-
baseEl.shadowRoot!.appendChild(clonedFooter);
82-
ionFooter.style.setProperty('display', 'none');
83-
ionFooter.setAttribute('aria-hidden', 'true');
84-
85-
// Padding is added to prevent some content from being hidden.
86-
const page = baseEl.querySelector('.ion-page') as HTMLElement;
87-
page.style.setProperty('padding-bottom', `${footerHeight}px`);
88-
}
89-
});
46+
.addAnimation([backdropAnimation, wrapperAnimation]);
9047

9148
if (contentAnimation) {
9249
baseAnimation.addAnimation(contentAnimation);

0 commit comments

Comments
 (0)