merge release-6.1.9

Release 6.1.9
This commit is contained in:
Liam DeBeasi
2022-06-08 09:59:01 -04:00
committed by GitHub
375 changed files with 1447 additions and 1388 deletions

View File

@ -231,6 +231,12 @@ Before creating a pull request, please read our requirements that explains the m
2. See the [Creating a pull request from a fork](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) GitHub help article for more information.
3. Please fill out the provided Pull Request template to the best of your ability and include any issues that are related.
### Review Process for Feature PRs
The team has an internal design process for new Ionic features, which must be completed before the PR can be reviewed or merged. As a result of the design process, community feature PRs are subject to large changes. In some cases, the team may instead create a separate PR using pieces of the community PR. Either way, you will always receive co-author commit credit when the feature is merged.
To expedite the process, please ensure that all feature PRs have an associated issue created, with a clear use case for why the feature should be added to Ionic.
## Commit Message Guidelines

View File

@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.1.9](https://github.com/ionic-team/ionic-framework/compare/v6.1.8...v6.1.9) (2022-06-08)
### Bug Fixes
* **all:** ripple effect is no longer added when scrolling ([#25352](https://github.com/ionic-team/ionic-framework/issues/25352)) ([0b275af](https://github.com/ionic-team/ionic-framework/commit/0b275af5ac06f470b4d908b889f513956bf5d868)), closes [#22030](https://github.com/ionic-team/ionic-framework/issues/22030)
* **angular:** add support for Angular 14 ([#25403](https://github.com/ionic-team/ionic-framework/issues/25403)) ([122cdcc](https://github.com/ionic-team/ionic-framework/commit/122cdcc8253e46d9537105b11045fd7d9ccd8917)), closes [#25353](https://github.com/ionic-team/ionic-framework/issues/25353)
* **datetime:** emit ionChange for non-calendar picker presentation ([#25380](https://github.com/ionic-team/ionic-framework/issues/25380)) ([4e6a60b](https://github.com/ionic-team/ionic-framework/commit/4e6a60b6a42287e5091728aecb61f6097e131b83)), closes [#25375](https://github.com/ionic-team/ionic-framework/issues/25375)
* **datetime:** ensure that default month shown is always in bounds ([#25351](https://github.com/ionic-team/ionic-framework/issues/25351)) ([866d452](https://github.com/ionic-team/ionic-framework/commit/866d4528ad1b8ffa65258595d553ea934daa4add)), closes [#25320](https://github.com/ionic-team/ionic-framework/issues/25320)
* **label:** text contents will repaint on change ([#25395](https://github.com/ionic-team/ionic-framework/issues/25395)) ([52ec741](https://github.com/ionic-team/ionic-framework/commit/52ec74193b4e2478cb84a6dfea261cb2113dcbff))
## [6.1.8](https://github.com/ionic-team/ionic-framework/compare/v6.1.7...v6.1.8) (2022-06-01)

View File

@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.1.9](https://github.com/ionic-team/ionic/compare/v6.1.8...v6.1.9) (2022-06-08)
### Bug Fixes
* **angular:** add support for Angular 14 ([#25403](https://github.com/ionic-team/ionic/issues/25403)) ([122cdcc](https://github.com/ionic-team/ionic/commit/122cdcc8253e46d9537105b11045fd7d9ccd8917)), closes [#25353](https://github.com/ionic-team/ionic/issues/25353)
## [6.1.8](https://github.com/ionic-team/ionic/compare/v6.1.7...v6.1.8) (2022-06-01)
**Note:** Version bump only for package @ionic/angular

View File

@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "6.1.8",
"version": "6.1.9",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "6.1.8",
"version": "6.1.9",
"license": "MIT",
"dependencies": {
"@ionic/core": "^6.1.8",
"@ionic/core": "^6.1.9",
"jsonc-parser": "^3.0.0",
"tslib": "^2.0.0"
},
@ -1023,9 +1023,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.8.tgz",
"integrity": "sha512-EMYebwmS4UogpNrTyuCgIETKwE7XA93V7X3YacK2UArm1Xf98w3FsnfAYeMaUqY93bDxih/CSKm3NpUhVWP44A==",
"version": "6.1.9",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.9.tgz",
"integrity": "sha512-EaClsiGB/E9wPkujnrMZ71BLVcA8t6DBZu+caJMmqPLF/64S37CiyfrrMbL1UnxDWP2TXsPFH3seWl6Ek/W1bw==",
"dependencies": {
"@stencil/core": "^2.16.0",
"ionicons": "^6.0.2",
@ -7939,9 +7939,9 @@
"dev": true
},
"@ionic/core": {
"version": "6.1.8",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.8.tgz",
"integrity": "sha512-EMYebwmS4UogpNrTyuCgIETKwE7XA93V7X3YacK2UArm1Xf98w3FsnfAYeMaUqY93bDxih/CSKm3NpUhVWP44A==",
"version": "6.1.9",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.9.tgz",
"integrity": "sha512-EaClsiGB/E9wPkujnrMZ71BLVcA8t6DBZu+caJMmqPLF/64S37CiyfrrMbL1UnxDWP2TXsPFH3seWl6Ek/W1bw==",
"requires": {
"@stencil/core": "^2.16.0",
"ionicons": "^6.0.2",

View File

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

View File

@ -0,0 +1,34 @@
/**
* This class is taken directly from Angular's codebase. It can be removed once
* we remove support for < Angular 14. The replacement class will come from @angular/core.
*
* TODO: FW-1641: Remove this class once Angular 13 support is dropped.
*
*/
import { Injector, ProviderToken, InjectFlags } from '@angular/core';
/**
* An `Injector` that's part of the environment injector hierarchy, which exists outside of the
* component tree.
*
* @developerPreview
*/
export abstract class EnvironmentInjector implements Injector {
/**
* Retrieves an instance from the injector based on the provided token.
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
*/
abstract get<T>(token: ProviderToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
/**
* @deprecated from v4.0.0 use ProviderToken<T>
* @suppress {duplicate}
*/
abstract get(token: any, notFoundValue?: any): any;
abstract destroy(): void;
/**
* @internal
*/
abstract onDestroy(callback: () => void): void;
}

View File

@ -20,9 +20,11 @@ import { componentOnReady } from '@ionic/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators';
import { EnvironmentInjector } from '../../di/r3_injector';
import { AnimationBuilder } from '../../ionic-core';
import { Config } from '../../providers/config';
import { NavController } from '../../providers/nav-controller';
import { isComponentFactoryResolver } from '../../util/util';
import { StackController } from './stack-controller';
import { RouteView, getUrl } from './stack-utils';
@ -82,11 +84,11 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
constructor(
private parentContexts: ChildrenOutletContexts,
private location: ViewContainerRef,
private resolver: ComponentFactoryResolver,
@Attribute('name') name: string,
@Optional() @Attribute('tabs') tabs: string,
private config: Config,
private navCtrl: NavController,
@Optional() private environmentInjector: EnvironmentInjector,
commonLocation: Location,
elementRef: ElementRef,
router: Router,
@ -206,7 +208,10 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
}
}
activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver | null): void {
activateWith(
activatedRoute: ActivatedRoute,
resolverOrInjector?: ComponentFactoryResolver | EnvironmentInjector | null
): void {
if (this.isActivated) {
throw new Error('Cannot activate an already activated outlet');
}
@ -229,9 +234,6 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
const snapshot = (activatedRoute as any)._futureSnapshot;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const component = snapshot.routeConfig!.component as any;
resolver = resolver || this.resolver;
const factory = resolver.resolveComponentFactory(component);
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
// We create an activated route proxy object that will maintain future updates for this component
@ -240,8 +242,35 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
const activatedRouteProxy = this.createActivatedRouteProxy(component$, activatedRoute);
const injector = new OutletInjector(activatedRouteProxy, childContexts, this.location.injector);
cmpRef = this.activated = this.location.createComponent(factory, this.location.length, injector);
if (resolverOrInjector && isComponentFactoryResolver(resolverOrInjector)) {
// Backwards compatibility for Angular 13 and lower
const factory = resolverOrInjector.resolveComponentFactory(component);
cmpRef = this.activated = this.location.createComponent(factory, this.location.length, injector);
} else {
/**
* Angular 14 and higher.
*
* TODO: FW-1641: Migrate once Angular 13 support is dropped.
*
* When we drop < Angular 14, we can replace the following code with:
* ```ts
const environmentInjector = resolverOrInjector ?? this.environmentInjector;
cmpRef = this.activated = location.createComponent(component, {
index: location.length,
injector,
environmentInjector,
});
* ```
* where `this.environmentInjector` is a provider of `EnvironmentInjector` from @angular/core.
*/
const environmentInjector = resolverOrInjector ?? this.environmentInjector;
cmpRef = this.activated = this.location.createComponent(component, {
index: this.location.length,
injector,
environmentInjector,
} as any);
}
// Once the component is created we can push it to our local subject supplied to the proxy
component$.next(cmpRef.instance);

View File

@ -6,6 +6,7 @@ import {
Injectable,
InjectionToken,
Injector,
ComponentRef,
} from '@angular/core';
import {
FrameworkDelegate,
@ -16,18 +17,20 @@ import {
LIFECYCLE_WILL_UNLOAD,
} from '@ionic/core';
import { EnvironmentInjector } from '../di/r3_injector';
import { NavParams } from '../directives/navigation/nav-params';
import { isComponentFactoryResolver } from '../util/util';
@Injectable()
export class AngularDelegate {
constructor(private zone: NgZone, private appRef: ApplicationRef) {}
create(
resolver: ComponentFactoryResolver,
resolverOrInjector: ComponentFactoryResolver,
injector: Injector,
location?: ViewContainerRef
): AngularFrameworkDelegate {
return new AngularFrameworkDelegate(resolver, injector, location, this.appRef, this.zone);
return new AngularFrameworkDelegate(resolverOrInjector, injector, location, this.appRef, this.zone);
}
}
@ -36,7 +39,7 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
private elEventsMap = new WeakMap<HTMLElement, () => void>();
constructor(
private resolver: ComponentFactoryResolver,
private resolverOrInjector: ComponentFactoryResolver | EnvironmentInjector,
private injector: Injector,
private location: ViewContainerRef | undefined,
private appRef: ApplicationRef,
@ -48,7 +51,7 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
return new Promise((resolve) => {
const el = attachView(
this.zone,
this.resolver,
this.resolverOrInjector,
this.injector,
this.location,
this.appRef,
@ -85,7 +88,7 @@ export class AngularFrameworkDelegate implements FrameworkDelegate {
export const attachView = (
zone: NgZone,
resolver: ComponentFactoryResolver,
resolverOrInjector: ComponentFactoryResolver | EnvironmentInjector,
injector: Injector,
location: ViewContainerRef | undefined,
appRef: ApplicationRef,
@ -96,14 +99,29 @@ export const attachView = (
params: any,
cssClasses: string[] | undefined
): any => {
const factory = resolver.resolveComponentFactory(component);
let componentRef: ComponentRef<any>;
const childInjector = Injector.create({
providers: getProviders(params),
parent: injector,
});
const componentRef = location
? location.createComponent(factory, location.length, childInjector)
: factory.create(childInjector);
if (resolverOrInjector && isComponentFactoryResolver(resolverOrInjector)) {
// Angular 13 and lower
const factory = resolverOrInjector.resolveComponentFactory(component);
componentRef = location
? location.createComponent(factory, location.length, childInjector)
: factory.create(childInjector);
} else if (location) {
// Angular 14
const environmentInjector = resolverOrInjector;
componentRef = location.createComponent(component, {
index: location.indexOf,
injector: childInjector,
environmentInjector,
} as any);
} else {
return null;
}
const instance = componentRef.instance;
const hostElement = componentRef.location.nativeElement;

View File

@ -1,6 +1,7 @@
import { ComponentFactoryResolver, Injector, Injectable } from '@angular/core';
import { ComponentFactoryResolver, Injector, Injectable, Optional } from '@angular/core';
import { ModalOptions, modalController } from '@ionic/core';
import { EnvironmentInjector } from '../di/r3_injector';
import { OverlayBaseController } from '../util/overlay';
import { AngularDelegate } from './angular-delegate';
@ -10,7 +11,9 @@ export class ModalController extends OverlayBaseController<ModalOptions, HTMLIon
constructor(
private angularDelegate: AngularDelegate,
private resolver: ComponentFactoryResolver,
private injector: Injector
private injector: Injector,
// TODO: FW-1641: Migrate to Angular's version once Angular 13 is dropped
@Optional() private environmentInjector: EnvironmentInjector
) {
super(modalController);
}
@ -18,7 +21,7 @@ export class ModalController extends OverlayBaseController<ModalOptions, HTMLIon
create(opts: ModalOptions): Promise<HTMLIonModalElement> {
return super.create({
...opts,
delegate: this.angularDelegate.create(this.resolver, this.injector),
delegate: this.angularDelegate.create(this.resolver ?? this.environmentInjector, this.injector),
});
}
}

View File

@ -1,6 +1,7 @@
import { ComponentFactoryResolver, Injector, Injectable } from '@angular/core';
import { ComponentFactoryResolver, Injector, Injectable, Optional } from '@angular/core';
import { PopoverOptions, popoverController } from '@ionic/core';
import { EnvironmentInjector } from '../di/r3_injector';
import { OverlayBaseController } from '../util/overlay';
import { AngularDelegate } from './angular-delegate';
@ -10,7 +11,9 @@ export class PopoverController extends OverlayBaseController<PopoverOptions, HTM
constructor(
private angularDelegate: AngularDelegate,
private resolver: ComponentFactoryResolver,
private injector: Injector
private injector: Injector,
// TODO: FW-1641: Migrate to Angular's version once Angular 13 is dropped
@Optional() private environmentInjector: EnvironmentInjector
) {
super(popoverController);
}
@ -18,7 +21,7 @@ export class PopoverController extends OverlayBaseController<PopoverOptions, HTM
create(opts: PopoverOptions): Promise<HTMLIonPopoverElement> {
return super.create({
...opts,
delegate: this.angularDelegate.create(this.resolver, this.injector),
delegate: this.angularDelegate.create(this.resolver ?? this.environmentInjector, this.injector),
});
}
}

View File

@ -1,3 +1,5 @@
import { ComponentFactoryResolver } from '@angular/core';
declare const __zone_symbol__requestAnimationFrame: any;
declare const requestAnimationFrame: any;
@ -10,3 +12,7 @@ export const raf = (h: any): any => {
}
return setTimeout(h);
};
export const isComponentFactoryResolver = (item: any): item is ComponentFactoryResolver => {
return !!item.resolveComponentFactory;
};

View File

@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.1.9](https://github.com/ionic-team/ionic/compare/v6.1.8...v6.1.9) (2022-06-08)
### Bug Fixes
* **all:** ripple effect is no longer added when scrolling ([#25352](https://github.com/ionic-team/ionic/issues/25352)) ([0b275af](https://github.com/ionic-team/ionic/commit/0b275af5ac06f470b4d908b889f513956bf5d868)), closes [#22030](https://github.com/ionic-team/ionic/issues/22030)
* **datetime:** emit ionChange for non-calendar picker presentation ([#25380](https://github.com/ionic-team/ionic/issues/25380)) ([4e6a60b](https://github.com/ionic-team/ionic/commit/4e6a60b6a42287e5091728aecb61f6097e131b83)), closes [#25375](https://github.com/ionic-team/ionic/issues/25375)
* **datetime:** ensure that default month shown is always in bounds ([#25351](https://github.com/ionic-team/ionic/issues/25351)) ([866d452](https://github.com/ionic-team/ionic/commit/866d4528ad1b8ffa65258595d553ea934daa4add)), closes [#25320](https://github.com/ionic-team/ionic/issues/25320)
* **label:** text contents will repaint on change ([#25395](https://github.com/ionic-team/ionic/issues/25395)) ([52ec741](https://github.com/ionic-team/ionic/commit/52ec74193b4e2478cb84a6dfea261cb2113dcbff))
## [6.1.8](https://github.com/ionic-team/ionic/compare/v6.1.7...v6.1.8) (2022-06-01)

View File

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

View File

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

View File

@ -338,11 +338,11 @@ export namespace Components {
*/
"color"?: Color;
/**
* The number of breadcrumbs to show after the collapsed indicator. If this property exists `maxItems` will be ignored.
* The number of breadcrumbs to show after the collapsed indicator. If `itemsBeforeCollapse` + `itemsAfterCollapse` is greater than `maxItems`, the breadcrumbs will not be collapsed.
*/
"itemsAfterCollapse": number;
/**
* The number of breadcrumbs to show before the collapsed indicator. If this property exists `maxItems` will be ignored.
* The number of breadcrumbs to show before the collapsed indicator. If `itemsBeforeCollapse` + `itemsAfterCollapse` is greater than `maxItems`, the breadcrumbs will not be collapsed.
*/
"itemsBeforeCollapse": number;
/**
@ -798,7 +798,7 @@ export namespace Components {
*/
"readonly": boolean;
/**
* Resets the internal state of the datetime but does not update the value. Passing a valid ISO-8601 string will reset the state of the component to the provided date. If no value is provided, the internal state will be reset to today.
* Resets the internal state of the datetime but does not update the value. Passing a valid ISO-8601 string will reset the state of the component to the provided date. If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
*/
"reset": (startDate?: string | undefined) => Promise<void>;
/**
@ -4228,11 +4228,11 @@ declare namespace LocalJSX {
*/
"color"?: Color;
/**
* The number of breadcrumbs to show after the collapsed indicator. If this property exists `maxItems` will be ignored.
* The number of breadcrumbs to show after the collapsed indicator. If `itemsBeforeCollapse` + `itemsAfterCollapse` is greater than `maxItems`, the breadcrumbs will not be collapsed.
*/
"itemsAfterCollapse"?: number;
/**
* The number of breadcrumbs to show before the collapsed indicator. If this property exists `maxItems` will be ignored.
* The number of breadcrumbs to show before the collapsed indicator. If `itemsBeforeCollapse` + `itemsAfterCollapse` is greater than `maxItems`, the breadcrumbs will not be collapsed.
*/
"itemsBeforeCollapse"?: number;
/**

View File

@ -38,13 +38,15 @@ export class Breadcrumbs implements ComponentInterface {
/**
* The number of breadcrumbs to show before the collapsed indicator.
* If this property exists `maxItems` will be ignored.
* If `itemsBeforeCollapse` + `itemsAfterCollapse` is greater than `maxItems`,
* the breadcrumbs will not be collapsed.
*/
@Prop() itemsBeforeCollapse = 1;
/**
* The number of breadcrumbs to show after the collapsed indicator.
* If this property exists `maxItems` will be ignored.
* If `itemsBeforeCollapse` + `itemsAfterCollapse` is greater than `maxItems`,
* the breadcrumbs will not be collapsed.
*/
@Prop() itemsAfterCollapse = 1;

View File

@ -79,7 +79,14 @@
overflow: hidden;
touch-action: manipulation;
/**
* touch-action: manipulation is an alias
* for this, but WebKit has an issue
* where pointercancel events are not fired
* when scrolling: https://bugs.webkit.org/show_bug.cgi?id=240917
* Using the long form below avoids the issue.
*/
touch-action: pan-x pan-y pinch-zoom;
}
.scroll-y,

View File

@ -37,7 +37,7 @@ import {
getPreviousYear,
getStartOfWeek,
} from './utils/manipulation';
import { convertToArrayOfNumbers, getPartsFromCalendarDay, parseDate } from './utils/parse';
import { clampDate, convertToArrayOfNumbers, getPartsFromCalendarDay, parseDate } from './utils/parse';
import {
getCalendarDayState,
isDayDisabled,
@ -442,12 +442,13 @@ export class Datetime implements ComponentInterface {
@Method()
async confirm(closeOverlay = false) {
/**
* If highlightActiveParts is false, this means the datetime was inited
* without a value, and the user hasn't selected one yet. We shouldn't
* update the value in this case, since otherwise it would be mysteriously
* set to today.
* We only update the value if the presentation is not a calendar picker,
* or if `highlightActiveParts` is true; indicating that the user
* has selected a date from the calendar picker.
*
* Otherwise "today" would accidentally be set as the value.
*/
if (this.highlightActiveParts) {
if (this.highlightActiveParts || !this.isCalendarPicker) {
/**
* Prevent convertDataToISO from doing any
* kind of transformation based on timezone
@ -471,7 +472,7 @@ export class Datetime implements ComponentInterface {
/**
* Resets the internal state of the datetime but does not update the value.
* Passing a valid ISO-8601 string will reset the state of the component to the provided date.
* If no value is provided, the internal state will be reset to today.
* If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
*/
@Method()
async reset(startDate?: string) {
@ -522,6 +523,11 @@ export class Datetime implements ComponentInterface {
this.confirm();
};
private get isCalendarPicker() {
const { presentation } = this;
return presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
}
/**
* Stencil sometimes sets calendarBodyRef to null on rerender, even though
* the element is present. Query for it manually as a fallback.
@ -1077,8 +1083,8 @@ export class Datetime implements ComponentInterface {
private processValue = (value?: string | null) => {
this.highlightActiveParts = !!value;
const valueToProcess = value || getToday();
const { month, day, year, hour, minute, tzOffset } = parseDate(valueToProcess);
const valueToProcess = parseDate(value || getToday());
const { month, day, year, hour, minute, tzOffset } = clampDate(valueToProcess, this.minParts, this.maxParts);
this.setWorkingParts({
month,
@ -1087,7 +1093,7 @@ export class Datetime implements ComponentInterface {
hour,
minute,
tzOffset,
ampm: hour >= 12 ? 'pm' : 'am',
ampm: hour! >= 12 ? 'pm' : 'am',
});
this.activeParts = {
@ -1097,7 +1103,7 @@ export class Datetime implements ComponentInterface {
hour,
minute,
tzOffset,
ampm: hour >= 12 ? 'pm' : 'am',
ampm: hour! >= 12 ? 'pm' : 'am',
};
};
@ -1670,8 +1676,8 @@ export class Datetime implements ComponentInterface {
const { hours, minutes, am, pm } = generateTime(
workingParts,
use24Hour ? 'h23' : 'h12',
this.minParts,
this.maxParts,
this.value ? this.minParts : undefined,
this.value ? this.maxParts : undefined,
this.parsedHourValues,
this.parsedMinuteValues
);

View File

@ -28,6 +28,21 @@ test.describe('datetime: selecting a day', () => {
test('should not highlight a day until one is selected, with default-buttons', async ({ page }) => {
await testHighlight(page, 'custom-datetime');
});
test('should update the active day', async ({ page }) => {
await page.setContent(`
<ion-datetime show-default-buttons="true" value="2021-12-25T12:40:00.000Z"></ion-datetime>
`);
const activeDay = page.locator('ion-datetime .calendar-day-active');
expect(activeDay).toHaveText('25');
const dayBtn = page.locator('ion-datetime .calendar-day[data-day="13"][data-month="12"]');
await dayBtn.click();
await page.waitForChanges();
expect(activeDay).toHaveText('13');
});
});
test.describe('datetime: confirm date', () => {
@ -45,4 +60,97 @@ test.describe('datetime: confirm date', () => {
const valueAgain = await datetime.evaluate((el: HTMLIonDatetimeElement) => el.value);
expect(valueAgain).toBeUndefined();
});
test('should set the date value based on the selected date', async ({ page }) => {
await page.setContent(`
<button id="bind">Bind datetimeMonthDidChange event</button>
<ion-datetime value="2021-12-25T12:40:00.000Z"></ion-datetime>
<script type="module">
import { InitMonthDidChangeEvent } from '/src/components/datetime/test/utils/month-did-change-event.js';
document.querySelector('button').addEventListener('click', function() {
InitMonthDidChangeEvent();
});
</script>
`);
const datetimeMonthDidChange = await page.spyOnEvent('datetimeMonthDidChange');
const eventButton = page.locator('button#bind');
await eventButton.click();
const buttons = page.locator('ion-datetime .calendar-next-prev ion-button');
await buttons.nth(1).click();
await datetimeMonthDidChange.next();
const datetime = page.locator('ion-datetime');
await datetime.evaluate((el: HTMLIonDatetimeElement) => el.confirm());
// Value may include timezone information so we need to check
// that the value at least includes the correct date/time info.
const value = (await datetime.evaluate((el: HTMLIonDatetimeElement) => el.value))!;
expect(value.includes('2021-12-25T12:40:00')).toBe(true);
});
});
test.describe('datetime: footer', () => {
test('should render default buttons', async ({ page }) => {
await page.setContent('<ion-datetime value="2022-05-03" show-default-buttons="true"></ion-datetime>');
const cancelButton = page.locator('ion-datetime #cancel-button');
expect(cancelButton).toHaveText('Cancel');
const confirmButton = page.locator('ion-datetime #confirm-button');
expect(confirmButton).toHaveText('Done');
const datetime = page.locator('ion-datetime');
expect(await datetime.screenshot()).toMatchSnapshot(
`datetime-footer-default-buttons-${page.getSnapshotSettings()}.png`
);
});
test('should render clear button', async ({ page }) => {
await page.setContent('<ion-datetime value="2022-05-03" show-clear-button="true"></ion-datetime>');
const clearButton = page.locator('ion-datetime #clear-button');
expect(clearButton).toHaveText('Clear');
const datetime = page.locator('ion-datetime');
expect(await datetime.screenshot()).toMatchSnapshot(
`datetime-footer-clear-button-${page.getSnapshotSettings()}.png`
);
});
test('should render default and clear buttons', async ({ page }) => {
await page.setContent(
'<ion-datetime value="2022-05-03" show-default-buttons="true" show-clear-button="true"></ion-datetime>'
);
const cancelButton = page.locator('ion-datetime #cancel-button');
expect(cancelButton).toHaveText('Cancel');
const confirmButton = page.locator('ion-datetime #confirm-button');
expect(confirmButton).toHaveText('Done');
const clearButton = page.locator('ion-datetime #clear-button');
expect(clearButton).toHaveText('Clear');
const datetime = page.locator('ion-datetime');
expect(await datetime.screenshot()).toMatchSnapshot(
`datetime-footer-default-clear-buttons-${page.getSnapshotSettings()}.png`
);
});
test('should render custom buttons', async ({ page }) => {
await page.setContent(`
<ion-datetime value="2022-05-03">
<ion-buttons slot="buttons">
<ion-button id="custom-button" color="primary">Hello!</ion-button>
</ion-buttons>
</ion-datetime>
`);
const customButton = page.locator('ion-datetime #custom-button');
expect(customButton).toBeVisible();
const datetime = page.locator('ion-datetime');
expect(await datetime.screenshot()).toMatchSnapshot(
`datetime-footer-custom-buttons-${page.getSnapshotSettings()}.png`
);
});
});

View File

@ -1,144 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
describe('Footer', () => {
test('should render default buttons', async () => {
const page = await newE2EPage({
html: '<ion-datetime show-default-buttons="true"></ion-datetime>',
});
const cancelButton = await page.find('ion-datetime >>> #cancel-button');
expect(cancelButton).toEqualText('Cancel');
const confirmButton = await page.find('ion-datetime >>> #confirm-button');
expect(confirmButton).toEqualText('Done');
expect(await page.compareScreenshot()).toMatchScreenshot();
});
test('should render clear button', async () => {
const page = await newE2EPage({
html: '<ion-datetime show-clear-button="true"></ion-datetime>',
});
const clearButton = await page.find('ion-datetime >>> #clear-button');
expect(clearButton).toEqualText('Clear');
expect(await page.compareScreenshot()).toMatchScreenshot();
});
test('should render clear and default buttons', async () => {
const page = await newE2EPage({
html: '<ion-datetime show-default-buttons="true" show-clear-button="true"></ion-datetime>',
});
const cancelButton = await page.find('ion-datetime >>> #cancel-button');
expect(cancelButton).toEqualText('Cancel');
const confirmButton = await page.find('ion-datetime >>> #confirm-button');
expect(confirmButton).toEqualText('Done');
const clearButton = await page.find('ion-datetime >>> #clear-button');
expect(clearButton).toEqualText('Clear');
expect(await page.compareScreenshot()).toMatchScreenshot();
});
test('should render custom buttons', async () => {
const page = await newE2EPage({
html: `
<ion-datetime show-default-buttons="true" show-clear-button="true">
<ion-buttons slot="buttons">
<ion-button id="custom-button">Hello!</ion-button>
</ion-buttons>
</ion-datetime>
`,
});
const customButton = await page.find('ion-datetime #custom-button');
expect(customButton).not.toBeNull();
expect(await page.compareScreenshot()).toMatchScreenshot();
});
test('should render custom buttons', async () => {
const page = await newE2EPage({
html: `
<ion-datetime show-default-buttons="true" show-clear-button="true">
<ion-buttons slot="buttons">
<ion-button id="custom-button">Hello!</ion-button>
</ion-buttons>
</ion-datetime>
`,
});
const customButton = await page.find('ion-datetime #custom-button');
expect(customButton).not.toBeNull();
expect(await page.compareScreenshot()).toMatchScreenshot();
});
});
describe('datetime: selecting a day', () => {
it('should update the active day', async () => {
const page = await newE2EPage({
html: `
<ion-datetime show-default-buttons="true" value="2021-12-25T12:40:00.000Z"></ion-datetime>
`,
});
const activeDay = await page.find('ion-datetime >>> .calendar-day-active');
expect(activeDay.innerText).toEqual('25');
const dayBtn = await page.find('ion-datetime >>> .calendar-day[data-day="13"][data-month="12"]');
dayBtn.click();
await page.waitForChanges();
const newActiveDay = await page.find('ion-datetime >>> .calendar-day-active');
expect(newActiveDay.innerText).toEqual('13');
});
});
test('datetime:rtl: basic', async () => {
const page = await newE2EPage({
url: '/src/components/datetime/test/basic?ionic:_testing=true&rtl=true',
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});
describe('datetime: confirm date', () => {
test('should set the date value based on the selected date', async () => {
const page = await newE2EPage({
html: `
<button>Bind datetimeMonthDidChange event</button>
<ion-datetime value="2021-12-25T12:40:00.000Z"></ion-datetime>
<script type="module">
import { InitMonthDidChangeEvent } from '/src/components/datetime/test/utils/month-did-change-event.js';
document.querySelector('button').addEventListener('click', function() {
InitMonthDidChangeEvent();
});
</script>
`,
});
const eventButton = await page.find('button');
await eventButton.click();
const buttons = await page.findAll('ion-datetime >>> .calendar-next-prev ion-button');
await buttons[1].click();
await page.waitForEvent('datetimeMonthDidChange');
await page.$eval('ion-datetime', async (el: any) => {
await el.confirm();
});
const value = await (await page.find('ion-datetime')).getProperty('value');
expect(value).toMatch('2021-12-25T12:40:00');
});
});

View File

@ -0,0 +1,43 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('datetime: color', () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto('/src/components/datetime/test/color');
const colorSelect = page.locator('ion-select');
const darkModeToggle = page.locator('ion-checkbox');
const datetime = page.locator('ion-datetime');
await darkModeToggle.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
await page.waitForChanges();
expect(await datetime.first().screenshot()).toMatchSnapshot(
`datetime-color-default-dark-${page.getSnapshotSettings()}.png`
);
expect(await datetime.last().screenshot()).toMatchSnapshot(
`datetime-color-custom-dark-${page.getSnapshotSettings()}.png`
);
await darkModeToggle.evaluate((el: HTMLIonCheckboxElement) => (el.checked = false));
await colorSelect.evaluate((el: HTMLIonSelectElement) => (el.value = 'danger'));
await page.waitForChanges();
expect(await datetime.first().screenshot()).toMatchSnapshot(
`datetime-color-default-light-color-${page.getSnapshotSettings()}.png`
);
expect(await datetime.last().screenshot()).toMatchSnapshot(
`datetime-color-custom-light-color-${page.getSnapshotSettings()}.png`
);
await darkModeToggle.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
await page.waitForChanges();
expect(await datetime.first().screenshot()).toMatchSnapshot(
`datetime-color-default-dark-color-${page.getSnapshotSettings()}.png`
);
expect(await datetime.last().screenshot()).toMatchSnapshot(
`datetime-color-custom-dark-color-${page.getSnapshotSettings()}.png`
);
});
});

Some files were not shown because too many files have changed in this diff Show More