const bootstrap = globalThis.__UI_COPY_EDITOR_BUILD__ || null; const placeholderPattern = /\{([a-zA-Z][a-zA-Z0-9_]*)\}/g; const targetRegistry = new Map(); const accessibleNameState = new WeakMap(); let activeDraft = bootstrap?.initial_draft || { overrides: {} }; function formatTemplate(text, values = {}) { return String(text).replace(placeholderPattern, (_match, name) => values[name] ?? `{${name}}`); } function preserveAccessibleName(record, text) { const host = record.host; if (!host) return; if (!accessibleNameState.has(host)) accessibleNameState.set(host, host.getAttribute("aria-label")); host.setAttribute("aria-label", text || record.defaultText); } function restoreAccessibleName(record) { if (!record.host || !accessibleNameState.has(record.host)) return; const original = accessibleNameState.get(record.host); if (original === null) record.host.removeAttribute("aria-label"); else record.host.setAttribute("aria-label", original); accessibleNameState.delete(record.host); } function applyRecord(record) { if (!record.target.isConnected) return; const override = activeDraft.overrides?.[record.copyId] || {}; const sourceText = override.text ?? record.defaultText; const renderedText = formatTemplate(sourceText, record.values); if (record.attribute) { record.target.setAttribute(record.attribute, renderedText); return; } const hidden = Object.hasOwn(override, "hidden") ? override.hidden : Boolean(record.defaultHidden); record.target.textContent = hidden ? "" : renderedText; if (hidden) preserveAccessibleName(record, renderedText); else restoreAccessibleName(record); if (override.font_size_px) { if (!record.fontCaptured) { record.originalFontSize = record.target.style.fontSize; record.fontCaptured = true; } record.target.style.fontSize = `${override.font_size_px}px`; } else if (record.fontCaptured) { record.target.style.fontSize = record.originalFontSize; record.fontCaptured = false; } record.target.classList.toggle("ui-copy-has-override", Boolean(activeDraft.overrides?.[record.copyId])); } function registerRecord(record) { const previous = targetRegistry.get(record.target); if (previous) { record.originalFontSize = previous.originalFontSize; record.fontCaptured = previous.fontCaptured; } targetRegistry.set(record.target, record); if (bootstrap) record.target.dataset.uiCopyId = record.copyId; applyRecord(record); return record; } export function renderUiCopy(copyId, element, defaultText, values = {}, options = {}) { if (!(element instanceof Element)) return; if (!bootstrap) { const renderedText = formatTemplate(defaultText, values); const record = { host: element, defaultText }; element.textContent = options.defaultHidden ? "" : renderedText; if (options.defaultHidden) preserveAccessibleName(record, renderedText); else restoreAccessibleName(record); return; } registerRecord({ copyId, target: element, host: element, defaultText, values, kind: "dynamic_template", attribute: null, defaultHidden: Boolean(options.defaultHidden), }); } export function renderUiValue(element, value) { if (!(element instanceof Element)) return; const priorRecord = targetRegistry.get(element); restoreAccessibleName(priorRecord || { host: element }); targetRegistry.delete(element); delete element.dataset.uiCopyId; element.textContent = String(value ?? ""); } export function registerStaticUiCopy(copyId, target, host, defaultText, kind, attribute = null, defaultHidden = false) { if (!bootstrap || !(target instanceof Element)) return null; return registerRecord({ copyId, target, host: host || target, defaultText, values: {}, kind, attribute, defaultHidden: Boolean(defaultHidden), }); } export function setUiCopyDraft(nextDraft) { activeDraft = nextDraft; for (const record of targetRegistry.values()) applyRecord(record); } export function applyUiCopyId(copyId) { for (const record of targetRegistry.values()) { if (record.copyId === copyId) applyRecord(record); } } export function uiCopyRecords(copyId = null) { const records = Array.from(targetRegistry.values()).filter((record) => record.target.isConnected); return copyId === null ? records : records.filter((record) => record.copyId === copyId); } export function unregisterUiCopySubtree(root) { for (const [target] of targetRegistry) { if (!target.isConnected || target === root || root?.contains?.(target)) targetRegistry.delete(target); } }