@tenphi/tasty 0.14.1 → 0.14.2
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 +24 -9
- package/dist/pipeline/index.js +21 -10
- package/dist/pipeline/index.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/adoption.md +1 -1
- package/docs/comparison.md +1 -1
- package/docs/dsl.md +40 -0
- package/docs/runtime.md +1 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,16 +17,27 @@
|
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
Tasty is a styling engine for design systems that generates deterministic CSS for stateful components.
|
|
20
|
+
Tasty is a styling engine for design systems that generates deterministic CSS for stateful components.
|
|
21
21
|
|
|
22
|
-
It
|
|
22
|
+
It compiles state maps into **mutually exclusive selectors**, so for a given property and component state, one branch wins by construction instead of competing through cascade and specificity.
|
|
23
|
+
|
|
24
|
+
That is the core guarantee: component styling resolves from declared state logic, not from source-order accidents or specificity fights.
|
|
25
|
+
|
|
26
|
+
Tasty fits best when you are building a design system or component library with intersecting states, variants, tokens, sub-elements, responsive rules, and extension semantics that need to stay predictable over time.
|
|
27
|
+
|
|
28
|
+
On top of that foundation, Tasty gives teams a governed styling model: a CSS-like DSL, tokens, recipes, typed style props, sub-elements, and multiple rendering modes.
|
|
29
|
+
|
|
30
|
+
- **New here?** Start with [Comparison](docs/comparison.md) if you are evaluating fit.
|
|
31
|
+
- **Adopting Tasty?** Read the [Adoption Guide](docs/adoption.md).
|
|
32
|
+
- **Want the mechanism first?** Jump to [How It Actually Works](#how-it-actually-works).
|
|
33
|
+
- **Ready to build?** Go to [Getting Started](docs/getting-started.md).
|
|
23
34
|
|
|
24
35
|
## Why Tasty
|
|
25
36
|
|
|
26
|
-
- **Deterministic composition, not cascade fights** —
|
|
27
|
-
- **Built for design-system teams** — Best fit for
|
|
28
|
-
- **A governed styling model, not just syntax sugar** —
|
|
29
|
-
- **DSL that still feels like CSS** —
|
|
37
|
+
- **Deterministic composition, not cascade fights** — Stateful styles resolve from the state map you declared, not from selector competition. See [How It Actually Works](#how-it-actually-works).
|
|
38
|
+
- **Built for design-system teams** — Best fit for reusable component systems with complex state interactions.
|
|
39
|
+
- **A governed styling model, not just syntax sugar** — Design-system authors define the styling language product teams consume.
|
|
40
|
+
- **DSL that still feels like CSS** — Familiar property names, less selector boilerplate. Start with the [Style DSL](docs/dsl.md), then use [Style Properties](docs/styles.md) as the handler reference.
|
|
30
41
|
|
|
31
42
|
### Supporting capabilities
|
|
32
43
|
|
|
@@ -40,7 +51,7 @@ It fits best when a team is defining a house styling language for reusable compo
|
|
|
40
51
|
|
|
41
52
|
Modern component styling becomes fragile when multiple selectors can still win for the same property. Hover, disabled, theme, breakpoint, parent state, and root state rules start competing through specificity and source order.
|
|
42
53
|
|
|
43
|
-
Tasty replaces that competition with explicit state-map resolution. Each property compiles into mutually exclusive branches, so
|
|
54
|
+
Tasty replaces that competition with explicit state-map resolution. Each property compiles into mutually exclusive branches, so component styling stays deterministic as systems grow. For the full mechanism, jump to [How It Actually Works](#how-it-actually-works).
|
|
44
55
|
|
|
45
56
|
## Installation
|
|
46
57
|
|
|
@@ -63,6 +74,8 @@ yarn add @tenphi/tasty
|
|
|
63
74
|
|
|
64
75
|
## Start Here
|
|
65
76
|
|
|
77
|
+
For the fuller docs map beyond the quick routes above, start here:
|
|
78
|
+
|
|
66
79
|
- **[Comparison](docs/comparison.md)** — read this first if you are evaluating whether Tasty fits your team's styling model
|
|
67
80
|
- **[Adoption Guide](docs/adoption.md)** — understand who Tasty is for, where it fits, and how to introduce it incrementally
|
|
68
81
|
- **[Getting Started](docs/getting-started.md)** — the canonical onboarding path: install, first component, optional shared `configure()`, ESLint, editor tooling, and rendering mode selection
|
|
@@ -95,7 +108,9 @@ const Card = tasty({
|
|
|
95
108
|
<Card>Hello World</Card>
|
|
96
109
|
```
|
|
97
110
|
|
|
98
|
-
Every value maps to CSS you'd recognize. This example is intentionally
|
|
111
|
+
Every value maps to CSS you'd recognize. This example is intentionally a simple first contact, not a tour of the whole DSL.
|
|
112
|
+
|
|
113
|
+
When you want a more design-system-shaped authoring model, Tasty also supports built-in units, tokens, recipes, state aliases, and color values such as `okhsl(...)` without extra runtime libraries.
|
|
99
114
|
|
|
100
115
|
Use `configure()` when you want to define shared tokens, state aliases, recipes, or other conventions for your app or design system. For a fuller onboarding path, follow [Getting Started](docs/getting-started.md).
|
|
101
116
|
|
|
@@ -214,7 +229,7 @@ The same props also support state maps, so responsive values use the exact same
|
|
|
214
229
|
</Space>
|
|
215
230
|
```
|
|
216
231
|
|
|
217
|
-
Layout components can expose flow props. Buttons can expose positioning props. Each component can offer only the style props that make sense for its role
|
|
232
|
+
Layout components can expose flow props. Buttons can expose positioning props. Each component can offer only the style props that make sense for its role while keeping tokens, custom units, and state maps fully typed. This works in runtime `tasty()` components, not in `tastyStatic()`.
|
|
218
233
|
|
|
219
234
|
## Choose a Styling Approach
|
|
220
235
|
|
package/dist/pipeline/index.js
CHANGED
|
@@ -170,7 +170,8 @@ function getAllSelectors(key, styles) {
|
|
|
170
170
|
* Supports:
|
|
171
171
|
* - Direct child: '>'
|
|
172
172
|
* - Chained elements: '>Body>Row>'
|
|
173
|
-
* - HTML tags: '
|
|
173
|
+
* - HTML tags (no key injection): 'h1', '>ul>li', 'button:hover'
|
|
174
|
+
* - Universal selector: '*', 'h1 *'
|
|
174
175
|
* - Pseudo-elements on root: '::before'
|
|
175
176
|
* - Pseudo on sub-element: '@::before', '>@:hover'
|
|
176
177
|
* - Classes: '.active', '>@.active'
|
|
@@ -207,6 +208,7 @@ function processAffix(affix, key) {
|
|
|
207
208
|
*/
|
|
208
209
|
const VALID_TOKEN_PATTERNS = [
|
|
209
210
|
/^[>+~]/,
|
|
211
|
+
/^\*/,
|
|
210
212
|
/^[A-Z][a-zA-Z0-9]*/,
|
|
211
213
|
/^@/,
|
|
212
214
|
/^::?[a-z][a-z0-9-]*(?:\([^)]*\))?/,
|
|
@@ -341,12 +343,13 @@ function processSinglePattern(pattern, key) {
|
|
|
341
343
|
* The tokenizer handles these token types in order:
|
|
342
344
|
* 1. Whitespace (skipped)
|
|
343
345
|
* 2. Combinators: >, +, ~ (add surrounding spaces)
|
|
344
|
-
* 3.
|
|
345
|
-
* 4.
|
|
346
|
-
* 5.
|
|
347
|
-
* 6.
|
|
348
|
-
* 7.
|
|
349
|
-
* 8.
|
|
346
|
+
* 3. Universal selector: * (keep as-is with spacing)
|
|
347
|
+
* 4. Uppercase names: Body, Row (convert to [data-element="..."])
|
|
348
|
+
* 5. @ placeholder (keep for later replacement)
|
|
349
|
+
* 6. Pseudo: :hover, ::before (attach to previous token)
|
|
350
|
+
* 7. Tags: a, div, button (keep as-is with spacing)
|
|
351
|
+
* 8. Classes: .active (attach to previous element/tag/placeholder)
|
|
352
|
+
* 9. Attributes: [type="text"] (keep as-is)
|
|
350
353
|
*
|
|
351
354
|
* @param pattern - The raw selector pattern to transform
|
|
352
355
|
* @returns Transformed pattern with proper CSS selector syntax
|
|
@@ -375,6 +378,13 @@ function transformPattern(pattern) {
|
|
|
375
378
|
i++;
|
|
376
379
|
continue;
|
|
377
380
|
}
|
|
381
|
+
if (char === "*") {
|
|
382
|
+
if (result && lastCh !== " ") result += " ";
|
|
383
|
+
result += "*";
|
|
384
|
+
lastCh = "*";
|
|
385
|
+
i++;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
378
388
|
if (/[A-Z]/.test(char)) {
|
|
379
389
|
const nameStart = i;
|
|
380
390
|
while (i < pattern.length && /[a-zA-Z0-9]/.test(pattern[i])) i++;
|
|
@@ -449,7 +459,8 @@ function transformPattern(pattern) {
|
|
|
449
459
|
* |----------------|-------------|---------|--------|
|
|
450
460
|
* | Combinator (>, +, ~) | Yes | `'>Body>'` | `> [data-element="Body"] > [el]` |
|
|
451
461
|
* | Uppercase element | Yes | `'>Body>Row'` | `> [el1] > [el2] [key]` |
|
|
452
|
-
* | Lowercase tag |
|
|
462
|
+
* | Lowercase tag | No | `'h1'` | ` h1` |
|
|
463
|
+
* | Universal (*) | No | `'h1 *'` | ` h1 *` |
|
|
453
464
|
* | Pseudo (:hover, ::before) | No | `'::before'` | `::before` |
|
|
454
465
|
* | Class (.active) | No | `'.active'` | `.active` |
|
|
455
466
|
* | Attribute ([type]) | No | `'[type="text"]'` | `[type="text"]` |
|
|
@@ -460,7 +471,8 @@ function transformPattern(pattern) {
|
|
|
460
471
|
* @example
|
|
461
472
|
* shouldInjectKey('>') // → true (trailing combinator)
|
|
462
473
|
* shouldInjectKey('>Body>Row') // → true (ends with element)
|
|
463
|
-
* shouldInjectKey('
|
|
474
|
+
* shouldInjectKey('h1') // → false (ends with tag)
|
|
475
|
+
* shouldInjectKey('*') // → false (universal selector)
|
|
464
476
|
* shouldInjectKey('::before') // → false (ends with pseudo)
|
|
465
477
|
* shouldInjectKey('.active') // → false (ends with class)
|
|
466
478
|
* shouldInjectKey('a:hover') // → false (ends with pseudo)
|
|
@@ -470,7 +482,6 @@ function shouldInjectKey(pattern) {
|
|
|
470
482
|
const trimmed = pattern.trim();
|
|
471
483
|
if (/[>+~]$/.test(trimmed)) return true;
|
|
472
484
|
if (/(?:^|[\s>+~\]:])[A-Z][a-zA-Z0-9]*$/.test(trimmed)) return true;
|
|
473
|
-
if (/(?<![:.])(?:^|[\s>+~])[a-z][a-z0-9-]*$/.test(trimmed)) return true;
|
|
474
485
|
return false;
|
|
475
486
|
}
|
|
476
487
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/pipeline/index.ts"],"sourcesContent":["/**\n * Tasty Style Rendering Pipeline\n *\n * This is the main entrypoint for the new pipeline implementation.\n * It implements the complete flow from style objects to CSS rules.\n *\n * Pipeline stages:\n * 1. PARSE CONDITIONS - Parse state keys into ConditionNode trees\n * 2. BUILD EXCLUSIVE CONDITIONS - AND with negation of higher-priority conditions\n * 3. SIMPLIFY CONDITIONS - Apply boolean algebra, detect contradictions\n * 4. GROUP BY HANDLER - Collect styles per handler, compute combinations\n * 5. COMPUTE CSS VALUES - Call handlers to get CSS declarations\n * 6. MERGE BY VALUE - Merge rules with identical CSS output\n * 7. MATERIALIZE CSS - Convert conditions to CSS selectors + at-rules\n */\n\nimport { Lru } from '../parser/lru';\nimport type { StateParserContext } from '../states';\nimport {\n createStateParserContext,\n extractLocalPredefinedStates,\n} from '../states';\nimport { createStyle, STYLE_HANDLER_MAP } from '../styles';\nimport type { Styles } from '../styles/types';\nimport type {\n StyleHandler,\n StyleMap,\n StyleValue,\n StyleValueStateMap,\n} from '../utils/styles';\nimport { stringifyStyles } from '../utils/styles';\n\nimport type { ConditionNode } from './conditions';\nimport { and, or, trueCondition } from './conditions';\nimport type { ExclusiveStyleEntry } from './exclusive';\nimport {\n buildExclusiveConditions,\n expandExclusiveOrs,\n expandOrConditions,\n isValueMapping,\n parseStyleEntries,\n} from './exclusive';\nimport type { CSSRule, SelectorVariant } from './materialize';\nimport {\n branchToCSS,\n buildAtRulesFromVariant,\n conditionToCSS,\n mergeVariantsIntoSelectorGroups,\n optimizeGroups,\n parentGroupsToCSS,\n rootGroupsToCSS,\n selectorGroupToCSS,\n} from './materialize';\nimport { parseStateKey } from './parseStateKey';\nimport { simplifyCondition } from './simplify';\nimport { emitWarning } from './warnings';\n\n// ============================================================================\n// Types (compatible with old renderStyles API)\n// ============================================================================\n\n/**\n * Matches the old StyleResult interface for backward compatibility\n */\nexport interface StyleResult {\n selector: string;\n declarations: string;\n atRules?: string[];\n needsClassName?: boolean;\n rootPrefix?: string;\n}\n\n/**\n * Matches the old RenderResult interface for backward compatibility\n */\nexport interface RenderResult {\n rules: StyleResult[];\n className?: string;\n}\n\nexport interface PipelineResult {\n rules: CSSRule[];\n className?: string;\n}\n\ninterface ComputedRule {\n condition: ConditionNode;\n declarations: Record<string, string>;\n selectorSuffix: string;\n}\n\n// ============================================================================\n// Caching\n// ============================================================================\n\nconst pipelineCache = new Lru<string, CSSRule[]>(5000);\n\n// ============================================================================\n// Main Pipeline Function\n// ============================================================================\n\n/**\n * Render styles using the new pipeline.\n *\n * This is the main entrypoint that implements the complete flow.\n */\nexport function renderStylesPipeline(\n styles?: Styles,\n className?: string,\n pipelineCacheKey?: string,\n): PipelineResult {\n if (!styles) {\n return { rules: [], className };\n }\n\n // Use pre-computed cache key when available, falling back to stringifyStyles\n const cacheKey = pipelineCacheKey || stringifyStyles(styles);\n let rules = pipelineCache.get(cacheKey);\n\n if (!rules) {\n // Create parser context\n const parserContext = createStateParserContext(styles);\n\n // Run pipeline\n rules = runPipeline(styles, parserContext);\n\n // Cache result\n pipelineCache.set(cacheKey, rules);\n }\n\n // If no className, rules need it to be prepended later\n if (!className) {\n return {\n rules: rules.map((r) => ({\n ...r,\n needsClassName: true,\n })),\n };\n }\n\n // Prepend className to selectors\n const finalRules = rules.map((rule) => {\n // Parse the selector to find where to insert className\n let selector = rule.selector;\n\n // If selector starts with :root, insert className after the :root part\n if (rule.rootPrefix) {\n selector = `${rule.rootPrefix} .${className}.${className}${selector}`;\n } else {\n selector = `.${className}.${className}${selector}`;\n }\n\n return {\n ...rule,\n selector,\n };\n });\n\n return {\n rules: finalRules,\n className,\n };\n}\n\n/**\n * Check if a cache key exists in the pipeline cache.\n * Used by renderStylesForChunk to avoid building filtered styles on cache hit.\n */\nexport function hasPipelineCacheEntry(cacheKey: string): boolean {\n return pipelineCache.get(cacheKey) !== undefined;\n}\n\n/**\n * Clear the pipeline cache (for testing)\n */\nexport function clearPipelineCache(): void {\n pipelineCache.clear();\n}\n\n// ============================================================================\n// Pipeline Implementation\n// ============================================================================\n\nfunction runPipeline(\n styles: Styles,\n parserContext: StateParserContext,\n): CSSRule[] {\n const allRules: CSSRule[] = [];\n\n // Process styles recursively (including nested selectors)\n processStyles(styles, '', parserContext, allRules);\n\n // Deduplicate rules\n const seen = new Set<string>();\n const dedupedRules = allRules.filter((rule) => {\n // Include rootPrefix in dedup key - rules with different root prefixes are distinct\n const key = `${rule.selector}|${rule.declarations}|${rule.atRules?.join('|') ?? ''}|${rule.rootPrefix || ''}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n return dedupedRules;\n}\n\n/**\n * Process styles at a given nesting level\n */\nfunction processStyles(\n styles: Styles,\n selectorSuffix: string,\n parserContext: StateParserContext,\n allRules: CSSRule[],\n): void {\n const keys = Object.keys(styles);\n\n // Separate selector keys from style keys\n // Skip @keyframes (processed separately) and other @ prefixed keys (predefined states)\n const selectorKeys = keys.filter((key) => isSelector(key));\n const styleKeys = keys.filter(\n (key) => !isSelector(key) && !key.startsWith('@'),\n );\n\n // Process nested selectors first\n for (const key of selectorKeys) {\n const nestedStyles = styles[key] as Styles;\n if (!nestedStyles || typeof nestedStyles !== 'object') continue;\n\n // Get all selectors (handles comma-separated patterns)\n const suffixes = getAllSelectors(key, nestedStyles);\n if (!suffixes) continue; // Invalid selector, skip\n\n // Remove $ from nested styles\n const { $: _$, ...cleanedStyles } = nestedStyles;\n\n // Extract local predefined states scoped to this sub-element\n const subLocalStates = extractLocalPredefinedStates(cleanedStyles);\n const hasSubStates = Object.keys(subLocalStates).length > 0;\n const subContext: StateParserContext = {\n ...parserContext,\n isSubElement: true,\n localPredefinedStates: hasSubStates\n ? { ...parserContext.localPredefinedStates, ...subLocalStates }\n : parserContext.localPredefinedStates,\n };\n\n // Process for each selector (multiple selectors = same styles applied to each)\n for (const suffix of suffixes) {\n processStyles(\n cleanedStyles,\n selectorSuffix + suffix,\n subContext,\n allRules,\n );\n }\n }\n\n // Build handler queue\n const handlerQueue = buildHandlerQueue(styleKeys, styles);\n\n // Process each handler\n for (const { handler, styleMap } of handlerQueue) {\n const lookupStyles = handler.__lookupStyles;\n\n // Stage 1 & 2: Parse and build exclusive conditions for each style\n // Exclusive conditions ensure each CSS rule applies to exactly one state.\n // OR conditions in exclusives are properly expanded to DNF (multiple CSS selectors).\n const exclusiveByStyle = new Map<string, ExclusiveStyleEntry[]>();\n\n for (const styleName of lookupStyles) {\n const value = styleMap[styleName];\n if (value === undefined) continue;\n\n if (isValueMapping(value)) {\n // Parse entries from value mapping\n const parsed = parseStyleEntries(styleName, value, (stateKey) =>\n parseStateKey(stateKey, { context: parserContext }),\n );\n\n // Expand OR conditions into exclusive branches\n // This ensures OR branches like `A | B | C` become:\n // A, B & !A, C & !A & !B\n const expanded = expandOrConditions(parsed);\n\n // Build exclusive conditions across all entries\n const exclusive = buildExclusiveConditions(expanded);\n\n // Expand ORs from De Morgan negation into exclusive branches\n // This transforms: !A | !B → !A, (A & !B)\n // Ensures each CSS rule has proper at-rule context\n const fullyExpanded = expandExclusiveOrs(exclusive);\n exclusiveByStyle.set(styleName, fullyExpanded);\n } else {\n // Simple value - single entry with TRUE condition\n exclusiveByStyle.set(styleName, [\n {\n styleKey: styleName,\n stateKey: '',\n value,\n condition: trueCondition(),\n priority: 0,\n exclusiveCondition: trueCondition(),\n },\n ]);\n }\n }\n\n // Stage 4: Compute all valid state combinations\n const stateSnapshots = computeStateCombinations(\n exclusiveByStyle,\n lookupStyles,\n );\n\n // Stage 5: Call handler for each snapshot\n const computedRules: ComputedRule[] = [];\n\n for (const snapshot of stateSnapshots) {\n const result = handler(snapshot.values as StyleValueStateMap);\n if (!result) continue;\n\n // Handler may return single or array\n const results = Array.isArray(result) ? result : [result];\n\n for (const r of results) {\n if (!r || typeof r !== 'object') continue;\n\n const { $, ...styleProps } = r;\n const declarations: Record<string, string> = {};\n\n for (const [prop, val] of Object.entries(styleProps)) {\n if (val != null && val !== '') {\n declarations[prop] = String(val);\n }\n }\n\n if (Object.keys(declarations).length === 0) continue;\n\n // Handle $ suffixes\n const suffixes = $\n ? (Array.isArray($) ? $ : [$]).map(\n (s) => selectorSuffix + normalizeSelectorSuffix(String(s)),\n )\n : [selectorSuffix];\n\n for (const suffix of suffixes) {\n computedRules.push({\n condition: snapshot.condition,\n declarations,\n selectorSuffix: suffix,\n });\n }\n }\n }\n\n // Stage 6: Merge rules with identical CSS output\n const mergedRules = mergeByValue(computedRules);\n\n // Stage 7: Materialize to CSS\n for (const rule of mergedRules) {\n const cssRules = materializeComputedRule(rule);\n allRules.push(...cssRules);\n }\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Check if a key is a CSS selector\n */\nexport function isSelector(key: string): boolean {\n return key.startsWith('&') || key.startsWith('.') || /^[A-Z]/.test(key);\n}\n\n/**\n * Result of processing a selector affix ($) pattern.\n *\n * @example\n * // Valid result with multiple selectors\n * { valid: true, selectors: ['> [data-element=\"Cell\"]', ' [data-element=\"Body\"] > [data-element=\"Cell\"]'] }\n *\n * // Invalid result with error message\n * { valid: false, reason: 'Selector affix \"+\" targets elements outside the root scope.' }\n */\ntype AffixResult =\n | { valid: true; selectors: string[] }\n | { valid: false; reason: string };\n\n/**\n * Get all selector suffixes for a sub-element key.\n *\n * Handles three types of selector keys:\n * - `&` prefix: Raw selector suffix (e.g., `&:hover` → `:hover`)\n * - `.` prefix: Class selector (e.g., `.active` → ` .active`)\n * - Uppercase: Sub-element with optional `$` affix pattern\n *\n * @param key - The sub-element key (e.g., 'Label', '&:hover', '.active')\n * @param styles - The styles object, may contain `$` property for selector affix\n * @returns Array of selector suffixes, or null if invalid (with console warning)\n *\n * @example\n * getAllSelectors('Label', {})\n * // → [' [data-element=\"Label\"]']\n *\n * getAllSelectors('Cell', { $: '>, >Body>' })\n * // → ['> [data-element=\"Cell\"]', ' [data-element=\"Body\"] > [data-element=\"Cell\"]']\n */\nfunction getAllSelectors(key: string, styles?: Styles): string[] | null {\n if (key.startsWith('&')) {\n return [key.slice(1)];\n }\n\n if (key.startsWith('.')) {\n return [` ${key}`];\n }\n\n if (/^[A-Z]/.test(key)) {\n const affix = styles?.$;\n if (affix !== undefined) {\n const result = processAffix(String(affix), key);\n if (!result.valid) {\n emitWarning('INVALID_SELECTOR_AFFIX', result.reason);\n return null;\n }\n return result.selectors;\n }\n return [` [data-element=\"${key}\"]`];\n }\n\n return null;\n}\n\n/**\n * Process selector affix pattern and return selector(s)\n *\n * Supports:\n * - Direct child: '>'\n * - Chained elements: '>Body>Row>'\n * - HTML tags: 'a', '>ul>li', 'button:hover'\n * - Pseudo-elements on root: '::before'\n * - Pseudo on sub-element: '@::before', '>@:hover'\n * - Classes: '.active', '>@.active'\n * - Multiple selectors: '>, >Body>'\n * - Sibling combinators (after element): '>Item+', '>Item~'\n */\nfunction processAffix(affix: string, key: string): AffixResult {\n const trimmed = affix.trim();\n\n // Empty = default behavior (descendant selector with key)\n if (!trimmed) {\n return { valid: true, selectors: [` [data-element=\"${key}\"]`] };\n }\n\n // Split by comma for multiple selectors\n const patterns = trimmed.split(',').map((p) => p.trim());\n const selectors: string[] = [];\n\n for (const pattern of patterns) {\n const validation = validatePattern(pattern);\n if (!validation.valid) {\n return validation;\n }\n\n const selector = processSinglePattern(pattern, key);\n selectors.push(selector);\n }\n\n return { valid: true, selectors };\n}\n\n/**\n * Recognized token patterns for selector affix validation.\n *\n * These patterns are used to tokenize and validate `$` affix strings.\n * Order matters: more specific patterns must come first to avoid\n * partial matches (e.g., `::before` must match before `:` alone).\n *\n * Unrecognized tokens (like `#id`, `*`, or numbers) will cause validation to fail.\n */\nconst VALID_TOKEN_PATTERNS = [\n /^[>+~]/, // Combinators: >, +, ~\n /^[A-Z][a-zA-Z0-9]*/, // Uppercase element names → [data-element=\"...\"]\n /^@/, // @ placeholder for key injection position\n /^::?[a-z][a-z0-9-]*(?:\\([^)]*\\))?/, // Pseudo-elements/classes (:hover, ::before, :not(.x))\n /^\\.[a-zA-Z_-][a-zA-Z0-9_-]*/, // Class selectors (.active, .is-open)\n /^\\[[^\\]]+\\]/, // Attribute selectors ([type=\"text\"], [role])\n /^[a-z][a-z0-9-]*/, // HTML tag names (a, div, button, my-component)\n /^\\s+/, // Whitespace (ignored during parsing)\n /^&/, // Root reference (stripped, kept for backward compat)\n];\n\n/**\n * Scan a pattern for unrecognized tokens.\n *\n * Iterates through the pattern, consuming recognized tokens until\n * either the pattern is fully consumed (valid) or an unrecognized\n * character sequence is found (invalid).\n *\n * @param pattern - The selector pattern to validate\n * @returns The first unrecognized token found, or null if all tokens are valid\n *\n * @example\n * findUnrecognizedTokens('>Body>Row>') // → null (valid)\n * findUnrecognizedTokens('123') // → '123' (invalid)\n * findUnrecognizedTokens('#myId') // → '#' (invalid)\n */\nfunction findUnrecognizedTokens(pattern: string): string | null {\n let remaining = pattern;\n\n while (remaining.length > 0) {\n let matched = false;\n\n for (const regex of VALID_TOKEN_PATTERNS) {\n const match = remaining.match(regex);\n if (match) {\n remaining = remaining.slice(match[0].length);\n matched = true;\n break;\n }\n }\n\n if (!matched) {\n // Found unrecognized content - extract the problematic part\n const unrecognized = remaining.match(/^[^\\s>+~@.:[\\]A-Z]+/);\n return unrecognized ? unrecognized[0] : remaining[0];\n }\n }\n\n return null;\n}\n\n/**\n * Validate a selector pattern for structural correctness.\n *\n * Checks for:\n * 1. Out-of-scope selectors: Patterns starting with `+` or `~` target siblings\n * of the root element, which is outside the component's DOM scope.\n * 2. Consecutive combinators: Patterns like `>>` or `>+` are malformed CSS.\n * 3. Unrecognized tokens: Characters/sequences not matching valid CSS selectors.\n *\n * @param pattern - A single selector pattern (already split by comma)\n * @returns AffixResult indicating validity and error reason if invalid\n *\n * @example\n * validatePattern('>Body>Row>') // → { valid: true, selectors: [] }\n * validatePattern('+') // → { valid: false, reason: '...outside root scope...' }\n * validatePattern('>>') // → { valid: false, reason: '...consecutive combinators...' }\n */\nfunction validatePattern(pattern: string): AffixResult {\n const trimmed = pattern.trim();\n\n // Patterns starting with + or ~ target siblings of the root element,\n // which is outside the component's scope. Valid sibling patterns must\n // be preceded by an element: \">Item+\", \">Item~\"\n if (/^[+~]/.test(trimmed)) {\n return {\n valid: false,\n reason:\n `Selector affix \"${pattern}\" targets elements outside the root scope. ` +\n `Sibling selectors (+, ~) must be preceded by an element inside the root. ` +\n `Use \">Element+\" or \">Element~\" instead.`,\n };\n }\n\n // Check for consecutive combinators\n if (/[>+~]{2,}/.test(trimmed.replace(/\\s+/g, ''))) {\n return {\n valid: false,\n reason: `Selector affix \"${pattern}\" contains consecutive combinators.`,\n };\n }\n\n // Check for unrecognized tokens (e.g., lowercase text like \"foo\")\n const unrecognized = findUnrecognizedTokens(trimmed);\n if (unrecognized) {\n return {\n valid: false,\n reason:\n `Selector affix \"${pattern}\" contains unrecognized token \"${unrecognized}\". ` +\n `Valid tokens: combinators (>, +, ~), element names (Uppercase), ` +\n `@ placeholder, pseudo (:hover, ::before), class (.name), attribute ([attr]).`,\n };\n }\n\n return { valid: true, selectors: [] };\n}\n\n/**\n * Process a single selector pattern into a CSS selector suffix.\n *\n * This is the main transformation function that converts a `$` affix pattern\n * into a valid CSS selector suffix. It handles:\n *\n * 1. `@` placeholder replacement with `[data-element=\"key\"]`\n * 2. Key injection based on pattern ending (see `shouldInjectKey`)\n * 3. Proper spacing for descendant vs direct child selectors\n *\n * @param pattern - A single validated selector pattern\n * @param key - The sub-element key to inject (e.g., 'Label', 'Cell')\n * @returns CSS selector suffix ready to append to the root selector\n *\n * @example\n * processSinglePattern('>', 'Row')\n * // → '> [data-element=\"Row\"]'\n *\n * processSinglePattern('>Body>Row>', 'Cell')\n * // → '> [data-element=\"Body\"] > [data-element=\"Row\"] > [data-element=\"Cell\"]'\n *\n * processSinglePattern('::before', 'Before')\n * // → '::before' (no key injection for pseudo on root)\n *\n * processSinglePattern('>@:hover', 'Item')\n * // → '> [data-element=\"Item\"]:hover'\n */\nfunction processSinglePattern(pattern: string, key: string): string {\n // Strip leading & if present (implicit root reference, kept for compat)\n const normalized = pattern.replace(/^&/, '').trim();\n\n if (!normalized) {\n return ` [data-element=\"${key}\"]`;\n }\n\n // Pseudo-elements/classes at start apply directly to root (no space prefix)\n const startsWithPseudo = /^::?[a-z]/.test(normalized);\n\n // Transform the pattern: convert element names and normalize spacing\n let result = transformPattern(normalized);\n\n // Handle @ placeholder: explicit key injection position\n if (result.includes('@')) {\n // Remove space between @ and following class/pseudo for proper attachment\n // e.g., \"@ .active\" → \"[el].active\", but \"@ > span\" → \"[el] > span\"\n result = result.replace(/@ (?=[.:])/g, '@');\n result = result.replace(/@/g, `[data-element=\"${key}\"]`);\n\n if (!startsWithPseudo && !result.startsWith(' ')) {\n result = ' ' + result;\n }\n return result;\n }\n\n // Auto-inject key based on pattern ending (see shouldInjectKey for rules)\n if (shouldInjectKey(normalized)) {\n result = result + ' ' + `[data-element=\"${key}\"]`;\n }\n\n // Add space prefix for selectors targeting inside root (not pseudo on root)\n if (!startsWithPseudo && !result.startsWith(' ')) {\n result = ' ' + result;\n }\n\n return result;\n}\n\n/**\n * Transform a selector pattern by converting element names and normalizing spacing.\n *\n * This is a character-by-character tokenizer that:\n * - Converts uppercase names to `[data-element=\"Name\"]` selectors\n * - Adds proper spacing around combinators (>, +, ~)\n * - Preserves lowercase tags, classes, pseudos, and attributes as-is\n * - Keeps @ placeholder for later replacement\n *\n * The tokenizer handles these token types in order:\n * 1. Whitespace (skipped)\n * 2. Combinators: >, +, ~ (add surrounding spaces)\n * 3. Uppercase names: Body, Row (convert to [data-element=\"...\"])\n * 4. @ placeholder (keep for later replacement)\n * 5. Pseudo: :hover, ::before (attach to previous token)\n * 6. Tags: a, div, button (keep as-is with spacing)\n * 7. Classes: .active (attach to previous element/tag/placeholder)\n * 8. Attributes: [type=\"text\"] (keep as-is)\n *\n * @param pattern - The raw selector pattern to transform\n * @returns Transformed pattern with proper CSS selector syntax\n *\n * @example\n * transformPattern('>Body>Row>')\n * // → '> [data-element=\"Body\"] > [data-element=\"Row\"] >'\n *\n * transformPattern('button.primary:hover')\n * // → 'button.primary:hover'\n */\nfunction transformPattern(pattern: string): string {\n let result = '';\n let lastCh = '';\n let i = 0;\n\n while (i < pattern.length) {\n const char = pattern[i];\n\n if (/\\s/.test(char)) {\n i++;\n continue;\n }\n\n if (/[>+~]/.test(char)) {\n if (result && lastCh !== ' ') {\n result += ' ';\n }\n result += char;\n lastCh = char;\n i++;\n continue;\n }\n\n if (/[A-Z]/.test(char)) {\n const nameStart = i;\n while (i < pattern.length && /[a-zA-Z0-9]/.test(pattern[i])) {\n i++;\n }\n if (result && lastCh !== ' ') {\n result += ' ';\n }\n const segment = `[data-element=\"${pattern.slice(nameStart, i)}\"]`;\n result += segment;\n lastCh = ']';\n continue;\n }\n\n if (char === '@') {\n if (result && lastCh !== ' ') {\n result += ' ';\n }\n result += '@';\n lastCh = '@';\n i++;\n continue;\n }\n\n if (char === ':') {\n const pseudoStart = i;\n while (\n i < pattern.length &&\n !/[\\s>+~,@]/.test(pattern[i]) &&\n !/[A-Z]/.test(pattern[i])\n ) {\n i++;\n }\n const segment = pattern.slice(pseudoStart, i);\n result += segment;\n lastCh = segment[segment.length - 1] || lastCh;\n continue;\n }\n\n if (/[a-z]/.test(char)) {\n const tagStart = i;\n while (i < pattern.length && /[a-z0-9-]/.test(pattern[i])) {\n i++;\n }\n if (result && lastCh !== ' ') {\n result += ' ';\n }\n const segment = pattern.slice(tagStart, i);\n result += segment;\n lastCh = segment[segment.length - 1] || lastCh;\n continue;\n }\n\n if (char === '.') {\n const attachToLast =\n lastCh === ']' || lastCh === '@' || /[a-zA-Z0-9-]/.test(lastCh);\n if (result && !attachToLast && lastCh !== ' ') {\n result += ' ';\n }\n const clsStart = i;\n i++;\n while (i < pattern.length && /[a-zA-Z0-9_-]/.test(pattern[i])) {\n i++;\n }\n const segment = pattern.slice(clsStart, i);\n result += segment;\n lastCh = segment[segment.length - 1] || lastCh;\n continue;\n }\n\n if (char === '[') {\n const attachToLast =\n lastCh === ']' || lastCh === '@' || /[a-zA-Z0-9-]/.test(lastCh);\n if (result && !attachToLast && lastCh !== ' ') {\n result += ' ';\n }\n const attrStart = i;\n let depth = 0;\n while (i < pattern.length) {\n if (pattern[i] === '[') depth++;\n if (pattern[i] === ']') depth--;\n i++;\n if (depth === 0) break;\n }\n result += pattern.slice(attrStart, i);\n lastCh = ']';\n continue;\n }\n\n result += char;\n lastCh = char;\n i++;\n }\n\n return result;\n}\n\n/**\n * Determine if the sub-element key should be auto-injected based on pattern ending.\n *\n * Key injection rules (when no @ placeholder is present):\n *\n * | Pattern Ending | Inject Key? | Example | Result |\n * |----------------|-------------|---------|--------|\n * | Combinator (>, +, ~) | Yes | `'>Body>'` | `> [data-element=\"Body\"] > [el]` |\n * | Uppercase element | Yes | `'>Body>Row'` | `> [el1] > [el2] [key]` |\n * | Lowercase tag | Yes | `'>ul>li'` | `> ul > li [key]` |\n * | Pseudo (:hover, ::before) | No | `'::before'` | `::before` |\n * | Class (.active) | No | `'.active'` | `.active` |\n * | Attribute ([type]) | No | `'[type=\"text\"]'` | `[type=\"text\"]` |\n *\n * @param pattern - The normalized pattern (after stripping &)\n * @returns true if key should be injected, false otherwise\n *\n * @example\n * shouldInjectKey('>') // → true (trailing combinator)\n * shouldInjectKey('>Body>Row') // → true (ends with element)\n * shouldInjectKey('>ul>li') // → true (ends with tag)\n * shouldInjectKey('::before') // → false (ends with pseudo)\n * shouldInjectKey('.active') // → false (ends with class)\n * shouldInjectKey('a:hover') // → false (ends with pseudo)\n * shouldInjectKey('button.primary') // → false (ends with class)\n */\nfunction shouldInjectKey(pattern: string): boolean {\n const trimmed = pattern.trim();\n\n // Rule 1: Ends with combinator → inject key after it\n // e.g., '>' → '> [data-element=\"Key\"]'\n if (/[>+~]$/.test(trimmed)) {\n return true;\n }\n\n // Rule 2: Ends with uppercase element name → inject key as descendant\n // The lookbehind ensures we're matching a standalone element name, not\n // part of a class like .myClass (where C is preceded by lowercase)\n // e.g., '>Body' → '> [data-element=\"Body\"] [data-element=\"Key\"]'\n if (/(?:^|[\\s>+~\\]:])[A-Z][a-zA-Z0-9]*$/.test(trimmed)) {\n return true;\n }\n\n // Rule 3: Ends with lowercase tag name → inject key as descendant\n // The negative lookbehind (?<![:.]) ensures we don't match:\n // - ':hover' (pseudo ending)\n // - '.primary' (class ending)\n // e.g., '>ul>li' → '> ul > li [data-element=\"Key\"]'\n if (/(?<![:.])(?:^|[\\s>+~])[a-z][a-z0-9-]*$/.test(trimmed)) {\n return true;\n }\n\n // Rule 4: Otherwise (pseudo, class, attribute) → no injection\n // The pattern is complete as-is, applying to root or a specific selector\n return false;\n}\n\n/**\n * Normalize selector suffix from $ property\n */\nfunction normalizeSelectorSuffix(suffix: string): string {\n if (!suffix) return '';\n return suffix.startsWith('&') ? suffix.slice(1) : suffix;\n}\n\n/**\n * Build handler queue from style keys\n */\nfunction buildHandlerQueue(\n styleKeys: string[],\n styles: Styles,\n): { handler: StyleHandler; styleMap: StyleMap }[] {\n const queue: { handler: StyleHandler; styleMap: StyleMap }[] = [];\n const seenHandlers = new Set<StyleHandler>();\n\n for (const styleName of styleKeys) {\n let handlers: StyleHandler[] = STYLE_HANDLER_MAP[styleName];\n\n if (!handlers) {\n handlers = STYLE_HANDLER_MAP[styleName] = [createStyle(styleName)];\n }\n\n for (const handler of handlers) {\n if (seenHandlers.has(handler)) continue;\n seenHandlers.add(handler);\n\n const lookupStyles = handler.__lookupStyles;\n const styleMap: StyleMap = {};\n\n for (const name of lookupStyles) {\n const val = styles[name];\n if (val !== undefined) {\n styleMap[name] = val as StyleValue | StyleValueStateMap;\n }\n }\n\n queue.push({ handler, styleMap });\n }\n }\n\n return queue;\n}\n\n/**\n * Compute all valid state combinations for a handler's lookup styles\n */\nfunction computeStateCombinations(\n exclusiveByStyle: Map<string, ExclusiveStyleEntry[]>,\n lookupStyles: string[],\n): { condition: ConditionNode; values: Record<string, StyleValue> }[] {\n // Get entries for each style\n const entriesPerStyle = lookupStyles.map(\n (style) => exclusiveByStyle.get(style) || [],\n );\n\n // Cartesian product of all combinations\n const combinations = cartesianProduct(entriesPerStyle);\n\n // Build snapshots, simplifying and filtering impossible combinations\n const snapshots: {\n condition: ConditionNode;\n values: Record<string, StyleValue>;\n }[] = [];\n\n for (const combo of combinations) {\n // Combine all exclusive conditions with AND\n const conditions = combo.map((e) => e.exclusiveCondition);\n const combined = and(...conditions);\n const simplified = simplifyCondition(combined);\n\n // Skip impossible combinations\n if (simplified.kind === 'false') continue;\n\n // Build values map\n const values: Record<string, StyleValue> = {};\n for (const entry of combo) {\n values[entry.styleKey] = entry.value;\n }\n\n snapshots.push({\n condition: simplified,\n values,\n });\n }\n\n return snapshots;\n}\n\n/**\n * Cartesian product of arrays\n */\nfunction cartesianProduct<T>(arrays: T[][]): T[][] {\n if (arrays.length === 0) return [[]];\n\n const nonEmpty = arrays.filter((a) => a.length > 0);\n if (nonEmpty.length === 0) return [[]];\n\n let result: T[][] = [[]];\n for (const arr of nonEmpty) {\n const next: T[][] = [];\n for (const combo of result) {\n for (const item of arr) {\n const newCombo = new Array<T>(combo.length + 1);\n for (let i = 0; i < combo.length; i++) newCombo[i] = combo[i];\n newCombo[combo.length] = item;\n next.push(newCombo);\n }\n }\n result = next;\n }\n return result;\n}\n\nconst declStringCache = new WeakMap<Record<string, string>, string>();\n\nfunction stringifyDeclarations(decl: Record<string, string>): string {\n let cached = declStringCache.get(decl);\n if (cached === undefined) {\n cached = JSON.stringify(decl);\n declStringCache.set(decl, cached);\n }\n return cached;\n}\n\n/**\n * Merge rules with identical CSS output\n */\nfunction mergeByValue(rules: ComputedRule[]): ComputedRule[] {\n const groups = new Map<string, ComputedRule[]>();\n\n for (const rule of rules) {\n const key = `${rule.selectorSuffix}|${stringifyDeclarations(rule.declarations)}`;\n if (!groups.has(key)) {\n groups.set(key, []);\n }\n groups.get(key)!.push(rule);\n }\n\n // Merge conditions with OR for each group\n const merged: ComputedRule[] = [];\n\n for (const [, groupRules] of groups) {\n if (groupRules.length === 1) {\n merged.push(groupRules[0]);\n } else {\n // Merge conditions with OR\n const mergedCondition = simplifyCondition(\n or(...groupRules.map((r) => r.condition)),\n );\n merged.push({\n condition: mergedCondition,\n declarations: groupRules[0].declarations,\n selectorSuffix: groupRules[0].selectorSuffix,\n });\n }\n }\n\n return merged;\n}\n\n/**\n * Build selector fragment from a variant (without className prefix)\n */\nfunction buildSelectorFromVariant(\n variant: SelectorVariant,\n selectorSuffix: string,\n): string {\n let selector = '';\n\n // Add flat modifier + pseudo selectors (sorted for canonical output)\n selector += branchToCSS([\n ...variant.modifierConditions,\n ...variant.pseudoConditions,\n ]);\n\n // Add selector groups (:is()/:not() on element)\n for (const group of variant.selectorGroups) {\n selector += selectorGroupToCSS(group);\n }\n\n // Add parent selectors (before sub-element suffix)\n if (variant.parentGroups.length > 0) {\n selector += parentGroupsToCSS(variant.parentGroups);\n }\n\n selector += selectorSuffix;\n\n // Add own groups (:is()/:not() on sub-element)\n const ownOptimized = optimizeGroups(variant.ownGroups);\n for (const group of ownOptimized) {\n selector += selectorGroupToCSS(group);\n }\n\n return selector;\n}\n\n/**\n * Materialize a computed rule to final CSS format\n *\n * Returns an array because OR conditions may generate multiple CSS rules\n * (when different branches have different at-rules)\n */\nfunction materializeComputedRule(rule: ComputedRule): CSSRule[] {\n const components = conditionToCSS(rule.condition);\n\n if (components.isImpossible || components.variants.length === 0) {\n return [];\n }\n\n const declarations = Object.entries(rule.declarations)\n .map(([prop, value]) => `${prop}: ${value};`)\n .join(' ');\n\n // Helper to get root prefix key for grouping\n const getRootPrefixKey = (variant: SelectorVariant): string => {\n return rootGroupsToCSS(variant.rootGroups) || '';\n };\n\n // Group variants by their at-rules (variants with same at-rules can be combined with commas)\n const byAtRules = new Map<\n string,\n { variants: SelectorVariant[]; atRules: string[]; rootPrefix?: string }\n >();\n\n for (const variant of components.variants) {\n const atRules = buildAtRulesFromVariant(variant);\n const key = atRules.sort().join('|||') + '###' + getRootPrefixKey(variant);\n\n const group = byAtRules.get(key);\n if (group) {\n group.variants.push(variant);\n } else {\n byAtRules.set(key, {\n variants: [variant],\n atRules,\n rootPrefix: rootGroupsToCSS(variant.rootGroups),\n });\n }\n }\n\n // Generate one CSSRule per at-rules group\n const rules: CSSRule[] = [];\n for (const [, group] of byAtRules) {\n // Merge variants that differ only in flat modifier/pseudo conditions\n // into :is() groups before building selector strings\n const mergedVariants = mergeVariantsIntoSelectorGroups(group.variants);\n\n // Build selector fragments for each variant (will be joined with className later)\n const selectorFragments = mergedVariants.map((v) =>\n buildSelectorFromVariant(v, rule.selectorSuffix),\n );\n\n // Store as array if multiple, string if single\n const selector =\n selectorFragments.length === 1 ? selectorFragments[0] : selectorFragments;\n\n const cssRule: CSSRule = {\n selector,\n declarations,\n };\n\n if (group.atRules.length > 0) {\n cssRule.atRules = group.atRules;\n }\n\n if (group.rootPrefix) {\n cssRule.rootPrefix = group.rootPrefix;\n }\n\n rules.push(cssRule);\n }\n\n return rules;\n}\n\n// ============================================================================\n// Public API: renderStyles (compatible with old API)\n// ============================================================================\n\n/**\n * Options for renderStyles when using direct selector mode.\n */\nexport interface RenderStylesOptions {\n /**\n * Whether to double the class selector for increased specificity.\n * When true, `.myClass` becomes `.myClass.myClass` for higher specificity.\n *\n * @default false - User-provided selectors are not doubled.\n *\n * Note: This only applies when a classNameOrSelector is provided.\n * When renderStyles returns RenderResult with needsClassName=true,\n * the injector handles doubling automatically.\n */\n doubleSelector?: boolean;\n}\n\n/**\n * Render styles to CSS rules.\n *\n * When called without classNameOrSelector, returns RenderResult with needsClassName=true.\n * When called with a selector/className string, returns StyleResult[] for direct injection.\n */\nexport function renderStyles(\n styles?: Styles,\n classNameOrSelector?: undefined,\n options?: undefined,\n pipelineCacheKey?: string,\n): RenderResult;\nexport function renderStyles(\n styles: Styles | undefined,\n classNameOrSelector: string,\n options?: RenderStylesOptions,\n): StyleResult[];\nexport function renderStyles(\n styles?: Styles,\n classNameOrSelector?: string,\n options?: RenderStylesOptions,\n pipelineCacheKey?: string,\n): RenderResult | StyleResult[] {\n // Check if we have a direct selector/className\n const directSelector = !!classNameOrSelector;\n\n // Check cache first when a pre-computed key is available.\n // This allows callers to skip building the styles object on cache hit.\n let rules: CSSRule[] | undefined;\n if (pipelineCacheKey) {\n rules = pipelineCache.get(pipelineCacheKey);\n }\n\n if (!rules && !styles) {\n return directSelector ? [] : { rules: [] };\n }\n\n // Use pre-computed cache key when available (from chunk path),\n // falling back to stringifyStyles for direct renderStyles() calls\n const cacheKey = pipelineCacheKey || stringifyStyles(styles!);\n if (!rules) {\n rules = pipelineCache.get(cacheKey);\n }\n\n if (!rules) {\n // styles is guaranteed non-null here: early return above handles (!rules && !styles)\n const parserContext = createStateParserContext(styles!);\n rules = runPipeline(styles!, parserContext);\n pipelineCache.set(cacheKey, rules);\n }\n\n // Direct selector/className mode: return StyleResult[] directly\n if (directSelector) {\n const shouldDouble = options?.doubleSelector ?? false;\n\n return rules.map((rule): StyleResult => {\n // Handle selector as array (OR conditions) or string\n const selectorParts = Array.isArray(rule.selector)\n ? rule.selector\n : rule.selector\n ? [rule.selector]\n : [''];\n\n const finalSelector = selectorParts\n .map((part) => {\n let sel = part\n ? `${classNameOrSelector}${part}`\n : classNameOrSelector;\n\n // Double class selector for increased specificity if requested\n // This is used when the caller explicitly wants higher specificity\n if (shouldDouble && sel.startsWith('.')) {\n const classMatch = sel.match(/^\\.[a-zA-Z_-][a-zA-Z0-9_-]*/);\n if (classMatch) {\n const baseClass = classMatch[0];\n sel = baseClass + sel;\n }\n }\n\n // Handle root prefix for this selector\n if (rule.rootPrefix) {\n sel = `${rule.rootPrefix} ${sel}`;\n }\n\n return sel;\n })\n .join(', ');\n\n const result: StyleResult = {\n selector: finalSelector,\n declarations: rule.declarations,\n };\n\n if (rule.atRules && rule.atRules.length > 0) {\n result.atRules = rule.atRules;\n }\n\n return result;\n });\n }\n\n // No className mode: return RenderResult with needsClassName flag\n // Normalize selector to string (join array with placeholder that injector will handle)\n return {\n rules: rules.map(\n (r): StyleResult => ({\n selector: Array.isArray(r.selector)\n ? r.selector.join('|||')\n : r.selector,\n declarations: r.declarations,\n atRules: r.atRules,\n needsClassName: true,\n rootPrefix: r.rootPrefix,\n }),\n ),\n };\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport type { ConditionNode } from './conditions';\nexport { and, or, not, trueCondition, falseCondition } from './conditions';\nexport { parseStateKey } from './parseStateKey';\nexport { simplifyCondition } from './simplify';\nexport { buildExclusiveConditions } from './exclusive';\nexport { conditionToCSS } from './materialize';\nexport type { CSSRule } from './materialize';\nexport { setWarningHandler, emitWarning } from './warnings';\nexport type {\n TastyWarning,\n TastyWarningCode,\n TastyWarningHandler,\n} from './warnings';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/pipeline/index.ts"],"sourcesContent":["/**\n * Tasty Style Rendering Pipeline\n *\n * This is the main entrypoint for the new pipeline implementation.\n * It implements the complete flow from style objects to CSS rules.\n *\n * Pipeline stages:\n * 1. PARSE CONDITIONS - Parse state keys into ConditionNode trees\n * 2. BUILD EXCLUSIVE CONDITIONS - AND with negation of higher-priority conditions\n * 3. SIMPLIFY CONDITIONS - Apply boolean algebra, detect contradictions\n * 4. GROUP BY HANDLER - Collect styles per handler, compute combinations\n * 5. COMPUTE CSS VALUES - Call handlers to get CSS declarations\n * 6. MERGE BY VALUE - Merge rules with identical CSS output\n * 7. MATERIALIZE CSS - Convert conditions to CSS selectors + at-rules\n */\n\nimport { Lru } from '../parser/lru';\nimport type { StateParserContext } from '../states';\nimport {\n createStateParserContext,\n extractLocalPredefinedStates,\n} from '../states';\nimport { createStyle, STYLE_HANDLER_MAP } from '../styles';\nimport type { Styles } from '../styles/types';\nimport type {\n StyleHandler,\n StyleMap,\n StyleValue,\n StyleValueStateMap,\n} from '../utils/styles';\nimport { stringifyStyles } from '../utils/styles';\n\nimport type { ConditionNode } from './conditions';\nimport { and, or, trueCondition } from './conditions';\nimport type { ExclusiveStyleEntry } from './exclusive';\nimport {\n buildExclusiveConditions,\n expandExclusiveOrs,\n expandOrConditions,\n isValueMapping,\n parseStyleEntries,\n} from './exclusive';\nimport type { CSSRule, SelectorVariant } from './materialize';\nimport {\n branchToCSS,\n buildAtRulesFromVariant,\n conditionToCSS,\n mergeVariantsIntoSelectorGroups,\n optimizeGroups,\n parentGroupsToCSS,\n rootGroupsToCSS,\n selectorGroupToCSS,\n} from './materialize';\nimport { parseStateKey } from './parseStateKey';\nimport { simplifyCondition } from './simplify';\nimport { emitWarning } from './warnings';\n\n// ============================================================================\n// Types (compatible with old renderStyles API)\n// ============================================================================\n\n/**\n * Matches the old StyleResult interface for backward compatibility\n */\nexport interface StyleResult {\n selector: string;\n declarations: string;\n atRules?: string[];\n needsClassName?: boolean;\n rootPrefix?: string;\n}\n\n/**\n * Matches the old RenderResult interface for backward compatibility\n */\nexport interface RenderResult {\n rules: StyleResult[];\n className?: string;\n}\n\nexport interface PipelineResult {\n rules: CSSRule[];\n className?: string;\n}\n\ninterface ComputedRule {\n condition: ConditionNode;\n declarations: Record<string, string>;\n selectorSuffix: string;\n}\n\n// ============================================================================\n// Caching\n// ============================================================================\n\nconst pipelineCache = new Lru<string, CSSRule[]>(5000);\n\n// ============================================================================\n// Main Pipeline Function\n// ============================================================================\n\n/**\n * Render styles using the new pipeline.\n *\n * This is the main entrypoint that implements the complete flow.\n */\nexport function renderStylesPipeline(\n styles?: Styles,\n className?: string,\n pipelineCacheKey?: string,\n): PipelineResult {\n if (!styles) {\n return { rules: [], className };\n }\n\n // Use pre-computed cache key when available, falling back to stringifyStyles\n const cacheKey = pipelineCacheKey || stringifyStyles(styles);\n let rules = pipelineCache.get(cacheKey);\n\n if (!rules) {\n // Create parser context\n const parserContext = createStateParserContext(styles);\n\n // Run pipeline\n rules = runPipeline(styles, parserContext);\n\n // Cache result\n pipelineCache.set(cacheKey, rules);\n }\n\n // If no className, rules need it to be prepended later\n if (!className) {\n return {\n rules: rules.map((r) => ({\n ...r,\n needsClassName: true,\n })),\n };\n }\n\n // Prepend className to selectors\n const finalRules = rules.map((rule) => {\n // Parse the selector to find where to insert className\n let selector = rule.selector;\n\n // If selector starts with :root, insert className after the :root part\n if (rule.rootPrefix) {\n selector = `${rule.rootPrefix} .${className}.${className}${selector}`;\n } else {\n selector = `.${className}.${className}${selector}`;\n }\n\n return {\n ...rule,\n selector,\n };\n });\n\n return {\n rules: finalRules,\n className,\n };\n}\n\n/**\n * Check if a cache key exists in the pipeline cache.\n * Used by renderStylesForChunk to avoid building filtered styles on cache hit.\n */\nexport function hasPipelineCacheEntry(cacheKey: string): boolean {\n return pipelineCache.get(cacheKey) !== undefined;\n}\n\n/**\n * Clear the pipeline cache (for testing)\n */\nexport function clearPipelineCache(): void {\n pipelineCache.clear();\n}\n\n// ============================================================================\n// Pipeline Implementation\n// ============================================================================\n\nfunction runPipeline(\n styles: Styles,\n parserContext: StateParserContext,\n): CSSRule[] {\n const allRules: CSSRule[] = [];\n\n // Process styles recursively (including nested selectors)\n processStyles(styles, '', parserContext, allRules);\n\n // Deduplicate rules\n const seen = new Set<string>();\n const dedupedRules = allRules.filter((rule) => {\n // Include rootPrefix in dedup key - rules with different root prefixes are distinct\n const key = `${rule.selector}|${rule.declarations}|${rule.atRules?.join('|') ?? ''}|${rule.rootPrefix || ''}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n return dedupedRules;\n}\n\n/**\n * Process styles at a given nesting level\n */\nfunction processStyles(\n styles: Styles,\n selectorSuffix: string,\n parserContext: StateParserContext,\n allRules: CSSRule[],\n): void {\n const keys = Object.keys(styles);\n\n // Separate selector keys from style keys\n // Skip @keyframes (processed separately) and other @ prefixed keys (predefined states)\n const selectorKeys = keys.filter((key) => isSelector(key));\n const styleKeys = keys.filter(\n (key) => !isSelector(key) && !key.startsWith('@'),\n );\n\n // Process nested selectors first\n for (const key of selectorKeys) {\n const nestedStyles = styles[key] as Styles;\n if (!nestedStyles || typeof nestedStyles !== 'object') continue;\n\n // Get all selectors (handles comma-separated patterns)\n const suffixes = getAllSelectors(key, nestedStyles);\n if (!suffixes) continue; // Invalid selector, skip\n\n // Remove $ from nested styles\n const { $: _$, ...cleanedStyles } = nestedStyles;\n\n // Extract local predefined states scoped to this sub-element\n const subLocalStates = extractLocalPredefinedStates(cleanedStyles);\n const hasSubStates = Object.keys(subLocalStates).length > 0;\n const subContext: StateParserContext = {\n ...parserContext,\n isSubElement: true,\n localPredefinedStates: hasSubStates\n ? { ...parserContext.localPredefinedStates, ...subLocalStates }\n : parserContext.localPredefinedStates,\n };\n\n // Process for each selector (multiple selectors = same styles applied to each)\n for (const suffix of suffixes) {\n processStyles(\n cleanedStyles,\n selectorSuffix + suffix,\n subContext,\n allRules,\n );\n }\n }\n\n // Build handler queue\n const handlerQueue = buildHandlerQueue(styleKeys, styles);\n\n // Process each handler\n for (const { handler, styleMap } of handlerQueue) {\n const lookupStyles = handler.__lookupStyles;\n\n // Stage 1 & 2: Parse and build exclusive conditions for each style\n // Exclusive conditions ensure each CSS rule applies to exactly one state.\n // OR conditions in exclusives are properly expanded to DNF (multiple CSS selectors).\n const exclusiveByStyle = new Map<string, ExclusiveStyleEntry[]>();\n\n for (const styleName of lookupStyles) {\n const value = styleMap[styleName];\n if (value === undefined) continue;\n\n if (isValueMapping(value)) {\n // Parse entries from value mapping\n const parsed = parseStyleEntries(styleName, value, (stateKey) =>\n parseStateKey(stateKey, { context: parserContext }),\n );\n\n // Expand OR conditions into exclusive branches\n // This ensures OR branches like `A | B | C` become:\n // A, B & !A, C & !A & !B\n const expanded = expandOrConditions(parsed);\n\n // Build exclusive conditions across all entries\n const exclusive = buildExclusiveConditions(expanded);\n\n // Expand ORs from De Morgan negation into exclusive branches\n // This transforms: !A | !B → !A, (A & !B)\n // Ensures each CSS rule has proper at-rule context\n const fullyExpanded = expandExclusiveOrs(exclusive);\n exclusiveByStyle.set(styleName, fullyExpanded);\n } else {\n // Simple value - single entry with TRUE condition\n exclusiveByStyle.set(styleName, [\n {\n styleKey: styleName,\n stateKey: '',\n value,\n condition: trueCondition(),\n priority: 0,\n exclusiveCondition: trueCondition(),\n },\n ]);\n }\n }\n\n // Stage 4: Compute all valid state combinations\n const stateSnapshots = computeStateCombinations(\n exclusiveByStyle,\n lookupStyles,\n );\n\n // Stage 5: Call handler for each snapshot\n const computedRules: ComputedRule[] = [];\n\n for (const snapshot of stateSnapshots) {\n const result = handler(snapshot.values as StyleValueStateMap);\n if (!result) continue;\n\n // Handler may return single or array\n const results = Array.isArray(result) ? result : [result];\n\n for (const r of results) {\n if (!r || typeof r !== 'object') continue;\n\n const { $, ...styleProps } = r;\n const declarations: Record<string, string> = {};\n\n for (const [prop, val] of Object.entries(styleProps)) {\n if (val != null && val !== '') {\n declarations[prop] = String(val);\n }\n }\n\n if (Object.keys(declarations).length === 0) continue;\n\n // Handle $ suffixes\n const suffixes = $\n ? (Array.isArray($) ? $ : [$]).map(\n (s) => selectorSuffix + normalizeSelectorSuffix(String(s)),\n )\n : [selectorSuffix];\n\n for (const suffix of suffixes) {\n computedRules.push({\n condition: snapshot.condition,\n declarations,\n selectorSuffix: suffix,\n });\n }\n }\n }\n\n // Stage 6: Merge rules with identical CSS output\n const mergedRules = mergeByValue(computedRules);\n\n // Stage 7: Materialize to CSS\n for (const rule of mergedRules) {\n const cssRules = materializeComputedRule(rule);\n allRules.push(...cssRules);\n }\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Check if a key is a CSS selector\n */\nexport function isSelector(key: string): boolean {\n return key.startsWith('&') || key.startsWith('.') || /^[A-Z]/.test(key);\n}\n\n/**\n * Result of processing a selector affix ($) pattern.\n *\n * @example\n * // Valid result with multiple selectors\n * { valid: true, selectors: ['> [data-element=\"Cell\"]', ' [data-element=\"Body\"] > [data-element=\"Cell\"]'] }\n *\n * // Invalid result with error message\n * { valid: false, reason: 'Selector affix \"+\" targets elements outside the root scope.' }\n */\ntype AffixResult =\n | { valid: true; selectors: string[] }\n | { valid: false; reason: string };\n\n/**\n * Get all selector suffixes for a sub-element key.\n *\n * Handles three types of selector keys:\n * - `&` prefix: Raw selector suffix (e.g., `&:hover` → `:hover`)\n * - `.` prefix: Class selector (e.g., `.active` → ` .active`)\n * - Uppercase: Sub-element with optional `$` affix pattern\n *\n * @param key - The sub-element key (e.g., 'Label', '&:hover', '.active')\n * @param styles - The styles object, may contain `$` property for selector affix\n * @returns Array of selector suffixes, or null if invalid (with console warning)\n *\n * @example\n * getAllSelectors('Label', {})\n * // → [' [data-element=\"Label\"]']\n *\n * getAllSelectors('Cell', { $: '>, >Body>' })\n * // → ['> [data-element=\"Cell\"]', ' [data-element=\"Body\"] > [data-element=\"Cell\"]']\n */\nfunction getAllSelectors(key: string, styles?: Styles): string[] | null {\n if (key.startsWith('&')) {\n return [key.slice(1)];\n }\n\n if (key.startsWith('.')) {\n return [` ${key}`];\n }\n\n if (/^[A-Z]/.test(key)) {\n const affix = styles?.$;\n if (affix !== undefined) {\n const result = processAffix(String(affix), key);\n if (!result.valid) {\n emitWarning('INVALID_SELECTOR_AFFIX', result.reason);\n return null;\n }\n return result.selectors;\n }\n return [` [data-element=\"${key}\"]`];\n }\n\n return null;\n}\n\n/**\n * Process selector affix pattern and return selector(s)\n *\n * Supports:\n * - Direct child: '>'\n * - Chained elements: '>Body>Row>'\n * - HTML tags (no key injection): 'h1', '>ul>li', 'button:hover'\n * - Universal selector: '*', 'h1 *'\n * - Pseudo-elements on root: '::before'\n * - Pseudo on sub-element: '@::before', '>@:hover'\n * - Classes: '.active', '>@.active'\n * - Multiple selectors: '>, >Body>'\n * - Sibling combinators (after element): '>Item+', '>Item~'\n */\nfunction processAffix(affix: string, key: string): AffixResult {\n const trimmed = affix.trim();\n\n // Empty = default behavior (descendant selector with key)\n if (!trimmed) {\n return { valid: true, selectors: [` [data-element=\"${key}\"]`] };\n }\n\n // Split by comma for multiple selectors\n const patterns = trimmed.split(',').map((p) => p.trim());\n const selectors: string[] = [];\n\n for (const pattern of patterns) {\n const validation = validatePattern(pattern);\n if (!validation.valid) {\n return validation;\n }\n\n const selector = processSinglePattern(pattern, key);\n selectors.push(selector);\n }\n\n return { valid: true, selectors };\n}\n\n/**\n * Recognized token patterns for selector affix validation.\n *\n * These patterns are used to tokenize and validate `$` affix strings.\n * Order matters: more specific patterns must come first to avoid\n * partial matches (e.g., `::before` must match before `:` alone).\n *\n * Unrecognized tokens (like `#id`, `*`, or numbers) will cause validation to fail.\n */\nconst VALID_TOKEN_PATTERNS = [\n /^[>+~]/, // Combinators: >, +, ~\n /^\\*/, // Universal selector (*)\n /^[A-Z][a-zA-Z0-9]*/, // Uppercase element names → [data-element=\"...\"]\n /^@/, // @ placeholder for key injection position\n /^::?[a-z][a-z0-9-]*(?:\\([^)]*\\))?/, // Pseudo-elements/classes (:hover, ::before, :not(.x))\n /^\\.[a-zA-Z_-][a-zA-Z0-9_-]*/, // Class selectors (.active, .is-open)\n /^\\[[^\\]]+\\]/, // Attribute selectors ([type=\"text\"], [role])\n /^[a-z][a-z0-9-]*/, // HTML tag names (a, div, button, my-component)\n /^\\s+/, // Whitespace (ignored during parsing)\n /^&/, // Root reference (stripped, kept for backward compat)\n];\n\n/**\n * Scan a pattern for unrecognized tokens.\n *\n * Iterates through the pattern, consuming recognized tokens until\n * either the pattern is fully consumed (valid) or an unrecognized\n * character sequence is found (invalid).\n *\n * @param pattern - The selector pattern to validate\n * @returns The first unrecognized token found, or null if all tokens are valid\n *\n * @example\n * findUnrecognizedTokens('>Body>Row>') // → null (valid)\n * findUnrecognizedTokens('123') // → '123' (invalid)\n * findUnrecognizedTokens('#myId') // → '#' (invalid)\n */\nfunction findUnrecognizedTokens(pattern: string): string | null {\n let remaining = pattern;\n\n while (remaining.length > 0) {\n let matched = false;\n\n for (const regex of VALID_TOKEN_PATTERNS) {\n const match = remaining.match(regex);\n if (match) {\n remaining = remaining.slice(match[0].length);\n matched = true;\n break;\n }\n }\n\n if (!matched) {\n // Found unrecognized content - extract the problematic part\n const unrecognized = remaining.match(/^[^\\s>+~@.:[\\]A-Z]+/);\n return unrecognized ? unrecognized[0] : remaining[0];\n }\n }\n\n return null;\n}\n\n/**\n * Validate a selector pattern for structural correctness.\n *\n * Checks for:\n * 1. Out-of-scope selectors: Patterns starting with `+` or `~` target siblings\n * of the root element, which is outside the component's DOM scope.\n * 2. Consecutive combinators: Patterns like `>>` or `>+` are malformed CSS.\n * 3. Unrecognized tokens: Characters/sequences not matching valid CSS selectors.\n *\n * @param pattern - A single selector pattern (already split by comma)\n * @returns AffixResult indicating validity and error reason if invalid\n *\n * @example\n * validatePattern('>Body>Row>') // → { valid: true, selectors: [] }\n * validatePattern('+') // → { valid: false, reason: '...outside root scope...' }\n * validatePattern('>>') // → { valid: false, reason: '...consecutive combinators...' }\n */\nfunction validatePattern(pattern: string): AffixResult {\n const trimmed = pattern.trim();\n\n // Patterns starting with + or ~ target siblings of the root element,\n // which is outside the component's scope. Valid sibling patterns must\n // be preceded by an element: \">Item+\", \">Item~\"\n if (/^[+~]/.test(trimmed)) {\n return {\n valid: false,\n reason:\n `Selector affix \"${pattern}\" targets elements outside the root scope. ` +\n `Sibling selectors (+, ~) must be preceded by an element inside the root. ` +\n `Use \">Element+\" or \">Element~\" instead.`,\n };\n }\n\n // Check for consecutive combinators\n if (/[>+~]{2,}/.test(trimmed.replace(/\\s+/g, ''))) {\n return {\n valid: false,\n reason: `Selector affix \"${pattern}\" contains consecutive combinators.`,\n };\n }\n\n // Check for unrecognized tokens (e.g., lowercase text like \"foo\")\n const unrecognized = findUnrecognizedTokens(trimmed);\n if (unrecognized) {\n return {\n valid: false,\n reason:\n `Selector affix \"${pattern}\" contains unrecognized token \"${unrecognized}\". ` +\n `Valid tokens: combinators (>, +, ~), element names (Uppercase), ` +\n `@ placeholder, pseudo (:hover, ::before), class (.name), attribute ([attr]).`,\n };\n }\n\n return { valid: true, selectors: [] };\n}\n\n/**\n * Process a single selector pattern into a CSS selector suffix.\n *\n * This is the main transformation function that converts a `$` affix pattern\n * into a valid CSS selector suffix. It handles:\n *\n * 1. `@` placeholder replacement with `[data-element=\"key\"]`\n * 2. Key injection based on pattern ending (see `shouldInjectKey`)\n * 3. Proper spacing for descendant vs direct child selectors\n *\n * @param pattern - A single validated selector pattern\n * @param key - The sub-element key to inject (e.g., 'Label', 'Cell')\n * @returns CSS selector suffix ready to append to the root selector\n *\n * @example\n * processSinglePattern('>', 'Row')\n * // → '> [data-element=\"Row\"]'\n *\n * processSinglePattern('>Body>Row>', 'Cell')\n * // → '> [data-element=\"Body\"] > [data-element=\"Row\"] > [data-element=\"Cell\"]'\n *\n * processSinglePattern('::before', 'Before')\n * // → '::before' (no key injection for pseudo on root)\n *\n * processSinglePattern('>@:hover', 'Item')\n * // → '> [data-element=\"Item\"]:hover'\n */\nfunction processSinglePattern(pattern: string, key: string): string {\n // Strip leading & if present (implicit root reference, kept for compat)\n const normalized = pattern.replace(/^&/, '').trim();\n\n if (!normalized) {\n return ` [data-element=\"${key}\"]`;\n }\n\n // Pseudo-elements/classes at start apply directly to root (no space prefix)\n const startsWithPseudo = /^::?[a-z]/.test(normalized);\n\n // Transform the pattern: convert element names and normalize spacing\n let result = transformPattern(normalized);\n\n // Handle @ placeholder: explicit key injection position\n if (result.includes('@')) {\n // Remove space between @ and following class/pseudo for proper attachment\n // e.g., \"@ .active\" → \"[el].active\", but \"@ > span\" → \"[el] > span\"\n result = result.replace(/@ (?=[.:])/g, '@');\n result = result.replace(/@/g, `[data-element=\"${key}\"]`);\n\n if (!startsWithPseudo && !result.startsWith(' ')) {\n result = ' ' + result;\n }\n return result;\n }\n\n // Auto-inject key based on pattern ending (see shouldInjectKey for rules)\n if (shouldInjectKey(normalized)) {\n result = result + ' ' + `[data-element=\"${key}\"]`;\n }\n\n // Add space prefix for selectors targeting inside root (not pseudo on root)\n if (!startsWithPseudo && !result.startsWith(' ')) {\n result = ' ' + result;\n }\n\n return result;\n}\n\n/**\n * Transform a selector pattern by converting element names and normalizing spacing.\n *\n * This is a character-by-character tokenizer that:\n * - Converts uppercase names to `[data-element=\"Name\"]` selectors\n * - Adds proper spacing around combinators (>, +, ~)\n * - Preserves lowercase tags, classes, pseudos, and attributes as-is\n * - Keeps @ placeholder for later replacement\n *\n * The tokenizer handles these token types in order:\n * 1. Whitespace (skipped)\n * 2. Combinators: >, +, ~ (add surrounding spaces)\n * 3. Universal selector: * (keep as-is with spacing)\n * 4. Uppercase names: Body, Row (convert to [data-element=\"...\"])\n * 5. @ placeholder (keep for later replacement)\n * 6. Pseudo: :hover, ::before (attach to previous token)\n * 7. Tags: a, div, button (keep as-is with spacing)\n * 8. Classes: .active (attach to previous element/tag/placeholder)\n * 9. Attributes: [type=\"text\"] (keep as-is)\n *\n * @param pattern - The raw selector pattern to transform\n * @returns Transformed pattern with proper CSS selector syntax\n *\n * @example\n * transformPattern('>Body>Row>')\n * // → '> [data-element=\"Body\"] > [data-element=\"Row\"] >'\n *\n * transformPattern('button.primary:hover')\n * // → 'button.primary:hover'\n */\nfunction transformPattern(pattern: string): string {\n let result = '';\n let lastCh = '';\n let i = 0;\n\n while (i < pattern.length) {\n const char = pattern[i];\n\n if (/\\s/.test(char)) {\n i++;\n continue;\n }\n\n if (/[>+~]/.test(char)) {\n if (result && lastCh !== ' ') {\n result += ' ';\n }\n result += char;\n lastCh = char;\n i++;\n continue;\n }\n\n if (char === '*') {\n if (result && lastCh !== ' ') {\n result += ' ';\n }\n result += '*';\n lastCh = '*';\n i++;\n continue;\n }\n\n if (/[A-Z]/.test(char)) {\n const nameStart = i;\n while (i < pattern.length && /[a-zA-Z0-9]/.test(pattern[i])) {\n i++;\n }\n if (result && lastCh !== ' ') {\n result += ' ';\n }\n const segment = `[data-element=\"${pattern.slice(nameStart, i)}\"]`;\n result += segment;\n lastCh = ']';\n continue;\n }\n\n if (char === '@') {\n if (result && lastCh !== ' ') {\n result += ' ';\n }\n result += '@';\n lastCh = '@';\n i++;\n continue;\n }\n\n if (char === ':') {\n const pseudoStart = i;\n while (\n i < pattern.length &&\n !/[\\s>+~,@]/.test(pattern[i]) &&\n !/[A-Z]/.test(pattern[i])\n ) {\n i++;\n }\n const segment = pattern.slice(pseudoStart, i);\n result += segment;\n lastCh = segment[segment.length - 1] || lastCh;\n continue;\n }\n\n if (/[a-z]/.test(char)) {\n const tagStart = i;\n while (i < pattern.length && /[a-z0-9-]/.test(pattern[i])) {\n i++;\n }\n if (result && lastCh !== ' ') {\n result += ' ';\n }\n const segment = pattern.slice(tagStart, i);\n result += segment;\n lastCh = segment[segment.length - 1] || lastCh;\n continue;\n }\n\n if (char === '.') {\n const attachToLast =\n lastCh === ']' || lastCh === '@' || /[a-zA-Z0-9-]/.test(lastCh);\n if (result && !attachToLast && lastCh !== ' ') {\n result += ' ';\n }\n const clsStart = i;\n i++;\n while (i < pattern.length && /[a-zA-Z0-9_-]/.test(pattern[i])) {\n i++;\n }\n const segment = pattern.slice(clsStart, i);\n result += segment;\n lastCh = segment[segment.length - 1] || lastCh;\n continue;\n }\n\n if (char === '[') {\n const attachToLast =\n lastCh === ']' || lastCh === '@' || /[a-zA-Z0-9-]/.test(lastCh);\n if (result && !attachToLast && lastCh !== ' ') {\n result += ' ';\n }\n const attrStart = i;\n let depth = 0;\n while (i < pattern.length) {\n if (pattern[i] === '[') depth++;\n if (pattern[i] === ']') depth--;\n i++;\n if (depth === 0) break;\n }\n result += pattern.slice(attrStart, i);\n lastCh = ']';\n continue;\n }\n\n result += char;\n lastCh = char;\n i++;\n }\n\n return result;\n}\n\n/**\n * Determine if the sub-element key should be auto-injected based on pattern ending.\n *\n * Key injection rules (when no @ placeholder is present):\n *\n * | Pattern Ending | Inject Key? | Example | Result |\n * |----------------|-------------|---------|--------|\n * | Combinator (>, +, ~) | Yes | `'>Body>'` | `> [data-element=\"Body\"] > [el]` |\n * | Uppercase element | Yes | `'>Body>Row'` | `> [el1] > [el2] [key]` |\n * | Lowercase tag | No | `'h1'` | ` h1` |\n * | Universal (*) | No | `'h1 *'` | ` h1 *` |\n * | Pseudo (:hover, ::before) | No | `'::before'` | `::before` |\n * | Class (.active) | No | `'.active'` | `.active` |\n * | Attribute ([type]) | No | `'[type=\"text\"]'` | `[type=\"text\"]` |\n *\n * @param pattern - The normalized pattern (after stripping &)\n * @returns true if key should be injected, false otherwise\n *\n * @example\n * shouldInjectKey('>') // → true (trailing combinator)\n * shouldInjectKey('>Body>Row') // → true (ends with element)\n * shouldInjectKey('h1') // → false (ends with tag)\n * shouldInjectKey('*') // → false (universal selector)\n * shouldInjectKey('::before') // → false (ends with pseudo)\n * shouldInjectKey('.active') // → false (ends with class)\n * shouldInjectKey('a:hover') // → false (ends with pseudo)\n * shouldInjectKey('button.primary') // → false (ends with class)\n */\nfunction shouldInjectKey(pattern: string): boolean {\n const trimmed = pattern.trim();\n\n // Rule 1: Ends with combinator → inject key after it\n // e.g., '>' → '> [data-element=\"Key\"]'\n if (/[>+~]$/.test(trimmed)) {\n return true;\n }\n\n // Rule 2: Ends with uppercase element name → inject key as descendant\n // The lookbehind ensures we're matching a standalone element name, not\n // part of a class like .myClass (where C is preceded by lowercase)\n // e.g., '>Body' → '> [data-element=\"Body\"] [data-element=\"Key\"]'\n if (/(?:^|[\\s>+~\\]:])[A-Z][a-zA-Z0-9]*$/.test(trimmed)) {\n return true;\n }\n\n // Otherwise (tags, universal *, pseudo, class, attribute) → no injection\n // The pattern is complete as-is, applying to root or a specific selector\n return false;\n}\n\n/**\n * Normalize selector suffix from $ property\n */\nfunction normalizeSelectorSuffix(suffix: string): string {\n if (!suffix) return '';\n return suffix.startsWith('&') ? suffix.slice(1) : suffix;\n}\n\n/**\n * Build handler queue from style keys\n */\nfunction buildHandlerQueue(\n styleKeys: string[],\n styles: Styles,\n): { handler: StyleHandler; styleMap: StyleMap }[] {\n const queue: { handler: StyleHandler; styleMap: StyleMap }[] = [];\n const seenHandlers = new Set<StyleHandler>();\n\n for (const styleName of styleKeys) {\n let handlers: StyleHandler[] = STYLE_HANDLER_MAP[styleName];\n\n if (!handlers) {\n handlers = STYLE_HANDLER_MAP[styleName] = [createStyle(styleName)];\n }\n\n for (const handler of handlers) {\n if (seenHandlers.has(handler)) continue;\n seenHandlers.add(handler);\n\n const lookupStyles = handler.__lookupStyles;\n const styleMap: StyleMap = {};\n\n for (const name of lookupStyles) {\n const val = styles[name];\n if (val !== undefined) {\n styleMap[name] = val as StyleValue | StyleValueStateMap;\n }\n }\n\n queue.push({ handler, styleMap });\n }\n }\n\n return queue;\n}\n\n/**\n * Compute all valid state combinations for a handler's lookup styles\n */\nfunction computeStateCombinations(\n exclusiveByStyle: Map<string, ExclusiveStyleEntry[]>,\n lookupStyles: string[],\n): { condition: ConditionNode; values: Record<string, StyleValue> }[] {\n // Get entries for each style\n const entriesPerStyle = lookupStyles.map(\n (style) => exclusiveByStyle.get(style) || [],\n );\n\n // Cartesian product of all combinations\n const combinations = cartesianProduct(entriesPerStyle);\n\n // Build snapshots, simplifying and filtering impossible combinations\n const snapshots: {\n condition: ConditionNode;\n values: Record<string, StyleValue>;\n }[] = [];\n\n for (const combo of combinations) {\n // Combine all exclusive conditions with AND\n const conditions = combo.map((e) => e.exclusiveCondition);\n const combined = and(...conditions);\n const simplified = simplifyCondition(combined);\n\n // Skip impossible combinations\n if (simplified.kind === 'false') continue;\n\n // Build values map\n const values: Record<string, StyleValue> = {};\n for (const entry of combo) {\n values[entry.styleKey] = entry.value;\n }\n\n snapshots.push({\n condition: simplified,\n values,\n });\n }\n\n return snapshots;\n}\n\n/**\n * Cartesian product of arrays\n */\nfunction cartesianProduct<T>(arrays: T[][]): T[][] {\n if (arrays.length === 0) return [[]];\n\n const nonEmpty = arrays.filter((a) => a.length > 0);\n if (nonEmpty.length === 0) return [[]];\n\n let result: T[][] = [[]];\n for (const arr of nonEmpty) {\n const next: T[][] = [];\n for (const combo of result) {\n for (const item of arr) {\n const newCombo = new Array<T>(combo.length + 1);\n for (let i = 0; i < combo.length; i++) newCombo[i] = combo[i];\n newCombo[combo.length] = item;\n next.push(newCombo);\n }\n }\n result = next;\n }\n return result;\n}\n\nconst declStringCache = new WeakMap<Record<string, string>, string>();\n\nfunction stringifyDeclarations(decl: Record<string, string>): string {\n let cached = declStringCache.get(decl);\n if (cached === undefined) {\n cached = JSON.stringify(decl);\n declStringCache.set(decl, cached);\n }\n return cached;\n}\n\n/**\n * Merge rules with identical CSS output\n */\nfunction mergeByValue(rules: ComputedRule[]): ComputedRule[] {\n const groups = new Map<string, ComputedRule[]>();\n\n for (const rule of rules) {\n const key = `${rule.selectorSuffix}|${stringifyDeclarations(rule.declarations)}`;\n if (!groups.has(key)) {\n groups.set(key, []);\n }\n groups.get(key)!.push(rule);\n }\n\n // Merge conditions with OR for each group\n const merged: ComputedRule[] = [];\n\n for (const [, groupRules] of groups) {\n if (groupRules.length === 1) {\n merged.push(groupRules[0]);\n } else {\n // Merge conditions with OR\n const mergedCondition = simplifyCondition(\n or(...groupRules.map((r) => r.condition)),\n );\n merged.push({\n condition: mergedCondition,\n declarations: groupRules[0].declarations,\n selectorSuffix: groupRules[0].selectorSuffix,\n });\n }\n }\n\n return merged;\n}\n\n/**\n * Build selector fragment from a variant (without className prefix)\n */\nfunction buildSelectorFromVariant(\n variant: SelectorVariant,\n selectorSuffix: string,\n): string {\n let selector = '';\n\n // Add flat modifier + pseudo selectors (sorted for canonical output)\n selector += branchToCSS([\n ...variant.modifierConditions,\n ...variant.pseudoConditions,\n ]);\n\n // Add selector groups (:is()/:not() on element)\n for (const group of variant.selectorGroups) {\n selector += selectorGroupToCSS(group);\n }\n\n // Add parent selectors (before sub-element suffix)\n if (variant.parentGroups.length > 0) {\n selector += parentGroupsToCSS(variant.parentGroups);\n }\n\n selector += selectorSuffix;\n\n // Add own groups (:is()/:not() on sub-element)\n const ownOptimized = optimizeGroups(variant.ownGroups);\n for (const group of ownOptimized) {\n selector += selectorGroupToCSS(group);\n }\n\n return selector;\n}\n\n/**\n * Materialize a computed rule to final CSS format\n *\n * Returns an array because OR conditions may generate multiple CSS rules\n * (when different branches have different at-rules)\n */\nfunction materializeComputedRule(rule: ComputedRule): CSSRule[] {\n const components = conditionToCSS(rule.condition);\n\n if (components.isImpossible || components.variants.length === 0) {\n return [];\n }\n\n const declarations = Object.entries(rule.declarations)\n .map(([prop, value]) => `${prop}: ${value};`)\n .join(' ');\n\n // Helper to get root prefix key for grouping\n const getRootPrefixKey = (variant: SelectorVariant): string => {\n return rootGroupsToCSS(variant.rootGroups) || '';\n };\n\n // Group variants by their at-rules (variants with same at-rules can be combined with commas)\n const byAtRules = new Map<\n string,\n { variants: SelectorVariant[]; atRules: string[]; rootPrefix?: string }\n >();\n\n for (const variant of components.variants) {\n const atRules = buildAtRulesFromVariant(variant);\n const key = atRules.sort().join('|||') + '###' + getRootPrefixKey(variant);\n\n const group = byAtRules.get(key);\n if (group) {\n group.variants.push(variant);\n } else {\n byAtRules.set(key, {\n variants: [variant],\n atRules,\n rootPrefix: rootGroupsToCSS(variant.rootGroups),\n });\n }\n }\n\n // Generate one CSSRule per at-rules group\n const rules: CSSRule[] = [];\n for (const [, group] of byAtRules) {\n // Merge variants that differ only in flat modifier/pseudo conditions\n // into :is() groups before building selector strings\n const mergedVariants = mergeVariantsIntoSelectorGroups(group.variants);\n\n // Build selector fragments for each variant (will be joined with className later)\n const selectorFragments = mergedVariants.map((v) =>\n buildSelectorFromVariant(v, rule.selectorSuffix),\n );\n\n // Store as array if multiple, string if single\n const selector =\n selectorFragments.length === 1 ? selectorFragments[0] : selectorFragments;\n\n const cssRule: CSSRule = {\n selector,\n declarations,\n };\n\n if (group.atRules.length > 0) {\n cssRule.atRules = group.atRules;\n }\n\n if (group.rootPrefix) {\n cssRule.rootPrefix = group.rootPrefix;\n }\n\n rules.push(cssRule);\n }\n\n return rules;\n}\n\n// ============================================================================\n// Public API: renderStyles (compatible with old API)\n// ============================================================================\n\n/**\n * Options for renderStyles when using direct selector mode.\n */\nexport interface RenderStylesOptions {\n /**\n * Whether to double the class selector for increased specificity.\n * When true, `.myClass` becomes `.myClass.myClass` for higher specificity.\n *\n * @default false - User-provided selectors are not doubled.\n *\n * Note: This only applies when a classNameOrSelector is provided.\n * When renderStyles returns RenderResult with needsClassName=true,\n * the injector handles doubling automatically.\n */\n doubleSelector?: boolean;\n}\n\n/**\n * Render styles to CSS rules.\n *\n * When called without classNameOrSelector, returns RenderResult with needsClassName=true.\n * When called with a selector/className string, returns StyleResult[] for direct injection.\n */\nexport function renderStyles(\n styles?: Styles,\n classNameOrSelector?: undefined,\n options?: undefined,\n pipelineCacheKey?: string,\n): RenderResult;\nexport function renderStyles(\n styles: Styles | undefined,\n classNameOrSelector: string,\n options?: RenderStylesOptions,\n): StyleResult[];\nexport function renderStyles(\n styles?: Styles,\n classNameOrSelector?: string,\n options?: RenderStylesOptions,\n pipelineCacheKey?: string,\n): RenderResult | StyleResult[] {\n // Check if we have a direct selector/className\n const directSelector = !!classNameOrSelector;\n\n // Check cache first when a pre-computed key is available.\n // This allows callers to skip building the styles object on cache hit.\n let rules: CSSRule[] | undefined;\n if (pipelineCacheKey) {\n rules = pipelineCache.get(pipelineCacheKey);\n }\n\n if (!rules && !styles) {\n return directSelector ? [] : { rules: [] };\n }\n\n // Use pre-computed cache key when available (from chunk path),\n // falling back to stringifyStyles for direct renderStyles() calls\n const cacheKey = pipelineCacheKey || stringifyStyles(styles!);\n if (!rules) {\n rules = pipelineCache.get(cacheKey);\n }\n\n if (!rules) {\n // styles is guaranteed non-null here: early return above handles (!rules && !styles)\n const parserContext = createStateParserContext(styles!);\n rules = runPipeline(styles!, parserContext);\n pipelineCache.set(cacheKey, rules);\n }\n\n // Direct selector/className mode: return StyleResult[] directly\n if (directSelector) {\n const shouldDouble = options?.doubleSelector ?? false;\n\n return rules.map((rule): StyleResult => {\n // Handle selector as array (OR conditions) or string\n const selectorParts = Array.isArray(rule.selector)\n ? rule.selector\n : rule.selector\n ? [rule.selector]\n : [''];\n\n const finalSelector = selectorParts\n .map((part) => {\n let sel = part\n ? `${classNameOrSelector}${part}`\n : classNameOrSelector;\n\n // Double class selector for increased specificity if requested\n // This is used when the caller explicitly wants higher specificity\n if (shouldDouble && sel.startsWith('.')) {\n const classMatch = sel.match(/^\\.[a-zA-Z_-][a-zA-Z0-9_-]*/);\n if (classMatch) {\n const baseClass = classMatch[0];\n sel = baseClass + sel;\n }\n }\n\n // Handle root prefix for this selector\n if (rule.rootPrefix) {\n sel = `${rule.rootPrefix} ${sel}`;\n }\n\n return sel;\n })\n .join(', ');\n\n const result: StyleResult = {\n selector: finalSelector,\n declarations: rule.declarations,\n };\n\n if (rule.atRules && rule.atRules.length > 0) {\n result.atRules = rule.atRules;\n }\n\n return result;\n });\n }\n\n // No className mode: return RenderResult with needsClassName flag\n // Normalize selector to string (join array with placeholder that injector will handle)\n return {\n rules: rules.map(\n (r): StyleResult => ({\n selector: Array.isArray(r.selector)\n ? r.selector.join('|||')\n : r.selector,\n declarations: r.declarations,\n atRules: r.atRules,\n needsClassName: true,\n rootPrefix: r.rootPrefix,\n }),\n ),\n };\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport type { ConditionNode } from './conditions';\nexport { and, or, not, trueCondition, falseCondition } from './conditions';\nexport { parseStateKey } from './parseStateKey';\nexport { simplifyCondition } from './simplify';\nexport { buildExclusiveConditions } from './exclusive';\nexport { conditionToCSS } from './materialize';\nexport type { CSSRule } from './materialize';\nexport { setWarningHandler, emitWarning } from './warnings';\nexport type {\n TastyWarning,\n TastyWarningCode,\n TastyWarningHandler,\n} from './warnings';\n"],"mappings":"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"}
|
package/docs/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tasty Docs
|
|
2
2
|
|
|
3
|
-
Tasty is a styling engine for design systems that turns component state into deterministic CSS by compiling state maps into mutually exclusive selectors. Use this hub to choose the right guide once you know whether you are evaluating the model, adopting it in a design system, or implementing
|
|
3
|
+
Tasty is a styling engine for design systems that turns component state into deterministic CSS by compiling state maps into mutually exclusive selectors. Use this hub to choose the right guide once you know whether you are evaluating the model, adopting it in a design system, or implementing reusable, stateful components day to day.
|
|
4
4
|
|
|
5
5
|
## Start Here
|
|
6
6
|
|
package/docs/adoption.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Adoption Guide
|
|
2
2
|
|
|
3
|
-
Tasty is not a drop-in replacement for another styling library. It is a **substrate for building a design-system-defined styling language**: what the comparison guide calls a house styling language. That means adoption usually starts where styling has already become a composition problem, not with an all-at-once rewrite.
|
|
3
|
+
Tasty is not a drop-in replacement for another styling library. It is a **substrate for building a design-system-defined styling language**: what the comparison guide calls a house styling language. That means adoption usually starts where styling for reusable, stateful components has already become a composition problem, not with an all-at-once rewrite.
|
|
4
4
|
|
|
5
5
|
This guide is for design-system maintainers and platform engineers evaluating Tasty or introducing it into an existing codebase. Use this document for rollout strategy and adoption sequencing; use the [Comparison guide](comparison.md) when the open question is whether Tasty is the right tool in the first place.
|
|
6
6
|
|
package/docs/comparison.md
CHANGED
|
@@ -16,7 +16,7 @@ Most styling tools focus on one of these layers:
|
|
|
16
16
|
- utility composition
|
|
17
17
|
- atomic CSS generation
|
|
18
18
|
|
|
19
|
-
Tasty's house styling language can include tokens, state semantics, style props, recipes, custom units, and sub-element rules.
|
|
19
|
+
Tasty's house styling language can include tokens, state semantics, style props, recipes, custom units, and sub-element rules. In other words, it is a governed styling model, not just another syntax for writing CSS.
|
|
20
20
|
|
|
21
21
|
That is why syntax-level comparisons are often shallow. The more meaningful comparison is about:
|
|
22
22
|
|
package/docs/dsl.md
CHANGED
|
@@ -36,6 +36,46 @@ styles: { Title: { preset: 'h3' } }
|
|
|
36
36
|
// Targets: <div data-element="Title">
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
#### Selector Affix (`$`)
|
|
40
|
+
|
|
41
|
+
Control how a sub-element selector attaches to the root selector using the `$` property inside the sub-element's styles:
|
|
42
|
+
|
|
43
|
+
| Pattern | Result | Description |
|
|
44
|
+
|---------|--------|-------------|
|
|
45
|
+
| *(none)* | ` [el]` | Descendant (default) |
|
|
46
|
+
| `>` | `> [el]` | Direct child |
|
|
47
|
+
| `>Body>Row>` | `> [Body] > [Row] > [el]` | Chained elements |
|
|
48
|
+
| `h1` | ` h1` | Tag selector (no key injection) |
|
|
49
|
+
| `h1 >` | ` h1 > [el]` | Key is direct child of tag |
|
|
50
|
+
| `h1 *` | ` h1 *` | Any descendant of tag |
|
|
51
|
+
| `*` | ` *` | All descendants |
|
|
52
|
+
| `::before` | `::before` | Root pseudo (no key) |
|
|
53
|
+
| `@::before` | `[el]::before` | Pseudo on the sub-element |
|
|
54
|
+
| `>@:hover` | `> [el]:hover` | Pseudo-class on the sub-element |
|
|
55
|
+
| `>@.active` | `> [el].active` | Class on the sub-element |
|
|
56
|
+
|
|
57
|
+
Rules for key injection (`[data-element="..."]`):
|
|
58
|
+
|
|
59
|
+
- **Trailing combinator** (`>`, `+`, `~`) — key is injected after it
|
|
60
|
+
- **Uppercase element name** (`Body`, `Row`) — key is injected as descendant
|
|
61
|
+
- **HTML tag** (`h1`, `a`, `span`) — no key injection; the tag IS the selector
|
|
62
|
+
- **Universal selector** (`*`) — no key injection
|
|
63
|
+
- **Pseudo / class / attribute** — no key injection
|
|
64
|
+
|
|
65
|
+
The `@` placeholder marks exactly where `[data-element="..."]` is injected, allowing you to attach pseudo-classes, pseudo-elements, or class selectors directly to the sub-element instead of the root:
|
|
66
|
+
|
|
67
|
+
```jsx
|
|
68
|
+
const List = tasty({
|
|
69
|
+
styles: {
|
|
70
|
+
Item: {
|
|
71
|
+
$: '>@:last-child',
|
|
72
|
+
border: 'none',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
// → .t0 > [data-element="Item"]:last-child { border: none }
|
|
77
|
+
```
|
|
78
|
+
|
|
39
79
|
### Color Token
|
|
40
80
|
|
|
41
81
|
Named color prefixed with `#` that maps to CSS custom properties. Supports opacity with `.N` suffix:
|
package/docs/runtime.md
CHANGED
|
@@ -178,31 +178,7 @@ const Card = tasty({
|
|
|
178
178
|
|
|
179
179
|
### Selector Affix (`$`)
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
| Pattern | Result | Description |
|
|
184
|
-
|---------|--------|-------------|
|
|
185
|
-
| *(none)* | ` [el]` | Descendant (default) |
|
|
186
|
-
| `>` | `> [el]` | Direct child |
|
|
187
|
-
| `>Body>Row>` | `> [Body] > [Row] > [el]` | Chained elements |
|
|
188
|
-
| `::before` | `::before` | Root pseudo (no key) |
|
|
189
|
-
| `@::before` | `[el]::before` | Pseudo on the sub-element |
|
|
190
|
-
| `>@:hover` | `> [el]:hover` | Pseudo-class on the sub-element |
|
|
191
|
-
| `>@.active` | `> [el].active` | Class on the sub-element |
|
|
192
|
-
|
|
193
|
-
The `@` placeholder marks exactly where the `[data-element="..."]` selector is injected, allowing you to attach pseudo-classes, pseudo-elements, or class selectors directly to the sub-element instead of the root:
|
|
194
|
-
|
|
195
|
-
```jsx
|
|
196
|
-
const List = tasty({
|
|
197
|
-
styles: {
|
|
198
|
-
Item: {
|
|
199
|
-
$: '>@:last-child',
|
|
200
|
-
border: 'none',
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
// → .t0 > [data-element="Item"]:last-child { border: none }
|
|
205
|
-
```
|
|
181
|
+
The `$` property inside a sub-element's styles controls how its selector attaches to the root selector — combinators, HTML tags, pseudo-elements, the `@` placeholder, and more. For the full reference table and injection rules, see [DSL — Selector Affix](dsl.md#selector-affix-).
|
|
206
182
|
|
|
207
183
|
For the mental model behind sub-elements — how they share root state context and how this differs from BEM — see [Methodology — Component architecture](methodology.md#component-architecture-root--sub-elements).
|
|
208
184
|
|