mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 10:41:13 +08:00
fix(all): component reusage (#18963)
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
This commit is contained in:
@ -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 { config } from '../../global/config';
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
@ -14,6 +14,7 @@ export class App implements ComponentInterface {
|
|||||||
@Element() el!: HTMLElement;
|
@Element() el!: HTMLElement;
|
||||||
|
|
||||||
componentDidLoad() {
|
componentDidLoad() {
|
||||||
|
if (Build.isBrowser) {
|
||||||
rIC(() => {
|
rIC(() => {
|
||||||
const isHybrid = isPlatform(window, 'hybrid');
|
const isHybrid = isPlatform(window, 'hybrid');
|
||||||
if (!config.getBoolean('_testing')) {
|
if (!config.getBoolean('_testing')) {
|
||||||
@ -29,9 +30,9 @@ export class App implements ComponentInterface {
|
|||||||
import('../../utils/hardware-back-button').then(module => module.startHardwareBackButton());
|
import('../../utils/hardware-back-button').then(module => module.startHardwareBackButton());
|
||||||
}
|
}
|
||||||
import('../../utils/focus-visible').then(module => module.startFocusVisible());
|
import('../../utils/focus-visible').then(module => module.startFocusVisible());
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
|
@ -39,14 +39,14 @@ export class Backdrop implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionBackdropTap!: EventEmitter<void>;
|
@Event() ionBackdropTap!: EventEmitter<void>;
|
||||||
|
|
||||||
componentDidLoad() {
|
connectedCallback() {
|
||||||
if (this.stopPropagation) {
|
if (this.stopPropagation) {
|
||||||
this.blocker.block();
|
this.blocker.block();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
this.blocker.destroy();
|
this.blocker.unblock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Listen('touchstart', { passive: false, capture: true })
|
@Listen('touchstart', { passive: false, capture: true })
|
||||||
|
@ -24,6 +24,7 @@ export class Content implements ComponentInterface {
|
|||||||
private cTop = -1;
|
private cTop = -1;
|
||||||
private cBottom = -1;
|
private cBottom = -1;
|
||||||
private scrollEl!: HTMLElement;
|
private scrollEl!: HTMLElement;
|
||||||
|
private mode = getIonMode(this);
|
||||||
|
|
||||||
// Detail is used in a hot loop in the scroll event, by allocating it here
|
// 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.
|
// V8 will be able to inline any read/write to it since it's a monomorphic class.
|
||||||
@ -102,21 +103,14 @@ export class Content implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionScrollEnd!: EventEmitter<ScrollBaseDetail>;
|
@Event() ionScrollEnd!: EventEmitter<ScrollBaseDetail>;
|
||||||
|
|
||||||
componentWillLoad() {
|
disconnectedCallback() {
|
||||||
if (this.forceOverscroll === undefined) {
|
this.onScrollEnd();
|
||||||
const mode = getIonMode(this);
|
|
||||||
this.forceOverscroll = mode === 'ios' && isPlatform(window, 'mobile');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidLoad() {
|
componentDidLoad() {
|
||||||
this.resize();
|
this.resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
|
||||||
this.onScrollEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Listen('click', { capture: true })
|
@Listen('click', { capture: true })
|
||||||
onClick(ev: Event) {
|
onClick(ev: Event) {
|
||||||
if (this.isScrolling) {
|
if (this.isScrolling) {
|
||||||
@ -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() {
|
private resize() {
|
||||||
if (this.fullscreen) {
|
if (this.fullscreen) {
|
||||||
readTask(this.readDimensions.bind(this));
|
readTask(this.readDimensions.bind(this));
|
||||||
@ -299,9 +300,9 @@ export class Content implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { scrollX, scrollY } = this;
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
const { scrollX, scrollY, forceOverscroll } = this;
|
const forceOverscroll = this.shouldForceOverscroll();
|
||||||
|
|
||||||
const transitionShadow = (mode === 'ios' && config.getBoolean('experimentalTransitionShadow', true));
|
const transitionShadow = (mode === 'ios' && config.getBoolean('experimentalTransitionShadow', true));
|
||||||
|
|
||||||
this.resize();
|
this.resize();
|
||||||
@ -312,7 +313,7 @@ export class Content implements ComponentInterface {
|
|||||||
...createColorClasses(this.color),
|
...createColorClasses(this.color),
|
||||||
[mode]: true,
|
[mode]: true,
|
||||||
'content-sizing': hostContext('ion-popover', this.el),
|
'content-sizing': hostContext('ion-popover', this.el),
|
||||||
'overscroll': !!this.forceOverscroll,
|
'overscroll': forceOverscroll,
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
'--offset-top': `${this.cTop}px`,
|
'--offset-top': `${this.cTop}px`,
|
||||||
@ -324,7 +325,7 @@ export class Content implements ComponentInterface {
|
|||||||
'inner-scroll': true,
|
'inner-scroll': true,
|
||||||
'scroll-x': scrollX,
|
'scroll-x': scrollX,
|
||||||
'scroll-y': scrollY,
|
'scroll-y': scrollY,
|
||||||
'overscroll': (scrollX || scrollY) && !!forceOverscroll
|
'overscroll': (scrollX || scrollY) && forceOverscroll
|
||||||
}}
|
}}
|
||||||
ref={el => this.scrollEl = el!}
|
ref={el => this.scrollEl = el!}
|
||||||
onScroll={ev => this.onScroll(ev)}
|
onScroll={ev => this.onScroll(ev)}
|
||||||
|
@ -76,11 +76,13 @@ export class InfiniteScroll implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionInfinite!: EventEmitter<void>;
|
@Event() ionInfinite!: EventEmitter<void>;
|
||||||
|
|
||||||
async componentDidLoad() {
|
async connectedCallback() {
|
||||||
const contentEl = this.el.closest('ion-content');
|
const contentEl = this.el.closest('ion-content');
|
||||||
if (contentEl) {
|
if (!contentEl) {
|
||||||
this.scrollEl = await contentEl.getScrollElement();
|
console.error('<ion-infinite-scroll> must be used inside an <ion-content>');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
this.scrollEl = await contentEl.getScrollElement();
|
||||||
this.thresholdChanged();
|
this.thresholdChanged();
|
||||||
this.disabledChanged();
|
this.disabledChanged();
|
||||||
if (this.position === 'top') {
|
if (this.position === 'top') {
|
||||||
@ -92,7 +94,7 @@ export class InfiniteScroll implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
this.enableScrollEvents(false);
|
this.enableScrollEvents(false);
|
||||||
this.scrollEl = undefined;
|
this.scrollEl = undefined;
|
||||||
}
|
}
|
||||||
|
@ -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 { getIonMode } from '../../global/ionic-global';
|
||||||
import { Color, InputChangeEventDetail, StyleEventDetail, TextFieldTypes } from '../../interface';
|
import { Color, InputChangeEventDetail, StyleEventDetail, TextFieldTypes } from '../../interface';
|
||||||
@ -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.
|
* 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.
|
* Set the amount of time, in milliseconds, to wait to trigger the `ionChange` event after each keystroke.
|
||||||
@ -218,22 +218,22 @@ export class Input implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionStyle!: EventEmitter<StyleEventDetail>;
|
@Event() ionStyle!: EventEmitter<StyleEventDetail>;
|
||||||
|
|
||||||
componentWillLoad() {
|
connectedCallback() {
|
||||||
// By default, password inputs clear after focus when they have content
|
|
||||||
if (this.clearOnEdit === undefined && this.type === 'password') {
|
|
||||||
this.clearOnEdit = true;
|
|
||||||
}
|
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
}
|
|
||||||
|
|
||||||
componentDidLoad() {
|
|
||||||
this.debounceChanged();
|
this.debounceChanged();
|
||||||
|
if (Build.isBrowser) {
|
||||||
this.ionInputDidLoad.emit();
|
this.el.dispatchEvent(new CustomEvent('ionInputDidLoad', {
|
||||||
|
detail: this.el
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
this.ionInputDidUnload.emit();
|
if (Build.isBrowser) {
|
||||||
|
document.dispatchEvent(new CustomEvent('ionInputDidUnload', {
|
||||||
|
detail: this.el
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -255,6 +255,13 @@ export class Input implements ComponentInterface {
|
|||||||
return Promise.resolve(this.nativeInput!);
|
return Promise.resolve(this.nativeInput!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldClearOnEdit() {
|
||||||
|
const { type, clearOnEdit } = this;
|
||||||
|
return (clearOnEdit === undefined)
|
||||||
|
? type === 'password'
|
||||||
|
: clearOnEdit;
|
||||||
|
}
|
||||||
|
|
||||||
private getValue(): string {
|
private getValue(): string {
|
||||||
return this.value || '';
|
return this.value || '';
|
||||||
}
|
}
|
||||||
@ -295,7 +302,7 @@ export class Input implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onKeydown = () => {
|
private onKeydown = () => {
|
||||||
if (this.clearOnEdit) {
|
if (this.shouldClearOnEdit()) {
|
||||||
// Did the input value change after it was blurred and edited?
|
// Did the input value change after it was blurred and edited?
|
||||||
if (this.didBlurAfterEdit && this.hasValue()) {
|
if (this.didBlurAfterEdit && this.hasValue()) {
|
||||||
// Clear the input
|
// Clear the input
|
||||||
@ -327,7 +334,7 @@ export class Input implements ComponentInterface {
|
|||||||
|
|
||||||
private focusChanged() {
|
private focusChanged() {
|
||||||
// If clearOnEdit is enabled and the input blurred but has a value, set a flag
|
// 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;
|
this.didBlurAfterEdit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ export class ItemSliding implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionDrag!: EventEmitter;
|
@Event() ionDrag!: EventEmitter;
|
||||||
|
|
||||||
async componentDidLoad() {
|
async connectedCallback() {
|
||||||
this.item = this.el.querySelector('ion-item');
|
this.item = this.el.querySelector('ion-item');
|
||||||
await this.updateOptions();
|
await this.updateOptions();
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ export class ItemSliding implements ComponentInterface {
|
|||||||
this.disabledChanged();
|
this.disabledChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
if (this.gesture) {
|
if (this.gesture) {
|
||||||
this.gesture.destroy();
|
this.gesture.destroy();
|
||||||
this.gesture = undefined;
|
this.gesture = undefined;
|
||||||
|
@ -136,7 +136,7 @@ export class Menu implements ComponentInterface, MenuI {
|
|||||||
*/
|
*/
|
||||||
@Event() protected ionMenuChange!: EventEmitter<MenuChangeEventDetail>;
|
@Event() protected ionMenuChange!: EventEmitter<MenuChangeEventDetail>;
|
||||||
|
|
||||||
async componentWillLoad() {
|
async connectedCallback() {
|
||||||
if (this.type === undefined) {
|
if (this.type === undefined) {
|
||||||
this.type = config.get('menuType', this.mode === 'ios' ? 'reveal' : 'overlay');
|
this.type = config.get('menuType', this.mode === 'ios' ? 'reveal' : 'overlay');
|
||||||
}
|
}
|
||||||
@ -182,11 +182,12 @@ export class Menu implements ComponentInterface, MenuI {
|
|||||||
this.updateState();
|
this.updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidLoad() {
|
async componentDidLoad() {
|
||||||
this.ionMenuChange.emit({ disabled: this.disabled, open: this._isOpen });
|
this.ionMenuChange.emit({ disabled: this.disabled, open: this._isOpen });
|
||||||
|
this.updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
this.blocker.destroy();
|
this.blocker.destroy();
|
||||||
menuController._unregister(this);
|
menuController._unregister(this);
|
||||||
if (this.animation) {
|
if (this.animation) {
|
||||||
|
@ -47,7 +47,7 @@ export class PickerColumnCmp implements ComponentInterface {
|
|||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillLoad() {
|
async connectedCallback() {
|
||||||
let pickerRotateFactor = 0;
|
let pickerRotateFactor = 0;
|
||||||
let pickerScaleFactor = 0.81;
|
let pickerScaleFactor = 0.81;
|
||||||
|
|
||||||
@ -60,16 +60,6 @@ export class PickerColumnCmp implements ComponentInterface {
|
|||||||
|
|
||||||
this.rotateFactor = pickerRotateFactor;
|
this.rotateFactor = pickerRotateFactor;
|
||||||
this.scaleFactor = pickerScaleFactor;
|
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({
|
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||||
el: this.el,
|
el: this.el,
|
||||||
@ -81,14 +71,24 @@ export class PickerColumnCmp implements ComponentInterface {
|
|||||||
onEnd: ev => this.onEnd(ev),
|
onEnd: ev => this.onEnd(ev),
|
||||||
});
|
});
|
||||||
this.gesture.setDisabled(false);
|
this.gesture.setDisabled(false);
|
||||||
|
|
||||||
this.tmrId = setTimeout(() => {
|
this.tmrId = setTimeout(() => {
|
||||||
this.noAnimate = false;
|
this.noAnimate = false;
|
||||||
this.refresh(true);
|
this.refresh(true);
|
||||||
}, 250);
|
}, 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);
|
cancelAnimationFrame(this.rafId);
|
||||||
clearTimeout(this.tmrId);
|
clearTimeout(this.tmrId);
|
||||||
if (this.gesture) {
|
if (this.gesture) {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Listen, Prop, Watch, h } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, Watch, h } from '@stencil/core';
|
||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { RadioGroupChangeEventDetail } from '../../interface';
|
import { RadioGroupChangeEventDetail } from '../../interface';
|
||||||
|
import { findCheckedOption, watchForOptions } from '../../utils/watch-options';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-radio-group'
|
tag: 'ion-radio-group'
|
||||||
@ -10,7 +11,7 @@ export class RadioGroup implements ComponentInterface {
|
|||||||
|
|
||||||
private inputId = `ion-rg-${radioGroupIds++}`;
|
private inputId = `ion-rg-${radioGroupIds++}`;
|
||||||
private labelId = `${this.inputId}-lbl`;
|
private labelId = `${this.inputId}-lbl`;
|
||||||
private radios: HTMLIonRadioElement[] = [];
|
private mutationO?: MutationObserver;
|
||||||
|
|
||||||
@Element() el!: HTMLElement;
|
@Element() el!: HTMLElement;
|
||||||
|
|
||||||
@ -40,58 +41,11 @@ export class RadioGroup implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionChange!: EventEmitter<RadioGroupChangeEventDetail>;
|
@Event() ionChange!: EventEmitter<RadioGroupChangeEventDetail>;
|
||||||
|
|
||||||
@Listen('ionRadioDidLoad')
|
async connectedCallback() {
|
||||||
onRadioDidLoad(ev: Event) {
|
|
||||||
const radio = ev.target as HTMLIonRadioElement;
|
|
||||||
radio.name = this.name;
|
|
||||||
|
|
||||||
// add radio to internal list
|
|
||||||
this.radios.push(radio);
|
|
||||||
|
|
||||||
// this radio-group does not have a value
|
|
||||||
// but this radio is checked, so let's set the
|
|
||||||
// radio-group's value from the checked radio
|
|
||||||
if (this.value == null && radio.checked) {
|
|
||||||
this.value = radio.value;
|
|
||||||
} else {
|
|
||||||
this.updateRadios();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Listen('ionRadioDidUnload')
|
|
||||||
onRadioDidUnload(ev: Event) {
|
|
||||||
const index = this.radios.indexOf(ev.target as HTMLIonRadioElement);
|
|
||||||
if (index > -1) {
|
|
||||||
this.radios.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Listen('ionSelect')
|
|
||||||
onRadioSelect(ev: Event) {
|
|
||||||
const selectedRadio = ev.target as HTMLIonRadioElement | null;
|
|
||||||
if (selectedRadio) {
|
|
||||||
this.value = selectedRadio.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Listen('ionDeselect')
|
|
||||||
onRadioDeselect(ev: Event) {
|
|
||||||
if (this.allowEmptySelection) {
|
|
||||||
const selectedRadio = ev.target as HTMLIonRadioElement | null;
|
|
||||||
if (selectedRadio) {
|
|
||||||
selectedRadio.checked = false;
|
|
||||||
this.value = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidLoad() {
|
|
||||||
// Get the list header if it exists and set the id
|
// Get the list header if it exists and set the id
|
||||||
// this is used to set aria-labelledby
|
// this is used to set aria-labelledby
|
||||||
let header = this.el.querySelector('ion-list-header');
|
const el = this.el;
|
||||||
if (!header) {
|
const header = el.querySelector('ion-list-header') || el.querySelector('ion-item-divider');
|
||||||
header = this.el.querySelector('ion-item-divider');
|
|
||||||
}
|
|
||||||
if (header) {
|
if (header) {
|
||||||
const label = header.querySelector('ion-label');
|
const label = header.querySelector('ion-label');
|
||||||
if (label) {
|
if (label) {
|
||||||
@ -99,13 +53,42 @@ export class RadioGroup implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.value === undefined) {
|
||||||
|
const radio = findCheckedOption(el, 'ion-radio') as HTMLIonRadioElement | undefined;
|
||||||
|
if (radio !== undefined) {
|
||||||
|
await radio.componentOnReady();
|
||||||
|
if (this.value === undefined) {
|
||||||
|
this.value = radio.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mutationO = watchForOptions<HTMLIonRadioElement>(el, 'ion-radio', newOption => {
|
||||||
|
if (newOption !== undefined) {
|
||||||
|
newOption.componentOnReady().then(() => {
|
||||||
|
this.value = newOption.value;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.updateRadios();
|
||||||
|
}
|
||||||
|
});
|
||||||
this.updateRadios();
|
this.updateRadios();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateRadios() {
|
disconnectedCallback() {
|
||||||
const value = this.value;
|
if (this.mutationO) {
|
||||||
|
this.mutationO.disconnect();
|
||||||
|
this.mutationO = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateRadios() {
|
||||||
|
const { value } = this;
|
||||||
|
const radios = await this.getRadios();
|
||||||
let hasChecked = false;
|
let hasChecked = false;
|
||||||
for (const radio of this.radios) {
|
|
||||||
|
// Walk the DOM in reverse order, since the last selected one wins!
|
||||||
|
for (const radio of radios) {
|
||||||
if (!hasChecked && radio.value === value) {
|
if (!hasChecked && radio.value === value) {
|
||||||
// correct value for this radio
|
// correct value for this radio
|
||||||
// but this radio isn't checked yet
|
// but this radio isn't checked yet
|
||||||
@ -118,6 +101,34 @@ export class RadioGroup implements ComponentInterface {
|
|||||||
radio.checked = false;
|
radio.checked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset value if
|
||||||
|
if (!hasChecked) {
|
||||||
|
this.value = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRadios() {
|
||||||
|
return Promise.all(
|
||||||
|
Array
|
||||||
|
.from(this.el.querySelectorAll('ion-radio'))
|
||||||
|
.map(r => r.componentOnReady())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSelect = (ev: Event) => {
|
||||||
|
const selectedRadio = ev.target as HTMLIonRadioElement | null;
|
||||||
|
if (selectedRadio) {
|
||||||
|
this.value = selectedRadio.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDeselect = (ev: Event) => {
|
||||||
|
const selectedRadio = ev.target as HTMLIonRadioElement | null;
|
||||||
|
if (selectedRadio) {
|
||||||
|
selectedRadio.checked = false;
|
||||||
|
this.value = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -125,6 +136,8 @@ export class RadioGroup implements ComponentInterface {
|
|||||||
<Host
|
<Host
|
||||||
role="radiogroup"
|
role="radiogroup"
|
||||||
aria-labelledby={this.labelId}
|
aria-labelledby={this.labelId}
|
||||||
|
onIonSelect={this.onSelect}
|
||||||
|
onIonDeselect={this.allowEmptySelection ? this.onDeselect : undefined}
|
||||||
class={getIonMode(this)}
|
class={getIonMode(this)}
|
||||||
>
|
>
|
||||||
</Host>
|
</Host>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<ion-content class="outer-content">
|
<ion-content class="outer-content">
|
||||||
<ion-radio-group id="dynamicDisabled" disabled name="tannen" id="group" value="biff">
|
<ion-radio-group id="dynamicDisabled" disabled name="tannen" id="group" value="biff">
|
||||||
<ion-list-header>
|
<ion-list-header>
|
||||||
<ion-label>Luckiest Man On Earth</ion-label>
|
<ion-label>Luckiest Man On Earth <span id="group-value"></span></ion-label>
|
||||||
</ion-list-header>
|
</ion-list-header>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
@ -53,7 +53,10 @@
|
|||||||
|
|
||||||
</ion-radio-group>
|
</ion-radio-group>
|
||||||
|
|
||||||
<ion-button onClick="toggleDisabled()">Toggle Disabled</ion-button>
|
<ion-button onClick="addSelect()">Add Select</ion-button>
|
||||||
|
<ion-button onClick="addCheckedSelect()">Add Checked Select</ion-button>
|
||||||
|
<ion-button onClick="removeSelect()">Remove Select</ion-button>
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -61,12 +64,39 @@
|
|||||||
--background: #f2f2f2;
|
--background: #f2f2f2;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var dynamicDisabled = document.getElementById('dynamicDisabled');
|
let count = 0;
|
||||||
|
const valueEl = document.querySelector('#group-value');
|
||||||
|
const group = document.querySelector('ion-radio-group');
|
||||||
|
group.addEventListener('ionChange', (ev) => {
|
||||||
|
valueEl.textContent = group.value;
|
||||||
|
});
|
||||||
|
customElements.whenDefined('ion-radio-group')
|
||||||
|
.then(() => group.componentOnReady())
|
||||||
|
.then(() => {
|
||||||
|
valueEl.textContent = group.value;
|
||||||
|
});
|
||||||
|
|
||||||
function toggleDisabled() {
|
function addSelect() {
|
||||||
dynamicDisabled.disabled = !dynamicDisabled.disabled;
|
const item = document.createElement('ion-item');
|
||||||
|
item.innerHTML = `
|
||||||
|
<ion-label>Item ${count}</ion-label>
|
||||||
|
<ion-radio value="item-${count}" slot="start"></ion-radio>
|
||||||
|
`;
|
||||||
|
group.appendChild(item);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
function addCheckedSelect() {
|
||||||
|
const item = document.createElement('ion-item');
|
||||||
|
item.innerHTML = `
|
||||||
|
<ion-label>Item ${count}</ion-label>
|
||||||
|
<ion-radio value="item-${count}" slot="start" checked></ion-radio>
|
||||||
|
`;
|
||||||
|
group.appendChild(item);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
function removeSelect() {
|
||||||
|
group.children[group.children.length - 1].remove();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</ion-app>
|
</ion-app>
|
||||||
|
@ -49,18 +49,6 @@ export class Radio implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop({ mutable: true }) value?: any | null;
|
@Prop({ mutable: true }) value?: any | null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when the radio loads.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
@Event() ionRadioDidLoad!: EventEmitter<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when the radio unloads.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
@Event() ionRadioDidUnload!: EventEmitter<void>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the styles change.
|
* Emitted when the styles change.
|
||||||
* @internal
|
* @internal
|
||||||
@ -116,14 +104,6 @@ export class Radio implements ComponentInterface {
|
|||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidLoad() {
|
|
||||||
this.ionRadioDidLoad.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUnload() {
|
|
||||||
this.ionRadioDidUnload.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitStyle() {
|
private emitStyle() {
|
||||||
this.ionStyle.emit({
|
this.ionStyle.emit({
|
||||||
'radio-checked': this.checked,
|
'radio-checked': this.checked,
|
||||||
|
@ -169,13 +169,11 @@ export class Range implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionBlur!: EventEmitter<void>;
|
@Event() ionBlur!: EventEmitter<void>;
|
||||||
|
|
||||||
componentWillLoad() {
|
async connectedCallback() {
|
||||||
this.updateRatio();
|
this.updateRatio();
|
||||||
this.debounceChanged();
|
this.debounceChanged();
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidLoad() {
|
|
||||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||||
el: this.rangeSlider!,
|
el: this.rangeSlider!,
|
||||||
gestureName: 'range',
|
gestureName: 'range',
|
||||||
@ -188,7 +186,7 @@ export class Range implements ComponentInterface {
|
|||||||
this.gesture.setDisabled(this.disabled);
|
this.gesture.setDisabled(this.disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
if (this.gesture) {
|
if (this.gesture) {
|
||||||
this.gesture.destroy();
|
this.gesture.destroy();
|
||||||
this.gesture = undefined;
|
this.gesture = undefined;
|
||||||
|
@ -97,20 +97,19 @@ export class Refresher implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionStart!: EventEmitter<void>;
|
@Event() ionStart!: EventEmitter<void>;
|
||||||
|
|
||||||
async componentDidLoad() {
|
async connectedCallback() {
|
||||||
if (this.el.getAttribute('slot') !== 'fixed') {
|
if (this.el.getAttribute('slot') !== 'fixed') {
|
||||||
console.error('Make sure you use: <ion-refresher slot="fixed">');
|
console.error('Make sure you use: <ion-refresher slot="fixed">');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const contentEl = this.el.closest('ion-content');
|
const contentEl = this.el.closest('ion-content');
|
||||||
if (contentEl) {
|
if (!contentEl) {
|
||||||
this.scrollEl = await contentEl.getScrollElement();
|
console.error('<ion-refresher> must be used inside an <ion-content>');
|
||||||
} else {
|
return;
|
||||||
console.error('ion-refresher did not attach, make sure the parent is an ion-content.');
|
|
||||||
}
|
}
|
||||||
|
this.scrollEl = await contentEl.getScrollElement();
|
||||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||||
el: this.el.closest('ion-content') as any,
|
el: contentEl,
|
||||||
gestureName: 'refresher',
|
gestureName: 'refresher',
|
||||||
gesturePriority: 10,
|
gesturePriority: 10,
|
||||||
direction: 'y',
|
direction: 'y',
|
||||||
@ -125,7 +124,7 @@ export class Refresher implements ComponentInterface {
|
|||||||
this.disabledChanged();
|
this.disabledChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
this.scrollEl = undefined;
|
this.scrollEl = undefined;
|
||||||
if (this.gesture) {
|
if (this.gesture) {
|
||||||
this.gesture.destroy();
|
this.gesture.destroy();
|
||||||
|
@ -52,12 +52,13 @@ export class ReorderGroup implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionItemReorder!: EventEmitter<ItemReorderEventDetail>;
|
@Event() ionItemReorder!: EventEmitter<ItemReorderEventDetail>;
|
||||||
|
|
||||||
async componentDidLoad() {
|
async connectedCallback() {
|
||||||
const contentEl = this.el.closest('ion-content');
|
const contentEl = this.el.closest('ion-content');
|
||||||
if (contentEl) {
|
if (!contentEl) {
|
||||||
this.scrollEl = await contentEl.getScrollElement();
|
console.error('<ion-reorder-group> must be used inside an <ion-content>');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
this.scrollEl = await contentEl.getScrollElement();
|
||||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||||
el: this.el,
|
el: this.el,
|
||||||
gestureName: 'reorder',
|
gestureName: 'reorder',
|
||||||
@ -74,7 +75,7 @@ export class ReorderGroup implements ComponentInterface {
|
|||||||
this.disabledChanged();
|
this.disabledChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
this.onEnd();
|
this.onEnd();
|
||||||
if (this.gesture) {
|
if (this.gesture) {
|
||||||
this.gesture.destroy();
|
this.gesture.destroy();
|
||||||
|
@ -45,10 +45,7 @@ export class RouteRedirect implements ComponentInterface {
|
|||||||
this.ionRouteRedirectChanged.emit();
|
this.ionRouteRedirectChanged.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidLoad() {
|
connectedCallback() {
|
||||||
this.ionRouteRedirectChanged.emit();
|
|
||||||
}
|
|
||||||
componentDidUnload() {
|
|
||||||
this.ionRouteRedirectChanged.emit();
|
this.ionRouteRedirectChanged.emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,7 @@ export class Route implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidLoad() {
|
connectedCallback() {
|
||||||
this.ionRouteDataChanged.emit();
|
|
||||||
}
|
|
||||||
componentDidUnload() {
|
|
||||||
this.ionRouteDataChanged.emit();
|
this.ionRouteDataChanged.emit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,11 +60,7 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
@Event({ bubbles: false }) ionNavDidChange!: EventEmitter<void>;
|
@Event({ bubbles: false }) ionNavDidChange!: EventEmitter<void>;
|
||||||
|
|
||||||
componentWillLoad() {
|
async connectedCallback() {
|
||||||
this.ionNavWillLoad.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidLoad() {
|
|
||||||
this.gesture = (await import('../../utils/gesture/swipe-back')).createSwipeBackGesture(
|
this.gesture = (await import('../../utils/gesture/swipe-back')).createSwipeBackGesture(
|
||||||
this.el,
|
this.el,
|
||||||
() => !!this.swipeHandler && this.swipeHandler.canStart() && this.animationEnabled,
|
() => !!this.swipeHandler && this.swipeHandler.canStart() && this.animationEnabled,
|
||||||
@ -106,8 +102,11 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
|
|||||||
this.swipeHandlerChanged();
|
this.swipeHandlerChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
componentWillLoad() {
|
||||||
this.activeEl = this.activeComponent = undefined;
|
this.ionNavWillLoad.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
if (this.gesture) {
|
if (this.gesture) {
|
||||||
this.gesture.destroy();
|
this.gesture.destroy();
|
||||||
this.gesture = undefined;
|
this.gesture = undefined;
|
||||||
|
10
core/src/components/router/test/basic/e2e.ts
Normal file
10
core/src/components/router/test/basic/e2e.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { newE2EPage } from '@stencil/core/testing';
|
||||||
|
|
||||||
|
test('router: basic', async () => {
|
||||||
|
const page = await newE2EPage({
|
||||||
|
url: '/src/components/router/test/basic?ionic:_testing=true'
|
||||||
|
});
|
||||||
|
|
||||||
|
const compare = await page.compareScreenshot();
|
||||||
|
expect(compare).toMatchScreenshot();
|
||||||
|
});
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, h } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Host, Prop, h } from '@stencil/core';
|
||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
|
|
||||||
@ -26,33 +26,7 @@ export class SelectOption implements ComponentInterface {
|
|||||||
/**
|
/**
|
||||||
* The text value of the option.
|
* The text value of the option.
|
||||||
*/
|
*/
|
||||||
@Prop({ mutable: true }) value?: any | null;
|
@Prop() value?: any | null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when the select option loads.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
@Event() ionSelectOptionDidLoad!: EventEmitter<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when the select option unloads.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
@Event() ionSelectOptionDidUnload!: EventEmitter<void>;
|
|
||||||
|
|
||||||
componentWillLoad() {
|
|
||||||
if (this.value === undefined) {
|
|
||||||
this.value = this.el.textContent || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidLoad() {
|
|
||||||
this.ionSelectOptionDidLoad.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUnload() {
|
|
||||||
this.ionSelectOptionDidUnload.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Listen, Method, Prop, State, Watch, h } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h } from '@stencil/core';
|
||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { ActionSheetButton, ActionSheetOptions, AlertInput, AlertOptions, CssClassMap, OverlaySelect, PopoverOptions, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, StyleEventDetail } from '../../interface';
|
import { ActionSheetButton, ActionSheetOptions, AlertInput, AlertOptions, CssClassMap, OverlaySelect, PopoverOptions, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, StyleEventDetail } from '../../interface';
|
||||||
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||||
import { actionSheetController, alertController, popoverController } from '../../utils/overlays';
|
import { actionSheetController, alertController, popoverController } from '../../utils/overlays';
|
||||||
import { hostContext } from '../../utils/theme';
|
import { hostContext } from '../../utils/theme';
|
||||||
|
import { watchForOptions } from '../../utils/watch-options';
|
||||||
|
|
||||||
import { SelectCompareFn } from './select-interface';
|
import { SelectCompareFn } from './select-interface';
|
||||||
|
|
||||||
@ -21,11 +22,11 @@ import { SelectCompareFn } from './select-interface';
|
|||||||
})
|
})
|
||||||
export class Select implements ComponentInterface {
|
export class Select implements ComponentInterface {
|
||||||
|
|
||||||
private childOpts: HTMLIonSelectOptionElement[] = [];
|
|
||||||
private inputId = `ion-sel-${selectIds++}`;
|
private inputId = `ion-sel-${selectIds++}`;
|
||||||
private overlay?: OverlaySelect;
|
private overlay?: OverlaySelect;
|
||||||
private didInit = false;
|
private didInit = false;
|
||||||
private buttonEl?: HTMLButtonElement;
|
private buttonEl?: HTMLButtonElement;
|
||||||
|
private mutationO?: MutationObserver;
|
||||||
|
|
||||||
@Element() el!: HTMLIonSelectElement;
|
@Element() el!: HTMLIonSelectElement;
|
||||||
|
|
||||||
@ -117,64 +118,54 @@ export class Select implements ComponentInterface {
|
|||||||
@Event() ionStyle!: EventEmitter<StyleEventDetail>;
|
@Event() ionStyle!: EventEmitter<StyleEventDetail>;
|
||||||
|
|
||||||
@Watch('disabled')
|
@Watch('disabled')
|
||||||
|
@Watch('placeholder')
|
||||||
disabledChanged() {
|
disabledChanged() {
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Watch('value')
|
@Watch('value')
|
||||||
valueChanged() {
|
valueChanged() {
|
||||||
if (this.didInit) {
|
|
||||||
this.updateOptions();
|
this.updateOptions();
|
||||||
|
this.emitStyle();
|
||||||
|
if (this.didInit) {
|
||||||
this.ionChange.emit({
|
this.ionChange.emit({
|
||||||
value: this.value,
|
value: this.value,
|
||||||
});
|
});
|
||||||
this.emitStyle();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Listen('ionSelectOptionDidLoad')
|
async connectedCallback() {
|
||||||
@Listen('ionSelectOptionDidUnload')
|
|
||||||
async selectOptionChanged() {
|
|
||||||
await this.loadOptions();
|
|
||||||
|
|
||||||
if (this.didInit) {
|
|
||||||
this.updateOptions();
|
|
||||||
this.updateOverlayOptions();
|
|
||||||
this.emitStyle();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In the event that options
|
|
||||||
* are not loaded at component load
|
|
||||||
* this ensures that any value that is
|
|
||||||
* set is properly rendered once
|
|
||||||
* options have been loaded
|
|
||||||
*/
|
|
||||||
if (this.value !== undefined) {
|
|
||||||
this.el.forceUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidLoad() {
|
|
||||||
await this.loadOptions();
|
|
||||||
|
|
||||||
if (this.value === undefined) {
|
if (this.value === undefined) {
|
||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
// there are no values set at this point
|
// there are no values set at this point
|
||||||
// so check to see who should be selected
|
// so check to see who should be selected
|
||||||
const checked = this.childOpts.filter(o => o.selected);
|
const checked = this.childOpts.filter(o => o.selected);
|
||||||
this.value = checked.map(o => o.value);
|
this.value = checked.map(o => getOptionValue(o));
|
||||||
} else {
|
} else {
|
||||||
const checked = this.childOpts.find(o => o.selected);
|
const checked = this.childOpts.find(o => o.selected);
|
||||||
if (checked) {
|
if (checked) {
|
||||||
this.value = checked.value;
|
this.value = getOptionValue(checked);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.updateOptions();
|
this.updateOptions();
|
||||||
|
this.updateOverlayOptions();
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
this.el.forceUpdate();
|
|
||||||
|
this.mutationO = watchForOptions<HTMLIonSelectOptionElement>(this.el, 'ion-select-option', async () => {
|
||||||
|
this.updateOptions();
|
||||||
|
this.updateOverlayOptions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
if (this.mutationO) {
|
||||||
|
this.mutationO.disconnect();
|
||||||
|
this.mutationO = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidLoad() {
|
||||||
this.didInit = true;
|
this.didInit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,22 +213,24 @@ export class Select implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateOverlayOptions(): void {
|
private updateOverlayOptions(): void {
|
||||||
if (!this.overlay) { return; }
|
|
||||||
const overlay = (this.overlay as any);
|
const overlay = (this.overlay as any);
|
||||||
|
if (!overlay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const childOpts = this.childOpts;
|
||||||
switch (this.interface) {
|
switch (this.interface) {
|
||||||
case 'action-sheet':
|
case 'action-sheet':
|
||||||
overlay.buttons = this.createActionSheetButtons(this.childOpts);
|
overlay.buttons = this.createActionSheetButtons(childOpts);
|
||||||
break;
|
break;
|
||||||
case 'popover':
|
case 'popover':
|
||||||
const popover = overlay.querySelector('ion-select-popover');
|
const popover = overlay.querySelector('ion-select-popover');
|
||||||
if (popover) {
|
if (popover) {
|
||||||
popover.options = this.createPopoverOptions(this.childOpts);
|
popover.options = this.createPopoverOptions(childOpts);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
case 'alert':
|
||||||
const inputType = (this.multiple ? 'checkbox' : 'radio');
|
const inputType = (this.multiple ? 'checkbox' : 'radio');
|
||||||
overlay.inputs = this.createAlertInputs(this.childOpts, inputType);
|
overlay.inputs = this.createAlertInputs(childOpts, inputType);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -248,7 +241,7 @@ export class Select implements ComponentInterface {
|
|||||||
role: (option.selected ? 'selected' : ''),
|
role: (option.selected ? 'selected' : ''),
|
||||||
text: option.textContent,
|
text: option.textContent,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.value = option.value;
|
this.value = getOptionValue(option);
|
||||||
}
|
}
|
||||||
} as ActionSheetButton;
|
} as ActionSheetButton;
|
||||||
});
|
});
|
||||||
@ -270,7 +263,7 @@ export class Select implements ComponentInterface {
|
|||||||
return {
|
return {
|
||||||
type: inputType,
|
type: inputType,
|
||||||
label: o.textContent,
|
label: o.textContent,
|
||||||
value: o.value,
|
value: getOptionValue(o),
|
||||||
checked: o.selected,
|
checked: o.selected,
|
||||||
disabled: o.disabled
|
disabled: o.disabled
|
||||||
} as AlertInput;
|
} as AlertInput;
|
||||||
@ -279,13 +272,14 @@ export class Select implements ComponentInterface {
|
|||||||
|
|
||||||
private createPopoverOptions(data: any[]): SelectPopoverOption[] {
|
private createPopoverOptions(data: any[]): SelectPopoverOption[] {
|
||||||
return data.map(o => {
|
return data.map(o => {
|
||||||
|
const value = getOptionValue(o);
|
||||||
return {
|
return {
|
||||||
text: o.textContent,
|
text: o.textContent,
|
||||||
value: o.value,
|
value,
|
||||||
checked: o.selected,
|
checked: o.selected,
|
||||||
disabled: o.disabled,
|
disabled: o.disabled,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
this.value = o.value;
|
this.value = value;
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
} as SelectPopoverOption;
|
} as SelectPopoverOption;
|
||||||
@ -374,22 +368,18 @@ export class Select implements ComponentInterface {
|
|||||||
return this.overlay.dismiss();
|
return this.overlay.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadOptions() {
|
|
||||||
this.childOpts = await Promise.all(
|
|
||||||
Array.from(this.el.querySelectorAll('ion-select-option')).map(o => o.componentOnReady())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateOptions() {
|
private updateOptions() {
|
||||||
// iterate all options, updating the selected prop
|
// iterate all options, updating the selected prop
|
||||||
let canSelect = true;
|
let canSelect = true;
|
||||||
for (const selectOption of this.childOpts) {
|
const { value, childOpts, compareWith, multiple } = this;
|
||||||
const selected = canSelect && isOptionSelected(this.value, selectOption.value, this.compareWith);
|
for (const selectOption of childOpts) {
|
||||||
|
const optValue = getOptionValue(selectOption);
|
||||||
|
const selected = canSelect && isOptionSelected(value, optValue, compareWith);
|
||||||
selectOption.selected = selected;
|
selectOption.selected = selected;
|
||||||
|
|
||||||
// if current option is selected and select is single-option, we can't select
|
// if current option is selected and select is single-option, we can't select
|
||||||
// any option more
|
// any option more
|
||||||
if (selected && !this.multiple) {
|
if (selected && !multiple) {
|
||||||
canSelect = false;
|
canSelect = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,6 +393,10 @@ export class Select implements ComponentInterface {
|
|||||||
return this.getText() !== '';
|
return this.getText() !== '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get childOpts() {
|
||||||
|
return Array.from(this.el.querySelectorAll('ion-select-option'));
|
||||||
|
}
|
||||||
|
|
||||||
private getText(): string {
|
private getText(): string {
|
||||||
const selectedText = this.selectedText;
|
const selectedText = this.selectedText;
|
||||||
if (selectedText != null && selectedText !== '') {
|
if (selectedText != null && selectedText !== '') {
|
||||||
@ -496,6 +490,13 @@ export class Select implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getOptionValue = (el: HTMLIonSelectOptionElement) => {
|
||||||
|
const value = el.value;
|
||||||
|
return (value === undefined)
|
||||||
|
? el.textContent || ''
|
||||||
|
: value;
|
||||||
|
};
|
||||||
|
|
||||||
const parseValue = (value: any) => {
|
const parseValue = (value: any) => {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -543,7 +544,7 @@ const generateText = (opts: HTMLIonSelectOptionElement[], value: any | any[], co
|
|||||||
|
|
||||||
const textForValue = (opts: HTMLIonSelectOptionElement[], value: any, compareWith?: string | SelectCompareFn | null): string | null => {
|
const textForValue = (opts: HTMLIonSelectOptionElement[], value: any, compareWith?: string | SelectCompareFn | null): string | null => {
|
||||||
const selectOpt = opts.find(opt => {
|
const selectOpt = opts.find(opt => {
|
||||||
return compareOptions(opt.value, value, compareWith);
|
return compareOptions(getOptionValue(opt), value, compareWith);
|
||||||
});
|
});
|
||||||
return selectOpt
|
return selectOpt
|
||||||
? selectOpt.textContent
|
? selectOpt.textContent
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
<ion-label position="floating">Label with Placeholder</ion-label>
|
<ion-label position="floating">Label with Placeholder</ion-label>
|
||||||
<ion-select id="actionSheet" interface="action-sheet" placeholder="A Placeholder"></ion-select>
|
<ion-select id="actionSheet" interface="action-sheet" placeholder="A Placeholder"></ion-select>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let selects = document.querySelectorAll('ion-select');
|
let selects = document.querySelectorAll('ion-select');
|
||||||
const options = ['bird', 'dog', 'shark', 'lizard'];
|
const options = ['bird', 'dog', 'shark', 'lizard'];
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Component, ComponentInterface, Event, Host, h } from '@stencil/core';
|
import { Component, ComponentInterface, Host, h } from '@stencil/core';
|
||||||
import { EventEmitter } from 'ionicons/dist/types/stencil.core';
|
|
||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
|
|
||||||
@ -9,17 +8,6 @@ import { getIonMode } from '../../global/ionic-global';
|
|||||||
})
|
})
|
||||||
export class Slide implements ComponentInterface {
|
export class Slide implements ComponentInterface {
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
@Event() ionSlideChanged!: EventEmitter<void>;
|
|
||||||
|
|
||||||
componentDidLoad() {
|
|
||||||
this.ionSlideChanged.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUnload() {
|
|
||||||
this.ionSlideChanged.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Listen, Method, Prop, Watch, h } from '@stencil/core';
|
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, Watch, h } from '@stencil/core';
|
||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { rIC } from '../../utils/helpers.js';
|
import { rIC } from '../../utils/helpers.js';
|
||||||
@ -20,8 +20,8 @@ export class Slides implements ComponentInterface {
|
|||||||
|
|
||||||
private scrollbarEl?: HTMLElement;
|
private scrollbarEl?: HTMLElement;
|
||||||
private paginationEl?: HTMLElement;
|
private paginationEl?: HTMLElement;
|
||||||
private didInit = false;
|
private swiperReady = false;
|
||||||
|
private mutationO?: MutationObserver;
|
||||||
private readySwiper!: (swiper: SwiperInterface) => void;
|
private readySwiper!: (swiper: SwiperInterface) => void;
|
||||||
private swiper: Promise<SwiperInterface> = new Promise(resolve => { this.readySwiper = resolve; });
|
private swiper: Promise<SwiperInterface> = new Promise(resolve => { this.readySwiper = resolve; });
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ export class Slides implements ComponentInterface {
|
|||||||
|
|
||||||
@Watch('options')
|
@Watch('options')
|
||||||
async optionsChanged() {
|
async optionsChanged() {
|
||||||
if (this.didInit) {
|
if (this.swiperReady) {
|
||||||
const swiper = await this.getSwiper();
|
const swiper = await this.getSwiper();
|
||||||
Object.assign(swiper.params, this.options);
|
Object.assign(swiper.params, this.options);
|
||||||
await this.update();
|
await this.update();
|
||||||
@ -132,20 +132,28 @@ export class Slides implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionSlideTouchEnd!: EventEmitter<void>;
|
@Event() ionSlideTouchEnd!: EventEmitter<void>;
|
||||||
|
|
||||||
componentDidLoad() {
|
connectedCallback() {
|
||||||
|
const mut = this.mutationO = new MutationObserver(() => {
|
||||||
|
if (this.swiperReady) {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mut.observe(this.el, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
rIC(() => this.initSwiper());
|
rIC(() => this.initSwiper());
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidUnload() {
|
async disconnectedCallback() {
|
||||||
|
if (this.mutationO) {
|
||||||
|
this.mutationO.disconnect();
|
||||||
|
this.mutationO = undefined;
|
||||||
|
}
|
||||||
const swiper = await this.getSwiper();
|
const swiper = await this.getSwiper();
|
||||||
swiper.destroy(true, true);
|
swiper.destroy(true, true);
|
||||||
}
|
this.swiper = new Promise(resolve => { this.readySwiper = resolve; });
|
||||||
|
this.swiperReady = false;
|
||||||
@Listen('ionSlideChanged')
|
|
||||||
onSlideChanged() {
|
|
||||||
if (this.didInit) {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -154,7 +162,10 @@ export class Slides implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Method()
|
@Method()
|
||||||
async update() {
|
async update() {
|
||||||
const swiper = await this.getSwiper();
|
const [swiper] = await Promise.all([
|
||||||
|
this.getSwiper(),
|
||||||
|
waitForSlides(this.el)
|
||||||
|
]);
|
||||||
swiper.update();
|
swiper.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,8 +326,9 @@ export class Slides implements ComponentInterface {
|
|||||||
// init swiper core
|
// init swiper core
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const { Swiper } = await import('./swiper/swiper.bundle.js');
|
const { Swiper } = await import('./swiper/swiper.bundle.js');
|
||||||
|
await waitForSlides(this.el);
|
||||||
const swiper = new Swiper(this.el, finalOptions);
|
const swiper = new Swiper(this.el, finalOptions);
|
||||||
this.didInit = true;
|
this.swiperReady = true;
|
||||||
this.readySwiper(swiper);
|
this.readySwiper(swiper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -485,3 +497,9 @@ export class Slides implements ComponentInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const waitForSlides = (el: HTMLElement) => {
|
||||||
|
return Promise.all(
|
||||||
|
Array.from(el.querySelectorAll('ion-slide')).map(s => s.componentOnReady())
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -33,6 +33,9 @@
|
|||||||
<h1>Slide 3</h1>
|
<h1>Slide 3</h1>
|
||||||
</ion-slide>
|
</ion-slide>
|
||||||
</ion-slides>
|
</ion-slides>
|
||||||
|
<ion-button expand="block" onclick="addSlide()">Add slide</ion-button>
|
||||||
|
<ion-button expand="block" onclick="removeSlide()">Remove slide</ion-button>
|
||||||
|
|
||||||
<ion-button expand="block" onclick="slidePrev()">Slide Prev</ion-button>
|
<ion-button expand="block" onclick="slidePrev()">Slide Prev</ion-button>
|
||||||
<ion-button expand="block" onclick="slideNext()">Slide Next</ion-button>
|
<ion-button expand="block" onclick="slideNext()">Slide Next</ion-button>
|
||||||
<ion-button expand="block" onclick="getActiveIndex()">Get Active Index</ion-button>
|
<ion-button expand="block" onclick="getActiveIndex()">Get Active Index</ion-button>
|
||||||
@ -50,10 +53,24 @@
|
|||||||
|
|
||||||
</ion-app>
|
</ion-app>
|
||||||
<script>
|
<script>
|
||||||
|
let slideCount = 4;
|
||||||
const slides = document.getElementById('slides')
|
const slides = document.getElementById('slides')
|
||||||
slides.pager = false;
|
slides.pager = false;
|
||||||
slides.options = {}
|
slides.options = {}
|
||||||
|
|
||||||
|
async function addSlide() {
|
||||||
|
const slide = document.createElement('ion-slide');
|
||||||
|
slide.style.background = 'white';
|
||||||
|
slide.innerHTML = `<h1>Slide ${slideCount}</h1>`;
|
||||||
|
slideCount++;
|
||||||
|
slides.querySelector('.swiper-wrapper').appendChild(slide);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function removeSlide() {
|
||||||
|
const wraper = slides.querySelector('.swiper-wrapper');
|
||||||
|
wraper.children[wraper.children.length-1].remove();
|
||||||
|
};
|
||||||
|
|
||||||
async function slideNext() {
|
async function slideNext() {
|
||||||
await slides.slideNext(500)
|
await slides.slideNext(500)
|
||||||
};
|
};
|
||||||
|
@ -57,12 +57,12 @@ export class SplitPane implements ComponentInterface {
|
|||||||
this.ionSplitPaneVisible.emit(detail);
|
this.ionSplitPaneVisible.emit(detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidLoad() {
|
connectedCallback() {
|
||||||
this.styleChildren();
|
this.styleChildren();
|
||||||
this.updateState();
|
this.updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
if (this.rmL) {
|
if (this.rmL) {
|
||||||
this.rmL();
|
this.rmL();
|
||||||
this.rmL = undefined;
|
this.rmL = undefined;
|
||||||
|
@ -64,7 +64,6 @@
|
|||||||
|
|
||||||
<ion-content class="ion-padding">
|
<ion-content class="ion-padding">
|
||||||
<h1>Page 1</h1>
|
<h1>Page 1</h1>
|
||||||
<ion-button onclick="push()">Push</ion-button>
|
|
||||||
<ion-button onclick="menu()">Disable/enable menu</ion-button>
|
<ion-button onclick="menu()">Disable/enable menu</ion-button>
|
||||||
<f></f>
|
<f></f>
|
||||||
<f></f>
|
<f></f>
|
||||||
@ -84,9 +83,6 @@
|
|||||||
menuCtrl.open('start');
|
menuCtrl.open('start');
|
||||||
}
|
}
|
||||||
|
|
||||||
function push() {
|
|
||||||
}
|
|
||||||
|
|
||||||
async function menu() {
|
async function menu() {
|
||||||
menuCtrl.enable(!await menuCtrl.isEnabled());
|
menuCtrl.enable(!await menuCtrl.isEnabled());
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ export class Tab implements ComponentInterface {
|
|||||||
@Prop() component?: ComponentRef;
|
@Prop() component?: ComponentRef;
|
||||||
|
|
||||||
componentWillLoad() {
|
componentWillLoad() {
|
||||||
|
|
||||||
if (Build.isDev) {
|
if (Build.isDev) {
|
||||||
if (this.component !== undefined && this.el.childElementCount > 0) {
|
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.' +
|
console.error('You can not use a lazy-loaded component in a tab and inlined content at the same time.' +
|
||||||
|
@ -19,7 +19,6 @@ export class Tabs implements NavOutlet {
|
|||||||
|
|
||||||
@Element() el!: HTMLIonTabsElement;
|
@Element() el!: HTMLIonTabsElement;
|
||||||
|
|
||||||
@State() tabs: HTMLIonTabElement[] = [];
|
|
||||||
@State() selectedTab?: HTMLIonTabElement;
|
@State() selectedTab?: HTMLIonTabElement;
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
@ -41,23 +40,18 @@ export class Tabs implements NavOutlet {
|
|||||||
*/
|
*/
|
||||||
@Event({ bubbles: false }) ionTabsDidChange!: EventEmitter<{tab: string}>;
|
@Event({ bubbles: false }) ionTabsDidChange!: EventEmitter<{tab: string}>;
|
||||||
|
|
||||||
componentWillLoad() {
|
async componentWillLoad() {
|
||||||
if (!this.useRouter) {
|
if (!this.useRouter) {
|
||||||
this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]');
|
this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]');
|
||||||
}
|
}
|
||||||
this.tabs = Array.from(this.el.querySelectorAll('ion-tab'));
|
if (!this.useRouter) {
|
||||||
this.initSelect().then(() => {
|
const tabs = this.tabs;
|
||||||
|
await this.select(tabs[0]);
|
||||||
|
}
|
||||||
this.ionNavWillLoad.emit();
|
this.ionNavWillLoad.emit();
|
||||||
this.componentWillUpdate();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
componentWillRender() {
|
||||||
this.tabs.length = 0;
|
|
||||||
this.selectedTab = this.leavingTab = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUpdate() {
|
|
||||||
const tabBar = this.el.querySelector('ion-tab-bar');
|
const tabBar = this.el.querySelector('ion-tab-bar');
|
||||||
if (tabBar) {
|
if (tabBar) {
|
||||||
const tab = this.selectedTab ? this.selectedTab.tab : undefined;
|
const tab = this.selectedTab ? this.selectedTab.tab : undefined;
|
||||||
@ -131,16 +125,6 @@ export class Tabs implements NavOutlet {
|
|||||||
return tabId !== undefined ? { id: tabId, element: this.selectedTab } : undefined;
|
return tabId !== undefined ? { id: tabId, element: this.selectedTab } : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initSelect(): Promise<void> {
|
|
||||||
if (this.useRouter) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// wait for all tabs to be ready
|
|
||||||
await Promise.all(this.tabs.map(tab => tab.componentOnReady()));
|
|
||||||
|
|
||||||
await this.select(this.tabs[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setActive(selectedTab: HTMLIonTabElement): Promise<void> {
|
private setActive(selectedTab: HTMLIonTabElement): Promise<void> {
|
||||||
if (this.transitioning) {
|
if (this.transitioning) {
|
||||||
return Promise.reject('transitioning already happening');
|
return Promise.reject('transitioning already happening');
|
||||||
@ -186,16 +170,19 @@ export class Tabs implements NavOutlet {
|
|||||||
return selectedTab !== undefined && selectedTab !== leavingTab && !this.transitioning;
|
return selectedTab !== undefined && selectedTab !== leavingTab && !this.transitioning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get tabs() {
|
||||||
|
return Array.from(this.el.querySelectorAll('ion-tab'));
|
||||||
|
}
|
||||||
|
|
||||||
private onTabClicked = (ev: CustomEvent<TabButtonClickEventDetail>) => {
|
private onTabClicked = (ev: CustomEvent<TabButtonClickEventDetail>) => {
|
||||||
const { href, tab } = ev.detail;
|
const { href, tab } = ev.detail;
|
||||||
const selectedTab = this.tabs.find(t => t.tab === tab);
|
|
||||||
if (this.useRouter && href !== undefined) {
|
if (this.useRouter && href !== undefined) {
|
||||||
const router = document.querySelector('ion-router');
|
const router = document.querySelector('ion-router');
|
||||||
if (router) {
|
if (router) {
|
||||||
router.push(href);
|
router.push(href);
|
||||||
}
|
}
|
||||||
} else if (selectedTab) {
|
} else {
|
||||||
this.select(selectedTab);
|
this.select(tab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h, readTask } from '@stencil/core';
|
import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h, readTask } from '@stencil/core';
|
||||||
|
|
||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import { Color, StyleEventDetail, TextareaChangeEventDetail } from '../../interface';
|
import { Color, StyleEventDetail, TextareaChangeEventDetail } from '../../interface';
|
||||||
@ -169,28 +169,26 @@ export class Textarea implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Event() ionFocus!: EventEmitter<void>;
|
@Event() ionFocus!: EventEmitter<void>;
|
||||||
|
|
||||||
/**
|
connectedCallback() {
|
||||||
* Emitted when the input has been created.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
@Event() ionInputDidLoad!: EventEmitter<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emitted when the input has been removed.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
@Event() ionInputDidUnload!: EventEmitter<void>;
|
|
||||||
|
|
||||||
componentWillLoad() {
|
|
||||||
this.emitStyle();
|
this.emitStyle();
|
||||||
|
this.debounceChanged();
|
||||||
|
if (Build.isBrowser) {
|
||||||
|
this.el.dispatchEvent(new CustomEvent('ionInputDidLoad', {
|
||||||
|
detail: this.el
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
if (Build.isBrowser) {
|
||||||
|
document.dispatchEvent(new CustomEvent('ionInputDidUnload', {
|
||||||
|
detail: this.el
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidLoad() {
|
componentDidLoad() {
|
||||||
this.debounceChanged();
|
|
||||||
|
|
||||||
this.runAutoGrow();
|
this.runAutoGrow();
|
||||||
|
|
||||||
this.ionInputDidLoad.emit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: performance hit, this cause layout thrashing
|
// TODO: performance hit, this cause layout thrashing
|
||||||
@ -204,10 +202,6 @@ export class Textarea implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
|
||||||
this.ionInputDidUnload.emit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets focus on the specified `ion-textarea`. Use this method instead of the global
|
* Sets focus on the specified `ion-textarea`. Use this method instead of the global
|
||||||
* `input.focus()`.
|
* `input.focus()`.
|
||||||
|
@ -96,11 +96,7 @@ export class Toggle implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillLoad() {
|
async connectedCallback() {
|
||||||
this.emitStyle();
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidLoad() {
|
|
||||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||||
el: this.el,
|
el: this.el,
|
||||||
gestureName: 'toggle',
|
gestureName: 'toggle',
|
||||||
@ -114,13 +110,17 @@ export class Toggle implements ComponentInterface {
|
|||||||
this.disabledChanged();
|
this.disabledChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
if (this.gesture) {
|
if (this.gesture) {
|
||||||
this.gesture.destroy();
|
this.gesture.destroy();
|
||||||
this.gesture = undefined;
|
this.gesture = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillLoad() {
|
||||||
|
this.emitStyle();
|
||||||
|
}
|
||||||
|
|
||||||
private emitStyle() {
|
private emitStyle() {
|
||||||
this.ionStyle.emit({
|
this.ionStyle.emit({
|
||||||
'interactive-disabled': this.disabled,
|
'interactive-disabled': this.disabled,
|
||||||
|
@ -152,16 +152,14 @@ export class VirtualScroll implements ComponentInterface {
|
|||||||
this.updateVirtualScroll();
|
this.updateVirtualScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidLoad() {
|
async connectedCallback() {
|
||||||
const contentEl = this.el.closest('ion-content');
|
const contentEl = this.el.closest('ion-content');
|
||||||
if (!contentEl) {
|
if (!contentEl) {
|
||||||
console.error('virtual-scroll must be used inside ion-content');
|
console.error('<ion-virtual-scroll> must be used inside an <ion-content>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await contentEl.componentOnReady();
|
|
||||||
|
|
||||||
this.contentEl = contentEl;
|
|
||||||
this.scrollEl = await contentEl.getScrollElement();
|
this.scrollEl = await contentEl.getScrollElement();
|
||||||
|
this.contentEl = contentEl;
|
||||||
this.calcCells();
|
this.calcCells();
|
||||||
this.updateState();
|
this.updateState();
|
||||||
}
|
}
|
||||||
@ -170,7 +168,7 @@ export class VirtualScroll implements ComponentInterface {
|
|||||||
this.updateState();
|
this.updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnload() {
|
disconnectedCallback() {
|
||||||
this.scrollEl = undefined;
|
this.scrollEl = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,11 +75,13 @@ export const startInputShims = (config: Config) => {
|
|||||||
registerInput(input);
|
registerInput(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
doc.body.addEventListener('ionInputDidLoad', event => {
|
doc.addEventListener('ionInputDidLoad', ((ev: InputEvent) => {
|
||||||
registerInput(event.target as any);
|
registerInput(ev.detail);
|
||||||
});
|
}) as any);
|
||||||
|
|
||||||
doc.body.addEventListener('ionInputDidUnload', event => {
|
doc.addEventListener('ionInputDidUnload', ((ev: InputEvent) => {
|
||||||
unregisterInput(event.target as any);
|
unregisterInput(ev.detail);
|
||||||
});
|
}) as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type InputEvent = CustomEvent<HTMLElement>;
|
||||||
|
32
core/src/utils/watch-options.ts
Normal file
32
core/src/utils/watch-options.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
export const watchForOptions = <T extends HTMLElement>(containerEl: HTMLElement, tagName: string, onChange: (el: T | undefined) => void) => {
|
||||||
|
const mutation = new MutationObserver(mutationList => {
|
||||||
|
onChange(getSelectedOption<T>(mutationList, tagName));
|
||||||
|
});
|
||||||
|
mutation.observe(containerEl, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
return mutation;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectedOption = <T extends HTMLElement>(mutationList: MutationRecord[], tagName: string) => {
|
||||||
|
let newOption: T | undefined;
|
||||||
|
mutationList.forEach(mut => {
|
||||||
|
// tslint:disable-next-line: prefer-for-of
|
||||||
|
for (let i = 0; i < mut.addedNodes.length; i++) {
|
||||||
|
newOption = findCheckedOption(mut.addedNodes[i], tagName) || newOption;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newOption;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findCheckedOption = (el: any, tagName: string) => {
|
||||||
|
if (el.nodeType !== 1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const options = (el.tagName === tagName.toUpperCase())
|
||||||
|
? [el]
|
||||||
|
: Array.from(el.querySelectorAll(tagName));
|
||||||
|
|
||||||
|
return options.find((o: any) => o.checked === true);
|
||||||
|
};
|
Reference in New Issue
Block a user