Skip to content

Commit

Permalink
fix(all): component reusage (#18963)
Browse files Browse the repository at this point in the history
Use new stencil APIs to allow ionic elements to be reused once removed from the DOM.

fixes #18843
fixes #17344
fixes #16453
fixes #15879
fixes #15788
fixes #15484
fixes #17890
fixes #16364
  • Loading branch information
manucorporat authored Aug 27, 2019
1 parent a65d897 commit 48a2763
Show file tree
Hide file tree
Showing 33 changed files with 411 additions and 368 deletions.
37 changes: 19 additions & 18 deletions core/src/components/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ComponentInterface, Element, Host, h } from '@stencil/core';
import { Build, Component, ComponentInterface, Element, Host, h } from '@stencil/core';

import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
Expand All @@ -14,23 +14,24 @@ export class App implements ComponentInterface {
@Element() el!: HTMLElement;

componentDidLoad() {
rIC(() => {
const isHybrid = isPlatform(window, 'hybrid');
if (!config.getBoolean('_testing')) {
import('../../utils/tap-click').then(module => module.startTapClick(config));
}
if (config.getBoolean('statusTap', isHybrid)) {
import('../../utils/status-tap').then(module => module.startStatusTap());
}
if (config.getBoolean('inputShims', needInputShims())) {
import('../../utils/input-shims/input-shims').then(module => module.startInputShims(config));
}
if (config.getBoolean('hardwareBackButton', isHybrid)) {
import('../../utils/hardware-back-button').then(module => module.startHardwareBackButton());
}
import('../../utils/focus-visible').then(module => module.startFocusVisible());

});
if (Build.isBrowser) {
rIC(() => {
const isHybrid = isPlatform(window, 'hybrid');
if (!config.getBoolean('_testing')) {
import('../../utils/tap-click').then(module => module.startTapClick(config));
}
if (config.getBoolean('statusTap', isHybrid)) {
import('../../utils/status-tap').then(module => module.startStatusTap());
}
if (config.getBoolean('inputShims', needInputShims())) {
import('../../utils/input-shims/input-shims').then(module => module.startInputShims(config));
}
if (config.getBoolean('hardwareBackButton', isHybrid)) {
import('../../utils/hardware-back-button').then(module => module.startHardwareBackButton());
}
import('../../utils/focus-visible').then(module => module.startFocusVisible());
});
}
}

render() {
Expand Down
6 changes: 3 additions & 3 deletions core/src/components/backdrop/backdrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ export class Backdrop implements ComponentInterface {
*/
@Event() ionBackdropTap!: EventEmitter<void>;

componentDidLoad() {
connectedCallback() {
if (this.stopPropagation) {
this.blocker.block();
}
}

componentDidUnload() {
this.blocker.destroy();
disconnectedCallback() {
this.blocker.unblock();
}

@Listen('touchstart', { passive: false, capture: true })
Expand Down
27 changes: 14 additions & 13 deletions core/src/components/content/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class Content implements ComponentInterface {
private cTop = -1;
private cBottom = -1;
private scrollEl!: HTMLElement;
private mode = getIonMode(this);

// Detail is used in a hot loop in the scroll event, by allocating it here
// V8 will be able to inline any read/write to it since it's a monomorphic class.
Expand Down Expand Up @@ -102,21 +103,14 @@ export class Content implements ComponentInterface {
*/
@Event() ionScrollEnd!: EventEmitter<ScrollBaseDetail>;

componentWillLoad() {
if (this.forceOverscroll === undefined) {
const mode = getIonMode(this);
this.forceOverscroll = mode === 'ios' && isPlatform(window, 'mobile');
}
disconnectedCallback() {
this.onScrollEnd();
}

componentDidLoad() {
this.resize();
}

componentDidUnload() {
this.onScrollEnd();
}

@Listen('click', { capture: true })
onClick(ev: Event) {
if (this.isScrolling) {
Expand All @@ -125,6 +119,13 @@ export class Content implements ComponentInterface {
}
}

private shouldForceOverscroll() {
const { forceOverscroll, mode } = this;
return forceOverscroll === undefined
? mode === 'ios' && isPlatform(window, 'mobile')
: forceOverscroll;
}

private resize() {
if (this.fullscreen) {
readTask(this.readDimensions.bind(this));
Expand Down Expand Up @@ -299,9 +300,9 @@ export class Content implements ComponentInterface {
}

render() {
const { scrollX, scrollY } = this;
const mode = getIonMode(this);
const { scrollX, scrollY, forceOverscroll } = this;

const forceOverscroll = this.shouldForceOverscroll();
const transitionShadow = (mode === 'ios' && config.getBoolean('experimentalTransitionShadow', true));

this.resize();
Expand All @@ -312,7 +313,7 @@ export class Content implements ComponentInterface {
...createColorClasses(this.color),
[mode]: true,
'content-sizing': hostContext('ion-popover', this.el),
'overscroll': !!this.forceOverscroll,
'overscroll': forceOverscroll,
}}
style={{
'--offset-top': `${this.cTop}px`,
Expand All @@ -324,7 +325,7 @@ export class Content implements ComponentInterface {
'inner-scroll': true,
'scroll-x': scrollX,
'scroll-y': scrollY,
'overscroll': (scrollX || scrollY) && !!forceOverscroll
'overscroll': (scrollX || scrollY) && forceOverscroll
}}
ref={el => this.scrollEl = el!}
onScroll={ev => this.onScroll(ev)}
Expand Down
10 changes: 6 additions & 4 deletions core/src/components/infinite-scroll/infinite-scroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@ export class InfiniteScroll implements ComponentInterface {
*/
@Event() ionInfinite!: EventEmitter<void>;

async componentDidLoad() {
async connectedCallback() {
const contentEl = this.el.closest('ion-content');
if (contentEl) {
this.scrollEl = await contentEl.getScrollElement();
if (!contentEl) {
console.error('<ion-infinite-scroll> must be used inside an <ion-content>');
return;
}
this.scrollEl = await contentEl.getScrollElement();
this.thresholdChanged();
this.disabledChanged();
if (this.position === 'top') {
Expand All @@ -92,7 +94,7 @@ export class InfiniteScroll implements ComponentInterface {
}
}

componentDidUnload() {
disconnectedCallback() {
this.enableScrollEvents(false);
this.scrollEl = undefined;
}
Expand Down
39 changes: 23 additions & 16 deletions core/src/components/input/input.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h } from '@stencil/core';
import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h } from '@stencil/core';

import { getIonMode } from '../../global/ionic-global';
import { Color, InputChangeEventDetail, StyleEventDetail, TextFieldTypes } from '../../interface';
Expand Down Expand Up @@ -66,7 +66,7 @@ export class Input implements ComponentInterface {
/**
* If `true`, the value will be cleared after focus upon edit. Defaults to `true` when `type` is `"password"`, `false` for all other types.
*/
@Prop({ mutable: true }) clearOnEdit?: boolean;
@Prop() clearOnEdit?: boolean;

/**
* Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke.
Expand Down Expand Up @@ -218,22 +218,22 @@ export class Input implements ComponentInterface {
*/
@Event() ionStyle!: EventEmitter<StyleEventDetail>;

componentWillLoad() {
// By default, password inputs clear after focus when they have content
if (this.clearOnEdit === undefined && this.type === 'password') {
this.clearOnEdit = true;
}
connectedCallback() {
this.emitStyle();
}

componentDidLoad() {
this.debounceChanged();

this.ionInputDidLoad.emit();
if (Build.isBrowser) {
this.el.dispatchEvent(new CustomEvent('ionInputDidLoad', {
detail: this.el
}));
}
}

componentDidUnload() {
this.ionInputDidUnload.emit();
disconnectedCallback() {
if (Build.isBrowser) {
document.dispatchEvent(new CustomEvent('ionInputDidUnload', {
detail: this.el
}));
}
}

/**
Expand All @@ -255,6 +255,13 @@ export class Input implements ComponentInterface {
return Promise.resolve(this.nativeInput!);
}

private shouldClearOnEdit() {
const { type, clearOnEdit } = this;
return (clearOnEdit === undefined)
? type === 'password'
: clearOnEdit;
}

private getValue(): string {
return this.value || '';
}
Expand Down Expand Up @@ -295,7 +302,7 @@ export class Input implements ComponentInterface {
}

private onKeydown = () => {
if (this.clearOnEdit) {
if (this.shouldClearOnEdit()) {
// Did the input value change after it was blurred and edited?
if (this.didBlurAfterEdit && this.hasValue()) {
// Clear the input
Expand Down Expand Up @@ -327,7 +334,7 @@ export class Input implements ComponentInterface {

private focusChanged() {
// If clearOnEdit is enabled and the input blurred but has a value, set a flag
if (this.clearOnEdit && !this.hasFocus && this.hasValue()) {
if (!this.hasFocus && this.shouldClearOnEdit() && this.hasValue()) {
this.didBlurAfterEdit = true;
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/src/components/item-sliding/item-sliding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class ItemSliding implements ComponentInterface {
*/
@Event() ionDrag!: EventEmitter;

async componentDidLoad() {
async connectedCallback() {
this.item = this.el.querySelector('ion-item');
await this.updateOptions();

Expand All @@ -81,7 +81,7 @@ export class ItemSliding implements ComponentInterface {
this.disabledChanged();
}

componentDidUnload() {
disconnectedCallback() {
if (this.gesture) {
this.gesture.destroy();
this.gesture = undefined;
Expand Down
7 changes: 4 additions & 3 deletions core/src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export class Menu implements ComponentInterface, MenuI {
*/
@Event() protected ionMenuChange!: EventEmitter<MenuChangeEventDetail>;

async componentWillLoad() {
async connectedCallback() {
if (this.type === undefined) {
this.type = config.get('menuType', this.mode === 'ios' ? 'reveal' : 'overlay');
}
Expand Down Expand Up @@ -182,11 +182,12 @@ export class Menu implements ComponentInterface, MenuI {
this.updateState();
}

componentDidLoad() {
async componentDidLoad() {
this.ionMenuChange.emit({ disabled: this.disabled, open: this._isOpen });
this.updateState();
}

componentDidUnload() {
disconnectedCallback() {
this.blocker.destroy();
menuController._unregister(this);
if (this.animation) {
Expand Down
26 changes: 13 additions & 13 deletions core/src/components/picker-column/picker-column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class PickerColumnCmp implements ComponentInterface {
this.refresh();
}

componentWillLoad() {
async connectedCallback() {
let pickerRotateFactor = 0;
let pickerScaleFactor = 0.81;

Expand All @@ -60,16 +60,6 @@ export class PickerColumnCmp implements ComponentInterface {

this.rotateFactor = pickerRotateFactor;
this.scaleFactor = pickerScaleFactor;
}

async componentDidLoad() {
// get the height of one option
const colEl = this.optsEl;
if (colEl) {
this.optHeight = (colEl.firstElementChild ? colEl.firstElementChild.clientHeight : 0);
}

this.refresh();

this.gesture = (await import('../../utils/gesture')).createGesture({
el: this.el,
Expand All @@ -81,14 +71,24 @@ export class PickerColumnCmp implements ComponentInterface {
onEnd: ev => this.onEnd(ev),
});
this.gesture.setDisabled(false);

this.tmrId = setTimeout(() => {
this.noAnimate = false;
this.refresh(true);
}, 250);
}

componentDidUnload() {
componentDidLoad() {
const colEl = this.optsEl;
if (colEl) {
// DOM READ
// We perfom a DOM read over a rendered item, this needs to happen after the first render
this.optHeight = (colEl.firstElementChild ? colEl.firstElementChild.clientHeight : 0);
}

this.refresh();
}

disconnectedCallback() {
cancelAnimationFrame(this.rafId);
clearTimeout(this.tmrId);
if (this.gesture) {
Expand Down
Loading

0 comments on commit 48a2763

Please sign in to comment.