Compare commits

..

33 Commits

Author SHA1 Message Date
Liam DeBeasi
a3666ddf0c fix(header): avoid flicker on ios when collapsing (#19850)
fixes #19839
2019-11-07 09:51:03 -05:00
Liam DeBeasi
cace1b357e fix(ios): only animate large title if back button is in start slot (#19846)
fixes #19840
2019-11-06 11:14:09 -05:00
Mike Hartington
ab9d3b2696 chore(): update stencil (#19734)
* chore(): update stencil

* bump deps

* chore(): bump jest
2019-11-01 10:06:23 -07:00
Mike Hartington
60aa027903 chore(): fix tabs (#19821)
* fix(tabs): do not wait on child tab setActive() lazy loading

* whenDefined example

* chore(): fix lint
2019-11-01 09:29:30 -07:00
Liam DeBeasi
7bd4412889 fix(animation): track correctly when updating CSS Animation (#19813)
* fix bug

* update menu

* fix
2019-10-31 12:50:54 -04:00
Liam DeBeasi
96a5e600e5 feat(animation): cubic-bezier easing conversion utility (experimental) (#19788)
resolves #19789
2019-10-31 10:16:33 -04:00
Ely Lucas
33cf08bf0e chore(react): removing beta verbiage from readme (#19803) 2019-10-30 17:10:00 -06:00
Ely Lucas
7e6e81db55 chore(build) setting max workers in core spec and e2e tests to 2 (#19805) 2019-10-30 16:47:49 -06:00
Ray Clanan
f2d18a2346 chore(react): update readme to remove capacitor instruction 2019-10-30 15:51:37 -06:00
Ely Lucas
77d18c7172 release-4.11.3 2019-10-30 13:31:36 -06:00
Ely Lucas
ec43b8c9b8 chore(changelog): fixing changelog items 2019-10-30 13:18:05 -06:00
Ely Lucas
b4938db5f9 4.11.3 2019-10-30 13:18:04 -06:00
Ely Lucas
9c7beff910 chore(build): setting max workers to 2 in core tests 2019-10-30 13:16:15 -06:00
Ely Lucas
16815b3cc8 chore(build): awaiting on test task 2019-10-30 13:16:15 -06:00
Ely Lucas
e23d91c6e7 fix(react): checking if node is actually an element before treating it like one, fixes #19769 (#19783) 2019-10-30 13:16:15 -06:00
Ely Lucas
1f2b161ccd fix(react): unmount leaving view when using browser back button, fixes #19749 (#19781) 2019-10-30 13:16:15 -06:00
Ely Lucas
67737bbb54 fix(react): checking isOpen again after async call before opening overlay, fixes #19755 2019-10-30 13:16:15 -06:00
Ely Lucas
29f8178794 fix(react): don't remove current view, provide a better method to determine showGoBack fixes #19731 and #19732 2019-10-30 13:16:15 -06:00
Liam DeBeasi
d80f45516d feat(split-pane): convert to shadow component, add width, max-width, and min-width vars (#19754)
resolves #17088


Co-authored-by: troyanskiy <roman.rosluk@utopix.ch>
2019-10-30 14:16:39 -04:00
Liam DeBeasi
420aa66392 fix(toast): call button handler on cancel (#19793)
fixes #19791
2019-10-30 14:02:18 -04:00
Brandy Carney
7988720b1c fix(picker): pass data and role to the dismiss based on button click or backdrop (#19787)
- Pass the button role on dismiss of the picker (on button click or backdrop tap)
- Pass the selected values in the data on dismiss ONLY if the dismiss role is not "cancel" or "backdrop"
- Call the cancel handler when dismissing if the role is "cancel" or "backdrop"

Fixes #18454
2019-10-30 13:25:11 -04:00
Liam DeBeasi
67a7e232b9 feat(angular): Expose Ionic Animations via the AnimationController (#19745)
* Add animation controller for angular

* remove empty constructor

* sync with master
2019-10-29 16:14:26 -04:00
Liam DeBeasi
7d417154c5 feat(animation): Animation Identifiers (#19771)
resolves #19550
2019-10-29 15:44:41 -04:00
Liam DeBeasi
b642b532e8 fix(header): translucent toolbars now work with collapsible headers (#19774)
fixes #19773
2019-10-29 15:43:26 -04:00
Liam DeBeasi
0a7aae28a7 fix(header): avoid flicker on collapsible header load (#19682)
* fix flicker on collapse load

* remove old test
2019-10-29 14:36:26 -04:00
Liam DeBeasi
39fb8f6720 fix(menu): clicking backdrop closes menu properly (#19785) 2019-10-29 13:26:15 -04:00
Brandy Carney
f63d37a4c5 fix(textarea): remove padding from textarea placeholder (#19694)
fixes #19616
2019-10-22 11:01:15 -04:00
Liam DeBeasi
9568f228b2 merge release-4.11.2
Sync 4.11.2
2019-10-21 17:54:25 -04:00
Liam DeBeasi
a2e1c55911 4.11.2 2019-10-21 17:36:16 -04:00
Ely Lucas
834666c754 fix(react): adding change events to iontabs, fixes #19665 (#19711) 2019-10-21 17:32:37 -04:00
Ely Lucas
5acb58a03d fix(react): removing pages from DOM on nav, fixes #19701 (#19712) 2019-10-21 17:32:32 -04:00
Ely Lucas
ce7314db0b chore(react): removing console log and tree shaking change 2019-10-21 17:32:28 -04:00
Ely Lucas
71fdcbc1b4 fix(react): adding HashRouter to available ion routers, fixes #19621 (#19683) 2019-10-21 17:32:22 -04:00
51 changed files with 592 additions and 298 deletions

View File

@@ -158,7 +158,7 @@ function preparePackage(tasks, package, version, install) {
});
projectTasks.push({
title: `${pkg.name}: test`,
task: () => execa('npm', ['test'], { cwd: projectRoot })
task: async () => await execa('npm', ['test'], { cwd: projectRoot })
});
}

View File

@@ -1,3 +1,35 @@
## [4.11.3](https://github.com/ionic-team/ionic/compare/v4.11.1...v4.11.3) (2019-10-30)
### Bug Fixes
* **react:** adding change events to iontabs, fixes [#19665](https://github.com/ionic-team/ionic/issues/19665) ([#19711](https://github.com/ionic-team/ionic/issues/19711)) ([b7baf24](https://github.com/ionic-team/ionic/commit/b7baf24e5053a379156e6c3d82c2b5d3afa999f1))
* **react:** adding HashRouter to available ion routers, fixes [#19621](https://github.com/ionic-team/ionic/issues/19621) ([#19683](https://github.com/ionic-team/ionic/issues/19683)) ([fcdbb3c](https://github.com/ionic-team/ionic/commit/fcdbb3ce98747d3b37107904ca110daad95e48bc))
* **react:** checking if node is actually an element before treating it like one, fixes [#19769](https://github.com/ionic-team/ionic/issues/19769) ([#19783](https://github.com/ionic-team/ionic/issues/19783)) ([9d0caf6](https://github.com/ionic-team/ionic/commit/9d0caf6de070145c4af618847b27e24c49027b8e))
* **react:** checking isOpen again after async call before opening overlay, fixes [#19755](https://github.com/ionic-team/ionic/issues/19755) ([f70e71a](https://github.com/ionic-team/ionic/commit/f70e71a3d461cdab65626a5a7e1b6f4d03b852b1))
* **react:** don't remove current view, provide a better method to determine showGoBack fixes [#19731](https://github.com/ionic-team/ionic/issues/19731) and [#19732](https://github.com/ionic-team/ionic/issues/19732) ([31c754d](https://github.com/ionic-team/ionic/commit/31c754dab7ada494ff5f0026d5cf3f7f65198eff))
* **react:** removing pages from DOM on nav, fixes [#19701](https://github.com/ionic-team/ionic/issues/19701) ([#19712](https://github.com/ionic-team/ionic/issues/19712)) ([ee21d3a](https://github.com/ionic-team/ionic/commit/ee21d3ae43d8c6b076387a58bca655a56c920bcd))
* **react:** unmount leaving view when using browser back button, fixes [#19749](https://github.com/ionic-team/ionic/issues/19749) ([#19781](https://github.com/ionic-team/ionic/issues/19781)) ([2dc5540](https://github.com/ionic-team/ionic/commit/2dc554091056612f1bcd2751d6eeb41cae488751))
## [4.11.2](https://github.com/ionic-team/ionic/compare/v4.11.0...v4.11.2) (2019-10-21)
### Bug Fixes
* **animations:** ensure all elements are cleaned up when calling .destroy() ([#19654](https://github.com/ionic-team/ionic/issues/19654)) ([d97e167](https://github.com/ionic-team/ionic/commit/d97e167))
* **header:** collapsible header works in tabs ([#19658](https://github.com/ionic-team/ionic/issues/19658)) ([4853909](https://github.com/ionic-team/ionic/commit/4853909)), closes [#19640](https://github.com/ionic-team/ionic/issues/19640)
* **ios:** hide leaving view after nav transition to avoid flicker ([#19691](https://github.com/ionic-team/ionic/issues/19691)) ([70e0562](https://github.com/ionic-team/ionic/commit/70e0562)), closes [#19674](https://github.com/ionic-team/ionic/issues/19674)
* **menu:** clamp out of bounds swipe value ([#19684](https://github.com/ionic-team/ionic/issues/19684)) ([1535e95](https://github.com/ionic-team/ionic/commit/1535e95)), closes [#18927](https://github.com/ionic-team/ionic/issues/18927)
* **react:** add IonPicker as controller component, fixes [#19620](https://github.com/ionic-team/ionic/issues/19620) ([#19643](https://github.com/ionic-team/ionic/issues/19643)) ([ed98d9e](https://github.com/ionic-team/ionic/commit/ed98d9e))
* **react:** adding change events to IonTabs, fixes [#19665](https://github.com/ionic-team/ionic/issues/19665) ([#19711](https://github.com/ionic-team/ionic/issues/19711)) ([b7baf24](https://github.com/ionic-team/ionic/commit/b7baf24))
* **react:** adding HashRouter to available ion routers, fixes [#19621](https://github.com/ionic-team/ionic/issues/19621) ([#19683](https://github.com/ionic-team/ionic/issues/19683)) ([fcdbb3c](https://github.com/ionic-team/ionic/commit/fcdbb3c))
* **react:** pages no longer hidden when navigating between tabs, fixes [#19646](https://github.com/ionic-team/ionic/issues/19646) ([#19647](https://github.com/ionic-team/ionic/issues/19647)) ([8776556](https://github.com/ionic-team/ionic/commit/8776556))
* **react:** ensure views are removed from DOM after navigating back, fixes [#19701](https://github.com/ionic-team/ionic/issues/19701) ([#19712](https://github.com/ionic-team/ionic/issues/19712)) ([ee21d3a](https://github.com/ionic-team/ionic/commit/ee21d3a))
# [5.0.0-beta.0](https://github.com/ionic-team/ionic/compare/v4.11.1...v5.0.0-beta.0) (2019-10-15)

View File

@@ -30,6 +30,7 @@ export { ToastController } from './providers/toast-controller';
export { NavController } from './providers/nav-controller';
export { DomController } from './providers/dom-controller';
export { Config } from './providers/config';
export { AnimationController } from './providers/animation-controller';
// ROUTER STRATEGY
export { IonicRouteStrategy } from './util/ionic-router-reuse-strategy';

View File

@@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { Animation, createAnimation, getTimeGivenProgression } from '@ionic/core';
@Injectable({
providedIn: 'root',
})
export class AnimationController {
/**
* Create a new animation
*/
create(animationId?: string): Animation {
return createAnimation(animationId);
}
/**
* EXPERIMENTAL
*
* Given a progression and a cubic bezier function,
* this utility returns the time value(s) at which the
* cubic bezier reaches the given time progression.
*
* If the cubic bezier never reaches the progression
* the result will be an empty array.
*
* This is most useful for switching between easing curves
* when doing a gesture animation (i.e. going from linear easing
* during a drag, to another easing when `progressEnd` is called)
*/
easingTime(p0: number[], p1: number[], p2: number[], p3: number[], progression: number): number[] {
return getTimeGivenProgression(p0, p1, p2, p3, progression);
}
}

View File

@@ -1098,12 +1098,15 @@ ion-spinner,prop,name,"bubbles" | "circles" | "circular" | "crescent" | "dots" |
ion-spinner,prop,paused,boolean,false,false,false
ion-spinner,css-prop,--color
ion-split-pane,none
ion-split-pane,shadow
ion-split-pane,prop,contentId,string | undefined,undefined,false,false
ion-split-pane,prop,disabled,boolean,false,false,false
ion-split-pane,prop,when,boolean | string,QUERY['lg'],false,false
ion-split-pane,event,ionSplitPaneVisible,{ visible: boolean; },true
ion-split-pane,css-prop,--border
ion-split-pane,css-prop,--side-max-width
ion-split-pane,css-prop,--side-min-width
ion-split-pane,css-prop,--side-width
ion-tab,shadow
ion-tab,prop,component,Function | HTMLElement | null | string | undefined,undefined,false,false

View File

@@ -34,18 +34,18 @@
"tslib": "^1.10.0"
},
"devDependencies": {
"@stencil/core": "1.6.1",
"@stencil/core": "1.7.5",
"@stencil/sass": "1.0.1",
"@types/jest": "24.0.17",
"@types/node": "12.7.1",
"@types/jest": "24.0.21",
"@types/node": "12.12.3",
"@types/puppeteer": "1.19.1",
"@types/swiper": "4.4.4",
"aws-sdk": "^2.497.0",
"clean-css-cli": "^4.1.11",
"domino": "^2.1.3",
"fs-extra": "^8.0.1",
"jest": "24.8.0",
"jest-cli": "24.8.0",
"jest": "24.9.0",
"jest-cli": "24.9.0",
"np": "^5.0.3",
"pixelmatch": "4.0.2",
"puppeteer": "1.20.0",
@@ -79,10 +79,10 @@
"prerelease": "npm run validate && np prerelease --yolo --any-branch --tag next",
"prerender.e2e": "node scripts/testing/prerender.js",
"start": "npm run build.css && stencil build --dev --watch --serve",
"test": "stencil test --spec --e2e",
"test.spec": "stencil test --spec",
"test": "stencil test --spec --e2e --max-workers=2",
"test.spec": "stencil test --spec --max-workers=2",
"test.spec.debug": "npx --node-arg=\"--inspect-brk\" stencil test --spec",
"test.e2e": "stencil test --e2e",
"test.e2e": "stencil test --e2e --max-workers=2",
"test.screenshot": "stencil test --e2e --screenshot --screenshot-connector=scripts/screenshot/dev.js",
"test.screenshot.ci": "stencil test --e2e --screenshot --screenshot-connector=scripts/screenshot/ci.js --ci",
"test.watch": "jest --watch --no-cache",

View File

@@ -1,3 +1,4 @@
/* eslint-disable */
/* tslint:disable */
/**
* This is an autogenerated file created by the Stencil compiler.

View File

@@ -51,23 +51,24 @@
padding-bottom: 13px;
}
ion-toolbar.in-toolbar ion-title,
ion-toolbar.in-toolbar ion-buttons {
.header-collapse-main ion-toolbar.in-toolbar ion-title,
.header-collapse-main ion-toolbar.in-toolbar ion-buttons {
transition: all 0.2s ease-in-out;
}
/**
* There is a bug in Safari where animating the opacity
* on an element in a scrollable container while scrolling
* causes the scroll position to jump to the top
*/
.header-collapse-condense ion-toolbar ion-title,
.header-collapse-condense ion-toolbar ion-buttons {
transition: none;
}
.header-collapse-condense-inactive ion-toolbar.in-toolbar ion-title,
.header-collapse-condense-inactive ion-toolbar.in-toolbar ion-buttons.buttons-collapse {
.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-title,
.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-buttons.buttons-collapse {
opacity: 0;
pointer-events: none;
}
/**
* There is a bug in Safari where changing
* the opacity of an element in a scrollable container
* while rubber-banding causes the scroll position
* to jump to the top
*/
.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-title,
.header-collapse-condense-inactive.header-collapse-condense ion-toolbar.in-toolbar ion-buttons.buttons-collapse {
visibility: hidden;
}

View File

@@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Host, Prop, h, readTask, writeT
import { getIonMode } from '../../global/ionic-global';
import { cloneElement, createHeaderIndex, handleContentScroll, handleToolbarIntersection, setHeaderActive } from './header.utils';
import { cloneElement, createHeaderIndex, handleContentScroll, handleToolbarIntersection, setHeaderActive, setToolbarBackgroundOpacity } from './header.utils';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
*/
@@ -19,6 +19,7 @@ export class Header implements ComponentInterface {
private scrollEl?: HTMLElement;
private contentScrollCallback?: any;
private intersectionObserver?: any;
private collapsibleMainHeader?: HTMLElement;
@Element() el!: HTMLElement;
@@ -77,6 +78,11 @@ export class Header implements ComponentInterface {
this.scrollEl.removeEventListener('scroll', this.contentScrollCallback);
this.contentScrollCallback = undefined;
}
if (this.collapsibleMainHeader) {
this.collapsibleMainHeader.classList.remove('header-collapse-main');
this.collapsibleMainHeader = undefined;
}
}
private async setupCollapsibleHeader(contentEl: HTMLIonContentElement | null, pageEl: Element | null) {
@@ -84,20 +90,20 @@ export class Header implements ComponentInterface {
this.scrollEl = await contentEl.getScrollElement();
const headers = pageEl.querySelectorAll('ion-header');
this.collapsibleMainHeader = Array.from(headers).find((header: any) => header.collapse !== 'condense') as HTMLElement | undefined;
if (!this.collapsibleMainHeader) { return; }
const mainHeaderIndex = createHeaderIndex(this.collapsibleMainHeader);
const scrollHeaderIndex = createHeaderIndex(this.el);
if (!mainHeaderIndex || !scrollHeaderIndex) { return; }
setHeaderActive(mainHeaderIndex, false);
setToolbarBackgroundOpacity(mainHeaderIndex.toolbars[0], 0);
readTask(() => {
const headers = pageEl.querySelectorAll('ion-header');
const mainHeader = Array.from(headers).find((header: any) => header.collapse !== 'condense') as HTMLElement | undefined;
if (!mainHeader || !this.scrollEl) { return; }
const mainHeaderIndex = createHeaderIndex(mainHeader);
const scrollHeaderIndex = createHeaderIndex(this.el);
if (!mainHeaderIndex || !scrollHeaderIndex) { return; }
setHeaderActive(mainHeaderIndex, false);
readTask(() => {
const mainHeaderHeight = mainHeaderIndex.el.clientHeight;
/**
@@ -109,20 +115,21 @@ export class Header implements ComponentInterface {
const toolbarIntersection = (ev: any) => { handleToolbarIntersection(ev, mainHeaderIndex, scrollHeaderIndex); };
this.intersectionObserver = new IntersectionObserver(toolbarIntersection, { threshold: [0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1], rootMargin: `-${mainHeaderHeight}px 0px 0px 0px` });
this.intersectionObserver.observe(scrollHeaderIndex.toolbars[0].el);
});
/**
* Handle scaling of large iOS titles and
* showing/hiding border on last toolbar
* in primary header
*/
this.contentScrollCallback = () => { handleContentScroll(this.scrollEl!, scrollHeaderIndex); };
this.scrollEl.addEventListener('scroll', this.contentScrollCallback);
this.contentScrollCallback = () => { handleContentScroll(this.scrollEl!, scrollHeaderIndex); };
this.scrollEl!.addEventListener('scroll', this.contentScrollCallback);
});
writeTask(() => {
cloneElement('ion-title');
cloneElement('ion-back-button');
this.collapsibleMainHeader!.classList.add('header-collapse-main');
});
this.collapsibleHeaderInitialized = true;

View File

@@ -45,7 +45,7 @@ export const createHeaderIndex = (headerEl: HTMLElement | undefined): HeaderInde
innerTitleEl: (ionTitleEl) ? ionTitleEl.shadowRoot!.querySelector('.toolbar-title') : null,
ionButtonsEl: Array.from(toolbar.querySelectorAll('ion-buttons')) || []
} as ToolbarIndex;
}) || [[]]
}) || []
} as HeaderIndex;
};
@@ -60,7 +60,7 @@ export const handleContentScroll = (scrollEl: HTMLElement, scrollHeaderIndex: He
});
};
const setToolbarBackgroundOpacity = (toolbar: ToolbarIndex, opacity: number | undefined) => {
export const setToolbarBackgroundOpacity = (toolbar: ToolbarIndex, opacity?: number) => {
if (opacity === undefined) {
toolbar.background.style.removeProperty('--opacity');
} else {
@@ -117,20 +117,18 @@ export const handleToolbarIntersection = (ev: any, mainHeaderIndex: HeaderIndex,
if (hasValidIntersection) {
setHeaderActive(mainHeaderIndex);
setHeaderActive(scrollHeaderIndex, false);
setToolbarBackgroundOpacity(mainHeaderIndex.toolbars[0], 1);
setToolbarBackgroundOpacity(mainHeaderIndex.toolbars[0]);
}
}
});
};
export const setHeaderActive = (headerIndex: HeaderIndex, active = true) => {
writeTask(() => {
if (active) {
headerIndex.el.classList.remove('header-collapse-condense-inactive');
} else {
headerIndex.el.classList.add('header-collapse-condense-inactive');
}
});
if (active) {
headerIndex.el.classList.remove('header-collapse-condense-inactive');
} else {
headerIndex.el.classList.add('header-collapse-condense-inactive');
}
};
export const scaleLargeTitles = (toolbars: ToolbarIndex[] = [], scale = 1, transition = false) => {

View File

@@ -126,6 +126,12 @@ ion-backdrop {
// Menu Split Pane
// --------------------------------------------------
:host(.menu-pane-visible) {
width: var(--width);
min-width: var(--min-width);
max-width: var(--max-width);
}
:host(.menu-pane-visible) .menu-inner {
@include position-horizontal(0, 0);

View File

@@ -3,7 +3,7 @@ import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Hos
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { Animation, Gesture, GestureDetail, MenuChangeEventDetail, MenuI, Side } from '../../interface';
import { Point, getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { GESTURE_CONTROLLER } from '../../utils/gesture';
import { assert, clamp, isEndSide as isEnd } from '../../utils/helpers';
import { menuController } from '../../utils/menu-controller';
@@ -229,7 +229,7 @@ AFTER:
@Listen('click', { capture: true })
onBackdropClick(ev: any) {
if (this._isOpen && this.lastOnEnd < ev.currentTime - 100) {
if (this._isOpen && this.lastOnEnd < ev.timeStamp - 100) {
const shouldClose = (ev.composedPath)
? !ev.composedPath().includes(this.menuInnerEl)
: false;
@@ -337,7 +337,12 @@ AFTER:
const isReversed = !shouldOpen;
const ani = (this.animation as Animation)!
.direction((isReversed) ? 'reverse' : 'normal')
.easing((isReversed) ? this.easingReverse : this.easing);
.easing((isReversed) ? this.easingReverse : this.easing)
.onFinish(() => {
if (ani.getDirection() === 'reverse') {
ani.direction('normal');
}
});
if (animated) {
await ani.play();
@@ -384,9 +389,7 @@ AFTER:
}
// the cloned animation should not use an easing curve during seek
(this.animation as Animation)
.direction((this._isOpen) ? 'reverse' : 'normal')
.progressStart(true);
(this.animation as Animation).progressStart(true, (this._isOpen) ? 1 : 0);
}
private onMove(detail: GestureDetail) {
@@ -398,7 +401,7 @@ AFTER:
const delta = computeDelta(detail.deltaX, this._isOpen, this.isEndSide);
const stepValue = delta / this.width;
this.animation.progressStep(stepValue);
this.animation.progressStep((this._isOpen) ? 1 - stepValue : stepValue);
}
private onEnd(detail: GestureDetail) {
@@ -449,14 +452,16 @@ AFTER:
* to the new easing curve, as `stepValue` is going to be given
* in terms of a linear curve.
*/
newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(0.4, 0), new Point(0.6, 1), new Point(1, 1), clamp(0, adjustedStepValue, 1));
newStepValue += getTimeGivenProgression([0, 0], [0.4, 0], [0.6, 1], [1, 1], clamp(0, adjustedStepValue, 1))[0];
const playTo = (this._isOpen) ? !shouldComplete : shouldComplete;
this.animation
.easing('cubic-bezier(0.4, 0.0, 0.6, 1)')
.onFinish(
() => this.afterAnimation(shouldOpen),
{ oneTimeCallback: true })
.progressEnd(shouldComplete ? 1 : 0, newStepValue, 300);
.progressEnd((playTo) ? 1 : 0, (this._isOpen) ? 1 - newStepValue : newStepValue, 300);
}
private beforeAnimation(shouldOpen: boolean) {

View File

@@ -3,7 +3,7 @@ import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch, h
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { Animation, AnimationBuilder, ComponentProps, FrameworkDelegate, Gesture, NavComponent, NavOptions, NavOutlet, NavResult, RouteID, RouteWrite, RouterDirection, TransitionDoneFn, TransitionInstruction, ViewController } from '../../interface';
import { Point, getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { assert } from '../../utils/helpers';
import { TransitionOptions, lifecycle, setPageHidden, transition } from '../../utils/transition';
@@ -981,9 +981,9 @@ export class Nav implements NavOutlet {
*/
if (!shouldComplete) {
this.sbAni.easing('cubic-bezier(1, 0, 0.68, 0.28)');
newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(1, 0), new Point(0.68, 0.28), new Point(1, 1), stepValue);
newStepValue += getTimeGivenProgression([0, 0], [1, 0], [0.68, 0.28], [1, 1], stepValue)[0];
} else {
newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(0.32, 0.72), new Point(0, 1), new Point(1, 1), stepValue);
newStepValue += getTimeGivenProgression([0, 0], [0.32, 0.72], [0, 1], [1, 1], stepValue)[0];
}
(this.sbAni as Animation).progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);

View File

@@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Meth
import { getIonMode } from '../../global/ionic-global';
import { Animation, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface, PickerButton, PickerColumn } from '../../interface';
import { dismiss, eventMethod, prepareOverlay, present, safeCall } from '../../utils/overlays';
import { BACKDROP, dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall } from '../../utils/overlays';
import { getClassMap } from '../../utils/theme';
import { iosEnterAnimation } from './animations/ios.enter';
@@ -163,19 +163,29 @@ export class Picker implements ComponentInterface, OverlayInterface {
return Promise.resolve(this.columns.find(column => column.name === name));
}
private buttonClick(button: PickerButton) {
// if (this.disabled) {
// return;
// }
// keep the time of the most recent button click
// a handler has been provided, execute it
// pass the handler the values from the inputs
const shouldDismiss = safeCall(button.handler, this.getSelected()) !== false;
if (shouldDismiss) {
return this.dismiss();
private async buttonClick(button: PickerButton) {
const role = button.role;
if (isCancel(role)) {
return this.dismiss(undefined, role);
}
return Promise.resolve(false);
const shouldDismiss = await this.callButtonHandler(button);
if (shouldDismiss) {
return this.dismiss(this.getSelected(), button.role);
}
return Promise.resolve();
}
private async callButtonHandler(button: PickerButton | undefined) {
if (button) {
// a handler has been provided, execute it
// pass the handler the values from the inputs
const rtn = await safeCall(button.handler);
if (rtn === false) {
// if the return value of the handler is false then do not dismiss
return false;
}
}
return true;
}
private getSelected() {
@@ -194,11 +204,14 @@ export class Picker implements ComponentInterface, OverlayInterface {
}
private onBackdropTap = () => {
const cancelBtn = this.buttons.find(b => b.role === 'cancel');
if (cancelBtn) {
this.buttonClick(cancelBtn);
} else {
this.dismiss();
this.dismiss(undefined, BACKDROP);
}
private dispatchCancelHandler = (ev: CustomEvent) => {
const role = ev.detail.role;
if (isCancel(role)) {
const cancelButton = this.buttons.find(b => b.role === 'cancel');
this.callButtonHandler(cancelButton);
}
}
@@ -219,6 +232,7 @@ export class Picker implements ComponentInterface, OverlayInterface {
zIndex: `${20000 + this.overlayIndex}`
}}
onIonBackdropTap={this.onBackdropTap}
onIonPickerWillDismiss={this.dispatchCancelHandler}
>
<ion-backdrop
visible={this.showBackdrop}

View File

@@ -48,6 +48,10 @@
const pickerController = document.querySelector('ion-picker-controller');
const pickerElement = await pickerController.create({
buttons: [{
text: 'Cancel',
role: 'cancel',
handler: () => console.log('Clicked Cancel!')
}, {
text: 'Save',
handler: () => console.log('Clicked Save!')
}, {
@@ -112,7 +116,12 @@
}],
cssClass: customClass
});
return await pickerElement.present();
await pickerElement.present();
const { data, role } = await pickerElement.onDidDismiss();
console.log('Picker dismissed!', data, role);
}
</script>
</body>

View File

@@ -3,7 +3,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Method, Pr
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import { Animation, AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, Gesture, NavOutlet, RouteID, RouteWrite, RouterDirection, RouterOutletOptions, SwipeGestureHandler } from '../../interface';
import { Point, getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { attachComponent, detachComponent } from '../../utils/framework-delegate';
import { transition } from '../../utils/transition';
@@ -91,9 +91,9 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
*/
if (!shouldComplete) {
this.ani.easing('cubic-bezier(1, 0, 0.68, 0.28)');
newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(1, 0), new Point(0.68, 0.28), new Point(1, 1), step);
newStepValue += getTimeGivenProgression([0, 0], [1, 0], [0.68, 0.28], [1, 1], step)[0];
} else {
newStepValue += getTimeGivenProgression(new Point(0, 0), new Point(0.32, 0.72), new Point(0, 1), new Point(1, 1), step);
newStepValue += getTimeGivenProgression([0, 0], [0.32, 0.72], [0, 1], [1, 1], step)[0];
}
(this.ani as Animation).progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);

View File

@@ -149,9 +149,12 @@ export const SplitPlaneExample: React.SFC<{}> = () => (
## CSS Custom Properties
| Name | Description |
| ---------- | -------------------- |
| `--border` | Border between panes |
| Name | Description |
| ------------------ | ---------------------------------------------------------------------------- |
| `--border` | Border between panes |
| `--side-max-width` | Maximum width of the side pane. Does not apply when split pane is collapsed. |
| `--side-min-width` | Minimum width of the side pane. Does not apply when split pane is collapsed. |
| `--side-width` | Width of the side pane. Does not apply when split pane is collapsed. |
----------------------------------------------

View File

@@ -4,21 +4,23 @@
// Split Pane
// --------------------------------------------------
.split-pane-ios {
:host {
--border: #{$split-pane-ios-border};
--side-min-width: #{$split-pane-ios-side-min-width};
--side-max-width: #{$split-pane-ios-side-max-width};
}
.split-pane-ios.split-pane-visible > .split-pane-side {
min-width: $split-pane-ios-side-min-width;
max-width: $split-pane-ios-side-max-width;
:host(.split-pane-visible) ::slotted(.split-pane-side) {
min-width: var(--side-min-width);
max-width: var(--side-max-width);
border-right: var(--border);
border-left: 0;
}
.split-pane-ios.split-pane-visible > .split-pane-side[side=end] {
min-width: $split-pane-ios-side-min-width;
max-width: $split-pane-ios-side-max-width;
:host(.split-pane-visible) ::slotted(.split-pane-side[side=end]) {
min-width: var(--side-min-width);
max-width: var(--side-max-width);
border-right: 0;
border-left: var(--border);

View File

@@ -4,21 +4,23 @@
// Split Pane
// --------------------------------------------------
.split-pane-md {
:host {
--border: #{$split-pane-md-border};
--side-min-width: #{$split-pane-md-side-min-width};
--side-max-width: #{$split-pane-md-side-max-width};
}
.split-pane-md.split-pane-visible > .split-pane-side {
min-width: $split-pane-md-side-min-width;
max-width: $split-pane-md-side-max-width;
:host(.split-pane-visible) ::slotted(.split-pane-side) {
min-width: var(--side-min-width);
max-width: var(--side-max-width);
border-right: var(--border);
border-left: 0;
}
.split-pane-md.split-pane-visible > .split-pane-side[side=end] {
min-width: $split-pane-md-side-min-width;
max-width: $split-pane-md-side-max-width;
:host(.split-pane-visible) ::slotted(.split-pane-side[side=end]) {
min-width: var(--side-min-width);
max-width: var(--side-max-width);
border-right: 0;
border-left: var(--border);

View File

@@ -1,12 +1,17 @@
@import "./split-pane.vars";
@import "../menu/menu.vars";
// Split Pane
// --------------------------------------------------
ion-split-pane {
:host {
/**
* @prop --border: Border between panes
* @prop --side-min-width: Minimum width of the side pane. Does not apply when split pane is collapsed.
* @prop --side-max-width: Maximum width of the side pane. Does not apply when split pane is collapsed.
* @prop --side-width: Width of the side pane. Does not apply when split pane is collapsed.
*/
--side-width: 100%;
@include position(0, 0, 0, 0);
@@ -19,34 +24,50 @@ ion-split-pane {
contain: strict;
}
.split-pane-visible > .split-pane-side,
.split-pane-visible > .split-pane-main {
/**
* Do not pass CSS Variables down on larger
* screens as we want them to affect the outer
* `ion-menu` rather than the inner content
*/
::slotted(ion-menu.menu-pane-visible) {
flex: 0 1 auto;
width: var(--side-width);
min-width: var(--side-min-width);
max-width: var(--side-max-width);
}
:host(.split-pane-visible) ::slotted(.split-pane-side),
:host(.split-pane-visible) ::slotted(.split-pane-main) {
@include position(0, 0, 0, 0);
position: relative;
flex: 1;
/* stylelint-disable-next-line declaration-no-important */
box-shadow: none !important;
z-index: 0;
}
.split-pane-visible > .split-pane-side:not(ion-menu),
.split-pane-visible > ion-menu.split-pane-side.menu-enabled {
:host(.split-pane-visible) ::slotted(.split-pane-main) {
flex: 1;
}
:host(.split-pane-visible) ::slotted(.split-pane-side:not(ion-menu)),
:host(.split-pane-visible) ::slotted(ion-menu.split-pane-side.menu-enabled) {
display: flex;
flex-shrink: 0;
}
.split-pane-side:not(ion-menu) {
::slotted(.split-pane-side:not(ion-menu)) {
display: none;
}
.split-pane-visible > .split-pane-side {
:host(.split-pane-visible) ::slotted(.split-pane-side) {
order: -1;
}
.split-pane-visible > .split-pane-side[side=end] {
:host(.split-pane-visible) ::slotted(.split-pane-side[side=end]) {
order: 1;
}

View File

@@ -18,7 +18,8 @@ const QUERY: { [key: string]: string } = {
styleUrls: {
ios: 'split-pane.ios.scss',
md: 'split-pane.md.scss'
}
},
shadow: true
})
export class SplitPane implements ComponentInterface {
@@ -159,6 +160,7 @@ export class SplitPane implements ComponentInterface {
'split-pane-visible': this.visible
}}
>
<slot></slot>
</Host>
);
}

View File

@@ -5,6 +5,28 @@ test('split-pane: basic', async () => {
url: '/src/components/split-pane/test/basic?ionic:_testing=true'
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
const screenshotCompares = [];
const MIN_WIDTH = '#side-min-width';
const MAX_WIDTH = '#side-max-width';
const WIDTH = '#side-width';
screenshotCompares.push(await page.compareScreenshot());
await page.click(MIN_WIDTH);
screenshotCompares.push(await page.compareScreenshot());
await page.click(MIN_WIDTH);
await page.click(MAX_WIDTH);
screenshotCompares.push(await page.compareScreenshot());
await page.click(MAX_WIDTH);
await page.click(WIDTH);
screenshotCompares.push(await page.compareScreenshot());
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});

View File

@@ -69,8 +69,22 @@
<f></f>
<f></f>
<f></f>
<ion-item>
<ion-label>Toggle Side Min Width</ion-label>
<ion-toggle id="side-min-width"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Toggle Side Max Width</ion-label>
<ion-toggle id="side-max-width"></ion-toggle>
</ion-item>
<ion-item>
<ion-label>Toggle Side Fixed Width</ion-label>
<ion-toggle id="side-width"></ion-toggle>
</ion-item>
</ion-content>
</div>
</ion-split-pane>
@@ -78,6 +92,16 @@
<ion-menu-controller></ion-menu-controller>
<script>
const sideMinWidth = document.querySelector('ion-toggle#side-min-width');
const sideMaxWidth = document.querySelector('ion-toggle#side-max-width');
const sideWidth = document.querySelector('ion-toggle#side-width');
const splitPane = document.querySelector('ion-split-pane');
sideMinWidth.addEventListener('ionChange', () => splitPane.classList.toggle('side-min-width'));
sideMaxWidth.addEventListener('ionChange', () => splitPane.classList.toggle('side-max-width'));
sideWidth.addEventListener('ionChange', () => splitPane.classList.toggle('side-width'));
const menuCtrl = document.querySelector('ion-menu-controller');
function openStart() {
menuCtrl.open('start');
@@ -89,6 +113,20 @@
</script>
<style>
.side-width {
--side-width: 150px;
--side-min-width: 150px;
}
.side-min-width {
--side-min-width: 500px;
}
.side-max-width {
--side-max-width: 75px;
--side-min-width: 0px;
}
f {
display: block;
height: 150px;

View File

@@ -1,4 +1,4 @@
import { Build, Component, ComponentInterface, Element, Host, Method, Prop, h } from '@stencil/core';
import { Build, Component, ComponentInterface, Element, Host, Method, Prop, Watch, h } from '@stencil/core';
import { ComponentRef, FrameworkDelegate } from '../../interface';
import { attachComponent } from '../../utils/framework-delegate';
@@ -30,7 +30,7 @@ export class Tab implements ComponentInterface {
*/
@Prop() component?: ComponentRef;
componentWillLoad() {
async componentWillLoad() {
if (Build.isDev) {
if (this.component !== undefined && this.el.childElementCount > 0) {
console.error('You can not use a lazy-loaded component in a tab and inlined content at the same time.' +
@@ -39,6 +39,9 @@ export class Tab implements ComponentInterface {
`- Remove the embedded content inside the ion-tab: <ion-tab></ion-tab>`);
}
}
if (this.active) {
await this.setActive();
}
}
/** Set the active component for the tab */
@@ -48,6 +51,13 @@ export class Tab implements ComponentInterface {
this.active = true;
}
@Watch('active')
changeActive(isActive: boolean) {
if (isActive) {
this.prepareLazyLoaded();
}
}
private prepareLazyLoaded(): Promise<HTMLElement | undefined> {
if (!this.loaded && this.component != null) {
this.loaded = true;

View File

@@ -46,7 +46,9 @@ export class Tabs implements NavOutlet {
}
if (!this.useRouter) {
const tabs = this.tabs;
await this.select(tabs[0]);
if (tabs.length > 0) {
await this.select(tabs[0]);
}
}
this.ionNavWillLoad.emit();
}
@@ -127,7 +129,8 @@ export class Tabs implements NavOutlet {
this.leavingTab = this.selectedTab;
this.selectedTab = selectedTab;
this.ionTabsWillChange.emit({ tab: selectedTab.tab });
return selectedTab.setActive();
selectedTab.active = true;
return Promise.resolve();
}
private tabSwitch() {

View File

@@ -12,7 +12,7 @@
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body onload="selectTab()">
<body>
<ion-tabs>
<ion-tab tab="tab-one">
<div class="content-div div-one">
@@ -71,11 +71,11 @@
</style>
<script>
async function selectTab() {
customElements.whenDefined('ion-tabs').then(async () => {
const tabs = document.querySelector('ion-tabs');
await tabs.componentOnReady();
await tabs.select('tab-three');
}
});
</script>
</body>

View File

@@ -96,6 +96,8 @@
appearance: none;
&::placeholder {
@include padding(0);
color: var(--placeholder-color);
font-family: inherit;

View File

@@ -191,7 +191,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
}
const shouldDismiss = await this.callButtonHandler(button);
if (shouldDismiss) {
return this.dismiss(undefined, button.role);
return this.dismiss(undefined, role);
}
return Promise.resolve();
}
@@ -213,6 +213,14 @@ export class Toast implements ComponentInterface, OverlayInterface {
return true;
}
private dispatchCancelHandler = (ev: CustomEvent) => {
const role = ev.detail.role;
if (isCancel(role)) {
const cancelButton = this.getButtons().find(b => b.role === 'cancel');
this.callButtonHandler(cancelButton);
}
}
renderButtons(buttons: ToastButton[], side: 'start' | 'end') {
if (buttons.length === 0) {
return;
@@ -265,6 +273,7 @@ export class Toast implements ComponentInterface, OverlayInterface {
...getClassMap(this.cssClass),
'toast-translucent': this.translucent
}}
onIonToastWillDismiss={this.dispatchCancelHandler}
>
<div class={wrapperClass}>
<div class="toast-container">

View File

@@ -30,6 +30,10 @@
.create ion-icon {
transform: scale(0.9);
}
.main-toolbar {
--background: rgba(0, 0, 0, 0.05);
}
</style>
</head>
@@ -77,8 +81,8 @@
connectedCallback() {
this.innerHTML = `
<ion-header class="main-header">
<ion-toolbar>
<ion-header class="main-header" translucent>
<ion-toolbar class="main-toolbar">
<ion-buttons collapse>
<ion-back-button default-href="/"></ion-back-button>
</ion-buttons>
@@ -89,7 +93,7 @@
</ion-toolbar>
</ion-header>
<ion-content id="content" class="main-content">
<ion-content id="content" class="main-content" fullscreen>
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Page Two</ion-title>

View File

@@ -76,6 +76,10 @@ body.backdrop-no-scroll {
z-index: $z-index-page-container;
}
.split-pane-visible > .ion-page.split-pane-main {
position: relative;
}
ion-route,
ion-route-redirect,
ion-router,

View File

@@ -1,6 +1,7 @@
import 'ionicons';
export { createAnimation } from './utils/animation/animation';
export { getTimeGivenProgression } from './utils/animation/cubic-bezier';
export { createGesture } from './utils/gesture';
export { isPlatform, Platforms, getPlatforms } from './utils/platform';

View File

@@ -2,6 +2,7 @@ export interface Animation {
parentAnimation: Animation | undefined;
elements: HTMLElement[];
childAnimations: Animation[];
id: string | undefined;
/**
* Play the animation
@@ -28,7 +29,7 @@ export interface Animation {
*/
destroy(): void;
progressStart(forceLinearEasing: boolean): void;
progressStart(forceLinearEasing: boolean, step?: number): void;
progressStep(step: number): void;
progressEnd(playTo: 0 | 1 | undefined, step: number, dur?: number): void;

View File

@@ -19,12 +19,12 @@ interface AnimationInternal extends Animation {
/**
* Updates any existing animations.
*/
update(deep: boolean): Animation;
update(deep: boolean, toggleAnimationName: boolean, step?: number): Animation;
animationFinish(): void;
}
export const createAnimation = (): Animation => {
export const createAnimation = (animationId?: string): Animation => {
let _delay: number | undefined;
let _duration: number | undefined;
let _easing: string | undefined;
@@ -32,6 +32,8 @@ export const createAnimation = (): Animation => {
let _fill: AnimationFill | undefined;
let _direction: AnimationDirection | undefined;
let _keyframes: AnimationKeyFrames = [];
const id: string | undefined = animationId;
let beforeAddClasses: string[] = [];
let beforeRemoveClasses: string[] = [];
let initialized = false;
@@ -533,7 +535,7 @@ export const createAnimation = (): Animation => {
elements.forEach(element => {
if (_keyframes.length > 0) {
const keyframeRules = generateKeyframeRules(_keyframes);
keyframeName = generateKeyframeName(keyframeRules);
keyframeName = (animationId !== undefined) ? animationId : generateKeyframeName(keyframeRules);
const stylesheet = createKeyframeStylesheet(keyframeName, keyframeRules, element);
stylesheets.push(stylesheet);
@@ -564,6 +566,7 @@ export const createAnimation = (): Animation => {
const initializeWebAnimation = () => {
elements.forEach(element => {
const animation = element.animate(_keyframes, {
id,
delay: getDelay(),
duration: getDuration(),
easing: getEasing(),
@@ -608,8 +611,7 @@ export const createAnimation = (): Animation => {
});
} else {
const animationDelay = getDelay() || 0;
const animationDuration = `-${animationDelay + (getDuration()! * step)}ms`;
const animationDuration = `-${getDuration()! * step}ms`;
elements.forEach(element => {
if (_keyframes.length > 0) {
@@ -620,7 +622,7 @@ export const createAnimation = (): Animation => {
}
};
const updateWebAnimation = () => {
const updateWebAnimation = (step?: number) => {
webAnimations.forEach(animation => {
animation.effect.updateTiming({
delay: getDelay(),
@@ -631,15 +633,19 @@ export const createAnimation = (): Animation => {
direction: getDirection()
});
});
if (step !== undefined) {
setAnimationStep(step);
}
};
const updateCSSAnimation = (toggleAnimationName = true) => {
const updateCSSAnimation = (toggleAnimationName = true, step?: number) => {
raf(() => {
elements.forEach(element => {
setStyleProperty(element, 'animation-name', keyframeName || null);
setStyleProperty(element, 'animation-duration', `${getDuration()}ms`);
setStyleProperty(element, 'animation-timing-function', getEasing());
setStyleProperty(element, 'animation-delay', `${getDelay()}ms`);
setStyleProperty(element, 'animation-delay', (step !== undefined) ? `-${step! * getDuration()}ms` : `${getDelay()}ms`);
setStyleProperty(element, 'animation-fill-mode', getFill() || null);
setStyleProperty(element, 'animation-direction', getDirection() || null);
@@ -660,25 +666,25 @@ export const createAnimation = (): Animation => {
});
};
const update = (deep = false, toggleAnimationName = true) => {
const update = (deep = false, toggleAnimationName = true, step?: number) => {
if (deep) {
childAnimations.forEach(animation => {
animation.update(deep);
animation.update(deep, toggleAnimationName, step);
});
}
if (supportsWebAnimations) {
updateWebAnimation();
updateWebAnimation(step);
} else {
updateCSSAnimation(toggleAnimationName);
updateCSSAnimation(toggleAnimationName, step);
}
return ani;
};
const progressStart = (forceLinearEasing = false) => {
const progressStart = (forceLinearEasing = false, step?: number) => {
childAnimations.forEach(animation => {
animation.progressStart(forceLinearEasing);
animation.progressStart(forceLinearEasing, step);
});
pauseAnimation();
@@ -687,8 +693,7 @@ export const createAnimation = (): Animation => {
if (!initialized) {
initializeAnimation();
} else {
update();
setAnimationStep(0);
update(false, true, step);
}
return ani;
@@ -957,6 +962,7 @@ export const createAnimation = (): Animation => {
parentAnimation,
elements,
childAnimations,
id,
animationFinish,
from,
to,

View File

@@ -5,11 +5,8 @@
* TODO: Reduce rounding error
*/
export class Point {
constructor(public x: number, public y: number) {}
}
/**
* EXPERIMENTAL
* Given a cubic-bezier curve, get the x value (time) given
* the y value (progression).
* Ex: cubic-bezier(0.32, 0.72, 0, 1);
@@ -19,11 +16,12 @@ export class Point {
* P3: (1, 1)
*
* If you give a cubic bezier curve that never reaches the
* provided progression, this function will return NaN.
* provided progression, this function will return an empty array.
*/
export const getTimeGivenProgression = (p0: Point, p1: Point, p2: Point, p3: Point, progression: number) => {
const tValues = solveCubicBezier(p0.y, p1.y, p2.y, p3.y, progression);
return solveCubicParametricEquation(p0.x, p1.x, p2.x, p3.x, tValues[0]); // TODO: Add better strategy for dealing with multiple solutions
export const getTimeGivenProgression = (p0: number[], p1: number[], p2: number[], p3: number[], progression: number): number[] => {
return solveCubicBezier(p0[1], p1[1], p2[1], p3[1], progression).map(tValue => {
return solveCubicParametricEquation(p0[0], p1[0], p2[0], p3[0], tValue);
});
};
/**

View File

@@ -1,5 +1,5 @@
import { createAnimation } from '../animation';
import { getTimeGivenProgression, Point } from '../cubic-bezier';
import { getTimeGivenProgression } from '../cubic-bezier';
import { Animation } from '../animation-interface';
describe('Animation Class', () => {
@@ -313,70 +313,83 @@ describe('cubic-bezier conversion', () => {
describe('should properly get a time value (x value) given a progression value (y value)', () => {
it('cubic-bezier(0.32, 0.72, 0, 1)', () => {
const equation = [
new Point(0, 0),
new Point(0.32, 0.72),
new Point(0, 1),
new Point(1, 1)
[0, 0],
[0.32, 0.72],
[0, 1],
[1, 1]
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.5), 0.16);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.97), 0.56);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.33), 0.11);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.5), [0.16]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.97), [0.56]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.33), [0.11]);
});
it('cubic-bezier(1, 0, 0.68, 0.28)', () => {
const equation = [
new Point(0, 0),
new Point(1, 0),
new Point(0.68, 0.28),
new Point(1, 1)
[0, 0],
[1, 0],
[0.68, 0.28],
[1, 1]
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.08), 0.60);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.50), 0.84);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.94), 0.98);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.08), [0.60]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.50), [0.84]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.94), [0.98]);
})
it('cubic-bezier(0.4, 0, 0.6, 1)', () => {
const equation = [
new Point(0, 0),
new Point(0.4, 0),
new Point(0.6, 1),
new Point(1, 1)
[0, 0],
[0.4, 0],
[0.6, 1],
[1, 1]
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.39), 0.43);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.03), 0.11);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.89), 0.78);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.39), [0.43]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.03), [0.11]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.89), [0.78]);
})
it('cubic-bezier(0, 0, 0.2, 1)', () => {
const equation = [
new Point(0, 0),
new Point(0, 0),
new Point(0.2, 1),
new Point(1, 1)
[0, 0],
[0, 0],
[0.2, 1],
[1, 1]
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.95), 0.71);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.1), 0.03);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.70), 0.35);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.95), [0.71]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.1), [0.03]);
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 0.70), [0.35]);
})
it('cubic-bezier(0.32, 0.72, 0, 1) (with out of bounds progression)', () => {
const equation = [
new Point(0, 0),
new Point(0.05, 0.2),
new Point(.14, 1.72),
new Point(1, 1)
[0, 0],
[0.05, 0.2],
[.14, 1.72],
[1, 1]
];
expect(getTimeGivenProgression(...equation, 1.32)).toBeNaN();
expect(getTimeGivenProgression(...equation, -0.32)).toBeNaN();
expect(getTimeGivenProgression(...equation, 1.32)[0]).toBeUndefined();
expect(getTimeGivenProgression(...equation, -0.32)[0]).toBeUndefined();
})
it('cubic-bezier(0.21, 1.71, 0.88, 0.9) (multiple solutions)', () => {
const equation = [
[0, 0],
[0.21, 1.71],
[0.88, 0.9],
[1, 1]
];
shouldApproximatelyEqual(getTimeGivenProgression(...equation, 1.02), [0.35, 0.87]);
})
})
});
const shouldApproximatelyEqual = (givenValue: number, expectedValue: number): boolean => {
expect(Math.abs(expectedValue - givenValue)).toBeLessThanOrEqual(0.01);
const shouldApproximatelyEqual = (givenValues: number[], expectedValues: number[]): void => {
givenValues.forEach((givenValue, i) => {
expect(Math.abs(expectedValues[i] - givenValue)).toBeLessThanOrEqual(0.01);
});
}

View File

@@ -21,7 +21,7 @@
const squareA = document.querySelector('.square-a');
const rootAnimation = createAnimation();
const rootAnimation = createAnimation('root-animation-id');
rootAnimation
.addElement(squareA)

View File

@@ -92,8 +92,8 @@ export const createPointerEvents = (
stopMouse();
};
const enable = (enable = true) => {
if (!enable) {
const enable = (isEnabled = true) => {
if (!isEnabled) {
if (rmTouchStart) {
rmTouchStart();
}

View File

@@ -27,8 +27,9 @@ const getBackButton = (refEl: any, backDirection: boolean) => {
const activeHeader = parentHeader && !parentHeader.classList.contains('header-collapse-condense-inactive');
const backButton = buttons.querySelector('ion-back-button');
const buttonsCollapse = buttons.classList.contains('buttons-collapse');
const startSlot = buttons.slot === 'start' || buttons.slot === '';
if (backButton !== null && ((buttonsCollapse && activeHeader && backDirection) || !buttonsCollapse)) {
if (backButton !== null && startSlot && ((buttonsCollapse && activeHeader && backDirection) || !buttonsCollapse)) {
return backButton;
}
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { HashRouter, HashRouterProps } from 'react-router-dom';
import { RouteManagerWithRouter } from './Router';
export class IonReactHashRouter extends React.Component<HashRouterProps> {
render() {
const { children, ...props } = this.props;
return (
<HashRouter {...props}>
<RouteManagerWithRouter>{children}</RouteManagerWithRouter>
</HashRouter>
);
}
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { BrowserRouter, BrowserRouterProps } from 'react-router-dom';
import { RouteManagerWithRouter } from './Router';
export class IonReactRouter extends React.Component<BrowserRouterProps> {
render() {
const { children, ...props } = this.props;
return (
<BrowserRouter {...props}>
<RouteManagerWithRouter>{children}</RouteManagerWithRouter>
</BrowserRouter>
);
}
}

View File

@@ -15,6 +15,7 @@ interface NavManagerProps extends RouteComponentProps {
findViewInfoByLocation: (location: HistoryLocation) => { view?: ViewItem, viewStack?: ViewStack };
findViewInfoById: (id: string) => { view?: ViewItem, viewStack?: ViewStack };
getActiveIonPage: () => { view?: ViewItem, viewStack?: ViewStack };
onNavigate: (type: 'push' | 'replace', path: string, state?: any) => void;
}
export class NavManager extends React.Component<NavManagerProps, NavContextState> {
@@ -27,8 +28,6 @@ export class NavManager extends React.Component<NavManagerProps, NavContextState
this.state = {
goBack: this.goBack.bind(this),
hasIonicRouter: () => true,
getHistory: this.getHistory.bind(this),
getLocation: this.getLocation.bind(this),
navigate: this.navigate.bind(this),
getStackManager: this.getStackManager.bind(this),
getPageManager: this.getPageManager.bind(this),
@@ -66,36 +65,28 @@ export class NavManager extends React.Component<NavManagerProps, NavContextState
if (enteringView) {
const lastLocation = this.locationHistory.findLastLocation(enteringView.routeData.match.url);
if (lastLocation) {
this.props.history.replace(lastLocation.pathname + lastLocation.search, { direction: 'back' });
this.props.onNavigate('replace', lastLocation.pathname + lastLocation.search, 'back');
} else {
this.props.history.replace(enteringView.routeData.match.url, { direction: 'back' });
this.props.onNavigate('replace', enteringView.routeData.match.url, 'back');
}
} else {
if (defaultHref) {
this.props.history.replace(defaultHref, { direction: 'back' });
this.props.onNavigate('replace', defaultHref, 'back');
}
}
} else {
if (defaultHref) {
this.props.history.replace(defaultHref, { direction: 'back' });
this.props.onNavigate('replace', defaultHref, 'back');
}
}
}
getHistory() {
return this.props.history as any;
}
getLocation() {
return this.props.location as any;
}
navigate(path: string, direction?: RouterDirection | 'none') {
this.props.history.push(path, { direction });
this.props.onNavigate('push', path, direction);
}
tabNavigate(url: string) {
this.props.history.replace(url, { direction: 'back' });
tabNavigate(path: string) {
this.props.onNavigate('replace', path, 'back');
}
getPageManager() {

View File

@@ -1,4 +1,3 @@
import { NavDirection } from '@ionic/core';
import React, { ReactNode } from 'react';
import { ViewStacks } from './ViewStacks';
@@ -9,7 +8,6 @@ export interface RouteManagerContextState {
viewStacks: ViewStacks;
setupIonRouter: (id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) => Promise<void>;
removeViewStack: (stack: string) => void;
transitionView: (enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOuter: HTMLIonRouterOutletElement, direction: NavDirection) => void;
}
export const RouteManagerContext = /*@__PURE__*/React.createContext<RouteManagerContextState>({
@@ -17,8 +15,7 @@ export const RouteManagerContext = /*@__PURE__*/React.createContext<RouteManager
syncView: () => { navContextNotFoundError(); },
hideView: () => { navContextNotFoundError(); },
setupIonRouter: () => Promise.reject(navContextNotFoundError()),
removeViewStack: () => { navContextNotFoundError(); },
transitionView: () => { navContextNotFoundError(); }
removeViewStack: () => { navContextNotFoundError(); }
});
function navContextNotFoundError() {

View File

@@ -2,7 +2,7 @@ import { NavDirection } from '@ionic/core';
import { RouterDirection } from '@ionic/react';
import { Action as HistoryAction, Location as HistoryLocation, UnregisterCallback } from 'history';
import React from 'react';
import { BrowserRouter, BrowserRouterProps, RouteComponentProps, matchPath, withRouter } from 'react-router-dom';
import { RouteComponentProps, matchPath, withRouter } from 'react-router-dom';
import { generateId } from '../utils';
@@ -20,17 +20,18 @@ interface RouteManagerState extends RouteManagerContextState {
class RouteManager extends React.Component<RouteComponentProps, RouteManagerState> {
listenUnregisterCallback: UnregisterCallback | undefined;
activeIonPageId?: string;
currentDirection?: RouterDirection;
constructor(props: RouteComponentProps) {
super(props);
this.listenUnregisterCallback = this.props.history.listen(this.historyChange.bind(this));
this.handleNavigate = this.handleNavigate.bind(this);
this.state = {
viewStacks: new ViewStacks(),
hideView: this.hideView.bind(this),
setupIonRouter: this.setupIonRouter.bind(this),
removeViewStack: this.removeViewStack.bind(this),
syncView: this.syncView.bind(this),
transitionView: this.transitionView.bind(this),
syncView: this.syncView.bind(this)
};
}
@@ -41,6 +42,12 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
}
}
componentWillUnmount() {
if (this.listenUnregisterCallback) {
this.listenUnregisterCallback();
}
}
hideView(viewId: string) {
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
const { view } = viewStacks.findViewInfoById(viewId);
@@ -57,6 +64,8 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
}
historyChange(location: HistoryLocation, action: HistoryAction) {
location.state = location.state || { direction: this.currentDirection };
this.currentDirection = undefined;
this.setState({
location,
action
@@ -65,7 +74,7 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
setActiveView(location: HistoryLocation, action: HistoryAction) {
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
let direction: RouterDirection = location.state && location.state.direction || 'forward';
let direction: RouterDirection | undefined = (location.state && location.state.direction) || 'forward';
let leavingView: ViewItem | undefined;
const viewStackKeys = viewStacks.getKeys();
@@ -91,19 +100,26 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
* record the view that originally directed to the new view for back button purposes.
*/
enteringView.prevId = enteringView.prevId || leavingView.id;
} else if (action === 'POP') {
direction = leavingView.prevId === enteringView.id ? 'back' : 'none';
} else {
direction = direction || 'back';
leavingView.mount = false;
}
} else if (action === 'REPLACE') {
} else if (direction === 'back' || action === 'REPLACE') {
leavingView.mount = false;
}
} else {
// If there is not a leavingView, then we shouldn't provide a direction
direction = undefined;
}
this.removeOrphanedViews(enteringView, enteringViewStack);
} else {
enteringView.show = true;
enteringView.mount = true;
enteringView.routeData.match = match!;
}
});
if (leavingView) {
@@ -120,7 +136,6 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
if (enteringView && viewStack) {
const enteringEl = enteringView.ionPageElement ? enteringView.ionPageElement : undefined;
const leavingEl = leavingView && leavingView.ionPageElement ? leavingView.ionPageElement : undefined;
if (enteringEl) {
// Don't animate from an empty view
const navDirection = leavingEl && leavingEl.innerHTML === '' ? undefined : direction === 'none' ? undefined : direction;
@@ -128,7 +143,8 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
enteringEl!,
leavingEl!,
viewStack.routerOutlet,
navDirection);
navDirection,
!!enteringView.prevId);
} else if (leavingEl) {
leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true');
@@ -137,10 +153,24 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
});
}
componentWillUnmount() {
if (this.listenUnregisterCallback) {
this.listenUnregisterCallback();
}
removeOrphanedViews(view: ViewItem, viewStack: ViewStack) {
const viewsToRemove = viewStack.views.filter(v => v.prevId === view.id);
viewsToRemove.forEach(v => {
// Don't remove if view is currently active
if (v.id !== this.activeIonPageId) {
this.removeOrphanedViews(v, viewStack);
// If view is not currently visible, go ahead and remove it from DOM
if (v.ionPageElement!.classList.contains('ion-page-hidden')) {
v.show = false;
v.ionPageElement = undefined;
v.isIonRoute = false;
v.prevId = undefined;
v.key = generateId();
}
v.mount = false;
}
});
}
async setupIonRouter(id: string, children: any, routerOutlet: HTMLIonRouterOutletElement) {
@@ -231,21 +261,21 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
});
}
transitionView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet?: HTMLIonRouterOutletElement, direction?: NavDirection) {
transitionView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet: HTMLIonRouterOutletElement | undefined, direction: NavDirection | undefined, showGoBack: boolean) {
/**
* Super hacky workaround to make sure ionRouterOutlet is available
* since transitionView might be called before IonRouterOutlet is fully mounted
*/
if (ionRouterOutlet && ionRouterOutlet.componentOnReady) {
this.commitView(enteringEl, leavingEl, ionRouterOutlet, direction);
this.commitView(enteringEl, leavingEl, ionRouterOutlet, direction, showGoBack);
} else {
setTimeout(() => {
this.transitionView(enteringEl, leavingEl, ionRouterOutlet, direction);
this.transitionView(enteringEl, leavingEl, ionRouterOutlet, direction, showGoBack);
}, 10);
}
}
private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOuter: HTMLIonRouterOutletElement, direction?: NavDirection) {
private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOuter: HTMLIonRouterOutletElement, direction?: NavDirection, showGoBack?: boolean) {
if (enteringEl === leavingEl) {
return;
@@ -255,7 +285,7 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
deepWait: true,
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack: direction === 'forward',
showGoBack,
progressAnimation: false
});
@@ -266,11 +296,21 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
}
}
handleNavigate(type: 'push' | 'replace', path: string, direction?: RouterDirection) {
this.currentDirection = direction;
if (type === 'push') {
this.props.history.push(path);
} else {
this.props.history.replace(path);
}
}
render() {
return (
<RouteManagerContext.Provider value={this.state}>
<NavManager
{...this.props}
onNavigate={this.handleNavigate}
findViewInfoById={(id: string) => this.state.viewStacks.findViewInfoById(id)}
findViewInfoByLocation={(location: HistoryLocation) => this.state.viewStacks.findViewInfoByLocation(location)}
getActiveIonPage={() => this.state.viewStacks.findViewInfoById(this.activeIonPageId)}
@@ -282,16 +322,5 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
}
}
const RouteManagerWithRouter = withRouter(RouteManager);
export const RouteManagerWithRouter = withRouter(RouteManager);
RouteManagerWithRouter.displayName = 'RouteManager';
export class IonReactRouter extends React.Component<BrowserRouterProps> {
render() {
const { children, ...props } = this.props;
return (
<BrowserRouter {...props}>
<RouteManagerWithRouter>{children}</RouteManagerWithRouter>
</BrowserRouter>
);
}
}

View File

@@ -85,16 +85,4 @@ export class ViewStacks {
return { view, viewStack };
}
setHiddenViews() {
const keys = this.getKeys();
keys.forEach(key => {
const viewStack = this.viewStacks[key];
viewStack!.views.forEach(view => {
if (!view.routeData.match && !view.isIonRoute) {
view.show = false;
view.mount = false;
}
});
});
}
}

View File

@@ -1,2 +1,2 @@
export { IonReactRouter } from './Router';
export { IonReactRouter } from './IonReactRouter';
export { IonReactHashRouter } from './IonReactHashRouter';

View File

@@ -1,4 +1,4 @@
## @ionic/react (beta)
## @ionic/react
These are React specific building blocks on top of [@ionic/core](https://www.npmjs.com/package/@ionic/core) components/services.
@@ -27,9 +27,6 @@ ionic init "My React App" --type=react
ionic integrations enable capacitor
```
Open the './capacitor.config.json' file in your projects root.
Change `"webDir": "www"` to be `"webDir": "build"` (dependent on your config but Ionic React defaults with this as the build directory)
Then run the following command to get started with either `ios` or `android` platforms.
```
ionic capacitor add <android|ios>

View File

@@ -54,13 +54,19 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
async present(prevProps?: Props) {
const { isOpen, onDidDismiss, ...cProps } = this.props;
const overlay = this.overlay = await controller.create({
...cProps as any
});
let overlay = this.overlay;
if (!overlay) {
overlay = this.overlay = await controller.create({
...cProps as any
});
}
attachProps(overlay, {
[dismissEventName]: onDidDismiss
}, prevProps);
await overlay.present();
// Check isOpen again since the value could of changed during the async call to controller.create
if (this.props.isOpen === true) {
await overlay.present();
}
}
render(): null {

View File

@@ -6,6 +6,8 @@ import { IonTabBarInner } from '../inner-proxies';
import { IonTabButton } from '../proxies';
type Props = LocalJSX.IonTabBar & {
onIonTabsDidChange?: (event: CustomEvent<{ tab: string }>) => void;
onIonTabsWillChange?: (event: CustomEvent<{ tab: string }>) => void;
currentPath?: string;
slot?: 'bottom' | 'top';
};
@@ -75,6 +77,12 @@ const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component<Pro
this.context.navigate(originalHref, 'back');
}
} else {
if (this.props.onIonTabsWillChange) {
this.props.onIonTabsWillChange(new CustomEvent('ionTabWillChange', { detail: { tab: e.detail.tab } }));
}
if (this.props.onIonTabsDidChange) {
this.props.onIonTabsDidChange(new CustomEvent('ionTabDidChange', { detail: { tab: e.detail.tab } }));
}
this.context.navigate(this.state.tabs[e.detail.tab].currentHref, 'none');
}
}

View File

@@ -1,3 +1,4 @@
import { JSX as LocalJSX } from '@ionic/core';
import React from 'react';
import { NavContext } from '../../contexts/NavContext';
@@ -5,7 +6,7 @@ import { IonRouterOutlet } from '../IonRouterOutlet';
import { IonTabBar } from './IonTabBar';
interface Props {
interface Props extends LocalJSX.IonTabs {
children: React.ReactNode;
}
@@ -28,7 +29,7 @@ const tabsInner: React.CSSProperties = {
contain: 'layout size style'
};
export const IonTabs = /*@__PURE__*/(() => class extends React.Component<Props> {
export class IonTabs extends React.Component<Props> {
context!: React.ContextType<typeof NavContext>;
routerOutletRef: React.Ref<HTMLIonRouterOutletElement> = React.createRef();
@@ -38,7 +39,7 @@ export const IonTabs = /*@__PURE__*/(() => class extends React.Component<Props>
render() {
let outlet: React.ReactElement<{}> | undefined;
let tabBar: React.ReactElement<{ slot: 'bottom' | 'top' }> | undefined;
let tabBar: React.ReactElement | undefined;
React.Children.forEach(this.props.children, (child: any) => {
if (child == null || typeof child !== 'object' || !child.hasOwnProperty('type')) {
@@ -48,7 +49,8 @@ export const IonTabs = /*@__PURE__*/(() => class extends React.Component<Props>
outlet = child;
}
if (child.type === IonTabBar) {
tabBar = child;
const { onIonTabsDidChange, onIonTabsWillChange } = this.props;
tabBar = React.cloneElement(child, { onIonTabsDidChange, onIonTabsWillChange });
}
});
@@ -71,11 +73,7 @@ export const IonTabs = /*@__PURE__*/(() => class extends React.Component<Props>
);
}
static get displayName() {
return 'IonTabs';
}
static get contextType() {
return NavContext;
}
})();
}

View File

@@ -1,33 +1,36 @@
import { camelToDashCase } from './case';
export const attachProps = (node: HTMLElement, newProps: any, oldProps: any = {}) => {
// add any classes in className to the class list
const className = getClassName(node.classList, newProps, oldProps);
if (className !== '') {
node.className = className;
}
Object.keys(newProps).forEach(name => {
if (name === 'children' || name === 'style' || name === 'ref' || name === 'class' || name === 'className' || name === 'forwardedRef') {
return;
// some test frameworks don't render DOM elements, so we test here to make sure we are dealing with DOM first
if (node instanceof Element) {
// add any classes in className to the class list
const className = getClassName(node.classList, newProps, oldProps);
if (className !== '') {
node.className = className;
}
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
const eventName = name.substring(2);
const eventNameLc = eventName[0].toLowerCase() + eventName.substring(1);
if (!isCoveredByReact(eventNameLc)) {
syncEvent(node, eventNameLc, newProps[name]);
Object.keys(newProps).forEach(name => {
if (name === 'children' || name === 'style' || name === 'ref' || name === 'class' || name === 'className' || name === 'forwardedRef') {
return;
}
} else {
(node as any)[name] = newProps[name];
const propType = typeof newProps[name];
if (propType === 'string') {
node.setAttribute(camelToDashCase(name), newProps[name]);
if (name.indexOf('on') === 0 && name[2] === name[2].toUpperCase()) {
const eventName = name.substring(2);
const eventNameLc = eventName[0].toLowerCase() + eventName.substring(1);
if (!isCoveredByReact(eventNameLc)) {
syncEvent(node, eventNameLc, newProps[name]);
}
} else {
(node as any)[name] = newProps[name];
const propType = typeof newProps[name];
if (propType === 'string') {
node.setAttribute(camelToDashCase(name), newProps[name]);
} else {
(node as any)[name] = newProps[name];
}
}
}
});
});
}
};
export const getClassName = (classList: DOMTokenList, newProps: any, oldProps: any) => {

View File

@@ -2,8 +2,6 @@ import { RouterDirection } from '@ionic/core';
import React from 'react';
export interface NavContextState {
getHistory: () => History;
getLocation: () => Location;
getPageManager: () => any;
getStackManager: () => any;
goBack: (defaultHref?: string) => void;
@@ -15,8 +13,6 @@ export interface NavContextState {
}
export const NavContext = /*@__PURE__*/React.createContext<NavContextState>({
getHistory: () => window.history,
getLocation: () => window.location,
getPageManager: () => undefined,
getStackManager: () => undefined,
goBack: (defaultHref?: string) => {