@tenphi/tasty 0.1.0
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/LICENSE +21 -0
- package/README.md +236 -0
- package/dist/_virtual/_rolldown/runtime.mjs +7 -0
- package/dist/chunks/cacheKey.d.ts +1 -0
- package/dist/chunks/cacheKey.js +70 -0
- package/dist/chunks/cacheKey.js.map +1 -0
- package/dist/chunks/cacheKey.mjs +70 -0
- package/dist/chunks/cacheKey.mjs.map +1 -0
- package/dist/chunks/definitions.d.ts +37 -0
- package/dist/chunks/definitions.js +260 -0
- package/dist/chunks/definitions.js.map +1 -0
- package/dist/chunks/definitions.mjs +260 -0
- package/dist/chunks/definitions.mjs.map +1 -0
- package/dist/chunks/index.d.ts +3 -0
- package/dist/chunks/renderChunk.d.ts +2 -0
- package/dist/chunks/renderChunk.js +61 -0
- package/dist/chunks/renderChunk.js.map +1 -0
- package/dist/chunks/renderChunk.mjs +61 -0
- package/dist/chunks/renderChunk.mjs.map +1 -0
- package/dist/config.d.ts +279 -0
- package/dist/config.js +400 -0
- package/dist/config.js.map +1 -0
- package/dist/config.mjs +231 -0
- package/dist/config.mjs.map +1 -0
- package/dist/css-writer.d.mts +45 -0
- package/dist/css-writer.mjs +74 -0
- package/dist/css-writer.mjs.map +1 -0
- package/dist/debug.d.ts +204 -0
- package/dist/debug.js +733 -0
- package/dist/debug.js.map +1 -0
- package/dist/extractor.d.mts +24 -0
- package/dist/extractor.mjs +150 -0
- package/dist/extractor.mjs.map +1 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/useGlobalStyles.d.ts +27 -0
- package/dist/hooks/useGlobalStyles.js +56 -0
- package/dist/hooks/useGlobalStyles.js.map +1 -0
- package/dist/hooks/useKeyframes.d.ts +56 -0
- package/dist/hooks/useKeyframes.js +54 -0
- package/dist/hooks/useKeyframes.js.map +1 -0
- package/dist/hooks/useProperty.d.ts +79 -0
- package/dist/hooks/useProperty.js +91 -0
- package/dist/hooks/useProperty.js.map +1 -0
- package/dist/hooks/useRawCSS.d.ts +53 -0
- package/dist/hooks/useRawCSS.js +28 -0
- package/dist/hooks/useRawCSS.js.map +1 -0
- package/dist/hooks/useStyles.d.ts +40 -0
- package/dist/hooks/useStyles.js +169 -0
- package/dist/hooks/useStyles.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +30 -0
- package/dist/injector/index.d.ts +157 -0
- package/dist/injector/index.js +154 -0
- package/dist/injector/index.js.map +1 -0
- package/dist/injector/injector.d.ts +139 -0
- package/dist/injector/injector.js +404 -0
- package/dist/injector/injector.js.map +1 -0
- package/dist/injector/injector.mjs +404 -0
- package/dist/injector/injector.mjs.map +1 -0
- package/dist/injector/sheet-manager.d.ts +127 -0
- package/dist/injector/sheet-manager.js +714 -0
- package/dist/injector/sheet-manager.js.map +1 -0
- package/dist/injector/sheet-manager.mjs +714 -0
- package/dist/injector/sheet-manager.mjs.map +1 -0
- package/dist/injector/types.d.mts +18 -0
- package/dist/injector/types.d.ts +135 -0
- package/dist/keyframes/index.js +206 -0
- package/dist/keyframes/index.js.map +1 -0
- package/dist/keyframes/index.mjs +156 -0
- package/dist/keyframes/index.mjs.map +1 -0
- package/dist/parser/classify.js +319 -0
- package/dist/parser/classify.js.map +1 -0
- package/dist/parser/classify.mjs +319 -0
- package/dist/parser/classify.mjs.map +1 -0
- package/dist/parser/const.js +33 -0
- package/dist/parser/const.js.map +1 -0
- package/dist/parser/const.mjs +33 -0
- package/dist/parser/const.mjs.map +1 -0
- package/dist/parser/lru.js +109 -0
- package/dist/parser/lru.js.map +1 -0
- package/dist/parser/lru.mjs +109 -0
- package/dist/parser/lru.mjs.map +1 -0
- package/dist/parser/parser.d.ts +25 -0
- package/dist/parser/parser.js +116 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/parser.mjs +116 -0
- package/dist/parser/parser.mjs.map +1 -0
- package/dist/parser/tokenizer.js +69 -0
- package/dist/parser/tokenizer.js.map +1 -0
- package/dist/parser/tokenizer.mjs +69 -0
- package/dist/parser/tokenizer.mjs.map +1 -0
- package/dist/parser/types.d.mts +37 -0
- package/dist/parser/types.d.ts +46 -0
- package/dist/parser/types.js +46 -0
- package/dist/parser/types.js.map +1 -0
- package/dist/parser/types.mjs +46 -0
- package/dist/parser/types.mjs.map +1 -0
- package/dist/pipeline/conditions.js +377 -0
- package/dist/pipeline/conditions.js.map +1 -0
- package/dist/pipeline/conditions.mjs +377 -0
- package/dist/pipeline/conditions.mjs.map +1 -0
- package/dist/pipeline/exclusive.d.ts +1 -0
- package/dist/pipeline/exclusive.js +231 -0
- package/dist/pipeline/exclusive.js.map +1 -0
- package/dist/pipeline/exclusive.mjs +231 -0
- package/dist/pipeline/exclusive.mjs.map +1 -0
- package/dist/pipeline/index.d.ts +53 -0
- package/dist/pipeline/index.js +641 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/index.mjs +635 -0
- package/dist/pipeline/index.mjs.map +1 -0
- package/dist/pipeline/materialize.js +821 -0
- package/dist/pipeline/materialize.js.map +1 -0
- package/dist/pipeline/materialize.mjs +821 -0
- package/dist/pipeline/materialize.mjs.map +1 -0
- package/dist/pipeline/parseStateKey.d.ts +1 -0
- package/dist/pipeline/parseStateKey.js +418 -0
- package/dist/pipeline/parseStateKey.js.map +1 -0
- package/dist/pipeline/parseStateKey.mjs +418 -0
- package/dist/pipeline/parseStateKey.mjs.map +1 -0
- package/dist/pipeline/simplify.js +557 -0
- package/dist/pipeline/simplify.js.map +1 -0
- package/dist/pipeline/simplify.mjs +557 -0
- package/dist/pipeline/simplify.mjs.map +1 -0
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/okhsl-plugin.d.ts +35 -0
- package/dist/plugins/okhsl-plugin.js +371 -0
- package/dist/plugins/okhsl-plugin.js.map +1 -0
- package/dist/plugins/okhsl-plugin.mjs +345 -0
- package/dist/plugins/okhsl-plugin.mjs.map +1 -0
- package/dist/plugins/types.d.mts +49 -0
- package/dist/plugins/types.d.ts +69 -0
- package/dist/properties/index.js +158 -0
- package/dist/properties/index.js.map +1 -0
- package/dist/properties/index.mjs +141 -0
- package/dist/properties/index.mjs.map +1 -0
- package/dist/states/index.d.ts +45 -0
- package/dist/states/index.js +389 -0
- package/dist/states/index.js.map +1 -0
- package/dist/states/index.mjs +161 -0
- package/dist/states/index.mjs.map +1 -0
- package/dist/static/index.d.ts +5 -0
- package/dist/static/index.js +5 -0
- package/dist/static/tastyStatic.d.ts +46 -0
- package/dist/static/tastyStatic.js +31 -0
- package/dist/static/tastyStatic.js.map +1 -0
- package/dist/static/types.d.ts +49 -0
- package/dist/static/types.js +24 -0
- package/dist/static/types.js.map +1 -0
- package/dist/styles/align.d.ts +15 -0
- package/dist/styles/align.js +14 -0
- package/dist/styles/align.js.map +1 -0
- package/dist/styles/align.mjs +14 -0
- package/dist/styles/align.mjs.map +1 -0
- package/dist/styles/border.d.ts +25 -0
- package/dist/styles/border.js +114 -0
- package/dist/styles/border.js.map +1 -0
- package/dist/styles/border.mjs +114 -0
- package/dist/styles/border.mjs.map +1 -0
- package/dist/styles/color.d.ts +14 -0
- package/dist/styles/color.js +23 -0
- package/dist/styles/color.js.map +1 -0
- package/dist/styles/color.mjs +23 -0
- package/dist/styles/color.mjs.map +1 -0
- package/dist/styles/createStyle.js +77 -0
- package/dist/styles/createStyle.js.map +1 -0
- package/dist/styles/createStyle.mjs +77 -0
- package/dist/styles/createStyle.mjs.map +1 -0
- package/dist/styles/dimension.js +97 -0
- package/dist/styles/dimension.js.map +1 -0
- package/dist/styles/dimension.mjs +97 -0
- package/dist/styles/dimension.mjs.map +1 -0
- package/dist/styles/display.d.ts +37 -0
- package/dist/styles/display.js +67 -0
- package/dist/styles/display.js.map +1 -0
- package/dist/styles/display.mjs +67 -0
- package/dist/styles/display.mjs.map +1 -0
- package/dist/styles/fade.d.ts +15 -0
- package/dist/styles/fade.js +58 -0
- package/dist/styles/fade.js.map +1 -0
- package/dist/styles/fade.mjs +58 -0
- package/dist/styles/fade.mjs.map +1 -0
- package/dist/styles/fill.d.ts +44 -0
- package/dist/styles/fill.js +51 -0
- package/dist/styles/fill.js.map +1 -0
- package/dist/styles/fill.mjs +51 -0
- package/dist/styles/fill.mjs.map +1 -0
- package/dist/styles/flow.d.ts +16 -0
- package/dist/styles/flow.js +12 -0
- package/dist/styles/flow.js.map +1 -0
- package/dist/styles/flow.mjs +12 -0
- package/dist/styles/flow.mjs.map +1 -0
- package/dist/styles/gap.d.ts +31 -0
- package/dist/styles/gap.js +37 -0
- package/dist/styles/gap.js.map +1 -0
- package/dist/styles/gap.mjs +37 -0
- package/dist/styles/gap.mjs.map +1 -0
- package/dist/styles/height.d.ts +17 -0
- package/dist/styles/height.js +20 -0
- package/dist/styles/height.js.map +1 -0
- package/dist/styles/height.mjs +20 -0
- package/dist/styles/height.mjs.map +1 -0
- package/dist/styles/index.d.ts +2 -0
- package/dist/styles/index.js +9 -0
- package/dist/styles/index.js.map +1 -0
- package/dist/styles/index.mjs +9 -0
- package/dist/styles/index.mjs.map +1 -0
- package/dist/styles/inset.d.ts +50 -0
- package/dist/styles/inset.js +142 -0
- package/dist/styles/inset.js.map +1 -0
- package/dist/styles/inset.mjs +142 -0
- package/dist/styles/inset.mjs.map +1 -0
- package/dist/styles/justify.d.ts +15 -0
- package/dist/styles/justify.js +14 -0
- package/dist/styles/justify.js.map +1 -0
- package/dist/styles/justify.mjs +14 -0
- package/dist/styles/justify.mjs.map +1 -0
- package/dist/styles/list.d.ts +16 -0
- package/dist/styles/list.js +98 -0
- package/dist/styles/list.js.map +1 -0
- package/dist/styles/margin.d.ts +28 -0
- package/dist/styles/margin.js +96 -0
- package/dist/styles/margin.js.map +1 -0
- package/dist/styles/margin.mjs +96 -0
- package/dist/styles/margin.mjs.map +1 -0
- package/dist/styles/outline.d.ts +29 -0
- package/dist/styles/outline.js +65 -0
- package/dist/styles/outline.js.map +1 -0
- package/dist/styles/outline.mjs +65 -0
- package/dist/styles/outline.mjs.map +1 -0
- package/dist/styles/padding.d.ts +28 -0
- package/dist/styles/padding.js +96 -0
- package/dist/styles/padding.js.map +1 -0
- package/dist/styles/padding.mjs +96 -0
- package/dist/styles/padding.mjs.map +1 -0
- package/dist/styles/predefined.d.ts +74 -0
- package/dist/styles/predefined.js +241 -0
- package/dist/styles/predefined.js.map +1 -0
- package/dist/styles/predefined.mjs +232 -0
- package/dist/styles/predefined.mjs.map +1 -0
- package/dist/styles/preset.d.ts +47 -0
- package/dist/styles/preset.js +126 -0
- package/dist/styles/preset.js.map +1 -0
- package/dist/styles/preset.mjs +126 -0
- package/dist/styles/preset.mjs.map +1 -0
- package/dist/styles/radius.d.ts +14 -0
- package/dist/styles/radius.js +51 -0
- package/dist/styles/radius.js.map +1 -0
- package/dist/styles/radius.mjs +51 -0
- package/dist/styles/radius.mjs.map +1 -0
- package/dist/styles/scrollbar.d.ts +21 -0
- package/dist/styles/scrollbar.js +105 -0
- package/dist/styles/scrollbar.js.map +1 -0
- package/dist/styles/scrollbar.mjs +105 -0
- package/dist/styles/scrollbar.mjs.map +1 -0
- package/dist/styles/shadow.d.ts +14 -0
- package/dist/styles/shadow.js +24 -0
- package/dist/styles/shadow.js.map +1 -0
- package/dist/styles/shadow.mjs +24 -0
- package/dist/styles/shadow.mjs.map +1 -0
- package/dist/styles/styledScrollbar.d.ts +47 -0
- package/dist/styles/styledScrollbar.js +38 -0
- package/dist/styles/styledScrollbar.js.map +1 -0
- package/dist/styles/styledScrollbar.mjs +38 -0
- package/dist/styles/styledScrollbar.mjs.map +1 -0
- package/dist/styles/transition.d.ts +14 -0
- package/dist/styles/transition.js +138 -0
- package/dist/styles/transition.js.map +1 -0
- package/dist/styles/transition.mjs +138 -0
- package/dist/styles/transition.mjs.map +1 -0
- package/dist/styles/types.d.mts +492 -0
- package/dist/styles/types.d.ts +496 -0
- package/dist/styles/width.d.ts +17 -0
- package/dist/styles/width.js +20 -0
- package/dist/styles/width.js.map +1 -0
- package/dist/styles/width.mjs +20 -0
- package/dist/styles/width.mjs.map +1 -0
- package/dist/tasty.d.ts +983 -0
- package/dist/tasty.js +191 -0
- package/dist/tasty.js.map +1 -0
- package/dist/tokens/typography.d.ts +19 -0
- package/dist/tokens/typography.js +237 -0
- package/dist/tokens/typography.js.map +1 -0
- package/dist/types.d.ts +182 -0
- package/dist/utils/cache-wrapper.js +26 -0
- package/dist/utils/cache-wrapper.js.map +1 -0
- package/dist/utils/cache-wrapper.mjs +26 -0
- package/dist/utils/cache-wrapper.mjs.map +1 -0
- package/dist/utils/case-converter.js +8 -0
- package/dist/utils/case-converter.js.map +1 -0
- package/dist/utils/case-converter.mjs +8 -0
- package/dist/utils/case-converter.mjs.map +1 -0
- package/dist/utils/colors.d.ts +5 -0
- package/dist/utils/colors.js +9 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/utils/dotize.d.ts +26 -0
- package/dist/utils/dotize.js +122 -0
- package/dist/utils/dotize.js.map +1 -0
- package/dist/utils/filter-base-props.d.ts +15 -0
- package/dist/utils/filter-base-props.js +45 -0
- package/dist/utils/filter-base-props.js.map +1 -0
- package/dist/utils/get-display-name.d.ts +7 -0
- package/dist/utils/get-display-name.js +10 -0
- package/dist/utils/get-display-name.js.map +1 -0
- package/dist/utils/hsl-to-rgb.js +38 -0
- package/dist/utils/hsl-to-rgb.js.map +1 -0
- package/dist/utils/hsl-to-rgb.mjs +38 -0
- package/dist/utils/hsl-to-rgb.mjs.map +1 -0
- package/dist/utils/is-dev-env.js +19 -0
- package/dist/utils/is-dev-env.js.map +1 -0
- package/dist/utils/is-dev-env.mjs +19 -0
- package/dist/utils/is-dev-env.mjs.map +1 -0
- package/dist/utils/merge-styles.d.ts +7 -0
- package/dist/utils/merge-styles.js +146 -0
- package/dist/utils/merge-styles.js.map +1 -0
- package/dist/utils/merge-styles.mjs +146 -0
- package/dist/utils/merge-styles.mjs.map +1 -0
- package/dist/utils/mod-attrs.d.ts +8 -0
- package/dist/utils/mod-attrs.js +21 -0
- package/dist/utils/mod-attrs.js.map +1 -0
- package/dist/utils/okhsl-to-rgb.js +296 -0
- package/dist/utils/okhsl-to-rgb.js.map +1 -0
- package/dist/utils/okhsl-to-rgb.mjs +296 -0
- package/dist/utils/okhsl-to-rgb.mjs.map +1 -0
- package/dist/utils/process-tokens.d.ts +31 -0
- package/dist/utils/process-tokens.js +171 -0
- package/dist/utils/process-tokens.js.map +1 -0
- package/dist/utils/process-tokens.mjs +28 -0
- package/dist/utils/process-tokens.mjs.map +1 -0
- package/dist/utils/resolve-recipes.d.ts +17 -0
- package/dist/utils/resolve-recipes.js +143 -0
- package/dist/utils/resolve-recipes.js.map +1 -0
- package/dist/utils/resolve-recipes.mjs +143 -0
- package/dist/utils/resolve-recipes.mjs.map +1 -0
- package/dist/utils/string.js +8 -0
- package/dist/utils/string.js.map +1 -0
- package/dist/utils/string.mjs +8 -0
- package/dist/utils/string.mjs.map +1 -0
- package/dist/utils/styles.d.mts +18 -0
- package/dist/utils/styles.d.ts +183 -0
- package/dist/utils/styles.js +585 -0
- package/dist/utils/styles.js.map +1 -0
- package/dist/utils/styles.mjs +346 -0
- package/dist/utils/styles.mjs.map +1 -0
- package/dist/utils/typography.d.ts +36 -0
- package/dist/utils/typography.js +53 -0
- package/dist/utils/typography.js.map +1 -0
- package/dist/utils/warnings.d.ts +16 -0
- package/dist/utils/warnings.js +16 -0
- package/dist/utils/warnings.js.map +1 -0
- package/dist/zero/babel.d.mts +108 -0
- package/dist/zero/babel.mjs +282 -0
- package/dist/zero/babel.mjs.map +1 -0
- package/dist/zero/index.d.mts +3 -0
- package/dist/zero/index.mjs +4 -0
- package/dist/zero/next.d.mts +60 -0
- package/dist/zero/next.mjs +78 -0
- package/dist/zero/next.mjs.map +1 -0
- package/package.json +127 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
//#region src/keyframes/index.ts
|
|
2
|
+
const KEYFRAMES_KEY = "@keyframes";
|
|
3
|
+
/**
|
|
4
|
+
* Pattern to extract animation names from CSS animation property values.
|
|
5
|
+
* Animation name is typically the first identifier in the shorthand.
|
|
6
|
+
* Handles: "fadeIn 300ms ease-in", "pulse 1s infinite", etc.
|
|
7
|
+
*
|
|
8
|
+
* CSS animation shorthand order (all optional except name):
|
|
9
|
+
* animation: name | duration | timing | delay | iteration | direction | fill-mode | play-state
|
|
10
|
+
*
|
|
11
|
+
* Animation names must:
|
|
12
|
+
* - Start with a letter, underscore, or hyphen (but not a digit or CSS keyword)
|
|
13
|
+
* - Not be CSS keywords: none, initial, inherit, unset, revert
|
|
14
|
+
*/
|
|
15
|
+
const CSS_KEYWORDS = new Set([
|
|
16
|
+
"none",
|
|
17
|
+
"initial",
|
|
18
|
+
"inherit",
|
|
19
|
+
"unset",
|
|
20
|
+
"revert",
|
|
21
|
+
"auto",
|
|
22
|
+
"normal",
|
|
23
|
+
"running",
|
|
24
|
+
"paused"
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* Pattern to match animation name at the start of an animation value.
|
|
28
|
+
* Must start with letter, underscore, or hyphen (not digit).
|
|
29
|
+
*/
|
|
30
|
+
const ANIMATION_NAME_PATTERN = /^([a-zA-Z_-][a-zA-Z0-9_-]*)/;
|
|
31
|
+
/**
|
|
32
|
+
* Check if styles object has local @keyframes definition.
|
|
33
|
+
* Fast path: single property lookup.
|
|
34
|
+
*/
|
|
35
|
+
function hasLocalKeyframes(styles) {
|
|
36
|
+
return KEYFRAMES_KEY in styles;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Extract local @keyframes from styles object.
|
|
40
|
+
* Returns null if no local keyframes (fast path).
|
|
41
|
+
*/
|
|
42
|
+
function extractLocalKeyframes(styles) {
|
|
43
|
+
const keyframes = styles[KEYFRAMES_KEY];
|
|
44
|
+
if (!keyframes || typeof keyframes !== "object") return null;
|
|
45
|
+
return keyframes;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Merge local and global keyframes.
|
|
49
|
+
* Local keyframes take priority over global.
|
|
50
|
+
* Returns null if no keyframes exist (fast path).
|
|
51
|
+
*/
|
|
52
|
+
function mergeKeyframes(local, global) {
|
|
53
|
+
if (!local && !global) return null;
|
|
54
|
+
if (!local) return global;
|
|
55
|
+
if (!global) return local;
|
|
56
|
+
return {
|
|
57
|
+
...global,
|
|
58
|
+
...local
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract animation name from a single animation value.
|
|
63
|
+
* Returns null if no valid name found.
|
|
64
|
+
*
|
|
65
|
+
* Examples:
|
|
66
|
+
* - "fadeIn 300ms ease-in" → "fadeIn"
|
|
67
|
+
* - "1s pulse infinite" → "pulse" (name can be anywhere)
|
|
68
|
+
* - "none" → null (CSS keyword)
|
|
69
|
+
* - "300ms ease-in" → null (no name, just duration/timing)
|
|
70
|
+
*/
|
|
71
|
+
function extractAnimationNameFromValue(value) {
|
|
72
|
+
const trimmed = value.trim();
|
|
73
|
+
if (!trimmed) return null;
|
|
74
|
+
const parts = trimmed.split(/\s+/);
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
if (CSS_KEYWORDS.has(part.toLowerCase())) continue;
|
|
77
|
+
if (/^-?[\d.]+m?s$/i.test(part)) continue;
|
|
78
|
+
if (part === "infinite" || /^\d+$/.test(part)) continue;
|
|
79
|
+
if ([
|
|
80
|
+
"normal",
|
|
81
|
+
"reverse",
|
|
82
|
+
"alternate",
|
|
83
|
+
"alternate-reverse"
|
|
84
|
+
].includes(part.toLowerCase())) continue;
|
|
85
|
+
if ([
|
|
86
|
+
"forwards",
|
|
87
|
+
"backwards",
|
|
88
|
+
"both"
|
|
89
|
+
].includes(part.toLowerCase())) continue;
|
|
90
|
+
if (["running", "paused"].includes(part.toLowerCase())) continue;
|
|
91
|
+
if (/^(ease|linear|ease-in|ease-out|ease-in-out|step-start|step-end)$/i.test(part)) continue;
|
|
92
|
+
if (/^(cubic-bezier|steps)\(/i.test(part)) continue;
|
|
93
|
+
const match = ANIMATION_NAME_PATTERN.exec(part);
|
|
94
|
+
if (match) return match[1];
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Extract all animation names from an animation property value.
|
|
100
|
+
* Handles multiple animations separated by commas.
|
|
101
|
+
*
|
|
102
|
+
* Example: "fadeIn 300ms, slideIn 500ms ease-out" → ["fadeIn", "slideIn"]
|
|
103
|
+
*/
|
|
104
|
+
function extractAnimationNamesFromAnimationValue(value) {
|
|
105
|
+
const names = [];
|
|
106
|
+
const animations = value.split(",");
|
|
107
|
+
for (const animation of animations) {
|
|
108
|
+
const name = extractAnimationNameFromValue(animation);
|
|
109
|
+
if (name && !names.includes(name)) names.push(name);
|
|
110
|
+
}
|
|
111
|
+
return names;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Extract animation names from a style value (handles mappings and arrays).
|
|
115
|
+
*/
|
|
116
|
+
function extractAnimationNamesFromStyleValue(value, names) {
|
|
117
|
+
if (typeof value === "string") for (const name of extractAnimationNamesFromAnimationValue(value)) names.add(name);
|
|
118
|
+
else if (Array.isArray(value)) for (const v of value) extractAnimationNamesFromStyleValue(v, names);
|
|
119
|
+
else if (value && typeof value === "object") for (const v of Object.values(value)) extractAnimationNamesFromStyleValue(v, names);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Extract all animation names referenced in styles.
|
|
123
|
+
* Scans 'animation' and 'animationName' properties including in state mappings.
|
|
124
|
+
* Returns empty set if no animation properties found (fast path).
|
|
125
|
+
*/
|
|
126
|
+
function extractAnimationNamesFromStyles(styles) {
|
|
127
|
+
const names = /* @__PURE__ */ new Set();
|
|
128
|
+
if ("animation" in styles) extractAnimationNamesFromStyleValue(styles.animation, names);
|
|
129
|
+
if ("animationName" in styles) extractAnimationNamesFromStyleValue(styles.animationName, names);
|
|
130
|
+
for (const [key, value] of Object.entries(styles)) {
|
|
131
|
+
if (key === "$" || key === KEYFRAMES_KEY) continue;
|
|
132
|
+
if ((key.startsWith("&") || key.startsWith(".") || /^[A-Z]/.test(key)) && value && typeof value === "object") {
|
|
133
|
+
const nestedNames = extractAnimationNamesFromStyles(value);
|
|
134
|
+
for (const name of nestedNames) names.add(name);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return names;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Filter keyframes to only those that are actually used.
|
|
141
|
+
* Returns null if no keyframes are used (fast path).
|
|
142
|
+
*/
|
|
143
|
+
function filterUsedKeyframes(keyframes, usedNames) {
|
|
144
|
+
if (!keyframes || usedNames.size === 0) return null;
|
|
145
|
+
const used = {};
|
|
146
|
+
let hasAny = false;
|
|
147
|
+
for (const name of usedNames) if (keyframes[name]) {
|
|
148
|
+
used[name] = keyframes[name];
|
|
149
|
+
hasAny = true;
|
|
150
|
+
}
|
|
151
|
+
return hasAny ? used : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
export { extractAnimationNamesFromStyles, extractLocalKeyframes, filterUsedKeyframes, hasLocalKeyframes, mergeKeyframes };
|
|
156
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/keyframes/index.ts"],"sourcesContent":["/**\n * Keyframes Utilities\n *\n * Optimized utilities for extracting and processing keyframes in styles.\n * Designed for zero overhead when no keyframes are used.\n */\n\nimport { getGlobalKeyframes, hasGlobalKeyframes } from '../config';\nimport type { KeyframesSteps } from '../injector/types';\nimport type { Styles } from '../styles/types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst KEYFRAMES_KEY = '@keyframes';\n\n/**\n * Pattern to extract animation names from CSS animation property values.\n * Animation name is typically the first identifier in the shorthand.\n * Handles: \"fadeIn 300ms ease-in\", \"pulse 1s infinite\", etc.\n *\n * CSS animation shorthand order (all optional except name):\n * animation: name | duration | timing | delay | iteration | direction | fill-mode | play-state\n *\n * Animation names must:\n * - Start with a letter, underscore, or hyphen (but not a digit or CSS keyword)\n * - Not be CSS keywords: none, initial, inherit, unset, revert\n */\nconst CSS_KEYWORDS = new Set([\n 'none',\n 'initial',\n 'inherit',\n 'unset',\n 'revert',\n 'auto',\n 'normal',\n 'running',\n 'paused',\n]);\n\n/**\n * Pattern to match animation name at the start of an animation value.\n * Must start with letter, underscore, or hyphen (not digit).\n */\nconst ANIMATION_NAME_PATTERN = /^([a-zA-Z_-][a-zA-Z0-9_-]*)/;\n\n// ============================================================================\n// Extraction Functions\n// ============================================================================\n\n/**\n * Check if styles object has local @keyframes definition.\n * Fast path: single property lookup.\n */\nexport function hasLocalKeyframes(styles: Styles): boolean {\n return KEYFRAMES_KEY in styles;\n}\n\n/**\n * Extract local @keyframes from styles object.\n * Returns null if no local keyframes (fast path).\n */\nexport function extractLocalKeyframes(\n styles: Styles,\n): Record<string, KeyframesSteps> | null {\n const keyframes = styles[KEYFRAMES_KEY];\n if (!keyframes || typeof keyframes !== 'object') {\n return null;\n }\n return keyframes as Record<string, KeyframesSteps>;\n}\n\n/**\n * Merge local and global keyframes.\n * Local keyframes take priority over global.\n * Returns null if no keyframes exist (fast path).\n */\nexport function mergeKeyframes(\n local: Record<string, KeyframesSteps> | null,\n global: Record<string, KeyframesSteps> | null,\n): Record<string, KeyframesSteps> | null {\n if (!local && !global) return null;\n if (!local) return global;\n if (!global) return local;\n // Local overrides global\n return { ...global, ...local };\n}\n\n/**\n * Get merged keyframes for styles (local + global).\n * Returns null if no keyframes defined anywhere (fast path).\n */\nexport function getKeyframesForStyles(\n styles: Styles,\n): Record<string, KeyframesSteps> | null {\n const local = extractLocalKeyframes(styles);\n const global = hasGlobalKeyframes() ? getGlobalKeyframes() : null;\n return mergeKeyframes(local, global);\n}\n\n// ============================================================================\n// Animation Name Extraction\n// ============================================================================\n\n/**\n * Extract animation name from a single animation value.\n * Returns null if no valid name found.\n *\n * Examples:\n * - \"fadeIn 300ms ease-in\" → \"fadeIn\"\n * - \"1s pulse infinite\" → \"pulse\" (name can be anywhere)\n * - \"none\" → null (CSS keyword)\n * - \"300ms ease-in\" → null (no name, just duration/timing)\n */\nfunction extractAnimationNameFromValue(value: string): string | null {\n const trimmed = value.trim();\n if (!trimmed) return null;\n\n // Split by whitespace and find the first valid animation name\n const parts = trimmed.split(/\\s+/);\n\n for (const part of parts) {\n // Skip CSS keywords\n if (CSS_KEYWORDS.has(part.toLowerCase())) continue;\n\n // Skip time values (e.g., 300ms, 1s, 0.5s)\n if (/^-?[\\d.]+m?s$/i.test(part)) continue;\n\n // Skip iteration counts (e.g., infinite, 3)\n if (part === 'infinite' || /^\\d+$/.test(part)) continue;\n\n // Skip direction values\n if (\n ['normal', 'reverse', 'alternate', 'alternate-reverse'].includes(\n part.toLowerCase(),\n )\n )\n continue;\n\n // Skip fill-mode values\n if (['forwards', 'backwards', 'both'].includes(part.toLowerCase()))\n continue;\n\n // Skip play-state values\n if (['running', 'paused'].includes(part.toLowerCase())) continue;\n\n // Skip timing functions (ease, linear, ease-in, etc., or cubic-bezier/steps)\n if (\n /^(ease|linear|ease-in|ease-out|ease-in-out|step-start|step-end)$/i.test(\n part,\n )\n )\n continue;\n if (/^(cubic-bezier|steps)\\(/i.test(part)) continue;\n\n // Check if it looks like a valid animation name\n const match = ANIMATION_NAME_PATTERN.exec(part);\n if (match) {\n return match[1];\n }\n }\n\n return null;\n}\n\n/**\n * Extract all animation names from an animation property value.\n * Handles multiple animations separated by commas.\n *\n * Example: \"fadeIn 300ms, slideIn 500ms ease-out\" → [\"fadeIn\", \"slideIn\"]\n */\nfunction extractAnimationNamesFromAnimationValue(value: string): string[] {\n const names: string[] = [];\n\n // Split by comma for multiple animations\n const animations = value.split(',');\n\n for (const animation of animations) {\n const name = extractAnimationNameFromValue(animation);\n if (name && !names.includes(name)) {\n names.push(name);\n }\n }\n\n return names;\n}\n\n/**\n * Extract animation names from a style value (handles mappings and arrays).\n */\nfunction extractAnimationNamesFromStyleValue(\n value: unknown,\n names: Set<string>,\n): void {\n if (typeof value === 'string') {\n for (const name of extractAnimationNamesFromAnimationValue(value)) {\n names.add(name);\n }\n } else if (Array.isArray(value)) {\n // Responsive array\n for (const v of value) {\n extractAnimationNamesFromStyleValue(v, names);\n }\n } else if (value && typeof value === 'object') {\n // State mapping\n for (const v of Object.values(value)) {\n extractAnimationNamesFromStyleValue(v, names);\n }\n }\n}\n\n/**\n * Extract all animation names referenced in styles.\n * Scans 'animation' and 'animationName' properties including in state mappings.\n * Returns empty set if no animation properties found (fast path).\n */\nexport function extractAnimationNamesFromStyles(styles: Styles): Set<string> {\n const names = new Set<string>();\n\n // Check animation property\n if ('animation' in styles) {\n extractAnimationNamesFromStyleValue(styles.animation, names);\n }\n\n // Check animationName property\n if ('animationName' in styles) {\n extractAnimationNamesFromStyleValue(styles.animationName, names);\n }\n\n // Check nested selectors (sub-elements)\n for (const [key, value] of Object.entries(styles)) {\n // Skip non-selector keys and special keys\n if (key === '$' || key === KEYFRAMES_KEY) continue;\n\n // Check if it's a selector (starts with &, ., or uppercase)\n if (\n (key.startsWith('&') || key.startsWith('.') || /^[A-Z]/.test(key)) &&\n value &&\n typeof value === 'object'\n ) {\n // Recursively extract from nested styles\n const nestedNames = extractAnimationNamesFromStyles(value as Styles);\n for (const name of nestedNames) {\n names.add(name);\n }\n }\n }\n\n return names;\n}\n\n// ============================================================================\n// Name Replacement\n// ============================================================================\n\n/**\n * Replace animation names in CSS declarations with injected names.\n * Optimized to avoid regex creation - uses simple string replacement.\n *\n * @param declarations CSS declarations string\n * @param nameMap Map from original name to injected name (only contains names that differ)\n * @returns Updated declarations string\n */\nexport function replaceAnimationNames(\n declarations: string,\n nameMap: Map<string, string>,\n): string {\n // Fast path: no animation properties\n if (!declarations.includes('animation')) return declarations;\n\n // Parse and replace\n const parts = declarations.split(';');\n let modified = false;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const colonIdx = part.indexOf(':');\n if (colonIdx === -1) continue;\n\n const prop = part.slice(0, colonIdx).trim().toLowerCase();\n\n if (prop === 'animation' || prop === 'animation-name') {\n const prefix = part.slice(0, colonIdx + 1);\n let value = part.slice(colonIdx + 1);\n\n // Replace each animation name using simple word replacement\n for (const [original, injected] of nameMap) {\n // Simple word boundary replacement without regex\n const newValue = replaceWord(value, original, injected);\n if (newValue !== value) {\n value = newValue;\n modified = true;\n }\n }\n\n parts[i] = prefix + value;\n }\n }\n\n return modified ? parts.join(';') : declarations;\n}\n\n/**\n * Replace a word in a string (word boundary aware, no regex).\n */\nfunction replaceWord(str: string, word: string, replacement: string): string {\n let result = str;\n let idx = 0;\n\n while ((idx = result.indexOf(word, idx)) !== -1) {\n // Check word boundaries\n const before = idx === 0 ? ' ' : result[idx - 1];\n const after =\n idx + word.length >= result.length ? ' ' : result[idx + word.length];\n\n const isWordBoundaryBefore = !/[a-zA-Z0-9_-]/.test(before);\n const isWordBoundaryAfter = !/[a-zA-Z0-9_-]/.test(after);\n\n if (isWordBoundaryBefore && isWordBoundaryAfter) {\n result =\n result.slice(0, idx) + replacement + result.slice(idx + word.length);\n idx += replacement.length;\n } else {\n idx += word.length;\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Filter Functions\n// ============================================================================\n\n/**\n * Filter keyframes to only those that are actually used.\n * Returns null if no keyframes are used (fast path).\n */\nexport function filterUsedKeyframes(\n keyframes: Record<string, KeyframesSteps> | null,\n usedNames: Set<string>,\n): Record<string, KeyframesSteps> | null {\n if (!keyframes || usedNames.size === 0) return null;\n\n const used: Record<string, KeyframesSteps> = {};\n let hasAny = false;\n\n for (const name of usedNames) {\n if (keyframes[name]) {\n used[name] = keyframes[name];\n hasAny = true;\n }\n }\n\n return hasAny ? used : null;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { COLOR_FUNCS, RE_HEX, RE_NUMBER, RE_RAW_UNIT, RE_UNIT_NUM, VALUE_KEYWORDS } from "./const.js";
|
|
2
|
+
import { Bucket } from "./types.js";
|
|
3
|
+
import { StyleParser } from "./parser.js";
|
|
4
|
+
import { getGlobalPredefinedTokens } from "../utils/styles.js";
|
|
5
|
+
|
|
6
|
+
//#region src/parser/classify.ts
|
|
7
|
+
/**
|
|
8
|
+
* Re-parses a value through the parser until it stabilizes (no changes)
|
|
9
|
+
* or max iterations reached. This allows units to reference other units.
|
|
10
|
+
* Example: { x: '8px', y: '2x' } -> '1y' resolves to '16px'
|
|
11
|
+
*/
|
|
12
|
+
function resolveUntilStable(value, opts, recurse, maxIterations = 10) {
|
|
13
|
+
let current = value;
|
|
14
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
15
|
+
const unitMatch = current.match(RE_UNIT_NUM);
|
|
16
|
+
if (!unitMatch) break;
|
|
17
|
+
const unitName = unitMatch[1];
|
|
18
|
+
if (!opts.units || !(unitName in opts.units)) break;
|
|
19
|
+
const result = recurse(current);
|
|
20
|
+
if (result.output === current) break;
|
|
21
|
+
current = result.output;
|
|
22
|
+
}
|
|
23
|
+
return current;
|
|
24
|
+
}
|
|
25
|
+
function classify(raw, opts, recurse) {
|
|
26
|
+
const token = raw.trim();
|
|
27
|
+
if (!token) return {
|
|
28
|
+
bucket: Bucket.Mod,
|
|
29
|
+
processed: ""
|
|
30
|
+
};
|
|
31
|
+
{
|
|
32
|
+
let depth = 0;
|
|
33
|
+
let inQuote = 0;
|
|
34
|
+
for (let i = 0; i < token.length; i++) {
|
|
35
|
+
const ch = token[i];
|
|
36
|
+
if (inQuote) {
|
|
37
|
+
if (ch === inQuote && token[i - 1] !== "\\") inQuote = 0;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (ch === "\"" || ch === "'") {
|
|
41
|
+
inQuote = ch;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (ch === "(") depth++;
|
|
45
|
+
else if (ch === ")") depth = Math.max(0, depth - 1);
|
|
46
|
+
}
|
|
47
|
+
if (depth !== 0) {
|
|
48
|
+
console.warn("tasty: skipped invalid function token with unmatched parentheses:", token);
|
|
49
|
+
return {
|
|
50
|
+
bucket: Bucket.Mod,
|
|
51
|
+
processed: ""
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (token.startsWith("\"") && token.endsWith("\"") || token.startsWith("'") && token.endsWith("'")) return {
|
|
56
|
+
bucket: Bucket.Value,
|
|
57
|
+
processed: token
|
|
58
|
+
};
|
|
59
|
+
if (token.startsWith("$$")) {
|
|
60
|
+
const name = token.slice(2);
|
|
61
|
+
if (/^[a-z_][a-z0-9-_]*$/i.test(name)) return {
|
|
62
|
+
bucket: Bucket.Value,
|
|
63
|
+
processed: `--${name}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (token.startsWith("##")) {
|
|
67
|
+
const name = token.slice(2);
|
|
68
|
+
if (/^[a-z_][a-z0-9-_]*$/i.test(name)) return {
|
|
69
|
+
bucket: Bucket.Value,
|
|
70
|
+
processed: `--${name}-color`
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (token === "#current") return {
|
|
74
|
+
bucket: Bucket.Color,
|
|
75
|
+
processed: "currentcolor"
|
|
76
|
+
};
|
|
77
|
+
const currentAlphaMatch = token.match(/^#current\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
|
|
78
|
+
if (currentAlphaMatch) {
|
|
79
|
+
const rawAlpha = currentAlphaMatch[1];
|
|
80
|
+
let percentage;
|
|
81
|
+
if (rawAlpha.startsWith("$")) percentage = `calc(var(--${rawAlpha.slice(1)}) * 100%)`;
|
|
82
|
+
else if (rawAlpha === "0") percentage = "0%";
|
|
83
|
+
else percentage = `${parseFloat("." + rawAlpha) * 100}%`;
|
|
84
|
+
return {
|
|
85
|
+
bucket: Bucket.Color,
|
|
86
|
+
processed: `color-mix(in oklab, currentcolor ${percentage}, transparent)`
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (token[0] === "$" || token[0] === "#") {
|
|
90
|
+
const predefinedTokens = getGlobalPredefinedTokens();
|
|
91
|
+
if (predefinedTokens) {
|
|
92
|
+
if (token in predefinedTokens) {
|
|
93
|
+
const tokenValue = predefinedTokens[token];
|
|
94
|
+
return classify(tokenValue.toLowerCase(), opts, recurse);
|
|
95
|
+
}
|
|
96
|
+
if (token[0] === "#") {
|
|
97
|
+
const alphaMatch = token.match(/^(#[a-z0-9-]+)\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
|
|
98
|
+
if (alphaMatch) {
|
|
99
|
+
const [, baseToken, rawAlpha] = alphaMatch;
|
|
100
|
+
if (baseToken in predefinedTokens) {
|
|
101
|
+
const resolvedValue = predefinedTokens[baseToken];
|
|
102
|
+
if (resolvedValue.startsWith("#")) return classify(`${resolvedValue.toLowerCase()}.${rawAlpha}`, opts, recurse);
|
|
103
|
+
const funcMatch = resolvedValue.match(/^(rgba?|hsla?|hwb|oklab|oklch|lab|lch|color|okhsl|device-cmyk|gray|color-mix|color-contrast)\((.+)\)$/i);
|
|
104
|
+
if (funcMatch) {
|
|
105
|
+
const [, funcName, args] = funcMatch;
|
|
106
|
+
let alpha;
|
|
107
|
+
if (rawAlpha.startsWith("$")) alpha = `var(--${rawAlpha.slice(1)})`;
|
|
108
|
+
else alpha = rawAlpha === "0" ? "0" : `.${rawAlpha}`;
|
|
109
|
+
const normalizedFunc = funcName.replace(/a$/i, "").toLowerCase();
|
|
110
|
+
const normalizeArgs = (a) => {
|
|
111
|
+
let result = "";
|
|
112
|
+
let depth = 0;
|
|
113
|
+
for (let i = 0; i < a.length; i++) {
|
|
114
|
+
const c = a[i];
|
|
115
|
+
if (c === "(") {
|
|
116
|
+
depth++;
|
|
117
|
+
result += c;
|
|
118
|
+
} else if (c === ")") {
|
|
119
|
+
depth = Math.max(0, depth - 1);
|
|
120
|
+
result += c;
|
|
121
|
+
} else if (c === "," && depth === 0) {
|
|
122
|
+
while (i + 1 < a.length && /\s/.test(a[i + 1])) i++;
|
|
123
|
+
result += " ";
|
|
124
|
+
} else result += c;
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
};
|
|
128
|
+
const findLastTopLevel = (str, ch) => {
|
|
129
|
+
let depth = 0;
|
|
130
|
+
for (let i = str.length - 1; i >= 0; i--) {
|
|
131
|
+
const c = str[i];
|
|
132
|
+
if (c === ")") depth++;
|
|
133
|
+
else if (c === "(") depth = Math.max(0, depth - 1);
|
|
134
|
+
else if (c === ch && depth === 0) return i;
|
|
135
|
+
}
|
|
136
|
+
return -1;
|
|
137
|
+
};
|
|
138
|
+
const slashIdx = findLastTopLevel(args, "/");
|
|
139
|
+
const hasModernAlpha = slashIdx !== -1;
|
|
140
|
+
let topLevelCommaCount = 0;
|
|
141
|
+
let lastTopLevelComma = -1;
|
|
142
|
+
{
|
|
143
|
+
let depth = 0;
|
|
144
|
+
for (let i = 0; i < args.length; i++) {
|
|
145
|
+
const c = args[i];
|
|
146
|
+
if (c === "(") depth++;
|
|
147
|
+
else if (c === ")") depth = Math.max(0, depth - 1);
|
|
148
|
+
else if (c === "," && depth === 0) {
|
|
149
|
+
topLevelCommaCount++;
|
|
150
|
+
lastTopLevelComma = i;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const hasLegacyAlpha = !hasModernAlpha && /a$/i.test(funcName) && topLevelCommaCount === 3;
|
|
155
|
+
const constructed = `${normalizedFunc}(${hasModernAlpha || hasLegacyAlpha ? normalizeArgs(hasModernAlpha ? args.slice(0, slashIdx).trim() : args.slice(0, lastTopLevelComma).trim()) : normalizeArgs(args)} / ${alpha})`;
|
|
156
|
+
if (!COLOR_FUNCS.has(normalizedFunc) && opts.funcs && normalizedFunc in opts.funcs) return classify(constructed, opts, recurse);
|
|
157
|
+
return {
|
|
158
|
+
bucket: Bucket.Color,
|
|
159
|
+
processed: constructed
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return classify(`${resolvedValue}.${rawAlpha}`, opts, recurse);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (token.match(/^var\(--([a-z0-9-]+)-color\)$/)) return {
|
|
169
|
+
bucket: Bucket.Color,
|
|
170
|
+
processed: token
|
|
171
|
+
};
|
|
172
|
+
if (token.startsWith("url(")) return {
|
|
173
|
+
bucket: Bucket.Value,
|
|
174
|
+
processed: token
|
|
175
|
+
};
|
|
176
|
+
if (token[0] === "$") {
|
|
177
|
+
const identMatch = token.match(/^\$([a-z_][a-z0-9-_]*)$/);
|
|
178
|
+
if (identMatch) {
|
|
179
|
+
const name = identMatch[1];
|
|
180
|
+
const processed = `var(--${name})`;
|
|
181
|
+
return {
|
|
182
|
+
bucket: name.endsWith("-color") ? Bucket.Color : Bucket.Value,
|
|
183
|
+
processed
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (token[0] === "#" && token.length > 1) {
|
|
188
|
+
const alphaMatch = token.match(/^#([a-z0-9-]+)\.(\$[a-z_][a-z0-9-_]*|[0-9]+)$/i);
|
|
189
|
+
if (alphaMatch) {
|
|
190
|
+
const [, base, rawAlpha] = alphaMatch;
|
|
191
|
+
let alpha;
|
|
192
|
+
if (rawAlpha.startsWith("$")) alpha = `var(--${rawAlpha.slice(1)})`;
|
|
193
|
+
else if (rawAlpha === "0") alpha = "0";
|
|
194
|
+
else alpha = `.${rawAlpha}`;
|
|
195
|
+
return {
|
|
196
|
+
bucket: Bucket.Color,
|
|
197
|
+
processed: `rgb(var(--${base}-color-rgb) / ${alpha})`
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const name = token.slice(1);
|
|
201
|
+
if (RE_HEX.test(name)) return {
|
|
202
|
+
bucket: Bucket.Color,
|
|
203
|
+
processed: `var(--${name}-color, #${name})`
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
bucket: Bucket.Color,
|
|
207
|
+
processed: `var(--${name}-color)`
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const openIdx = token.indexOf("(");
|
|
211
|
+
if (openIdx > 0 && token.endsWith(")")) {
|
|
212
|
+
const fname = token.slice(0, openIdx);
|
|
213
|
+
const inner = token.slice(openIdx + 1, -1);
|
|
214
|
+
if (COLOR_FUNCS.has(fname)) {
|
|
215
|
+
const argProcessed = recurse(inner).output.replace(/,\s+/g, ",");
|
|
216
|
+
return {
|
|
217
|
+
bucket: Bucket.Color,
|
|
218
|
+
processed: `${fname}(${argProcessed})`
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (opts.funcs && fname in opts.funcs) {
|
|
222
|
+
const tmp = new StyleParser(opts).process(inner);
|
|
223
|
+
return classify(opts.funcs[fname](tmp.groups), {
|
|
224
|
+
...opts,
|
|
225
|
+
funcs: void 0
|
|
226
|
+
}, recurse);
|
|
227
|
+
}
|
|
228
|
+
const argProcessed = recurse(inner).output;
|
|
229
|
+
return {
|
|
230
|
+
bucket: Bucket.Value,
|
|
231
|
+
processed: `${fname}(${argProcessed})`
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (token.startsWith("(") && token.endsWith(")")) {
|
|
235
|
+
const colorMatch = token.slice(1, -1).match(/^#([a-z0-9-]+)\s*,\s*(.*)$/i);
|
|
236
|
+
if (colorMatch) {
|
|
237
|
+
const [, name, fallback] = colorMatch;
|
|
238
|
+
const processedFallback = recurse(fallback).output;
|
|
239
|
+
return {
|
|
240
|
+
bucket: Bucket.Color,
|
|
241
|
+
processed: `var(--${name}-color, ${processedFallback})`
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (token.startsWith("(") && token.endsWith(")")) {
|
|
246
|
+
const match = token.slice(1, -1).match(/^\$([a-z_][a-z0-9-_]*)\s*,\s*(.*)$/);
|
|
247
|
+
if (match) {
|
|
248
|
+
const [, name, fallback] = match;
|
|
249
|
+
const processedFallback = recurse(fallback).output;
|
|
250
|
+
return {
|
|
251
|
+
bucket: name.endsWith("-color") ? Bucket.Color : Bucket.Value,
|
|
252
|
+
processed: `var(--${name}, ${processedFallback})`
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (token[0] === "(" && token[token.length - 1] === ")") {
|
|
257
|
+
const innerProcessed = recurse(token.slice(1, -1)).output;
|
|
258
|
+
return {
|
|
259
|
+
bucket: Bucket.Value,
|
|
260
|
+
processed: `calc(${innerProcessed})`
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
const um = token.match(RE_UNIT_NUM);
|
|
264
|
+
if (um) {
|
|
265
|
+
const unit = um[1];
|
|
266
|
+
const numericPart = parseFloat(token.slice(0, -unit.length));
|
|
267
|
+
const handler = opts.units && opts.units[unit];
|
|
268
|
+
if (handler) if (typeof handler === "string") {
|
|
269
|
+
const rawMatch = handler.match(RE_RAW_UNIT);
|
|
270
|
+
if (rawMatch) {
|
|
271
|
+
const [, baseNum, cssUnit] = rawMatch;
|
|
272
|
+
const resolved = resolveUntilStable(`${numericPart * parseFloat(baseNum)}${cssUnit}`, opts, recurse);
|
|
273
|
+
return {
|
|
274
|
+
bucket: Bucket.Value,
|
|
275
|
+
processed: resolved
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const base = handler;
|
|
279
|
+
if (numericPart === 1) return {
|
|
280
|
+
bucket: Bucket.Value,
|
|
281
|
+
processed: base
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
bucket: Bucket.Value,
|
|
285
|
+
processed: `calc(${numericPart} * ${base})`
|
|
286
|
+
};
|
|
287
|
+
} else {
|
|
288
|
+
const inner = handler(numericPart);
|
|
289
|
+
return {
|
|
290
|
+
bucket: Bucket.Value,
|
|
291
|
+
processed: inner
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (/^[+-]?(?:\d*\.\d+|\d+)[a-z%]+$/.test(token)) return {
|
|
296
|
+
bucket: Bucket.Value,
|
|
297
|
+
processed: token
|
|
298
|
+
};
|
|
299
|
+
if (RE_NUMBER.test(token)) return {
|
|
300
|
+
bucket: Bucket.Value,
|
|
301
|
+
processed: token
|
|
302
|
+
};
|
|
303
|
+
if (VALUE_KEYWORDS.has(token)) return {
|
|
304
|
+
bucket: Bucket.Value,
|
|
305
|
+
processed: token
|
|
306
|
+
};
|
|
307
|
+
if (token === "transparent" || token === "currentcolor") return {
|
|
308
|
+
bucket: Bucket.Color,
|
|
309
|
+
processed: token
|
|
310
|
+
};
|
|
311
|
+
return {
|
|
312
|
+
bucket: Bucket.Mod,
|
|
313
|
+
processed: token
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
//#endregion
|
|
318
|
+
export { classify };
|
|
319
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.js","names":[],"sources":["../../src/parser/classify.ts"],"sourcesContent":["import { 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({ tokens: {...} }))\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: `rgb(var(--${base}-color-rgb) / ${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 so that\n // code such as `scrollbar={10}` resolves correctly.\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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"}
|