'use client'; import type { LinePoint, LineStyleType, PPTLineElement } from '@openmaic/dsl'; import { useEffect, useLayoutEffect, useRef, useState, type MouseEvent } from 'react'; import { createPortal } from 'react-dom'; import { BringToFront, SendToBack, Trash2 } from 'lucide-react'; import { resolveLineToolbarLabels } from '../labels'; import type { LineFormatToolbarProps, TextToolbarLabels } from '../types'; import { DefaultColorPicker } from '../text/DefaultColorPicker'; import { getLineKind, toLineKindPatch, type LineKind } from './line-format'; const COLOR_POPOVER_WIDTH = 248; const COLOR_POPOVER_OFFSET = 8; const VIEWPORT_EDGE_OFFSET = 12; const useColorPopoverLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect; const WIDTHS = [1, 2, 3, 4, 6, 8] as const; const LINE_STYLES: readonly LineStyleType[] = ['solid', 'dashed', 'dotted']; const LINE_POINTS: readonly LinePoint[] = ['', 'arrow', 'dot']; export function LineFormatToolbar({ element, onChange, locale, labels: labelOverrides, placement, className, onBringToFront, onSendToBack, onDelete, }: LineFormatToolbarProps) { const labels = resolveLineToolbarLabels(locale, labelOverrides); const [isColorPickerOpen, setIsColorPickerOpen] = useState(false); const [colorPopoverPosition, setColorPopoverPosition] = useState({ left: 0, top: 0 }); const colorButtonRef = useRef(null); const colorPopoverRef = useRef(null); const emit = (props: Partial) => onChange([{ type: 'element.update', id: element.id, props }]); const preventFocusLoss = (event: MouseEvent) => event.preventDefault(); useEffect(() => { if (!isColorPickerOpen) return; const closeOnOutsidePointer = (event: PointerEvent) => { const target = event.target as Node; if (!colorButtonRef.current?.contains(target) && !colorPopoverRef.current?.contains(target)) { setIsColorPickerOpen(false); } }; document.addEventListener('pointerdown', closeOnOutsidePointer); return () => document.removeEventListener('pointerdown', closeOnOutsidePointer); }, [isColorPickerOpen]); useColorPopoverLayoutEffect(() => { if (!isColorPickerOpen) return; const updatePosition = () => { const rect = colorButtonRef.current?.getBoundingClientRect(); if (!rect) return; const minLeft = VIEWPORT_EDGE_OFFSET; const maxLeft = Math.max( minLeft, window.innerWidth - COLOR_POPOVER_WIDTH - VIEWPORT_EDGE_OFFSET, ); setColorPopoverPosition({ left: Math.min( Math.max(rect.left + rect.width / 2 - COLOR_POPOVER_WIDTH / 2, minLeft), maxLeft, ), top: rect.bottom + COLOR_POPOVER_OFFSET, }); }; updatePosition(); window.addEventListener('resize', updatePosition); document.addEventListener('scroll', updatePosition, true); return () => { window.removeEventListener('resize', updatePosition); document.removeEventListener('scroll', updatePosition, true); }; }, [isColorPickerOpen]); const currentKind = getLineKind(element); const colorPickerLabels = { color: labels.color } as TextToolbarLabels; const pointLabel = (point: LinePoint) => point === 'arrow' ? labels.arrow : point === 'dot' ? labels.dot : labels.none; return (
{(['start', 'end'] as const).map((position, index) => ( ))} {onBringToFront || onSendToBack || onDelete ? ( <> ); }