rabbit-design
Version:
A lightweight UI plugin library written in TypeScript and based on JavaScript
284 lines (233 loc) • 9.75 kB
text/typescript
import { $el, createElem, setCss, setHtml } from '../../dom-utils';
import { CssTransition, usePromiseCallback } from '../../mixins';
import { destroyElem, destroyElemByKey, type, useHTMLString } from '../../utils';
import PREFIX from '../prefix';
interface MsgGlobalAPI {
top?: number; // 消息从顶部弹出时,距离顶部的位置,单位像素
duration?: number; // 默认自动关闭延时,单位秒
}
interface MessageOptions {
key?: string | number; // 当前消息唯一标志
content?: string; // 提示内容
duration?: number; // 自动关闭的延时,单位秒,不关闭可以写 0
onClose?: () => void; // 点击消息关闭按钮时的回调
closable?: boolean; // 是否显示关闭按钮
background?: boolean; // 是否显示背景色
dangerouslyUseHTMLString?: boolean; // 是否支持传入 HTML 片段
}
interface Events {
info(config: string | MessageOptions): Promise<void>;
success(config: string | MessageOptions): Promise<void>;
warning(config: string | MessageOptions): Promise<void>;
error(config: string | MessageOptions): Promise<void>;
loading(config: string | MessageOptions): Promise<void>;
config(options: MsgGlobalAPI): void;
destroy(key?: string | number): void;
}
const prefixKey = 'rab-message';
const MsgMoveEnter = `${PREFIX.message}-move-enter`;
const MsgMoveLeave = `${PREFIX.message}-move-leave`;
const iconTypes = {
info: 'ios-information-circle',
success: 'ios-checkmark-circle',
warning: 'ios-alert',
error: 'ios-close-circle',
loading: 'loading-solid'
};
const DEFAULT_MESSAGE: {
top: number;
duration: number;
} = {
top: 24,
duration: 3
};
let zIndex = 1010;
// 创建实例的最外层父容器
function createMessageInstanceWrapper(): HTMLElement {
const MsgWrapper = createElem('div');
MsgWrapper.className = `${PREFIX.message}`;
setCss(MsgWrapper, 'zIndex', `${zIndex}`);
setTimeout(() => {
setCss(MsgWrapper, 'top', `${DEFAULT_MESSAGE.top}px`);
}, 0);
document.body.appendChild(MsgWrapper);
return MsgWrapper;
}
class $Message implements Events {
readonly VERSION: string;
readonly INSTANCE: Array<HTMLElement>;
constructor() {
this.VERSION = 'v1.0';
// 存储已经创建的实例,在 destroy方法里需要用到
this.INSTANCE = [];
createMessageInstanceWrapper();
}
public info(config: string | MessageOptions): Promise<void> {
this._createInstance('info', config);
// message 结束时提供 then 接口在关闭后运行 callback
return usePromiseCallback(DEFAULT_MESSAGE.duration, config);
}
public success(config: string | MessageOptions): Promise<void> {
this._createInstance('success', config);
return usePromiseCallback(DEFAULT_MESSAGE.duration, config);
}
public warning(config: string | MessageOptions): Promise<void> {
this._createInstance('warning', config);
return usePromiseCallback(DEFAULT_MESSAGE.duration, config);
}
public error(config: string | MessageOptions): Promise<void> {
this._createInstance('error', config);
return usePromiseCallback(DEFAULT_MESSAGE.duration, config);
}
public loading(config: string | MessageOptions): Promise<void> {
this._createInstance('loading', config);
return usePromiseCallback(DEFAULT_MESSAGE.duration, config);
}
public config(options: MsgGlobalAPI): void {
if (options.top && type.isNum(options.top)) {
DEFAULT_MESSAGE.top = options.top;
}
if ((options.duration && type.isNum(options.duration)) || options.duration === 0) {
DEFAULT_MESSAGE.duration = options.duration;
}
}
public destroy(key?: string | number): void {
// 通过设置的 key 消除
if (key && (type.isStr(key) || type.isNum(key))) {
destroyElemByKey({
key,
duration: 0.1,
prefixKey,
clsLeave: MsgMoveLeave
});
} else {
// 销毁所有实例
this.INSTANCE.forEach((instance) => {
destroyElem(instance, {
duration: 0.1,
clsLeave: MsgMoveLeave
});
});
// 清空存放的所有实例
this.INSTANCE.length = 0;
}
}
private _autoSetZindex(): void {
// 每次创建实例自动增加最外层父容器的层级
zIndex++;
setCss($el(`.${PREFIX.message}`), 'zIndex', `${zIndex}`);
}
private _createInstance(_type: string, config: string | MessageOptions): HTMLElement {
this._autoSetZindex();
const Message = createElem('div');
const MsgContentWrap = createElem('div');
const MsgContentBox = createElem('div');
const MsgInfoBox = createElem('div');
const MsgIcon = createElem('i');
const MsgText = createElem('span');
this._setCls(_type, [Message, MsgContentWrap, MsgContentBox, MsgInfoBox, MsgIcon]);
this._setContent(MsgText, config);
this._setIcon(_type, MsgIcon);
// 参数 config 为字符串
if (typeof config === 'string') {
this._autoClose(Message, DEFAULT_MESSAGE.duration);
}
// 参数 config 为对象
if (typeof config === 'object') {
const { key } = config;
let { closable, duration, onClose, background } = config;
if (type.isUndef(closable)) closable = false;
if (type.isUndef(onClose)) onClose = () => void 0;
if (type.isUndef(background)) background = false;
// 为每个实例自己的 duration 参数设置默认值,如果有传入值则使用自定义的值
if (type.isUndef(duration)) duration = DEFAULT_MESSAGE.duration;
// 当全局的 duration 不为 3 时说明进行了全局配置进行改变
if (DEFAULT_MESSAGE.duration !== 3) duration = DEFAULT_MESSAGE.duration;
this._setClosable(closable, Message, MsgContentWrap, onClose);
this._setBackground(Message, MsgContentWrap, background);
this._autoClose(Message, duration);
this._setKey(Message, key);
}
MsgContentWrap.appendChild(MsgContentBox);
MsgContentBox.append(MsgInfoBox);
MsgInfoBox.append(MsgIcon, MsgText);
Message.appendChild(MsgContentWrap);
$el(`.${PREFIX.message}`)?.appendChild(Message);
// 存放每次创建的实例
this.INSTANCE.push(Message);
CssTransition(Message, {
inOrOut: 'in',
enterCls: MsgMoveEnter,
rmCls: true,
timeout: 250
});
return Message;
}
private _setCls(type: string, elems: Array<Element>): void {
const nodesCls = [
`${PREFIX.messageChild}`,
`${PREFIX.messageChild}-content ${PREFIX.messageChild}-content-${type}`,
`${PREFIX.messageChild}-content-text`,
`${PREFIX.message}-${type}`,
`${PREFIX.icon}`
];
elems.forEach((elem, i) => {
elem.className = nodesCls[i];
});
}
private _setIcon(type: string, icon: HTMLElement): void {
if (type === 'loading') {
icon.classList.add('rab-load-loop');
}
// @ts-ignore
icon.classList.add(`${PREFIX.icon}-${iconTypes[type]}`);
}
private _setContent(node: HTMLElement, config: string | MessageOptions): void {
if (typeof config === 'string') {
useHTMLString(node, config, false);
} else if (typeof config === 'object' && config.content) {
useHTMLString(node, config.content, config.dangerouslyUseHTMLString);
}
}
private _setClosable(
closable: any,
parent: HTMLElement,
children: HTMLElement,
onClose: any
): void {
if (!closable) return;
parent.classList.add(`${PREFIX.messageChild}-closable`);
const MsgCloseBtn = createElem('a');
MsgCloseBtn.className = `${PREFIX.messageChild}-close`;
setHtml(MsgCloseBtn, `<i class="${PREFIX.icon} ${PREFIX.icon}-ios-close"></i>`);
this._handleClose(parent, MsgCloseBtn, onClose);
children.appendChild(MsgCloseBtn);
}
private _setKey(node: HTMLElement, key: any): void {
if (!key) return;
node.setAttribute(`${prefixKey}-key`, key);
}
private _autoClose(node: HTMLElement, duration: any): void {
destroyElem(node, {
duration,
clsLeave: MsgMoveLeave
});
}
private _handleClose(parent: HTMLElement, closeBtn: HTMLElement, onClose: any): void {
closeBtn.addEventListener('click', () => {
// 手动关闭后的回调
type.isFn(onClose);
destroyElem(parent, {
duration: 0.1,
clsEnter: MsgMoveEnter,
clsLeave: MsgMoveLeave
});
});
}
private _setBackground(node: HTMLElement, children: HTMLElement, background: any): void {
if (!background) return;
node.classList.add(`${PREFIX.messageChild}-with-background`);
children.classList.add(`${PREFIX.messageChild}-content-background`);
}
}
export default $Message;