Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions demo/e2e/modal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ for (const scenario of [
height: scenario.modalHeight,
top: scenario.height - scenario.modalHeight,
});
// The sheet offset controls its height while its bottom stays at the viewport edge.
await modal.evaluate((el: HTMLElement) => el.style.setProperty('--ion-modal-offset-top', '80px'));
await expect.poll(bounds).toEqual({ width: scenario.modalWidth, height: scenario.height - 80, top: 80 });
await expect(modal.getByRole('dialog')).toHaveCSS('bottom', '0px');
});
}

Expand Down
39 changes: 31 additions & 8 deletions demo/e2e/screenshot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,34 +209,57 @@ test.describe('Screenshot Tests - VerticalBars Back Button', () => {

test.describe('Screenshot Tests - Datetime Modal', () => {
for (const scenario of [
{ name: 'desktop', width: 1200, height: 800, folded: false, customWidth: false },
{ name: 'phone', width: 390, height: 844, folded: false, customWidth: false },
{ name: 'folded', width: 951, height: 669, folded: true, customWidth: false },
{ name: 'custom-width', width: 1200, height: 800, folded: false, customWidth: true },
{ name: 'desktop', width: 1200, height: 800, folded: false, customWidth: false, customOverlay: false },
{ name: 'phone', width: 390, height: 844, folded: false, customWidth: false, customOverlay: false },
{ name: 'folded', width: 951, height: 669, folded: true, customWidth: false, customOverlay: false },
{ name: 'custom-width', width: 1200, height: 800, folded: false, customWidth: true, customOverlay: false },
{ name: 'custom-overlay', width: 1200, height: 800, folded: false, customWidth: false, customOverlay: true },
{ name: 'custom-overlay-folded', width: 951, height: 669, folded: true, customWidth: false, customOverlay: true },
{ name: 'custom-overlay-phone', width: 390, height: 844, folded: false, customWidth: false, customOverlay: true },
]) {
for (const dark of [false, true]) {
test(`should match datetime modal ${scenario.name}${dark ? ' dark' : ''}`, async ({ page }) => {
await page.addInitScript(() => (document.IONIC_E2E_TESTING = true));
await page.setViewportSize({ width: scenario.width, height: scenario.height });
await page.goto('/main/index/date-and-time-pickers', { waitUntil: 'networkidle' });
await page.evaluate(
async ({ dark, folded, customWidth }) => {
async ({ dark, folded, customWidth, customOverlay }) => {
document.documentElement.classList.toggle('ion-palette-dark', dark);
if (folded) document.querySelector('ion-app')!.classList.add('ios-theme-fold-expanded');
if (customWidth) {
const picker = document.querySelector<HTMLElement>('ion-datetime#datetime')!;
picker.style.width = '360px';
picker.style.maxWidth = 'none';
}
if (customOverlay) {
// Equivalent specificity to an Angular component's scoped ion-modal rule.
const style = document.createElement('style');
style.textContent = 'ion-modal[data-custom-datetime] { --width: auto; --height: auto; }';
document.head.append(style);
const modal = document.createElement('ion-modal');
modal.setAttribute('data-custom-datetime', '');
modal.keepContentsMounted = true;
const content = document.createElement('div');
const picker = document.createElement('ion-datetime');
picker.presentation = 'date';
picker.value = document.querySelector<HTMLIonDatetimeElement>('ion-datetime#datetime')!.value;
picker.showDefaultTitle = true;
picker.innerHTML =
'<span slot="title">Date</span><ion-buttons slot="buttons"><ion-button>Cancel</ion-button><ion-button>Apply</ion-button></ion-buttons>';
content.append(picker);
modal.append(content);
document.querySelector('ion-app')!.append(modal);
}
await document.fonts.ready;
},
{ dark, folded: scenario.folded, customWidth: scenario.customWidth },
{ dark, folded: scenario.folded, customWidth: scenario.customWidth, customOverlay: scenario.customOverlay },
);
const modal = page.locator('ion-modal.ion-datetime-button-overlay');
const modal = page.locator(scenario.customOverlay ? 'ion-modal[data-custom-datetime]' : 'ion-modal.ion-datetime-button-overlay');
await modal.evaluate((el) => {
el.addEventListener('ionModalDidPresent', () => el.setAttribute('data-test-presented', 'true'), { once: true });
});
await page.locator('ion-datetime-button button').first().click();
if (scenario.customOverlay) await modal.evaluate((el: HTMLIonModalElement) => el.present());
else await page.locator('ion-datetime-button button').first().click();
await expect(modal).toHaveAttribute('data-test-presented', 'true');
await expect(page).toHaveScreenshot(`datetime-modal-${scenario.name}${dark ? '-dark' : ''}.png`, { animations: 'disabled' });
});
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 17 additions & 12 deletions src/styles/components/ion-modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,25 @@ ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled) {
// Datetime Button overlays retain Ionic's content-based dimensions.
&:where(:not(.ion-datetime-button-overlay)) {
--border-radius: 32px;
--width: 580px;
--height: min(660px, calc(100% - 64px));
// Dimension defaults must yield to component-scoped sizing.
:where(&) {
--width: 580px;
--height: min(660px, calc(100% - 64px));
}
--box-shadow: 0 24px 64px color-mix(in srgb, var(--ion-box-shadow-color, #000) 18%, transparent);

&:not(.modal-card, .modal-sheet)::part(content) {
// Keep translucent headers from sampling the shadow outside the rounded surface.
clip-path: inset(0 round var(--border-radius));
}

&.modal-card {
:where(&.modal-card) {
--width: min(936px, calc(100% - 40px));
--max-width: 936px;
--height: calc(100% - max(32px, var(--ion-safe-area-top, 0px)) * 2);
}

&.modal-sheet {
:where(&.modal-sheet) {
--width: min(936px, calc(100vw - 40px));
--height: calc(100% - max(32px, var(--ion-modal-offset-top, 0px)));
}
Expand Down Expand Up @@ -97,8 +100,8 @@ ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled) {
// Keep ordinary phone and tablet defaults independent of these device layouts.
:is(ion-app.ios-theme-vertical-bars, ion-app.ios-theme-fold-expanded)
ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled, .ion-datetime-button-overlay) {
&.modal-card,
&.modal-sheet {
:where(&.modal-card),
:where(&.modal-sheet) {
--width: calc(100% - 16px);
--max-width: none;
--height: calc(100% - max(8px, var(--ion-safe-area-top, 0px)));
Expand All @@ -112,16 +115,18 @@ ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled) {
align-items: flex-end;
}

&.modal-sheet {
--height: calc(100% - max(8px, var(--ion-modal-offset-top, 0px)));
}

@media (min-width: 768px) and (min-height: 600px) {
--width: min(653px, calc(100% - 16px));
--height: calc(100% - max(8px, var(--ion-safe-area-top, 0px)));
:where(&) {
--width: min(653px, calc(100% - 16px));
--height: calc(100% - max(8px, var(--ion-safe-area-top, 0px)));
}
align-items: flex-end;
}

:where(&.modal-sheet) {
--height: calc(100% - max(8px, var(--ion-modal-offset-top, 0px)));
}

&.modal-sheet.modal-no-expand-scroll::part(content) {
bottom: 0;
}
Expand Down
Loading