mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
32 Commits
v4.11.4
...
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 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,6 +3,8 @@ 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';
|
||||
|
||||
@@ -385,6 +385,18 @@ describe('cubic-bezier conversion', () => {
|
||||
|
||||
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([]);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
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>
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -6,7 +6,7 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,11 +22,13 @@ 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),
|
||||
@@ -33,6 +36,14 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
||||
removeViewStack: this.removeViewStack.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) {
|
||||
@@ -66,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
|
||||
@@ -99,21 +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;
|
||||
}
|
||||
} else if (direction === 'back' || action === 'REPLACE') {
|
||||
}
|
||||
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;
|
||||
@@ -139,21 +158,36 @@ 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,
|
||||
!!enteringView.prevId);
|
||||
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 => {
|
||||
// Don't remove if view is currently active
|
||||
@@ -173,7 +207,7 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -181,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();
|
||||
@@ -212,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);
|
||||
@@ -245,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);
|
||||
|
||||
@@ -261,20 +326,6 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
||||
});
|
||||
}
|
||||
|
||||
transitionView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet: HTMLIonRouterOutletElement | undefined, direction: NavDirection | undefined, showGoBack: boolean) {
|
||||
/**
|
||||
* Super hacky workaround to make sure ionRouterOutlet is available
|
||||
* since transitionView might be called before IonRouterOutlet is fully mounted
|
||||
*/
|
||||
if (ionRouterOutlet && ionRouterOutlet.componentOnReady) {
|
||||
this.commitView(enteringEl, leavingEl, ionRouterOutlet, direction, showGoBack);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.transitionView(enteringEl, leavingEl, ionRouterOutlet, direction, showGoBack);
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOuter: HTMLIonRouterOutletElement, direction?: NavDirection, showGoBack?: boolean) {
|
||||
|
||||
if (enteringEl === leavingEl) {
|
||||
@@ -305,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"],
|
||||
|
||||
@@ -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,18 +56,16 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
|
||||
|
||||
async present(prevProps?: Props) {
|
||||
const { isOpen, onDidDismiss, ...cProps } = this.props;
|
||||
let overlay = this.overlay;
|
||||
if (!overlay) {
|
||||
overlay = this.overlay = await controller.create({
|
||||
...cProps as any
|
||||
});
|
||||
}
|
||||
attachProps(overlay, {
|
||||
this.overlay = await controller.create({
|
||||
...cProps as any
|
||||
});
|
||||
attachProps(this.overlay, {
|
||||
[dismissEventName]: onDidDismiss
|
||||
}, prevProps);
|
||||
// Check isOpen again since the value could of changed during the async call to controller.create
|
||||
if (this.props.isOpen === true) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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