mdui
Version:
a CSS Framework based on material design
373 lines (315 loc) • 8.99 kB
text/typescript
import $ from 'mdui.jq/es/$';
import extend from 'mdui.jq/es/functions/extend';
import { JQ } from 'mdui.jq/es/JQ';
import 'mdui.jq/es/methods/addClass';
import 'mdui.jq/es/methods/appendTo';
import 'mdui.jq/es/methods/attr';
import 'mdui.jq/es/methods/children';
import 'mdui.jq/es/methods/css';
import 'mdui.jq/es/methods/each';
import 'mdui.jq/es/methods/eq';
import 'mdui.jq/es/methods/first';
import 'mdui.jq/es/methods/get';
import 'mdui.jq/es/methods/hasClass';
import 'mdui.jq/es/methods/hide';
import 'mdui.jq/es/methods/index';
import 'mdui.jq/es/methods/innerWidth';
import 'mdui.jq/es/methods/offset';
import 'mdui.jq/es/methods/on';
import 'mdui.jq/es/methods/removeClass';
import 'mdui.jq/es/methods/show';
import Selector from 'mdui.jq/es/types/Selector';
import { isNumber } from 'mdui.jq/es/utils';
import mdui from '../../mdui';
import '../../jq_extends/static/throttle';
import { componentEvent } from '../../utils/componentEvent';
import { $window } from '../../utils/dom';
declare module '../../interfaces/MduiStatic' {
interface MduiStatic {
/**
* Tab 选项卡组件
*
* 请通过 `new mdui.Tab()` 调用
*/
Tab: {
/**
* 实例化 Tab 组件
* @param selector CSS 选择器、或 DOM 元素、或 JQ 对象
* @param options 配置参数
*/
new (
selector: Selector | HTMLElement | ArrayLike<HTMLElement>,
options?: OPTIONS,
): Tab;
};
}
}
type OPTIONS = {
/**
* 切换选项卡的触发方式。`click`: 点击切换;`hover`: 鼠标悬浮切换
*/
trigger?: 'click' | 'hover';
/**
* 是否启用循环切换,若为 `true`,则最后一个选项激活时调用 `next` 方法将回到第一个选项,第一个选项激活时调用 `prev` 方法将回到最后一个选项。
*/
loop?: boolean;
};
type EVENT = 'change' | 'show';
const DEFAULT_OPTIONS: OPTIONS = {
trigger: 'click',
loop: false,
};
class Tab {
/**
* tab 元素的 JQ 对象
*/
public $element: JQ;
/**
* 配置参数
*/
public options: OPTIONS = extend({}, DEFAULT_OPTIONS);
/**
* 当前激活的 tab 的索引号。为 -1 时表示没有激活的选项卡,或不存在选项卡
*/
public activeIndex = -1;
/**
* 选项数组 JQ 对象
*/
private $tabs: JQ;
/**
* 激活状态的 tab 底部的指示符
*/
private $indicator: JQ;
public constructor(
selector: Selector | HTMLElement | ArrayLike<HTMLElement>,
options: OPTIONS = {},
) {
this.$element = $(selector).first();
extend(this.options, options);
this.$tabs = this.$element.children('a');
this.$indicator = $('<div class="mdui-tab-indicator"></div>').appendTo(
this.$element,
);
// 根据 url hash 获取默认激活的选项卡
const hash = window.location.hash;
if (hash) {
this.$tabs.each((index, tab) => {
if ($(tab).attr('href') === hash) {
this.activeIndex = index;
return false;
}
return true;
});
}
// 含 .mdui-tab-active 的元素默认激活
if (this.activeIndex === -1) {
this.$tabs.each((index, tab) => {
if ($(tab).hasClass('mdui-tab-active')) {
this.activeIndex = index;
return false;
}
return true;
});
}
// 存在选项卡时,默认激活第一个选项卡
if (this.$tabs.length && this.activeIndex === -1) {
this.activeIndex = 0;
}
// 设置激活状态选项卡
this.setActive();
// 监听窗口大小变化事件,调整指示器位置
$window.on(
'resize',
$.throttle(() => this.setIndicatorPosition(), 100),
);
// 监听点击选项卡事件
this.$tabs.each((_, tab) => {
this.bindTabEvent(tab);
});
}
/**
* 指定选项卡是否已禁用
* @param $tab
*/
private isDisabled($tab: JQ): boolean {
return $tab.attr('disabled') !== undefined;
}
/**
* 绑定在 Tab 上点击或悬浮的事件
* @param tab
*/
private bindTabEvent(tab: HTMLElement): void {
const $tab = $(tab);
// 点击或鼠标移入触发的事件
const clickEvent = (): void | false => {
// 禁用状态的选项卡无法选中
if (this.isDisabled($tab)) {
return false;
}
this.activeIndex = this.$tabs.index(tab);
this.setActive();
};
// 无论 trigger 是 click 还是 hover,都会响应 click 事件
$tab.on('click', clickEvent);
// trigger 为 hover 时,额外响应 mouseenter 事件
if (this.options.trigger === 'hover') {
$tab.on('mouseenter', clickEvent);
}
// 阻止链接的默认点击动作
$tab.on('click', (): void | false => {
if (($tab.attr('href') || '').indexOf('#') === 0) {
return false;
}
});
}
/**
* 触发组件事件
* @param name
* @param $element
* @param parameters
*/
private triggerEvent(name: EVENT, $element: JQ, parameters = {}): void {
componentEvent(name, 'tab', $element, this, parameters);
}
/**
* 设置激活状态的选项卡
*/
private setActive(): void {
this.$tabs.each((index, tab) => {
const $tab = $(tab);
const targetId = $tab.attr('href') || '';
// 设置选项卡激活状态
if (index === this.activeIndex && !this.isDisabled($tab)) {
if (!$tab.hasClass('mdui-tab-active')) {
this.triggerEvent('change', this.$element, {
index: this.activeIndex,
id: targetId.substr(1),
});
this.triggerEvent('show', $tab);
$tab.addClass('mdui-tab-active');
}
$(targetId).show();
this.setIndicatorPosition();
} else {
$tab.removeClass('mdui-tab-active');
$(targetId).hide();
}
});
}
/**
* 设置选项卡指示器的位置
*/
private setIndicatorPosition(): void {
// 选项卡数量为 0 时,不显示指示器
if (this.activeIndex === -1) {
this.$indicator.css({
left: 0,
width: 0,
});
return;
}
const $activeTab = this.$tabs.eq(this.activeIndex);
if (this.isDisabled($activeTab)) {
return;
}
const activeTabOffset = $activeTab.offset();
this.$indicator.css({
left: `${
activeTabOffset.left +
this.$element[0].scrollLeft -
this.$element[0].getBoundingClientRect().left
}px`,
width: `${$activeTab.innerWidth()}px`,
});
}
/**
* 切换到下一个选项卡
*/
public next(): void {
if (this.activeIndex === -1) {
return;
}
if (this.$tabs.length > this.activeIndex + 1) {
this.activeIndex++;
} else if (this.options.loop) {
this.activeIndex = 0;
}
this.setActive();
}
/**
* 切换到上一个选项卡
*/
public prev(): void {
if (this.activeIndex === -1) {
return;
}
if (this.activeIndex > 0) {
this.activeIndex--;
} else if (this.options.loop) {
this.activeIndex = this.$tabs.length - 1;
}
this.setActive();
}
/**
* 显示指定索引号、或指定id的选项卡
* @param index 索引号、或id
*/
public show(index: number | string): void {
if (this.activeIndex === -1) {
return;
}
if (isNumber(index)) {
this.activeIndex = index;
} else {
this.$tabs.each((i, tab): void | false => {
if (tab.id === index) {
this.activeIndex = i;
return false;
}
});
}
this.setActive();
}
/**
* 在父元素的宽度变化时,需要调用该方法重新调整指示器位置
* 在添加或删除选项卡时,需要调用该方法
*/
public handleUpdate(): void {
const $oldTabs = this.$tabs; // 旧的 tabs JQ对象
const $newTabs = this.$element.children('a'); // 新的 tabs JQ对象
const oldTabsElement = $oldTabs.get(); // 旧的 tabs 元素数组
const newTabsElement = $newTabs.get(); // 新的 tabs 元素数组
if (!$newTabs.length) {
this.activeIndex = -1;
this.$tabs = $newTabs;
this.setIndicatorPosition();
return;
}
// 重新遍历选项卡,找出新增的选项卡
$newTabs.each((index, tab) => {
// 有新增的选项卡
if (oldTabsElement.indexOf(tab) < 0) {
this.bindTabEvent(tab);
if (this.activeIndex === -1) {
this.activeIndex = 0;
} else if (index <= this.activeIndex) {
this.activeIndex++;
}
}
});
// 找出被移除的选项卡
$oldTabs.each((index, tab) => {
// 有被移除的选项卡
if (newTabsElement.indexOf(tab) < 0) {
if (index < this.activeIndex) {
this.activeIndex--;
} else if (index === this.activeIndex) {
this.activeIndex = 0;
}
}
});
this.$tabs = $newTabs;
this.setActive();
}
}
mdui.Tab = Tab;