@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
JavaScript
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
;