UNPKG

@mitre/nuxt-smartscript

Version:

Smart typography transformations for Nuxt - automatic superscript, subscript, and symbol formatting

120 lines (119 loc) 4.2 kB
import { CSS_CLASSES } from "./config.js"; import { logger } from "./logger.js"; const DIGITS_ONLY = /^\d+$/; const ORDINAL_SUFFIX = /^(?:st|nd|rd|th)$/; export function createSuperscriptElement(content, type) { const sup = document.createElement( type === "trademark" || type === "registered" ? "span" : "sup" ); sup.textContent = content; switch (type) { case "trademark": sup.className = `${CSS_CLASSES.superscript} ${CSS_CLASSES.trademark}`; sup.setAttribute("aria-label", "trademark"); break; case "registered": sup.className = `${CSS_CLASSES.superscript} ${CSS_CLASSES.registered}`; sup.setAttribute("aria-label", "registered"); break; case "ordinal": sup.className = `${CSS_CLASSES.superscript} ${CSS_CLASSES.ordinal}`; sup.setAttribute("aria-label", content); break; case "math": sup.className = `${CSS_CLASSES.superscript} ${CSS_CLASSES.math}`; sup.setAttribute("aria-label", `superscript ${content}`); break; default: sup.className = CSS_CLASSES.superscript; sup.setAttribute("aria-label", `superscript ${content}`); } return sup; } export function createSubscriptElement(content, type) { const sub = document.createElement("sub"); sub.textContent = content; sub.className = CSS_CLASSES.subscript; switch (type) { case "chemical": if (DIGITS_ONLY.test(content)) { sub.setAttribute("aria-label", content); } else { sub.setAttribute("aria-label", `subscript ${content}`); } break; case "math": sub.setAttribute("aria-label", `subscript ${content}`); break; default: sub.setAttribute("aria-label", `subscript ${content}`); } return sub; } export function createFragmentFromParts(parts) { const fragment = document.createDocumentFragment(); logger.trace("Creating fragment from parts:", parts); parts.forEach((part) => { if (part.type === "super") { let element; if (part.subtype && part.subtype !== "chemical") { element = createSuperscriptElement(part.content, part.subtype); } else { if (part.content === "\u2122") { element = createSuperscriptElement(part.content, "trademark"); } else if (part.content === "\xAE") { element = createSuperscriptElement(part.content, "registered"); } else if (ORDINAL_SUFFIX.test(part.content)) { element = createSuperscriptElement(part.content, "ordinal"); } else { element = createSuperscriptElement(part.content, "generic"); } } fragment.appendChild(element); } else if (part.type === "sub") { const subtype = part.subtype === "math" ? "math" : part.subtype === "chemical" ? "chemical" : DIGITS_ONLY.test(part.content) ? "chemical" : "generic"; const element = createSubscriptElement(part.content, subtype); fragment.appendChild(element); } else { fragment.appendChild(document.createTextNode(part.content)); } }); return fragment; } export function shouldExcludeElement(element, excludeSelectors) { return excludeSelectors.some((selector) => { try { return element.matches(selector) || element.closest(selector) !== null; } catch (error) { logger.warn("Invalid exclude selector:", selector, error); return false; } }); } export function shouldIncludeElement(element, includeSelectors) { return includeSelectors.some((selector) => { try { return element.matches(selector); } catch { return false; } }); } export function markAsProcessed(element) { if (element instanceof HTMLElement) { element.dataset.superscriptProcessed = "true"; logger.trace("Marked element as processed:", element.tagName); } } export function isProcessed(element) { return element.dataset?.superscriptProcessed === "true"; } export function resetProcessingFlags() { const processed = document.querySelectorAll("[data-superscript-processed]"); logger.debug(`Resetting ${processed.length} processing flags`); processed.forEach((el) => { if (el instanceof HTMLElement) { delete el.dataset.superscriptProcessed; } }); }