rabbit-simple-ui
Version:
A simple UI component library based on JavaScript
335 lines (266 loc) • 10.8 kB
text/typescript
import { CssTransition, warn } from '../../mixins';
import { type, destroyElem, destroyElemByKey, useHTMLString } from '../../utils';
import { usePromiseCallback } from '../../mixins';
import PREFIX from '../prefix';
import { $el, createElem, setCss, setHtml } from '../../dom-utils';
interface NoticeGlobalAPI {
top?: number; // 通知组件距离顶端的距离,单位像素
duration?: number; // 默认自动关闭的延时,单位秒
}
interface NoticeOptions {
key?: string | number; // 当前通知的唯一标识
icon?: string; // 自定义图标
title?: string; // 通知提醒的标题
desc?: string; // 通知提醒的内容,为空或不填时,自动应用仅标题模式下的样式
style?: string; // 自定义内联样式
onClose?: () => void; // 点击通知关闭按钮时的回调
onClick?: () => void; // 点击通知时触发的回调函数
duration?: number; // 自动关闭的延时,单位秒,不关闭可以写 0
closable?: boolean; // 是否显示右上角关闭按钮,默认为 true
className?: string; // 自定义 CSS class
dangerouslyUseHTMLString?: boolean;
}
interface Events {
open(config: NoticeOptions): Promise<void>;
info(config: NoticeOptions): Promise<void>;
success(config: NoticeOptions): Promise<void>;
warning(config: NoticeOptions): Promise<void>;
error(config: NoticeOptions): Promise<void>;
close(key: string): void;
destroy(): void;
}
const NotPrefixKey = 'rab-notice';
const NotMoveEnter = `${PREFIX.notice}-move-enter`;
const NotMoveLeave = `${PREFIX.notice}-move-leave`;
const iconTypes = {
info: 'ios-information-circle',
success: 'ios-checkmark-circle',
warning: 'ios-alert',
error: 'ios-close-circle',
loading: 'loading-solid'
};
const DEFAULT_NOTICE: {
top: number;
duration: number;
} = {
top: 24,
duration: 4.5
};
let zIndex = 1180;
// 创建实例的最外层父容器
function createNoticeInsanceWrapper(): HTMLElement {
const NoticeWrapper = createElem('div');
NoticeWrapper.className = `${PREFIX.notice}`;
setCss(NoticeWrapper, 'zIndex', `${zIndex}`);
setCss(NoticeWrapper, 'right', '0');
setTimeout(() => setCss(NoticeWrapper, 'top', `${DEFAULT_NOTICE.top}px`), 0);
document.body.appendChild(NoticeWrapper);
return NoticeWrapper;
}
class $Notice implements Events {
readonly VERSION: string;
readonly instances: Array<HTMLElement>;
constructor() {
this.VERSION = 'v1.0';
// 存储已经创建的实例,在 destroy方法里需要用到
this.instances = [];
createNoticeInsanceWrapper();
}
public open(config: NoticeOptions): Promise<void> {
this._createInstance('normal', config);
return usePromiseCallback(DEFAULT_NOTICE.duration, config.duration);
}
public info(config: NoticeOptions): Promise<void> {
this._createInstance('info', config);
return usePromiseCallback(DEFAULT_NOTICE.duration, config.duration);
}
public success(config: NoticeOptions): Promise<void> {
this._createInstance('success', config);
return usePromiseCallback(DEFAULT_NOTICE.duration, config.duration);
}
public warning(config: NoticeOptions): Promise<void> {
this._createInstance('warning', config);
return usePromiseCallback(DEFAULT_NOTICE.duration, config.duration);
}
public error(config: NoticeOptions): Promise<void> {
this._createInstance('error', config);
return usePromiseCallback(DEFAULT_NOTICE.duration, config.duration);
}
public config(options: NoticeGlobalAPI): void {
if (options.top) {
DEFAULT_NOTICE.top = options.top;
}
if (options.duration || options.duration === 0) {
DEFAULT_NOTICE.duration = options.duration;
}
}
public close(key: string): void {
destroyElemByKey({
key,
duration: 0.1,
clsLeave: NotMoveLeave,
prefixKey: NotPrefixKey
});
}
public destroy(): void {
this.instances.forEach((instance) => {
destroyElem(instance, {
clsLeave: NotMoveLeave,
duration: 0.1
});
});
// 清空存放的所有实例
this.instances.length = 0;
}
private _autoSetZindex(): void {
zIndex++;
setCss($el(`.${PREFIX.notice}`), 'zIndex', `${zIndex}`);
}
private _createInstance(type: string, config: NoticeOptions): HTMLElement {
this._autoSetZindex();
const Notice = createElem('div');
const NoticeContent = createElem('div');
const NoticeCustomContent = createElem('div');
const NoticeTitle = createElem('div');
const NoticeDesc = createElem('div');
this._setCls(
type,
[Notice, NoticeContent, NoticeCustomContent, NoticeTitle, NoticeDesc],
config.className
);
this._setKey(Notice, config.key);
this._setTitle(NoticeTitle, config.title, config.dangerouslyUseHTMLString);
this._setDesc(
Notice,
NoticeCustomContent,
NoticeDesc,
config.desc,
config.dangerouslyUseHTMLString
);
this._setIcon(type, NoticeCustomContent, NoticeDesc, config.icon);
this._setClosable(Notice, config.closable, config.onClose);
this._customStyle(Notice, config.style);
NoticeCustomContent.append(NoticeTitle, NoticeDesc);
NoticeContent.appendChild(NoticeCustomContent);
Notice.appendChild(NoticeContent);
document.querySelector(`.${PREFIX.notice}`)?.appendChild(Notice);
CssTransition(Notice, {
inOrOut: 'in',
enterCls: NotMoveEnter
});
this.instances.push(Notice);
this._handleNoticeClick(Notice, config.onClick);
this._autoClose(Notice, config.duration);
return Notice;
}
private _setCls(type: string, nodes: Array<HTMLElement>, customCls?: string): void {
const nodesCls = [
`${PREFIX.noticeChild} ${customCls ? customCls : ''}`,
`${PREFIX.noticeChild}-content`,
`${PREFIX.noticeChild}-custom-content ${PREFIX.notice}-with-${type}`,
`${PREFIX.notice}-title`,
`${PREFIX.notice}-desc`
];
let i = 0;
const { length } = nodes;
for (; i < length; i++) {
const node = nodes[i];
node.className = nodesCls[i];
}
}
private _setKey(node: HTMLElement, key: any): void {
if (!key) return;
node.setAttribute(`${NotPrefixKey}-key`, key);
}
private _setTitle(node: HTMLElement, title?: string, dangerouslyUseHTMLString?: boolean): void {
// 必须设置一个通知提醒标题
if (!title) {
warn('You must set a notification to remind the title');
return;
}
// 是否支持传入 HTML 片段
useHTMLString(node, title, dangerouslyUseHTMLString);
}
private _setDesc(
parent: HTMLElement,
children_custm: HTMLElement,
child_desc: HTMLElement,
desc?: string,
dangerouslyUseHTMLString?: boolean
): void {
if (!desc) return;
parent.classList.add(`${PREFIX.noticeChild}-with-desc`);
children_custm.classList.add(`${PREFIX.notice}-with-desc`);
// 是否支持传入 HTML 片段
useHTMLString(child_desc, desc, dangerouslyUseHTMLString);
}
private _setIcon(
type: string,
child_custom: HTMLElement,
child_desc: HTMLElement,
customIcon?: string
): void {
// 不带状态图标的类型
if (type === 'noraml') return;
if (type !== 'normal' || customIcon) {
child_custom.classList.add(`${PREFIX.notice}-with-icon`);
}
let isOutline = '';
// 带有状态图标并且是否带有提示内容,如果有则将图标设为 outline 外观
if (child_desc.innerHTML) isOutline = '-outline';
const NoticeIcon = createElem('span');
NoticeIcon.className = `${PREFIX.notice}-icon ${PREFIX.notice}-icon-${type}`;
// 是否自定义状态图标
if (customIcon) {
setHtml(NoticeIcon, customIcon);
} else {
// @ts-ignore
const defaultIcon = `<i class="${PREFIX.icon} ${PREFIX.icon}-${iconTypes[type]}${isOutline}"></i>`;
setHtml(NoticeIcon, defaultIcon);
}
child_custom.prepend(NoticeIcon);
}
private _setClosable(parent: HTMLElement, closable?: boolean, onClose?: () => void): void {
// 默认显示右上角关闭按钮
type.isUndef(closable) ? (closable = true) : closable;
if (!closable) return;
parent.classList.add(`${PREFIX.noticeChild}-closable`);
const NoticeClose = createElem('a');
const closeIcon = `<i class="${PREFIX.icon} ${PREFIX.icon}-ios-close"></i>`;
NoticeClose.className = `${PREFIX.noticeChild}-close`;
setHtml(NoticeClose, closeIcon);
this._handleClose(parent, NoticeClose, onClose);
parent.appendChild(NoticeClose);
}
// 自定义内联样式
private _customStyle(node: HTMLElement, style?: string): void {
if (!style) return;
setCss(node, 'cssText', style);
}
// 点击通知时触发的回调函数
private _handleNoticeClick(parent: HTMLElement, onClick?: () => void): void {
parent.onclick = (e) => {
e.stopPropagation();
if (onClick) type.isFn(onClick);
};
}
private _handleClose(parent: HTMLElement, closeBtn: HTMLElement, onClose?: () => void): void {
closeBtn.onclick = (e) => {
e.stopPropagation();
if (onClose) type.isFn(onClose);
destroyElem(parent, {
clsLeave: NotMoveLeave,
duration: 0.1
});
};
}
private _autoClose(instance: HTMLElement, duration?: number): void {
// 为每个实例自己的 duration参数设置默认值,如果有传入值则使用自定义的值
type.isUndef(duration) ? (duration = DEFAULT_NOTICE.duration) : duration;
destroyElem(instance, {
duration,
clsLeave: NotMoveLeave
});
}
}
export default $Notice;