UNPKG

@oddbird/css-toggles

Version:

Polyfill for the proposed CSS Toggles syntax

619 lines (611 loc) 19.3 kB
// node_modules/stylis/src/Enum.js var COMMENT = "comm"; var RULESET = "rule"; var DECLARATION = "decl"; var IMPORT = "@import"; var KEYFRAMES = "@keyframes"; // node_modules/stylis/src/Utility.js var abs = Math.abs; var from = String.fromCharCode; function trim(value) { return value.trim(); } function replace(value, pattern, replacement) { return value.replace(pattern, replacement); } function indexof(value, search) { return value.indexOf(search); } function charat(value, index) { return value.charCodeAt(index) | 0; } function substr(value, begin, end) { return value.slice(begin, end); } function strlen(value) { return value.length; } function sizeof(value) { return value.length; } function append(value, array) { return array.push(value), value; } // node_modules/stylis/src/Tokenizer.js var line = 1; var column = 1; var length = 0; var position = 0; var character = 0; var characters = ""; function node(value, root, parent, type, props, children, length2) { return { value, root, parent, type, props, children, line, column, length: length2, return: "" }; } function char() { return character; } function prev() { character = position > 0 ? charat(characters, --position) : 0; if (column--, character === 10) column = 1, line--; return character; } function next() { character = position < length ? charat(characters, position++) : 0; if (column++, character === 10) column = 1, line++; return character; } function peek() { return charat(characters, position); } function caret() { return position; } function slice(begin, end) { return substr(characters, begin, end); } function token(type) { switch (type) { case 0: case 9: case 10: case 13: case 32: return 5; case 33: case 43: case 44: case 47: case 62: case 64: case 126: case 59: case 123: case 125: return 4; case 58: return 3; case 34: case 39: case 40: case 91: return 2; case 41: case 93: return 1; } return 0; } function alloc(value) { return line = column = 1, length = strlen(characters = value), position = 0, []; } function dealloc(value) { return characters = "", value; } function delimit(type) { return trim(slice(position - 1, delimiter(type === 91 ? type + 2 : type === 40 ? type + 1 : type))); } function whitespace(type) { while (character = peek()) if (character < 33) next(); else break; return token(type) > 2 || token(character) > 3 ? "" : " "; } function escaping(index, count) { while (--count && next()) if (character < 48 || character > 102 || character > 57 && character < 65 || character > 70 && character < 97) break; return slice(index, caret() + (count < 6 && peek() == 32 && next() == 32)); } function delimiter(type) { while (next()) switch (character) { case type: return position; case 34: case 39: if (type !== 34 && type !== 39) delimiter(character); break; case 40: if (type === 41) delimiter(type); break; case 92: next(); break; } return position; } function commenter(type, index) { while (next()) if (type + character === 47 + 10) break; else if (type + character === 42 + 42 && peek() === 47) break; return "/*" + slice(index, position - 1) + "*" + from(type === 47 ? type : next()); } function identifier(index) { while (!token(peek())) next(); return slice(index, position); } // node_modules/stylis/src/Parser.js function compile(value) { return dealloc(parse("", null, null, null, [""], value = alloc(value), 0, [0], value)); } function parse(value, root, parent, rule, rules, rulesets, pseudo, points, declarations) { var index = 0; var offset = 0; var length2 = pseudo; var atrule = 0; var property = 0; var previous = 0; var variable = 1; var scanning = 1; var ampersand = 1; var character2 = 0; var type = ""; var props = rules; var children = rulesets; var reference = rule; var characters2 = type; while (scanning) switch (previous = character2, character2 = next()) { case 40: if (previous != 108 && characters2.charCodeAt(length2 - 1) == 58) { if (indexof(characters2 += replace(delimit(character2), "&", "&\f"), "&\f") != -1) ampersand = -1; break; } case 34: case 39: case 91: characters2 += delimit(character2); break; case 9: case 10: case 13: case 32: characters2 += whitespace(previous); break; case 92: characters2 += escaping(caret() - 1, 7); continue; case 47: switch (peek()) { case 42: case 47: append(comment(commenter(next(), caret()), root, parent), declarations); break; default: characters2 += "/"; } break; case 123 * variable: points[index++] = strlen(characters2) * ampersand; case 125 * variable: case 59: case 0: switch (character2) { case 0: case 125: scanning = 0; case 59 + offset: if (property > 0 && strlen(characters2) - length2) append(property > 32 ? declaration(characters2 + ";", rule, parent, length2 - 1) : declaration(replace(characters2, " ", "") + ";", rule, parent, length2 - 2), declarations); break; case 59: characters2 += ";"; default: append(reference = ruleset(characters2, root, parent, index, offset, rules, points, type, props = [], children = [], length2), rulesets); if (character2 === 123) if (offset === 0) parse(characters2, root, reference, reference, props, rulesets, length2, points, children); else switch (atrule) { case 100: case 109: case 115: parse(value, reference, reference, rule && append(ruleset(value, reference, reference, 0, 0, rules, points, type, rules, props = [], length2), children), rules, children, length2, points, rule ? props : children); break; default: parse(characters2, reference, reference, reference, [""], children, 0, points, children); } } index = offset = property = 0, variable = ampersand = 1, type = characters2 = "", length2 = pseudo; break; case 58: length2 = 1 + strlen(characters2), property = previous; default: if (variable < 1) { if (character2 == 123) --variable; else if (character2 == 125 && variable++ == 0 && prev() == 125) continue; } switch (characters2 += from(character2), character2 * variable) { case 38: ampersand = offset > 0 ? 1 : (characters2 += "\f", -1); break; case 44: points[index++] = (strlen(characters2) - 1) * ampersand, ampersand = 1; break; case 64: if (peek() === 45) characters2 += delimit(next()); atrule = peek(), offset = length2 = strlen(type = characters2 += identifier(caret())), character2++; break; case 45: if (previous === 45 && strlen(characters2) == 2) variable = 0; } } return rulesets; } function ruleset(value, root, parent, index, offset, rules, points, type, props, children, length2) { var post = offset - 1; var rule = offset === 0 ? rules : [""]; var size = sizeof(rule); for (var i = 0, j = 0, k = 0; i < index; ++i) for (var x = 0, y = substr(value, post + 1, post = abs(j = points[i])), z = value; x < size; ++x) if (z = trim(j > 0 ? rule[x] + " " + y : replace(y, /&\f/g, rule[x]))) props[k++] = z; return node(value, root, parent, offset === 0 ? RULESET : type, props, children, length2); } function comment(value, root, parent) { return node(value, root, parent, COMMENT, from(char()), substr(value, 2, -2), 0); } function declaration(value, root, parent, length2) { return node(value, root, parent, DECLARATION, substr(value, 0, length2), substr(value, length2 + 1, -1), length2); } // node_modules/stylis/src/Serializer.js function serialize(children, callback) { var output = ""; var length2 = sizeof(children); for (var i = 0; i < length2; i++) output += callback(children[i], i, children, callback) || ""; return output; } function stringify(element, index, children, callback) { switch (element.type) { case IMPORT: case DECLARATION: return element.return = element.return || element.value; case COMMENT: return ""; case KEYFRAMES: return element.return = element.value + "{" + serialize(element.children, callback) + "}"; case RULESET: element.value = element.props.join(","); } return strlen(children = serialize(element.children, callback)) ? element.return = element.value + "{" + children + "}" : ""; } // src/store.js var makeRegex = (parts, opts) => RegExp(parts.map((p) => p.source).join(""), opts); var toggleRootRe = makeRegex([ /(?<name>[\w-]+)/, / */, /((?<initial>\d+)\/)?/, /(?<numActive>\d*)?/, /(\[(?<states>.+)\])?/, / */, /(at +(?<at>[\w-]+))?/, / */, /(?<modifiers>.*)/ ]); var toggleRootMachineRe = makeRegex([ /((?<name>[\w-]+) +)?/, /(machine\((?<machine>[\w-]+)(?<strict> *, *strict)?\))/, / */, /(at +(?<at>[\w-]+))?/, / */, /(?<modifiers>.*)/ ]); var toggleTriggerRe = makeRegex([ /(?<name>[\w-]+)/, / */, /(do[ (](?<transition>[\w-]+)\)?)?/, /(?<targetState>[\w-]*)/ ]); var counter = 0; var uid = () => counter++; var toggleRoots = {}; var toggleMachines = {}; function withNextSiblings(element) { const siblings = [element]; let next2 = element.nextElementSibling; while (next2 !== null) { siblings.push(next2); next2 = next2.nextElementSibling; } return siblings; } function createToggleRoots(ruleValue, selectors) { const regex = ruleValue.includes("machine(") ? toggleRootMachineRe : toggleRootRe; let { name, machine, strict, initial, numActive, states, at, modifiers } = regex.exec(ruleValue).groups; name = name || machine; if (name === void 0) return; let total; const machineDef = toggleMachines[machine]; if (machineDef !== void 0) { states = Object.keys(machineDef.states); total = states.length; } else if (states !== void 0) { states = states.split(/ +/); total = states.length; } else { total = parseInt(numActive || 1) + 1; states = [...Array(total).keys()].map(String); } modifiers = modifiers?.split(/ +/) || []; const group = modifiers.includes("group"); const isNarrow = modifiers.includes("self"); let activeIndex = states.indexOf(initial); if (activeIndex === -1) activeIndex = states.indexOf(at); if (activeIndex === -1) activeIndex = 0; let resetTo = 0; if (modifiers.includes("linear")) resetTo = total - 1; if (modifiers.includes("sticky")) resetTo = 1; const config = { name, resetTo, group, isNarrow, total, states, machine, activeIndex, strict: Boolean(strict) }; document.querySelectorAll(selectors).forEach((el) => { const id = `${name}-${uid()}`; const elements = isNarrow ? [el] : withNextSiblings(el); elements.forEach((el2) => el2.dataset.toggleRoot = id); toggleRoots[id] = { ...config }; }); } function createToggleTriggers(ruleValue, selectors) { const { name, targetState, transition } = toggleTriggerRe.exec(ruleValue).groups; if (name === void 0) return; const dispatchToggleEvent = ({ target }) => { target.dispatchEvent(new CustomEvent("_toggleTrigger", { bubbles: true, detail: { toggleRoot: name, targetState, transition } })); }; function handleKeyDown(event) { if (![" ", "Enter", "Spacebar"].includes(event.key)) return; event.preventDefault(); dispatchToggleEvent(event); } document.querySelectorAll(selectors).forEach((el) => { el.dataset.toggleTrigger = ""; el.addEventListener("click", dispatchToggleEvent); if (["button", "a", "input"].includes(el.nodeName.toLowerCase())) return; el.addEventListener("keydown", handleKeyDown); el.setAttribute("tabindex", 0); el.setAttribute("role", "button"); el.setAttribute("aria-pressed", false); }); } // src/walkers.js var pseudoElements = [ "after", "backdrop", "before", "cue", "cue-region", "first-letter", "first-line", "file-selector-button", "grammar-error", "marker", "placeholder", "selection", "spelling-error", "target-text" ].join("|"); var pseudoElementRe = RegExp(`::?(${pseudoElements})`); var togglePseudoClassRe = /:toggle\((?<name>[\w-]+) *(?<value>[\w-]*)\)/; var toggleVisibilityRe = /toggle *(?<name>[\w-]+)/; function toggleMachineWalker(element) { const name = (element.props || [])[0]; if (!(element.type === "@machine" && name)) return; const states = {}; element.children.filter((child) => child.type === "rule").forEach((rule) => { states[rule.value] = Object.fromEntries(rule.children.filter((child) => child.type === "decl").map((decl) => [decl.props, decl.children])); }); toggleMachines[name] = { name, states }; } function togglePseudoClassWalker(element) { if (element.type !== "rule") return; let didReplace = false; element.props = element.props.map((selector) => { if (!selector.match(togglePseudoClassRe)) return selector; const baseSelector = selector.slice(0, selector.search(togglePseudoClassRe)).replace(pseudoElementRe, ""); document.querySelectorAll(baseSelector).forEach((el) => el.dataset.toggle = ""); let replacement; const { name, value } = togglePseudoClassRe.exec(selector).groups; if (value) { replacement = `[data-toggle="${name} ${value}"]`; } else { replacement = `[data-toggle^="${name} "]:not([data-toggle="${name} 0"])`; } didReplace = true; return selector.replace(togglePseudoClassRe, replacement); }); return didReplace; } function toggleVisibilityWalker(element) { if (!(element.type === "decl" && element.props === "toggle-visibility")) return; const { name } = toggleVisibilityRe.exec(element.children).groups; if (name === void 0) return; document.querySelectorAll(element.parent.value).forEach((el) => { el.dataset.toggleVisibility = ""; }); } function toggleRootWalker(element) { if (!(element.type === "decl" && element.props === "toggle-root")) return; createToggleRoots(element.children, element.parent.value); } function toggleTriggerWalker(element) { if (!(element.type === "decl" && element.props === "toggle-trigger")) return; createToggleTriggers(element.children, element.parent.value); } function toggleWalker(element) { if (!(element.type === "decl" && element.props === "toggle")) return; const selectors = element.parent.value; const ruleValue = element.children; createToggleRoots(ruleValue, selectors); createToggleTriggers(ruleValue, selectors); } // src/css-toggles.js function renderToggleState(toggleRootId) { const toggleRoot = toggleRoots[toggleRootId]; const { activeIndex } = toggleRoot; document.querySelectorAll(` [data-toggle-root="${toggleRootId}"][data-toggle-visibility], [data-toggle-root="${toggleRootId}"] [data-toggle-visibility] `).forEach((el) => { const closestRoot = el.closest("[data-toggle-root]"); if (closestRoot?.dataset.toggleRoot !== toggleRootId) return; el.dataset.toggleVisibility = activeIndex > 0 ? "visible" : "hidden"; }); document.querySelectorAll(` [data-toggle-root="${toggleRootId}"][data-toggle], [data-toggle-root="${toggleRootId}"] [data-toggle] `).forEach((el) => { const closestRoot = el.closest("[data-toggle-root]"); if (closestRoot?.dataset.toggleRoot !== toggleRootId) return; el.dataset.toggle = `${toggleRoot.name} ${toggleRoot.states[activeIndex]}`; }); document.querySelectorAll(` [data-toggle-root="${toggleRootId}"][data-toggle-trigger][aria-pressed], [data-toggle-root="${toggleRootId}"] [data-toggle-trigger][aria-pressed] `).forEach((el) => { const closestRoot = el.closest("[data-toggle-root]"); if (closestRoot?.dataset.toggleRoot !== toggleRootId) return; el.setAttribute("aria-pressed", activeIndex > 0); }); } function initStylesheet(sheetSrc, url) { let didReplace = false; function walk(element) { if (element.type === "comm") return; toggleMachineWalker(element); didReplace |= togglePseudoClassWalker(element); toggleVisibilityWalker(element); toggleRootWalker(element); toggleTriggerWalker(element); toggleWalker(element); const size = (element.children || []).length; for (let i = 0; i < size; i++) walk(element.children[i]); } const cssAst = compile(sheetSrc); cssAst.forEach(walk); return didReplace ? serialize(cssAst, stringify) : ""; } function handleStyleTag(el) { const newSrc = initStylesheet(el.innerHTML); if (!newSrc) return; el.innerHTML = newSrc; } async function handleLinkedStylesheet(el) { if (el.rel !== "stylesheet") return; const srcUrl = new URL(el.href, document.baseURI); if (srcUrl.origin !== location.origin) return; const src = await fetch(srcUrl.toString()).then((r) => r.text()); const newSrc = initStylesheet(src, srcUrl.toString()); if (!newSrc) return; const blob = new Blob([newSrc], { type: "text/css" }); el.href = URL.createObjectURL(blob); } document.body.addEventListener("_toggleTrigger", (event) => { const { target } = event; let { toggleRoot: name, targetState, transition } = event.detail; const toggleRootElement = target.closest(`[data-toggle-root^="${name}-"]`); const id = toggleRootElement?.dataset?.toggleRoot || null; const toggleRoot = toggleRoots[id]; if (toggleRoot === void 0) return; const currentState = toggleRoot.states[toggleRoot.activeIndex]; if (toggleRoot.group) { for (const _id of Object.keys(toggleRoots)) { if (toggleRoots[_id].name === name) { toggleRoots[_id].activeIndex = 0; renderToggleState(_id); } } } const machine = toggleMachines[toggleRoot.machine]; if (machine !== void 0) { targetState = machine.states[currentState][transition]; if (targetState === void 0) return; } const nextIndex = toggleRoot.states.indexOf(targetState); if (nextIndex === -1) { if (toggleRoot.activeIndex === toggleRoot.total - 1) { toggleRoot.activeIndex = toggleRoot.resetTo; } else { toggleRoot.activeIndex++; } } else { toggleRoot.activeIndex = nextIndex; } renderToggleState(id); }); document.querySelectorAll("style").forEach(handleStyleTag); Promise.all([...document.querySelectorAll("link")].map(handleLinkedStylesheet)).then(() => { Object.keys(toggleRoots).forEach(renderToggleState); }); document.head.insertAdjacentHTML("beforeend", '<style>[data-toggle-visibility="hidden"] { display: none; }</style>');