import { createComputed, createContext, createSignal, onCleanup, onMount, Show, useContext } from "solid-js";
import { Portal } from "solid-js/web";
import { useClassList } from "../utils/useProps";
import createModel from "../utils/createModel";
import usePortal from "../utils/usePortal";
import useAlignPostion from "../utils/useAlignPostion";
import { useClickOutside } from "../utils/useClickOutside";
import usezIndex from "../utils/usezIndex";
import { useTransition } from "../utils/useTransition";
export * from './DropdownMenu';
export * from './DropdownItem';
const DropdownContext = createContext();
export const useDropdownConext = () => useContext(DropdownContext);
type DropdownProps = {
trigger?: 'hover'|'click'|'contextMenu'|'custom',
align?: 'bottom'|'bottomLeft'|'bottomRight'|'right'|'left'|'rightTop'|'leftTop',
classList?:any,
class?:any,
style?:any,
onSelect?: Function,
children: any,
menu?: any,
visible?: boolean|Function[],
transfer?: boolean,
theme?: 'dark'|'light',
disabled?: boolean,
revers?: boolean,
handler?: string,
fixWidth?: boolean,
onBeforeDrop?: Function
}
export function Dropdown(props: DropdownProps){
const [visible, setVisible] = createModel(props, 'visible', false);
const [opened, setOpened] = createSignal(visible());
let targetEle: any;
let target: any;
let trigger = props.trigger || 'hover';
let timer: any;
let align = props.align || 'bottom';
let wrap: any;
const zindex = usezIndex();
const revers = props.revers ?? true;
const classList = () => useClassList(props, 'cm-dropdown', {
// 'cm-dropdown-open': visible(),
[`cm-dropdown-${props.theme}`]: props.theme,
});
const transition = useTransition({
el: ()=> wrap,
startClass: 'cm-dropdown-visible',
activeClass: 'cm-dropdown-open',
onLeave: () => {
setOpened(false);
},
onEnter: () => {
setOpened(true);
}
})
createComputed(() => {
const v = visible();
if (v) {
transition.enter();
// props.onShow && props.onShow();
} else {
transition.leave();
}
})
// 防抖
const clearDelayTimer = () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
}
// 点击显示
const onMouseClick = (e: any) => {
if (!target.nextElementSibling.contains(e.target)) {
return false;
}
if (props.disabled) {
return;
}
e.preventDefault && e.preventDefault();
e.stopPropagation && e.stopPropagation();
targetEle = e.target;
if (props.handler) {
const te = targetEle.closest(props.handler);
if (!te) {
return;
}
}
const ret = props.onBeforeDrop && props.onBeforeDrop(visible());
if (ret === undefined || ret) {
setVisible(!visible());
}
}
const onMouseEnter = () => {
if (props.disabled) {
return;
}
if (trigger === 'hover') {
clearDelayTimer();
setVisible(true);
if (wrap) {
// dropdown容器注册划走事件进行关闭
wrap.removeEventListener('mouseleave', onMouseLeave);
wrap.addEventListener('mouseleave', onMouseLeave, false);
}
}
}
const onMouseLeave = () => {
if (props.disabled) {
return;
}
if (trigger === 'hover') {
timer = setTimeout( () => {
setVisible(false);
}, 200);
}
}
const getOffsetWidth = (align: string, rect: any) => {
if (align === 'bottomRight' || align === 'topRight') {
return 0;
}
if (align === 'top' || align === 'bottom') {
return rect.width / 2;
}
if (align === 'topLeft' || align === 'bottomLeft') {
return rect.width;
}
if (align === 'left' || align === 'leftTop' || align === 'leftBottom') {
return 0;
}
if (align === 'right' || align === 'rightTop' || align === 'rightBottom') {
return rect.width;
}
}
const getOffsetHeight = (align: string, rect: any) => {
if (align === 'leftBottom' || align === 'rightBottom') {
return 0;
}
if (align === 'top' || align === 'topLeft' || align === 'topRight') {
return 0;
}
if (align === 'leftTop' || align === 'rightTop') {
return rect.height;
}
if (align === 'left' || align === 'right') {
return rect.height / 2;
}
if (align === 'bottom' || align === 'bottomLeft' || align === 'bottomRight') {
return rect.height;
}
}
const posStyle = () => {
opened()
if (target && target.nextElementSibling) {
let te = target.nextElementSibling;
if (props.handler) {
te = targetEle.closest(props.handler);
}
if (!te) {
return;
}
const parent = te.offsetParent;
if (!parent) {
return;
}
const parentPos = parent.getBoundingClientRect();
const pos: any = useAlignPostion(align, te);
const originTop = pos.top;
const originLeft = pos.left;
if (props.transfer) {
const targetReact = te.getBoundingClientRect();
pos.top = pos.top + document.documentElement.scrollTop;
pos.left = pos.left + document.documentElement.scrollLeft;
props.fixWidth ? pos['min-width'] = targetReact.width + 'px' : false;
} else {
pos.top = pos.top + parent.scrollTop - parentPos.top;
pos.left = pos.left + parent.scrollLeft - parentPos.left;
}
const rect = wrap.getBoundingClientRect();
const offsetWidth = getOffsetWidth(align, rect);
const offsetHeight = getOffsetHeight(align, rect);
const h = originTop + offsetHeight;
const w = originLeft + offsetWidth;
const containerHeight = window.innerHeight || document.documentElement.clientHeight;
const containerWidth = window.innerWidth || document.documentElement.clientWidth;
const targetRect = te.getBoundingClientRect();
if (revers) {
if (h > containerHeight) {
if (align === 'bottom' || align === 'bottomLeft' || align === 'bottomRight') {
pos.top = pos.top - rect.height - targetRect.height - 12;
} else if (align === 'left' || align === 'right') {
pos.top = pos.top - (rect.height - targetRect.height) / 2;
} else if (align === 'leftTop' || align === 'rightTop') {
pos.top = pos.top - (rect.height - targetRect.height);
}
}
// align 为 top bottom topLeft bottomLeft right rightTop rightBottom 存在该情况
if (w > containerWidth - 5) {
if (align === 'bottom') {
pos.left = pos.left - (rect.width - targetRect.width) / 2;
} else if (align === 'bottomLeft') {
pos.left = pos.left - rect.width + targetRect.width;
} else if (align === 'right' || align === 'rightTop') {
pos.left = pos.left - rect.width - targetRect.width;
}
}
}
pos.top = pos.top + 'px'
pos.left = pos.left + 'px'
pos['z-index'] = zindex;
return pos;
}
};
let removeClickOutside: Function;
onMount(() => {
if (target.nextElementSibling) {
if (trigger === 'hover') {
target.nextElementSibling.addEventListener('mouseenter', onMouseEnter, false);
target.nextElementSibling.addEventListener('mouseleave', onMouseLeave, false);
}
if (trigger === 'click' || trigger === 'custom') {
document.addEventListener('click', onMouseClick);
// target.nextElementSibling.addEventListener('click', onMouseClick);
if (trigger === 'click') {
const other = props.handler ? target.nextElementSibling.querySelectorAll(props.handler) : target.nextElementSibling;
removeClickOutside = useClickOutside([wrap, other], () => {
setVisible(false);
});
}
}
if (trigger === 'contextMenu') {
document.addEventListener('contextmenu', onMouseClick);
// target.nextElementSibling.addEventListener('contextmenu', onMouseClick);
const other = props.handler ? target.nextElementSibling.querySelectorAll(props.handler) : target.nextElementSibling;
removeClickOutside = useClickOutside([wrap, other], () => {
setVisible(false);
});
}
}
});
onCleanup(() => {
if (target.nextElementSibling) {
if (trigger === 'hover') {
target.nextElementSibling.removeEventListener('mouseenter', onMouseEnter);
target.nextElementSibling.removeEventListener('mouseleave', onMouseLeave);
}
if (trigger === 'click' || trigger === 'custom') {
document.removeEventListener('click', onMouseClick);
// target.nextElementSibling.removeEventListener('click', onMouseClick, false);
}
if (trigger === 'contextMenu') {
document.removeEventListener('contextmenu', onMouseClick);
// target.nextElementSibling.removeEventListener('contextmenu', onMouseClick, false);
}
}
removeClickOutside && removeClickOutside();
});
const onSelect = (name: string) => {
props.onSelect && props.onSelect(name);
wrap.removeEventListener('mouseleave', onMouseLeave);
setVisible(false);
}
const id = 'cm-dropdown-portal';
return <>
{props.children}
{props.menu}
}>
{props.menu}
>
}