fix(tab): props are reactive

This commit is contained in:
Manu Mtz.-Almeida
2018-07-12 18:44:44 +02:00
parent 43f1feccad
commit 00c4c77542
8 changed files with 164 additions and 113 deletions

View File

@ -6961,16 +6961,18 @@ declare global {
namespace StencilComponents { namespace StencilComponents {
interface IonTabButton { interface IonTabButton {
'badge': string;
'badgeColor': string;
'color': Color; 'color': Color;
'disabled': boolean;
'href': string;
'icon': string;
'label': string;
'mode': Mode; 'mode': Mode;
/** /**
* If true, the tab button will be selected. Defaults to `false`. * If true, the tab button will be selected. Defaults to `false`.
*/ */
'selected': boolean; 'selected': boolean;
/**
* The tab component for the button
*/
'tab': HTMLIonTabElement;
} }
} }
@ -6993,28 +6995,18 @@ declare global {
} }
namespace JSXElements { namespace JSXElements {
export interface IonTabButtonAttributes extends HTMLAttributes { export interface IonTabButtonAttributes extends HTMLAttributes {
'badge'?: string;
'badgeColor'?: string;
'color'?: Color; 'color'?: Color;
'disabled'?: boolean;
'href'?: string;
'icon'?: string;
'label'?: string;
'mode'?: Mode; 'mode'?: Mode;
/**
* Emitted when the tab button is loaded
*/
'onIonTabButtonDidLoad'?: (event: CustomEvent<void>) => void;
/**
* Emitted when the tab button is destroyed
*/
'onIonTabButtonDidUnload'?: (event: CustomEvent<void>) => void;
/**
* Emitted when the tab bar is clicked
*/
'onIonTabbarClick'?: (event: CustomEvent<HTMLIonTabElement>) => void;
/** /**
* If true, the tab button will be selected. Defaults to `false`. * If true, the tab button will be selected. Defaults to `false`.
*/ */
'selected'?: boolean; 'selected'?: boolean;
/**
* The tab component for the button
*/
'tab'?: HTMLIonTabElement;
} }
} }
} }
@ -7158,6 +7150,10 @@ declare global {
* Emitted when the current tab is selected. * Emitted when the current tab is selected.
*/ */
'onIonSelect'?: (event: CustomEvent<void>) => void; 'onIonSelect'?: (event: CustomEvent<void>) => void;
/**
* Emitted when the tab props mutates. Used internally.
*/
'onIonTabMutated'?: (event: CustomEvent<void>) => void;
/** /**
* If true, the tab will be selected. Defaults to `false`. * If true, the tab will be selected. Defaults to `false`.
*/ */
@ -7241,6 +7237,10 @@ declare global {
*/ */
'layout'?: TabbarLayout; 'layout'?: TabbarLayout;
'mode'?: Mode; 'mode'?: Mode;
/**
* Emitted when the tab bar is clicked
*/
'onIonTabbarClick'?: (event: CustomEvent<HTMLIonTabElement>) => void;
/** /**
* Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`. * Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`.
*/ */

View File

@ -8,11 +8,41 @@ TabButton is an internal component for tabs. Please see the [Tab docs](../tab) f
## Properties ## Properties
#### badge
string
#### badgeColor
string
#### color #### color
string string
#### disabled
boolean
#### href
string
#### icon
string
#### label
string
#### mode #### mode
string string
@ -25,20 +55,43 @@ boolean
If true, the tab button will be selected. Defaults to `false`. If true, the tab button will be selected. Defaults to `false`.
#### tab
HTMLIonTabElement
The tab component for the button
## Attributes ## Attributes
#### badge
string
#### badge-color
string
#### color #### color
string string
#### disabled
boolean
#### href
string
#### icon
string
#### label
string
#### mode #### mode
string string
@ -51,30 +104,6 @@ boolean
If true, the tab button will be selected. Defaults to `false`. If true, the tab button will be selected. Defaults to `false`.
#### tab
The tab component for the button
## Events
#### ionTabButtonDidLoad
Emitted when the tab button is loaded
#### ionTabButtonDidUnload
Emitted when the tab button is destroyed
#### ionTabbarClick
Emitted when the tab bar is clicked
---------------------------------------------- ----------------------------------------------

View File

@ -1,4 +1,4 @@
import { Component, Element, Event, EventEmitter, Listen, Prop, State } from '@stencil/core'; import { Component, Element, Prop, State } from '@stencil/core';
import { Color, Mode } from '../../interface'; import { Color, Mode } from '../../interface';
import { createColorClasses } from '../../utils/theme'; import { createColorClasses } from '../../utils/theme';
@ -24,35 +24,12 @@ export class TabButton {
* If true, the tab button will be selected. Defaults to `false`. * If true, the tab button will be selected. Defaults to `false`.
*/ */
@Prop() selected = false; @Prop() selected = false;
@Prop() label?: string;
/** The tab component for the button */ @Prop() icon?: string;
@Prop() tab!: HTMLIonTabElement; @Prop() badge?: string;
@Prop() disabled?: boolean;
/** Emitted when the tab bar is clicked */ @Prop() badgeColor?: string;
@Event() ionTabbarClick!: EventEmitter<HTMLIonTabElement>; @Prop() href?: string;
/** Emitted when the tab button is loaded */
@Event() ionTabButtonDidLoad!: EventEmitter<void>;
/** Emitted when the tab button is destroyed */
@Event() ionTabButtonDidUnload!: EventEmitter<void>;
componentDidLoad() {
this.ionTabButtonDidLoad.emit();
}
componentDidUnload() {
this.ionTabButtonDidUnload.emit();
}
@Listen('click')
protected onClick(ev: UIEvent) {
if (!this.tab.disabled) {
this.ionTabbarClick.emit(this.tab);
}
ev.stopPropagation();
ev.preventDefault();
}
private onKeyUp() { private onKeyUp() {
this.keyFocus = true; this.keyFocus = true;
@ -64,45 +41,41 @@ export class TabButton {
hostData() { hostData() {
const selected = this.selected; const selected = this.selected;
const tab = this.tab; const hasLabel = !!this.label;
const hasLabel = !!tab.label; const hasIcon = !!this.icon;
const hasIcon = !!tab.icon;
const hasLabelOnly = (hasLabel && !hasIcon); const hasLabelOnly = (hasLabel && !hasIcon);
const hasIconOnly = (hasIcon && !hasLabel); const hasIconOnly = (hasIcon && !hasLabel);
const hasBadge = !!tab.badge; const hasBadge = !!this.badge;
return { return {
'role': 'tab', 'role': 'tab',
'id': tab.btnId,
'aria-selected': selected, 'aria-selected': selected,
class: { class: {
...createColorClasses(this.color), ...createColorClasses(this.color),
'tab-hidden': !tab.show,
'tab-selected': selected, 'tab-selected': selected,
'has-label': hasLabel, 'has-label': hasLabel,
'has-icon': hasIcon, 'has-icon': hasIcon,
'has-label-only': hasLabelOnly, 'has-label-only': hasLabelOnly,
'has-icon-only': hasIconOnly, 'has-icon-only': hasIconOnly,
'has-badge': hasBadge, 'has-badge': hasBadge,
'tab-button-disabled': tab.disabled, 'tab-button-disabled': this.disabled,
'focused': this.keyFocus 'focused': this.keyFocus
} }
}; };
} }
render() { render() {
const tab = this.tab; const { icon, label, href, badge, badgeColor, mode } = this;
const href = tab.href || '#';
return [ return [
<a <a
href={href} href={href || '#'}
class="tab-button-native" class="tab-button-native"
onKeyUp={this.onKeyUp.bind(this)} onKeyUp={this.onKeyUp.bind(this)}
onBlur={this.onBlur.bind(this)}> onBlur={this.onBlur.bind(this)}>
{ tab.icon && <ion-icon class="tab-button-icon" icon={tab.icon}></ion-icon> } { icon && <ion-icon class="tab-button-icon" icon={icon}></ion-icon> }
{ tab.label && <span class="tab-button-text">{tab.label}</span> } { label && <span class="tab-button-text">{label}</span> }
{ tab.badge && <ion-badge class="tab-badge" color={tab.badgeColor}>{tab.badge}</ion-badge> } { badge && <ion-badge class="tab-badge" color={badgeColor}>{badge}</ion-badge> }
{ this.mode === 'md' && <ion-ripple-effect tapClick={true}/> } { mode === 'md' && <ion-ripple-effect tapClick={true}/> }
</a> </a>
]; ];
} }

View File

@ -217,6 +217,11 @@ If true, hide the tabs on child pages.
Emitted when the current tab is selected. Emitted when the current tab is selected.
#### ionTabMutated
Emitted when the tab props mutates. Used internally.
## Methods ## Methods
#### getTabId() #### getTabId()

View File

@ -88,6 +88,11 @@ export class Tab {
*/ */
@Event() ionSelect!: EventEmitter<void>; @Event() ionSelect!: EventEmitter<void>;
/**
* Emitted when the tab props mutates. Used internally.
*/
@Event() ionTabMutated!: EventEmitter<void>;
componentWillLoad() { componentWillLoad() {
if (Build.isDev) { if (Build.isDev) {
if (this.component && this.el.childElementCount > 0) { if (this.component && this.el.childElementCount > 0) {
@ -99,6 +104,10 @@ export class Tab {
} }
} }
componentWillUpdate() {
this.ionTabMutated.emit();
}
/** Get the Id for the tab */ /** Get the Id for the tab */
@Method() @Method()
getTabId(): string|null { getTabId(): string|null {

View File

@ -128,6 +128,13 @@ boolean
If true, the tabbar will be translucent. Defaults to `false`. If true, the tabbar will be translucent. Defaults to `false`.
## Events
#### ionTabbarClick
Emitted when the tab bar is clicked
---------------------------------------------- ----------------------------------------------

View File

@ -1,4 +1,4 @@
import { Component, Element, Listen, Prop, QueueApi, State, Watch } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Listen, Prop, QueueApi, State, Watch } from '@stencil/core';
import { Color, Mode, TabbarLayout, TabbarPlacement } from '../../interface'; import { Color, Mode, TabbarLayout, TabbarPlacement } from '../../interface';
import { createColorClasses } from '../../utils/theme'; import { createColorClasses } from '../../utils/theme';
@ -63,6 +63,9 @@ export class Tabbar {
*/ */
@Prop() translucent = false; @Prop() translucent = false;
/** Emitted when the tab bar is clicked */
@Event() ionTabbarClick!: EventEmitter<HTMLIonTabElement>;
@Listen('body:keyboardWillHide') @Listen('body:keyboardWillHide')
protected onKeyboardWillHide() { protected onKeyboardWillHide() {
setTimeout(() => this.hidden = false, 50); setTimeout(() => this.hidden = false, 50);
@ -80,9 +83,7 @@ export class Tabbar {
this.highlight && this.updateHighlight(); this.highlight && this.updateHighlight();
} }
@Listen('ionTabButtonDidLoad') componentDidLoad() {
@Listen('ionTabButtonDidUnload')
onTabButtonLoad() {
this.scrollable && this.updateBoundaries(); this.scrollable && this.updateBoundaries();
this.highlight && this.updateHighlight(); this.highlight && this.updateHighlight();
} }
@ -195,9 +196,30 @@ export class Tabbar {
} }
render() { render() {
console.log('render tabbar');
const selectedTab = this.selectedTab; const selectedTab = this.selectedTab;
const ionTabbarHighlight = this.highlight ? <div class="animated tabbar-highlight" /> as HTMLElement : null; const ionTabbarHighlight = this.highlight ? <div class="animated tabbar-highlight" /> as HTMLElement : null;
const tabButtons = this.tabs.map(tab => <ion-tab-button tab={tab} selected={selectedTab === tab} mode={this.mode} color={this.color}/>); const tabButtons = this.tabs.map(tab => <ion-tab-button
id={tab.btnId}
label={tab.label}
icon={tab.icon}
badge={tab.badge}
disabled={tab.disabled}
badgeColor={tab.badgeColor}
href={tab.href}
selected={selectedTab === tab}
mode={this.mode}
color={this.color}
class={{ 'tab-hidden': !tab.show }}
onClick={(ev) => {
if (!tab.disabled) {
this.ionTabbarClick.emit(tab);
}
ev.stopPropagation();
ev.preventDefault();
}}
/>);
if (this.scrollable) { if (this.scrollable) {
return [ return [

View File

@ -14,7 +14,7 @@ export class Tabs implements NavOutlet {
private tabsId = (++tabIds); private tabsId = (++tabIds);
private leavingTab?: HTMLIonTabElement; private leavingTab?: HTMLIonTabElement;
@Element() el!: HTMLElement; @Element() el!: HTMLStencilElement;
@State() tabs: HTMLIonTabElement[] = []; @State() tabs: HTMLIonTabElement[] = [];
@State() selectedTab?: HTMLIonTabElement; @State() selectedTab?: HTMLIonTabElement;
@ -101,11 +101,12 @@ export class Tabs implements NavOutlet {
this.loadConfig('tabbarLayout', 'icon-top'); this.loadConfig('tabbarLayout', 'icon-top');
this.loadConfig('tabbarHighlight', false); this.loadConfig('tabbarHighlight', false);
this.initTabs();
this.ionNavWillLoad.emit(); this.ionNavWillLoad.emit();
} }
async componentDidLoad() { async componentDidLoad() {
await this.initTabs();
await this.initSelect(); await this.initSelect();
} }
@ -114,8 +115,13 @@ export class Tabs implements NavOutlet {
this.selectedTab = this.leavingTab = undefined; this.selectedTab = this.leavingTab = undefined;
} }
@Listen('ionTabMutated')
protected onTabMutated() {
this.el.forceUpdate();
}
@Listen('ionTabbarClick') @Listen('ionTabbarClick')
protected tabChange(ev: CustomEvent<HTMLIonTabElement>) { protected onTabClicked(ev: CustomEvent<HTMLIonTabElement>) {
const selectedTab = ev.detail; const selectedTab = ev.detail;
if (this.useRouter && selectedTab.href != null) { if (this.useRouter && selectedTab.href != null) {
const router = this.doc.querySelector('ion-router'); const router = this.doc.querySelector('ion-router');
@ -188,20 +194,18 @@ export class Tabs implements NavOutlet {
private initTabs() { private initTabs() {
const tabs = this.tabs = Array.from(this.el.querySelectorAll('ion-tab')); const tabs = this.tabs = Array.from(this.el.querySelectorAll('ion-tab'));
const tabPromises = tabs.map(tab => { tabs.forEach(tab => {
const id = `t-${this.tabsId}-${++this.ids}`; const id = `t-${this.tabsId}-${++this.ids}`;
tab.btnId = 'tab-' + id; tab.btnId = 'tab-' + id;
tab.id = 'tabpanel-' + id; tab.id = 'tabpanel-' + id;
return tab.componentOnReady();
}); });
return Promise.all(tabPromises);
} }
private async initSelect(): Promise<void> { private async initSelect(): Promise<void> {
const tabs = this.tabs;
if (this.useRouter) { if (this.useRouter) {
if (Build.isDev) { if (Build.isDev) {
const selectedTab = this.tabs.find(t => t.selected); const selectedTab = tabs.find(t => t.selected);
if (selectedTab) { if (selectedTab) {
console.warn('When using a router (ion-router) <ion-tab selected="true"> makes no difference' + console.warn('When using a router (ion-router) <ion-tab selected="true"> makes no difference' +
'Define routes properly the define which tab is selected'); 'Define routes properly the define which tab is selected');
@ -210,11 +214,11 @@ export class Tabs implements NavOutlet {
return; return;
} }
// find pre-selected tabs // find pre-selected tabs
const selectedTab = this.tabs.find(t => t.selected) || const selectedTab = tabs.find(t => t.selected) ||
this.tabs.find(t => t.show && !t.disabled); tabs.find(t => t.show && !t.disabled);
// reset all tabs none is selected // reset all tabs none is selected
for (const tab of this.tabs) { for (const tab of tabs) {
if (tab !== selectedTab) { if (tab !== selectedTab) {
tab.selected = false; tab.selected = false;
} }
@ -302,9 +306,11 @@ export class Tabs implements NavOutlet {
]; ];
if (!this.tabbarHidden) { if (!this.tabbarHidden) {
console.log('render tabs');
dom.push( dom.push(
<ion-tabbar <ion-tabbar
tabs={this.tabs} tabs={this.tabs.slice()}
color={this.color} color={this.color}
selectedTab={this.selectedTab} selectedTab={this.selectedTab}
highlight={this.tabbarHighlight} highlight={this.tabbarHighlight}