Files
ionic-framework/core/src/components/tabs/tabs.tsx
Shawn Taylor db121ac473 docs(tabs): document methods only available for vanilla (#27269)
When using Angular, React, or Vue, the use of tabs is coupled to the
framework's routing. These methods are only available when using tabs
with vanilla JavaScript.

Issue number: N/A

---------

<!-- Please refer to our contributing documentation for any questions on
submitting a pull request, or let us know here if you need any help:
https://ionicframework.com/docs/building/contributing -->

<!-- Some docs updates need to be made in the `ionic-docs` repo, in a
separate PR. See
https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#modifying-documentation
for details. -->

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Adds more context to the description of the methods on tabs

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
2023-04-26 14:57:00 +00:00

207 lines
6.1 KiB
TypeScript

import type { EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, h } from '@stencil/core';
import type { NavOutlet, RouteID, RouteWrite } from '../router/utils/interface';
import type { TabButtonClickEventDetail } from '../tab-bar/tab-bar-interface';
/**
* @slot - Content is placed between the named slots if provided without a slot.
* @slot top - Content is placed at the top of the screen.
* @slot bottom - Content is placed at the bottom of the screen.
*/
@Component({
tag: 'ion-tabs',
styleUrl: 'tabs.scss',
shadow: true,
})
export class Tabs implements NavOutlet {
private transitioning = false;
private leavingTab?: HTMLIonTabElement;
@Element() el!: HTMLIonTabsElement;
@State() selectedTab?: HTMLIonTabElement;
/** @internal */
@Prop({ mutable: true }) useRouter = false;
/**
* Emitted when the navigation will load a component.
* @internal
*/
@Event() ionNavWillLoad!: EventEmitter<void>;
/**
* Emitted when the navigation is about to transition to a new component.
*/
@Event({ bubbles: false }) ionTabsWillChange!: EventEmitter<{ tab: string }>;
/**
* Emitted when the navigation has finished transitioning to a new component.
*/
@Event({ bubbles: false }) ionTabsDidChange!: EventEmitter<{ tab: string }>;
async componentWillLoad() {
if (!this.useRouter) {
this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]');
}
if (!this.useRouter) {
const tabs = this.tabs;
if (tabs.length > 0) {
await this.select(tabs[0]);
}
}
this.ionNavWillLoad.emit();
}
componentWillRender() {
const tabBar = this.el.querySelector('ion-tab-bar');
if (tabBar) {
const tab = this.selectedTab ? this.selectedTab.tab : undefined;
tabBar.selectedTab = tab;
}
}
/**
* Select a tab by the value of its `tab` property or an element reference. This method is only available for vanilla JavaScript projects. The Angular, React, and Vue implementations of tabs are coupled to each framework's router.
*
* @param tab The tab instance to select. If passed a string, it should be the value of the tab's `tab` property.
*/
@Method()
async select(tab: string | HTMLIonTabElement): Promise<boolean> {
const selectedTab = getTab(this.tabs, tab);
if (!this.shouldSwitch(selectedTab)) {
return false;
}
await this.setActive(selectedTab);
await this.notifyRouter();
this.tabSwitch();
return true;
}
/**
* Get a specific tab by the value of its `tab` property or an element reference. This method is only available for vanilla JavaScript projects. The Angular, React, and Vue implementations of tabs are coupled to each framework's router.
*
* @param tab The tab instance to select. If passed a string, it should be the value of the tab's `tab` property.
*/
@Method()
async getTab(tab: string | HTMLIonTabElement): Promise<HTMLIonTabElement | undefined> {
return getTab(this.tabs, tab);
}
/**
* Get the currently selected tab. This method is only available for vanilla JavaScript projects. The Angular, React, and Vue implementations of tabs are coupled to each framework's router.
*/
@Method()
getSelected(): Promise<string | undefined> {
return Promise.resolve(this.selectedTab ? this.selectedTab.tab : undefined);
}
/** @internal */
@Method()
async setRouteId(id: string): Promise<RouteWrite> {
const selectedTab = getTab(this.tabs, id);
if (!this.shouldSwitch(selectedTab)) {
return { changed: false, element: this.selectedTab };
}
await this.setActive(selectedTab);
return {
changed: true,
element: this.selectedTab,
markVisible: () => this.tabSwitch(),
};
}
/** @internal */
@Method()
async getRouteId(): Promise<RouteID | undefined> {
const tabId = this.selectedTab?.tab;
return tabId !== undefined ? { id: tabId, element: this.selectedTab } : undefined;
}
private setActive(selectedTab: HTMLIonTabElement): Promise<void> {
if (this.transitioning) {
return Promise.reject('transitioning already happening');
}
this.transitioning = true;
this.leavingTab = this.selectedTab;
this.selectedTab = selectedTab;
this.ionTabsWillChange.emit({ tab: selectedTab.tab });
selectedTab.active = true;
return Promise.resolve();
}
private tabSwitch() {
const selectedTab = this.selectedTab;
const leavingTab = this.leavingTab;
this.leavingTab = undefined;
this.transitioning = false;
if (!selectedTab) {
return;
}
if (leavingTab !== selectedTab) {
if (leavingTab) {
leavingTab.active = false;
}
this.ionTabsDidChange.emit({ tab: selectedTab.tab });
}
}
private notifyRouter() {
if (this.useRouter) {
const router = document.querySelector('ion-router');
if (router) {
return router.navChanged('forward');
}
}
return Promise.resolve(false);
}
private shouldSwitch(selectedTab: HTMLIonTabElement | undefined): selectedTab is HTMLIonTabElement {
const leavingTab = this.selectedTab;
return selectedTab !== undefined && selectedTab !== leavingTab && !this.transitioning;
}
private get tabs() {
return Array.from(this.el.querySelectorAll('ion-tab'));
}
private onTabClicked = (ev: CustomEvent<TabButtonClickEventDetail>) => {
const { href, tab } = ev.detail;
if (this.useRouter && href !== undefined) {
const router = document.querySelector('ion-router');
if (router) {
router.push(href);
}
} else {
this.select(tab);
}
};
render() {
return (
<Host onIonTabButtonClick={this.onTabClicked}>
<slot name="top"></slot>
<div class="tabs-inner">
<slot></slot>
</div>
<slot name="bottom"></slot>
</Host>
);
}
}
const getTab = (tabs: HTMLIonTabElement[], tab: string | HTMLIonTabElement): HTMLIonTabElement | undefined => {
const tabEl = typeof tab === 'string' ? tabs.find((t) => t.tab === tab) : tab;
if (!tabEl) {
console.error(`tab with id: "${tabEl}" does not exist`);
}
return tabEl;
};