import { useEffect, useRef, useState } from "react"; import { Plus, Type } from "../../icons/SystemIcons"; import { isTextEditableSelection, type DomEditSelection } from "./domEditing"; import type { ImportedFontAsset } from "./fontAssets"; import { FIELD, LABEL, normalizeTextMetricValue, RESPONSIVE_GRID } from "./propertyPanelHelpers"; import { MetricField, Section, SelectField } from "./propertyPanelPrimitives"; import { ColorField } from "./propertyPanelColor"; import { FontFamilyField } from "./propertyPanelFont"; /* ------------------------------------------------------------------ */ /* Text helpers (used only by text section components) */ /* ------------------------------------------------------------------ */ function formatTextFieldPreview(value: string): string { const collapsed = value.trim().replace(/\s+/g, " "); if (collapsed.length <= 56) return collapsed; return `${collapsed.slice(0, 55)}…`; } function getTextFieldColor( field: { computedStyles: Record }, inheritedStyles: Record, ): string { return field.computedStyles.color || inheritedStyles.color || "rgb(0, 0, 0)"; } function getTextStyleValue( field: { computedStyles: Record }, inheritedStyles: Record, property: string, fallback: string, ): string { return field.computedStyles[property] || inheritedStyles[property] || fallback; } const ALL_WEIGHTS = ["100", "200", "300", "400", "500", "600", "700", "800", "900"]; const WEIGHT_LABELS: Record = { "100": "100 · Thin", "200": "200 · Extra Light", "300": "300 · Light", "400": "400 · Regular", "500": "500 · Medium", "600": "600 · Semi Bold", "700": "700 · Bold", "800": "800 · Extra Bold", "900": "900 · Black", }; function detectAvailableWeights(fontFamily: string): string[] { const fonts = document.fonts; if (!fonts) return ALL_WEIGHTS; const family = fontFamily.split(",")[0]?.trim().replace(/['"]/g, ""); if (!family) return ALL_WEIGHTS; const available: string[] = []; for (const w of ALL_WEIGHTS) { if (fonts.check(`${w} 16px "${family}"`)) available.push(w); } return available.length > 0 ? available : ALL_WEIGHTS; } function TextAreaField({ label, value, disabled, autoFocus, onCommit, }: { label: string; value: string; disabled?: boolean; autoFocus?: boolean; onCommit: (nextValue: string) => void; }) { const [draft, setDraft] = useState(value); const textareaRef = useRef(null); const commitTimerRef = useRef | null>(null); const focusedRef = useRef(false); const valueRef = useRef(value); valueRef.current = value; useEffect(() => { if (focusedRef.current) return; setDraft(value); }, [value]); useEffect( () => () => { if (commitTimerRef.current) clearTimeout(commitTimerRef.current); }, [], ); useEffect(() => { if (!autoFocus) return; textareaRef.current?.focus(); }, [autoFocus]); const commitDraft = (d: string) => { if (commitTimerRef.current) clearTimeout(commitTimerRef.current); if (d !== valueRef.current) onCommit(d); }; const scheduleCommit = (d: string) => { if (commitTimerRef.current) clearTimeout(commitTimerRef.current); commitTimerRef.current = setTimeout(() => { if (d !== valueRef.current) onCommit(d); }, 120); }; return (