UNPKG

@opensig/opendesign

Version:

420 lines (419 loc) 13.2 kB
"use strict"; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const dom = require("../_utils/dom.js"); const useOutClick = require("../hooks/use-out-click.js"); function getWrapperContentRect(wrapperEl, wrapperRect) { const rect = wrapperRect || wrapperEl.getBoundingClientRect(); const left = rect.left + wrapperEl.clientLeft; const top = rect.top + wrapperEl.clientTop; return { left, top, right: left + wrapperEl.clientWidth, bottom: top + wrapperEl.clientHeight }; } const viewOffsetBuilders = { top: (t, pSize, offset) => ({ left: t.left + t.width / 2 - pSize.width / 2, top: t.top - offset - pSize.height }), bottom: (t, pSize, offset) => ({ left: t.left + t.width / 2 - pSize.width / 2, top: t.bottom + offset }), left: (t, pSize, offset) => ({ left: t.left - offset - pSize.width, top: t.top + t.height / 2 - pSize.height / 2 }), right: (t, pSize, offset) => ({ left: t.right + offset, top: t.top + t.height / 2 - pSize.height / 2 }), tl: (t, pSize, offset) => ({ left: t.left, top: t.top - offset - pSize.height }), tr: (t, pSize, offset) => ({ left: t.right - pSize.width, top: t.top - offset - pSize.height }), bl: (t, _pSize, offset) => ({ left: t.left, top: t.bottom + offset }), br: (t, pSize, offset) => ({ left: t.right - pSize.width, top: t.bottom + offset }), lt: (t, pSize, offset) => ({ left: t.left - offset - pSize.width, top: t.top }), lb: (t, pSize, offset) => ({ left: t.left - offset - pSize.width, top: t.bottom - pSize.height }), rt: (t, _pSize, offset) => ({ left: t.right + offset, top: t.top }), rb: (t, pSize, offset) => ({ left: t.right + offset, top: t.bottom - pSize.height }) }; function getPopupViewOffset(position, { t, pSize, offset = 0 }) { return viewOffsetBuilders[position](t, pSize, offset); } function getWrapperViewEdge(popupSize, wrapperRect, edgeOffset = 0) { const viewport = { left: edgeOffset, // 使用 document.documentElement.clientWidth 而非 window.innerWidth 是为了去除滚动条宽度 right: document.documentElement.clientWidth - popupSize.width - edgeOffset, top: edgeOffset, bottom: document.documentElement.clientHeight - popupSize.height - edgeOffset }; if (!wrapperRect) { return viewport; } return { left: Math.max(viewport.left, wrapperRect.left), top: Math.max(viewport.top, wrapperRect.top), right: Math.min(viewport.right, wrapperRect.right - popupSize.width), bottom: Math.min(viewport.bottom, wrapperRect.bottom - popupSize.height) }; } function getPopupWrapOffset(pos, { wrapperEl, wrapperContentRect }) { if (!wrapperEl) { return pos; } const cs = dom.getScroll(wrapperEl); const offsetX = cs.scrollLeft - ((wrapperContentRect == null ? void 0 : wrapperContentRect.left) ?? 0); const offsetY = cs.scrollTop - ((wrapperContentRect == null ? void 0 : wrapperContentRect.top) ?? 0); const tx = typeof pos.left === "number" ? pos.left + offsetX : 0; const ty = typeof pos.top === "number" ? pos.top + offsetY : 0; return { left: tx, top: ty }; } const directionByPosition = { tl: "top", tr: "top", top: "top", bl: "bottom", br: "bottom", bottom: "bottom", lt: "left", lb: "left", left: "left", rt: "right", rb: "right", right: "right" }; function getDirection(position) { return directionByPosition[position]; } const positionFlipMap = { top: { bottom: "top", bl: "tl", br: "tr" }, bottom: { top: "bottom", tl: "bl", tr: "br" }, left: { right: "left", rt: "lt", rb: "lb" }, right: { left: "right", lt: "rt", lb: "rb" } }; function adjustPosition(position, direction) { return positionFlipMap[direction][position] ?? position; } function detectTopFlip({ pos, edge }) { if (typeof pos.top !== "number") return null; return edge.top > pos.top ? "bottom" : null; } function detectBottomFlip({ pos, edge }) { if (typeof pos.top !== "number") return null; return edge.bottom < pos.top ? "top" : null; } function detectLeftFlip({ pos, edge }) { if (typeof pos.left !== "number") return null; return edge.left > pos.left ? "right" : null; } function detectRightFlip({ pos, edge }) { if (typeof pos.left !== "number") return null; return edge.right < pos.left ? "left" : null; } const flipDetectors = { top: detectTopFlip, bottom: detectBottomFlip, left: detectLeftFlip, right: detectRightFlip }; function maybeFlipDirection(position, ctx) { const d = getDirection(position); const style = { ...ctx.popupPosition }; const direction = flipDetectors[d]({ pos: ctx.popupPosition, edge: ctx.edge, popupSize: ctx.popupSize }); if (direction === null) { return { fixedPosition: position, style }; } const fixedPosition = adjustPosition(position, direction); return { fixedPosition, style: getPopupViewOffset(fixedPosition, { t: ctx.tRect, pSize: ctx.popupSize, offset: ctx.offset }) }; } function clampLeft(left, edge) { if (edge.left > left) { return edge.left; } if (edge.right < left) { return edge.right; } return left; } function clampTop(top, edge) { if (edge.top > top) { return edge.top; } if (edge.bottom < top) { return edge.bottom; } return top; } function adjustOffset(position, { popupPosition, popupSize, tRect, wRect, offset, edgeOffset }) { const edge = getWrapperViewEdge(popupSize, wRect, edgeOffset); const flipped = maybeFlipDirection(position, { popupPosition, popupSize, tRect, edge, offset }); const style = { ...flipped.style }; if (typeof style.left === "number") { style.left = clampLeft(style.left, edge); } if (typeof style.top === "number") { style.top = clampTop(style.top, edge); } return { position: flipped.fixedPosition, popupStyle: style }; } const bottomEdgeRules = (x) => ({ left: `${x}px`, bottom: "0px" }); const topEdgeRules = (x) => ({ left: `${x}px`, top: "0px" }); const rightEdgeRules = (_x, y) => ({ top: `${y}px`, right: "0px" }); const leftEdgeRules = (_x, y) => ({ top: `${y}px`, left: "0px" }); const anchorRuleByPosition = { top: bottomEdgeRules, tl: bottomEdgeRules, tr: bottomEdgeRules, bottom: topEdgeRules, bl: topEdgeRules, br: topEdgeRules, left: rightEdgeRules, lt: rightEdgeRules, lb: rightEdgeRules, right: leftEdgeRules, rt: leftEdgeRules, rb: leftEdgeRules }; function getAnchorOffset(position, { tRect, popupStyle, popupSize, anchorOffset = 8 }) { const targetCenterX = tRect.left + tRect.width / 2; const targetCenterY = tRect.top + tRect.height / 2; const rawX = targetCenterX - (popupStyle.left ?? 0); const rawY = targetCenterY - (popupStyle.top ?? 0); const x = Math.min(Math.max(rawX, anchorOffset), popupSize.width - anchorOffset); const y = Math.min(Math.max(rawY, anchorOffset), popupSize.height - anchorOffset); return anchorRuleByPosition[position](x, y); } function resolveWrapperContext(popupEl) { const wrapperEl = dom.getOffsetElement(popupEl); if (!wrapperEl) { return { wrapperEl: null, isWrapperBounded: false }; } const wrapperRect = wrapperEl.getBoundingClientRect(); const wrapperContentRect = wrapperEl.nodeName === "HTML" ? void 0 : getWrapperContentRect(wrapperEl, wrapperRect); let isWrapperBounded = false; if (wrapperContentRect && typeof window !== "undefined") { const cs = window.getComputedStyle(wrapperEl); isWrapperBounded = cs.overflowX !== "visible" || cs.overflowY !== "visible"; } return { wrapperEl, wrapperContentRect, isWrapperBounded }; } function calcPopupStyle({ popupEl, targetEl, position, adaptive = true, anchor = true, anchorOffset = 8, offset = 8, edgeOffset = 0 }) { const tRect = targetEl.getBoundingClientRect(); const popupSize = dom.getElementSize(popupEl); const { wrapperEl, wrapperContentRect, isWrapperBounded } = resolveWrapperContext(popupEl); const emptyAnchor = {}; if (!wrapperEl) { return { popupStyle: getPopupViewOffset(position, { t: tRect, pSize: popupSize, offset }), position, anchorStyle: emptyAnchor }; } let popupStyle = getPopupViewOffset(position, { t: tRect, pSize: popupSize, offset }); let fixedPosition = position; if (adaptive) { const rlt = adjustOffset(position, { popupPosition: popupStyle, popupSize, tRect, wRect: isWrapperBounded ? wrapperContentRect : void 0, offset, edgeOffset }); fixedPosition = rlt.position; popupStyle = rlt.popupStyle; } const anchorStyle = anchor ? getAnchorOffset(fixedPosition, { tRect, popupStyle, popupSize, anchorOffset }) : emptyAnchor; popupStyle = getPopupWrapOffset(popupStyle, { wrapperEl, wrapperContentRect }); return { position: fixedPosition, popupStyle, anchorStyle }; } function isInsidePopup(event, popupRef) { var _a; return !!((_a = popupRef.value) == null ? void 0 : _a.contains(event.target)); } function buildClickHandler({ el, autoHide, outClick, popupRef, updateFn, inner }) { return () => { inner(); if (autoHide) { const onOutsideClick = () => { updateFn(false); outClick.removeListener(el, onOutsideClick); }; outClick.addListener(el, onOutsideClick, { exception: (e) => isInsidePopup(e, popupRef) }); } }; } function bindClickTrigger(ctx, toggle) { const { el, autoHide, outClick, listeners, popupRef, updateFn } = ctx; const inner = toggle ? () => updateFn() : () => updateFn(true); const fn = buildClickHandler({ el, autoHide, outClick, popupRef, updateFn, inner }); el.addEventListener("click", fn); listeners.push(() => el.removeEventListener("click", fn)); } function bindDomEvent({ el, type, handler, listeners }) { el.addEventListener(type, handler); listeners.push(() => el.removeEventListener(type, handler)); } function bindHoverTrigger(ctx) { const { el, autoHide, hoverDelay, listeners, updateFn } = ctx; bindDomEvent({ el, type: "mouseenter", handler: () => updateFn(true, hoverDelay), listeners }); if (autoHide) { bindDomEvent({ el, type: "mouseleave", handler: () => updateFn(false, hoverDelay), listeners }); } } function bindFocusTrigger(ctx) { const { el, autoHide, listeners, updateFn } = ctx; bindDomEvent({ el, type: "focusin", handler: () => updateFn(true), listeners }); if (autoHide) { bindDomEvent({ el, type: "focusout", handler: () => updateFn(false), listeners }); } } function bindContextmenuTrigger(ctx) { const { el, autoHide, outClick, popupRef, listeners, updateFn } = ctx; bindDomEvent({ el, type: "contextmenu", handler: (e) => { e.preventDefault(); updateFn(true); }, listeners }); if (autoHide) { const hideFn = () => updateFn(false); outClick.addListener(el, hideFn, { exception: (e) => isInsidePopup(e, popupRef) }); listeners.push(() => outClick.removeListener(el, hideFn)); } } function bindHoverOutclickTrigger(ctx) { const { el, autoHide, hoverDelay, outClick, popupRef, listeners, updateFn } = ctx; bindDomEvent({ el, type: "mouseenter", handler: () => updateFn(true, hoverDelay), listeners }); if (autoHide) { const hideFn = () => updateFn(false); outClick.addListener(el, hideFn, { exception: (e) => isInsidePopup(e, popupRef) }); listeners.push(() => outClick.removeListener(el, hideFn)); } } function toClickCtx(ctx) { return { el: ctx.el, popupRef: ctx.popupRef, autoHide: ctx.autoHide, outClick: ctx.outClick, listeners: ctx.listeners, updateFn: ctx.updateFn }; } function noop() { } const triggerBinders = { hover: bindHoverTrigger, focus: bindFocusTrigger, contextmenu: bindContextmenuTrigger, none: noop, "hover-outclick": bindHoverOutclickTrigger }; const clickToggleMap = { click: true, "click-outclick": false }; function bindTrigger({ el, popupRef, triggers, updateFn, hoverDelay = 100, autoHide = true }) { if (!el) { return []; } const outClick = useOutClick.useOutClick(); const listeners = []; const ctx = { el, popupRef, autoHide, hoverDelay, outClick, listeners, updateFn }; const clickCtx = toClickCtx(ctx); triggers.forEach((tr) => { var _a; if (tr === "click" || tr === "click-outclick") { bindClickTrigger(clickCtx, clickToggleMap[tr]); return; } (_a = triggerBinders[tr]) == null ? void 0 : _a.call(triggerBinders, ctx); }); return listeners; } const TRANSFORM_ORIGIN_MAP = { top: { left: "50%", top: "100%" }, tl: { left: "0px", top: "100%" }, tr: { left: "100%", top: "100%" }, bottom: { left: "50%", top: "0px" }, bl: { left: "0px", top: "0px" }, br: { left: "100%", top: "0px" }, left: { left: "100%", top: "50%" }, lt: { left: "100%", top: "100%" }, lb: { left: "100%", top: "100%" }, right: { left: "0px", top: "50%" }, rt: { left: "0px", top: "0px" }, rb: { left: "0px", top: "100%" } }; function getTransformOrigin(position) { return TRANSFORM_ORIGIN_MAP[position]; } exports.bindTrigger = bindTrigger; exports.calcPopupStyle = calcPopupStyle; exports.getTransformOrigin = getTransformOrigin;