@tenphi/tasty 1.1.0 → 1.2.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/dist/_virtual/_rolldown/runtime.js +1 -2
- package/dist/chunks/cacheKey.d.ts +1 -0
- package/dist/chunks/cacheKey.js +1 -2
- package/dist/chunks/cacheKey.js.map +1 -1
- package/dist/chunks/definitions.js +1 -2
- package/dist/chunks/definitions.js.map +1 -1
- package/dist/chunks/index.d.ts +1 -0
- package/dist/chunks/renderChunk.d.ts +1 -0
- package/dist/chunks/renderChunk.js +1 -2
- package/dist/chunks/renderChunk.js.map +1 -1
- package/dist/config.js +1 -2
- package/dist/config.js.map +1 -1
- package/dist/core/index.d.ts +0 -1
- package/dist/core/index.js +1 -2
- package/dist/counter-style/index.js +1 -1
- package/dist/counter-style/index.js.map +1 -1
- package/dist/debug.js +1 -2
- package/dist/debug.js.map +1 -1
- package/dist/font-face/index.js +1 -1
- package/dist/font-face/index.js.map +1 -1
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/resolve-ssr-collector.js +1 -2
- package/dist/hooks/resolve-ssr-collector.js.map +1 -1
- package/dist/hooks/useCounterStyle.js +1 -2
- package/dist/hooks/useCounterStyle.js.map +1 -1
- package/dist/hooks/useFontFace.js +1 -2
- package/dist/hooks/useFontFace.js.map +1 -1
- package/dist/hooks/useGlobalStyles.js +1 -2
- package/dist/hooks/useGlobalStyles.js.map +1 -1
- package/dist/hooks/useKeyframes.js +1 -2
- package/dist/hooks/useKeyframes.js.map +1 -1
- package/dist/hooks/useProperty.js +1 -2
- package/dist/hooks/useProperty.js.map +1 -1
- package/dist/hooks/useRawCSS.js +1 -2
- package/dist/hooks/useRawCSS.js.map +1 -1
- package/dist/hooks/useStyles.js +1 -2
- package/dist/hooks/useStyles.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -2
- package/dist/injector/index.js +2 -3
- package/dist/injector/index.js.map +1 -1
- package/dist/injector/injector.js +1 -2
- package/dist/injector/injector.js.map +1 -1
- package/dist/injector/sheet-manager.js +1 -2
- package/dist/injector/sheet-manager.js.map +1 -1
- package/dist/keyframes/index.js +1 -1
- package/dist/parser/classify.js +1 -2
- package/dist/parser/classify.js.map +1 -1
- package/dist/parser/const.js +14 -3
- package/dist/parser/const.js.map +1 -1
- package/dist/parser/lru.js +1 -1
- package/dist/parser/lru.js.map +1 -1
- package/dist/parser/parser.js +1 -2
- package/dist/parser/parser.js.map +1 -1
- package/dist/parser/tokenizer.js +1 -1
- package/dist/parser/tokenizer.js.map +1 -1
- package/dist/parser/types.js +1 -1
- package/dist/parser/types.js.map +1 -1
- package/dist/pipeline/conditions.js +1 -1
- package/dist/pipeline/conditions.js.map +1 -1
- package/dist/pipeline/exclusive.js +1 -2
- package/dist/pipeline/exclusive.js.map +1 -1
- package/dist/pipeline/index.js +2 -3
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/materialize.js +1 -2
- package/dist/pipeline/materialize.js.map +1 -1
- package/dist/pipeline/parseStateKey.js +1 -2
- package/dist/pipeline/parseStateKey.js.map +1 -1
- package/dist/pipeline/simplify.js +1 -2
- package/dist/pipeline/simplify.js.map +1 -1
- package/dist/pipeline/warnings.js +1 -1
- package/dist/plugins/index.d.ts +2 -0
- package/dist/plugins/okhsl-plugin.js +1 -2
- package/dist/plugins/okhsl-plugin.js.map +1 -1
- package/dist/properties/index.js +2 -3
- package/dist/properties/index.js.map +1 -1
- package/dist/properties/property-type-resolver.js +1 -2
- package/dist/properties/property-type-resolver.js.map +1 -1
- package/dist/ssr/astro.js +1 -2
- package/dist/ssr/astro.js.map +1 -1
- package/dist/ssr/async-storage.js +1 -2
- package/dist/ssr/async-storage.js.map +1 -1
- package/dist/ssr/collect-auto-properties.js +1 -2
- package/dist/ssr/collect-auto-properties.js.map +1 -1
- package/dist/ssr/collector.js +1 -2
- package/dist/ssr/collector.js.map +1 -1
- package/dist/ssr/context.d.ts +2 -2
- package/dist/ssr/context.js +1 -2
- package/dist/ssr/context.js.map +1 -1
- package/dist/ssr/format-global-rules.js +1 -1
- package/dist/ssr/format-keyframes.js +1 -2
- package/dist/ssr/format-keyframes.js.map +1 -1
- package/dist/ssr/format-property.js +1 -2
- package/dist/ssr/format-property.js.map +1 -1
- package/dist/ssr/format-rules.js +1 -1
- package/dist/ssr/hydrate.js +1 -2
- package/dist/ssr/hydrate.js.map +1 -1
- package/dist/ssr/index.js +1 -2
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/next.d.ts +2 -2
- package/dist/ssr/next.js +2 -4
- package/dist/ssr/next.js.map +1 -1
- package/dist/ssr/ssr-collector-ref.js +1 -1
- package/dist/states/index.js +1 -2
- package/dist/states/index.js.map +1 -1
- package/dist/static/index.js +1 -2
- package/dist/static/inject.d.ts +5 -0
- package/dist/static/inject.js +17 -0
- package/dist/static/inject.js.map +1 -0
- package/dist/static/tastyStatic.js +1 -2
- package/dist/static/tastyStatic.js.map +1 -1
- package/dist/static/types.js +1 -1
- package/dist/styles/border.d.ts +1 -1
- package/dist/styles/border.js +28 -22
- package/dist/styles/border.js.map +1 -1
- package/dist/styles/color.d.ts +1 -1
- package/dist/styles/color.js +2 -3
- package/dist/styles/color.js.map +1 -1
- package/dist/styles/const.js +17 -0
- package/dist/styles/const.js.map +1 -0
- package/dist/styles/createStyle.js +4 -5
- package/dist/styles/createStyle.js.map +1 -1
- package/dist/styles/dimension.js +15 -3
- package/dist/styles/dimension.js.map +1 -1
- package/dist/styles/directional.js +133 -0
- package/dist/styles/directional.js.map +1 -0
- package/dist/styles/display.d.ts +3 -10
- package/dist/styles/display.js +45 -39
- package/dist/styles/display.js.map +1 -1
- package/dist/styles/fade.d.ts +1 -1
- package/dist/styles/fade.js +9 -5
- package/dist/styles/fade.js.map +1 -1
- package/dist/styles/fill.d.ts +2 -2
- package/dist/styles/fill.js +3 -4
- package/dist/styles/fill.js.map +1 -1
- package/dist/styles/flow.js +1 -1
- package/dist/styles/gap.d.ts +1 -1
- package/dist/styles/gap.js +4 -3
- package/dist/styles/gap.js.map +1 -1
- package/dist/styles/height.d.ts +1 -1
- package/dist/styles/height.js +1 -2
- package/dist/styles/height.js.map +1 -1
- package/dist/styles/index.d.ts +0 -1
- package/dist/styles/index.js +3 -4
- package/dist/styles/index.js.map +1 -1
- package/dist/styles/inset.d.ts +1 -29
- package/dist/styles/inset.js +19 -135
- package/dist/styles/inset.js.map +1 -1
- package/dist/styles/list.d.ts +5 -5
- package/dist/styles/list.js +4 -2
- package/dist/styles/list.js.map +1 -1
- package/dist/styles/margin.d.ts +1 -1
- package/dist/styles/margin.js +17 -89
- package/dist/styles/margin.js.map +1 -1
- package/dist/styles/outline.d.ts +1 -1
- package/dist/styles/outline.js +6 -16
- package/dist/styles/outline.js.map +1 -1
- package/dist/styles/padding.d.ts +1 -1
- package/dist/styles/padding.js +17 -89
- package/dist/styles/padding.js.map +1 -1
- package/dist/styles/placement.d.ts +37 -0
- package/dist/styles/placement.js +74 -0
- package/dist/styles/placement.js.map +1 -0
- package/dist/styles/predefined.d.ts +4 -4
- package/dist/styles/predefined.js +8 -9
- package/dist/styles/predefined.js.map +1 -1
- package/dist/styles/preset.d.ts +1 -1
- package/dist/styles/preset.js +7 -7
- package/dist/styles/preset.js.map +1 -1
- package/dist/styles/radius.d.ts +1 -3
- package/dist/styles/radius.js +38 -6
- package/dist/styles/radius.js.map +1 -1
- package/dist/styles/scrollMargin.d.ts +24 -0
- package/dist/styles/scrollMargin.js +32 -0
- package/dist/styles/scrollMargin.js.map +1 -0
- package/dist/styles/scrollbar.d.ts +1 -1
- package/dist/styles/scrollbar.js +8 -5
- package/dist/styles/scrollbar.js.map +1 -1
- package/dist/styles/shadow.d.ts +1 -1
- package/dist/styles/shadow.js +4 -3
- package/dist/styles/shadow.js.map +1 -1
- package/dist/styles/shared.js +17 -0
- package/dist/styles/shared.js.map +1 -0
- package/dist/styles/transition.d.ts +1 -1
- package/dist/styles/transition.js +5 -4
- package/dist/styles/transition.js.map +1 -1
- package/dist/styles/types.d.ts +24 -7
- package/dist/styles/width.d.ts +1 -1
- package/dist/styles/width.js +1 -2
- package/dist/styles/width.js.map +1 -1
- package/dist/tasty.d.ts +2 -2
- package/dist/tasty.js +1 -2
- package/dist/tasty.js.map +1 -1
- package/dist/utils/cache-wrapper.js +1 -2
- package/dist/utils/cache-wrapper.js.map +1 -1
- package/dist/utils/case-converter.js +1 -1
- package/dist/utils/color-math.js +1 -1
- package/dist/utils/color-math.js.map +1 -1
- package/dist/utils/color-space.js +1 -2
- package/dist/utils/color-space.js.map +1 -1
- package/dist/utils/colors.js +1 -2
- package/dist/utils/colors.js.map +1 -1
- package/dist/utils/dotize.js +1 -1
- package/dist/utils/dotize.js.map +1 -1
- package/dist/utils/filter-base-props.js +1 -1
- package/dist/utils/get-display-name.js +1 -1
- package/dist/utils/has-keys.js +1 -1
- package/dist/utils/is-dev-env.js +1 -1
- package/dist/utils/is-dev-env.js.map +1 -1
- package/dist/utils/is-valid-element-type.js +1 -1
- package/dist/utils/merge-styles.js +1 -2
- package/dist/utils/merge-styles.js.map +1 -1
- package/dist/utils/mod-attrs.d.ts +0 -2
- package/dist/utils/mod-attrs.js +1 -2
- package/dist/utils/mod-attrs.js.map +1 -1
- package/dist/utils/process-tokens.js +2 -3
- package/dist/utils/process-tokens.js.map +1 -1
- package/dist/utils/resolve-recipes.js +1 -2
- package/dist/utils/resolve-recipes.js.map +1 -1
- package/dist/utils/selector-transform.js +1 -1
- package/dist/utils/string.js +1 -1
- package/dist/utils/styles.d.ts +1 -1
- package/dist/utils/styles.js +2 -3
- package/dist/utils/styles.js.map +1 -1
- package/dist/utils/typography.js +1 -1
- package/dist/utils/typography.js.map +1 -1
- package/dist/utils/warnings.js +1 -1
- package/dist/zero/babel.d.ts +17 -2
- package/dist/zero/babel.js +105 -41
- package/dist/zero/babel.js.map +1 -1
- package/dist/zero/css-writer.js +1 -2
- package/dist/zero/css-writer.js.map +1 -1
- package/dist/zero/extractor.js +1 -2
- package/dist/zero/extractor.js.map +1 -1
- package/dist/zero/index.js +1 -2
- package/dist/zero/next.d.ts +12 -0
- package/dist/zero/next.js +5 -4
- package/dist/zero/next.js.map +1 -1
- package/docs/styles.md +17 -0
- package/docs/tasty-static.md +87 -0
- package/package.json +11 -6
- package/dist/styles/align.d.ts +0 -15
- package/dist/styles/align.js +0 -14
- package/dist/styles/align.js.map +0 -1
- package/dist/styles/justify.d.ts +0 -15
- package/dist/styles/justify.js +0 -14
- package/dist/styles/justify.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseStateKey.js","names":[],"sources":["../../src/pipeline/parseStateKey.ts"],"sourcesContent":["/**\n * State Key Parser\n *\n * Parses state notation strings (like 'hovered & !disabled', '@media(w < 768px)')\n * into ConditionNode trees for processing in the pipeline.\n */\n\nimport { Lru } from '../parser/lru';\nimport type { StateParserContext } from '../states';\nimport {\n expandDimensionShorthands,\n expandTastyUnits,\n findTopLevelComma,\n resolvePredefinedState,\n} from '../states';\nimport { camelToKebab } from '../utils/case-converter';\nimport { transformSelectorContent } from '../utils/selector-transform';\n\nimport type { ConditionNode, NumericBound } from './conditions';\nimport { emitWarning } from './warnings';\nimport {\n and,\n createContainerDimensionCondition,\n createContainerRawCondition,\n createContainerStyleCondition,\n createMediaDimensionCondition,\n createMediaFeatureCondition,\n createMediaTypeCondition,\n createModifierCondition,\n createOwnCondition,\n createParentCondition,\n createPseudoCondition,\n createRootCondition,\n createStartingCondition,\n createSupportsCondition,\n not,\n or,\n trueCondition,\n} from './conditions';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Maximum XOR operands before emitting a performance warning.\n * A ^ B ^ C ^ D = 8 OR branches (2^(n-1)), so chains above 4\n * risk exponential blowup in downstream processing.\n */\nconst MAX_XOR_CHAIN_LENGTH = 4;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ParseStateKeyOptions {\n context?: StateParserContext;\n isSubElement?: boolean;\n}\n\n// ============================================================================\n// Caching\n// ============================================================================\n\n// Cache for parsed state keys (key -> ConditionNode)\nconst parseCache = new Lru<string, ConditionNode>(5000);\n\n// ============================================================================\n// Tokenizer Patterns\n// ============================================================================\n\n/**\n * Pattern for tokenizing state notation.\n * Matches: operators, parentheses, @-prefixed states, value mods, boolean mods,\n * pseudo-classes, class selectors, and attribute selectors.\n *\n * Note: For @supports, @(...) container queries, and :is/:has/:not/:where\n * we need to handle nested parentheses.\n * We use a pattern that allows up to 2 levels of nesting:\n * [^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\n */\nconst STATE_TOKEN_PATTERN =\n /([&|!^])|([()])|(@media:[a-z]+)|(@media\\([^)]+\\))|(@supports\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@root\\([^)]+\\))|(@parent\\([^)]+\\))|(@own\\([^)]+\\))|(@\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@starting)|(@[A-Za-z][A-Za-z0-9-]*)|([a-z][a-z0-9-]*(?:\\^=|\\$=|\\*=|=)(?:\"[^\"]*\"|'[^']*'|[^\\s&|!^()]+))|([a-z][a-z0-9-]+)|(:(?:is|has|not|where)\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(:[-a-z][a-z0-9-]*(?:\\([^)]+\\))?)|(\\.[a-z][a-z0-9-]+)|(\\[[^\\]]+\\])/gi;\n\n// ============================================================================\n// Token Types\n// ============================================================================\n\ntype TokenType = 'AND' | 'OR' | 'NOT' | 'XOR' | 'LPAREN' | 'RPAREN' | 'STATE';\n\ninterface Token {\n type: TokenType;\n value: string;\n raw: string;\n}\n\n// ============================================================================\n// Tokenizer\n// ============================================================================\n\n/**\n * Tokenize a state notation string\n */\nfunction tokenize(stateKey: string): Token[] {\n const tokens: Token[] = [];\n let match: RegExpExecArray | null;\n\n // Replace commas with | outside of parentheses (for compatibility)\n const normalized = replaceCommasOutsideParens(stateKey);\n\n STATE_TOKEN_PATTERN.lastIndex = 0;\n while ((match = STATE_TOKEN_PATTERN.exec(normalized)) !== null) {\n const fullMatch = match[0];\n\n if (match[1]) {\n // Operator: &, |, !, ^\n switch (fullMatch) {\n case '&':\n tokens.push({ type: 'AND', value: '&', raw: fullMatch });\n break;\n case '|':\n tokens.push({ type: 'OR', value: '|', raw: fullMatch });\n break;\n case '!':\n tokens.push({ type: 'NOT', value: '!', raw: fullMatch });\n break;\n case '^':\n tokens.push({ type: 'XOR', value: '^', raw: fullMatch });\n break;\n }\n } else if (match[2]) {\n // Parenthesis\n if (fullMatch === '(') {\n tokens.push({ type: 'LPAREN', value: '(', raw: fullMatch });\n } else {\n tokens.push({ type: 'RPAREN', value: ')', raw: fullMatch });\n }\n } else {\n // State token (all other capture groups)\n tokens.push({ type: 'STATE', value: fullMatch, raw: fullMatch });\n }\n }\n\n return tokens;\n}\n\n/**\n * Replace commas with | only outside of parentheses\n */\nfunction replaceCommasOutsideParens(str: string): string {\n let result = '';\n let depth = 0;\n\n for (const char of str) {\n if (char === '(') {\n depth++;\n result += char;\n } else if (char === ')') {\n depth--;\n result += char;\n } else if (char === ',' && depth === 0) {\n result += '|';\n } else {\n result += char;\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Recursive Descent Parser\n// ============================================================================\n\n/**\n * Parser state\n */\nclass Parser {\n private tokens: Token[];\n private pos = 0;\n private options: ParseStateKeyOptions;\n\n constructor(tokens: Token[], options: ParseStateKeyOptions) {\n this.tokens = tokens;\n this.options = options;\n }\n\n parse(): ConditionNode {\n if (this.tokens.length === 0) {\n return trueCondition();\n }\n const result = this.parseExpression();\n return result;\n }\n\n private current(): Token | undefined {\n return this.tokens[this.pos];\n }\n\n private advance(): Token | undefined {\n return this.tokens[this.pos++];\n }\n\n private match(type: TokenType): boolean {\n if (this.current()?.type === type) {\n this.advance();\n return true;\n }\n return false;\n }\n\n /**\n * Parse expression with operator precedence:\n * ! (NOT) > ^ (XOR) > | (OR) > & (AND)\n */\n private parseExpression(): ConditionNode {\n return this.parseAnd();\n }\n\n private parseAnd(): ConditionNode {\n let left = this.parseOr();\n\n while (this.current()?.type === 'AND') {\n this.advance();\n const right = this.parseOr();\n left = and(left, right);\n }\n\n return left;\n }\n\n private parseOr(): ConditionNode {\n let left = this.parseXor();\n\n while (this.current()?.type === 'OR') {\n this.advance();\n const right = this.parseXor();\n left = or(left, right);\n }\n\n return left;\n }\n\n private parseXor(): ConditionNode {\n let left = this.parseUnary();\n let operandCount = 1;\n\n while (this.current()?.type === 'XOR') {\n this.advance();\n const right = this.parseUnary();\n operandCount++;\n\n if (operandCount > MAX_XOR_CHAIN_LENGTH) {\n emitWarning(\n 'XOR_CHAIN_TOO_LONG',\n `XOR chain with ${operandCount} operands produces ${Math.pow(2, operandCount - 1)} OR branches. ` +\n `Consider breaking into smaller expressions to avoid exponential growth.`,\n );\n }\n\n // XOR: (A & !B) | (!A & B)\n left = or(and(left, not(right)), and(not(left), right));\n }\n\n return left;\n }\n\n private parseUnary(): ConditionNode {\n if (this.match('NOT')) {\n const operand = this.parseUnary();\n return not(operand);\n }\n return this.parsePrimary();\n }\n\n private parsePrimary(): ConditionNode {\n // Handle parentheses\n if (this.match('LPAREN')) {\n const expr = this.parseExpression();\n this.match('RPAREN'); // Consume closing paren (lenient if missing)\n return expr;\n }\n\n // Handle state tokens\n const token = this.current();\n if (token?.type === 'STATE') {\n this.advance();\n return this.parseStateToken(token.value);\n }\n\n // Fallback for empty/invalid - return TRUE\n return trueCondition();\n }\n\n /**\n * Parse a state token into a ConditionNode\n */\n private parseStateToken(value: string): ConditionNode {\n // @starting\n if (value === '@starting') {\n return createStartingCondition(false, value);\n }\n\n // @media:type (e.g., @media:print)\n if (value.startsWith('@media:')) {\n const mediaType = value.slice(7) as 'print' | 'screen' | 'all' | 'speech';\n return createMediaTypeCondition(mediaType, false, value);\n }\n\n // @media(...) - media query\n if (value.startsWith('@media(')) {\n return this.parseMediaQuery(value);\n }\n\n // @supports(...) - feature/selector support query\n if (value.startsWith('@supports(')) {\n return this.parseSupportsQuery(value);\n }\n\n // @root(...) - root state\n if (value.startsWith('@root(')) {\n return this.parseRootState(value);\n }\n\n // @parent(...) - parent element state\n if (value.startsWith('@parent(')) {\n return this.parseParentState(value);\n }\n\n // @own(...) - own state (sub-element)\n if (value.startsWith('@own(')) {\n return this.parseOwnState(value);\n }\n\n // @(...) - container query\n if (value.startsWith('@(')) {\n return this.parseContainerQuery(value);\n }\n\n // @name - predefined state\n if (value.startsWith('@') && /^@[A-Za-z][A-Za-z0-9-]*$/.test(value)) {\n return this.parsePredefinedState(value);\n }\n\n // Enhanced pseudo-classes: :is(), :has(), :not(), :where()\n // Transform capitalized words to [data-element=\"...\"] selectors,\n // auto-complete trailing combinators with *, and\n // normalize :not(X) → negated :is(X) for deduplication.\n if (value.startsWith(':')) {\n const enhancedMatch = /^:(is|has|not|where)\\(/.exec(value);\n if (enhancedMatch) {\n const fn = enhancedMatch[1];\n const prefix = enhancedMatch[0];\n let content = transformSelectorContent(value.slice(prefix.length, -1));\n\n // Auto-complete trailing combinator: :has(Icon >) → :has(... > *)\n content = content.replace(/([>+~])\\s*$/, '$1 *');\n\n if (fn === 'not') {\n return createPseudoCondition(`:is(${content})`, true, value);\n }\n\n return createPseudoCondition(`:${fn}(${content})`, false, value);\n }\n\n return createPseudoCondition(value, false, value);\n }\n\n // Class selector (e.g., .active)\n if (value.startsWith('.')) {\n return createPseudoCondition(value, false, value);\n }\n\n // Attribute selector (e.g., [disabled], [data-state=\"active\"])\n if (value.startsWith('[')) {\n return createPseudoCondition(value, false, value);\n }\n\n // Value modifier (e.g., theme=danger, size=large)\n if (value.includes('=')) {\n return this.parseValueModifier(value);\n }\n\n // Boolean modifier (e.g., hovered, disabled)\n return this.parseBooleanModifier(value);\n }\n\n /**\n * Parse @media(...) query\n */\n private parseMediaQuery(raw: string): ConditionNode {\n const content = raw.slice(7, -1); // Remove '@media(' and ')'\n if (!content.trim()) {\n return trueCondition();\n }\n\n // Expand shorthands and units\n let condition = expandDimensionShorthands(content);\n condition = expandTastyUnits(condition);\n\n // Check for feature queries (contains ':' but not dimension comparison)\n if (\n condition.includes(':') &&\n !condition.includes('<') &&\n !condition.includes('>') &&\n !condition.includes('=')\n ) {\n // Feature query: @media(prefers-contrast: high)\n const colonIdx = condition.indexOf(':');\n const feature = condition.slice(0, colonIdx).trim();\n const featureValue = condition.slice(colonIdx + 1).trim();\n return createMediaFeatureCondition(feature, featureValue, false, raw);\n }\n\n // Boolean feature query: @media(prefers-reduced-motion)\n if (\n !condition.includes('<') &&\n !condition.includes('>') &&\n !condition.includes('=')\n ) {\n return createMediaFeatureCondition(\n condition.trim(),\n undefined,\n false,\n raw,\n );\n }\n\n // Dimension query - parse bounds\n const { dimension, lowerBound, upperBound } =\n this.parseDimensionCondition(condition);\n\n if (!dimension) {\n // Fallback for unparseable - treat as pseudo\n return createPseudoCondition(raw, false, raw);\n }\n\n return createMediaDimensionCondition(\n dimension as 'width' | 'height',\n lowerBound,\n upperBound,\n false,\n raw,\n );\n }\n\n /**\n * Parse dimension condition string (e.g., \"width < 768px\", \"600px <= width < 1200px\")\n */\n private parseDimensionCondition(condition: string): {\n dimension?: string;\n lowerBound?: NumericBound;\n upperBound?: NumericBound;\n } {\n // Range syntax: \"600px <= width < 1200px\"\n const rangeMatch = condition.match(\n /^(.+?)\\s*(<=|<)\\s*(width|height|inline-size|block-size)\\s*(<=|<)\\s*(.+)$/,\n );\n if (rangeMatch) {\n const [, lowerValue, lowerOp, dimension, upperOp, upperValue] =\n rangeMatch;\n return {\n dimension,\n lowerBound: {\n value: lowerValue.trim(),\n valueNumeric: parseNumericValue(lowerValue.trim()),\n inclusive: lowerOp === '<=',\n },\n upperBound: {\n value: upperValue.trim(),\n valueNumeric: parseNumericValue(upperValue.trim()),\n inclusive: upperOp === '<=',\n },\n };\n }\n\n // Simple comparison: \"width < 768px\"\n const simpleMatch = condition.match(\n /^(width|height|inline-size|block-size)\\s*(<=|>=|<|>|=)\\s*(.+)$/,\n );\n if (simpleMatch) {\n const [, dimension, operator, value] = simpleMatch;\n const numeric = parseNumericValue(value.trim());\n\n if (operator === '<' || operator === '<=') {\n return {\n dimension,\n upperBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: operator === '<=',\n },\n };\n } else if (operator === '>' || operator === '>=') {\n return {\n dimension,\n lowerBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: operator === '>=',\n },\n };\n } else if (operator === '=') {\n // Exact match: both bounds are the same and inclusive\n return {\n dimension,\n lowerBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: true,\n },\n upperBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: true,\n },\n };\n }\n }\n\n // Reversed: \"768px > width\"\n const reversedMatch = condition.match(\n /^(.+?)\\s*(<=|>=|<|>|=)\\s*(width|height|inline-size|block-size)$/,\n );\n if (reversedMatch) {\n const [, value, operator, dimension] = reversedMatch;\n const numeric = parseNumericValue(value.trim());\n\n // Reverse the operator\n if (operator === '<' || operator === '<=') {\n return {\n dimension,\n lowerBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: operator === '<=',\n },\n };\n } else if (operator === '>' || operator === '>=') {\n return {\n dimension,\n upperBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: operator === '>=',\n },\n };\n }\n }\n\n return {};\n }\n\n /**\n * Parse @root(...) state\n */\n private parseInnerCondition(\n raw: string,\n prefixLen: number,\n wrap: (inner: ConditionNode) => ConditionNode,\n ): ConditionNode {\n const content = raw.slice(prefixLen, -1);\n if (!content.trim()) return trueCondition();\n return wrap(parseStateKey(content, this.options));\n }\n\n private parseRootState(raw: string): ConditionNode {\n return this.parseInnerCondition(raw, 6, (inner) =>\n createRootCondition(inner, false, raw),\n );\n }\n\n /**\n * Parse @parent(...) state\n *\n * Syntax:\n * @parent(hovered) → :is([data-hovered] *)\n * @parent(theme=dark) → :is([data-theme=\"dark\"] *)\n * @parent(hovered, >) → :is([data-hovered] > *) (direct parent)\n * @parent(.my-class) → :is(.my-class *)\n */\n private parseParentState(raw: string): ConditionNode {\n const content = raw.slice(8, -1);\n if (!content.trim()) {\n return trueCondition();\n }\n\n let condition = content.trim();\n let direct = false;\n\n const lastCommaIdx = condition.lastIndexOf(',');\n if (lastCommaIdx !== -1) {\n const afterComma = condition.slice(lastCommaIdx + 1).trim();\n if (afterComma === '>') {\n direct = true;\n condition = condition.slice(0, lastCommaIdx).trim();\n }\n }\n\n const innerCondition = parseStateKey(condition, this.options);\n return createParentCondition(innerCondition, direct, false, raw);\n }\n\n /**\n * Parse @supports(...) query\n *\n * Syntax:\n * @supports(display: grid) → @supports (display: grid)\n * @supports($, :has(*)) → @supports selector(:has(*))\n */\n private parseSupportsQuery(raw: string): ConditionNode {\n const content = raw.slice(10, -1); // Remove '@supports(' and ')'\n if (!content.trim()) {\n return trueCondition();\n }\n\n // Check for selector syntax: @supports($, :has(*))\n if (content.startsWith('$,')) {\n const selector = content.slice(2).trim(); // Remove '$,' prefix\n return createSupportsCondition('selector', selector, false, raw);\n }\n\n // Feature syntax: @supports(display: grid)\n return createSupportsCondition('feature', content, false, raw);\n }\n\n private parseOwnState(raw: string): ConditionNode {\n return this.parseInnerCondition(raw, 5, (inner) =>\n createOwnCondition(inner, false, raw),\n );\n }\n\n /**\n * Parse @(...) container query\n */\n private parseContainerQuery(raw: string): ConditionNode {\n const content = raw.slice(2, -1); // Remove '@(' and ')'\n if (!content.trim()) {\n return trueCondition();\n }\n\n // Check for named container: @(layout, w < 600px)\n // Use parentheses-aware comma search so inner commas (e.g., scroll-state(a, b)) are skipped\n const commaIdx = findTopLevelComma(content);\n let containerName: string | undefined;\n let condition: string;\n\n if (commaIdx !== -1) {\n containerName = content.slice(0, commaIdx).trim();\n condition = content.slice(commaIdx + 1).trim();\n } else {\n condition = content.trim();\n }\n\n // Check for style query shorthand: @($variant=primary)\n if (condition.startsWith('$')) {\n const styleQuery = condition.slice(1); // Remove '$'\n const eqIdx = styleQuery.indexOf('=');\n\n if (eqIdx === -1) {\n // Existence check: @($variant)\n return createContainerStyleCondition(\n styleQuery,\n undefined,\n containerName,\n false,\n raw,\n );\n }\n\n const property = styleQuery.slice(0, eqIdx).trim();\n let propertyValue = styleQuery.slice(eqIdx + 1).trim();\n\n // Remove quotes if present\n if (\n (propertyValue.startsWith('\"') && propertyValue.endsWith('\"')) ||\n (propertyValue.startsWith(\"'\") && propertyValue.endsWith(\"'\"))\n ) {\n propertyValue = propertyValue.slice(1, -1);\n }\n\n return createContainerStyleCondition(\n property,\n propertyValue,\n containerName,\n false,\n raw,\n );\n }\n\n // Check for function-like syntax: scroll-state(...), style(...), etc.\n // Passes the condition through to CSS verbatim.\n if (/^[a-zA-Z][\\w-]*\\s*\\(/.test(condition)) {\n return createContainerRawCondition(condition, containerName, false, raw);\n }\n\n // Dimension query\n let expandedCondition = expandDimensionShorthands(condition);\n expandedCondition = expandTastyUnits(expandedCondition);\n\n const { dimension, lowerBound, upperBound } =\n this.parseDimensionCondition(expandedCondition);\n\n if (!dimension) {\n // Fallback\n return createPseudoCondition(raw, false, raw);\n }\n\n return createContainerDimensionCondition(\n dimension as 'width' | 'height',\n lowerBound,\n upperBound,\n containerName,\n false,\n raw,\n );\n }\n\n /**\n * Parse predefined state (@mobile, @dark, etc.)\n */\n private parsePredefinedState(raw: string): ConditionNode {\n const ctx = this.options.context;\n if (!ctx) {\n // No context - can't resolve predefined states\n return createPseudoCondition(raw, false, raw);\n }\n\n const resolved = resolvePredefinedState(raw, ctx);\n if (!resolved) {\n // Undefined predefined state - treat as modifier\n return createModifierCondition(\n `data-${camelToKebab(raw.slice(1))}`,\n undefined,\n '=',\n false,\n raw,\n );\n }\n\n // Parse the resolved value recursively\n return parseStateKey(resolved, this.options);\n }\n\n /**\n * Parse value modifier (e.g., theme=danger, size^=sm)\n */\n private parseValueModifier(raw: string): ConditionNode {\n // Match operators: =, ^=, $=, *=\n const opMatch = raw.match(/^([a-z][a-z0-9-]*)(\\^=|\\$=|\\*=|=)(.+)$/i);\n if (!opMatch) {\n return createModifierCondition(\n `data-${camelToKebab(raw)}`,\n undefined,\n '=',\n false,\n raw,\n );\n }\n\n const [, key, operator, value] = opMatch;\n let cleanValue = value;\n\n // Remove quotes if present\n if (\n (cleanValue.startsWith('\"') && cleanValue.endsWith('\"')) ||\n (cleanValue.startsWith(\"'\") && cleanValue.endsWith(\"'\"))\n ) {\n cleanValue = cleanValue.slice(1, -1);\n }\n\n return createModifierCondition(\n `data-${camelToKebab(key)}`,\n cleanValue,\n operator as '=' | '^=' | '$=' | '*=',\n false,\n raw,\n );\n }\n\n /**\n * Parse boolean modifier (e.g., hovered, disabled)\n */\n private parseBooleanModifier(raw: string): ConditionNode {\n return createModifierCondition(\n `data-${camelToKebab(raw)}`,\n undefined,\n '=',\n false,\n raw,\n );\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Parse a numeric value from a CSS value string\n */\nfunction parseNumericValue(value: string): number | null {\n const match = value.match(/^(\\d+(?:\\.\\d+)?)(px|em|rem|vh|vw|%)?$/);\n if (match) {\n return parseFloat(match[1]);\n }\n return null;\n}\n\n// ============================================================================\n// Main Export\n// ============================================================================\n\n/**\n * Parse a state key string into a ConditionNode\n */\nexport function parseStateKey(\n stateKey: string,\n options: ParseStateKeyOptions = {},\n): ConditionNode {\n // Handle empty/default state\n if (!stateKey || !stateKey.trim()) {\n return trueCondition();\n }\n\n const trimmed = stateKey.trim();\n\n // Build cache key including local predefined states (they affect parsing)\n // Global predefined states are set once at initialization and don't change\n const ctx = options.context;\n const localStatesKey =\n ctx && Object.keys(ctx.localPredefinedStates).length > 0\n ? JSON.stringify(ctx.localPredefinedStates)\n : '';\n const cacheKey =\n trimmed + '\\0' + (options.isSubElement ? '1' : '0') + '\\0' + localStatesKey;\n\n // Check cache\n const cached = parseCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n // Tokenize and parse\n const tokens = tokenize(trimmed);\n const parser = new Parser(tokens, options);\n const result = parser.parse();\n\n // Cache result\n parseCache.set(cacheKey, result);\n\n return result;\n}\n\n/**\n * Clear the parse cache (for testing)\n */\nexport function clearParseCache(): void {\n parseCache.clear();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parseStateKey.js","names":[],"sources":["../../src/pipeline/parseStateKey.ts"],"sourcesContent":["/**\n * State Key Parser\n *\n * Parses state notation strings (like 'hovered & !disabled', '@media(w < 768px)')\n * into ConditionNode trees for processing in the pipeline.\n */\n\nimport { Lru } from '../parser/lru';\nimport type { StateParserContext } from '../states';\nimport {\n expandDimensionShorthands,\n expandTastyUnits,\n findTopLevelComma,\n resolvePredefinedState,\n} from '../states';\nimport { camelToKebab } from '../utils/case-converter';\nimport { transformSelectorContent } from '../utils/selector-transform';\n\nimport type { ConditionNode, NumericBound } from './conditions';\nimport { emitWarning } from './warnings';\nimport {\n and,\n createContainerDimensionCondition,\n createContainerRawCondition,\n createContainerStyleCondition,\n createMediaDimensionCondition,\n createMediaFeatureCondition,\n createMediaTypeCondition,\n createModifierCondition,\n createOwnCondition,\n createParentCondition,\n createPseudoCondition,\n createRootCondition,\n createStartingCondition,\n createSupportsCondition,\n not,\n or,\n trueCondition,\n} from './conditions';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Maximum XOR operands before emitting a performance warning.\n * A ^ B ^ C ^ D = 8 OR branches (2^(n-1)), so chains above 4\n * risk exponential blowup in downstream processing.\n */\nconst MAX_XOR_CHAIN_LENGTH = 4;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ParseStateKeyOptions {\n context?: StateParserContext;\n isSubElement?: boolean;\n}\n\n// ============================================================================\n// Caching\n// ============================================================================\n\n// Cache for parsed state keys (key -> ConditionNode)\nconst parseCache = new Lru<string, ConditionNode>(5000);\n\n// ============================================================================\n// Tokenizer Patterns\n// ============================================================================\n\n/**\n * Pattern for tokenizing state notation.\n * Matches: operators, parentheses, @-prefixed states, value mods, boolean mods,\n * pseudo-classes, class selectors, and attribute selectors.\n *\n * Note: For @supports, @(...) container queries, and :is/:has/:not/:where\n * we need to handle nested parentheses.\n * We use a pattern that allows up to 2 levels of nesting:\n * [^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\n */\nconst STATE_TOKEN_PATTERN =\n /([&|!^])|([()])|(@media:[a-z]+)|(@media\\([^)]+\\))|(@supports\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@root\\([^)]+\\))|(@parent\\([^)]+\\))|(@own\\([^)]+\\))|(@\\([^()]*(?:\\([^)]*\\))?[^)]*\\))|(@starting)|(@[A-Za-z][A-Za-z0-9-]*)|([a-z][a-z0-9-]*(?:\\^=|\\$=|\\*=|=)(?:\"[^\"]*\"|'[^']*'|[^\\s&|!^()]+))|([a-z][a-z0-9-]+)|(:(?:is|has|not|where)\\([^()]*(?:\\([^()]*(?:\\([^)]*\\))?[^)]*\\))*[^)]*\\))|(:[-a-z][a-z0-9-]*(?:\\([^)]+\\))?)|(\\.[a-z][a-z0-9-]+)|(\\[[^\\]]+\\])/gi;\n\n// ============================================================================\n// Token Types\n// ============================================================================\n\ntype TokenType = 'AND' | 'OR' | 'NOT' | 'XOR' | 'LPAREN' | 'RPAREN' | 'STATE';\n\ninterface Token {\n type: TokenType;\n value: string;\n raw: string;\n}\n\n// ============================================================================\n// Tokenizer\n// ============================================================================\n\n/**\n * Tokenize a state notation string\n */\nfunction tokenize(stateKey: string): Token[] {\n const tokens: Token[] = [];\n let match: RegExpExecArray | null;\n\n // Replace commas with | outside of parentheses (for compatibility)\n const normalized = replaceCommasOutsideParens(stateKey);\n\n STATE_TOKEN_PATTERN.lastIndex = 0;\n while ((match = STATE_TOKEN_PATTERN.exec(normalized)) !== null) {\n const fullMatch = match[0];\n\n if (match[1]) {\n // Operator: &, |, !, ^\n switch (fullMatch) {\n case '&':\n tokens.push({ type: 'AND', value: '&', raw: fullMatch });\n break;\n case '|':\n tokens.push({ type: 'OR', value: '|', raw: fullMatch });\n break;\n case '!':\n tokens.push({ type: 'NOT', value: '!', raw: fullMatch });\n break;\n case '^':\n tokens.push({ type: 'XOR', value: '^', raw: fullMatch });\n break;\n }\n } else if (match[2]) {\n // Parenthesis\n if (fullMatch === '(') {\n tokens.push({ type: 'LPAREN', value: '(', raw: fullMatch });\n } else {\n tokens.push({ type: 'RPAREN', value: ')', raw: fullMatch });\n }\n } else {\n // State token (all other capture groups)\n tokens.push({ type: 'STATE', value: fullMatch, raw: fullMatch });\n }\n }\n\n return tokens;\n}\n\n/**\n * Replace commas with | only outside of parentheses\n */\nfunction replaceCommasOutsideParens(str: string): string {\n let result = '';\n let depth = 0;\n\n for (const char of str) {\n if (char === '(') {\n depth++;\n result += char;\n } else if (char === ')') {\n depth--;\n result += char;\n } else if (char === ',' && depth === 0) {\n result += '|';\n } else {\n result += char;\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Recursive Descent Parser\n// ============================================================================\n\n/**\n * Parser state\n */\nclass Parser {\n private tokens: Token[];\n private pos = 0;\n private options: ParseStateKeyOptions;\n\n constructor(tokens: Token[], options: ParseStateKeyOptions) {\n this.tokens = tokens;\n this.options = options;\n }\n\n parse(): ConditionNode {\n if (this.tokens.length === 0) {\n return trueCondition();\n }\n const result = this.parseExpression();\n return result;\n }\n\n private current(): Token | undefined {\n return this.tokens[this.pos];\n }\n\n private advance(): Token | undefined {\n return this.tokens[this.pos++];\n }\n\n private match(type: TokenType): boolean {\n if (this.current()?.type === type) {\n this.advance();\n return true;\n }\n return false;\n }\n\n /**\n * Parse expression with operator precedence:\n * ! (NOT) > ^ (XOR) > | (OR) > & (AND)\n */\n private parseExpression(): ConditionNode {\n return this.parseAnd();\n }\n\n private parseAnd(): ConditionNode {\n let left = this.parseOr();\n\n while (this.current()?.type === 'AND') {\n this.advance();\n const right = this.parseOr();\n left = and(left, right);\n }\n\n return left;\n }\n\n private parseOr(): ConditionNode {\n let left = this.parseXor();\n\n while (this.current()?.type === 'OR') {\n this.advance();\n const right = this.parseXor();\n left = or(left, right);\n }\n\n return left;\n }\n\n private parseXor(): ConditionNode {\n let left = this.parseUnary();\n let operandCount = 1;\n\n while (this.current()?.type === 'XOR') {\n this.advance();\n const right = this.parseUnary();\n operandCount++;\n\n if (operandCount > MAX_XOR_CHAIN_LENGTH) {\n emitWarning(\n 'XOR_CHAIN_TOO_LONG',\n `XOR chain with ${operandCount} operands produces ${Math.pow(2, operandCount - 1)} OR branches. ` +\n `Consider breaking into smaller expressions to avoid exponential growth.`,\n );\n }\n\n // XOR: (A & !B) | (!A & B)\n left = or(and(left, not(right)), and(not(left), right));\n }\n\n return left;\n }\n\n private parseUnary(): ConditionNode {\n if (this.match('NOT')) {\n const operand = this.parseUnary();\n return not(operand);\n }\n return this.parsePrimary();\n }\n\n private parsePrimary(): ConditionNode {\n // Handle parentheses\n if (this.match('LPAREN')) {\n const expr = this.parseExpression();\n this.match('RPAREN'); // Consume closing paren (lenient if missing)\n return expr;\n }\n\n // Handle state tokens\n const token = this.current();\n if (token?.type === 'STATE') {\n this.advance();\n return this.parseStateToken(token.value);\n }\n\n // Fallback for empty/invalid - return TRUE\n return trueCondition();\n }\n\n /**\n * Parse a state token into a ConditionNode\n */\n private parseStateToken(value: string): ConditionNode {\n // @starting\n if (value === '@starting') {\n return createStartingCondition(false, value);\n }\n\n // @media:type (e.g., @media:print)\n if (value.startsWith('@media:')) {\n const mediaType = value.slice(7) as 'print' | 'screen' | 'all' | 'speech';\n return createMediaTypeCondition(mediaType, false, value);\n }\n\n // @media(...) - media query\n if (value.startsWith('@media(')) {\n return this.parseMediaQuery(value);\n }\n\n // @supports(...) - feature/selector support query\n if (value.startsWith('@supports(')) {\n return this.parseSupportsQuery(value);\n }\n\n // @root(...) - root state\n if (value.startsWith('@root(')) {\n return this.parseRootState(value);\n }\n\n // @parent(...) - parent element state\n if (value.startsWith('@parent(')) {\n return this.parseParentState(value);\n }\n\n // @own(...) - own state (sub-element)\n if (value.startsWith('@own(')) {\n return this.parseOwnState(value);\n }\n\n // @(...) - container query\n if (value.startsWith('@(')) {\n return this.parseContainerQuery(value);\n }\n\n // @name - predefined state\n if (value.startsWith('@') && /^@[A-Za-z][A-Za-z0-9-]*$/.test(value)) {\n return this.parsePredefinedState(value);\n }\n\n // Enhanced pseudo-classes: :is(), :has(), :not(), :where()\n // Transform capitalized words to [data-element=\"...\"] selectors,\n // auto-complete trailing combinators with *, and\n // normalize :not(X) → negated :is(X) for deduplication.\n if (value.startsWith(':')) {\n const enhancedMatch = /^:(is|has|not|where)\\(/.exec(value);\n if (enhancedMatch) {\n const fn = enhancedMatch[1];\n const prefix = enhancedMatch[0];\n let content = transformSelectorContent(value.slice(prefix.length, -1));\n\n // Auto-complete trailing combinator: :has(Icon >) → :has(... > *)\n content = content.replace(/([>+~])\\s*$/, '$1 *');\n\n if (fn === 'not') {\n return createPseudoCondition(`:is(${content})`, true, value);\n }\n\n return createPseudoCondition(`:${fn}(${content})`, false, value);\n }\n\n return createPseudoCondition(value, false, value);\n }\n\n // Class selector (e.g., .active)\n if (value.startsWith('.')) {\n return createPseudoCondition(value, false, value);\n }\n\n // Attribute selector (e.g., [disabled], [data-state=\"active\"])\n if (value.startsWith('[')) {\n return createPseudoCondition(value, false, value);\n }\n\n // Value modifier (e.g., theme=danger, size=large)\n if (value.includes('=')) {\n return this.parseValueModifier(value);\n }\n\n // Boolean modifier (e.g., hovered, disabled)\n return this.parseBooleanModifier(value);\n }\n\n /**\n * Parse @media(...) query\n */\n private parseMediaQuery(raw: string): ConditionNode {\n const content = raw.slice(7, -1); // Remove '@media(' and ')'\n if (!content.trim()) {\n return trueCondition();\n }\n\n // Expand shorthands and units\n let condition = expandDimensionShorthands(content);\n condition = expandTastyUnits(condition);\n\n // Check for feature queries (contains ':' but not dimension comparison)\n if (\n condition.includes(':') &&\n !condition.includes('<') &&\n !condition.includes('>') &&\n !condition.includes('=')\n ) {\n // Feature query: @media(prefers-contrast: high)\n const colonIdx = condition.indexOf(':');\n const feature = condition.slice(0, colonIdx).trim();\n const featureValue = condition.slice(colonIdx + 1).trim();\n return createMediaFeatureCondition(feature, featureValue, false, raw);\n }\n\n // Boolean feature query: @media(prefers-reduced-motion)\n if (\n !condition.includes('<') &&\n !condition.includes('>') &&\n !condition.includes('=')\n ) {\n return createMediaFeatureCondition(\n condition.trim(),\n undefined,\n false,\n raw,\n );\n }\n\n // Dimension query - parse bounds\n const { dimension, lowerBound, upperBound } =\n this.parseDimensionCondition(condition);\n\n if (!dimension) {\n // Fallback for unparseable - treat as pseudo\n return createPseudoCondition(raw, false, raw);\n }\n\n return createMediaDimensionCondition(\n dimension as 'width' | 'height',\n lowerBound,\n upperBound,\n false,\n raw,\n );\n }\n\n /**\n * Parse dimension condition string (e.g., \"width < 768px\", \"600px <= width < 1200px\")\n */\n private parseDimensionCondition(condition: string): {\n dimension?: string;\n lowerBound?: NumericBound;\n upperBound?: NumericBound;\n } {\n // Range syntax: \"600px <= width < 1200px\"\n const rangeMatch = condition.match(\n /^(.+?)\\s*(<=|<)\\s*(width|height|inline-size|block-size)\\s*(<=|<)\\s*(.+)$/,\n );\n if (rangeMatch) {\n const [, lowerValue, lowerOp, dimension, upperOp, upperValue] =\n rangeMatch;\n return {\n dimension,\n lowerBound: {\n value: lowerValue.trim(),\n valueNumeric: parseNumericValue(lowerValue.trim()),\n inclusive: lowerOp === '<=',\n },\n upperBound: {\n value: upperValue.trim(),\n valueNumeric: parseNumericValue(upperValue.trim()),\n inclusive: upperOp === '<=',\n },\n };\n }\n\n // Simple comparison: \"width < 768px\"\n const simpleMatch = condition.match(\n /^(width|height|inline-size|block-size)\\s*(<=|>=|<|>|=)\\s*(.+)$/,\n );\n if (simpleMatch) {\n const [, dimension, operator, value] = simpleMatch;\n const numeric = parseNumericValue(value.trim());\n\n if (operator === '<' || operator === '<=') {\n return {\n dimension,\n upperBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: operator === '<=',\n },\n };\n } else if (operator === '>' || operator === '>=') {\n return {\n dimension,\n lowerBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: operator === '>=',\n },\n };\n } else if (operator === '=') {\n // Exact match: both bounds are the same and inclusive\n return {\n dimension,\n lowerBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: true,\n },\n upperBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: true,\n },\n };\n }\n }\n\n // Reversed: \"768px > width\"\n const reversedMatch = condition.match(\n /^(.+?)\\s*(<=|>=|<|>|=)\\s*(width|height|inline-size|block-size)$/,\n );\n if (reversedMatch) {\n const [, value, operator, dimension] = reversedMatch;\n const numeric = parseNumericValue(value.trim());\n\n // Reverse the operator\n if (operator === '<' || operator === '<=') {\n return {\n dimension,\n lowerBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: operator === '<=',\n },\n };\n } else if (operator === '>' || operator === '>=') {\n return {\n dimension,\n upperBound: {\n value: value.trim(),\n valueNumeric: numeric,\n inclusive: operator === '>=',\n },\n };\n }\n }\n\n return {};\n }\n\n /**\n * Parse @root(...) state\n */\n private parseInnerCondition(\n raw: string,\n prefixLen: number,\n wrap: (inner: ConditionNode) => ConditionNode,\n ): ConditionNode {\n const content = raw.slice(prefixLen, -1);\n if (!content.trim()) return trueCondition();\n return wrap(parseStateKey(content, this.options));\n }\n\n private parseRootState(raw: string): ConditionNode {\n return this.parseInnerCondition(raw, 6, (inner) =>\n createRootCondition(inner, false, raw),\n );\n }\n\n /**\n * Parse @parent(...) state\n *\n * Syntax:\n * @parent(hovered) → :is([data-hovered] *)\n * @parent(theme=dark) → :is([data-theme=\"dark\"] *)\n * @parent(hovered, >) → :is([data-hovered] > *) (direct parent)\n * @parent(.my-class) → :is(.my-class *)\n */\n private parseParentState(raw: string): ConditionNode {\n const content = raw.slice(8, -1);\n if (!content.trim()) {\n return trueCondition();\n }\n\n let condition = content.trim();\n let direct = false;\n\n const lastCommaIdx = condition.lastIndexOf(',');\n if (lastCommaIdx !== -1) {\n const afterComma = condition.slice(lastCommaIdx + 1).trim();\n if (afterComma === '>') {\n direct = true;\n condition = condition.slice(0, lastCommaIdx).trim();\n }\n }\n\n const innerCondition = parseStateKey(condition, this.options);\n return createParentCondition(innerCondition, direct, false, raw);\n }\n\n /**\n * Parse @supports(...) query\n *\n * Syntax:\n * @supports(display: grid) → @supports (display: grid)\n * @supports($, :has(*)) → @supports selector(:has(*))\n */\n private parseSupportsQuery(raw: string): ConditionNode {\n const content = raw.slice(10, -1); // Remove '@supports(' and ')'\n if (!content.trim()) {\n return trueCondition();\n }\n\n // Check for selector syntax: @supports($, :has(*))\n if (content.startsWith('$,')) {\n const selector = content.slice(2).trim(); // Remove '$,' prefix\n return createSupportsCondition('selector', selector, false, raw);\n }\n\n // Feature syntax: @supports(display: grid)\n return createSupportsCondition('feature', content, false, raw);\n }\n\n private parseOwnState(raw: string): ConditionNode {\n return this.parseInnerCondition(raw, 5, (inner) =>\n createOwnCondition(inner, false, raw),\n );\n }\n\n /**\n * Parse @(...) container query\n */\n private parseContainerQuery(raw: string): ConditionNode {\n const content = raw.slice(2, -1); // Remove '@(' and ')'\n if (!content.trim()) {\n return trueCondition();\n }\n\n // Check for named container: @(layout, w < 600px)\n // Use parentheses-aware comma search so inner commas (e.g., scroll-state(a, b)) are skipped\n const commaIdx = findTopLevelComma(content);\n let containerName: string | undefined;\n let condition: string;\n\n if (commaIdx !== -1) {\n containerName = content.slice(0, commaIdx).trim();\n condition = content.slice(commaIdx + 1).trim();\n } else {\n condition = content.trim();\n }\n\n // Check for style query shorthand: @($variant=primary)\n if (condition.startsWith('$')) {\n const styleQuery = condition.slice(1); // Remove '$'\n const eqIdx = styleQuery.indexOf('=');\n\n if (eqIdx === -1) {\n // Existence check: @($variant)\n return createContainerStyleCondition(\n styleQuery,\n undefined,\n containerName,\n false,\n raw,\n );\n }\n\n const property = styleQuery.slice(0, eqIdx).trim();\n let propertyValue = styleQuery.slice(eqIdx + 1).trim();\n\n // Remove quotes if present\n if (\n (propertyValue.startsWith('\"') && propertyValue.endsWith('\"')) ||\n (propertyValue.startsWith(\"'\") && propertyValue.endsWith(\"'\"))\n ) {\n propertyValue = propertyValue.slice(1, -1);\n }\n\n return createContainerStyleCondition(\n property,\n propertyValue,\n containerName,\n false,\n raw,\n );\n }\n\n // Check for function-like syntax: scroll-state(...), style(...), etc.\n // Passes the condition through to CSS verbatim.\n if (/^[a-zA-Z][\\w-]*\\s*\\(/.test(condition)) {\n return createContainerRawCondition(condition, containerName, false, raw);\n }\n\n // Dimension query\n let expandedCondition = expandDimensionShorthands(condition);\n expandedCondition = expandTastyUnits(expandedCondition);\n\n const { dimension, lowerBound, upperBound } =\n this.parseDimensionCondition(expandedCondition);\n\n if (!dimension) {\n // Fallback\n return createPseudoCondition(raw, false, raw);\n }\n\n return createContainerDimensionCondition(\n dimension as 'width' | 'height',\n lowerBound,\n upperBound,\n containerName,\n false,\n raw,\n );\n }\n\n /**\n * Parse predefined state (@mobile, @dark, etc.)\n */\n private parsePredefinedState(raw: string): ConditionNode {\n const ctx = this.options.context;\n if (!ctx) {\n // No context - can't resolve predefined states\n return createPseudoCondition(raw, false, raw);\n }\n\n const resolved = resolvePredefinedState(raw, ctx);\n if (!resolved) {\n // Undefined predefined state - treat as modifier\n return createModifierCondition(\n `data-${camelToKebab(raw.slice(1))}`,\n undefined,\n '=',\n false,\n raw,\n );\n }\n\n // Parse the resolved value recursively\n return parseStateKey(resolved, this.options);\n }\n\n /**\n * Parse value modifier (e.g., theme=danger, size^=sm)\n */\n private parseValueModifier(raw: string): ConditionNode {\n // Match operators: =, ^=, $=, *=\n const opMatch = raw.match(/^([a-z][a-z0-9-]*)(\\^=|\\$=|\\*=|=)(.+)$/i);\n if (!opMatch) {\n return createModifierCondition(\n `data-${camelToKebab(raw)}`,\n undefined,\n '=',\n false,\n raw,\n );\n }\n\n const [, key, operator, value] = opMatch;\n let cleanValue = value;\n\n // Remove quotes if present\n if (\n (cleanValue.startsWith('\"') && cleanValue.endsWith('\"')) ||\n (cleanValue.startsWith(\"'\") && cleanValue.endsWith(\"'\"))\n ) {\n cleanValue = cleanValue.slice(1, -1);\n }\n\n return createModifierCondition(\n `data-${camelToKebab(key)}`,\n cleanValue,\n operator as '=' | '^=' | '$=' | '*=',\n false,\n raw,\n );\n }\n\n /**\n * Parse boolean modifier (e.g., hovered, disabled)\n */\n private parseBooleanModifier(raw: string): ConditionNode {\n return createModifierCondition(\n `data-${camelToKebab(raw)}`,\n undefined,\n '=',\n false,\n raw,\n );\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Parse a numeric value from a CSS value string\n */\nfunction parseNumericValue(value: string): number | null {\n const match = value.match(/^(\\d+(?:\\.\\d+)?)(px|em|rem|vh|vw|%)?$/);\n if (match) {\n return parseFloat(match[1]);\n }\n return null;\n}\n\n// ============================================================================\n// Main Export\n// ============================================================================\n\n/**\n * Parse a state key string into a ConditionNode\n */\nexport function parseStateKey(\n stateKey: string,\n options: ParseStateKeyOptions = {},\n): ConditionNode {\n // Handle empty/default state\n if (!stateKey || !stateKey.trim()) {\n return trueCondition();\n }\n\n const trimmed = stateKey.trim();\n\n // Build cache key including local predefined states (they affect parsing)\n // Global predefined states are set once at initialization and don't change\n const ctx = options.context;\n const localStatesKey =\n ctx && Object.keys(ctx.localPredefinedStates).length > 0\n ? JSON.stringify(ctx.localPredefinedStates)\n : '';\n const cacheKey =\n trimmed + '\\0' + (options.isSubElement ? '1' : '0') + '\\0' + localStatesKey;\n\n // Check cache\n const cached = parseCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n // Tokenize and parse\n const tokens = tokenize(trimmed);\n const parser = new Parser(tokens, options);\n const result = parser.parse();\n\n // Cache result\n parseCache.set(cacheKey, result);\n\n return result;\n}\n\n/**\n * Clear the parse cache (for testing)\n */\nexport function clearParseCache(): void {\n parseCache.clear();\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Lru } from "../parser/lru.js";
|
|
2
2
|
import { falseCondition, getConditionUniqueId, trueCondition } from "./conditions.js";
|
|
3
|
-
|
|
4
3
|
//#region src/pipeline/simplify.ts
|
|
5
4
|
/**
|
|
6
5
|
* Condition Simplification Engine
|
|
@@ -510,7 +509,7 @@ function applyAbsorptionAnd(terms) {
|
|
|
510
509
|
function applyAbsorptionOr(terms) {
|
|
511
510
|
return applyAbsorption(terms, "AND");
|
|
512
511
|
}
|
|
513
|
-
|
|
514
512
|
//#endregion
|
|
515
513
|
export { simplifyCondition };
|
|
514
|
+
|
|
516
515
|
//# sourceMappingURL=simplify.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simplify.js","names":[],"sources":["../../src/pipeline/simplify.ts"],"sourcesContent":["/**\n * Condition Simplification Engine\n *\n * Simplifies condition trees by applying boolean algebra rules,\n * detecting contradictions, merging ranges, and deduplicating terms.\n *\n * This is critical for:\n * 1. Detecting invalid combinations (A & !A → FALSE)\n * 2. Reducing CSS output size\n * 3. Producing cleaner selectors\n */\n\nimport { Lru } from '../parser/lru';\n\nimport type {\n ConditionNode,\n ContainerCondition,\n MediaCondition,\n ModifierCondition,\n NumericBound,\n} from './conditions';\nimport {\n falseCondition,\n getConditionUniqueId,\n trueCondition,\n} from './conditions';\n\n// ============================================================================\n// Caching\n// ============================================================================\n\nconst simplifyCache = new Lru<string, ConditionNode>(5000);\n\n// ============================================================================\n// Main Simplify Function\n// ============================================================================\n\n/**\n * Simplify a condition tree aggressively.\n *\n * This applies all possible simplification rules:\n * - Boolean algebra (identity, annihilator, idempotent, absorption)\n * - Contradiction detection (A & !A → FALSE)\n * - Tautology detection (A | !A → TRUE)\n * - Range intersection for numeric queries\n * - Attribute value conflict detection\n * - Deduplication and sorting\n */\nexport function simplifyCondition(node: ConditionNode): ConditionNode {\n // Check cache\n const key = getConditionUniqueId(node);\n const cached = simplifyCache.get(key);\n if (cached) {\n return cached;\n }\n\n const result = simplifyInner(node);\n\n // Cache result\n simplifyCache.set(key, result);\n\n return result;\n}\n\n/**\n * Clear the simplify cache (for testing)\n */\nexport function clearSimplifyCache(): void {\n simplifyCache.clear();\n}\n\n// ============================================================================\n// Inner Simplification\n// ============================================================================\n\nfunction simplifyInner(node: ConditionNode): ConditionNode {\n // Base cases\n if (node.kind === 'true' || node.kind === 'false') {\n return node;\n }\n\n // State conditions - return as-is (they're already leaf nodes)\n if (node.kind === 'state') {\n return node;\n }\n\n // Compound conditions - recursively simplify\n if (node.kind === 'compound') {\n // First, recursively simplify all children\n const simplifiedChildren = node.children.map((c) => simplifyInner(c));\n\n // Then apply compound-specific simplifications\n if (node.operator === 'AND') {\n return simplifyAnd(simplifiedChildren);\n } else {\n return simplifyOr(simplifiedChildren);\n }\n }\n\n return node;\n}\n\n// ============================================================================\n// AND Simplification\n// ============================================================================\n\nfunction simplifyAnd(children: ConditionNode[]): ConditionNode {\n let terms: ConditionNode[] = [];\n\n // Flatten nested ANDs and handle TRUE/FALSE\n for (const child of children) {\n if (child.kind === 'false') {\n // AND with FALSE → FALSE\n return falseCondition();\n }\n if (child.kind === 'true') {\n // AND with TRUE → skip (identity)\n continue;\n }\n if (child.kind === 'compound' && child.operator === 'AND') {\n // Flatten nested AND\n terms.push(...child.children);\n } else {\n terms.push(child);\n }\n }\n\n // Empty → TRUE\n if (terms.length === 0) {\n return trueCondition();\n }\n\n // Single term → return it\n if (terms.length === 1) {\n return terms[0];\n }\n\n // Check for contradictions\n if (hasContradiction(terms)) {\n return falseCondition();\n }\n\n // Check for range contradictions in media/container queries\n if (hasRangeContradiction(terms)) {\n return falseCondition();\n }\n\n // Check for attribute value conflicts\n if (hasAttributeConflict(terms)) {\n return falseCondition();\n }\n\n // Check for container style query conflicts\n if (hasContainerStyleConflict(terms)) {\n return falseCondition();\n }\n\n // Remove redundant negations implied by positive terms\n // e.g., style(--variant: danger) implies NOT style(--variant: success)\n // and style(--variant: danger) implies style(--variant) (existence)\n terms = removeImpliedNegations(terms);\n\n // Deduplicate (by uniqueId)\n terms = deduplicateTerms(terms);\n\n // Try to merge numeric ranges\n terms = mergeRanges(terms);\n\n // Sort for canonical form\n terms = sortTerms(terms);\n\n // Apply absorption: A & (A | B) → A\n terms = applyAbsorptionAnd(terms);\n\n if (terms.length === 0) {\n return trueCondition();\n }\n if (terms.length === 1) {\n return terms[0];\n }\n\n return {\n kind: 'compound',\n operator: 'AND',\n children: terms,\n };\n}\n\n// ============================================================================\n// OR Simplification\n// ============================================================================\n\nfunction simplifyOr(children: ConditionNode[]): ConditionNode {\n let terms: ConditionNode[] = [];\n\n // Flatten nested ORs and handle TRUE/FALSE\n for (const child of children) {\n if (child.kind === 'true') {\n // OR with TRUE → TRUE\n return trueCondition();\n }\n if (child.kind === 'false') {\n // OR with FALSE → skip (identity)\n continue;\n }\n if (child.kind === 'compound' && child.operator === 'OR') {\n // Flatten nested OR\n terms.push(...child.children);\n } else {\n terms.push(child);\n }\n }\n\n // Empty → FALSE\n if (terms.length === 0) {\n return falseCondition();\n }\n\n // Single term → return it\n if (terms.length === 1) {\n return terms[0];\n }\n\n // Check for tautologies (A | !A)\n if (hasTautology(terms)) {\n return trueCondition();\n }\n\n // Deduplicate\n terms = deduplicateTerms(terms);\n\n // Sort for canonical form\n terms = sortTerms(terms);\n\n // Apply absorption: A | (A & B) → A\n terms = applyAbsorptionOr(terms);\n\n if (terms.length === 0) {\n return falseCondition();\n }\n if (terms.length === 1) {\n return terms[0];\n }\n\n return {\n kind: 'compound',\n operator: 'OR',\n children: terms,\n };\n}\n\n// ============================================================================\n// Contradiction Detection\n// ============================================================================\n\n/**\n * Check if any pair of terms has complementary negation (A and !A).\n * Used for both contradiction detection (in AND) and tautology detection (in OR),\n * since the underlying check is identical: the context determines the semantics.\n */\nfunction hasComplementaryPair(terms: ConditionNode[]): boolean {\n const uniqueIds = new Set<string>();\n\n for (const term of terms) {\n if (term.kind !== 'state') continue;\n\n const id = term.uniqueId;\n const negatedId = term.negated ? id.slice(1) : `!${id}`;\n\n if (uniqueIds.has(negatedId)) {\n return true;\n }\n uniqueIds.add(id);\n }\n\n return false;\n}\n\nconst hasContradiction = hasComplementaryPair;\nconst hasTautology = hasComplementaryPair;\n\n// ============================================================================\n// Range Contradiction Detection\n// ============================================================================\n\n/**\n * Effective bounds computed from conditions (including negated single-bound conditions)\n */\ninterface EffectiveBounds {\n lowerBound: number | null;\n lowerInclusive: boolean;\n upperBound: number | null;\n upperInclusive: boolean;\n}\n\n/**\n * Excluded range from a negated range condition\n */\ninterface ExcludedRange {\n lower: number;\n lowerInclusive: boolean;\n upper: number;\n upperInclusive: boolean;\n}\n\n/**\n * Check for range contradictions in media/container queries\n * e.g., @media(w < 400px) & @media(w > 800px) → FALSE\n *\n * Also handles negated conditions:\n * - Single-bound negations are inverted (not (w < 600px) → w >= 600px)\n * - Range negations create excluded ranges that are checked against positive bounds\n */\nfunction hasRangeContradiction(terms: ConditionNode[]): boolean {\n // Group by dimension, separating positive and negated conditions\n const mediaByDim = new Map<\n string,\n { positive: MediaCondition[]; negated: MediaCondition[] }\n >();\n const containerByDim = new Map<\n string,\n { positive: ContainerCondition[]; negated: ContainerCondition[] }\n >();\n\n for (const term of terms) {\n if (term.kind !== 'state') continue;\n\n if (term.type === 'media' && term.subtype === 'dimension') {\n const key = term.dimension || 'width';\n if (!mediaByDim.has(key)) {\n mediaByDim.set(key, { positive: [], negated: [] });\n }\n const group = mediaByDim.get(key)!;\n if (term.negated) {\n group.negated.push(term);\n } else {\n group.positive.push(term);\n }\n }\n\n if (term.type === 'container' && term.subtype === 'dimension') {\n const key = `${term.containerName || '_'}:${term.dimension || 'width'}`;\n if (!containerByDim.has(key)) {\n containerByDim.set(key, { positive: [], negated: [] });\n }\n const group = containerByDim.get(key)!;\n if (term.negated) {\n group.negated.push(term);\n } else {\n group.positive.push(term);\n }\n }\n }\n\n // Check each dimension group for impossible ranges\n for (const group of mediaByDim.values()) {\n if (rangesAreImpossibleWithNegations(group.positive, group.negated)) {\n return true;\n }\n }\n\n for (const group of containerByDim.values()) {\n if (rangesAreImpossibleWithNegations(group.positive, group.negated)) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Check if conditions are impossible, including negated conditions.\n *\n * For negated single-bound conditions:\n * not (w < 600px) → w >= 600px (inverted to lower bound)\n * not (w >= 800px) → w < 800px (inverted to upper bound)\n *\n * For negated range conditions:\n * not (400px <= w < 800px) → excludes [400, 800)\n * If the effective bounds fall entirely within an excluded range, it's impossible.\n */\nfunction rangesAreImpossibleWithNegations(\n positive: (MediaCondition | ContainerCondition)[],\n negated: (MediaCondition | ContainerCondition)[],\n): boolean {\n // Start with bounds from positive conditions\n const bounds = computeEffectiveBounds(positive);\n\n // Apply inverted bounds from single-bound negated conditions\n // and collect excluded ranges from range negated conditions\n const excludedRanges: ExcludedRange[] = [];\n\n for (const cond of negated) {\n const hasLower = cond.lowerBound?.valueNumeric != null;\n const hasUpper = cond.upperBound?.valueNumeric != null;\n\n if (hasLower && hasUpper) {\n // Range negation: not (lower <= w < upper) excludes [lower, upper)\n excludedRanges.push({\n lower: cond.lowerBound!.valueNumeric!,\n lowerInclusive: cond.lowerBound!.inclusive,\n upper: cond.upperBound!.valueNumeric!,\n upperInclusive: cond.upperBound!.inclusive,\n });\n } else if (hasUpper) {\n // not (w < upper) → w >= upper (becomes lower bound)\n // not (w <= upper) → w > upper (becomes lower bound, exclusive)\n const value = cond.upperBound!.valueNumeric!;\n const inclusive = !cond.upperBound!.inclusive; // flip inclusivity\n\n if (bounds.lowerBound === null || value > bounds.lowerBound) {\n bounds.lowerBound = value;\n bounds.lowerInclusive = inclusive;\n } else if (value === bounds.lowerBound && !inclusive) {\n bounds.lowerInclusive = false;\n }\n } else if (hasLower) {\n // not (w >= lower) → w < lower (becomes upper bound)\n // not (w > lower) → w <= lower (becomes upper bound, inclusive)\n const value = cond.lowerBound!.valueNumeric!;\n const inclusive = !cond.lowerBound!.inclusive; // flip inclusivity\n\n if (bounds.upperBound === null || value < bounds.upperBound) {\n bounds.upperBound = value;\n bounds.upperInclusive = inclusive;\n } else if (value === bounds.upperBound && !inclusive) {\n bounds.upperInclusive = false;\n }\n }\n }\n\n // Check if effective bounds are impossible on their own\n if (bounds.lowerBound !== null && bounds.upperBound !== null) {\n if (bounds.lowerBound > bounds.upperBound) {\n return true;\n }\n if (\n bounds.lowerBound === bounds.upperBound &&\n (!bounds.lowerInclusive || !bounds.upperInclusive)\n ) {\n return true;\n }\n }\n\n // Check if effective bounds fall entirely within any excluded range\n if (\n bounds.lowerBound !== null &&\n bounds.upperBound !== null &&\n excludedRanges.length > 0\n ) {\n for (const excluded of excludedRanges) {\n if (boundsWithinExcludedRange(bounds, excluded)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Compute effective bounds from positive (non-negated) conditions\n */\nfunction computeEffectiveBounds(\n conditions: (MediaCondition | ContainerCondition)[],\n): EffectiveBounds {\n let lowerBound: number | null = null;\n let lowerInclusive = false;\n let upperBound: number | null = null;\n let upperInclusive = false;\n\n for (const cond of conditions) {\n if (cond.lowerBound?.valueNumeric != null) {\n const value = cond.lowerBound.valueNumeric;\n const inclusive = cond.lowerBound.inclusive;\n\n if (lowerBound === null || value > lowerBound) {\n lowerBound = value;\n lowerInclusive = inclusive;\n } else if (value === lowerBound && !inclusive) {\n lowerInclusive = false;\n }\n }\n\n if (cond.upperBound?.valueNumeric != null) {\n const value = cond.upperBound.valueNumeric;\n const inclusive = cond.upperBound.inclusive;\n\n if (upperBound === null || value < upperBound) {\n upperBound = value;\n upperInclusive = inclusive;\n } else if (value === upperBound && !inclusive) {\n upperInclusive = false;\n }\n }\n }\n\n return { lowerBound, lowerInclusive, upperBound, upperInclusive };\n}\n\n/**\n * Check if effective bounds fall entirely within an excluded range.\n *\n * For example:\n * Effective: [400, 800)\n * Excluded: [400, 800)\n * → bounds fall entirely within excluded range → impossible\n */\nfunction boundsWithinExcludedRange(\n bounds: EffectiveBounds,\n excluded: ExcludedRange,\n): boolean {\n if (bounds.lowerBound === null || bounds.upperBound === null) {\n return false;\n }\n\n // Check if bounds.lower >= excluded.lower\n let lowerOk = false;\n if (bounds.lowerBound > excluded.lower) {\n lowerOk = true;\n } else if (bounds.lowerBound === excluded.lower) {\n // If excluded includes lower, and bounds includes or excludes lower, it's within\n // If excluded excludes lower, bounds must also exclude it to be within\n lowerOk = excluded.lowerInclusive || !bounds.lowerInclusive;\n }\n\n // Check if bounds.upper <= excluded.upper\n let upperOk = false;\n if (bounds.upperBound < excluded.upper) {\n upperOk = true;\n } else if (bounds.upperBound === excluded.upper) {\n // If excluded includes upper, and bounds includes or excludes upper, it's within\n // If excluded excludes upper, bounds must also exclude it to be within\n upperOk = excluded.upperInclusive || !bounds.upperInclusive;\n }\n\n return lowerOk && upperOk;\n}\n\n// ============================================================================\n// Attribute Conflict Detection\n// ============================================================================\n\n/**\n * Check for attribute value conflicts\n * e.g., [data-theme=\"dark\"] & [data-theme=\"light\"] → FALSE\n * e.g., [data-theme=\"dark\"] & ![data-theme] → FALSE\n */\n/**\n * Generic value-conflict checker for grouped conditions.\n *\n * Groups terms by a key, splits into positive/negated, then checks:\n * 1. Multiple distinct positive values → conflict\n * 2. Positive value + negated existence (value === undefined) → conflict\n * 3. Positive value + negated same value → conflict\n */\nfunction hasGroupedValueConflict<T extends { negated: boolean }>(\n terms: ConditionNode[],\n match: (term: ConditionNode) => T | null,\n groupKey: (term: T) => string,\n getValue: (term: T) => string | undefined,\n): boolean {\n const groups = new Map<string, { positive: T[]; negated: T[] }>();\n\n for (const term of terms) {\n const matched = match(term);\n if (!matched) continue;\n\n const key = groupKey(matched);\n let group = groups.get(key);\n if (!group) {\n group = { positive: [], negated: [] };\n groups.set(key, group);\n }\n\n if (matched.negated) {\n group.negated.push(matched);\n } else {\n group.positive.push(matched);\n }\n }\n\n for (const [, group] of groups) {\n const positiveValues = group.positive\n .map(getValue)\n .filter((v) => v !== undefined);\n if (new Set(positiveValues).size > 1) return true;\n\n const hasPositiveValue = positiveValues.length > 0;\n const hasNegatedExistence = group.negated.some(\n (t) => getValue(t) === undefined,\n );\n if (hasPositiveValue && hasNegatedExistence) return true;\n\n for (const pos of group.positive) {\n const posVal = getValue(pos);\n if (posVal !== undefined) {\n for (const neg of group.negated) {\n if (getValue(neg) === posVal) return true;\n }\n }\n }\n }\n\n return false;\n}\n\nfunction hasAttributeConflict(terms: ConditionNode[]): boolean {\n return hasGroupedValueConflict<ModifierCondition>(\n terms,\n (t) => (t.kind === 'state' && t.type === 'modifier' ? t : null),\n (t) => t.attribute,\n (t) => t.value,\n );\n}\n\nfunction hasContainerStyleConflict(terms: ConditionNode[]): boolean {\n return hasGroupedValueConflict<ContainerCondition>(\n terms,\n (t) =>\n t.kind === 'state' && t.type === 'container' && t.subtype === 'style'\n ? t\n : null,\n (t) => `${t.containerName || '_'}:${t.property}`,\n (t) => t.propertyValue,\n );\n}\n\n// ============================================================================\n// Implied Negation Removal\n// ============================================================================\n\n/**\n * Remove negations that are implied by positive terms.\n *\n * Key optimizations:\n * 1. style(--variant: danger) implies NOT style(--variant: success)\n * → If we have style(--variant: danger) & not style(--variant: success),\n * the negation is redundant and can be removed.\n *\n * 2. [data-theme=\"dark\"] implies NOT [data-theme=\"light\"]\n * → Same logic for attribute selectors.\n *\n * This produces cleaner CSS:\n * Before: @container style(--variant: danger) and (not style(--variant: success))\n * After: @container style(--variant: danger)\n */\n/**\n * Collect positive values from terms and build a \"is this negation implied?\" check.\n *\n * A negation is implied (redundant) when a positive term for the same group\n * already pins a specific value, making \"NOT other-value\" obvious.\n * e.g. style(--variant: danger) implies NOT style(--variant: success).\n */\nfunction buildImpliedNegationCheck(\n terms: ConditionNode[],\n): (term: ConditionNode) => boolean {\n const positiveValues = new Map<string, string>();\n\n for (const term of terms) {\n if (term.kind !== 'state' || term.negated) continue;\n\n if (term.type === 'container' && term.subtype === 'style') {\n if (term.propertyValue !== undefined) {\n positiveValues.set(\n `c:${term.containerName || '_'}:${term.property}`,\n term.propertyValue,\n );\n }\n } else if (term.type === 'modifier' && term.value !== undefined) {\n positiveValues.set(`m:${term.attribute}`, term.value);\n }\n }\n\n return (term: ConditionNode): boolean => {\n if (term.kind !== 'state' || !term.negated) return false;\n\n if (term.type === 'container' && term.subtype === 'style') {\n if (term.propertyValue === undefined) return false;\n const pos = positiveValues.get(\n `c:${term.containerName || '_'}:${term.property}`,\n );\n return pos !== undefined && term.propertyValue !== pos;\n }\n\n if (term.type === 'modifier' && term.value !== undefined) {\n const pos = positiveValues.get(`m:${term.attribute}`);\n return pos !== undefined && term.value !== pos;\n }\n\n return false;\n };\n}\n\nfunction removeImpliedNegations(terms: ConditionNode[]): ConditionNode[] {\n const isImplied = buildImpliedNegationCheck(terms);\n return terms.filter((t) => !isImplied(t));\n}\n\n// ============================================================================\n// Deduplication\n// ============================================================================\n\nfunction deduplicateTerms(terms: ConditionNode[]): ConditionNode[] {\n const seen = new Set<string>();\n const result: ConditionNode[] = [];\n\n for (const term of terms) {\n const id = getConditionUniqueId(term);\n if (!seen.has(id)) {\n seen.add(id);\n result.push(term);\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Range Merging\n// ============================================================================\n\n/**\n * Merge compatible range conditions\n * e.g., @media(w >= 400px) & @media(w <= 800px) → @media(400px <= w <= 800px)\n */\nfunction mergeRanges(terms: ConditionNode[]): ConditionNode[] {\n // Group media conditions by dimension\n const mediaByDim = new Map<\n string,\n { conditions: MediaCondition[]; indices: number[] }\n >();\n const containerByDim = new Map<\n string,\n { conditions: ContainerCondition[]; indices: number[] }\n >();\n\n terms.forEach((term, index) => {\n if (term.kind !== 'state') return;\n\n if (\n term.type === 'media' &&\n term.subtype === 'dimension' &&\n !term.negated\n ) {\n const key = term.dimension || 'width';\n if (!mediaByDim.has(key)) {\n mediaByDim.set(key, { conditions: [], indices: [] });\n }\n const group = mediaByDim.get(key)!;\n group.conditions.push(term);\n group.indices.push(index);\n }\n\n if (\n term.type === 'container' &&\n term.subtype === 'dimension' &&\n !term.negated\n ) {\n const key = `${term.containerName || '_'}:${term.dimension || 'width'}`;\n if (!containerByDim.has(key)) {\n containerByDim.set(key, { conditions: [], indices: [] });\n }\n const group = containerByDim.get(key)!;\n group.conditions.push(term);\n group.indices.push(index);\n }\n });\n\n // Track indices to remove\n const indicesToRemove = new Set<number>();\n const mergedTerms: ConditionNode[] = [];\n\n // Merge media conditions\n for (const [_dim, group] of mediaByDim) {\n if (group.conditions.length > 1) {\n const merged = mergeMediaRanges(group.conditions);\n if (merged) {\n group.indices.forEach((i) => indicesToRemove.add(i));\n mergedTerms.push(merged);\n }\n }\n }\n\n // Merge container conditions\n for (const [, group] of containerByDim) {\n if (group.conditions.length > 1) {\n const merged = mergeContainerRanges(group.conditions);\n if (merged) {\n group.indices.forEach((i) => indicesToRemove.add(i));\n mergedTerms.push(merged);\n }\n }\n }\n\n // Build result\n const result: ConditionNode[] = [];\n terms.forEach((term, index) => {\n if (!indicesToRemove.has(index)) {\n result.push(term);\n }\n });\n result.push(...mergedTerms);\n\n return result;\n}\n\n/**\n * Tighten bounds by picking the most restrictive lower and upper bounds\n * from a set of conditions that have lowerBound/upperBound fields.\n */\nfunction tightenBounds(\n conditions: { lowerBound?: NumericBound; upperBound?: NumericBound }[],\n): { lowerBound?: NumericBound; upperBound?: NumericBound } {\n let lowerBound: NumericBound | undefined;\n let upperBound: NumericBound | undefined;\n\n for (const cond of conditions) {\n if (cond.lowerBound) {\n if (\n !lowerBound ||\n (cond.lowerBound.valueNumeric ?? -Infinity) >\n (lowerBound.valueNumeric ?? -Infinity)\n ) {\n lowerBound = cond.lowerBound;\n }\n }\n if (cond.upperBound) {\n if (\n !upperBound ||\n (cond.upperBound.valueNumeric ?? Infinity) <\n (upperBound.valueNumeric ?? Infinity)\n ) {\n upperBound = cond.upperBound;\n }\n }\n }\n\n return { lowerBound, upperBound };\n}\n\nfunction appendBoundsToUniqueId(\n parts: string[],\n lowerBound?: NumericBound,\n upperBound?: NumericBound,\n): void {\n if (lowerBound) {\n parts.push(lowerBound.inclusive ? '>=' : '>');\n parts.push(lowerBound.value);\n }\n if (upperBound) {\n parts.push(upperBound.inclusive ? '<=' : '<');\n parts.push(upperBound.value);\n }\n}\n\nfunction mergeDimensionRanges<T extends MediaCondition | ContainerCondition>(\n conditions: T[],\n idPrefix: string[],\n): T | null {\n if (conditions.length === 0) return null;\n\n const { lowerBound, upperBound } = tightenBounds(conditions);\n const base = conditions[0];\n\n const parts = [...idPrefix];\n appendBoundsToUniqueId(parts, lowerBound, upperBound);\n\n return {\n ...base,\n negated: false,\n raw: buildMergedRaw(base.dimension || 'width', lowerBound, upperBound),\n uniqueId: parts.join(':'),\n lowerBound,\n upperBound,\n };\n}\n\nfunction mergeMediaRanges(conditions: MediaCondition[]): MediaCondition | null {\n const dim = conditions[0]?.dimension ?? 'width';\n return mergeDimensionRanges(conditions, ['media', 'dim', dim]);\n}\n\nfunction mergeContainerRanges(\n conditions: ContainerCondition[],\n): ContainerCondition | null {\n const base = conditions[0];\n if (!base) return null;\n const name = base.containerName || '_';\n const dim = base.dimension ?? 'width';\n return mergeDimensionRanges(conditions, ['container', 'dim', name, dim]);\n}\n\nfunction buildMergedRaw(\n dimension: string,\n lowerBound?: NumericBound,\n upperBound?: NumericBound,\n): string {\n if (lowerBound && upperBound) {\n const lowerOp = lowerBound.inclusive ? '<=' : '<';\n const upperOp = upperBound.inclusive ? '<=' : '<';\n return `@media(${lowerBound.value} ${lowerOp} ${dimension} ${upperOp} ${upperBound.value})`;\n } else if (upperBound) {\n const op = upperBound.inclusive ? '<=' : '<';\n return `@media(${dimension} ${op} ${upperBound.value})`;\n } else if (lowerBound) {\n const op = lowerBound.inclusive ? '>=' : '>';\n return `@media(${dimension} ${op} ${lowerBound.value})`;\n }\n return '@media()';\n}\n\n// ============================================================================\n// Sorting\n// ============================================================================\n\nfunction sortTerms(terms: ConditionNode[]): ConditionNode[] {\n const withIds = terms.map((t) => [getConditionUniqueId(t), t] as const);\n withIds.sort((a, b) => a[0].localeCompare(b[0]));\n return withIds.map(([, t]) => t);\n}\n\n// ============================================================================\n// Absorption\n// ============================================================================\n\n/**\n * Apply the absorption law: removes compound terms that are absorbed by\n * a simple term already present.\n *\n * For AND context: A & (A | B) → A (absorbs OR compounds)\n * For OR context: A | (A & B) → A (absorbs AND compounds)\n */\nfunction applyAbsorption(\n terms: ConditionNode[],\n absorbedOperator: 'OR' | 'AND',\n): ConditionNode[] {\n const simpleIds = new Set<string>();\n for (const term of terms) {\n if (term.kind !== 'compound') {\n simpleIds.add(getConditionUniqueId(term));\n }\n }\n\n return terms.filter((term) => {\n if (term.kind === 'compound' && term.operator === absorbedOperator) {\n for (const child of term.children) {\n if (simpleIds.has(getConditionUniqueId(child))) {\n return false;\n }\n }\n }\n return true;\n });\n}\n\nfunction applyAbsorptionAnd(terms: ConditionNode[]): ConditionNode[] {\n return applyAbsorption(terms, 'OR');\n}\n\nfunction applyAbsorptionOr(terms: ConditionNode[]): ConditionNode[] {\n return applyAbsorption(terms, 'AND');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"simplify.js","names":[],"sources":["../../src/pipeline/simplify.ts"],"sourcesContent":["/**\n * Condition Simplification Engine\n *\n * Simplifies condition trees by applying boolean algebra rules,\n * detecting contradictions, merging ranges, and deduplicating terms.\n *\n * This is critical for:\n * 1. Detecting invalid combinations (A & !A → FALSE)\n * 2. Reducing CSS output size\n * 3. Producing cleaner selectors\n */\n\nimport { Lru } from '../parser/lru';\n\nimport type {\n ConditionNode,\n ContainerCondition,\n MediaCondition,\n ModifierCondition,\n NumericBound,\n} from './conditions';\nimport {\n falseCondition,\n getConditionUniqueId,\n trueCondition,\n} from './conditions';\n\n// ============================================================================\n// Caching\n// ============================================================================\n\nconst simplifyCache = new Lru<string, ConditionNode>(5000);\n\n// ============================================================================\n// Main Simplify Function\n// ============================================================================\n\n/**\n * Simplify a condition tree aggressively.\n *\n * This applies all possible simplification rules:\n * - Boolean algebra (identity, annihilator, idempotent, absorption)\n * - Contradiction detection (A & !A → FALSE)\n * - Tautology detection (A | !A → TRUE)\n * - Range intersection for numeric queries\n * - Attribute value conflict detection\n * - Deduplication and sorting\n */\nexport function simplifyCondition(node: ConditionNode): ConditionNode {\n // Check cache\n const key = getConditionUniqueId(node);\n const cached = simplifyCache.get(key);\n if (cached) {\n return cached;\n }\n\n const result = simplifyInner(node);\n\n // Cache result\n simplifyCache.set(key, result);\n\n return result;\n}\n\n/**\n * Clear the simplify cache (for testing)\n */\nexport function clearSimplifyCache(): void {\n simplifyCache.clear();\n}\n\n// ============================================================================\n// Inner Simplification\n// ============================================================================\n\nfunction simplifyInner(node: ConditionNode): ConditionNode {\n // Base cases\n if (node.kind === 'true' || node.kind === 'false') {\n return node;\n }\n\n // State conditions - return as-is (they're already leaf nodes)\n if (node.kind === 'state') {\n return node;\n }\n\n // Compound conditions - recursively simplify\n if (node.kind === 'compound') {\n // First, recursively simplify all children\n const simplifiedChildren = node.children.map((c) => simplifyInner(c));\n\n // Then apply compound-specific simplifications\n if (node.operator === 'AND') {\n return simplifyAnd(simplifiedChildren);\n } else {\n return simplifyOr(simplifiedChildren);\n }\n }\n\n return node;\n}\n\n// ============================================================================\n// AND Simplification\n// ============================================================================\n\nfunction simplifyAnd(children: ConditionNode[]): ConditionNode {\n let terms: ConditionNode[] = [];\n\n // Flatten nested ANDs and handle TRUE/FALSE\n for (const child of children) {\n if (child.kind === 'false') {\n // AND with FALSE → FALSE\n return falseCondition();\n }\n if (child.kind === 'true') {\n // AND with TRUE → skip (identity)\n continue;\n }\n if (child.kind === 'compound' && child.operator === 'AND') {\n // Flatten nested AND\n terms.push(...child.children);\n } else {\n terms.push(child);\n }\n }\n\n // Empty → TRUE\n if (terms.length === 0) {\n return trueCondition();\n }\n\n // Single term → return it\n if (terms.length === 1) {\n return terms[0];\n }\n\n // Check for contradictions\n if (hasContradiction(terms)) {\n return falseCondition();\n }\n\n // Check for range contradictions in media/container queries\n if (hasRangeContradiction(terms)) {\n return falseCondition();\n }\n\n // Check for attribute value conflicts\n if (hasAttributeConflict(terms)) {\n return falseCondition();\n }\n\n // Check for container style query conflicts\n if (hasContainerStyleConflict(terms)) {\n return falseCondition();\n }\n\n // Remove redundant negations implied by positive terms\n // e.g., style(--variant: danger) implies NOT style(--variant: success)\n // and style(--variant: danger) implies style(--variant) (existence)\n terms = removeImpliedNegations(terms);\n\n // Deduplicate (by uniqueId)\n terms = deduplicateTerms(terms);\n\n // Try to merge numeric ranges\n terms = mergeRanges(terms);\n\n // Sort for canonical form\n terms = sortTerms(terms);\n\n // Apply absorption: A & (A | B) → A\n terms = applyAbsorptionAnd(terms);\n\n if (terms.length === 0) {\n return trueCondition();\n }\n if (terms.length === 1) {\n return terms[0];\n }\n\n return {\n kind: 'compound',\n operator: 'AND',\n children: terms,\n };\n}\n\n// ============================================================================\n// OR Simplification\n// ============================================================================\n\nfunction simplifyOr(children: ConditionNode[]): ConditionNode {\n let terms: ConditionNode[] = [];\n\n // Flatten nested ORs and handle TRUE/FALSE\n for (const child of children) {\n if (child.kind === 'true') {\n // OR with TRUE → TRUE\n return trueCondition();\n }\n if (child.kind === 'false') {\n // OR with FALSE → skip (identity)\n continue;\n }\n if (child.kind === 'compound' && child.operator === 'OR') {\n // Flatten nested OR\n terms.push(...child.children);\n } else {\n terms.push(child);\n }\n }\n\n // Empty → FALSE\n if (terms.length === 0) {\n return falseCondition();\n }\n\n // Single term → return it\n if (terms.length === 1) {\n return terms[0];\n }\n\n // Check for tautologies (A | !A)\n if (hasTautology(terms)) {\n return trueCondition();\n }\n\n // Deduplicate\n terms = deduplicateTerms(terms);\n\n // Sort for canonical form\n terms = sortTerms(terms);\n\n // Apply absorption: A | (A & B) → A\n terms = applyAbsorptionOr(terms);\n\n if (terms.length === 0) {\n return falseCondition();\n }\n if (terms.length === 1) {\n return terms[0];\n }\n\n return {\n kind: 'compound',\n operator: 'OR',\n children: terms,\n };\n}\n\n// ============================================================================\n// Contradiction Detection\n// ============================================================================\n\n/**\n * Check if any pair of terms has complementary negation (A and !A).\n * Used for both contradiction detection (in AND) and tautology detection (in OR),\n * since the underlying check is identical: the context determines the semantics.\n */\nfunction hasComplementaryPair(terms: ConditionNode[]): boolean {\n const uniqueIds = new Set<string>();\n\n for (const term of terms) {\n if (term.kind !== 'state') continue;\n\n const id = term.uniqueId;\n const negatedId = term.negated ? id.slice(1) : `!${id}`;\n\n if (uniqueIds.has(negatedId)) {\n return true;\n }\n uniqueIds.add(id);\n }\n\n return false;\n}\n\nconst hasContradiction = hasComplementaryPair;\nconst hasTautology = hasComplementaryPair;\n\n// ============================================================================\n// Range Contradiction Detection\n// ============================================================================\n\n/**\n * Effective bounds computed from conditions (including negated single-bound conditions)\n */\ninterface EffectiveBounds {\n lowerBound: number | null;\n lowerInclusive: boolean;\n upperBound: number | null;\n upperInclusive: boolean;\n}\n\n/**\n * Excluded range from a negated range condition\n */\ninterface ExcludedRange {\n lower: number;\n lowerInclusive: boolean;\n upper: number;\n upperInclusive: boolean;\n}\n\n/**\n * Check for range contradictions in media/container queries\n * e.g., @media(w < 400px) & @media(w > 800px) → FALSE\n *\n * Also handles negated conditions:\n * - Single-bound negations are inverted (not (w < 600px) → w >= 600px)\n * - Range negations create excluded ranges that are checked against positive bounds\n */\nfunction hasRangeContradiction(terms: ConditionNode[]): boolean {\n // Group by dimension, separating positive and negated conditions\n const mediaByDim = new Map<\n string,\n { positive: MediaCondition[]; negated: MediaCondition[] }\n >();\n const containerByDim = new Map<\n string,\n { positive: ContainerCondition[]; negated: ContainerCondition[] }\n >();\n\n for (const term of terms) {\n if (term.kind !== 'state') continue;\n\n if (term.type === 'media' && term.subtype === 'dimension') {\n const key = term.dimension || 'width';\n if (!mediaByDim.has(key)) {\n mediaByDim.set(key, { positive: [], negated: [] });\n }\n const group = mediaByDim.get(key)!;\n if (term.negated) {\n group.negated.push(term);\n } else {\n group.positive.push(term);\n }\n }\n\n if (term.type === 'container' && term.subtype === 'dimension') {\n const key = `${term.containerName || '_'}:${term.dimension || 'width'}`;\n if (!containerByDim.has(key)) {\n containerByDim.set(key, { positive: [], negated: [] });\n }\n const group = containerByDim.get(key)!;\n if (term.negated) {\n group.negated.push(term);\n } else {\n group.positive.push(term);\n }\n }\n }\n\n // Check each dimension group for impossible ranges\n for (const group of mediaByDim.values()) {\n if (rangesAreImpossibleWithNegations(group.positive, group.negated)) {\n return true;\n }\n }\n\n for (const group of containerByDim.values()) {\n if (rangesAreImpossibleWithNegations(group.positive, group.negated)) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Check if conditions are impossible, including negated conditions.\n *\n * For negated single-bound conditions:\n * not (w < 600px) → w >= 600px (inverted to lower bound)\n * not (w >= 800px) → w < 800px (inverted to upper bound)\n *\n * For negated range conditions:\n * not (400px <= w < 800px) → excludes [400, 800)\n * If the effective bounds fall entirely within an excluded range, it's impossible.\n */\nfunction rangesAreImpossibleWithNegations(\n positive: (MediaCondition | ContainerCondition)[],\n negated: (MediaCondition | ContainerCondition)[],\n): boolean {\n // Start with bounds from positive conditions\n const bounds = computeEffectiveBounds(positive);\n\n // Apply inverted bounds from single-bound negated conditions\n // and collect excluded ranges from range negated conditions\n const excludedRanges: ExcludedRange[] = [];\n\n for (const cond of negated) {\n const hasLower = cond.lowerBound?.valueNumeric != null;\n const hasUpper = cond.upperBound?.valueNumeric != null;\n\n if (hasLower && hasUpper) {\n // Range negation: not (lower <= w < upper) excludes [lower, upper)\n excludedRanges.push({\n lower: cond.lowerBound!.valueNumeric!,\n lowerInclusive: cond.lowerBound!.inclusive,\n upper: cond.upperBound!.valueNumeric!,\n upperInclusive: cond.upperBound!.inclusive,\n });\n } else if (hasUpper) {\n // not (w < upper) → w >= upper (becomes lower bound)\n // not (w <= upper) → w > upper (becomes lower bound, exclusive)\n const value = cond.upperBound!.valueNumeric!;\n const inclusive = !cond.upperBound!.inclusive; // flip inclusivity\n\n if (bounds.lowerBound === null || value > bounds.lowerBound) {\n bounds.lowerBound = value;\n bounds.lowerInclusive = inclusive;\n } else if (value === bounds.lowerBound && !inclusive) {\n bounds.lowerInclusive = false;\n }\n } else if (hasLower) {\n // not (w >= lower) → w < lower (becomes upper bound)\n // not (w > lower) → w <= lower (becomes upper bound, inclusive)\n const value = cond.lowerBound!.valueNumeric!;\n const inclusive = !cond.lowerBound!.inclusive; // flip inclusivity\n\n if (bounds.upperBound === null || value < bounds.upperBound) {\n bounds.upperBound = value;\n bounds.upperInclusive = inclusive;\n } else if (value === bounds.upperBound && !inclusive) {\n bounds.upperInclusive = false;\n }\n }\n }\n\n // Check if effective bounds are impossible on their own\n if (bounds.lowerBound !== null && bounds.upperBound !== null) {\n if (bounds.lowerBound > bounds.upperBound) {\n return true;\n }\n if (\n bounds.lowerBound === bounds.upperBound &&\n (!bounds.lowerInclusive || !bounds.upperInclusive)\n ) {\n return true;\n }\n }\n\n // Check if effective bounds fall entirely within any excluded range\n if (\n bounds.lowerBound !== null &&\n bounds.upperBound !== null &&\n excludedRanges.length > 0\n ) {\n for (const excluded of excludedRanges) {\n if (boundsWithinExcludedRange(bounds, excluded)) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Compute effective bounds from positive (non-negated) conditions\n */\nfunction computeEffectiveBounds(\n conditions: (MediaCondition | ContainerCondition)[],\n): EffectiveBounds {\n let lowerBound: number | null = null;\n let lowerInclusive = false;\n let upperBound: number | null = null;\n let upperInclusive = false;\n\n for (const cond of conditions) {\n if (cond.lowerBound?.valueNumeric != null) {\n const value = cond.lowerBound.valueNumeric;\n const inclusive = cond.lowerBound.inclusive;\n\n if (lowerBound === null || value > lowerBound) {\n lowerBound = value;\n lowerInclusive = inclusive;\n } else if (value === lowerBound && !inclusive) {\n lowerInclusive = false;\n }\n }\n\n if (cond.upperBound?.valueNumeric != null) {\n const value = cond.upperBound.valueNumeric;\n const inclusive = cond.upperBound.inclusive;\n\n if (upperBound === null || value < upperBound) {\n upperBound = value;\n upperInclusive = inclusive;\n } else if (value === upperBound && !inclusive) {\n upperInclusive = false;\n }\n }\n }\n\n return { lowerBound, lowerInclusive, upperBound, upperInclusive };\n}\n\n/**\n * Check if effective bounds fall entirely within an excluded range.\n *\n * For example:\n * Effective: [400, 800)\n * Excluded: [400, 800)\n * → bounds fall entirely within excluded range → impossible\n */\nfunction boundsWithinExcludedRange(\n bounds: EffectiveBounds,\n excluded: ExcludedRange,\n): boolean {\n if (bounds.lowerBound === null || bounds.upperBound === null) {\n return false;\n }\n\n // Check if bounds.lower >= excluded.lower\n let lowerOk = false;\n if (bounds.lowerBound > excluded.lower) {\n lowerOk = true;\n } else if (bounds.lowerBound === excluded.lower) {\n // If excluded includes lower, and bounds includes or excludes lower, it's within\n // If excluded excludes lower, bounds must also exclude it to be within\n lowerOk = excluded.lowerInclusive || !bounds.lowerInclusive;\n }\n\n // Check if bounds.upper <= excluded.upper\n let upperOk = false;\n if (bounds.upperBound < excluded.upper) {\n upperOk = true;\n } else if (bounds.upperBound === excluded.upper) {\n // If excluded includes upper, and bounds includes or excludes upper, it's within\n // If excluded excludes upper, bounds must also exclude it to be within\n upperOk = excluded.upperInclusive || !bounds.upperInclusive;\n }\n\n return lowerOk && upperOk;\n}\n\n// ============================================================================\n// Attribute Conflict Detection\n// ============================================================================\n\n/**\n * Check for attribute value conflicts\n * e.g., [data-theme=\"dark\"] & [data-theme=\"light\"] → FALSE\n * e.g., [data-theme=\"dark\"] & ![data-theme] → FALSE\n */\n/**\n * Generic value-conflict checker for grouped conditions.\n *\n * Groups terms by a key, splits into positive/negated, then checks:\n * 1. Multiple distinct positive values → conflict\n * 2. Positive value + negated existence (value === undefined) → conflict\n * 3. Positive value + negated same value → conflict\n */\nfunction hasGroupedValueConflict<T extends { negated: boolean }>(\n terms: ConditionNode[],\n match: (term: ConditionNode) => T | null,\n groupKey: (term: T) => string,\n getValue: (term: T) => string | undefined,\n): boolean {\n const groups = new Map<string, { positive: T[]; negated: T[] }>();\n\n for (const term of terms) {\n const matched = match(term);\n if (!matched) continue;\n\n const key = groupKey(matched);\n let group = groups.get(key);\n if (!group) {\n group = { positive: [], negated: [] };\n groups.set(key, group);\n }\n\n if (matched.negated) {\n group.negated.push(matched);\n } else {\n group.positive.push(matched);\n }\n }\n\n for (const [, group] of groups) {\n const positiveValues = group.positive\n .map(getValue)\n .filter((v) => v !== undefined);\n if (new Set(positiveValues).size > 1) return true;\n\n const hasPositiveValue = positiveValues.length > 0;\n const hasNegatedExistence = group.negated.some(\n (t) => getValue(t) === undefined,\n );\n if (hasPositiveValue && hasNegatedExistence) return true;\n\n for (const pos of group.positive) {\n const posVal = getValue(pos);\n if (posVal !== undefined) {\n for (const neg of group.negated) {\n if (getValue(neg) === posVal) return true;\n }\n }\n }\n }\n\n return false;\n}\n\nfunction hasAttributeConflict(terms: ConditionNode[]): boolean {\n return hasGroupedValueConflict<ModifierCondition>(\n terms,\n (t) => (t.kind === 'state' && t.type === 'modifier' ? t : null),\n (t) => t.attribute,\n (t) => t.value,\n );\n}\n\nfunction hasContainerStyleConflict(terms: ConditionNode[]): boolean {\n return hasGroupedValueConflict<ContainerCondition>(\n terms,\n (t) =>\n t.kind === 'state' && t.type === 'container' && t.subtype === 'style'\n ? t\n : null,\n (t) => `${t.containerName || '_'}:${t.property}`,\n (t) => t.propertyValue,\n );\n}\n\n// ============================================================================\n// Implied Negation Removal\n// ============================================================================\n\n/**\n * Remove negations that are implied by positive terms.\n *\n * Key optimizations:\n * 1. style(--variant: danger) implies NOT style(--variant: success)\n * → If we have style(--variant: danger) & not style(--variant: success),\n * the negation is redundant and can be removed.\n *\n * 2. [data-theme=\"dark\"] implies NOT [data-theme=\"light\"]\n * → Same logic for attribute selectors.\n *\n * This produces cleaner CSS:\n * Before: @container style(--variant: danger) and (not style(--variant: success))\n * After: @container style(--variant: danger)\n */\n/**\n * Collect positive values from terms and build a \"is this negation implied?\" check.\n *\n * A negation is implied (redundant) when a positive term for the same group\n * already pins a specific value, making \"NOT other-value\" obvious.\n * e.g. style(--variant: danger) implies NOT style(--variant: success).\n */\nfunction buildImpliedNegationCheck(\n terms: ConditionNode[],\n): (term: ConditionNode) => boolean {\n const positiveValues = new Map<string, string>();\n\n for (const term of terms) {\n if (term.kind !== 'state' || term.negated) continue;\n\n if (term.type === 'container' && term.subtype === 'style') {\n if (term.propertyValue !== undefined) {\n positiveValues.set(\n `c:${term.containerName || '_'}:${term.property}`,\n term.propertyValue,\n );\n }\n } else if (term.type === 'modifier' && term.value !== undefined) {\n positiveValues.set(`m:${term.attribute}`, term.value);\n }\n }\n\n return (term: ConditionNode): boolean => {\n if (term.kind !== 'state' || !term.negated) return false;\n\n if (term.type === 'container' && term.subtype === 'style') {\n if (term.propertyValue === undefined) return false;\n const pos = positiveValues.get(\n `c:${term.containerName || '_'}:${term.property}`,\n );\n return pos !== undefined && term.propertyValue !== pos;\n }\n\n if (term.type === 'modifier' && term.value !== undefined) {\n const pos = positiveValues.get(`m:${term.attribute}`);\n return pos !== undefined && term.value !== pos;\n }\n\n return false;\n };\n}\n\nfunction removeImpliedNegations(terms: ConditionNode[]): ConditionNode[] {\n const isImplied = buildImpliedNegationCheck(terms);\n return terms.filter((t) => !isImplied(t));\n}\n\n// ============================================================================\n// Deduplication\n// ============================================================================\n\nfunction deduplicateTerms(terms: ConditionNode[]): ConditionNode[] {\n const seen = new Set<string>();\n const result: ConditionNode[] = [];\n\n for (const term of terms) {\n const id = getConditionUniqueId(term);\n if (!seen.has(id)) {\n seen.add(id);\n result.push(term);\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Range Merging\n// ============================================================================\n\n/**\n * Merge compatible range conditions\n * e.g., @media(w >= 400px) & @media(w <= 800px) → @media(400px <= w <= 800px)\n */\nfunction mergeRanges(terms: ConditionNode[]): ConditionNode[] {\n // Group media conditions by dimension\n const mediaByDim = new Map<\n string,\n { conditions: MediaCondition[]; indices: number[] }\n >();\n const containerByDim = new Map<\n string,\n { conditions: ContainerCondition[]; indices: number[] }\n >();\n\n terms.forEach((term, index) => {\n if (term.kind !== 'state') return;\n\n if (\n term.type === 'media' &&\n term.subtype === 'dimension' &&\n !term.negated\n ) {\n const key = term.dimension || 'width';\n if (!mediaByDim.has(key)) {\n mediaByDim.set(key, { conditions: [], indices: [] });\n }\n const group = mediaByDim.get(key)!;\n group.conditions.push(term);\n group.indices.push(index);\n }\n\n if (\n term.type === 'container' &&\n term.subtype === 'dimension' &&\n !term.negated\n ) {\n const key = `${term.containerName || '_'}:${term.dimension || 'width'}`;\n if (!containerByDim.has(key)) {\n containerByDim.set(key, { conditions: [], indices: [] });\n }\n const group = containerByDim.get(key)!;\n group.conditions.push(term);\n group.indices.push(index);\n }\n });\n\n // Track indices to remove\n const indicesToRemove = new Set<number>();\n const mergedTerms: ConditionNode[] = [];\n\n // Merge media conditions\n for (const [_dim, group] of mediaByDim) {\n if (group.conditions.length > 1) {\n const merged = mergeMediaRanges(group.conditions);\n if (merged) {\n group.indices.forEach((i) => indicesToRemove.add(i));\n mergedTerms.push(merged);\n }\n }\n }\n\n // Merge container conditions\n for (const [, group] of containerByDim) {\n if (group.conditions.length > 1) {\n const merged = mergeContainerRanges(group.conditions);\n if (merged) {\n group.indices.forEach((i) => indicesToRemove.add(i));\n mergedTerms.push(merged);\n }\n }\n }\n\n // Build result\n const result: ConditionNode[] = [];\n terms.forEach((term, index) => {\n if (!indicesToRemove.has(index)) {\n result.push(term);\n }\n });\n result.push(...mergedTerms);\n\n return result;\n}\n\n/**\n * Tighten bounds by picking the most restrictive lower and upper bounds\n * from a set of conditions that have lowerBound/upperBound fields.\n */\nfunction tightenBounds(\n conditions: { lowerBound?: NumericBound; upperBound?: NumericBound }[],\n): { lowerBound?: NumericBound; upperBound?: NumericBound } {\n let lowerBound: NumericBound | undefined;\n let upperBound: NumericBound | undefined;\n\n for (const cond of conditions) {\n if (cond.lowerBound) {\n if (\n !lowerBound ||\n (cond.lowerBound.valueNumeric ?? -Infinity) >\n (lowerBound.valueNumeric ?? -Infinity)\n ) {\n lowerBound = cond.lowerBound;\n }\n }\n if (cond.upperBound) {\n if (\n !upperBound ||\n (cond.upperBound.valueNumeric ?? Infinity) <\n (upperBound.valueNumeric ?? Infinity)\n ) {\n upperBound = cond.upperBound;\n }\n }\n }\n\n return { lowerBound, upperBound };\n}\n\nfunction appendBoundsToUniqueId(\n parts: string[],\n lowerBound?: NumericBound,\n upperBound?: NumericBound,\n): void {\n if (lowerBound) {\n parts.push(lowerBound.inclusive ? '>=' : '>');\n parts.push(lowerBound.value);\n }\n if (upperBound) {\n parts.push(upperBound.inclusive ? '<=' : '<');\n parts.push(upperBound.value);\n }\n}\n\nfunction mergeDimensionRanges<T extends MediaCondition | ContainerCondition>(\n conditions: T[],\n idPrefix: string[],\n): T | null {\n if (conditions.length === 0) return null;\n\n const { lowerBound, upperBound } = tightenBounds(conditions);\n const base = conditions[0];\n\n const parts = [...idPrefix];\n appendBoundsToUniqueId(parts, lowerBound, upperBound);\n\n return {\n ...base,\n negated: false,\n raw: buildMergedRaw(base.dimension || 'width', lowerBound, upperBound),\n uniqueId: parts.join(':'),\n lowerBound,\n upperBound,\n };\n}\n\nfunction mergeMediaRanges(conditions: MediaCondition[]): MediaCondition | null {\n const dim = conditions[0]?.dimension ?? 'width';\n return mergeDimensionRanges(conditions, ['media', 'dim', dim]);\n}\n\nfunction mergeContainerRanges(\n conditions: ContainerCondition[],\n): ContainerCondition | null {\n const base = conditions[0];\n if (!base) return null;\n const name = base.containerName || '_';\n const dim = base.dimension ?? 'width';\n return mergeDimensionRanges(conditions, ['container', 'dim', name, dim]);\n}\n\nfunction buildMergedRaw(\n dimension: string,\n lowerBound?: NumericBound,\n upperBound?: NumericBound,\n): string {\n if (lowerBound && upperBound) {\n const lowerOp = lowerBound.inclusive ? '<=' : '<';\n const upperOp = upperBound.inclusive ? '<=' : '<';\n return `@media(${lowerBound.value} ${lowerOp} ${dimension} ${upperOp} ${upperBound.value})`;\n } else if (upperBound) {\n const op = upperBound.inclusive ? '<=' : '<';\n return `@media(${dimension} ${op} ${upperBound.value})`;\n } else if (lowerBound) {\n const op = lowerBound.inclusive ? '>=' : '>';\n return `@media(${dimension} ${op} ${lowerBound.value})`;\n }\n return '@media()';\n}\n\n// ============================================================================\n// Sorting\n// ============================================================================\n\nfunction sortTerms(terms: ConditionNode[]): ConditionNode[] {\n const withIds = terms.map((t) => [getConditionUniqueId(t), t] as const);\n withIds.sort((a, b) => a[0].localeCompare(b[0]));\n return withIds.map(([, t]) => t);\n}\n\n// ============================================================================\n// Absorption\n// ============================================================================\n\n/**\n * Apply the absorption law: removes compound terms that are absorbed by\n * a simple term already present.\n *\n * For AND context: A & (A | B) → A (absorbs OR compounds)\n * For OR context: A | (A & B) → A (absorbs AND compounds)\n */\nfunction applyAbsorption(\n terms: ConditionNode[],\n absorbedOperator: 'OR' | 'AND',\n): ConditionNode[] {\n const simpleIds = new Set<string>();\n for (const term of terms) {\n if (term.kind !== 'compound') {\n simpleIds.add(getConditionUniqueId(term));\n }\n }\n\n return terms.filter((term) => {\n if (term.kind === 'compound' && term.operator === absorbedOperator) {\n for (const child of term.children) {\n if (simpleIds.has(getConditionUniqueId(child))) {\n return false;\n }\n }\n }\n return true;\n });\n}\n\nfunction applyAbsorptionAnd(terms: ConditionNode[]): ConditionNode[] {\n return applyAbsorption(terms, 'OR');\n}\n\nfunction applyAbsorptionOr(terms: ConditionNode[]): ConditionNode[] {\n return applyAbsorption(terms, 'AND');\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Lru } from "../parser/lru.js";
|
|
2
2
|
import { okhslToSrgb } from "../utils/color-math.js";
|
|
3
|
-
|
|
4
3
|
//#region src/plugins/okhsl-plugin.ts
|
|
5
4
|
/**
|
|
6
5
|
* OKHSL Plugin for Tasty
|
|
@@ -92,7 +91,7 @@ const okhslPlugin = () => ({
|
|
|
92
91
|
name: "okhsl",
|
|
93
92
|
funcs: { okhsl: okhslFunc }
|
|
94
93
|
});
|
|
95
|
-
|
|
96
94
|
//#endregion
|
|
97
95
|
export { okhslFunc, okhslPlugin };
|
|
96
|
+
|
|
98
97
|
//# sourceMappingURL=okhsl-plugin.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"okhsl-plugin.js","names":[],"sources":["../../src/plugins/okhsl-plugin.ts"],"sourcesContent":["/**\n * OKHSL Plugin for Tasty\n *\n * Converts OKHSL color syntax to RGB notation.\n * Supports angle units: deg, turn, rad, or unitless (degrees).\n *\n * Examples:\n * okhsl(240.5 50% 50%)\n * okhsl(240.5deg 50% 50%)\n * okhsl(0.25turn 50% 50%)\n * okhsl(1.57rad 50% 50%)\n */\n\nimport { Lru } from '../parser/lru';\nimport { okhslToSrgb } from '../utils/color-math';\n\nimport type { StyleDetails } from '../parser/types';\nimport type { TastyPlugin, TastyPluginFactory } from './types';\n\nconst conversionCache = new Lru<string, string>(500);\n\nconst clamp = (value: number, min: number, max: number): number =>\n Math.max(Math.min(value, max), min);\n\n/**\n * Parse an angle value with optional unit.\n * Supports: deg, turn, rad, or unitless (treated as degrees).\n */\nconst parseAngle = (value: string): number => {\n const match = value.match(/^([+-]?\\d*\\.?\\d+)(deg|turn|rad)?$/);\n if (!match) return 0;\n\n const num = parseFloat(match[1]);\n const unit = match[2];\n\n switch (unit) {\n case 'turn':\n return num * 360;\n case 'rad':\n return (num * 180) / Math.PI;\n case 'deg':\n default:\n return num;\n }\n};\n\n/**\n * Parse a percentage value (e.g., \"50%\") to a 0-1 range.\n */\nconst parsePercentage = (value: string): number => {\n const match = value.match(/^([+-]?\\d*\\.?\\d+)%?$/);\n if (!match) return 0;\n\n const num = parseFloat(match[1]);\n return value.includes('%') ? num / 100 : num;\n};\n\n/**\n * The okhsl function handler for tasty parser.\n * Receives parsed style groups and returns an RGB color string.\n */\nconst okhslFunc = (groups: StyleDetails[]): string => {\n if (groups.length === 0 || groups[0].all.length < 3) {\n console.warn('[okhsl] Expected 3 values (H S L), got:', groups);\n return 'rgb(0% 0% 0%)';\n }\n\n const group = groups[0];\n const tokens = group.all;\n\n const alpha =\n group.parts.length > 1 && group.parts[1].all.length > 0\n ? group.parts[1].output\n : undefined;\n\n const cacheKey = tokens.slice(0, 3).join(' ') + (alpha ? ` / ${alpha}` : '');\n const cached = conversionCache.get(cacheKey);\n if (cached) return cached;\n\n const h = parseAngle(tokens[0]);\n const s = parsePercentage(tokens[1]);\n const l = parsePercentage(tokens[2]);\n\n const [r, g, b] = okhslToSrgb(h, clamp(s, 0, 1), clamp(l, 0, 1));\n\n const format = (n: number): string => {\n const pct = n * 100;\n return parseFloat(pct.toFixed(1)).toString() + '%';\n };\n\n const result = alpha\n ? `rgb(${format(r)} ${format(g)} ${format(b)} / ${alpha})`\n : `rgb(${format(r)} ${format(g)} ${format(b)})`;\n\n conversionCache.set(cacheKey, result);\n return result;\n};\n\n/**\n * OKHSL Plugin for Tasty.\n *\n * Adds support for the `okhsl()` color function in tasty styles.\n *\n * @example\n * ```ts\n * import { configure } from '@tenphi/tasty';\n * import { okhslPlugin } from '@tenphi/tasty';\n *\n * configure({\n * plugins: [okhslPlugin()],\n * });\n *\n * // Now you can use okhsl in styles:\n * const Box = tasty({\n * styles: {\n * fill: 'okhsl(240 50% 50%)',\n * },\n * });\n * ```\n */\nexport const okhslPlugin: TastyPluginFactory = (): TastyPlugin => ({\n name: 'okhsl',\n funcs: {\n okhsl: okhslFunc,\n },\n});\n\nexport { okhslFunc };\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"okhsl-plugin.js","names":[],"sources":["../../src/plugins/okhsl-plugin.ts"],"sourcesContent":["/**\n * OKHSL Plugin for Tasty\n *\n * Converts OKHSL color syntax to RGB notation.\n * Supports angle units: deg, turn, rad, or unitless (degrees).\n *\n * Examples:\n * okhsl(240.5 50% 50%)\n * okhsl(240.5deg 50% 50%)\n * okhsl(0.25turn 50% 50%)\n * okhsl(1.57rad 50% 50%)\n */\n\nimport { Lru } from '../parser/lru';\nimport { okhslToSrgb } from '../utils/color-math';\n\nimport type { StyleDetails } from '../parser/types';\nimport type { TastyPlugin, TastyPluginFactory } from './types';\n\nconst conversionCache = new Lru<string, string>(500);\n\nconst clamp = (value: number, min: number, max: number): number =>\n Math.max(Math.min(value, max), min);\n\n/**\n * Parse an angle value with optional unit.\n * Supports: deg, turn, rad, or unitless (treated as degrees).\n */\nconst parseAngle = (value: string): number => {\n const match = value.match(/^([+-]?\\d*\\.?\\d+)(deg|turn|rad)?$/);\n if (!match) return 0;\n\n const num = parseFloat(match[1]);\n const unit = match[2];\n\n switch (unit) {\n case 'turn':\n return num * 360;\n case 'rad':\n return (num * 180) / Math.PI;\n case 'deg':\n default:\n return num;\n }\n};\n\n/**\n * Parse a percentage value (e.g., \"50%\") to a 0-1 range.\n */\nconst parsePercentage = (value: string): number => {\n const match = value.match(/^([+-]?\\d*\\.?\\d+)%?$/);\n if (!match) return 0;\n\n const num = parseFloat(match[1]);\n return value.includes('%') ? num / 100 : num;\n};\n\n/**\n * The okhsl function handler for tasty parser.\n * Receives parsed style groups and returns an RGB color string.\n */\nconst okhslFunc = (groups: StyleDetails[]): string => {\n if (groups.length === 0 || groups[0].all.length < 3) {\n console.warn('[okhsl] Expected 3 values (H S L), got:', groups);\n return 'rgb(0% 0% 0%)';\n }\n\n const group = groups[0];\n const tokens = group.all;\n\n const alpha =\n group.parts.length > 1 && group.parts[1].all.length > 0\n ? group.parts[1].output\n : undefined;\n\n const cacheKey = tokens.slice(0, 3).join(' ') + (alpha ? ` / ${alpha}` : '');\n const cached = conversionCache.get(cacheKey);\n if (cached) return cached;\n\n const h = parseAngle(tokens[0]);\n const s = parsePercentage(tokens[1]);\n const l = parsePercentage(tokens[2]);\n\n const [r, g, b] = okhslToSrgb(h, clamp(s, 0, 1), clamp(l, 0, 1));\n\n const format = (n: number): string => {\n const pct = n * 100;\n return parseFloat(pct.toFixed(1)).toString() + '%';\n };\n\n const result = alpha\n ? `rgb(${format(r)} ${format(g)} ${format(b)} / ${alpha})`\n : `rgb(${format(r)} ${format(g)} ${format(b)})`;\n\n conversionCache.set(cacheKey, result);\n return result;\n};\n\n/**\n * OKHSL Plugin for Tasty.\n *\n * Adds support for the `okhsl()` color function in tasty styles.\n *\n * @example\n * ```ts\n * import { configure } from '@tenphi/tasty';\n * import { okhslPlugin } from '@tenphi/tasty';\n *\n * configure({\n * plugins: [okhslPlugin()],\n * });\n *\n * // Now you can use okhsl in styles:\n * const Box = tasty({\n * styles: {\n * fill: 'okhsl(240 50% 50%)',\n * },\n * });\n * ```\n */\nexport const okhslPlugin: TastyPluginFactory = (): TastyPlugin => ({\n name: 'okhsl',\n funcs: {\n okhsl: okhslFunc,\n },\n});\n\nexport { okhslFunc };\n"],"mappings":";;;;;;;;;;;;;;;AAmBA,MAAM,kBAAkB,IAAI,IAAoB,IAAI;AAEpD,MAAM,SAAS,OAAe,KAAa,QACzC,KAAK,IAAI,KAAK,IAAI,OAAO,IAAI,EAAE,IAAI;;;;;AAMrC,MAAM,cAAc,UAA0B;CAC5C,MAAM,QAAQ,MAAM,MAAM,oCAAoC;AAC9D,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,MAAM,WAAW,MAAM,GAAG;AAGhC,SAFa,MAAM,IAEnB;EACE,KAAK,OACH,QAAO,MAAM;EACf,KAAK,MACH,QAAQ,MAAM,MAAO,KAAK;EAE5B,QACE,QAAO;;;;;;AAOb,MAAM,mBAAmB,UAA0B;CACjD,MAAM,QAAQ,MAAM,MAAM,uBAAuB;AACjD,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,MAAM,WAAW,MAAM,GAAG;AAChC,QAAO,MAAM,SAAS,IAAI,GAAG,MAAM,MAAM;;;;;;AAO3C,MAAM,aAAa,WAAmC;AACpD,KAAI,OAAO,WAAW,KAAK,OAAO,GAAG,IAAI,SAAS,GAAG;AACnD,UAAQ,KAAK,2CAA2C,OAAO;AAC/D,SAAO;;CAGT,MAAM,QAAQ,OAAO;CACrB,MAAM,SAAS,MAAM;CAErB,MAAM,QACJ,MAAM,MAAM,SAAS,KAAK,MAAM,MAAM,GAAG,IAAI,SAAS,IAClD,MAAM,MAAM,GAAG,SACf,KAAA;CAEN,MAAM,WAAW,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,IAAI,QAAQ,MAAM,UAAU;CACzE,MAAM,SAAS,gBAAgB,IAAI,SAAS;AAC5C,KAAI,OAAQ,QAAO;CAEnB,MAAM,IAAI,WAAW,OAAO,GAAG;CAC/B,MAAM,IAAI,gBAAgB,OAAO,GAAG;CACpC,MAAM,IAAI,gBAAgB,OAAO,GAAG;CAEpC,MAAM,CAAC,GAAG,GAAG,KAAK,YAAY,GAAG,MAAM,GAAG,GAAG,EAAE,EAAE,MAAM,GAAG,GAAG,EAAE,CAAC;CAEhE,MAAM,UAAU,MAAsB;EACpC,MAAM,MAAM,IAAI;AAChB,SAAO,WAAW,IAAI,QAAQ,EAAE,CAAC,CAAC,UAAU,GAAG;;CAGjD,MAAM,SAAS,QACX,OAAO,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,KAAK,MAAM,KACtD,OAAO,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC;AAE/C,iBAAgB,IAAI,UAAU,OAAO;AACrC,QAAO;;;;;;;;;;;;;;;;;;;;;;;;AAyBT,MAAa,qBAAsD;CACjE,MAAM;CACN,OAAO,EACL,OAAO,WACR;CACF"}
|
package/dist/properties/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { RE_NUMBER, RE_RAW_UNIT } from "../parser/const.js";
|
|
2
|
-
import
|
|
3
|
-
|
|
2
|
+
import "../utils/color-space.js";
|
|
4
3
|
//#region src/properties/index.ts
|
|
5
4
|
const PROPERTIES_KEY = "@properties";
|
|
6
5
|
/**
|
|
@@ -217,7 +216,7 @@ function inferSyntaxFromValue(value) {
|
|
|
217
216
|
}
|
|
218
217
|
return null;
|
|
219
218
|
}
|
|
220
|
-
|
|
221
219
|
//#endregion
|
|
222
220
|
export { extractLocalProperties, getEffectiveDefinition, hasLocalProperties, inferSyntaxFromValue, normalizePropertyDefinition, parsePropertyToken };
|
|
221
|
+
|
|
223
222
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/properties/index.ts"],"sourcesContent":["/**\n * Properties Utilities\n *\n * Utilities for extracting and processing CSS @property definitions in styles.\n * Unlike keyframes, properties are permanent once registered and don't need cleanup.\n *\n * Property names use tasty token syntax:\n * - `$name` for regular properties → `--name`\n * - `#name` for color properties → `--name-color` (auto-sets syntax: '<color>')\n */\n\nimport type { PropertyDefinition } from '../injector/types';\nimport { RE_NUMBER, RE_RAW_UNIT } from '../parser/const';\nimport type { Styles } from '../styles/types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst PROPERTIES_KEY = '@properties';\n\n/**\n * Valid CSS custom property name pattern (after the -- prefix).\n * Must start with a letter or underscore, followed by letters, digits, hyphens, or underscores.\n */\nconst VALID_PROPERTY_NAME_PATTERN = /^[a-z_][a-z0-9-_]*$/i;\n\n// ============================================================================\n// Validation Functions\n// ============================================================================\n\n/**\n * Validate a CSS custom property name (the part after --).\n * Returns true if the name is valid for use as a CSS custom property.\n */\nexport function isValidPropertyName(name: string): boolean {\n return VALID_PROPERTY_NAME_PATTERN.test(name);\n}\n\n/**\n * Result of parsing a property token.\n */\nexport interface ParsedPropertyToken {\n /** The CSS custom property name (e.g., '--my-prop') */\n cssName: string;\n /** Whether this is a color property */\n isColor: boolean;\n /** Whether the token was valid */\n isValid: boolean;\n /** Error message if invalid */\n error?: string;\n}\n\n// ============================================================================\n// Extraction Functions\n// ============================================================================\n\n/**\n * Check if styles object has local @properties definition.\n * Fast path: single property lookup.\n */\nexport function hasLocalProperties(styles: Styles): boolean {\n return PROPERTIES_KEY in styles;\n}\n\n/**\n * Extract local @properties from styles object.\n * Returns null if no local properties (fast path).\n */\nexport function extractLocalProperties(\n styles: Styles,\n): Record<string, PropertyDefinition> | null {\n const properties = styles[PROPERTIES_KEY];\n if (!properties || typeof properties !== 'object') {\n return null;\n }\n return properties as Record<string, PropertyDefinition>;\n}\n\n// ============================================================================\n// Token Parsing Functions\n// ============================================================================\n\n/**\n * Parse a property token name and return the CSS property name and whether it's a color.\n * Supports tasty token syntax and validates the property name.\n *\n * Token formats:\n * - `$name` → { cssName: '--name', isColor: false }\n * - `#name` → { cssName: '--name-color', isColor: true }\n * - `--name` → { cssName: '--name', isColor: false } (legacy, auto-detect color by suffix)\n * - `name` → { cssName: '--name', isColor: false } (legacy)\n *\n * @param token - The property token to parse\n * @returns Parsed result with cssName, isColor, isValid, and optional error\n */\nexport function parsePropertyToken(token: string): ParsedPropertyToken {\n if (!token || typeof token !== 'string') {\n return {\n cssName: '',\n isColor: false,\n isValid: false,\n error: 'Property token must be a non-empty string',\n };\n }\n\n let name: string;\n let isColor: boolean;\n\n if (token.startsWith('$')) {\n // Regular property token: $name → --name\n name = token.slice(1);\n isColor = false;\n } else if (token.startsWith('#')) {\n // Color property token: #name → --name-color\n name = token.slice(1);\n isColor = true;\n } else if (token.startsWith('--')) {\n // Legacy format with -- prefix\n name = token.slice(2);\n isColor = token.endsWith('-color');\n } else {\n // Legacy format without prefix\n name = token;\n isColor = token.endsWith('-color');\n }\n\n // Validate the name\n if (!name) {\n return {\n cssName: '',\n isColor,\n isValid: false,\n error: 'Property name cannot be empty',\n };\n }\n\n if (!isValidPropertyName(name)) {\n return {\n cssName: '',\n isColor,\n isValid: false,\n error: `Invalid property name \"${name}\". Must start with a letter or underscore, followed by letters, digits, hyphens, or underscores.`,\n };\n }\n\n // Build the CSS custom property name\n // For #name tokens, we add -color suffix\n // For legacy formats (--name-color or name-color), the name already includes -color\n let cssName: string;\n if (token.startsWith('#')) {\n // Color token: #name → --name-color\n cssName = `--${name}-color`;\n } else {\n // All other formats: just add -- prefix\n cssName = `--${name}`;\n }\n\n return {\n cssName,\n isColor,\n isValid: true,\n };\n}\n\n// ============================================================================\n// Normalization Functions\n// ============================================================================\n\n/**\n * Normalize a property name to the CSS custom property format.\n *\n * @deprecated Use parsePropertyToken instead for proper token handling\n */\nexport function normalizePropertyName(name: string): string {\n const result = parsePropertyToken(name);\n return result.isValid ? result.cssName : `--${name}`;\n}\n\n/**\n * Normalize a property definition to a consistent string representation.\n * Used for comparing definitions to detect type conflicts.\n *\n * Only `syntax` and `inherits` are compared — `initialValue` is intentionally\n * excluded because different components may set different defaults for the\n * same typed property (e.g. auto-inferred `0px` vs explicit `6px`).\n *\n * Keys are sorted alphabetically to ensure consistent comparison:\n * { inherits: true, syntax: '<color>' } === { syntax: '<color>', inherits: true }\n */\nexport function normalizePropertyDefinition(def: PropertyDefinition): string {\n const normalized: Record<string, unknown> = {};\n\n if (def.inherits !== undefined) {\n normalized.inherits = def.inherits;\n }\n if (def.syntax !== undefined) {\n normalized.syntax = def.syntax;\n }\n\n return JSON.stringify(normalized);\n}\n\n/**\n * Result of getEffectiveDefinition.\n */\nexport interface EffectiveDefinitionResult {\n /** The CSS custom property name */\n cssName: string;\n /** The effective property definition */\n definition: PropertyDefinition;\n /** Whether this is a color property */\n isColor: boolean;\n /** Whether the token was valid */\n isValid: boolean;\n /** Error message if invalid */\n error?: string;\n}\n\n/**\n * Get the effective property definition for a token.\n * For color tokens (#name), auto-sets syntax to '<color>' and defaults initialValue to 'transparent'.\n *\n * @param token - Property token ($name, #name, --name, or plain name)\n * @param userDefinition - User-provided definition options\n * @returns Effective definition with cssName, definition, isValid, and optional error\n */\nexport function getEffectiveDefinition(\n token: string,\n userDefinition: PropertyDefinition,\n): EffectiveDefinitionResult {\n const parsed = parsePropertyToken(token);\n\n if (!parsed.isValid) {\n return {\n cssName: '',\n definition: userDefinition,\n isColor: false,\n isValid: false,\n error: parsed.error,\n };\n }\n\n if (parsed.isColor) {\n // Color properties have fixed syntax and default initialValue\n return {\n cssName: parsed.cssName,\n definition: {\n syntax: '<color>', // Always '<color>' for color tokens, cannot be overridden\n inherits: userDefinition.inherits, // Allow inherits to be customized\n initialValue: userDefinition.initialValue ?? 'transparent', // Default to transparent\n },\n isColor: true,\n isValid: true,\n };\n }\n\n // Regular properties use the definition as-is\n return {\n cssName: parsed.cssName,\n definition: userDefinition,\n isColor: false,\n isValid: true,\n };\n}\n\n// ============================================================================\n// Color Utilities\n// ============================================================================\n\nexport { colorInitialValueToComponents } from '../utils/color-space';\n\n// ============================================================================\n// Value Type Inference\n// ============================================================================\n\n/**\n * Result of inferring a CSS @property syntax from a value.\n */\nexport interface InferredSyntax {\n syntax: string;\n initialValue: string;\n}\n\nconst UNIT_TO_SYNTAX: Record<string, InferredSyntax> = {};\n\nconst LENGTH_UNITS = [\n 'px',\n 'em',\n 'rem',\n 'vw',\n 'vh',\n 'vmin',\n 'vmax',\n 'ch',\n 'ex',\n 'cap',\n 'ic',\n 'lh',\n 'rlh',\n 'svw',\n 'svh',\n 'lvw',\n 'lvh',\n 'dvw',\n 'dvh',\n 'cqw',\n 'cqh',\n 'cqi',\n 'cqb',\n 'cqmin',\n 'cqmax',\n];\n\nconst ANGLE_UNITS = ['deg', 'rad', 'grad', 'turn'];\nconst TIME_UNITS = ['ms', 's'];\n\nfor (const u of LENGTH_UNITS) {\n UNIT_TO_SYNTAX[u] = { syntax: '<length-percentage>', initialValue: '0px' };\n}\nUNIT_TO_SYNTAX['%'] = { syntax: '<length-percentage>', initialValue: '0px' };\nfor (const u of ANGLE_UNITS) {\n UNIT_TO_SYNTAX[u] = { syntax: '<angle>', initialValue: '0deg' };\n}\nfor (const u of TIME_UNITS) {\n UNIT_TO_SYNTAX[u] = { syntax: '<time>', initialValue: '0s' };\n}\n\n/**\n * Infer CSS @property syntax from a concrete value.\n * Detects numeric types: \\<number\\>, \\<length-percentage\\>, \\<angle\\>, \\<time\\>.\n * Length and percentage values both map to \\<length-percentage\\> for maximum flexibility.\n * Color properties are handled separately via the #name token convention\n * (--name-color gets \\<color\\> syntax automatically in getEffectiveDefinition).\n *\n * @param value - The CSS value to infer from (e.g. '10px', '1', '45deg')\n * @returns Inferred syntax and initial value, or null if not inferable\n */\nexport function inferSyntaxFromValue(value: string): InferredSyntax | null {\n if (!value || typeof value !== 'string') return null;\n\n const trimmed = value.trim();\n if (!trimmed) return null;\n\n if (RE_NUMBER.test(trimmed)) {\n // Bare zero is ambiguous (could be <length>, <angle>, <percentage>, etc.)\n if (parseFloat(trimmed) === 0) return null;\n return { syntax: '<number>', initialValue: '0' };\n }\n\n const unitMatch = trimmed.match(RE_RAW_UNIT);\n if (unitMatch) {\n const unit = unitMatch[2];\n const mapping = UNIT_TO_SYNTAX[unit];\n if (mapping) return mapping;\n }\n\n return null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/properties/index.ts"],"sourcesContent":["/**\n * Properties Utilities\n *\n * Utilities for extracting and processing CSS @property definitions in styles.\n * Unlike keyframes, properties are permanent once registered and don't need cleanup.\n *\n * Property names use tasty token syntax:\n * - `$name` for regular properties → `--name`\n * - `#name` for color properties → `--name-color` (auto-sets syntax: '<color>')\n */\n\nimport type { PropertyDefinition } from '../injector/types';\nimport { RE_NUMBER, RE_RAW_UNIT } from '../parser/const';\nimport type { Styles } from '../styles/types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst PROPERTIES_KEY = '@properties';\n\n/**\n * Valid CSS custom property name pattern (after the -- prefix).\n * Must start with a letter or underscore, followed by letters, digits, hyphens, or underscores.\n */\nconst VALID_PROPERTY_NAME_PATTERN = /^[a-z_][a-z0-9-_]*$/i;\n\n// ============================================================================\n// Validation Functions\n// ============================================================================\n\n/**\n * Validate a CSS custom property name (the part after --).\n * Returns true if the name is valid for use as a CSS custom property.\n */\nexport function isValidPropertyName(name: string): boolean {\n return VALID_PROPERTY_NAME_PATTERN.test(name);\n}\n\n/**\n * Result of parsing a property token.\n */\nexport interface ParsedPropertyToken {\n /** The CSS custom property name (e.g., '--my-prop') */\n cssName: string;\n /** Whether this is a color property */\n isColor: boolean;\n /** Whether the token was valid */\n isValid: boolean;\n /** Error message if invalid */\n error?: string;\n}\n\n// ============================================================================\n// Extraction Functions\n// ============================================================================\n\n/**\n * Check if styles object has local @properties definition.\n * Fast path: single property lookup.\n */\nexport function hasLocalProperties(styles: Styles): boolean {\n return PROPERTIES_KEY in styles;\n}\n\n/**\n * Extract local @properties from styles object.\n * Returns null if no local properties (fast path).\n */\nexport function extractLocalProperties(\n styles: Styles,\n): Record<string, PropertyDefinition> | null {\n const properties = styles[PROPERTIES_KEY];\n if (!properties || typeof properties !== 'object') {\n return null;\n }\n return properties as Record<string, PropertyDefinition>;\n}\n\n// ============================================================================\n// Token Parsing Functions\n// ============================================================================\n\n/**\n * Parse a property token name and return the CSS property name and whether it's a color.\n * Supports tasty token syntax and validates the property name.\n *\n * Token formats:\n * - `$name` → { cssName: '--name', isColor: false }\n * - `#name` → { cssName: '--name-color', isColor: true }\n * - `--name` → { cssName: '--name', isColor: false } (legacy, auto-detect color by suffix)\n * - `name` → { cssName: '--name', isColor: false } (legacy)\n *\n * @param token - The property token to parse\n * @returns Parsed result with cssName, isColor, isValid, and optional error\n */\nexport function parsePropertyToken(token: string): ParsedPropertyToken {\n if (!token || typeof token !== 'string') {\n return {\n cssName: '',\n isColor: false,\n isValid: false,\n error: 'Property token must be a non-empty string',\n };\n }\n\n let name: string;\n let isColor: boolean;\n\n if (token.startsWith('$')) {\n // Regular property token: $name → --name\n name = token.slice(1);\n isColor = false;\n } else if (token.startsWith('#')) {\n // Color property token: #name → --name-color\n name = token.slice(1);\n isColor = true;\n } else if (token.startsWith('--')) {\n // Legacy format with -- prefix\n name = token.slice(2);\n isColor = token.endsWith('-color');\n } else {\n // Legacy format without prefix\n name = token;\n isColor = token.endsWith('-color');\n }\n\n // Validate the name\n if (!name) {\n return {\n cssName: '',\n isColor,\n isValid: false,\n error: 'Property name cannot be empty',\n };\n }\n\n if (!isValidPropertyName(name)) {\n return {\n cssName: '',\n isColor,\n isValid: false,\n error: `Invalid property name \"${name}\". Must start with a letter or underscore, followed by letters, digits, hyphens, or underscores.`,\n };\n }\n\n // Build the CSS custom property name\n // For #name tokens, we add -color suffix\n // For legacy formats (--name-color or name-color), the name already includes -color\n let cssName: string;\n if (token.startsWith('#')) {\n // Color token: #name → --name-color\n cssName = `--${name}-color`;\n } else {\n // All other formats: just add -- prefix\n cssName = `--${name}`;\n }\n\n return {\n cssName,\n isColor,\n isValid: true,\n };\n}\n\n// ============================================================================\n// Normalization Functions\n// ============================================================================\n\n/**\n * Normalize a property name to the CSS custom property format.\n *\n * @deprecated Use parsePropertyToken instead for proper token handling\n */\nexport function normalizePropertyName(name: string): string {\n const result = parsePropertyToken(name);\n return result.isValid ? result.cssName : `--${name}`;\n}\n\n/**\n * Normalize a property definition to a consistent string representation.\n * Used for comparing definitions to detect type conflicts.\n *\n * Only `syntax` and `inherits` are compared — `initialValue` is intentionally\n * excluded because different components may set different defaults for the\n * same typed property (e.g. auto-inferred `0px` vs explicit `6px`).\n *\n * Keys are sorted alphabetically to ensure consistent comparison:\n * { inherits: true, syntax: '<color>' } === { syntax: '<color>', inherits: true }\n */\nexport function normalizePropertyDefinition(def: PropertyDefinition): string {\n const normalized: Record<string, unknown> = {};\n\n if (def.inherits !== undefined) {\n normalized.inherits = def.inherits;\n }\n if (def.syntax !== undefined) {\n normalized.syntax = def.syntax;\n }\n\n return JSON.stringify(normalized);\n}\n\n/**\n * Result of getEffectiveDefinition.\n */\nexport interface EffectiveDefinitionResult {\n /** The CSS custom property name */\n cssName: string;\n /** The effective property definition */\n definition: PropertyDefinition;\n /** Whether this is a color property */\n isColor: boolean;\n /** Whether the token was valid */\n isValid: boolean;\n /** Error message if invalid */\n error?: string;\n}\n\n/**\n * Get the effective property definition for a token.\n * For color tokens (#name), auto-sets syntax to '<color>' and defaults initialValue to 'transparent'.\n *\n * @param token - Property token ($name, #name, --name, or plain name)\n * @param userDefinition - User-provided definition options\n * @returns Effective definition with cssName, definition, isValid, and optional error\n */\nexport function getEffectiveDefinition(\n token: string,\n userDefinition: PropertyDefinition,\n): EffectiveDefinitionResult {\n const parsed = parsePropertyToken(token);\n\n if (!parsed.isValid) {\n return {\n cssName: '',\n definition: userDefinition,\n isColor: false,\n isValid: false,\n error: parsed.error,\n };\n }\n\n if (parsed.isColor) {\n // Color properties have fixed syntax and default initialValue\n return {\n cssName: parsed.cssName,\n definition: {\n syntax: '<color>', // Always '<color>' for color tokens, cannot be overridden\n inherits: userDefinition.inherits, // Allow inherits to be customized\n initialValue: userDefinition.initialValue ?? 'transparent', // Default to transparent\n },\n isColor: true,\n isValid: true,\n };\n }\n\n // Regular properties use the definition as-is\n return {\n cssName: parsed.cssName,\n definition: userDefinition,\n isColor: false,\n isValid: true,\n };\n}\n\n// ============================================================================\n// Color Utilities\n// ============================================================================\n\nexport { colorInitialValueToComponents } from '../utils/color-space';\n\n// ============================================================================\n// Value Type Inference\n// ============================================================================\n\n/**\n * Result of inferring a CSS @property syntax from a value.\n */\nexport interface InferredSyntax {\n syntax: string;\n initialValue: string;\n}\n\nconst UNIT_TO_SYNTAX: Record<string, InferredSyntax> = {};\n\nconst LENGTH_UNITS = [\n 'px',\n 'em',\n 'rem',\n 'vw',\n 'vh',\n 'vmin',\n 'vmax',\n 'ch',\n 'ex',\n 'cap',\n 'ic',\n 'lh',\n 'rlh',\n 'svw',\n 'svh',\n 'lvw',\n 'lvh',\n 'dvw',\n 'dvh',\n 'cqw',\n 'cqh',\n 'cqi',\n 'cqb',\n 'cqmin',\n 'cqmax',\n];\n\nconst ANGLE_UNITS = ['deg', 'rad', 'grad', 'turn'];\nconst TIME_UNITS = ['ms', 's'];\n\nfor (const u of LENGTH_UNITS) {\n UNIT_TO_SYNTAX[u] = { syntax: '<length-percentage>', initialValue: '0px' };\n}\nUNIT_TO_SYNTAX['%'] = { syntax: '<length-percentage>', initialValue: '0px' };\nfor (const u of ANGLE_UNITS) {\n UNIT_TO_SYNTAX[u] = { syntax: '<angle>', initialValue: '0deg' };\n}\nfor (const u of TIME_UNITS) {\n UNIT_TO_SYNTAX[u] = { syntax: '<time>', initialValue: '0s' };\n}\n\n/**\n * Infer CSS @property syntax from a concrete value.\n * Detects numeric types: \\<number\\>, \\<length-percentage\\>, \\<angle\\>, \\<time\\>.\n * Length and percentage values both map to \\<length-percentage\\> for maximum flexibility.\n * Color properties are handled separately via the #name token convention\n * (--name-color gets \\<color\\> syntax automatically in getEffectiveDefinition).\n *\n * @param value - The CSS value to infer from (e.g. '10px', '1', '45deg')\n * @returns Inferred syntax and initial value, or null if not inferable\n */\nexport function inferSyntaxFromValue(value: string): InferredSyntax | null {\n if (!value || typeof value !== 'string') return null;\n\n const trimmed = value.trim();\n if (!trimmed) return null;\n\n if (RE_NUMBER.test(trimmed)) {\n // Bare zero is ambiguous (could be <length>, <angle>, <percentage>, etc.)\n if (parseFloat(trimmed) === 0) return null;\n return { syntax: '<number>', initialValue: '0' };\n }\n\n const unitMatch = trimmed.match(RE_RAW_UNIT);\n if (unitMatch) {\n const unit = unitMatch[2];\n const mapping = UNIT_TO_SYNTAX[unit];\n if (mapping) return mapping;\n }\n\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { inferSyntaxFromValue } from "./index.js";
|
|
2
|
-
|
|
3
2
|
//#region src/properties/property-type-resolver.ts
|
|
4
3
|
/**
|
|
5
4
|
* PropertyTypeResolver
|
|
@@ -85,7 +84,7 @@ var PropertyTypeResolver = class {
|
|
|
85
84
|
return !SINGLE_VAR_REF.test(value);
|
|
86
85
|
}
|
|
87
86
|
};
|
|
88
|
-
|
|
89
87
|
//#endregion
|
|
90
88
|
export { PropertyTypeResolver };
|
|
89
|
+
|
|
91
90
|
//# sourceMappingURL=property-type-resolver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"property-type-resolver.js","names":[],"sources":["../../src/properties/property-type-resolver.ts"],"sourcesContent":["/**\n * PropertyTypeResolver\n *\n * Automatically infers CSS @property types from custom property values.\n * Supports deferred resolution for var() reference chains of arbitrary depth.\n */\n\nimport { inferSyntaxFromValue } from './index';\n\nconst CUSTOM_PROP_DECL = /^\\s*(--[a-z0-9_-]+)\\s*:\\s*(.+?)\\s*$/i;\nconst SINGLE_VAR_REF = /^var\\((--[a-z0-9_-]+)\\)$/i;\n\nexport class PropertyTypeResolver {\n /** propName → the prop it depends on */\n private pendingDeps = new Map<string, string>();\n /** propName → list of props waiting on it */\n private reverseDeps = new Map<string, string[]>();\n\n /**\n * Scan CSS declarations and auto-register @property for custom properties\n * whose types can be inferred from their values.\n */\n scanDeclarations(\n declarations: string,\n isPropertyDefined: (name: string) => boolean,\n registerProperty: (\n name: string,\n syntax: string,\n initialValue: string,\n ) => void,\n ): void {\n if (!declarations.includes('--')) return;\n\n const parts = declarations.split(/;+/);\n\n for (const part of parts) {\n if (!part.trim()) continue;\n\n const match = CUSTOM_PROP_DECL.exec(part);\n if (!match) continue;\n\n const propName = match[1];\n const value = match[2].trim();\n\n if (isPropertyDefined(propName)) continue;\n\n // Name-based: --*-color properties are always <color> (from #name tokens)\n if (propName.endsWith('-color')) {\n registerProperty(propName, '<color>', 'transparent');\n continue;\n }\n\n // Name-based: --*-line-height accepts numbers, lengths, and percentages\n if (propName.endsWith('-line-height')) {\n registerProperty(propName, '<number> | <length-percentage>', '0');\n continue;\n }\n\n // Name-based: --*-opacity accepts numbers and percentages\n if (propName.endsWith('-opacity')) {\n registerProperty(propName, '<number> | <percentage>', '0');\n continue;\n }\n\n // Single var() reference → record dependency for deferred resolution\n const varMatch = SINGLE_VAR_REF.exec(value);\n if (varMatch) {\n const depName = varMatch[1];\n this.addDependency(propName, depName);\n continue;\n }\n\n // Skip complex expressions (calc, multiple var, etc.)\n if (this.isComplexValue(value)) continue;\n\n const inferred = inferSyntaxFromValue(value);\n if (!inferred) continue;\n\n this.resolve(\n propName,\n inferred.syntax,\n inferred.initialValue,\n isPropertyDefined,\n registerProperty,\n );\n }\n }\n\n private addDependency(propName: string, depName: string): void {\n if (propName === depName) return;\n\n this.pendingDeps.set(propName, depName);\n\n let dependents = this.reverseDeps.get(depName);\n if (!dependents) {\n dependents = [];\n this.reverseDeps.set(depName, dependents);\n }\n if (!dependents.includes(propName)) {\n dependents.push(propName);\n }\n }\n\n private resolve(\n propName: string,\n syntax: string,\n initialValue: string,\n isPropertyDefined: (name: string) => boolean,\n registerProperty: (\n name: string,\n syntax: string,\n initialValue: string,\n ) => void,\n resolving?: Set<string>,\n ): void {\n if (!resolving) resolving = new Set();\n if (resolving.has(propName)) return;\n resolving.add(propName);\n\n if (!isPropertyDefined(propName)) {\n registerProperty(propName, syntax, initialValue);\n }\n\n const dependents = this.reverseDeps.get(propName);\n if (dependents) {\n this.reverseDeps.delete(propName);\n\n for (const dependent of dependents) {\n this.pendingDeps.delete(dependent);\n\n if (isPropertyDefined(dependent)) continue;\n\n this.resolve(\n dependent,\n syntax,\n initialValue,\n isPropertyDefined,\n registerProperty,\n resolving,\n );\n }\n }\n }\n\n private isComplexValue(value: string): boolean {\n if (value.includes('calc(')) return true;\n const firstVar = value.indexOf('var(');\n if (firstVar === -1) return false;\n if (value.indexOf('var(', firstVar + 4) !== -1) return true;\n return !SINGLE_VAR_REF.test(value);\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"property-type-resolver.js","names":[],"sources":["../../src/properties/property-type-resolver.ts"],"sourcesContent":["/**\n * PropertyTypeResolver\n *\n * Automatically infers CSS @property types from custom property values.\n * Supports deferred resolution for var() reference chains of arbitrary depth.\n */\n\nimport { inferSyntaxFromValue } from './index';\n\nconst CUSTOM_PROP_DECL = /^\\s*(--[a-z0-9_-]+)\\s*:\\s*(.+?)\\s*$/i;\nconst SINGLE_VAR_REF = /^var\\((--[a-z0-9_-]+)\\)$/i;\n\nexport class PropertyTypeResolver {\n /** propName → the prop it depends on */\n private pendingDeps = new Map<string, string>();\n /** propName → list of props waiting on it */\n private reverseDeps = new Map<string, string[]>();\n\n /**\n * Scan CSS declarations and auto-register @property for custom properties\n * whose types can be inferred from their values.\n */\n scanDeclarations(\n declarations: string,\n isPropertyDefined: (name: string) => boolean,\n registerProperty: (\n name: string,\n syntax: string,\n initialValue: string,\n ) => void,\n ): void {\n if (!declarations.includes('--')) return;\n\n const parts = declarations.split(/;+/);\n\n for (const part of parts) {\n if (!part.trim()) continue;\n\n const match = CUSTOM_PROP_DECL.exec(part);\n if (!match) continue;\n\n const propName = match[1];\n const value = match[2].trim();\n\n if (isPropertyDefined(propName)) continue;\n\n // Name-based: --*-color properties are always <color> (from #name tokens)\n if (propName.endsWith('-color')) {\n registerProperty(propName, '<color>', 'transparent');\n continue;\n }\n\n // Name-based: --*-line-height accepts numbers, lengths, and percentages\n if (propName.endsWith('-line-height')) {\n registerProperty(propName, '<number> | <length-percentage>', '0');\n continue;\n }\n\n // Name-based: --*-opacity accepts numbers and percentages\n if (propName.endsWith('-opacity')) {\n registerProperty(propName, '<number> | <percentage>', '0');\n continue;\n }\n\n // Single var() reference → record dependency for deferred resolution\n const varMatch = SINGLE_VAR_REF.exec(value);\n if (varMatch) {\n const depName = varMatch[1];\n this.addDependency(propName, depName);\n continue;\n }\n\n // Skip complex expressions (calc, multiple var, etc.)\n if (this.isComplexValue(value)) continue;\n\n const inferred = inferSyntaxFromValue(value);\n if (!inferred) continue;\n\n this.resolve(\n propName,\n inferred.syntax,\n inferred.initialValue,\n isPropertyDefined,\n registerProperty,\n );\n }\n }\n\n private addDependency(propName: string, depName: string): void {\n if (propName === depName) return;\n\n this.pendingDeps.set(propName, depName);\n\n let dependents = this.reverseDeps.get(depName);\n if (!dependents) {\n dependents = [];\n this.reverseDeps.set(depName, dependents);\n }\n if (!dependents.includes(propName)) {\n dependents.push(propName);\n }\n }\n\n private resolve(\n propName: string,\n syntax: string,\n initialValue: string,\n isPropertyDefined: (name: string) => boolean,\n registerProperty: (\n name: string,\n syntax: string,\n initialValue: string,\n ) => void,\n resolving?: Set<string>,\n ): void {\n if (!resolving) resolving = new Set();\n if (resolving.has(propName)) return;\n resolving.add(propName);\n\n if (!isPropertyDefined(propName)) {\n registerProperty(propName, syntax, initialValue);\n }\n\n const dependents = this.reverseDeps.get(propName);\n if (dependents) {\n this.reverseDeps.delete(propName);\n\n for (const dependent of dependents) {\n this.pendingDeps.delete(dependent);\n\n if (isPropertyDefined(dependent)) continue;\n\n this.resolve(\n dependent,\n syntax,\n initialValue,\n isPropertyDefined,\n registerProperty,\n resolving,\n );\n }\n }\n }\n\n private isComplexValue(value: string): boolean {\n if (value.includes('calc(')) return true;\n const firstVar = value.indexOf('var(');\n if (firstVar === -1) return false;\n if (value.indexOf('var(', firstVar + 4) !== -1) return true;\n return !SINGLE_VAR_REF.test(value);\n }\n}\n"],"mappings":";;;;;;;;AASA,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;AAEvB,IAAa,uBAAb,MAAkC;;CAEhC,8BAAsB,IAAI,KAAqB;;CAE/C,8BAAsB,IAAI,KAAuB;;;;;CAMjD,iBACE,cACA,mBACA,kBAKM;AACN,MAAI,CAAC,aAAa,SAAS,KAAK,CAAE;EAElC,MAAM,QAAQ,aAAa,MAAM,KAAK;AAEtC,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,CAAC,KAAK,MAAM,CAAE;GAElB,MAAM,QAAQ,iBAAiB,KAAK,KAAK;AACzC,OAAI,CAAC,MAAO;GAEZ,MAAM,WAAW,MAAM;GACvB,MAAM,QAAQ,MAAM,GAAG,MAAM;AAE7B,OAAI,kBAAkB,SAAS,CAAE;AAGjC,OAAI,SAAS,SAAS,SAAS,EAAE;AAC/B,qBAAiB,UAAU,WAAW,cAAc;AACpD;;AAIF,OAAI,SAAS,SAAS,eAAe,EAAE;AACrC,qBAAiB,UAAU,kCAAkC,IAAI;AACjE;;AAIF,OAAI,SAAS,SAAS,WAAW,EAAE;AACjC,qBAAiB,UAAU,2BAA2B,IAAI;AAC1D;;GAIF,MAAM,WAAW,eAAe,KAAK,MAAM;AAC3C,OAAI,UAAU;IACZ,MAAM,UAAU,SAAS;AACzB,SAAK,cAAc,UAAU,QAAQ;AACrC;;AAIF,OAAI,KAAK,eAAe,MAAM,CAAE;GAEhC,MAAM,WAAW,qBAAqB,MAAM;AAC5C,OAAI,CAAC,SAAU;AAEf,QAAK,QACH,UACA,SAAS,QACT,SAAS,cACT,mBACA,iBACD;;;CAIL,cAAsB,UAAkB,SAAuB;AAC7D,MAAI,aAAa,QAAS;AAE1B,OAAK,YAAY,IAAI,UAAU,QAAQ;EAEvC,IAAI,aAAa,KAAK,YAAY,IAAI,QAAQ;AAC9C,MAAI,CAAC,YAAY;AACf,gBAAa,EAAE;AACf,QAAK,YAAY,IAAI,SAAS,WAAW;;AAE3C,MAAI,CAAC,WAAW,SAAS,SAAS,CAChC,YAAW,KAAK,SAAS;;CAI7B,QACE,UACA,QACA,cACA,mBACA,kBAKA,WACM;AACN,MAAI,CAAC,UAAW,6BAAY,IAAI,KAAK;AACrC,MAAI,UAAU,IAAI,SAAS,CAAE;AAC7B,YAAU,IAAI,SAAS;AAEvB,MAAI,CAAC,kBAAkB,SAAS,CAC9B,kBAAiB,UAAU,QAAQ,aAAa;EAGlD,MAAM,aAAa,KAAK,YAAY,IAAI,SAAS;AACjD,MAAI,YAAY;AACd,QAAK,YAAY,OAAO,SAAS;AAEjC,QAAK,MAAM,aAAa,YAAY;AAClC,SAAK,YAAY,OAAO,UAAU;AAElC,QAAI,kBAAkB,UAAU,CAAE;AAElC,SAAK,QACH,WACA,QACA,cACA,mBACA,kBACA,UACD;;;;CAKP,eAAuB,OAAwB;AAC7C,MAAI,MAAM,SAAS,QAAQ,CAAE,QAAO;EACpC,MAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,MAAI,aAAa,GAAI,QAAO;AAC5B,MAAI,MAAM,QAAQ,QAAQ,WAAW,EAAE,KAAK,GAAI,QAAO;AACvD,SAAO,CAAC,eAAe,KAAK,MAAM"}
|
package/dist/ssr/astro.js
CHANGED
|
@@ -3,7 +3,6 @@ import { registerSSRCollectorGetter } from "./ssr-collector-ref.js";
|
|
|
3
3
|
import { ServerStyleCollector } from "./collector.js";
|
|
4
4
|
import { getSSRCollector, runWithCollector } from "./async-storage.js";
|
|
5
5
|
import { hydrateTastyCache } from "./hydrate.js";
|
|
6
|
-
|
|
7
6
|
//#region src/ssr/astro.ts
|
|
8
7
|
/**
|
|
9
8
|
* Astro integration for Tasty SSR.
|
|
@@ -59,7 +58,7 @@ if (typeof window !== "undefined") {
|
|
|
59
58
|
hydrateTastyCache(JSON.parse(script.textContent));
|
|
60
59
|
} catch {}
|
|
61
60
|
}
|
|
62
|
-
|
|
63
61
|
//#endregion
|
|
64
62
|
export { hydrateTastyCache, tastyMiddleware };
|
|
63
|
+
|
|
65
64
|
//# sourceMappingURL=astro.js.map
|