UNPKG

@toolwind/anchors

Version:

Anchors for Tailwind CSS provides a simple API for working with CSS anchor positioning, enabling flexible, declarative positioning relative to custom anchors.

707 lines (698 loc) 17.9 kB
"use strict"; Object.defineProperty(exports, '__esModule', { value: true }); //#region node_modules/tailwindcss-v4/src/value-parser.ts function word(value) { return { kind: "word", value }; } function fun(value, nodes) { return { kind: "function", value, nodes }; } function separator(value) { return { kind: "separator", value }; } function toCss(ast) { let css = ""; for (const node of ast) switch (node.kind) { case "word": case "separator": { css += node.value; break; } case "function": css += node.value + "(" + toCss(node.nodes) + ")"; } return css; } const BACKSLASH$1 = 92; const CLOSE_PAREN$1 = 41; const COLON = 58; const COMMA = 44; const DOUBLE_QUOTE$1 = 34; const EQUALS = 61; const GREATER_THAN = 62; const LESS_THAN = 60; const NEWLINE = 10; const OPEN_PAREN$1 = 40; const SINGLE_QUOTE$1 = 39; const SLASH = 47; const SPACE = 32; const TAB = 9; function parse(input) { input = input.replaceAll("\r\n", "\n"); let ast = []; let stack = []; let parent = null; let buffer = ""; let peekChar; for (let i = 0; i < input.length; i++) { let currentChar = input.charCodeAt(i); switch (currentChar) { case BACKSLASH$1: { buffer += input[i] + input[i + 1]; i++; break; } case COLON: case COMMA: case EQUALS: case GREATER_THAN: case LESS_THAN: case NEWLINE: case SLASH: case SPACE: case TAB: { if (buffer.length > 0) { let node$1 = word(buffer); if (parent) parent.nodes.push(node$1); else ast.push(node$1); buffer = ""; } let start = i; let end = i + 1; for (; end < input.length; end++) { peekChar = input.charCodeAt(end); if (peekChar !== COLON && peekChar !== COMMA && peekChar !== EQUALS && peekChar !== GREATER_THAN && peekChar !== LESS_THAN && peekChar !== NEWLINE && peekChar !== SLASH && peekChar !== SPACE && peekChar !== TAB) break; } i = end - 1; let node = separator(input.slice(start, end)); if (parent) parent.nodes.push(node); else ast.push(node); break; } case SINGLE_QUOTE$1: case DOUBLE_QUOTE$1: { let start = i; for (let j = i + 1; j < input.length; j++) { peekChar = input.charCodeAt(j); if (peekChar === BACKSLASH$1) j += 1; else if (peekChar === currentChar) { i = j; break; } } buffer += input.slice(start, i + 1); break; } case OPEN_PAREN$1: { let node = fun(buffer, []); buffer = ""; if (parent) parent.nodes.push(node); else ast.push(node); stack.push(node); parent = node; break; } case CLOSE_PAREN$1: { let tail = stack.pop(); if (buffer.length > 0) { let node = word(buffer); tail.nodes.push(node); buffer = ""; } if (stack.length > 0) parent = stack[stack.length - 1]; else parent = null; break; } default: buffer += String.fromCharCode(currentChar); } } if (buffer.length > 0) ast.push(word(buffer)); return ast; } //#endregion //#region node_modules/tailwindcss-v4/src/utils/math-operators.ts const MATH_FUNCTIONS = [ "calc", "min", "max", "clamp", "mod", "rem", "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "sqrt", "hypot", "log", "exp", "round" ]; const KNOWN_DASHED_FUNCTIONS = ["anchor-size"]; const DASHED_FUNCTIONS_REGEX = new RegExp(`(${KNOWN_DASHED_FUNCTIONS.join("|")})\\(`, "g"); function addWhitespaceAroundMathOperators(input) { if (!MATH_FUNCTIONS.some((fn) => input.includes(fn))) return input; let hasKnownFunctions = false; if (KNOWN_DASHED_FUNCTIONS.some((fn) => input.includes(fn))) { DASHED_FUNCTIONS_REGEX.lastIndex = 0; input = input.replace(DASHED_FUNCTIONS_REGEX, (_, fn) => { hasKnownFunctions = true; return `$${KNOWN_DASHED_FUNCTIONS.indexOf(fn)}$(`; }); } let result = ""; let formattable = []; for (let i = 0; i < input.length; i++) { let char = input[i]; if (char === "(") { result += char; let start = i; for (let j = i - 1; j >= 0; j--) { let inner = input.charCodeAt(j); if (inner >= 48 && inner <= 57) start = j; else if (inner >= 97 && inner <= 122) start = j; else break; } let fn = input.slice(start, i); if (MATH_FUNCTIONS.includes(fn)) { formattable.unshift(true); continue; } else if (formattable[0] && fn === "") { formattable.unshift(true); continue; } formattable.unshift(false); continue; } else if (char === ")") { result += char; formattable.shift(); } else if (char === "," && formattable[0]) { result += `, `; continue; } else if (char === " " && formattable[0] && result[result.length - 1] === " ") continue; else if ((char === "+" || char === "*" || char === "/" || char === "-") && formattable[0]) { let trimmed = result.trimEnd(); let prev = trimmed[trimmed.length - 1]; if (prev === "+" || prev === "*" || prev === "/" || prev === "-") { result += char; continue; } else if (prev === "(" || prev === ",") { result += char; continue; } else if (input[i - 1] === " ") result += `${char} `; else result += ` ${char} `; } else if (formattable[0] && input.startsWith("to-zero", i)) { let start = i; i += 7; result += input.slice(start, i + 1); } else result += char; } if (hasKnownFunctions) return result.replace(/\$(\d+)\$/g, (fn, idx) => KNOWN_DASHED_FUNCTIONS[idx] ?? fn); return result; } //#endregion //#region node_modules/tailwindcss-v4/src/utils/decode-arbitrary-value.ts function decodeArbitraryValue(input) { if (input.indexOf("(") === -1) return convertUnderscoresToWhitespace(input); let ast = parse(input); recursivelyDecodeArbitraryValues(ast); input = toCss(ast); input = addWhitespaceAroundMathOperators(input); return input; } /** * Convert `_` to ` `, except for escaped underscores `\_` they should be * converted to `_` instead. */ function convertUnderscoresToWhitespace(input, skipUnderscoreToSpace = false) { let output = ""; for (let i = 0; i < input.length; i++) { let char = input[i]; if (char === "\\" && input[i + 1] === "_") { output += "_"; i += 1; } else if (char === "_" && !skipUnderscoreToSpace) output += " "; else output += char; } return output; } function recursivelyDecodeArbitraryValues(ast) { for (let node of ast) switch (node.kind) { case "function": { if (node.value === "url" || node.value.endsWith("_url")) { node.value = convertUnderscoresToWhitespace(node.value); break; } if (node.value === "var" || node.value.endsWith("_var") || node.value === "theme" || node.value.endsWith("_theme")) { node.value = convertUnderscoresToWhitespace(node.value); for (let i = 0; i < node.nodes.length; i++) { if (i == 0 && node.nodes[i].kind === "word") { node.nodes[i].value = convertUnderscoresToWhitespace(node.nodes[i].value, true); continue; } recursivelyDecodeArbitraryValues([node.nodes[i]]); } break; } node.value = convertUnderscoresToWhitespace(node.value); recursivelyDecodeArbitraryValues(node.nodes); break; } case "separator": case "word": { node.value = convertUnderscoresToWhitespace(node.value); break; } default: never(node); } } function never(value) { throw new Error(`Unexpected value: ${value}`); } //#endregion //#region node_modules/tailwindcss-v4/src/utils/is-valid-arbitrary.ts const BACKSLASH = 92; const OPEN_CURLY = 123; const CLOSE_CURLY = 125; const OPEN_PAREN = 40; const CLOSE_PAREN = 41; const OPEN_BRACKET = 91; const CLOSE_BRACKET = 93; const DOUBLE_QUOTE = 34; const SINGLE_QUOTE = 39; const SEMICOLON = 59; const closingBracketStack$1 = new Uint8Array(256); /** * Determine if a given string might be a valid arbitrary value. * * Unbalanced parens, brackets, and braces are not allowed. Additionally, a * top-level `;` is not allowed. * * This function is very similar to `segment` but `segment` cannot be used * because we'd need to split on a bracket stack character. */ function isValidArbitrary(input) { let stackPos = 0; let len = input.length; for (let idx = 0; idx < len; idx++) { let char = input.charCodeAt(idx); switch (char) { case BACKSLASH: idx += 1; break; case SINGLE_QUOTE: case DOUBLE_QUOTE: while (++idx < len) { let nextChar = input.charCodeAt(idx); if (nextChar === BACKSLASH) { idx += 1; continue; } if (nextChar === char) break; } break; case OPEN_PAREN: closingBracketStack$1[stackPos] = CLOSE_PAREN; stackPos++; break; case OPEN_BRACKET: closingBracketStack$1[stackPos] = CLOSE_BRACKET; stackPos++; break; case OPEN_CURLY: break; case CLOSE_BRACKET: case CLOSE_CURLY: case CLOSE_PAREN: if (stackPos === 0) return false; if (stackPos > 0 && char === closingBracketStack$1[stackPos - 1]) stackPos--; break; case SEMICOLON: if (stackPos === 0) return false; break; } } return true; } //#endregion //#region node_modules/tailwindcss-v4/src/utils/segment.ts const closingBracketStack = new Uint8Array(256); //#endregion //#region node_modules/tailwindcss-v4/src/candidate.ts function parseModifier(modifier) { if (modifier[0] === "[" && modifier[modifier.length - 1] === "]") { let arbitraryValue = decodeArbitraryValue(modifier.slice(1, -1)); if (!isValidArbitrary(arbitraryValue)) return null; if (arbitraryValue.length === 0 || arbitraryValue.trim().length === 0) return null; return { kind: "arbitrary", value: arbitraryValue }; } if (modifier[0] === "(" && modifier[modifier.length - 1] === ")") { let arbitraryValue = decodeArbitraryValue(modifier.slice(1, -1)); if (!isValidArbitrary(arbitraryValue)) return null; if (arbitraryValue.length === 0 || arbitraryValue.trim().length === 0) return null; if (arbitraryValue[0] !== "-" && arbitraryValue[1] !== "-") return null; return { kind: "arbitrary", value: `var(${arbitraryValue})` }; } return { kind: "named", value: modifier }; } //#endregion //#region utils.ts const prefixAnchorName = (name) => `--tw-anchor_${name}`; const reservedNames = [ "inherit", "initial", "revert", "revert-layer", "unset", "none" ]; const normalizeAnchorNameCore = (modifier) => { modifier = modifier?.trim(); if (!modifier) return null; /** current bug: v4 parses variable shorthand syntax in modifiers as * standard arbitrary values and replaces underscores with spaces, * so this undoes that as a stop-gap-solution */ modifier = modifier.replace(/ /g, "_"); if (reservedNames.some((name) => modifier === name) || modifier.startsWith("--") || modifier.startsWith("var(")) return modifier; return prefixAnchorName(modifier); }; const normalizeAnchorName = (modifier, isV4) => { if (!modifier) return null; if (isV4) return normalizeAnchorNameCore(modifier); if (modifier.startsWith("(") && modifier.endsWith(")")) throw new Error(`This variable shorthand syntax is only supported in Tailwind CSS v4.0 and above: ${modifier}. In v3.x, you must use [${modifier.slice(1, -1)}].`); if (modifier.startsWith("[--")) return `var(${modifier.slice(1, -1)})`; return normalizeAnchorNameCore(parseModifier(modifier)?.value); }; const encoding = { encode: (str) => { let encoded = ""; for (const char of str) encoded += char.charCodeAt(0).toString(36); return encoded; }, decode: (encodedStr) => { const decodedChars = []; let charCode = ""; for (const char of encodedStr) { charCode += char; const code = Number.parseInt(charCode, 36); if (!isNaN(code) && code >= 32 && code <= 126) { decodedChars.push(String.fromCharCode(code)); charCode = ""; } } return decodedChars.join(""); } }; const generateRandomString = (length = 10) => { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; for (let i = 0; i < length; i++) { const randomIndex = Math.floor(Math.random() * charset.length); result += charset[randomIndex]; } return result; }; const createToggle = (property, on, off) => { const varName = `--toolwind-toggle-${generateRandomString()}`; return [{ [property]: `var(${varName}, ${off})` }, { on: { [varName]: on }, off: { [varName]: off } }]; }; const createToggles = (togglesData) => { return togglesData.reduce((acc, [property, on, off]) => { const [cssStyles, toggle] = createToggle(property, on, off); return [ { ...acc[0], ...cssStyles }, { ...acc[1], [property]: toggle }, { ...acc[2], on: { ...acc[2].on, ...toggle.on }, off: { ...acc[2].off, ...toggle.off } } ]; }, [ {}, {}, { on: {}, off: {} } ]); }; const positionAreaValues = Object.fromEntries([ "top center", "top span-left", "top span-right", "top", "left center", "left span-top", "left span-bottom", "left", "right center", "right span-top", "right span-bottom", "right", "bottom center", "bottom span-left", "bottom span-right", "bottom", "top left", "top right", "bottom left", "bottom right" ].map((value) => [value.replace(/ /g, "-"), value])); //#endregion //#region index.ts const generateViewTransitionId = (str) => `--tw-anchor-view-transition-${encoding.encode(str)}`; const anchors = (api) => { const { addBase, addUtilities, matchUtilities, theme } = api; const [popoverStyles, _, popoverToggles] = createToggles([ [ "inset", "auto", "0px" ], [ "background-color", "transparent", "canvas" ], [ "color", "inherit", "canvastext" ], [ "margin", "0px", "auto" ] ]); addBase({ "[popover]": popoverStyles }); const isV4 = !("postcss" in api); matchUtilities({ anchor: (_$1, { modifier }) => { const styles = {}; if (modifier) { const anchorName = normalizeAnchorName(modifier, isV4); if (anchorName) styles["anchor-name"] = anchorName; } return styles; } }, { values: { DEFAULT: "" }, modifiers: "any" }); matchUtilities({ "anchor-scope": (_$1, { modifier }) => { const styles = {}; if (modifier) { const anchorName = normalizeAnchorName(modifier, isV4); if (anchorName) styles["anchor-scope"] = anchorName; } return styles; } }, { values: { DEFAULT: "" }, modifiers: "any" }); matchUtilities({ anchored: (value, { modifier }) => { if (!value && !modifier) return {}; const viewTransitionName = modifier && generateViewTransitionId(modifier); const anchorName = modifier && normalizeAnchorName(modifier, isV4); return { ...value && { "position-area": value }, ...anchorName && { "position-anchor": anchorName, "&:where(&)": { position: "absolute", ...viewTransitionName && { "view-transition-name": viewTransitionName }, ...popoverToggles.on } } }; } }, { values: { DEFAULT: "", ...positionAreaValues }, modifiers: "any" }); [ ["top", theme("inset")], ["right", theme("inset")], ["bottom", theme("inset")], ["left", theme("inset")], ["inset", theme("inset")] ].forEach(([property, themeValues]) => { [ "top", "right", "bottom", "left", "start", "end", "self-start", "self-end", "center" ].forEach((anchorSide) => { matchUtilities({ [`${property}-anchor-${anchorSide}`]: (offset, { modifier }) => { const anchorRef = modifier ? `${normalizeAnchorName(modifier, isV4)} ` : ""; const anchorFnExpr = `anchor(${anchorRef}${anchorSide})`; const value = offset ? `calc(${anchorFnExpr} + ${offset})` : anchorFnExpr; return { [property]: value, ...popoverToggles.on }; } }, { values: { DEFAULT: "", ...themeValues }, supportsNegativeValues: true, modifiers: "any" }); }); }); [ [ "w", "width", theme("width") ], [ "h", "height", theme("height") ], [ "min-w", "min-width", theme("minWidth") ], [ "min-h", "min-height", theme("minHeight") ], [ "max-w", "max-width", theme("maxWidth") ], [ "max-h", "max-height", theme("maxHeight") ] ].forEach(([propertyAbbr, property, themeValues]) => { [ "", "width", "height", "block", "inline", "self-block", "self-inline" ].forEach((anchorSize) => { const anchorSizeUtilitySuffix = anchorSize ? `-${anchorSize}` : anchorSize; matchUtilities({ [`${propertyAbbr}-anchor${anchorSizeUtilitySuffix}`]: (offset, { modifier }) => { const anchorRef = modifier ? `${normalizeAnchorName(modifier, isV4)} ` : ""; const anchorFnExpr = `anchor-size(${anchorRef}${anchorSize})`; const value = offset ? `calc(${anchorFnExpr} + ${offset})` : anchorFnExpr; return { [property]: value }; } }, { values: { DEFAULT: "", ...themeValues }, supportsNegativeValues: true, modifiers: "any" }); }); }); [ ["justify-self", "justify-self"], ["self", "align-self"], ["justify-items", "justify-items"], ["items", "align-items"], ["place-items", "place-items"], ["place-self", "place-self"] ].forEach(([propertyAbbr, property]) => { addUtilities({ [`.${propertyAbbr}-anchor`]: { [property]: "anchor-center", ...popoverToggles.on } }); }); matchUtilities({ "anchored-visible": (value) => ({ "position-visibility": value, ...popoverToggles.on }) }, { values: { always: "always", anchor: "anchors-visible", "no-overflow": "no-overflow" } }); matchUtilities({ "try-order": (value) => ({ "position-try-order": value, ...popoverToggles.on }) }, { values: { normal: "normal", w: "most-width", h: "most-height" } }); matchUtilities({ "try": (value) => ({ "position-try-fallbacks": value, ...popoverToggles.on }) }, { values: { none: "none", "flip-all": "flip-block, flip-inline, flip-block flip-inline", "flip-x": "flip-inline", "flip-y": "flip-block", "flip-s": "flip-start", ...positionAreaValues } }); }; var anchors_default = anchors; //#endregion exports.default = anchors_default exports.encoding = encoding