UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

1 lines • 3.99 kB
{"version":3,"file":"useRepopOnFarTriggerSwitch.mjs","names":[],"sources":["../../src/utils/useRepopOnFarTriggerSwitch.ts"],"sourcesContent":["import { useLayoutEffect, useRef, useState } from 'react';\n\ntype PopupStoreLike = {\n state: {\n activeTriggerElement?: Element | null;\n open?: boolean;\n popupElement?: HTMLElement | null;\n };\n useState?: (...args: any[]) => unknown;\n};\n\nexport const FAR_TRIGGER_SWITCH_THRESHOLD = 280;\n\nconst REPOP_ANIMATION: [Keyframe[], KeyframeAnimationOptions] = [\n [\n { opacity: 0, scale: 0.97 },\n { opacity: 1, scale: 1 },\n ],\n { duration: 200, easing: 'cubic-bezier(0.33, 1, 0.68, 1)' },\n];\n\nconst centerOf = (el: Element) => {\n const rect = el.getBoundingClientRect();\n return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };\n};\n\nconst prefersReducedMotion = () =>\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n// A morph across a long distance reads as a stray box flying over the page, so past `threshold`\n// the switch is made instant and the popup re-plays a short enter animation at the new anchor.\nexport const useRepopOnFarTriggerSwitch = (\n store: PopupStoreLike,\n options?: { enabled?: boolean; threshold?: number },\n): boolean => {\n const enabled = options?.enabled ?? true;\n const threshold = options?.threshold ?? FAR_TRIGGER_SWITCH_THRESHOLD;\n\n const activeTrigger =\n (store.useState?.('activeTriggerElement') as Element | null | undefined) ??\n store.state.activeTriggerElement ??\n null;\n const open = (store.useState?.('open') as boolean | undefined) ?? Boolean(store.state.open);\n const popupElement =\n (store.useState?.('popupElement') as HTMLElement | null | undefined) ??\n store.state.popupElement ??\n null;\n\n const lastRef = useRef<{ center: { x: number; y: number } | null; el: Element | null }>({\n center: null,\n el: null,\n });\n const [repop, setRepop] = useState(false);\n\n useLayoutEffect(() => {\n if (!open || !enabled) {\n lastRef.current = { center: null, el: null };\n setRepop(false);\n return;\n }\n const last = lastRef.current;\n if (activeTrigger === last.el) return;\n\n const center = activeTrigger ? centerOf(activeTrigger) : null;\n const far = Boolean(\n last.el &&\n last.center &&\n center &&\n Math.hypot(center.x - last.center.x, center.y - last.center.y) > threshold,\n );\n lastRef.current = { center, el: activeTrigger };\n setRepop(far);\n\n if (far && popupElement && !prefersReducedMotion()) {\n popupElement.animate?.(...REPOP_ANIMATION);\n }\n }, [activeTrigger, enabled, open, popupElement, threshold]);\n\n return repop;\n};\n"],"mappings":";AAaA,MAAM,kBAA0D,CAC9D,CACE;CAAE,SAAS;CAAG,OAAO;AAAK,GAC1B;CAAE,SAAS;CAAG,OAAO;AAAE,CACzB,GACA;CAAE,UAAU;CAAK,QAAQ;AAAiC,CAC5D;AAEA,MAAM,YAAY,OAAgB;CAChC,MAAM,OAAO,GAAG,sBAAsB;CACtC,OAAO;EAAE,GAAG,KAAK,OAAO,KAAK,QAAQ;EAAG,GAAG,KAAK,MAAM,KAAK,SAAS;CAAE;AACxE;AAEA,MAAM,6BACJ,OAAO,WAAW,eAClB,OAAO,OAAO,eAAe,cAC7B,OAAO,WAAW,kCAAkC,CAAC,CAAC;AAIxD,MAAa,8BACX,OACA,YACY;CACZ,MAAM,UAAU,SAAS,WAAW;CACpC,MAAM,YAAY,SAAS,aAAA;CAE3B,MAAM,gBACH,MAAM,WAAW,sBAAsB,KACxC,MAAM,MAAM,wBACZ;CACF,MAAM,OAAQ,MAAM,WAAW,MAAM,KAA6B,QAAQ,MAAM,MAAM,IAAI;CAC1F,MAAM,eACH,MAAM,WAAW,cAAc,KAChC,MAAM,MAAM,gBACZ;CAEF,MAAM,UAAU,OAAwE;EACtF,QAAQ;EACR,IAAI;CACN,CAAC;CACD,MAAM,CAAC,OAAO,YAAY,SAAS,KAAK;CAExC,sBAAsB;EACpB,IAAI,CAAC,QAAQ,CAAC,SAAS;GACrB,QAAQ,UAAU;IAAE,QAAQ;IAAM,IAAI;GAAK;GAC3C,SAAS,KAAK;GACd;EACF;EACA,MAAM,OAAO,QAAQ;EACrB,IAAI,kBAAkB,KAAK,IAAI;EAE/B,MAAM,SAAS,gBAAgB,SAAS,aAAa,IAAI;EACzD,MAAM,MAAM,QACV,KAAK,MACL,KAAK,UACL,UACA,KAAK,MAAM,OAAO,IAAI,KAAK,OAAO,GAAG,OAAO,IAAI,KAAK,OAAO,CAAC,IAAI,SACnE;EACA,QAAQ,UAAU;GAAE;GAAQ,IAAI;EAAc;EAC9C,SAAS,GAAG;EAEZ,IAAI,OAAO,gBAAgB,CAAC,qBAAqB,GAC/C,aAAa,UAAU,GAAG,eAAe;CAE7C,GAAG;EAAC;EAAe;EAAS;EAAM;EAAc;CAAS,CAAC;CAE1D,OAAO;AACT"}