diff --git a/angular/src/directives/overlays/modal.ts b/angular/src/directives/overlays/modal.ts index 10f1120906..960b5d2242 100644 --- a/angular/src/directives/overlays/modal.ts +++ b/angular/src/directives/overlays/modal.ts @@ -54,6 +54,7 @@ export declare interface IonModal extends Components.IonModal { @ProxyCmp({ inputs: [ 'animated', + 'keepContentsMounted', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', @@ -62,6 +63,7 @@ export declare interface IonModal extends Components.IonModal { 'enterAnimation', 'event', 'handle', + 'handleBehavior', 'initialBreakpoint', 'isOpen', 'keyboardClose', @@ -78,9 +80,12 @@ export declare interface IonModal extends Components.IonModal { @Component({ selector: 'ion-modal', changeDetection: ChangeDetectionStrategy.OnPush, - template: `
`, + template: `
+ +
`, inputs: [ 'animated', + 'keepContentsMounted', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', @@ -89,6 +94,7 @@ export declare interface IonModal extends Components.IonModal { 'enterAnimation', 'event', 'handle', + 'handleBehavior', 'initialBreakpoint', 'isOpen', 'keyboardClose', diff --git a/angular/src/directives/overlays/popover.ts b/angular/src/directives/overlays/popover.ts index 0bcf5f7e26..aedbb97f9c 100644 --- a/angular/src/directives/overlays/popover.ts +++ b/angular/src/directives/overlays/popover.ts @@ -51,6 +51,7 @@ export declare interface IonPopover extends Components.IonPopover { 'alignment', 'animated', 'arrow', + 'keepContentsMounted', 'backdropDismiss', 'cssClass', 'dismissOnSelect', @@ -73,11 +74,12 @@ export declare interface IonPopover extends Components.IonPopover { @Component({ selector: 'ion-popover', changeDetection: ChangeDetectionStrategy.OnPush, - template: ``, + template: ``, inputs: [ 'alignment', 'animated', 'arrow', + 'keepContentsMounted', 'backdropDismiss', 'cssClass', 'dismissOnSelect', diff --git a/angular/src/directives/proxies-list.txt b/angular/src/directives/proxies-list.txt index 1ec13c5c85..06d60e7c6f 100644 --- a/angular/src/directives/proxies-list.txt +++ b/angular/src/directives/proxies-list.txt @@ -23,6 +23,7 @@ export const DIRECTIVES = [ d.IonCol, d.IonContent, d.IonDatetime, + d.IonDatetimeButton, d.IonFab, d.IonFabButton, d.IonFabList, diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts index 69aa81283b..f600ad7fde 100644 --- a/angular/src/directives/proxies.ts +++ b/angular/src/directives/proxies.ts @@ -524,14 +524,14 @@ export declare interface IonDatetime extends Components.IonDatetime { @ProxyCmp({ defineCustomElementFn: undefined, - inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'name', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'value', 'yearValues'], + inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'value', 'yearValues'], methods: ['confirm', 'reset', 'cancel'] }) @Component({ selector: 'ion-datetime', changeDetection: ChangeDetectionStrategy.OnPush, template: '', - inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'name', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'value', 'yearValues'] + inputs: ['cancelText', 'clearText', 'color', 'dayValues', 'disabled', 'doneText', 'firstDayOfWeek', 'hourCycle', 'hourValues', 'isDateEnabled', 'locale', 'max', 'min', 'minuteValues', 'mode', 'monthValues', 'multiple', 'name', 'preferWheel', 'presentation', 'readonly', 'showClearButton', 'showDefaultButtons', 'showDefaultTimeLabel', 'showDefaultTitle', 'size', 'value', 'yearValues'] }) export class IonDatetime { protected el: HTMLElement; @@ -543,6 +543,27 @@ export class IonDatetime { } +export declare interface IonDatetimeButton extends Components.IonDatetimeButton {} + +@ProxyCmp({ + defineCustomElementFn: undefined, + inputs: ['color', 'datetime', 'disabled', 'mode'] +}) +@Component({ + selector: 'ion-datetime-button', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + inputs: ['color', 'datetime', 'disabled', 'mode'] +}) +export class IonDatetimeButton { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + + export declare interface IonFab extends Components.IonFab {} @ProxyCmp({ @@ -1304,13 +1325,13 @@ mouse drag, touch gesture, or keyboard interaction. @ProxyCmp({ defineCustomElementFn: undefined, - inputs: ['color', 'debounce', 'disabled', 'dualKnobs', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] + inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] }) @Component({ selector: 'ion-range', changeDetection: ChangeDetectionStrategy.OnPush, template: '', - inputs: ['color', 'debounce', 'disabled', 'dualKnobs', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] + inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] }) export class IonRange { protected el: HTMLElement; @@ -1979,13 +2000,13 @@ export declare interface IonToggle extends Components.IonToggle { @ProxyCmp({ defineCustomElementFn: undefined, - inputs: ['checked', 'color', 'disabled', 'mode', 'name', 'value'] + inputs: ['checked', 'color', 'disabled', 'enableOnOffLabels', 'mode', 'name', 'value'] }) @Component({ selector: 'ion-toggle', changeDetection: ChangeDetectionStrategy.OnPush, template: '', - inputs: ['checked', 'color', 'disabled', 'mode', 'name', 'value'] + inputs: ['checked', 'color', 'disabled', 'enableOnOffLabels', 'mode', 'name', 'value'] }) export class IonToggle { protected el: HTMLElement; diff --git a/angular/test/test-app/e2e/src/keep-contents-mounted.spec.ts b/angular/test/test-app/e2e/src/keep-contents-mounted.spec.ts new file mode 100644 index 0000000000..a4bc99167f --- /dev/null +++ b/angular/test/test-app/e2e/src/keep-contents-mounted.spec.ts @@ -0,0 +1,60 @@ +describe('overlays - keepContentsMounted', () => { + describe('modal', () => { + it('should not mount component if false', () => { + cy.visit('/modal-inline'); + + cy.get('ion-modal ion-content').should('not.exist'); + }); + + it('should mount component if true', () => { + cy.visit('/keep-contents-mounted'); + + cy.get('ion-modal ion-content').should('exist'); + }); + + it('should keep component mounted after dismissing if true', () => { + cy.visit('/keep-contents-mounted'); + + cy.get('#open-modal').click(); + + cy.get('ion-modal ion-content').should('exist'); + + cy.get('ion-modal ion-button').click(); + + cy.get('ion-modal') + .should('not.be.visible') + .should('have.class', 'overlay-hidden'); + + cy.get('ion-modal ion-content').should('exist'); + }); + }) + describe('popover', () => { + it('should not mount component if false', () => { + cy.visit('/popover-inline'); + + cy.get('ion-popover ion-content').should('not.exist'); + }); + + it('should mount component if true', () => { + cy.visit('/keep-contents-mounted'); + + cy.get('ion-popover ion-content').should('exist'); + }); + + it('should keep component mounted after dismissing if true', () => { + cy.visit('/keep-contents-mounted'); + + cy.get('#open-popover').click(); + + cy.get('ion-popover ion-content').should('exist'); + + cy.get('ion-popover ion-button').click(); + + cy.get('ion-popover') + .should('not.be.visible') + .should('have.class', 'overlay-hidden'); + + cy.get('ion-popover ion-content').should('exist'); + }); + }); +}); diff --git a/angular/test/test-app/e2e/src/popover.spec.ts b/angular/test/test-app/e2e/src/popover.spec.ts index abf1c06fa1..17f8fe6445 100644 --- a/angular/test/test-app/e2e/src/popover.spec.ts +++ b/angular/test/test-app/e2e/src/popover.spec.ts @@ -4,10 +4,17 @@ describe('Popovers: Inline', () => { }); it('should initially have no items', () => { + cy.get('ion-button').click(); + + cy.get('ion-popover').should('be.visible'); cy.get('ion-list ion-item').should('not.exist'); }); it('should have items after 1500ms', () => { + cy.get('ion-button').click(); + + cy.get('ion-popover').should('be.visible'); + cy.wait(1500); cy.get('ion-list ion-item:nth-child(1)').should('have.text', 'A'); diff --git a/angular/test/test-app/src/app/app-routing.module.ts b/angular/test/test-app/src/app/app-routing.module.ts index be5ad94bb5..6ffdfd8d32 100644 --- a/angular/test/test-app/src/app/app-routing.module.ts +++ b/angular/test/test-app/src/app/app-routing.module.ts @@ -31,6 +31,7 @@ const routes: Routes = [ { path: 'modals', component: ModalComponent }, { path: 'modal-inline', loadChildren: () => import('./modal-inline').then(m => m.ModalInlineModule) }, { path: 'view-child', component: ViewChildComponent }, + { path: 'keep-contents-mounted', loadChildren: () => import('./keep-contents-mounted').then(m => m.OverlayAutoMountModule) }, { path: 'popover-inline', loadChildren: () => import('./popover-inline').then(m => m.PopoverInlineModule) }, { path: 'providers', component: ProvidersComponent }, { path: 'router-link', component: RouterLinkComponent }, diff --git a/angular/test/test-app/src/app/keep-contents-mounted/index.ts b/angular/test/test-app/src/app/keep-contents-mounted/index.ts new file mode 100644 index 0000000000..c949db9a98 --- /dev/null +++ b/angular/test/test-app/src/app/keep-contents-mounted/index.ts @@ -0,0 +1,2 @@ +export * from './keep-contents-mounted.component'; +export * from './keep-contents-mounted.module'; diff --git a/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted-routing.module.ts b/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted-routing.module.ts new file mode 100644 index 0000000000..e453cb212e --- /dev/null +++ b/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted-routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { OverlayKeepContentsMounted } from "."; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + component: OverlayKeepContentsMounted + } + ]) + ], + exports: [RouterModule] +}) +export class OverlayKeepContentsMountedRoutingModule { } diff --git a/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted.component.html b/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted.component.html new file mode 100644 index 0000000000..8311ecd5a7 --- /dev/null +++ b/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted.component.html @@ -0,0 +1,22 @@ + + Open Modal + Open Popover + + + + + Dismiss + Modal Content + + + + + + + + Dismiss + Popover Content + + + + diff --git a/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted.component.ts b/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted.component.ts new file mode 100644 index 0000000000..ccf0e90165 --- /dev/null +++ b/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted.component.ts @@ -0,0 +1,13 @@ +import { Component } from "@angular/core"; + +/** + * Validates that inline modals correctly mount + * inner components when keepContentsMounted is + * enabled. + */ +@Component({ + selector: 'app-keep-contents-mounted', + templateUrl: 'keep-contents-mounted.component.html' +}) +export class OverlayKeepContentsMounted { +} diff --git a/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted.module.ts b/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted.module.ts new file mode 100644 index 0000000000..265e850126 --- /dev/null +++ b/angular/test/test-app/src/app/keep-contents-mounted/keep-contents-mounted.module.ts @@ -0,0 +1,12 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { IonicModule } from "@ionic/angular"; +import { OverlayKeepContentsMountedRoutingModule } from "./keep-contents-mounted-routing.module"; +import { OverlayKeepContentsMounted } from "./keep-contents-mounted.component"; + +@NgModule({ + imports: [CommonModule, IonicModule, OverlayKeepContentsMountedRoutingModule], + declarations: [OverlayKeepContentsMounted], + exports: [OverlayKeepContentsMounted] +}) +export class OverlayAutoMountModule { } diff --git a/angular/test/test-app/src/app/popover-inline/popover-inline.component.html b/angular/test/test-app/src/app/popover-inline/popover-inline.component.html index 1de4bc20af..f4f311646c 100644 --- a/angular/test/test-app/src/app/popover-inline/popover-inline.component.html +++ b/angular/test/test-app/src/app/popover-inline/popover-inline.component.html @@ -1,4 +1,6 @@ - +Open Popover + + diff --git a/angular/test/test-app/src/app/popover-inline/popover-inline.component.ts b/angular/test/test-app/src/app/popover-inline/popover-inline.component.ts index d162795795..36e472900c 100644 --- a/angular/test/test-app/src/app/popover-inline/popover-inline.component.ts +++ b/angular/test/test-app/src/app/popover-inline/popover-inline.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component } from "@angular/core"; +import { Component } from "@angular/core"; /** * Validates that inline popovers will correctly display @@ -9,11 +9,13 @@ import { AfterViewInit, Component } from "@angular/core"; selector: 'app-popover-inline', templateUrl: 'popover-inline.component.html' }) -export class PopoverInlineComponent implements AfterViewInit { +export class PopoverInlineComponent { items: string[] = []; - ngAfterViewInit(): void { + openPopover(popover: HTMLIonPopoverElement) { + popover.present(); + setTimeout(() => { this.items = ['A', 'B', 'C', 'D']; }, 1000); diff --git a/core/api.txt b/core/api.txt index 0c5bcdcf33..8a7ed74c34 100644 --- a/core/api.txt +++ b/core/api.txt @@ -387,7 +387,9 @@ ion-datetime,prop,min,string | undefined,undefined,false,false ion-datetime,prop,minuteValues,number | number[] | string | undefined,undefined,false,false ion-datetime,prop,mode,"ios" | "md",undefined,false,false ion-datetime,prop,monthValues,number | number[] | string | undefined,undefined,false,false +ion-datetime,prop,multiple,boolean,false,false,false ion-datetime,prop,name,string,this.inputId,false,false +ion-datetime,prop,preferWheel,boolean,false,false,false ion-datetime,prop,presentation,"date" | "date-time" | "month" | "month-year" | "time" | "time-date" | "year",'date-time',false,false ion-datetime,prop,readonly,boolean,false,false,false ion-datetime,prop,showClearButton,boolean,false,false,false @@ -395,7 +397,7 @@ ion-datetime,prop,showDefaultButtons,boolean,false,false,false ion-datetime,prop,showDefaultTimeLabel,boolean,true,false,false ion-datetime,prop,showDefaultTitle,boolean,false,false,false ion-datetime,prop,size,"cover" | "fixed",'fixed',false,false -ion-datetime,prop,value,null | string | undefined,undefined,false,false +ion-datetime,prop,value,null | string | string[] | undefined,undefined,false,false ion-datetime,prop,yearValues,number | number[] | string | undefined,undefined,false,false ion-datetime,method,cancel,cancel(closeOverlay?: boolean) => Promise ion-datetime,method,confirm,confirm(closeOverlay?: boolean) => Promise @@ -408,6 +410,13 @@ ion-datetime,css-prop,--background ion-datetime,css-prop,--background-rgb ion-datetime,css-prop,--title-color +ion-datetime-button,shadow +ion-datetime-button,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,'primary',false,true +ion-datetime-button,prop,datetime,string | undefined,undefined,false,false +ion-datetime-button,prop,disabled,boolean,false,false,true +ion-datetime-button,prop,mode,"ios" | "md",undefined,false,false +ion-datetime-button,part,native + ion-fab,shadow ion-fab,prop,activated,boolean,false,false,false ion-fab,prop,edge,boolean,false,false,false @@ -772,9 +781,11 @@ ion-modal,prop,breakpoints,number[] | undefined,undefined,false,false ion-modal,prop,canDismiss,(() => Promise) | boolean | undefined,undefined,false,false ion-modal,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-modal,prop,handle,boolean | undefined,undefined,false,false +ion-modal,prop,handleBehavior,"cycle" | "none" | undefined,'none',false,false ion-modal,prop,htmlAttributes,undefined | { [key: string]: any; },undefined,false,false ion-modal,prop,initialBreakpoint,number | undefined,undefined,false,false ion-modal,prop,isOpen,boolean,false,false,false +ion-modal,prop,keepContentsMounted,boolean,false,false,false ion-modal,prop,keyboardClose,boolean,true,false,false ion-modal,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-modal,prop,mode,"ios" | "md",undefined,false,false @@ -894,6 +905,7 @@ ion-popover,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undef ion-popover,prop,event,any,undefined,false,false ion-popover,prop,htmlAttributes,undefined | { [key: string]: any; },undefined,false,false ion-popover,prop,isOpen,boolean,false,false,false +ion-popover,prop,keepContentsMounted,boolean,false,false,false ion-popover,prop,keyboardClose,boolean,true,false,false ion-popover,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-popover,prop,mode,"ios" | "md",undefined,false,false @@ -967,6 +979,7 @@ ion-radio-group,prop,value,any,undefined,false,false ion-radio-group,event,ionChange,RadioGroupChangeEventDetail,true ion-range,shadow +ion-range,prop,activeBarStart,number | undefined,undefined,false,false ion-range,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-range,prop,debounce,number,0,false,false ion-range,prop,disabled,boolean,false,false,false @@ -1413,6 +1426,7 @@ ion-toggle,shadow ion-toggle,prop,checked,boolean,false,false,false ion-toggle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-toggle,prop,disabled,boolean,false,false,false +ion-toggle,prop,enableOnOffLabels,boolean | undefined,undefined,false,false ion-toggle,prop,mode,"ios" | "md",undefined,false,false ion-toggle,prop,name,string,this.inputId,false,false ion-toggle,prop,value,null | string | undefined,'on',false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 2a8d858022..f2b1fbe72e 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -5,7 +5,7 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { AccordionGroupChangeEventDetail, ActionSheetAttributes, ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, BreadcrumbCollapsedClickEventDetail, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, LoadingAttributes, MenuChangeEventDetail, ModalAttributes, ModalBreakpointChangeEventDetail, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerAttributes, PickerButton, PickerColumn, PopoverAttributes, PopoverSize, PositionAlign, PositionReference, PositionSide, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, TriggerAction, ViewController } from "./interface"; +import { AccordionGroupChangeEventDetail, ActionSheetAttributes, ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, BreadcrumbCollapsedClickEventDetail, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DatetimePresentation, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, LoadingAttributes, MenuChangeEventDetail, ModalAttributes, ModalBreakpointChangeEventDetail, ModalHandleBehavior, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerAttributes, PickerButton, PickerColumn, PopoverAttributes, PopoverSize, PositionAlign, PositionReference, PositionSide, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, TriggerAction, ViewController } from "./interface"; import { IonicSafeString } from "./utils/sanitization"; import { AlertAttributes } from "./components/alert/alert-interface"; import { CounterFormatter } from "./components/item/item-interface"; @@ -785,14 +785,22 @@ export namespace Components { * Values used to create the list of selectable months. By default the month values range from `1` to `12`. However, to control exactly which months to display, the `monthValues` input can take a number, an array of numbers, or a string of comma separated numbers. For example, if only summer months should be shown, then this input value would be `monthValues="6,7,8"`. Note that month numbers do *not* have a zero-based index, meaning January's value is `1`, and December's is `12`. */ "monthValues"?: number[] | number | string; + /** + * If `true`, multiple dates can be selected at once. Only applies to `presentation="date"` and `preferWheel="false"`. + */ + "multiple": boolean; /** * The name of the control, which is submitted with the form data. */ "name": string; + /** + * If `true`, a wheel picker will be rendered instead of a calendar grid where possible. If `false`, a calendar grid will be rendered instead of a wheel picker where possible. A wheel picker can be rendered instead of a grid when `presentation` is one of the following values: `'date'`, `'date-time'`, or `'time-date'`. A wheel picker will always be rendered regardless of the `preferWheel` value when `presentation` is one of the following values: `'time'`, `'month'`, `'month-year'`, or `'year'`. + */ + "preferWheel": boolean; /** * Which values you want to select. `'date'` will show a calendar picker to select the month, day, and year. `'time'` will show a time picker to select the hour, minute, and (optionally) AM/PM. `'date-time'` will show the date picker first and time picker second. `'time-date'` will show the time picker first and date picker second. */ - "presentation": 'date-time' | 'time-date' | 'date' | 'time' | 'month' | 'year' | 'month-year'; + "presentation": DatetimePresentation; /** * If `true`, the datetime appears normal but is not interactive. */ @@ -822,14 +830,32 @@ export namespace Components { */ "size": 'cover' | 'fixed'; /** - * The value of the datetime as a valid ISO 8601 datetime string. + * The value of the datetime as a valid ISO 8601 datetime string. Should be an array of strings if `multiple="true"`. */ - "value"?: string | null; + "value"?: string | string[] | null; /** * Values used to create the list of selectable years. By default the year values range between the `min` and `max` datetime inputs. However, to control exactly which years to display, the `yearValues` input can take a number, an array of numbers, or string of comma separated numbers. For example, to show upcoming and recent leap years, then this input's value would be `yearValues="2024,2020,2016,2012,2008"`. */ "yearValues"?: number[] | number | string; } + interface IonDatetimeButton { + /** + * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). + */ + "color"?: Color; + /** + * The ID of the `ion-datetime` instance associated with the datetime button. + */ + "datetime"?: string; + /** + * If `true`, the user cannot interact with the button. + */ + "disabled": boolean; + /** + * The mode determines which platform styles to use. + */ + "mode"?: "ios" | "md"; + } interface IonFab { /** * If `true`, both the `ion-fab-button` and all `ion-fab-list` inside `ion-fab` will become active. That means `ion-fab-button` will become a `close` icon and `ion-fab-list` will become visible. @@ -1554,6 +1580,10 @@ export namespace Components { * The horizontal line that displays at the top of a sheet modal. It is `true` by default when setting the `breakpoints` and `initialBreakpoint` properties. */ "handle"?: boolean; + /** + * The interaction behavior for the sheet modal when the handle is pressed. Defaults to `"none"`, which means the modal will not change size or position when the handle is pressed. Set to `"cycle"` to let the modal cycle between available breakpoints when pressed. Handle behavior is unavailable when the `handle` property is set to `false` or when the `breakpoints` property is not set (using a fullscreen or card modal). + */ + "handleBehavior"?: ModalHandleBehavior; "hasController": boolean; /** * Additional attributes to pass to the modal. @@ -1567,6 +1597,10 @@ export namespace Components { * If `true`, the modal will open. If `false`, the modal will close. Use this if you need finer grained control over presentation, otherwise just use the modalController or the `trigger` property. Note: `isOpen` will not automatically be set back to `false` when the modal dismisses. You will need to do that in your code. */ "isOpen": boolean; + /** + * If `true`, the component passed into `ion-modal` will automatically be mounted when the modal is created. The component will remain mounted even when the modal is dismissed. However, the component will be destroyed when the modal is destroyed. This property is not reactive and should only be used when initially creating a modal. Note: This feature only applies to inline modals in JavaScript frameworks such as Angular, React, and Vue. + */ + "keepContentsMounted": boolean; /** * If `true`, the keyboard will be automatically dismissed when the overlay is presented. */ @@ -1936,6 +1970,10 @@ export namespace Components { * If `true`, the popover will open. If `false`, the popover will close. Use this if you need finer grained control over presentation, otherwise just use the popoverController or the `trigger` property. Note: `isOpen` will not automatically be set back to `false` when the popover dismisses. You will need to do that in your code. */ "isOpen": boolean; + /** + * If `true`, the component passed into `ion-popover` will automatically be mounted when the popover is created. The component will remain mounted even when the popover is dismissed. However, the component will be destroyed when the popover is destroyed. This property is not reactive and should only be used when initially creating a popover. Note: This feature only applies to inline popovers in JavaScript frameworks such as Angular, React, and Vue. + */ + "keepContentsMounted": boolean; /** * If `true`, the keyboard will be automatically dismissed when the overlay is presented. */ @@ -2060,6 +2098,10 @@ export namespace Components { "value"?: any | null; } interface IonRange { + /** + * The start position of the range active bar. This feature is only available with a single knob (dualKnobs="false"). Valid values are greater than or equal to the min value and less than or equal to the max value. + */ + "activeBarStart"?: number; /** * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). */ @@ -2967,6 +3009,10 @@ export namespace Components { * If `true`, the user cannot interact with the toggle. */ "disabled": boolean; + /** + * Enables the on/off accessibility switch labels within the toggle. + */ + "enableOnOffLabels": boolean | undefined; /** * The mode determines which platform styles to use. */ @@ -3385,6 +3431,12 @@ declare global { prototype: HTMLIonDatetimeElement; new (): HTMLIonDatetimeElement; }; + interface HTMLIonDatetimeButtonElement extends Components.IonDatetimeButton, HTMLStencilElement { + } + var HTMLIonDatetimeButtonElement: { + prototype: HTMLIonDatetimeButtonElement; + new (): HTMLIonDatetimeButtonElement; + }; interface HTMLIonFabElement extends Components.IonFab, HTMLStencilElement { } var HTMLIonFabElement: { @@ -3829,6 +3881,7 @@ declare global { "ion-col": HTMLIonColElement; "ion-content": HTMLIonContentElement; "ion-datetime": HTMLIonDatetimeElement; + "ion-datetime-button": HTMLIonDatetimeButtonElement; "ion-fab": HTMLIonFabElement; "ion-fab-button": HTMLIonFabButtonElement; "ion-fab-list": HTMLIonFabListElement; @@ -4688,6 +4741,10 @@ declare namespace LocalJSX { * Values used to create the list of selectable months. By default the month values range from `1` to `12`. However, to control exactly which months to display, the `monthValues` input can take a number, an array of numbers, or a string of comma separated numbers. For example, if only summer months should be shown, then this input value would be `monthValues="6,7,8"`. Note that month numbers do *not* have a zero-based index, meaning January's value is `1`, and December's is `12`. */ "monthValues"?: number[] | number | string; + /** + * If `true`, multiple dates can be selected at once. Only applies to `presentation="date"` and `preferWheel="false"`. + */ + "multiple"?: boolean; /** * The name of the control, which is submitted with the form data. */ @@ -4708,14 +4765,22 @@ declare namespace LocalJSX { * Emitted when the datetime has focus. */ "onIonFocus"?: (event: IonDatetimeCustomEvent) => void; + /** + * Emitted when componentDidRender is fired. + */ + "onIonRender"?: (event: IonDatetimeCustomEvent) => void; /** * Emitted when the styles change. */ "onIonStyle"?: (event: IonDatetimeCustomEvent) => void; + /** + * If `true`, a wheel picker will be rendered instead of a calendar grid where possible. If `false`, a calendar grid will be rendered instead of a wheel picker where possible. A wheel picker can be rendered instead of a grid when `presentation` is one of the following values: `'date'`, `'date-time'`, or `'time-date'`. A wheel picker will always be rendered regardless of the `preferWheel` value when `presentation` is one of the following values: `'time'`, `'month'`, `'month-year'`, or `'year'`. + */ + "preferWheel"?: boolean; /** * Which values you want to select. `'date'` will show a calendar picker to select the month, day, and year. `'time'` will show a time picker to select the hour, minute, and (optionally) AM/PM. `'date-time'` will show the date picker first and time picker second. `'time-date'` will show the time picker first and date picker second. */ - "presentation"?: 'date-time' | 'time-date' | 'date' | 'time' | 'month' | 'year' | 'month-year'; + "presentation"?: DatetimePresentation; /** * If `true`, the datetime appears normal but is not interactive. */ @@ -4741,14 +4806,32 @@ declare namespace LocalJSX { */ "size"?: 'cover' | 'fixed'; /** - * The value of the datetime as a valid ISO 8601 datetime string. + * The value of the datetime as a valid ISO 8601 datetime string. Should be an array of strings if `multiple="true"`. */ - "value"?: string | null; + "value"?: string | string[] | null; /** * Values used to create the list of selectable years. By default the year values range between the `min` and `max` datetime inputs. However, to control exactly which years to display, the `yearValues` input can take a number, an array of numbers, or string of comma separated numbers. For example, to show upcoming and recent leap years, then this input's value would be `yearValues="2024,2020,2016,2012,2008"`. */ "yearValues"?: number[] | number | string; } + interface IonDatetimeButton { + /** + * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). + */ + "color"?: Color; + /** + * The ID of the `ion-datetime` instance associated with the datetime button. + */ + "datetime"?: string; + /** + * If `true`, the user cannot interact with the button. + */ + "disabled"?: boolean; + /** + * The mode determines which platform styles to use. + */ + "mode"?: "ios" | "md"; + } interface IonFab { /** * If `true`, both the `ion-fab-button` and all `ion-fab-list` inside `ion-fab` will become active. That means `ion-fab-button` will become a `close` icon and `ion-fab-list` will become visible. @@ -5467,6 +5550,10 @@ declare namespace LocalJSX { * The horizontal line that displays at the top of a sheet modal. It is `true` by default when setting the `breakpoints` and `initialBreakpoint` properties. */ "handle"?: boolean; + /** + * The interaction behavior for the sheet modal when the handle is pressed. Defaults to `"none"`, which means the modal will not change size or position when the handle is pressed. Set to `"cycle"` to let the modal cycle between available breakpoints when pressed. Handle behavior is unavailable when the `handle` property is set to `false` or when the `breakpoints` property is not set (using a fullscreen or card modal). + */ + "handleBehavior"?: ModalHandleBehavior; "hasController"?: boolean; /** * Additional attributes to pass to the modal. @@ -5480,6 +5567,10 @@ declare namespace LocalJSX { * If `true`, the modal will open. If `false`, the modal will close. Use this if you need finer grained control over presentation, otherwise just use the modalController or the `trigger` property. Note: `isOpen` will not automatically be set back to `false` when the modal dismisses. You will need to do that in your code. */ "isOpen"?: boolean; + /** + * If `true`, the component passed into `ion-modal` will automatically be mounted when the modal is created. The component will remain mounted even when the modal is dismissed. However, the component will be destroyed when the modal is destroyed. This property is not reactive and should only be used when initially creating a modal. Note: This feature only applies to inline modals in JavaScript frameworks such as Angular, React, and Vue. + */ + "keepContentsMounted"?: boolean; /** * If `true`, the keyboard will be automatically dismissed when the overlay is presented. */ @@ -5771,6 +5862,10 @@ declare namespace LocalJSX { * If `true`, the popover will open. If `false`, the popover will close. Use this if you need finer grained control over presentation, otherwise just use the popoverController or the `trigger` property. Note: `isOpen` will not automatically be set back to `false` when the popover dismisses. You will need to do that in your code. */ "isOpen"?: boolean; + /** + * If `true`, the component passed into `ion-popover` will automatically be mounted when the popover is created. The component will remain mounted even when the popover is dismissed. However, the component will be destroyed when the popover is destroyed. This property is not reactive and should only be used when initially creating a popover. Note: This feature only applies to inline popovers in JavaScript frameworks such as Angular, React, and Vue. + */ + "keepContentsMounted"?: boolean; /** * If `true`, the keyboard will be automatically dismissed when the overlay is presented. */ @@ -5925,6 +6020,10 @@ declare namespace LocalJSX { "value"?: any | null; } interface IonRange { + /** + * The start position of the range active bar. This feature is only available with a single knob (dualKnobs="false"). Valid values are greater than or equal to the min value and less than or equal to the max value. + */ + "activeBarStart"?: number; /** * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). */ @@ -6903,6 +7002,10 @@ declare namespace LocalJSX { * If `true`, the user cannot interact with the toggle. */ "disabled"?: boolean; + /** + * Enables the on/off accessibility switch labels within the toggle. + */ + "enableOnOffLabels"?: boolean | undefined; /** * The mode determines which platform styles to use. */ @@ -7021,6 +7124,7 @@ declare namespace LocalJSX { "ion-col": IonCol; "ion-content": IonContent; "ion-datetime": IonDatetime; + "ion-datetime-button": IonDatetimeButton; "ion-fab": IonFab; "ion-fab-button": IonFabButton; "ion-fab-list": IonFabList; @@ -7120,6 +7224,7 @@ declare module "@stencil/core" { "ion-col": LocalJSX.IonCol & JSXBase.HTMLAttributes; "ion-content": LocalJSX.IonContent & JSXBase.HTMLAttributes; "ion-datetime": LocalJSX.IonDatetime & JSXBase.HTMLAttributes; + "ion-datetime-button": LocalJSX.IonDatetimeButton & JSXBase.HTMLAttributes; "ion-fab": LocalJSX.IonFab & JSXBase.HTMLAttributes; "ion-fab-button": LocalJSX.IonFabButton & JSXBase.HTMLAttributes; "ion-fab-list": LocalJSX.IonFabList & JSXBase.HTMLAttributes; diff --git a/core/src/components/accordion/test/standalone/accordion.e2e.ts b/core/src/components/accordion/test/standalone/accordion.e2e.ts index 46470182e8..48bb7327ba 100644 --- a/core/src/components/accordion/test/standalone/accordion.e2e.ts +++ b/core/src/components/accordion/test/standalone/accordion.e2e.ts @@ -11,7 +11,8 @@ test.describe('accordion: standalone', () => { expect(results.violations).toEqual([]); }); - test('should not have visual regressions', async ({ page }) => { + // TODO(FW-1842) Re-enable when flakiness has been addressed. + test.skip('should not have visual regressions', async ({ page }) => { await page.goto(`/src/components/accordion/test/standalone`); expect(await page.screenshot({ fullPage: true })).toMatchSnapshot( diff --git a/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-ios-ltr-Mobile-Safari-linux.png b/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-ios-ltr-Mobile-Safari-linux.png index 497cfb1a56..1c985494d6 100644 Binary files a/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-ios-rtl-Mobile-Safari-linux.png b/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-ios-rtl-Mobile-Safari-linux.png index 91980dc95c..fe09385354 100644 Binary files a/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-md-rtl-Mobile-Safari-linux.png b/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-md-rtl-Mobile-Safari-linux.png index 2b21eb8dad..10fd895d34 100644 Binary files a/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-md-rtl-Mobile-Safari-linux.png and b/core/src/components/accordion/test/standalone/accordion.e2e.ts-snapshots/accordion-standalone-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/datetime-button.scss b/core/src/components/datetime-button/datetime-button.scss new file mode 100644 index 0000000000..3a87b88128 --- /dev/null +++ b/core/src/components/datetime-button/datetime-button.scss @@ -0,0 +1,50 @@ +@import "../../themes/ionic.globals"; + +// Datetime Button +// -------------------------------------------------- + +:host { + display: flex; + + align-items: center; + + justify-content: center; +} + +:host button { + @include border-radius(8px); + @include padding(6px, 12px, 6px, 12px); + @include margin(0px, 2px, 0px, 2px); + + position: relative; + + transition: 150ms color ease-in-out; + + border: none; + + background: var(--ion-color-step-300, #edeef0); + + color: $text-color; + + font-family: inherit; + font-size: inherit; + + cursor: pointer; + + appearance: none; + + overflow: hidden; +} + +:host(.time-active) #time-button, +:host(.date-active) #date-button { + color: current-color(base); +} + +:host(.datetime-button-disabled) { + pointer-events: none; +} + +:host(.datetime-button-disabled) button { + opacity: 0.4; +} diff --git a/core/src/components/datetime-button/datetime-button.tsx b/core/src/components/datetime-button/datetime-button.tsx new file mode 100644 index 0000000000..a5536f4738 --- /dev/null +++ b/core/src/components/datetime-button/datetime-button.tsx @@ -0,0 +1,414 @@ +import type { ComponentInterface } from '@stencil/core'; +import { Component, Element, Host, Prop, State, h } from '@stencil/core'; + +import { getIonMode } from '../../global/ionic-global'; +import type { Color, DatetimePresentation, DatetimeParts } from '../../interface'; +import { componentOnReady, addEventListener } from '../../utils/helpers'; +import { printIonError, printIonWarning } from '../../utils/logging'; +import { createColorClasses } from '../../utils/theme'; +import { getToday } from '../datetime/utils/data'; +import { getMonthAndYear, getMonthDayAndYear, getLocalizedDateTime, getLocalizedTime } from '../datetime/utils/format'; +import { is24Hour } from '../datetime/utils/helpers'; +import { parseDate } from '../datetime/utils/parse'; +/** + * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. + * + * @slot date-target - Content displayed inside of the date button. + * @slot time-target - Content displayed inside of the time button. + * + * @part native - The native HTML button that wraps the slotted text. + */ +@Component({ + tag: 'ion-datetime-button', + styleUrls: { + ios: 'datetime-button.scss', + md: 'datetime-button.scss', + }, + shadow: true, +}) +export class DatetimeButton implements ComponentInterface { + private datetimeEl: HTMLIonDatetimeElement | null = null; + private overlayEl: HTMLIonModalElement | HTMLIonPopoverElement | null = null; + private dateTargetEl: HTMLElement | undefined; + private timeTargetEl: HTMLElement | undefined; + + @Element() el!: HTMLIonDatetimeButtonElement; + + @State() datetimePresentation?: DatetimePresentation = 'date-time'; + @State() dateText?: string; + @State() timeText?: string; + @State() datetimeActive = false; + @State() selectedButton?: 'date' | 'time'; + + /** + * The color to use from your application's color palette. + * Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. + * For more information on colors, see [theming](/docs/theming/basics). + */ + @Prop({ reflect: true }) color?: Color = 'primary'; + + /** + * If `true`, the user cannot interact with the button. + */ + @Prop({ reflect: true }) disabled = false; + + /** + * The ID of the `ion-datetime` instance + * associated with the datetime button. + */ + @Prop() datetime?: string; + + async componentWillLoad() { + const { datetime } = this; + if (!datetime) { + printIonError( + 'An ID associated with an ion-datetime instance is required for ion-datetime-button to function properly.', + this.el + ); + return; + } + + const datetimeEl = (this.datetimeEl = document.getElementById(datetime) as HTMLIonDatetimeElement | null); + if (!datetimeEl) { + printIonError(`No ion-datetime instance found for ID '${datetime}'.`, this.el); + return; + } + + /** + * Since the datetime can be used in any context (overlays, accordion, etc) + * we track when it is visible to determine when it is active. + * This informs which button is highlighted as well as the + * aria-expanded state. + */ + const io = new IntersectionObserver( + (entries: IntersectionObserverEntry[]) => { + const ev = entries[0]; + this.datetimeActive = ev.isIntersecting; + }, + { + threshold: 0.01, + } + ); + + io.observe(datetimeEl); + + /** + * Get a reference to any modal/popover + * the datetime is being used in so we can + * correctly size it when it is presented. + */ + const overlayEl = (this.overlayEl = datetimeEl.closest('ion-modal, ion-popover')); + + /** + * The .ion-datetime-button-overlay class contains + * styles that allow any modal/popover to be + * sized according to the dimensions of the datetime. + * If developers want a smaller/larger overlay all they need + * to do is change the width/height of the datetime. + * Additionally, this lets us avoid having to set + * explicit widths on each variant of datetime. + */ + if (overlayEl) { + overlayEl.classList.add('ion-datetime-button-overlay'); + } + + componentOnReady(datetimeEl, () => { + const datetimePresentation = (this.datetimePresentation = datetimeEl.presentation || 'date-time'); + + /** + * Set the initial display + * in the rendered buttons. + * + * From there, we need to listen + * for ionChange to be emitted + * from datetime so we know when + * to re-render the displayed + * text in the buttons. + */ + this.setDateTimeText(); + addEventListener(datetimeEl, 'ionChange', this.setDateTimeText); + + /** + * Configure the initial selected button + * in the event that the datetime is displayed + * without clicking one of the datetime buttons. + * For example, a datetime could be expanded + * in an accordion. In this case users only + * need to click the accordion header to show + * the datetime. + */ + switch (datetimePresentation) { + case 'date-time': + case 'date': + case 'month-year': + case 'month': + case 'year': + this.selectedButton = 'date'; + break; + case 'time-date': + case 'time': + this.selectedButton = 'time'; + break; + } + }); + } + + /** + * Check the value property on the linked + * ion-datetime and then format it according + * to the locale specified on ion-datetime. + */ + private setDateTimeText = () => { + const { datetimeEl, datetimePresentation } = this; + + if (!datetimeEl) { + return; + } + + const { value, locale, hourCycle, preferWheel, multiple } = datetimeEl; + + if (multiple) { + printIonWarning( + `Multi-date selection cannot be used with ion-datetime-button. + +Please upvote https://github.com/ionic-team/ionic-framework/issues/25668 if you are interested in seeing this functionality added. + `, + this.el + ); + return; + } + + /** + * Both ion-datetime and ion-datetime-button default + * to today's date and time if no value is set. + */ + const parsedDatetime = parseDate(value || getToday()) as DatetimeParts; + const use24Hour = is24Hour(locale, hourCycle); + + // TODO(FW-1865) - Remove once FW-1831 is fixed. + parsedDatetime.tzOffset = undefined; + + this.dateText = this.timeText = undefined; + + switch (datetimePresentation) { + case 'date-time': + case 'time-date': + const dateText = getMonthDayAndYear(locale, parsedDatetime); + const timeText = getLocalizedTime(locale, parsedDatetime, use24Hour); + if (preferWheel) { + this.dateText = `${dateText} ${timeText}`; + } else { + this.dateText = dateText; + this.timeText = timeText; + } + break; + case 'date': + this.dateText = getMonthDayAndYear(locale, parsedDatetime); + break; + case 'time': + this.timeText = getLocalizedTime(locale, parsedDatetime, use24Hour); + break; + case 'month-year': + this.dateText = getMonthAndYear(locale, parsedDatetime); + break; + case 'month': + this.dateText = getLocalizedDateTime(locale, parsedDatetime, { month: 'long' }); + break; + case 'year': + this.dateText = getLocalizedDateTime(locale, parsedDatetime, { year: 'numeric' }); + break; + } + }; + + /** + * Waits for the ion-datetime to re-render. + * This is needed in order to correctly position + * a popover relative to the trigger element. + */ + private waitForDatetimeChanges = async () => { + const { datetimeEl } = this; + if (!datetimeEl) { + return Promise.resolve(); + } + + return new Promise((resolve) => { + addEventListener(datetimeEl, 'ionRender', resolve, { once: true }); + }); + }; + + private handleDateClick = async (ev: Event) => { + const { datetimeEl, datetimePresentation } = this; + + if (!datetimeEl) { + return; + } + + let needsPresentationChange = false; + + /** + * When clicking the date button, + * we need to make sure that only a date + * picker is displayed. For presentation styles + * that display content other than a date picker, + * we need to update the presentation style. + */ + switch (datetimePresentation) { + case 'date-time': + case 'time-date': + const needsChange = datetimeEl.presentation !== 'date'; + /** + * The date+time wheel picker + * shows date and time together, + * so do not adjust the presentation + * in that case. + */ + if (!datetimeEl.preferWheel && needsChange) { + datetimeEl.presentation = 'date'; + needsPresentationChange = true; + } + break; + } + + /** + * Track which button was clicked + * so that it can have the correct + * activated styles applied when + * the modal/popover containing + * the datetime is opened. + */ + this.selectedButton = 'date'; + + this.presentOverlay(ev, needsPresentationChange, this.dateTargetEl); + }; + + private handleTimeClick = (ev: Event) => { + const { datetimeEl, datetimePresentation } = this; + + if (!datetimeEl) { + return; + } + + let needsPresentationChange = false; + + /** + * When clicking the time button, + * we need to make sure that only a time + * picker is displayed. For presentation styles + * that display content other than a time picker, + * we need to update the presentation style. + */ + switch (datetimePresentation) { + case 'date-time': + case 'time-date': + const needsChange = datetimeEl.presentation !== 'time'; + if (needsChange) { + datetimeEl.presentation = 'time'; + needsPresentationChange = true; + } + break; + } + + /** + * Track which button was clicked + * so that it can have the correct + * activated styles applied when + * the modal/popover containing + * the datetime is opened. + */ + this.selectedButton = 'time'; + + this.presentOverlay(ev, needsPresentationChange, this.timeTargetEl); + }; + + /** + * If the datetime is presented in an + * overlay, the datetime and overlay + * should be appropriately sized. + * These classes provide default sizing values + * that developers can customize. + * The goal is to provide an overlay that is + * reasonably sized with a datetime that + * fills the entire container. + */ + private presentOverlay = async (ev: Event, needsPresentationChange: boolean, triggerEl?: HTMLElement) => { + const { overlayEl } = this; + + if (!overlayEl) { + return; + } + + if (overlayEl.tagName === 'ION-POPOVER') { + /** + * When the presentation on datetime changes, + * we need to wait for the component to re-render + * otherwise the computed width/height of the + * popover content will be wrong, causing + * the popover to not align with the trigger element. + */ + + if (needsPresentationChange) { + await this.waitForDatetimeChanges(); + } + + /** + * We pass the trigger button element + * so that the popover aligns with the individual + * button that was clicked, not the component container. + */ + (overlayEl as HTMLIonPopoverElement).present({ + ...ev, + detail: { + ionShadowTarget: triggerEl, + }, + } as CustomEvent); + } else { + overlayEl.present(); + } + }; + + render() { + const { color, dateText, timeText, selectedButton, datetimeActive, disabled } = this; + + const mode = getIonMode(this); + + return ( + + {dateText && ( + + )} + + {timeText && ( + + )} + + ); + } +} diff --git a/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts b/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts new file mode 100644 index 0000000000..6654ddf87b --- /dev/null +++ b/core/src/components/datetime-button/test/basic/datetime-button.e2e.ts @@ -0,0 +1,186 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +test.describe('datetime-button: switching to correct view', () => { + test.beforeEach(async ({ page }, testInfo) => { + test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); + test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); + + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + }); + test('should switch to a date-only view when the date button is clicked', async ({ page }) => { + const datetime = page.locator('ion-datetime'); + expect(datetime).toHaveJSProperty('presentation', 'date-time'); + + await page.locator('#date-button').click(); + + expect(datetime).toHaveJSProperty('presentation', 'date'); + }); + test('should switch to a time-only view when the time button is clicked', async ({ page }) => { + const datetime = page.locator('ion-datetime'); + expect(datetime).toHaveJSProperty('presentation', 'date-time'); + + await page.locator('#time-button').click(); + + expect(datetime).toHaveJSProperty('presentation', 'time'); + }); +}); + +test.describe('datetime-button: labels', () => { + // eslint-disable-next-line no-empty-pattern + test.beforeEach(({}, testInfo) => { + test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); + test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); + }); + test('should set date and time labels in separate buttons', async ({ page }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + await expect(page.locator('#date-button')).toContainText('Jan 1, 2022'); + await expect(page.locator('#time-button')).toContainText('6:30 AM'); + }); + test('should set only month and year', async ({ page }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + await expect(page.locator('#date-button')).toContainText('January 2022'); + await expect(page.locator('#time-button')).toBeHidden(); + }); + test('should set only year', async ({ page }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + await expect(page.locator('#date-button')).toContainText('2022'); + await expect(page.locator('#time-button')).toBeHidden(); + }); + test('should set only month', async ({ page }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + await expect(page.locator('#date-button')).toContainText('January'); + await expect(page.locator('#time-button')).toBeHidden(); + }); + test('should set only time', async ({ page }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + await expect(page.locator('#time-button')).toContainText('6:30 AM'); + await expect(page.locator('#date-button')).toBeHidden(); + }); + test('should update the label when the value of the datetime changes', async ({ page }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + const datetime = page.locator('ion-datetime'); + const dateTarget = page.locator('#date-button'); + + await expect(dateTarget).toContainText('Jan 1, 2022'); + + await datetime.evaluate((el: HTMLIonDatetimeElement) => (el.value = '2023-05-10')); + await page.waitForChanges(); + + await expect(dateTarget).toContainText('May 10, 2023'); + }); +}); + +test.describe('datetime-button: locale', () => { + // eslint-disable-next-line no-empty-pattern + test.beforeEach(({}, testInfo) => { + test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); + test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); + }); + test('should use the same locale as datetime', async ({ page }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + /** + * The entire text reads 1 ene 2022, but some browsers will add + * a period after "ene". Just checking ene allows us to verify the + * behavior while avoiding these cross browser differences. + */ + await expect(page.locator('#date-button')).toContainText(/ene/); + await expect(page.locator('#time-button')).toContainText('6:30'); + }); + test('should respect hour cycle even if different from locale default', async ({ page }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + await expect(page.locator('#time-button')).toContainText('16:30'); + }); + test('should ignore the timezone when selecting a date', async ({ page }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + const timeTarget = page.locator('#time-button'); + await expect(timeTarget).toContainText('6:30'); + + const firstOfMonth = page.locator('ion-datetime .calendar-day[data-month="1"][data-day="1"]'); + await firstOfMonth.click(); + await page.waitForChanges(); + + await expect(timeTarget).toContainText('6:30'); + }); +}); + +test.describe('datetime-button: wheel', () => { + // eslint-disable-next-line no-empty-pattern + test.beforeEach(({}, testInfo) => { + test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); + test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); + }); + test('should only show a single date button when presentation="date-time" and prefer-wheel="true"', async ({ + page, + }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + await expect(page.locator('#date-button')).toContainText('Jan 1, 2022 6:30 AM'); + await expect(page.locator('#time-button')).not.toBeVisible(); + }); + test('should only show a single date button when presentation="time-date" and prefer-wheel="true"', async ({ + page, + }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + await expect(page.locator('#date-button')).toContainText('Jan 1, 2022 6:30 AM'); + await expect(page.locator('#time-button')).not.toBeVisible(); + }); +}); diff --git a/core/src/components/datetime-button/test/basic/index.html b/core/src/components/datetime-button/test/basic/index.html new file mode 100644 index 0000000000..c446544a0a --- /dev/null +++ b/core/src/components/datetime-button/test/basic/index.html @@ -0,0 +1,222 @@ + + + + + Datetime Button - Basic + + + + + + + + + + + + Datetime Button - Basic + + + +
+
+

Date/Time

+ + + Start Date + + + + + + +
+ +
+

Date Only

+ + + Start Date + + + + + + +
+ +
+

Time Only

+ + + Start Time + + + + + + +
+
+

Time Only (24 hour)

+ + + Start Time + + + + + + +
+
+

Time/Date

+ + + Start Date + + + + + + +
+
+

Month

+ + + Start Date + + + + + + +
+
+

Month/Year

+ + + Start Date + + + + + + +
+
+

Year

+ + + Start Date + + + + + + +
+
+

preferWheel / date

+ + + Start Date + + + + + + +
+ +
+

preferWheel / date-time

+ + + Start Date + + + + + + +
+
+
+
+ + diff --git a/core/src/components/datetime-button/test/buttons/index.html b/core/src/components/datetime-button/test/buttons/index.html new file mode 100644 index 0000000000..2b6f1f7bcc --- /dev/null +++ b/core/src/components/datetime-button/test/buttons/index.html @@ -0,0 +1,49 @@ + + + + + Datetime Button - Custom Buttons + + + + + + + + + + + Datetime Button - Custom Buttons + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts new file mode 100644 index 0000000000..c9e23913a6 --- /dev/null +++ b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts @@ -0,0 +1,30 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +test.describe('datetime-button: disabled buttons', () => { + test('buttons should not be enabled when component is disabled', async ({ page }, testInfo) => { + test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); + test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); + + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + await expect(page.locator('#date-button')).toBeDisabled(); + await expect(page.locator('#time-button')).toBeDisabled(); + }); + test('buttons should visually be disabled', async ({ page }) => { + await page.setContent(` + + + `); + await page.waitForSelector('.datetime-ready'); + + const datetimeButton = page.locator('ion-datetime-button'); + expect(await datetimeButton.screenshot()).toMatchSnapshot( + `datetime-button-disabled-${page.getSnapshotSettings()}.png` + ); + }); +}); diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..ea6feb18f4 Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..4d16e9d498 Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..38a885fa46 Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a86e4b54a1 Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..acbb4da0db Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..bfc90a5912 Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..0063e48b2f Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..d73f47ad46 Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..a8ebc64e22 Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..b10eff5574 Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..8f9e8b1931 Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..f70372d833 Binary files /dev/null and b/core/src/components/datetime-button/test/disabled/datetime-button.e2e.ts-snapshots/datetime-button-disabled-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/disabled/index.html b/core/src/components/datetime-button/test/disabled/index.html new file mode 100644 index 0000000000..a6bd1dc7ea --- /dev/null +++ b/core/src/components/datetime-button/test/disabled/index.html @@ -0,0 +1,74 @@ + + + + + Datetime Button - Disabled + + + + + + + + + + + + Datetime Button - Disabled + + + +
+
+

Datetime Button Disabled

+ + + Start Date + + + + + + +
+
+

Custom Button Disabled

+ + + Start Date + + + + + + + + + +
+
+
+
+ + diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts new file mode 100644 index 0000000000..49d18c5637 --- /dev/null +++ b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts @@ -0,0 +1,162 @@ +import { expect } from '@playwright/test'; +import type { Locator } from '@playwright/test'; +import { test } from '@utils/test/playwright'; +import type { EventSpy } from '@utils/test/playwright'; + +test.describe('datetime-button: rendering', () => { + test('should size the modal correctly', async ({ page }) => { + await page.setContent(` + + + + + `); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const dateButton = page.locator('ion-datetime-button #date-button'); + await dateButton.click(); + await ionModalDidPresent.next(); + + expect(await page.screenshot()).toMatchSnapshot(`datetime-overlay-modal-${page.getSnapshotSettings()}.png`); + }); + + test('should size the popover correctly', async ({ page }) => { + await page.setContent(` + + + + + `); + + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + const dateButton = page.locator('ion-datetime-button #date-button'); + await dateButton.click(); + await ionPopoverDidPresent.next(); + + expect(await page.screenshot()).toMatchSnapshot(`datetime-overlay-popover-${page.getSnapshotSettings()}.png`); + }); +}); + +test.describe('datetime-button: popover', () => { + let datetime: Locator; + let popover: Locator; + let ionPopoverDidPresent: EventSpy; + let ionPopoverDidDismiss: EventSpy; + test.beforeEach(async ({ page }, testInfo) => { + test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); + test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); + + await page.setContent(` + + + + + + `); + + datetime = page.locator('ion-datetime'); + popover = page.locator('ion-popover'); + ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + ionPopoverDidDismiss = await page.spyOnEvent('ionPopoverDidDismiss'); + }); + test('should open the date popover', async ({ page }) => { + await page.locator('#date-button').click(); + + await ionPopoverDidPresent.next(); + + expect(datetime).toBeVisible(); + }); + test('should open the time popover', async ({ page }) => { + await page.locator('#time-button').click(); + + await ionPopoverDidPresent.next(); + + expect(datetime).toBeVisible(); + }); + test('should open the date popover then the time popover', async ({ page }) => { + await page.locator('#date-button').click(); + await ionPopoverDidPresent.next(); + expect(datetime).toBeVisible(); + + await popover.evaluate((el: HTMLIonPopoverElement) => el.dismiss()); + await ionPopoverDidDismiss.next(); + + await page.locator('#time-button').click(); + await ionPopoverDidPresent.next(); + expect(datetime).toBeVisible(); + }); + test('should open the time popover then the date popover', async ({ page }) => { + await page.locator('#time-button').click(); + await ionPopoverDidPresent.next(); + expect(datetime).toBeVisible(); + + await popover.evaluate((el: HTMLIonPopoverElement) => el.dismiss()); + await ionPopoverDidDismiss.next(); + + await page.locator('#date-button').click(); + await ionPopoverDidPresent.next(); + expect(datetime).toBeVisible(); + }); +}); + +test.describe('datetime-button: modal', () => { + let datetime: Locator; + let modal: Locator; + let ionModalDidPresent: EventSpy; + let ionModalDidDismiss: EventSpy; + test.beforeEach(async ({ page }, testInfo) => { + test.skip(testInfo.project.metadata.rtl === 'rtl', 'No layout tests'); + test.skip(testInfo.project.metadata.mode === 'ios', 'No mode-specific logic'); + + await page.setContent(` + + + + + + `); + + datetime = page.locator('ion-datetime'); + modal = page.locator('ion-modal'); + ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); + }); + test('should open the date modal', async ({ page }) => { + await page.locator('#date-button').click(); + + await ionModalDidPresent.next(); + + expect(datetime).toBeVisible(); + }); + test('should open the time modal', async ({ page }) => { + await page.locator('#time-button').click(); + + await ionModalDidPresent.next(); + + expect(datetime).toBeVisible(); + }); + test('should open the date modal then the time modal', async ({ page }) => { + await page.locator('#date-button').click(); + await ionModalDidPresent.next(); + expect(datetime).toBeVisible(); + + await modal.evaluate((el: HTMLIonModalElement) => el.dismiss()); + await ionModalDidDismiss.next(); + + await page.locator('#time-button').click(); + await ionModalDidPresent.next(); + expect(datetime).toBeVisible(); + }); + test('should open the time modal then the date modal', async ({ page }) => { + await page.locator('#time-button').click(); + await ionModalDidPresent.next(); + expect(datetime).toBeVisible(); + + await modal.evaluate((el: HTMLIonModalElement) => el.dismiss()); + await ionModalDidDismiss.next(); + + await page.locator('#date-button').click(); + await ionModalDidPresent.next(); + expect(datetime).toBeVisible(); + }); +}); diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a7e751103a Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..17ae661d72 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..9fbd2ed454 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..e226527ecb Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..a9920562b4 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..ba7b0872ed Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..7e9514c23b Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..e730ef1879 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..93e1117e64 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..b7e5ce0647 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..7a986b79bc Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..8281b2fd15 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-modal-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..85159b4fcf Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..b14b873670 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..cab65a42bc Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..366bcc821d Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..14de14bda4 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..17f42dadb2 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..38ff7b13da Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..3138117aa3 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..5d2e271a42 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..4c473322fb Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..85edfe5507 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..13afce1f84 Binary files /dev/null and b/core/src/components/datetime-button/test/overlays/datetime-button.e2e.ts-snapshots/datetime-overlay-popover-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime-button/test/overlays/index.html b/core/src/components/datetime-button/test/overlays/index.html new file mode 100644 index 0000000000..78722979f2 --- /dev/null +++ b/core/src/components/datetime-button/test/overlays/index.html @@ -0,0 +1,118 @@ + + + + + Datetime Button - Overlays + + + + + + + + + + + + Datetime Button - Overlays + + + +
+
+

Popover - Default

+ + + + + + + + +
+
+

Modal - Default

+ + + + + + + + +
+
+

Popover - Custom

+ + + + + + + +
Custom select a Date
+
+
+
+
+

Modal - Custom

+ + + + + + + +
Custom select a Date
+
+
+
+
+
+
+ + diff --git a/core/src/components/datetime-button/test/style/index.html b/core/src/components/datetime-button/test/style/index.html new file mode 100644 index 0000000000..f4bb659a85 --- /dev/null +++ b/core/src/components/datetime-button/test/style/index.html @@ -0,0 +1,40 @@ + + + + + Datetime Button - Custom Style + + + + + + + + + + + Datetime Button - Custom Style + + + + + + + + + + + + + + diff --git a/core/src/components/datetime/datetime-interface.ts b/core/src/components/datetime/datetime-interface.ts index b2fd944c83..8c2ce79da8 100644 --- a/core/src/components/datetime/datetime-interface.ts +++ b/core/src/components/datetime/datetime-interface.ts @@ -3,7 +3,7 @@ export interface DatetimeOptions { } export interface DatetimeChangeEventDetail { - value?: string | null; + value?: string | string[] | null; } export interface DatetimeCustomEvent extends CustomEvent { @@ -21,3 +21,5 @@ export interface DatetimeParts { ampm?: 'am' | 'pm'; tzOffset?: number; } + +export type DatetimePresentation = 'date-time' | 'time-date' | 'date' | 'time' | 'month' | 'year' | 'month-year'; diff --git a/core/src/components/datetime/datetime.ios.scss b/core/src/components/datetime/datetime.ios.scss index bc3b010552..c7b60299d5 100644 --- a/core/src/components/datetime/datetime.ios.scss +++ b/core/src/components/datetime/datetime.ios.scss @@ -8,9 +8,9 @@ --title-color: #{$text-color-step-400}; } -:host(.datetime-presentation-date-time), -:host(.datetime-presentation-time-date), -:host(.datetime-presentation-date) { +:host(.datetime-presentation-date-time:not(.datetime-prefer-wheel)), +:host(.datetime-presentation-time-date:not(.datetime-prefer-wheel)), +:host(.datetime-presentation-date:not(.datetime-prefer-wheel)) { min-height: 350px; } diff --git a/core/src/components/datetime/datetime.scss b/core/src/components/datetime/datetime.scss index 3d70d71e62..309e347de6 100644 --- a/core/src/components/datetime/datetime.scss +++ b/core/src/components/datetime/datetime.scss @@ -19,12 +19,37 @@ overflow: hidden; } +/** + * When using the wheel picker to switch + * between months, sometimes the allowed + * dates may be filtered. As a result, it + * is possible to get a layout shift as + * the picker column will shrink to fit the + * widest item in the column. Setting a minimum + * width avoids this layout shifting. + */ +ion-picker-column-internal { + min-width: 26px; +} + :host(.datetime-size-fixed) { width: auto; - max-width: 350px; height: auto; } +:host(.datetime-size-fixed:not(.datetime-prefer-wheel)) { + max-width: 350px; +} + +/** + * This ensures that the picker is apppropriately + * sized and never truncates the text. + */ +:host(.datetime-size-fixed.datetime-prefer-wheel) { + min-width: 350px; + max-width: max-content; +} + :host(.datetime-size-cover) { width: 100%; } @@ -58,21 +83,19 @@ * the order we need to manually switch * the text alignment too. */ -:host .datetime-year .order-month-first .month-column { - order: 1; -} +:host .wheel-order-year-first .day-column { + order: 3; -:host .datetime-year .order-month-first .year-column { - order: 2; + text-align: end; } -:host .datetime-year .order-year-first .month-column { +:host .wheel-order-year-first .month-column { order: 2; text-align: end; } -:host .datetime-year .order-year-first .year-column { +:host .wheel-order-year-first .year-column { order: 1; text-align: start; diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 8008e4a8fe..2bd214e74d 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -3,7 +3,14 @@ import { Component, Element, Event, Host, Method, Prop, State, Watch, h, writeTa import { caretDownSharp, caretUpSharp, chevronBack, chevronDown, chevronForward } from 'ionicons/icons'; import { getIonMode } from '../../global/ionic-global'; -import type { Color, DatetimeChangeEventDetail, DatetimeParts, Mode, StyleEventDetail } from '../../interface'; +import type { + Color, + DatetimePresentation, + DatetimeChangeEventDetail, + DatetimeParts, + Mode, + StyleEventDetail, +} from '../../interface'; import { startFocusVisible } from '../../utils/focus-visible'; import { getElementRoot, raf, renderHiddenInput } from '../../utils/helpers'; import { printIonError, printIonWarning } from '../../utils/logging'; @@ -11,23 +18,24 @@ import { isRTL } from '../../utils/rtl'; import { createColorClasses } from '../../utils/theme'; import type { PickerColumnItem } from '../picker-column-internal/picker-column-internal-interfaces'; -import { warnIfValueOutOfBounds } from './utils/comparison'; +import { isSameDay, warnIfValueOutOfBounds } from './utils/comparison'; import { generateMonths, - generateTime, - getCalendarYears, getDaysOfMonth, getDaysOfWeek, - getPickerMonths, getToday, + getMonthColumnData, + getDayColumnData, + getYearColumnData, + getTimeColumnsData, + getCombinedDateColumnData, } from './utils/data'; -import { addTimePadding, getFormattedHour, getFormattedTime, getMonthAndDay, getMonthAndYear } from './utils/format'; -import { is24Hour, isMonthFirstLocale } from './utils/helpers'; +import { formatValue, getLocalizedTime, getMonthAndDay, getMonthAndYear } from './utils/format'; +import { is24Hour, isLocaleDayPeriodRTL, isMonthFirstLocale } from './utils/helpers'; import { calculateHourFromAMPM, convertDataToISO, getEndOfWeek, - getInternalHourValue, getNextDay, getNextMonth, getNextWeek, @@ -38,7 +46,7 @@ import { getPreviousYear, getStartOfWeek, } from './utils/manipulation'; -import { clampDate, convertToArrayOfNumbers, getPartsFromCalendarDay, parseDate } from './utils/parse'; +import { clampDate, convertToArrayOfNumbers, getPartsFromCalendarDay, parseAmPm, parseDate } from './utils/parse'; import { getCalendarDayState, isDayDisabled, @@ -97,11 +105,11 @@ export class Datetime implements ComponentInterface { * Duplicate reference to `activeParts` that does not trigger a re-render of the component. * Allows caching an instance of the `activeParts` in between render cycles. */ - private activePartsClone!: DatetimeParts; + private activePartsClone!: DatetimeParts | DatetimeParts[]; @State() showMonthAndYear = false; - @State() activeParts: DatetimeParts = { + @State() activeParts: DatetimeParts | DatetimeParts[] = { month: 5, day: 28, year: 2021, @@ -203,7 +211,7 @@ export class Datetime implements ComponentInterface { * AM/PM. `'date-time'` will show the date picker first and time picker second. * `'time-date'` will show the time picker first and date picker second. */ - @Prop() presentation: 'date-time' | 'time-date' | 'date' | 'time' | 'month' | 'year' | 'month-year' = 'date-time'; + @Prop() presentation: DatetimePresentation = 'date-time'; /** * The text to display on the picker's cancel button. @@ -305,17 +313,31 @@ export class Datetime implements ComponentInterface { */ @Prop() firstDayOfWeek = 0; + /** + * If `true`, multiple dates can be selected at once. Only + * applies to `presentation="date"` and `preferWheel="false"`. + */ + @Prop() multiple = false; + /** * The value of the datetime as a valid ISO 8601 datetime string. + * Should be an array of strings if `multiple="true"`. */ - @Prop({ mutable: true }) value?: string | null; + @Prop({ mutable: true }) value?: string | string[] | null; /** * Update the datetime value when the value changes */ @Watch('value') protected valueChanged() { + const { value, minParts, maxParts, workingParts, multiple } = this; + if (this.hasValue()) { + if (!multiple && Array.isArray(value)) { + this.value = value[0]; + return; // setting this.value will trigger re-run of this function + } + /** * Clones the value of the `activeParts` to the private clone, to update * the date display on the current render cycle without causing another render. @@ -323,40 +345,45 @@ export class Datetime implements ComponentInterface { * This allows us to update the current value's date/time display without * refocusing or shifting the user's display (leaves the user in place). */ - const valueDateParts = parseDate(this.value); + const valueDateParts = parseDate(value); if (valueDateParts) { - warnIfValueOutOfBounds(valueDateParts, this.minParts, this.maxParts); - - const { month, day, year, hour, minute } = valueDateParts; - const ampm = hour >= 12 ? 'pm' : 'am'; - - this.activePartsClone = { - ...this.activeParts, - month, - day, - year, - hour, - minute, - ampm, - }; + warnIfValueOutOfBounds(valueDateParts, minParts, maxParts); - /** - * The working parts am/pm value must be updated when the value changes, to - * ensure the time picker hour column values are generated correctly. - */ - this.setWorkingParts({ - ...this.workingParts, - ampm, - }); + if (Array.isArray(valueDateParts)) { + this.activePartsClone = [...valueDateParts]; + } else { + const { month, day, year, hour, minute } = valueDateParts; + const ampm = hour ? (hour >= 12 ? 'pm' : 'am') : undefined; + + this.activePartsClone = { + ...this.activeParts, + month, + day, + year, + hour, + minute, + ampm, + }; + + /** + * The working parts am/pm value must be updated when the value changes, to + * ensure the time picker hour column values are generated correctly. + * + * Note that we don't need to do this if valueDateParts is an array, since + * multiple="true" does not apply to time pickers. + */ + this.setWorkingParts({ + ...workingParts, + ampm, + }); + } } else { - printIonWarning(`Unable to parse date string: ${this.value}. Please provide a valid ISO 8601 datetime string.`); + printIonWarning(`Unable to parse date string: ${value}. Please provide a valid ISO 8601 datetime string.`); } } this.emitStyle(); - this.ionChange.emit({ - value: this.value, - }); + this.ionChange.emit({ value }); } /** @@ -409,6 +436,20 @@ export class Datetime implements ComponentInterface { */ @Prop() size: 'cover' | 'fixed' = 'fixed'; + /** + * If `true`, a wheel picker will be rendered instead of a calendar grid + * where possible. If `false`, a calendar grid will be rendered instead of + * a wheel picker where possible. + * + * A wheel picker can be rendered instead of a grid when `presentation` is + * one of the following values: `'date'`, `'date-time'`, or `'time-date'`. + * + * A wheel picker will always be rendered regardless of + * the `preferWheel` value when `presentation` is one of the following values: + * `'time'`, `'month'`, `'month-year'`, or `'year'`. + */ + @Prop() preferWheel = false; + /** * Emitted when the datetime selection was cancelled. */ @@ -435,6 +476,12 @@ export class Datetime implements ComponentInterface { */ @Event() ionStyle!: EventEmitter; + /** + * Emitted when componentDidRender is fired. + * @internal + */ + @Event() ionRender!: EventEmitter; + /** * Confirms the selected datetime value, updates the * `value` property, and optionally closes the popover @@ -442,6 +489,8 @@ export class Datetime implements ComponentInterface { */ @Method() async confirm(closeOverlay = false) { + const { highlightActiveParts, isCalendarPicker, activeParts } = this; + /** * We only update the value if the presentation is not a calendar picker, * or if `highlightActiveParts` is true; indicating that the user @@ -449,20 +498,32 @@ export class Datetime implements ComponentInterface { * * Otherwise "today" would accidentally be set as the value. */ - if (this.highlightActiveParts || !this.isCalendarPicker) { - /** - * Prevent convertDataToISO from doing any - * kind of transformation based on timezone - * This cancels out any change it attempts to make - * - * Important: Take the timezone offset based on - * the date that is currently selected, otherwise - * there can be 1 hr difference when dealing w/ DST - */ - const date = new Date(convertDataToISO(this.activeParts)); - this.activeParts.tzOffset = date.getTimezoneOffset() * -1; + if (highlightActiveParts || !isCalendarPicker) { + const activePartsIsArray = Array.isArray(activeParts); + if (activePartsIsArray && activeParts.length === 0) { + this.value = undefined; + } else { + /** + * Prevent convertDataToISO from doing any + * kind of transformation based on timezone + * This cancels out any change it attempts to make + * + * Important: Take the timezone offset based on + * the date that is currently selected, otherwise + * there can be 1 hr difference when dealing w/ DST + */ + if (activePartsIsArray) { + const dates = convertDataToISO(activeParts).map((str) => new Date(str)); + for (let i = 0; i < dates.length; i++) { + activeParts[i].tzOffset = dates[i].getTimezoneOffset() * -1; + } + } else { + const date = new Date(convertDataToISO(activeParts)); + activeParts.tzOffset = date.getTimezoneOffset() * -1; + } - this.value = convertDataToISO(this.activeParts); + this.value = convertDataToISO(activeParts); + } } if (closeOverlay) { @@ -511,10 +572,49 @@ export class Datetime implements ComponentInterface { }; }; - private setActiveParts = (parts: DatetimeParts) => { - this.activeParts = { - ...parts, - }; + private setActiveParts = (parts: DatetimeParts, removeDate = false) => { + const { multiple, activePartsClone, highlightActiveParts } = this; + + if (multiple) { + /** + * We read from activePartsClone here because valueChanged() only updates that, + * so it's the more reliable source of truth. If we read from activeParts, then + * if you click July 1, manually set the value to July 2, and then click July 3, + * the new value would be [July 1, July 3], ignoring the value set. + * + * We can then pass the new value to activeParts (rather than activePartsClone) + * since the clone will be updated automatically by activePartsChanged(). + */ + const activePartsArray = Array.isArray(activePartsClone) ? activePartsClone : [activePartsClone]; + if (removeDate) { + this.activeParts = activePartsArray.filter((p) => !isSameDay(p, parts)); + } else if (highlightActiveParts) { + this.activeParts = [...activePartsArray, parts]; + } else { + /** + * If highlightActiveParts is false, that means we just have a + * default value of today in activeParts; we need to replace that + * rather than adding to it since it's just a placeholder. + */ + this.activeParts = [parts]; + } + } else { + this.activeParts = { + ...parts, + }; + } + + /** + * Now that the user has interacted somehow to select something, we can + * show the solid highlight. This needs to be done after checking it above, + * but before the confirm call below. + * + * Note that for datetimes with confirm/cancel buttons, the value + * isn't updated until you call confirm(). We need to bring the + * solid circle back on day click for UX reasons, rather than only + * show the circle if `value` is truthy. + */ + this.highlightActiveParts = true; const hasSlottedButtons = this.el.querySelector('[slot="buttons"]') !== null; if (hasSlottedButtons || this.showDefaultButtons) { @@ -1034,16 +1134,33 @@ export class Datetime implements ComponentInterface { * interfaces. */ this.showMonthAndYear = false; + + raf(() => { + this.ionRender.emit(); + }); } - private processValue = (value?: string | null) => { + private processValue = (value?: string | string[] | null) => { this.highlightActiveParts = !!value; - const valueToProcess = parseDate(value || getToday()); + let valueToProcess = parseDate(value || getToday()); + + const { minParts, maxParts, multiple } = this; + if (!multiple && Array.isArray(value)) { + this.value = value[0]; + valueToProcess = (valueToProcess as DatetimeParts[])[0]; + } - const { minParts, maxParts } = this; warnIfValueOutOfBounds(valueToProcess, minParts, maxParts); - const { month, day, year, hour, minute, tzOffset } = clampDate(valueToProcess, minParts, maxParts); + /** + * If there are multiple values, pick an arbitrary one to clamp to. This way, + * if the values are across months, we always show at least one of them. Note + * that the values don't necessarily have to be in order. + */ + const singleValue = Array.isArray(valueToProcess) ? valueToProcess[0] : valueToProcess; + + const { month, day, year, hour, minute, tzOffset } = clampDate(singleValue, minParts, maxParts); + const ampm = parseAmPm(hour!); this.setWorkingParts({ month, @@ -1052,21 +1169,37 @@ export class Datetime implements ComponentInterface { hour, minute, tzOffset, - ampm: hour! >= 12 ? 'pm' : 'am', + ampm, }); - this.activeParts = { - month, - day, - year, - hour, - minute, - tzOffset, - ampm: hour! >= 12 ? 'pm' : 'am', - }; + if (Array.isArray(valueToProcess)) { + this.activeParts = [...valueToProcess]; + } else { + this.activeParts = { + month, + day, + year, + hour, + minute, + tzOffset, + ampm, + }; + } }; componentWillLoad() { + const { el, multiple, presentation, preferWheel } = this; + + if (multiple) { + if (presentation !== 'date') { + printIonWarning('Multiple date selection is only supported for presentation="date".', el); + } + + if (preferWheel) { + printIonWarning('Multiple date selection is not supported with preferWheel="true".', el); + } + } + this.processMinParts(); this.processMaxParts(); this.processValue(this.value); @@ -1136,6 +1269,16 @@ export class Datetime implements ComponentInterface { }); }; + private toggleMonthAndYearView = () => { + this.showMonthAndYear = !this.showMonthAndYear; + }; + + /** + * Universal render methods + * These are pieces of datetime that + * are rendered independently of presentation. + */ + private renderFooter() { const { showDefaultButtons, showClearButton } = this; const hasSlottedButtons = this.el.querySelector('[slot="buttons"]') !== null; @@ -1191,110 +1334,461 @@ export class Datetime implements ComponentInterface { ); } - private toggleMonthAndYearView = () => { - this.showMonthAndYear = !this.showMonthAndYear; - }; + /** + * Wheel picker render methods + */ + + private renderWheelPicker(forcePresentation: string = this.presentation) { + /** + * If presentation="time-date" we switch the + * order of the render array here instead of + * manually reordering each date/time picker + * column with CSS. This allows for additional + * flexibility if we need to render subsets + * of the date/time data or do additional ordering + * within the child render functions. + */ + const renderArray = + forcePresentation === 'time-date' + ? [this.renderTimePickerColumns(forcePresentation), this.renderDatePickerColumns(forcePresentation)] + : [this.renderDatePickerColumns(forcePresentation), this.renderTimePickerColumns(forcePresentation)]; + return {renderArray}; + } + + private renderDatePickerColumns(forcePresentation: string) { + return forcePresentation === 'date-time' || forcePresentation === 'time-date' + ? this.renderCombinedDatePickerColumn() + : this.renderIndividualDatePickerColumns(forcePresentation); + } + + private renderCombinedDatePickerColumn() { + const { activeParts, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this; + + /** + * By default, generate a range of 3 months: + * Previous month, current month, and next month + */ + const monthsToRender = generateMonths(workingParts); + + /** + * generateMonths returns the day data as well, + * but we do not want the day value to act as a max/min + * on the data we are going to generate. + */ + for (let i = 0; i <= monthsToRender.length - 1; i++) { + monthsToRender[i].day = null; + } + + /** + * If developers have provided their own + * min/max values, use that instead. Otherwise, + * fallback to the default range of 3 months. + */ + const min = minParts || monthsToRender[0]; + const max = maxParts || monthsToRender[monthsToRender.length - 1]; + + const result = getCombinedDateColumnData( + locale, + workingParts, + todayParts, + min, + max, + this.parsedDayValues, + this.parsedMonthValues + ); + let items = result.items; + const parts = result.parts; + + if (isDateEnabled) { + items = items.map((itemObject, index) => { + const referenceParts = parts[index]; + + let disabled; + try { + /** + * The `isDateEnabled` implementation is try-catch wrapped + * to prevent exceptions in the user's function from + * interrupting the calendar rendering. + */ + disabled = !isDateEnabled(convertDataToISO(referenceParts)); + } catch (e) { + printIonError( + 'Exception thrown from provided `isDateEnabled` function. Please check your function and try again.', + e + ); + } + + return { + ...itemObject, + disabled, + }; + }); + } + + /** + * If we have selected a day already, then default the column + * to that value. Otherwise, default it to today. + */ + const todayString = workingParts.day + ? `${workingParts.year}-${workingParts.month}-${workingParts.day}` + : `${todayParts.year}-${todayParts.month}-${todayParts.day}`; - private renderYearView() { - const { presentation, workingParts, locale } = this; - const calendarYears = getCalendarYears(this.todayParts, this.minParts, this.maxParts, this.parsedYearValues); - const showMonth = presentation !== 'year'; - const showYear = presentation !== 'month'; - - const months = getPickerMonths(locale, workingParts, this.minParts, this.maxParts, this.parsedMonthValues); - const years = calendarYears.map((year) => { - return { - text: `${year}`, - value: year, - }; - }); - const showMonthFirst = isMonthFirstLocale(locale); - const columnOrder = showMonthFirst ? 'month-first' : 'year-first'; return ( -
-
- - {showMonth && ( - { - // TODO(FW-1823) Remove this when iOS 14 support is dropped. - // Due to a Safari 14 issue we need to destroy - // the scroll listener before we update state - // and trigger a re-render. - if (this.destroyCalendarListener) { - this.destroyCalendarListener(); - } + { + // TODO(FW-1823) Remove this when iOS 14 support is dropped. + // Due to a Safari 14 issue we need to destroy + // the scroll listener before we update state + // and trigger a re-render. + if (this.destroyCalendarListener) { + this.destroyCalendarListener(); + } - this.setWorkingParts({ - ...this.workingParts, - month: ev.detail.value, - }); + const { value } = ev.detail; + const findPart = parts.find(({ month, day, year }) => value === `${year}-${month}-${day}`); - if (presentation === 'month' || presentation === 'month-year') { - this.setActiveParts({ - ...this.activeParts, - month: ev.detail.value, - }); - } + this.setWorkingParts({ + ...workingParts, + ...findPart, + }); + + if (!Array.isArray(activeParts)) { + this.setActiveParts({ + ...activeParts, + ...findPart, + }); + } - // We can re-attach the scroll listener after - // the working parts have been updated. - this.initializeCalendarListener(); + // We can re-attach the scroll listener after + // the working parts have been updated. + this.initializeCalendarListener(); - ev.stopPropagation(); - }} - > - )} - {showYear && ( - { - // TODO(FW-1823) Remove this when iOS 14 support is dropped. - // Due to a Safari 14 issue we need to destroy - // the scroll listener before we update state - // and trigger a re-render. - if (this.destroyCalendarListener) { - this.destroyCalendarListener(); - } + ev.stopPropagation(); + }} + > + ); + } - this.setWorkingParts({ - ...this.workingParts, - year: ev.detail.value, - }); + private renderIndividualDatePickerColumns(forcePresentation: string) { + const { workingParts, isDateEnabled } = this; + const shouldRenderMonths = forcePresentation !== 'year' && forcePresentation !== 'time'; + const months = shouldRenderMonths + ? getMonthColumnData(this.locale, workingParts, this.minParts, this.maxParts, this.parsedMonthValues) + : []; + + const shouldRenderDays = forcePresentation === 'date'; + let days = shouldRenderDays + ? getDayColumnData(this.locale, workingParts, this.minParts, this.maxParts, this.parsedDayValues) + : []; + + if (isDateEnabled) { + days = days.map((dayObject) => { + const { value } = dayObject; + const valueNum = typeof value === 'string' ? parseInt(value) : value; + const referenceParts: DatetimeParts = { + month: workingParts.month, + day: valueNum, + year: workingParts.year, + }; - if (presentation === 'year' || presentation === 'month-year') { - this.setActiveParts({ - ...this.activeParts, - year: ev.detail.value, - }); - } + let disabled; + try { + /** + * The `isDateEnabled` implementation is try-catch wrapped + * to prevent exceptions in the user's function from + * interrupting the calendar rendering. + */ + disabled = !isDateEnabled(convertDataToISO(referenceParts)); + } catch (e) { + printIonError( + 'Exception thrown from provided `isDateEnabled` function. Please check your function and try again.', + e + ); + } + + return { + ...dayObject, + disabled, + }; + }); + } - // We can re-attach the scroll listener after - // the working parts have been updated. - this.initializeCalendarListener(); + const shouldRenderYears = forcePresentation !== 'month' && forcePresentation !== 'time'; + const years = shouldRenderYears + ? getYearColumnData(this.todayParts, this.minParts, this.maxParts, this.parsedYearValues) + : []; - ev.stopPropagation(); - }} - > - )} - -
+ return [this.renderMonthPickerColumn(months), this.renderDayPickerColumn(days), this.renderYearPickerColumn(years)]; + } + + private renderDayPickerColumn(days: PickerColumnItem[]) { + if (days.length === 0) { + return []; + } + + const { activeParts, workingParts } = this; + + return ( + { + // TODO(FW-1823) Remove this when iOS 14 support is dropped. + // Due to a Safari 14 issue we need to destroy + // the scroll listener before we update state + // and trigger a re-render. + if (this.destroyCalendarListener) { + this.destroyCalendarListener(); + } + + this.setWorkingParts({ + ...workingParts, + day: ev.detail.value, + }); + + if (!Array.isArray(activeParts)) { + this.setActiveParts({ + ...activeParts, + day: ev.detail.value, + }); + } + + // We can re-attach the scroll listener after + // the working parts have been updated. + this.initializeCalendarListener(); + + ev.stopPropagation(); + }} + > + ); + } + + private renderMonthPickerColumn(months: PickerColumnItem[]) { + if (months.length === 0) { + return []; + } + + const { activeParts, workingParts } = this; + + return ( + { + // TODO(FW-1823) Remove this when iOS 14 support is dropped. + // Due to a Safari 14 issue we need to destroy + // the scroll listener before we update state + // and trigger a re-render. + if (this.destroyCalendarListener) { + this.destroyCalendarListener(); + } + + this.setWorkingParts({ + ...workingParts, + month: ev.detail.value, + }); + + if (!Array.isArray(activeParts)) { + this.setActiveParts({ + ...activeParts, + month: ev.detail.value, + }); + } + + // We can re-attach the scroll listener after + // the working parts have been updated. + this.initializeCalendarListener(); + + ev.stopPropagation(); + }} + > + ); + } + private renderYearPickerColumn(years: PickerColumnItem[]) { + if (years.length === 0) { + return []; + } + + const { activeParts, workingParts } = this; + + return ( + { + // TODO(FW-1823) Remove this when iOS 14 support is dropped. + // Due to a Safari 14 issue we need to destroy + // the scroll listener before we update state + // and trigger a re-render. + if (this.destroyCalendarListener) { + this.destroyCalendarListener(); + } + + this.setWorkingParts({ + ...workingParts, + year: ev.detail.value, + }); + + if (!Array.isArray(activeParts)) { + this.setActiveParts({ + ...activeParts, + year: ev.detail.value, + }); + } + + // We can re-attach the scroll listener after + // the working parts have been updated. + this.initializeCalendarListener(); + + ev.stopPropagation(); + }} + > + ); + } + private renderTimePickerColumns(forcePresentation: string) { + if (['date', 'month', 'month-year', 'year'].includes(forcePresentation)) { + return []; + } + + const { hoursData, minutesData, dayPeriodData } = getTimeColumnsData( + this.locale, + this.workingParts, + this.hourCycle, + this.value ? this.minParts : undefined, + this.value ? this.maxParts : undefined, + this.parsedHourValues, + this.parsedMinuteValues + ); + + return [ + this.renderHourPickerColumn(hoursData), + this.renderMinutePickerColumn(minutesData), + this.renderDayPeriodPickerColumn(dayPeriodData), + ]; + } + + private renderHourPickerColumn(hoursData: PickerColumnItem[]) { + const { workingParts, activePartsClone } = this; + if (hoursData.length === 0) return []; + + return ( + { + this.setWorkingParts({ + ...workingParts, + hour: ev.detail.value, + }); + + if (!Array.isArray(activePartsClone)) { + this.setActiveParts({ + ...activePartsClone, + hour: ev.detail.value, + }); + } + + ev.stopPropagation(); + }} + > + ); + } + private renderMinutePickerColumn(minutesData: PickerColumnItem[]) { + const { workingParts, activePartsClone } = this; + if (minutesData.length === 0) return []; + + return ( + { + this.setWorkingParts({ + ...workingParts, + minute: ev.detail.value, + }); + + if (!Array.isArray(activePartsClone)) { + this.setActiveParts({ + ...activePartsClone, + minute: ev.detail.value, + }); + } + + ev.stopPropagation(); + }} + > + ); + } + private renderDayPeriodPickerColumn(dayPeriodData: PickerColumnItem[]) { + const { workingParts, activePartsClone } = this; + if (dayPeriodData.length === 0) { + return []; + } + + const isDayPeriodRTL = isLocaleDayPeriodRTL(this.locale); + + return ( + { + const hour = calculateHourFromAMPM(workingParts, ev.detail.value); + + this.setWorkingParts({ + ...workingParts, + ampm: ev.detail.value, + hour, + }); + + if (!Array.isArray(activePartsClone)) { + this.setActiveParts({ + ...activePartsClone, + ampm: ev.detail.value, + hour, + }); + } + + ev.stopPropagation(); + }} + > + ); + } + + private renderWheelView(forcePresentation?: string) { + const { locale } = this; + const showMonthFirst = isMonthFirstLocale(locale); + const columnOrder = showMonthFirst ? 'month-first' : 'year-first'; + return ( +
+ {this.renderWheelPicker(forcePresentation)}
); } + /** + * Grid Render Methods + */ + private renderCalendarHeader(mode: Mode) { const expandedIcon = mode === 'ios' ? chevronDown : caretUpSharp; const collapsedIcon = mode === 'ios' ? chevronForward : caretDownSharp; @@ -1333,7 +1827,6 @@ export class Datetime implements ComponentInterface {
); } - private renderMonth(month: number, year: number) { const { highlightActiveParts } = this; const yearAllowed = this.parsedYearValues === undefined || this.parsedYearValues.includes(year); @@ -1369,7 +1862,7 @@ export class Datetime implements ComponentInterface {
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => { const { day, dayOfWeek } = dateObject; - const { isDateEnabled } = this; + const { isDateEnabled, multiple } = this; const referenceParts = { month, day, year }; const { isActive, isToday, ariaLabel, ariaSelected, disabled } = getCalendarDayState( this.locale, @@ -1421,14 +1914,6 @@ export class Datetime implements ComponentInterface { return; } - /** - * Note that for datetimes with confirm/cancel buttons, the value - * isn't updated until you call confirm(). We need to bring the - * solid circle back on day click for UX reasons, rather than only - * show the circle if `value` is truthy. - */ - this.highlightActiveParts = true; - this.setWorkingParts({ ...this.workingParts, month, @@ -1436,12 +1921,24 @@ export class Datetime implements ComponentInterface { year, }); - this.setActiveParts({ - ...this.activeParts, - month, - day, - year, - }); + // multiple only needs date info, so we can wipe out other fields like time + if (multiple) { + this.setActiveParts( + { + month, + day, + year, + }, + isActive + ); + } else { + this.setActiveParts({ + ...this.activeParts, + month, + day, + year, + }); + } }} > {day} @@ -1452,7 +1949,6 @@ export class Datetime implements ComponentInterface {
); } - private renderCalendarBody() { return (
(this.calendarBodyRef = el)} tabindex="0"> @@ -1462,7 +1958,6 @@ export class Datetime implements ComponentInterface {
); } - private renderCalendar(mode: Mode) { return (
@@ -1471,7 +1966,6 @@ export class Datetime implements ComponentInterface {
); } - private renderTimeLabel() { const hasSlottedTimeLabel = this.el.querySelector('[slot="time-label"]') !== null; if (!hasSlottedTimeLabel && !this.showDefaultTimeLabel) { @@ -1481,86 +1975,8 @@ export class Datetime implements ComponentInterface { return Time; } - private renderTimePicker( - hoursItems: PickerColumnItem[], - minutesItems: PickerColumnItem[], - ampmItems: PickerColumnItem[], - use24Hour: boolean - ) { - const { color, activePartsClone, workingParts } = this; - - return ( - - { - this.setWorkingParts({ - ...workingParts, - hour: ev.detail.value, - }); - this.setActiveParts({ - ...activePartsClone, - hour: ev.detail.value, - }); - - ev.stopPropagation(); - }} - > - { - this.setWorkingParts({ - ...workingParts, - minute: ev.detail.value, - }); - this.setActiveParts({ - ...activePartsClone, - minute: ev.detail.value, - }); - - ev.stopPropagation(); - }} - > - {!use24Hour && ( - { - const hour = calculateHourFromAMPM(workingParts, ev.detail.value); - - this.setWorkingParts({ - ...workingParts, - ampm: ev.detail.value, - hour, - }); - - this.setActiveParts({ - ...activePartsClone, - ampm: ev.detail.value, - hour, - }); - - ev.stopPropagation(); - }} - > - )} - - ); - } - - private renderTimeOverlay( - hoursItems: PickerColumnItem[], - minutesItems: PickerColumnItem[], - ampmItems: PickerColumnItem[], - use24Hour: boolean - ) { + private renderTimeOverlay() { + const use24Hour = is24Hour(this.locale, this.hourCycle); return [
{this.renderTimeLabel()}
, , (this.popoverRef = el)} > - {this.renderTimePicker(hoursItems, minutesItems, ampmItems, use24Hour)} + {this.renderWheelPicker('time')} , ]; } + private renderCalendarViewHeader(mode: Mode) { + const hasSlottedTitle = this.el.querySelector('[slot="title"]') !== null; + if (!hasSlottedTitle && !this.showDefaultTitle) { + return; + } + + return ( +
+
+ Select Date +
+ {mode === 'md' && !this.multiple && ( +
{getMonthAndDay(this.locale, this.activeParts as DatetimeParts)}
+ )} +
+ ); + } /** * Render time picker inside of datetime. @@ -1631,80 +2065,49 @@ export class Datetime implements ComponentInterface { * should just be the default segment. */ private renderTime() { - const { workingParts, presentation } = this; + const { presentation } = this; const timeOnlyPresentation = presentation === 'time'; - const use24Hour = is24Hour(this.locale, this.hourCycle); - const { hours, minutes, am, pm } = generateTime( - workingParts, - use24Hour ? 'h23' : 'h12', - this.value ? this.minParts : undefined, - this.value ? this.maxParts : undefined, - this.parsedHourValues, - this.parsedMinuteValues - ); - - const hoursItems = hours.map((hour) => { - return { - text: getFormattedHour(hour, use24Hour), - value: getInternalHourValue(hour, use24Hour, workingParts.ampm), - }; - }); - - const minutesItems = minutes.map((minute) => { - return { - text: addTimePadding(minute), - value: minute, - }; - }); - - const ampmItems = []; - if (am) { - ampmItems.push({ - text: 'AM', - value: 'am', - }); - } - - if (pm) { - ampmItems.push({ - text: 'PM', - value: 'pm', - }); - } return ( -
- {timeOnlyPresentation - ? this.renderTimePicker(hoursItems, minutesItems, ampmItems, use24Hour) - : this.renderTimeOverlay(hoursItems, minutesItems, ampmItems, use24Hour)} -
+
{timeOnlyPresentation ? this.renderWheelPicker() : this.renderTimeOverlay()}
); } - private renderCalendarViewHeader(mode: Mode) { - const hasSlottedTitle = this.el.querySelector('[slot="title"]') !== null; - if (!hasSlottedTitle && !this.showDefaultTitle) { - return; - } - - return ( -
-
- Select Date -
- {mode === 'md' &&
{getMonthAndDay(this.locale, this.activeParts)}
} -
- ); + /** + * Renders the month/year picker that is + * displayed on the calendar grid. + * The .datetime-year class has additional + * styles that let us show/hide the + * picker when the user clicks on the + * toggle in the calendar header. + */ + private renderCalendarViewMonthYearPicker() { + return
{this.renderWheelView('month-year')}
; } + /** + * Render entry point + * All presentation types are rendered from here. + */ + private renderDatetime(mode: Mode) { - const { presentation } = this; + const { presentation, preferWheel } = this; + + /** + * Certain presentation types have separate grid and wheel displays. + * If preferWheel is true then we should show a wheel picker instead. + */ + const hasWheelVariant = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date'; + if (preferWheel && hasWheelVariant) { + return [this.renderWheelView(), this.renderFooter()]; + } + switch (presentation) { case 'date-time': return [ this.renderCalendarViewHeader(mode), this.renderCalendar(mode), - this.renderYearView(), + this.renderCalendarViewMonthYearPicker(), this.renderTime(), this.renderFooter(), ]; @@ -1713,7 +2116,7 @@ export class Datetime implements ComponentInterface { this.renderCalendarViewHeader(mode), this.renderTime(), this.renderCalendar(mode), - this.renderYearView(), + this.renderCalendarViewMonthYearPicker(), this.renderFooter(), ]; case 'time': @@ -1721,26 +2124,41 @@ export class Datetime implements ComponentInterface { case 'month': case 'month-year': case 'year': - return [this.renderYearView(), this.renderFooter()]; + return [this.renderWheelView(), this.renderFooter()]; default: return [ this.renderCalendarViewHeader(mode), this.renderCalendar(mode), - this.renderYearView(), + this.renderCalendarViewMonthYearPicker(), this.renderFooter(), ]; } } render() { - const { name, value, disabled, el, color, isPresented, readonly, showMonthAndYear, presentation, size } = this; + const { + name, + value, + disabled, + el, + color, + isPresented, + readonly, + showMonthAndYear, + preferWheel, + presentation, + size, + } = this; const mode = getIonMode(this); const isMonthAndYearPresentation = presentation === 'year' || presentation === 'month' || presentation === 'month-year'; const shouldShowMonthAndYear = showMonthAndYear || isMonthAndYearPresentation; const monthYearPickerOpen = showMonthAndYear && !isMonthAndYearPresentation; + const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date'; + const hasWheelVariant = hasDatePresentation && preferWheel; + const hasGrid = hasDatePresentation && !preferWheel; - renderHiddenInput(true, el, name, value, disabled); + renderHiddenInput(true, el, name, formatValue(value), disabled); return ( diff --git a/core/src/components/datetime/test/format.spec.ts b/core/src/components/datetime/test/format.spec.ts index ae9bc1c0cc..23676b44de 100644 --- a/core/src/components/datetime/test/format.spec.ts +++ b/core/src/components/datetime/test/format.spec.ts @@ -4,6 +4,7 @@ import { getFormattedHour, addTimePadding, getMonthAndYear, + getLocalizedDayPeriod, } from '../utils/format'; describe('generateDayAriaLabel()', () => { @@ -88,3 +89,13 @@ describe('getMonthAndYear()', () => { expect(getMonthAndYear('es-ES', { month: 4, day: 1, year: 2006 })).toEqual('abril de 2006'); }); }); + +describe('getLocalizedDayPeriod', () => { + it('should return AM when the date is in the morning', () => { + expect(getLocalizedDayPeriod('en-US', 'am')); + }); + + it('should return PM when the date is in the afternoon', () => { + expect(getLocalizedDayPeriod('en-US', 'pm')); + }); +}); diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts b/core/src/components/datetime/test/locale/datetime.e2e.ts new file mode 100644 index 0000000000..a15a23820d --- /dev/null +++ b/core/src/components/datetime/test/locale/datetime.e2e.ts @@ -0,0 +1,146 @@ +import { expect } from '@playwright/test'; +import type { E2EPage } from '@utils/test/playwright'; +import { test } from '@utils/test/playwright'; + +test.describe('datetime: locale', () => { + let datetimeFixture: DatetimeLocaleFixture; + + test.beforeEach(async ({ page }) => { + datetimeFixture = new DatetimeLocaleFixture(page); + await datetimeFixture.goto(); + }); + + test.describe('en-US', () => { + test.beforeEach(async () => { + await datetimeFixture.setLocale('en-US'); + }); + + test('should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedDatePicker(); + }); + + test('month/year picker should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedMonthYearPicker(); + }); + + test('time picker should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedTimePicker(); + }); + }); + + test.describe('ta-IN', () => { + test.beforeEach(async () => { + await datetimeFixture.setLocale('ta-IN'); + }); + + test('should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedDatePicker(); + }); + + test('month/year picker should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedMonthYearPicker(); + }); + + test('time picker should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedTimePicker(); + }); + }); + + test.describe('ja-JP', () => { + test.beforeEach(async () => { + await datetimeFixture.setLocale('ja-JP'); + }); + + test('should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedDatePicker(); + }); + + test('month/year picker should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedMonthYearPicker(); + }); + + test('time picker should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedTimePicker(); + }); + }); + + test.describe('es-ES', () => { + test.beforeEach(async () => { + await datetimeFixture.setLocale('es-ES'); + }); + + test('should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedDatePicker(); + }); + + test('month/year picker should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedMonthYearPicker(); + }); + + test('time picker should not have visual regressions', async () => { + await datetimeFixture.expectLocalizedTimePicker(); + }); + }); +}); + +class DatetimeLocaleFixture { + readonly page: E2EPage; + locale = 'en-US'; + + constructor(page: E2EPage) { + this.page = page; + } + + async goto() { + await this.page.goto(`/src/components/datetime/test/locale`); + } + + async setLocale(locale: string) { + this.locale = locale; + await this.page.locator('select').selectOption(locale); + await this.page.waitForChanges(); + } + + async expectLocalizedDatePicker() { + await this.waitForDatetime(); + + await this.page.setIonViewport(); + // Captures a screenshot of the datepicker with localized am/pm labels + expect(await this.page.screenshot()).toMatchSnapshot( + `datetime-locale-${this.locale}-diff-${this.page.getSnapshotSettings()}.png` + ); + } + + async expectLocalizedMonthYearPicker() { + await this.waitForDatetime(); + await this.page.setIonViewport(); + // Opens the month/year picker + const monthYearButton = this.page.locator('#am .calendar-month-year ion-item'); + await monthYearButton.click(); + await this.page.waitForChanges(); + // Capture a screenshot of the month/year picker with localized month labels. + expect(await this.page.screenshot()).toMatchSnapshot( + `datetime-locale-${this.locale}-month-year-diff-${this.page.getSnapshotSettings()}.png` + ); + } + + async expectLocalizedTimePicker() { + await this.waitForDatetime(); + await this.page.setIonViewport(); + // Opens the timepicker + const timePickerButton = this.page.locator('#am .time-body'); + const timePickerPopoverPresentSpy = await this.page.spyOnEvent('ionPopoverDidPresent'); + await timePickerButton.click(); + await timePickerPopoverPresentSpy.next(); + // Capture a screenshot of the time picker with localized am/pm labels + expect(await this.page.screenshot()).toMatchSnapshot( + `datetime-locale-${this.locale}-time-diff-${this.page.getSnapshotSettings()}.png` + ); + } + + private async waitForDatetime() { + await this.page.locator('#am.datetime-ready').waitFor({ state: 'attached' }); + await this.page.locator('#pm').scrollIntoViewIfNeeded(); + await this.page.locator('#pm.datetime-ready').waitFor({ state: 'attached' }); + } +} diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..da46a88b19 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..139ac82032 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..0d9a9fdaeb Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..dced0a8f4b Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..b2c3509d2a Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..8bbbeaf4b9 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..e2a55be930 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..c6bb179a79 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..d0ab8cb5a6 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a5cf52a768 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..9a2ba1c5ff Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..da9be10820 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..b37783ffdc Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..eb2e545fba Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..4a4aec62c9 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..e94a79ce1c Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..bc2eca9555 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..11459d447c Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..ce86ee944e Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..3dfd01d9c8 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..4820b9e842 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..2a58b1622e Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..8bbd0c2a88 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..d2af417fa4 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..534a3975a4 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..b960749057 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..360472ce30 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..c87cad400c Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..ff569b7b55 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..6711779046 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..7c90e182dd Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..20ed8f89b8 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..09cd1b3b4a Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..bde8a0cdb5 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..6c1f915fd4 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..9d9f07dd71 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..1dffbf8edb Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..533ae5aa5f Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..7417c40ba6 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..38c65e7a39 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..9dbbac87eb Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..40a4393998 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..258a69911d Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..fb83654c75 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..f26fe18436 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..0e86069927 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..88c4c1a98f Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..a5def520eb Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..ab4d658090 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..30db8e1df8 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..4ac4838e2d Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..24498f2633 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..fa8e13fdf2 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..c199537f53 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..bac9656bed Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..1abe50304e Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..2615c13946 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..5104bf1538 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..f305f7a409 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..1527c4c7cc Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..f33e67c62b Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..5c4a0a9561 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..a2c1ae647e Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..5dd5599233 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..7335055639 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..fa575af4e0 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a8ffdf1090 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..12a6813c20 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..93efe6877c Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..bfdfc48079 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..715d2f9dc6 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..640b891dcc Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..eb01fb871b Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..26339c94b3 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..4efd9dd839 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a50cde4481 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..eee0582ac8 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..e7cb68ce29 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..21a9ba390d Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..8ef0690d8b Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..a8846aad75 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..239ae5f2c2 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..924f988e45 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..a78a36fedf Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..ef07a8ad40 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..d42a553a0d Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..b4e143bb48 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..0e64d6b3d5 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..4517a55fff Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..3d2bc47f2f Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..5b1ebfcc3f Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..2d6a7115f4 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..7cf75f9b86 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..9c82723d33 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..3477033b86 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..306f527d17 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a1ef144a5c Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..2a0a19649a Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..4be6a2dac2 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..91508e6c8a Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..5dff24f909 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..5d530fa3db Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..ed54d36223 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..2489575ed0 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..b6b0851558 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..71f2e95813 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..47dcad4d43 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..4378392b53 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..43847bd060 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..b853fb3251 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..32f56a267c Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..273294b218 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..1478ba8a6d Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..fcd3ab5740 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..b0009961f1 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..83252ee537 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..6cc4043c3b Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a735b8df6a Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..abebcf79c0 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..98590b9def Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..8d268bc2cf Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..e089552098 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..8cc02b69d6 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..b4c2afa04e Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..f0aad7cb4a Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..50518ddce5 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..4ad6b9ee4a Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..de83ca6422 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..66d8880ee0 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..d76848e786 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..1a00ec582a Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..f5e08eee07 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-month-year-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..2cef99d88f Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..556ef6d103 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..5e1f7fc9a4 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..aef0cb9263 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..d102b86478 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..7ade8875d5 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..02ee0263df Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..89d62ef710 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..929bcfd6f3 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..1081624db4 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..dcaf9952b3 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..7adebaf440 Binary files /dev/null and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ta-IN-time-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/e2e.ts b/core/src/components/datetime/test/locale/e2e.ts deleted file mode 100644 index 49497ab589..0000000000 --- a/core/src/components/datetime/test/locale/e2e.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { newE2EPage } from '@stencil/core/testing'; - -test('locale', async () => { - const page = await newE2EPage({ - url: '/src/components/datetime/test/locale?ionic:_testing=true', - }); - - const screenshotCompares = []; - const datetime = await page.find('ion-datetime'); - - screenshotCompares.push(await page.compareScreenshot()); - - datetime.setProperty('locale', 'es-ES'); - await page.waitForChanges(); - - screenshotCompares.push(await page.compareScreenshot()); - - for (const screenshotCompare of screenshotCompares) { - expect(screenshotCompare).toMatchScreenshot(); - } -}); - -test('it should render month and year with an en-US locale', async () => { - const page = await newE2EPage({ - url: '/src/components/datetime/test/locale?ionic:_testing=true', - }); - - const screenshotCompares = []; - const datetime = await page.find('ion-datetime'); - - datetime.setProperty('locale', 'en-US'); - await page.waitForChanges(); - - const button = await page.find('ion-datetime >>> .calendar-month-year ion-item'); - await button.click(); - await page.waitForChanges(); - - const yearBody = await page.find('ion-datetime >>> .datetime-year-body'); - expect(yearBody).toHaveClass('order-month-first'); - - screenshotCompares.push(await page.compareScreenshot()); - - for (const screenshotCompare of screenshotCompares) { - expect(screenshotCompare).toMatchScreenshot(); - } -}); - -test('it should render year and month with a ja-JP locale', async () => { - const page = await newE2EPage({ - url: '/src/components/datetime/test/locale?ionic:_testing=true', - }); - - const screenshotCompares = []; - const datetime = await page.find('ion-datetime'); - - datetime.setProperty('locale', 'ja-JP'); - await page.waitForChanges(); - - const button = await page.find('ion-datetime >>> .calendar-month-year ion-item'); - await button.click(); - await page.waitForChanges(); - - const yearBody = await page.find('ion-datetime >>> .datetime-year-body'); - expect(yearBody).toHaveClass('order-year-first'); - - screenshotCompares.push(await page.compareScreenshot()); - - for (const screenshotCompare of screenshotCompares) { - expect(screenshotCompare).toMatchScreenshot(); - } -}); diff --git a/core/src/components/datetime/test/locale/index.html b/core/src/components/datetime/test/locale/index.html index 1efe928983..9a4f298116 100644 --- a/core/src/components/datetime/test/locale/index.html +++ b/core/src/components/datetime/test/locale/index.html @@ -11,9 +11,10 @@ + Datetime - Locale + + +
-

Default

- +

Default (AM)

+ +
+
+

Default (PM)

+
+ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts b/core/src/components/datetime/test/multiple/datetime.e2e.ts new file mode 100644 index 0000000000..98d7b60442 --- /dev/null +++ b/core/src/components/datetime/test/multiple/datetime.e2e.ts @@ -0,0 +1,161 @@ +import { expect } from '@playwright/test'; +import type { E2EPage } from '@utils/test/playwright'; +import { test } from '@utils/test/playwright'; + +const setup = async (page: E2EPage, datetimeID: string, shouldNavigate = true) => { + if (shouldNavigate) await page.goto('/src/components/datetime/test/multiple/'); + const datetime = page.locator(`#${datetimeID}`); + await datetime.scrollIntoViewIfNeeded(); + await page.waitForSelector(`#${datetimeID}.datetime-ready`); + return datetime; +}; + +const screenshotDatetime = async (page: E2EPage, datetimeID: string) => { + const datetime = await setup(page, datetimeID); + expect(await datetime.screenshot()).toMatchSnapshot( + `datetime-multiple-${datetimeID}-${page.getSnapshotSettings()}.png` + ); +}; + +test.describe('datetime: multiple date selection (visual regressions)', () => { + test('single default value should not have visual regressions', async ({ page }) => { + await screenshotDatetime(page, 'singleDefaultValue'); + }); + + test('multiple default values should not have visual regressions', async ({ page }) => { + await screenshotDatetime(page, 'multipleDefaultValues'); + }); + + test('header should not have visual regressions', async ({ page }) => { + await screenshotDatetime(page, 'withHeader'); + }); +}); + +test.describe('datetime: multiple date selection (functionality)', () => { + // eslint-disable-next-line no-empty-pattern + test.beforeEach(async ({}, testInfo) => { + test.skip(testInfo.project.metadata.rtl === true, 'Does not test LTR vs. RTL layout.'); + }); + + test('clicking unselected days should select them', async ({ page }) => { + const datetime = await setup(page, 'singleDefaultValue'); + const juneButtons = datetime.locator('[data-month="6"][data-day]'); + const ionChangeSpy = await page.spyOnEvent('ionChange'); + + await juneButtons.nth(1).click(); + await ionChangeSpy.next(); + await expect(datetime).toHaveJSProperty('value', ['2022-06-01', '2022-06-02']); + + await juneButtons.nth(2).click(); + await ionChangeSpy.next(); + await expect(datetime).toHaveJSProperty('value', ['2022-06-01', '2022-06-02', '2022-06-03']); + + for (let i = 0; i < 3; i++) { + await expect(juneButtons.nth(i)).toHaveClass(/calendar-day-active/); + } + }); + + test('clicking selected days should unselect them', async ({ page }) => { + const datetime = await setup(page, 'multipleDefaultValues'); + const juneButtons = datetime.locator('[data-month="6"][data-day]'); + const ionChangeSpy = await page.spyOnEvent('ionChange'); + + await juneButtons.nth(0).click(); + await ionChangeSpy.next(); + await expect(datetime).toHaveJSProperty('value', ['2022-06-02', '2022-06-03']); + + await juneButtons.nth(1).click(); + await ionChangeSpy.next(); + await expect(datetime).toHaveJSProperty('value', ['2022-06-03']); + + await juneButtons.nth(2).click(); + await ionChangeSpy.next(); + await expect(datetime).toHaveJSProperty('value', undefined); + + for (let i = 0; i < 3; i++) { + await expect(juneButtons.nth(i)).not.toHaveClass(/calendar-day-active/); + } + }); + + test('change event should emit with array detail', async ({ page }) => { + const datetime = await setup(page, 'singleDefaultValue'); + const june2Button = datetime.locator('[data-month="6"][data-day="2"]'); + const ionChangeSpy = await page.spyOnEvent('ionChange'); + + await june2Button.click(); + expect(ionChangeSpy).toHaveReceivedEventDetail({ + value: ['2022-06-01', '2022-06-02'], + }); + }); + + test('multiple default values across months should display at least one value', async ({ page }) => { + const datetime = await setup(page, 'multipleValuesSeparateMonths'); + const monthYear = datetime.locator('.calendar-month-year'); + await expect(monthYear).toHaveText('April 2022'); + }); + + test('multiple=false and array for defaulut value should switch to first item', async ({ page }) => { + const datetime = await setup(page, 'multipleFalseArrayValue'); + await expect(datetime).toHaveJSProperty('value', '2022-06-01'); + }); + + test('with buttons, should only update value when confirm is called', async ({ page }) => { + const datetime = await setup(page, 'withButtons'); + const june2Button = datetime.locator('[data-month="6"][data-day="2"]'); + + await june2Button.click(); + await page.waitForChanges(); + await expect(datetime).toHaveJSProperty('value', '2022-06-01'); // value should not change yet + + await datetime.evaluate((el: HTMLIonDatetimeElement) => el.confirm()); + await expect(datetime).toHaveJSProperty('value', ['2022-06-01', '2022-06-02']); + }); + + test('clear button should work with multiple values', async ({ page }) => { + const datetime = await setup(page, 'withButtons'); + const june2Button = datetime.locator('[data-month="6"][data-day="2"]'); + const doneButton = datetime.locator('#confirm-button'); + const clearButton = datetime.locator('#clear-button'); + + await june2Button.click(); + await doneButton.click(); + await clearButton.click(); + + await expect(datetime).toHaveJSProperty('value', undefined); + }); + + test('setting value programmatically should update active days', async ({ page }) => { + const datetime = await setup(page, 'singleDefaultValue'); + const juneButtons = datetime.locator('[data-month="6"][data-day]'); + + await datetime.evaluate((el: HTMLIonDatetimeElement) => { + el.value = ['2022-06-01', '2022-06-02', '2022-06-03']; + }); + + for (let i = 0; i < 3; i++) { + await expect(juneButtons.nth(i)).toHaveClass(/calendar-day-active/); + } + + // ensure all days are still highlighted if we click another one after + await juneButtons.nth(3).click(); + for (let i = 0; i < 4; i++) { + await expect(juneButtons.nth(i)).toHaveClass(/calendar-day-active/); + } + }); + + test('clicking day when no default value should set value to only clicked day', async ({ page }) => { + const datetime = await setup(page, 'noDefaultValue'); + const ionChangeSpy = await page.spyOnEvent('ionChange'); + + // can't use specific data-month b/c no default value -- we don't know what it'll be + const firstDayButton = datetime.locator('.calendar-month:nth-child(2) [data-day="1"]'); + + const year = await firstDayButton.getAttribute('data-year'); + let month = await firstDayButton.getAttribute('data-month'); + if (month && month.length < 2) month = '0' + month; // pad with zero + + await firstDayButton.click(); + await ionChangeSpy.next(); + await expect(datetime).toHaveJSProperty('value', [`${year}-${month}-01`]); + }); +}); diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..421fc694fc Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..a9f7f8cae7 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..b044c2166a Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..b37426e065 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..6b3bd53b39 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..1a6e3f7725 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..c79972ace7 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..e455c6b928 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..50c71780f9 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a735375be8 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..39b68bb596 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..f152e23595 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..2c1da7de8a Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..21c6e57fb9 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..cd06e631f8 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..e5b44818db Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..6f0cc69584 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..bfe6e9d02f Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..78ed0abbd1 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..7624915f6b Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..fd32bba504 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..78f61a7965 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..78bef5f4cc Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..22ad1d9847 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..21f2f363c2 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..cc0ea3a94a Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..79572350cd Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..9a238872aa Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..189690a348 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..166b1bae54 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..87eb3a602e Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..43693aadfb Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..1ebabccf1e Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..4f824fe1ec Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..c93d2db565 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..ca16febf35 Binary files /dev/null and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/index.html b/core/src/components/datetime/test/multiple/index.html new file mode 100644 index 0000000000..4c3c6a5202 --- /dev/null +++ b/core/src/components/datetime/test/multiple/index.html @@ -0,0 +1,112 @@ + + + + + Datetime - Multiple + + + + + + + + + + + + + Datetime - Multiple + + + +
+
+

No Default Value

+ +
+
+

Single Default Value

+ +
+
+

Multiple Default Values

+ +
+
+

Multiple Default Values - Separate Months

+ +
+
+

Multiple=False, Array For Default Value

+ +
+
+

With Buttons

+ +
+
+

With Header

+ +
+
+
+
+ + + + diff --git a/core/src/components/datetime/test/parse.spec.ts b/core/src/components/datetime/test/parse.spec.ts index f69362c4ab..641b075349 100644 --- a/core/src/components/datetime/test/parse.spec.ts +++ b/core/src/components/datetime/test/parse.spec.ts @@ -1,4 +1,4 @@ -import { clampDate, getPartsFromCalendarDay } from '../utils/parse'; +import { clampDate, getPartsFromCalendarDay, parseAmPm } from '../utils/parse'; describe('getPartsFromCalendarDay()', () => { it('should extract DatetimeParts from a calendar day element', () => { @@ -61,3 +61,14 @@ describe('clampDate()', () => { expect(value).toStrictEqual(dateParts); }); }); + +describe('parseAmPm()', () => { + it('should return pm when the hour is greater than or equal to 12', () => { + expect(parseAmPm(12)).toEqual('pm'); + expect(parseAmPm(13)).toEqual('pm'); + }); + + it('should return am when the hour is less than 12', () => { + expect(parseAmPm(11)).toEqual('am'); + }); +}); diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts new file mode 100644 index 0000000000..3072c7fc03 --- /dev/null +++ b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts @@ -0,0 +1,324 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +test.describe('datetime: prefer wheel', () => { + test.beforeEach(async ({ page }) => { + await page.setViewportSize({ + width: 400, + height: 200, + }); + }); + /** + * When taking screenshots, be sure to + * set the datetime to size="cover". There + * are rendering quirks on Linux + * if the datetime is too small. + */ + test.describe('datetime: date wheel rendering', () => { + test('should not have visual regressions', async ({ page }) => { + await page.setContent(` + + `); + + expect(await page.screenshot()).toMatchSnapshot(`datetime-wheel-date-diff-${page.getSnapshotSettings()}.png`); + }); + test('should respect the min bounds', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dayValues = page.locator('ion-datetime .day-column .picker-item[data-value]'); + expect(await dayValues.count()).toEqual(27); + }); + test('should respect the max bounds', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dayValues = page.locator('ion-datetime .day-column .picker-item[data-value]'); + expect(await dayValues.count()).toEqual(1); + }); + test('should respect isDateEnabled preference', async ({ page }) => { + await page.setContent(` + + + `); + + await page.waitForSelector('.datetime-ready'); + + const disabledMonths = page.locator('.month-column .picker-item[disabled]'); + const disabledYears = page.locator('.year-column .picker-item[disabled]'); + const disabledDays = page.locator('.day-column .picker-item[disabled]'); + + expect(await disabledMonths.count()).toBe(0); + expect(await disabledYears.count()).toBe(0); + expect(await disabledDays.count()).toBe(15); + }); + test('should respect month, day, and year preferences', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const monthValues = page.locator('.month-column .picker-item:not(.picker-item-empty)'); + const yearValues = page.locator('.year-column .picker-item:not(.picker-item-empty)'); + const dayValues = page.locator('.day-column .picker-item:not(.picker-item-empty)'); + + expect(await monthValues.count()).toBe(2); + expect(await yearValues.count()).toBe(3); + expect(await dayValues.count()).toBe(5); + }); + test('should correctly localize the date data', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const monthValues = page.locator('.month-column .picker-item:not(.picker-item-empty)'); + const dayValues = page.locator('.day-column .picker-item:not(.picker-item-empty)'); + + expect(monthValues).toHaveText(['1月', '2月', '3月']); + expect(dayValues).toHaveText(['1日', '2日', '3日']); + }); + }); + test.describe('datetime: date-time wheel rendering', () => { + test('should not have visual regressions', async ({ page }) => { + await page.setContent(` + + `); + + expect(await page.screenshot()).toMatchSnapshot( + `datetime-wheel-date-time-diff-${page.getSnapshotSettings()}.png` + ); + }); + test('should respect the min bounds', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dayValues = page.locator('ion-datetime .date-column .picker-item[data-value]'); + expect(await dayValues.count()).toEqual(57); + }); + test('should respect the max bounds', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dayValues = page.locator('ion-datetime .date-column .picker-item[data-value]'); + expect(await dayValues.count()).toEqual(41); + }); + test('should respect isDateEnabled preference', async ({ page }) => { + await page.setContent(` + + + `); + + await page.waitForSelector('.datetime-ready'); + + const disabledDates = page.locator('.date-column .picker-item[disabled]'); + + expect(await disabledDates.count()).toBe(44); + }); + test('should respect month, day, and year preferences', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)'); + + expect(await dateValues.count()).toBe(5); + }); + test('should correctly localize the date data', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)'); + + expect(dateValues).toHaveText(['2月1日(火)', '2月2日(水)', '2月3日(木)']); + }); + test('should respect min and max bounds even across years', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)'); + + expect(await dateValues.count()).toBe(427); + }); + }); + test.describe('datetime: time-date wheel rendering', () => { + test('should not have visual regressions', async ({ page }) => { + await page.setContent(` + + `); + + expect(await page.screenshot()).toMatchSnapshot( + `datetime-wheel-time-date-diff-${page.getSnapshotSettings()}.png` + ); + }); + test('should respect the min bounds', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dayValues = page.locator('ion-datetime .date-column .picker-item[data-value]'); + expect(await dayValues.count()).toEqual(57); + }); + test('should respect the max bounds', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dayValues = page.locator('ion-datetime .date-column .picker-item[data-value]'); + expect(await dayValues.count()).toEqual(41); + }); + test('should respect isDateEnabled preference', async ({ page }) => { + await page.setContent(` + + + `); + + await page.waitForSelector('.datetime-ready'); + + const disabledDates = page.locator('.date-column .picker-item[disabled]'); + + expect(await disabledDates.count()).toBe(44); + }); + test('should respect month, day, and year preferences', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)'); + + expect(await dateValues.count()).toBe(5); + }); + test('should correctly localize the date data', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)'); + + expect(dateValues).toHaveText(['2月1日(火)', '2月2日(水)', '2月3日(木)']); + }); + test('should respect min and max bounds even across years', async ({ page }) => { + await page.setContent(` + + `); + + await page.waitForSelector('.datetime-ready'); + + const dateValues = page.locator('.date-column .picker-item:not(.picker-item-empty)'); + + expect(await dateValues.count()).toBe(427); + }); + }); +}); diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..e8a0b52464 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..c805c3a68a Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..de3e83721d Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..e8a0b52464 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..c805c3a68a Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..de3e83721d Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..1d8d86c988 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..0cc9e18b2d Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..561ba9da58 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..1d8d86c988 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..0cc9e18b2d Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..561ba9da58 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..eb34fa7bf8 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..6f66e23f9e Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..452ad0bca3 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..eb34fa7bf8 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..6f66e23f9e Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..452ad0bca3 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..568b85eb3d Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..4de363438a Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..ab7944cef6 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..568b85eb3d Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..4de363438a Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..ab7944cef6 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-date-time-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..fbee02297a Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..8eade3ca88 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..77016953c3 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..fbee02297a Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..8eade3ca88 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..77016953c3 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..8e127c74d3 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..895899a142 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..f7a6893b19 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..8e127c74d3 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..895899a142 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..f7a6893b19 Binary files /dev/null and b/core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts-snapshots/datetime-wheel-time-date-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/prefer-wheel/index.html b/core/src/components/datetime/test/prefer-wheel/index.html new file mode 100644 index 0000000000..505619b03a --- /dev/null +++ b/core/src/components/datetime/test/prefer-wheel/index.html @@ -0,0 +1,58 @@ + + + + + Datetime - Prefer Wheel + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/components/datetime/utils/comparison.ts b/core/src/components/datetime/utils/comparison.ts index 69e29ccfe8..2d2050a9a4 100644 --- a/core/src/components/datetime/utils/comparison.ts +++ b/core/src/components/datetime/utils/comparison.ts @@ -38,13 +38,21 @@ export const isAfter = (baseParts: DatetimeParts, compareParts: DatetimeParts) = ); }; -export const warnIfValueOutOfBounds = (value: DatetimeParts, min: DatetimeParts, max: DatetimeParts) => { - if ((min && isBefore(value, min)) || (max && isAfter(value, max))) { - printIonWarning( - 'The value provided to ion-datetime is out of bounds.\n\n' + - `Min: ${JSON.stringify(min)}\n` + - `Max: ${JSON.stringify(max)}\n` + - `Value: ${JSON.stringify(value)}` - ); +export const warnIfValueOutOfBounds = ( + value: DatetimeParts | DatetimeParts[], + min: DatetimeParts, + max: DatetimeParts +) => { + const valueArray = Array.isArray(value) ? value : [value]; + for (const val of valueArray) { + if ((min && isBefore(val, min)) || (max && isAfter(val, max))) { + printIonWarning( + 'The value provided to ion-datetime is out of bounds.\n\n' + + `Min: ${JSON.stringify(min)}\n` + + `Max: ${JSON.stringify(max)}\n` + + `Value: ${JSON.stringify(value)}` + ); + break; + } } }; diff --git a/core/src/components/datetime/utils/data.ts b/core/src/components/datetime/utils/data.ts index 77f0c8f6d6..ed11abc040 100644 --- a/core/src/components/datetime/utils/data.ts +++ b/core/src/components/datetime/utils/data.ts @@ -1,9 +1,11 @@ import type { Mode } from '../../../interface'; +import type { PickerColumnItem } from '../../picker-column-internal/picker-column-internal-interfaces'; import type { DatetimeParts } from '../datetime-interface'; import { isAfter, isBefore, isSameDay } from './comparison'; -import { getNumDaysInMonth } from './helpers'; -import { getNextMonth, getPreviousMonth } from './manipulation'; +import { getLocalizedDayPeriod, removeDateTzOffset, getFormattedHour, addTimePadding, getTodayLabel } from './format'; +import { getNumDaysInMonth, is24Hour } from './helpers'; +import { getNextMonth, getPreviousMonth, getInternalHourValue } from './manipulation'; /** * Returns the current date as @@ -27,31 +29,8 @@ export const getToday = () => { * object prior to calling toISOString(). * This allows us to get an ISO string * that is in the user's time zone. - * - * Example: - * Time zone offset is 240 - * Meaning: The browser needs to add 240 minutes - * to the Date object to get UTC time. - * What Ionic does: We subtract 240 minutes - * from the Date object. The browser then adds - * 240 minutes in toISOString(). The result - * is a time that is in the user's time zone - * and not UTC. - * - * Note: Some timezones include minute adjustments - * such as 30 or 45 minutes. This is why we use setMinutes - * instead of setHours. - * Example: India Standard Time - * Timezone offset: -330 = -5.5 hours. - * - * List of timezones with 30 and 45 minute timezones: - * https://www.timeanddate.com/time/time-zones-interesting.html */ - const date = new Date(); - const tzOffset = date.getTimezoneOffset(); - date.setMinutes(date.getMinutes() - tzOffset); - - return date.toISOString(); + return removeDateTzOffset(new Date()).toISOString(); }; const minutes = [ @@ -276,13 +255,16 @@ export const generateMonths = (refParts: DatetimeParts): DatetimeParts[] => { ]; }; -export const getPickerMonths = ( +export const getMonthColumnData = ( locale: string, refParts: DatetimeParts, minParts?: DatetimeParts, maxParts?: DatetimeParts, - monthValues?: number[] -) => { + monthValues?: number[], + formatOptions: Intl.DateTimeFormatOptions = { + month: 'long', + } +): PickerColumnItem[] => { const { year } = refParts; const months = []; @@ -298,7 +280,7 @@ export const getPickerMonths = ( processedMonths.forEach((processedMonth) => { const date = new Date(`${processedMonth}/1/${year} GMT+0000`); - const monthString = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' }).format(date); + const monthString = new Intl.DateTimeFormat(locale, { ...formatOptions, timeZone: 'UTC' }).format(date); months.push({ text: monthString, value: processedMonth }); }); } else { @@ -332,7 +314,7 @@ export const getPickerMonths = ( */ const date = new Date(`${i}/1/${year} GMT+0000`); - const monthString = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'UTC' }).format(date); + const monthString = new Intl.DateTimeFormat(locale, { ...formatOptions, timeZone: 'UTC' }).format(date); months.push({ text: monthString, value: i }); } } @@ -340,31 +322,247 @@ export const getPickerMonths = ( return months; }; -export const getCalendarYears = ( +/** + * Returns information regarding + * selectable dates (i.e 1st, 2nd, 3rd, etc) + * within a reference month. + * @param locale The locale to format the date with + * @param refParts The reference month/year to generate dates for + * @param minParts The minimum bound on the date that can be returned + * @param maxParts The maximum bound on the date that can be returned + * @param dayValues The allowed date values + * @returns Date data to be used in ion-picker-column-internal + */ +export const getDayColumnData = ( + locale: string, + refParts: DatetimeParts, + minParts?: DatetimeParts, + maxParts?: DatetimeParts, + dayValues?: number[], + formatOptions: Intl.DateTimeFormatOptions = { + day: 'numeric', + } +): PickerColumnItem[] => { + const { month, year } = refParts; + const days = []; + + /** + * If we have max/min bounds that in the same + * month/year as the refParts, we should + * use the define day as the max/min day. + * Otherwise, fallback to the max/min days in a month. + */ + const numDaysInMonth = getNumDaysInMonth(month, year); + const maxDay = maxParts?.day && maxParts.year === year && maxParts.month === month ? maxParts.day : numDaysInMonth; + const minDay = minParts?.day && minParts.year === year && minParts.month === month ? minParts.day : 1; + + if (dayValues !== undefined) { + let processedDays = dayValues; + processedDays = processedDays.filter((day) => day >= minDay && day <= maxDay); + processedDays.forEach((processedDay) => { + const date = new Date(`${month}/${processedDay}/${year} GMT+0000`); + + const dayString = new Intl.DateTimeFormat(locale, { ...formatOptions, timeZone: 'UTC' }).format(date); + days.push({ text: dayString, value: processedDay }); + }); + } else { + for (let i = minDay; i <= maxDay; i++) { + const date = new Date(`${month}/${i}/${year} GMT+0000`); + + const dayString = new Intl.DateTimeFormat(locale, { ...formatOptions, timeZone: 'UTC' }).format(date); + days.push({ text: dayString, value: i }); + } + } + + return days; +}; + +export const getYearColumnData = ( refParts: DatetimeParts, minParts?: DatetimeParts, maxParts?: DatetimeParts, yearValues?: number[] -) => { +): PickerColumnItem[] => { + let processedYears = []; if (yearValues !== undefined) { - let processedYears = yearValues; + processedYears = yearValues; if (maxParts?.year !== undefined) { processedYears = processedYears.filter((year) => year <= maxParts.year!); } if (minParts?.year !== undefined) { processedYears = processedYears.filter((year) => year >= minParts.year!); } - return processedYears; } else { const { year } = refParts; const maxYear = maxParts?.year || year; const minYear = minParts?.year || year - 100; - const years = []; for (let i = maxYear; i >= minYear; i--) { - years.push(i); + processedYears.push(i); } + } + + return processedYears.map((year) => ({ + text: `${year}`, + value: year, + })); +}; + +interface CombinedDateColumnData { + parts: DatetimeParts[]; + items: PickerColumnItem[]; +} + +/** + * Given a starting date and an upper bound, + * this functions returns an array of all + * month objects in that range. + */ +const getAllMonthsInRange = (currentParts: DatetimeParts, maxParts: DatetimeParts): DatetimeParts[] => { + if (currentParts.month === maxParts.month && currentParts.year === maxParts.year) { + return [currentParts]; + } + + return [currentParts, ...getAllMonthsInRange(getNextMonth(currentParts), maxParts)]; +}; + +/** + * Creates and returns picker items + * that represent the days in a month. + * Example: "Thu, Jun 2" + */ +export const getCombinedDateColumnData = ( + locale: string, + refParts: DatetimeParts, + todayParts: DatetimeParts, + minParts: DatetimeParts, + maxParts: DatetimeParts, + dayValues?: number[], + monthValues?: number[] +): CombinedDateColumnData => { + let items: PickerColumnItem[] = []; + let parts: DatetimeParts[] = []; + + /** + * Get all month objects from the min date + * to the max date. Note: Do not use getMonthColumnData + * as that function only generates dates within a + * single year. + */ + let months = getAllMonthsInRange(minParts, maxParts); - return years; + /** + * Filter out any disallowed month values. + */ + if (monthValues) { + months = months.filter(({ month }) => monthValues.includes(month)); } + + /** + * Get all of the days in the month. + * From there, generate an array where + * each item has the month, date, and day + * of work as the text. + */ + months.forEach((monthObject) => { + const referenceMonth = { month: monthObject.month, day: null, year: refParts.year }; + const monthDays = getDayColumnData(locale, referenceMonth, minParts, maxParts, dayValues, { + month: 'short', + day: 'numeric', + weekday: 'short', + }); + + const dateParts: DatetimeParts[] = []; + const dateColumnItems: PickerColumnItem[] = []; + + monthDays.forEach((dayObject) => { + const isToday = isSameDay({ ...referenceMonth, day: dayObject.value as number }, todayParts); + + /** + * Today's date should read as "Today" (localized) + * not the actual date string + */ + dateColumnItems.push({ + text: isToday ? getTodayLabel(locale) : dayObject.text, + value: `${refParts.year}-${monthObject.month}-${dayObject.value}`, + }); + + /** + * When selecting a date in the wheel picker + * we need access to the raw datetime parts data. + * The picker column only accepts values of + * type string or number, so we need to return + * two sets of data: A data set to be passed + * to the picker column, and a data set to + * be used to reference the raw data when + * updating the picker column value. + */ + dateParts.push({ + month: monthObject.month, + year: refParts.year, + day: dayObject.value as number, + }); + }); + parts = [...parts, ...dateParts]; + items = [...items, ...dateColumnItems]; + }); + + return { + parts, + items, + }; +}; + +export const getTimeColumnsData = ( + locale: string, + refParts: DatetimeParts, + hourCycle?: 'h23' | 'h12', + minParts?: DatetimeParts, + maxParts?: DatetimeParts, + allowedHourValues?: number[], + allowedMinuteVaues?: number[] +): { [key: string]: PickerColumnItem[] } => { + const use24Hour = is24Hour(locale, hourCycle); + const { hours, minutes, am, pm } = generateTime( + refParts, + use24Hour ? 'h23' : 'h12', + minParts, + maxParts, + allowedHourValues, + allowedMinuteVaues + ); + + const hoursItems = hours.map((hour) => { + return { + text: getFormattedHour(hour, use24Hour), + value: getInternalHourValue(hour, use24Hour, refParts.ampm), + }; + }); + const minutesItems = minutes.map((minute) => { + return { + text: addTimePadding(minute), + value: minute, + }; + }); + + const dayPeriodItems = []; + if (am && !use24Hour) { + dayPeriodItems.push({ + text: getLocalizedDayPeriod(locale, 'am'), + value: 'am', + }); + } + + if (pm && !use24Hour) { + dayPeriodItems.push({ + text: getLocalizedDayPeriod(locale, 'pm'), + value: 'pm', + }); + } + + return { + minutesData: minutesItems, + hoursData: hoursItems, + dayPeriodData: dayPeriodItems, + }; }; diff --git a/core/src/components/datetime/utils/format.ts b/core/src/components/datetime/utils/format.ts index b91e191bea..64747f770e 100644 --- a/core/src/components/datetime/utils/format.ts +++ b/core/src/components/datetime/utils/format.ts @@ -1,30 +1,26 @@ import type { DatetimeParts } from '../datetime-interface'; -const get12HourTime = (hour: number) => { - return hour % 12 || 12; -}; +import { convertDataToISO } from './manipulation'; -const getFormattedAMPM = (ampm?: string) => { - if (ampm === undefined) { +const getFormattedDayPeriod = (dayPeriod?: string) => { + if (dayPeriod === undefined) { return ''; } - return ampm.toUpperCase(); + return dayPeriod.toUpperCase(); }; -export const getFormattedTime = (refParts: DatetimeParts, use24Hour: boolean): string => { +export const getLocalizedTime = (locale: string, refParts: DatetimeParts, use24Hour: boolean): string => { if (refParts.hour === undefined || refParts.minute === undefined) { return 'Invalid Time'; } - const hour = use24Hour ? getFormattedHour(refParts.hour, use24Hour) : get12HourTime(refParts.hour); - const minute = addTimePadding(refParts.minute); - - if (use24Hour) { - return `${hour}:${minute}`; - } - - return `${hour}:${minute} ${getFormattedAMPM(refParts.ampm)}`; + return new Intl.DateTimeFormat(locale, { + hour: 'numeric', + minute: 'numeric', + timeZone: 'UTC', + hour12: !use24Hour, + }).format(new Date(convertDataToISO(refParts))); }; /** @@ -103,3 +99,114 @@ export const getMonthAndYear = (locale: string, refParts: DatetimeParts) => { const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`); return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date); }; + +/** + * Given a locale and a date object, + * return a formatted string that includes + * the short month, numeric day, and full year. + * Example: Apr 22, 2021 + */ +export const getMonthDayAndYear = (locale: string, refParts: DatetimeParts) => { + return getLocalizedDateTime(locale, refParts, { month: 'short', day: 'numeric', year: 'numeric' }); +}; + +/** + * Wrapper function for Intl.DateTimeFormat. + * Allows developers to apply an allowed format to DatetimeParts. + * This function also has built in safeguards for older browser bugs + * with Intl.DateTimeFormat. + */ +export const getLocalizedDateTime = ( + locale: string, + refParts: DatetimeParts, + options: Intl.DateTimeFormatOptions +): string => { + const timeString = !!refParts.hour && !!refParts.minute ? ` ${refParts.hour}:${refParts.minute}` : ''; + const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year}${timeString} GMT+0000`); + return new Intl.DateTimeFormat(locale, { ...options, timeZone: 'UTC' }).format(date); +}; + +/** + * Gets a localized version of "Today" + * Falls back to "Today" in English for + * browsers that do not support RelativeTimeFormat. + */ +export const getTodayLabel = (locale: string) => { + if ('RelativeTimeFormat' in Intl) { + const label = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(0, 'day'); + return label.charAt(0).toUpperCase() + label.slice(1); + } else { + return 'Today'; + } +}; + +/** + * When calling toISOString(), the browser + * will convert the date to UTC time by either adding + * or subtracting the time zone offset. + * To work around this, we need to either add + * or subtract the time zone offset to the Date + * object prior to calling toISOString(). + * This allows us to get an ISO string + * that is in the user's time zone. + * + * Example: + * Time zone offset is 240 + * Meaning: The browser needs to add 240 minutes + * to the Date object to get UTC time. + * What Ionic does: We subtract 240 minutes + * from the Date object. The browser then adds + * 240 minutes in toISOString(). The result + * is a time that is in the user's time zone + * and not UTC. + * + * Note: Some timezones include minute adjustments + * such as 30 or 45 minutes. This is why we use setMinutes + * instead of setHours. + * Example: India Standard Time + * Timezone offset: -330 = -5.5 hours. + * + * List of timezones with 30 and 45 minute timezones: + * https://www.timeanddate.com/time/time-zones-interesting.html + */ +export const removeDateTzOffset = (date: Date) => { + const tzOffset = date.getTimezoneOffset(); + date.setMinutes(date.getMinutes() - tzOffset); + return date; +}; + +const DATE_AM = removeDateTzOffset(new Date('2022T01:00')); +const DATE_PM = removeDateTzOffset(new Date('2022T13:00')); + +/** + * Formats the locale's string representation of the day period (am/pm) for a given + * ref parts day period. + * + * @param locale The locale to format the day period in. + * @param value The date string, in ISO format. + * @returns The localized day period (am/pm) representation of the given value. + */ +export const getLocalizedDayPeriod = (locale: string, dayPeriod: 'am' | 'pm' | undefined) => { + const date = dayPeriod === 'am' ? DATE_AM : DATE_PM; + const localizedDayPeriod = new Intl.DateTimeFormat(locale, { + hour: 'numeric', + timeZone: 'UTC', + }) + .formatToParts(date) + .find((part) => part.type === 'dayPeriod'); + + if (localizedDayPeriod) { + return localizedDayPeriod.value; + } + + return getFormattedDayPeriod(dayPeriod); +}; + +/** + * Formats the datetime's value to a string, for use in the native input. + * + * @param value The value to format, either an ISO string or an array thereof. + */ +export const formatValue = (value: string | string[] | null | undefined) => { + return Array.isArray(value) ? value.join(',') : value; +}; diff --git a/core/src/components/datetime/utils/helpers.ts b/core/src/components/datetime/utils/helpers.ts index 4bab50e6f2..141b965502 100644 --- a/core/src/components/datetime/utils/helpers.ts +++ b/core/src/components/datetime/utils/helpers.ts @@ -92,3 +92,14 @@ export const isMonthFirstLocale = (locale: string) => { return parts[0].type === 'month'; }; + +/** + * Determines if the given locale formats the day period (am/pm) to the + * left or right of the hour. + * @param locale The locale to check. + * @returns `true` if the locale formats the day period to the left of the hour. + */ +export const isLocaleDayPeriodRTL = (locale: string) => { + const parts = new Intl.DateTimeFormat(locale, { hour: 'numeric' }).formatToParts(new Date()); + return parts[0].type === 'dayPeriod'; +}; diff --git a/core/src/components/datetime/utils/manipulation.ts b/core/src/components/datetime/utils/manipulation.ts index c522e63905..50d019ac16 100644 --- a/core/src/components/datetime/utils/manipulation.ts +++ b/core/src/components/datetime/utils/manipulation.ts @@ -10,7 +10,14 @@ const fourDigit = (val: number | undefined): string => { return ('000' + (val !== undefined ? Math.abs(val) : '0')).slice(-4); }; -export const convertDataToISO = (data: any): string => { +export function convertDataToISO(data: DatetimeParts): string; +export function convertDataToISO(data: DatetimeParts[]): string[]; +export function convertDataToISO(data: DatetimeParts | DatetimeParts[]): string | string[]; +export function convertDataToISO(data: DatetimeParts | DatetimeParts[]): string | string[] { + if (Array.isArray(data)) { + return data.map((parts) => convertDataToISO(parts)); + } + // https://www.w3.org/TR/NOTE-datetime let rtn = ''; if (data.year !== undefined) { @@ -49,7 +56,7 @@ export const convertDataToISO = (data: any): string => { } return rtn; -}; +} /** * Converts an 12 hour value to 24 hours. diff --git a/core/src/components/datetime/utils/parse.ts b/core/src/components/datetime/utils/parse.ts index c6e5e47d5f..4eb54ac7d2 100644 --- a/core/src/components/datetime/utils/parse.ts +++ b/core/src/components/datetime/utils/parse.ts @@ -55,7 +55,16 @@ export const getPartsFromCalendarDay = (el: HTMLElement): DatetimeParts => { * We do not use the JS Date object here because * it adjusts the date for the current timezone. */ -export const parseDate = (val: string | undefined | null): any | undefined => { +export function parseDate(val: string): DatetimeParts; +export function parseDate(val: string[]): DatetimeParts[]; +export function parseDate(val: undefined | null): undefined; +export function parseDate(val: string | string[]): DatetimeParts | DatetimeParts[]; +export function parseDate(val: string | string[] | undefined | null): DatetimeParts | DatetimeParts[] | undefined; +export function parseDate(val: string | string[] | undefined | null): DatetimeParts | DatetimeParts[] | undefined { + if (Array.isArray(val)) { + return val.map((valStr) => parseDate(valStr)); + } + // manually parse IS0 cuz Date.parse cannot be trusted // ISO 8601 format: 1994-12-15T13:47:20Z let parse: any[] | null = null; @@ -97,17 +106,16 @@ export const parseDate = (val: string | undefined | null): any | undefined => { } } + // can also get second and millisecond from parse[6] and parse[7] if needed return { year: parse[1], month: parse[2], day: parse[3], hour: parse[4], minute: parse[5], - second: parse[6], - millisecond: parse[7], tzOffset, }; -}; +} export const clampDate = ( dateParts: DatetimeParts, @@ -121,3 +129,12 @@ export const clampDate = ( } return dateParts; }; + +/** + * Parses an hour and returns if the value is in the morning (am) or afternoon (pm). + * @param hour The hour to format, should be 0-23 + * @returns `pm` if the hour is greater than or equal to 12, `am` if less than 12. + */ +export const parseAmPm = (hour: number) => { + return hour >= 12 ? 'pm' : 'am'; +}; diff --git a/core/src/components/datetime/utils/state.ts b/core/src/components/datetime/utils/state.ts index 238f4000d1..1ad345d4e1 100644 --- a/core/src/components/datetime/utils/state.ts +++ b/core/src/components/datetime/utils/state.ts @@ -83,22 +83,40 @@ export const isDayDisabled = ( }; /** - * Given a locale, a date, the selected date, and today's date, + * Given a locale, a date, the selected date(s), and today's date, * generate the state for a given calendar day button. */ export const getCalendarDayState = ( locale: string, refParts: DatetimeParts, - activeParts: DatetimeParts, + activeParts: DatetimeParts | DatetimeParts[], todayParts: DatetimeParts, minParts?: DatetimeParts, maxParts?: DatetimeParts, dayValues?: number[] ) => { - const isActive = isSameDay(refParts, activeParts); + /** + * activeParts signals what day(s) are currently selected in the datetime. + * If multiple="true", this will be an array, but the logic in this util + * is the same whether we have one selected day or many because we're only + * calculating the state for one button. So, we treat a single activeParts value + * the same as an array of length one. + */ + const activePartsArray = Array.isArray(activeParts) ? activeParts : [activeParts]; + + /** + * The day button is active if it is selected, or in other words, if refParts + * matches at least one selected date. + */ + const isActive = activePartsArray.find((parts) => isSameDay(refParts, parts)) !== undefined; + const isToday = isSameDay(refParts, todayParts); const disabled = isDayDisabled(refParts, minParts, maxParts, dayValues); + /** + * Note that we always return one object regardless of whether activeParts + * was an array, since we pare down to one value for isActive. + */ return { disabled, isActive, diff --git a/core/src/components/modal/gestures/sheet.ts b/core/src/components/modal/gestures/sheet.ts index 72cee60a2a..cd1c51247f 100644 --- a/core/src/components/modal/gestures/sheet.ts +++ b/core/src/components/modal/gestures/sheet.ts @@ -294,66 +294,72 @@ export const createSheetGesture = ( */ gesture.enable(false); - animation - .onFinish( - () => { - if (shouldRemainOpen) { - /** - * Once the snapping animation completes, - * we need to reset the animation to go - * from 0 to 1 so users can swipe in any direction. - * We then set the animation offset to the current - * breakpoint so that it starts at the snapped position. - */ - if (wrapperAnimation && backdropAnimation) { - raf(() => { - wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]); - backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]); - animation.progressStart(true, 1 - snapToBreakpoint); - currentBreakpoint = snapToBreakpoint; - onBreakpointChange(currentBreakpoint); - - /** - * If the sheet is fully expanded, we can safely - * enable scrolling again. - */ - if (contentEl && currentBreakpoint === breakpoints[breakpoints.length - 1]) { - contentEl.scrollY = true; - } - - /** - * Backdrop should become enabled - * after the backdropBreakpoint value - */ - const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint; - if (shouldEnableBackdrop) { - enableBackdrop(); - } else { - disableBackdrop(); - } - - gesture.enable(true); - }); - } else { - gesture.enable(true); - } - } - - /** - * This must be a one time callback - * otherwise a new callback will - * be added every time onEnd runs. - */ - }, - { oneTimeCallback: true } - ) - .progressEnd(1, 0, 500); - if (shouldPreventDismiss) { handleCanDismiss(baseEl, animation); } else if (!shouldRemainOpen) { onDismiss(); } + + return new Promise((resolve) => { + animation + .onFinish( + () => { + if (shouldRemainOpen) { + /** + * Once the snapping animation completes, + * we need to reset the animation to go + * from 0 to 1 so users can swipe in any direction. + * We then set the animation offset to the current + * breakpoint so that it starts at the snapped position. + */ + if (wrapperAnimation && backdropAnimation) { + raf(() => { + wrapperAnimation.keyframes([...SheetDefaults.WRAPPER_KEYFRAMES]); + backdropAnimation.keyframes([...SheetDefaults.BACKDROP_KEYFRAMES]); + animation.progressStart(true, 1 - snapToBreakpoint); + currentBreakpoint = snapToBreakpoint; + onBreakpointChange(currentBreakpoint); + + /** + * If the sheet is fully expanded, we can safely + * enable scrolling again. + */ + if (contentEl && currentBreakpoint === breakpoints[breakpoints.length - 1]) { + contentEl.scrollY = true; + } + + /** + * Backdrop should become enabled + * after the backdropBreakpoint value + */ + const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint; + if (shouldEnableBackdrop) { + enableBackdrop(); + } else { + disableBackdrop(); + } + + gesture.enable(true); + resolve(); + }); + } else { + gesture.enable(true); + resolve(); + } + } else { + resolve(); + } + + /** + * This must be a one time callback + * otherwise a new callback will + * be added every time onEnd runs. + */ + }, + { oneTimeCallback: true } + ) + .progressEnd(1, 0, 500); + }); }; const gesture = createGesture({ diff --git a/core/src/components/modal/modal-interface.ts b/core/src/components/modal/modal-interface.ts index d0e635e750..187e3c92bf 100644 --- a/core/src/components/modal/modal-interface.ts +++ b/core/src/components/modal/modal-interface.ts @@ -48,3 +48,8 @@ export interface ModalCustomEvent extends CustomEvent { * @deprecated - Use { [key: string]: any } directly instead. */ export type ModalAttributes = { [key: string]: any }; + +/** + * The behavior setting for modals when the handle is pressed. + */ +export type ModalHandleBehavior = 'none' | 'cycle'; diff --git a/core/src/components/modal/modal.scss b/core/src/components/modal/modal.scss index c7406e2e9e..72bab0e5b5 100644 --- a/core/src/components/modal/modal.scss +++ b/core/src/components/modal/modal.scss @@ -50,7 +50,8 @@ contain: strict; } -.modal-wrapper, ion-backdrop { +.modal-wrapper, +ion-backdrop { pointer-events: auto; } @@ -124,9 +125,30 @@ */ transform: translateZ(0); + border: 0; + background: var(--ion-color-step-350, #c0c0be); + cursor: pointer; + z-index: 11; + + &::before { + /** + * Adds a 4px tap area to the perimeter + * of the handle. + */ + @include padding(4px, 4px, 4px, 4px); + + position: absolute; + + width: 36px; + height: 5px; + + transform: translate(-50%, -50%); + + content: ""; + } } /** diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index 766423a948..12056b5204 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -12,6 +12,7 @@ import type { Gesture, ModalAttributes, ModalBreakpointChangeEventDetail, + ModalHandleBehavior, OverlayEventDetail, OverlayInterface, } from '../../interface'; @@ -56,6 +57,7 @@ export class Modal implements ComponentInterface, OverlayInterface { private modalId?: string; private coreDelegate: FrameworkDelegate = CoreDelegate(); private currentTransition?: Promise; + private sheetTransition?: Promise; private destroyTriggerInteraction?: () => void; private isSheetModal = false; private currentBreakpoint?: number; @@ -63,7 +65,7 @@ export class Modal implements ComponentInterface, OverlayInterface { private backdropEl?: HTMLIonBackdropElement; private sortedBreakpoints?: number[]; private keyboardOpenCallback?: () => void; - private moveSheetToBreakpoint?: (options: MoveSheetToBreakpointOptions) => void; + private moveSheetToBreakpoint?: (options: MoveSheetToBreakpointOptions) => Promise; private inline = false; private workingDelegate?: FrameworkDelegate; @@ -140,6 +142,17 @@ export class Modal implements ComponentInterface, OverlayInterface { */ @Prop() handle?: boolean; + /** + * The interaction behavior for the sheet modal when the handle is pressed. + * + * Defaults to `"none"`, which means the modal will not change size or position when the handle is pressed. + * Set to `"cycle"` to let the modal cycle between available breakpoints when pressed. + * + * Handle behavior is unavailable when the `handle` property is set to `false` or + * when the `breakpoints` property is not set (using a fullscreen or card modal). + */ + @Prop() handleBehavior?: ModalHandleBehavior = 'none'; + /** * The component to display inside of the modal. * @internal @@ -222,6 +235,19 @@ export class Modal implements ComponentInterface, OverlayInterface { this.configureTriggerInteraction(); } + /** + * If `true`, the component passed into `ion-modal` will + * automatically be mounted when the modal is created. The + * component will remain mounted even when the modal is dismissed. + * However, the component will be destroyed when the modal is + * destroyed. This property is not reactive and should only be + * used when initially creating a modal. + * + * Note: This feature only applies to inline modals in JavaScript + * frameworks such as Angular, React, and Vue. + */ + @Prop() keepContentsMounted = false; + /** * TODO (FW-937) * This needs to default to true in the next @@ -745,11 +771,13 @@ export class Modal implements ComponentInterface, OverlayInterface { } if (moveSheetToBreakpoint) { - moveSheetToBreakpoint({ + this.sheetTransition = moveSheetToBreakpoint({ breakpoint, breakpointOffset: 1 - currentBreakpoint!, canDismiss: canDismiss !== undefined && canDismiss !== true && breakpoints![0] === 0, }); + await this.sheetTransition; + this.sheetTransition = undefined; } } @@ -761,7 +789,55 @@ export class Modal implements ComponentInterface, OverlayInterface { return this.currentBreakpoint; } + private async moveToNextBreakpoint() { + const { breakpoints, currentBreakpoint } = this; + + if (!breakpoints || currentBreakpoint == null) { + /** + * If the modal does not have breakpoints and/or the current + * breakpoint is not set, we can't move to the next breakpoint. + */ + return false; + } + + const allowedBreakpoints = breakpoints.filter((b) => b !== 0); + const currentBreakpointIndex = allowedBreakpoints.indexOf(currentBreakpoint); + const nextBreakpointIndex = (currentBreakpointIndex + 1) % allowedBreakpoints.length; + const nextBreakpoint = allowedBreakpoints[nextBreakpointIndex]; + + /** + * Sets the current breakpoint to the next available breakpoint. + * If the current breakpoint is the last breakpoint, we set the current + * breakpoint to the first non-zero breakpoint to avoid dismissing the sheet. + */ + await this.setCurrentBreakpoint(nextBreakpoint); + return true; + } + + private onHandleClick = () => { + const { sheetTransition, handleBehavior } = this; + if (handleBehavior !== 'cycle' || sheetTransition !== undefined) { + /** + * The sheet modal should not advance to the next breakpoint + * if the handle behavior is not `cycle` or if the handle + * is clicked while the sheet is moving to a breakpoint. + */ + return; + } + this.moveToNextBreakpoint(); + }; + private onBackdropTap = () => { + const { sheetTransition } = this; + if (sheetTransition !== undefined) { + /** + * When the handle is double clicked at the largest breakpoint, + * it will start to move to the first breakpoint. While transitioning, + * the backdrop will often receive the second click. We prevent the + * backdrop from dismissing the modal while moving between breakpoints. + */ + return; + } this.dismiss(undefined, BACKDROP); }; @@ -779,12 +855,13 @@ export class Modal implements ComponentInterface, OverlayInterface { }; render() { - const { handle, isSheetModal, presentingElement, htmlAttributes } = this; + const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior } = this; const showHandle = handle !== false && isSheetModal; const mode = getIonMode(this); const { modalId } = this; const isCardModal = presentingElement !== undefined && mode === 'ios'; + const isHandleCycle = handleBehavior === 'cycle'; return ( } diff --git a/core/src/components/modal/test/sheet/index.html b/core/src/components/modal/test/sheet/index.html index 2731b5e84d..6ac6b24488 100644 --- a/core/src/components/modal/test/sheet/index.html +++ b/core/src/components/modal/test/sheet/index.html @@ -115,6 +115,14 @@ Present Sheet Modal (Custom Height) + + + Present Sheet Modal (HandleBehavior: Cycle) + Present Sheet Modal (Custom Handle) diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts b/core/src/components/modal/test/sheet/modal.e2e.ts index e65a2268dc..9f580605d0 100644 --- a/core/src/components/modal/test/sheet/modal.e2e.ts +++ b/core/src/components/modal/test/sheet/modal.e2e.ts @@ -195,3 +195,78 @@ test.describe('sheet modal: setting the breakpoint', () => { expect(updatedBreakpoint).toBe(0.5); }); }); + +test.describe('sheet modal: clicking the handle', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/src/components/modal/test/sheet'); + }); + + test('should advance to the next breakpoint when handleBehavior is cycle', async ({ page }) => { + const ionBreakpointDidChange = await page.spyOnEvent('ionBreakpointDidChange'); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const modal = page.locator('ion-modal'); + + await page.click('#handle-behavior-cycle-modal'); + await ionModalDidPresent.next(); + + const handle = page.locator('ion-modal .modal-handle'); + + await handle.click(); + await ionBreakpointDidChange.next(); + + await expect(await modal.evaluate((el: HTMLIonModalElement) => el.getCurrentBreakpoint())).toBe(0.5); + + await handle.click(); + await ionBreakpointDidChange.next(); + + await expect(await modal.evaluate((el: HTMLIonModalElement) => el.getCurrentBreakpoint())).toBe(0.75); + + await handle.click(); + await ionBreakpointDidChange.next(); + + await expect(await modal.evaluate((el: HTMLIonModalElement) => el.getCurrentBreakpoint())).toBe(1); + + await handle.click(); + await ionBreakpointDidChange.next(); + + // Advancing from the last breakpoint should change the breakpoint to the first non-zero breakpoint + await expect(await modal.evaluate((el: HTMLIonModalElement) => el.getCurrentBreakpoint())).toBe(0.25); + }); + + test('should not advance the breakpoint when handleBehavior is none', async ({ page }) => { + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const modal = page.locator('ion-modal'); + + await page.click('#sheet-modal'); + await ionModalDidPresent.next(); + + const handle = page.locator('ion-modal .modal-handle'); + + await handle.click(); + + await expect(await modal.evaluate((el: HTMLIonModalElement) => el.getCurrentBreakpoint())).toBe(0.25); + }); + + test('should not dismiss the modal when backdrop is clicked and breakpoint is moving', async ({ page }) => { + const ionBreakpointDidChange = await page.spyOnEvent('ionBreakpointDidChange'); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const modal = page.locator('ion-modal'); + + await page.click('#handle-behavior-cycle-modal'); + await ionModalDidPresent.next(); + + const handle = page.locator('ion-modal .modal-handle'); + const backdrop = page.locator('ion-modal ion-backdrop'); + + await handle.click(); + backdrop.click(); + + await ionBreakpointDidChange.next(); + + await handle.click(); + + await ionBreakpointDidChange.next(); + + await expect(await modal.evaluate((el: HTMLIonModalElement) => el.getCurrentBreakpoint())).toBe(0.75); + }); +}); diff --git a/core/src/components/picker-column-internal/picker-column-internal-interfaces.ts b/core/src/components/picker-column-internal/picker-column-internal-interfaces.ts index 6a2e27449f..e6f811502b 100644 --- a/core/src/components/picker-column-internal/picker-column-internal-interfaces.ts +++ b/core/src/components/picker-column-internal/picker-column-internal-interfaces.ts @@ -1,4 +1,5 @@ export interface PickerColumnItem { text: string; value: string | number; + disabled?: boolean; } diff --git a/core/src/components/picker-column-internal/picker-column-internal.scss b/core/src/components/picker-column-internal/picker-column-internal.scss index 6fbedf8fff..09834246f0 100644 --- a/core/src/components/picker-column-internal/picker-column-internal.scss +++ b/core/src/components/picker-column-internal/picker-column-internal.scss @@ -34,21 +34,49 @@ } :host .picker-item { + @include padding(0); + @include margin(0); + + display: block; + + width: 100%; + height: 34px; + border: 0px; + + outline: none; + + background: transparent; + + color: inherit; + + font-size: inherit; + line-height: 34px; + text-align: inherit; + text-overflow: ellipsis; white-space: nowrap; + cursor: pointer; + overflow: hidden; scroll-snap-align: center; } -:host .picker-item-empty { +:host .picker-item-empty, +:host .picker-item.picker-item-disabled { scroll-snap-align: none; + + cursor: default; +} + +:host .picker-item.picker-item-disabled { + opacity: 0.4; } :host(.picker-column-active) .picker-item.picker-item-active { diff --git a/core/src/components/picker-column-internal/picker-column-internal.tsx b/core/src/components/picker-column-internal/picker-column-internal.tsx index 1b0e709cdf..64daf337c8 100644 --- a/core/src/components/picker-column-internal/picker-column-internal.tsx +++ b/core/src/components/picker-column-internal/picker-column-internal.tsx @@ -36,6 +36,70 @@ 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. @@ -155,7 +219,7 @@ export class PickerColumnInternal implements ComponentInterface { async setValue(value?: string | number) { const { items } = this; this.value = value; - const findItem = items.find((item) => item.value === value); + const findItem = items.find((item) => item.value === value && item.disabled !== true); if (findItem) { this.ionChange.emit(findItem); } @@ -254,11 +318,15 @@ export class PickerColumnInternal implements ComponentInterface { const centerX = bbox.x + bbox.width / 2; const centerY = bbox.y + bbox.height / 2; - const activeElement = el.shadowRoot!.elementFromPoint(centerX, centerY) as HTMLElement; + const activeElement = el.shadowRoot!.elementFromPoint(centerX, centerY) as HTMLButtonElement; if (activeEl !== null) { activeEl.classList.remove(PICKER_COL_ACTIVE); } + if (activeElement.disabled) { + return; + } + /** * If we are selecting a new value, * we need to run haptics again. @@ -321,7 +389,9 @@ export class PickerColumnInternal implements ComponentInterface { }; get activeItem() { - return getElementRoot(this.el).querySelector(`.picker-item[data-value="${this.value}"]`) as HTMLElement | null; + return getElementRoot(this.el).querySelector( + `.picker-item[data-value="${this.value}"]:not([disabled])` + ) as HTMLElement | null; } render() { @@ -341,17 +411,32 @@ export class PickerColumnInternal implements ComponentInterface {
 
 
{items.map((item, index) => { + { + /* + Users should be able to tab + between multiple columns. As a result, + we set tabindex here so that tabbing switches + between columns instead of buttons. Users + can still use arrow keys on the keyboard to + navigate the column up and down. + */ + } return ( -
{ this.centerPickerItemInView(ev.target as HTMLElement); }} + disabled={item.disabled} > {item.text} -
+ ); })}
 
diff --git a/core/src/components/picker-column-internal/test/disabled/index.html b/core/src/components/picker-column-internal/test/disabled/index.html new file mode 100644 index 0000000000..5ff9b28f37 --- /dev/null +++ b/core/src/components/picker-column-internal/test/disabled/index.html @@ -0,0 +1,71 @@ + + + + + Picker Column Internal - Basic + + + + + + + + + + + + + Picker Column Internal - Disabled + + + +
+
+

Default

+ + + +
+
+
+ +
+ + diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts new file mode 100644 index 0000000000..879f3ff6e8 --- /dev/null +++ b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts @@ -0,0 +1,130 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +test.describe('picker-column-internal: disabled', () => { + test('should not have visual regressions', async ({ page }) => { + await page.setContent(` + + + + + + `); + + const picker = page.locator('ion-picker-internal'); + expect(await picker.screenshot()).toMatchSnapshot(`picker-internal-disabled-${page.getSnapshotSettings()}.png`); + }); + test('all picker items should be enabled by default', async ({ page }) => { + await page.setContent(` + + + + + + `); + + const pickerItems = page.locator( + 'ion-picker-column-internal .picker-item:not(.picker-item-empty, .picker-item-disabled)' + ); + + expect(await pickerItems.count()).toBe(3); + }); + test('disabled picker item should not be interactive', async ({ page }) => { + await page.setContent(` + + + + + + `); + + const disabledItem = page.locator('ion-picker-column-internal .picker-item.picker-item-disabled'); + expect(disabledItem).not.toBeEnabled(); + }); + test('disabled picker item should not be considered active', async ({ page }) => { + await page.setContent(` + + + + + + `); + + const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]'); + expect(disabledItem).not.toHaveClass(/picker-item-active/); + }); + test('setting the value to a disabled item should not cause that item to be active', async ({ page }) => { + await page.setContent(` + + + + + + `); + + const pickerColumn = page.locator('ion-picker-column-internal'); + await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => (el.value = 'b')); + + await page.waitForChanges(); + + const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]'); + expect(disabledItem).toHaveClass(/picker-item-disabled/); + expect(disabledItem).not.toHaveClass(/picker-item-active/); + }); + test('defaulting the value to a disabled item should not cause that item to be active', async ({ page }) => { + await page.setContent(` + + + + + + `); + + const disabledItem = page.locator('ion-picker-column-internal .picker-item[data-value="b"]'); + expect(disabledItem).toHaveClass(/picker-item-disabled/); + expect(disabledItem).not.toHaveClass(/picker-item-active/); + }); +}); diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..0c65639698 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..84678dcd30 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-ltr-Mobile-Safari-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..17e7e4e3c4 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..0c65639698 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..84678dcd30 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-rtl-Mobile-Safari-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..17e7e4e3c4 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-ltr-Mobile-Chrome-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..d3ffff33e6 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-ltr-Mobile-Firefox-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..60a07013e2 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-ltr-Mobile-Safari-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..8daf2266e4 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-rtl-Mobile-Chrome-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..d3ffff33e6 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-rtl-Mobile-Firefox-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..60a07013e2 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-rtl-Mobile-Safari-linux.png b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..8daf2266e4 Binary files /dev/null and b/core/src/components/picker-column-internal/test/disabled/picker-column-internal.e2e.ts-snapshots/picker-internal-disabled-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/picker-column-internal/test/update-items/picker-column-internal.e2e.ts b/core/src/components/picker-column-internal/test/update-items/picker-column-internal.e2e.ts new file mode 100644 index 0000000000..4ca337ab38 --- /dev/null +++ b/core/src/components/picker-column-internal/test/update-items/picker-column-internal.e2e.ts @@ -0,0 +1,175 @@ +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(` + + + + + + `); + + 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(` + + + + + + `); + + 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(` + + + + + + `); + + 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(` + + + + + + `); + + 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(` + + + + + + `); + + 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); + }); +}); diff --git a/core/src/components/picker-internal/picker-internal.scss b/core/src/components/picker-internal/picker-internal.scss index 6236f2e428..93ee10330b 100644 --- a/core/src/components/picker-internal/picker-internal.scss +++ b/core/src/components/picker-internal/picker-internal.scss @@ -84,3 +84,7 @@ :host ::slotted(ion-picker-column-internal:last-of-type) { text-align: end; } + +:host ::slotted(ion-picker-column-internal:only-child) { + text-align: center; +} diff --git a/core/src/components/picker-internal/picker-internal.tsx b/core/src/components/picker-internal/picker-internal.tsx index bda6ea9336..4cde38426a 100644 --- a/core/src/components/picker-internal/picker-internal.tsx +++ b/core/src/components/picker-internal/picker-internal.tsx @@ -303,7 +303,7 @@ export class PickerInternal implements ComponentInterface { return; } - const values = inputModeColumn.items; + const values = inputModeColumn.items.filter((item) => item.disabled !== true); /** * If users pause for a bit, the search @@ -374,7 +374,7 @@ export class PickerInternal implements ComponentInterface { zeroBehavior: 'start' | 'end' = 'start' ) => { const behavior = zeroBehavior === 'start' ? /^0+/ : /0$/; - const item = colEl.items.find(({ text }) => text.replace(behavior, '') === value); + const item = colEl.items.find(({ text, disabled }) => disabled !== true && text.replace(behavior, '') === value); if (item) { colEl.setValue(item.value); diff --git a/core/src/components/picker-internal/test/basic/picker-internal.e2e.ts b/core/src/components/picker-internal/test/basic/picker-internal.e2e.ts index cce0ec95d9..1789273377 100644 --- a/core/src/components/picker-internal/test/basic/picker-internal.e2e.ts +++ b/core/src/components/picker-internal/test/basic/picker-internal.e2e.ts @@ -12,6 +12,65 @@ test.describe('picker-internal', () => { ); }); + test.describe('picker-internal: focus', () => { + test.beforeEach(async ({ page }) => { + await page.setContent(` + + + + + + + `); + }); + + test('tabbing should correctly move focus between columns', async ({ page }) => { + const firstColumn = page.locator('ion-picker-column-internal#first'); + const secondColumn = page.locator('ion-picker-column-internal#second'); + + // Focus first column + await page.keyboard.press('Tab'); + expect(firstColumn).toBeFocused(); + + await page.waitForChanges(); + + // Focus second column + await page.keyboard.press('Tab'); + expect(secondColumn).toBeFocused(); + }); + + test('tabbing should correctly move focus back', async ({ page }) => { + const firstColumn = page.locator('ion-picker-column-internal#first'); + const secondColumn = page.locator('ion-picker-column-internal#second'); + + await secondColumn.focus(); + expect(secondColumn).toBeFocused(); + + await page.waitForChanges(); + + // Focus first column + await page.keyboard.press('Shift+Tab'); + expect(firstColumn).toBeFocused(); + }); + }); + test.describe('within overlay:', () => { // TODO (FW-1397): Remove this test.skip when the issue is fixed. test.skip(true, 'Mobile Safari and Chrome on Linux renders the selected option incorrectly'); diff --git a/core/src/components/popover/popover.tsx b/core/src/components/popover/popover.tsx index 4237443911..e9d6bacfc9 100644 --- a/core/src/components/popover/popover.tsx +++ b/core/src/components/popover/popover.tsx @@ -253,6 +253,19 @@ export class Popover implements ComponentInterface, PopoverInterface { } } + /** + * If `true`, the component passed into `ion-popover` will + * automatically be mounted when the popover is created. The + * component will remain mounted even when the popover is dismissed. + * However, the component will be destroyed when the popover is + * destroyed. This property is not reactive and should only be + * used when initially creating a popover. + * + * Note: This feature only applies to inline popovers in JavaScript + * frameworks such as Angular, React, and Vue. + */ + @Prop() keepContentsMounted = false; + /** * Emitted after the popover has presented. */ diff --git a/core/src/components/range/range.tsx b/core/src/components/range/range.tsx index 5bf61d19c8..f97ce9c5d1 100644 --- a/core/src/components/range/range.tsx +++ b/core/src/components/range/range.tsx @@ -16,6 +16,7 @@ import type { import { findClosestIonContent, disableContentScrollY, resetContentScrollY } from '../../utils/content'; import type { Attributes } from '../../utils/helpers'; import { inheritAriaAttributes, clamp, debounceEvent, getAriaLabel, renderHiddenInput } from '../../utils/helpers'; +import { printIonWarning } from '../../utils/logging'; import { isRTL } from '../../utils/rtl'; import { createColorClasses, hostContext } from '../../utils/theme'; @@ -142,6 +143,31 @@ export class Range implements ComponentInterface { */ @Prop() ticks = true; + /** + * The start position of the range active bar. This feature is only available with a single knob (dualKnobs="false"). + * Valid values are greater than or equal to the min value and less than or equal to the max value. + */ + @Prop({ mutable: true }) activeBarStart?: number; + @Watch('activeBarStart') + protected activeBarStartChanged() { + const { activeBarStart } = this; + if (activeBarStart !== undefined) { + if (activeBarStart > this.max) { + printIonWarning( + `Range: The value of activeBarStart (${activeBarStart}) is greater than the max (${this.max}). Valid values are greater than or equal to the min value and less than or equal to the max value.`, + this.el + ); + this.activeBarStart = this.max; + } else if (activeBarStart < this.min) { + printIonWarning( + `Range: The value of activeBarStart (${activeBarStart}) is less than the min (${this.min}). Valid values are greater than or equal to the min value and less than or equal to the max value.`, + this.el + ); + this.activeBarStart = this.min; + } + } + } + /** * If `true`, the user cannot interact with the range. */ @@ -252,6 +278,7 @@ export class Range implements ComponentInterface { this.updateRatio(); this.debounceChanged(); this.disabledChanged(); + this.activeBarStartChanged(); /** * If we have not yet rendered @@ -395,7 +422,11 @@ export class Range implements ComponentInterface { if (this.dualKnobs) { return Math.min(this.ratioA, this.ratioB); } - return 0; + const { activeBarStart } = this; + if (activeBarStart == null) { + return 0; + } + return valueToRatio(activeBarStart, this.min, this.max); } private get ratioUpper() { @@ -484,8 +515,8 @@ export class Range implements ComponentInterface { labelText = inheritedAttributes['aria-label']; } const mode = getIonMode(this); - const barStart = `${ratioLower * 100}%`; - const barEnd = `${100 - ratioUpper * 100}%`; + let barStart = `${ratioLower * 100}%`; + let barEnd = `${100 - ratioUpper * 100}%`; const rtl = isRTL(this.el); @@ -498,6 +529,33 @@ export class Range implements ComponentInterface { }; }; + if (this.dualKnobs === false) { + /** + * When the value is less than the activeBarStart or the min value, + * the knob will display at the start of the active bar. + */ + if (this.valA < (this.activeBarStart ?? this.min)) { + /** + * Sets the bar positions relative to the upper and lower limits. + * Converts the ratio values into percentages, used as offsets for left/right styles. + * + * The ratioUpper refers to the knob position on the bar. + * The ratioLower refers to the end position of the active bar (the value). + */ + barStart = `${ratioUpper * 100}%`; + barEnd = `${100 - ratioLower * 100}%`; + } else { + /** + * Otherwise, the knob will display at the end of the active bar. + * + * The ratioLower refers to the start position of the active bar (the value). + * The ratioUpper refers to the knob position on the bar. + */ + barStart = `${ratioLower * 100}%`; + barEnd = `${100 - ratioUpper * 100}%`; + } + } + const barStyle = { [start]: barStart, [end]: barEnd, @@ -508,9 +566,16 @@ export class Range implements ComponentInterface { for (let value = min; value <= max; value += step) { const ratio = valueToRatio(value, min, max); + const ratioMin = Math.min(ratioLower, ratioUpper); + const ratioMax = Math.max(ratioLower, ratioUpper); + const tick: any = { ratio, - active: ratio >= ratioLower && ratio <= ratioUpper, + /** + * Sets the tick mark as active when the tick is between the min bounds and the knob. + * When using activeBarStart, the tick mark will be active between the knob and activeBarStart. + */ + active: ratio >= ratioMin && ratio <= ratioMax, }; tick[start] = `${ratio * 100}%`; diff --git a/core/src/components/range/test/activeBarStart/index.html b/core/src/components/range/test/activeBarStart/index.html new file mode 100644 index 0000000000..a34e159cae --- /dev/null +++ b/core/src/components/range/test/activeBarStart/index.html @@ -0,0 +1,148 @@ + + + + + Range - activeBarStart + + + + + + + + + + + + + + + + Range - activeBarStart + + + + + + activeBarStart is 0 and value is 0. + + + + + activeBarStart is 0 and value is 70. + + + + + activeBarStart is 0 and value is -70. + + + + + activeBarStart is -30 and value is 0. + + + + + activeBarStart is 30 and value is 0. + + + + + activeBarStart is between steps + + + + invalid activeBarStart value (less than min) + + + + + invalid activeBarStart value (greater than max) + + + + + activeBarStart is ignored with dual knobs enabled. + + + + + + + diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts b/core/src/components/range/test/activeBarStart/range.e2e.ts new file mode 100644 index 0000000000..b3eb72e0be --- /dev/null +++ b/core/src/components/range/test/activeBarStart/range.e2e.ts @@ -0,0 +1,12 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +test.describe('range: activeBarStart', () => { + test('should not have visual regressions', async ({ page }) => { + await page.goto(`/src/components/range/test/activeBarStart`); + + await page.setIonViewport(); + + expect(await page.screenshot()).toMatchSnapshot(`range-activeBarStart-diff-${page.getSnapshotSettings()}.png`); + }); +}); diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..e61f2f80b8 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..060bac98cd Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..b3fdbdb3ef Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..062ab1bc90 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..ec3e42f8ad Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..19b99ad6fb Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..defe7434e9 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..b565d59dd9 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..560e9a3e35 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..e0fca449b6 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..7ea1ed5256 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..17fbd22d5c Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/index.html b/core/src/components/toggle/test/enable-on-off-labels/index.html new file mode 100644 index 0000000000..9b9a48c967 --- /dev/null +++ b/core/src/components/toggle/test/enable-on-off-labels/index.html @@ -0,0 +1,284 @@ + + + + + Toggle - enableOnOffLabels + + + + + + + + + + + + + + + Toggle - enableOnOffLabels + + Options + + + + + Dark Mode + + + + + + + + + + + Unchecked + + + + + Checked + + + + + Secondary Unchecked + + + + + Secondary Checked + + + + + Success Unchecked + + + + + Success Checked + + + + + Danger Unchecked + + + + + Danger Checked + + + + + Tertiary Unchecked + + + + + Tertiary Checked + + + + + Light Unchecked + + + + + Light Checked + + + + + Medium Unchecked + + + + + Medium Checked + + + + + Dark Unchecked + + + + + Dark Checked + + + + + + + + + diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts new file mode 100644 index 0000000000..ded22f9863 --- /dev/null +++ b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts @@ -0,0 +1,40 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +test.describe('toggle: enableOnOffLabels', () => { + test.beforeEach(async ({ page }) => { + await page.goto(`/src/components/toggle/test/enable-on-off-labels`); + }); + + test('should not have visual regressions', async ({ page }) => { + await page.setIonViewport(); + + expect(await page.screenshot()).toMatchSnapshot(`toggle-on-off-labels-diff-${page.getSnapshotSettings()}.png`); + }); + + test.describe('dark mode', () => { + test('should not have visual regressions', async ({ page }) => { + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + const ionPopoverDidDismiss = await page.spyOnEvent('ionPopoverDidDismiss'); + + await page.click('#popover-trigger'); + await ionPopoverDidPresent.next(); + + await page.click('#dark-mode'); + + await page.evaluate(() => { + const popover = document.querySelector('ion-popover'); + return popover?.dismiss(); + }); + await ionPopoverDidDismiss.next(); + + await page.waitForChanges(); + + await page.setIonViewport(); + + expect(await page.screenshot()).toMatchSnapshot( + `toggle-on-off-labels-dark-mode-diff-${page.getSnapshotSettings()}.png` + ); + }); + }); +}); diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..9040f90f8e Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..2019d85c49 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..838f1de7eb Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..4757876ff4 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..01e8fbccfe Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..67324f8218 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..3c53e88021 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..dca4650ce3 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..2b6c1698fd Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..a740b7b15c Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..265595aabf Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..4e5f992799 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-dark-mode-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..2b18f2d529 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..4f07381af8 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..04a53e9887 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..7509abbe31 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..ba1a4f0c59 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..9a8c30bcca Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..8790a73192 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..a3a3cde925 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..f9080c11d0 Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..74888ca33b Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..4aa787b72b Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..01197b150e Binary files /dev/null and b/core/src/components/toggle/test/enable-on-off-labels/toggle.e2e.ts-snapshots/toggle-on-off-labels-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/toggle/toggle.ios.scss b/core/src/components/toggle/toggle.ios.scss index 0e0a20a890..d442fcb6eb 100644 --- a/core/src/components/toggle/toggle.ios.scss +++ b/core/src/components/toggle/toggle.ios.scss @@ -30,6 +30,9 @@ background: current-color(base); } +:host(.toggle-activated) .toggle-switch-icon { + opacity: 0; +} // iOS Toggle Background Track: Unchecked // ---------------------------------------------------------- @@ -42,7 +45,6 @@ transition: background-color $toggle-ios-transition-duration; } - // iOS Toggle Inner Knob: Unchecked // ---------------------------------------------------------- @@ -50,6 +52,70 @@ will-change: transform; } +// iOS Toggle On/Off Labels +// ---------------------------------------------------------- + +.toggle-switch-icon { + position: absolute; + + top: 50%; + + width: 11px; + height: 11px; + + transform: translateY(-50%); + + transition: opacity $toggle-ios-transition-duration, color $toggle-ios-transition-duration; +} + +.toggle-switch-icon { + @include ltr() { + /* stylelint-disable-next-line property-disallowed-list */ + right: 6px; + } + + @include rtl() { + /* stylelint-disable property-disallowed-list */ + right: initial; + left: 6px; + /* stylelint-enable property-disallowed-list */ + } + + position: absolute; + + color: var(--ion-color-dark); +} + +:host(.toggle-checked) .toggle-switch-icon.toggle-switch-icon-checked { + // The color contrast of iOS default on/off labels fails to meet WCAG 2.0. + // We use Ionic's color contrast variables to meet the WCAG 2.0 standard (AAA). + color: var(--ion-color-contrast, $toggle-ios-on-off-label-checked-color); +} + +:host(.toggle-checked) .toggle-switch-icon:not(.toggle-switch-icon-checked) { + opacity: 0; +} + +.toggle-switch-icon-checked { + @include ltr() { + /* stylelint-disable property-disallowed-list */ + right: initial; + left: 4px; + /* stylelint-enable property-disallowed-list */ + } + + @include rtl() { + /* stylelint-disable-next-line property-disallowed-list */ + right: 4px; + } + + position: absolute; + + width: 15px; + height: 15px; + + transform: translateY(-50%) rotate(90deg); +} // iOS Toggle Background Oval: Activated or Checked // ---------------------------------------------------------- @@ -59,7 +125,6 @@ transform: scale3d(0, 0, 0); } - // iOS Toggle Background Oval: Activated and Checked // ---------------------------------------------------------- @@ -67,7 +132,6 @@ transform: scale3d(0, 0, 0); } - // iOS Toggle Inner Knob: Activated and Unchecked // ---------------------------------------------------------- @@ -75,7 +139,6 @@ width: calc(var(--handle-width) + 6px); } - // iOS Toggle Inner Knob: Activated and Checked // ---------------------------------------------------------- @@ -93,25 +156,31 @@ } } - // iOS Toggle: Disabled // ---------------------------------------------------------- -// .item-ios.item-toggle-disabled ion-label - :host(.toggle-disabled) { opacity: $toggle-ios-disabled-opacity; } - // iOS Toggle Within An Item // ---------------------------------------------------------- :host(.in-item[slot]) { @include margin($toggle-ios-media-margin); - @include padding($toggle-ios-item-end-padding-top, $toggle-ios-item-end-padding-end, $toggle-ios-item-end-padding-bottom, $toggle-ios-item-end-padding-start); + @include padding( + $toggle-ios-item-end-padding-top, + $toggle-ios-item-end-padding-end, + $toggle-ios-item-end-padding-bottom, + $toggle-ios-item-end-padding-start + ); } :host(.in-item[slot="start"]) { - @include padding($toggle-ios-item-start-padding-top, $toggle-ios-item-start-padding-end, $toggle-ios-item-start-padding-bottom, $toggle-ios-item-start-padding-start); + @include padding( + $toggle-ios-item-start-padding-top, + $toggle-ios-item-start-padding-end, + $toggle-ios-item-start-padding-bottom, + $toggle-ios-item-start-padding-start + ); } diff --git a/core/src/components/toggle/toggle.ios.vars.scss b/core/src/components/toggle/toggle.ios.vars.scss index e97cf98ef0..28d8284be4 100644 --- a/core/src/components/toggle/toggle.ios.vars.scss +++ b/core/src/components/toggle/toggle.ios.vars.scss @@ -75,3 +75,6 @@ $toggle-ios-item-end-padding-bottom: 5px !default; /// @prop - Padding start of the toggle positioned on the end in an item $toggle-ios-item-end-padding-start: $item-ios-padding-start !default; + +/// @prop - The text color of the on/off labels when the toggle is checked +$toggle-ios-on-off-label-checked-color: #fff !default; \ No newline at end of file diff --git a/core/src/components/toggle/toggle.md.scss b/core/src/components/toggle/toggle.md.scss index b44cedb3cd..dd593cc8c0 100644 --- a/core/src/components/toggle/toggle.md.scss +++ b/core/src/components/toggle/toggle.md.scss @@ -34,6 +34,10 @@ background: current-color(base); } +:host(.toggle-checked) .toggle-inner { + color: var(--ion-color-contrast, $toggle-md-on-off-label-checked-color); +} + // Material Design Toggle Background Track: Unchecked // ---------------------------------------------------------- @@ -41,14 +45,26 @@ transition: background-color $toggle-md-transition-duration; } - // Material Design Toggle Inner Knob: Unchecked // ---------------------------------------------------------- .toggle-inner { will-change: background-color, transform; + + display: flex; + + align-items: center; + justify-content: center; + + color: $toggle-md-on-off-label-color; } +.toggle-inner .toggle-switch-icon { + @include padding(1px); + + width: 100%; + height: 100%; +} // Material Design Toggle: Disabled // ---------------------------------------------------------- @@ -68,17 +84,31 @@ // opacity: $toggle-md-disabled-opacity; // } - // Material Design Toggle Within An Item // ---------------------------------------------------------- :host(.in-item[slot]) { - @include margin($toggle-md-media-margin-top, $toggle-md-media-margin-end, $toggle-md-media-margin-bottom, $toggle-md-media-margin-start); - @include padding($toggle-md-item-end-padding-top, $toggle-md-item-end-padding-end, $toggle-md-item-end-padding-bottom, $toggle-md-item-end-padding-start); + @include margin( + $toggle-md-media-margin-top, + $toggle-md-media-margin-end, + $toggle-md-media-margin-bottom, + $toggle-md-media-margin-start + ); + @include padding( + $toggle-md-item-end-padding-top, + $toggle-md-item-end-padding-end, + $toggle-md-item-end-padding-bottom, + $toggle-md-item-end-padding-start + ); cursor: pointer; } :host(.in-item[slot="start"]) { - @include padding($toggle-md-item-start-padding-top, $toggle-md-item-start-padding-end, $toggle-md-item-start-padding-bottom, $toggle-md-item-start-padding-start); + @include padding( + $toggle-md-item-start-padding-top, + $toggle-md-item-start-padding-end, + $toggle-md-item-start-padding-bottom, + $toggle-md-item-start-padding-start + ); } diff --git a/core/src/components/toggle/toggle.md.vars.scss b/core/src/components/toggle/toggle.md.vars.scss index 245dd7f2bb..69584febd4 100644 --- a/core/src/components/toggle/toggle.md.vars.scss +++ b/core/src/components/toggle/toggle.md.vars.scss @@ -99,3 +99,9 @@ $toggle-md-item-end-padding-bottom: 12px !default; /// @prop - Padding start of the toggle positioned on the end in an item $toggle-md-item-end-padding-start: $item-md-padding-start !default; + +/// @prop - The text color of the on/off labels +$toggle-md-on-off-label-color: #000 !default; + +/// @prop - The text color of the on/off labels when the toggle is checked +$toggle-md-on-off-label-checked-color: #fff !default; diff --git a/core/src/components/toggle/toggle.scss b/core/src/components/toggle/toggle.scss index 943f442f18..3d1e8fad07 100644 --- a/core/src/components/toggle/toggle.scss +++ b/core/src/components/toggle/toggle.scss @@ -83,6 +83,7 @@ input { @include border-radius(var(--border-radius)); display: block; + position: relative; width: 100%; @@ -95,7 +96,6 @@ input { overflow: inherit; } - // Toggle Background Track: Checked // ---------------------------------------------------------- @@ -103,7 +103,6 @@ input { background: var(--background-checked); } - // Toggle Inner Knob: Unchecked // -------------------------------------------------- @@ -127,7 +126,6 @@ input { contain: strict; } - // Toggle Inner Knob: Checked // ---------------------------------------------------------- diff --git a/core/src/components/toggle/toggle.tsx b/core/src/components/toggle/toggle.tsx index a483b36120..d7e941d0a1 100644 --- a/core/src/components/toggle/toggle.tsx +++ b/core/src/components/toggle/toggle.tsx @@ -1,8 +1,9 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; import { Component, Element, Event, Host, Prop, State, Watch, h } from '@stencil/core'; +import { checkmarkOutline, removeOutline, ellipseOutline } from 'ionicons/icons'; import { getIonMode } from '../../global/ionic-global'; -import type { Color, Gesture, GestureDetail, StyleEventDetail, ToggleChangeEventDetail } from '../../interface'; +import type { Color, Gesture, GestureDetail, Mode, StyleEventDetail, ToggleChangeEventDetail } from '../../interface'; import { getAriaLabel, renderHiddenInput } from '../../utils/helpers'; import { hapticSelection } from '../../utils/native/haptic'; import { isRTL } from '../../utils/rtl'; @@ -63,6 +64,11 @@ export class Toggle implements ComponentInterface { */ @Prop() value?: string | null = 'on'; + /** + * Enables the on/off accessibility switch labels within the toggle. + */ + @Prop() enableOnOffLabels: boolean | undefined = undefined; + /** * Emitted when the value property has changed. */ @@ -178,8 +184,29 @@ export class Toggle implements ComponentInterface { this.ionBlur.emit(); }; + private getSwitchLabelIcon = (mode: Mode, checked: boolean) => { + if (mode === 'md') { + return checked ? checkmarkOutline : removeOutline; + } + return checked ? removeOutline : ellipseOutline; + }; + + private renderOnOffSwitchLabels(mode: Mode, checked: boolean) { + const icon = this.getSwitchLabelIcon(mode, checked); + + return ( + + ); + } + render() { - const { activated, color, checked, disabled, el, inputId, name } = this; + const { activated, color, checked, disabled, el, inputId, name, enableOnOffLabels } = this; const mode = getIonMode(this); const { label, labelId, labelText } = getAriaLabel(el, inputId); const value = this.getValue(); @@ -203,8 +230,15 @@ export class Toggle implements ComponentInterface { })} >
+ {/* The iOS on/off labels are rendered outside of .toggle-icon-wrapper, + since the wrapper is translated when the handle is interacted with and + this would move the on/off labels outside of the view box */} + {enableOnOffLabels && + mode === 'ios' && [this.renderOnOffSwitchLabels(mode, true), this.renderOnOffSwitchLabels(mode, false)]}
-
+
+ {enableOnOffLabels && mode === 'md' && this.renderOnOffSwitchLabels(mode, checked)} +
diff --git a/core/src/css/core.scss b/core/src/css/core.scss index 2ba1779c8b..9291dba0d6 100644 --- a/core/src/css/core.scss +++ b/core/src/css/core.scss @@ -339,3 +339,26 @@ ion-accordion-group.accordion-group-expand-inset.md > ion-accordion.accordion-ex ion-input input::-webkit-date-and-time-value { text-align: start; } + +/** + * The .ion-datetime-button-overlay class contains + * styles that allow any modal/popover to be + * sized according to the dimensions of the datetime + * when used with ion-datetime-button. + */ +.ion-datetime-button-overlay { + --width: fit-content; + --height: fit-content; +} + +/** + * The grid variant can scale down when inline. + * When used in a `fit-content` overlay, this causes + * the overlay to shrink when the month/year picker is open. + * Explicitly setting the dimensions lets us have a consistently + * sized grid interface. + */ +.ion-datetime-button-overlay ion-datetime.datetime-grid { + width: 320px; + min-height: 320px; +} diff --git a/core/src/utils/logging/index.ts b/core/src/utils/logging/index.ts index 45c03cbdcf..ee4234cb6a 100644 --- a/core/src/utils/logging/index.ts +++ b/core/src/utils/logging/index.ts @@ -4,8 +4,8 @@ * * @param message - The string message to be logged to the console. */ -export const printIonWarning = (message: string) => { - return console.warn(`[Ionic Warning]: ${message}`); +export const printIonWarning = (message: string, ...params: any[]) => { + return console.warn(`[Ionic Warning]: ${message}`, ...params); }; /* diff --git a/core/tsconfig.json b/core/tsconfig.json index 262bd95fc5..4cf2a589b4 100644 --- a/core/tsconfig.json +++ b/core/tsconfig.json @@ -12,7 +12,8 @@ "jsxFactory": "h", "lib": [ "dom", - "es2017" + "es2017", + "es2020" ], "module": "esnext", "moduleResolution": "node", diff --git a/packages/react/src/components/createInlineOverlayComponent.tsx b/packages/react/src/components/createInlineOverlayComponent.tsx index 783ba5b495..2851a371b8 100644 --- a/packages/react/src/components/createInlineOverlayComponent.tsx +++ b/packages/react/src/components/createInlineOverlayComponent.tsx @@ -21,6 +21,7 @@ interface IonicReactInternalProps extends React.HTMLAttributes) => void; onWillDismiss?: (event: CustomEvent) => void; onWillPresent?: (event: CustomEvent) => void; + keepContentsMounted?: boolean; } export const createInlineOverlayComponent = ( @@ -128,7 +129,7 @@ export const createInlineOverlayComponent = ( * so conditionally render the component * based on the isOpen state. */ - return createElement(tagName, newProps, (this.state.isOpen) ? + return createElement(tagName, newProps, (this.state.isOpen || this.props.keepContentsMounted) ? createElement('div', { id: 'ion-react-wrapper', ref: this.wrapperRef, diff --git a/packages/react/src/components/proxies.ts b/packages/react/src/components/proxies.ts index ec9e044fe9..dd568efaa6 100644 --- a/packages/react/src/components/proxies.ts +++ b/packages/react/src/components/proxies.ts @@ -21,6 +21,7 @@ import { defineCustomElement as defineIonChip } from '@ionic/core/components/ion import { defineCustomElement as defineIonCol } from '@ionic/core/components/ion-col.js'; import { defineCustomElement as defineIonContent } from '@ionic/core/components/ion-content.js'; import { defineCustomElement as defineIonDatetime } from '@ionic/core/components/ion-datetime.js'; +import { defineCustomElement as defineIonDatetimeButton } from '@ionic/core/components/ion-datetime-button.js'; import { defineCustomElement as defineIonFab } from '@ionic/core/components/ion-fab.js'; import { defineCustomElement as defineIonFabList } from '@ionic/core/components/ion-fab-list.js'; import { defineCustomElement as defineIonFooter } from '@ionic/core/components/ion-footer.js'; @@ -88,6 +89,7 @@ export const IonChip = /*@__PURE__*/createReactComponent('ion-col', undefined, undefined, defineIonCol); export const IonContent = /*@__PURE__*/createReactComponent('ion-content', undefined, undefined, defineIonContent); export const IonDatetime = /*@__PURE__*/createReactComponent('ion-datetime', undefined, undefined, defineIonDatetime); +export const IonDatetimeButton = /*@__PURE__*/createReactComponent('ion-datetime-button', undefined, undefined, defineIonDatetimeButton); export const IonFab = /*@__PURE__*/createReactComponent('ion-fab', undefined, undefined, defineIonFab); export const IonFabList = /*@__PURE__*/createReactComponent('ion-fab-list', undefined, undefined, defineIonFabList); export const IonFooter = /*@__PURE__*/createReactComponent('ion-footer', undefined, undefined, defineIonFooter); diff --git a/packages/react/test-app/cypress/integration/overlay-components/KeepContentsMounted.spec.ts b/packages/react/test-app/cypress/integration/overlay-components/KeepContentsMounted.spec.ts new file mode 100644 index 0000000000..b466e0e753 --- /dev/null +++ b/packages/react/test-app/cypress/integration/overlay-components/KeepContentsMounted.spec.ts @@ -0,0 +1,60 @@ +describe('keepContentsMounted', () => { + describe('modal', () => { + it('should not mount component if false', () => { + cy.visit('/overlay-components/modal'); + + cy.get('ion-modal ion-content').should('not.exist'); + }); + + it('should mount component if true', () => { + cy.visit('/keep-contents-mounted'); + + cy.get('ion-modal ion-content').should('exist'); + }); + + it('should keep component mounted after dismissing if true', () => { + cy.visit('/keep-contents-mounted'); + + cy.get('#open-modal').click(); + + cy.get('ion-modal ion-content').should('exist'); + + cy.get('ion-modal ion-button').click(); + + cy.get('ion-modal') + .should('not.be.visible') + .should('have.class', 'overlay-hidden'); + + cy.get('ion-modal ion-content').should('exist'); + }); + }) + describe('popover', () => { + it('should not mount component if false', () => { + cy.visit('/overlay-components/popover'); + + cy.get('ion-popover ion-content').should('not.exist'); + }); + + it('should mount component if true', () => { + cy.visit('/keep-contents-mounted'); + + cy.get('ion-popover ion-content').should('exist'); + }); + + it('should keep component mounted after dismissing if true', () => { + cy.visit('/keep-contents-mounted'); + + cy.get('#open-popover').click(); + + cy.get('ion-popover ion-content').should('exist'); + + cy.get('ion-popover ion-button').click(); + + cy.get('ion-popover') + .should('not.be.visible') + .should('have.class', 'overlay-hidden'); + + cy.get('ion-popover ion-content').should('exist'); + }); + }) +}); diff --git a/packages/react/test-app/src/App.tsx b/packages/react/test-app/src/App.tsx index dcefa51c3d..268a44479a 100644 --- a/packages/react/test-app/src/App.tsx +++ b/packages/react/test-app/src/App.tsx @@ -24,6 +24,7 @@ import './theme/variables.css'; import Main from './pages/Main'; import OverlayHooks from './pages/overlay-hooks/OverlayHooks'; 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'; @@ -36,6 +37,7 @@ const App: React.FC = () => ( + diff --git a/packages/react/test-app/src/pages/Main.tsx b/packages/react/test-app/src/pages/Main.tsx index 419589385c..c5f097357d 100644 --- a/packages/react/test-app/src/pages/Main.tsx +++ b/packages/react/test-app/src/pages/Main.tsx @@ -31,6 +31,11 @@ const Main: React.FC = () => { Overlay Components + + + Keep Contents Mounted Overlay Components + + Navigation diff --git a/packages/react/test-app/src/pages/overlay-components/KeepContentsMounted.tsx b/packages/react/test-app/src/pages/overlay-components/KeepContentsMounted.tsx new file mode 100644 index 0000000000..8f4e852737 --- /dev/null +++ b/packages/react/test-app/src/pages/overlay-components/KeepContentsMounted.tsx @@ -0,0 +1,38 @@ +import React, { useState } from 'react'; +import { + IonButton, + IonContent, + IonPage, + IonModal, + IonPopover, +} from '@ionic/react'; + +const KeepContentsMounted: React.FC = () => { + const [showModal, setShowModal] = useState(false); + const [showPopover, setShowPopover] = useState(false); + + return ( + + + setShowModal(true)}>Open Modal + setShowPopover(true)}>Open Popover + + setShowPopover(false)}> + + setShowModal(false)}>Dismiss + Modal Content + + + + setShowPopover(false)}> + + setShowPopover(false)}>Dismiss + Popover Content + + + + + ); +}; + +export default KeepContentsMounted; diff --git a/packages/vue/src/components/Overlays.ts b/packages/vue/src/components/Overlays.ts index 95f40dda76..1f02542841 100644 --- a/packages/vue/src/components/Overlays.ts +++ b/packages/vue/src/components/Overlays.ts @@ -29,7 +29,7 @@ export const IonPicker = /*@__PURE__*/ defineOverlayContainer('io export const IonToast = /*@__PURE__*/ defineOverlayContainer('ion-toast', defineIonToastCustomElement, ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'icon', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController); -export const IonModal = /*@__PURE__*/ defineOverlayContainer('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'handle', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'swipeToClose', 'trigger']); +export const IonModal = /*@__PURE__*/ defineOverlayContainer('ion-modal', defineIonModalCustomElement, ['animated', 'backdropBreakpoint', 'backdropDismiss', 'breakpoints', 'canDismiss', 'enterAnimation', 'handle', 'handleBehavior', 'htmlAttributes', 'initialBreakpoint', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'presentingElement', 'showBackdrop', 'swipeToClose', 'trigger']); -export const IonPopover = /*@__PURE__*/ defineOverlayContainer('ion-popover', defineIonPopoverCustomElement, ['alignment', 'animated', 'arrow', 'backdropDismiss', 'component', 'componentProps', 'dismissOnSelect', 'enterAnimation', 'event', 'htmlAttributes', 'isOpen', 'keyboardClose', 'leaveAnimation', 'mode', 'reference', 'showBackdrop', 'side', 'size', 'translucent', 'trigger', 'triggerAction']); +export const IonPopover = /*@__PURE__*/ defineOverlayContainer('ion-popover', defineIonPopoverCustomElement, ['alignment', 'animated', 'arrow', 'backdropDismiss', 'component', 'componentProps', 'dismissOnSelect', 'enterAnimation', 'event', 'htmlAttributes', 'isOpen', 'keepContentsMounted', 'keyboardClose', 'leaveAnimation', 'mode', 'reference', 'showBackdrop', 'side', 'size', 'translucent', 'trigger', 'triggerAction']); diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index ad92c222d1..db99f1e905 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -24,6 +24,7 @@ import { defineCustomElement as defineIonChip } from '@ionic/core/components/ion import { defineCustomElement as defineIonCol } from '@ionic/core/components/ion-col.js'; import { defineCustomElement as defineIonContent } from '@ionic/core/components/ion-content.js'; import { defineCustomElement as defineIonDatetime } from '@ionic/core/components/ion-datetime.js'; +import { defineCustomElement as defineIonDatetimeButton } from '@ionic/core/components/ion-datetime-button.js'; import { defineCustomElement as defineIonFab } from '@ionic/core/components/ion-fab.js'; import { defineCustomElement as defineIonFabButton } from '@ionic/core/components/ion-fab-button.js'; import { defineCustomElement as defineIonFabList } from '@ionic/core/components/ion-fab-list.js'; @@ -285,6 +286,7 @@ export const IonDatetime = /*@__PURE__*/ defineContainer('ion-d 'minuteValues', 'locale', 'firstDayOfWeek', + 'multiple', 'value', 'showDefaultTitle', 'showDefaultButtons', @@ -292,15 +294,24 @@ export const IonDatetime = /*@__PURE__*/ defineContainer('ion-d 'showDefaultTimeLabel', 'hourCycle', 'size', + 'preferWheel', 'ionCancel', 'ionChange', 'ionFocus', 'ionBlur', - 'ionStyle' + 'ionStyle', + 'ionRender' ], 'value', 'v-ion-change', 'ionChange'); +export const IonDatetimeButton = /*@__PURE__*/ defineContainer('ion-datetime-button', defineIonDatetimeButton, [ + 'color', + 'disabled', + 'datetime' +]); + + export const IonFab = /*@__PURE__*/ defineContainer('ion-fab', defineIonFab, [ 'horizontal', 'vertical', @@ -586,6 +597,7 @@ export const IonRange = /*@__PURE__*/ defineContainer('ion-range', 'snaps', 'step', 'ticks', + 'activeBarStart', 'disabled', 'value', 'ionChange', @@ -816,6 +828,7 @@ export const IonToggle = /*@__PURE__*/ defineContainer('ion-toggl 'checked', 'disabled', 'value', + 'enableOnOffLabels', 'ionChange', 'ionFocus', 'ionBlur', diff --git a/packages/vue/src/vue-component-lib/overlays.ts b/packages/vue/src/vue-component-lib/overlays.ts index a59e72d7b5..ede3ac4a37 100644 --- a/packages/vue/src/vue-component-lib/overlays.ts +++ b/packages/vue/src/vue-component-lib/overlays.ts @@ -162,7 +162,7 @@ export const defineOverlayContainer = (name: string, defin return h( name, { ...restOfProps, ref: elementRef }, - (isOpen.value) ? slots : undefined + (isOpen.value || restOfProps.keepContentsMounted) ? slots : undefined ) } }); diff --git a/packages/vue/test-app/src/components/PopoverContent.vue b/packages/vue/test-app/src/components/PopoverContent.vue index 8a3717968d..94534e57da 100644 --- a/packages/vue/test-app/src/components/PopoverContent.vue +++ b/packages/vue/test-app/src/components/PopoverContent.vue @@ -1,11 +1,16 @@ diff --git a/packages/vue/test-app/tests/e2e/specs/overlays-keep-contents-mounted.js b/packages/vue/test-app/tests/e2e/specs/overlays-keep-contents-mounted.js new file mode 100644 index 0000000000..269f3fc25c --- /dev/null +++ b/packages/vue/test-app/tests/e2e/specs/overlays-keep-contents-mounted.js @@ -0,0 +1,52 @@ +describe('overlays - keepContentsMounted', () => { + beforeEach(() => { + cy.viewport(1000, 900); + cy.visit('http://localhost:8080/keep-contents-mounted') + }) + describe('modal', () => { + it('should not mount component if false', () => { + cy.get('ion-modal#default-modal ion-content').should('not.exist'); + }); + + it('should mount component if true', () => { + cy.get('ion-modal#auto-mount-modal ion-content').should('exist'); + }); + + it('should keep component mounted after dismissing if true', () => { + cy.get('#open-auto-mount-modal').click(); + + cy.get('ion-modal#auto-mount-modal ion-content').should('exist'); + + cy.get('ion-modal#auto-mount-modal #dismiss').click(); + + cy.get('ion-modal#auto-mount-modal') + .should('not.be.visible') + .should('have.class', 'overlay-hidden'); + + cy.get('ion-modal#auto-mount-modal ion-content').should('exist'); + }); + }) + describe('popover', () => { + it('should not mount component if false', () => { + cy.get('ion-popover#default-popover ion-content').should('not.exist'); + }); + + it('should mount component if true', () => { + cy.get('ion-popover#auto-mount-popover ion-content').should('exist'); + }); + + it('should keep component mounted after dismissing if true', () => { + cy.get('#open-auto-mount-popover').click(); + + cy.get('ion-popover#auto-mount-popover ion-content').should('exist'); + + cy.get('ion-popover#auto-mount-popover #dismiss').click(); + + cy.get('ion-popover#auto-mount-popover') + .should('not.be.visible') + .should('have.class', 'overlay-hidden'); + + cy.get('ion-popover#auto-mount-popover ion-content').should('exist'); + }); + }) +}) diff --git a/packages/vue/test-app/tests/e2e/specs/overlays.js b/packages/vue/test-app/tests/e2e/specs/overlays.js index 5026fd244a..fcb0360d3b 100644 --- a/packages/vue/test-app/tests/e2e/specs/overlays.js +++ b/packages/vue/test-app/tests/e2e/specs/overlays.js @@ -134,7 +134,7 @@ describe('Overlays', () => { cy.get('ion-button#present-overlay').click(); cy.get('ion-popover.ion-popover-controller').should('exist'); - cy.get('ion-popover.ion-popover-controller ion-content').should('have.text', 'Custom Title'); + cy.get('ion-popover.ion-popover-controller ion-content #title').should('have.text', 'Custom Title'); }); it('should pass props to popover via component', () => { @@ -144,7 +144,7 @@ describe('Overlays', () => { cy.get('ion-button#present-overlay').click(); cy.get('ion-popover').should('exist'); - cy.get('ion-popover.popover-inline ion-content').should('have.text', 'Custom Title'); + cy.get('ion-popover.popover-inline ion-content #title').should('have.text', 'Custom Title'); }); it('should only open one instance at a time when props change quickly on component', () => {