Compare commits

..

51 Commits

Author SHA1 Message Date
Liam DeBeasi
926b119231 remove old lines 2019-11-18 15:03:42 -05:00
Liam DeBeasi
256c7de469 only fire pressUp if press was fired; allow overriding of other gesture params 2019-11-18 15:02:38 -05:00
Liam DeBeasi
b7b5b94cce add angular integration 2019-11-12 11:39:21 -05:00
Liam DeBeasi
5e1e883585 sync with master 2019-11-12 11:33:42 -05:00
Liam DeBeasi
21484f1f3a fix(gesture): release gesture when disabling (#19855)
fixes #19848
2019-11-12 10:45:06 -05:00
Tawfiek Khalaf
b28cf02ef3 feat(alert): add support for textarea (#16851)
resolves #14153
2019-11-11 14:27:45 -05:00
Liam DeBeasi
bef0f53d0d feat(select): add --placeholder-opacity and --placeholder-color, expose shadow parts (#19893)
resolves #17446
2019-11-11 12:49:10 -05:00
Brandy Carney
1a8b7a4559 fix(card): update background to use the same as item (#19602)
uses item background but falls back to the background of the content
2019-11-08 12:55:19 -05:00
Brandy Carney
b23c759456 docs(slides): document css vars (#19872) 2019-11-08 12:37:00 -05:00
Antoine
1ab7066aa0 feat(searchbar): add --box-shadow variable to style searchbar input (#19838)
* Updates searchbar.md.scss to expose box-shadow

We would like to style the box-shadow property in searchbar and give the parent the ability to control it.

* fix(searchbar): add box shadow to main input for both modes
2019-11-08 10:53:42 -05:00
Liam DeBeasi
48a766246d feat(angular): expose Ionic Gestures via GestureController (#19864)
* expose gestures thru angular

* run linter
2019-11-08 10:35:52 -05:00
Brandy Carney
fce3e24600 fix(content): set fixed content to position absolute (#19867)
Adds absolute position to fixed slot in order to display text that is inside of the slot.

Fixes #17754

Co-authored-by: Matthew de Nobrega matthew@caro.health
2019-11-08 10:31:33 -05:00
Masahiko Sakakibara
be0ab5c2ff chore(menu): change deprecated error message (#19717) 2019-11-07 18:58:32 -05:00
Simon
c5be8831a2 docs(searchbar): update vue usage to kebab-case for the attributes (#19625) 2019-11-07 18:55:47 -05:00
Ely Lucas
edf64ef121 merge release-4.11.4 2019-11-07 15:54:48 -07:00
Himujjal Upadhyaya
504051d709 fix(nav-params): set generic type on navigation parameters get() (#19195) 2019-11-07 17:45:31 -05:00
Dylan Ballandras
90be7ec4b6 docs(toggle): update prop name for toggle OnChange in react (#19344)
closes #19343
2019-11-07 17:36:37 -05:00
Ely Lucas
1af1e2a14f chore(core): fixing test from bad rebase 2019-11-07 15:32:39 -07:00
Ely Lucas
2cc4244c05 4.11.4 2019-11-07 15:26:04 -07:00
Aubrey Holland
74e40cdc35 fix(react): check for component unmount, fixes #19859 2019-11-07 15:24:52 -07:00
Ely Lucas
13b323f25d chore(react): lowering the timeout for the ionpage check to avoid false positives 2019-11-07 15:24:52 -07:00
Ely Lucas
3e14a57f84 fix(react): adding multiple subscriptions to lifecycle events, fixes #19792 (#19858) 2019-11-07 15:24:52 -07:00
Ely Lucas
9864c17c1c fix(react): add check to warn if no ionpage is found, fixes #19832 (#19857) 2019-11-07 15:24:52 -07:00
Ely Lucas
e2ed0e9e87 fix(react): expand the location stack to better support back button, fixes #19748 (#19856) 2019-11-07 15:24:52 -07:00
Ely Lucas
a4b2de5730 fix(react): adding hardware back button support, fixes(19819) (#19851) 2019-11-07 15:24:52 -07:00
Ely Lucas
f3ac682010 fix(react): adding swipe back functionality and routerOutlet ready improvements, fixes #19818 (#19849) 2019-11-07 15:24:52 -07:00
Ely Lucas
2f5d823748 fix(react): create a new overlay each time component is presented, fixes #19841, #19823 (#19842) 2019-11-07 15:24:52 -07:00
Liam DeBeasi
e5e9dd5dfe fix(menu): clamp out of bounds swipe value (#19684)
fixes #18927
2019-11-07 15:24:09 -07:00
Liam DeBeasi
ad747b05f1 add press up handler 2019-11-07 15:20:49 -05:00
Liam DeBeasi
3b52074a10 fix typo 2019-11-07 15:17:26 -05:00
Liam DeBeasi
9eac7266a0 Fix 2019-11-07 15:04:18 -05:00
Liam DeBeasi
12a4c974b4 add press recognizer 2019-11-07 15:03:05 -05:00
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
78 changed files with 1121 additions and 391 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,4 +1,33 @@
## [4.11.2](https://github.com/ionic-team/ionic/compare/v4.11.1...v4.11.2) (2019-10-21)
## [4.11.4](https://github.com/ionic-team/ionic/compare/v4.11.1...v4.11.4) (2019-11-07)
### Bug Fixes
* **react:** check for component unmount, fixes [#19859](https://github.com/ionic-team/ionic/issues/19859) ([7356c40](https://github.com/ionic-team/ionic/commit/7356c401742ce2b3241d6ab05fce0fa65d2f1f8a))
* **react:** adding multiple subscriptions to lifecycle events, fixes [#19792](https://github.com/ionic-team/ionic/issues/19792) ([#19858](https://github.com/ionic-team/ionic/issues/19858)) ([0a3014d](https://github.com/ionic-team/ionic/commit/0a3014d35e2102570fd3d8c5ada29eb01aab18e9))
* **react:** add check to warn if no ionpage is found, fixes [#19832](https://github.com/ionic-team/ionic/issues/19832) ([#19857](https://github.com/ionic-team/ionic/issues/19857)) ([628e766](https://github.com/ionic-team/ionic/commit/628e76668ea72baebdb02b9dcfe24c0da837fb08))
* **react:** expand the location stack to better support back button, fixes [#19748](https://github.com/ionic-team/ionic/issues/19748) ([#19856](https://github.com/ionic-team/ionic/issues/19856)) ([d89508b](https://github.com/ionic-team/ionic/commit/d89508b1b58481d518b89362a8792d05f3f451c9))
* **react:** adding hardware back button support, fixes(19819) ([#19851](https://github.com/ionic-team/ionic/issues/19851)) ([fd9745d](https://github.com/ionic-team/ionic/commit/fd9745ddcddded76d64220838aef0f599bf4352f))
* **react:** adding swipe back functionality and routerOutlet ready improvements, fixes [#19818](https://github.com/ionic-team/ionic/issues/19818) ([#19849](https://github.com/ionic-team/ionic/issues/19849)) ([bcc40c8](https://github.com/ionic-team/ionic/commit/bcc40c8d59b723bbdb1dfd318bfb2219eb8df3cf))
* **react:** create a new overlay each time component is presented, fixes [#19841](https://github.com/ionic-team/ionic/issues/19841), [#19823](https://github.com/ionic-team/ionic/issues/19823) ([#19842](https://github.com/ionic-team/ionic/issues/19842)) ([9fad416](https://github.com/ionic-team/ionic/commit/9fad4161be4859969e14d4d33169ef022052d6bf))
## [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

View File

@@ -38,7 +38,7 @@ export class NavParams {
*
* @param param Which param you want to look up
*/
get(param: string): any {
get<T = any>(param: string): T {
return this.data[param];
}
}

View File

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

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { Animation, createAnimation } from '@ionic/core';
import { Animation, createAnimation, getTimeGivenProgression } from '@ionic/core';
@Injectable({
providedIn: 'root',
@@ -11,4 +11,22 @@ export class AnimationController {
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

@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { Gesture, GestureConfig, PressRecognizerOptions, createGesture, createPressRecognizer } from '@ionic/core';
@Injectable({
providedIn: 'root',
})
export class GestureController {
/**
* Create a new gesture
*/
create(opts: GestureConfig): Gesture {
return createGesture(opts);
}
/**
* Create a new Press recognizer gesture
*/
pressRecognizer(opts: PressRecognizerOptions): Gesture {
return createPressRecognizer(opts);
}
}

View File

@@ -965,6 +965,7 @@ ion-searchbar,event,ionClear,void,true
ion-searchbar,event,ionFocus,void,true
ion-searchbar,event,ionInput,KeyboardEvent,true
ion-searchbar,css-prop,--background
ion-searchbar,css-prop,--box-shadow
ion-searchbar,css-prop,--cancel-button-color
ion-searchbar,css-prop,--clear-button-color
ion-searchbar,css-prop,--color
@@ -1037,6 +1038,8 @@ ion-select,css-prop,--padding-bottom
ion-select,css-prop,--padding-end
ion-select,css-prop,--padding-start
ion-select,css-prop,--padding-top
ion-select,css-prop,--placeholder-color
ion-select,css-prop,--placeholder-opacity
ion-select-option,shadow
ion-select-option,prop,disabled,boolean,false,false,false
@@ -1090,6 +1093,10 @@ ion-slides,event,ionSlideTransitionStart,void,true
ion-slides,event,ionSlideWillChange,void,true
ion-slides,css-prop,--bullet-background
ion-slides,css-prop,--bullet-background-active
ion-slides,css-prop,--progress-bar-background
ion-slides,css-prop,--progress-bar-background-active
ion-slides,css-prop,--scroll-bar-background
ion-slides,css-prop,--scroll-bar-background-active
ion-spinner,shadow
ion-spinner,prop,color,string | undefined,undefined,false,false
@@ -1098,12 +1105,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

@@ -20,7 +20,7 @@ export interface AlertOptions {
}
export interface AlertInput {
type?: TextFieldTypes | 'checkbox' | 'radio';
type?: TextFieldTypes | 'checkbox' | 'radio' | 'textarea';
name?: string;
placeholder?: string;
value?: any;

View File

@@ -197,3 +197,8 @@
.alert-checkbox-inner {
box-sizing: border-box;
}
textarea.alert-input {
min-height: $alert-input-min-height;
resize: none;
}

View File

@@ -373,25 +373,46 @@ export class Alert implements ComponentInterface, OverlayInterface {
}
return (
<div class="alert-input-group" aria-labelledby={labelledby}>
{ inputs.map(i => (
<div class="alert-input-wrapper">
<input
placeholder={i.placeholder}
value={i.value}
type={i.type}
min={i.min}
max={i.max}
onInput={e => i.value = (e.target as any).value}
id={i.id}
disabled={i.disabled}
tabIndex={0}
class={{
'alert-input': true,
'alert-input-disabled': i.disabled || false
}}
/>
</div>
))}
{ inputs.map(i => {
if (i.type === 'textarea') {
return (
<div class="alert-input-wrapper">
<textarea
placeholder={i.placeholder}
value={i.value}
onInput={e => i.value = (e.target as any).value}
id={i.id}
disabled={i.disabled}
tabIndex={0}
class={{
'alert-input': true,
'alert-input-disabled': i.disabled || false
}}
/>
</div>
);
} else {
return (
<div class="alert-input-wrapper">
<input
placeholder={i.placeholder}
value={i.value}
type={i.type}
min={i.min}
max={i.max}
onInput={e => i.value = (e.target as any).value}
id={i.id}
disabled={i.disabled}
tabIndex={0}
class={{
'alert-input': true,
'alert-input-disabled': i.disabled || false
}}
/>
</div>
);
}
})}
</div>
);
}

View File

@@ -14,3 +14,6 @@ $alert-button-line-height: 20px !default;
/// @prop - Font size of the alert button
$alert-button-font-size: 14px !default;
/// @prop - Minimum height of a textarea in the alert
$alert-input-min-height: 37px !default;

View File

@@ -17,7 +17,7 @@ Optionally, a `role` property can be added to a button, such as `cancel`. If a `
### Inputs
Alerts can also include several different inputs whose data can be passed back to the app. Inputs can be used as a simple way to prompt users for information. Radios, checkboxes and text inputs are all accepted, but they cannot be mixed. For example, an alert could have all radio button inputs, or all checkbox inputs, but the same alert cannot mix radio and checkbox inputs. Do note however, different types of "text" inputs can be mixed, such as `url`, `email`, `text`, etc. If you require a complex form UI which doesn't fit within the guidelines of an alert then we recommend building the form within a modal instead.
Alerts can also include several different inputs whose data can be passed back to the app. Inputs can be used as a simple way to prompt users for information. Radios, checkboxes and text inputs are all accepted, but they cannot be mixed. For example, an alert could have all radio button inputs, or all checkbox inputs, but the same alert cannot mix radio and checkbox inputs. Do note however, different types of "text" inputs can be mixed, such as `url`, `email`, `text`, `textarea` etc. If you require a complex form UI which doesn't fit within the guidelines of an alert then we recommend building the form within a modal instead.
<!-- Auto Generated Below -->
@@ -102,6 +102,13 @@ export class AlertExample {
value: 'hello',
placeholder: 'Placeholder 2'
},
// multiline input.
{
name: 'paragraph',
id: 'paragraph',
type: 'textarea',
placeholder: 'Placeholder 3'
},
{
name: 'name3',
value: 'http://ionicframework.com',
@@ -346,6 +353,13 @@ function presentAlertPrompt() {
value: 'hello',
placeholder: 'Placeholder 2'
},
// multiline input.
{
name: 'paragraph',
id: 'paragraph',
type: 'textarea',
placeholder: 'Placeholder 3'
},
{
name: 'name3',
value: 'http://ionicframework.com',

View File

@@ -131,6 +131,11 @@
placeholder: 'Placeholder 3',
disabled: true
},
{
type: 'textarea',
placeholder: 'Placeholder 4',
value: 'Textarea hello'
},
{
name: 'name3',
type: 'text',

View File

View File

@@ -73,6 +73,13 @@ export class AlertExample {
value: 'hello',
placeholder: 'Placeholder 2'
},
// multiline input.
{
name: 'paragraph',
id: 'paragraph',
type: 'textarea',
placeholder: 'Placeholder 3'
},
{
name: 'name3',
value: 'http://ionicframework.com',

View File

@@ -58,6 +58,13 @@ function presentAlertPrompt() {
value: 'hello',
placeholder: 'Placeholder 2'
},
// multiline input.
{
name: 'paragraph',
id: 'paragraph',
type: 'textarea',
placeholder: 'Placeholder 3'
},
{
name: 'name3',
value: 'http://ionicframework.com',

View File

@@ -5,7 +5,7 @@
// --------------------------------------------------
:host {
--background: var(--ion-item-background, transparent);
--background: #{$item-ios-background};
--color: #{$card-ios-text-color};
@include margin($card-ios-margin-top, $card-ios-margin-end, $card-ios-margin-bottom, $card-ios-margin-start);

View File

@@ -5,7 +5,7 @@
// --------------------------------------------------
:host {
--background: var(--ion-item-background, transparent);
--background: #{$item-md-background};
--color: #{$card-md-text-color};
@include margin($card-md-margin-top, $card-md-margin-end, $card-md-margin-bottom, $card-md-margin-start);

View File

@@ -130,7 +130,7 @@
height: 100%;
opacity: 0;
pointer-events: none;
}
@@ -164,3 +164,11 @@
background-repeat: repeat-y;
background-size: 10px 16px;
}
// Content: Fixed
// --------------------------------------------------
::slotted([slot="fixed"]) {
position: absolute;
}

View File

@@ -0,0 +1,10 @@
import { newE2EPage } from '@stencil/core/testing';
test('content: fixed', async () => {
const page = await newE2EPage({
url: '/src/components/content/test/fixed?ionic:_testing=true'
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Content - Fixed</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script></head>
<body>
<ion-app>
<ion-header id="header">
<ion-toolbar>
<ion-title>Content - Fixed</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<h1>Regular Content</h1>
<div slot="fixed">
<h1>Fixed content</h1>
<p>Fixed paragraph</p>
<ion-button>Button</ion-button>
</div>
<ion-fab vertical="top" horizontal="end" slot="fixed">
<ion-fab-button>
<ion-icon name="add"></ion-icon>
</ion-fab-button>
</ion-fab>
<ion-fab vertical="bottom" horizontal="start" slot="fixed">
<ion-fab-button color="tertiary">
<ion-icon name="star"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -56,18 +56,19 @@
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

@@ -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';
@@ -163,8 +163,8 @@ BEFORE:
<div main>...</div>
AFTER:
<ion-menu contentId="my-content"></ion-menu>
<div id="my-content">...</div>
<ion-menu contentId="main-content"></ion-menu>
<div id="main-content">...</div>
`);
}
const content = this.contentId !== undefined
@@ -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

@@ -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

@@ -171,15 +171,15 @@ export const SearchbarExample: React.FC = () => (
<template>
<!-- Default Searchbar -->
<ion-searchbar></ion-searchbar>
<!-- Searchbar with cancel button always shown -->
<ion-searchbar showCancelButton="always"></ion-searchbar>
<ion-searchbar show-cancel-button="always"></ion-searchbar>
<!-- Searchbar with cancel button never shown -->
<ion-searchbar showCancelButton="never"></ion-searchbar>
<ion-searchbar show-cancel-button="never"></ion-searchbar>
<!-- Searchbar with cancel button shown on focus -->
<ion-searchbar showCancelButton="focus"></ion-searchbar>
<ion-searchbar show-cancel-button="focus"></ion-searchbar>
<!-- Searchbar with danger color -->
<ion-searchbar color="danger"></ion-searchbar>
@@ -197,7 +197,7 @@ export const SearchbarExample: React.FC = () => (
<ion-searchbar disabled="true"></ion-searchbar>
<!-- Searchbar with a cancel button and custom cancel button text -->
<ion-searchbar showCancelButton="focus" cancelButtonText="Custom Cancel"></ion-searchbar>
<ion-searchbar show-cancel-button="focus" cancel-button-text="Custom Cancel"></ion-searchbar>
<!-- Searchbar with a custom debounce -->
<ion-searchbar debounce="500"></ion-searchbar>
@@ -280,7 +280,8 @@ Type: `Promise<void>`
| Name | Description |
| --------------------------- | ---------------------------------------- |
| `--background` | Background of the searchbar |
| `--background` | Background of the searchbar input |
| `--box-shadow` | Box shadow of the searchbar input |
| `--cancel-button-color` | Color of the searchbar cancel button |
| `--clear-button-color` | Color of the searchbar clear button |
| `--color` | Color of the searchbar text |

View File

@@ -5,11 +5,12 @@
// --------------------------------------------------
:host {
--clear-button-color: #{$searchbar-ios-input-clear-icon-color};
--background: #{$searchbar-ios-input-background-color};
--box-shadow: none;
--cancel-button-color: #{$searchbar-ios-cancel-button-color};
--clear-button-color: #{$searchbar-ios-input-clear-icon-color};
--color: #{$searchbar-ios-input-text-color};
--icon-color: #{$searchbar-ios-input-search-icon-color};
--background: #{$searchbar-ios-input-background-color};
@include padding($searchbar-ios-padding-top, $searchbar-ios-padding-end, $searchbar-ios-padding-bottom, $searchbar-ios-padding-start);
@@ -20,8 +21,6 @@
.searchbar-input-container {
height: $searchbar-ios-input-height;
contain: strict;
}

View File

@@ -5,11 +5,12 @@
// --------------------------------------------------
:host {
--clear-button-color: initial;
--background: #{$searchbar-md-input-background-color};
--box-shadow: #{$searchbar-md-input-box-shadow};
--cancel-button-color: #{$searchbar-md-cancel-button-color};
--clear-button-color: initial;
--color: #{$searchbar-md-input-text-color};
--icon-color: #{$searchbar-md-input-search-icon-color};
--background: #{$searchbar-md-input-background-color};
@include padding($searchbar-md-padding-top, $searchbar-md-padding-end, $searchbar-md-padding-bottom, $searchbar-md-padding-start);
@@ -67,8 +68,6 @@
font-weight: 400;
line-height: $searchbar-md-input-line-height;
box-shadow: $searchbar-md-input-box-shadow;
}

View File

@@ -5,7 +5,8 @@
:host {
/**
* @prop --background: Background of the searchbar
* @prop --background: Background of the searchbar input
* @prop --box-shadow: Box shadow of the searchbar input
* @prop --cancel-button-color: Color of the searchbar cancel button
* @prop --clear-button-color: Color of the searchbar clear button
* @prop --color: Color of the searchbar text
@@ -68,8 +69,6 @@
.searchbar-input {
@include text-inherit();
box-sizing: border-box;
display: block;
width: 100%;
@@ -81,6 +80,10 @@
background: var(--background);
font-family: inherit;
box-shadow: var(--box-shadow);
box-sizing: border-box;
appearance: none;
&::placeholder {

View File

@@ -26,7 +26,7 @@
</ion-searchbar>
<h5 class="ion-padding-start ion-padding-top"> Search - No Cancel Button </h5>
<ion-searchbar id="noCancel" value="after view" autocorrect="off" autocomplete="off" spellcheck="true" type="text" show-cancel-button="never">
<ion-searchbar class="red-box" id="noCancel" value="after view" autocorrect="off" autocomplete="off" spellcheck="true" type="text" show-cancel-button="never">
</ion-searchbar>
<h5 class="ion-padding-start ion-padding-top"> Search - Always Show Cancel Button</h5>
@@ -118,6 +118,12 @@
<ion-button expand="block" color="secondary" onClick="toggleDisabled()">Toggle disabled</ion-button>
</div>
<style>
.red-box {
--box-shadow: 0 2px 2px 0 rgba(255, 0, 0, 0.14), 0 3px 1px -2px rgba(255, 0, 0, 0.2), 0 1px 5px 0 rgba(255, 0, 0, 0.12);
}
</style>
<script>
function toggleAttr() {
var dynamicAttr = document.getElementById('dynamicAttr');

View File

@@ -2,15 +2,15 @@
<template>
<!-- Default Searchbar -->
<ion-searchbar></ion-searchbar>
<!-- Searchbar with cancel button always shown -->
<ion-searchbar showCancelButton="always"></ion-searchbar>
<ion-searchbar show-cancel-button="always"></ion-searchbar>
<!-- Searchbar with cancel button never shown -->
<ion-searchbar showCancelButton="never"></ion-searchbar>
<ion-searchbar show-cancel-button="never"></ion-searchbar>
<!-- Searchbar with cancel button shown on focus -->
<ion-searchbar showCancelButton="focus"></ion-searchbar>
<ion-searchbar show-cancel-button="focus"></ion-searchbar>
<!-- Searchbar with danger color -->
<ion-searchbar color="danger"></ion-searchbar>
@@ -28,7 +28,7 @@
<ion-searchbar disabled="true"></ion-searchbar>
<!-- Searchbar with a cancel button and custom cancel button text -->
<ion-searchbar showCancelButton="focus" cancelButtonText="Custom Cancel"></ion-searchbar>
<ion-searchbar show-cancel-button="focus" cancel-button-text="Custom Cancel"></ion-searchbar>
<!-- Searchbar with a custom debounce -->
<ion-searchbar debounce="500"></ion-searchbar>

View File

@@ -793,12 +793,14 @@ Type: `Promise<any>`
## CSS Custom Properties
| Name | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------- |
| `--padding-bottom` | Bottom padding of the select |
| `--padding-end` | Right padding if direction is left-to-right, and left padding if direction is right-to-left of the select |
| `--padding-start` | Left padding if direction is left-to-right, and right padding if direction is right-to-left of the select |
| `--padding-top` | Top padding of the select |
| Name | Description |
| ----------------------- | --------------------------------------------------------------------------------------------------------- |
| `--padding-bottom` | Bottom padding of the select |
| `--padding-end` | Right padding if direction is left-to-right, and left padding if direction is right-to-left of the select |
| `--padding-start` | Left padding if direction is left-to-right, and right padding if direction is right-to-left of the select |
| `--padding-top` | Top padding of the select |
| `--placeholder-color` | Color of the select placeholder text |
| `--placeholder-opacity` | Opacity of the select placeholder text |
----------------------------------------------

View File

@@ -9,7 +9,13 @@
* @prop --padding-end: Right padding if direction is left-to-right, and left padding if direction is right-to-left of the select
* @prop --padding-bottom: Bottom padding of the select
* @prop --padding-start: Left padding if direction is left-to-right, and right padding if direction is right-to-left of the select
*
* @prop --placeholder-color: Color of the select placeholder text
* @prop --placeholder-opacity: Opacity of the select placeholder text
*/
--placeholder-color: currentColor;
--placeholder-opacity: 0.33;
@include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
display: flex;
@@ -37,9 +43,9 @@
}
.select-placeholder {
color: currentColor;
color: var(--placeholder-color);
opacity: .33;
opacity: var(--placeholder-opacity);
}
button {

View File

@@ -471,10 +471,10 @@ export class Select implements ComponentInterface {
'select-disabled': disabled,
}}
>
<div class={selectTextClasses}>
<div class={selectTextClasses} part="text">
{selectText}
</div>
<div class="select-icon" role="presentation">
<div class="select-icon" role="presentation" part="icon">
<div class="select-icon-inner"></div>
</div>
<button

View File

@@ -26,7 +26,7 @@
<ion-item>
<ion-label>Gender</ion-label>
<ion-select id="gender" placeholder="Select One">
<ion-select id="gender" placeholder="Select One" class="custom-select">
<ion-select-option value="fn">Female</ion-select-option>
<ion-select-option value="ml">Male</ion-select-option>
</ion-select>
@@ -286,6 +286,9 @@
.outer-content {
--background: #f2f2f2;
}
.custom-select {
--placeholder-opacity: 0.5;
}
</style>
<script>

View File

@@ -761,10 +761,14 @@ Type: `Promise<void>`
## CSS Custom Properties
| Name | Description |
| ---------------------------- | ------------------------------------------ |
| `--bullet-background` | Background of the pagination bullets |
| `--bullet-background-active` | Background of the active pagination bullet |
| Name | Description |
| ---------------------------------- | ------------------------------------------------ |
| `--bullet-background` | Background of the pagination bullets |
| `--bullet-background-active` | Background of the active pagination bullet |
| `--progress-bar-background` | Background of the pagination progress-bar |
| `--progress-bar-background-active` | Background of the active pagination progress-bar |
| `--scroll-bar-background` | Background of the pagination scroll-bar |
| `--scroll-bar-background-active` | Background of the active pagination scroll-bar |
----------------------------------------------

View File

@@ -8,6 +8,12 @@ ion-slides {
/**
* @prop --bullet-background: Background of the pagination bullets
* @prop --bullet-background-active: Background of the active pagination bullet
*
* @prop --progress-bar-background: Background of the pagination progress-bar
* @prop --progress-bar-background-active: Background of the active pagination progress-bar
*
* @prop --scroll-bar-background: Background of the pagination scroll-bar
* @prop --scroll-bar-background-active: Background of the active pagination scroll-bar
*/
display: block;

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

@@ -115,17 +115,17 @@ export const ToggleExample: React.FC = () => (
<IonList>
<IonItem>
<IonLabel>Pepperoni</IonLabel>
<IonToggle value="pepperoni" onChange={() => {}} />
<IonToggle value="pepperoni" onIonChange={() => {}} />
</IonItem>
<IonItem>
<IonLabel>Sausage</IonLabel>
<IonToggle value="sausage" onChange={() => {}} disabled={true} />
<IonToggle value="sausage" onIonChange={() => {}} disabled={true} />
</IonItem>
<IonItem>
<IonLabel>Mushrooms</IonLabel>
<IonToggle value="mushrooms" onChange={() => {}} />
<IonToggle value="mushrooms" onIonChange={() => {}} />
</IonItem>
</IonList>
</IonContent>

View File

@@ -24,17 +24,17 @@ export const ToggleExample: React.FC = () => (
<IonList>
<IonItem>
<IonLabel>Pepperoni</IonLabel>
<IonToggle value="pepperoni" onChange={() => {}} />
<IonToggle value="pepperoni" onIonChange={() => {}} />
</IonItem>
<IonItem>
<IonLabel>Sausage</IonLabel>
<IonToggle value="sausage" onChange={() => {}} disabled={true} />
<IonToggle value="sausage" onIonChange={() => {}} disabled={true} />
</IonItem>
<IonItem>
<IonLabel>Mushrooms</IonLabel>
<IonToggle value="mushrooms" onChange={() => {}} />
<IonToggle value="mushrooms" onIonChange={() => {}} />
</IonItem>
</IonList>
</IonContent>

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,7 +1,10 @@
import 'ionicons';
export { createAnimation } from './utils/animation/animation';
export { getTimeGivenProgression } from './utils/animation/cubic-bezier';
export { createGesture } from './utils/gesture';
export { createPressRecognizer } from './utils/gesture/recognizers/press';
export { isPlatform, Platforms, getPlatforms } from './utils/platform';
export * from './utils/config';

View File

@@ -34,9 +34,9 @@ export * from './components/virtual-scroll/virtual-scroll-interface';
export { Animation, AnimationBuilder } from './utils/animation/animation-interface';
export * from './utils/overlays-interface';
export * from './global/config';
export { Gesture, GestureDetail } from './utils/gesture';
export { Gesture, GestureConfig, GestureDetail } from './utils/gesture';
export { PressRecognizerOptions } from './utils/gesture/recognizers/press';
// Global aux types
export type TextFieldTypes = 'date' | 'email' | 'number' | 'password' | 'search' | 'tel' | 'text' | 'url' | 'time';
export type Side = 'start' | 'end';
export type PredefinedColors = 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'danger' | 'light' | 'medium' | 'dark';

View File

@@ -29,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,7 +19,7 @@ interface AnimationInternal extends Animation {
/**
* Updates any existing animations.
*/
update(deep: boolean): Animation;
update(deep: boolean, toggleAnimationName: boolean, step?: number): Animation;
animationFinish(): void;
}
@@ -611,8 +611,7 @@ export const createAnimation = (animationId?: string): Animation => {
});
} else {
const animationDelay = getDelay() || 0;
const animationDuration = `-${animationDelay + (getDuration()! * step)}ms`;
const animationDuration = `-${getDuration()! * step}ms`;
elements.forEach(element => {
if (_keyframes.length > 0) {
@@ -623,7 +622,7 @@ export const createAnimation = (animationId?: string): Animation => {
}
};
const updateWebAnimation = () => {
const updateWebAnimation = (step?: number) => {
webAnimations.forEach(animation => {
animation.effect.updateTiming({
delay: getDelay(),
@@ -634,15 +633,19 @@ export const createAnimation = (animationId?: string): 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);
@@ -663,25 +666,25 @@ export const createAnimation = (animationId?: string): 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();
@@ -690,8 +693,7 @@ export const createAnimation = (animationId?: string): Animation => {
if (!initialized) {
initializeAnimation();
} else {
update();
setAnimationStep(0);
update(false, true, step);
}
return ani;

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,95 @@ 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]);
})
it('cubic-bezier(0.32, 0.72, 0, 1) (with out of bounds progression)', () => {
const equation = [
[0, 0],
[0.05, 0.2],
[.14, 1.72],
[1, 1]
];
expect(getTimeGivenProgression(...equation, 1.32)).toEqual([]);
expect(getTimeGivenProgression(...equation, -0.32)).toEqual([]);
})
})
});
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

@@ -203,8 +203,12 @@ export const createGesture = (config: GestureConfig): Gesture => {
return {
enable(enable = true) {
if (!enable && hasCapturedPan) {
pointerUp(undefined);
if (!enable) {
if (hasCapturedPan) {
pointerUp(undefined);
}
reset();
}
pointerEvents.enable(enable);
},

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

@@ -0,0 +1,91 @@
import { GestureConfig, GestureDetail, createGesture } from '../index';
export interface PressRecognizerOptions extends GestureConfig {
/**
* How long the user must press the
* element before onPressHandler is fired.
*/
time: number;
/**
* The maximum amount of movement on the x and y
* axes that is allowed and still have onPressHandler fire.
*/
maxThreshold: number;
/**
* Fired when the long press occurs.
*/
onPressHandler?: () => void;
/**
* Fired when the pointer is lifted.
*/
onPressUpHandler?: () => void;
}
export const createPressRecognizer = (opts: PressRecognizerOptions) => {
const THRESHOLD = opts.maxThreshold || 10;
let timeout: any;
let pressed = false;
const onStart = (detail: GestureDetail) => {
if (opts.onStart) {
opts.onStart(detail);
}
clearGestureTimeout();
timeout = setTimeout(() => {
if (opts.onPressHandler) {
opts.onPressHandler();
}
pressed = true;
clearGestureTimeout();
}, opts.time || 500);
};
const onMove = (detail: GestureDetail) => {
if (opts.onMove) {
opts.onMove(detail);
}
if (Math.abs(detail.deltaX) + Math.abs(detail.deltaY) <= THRESHOLD) {
return;
}
clearGestureTimeout();
};
const onEnd = (detail: GestureDetail) => {
if (opts.onEnd) {
opts.onEnd(detail);
}
if (!pressed) { return; }
if (opts.onPressUpHandler) {
opts.onPressUpHandler();
}
pressed = false;
clearGestureTimeout();
};
const clearGestureTimeout = () => {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
};
return createGesture({
...opts,
gestureName: opts.gestureName || 'press',
threshold: opts.threshold || 0,
onStart,
onMove,
onEnd,
});
};

View File

@@ -0,0 +1,77 @@
import { newE2EPage } from '@stencil/core/testing';
test(`gesture: press`, async () => {
const page = await newE2EPage({ url: '/src/utils/gesture/recognizers/tests/press' });
const screenshotCompares = [];
screenshotCompares.push(await page.compareScreenshot());
const square = await page.$('.square');
const { x, y } = await coords(square);
page.mouse.move(x, y);
page.mouse.down();
await page.waitFor(300);
expect(await page.find('.square')).toHaveClass('pressed');
page.mouse.up();
expect(await page.find('.square')).toHaveClass('pressedUp');
screenshotCompares.push(await page.compareScreenshot('end press'));
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});
test(`gesture: press:short press`, async () => {
const page = await newE2EPage({ url: '/src/utils/gesture/recognizers/tests/press' });
const screenshotCompares = [];
screenshotCompares.push(await page.compareScreenshot());
const square = await page.$('.square');
const { x, y } = await coords(square);
page.mouse.move(x, y);
page.mouse.down();
await page.waitFor(50);
page.mouse.up();
const squareAgain = await page.find('.square');
expect(squareAgain).not.toHaveClass('pressed');
expect(squareAgain).not.toHaveClass('pressedUp');
screenshotCompares.push(await page.compareScreenshot('end press'));
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});
test(`gesture: press:click`, async () => {
const page = await newE2EPage({ url: '/src/utils/gesture/recognizers/tests/press' });
const screenshotCompares = [];
screenshotCompares.push(await page.compareScreenshot());
await page.click('.square');
const square = await page.find('.square');
expect(square).not.toHaveClass('pressed');
expect(square).not.toHaveClass('pressedUp');
screenshotCompares.push(await page.compareScreenshot('end press'));
for (const screenshotCompare of screenshotCompares) {
expect(screenshotCompare).toMatchScreenshot();
}
});
const coords = async el => {
const box = await el.boundingBox();
return {
x: Math.floor(box.x + (box.width / 2)),
y: Math.floor(box.y + (box.height / 2))
};
};

View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Gesture - Press</title>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../../dist/ionic/ionic.esm.js"></script>
<script type="module">
import { createPressRecognizer } from '../../../../../dist/ionic/index.esm.js';
const square = document.querySelector('.square');
const pressRecognizer = createPressRecognizer({
el: square,
time: 251,
minThreshold: 10,
onStart: () => square.classList.remove('pressedUp'),
onPressHandler: () => square.classList.toggle('pressed'),
onPressUpHandler: () => square.classList.add('pressedUp')
});
pressRecognizer.enable(true);
</script>
<style>
.square {
width: 100px;
height: 100px;
background: rgba(255, 0, 0, 0.5);
text-align: center;
line-height: 100px;
margin-left: 25px;
margin-top: 25px;
transition: 0.2s all ease-in-out;
cursor: pointer;
}
.pressed {
transform: scale(1.5);
background: rgba(0, 255, 0, 0.5);
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Gesture - Press</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div class="ion-padding">
<div class="square">Hello</div>
</div>
</ion-content>
</ion-app>
</body>
</html>

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

@@ -4,24 +4,16 @@ import { Location as HistoryLocation, UnregisterCallback } from 'history';
import React from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { generateId } from '../utils';
import { LocationHistory } from '../utils/LocationHistory';
import { StackManager } from './StackManager';
import { ViewItem } from './ViewItem';
import { ViewStack } from './ViewStacks';
interface NavManagerProps extends RouteComponentProps {
findViewInfoByLocation: (location: HistoryLocation) => { view?: ViewItem, viewStack?: ViewStack };
findViewInfoById: (id: string) => { view?: ViewItem, viewStack?: ViewStack };
getActiveIonPage: () => { view?: ViewItem, viewStack?: ViewStack };
onNavigateBack: (defaultHref?: string) => void;
onNavigate: (type: 'push' | 'replace', path: string, state?: any) => void;
}
export class NavManager extends React.Component<NavManagerProps, NavContextState> {
listenUnregisterCallback: UnregisterCallback | undefined;
locationHistory: LocationHistory = new LocationHistory();
constructor(props: NavManagerProps) {
super(props);
@@ -40,16 +32,15 @@ export class NavManager extends React.Component<NavManagerProps, NavContextState
this.setState({
currentPath: location.pathname
});
this.locationHistory.add(location);
});
this.locationHistory.add({
hash: window.location.hash,
key: generateId(),
pathname: window.location.pathname,
search: window.location.search,
state: {}
});
if (document) {
document.addEventListener('ionBackButton', (e: any) => {
e.detail.register(0, () => {
this.props.history.goBack();
});
});
}
}
componentWillUnmount() {
@@ -59,26 +50,7 @@ export class NavManager extends React.Component<NavManagerProps, NavContextState
}
goBack(defaultHref?: string) {
const { view: activeIonPage } = this.props.getActiveIonPage();
if (activeIonPage) {
const { view: enteringView } = this.props.findViewInfoById(activeIonPage.prevId!);
if (enteringView) {
const lastLocation = this.locationHistory.findLastLocation(enteringView.routeData.match.url);
if (lastLocation) {
this.props.onNavigate('replace', lastLocation.pathname + lastLocation.search, 'back');
} else {
this.props.onNavigate('replace', enteringView.routeData.match.url, 'back');
}
} else {
if (defaultHref) {
this.props.onNavigate('replace', defaultHref, 'back');
}
}
} else {
if (defaultHref) {
this.props.onNavigate('replace', defaultHref, 'back');
}
}
this.props.onNavigateBack(defaultHref);
}
navigate(path: string, direction?: RouterDirection | 'none') {

View File

@@ -1,4 +1,3 @@
import { NavDirection } from '@ionic/core';
import React, { ReactNode } from 'react';
import { ViewStacks } from './ViewStacks';
@@ -7,9 +6,8 @@ export interface RouteManagerContextState {
syncView: (page: HTMLElement, viewId: string) => void;
hideView: (viewId: string) => void;
viewStacks: ViewStacks;
setupIonRouter: (id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) => Promise<void>;
setupIonRouter: (id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) => 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

@@ -1,10 +1,11 @@
import { NavDirection } from '@ionic/core';
import { RouterDirection } from '@ionic/react';
import { RouterDirection, getConfig } from '@ionic/react';
import { Action as HistoryAction, Location as HistoryLocation, UnregisterCallback } from 'history';
import React from 'react';
import { RouteComponentProps, matchPath, withRouter } from 'react-router-dom';
import { generateId } from '../utils';
import { generateId, isDevMode } from '../utils';
import { LocationHistory } from '../utils/LocationHistory';
import { IonRouteData } from './IonRouteData';
import { NavManager } from './NavManager';
@@ -21,19 +22,28 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
listenUnregisterCallback: UnregisterCallback | undefined;
activeIonPageId?: string;
currentDirection?: RouterDirection;
locationHistory = new LocationHistory();
constructor(props: RouteComponentProps) {
super(props);
this.listenUnregisterCallback = this.props.history.listen(this.historyChange.bind(this));
this.handleNavigate = this.handleNavigate.bind(this);
this.navigateBack = this.navigateBack.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)
};
this.locationHistory.add({
hash: window.location.hash,
key: generateId(),
pathname: window.location.pathname,
search: window.location.search,
state: {}
});
}
componentDidUpdate(_prevProps: RouteComponentProps, prevState: RouteManagerState) {
@@ -67,6 +77,13 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
historyChange(location: HistoryLocation, action: HistoryAction) {
location.state = location.state || { direction: this.currentDirection };
this.currentDirection = undefined;
if (action === 'PUSH') {
this.locationHistory.add(location);
} else if ((action === 'REPLACE' && location.state.direction === 'back') || action === 'POP') {
this.locationHistory.pop();
} else {
this.locationHistory.replace(location);
}
this.setState({
location,
action
@@ -75,7 +92,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();
@@ -100,13 +117,22 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
* If the page is being pushed into the stack by another view,
* record the view that originally directed to the new view for back button purposes.
*/
enteringView.prevId = enteringView.prevId || leavingView.id;
enteringView.prevId = leavingView.id;
} else if (action === 'POP') {
direction = leavingView.prevId === enteringView.id ? 'back' : 'none';
} else {
direction = direction || 'back';
leavingView.mount = false;
}
}
if (direction === 'back' || action === 'REPLACE') {
leavingView.mount = false;
this.removeOrphanedViews(enteringView, enteringViewStack);
}
} 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;
@@ -132,36 +158,56 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
if (enteringEl) {
// Don't animate from an empty view
const navDirection = leavingEl && leavingEl.innerHTML === '' ? undefined : direction === 'none' ? undefined : direction;
this.transitionView(
const shouldGoBack = !!enteringView.prevId || !!this.locationHistory.previous();
this.commitView(
enteringEl!,
leavingEl!,
viewStack.routerOutlet,
navDirection);
navDirection,
shouldGoBack);
} else if (leavingEl) {
leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true');
}
// Warn if an IonPage was not eventually rendered in Dev Mode
if (isDevMode()) {
if (enteringView.routeData.match!.url !== location.pathname) {
setTimeout(() => {
const { view } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
if (view!.routeData.match!.url !== location.pathname) {
console.warn('No IonPage was found to render. Make sure you wrap your page with an IonPage component.');
}
}, 100);
}
}
}
});
}
removeOrphanedViews(view: ViewItem, viewStack: ViewStack) {
// Note: This technique is a bit wonky for views that reference each other and get into a circular loop.
// It can still remove a view that probably shouldn't be.
const viewsToRemove = viewStack.views.filter(v => v.prevId === view.id);
viewsToRemove.forEach(v => {
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();
// 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;
}
v.mount = false;
});
}
async setupIonRouter(id: string, children: any, routerOutlet: HTMLIonRouterOutletElement) {
setupIonRouter(id: string, children: any, routerOutlet: HTMLIonRouterOutletElement) {
const views: ViewItem[] = [];
let activeId: string | undefined;
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
@@ -169,7 +215,7 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
views.push(createViewItem(child, this.props.history.location));
});
await this.registerViewStack(id, activeId, views, routerOutlet, this.props.location);
this.registerViewStack(id, activeId, views, routerOutlet, this.props.location);
function createViewItem(child: React.ReactElement<any>, location: HistoryLocation) {
const viewId = generateId();
@@ -200,29 +246,61 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
}
}
async registerViewStack(stack: string, activeId: string | undefined, stackItems: ViewItem[], routerOutlet: HTMLIonRouterOutletElement, _location: HistoryLocation) {
return new Promise(resolve => {
this.setState(prevState => {
const prevViewStacks = Object.assign(new ViewStacks(), prevState.viewStacks);
const newStack: ViewStack = {
id: stack,
views: stackItems,
routerOutlet
};
if (activeId) {
this.activeIonPageId = activeId;
}
prevViewStacks.set(stack, newStack);
return {
viewStacks: prevViewStacks
};
}, () => {
resolve();
});
registerViewStack(stack: string, activeId: string | undefined, stackItems: ViewItem[], routerOutlet: HTMLIonRouterOutletElement, _location: HistoryLocation) {
this.setState(prevState => {
const prevViewStacks = Object.assign(new ViewStacks(), prevState.viewStacks);
const newStack: ViewStack = {
id: stack,
views: stackItems,
routerOutlet
};
if (activeId) {
this.activeIonPageId = activeId;
}
prevViewStacks.set(stack, newStack);
return {
viewStacks: prevViewStacks
};
}, () => {
this.setupRouterOutlet(routerOutlet);
});
}
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
const waitUntilReady = async () => {
if (routerOutlet.componentOnReady) {
routerOutlet.dispatchEvent(new Event('routerOutletReady'));
return;
} else {
setTimeout(() => {
waitUntilReady();
}, 0);
}
};
await waitUntilReady();
const canStart = () => {
const config = getConfig();
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
if (swipeEnabled) {
const { view } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
return !!(view && view.prevId);
} else {
return false;
}
};
const onStart = () => {
this.navigateBack();
};
routerOutlet.swipeHandler = {
canStart,
onStart,
onEnd: _shouldContinue => true
};
}
removeViewStack(stack: string) {
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
viewStacks.delete(stack);
@@ -233,7 +311,6 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
syncView(page: HTMLElement, viewId: string) {
this.setState(state => {
const viewStacks = Object.assign(new ViewStacks(), state.viewStacks);
const { view } = viewStacks.findViewInfoById(viewId);
@@ -249,21 +326,7 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
});
}
transitionView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet?: HTMLIonRouterOutletElement, direction?: NavDirection) {
/**
* 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);
} else {
setTimeout(() => {
this.transitionView(enteringEl, leavingEl, ionRouterOutlet, direction);
}, 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;
@@ -273,7 +336,7 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
deepWait: true,
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack: direction === 'forward',
showGoBack,
progressAnimation: false
});
@@ -293,15 +356,41 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
}
}
navigateBack(defaultHref?: string) {
const { view: activeIonPage } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
if (activeIonPage) {
const { view: enteringView } = this.state.viewStacks.findViewInfoById(activeIonPage.prevId);
if (enteringView) {
const lastLocation = this.locationHistory.findLastLocationByUrl(enteringView.routeData.match!.url);
if (lastLocation) {
this.handleNavigate('replace', lastLocation.pathname + lastLocation.search, 'back');
} else {
this.handleNavigate('replace', enteringView.routeData.match!.url, 'back');
}
} else {
const currentLocation = this.locationHistory.previous();
if (currentLocation) {
this.handleNavigate('replace', currentLocation.pathname + currentLocation.search, 'back');
} else {
if (defaultHref) {
this.handleNavigate('replace', defaultHref, 'back');
}
}
}
} else {
if (defaultHref) {
this.handleNavigate('replace', defaultHref, 'back');
}
}
}
render() {
return (
<RouteManagerContext.Provider value={this.state}>
<NavManager
{...this.props}
onNavigateBack={this.navigateBack}
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)}
>
{this.props.children}
</NavManager>

View File

@@ -11,7 +11,11 @@ interface StackManagerProps {
id?: string;
}
export class StackManager extends React.Component<StackManagerProps, {}> {
interface StackManagerState {
routerOutletReady: boolean;
}
export class StackManager extends React.Component<StackManagerProps, StackManagerState> {
routerOutletEl: React.RefObject<HTMLIonRouterOutletElement> = React.createRef();
context!: React.ContextType<typeof RouteManagerContext>;
id: string;
@@ -21,10 +25,18 @@ export class StackManager extends React.Component<StackManagerProps, {}> {
this.id = this.props.id || generateId();
this.handleViewSync = this.handleViewSync.bind(this);
this.handleHideView = this.handleHideView.bind(this);
this.state = {
routerOutletReady: false
};
}
componentDidMount() {
this.context.setupIonRouter(this.id, this.props.children, this.routerOutletEl.current!);
this.routerOutletEl.current!.addEventListener('routerOutletReady', () => {
this.setState({
routerOutletReady: true
});
});
}
componentWillUnmount() {
@@ -51,8 +63,9 @@ export class StackManager extends React.Component<StackManagerProps, {}> {
const viewStack = context.viewStacks.get(this.id);
const views = (viewStack || { views: [] }).views.filter(x => x.show);
const ionRouterOutlet = React.Children.only(this.props.children) as React.ReactElement;
const { routerOutletReady } = this.state;
const childElements = views.map(view => {
const childElements = routerOutletReady ? views.map(view => {
return (
<ViewTransitionManager
id={view.id}
@@ -68,7 +81,7 @@ export class StackManager extends React.Component<StackManagerProps, {}> {
</View>
</ViewTransitionManager>
);
});
}) : <div></div>;
const elementProps: any = {
ref: this.routerOutletEl

View File

@@ -12,9 +12,30 @@ export class LocationHistory {
}
}
findLastLocation(url: string) {
const reversedLocations = [...this.locationHistory].reverse();
const last = reversedLocations.find(x => x.pathname.toLowerCase() === url.toLowerCase());
return last;
pop() {
this.locationHistory.pop();
}
replace(location: HistoryLocation) {
this.locationHistory.pop();
this.locationHistory.push(location);
}
findLastLocationByUrl(url: string) {
for (let i = this.locationHistory.length - 1; i >= 0; i--) {
const location = this.locationHistory[i];
if (location.pathname.toLocaleLowerCase() === url.toLocaleLowerCase()) {
return location;
}
}
return undefined;
}
previous() {
return this.locationHistory[this.locationHistory.length - 2];
}
current() {
return this.locationHistory[this.locationHistory.length - 1];
}
}

View File

@@ -19,7 +19,7 @@
"no-invalid-template-strings": true,
"ban-export-const-enum": true,
"only-arrow-functions": false,
"strict-boolean-conditions": [true, "allow-null-union", "allow-undefined-union", "allow-boolean-or-undefined", "allow-string"],
"strict-boolean-conditions": [false],
"jsx-key": false,
"jsx-self-close": false,
"jsx-curly-spacing": [true, "never"],

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

@@ -23,6 +23,7 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
return class extends React.Component<Props> {
overlay?: OverlayType;
isUnmounted = false;
constructor(props: Props) {
super(props);
@@ -40,6 +41,7 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
}
componentWillUnmount() {
this.isUnmounted = true;
if (this.overlay) { this.overlay.dismiss(); }
}
@@ -54,13 +56,17 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
async present(prevProps?: Props) {
const { isOpen, onDidDismiss, ...cProps } = this.props;
const overlay = this.overlay = await controller.create({
this.overlay = await controller.create({
...cProps as any
});
attachProps(overlay, {
attachProps(this.overlay, {
[dismissEventName]: onDidDismiss
}, prevProps);
await overlay.present();
// Check isOpen again since the value could have changed during the async call to controller.create
// It's also possible for the component to have become unmounted.
if (this.props.isOpen === true && this.isUnmounted === false) {
await this.overlay.present();
}
}
render(): null {

View File

@@ -24,7 +24,7 @@ export { IonBackButton } from './navigation/IonBackButton';
export { IonRouterOutlet } from './IonRouterOutlet';
// Utils
export { isPlatform, getPlatforms } from './utils';
export { isPlatform, getPlatforms, getConfig } from './utils';
export { RouterDirection } from './hrefprops';
// Icons that are used by internal components

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

@@ -1,4 +1,4 @@
import { Platforms, getPlatforms as getPlatformsCore, isPlatform as isPlatformCore } from '@ionic/core';
import { Config as CoreConfig, Platforms, getPlatforms as getPlatformsCore, isPlatform as isPlatformCore } from '@ionic/core';
import React from 'react';
import { IonicReactProps } from '../IonicReactProps';
@@ -24,3 +24,13 @@ export const isPlatform = (platform: Platforms) => {
export const getPlatforms = () => {
return getPlatformsCore(window);
};
export const getConfig = (): CoreConfig | null => {
if (typeof (window as any) !== 'undefined') {
const Ionic = (window as any).Ionic;
if (Ionic && Ionic.config) {
return Ionic.config;
}
}
return null;
};

View File

@@ -22,52 +22,46 @@ export const IonLifeCycleContext = /*@__PURE__*/React.createContext<IonLifeCycle
ionViewDidLeave: () => { return; },
});
type LifeCycleCallback = () => void;
export const DefaultIonLifeCycleContext = class implements IonLifeCycleContextInterface {
ionViewWillEnterCallback?: () => void;
ionViewDidEnterCallback?: () => void;
ionViewWillLeaveCallback?: () => void;
ionViewDidLeaveCallback?: () => void;
ionViewWillEnterCallbacks: LifeCycleCallback[] = [];
ionViewDidEnterCallbacks: LifeCycleCallback[] = [];
ionViewWillLeaveCallbacks: LifeCycleCallback[] = [];
ionViewDidLeaveCallbacks: LifeCycleCallback[] = [];
componentCanBeDestroyedCallback?: () => void;
onIonViewWillEnter(callback: () => void) {
this.ionViewWillEnterCallback = callback;
onIonViewWillEnter(callback: LifeCycleCallback) {
this.ionViewWillEnterCallbacks.push(callback);
}
ionViewWillEnter() {
if (this.ionViewWillEnterCallback) {
this.ionViewWillEnterCallback();
}
this.ionViewWillEnterCallbacks.forEach(cb => cb());
}
onIonViewDidEnter(callback: () => void) {
this.ionViewDidEnterCallback = callback;
onIonViewDidEnter(callback: LifeCycleCallback) {
this.ionViewDidEnterCallbacks.push(callback);
}
ionViewDidEnter() {
if (this.ionViewDidEnterCallback) {
this.ionViewDidEnterCallback();
}
this.ionViewDidEnterCallbacks.forEach(cb => cb());
}
onIonViewWillLeave(callback: () => void) {
this.ionViewWillLeaveCallback = callback;
onIonViewWillLeave(callback: LifeCycleCallback) {
this.ionViewWillLeaveCallbacks.push(callback);
}
ionViewWillLeave() {
if (this.ionViewWillLeaveCallback) {
this.ionViewWillLeaveCallback();
}
this.ionViewWillLeaveCallbacks.forEach(cb => cb());
}
onIonViewDidLeave(callback: () => void) {
this.ionViewDidLeaveCallback = callback;
onIonViewDidLeave(callback: LifeCycleCallback) {
this.ionViewDidLeaveCallbacks.push(callback);
}
ionViewDidLeave() {
if (this.ionViewDidLeaveCallback) {
this.ionViewDidLeaveCallback();
}
this.ionViewDidLeaveCallbacks.forEach(cb => cb());
this.componentCanBeDestroyed();
}

View File

@@ -1,23 +1,31 @@
import { useContext } from 'react';
import { useContext, useEffect } from 'react';
import { IonLifeCycleContext } from '../contexts/IonLifeCycleContext';
export const useIonViewWillEnter = (callback: () => void) => {
const value = useContext(IonLifeCycleContext);
value.onIonViewWillEnter(callback);
const context = useContext(IonLifeCycleContext);
useEffect(() => {
context.onIonViewWillEnter(callback);
}, []);
};
export const useIonViewDidEnter = (callback: () => void) => {
const value = useContext(IonLifeCycleContext);
value.onIonViewDidEnter(callback);
const context = useContext(IonLifeCycleContext);
useEffect(() => {
context.onIonViewDidEnter(callback);
}, []);
};
export const useIonViewWillLeave = (callback: () => void) => {
const value = useContext(IonLifeCycleContext);
value.onIonViewWillLeave(callback);
const context = useContext(IonLifeCycleContext);
useEffect(() => {
context.onIonViewWillLeave(callback);
}, []);
};
export const useIonViewDidLeave = (callback: () => void) => {
const value = useContext(IonLifeCycleContext);
value.onIonViewDidLeave(callback);
const context = useContext(IonLifeCycleContext);
useEffect(() => {
context.onIonViewDidLeave(callback);
}, []);
};