From 0bfc6c107bf05e95cfac7a95824dc83edd823e49 Mon Sep 17 00:00:00 2001 From: Olav Date: Wed, 2 Jul 2025 00:00:16 +0200 Subject: [PATCH 1/3] selector & --- cssr.js | 103 ++++++++++++++++++++++++++++++++++--------------- selectors.html | 58 ++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 31 deletions(-) create mode 100644 selectors.html diff --git a/cssr.js b/cssr.js index 768198d..e73c38d 100644 --- a/cssr.js +++ b/cssr.js @@ -1,49 +1,90 @@ (() => { if (document.__cssr_mo) return; - let styleEl = document.head.appendChild(document.createElement("style")); - let gen = new Set(); - let newRules = []; - let processNode = (node) => { - (node.getAttribute("class") || "") - .split(/\s+/) - .filter((t) => t.includes(":")) - .forEach((token) => { - if (gen.has(token)) return; - - let [prop, val] = token.split(/:(.+)/); - let cssValue = val.replace(/_/g, " "); - - if (!CSS.supports(prop, cssValue)) { - console.warn(`Unsupported CSS: ${prop}: ${cssValue}`); - return; + let styleElement = document.head.appendChild(document.createElement("style")); + let generatedTokens = new Set(); + let pendingRules = []; + + let escapeClassSelector = (className) => { + return "." + CSS.escape(className); + }; + + let parseDeclarations = (styleString) => { + if (!styleString) return []; + return styleString + .split(";") + .map((decl) => { + let [property, value] = decl.split(/:(.+)/); + if (!property || !value) return null; + return [property.trim(), value.replace(/_/g, " ").trim()]; + }) + .filter(Boolean); + }; + + let queueRule = (selector, declarations, token) => { + if (declarations.length === 0) return; + let ruleBody = declarations + .map(([prop, val]) => { + if (!CSS.supports || !CSS.supports(prop, val)) { + console.warn(`(Warning) Possibly unsupported CSS: ${prop}: ${val}`); } + return `${prop}: ${val};`; + }) + .join(" "); + pendingRules.push(`${selector} { ${ruleBody} }`); + generatedTokens.add(token); + }; - let rule = `.${CSS.escape(token)} { ${prop}: ${cssValue}; }`; - newRules.push(rule); - gen.add(token); - }); + let processClassToken = (token) => { + if (generatedTokens.has(token)) return; + if (!token.includes(":")) return; + if (token.startsWith("&")) { + let firstColon = token.indexOf(":"); + let selectorPart = token.slice(0, firstColon); + let stylePart = token.slice(firstColon + 1); + let baseSelector = escapeClassSelector(token); + let restSelector = selectorPart + .slice(1) + .replace(/_>_/g, " > ") + .replace(/_/g, " "); + let fullSelector = baseSelector + restSelector; + let declarations = parseDeclarations(stylePart); + queueRule(fullSelector, declarations, token); + } else { + let firstColon = token.indexOf(":"); + let property = token.slice(0, firstColon); + let value = token.slice(firstColon + 1).replace(/_/g, " "); + let selector = escapeClassSelector(token); + queueRule(selector, [[property, value]], token); + } + }; + + let processNode = (node) => { + let classAttr = node.getAttribute("class") || ""; + let tokens = classAttr.split(/\s+/).filter((t) => t.length > 0); + tokens.forEach(processClassToken); + flushRules(); }; - let updateStyle = () => { - if (newRules.length > 0) { - styleEl.textContent += "\n" + newRules.join("\n"); - newRules = []; + let flushRules = () => { + if (pendingRules.length > 0) { + styleElement.textContent += "\n" + pendingRules.join("\n"); + pendingRules = []; } }; let processTree = (root) => { - root.querySelectorAll('[class*=":"]').forEach((node) => processNode(node)); - updateStyle(); + root.querySelectorAll('[class*=":"]').forEach(processNode); + flushRules(); }; - document.__cssr_mo = new MutationObserver((muts) => { - muts.forEach((m) => - m.addedNodes.forEach( - (n) => n.nodeType === 1 && (processNode(n), processTree(n)) + document.__cssr_mo = new MutationObserver((mutations) => { + mutations.forEach((mutation) => + mutation.addedNodes.forEach( + (node) => node.nodeType === 1 && (processNode(node), processTree(node)) ) ); - updateStyle(); + flushRules(); }); document.addEventListener("DOMContentLoaded", () => { diff --git a/selectors.html b/selectors.html new file mode 100644 index 0000000..19cd30d --- /dev/null +++ b/selectors.html @@ -0,0 +1,58 @@ + + + + + + Runtime CSS Generator with Regex Token Extraction + + + + + +
+ Styled box +
+
+ Calc box +
+
Border box
+ + + +
+
+
+
+ Title +
+ Subtitle +

Subsubtitle

+
+
+ +
+ Title +
+ Subtitle +

Subsubtitle

+
+
+ + From b28c1d560e74a0991e4b730552444f4c06e5c542 Mon Sep 17 00:00:00 2001 From: Olav Date: Wed, 2 Jul 2025 00:03:36 +0200 Subject: [PATCH 2/3] cssar namechange --- cssr.js => cssar.js | 6 +++--- index.html | 4 ++-- selectors.html | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename cssr.js => cssar.js (95%) diff --git a/cssr.js b/cssar.js similarity index 95% rename from cssr.js rename to cssar.js index e73c38d..6cc1bf2 100644 --- a/cssr.js +++ b/cssar.js @@ -1,5 +1,5 @@ (() => { - if (document.__cssr_mo) return; + if (document.__cssar_mo) return; let styleElement = document.head.appendChild(document.createElement("style")); let generatedTokens = new Set(); @@ -78,7 +78,7 @@ flushRules(); }; - document.__cssr_mo = new MutationObserver((mutations) => { + document.__cssar_mo = new MutationObserver((mutations) => { mutations.forEach((mutation) => mutation.addedNodes.forEach( (node) => node.nodeType === 1 && (processNode(node), processTree(node)) @@ -89,7 +89,7 @@ document.addEventListener("DOMContentLoaded", () => { processTree(document.body); - document.__cssr_mo.observe(document.body, { + document.__cssar_mo.observe(document.body, { childList: true, subtree: true, }); diff --git a/index.html b/index.html index fd5c059..c8eb28b 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - Runtime CSS Generator with Regex Token Extraction + Runtime CSS Generator - + diff --git a/selectors.html b/selectors.html index 19cd30d..ecb495b 100644 --- a/selectors.html +++ b/selectors.html @@ -3,7 +3,7 @@ - Runtime CSS Generator with Regex Token Extraction + Runtime CSS Generator - +
Date: Mon, 7 Jul 2025 16:55:48 +0200 Subject: [PATCH 3/3] sophisticated handletoken --- cssar.js | 186 ++++++++++++++++++++++++++++++------------------- selectors.html | 36 ++++++++-- 2 files changed, 144 insertions(+), 78 deletions(-) diff --git a/cssar.js b/cssar.js index 6cc1bf2..7030dde 100644 --- a/cssar.js +++ b/cssar.js @@ -1,97 +1,141 @@ (() => { if (document.__cssar_mo) return; - let styleElement = document.head.appendChild(document.createElement("style")); - let generatedTokens = new Set(); - let pendingRules = []; + const styleEl = document.head.appendChild(document.createElement('style')); + const seen = new Set(); + const rules = []; - let escapeClassSelector = (className) => { - return "." + CSS.escape(className); - }; + const escapeClass = cls => '.' + CSS.escape(cls); - let parseDeclarations = (styleString) => { - if (!styleString) return []; - return styleString - .split(";") - .map((decl) => { - let [property, value] = decl.split(/:(.+)/); - if (!property || !value) return null; - return [property.trim(), value.replace(/_/g, " ").trim()]; - }) - .filter(Boolean); - }; + const parseDecls = str => + str + ? str + .split(';') + .map(d => d.split(/:(.+)/)) + .filter(([p, v]) => p && v != null) + .map(([p, v]) => { + let val = v.replace(/_/g, ' ').trim(); + // space‑wrap math ops between non‑spaces + val = val.replace(/(\S)([+\-*/])(\S)/g, '$1 $2 $3'); + return [p.trim(), val]; + }) + : []; - let queueRule = (selector, declarations, token) => { - if (declarations.length === 0) return; - let ruleBody = declarations - .map(([prop, val]) => { - if (!CSS.supports || !CSS.supports(prop, val)) { - console.warn(`(Warning) Possibly unsupported CSS: ${prop}: ${val}`); - } - return `${prop}: ${val};`; - }) - .join(" "); - pendingRules.push(`${selector} { ${ruleBody} }`); - generatedTokens.add(token); + const addRule = (sel, decls, tok) => { + if (!decls.length || seen.has(tok)) return; + rules.push(`${sel}{${decls.map(([p, v])=>`${p}:${v};`).join('')}}`); + seen.add(tok); }; - let processClassToken = (token) => { - if (generatedTokens.has(token)) return; - if (!token.includes(":")) return; - if (token.startsWith("&")) { - let firstColon = token.indexOf(":"); - let selectorPart = token.slice(0, firstColon); - let stylePart = token.slice(firstColon + 1); - let baseSelector = escapeClassSelector(token); - let restSelector = selectorPart - .slice(1) - .replace(/_>_/g, " > ") - .replace(/_/g, " "); - let fullSelector = baseSelector + restSelector; - let declarations = parseDeclarations(stylePart); - queueRule(fullSelector, declarations, token); + const handleToken = (token) => { + // 1) Skip if already seen or not a CSS token + if (seen.has(token) || !token.includes(':')) return; + + // 2) Which pseudos we support + const pseudos = ['hover','active','focus','visited','disabled','required']; + + // 3) Split on the *first* colon only + // e.g. ['hover', '#submitBtn:background-color:green;color:white'] + const [selPart, rest] = token.split(/:(.+)/); + + if (selPart.startsWith('&')) { + // ─── Nested selector branch ─── + // Strip leading &, handle > + ~ and underscores + const raw = selPart.slice(1); + const path = raw + .replace(/([>+~])/g, ' $1 ') + .replace(/_/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + + addRule( + escapeClass(token) + path, + parseDecls(rest), + token + ); + + } else if (pseudos.includes(selPart)) { + // ─── Pseudo‑class branch ─── + // rest might be: + // a) property:value… (apply to the element itself) + // b) selector:property:value… (apply to a nested selector) + const [maybeSel, declsPart] = rest.split(/:(.+)/); + + if (/^[#.]/.test(maybeSel) || maybeSel.startsWith('&')) { + // 3a) Pseudo on a nested selector + const raw = maybeSel.startsWith('&') + ? maybeSel.slice(1) + : maybeSel; + const path = raw + .replace(/([>+~])/g, ' $1 ') + .replace(/_/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + + addRule( + // .:hover
+ `${escapeClass(token)}:${selPart}${path}`, + parseDecls(declsPart), + token + ); + } else { + // 3b) Pseudo on the element itself + addRule( + // .:hover + `${escapeClass(token)}:${selPart}`, + parseDecls(rest), + token + ); + } + } else { - let firstColon = token.indexOf(":"); - let property = token.slice(0, firstColon); - let value = token.slice(firstColon + 1).replace(/_/g, " "); - let selector = escapeClassSelector(token); - queueRule(selector, [[property, value]], token); + // ─── Simple property:value branch ─── + // single declaration—do math‑spacing & underscore conversion + const property = selPart.trim(); + let value = rest + .replace(/_/g, ' ') + .replace(/(\S)([+\-*/])(\S)/g, '$1 $2 $3') + .trim(); + + addRule( + escapeClass(token), + [[property, value]], + token + ); } }; + - let processNode = (node) => { - let classAttr = node.getAttribute("class") || ""; - let tokens = classAttr.split(/\s+/).filter((t) => t.length > 0); - tokens.forEach(processClassToken); - flushRules(); - }; + const processNode = n => + (n.className || '') + .split(/\s+/) + .filter(Boolean) + .forEach(handleToken); - let flushRules = () => { - if (pendingRules.length > 0) { - styleElement.textContent += "\n" + pendingRules.join("\n"); - pendingRules = []; - } - }; - - let processTree = (root) => { + const processTree = root => root.querySelectorAll('[class*=":"]').forEach(processNode); - flushRules(); + + const flush = () => { + if (!rules.length) return; + styleEl.textContent += '\n' + rules.join('\n'); + rules.length = 0; }; - document.__cssar_mo = new MutationObserver((mutations) => { - mutations.forEach((mutation) => - mutation.addedNodes.forEach( - (node) => node.nodeType === 1 && (processNode(node), processTree(node)) + document.__cssar_mo = new MutationObserver(muts => { + muts.forEach(m => + m.addedNodes.forEach(n => + n.nodeType === 1 && (processNode(n), processTree(n)) ) ); - flushRules(); + flush(); }); - document.addEventListener("DOMContentLoaded", () => { + document.addEventListener('DOMContentLoaded', () => { processTree(document.body); document.__cssar_mo.observe(document.body, { childList: true, - subtree: true, + subtree: true }); + flush(); }); })(); diff --git a/selectors.html b/selectors.html index ecb495b..dd45f5d 100644 --- a/selectors.html +++ b/selectors.html @@ -23,7 +23,7 @@
Calc box
@@ -31,7 +31,7 @@ @@ -39,7 +39,7 @@


-
+
Title
Subtitle @@ -47,12 +47,34 @@

Subsubtitle

-
- Title +
+ Title222
- Subtitle -

Subsubtitle

+ Subtitle222 +

Subsubtitle222

+
+
+ + + +
+ Title222 +
+ Subtitle222 +

Subsubtitle222

+ +
Submit
+
+ +