import React, { useEffect, useRef } from 'react'; import { hostProps } from '../utils/host-props'; import { useBooleanProperty } from '../utils/use-boolean-prop'; type Placement = | 'top-start' | 'top' | 'top-end' | 'right-start' | 'right' | 'right-end' | 'bottom-start' | 'bottom' | 'bottom-end' | 'left-start' | 'left' | 'left-end'; export interface TyPopupProps extends Omit, 'onClose'> { /** Preferred placement of the popup relative to anchor parent: a side plus an optional cross-axis alignment (e.g. "bottom-start") */ placement?: Placement; /** Distance offset from the anchor in pixels (default: 8) */ offset?: number; /** Disable automatic click trigger - requires manual open/close via ref methods */ manual?: boolean; /** Disable automatic close on outside click and ESC key */ disableClose?: boolean; /** Fired when the popup opens (after the open animation starts) */ onOpen?: (event: CustomEvent) => void; /** Fired when the popup closes */ onClose?: (event: CustomEvent) => void; /** Popup content - popup should be a child of the anchor element */ children?: React.ReactNode; } export interface TyPopupElement extends HTMLElement { openPopup(): void; closePopup(): void; togglePopup(): void; } export const TyPopup = React.forwardRef( ({ placement, offset, manual, disableClose, onOpen, onClose, children, ...props }, ref) => { const elementRef = useRef(null); useEffect(() => { if (ref && elementRef.current) { if (typeof ref === 'function') { ref(elementRef.current); } else { ref.current = elementRef.current; } } }, [ref]); // Listen for popup open/close events. See TyModal.tsx for why the // `event.target === element` guard is still here. useEffect(() => { const element = elementRef.current; if (!element) return; const handleOpen = (event: Event) => { if (event.target !== element) return; if (onOpen) onOpen(event as CustomEvent); }; const handleClose = (event: Event) => { if (event.target !== element) return; if (onClose) onClose(event as CustomEvent); }; if (onOpen) element.addEventListener('open', handleOpen); if (onClose) element.addEventListener('close', handleClose); return () => { if (onOpen) element.removeEventListener('open', handleOpen); if (onClose) element.removeEventListener('close', handleClose); }; }, [onOpen, onClose]); const webComponentProps: Record = { ...hostProps(props), ref: elementRef, }; if (placement) { webComponentProps.placement = placement; } if (offset !== undefined) { webComponentProps.offset = offset.toString(); } const isManual = useBooleanProperty(elementRef, 'manual', manual); const isDisableClose = useBooleanProperty(elementRef, 'disableClose', disableClose); if (isManual) webComponentProps.manual = ''; if (isDisableClose) webComponentProps['disable-close'] = ''; return React.createElement( 'ty-popup', webComponentProps, children ); } ); TyPopup.displayName = 'TyPopup';