rabbit-simple-ui
Version:
A simple UI component library based on JavaScript
276 lines (227 loc) • 9.73 kB
text/typescript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { $el, bind, removeAttrs, siblings, slider } from '../../dom-utils';
import {
getArrTypeAttr,
getBooleanTypeAttr,
getStrTypeAttr,
setCss,
setHtml
} from '../../dom-utils/elem';
import { type, validComps } from '../../utils';
import PREFIX from '../prefix';
interface CollapseEvents {
onChange?: (index: []) => void;
}
interface Config {
config(
el: string
): {
activeIndex: string | number | string[] | number[];
events({ onChange }: CollapseEvents): void;
};
}
class Collapse implements Config {
readonly VERSION: string;
readonly COMPONENTS: NodeListOf<Element>;
constructor() {
this.VERSION = 'v1.0';
this.COMPONENTS = $el('r-collapse', { all: true });
this._create(this.COMPONENTS);
}
public config(
el: string
): {
activeIndex: string | number | string[] | number[];
events({ onChange }: { onChange: (key: []) => void }): void;
} {
const target = $el(el);
validComps(target, 'collapse');
const { _attrs, _dataSetActiveKey, _openByKey } = Collapse.prototype;
const activeIndex = JSON.parse(target.dataset['activeIndex']);
return {
get activeIndex() {
return activeIndex;
},
set activeIndex(newVal: string | number | string[] | number[]) {
if (newVal == activeIndex) return;
_dataSetActiveKey(target, newVal);
_openByKey(target, newVal, target.getAttribute('accordion'));
},
events({ onChange }) {
const panels = target.querySelectorAll('r-collapse-panel');
// 储存已展开面板的 key
const key: string[] = [];
const pushKey = (el: Element, toggle: boolean) => {
const { accordion } = _attrs(target);
// @ts-ignore
const panelKey = el.dataset.panelKey;
if (el.classList.contains(`${PREFIX.collapse}-item-active`)) {
key.push(panelKey);
} else if (toggle) {
const idx = key.indexOf(panelKey);
idx > -1 ? key.splice(idx, 1) : '';
}
// 手风琴模式下每展开一个面板就删除其他的 key
if (accordion) {
siblings(el).forEach((o) => {
const otherIdx = key.indexOf(o.dataset.panelKey);
otherIdx > -1 ? key.splice(otherIdx, 1) : '';
});
}
};
panels.forEach((panel: Element) => {
const header = panel.querySelector(`.${PREFIX.collapse}-header`);
// 存放初始化面板时默认已展开的 key
pushKey(panel, false);
bind(header, 'click', () => {
// 这里用定时器是为了跟移除显示面板样式类名的时机同步
setTimeout(() => {
pushKey(panel, true);
onChange && type.isFn(onChange, key);
}, 150);
});
});
}
};
}
private _create(COMPONENTS: NodeListOf<Element>): void {
COMPONENTS.forEach((node) => {
const { simple, ghost, activekey, accordion } = this._attrs(node);
this._dataSetActiveKey(node, activekey);
this._setGhost(node, ghost);
this._setSimple(node, simple);
this._createChildNodes(node);
this._openByKey(node, activekey, accordion);
removeAttrs(node, ['simple', 'ghost', 'active-key']);
});
}
private _dataSetActiveKey(
node: Element,
activeIndex: string | number | string[] | number[]
): void {
if (activeIndex) {
// @ts-ignore
node.dataset['activeIndex'] = Array.isArray(activeIndex)
? `[${activeIndex}]`
: activeIndex;
}
}
private _setGhost(node: Element, ghost: boolean): void {
ghost ? node.classList.add(`${PREFIX.collapse}-ghost`) : '';
}
private _setSimple(node: Element, simple: boolean): void {
simple ? node.classList.add(`${PREFIX.collapse}-simple`) : '';
}
private _createChildNodes(node: Element): void {
const collapsePanels = node.querySelectorAll('r-collapse-panel');
this._setPanel(node, collapsePanels);
}
private _setPanel(parent: Element, panels: NodeListOf<Element>): void {
// 遍历所有面板节点
panels.forEach((panel, index) => {
const { index: key, title, hideArrow } = this._attrs(panel);
// @ts-ignore
// 面板的 key 如果没填则默认为索引值
panel.dataset.panelKey = !key ? index : key;
// 保存面板原先的第一个节点,也就是要填充的内容
const content = panel.firstElementChild;
const arrowIcon = `<i class="${PREFIX.icon} ${PREFIX.icon}-ios-arrow-forward"></i>`;
const template = `
<div class="${PREFIX.collapse}-header">
${!hideArrow ? arrowIcon : ''} ${title}
</div>
<div class="${PREFIX.collapse}-content">
<div class="${PREFIX.collapse}-content-box"></div>
</div>`;
// 清空面板原先的所有内容
setHtml(panel, '');
// 追加html模板
setHtml(panel, template);
// 将原先的占位内容填充至面板内容盒子
const panelContentBox = panel.querySelector(`.${PREFIX.collapse}-content-box`);
content ? panelContentBox?.appendChild(content) : null;
setCss(panel, 'display', 'block');
this._handleToggle(parent, panel);
removeAttrs(panel, ['index', 'title', 'hide-arrow']);
});
}
private _handleToggle(parent: Element, panel: Element): void {
const { accordion } = this._attrs(parent);
const collapseHeader = panel.querySelector(`.${PREFIX.collapse}-header`);
const collapseContent = panel.querySelector(`.${PREFIX.collapse}-content`);
bind(collapseHeader, 'click', () => this._slide(panel, collapseContent!, accordion));
}
private _slide(p: Element, c: Element, accordion: boolean): void {
const activeCls = `${PREFIX.collapse}-item-active`;
const slideUp = (arg1: Element, arg2: Element) => {
slider.slideUp(arg2, 150);
setTimeout(() => {
arg1.classList.remove(activeCls);
}, 150);
};
if (p.classList.contains(activeCls)) {
slideUp(p, c);
} else {
slider.slideDown(c, 150);
p.classList.add(activeCls);
}
// 手风琴模式
if (accordion) {
// 获取除了已展开的面板外的所有其他面板
siblings(p).forEach((otherP) => {
const otherC = otherP.querySelector(`.${PREFIX.collapse}-content`);
slideUp(otherP, otherC);
});
}
}
private _openByKey(
node: Element,
key: string | number | string[] | number[],
accordion: boolean
): void {
// 获取选中的 key 的面板
let selected: Element | null;
const open = () => {
if (selected) {
selected.classList.add(`${PREFIX.collapse}-item-active`);
if (accordion) {
siblings(selected).forEach((o) => {
o.classList.remove(`${PREFIX.collapse}-item-active`);
});
}
}
};
// 如果 activeIndex 是数组则对其进行遍历获取所有面板
// 且如果是手风琴模式则只选取数组的第一项,只展开一个面板
if (Array.isArray(key)) {
const { length } = key;
// length == 1 说明数组只有一项所以不必对其进行遍历
if (accordion || length == 1) {
selected = node.querySelector(`[data-panel-key="${key[0]}"]`);
open();
} else {
let i = 0;
for (; i < length; i++) {
selected = node.querySelector(`[data-panel-key="${key[i]}"]`);
open();
}
}
} else {
selected = node.querySelector(`[data-panel-key="${key}"]`);
open();
}
}
private _attrs(node: Element) {
return {
index: getStrTypeAttr(node, 'index', ''),
title: getStrTypeAttr(node, 'title', ''),
ghost: getBooleanTypeAttr(node, 'ghost'),
simple: getBooleanTypeAttr(node, 'simple'),
hideArrow: getBooleanTypeAttr(node, 'hide-arrow'),
accordion: getBooleanTypeAttr(node, 'accordion'),
activekey:
getStrTypeAttr(node, 'active-index', '') && getArrTypeAttr(node, 'active-index')
};
}
}
export default Collapse;