Merge remote-tracking branch 'origin/main' into sync-feature-10-21
14
CHANGELOG.md
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
18
angular/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
4
core/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "6.3.1",
|
||||
"version": "6.3.2",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 52 KiB |
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
57
core/src/components/footer/test/basic/footer.e2e.ts
Normal 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`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 53 KiB |
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
24
core/src/components/footer/test/fade/footer.e2e.ts
Normal 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`);
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -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>
|
||||
|
||||
BIN
core/src/components/footer/test/img.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
33
core/src/components/footer/test/scroll-target/footer.e2e.ts
Normal 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`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
4
docs/package-lock.json
generated
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"angular",
|
||||
"packages/*"
|
||||
],
|
||||
"version": "6.3.1"
|
||||
"version": "6.3.2"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
18
packages/angular-server/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
34
packages/react-router/package-lock.json
generated
@@ -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": "*"
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
18
packages/react/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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": "*"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
});
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
34
packages/vue-router/package-lock.json
generated
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
18
packages/vue/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||