mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
51 Commits
v4.11.3
...
gesture-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
926b119231 | ||
|
|
256c7de469 | ||
|
|
b7b5b94cce | ||
|
|
5e1e883585 | ||
|
|
21484f1f3a | ||
|
|
b28cf02ef3 | ||
|
|
bef0f53d0d | ||
|
|
1a8b7a4559 | ||
|
|
b23c759456 | ||
|
|
1ab7066aa0 | ||
|
|
48a766246d | ||
|
|
fce3e24600 | ||
|
|
be0ab5c2ff | ||
|
|
c5be8831a2 | ||
|
|
edf64ef121 | ||
|
|
504051d709 | ||
|
|
90be7ec4b6 | ||
|
|
1af1e2a14f | ||
|
|
2cc4244c05 | ||
|
|
74e40cdc35 | ||
|
|
13b323f25d | ||
|
|
3e14a57f84 | ||
|
|
9864c17c1c | ||
|
|
e2ed0e9e87 | ||
|
|
a4b2de5730 | ||
|
|
f3ac682010 | ||
|
|
2f5d823748 | ||
|
|
e5e9dd5dfe | ||
|
|
ad747b05f1 | ||
|
|
3b52074a10 | ||
|
|
9eac7266a0 | ||
|
|
12a4c974b4 | ||
|
|
a3666ddf0c | ||
|
|
cace1b357e | ||
|
|
ab9d3b2696 | ||
|
|
60aa027903 | ||
|
|
7bd4412889 | ||
|
|
96a5e600e5 | ||
|
|
33cf08bf0e | ||
|
|
7e6e81db55 | ||
|
|
f2d18a2346 | ||
|
|
77d18c7172 | ||
|
|
ec43b8c9b8 | ||
|
|
b4938db5f9 | ||
|
|
9c7beff910 | ||
|
|
16815b3cc8 | ||
|
|
e23d91c6e7 | ||
|
|
1f2b161ccd | ||
|
|
67737bbb54 | ||
|
|
29f8178794 | ||
|
|
d80f45516d |
@@ -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 })
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
31
CHANGELOG.md
31
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
21
angular/src/providers/gesture-controller.ts
Normal file
21
angular/src/providers/gesture-controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
12
core/api.txt
12
core/api.txt
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
1
core/src/components.d.ts
vendored
1
core/src/components.d.ts
vendored
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
/**
|
||||
* This is an autogenerated file created by the Stencil compiler.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -197,3 +197,8 @@
|
||||
.alert-checkbox-inner {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea.alert-input {
|
||||
min-height: $alert-input-min-height;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -131,6 +131,11 @@
|
||||
placeholder: 'Placeholder 3',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
placeholder: 'Placeholder 4',
|
||||
value: 'Textarea hello'
|
||||
},
|
||||
{
|
||||
name: 'name3',
|
||||
type: 'text',
|
||||
|
||||
0
core/src/components/alert/test/preview/index.html
Normal file
0
core/src/components/alert/test/preview/index.html
Normal 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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
10
core/src/components/content/test/fixed/e2e.ts
Normal file
10
core/src/components/content/test/fixed/e2e.ts
Normal 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();
|
||||
});
|
||||
44
core/src/components/content/test/fixed/index.html
Normal file
44
core/src/components/content/test/fixed/index.html
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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. |
|
||||
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
4
core/src/interface.d.ts
vendored
4
core/src/interface.d.ts
vendored
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -92,8 +92,8 @@ export const createPointerEvents = (
|
||||
stopMouse();
|
||||
};
|
||||
|
||||
const enable = (enable = true) => {
|
||||
if (!enable) {
|
||||
const enable = (isEnabled = true) => {
|
||||
if (!isEnabled) {
|
||||
if (rmTouchStart) {
|
||||
rmTouchStart();
|
||||
}
|
||||
|
||||
91
core/src/utils/gesture/recognizers/press.ts
Normal file
91
core/src/utils/gesture/recognizers/press.ts
Normal 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,
|
||||
});
|
||||
};
|
||||
77
core/src/utils/gesture/recognizers/tests/press/e2e.ts
Normal file
77
core/src/utils/gesture/recognizers/tests/press/e2e.ts
Normal 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))
|
||||
};
|
||||
};
|
||||
67
core/src/utils/gesture/recognizers/tests/press/index.html
Normal file
67
core/src/utils/gesture/recognizers/tests/press/index.html
Normal 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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}, []);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user