diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index f898c581fd..0c32666fbe 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -1388,7 +1388,10 @@ export class Datetime implements ComponentInterface { */ isCalDayDisabled = !isDateEnabled(convertDataToISO(referenceParts)); } catch (e) { - printIonError('Exception thrown from provided `isDateEnabled` function. Please check your function and try again.', e); + printIonError( + 'Exception thrown from provided `isDateEnabled` function. Please check your function and try again.', + e + ); } } diff --git a/core/src/components/datetime/test/disable-dates/e2e.ts b/core/src/components/datetime/test/disable-dates/e2e.ts index bf2e65a878..4146e8ea20 100644 --- a/core/src/components/datetime/test/disable-dates/e2e.ts +++ b/core/src/components/datetime/test/disable-dates/e2e.ts @@ -16,20 +16,16 @@ function queryAllWorkingMonthDisabledDays(page: E2EPage, datetimeSelector = 'ion } describe('datetime: disable dates', () => { - describe('return values', () => { - let page: E2EPage; beforeEach(async () => { page = await newE2EPage({ - html: '' + html: '', }); }); - describe('when isDateEnabled returns true', () => { - it('calendar days should be enabled', async () => { await page.$eval('ion-datetime', (el: any) => { el.isDateEnabled = () => true; @@ -41,13 +37,10 @@ describe('datetime: disable dates', () => { expect(disabledDays.length).toBe(0); }); - }); describe('when isDateEnabled returns false', () => { - it('calendar days should be disabled', async () => { - await page.$eval('ion-datetime', (el: any) => { el.isDateEnabled = () => false; }); @@ -58,11 +51,9 @@ describe('datetime: disable dates', () => { expect(disabledDays.length).toBe(91); }); - }); describe('when isDateEnabled throws an exception', () => { - beforeEach(async () => { await page.$eval('ion-datetime', (el: any) => { el.isDateEnabled = (dateIsoString: string) => { @@ -79,10 +70,11 @@ describe('datetime: disable dates', () => { }); it('calendar days should be enabled', async () => { - await page.waitForChanges(); - const enabledDays = await page.findAll('ion-datetime >>> .calendar-month:nth-child(2) .calendar-day:not([disabled]):not(.calendar-day-padding)'); + const enabledDays = await page.findAll( + 'ion-datetime >>> .calendar-month:nth-child(2) .calendar-day:not([disabled]):not(.calendar-day-padding)' + ); expect(enabledDays.length).toBe(1); }); @@ -99,13 +91,13 @@ describe('datetime: disable dates', () => { await page.waitForChanges(); expect(errors.length).toBe(1); - expect(errors[0]).toContain('[Ionic Error]: Exception thrown from provided `isDateEnabled` function. Please check your function and try again.'); + expect(errors[0]).toContain( + '[Ionic Error]: Exception thrown from provided `isDateEnabled` function. Please check your function and try again.' + ); }); - }); describe('when isDateEnabled returns undefined', () => { - it('calendar days should be disabled', async () => { await page.$eval('ion-datetime', (el: any) => { el.isDateEnabled = () => undefined; @@ -117,11 +109,9 @@ describe('datetime: disable dates', () => { expect(disabledDays.length).toBe(91); }); - }); describe('when isDateEnabled returns null', () => { - it('calendar days should be disabled', async () => { await page.$eval('ion-datetime', (el: any) => { el.isDateEnabled = () => null; @@ -133,18 +123,15 @@ describe('datetime: disable dates', () => { expect(disabledDays.length).toBe(91); }); - }); - }); describe('examples', () => { - let page: E2EPage; beforeEach(async () => { page = await newE2EPage({ - url: '/src/components/datetime/test/disable-dates?ionic:_testing=true' + url: '/src/components/datetime/test/disable-dates?ionic:_testing=true', }); }); @@ -156,29 +143,27 @@ describe('datetime: disable dates', () => { it('should disable specific days of the week', async () => { const disabledDays = await queryAllWorkingMonthDisabledDays(page, '#weekends'); - const disabledValues = disabledDays.map(d => d.textContent); + const disabledValues = disabledDays.map((d) => d.textContent); expect(disabledValues).toEqual(['2', '3', '9', '10', '16', '17', '23', '24', '30', '31']); }); it('should disable a range of dates', async () => { const disabledDays = await queryAllDisabledDays(page, '#dateRange'); - const disabledValues = disabledDays.map(d => d.textContent); + const disabledValues = disabledDays.map((d) => d.textContent); expect(disabledValues).toEqual(['10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20']); }); it('should disable a month', async () => { const disabledDays = await queryAllDisabledDays(page, '#month'); - const disabledValues = disabledDays.map(d => d.textContent); + const disabledValues = disabledDays.map((d) => d.textContent); expect(disabledValues.length).toBe(31); }); - }); describe('with a min date range', () => { - it('should not enable already disabled dates', async () => { const page = await newE2EPage({ html: ` @@ -187,18 +172,16 @@ describe('datetime: disable dates', () => { const datetime = document.querySelector('ion-datetime'); datetime.isDateEnabled = () => true; - ` + `, }); const disabledDays = await queryAllWorkingMonthDisabledDays(page); expect(disabledDays.length).toBe(14); }); - }); describe('with a max date range', () => { - it('should not enable already disabled dates', async () => { const page = await newE2EPage({ html: ` @@ -207,14 +190,12 @@ describe('datetime: disable dates', () => { const datetime = document.querySelector('ion-datetime'); datetime.isDateEnabled = () => true; - ` + `, }); const disabledDays = await queryAllWorkingMonthDisabledDays(page); expect(disabledDays.length).toBe(16); }); - }); - }); diff --git a/core/src/components/datetime/test/disable-dates/index.html b/core/src/components/datetime/test/disable-dates/index.html index b4b83c3a20..6e3dea2c9d 100644 --- a/core/src/components/datetime/test/disable-dates/index.html +++ b/core/src/components/datetime/test/disable-dates/index.html @@ -1,114 +1,111 @@ - - - - Datetime - Disable Dates - - - - - - - - - - - - - Datetime - Disable Dates - - - -
-
-

Disable Specific Date

- -
- -
-

Disable Weekends

- -
- -
-

Disable Date Range

- -
- -
-

Disable Month

- -
- -
-
- - + + + + + + + + + Datetime - Disable Dates + + + +
+
+

Disable Specific Date

+ +
+ +
+

Disable Weekends

+ +
+ +
+

Disable Date Range

+ +
+ +
+

Disable Month

+ +
+
+
+ + -
- + const weekendsDisabled = document.querySelector('#weekends'); + weekendsDisabled.isDateEnabled = (dateIsoString) => { + const date = new Date(dateIsoString); + // Disables Sunday and Saturday + if (date.getUTCDay() === 0 || date.getUTCDay() === 6) { + return false; + } + return true; + }; + const dateRangeDisabled = document.querySelector('#dateRange'); + dateRangeDisabled.isDateEnabled = (dateIsoString) => { + const date = new Date(dateIsoString); + // Disables dates between October 10, 2021 and October 20, 2021. + if (date.getUTCMonth() === 9 && date.getUTCFullYear() === 2021) { + if (date.getUTCDate() >= 10 && date.getUTCDate() <= 20) { + return false; + } + } + return true; + }; + + const monthDisabled = document.querySelector('#month'); + monthDisabled.isDateEnabled = (dateIsoString) => { + const date = new Date(dateIsoString); + // Disables October (every year) + if (date.getUTCMonth() === 9) { + return false; + } + return true; + }; + +
+ diff --git a/core/src/components/footer/footer.tsx b/core/src/components/footer/footer.tsx index 5e5255f6bf..ebc60c114c 100644 --- a/core/src/components/footer/footer.tsx +++ b/core/src/components/footer/footer.tsx @@ -59,7 +59,7 @@ export class Footer implements ComponentInterface { if (hasFade) { const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner'); - const contentEl = (pageEl) ? findIonContent(pageEl) : null; + const contentEl = pageEl ? findIonContent(pageEl) : null; if (!contentEl) { printIonContentErrorMsg(this.el); @@ -71,7 +71,7 @@ export class Footer implements ComponentInterface { }; private setupFadeFooter = async (contentEl: HTMLElement) => { - const scrollEl = this.scrollEl = await getScrollElement(contentEl); + const scrollEl = (this.scrollEl = await getScrollElement(contentEl)); /** * Handle fading of toolbars on scroll diff --git a/core/src/components/footer/test/scroll-target/e2e.ts b/core/src/components/footer/test/scroll-target/e2e.ts index a578057090..f96f99c813 100644 --- a/core/src/components/footer/test/scroll-target/e2e.ts +++ b/core/src/components/footer/test/scroll-target/e2e.ts @@ -8,12 +8,11 @@ import { scrollToBottom } from '@utils/test'; * selector. */ describe('footer: fade with custom scroll target: iOS', () => { - let page: E2EPage; beforeEach(async () => { page = await newE2EPage({ - url: '/src/components/footer/test/scroll-target?ionic:_testing=true&ionic:mode=ios' + url: '/src/components/footer/test/scroll-target?ionic:_testing=true&ionic:mode=ios', }); }); @@ -30,5 +29,4 @@ describe('footer: fade with custom scroll target: iOS', () => { expect(compare).toMatchScreenshot(); } }); - }); diff --git a/core/src/components/footer/test/scroll-target/index.html b/core/src/components/footer/test/scroll-target/index.html index 9331f31852..7fb03f5aee 100644 --- a/core/src/components/footer/test/scroll-target/index.html +++ b/core/src/components/footer/test/scroll-target/index.html @@ -1,102 +1,102 @@ + + + Footer - Fade (custom scroll host) + + + + + + + + - padding: 50px 0; - } - - - - - -
- - - Mailboxes - - - -
-
-
-
-
-
-
-
-
-
+ + +
+ + + Mailboxes + + + +
+
+
+
+
+
+
+
+
+
+
-
- - - - Updated Just Now - - -
- - - + + + + Updated Just Now + + +
+ + diff --git a/core/src/components/header/header.tsx b/core/src/components/header/header.tsx index 6cd2642fdf..092bb0be2a 100644 --- a/core/src/components/header/header.tsx +++ b/core/src/components/header/header.tsx @@ -1,5 +1,3 @@ - - import type { ComponentInterface } from '@stencil/core'; import { Component, Element, Host, Prop, h, writeTask } from '@stencil/core'; import { findIonContent, getScrollElement, printIonContentErrorMsg } from '@utils/content'; @@ -87,7 +85,7 @@ export class Header implements ComponentInterface { if (hasCondense) { const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner'); - const contentEl = (pageEl) ? findIonContent(pageEl) : null; + const contentEl = pageEl ? findIonContent(pageEl) : null; // Cloned elements are always needed in iOS transition writeTask(() => { @@ -99,7 +97,7 @@ export class Header implements ComponentInterface { await this.setupCondenseHeader(contentEl, pageEl); } else if (hasFade) { const pageEl = this.el.closest('ion-app,ion-page,.ion-page,page-inner'); - const contentEl = (pageEl) ? findIonContent(pageEl) : null; + const contentEl = pageEl ? findIonContent(pageEl) : null; if (!contentEl) { printIonContentErrorMsg(this.el); @@ -113,12 +111,14 @@ export class Header implements ComponentInterface { } private setupFadeHeader = async (contentEl: HTMLElement, condenseHeader: HTMLElement | null) => { - const scrollEl = this.scrollEl = await getScrollElement(contentEl); + const scrollEl = (this.scrollEl = await getScrollElement(contentEl)); /** * Handle fading of toolbars on scroll */ - this.contentScrollCallback = () => { handleHeaderFade(this.scrollEl!, this.el, condenseHeader); }; + this.contentScrollCallback = () => { + handleHeaderFade(this.scrollEl!, this.el, condenseHeader); + }; scrollEl!.addEventListener('scroll', this.contentScrollCallback); handleHeaderFade(this.scrollEl!, this.el, condenseHeader); @@ -146,7 +146,9 @@ export class Header implements ComponentInterface { printIonContentErrorMsg(this.el); return; } - if (typeof (IntersectionObserver as any) === 'undefined') { return; } + if (typeof (IntersectionObserver as any) === 'undefined') { + return; + } this.scrollEl = await getScrollElement(contentEl); diff --git a/core/src/components/header/test/fade/index.html b/core/src/components/header/test/fade/index.html index 031221067c..b93a9da4ed 100644 --- a/core/src/components/header/test/fade/index.html +++ b/core/src/components/header/test/fade/index.html @@ -1,92 +1,93 @@ - - - Header - Fade - - - - - - - - + .grid-item { + height: 200px; + } + + - - -
- - - Mailboxes - - - - + + +
+ - Mailboxes + Mailboxes -
-
-
-
-
-
-
-
-
-
- - - - Updated Just Now - - -
-
- - + + + + Mailboxes + + +
+
+
+
+
+
+
+
+
+
+
+ + + Updated Just Now + + +
+
+ diff --git a/core/src/components/header/test/scroll-target/e2e.ts b/core/src/components/header/test/scroll-target/e2e.ts index 232190b82c..3a460e6b93 100644 --- a/core/src/components/header/test/scroll-target/e2e.ts +++ b/core/src/components/header/test/scroll-target/e2e.ts @@ -3,12 +3,11 @@ import type { E2EPage } from '@stencil/core/testing'; import { scrollToBottom } from '@utils/test'; describe('ion-header: custom scroll target', () => { - let page: E2EPage; beforeEach(async () => { page = await newE2EPage({ - url: '/src/components/header/test/scroll-target?ionic:_testing=true&ionic:mode=ios' + url: '/src/components/header/test/scroll-target?ionic:_testing=true&ionic:mode=ios', }); }); @@ -18,7 +17,6 @@ describe('ion-header: custom scroll target', () => { }); describe('large title', () => { - it('should display the large title initially', async () => { const largeHeader = await page.find('ion-header[collapse="condense"]'); const collapseHeader = await page.find('ion-header[collapse="fade"]'); @@ -28,7 +26,6 @@ describe('ion-header: custom scroll target', () => { }); describe('when the scroll container has overflow', () => { - it('should display the collapsed title on scroll', async () => { const screenshotCompares = []; @@ -48,11 +45,7 @@ describe('ion-header: custom scroll target', () => { for (const screenshotCompare of screenshotCompares) { expect(screenshotCompare).toMatchScreenshot(); } - }); - }); - }); - }); diff --git a/core/src/components/header/test/scroll-target/index.html b/core/src/components/header/test/scroll-target/index.html index ef6d2238e5..9224c9ea2c 100644 --- a/core/src/components/header/test/scroll-target/index.html +++ b/core/src/components/header/test/scroll-target/index.html @@ -1,107 +1,107 @@ + + + Header - Custom Scroll Target + + + + + + + + - padding: 50px 0; - } - - - - - -
- - - Mailboxes - - - -
- - - Mailboxes - - -
-
-
-
-
-
-
-
-
+ + +
+ + + Mailboxes + + + +
+ + + Mailboxes + + +
+
+
+
+
+
+
+
+
+
-
- - - - Updated Just Now - - -
- - - + + + + Updated Just Now + + +
+ + diff --git a/core/src/components/infinite-scroll/test/basic/index.html b/core/src/components/infinite-scroll/test/basic/index.html index 5fd862c375..7c14f0b62e 100644 --- a/core/src/components/infinite-scroll/test/basic/index.html +++ b/core/src/components/infinite-scroll/test/basic/index.html @@ -1,64 +1,45 @@ + + + Infinite Scroll - Basic + + + + + + + - - - Infinite Scroll - Basic - - - - - - - + + + + + Infinite Scroll - Basic + + - - + + Toggle InfiniteScroll - - - Infinite Scroll - Basic - - + - + + + + + + - - Toggle InfiniteScroll - + diff --git a/core/src/components/infinite-scroll/test/scroll-target/e2e.ts b/core/src/components/infinite-scroll/test/scroll-target/e2e.ts index 22e2ba7588..7688639acb 100644 --- a/core/src/components/infinite-scroll/test/scroll-target/e2e.ts +++ b/core/src/components/infinite-scroll/test/scroll-target/e2e.ts @@ -15,10 +15,9 @@ async function scrollPage(page: E2EPage) { } describe('infinite-scroll: custom scroll target', () => { - it('should load more items when scrolled to the bottom', async () => { const page = await newE2EPage({ - url: '/src/components/infinite-scroll/test/scroll-target?ionic:_testing=true' + url: '/src/components/infinite-scroll/test/scroll-target?ionic:_testing=true', }); const initialItems = await page.findAll('ion-item'); @@ -30,5 +29,4 @@ describe('infinite-scroll: custom scroll target', () => { expect(items.length).toBe(60); }); - }); diff --git a/core/src/components/infinite-scroll/test/scroll-target/index.html b/core/src/components/infinite-scroll/test/scroll-target/index.html index 3f5f3e21da..0d255ccf06 100644 --- a/core/src/components/infinite-scroll/test/scroll-target/index.html +++ b/core/src/components/infinite-scroll/test/scroll-target/index.html @@ -1,80 +1,76 @@ + + + Infinite Scroll - Custom Scroll Target + + + + + + - - - Infinite Scroll - Custom Scroll Target - - - - - - - - - - - - - - - - Infinite Scroll - Custom Scroll Target - - - - -
- - - - - - - -
-
- -
- - - + function wait(time) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, time); + }); + } + appendItems(); + + diff --git a/core/src/components/item/item-interface.ts b/core/src/components/item/item-interface.ts index 22cef4c082..282e639100 100644 --- a/core/src/components/item/item-interface.ts +++ b/core/src/components/item/item-interface.ts @@ -1,2 +1 @@ - export type CounterFormatter = (inputLength: number, maxLength: number) => string; diff --git a/core/src/components/item/item.tsx b/core/src/components/item/item.tsx index d55d13e8e1..1710c546ee 100644 --- a/core/src/components/item/item.tsx +++ b/core/src/components/item/item.tsx @@ -1,4 +1,3 @@ - import type { ComponentInterface } from '@stencil/core'; import { Component, Element, Host, Listen, Prop, State, Watch, forceUpdate, h } from '@stencil/core'; import { printIonError } from '@utils/logging'; diff --git a/core/src/components/item/test/counter/e2e.ts b/core/src/components/item/test/counter/e2e.ts index ac97469cc3..875af2e1bc 100644 --- a/core/src/components/item/test/counter/e2e.ts +++ b/core/src/components/item/test/counter/e2e.ts @@ -1,11 +1,10 @@ -import type { E2EPage} from '@stencil/core/testing'; +import type { E2EPage } from '@stencil/core/testing'; import { newE2EPage } from '@stencil/core/testing'; describe('item: counter', () => { - it('should match existing visual screenshots', async () => { const page = await newE2EPage({ - url: '/src/components/item/test/counter?ionic:_testing=true' + url: '/src/components/item/test/counter?ionic:_testing=true', }); const compare = await page.compareScreenshot(); @@ -13,12 +12,11 @@ describe('item: counter', () => { }); describe('custom formatter', () => { - let page: E2EPage; beforeEach(async () => { page = await newE2EPage({ - url: '/src/components/item/test/counter?ionic:_testing=true' + url: '/src/components/item/test/counter?ionic:_testing=true', }); }); @@ -59,7 +57,6 @@ describe('item: counter', () => { }); describe('when an exception occurs', () => { - const logs = []; beforeEach(async () => { @@ -67,10 +64,10 @@ describe('item: counter', () => { html: ` - ` + `, }); - page.on('console', ev => { + page.on('console', (ev) => { if (ev.type() === 'error') { logs.push(ev.text()); } @@ -86,7 +83,6 @@ describe('item: counter', () => { }; }); await page.waitForChanges(); - }); it('should default the formatting to length / maxlength', async () => { @@ -104,7 +100,6 @@ describe('item: counter', () => { expect(logs.length).toBeGreaterThan(0); expect(logs[0]).toMatch('[Ionic Error]: Exception in provided `counterFormatter`.'); }); - }); }); -}) +}); diff --git a/core/src/components/item/test/counter/index.html b/core/src/components/item/test/counter/index.html index 8510254020..f94886f219 100644 --- a/core/src/components/item/test/counter/index.html +++ b/core/src/components/item/test/counter/index.html @@ -1,54 +1,52 @@ + + + Item - Counter + + + + + + + - - - Item - Counter - - - - - - - + + + + + Item counter + + - - + + + + Counter + + - - - Item counter - - + + Counter with value + + - - - - - Counter - - - - - Counter with value - - - - - Counter with custom formatter - - - - - - - - + + Counter with custom formatter + + + +
+ + + diff --git a/core/src/components/modal/gestures/sheet.ts b/core/src/components/modal/gestures/sheet.ts index c2027dcaf1..72cee60a2a 100644 --- a/core/src/components/modal/gestures/sheet.ts +++ b/core/src/components/modal/gestures/sheet.ts @@ -1,5 +1,5 @@ import type { Animation } from '../../../interface'; -import type { GestureDetail} from '../../../utils/gesture'; +import type { GestureDetail } from '../../../utils/gesture'; import { createGesture } from '../../../utils/gesture'; import { clamp, raf } from '../../../utils/helpers'; import { getBackdropValueForSheet } from '../utils'; @@ -47,21 +47,21 @@ export const createSheetGesture = ( // Defaults for the sheet swipe animation const defaultBackdrop = [ { offset: 0, opacity: 'var(--backdrop-opacity)' }, - { offset: 1, opacity: 0.01 } - ] + { offset: 1, opacity: 0.01 }, + ]; const customBackdrop = [ { offset: 0, opacity: 'var(--backdrop-opacity)' }, { offset: 1 - backdropBreakpoint, opacity: 0 }, - { offset: 1, opacity: 0 } - ] + { offset: 1, opacity: 0 }, + ]; const SheetDefaults = { WRAPPER_KEYFRAMES: [ { offset: 0, transform: 'translateY(0%)' }, - { offset: 1, transform: 'translateY(100%)' } + { offset: 1, transform: 'translateY(100%)' }, ], - BACKDROP_KEYFRAMES: (backdropBreakpoint !== 0) ? customBackdrop : defaultBackdrop + BACKDROP_KEYFRAMES: backdropBreakpoint !== 0 ? customBackdrop : defaultBackdrop, }; const contentEl = baseEl.querySelector('ion-content'); @@ -70,8 +70,8 @@ export const createSheetGesture = ( let offset = 0; let canDismissBlocksGesture = false; const canDismissMaxStep = 0.95; - const wrapperAnimation = animation.childAnimations.find(ani => ani.id === 'wrapperAnimation'); - const backdropAnimation = animation.childAnimations.find(ani => ani.id === 'backdropAnimation'); + const wrapperAnimation = animation.childAnimations.find((ani) => ani.id === 'wrapperAnimation'); + const backdropAnimation = animation.childAnimations.find((ani) => ani.id === 'backdropAnimation'); const maxBreakpoint = breakpoints[breakpoints.length - 1]; const minBreakpoint = breakpoints[0]; @@ -85,7 +85,7 @@ export const createSheetGesture = ( * the sheet. */ baseEl.classList.remove('ion-disable-focus-trap'); - } + }; const disableBackdrop = () => { baseEl.style.setProperty('pointer-events', 'none'); @@ -99,7 +99,7 @@ export const createSheetGesture = ( * for the sheet temporarily. */ baseEl.classList.add('ion-disable-focus-trap'); - } + }; /** * After the entering animation completes, @@ -193,8 +193,9 @@ export const createSheetGesture = ( */ const initialStep = 1 - currentBreakpoint; const secondToLastBreakpoint = breakpoints.length > 1 ? 1 - breakpoints[1] : undefined; - const step = initialStep + (detail.deltaY / height); - const isAttemptingDismissWithCanDismiss = secondToLastBreakpoint !== undefined && step >= secondToLastBreakpoint && canDismissBlocksGesture; + const step = initialStep + detail.deltaY / height; + const isAttemptingDismissWithCanDismiss = + secondToLastBreakpoint !== undefined && step >= secondToLastBreakpoint && canDismissBlocksGesture; /** * If we are blocking the gesture from dismissing, @@ -217,7 +218,11 @@ export const createSheetGesture = ( * why we subtract secondToLastBreakpoint. This lets us get * the result as a value from 0 to 1. */ - const processedStep = isAttemptingDismissWithCanDismiss && secondToLastBreakpoint !== undefined ? secondToLastBreakpoint + calculateSpringStep((step - secondToLastBreakpoint) / (maxStep - secondToLastBreakpoint)) : step; + const processedStep = + isAttemptingDismissWithCanDismiss && secondToLastBreakpoint !== undefined + ? secondToLastBreakpoint + + calculateSpringStep((step - secondToLastBreakpoint) / (maxStep - secondToLastBreakpoint)) + : step; offset = clamp(0.0001, processedStep, maxStep); animation.progressStep(offset); @@ -263,12 +268,21 @@ export const createSheetGesture = ( if (wrapperAnimation && backdropAnimation) { wrapperAnimation.keyframes([ { offset: 0, transform: `translateY(${breakpointOffset * 100}%)` }, - { offset: 1, transform: `translateY(${(1 - snapToBreakpoint) * 100}%)` } + { offset: 1, transform: `translateY(${(1 - snapToBreakpoint) * 100}%)` }, ]); backdropAnimation.keyframes([ - { offset: 0, opacity: `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(1 - breakpointOffset, backdropBreakpoint)})` }, - { offset: 1, opacity: `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(snapToBreakpoint, backdropBreakpoint)})` } + { + offset: 0, + opacity: `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet( + 1 - breakpointOffset, + backdropBreakpoint + )})`, + }, + { + offset: 1, + opacity: `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(snapToBreakpoint, backdropBreakpoint)})`, + }, ]); animation.progressStep(0); @@ -281,56 +295,58 @@ export const createSheetGesture = ( gesture.enable(false); animation - .onFinish(() => { - if (shouldRemainOpen) { + .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); + } + } /** - * 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. + * This must be a one time callback + * otherwise a new callback will + * be added every time onEnd runs. */ - 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 }) + }, + { oneTimeCallback: true } + ) .progressEnd(1, 0, 500); if (shouldPreventDismiss) { @@ -349,11 +365,11 @@ export const createSheetGesture = ( canStart, onStart, onMove, - onEnd + onEnd, }); return { gesture, - moveSheetToBreakpoint + moveSheetToBreakpoint, }; }; diff --git a/core/src/components/modal/gestures/swipe-to-close.ts b/core/src/components/modal/gestures/swipe-to-close.ts index b8e735f4e3..9acc5d5169 100644 --- a/core/src/components/modal/gestures/swipe-to-close.ts +++ b/core/src/components/modal/gestures/swipe-to-close.ts @@ -1,6 +1,6 @@ import type { Animation } from '../../../interface'; import { getTimeGivenProgression } from '../../../utils/animation/cubic-bezier'; -import type { GestureDetail} from '../../../utils/gesture'; +import type { GestureDetail } from '../../../utils/gesture'; import { createGesture } from '../../../utils/gesture'; import { clamp } from '../../../utils/helpers'; @@ -11,21 +11,16 @@ export const SwipeToCloseDefaults = { MIN_PRESENTING_SCALE: 0.93, }; -export const createSwipeToCloseGesture = ( - el: HTMLIonModalElement, - animation: Animation, - onDismiss: () => void -) => { +export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: Animation, onDismiss: () => void) => { const height = el.offsetHeight; let isOpen = false; let canDismissBlocksGesture = false; - const canDismissMaxStep = 0.20; + const canDismissMaxStep = 0.2; const canStart = (detail: GestureDetail) => { const target = detail.event.target as HTMLElement | null; - if (target === null || - !(target as any).closest) { + if (target === null || !(target as any).closest) { return true; } @@ -49,7 +44,7 @@ export const createSwipeToCloseGesture = ( * Remove undefined check */ canDismissBlocksGesture = el.canDismiss !== undefined && el.canDismiss !== true; - animation.progressStart(true, (isOpen) ? 1 : 0); + animation.progressStart(true, isOpen ? 1 : 0); }; const onMove = (detail: GestureDetail) => { @@ -105,7 +100,7 @@ export const createSwipeToCloseGesture = ( * canDismiss is checked. */ const shouldComplete = !isAttempingDismissWithCanDismiss && threshold >= 0.5; - let newStepValue = (shouldComplete) ? -0.001 : 0.001; + let newStepValue = shouldComplete ? -0.001 : 0.001; if (!shouldComplete) { animation.easing('cubic-bezier(1, 0, 0.68, 0.28)'); @@ -115,7 +110,9 @@ export const createSwipeToCloseGesture = ( newStepValue += getTimeGivenProgression([0, 0], [0.32, 0.72], [0, 1], [1, 1], clampedStep)[0]; } - const duration = (shouldComplete) ? computeDuration(step * height, velocity) : computeDuration((1 - clampedStep) * height, velocity); + const duration = shouldComplete + ? computeDuration(step * height, velocity) + : computeDuration((1 - clampedStep) * height, velocity); isOpen = shouldComplete; gesture.enable(false); @@ -126,7 +123,7 @@ export const createSwipeToCloseGesture = ( gesture.enable(true); } }) - .progressEnd((shouldComplete) ? 1 : 0, newStepValue, duration); + .progressEnd(shouldComplete ? 1 : 0, newStepValue, duration); /** * If the canDismiss value blocked the gesture @@ -140,7 +137,7 @@ export const createSwipeToCloseGesture = ( * check canDismiss. 25% was chosen * to avoid accidental swipes. */ - if (isAttempingDismissWithCanDismiss && clampedStep > (maxStep / 4)) { + if (isAttempingDismissWithCanDismiss && clampedStep > maxStep / 4) { handleCanDismiss(el, animation); } else if (shouldComplete) { onDismiss(); @@ -156,7 +153,7 @@ export const createSwipeToCloseGesture = ( canStart, onStart, onMove, - onEnd + onEnd, }); return gesture; }; diff --git a/core/src/components/modal/gestures/utils.ts b/core/src/components/modal/gestures/utils.ts index efff403def..36268e8412 100644 --- a/core/src/components/modal/gestures/utils.ts +++ b/core/src/components/modal/gestures/utils.ts @@ -1,9 +1,6 @@ import type { Animation } from '../../../interface'; -export const handleCanDismiss = async ( - el: HTMLIonModalElement, - animation: Animation, -) => { +export const handleCanDismiss = async (el: HTMLIonModalElement, animation: Animation) => { /** * If canDismiss is not a function * then we can return early. If canDismiss is `true`, @@ -12,7 +9,9 @@ export const handleCanDismiss = async ( * this code block is never reached. If canDismiss is `false`, * then we never dismiss. */ - if (typeof el.canDismiss !== 'function') { return; } + if (typeof el.canDismiss !== 'function') { + return; + } /** * Run the canDismiss callback. @@ -20,7 +19,9 @@ export const handleCanDismiss = async ( * then we can proceed with dismiss. */ const shouldDismiss = await el.canDismiss(); - if (!shouldDismiss) { return; } + if (!shouldDismiss) { + return; + } /** * If canDismiss resolved after the snap @@ -34,13 +35,16 @@ export const handleCanDismiss = async ( */ if (animation.isRunning()) { - animation.onFinish(() => { - el.dismiss(undefined, 'handler') - }, { oneTimeCallback: true }) + animation.onFinish( + () => { + el.dismiss(undefined, 'handler'); + }, + { oneTimeCallback: true } + ); } else { el.dismiss(undefined, 'handler'); } -} +}; /** * This function lets us simulate a realistic spring-like animation @@ -115,5 +119,5 @@ export const handleCanDismiss = async ( * give you a complex differential equation too. */ export const calculateSpringStep = (t: number) => { - return 0.00255275 * 2.71828 ** (-14.9619 * t) - 1.00255 * 2.71828 ** (-0.0380968 * t) + 1 -} + return 0.00255275 * 2.71828 ** (-14.9619 * t) - 1.00255 * 2.71828 ** (-0.0380968 * t) + 1; +}; diff --git a/core/src/components/modal/modal-interface.ts b/core/src/components/modal/modal-interface.ts index 1e1cb4b848..ffe46d2768 100644 --- a/core/src/components/modal/modal-interface.ts +++ b/core/src/components/modal/modal-interface.ts @@ -33,7 +33,7 @@ export interface ModalAnimationOptions { backdropBreakpoint?: number; } -export type ModalAttributes = JSXBase.HTMLAttributes +export type ModalAttributes = JSXBase.HTMLAttributes; export interface ModalBreakpointChangeEventDetail { breakpoint: number; diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index fde148894c..d99316bc06 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -1,10 +1,21 @@ -import type { ComponentInterface, EventEmitter} from '@stencil/core'; +import type { ComponentInterface, EventEmitter } from '@stencil/core'; import { Component, Element, Event, Host, Method, Prop, State, Watch, h, writeTask } from '@stencil/core'; import { printIonWarning } from '@utils/logging'; import { config } from '../../global/config'; import { getIonMode } from '../../global/ionic-global'; -import type { Animation, AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, Gesture, ModalAttributes, ModalBreakpointChangeEventDetail, OverlayEventDetail, OverlayInterface } from '../../interface'; +import type { + Animation, + AnimationBuilder, + ComponentProps, + ComponentRef, + FrameworkDelegate, + Gesture, + ModalAttributes, + ModalBreakpointChangeEventDetail, + OverlayEventDetail, + OverlayInterface, +} from '../../interface'; import { CoreDelegate, attachComponent, detachComponent } from '../../utils/framework-delegate'; import { raf } from '../../utils/helpers'; import { KEYBOARD_DID_OPEN } from '../../utils/keyboard/keyboard'; @@ -16,7 +27,7 @@ import { iosEnterAnimation } from './animations/ios.enter'; import { iosLeaveAnimation } from './animations/ios.leave'; import { mdEnterAnimation } from './animations/md.enter'; import { mdLeaveAnimation } from './animations/md.leave'; -import type { MoveSheetToBreakpointOptions} from './gestures/sheet'; +import type { MoveSheetToBreakpointOptions } from './gestures/sheet'; import { createSheetGesture } from './gestures/sheet'; import { createSwipeToCloseGesture } from './gestures/swipe-to-close'; @@ -33,9 +44,9 @@ import { createSwipeToCloseGesture } from './gestures/swipe-to-close'; tag: 'ion-modal', styleUrls: { ios: 'modal.ios.scss', - md: 'modal.md.scss' + md: 'modal.md.scss', }, - shadow: true + shadow: true, }) export class Modal implements ComponentInterface, OverlayInterface { private gesture?: Gesture; @@ -297,19 +308,21 @@ export class Modal implements ComponentInterface, OverlayInterface { * If user has custom ID set then we should * not assign the default incrementing ID. */ - this.modalId = (this.el.hasAttribute('id')) ? this.el.getAttribute('id')! : `ion-modal-${this.modalIndex}`; - const isSheetModal = this.isSheetModal = breakpoints !== undefined && initialBreakpoint !== undefined; + this.modalId = this.el.hasAttribute('id') ? this.el.getAttribute('id')! : `ion-modal-${this.modalIndex}`; + const isSheetModal = (this.isSheetModal = breakpoints !== undefined && initialBreakpoint !== undefined); if (isSheetModal) { this.currentBreakpoint = this.initialBreakpoint; } if (breakpoints !== undefined && initialBreakpoint !== undefined && !breakpoints.includes(initialBreakpoint)) { - printIonWarning('Your breakpoints array must include the initialBreakpoint value.') + printIonWarning('Your breakpoints array must include the initialBreakpoint value.'); } if (swipeToClose) { - printIonWarning('swipeToClose has been deprecated in favor of canDismiss.\n\nIf you want a card modal to be swipeable, set canDismiss to `true`. In the next major release of Ionic, swipeToClose will be removed, and all card modals will be swipeable by default.'); + printIonWarning( + 'swipeToClose has been deprecated in favor of canDismiss.\n\nIf you want a card modal to be swipeable, set canDismiss to `true`. In the next major release of Ionic, swipeToClose will be removed, and all card modals will be swipeable by default.' + ); } } @@ -332,22 +345,24 @@ export class Modal implements ComponentInterface, OverlayInterface { destroyTriggerInteraction(); } - const triggerEl = (trigger !== undefined) ? document.getElementById(trigger) : null; - if (!triggerEl) { return; } + const triggerEl = trigger !== undefined ? document.getElementById(trigger) : null; + if (!triggerEl) { + return; + } const configureTriggerInteraction = (trigEl: HTMLElement, modalEl: HTMLIonModalElement) => { const openModal = () => { modalEl.present(); - } + }; trigEl.addEventListener('click', openModal); return () => { trigEl.removeEventListener('click', openModal); - } - } + }; + }; this.destroyTriggerInteraction = configureTriggerInteraction(triggerEl, el); - } + }; /** * Determines whether or not an overlay @@ -362,8 +377,8 @@ export class Modal implements ComponentInterface, OverlayInterface { if (this.workingDelegate && !force) { return { delegate: this.workingDelegate, - inline: this.inline - } + inline: this.inline, + }; } /** @@ -376,10 +391,10 @@ export class Modal implements ComponentInterface, OverlayInterface { * correct place. */ const parentEl = this.el.parentNode as HTMLElement | null; - const inline = this.inline = parentEl !== null && !this.hasController; - const delegate = this.workingDelegate = (inline) ? this.delegate || this.coreDelegate : this.delegate + const inline = (this.inline = parentEl !== null && !this.hasController); + const delegate = (this.workingDelegate = inline ? this.delegate || this.coreDelegate : this.delegate); - return { inline, delegate } + return { inline, delegate }; } /** @@ -394,7 +409,9 @@ export class Modal implements ComponentInterface, OverlayInterface { * TODO (FW-937) - Remove the following check in * the next major release of Ionic. */ - if (canDismiss === undefined) { return true; } + if (canDismiss === undefined) { + return true; + } if (typeof canDismiss === 'function') { return canDismiss(); @@ -426,7 +443,7 @@ export class Modal implements ComponentInterface, OverlayInterface { const data = { ...this.componentProps, - modal: this.el + modal: this.el, }; const { inline, delegate } = this.getDelegate(true); @@ -436,22 +453,26 @@ export class Modal implements ComponentInterface, OverlayInterface { writeTask(() => this.el.classList.add('show-modal')); - this.currentTransition = present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, { presentingEl: this.presentingElement, currentBreakpoint: this.initialBreakpoint, backdropBreakpoint: this.backdropBreakpoint }); + this.currentTransition = present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, { + presentingEl: this.presentingElement, + currentBreakpoint: this.initialBreakpoint, + backdropBreakpoint: this.backdropBreakpoint, + }); await this.currentTransition; if (this.isSheetModal) { this.initSheetGesture(); - /** - * TODO (FW-937) - In the next major release of Ionic, all card modals - * will be swipeable by default. canDismiss will be used to determine if the - * modal can be dismissed. This check should change to check the presence of - * presentingElement instead. - * - * If we did not do this check, then not using swipeToClose would mean you could - * not run canDismiss on swipe as there would be no swipe gesture created. - */ + /** + * TODO (FW-937) - In the next major release of Ionic, all card modals + * will be swipeable by default. canDismiss will be used to determine if the + * modal can be dismissed. This check should change to check the presence of + * presentingElement instead. + * + * If we did not do this check, then not using swipeToClose would mean you could + * not run canDismiss on swipe as there would be no swipe gesture created. + */ } else if (this.swipeToClose || (this.canDismiss !== undefined && this.presentingElement !== undefined)) { this.initSwipeToClose(); } @@ -472,11 +493,11 @@ export class Modal implements ComponentInterface, OverlayInterface { this.gesture.enable(false); raf(() => { if (this.gesture) { - this.gesture.enable(true) + this.gesture.enable(true); } }); } - } + }; window.addEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback); } @@ -484,35 +505,32 @@ export class Modal implements ComponentInterface, OverlayInterface { } private initSwipeToClose() { - if (getIonMode(this) !== 'ios') { return; } + if (getIonMode(this) !== 'ios') { + return; + } // All of the elements needed for the swipe gesture // should be in the DOM and referenced by now, except // for the presenting el const animationBuilder = this.leaveAnimation || config.get('modalLeave', iosLeaveAnimation); - const ani = this.animation = animationBuilder(this.el, { presentingEl: this.presentingElement }); - this.gesture = createSwipeToCloseGesture( - this.el, - ani, - () => { - /** - * While the gesture animation is finishing - * it is possible for a user to tap the backdrop. - * This would result in the dismiss animation - * being played again. Typically this is avoided - * by setting `presented = false` on the overlay - * component; however, we cannot do that here as - * that would prevent the element from being - * removed from the DOM. - */ - this.gestureAnimationDismissing = true; - this.animation!.onFinish(async () => { - await this.dismiss(undefined, 'gesture'); - this.gestureAnimationDismissing = false; - }); - }, - - ); + const ani = (this.animation = animationBuilder(this.el, { presentingEl: this.presentingElement })); + this.gesture = createSwipeToCloseGesture(this.el, ani, () => { + /** + * While the gesture animation is finishing + * it is possible for a user to tap the backdrop. + * This would result in the dismiss animation + * being played again. Typically this is avoided + * by setting `presented = false` on the overlay + * component; however, we cannot do that here as + * that would prevent the element from being + * removed from the DOM. + */ + this.gestureAnimationDismissing = true; + this.animation!.onFinish(async () => { + await this.dismiss(undefined, 'gesture'); + this.gestureAnimationDismissing = false; + }); + }); this.gesture.enable(true); } @@ -524,7 +542,11 @@ export class Modal implements ComponentInterface, OverlayInterface { } const animationBuilder = this.enterAnimation || config.get('modalEnter', iosEnterAnimation); - const ani: Animation = this.animation = animationBuilder(this.el, { presentingEl: this.presentingElement, currentBreakpoint: initialBreakpoint, backdropBreakpoint }); + const ani: Animation = (this.animation = animationBuilder(this.el, { + presentingEl: this.presentingElement, + currentBreakpoint: initialBreakpoint, + backdropBreakpoint, + })); ani.progressStart(true, 1); @@ -589,7 +611,7 @@ export class Modal implements ComponentInterface, OverlayInterface { * for calling the dismiss method, we should * not run the canDismiss check again. */ - if (role !== 'handler' && !await this.checkCanDismiss()) { + if (role !== 'handler' && !(await this.checkCanDismiss())) { return false; } @@ -612,7 +634,11 @@ export class Modal implements ComponentInterface, OverlayInterface { const enteringAnimation = activeAnimations.get(this) || []; - this.currentTransition = dismiss(this, data, role, 'modalLeave', iosLeaveAnimation, mdLeaveAnimation, { presentingEl: this.presentingElement, currentBreakpoint: this.currentBreakpoint || this.initialBreakpoint, backdropBreakpoint: this.backdropBreakpoint }); + this.currentTransition = dismiss(this, data, role, 'modalLeave', iosLeaveAnimation, mdLeaveAnimation, { + presentingEl: this.presentingElement, + currentBreakpoint: this.currentBreakpoint || this.initialBreakpoint, + backdropBreakpoint: this.backdropBreakpoint, + }); const dismissed = await this.currentTransition; @@ -629,7 +655,7 @@ export class Modal implements ComponentInterface, OverlayInterface { this.gesture.destroy(); } - enteringAnimation.forEach(ani => ani.destroy()); + enteringAnimation.forEach((ani) => ani.destroy()); } this.currentTransition = undefined; @@ -664,7 +690,9 @@ export class Modal implements ComponentInterface, OverlayInterface { return; } if (!this.breakpoints!.includes(breakpoint)) { - printIonWarning(`Attempted to set invalid breakpoint value ${breakpoint}. Please double check that the breakpoint value is part of your defined breakpoints.`); + printIonWarning( + `Attempted to set invalid breakpoint value ${breakpoint}. Please double check that the breakpoint value is part of your defined breakpoints.` + ); return; } @@ -693,14 +721,14 @@ export class Modal implements ComponentInterface, OverlayInterface { private onBackdropTap = () => { this.dismiss(undefined, BACKDROP); - } + }; private onDismiss = (ev: UIEvent) => { ev.stopPropagation(); ev.preventDefault(); this.dismiss(); - } + }; private onLifecycle = (modalEvent: CustomEvent) => { const el = this.usersElement; @@ -709,11 +737,11 @@ export class Modal implements ComponentInterface, OverlayInterface { const ev = new CustomEvent(name, { bubbles: false, cancelable: false, - detail: modalEvent.detail + detail: modalEvent.detail, }); el.dispatchEvent(ev); } - } + }; render() { const { handle, isSheetModal, presentingElement, htmlAttributes } = this; @@ -728,7 +756,7 @@ export class Modal implements ComponentInterface, OverlayInterface { no-router aria-modal="true" tabindex="-1" - {...htmlAttributes as any} + {...(htmlAttributes as any)} style={{ zIndex: `${20000 + this.overlayIndex}`, }} @@ -738,7 +766,7 @@ export class Modal implements ComponentInterface, OverlayInterface { [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, 'overlay-hidden': true, - ...getClassMap(this.cssClass) + ...getClassMap(this.cssClass), }} id={modalId} onIonBackdropTap={this.onBackdropTap} @@ -748,30 +776,29 @@ export class Modal implements ComponentInterface, OverlayInterface { onIonModalWillDismiss={this.onLifecycle} onIonModalDidDismiss={this.onLifecycle} > - this.backdropEl = el} visible={this.showBackdrop} tappable={this.backdropDismiss} part="backdrop" /> + (this.backdropEl = el)} + visible={this.showBackdrop} + tappable={this.backdropDismiss} + part="backdrop" + /> {mode === 'ios' && } -