@tenphi/tasty 0.0.0-snapshot.f4903bd → 0.0.0-snapshot.f5861ed
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/config.js.map +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/pipeline/index.js +6 -1
- package/dist/pipeline/index.js.map +1 -1
- package/dist/styles/preset.d.ts +5 -0
- package/dist/styles/preset.js +25 -10
- package/dist/styles/preset.js.map +1 -1
- package/dist/styles/types.d.ts +3 -1
- package/dist/tasty.d.ts +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/dist/utils/typography.js +8 -0
- package/dist/utils/typography.js.map +1 -1
- package/docs/dsl.md +0 -1
- package/docs/styles.md +9 -6
- package/package.json +2 -2
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/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["/**\n * Tasty Configuration Module\n *\n * Centralizes all tasty configuration, including:\n * - Style injector settings (nonce, cleanup thresholds, etc.)\n * - Global predefined states for advanced state mapping\n * - stylesGenerated flag that locks configuration after first style generation\n *\n * Configuration must be done BEFORE any styles are generated.\n * After the first `inject()` call, configuration is locked and attempts to\n * reconfigure will emit a warning and be ignored.\n */\n\nimport { StyleInjector } from './injector/injector';\nimport { clearPipelineCache, isSelector, renderStyles } from './pipeline';\nimport { setGlobalPredefinedStates } from './states';\nimport {\n normalizeHandlerDefinition,\n registerHandler,\n resetHandlers,\n} from './styles/predefined';\nimport { resetColorSpace, setColorSpace } from './utils/color-space';\nimport { isDevEnv } from './utils/is-dev-env';\nimport {\n CUSTOM_UNITS,\n getGlobalFuncs,\n getGlobalParser,\n normalizeColorTokenValue,\n resetGlobalPredefinedTokens,\n setGlobalPredefinedTokens,\n} from './utils/styles';\n\nimport type { ColorSpace } from './utils/color-space';\n\nimport type {\n CounterStyleDescriptors,\n FontFaceInput,\n KeyframesSteps,\n PropertyDefinition,\n} from './injector/types';\nimport type { StyleDetails, UnitHandler } from './parser/types';\nimport type { StyleResult } from './pipeline';\nimport type { TastyPlugin } from './plugins/types';\nimport type { RecipeStyles, ConfigTokens } from './styles/types';\nimport type { StyleHandlerDefinition } from './utils/styles';\n\n/**\n * Configuration options for the Tasty style system\n */\nexport interface TastyConfig {\n /** CSP nonce for style elements */\n nonce?: string;\n /** Maximum rules per stylesheet (default: 8192) */\n maxRulesPerSheet?: number;\n /** Threshold for bulk cleanup of unused styles (default: 500) */\n unusedStylesThreshold?: number;\n /** Delay before bulk cleanup in ms, ignored if idleCleanup is true (default: 5000) */\n bulkCleanupDelay?: number;\n /** Use requestIdleCallback for cleanup when available (default: true) */\n idleCleanup?: boolean;\n /** Force text injection mode, auto-detected in test environments (default: auto) */\n forceTextInjection?: boolean;\n /** Enable development mode features: performance metrics and debug info (default: auto) */\n devMode?: boolean;\n /**\n * Ratio of unused styles to delete per bulk cleanup run (0..1).\n * Defaults to 0.5 (oldest half) to reduce risk of removing styles\n * that may be restored shortly after being marked unused.\n */\n bulkCleanupBatchRatio?: number;\n /**\n * Minimum age (in ms) a style must remain unused before eligible for deletion.\n * Helps avoid races during rapid mount/unmount cycles. Default: 10000ms.\n */\n unusedStylesMinAgeMs?: number;\n /**\n * Global predefined states for advanced state mapping.\n * These are state aliases that can be used in any component.\n * Example: { '@mobile': '@media(w < 920px)', '@dark': '@root(theme=dark)' }\n */\n states?: Record<string, string>;\n /**\n * Parser LRU cache size (default: 1000).\n * Larger values improve performance for apps with many unique style values.\n */\n parserCacheSize?: number;\n /**\n * Custom units for the style parser (merged with built-in units).\n * Units transform numeric values like `2x` → `calc(2 * var(--gap))`.\n * @example { em: 'em', vw: 'vw', custom: (n) => `${n * 10}px` }\n */\n units?: Record<string, string | UnitHandler>;\n /**\n * Custom functions for the style parser (merged with existing).\n * Functions process parsed style groups and return CSS values.\n * @example { myFunc: (groups) => groups.map(g => g.output).join(' ') }\n */\n funcs?: Record<string, (groups: StyleDetails[]) => string>;\n /**\n * Color space used for decomposed color token companion variables.\n * Controls the CSS function and suffix for alpha composition.\n *\n * - `'rgb'` — suffix `-rgb`, e.g. `rgb(var(--name-color-rgb) / .5)`\n * - `'hsl'` — suffix `-hsl`, e.g. `hsl(var(--name-color-hsl) / .5)`\n * - `'oklch'` — suffix `-oklch`, e.g. `oklch(var(--name-color-oklch) / .5)`\n *\n * @default 'oklch'\n */\n colorSpace?: ColorSpace;\n /**\n * Automatically infer and register CSS @property declarations\n * from custom property values found in styles, keyframes, and global config.\n * Covers all types: \\<color\\>, \\<number\\>, \\<length\\>, \\<angle\\>, \\<percentage\\>, \\<time\\>.\n * When false, only explicitly declared @properties are registered.\n * @default true\n */\n autoPropertyTypes?: boolean;\n /**\n * Plugins that extend tasty with custom functions, units, or states.\n * Plugins are processed in order, with later plugins overriding earlier ones.\n * @example\n * ```ts\n * import { okhslPlugin } from '@tenphi/tasty';\n *\n * configure({\n * plugins: [okhslPlugin()],\n * });\n * ```\n */\n plugins?: TastyPlugin[];\n /**\n * Global keyframes definitions that can be referenced by animation names in styles.\n * Keys are animation names, values are keyframes step definitions.\n * Keyframes are only injected when actually used in styles.\n * @example\n * ```ts\n * configure({\n * keyframes: {\n * fadeIn: { from: { opacity: 0 }, to: { opacity: 1 } },\n * pulse: { '0%, 100%': { transform: 'scale(1)' }, '50%': { transform: 'scale(1.05)' } },\n * },\n * });\n * ```\n */\n keyframes?: Record<string, KeyframesSteps>;\n /**\n * Global CSS @property definitions for custom properties.\n * Keys use tasty token syntax ($name for properties, #name for colors).\n * Properties are only injected when the component using them is rendered.\n *\n * For color tokens (#name), `syntax: '<color>'` is auto-set and\n * `initialValue` defaults to `'transparent'` if not specified.\n *\n * @example\n * ```ts\n * configure({\n * properties: {\n * '$rotation': { syntax: '<angle>', initialValue: '0deg' },\n * '$scale': { syntax: '<number>', inherits: false, initialValue: 1 },\n * '#accent': { initialValue: 'purple' }, // syntax: '<color>' auto-set\n * },\n * });\n *\n * // Now use in styles - properties are registered when component renders:\n * const Spinner = tasty({\n * styles: {\n * transform: 'rotate($rotation)',\n * transition: '$$rotation 0.3s', // outputs: --rotation 0.3s\n * },\n * });\n * ```\n */\n properties?: Record<string, PropertyDefinition>;\n /**\n * Global @font-face definitions.\n * Keys are font-family names, values are descriptors or arrays of descriptors\n * (for multiple weights/styles of the same family).\n * Injected eagerly when styles are first generated.\n * @example\n * ```ts\n * configure({\n * fontFace: {\n * 'Brand Sans': [\n * { src: 'url(\"/fonts/brand-regular.woff2\") format(\"woff2\")', fontWeight: 400, fontDisplay: 'swap' },\n * { src: 'url(\"/fonts/brand-bold.woff2\") format(\"woff2\")', fontWeight: 700, fontDisplay: 'swap' },\n * ],\n * Icons: { src: 'url(\"/fonts/icons.woff2\") format(\"woff2\")', fontDisplay: 'block' },\n * },\n * });\n * ```\n */\n fontFace?: Record<string, FontFaceInput>;\n /**\n * Global @counter-style definitions.\n * Keys are counter-style names, values are descriptor objects.\n * Injected eagerly when styles are first generated.\n * @example\n * ```ts\n * configure({\n * counterStyle: {\n * thumbs: { system: 'cyclic', symbols: '\"👍\"', suffix: '\" \"' },\n * },\n * });\n * ```\n */\n counterStyle?: Record<string, CounterStyleDescriptors>;\n /**\n * Custom style handlers that transform style properties into CSS declarations.\n * Handlers replace built-in handlers for the same style name.\n * @example\n * ```ts\n * import { styleHandlers } from '@tenphi/tasty';\n *\n * configure({\n * handlers: {\n * // Override fill with custom behavior\n * fill: ({ fill }) => {\n * if (fill?.startsWith('gradient:')) {\n * return { background: fill.slice(9) };\n * }\n * return styleHandlers.fill({ fill });\n * },\n * // Add new custom style\n * elevation: ({ elevation }) => {\n * const level = parseInt(elevation) || 1;\n * return {\n * 'box-shadow': `0 ${level * 2}px ${level * 4}px rgba(0,0,0,0.1)`,\n * 'z-index': String(level * 100),\n * };\n * },\n * },\n * });\n * ```\n */\n handlers?: Record<string, StyleHandlerDefinition>;\n /**\n * Design tokens injected as CSS custom properties on `:root`.\n * Values are parsed through the Tasty DSL. Supports state maps\n * for responsive/theme-aware tokens.\n *\n * - `$name` keys become `--name` CSS custom properties\n * - `#name` keys become `--name-color` and `--name-color-{colorSpace}` properties\n *\n * Tokens are injected once when the first style is rendered.\n *\n * @example\n * ```ts\n * configure({\n * tokens: {\n * '$gap': '4px',\n * '#primary': {\n * '': '#purple',\n * '@dark': '#light-purple',\n * },\n * },\n * });\n * ```\n */\n tokens?: ConfigTokens;\n /**\n * Predefined tokens that are replaced during style parsing (parse-time substitution).\n * Use `$name` for custom properties and `#name` for color tokens.\n * Values are substituted inline before CSS generation, unlike `tokens` which\n * inject CSS custom properties on `:root`.\n *\n * For color tokens (#name), boolean `true` is converted to `transparent`.\n *\n * @example\n * ```ts\n * configure({\n * replaceTokens: {\n * $spacing: '2x',\n * '#accent': '#purple',\n * '#overlay': true, // → transparent\n * },\n * });\n *\n * // Now use in styles - tokens are replaced at parse time:\n * const Card = tasty({\n * styles: {\n * padding: '$spacing', // → calc(2 * var(--gap))\n * fill: '#accent', // → var(--purple-color)\n * },\n * });\n * ```\n */\n replaceTokens?: Record<`$${string}`, string | number | boolean> &\n Record<`#${string}`, string | number | boolean>;\n /**\n * Predefined style recipes -- named style bundles that can be applied via `recipe` style property.\n * Recipe values are flat tasty styles (no sub-element keys). They may contain base styles,\n * tokens (`$name`/`#name` definitions), local states, `@keyframes`, and `@properties`.\n *\n * Components reference recipes via: `recipe: 'name1 name2'` in their styles.\n * Use `/` to separate base recipes from post recipes: `recipe: 'base1 base2 / post1'`.\n * Use `none` to skip base recipes: `recipe: 'none / post1'`.\n * Resolution order: `base_recipes → component styles → post_recipes`.\n *\n * Recipes cannot reference other recipes.\n *\n * @example\n * ```ts\n * configure({\n * recipes: {\n * card: { padding: '4x', fill: '#surface', radius: '1r', border: true },\n * elevated: { shadow: '2x 2x 4x #shadow' },\n * },\n * });\n *\n * // Usage in styles:\n * const Card = tasty({\n * styles: {\n * recipe: 'card elevated',\n * color: '#text', // Overrides recipe values\n * },\n * });\n * ```\n */\n recipes?: Record<string, RecipeStyles>;\n}\n\n// Warnings tracking to avoid duplicates\nconst emittedWarnings = new Set<string>();\n\nconst devMode = isDevEnv();\n\n/**\n * Emit a warning only once\n */\nfunction warnOnce(key: string, message: string): void {\n if (devMode && !emittedWarnings.has(key)) {\n emittedWarnings.add(key);\n console.warn(message);\n }\n}\n\n// ============================================================================\n// Configuration State\n// ============================================================================\n\n// Track whether styles have been generated (locks configuration)\nlet stylesGenerated = false;\n\n// Current configuration (null until first configure() or auto-configured on first use)\nlet currentConfig: TastyConfig | null = null;\n\n// Global keyframes storage (null = no keyframes configured, empty object checked via hasGlobalKeyframes)\nlet globalKeyframes: Record<string, KeyframesSteps> | null = null;\n\n// Global font-face storage (null = no font faces configured)\nlet globalFontFace: Record<string, FontFaceInput> | null = null;\n\n// Global counter-style storage (null = no counter styles configured)\nlet globalCounterStyle: Record<string, CounterStyleDescriptors> | null = null;\n\n// Global properties storage (null = no properties configured)\nlet globalProperties: Record<string, PropertyDefinition> | null = null;\n\n// Global recipes storage (null = no recipes configured)\nlet globalRecipes: Record<string, RecipeStyles> | null = null;\n\n// Global token styles storage (injected as :root CSS custom properties)\nlet globalConfigTokens: ConfigTokens | null = null;\n\n/**\n * Internal properties required by tasty core features.\n * These are always injected when styles are first generated.\n * Keys use tasty token syntax (#name for colors, $name for other properties).\n *\n * For properties with CSS @property-compatible types (length, time, number, color),\n * an `initialValue` is provided so the property works even without a project-level token.\n */\nexport const INTERNAL_PROPERTIES: Record<string, PropertyDefinition> = {\n // Used by dual-fill feature to enable CSS transitions on the second fill color\n '#tasty-second-fill': {\n inherits: false,\n initialValue: 'transparent',\n },\n // Current color context variable (set by the color style handler).\n '#current': {\n inherits: true,\n initialValue: 'transparent',\n },\n // White and black are fundamental colors that need explicit initial values.\n '#white': {\n inherits: true,\n initialValue: 'rgb(255 255 255)',\n },\n '#black': {\n inherits: true,\n initialValue: 'rgb(0 0 0)',\n },\n\n // ---- Core design tokens used by style handlers ----\n // These provide sensible defaults so tasty works standalone without a design system.\n // Consuming projects (e.g. uikit) override these by defining tokens on :root.\n\n $gap: {\n syntax: '<length>',\n inherits: true,\n initialValue: '4px',\n },\n $radius: {\n syntax: '<length>',\n inherits: true,\n initialValue: '6px',\n },\n '$border-width': {\n syntax: '<length>',\n inherits: true,\n initialValue: '1px',\n },\n '$outline-width': {\n syntax: '<length>',\n inherits: true,\n initialValue: '3px',\n },\n $transition: {\n syntax: '<time>',\n inherits: true,\n initialValue: '80ms',\n },\n // Used by radius.ts for `radius=\"leaf\"` modifier\n '$sharp-radius': {\n syntax: '<length>',\n inherits: true,\n initialValue: '0px',\n },\n // Used by preset.ts for `preset=\"... strong\"`\n '$bold-font-weight': {\n syntax: '<number>',\n inherits: true,\n initialValue: '700',\n },\n // Used by preset.ts as fallback font stacks\n '$font-sans-fallback': {\n syntax: '*',\n inherits: true,\n initialValue:\n 'system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\", sans-serif',\n },\n '$font-mono-fallback': {\n syntax: '*',\n inherits: true,\n initialValue:\n 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace',\n },\n};\n\n// Global injector instance key\nconst GLOBAL_INJECTOR_KEY = '__TASTY_GLOBAL_INJECTOR__';\n\ninterface TastyGlobalStorage {\n [GLOBAL_INJECTOR_KEY]?: StyleInjector;\n}\n\ndeclare global {\n interface Window {\n [GLOBAL_INJECTOR_KEY]?: StyleInjector;\n }\n\n var __TASTY_GLOBAL_INJECTOR__: StyleInjector | undefined;\n}\n\n/**\n * Detect if we're running in a test environment\n */\nexport function isTestEnvironment(): boolean {\n // Check Node.js environment\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'test') {\n return true;\n }\n\n // Check for test runner globals (safely)\n if (typeof global !== 'undefined') {\n const g = global as unknown as Record<string, unknown>;\n if (g.vi || g.jest || g.expect || g.describe || g.it) {\n return true;\n }\n }\n\n // Check for jsdom environment (common in tests)\n if (\n typeof window !== 'undefined' &&\n window.navigator?.userAgent?.includes('jsdom')\n ) {\n return true;\n }\n\n // Check for other test runners\n if (typeof globalThis !== 'undefined') {\n const gt = globalThis as unknown as Record<string, unknown>;\n if (gt.vitest || gt.mocha) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Create default configuration with optional test environment detection\n */\nfunction createDefaultConfig(isTest?: boolean): TastyConfig {\n return {\n maxRulesPerSheet: 8192,\n unusedStylesThreshold: 500,\n bulkCleanupDelay: 5000,\n idleCleanup: true,\n forceTextInjection: isTest ?? false,\n devMode: isDevEnv(),\n bulkCleanupBatchRatio: 0.5,\n unusedStylesMinAgeMs: 10000,\n };\n}\n\n// ============================================================================\n// stylesGenerated Flag Management\n// ============================================================================\n\n/**\n * Mark that styles have been generated (called by injector on first inject)\n * This locks the configuration - no further changes allowed.\n * Also injects internal and global properties.\n */\nexport function markStylesGenerated(): void {\n if (stylesGenerated) return; // Already marked, skip\n\n stylesGenerated = true;\n\n const injector = getGlobalInjector();\n\n // Inject internal properties required by tasty core features\n for (const [token, definition] of Object.entries(INTERNAL_PROPERTIES)) {\n injector.property(token, definition);\n }\n\n // Inject global properties if any were configured\n // Properties are permanent and only need to be injected once\n if (globalProperties && Object.keys(globalProperties).length > 0) {\n for (const [token, definition] of Object.entries(globalProperties)) {\n injector.property(token, definition);\n }\n }\n\n // Inject global @font-face rules (eagerly — fonts should be available before render)\n if (globalFontFace && Object.keys(globalFontFace).length > 0) {\n for (const [family, input] of Object.entries(globalFontFace)) {\n const descriptors = Array.isArray(input) ? input : [input];\n for (const desc of descriptors) {\n injector.fontFace(family, desc);\n }\n }\n }\n\n // Inject global @counter-style rules (eagerly)\n if (globalCounterStyle && Object.keys(globalCounterStyle).length > 0) {\n for (const [name, descriptors] of Object.entries(globalCounterStyle)) {\n injector.counterStyle(name, descriptors);\n }\n }\n\n // Inject configured tokens as :root CSS custom properties\n if (globalConfigTokens && Object.keys(globalConfigTokens).length > 0) {\n const tokenRules = renderStyles(\n globalConfigTokens,\n ':root',\n ) as StyleResult[];\n if (tokenRules.length > 0) {\n injector.injectGlobal(tokenRules);\n }\n }\n}\n\n/**\n * Check if styles have been generated (configuration is locked)\n */\nexport function hasStylesGenerated(): boolean {\n return stylesGenerated;\n}\n\n/**\n * Reset styles generated flag (for testing only)\n */\nexport function resetStylesGenerated(): void {\n stylesGenerated = false;\n emittedWarnings.clear();\n}\n\n// ============================================================================\n// Global Keyframes Management\n// ============================================================================\n\nlet _hasGlobalKeyframes = false;\n\n/**\n * Check if any global keyframes are configured.\n * Uses a pre-computed flag to avoid Object.keys() allocation on every call.\n */\nexport function hasGlobalKeyframes(): boolean {\n return _hasGlobalKeyframes;\n}\n\n/**\n * Get global keyframes configuration.\n * Returns null if no keyframes configured (fast path for zero-overhead).\n */\nexport function getGlobalKeyframes(): Record<string, KeyframesSteps> | null {\n return globalKeyframes;\n}\n\n/**\n * Set global keyframes (called from configure).\n * Internal use only.\n */\nfunction setGlobalKeyframes(keyframes: Record<string, KeyframesSteps>): void {\n if (stylesGenerated) {\n warnOnce(\n 'keyframes-after-styles',\n `[Tasty] Cannot update keyframes after styles have been generated.\\n` +\n `The new keyframes will be ignored.`,\n );\n return;\n }\n globalKeyframes = keyframes;\n _hasGlobalKeyframes = Object.keys(keyframes).length > 0;\n}\n\n// ============================================================================\n// Global Properties Management\n// ============================================================================\n\n/**\n * Check if any global properties are configured.\n * Fast path: returns false if no properties were ever set.\n */\nexport function hasGlobalProperties(): boolean {\n return globalProperties !== null && Object.keys(globalProperties).length > 0;\n}\n\n/**\n * Get global properties configuration.\n * Returns null if no properties configured (fast path for zero-overhead).\n */\nexport function getGlobalProperties(): Record<\n string,\n PropertyDefinition\n> | null {\n return globalProperties;\n}\n\n/**\n * Set global properties (called from configure).\n * Internal use only.\n */\nfunction setGlobalProperties(\n properties: Record<string, PropertyDefinition>,\n): void {\n if (stylesGenerated) {\n warnOnce(\n 'properties-after-styles',\n `[Tasty] Cannot update properties after styles have been generated.\\n` +\n `The new properties will be ignored.`,\n );\n return;\n }\n globalProperties = properties;\n}\n\n// ============================================================================\n// Global Font Face Management\n// ============================================================================\n\n/**\n * Get global font-face configuration.\n * Returns null if no font faces configured.\n */\nexport function getGlobalFontFace(): Record<string, FontFaceInput> | null {\n return globalFontFace;\n}\n\n/**\n * Set global font faces (called from configure).\n * Internal use only.\n */\nfunction setGlobalFontFace(fontFace: Record<string, FontFaceInput>): void {\n if (stylesGenerated) {\n warnOnce(\n 'fontface-after-styles',\n `[Tasty] Cannot update fontFace after styles have been generated.\\n` +\n `The new font faces will be ignored.`,\n );\n return;\n }\n globalFontFace = fontFace;\n}\n\n// ============================================================================\n// Global Counter Style Management\n// ============================================================================\n\n/**\n * Get global counter-style configuration.\n * Returns null if no counter styles configured.\n */\nexport function getGlobalCounterStyle(): Record<\n string,\n CounterStyleDescriptors\n> | null {\n return globalCounterStyle;\n}\n\n/**\n * Set global counter styles (called from configure).\n * Internal use only.\n */\nfunction setGlobalCounterStyle(\n counterStyle: Record<string, CounterStyleDescriptors>,\n): void {\n if (stylesGenerated) {\n warnOnce(\n 'counterstyle-after-styles',\n `[Tasty] Cannot update counterStyle after styles have been generated.\\n` +\n `The new counter styles will be ignored.`,\n );\n return;\n }\n globalCounterStyle = counterStyle;\n}\n\n// ============================================================================\n// Global Recipes Management\n// ============================================================================\n\n/**\n * Check if any global recipes are configured.\n * Fast path: returns false if no recipes were ever set.\n */\nexport function hasGlobalRecipes(): boolean {\n return globalRecipes !== null && Object.keys(globalRecipes).length > 0;\n}\n\n/**\n * Get global recipes configuration.\n * Returns null if no recipes configured (fast path for zero-overhead).\n */\nexport function getGlobalRecipes(): Record<string, RecipeStyles> | null {\n return globalRecipes;\n}\n\n/**\n * Set global recipes (called from configure).\n * Internal use only.\n */\nfunction setGlobalRecipes(recipes: Record<string, RecipeStyles>): void {\n if (stylesGenerated) {\n warnOnce(\n 'recipes-after-styles',\n `[Tasty] Cannot update recipes after styles have been generated.\\n` +\n `The new recipes will be ignored.`,\n );\n return;\n }\n\n // Dev-mode validation\n if (devMode) {\n for (const [name, recipeStyles] of Object.entries(recipes)) {\n if (name === 'none') {\n warnOnce(\n 'recipe-reserved-none',\n `[Tasty] Recipe name \"none\" is reserved. ` +\n `It is used as a keyword meaning \"no base recipes\" ` +\n `(e.g. recipe: 'none / post-recipe'). ` +\n `Choose a different name for your recipe.`,\n );\n }\n\n for (const key of Object.keys(recipeStyles)) {\n if (isSelector(key)) {\n warnOnce(\n `recipe-selector-${name}-${key}`,\n `[Tasty] Recipe \"${name}\" contains sub-element key \"${key}\". ` +\n `Recipes must be flat styles without sub-element keys. ` +\n `Remove the sub-element key from the recipe definition.`,\n );\n }\n if (key === 'recipe') {\n warnOnce(\n `recipe-recursive-${name}`,\n `[Tasty] Recipe \"${name}\" contains a \"recipe\" key. ` +\n `Recipes cannot reference other recipes. ` +\n `Use space-separated names for composition: recipe: 'base elevated'.`,\n );\n }\n }\n }\n }\n\n globalRecipes = recipes;\n}\n\n// ============================================================================\n// Global Token Styles Management\n// ============================================================================\n\n/**\n * Get global token styles for :root injection.\n * Returns null if no tokens configured.\n */\nexport function getGlobalConfigTokens(): ConfigTokens | null {\n return globalConfigTokens;\n}\n\n/**\n * Set global token styles (called from configure).\n * Internal use only.\n */\nfunction setGlobalConfigTokens(styles: ConfigTokens): void {\n if (stylesGenerated) {\n warnOnce(\n 'tokens-after-styles',\n `[Tasty] Cannot update tokens after styles have been generated.\\n` +\n `The new tokens will be ignored.`,\n );\n return;\n }\n globalConfigTokens = globalConfigTokens\n ? { ...globalConfigTokens, ...styles }\n : styles;\n}\n\n/**\n * Check if configuration is locked (styles have been generated)\n */\nexport function isConfigLocked(): boolean {\n return stylesGenerated;\n}\n\n// ============================================================================\n// Configuration API\n// ============================================================================\n\n/**\n * Configure the Tasty style system.\n *\n * Must be called BEFORE any styles are generated (before first render that uses tasty).\n * After styles are generated, configuration is locked and calls to configure() will\n * emit a warning and be ignored.\n *\n * @example\n * ```ts\n * import { configure } from '@tenphi/tasty';\n *\n * // Configure before app renders\n * configure({\n * nonce: 'abc123',\n * states: {\n * '@mobile': '@media(w < 768px)',\n * '@dark': '@root(theme=dark)',\n * },\n * });\n * ```\n */\nexport function configure(config: Partial<TastyConfig> = {}): void {\n if (stylesGenerated) {\n warnOnce(\n 'configure-after-styles',\n `[Tasty] Cannot call configure() after styles have been generated.\\n` +\n `Configuration must be done before the first render. The configuration will be ignored.`,\n );\n return;\n }\n\n // Collect merged values from plugins first, then override with direct config\n let mergedStates: Record<string, string> = {};\n let mergedUnits: Record<string, string | UnitHandler> = {};\n let mergedFuncs: Record<string, (groups: StyleDetails[]) => string> = {};\n let mergedHandlers: Record<string, StyleHandlerDefinition> = {};\n let mergedReplaceTokens: Record<string, string | number | boolean> = {};\n let mergedConfigTokens: ConfigTokens = {} as ConfigTokens;\n let mergedRecipes: Record<string, RecipeStyles> = {};\n\n // Process plugins in order\n if (config.plugins) {\n for (const plugin of config.plugins) {\n if (plugin.states) {\n mergedStates = { ...mergedStates, ...plugin.states };\n }\n if (plugin.units) {\n mergedUnits = { ...mergedUnits, ...plugin.units };\n }\n if (plugin.funcs) {\n mergedFuncs = { ...mergedFuncs, ...plugin.funcs };\n }\n if (plugin.handlers) {\n mergedHandlers = { ...mergedHandlers, ...plugin.handlers };\n }\n if (plugin.replaceTokens) {\n mergedReplaceTokens = {\n ...mergedReplaceTokens,\n ...plugin.replaceTokens,\n };\n }\n if (plugin.tokens) {\n mergedConfigTokens = { ...mergedConfigTokens, ...plugin.tokens };\n }\n if (plugin.recipes) {\n mergedRecipes = { ...mergedRecipes, ...plugin.recipes };\n }\n }\n }\n\n // Direct config overrides plugins\n if (config.states) {\n mergedStates = { ...mergedStates, ...config.states };\n }\n if (config.units) {\n mergedUnits = { ...mergedUnits, ...config.units };\n }\n if (config.funcs) {\n mergedFuncs = { ...mergedFuncs, ...config.funcs };\n }\n if (config.handlers) {\n mergedHandlers = { ...mergedHandlers, ...config.handlers };\n }\n if (config.replaceTokens) {\n mergedReplaceTokens = { ...mergedReplaceTokens, ...config.replaceTokens };\n }\n if (config.tokens) {\n mergedConfigTokens = { ...mergedConfigTokens, ...config.tokens };\n }\n if (config.recipes) {\n mergedRecipes = { ...mergedRecipes, ...config.recipes };\n }\n\n // Warn on tokens/replaceTokens key conflicts\n if (devMode) {\n const tokenKeys = new Set(Object.keys(mergedConfigTokens));\n for (const key of Object.keys(mergedReplaceTokens)) {\n if (tokenKeys.has(key)) {\n warnOnce(\n `token-conflict-${key}`,\n `[Tasty] Token \"${key}\" is defined in both \\`tokens\\` and \\`replaceTokens\\`. ` +\n `\\`replaceTokens\\` performs parse-time substitution, so the \\`tokens\\` ` +\n `CSS custom property will be injected but never used by Tasty styles. ` +\n `Remove it from one of the two.`,\n );\n }\n }\n }\n\n // Handle color space (must be set before any token processing)\n if (config.colorSpace) {\n setColorSpace(config.colorSpace);\n // Color space affects parser output (e.g. #name.5 → oklch(...) vs rgb(...))\n getGlobalParser().clearCache();\n }\n\n // Handle predefined states\n if (Object.keys(mergedStates).length > 0) {\n setGlobalPredefinedStates(mergedStates);\n }\n\n // Handle parser configuration (merge semantics - extend, not replace)\n const parser = getGlobalParser();\n\n if (config.parserCacheSize !== undefined) {\n parser.updateOptions({ cacheSize: config.parserCacheSize });\n }\n\n if (Object.keys(mergedUnits).length > 0) {\n // Merge with existing units\n const currentUnits = parser.getUnits() ?? CUSTOM_UNITS;\n parser.setUnits({ ...currentUnits, ...mergedUnits });\n }\n\n if (Object.keys(mergedFuncs).length > 0) {\n // Merge with existing funcs\n const currentFuncs = getGlobalFuncs();\n const finalFuncs = { ...currentFuncs, ...mergedFuncs };\n parser.setFuncs(finalFuncs);\n // Also update the global registry so customFunc() continues to work\n Object.assign(currentFuncs, mergedFuncs);\n }\n\n // Handle keyframes\n if (config.keyframes) {\n setGlobalKeyframes(config.keyframes);\n }\n\n // Handle properties\n if (config.properties) {\n setGlobalProperties(config.properties);\n }\n\n // Handle font faces\n if (config.fontFace) {\n setGlobalFontFace(config.fontFace);\n }\n\n // Handle counter styles\n if (config.counterStyle) {\n setGlobalCounterStyle(config.counterStyle);\n }\n\n // Handle custom handlers\n if (Object.keys(mergedHandlers).length > 0) {\n for (const [name, definition] of Object.entries(mergedHandlers)) {\n const handler = normalizeHandlerDefinition(name, definition);\n registerHandler(handler);\n }\n }\n\n // Handle replaceTokens (parse-time substitution)\n if (Object.keys(mergedReplaceTokens).length > 0) {\n const processedTokens: Record<string, string> = {};\n for (const [key, value] of Object.entries(mergedReplaceTokens)) {\n if (key.startsWith('#')) {\n const normalized = normalizeColorTokenValue(value);\n if (normalized === null) continue;\n processedTokens[key] = String(normalized);\n } else if (value === false) {\n continue;\n } else {\n processedTokens[key] = String(value);\n }\n }\n setGlobalPredefinedTokens(processedTokens);\n }\n\n // Handle tokens (CSS custom properties on :root)\n if (Object.keys(mergedConfigTokens).length > 0) {\n setGlobalConfigTokens(mergedConfigTokens);\n }\n\n // Handle recipes\n if (Object.keys(mergedRecipes).length > 0) {\n setGlobalRecipes(mergedRecipes);\n }\n\n const {\n states: _states,\n parserCacheSize: _parserCacheSize,\n units: _units,\n funcs: _funcs,\n plugins: _plugins,\n keyframes: _keyframes,\n properties: _properties,\n fontFace: _fontFace,\n counterStyle: _counterStyle,\n handlers: _handlers,\n tokens: _tokens,\n replaceTokens: _replaceTokens,\n recipes: _recipes,\n colorSpace: _colorSpace,\n ...injectorConfig\n } = config;\n\n const fullConfig: TastyConfig = {\n ...createDefaultConfig(),\n ...currentConfig,\n ...injectorConfig,\n };\n\n // Store the config\n currentConfig = fullConfig;\n\n // Create/replace the global injector\n const storage: TastyGlobalStorage =\n typeof window !== 'undefined' ? window : globalThis;\n storage[GLOBAL_INJECTOR_KEY] = new StyleInjector(fullConfig);\n}\n\n/**\n * Get the current configuration.\n * If not configured, returns default configuration.\n */\nexport function getConfig(): TastyConfig {\n if (!currentConfig) {\n currentConfig = createDefaultConfig(isTestEnvironment());\n }\n return currentConfig;\n}\n\n/**\n * Get the global injector instance.\n * Auto-configures with defaults if not already configured.\n */\nexport function getGlobalInjector(): StyleInjector {\n const storage: TastyGlobalStorage =\n typeof window !== 'undefined' ? window : globalThis;\n\n if (!storage[GLOBAL_INJECTOR_KEY]) {\n configure();\n }\n\n return storage[GLOBAL_INJECTOR_KEY]!;\n}\n\n/**\n * Reset configuration (for testing only).\n * Clears the global injector and allows reconfiguration.\n */\nexport function resetConfig(): void {\n stylesGenerated = false;\n currentConfig = null;\n globalKeyframes = null;\n _hasGlobalKeyframes = false;\n globalProperties = null;\n globalFontFace = null;\n globalCounterStyle = null;\n globalRecipes = null;\n globalConfigTokens = null;\n resetGlobalPredefinedTokens();\n resetHandlers();\n resetColorSpace();\n clearPipelineCache();\n emittedWarnings.clear();\n\n const storage: TastyGlobalStorage =\n typeof window !== 'undefined' ? window : globalThis;\n delete storage[GLOBAL_INJECTOR_KEY];\n}\n\n// Re-export TastyConfig as StyleInjectorConfig for backward compatibility\nexport type { TastyConfig as StyleInjectorConfig };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.js","names":[],"sources":["../src/config.ts"],"sourcesContent":["/**\n * Tasty Configuration Module\n *\n * Centralizes all tasty configuration, including:\n * - Style injector settings (nonce, cleanup thresholds, etc.)\n * - Global predefined states for advanced state mapping\n * - stylesGenerated flag that locks configuration after first style generation\n *\n * Configuration must be done BEFORE any styles are generated.\n * After the first `inject()` call, configuration is locked and attempts to\n * reconfigure will emit a warning and be ignored.\n */\n\nimport { StyleInjector } from './injector/injector';\nimport { clearPipelineCache, isSelector, renderStyles } from './pipeline';\nimport { setGlobalPredefinedStates } from './states';\nimport {\n normalizeHandlerDefinition,\n registerHandler,\n resetHandlers,\n} from './styles/predefined';\nimport { resetColorSpace, setColorSpace } from './utils/color-space';\nimport { isDevEnv } from './utils/is-dev-env';\nimport {\n CUSTOM_UNITS,\n getGlobalFuncs,\n getGlobalParser,\n normalizeColorTokenValue,\n resetGlobalPredefinedTokens,\n setGlobalPredefinedTokens,\n} from './utils/styles';\n\nimport type { ColorSpace } from './utils/color-space';\n\nimport type {\n CounterStyleDescriptors,\n FontFaceInput,\n KeyframesSteps,\n PropertyDefinition,\n} from './injector/types';\nimport type { StyleDetails, UnitHandler } from './parser/types';\nimport type { StyleResult } from './pipeline';\nimport type { TastyPlugin } from './plugins/types';\nimport type { RecipeStyles, ConfigTokens } from './styles/types';\nimport type { StyleHandlerDefinition } from './utils/styles';\n\n/**\n * Configuration options for the Tasty style system\n */\nexport interface TastyConfig {\n /** CSP nonce for style elements */\n nonce?: string;\n /** Maximum rules per stylesheet (default: 8192) */\n maxRulesPerSheet?: number;\n /** Threshold for bulk cleanup of unused styles (default: 500) */\n unusedStylesThreshold?: number;\n /** Delay before bulk cleanup in ms, ignored if idleCleanup is true (default: 5000) */\n bulkCleanupDelay?: number;\n /** Use requestIdleCallback for cleanup when available (default: true) */\n idleCleanup?: boolean;\n /** Force text injection mode, auto-detected in test environments (default: auto) */\n forceTextInjection?: boolean;\n /** Enable development mode features: performance metrics and debug info (default: auto) */\n devMode?: boolean;\n /**\n * Ratio of unused styles to delete per bulk cleanup run (0..1).\n * Defaults to 0.5 (oldest half) to reduce risk of removing styles\n * that may be restored shortly after being marked unused.\n */\n bulkCleanupBatchRatio?: number;\n /**\n * Minimum age (in ms) a style must remain unused before eligible for deletion.\n * Helps avoid races during rapid mount/unmount cycles. Default: 10000ms.\n */\n unusedStylesMinAgeMs?: number;\n /**\n * Global predefined states for advanced state mapping.\n * These are state aliases that can be used in any component.\n * Example: { '@mobile': '@media(w < 920px)', '@dark': '@root(theme=dark)' }\n */\n states?: Record<string, string>;\n /**\n * Parser LRU cache size (default: 1000).\n * Larger values improve performance for apps with many unique style values.\n */\n parserCacheSize?: number;\n /**\n * Custom units for the style parser (merged with built-in units).\n * Units transform numeric values like `2x` → `calc(2 * var(--gap))`.\n * @example { em: 'em', vw: 'vw', custom: (n) => `${n * 10}px` }\n */\n units?: Record<string, string | UnitHandler>;\n /**\n * Custom functions for the style parser (merged with existing).\n * Functions process parsed style groups and return CSS values.\n * @example { myFunc: (groups) => groups.map(g => g.output).join(' ') }\n */\n funcs?: Record<string, (groups: StyleDetails[]) => string>;\n /**\n * Color space used for decomposed color token companion variables.\n * Controls the CSS function and suffix for alpha composition.\n *\n * - `'rgb'` — suffix `-rgb`, e.g. `rgb(var(--name-color-rgb) / .5)`\n * - `'hsl'` — suffix `-hsl`, e.g. `hsl(var(--name-color-hsl) / .5)`\n * - `'oklch'` — suffix `-oklch`, e.g. `oklch(var(--name-color-oklch) / .5)`\n *\n * @default 'oklch'\n */\n colorSpace?: ColorSpace;\n /**\n * Automatically infer and register CSS @property declarations\n * from custom property values found in styles, keyframes, and global config.\n * Covers all types: \\<color\\>, \\<number\\>, \\<length\\>, \\<angle\\>, \\<percentage\\>, \\<time\\>.\n * When false, only explicitly declared @properties are registered.\n * @default true\n */\n autoPropertyTypes?: boolean;\n /**\n * Plugins that extend tasty with custom functions, units, or states.\n * Plugins are processed in order, with later plugins overriding earlier ones.\n * @example\n * ```ts\n * import { okhslPlugin } from '@tenphi/tasty';\n *\n * configure({\n * plugins: [okhslPlugin()],\n * });\n * ```\n */\n plugins?: TastyPlugin[];\n /**\n * Global keyframes definitions that can be referenced by animation names in styles.\n * Keys are animation names, values are keyframes step definitions.\n * Keyframes are only injected when actually used in styles.\n * @example\n * ```ts\n * configure({\n * keyframes: {\n * fadeIn: { from: { opacity: 0 }, to: { opacity: 1 } },\n * pulse: { '0%, 100%': { transform: 'scale(1)' }, '50%': { transform: 'scale(1.05)' } },\n * },\n * });\n * ```\n */\n keyframes?: Record<string, KeyframesSteps>;\n /**\n * Global CSS @property definitions for custom properties.\n * Keys use tasty token syntax ($name for properties, #name for colors).\n * Properties are only injected when the component using them is rendered.\n *\n * For color tokens (#name), `syntax: '<color>'` is auto-set and\n * `initialValue` defaults to `'transparent'` if not specified.\n *\n * @example\n * ```ts\n * configure({\n * properties: {\n * '$rotation': { syntax: '<angle>', initialValue: '0deg' },\n * '$scale': { syntax: '<number>', inherits: false, initialValue: 1 },\n * '#accent': { initialValue: 'purple' }, // syntax: '<color>' auto-set\n * },\n * });\n *\n * // Now use in styles - properties are registered when component renders:\n * const Spinner = tasty({\n * styles: {\n * transform: 'rotate($rotation)',\n * transition: '$$rotation 0.3s', // outputs: --rotation 0.3s\n * },\n * });\n * ```\n */\n properties?: Record<string, PropertyDefinition>;\n /**\n * Global @font-face definitions.\n * Keys are font-family names, values are descriptors or arrays of descriptors\n * (for multiple weights/styles of the same family).\n * Injected eagerly when styles are first generated.\n * @example\n * ```ts\n * configure({\n * fontFace: {\n * 'Brand Sans': [\n * { src: 'url(\"/fonts/brand-regular.woff2\") format(\"woff2\")', fontWeight: 400, fontDisplay: 'swap' },\n * { src: 'url(\"/fonts/brand-bold.woff2\") format(\"woff2\")', fontWeight: 700, fontDisplay: 'swap' },\n * ],\n * Icons: { src: 'url(\"/fonts/icons.woff2\") format(\"woff2\")', fontDisplay: 'block' },\n * },\n * });\n * ```\n */\n fontFace?: Record<string, FontFaceInput>;\n /**\n * Global @counter-style definitions.\n * Keys are counter-style names, values are descriptor objects.\n * Injected eagerly when styles are first generated.\n * @example\n * ```ts\n * configure({\n * counterStyle: {\n * thumbs: { system: 'cyclic', symbols: '\"👍\"', suffix: '\" \"' },\n * },\n * });\n * ```\n */\n counterStyle?: Record<string, CounterStyleDescriptors>;\n /**\n * Custom style handlers that transform style properties into CSS declarations.\n * Handlers replace built-in handlers for the same style name.\n * @example\n * ```ts\n * import { styleHandlers } from '@tenphi/tasty';\n *\n * configure({\n * handlers: {\n * // Override fill with custom behavior\n * fill: ({ fill }) => {\n * if (fill?.startsWith('gradient:')) {\n * return { background: fill.slice(9) };\n * }\n * return styleHandlers.fill({ fill });\n * },\n * // Add new custom style\n * elevation: ({ elevation }) => {\n * const level = parseInt(elevation) || 1;\n * return {\n * 'box-shadow': `0 ${level * 2}px ${level * 4}px rgba(0,0,0,0.1)`,\n * 'z-index': String(level * 100),\n * };\n * },\n * },\n * });\n * ```\n */\n handlers?: Record<string, StyleHandlerDefinition>;\n /**\n * Design tokens injected as CSS custom properties on `:root`.\n * Values are parsed through the Tasty DSL. Supports state maps\n * for responsive/theme-aware tokens.\n *\n * - `$name` keys become `--name` CSS custom properties\n * - `#name` keys become `--name-color` and `--name-color-{colorSpace}` properties\n *\n * Tokens are injected once when the first style is rendered.\n *\n * @example\n * ```ts\n * configure({\n * tokens: {\n * '$gap': '4px',\n * '#primary': {\n * '': '#purple',\n * '@dark': '#light-purple',\n * },\n * },\n * });\n * ```\n */\n tokens?: ConfigTokens;\n /**\n * Predefined tokens that are replaced during style parsing (parse-time substitution).\n * Use `$name` for custom properties and `#name` for color tokens.\n * Values are substituted inline before CSS generation, unlike `tokens` which\n * inject CSS custom properties on `:root`.\n *\n * For color tokens (#name), boolean `true` is converted to `transparent`.\n *\n * @example\n * ```ts\n * configure({\n * replaceTokens: {\n * $spacing: '2x',\n * '#accent': '#purple',\n * '#overlay': true, // → transparent\n * },\n * });\n *\n * // Now use in styles - tokens are replaced at parse time:\n * const Card = tasty({\n * styles: {\n * padding: '$spacing', // → calc(2 * var(--gap))\n * fill: '#accent', // → var(--purple-color)\n * },\n * });\n * ```\n */\n replaceTokens?: Record<`$${string}`, string | number | boolean> &\n Record<`#${string}`, string | number | boolean>;\n /**\n * Predefined style recipes -- named style bundles that can be applied via `recipe` style property.\n * Recipe values are flat tasty styles (no sub-element keys). They may contain base styles,\n * tokens (`$name`/`#name` definitions), local states, `@keyframes`, and `@properties`.\n *\n * Components reference recipes via: `recipe: 'name1 name2'` in their styles.\n * Use `/` to separate base recipes from post recipes: `recipe: 'base1 base2 / post1'`.\n * Use `none` to skip base recipes: `recipe: 'none / post1'`.\n * Resolution order: `base_recipes → component styles → post_recipes`.\n *\n * Recipes cannot reference other recipes.\n *\n * @example\n * ```ts\n * configure({\n * recipes: {\n * card: { padding: '4x', fill: '#surface', radius: '1r', border: true },\n * elevated: { shadow: '2x 2x 4x #shadow' },\n * },\n * });\n *\n * // Usage in styles:\n * const Card = tasty({\n * styles: {\n * recipe: 'card elevated',\n * color: '#text', // Overrides recipe values\n * },\n * });\n * ```\n */\n recipes?: Record<string, RecipeStyles>;\n}\n\n// Warnings tracking to avoid duplicates\nconst emittedWarnings = new Set<string>();\n\nconst devMode = isDevEnv();\n\n/**\n * Emit a warning only once\n */\nfunction warnOnce(key: string, message: string): void {\n if (devMode && !emittedWarnings.has(key)) {\n emittedWarnings.add(key);\n console.warn(message);\n }\n}\n\n// ============================================================================\n// Configuration State\n// ============================================================================\n\n// Track whether styles have been generated (locks configuration)\nlet stylesGenerated = false;\n\n// Current configuration (null until first configure() or auto-configured on first use)\nlet currentConfig: TastyConfig | null = null;\n\n// Global keyframes storage (null = no keyframes configured, empty object checked via hasGlobalKeyframes)\nlet globalKeyframes: Record<string, KeyframesSteps> | null = null;\n\n// Global font-face storage (null = no font faces configured)\nlet globalFontFace: Record<string, FontFaceInput> | null = null;\n\n// Global counter-style storage (null = no counter styles configured)\nlet globalCounterStyle: Record<string, CounterStyleDescriptors> | null = null;\n\n// Global properties storage (null = no properties configured)\nlet globalProperties: Record<string, PropertyDefinition> | null = null;\n\n// Global recipes storage (null = no recipes configured)\nlet globalRecipes: Record<string, RecipeStyles> | null = null;\n\n// Global token styles storage (injected as :root CSS custom properties)\nlet globalConfigTokens: ConfigTokens | null = null;\n\n/**\n * Internal properties required by tasty core features.\n * These are always injected when styles are first generated.\n * Keys use tasty token syntax (#name for colors, $name for other properties).\n *\n * For properties with CSS @property-compatible types (length, time, number, color),\n * an `initialValue` is provided so the property works even without a project-level token.\n */\nexport const INTERNAL_PROPERTIES: Record<string, PropertyDefinition> = {\n // Used by dual-fill feature to enable CSS transitions on the second fill color\n '#tasty-second-fill': {\n inherits: false,\n initialValue: 'transparent',\n },\n // Current color context variable (set by the color style handler).\n '#current': {\n inherits: true,\n initialValue: 'transparent',\n },\n // White and black are fundamental colors that need explicit initial values.\n '#white': {\n inherits: true,\n initialValue: 'rgb(255 255 255)',\n },\n '#black': {\n inherits: true,\n initialValue: 'rgb(0 0 0)',\n },\n\n // ---- Core design tokens used by style handlers ----\n // These provide sensible defaults so tasty works standalone without a design system.\n // Consuming projects (e.g. uikit) override these by defining tokens on :root.\n\n $gap: {\n syntax: '<length>',\n inherits: true,\n initialValue: '4px',\n },\n $radius: {\n syntax: '<length>',\n inherits: true,\n initialValue: '6px',\n },\n '$border-width': {\n syntax: '<length>',\n inherits: true,\n initialValue: '1px',\n },\n '$outline-width': {\n syntax: '<length>',\n inherits: true,\n initialValue: '3px',\n },\n $transition: {\n syntax: '<time>',\n inherits: true,\n initialValue: '80ms',\n },\n // Used by radius.ts for `radius=\"leaf\"` modifier\n '$sharp-radius': {\n syntax: '<length>',\n inherits: true,\n initialValue: '0px',\n },\n // Used by preset.ts for `preset=\"name / strong\"`\n '$bold-font-weight': {\n syntax: '<number>',\n inherits: true,\n initialValue: '700',\n },\n // Used by preset.ts as fallback font stacks\n '$font-sans-fallback': {\n syntax: '*',\n inherits: true,\n initialValue:\n 'system-ui, -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, \"Apple Color Emoji\", \"Segoe UI Emoji\", sans-serif',\n },\n '$font-mono-fallback': {\n syntax: '*',\n inherits: true,\n initialValue:\n 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace',\n },\n};\n\n// Global injector instance key\nconst GLOBAL_INJECTOR_KEY = '__TASTY_GLOBAL_INJECTOR__';\n\ninterface TastyGlobalStorage {\n [GLOBAL_INJECTOR_KEY]?: StyleInjector;\n}\n\ndeclare global {\n interface Window {\n [GLOBAL_INJECTOR_KEY]?: StyleInjector;\n }\n\n var __TASTY_GLOBAL_INJECTOR__: StyleInjector | undefined;\n}\n\n/**\n * Detect if we're running in a test environment\n */\nexport function isTestEnvironment(): boolean {\n // Check Node.js environment\n if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'test') {\n return true;\n }\n\n // Check for test runner globals (safely)\n if (typeof global !== 'undefined') {\n const g = global as unknown as Record<string, unknown>;\n if (g.vi || g.jest || g.expect || g.describe || g.it) {\n return true;\n }\n }\n\n // Check for jsdom environment (common in tests)\n if (\n typeof window !== 'undefined' &&\n window.navigator?.userAgent?.includes('jsdom')\n ) {\n return true;\n }\n\n // Check for other test runners\n if (typeof globalThis !== 'undefined') {\n const gt = globalThis as unknown as Record<string, unknown>;\n if (gt.vitest || gt.mocha) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Create default configuration with optional test environment detection\n */\nfunction createDefaultConfig(isTest?: boolean): TastyConfig {\n return {\n maxRulesPerSheet: 8192,\n unusedStylesThreshold: 500,\n bulkCleanupDelay: 5000,\n idleCleanup: true,\n forceTextInjection: isTest ?? false,\n devMode: isDevEnv(),\n bulkCleanupBatchRatio: 0.5,\n unusedStylesMinAgeMs: 10000,\n };\n}\n\n// ============================================================================\n// stylesGenerated Flag Management\n// ============================================================================\n\n/**\n * Mark that styles have been generated (called by injector on first inject)\n * This locks the configuration - no further changes allowed.\n * Also injects internal and global properties.\n */\nexport function markStylesGenerated(): void {\n if (stylesGenerated) return; // Already marked, skip\n\n stylesGenerated = true;\n\n const injector = getGlobalInjector();\n\n // Inject internal properties required by tasty core features\n for (const [token, definition] of Object.entries(INTERNAL_PROPERTIES)) {\n injector.property(token, definition);\n }\n\n // Inject global properties if any were configured\n // Properties are permanent and only need to be injected once\n if (globalProperties && Object.keys(globalProperties).length > 0) {\n for (const [token, definition] of Object.entries(globalProperties)) {\n injector.property(token, definition);\n }\n }\n\n // Inject global @font-face rules (eagerly — fonts should be available before render)\n if (globalFontFace && Object.keys(globalFontFace).length > 0) {\n for (const [family, input] of Object.entries(globalFontFace)) {\n const descriptors = Array.isArray(input) ? input : [input];\n for (const desc of descriptors) {\n injector.fontFace(family, desc);\n }\n }\n }\n\n // Inject global @counter-style rules (eagerly)\n if (globalCounterStyle && Object.keys(globalCounterStyle).length > 0) {\n for (const [name, descriptors] of Object.entries(globalCounterStyle)) {\n injector.counterStyle(name, descriptors);\n }\n }\n\n // Inject configured tokens as :root CSS custom properties\n if (globalConfigTokens && Object.keys(globalConfigTokens).length > 0) {\n const tokenRules = renderStyles(\n globalConfigTokens,\n ':root',\n ) as StyleResult[];\n if (tokenRules.length > 0) {\n injector.injectGlobal(tokenRules);\n }\n }\n}\n\n/**\n * Check if styles have been generated (configuration is locked)\n */\nexport function hasStylesGenerated(): boolean {\n return stylesGenerated;\n}\n\n/**\n * Reset styles generated flag (for testing only)\n */\nexport function resetStylesGenerated(): void {\n stylesGenerated = false;\n emittedWarnings.clear();\n}\n\n// ============================================================================\n// Global Keyframes Management\n// ============================================================================\n\nlet _hasGlobalKeyframes = false;\n\n/**\n * Check if any global keyframes are configured.\n * Uses a pre-computed flag to avoid Object.keys() allocation on every call.\n */\nexport function hasGlobalKeyframes(): boolean {\n return _hasGlobalKeyframes;\n}\n\n/**\n * Get global keyframes configuration.\n * Returns null if no keyframes configured (fast path for zero-overhead).\n */\nexport function getGlobalKeyframes(): Record<string, KeyframesSteps> | null {\n return globalKeyframes;\n}\n\n/**\n * Set global keyframes (called from configure).\n * Internal use only.\n */\nfunction setGlobalKeyframes(keyframes: Record<string, KeyframesSteps>): void {\n if (stylesGenerated) {\n warnOnce(\n 'keyframes-after-styles',\n `[Tasty] Cannot update keyframes after styles have been generated.\\n` +\n `The new keyframes will be ignored.`,\n );\n return;\n }\n globalKeyframes = keyframes;\n _hasGlobalKeyframes = Object.keys(keyframes).length > 0;\n}\n\n// ============================================================================\n// Global Properties Management\n// ============================================================================\n\n/**\n * Check if any global properties are configured.\n * Fast path: returns false if no properties were ever set.\n */\nexport function hasGlobalProperties(): boolean {\n return globalProperties !== null && Object.keys(globalProperties).length > 0;\n}\n\n/**\n * Get global properties configuration.\n * Returns null if no properties configured (fast path for zero-overhead).\n */\nexport function getGlobalProperties(): Record<\n string,\n PropertyDefinition\n> | null {\n return globalProperties;\n}\n\n/**\n * Set global properties (called from configure).\n * Internal use only.\n */\nfunction setGlobalProperties(\n properties: Record<string, PropertyDefinition>,\n): void {\n if (stylesGenerated) {\n warnOnce(\n 'properties-after-styles',\n `[Tasty] Cannot update properties after styles have been generated.\\n` +\n `The new properties will be ignored.`,\n );\n return;\n }\n globalProperties = properties;\n}\n\n// ============================================================================\n// Global Font Face Management\n// ============================================================================\n\n/**\n * Get global font-face configuration.\n * Returns null if no font faces configured.\n */\nexport function getGlobalFontFace(): Record<string, FontFaceInput> | null {\n return globalFontFace;\n}\n\n/**\n * Set global font faces (called from configure).\n * Internal use only.\n */\nfunction setGlobalFontFace(fontFace: Record<string, FontFaceInput>): void {\n if (stylesGenerated) {\n warnOnce(\n 'fontface-after-styles',\n `[Tasty] Cannot update fontFace after styles have been generated.\\n` +\n `The new font faces will be ignored.`,\n );\n return;\n }\n globalFontFace = fontFace;\n}\n\n// ============================================================================\n// Global Counter Style Management\n// ============================================================================\n\n/**\n * Get global counter-style configuration.\n * Returns null if no counter styles configured.\n */\nexport function getGlobalCounterStyle(): Record<\n string,\n CounterStyleDescriptors\n> | null {\n return globalCounterStyle;\n}\n\n/**\n * Set global counter styles (called from configure).\n * Internal use only.\n */\nfunction setGlobalCounterStyle(\n counterStyle: Record<string, CounterStyleDescriptors>,\n): void {\n if (stylesGenerated) {\n warnOnce(\n 'counterstyle-after-styles',\n `[Tasty] Cannot update counterStyle after styles have been generated.\\n` +\n `The new counter styles will be ignored.`,\n );\n return;\n }\n globalCounterStyle = counterStyle;\n}\n\n// ============================================================================\n// Global Recipes Management\n// ============================================================================\n\n/**\n * Check if any global recipes are configured.\n * Fast path: returns false if no recipes were ever set.\n */\nexport function hasGlobalRecipes(): boolean {\n return globalRecipes !== null && Object.keys(globalRecipes).length > 0;\n}\n\n/**\n * Get global recipes configuration.\n * Returns null if no recipes configured (fast path for zero-overhead).\n */\nexport function getGlobalRecipes(): Record<string, RecipeStyles> | null {\n return globalRecipes;\n}\n\n/**\n * Set global recipes (called from configure).\n * Internal use only.\n */\nfunction setGlobalRecipes(recipes: Record<string, RecipeStyles>): void {\n if (stylesGenerated) {\n warnOnce(\n 'recipes-after-styles',\n `[Tasty] Cannot update recipes after styles have been generated.\\n` +\n `The new recipes will be ignored.`,\n );\n return;\n }\n\n // Dev-mode validation\n if (devMode) {\n for (const [name, recipeStyles] of Object.entries(recipes)) {\n if (name === 'none') {\n warnOnce(\n 'recipe-reserved-none',\n `[Tasty] Recipe name \"none\" is reserved. ` +\n `It is used as a keyword meaning \"no base recipes\" ` +\n `(e.g. recipe: 'none / post-recipe'). ` +\n `Choose a different name for your recipe.`,\n );\n }\n\n for (const key of Object.keys(recipeStyles)) {\n if (isSelector(key)) {\n warnOnce(\n `recipe-selector-${name}-${key}`,\n `[Tasty] Recipe \"${name}\" contains sub-element key \"${key}\". ` +\n `Recipes must be flat styles without sub-element keys. ` +\n `Remove the sub-element key from the recipe definition.`,\n );\n }\n if (key === 'recipe') {\n warnOnce(\n `recipe-recursive-${name}`,\n `[Tasty] Recipe \"${name}\" contains a \"recipe\" key. ` +\n `Recipes cannot reference other recipes. ` +\n `Use space-separated names for composition: recipe: 'base elevated'.`,\n );\n }\n }\n }\n }\n\n globalRecipes = recipes;\n}\n\n// ============================================================================\n// Global Token Styles Management\n// ============================================================================\n\n/**\n * Get global token styles for :root injection.\n * Returns null if no tokens configured.\n */\nexport function getGlobalConfigTokens(): ConfigTokens | null {\n return globalConfigTokens;\n}\n\n/**\n * Set global token styles (called from configure).\n * Internal use only.\n */\nfunction setGlobalConfigTokens(styles: ConfigTokens): void {\n if (stylesGenerated) {\n warnOnce(\n 'tokens-after-styles',\n `[Tasty] Cannot update tokens after styles have been generated.\\n` +\n `The new tokens will be ignored.`,\n );\n return;\n }\n globalConfigTokens = globalConfigTokens\n ? { ...globalConfigTokens, ...styles }\n : styles;\n}\n\n/**\n * Check if configuration is locked (styles have been generated)\n */\nexport function isConfigLocked(): boolean {\n return stylesGenerated;\n}\n\n// ============================================================================\n// Configuration API\n// ============================================================================\n\n/**\n * Configure the Tasty style system.\n *\n * Must be called BEFORE any styles are generated (before first render that uses tasty).\n * After styles are generated, configuration is locked and calls to configure() will\n * emit a warning and be ignored.\n *\n * @example\n * ```ts\n * import { configure } from '@tenphi/tasty';\n *\n * // Configure before app renders\n * configure({\n * nonce: 'abc123',\n * states: {\n * '@mobile': '@media(w < 768px)',\n * '@dark': '@root(theme=dark)',\n * },\n * });\n * ```\n */\nexport function configure(config: Partial<TastyConfig> = {}): void {\n if (stylesGenerated) {\n warnOnce(\n 'configure-after-styles',\n `[Tasty] Cannot call configure() after styles have been generated.\\n` +\n `Configuration must be done before the first render. The configuration will be ignored.`,\n );\n return;\n }\n\n // Collect merged values from plugins first, then override with direct config\n let mergedStates: Record<string, string> = {};\n let mergedUnits: Record<string, string | UnitHandler> = {};\n let mergedFuncs: Record<string, (groups: StyleDetails[]) => string> = {};\n let mergedHandlers: Record<string, StyleHandlerDefinition> = {};\n let mergedReplaceTokens: Record<string, string | number | boolean> = {};\n let mergedConfigTokens: ConfigTokens = {} as ConfigTokens;\n let mergedRecipes: Record<string, RecipeStyles> = {};\n\n // Process plugins in order\n if (config.plugins) {\n for (const plugin of config.plugins) {\n if (plugin.states) {\n mergedStates = { ...mergedStates, ...plugin.states };\n }\n if (plugin.units) {\n mergedUnits = { ...mergedUnits, ...plugin.units };\n }\n if (plugin.funcs) {\n mergedFuncs = { ...mergedFuncs, ...plugin.funcs };\n }\n if (plugin.handlers) {\n mergedHandlers = { ...mergedHandlers, ...plugin.handlers };\n }\n if (plugin.replaceTokens) {\n mergedReplaceTokens = {\n ...mergedReplaceTokens,\n ...plugin.replaceTokens,\n };\n }\n if (plugin.tokens) {\n mergedConfigTokens = { ...mergedConfigTokens, ...plugin.tokens };\n }\n if (plugin.recipes) {\n mergedRecipes = { ...mergedRecipes, ...plugin.recipes };\n }\n }\n }\n\n // Direct config overrides plugins\n if (config.states) {\n mergedStates = { ...mergedStates, ...config.states };\n }\n if (config.units) {\n mergedUnits = { ...mergedUnits, ...config.units };\n }\n if (config.funcs) {\n mergedFuncs = { ...mergedFuncs, ...config.funcs };\n }\n if (config.handlers) {\n mergedHandlers = { ...mergedHandlers, ...config.handlers };\n }\n if (config.replaceTokens) {\n mergedReplaceTokens = { ...mergedReplaceTokens, ...config.replaceTokens };\n }\n if (config.tokens) {\n mergedConfigTokens = { ...mergedConfigTokens, ...config.tokens };\n }\n if (config.recipes) {\n mergedRecipes = { ...mergedRecipes, ...config.recipes };\n }\n\n // Warn on tokens/replaceTokens key conflicts\n if (devMode) {\n const tokenKeys = new Set(Object.keys(mergedConfigTokens));\n for (const key of Object.keys(mergedReplaceTokens)) {\n if (tokenKeys.has(key)) {\n warnOnce(\n `token-conflict-${key}`,\n `[Tasty] Token \"${key}\" is defined in both \\`tokens\\` and \\`replaceTokens\\`. ` +\n `\\`replaceTokens\\` performs parse-time substitution, so the \\`tokens\\` ` +\n `CSS custom property will be injected but never used by Tasty styles. ` +\n `Remove it from one of the two.`,\n );\n }\n }\n }\n\n // Handle color space (must be set before any token processing)\n if (config.colorSpace) {\n setColorSpace(config.colorSpace);\n // Color space affects parser output (e.g. #name.5 → oklch(...) vs rgb(...))\n getGlobalParser().clearCache();\n }\n\n // Handle predefined states\n if (Object.keys(mergedStates).length > 0) {\n setGlobalPredefinedStates(mergedStates);\n }\n\n // Handle parser configuration (merge semantics - extend, not replace)\n const parser = getGlobalParser();\n\n if (config.parserCacheSize !== undefined) {\n parser.updateOptions({ cacheSize: config.parserCacheSize });\n }\n\n if (Object.keys(mergedUnits).length > 0) {\n // Merge with existing units\n const currentUnits = parser.getUnits() ?? CUSTOM_UNITS;\n parser.setUnits({ ...currentUnits, ...mergedUnits });\n }\n\n if (Object.keys(mergedFuncs).length > 0) {\n // Merge with existing funcs\n const currentFuncs = getGlobalFuncs();\n const finalFuncs = { ...currentFuncs, ...mergedFuncs };\n parser.setFuncs(finalFuncs);\n // Also update the global registry so customFunc() continues to work\n Object.assign(currentFuncs, mergedFuncs);\n }\n\n // Handle keyframes\n if (config.keyframes) {\n setGlobalKeyframes(config.keyframes);\n }\n\n // Handle properties\n if (config.properties) {\n setGlobalProperties(config.properties);\n }\n\n // Handle font faces\n if (config.fontFace) {\n setGlobalFontFace(config.fontFace);\n }\n\n // Handle counter styles\n if (config.counterStyle) {\n setGlobalCounterStyle(config.counterStyle);\n }\n\n // Handle custom handlers\n if (Object.keys(mergedHandlers).length > 0) {\n for (const [name, definition] of Object.entries(mergedHandlers)) {\n const handler = normalizeHandlerDefinition(name, definition);\n registerHandler(handler);\n }\n }\n\n // Handle replaceTokens (parse-time substitution)\n if (Object.keys(mergedReplaceTokens).length > 0) {\n const processedTokens: Record<string, string> = {};\n for (const [key, value] of Object.entries(mergedReplaceTokens)) {\n if (key.startsWith('#')) {\n const normalized = normalizeColorTokenValue(value);\n if (normalized === null) continue;\n processedTokens[key] = String(normalized);\n } else if (value === false) {\n continue;\n } else {\n processedTokens[key] = String(value);\n }\n }\n setGlobalPredefinedTokens(processedTokens);\n }\n\n // Handle tokens (CSS custom properties on :root)\n if (Object.keys(mergedConfigTokens).length > 0) {\n setGlobalConfigTokens(mergedConfigTokens);\n }\n\n // Handle recipes\n if (Object.keys(mergedRecipes).length > 0) {\n setGlobalRecipes(mergedRecipes);\n }\n\n const {\n states: _states,\n parserCacheSize: _parserCacheSize,\n units: _units,\n funcs: _funcs,\n plugins: _plugins,\n keyframes: _keyframes,\n properties: _properties,\n fontFace: _fontFace,\n counterStyle: _counterStyle,\n handlers: _handlers,\n tokens: _tokens,\n replaceTokens: _replaceTokens,\n recipes: _recipes,\n colorSpace: _colorSpace,\n ...injectorConfig\n } = config;\n\n const fullConfig: TastyConfig = {\n ...createDefaultConfig(),\n ...currentConfig,\n ...injectorConfig,\n };\n\n // Store the config\n currentConfig = fullConfig;\n\n // Create/replace the global injector\n const storage: TastyGlobalStorage =\n typeof window !== 'undefined' ? window : globalThis;\n storage[GLOBAL_INJECTOR_KEY] = new StyleInjector(fullConfig);\n}\n\n/**\n * Get the current configuration.\n * If not configured, returns default configuration.\n */\nexport function getConfig(): TastyConfig {\n if (!currentConfig) {\n currentConfig = createDefaultConfig(isTestEnvironment());\n }\n return currentConfig;\n}\n\n/**\n * Get the global injector instance.\n * Auto-configures with defaults if not already configured.\n */\nexport function getGlobalInjector(): StyleInjector {\n const storage: TastyGlobalStorage =\n typeof window !== 'undefined' ? window : globalThis;\n\n if (!storage[GLOBAL_INJECTOR_KEY]) {\n configure();\n }\n\n return storage[GLOBAL_INJECTOR_KEY]!;\n}\n\n/**\n * Reset configuration (for testing only).\n * Clears the global injector and allows reconfiguration.\n */\nexport function resetConfig(): void {\n stylesGenerated = false;\n currentConfig = null;\n globalKeyframes = null;\n _hasGlobalKeyframes = false;\n globalProperties = null;\n globalFontFace = null;\n globalCounterStyle = null;\n globalRecipes = null;\n globalConfigTokens = null;\n resetGlobalPredefinedTokens();\n resetHandlers();\n resetColorSpace();\n clearPipelineCache();\n emittedWarnings.clear();\n\n const storage: TastyGlobalStorage =\n typeof window !== 'undefined' ? window : globalThis;\n delete storage[GLOBAL_INJECTOR_KEY];\n}\n\n// Re-export TastyConfig as StyleInjectorConfig for backward compatibility\nexport type { TastyConfig as StyleInjectorConfig };\n"],"mappings":"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"}
|
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/pipeline/index.js
CHANGED
|
@@ -44,12 +44,17 @@ function runPipeline(styles, parserContext) {
|
|
|
44
44
|
const allRules = [];
|
|
45
45
|
processStyles(styles, "", parserContext, allRules);
|
|
46
46
|
const seen = /* @__PURE__ */ new Set();
|
|
47
|
-
|
|
47
|
+
const dedupedRules = allRules.filter((rule) => {
|
|
48
48
|
const key = `${rule.selector}|${rule.declarations}|${rule.atRules?.join("|") ?? ""}|${rule.rootPrefix || ""}|${rule.startingStyle ? "1" : "0"}`;
|
|
49
49
|
if (seen.has(key)) return false;
|
|
50
50
|
seen.add(key);
|
|
51
51
|
return true;
|
|
52
52
|
});
|
|
53
|
+
const normal = [];
|
|
54
|
+
const starting = [];
|
|
55
|
+
for (const rule of dedupedRules) if (rule.startingStyle) starting.push(rule);
|
|
56
|
+
else normal.push(rule);
|
|
57
|
+
return normal.concat(starting);
|
|
53
58
|
}
|
|
54
59
|
/**
|
|
55
60
|
* Process styles at a given nesting level
|