rabbit-simple-ui
Version:
A simple UI component library based on JavaScript
439 lines (392 loc) • 15.8 kB
text/typescript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import {
$el,
bind,
getBooleanTypeAttr,
getNumTypeAttr,
getStrTypeAttr,
removeAttrs,
setCss,
setHtml
} from '../../dom-utils';
import { clickoutside, CssTransition, _arrow, _Popper } from '../../mixins';
import { type, validComps } from '../../utils';
import PREFIX from '../prefix';
interface Config {
config(
el: string
): {
visible: boolean;
disabled: boolean;
title: string;
content: string;
events: ({ onPopperShow, onPopperHide, onOk, onCancel }: PoptipEvents) => void;
};
}
interface PoptipEvents {
onPopperShow?: () => void;
onPopperHide?: () => void;
onOk?: () => void;
onCancel?: () => void;
}
const STATEKEY = 'visibleState';
const DEFAULTDELAY = 80;
let VISIBLETIMER: any = null,
EVENTTIMER: any = null;
class Poptip implements Config {
readonly VERSION: string;
readonly COMPONENTS: NodeListOf<Element>;
constructor() {
this.VERSION = 'v2.0';
this.COMPONENTS = $el('r-poptip', { all: true });
this._create(this.COMPONENTS);
_arrow.scrollUpdate();
}
public config(
el: string
): {
visible: boolean;
disabled: boolean;
title: string;
content: string;
events: ({ onPopperShow, onPopperHide, onOk, onCancel }: PoptipEvents) => void;
} {
const target = $el(el) as HTMLElement;
validComps(target, 'poptip');
const { _attrs, _setVisible, _handleTrigger } = Poptip.prototype;
const { trigger, title, content, disabled, placement, offset, confirm } = _attrs(target);
const PoptipRefElem = target.querySelector(`.${PREFIX.poptip}-rel`)! as HTMLElement;
const PoptipPopper = target.querySelector(`.${PREFIX.poptip}-popper`)! as HTMLElement;
const PoptipTitle = target.querySelector(`.${PREFIX.poptip}-title`)!;
const PoptipContent = target.querySelector(`.${PREFIX.poptip}-body-content`)!;
return {
get visible() {
return PoptipPopper.dataset[STATEKEY] === 'show';
},
set visible(newVal: boolean) {
if (newVal && !type.isBol(newVal)) return;
_setVisible(newVal, PoptipRefElem, PoptipPopper, placement, offset);
},
get disabled() {
return disabled;
},
set disabled(newVal: boolean) {
if (newVal && !type.isBol(newVal)) return;
_handleTrigger(
trigger,
confirm,
newVal,
target,
PoptipRefElem,
PoptipPopper,
placement,
offset
);
},
get title() {
return title;
},
set title(newVal: string) {
if (newVal && !type.isStr(newVal)) return;
setHtml(PoptipTitle, newVal);
},
get content() {
return content;
},
set content(newVal: string) {
if (newVal && !type.isStr(newVal)) return;
setHtml(PoptipContent, newVal);
},
events({ onPopperShow, onPopperHide, onOk, onCancel }) {
const visibleEvent = (show: boolean) => {
if (show) {
onPopperShow && type.isFn(onPopperShow);
} else {
onPopperHide && type.isFn(onPopperHide);
}
};
const toogleEv = () => {
setTimeout(() => {
PoptipPopper.dataset[STATEKEY] === 'show'
? visibleEvent(true)
: visibleEvent(false);
}, 200);
};
const clickoutsideEv = () => {
if (PoptipPopper.style.visibility === 'visible') {
setTimeout(() => visibleEvent(false), DEFAULTDELAY);
}
};
if (!confirm) {
if (trigger === 'hover') {
bind(target, 'mouseenter', () => {
if (EVENTTIMER) clearTimeout(EVENTTIMER);
EVENTTIMER = setTimeout(() => visibleEvent(true), DEFAULTDELAY);
});
bind(target, 'mouseleave', () => {
if (EVENTTIMER) clearTimeout(EVENTTIMER);
if (PoptipPopper.dataset[STATEKEY] === 'show')
setTimeout(() => visibleEvent(false), DEFAULTDELAY);
});
}
if (trigger === 'focus') {
bind(PoptipRefElem, 'mousedown', () => visibleEvent(true));
bind(PoptipRefElem, 'mouseup', () => visibleEvent(false));
}
}
if (trigger === 'click' || trigger === 'focus') {
clickoutside(target, clickoutsideEv);
}
if (trigger === 'click') {
bind(PoptipRefElem, 'click', toogleEv);
}
if (confirm) {
const PoptipOkBtn = PoptipPopper.querySelector(`.${PREFIX.button}-primary`);
const PoptipCancelBtn = PoptipPopper.querySelector(`.${PREFIX.button}-text`);
bind(PoptipOkBtn, 'click', () => onOk && type.isFn(onOk));
bind(PoptipCancelBtn, 'click', () => onCancel && type.isFn(onCancel));
}
}
};
}
private _create(COMPONENTS: NodeListOf<Element>): void {
COMPONENTS.forEach((node) => {
const ReferenceElem = node.firstElementChild || node.innerHTML;
const {
trigger,
title,
content,
placement,
padding,
offset,
confirm,
visible,
width,
wordWrap,
disabled,
okText,
cancelText
} = this._attrs(node);
this.setMainTemplate(node, title, content, placement);
const PoptipRel = node.querySelector(`.${PREFIX.poptip}-rel`)!;
const PoptipPopper = node.querySelector(
`.${PREFIX.poptip}-popper`
)! as HTMLEmbedElement;
this.addReferenceElem(PoptipRel, ReferenceElem);
this.setConfirmTemplate(confirm, node, title, okText, cancelText);
this._setPlacement(PoptipRel, PoptipPopper, placement, offset);
this._setVisible(visible, PoptipRel, PoptipPopper, placement, offset);
this._setPadding(node, padding);
this._setWidthAndWordWrap(PoptipPopper, width, wordWrap);
this._handleTrigger(
trigger,
confirm,
disabled,
node,
PoptipRel,
PoptipPopper,
placement,
offset
);
this._handleBtnClick(PoptipRel, PoptipPopper, placement, offset);
removeAttrs(node, [
'width',
'title',
'offset',
'content',
'confirm',
'visible',
'padding',
'disabled',
'trigger',
'placement',
'word-wrap',
'ok-text',
'cancel-text'
]);
});
}
private setMainTemplate(
node: Element,
title: string,
content: string,
placement: string
): void {
const template = `
<div class="${PREFIX.poptip}-rel"></div>
<div class="${PREFIX.poptip}-popper" x-placement="${placement}">
<div class="${PREFIX.poptip}-content">
<div class="${PREFIX.poptip}-arrow"></div>
<div class="${PREFIX.poptip}-inner">
${title ? `<div class="${PREFIX.poptip}-title">${title}</div>` : ''}
<div class="${PREFIX.poptip}-body">
<div class="${PREFIX.poptip}-body-content">${content}</div>
</div>
</div>
</div>
</div>
`;
setHtml(node, template);
}
private addReferenceElem(child: Element, ReferenceElem: string | Element): void {
if (typeof ReferenceElem === 'string') {
setHtml(child, ReferenceElem);
} else if (ReferenceElem instanceof Element) {
child.appendChild(ReferenceElem);
}
}
private setConfirmTemplate(
confirm: boolean,
node: Element,
title: string,
okText: string,
cancelText: string
): void {
if (!confirm) return;
const template = `
<i class="${PREFIX.icon} ${PREFIX.icon}-ios-help-circle"></i>
<div class="${PREFIX.poptip}-body-message">${title}</div>
<div class="${PREFIX.poptip}-footer">
<button type="button" class="${PREFIX.button} ${PREFIX.button}-text ${PREFIX.button}-sm">${cancelText}</button>
<button type="button" class="${PREFIX.button} ${PREFIX.button}-primary ${PREFIX.button}-sm">${okText}</button>
</div>
`;
const PoptipBody = node.querySelector(`.${PREFIX.poptip}-body`)!;
setHtml(PoptipBody, template);
node.querySelector(`.${PREFIX.poptip}-title`)!.remove();
node.classList.add(`${PREFIX.poptip}-confirm`);
}
private _handleTrigger(
type: string,
confirm: boolean,
disabled: boolean,
node: Element,
refElem: Element,
popper: HTMLElement,
placement: string,
offset: number
): void {
if (disabled) return;
const show = (show: boolean, e?: MouseEvent) => {
if (e) e.stopPropagation();
this._setVisible(show, refElem, popper, placement, offset);
};
if (!confirm) {
if (type === 'hover') {
bind(node, 'mouseenter', (e: MouseEvent) => {
if (VISIBLETIMER) clearTimeout(VISIBLETIMER);
if (popper.dataset[STATEKEY] === 'show') return;
VISIBLETIMER = setTimeout(() => show(true, e), DEFAULTDELAY);
});
bind(node, 'mouseleave', (e: MouseEvent) => {
if (VISIBLETIMER) clearTimeout(VISIBLETIMER);
if (popper.dataset[STATEKEY] === 'show')
setTimeout(() => show(false, e), DEFAULTDELAY);
});
}
if (type === 'focus') {
bind(refElem, 'mousedown', (e: MouseEvent) => show(true, e));
bind(refElem, 'mouseup', (e: MouseEvent) => show(false, e));
}
}
if (type === 'click') {
const hide = () => {
if (popper.dataset[STATEKEY] === 'close') return;
show(false);
};
const clickEv = (e: MouseEvent) => {
const poppers = document.querySelectorAll(`.${PREFIX.poptip}-popper`);
poppers.forEach((child) => {
const otherPopper = child as HTMLElement;
if (otherPopper.dataset[STATEKEY] === 'show') {
this._setVisible(false, refElem, otherPopper, placement, offset);
}
});
popper.style.visibility === 'visible' ? show(false, e) : show(true, e);
};
clickoutside(node, hide);
bind(refElem, 'click', (e: MouseEvent) => clickEv(e));
}
}
private _handleBtnClick(
refElem: Element,
popper: HTMLElement,
placement: string,
offset: number
): void {
const PoptipOkBtn = popper.querySelector(`.${PREFIX.button}-primary`);
const PoptipCancelBtn = popper.querySelector(`.${PREFIX.button}-text`);
if (!PoptipOkBtn) return;
const hidden = () => this._setVisible(false, refElem, popper, placement, offset);
bind(PoptipOkBtn, 'click', hidden);
bind(PoptipCancelBtn, 'click', hidden);
}
private _setVisible(
visible: boolean,
refElem: Element,
popper: HTMLElement,
placement: string,
offset: number
): void {
const { _setPlacement, _visibleTransition } = Poptip.prototype;
if (visible) {
popper.dataset[STATEKEY] = 'show';
_visibleTransition('in', popper);
_setPlacement(refElem, popper, placement, offset);
} else {
popper.dataset[STATEKEY] = 'close';
setTimeout(() => {
popper.dataset[STATEKEY] === 'close' && _visibleTransition('out', popper);
}, 0);
}
}
private _setPlacement(
refElem: Element,
popper: HTMLElement,
placement: string,
offset: number
): void {
_Popper._newCreatePopper(refElem, popper, placement, offset);
}
private _visibleTransition(type: 'in' | 'out', popper: HTMLElement): void {
const aniCls =
type === 'in'
? { enterCls: 'zoom-big-fast-enter' }
: { leaveCls: 'zoom-big-fast-leave' };
CssTransition(popper, {
inOrOut: type,
...aniCls,
rmCls: true,
timeout: 190
});
}
private _setWidthAndWordWrap(child: Element, width: number, wordWrap: boolean): void {
setCss(child, 'width', `${width}px`);
if (!wordWrap) return;
const PoptipBodyContent = child.querySelector(`.${PREFIX.poptip}-body-content`)!;
PoptipBodyContent.classList.add(`${PREFIX.poptip}-body-content-word-wrap`);
}
private _setPadding(node: Element, padding: string): void {
if (!padding) return;
setCss(node.querySelector(`.${PREFIX.poptip}-title`)!, 'padding', padding);
setCss(node.querySelector(`.${PREFIX.poptip}-body`)!, 'padding', padding);
}
private _attrs(node: Element) {
return {
width: getNumTypeAttr(node, 'width', -1),
offset: getNumTypeAttr(node, 'offset', 0),
title: getStrTypeAttr(node, 'title', ''),
okText: getStrTypeAttr(node, 'ok-text', '确定'),
content: getStrTypeAttr(node, 'content', ''),
trigger: getStrTypeAttr(node, 'trigger', 'click'),
padding: getStrTypeAttr(node, 'padding', ''),
placement: getStrTypeAttr(node, 'placement', 'top'),
cancelText: getStrTypeAttr(node, 'cancel-text', '取消'),
confirm: getBooleanTypeAttr(node, 'confirm'),
visible: getBooleanTypeAttr(node, 'visible'),
disabled: getBooleanTypeAttr(node, 'disabled'),
wordWrap: getBooleanTypeAttr(node, 'word-wrap')
};
}
}
export default Poptip;