Merge remote-tracking branch 'origin/main' into sync-feature-10-21

This commit is contained in:
Sean Perkins
2022-10-21 14:36:30 -04:00
100 changed files with 992 additions and 752 deletions

View File

@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.2](https://github.com/ionic-team/ionic-framework/compare/v6.3.1...v6.3.2) (2022-10-17)
### Bug Fixes
* **datetime:** header renders correct date ([#26120](https://github.com/ionic-team/ionic-framework/issues/26120)) ([04df45a](https://github.com/ionic-team/ionic-framework/commit/04df45a443e4faeea644daa76dc509fea0d24ca2)), closes [#26116](https://github.com/ionic-team/ionic-framework/issues/26116)
* **datetime:** selecting days updates value ([#26121](https://github.com/ionic-team/ionic-framework/issues/26121)) ([d76a24d](https://github.com/ionic-team/ionic-framework/commit/d76a24dd9e485a2f3cc517231bbb1dab51fa1fd3))
* **modal:** sheet modal dismisses correctly ([#26110](https://github.com/ionic-team/ionic-framework/issues/26110)) ([256b03f](https://github.com/ionic-team/ionic-framework/commit/256b03f12a57c2b5904d9017e4fa93b11eea8fc7)), closes [#26108](https://github.com/ionic-team/ionic-framework/issues/26108)
* **vue:** routing components define child components ([#26107](https://github.com/ionic-team/ionic-framework/issues/26107)) ([d60973b](https://github.com/ionic-team/ionic-framework/commit/d60973b2449b29a982b752a98b10d2b043ecff2f))
## [6.3.1](https://github.com/ionic-team/ionic-framework/compare/v6.3.0...v6.3.1) (2022-10-12)

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.2](https://github.com/ionic-team/ionic/compare/v6.3.1...v6.3.2) (2022-10-17)
**Note:** Version bump only for package @ionic/angular
## [6.3.1](https://github.com/ionic-team/ionic/compare/v6.3.0...v6.3.1) (2022-10-12)
**Note:** Version bump only for package @ionic/angular

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "6.3.1",
"version": "6.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "6.3.1",
"version": "6.3.2",
"license": "MIT",
"dependencies": {
"@ionic/core": "^6.3.1",
"@ionic/core": "6.3.2",
"jsonc-parser": "^3.0.0",
"tslib": "^2.0.0"
},
@@ -1023,9 +1023,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"dependencies": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",
@@ -7951,9 +7951,9 @@
"dev": true
},
"@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"requires": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "6.3.1",
"version": "6.3.2",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -46,7 +46,7 @@
"validate": "npm i && npm run lint && npm run test && npm run build"
},
"dependencies": {
"@ionic/core": "^6.3.1",
"@ionic/core": "6.3.2",
"jsonc-parser": "^3.0.0",
"tslib": "^2.0.0"
},

View File

@@ -19,7 +19,7 @@ export class ValueAccessor implements ControlValueAccessor, AfterViewInit, OnDes
writeValue(value: any): void {
/**
* TODO for Ionic 6:
* TODO FW-2646
* Change `value == null ? '' : value;`
* to `value`. This was a fix for IE9, but IE9
* is no longer supported; however, this change

View File

@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.2](https://github.com/ionic-team/ionic/compare/v6.3.1...v6.3.2) (2022-10-17)
### Bug Fixes
* **datetime:** header renders correct date ([#26120](https://github.com/ionic-team/ionic/issues/26120)) ([04df45a](https://github.com/ionic-team/ionic/commit/04df45a443e4faeea644daa76dc509fea0d24ca2)), closes [#26116](https://github.com/ionic-team/ionic/issues/26116)
* **datetime:** selecting days updates value ([#26121](https://github.com/ionic-team/ionic/issues/26121)) ([d76a24d](https://github.com/ionic-team/ionic/commit/d76a24dd9e485a2f3cc517231bbb1dab51fa1fd3))
* **modal:** sheet modal dismisses correctly ([#26110](https://github.com/ionic-team/ionic/issues/26110)) ([256b03f](https://github.com/ionic-team/ionic/commit/256b03f12a57c2b5904d9017e4fa93b11eea8fc7)), closes [#26108](https://github.com/ionic-team/ionic/issues/26108)
## [6.3.1](https://github.com/ionic-team/ionic/compare/v6.3.0...v6.3.1) (2022-10-12)

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "6.3.1",
"version": "6.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "6.3.1",
"version": "6.3.2",
"license": "MIT",
"dependencies": {
"@stencil/core": "^2.18.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "6.3.1",
"version": "6.3.2",
"description": "Base components for Ionic",
"keywords": [
"ionic",

View File

@@ -160,7 +160,8 @@ export class DatetimeButton implements ComponentInterface {
* to keep checking if the datetime value is `string` or `string[]`.
*/
private getParsedDateValues = (value?: string[] | string | null): string[] => {
if (value === undefined || value === null) {
// TODO FW-2646 Remove value === ''
if (value === '' || value === undefined || value === null) {
return [];
}

View File

@@ -616,7 +616,7 @@ export class Datetime implements ComponentInterface {
};
private setActiveParts = (parts: DatetimeParts, removeDate = false) => {
const { multiple, activePartsClone } = this;
const { multiple, minParts, maxParts, activePartsClone } = this;
/**
* When setting the active parts, it is possible
@@ -628,7 +628,7 @@ export class Datetime implements ComponentInterface {
* Additionally, we need to update the working parts
* too in the event that the validated parts are different.
*/
const validatedParts = validateParts(parts);
const validatedParts = validateParts(parts, minParts, maxParts);
this.setWorkingParts(validatedParts);
if (multiple) {
@@ -2131,12 +2131,8 @@ export class Datetime implements ComponentInterface {
</ion-popover>,
];
}
private renderCalendarViewHeader() {
const hasSlottedTitle = this.el.querySelector('[slot="title"]') !== null;
if (!hasSlottedTitle && !this.showDefaultTitle) {
return;
}
private getHeaderSelectedDateText() {
const { activeParts, multiple, titleSelectedDatesFormatter } = this;
const isArray = Array.isArray(activeParts);
@@ -2155,12 +2151,21 @@ export class Datetime implements ComponentInterface {
headerText = getMonthAndDay(this.locale, this.getDefaultPart());
}
return headerText;
}
private renderCalendarViewHeader(showExpandedHeader = true) {
const hasSlottedTitle = this.el.querySelector('[slot="title"]') !== null;
if (!hasSlottedTitle && !this.showDefaultTitle) {
return;
}
return (
<div class="datetime-header">
<div class="datetime-title">
<slot name="title">Select Date</slot>
</div>
<div class="datetime-selected-date">{headerText}</div>
{showExpandedHeader && <div class="datetime-selected-date">{this.getHeaderSelectedDateText()}</div>}
</div>
);
}
@@ -2207,7 +2212,7 @@ export class Datetime implements ComponentInterface {
*/
const hasWheelVariant = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
if (preferWheel && hasWheelVariant) {
return [this.renderWheelView(), this.renderFooter()];
return [this.renderCalendarViewHeader(false), this.renderWheelView(), this.renderFooter()];
}
switch (presentation) {

View File

@@ -14,6 +14,7 @@ import {
calculateHourFromAMPM,
subtractDays,
addDays,
validateParts,
} from '../utils/manipulation';
describe('addDays()', () => {
@@ -487,3 +488,72 @@ describe('getPreviousYear()', () => {
});
});
});
describe('validateParts()', () => {
it('should move day in bounds', () => {
expect(validateParts({ month: 2, day: 31, year: 2022, hour: 8, minute: 0 })).toEqual({
month: 2,
day: 28,
year: 2022,
hour: 8,
minute: 0,
});
});
it('should move the hour back in bounds according to the min', () => {
expect(
validateParts(
{ month: 1, day: 1, year: 2022, hour: 8, minute: 0 },
{ month: 1, day: 1, year: 2022, hour: 9, minute: 0 }
)
).toEqual({ month: 1, day: 1, year: 2022, hour: 9, minute: 0 });
});
it('should move the minute back in bounds according to the min', () => {
expect(
validateParts(
{ month: 1, day: 1, year: 2022, hour: 9, minute: 20 },
{ month: 1, day: 1, year: 2022, hour: 9, minute: 30 }
)
).toEqual({ month: 1, day: 1, year: 2022, hour: 9, minute: 30 });
});
it('should move the hour and minute back in bounds according to the min', () => {
expect(
validateParts(
{ month: 1, day: 1, year: 2022, hour: 8, minute: 30 },
{ month: 1, day: 1, year: 2022, hour: 9, minute: 0 }
)
).toEqual({ month: 1, day: 1, year: 2022, hour: 9, minute: 0 });
});
it('should move the hour back in bounds according to the max', () => {
expect(
validateParts({ month: 1, day: 1, year: 2022, hour: 10, minute: 0 }, undefined, {
month: 1,
day: 1,
year: 2022,
hour: 9,
minute: 0,
})
).toEqual({ month: 1, day: 1, year: 2022, hour: 9, minute: 0 });
});
it('should move the minute back in bounds according to the max', () => {
expect(
validateParts({ month: 1, day: 1, year: 2022, hour: 9, minute: 40 }, undefined, {
month: 1,
day: 1,
year: 2022,
hour: 9,
minute: 30,
})
).toEqual({ month: 1, day: 1, year: 2022, hour: 9, minute: 30 });
});
it('should move the hour and minute back in bounds according to the max', () => {
expect(
validateParts({ month: 1, day: 1, year: 2022, hour: 10, minute: 20 }, undefined, {
month: 1,
day: 1,
year: 2022,
hour: 9,
minute: 30,
})
).toEqual({ month: 1, day: 1, year: 2022, hour: 9, minute: 30 });
});
});

View File

@@ -234,4 +234,53 @@ test.describe('datetime: minmax', () => {
);
await expect(hourPickerItems).toHaveText(['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']);
});
test.describe('minmax value adjustment when out of bounds', () => {
test.beforeEach(({ skip }) => {
skip.rtl();
skip.mode('ios', 'This implementation is the same across modes.');
});
test('should reset to min time if out of bounds', async ({ page }) => {
await page.setContent(`
<ion-datetime
min="2022-10-10T08:00"
value="2022-10-11T06:00"
></ion-datetime>
`);
await page.waitForSelector('.datetime-ready');
const datetime = page.locator('ion-datetime');
const ionChange = await page.spyOnEvent('ionChange');
const dayButton = page.locator('ion-datetime .calendar-day[data-day="10"][data-month="10"][data-year="2022"]');
await dayButton.click();
await ionChange.next();
const value = await datetime.evaluate((el: HTMLIonDatetimeElement) => el.value);
await expect(typeof value).toBe('string');
await expect(value!.includes('2022-10-10T08:00')).toBe(true);
});
test('should reset to max time if out of bounds', async ({ page }) => {
await page.setContent(`
<ion-datetime
max="2022-10-10T08:00"
value="2022-10-11T09:00"
></ion-datetime>
`);
await page.waitForSelector('.datetime-ready');
const datetime = page.locator('ion-datetime');
const ionChange = await page.spyOnEvent('ionChange');
const dayButton = page.locator('ion-datetime .calendar-day[data-day="10"][data-month="10"][data-year="2022"]');
await dayButton.click();
await ionChange.next();
const value = await datetime.evaluate((el: HTMLIonDatetimeElement) => el.value);
await expect(typeof value).toBe('string');
await expect(value!.includes('2022-10-10T08:00')).toBe(true);
});
});
});

View File

@@ -40,6 +40,18 @@ test.describe('datetime: prefer wheel', () => {
`datetime-wheel-time-date-diff-${page.getSnapshotSettings()}.png`
);
});
test('should render a condense header when specified', async ({ page }) => {
await page.setContent(`
<ion-datetime size="cover" presentation="time-date" prefer-wheel="true" value="2019-05-30T16:30:00"><div slot="title">My Custom Title</div></ion-datetime>
`);
await page.waitForSelector('.datetime-ready');
const datetime = page.locator('ion-datetime');
expect(await datetime.screenshot()).toMatchSnapshot(
`datetime-wheel-header-diff-${page.getSnapshotSettings()}.png`
);
});
});
test.describe('datetime: date wheel', () => {
test.beforeEach(({ skip }) => {

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -50,3 +50,17 @@ test.describe('datetime: values', () => {
await expect(items).toHaveText(['01', '02', '03']);
});
});
test('setting value to empty string should treat it as having no date', async ({ page, skip }) => {
skip.rtl();
skip.mode('ios');
await page.setContent(`
<ion-datetime value="" locale="en-US"></ion-datetime>
`);
await page.waitForSelector('.datetime-ready');
// Should render current month with today outlined.
const calendarDayToday = page.locator('ion-datetime .calendar-day-today');
await expect(calendarDayToday).toBeVisible();
});

View File

@@ -1,5 +1,6 @@
import type { DatetimeParts } from '../datetime-interface';
import { isSameDay } from './comparison';
import { getNumDaysInMonth } from './helpers';
const twoDigit = (val: number | undefined): string => {
@@ -345,7 +346,11 @@ export const calculateHourFromAMPM = (currentParts: DatetimeParts, newAMPM: 'am'
* values are valid. For days that do not exist,
* the closest valid day is used.
*/
export const validateParts = (parts: DatetimeParts): DatetimeParts => {
export const validateParts = (
parts: DatetimeParts,
minParts?: DatetimeParts,
maxParts?: DatetimeParts
): DatetimeParts => {
const { month, day, year } = parts;
const partsCopy = { ...parts };
@@ -361,5 +366,66 @@ export const validateParts = (parts: DatetimeParts): DatetimeParts => {
partsCopy.day = numDays;
}
/**
* If value is same day as min day,
* make sure the time value is in bounds.
*/
if (minParts !== undefined && isSameDay(partsCopy, minParts)) {
/**
* If the hour is out of bounds,
* update both the hour and minute.
* This is done so that the new time
* is closest to what the user selected.
*/
if (partsCopy.hour !== undefined && minParts.hour !== undefined) {
if (partsCopy.hour < minParts.hour) {
partsCopy.hour = minParts.hour;
partsCopy.minute = minParts.minute;
/**
* If only the minute is out of bounds,
* set it to the min minute.
*/
} else if (
partsCopy.hour === minParts.hour &&
partsCopy.minute !== undefined &&
minParts.minute !== undefined &&
partsCopy.minute < minParts.minute
) {
partsCopy.minute = minParts.minute;
}
}
}
/**
* If value is same day as max day,
* make sure the time value is in bounds.
*/
if (maxParts !== undefined && isSameDay(parts, maxParts)) {
/**
* If the hour is out of bounds,
* update both the hour and minute.
* This is done so that the new time
* is closest to what the user selected.
*/
if (partsCopy.hour !== undefined && maxParts.hour !== undefined) {
if (partsCopy.hour > maxParts.hour) {
partsCopy.hour = maxParts.hour;
partsCopy.minute = maxParts.minute;
/**
* If only the minute is out of bounds,
* set it to the max minute.
*/
} else if (
partsCopy.hour === maxParts.hour &&
partsCopy.minute !== undefined &&
maxParts.minute !== undefined &&
partsCopy.minute > maxParts.minute
) {
partsCopy.minute = maxParts.minute;
}
}
}
return partsCopy;
};

View File

@@ -1,10 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('footer: basic', async () => {
const page = await newE2EPage({
url: '/src/components/footer/test/basic?ionic:_testing=true',
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@@ -0,0 +1,57 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('footer: basic', () => {
test.describe('footer: rendering', () => {
test('should not have visual regressions with basic footer', async ({ page }) => {
await page.setContent(`
<ion-footer>
<ion-toolbar>
<ion-title>Footer - Default</ion-title>
</ion-toolbar>
</ion-footer>
`);
const footer = page.locator('ion-footer');
expect(await footer.screenshot()).toMatchSnapshot(`footer-diff-${page.getSnapshotSettings()}.png`);
});
});
test.describe('footer: feature rendering', () => {
test.beforeEach(({ skip }) => {
skip.rtl();
});
test('should not have visual regressions with no border', async ({ page }) => {
await page.setContent(`
<ion-footer class="ion-no-border">
<ion-toolbar>
<ion-title>Footer - No Border</ion-title>
</ion-toolbar>
</ion-footer>
`);
const footer = page.locator('ion-footer');
expect(await footer.screenshot()).toMatchSnapshot(`footer-no-border-diff-${page.getSnapshotSettings()}.png`);
});
test('should not have visual regressions with translucent footer', async ({ page, skip }) => {
skip.mode('md', 'Translucent effect is only available in iOS mode.');
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
await page.setContent(`
<ion-footer translucent="true">
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0">
<img style="transform: rotate(145deg) scale(1.5)" src="/src/components/footer/test/img.jpg" />
</div>
<ion-toolbar>
<ion-title>Footer - Translucent</ion-title>
</ion-toolbar>
</ion-footer>
`);
const footer = page.locator('ion-footer');
expect(await footer.screenshot()).toMatchSnapshot(`footer-translucent-diff-${page.getSnapshotSettings()}.png`);
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -15,23 +15,14 @@
</head>
<body>
<ion-app>
<ion-content>
<br />
<ion-footer>
<ion-toolbar>
<ion-title>Footer - Default</ion-title>
</ion-toolbar>
</ion-footer>
<br />
<ion-footer class="ion-no-border">
<ion-toolbar>
<ion-title>Footer - No Border</ion-title>
</ion-toolbar>
</ion-footer>
<ion-content class="ion-padding">
<h1>Content</h1>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
</ion-app>
</body>
</html>

View File

@@ -1,27 +0,0 @@
import type { E2EPage } from '@stencil/core/testing';
import { newE2EPage } from '@stencil/core/testing';
import { scrollToBottom } from '@utils/test';
describe('footer: fade: iOS', () => {
let page: E2EPage;
beforeEach(async () => {
page = await newE2EPage({
url: '/src/components/footer/test/fade?ionic:_testing=true&ionic:mode=ios',
});
});
it('should match existing visual screenshots', async () => {
const compares = [];
compares.push(await page.compareScreenshot('footer: blurred'));
await scrollToBottom(page, 'ion-content');
compares.push(await page.compareScreenshot('footer: not blurred'));
for (const compare of compares) {
expect(compare).toMatchScreenshot();
}
});
});

View File

@@ -0,0 +1,24 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('footer: fade', () => {
test.beforeEach(({ skip }) => {
skip.rtl();
});
test('should not have visual regressions with fade footer', async ({ page, skip }) => {
skip.mode('md', 'Translucent effect is only available in iOS mode.');
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
await page.goto('/src/components/footer/test/fade');
const footer = page.locator('ion-footer');
expect(await footer.screenshot()).toMatchSnapshot(`footer-fade-blurred-diff-${page.getSnapshotSettings()}.png`);
const content = page.locator('ion-content');
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
await page.waitForChanges();
expect(await footer.screenshot()).toMatchSnapshot(`footer-fade-not-blurred-diff-${page.getSnapshotSettings()}.png`);
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -12,78 +12,73 @@
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
.red {
background-color: #ea445a;
}
.green {
background-color: #76d672;
}
.blue {
background-color: #3478f6;
}
.yellow {
background-color: #ffff80;
}
.pink {
background-color: #ff6b86;
}
.purple {
background-color: #7e34f6;
}
.black {
background-color: #000;
}
.orange {
background-color: #f69234;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
}
.grid-item {
height: 200px;
}
</style>
</head>
<body>
<ion-app>
<div class="ion-page">
<ion-header translucent="true">
<ion-toolbar>
<ion-title>Mailboxes</ion-title>
<ion-title>Header</ion-title>
</ion-toolbar>
</ion-header>
<ion-content fullscreen="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Mailboxes</ion-title>
<ion-title size="large">Header</ion-title>
</ion-toolbar>
</ion-header>
<div class="grid ion-padding">
<div class="grid-item red"></div>
<div class="grid-item green"></div>
<div class="grid-item blue"></div>
<div class="grid-item yellow"></div>
<div class="grid-item pink"></div>
<div class="grid-item purple"></div>
<div class="grid-item black"></div>
<div class="grid-item orange"></div>
<div class="ion-padding">
<h1>Content</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas blandit, justo vitae rhoncus porttitor,
massa elit molestie nisl, ut tincidunt orci enim ac ante. Vestibulum tincidunt dignissim elit. Morbi
cursus hendrerit turpis, ut egestas tortor pulvinar sit amet. Interdum et malesuada fames ac ante ipsum
primis in faucibus. Phasellus faucibus consequat purus vel mollis. Ut ultricies elit nunc. Quisque
ultrices turpis vel augue auctor accumsan. Donec bibendum at nisi vel finibus. Fusce id imperdiet odio.
Morbi orci ipsum, imperdiet ut iaculis sit amet, suscipit vulputate felis. Nullam arcu leo, volutpat eu
porttitor sed, fringilla et arcu. Pellentesque ac libero sapien. Quisque id dui velit. Mauris et pretium
tortor.
</p>
<p>
Ut ultricies id augue vel aliquam. Etiam ornare finibus nisl, nec egestas urna. Nam pellentesque libero
nec justo tristique lacinia. In sit amet gravida metus, ac tincidunt mauris. Fusce sit amet tempus turpis.
Nulla ligula nunc, vestibulum quis quam in, feugiat aliquet nibh. Quisque in ante non nulla luctus gravida
vel at lacus. Vivamus erat magna, elementum et dignissim nec, posuere eu ante. Praesent elementum, arcu
scelerisque venenatis sodales, turpis nulla aliquam urna, id hendrerit est orci et purus. Duis sem ipsum,
imperdiet eu elit id, tincidunt tempus sapien. Praesent tincidunt, sapien sed rhoncus euismod, lectus
velit ornare nunc, dapibus varius turpis leo ut magna.
</p>
<p>
Nam quis quam id ante mattis pulvinar non sed mauris. Donec tempor sed nulla at semper. Vivamus ac nunc
bibendum, ullamcorper lacus quis, ornare massa. Cras gravida nibh risus, id sollicitudin eros ultricies
non. Integer velit massa, suscipit tincidunt rhoncus ut, lacinia et nisl. Maecenas volutpat ipsum blandit
sollicitudin lobortis. Suspendisse potenti. Cras non mi non arcu varius dapibus. Suspendisse maximus eget
justo a lobortis. Donec nulla ipsum, efficitur eget velit eget, varius rutrum quam. Nulla metus risus,
accumsan a tellus ac, faucibus blandit quam. Donec luctus, nisl ac ultricies ornare, nunc elit finibus
magna, id elementum ante urna congue ex. Cras condimentum nisi sollicitudin tortor vestibulum luctus.
Curabitur non ipsum et ex vestibulum congue.
</p>
<p>
Maecenas rhoncus elit ut consectetur faucibus. Etiam sed sem sed mauris condimentum viverra sit amet at
nibh. Mauris bibendum at purus a cursus. Suspendisse potenti. Donec vel lacus ac odio euismod lacinia id
in urna. Donec commodo ipsum augue, at bibendum ex convallis suscipit. Nulla ac rhoncus odio. Aenean
elementum est nec arcu ultricies dignissim.
</p>
<p>
Sed tincidunt bibendum massa, egestas bibendum est imperdiet vitae. Fusce dignissim consectetur ante a
fermentum. Morbi suscipit turpis sapien. Suspendisse eleifend sapien eget nunc mattis mattis. Phasellus
rhoncus sodales libero a imperdiet. Nam in vulputate lectus. Proin accumsan enim non nibh sagittis
ultricies. Nullam vitae ultricies nunc. Nullam ultrices dolor nec vehicula posuere.
</p>
</div>
</ion-content>
<ion-footer collapse="fade" translucent="true">
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0">
<img src="/src/components/footer/test/img.jpg" />
</div>
<ion-toolbar>
<ion-title>Updated Just Now</ion-title>
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
</div>

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -1,32 +0,0 @@
import type { E2EPage } from '@stencil/core/testing';
import { newE2EPage } from '@stencil/core/testing';
import { scrollToBottom } from '@utils/test';
/**
* This test suite verifies that the fade effect for iOS is working correctly
* when the `ion-footer` is using a custom scroll target with the `.ion-content-scroll-host`
* selector.
*/
describe('footer: fade with custom scroll target: iOS', () => {
let page: E2EPage;
beforeEach(async () => {
page = await newE2EPage({
url: '/src/components/footer/test/scroll-target?ionic:_testing=true&ionic:mode=ios',
});
});
it('should match existing visual screenshots', async () => {
const compares = [];
compares.push(await page.compareScreenshot('footer: blurred'));
await scrollToBottom(page, '#scroll-target');
compares.push(await page.compareScreenshot('footer: not blurred'));
for (const compare of compares) {
expect(compare).toMatchScreenshot();
}
});
});

View File

@@ -0,0 +1,33 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('footer: scroll-target', () => {
test.beforeEach(({ skip }) => {
skip.rtl();
});
/**
* This test suite verifies that the fade effect for iOS is working correctly
* when the `ion-footer` is using a custom scroll target with the `.ion-content-scroll-host`
* selector.
*/
test('should not have visual regressions with custom scroll target footer', async ({ page, skip }) => {
skip.mode('md', 'Translucent effect is only available in iOS mode.');
skip.browser('firefox', 'Firefox has some issues rendering translucent effects on Linux.');
await page.goto('/src/components/footer/test/scroll-target');
const footer = page.locator('ion-footer');
expect(await footer.screenshot()).toMatchSnapshot(
`footer-fade-scroll-target-blurred-diff-${page.getSnapshotSettings()}.png`
);
const scrollTarget = page.locator('#scroll-target');
await scrollTarget.evaluate((el: HTMLDivElement) => (el.scrollTop = el.scrollHeight));
await page.waitForChanges();
expect(await footer.screenshot()).toMatchSnapshot(
`footer-fade-scroll-target-not-blurred-diff-${page.getSnapshotSettings()}.png`
);
});
});

View File

@@ -2,7 +2,7 @@
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Footer - Fade (custom scroll host)</title>
<title>Footer - Scroll Target</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
@@ -12,49 +12,8 @@
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
.red {
background-color: #ea445a;
}
.green {
background-color: #76d672;
}
.blue {
background-color: #3478f6;
}
.yellow {
background-color: #ffff80;
}
.pink {
background-color: #ff6b86;
}
.purple {
background-color: #7e34f6;
}
.black {
background-color: #000;
}
.orange {
background-color: #f69234;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
}
.grid-item {
height: 200px;
}
#scroll-target {
position: absolute;
top: 0;
@@ -68,32 +27,67 @@
}
</style>
</head>
<body>
<ion-app>
<div class="ion-page">
<ion-header translucent="true">
<ion-toolbar>
<ion-title>Mailboxes</ion-title>
<ion-title>Header</ion-title>
</ion-toolbar>
</ion-header>
<ion-content fullscreen="true" scroll-y="false">
<div id="scroll-target" class="ion-content-scroll-host">
<div class="grid ion-padding">
<div class="grid-item red"></div>
<div class="grid-item green"></div>
<div class="grid-item blue"></div>
<div class="grid-item yellow"></div>
<div class="grid-item pink"></div>
<div class="grid-item purple"></div>
<div class="grid-item black"></div>
<div class="grid-item orange"></div>
</div>
<h1>Content</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas blandit, justo vitae rhoncus porttitor,
massa elit molestie nisl, ut tincidunt orci enim ac ante. Vestibulum tincidunt dignissim elit. Morbi
cursus hendrerit turpis, ut egestas tortor pulvinar sit amet. Interdum et malesuada fames ac ante ipsum
primis in faucibus. Phasellus faucibus consequat purus vel mollis. Ut ultricies elit nunc. Quisque
ultrices turpis vel augue auctor accumsan. Donec bibendum at nisi vel finibus. Fusce id imperdiet odio.
Morbi orci ipsum, imperdiet ut iaculis sit amet, suscipit vulputate felis. Nullam arcu leo, volutpat eu
porttitor sed, fringilla et arcu. Pellentesque ac libero sapien. Quisque id dui velit. Mauris et pretium
tortor.
</p>
<p>
Ut ultricies id augue vel aliquam. Etiam ornare finibus nisl, nec egestas urna. Nam pellentesque libero
nec justo tristique lacinia. In sit amet gravida metus, ac tincidunt mauris. Fusce sit amet tempus turpis.
Nulla ligula nunc, vestibulum quis quam in, feugiat aliquet nibh. Quisque in ante non nulla luctus gravida
vel at lacus. Vivamus erat magna, elementum et dignissim nec, posuere eu ante. Praesent elementum, arcu
scelerisque venenatis sodales, turpis nulla aliquam urna, id hendrerit est orci et purus. Duis sem ipsum,
imperdiet eu elit id, tincidunt tempus sapien. Praesent tincidunt, sapien sed rhoncus euismod, lectus
velit ornare nunc, dapibus varius turpis leo ut magna.
</p>
<p>
Nam quis quam id ante mattis pulvinar non sed mauris. Donec tempor sed nulla at semper. Vivamus ac nunc
bibendum, ullamcorper lacus quis, ornare massa. Cras gravida nibh risus, id sollicitudin eros ultricies
non. Integer velit massa, suscipit tincidunt rhoncus ut, lacinia et nisl. Maecenas volutpat ipsum blandit
sollicitudin lobortis. Suspendisse potenti. Cras non mi non arcu varius dapibus. Suspendisse maximus eget
justo a lobortis. Donec nulla ipsum, efficitur eget velit eget, varius rutrum quam. Nulla metus risus,
accumsan a tellus ac, faucibus blandit quam. Donec luctus, nisl ac ultricies ornare, nunc elit finibus
magna, id elementum ante urna congue ex. Cras condimentum nisi sollicitudin tortor vestibulum luctus.
Curabitur non ipsum et ex vestibulum congue.
</p>
<p>
Maecenas rhoncus elit ut consectetur faucibus. Etiam sed sem sed mauris condimentum viverra sit amet at
nibh. Mauris bibendum at purus a cursus. Suspendisse potenti. Donec vel lacus ac odio euismod lacinia id
in urna. Donec commodo ipsum augue, at bibendum ex convallis suscipit. Nulla ac rhoncus odio. Aenean
elementum est nec arcu ultricies dignissim.
</p>
<p>
Sed tincidunt bibendum massa, egestas bibendum est imperdiet vitae. Fusce dignissim consectetur ante a
fermentum. Morbi suscipit turpis sapien. Suspendisse eleifend sapien eget nunc mattis mattis. Phasellus
rhoncus sodales libero a imperdiet. Nam in vulputate lectus. Proin accumsan enim non nibh sagittis
ultricies. Nullam vitae ultricies nunc. Nullam ultrices dolor nec vehicula posuere.
</p>
</div>
</ion-content>
<ion-footer collapse="fade" translucent="true">
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0">
<img src="/src/components/footer/test/img.jpg" />
</div>
<ion-toolbar>
<ion-title>Updated Just Now</ion-title>
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
</div>

View File

@@ -1,14 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
import { checkComponentModeClasses } from '@utils/test';
test('footer: translucent', async () => {
const page = await newE2EPage({
url: '/src/components/footer/test/translucent?ionic:_testing=true',
});
const globalMode = await page.evaluate(() => document.documentElement.getAttribute('mode'));
await checkComponentModeClasses(await page.find('ion-footer'), globalMode!, 'footer-translucent');
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@@ -1,171 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Header - Translucent</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-content fullscreen>
<ion-grid>
<ion-row>
<ion-col size="6">
<f class="red"></f>
</ion-col>
<ion-col size="6">
<f class="green"></f>
</ion-col>
<ion-col size="6">
<f class="blue"></f>
</ion-col>
<ion-col size="6">
<f class="yellow"></f>
</ion-col>
<ion-col size="6">
<f class="pink"></f>
</ion-col>
<ion-col size="6">
<f class="purple"></f>
</ion-col>
<ion-col size="6">
<f class="black"></f>
</ion-col>
<ion-col size="6">
<f class="orange"></f>
</ion-col>
</ion-row>
</ion-grid>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
scelerisque nisl eu suscipit consectetur.
</p>
<ion-grid>
<ion-row>
<ion-col size="6">
<f class="red"></f>
</ion-col>
<ion-col size="6">
<f class="green"></f>
</ion-col>
<ion-col size="6">
<f class="blue"></f>
</ion-col>
<ion-col size="6">
<f class="yellow"></f>
</ion-col>
<ion-col size="6">
<f class="pink"></f>
</ion-col>
<ion-col size="6">
<f class="purple"></f>
</ion-col>
<ion-col size="6">
<f class="black"></f>
</ion-col>
<ion-col size="6">
<f class="orange"></f>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>
<ion-footer translucent>
<ion-toolbar>
<ion-title>Footer - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="primary">
<ion-title>Primary - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="secondary">
<ion-title>Secondary - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="tertiary">
<ion-title>Tertiary - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="success">
<ion-title>Success - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="warning">
<ion-title>Warning - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="danger">
<ion-title>Danger - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="light">
<ion-title>Light - Translucent</ion-title>
</ion-toolbar>
<ion-toolbar color="medium">
<ion-title>Medium - Translucent</ion-title>
</ion-toolbar>
</ion-footer>
</ion-app>
<style>
f {
display: block;
height: 200px;
}
.red {
background-color: #ea445a;
}
.green {
background-color: #76d672;
}
.blue {
background-color: #3478f6;
}
.yellow {
background-color: #ffff80;
}
.pink {
background-color: #ff6b86;
}
.purple {
background-color: #7e34f6;
}
.black {
background-color: #000;
}
.orange {
background-color: #f69234;
}
</style>
</body>
</html>

View File

@@ -12,6 +12,7 @@
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
:root {
--ion-safe-area-bottom: 40px;

View File

@@ -22,7 +22,8 @@ test.describe('item-sliding: basic', () => {
await testSlidingItem(page, item, 'end');
});
test('should open when swiped', async ({ page, skip }) => {
// mouse gesture is flaky on CI, skip for now
test.fixme('should open when swiped', async ({ page, skip }) => {
skip.rtl();
skip.browser(
(browserName: string) => browserName !== 'chromium',

View File

@@ -36,70 +36,6 @@ export class PickerColumnInternal implements ComponentInterface {
* A list of options to be displayed in the picker
*/
@Prop() items: PickerColumnItem[] = [];
@Watch('items')
itemsChange(currentItems: PickerColumnItem[], previousItems: PickerColumnItem[]) {
const { value } = this;
/**
* When the items change, it is possible for the item
* that was selected to no longer exist. In that case, we need
* to automatically select the nearest item. If we do not,
* then the scroll position will be reset to zero and it will
* look like the first item was automatically selected.
*
* If we cannot find a closest item then we do nothing, and
* the browser will reset the scroll position to 0.
*/
const findCurrentItem = currentItems.find((item) => item.value === value);
if (!findCurrentItem) {
/**
* The default behavior is to assume
* that the new set of data is similar to the old
* set of data, just with some items filtered out.
* We walk backwards through the data to find the
* closest enabled picker item and select it.
*
* Developers can also swap the items out for an entirely
* new set of data. In that case, the value we select
* here likely will not make much sense. For this use case,
* developers should update the `value` prop themselves
* when swapping out the data.
*/
const findPreviousItemIndex = previousItems.findIndex((item) => item.value === value);
if (findPreviousItemIndex === -1) {
return;
}
/**
* Step through the current items backwards
* until we find a neighbor we can select.
* We start at the last known location of the
* current selected item in order to
* account for data that has been added. This
* search prioritizes stability in that it
* tries to keep the scroll position as close
* to where it was before the update.
* Before Items: ['a', 'b', 'c'], Selected Value: 'b'
* After Items: ['a', 'dog', 'c']
* Even though 'dog' is a different item than 'b',
* it is the closest item we can select while
* preserving the scroll position.
*/
let nearestItem;
for (let i = findPreviousItemIndex; i >= 0; i--) {
const item = currentItems[i];
if (item !== undefined && item.disabled !== true) {
nearestItem = item;
break;
}
}
if (nearestItem) {
this.setValue(nearestItem.value);
return;
}
}
}
/**
* The selected option in the picker.

View File

@@ -1,175 +0,0 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('picker-column-internal: updating items', () => {
test('should select nearest neighbor when updating items', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '5', value: 5 },
];
column.value = 5;
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await expect(pickerColumn).toHaveJSProperty('value', 5);
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
];
});
await page.waitForChanges();
await expect(pickerColumn).toHaveJSProperty('value', 2);
});
test('should select same position item even if item value is different', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '5', value: 5 },
];
column.value = 5;
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await expect(pickerColumn).toHaveJSProperty('value', 5);
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '1000', value: 1000 },
];
});
await page.waitForChanges();
await expect(pickerColumn).toHaveJSProperty('value', 1000);
});
test('should not select a disabled item', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '5', value: 5 },
];
column.value = 5;
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await expect(pickerColumn).toHaveJSProperty('value', 5);
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3, disabled: true },
];
});
await page.waitForChanges();
await expect(pickerColumn).toHaveJSProperty('value', 2);
});
test('should reset to the first item if no good item was found', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '5', value: 5 },
];
column.value = 5;
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await expect(pickerColumn).toHaveJSProperty('value', 5);
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2, disabled: true },
{ text: '3', value: 3, disabled: true },
];
});
await page.waitForChanges();
await expect(pickerColumn).toHaveJSProperty('value', 1);
});
test('should still select correct value if data was added', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '5', value: 5 },
];
column.value = 5;
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await expect(pickerColumn).toHaveJSProperty('value', 5);
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '6', value: 6 },
{ text: '7', value: 7 },
{ text: '5', value: 5 },
];
});
await page.waitForChanges();
await expect(pickerColumn).toHaveJSProperty('value', 5);
});
});

View File

@@ -13,7 +13,26 @@ const HapticEngine = {
return win.TapticEngine || (win.Capacitor?.isPluginAvailable('Haptics') && win.Capacitor.Plugins.Haptics);
},
available() {
return !!this.getEngine();
const win = window as any;
const engine = this.getEngine();
if (!engine) {
return false;
}
/**
* Developers can manually import the
* Haptics plugin in their app which will cause
* getEngine to return the Haptics engine. However,
* the Haptics engine will throw an error if
* used in a web browser that does not support
* the Vibrate API. This check avoids that error
* if the browser does not support the Vibrate API.
*/
if (win.Capacitor?.getPlatform() === 'web') {
return typeof navigator !== 'undefined' && navigator.vibrate !== undefined;
}
return true;
},
isCordova() {
return !!(window as any).TapticEngine;
@@ -89,21 +108,21 @@ export const hapticAvailable = (): boolean => {
* (not for gestures)
*/
export const hapticSelection = () => {
HapticEngine.selection();
hapticAvailable() && HapticEngine.selection();
};
/**
* Tell the haptic engine that a gesture for a selection change is starting.
*/
export const hapticSelectionStart = () => {
HapticEngine.selectionStart();
hapticAvailable() && HapticEngine.selectionStart();
};
/**
* Tell the haptic engine that a selection changed during a gesture.
*/
export const hapticSelectionChanged = () => {
HapticEngine.selectionChanged();
hapticAvailable() && HapticEngine.selectionChanged();
};
/**
@@ -111,7 +130,7 @@ export const hapticSelectionChanged = () => {
* called lest resources are not properly recycled.
*/
export const hapticSelectionEnd = () => {
HapticEngine.selectionEnd();
hapticAvailable() && HapticEngine.selectionEnd();
};
/**
@@ -119,7 +138,7 @@ export const hapticSelectionEnd = () => {
* options should be of the type `{ type: 'success' }` (or `warning`/`error`)
*/
export const hapticNotification = (options: HapticNotificationOptions) => {
HapticEngine.notification(options);
hapticAvailable() && HapticEngine.notification(options);
};
/**
@@ -127,5 +146,5 @@ export const hapticNotification = (options: HapticNotificationOptions) => {
* options should be of the type `{ style: 'light' }` (or `medium`/`heavy`)
*/
export const hapticImpact = (options: HapticImpactOptions) => {
HapticEngine.impact(options);
hapticAvailable() && HapticEngine.impact(options);
};

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.2](https://github.com/ionic-team/ionic-docs/compare/v6.3.1...v6.3.2) (2022-10-17)
**Note:** Version bump only for package @ionic/docs
## [6.3.1](https://github.com/ionic-team/ionic-docs/compare/v6.3.0...v6.3.1) (2022-10-12)
**Note:** Version bump only for package @ionic/docs

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/docs",
"version": "6.3.1",
"version": "6.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/docs",
"version": "6.3.1",
"version": "6.3.2",
"license": "MIT"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/docs",
"version": "6.3.1",
"version": "6.3.2",
"description": "Pre-packaged API documentation for the Ionic docs.",
"main": "core.json",
"types": "core.d.ts",

View File

@@ -5,5 +5,5 @@
"angular",
"packages/*"
],
"version": "6.3.1"
"version": "6.3.2"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.2](https://github.com/ionic-team/ionic/compare/v6.3.1...v6.3.2) (2022-10-17)
**Note:** Version bump only for package @ionic/angular-server
## [6.3.1](https://github.com/ionic-team/ionic/compare/v6.3.0...v6.3.1) (2022-10-12)
**Note:** Version bump only for package @ionic/angular-server

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/angular-server",
"version": "6.3.1",
"version": "6.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
"version": "6.3.1",
"version": "6.3.2",
"license": "MIT",
"devDependencies": {
"@angular-eslint/eslint-plugin": "^12.6.1",
@@ -18,7 +18,7 @@
"@angular/platform-browser": "^12.0.0",
"@angular/platform-browser-dynamic": "^12.2.10",
"@angular/platform-server": "^12.0.0",
"@ionic/core": "^6.3.1",
"@ionic/core": "^6.3.2",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^5.2.0",
@@ -786,9 +786,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"dev": true,
"dependencies": {
"@stencil/core": "^2.18.0",
@@ -7116,9 +7116,9 @@
"dev": true
},
"@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"dev": true,
"requires": {
"@stencil/core": "^2.18.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular-server",
"version": "6.3.1",
"version": "6.3.2",
"description": "Angular SSR Module for Ionic",
"keywords": [
"ionic",
@@ -56,7 +56,7 @@
"@angular/platform-browser": "^12.0.0",
"@angular/platform-browser-dynamic": "^12.2.10",
"@angular/platform-server": "^12.0.0",
"@ionic/core": "^6.3.1",
"@ionic/core": "^6.3.2",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^5.2.0",

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.2](https://github.com/ionic-team/ionic/compare/v6.3.1...v6.3.2) (2022-10-17)
**Note:** Version bump only for package @ionic/react-router
## [6.3.1](https://github.com/ionic-team/ionic/compare/v6.3.0...v6.3.1) (2022-10-12)
**Note:** Version bump only for package @ionic/react-router

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react-router",
"version": "6.3.1",
"version": "6.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react-router",
"version": "6.3.1",
"version": "6.3.2",
"license": "MIT",
"dependencies": {
"@ionic/react": "^6.3.1",
"@ionic/react": "6.3.2",
"tslib": "*"
},
"devDependencies": {
@@ -147,9 +147,9 @@
}
},
"node_modules/@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"dependencies": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",
@@ -157,11 +157,11 @@
}
},
"node_modules/@ionic/react": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-6.3.1.tgz",
"integrity": "sha512-8Omxifn+b6wXjfm/+OH+AzKpFsR7xn0u8dxaAKeT66txh4fA7DBMgzEPBOe5+VvWarWI6B7cElX/7Xq8qJNwbw==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-6.3.2.tgz",
"integrity": "sha512-+YZkUlOvygnowTmtEACd+B1fhmets3sSazr5Ipnv/O/genGgcY5llpxI+LeqvrWt0ECAtiNdF3c35M1IcoAuxA==",
"dependencies": {
"@ionic/core": "^6.3.1",
"@ionic/core": "^6.3.2",
"ionicons": "^6.0.2",
"tslib": "*"
},
@@ -1157,9 +1157,9 @@
}
},
"@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"requires": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",
@@ -1167,11 +1167,11 @@
}
},
"@ionic/react": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-6.3.1.tgz",
"integrity": "sha512-8Omxifn+b6wXjfm/+OH+AzKpFsR7xn0u8dxaAKeT66txh4fA7DBMgzEPBOe5+VvWarWI6B7cElX/7Xq8qJNwbw==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-6.3.2.tgz",
"integrity": "sha512-+YZkUlOvygnowTmtEACd+B1fhmets3sSazr5Ipnv/O/genGgcY5llpxI+LeqvrWt0ECAtiNdF3c35M1IcoAuxA==",
"requires": {
"@ionic/core": "^6.3.1",
"@ionic/core": "^6.3.2",
"ionicons": "^6.0.2",
"tslib": "*"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react-router",
"version": "6.3.1",
"version": "6.3.2",
"description": "React Router wrapper for @ionic/react",
"keywords": [
"ionic",
@@ -36,7 +36,7 @@
"dist/"
],
"dependencies": {
"@ionic/react": "^6.3.1",
"@ionic/react": "6.3.2",
"tslib": "*"
},
"peerDependencies": {

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.2](https://github.com/ionic-team/ionic/compare/v6.3.1...v6.3.2) (2022-10-17)
**Note:** Version bump only for package @ionic/react
## [6.3.1](https://github.com/ionic-team/ionic/compare/v6.3.0...v6.3.1) (2022-10-12)
**Note:** Version bump only for package @ionic/react

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react",
"version": "6.3.1",
"version": "6.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react",
"version": "6.3.1",
"version": "6.3.2",
"license": "MIT",
"dependencies": {
"@ionic/core": "^6.3.1",
"@ionic/core": "6.3.2",
"ionicons": "^6.0.2",
"tslib": "*"
},
@@ -607,9 +607,9 @@
}
},
"node_modules/@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"dependencies": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",
@@ -9534,9 +9534,9 @@
}
},
"@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"requires": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react",
"version": "6.3.1",
"version": "6.3.2",
"description": "React specific wrapper for @ionic/core",
"keywords": [
"ionic",
@@ -40,7 +40,7 @@
"css/"
],
"dependencies": {
"@ionic/core": "^6.3.1",
"@ionic/core": "6.3.2",
"ionicons": "^6.0.2",
"tslib": "*"
},

View File

@@ -1,4 +1,4 @@
import { OverlayEventDetail } from '@ionic/core/components'
import { OverlayEventDetail } from '@ionic/core/components';
import React, { createElement } from 'react';
import {
@@ -12,7 +12,7 @@ import { createForwardRef } from './utils';
type InlineOverlayState = {
isOpen: boolean;
}
};
interface IonicReactInternalProps<ElementType> extends React.HTMLAttributes<ElementType> {
forwardedRef?: React.ForwardedRef<ElementType>;
@@ -32,17 +32,20 @@ export const createInlineOverlayComponent = <PropType, ElementType>(
defineCustomElement();
}
const displayName = dashToPascalCase(tagName);
const ReactComponent = class extends React.Component<IonicReactInternalProps<PropType>, InlineOverlayState> {
const ReactComponent = class extends React.Component<
IonicReactInternalProps<PropType>,
InlineOverlayState
> {
ref: React.RefObject<HTMLElement>;
wrapperRef: React.RefObject<HTMLElement>;
stableMergedRefs: React.RefCallback<HTMLElement>
stableMergedRefs: React.RefCallback<HTMLElement>;
constructor(props: IonicReactInternalProps<PropType>) {
super(props);
// Create a local ref to to attach props to the wrapped element.
this.ref = React.createRef();
// React refs must be stable (not created inline).
this.stableMergedRefs = mergeRefs(this.ref, this.props.forwardedRef)
this.stableMergedRefs = mergeRefs(this.ref, this.props.forwardedRef);
// Component is hidden by default
this.state = { isOpen: false };
// Create a local ref to the inner child element.
@@ -123,23 +126,34 @@ export const createInlineOverlayComponent = <PropType, ElementType>(
style,
};
/**
* We only want the inner component
* to be mounted if the overlay is open,
* so conditionally render the component
* based on the isOpen state.
*/
return createElement(tagName, newProps, (this.state.isOpen || this.props.keepContentsMounted) ?
createElement('div', {
id: 'ion-react-wrapper',
ref: this.wrapperRef,
style: {
display: 'flex',
flexDirection: 'column',
height: '100%'
}
}, children) :
null
return createElement(
'template',
{},
createElement(
tagName,
newProps,
/**
* We only want the inner component
* to be mounted if the overlay is open,
* so conditionally render the component
* based on the isOpen state.
*/
this.state.isOpen || this.props.keepContentsMounted
? createElement(
'div',
{
id: 'ion-react-wrapper',
ref: this.wrapperRef,
style: {
display: 'flex',
flexDirection: 'column',
height: '100%',
},
},
children
)
: null
)
);
}

View File

@@ -27,6 +27,10 @@ import OverlayComponents from './pages/overlay-components/OverlayComponents';
import KeepContentsMounted from './pages/overlay-components/KeepContentsMounted';
import Tabs from './pages/Tabs';
import NavComponent from './pages/navigation/NavComponent';
import IonModalConditionalSibling from './pages/overlay-components/IonModalConditionalSibling';
import IonModalConditional from './pages/overlay-components/IonModalConditional';
import IonModalDatetimeButton from './pages/overlay-components/IonModalDatetimeButton';
import IonPopoverNested from './pages/overlay-components/IonPopoverNested';
setupIonicReact();
@@ -37,6 +41,16 @@ const App: React.FC = () => (
<Route path="/" component={Main} />
<Route path="/overlay-hooks" component={OverlayHooks} />
<Route path="/overlay-components" component={OverlayComponents} />
<Route path="/overlay-components/nested-popover" component={IonPopoverNested} />
<Route
path="/overlay-components/modal-conditional-sibling"
component={IonModalConditionalSibling}
/>
<Route path="/overlay-components/modal-conditional" component={IonModalConditional} />
<Route
path="/overlay-components/modal-datetime-button"
component={IonModalDatetimeButton}
/>
<Route path="/keep-contents-mounted" component={KeepContentsMounted} />
<Route path="/navigation" component={NavComponent} />
<Route path="/tabs" component={Tabs} />

View File

@@ -0,0 +1,48 @@
import { IonButton, IonContent, IonModal } from '@ionic/react';
import { useRef } from 'react';
import { useState } from 'react';
/**
* Issue: https://github.com/ionic-team/ionic-framework/issues/25590
*
* Exception is thrown when IonModal is conditionally rendered inline.
*/
const IonModalConditional = () => {
const [showIonModal, setShowIonModal] = useState(false);
const [isOpen, setIsOpen] = useState(true);
const modal = useRef<HTMLIonModalElement>(null);
return (
<IonContent>
<IonButton
id="renderModalBtn"
onClick={() => {
setShowIonModal(true);
setIsOpen(true);
}}
>
Render Modal
</IonButton>
{showIonModal && (
<IonModal
ref={modal}
isOpen={isOpen}
onDidDismiss={() => {
setIsOpen(false);
setShowIonModal(false);
}}
>
<IonContent>
Modal Content
<IonButton id="dismissModalBtn" onClick={() => modal.current!.dismiss()}>
Close
</IonButton>
</IonContent>
</IonModal>
)}
</IonContent>
);
};
export default IonModalConditional;

View File

@@ -0,0 +1,38 @@
import { IonButton, IonCard, IonContent, IonModal } from '@ionic/react';
import { useRef } from 'react';
import { useState } from 'react';
/**
* Issue: https://github.com/ionic-team/ionic-framework/issues/25590
*
* Exception is thrown when adding/removing nodes that are siblings of IonModal,
* while the modal is being dismissed.
*/
const IonModalConditionalSibling = () => {
const [items, setItems] = useState<string[]>(['Item 1']);
const [isOpen, setIsOpen] = useState(true);
const modal = useRef<HTMLIonModalElement>(null);
return (
<IonContent>
{items && items.map((item) => <IonCard key={item}>Before {item}</IonCard>)}
<IonModal
ref={modal}
isOpen={isOpen}
onWillDismiss={() => {
setItems([...items, `Item ${items.length + 1}`]);
}}
onDidDismiss={() => setIsOpen(false)}
>
<IonContent>
Modal Content
<IonButton onClick={() => modal.current!.dismiss()}>Close</IonButton>
</IonContent>
</IonModal>
{items && items.map((item) => <IonCard key={item}>After {item}</IonCard>)}
</IonContent>
);
};
export default IonModalConditionalSibling;

View File

@@ -0,0 +1,51 @@
import { IonButton, IonContent, IonDatetime, IonDatetimeButton, IonModal } from '@ionic/react';
import { useRef } from 'react';
import { useState } from 'react';
const IonModalDatetimeButton = () => {
const [showIonModal, setShowIonModal] = useState(false);
const [isOpen, setIsOpen] = useState(true);
const modal = useRef<HTMLIonModalElement>(null);
return (
<IonContent>
<IonButton
id="renderModalBtn"
onClick={() => {
setShowIonModal(true);
setIsOpen(true);
}}
>
Render Modal
</IonButton>
{showIonModal && (
<IonModal
ref={modal}
isOpen={isOpen}
onDidDismiss={() => {
setIsOpen(false);
setShowIonModal(false);
}}
>
<IonContent>
Modal Content
<IonDatetimeButton datetime="startDate" />
<IonModal id="datetimeModal" keepContentsMounted={true}>
<IonDatetime
id="startDate"
preferWheel
presentation="date"
name="startDate"
showDefaultButtons
color="primary"
/>
</IonModal>
</IonContent>
</IonModal>
)}
</IonContent>
);
};
export default IonModalDatetimeButton;

View File

@@ -0,0 +1,65 @@
import {
IonButton,
IonContent,
IonIcon,
IonItem,
IonList,
IonListHeader,
IonPage,
IonPopover,
IonHeader,
IonTitle,
IonToolbar,
} from '@ionic/react';
import { arrowForward } from 'ionicons/icons';
import { useRef } from 'react';
const IonPopoverNested = () => {
const menuPopover = useRef<HTMLIonPopoverElement>(null);
const submenuPopover = useRef<HTMLIonPopoverElement>(null);
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Nested Popover</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent className="ion-padding">
<IonButton id="open">Show Popover</IonButton>
<IonPopover ref={menuPopover} id="menu-popover" trigger="open">
<IonList>
<IonListHeader>Menu Items</IonListHeader>
<IonItem>Item 1</IonItem>
<IonItem>Item 2</IonItem>
<IonItem>Item 3</IonItem>
<IonItem button id="item-4">
More
<IonIcon icon={arrowForward} slot="end" />
</IonItem>
<IonItem button id="close-menu-popover" onClick={() => menuPopover.current!.dismiss()}>
Close
</IonItem>
</IonList>
<IonPopover ref={submenuPopover} id="submenu-popover" trigger="item-4" side="right">
<IonList>
<IonListHeader>Submenu Items</IonListHeader>
<IonItem>Item 1</IonItem>
<IonItem>Item 2</IonItem>
<IonItem>Item 3</IonItem>
<IonItem
id="close-submenu-popover"
button
onClick={() => submenuPopover.current!.dismiss()}
>
Close
</IonItem>
</IonList>
</IonPopover>
</IonPopover>
</IonContent>
</IonPage>
);
};
export default IonPopoverNested;

View File

@@ -17,7 +17,7 @@ const KeepContentsMounted: React.FC = () => {
<IonButton id="open-modal" onClick={() => setShowModal(true)}>Open Modal</IonButton>
<IonButton id="open-popover" onClick={() => setShowPopover(true)}>Open Popover</IonButton>
<IonModal keepContentsMounted={true} id="default-modal" isOpen={showModal} onDidDismiss={() => setShowPopover(false)}>
<IonModal keepContentsMounted={true} id="default-modal" isOpen={showModal} onDidDismiss={() => setShowModal(false)}>
<IonContent>
<IonButton onClick={() => setShowModal(false)}>Dismiss</IonButton>
Modal Content

View File

@@ -34,3 +34,45 @@ describe('IonModal', () => {
cy.get('ion-modal div').contains('overriden value')
});
});
describe('IonModal: conditional rendering', () => {
// Issue: https://github.com/ionic-team/ionic-framework/issues/25590
it('should push a new IonCard when dismissed', () => {
cy.visit('/overlay-components/modal-conditional-sibling');
// Renders a card before and after the modal
cy.get('ion-card').should('have.length', 2);
cy.get('ion-button').click();
cy.get('ion-card').should('have.length', 4);
});
// Issue: https://github.com/ionic-team/ionic-framework/issues/25590
it('should be conditionally rendered', () => {
cy.visit('/overlay-components/modal-conditional');
cy.get('ion-modal').should('not.exist');
cy.get('ion-button#renderModalBtn').click();
cy.get('ion-modal').should('be.visible');
cy.get('ion-button#dismissModalBtn').click();
cy.get('ion-button#renderModalBtn').should('be.visible');
});
it('should display an inline modal within a modal', () => {
cy.visit('/overlay-components/modal-datetime-button');
cy.get('ion-modal').should('not.exist');
cy.get('ion-button#renderModalBtn').click();
cy.get('ion-modal').should('be.visible');
cy.get('ion-datetime-button').click();
cy.get('ion-modal#datetimeModal').should('be.visible');
cy.get('ion-datetime').should('be.visible');
});
});

View File

@@ -27,3 +27,46 @@ describe('IonPopover', () => {
cy.get('#popoverInItem').should('not.exist');
});
});
describe('IonPopover: nested', () => {
beforeEach(() => {
cy.visit('/overlay-components/nested-popover');
});
it('display nested popover', () => {
// Open the popover
cy.get('ion-button').contains('Show Popover').click();
cy.get('ion-popover ion-list-header').contains('Menu Items');
// Open the nested popover
cy.get('#item-4').click();
cy.get('ion-popover ion-list-header').contains('Submenu Items');
/**
* These tests are disabled due to: https://github.com/ionic-team/ionic-framework/issues/25324
*
* When the nested overlay is dismissed, the parent overlay is also dismissed.
*
* Dev note: This behavior may be resolved when moving overlays to open with a React Portal.
* In brief exploration, this problem did not reproduce when using a React Portal.
*/
// Close the nested popover
// cy.get('#close-submenu-popover').click();
// // The nested popover should be dismissed, but still in the DOM.
// cy.get('#submenu-popover').should('not.be.visible');
// cy.get('#submenu-popover').should('exist');
// // The parent popover should remain visible
// cy.get('#menu-popover').should('be.visible');
// Close the parent popover
cy.get('#close-menu-popover').click();
// The nested popover should not exist in the DOM.
cy.get('#submenu-popover').should('not.exist');
// The parent popover should be dismissed, but still in the DOM.
cy.get('#menu-popover').should('not.be.visible');
cy.get('#menu-popover').should('exist');
});
})

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.2](https://github.com/ionic-team/ionic/compare/v6.3.1...v6.3.2) (2022-10-17)
**Note:** Version bump only for package @ionic/vue-router
## [6.3.1](https://github.com/ionic-team/ionic/compare/v6.3.0...v6.3.1) (2022-10-12)
**Note:** Version bump only for package @ionic/vue-router

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue-router",
"version": "6.3.1",
"version": "6.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue-router",
"version": "6.3.1",
"version": "6.3.2",
"license": "MIT",
"dependencies": {
"@ionic/vue": "^6.3.1"
"@ionic/vue": "6.3.2"
},
"devDependencies": {
"@types/jest": "^28.1.1",
@@ -578,9 +578,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"dependencies": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",
@@ -588,11 +588,11 @@
}
},
"node_modules/@ionic/vue": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-6.3.1.tgz",
"integrity": "sha512-QFPF9Pshg4Ns3UDrbA+vGA1QyadIvW0r1Gct6Rv9S/eAfHMOXfAiaIqUgTy0BrFjcR+VCgiCZzg1atEvU0XvyA==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-6.3.2.tgz",
"integrity": "sha512-jvcqcVG6cgFBP+JtLW1dg8a77wYLsFpx1vcC0LK59OmFVLkEIcE6TodhXmfaJVglkXTVMbzPRvO7xIUVHydQzQ==",
"dependencies": {
"@ionic/core": "^6.3.1",
"@ionic/core": "^6.3.2",
"ionicons": "^6.0.2"
}
},
@@ -5233,9 +5233,9 @@
"dev": true
},
"@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"requires": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",
@@ -5243,11 +5243,11 @@
}
},
"@ionic/vue": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-6.3.1.tgz",
"integrity": "sha512-QFPF9Pshg4Ns3UDrbA+vGA1QyadIvW0r1Gct6Rv9S/eAfHMOXfAiaIqUgTy0BrFjcR+VCgiCZzg1atEvU0XvyA==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-6.3.2.tgz",
"integrity": "sha512-jvcqcVG6cgFBP+JtLW1dg8a77wYLsFpx1vcC0LK59OmFVLkEIcE6TodhXmfaJVglkXTVMbzPRvO7xIUVHydQzQ==",
"requires": {
"@ionic/core": "^6.3.1",
"@ionic/core": "^6.3.2",
"ionicons": "^6.0.2"
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue-router",
"version": "6.3.1",
"version": "6.3.2",
"description": "Vue Router integration for @ionic/vue",
"scripts": {
"prepublishOnly": "npm run build",
@@ -43,7 +43,7 @@
},
"homepage": "https://github.com/ionic-team/ionic#readme",
"dependencies": {
"@ionic/vue": "^6.3.1"
"@ionic/vue": "6.3.2"
},
"devDependencies": {
"@types/jest": "^28.1.1",

View File

@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.3.2](https://github.com/ionic-team/ionic/compare/v6.3.1...v6.3.2) (2022-10-17)
### Bug Fixes
* **vue:** routing components define child components ([#26107](https://github.com/ionic-team/ionic/issues/26107)) ([d60973b](https://github.com/ionic-team/ionic/commit/d60973b2449b29a982b752a98b10d2b043ecff2f))
## [6.3.1](https://github.com/ionic-team/ionic/compare/v6.3.0...v6.3.1) (2022-10-12)
**Note:** Version bump only for package @ionic/vue

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue",
"version": "6.3.1",
"version": "6.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue",
"version": "6.3.1",
"version": "6.3.2",
"license": "MIT",
"dependencies": {
"@ionic/core": "^6.3.1",
"@ionic/core": "6.3.2",
"ionicons": "^6.0.2"
},
"devDependencies": {
@@ -59,9 +59,9 @@
}
},
"node_modules/@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"dependencies": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",
@@ -768,9 +768,9 @@
}
},
"@ionic/core": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.1.tgz",
"integrity": "sha512-OpmGQQ/sGtoXW4PQRvRjYa9mTlAmAWwFMu8g/1rcdAPaTQQAlRnfZ62wYYqmChLSFlVbn6mCleeljzR9FaYrAg==",
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.3.2.tgz",
"integrity": "sha512-L4xqJyixmGApwYc5fQgGoK80wXGCrbjL8vGfeNbjYqxxP0ZIKGAhURPoMAtSTqLLK9gdhh4Mv6gw4gNKvxodPA==",
"requires": {
"@stencil/core": "^2.18.0",
"ionicons": "^6.0.3",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue",
"version": "6.3.1",
"version": "6.3.2",
"description": "Vue specific wrapper for @ionic/core",
"scripts": {
"prepublishOnly": "npm run build",
@@ -60,7 +60,7 @@
"vue-router": "^4.0.16"
},
"dependencies": {
"@ionic/core": "^6.3.1",
"@ionic/core": "6.3.2",
"ionicons": "^6.0.2"
},
"vetur": {