import { readFileSync, existsSync } from "fs"; // TODO import.meta.resolve is supposed to accomplish this without "path" import path from "path"; import { stringify, parse } from "css"; export function style(options) { if (options.inline || options.css === "none") return ""; const rules = load(options); const schemes = rules.find(({ media }) => media) ? `` : ""; return `\n${schemes}\n`; } export const inline = (options) => { const rules = load(options); return function inlineCSS(tag) { if ((options.body || options.inline) && options.css !== "none") { const styles = reduceVariables(rules, options) .filter(({ selectors }) => selectors && selectors.includes(tag)) .reduce((style, { declarations }) => { declarations.forEach(({ property, value }) => { style = `${style}${property}:${value};`; }); return style; }, ""); return styles !== "" ? ` style="${styles}"` : ""; } else { return ""; } }; }; export function load(options) { if (options.css === "none") return []; options.css = options.css ? options.css : options.body ? "gmi.css" : "gmi-web.css"; if ( ["gmi.css", "gmi-web.css"].includes(options.css) || existsSync(options.css) ) { const internal = (file) => path.resolve(path.dirname(new URL(import.meta.url).pathname), file); return parse( readFileSync( path.resolve( ["gmi-web.css", "gmi.css"].includes(options.css) ? internal(options.css) : resolve(options.css) ), "utf-8" ) ).stylesheet.rules; } else { throw new Error(`Cannot find file ${options.css}.`); } } export function rootVariables(rules) { const root = rules.find( ({ selectors }) => selectors && selectors.includes(":root") ); if (!root) return {}; return root.declarations.reduce( (obj, { property, value }) => !/^\-\-/.test(property) ? obj : Object.assign(obj, { [property]: value }), {} ); } function reduceVariables(rules, options) { const defaultVariables = rootVariables(rules); const CSS_VAR = /(^var\((?.+)\)|(?.+))/; return rules .map((rule) => Object.assign( rule, rule.declarations ? { declarations: rule.declarations.map((declaration) => { let { key, val } = CSS_VAR.exec(declaration.value).groups; // only one level of variable referencing is supported key = CSS_VAR.exec(options[key] || defaultVariables[key]).groups .key || key; return Object.assign(declaration, { value: key ? options[key] || defaultVariables[key] : declaration.value, }); }), } : {} ) ) .map((rule) => { if (!rule.media) return rule; return Object.assign(rule, { rules: rule.rules.map((rule) => Object.assign( rule, rule.declarations ? { declarations: rule.declarations.map((declaration) => { let { key, val } = CSS_VAR.exec(declaration.value).groups; // only one level of variable referencing is supported key = CSS_VAR.exec(options[key] || defaultVariables[key]).groups .key || key; return Object.assign(declaration, { value: key ? options[key] || defaultVariables[key] : declaration.value, }); }), } : {} ) ), }); }) .filter( ({ selectors, media }) => media || (selectors && !selectors.includes(":root")) ); }