@tenphi/tasty 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/definitions.d.ts +1 -1
- package/dist/chunks/definitions.js.map +1 -1
- package/dist/hooks/useStyles.js +2 -34
- package/dist/hooks/useStyles.js.map +1 -1
- package/dist/injector/sheet-manager.js +5 -2
- package/dist/injector/sheet-manager.js.map +1 -1
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +16 -6
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/materialize.js +0 -1
- package/dist/pipeline/materialize.js.map +1 -1
- package/dist/ssr/format-rules.js +7 -4
- package/dist/ssr/format-rules.js.map +1 -1
- package/dist/styles/preset.js +2 -2
- package/dist/styles/preset.js.map +1 -1
- package/dist/utils/styles.d.ts +0 -1
- package/dist/utils/styles.js +0 -1
- package/dist/utils/styles.js.map +1 -1
- package/docs/dsl.md +0 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/chunks/definitions.d.ts
|
|
2
2
|
declare const CHUNK_NAMES: {
|
|
3
|
-
/** Special chunk for styles that cannot be split
|
|
3
|
+
/** Special chunk for styles that cannot be split */readonly COMBINED: "combined";
|
|
4
4
|
readonly SUBCOMPONENTS: "subcomponents";
|
|
5
5
|
readonly APPEARANCE: "appearance";
|
|
6
6
|
readonly FONT: "font";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","names":[],"sources":["../../src/chunks/definitions.ts"],"sourcesContent":["/**\n * Style chunk definitions for CSS chunking optimization.\n *\n * Styles are grouped into chunks based on:\n * 1. Handler dependencies - styles that share a handler MUST be in the same chunk\n * 2. Logical grouping - related styles grouped for better cache reuse\n *\n * See STYLE_CHUNKING_SPEC.md for detailed rationale.\n *\n * ============================================================================\n * ⚠️ CRITICAL ARCHITECTURAL CONSTRAINT: NO CROSS-CHUNK HANDLER DEPENDENCIES\n * ============================================================================\n *\n * Style handlers declare their dependencies via `__lookupStyles` array.\n * This creates a dependency graph where handlers read multiple style props.\n *\n * **ALL styles in a handler's `__lookupStyles` MUST be in the SAME chunk.**\n *\n * Why this matters:\n * 1. Each chunk computes a cache key from ONLY its own style values\n * 2. If a handler reads a style from another chunk, that value isn't in the cache key\n * 3. Changing the cross-chunk style won't invalidate this chunk's cache\n * 4. Result: stale CSS output or incorrect cache hits\n *\n * Example of a violation:\n * ```\n * // flowStyle.__lookupStyles = ['display', 'flow']\n * // If 'display' is in DISPLAY chunk and 'flow' is in LAYOUT chunk:\n * // - User sets { display: 'grid', flow: 'column' }\n * // - LAYOUT chunk caches CSS with flow=column, display=grid\n * // - User changes to { display: 'flex', flow: 'column' }\n * // - LAYOUT chunk cache key unchanged (only has 'flow')\n * // - Returns stale CSS computed with display=grid!\n * ```\n *\n * Before adding/moving styles, verify:\n * 1. Find all handlers that use this style (grep for the style name in __lookupStyles)\n * 2. Ensure ALL styles from each handler's __lookupStyles are in the same chunk\n * ============================================================================\n */\n\nimport { isSelector } from '../pipeline';\n\n// ============================================================================\n// Chunk Style Lists\n// ============================================================================\n\n/**\n * Appearance chunk - visual styling with independent handlers\n */\nexport const APPEARANCE_CHUNK_STYLES = [\n 'fill', // fillStyle (independent)\n 'color', // colorStyle (independent)\n 'opacity', // independent\n 'border', // borderStyle (independent)\n 'radius', // radiusStyle (independent)\n 'outline', // outlineStyle: outline ↔ outlineOffset\n 'outlineOffset', // outlineStyle: outline ↔ outlineOffset\n 'shadow', // shadowStyle (independent)\n 'fade', // fadeStyle (independent)\n] as const;\n\n/**\n * Font chunk - typography styles\n *\n * Handler dependencies (all styles in each handler MUST stay in this chunk):\n * ⚠️ presetStyle: preset, fontSize, lineHeight, letterSpacing, textTransform,\n * fontWeight, fontStyle, font\n */\nexport const FONT_CHUNK_STYLES = [\n // All from presetStyle handler - MUST stay together\n 'preset',\n 'font',\n 'fontWeight',\n 'fontStyle',\n 'fontSize',\n 'lineHeight',\n 'letterSpacing',\n 'textTransform',\n // Independent text styles grouped for cohesion\n 'fontFamily', // independent alias (logical grouping with font styles)\n 'textAlign',\n 'textDecoration',\n 'wordBreak',\n 'wordWrap',\n 'boldFontWeight',\n] as const;\n\n/**\n * Dimension chunk - sizing and spacing\n *\n * Handler dependencies (all styles in each handler MUST stay in this chunk):\n * ⚠️ paddingStyle: padding, paddingTop/Right/Bottom/Left, paddingBlock/Inline\n * ⚠️ marginStyle: margin, marginTop/Right/Bottom/Left, marginBlock/Inline\n * ⚠️ widthStyle: width, minWidth, maxWidth\n * ⚠️ heightStyle: height, minHeight, maxHeight\n */\nexport const DIMENSION_CHUNK_STYLES = [\n // All from paddingStyle handler - MUST stay together\n 'padding',\n 'paddingTop',\n 'paddingRight',\n 'paddingBottom',\n 'paddingLeft',\n 'paddingBlock',\n 'paddingInline',\n // All from marginStyle handler - MUST stay together\n 'margin',\n 'marginTop',\n 'marginRight',\n 'marginBottom',\n 'marginLeft',\n 'marginBlock',\n 'marginInline',\n // widthStyle handler - MUST stay together\n 'width',\n 'minWidth',\n 'maxWidth',\n // heightStyle handler - MUST stay together\n 'height',\n 'minHeight',\n 'maxHeight',\n 'flexBasis',\n 'flexGrow',\n 'flexShrink',\n 'flex',\n] as const;\n\n/**\n * Display chunk - display mode, layout flow, text overflow, and scrollbar\n *\n * Handler dependencies (all styles in each handler MUST stay in this chunk):\n * ⚠️ displayStyle: display, hide, textOverflow, overflow, whiteSpace\n * ⚠️ flowStyle: display, flow\n * ⚠️ gapStyle: display, flow, gap\n * ⚠️ scrollbarStyle: scrollbar, overflow\n */\nexport const DISPLAY_CHUNK_STYLES = [\n // displayStyle handler\n 'display',\n 'hide',\n 'textOverflow',\n 'overflow', // also used by scrollbarStyle\n 'whiteSpace',\n // flowStyle handler (requires display)\n 'flow',\n // gapStyle handler (requires display, flow)\n 'gap',\n // scrollbarStyle handler (requires overflow)\n 'scrollbar',\n] as const;\n\n/**\n * Layout chunk - flex/grid alignment and grid templates\n *\n * Note: flow and gap are in DISPLAY chunk due to handler dependencies\n * (flowStyle and gapStyle both require 'display' prop).\n */\nexport const LAYOUT_CHUNK_STYLES = [\n // Alignment styles (all independent handlers)\n 'placeItems',\n 'placeContent',\n 'alignItems',\n 'alignContent',\n 'justifyItems',\n 'justifyContent',\n 'align', // alignStyle (independent)\n 'justify', // justifyStyle (independent)\n 'place', // placeStyle (independent)\n 'columnGap',\n 'rowGap',\n // Grid template styles\n 'gridColumns',\n 'gridRows',\n 'gridTemplate',\n 'gridAreas',\n 'gridAutoFlow',\n 'gridAutoColumns',\n 'gridAutoRows',\n] as const;\n\n/**\n * Position chunk - element positioning\n *\n * Handler dependencies (all styles in each handler MUST stay in this chunk):\n * ⚠️ insetStyle: inset, insetBlock, insetInline, top, right, bottom, left\n */\nexport const POSITION_CHUNK_STYLES = [\n 'position',\n // All from insetStyle handler - MUST stay together\n 'inset',\n 'insetBlock',\n 'insetInline',\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'zIndex',\n 'gridArea',\n 'gridColumn',\n 'gridRow',\n 'order',\n 'placeSelf',\n 'alignSelf',\n 'justifySelf',\n 'transform',\n 'transition',\n 'animation',\n] as const;\n\n// ============================================================================\n// Chunk Names\n// ============================================================================\n\nexport const CHUNK_NAMES = {\n /** Special chunk for styles that cannot be split (e.g., @starting-style) */\n COMBINED: 'combined',\n SUBCOMPONENTS: 'subcomponents',\n APPEARANCE: 'appearance',\n FONT: 'font',\n DIMENSION: 'dimension',\n DISPLAY: 'display',\n LAYOUT: 'layout',\n POSITION: 'position',\n MISC: 'misc',\n} as const;\n\nexport type ChunkName = (typeof CHUNK_NAMES)[keyof typeof CHUNK_NAMES];\n\n// ============================================================================\n// Style-to-Chunk Lookup Map (O(1) categorization)\n// ============================================================================\n\n/**\n * Pre-computed map for O(1) style-to-chunk lookup.\n * Built once at module load time.\n */\nexport const STYLE_TO_CHUNK = new Map<string, ChunkName>();\n\n// Populate the lookup map\nfunction populateStyleToChunkMap() {\n for (const style of APPEARANCE_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.APPEARANCE);\n }\n for (const style of FONT_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.FONT);\n }\n for (const style of DIMENSION_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.DIMENSION);\n }\n for (const style of DISPLAY_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.DISPLAY);\n }\n for (const style of LAYOUT_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.LAYOUT);\n }\n for (const style of POSITION_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.POSITION);\n }\n}\n\n// Initialize at module load\npopulateStyleToChunkMap();\n\n// ============================================================================\n// Chunk Priority Order\n// ============================================================================\n\n/**\n * Chunk processing order. This ensures deterministic className allocation\n * regardless of style key order in the input.\n */\nconst CHUNK_ORDER: readonly string[] = [\n CHUNK_NAMES.APPEARANCE,\n CHUNK_NAMES.FONT,\n CHUNK_NAMES.DIMENSION,\n CHUNK_NAMES.DISPLAY,\n CHUNK_NAMES.LAYOUT,\n CHUNK_NAMES.POSITION,\n CHUNK_NAMES.MISC,\n CHUNK_NAMES.SUBCOMPONENTS,\n] as const;\n\n/**\n * Map from chunk name to its priority index for sorting.\n */\nconst _CHUNK_PRIORITY = new Map<string, number>(\n CHUNK_ORDER.map((name, index) => [name, index]),\n);\n\n// ============================================================================\n// Chunk Info Interface\n// ============================================================================\n\nexport interface ChunkInfo {\n /** Name of the chunk */\n name: ChunkName | string;\n /** Style keys belonging to this chunk */\n styleKeys: string[];\n}\n\n// ============================================================================\n// Style Categorization\n// ============================================================================\n\n/**\n * Categorize style keys into chunks.\n *\n * Returns chunks in a deterministic order (by CHUNK_ORDER) regardless\n * of the order of keys in the input styles object.\n *\n * @param styles - The styles object to categorize\n * @returns Map of chunk name to array of style keys in that chunk (in priority order)\n */\nexport function categorizeStyleKeys(\n styles: Record<string, unknown>,\n): Map<string, string[]> {\n // First pass: collect keys into chunks (unordered)\n const chunkData: Record<string, string[]> = {};\n const keys = Object.keys(styles);\n\n for (const key of keys) {\n // Skip the $ helper key (used for selector combinators)\n // Skip @keyframes and @properties (processed separately in useStyles)\n // Skip recipe (resolved before pipeline by resolveRecipes)\n if (\n key === '$' ||\n key === '@keyframes' ||\n key === '@properties' ||\n key === '@fontFace' ||\n key === '@counterStyle' ||\n key === 'recipe'\n ) {\n continue;\n }\n\n if (isSelector(key)) {\n // All selectors go into the subcomponents chunk\n if (!chunkData[CHUNK_NAMES.SUBCOMPONENTS]) {\n chunkData[CHUNK_NAMES.SUBCOMPONENTS] = [];\n }\n chunkData[CHUNK_NAMES.SUBCOMPONENTS].push(key);\n } else {\n // Look up the chunk for this style, default to misc\n const chunkName = STYLE_TO_CHUNK.get(key) ?? CHUNK_NAMES.MISC;\n if (!chunkData[chunkName]) {\n chunkData[chunkName] = [];\n }\n chunkData[chunkName].push(key);\n }\n }\n\n // Second pass: build ordered Map based on CHUNK_ORDER\n const orderedChunks = new Map<string, string[]>();\n\n // Add chunks in priority order\n for (const chunkName of CHUNK_ORDER) {\n if (chunkData[chunkName] && chunkData[chunkName].length > 0) {\n // Sort keys within chunk for consistent cache key generation\n orderedChunks.set(chunkName, chunkData[chunkName].sort());\n }\n }\n\n // Handle any unknown chunks (shouldn't happen, but be defensive)\n for (const chunkName of Object.keys(chunkData)) {\n if (!orderedChunks.has(chunkName)) {\n orderedChunks.set(chunkName, chunkData[chunkName].sort());\n }\n }\n\n return orderedChunks;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"definitions.js","names":[],"sources":["../../src/chunks/definitions.ts"],"sourcesContent":["/**\n * Style chunk definitions for CSS chunking optimization.\n *\n * Styles are grouped into chunks based on:\n * 1. Handler dependencies - styles that share a handler MUST be in the same chunk\n * 2. Logical grouping - related styles grouped for better cache reuse\n *\n * See STYLE_CHUNKING_SPEC.md for detailed rationale.\n *\n * ============================================================================\n * ⚠️ CRITICAL ARCHITECTURAL CONSTRAINT: NO CROSS-CHUNK HANDLER DEPENDENCIES\n * ============================================================================\n *\n * Style handlers declare their dependencies via `__lookupStyles` array.\n * This creates a dependency graph where handlers read multiple style props.\n *\n * **ALL styles in a handler's `__lookupStyles` MUST be in the SAME chunk.**\n *\n * Why this matters:\n * 1. Each chunk computes a cache key from ONLY its own style values\n * 2. If a handler reads a style from another chunk, that value isn't in the cache key\n * 3. Changing the cross-chunk style won't invalidate this chunk's cache\n * 4. Result: stale CSS output or incorrect cache hits\n *\n * Example of a violation:\n * ```\n * // flowStyle.__lookupStyles = ['display', 'flow']\n * // If 'display' is in DISPLAY chunk and 'flow' is in LAYOUT chunk:\n * // - User sets { display: 'grid', flow: 'column' }\n * // - LAYOUT chunk caches CSS with flow=column, display=grid\n * // - User changes to { display: 'flex', flow: 'column' }\n * // - LAYOUT chunk cache key unchanged (only has 'flow')\n * // - Returns stale CSS computed with display=grid!\n * ```\n *\n * Before adding/moving styles, verify:\n * 1. Find all handlers that use this style (grep for the style name in __lookupStyles)\n * 2. Ensure ALL styles from each handler's __lookupStyles are in the same chunk\n * ============================================================================\n */\n\nimport { isSelector } from '../pipeline';\n\n// ============================================================================\n// Chunk Style Lists\n// ============================================================================\n\n/**\n * Appearance chunk - visual styling with independent handlers\n */\nexport const APPEARANCE_CHUNK_STYLES = [\n 'fill', // fillStyle (independent)\n 'color', // colorStyle (independent)\n 'opacity', // independent\n 'border', // borderStyle (independent)\n 'radius', // radiusStyle (independent)\n 'outline', // outlineStyle: outline ↔ outlineOffset\n 'outlineOffset', // outlineStyle: outline ↔ outlineOffset\n 'shadow', // shadowStyle (independent)\n 'fade', // fadeStyle (independent)\n] as const;\n\n/**\n * Font chunk - typography styles\n *\n * Handler dependencies (all styles in each handler MUST stay in this chunk):\n * ⚠️ presetStyle: preset, fontSize, lineHeight, letterSpacing, textTransform,\n * fontWeight, fontStyle, font\n */\nexport const FONT_CHUNK_STYLES = [\n // All from presetStyle handler - MUST stay together\n 'preset',\n 'font',\n 'fontWeight',\n 'fontStyle',\n 'fontSize',\n 'lineHeight',\n 'letterSpacing',\n 'textTransform',\n // Independent text styles grouped for cohesion\n 'fontFamily', // independent alias (logical grouping with font styles)\n 'textAlign',\n 'textDecoration',\n 'wordBreak',\n 'wordWrap',\n 'boldFontWeight',\n] as const;\n\n/**\n * Dimension chunk - sizing and spacing\n *\n * Handler dependencies (all styles in each handler MUST stay in this chunk):\n * ⚠️ paddingStyle: padding, paddingTop/Right/Bottom/Left, paddingBlock/Inline\n * ⚠️ marginStyle: margin, marginTop/Right/Bottom/Left, marginBlock/Inline\n * ⚠️ widthStyle: width, minWidth, maxWidth\n * ⚠️ heightStyle: height, minHeight, maxHeight\n */\nexport const DIMENSION_CHUNK_STYLES = [\n // All from paddingStyle handler - MUST stay together\n 'padding',\n 'paddingTop',\n 'paddingRight',\n 'paddingBottom',\n 'paddingLeft',\n 'paddingBlock',\n 'paddingInline',\n // All from marginStyle handler - MUST stay together\n 'margin',\n 'marginTop',\n 'marginRight',\n 'marginBottom',\n 'marginLeft',\n 'marginBlock',\n 'marginInline',\n // widthStyle handler - MUST stay together\n 'width',\n 'minWidth',\n 'maxWidth',\n // heightStyle handler - MUST stay together\n 'height',\n 'minHeight',\n 'maxHeight',\n 'flexBasis',\n 'flexGrow',\n 'flexShrink',\n 'flex',\n] as const;\n\n/**\n * Display chunk - display mode, layout flow, text overflow, and scrollbar\n *\n * Handler dependencies (all styles in each handler MUST stay in this chunk):\n * ⚠️ displayStyle: display, hide, textOverflow, overflow, whiteSpace\n * ⚠️ flowStyle: display, flow\n * ⚠️ gapStyle: display, flow, gap\n * ⚠️ scrollbarStyle: scrollbar, overflow\n */\nexport const DISPLAY_CHUNK_STYLES = [\n // displayStyle handler\n 'display',\n 'hide',\n 'textOverflow',\n 'overflow', // also used by scrollbarStyle\n 'whiteSpace',\n // flowStyle handler (requires display)\n 'flow',\n // gapStyle handler (requires display, flow)\n 'gap',\n // scrollbarStyle handler (requires overflow)\n 'scrollbar',\n] as const;\n\n/**\n * Layout chunk - flex/grid alignment and grid templates\n *\n * Note: flow and gap are in DISPLAY chunk due to handler dependencies\n * (flowStyle and gapStyle both require 'display' prop).\n */\nexport const LAYOUT_CHUNK_STYLES = [\n // Alignment styles (all independent handlers)\n 'placeItems',\n 'placeContent',\n 'alignItems',\n 'alignContent',\n 'justifyItems',\n 'justifyContent',\n 'align', // alignStyle (independent)\n 'justify', // justifyStyle (independent)\n 'place', // placeStyle (independent)\n 'columnGap',\n 'rowGap',\n // Grid template styles\n 'gridColumns',\n 'gridRows',\n 'gridTemplate',\n 'gridAreas',\n 'gridAutoFlow',\n 'gridAutoColumns',\n 'gridAutoRows',\n] as const;\n\n/**\n * Position chunk - element positioning\n *\n * Handler dependencies (all styles in each handler MUST stay in this chunk):\n * ⚠️ insetStyle: inset, insetBlock, insetInline, top, right, bottom, left\n */\nexport const POSITION_CHUNK_STYLES = [\n 'position',\n // All from insetStyle handler - MUST stay together\n 'inset',\n 'insetBlock',\n 'insetInline',\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'zIndex',\n 'gridArea',\n 'gridColumn',\n 'gridRow',\n 'order',\n 'placeSelf',\n 'alignSelf',\n 'justifySelf',\n 'transform',\n 'transition',\n 'animation',\n] as const;\n\n// ============================================================================\n// Chunk Names\n// ============================================================================\n\nexport const CHUNK_NAMES = {\n /** Special chunk for styles that cannot be split */\n COMBINED: 'combined',\n SUBCOMPONENTS: 'subcomponents',\n APPEARANCE: 'appearance',\n FONT: 'font',\n DIMENSION: 'dimension',\n DISPLAY: 'display',\n LAYOUT: 'layout',\n POSITION: 'position',\n MISC: 'misc',\n} as const;\n\nexport type ChunkName = (typeof CHUNK_NAMES)[keyof typeof CHUNK_NAMES];\n\n// ============================================================================\n// Style-to-Chunk Lookup Map (O(1) categorization)\n// ============================================================================\n\n/**\n * Pre-computed map for O(1) style-to-chunk lookup.\n * Built once at module load time.\n */\nexport const STYLE_TO_CHUNK = new Map<string, ChunkName>();\n\n// Populate the lookup map\nfunction populateStyleToChunkMap() {\n for (const style of APPEARANCE_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.APPEARANCE);\n }\n for (const style of FONT_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.FONT);\n }\n for (const style of DIMENSION_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.DIMENSION);\n }\n for (const style of DISPLAY_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.DISPLAY);\n }\n for (const style of LAYOUT_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.LAYOUT);\n }\n for (const style of POSITION_CHUNK_STYLES) {\n STYLE_TO_CHUNK.set(style, CHUNK_NAMES.POSITION);\n }\n}\n\n// Initialize at module load\npopulateStyleToChunkMap();\n\n// ============================================================================\n// Chunk Priority Order\n// ============================================================================\n\n/**\n * Chunk processing order. This ensures deterministic className allocation\n * regardless of style key order in the input.\n */\nconst CHUNK_ORDER: readonly string[] = [\n CHUNK_NAMES.APPEARANCE,\n CHUNK_NAMES.FONT,\n CHUNK_NAMES.DIMENSION,\n CHUNK_NAMES.DISPLAY,\n CHUNK_NAMES.LAYOUT,\n CHUNK_NAMES.POSITION,\n CHUNK_NAMES.MISC,\n CHUNK_NAMES.SUBCOMPONENTS,\n] as const;\n\n/**\n * Map from chunk name to its priority index for sorting.\n */\nconst _CHUNK_PRIORITY = new Map<string, number>(\n CHUNK_ORDER.map((name, index) => [name, index]),\n);\n\n// ============================================================================\n// Chunk Info Interface\n// ============================================================================\n\nexport interface ChunkInfo {\n /** Name of the chunk */\n name: ChunkName | string;\n /** Style keys belonging to this chunk */\n styleKeys: string[];\n}\n\n// ============================================================================\n// Style Categorization\n// ============================================================================\n\n/**\n * Categorize style keys into chunks.\n *\n * Returns chunks in a deterministic order (by CHUNK_ORDER) regardless\n * of the order of keys in the input styles object.\n *\n * @param styles - The styles object to categorize\n * @returns Map of chunk name to array of style keys in that chunk (in priority order)\n */\nexport function categorizeStyleKeys(\n styles: Record<string, unknown>,\n): Map<string, string[]> {\n // First pass: collect keys into chunks (unordered)\n const chunkData: Record<string, string[]> = {};\n const keys = Object.keys(styles);\n\n for (const key of keys) {\n // Skip the $ helper key (used for selector combinators)\n // Skip @keyframes and @properties (processed separately in useStyles)\n // Skip recipe (resolved before pipeline by resolveRecipes)\n if (\n key === '$' ||\n key === '@keyframes' ||\n key === '@properties' ||\n key === '@fontFace' ||\n key === '@counterStyle' ||\n key === 'recipe'\n ) {\n continue;\n }\n\n if (isSelector(key)) {\n // All selectors go into the subcomponents chunk\n if (!chunkData[CHUNK_NAMES.SUBCOMPONENTS]) {\n chunkData[CHUNK_NAMES.SUBCOMPONENTS] = [];\n }\n chunkData[CHUNK_NAMES.SUBCOMPONENTS].push(key);\n } else {\n // Look up the chunk for this style, default to misc\n const chunkName = STYLE_TO_CHUNK.get(key) ?? CHUNK_NAMES.MISC;\n if (!chunkData[chunkName]) {\n chunkData[chunkName] = [];\n }\n chunkData[chunkName].push(key);\n }\n }\n\n // Second pass: build ordered Map based on CHUNK_ORDER\n const orderedChunks = new Map<string, string[]>();\n\n // Add chunks in priority order\n for (const chunkName of CHUNK_ORDER) {\n if (chunkData[chunkName] && chunkData[chunkName].length > 0) {\n // Sort keys within chunk for consistent cache key generation\n orderedChunks.set(chunkName, chunkData[chunkName].sort());\n }\n }\n\n // Handle any unknown chunks (shouldn't happen, but be defensive)\n for (const chunkName of Object.keys(chunkData)) {\n if (!orderedChunks.has(chunkName)) {\n orderedChunks.set(chunkName, chunkData[chunkName].sort());\n }\n }\n\n return orderedChunks;\n}\n"],"mappings":"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"}
|
package/dist/hooks/useStyles.js
CHANGED
|
@@ -3,7 +3,7 @@ import { stringifyStyles } from "../utils/styles.js";
|
|
|
3
3
|
import { extractLocalFontFace, fontFaceContentHash, formatFontFaceRule, hasLocalFontFace } from "../font-face/index.js";
|
|
4
4
|
import { extractLocalCounterStyle, formatCounterStyleRule, hasLocalCounterStyle } from "../counter-style/index.js";
|
|
5
5
|
import { getConfig, getGlobalKeyframes, hasGlobalKeyframes } from "../config.js";
|
|
6
|
-
import {
|
|
6
|
+
import { categorizeStyleKeys } from "../chunks/definitions.js";
|
|
7
7
|
import { generateChunkCacheKey } from "../chunks/cacheKey.js";
|
|
8
8
|
import { renderStylesForChunk } from "../chunks/renderChunk.js";
|
|
9
9
|
import { allocateClassName, counterStyle, fontFace, inject, keyframes, property } from "../injector/index.js";
|
|
@@ -19,17 +19,6 @@ import { useContext, useInsertionEffect, useMemo, useRef } from "react";
|
|
|
19
19
|
|
|
20
20
|
//#region src/hooks/useStyles.ts
|
|
21
21
|
/**
|
|
22
|
-
* Check if styles contain @starting-style rules.
|
|
23
|
-
*
|
|
24
|
-
* @starting-style CSS cannot be applied via multiple class names because
|
|
25
|
-
* of cascade - later rules override earlier ones. When @starting is detected,
|
|
26
|
-
* we combine top-level styles into a single chunk but keep sub-element styles
|
|
27
|
-
* in their own chunk for better caching.
|
|
28
|
-
*/
|
|
29
|
-
function containsStartingStyle(styleKey) {
|
|
30
|
-
return styleKey.includes("@starting");
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
22
|
* Render, cache-key, and allocate a className for a single chunk.
|
|
34
23
|
* Returns a ProcessedChunk, or null if the chunk produces no CSS rules.
|
|
35
24
|
*
|
|
@@ -54,26 +43,6 @@ function processChunk(styles, chunkName, styleKeys) {
|
|
|
54
43
|
};
|
|
55
44
|
}
|
|
56
45
|
/**
|
|
57
|
-
* Merge chunk map entries for @starting-style partial chunking.
|
|
58
|
-
*
|
|
59
|
-
* All non-subcomponent chunks are merged into a single COMBINED entry,
|
|
60
|
-
* while SUBCOMPONENTS stays separate. This preserves CSS cascade for
|
|
61
|
-
* @starting-style while still allowing sub-element styles to cache independently.
|
|
62
|
-
*/
|
|
63
|
-
function mergeChunksForStartingStyle(chunkMap) {
|
|
64
|
-
const merged = /* @__PURE__ */ new Map();
|
|
65
|
-
const combinedKeys = [];
|
|
66
|
-
for (const [chunkName, keys] of chunkMap) if (chunkName === CHUNK_NAMES.SUBCOMPONENTS) merged.set(CHUNK_NAMES.SUBCOMPONENTS, keys);
|
|
67
|
-
else combinedKeys.push(...keys);
|
|
68
|
-
if (combinedKeys.length > 0) {
|
|
69
|
-
const result = /* @__PURE__ */ new Map();
|
|
70
|
-
result.set(CHUNK_NAMES.COMBINED, combinedKeys);
|
|
71
|
-
for (const [k, v] of merged) result.set(k, v);
|
|
72
|
-
return result;
|
|
73
|
-
}
|
|
74
|
-
return merged;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
46
|
* Get keyframes that are actually used in styles.
|
|
78
47
|
* Returns null if no keyframes are used (fast path for zero overhead).
|
|
79
48
|
*
|
|
@@ -167,8 +136,7 @@ function useStyles(styles) {
|
|
|
167
136
|
const processedChunks = useMemo(() => {
|
|
168
137
|
const currentStyles = stylesRef.current.styles;
|
|
169
138
|
if (!styleKey || !currentStyles) return [];
|
|
170
|
-
|
|
171
|
-
if (containsStartingStyle(styleKey)) chunkMap = mergeChunksForStartingStyle(chunkMap);
|
|
139
|
+
const chunkMap = categorizeStyleKeys(currentStyles);
|
|
172
140
|
const chunks = [];
|
|
173
141
|
if (ssrCollector) {
|
|
174
142
|
ssrCollector.collectInternals();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStyles.js","names":[],"sources":["../../src/hooks/useStyles.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo, useRef } from 'react';\n\nimport {\n categorizeStyleKeys,\n CHUNK_NAMES,\n generateChunkCacheKey,\n renderStylesForChunk,\n} from '../chunks';\nimport { getConfig, getGlobalKeyframes, hasGlobalKeyframes } from '../config';\nimport {\n allocateClassName,\n counterStyle,\n fontFace,\n inject,\n keyframes,\n property,\n} from '../injector';\nimport type { FontFaceDescriptors, KeyframesSteps } from '../injector/types';\nimport {\n extractLocalCounterStyle,\n formatCounterStyleRule,\n hasLocalCounterStyle,\n} from '../counter-style';\nimport {\n extractLocalFontFace,\n fontFaceContentHash,\n formatFontFaceRule,\n hasLocalFontFace,\n} from '../font-face';\nimport {\n extractAnimationNamesFromStyles,\n extractLocalKeyframes,\n filterUsedKeyframes,\n hasLocalKeyframes,\n mergeKeyframes,\n replaceAnimationNames,\n} from '../keyframes';\nimport type { RenderResult } from '../pipeline';\nimport { extractLocalProperties, hasLocalProperties } from '../properties';\nimport { collectAutoInferredProperties } from '../ssr/collect-auto-properties';\nimport type { ServerStyleCollector } from '../ssr/collector';\nimport { resolveSSRCollector } from './resolve-ssr-collector';\nimport { TastySSRContext } from '../ssr/context';\nimport { formatKeyframesCSS } from '../ssr/format-keyframes';\nimport { formatPropertyCSS } from '../ssr/format-property';\nimport type { Styles } from '../styles/types';\nimport { hasKeys } from '../utils/has-keys';\nimport { resolveRecipes } from '../utils/resolve-recipes';\nimport { stringifyStyles } from '../utils/styles';\n\n/**\n * Check if styles contain @starting-style rules.\n *\n * @starting-style CSS cannot be applied via multiple class names because\n * of cascade - later rules override earlier ones. When @starting is detected,\n * we combine top-level styles into a single chunk but keep sub-element styles\n * in their own chunk for better caching.\n */\nfunction containsStartingStyle(styleKey: string): boolean {\n return styleKey.includes('@starting');\n}\n\n/**\n * Tasty styles object to generate CSS classes for.\n * Can be undefined or empty object for no styles.\n */\nexport type UseStylesOptions = Styles | undefined;\n\nexport interface UseStylesResult {\n /**\n * Generated className(s) to apply to the element.\n * Can be empty string if no styles are provided.\n * With chunking enabled, may contain multiple space-separated class names.\n */\n className: string;\n}\n\n/**\n * Information about a processed chunk\n */\ninterface ProcessedChunk {\n name: string;\n styleKeys: string[];\n cacheKey: string;\n renderResult: RenderResult;\n className: string;\n}\n\n/**\n * Render, cache-key, and allocate a className for a single chunk.\n * Returns a ProcessedChunk, or null if the chunk produces no CSS rules.\n *\n * Always runs the pipeline and calls allocateClassName. The inject()\n * call in useInsertionEffect handles all edge cases: placeholders from\n * abandoned concurrent renders, hydration hits (ruleIndex -2), and\n * runtime cache hits (already injected). The pipeline's own LRU cache\n * makes repeated calls for identical styles cheap.\n */\nfunction processChunk(\n styles: Styles,\n chunkName: string,\n styleKeys: string[],\n): ProcessedChunk | null {\n if (styleKeys.length === 0) return null;\n\n const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys);\n const renderResult = renderStylesForChunk(\n styles,\n chunkName,\n styleKeys,\n cacheKey,\n );\n if (renderResult.rules.length === 0) return null;\n\n const { className } = allocateClassName(cacheKey);\n\n return { name: chunkName, styleKeys, cacheKey, renderResult, className };\n}\n\n/**\n * Merge chunk map entries for @starting-style partial chunking.\n *\n * All non-subcomponent chunks are merged into a single COMBINED entry,\n * while SUBCOMPONENTS stays separate. This preserves CSS cascade for\n * @starting-style while still allowing sub-element styles to cache independently.\n */\nfunction mergeChunksForStartingStyle(\n chunkMap: Map<string, string[]>,\n): Map<string, string[]> {\n const merged = new Map<string, string[]>();\n const combinedKeys: string[] = [];\n\n for (const [chunkName, keys] of chunkMap) {\n if (chunkName === CHUNK_NAMES.SUBCOMPONENTS) {\n merged.set(CHUNK_NAMES.SUBCOMPONENTS, keys);\n } else {\n combinedKeys.push(...keys);\n }\n }\n\n if (combinedKeys.length > 0) {\n // Insert COMBINED first so it appears before SUBCOMPONENTS\n const result = new Map<string, string[]>();\n result.set(CHUNK_NAMES.COMBINED, combinedKeys);\n for (const [k, v] of merged) {\n result.set(k, v);\n }\n return result;\n }\n\n return merged;\n}\n\n/**\n * Get keyframes that are actually used in styles.\n * Returns null if no keyframes are used (fast path for zero overhead).\n *\n * Optimization order:\n * 1. Check if any keyframes are defined (local or global) - if not, return null\n * 2. Extract animation names from styles - if none, return null\n * 3. Merge and filter keyframes to only used ones\n */\nfunction getUsedKeyframes(\n styles: Styles,\n): Record<string, KeyframesSteps> | null {\n // Fast path: no keyframes defined anywhere\n const hasLocal = hasLocalKeyframes(styles);\n const hasGlobal = hasGlobalKeyframes();\n if (!hasLocal && !hasGlobal) return null;\n\n // Extract animation names from styles (not from rendered CSS - faster)\n const usedNames = extractAnimationNamesFromStyles(styles);\n if (usedNames.size === 0) return null;\n\n // Merge local and global keyframes\n const local = hasLocal ? extractLocalKeyframes(styles) : null;\n const global = hasGlobal ? getGlobalKeyframes() : null;\n const allKeyframes = mergeKeyframes(local, global);\n\n // Filter to only used keyframes\n return filterUsedKeyframes(allKeyframes, usedNames);\n}\n\n/**\n * Process a chunk on the SSR path: allocate via collector, render, collect CSS.\n * Returns null if the chunk produces no CSS rules.\n */\nfunction processChunkSSR(\n collector: ServerStyleCollector,\n styles: Styles,\n chunkName: string,\n styleKeys: string[],\n): ProcessedChunk | null {\n if (styleKeys.length === 0) return null;\n\n const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys);\n const { className, isNewAllocation } = collector.allocateClassName(cacheKey);\n\n if (isNewAllocation) {\n const renderResult = renderStylesForChunk(styles, chunkName, styleKeys);\n if (renderResult.rules.length > 0) {\n collector.collectChunk(cacheKey, className, renderResult.rules);\n return {\n name: chunkName,\n styleKeys,\n cacheKey,\n renderResult,\n className,\n };\n }\n return null;\n }\n\n return {\n name: chunkName,\n styleKeys,\n cacheKey,\n renderResult: { rules: [] },\n className,\n };\n}\n\n/**\n * Hook to generate CSS classes from Tasty styles.\n * Handles style rendering, className allocation, and CSS injection.\n *\n * SSR-aware: when a ServerStyleCollector is available (via React context\n * or AsyncLocalStorage), CSS is collected during the render phase instead\n * of being injected into the DOM. useInsertionEffect does not run on the\n * server, so the collector path is the only active path during SSR.\n *\n * Uses chunking to split styles into logical groups for better caching\n * and CSS reuse across components.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { className } = useStyles({\n * padding: '2x',\n * fill: '#purple',\n * radius: '1r',\n * });\n *\n * return <div className={className}>Styled content</div>;\n * }\n * ```\n */\nexport function useStyles(styles: UseStylesOptions): UseStylesResult {\n const ssrContextValue = useContext(TastySSRContext);\n const ssrCollector = resolveSSRCollector(ssrContextValue);\n\n // Array of dispose functions for each chunk\n const disposeRef = useRef<(() => void)[]>([]);\n\n // Store styles by their stringified key to avoid recomputing when only reference changes\n const stylesRef = useRef<{ key: string; styles: Styles | undefined }>({\n key: '',\n styles: undefined,\n });\n\n // Resolve recipes before any processing (zero overhead if no recipes configured)\n const resolvedStyles = useMemo(() => {\n if (!styles) return styles;\n return resolveRecipes(styles);\n }, [styles]);\n\n // Compute style key - this is a primitive string that captures style content\n const styleKey = useMemo(() => {\n if (!resolvedStyles || !hasKeys(resolvedStyles)) {\n return '';\n }\n return stringifyStyles(resolvedStyles);\n }, [resolvedStyles]);\n\n // Update ref when styleKey changes (content actually changed)\n if (stylesRef.current.key !== styleKey) {\n stylesRef.current = { key: styleKey, styles: resolvedStyles };\n }\n\n // Process chunks: categorize, generate cache keys, render, and allocate classNames\n // Only depends on styleKey (primitive), not styles object reference\n const processedChunks: ProcessedChunk[] = useMemo(() => {\n const currentStyles = stylesRef.current.styles;\n if (!styleKey || !currentStyles) {\n return [];\n }\n\n // Categorize style keys into chunks\n let chunkMap = categorizeStyleKeys(\n currentStyles as Record<string, unknown>,\n );\n\n // Partial chunking for styles containing @starting-style rules.\n // @starting-style CSS cannot work with multiple class names due to cascade,\n // so we merge all top-level chunks into one but keep sub-element styles separate.\n if (containsStartingStyle(styleKey)) {\n chunkMap = mergeChunksForStartingStyle(chunkMap);\n }\n\n const chunks: ProcessedChunk[] = [];\n\n if (ssrCollector) {\n // Ensure internal @property and :root token CSS is included\n ssrCollector.collectInternals();\n\n // SERVER PATH: allocate via collector, collect CSS\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n const chunk = processChunkSSR(\n ssrCollector,\n currentStyles,\n chunkName,\n chunkStyleKeys,\n );\n if (chunk) chunks.push(chunk);\n }\n\n // Collect keyframes on the server\n const usedKeyframes = getUsedKeyframes(currentStyles);\n if (usedKeyframes) {\n for (const [name, steps] of Object.entries(usedKeyframes)) {\n const css = formatKeyframesCSS(name, steps);\n ssrCollector.collectKeyframes(name, css);\n }\n }\n\n // Collect explicit @property rules on the server\n if (hasLocalProperties(currentStyles)) {\n const localProperties = extractLocalProperties(currentStyles);\n if (localProperties) {\n for (const [token, definition] of Object.entries(localProperties)) {\n const css = formatPropertyCSS(token, definition);\n if (css) {\n ssrCollector.collectProperty(token, css);\n }\n }\n }\n }\n\n // Collect @font-face rules on the server\n if (hasLocalFontFace(currentStyles)) {\n const localFontFace = extractLocalFontFace(currentStyles);\n if (localFontFace) {\n for (const [family, input] of Object.entries(localFontFace)) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n const hash = fontFaceContentHash(family, desc);\n const css = formatFontFaceRule(family, desc);\n ssrCollector.collectFontFace(hash, css);\n }\n }\n }\n }\n\n // Collect @counter-style rules on the server\n if (hasLocalCounterStyle(currentStyles)) {\n const localCounterStyle = extractLocalCounterStyle(currentStyles);\n if (localCounterStyle) {\n for (const [name, descriptors] of Object.entries(localCounterStyle)) {\n const css = formatCounterStyleRule(name, descriptors);\n ssrCollector.collectCounterStyle(name, css);\n }\n }\n }\n\n // Auto-infer @property types from rendered declarations (SSR).\n // On the client this happens inside StyleInjector.inject(), which\n // runs in useInsertionEffect and therefore never executes on the server.\n if (getConfig().autoPropertyTypes !== false) {\n const allRules = chunks.flatMap((c) => c.renderResult.rules);\n if (allRules.length > 0) {\n collectAutoInferredProperties(allRules, ssrCollector, currentStyles);\n }\n }\n } else {\n // CLIENT PATH: unchanged behavior\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n const chunk = processChunk(currentStyles, chunkName, chunkStyleKeys);\n if (chunk) chunks.push(chunk);\n }\n }\n\n return chunks;\n }, [styleKey]);\n\n // Inject styles in insertion effect (avoids render phase side effects).\n // Does NOT run on the server — the SSR path above handles collection.\n useInsertionEffect(() => {\n // Cleanup all previous disposals\n disposeRef.current.forEach((dispose) => dispose?.());\n disposeRef.current = [];\n\n // Fast path: no chunks to inject\n if (processedChunks.length === 0) {\n return;\n }\n\n const currentStyles = stylesRef.current.styles;\n\n // Register explicit @properties first so they take precedence over auto-inference\n // that happens during keyframe and style chunk injection.\n // Token formats: $name → --name, #name → --name-color (with auto syntax: '<color>')\n // Note: Global properties are injected once when styles are first generated (see markStylesGenerated)\n if (currentStyles && hasLocalProperties(currentStyles)) {\n const localProperties = extractLocalProperties(currentStyles);\n if (localProperties) {\n for (const [token, definition] of Object.entries(localProperties)) {\n property(token, definition);\n }\n }\n }\n\n // Inject @font-face rules (permanent, no cleanup needed)\n if (currentStyles && hasLocalFontFace(currentStyles)) {\n const localFontFace = extractLocalFontFace(currentStyles);\n if (localFontFace) {\n for (const [family, input] of Object.entries(localFontFace)) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n fontFace(family, desc);\n }\n }\n }\n }\n\n // Inject @counter-style rules (permanent, no cleanup needed)\n if (currentStyles && hasLocalCounterStyle(currentStyles)) {\n const localCounterStyle = extractLocalCounterStyle(currentStyles);\n if (localCounterStyle) {\n for (const [name, descriptors] of Object.entries(localCounterStyle)) {\n counterStyle(name, descriptors);\n }\n }\n }\n\n // Get keyframes that are actually used (returns null if none - zero overhead)\n const usedKeyframes = currentStyles\n ? getUsedKeyframes(currentStyles)\n : null;\n\n // Inject keyframes and build name map (only if we have keyframes)\n let nameMap: Map<string, string> | null = null;\n\n if (usedKeyframes) {\n nameMap = new Map();\n for (const [name, steps] of Object.entries(usedKeyframes)) {\n const result = keyframes(steps, { name });\n const injectedName = result.toString();\n // Only add to map if name differs (optimization for replacement check)\n if (injectedName !== name) {\n nameMap.set(name, injectedName);\n }\n disposeRef.current.push(result.dispose);\n }\n // Clear map if no replacements needed\n if (nameMap.size === 0) {\n nameMap = null;\n }\n }\n\n // Inject each chunk\n for (const chunk of processedChunks) {\n if (chunk.renderResult.rules.length > 0) {\n // Replace animation names only if needed\n const rulesToInject = nameMap\n ? chunk.renderResult.rules.map((rule) => ({\n ...rule,\n declarations: replaceAnimationNames(rule.declarations, nameMap!),\n }))\n : chunk.renderResult.rules;\n\n const { dispose } = inject(rulesToInject, {\n cacheKey: chunk.cacheKey,\n });\n disposeRef.current.push(dispose);\n }\n }\n\n return () => {\n disposeRef.current.forEach((dispose) => dispose?.());\n disposeRef.current = [];\n };\n }, [processedChunks]);\n\n // Combine all chunk classNames\n const className = useMemo(() => {\n return processedChunks.map((chunk) => chunk.className).join(' ');\n }, [processedChunks]);\n\n return {\n className,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useStyles.js","names":[],"sources":["../../src/hooks/useStyles.ts"],"sourcesContent":["import { useContext, useInsertionEffect, useMemo, useRef } from 'react';\n\nimport {\n categorizeStyleKeys,\n generateChunkCacheKey,\n renderStylesForChunk,\n} from '../chunks';\nimport { getConfig, getGlobalKeyframes, hasGlobalKeyframes } from '../config';\nimport {\n allocateClassName,\n counterStyle,\n fontFace,\n inject,\n keyframes,\n property,\n} from '../injector';\nimport type { FontFaceDescriptors, KeyframesSteps } from '../injector/types';\nimport {\n extractLocalCounterStyle,\n formatCounterStyleRule,\n hasLocalCounterStyle,\n} from '../counter-style';\nimport {\n extractLocalFontFace,\n fontFaceContentHash,\n formatFontFaceRule,\n hasLocalFontFace,\n} from '../font-face';\nimport {\n extractAnimationNamesFromStyles,\n extractLocalKeyframes,\n filterUsedKeyframes,\n hasLocalKeyframes,\n mergeKeyframes,\n replaceAnimationNames,\n} from '../keyframes';\nimport type { RenderResult } from '../pipeline';\nimport { extractLocalProperties, hasLocalProperties } from '../properties';\nimport { collectAutoInferredProperties } from '../ssr/collect-auto-properties';\nimport type { ServerStyleCollector } from '../ssr/collector';\nimport { resolveSSRCollector } from './resolve-ssr-collector';\nimport { TastySSRContext } from '../ssr/context';\nimport { formatKeyframesCSS } from '../ssr/format-keyframes';\nimport { formatPropertyCSS } from '../ssr/format-property';\nimport type { Styles } from '../styles/types';\nimport { hasKeys } from '../utils/has-keys';\nimport { resolveRecipes } from '../utils/resolve-recipes';\nimport { stringifyStyles } from '../utils/styles';\n\n/**\n * Tasty styles object to generate CSS classes for.\n * Can be undefined or empty object for no styles.\n */\nexport type UseStylesOptions = Styles | undefined;\n\nexport interface UseStylesResult {\n /**\n * Generated className(s) to apply to the element.\n * Can be empty string if no styles are provided.\n * With chunking enabled, may contain multiple space-separated class names.\n */\n className: string;\n}\n\n/**\n * Information about a processed chunk\n */\ninterface ProcessedChunk {\n name: string;\n styleKeys: string[];\n cacheKey: string;\n renderResult: RenderResult;\n className: string;\n}\n\n/**\n * Render, cache-key, and allocate a className for a single chunk.\n * Returns a ProcessedChunk, or null if the chunk produces no CSS rules.\n *\n * Always runs the pipeline and calls allocateClassName. The inject()\n * call in useInsertionEffect handles all edge cases: placeholders from\n * abandoned concurrent renders, hydration hits (ruleIndex -2), and\n * runtime cache hits (already injected). The pipeline's own LRU cache\n * makes repeated calls for identical styles cheap.\n */\nfunction processChunk(\n styles: Styles,\n chunkName: string,\n styleKeys: string[],\n): ProcessedChunk | null {\n if (styleKeys.length === 0) return null;\n\n const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys);\n const renderResult = renderStylesForChunk(\n styles,\n chunkName,\n styleKeys,\n cacheKey,\n );\n if (renderResult.rules.length === 0) return null;\n\n const { className } = allocateClassName(cacheKey);\n\n return { name: chunkName, styleKeys, cacheKey, renderResult, className };\n}\n\n/**\n * Get keyframes that are actually used in styles.\n * Returns null if no keyframes are used (fast path for zero overhead).\n *\n * Optimization order:\n * 1. Check if any keyframes are defined (local or global) - if not, return null\n * 2. Extract animation names from styles - if none, return null\n * 3. Merge and filter keyframes to only used ones\n */\nfunction getUsedKeyframes(\n styles: Styles,\n): Record<string, KeyframesSteps> | null {\n // Fast path: no keyframes defined anywhere\n const hasLocal = hasLocalKeyframes(styles);\n const hasGlobal = hasGlobalKeyframes();\n if (!hasLocal && !hasGlobal) return null;\n\n // Extract animation names from styles (not from rendered CSS - faster)\n const usedNames = extractAnimationNamesFromStyles(styles);\n if (usedNames.size === 0) return null;\n\n // Merge local and global keyframes\n const local = hasLocal ? extractLocalKeyframes(styles) : null;\n const global = hasGlobal ? getGlobalKeyframes() : null;\n const allKeyframes = mergeKeyframes(local, global);\n\n // Filter to only used keyframes\n return filterUsedKeyframes(allKeyframes, usedNames);\n}\n\n/**\n * Process a chunk on the SSR path: allocate via collector, render, collect CSS.\n * Returns null if the chunk produces no CSS rules.\n */\nfunction processChunkSSR(\n collector: ServerStyleCollector,\n styles: Styles,\n chunkName: string,\n styleKeys: string[],\n): ProcessedChunk | null {\n if (styleKeys.length === 0) return null;\n\n const cacheKey = generateChunkCacheKey(styles, chunkName, styleKeys);\n const { className, isNewAllocation } = collector.allocateClassName(cacheKey);\n\n if (isNewAllocation) {\n const renderResult = renderStylesForChunk(styles, chunkName, styleKeys);\n if (renderResult.rules.length > 0) {\n collector.collectChunk(cacheKey, className, renderResult.rules);\n return {\n name: chunkName,\n styleKeys,\n cacheKey,\n renderResult,\n className,\n };\n }\n return null;\n }\n\n return {\n name: chunkName,\n styleKeys,\n cacheKey,\n renderResult: { rules: [] },\n className,\n };\n}\n\n/**\n * Hook to generate CSS classes from Tasty styles.\n * Handles style rendering, className allocation, and CSS injection.\n *\n * SSR-aware: when a ServerStyleCollector is available (via React context\n * or AsyncLocalStorage), CSS is collected during the render phase instead\n * of being injected into the DOM. useInsertionEffect does not run on the\n * server, so the collector path is the only active path during SSR.\n *\n * Uses chunking to split styles into logical groups for better caching\n * and CSS reuse across components.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { className } = useStyles({\n * padding: '2x',\n * fill: '#purple',\n * radius: '1r',\n * });\n *\n * return <div className={className}>Styled content</div>;\n * }\n * ```\n */\nexport function useStyles(styles: UseStylesOptions): UseStylesResult {\n const ssrContextValue = useContext(TastySSRContext);\n const ssrCollector = resolveSSRCollector(ssrContextValue);\n\n // Array of dispose functions for each chunk\n const disposeRef = useRef<(() => void)[]>([]);\n\n // Store styles by their stringified key to avoid recomputing when only reference changes\n const stylesRef = useRef<{ key: string; styles: Styles | undefined }>({\n key: '',\n styles: undefined,\n });\n\n // Resolve recipes before any processing (zero overhead if no recipes configured)\n const resolvedStyles = useMemo(() => {\n if (!styles) return styles;\n return resolveRecipes(styles);\n }, [styles]);\n\n // Compute style key - this is a primitive string that captures style content\n const styleKey = useMemo(() => {\n if (!resolvedStyles || !hasKeys(resolvedStyles)) {\n return '';\n }\n return stringifyStyles(resolvedStyles);\n }, [resolvedStyles]);\n\n // Update ref when styleKey changes (content actually changed)\n if (stylesRef.current.key !== styleKey) {\n stylesRef.current = { key: styleKey, styles: resolvedStyles };\n }\n\n // Process chunks: categorize, generate cache keys, render, and allocate classNames\n // Only depends on styleKey (primitive), not styles object reference\n const processedChunks: ProcessedChunk[] = useMemo(() => {\n const currentStyles = stylesRef.current.styles;\n if (!styleKey || !currentStyles) {\n return [];\n }\n\n // Categorize style keys into chunks\n const chunkMap = categorizeStyleKeys(\n currentStyles as Record<string, unknown>,\n );\n\n const chunks: ProcessedChunk[] = [];\n\n if (ssrCollector) {\n // Ensure internal @property and :root token CSS is included\n ssrCollector.collectInternals();\n\n // SERVER PATH: allocate via collector, collect CSS\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n const chunk = processChunkSSR(\n ssrCollector,\n currentStyles,\n chunkName,\n chunkStyleKeys,\n );\n if (chunk) chunks.push(chunk);\n }\n\n // Collect keyframes on the server\n const usedKeyframes = getUsedKeyframes(currentStyles);\n if (usedKeyframes) {\n for (const [name, steps] of Object.entries(usedKeyframes)) {\n const css = formatKeyframesCSS(name, steps);\n ssrCollector.collectKeyframes(name, css);\n }\n }\n\n // Collect explicit @property rules on the server\n if (hasLocalProperties(currentStyles)) {\n const localProperties = extractLocalProperties(currentStyles);\n if (localProperties) {\n for (const [token, definition] of Object.entries(localProperties)) {\n const css = formatPropertyCSS(token, definition);\n if (css) {\n ssrCollector.collectProperty(token, css);\n }\n }\n }\n }\n\n // Collect @font-face rules on the server\n if (hasLocalFontFace(currentStyles)) {\n const localFontFace = extractLocalFontFace(currentStyles);\n if (localFontFace) {\n for (const [family, input] of Object.entries(localFontFace)) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n const hash = fontFaceContentHash(family, desc);\n const css = formatFontFaceRule(family, desc);\n ssrCollector.collectFontFace(hash, css);\n }\n }\n }\n }\n\n // Collect @counter-style rules on the server\n if (hasLocalCounterStyle(currentStyles)) {\n const localCounterStyle = extractLocalCounterStyle(currentStyles);\n if (localCounterStyle) {\n for (const [name, descriptors] of Object.entries(localCounterStyle)) {\n const css = formatCounterStyleRule(name, descriptors);\n ssrCollector.collectCounterStyle(name, css);\n }\n }\n }\n\n // Auto-infer @property types from rendered declarations (SSR).\n // On the client this happens inside StyleInjector.inject(), which\n // runs in useInsertionEffect and therefore never executes on the server.\n if (getConfig().autoPropertyTypes !== false) {\n const allRules = chunks.flatMap((c) => c.renderResult.rules);\n if (allRules.length > 0) {\n collectAutoInferredProperties(allRules, ssrCollector, currentStyles);\n }\n }\n } else {\n // CLIENT PATH: unchanged behavior\n for (const [chunkName, chunkStyleKeys] of chunkMap) {\n const chunk = processChunk(currentStyles, chunkName, chunkStyleKeys);\n if (chunk) chunks.push(chunk);\n }\n }\n\n return chunks;\n }, [styleKey]);\n\n // Inject styles in insertion effect (avoids render phase side effects).\n // Does NOT run on the server — the SSR path above handles collection.\n useInsertionEffect(() => {\n // Cleanup all previous disposals\n disposeRef.current.forEach((dispose) => dispose?.());\n disposeRef.current = [];\n\n // Fast path: no chunks to inject\n if (processedChunks.length === 0) {\n return;\n }\n\n const currentStyles = stylesRef.current.styles;\n\n // Register explicit @properties first so they take precedence over auto-inference\n // that happens during keyframe and style chunk injection.\n // Token formats: $name → --name, #name → --name-color (with auto syntax: '<color>')\n // Note: Global properties are injected once when styles are first generated (see markStylesGenerated)\n if (currentStyles && hasLocalProperties(currentStyles)) {\n const localProperties = extractLocalProperties(currentStyles);\n if (localProperties) {\n for (const [token, definition] of Object.entries(localProperties)) {\n property(token, definition);\n }\n }\n }\n\n // Inject @font-face rules (permanent, no cleanup needed)\n if (currentStyles && hasLocalFontFace(currentStyles)) {\n const localFontFace = extractLocalFontFace(currentStyles);\n if (localFontFace) {\n for (const [family, input] of Object.entries(localFontFace)) {\n const descriptors: FontFaceDescriptors[] = Array.isArray(input)\n ? input\n : [input];\n for (const desc of descriptors) {\n fontFace(family, desc);\n }\n }\n }\n }\n\n // Inject @counter-style rules (permanent, no cleanup needed)\n if (currentStyles && hasLocalCounterStyle(currentStyles)) {\n const localCounterStyle = extractLocalCounterStyle(currentStyles);\n if (localCounterStyle) {\n for (const [name, descriptors] of Object.entries(localCounterStyle)) {\n counterStyle(name, descriptors);\n }\n }\n }\n\n // Get keyframes that are actually used (returns null if none - zero overhead)\n const usedKeyframes = currentStyles\n ? getUsedKeyframes(currentStyles)\n : null;\n\n // Inject keyframes and build name map (only if we have keyframes)\n let nameMap: Map<string, string> | null = null;\n\n if (usedKeyframes) {\n nameMap = new Map();\n for (const [name, steps] of Object.entries(usedKeyframes)) {\n const result = keyframes(steps, { name });\n const injectedName = result.toString();\n // Only add to map if name differs (optimization for replacement check)\n if (injectedName !== name) {\n nameMap.set(name, injectedName);\n }\n disposeRef.current.push(result.dispose);\n }\n // Clear map if no replacements needed\n if (nameMap.size === 0) {\n nameMap = null;\n }\n }\n\n // Inject each chunk\n for (const chunk of processedChunks) {\n if (chunk.renderResult.rules.length > 0) {\n // Replace animation names only if needed\n const rulesToInject = nameMap\n ? chunk.renderResult.rules.map((rule) => ({\n ...rule,\n declarations: replaceAnimationNames(rule.declarations, nameMap!),\n }))\n : chunk.renderResult.rules;\n\n const { dispose } = inject(rulesToInject, {\n cacheKey: chunk.cacheKey,\n });\n disposeRef.current.push(dispose);\n }\n }\n\n return () => {\n disposeRef.current.forEach((dispose) => dispose?.());\n disposeRef.current = [];\n };\n }, [processedChunks]);\n\n // Combine all chunk classNames\n const className = useMemo(() => {\n return processedChunks.map((chunk) => chunk.className).join(' ');\n }, [processedChunks]);\n\n return {\n className,\n };\n}\n"],"mappings":"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"}
|
|
@@ -94,7 +94,7 @@ var SheetManager = class {
|
|
|
94
94
|
const groupMap = /* @__PURE__ */ new Map();
|
|
95
95
|
const atKey = (at) => at && at.length ? at.join("|") : "";
|
|
96
96
|
flattenedRules.forEach((r) => {
|
|
97
|
-
const key = `${atKey(r.atRules)}||${r.selector}`;
|
|
97
|
+
const key = `${atKey(r.atRules)}||${r.selector}||${r.startingStyle ? "1" : "0"}`;
|
|
98
98
|
const existing = groupMap.get(key);
|
|
99
99
|
if (existing) existing.declarations = existing.declarations ? `${existing.declarations} ${r.declarations}` : r.declarations;
|
|
100
100
|
else {
|
|
@@ -102,6 +102,7 @@ var SheetManager = class {
|
|
|
102
102
|
idx: groupedRules.length,
|
|
103
103
|
selector: r.selector,
|
|
104
104
|
atRules: r.atRules,
|
|
105
|
+
startingStyle: r.startingStyle,
|
|
105
106
|
declarations: r.declarations
|
|
106
107
|
});
|
|
107
108
|
groupedRules.push({ ...r });
|
|
@@ -111,6 +112,7 @@ var SheetManager = class {
|
|
|
111
112
|
groupedRules[val.idx] = {
|
|
112
113
|
selector: val.selector,
|
|
113
114
|
atRules: val.atRules,
|
|
115
|
+
startingStyle: val.startingStyle,
|
|
114
116
|
declarations: val.declarations
|
|
115
117
|
};
|
|
116
118
|
});
|
|
@@ -121,7 +123,8 @@ var SheetManager = class {
|
|
|
121
123
|
let lastInsertedIndex = null;
|
|
122
124
|
for (const rule of groupedRules) {
|
|
123
125
|
const declarations = rule.declarations;
|
|
124
|
-
const
|
|
126
|
+
const innerContent = rule.startingStyle ? `@starting-style { ${declarations} }` : declarations;
|
|
127
|
+
const baseRule = `${rule.selector} { ${innerContent} }`;
|
|
125
128
|
let fullRule = baseRule;
|
|
126
129
|
if (rule.atRules && rule.atRules.length > 0) fullRule = rule.atRules.reduce((css, atRule) => `${atRule} { ${css} }`, baseRule);
|
|
127
130
|
const styleElement = targetSheet.sheet;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sheet-manager.js","names":[],"sources":["../../src/injector/sheet-manager.ts"],"sourcesContent":["import { PropertyTypeResolver } from '../properties/property-type-resolver';\nimport { createStyle, STYLE_HANDLER_MAP } from '../styles';\n\nimport type {\n CacheMetrics,\n KeyframesInfo,\n KeyframesSteps,\n RawCSSInfo,\n RawCSSResult,\n RootRegistry,\n RuleInfo,\n SheetInfo,\n StyleInjectorConfig,\n StyleRule,\n} from './types';\n\nimport type { CSSMap, StyleHandler, StyleValueStateMap } from '../utils/styles';\n\nexport class SheetManager {\n private rootRegistries = new WeakMap<Document | ShadowRoot, RootRegistry>();\n private config: StyleInjectorConfig;\n /** Dedicated style elements for raw CSS per root */\n private rawStyleElements = new WeakMap<\n Document | ShadowRoot,\n HTMLStyleElement\n >();\n /** Tracking for raw CSS blocks per root */\n private rawCSSBlocks = new WeakMap<\n Document | ShadowRoot,\n Map<string, RawCSSInfo>\n >();\n /** Counter for generating unique raw CSS IDs */\n private rawCSSCounter = 0;\n\n constructor(config: StyleInjectorConfig) {\n this.config = config;\n }\n\n /**\n * Get or create registry for a root (Document or ShadowRoot)\n */\n getRegistry(root: Document | ShadowRoot): RootRegistry {\n let registry = this.rootRegistries.get(root);\n\n if (!registry) {\n const metrics: CacheMetrics | undefined = this.config.devMode\n ? {\n hits: 0,\n misses: 0,\n bulkCleanups: 0,\n totalInsertions: 0,\n totalUnused: 0,\n stylesCleanedUp: 0,\n cleanupHistory: [],\n startTime: Date.now(),\n }\n : undefined;\n\n registry = {\n sheets: [],\n refCounts: new Map(),\n rules: new Map(),\n cacheKeyToClassName: new Map(),\n ruleTextSet: new Set<string>(),\n bulkCleanupTimeout: null,\n cleanupCheckTimeout: null,\n metrics,\n classCounter: 0,\n keyframesCache: new Map(),\n keyframesNameToContent: new Map(),\n keyframesCounter: 0,\n injectedProperties: new Map<string, string>(),\n injectedFontFaces: new Set<string>(),\n injectedCounterStyles: new Set<string>(),\n globalRules: new Map(),\n propertyTypeResolver: new PropertyTypeResolver(),\n } as unknown as RootRegistry;\n\n this.rootRegistries.set(root, registry);\n }\n\n return registry;\n }\n\n /**\n * Create a new stylesheet for the registry\n */\n createSheet(registry: RootRegistry, root: Document | ShadowRoot): SheetInfo {\n const sheet = this.createStyleElement(root);\n\n const sheetInfo: SheetInfo = {\n sheet,\n ruleCount: 0,\n holes: [],\n };\n\n registry.sheets.push(sheetInfo);\n return sheetInfo;\n }\n\n /**\n * Create a style element and append to document\n */\n private createStyleElement(root: Document | ShadowRoot): HTMLStyleElement {\n const style =\n (root as Document).createElement?.('style') ||\n document.createElement('style');\n\n if (this.config.nonce) {\n style.nonce = this.config.nonce;\n }\n\n style.setAttribute('data-tasty', '');\n\n // Append to head or shadow root\n if ('head' in root && root.head) {\n root.head.appendChild(style);\n } else if ('appendChild' in root) {\n root.appendChild(style);\n } else {\n document.head.appendChild(style);\n }\n\n // Verify it was actually added - log only if there's a problem and we're not using forceTextInjection\n if (!style.isConnected && !this.config.forceTextInjection) {\n console.error('SheetManager: Style element failed to connect to DOM!', {\n parentNode: style.parentNode?.nodeName,\n isConnected: style.isConnected,\n });\n }\n\n return style;\n }\n\n /**\n * Insert CSS rules as a single block\n */\n insertRule(\n registry: RootRegistry,\n flattenedRules: StyleRule[],\n className: string,\n root: Document | ShadowRoot,\n ): RuleInfo | null {\n // Find or create a sheet with available space\n let targetSheet = this.findAvailableSheet(registry, root);\n\n if (!targetSheet) {\n targetSheet = this.createSheet(registry, root);\n }\n\n const sheetIndex = registry.sheets.indexOf(targetSheet);\n\n try {\n // Group rules by selector and at-rules to combine declarations\n const groupedRules: StyleRule[] = [];\n const groupMap = new Map<\n string,\n {\n idx: number;\n selector: string;\n atRules?: string[];\n declarations: string;\n }\n >();\n\n const atKey = (at?: string[]) => (at && at.length ? at.join('|') : '');\n\n flattenedRules.forEach((r) => {\n const key = `${atKey(r.atRules)}||${r.selector}`;\n const existing = groupMap.get(key);\n if (existing) {\n // Append declarations, preserving order\n existing.declarations = existing.declarations\n ? `${existing.declarations} ${r.declarations}`\n : r.declarations;\n } else {\n groupMap.set(key, {\n idx: groupedRules.length,\n selector: r.selector,\n atRules: r.atRules,\n declarations: r.declarations,\n });\n groupedRules.push({ ...r });\n }\n });\n\n // Normalize groupedRules from map (with merged declarations)\n groupMap.forEach((val) => {\n groupedRules[val.idx] = {\n selector: val.selector,\n atRules: val.atRules,\n declarations: val.declarations,\n } as StyleRule;\n });\n\n // Insert grouped rules\n const insertedRuleTexts: string[] = [];\n const insertedIndices: number[] = []; // Track exact indices\n // Calculate rule index atomically right before insertion to prevent race conditions\n let currentRuleIndex = this.findAvailableRuleIndex(targetSheet);\n let firstInsertedIndex: number | null = null;\n let lastInsertedIndex: number | null = null;\n\n for (const rule of groupedRules) {\n const declarations = rule.declarations;\n const baseRule = `${rule.selector} { ${declarations} }`;\n\n // Wrap with at-rules if present\n let fullRule = baseRule;\n if (rule.atRules && rule.atRules.length > 0) {\n fullRule = rule.atRules.reduce(\n (css, atRule) => `${atRule} { ${css} }`,\n baseRule,\n );\n }\n\n // Insert individual rule into style element\n const styleElement = targetSheet.sheet;\n const styleSheet = styleElement.sheet;\n\n if (styleSheet && !this.config.forceTextInjection) {\n // Calculate index atomically for each rule to prevent concurrent insertion races\n const maxIndex = styleSheet.cssRules.length;\n const atomicRuleIndex = this.findAvailableRuleIndex(targetSheet);\n const safeIndex = Math.min(Math.max(0, atomicRuleIndex), maxIndex);\n\n // Helper: split comma-separated selectors safely (ignores commas inside [] () \" ')\n const splitSelectorsSafely = (selectorList: string): string[] => {\n const parts: string[] = [];\n let buf = '';\n let depthSq = 0; // [] depth\n let depthPar = 0; // () depth\n let inStr: '\"' | \"'\" | '' = '';\n for (let i = 0; i < selectorList.length; i++) {\n const ch = selectorList[i];\n if (inStr) {\n if (ch === inStr && selectorList[i - 1] !== '\\\\') {\n inStr = '';\n }\n buf += ch;\n continue;\n }\n if (ch === '\"' || ch === \"'\") {\n inStr = ch as '\"' | \"'\";\n buf += ch;\n continue;\n }\n if (ch === '[') depthSq++;\n else if (ch === ']') depthSq = Math.max(0, depthSq - 1);\n else if (ch === '(') depthPar++;\n else if (ch === ')') depthPar = Math.max(0, depthPar - 1);\n\n if (ch === ',' && depthSq === 0 && depthPar === 0) {\n const part = buf.trim();\n if (part) parts.push(part);\n buf = '';\n } else {\n buf += ch;\n }\n }\n const tail = buf.trim();\n if (tail) parts.push(tail);\n return parts;\n };\n\n try {\n styleSheet.insertRule(fullRule, safeIndex);\n // Update sheet ruleCount immediately to prevent concurrent race conditions\n targetSheet.ruleCount++;\n insertedIndices.push(safeIndex); // Track this index\n if (firstInsertedIndex == null) firstInsertedIndex = safeIndex;\n lastInsertedIndex = safeIndex;\n currentRuleIndex = safeIndex + 1;\n } catch (e) {\n // If the browser rejects the combined selector (e.g., vendor pseudo-elements),\n // try to split and insert each selector independently. Skip unsupported ones.\n const selectors = splitSelectorsSafely(rule.selector);\n if (selectors.length > 1) {\n let anyInserted = false;\n for (const sel of selectors) {\n const singleBase = `${sel} { ${declarations} }`;\n let singleRule = singleBase;\n if (rule.atRules && rule.atRules.length > 0) {\n singleRule = rule.atRules.reduce(\n (css, atRule) => `${atRule} { ${css} }`,\n singleBase,\n );\n }\n\n try {\n // Calculate index atomically for each individual selector insertion\n const maxIdx = styleSheet.cssRules.length;\n const atomicIdx = this.findAvailableRuleIndex(targetSheet);\n const idx = Math.min(Math.max(0, atomicIdx), maxIdx);\n styleSheet.insertRule(singleRule, idx);\n // Update sheet ruleCount immediately\n targetSheet.ruleCount++;\n insertedIndices.push(idx); // Track this index\n if (firstInsertedIndex == null) firstInsertedIndex = idx;\n lastInsertedIndex = idx;\n currentRuleIndex = idx + 1;\n anyInserted = true;\n } catch (singleErr) {\n // Skip unsupported selector in this engine (e.g., ::-moz-selection in Blink)\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[tasty] Browser rejected CSS rule:',\n singleRule,\n singleErr,\n );\n }\n }\n }\n // If none inserted, continue without throwing to avoid aborting the whole batch\n if (!anyInserted) {\n // noop: all selectors invalid here; safe to skip\n }\n } else {\n // Single selector failed — skip it silently (likely unsupported in this engine)\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[tasty] Browser rejected CSS rule:', fullRule, e);\n }\n }\n }\n } else {\n // Use textContent (either as fallback or when forceTextInjection is enabled)\n // Calculate index atomically for textContent insertion too\n const atomicRuleIndex = this.findAvailableRuleIndex(targetSheet);\n styleElement.textContent =\n (styleElement.textContent || '') + '\\n' + fullRule;\n // Update sheet ruleCount immediately\n targetSheet.ruleCount++;\n insertedIndices.push(atomicRuleIndex); // Track this index\n if (firstInsertedIndex == null) firstInsertedIndex = atomicRuleIndex;\n lastInsertedIndex = atomicRuleIndex;\n currentRuleIndex = atomicRuleIndex + 1;\n }\n\n // CRITICAL DEBUG: Verify the style element is in DOM only if there are issues and we're not using forceTextInjection\n if (!styleElement.parentNode && !this.config.forceTextInjection) {\n console.error(\n 'SheetManager: Style element is NOT in DOM! This is the problem!',\n {\n className,\n ruleIndex: currentRuleIndex,\n },\n );\n }\n\n // Dev-only: store cssText for debugging tools\n if (this.config.devMode) {\n insertedRuleTexts.push(fullRule);\n try {\n registry.ruleTextSet.add(fullRule);\n } catch {\n // noop: defensive in case ruleTextSet is unavailable\n }\n }\n // currentRuleIndex already adjusted above\n }\n\n // Sheet ruleCount is now updated immediately after each insertion\n // No need for deferred update logic\n\n if (insertedIndices.length === 0) {\n return null;\n }\n\n return {\n className,\n ruleIndex: firstInsertedIndex ?? 0,\n sheetIndex,\n cssText: this.config.devMode ? insertedRuleTexts : undefined,\n endRuleIndex: lastInsertedIndex ?? firstInsertedIndex ?? 0,\n indices: insertedIndices,\n };\n } catch (error) {\n console.warn('Failed to insert CSS rules:', error, {\n flattenedRules,\n className,\n });\n return null;\n }\n }\n\n /**\n * Insert global CSS rules\n */\n insertGlobalRule(\n registry: RootRegistry,\n flattenedRules: StyleRule[],\n globalKey: string,\n root: Document | ShadowRoot,\n ): RuleInfo | null {\n // Insert the rule using the same mechanism as regular rules\n const ruleInfo = this.insertRule(registry, flattenedRules, globalKey, root);\n\n // Track global rules for index adjustment\n if (ruleInfo) {\n registry.globalRules.set(globalKey, ruleInfo);\n }\n\n return ruleInfo;\n }\n\n /**\n * Delete a global CSS rule by key\n */\n public deleteGlobalRule(registry: RootRegistry, globalKey: string): void {\n const ruleInfo = registry.globalRules.get(globalKey);\n if (!ruleInfo) {\n return;\n }\n\n // Delete the rule using the standard deletion mechanism\n this.deleteRule(registry, ruleInfo);\n\n // Remove from global rules tracking\n registry.globalRules.delete(globalKey);\n }\n\n /**\n * Adjust rule indices after deletion to account for shifting\n */\n private adjustIndicesAfterDeletion(\n registry: RootRegistry,\n sheetIndex: number,\n startIdx: number,\n endIdx: number,\n deleteCount: number,\n deletedRuleInfo: RuleInfo,\n deletedIndices?: number[],\n ): void {\n try {\n const sortedDeleted =\n deletedIndices && deletedIndices.length > 0\n ? [...deletedIndices].sort((a, b) => a - b)\n : null;\n const countDeletedBefore = (sorted: number[], idx: number): number => {\n let shift = 0;\n for (const delIdx of sorted) {\n if (delIdx < idx) shift++;\n else break;\n }\n return shift;\n };\n // Helper function to adjust a single RuleInfo\n const adjustRuleInfo = (info: RuleInfo): void => {\n if (info === deletedRuleInfo) return; // Skip the deleted rule\n if (info.sheetIndex !== sheetIndex) return; // Different sheet\n\n if (!info.indices || info.indices.length === 0) {\n return;\n }\n\n if (sortedDeleted) {\n // Adjust each index based on how many deleted indices are before it\n info.indices = info.indices.map((idx) => {\n return idx - countDeletedBefore(sortedDeleted, idx);\n });\n } else {\n // Contiguous deletion: shift indices after the deleted range\n info.indices = info.indices.map((idx) =>\n idx > endIdx ? Math.max(0, idx - deleteCount) : idx,\n );\n }\n\n // Update ruleIndex and endRuleIndex to match adjusted indices\n if (info.indices.length > 0) {\n info.ruleIndex = Math.min(...info.indices);\n info.endRuleIndex = Math.max(...info.indices);\n }\n };\n\n // Adjust active rules\n for (const info of registry.rules.values()) {\n adjustRuleInfo(info);\n }\n\n // Adjust global rules\n for (const info of registry.globalRules.values()) {\n adjustRuleInfo(info);\n }\n\n // No need to separately adjust unused rules since they're part of the rules Map\n\n // Adjust keyframes indices stored in cache\n for (const entry of registry.keyframesCache.values()) {\n const ki = entry.info as KeyframesInfo;\n if (ki.sheetIndex !== sheetIndex) continue;\n if (sortedDeleted) {\n const shift = countDeletedBefore(sortedDeleted, ki.ruleIndex);\n if (shift > 0) {\n ki.ruleIndex = Math.max(0, ki.ruleIndex - shift);\n }\n } else if (ki.ruleIndex > endIdx) {\n ki.ruleIndex = Math.max(0, ki.ruleIndex - deleteCount);\n }\n }\n } catch {\n // Defensive: do not let index adjustments crash cleanup\n }\n }\n\n /**\n * Delete a CSS rule from the sheet\n */\n deleteRule(registry: RootRegistry, ruleInfo: RuleInfo): void {\n const sheet = registry.sheets[ruleInfo.sheetIndex];\n\n if (!sheet) {\n return;\n }\n\n try {\n const texts: string[] =\n this.config.devMode && Array.isArray(ruleInfo.cssText)\n ? ruleInfo.cssText.slice()\n : [];\n\n const styleElement = sheet.sheet;\n const styleSheet = styleElement.sheet;\n\n if (styleSheet) {\n const rules = styleSheet.cssRules;\n\n // Use exact indices if available, otherwise fall back to range\n if (ruleInfo.indices && ruleInfo.indices.length > 0) {\n // NEW: Delete using exact tracked indices\n const sortedIndices = [...ruleInfo.indices].sort((a, b) => b - a); // Sort descending\n const deletedIndices: number[] = [];\n\n for (const idx of sortedIndices) {\n if (idx >= 0 && idx < styleSheet.cssRules.length) {\n try {\n styleSheet.deleteRule(idx);\n deletedIndices.push(idx);\n } catch (e) {\n console.warn(`Failed to delete rule at index ${idx}:`, e);\n }\n }\n }\n\n sheet.ruleCount = Math.max(\n 0,\n sheet.ruleCount - deletedIndices.length,\n );\n\n // Adjust indices for all other rules\n if (deletedIndices.length > 0) {\n this.adjustIndicesAfterDeletion(\n registry,\n ruleInfo.sheetIndex,\n Math.min(...deletedIndices),\n Math.max(...deletedIndices),\n deletedIndices.length,\n ruleInfo,\n deletedIndices,\n );\n }\n } else {\n // FALLBACK: Use old range-based deletion for backwards compatibility\n const startIdx = Math.max(0, ruleInfo.ruleIndex);\n const endIdx = Math.min(\n rules.length - 1,\n Number.isFinite(ruleInfo.endRuleIndex as number)\n ? (ruleInfo.endRuleIndex as number)\n : startIdx,\n );\n\n if (Number.isFinite(startIdx) && endIdx >= startIdx) {\n const deleteCount = endIdx - startIdx + 1;\n for (let idx = endIdx; idx >= startIdx; idx--) {\n if (idx < 0 || idx >= styleSheet.cssRules.length) continue;\n styleSheet.deleteRule(idx);\n }\n sheet.ruleCount = Math.max(0, sheet.ruleCount - deleteCount);\n\n // After deletion, all subsequent rule indices shift left by deleteCount.\n // We must adjust stored indices for all other RuleInfo within the same sheet.\n this.adjustIndicesAfterDeletion(\n registry,\n ruleInfo.sheetIndex,\n startIdx,\n endIdx,\n deleteCount,\n ruleInfo,\n );\n }\n }\n }\n\n // Dev-only: remove cssText entries from validation set\n if (this.config.devMode && texts.length) {\n try {\n for (const text of texts) {\n registry.ruleTextSet.delete(text);\n }\n } catch {\n // noop\n }\n }\n } catch (error) {\n console.warn('Failed to delete CSS rule:', error);\n }\n }\n\n /**\n * Find a sheet with available space or return null\n */\n private findAvailableSheet(\n registry: RootRegistry,\n _root: Document | ShadowRoot,\n ): SheetInfo | null {\n const maxRules = this.config.maxRulesPerSheet;\n\n if (!maxRules) {\n // No limit, use the last sheet if it exists\n const lastSheet = registry.sheets[registry.sheets.length - 1];\n return lastSheet || null;\n }\n\n // Find sheet with space\n for (const sheet of registry.sheets) {\n if (sheet.ruleCount < maxRules) {\n return sheet;\n }\n }\n\n return null; // No available sheet found\n }\n\n /**\n * Find an available rule index in the sheet\n */\n findAvailableRuleIndex(sheet: SheetInfo): number {\n // Always append to the end - CSS doesn't have holes\n return sheet.ruleCount;\n }\n\n /**\n * Schedule bulk cleanup of all unused styles (non-stacking)\n */\n private scheduleBulkCleanup(registry: RootRegistry): void {\n // Clear any existing timeout to prevent stacking\n if (registry.bulkCleanupTimeout) {\n if (\n this.config.idleCleanup &&\n typeof cancelIdleCallback !== 'undefined'\n ) {\n cancelIdleCallback(registry.bulkCleanupTimeout as unknown as number);\n } else {\n clearTimeout(registry.bulkCleanupTimeout);\n }\n registry.bulkCleanupTimeout = null;\n }\n\n const performCleanup = () => {\n this.performBulkCleanup(registry);\n registry.bulkCleanupTimeout = null;\n };\n\n if (this.config.idleCleanup && typeof requestIdleCallback !== 'undefined') {\n registry.bulkCleanupTimeout = requestIdleCallback(performCleanup);\n } else {\n const delay = this.config.bulkCleanupDelay || 5000;\n registry.bulkCleanupTimeout = setTimeout(performCleanup, delay);\n }\n }\n\n /**\n * Force cleanup of unused styles\n */\n public forceCleanup(registry: RootRegistry): void {\n this.performBulkCleanup(registry, true);\n }\n\n /**\n * Perform bulk cleanup of all unused styles\n */\n private performBulkCleanup(registry: RootRegistry, cleanupAll = false): void {\n const cleanupStartTime = Date.now();\n\n // Calculate unused rules dynamically: rules that have refCount = 0\n const unusedClassNames = Array.from(registry.refCounts.entries())\n .filter(([, refCount]) => refCount === 0)\n .map(([className]) => className);\n\n if (unusedClassNames.length === 0) return;\n\n // Build candidates list - no age filtering needed\n const candidates = unusedClassNames.map((className) => {\n const ruleInfo = registry.rules.get(className)!; // We know it exists\n return {\n className,\n ruleInfo,\n };\n });\n\n if (candidates.length === 0) return;\n\n // Limit deletion scope per run (batch ratio) unless cleanupAll is true\n let selected = candidates;\n if (!cleanupAll) {\n const ratio = this.config.bulkCleanupBatchRatio ?? 0.5;\n const limit = Math.max(\n 1,\n Math.floor(candidates.length * Math.min(1, Math.max(0, ratio))),\n );\n selected = candidates.slice(0, limit);\n }\n\n let cleanedUpCount = 0;\n let totalCssSize = 0;\n let totalRulesDeleted = 0;\n\n // Group by sheet for efficient deletion\n const rulesBySheet = new Map<\n number,\n { className: string; ruleInfo: RuleInfo }[]\n >();\n\n // Calculate CSS size before deletion and group rules\n for (const { className, ruleInfo } of selected) {\n const sheetIndex = ruleInfo.sheetIndex;\n\n // Dev-only metrics: estimate CSS size and rule count if available\n if (this.config.devMode && Array.isArray(ruleInfo.cssText)) {\n const cssSize = ruleInfo.cssText.reduce(\n (total, css) => total + css.length,\n 0,\n );\n totalCssSize += cssSize;\n totalRulesDeleted += ruleInfo.cssText.length;\n }\n\n if (!rulesBySheet.has(sheetIndex)) {\n rulesBySheet.set(sheetIndex, []);\n }\n rulesBySheet.get(sheetIndex)!.push({ className, ruleInfo });\n }\n\n // Delete rules from each sheet (in reverse order to preserve indices)\n for (const [_sheetIndex, rulesInSheet] of rulesBySheet) {\n // Sort by rule index in descending order for safe deletion\n rulesInSheet.sort((a, b) => b.ruleInfo.ruleIndex - a.ruleInfo.ruleIndex);\n\n for (const { className, ruleInfo } of rulesInSheet) {\n // SAFETY 1: Double-check refCount is still 0\n const currentRefCount = registry.refCounts.get(className) || 0;\n if (currentRefCount > 0) {\n // Class became active again; do not delete\n continue;\n }\n\n // SAFETY 2: Ensure rule wasn't replaced\n // Between scheduling and execution a class may have been replaced with a new RuleInfo\n const currentInfo = registry.rules.get(className);\n if (currentInfo !== ruleInfo) {\n // Rule was replaced; skip deletion of the old reference\n continue;\n }\n\n // SAFETY 3: Verify the sheet element is still valid and accessible\n const sheetInfo = registry.sheets[ruleInfo.sheetIndex];\n if (!sheetInfo || !sheetInfo.sheet) {\n // Sheet was removed or corrupted; skip this rule\n continue;\n }\n\n // SAFETY 4: Verify the stylesheet itself is accessible\n const styleSheet = sheetInfo.sheet.sheet;\n if (!styleSheet) {\n // Stylesheet not available; skip this rule\n continue;\n }\n\n // SAFETY 5: Verify rule index is still within valid range\n const maxRuleIndex = styleSheet.cssRules.length - 1;\n const startIdx = ruleInfo.ruleIndex;\n const endIdx = ruleInfo.endRuleIndex ?? ruleInfo.ruleIndex;\n\n if (startIdx < 0 || endIdx > maxRuleIndex || startIdx > endIdx) {\n // Rule indices are out of bounds; skip this rule\n continue;\n }\n\n // All safety checks passed - proceed with deletion\n this.deleteRule(registry, ruleInfo);\n registry.rules.delete(className);\n registry.refCounts.delete(className);\n\n // Clean up cache key mappings that point to this className\n const keysToDelete: string[] = [];\n for (const [\n key,\n mappedClassName,\n ] of registry.cacheKeyToClassName.entries()) {\n if (mappedClassName === className) {\n keysToDelete.push(key);\n }\n }\n for (const key of keysToDelete) {\n registry.cacheKeyToClassName.delete(key);\n }\n cleanedUpCount++;\n }\n }\n\n // Update metrics\n if (registry.metrics) {\n registry.metrics.bulkCleanups++;\n registry.metrics.stylesCleanedUp += cleanedUpCount;\n\n // Add detailed cleanup stats to history\n registry.metrics.cleanupHistory.push({\n timestamp: cleanupStartTime,\n classesDeleted: cleanedUpCount,\n cssSize: totalCssSize,\n rulesDeleted: totalRulesDeleted,\n });\n }\n }\n\n /**\n * Get total number of rules across all sheets\n */\n getTotalRuleCount(registry: RootRegistry): number {\n return registry.sheets.reduce(\n (total, sheet) => total + sheet.ruleCount - sheet.holes.length,\n 0,\n );\n }\n\n /**\n * Get CSS text from all sheets (for SSR)\n */\n getCssText(registry: RootRegistry): string {\n const cssChunks: string[] = [];\n\n for (const sheet of registry.sheets) {\n try {\n const styleElement = sheet.sheet;\n if (styleElement.textContent) {\n cssChunks.push(styleElement.textContent);\n } else if (styleElement.sheet) {\n const rules = Array.from(styleElement.sheet.cssRules);\n cssChunks.push(rules.map((rule) => rule.cssText).join('\\n'));\n }\n } catch (error) {\n console.warn('Failed to read CSS from sheet:', error);\n }\n }\n\n return cssChunks.join('\\n');\n }\n\n /**\n * Get cache performance metrics\n */\n getMetrics(registry: RootRegistry): CacheMetrics | null {\n if (!registry.metrics) return null;\n\n // Calculate unusedHits on demand - only count CSS rules since keyframes are disposed immediately\n const unusedRulesCount = Array.from(registry.refCounts.values()).filter(\n (count) => count === 0,\n ).length;\n\n return {\n ...registry.metrics,\n unusedHits: unusedRulesCount,\n };\n }\n\n /**\n * Reset cache performance metrics\n */\n resetMetrics(registry: RootRegistry): void {\n if (registry.metrics) {\n registry.metrics = {\n hits: 0,\n misses: 0,\n bulkCleanups: 0,\n totalInsertions: 0,\n totalUnused: 0,\n stylesCleanedUp: 0,\n cleanupHistory: [],\n startTime: Date.now(),\n };\n }\n }\n\n /**\n * Convert keyframes steps to CSS string.\n * Public so the SSR collector can format keyframes without DOM access.\n * Returns both the CSS text and a combined declarations string for property type scanning.\n */\n stepsToCSS(steps: KeyframesSteps): {\n css: string;\n declarations: string;\n } {\n const rules: string[] = [];\n const allDeclarations: string[] = [];\n\n for (const [key, value] of Object.entries(steps)) {\n // Support raw CSS strings for backwards compatibility\n if (typeof value === 'string') {\n rules.push(`${key} { ${value.trim()} }`);\n allDeclarations.push(value.trim());\n continue;\n }\n\n // Treat value as a style map and process via tasty style handlers\n const styleMap = (value || {}) as StyleValueStateMap;\n\n // Build a deterministic handler queue based on present style keys\n const styleNames = Object.keys(styleMap).sort();\n const handlerQueue: StyleHandler[] = [];\n const seenHandlers = new Set<StyleHandler>();\n\n styleNames.forEach((styleName) => {\n let handlers = STYLE_HANDLER_MAP[styleName];\n if (!handlers) {\n // Create a default handler for unknown styles (maps to kebab-case CSS or custom props)\n handlers = STYLE_HANDLER_MAP[styleName] = [createStyle(styleName)];\n }\n\n handlers.forEach((handler) => {\n if (!seenHandlers.has(handler)) {\n seenHandlers.add(handler);\n handlerQueue.push(handler);\n }\n });\n });\n\n // Accumulate declarations (ordered). We intentionally ignore `$` selector fan-out\n // and any responsive/state bindings for keyframes.\n const declarationPairs: { prop: string; value: string }[] = [];\n\n handlerQueue.forEach((handler) => {\n const lookup = handler.__lookupStyles;\n const filteredMap = lookup.reduce<StyleValueStateMap>((acc, name) => {\n const v = styleMap[name];\n if (v !== undefined) acc[name] = v;\n return acc;\n }, {});\n\n const result = handler(filteredMap);\n if (!result) return;\n\n const results = Array.isArray(result) ? result : [result];\n results.forEach((cssMap) => {\n if (!cssMap || typeof cssMap !== 'object') return;\n const { $: _$, ...props } = cssMap as CSSMap;\n\n Object.entries(props).forEach(([prop, val]) => {\n if (val == null || val === '') return;\n\n if (Array.isArray(val)) {\n // Multiple values for the same property -> emit in order\n val.forEach((v) => {\n if (v != null && v !== '') {\n declarationPairs.push({ prop, value: String(v) });\n }\n });\n } else {\n declarationPairs.push({ prop, value: String(val) });\n }\n });\n });\n });\n\n // Fallback: if nothing produced (e.g., empty object), generate empty block\n const declarations = declarationPairs\n .map((d) => `${d.prop}: ${d.value}`)\n .join('; ');\n\n rules.push(`${key} { ${declarations.trim()} }`);\n allDeclarations.push(declarations);\n }\n\n return { css: rules.join(' '), declarations: allDeclarations.join('; ') };\n }\n\n /**\n * Insert keyframes rule.\n * Returns the KeyframesInfo and the raw declarations string for property type scanning.\n */\n insertKeyframes(\n registry: RootRegistry,\n steps: KeyframesSteps,\n name: string,\n root: Document | ShadowRoot,\n ): { info: KeyframesInfo; declarations: string } | null {\n let targetSheet = this.findAvailableSheet(registry, root);\n if (!targetSheet) {\n targetSheet = this.createSheet(registry, root);\n }\n\n const ruleIndex = this.findAvailableRuleIndex(targetSheet);\n const sheetIndex = registry.sheets.indexOf(targetSheet);\n\n try {\n const { css: cssSteps, declarations } = this.stepsToCSS(steps);\n const fullRule = `@keyframes ${name} { ${cssSteps} }`;\n\n const styleElement = targetSheet.sheet;\n const styleSheet = styleElement.sheet;\n\n if (styleSheet && !this.config.forceTextInjection) {\n const safeIndex = Math.min(\n Math.max(0, ruleIndex),\n styleSheet.cssRules.length,\n );\n styleSheet.insertRule(fullRule, safeIndex);\n } else {\n styleElement.textContent =\n (styleElement.textContent || '') + '\\n' + fullRule;\n }\n\n targetSheet.ruleCount++;\n\n return {\n info: {\n name,\n ruleIndex,\n sheetIndex,\n cssText: this.config.devMode ? fullRule : undefined,\n },\n declarations,\n };\n } catch (error) {\n console.warn('Failed to insert keyframes:', error);\n return null;\n }\n }\n\n /**\n * Delete keyframes rule\n */\n deleteKeyframes(registry: RootRegistry, info: KeyframesInfo): void {\n const sheet = registry.sheets[info.sheetIndex];\n if (!sheet) return;\n\n try {\n const styleElement = sheet.sheet;\n const styleSheet = styleElement.sheet;\n\n if (styleSheet) {\n if (\n info.ruleIndex >= 0 &&\n info.ruleIndex < styleSheet.cssRules.length\n ) {\n styleSheet.deleteRule(info.ruleIndex);\n sheet.ruleCount = Math.max(0, sheet.ruleCount - 1);\n\n // Adjust indices for all other rules in the same sheet\n // This is critical - when a keyframe rule is deleted, all rules\n // with higher indices shift down by 1\n this.adjustIndicesAfterDeletion(\n registry,\n info.sheetIndex,\n info.ruleIndex,\n info.ruleIndex,\n 1,\n // Create a dummy RuleInfo to satisfy the function signature\n {\n className: '',\n ruleIndex: info.ruleIndex,\n sheetIndex: info.sheetIndex,\n } as RuleInfo,\n [info.ruleIndex],\n );\n }\n }\n } catch (error) {\n console.warn('Failed to delete keyframes:', error);\n }\n }\n\n /**\n * Schedule async cleanup check (non-stacking)\n */\n public checkCleanupNeeded(registry: RootRegistry): void {\n // Clear any existing check timeout to prevent stacking\n if (registry.cleanupCheckTimeout) {\n clearTimeout(registry.cleanupCheckTimeout);\n registry.cleanupCheckTimeout = null;\n }\n\n // Schedule the actual check with setTimeout(..., 0)\n registry.cleanupCheckTimeout = setTimeout(() => {\n this.performCleanupCheck(registry);\n registry.cleanupCheckTimeout = null;\n }, 0);\n }\n\n /**\n * Perform the actual cleanup check (called asynchronously)\n */\n private performCleanupCheck(registry: RootRegistry): void {\n // Count unused rules (refCount = 0) - keyframes are disposed immediately\n const unusedRulesCount = Array.from(registry.refCounts.values()).filter(\n (count) => count === 0,\n ).length;\n const threshold = this.config.unusedStylesThreshold || 500;\n\n if (unusedRulesCount >= threshold) {\n this.scheduleBulkCleanup(registry);\n }\n }\n\n /**\n * Clean up resources for a root\n */\n cleanup(root: Document | ShadowRoot): void {\n const registry = this.rootRegistries.get(root);\n\n if (!registry) {\n return;\n }\n\n // Cancel any scheduled bulk cleanup\n if (registry.bulkCleanupTimeout) {\n if (\n this.config.idleCleanup &&\n typeof cancelIdleCallback !== 'undefined'\n ) {\n cancelIdleCallback(registry.bulkCleanupTimeout as unknown as number);\n } else {\n clearTimeout(registry.bulkCleanupTimeout);\n }\n registry.bulkCleanupTimeout = null;\n }\n\n // Cancel any scheduled cleanup check\n if (registry.cleanupCheckTimeout) {\n clearTimeout(registry.cleanupCheckTimeout);\n registry.cleanupCheckTimeout = null;\n }\n\n // Remove all sheets\n for (const sheet of registry.sheets) {\n try {\n // Remove style element\n const styleElement = sheet.sheet;\n if (styleElement.parentNode) {\n styleElement.parentNode.removeChild(styleElement);\n }\n } catch (error) {\n console.warn('Failed to cleanup sheet:', error);\n }\n }\n\n // Clear registry\n this.rootRegistries.delete(root);\n\n // Clean up raw CSS style element\n const rawStyleElement = this.rawStyleElements.get(root);\n if (rawStyleElement?.parentNode) {\n rawStyleElement.parentNode.removeChild(rawStyleElement);\n }\n this.rawStyleElements.delete(root);\n this.rawCSSBlocks.delete(root);\n }\n\n /**\n * Get or create a dedicated style element for raw CSS\n * Raw CSS is kept separate from tasty-managed sheets to avoid index conflicts\n */\n private getOrCreateRawStyleElement(\n root: Document | ShadowRoot,\n ): HTMLStyleElement {\n let styleElement = this.rawStyleElements.get(root);\n\n if (!styleElement) {\n styleElement =\n (root as Document).createElement?.('style') ||\n document.createElement('style');\n\n if (this.config.nonce) {\n styleElement.nonce = this.config.nonce;\n }\n\n styleElement.setAttribute('data-tasty-raw', '');\n\n // Append to head or shadow root\n if ('head' in root && root.head) {\n root.head.appendChild(styleElement);\n } else if ('appendChild' in root) {\n root.appendChild(styleElement);\n } else {\n document.head.appendChild(styleElement);\n }\n\n this.rawStyleElements.set(root, styleElement);\n this.rawCSSBlocks.set(root, new Map());\n }\n\n return styleElement;\n }\n\n /**\n * Inject raw CSS text directly without parsing\n * Returns a dispose function to remove the injected CSS\n */\n injectRawCSS(css: string, root: Document | ShadowRoot): RawCSSResult {\n if (!css.trim()) {\n return {\n dispose: () => {\n /* noop */\n },\n };\n }\n\n const styleElement = this.getOrCreateRawStyleElement(root);\n const blocksMap = this.rawCSSBlocks.get(root)!;\n\n // Generate unique ID for this block\n const id = `raw_${this.rawCSSCounter++}`;\n\n // Calculate offsets\n const currentContent = styleElement.textContent || '';\n const startOffset = currentContent.length;\n const cssWithNewline = (currentContent ? '\\n' : '') + css;\n const endOffset = startOffset + cssWithNewline.length;\n\n // Append CSS\n styleElement.textContent = currentContent + cssWithNewline;\n\n // Track the block\n const info: RawCSSInfo = {\n id,\n css,\n startOffset,\n endOffset,\n };\n blocksMap.set(id, info);\n\n return {\n dispose: () => {\n this.disposeRawCSS(id, root);\n },\n };\n }\n\n /**\n * Remove a raw CSS block by ID\n */\n private disposeRawCSS(id: string, root: Document | ShadowRoot): void {\n const styleElement = this.rawStyleElements.get(root);\n const blocksMap = this.rawCSSBlocks.get(root);\n\n if (!styleElement || !blocksMap) {\n return;\n }\n\n const info = blocksMap.get(id);\n if (!info) {\n return;\n }\n\n // Remove from tracking\n blocksMap.delete(id);\n\n // Rebuild the CSS content from remaining blocks\n // This is simpler and more reliable than trying to splice strings\n const remainingBlocks = Array.from(blocksMap.values());\n\n if (remainingBlocks.length === 0) {\n styleElement.textContent = '';\n } else {\n // Rebuild with remaining CSS blocks in order of their original insertion\n // Sort by original startOffset to maintain order\n remainingBlocks.sort((a, b) => a.startOffset - b.startOffset);\n const newContent = remainingBlocks.map((block) => block.css).join('\\n');\n styleElement.textContent = newContent;\n\n // Update offsets for remaining blocks\n let offset = 0;\n for (const block of remainingBlocks) {\n block.startOffset = offset;\n block.endOffset = offset + block.css.length;\n offset = block.endOffset + 1; // +1 for newline\n }\n }\n }\n\n /**\n * Get the raw CSS content for SSR\n */\n getRawCSSText(root: Document | ShadowRoot): string {\n const styleElement = this.rawStyleElements.get(root);\n return styleElement?.textContent || '';\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sheet-manager.js","names":[],"sources":["../../src/injector/sheet-manager.ts"],"sourcesContent":["import { PropertyTypeResolver } from '../properties/property-type-resolver';\nimport { createStyle, STYLE_HANDLER_MAP } from '../styles';\n\nimport type {\n CacheMetrics,\n KeyframesInfo,\n KeyframesSteps,\n RawCSSInfo,\n RawCSSResult,\n RootRegistry,\n RuleInfo,\n SheetInfo,\n StyleInjectorConfig,\n StyleRule,\n} from './types';\n\nimport type { CSSMap, StyleHandler, StyleValueStateMap } from '../utils/styles';\n\nexport class SheetManager {\n private rootRegistries = new WeakMap<Document | ShadowRoot, RootRegistry>();\n private config: StyleInjectorConfig;\n /** Dedicated style elements for raw CSS per root */\n private rawStyleElements = new WeakMap<\n Document | ShadowRoot,\n HTMLStyleElement\n >();\n /** Tracking for raw CSS blocks per root */\n private rawCSSBlocks = new WeakMap<\n Document | ShadowRoot,\n Map<string, RawCSSInfo>\n >();\n /** Counter for generating unique raw CSS IDs */\n private rawCSSCounter = 0;\n\n constructor(config: StyleInjectorConfig) {\n this.config = config;\n }\n\n /**\n * Get or create registry for a root (Document or ShadowRoot)\n */\n getRegistry(root: Document | ShadowRoot): RootRegistry {\n let registry = this.rootRegistries.get(root);\n\n if (!registry) {\n const metrics: CacheMetrics | undefined = this.config.devMode\n ? {\n hits: 0,\n misses: 0,\n bulkCleanups: 0,\n totalInsertions: 0,\n totalUnused: 0,\n stylesCleanedUp: 0,\n cleanupHistory: [],\n startTime: Date.now(),\n }\n : undefined;\n\n registry = {\n sheets: [],\n refCounts: new Map(),\n rules: new Map(),\n cacheKeyToClassName: new Map(),\n ruleTextSet: new Set<string>(),\n bulkCleanupTimeout: null,\n cleanupCheckTimeout: null,\n metrics,\n classCounter: 0,\n keyframesCache: new Map(),\n keyframesNameToContent: new Map(),\n keyframesCounter: 0,\n injectedProperties: new Map<string, string>(),\n injectedFontFaces: new Set<string>(),\n injectedCounterStyles: new Set<string>(),\n globalRules: new Map(),\n propertyTypeResolver: new PropertyTypeResolver(),\n } as unknown as RootRegistry;\n\n this.rootRegistries.set(root, registry);\n }\n\n return registry;\n }\n\n /**\n * Create a new stylesheet for the registry\n */\n createSheet(registry: RootRegistry, root: Document | ShadowRoot): SheetInfo {\n const sheet = this.createStyleElement(root);\n\n const sheetInfo: SheetInfo = {\n sheet,\n ruleCount: 0,\n holes: [],\n };\n\n registry.sheets.push(sheetInfo);\n return sheetInfo;\n }\n\n /**\n * Create a style element and append to document\n */\n private createStyleElement(root: Document | ShadowRoot): HTMLStyleElement {\n const style =\n (root as Document).createElement?.('style') ||\n document.createElement('style');\n\n if (this.config.nonce) {\n style.nonce = this.config.nonce;\n }\n\n style.setAttribute('data-tasty', '');\n\n // Append to head or shadow root\n if ('head' in root && root.head) {\n root.head.appendChild(style);\n } else if ('appendChild' in root) {\n root.appendChild(style);\n } else {\n document.head.appendChild(style);\n }\n\n // Verify it was actually added - log only if there's a problem and we're not using forceTextInjection\n if (!style.isConnected && !this.config.forceTextInjection) {\n console.error('SheetManager: Style element failed to connect to DOM!', {\n parentNode: style.parentNode?.nodeName,\n isConnected: style.isConnected,\n });\n }\n\n return style;\n }\n\n /**\n * Insert CSS rules as a single block\n */\n insertRule(\n registry: RootRegistry,\n flattenedRules: StyleRule[],\n className: string,\n root: Document | ShadowRoot,\n ): RuleInfo | null {\n // Find or create a sheet with available space\n let targetSheet = this.findAvailableSheet(registry, root);\n\n if (!targetSheet) {\n targetSheet = this.createSheet(registry, root);\n }\n\n const sheetIndex = registry.sheets.indexOf(targetSheet);\n\n try {\n // Group rules by selector, at-rules, and startingStyle to combine declarations\n const groupedRules: StyleRule[] = [];\n const groupMap = new Map<\n string,\n {\n idx: number;\n selector: string;\n atRules?: string[];\n startingStyle?: boolean;\n declarations: string;\n }\n >();\n\n const atKey = (at?: string[]) => (at && at.length ? at.join('|') : '');\n\n flattenedRules.forEach((r) => {\n const key = `${atKey(r.atRules)}||${r.selector}||${r.startingStyle ? '1' : '0'}`;\n const existing = groupMap.get(key);\n if (existing) {\n // Append declarations, preserving order\n existing.declarations = existing.declarations\n ? `${existing.declarations} ${r.declarations}`\n : r.declarations;\n } else {\n groupMap.set(key, {\n idx: groupedRules.length,\n selector: r.selector,\n atRules: r.atRules,\n startingStyle: r.startingStyle,\n declarations: r.declarations,\n });\n groupedRules.push({ ...r });\n }\n });\n\n // Normalize groupedRules from map (with merged declarations)\n groupMap.forEach((val) => {\n groupedRules[val.idx] = {\n selector: val.selector,\n atRules: val.atRules,\n startingStyle: val.startingStyle,\n declarations: val.declarations,\n } as StyleRule;\n });\n\n // Insert grouped rules\n const insertedRuleTexts: string[] = [];\n const insertedIndices: number[] = []; // Track exact indices\n // Calculate rule index atomically right before insertion to prevent race conditions\n let currentRuleIndex = this.findAvailableRuleIndex(targetSheet);\n let firstInsertedIndex: number | null = null;\n let lastInsertedIndex: number | null = null;\n\n for (const rule of groupedRules) {\n const declarations = rule.declarations;\n const innerContent = rule.startingStyle\n ? `@starting-style { ${declarations} }`\n : declarations;\n const baseRule = `${rule.selector} { ${innerContent} }`;\n\n // Wrap with at-rules if present\n let fullRule = baseRule;\n if (rule.atRules && rule.atRules.length > 0) {\n fullRule = rule.atRules.reduce(\n (css, atRule) => `${atRule} { ${css} }`,\n baseRule,\n );\n }\n\n // Insert individual rule into style element\n const styleElement = targetSheet.sheet;\n const styleSheet = styleElement.sheet;\n\n if (styleSheet && !this.config.forceTextInjection) {\n // Calculate index atomically for each rule to prevent concurrent insertion races\n const maxIndex = styleSheet.cssRules.length;\n const atomicRuleIndex = this.findAvailableRuleIndex(targetSheet);\n const safeIndex = Math.min(Math.max(0, atomicRuleIndex), maxIndex);\n\n // Helper: split comma-separated selectors safely (ignores commas inside [] () \" ')\n const splitSelectorsSafely = (selectorList: string): string[] => {\n const parts: string[] = [];\n let buf = '';\n let depthSq = 0; // [] depth\n let depthPar = 0; // () depth\n let inStr: '\"' | \"'\" | '' = '';\n for (let i = 0; i < selectorList.length; i++) {\n const ch = selectorList[i];\n if (inStr) {\n if (ch === inStr && selectorList[i - 1] !== '\\\\') {\n inStr = '';\n }\n buf += ch;\n continue;\n }\n if (ch === '\"' || ch === \"'\") {\n inStr = ch as '\"' | \"'\";\n buf += ch;\n continue;\n }\n if (ch === '[') depthSq++;\n else if (ch === ']') depthSq = Math.max(0, depthSq - 1);\n else if (ch === '(') depthPar++;\n else if (ch === ')') depthPar = Math.max(0, depthPar - 1);\n\n if (ch === ',' && depthSq === 0 && depthPar === 0) {\n const part = buf.trim();\n if (part) parts.push(part);\n buf = '';\n } else {\n buf += ch;\n }\n }\n const tail = buf.trim();\n if (tail) parts.push(tail);\n return parts;\n };\n\n try {\n styleSheet.insertRule(fullRule, safeIndex);\n // Update sheet ruleCount immediately to prevent concurrent race conditions\n targetSheet.ruleCount++;\n insertedIndices.push(safeIndex); // Track this index\n if (firstInsertedIndex == null) firstInsertedIndex = safeIndex;\n lastInsertedIndex = safeIndex;\n currentRuleIndex = safeIndex + 1;\n } catch (e) {\n // If the browser rejects the combined selector (e.g., vendor pseudo-elements),\n // try to split and insert each selector independently. Skip unsupported ones.\n const selectors = splitSelectorsSafely(rule.selector);\n if (selectors.length > 1) {\n let anyInserted = false;\n for (const sel of selectors) {\n const singleBase = `${sel} { ${declarations} }`;\n let singleRule = singleBase;\n if (rule.atRules && rule.atRules.length > 0) {\n singleRule = rule.atRules.reduce(\n (css, atRule) => `${atRule} { ${css} }`,\n singleBase,\n );\n }\n\n try {\n // Calculate index atomically for each individual selector insertion\n const maxIdx = styleSheet.cssRules.length;\n const atomicIdx = this.findAvailableRuleIndex(targetSheet);\n const idx = Math.min(Math.max(0, atomicIdx), maxIdx);\n styleSheet.insertRule(singleRule, idx);\n // Update sheet ruleCount immediately\n targetSheet.ruleCount++;\n insertedIndices.push(idx); // Track this index\n if (firstInsertedIndex == null) firstInsertedIndex = idx;\n lastInsertedIndex = idx;\n currentRuleIndex = idx + 1;\n anyInserted = true;\n } catch (singleErr) {\n // Skip unsupported selector in this engine (e.g., ::-moz-selection in Blink)\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[tasty] Browser rejected CSS rule:',\n singleRule,\n singleErr,\n );\n }\n }\n }\n // If none inserted, continue without throwing to avoid aborting the whole batch\n if (!anyInserted) {\n // noop: all selectors invalid here; safe to skip\n }\n } else {\n // Single selector failed — skip it silently (likely unsupported in this engine)\n if (process.env.NODE_ENV !== 'production') {\n console.warn('[tasty] Browser rejected CSS rule:', fullRule, e);\n }\n }\n }\n } else {\n // Use textContent (either as fallback or when forceTextInjection is enabled)\n // Calculate index atomically for textContent insertion too\n const atomicRuleIndex = this.findAvailableRuleIndex(targetSheet);\n styleElement.textContent =\n (styleElement.textContent || '') + '\\n' + fullRule;\n // Update sheet ruleCount immediately\n targetSheet.ruleCount++;\n insertedIndices.push(atomicRuleIndex); // Track this index\n if (firstInsertedIndex == null) firstInsertedIndex = atomicRuleIndex;\n lastInsertedIndex = atomicRuleIndex;\n currentRuleIndex = atomicRuleIndex + 1;\n }\n\n // CRITICAL DEBUG: Verify the style element is in DOM only if there are issues and we're not using forceTextInjection\n if (!styleElement.parentNode && !this.config.forceTextInjection) {\n console.error(\n 'SheetManager: Style element is NOT in DOM! This is the problem!',\n {\n className,\n ruleIndex: currentRuleIndex,\n },\n );\n }\n\n // Dev-only: store cssText for debugging tools\n if (this.config.devMode) {\n insertedRuleTexts.push(fullRule);\n try {\n registry.ruleTextSet.add(fullRule);\n } catch {\n // noop: defensive in case ruleTextSet is unavailable\n }\n }\n // currentRuleIndex already adjusted above\n }\n\n // Sheet ruleCount is now updated immediately after each insertion\n // No need for deferred update logic\n\n if (insertedIndices.length === 0) {\n return null;\n }\n\n return {\n className,\n ruleIndex: firstInsertedIndex ?? 0,\n sheetIndex,\n cssText: this.config.devMode ? insertedRuleTexts : undefined,\n endRuleIndex: lastInsertedIndex ?? firstInsertedIndex ?? 0,\n indices: insertedIndices,\n };\n } catch (error) {\n console.warn('Failed to insert CSS rules:', error, {\n flattenedRules,\n className,\n });\n return null;\n }\n }\n\n /**\n * Insert global CSS rules\n */\n insertGlobalRule(\n registry: RootRegistry,\n flattenedRules: StyleRule[],\n globalKey: string,\n root: Document | ShadowRoot,\n ): RuleInfo | null {\n // Insert the rule using the same mechanism as regular rules\n const ruleInfo = this.insertRule(registry, flattenedRules, globalKey, root);\n\n // Track global rules for index adjustment\n if (ruleInfo) {\n registry.globalRules.set(globalKey, ruleInfo);\n }\n\n return ruleInfo;\n }\n\n /**\n * Delete a global CSS rule by key\n */\n public deleteGlobalRule(registry: RootRegistry, globalKey: string): void {\n const ruleInfo = registry.globalRules.get(globalKey);\n if (!ruleInfo) {\n return;\n }\n\n // Delete the rule using the standard deletion mechanism\n this.deleteRule(registry, ruleInfo);\n\n // Remove from global rules tracking\n registry.globalRules.delete(globalKey);\n }\n\n /**\n * Adjust rule indices after deletion to account for shifting\n */\n private adjustIndicesAfterDeletion(\n registry: RootRegistry,\n sheetIndex: number,\n startIdx: number,\n endIdx: number,\n deleteCount: number,\n deletedRuleInfo: RuleInfo,\n deletedIndices?: number[],\n ): void {\n try {\n const sortedDeleted =\n deletedIndices && deletedIndices.length > 0\n ? [...deletedIndices].sort((a, b) => a - b)\n : null;\n const countDeletedBefore = (sorted: number[], idx: number): number => {\n let shift = 0;\n for (const delIdx of sorted) {\n if (delIdx < idx) shift++;\n else break;\n }\n return shift;\n };\n // Helper function to adjust a single RuleInfo\n const adjustRuleInfo = (info: RuleInfo): void => {\n if (info === deletedRuleInfo) return; // Skip the deleted rule\n if (info.sheetIndex !== sheetIndex) return; // Different sheet\n\n if (!info.indices || info.indices.length === 0) {\n return;\n }\n\n if (sortedDeleted) {\n // Adjust each index based on how many deleted indices are before it\n info.indices = info.indices.map((idx) => {\n return idx - countDeletedBefore(sortedDeleted, idx);\n });\n } else {\n // Contiguous deletion: shift indices after the deleted range\n info.indices = info.indices.map((idx) =>\n idx > endIdx ? Math.max(0, idx - deleteCount) : idx,\n );\n }\n\n // Update ruleIndex and endRuleIndex to match adjusted indices\n if (info.indices.length > 0) {\n info.ruleIndex = Math.min(...info.indices);\n info.endRuleIndex = Math.max(...info.indices);\n }\n };\n\n // Adjust active rules\n for (const info of registry.rules.values()) {\n adjustRuleInfo(info);\n }\n\n // Adjust global rules\n for (const info of registry.globalRules.values()) {\n adjustRuleInfo(info);\n }\n\n // No need to separately adjust unused rules since they're part of the rules Map\n\n // Adjust keyframes indices stored in cache\n for (const entry of registry.keyframesCache.values()) {\n const ki = entry.info as KeyframesInfo;\n if (ki.sheetIndex !== sheetIndex) continue;\n if (sortedDeleted) {\n const shift = countDeletedBefore(sortedDeleted, ki.ruleIndex);\n if (shift > 0) {\n ki.ruleIndex = Math.max(0, ki.ruleIndex - shift);\n }\n } else if (ki.ruleIndex > endIdx) {\n ki.ruleIndex = Math.max(0, ki.ruleIndex - deleteCount);\n }\n }\n } catch {\n // Defensive: do not let index adjustments crash cleanup\n }\n }\n\n /**\n * Delete a CSS rule from the sheet\n */\n deleteRule(registry: RootRegistry, ruleInfo: RuleInfo): void {\n const sheet = registry.sheets[ruleInfo.sheetIndex];\n\n if (!sheet) {\n return;\n }\n\n try {\n const texts: string[] =\n this.config.devMode && Array.isArray(ruleInfo.cssText)\n ? ruleInfo.cssText.slice()\n : [];\n\n const styleElement = sheet.sheet;\n const styleSheet = styleElement.sheet;\n\n if (styleSheet) {\n const rules = styleSheet.cssRules;\n\n // Use exact indices if available, otherwise fall back to range\n if (ruleInfo.indices && ruleInfo.indices.length > 0) {\n // NEW: Delete using exact tracked indices\n const sortedIndices = [...ruleInfo.indices].sort((a, b) => b - a); // Sort descending\n const deletedIndices: number[] = [];\n\n for (const idx of sortedIndices) {\n if (idx >= 0 && idx < styleSheet.cssRules.length) {\n try {\n styleSheet.deleteRule(idx);\n deletedIndices.push(idx);\n } catch (e) {\n console.warn(`Failed to delete rule at index ${idx}:`, e);\n }\n }\n }\n\n sheet.ruleCount = Math.max(\n 0,\n sheet.ruleCount - deletedIndices.length,\n );\n\n // Adjust indices for all other rules\n if (deletedIndices.length > 0) {\n this.adjustIndicesAfterDeletion(\n registry,\n ruleInfo.sheetIndex,\n Math.min(...deletedIndices),\n Math.max(...deletedIndices),\n deletedIndices.length,\n ruleInfo,\n deletedIndices,\n );\n }\n } else {\n // FALLBACK: Use old range-based deletion for backwards compatibility\n const startIdx = Math.max(0, ruleInfo.ruleIndex);\n const endIdx = Math.min(\n rules.length - 1,\n Number.isFinite(ruleInfo.endRuleIndex as number)\n ? (ruleInfo.endRuleIndex as number)\n : startIdx,\n );\n\n if (Number.isFinite(startIdx) && endIdx >= startIdx) {\n const deleteCount = endIdx - startIdx + 1;\n for (let idx = endIdx; idx >= startIdx; idx--) {\n if (idx < 0 || idx >= styleSheet.cssRules.length) continue;\n styleSheet.deleteRule(idx);\n }\n sheet.ruleCount = Math.max(0, sheet.ruleCount - deleteCount);\n\n // After deletion, all subsequent rule indices shift left by deleteCount.\n // We must adjust stored indices for all other RuleInfo within the same sheet.\n this.adjustIndicesAfterDeletion(\n registry,\n ruleInfo.sheetIndex,\n startIdx,\n endIdx,\n deleteCount,\n ruleInfo,\n );\n }\n }\n }\n\n // Dev-only: remove cssText entries from validation set\n if (this.config.devMode && texts.length) {\n try {\n for (const text of texts) {\n registry.ruleTextSet.delete(text);\n }\n } catch {\n // noop\n }\n }\n } catch (error) {\n console.warn('Failed to delete CSS rule:', error);\n }\n }\n\n /**\n * Find a sheet with available space or return null\n */\n private findAvailableSheet(\n registry: RootRegistry,\n _root: Document | ShadowRoot,\n ): SheetInfo | null {\n const maxRules = this.config.maxRulesPerSheet;\n\n if (!maxRules) {\n // No limit, use the last sheet if it exists\n const lastSheet = registry.sheets[registry.sheets.length - 1];\n return lastSheet || null;\n }\n\n // Find sheet with space\n for (const sheet of registry.sheets) {\n if (sheet.ruleCount < maxRules) {\n return sheet;\n }\n }\n\n return null; // No available sheet found\n }\n\n /**\n * Find an available rule index in the sheet\n */\n findAvailableRuleIndex(sheet: SheetInfo): number {\n // Always append to the end - CSS doesn't have holes\n return sheet.ruleCount;\n }\n\n /**\n * Schedule bulk cleanup of all unused styles (non-stacking)\n */\n private scheduleBulkCleanup(registry: RootRegistry): void {\n // Clear any existing timeout to prevent stacking\n if (registry.bulkCleanupTimeout) {\n if (\n this.config.idleCleanup &&\n typeof cancelIdleCallback !== 'undefined'\n ) {\n cancelIdleCallback(registry.bulkCleanupTimeout as unknown as number);\n } else {\n clearTimeout(registry.bulkCleanupTimeout);\n }\n registry.bulkCleanupTimeout = null;\n }\n\n const performCleanup = () => {\n this.performBulkCleanup(registry);\n registry.bulkCleanupTimeout = null;\n };\n\n if (this.config.idleCleanup && typeof requestIdleCallback !== 'undefined') {\n registry.bulkCleanupTimeout = requestIdleCallback(performCleanup);\n } else {\n const delay = this.config.bulkCleanupDelay || 5000;\n registry.bulkCleanupTimeout = setTimeout(performCleanup, delay);\n }\n }\n\n /**\n * Force cleanup of unused styles\n */\n public forceCleanup(registry: RootRegistry): void {\n this.performBulkCleanup(registry, true);\n }\n\n /**\n * Perform bulk cleanup of all unused styles\n */\n private performBulkCleanup(registry: RootRegistry, cleanupAll = false): void {\n const cleanupStartTime = Date.now();\n\n // Calculate unused rules dynamically: rules that have refCount = 0\n const unusedClassNames = Array.from(registry.refCounts.entries())\n .filter(([, refCount]) => refCount === 0)\n .map(([className]) => className);\n\n if (unusedClassNames.length === 0) return;\n\n // Build candidates list - no age filtering needed\n const candidates = unusedClassNames.map((className) => {\n const ruleInfo = registry.rules.get(className)!; // We know it exists\n return {\n className,\n ruleInfo,\n };\n });\n\n if (candidates.length === 0) return;\n\n // Limit deletion scope per run (batch ratio) unless cleanupAll is true\n let selected = candidates;\n if (!cleanupAll) {\n const ratio = this.config.bulkCleanupBatchRatio ?? 0.5;\n const limit = Math.max(\n 1,\n Math.floor(candidates.length * Math.min(1, Math.max(0, ratio))),\n );\n selected = candidates.slice(0, limit);\n }\n\n let cleanedUpCount = 0;\n let totalCssSize = 0;\n let totalRulesDeleted = 0;\n\n // Group by sheet for efficient deletion\n const rulesBySheet = new Map<\n number,\n { className: string; ruleInfo: RuleInfo }[]\n >();\n\n // Calculate CSS size before deletion and group rules\n for (const { className, ruleInfo } of selected) {\n const sheetIndex = ruleInfo.sheetIndex;\n\n // Dev-only metrics: estimate CSS size and rule count if available\n if (this.config.devMode && Array.isArray(ruleInfo.cssText)) {\n const cssSize = ruleInfo.cssText.reduce(\n (total, css) => total + css.length,\n 0,\n );\n totalCssSize += cssSize;\n totalRulesDeleted += ruleInfo.cssText.length;\n }\n\n if (!rulesBySheet.has(sheetIndex)) {\n rulesBySheet.set(sheetIndex, []);\n }\n rulesBySheet.get(sheetIndex)!.push({ className, ruleInfo });\n }\n\n // Delete rules from each sheet (in reverse order to preserve indices)\n for (const [_sheetIndex, rulesInSheet] of rulesBySheet) {\n // Sort by rule index in descending order for safe deletion\n rulesInSheet.sort((a, b) => b.ruleInfo.ruleIndex - a.ruleInfo.ruleIndex);\n\n for (const { className, ruleInfo } of rulesInSheet) {\n // SAFETY 1: Double-check refCount is still 0\n const currentRefCount = registry.refCounts.get(className) || 0;\n if (currentRefCount > 0) {\n // Class became active again; do not delete\n continue;\n }\n\n // SAFETY 2: Ensure rule wasn't replaced\n // Between scheduling and execution a class may have been replaced with a new RuleInfo\n const currentInfo = registry.rules.get(className);\n if (currentInfo !== ruleInfo) {\n // Rule was replaced; skip deletion of the old reference\n continue;\n }\n\n // SAFETY 3: Verify the sheet element is still valid and accessible\n const sheetInfo = registry.sheets[ruleInfo.sheetIndex];\n if (!sheetInfo || !sheetInfo.sheet) {\n // Sheet was removed or corrupted; skip this rule\n continue;\n }\n\n // SAFETY 4: Verify the stylesheet itself is accessible\n const styleSheet = sheetInfo.sheet.sheet;\n if (!styleSheet) {\n // Stylesheet not available; skip this rule\n continue;\n }\n\n // SAFETY 5: Verify rule index is still within valid range\n const maxRuleIndex = styleSheet.cssRules.length - 1;\n const startIdx = ruleInfo.ruleIndex;\n const endIdx = ruleInfo.endRuleIndex ?? ruleInfo.ruleIndex;\n\n if (startIdx < 0 || endIdx > maxRuleIndex || startIdx > endIdx) {\n // Rule indices are out of bounds; skip this rule\n continue;\n }\n\n // All safety checks passed - proceed with deletion\n this.deleteRule(registry, ruleInfo);\n registry.rules.delete(className);\n registry.refCounts.delete(className);\n\n // Clean up cache key mappings that point to this className\n const keysToDelete: string[] = [];\n for (const [\n key,\n mappedClassName,\n ] of registry.cacheKeyToClassName.entries()) {\n if (mappedClassName === className) {\n keysToDelete.push(key);\n }\n }\n for (const key of keysToDelete) {\n registry.cacheKeyToClassName.delete(key);\n }\n cleanedUpCount++;\n }\n }\n\n // Update metrics\n if (registry.metrics) {\n registry.metrics.bulkCleanups++;\n registry.metrics.stylesCleanedUp += cleanedUpCount;\n\n // Add detailed cleanup stats to history\n registry.metrics.cleanupHistory.push({\n timestamp: cleanupStartTime,\n classesDeleted: cleanedUpCount,\n cssSize: totalCssSize,\n rulesDeleted: totalRulesDeleted,\n });\n }\n }\n\n /**\n * Get total number of rules across all sheets\n */\n getTotalRuleCount(registry: RootRegistry): number {\n return registry.sheets.reduce(\n (total, sheet) => total + sheet.ruleCount - sheet.holes.length,\n 0,\n );\n }\n\n /**\n * Get CSS text from all sheets (for SSR)\n */\n getCssText(registry: RootRegistry): string {\n const cssChunks: string[] = [];\n\n for (const sheet of registry.sheets) {\n try {\n const styleElement = sheet.sheet;\n if (styleElement.textContent) {\n cssChunks.push(styleElement.textContent);\n } else if (styleElement.sheet) {\n const rules = Array.from(styleElement.sheet.cssRules);\n cssChunks.push(rules.map((rule) => rule.cssText).join('\\n'));\n }\n } catch (error) {\n console.warn('Failed to read CSS from sheet:', error);\n }\n }\n\n return cssChunks.join('\\n');\n }\n\n /**\n * Get cache performance metrics\n */\n getMetrics(registry: RootRegistry): CacheMetrics | null {\n if (!registry.metrics) return null;\n\n // Calculate unusedHits on demand - only count CSS rules since keyframes are disposed immediately\n const unusedRulesCount = Array.from(registry.refCounts.values()).filter(\n (count) => count === 0,\n ).length;\n\n return {\n ...registry.metrics,\n unusedHits: unusedRulesCount,\n };\n }\n\n /**\n * Reset cache performance metrics\n */\n resetMetrics(registry: RootRegistry): void {\n if (registry.metrics) {\n registry.metrics = {\n hits: 0,\n misses: 0,\n bulkCleanups: 0,\n totalInsertions: 0,\n totalUnused: 0,\n stylesCleanedUp: 0,\n cleanupHistory: [],\n startTime: Date.now(),\n };\n }\n }\n\n /**\n * Convert keyframes steps to CSS string.\n * Public so the SSR collector can format keyframes without DOM access.\n * Returns both the CSS text and a combined declarations string for property type scanning.\n */\n stepsToCSS(steps: KeyframesSteps): {\n css: string;\n declarations: string;\n } {\n const rules: string[] = [];\n const allDeclarations: string[] = [];\n\n for (const [key, value] of Object.entries(steps)) {\n // Support raw CSS strings for backwards compatibility\n if (typeof value === 'string') {\n rules.push(`${key} { ${value.trim()} }`);\n allDeclarations.push(value.trim());\n continue;\n }\n\n // Treat value as a style map and process via tasty style handlers\n const styleMap = (value || {}) as StyleValueStateMap;\n\n // Build a deterministic handler queue based on present style keys\n const styleNames = Object.keys(styleMap).sort();\n const handlerQueue: StyleHandler[] = [];\n const seenHandlers = new Set<StyleHandler>();\n\n styleNames.forEach((styleName) => {\n let handlers = STYLE_HANDLER_MAP[styleName];\n if (!handlers) {\n // Create a default handler for unknown styles (maps to kebab-case CSS or custom props)\n handlers = STYLE_HANDLER_MAP[styleName] = [createStyle(styleName)];\n }\n\n handlers.forEach((handler) => {\n if (!seenHandlers.has(handler)) {\n seenHandlers.add(handler);\n handlerQueue.push(handler);\n }\n });\n });\n\n // Accumulate declarations (ordered). We intentionally ignore `$` selector fan-out\n // and any responsive/state bindings for keyframes.\n const declarationPairs: { prop: string; value: string }[] = [];\n\n handlerQueue.forEach((handler) => {\n const lookup = handler.__lookupStyles;\n const filteredMap = lookup.reduce<StyleValueStateMap>((acc, name) => {\n const v = styleMap[name];\n if (v !== undefined) acc[name] = v;\n return acc;\n }, {});\n\n const result = handler(filteredMap);\n if (!result) return;\n\n const results = Array.isArray(result) ? result : [result];\n results.forEach((cssMap) => {\n if (!cssMap || typeof cssMap !== 'object') return;\n const { $: _$, ...props } = cssMap as CSSMap;\n\n Object.entries(props).forEach(([prop, val]) => {\n if (val == null || val === '') return;\n\n if (Array.isArray(val)) {\n // Multiple values for the same property -> emit in order\n val.forEach((v) => {\n if (v != null && v !== '') {\n declarationPairs.push({ prop, value: String(v) });\n }\n });\n } else {\n declarationPairs.push({ prop, value: String(val) });\n }\n });\n });\n });\n\n // Fallback: if nothing produced (e.g., empty object), generate empty block\n const declarations = declarationPairs\n .map((d) => `${d.prop}: ${d.value}`)\n .join('; ');\n\n rules.push(`${key} { ${declarations.trim()} }`);\n allDeclarations.push(declarations);\n }\n\n return { css: rules.join(' '), declarations: allDeclarations.join('; ') };\n }\n\n /**\n * Insert keyframes rule.\n * Returns the KeyframesInfo and the raw declarations string for property type scanning.\n */\n insertKeyframes(\n registry: RootRegistry,\n steps: KeyframesSteps,\n name: string,\n root: Document | ShadowRoot,\n ): { info: KeyframesInfo; declarations: string } | null {\n let targetSheet = this.findAvailableSheet(registry, root);\n if (!targetSheet) {\n targetSheet = this.createSheet(registry, root);\n }\n\n const ruleIndex = this.findAvailableRuleIndex(targetSheet);\n const sheetIndex = registry.sheets.indexOf(targetSheet);\n\n try {\n const { css: cssSteps, declarations } = this.stepsToCSS(steps);\n const fullRule = `@keyframes ${name} { ${cssSteps} }`;\n\n const styleElement = targetSheet.sheet;\n const styleSheet = styleElement.sheet;\n\n if (styleSheet && !this.config.forceTextInjection) {\n const safeIndex = Math.min(\n Math.max(0, ruleIndex),\n styleSheet.cssRules.length,\n );\n styleSheet.insertRule(fullRule, safeIndex);\n } else {\n styleElement.textContent =\n (styleElement.textContent || '') + '\\n' + fullRule;\n }\n\n targetSheet.ruleCount++;\n\n return {\n info: {\n name,\n ruleIndex,\n sheetIndex,\n cssText: this.config.devMode ? fullRule : undefined,\n },\n declarations,\n };\n } catch (error) {\n console.warn('Failed to insert keyframes:', error);\n return null;\n }\n }\n\n /**\n * Delete keyframes rule\n */\n deleteKeyframes(registry: RootRegistry, info: KeyframesInfo): void {\n const sheet = registry.sheets[info.sheetIndex];\n if (!sheet) return;\n\n try {\n const styleElement = sheet.sheet;\n const styleSheet = styleElement.sheet;\n\n if (styleSheet) {\n if (\n info.ruleIndex >= 0 &&\n info.ruleIndex < styleSheet.cssRules.length\n ) {\n styleSheet.deleteRule(info.ruleIndex);\n sheet.ruleCount = Math.max(0, sheet.ruleCount - 1);\n\n // Adjust indices for all other rules in the same sheet\n // This is critical - when a keyframe rule is deleted, all rules\n // with higher indices shift down by 1\n this.adjustIndicesAfterDeletion(\n registry,\n info.sheetIndex,\n info.ruleIndex,\n info.ruleIndex,\n 1,\n // Create a dummy RuleInfo to satisfy the function signature\n {\n className: '',\n ruleIndex: info.ruleIndex,\n sheetIndex: info.sheetIndex,\n } as RuleInfo,\n [info.ruleIndex],\n );\n }\n }\n } catch (error) {\n console.warn('Failed to delete keyframes:', error);\n }\n }\n\n /**\n * Schedule async cleanup check (non-stacking)\n */\n public checkCleanupNeeded(registry: RootRegistry): void {\n // Clear any existing check timeout to prevent stacking\n if (registry.cleanupCheckTimeout) {\n clearTimeout(registry.cleanupCheckTimeout);\n registry.cleanupCheckTimeout = null;\n }\n\n // Schedule the actual check with setTimeout(..., 0)\n registry.cleanupCheckTimeout = setTimeout(() => {\n this.performCleanupCheck(registry);\n registry.cleanupCheckTimeout = null;\n }, 0);\n }\n\n /**\n * Perform the actual cleanup check (called asynchronously)\n */\n private performCleanupCheck(registry: RootRegistry): void {\n // Count unused rules (refCount = 0) - keyframes are disposed immediately\n const unusedRulesCount = Array.from(registry.refCounts.values()).filter(\n (count) => count === 0,\n ).length;\n const threshold = this.config.unusedStylesThreshold || 500;\n\n if (unusedRulesCount >= threshold) {\n this.scheduleBulkCleanup(registry);\n }\n }\n\n /**\n * Clean up resources for a root\n */\n cleanup(root: Document | ShadowRoot): void {\n const registry = this.rootRegistries.get(root);\n\n if (!registry) {\n return;\n }\n\n // Cancel any scheduled bulk cleanup\n if (registry.bulkCleanupTimeout) {\n if (\n this.config.idleCleanup &&\n typeof cancelIdleCallback !== 'undefined'\n ) {\n cancelIdleCallback(registry.bulkCleanupTimeout as unknown as number);\n } else {\n clearTimeout(registry.bulkCleanupTimeout);\n }\n registry.bulkCleanupTimeout = null;\n }\n\n // Cancel any scheduled cleanup check\n if (registry.cleanupCheckTimeout) {\n clearTimeout(registry.cleanupCheckTimeout);\n registry.cleanupCheckTimeout = null;\n }\n\n // Remove all sheets\n for (const sheet of registry.sheets) {\n try {\n // Remove style element\n const styleElement = sheet.sheet;\n if (styleElement.parentNode) {\n styleElement.parentNode.removeChild(styleElement);\n }\n } catch (error) {\n console.warn('Failed to cleanup sheet:', error);\n }\n }\n\n // Clear registry\n this.rootRegistries.delete(root);\n\n // Clean up raw CSS style element\n const rawStyleElement = this.rawStyleElements.get(root);\n if (rawStyleElement?.parentNode) {\n rawStyleElement.parentNode.removeChild(rawStyleElement);\n }\n this.rawStyleElements.delete(root);\n this.rawCSSBlocks.delete(root);\n }\n\n /**\n * Get or create a dedicated style element for raw CSS\n * Raw CSS is kept separate from tasty-managed sheets to avoid index conflicts\n */\n private getOrCreateRawStyleElement(\n root: Document | ShadowRoot,\n ): HTMLStyleElement {\n let styleElement = this.rawStyleElements.get(root);\n\n if (!styleElement) {\n styleElement =\n (root as Document).createElement?.('style') ||\n document.createElement('style');\n\n if (this.config.nonce) {\n styleElement.nonce = this.config.nonce;\n }\n\n styleElement.setAttribute('data-tasty-raw', '');\n\n // Append to head or shadow root\n if ('head' in root && root.head) {\n root.head.appendChild(styleElement);\n } else if ('appendChild' in root) {\n root.appendChild(styleElement);\n } else {\n document.head.appendChild(styleElement);\n }\n\n this.rawStyleElements.set(root, styleElement);\n this.rawCSSBlocks.set(root, new Map());\n }\n\n return styleElement;\n }\n\n /**\n * Inject raw CSS text directly without parsing\n * Returns a dispose function to remove the injected CSS\n */\n injectRawCSS(css: string, root: Document | ShadowRoot): RawCSSResult {\n if (!css.trim()) {\n return {\n dispose: () => {\n /* noop */\n },\n };\n }\n\n const styleElement = this.getOrCreateRawStyleElement(root);\n const blocksMap = this.rawCSSBlocks.get(root)!;\n\n // Generate unique ID for this block\n const id = `raw_${this.rawCSSCounter++}`;\n\n // Calculate offsets\n const currentContent = styleElement.textContent || '';\n const startOffset = currentContent.length;\n const cssWithNewline = (currentContent ? '\\n' : '') + css;\n const endOffset = startOffset + cssWithNewline.length;\n\n // Append CSS\n styleElement.textContent = currentContent + cssWithNewline;\n\n // Track the block\n const info: RawCSSInfo = {\n id,\n css,\n startOffset,\n endOffset,\n };\n blocksMap.set(id, info);\n\n return {\n dispose: () => {\n this.disposeRawCSS(id, root);\n },\n };\n }\n\n /**\n * Remove a raw CSS block by ID\n */\n private disposeRawCSS(id: string, root: Document | ShadowRoot): void {\n const styleElement = this.rawStyleElements.get(root);\n const blocksMap = this.rawCSSBlocks.get(root);\n\n if (!styleElement || !blocksMap) {\n return;\n }\n\n const info = blocksMap.get(id);\n if (!info) {\n return;\n }\n\n // Remove from tracking\n blocksMap.delete(id);\n\n // Rebuild the CSS content from remaining blocks\n // This is simpler and more reliable than trying to splice strings\n const remainingBlocks = Array.from(blocksMap.values());\n\n if (remainingBlocks.length === 0) {\n styleElement.textContent = '';\n } else {\n // Rebuild with remaining CSS blocks in order of their original insertion\n // Sort by original startOffset to maintain order\n remainingBlocks.sort((a, b) => a.startOffset - b.startOffset);\n const newContent = remainingBlocks.map((block) => block.css).join('\\n');\n styleElement.textContent = newContent;\n\n // Update offsets for remaining blocks\n let offset = 0;\n for (const block of remainingBlocks) {\n block.startOffset = offset;\n block.endOffset = offset + block.css.length;\n offset = block.endOffset + 1; // +1 for newline\n }\n }\n }\n\n /**\n * Get the raw CSS content for SSR\n */\n getRawCSSText(root: Document | ShadowRoot): string {\n const styleElement = this.rawStyleElements.get(root);\n return styleElement?.textContent || '';\n }\n}\n"],"mappings":";;;;;AAkBA,IAAa,eAAb,MAA0B;CACxB,AAAQ,iCAAiB,IAAI,SAA8C;CAC3E,AAAQ;;CAER,AAAQ,mCAAmB,IAAI,SAG5B;;CAEH,AAAQ,+BAAe,IAAI,SAGxB;;CAEH,AAAQ,gBAAgB;CAExB,YAAY,QAA6B;AACvC,OAAK,SAAS;;;;;CAMhB,YAAY,MAA2C;EACrD,IAAI,WAAW,KAAK,eAAe,IAAI,KAAK;AAE5C,MAAI,CAAC,UAAU;GACb,MAAM,UAAoC,KAAK,OAAO,UAClD;IACE,MAAM;IACN,QAAQ;IACR,cAAc;IACd,iBAAiB;IACjB,aAAa;IACb,iBAAiB;IACjB,gBAAgB,EAAE;IAClB,WAAW,KAAK,KAAK;IACtB,GACD;AAEJ,cAAW;IACT,QAAQ,EAAE;IACV,2BAAW,IAAI,KAAK;IACpB,uBAAO,IAAI,KAAK;IAChB,qCAAqB,IAAI,KAAK;IAC9B,6BAAa,IAAI,KAAa;IAC9B,oBAAoB;IACpB,qBAAqB;IACrB;IACA,cAAc;IACd,gCAAgB,IAAI,KAAK;IACzB,wCAAwB,IAAI,KAAK;IACjC,kBAAkB;IAClB,oCAAoB,IAAI,KAAqB;IAC7C,mCAAmB,IAAI,KAAa;IACpC,uCAAuB,IAAI,KAAa;IACxC,6BAAa,IAAI,KAAK;IACtB,sBAAsB,IAAI,sBAAsB;IACjD;AAED,QAAK,eAAe,IAAI,MAAM,SAAS;;AAGzC,SAAO;;;;;CAMT,YAAY,UAAwB,MAAwC;EAG1E,MAAM,YAAuB;GAC3B,OAHY,KAAK,mBAAmB,KAAK;GAIzC,WAAW;GACX,OAAO,EAAE;GACV;AAED,WAAS,OAAO,KAAK,UAAU;AAC/B,SAAO;;;;;CAMT,AAAQ,mBAAmB,MAA+C;EACxE,MAAM,QACH,KAAkB,gBAAgB,QAAQ,IAC3C,SAAS,cAAc,QAAQ;AAEjC,MAAI,KAAK,OAAO,MACd,OAAM,QAAQ,KAAK,OAAO;AAG5B,QAAM,aAAa,cAAc,GAAG;AAGpC,MAAI,UAAU,QAAQ,KAAK,KACzB,MAAK,KAAK,YAAY,MAAM;WACnB,iBAAiB,KAC1B,MAAK,YAAY,MAAM;MAEvB,UAAS,KAAK,YAAY,MAAM;AAIlC,MAAI,CAAC,MAAM,eAAe,CAAC,KAAK,OAAO,mBACrC,SAAQ,MAAM,yDAAyD;GACrE,YAAY,MAAM,YAAY;GAC9B,aAAa,MAAM;GACpB,CAAC;AAGJ,SAAO;;;;;CAMT,WACE,UACA,gBACA,WACA,MACiB;EAEjB,IAAI,cAAc,KAAK,mBAAmB,UAAU,KAAK;AAEzD,MAAI,CAAC,YACH,eAAc,KAAK,YAAY,UAAU,KAAK;EAGhD,MAAM,aAAa,SAAS,OAAO,QAAQ,YAAY;AAEvD,MAAI;GAEF,MAAM,eAA4B,EAAE;GACpC,MAAM,2BAAW,IAAI,KASlB;GAEH,MAAM,SAAS,OAAmB,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,GAAG;AAEnE,kBAAe,SAAS,MAAM;IAC5B,MAAM,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,IAAI,EAAE,gBAAgB,MAAM;IAC3E,MAAM,WAAW,SAAS,IAAI,IAAI;AAClC,QAAI,SAEF,UAAS,eAAe,SAAS,eAC7B,GAAG,SAAS,aAAa,GAAG,EAAE,iBAC9B,EAAE;SACD;AACL,cAAS,IAAI,KAAK;MAChB,KAAK,aAAa;MAClB,UAAU,EAAE;MACZ,SAAS,EAAE;MACX,eAAe,EAAE;MACjB,cAAc,EAAE;MACjB,CAAC;AACF,kBAAa,KAAK,EAAE,GAAG,GAAG,CAAC;;KAE7B;AAGF,YAAS,SAAS,QAAQ;AACxB,iBAAa,IAAI,OAAO;KACtB,UAAU,IAAI;KACd,SAAS,IAAI;KACb,eAAe,IAAI;KACnB,cAAc,IAAI;KACnB;KACD;GAGF,MAAM,oBAA8B,EAAE;GACtC,MAAM,kBAA4B,EAAE;GAEpC,IAAI,mBAAmB,KAAK,uBAAuB,YAAY;GAC/D,IAAI,qBAAoC;GACxC,IAAI,oBAAmC;AAEvC,QAAK,MAAM,QAAQ,cAAc;IAC/B,MAAM,eAAe,KAAK;IAC1B,MAAM,eAAe,KAAK,gBACtB,qBAAqB,aAAa,MAClC;IACJ,MAAM,WAAW,GAAG,KAAK,SAAS,KAAK,aAAa;IAGpD,IAAI,WAAW;AACf,QAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,EACxC,YAAW,KAAK,QAAQ,QACrB,KAAK,WAAW,GAAG,OAAO,KAAK,IAAI,KACpC,SACD;IAIH,MAAM,eAAe,YAAY;IACjC,MAAM,aAAa,aAAa;AAEhC,QAAI,cAAc,CAAC,KAAK,OAAO,oBAAoB;KAEjD,MAAM,WAAW,WAAW,SAAS;KACrC,MAAM,kBAAkB,KAAK,uBAAuB,YAAY;KAChE,MAAM,YAAY,KAAK,IAAI,KAAK,IAAI,GAAG,gBAAgB,EAAE,SAAS;KAGlE,MAAM,wBAAwB,iBAAmC;MAC/D,MAAM,QAAkB,EAAE;MAC1B,IAAI,MAAM;MACV,IAAI,UAAU;MACd,IAAI,WAAW;MACf,IAAI,QAAwB;AAC5B,WAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;OAC5C,MAAM,KAAK,aAAa;AACxB,WAAI,OAAO;AACT,YAAI,OAAO,SAAS,aAAa,IAAI,OAAO,KAC1C,SAAQ;AAEV,eAAO;AACP;;AAEF,WAAI,OAAO,QAAO,OAAO,KAAK;AAC5B,gBAAQ;AACR,eAAO;AACP;;AAEF,WAAI,OAAO,IAAK;gBACP,OAAO,IAAK,WAAU,KAAK,IAAI,GAAG,UAAU,EAAE;gBAC9C,OAAO,IAAK;gBACZ,OAAO,IAAK,YAAW,KAAK,IAAI,GAAG,WAAW,EAAE;AAEzD,WAAI,OAAO,OAAO,YAAY,KAAK,aAAa,GAAG;QACjD,MAAM,OAAO,IAAI,MAAM;AACvB,YAAI,KAAM,OAAM,KAAK,KAAK;AAC1B,cAAM;aAEN,QAAO;;MAGX,MAAM,OAAO,IAAI,MAAM;AACvB,UAAI,KAAM,OAAM,KAAK,KAAK;AAC1B,aAAO;;AAGT,SAAI;AACF,iBAAW,WAAW,UAAU,UAAU;AAE1C,kBAAY;AACZ,sBAAgB,KAAK,UAAU;AAC/B,UAAI,sBAAsB,KAAM,sBAAqB;AACrD,0BAAoB;AACpB,yBAAmB,YAAY;cACxB,GAAG;MAGV,MAAM,YAAY,qBAAqB,KAAK,SAAS;AACrD,UAAI,UAAU,SAAS,GAAG;OACxB,IAAI,cAAc;AAClB,YAAK,MAAM,OAAO,WAAW;QAC3B,MAAM,aAAa,GAAG,IAAI,KAAK,aAAa;QAC5C,IAAI,aAAa;AACjB,YAAI,KAAK,WAAW,KAAK,QAAQ,SAAS,EACxC,cAAa,KAAK,QAAQ,QACvB,KAAK,WAAW,GAAG,OAAO,KAAK,IAAI,KACpC,WACD;AAGH,YAAI;SAEF,MAAM,SAAS,WAAW,SAAS;SACnC,MAAM,YAAY,KAAK,uBAAuB,YAAY;SAC1D,MAAM,MAAM,KAAK,IAAI,KAAK,IAAI,GAAG,UAAU,EAAE,OAAO;AACpD,oBAAW,WAAW,YAAY,IAAI;AAEtC,qBAAY;AACZ,yBAAgB,KAAK,IAAI;AACzB,aAAI,sBAAsB,KAAM,sBAAqB;AACrD,6BAAoB;AACpB,4BAAmB,MAAM;AACzB,uBAAc;iBACP,WAAW;AAGhB,iBAAQ,KACN,sCACA,YACA,UACD;;;AAKP,WAAI,CAAC,aAAa;YAMhB,SAAQ,KAAK,sCAAsC,UAAU,EAAE;;WAIhE;KAGL,MAAM,kBAAkB,KAAK,uBAAuB,YAAY;AAChE,kBAAa,eACV,aAAa,eAAe,MAAM,OAAO;AAE5C,iBAAY;AACZ,qBAAgB,KAAK,gBAAgB;AACrC,SAAI,sBAAsB,KAAM,sBAAqB;AACrD,yBAAoB;AACpB,wBAAmB,kBAAkB;;AAIvC,QAAI,CAAC,aAAa,cAAc,CAAC,KAAK,OAAO,mBAC3C,SAAQ,MACN,mEACA;KACE;KACA,WAAW;KACZ,CACF;AAIH,QAAI,KAAK,OAAO,SAAS;AACvB,uBAAkB,KAAK,SAAS;AAChC,SAAI;AACF,eAAS,YAAY,IAAI,SAAS;aAC5B;;;AAUZ,OAAI,gBAAgB,WAAW,EAC7B,QAAO;AAGT,UAAO;IACL;IACA,WAAW,sBAAsB;IACjC;IACA,SAAS,KAAK,OAAO,UAAU,oBAAoB;IACnD,cAAc,qBAAqB,sBAAsB;IACzD,SAAS;IACV;WACM,OAAO;AACd,WAAQ,KAAK,+BAA+B,OAAO;IACjD;IACA;IACD,CAAC;AACF,UAAO;;;;;;CAOX,iBACE,UACA,gBACA,WACA,MACiB;EAEjB,MAAM,WAAW,KAAK,WAAW,UAAU,gBAAgB,WAAW,KAAK;AAG3E,MAAI,SACF,UAAS,YAAY,IAAI,WAAW,SAAS;AAG/C,SAAO;;;;;CAMT,AAAO,iBAAiB,UAAwB,WAAyB;EACvE,MAAM,WAAW,SAAS,YAAY,IAAI,UAAU;AACpD,MAAI,CAAC,SACH;AAIF,OAAK,WAAW,UAAU,SAAS;AAGnC,WAAS,YAAY,OAAO,UAAU;;;;;CAMxC,AAAQ,2BACN,UACA,YACA,UACA,QACA,aACA,iBACA,gBACM;AACN,MAAI;GACF,MAAM,gBACJ,kBAAkB,eAAe,SAAS,IACtC,CAAC,GAAG,eAAe,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE,GACzC;GACN,MAAM,sBAAsB,QAAkB,QAAwB;IACpE,IAAI,QAAQ;AACZ,SAAK,MAAM,UAAU,OACnB,KAAI,SAAS,IAAK;QACb;AAEP,WAAO;;GAGT,MAAM,kBAAkB,SAAyB;AAC/C,QAAI,SAAS,gBAAiB;AAC9B,QAAI,KAAK,eAAe,WAAY;AAEpC,QAAI,CAAC,KAAK,WAAW,KAAK,QAAQ,WAAW,EAC3C;AAGF,QAAI,cAEF,MAAK,UAAU,KAAK,QAAQ,KAAK,QAAQ;AACvC,YAAO,MAAM,mBAAmB,eAAe,IAAI;MACnD;QAGF,MAAK,UAAU,KAAK,QAAQ,KAAK,QAC/B,MAAM,SAAS,KAAK,IAAI,GAAG,MAAM,YAAY,GAAG,IACjD;AAIH,QAAI,KAAK,QAAQ,SAAS,GAAG;AAC3B,UAAK,YAAY,KAAK,IAAI,GAAG,KAAK,QAAQ;AAC1C,UAAK,eAAe,KAAK,IAAI,GAAG,KAAK,QAAQ;;;AAKjD,QAAK,MAAM,QAAQ,SAAS,MAAM,QAAQ,CACxC,gBAAe,KAAK;AAItB,QAAK,MAAM,QAAQ,SAAS,YAAY,QAAQ,CAC9C,gBAAe,KAAK;AAMtB,QAAK,MAAM,SAAS,SAAS,eAAe,QAAQ,EAAE;IACpD,MAAM,KAAK,MAAM;AACjB,QAAI,GAAG,eAAe,WAAY;AAClC,QAAI,eAAe;KACjB,MAAM,QAAQ,mBAAmB,eAAe,GAAG,UAAU;AAC7D,SAAI,QAAQ,EACV,IAAG,YAAY,KAAK,IAAI,GAAG,GAAG,YAAY,MAAM;eAEzC,GAAG,YAAY,OACxB,IAAG,YAAY,KAAK,IAAI,GAAG,GAAG,YAAY,YAAY;;UAGpD;;;;;CAQV,WAAW,UAAwB,UAA0B;EAC3D,MAAM,QAAQ,SAAS,OAAO,SAAS;AAEvC,MAAI,CAAC,MACH;AAGF,MAAI;GACF,MAAM,QACJ,KAAK,OAAO,WAAW,MAAM,QAAQ,SAAS,QAAQ,GAClD,SAAS,QAAQ,OAAO,GACxB,EAAE;GAGR,MAAM,aADe,MAAM,MACK;AAEhC,OAAI,YAAY;IACd,MAAM,QAAQ,WAAW;AAGzB,QAAI,SAAS,WAAW,SAAS,QAAQ,SAAS,GAAG;KAEnD,MAAM,gBAAgB,CAAC,GAAG,SAAS,QAAQ,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE;KACjE,MAAM,iBAA2B,EAAE;AAEnC,UAAK,MAAM,OAAO,cAChB,KAAI,OAAO,KAAK,MAAM,WAAW,SAAS,OACxC,KAAI;AACF,iBAAW,WAAW,IAAI;AAC1B,qBAAe,KAAK,IAAI;cACjB,GAAG;AACV,cAAQ,KAAK,kCAAkC,IAAI,IAAI,EAAE;;AAK/D,WAAM,YAAY,KAAK,IACrB,GACA,MAAM,YAAY,eAAe,OAClC;AAGD,SAAI,eAAe,SAAS,EAC1B,MAAK,2BACH,UACA,SAAS,YACT,KAAK,IAAI,GAAG,eAAe,EAC3B,KAAK,IAAI,GAAG,eAAe,EAC3B,eAAe,QACf,UACA,eACD;WAEE;KAEL,MAAM,WAAW,KAAK,IAAI,GAAG,SAAS,UAAU;KAChD,MAAM,SAAS,KAAK,IAClB,MAAM,SAAS,GACf,OAAO,SAAS,SAAS,aAAuB,GAC3C,SAAS,eACV,SACL;AAED,SAAI,OAAO,SAAS,SAAS,IAAI,UAAU,UAAU;MACnD,MAAM,cAAc,SAAS,WAAW;AACxC,WAAK,IAAI,MAAM,QAAQ,OAAO,UAAU,OAAO;AAC7C,WAAI,MAAM,KAAK,OAAO,WAAW,SAAS,OAAQ;AAClD,kBAAW,WAAW,IAAI;;AAE5B,YAAM,YAAY,KAAK,IAAI,GAAG,MAAM,YAAY,YAAY;AAI5D,WAAK,2BACH,UACA,SAAS,YACT,UACA,QACA,aACA,SACD;;;;AAMP,OAAI,KAAK,OAAO,WAAW,MAAM,OAC/B,KAAI;AACF,SAAK,MAAM,QAAQ,MACjB,UAAS,YAAY,OAAO,KAAK;WAE7B;WAIH,OAAO;AACd,WAAQ,KAAK,8BAA8B,MAAM;;;;;;CAOrD,AAAQ,mBACN,UACA,OACkB;EAClB,MAAM,WAAW,KAAK,OAAO;AAE7B,MAAI,CAAC,SAGH,QADkB,SAAS,OAAO,SAAS,OAAO,SAAS,MACvC;AAItB,OAAK,MAAM,SAAS,SAAS,OAC3B,KAAI,MAAM,YAAY,SACpB,QAAO;AAIX,SAAO;;;;;CAMT,uBAAuB,OAA0B;AAE/C,SAAO,MAAM;;;;;CAMf,AAAQ,oBAAoB,UAA8B;AAExD,MAAI,SAAS,oBAAoB;AAC/B,OACE,KAAK,OAAO,eACZ,OAAO,uBAAuB,YAE9B,oBAAmB,SAAS,mBAAwC;OAEpE,cAAa,SAAS,mBAAmB;AAE3C,YAAS,qBAAqB;;EAGhC,MAAM,uBAAuB;AAC3B,QAAK,mBAAmB,SAAS;AACjC,YAAS,qBAAqB;;AAGhC,MAAI,KAAK,OAAO,eAAe,OAAO,wBAAwB,YAC5D,UAAS,qBAAqB,oBAAoB,eAAe;OAC5D;GACL,MAAM,QAAQ,KAAK,OAAO,oBAAoB;AAC9C,YAAS,qBAAqB,WAAW,gBAAgB,MAAM;;;;;;CAOnE,AAAO,aAAa,UAA8B;AAChD,OAAK,mBAAmB,UAAU,KAAK;;;;;CAMzC,AAAQ,mBAAmB,UAAwB,aAAa,OAAa;EAC3E,MAAM,mBAAmB,KAAK,KAAK;EAGnC,MAAM,mBAAmB,MAAM,KAAK,SAAS,UAAU,SAAS,CAAC,CAC9D,QAAQ,GAAG,cAAc,aAAa,EAAE,CACxC,KAAK,CAAC,eAAe,UAAU;AAElC,MAAI,iBAAiB,WAAW,EAAG;EAGnC,MAAM,aAAa,iBAAiB,KAAK,cAAc;AAErD,UAAO;IACL;IACA,UAHe,SAAS,MAAM,IAAI,UAAU;IAI7C;IACD;AAEF,MAAI,WAAW,WAAW,EAAG;EAG7B,IAAI,WAAW;AACf,MAAI,CAAC,YAAY;GACf,MAAM,QAAQ,KAAK,OAAO,yBAAyB;GACnD,MAAM,QAAQ,KAAK,IACjB,GACA,KAAK,MAAM,WAAW,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,CAAC,CAAC,CAChE;AACD,cAAW,WAAW,MAAM,GAAG,MAAM;;EAGvC,IAAI,iBAAiB;EACrB,IAAI,eAAe;EACnB,IAAI,oBAAoB;EAGxB,MAAM,+BAAe,IAAI,KAGtB;AAGH,OAAK,MAAM,EAAE,WAAW,cAAc,UAAU;GAC9C,MAAM,aAAa,SAAS;AAG5B,OAAI,KAAK,OAAO,WAAW,MAAM,QAAQ,SAAS,QAAQ,EAAE;IAC1D,MAAM,UAAU,SAAS,QAAQ,QAC9B,OAAO,QAAQ,QAAQ,IAAI,QAC5B,EACD;AACD,oBAAgB;AAChB,yBAAqB,SAAS,QAAQ;;AAGxC,OAAI,CAAC,aAAa,IAAI,WAAW,CAC/B,cAAa,IAAI,YAAY,EAAE,CAAC;AAElC,gBAAa,IAAI,WAAW,CAAE,KAAK;IAAE;IAAW;IAAU,CAAC;;AAI7D,OAAK,MAAM,CAAC,aAAa,iBAAiB,cAAc;AAEtD,gBAAa,MAAM,GAAG,MAAM,EAAE,SAAS,YAAY,EAAE,SAAS,UAAU;AAExE,QAAK,MAAM,EAAE,WAAW,cAAc,cAAc;AAGlD,SADwB,SAAS,UAAU,IAAI,UAAU,IAAI,KACvC,EAEpB;AAMF,QADoB,SAAS,MAAM,IAAI,UAAU,KAC7B,SAElB;IAIF,MAAM,YAAY,SAAS,OAAO,SAAS;AAC3C,QAAI,CAAC,aAAa,CAAC,UAAU,MAE3B;IAIF,MAAM,aAAa,UAAU,MAAM;AACnC,QAAI,CAAC,WAEH;IAIF,MAAM,eAAe,WAAW,SAAS,SAAS;IAClD,MAAM,WAAW,SAAS;IAC1B,MAAM,SAAS,SAAS,gBAAgB,SAAS;AAEjD,QAAI,WAAW,KAAK,SAAS,gBAAgB,WAAW,OAEtD;AAIF,SAAK,WAAW,UAAU,SAAS;AACnC,aAAS,MAAM,OAAO,UAAU;AAChC,aAAS,UAAU,OAAO,UAAU;IAGpC,MAAM,eAAyB,EAAE;AACjC,SAAK,MAAM,CACT,KACA,oBACG,SAAS,oBAAoB,SAAS,CACzC,KAAI,oBAAoB,UACtB,cAAa,KAAK,IAAI;AAG1B,SAAK,MAAM,OAAO,aAChB,UAAS,oBAAoB,OAAO,IAAI;AAE1C;;;AAKJ,MAAI,SAAS,SAAS;AACpB,YAAS,QAAQ;AACjB,YAAS,QAAQ,mBAAmB;AAGpC,YAAS,QAAQ,eAAe,KAAK;IACnC,WAAW;IACX,gBAAgB;IAChB,SAAS;IACT,cAAc;IACf,CAAC;;;;;;CAON,kBAAkB,UAAgC;AAChD,SAAO,SAAS,OAAO,QACpB,OAAO,UAAU,QAAQ,MAAM,YAAY,MAAM,MAAM,QACxD,EACD;;;;;CAMH,WAAW,UAAgC;EACzC,MAAM,YAAsB,EAAE;AAE9B,OAAK,MAAM,SAAS,SAAS,OAC3B,KAAI;GACF,MAAM,eAAe,MAAM;AAC3B,OAAI,aAAa,YACf,WAAU,KAAK,aAAa,YAAY;YAC/B,aAAa,OAAO;IAC7B,MAAM,QAAQ,MAAM,KAAK,aAAa,MAAM,SAAS;AACrD,cAAU,KAAK,MAAM,KAAK,SAAS,KAAK,QAAQ,CAAC,KAAK,KAAK,CAAC;;WAEvD,OAAO;AACd,WAAQ,KAAK,kCAAkC,MAAM;;AAIzD,SAAO,UAAU,KAAK,KAAK;;;;;CAM7B,WAAW,UAA6C;AACtD,MAAI,CAAC,SAAS,QAAS,QAAO;EAG9B,MAAM,mBAAmB,MAAM,KAAK,SAAS,UAAU,QAAQ,CAAC,CAAC,QAC9D,UAAU,UAAU,EACtB,CAAC;AAEF,SAAO;GACL,GAAG,SAAS;GACZ,YAAY;GACb;;;;;CAMH,aAAa,UAA8B;AACzC,MAAI,SAAS,QACX,UAAS,UAAU;GACjB,MAAM;GACN,QAAQ;GACR,cAAc;GACd,iBAAiB;GACjB,aAAa;GACb,iBAAiB;GACjB,gBAAgB,EAAE;GAClB,WAAW,KAAK,KAAK;GACtB;;;;;;;CASL,WAAW,OAGT;EACA,MAAM,QAAkB,EAAE;EAC1B,MAAM,kBAA4B,EAAE;AAEpC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,EAAE;AAEhD,OAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,KAAK,GAAG,IAAI,KAAK,MAAM,MAAM,CAAC,IAAI;AACxC,oBAAgB,KAAK,MAAM,MAAM,CAAC;AAClC;;GAIF,MAAM,WAAY,SAAS,EAAE;GAG7B,MAAM,aAAa,OAAO,KAAK,SAAS,CAAC,MAAM;GAC/C,MAAM,eAA+B,EAAE;GACvC,MAAM,+BAAe,IAAI,KAAmB;AAE5C,cAAW,SAAS,cAAc;IAChC,IAAI,WAAW,kBAAkB;AACjC,QAAI,CAAC,SAEH,YAAW,kBAAkB,aAAa,CAAC,YAAY,UAAU,CAAC;AAGpE,aAAS,SAAS,YAAY;AAC5B,SAAI,CAAC,aAAa,IAAI,QAAQ,EAAE;AAC9B,mBAAa,IAAI,QAAQ;AACzB,mBAAa,KAAK,QAAQ;;MAE5B;KACF;GAIF,MAAM,mBAAsD,EAAE;AAE9D,gBAAa,SAAS,YAAY;IAQhC,MAAM,SAAS,QAPA,QAAQ,eACI,QAA4B,KAAK,SAAS;KACnE,MAAM,IAAI,SAAS;AACnB,SAAI,MAAM,OAAW,KAAI,QAAQ;AACjC,YAAO;OACN,EAAE,CAAC,CAE6B;AACnC,QAAI,CAAC,OAAQ;AAGb,KADgB,MAAM,QAAQ,OAAO,GAAG,SAAS,CAAC,OAAO,EACjD,SAAS,WAAW;AAC1B,SAAI,CAAC,UAAU,OAAO,WAAW,SAAU;KAC3C,MAAM,EAAE,GAAG,IAAI,GAAG,UAAU;AAE5B,YAAO,QAAQ,MAAM,CAAC,SAAS,CAAC,MAAM,SAAS;AAC7C,UAAI,OAAO,QAAQ,QAAQ,GAAI;AAE/B,UAAI,MAAM,QAAQ,IAAI,CAEpB,KAAI,SAAS,MAAM;AACjB,WAAI,KAAK,QAAQ,MAAM,GACrB,kBAAiB,KAAK;QAAE;QAAM,OAAO,OAAO,EAAE;QAAE,CAAC;QAEnD;UAEF,kBAAiB,KAAK;OAAE;OAAM,OAAO,OAAO,IAAI;OAAE,CAAC;OAErD;MACF;KACF;GAGF,MAAM,eAAe,iBAClB,KAAK,MAAM,GAAG,EAAE,KAAK,IAAI,EAAE,QAAQ,CACnC,KAAK,KAAK;AAEb,SAAM,KAAK,GAAG,IAAI,KAAK,aAAa,MAAM,CAAC,IAAI;AAC/C,mBAAgB,KAAK,aAAa;;AAGpC,SAAO;GAAE,KAAK,MAAM,KAAK,IAAI;GAAE,cAAc,gBAAgB,KAAK,KAAK;GAAE;;;;;;CAO3E,gBACE,UACA,OACA,MACA,MACsD;EACtD,IAAI,cAAc,KAAK,mBAAmB,UAAU,KAAK;AACzD,MAAI,CAAC,YACH,eAAc,KAAK,YAAY,UAAU,KAAK;EAGhD,MAAM,YAAY,KAAK,uBAAuB,YAAY;EAC1D,MAAM,aAAa,SAAS,OAAO,QAAQ,YAAY;AAEvD,MAAI;GACF,MAAM,EAAE,KAAK,UAAU,iBAAiB,KAAK,WAAW,MAAM;GAC9D,MAAM,WAAW,cAAc,KAAK,KAAK,SAAS;GAElD,MAAM,eAAe,YAAY;GACjC,MAAM,aAAa,aAAa;AAEhC,OAAI,cAAc,CAAC,KAAK,OAAO,oBAAoB;IACjD,MAAM,YAAY,KAAK,IACrB,KAAK,IAAI,GAAG,UAAU,EACtB,WAAW,SAAS,OACrB;AACD,eAAW,WAAW,UAAU,UAAU;SAE1C,cAAa,eACV,aAAa,eAAe,MAAM,OAAO;AAG9C,eAAY;AAEZ,UAAO;IACL,MAAM;KACJ;KACA;KACA;KACA,SAAS,KAAK,OAAO,UAAU,WAAW;KAC3C;IACD;IACD;WACM,OAAO;AACd,WAAQ,KAAK,+BAA+B,MAAM;AAClD,UAAO;;;;;;CAOX,gBAAgB,UAAwB,MAA2B;EACjE,MAAM,QAAQ,SAAS,OAAO,KAAK;AACnC,MAAI,CAAC,MAAO;AAEZ,MAAI;GAEF,MAAM,aADe,MAAM,MACK;AAEhC,OAAI,YACF;QACE,KAAK,aAAa,KAClB,KAAK,YAAY,WAAW,SAAS,QACrC;AACA,gBAAW,WAAW,KAAK,UAAU;AACrC,WAAM,YAAY,KAAK,IAAI,GAAG,MAAM,YAAY,EAAE;AAKlD,UAAK,2BACH,UACA,KAAK,YACL,KAAK,WACL,KAAK,WACL,GAEA;MACE,WAAW;MACX,WAAW,KAAK;MAChB,YAAY,KAAK;MAClB,EACD,CAAC,KAAK,UAAU,CACjB;;;WAGE,OAAO;AACd,WAAQ,KAAK,+BAA+B,MAAM;;;;;;CAOtD,AAAO,mBAAmB,UAA8B;AAEtD,MAAI,SAAS,qBAAqB;AAChC,gBAAa,SAAS,oBAAoB;AAC1C,YAAS,sBAAsB;;AAIjC,WAAS,sBAAsB,iBAAiB;AAC9C,QAAK,oBAAoB,SAAS;AAClC,YAAS,sBAAsB;KAC9B,EAAE;;;;;CAMP,AAAQ,oBAAoB,UAA8B;AAOxD,MALyB,MAAM,KAAK,SAAS,UAAU,QAAQ,CAAC,CAAC,QAC9D,UAAU,UAAU,EACtB,CAAC,WACgB,KAAK,OAAO,yBAAyB,KAGrD,MAAK,oBAAoB,SAAS;;;;;CAOtC,QAAQ,MAAmC;EACzC,MAAM,WAAW,KAAK,eAAe,IAAI,KAAK;AAE9C,MAAI,CAAC,SACH;AAIF,MAAI,SAAS,oBAAoB;AAC/B,OACE,KAAK,OAAO,eACZ,OAAO,uBAAuB,YAE9B,oBAAmB,SAAS,mBAAwC;OAEpE,cAAa,SAAS,mBAAmB;AAE3C,YAAS,qBAAqB;;AAIhC,MAAI,SAAS,qBAAqB;AAChC,gBAAa,SAAS,oBAAoB;AAC1C,YAAS,sBAAsB;;AAIjC,OAAK,MAAM,SAAS,SAAS,OAC3B,KAAI;GAEF,MAAM,eAAe,MAAM;AAC3B,OAAI,aAAa,WACf,cAAa,WAAW,YAAY,aAAa;WAE5C,OAAO;AACd,WAAQ,KAAK,4BAA4B,MAAM;;AAKnD,OAAK,eAAe,OAAO,KAAK;EAGhC,MAAM,kBAAkB,KAAK,iBAAiB,IAAI,KAAK;AACvD,MAAI,iBAAiB,WACnB,iBAAgB,WAAW,YAAY,gBAAgB;AAEzD,OAAK,iBAAiB,OAAO,KAAK;AAClC,OAAK,aAAa,OAAO,KAAK;;;;;;CAOhC,AAAQ,2BACN,MACkB;EAClB,IAAI,eAAe,KAAK,iBAAiB,IAAI,KAAK;AAElD,MAAI,CAAC,cAAc;AACjB,kBACG,KAAkB,gBAAgB,QAAQ,IAC3C,SAAS,cAAc,QAAQ;AAEjC,OAAI,KAAK,OAAO,MACd,cAAa,QAAQ,KAAK,OAAO;AAGnC,gBAAa,aAAa,kBAAkB,GAAG;AAG/C,OAAI,UAAU,QAAQ,KAAK,KACzB,MAAK,KAAK,YAAY,aAAa;YAC1B,iBAAiB,KAC1B,MAAK,YAAY,aAAa;OAE9B,UAAS,KAAK,YAAY,aAAa;AAGzC,QAAK,iBAAiB,IAAI,MAAM,aAAa;AAC7C,QAAK,aAAa,IAAI,sBAAM,IAAI,KAAK,CAAC;;AAGxC,SAAO;;;;;;CAOT,aAAa,KAAa,MAA2C;AACnE,MAAI,CAAC,IAAI,MAAM,CACb,QAAO,EACL,eAAe,IAGhB;EAGH,MAAM,eAAe,KAAK,2BAA2B,KAAK;EAC1D,MAAM,YAAY,KAAK,aAAa,IAAI,KAAK;EAG7C,MAAM,KAAK,OAAO,KAAK;EAGvB,MAAM,iBAAiB,aAAa,eAAe;EACnD,MAAM,cAAc,eAAe;EACnC,MAAM,kBAAkB,iBAAiB,OAAO,MAAM;EACtD,MAAM,YAAY,cAAc,eAAe;AAG/C,eAAa,cAAc,iBAAiB;EAG5C,MAAM,OAAmB;GACvB;GACA;GACA;GACA;GACD;AACD,YAAU,IAAI,IAAI,KAAK;AAEvB,SAAO,EACL,eAAe;AACb,QAAK,cAAc,IAAI,KAAK;KAE/B;;;;;CAMH,AAAQ,cAAc,IAAY,MAAmC;EACnE,MAAM,eAAe,KAAK,iBAAiB,IAAI,KAAK;EACpD,MAAM,YAAY,KAAK,aAAa,IAAI,KAAK;AAE7C,MAAI,CAAC,gBAAgB,CAAC,UACpB;AAIF,MAAI,CADS,UAAU,IAAI,GAAG,CAE5B;AAIF,YAAU,OAAO,GAAG;EAIpB,MAAM,kBAAkB,MAAM,KAAK,UAAU,QAAQ,CAAC;AAEtD,MAAI,gBAAgB,WAAW,EAC7B,cAAa,cAAc;OACtB;AAGL,mBAAgB,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,YAAY;AAE7D,gBAAa,cADM,gBAAgB,KAAK,UAAU,MAAM,IAAI,CAAC,KAAK,KAAK;GAIvE,IAAI,SAAS;AACb,QAAK,MAAM,SAAS,iBAAiB;AACnC,UAAM,cAAc;AACpB,UAAM,YAAY,SAAS,MAAM,IAAI;AACrC,aAAS,MAAM,YAAY;;;;;;;CAQjC,cAAc,MAAqC;AAEjD,SADqB,KAAK,iBAAiB,IAAI,KAAK,EAC/B,eAAe"}
|
package/dist/pipeline/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ interface StyleResult {
|
|
|
12
12
|
atRules?: string[];
|
|
13
13
|
needsClassName?: boolean;
|
|
14
14
|
rootPrefix?: string;
|
|
15
|
+
/** When true, declarations are wrapped in @starting-style { ... } inside the selector rule */
|
|
16
|
+
startingStyle?: boolean;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Matches the old RenderResult interface for backward compatibility
|