UNPKG

rabbit-design

Version:

A lightweight UI plugin library written in TypeScript and based on JavaScript

278 lines (222 loc) 10.1 kB
/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { warn } from '../../mixins'; import { prevAll, nextAll, removeAttrs, $el, setText, setHtml } from '../../dom-utils'; import { type, validComps } from '../../utils'; import PREFIX from '../prefix'; interface StepsConfig { current: number; status?: 'wait' | 'process' | 'finish' | 'error'; } interface StepConfig { idx?: number; // 要设置的第几个步骤,默认为0 title?: string; // 设置标题 content?: string; // 设置步骤的详细描述 } interface Config { config( el: string ): { setSteps: ({ current, status }: StepsConfig) => void; setChildren: ({ idx, title, content }: StepConfig) => void; }; } class Steps implements Config { readonly VERSION: string; readonly COMPONENTS: NodeListOf<Element>; constructor() { this.VERSION = '1.0'; this.COMPONENTS = $el('r-steps', { all: true }); this._create(this.COMPONENTS); } public config( el: string ): { setSteps({ current, status }: StepsConfig): void; setChildren({ idx, title, content }: StepConfig): void; } { const target = $el(el); validComps(target, 'steps'); const { _updateStatus, _getCurrent } = Steps.prototype; const oldCurrent = _getCurrent(target); return { setSteps({ current, status }: StepsConfig) { if (current !== oldCurrent) { if (!current && current !== 0) { warn('The current property is missing in the Steps configuration'); return; } else if ((current && type.isNum(current)) || (status && type.isStr(status))) { _updateStatus(target, current, status); } } }, setChildren({ idx, title, content }: StepConfig) { // 如果没有传入索引值则默认为第一个 if (!idx) idx = 0; const ChildCurrent = target.children[idx]; const ChildTitle = ChildCurrent.querySelector(`.${PREFIX.steps}-title`); const ChildContent = ChildCurrent.querySelector(`.${PREFIX.steps}-content`); if (title && type.isStr(title)) setText(ChildTitle, title); if (content && type.isStr(content)) setText(ChildContent, content); } }; } private _create(COMPONENTS: NodeListOf<Element>): void { COMPONENTS.forEach((node) => { this._setDirection(node); this._updateStatus(node, this._getCurrent(node)); this._createStepItem(node); removeAttrs(node, ['status']); }); } private _createStepItem(node: Element): void { // 该父节点下的所有 r-step 标签 const { children } = node; let i = 0; const { length } = children; for (; i < length; i++) { const child = children[i]; const uid = `steps${i}`; const idxText = `${i + 1}`; const title = this._getTitle(child); const content = this._getContent(child); child.innerHTML = ` <div class="${PREFIX.steps}-tail"><i></i></div> <div class="${PREFIX.steps}-head"> <div class="${PREFIX.steps}-head-inner"> <span data-steps-uid=${uid} data-step="current">${idxText}</span> </div> </div> <div class="${PREFIX.steps}-main"> <div class="${PREFIX.steps}-title">${title}</div> <div class="${PREFIX.steps}-content">${content}</div> </div> `; this._setCustomIcon(child); this._setNextErrorStatus(child); this._autoSetFinishOrErrorIcon(child, i); removeAttrs(child, ['title', 'content', 'icon']); } } private _setDirection(node: Element): void { if (!node.getAttribute('direction')) node.setAttribute('direction', 'horizontal'); } private _updateStatus(parent: Element, current: number, status?: string): void { const _this = Steps.prototype; const total = parent.children.length - 1; const currentStep = parent.children[current]; validComps(currentStep, 'step'); _this._validIndexCheck(total, current, currentStep); const isParentStatus = parent.getAttribute('status'); const isChildStatus = currentStep.getAttribute('status'); let currentStatus: string; // 如果当前步骤没有状态则默认设为 process 状态 if (!isParentStatus && !isChildStatus) { currentStatus = 'process'; // 父节点有设置状态并且当前选中的子节点没有设置,则采用父节点的状态,否则反之 } else if (isParentStatus && !isChildStatus) { currentStatus = isParentStatus; } else if (isChildStatus && isChildStatus !== 'wait') { currentStatus = isChildStatus; } else { currentStatus = 'process'; } _this._setCurrentStatus(currentStep, !status ? currentStatus : status); _this._setPrevStatus(currentStep); _this._setNextStatus(currentStep); _this._setNextErrorStatus(currentStep); setTimeout(() => _this._autoSetFinishOrErrorIcon(currentStep, current), 0); } private _setCurrentStatus(node: Element, status?: string): void { if (status) { node.setAttribute('status', status); } else { // 如果没有设置status则默认为wait node.setAttribute('status', this._geStatus(node) || 'wait'); } } private _setPrevStatus(node: Element): void { prevAll(node).forEach((prevNode) => { // 除去最开始的步骤,当前步骤是正在进行中的,那么它前面的状态一定是完成或者错误 // 因此,前面的步骤不能随便的设为等待或进行中状态 // 除了 error 状态其余前面的节点都覆盖为 finish 状态 if (this._geStatus(prevNode) !== 'error') { this._setCurrentStatus(prevNode, 'finish'); } }); } private _setNextStatus(node: Element): void { // 从当前节点位置开始,设置其后面的所有节点状态为 wait nextAll(node).forEach((nextNode) => this._setCurrentStatus(nextNode)); } private _setNextErrorStatus(node: Element): void { if ( this._geStatus(node) === 'error' && node.previousElementSibling && this._geStatus(node.previousElementSibling) === 'error' ) { node.previousElementSibling.classList.add(`${PREFIX.steps}-next-error`); } } // 设置已被标记状态为成功或失败的图标 private _autoSetFinishOrErrorIcon(node: Element, current: number): void { if (this._getIcon(node)) return; const prefixIconCls = `${PREFIX.steps}-icon ${PREFIX.icon} ${PREFIX.icon}-`; const currentStatus = this._geStatus(node); const setFinishOrErrorIcon = (status: string, children: Element): void => { if (status === 'finish' || status === 'error') { setHtml(children, ''); if (status === 'finish') children.className = `${prefixIconCls}ios-checkmark`; if (status === 'error') children.className = `${prefixIconCls}ios-close`; } }; // 判断当前选中的步骤的状态是完成还是错误 if (currentStatus === 'finish' || currentStatus === 'error') { const uid = `[data-steps-uid=${current}]`; const HeadInner = node.querySelector(uid)!; setFinishOrErrorIcon(currentStatus, HeadInner); } // 判断之前的所有步骤的状态 prevAll(node).forEach((prevNode) => { const prevStatus = this._geStatus(prevNode); // 如果之前的步骤的状态存在有完成或者是错误的则添加对应图标 if (prevStatus === 'finish' || prevStatus === 'error') { const HeadInner = prevNode.querySelector('[data-step="current"]')!; setFinishOrErrorIcon(prevStatus, HeadInner); } }); } private _setCustomIcon(node: Element): void { const iconType = this._getIcon(node); if (!iconType) return; node.classList.add(`${PREFIX.steps}-custom`); const child = node.querySelector(`.${PREFIX.steps}-head-inner`)!.children[0]; setHtml(child, ''); child.className = `${PREFIX.steps}-icon ${PREFIX.icon} ${PREFIX.icon}-${iconType}`; } private _getCurrent(node: Element): number { return Number(node.getAttribute('current')) || 0; } private _geStatus(node: Element): string | null { return node.getAttribute('status'); } private _getTitle(node: Element): string { return node.getAttribute('title') || ''; } private _getContent(node: Element): string { return node.getAttribute('content') || ''; } private _getIcon(node: Element): string { return node.getAttribute('icon') || ''; } // 判断设置的索引值是否超过了最大索引值 private _validIndexCheck(total: number, current: number, inode: Element): void { // 如果超过最大索引值则该节点是不存在的 if (!inode) { warn( `The current total number of steps is only ${total}, you cannot set it to ${current}` ); return; } } } export default Steps;