From 00c4c775426d5bd1376359963baac67d215be511 Mon Sep 17 00:00:00 2001 From: "Manu Mtz.-Almeida" Date: Thu, 12 Jul 2018 18:44:44 +0200 Subject: [PATCH] fix(tab): props are reactive --- core/src/components.d.ts | 40 ++++---- core/src/components/tab-button/readme.md | 91 ++++++++++++------- core/src/components/tab-button/tab-button.tsx | 61 ++++--------- core/src/components/tab/readme.md | 5 + core/src/components/tab/tab.tsx | 9 ++ core/src/components/tabbar/readme.md | 7 ++ core/src/components/tabbar/tabbar.tsx | 34 +++++-- core/src/components/tabs/tabs.tsx | 30 +++--- 8 files changed, 164 insertions(+), 113 deletions(-) diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 5a061e73fd..a934319cef 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -6961,16 +6961,18 @@ declare global { namespace StencilComponents { interface IonTabButton { + 'badge': string; + 'badgeColor': string; 'color': Color; + 'disabled': boolean; + 'href': string; + 'icon': string; + 'label': string; 'mode': Mode; /** * If true, the tab button will be selected. Defaults to `false`. */ 'selected': boolean; - /** - * The tab component for the button - */ - 'tab': HTMLIonTabElement; } } @@ -6993,28 +6995,18 @@ declare global { } namespace JSXElements { export interface IonTabButtonAttributes extends HTMLAttributes { + 'badge'?: string; + 'badgeColor'?: string; 'color'?: Color; + 'disabled'?: boolean; + 'href'?: string; + 'icon'?: string; + 'label'?: string; 'mode'?: Mode; - /** - * Emitted when the tab button is loaded - */ - 'onIonTabButtonDidLoad'?: (event: CustomEvent) => void; - /** - * Emitted when the tab button is destroyed - */ - 'onIonTabButtonDidUnload'?: (event: CustomEvent) => void; - /** - * Emitted when the tab bar is clicked - */ - 'onIonTabbarClick'?: (event: CustomEvent) => void; /** * If true, the tab button will be selected. Defaults to `false`. */ 'selected'?: boolean; - /** - * The tab component for the button - */ - 'tab'?: HTMLIonTabElement; } } } @@ -7158,6 +7150,10 @@ declare global { * Emitted when the current tab is selected. */ 'onIonSelect'?: (event: CustomEvent) => void; + /** + * Emitted when the tab props mutates. Used internally. + */ + 'onIonTabMutated'?: (event: CustomEvent) => void; /** * If true, the tab will be selected. Defaults to `false`. */ @@ -7241,6 +7237,10 @@ declare global { */ 'layout'?: TabbarLayout; 'mode'?: Mode; + /** + * Emitted when the tab bar is clicked + */ + 'onIonTabbarClick'?: (event: CustomEvent) => void; /** * Set the position of the tabbar, relative to the content. Available options: `"top"`, `"bottom"`. */ diff --git a/core/src/components/tab-button/readme.md b/core/src/components/tab-button/readme.md index 77406bcc65..bc9d64413a 100644 --- a/core/src/components/tab-button/readme.md +++ b/core/src/components/tab-button/readme.md @@ -8,11 +8,41 @@ TabButton is an internal component for tabs. Please see the [Tab docs](../tab) f ## Properties +#### badge + +string + + +#### badgeColor + +string + + #### color string +#### disabled + +boolean + + +#### href + +string + + +#### icon + +string + + +#### label + +string + + #### mode string @@ -25,20 +55,43 @@ boolean If true, the tab button will be selected. Defaults to `false`. -#### tab - -HTMLIonTabElement - -The tab component for the button - - ## Attributes +#### badge + +string + + +#### badge-color + +string + + #### color string +#### disabled + +boolean + + +#### href + +string + + +#### icon + +string + + +#### label + +string + + #### mode string @@ -51,30 +104,6 @@ boolean 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 - - ---------------------------------------------- diff --git a/core/src/components/tab-button/tab-button.tsx b/core/src/components/tab-button/tab-button.tsx index 378e950d8a..a3abf2c74b 100644 --- a/core/src/components/tab-button/tab-button.tsx +++ b/core/src/components/tab-button/tab-button.tsx @@ -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 { createColorClasses } from '../../utils/theme'; @@ -24,35 +24,12 @@ export class TabButton { * If true, the tab button will be selected. Defaults to `false`. */ @Prop() selected = false; - - /** The tab component for the button */ - @Prop() tab!: HTMLIonTabElement; - - /** Emitted when the tab bar is clicked */ - @Event() ionTabbarClick!: EventEmitter; - - /** Emitted when the tab button is loaded */ - @Event() ionTabButtonDidLoad!: EventEmitter; - - /** Emitted when the tab button is destroyed */ - @Event() ionTabButtonDidUnload!: EventEmitter; - - 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(); - } + @Prop() label?: string; + @Prop() icon?: string; + @Prop() badge?: string; + @Prop() disabled?: boolean; + @Prop() badgeColor?: string; + @Prop() href?: string; private onKeyUp() { this.keyFocus = true; @@ -64,45 +41,41 @@ export class TabButton { hostData() { const selected = this.selected; - const tab = this.tab; - const hasLabel = !!tab.label; - const hasIcon = !!tab.icon; + const hasLabel = !!this.label; + const hasIcon = !!this.icon; const hasLabelOnly = (hasLabel && !hasIcon); const hasIconOnly = (hasIcon && !hasLabel); - const hasBadge = !!tab.badge; + const hasBadge = !!this.badge; return { 'role': 'tab', - 'id': tab.btnId, 'aria-selected': selected, class: { ...createColorClasses(this.color), - 'tab-hidden': !tab.show, 'tab-selected': selected, 'has-label': hasLabel, 'has-icon': hasIcon, 'has-label-only': hasLabelOnly, 'has-icon-only': hasIconOnly, 'has-badge': hasBadge, - 'tab-button-disabled': tab.disabled, + 'tab-button-disabled': this.disabled, 'focused': this.keyFocus } }; } render() { - const tab = this.tab; - const href = tab.href || '#'; + const { icon, label, href, badge, badgeColor, mode } = this; return [ - { tab.icon && } - { tab.label && {tab.label} } - { tab.badge && {tab.badge} } - { this.mode === 'md' && } + { icon && } + { label && {label} } + { badge && {badge} } + { mode === 'md' && } ]; } diff --git a/core/src/components/tab/readme.md b/core/src/components/tab/readme.md index ccea35f366..1194e0e17c 100644 --- a/core/src/components/tab/readme.md +++ b/core/src/components/tab/readme.md @@ -217,6 +217,11 @@ If true, hide the tabs on child pages. Emitted when the current tab is selected. +#### ionTabMutated + +Emitted when the tab props mutates. Used internally. + + ## Methods #### getTabId() diff --git a/core/src/components/tab/tab.tsx b/core/src/components/tab/tab.tsx index 926831130a..518e8fb437 100644 --- a/core/src/components/tab/tab.tsx +++ b/core/src/components/tab/tab.tsx @@ -88,6 +88,11 @@ export class Tab { */ @Event() ionSelect!: EventEmitter; + /** + * Emitted when the tab props mutates. Used internally. + */ + @Event() ionTabMutated!: EventEmitter; + componentWillLoad() { if (Build.isDev) { if (this.component && this.el.childElementCount > 0) { @@ -99,6 +104,10 @@ export class Tab { } } + componentWillUpdate() { + this.ionTabMutated.emit(); + } + /** Get the Id for the tab */ @Method() getTabId(): string|null { diff --git a/core/src/components/tabbar/readme.md b/core/src/components/tabbar/readme.md index c8ba1829c1..d480c037ba 100644 --- a/core/src/components/tabbar/readme.md +++ b/core/src/components/tabbar/readme.md @@ -128,6 +128,13 @@ boolean If true, the tabbar will be translucent. Defaults to `false`. +## Events + +#### ionTabbarClick + +Emitted when the tab bar is clicked + + ---------------------------------------------- diff --git a/core/src/components/tabbar/tabbar.tsx b/core/src/components/tabbar/tabbar.tsx index ce715ce669..98e3fde843 100644 --- a/core/src/components/tabbar/tabbar.tsx +++ b/core/src/components/tabbar/tabbar.tsx @@ -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 { createColorClasses } from '../../utils/theme'; @@ -63,6 +63,9 @@ export class Tabbar { */ @Prop() translucent = false; + /** Emitted when the tab bar is clicked */ + @Event() ionTabbarClick!: EventEmitter; + @Listen('body:keyboardWillHide') protected onKeyboardWillHide() { setTimeout(() => this.hidden = false, 50); @@ -80,9 +83,7 @@ export class Tabbar { this.highlight && this.updateHighlight(); } - @Listen('ionTabButtonDidLoad') - @Listen('ionTabButtonDidUnload') - onTabButtonLoad() { + componentDidLoad() { this.scrollable && this.updateBoundaries(); this.highlight && this.updateHighlight(); } @@ -195,9 +196,30 @@ export class Tabbar { } render() { + console.log('render tabbar'); + const selectedTab = this.selectedTab; - const ionTabbarHighlight = this.highlight ?
as HTMLElement : null; - const tabButtons = this.tabs.map(tab => ); + const ionTabbarHighlight = this.highlight ?
as HTMLElement : null; + const tabButtons = this.tabs.map(tab => { + if (!tab.disabled) { + this.ionTabbarClick.emit(tab); + } + ev.stopPropagation(); + ev.preventDefault(); + }} + />); if (this.scrollable) { return [ diff --git a/core/src/components/tabs/tabs.tsx b/core/src/components/tabs/tabs.tsx index 2fdc56024d..4f640de242 100644 --- a/core/src/components/tabs/tabs.tsx +++ b/core/src/components/tabs/tabs.tsx @@ -14,7 +14,7 @@ export class Tabs implements NavOutlet { private tabsId = (++tabIds); private leavingTab?: HTMLIonTabElement; - @Element() el!: HTMLElement; + @Element() el!: HTMLStencilElement; @State() tabs: HTMLIonTabElement[] = []; @State() selectedTab?: HTMLIonTabElement; @@ -101,11 +101,12 @@ export class Tabs implements NavOutlet { this.loadConfig('tabbarLayout', 'icon-top'); this.loadConfig('tabbarHighlight', false); + this.initTabs(); + this.ionNavWillLoad.emit(); } async componentDidLoad() { - await this.initTabs(); await this.initSelect(); } @@ -114,8 +115,13 @@ export class Tabs implements NavOutlet { this.selectedTab = this.leavingTab = undefined; } + @Listen('ionTabMutated') + protected onTabMutated() { + this.el.forceUpdate(); + } + @Listen('ionTabbarClick') - protected tabChange(ev: CustomEvent) { + protected onTabClicked(ev: CustomEvent) { const selectedTab = ev.detail; if (this.useRouter && selectedTab.href != null) { const router = this.doc.querySelector('ion-router'); @@ -188,20 +194,18 @@ export class Tabs implements NavOutlet { private initTabs() { 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}`; tab.btnId = 'tab-' + id; tab.id = 'tabpanel-' + id; - return tab.componentOnReady(); }); - - return Promise.all(tabPromises); } private async initSelect(): Promise { + const tabs = this.tabs; if (this.useRouter) { if (Build.isDev) { - const selectedTab = this.tabs.find(t => t.selected); + const selectedTab = tabs.find(t => t.selected); if (selectedTab) { console.warn('When using a router (ion-router) makes no difference' + 'Define routes properly the define which tab is selected'); @@ -210,11 +214,11 @@ export class Tabs implements NavOutlet { return; } // find pre-selected tabs - const selectedTab = this.tabs.find(t => t.selected) || - this.tabs.find(t => t.show && !t.disabled); + const selectedTab = tabs.find(t => t.selected) || + tabs.find(t => t.show && !t.disabled); // reset all tabs none is selected - for (const tab of this.tabs) { + for (const tab of tabs) { if (tab !== selectedTab) { tab.selected = false; } @@ -302,9 +306,11 @@ export class Tabs implements NavOutlet { ]; if (!this.tabbarHidden) { + console.log('render tabs'); + dom.push(