@mitre/nuxt-smartscript
Version:
Smart typography transformations for Nuxt - automatic superscript, subscript, and symbol formatting
120 lines (119 loc) • 4.2 kB
JavaScript
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;
}
});
}