@tenphi/tasty 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/debug.js +1 -1
- package/dist/debug.js.map +1 -1
- package/dist/pipeline/conditions.js +14 -8
- package/dist/pipeline/conditions.js.map +1 -1
- package/dist/pipeline/index.js +59 -40
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/materialize.js +34 -98
- package/dist/pipeline/materialize.js.map +1 -1
- package/dist/pipeline/parseStateKey.js +17 -9
- package/dist/pipeline/parseStateKey.js.map +1 -1
- package/dist/pipeline/simplify.js +111 -152
- package/dist/pipeline/simplify.js.map +1 -1
- package/dist/pipeline/warnings.js +18 -0
- package/dist/pipeline/warnings.js.map +1 -0
- package/dist/styles/align.d.ts +1 -1
- package/dist/styles/align.js.map +1 -1
- package/dist/styles/border.d.ts +1 -1
- package/dist/styles/border.js.map +1 -1
- package/dist/styles/color.d.ts +2 -2
- package/dist/styles/color.js.map +1 -1
- package/dist/styles/createStyle.js +1 -1
- package/dist/styles/createStyle.js.map +1 -1
- package/dist/styles/fade.d.ts +1 -1
- package/dist/styles/fade.js.map +1 -1
- package/dist/styles/fill.d.ts +14 -16
- package/dist/styles/fill.js.map +1 -1
- package/dist/styles/flow.d.ts +3 -3
- package/dist/styles/flow.js.map +1 -1
- package/dist/styles/justify.d.ts +1 -1
- package/dist/styles/justify.js.map +1 -1
- package/dist/styles/predefined.js.map +1 -1
- package/dist/styles/radius.d.ts +1 -1
- package/dist/styles/radius.js.map +1 -1
- package/dist/styles/scrollbar.d.ts +1 -1
- package/dist/styles/scrollbar.js +19 -12
- package/dist/styles/scrollbar.js.map +1 -1
- package/dist/styles/shadow.d.ts +2 -2
- package/dist/styles/shadow.js.map +1 -1
- package/dist/styles/styledScrollbar.d.ts +1 -1
- package/dist/styles/styledScrollbar.js.map +1 -1
- package/dist/styles/transition.d.ts +1 -1
- package/dist/styles/transition.js.map +1 -1
- package/dist/utils/colors.d.ts +1 -1
- package/dist/utils/colors.js.map +1 -1
- package/dist/utils/dotize.d.ts +1 -1
- package/dist/utils/mod-attrs.js.map +1 -1
- package/dist/utils/string.js.map +1 -1
- package/dist/utils/styles.d.ts +7 -12
- package/dist/utils/styles.js +13 -8
- package/dist/utils/styles.js.map +1 -1
- package/package.json +47 -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';\n\nimport type { ConditionNode, NumericBound } from './conditions';\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// 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 and @(...) container queries we need to handle nested parentheses\n * like @supports($, :has(*)) or @(scroll-state(stuck: top)).\n * We use a pattern that allows one level of nesting: [^()]*(?:\\([^)]*\\))?[^)]*\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-]+)|(:[-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\n while (this.current()?.type === 'XOR') {\n this.advance();\n const right = this.parseUnary();\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 // Pseudo-class (e.g., :hover, :focus-visible, :nth-child(2n))\n if (value.startsWith(':')) {\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 parseRootState(raw: string): ConditionNode {\n const content = raw.slice(6, -1); // Remove '@root(' and ')'\n if (!content.trim()) {\n return trueCondition();\n }\n\n const innerCondition = parseStateKey(content, this.options);\n return createRootCondition(innerCondition, false, raw);\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); // Remove '@parent(' and ')'\n if (!content.trim()) {\n return trueCondition();\n }\n\n let condition = content.trim();\n let direct = false;\n\n // Detect \", >\" suffix for direct parent mode\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 /**\n * Parse @own(...) state\n */\n private parseOwnState(raw: string): ConditionNode {\n const content = raw.slice(5, -1); // Remove '@own(' and ')'\n if (!content.trim()) {\n return trueCondition();\n }\n\n // Parse the inner condition recursively\n const innerCondition = parseStateKey(content, this.options);\n return createOwnCondition(innerCondition, false, raw);\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 = JSON.stringify([\n trimmed,\n options.isSubElement,\n localStatesKey,\n ]);\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":";;;;;;;;;;;;AAoDA,MAAM,aAAa,IAAI,IAA2B,IAAK;;;;;;;;;;AAevD,MAAM,sBACJ;;;;AAqBF,SAAS,SAAS,UAA2B;CAC3C,MAAM,SAAkB,EAAE;CAC1B,IAAI;CAGJ,MAAM,aAAa,2BAA2B,SAAS;AAEvD,qBAAoB,YAAY;AAChC,SAAQ,QAAQ,oBAAoB,KAAK,WAAW,MAAM,MAAM;EAC9D,MAAM,YAAY,MAAM;AAExB,MAAI,MAAM,GAER,SAAQ,WAAR;GACE,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAO,OAAO;KAAK,KAAK;KAAW,CAAC;AACxD;GACF,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAM,OAAO;KAAK,KAAK;KAAW,CAAC;AACvD;GACF,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAO,OAAO;KAAK,KAAK;KAAW,CAAC;AACxD;GACF,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAO,OAAO;KAAK,KAAK;KAAW,CAAC;AACxD;;WAEK,MAAM,GAEf,KAAI,cAAc,IAChB,QAAO,KAAK;GAAE,MAAM;GAAU,OAAO;GAAK,KAAK;GAAW,CAAC;MAE3D,QAAO,KAAK;GAAE,MAAM;GAAU,OAAO;GAAK,KAAK;GAAW,CAAC;MAI7D,QAAO,KAAK;GAAE,MAAM;GAAS,OAAO;GAAW,KAAK;GAAW,CAAC;;AAIpE,QAAO;;;;;AAMT,SAAS,2BAA2B,KAAqB;CACvD,IAAI,SAAS;CACb,IAAI,QAAQ;AAEZ,MAAK,MAAM,QAAQ,IACjB,KAAI,SAAS,KAAK;AAChB;AACA,YAAU;YACD,SAAS,KAAK;AACvB;AACA,YAAU;YACD,SAAS,OAAO,UAAU,EACnC,WAAU;KAEV,WAAU;AAId,QAAO;;;;;AAUT,IAAM,SAAN,MAAa;CACX,AAAQ;CACR,AAAQ,MAAM;CACd,AAAQ;CAER,YAAY,QAAiB,SAA+B;AAC1D,OAAK,SAAS;AACd,OAAK,UAAU;;CAGjB,QAAuB;AACrB,MAAI,KAAK,OAAO,WAAW,EACzB,QAAO,eAAe;AAGxB,SADe,KAAK,iBAAiB;;CAIvC,AAAQ,UAA6B;AACnC,SAAO,KAAK,OAAO,KAAK;;CAG1B,AAAQ,UAA6B;AACnC,SAAO,KAAK,OAAO,KAAK;;CAG1B,AAAQ,MAAM,MAA0B;AACtC,MAAI,KAAK,SAAS,EAAE,SAAS,MAAM;AACjC,QAAK,SAAS;AACd,UAAO;;AAET,SAAO;;;;;;CAOT,AAAQ,kBAAiC;AACvC,SAAO,KAAK,UAAU;;CAGxB,AAAQ,WAA0B;EAChC,IAAI,OAAO,KAAK,SAAS;AAEzB,SAAO,KAAK,SAAS,EAAE,SAAS,OAAO;AACrC,QAAK,SAAS;GACd,MAAM,QAAQ,KAAK,SAAS;AAC5B,UAAO,IAAI,MAAM,MAAM;;AAGzB,SAAO;;CAGT,AAAQ,UAAyB;EAC/B,IAAI,OAAO,KAAK,UAAU;AAE1B,SAAO,KAAK,SAAS,EAAE,SAAS,MAAM;AACpC,QAAK,SAAS;GACd,MAAM,QAAQ,KAAK,UAAU;AAC7B,UAAO,GAAG,MAAM,MAAM;;AAGxB,SAAO;;CAGT,AAAQ,WAA0B;EAChC,IAAI,OAAO,KAAK,YAAY;AAE5B,SAAO,KAAK,SAAS,EAAE,SAAS,OAAO;AACrC,QAAK,SAAS;GACd,MAAM,QAAQ,KAAK,YAAY;AAE/B,UAAO,GAAG,IAAI,MAAM,IAAI,MAAM,CAAC,EAAE,IAAI,IAAI,KAAK,EAAE,MAAM,CAAC;;AAGzD,SAAO;;CAGT,AAAQ,aAA4B;AAClC,MAAI,KAAK,MAAM,MAAM,CAEnB,QAAO,IADS,KAAK,YAAY,CACd;AAErB,SAAO,KAAK,cAAc;;CAG5B,AAAQ,eAA8B;AAEpC,MAAI,KAAK,MAAM,SAAS,EAAE;GACxB,MAAM,OAAO,KAAK,iBAAiB;AACnC,QAAK,MAAM,SAAS;AACpB,UAAO;;EAIT,MAAM,QAAQ,KAAK,SAAS;AAC5B,MAAI,OAAO,SAAS,SAAS;AAC3B,QAAK,SAAS;AACd,UAAO,KAAK,gBAAgB,MAAM,MAAM;;AAI1C,SAAO,eAAe;;;;;CAMxB,AAAQ,gBAAgB,OAA8B;AAEpD,MAAI,UAAU,YACZ,QAAO,wBAAwB,OAAO,MAAM;AAI9C,MAAI,MAAM,WAAW,UAAU,CAE7B,QAAO,yBADW,MAAM,MAAM,EAAE,EACW,OAAO,MAAM;AAI1D,MAAI,MAAM,WAAW,UAAU,CAC7B,QAAO,KAAK,gBAAgB,MAAM;AAIpC,MAAI,MAAM,WAAW,aAAa,CAChC,QAAO,KAAK,mBAAmB,MAAM;AAIvC,MAAI,MAAM,WAAW,SAAS,CAC5B,QAAO,KAAK,eAAe,MAAM;AAInC,MAAI,MAAM,WAAW,WAAW,CAC9B,QAAO,KAAK,iBAAiB,MAAM;AAIrC,MAAI,MAAM,WAAW,QAAQ,CAC3B,QAAO,KAAK,cAAc,MAAM;AAIlC,MAAI,MAAM,WAAW,KAAK,CACxB,QAAO,KAAK,oBAAoB,MAAM;AAIxC,MAAI,MAAM,WAAW,IAAI,IAAI,2BAA2B,KAAK,MAAM,CACjE,QAAO,KAAK,qBAAqB,MAAM;AAIzC,MAAI,MAAM,WAAW,IAAI,CACvB,QAAO,sBAAsB,OAAO,OAAO,MAAM;AAInD,MAAI,MAAM,WAAW,IAAI,CACvB,QAAO,sBAAsB,OAAO,OAAO,MAAM;AAInD,MAAI,MAAM,WAAW,IAAI,CACvB,QAAO,sBAAsB,OAAO,OAAO,MAAM;AAInD,MAAI,MAAM,SAAS,IAAI,CACrB,QAAO,KAAK,mBAAmB,MAAM;AAIvC,SAAO,KAAK,qBAAqB,MAAM;;;;;CAMzC,AAAQ,gBAAgB,KAA4B;EAClD,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO,eAAe;EAIxB,IAAI,YAAY,0BAA0B,QAAQ;AAClD,cAAY,iBAAiB,UAAU;AAGvC,MACE,UAAU,SAAS,IAAI,IACvB,CAAC,UAAU,SAAS,IAAI,IACxB,CAAC,UAAU,SAAS,IAAI,IACxB,CAAC,UAAU,SAAS,IAAI,EACxB;GAEA,MAAM,WAAW,UAAU,QAAQ,IAAI;AAGvC,UAAO,4BAFS,UAAU,MAAM,GAAG,SAAS,CAAC,MAAM,EAC9B,UAAU,MAAM,WAAW,EAAE,CAAC,MAAM,EACC,OAAO,IAAI;;AAIvE,MACE,CAAC,UAAU,SAAS,IAAI,IACxB,CAAC,UAAU,SAAS,IAAI,IACxB,CAAC,UAAU,SAAS,IAAI,CAExB,QAAO,4BACL,UAAU,MAAM,EAChB,QACA,OACA,IACD;EAIH,MAAM,EAAE,WAAW,YAAY,eAC7B,KAAK,wBAAwB,UAAU;AAEzC,MAAI,CAAC,UAEH,QAAO,sBAAsB,KAAK,OAAO,IAAI;AAG/C,SAAO,8BACL,WACA,YACA,YACA,OACA,IACD;;;;;CAMH,AAAQ,wBAAwB,WAI9B;EAEA,MAAM,aAAa,UAAU,MAC3B,2EACD;AACD,MAAI,YAAY;GACd,MAAM,GAAG,YAAY,SAAS,WAAW,SAAS,cAChD;AACF,UAAO;IACL;IACA,YAAY;KACV,OAAO,WAAW,MAAM;KACxB,cAAc,kBAAkB,WAAW,MAAM,CAAC;KAClD,WAAW,YAAY;KACxB;IACD,YAAY;KACV,OAAO,WAAW,MAAM;KACxB,cAAc,kBAAkB,WAAW,MAAM,CAAC;KAClD,WAAW,YAAY;KACxB;IACF;;EAIH,MAAM,cAAc,UAAU,MAC5B,iEACD;AACD,MAAI,aAAa;GACf,MAAM,GAAG,WAAW,UAAU,SAAS;GACvC,MAAM,UAAU,kBAAkB,MAAM,MAAM,CAAC;AAE/C,OAAI,aAAa,OAAO,aAAa,KACnC,QAAO;IACL;IACA,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW,aAAa;KACzB;IACF;YACQ,aAAa,OAAO,aAAa,KAC1C,QAAO;IACL;IACA,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW,aAAa;KACzB;IACF;YACQ,aAAa,IAEtB,QAAO;IACL;IACA,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW;KACZ;IACD,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW;KACZ;IACF;;EAKL,MAAM,gBAAgB,UAAU,MAC9B,kEACD;AACD,MAAI,eAAe;GACjB,MAAM,GAAG,OAAO,UAAU,aAAa;GACvC,MAAM,UAAU,kBAAkB,MAAM,MAAM,CAAC;AAG/C,OAAI,aAAa,OAAO,aAAa,KACnC,QAAO;IACL;IACA,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW,aAAa;KACzB;IACF;YACQ,aAAa,OAAO,aAAa,KAC1C,QAAO;IACL;IACA,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW,aAAa;KACzB;IACF;;AAIL,SAAO,EAAE;;;;;CAMX,AAAQ,eAAe,KAA4B;EACjD,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO,eAAe;AAIxB,SAAO,oBADgB,cAAc,SAAS,KAAK,QAAQ,EAChB,OAAO,IAAI;;;;;;;;;;;CAYxD,AAAQ,iBAAiB,KAA4B;EACnD,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO,eAAe;EAGxB,IAAI,YAAY,QAAQ,MAAM;EAC9B,IAAI,SAAS;EAGb,MAAM,eAAe,UAAU,YAAY,IAAI;AAC/C,MAAI,iBAAiB,IAEnB;OADmB,UAAU,MAAM,eAAe,EAAE,CAAC,MAAM,KACxC,KAAK;AACtB,aAAS;AACT,gBAAY,UAAU,MAAM,GAAG,aAAa,CAAC,MAAM;;;AAKvD,SAAO,sBADgB,cAAc,WAAW,KAAK,QAAQ,EAChB,QAAQ,OAAO,IAAI;;;;;;;;;CAUlE,AAAQ,mBAAmB,KAA4B;EACrD,MAAM,UAAU,IAAI,MAAM,IAAI,GAAG;AACjC,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO,eAAe;AAIxB,MAAI,QAAQ,WAAW,KAAK,CAE1B,QAAO,wBAAwB,YADd,QAAQ,MAAM,EAAE,CAAC,MAAM,EACa,OAAO,IAAI;AAIlE,SAAO,wBAAwB,WAAW,SAAS,OAAO,IAAI;;;;;CAMhE,AAAQ,cAAc,KAA4B;EAChD,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO,eAAe;AAKxB,SAAO,mBADgB,cAAc,SAAS,KAAK,QAAQ,EACjB,OAAO,IAAI;;;;;CAMvD,AAAQ,oBAAoB,KAA4B;EACtD,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO,eAAe;EAKxB,MAAM,WAAW,kBAAkB,QAAQ;EAC3C,IAAI;EACJ,IAAI;AAEJ,MAAI,aAAa,IAAI;AACnB,mBAAgB,QAAQ,MAAM,GAAG,SAAS,CAAC,MAAM;AACjD,eAAY,QAAQ,MAAM,WAAW,EAAE,CAAC,MAAM;QAE9C,aAAY,QAAQ,MAAM;AAI5B,MAAI,UAAU,WAAW,IAAI,EAAE;GAC7B,MAAM,aAAa,UAAU,MAAM,EAAE;GACrC,MAAM,QAAQ,WAAW,QAAQ,IAAI;AAErC,OAAI,UAAU,GAEZ,QAAO,8BACL,YACA,QACA,eACA,OACA,IACD;GAGH,MAAM,WAAW,WAAW,MAAM,GAAG,MAAM,CAAC,MAAM;GAClD,IAAI,gBAAgB,WAAW,MAAM,QAAQ,EAAE,CAAC,MAAM;AAGtD,OACG,cAAc,WAAW,KAAI,IAAI,cAAc,SAAS,KAAI,IAC5D,cAAc,WAAW,IAAI,IAAI,cAAc,SAAS,IAAI,CAE7D,iBAAgB,cAAc,MAAM,GAAG,GAAG;AAG5C,UAAO,8BACL,UACA,eACA,eACA,OACA,IACD;;AAKH,MAAI,uBAAuB,KAAK,UAAU,CACxC,QAAO,4BAA4B,WAAW,eAAe,OAAO,IAAI;EAI1E,IAAI,oBAAoB,0BAA0B,UAAU;AAC5D,sBAAoB,iBAAiB,kBAAkB;EAEvD,MAAM,EAAE,WAAW,YAAY,eAC7B,KAAK,wBAAwB,kBAAkB;AAEjD,MAAI,CAAC,UAEH,QAAO,sBAAsB,KAAK,OAAO,IAAI;AAG/C,SAAO,kCACL,WACA,YACA,YACA,eACA,OACA,IACD;;;;;CAMH,AAAQ,qBAAqB,KAA4B;EACvD,MAAM,MAAM,KAAK,QAAQ;AACzB,MAAI,CAAC,IAEH,QAAO,sBAAsB,KAAK,OAAO,IAAI;EAG/C,MAAM,WAAW,uBAAuB,KAAK,IAAI;AACjD,MAAI,CAAC,SAEH,QAAO,wBACL,QAAQ,aAAa,IAAI,MAAM,EAAE,CAAC,IAClC,QACA,KACA,OACA,IACD;AAIH,SAAO,cAAc,UAAU,KAAK,QAAQ;;;;;CAM9C,AAAQ,mBAAmB,KAA4B;EAErD,MAAM,UAAU,IAAI,MAAM,0CAA0C;AACpE,MAAI,CAAC,QACH,QAAO,wBACL,QAAQ,aAAa,IAAI,IACzB,QACA,KACA,OACA,IACD;EAGH,MAAM,GAAG,KAAK,UAAU,SAAS;EACjC,IAAI,aAAa;AAGjB,MACG,WAAW,WAAW,KAAI,IAAI,WAAW,SAAS,KAAI,IACtD,WAAW,WAAW,IAAI,IAAI,WAAW,SAAS,IAAI,CAEvD,cAAa,WAAW,MAAM,GAAG,GAAG;AAGtC,SAAO,wBACL,QAAQ,aAAa,IAAI,IACzB,YACA,UACA,OACA,IACD;;;;;CAMH,AAAQ,qBAAqB,KAA4B;AACvD,SAAO,wBACL,QAAQ,aAAa,IAAI,IACzB,QACA,KACA,OACA,IACD;;;;;;AAWL,SAAS,kBAAkB,OAA8B;CACvD,MAAM,QAAQ,MAAM,MAAM,wCAAwC;AAClE,KAAI,MACF,QAAO,WAAW,MAAM,GAAG;AAE7B,QAAO;;;;;AAUT,SAAgB,cACd,UACA,UAAgC,EAAE,EACnB;AAEf,KAAI,CAAC,YAAY,CAAC,SAAS,MAAM,CAC/B,QAAO,eAAe;CAGxB,MAAM,UAAU,SAAS,MAAM;CAI/B,MAAM,MAAM,QAAQ;CACpB,MAAM,iBACJ,OAAO,OAAO,KAAK,IAAI,sBAAsB,CAAC,SAAS,IACnD,KAAK,UAAU,IAAI,sBAAsB,GACzC;CACN,MAAM,WAAW,KAAK,UAAU;EAC9B;EACA,QAAQ;EACR;EACD,CAAC;CAGF,MAAM,SAAS,WAAW,IAAI,SAAS;AACvC,KAAI,OACF,QAAO;CAMT,MAAM,SADS,IAAI,OADJ,SAAS,QAAQ,EACE,QAAQ,CACpB,OAAO;AAG7B,YAAW,IAAI,UAAU,OAAO;AAEhC,QAAO"}
|
|
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';\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 and @(...) container queries we need to handle nested parentheses\n * like @supports($, :has(*)) or @(scroll-state(stuck: top)).\n * We use a pattern that allows one level of nesting: [^()]*(?:\\([^)]*\\))?[^)]*\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-]+)|(:[-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 // Pseudo-class (e.g., :hover, :focus-visible, :nth-child(2n))\n if (value.startsWith(':')) {\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 = JSON.stringify([\n trimmed,\n options.isSubElement,\n localStatesKey,\n ]);\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":";;;;;;;;;;;;;;;;;;AAgDA,MAAM,uBAAuB;AAgB7B,MAAM,aAAa,IAAI,IAA2B,IAAK;;;;;;;;;;AAevD,MAAM,sBACJ;;;;AAqBF,SAAS,SAAS,UAA2B;CAC3C,MAAM,SAAkB,EAAE;CAC1B,IAAI;CAGJ,MAAM,aAAa,2BAA2B,SAAS;AAEvD,qBAAoB,YAAY;AAChC,SAAQ,QAAQ,oBAAoB,KAAK,WAAW,MAAM,MAAM;EAC9D,MAAM,YAAY,MAAM;AAExB,MAAI,MAAM,GAER,SAAQ,WAAR;GACE,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAO,OAAO;KAAK,KAAK;KAAW,CAAC;AACxD;GACF,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAM,OAAO;KAAK,KAAK;KAAW,CAAC;AACvD;GACF,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAO,OAAO;KAAK,KAAK;KAAW,CAAC;AACxD;GACF,KAAK;AACH,WAAO,KAAK;KAAE,MAAM;KAAO,OAAO;KAAK,KAAK;KAAW,CAAC;AACxD;;WAEK,MAAM,GAEf,KAAI,cAAc,IAChB,QAAO,KAAK;GAAE,MAAM;GAAU,OAAO;GAAK,KAAK;GAAW,CAAC;MAE3D,QAAO,KAAK;GAAE,MAAM;GAAU,OAAO;GAAK,KAAK;GAAW,CAAC;MAI7D,QAAO,KAAK;GAAE,MAAM;GAAS,OAAO;GAAW,KAAK;GAAW,CAAC;;AAIpE,QAAO;;;;;AAMT,SAAS,2BAA2B,KAAqB;CACvD,IAAI,SAAS;CACb,IAAI,QAAQ;AAEZ,MAAK,MAAM,QAAQ,IACjB,KAAI,SAAS,KAAK;AAChB;AACA,YAAU;YACD,SAAS,KAAK;AACvB;AACA,YAAU;YACD,SAAS,OAAO,UAAU,EACnC,WAAU;KAEV,WAAU;AAId,QAAO;;;;;AAUT,IAAM,SAAN,MAAa;CACX,AAAQ;CACR,AAAQ,MAAM;CACd,AAAQ;CAER,YAAY,QAAiB,SAA+B;AAC1D,OAAK,SAAS;AACd,OAAK,UAAU;;CAGjB,QAAuB;AACrB,MAAI,KAAK,OAAO,WAAW,EACzB,QAAO,eAAe;AAGxB,SADe,KAAK,iBAAiB;;CAIvC,AAAQ,UAA6B;AACnC,SAAO,KAAK,OAAO,KAAK;;CAG1B,AAAQ,UAA6B;AACnC,SAAO,KAAK,OAAO,KAAK;;CAG1B,AAAQ,MAAM,MAA0B;AACtC,MAAI,KAAK,SAAS,EAAE,SAAS,MAAM;AACjC,QAAK,SAAS;AACd,UAAO;;AAET,SAAO;;;;;;CAOT,AAAQ,kBAAiC;AACvC,SAAO,KAAK,UAAU;;CAGxB,AAAQ,WAA0B;EAChC,IAAI,OAAO,KAAK,SAAS;AAEzB,SAAO,KAAK,SAAS,EAAE,SAAS,OAAO;AACrC,QAAK,SAAS;GACd,MAAM,QAAQ,KAAK,SAAS;AAC5B,UAAO,IAAI,MAAM,MAAM;;AAGzB,SAAO;;CAGT,AAAQ,UAAyB;EAC/B,IAAI,OAAO,KAAK,UAAU;AAE1B,SAAO,KAAK,SAAS,EAAE,SAAS,MAAM;AACpC,QAAK,SAAS;GACd,MAAM,QAAQ,KAAK,UAAU;AAC7B,UAAO,GAAG,MAAM,MAAM;;AAGxB,SAAO;;CAGT,AAAQ,WAA0B;EAChC,IAAI,OAAO,KAAK,YAAY;EAC5B,IAAI,eAAe;AAEnB,SAAO,KAAK,SAAS,EAAE,SAAS,OAAO;AACrC,QAAK,SAAS;GACd,MAAM,QAAQ,KAAK,YAAY;AAC/B;AAEA,OAAI,eAAe,qBACjB,aACE,sBACA,kBAAkB,aAAa,qBAAqB,KAAK,IAAI,GAAG,eAAe,EAAE,CAAC,uFAEnF;AAIH,UAAO,GAAG,IAAI,MAAM,IAAI,MAAM,CAAC,EAAE,IAAI,IAAI,KAAK,EAAE,MAAM,CAAC;;AAGzD,SAAO;;CAGT,AAAQ,aAA4B;AAClC,MAAI,KAAK,MAAM,MAAM,CAEnB,QAAO,IADS,KAAK,YAAY,CACd;AAErB,SAAO,KAAK,cAAc;;CAG5B,AAAQ,eAA8B;AAEpC,MAAI,KAAK,MAAM,SAAS,EAAE;GACxB,MAAM,OAAO,KAAK,iBAAiB;AACnC,QAAK,MAAM,SAAS;AACpB,UAAO;;EAIT,MAAM,QAAQ,KAAK,SAAS;AAC5B,MAAI,OAAO,SAAS,SAAS;AAC3B,QAAK,SAAS;AACd,UAAO,KAAK,gBAAgB,MAAM,MAAM;;AAI1C,SAAO,eAAe;;;;;CAMxB,AAAQ,gBAAgB,OAA8B;AAEpD,MAAI,UAAU,YACZ,QAAO,wBAAwB,OAAO,MAAM;AAI9C,MAAI,MAAM,WAAW,UAAU,CAE7B,QAAO,yBADW,MAAM,MAAM,EAAE,EACW,OAAO,MAAM;AAI1D,MAAI,MAAM,WAAW,UAAU,CAC7B,QAAO,KAAK,gBAAgB,MAAM;AAIpC,MAAI,MAAM,WAAW,aAAa,CAChC,QAAO,KAAK,mBAAmB,MAAM;AAIvC,MAAI,MAAM,WAAW,SAAS,CAC5B,QAAO,KAAK,eAAe,MAAM;AAInC,MAAI,MAAM,WAAW,WAAW,CAC9B,QAAO,KAAK,iBAAiB,MAAM;AAIrC,MAAI,MAAM,WAAW,QAAQ,CAC3B,QAAO,KAAK,cAAc,MAAM;AAIlC,MAAI,MAAM,WAAW,KAAK,CACxB,QAAO,KAAK,oBAAoB,MAAM;AAIxC,MAAI,MAAM,WAAW,IAAI,IAAI,2BAA2B,KAAK,MAAM,CACjE,QAAO,KAAK,qBAAqB,MAAM;AAIzC,MAAI,MAAM,WAAW,IAAI,CACvB,QAAO,sBAAsB,OAAO,OAAO,MAAM;AAInD,MAAI,MAAM,WAAW,IAAI,CACvB,QAAO,sBAAsB,OAAO,OAAO,MAAM;AAInD,MAAI,MAAM,WAAW,IAAI,CACvB,QAAO,sBAAsB,OAAO,OAAO,MAAM;AAInD,MAAI,MAAM,SAAS,IAAI,CACrB,QAAO,KAAK,mBAAmB,MAAM;AAIvC,SAAO,KAAK,qBAAqB,MAAM;;;;;CAMzC,AAAQ,gBAAgB,KAA4B;EAClD,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO,eAAe;EAIxB,IAAI,YAAY,0BAA0B,QAAQ;AAClD,cAAY,iBAAiB,UAAU;AAGvC,MACE,UAAU,SAAS,IAAI,IACvB,CAAC,UAAU,SAAS,IAAI,IACxB,CAAC,UAAU,SAAS,IAAI,IACxB,CAAC,UAAU,SAAS,IAAI,EACxB;GAEA,MAAM,WAAW,UAAU,QAAQ,IAAI;AAGvC,UAAO,4BAFS,UAAU,MAAM,GAAG,SAAS,CAAC,MAAM,EAC9B,UAAU,MAAM,WAAW,EAAE,CAAC,MAAM,EACC,OAAO,IAAI;;AAIvE,MACE,CAAC,UAAU,SAAS,IAAI,IACxB,CAAC,UAAU,SAAS,IAAI,IACxB,CAAC,UAAU,SAAS,IAAI,CAExB,QAAO,4BACL,UAAU,MAAM,EAChB,QACA,OACA,IACD;EAIH,MAAM,EAAE,WAAW,YAAY,eAC7B,KAAK,wBAAwB,UAAU;AAEzC,MAAI,CAAC,UAEH,QAAO,sBAAsB,KAAK,OAAO,IAAI;AAG/C,SAAO,8BACL,WACA,YACA,YACA,OACA,IACD;;;;;CAMH,AAAQ,wBAAwB,WAI9B;EAEA,MAAM,aAAa,UAAU,MAC3B,2EACD;AACD,MAAI,YAAY;GACd,MAAM,GAAG,YAAY,SAAS,WAAW,SAAS,cAChD;AACF,UAAO;IACL;IACA,YAAY;KACV,OAAO,WAAW,MAAM;KACxB,cAAc,kBAAkB,WAAW,MAAM,CAAC;KAClD,WAAW,YAAY;KACxB;IACD,YAAY;KACV,OAAO,WAAW,MAAM;KACxB,cAAc,kBAAkB,WAAW,MAAM,CAAC;KAClD,WAAW,YAAY;KACxB;IACF;;EAIH,MAAM,cAAc,UAAU,MAC5B,iEACD;AACD,MAAI,aAAa;GACf,MAAM,GAAG,WAAW,UAAU,SAAS;GACvC,MAAM,UAAU,kBAAkB,MAAM,MAAM,CAAC;AAE/C,OAAI,aAAa,OAAO,aAAa,KACnC,QAAO;IACL;IACA,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW,aAAa;KACzB;IACF;YACQ,aAAa,OAAO,aAAa,KAC1C,QAAO;IACL;IACA,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW,aAAa;KACzB;IACF;YACQ,aAAa,IAEtB,QAAO;IACL;IACA,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW;KACZ;IACD,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW;KACZ;IACF;;EAKL,MAAM,gBAAgB,UAAU,MAC9B,kEACD;AACD,MAAI,eAAe;GACjB,MAAM,GAAG,OAAO,UAAU,aAAa;GACvC,MAAM,UAAU,kBAAkB,MAAM,MAAM,CAAC;AAG/C,OAAI,aAAa,OAAO,aAAa,KACnC,QAAO;IACL;IACA,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW,aAAa;KACzB;IACF;YACQ,aAAa,OAAO,aAAa,KAC1C,QAAO;IACL;IACA,YAAY;KACV,OAAO,MAAM,MAAM;KACnB,cAAc;KACd,WAAW,aAAa;KACzB;IACF;;AAIL,SAAO,EAAE;;;;;CAMX,AAAQ,oBACN,KACA,WACA,MACe;EACf,MAAM,UAAU,IAAI,MAAM,WAAW,GAAG;AACxC,MAAI,CAAC,QAAQ,MAAM,CAAE,QAAO,eAAe;AAC3C,SAAO,KAAK,cAAc,SAAS,KAAK,QAAQ,CAAC;;CAGnD,AAAQ,eAAe,KAA4B;AACjD,SAAO,KAAK,oBAAoB,KAAK,IAAI,UACvC,oBAAoB,OAAO,OAAO,IAAI,CACvC;;;;;;;;;;;CAYH,AAAQ,iBAAiB,KAA4B;EACnD,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO,eAAe;EAGxB,IAAI,YAAY,QAAQ,MAAM;EAC9B,IAAI,SAAS;EAEb,MAAM,eAAe,UAAU,YAAY,IAAI;AAC/C,MAAI,iBAAiB,IAEnB;OADmB,UAAU,MAAM,eAAe,EAAE,CAAC,MAAM,KACxC,KAAK;AACtB,aAAS;AACT,gBAAY,UAAU,MAAM,GAAG,aAAa,CAAC,MAAM;;;AAKvD,SAAO,sBADgB,cAAc,WAAW,KAAK,QAAQ,EAChB,QAAQ,OAAO,IAAI;;;;;;;;;CAUlE,AAAQ,mBAAmB,KAA4B;EACrD,MAAM,UAAU,IAAI,MAAM,IAAI,GAAG;AACjC,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO,eAAe;AAIxB,MAAI,QAAQ,WAAW,KAAK,CAE1B,QAAO,wBAAwB,YADd,QAAQ,MAAM,EAAE,CAAC,MAAM,EACa,OAAO,IAAI;AAIlE,SAAO,wBAAwB,WAAW,SAAS,OAAO,IAAI;;CAGhE,AAAQ,cAAc,KAA4B;AAChD,SAAO,KAAK,oBAAoB,KAAK,IAAI,UACvC,mBAAmB,OAAO,OAAO,IAAI,CACtC;;;;;CAMH,AAAQ,oBAAoB,KAA4B;EACtD,MAAM,UAAU,IAAI,MAAM,GAAG,GAAG;AAChC,MAAI,CAAC,QAAQ,MAAM,CACjB,QAAO,eAAe;EAKxB,MAAM,WAAW,kBAAkB,QAAQ;EAC3C,IAAI;EACJ,IAAI;AAEJ,MAAI,aAAa,IAAI;AACnB,mBAAgB,QAAQ,MAAM,GAAG,SAAS,CAAC,MAAM;AACjD,eAAY,QAAQ,MAAM,WAAW,EAAE,CAAC,MAAM;QAE9C,aAAY,QAAQ,MAAM;AAI5B,MAAI,UAAU,WAAW,IAAI,EAAE;GAC7B,MAAM,aAAa,UAAU,MAAM,EAAE;GACrC,MAAM,QAAQ,WAAW,QAAQ,IAAI;AAErC,OAAI,UAAU,GAEZ,QAAO,8BACL,YACA,QACA,eACA,OACA,IACD;GAGH,MAAM,WAAW,WAAW,MAAM,GAAG,MAAM,CAAC,MAAM;GAClD,IAAI,gBAAgB,WAAW,MAAM,QAAQ,EAAE,CAAC,MAAM;AAGtD,OACG,cAAc,WAAW,KAAI,IAAI,cAAc,SAAS,KAAI,IAC5D,cAAc,WAAW,IAAI,IAAI,cAAc,SAAS,IAAI,CAE7D,iBAAgB,cAAc,MAAM,GAAG,GAAG;AAG5C,UAAO,8BACL,UACA,eACA,eACA,OACA,IACD;;AAKH,MAAI,uBAAuB,KAAK,UAAU,CACxC,QAAO,4BAA4B,WAAW,eAAe,OAAO,IAAI;EAI1E,IAAI,oBAAoB,0BAA0B,UAAU;AAC5D,sBAAoB,iBAAiB,kBAAkB;EAEvD,MAAM,EAAE,WAAW,YAAY,eAC7B,KAAK,wBAAwB,kBAAkB;AAEjD,MAAI,CAAC,UAEH,QAAO,sBAAsB,KAAK,OAAO,IAAI;AAG/C,SAAO,kCACL,WACA,YACA,YACA,eACA,OACA,IACD;;;;;CAMH,AAAQ,qBAAqB,KAA4B;EACvD,MAAM,MAAM,KAAK,QAAQ;AACzB,MAAI,CAAC,IAEH,QAAO,sBAAsB,KAAK,OAAO,IAAI;EAG/C,MAAM,WAAW,uBAAuB,KAAK,IAAI;AACjD,MAAI,CAAC,SAEH,QAAO,wBACL,QAAQ,aAAa,IAAI,MAAM,EAAE,CAAC,IAClC,QACA,KACA,OACA,IACD;AAIH,SAAO,cAAc,UAAU,KAAK,QAAQ;;;;;CAM9C,AAAQ,mBAAmB,KAA4B;EAErD,MAAM,UAAU,IAAI,MAAM,0CAA0C;AACpE,MAAI,CAAC,QACH,QAAO,wBACL,QAAQ,aAAa,IAAI,IACzB,QACA,KACA,OACA,IACD;EAGH,MAAM,GAAG,KAAK,UAAU,SAAS;EACjC,IAAI,aAAa;AAGjB,MACG,WAAW,WAAW,KAAI,IAAI,WAAW,SAAS,KAAI,IACtD,WAAW,WAAW,IAAI,IAAI,WAAW,SAAS,IAAI,CAEvD,cAAa,WAAW,MAAM,GAAG,GAAG;AAGtC,SAAO,wBACL,QAAQ,aAAa,IAAI,IACzB,YACA,UACA,OACA,IACD;;;;;CAMH,AAAQ,qBAAqB,KAA4B;AACvD,SAAO,wBACL,QAAQ,aAAa,IAAI,IACzB,QACA,KACA,OACA,IACD;;;;;;AAWL,SAAS,kBAAkB,OAA8B;CACvD,MAAM,QAAQ,MAAM,MAAM,wCAAwC;AAClE,KAAI,MACF,QAAO,WAAW,MAAM,GAAG;AAE7B,QAAO;;;;;AAUT,SAAgB,cACd,UACA,UAAgC,EAAE,EACnB;AAEf,KAAI,CAAC,YAAY,CAAC,SAAS,MAAM,CAC/B,QAAO,eAAe;CAGxB,MAAM,UAAU,SAAS,MAAM;CAI/B,MAAM,MAAM,QAAQ;CACpB,MAAM,iBACJ,OAAO,OAAO,KAAK,IAAI,sBAAsB,CAAC,SAAS,IACnD,KAAK,UAAU,IAAI,sBAAsB,GACzC;CACN,MAAM,WAAW,KAAK,UAAU;EAC9B;EACA,QAAQ;EACR;EACD,CAAC;CAGF,MAAM,SAAS,WAAW,IAAI,SAAS;AACvC,KAAI,OACF,QAAO;CAMT,MAAM,SADS,IAAI,OADJ,SAAS,QAAQ,EACE,QAAQ,CACpB,OAAO;AAG7B,YAAW,IAAI,UAAU,OAAO;AAEhC,QAAO"}
|
|
@@ -93,23 +93,11 @@ function simplifyOr(children) {
|
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
|
-
* Check if any
|
|
96
|
+
* Check if any pair of terms has complementary negation (A and !A).
|
|
97
|
+
* Used for both contradiction detection (in AND) and tautology detection (in OR),
|
|
98
|
+
* since the underlying check is identical: the context determines the semantics.
|
|
97
99
|
*/
|
|
98
|
-
function
|
|
99
|
-
const uniqueIds = /* @__PURE__ */ new Set();
|
|
100
|
-
for (const term of terms) {
|
|
101
|
-
if (term.kind !== "state") continue;
|
|
102
|
-
const id = term.uniqueId;
|
|
103
|
-
const negatedId = term.negated ? id.slice(1) : `!${id}`;
|
|
104
|
-
if (uniqueIds.has(negatedId)) return true;
|
|
105
|
-
uniqueIds.add(id);
|
|
106
|
-
}
|
|
107
|
-
return false;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Check for tautologies (A | !A)
|
|
111
|
-
*/
|
|
112
|
-
function hasTautology(terms) {
|
|
100
|
+
function hasComplementaryPair(terms) {
|
|
113
101
|
const uniqueIds = /* @__PURE__ */ new Set();
|
|
114
102
|
for (const term of terms) {
|
|
115
103
|
if (term.kind !== "state") continue;
|
|
@@ -120,6 +108,8 @@ function hasTautology(terms) {
|
|
|
120
108
|
}
|
|
121
109
|
return false;
|
|
122
110
|
}
|
|
111
|
+
const hasContradiction = hasComplementaryPair;
|
|
112
|
+
const hasTautology = hasComplementaryPair;
|
|
123
113
|
/**
|
|
124
114
|
* Check for range contradictions in media/container queries
|
|
125
115
|
* e.g., @media(w < 400px) & @media(w > 800px) → FALSE
|
|
@@ -262,61 +252,52 @@ function boundsWithinExcludedRange(bounds, excluded) {
|
|
|
262
252
|
* e.g., [data-theme="dark"] & [data-theme="light"] → FALSE
|
|
263
253
|
* e.g., [data-theme="dark"] & ![data-theme] → FALSE
|
|
264
254
|
*/
|
|
265
|
-
function hasAttributeConflict(terms) {
|
|
266
|
-
const modifiersByAttr = /* @__PURE__ */ new Map();
|
|
267
|
-
for (const term of terms) {
|
|
268
|
-
if (term.kind !== "state" || term.type !== "modifier") continue;
|
|
269
|
-
const attr = term.attribute;
|
|
270
|
-
if (!modifiersByAttr.has(attr)) modifiersByAttr.set(attr, {
|
|
271
|
-
positive: [],
|
|
272
|
-
negated: []
|
|
273
|
-
});
|
|
274
|
-
const group = modifiersByAttr.get(attr);
|
|
275
|
-
if (term.negated) group.negated.push(term);
|
|
276
|
-
else group.positive.push(term);
|
|
277
|
-
}
|
|
278
|
-
for (const [_attr, group] of modifiersByAttr) {
|
|
279
|
-
const positiveValues = group.positive.filter((m) => m.value !== void 0).map((m) => m.value);
|
|
280
|
-
if (new Set(positiveValues).size > 1) return true;
|
|
281
|
-
const hasPositiveValue = group.positive.some((m) => m.value !== void 0);
|
|
282
|
-
const hasNegatedBoolean = group.negated.some((m) => m.value === void 0);
|
|
283
|
-
if (hasPositiveValue && hasNegatedBoolean) return true;
|
|
284
|
-
for (const pos of group.positive) if (pos.value !== void 0) {
|
|
285
|
-
for (const neg of group.negated) if (neg.value === pos.value) return true;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
255
|
/**
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
256
|
+
* Generic value-conflict checker for grouped conditions.
|
|
257
|
+
*
|
|
258
|
+
* Groups terms by a key, splits into positive/negated, then checks:
|
|
259
|
+
* 1. Multiple distinct positive values → conflict
|
|
260
|
+
* 2. Positive value + negated existence (value === undefined) → conflict
|
|
261
|
+
* 3. Positive value + negated same value → conflict
|
|
294
262
|
*/
|
|
295
|
-
function
|
|
296
|
-
const
|
|
263
|
+
function hasGroupedValueConflict(terms, match, groupKey, getValue) {
|
|
264
|
+
const groups = /* @__PURE__ */ new Map();
|
|
297
265
|
for (const term of terms) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
266
|
+
const matched = match(term);
|
|
267
|
+
if (!matched) continue;
|
|
268
|
+
const key = groupKey(matched);
|
|
269
|
+
let group = groups.get(key);
|
|
270
|
+
if (!group) {
|
|
271
|
+
group = {
|
|
272
|
+
positive: [],
|
|
273
|
+
negated: []
|
|
274
|
+
};
|
|
275
|
+
groups.set(key, group);
|
|
276
|
+
}
|
|
277
|
+
if (matched.negated) group.negated.push(matched);
|
|
278
|
+
else group.positive.push(matched);
|
|
307
279
|
}
|
|
308
|
-
for (const [, group] of
|
|
309
|
-
const positiveValues = group.positive.filter((
|
|
280
|
+
for (const [, group] of groups) {
|
|
281
|
+
const positiveValues = group.positive.map(getValue).filter((v) => v !== void 0);
|
|
310
282
|
if (new Set(positiveValues).size > 1) return true;
|
|
311
|
-
const hasPositiveValue =
|
|
312
|
-
const hasNegatedExistence = group.negated.some((
|
|
283
|
+
const hasPositiveValue = positiveValues.length > 0;
|
|
284
|
+
const hasNegatedExistence = group.negated.some((t) => getValue(t) === void 0);
|
|
313
285
|
if (hasPositiveValue && hasNegatedExistence) return true;
|
|
314
|
-
for (const pos of group.positive)
|
|
315
|
-
|
|
286
|
+
for (const pos of group.positive) {
|
|
287
|
+
const posVal = getValue(pos);
|
|
288
|
+
if (posVal !== void 0) {
|
|
289
|
+
for (const neg of group.negated) if (getValue(neg) === posVal) return true;
|
|
290
|
+
}
|
|
316
291
|
}
|
|
317
292
|
}
|
|
318
293
|
return false;
|
|
319
294
|
}
|
|
295
|
+
function hasAttributeConflict(terms) {
|
|
296
|
+
return hasGroupedValueConflict(terms, (t) => t.kind === "state" && t.type === "modifier" ? t : null, (t) => t.attribute, (t) => t.value);
|
|
297
|
+
}
|
|
298
|
+
function hasContainerStyleConflict(terms) {
|
|
299
|
+
return hasGroupedValueConflict(terms, (t) => t.kind === "state" && t.type === "container" && t.subtype === "style" ? t : null, (t) => `${t.containerName || "_"}:${t.property}`, (t) => t.propertyValue);
|
|
300
|
+
}
|
|
320
301
|
/**
|
|
321
302
|
* Remove negations that are implied by positive terms.
|
|
322
303
|
*
|
|
@@ -332,35 +313,38 @@ function hasContainerStyleConflict(terms) {
|
|
|
332
313
|
* Before: @container style(--variant: danger) and (not style(--variant: success))
|
|
333
314
|
* After: @container style(--variant: danger)
|
|
334
315
|
*/
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Collect positive values from terms and build a "is this negation implied?" check.
|
|
318
|
+
*
|
|
319
|
+
* A negation is implied (redundant) when a positive term for the same group
|
|
320
|
+
* already pins a specific value, making "NOT other-value" obvious.
|
|
321
|
+
* e.g. style(--variant: danger) implies NOT style(--variant: success).
|
|
322
|
+
*/
|
|
323
|
+
function buildImpliedNegationCheck(terms) {
|
|
324
|
+
const positiveValues = /* @__PURE__ */ new Map();
|
|
338
325
|
for (const term of terms) {
|
|
339
326
|
if (term.kind !== "state" || term.negated) continue;
|
|
340
327
|
if (term.type === "container" && term.subtype === "style") {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
if (term.type === "modifier" && term.value !== void 0) positiveModifiers.set(term.attribute, term.value);
|
|
328
|
+
if (term.propertyValue !== void 0) positiveValues.set(`c:${term.containerName || "_"}:${term.property}`, term.propertyValue);
|
|
329
|
+
} else if (term.type === "modifier" && term.value !== void 0) positiveValues.set(`m:${term.attribute}`, term.value);
|
|
345
330
|
}
|
|
346
|
-
return
|
|
347
|
-
if (term.kind !== "state" || !term.negated) return
|
|
331
|
+
return (term) => {
|
|
332
|
+
if (term.kind !== "state" || !term.negated) return false;
|
|
348
333
|
if (term.type === "container" && term.subtype === "style") {
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
if (term.propertyValue === void 0) return true;
|
|
353
|
-
if (term.propertyValue !== positiveValue) return false;
|
|
354
|
-
}
|
|
334
|
+
if (term.propertyValue === void 0) return false;
|
|
335
|
+
const pos = positiveValues.get(`c:${term.containerName || "_"}:${term.property}`);
|
|
336
|
+
return pos !== void 0 && term.propertyValue !== pos;
|
|
355
337
|
}
|
|
356
|
-
if (term.type === "modifier") {
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
if (term.value !== positiveValue) return false;
|
|
360
|
-
}
|
|
338
|
+
if (term.type === "modifier" && term.value !== void 0) {
|
|
339
|
+
const pos = positiveValues.get(`m:${term.attribute}`);
|
|
340
|
+
return pos !== void 0 && term.value !== pos;
|
|
361
341
|
}
|
|
362
|
-
return
|
|
363
|
-
}
|
|
342
|
+
return false;
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function removeImpliedNegations(terms) {
|
|
346
|
+
const isImplied = buildImpliedNegationCheck(terms);
|
|
347
|
+
return terms.filter((t) => !isImplied(t));
|
|
364
348
|
}
|
|
365
349
|
function deduplicateTerms(terms) {
|
|
366
350
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -427,8 +411,11 @@ function mergeRanges(terms) {
|
|
|
427
411
|
result.push(...mergedTerms);
|
|
428
412
|
return result;
|
|
429
413
|
}
|
|
430
|
-
|
|
431
|
-
|
|
414
|
+
/**
|
|
415
|
+
* Tighten bounds by picking the most restrictive lower and upper bounds
|
|
416
|
+
* from a set of conditions that have lowerBound/upperBound fields.
|
|
417
|
+
*/
|
|
418
|
+
function tightenBounds(conditions) {
|
|
432
419
|
let lowerBound;
|
|
433
420
|
let upperBound;
|
|
434
421
|
for (const cond of conditions) {
|
|
@@ -439,23 +426,12 @@ function mergeMediaRanges(conditions) {
|
|
|
439
426
|
if (!upperBound || (cond.upperBound.valueNumeric ?? Infinity) < (upperBound.valueNumeric ?? Infinity)) upperBound = cond.upperBound;
|
|
440
427
|
}
|
|
441
428
|
}
|
|
442
|
-
|
|
443
|
-
const merged = {
|
|
444
|
-
kind: "state",
|
|
445
|
-
type: "media",
|
|
446
|
-
subtype: "dimension",
|
|
447
|
-
negated: false,
|
|
448
|
-
raw: buildMergedRaw(base.dimension || "width", lowerBound, upperBound),
|
|
449
|
-
uniqueId: "",
|
|
450
|
-
dimension: base.dimension,
|
|
429
|
+
return {
|
|
451
430
|
lowerBound,
|
|
452
431
|
upperBound
|
|
453
432
|
};
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
"dim",
|
|
457
|
-
merged.dimension
|
|
458
|
-
];
|
|
433
|
+
}
|
|
434
|
+
function appendBoundsToUniqueId(parts, lowerBound, upperBound) {
|
|
459
435
|
if (lowerBound) {
|
|
460
436
|
parts.push(lowerBound.inclusive ? ">=" : ">");
|
|
461
437
|
parts.push(lowerBound.value);
|
|
@@ -464,50 +440,38 @@ function mergeMediaRanges(conditions) {
|
|
|
464
440
|
parts.push(upperBound.inclusive ? "<=" : "<");
|
|
465
441
|
parts.push(upperBound.value);
|
|
466
442
|
}
|
|
467
|
-
merged.uniqueId = parts.join(":");
|
|
468
|
-
return merged;
|
|
469
443
|
}
|
|
470
|
-
function
|
|
444
|
+
function mergeDimensionRanges(conditions, idPrefix) {
|
|
471
445
|
if (conditions.length === 0) return null;
|
|
472
|
-
|
|
473
|
-
let upperBound;
|
|
474
|
-
for (const cond of conditions) {
|
|
475
|
-
if (cond.lowerBound) {
|
|
476
|
-
if (!lowerBound || (cond.lowerBound.valueNumeric ?? -Infinity) > (lowerBound.valueNumeric ?? -Infinity)) lowerBound = cond.lowerBound;
|
|
477
|
-
}
|
|
478
|
-
if (cond.upperBound) {
|
|
479
|
-
if (!upperBound || (cond.upperBound.valueNumeric ?? Infinity) < (upperBound.valueNumeric ?? Infinity)) upperBound = cond.upperBound;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
446
|
+
const { lowerBound, upperBound } = tightenBounds(conditions);
|
|
482
447
|
const base = conditions[0];
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
448
|
+
const parts = [...idPrefix];
|
|
449
|
+
appendBoundsToUniqueId(parts, lowerBound, upperBound);
|
|
450
|
+
return {
|
|
451
|
+
...base,
|
|
487
452
|
negated: false,
|
|
488
453
|
raw: buildMergedRaw(base.dimension || "width", lowerBound, upperBound),
|
|
489
|
-
uniqueId: "",
|
|
490
|
-
containerName: base.containerName,
|
|
491
|
-
dimension: base.dimension,
|
|
454
|
+
uniqueId: parts.join(":"),
|
|
492
455
|
lowerBound,
|
|
493
456
|
upperBound
|
|
494
457
|
};
|
|
495
|
-
|
|
458
|
+
}
|
|
459
|
+
function mergeMediaRanges(conditions) {
|
|
460
|
+
return mergeDimensionRanges(conditions, [
|
|
461
|
+
"media",
|
|
462
|
+
"dim",
|
|
463
|
+
conditions[0]?.dimension ?? "width"
|
|
464
|
+
]);
|
|
465
|
+
}
|
|
466
|
+
function mergeContainerRanges(conditions) {
|
|
467
|
+
const base = conditions[0];
|
|
468
|
+
if (!base) return null;
|
|
469
|
+
return mergeDimensionRanges(conditions, [
|
|
496
470
|
"container",
|
|
497
471
|
"dim",
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
];
|
|
501
|
-
if (lowerBound) {
|
|
502
|
-
parts.push(lowerBound.inclusive ? ">=" : ">");
|
|
503
|
-
parts.push(lowerBound.value);
|
|
504
|
-
}
|
|
505
|
-
if (upperBound) {
|
|
506
|
-
parts.push(upperBound.inclusive ? "<=" : "<");
|
|
507
|
-
parts.push(upperBound.value);
|
|
508
|
-
}
|
|
509
|
-
merged.uniqueId = parts.join(":");
|
|
510
|
-
return merged;
|
|
472
|
+
base.containerName || "_",
|
|
473
|
+
base.dimension ?? "width"
|
|
474
|
+
]);
|
|
511
475
|
}
|
|
512
476
|
function buildMergedRaw(dimension, lowerBound, upperBound) {
|
|
513
477
|
if (lowerBound && upperBound) {
|
|
@@ -519,37 +483,32 @@ function buildMergedRaw(dimension, lowerBound, upperBound) {
|
|
|
519
483
|
return "@media()";
|
|
520
484
|
}
|
|
521
485
|
function sortTerms(terms) {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
return idA.localeCompare(idB);
|
|
526
|
-
});
|
|
486
|
+
const withIds = terms.map((t) => [getConditionUniqueId(t), t]);
|
|
487
|
+
withIds.sort((a, b) => a[0].localeCompare(b[0]));
|
|
488
|
+
return withIds.map(([, t]) => t);
|
|
527
489
|
}
|
|
528
490
|
/**
|
|
529
|
-
* Apply absorption law
|
|
491
|
+
* Apply the absorption law: removes compound terms that are absorbed by
|
|
492
|
+
* a simple term already present.
|
|
493
|
+
*
|
|
494
|
+
* For AND context: A & (A | B) → A (absorbs OR compounds)
|
|
495
|
+
* For OR context: A | (A & B) → A (absorbs AND compounds)
|
|
530
496
|
*/
|
|
531
|
-
function
|
|
497
|
+
function applyAbsorption(terms, absorbedOperator) {
|
|
532
498
|
const simpleIds = /* @__PURE__ */ new Set();
|
|
533
499
|
for (const term of terms) if (term.kind !== "compound") simpleIds.add(getConditionUniqueId(term));
|
|
534
500
|
return terms.filter((term) => {
|
|
535
|
-
if (term.kind === "compound" && term.operator ===
|
|
501
|
+
if (term.kind === "compound" && term.operator === absorbedOperator) {
|
|
536
502
|
for (const child of term.children) if (simpleIds.has(getConditionUniqueId(child))) return false;
|
|
537
503
|
}
|
|
538
504
|
return true;
|
|
539
505
|
});
|
|
540
506
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
507
|
+
function applyAbsorptionAnd(terms) {
|
|
508
|
+
return applyAbsorption(terms, "OR");
|
|
509
|
+
}
|
|
544
510
|
function applyAbsorptionOr(terms) {
|
|
545
|
-
|
|
546
|
-
for (const term of terms) if (term.kind !== "compound") simpleIds.add(getConditionUniqueId(term));
|
|
547
|
-
return terms.filter((term) => {
|
|
548
|
-
if (term.kind === "compound" && term.operator === "AND") {
|
|
549
|
-
for (const child of term.children) if (simpleIds.has(getConditionUniqueId(child))) return false;
|
|
550
|
-
}
|
|
551
|
-
return true;
|
|
552
|
-
});
|
|
511
|
+
return applyAbsorption(terms, "AND");
|
|
553
512
|
}
|
|
554
513
|
|
|
555
514
|
//#endregion
|