@tenphi/tasty 0.16.1 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/parser/classify.js +3 -3
- package/dist/parser/classify.js.map +1 -1
- package/dist/parser/const.js +17 -1
- package/dist/parser/const.js.map +1 -1
- package/dist/styles/preset.js +2 -2
- package/dist/styles/preset.js.map +1 -1
- package/dist/utils/styles.d.ts +0 -1
- package/dist/utils/styles.js +0 -1
- package/dist/utils/styles.js.map +1 -1
- package/docs/dsl.md +0 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -301,7 +301,7 @@ Every rule is guarded by the negation of higher-priority rules. No two rules can
|
|
|
301
301
|
|
|
302
302
|
By absorbing selector complexity, Tasty makes advanced CSS patterns practical again — nested container queries, multi-condition `@supports` gates, and combined root-state/media branches. You stay in pure CSS instead of relying on JavaScript workarounds, so the browser can optimize layout, painting, and transitions natively. Tasty keeps the solution in CSS while removing much of the selector bookkeeping that is hard to maintain by hand.
|
|
303
303
|
|
|
304
|
-
[Try it in the
|
|
304
|
+
[Try it in the playground →](https://tasty.style/playground)
|
|
305
305
|
|
|
306
306
|
## Capabilities
|
|
307
307
|
|
package/dist/parser/classify.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { COLOR_FUNCS, RE_HEX, RE_NUMBER, RE_RAW_UNIT, RE_UNIT_NUM, VALUE_KEYWORDS } from "./const.js";
|
|
1
|
+
import { COLOR_FUNCS, RE_HEX, RE_NUMBER, RE_RAW_UNIT, RE_UNIT_NUM, VALUE_KEYWORDS, canonicalFuncName } from "./const.js";
|
|
2
2
|
import { getColorSpaceFunc, getColorSpaceSuffix } from "../utils/color-space.js";
|
|
3
3
|
import { Bucket } from "./types.js";
|
|
4
4
|
import { StyleParser } from "./parser.js";
|
|
@@ -216,7 +216,7 @@ function classify(raw, opts, recurse) {
|
|
|
216
216
|
const argProcessed = recurse(inner).output.replace(/,\s+/g, ",");
|
|
217
217
|
return {
|
|
218
218
|
bucket: Bucket.Color,
|
|
219
|
-
processed: `${fname}(${argProcessed})`
|
|
219
|
+
processed: `${canonicalFuncName(fname)}(${argProcessed})`
|
|
220
220
|
};
|
|
221
221
|
}
|
|
222
222
|
if (opts.funcs && fname in opts.funcs) {
|
|
@@ -229,7 +229,7 @@ function classify(raw, opts, recurse) {
|
|
|
229
229
|
const argProcessed = recurse(inner).output;
|
|
230
230
|
return {
|
|
231
231
|
bucket: Bucket.Value,
|
|
232
|
-
processed: `${fname}(${argProcessed})`
|
|
232
|
+
processed: `${canonicalFuncName(fname)}(${argProcessed})`
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
235
|
if (token.startsWith("(") && token.endsWith(")")) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classify.js","names":[],"sources":["../../src/parser/classify.ts"],"sourcesContent":["import { getColorSpaceFunc, getColorSpaceSuffix } from '../utils/color-space';\nimport { getGlobalPredefinedTokens } from '../utils/styles';\n\nimport {\n COLOR_FUNCS,\n RE_HEX,\n RE_NUMBER,\n RE_RAW_UNIT,\n RE_UNIT_NUM,\n VALUE_KEYWORDS,\n} from './const';\nimport { StyleParser } from './parser';\nimport type { ParserOptions, ProcessedStyle } from './types';\nimport { Bucket } from './types';\n\n/**\n * Re-parses a value through the parser until it stabilizes (no changes)\n * or max iterations reached. This allows units to reference other units.\n * Example: { x: '8px', y: '2x' } -> '1y' resolves to '16px'\n */\nfunction resolveUntilStable(\n value: string,\n opts: ParserOptions,\n recurse: (str: string) => ProcessedStyle,\n maxIterations = 10,\n): string {\n let current = value;\n for (let i = 0; i < maxIterations; i++) {\n // Check if the current value contains a custom unit that needs resolution\n const unitMatch = current.match(RE_UNIT_NUM);\n if (!unitMatch) break; // Not a unit number, no resolution needed\n\n const unitName = unitMatch[1];\n // Only recurse if the unit is a custom unit we know about\n // Any unit not in opts.units is assumed to be a native CSS unit\n if (!opts.units || !(unitName in opts.units)) break;\n\n const result = recurse(current);\n if (result.output === current) break; // Stable\n current = result.output;\n }\n return current;\n}\n\nexport function classify(\n raw: string,\n opts: ParserOptions,\n recurse: (str: string) => ProcessedStyle,\n): { bucket: Bucket; processed: string } {\n const token = raw.trim();\n if (!token) return { bucket: Bucket.Mod, processed: '' };\n\n // Early-out: if the token contains unmatched parentheses treat it as invalid\n // and skip it. This avoids cases like `drop-shadow(` that are missing a\n // closing parenthesis (e.g., a user-typo in CSS). We count paren depth while\n // ignoring everything inside string literals to avoid false positives.\n {\n let depth = 0;\n let inQuote: string | 0 = 0;\n for (let i = 0; i < token.length; i++) {\n const ch = token[i];\n\n // track quote context so parentheses inside quotes are ignored\n if (inQuote) {\n if (ch === inQuote && token[i - 1] !== '\\\\') inQuote = 0;\n continue;\n }\n if (ch === '\"' || ch === \"'\") {\n inQuote = ch;\n continue;\n }\n\n if (ch === '(') depth++;\n else if (ch === ')') depth = Math.max(0, depth - 1);\n }\n\n if (depth !== 0) {\n // Unbalanced parens → treat as invalid token (skipped).\n console.warn(\n 'tasty: skipped invalid function token with unmatched parentheses:',\n token,\n );\n return { bucket: Bucket.Mod, processed: '' };\n }\n }\n\n // Quoted string literals should be treated as value tokens (e.g., \"\" for content)\n if (\n (token.startsWith('\"') && token.endsWith('\"')) ||\n (token.startsWith(\"'\") && token.endsWith(\"'\"))\n ) {\n return { bucket: Bucket.Value, processed: token };\n }\n\n // 0. Double prefix for literal CSS property names ($$name -> --name, ##name -> --name-color)\n // Used in transitions and animations to reference the property name itself, not its value\n if (token.startsWith('$$')) {\n const name = token.slice(2);\n if (/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n return { bucket: Bucket.Value, processed: `--${name}` };\n }\n }\n if (token.startsWith('##')) {\n const name = token.slice(2);\n if (/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n return { bucket: Bucket.Value, processed: `--${name}-color` };\n }\n }\n\n // 0b. Special handling for #current (reserved keyword, cannot be overridden by predefined tokens)\n // #current maps to CSS currentcolor keyword\n if (token === '#current') {\n return { bucket: Bucket.Color, processed: 'currentcolor' };\n }\n\n // #current with opacity: #current.5 or #current.$opacity\n // Uses color-mix since currentcolor doesn't support rgb() alpha syntax\n const currentAlphaMatch = token.match(\n /^#current\\.(\\$[a-z_][a-z0-9-_]*|[0-9]+)$/i,\n );\n if (currentAlphaMatch) {\n const rawAlpha = currentAlphaMatch[1];\n let percentage: string;\n if (rawAlpha.startsWith('$')) {\n // Custom property: $disabled -> calc(var(--disabled) * 100%)\n const propName = rawAlpha.slice(1);\n percentage = `calc(var(--${propName}) * 100%)`;\n } else if (rawAlpha === '0') {\n percentage = '0%';\n } else {\n // Convert .5 -> 50%, .05 -> 5%\n percentage = `${parseFloat('.' + rawAlpha) * 100}%`;\n }\n return {\n bucket: Bucket.Color,\n processed: `color-mix(in oklab, currentcolor ${percentage}, transparent)`,\n };\n }\n\n // 0c. Check for predefined tokens (configured via configure({ replaceTokens: {...} }))\n // Must happen before default $ and # handling to allow overriding\n if (token[0] === '$' || token[0] === '#') {\n const predefinedTokens = getGlobalPredefinedTokens();\n if (predefinedTokens) {\n // Exact match\n if (token in predefinedTokens) {\n const tokenValue = predefinedTokens[token];\n // Lowercase the token value to match parser behavior (parser lowercases input)\n return classify(tokenValue.toLowerCase(), opts, recurse);\n }\n // Check for color token with alpha suffix: #token.alpha or #token.$prop\n if (token[0] === '#') {\n const alphaMatch = token.match(\n /^(#[a-z0-9-]+)\\.(\\$[a-z_][a-z0-9-_]*|[0-9]+)$/i,\n );\n if (alphaMatch) {\n const [, baseToken, rawAlpha] = alphaMatch;\n if (baseToken in predefinedTokens) {\n const resolvedValue = predefinedTokens[baseToken];\n\n // If resolved value starts with # (color token), use standard alpha syntax\n if (resolvedValue.startsWith('#')) {\n // Lowercase to match parser behavior\n return classify(\n `${resolvedValue.toLowerCase()}.${rawAlpha}`,\n opts,\n recurse,\n );\n }\n\n // For color functions like rgb(), rgba(), hsl(), hwb(), etc., inject alpha\n // Includes all standard CSS color functions plus okhsl (plugin)\n const funcMatch = resolvedValue.match(\n /^(rgba?|hsla?|hwb|oklab|oklch|lab|lch|color|okhsl|device-cmyk|gray|color-mix|color-contrast)\\((.+)\\)$/i,\n );\n if (funcMatch) {\n const [, funcName, args] = funcMatch;\n // Handle $prop syntax for custom property alpha\n let alpha: string;\n if (rawAlpha.startsWith('$')) {\n const propName = rawAlpha.slice(1);\n alpha = `var(--${propName})`;\n } else {\n alpha = rawAlpha === '0' ? '0' : `.${rawAlpha}`;\n }\n // Normalize function name: rgba->rgb, hsla->hsl (modern syntax doesn't need 'a' suffix)\n const normalizedFunc = funcName.replace(/a$/i, '').toLowerCase();\n // Normalize to modern syntax: replace top-level commas with spaces\n // Preserves commas inside nested functions like min(), max(), clamp()\n const normalizeArgs = (a: string) => {\n let result = '';\n let depth = 0;\n for (let i = 0; i < a.length; i++) {\n const c = a[i];\n if (c === '(') {\n depth++;\n result += c;\n } else if (c === ')') {\n depth = Math.max(0, depth - 1);\n result += c;\n } else if (c === ',' && depth === 0) {\n // Skip comma and any following whitespace at top level\n while (i + 1 < a.length && /\\s/.test(a[i + 1])) i++;\n result += ' ';\n } else {\n result += c;\n }\n }\n return result;\n };\n // Helper: find last top-level occurrence of a character (ignores parentheses)\n const findLastTopLevel = (str: string, ch: string) => {\n let depth = 0;\n for (let i = str.length - 1; i >= 0; i--) {\n const c = str[i];\n if (c === ')') depth++;\n else if (c === '(') depth = Math.max(0, depth - 1);\n else if (c === ch && depth === 0) return i;\n }\n return -1;\n };\n\n // Check if already has alpha:\n // - Modern syntax: has `/` separator at top level (works with dynamic alpha like var()/calc())\n // - Legacy syntax: function ends with 'a' (rgba, hsla) and has exactly 4 top-level comma-separated values\n const slashIdx = findLastTopLevel(args, '/');\n const hasModernAlpha = slashIdx !== -1;\n\n // Count top-level commas to avoid commas inside nested functions\n let topLevelCommaCount = 0;\n let lastTopLevelComma = -1;\n {\n let depth = 0;\n for (let i = 0; i < args.length; i++) {\n const c = args[i];\n if (c === '(') depth++;\n else if (c === ')') depth = Math.max(0, depth - 1);\n else if (c === ',' && depth === 0) {\n topLevelCommaCount++;\n lastTopLevelComma = i;\n }\n }\n }\n\n const hasLegacyAlpha =\n !hasModernAlpha &&\n /a$/i.test(funcName) &&\n topLevelCommaCount === 3;\n\n const colorArgs =\n hasModernAlpha || hasLegacyAlpha\n ? normalizeArgs(\n hasModernAlpha\n ? args.slice(0, slashIdx).trim()\n : args.slice(0, lastTopLevelComma).trim(),\n )\n : normalizeArgs(args);\n\n const constructed = `${normalizedFunc}(${colorArgs} / ${alpha})`;\n\n // Custom functions (not native CSS) must be re-classified\n // so the function handler can convert them to valid CSS\n if (\n !COLOR_FUNCS.has(normalizedFunc) &&\n opts.funcs &&\n normalizedFunc in opts.funcs\n ) {\n return classify(constructed, opts, recurse);\n }\n\n return { bucket: Bucket.Color, processed: constructed };\n }\n\n // Fallback: try appending .alpha (may not work for all cases)\n return classify(`${resolvedValue}.${rawAlpha}`, opts, recurse);\n }\n }\n }\n }\n }\n\n // 0. Direct var(--*-color) token\n const varColorMatch = token.match(/^var\\(--([a-z0-9-]+)-color\\)$/);\n if (varColorMatch) {\n return { bucket: Bucket.Color, processed: token };\n }\n\n // 1. URL\n if (token.startsWith('url(')) {\n return { bucket: Bucket.Value, processed: token };\n }\n\n // 2. Custom property\n if (token[0] === '$') {\n const identMatch = token.match(/^\\$([a-z_][a-z0-9-_]*)$/);\n if (identMatch) {\n const name = identMatch[1];\n const processed = `var(--${name})`;\n const bucketType = name.endsWith('-color') ? Bucket.Color : Bucket.Value;\n return {\n bucket: bucketType,\n processed,\n };\n }\n // invalid custom property → modifier\n }\n\n // 3. Hash colors (with optional alpha suffix e.g., #purple.5 or #purple.$disabled)\n if (token[0] === '#' && token.length > 1) {\n // alpha form: #name.alpha or #name.$prop\n const alphaMatch = token.match(\n /^#([a-z0-9-]+)\\.(\\$[a-z_][a-z0-9-_]*|[0-9]+)$/i,\n );\n if (alphaMatch) {\n const [, base, rawAlpha] = alphaMatch;\n let alpha: string;\n if (rawAlpha.startsWith('$')) {\n // Custom property: $disabled -> var(--disabled)\n const propName = rawAlpha.slice(1);\n alpha = `var(--${propName})`;\n } else if (rawAlpha === '0') {\n alpha = '0';\n } else {\n alpha = `.${rawAlpha}`;\n }\n return {\n bucket: Bucket.Color,\n processed: `${getColorSpaceFunc()}(var(--${base}-color-${getColorSpaceSuffix()}) / ${alpha})`,\n };\n }\n\n // hyphenated names like #dark-05 should keep full name\n\n const name = token.slice(1);\n // valid hex → treat as hex literal with fallback\n if (RE_HEX.test(name)) {\n return {\n bucket: Bucket.Color,\n processed: `var(--${name}-color, #${name})`,\n };\n }\n // simple color name token → css variable lookup with rgb fallback\n return { bucket: Bucket.Color, processed: `var(--${name}-color)` };\n }\n\n // 4 & 5. Functions\n const openIdx = token.indexOf('(');\n if (openIdx > 0 && token.endsWith(')')) {\n const fname = token.slice(0, openIdx);\n const inner = token.slice(openIdx + 1, -1); // without ()\n\n if (COLOR_FUNCS.has(fname)) {\n // Process inner to expand nested colors or units.\n const argProcessed = recurse(inner).output.replace(/,\\s+/g, ','); // color funcs expect no spaces after commas\n return { bucket: Bucket.Color, processed: `${fname}(${argProcessed})` };\n }\n\n // user function (provided via opts)\n if (opts.funcs && fname in opts.funcs) {\n // split by top-level commas within inner\n const tmp = new StyleParser(opts).process(inner); // fresh parser w/ same opts but no cache share issues\n const funcResult = opts.funcs[fname](tmp.groups);\n // Re-classify the result to determine proper bucket (e.g., if it returns a color)\n // Pass funcs: undefined to prevent infinite recursion if result matches a function pattern\n return classify(funcResult, { ...opts, funcs: undefined }, recurse);\n }\n\n // generic: process inner and rebuild\n const argProcessed = recurse(inner).output;\n return { bucket: Bucket.Value, processed: `${fname}(${argProcessed})` };\n }\n\n // 6. Color fallback syntax: (#name, fallback)\n if (token.startsWith('(') && token.endsWith(')')) {\n const inner = token.slice(1, -1);\n const colorMatch = inner.match(/^#([a-z0-9-]+)\\s*,\\s*(.*)$/i);\n if (colorMatch) {\n const [, name, fallback] = colorMatch;\n const processedFallback = recurse(fallback).output;\n return {\n bucket: Bucket.Color,\n processed: `var(--${name}-color, ${processedFallback})`,\n };\n }\n }\n\n // 7. Custom property with fallback syntax: ($prop, fallback)\n if (token.startsWith('(') && token.endsWith(')')) {\n const inner = token.slice(1, -1);\n const match = inner.match(/^\\$([a-z_][a-z0-9-_]*)\\s*,\\s*(.*)$/);\n if (match) {\n const [, name, fallback] = match;\n const processedFallback = recurse(fallback).output;\n const bucketType = name.endsWith('-color') ? Bucket.Color : Bucket.Value;\n return {\n bucket: bucketType,\n processed: `var(--${name}, ${processedFallback})`,\n };\n }\n }\n\n // 8. Auto-calc group\n if (token[0] === '(' && token[token.length - 1] === ')') {\n const inner = token.slice(1, -1);\n const innerProcessed = recurse(inner).output;\n return { bucket: Bucket.Value, processed: `calc(${innerProcessed})` };\n }\n\n // 9. Unit number\n const um = token.match(RE_UNIT_NUM);\n if (um) {\n const unit = um[1];\n const numericPart = parseFloat(token.slice(0, -unit.length));\n const handler = opts.units && opts.units[unit];\n if (handler) {\n if (typeof handler === 'string') {\n // Check if this is a raw CSS unit (e.g., \"8px\", \"1rem\")\n const rawMatch = handler.match(RE_RAW_UNIT);\n if (rawMatch) {\n // Raw unit: calculate directly instead of using calc()\n const [, baseNum, cssUnit] = rawMatch;\n const result = numericPart * parseFloat(baseNum);\n const processed = `${result}${cssUnit}`;\n // Re-parse to resolve any nested units (e.g., units referencing other units)\n const resolved = resolveUntilStable(processed, opts, recurse);\n return { bucket: Bucket.Value, processed: resolved };\n }\n\n // Non-raw handler (e.g., \"var(--gap)\", \"calc(...)\"): use calc() wrapping\n const base = handler;\n if (numericPart === 1) {\n return { bucket: Bucket.Value, processed: base };\n }\n return {\n bucket: Bucket.Value,\n processed: `calc(${numericPart} * ${base})`,\n };\n } else {\n // Function units return complete CSS expressions, no wrapping needed\n const inner = handler(numericPart);\n return {\n bucket: Bucket.Value,\n processed: inner,\n };\n }\n }\n }\n\n // 9b. Unknown numeric+unit → treat as literal value (e.g., 1fr)\n if (/^[+-]?(?:\\d*\\.\\d+|\\d+)[a-z%]+$/.test(token)) {\n return { bucket: Bucket.Value, processed: token };\n }\n\n // 9c. Plain unit-less numbers should be treated as value tokens (e.g.,\n // numeric arguments in custom style handlers).\n if (RE_NUMBER.test(token)) {\n return { bucket: Bucket.Value, processed: token };\n }\n\n // 10. Literal value keywords\n if (VALUE_KEYWORDS.has(token)) {\n return { bucket: Bucket.Value, processed: token };\n }\n\n // 10b. Special keyword colors\n if (token === 'transparent' || token === 'currentcolor') {\n return { bucket: Bucket.Color, processed: token };\n }\n\n // 11. Fallback modifier\n return { bucket: Bucket.Mod, processed: token };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"classify.js","names":[],"sources":["../../src/parser/classify.ts"],"sourcesContent":["import { getColorSpaceFunc, getColorSpaceSuffix } from '../utils/color-space';\nimport { getGlobalPredefinedTokens } from '../utils/styles';\n\nimport {\n COLOR_FUNCS,\n RE_HEX,\n RE_NUMBER,\n RE_RAW_UNIT,\n RE_UNIT_NUM,\n VALUE_KEYWORDS,\n canonicalFuncName,\n} from './const';\nimport { StyleParser } from './parser';\nimport type { ParserOptions, ProcessedStyle } from './types';\nimport { Bucket } from './types';\n\n/**\n * Re-parses a value through the parser until it stabilizes (no changes)\n * or max iterations reached. This allows units to reference other units.\n * Example: { x: '8px', y: '2x' } -> '1y' resolves to '16px'\n */\nfunction resolveUntilStable(\n value: string,\n opts: ParserOptions,\n recurse: (str: string) => ProcessedStyle,\n maxIterations = 10,\n): string {\n let current = value;\n for (let i = 0; i < maxIterations; i++) {\n // Check if the current value contains a custom unit that needs resolution\n const unitMatch = current.match(RE_UNIT_NUM);\n if (!unitMatch) break; // Not a unit number, no resolution needed\n\n const unitName = unitMatch[1];\n // Only recurse if the unit is a custom unit we know about\n // Any unit not in opts.units is assumed to be a native CSS unit\n if (!opts.units || !(unitName in opts.units)) break;\n\n const result = recurse(current);\n if (result.output === current) break; // Stable\n current = result.output;\n }\n return current;\n}\n\nexport function classify(\n raw: string,\n opts: ParserOptions,\n recurse: (str: string) => ProcessedStyle,\n): { bucket: Bucket; processed: string } {\n const token = raw.trim();\n if (!token) return { bucket: Bucket.Mod, processed: '' };\n\n // Early-out: if the token contains unmatched parentheses treat it as invalid\n // and skip it. This avoids cases like `drop-shadow(` that are missing a\n // closing parenthesis (e.g., a user-typo in CSS). We count paren depth while\n // ignoring everything inside string literals to avoid false positives.\n {\n let depth = 0;\n let inQuote: string | 0 = 0;\n for (let i = 0; i < token.length; i++) {\n const ch = token[i];\n\n // track quote context so parentheses inside quotes are ignored\n if (inQuote) {\n if (ch === inQuote && token[i - 1] !== '\\\\') inQuote = 0;\n continue;\n }\n if (ch === '\"' || ch === \"'\") {\n inQuote = ch;\n continue;\n }\n\n if (ch === '(') depth++;\n else if (ch === ')') depth = Math.max(0, depth - 1);\n }\n\n if (depth !== 0) {\n // Unbalanced parens → treat as invalid token (skipped).\n console.warn(\n 'tasty: skipped invalid function token with unmatched parentheses:',\n token,\n );\n return { bucket: Bucket.Mod, processed: '' };\n }\n }\n\n // Quoted string literals should be treated as value tokens (e.g., \"\" for content)\n if (\n (token.startsWith('\"') && token.endsWith('\"')) ||\n (token.startsWith(\"'\") && token.endsWith(\"'\"))\n ) {\n return { bucket: Bucket.Value, processed: token };\n }\n\n // 0. Double prefix for literal CSS property names ($$name -> --name, ##name -> --name-color)\n // Used in transitions and animations to reference the property name itself, not its value\n if (token.startsWith('$$')) {\n const name = token.slice(2);\n if (/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n return { bucket: Bucket.Value, processed: `--${name}` };\n }\n }\n if (token.startsWith('##')) {\n const name = token.slice(2);\n if (/^[a-z_][a-z0-9-_]*$/i.test(name)) {\n return { bucket: Bucket.Value, processed: `--${name}-color` };\n }\n }\n\n // 0b. Special handling for #current (reserved keyword, cannot be overridden by predefined tokens)\n // #current maps to CSS currentcolor keyword\n if (token === '#current') {\n return { bucket: Bucket.Color, processed: 'currentcolor' };\n }\n\n // #current with opacity: #current.5 or #current.$opacity\n // Uses color-mix since currentcolor doesn't support rgb() alpha syntax\n const currentAlphaMatch = token.match(\n /^#current\\.(\\$[a-z_][a-z0-9-_]*|[0-9]+)$/i,\n );\n if (currentAlphaMatch) {\n const rawAlpha = currentAlphaMatch[1];\n let percentage: string;\n if (rawAlpha.startsWith('$')) {\n // Custom property: $disabled -> calc(var(--disabled) * 100%)\n const propName = rawAlpha.slice(1);\n percentage = `calc(var(--${propName}) * 100%)`;\n } else if (rawAlpha === '0') {\n percentage = '0%';\n } else {\n // Convert .5 -> 50%, .05 -> 5%\n percentage = `${parseFloat('.' + rawAlpha) * 100}%`;\n }\n return {\n bucket: Bucket.Color,\n processed: `color-mix(in oklab, currentcolor ${percentage}, transparent)`,\n };\n }\n\n // 0c. Check for predefined tokens (configured via configure({ replaceTokens: {...} }))\n // Must happen before default $ and # handling to allow overriding\n if (token[0] === '$' || token[0] === '#') {\n const predefinedTokens = getGlobalPredefinedTokens();\n if (predefinedTokens) {\n // Exact match\n if (token in predefinedTokens) {\n const tokenValue = predefinedTokens[token];\n // Lowercase the token value to match parser behavior (parser lowercases input)\n return classify(tokenValue.toLowerCase(), opts, recurse);\n }\n // Check for color token with alpha suffix: #token.alpha or #token.$prop\n if (token[0] === '#') {\n const alphaMatch = token.match(\n /^(#[a-z0-9-]+)\\.(\\$[a-z_][a-z0-9-_]*|[0-9]+)$/i,\n );\n if (alphaMatch) {\n const [, baseToken, rawAlpha] = alphaMatch;\n if (baseToken in predefinedTokens) {\n const resolvedValue = predefinedTokens[baseToken];\n\n // If resolved value starts with # (color token), use standard alpha syntax\n if (resolvedValue.startsWith('#')) {\n // Lowercase to match parser behavior\n return classify(\n `${resolvedValue.toLowerCase()}.${rawAlpha}`,\n opts,\n recurse,\n );\n }\n\n // For color functions like rgb(), rgba(), hsl(), hwb(), etc., inject alpha\n // Includes all standard CSS color functions plus okhsl (plugin)\n const funcMatch = resolvedValue.match(\n /^(rgba?|hsla?|hwb|oklab|oklch|lab|lch|color|okhsl|device-cmyk|gray|color-mix|color-contrast)\\((.+)\\)$/i,\n );\n if (funcMatch) {\n const [, funcName, args] = funcMatch;\n // Handle $prop syntax for custom property alpha\n let alpha: string;\n if (rawAlpha.startsWith('$')) {\n const propName = rawAlpha.slice(1);\n alpha = `var(--${propName})`;\n } else {\n alpha = rawAlpha === '0' ? '0' : `.${rawAlpha}`;\n }\n // Normalize function name: rgba->rgb, hsla->hsl (modern syntax doesn't need 'a' suffix)\n const normalizedFunc = funcName.replace(/a$/i, '').toLowerCase();\n // Normalize to modern syntax: replace top-level commas with spaces\n // Preserves commas inside nested functions like min(), max(), clamp()\n const normalizeArgs = (a: string) => {\n let result = '';\n let depth = 0;\n for (let i = 0; i < a.length; i++) {\n const c = a[i];\n if (c === '(') {\n depth++;\n result += c;\n } else if (c === ')') {\n depth = Math.max(0, depth - 1);\n result += c;\n } else if (c === ',' && depth === 0) {\n // Skip comma and any following whitespace at top level\n while (i + 1 < a.length && /\\s/.test(a[i + 1])) i++;\n result += ' ';\n } else {\n result += c;\n }\n }\n return result;\n };\n // Helper: find last top-level occurrence of a character (ignores parentheses)\n const findLastTopLevel = (str: string, ch: string) => {\n let depth = 0;\n for (let i = str.length - 1; i >= 0; i--) {\n const c = str[i];\n if (c === ')') depth++;\n else if (c === '(') depth = Math.max(0, depth - 1);\n else if (c === ch && depth === 0) return i;\n }\n return -1;\n };\n\n // Check if already has alpha:\n // - Modern syntax: has `/` separator at top level (works with dynamic alpha like var()/calc())\n // - Legacy syntax: function ends with 'a' (rgba, hsla) and has exactly 4 top-level comma-separated values\n const slashIdx = findLastTopLevel(args, '/');\n const hasModernAlpha = slashIdx !== -1;\n\n // Count top-level commas to avoid commas inside nested functions\n let topLevelCommaCount = 0;\n let lastTopLevelComma = -1;\n {\n let depth = 0;\n for (let i = 0; i < args.length; i++) {\n const c = args[i];\n if (c === '(') depth++;\n else if (c === ')') depth = Math.max(0, depth - 1);\n else if (c === ',' && depth === 0) {\n topLevelCommaCount++;\n lastTopLevelComma = i;\n }\n }\n }\n\n const hasLegacyAlpha =\n !hasModernAlpha &&\n /a$/i.test(funcName) &&\n topLevelCommaCount === 3;\n\n const colorArgs =\n hasModernAlpha || hasLegacyAlpha\n ? normalizeArgs(\n hasModernAlpha\n ? args.slice(0, slashIdx).trim()\n : args.slice(0, lastTopLevelComma).trim(),\n )\n : normalizeArgs(args);\n\n const constructed = `${normalizedFunc}(${colorArgs} / ${alpha})`;\n\n // Custom functions (not native CSS) must be re-classified\n // so the function handler can convert them to valid CSS\n if (\n !COLOR_FUNCS.has(normalizedFunc) &&\n opts.funcs &&\n normalizedFunc in opts.funcs\n ) {\n return classify(constructed, opts, recurse);\n }\n\n return { bucket: Bucket.Color, processed: constructed };\n }\n\n // Fallback: try appending .alpha (may not work for all cases)\n return classify(`${resolvedValue}.${rawAlpha}`, opts, recurse);\n }\n }\n }\n }\n }\n\n // 0. Direct var(--*-color) token\n const varColorMatch = token.match(/^var\\(--([a-z0-9-]+)-color\\)$/);\n if (varColorMatch) {\n return { bucket: Bucket.Color, processed: token };\n }\n\n // 1. URL\n if (token.startsWith('url(')) {\n return { bucket: Bucket.Value, processed: token };\n }\n\n // 2. Custom property\n if (token[0] === '$') {\n const identMatch = token.match(/^\\$([a-z_][a-z0-9-_]*)$/);\n if (identMatch) {\n const name = identMatch[1];\n const processed = `var(--${name})`;\n const bucketType = name.endsWith('-color') ? Bucket.Color : Bucket.Value;\n return {\n bucket: bucketType,\n processed,\n };\n }\n // invalid custom property → modifier\n }\n\n // 3. Hash colors (with optional alpha suffix e.g., #purple.5 or #purple.$disabled)\n if (token[0] === '#' && token.length > 1) {\n // alpha form: #name.alpha or #name.$prop\n const alphaMatch = token.match(\n /^#([a-z0-9-]+)\\.(\\$[a-z_][a-z0-9-_]*|[0-9]+)$/i,\n );\n if (alphaMatch) {\n const [, base, rawAlpha] = alphaMatch;\n let alpha: string;\n if (rawAlpha.startsWith('$')) {\n // Custom property: $disabled -> var(--disabled)\n const propName = rawAlpha.slice(1);\n alpha = `var(--${propName})`;\n } else if (rawAlpha === '0') {\n alpha = '0';\n } else {\n alpha = `.${rawAlpha}`;\n }\n return {\n bucket: Bucket.Color,\n processed: `${getColorSpaceFunc()}(var(--${base}-color-${getColorSpaceSuffix()}) / ${alpha})`,\n };\n }\n\n // hyphenated names like #dark-05 should keep full name\n\n const name = token.slice(1);\n // valid hex → treat as hex literal with fallback\n if (RE_HEX.test(name)) {\n return {\n bucket: Bucket.Color,\n processed: `var(--${name}-color, #${name})`,\n };\n }\n // simple color name token → css variable lookup with rgb fallback\n return { bucket: Bucket.Color, processed: `var(--${name}-color)` };\n }\n\n // 4 & 5. Functions\n const openIdx = token.indexOf('(');\n if (openIdx > 0 && token.endsWith(')')) {\n const fname = token.slice(0, openIdx);\n const inner = token.slice(openIdx + 1, -1); // without ()\n\n if (COLOR_FUNCS.has(fname)) {\n // Process inner to expand nested colors or units.\n const argProcessed = recurse(inner).output.replace(/,\\s+/g, ','); // color funcs expect no spaces after commas\n return {\n bucket: Bucket.Color,\n processed: `${canonicalFuncName(fname)}(${argProcessed})`,\n };\n }\n\n // user function (provided via opts)\n if (opts.funcs && fname in opts.funcs) {\n // split by top-level commas within inner\n const tmp = new StyleParser(opts).process(inner); // fresh parser w/ same opts but no cache share issues\n const funcResult = opts.funcs[fname](tmp.groups);\n // Re-classify the result to determine proper bucket (e.g., if it returns a color)\n // Pass funcs: undefined to prevent infinite recursion if result matches a function pattern\n return classify(funcResult, { ...opts, funcs: undefined }, recurse);\n }\n\n // generic: process inner and rebuild\n const argProcessed = recurse(inner).output;\n return {\n bucket: Bucket.Value,\n processed: `${canonicalFuncName(fname)}(${argProcessed})`,\n };\n }\n\n // 6. Color fallback syntax: (#name, fallback)\n if (token.startsWith('(') && token.endsWith(')')) {\n const inner = token.slice(1, -1);\n const colorMatch = inner.match(/^#([a-z0-9-]+)\\s*,\\s*(.*)$/i);\n if (colorMatch) {\n const [, name, fallback] = colorMatch;\n const processedFallback = recurse(fallback).output;\n return {\n bucket: Bucket.Color,\n processed: `var(--${name}-color, ${processedFallback})`,\n };\n }\n }\n\n // 7. Custom property with fallback syntax: ($prop, fallback)\n if (token.startsWith('(') && token.endsWith(')')) {\n const inner = token.slice(1, -1);\n const match = inner.match(/^\\$([a-z_][a-z0-9-_]*)\\s*,\\s*(.*)$/);\n if (match) {\n const [, name, fallback] = match;\n const processedFallback = recurse(fallback).output;\n const bucketType = name.endsWith('-color') ? Bucket.Color : Bucket.Value;\n return {\n bucket: bucketType,\n processed: `var(--${name}, ${processedFallback})`,\n };\n }\n }\n\n // 8. Auto-calc group\n if (token[0] === '(' && token[token.length - 1] === ')') {\n const inner = token.slice(1, -1);\n const innerProcessed = recurse(inner).output;\n return { bucket: Bucket.Value, processed: `calc(${innerProcessed})` };\n }\n\n // 9. Unit number\n const um = token.match(RE_UNIT_NUM);\n if (um) {\n const unit = um[1];\n const numericPart = parseFloat(token.slice(0, -unit.length));\n const handler = opts.units && opts.units[unit];\n if (handler) {\n if (typeof handler === 'string') {\n // Check if this is a raw CSS unit (e.g., \"8px\", \"1rem\")\n const rawMatch = handler.match(RE_RAW_UNIT);\n if (rawMatch) {\n // Raw unit: calculate directly instead of using calc()\n const [, baseNum, cssUnit] = rawMatch;\n const result = numericPart * parseFloat(baseNum);\n const processed = `${result}${cssUnit}`;\n // Re-parse to resolve any nested units (e.g., units referencing other units)\n const resolved = resolveUntilStable(processed, opts, recurse);\n return { bucket: Bucket.Value, processed: resolved };\n }\n\n // Non-raw handler (e.g., \"var(--gap)\", \"calc(...)\"): use calc() wrapping\n const base = handler;\n if (numericPart === 1) {\n return { bucket: Bucket.Value, processed: base };\n }\n return {\n bucket: Bucket.Value,\n processed: `calc(${numericPart} * ${base})`,\n };\n } else {\n // Function units return complete CSS expressions, no wrapping needed\n const inner = handler(numericPart);\n return {\n bucket: Bucket.Value,\n processed: inner,\n };\n }\n }\n }\n\n // 9b. Unknown numeric+unit → treat as literal value (e.g., 1fr)\n if (/^[+-]?(?:\\d*\\.\\d+|\\d+)[a-z%]+$/.test(token)) {\n return { bucket: Bucket.Value, processed: token };\n }\n\n // 9c. Plain unit-less numbers should be treated as value tokens (e.g.,\n // numeric arguments in custom style handlers).\n if (RE_NUMBER.test(token)) {\n return { bucket: Bucket.Value, processed: token };\n }\n\n // 10. Literal value keywords\n if (VALUE_KEYWORDS.has(token)) {\n return { bucket: Bucket.Value, processed: token };\n }\n\n // 10b. Special keyword colors\n if (token === 'transparent' || token === 'currentcolor') {\n return { bucket: Bucket.Color, processed: token };\n }\n\n // 11. Fallback modifier\n return { bucket: Bucket.Mod, processed: token };\n}\n"],"mappings":"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"}
|
package/dist/parser/const.js
CHANGED
|
@@ -27,7 +27,23 @@ const RE_UNIT_NUM = /^[+-]?(?:\d*\.\d+|\d+)([a-z][a-z0-9]*)$/;
|
|
|
27
27
|
const RE_NUMBER = /^[+-]?(?:\d*\.\d+|\d+)$/;
|
|
28
28
|
const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;
|
|
29
29
|
const RE_RAW_UNIT = /^([+-]?(?:\d*\.\d+|\d+))([a-z%]+)$/;
|
|
30
|
+
const CANONICAL_FUNC_CASE = new Map([
|
|
31
|
+
["translatex", "translateX"],
|
|
32
|
+
["translatey", "translateY"],
|
|
33
|
+
["translatez", "translateZ"],
|
|
34
|
+
["scalex", "scaleX"],
|
|
35
|
+
["scaley", "scaleY"],
|
|
36
|
+
["scalez", "scaleZ"],
|
|
37
|
+
["rotatex", "rotateX"],
|
|
38
|
+
["rotatey", "rotateY"],
|
|
39
|
+
["rotatez", "rotateZ"],
|
|
40
|
+
["skewx", "skewX"],
|
|
41
|
+
["skewy", "skewY"]
|
|
42
|
+
]);
|
|
43
|
+
function canonicalFuncName(lowered) {
|
|
44
|
+
return CANONICAL_FUNC_CASE.get(lowered) ?? lowered;
|
|
45
|
+
}
|
|
30
46
|
|
|
31
47
|
//#endregion
|
|
32
|
-
export { COLOR_FUNCS, RE_HEX, RE_NUMBER, RE_RAW_UNIT, RE_UNIT_NUM, VALUE_KEYWORDS };
|
|
48
|
+
export { COLOR_FUNCS, RE_HEX, RE_NUMBER, RE_RAW_UNIT, RE_UNIT_NUM, VALUE_KEYWORDS, canonicalFuncName };
|
|
33
49
|
//# sourceMappingURL=const.js.map
|
package/dist/parser/const.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"const.js","names":[],"sources":["../../src/parser/const.ts"],"sourcesContent":["export const VALUE_KEYWORDS = new Set([\n 'auto',\n 'max-content',\n 'min-content',\n 'fit-content',\n 'stretch',\n 'initial',\n]);\n\nexport const COLOR_FUNCS = new Set([\n 'rgb',\n 'rgba',\n 'hsl',\n 'hsla',\n 'hwb',\n 'lab',\n 'lch',\n 'oklab',\n 'oklch',\n 'color',\n 'device-cmyk',\n 'gray',\n 'color-mix',\n 'color-contrast',\n]);\n\nexport const RE_UNIT_NUM = /^[+-]?(?:\\d*\\.\\d+|\\d+)([a-z][a-z0-9]*)$/;\nexport const RE_NUMBER = /^[+-]?(?:\\d*\\.\\d+|\\d+)$/;\nexport const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;\n// Matches raw CSS unit values like \"8px\", \"1rem\", \"0.5em\" - captures number and unit separately\nexport const RE_RAW_UNIT = /^([+-]?(?:\\d*\\.\\d+|\\d+))([a-z%]+)$/;\n"],"mappings":";AAAA,MAAa,iBAAiB,IAAI,IAAI;CACpC;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAa,cAAc,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAa,cAAc;AAC3B,MAAa,YAAY;AACzB,MAAa,SAAS;AAEtB,MAAa,cAAc"}
|
|
1
|
+
{"version":3,"file":"const.js","names":[],"sources":["../../src/parser/const.ts"],"sourcesContent":["export const VALUE_KEYWORDS = new Set([\n 'auto',\n 'max-content',\n 'min-content',\n 'fit-content',\n 'stretch',\n 'initial',\n]);\n\nexport const COLOR_FUNCS = new Set([\n 'rgb',\n 'rgba',\n 'hsl',\n 'hsla',\n 'hwb',\n 'lab',\n 'lch',\n 'oklab',\n 'oklch',\n 'color',\n 'device-cmyk',\n 'gray',\n 'color-mix',\n 'color-contrast',\n]);\n\nexport const RE_UNIT_NUM = /^[+-]?(?:\\d*\\.\\d+|\\d+)([a-z][a-z0-9]*)$/;\nexport const RE_NUMBER = /^[+-]?(?:\\d*\\.\\d+|\\d+)$/;\nexport const RE_HEX = /^(?:[0-9a-f]{3,4}|[0-9a-f]{6}(?:[0-9a-f]{2})?)$/;\n// Matches raw CSS unit values like \"8px\", \"1rem\", \"0.5em\" - captures number and unit separately\nexport const RE_RAW_UNIT = /^([+-]?(?:\\d*\\.\\d+|\\d+))([a-z%]+)$/;\n\nconst CANONICAL_FUNC_CASE = new Map([\n ['translatex', 'translateX'],\n ['translatey', 'translateY'],\n ['translatez', 'translateZ'],\n ['scalex', 'scaleX'],\n ['scaley', 'scaleY'],\n ['scalez', 'scaleZ'],\n ['rotatex', 'rotateX'],\n ['rotatey', 'rotateY'],\n ['rotatez', 'rotateZ'],\n ['skewx', 'skewX'],\n ['skewy', 'skewY'],\n]);\n\nexport function canonicalFuncName(lowered: string): string {\n return CANONICAL_FUNC_CASE.get(lowered) ?? lowered;\n}\n"],"mappings":";AAAA,MAAa,iBAAiB,IAAI,IAAI;CACpC;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAa,cAAc,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAa,cAAc;AAC3B,MAAa,YAAY;AACzB,MAAa,SAAS;AAEtB,MAAa,cAAc;AAE3B,MAAM,sBAAsB,IAAI,IAAI;CAClC,CAAC,cAAc,aAAa;CAC5B,CAAC,cAAc,aAAa;CAC5B,CAAC,cAAc,aAAa;CAC5B,CAAC,UAAU,SAAS;CACpB,CAAC,UAAU,SAAS;CACpB,CAAC,UAAU,SAAS;CACpB,CAAC,WAAW,UAAU;CACtB,CAAC,WAAW,UAAU;CACtB,CAAC,WAAW,UAAU;CACtB,CAAC,SAAS,QAAQ;CAClB,CAAC,SAAS,QAAQ;CACnB,CAAC;AAEF,SAAgB,kBAAkB,SAAyB;AACzD,QAAO,oBAAoB,IAAI,QAAQ,IAAI"}
|
package/dist/styles/preset.js
CHANGED
|
@@ -62,10 +62,10 @@ function presetStyle({ preset, fontSize, lineHeight, textTransform, letterSpacin
|
|
|
62
62
|
mods = mods.filter((mod) => mod !== "strong" && mod !== "bold" && mod !== "italic" && mod !== "icon" && mod !== "tight");
|
|
63
63
|
const name = mods[0] || "inherit";
|
|
64
64
|
if (fontSize == null) setCSSValue(styles, "font-size", name, { cssOnly: true });
|
|
65
|
-
if (lineHeight == null) setCSSValue(styles, "line-height", name);
|
|
65
|
+
if (lineHeight == null) setCSSValue(styles, "line-height", name, { cssOnly: true });
|
|
66
66
|
if (letterSpacing == null) setCSSValue(styles, "letter-spacing", name, { cssOnly: true });
|
|
67
67
|
if (fontWeight == null) setCSSValue(styles, "font-weight", name, { cssOnly: true });
|
|
68
|
-
if (fontStyle == null) setCSSValue(styles, "font-style", name);
|
|
68
|
+
if (fontStyle == null) setCSSValue(styles, "font-style", name, { cssOnly: true });
|
|
69
69
|
if (textTransform == null) setCSSValue(styles, "text-transform", name, { cssOnly: true });
|
|
70
70
|
if (fontFamily == null && font == null) setCSSValue(styles, "font-family", name, { cssOnly: true });
|
|
71
71
|
setCSSValue(styles, "bold-font-weight", name, { varOnly: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preset.js","names":[],"sources":["../../src/styles/preset.ts"],"sourcesContent":["import { makeEmptyDetails } from '../parser/types';\nimport { parseStyle } from '../utils/styles';\n\nimport type { Styles } from './types';\n\n/**\n * Convert a value to CSS, handling numbers as pixels for numeric properties\n */\nfunction toCSS(\n value: string | number | undefined,\n isNumeric: boolean,\n): string | null {\n if (value == null) return null;\n if (typeof value === 'number') {\n return isNumeric ? `${value}px` : String(value);\n }\n // Parse through style parser to handle custom units like 1x, 2r, etc.\n const processed = parseStyle(String(value));\n return processed.groups[0]?.values[0] || String(value);\n}\n\nfunction setCSSValue(\n styles: Styles,\n styleName: string,\n presetName: string,\n { varOnly, cssOnly }: { varOnly?: boolean; cssOnly?: boolean } = {},\n) {\n const value = (() => {\n if (presetName === 'inherit') {\n return 'inherit';\n }\n\n const defaultValue = `var(--default-${styleName}${\n styleName === 'font-family'\n ? ', var(--font-sans, var(--font-sans-fallback))'\n : ''\n })`;\n const fontSuffix =\n styleName === 'font-family'\n ? ', var(--font-sans, var(--font-sans-fallback))'\n : '';\n\n if (presetName === 'default') {\n return `${defaultValue}${fontSuffix}`;\n } else {\n return `var(--${presetName}-${styleName}, ${defaultValue})${fontSuffix}`;\n }\n })();\n\n if (!cssOnly) {\n styles[`--${styleName}`] = value;\n }\n\n if (!varOnly) {\n styles[styleName] = value;\n }\n}\n\ninterface PresetStyleProps {\n preset?: string | boolean;\n fontSize?: string | number;\n lineHeight?: string | number;\n textTransform?: string;\n letterSpacing?: string | number;\n fontWeight?: string | number;\n fontStyle?: string | boolean;\n fontFamily?: string;\n /** Alias for fontFamily with special handling for 'monospace' and boolean */\n font?: string | boolean;\n}\n\n/**\n * Resolve font/fontFamily value to CSS font-family string.\n *\n * - `font=\"monospace\"` → var(--font-mono, var(--font-mono-fallback))\n * - `font={true}` → var(--font-sans, var(--font-sans-fallback))\n * - `font=\"CustomFont\"` → CustomFont, var(--font-sans, var(--font-sans-fallback))\n * - `fontFamily=\"Arial\"` → Arial (direct, no fallback)\n */\nfunction resolveFontFamily(\n font: string | boolean | undefined,\n fontFamily: string | undefined,\n): string | null {\n // fontFamily takes precedence as a direct value\n if (fontFamily) {\n return fontFamily;\n }\n\n if (font == null || font === false) {\n return null;\n }\n\n if (font === 'monospace') {\n return 'var(--font-mono, var(--font-mono-fallback))';\n }\n\n if (font === true) {\n return 'var(--font-sans, var(--font-sans-fallback))';\n }\n\n return `${font}, var(--font-sans, var(--font-sans-fallback))`;\n}\n\n/**\n * Handles typography preset and individual font properties.\n *\n * When `preset` is defined, it sets up CSS custom properties for typography.\n * Individual font props can be used with or without `preset`:\n * - With `preset`: overrides the preset value for that property\n * - Without `preset`: outputs the CSS directly\n *\n * Number values are converted to pixels for fontSize, lineHeight, letterSpacing.\n * fontWeight accepts numbers directly (e.g., 400, 700).\n *\n * font vs fontFamily:\n * - `font` is the recommended prop with special handling (monospace, boolean, fallback)\n * - `fontFamily` is a direct value without special handling\n */\nexport function presetStyle({\n preset,\n fontSize,\n lineHeight,\n textTransform,\n letterSpacing,\n fontWeight,\n fontStyle,\n fontFamily,\n font,\n}: PresetStyleProps) {\n const styles: Styles = {};\n const hasPreset = preset != null && preset !== false;\n\n // Handle preset if defined\n if (hasPreset) {\n const presetValue = preset === true ? '' : String(preset);\n\n const processed = parseStyle(presetValue);\n let { mods } = processed.groups[0] ?? makeEmptyDetails();\n\n const isStrong = mods.includes('strong');\n const isItalic = mods.includes('italic');\n const isIcon = mods.includes('icon');\n const isTight = mods.includes('tight');\n\n mods = mods.filter(\n (mod) =>\n mod !== 'strong' &&\n mod !== 'bold' &&\n mod !== 'italic' &&\n mod !== 'icon' &&\n mod !== 'tight',\n );\n\n const name = mods[0] || 'inherit';\n\n // Set preset values for properties not explicitly overridden\n if (fontSize == null) {\n setCSSValue(styles, 'font-size', name, { cssOnly: true });\n }\n if (lineHeight == null) {\n setCSSValue(styles, 'line-height', name);\n }\n if (letterSpacing == null) {\n setCSSValue(styles, 'letter-spacing', name, { cssOnly: true });\n }\n if (fontWeight == null) {\n setCSSValue(styles, 'font-weight', name, { cssOnly: true });\n }\n if (fontStyle == null) {\n setCSSValue(styles, 'font-style', name);\n }\n if (textTransform == null) {\n setCSSValue(styles, 'text-transform', name, { cssOnly: true });\n }\n if (fontFamily == null && font == null) {\n setCSSValue(styles, 'font-family', name, { cssOnly: true });\n }\n\n setCSSValue(styles, 'bold-font-weight', name, { varOnly: true });\n setCSSValue(styles, 'icon-size', name, { varOnly: true });\n\n if (isStrong) {\n styles['font-weight'] = 'var(--bold-font-weight)';\n }\n if (isItalic) {\n styles['font-style'] = 'italic';\n }\n if (isIcon) {\n styles['font-size'] = 'var(--icon-size)';\n styles['line-height'] = 'var(--icon-size)';\n }\n if (isTight) {\n styles['line-height'] = '1em';\n }\n }\n\n // Handle individual font properties (work with or without preset)\n const fontSizeVal = toCSS(fontSize, true);\n if (fontSizeVal) {\n styles['font-size'] = fontSizeVal;\n }\n\n const lineHeightVal = toCSS(lineHeight, true);\n if (lineHeightVal) {\n styles['line-height'] = lineHeightVal;\n }\n\n const letterSpacingVal = toCSS(letterSpacing, true);\n if (letterSpacingVal) {\n styles['letter-spacing'] = letterSpacingVal;\n }\n\n // fontWeight: numbers should NOT get 'px' suffix\n const fontWeightVal = toCSS(fontWeight, false);\n if (fontWeightVal) {\n styles['font-weight'] = fontWeightVal;\n }\n\n // fontStyle: handle boolean (true → italic) and string values\n if (fontStyle != null) {\n if (fontStyle === true) {\n styles['font-style'] = 'italic';\n } else if (fontStyle !== 'inherit') {\n styles['font-style'] = fontStyle ? 'italic' : 'normal';\n } else {\n styles['font-style'] = 'inherit';\n }\n }\n\n if (textTransform) {\n styles['text-transform'] = textTransform;\n }\n\n // Handle font/fontFamily (font has special handling, fontFamily is direct)\n const fontFamily_ = resolveFontFamily(font, fontFamily);\n if (fontFamily_) {\n styles['font-family'] = fontFamily_;\n }\n\n // Return undefined if no styles to apply\n if (Object.keys(styles).length === 0) {\n return;\n }\n\n return styles;\n}\n\npresetStyle.__lookupStyles = [\n 'preset',\n 'fontSize',\n 'lineHeight',\n 'letterSpacing',\n 'textTransform',\n 'fontWeight',\n 'fontStyle',\n 'fontFamily',\n 'font',\n];\n"],"mappings":";;;;;;;AAQA,SAAS,MACP,OACA,WACe;AACf,KAAI,SAAS,KAAM,QAAO;AAC1B,KAAI,OAAO,UAAU,SACnB,QAAO,YAAY,GAAG,MAAM,MAAM,OAAO,MAAM;AAIjD,QADkB,WAAW,OAAO,MAAM,CAAC,CAC1B,OAAO,IAAI,OAAO,MAAM,OAAO,MAAM;;AAGxD,SAAS,YACP,QACA,WACA,YACA,EAAE,SAAS,YAAsD,EAAE,EACnE;CACA,MAAM,eAAe;AACnB,MAAI,eAAe,UACjB,QAAO;EAGT,MAAM,eAAe,iBAAiB,YACpC,cAAc,gBACV,kDACA,GACL;EACD,MAAM,aACJ,cAAc,gBACV,kDACA;AAEN,MAAI,eAAe,UACjB,QAAO,GAAG,eAAe;MAEzB,QAAO,SAAS,WAAW,GAAG,UAAU,IAAI,aAAa,GAAG;KAE5D;AAEJ,KAAI,CAAC,QACH,QAAO,KAAK,eAAe;AAG7B,KAAI,CAAC,QACH,QAAO,aAAa;;;;;;;;;;AAyBxB,SAAS,kBACP,MACA,YACe;AAEf,KAAI,WACF,QAAO;AAGT,KAAI,QAAQ,QAAQ,SAAS,MAC3B,QAAO;AAGT,KAAI,SAAS,YACX,QAAO;AAGT,KAAI,SAAS,KACX,QAAO;AAGT,QAAO,GAAG,KAAK;;;;;;;;;;;;;;;;;AAkBjB,SAAgB,YAAY,EAC1B,QACA,UACA,YACA,eACA,eACA,YACA,WACA,YACA,QACmB;CACnB,MAAM,SAAiB,EAAE;AAIzB,KAHkB,UAAU,QAAQ,WAAW,OAGhC;EAIb,IAAI,EAAE,SADY,WAFE,WAAW,OAAO,KAAK,OAAO,OAAO,CAEhB,CAChB,OAAO,MAAM,kBAAkB;EAExD,MAAM,WAAW,KAAK,SAAS,SAAS;EACxC,MAAM,WAAW,KAAK,SAAS,SAAS;EACxC,MAAM,SAAS,KAAK,SAAS,OAAO;EACpC,MAAM,UAAU,KAAK,SAAS,QAAQ;AAEtC,SAAO,KAAK,QACT,QACC,QAAQ,YACR,QAAQ,UACR,QAAQ,YACR,QAAQ,UACR,QAAQ,QACX;EAED,MAAM,OAAO,KAAK,MAAM;AAGxB,MAAI,YAAY,KACd,aAAY,QAAQ,aAAa,MAAM,EAAE,SAAS,MAAM,CAAC;AAE3D,MAAI,cAAc,KAChB,aAAY,QAAQ,eAAe,
|
|
1
|
+
{"version":3,"file":"preset.js","names":[],"sources":["../../src/styles/preset.ts"],"sourcesContent":["import { makeEmptyDetails } from '../parser/types';\nimport { parseStyle } from '../utils/styles';\n\nimport type { Styles } from './types';\n\n/**\n * Convert a value to CSS, handling numbers as pixels for numeric properties\n */\nfunction toCSS(\n value: string | number | undefined,\n isNumeric: boolean,\n): string | null {\n if (value == null) return null;\n if (typeof value === 'number') {\n return isNumeric ? `${value}px` : String(value);\n }\n // Parse through style parser to handle custom units like 1x, 2r, etc.\n const processed = parseStyle(String(value));\n return processed.groups[0]?.values[0] || String(value);\n}\n\nfunction setCSSValue(\n styles: Styles,\n styleName: string,\n presetName: string,\n { varOnly, cssOnly }: { varOnly?: boolean; cssOnly?: boolean } = {},\n) {\n const value = (() => {\n if (presetName === 'inherit') {\n return 'inherit';\n }\n\n const defaultValue = `var(--default-${styleName}${\n styleName === 'font-family'\n ? ', var(--font-sans, var(--font-sans-fallback))'\n : ''\n })`;\n const fontSuffix =\n styleName === 'font-family'\n ? ', var(--font-sans, var(--font-sans-fallback))'\n : '';\n\n if (presetName === 'default') {\n return `${defaultValue}${fontSuffix}`;\n } else {\n return `var(--${presetName}-${styleName}, ${defaultValue})${fontSuffix}`;\n }\n })();\n\n if (!cssOnly) {\n styles[`--${styleName}`] = value;\n }\n\n if (!varOnly) {\n styles[styleName] = value;\n }\n}\n\ninterface PresetStyleProps {\n preset?: string | boolean;\n fontSize?: string | number;\n lineHeight?: string | number;\n textTransform?: string;\n letterSpacing?: string | number;\n fontWeight?: string | number;\n fontStyle?: string | boolean;\n fontFamily?: string;\n /** Alias for fontFamily with special handling for 'monospace' and boolean */\n font?: string | boolean;\n}\n\n/**\n * Resolve font/fontFamily value to CSS font-family string.\n *\n * - `font=\"monospace\"` → var(--font-mono, var(--font-mono-fallback))\n * - `font={true}` → var(--font-sans, var(--font-sans-fallback))\n * - `font=\"CustomFont\"` → CustomFont, var(--font-sans, var(--font-sans-fallback))\n * - `fontFamily=\"Arial\"` → Arial (direct, no fallback)\n */\nfunction resolveFontFamily(\n font: string | boolean | undefined,\n fontFamily: string | undefined,\n): string | null {\n // fontFamily takes precedence as a direct value\n if (fontFamily) {\n return fontFamily;\n }\n\n if (font == null || font === false) {\n return null;\n }\n\n if (font === 'monospace') {\n return 'var(--font-mono, var(--font-mono-fallback))';\n }\n\n if (font === true) {\n return 'var(--font-sans, var(--font-sans-fallback))';\n }\n\n return `${font}, var(--font-sans, var(--font-sans-fallback))`;\n}\n\n/**\n * Handles typography preset and individual font properties.\n *\n * When `preset` is defined, it sets up CSS custom properties for typography.\n * Individual font props can be used with or without `preset`:\n * - With `preset`: overrides the preset value for that property\n * - Without `preset`: outputs the CSS directly\n *\n * Number values are converted to pixels for fontSize, lineHeight, letterSpacing.\n * fontWeight accepts numbers directly (e.g., 400, 700).\n *\n * font vs fontFamily:\n * - `font` is the recommended prop with special handling (monospace, boolean, fallback)\n * - `fontFamily` is a direct value without special handling\n */\nexport function presetStyle({\n preset,\n fontSize,\n lineHeight,\n textTransform,\n letterSpacing,\n fontWeight,\n fontStyle,\n fontFamily,\n font,\n}: PresetStyleProps) {\n const styles: Styles = {};\n const hasPreset = preset != null && preset !== false;\n\n // Handle preset if defined\n if (hasPreset) {\n const presetValue = preset === true ? '' : String(preset);\n\n const processed = parseStyle(presetValue);\n let { mods } = processed.groups[0] ?? makeEmptyDetails();\n\n const isStrong = mods.includes('strong');\n const isItalic = mods.includes('italic');\n const isIcon = mods.includes('icon');\n const isTight = mods.includes('tight');\n\n mods = mods.filter(\n (mod) =>\n mod !== 'strong' &&\n mod !== 'bold' &&\n mod !== 'italic' &&\n mod !== 'icon' &&\n mod !== 'tight',\n );\n\n const name = mods[0] || 'inherit';\n\n // Set preset values for properties not explicitly overridden\n if (fontSize == null) {\n setCSSValue(styles, 'font-size', name, { cssOnly: true });\n }\n if (lineHeight == null) {\n setCSSValue(styles, 'line-height', name, { cssOnly: true });\n }\n if (letterSpacing == null) {\n setCSSValue(styles, 'letter-spacing', name, { cssOnly: true });\n }\n if (fontWeight == null) {\n setCSSValue(styles, 'font-weight', name, { cssOnly: true });\n }\n if (fontStyle == null) {\n setCSSValue(styles, 'font-style', name, { cssOnly: true });\n }\n if (textTransform == null) {\n setCSSValue(styles, 'text-transform', name, { cssOnly: true });\n }\n if (fontFamily == null && font == null) {\n setCSSValue(styles, 'font-family', name, { cssOnly: true });\n }\n\n setCSSValue(styles, 'bold-font-weight', name, { varOnly: true });\n setCSSValue(styles, 'icon-size', name, { varOnly: true });\n\n if (isStrong) {\n styles['font-weight'] = 'var(--bold-font-weight)';\n }\n if (isItalic) {\n styles['font-style'] = 'italic';\n }\n if (isIcon) {\n styles['font-size'] = 'var(--icon-size)';\n styles['line-height'] = 'var(--icon-size)';\n }\n if (isTight) {\n styles['line-height'] = '1em';\n }\n }\n\n // Handle individual font properties (work with or without preset)\n const fontSizeVal = toCSS(fontSize, true);\n if (fontSizeVal) {\n styles['font-size'] = fontSizeVal;\n }\n\n const lineHeightVal = toCSS(lineHeight, true);\n if (lineHeightVal) {\n styles['line-height'] = lineHeightVal;\n }\n\n const letterSpacingVal = toCSS(letterSpacing, true);\n if (letterSpacingVal) {\n styles['letter-spacing'] = letterSpacingVal;\n }\n\n // fontWeight: numbers should NOT get 'px' suffix\n const fontWeightVal = toCSS(fontWeight, false);\n if (fontWeightVal) {\n styles['font-weight'] = fontWeightVal;\n }\n\n // fontStyle: handle boolean (true → italic) and string values\n if (fontStyle != null) {\n if (fontStyle === true) {\n styles['font-style'] = 'italic';\n } else if (fontStyle !== 'inherit') {\n styles['font-style'] = fontStyle ? 'italic' : 'normal';\n } else {\n styles['font-style'] = 'inherit';\n }\n }\n\n if (textTransform) {\n styles['text-transform'] = textTransform;\n }\n\n // Handle font/fontFamily (font has special handling, fontFamily is direct)\n const fontFamily_ = resolveFontFamily(font, fontFamily);\n if (fontFamily_) {\n styles['font-family'] = fontFamily_;\n }\n\n // Return undefined if no styles to apply\n if (Object.keys(styles).length === 0) {\n return;\n }\n\n return styles;\n}\n\npresetStyle.__lookupStyles = [\n 'preset',\n 'fontSize',\n 'lineHeight',\n 'letterSpacing',\n 'textTransform',\n 'fontWeight',\n 'fontStyle',\n 'fontFamily',\n 'font',\n];\n"],"mappings":"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"}
|
package/dist/utils/styles.d.ts
CHANGED
package/dist/utils/styles.js
CHANGED
package/dist/utils/styles.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.js","names":[],"sources":["../../src/utils/styles.ts"],"sourcesContent":["import { StyleParser } from '../parser/parser';\nimport { okhslFunc } from '../plugins/okhsl-plugin';\n\nimport type { ProcessedStyle, StyleDetails } from '../parser/types';\n\nimport { getNamedColorHex } from './color-math';\n\nexport {\n getNamedColorHex,\n getRgbValuesFromRgbaString,\n hexToRgb,\n strToRgb,\n} from './color-math';\n\nexport type StyleValue<T = string> = T | boolean | number | null | undefined;\n\n/**\n * Normalize a color token value.\n * - Boolean `true` is converted to `'transparent'`\n * - Boolean `false` returns `null` (signals the token should be skipped)\n * - Other values are returned as-is\n *\n * @param value - The raw token value\n * @returns Normalized value or null if the token should be skipped\n */\nexport function normalizeColorTokenValue<T>(\n value: T | boolean,\n): T | 'transparent' | null {\n if (value === true) {\n return 'transparent';\n }\n if (value === false) {\n return null;\n }\n return value as T;\n}\n\nexport type StyleValueStateMap<T = string> = Record<\n string,\n StyleValue<T> | '@inherit'\n>;\n\n/**\n * Combined type for style values that can be either a direct value or a state map.\n * Use this for component props that accept style values.\n */\nexport type StylePropValue<T = string> = StyleValue<T> | StyleValueStateMap<T>;\n\nexport type CSSMap = { $?: string | string[] } & Record<\n string,\n string | string[]\n>;\n\nexport type StyleHandlerResult = CSSMap | CSSMap[] | void;\n\nexport type RawStyleHandler = (value: StyleValueStateMap) => StyleHandlerResult;\n\nexport type StyleHandler = RawStyleHandler & {\n __lookupStyles: string[];\n};\n\n/**\n * Handler definition forms for configure() and plugins.\n * - Function only: lookup styles inferred from key name\n * - Single property tuple: ['styleName', handler]\n * - Multi-property tuple: [['style1', 'style2'], handler]\n */\nexport type StyleHandlerDefinition =\n | RawStyleHandler\n | [string, RawStyleHandler]\n | [string[], RawStyleHandler];\n\nexport interface ParsedColor {\n color?: string;\n name?: string;\n opacity?: number;\n}\n\nexport type StyleMap = Record<string, StyleValue | StyleValueStateMap>;\n\nconst devMode = process.env.NODE_ENV !== 'production';\n\n// Precompiled regex patterns for parseColor optimization\n// Match var(--name-color...) and extract the name, regardless of fallbacks\nconst COLOR_VAR_PATTERN = /var\\(--([a-z0-9-]+)-color/;\nconst COLOR_VAR_COMPONENTS_PATTERN =\n /var\\(--([a-z0-9-]+)-color-(?:rgb|hsl|oklch)/;\nconst RGB_ALPHA_PATTERN = /\\/\\s*([0-9.]+)\\)/;\nconst RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;\nconst RE_VAR_COLOR = /^var\\(--[a-z0-9-]+-color/;\n\nfunction isSimpleColorFast(val: string): boolean {\n const c0 = val.charCodeAt(0);\n\n switch (c0) {\n case 35: // '#'\n return RE_HEX_COLOR.test(val);\n case 114: // 'r'\n return val.charCodeAt(1) === 103 && val.charCodeAt(2) === 98; // 'rgb'\n case 104: // 'h'\n return val.charCodeAt(1) === 115 && val.charCodeAt(2) === 108; // 'hsl'\n case 108: // 'l'\n return val.charCodeAt(1) === 99 && val.charCodeAt(2) === 104; // 'lch'\n case 111: // 'o'\n return val.startsWith('oklch(') || val.startsWith('okhsl(');\n case 118: // 'v'\n return RE_VAR_COLOR.test(val);\n case 99: // 'c'\n return val === 'currentColor' || val === 'currentcolor';\n case 116: // 't'\n return val === 'transparent';\n default:\n return getNamedColorHex().has(val.toLowerCase());\n }\n}\n\n// Rate limiting for dev warnings to avoid spam\nlet colorWarningCount = 0;\nconst MAX_COLOR_WARNINGS = 10;\n\nexport const CUSTOM_UNITS = {\n r: '6px',\n cr: '10px',\n bw: '1px',\n ow: '3px',\n x: '8px',\n lh: 'var(--line-height)',\n sf: function sf(num: number) {\n return `minmax(0, ${num}fr)`;\n },\n};\n\nexport const DIRECTIONS = ['top', 'right', 'bottom', 'left'];\n\n// Lazy-initialized to break the circular dependency:\n// parser.ts → classify.ts → utils/styles.ts → parser.ts\nlet __tastyParser: StyleParser | null = null;\n\nfunction getOrCreateParser(): StyleParser {\n if (!__tastyParser) {\n __tastyParser = new StyleParser({ units: CUSTOM_UNITS });\n __tastyParser.setFuncs(__tastyFuncs);\n }\n return __tastyParser;\n}\n\n// Registry for user-provided custom functions that the parser can call.\n// It is updated through the `customFunc` helper exported below.\n// okhsl is registered as a built-in function so it works regardless of\n// tree-shaking or module initialization order.\nconst __tastyFuncs: Record<string, (groups: StyleDetails[]) => string> = {\n okhsl: okhslFunc,\n};\n\nexport function customFunc(\n name: string,\n fn: (groups: StyleDetails[]) => string,\n) {\n __tastyFuncs[name] = fn;\n getOrCreateParser().setFuncs(__tastyFuncs);\n}\n\n/**\n * Get the global StyleParser instance.\n * Used by configure() to apply parser configuration.\n */\nexport function getGlobalParser(): StyleParser {\n return getOrCreateParser();\n}\n\n/**\n * Get the current custom functions registry.\n * Used by configure() to merge with new functions.\n */\nexport function getGlobalFuncs(): Record<\n string,\n (groups: StyleDetails[]) => string\n> {\n return __tastyFuncs;\n}\n\n// ============================================================================\n// Global Predefined Tokens\n// ============================================================================\n\n/**\n * Storage for predefined tokens that are replaced during style parsing.\n * Keys are token names (with $ or # prefix), values are pre-processed CSS values.\n */\nlet __globalPredefinedTokens: Record<string, string> | null = null;\n\n/**\n * Set global predefined tokens.\n * Called from configure() after processing token values.\n * Merges with existing tokens (new tokens override existing ones with same key).\n * Keys are normalized to lowercase (parser lowercases input before classification).\n * @internal\n */\nexport function setGlobalPredefinedTokens(\n tokens: Record<string, string>,\n): void {\n // Normalize keys to lowercase for case-insensitive matching\n const normalizedTokens: Record<string, string> = {};\n for (const [key, value] of Object.entries(tokens)) {\n const lowerKey = key.toLowerCase();\n const lowerValue = value.toLowerCase();\n\n // Warn if trying to use bare #current to define other color tokens\n // #current represents currentcolor which cannot be used as a base for recursive token resolution\n // Note: #current.5 (with opacity) is allowed since it resolves to a concrete color-mix value\n if (lowerKey.startsWith('#') && lowerValue === '#current') {\n console.warn(\n `Tasty: Using #current to define color token \"${key}\" is not supported. ` +\n `The #current token represents currentcolor which cannot be used as a base for other tokens.`,\n );\n continue; // Skip this token\n }\n\n normalizedTokens[lowerKey] = value;\n }\n // Merge with existing tokens (consistent with how states, units, funcs are handled)\n __globalPredefinedTokens = __globalPredefinedTokens\n ? { ...__globalPredefinedTokens, ...normalizedTokens }\n : normalizedTokens;\n // Clear parser cache since token values affect parsing\n getOrCreateParser().clearCache();\n}\n\n/**\n * Get the current global predefined tokens.\n * Returns null if no tokens are configured.\n */\nexport function getGlobalPredefinedTokens(): Record<string, string> | null {\n return __globalPredefinedTokens;\n}\n\n/**\n * Reset global predefined tokens.\n * Used for testing.\n * @internal\n */\nexport function resetGlobalPredefinedTokens(): void {\n __globalPredefinedTokens = null;\n // Clear parser cache since token availability affects parsing\n getOrCreateParser().clearCache();\n}\n\n/**\n *\n * @param {String} value\n * @param {Number} mode\n * @returns {Object<String,String|Array>}\n */\nexport function parseStyle(value: StyleValue): ProcessedStyle {\n let str: string;\n\n if (typeof value === 'string') {\n str = value;\n } else if (typeof value === 'number') {\n str = String(value);\n } else {\n // boolean, null, undefined, objects etc. → empty string\n str = '';\n }\n\n return getOrCreateParser().process(str);\n}\n\n/**\n * Parse color. Find it value, name and opacity.\n * Optimized to avoid heavy parseStyle calls for simple color patterns.\n */\nexport function parseColor(val: string, ignoreError = false): ParsedColor {\n // Early return for non-strings or empty values\n if (typeof val !== 'string') {\n val = String(val ?? '');\n }\n\n val = val.trim();\n if (!val) return {};\n\n // Fast path: Check if it's a simple color pattern that doesn't need full parsing\n const isSimpleColor = isSimpleColorFast(val);\n\n let firstColor: string;\n if (isSimpleColor) {\n // For simple colors, use the value directly without parsing\n firstColor = val;\n } else {\n const processed = parseStyle(val);\n const extractedColor = processed.groups.find((g) => g.colors.length)\n ?.colors[0];\n\n if (!extractedColor) {\n // Rate-limited warning to avoid spam\n if (!ignoreError && devMode && colorWarningCount < MAX_COLOR_WARNINGS) {\n console.warn('Tasty: unable to parse color:', val);\n colorWarningCount++;\n if (colorWarningCount === MAX_COLOR_WARNINGS) {\n console.warn(\n 'Tasty: color parsing warnings will be suppressed from now on',\n );\n }\n }\n return {};\n }\n\n firstColor = extractedColor;\n }\n\n // Extract color name (if present) from variable pattern using precompiled regex\n let nameMatch = firstColor.match(COLOR_VAR_PATTERN);\n if (!nameMatch) {\n nameMatch = firstColor.match(COLOR_VAR_COMPONENTS_PATTERN);\n }\n\n let opacity: number | undefined;\n if (\n firstColor.startsWith('rgb') ||\n firstColor.startsWith('hsl') ||\n firstColor.startsWith('lch') ||\n firstColor.startsWith('oklch') ||\n firstColor.startsWith('okhsl')\n ) {\n const alphaMatch = firstColor.match(RGB_ALPHA_PATTERN);\n if (alphaMatch) {\n const v = parseFloat(alphaMatch[1]);\n if (!isNaN(v)) opacity = v * 100;\n }\n }\n\n return {\n color: firstColor,\n name: nameMatch ? nameMatch[1] : undefined,\n opacity,\n };\n}\n\nexport function filterMods(mods: string[], allowedMods: string[]): string[] {\n return mods.filter((mod) => allowedMods.includes(mod));\n}\n\n// ============================================================================\n// Style Stringification\n// ============================================================================\n\nconst _innerCache = new WeakMap();\nconst _topLevelCache = new WeakMap<object, string>();\n\nexport function stringifyStyles(styles: unknown): string {\n if (styles == null || typeof styles !== 'object') return '';\n\n const cached = _topLevelCache.get(styles as object);\n if (cached !== undefined) return cached;\n\n const obj = styles as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const v = obj[k];\n if (v === undefined || typeof v === 'function' || typeof v === 'symbol')\n continue;\n\n const c0 = k.charCodeAt(0);\n const needsInnerSort =\n ((c0 >= 65 && c0 <= 90) || c0 === 38) &&\n v &&\n typeof v === 'object' &&\n !Array.isArray(v);\n\n let sv: string;\n if (needsInnerSort) {\n sv = _innerCache.get(v);\n if (sv === undefined) {\n const innerObj = v as Record<string, unknown>;\n const innerKeys = Object.keys(innerObj).sort();\n const innerParts: string[] = [];\n for (const ik of innerKeys) {\n const ivs = JSON.stringify(innerObj[ik]);\n if (ivs !== undefined)\n innerParts.push(JSON.stringify(ik) + ':' + ivs);\n }\n sv = '{' + innerParts.join(',') + '}';\n _innerCache.set(v, sv);\n }\n } else {\n sv = JSON.stringify(v);\n }\n parts.push(JSON.stringify(k) + ':' + sv);\n }\n const result = '{' + parts.join(',') + '}';\n _topLevelCache.set(styles as object, result);\n return result;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"styles.js","names":[],"sources":["../../src/utils/styles.ts"],"sourcesContent":["import { StyleParser } from '../parser/parser';\nimport { okhslFunc } from '../plugins/okhsl-plugin';\n\nimport type { ProcessedStyle, StyleDetails } from '../parser/types';\n\nimport { getNamedColorHex } from './color-math';\n\nexport {\n getNamedColorHex,\n getRgbValuesFromRgbaString,\n hexToRgb,\n strToRgb,\n} from './color-math';\n\nexport type StyleValue<T = string> = T | boolean | number | null | undefined;\n\n/**\n * Normalize a color token value.\n * - Boolean `true` is converted to `'transparent'`\n * - Boolean `false` returns `null` (signals the token should be skipped)\n * - Other values are returned as-is\n *\n * @param value - The raw token value\n * @returns Normalized value or null if the token should be skipped\n */\nexport function normalizeColorTokenValue<T>(\n value: T | boolean,\n): T | 'transparent' | null {\n if (value === true) {\n return 'transparent';\n }\n if (value === false) {\n return null;\n }\n return value as T;\n}\n\nexport type StyleValueStateMap<T = string> = Record<\n string,\n StyleValue<T> | '@inherit'\n>;\n\n/**\n * Combined type for style values that can be either a direct value or a state map.\n * Use this for component props that accept style values.\n */\nexport type StylePropValue<T = string> = StyleValue<T> | StyleValueStateMap<T>;\n\nexport type CSSMap = { $?: string | string[] } & Record<\n string,\n string | string[]\n>;\n\nexport type StyleHandlerResult = CSSMap | CSSMap[] | void;\n\nexport type RawStyleHandler = (value: StyleValueStateMap) => StyleHandlerResult;\n\nexport type StyleHandler = RawStyleHandler & {\n __lookupStyles: string[];\n};\n\n/**\n * Handler definition forms for configure() and plugins.\n * - Function only: lookup styles inferred from key name\n * - Single property tuple: ['styleName', handler]\n * - Multi-property tuple: [['style1', 'style2'], handler]\n */\nexport type StyleHandlerDefinition =\n | RawStyleHandler\n | [string, RawStyleHandler]\n | [string[], RawStyleHandler];\n\nexport interface ParsedColor {\n color?: string;\n name?: string;\n opacity?: number;\n}\n\nexport type StyleMap = Record<string, StyleValue | StyleValueStateMap>;\n\nconst devMode = process.env.NODE_ENV !== 'production';\n\n// Precompiled regex patterns for parseColor optimization\n// Match var(--name-color...) and extract the name, regardless of fallbacks\nconst COLOR_VAR_PATTERN = /var\\(--([a-z0-9-]+)-color/;\nconst COLOR_VAR_COMPONENTS_PATTERN =\n /var\\(--([a-z0-9-]+)-color-(?:rgb|hsl|oklch)/;\nconst RGB_ALPHA_PATTERN = /\\/\\s*([0-9.]+)\\)/;\nconst RE_HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;\nconst RE_VAR_COLOR = /^var\\(--[a-z0-9-]+-color/;\n\nfunction isSimpleColorFast(val: string): boolean {\n const c0 = val.charCodeAt(0);\n\n switch (c0) {\n case 35: // '#'\n return RE_HEX_COLOR.test(val);\n case 114: // 'r'\n return val.charCodeAt(1) === 103 && val.charCodeAt(2) === 98; // 'rgb'\n case 104: // 'h'\n return val.charCodeAt(1) === 115 && val.charCodeAt(2) === 108; // 'hsl'\n case 108: // 'l'\n return val.charCodeAt(1) === 99 && val.charCodeAt(2) === 104; // 'lch'\n case 111: // 'o'\n return val.startsWith('oklch(') || val.startsWith('okhsl(');\n case 118: // 'v'\n return RE_VAR_COLOR.test(val);\n case 99: // 'c'\n return val === 'currentColor' || val === 'currentcolor';\n case 116: // 't'\n return val === 'transparent';\n default:\n return getNamedColorHex().has(val.toLowerCase());\n }\n}\n\n// Rate limiting for dev warnings to avoid spam\nlet colorWarningCount = 0;\nconst MAX_COLOR_WARNINGS = 10;\n\nexport const CUSTOM_UNITS = {\n r: '6px',\n cr: '10px',\n bw: '1px',\n ow: '3px',\n x: '8px',\n sf: function sf(num: number) {\n return `minmax(0, ${num}fr)`;\n },\n};\n\nexport const DIRECTIONS = ['top', 'right', 'bottom', 'left'];\n\n// Lazy-initialized to break the circular dependency:\n// parser.ts → classify.ts → utils/styles.ts → parser.ts\nlet __tastyParser: StyleParser | null = null;\n\nfunction getOrCreateParser(): StyleParser {\n if (!__tastyParser) {\n __tastyParser = new StyleParser({ units: CUSTOM_UNITS });\n __tastyParser.setFuncs(__tastyFuncs);\n }\n return __tastyParser;\n}\n\n// Registry for user-provided custom functions that the parser can call.\n// It is updated through the `customFunc` helper exported below.\n// okhsl is registered as a built-in function so it works regardless of\n// tree-shaking or module initialization order.\nconst __tastyFuncs: Record<string, (groups: StyleDetails[]) => string> = {\n okhsl: okhslFunc,\n};\n\nexport function customFunc(\n name: string,\n fn: (groups: StyleDetails[]) => string,\n) {\n __tastyFuncs[name] = fn;\n getOrCreateParser().setFuncs(__tastyFuncs);\n}\n\n/**\n * Get the global StyleParser instance.\n * Used by configure() to apply parser configuration.\n */\nexport function getGlobalParser(): StyleParser {\n return getOrCreateParser();\n}\n\n/**\n * Get the current custom functions registry.\n * Used by configure() to merge with new functions.\n */\nexport function getGlobalFuncs(): Record<\n string,\n (groups: StyleDetails[]) => string\n> {\n return __tastyFuncs;\n}\n\n// ============================================================================\n// Global Predefined Tokens\n// ============================================================================\n\n/**\n * Storage for predefined tokens that are replaced during style parsing.\n * Keys are token names (with $ or # prefix), values are pre-processed CSS values.\n */\nlet __globalPredefinedTokens: Record<string, string> | null = null;\n\n/**\n * Set global predefined tokens.\n * Called from configure() after processing token values.\n * Merges with existing tokens (new tokens override existing ones with same key).\n * Keys are normalized to lowercase (parser lowercases input before classification).\n * @internal\n */\nexport function setGlobalPredefinedTokens(\n tokens: Record<string, string>,\n): void {\n // Normalize keys to lowercase for case-insensitive matching\n const normalizedTokens: Record<string, string> = {};\n for (const [key, value] of Object.entries(tokens)) {\n const lowerKey = key.toLowerCase();\n const lowerValue = value.toLowerCase();\n\n // Warn if trying to use bare #current to define other color tokens\n // #current represents currentcolor which cannot be used as a base for recursive token resolution\n // Note: #current.5 (with opacity) is allowed since it resolves to a concrete color-mix value\n if (lowerKey.startsWith('#') && lowerValue === '#current') {\n console.warn(\n `Tasty: Using #current to define color token \"${key}\" is not supported. ` +\n `The #current token represents currentcolor which cannot be used as a base for other tokens.`,\n );\n continue; // Skip this token\n }\n\n normalizedTokens[lowerKey] = value;\n }\n // Merge with existing tokens (consistent with how states, units, funcs are handled)\n __globalPredefinedTokens = __globalPredefinedTokens\n ? { ...__globalPredefinedTokens, ...normalizedTokens }\n : normalizedTokens;\n // Clear parser cache since token values affect parsing\n getOrCreateParser().clearCache();\n}\n\n/**\n * Get the current global predefined tokens.\n * Returns null if no tokens are configured.\n */\nexport function getGlobalPredefinedTokens(): Record<string, string> | null {\n return __globalPredefinedTokens;\n}\n\n/**\n * Reset global predefined tokens.\n * Used for testing.\n * @internal\n */\nexport function resetGlobalPredefinedTokens(): void {\n __globalPredefinedTokens = null;\n // Clear parser cache since token availability affects parsing\n getOrCreateParser().clearCache();\n}\n\n/**\n *\n * @param {String} value\n * @param {Number} mode\n * @returns {Object<String,String|Array>}\n */\nexport function parseStyle(value: StyleValue): ProcessedStyle {\n let str: string;\n\n if (typeof value === 'string') {\n str = value;\n } else if (typeof value === 'number') {\n str = String(value);\n } else {\n // boolean, null, undefined, objects etc. → empty string\n str = '';\n }\n\n return getOrCreateParser().process(str);\n}\n\n/**\n * Parse color. Find it value, name and opacity.\n * Optimized to avoid heavy parseStyle calls for simple color patterns.\n */\nexport function parseColor(val: string, ignoreError = false): ParsedColor {\n // Early return for non-strings or empty values\n if (typeof val !== 'string') {\n val = String(val ?? '');\n }\n\n val = val.trim();\n if (!val) return {};\n\n // Fast path: Check if it's a simple color pattern that doesn't need full parsing\n const isSimpleColor = isSimpleColorFast(val);\n\n let firstColor: string;\n if (isSimpleColor) {\n // For simple colors, use the value directly without parsing\n firstColor = val;\n } else {\n const processed = parseStyle(val);\n const extractedColor = processed.groups.find((g) => g.colors.length)\n ?.colors[0];\n\n if (!extractedColor) {\n // Rate-limited warning to avoid spam\n if (!ignoreError && devMode && colorWarningCount < MAX_COLOR_WARNINGS) {\n console.warn('Tasty: unable to parse color:', val);\n colorWarningCount++;\n if (colorWarningCount === MAX_COLOR_WARNINGS) {\n console.warn(\n 'Tasty: color parsing warnings will be suppressed from now on',\n );\n }\n }\n return {};\n }\n\n firstColor = extractedColor;\n }\n\n // Extract color name (if present) from variable pattern using precompiled regex\n let nameMatch = firstColor.match(COLOR_VAR_PATTERN);\n if (!nameMatch) {\n nameMatch = firstColor.match(COLOR_VAR_COMPONENTS_PATTERN);\n }\n\n let opacity: number | undefined;\n if (\n firstColor.startsWith('rgb') ||\n firstColor.startsWith('hsl') ||\n firstColor.startsWith('lch') ||\n firstColor.startsWith('oklch') ||\n firstColor.startsWith('okhsl')\n ) {\n const alphaMatch = firstColor.match(RGB_ALPHA_PATTERN);\n if (alphaMatch) {\n const v = parseFloat(alphaMatch[1]);\n if (!isNaN(v)) opacity = v * 100;\n }\n }\n\n return {\n color: firstColor,\n name: nameMatch ? nameMatch[1] : undefined,\n opacity,\n };\n}\n\nexport function filterMods(mods: string[], allowedMods: string[]): string[] {\n return mods.filter((mod) => allowedMods.includes(mod));\n}\n\n// ============================================================================\n// Style Stringification\n// ============================================================================\n\nconst _innerCache = new WeakMap();\nconst _topLevelCache = new WeakMap<object, string>();\n\nexport function stringifyStyles(styles: unknown): string {\n if (styles == null || typeof styles !== 'object') return '';\n\n const cached = _topLevelCache.get(styles as object);\n if (cached !== undefined) return cached;\n\n const obj = styles as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n const parts: string[] = [];\n for (const k of keys) {\n const v = obj[k];\n if (v === undefined || typeof v === 'function' || typeof v === 'symbol')\n continue;\n\n const c0 = k.charCodeAt(0);\n const needsInnerSort =\n ((c0 >= 65 && c0 <= 90) || c0 === 38) &&\n v &&\n typeof v === 'object' &&\n !Array.isArray(v);\n\n let sv: string;\n if (needsInnerSort) {\n sv = _innerCache.get(v);\n if (sv === undefined) {\n const innerObj = v as Record<string, unknown>;\n const innerKeys = Object.keys(innerObj).sort();\n const innerParts: string[] = [];\n for (const ik of innerKeys) {\n const ivs = JSON.stringify(innerObj[ik]);\n if (ivs !== undefined)\n innerParts.push(JSON.stringify(ik) + ':' + ivs);\n }\n sv = '{' + innerParts.join(',') + '}';\n _innerCache.set(v, sv);\n }\n } else {\n sv = JSON.stringify(v);\n }\n parts.push(JSON.stringify(k) + ':' + sv);\n }\n const result = '{' + parts.join(',') + '}';\n _topLevelCache.set(styles as object, result);\n return result;\n}\n"],"mappings":"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"}
|
package/docs/dsl.md
CHANGED
|
@@ -119,7 +119,6 @@ color: '(#primary, #secondary)', // Fallback syntax
|
|
|
119
119
|
| `cr` | Card border radius | `1cr` | `var(--card-radius)` |
|
|
120
120
|
| `bw` | Border width | `2bw` | `calc(var(--border-width) * 2)` |
|
|
121
121
|
| `ow` | Outline width | `1ow` | `var(--outline-width)` |
|
|
122
|
-
| `lh` | Line height | `1lh` | `var(--line-height)` |
|
|
123
122
|
| `sf` | Stable fraction | `1sf` | `minmax(0, 1fr)` |
|
|
124
123
|
|
|
125
124
|
You can register additional custom units via [`configure()`](configuration.md#options).
|