'use client'; /* eslint-disable @typescript-eslint/no-explicit-any */ import { createElement, useEffect, useRef } from 'react'; import type React from 'react'; import { InvalidField } from '@/TailwindPlay/Fields/Invalid.js'; import type { TailwindPlayCommonParams } from '@/TailwindPlay/Fields/types.js'; import { extractHusarData } from '@/TailwindPlay/shared/extract.js'; import { isValidHtmlComponent } from '@/TailwindPlay/shared/constants.js'; type ScriptKind = 'classic' | 'module'; const externalScriptLoads = new WeakMap>>(); const unsuitableContainerTags = new Set(['img', 'input', 'option', 'script', 'select', 'textarea']); const getScriptKind = (script: HTMLScriptElement): ScriptKind => script.type.trim().toLowerCase() === 'module' ? 'module' : 'classic'; const copyScript = (source: HTMLScriptElement, target: HTMLScriptElement) => { Array.from(source.attributes).forEach(({ name, value }) => target.setAttribute(name, value)); target.text = source.textContent || ''; }; const loadExternalScript = (source: HTMLScriptElement, ownerDocument: Document) => { let sourceUrl: string; try { sourceUrl = new URL(source.getAttribute('src') || '', ownerDocument.baseURI).href; } catch { return Promise.reject(new Error(`Invalid external script URL: ${source.getAttribute('src') || ''}`)); } const kind = getScriptKind(source); const registryKey = `${kind}:${sourceUrl}`; let registry = externalScriptLoads.get(ownerDocument); if (!registry) { registry = new Map(); externalScriptLoads.set(ownerDocument, registry); } const registeredLoad = registry.get(registryKey); if (registeredLoad) return registeredLoad; const existingScript = Array.from(ownerDocument.scripts).find( (script) => script.src === sourceUrl && getScriptKind(script) === kind, ); if (existingScript) { const existingLoad = Promise.resolve(existingScript); registry.set(registryKey, existingLoad); return existingLoad; } const executableScript = ownerDocument.createElement('script'); copyScript(source, executableScript); executableScript.async = source.async; let resolveLoad!: (script: HTMLScriptElement) => void; let rejectLoad!: (error: Error) => void; const load = new Promise((resolve, reject) => { resolveLoad = resolve; rejectLoad = reject; }); executableScript.addEventListener('load', () => resolveLoad(executableScript), { once: true }); executableScript.addEventListener( 'error', () => { if (registry?.get(registryKey) === load) registry.delete(registryKey); executableScript.remove(); rejectLoad(new Error(`Failed to load external script: ${sourceUrl}`)); }, { once: true }, ); registry.set(registryKey, load); const insertionPoint = ownerDocument.head || ownerDocument.documentElement; try { insertionPoint.appendChild(executableScript); } catch (error) { if (registry.get(registryKey) === load) registry.delete(registryKey); executableScript.remove(); rejectLoad(error instanceof Error ? error : new Error(`Failed to append external script: ${sourceUrl}`)); } return load; }; const executeScript = (source: HTMLScriptElement, container: HTMLElement) => { if (source.hasAttribute('src')) { void loadExternalScript(source, container.ownerDocument).catch(() => undefined); return; } const executableScript = container.ownerDocument.createElement('script'); copyScript(source, executableScript); container.appendChild(executableScript); }; export const FieldScript: React.FC = (props) => { const containerRef = useRef(null); const appliedValueRef = useRef<{ container: HTMLElement; value: string } | null>(null); const { v, attributes } = extractHusarData(props); useEffect(() => { const container = containerRef.current; if (!container || typeof v !== 'string') return; const appliedValue = appliedValueRef.current; if (appliedValue?.container === container && appliedValue.value === v) return; appliedValueRef.current = { container, value: v }; container.replaceChildren(); const trimmedValue = v.trim(); if (!trimmedValue) return; if (!trimmedValue.startsWith('<')) { const source = container.ownerDocument.createElement('script'); source.text = v; executeScript(source, container); return; } const template = container.ownerDocument.createElement('template'); template.innerHTML = v; const scripts = Array.from(template.content.querySelectorAll('script')); scripts.forEach((script) => script.remove()); container.appendChild(template.content); scripts.forEach((script) => executeScript(script, container)); }, [v]); if (typeof v !== 'string' && v !== null && typeof v !== 'undefined') return ; const configuredTag = isValidHtmlComponent(attributes); const tag = unsuitableContainerTags.has(configuredTag) ? 'div' : configuredTag; const containerAttributes = { ...attributes }; delete containerAttributes.children; delete containerAttributes.component; delete containerAttributes.dangerouslySetInnerHTML; return createElement(tag, { ...containerAttributes, ref: containerRef, }); };