@pyreon/styler 0.12.0 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -127,6 +127,7 @@ declare class StyleSheet {
127
127
  private isSSR;
128
128
  private maxCacheSize;
129
129
  private layer;
130
+ private supportsLayer;
130
131
  constructor(options?: StyleSheetOptions);
131
132
  private mount;
132
133
  /** Extract className from a selector like ".pyr-abc" or ".pyr-abc.pyr-abc" → "pyr-abc" */
@@ -149,10 +150,13 @@ declare class StyleSheet {
149
150
  * Deduplicates: same CSS text always produces the same class name and
150
151
  * the rules are only injected once.
151
152
  *
152
- * When `boost` is true, the selector is doubled (`.pyr-abc.pyr-abc`)
153
- * to raise specificity from (0,1,0) to (0,2,0).
153
+ * @param cssText - CSS declarations to insert
154
+ * @param _unused - Reserved for backward compatibility (was `boost`)
155
+ * @param insertLayer - CSS @layer to wrap this rule in (e.g. 'rocketstyle').
156
+ * Used by rocketstyle to ensure wrapper styles override inner component styles
157
+ * via @layer order (base < rocketstyle) instead of specificity hacks.
154
158
  */
155
- insert(cssText: string, boost?: boolean): string;
159
+ insert(cssText: string, _unused?: boolean, insertLayer?: string): string;
156
160
  /** Insert a @keyframes rule. Deduplicates by animation name. */
157
161
  insertKeyframes(name: string, body: string): void;
158
162
  /**
@@ -178,7 +182,7 @@ declare class StyleSheet {
178
182
  /**
179
183
  * Compute className and full CSS rule text without injecting.
180
184
  */
181
- prepare(cssText: string, boost?: boolean): {
185
+ prepare(cssText: string): {
182
186
  className: string;
183
187
  rules: string;
184
188
  };
@@ -201,11 +205,11 @@ interface StyledOptions {
201
205
  /** Custom prop filter. Return true to forward the prop to the DOM element. */
202
206
  shouldForwardProp?: (prop: string) => boolean;
203
207
  /**
204
- * Double the class selector to raise specificity from (0,1,0) to (0,2,0).
205
- * Ensures this component's styles override inner library components
206
- * regardless of CSS source order.
208
+ * CSS @layer name. Rules are wrapped in `@layer <name> { ... }`.
209
+ * Used by rocketstyle to ensure wrapper styles override inner component
210
+ * styles via layer order (base < rocketstyle) instead of specificity hacks.
207
211
  */
208
- boost?: boolean;
212
+ layer?: string;
209
213
  }
210
214
  type TagTemplateFn = (strings: TemplateStringsArray, ...values: Interpolation[]) => ComponentFn;
211
215
  type HtmlTags = 'a' | 'abbr' | 'address' | 'article' | 'aside' | 'audio' | 'b' | 'blockquote' | 'body' | 'br' | 'button' | 'canvas' | 'caption' | 'code' | 'col' | 'colgroup' | 'dd' | 'details' | 'div' | 'dl' | 'dt' | 'em' | 'fieldset' | 'figcaption' | 'figure' | 'footer' | 'form' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'head' | 'header' | 'hr' | 'html' | 'i' | 'iframe' | 'img' | 'input' | 'label' | 'legend' | 'li' | 'link' | 'main' | 'mark' | 'menu' | 'meta' | 'nav' | 'ol' | 'optgroup' | 'option' | 'output' | 'p' | 'picture' | 'pre' | 'progress' | 'q' | 'section' | 'select' | 'small' | 'source' | 'span' | 'strong' | 'style' | 'sub' | 'summary' | 'sup' | 'svg' | 'table' | 'tbody' | 'td' | 'template' | 'textarea' | 'tfoot' | 'th' | 'thead' | 'time' | 'tr' | 'u' | 'ul' | 'video';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/ThemeProvider.ts","../../src/resolve.ts","../../src/css.ts","../../src/forward.ts","../../src/globalStyle.ts","../../src/hash.ts","../../src/keyframes.ts","../../src/shared.ts","../../src/sheet.ts","../../src/styled.tsx","../../src/useCSS.ts"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -423,6 +423,7 @@ var StyleSheet = class {
423
423
  isSSR;
424
424
  maxCacheSize;
425
425
  layer;
426
+ supportsLayer = false;
426
427
  constructor(options = {}) {
427
428
  this.maxCacheSize = options.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE;
428
429
  this.layer = options.layer;
@@ -440,8 +441,9 @@ var StyleSheet = class {
440
441
  document.head.appendChild(el);
441
442
  this.sheet = el.sheet ?? null;
442
443
  }
443
- if (this.layer && this.sheet) try {
444
- this.sheet.insertRule(`@layer ${this.layer};`, 0);
444
+ if (this.sheet) try {
445
+ this.sheet.insertRule("@layer base, rocketstyle;", 0);
446
+ this.supportsLayer = true;
445
447
  } catch {}
446
448
  }
447
449
  /** Extract className from a selector like ".pyr-abc" or ".pyr-abc.pyr-abc" → "pyr-abc" */
@@ -542,11 +544,14 @@ var StyleSheet = class {
542
544
  * Deduplicates: same CSS text always produces the same class name and
543
545
  * the rules are only injected once.
544
546
  *
545
- * When `boost` is true, the selector is doubled (`.pyr-abc.pyr-abc`)
546
- * to raise specificity from (0,1,0) to (0,2,0).
547
+ * @param cssText - CSS declarations to insert
548
+ * @param _unused - Reserved for backward compatibility (was `boost`)
549
+ * @param insertLayer - CSS @layer to wrap this rule in (e.g. 'rocketstyle').
550
+ * Used by rocketstyle to ensure wrapper styles override inner component styles
551
+ * via @layer order (base < rocketstyle) instead of specificity hacks.
547
552
  */
548
- insert(cssText, boost = false) {
549
- const icKey = boost ? `${cssText}\0` : cssText;
553
+ insert(cssText, _unused = false, insertLayer) {
554
+ const icKey = insertLayer ? `${cssText}\0L:${insertLayer}` : cssText;
550
555
  const icHit = this.insertCache.get(icKey);
551
556
  if (icHit) return icHit;
552
557
  const className = `${PREFIX}-${hash(cssText)}`;
@@ -556,12 +561,13 @@ var StyleSheet = class {
556
561
  }
557
562
  this.evictIfNeeded();
558
563
  this.cache.set(className, className);
559
- const selector = boost ? `.${className}.${className}` : `.${className}`;
564
+ const selector = `.${className}`;
560
565
  const { base, atRules } = this.splitAtRules(cssText, selector);
561
566
  const rules = [];
562
567
  if (base) rules.push(`${selector}{${base}}`);
563
568
  rules.push(...atRules);
564
- const finalRules = this.layer ? rules.map((r) => `@layer ${this.layer}{${r}}`) : rules;
569
+ const layerName = this.isSSR || this.supportsLayer ? insertLayer ?? this.layer : void 0;
570
+ const finalRules = layerName ? rules.map((r) => `@layer ${layerName}{${r}}`) : rules;
565
571
  if (this.isSSR) for (const rule of finalRules) this.ssrBuffer.push(rule);
566
572
  else if (this.sheet) for (const rule of finalRules) try {
567
573
  this.sheet.insertRule(rule, this.sheet.cssRules.length);
@@ -624,11 +630,13 @@ var StyleSheet = class {
624
630
  }
625
631
  /** Returns collected CSS for SSR as a complete `<style>` tag string. */
626
632
  getStyleTag() {
627
- return `<style ${ATTR}="">${this.ssrBuffer.join("").replace(/<\/style/gi, "<\\/style")}</style>`;
633
+ if (this.ssrBuffer.length === 0) return `<style ${ATTR}=""></style>`;
634
+ return `<style ${ATTR}="">${((this.layer ? "@layer base, rocketstyle;" : "") + this.ssrBuffer.join("")).replace(/<\/style/gi, "<\\/style")}</style>`;
628
635
  }
629
636
  /** Returns collected CSS rules as a raw string (useful for streaming SSR). */
630
637
  getStyles() {
631
- return this.ssrBuffer.join("");
638
+ if (this.ssrBuffer.length === 0) return "";
639
+ return (this.layer ? "@layer base, rocketstyle;" : "") + this.ssrBuffer.join("");
632
640
  }
633
641
  /** Reset SSR buffer and cache (call between server requests). */
634
642
  reset() {
@@ -656,9 +664,9 @@ var StyleSheet = class {
656
664
  /**
657
665
  * Compute className and full CSS rule text without injecting.
658
666
  */
659
- prepare(cssText, boost = false) {
667
+ prepare(cssText) {
660
668
  const className = `${PREFIX}-${hash(cssText)}`;
661
- const selector = boost ? `.${className}.${className}` : `.${className}`;
669
+ const selector = `.${className}`;
662
670
  const { base, atRules } = this.splitAtRules(cssText, selector);
663
671
  const allRules = [];
664
672
  if (base) allRules.push(`${selector}{${base}}`);
@@ -678,7 +686,7 @@ var StyleSheet = class {
678
686
  }
679
687
  };
680
688
  /** Default singleton sheet for client-side use. */
681
- const sheet = new StyleSheet();
689
+ const sheet = new StyleSheet({ layer: "base" });
682
690
  /**
683
691
  * Factory for creating isolated StyleSheet instances.
684
692
  * Use in SSR to get per-request isolation.
@@ -774,10 +782,10 @@ const createStyledComponent = (tag, strings, values, options) => {
774
782
  }
775
783
  const hasDynamicValues = values.length > 0 && values.some(isDynamic);
776
784
  const customFilter = options ? options.shouldForwardProp : void 0;
777
- const boost = options ? options.boost ?? false : false;
785
+ const insertLayer = options?.layer;
778
786
  if (!hasDynamicValues) {
779
787
  const cssText = normalizeCSS(values.length === 0 ? strings[0] : resolve(strings, values, {}));
780
- const staticClassName = cssText.length > 0 ? sheet.insert(cssText, boost) : "";
788
+ const staticClassName = cssText.length > 0 ? sheet.insert(cssText, false, insertLayer) : "";
781
789
  const StaticStyled = (rawProps) => {
782
790
  const finalTag = rawProps.as || tag;
783
791
  return h(finalTag, buildProps(rawProps, staticClassName, typeof finalTag === "string", customFilter), ...Array.isArray(rawProps.children) ? rawProps.children : rawProps.children != null ? [rawProps.children] : []);
@@ -810,7 +818,7 @@ const createStyledComponent = (tag, strings, values, options) => {
810
818
  ...isReactiveState ? { $rocketstate: resolvedState } : {},
811
819
  theme
812
820
  }));
813
- const initialClassName = cssText.length > 0 ? sheet.insert(cssText, boost) : "";
821
+ const initialClassName = cssText.length > 0 ? sheet.insert(cssText, false, insertLayer) : "";
814
822
  const finalTag = rawProps.as || tag;
815
823
  const isDOM = typeof finalTag === "string";
816
824
  let el = null;
@@ -837,7 +845,7 @@ const createStyledComponent = (tag, strings, values, options) => {
837
845
  $rocketstate: newState,
838
846
  theme
839
847
  }));
840
- const newClass = newCss.length > 0 ? sheet.insert(newCss, boost) : "";
848
+ const newClass = newCss.length > 0 ? sheet.insert(newCss, false, insertLayer) : "";
841
849
  if (el && newClass !== currentClassName) {
842
850
  if (currentClassName) el.classList.remove(currentClassName);
843
851
  if (newClass) el.classList.add(newClass);
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/resolve.ts","../src/css.ts","../src/forward.ts","../src/shared.ts","../src/hash.ts","../src/sheet.ts","../src/ThemeProvider.ts","../src/globalStyle.ts","../src/keyframes.ts","../src/styled.tsx","../src/useCSS.ts"],"sourcesContent":["/**\n * Interpolation resolver: converts tagged template strings + values into a\n * final CSS string. Handles nested CSSResults, arrays, functions, and\n * primitive values.\n */\n\nimport type { DefaultTheme } from './ThemeProvider'\n\nexport type Interpolation =\n | string\n | number\n | boolean\n | null\n | undefined\n | CSSResult\n | Interpolation[]\n | ((props: { theme?: DefaultTheme & Record<string, any>; [key: string]: any }) => Interpolation)\n\n/**\n * Lazy representation of a `css` tagged template. Stores the raw template\n * strings and interpolation values without resolving them. Resolution is\n * deferred until a styled component renders (or until explicitly resolved).\n */\nexport class CSSResult {\n constructor(\n readonly strings: TemplateStringsArray,\n readonly values: Interpolation[],\n ) {}\n\n /** Resolve with empty props — useful for static templates, testing, and debugging. */\n toString(): string {\n return resolve(this.strings, this.values, {})\n }\n}\n\n/** Resolve a tagged template's strings + values into a final CSS string. */\nexport const resolve = (\n strings: TemplateStringsArray,\n values: Interpolation[],\n props: Record<string, any>,\n): string => {\n // Tagged templates guarantee strings.length === values.length + 1,\n // so strings[0] and strings[i+1] are always defined — no ?? needed.\n let result = strings[0] as string\n for (let i = 0; i < values.length; i++) {\n const v = values[i]\n const s = strings[i + 1] as string\n // Inline the most common value types to avoid function call overhead.\n if (typeof v === 'function') {\n const r = v(props)\n result +=\n (typeof r === 'string'\n ? r\n : r == null || r === false || r === true\n ? ''\n : resolveValue(r as Interpolation, props)) + s\n } else if (v == null || v === false || v === true) {\n result += s\n } else if (typeof v === 'string') {\n result += v + s\n } else if (typeof v === 'number') {\n result += v + s\n } else {\n result += resolveValue(v, props) + s\n }\n }\n return result\n}\n\n/**\n * Normalize resolved CSS text for strict `insertRule` compatibility.\n *\n * Single-pass scanner that handles all cleanup in one traversal:\n * - Strips block comments and line comments (preserves :// in URLs)\n * - Collapses whitespace to single spaces\n * - Removes redundant semicolons\n * - Trims leading/trailing whitespace\n */\nconst normCache = new Map<string, string>()\n/** Clear the normalizeCSS cache (called during HMR cleanup). */\nexport const clearNormCache = () => normCache.clear()\n\nexport const normalizeCSS = (css: string): string => {\n const cached = normCache.get(css)\n if (cached !== undefined) return cached\n\n const len = css.length\n let out = ''\n let space = false // pending space to emit before next non-whitespace char\n let last = 0 // charCode of last char written to output (0 = nothing yet)\n\n for (let i = 0; i < len; i++) {\n const c = css.charCodeAt(i)\n\n // /* block comment */\n if (c === 47 /* / */ && css.charCodeAt(i + 1) === 42 /* * */) {\n const end = css.indexOf('*/', i + 2)\n i = end === -1 ? len : end + 1\n space = true\n continue\n }\n\n // // line comment (but not :// in URLs)\n if (c === 47 /* / */ && css.charCodeAt(i + 1) === 47 /* / */ && last !== 58 /* : */) {\n const nl = css.indexOf('\\n', i + 2)\n i = nl === -1 ? len : nl\n space = true\n continue\n }\n\n // Whitespace → collapse\n if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12) {\n space = true\n continue\n }\n\n // Semicolon → skip if redundant (after start, {, }, or another ;)\n if (c === 59 /* ; */) {\n if (last === 0 || last === 123 /* { */ || last === 125 /* } */ || last === 59 /* ; */) {\n continue\n }\n space = false\n out += ';'\n last = 59\n continue\n }\n\n // Regular char — emit pending space (but not at start of output)\n if (space && last !== 0) out += ' '\n space = false\n\n out += css[i]\n last = c\n }\n\n // Evict oldest ~10% to prevent memory leaks without cliff-edge drop\n if (normCache.size > 2000) {\n let count = 0\n for (const key of normCache.keys()) {\n if (count >= 200) break\n normCache.delete(key)\n count++\n }\n }\n normCache.set(css, out)\n\n return out\n}\n\nexport const resolveValue = (value: Interpolation, props: Record<string, any>): string => {\n // null, undefined, false, true → empty (enables conditional: ${cond && css`...`})\n if (value == null || value === false || value === true) return ''\n\n // function interpolation → call with props/theme context, resolve result\n if (typeof value === 'function') return resolveValue(value(props) as Interpolation, props)\n\n // nested CSSResult → recursively resolve\n if (value instanceof CSSResult) return resolve(value.strings, value.values, props)\n\n // array of results (e.g. from makeItResponsive's breakpoints.map())\n if (Array.isArray(value)) {\n let arrayResult = ''\n for (let i = 0; i < value.length; i++) {\n arrayResult += resolveValue(value[i], props)\n }\n return arrayResult\n }\n\n return String(value)\n}\n","import { CSSResult, type Interpolation } from './resolve'\n\n/**\n * Tagged template function for CSS. Captures the template strings and\n * interpolation values as a lazy CSSResult — resolution is deferred\n * until a styled component renders.\n *\n * Works as both a tagged template (`css\\`...\\``) and a regular function\n * call (`css(...args)`) since tagged templates are syntactic sugar for\n * function calls with (TemplateStringsArray, ...values).\n */\nexport const css = (strings: TemplateStringsArray, ...values: Interpolation[]): CSSResult =>\n new CSSResult(strings, values)\n","/**\n * HTML prop filtering. Prevents unknown props from being forwarded to DOM\n * elements (which causes warnings). Props starting with `$` are\n * transient (styling-only) and are always filtered out.\n */\n\n// Common HTML attributes, event handlers, and ARIA/data attributes\nconst HTML_PROPS = new Set([\n // Core props\n 'className',\n 'class',\n 'dangerouslySetInnerHTML',\n 'htmlFor',\n 'id',\n 'key',\n 'ref',\n 'style',\n 'tabIndex',\n 'role',\n // Event handlers\n 'onAbort',\n 'onAnimationEnd',\n 'onAnimationIteration',\n 'onAnimationStart',\n 'onBlur',\n 'onChange',\n 'onClick',\n 'onCompositionEnd',\n 'onCompositionStart',\n 'onCompositionUpdate',\n 'onContextMenu',\n 'onCopy',\n 'onCut',\n 'onDoubleClick',\n 'onDrag',\n 'onDragEnd',\n 'onDragEnter',\n 'onDragLeave',\n 'onDragOver',\n 'onDragStart',\n 'onDrop',\n 'onError',\n 'onFocus',\n 'onInput',\n 'onKeyDown',\n 'onKeyPress',\n 'onKeyUp',\n 'onLoad',\n 'onMouseDown',\n 'onMouseEnter',\n 'onMouseLeave',\n 'onMouseMove',\n 'onMouseOut',\n 'onMouseOver',\n 'onMouseUp',\n 'onPaste',\n 'onPointerCancel',\n 'onPointerDown',\n 'onPointerEnter',\n 'onPointerLeave',\n 'onPointerMove',\n 'onPointerOut',\n 'onPointerOver',\n 'onPointerUp',\n 'onScroll',\n 'onSelect',\n 'onSubmit',\n 'onTouchCancel',\n 'onTouchEnd',\n 'onTouchMove',\n 'onTouchStart',\n 'onTransitionEnd',\n 'onWheel',\n // HTML attributes\n 'accept',\n 'acceptCharset',\n 'accessKey',\n 'action',\n 'allow',\n 'allowFullScreen',\n 'alt',\n 'as',\n 'async',\n 'autoCapitalize',\n 'autoComplete',\n 'autoCorrect',\n 'autoFocus',\n 'autoPlay',\n 'capture',\n 'cellPadding',\n 'cellSpacing',\n 'charSet',\n 'checked',\n 'cite',\n 'cols',\n 'colSpan',\n 'content',\n 'contentEditable',\n 'controls',\n 'controlsList',\n 'coords',\n 'crossOrigin',\n 'dateTime',\n 'decoding',\n 'default',\n 'defaultChecked',\n 'defaultValue',\n 'defer',\n 'dir',\n 'disabled',\n 'disablePictureInPicture',\n 'disableRemotePlayback',\n 'download',\n 'draggable',\n 'encType',\n 'enterKeyHint',\n 'fetchPriority',\n 'form',\n 'formAction',\n 'formEncType',\n 'formMethod',\n 'formNoValidate',\n 'formTarget',\n 'frameBorder',\n 'headers',\n 'height',\n 'hidden',\n 'high',\n 'href',\n 'hrefLang',\n 'httpEquiv',\n 'inputMode',\n 'integrity',\n 'is',\n 'label',\n 'lang',\n 'list',\n 'loading',\n 'loop',\n 'low',\n 'max',\n 'maxLength',\n 'media',\n 'method',\n 'min',\n 'minLength',\n 'multiple',\n 'muted',\n 'name',\n 'noModule',\n 'noValidate',\n 'nonce',\n 'open',\n 'optimum',\n 'pattern',\n 'placeholder',\n 'playsInline',\n 'poster',\n 'preload',\n 'readOnly',\n 'referrerPolicy',\n 'rel',\n 'required',\n 'reversed',\n 'rows',\n 'rowSpan',\n 'sandbox',\n 'scope',\n 'scoped',\n 'scrolling',\n 'selected',\n 'shape',\n 'size',\n 'sizes',\n 'slot',\n 'span',\n 'spellCheck',\n 'src',\n 'srcDoc',\n 'srcLang',\n 'srcSet',\n 'start',\n 'step',\n 'summary',\n 'target',\n 'title',\n 'translate',\n 'type',\n 'useMap',\n 'value',\n 'width',\n 'wrap',\n])\n\n/**\n * Filters props for HTML elements. Keeps valid HTML attrs, data-*, aria-*.\n * Rejects unknown props and $-prefixed transient props.\n */\nexport const filterProps = (props: Record<string, unknown>): Record<string, unknown> => {\n const filtered: Record<string, unknown> = {}\n\n for (const key in props) {\n // Skip transient props ($-prefixed) — used for styling-only props\n if (key.charCodeAt(0) === 36) continue // '$'\n\n // Skip `as` prop — handled separately by styled\n if (key === 'as') continue\n\n // Keep data-* and aria-* attributes\n if (key.startsWith('data-') || key.startsWith('aria-')) {\n filtered[key] = props[key]\n continue\n }\n\n // Keep known HTML props\n if (HTML_PROPS.has(key)) {\n filtered[key] = props[key]\n }\n }\n\n return filtered\n}\n\n/**\n * Build final props for a styled component in a single pass.\n * Combines className merging, ref injection, and prop filtering into one\n * allocation and one iteration.\n */\nexport const buildProps = (\n rawProps: Record<string, any>,\n generatedCls: string,\n isDOM: boolean,\n customFilter?: (prop: string) => boolean,\n): Record<string, any> => {\n const result: Record<string, any> = {}\n\n // Merge generated + user className\n const userCls = rawProps.class || rawProps.className\n if (generatedCls) {\n result.class = userCls ? `${generatedCls} ${userCls}` : generatedCls\n } else if (userCls) {\n result.class = userCls\n }\n\n // Component target — forward all props except as/className/class and $-prefixed\n if (!isDOM) {\n for (const key in rawProps) {\n if (key === 'as' || key === 'className' || key === 'class') continue\n if (key.charCodeAt(0) === 36) continue // $-prefixed transient\n result[key] = rawProps[key]\n }\n return result\n }\n\n // DOM element with custom shouldForwardProp\n if (customFilter) {\n for (const key in rawProps) {\n if (key === 'as' || key === 'className' || key === 'class') continue\n if (customFilter(key)) result[key] = rawProps[key]\n }\n return result\n }\n\n // DOM element with default filtering\n for (const key in rawProps) {\n if (key === 'as' || key === 'className' || key === 'class') continue\n if (key.charCodeAt(0) === 36) continue // $-prefixed transient\n if (key.startsWith('data-') || key.startsWith('aria-')) {\n result[key] = rawProps[key]\n continue\n }\n if (HTML_PROPS.has(key)) result[key] = rawProps[key]\n }\n return result\n}\n","/**\n * Shared utilities used across multiple modules.\n */\nimport { CSSResult, type Interpolation } from './resolve'\n\n/** Check if an interpolation value is dynamic (contains functions or nested dynamic CSSResults). */\nexport const isDynamic = (v: Interpolation): boolean => {\n if (typeof v === 'function') return true\n if (Array.isArray(v)) return v.some(isDynamic)\n if (v instanceof CSSResult) return v.values.some(isDynamic)\n return false\n}\n","/**\n * Fast FNV-1a non-cryptographic hash. Returns base-36 string for compact class names.\n *\n * 32-bit hash space → ~4.3 billion unique values. Collision probability is\n * negligible for typical applications (< 10,000 unique CSS rules).\n */\n\n/** FNV-1a offset basis — starting state for streaming hash. */\nexport const HASH_INIT = 2166136261\n\nconst FNV_PRIME = 16777619\n\n/**\n * Feed a string segment into the running hash state.\n * Streaming: hashUpdate(hashUpdate(HASH_INIT, 'ab'), 'cd') === hash('abcd').\n */\nexport const hashUpdate = (init: number, str: string): number => {\n let h = init\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i)\n h = Math.imul(h, FNV_PRIME)\n }\n return h\n}\n\n/** Finalize a hash state into a base-36 class name suffix. */\nexport const hashFinalize = (h: number): string => (h >>> 0).toString(36)\n\n/** Hash a complete string in one shot. Returns base-36 string. */\nexport const hash = (str: string): string => hashFinalize(hashUpdate(HASH_INIT, str))\n","/**\n * StyleSheet manager. Handles CSS rule injection, hash-based deduplication,\n * SSR buffering, client-side hydration, bounded cache, and @layer support.\n *\n * Media queries (@media), @supports, and @container blocks nested inside\n * component CSS are automatically extracted into separate top-level rules.\n */\nimport { hash } from './hash'\nimport { clearNormCache } from './resolve'\n\nconst PREFIX = 'pyr'\nconst ATTR = 'data-pyreon-styler'\nconst DEFAULT_MAX_CACHE_SIZE = 10000\n\nexport interface StyleSheetOptions {\n /** Maximum number of cached rules before eviction (default: 10000). */\n maxCacheSize?: number\n /** CSS @layer name to wrap scoped rules in. */\n layer?: string\n}\n\nexport class StyleSheet {\n private cache = new Map<string, string>()\n private insertCache = new Map<string, string>()\n private sheet: CSSStyleSheet | null = null\n private ssrBuffer: string[] = []\n private isSSR: boolean\n private maxCacheSize: number\n private layer: string | undefined\n private supportsLayer = false\n\n constructor(options: StyleSheetOptions = {}) {\n this.maxCacheSize = options.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE\n this.layer = options.layer\n this.isSSR = typeof document === 'undefined'\n if (!this.isSSR) this.mount()\n }\n\n private mount() {\n // Reuse existing <style> tag from SSR hydration\n const existing = document.querySelector(`style[${ATTR}]`) as HTMLStyleElement | null\n\n if (existing) {\n this.sheet = existing.sheet ?? null\n this.hydrateFromTag(existing)\n } else {\n const el = document.createElement('style')\n el.setAttribute(ATTR, '')\n document.head.appendChild(el)\n this.sheet = el.sheet ?? null\n }\n\n // Inject @layer declarations.\n // 'base' is for plain styled() components, 'rocketstyle' is for\n // rocketstyle wrappers. Layer order ensures rocketstyle overrides base\n // without needing doubled selectors (boost).\n if (this.sheet) {\n try {\n this.sheet.insertRule('@layer base, rocketstyle;', 0)\n this.supportsLayer = true\n } catch {\n // @layer not supported — falls back to source order\n }\n }\n }\n\n /** Extract className from a selector like \".pyr-abc\" or \".pyr-abc.pyr-abc\" → \"pyr-abc\" */\n private extractClassName(selectorText: string): string | null {\n if (selectorText[0] !== '.') return null\n const dotIdx = selectorText.indexOf('.', 1)\n return dotIdx > 0 ? selectorText.slice(1, dotIdx) : selectorText.slice(1)\n }\n\n /** Parse existing rules from SSR-rendered <style> tag into cache. */\n private hydrateFromTag(el: HTMLStyleElement) {\n const sheet = el.sheet\n if (!sheet) return\n\n for (let i = 0; i < sheet.cssRules.length; i++) {\n const rule = sheet.cssRules[i]\n\n if (rule instanceof CSSStyleRule) {\n const className = this.extractClassName(rule.selectorText)\n if (className) this.cache.set(className, className)\n }\n\n // Handle split @media rules that wrap our selectors\n if (typeof CSSMediaRule !== 'undefined' && rule instanceof CSSMediaRule) {\n for (let j = 0; j < rule.cssRules.length; j++) {\n const inner = rule.cssRules[j]\n if (inner instanceof CSSStyleRule) {\n const className = this.extractClassName(inner.selectorText)\n if (className) this.cache.set(className, className)\n }\n }\n }\n }\n }\n\n /** Evict oldest entries when cache exceeds max size. */\n private evictIfNeeded() {\n if (this.cache.size <= this.maxCacheSize) return\n\n // Map iteration order is insertion order — delete oldest 10%\n const toDelete = Math.floor(this.maxCacheSize * 0.1)\n let count = 0\n for (const key of this.cache.keys()) {\n if (count >= toDelete) break\n this.cache.delete(key)\n count++\n }\n }\n\n /**\n * Extract nested at-rules (@media, @supports, @container) from CSS text\n * and wrap their content in the given selector as separate top-level rules.\n */\n private splitAtRules(cssText: string, selector: string): { base: string; atRules: string[] } {\n // Fast path: no at-rules to split\n if (cssText.indexOf('@') === -1) return { base: cssText, atRules: [] }\n\n const atRules: string[] = []\n const baseParts: string[] = []\n let depth = 0\n let atStart = -1\n let lastBase = 0\n\n for (let i = 0; i < cssText.length; i++) {\n const ch = cssText[i]\n\n if (ch === '{') {\n depth++\n } else if (ch === '}') {\n depth--\n if (depth === 0 && atStart >= 0) {\n // End of a tracked at-rule block — extract and wrap with selector\n const openBrace = cssText.indexOf('{', atStart)\n const atPrefix = cssText.slice(atStart, openBrace).trim()\n const innerCSS = cssText.slice(openBrace + 1, i).trim()\n if (innerCSS) {\n atRules.push(`${atPrefix}{${selector}{${innerCSS}}}`)\n }\n atStart = -1\n lastBase = i + 1\n }\n } else if (depth === 0 && ch === '@' && atStart < 0) {\n // Check if this starts a splittable at-rule (not @keyframes, @font-face, etc.)\n const remaining = cssText.slice(i, i + 20)\n if (/^@(?:media|supports|container)\\b/.test(remaining)) {\n // Save any base CSS that precedes this at-rule\n const baseBefore = cssText.slice(lastBase, i).trim()\n if (baseBefore) baseParts.push(baseBefore)\n atStart = i\n }\n }\n }\n\n // Collect remaining base CSS after the last at-rule\n if (lastBase < cssText.length && atStart < 0) {\n const remaining = cssText.slice(lastBase).trim()\n if (remaining) baseParts.push(remaining)\n }\n\n // If no at-rules were found, return original unchanged\n if (atRules.length === 0) return { base: cssText, atRules: [] }\n\n return { base: baseParts.join(' '), atRules }\n }\n\n /**\n * Compute a className from CSS text without injecting (pure function).\n */\n getClassName(cssText: string): string {\n const cached = this.insertCache.get(cssText)\n if (cached) return cached\n const h = hash(cssText)\n return `${PREFIX}-${h}`\n }\n\n /**\n * Insert CSS rules for a component. Returns the class name (deterministic, hash-based).\n * Deduplicates: same CSS text always produces the same class name and\n * the rules are only injected once.\n *\n * @param cssText - CSS declarations to insert\n * @param _unused - Reserved for backward compatibility (was `boost`)\n * @param insertLayer - CSS @layer to wrap this rule in (e.g. 'rocketstyle').\n * Used by rocketstyle to ensure wrapper styles override inner component styles\n * via @layer order (base < rocketstyle) instead of specificity hacks.\n */\n insert(cssText: string, _unused = false, insertLayer?: string): string {\n // Fast path: skip hash computation on repeated insertions of same CSS text\n const icKey = insertLayer ? `${cssText}\\0L:${insertLayer}` : cssText\n const icHit = this.insertCache.get(icKey)\n if (icHit) return icHit\n\n const h = hash(cssText)\n const className = `${PREFIX}-${h}`\n\n if (this.cache.has(className)) {\n this.insertCache.set(icKey, className)\n return className\n }\n\n this.evictIfNeeded()\n this.cache.set(className, className)\n\n const selector = `.${className}`\n\n // Split nested at-rules into separate top-level rules\n const { base, atRules } = this.splitAtRules(cssText, selector)\n\n const rules: string[] = []\n if (base) rules.push(`${selector}{${base}}`)\n rules.push(...atRules)\n\n // Apply @layer wrapping — per-insert layer takes precedence over sheet-level layer.\n // In SSR, always apply layers (output goes to real browsers).\n // In client, skip if @layer isn't supported (e.g. happy-dom in tests).\n const layerName = (this.isSSR || this.supportsLayer) ? (insertLayer ?? this.layer) : undefined\n const finalRules = layerName ? rules.map((r) => `@layer ${layerName}{${r}}`) : rules\n\n if (this.isSSR) {\n for (const rule of finalRules) {\n this.ssrBuffer.push(rule)\n }\n } else if (this.sheet) {\n for (const rule of finalRules) {\n try {\n this.sheet.insertRule(rule, this.sheet.cssRules.length)\n } catch (_e) {\n if (process.env.NODE_ENV !== 'production') {\n // oxlint-disable-next-line no-console\n console.warn('[styler] Failed to insert CSS rule:', rule, _e)\n }\n }\n }\n }\n\n this.insertCache.set(icKey, className)\n return className\n }\n\n /** Insert a @keyframes rule. Deduplicates by animation name. */\n insertKeyframes(name: string, body: string): void {\n if (this.cache.has(name)) return\n\n this.evictIfNeeded()\n this.cache.set(name, name)\n\n const rule = `@keyframes ${name}{${body}}`\n\n if (this.isSSR) {\n this.ssrBuffer.push(rule)\n } else if (this.sheet) {\n try {\n this.sheet.insertRule(rule, this.sheet.cssRules.length)\n } catch (_e) {\n if (process.env.NODE_ENV !== 'production') {\n // silently ignore invalid CSS rules in production\n }\n }\n }\n }\n\n /**\n * Split CSS text into individual top-level rules.\n * CSSStyleSheet.insertRule() only accepts one rule at a time.\n */\n private splitRules(cssText: string): string[] {\n const rules: string[] = []\n let depth = 0\n let start = 0\n\n for (let i = 0; i < cssText.length; i++) {\n const ch = cssText[i]\n if (ch === '{') depth++\n else if (ch === '}') {\n depth--\n if (depth === 0) {\n const rule = cssText.slice(start, i + 1).trim()\n if (rule) rules.push(rule)\n start = i + 1\n }\n }\n }\n\n return rules\n }\n\n /** Insert global CSS rules (no wrapper selector). Deduplicates by hash. */\n insertGlobal(cssText: string): void {\n const h = hash(cssText)\n const key = `global-${h}`\n\n if (this.cache.has(key)) return\n\n this.evictIfNeeded()\n this.cache.set(key, key)\n\n if (this.isSSR) {\n this.ssrBuffer.push(cssText)\n } else if (this.sheet) {\n const rules = this.splitRules(cssText)\n for (const rule of rules) {\n try {\n this.sheet.insertRule(rule, this.sheet.cssRules.length)\n } catch (_e) {\n if (process.env.NODE_ENV !== 'production') {\n // oxlint-disable-next-line no-console\n console.warn('[styler] Failed to insert global CSS rule:', rule, _e)\n }\n }\n }\n }\n }\n\n /** Returns collected CSS for SSR as a complete `<style>` tag string. */\n getStyleTag(): string {\n if (this.ssrBuffer.length === 0) return `<style ${ATTR}=\"\"></style>`\n const layerDecl = this.layer ? '@layer base, rocketstyle;' : ''\n const css = (layerDecl + this.ssrBuffer.join('')).replace(/<\\/style/gi, '<\\\\/style')\n return `<style ${ATTR}=\"\">${css}</style>`\n }\n\n /** Returns collected CSS rules as a raw string (useful for streaming SSR). */\n getStyles(): string {\n if (this.ssrBuffer.length === 0) return ''\n const layerDecl = this.layer ? '@layer base, rocketstyle;' : ''\n return layerDecl + this.ssrBuffer.join('')\n }\n\n /** Reset SSR buffer and cache (call between server requests). */\n reset(): void {\n this.ssrBuffer = []\n this.cache.clear()\n this.insertCache.clear()\n }\n\n /** Clear the dedup cache. Useful for HMR / dev-time reloads. */\n clearCache(): void {\n this.cache.clear()\n this.insertCache.clear()\n clearNormCache()\n }\n\n /**\n * Full cleanup: clear cache and remove all CSS rules from the DOM.\n * Intended for HMR / dev-time reloads where stale styles must be purged.\n */\n clearAll(): void {\n this.cache.clear()\n this.insertCache.clear()\n clearNormCache()\n this.ssrBuffer = []\n if (this.sheet) {\n while (this.sheet.cssRules.length > 0) {\n this.sheet.deleteRule(0)\n }\n }\n }\n\n /**\n * Compute className and full CSS rule text without injecting.\n */\n prepare(cssText: string): { className: string; rules: string } {\n const h = hash(cssText)\n const className = `${PREFIX}-${h}`\n const selector = `.${className}`\n const { base, atRules } = this.splitAtRules(cssText, selector)\n\n const allRules: string[] = []\n if (base) allRules.push(`${selector}{${base}}`)\n allRules.push(...atRules)\n\n const finalRules = this.layer ? allRules.map((r) => `@layer ${this.layer}{${r}}`) : allRules\n\n return { className, rules: finalRules.join('') }\n }\n\n /** Check if a className is already in the cache. O(1) Map lookup. */\n has(className: string): boolean {\n return this.cache.has(className)\n }\n\n /** Current number of cached rules. */\n get cacheSize(): number {\n return this.cache.size\n }\n}\n\n/** Default singleton sheet for client-side use. */\nexport const sheet = new StyleSheet({ layer: 'base' })\n\n/**\n * Factory for creating isolated StyleSheet instances.\n * Use in SSR to get per-request isolation.\n */\nexport const createSheet = (options?: StyleSheetOptions): StyleSheet => new StyleSheet(options)\n","/**\n * Theme context for styled components.\n *\n * Extensible theme interface. Consumers can augment this via module\n * declaration merging for full strict types:\n *\n * declare module '@pyreon/styler' {\n * interface DefaultTheme {\n * colors: { primary: string; secondary: string }\n * spacing: (n: number) => string\n * }\n * }\n */\nimport type { VNode, VNodeChild } from '@pyreon/core'\nimport { createContext, provide, useContext } from '@pyreon/core'\n\nexport interface DefaultTheme {}\n\ntype Theme = DefaultTheme & Record<string, unknown>\n\nexport const ThemeContext = createContext<Theme>({} as Theme)\n\n/** Hook to read the current theme from the nearest ThemeProvider. */\nexport const useTheme = <T extends object = Theme>(): T => useContext(ThemeContext) as T\n\n/**\n * @internal Low-level provider — use `PyreonUI` from `@pyreon/ui-core` instead.\n *\n * Provides a theme object to all nested styled components via Pyreon context.\n *\n * @deprecated Prefer `<PyreonUI theme={theme}>` which provides theme to\n * all three context layers (styler, core, mode) in one component.\n */\nexport function ThemeProvider({\n theme,\n children,\n}: {\n theme: Theme\n children?: VNodeChild\n}): VNode | null {\n provide(ThemeContext, theme)\n return (children ?? null) as VNode | null\n}\n","/**\n * createGlobalStyle() — tagged template function that injects global CSS\n * rules (not scoped to a class name). Returns a component function that\n * injects styles when called and supports dynamic interpolations via\n * props/theme.\n *\n * Usage:\n * const GlobalStyle = createGlobalStyle`\n * body { margin: 0; font-family: ${({ theme }) => theme.font}; }\n * *, *::before, *::after { box-sizing: border-box; }\n * `\n */\nimport type { ComponentFn } from '@pyreon/core'\nimport { type Interpolation, normalizeCSS, resolve } from './resolve'\nimport { isDynamic } from './shared'\nimport { sheet } from './sheet'\nimport { useTheme } from './ThemeProvider'\n\nexport const createGlobalStyle = (\n strings: TemplateStringsArray,\n ...values: Interpolation[]\n): ComponentFn => {\n const hasDynamicValues = values.some(isDynamic)\n\n // STATIC FAST PATH: compute once at creation time\n if (!hasDynamicValues) {\n const cssText = normalizeCSS(resolve(strings, values, {}))\n\n // Inject into sheet immediately\n if (cssText.trim()) sheet.insertGlobal(cssText)\n\n const StaticGlobal: ComponentFn = () => null\n return StaticGlobal\n }\n\n // DYNAMIC PATH: resolve on every render with theme/props\n const DynamicGlobal: ComponentFn = (props: Record<string, any>) => {\n const theme = useTheme()\n const allProps = { ...props, theme }\n const cssText = normalizeCSS(resolve(strings, values, allProps))\n\n if (cssText.trim()) sheet.insertGlobal(cssText)\n\n return null\n }\n\n return DynamicGlobal\n}\n","/**\n * keyframes() tagged template function. Creates a CSS @keyframes rule,\n * injects it into the stylesheet, and returns the generated animation name.\n *\n * Usage:\n * const fadeIn = keyframes`\n * from { opacity: 0; }\n * to { opacity: 1; }\n * `\n * // fadeIn === \"pyr-kf-abc123\" (deterministic, hash-based)\n */\nimport { hash } from './hash'\nimport { type Interpolation, normalizeCSS, resolve } from './resolve'\nimport { sheet } from './sheet'\n\nclass KeyframesResult {\n readonly name: string\n\n constructor(strings: TemplateStringsArray, values: Interpolation[]) {\n const body = normalizeCSS(resolve(strings, values, {}))\n const h = hash(body)\n this.name = `pyr-kf-${h}`\n\n sheet.insertKeyframes(this.name, body)\n }\n\n /** Returns the animation name when used in string context. */\n toString(): string {\n return this.name\n }\n}\n\nexport const keyframes = (\n strings: TemplateStringsArray,\n ...values: Interpolation[]\n): KeyframesResult => new KeyframesResult(strings, values)\n","/**\n * styled() component factory. Creates Pyreon components that inject CSS\n * class names from tagged template literals.\n *\n * Supports:\n * - styled('div')`...` and styled(Component)`...`\n * - styled.div`...` (via Proxy)\n * - `as` prop for polymorphic rendering\n * - $-prefixed transient props (not forwarded to DOM)\n * - Custom shouldForwardProp for per-component prop filtering\n * - Static path optimization (templates with no dynamic interpolations)\n * - Boost specificity via doubled selector\n *\n * CSS nesting (`&` selectors) works natively — the resolver passes CSS\n * through without transformation, so `&:hover`, `&::before`, etc. work\n * as-is in browsers supporting CSS Nesting (all modern browsers).\n */\nimport type { ComponentFn, VNode } from '@pyreon/core'\nimport { h } from '@pyreon/core'\nimport { effect, runUntracked } from '@pyreon/reactivity'\nimport { buildProps } from './forward'\nimport { type Interpolation, normalizeCSS, resolve } from './resolve'\nimport { isDynamic } from './shared'\nimport { sheet } from './sheet'\nimport { useTheme } from './ThemeProvider'\n\ntype Tag = string | ComponentFn<any>\n\nexport interface StyledOptions {\n /** Custom prop filter. Return true to forward the prop to the DOM element. */\n shouldForwardProp?: (prop: string) => boolean\n /**\n * CSS @layer name. Rules are wrapped in `@layer <name> { ... }`.\n * Used by rocketstyle to ensure wrapper styles override inner component\n * styles via layer order (base < rocketstyle) instead of specificity hacks.\n */\n layer?: string\n}\n\nconst getDisplayName = (tag: Tag): string =>\n typeof tag === 'string'\n ? tag\n : (tag as ComponentFn<any> & { displayName?: string }).displayName || tag.name || 'Component'\n\n// Component cache: same template literal + tag + no options → same component.\n// WeakMap on `strings` (TemplateStringsArray is object-identity per source location).\nconst staticComponentCache = new WeakMap<TemplateStringsArray, Map<Tag, ComponentFn>>()\n\n// Single-entry hot cache — just 3 reference comparisons, no Map/WeakMap overhead.\nlet _hotStrings: TemplateStringsArray | null = null\nlet _hotTag: Tag | null = null\nlet _hotComponent: ComponentFn | null = null\n\nconst createStyledComponent = (\n tag: Tag,\n strings: TemplateStringsArray,\n values: Interpolation[],\n options?: StyledOptions,\n): ComponentFn => {\n // Ultra-fast hot cache: 3 reference comparisons → return immediately\n if (values.length === 0 && !options) {\n if (strings === _hotStrings && tag === _hotTag) return _hotComponent as ComponentFn\n\n // WeakMap fallback for alternating patterns\n const tagMap = staticComponentCache.get(strings)\n if (tagMap) {\n const cached = tagMap.get(tag)\n if (cached) {\n _hotStrings = strings\n _hotTag = tag\n _hotComponent = cached\n return cached\n }\n }\n }\n\n // Fast check: no values means no dynamic interpolations — avoids .some() scan\n const hasDynamicValues = values.length > 0 && values.some(isDynamic)\n const customFilter = options ? options.shouldForwardProp : undefined\n const insertLayer = options?.layer\n\n // STATIC FAST PATH: no function interpolations → compute class once at creation time\n if (!hasDynamicValues) {\n // Inline resolve for the common no-values case\n const raw = values.length === 0 ? (strings[0] as string) : resolve(strings, values, {})\n const cssText = normalizeCSS(raw)\n const hasCss = cssText.length > 0\n\n const staticClassName = hasCss ? sheet.insert(cssText, false, insertLayer) : ''\n\n const StaticStyled: ComponentFn = (rawProps: Record<string, any>): VNode | null => {\n const finalTag = rawProps.as || tag\n const isDOM = typeof finalTag === 'string'\n const finalProps = buildProps(rawProps, staticClassName, isDOM, customFilter)\n\n return h(\n finalTag as string,\n finalProps,\n ...(Array.isArray(rawProps.children)\n ? rawProps.children\n : rawProps.children != null\n ? [rawProps.children]\n : []),\n )\n }\n\n ;(StaticStyled as ComponentFn & { displayName?: string }).displayName =\n `styled(${getDisplayName(tag)})`\n\n // Store in component cache + hot cache for future reuse\n if (!options && values.length === 0) {\n let tagMap = staticComponentCache.get(strings)\n if (!tagMap) {\n tagMap = new Map()\n staticComponentCache.set(strings, tagMap)\n }\n tagMap.set(tag, StaticStyled)\n _hotStrings = strings\n _hotTag = tag\n _hotComponent = StaticStyled\n }\n\n return StaticStyled\n }\n\n // DYNAMIC PATH: resolve CSS on every render with theme/props.\n // When $rocketstyle is a function accessor (from rocketstyle's\n // EnhancedComponent), we resolve it initially for the first class,\n // then set up an effect to reactively swap classes when mode changes.\n // This avoids VNode remounting — only classList updates on the DOM element.\n const DynamicStyled: ComponentFn = (rawProps: Record<string, any>): VNode | null => {\n const theme = useTheme()\n const $rs = rawProps.$rocketstyle\n const $rsState = rawProps.$rocketstate\n const isReactiveRS = typeof $rs === 'function'\n const isReactiveState = typeof $rsState === 'function'\n\n // Resolve initial accessor values — both $rocketstyle and $rocketstate\n // must be plain objects when passed to resolve(), because .styles()\n // interpolation functions destructure them directly:\n // const { hover, pressed } = $rocketstate.pseudo\n // const { hover: hoverStyles } = $rocketstyle\n const resolvedRS = isReactiveRS ? $rs() : $rs\n const resolvedState = isReactiveState ? $rsState() : $rsState\n const initialProps = {\n ...rawProps,\n ...(isReactiveRS ? { $rocketstyle: resolvedRS } : {}),\n ...(isReactiveState ? { $rocketstate: resolvedState } : {}),\n }\n const cssText = normalizeCSS(resolve(strings, values, { ...initialProps, theme }))\n const initialClassName = cssText.length > 0 ? sheet.insert(cssText, false, insertLayer) : ''\n\n const finalTag = rawProps.as || tag\n const isDOM = typeof finalTag === 'string'\n\n // Track mounted element for reactive class updates\n let el: Element | null = null\n let currentClassName = initialClassName\n\n const originalRef = rawProps.ref\n const refCallback = (node: Element | null) => {\n el = node\n if (originalRef) {\n if (typeof originalRef === 'function') originalRef(node)\n else if (originalRef && typeof originalRef === 'object') originalRef.current = node\n }\n }\n\n const finalProps = buildProps(\n { ...rawProps, ref: isReactiveRS ? refCallback : rawProps.ref },\n initialClassName,\n isDOM,\n customFilter,\n )\n\n // Set up reactive class swap when $rocketstyle is a function accessor.\n // CRITICAL: only $rs() is tracked. resolve() and DOM mutations run\n // inside runUntracked() to prevent subscribing to signals read by\n // interpolation functions (theme properties, context getters, etc.).\n // Without this, every styled component's effect subscribes to every\n // signal touched during CSS resolution — causing an exponential\n // cascade across 50+ components on any signal change.\n if (isReactiveRS) {\n effect(() => {\n const newRS = $rs() // TRACKED: subscribes to mode + dimension signals\n const newState = isReactiveState ? $rsState() : $rsState // TRACKED\n\n runUntracked(() => {\n // UNTRACKED: resolve + DOM mutation — no additional subscriptions\n const newResolvedProps = {\n ...rawProps,\n $rocketstyle: newRS,\n $rocketstate: newState,\n }\n const newCss = normalizeCSS(\n resolve(strings, values, { ...newResolvedProps, theme }),\n )\n const newClass = newCss.length > 0 ? sheet.insert(newCss, false, insertLayer) : ''\n\n if (el && newClass !== currentClassName) {\n if (currentClassName) el.classList.remove(currentClassName)\n if (newClass) el.classList.add(newClass)\n currentClassName = newClass\n }\n })\n })\n }\n\n // STATIC VNode — created once, never remounted on mode change\n return h(\n finalTag as string,\n finalProps,\n ...(Array.isArray(rawProps.children)\n ? rawProps.children\n : rawProps.children != null\n ? [rawProps.children]\n : []),\n )\n }\n\n ;(DynamicStyled as ComponentFn & { displayName?: string }).displayName =\n `styled(${getDisplayName(tag)})`\n return DynamicStyled\n}\n\n/** Factory function: styled(tag) returns a tagged template function. */\nconst styledFactory = (tag: Tag, options?: StyledOptions) => {\n const templateFn = (strings: TemplateStringsArray, ...values: Interpolation[]) =>\n createStyledComponent(tag, strings, values, options)\n\n return templateFn\n}\n\n/**\n * Main styled export. Supports both calling conventions:\n * - `styled('div')` or `styled(Component)` → returns tagged template function\n * - `styled('div', { shouldForwardProp })` → with custom prop filtering\n * - `styled.div` → shorthand via Proxy (no options)\n */\n// Cache template functions per tag to avoid closure allocation on every Proxy get\nconst proxyCache = new Map<string, (...args: any[]) => any>()\n\ntype TagTemplateFn = (strings: TemplateStringsArray, ...values: Interpolation[]) => ComponentFn\n\ntype HtmlTags =\n | 'a'\n | 'abbr'\n | 'address'\n | 'article'\n | 'aside'\n | 'audio'\n | 'b'\n | 'blockquote'\n | 'body'\n | 'br'\n | 'button'\n | 'canvas'\n | 'caption'\n | 'code'\n | 'col'\n | 'colgroup'\n | 'dd'\n | 'details'\n | 'div'\n | 'dl'\n | 'dt'\n | 'em'\n | 'fieldset'\n | 'figcaption'\n | 'figure'\n | 'footer'\n | 'form'\n | 'h1'\n | 'h2'\n | 'h3'\n | 'h4'\n | 'h5'\n | 'h6'\n | 'head'\n | 'header'\n | 'hr'\n | 'html'\n | 'i'\n | 'iframe'\n | 'img'\n | 'input'\n | 'label'\n | 'legend'\n | 'li'\n | 'link'\n | 'main'\n | 'mark'\n | 'menu'\n | 'meta'\n | 'nav'\n | 'ol'\n | 'optgroup'\n | 'option'\n | 'output'\n | 'p'\n | 'picture'\n | 'pre'\n | 'progress'\n | 'q'\n | 'section'\n | 'select'\n | 'small'\n | 'source'\n | 'span'\n | 'strong'\n | 'style'\n | 'sub'\n | 'summary'\n | 'sup'\n | 'svg'\n | 'table'\n | 'tbody'\n | 'td'\n | 'template'\n | 'textarea'\n | 'tfoot'\n | 'th'\n | 'thead'\n | 'time'\n | 'tr'\n | 'u'\n | 'ul'\n | 'video'\n\nexport type StyledFunction = ((tag: Tag, options?: StyledOptions) => TagTemplateFn) & {\n [K in HtmlTags]: TagTemplateFn\n}\n\n// Proxy is needed to support styled.div`...` syntax; the cast bridges\n// styledFactory's call signature to StyledFunction which adds HTML tag properties.\n// Proxy target uses `as any` because TS can't resolve Proxy<StyledFunction> with mapped types\nexport const styled: StyledFunction = new Proxy(styledFactory as any, {\n get(_target: unknown, prop: string) {\n if (prop === 'prototype' || prop === '$$typeof') return undefined\n // styled.div`...`, styled.span`...`, etc.\n let fn = proxyCache.get(prop)\n if (!fn) {\n fn = (strings: TemplateStringsArray, ...values: Interpolation[]) =>\n createStyledComponent(prop, strings, values)\n proxyCache.set(prop, fn)\n }\n return fn\n },\n})\n","/**\n * Hook that resolves a CSSResult template with props, injects CSS\n * into the shared stylesheet, and returns the className.\n *\n * Use this when you need computed CSS class names on plain elements\n * without the overhead of a styled component layer.\n */\nimport { type CSSResult, normalizeCSS, resolve } from './resolve'\nimport { sheet } from './sheet'\nimport { useTheme } from './ThemeProvider'\n\nexport function useCSS(template: CSSResult, props?: Record<string, any>, boost?: boolean): string {\n const theme = useTheme()\n const allProps = theme ? { ...props, theme } : (props ?? {})\n const cssText = normalizeCSS(resolve(template.strings, template.values, allProps))\n\n if (!cssText.trim()) return ''\n\n return sheet.insert(cssText, boost)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/styler",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
4
4
  "description": "Lightweight CSS-in-JS engine for Pyreon",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,7 +10,6 @@
10
10
  },
11
11
  "files": [
12
12
  "lib",
13
- "!lib/**/*.map",
14
13
  "!lib/analysis",
15
14
  "README.md",
16
15
  "LICENSE",
@@ -41,12 +40,12 @@
41
40
  "typecheck": "tsc --noEmit"
42
41
  },
43
42
  "devDependencies": {
44
- "@pyreon/typescript": "^0.12.0",
43
+ "@pyreon/typescript": "^0.12.2",
45
44
  "@vitus-labs/tools-rolldown": "^1.15.3"
46
45
  },
47
46
  "peerDependencies": {
48
- "@pyreon/core": "^0.12.0",
49
- "@pyreon/reactivity": "^0.12.0"
47
+ "@pyreon/core": "^0.12.2",
48
+ "@pyreon/reactivity": "^0.12.2"
50
49
  },
51
50
  "engines": {
52
51
  "node": ">= 22"
@@ -345,7 +345,7 @@ describe('resolve composition chain', () => {
345
345
  describe('styled component composition', () => {
346
346
  it('handles array of functions as single interpolation (calculateStyles pattern)', () => {
347
347
  // This is EXACTLY what rocketstyle does:
348
- // styled(component, { boost: true })`${calculateStyles(styles)};`
348
+ // styled(component, { layer: 'rocketstyle' })`${calculateStyles(styles)};`
349
349
  // calculateStyles returns an array of function results
350
350
 
351
351
  const fn1 = (props: any) => `position: ${props.$rocketstyle?.position ?? 'static'};`
@@ -2,14 +2,14 @@
2
2
  * Tests for the hybrid injection approach:
3
3
  * - Client (jsdom): shared <style data-pyreon-styler> sheet
4
4
  * - CSS rules present in the CSSOM sheet after insertion
5
- * - `boost` option threaded from styled() through to the sheet
5
+ * - `layer` option threaded from styled() through to the sheet
6
6
  *
7
7
  * Ported to VNode-level testing: we call the component function directly
8
8
  * and inspect the returned VNode + the sheet's CSSOM.
9
9
  */
10
10
 
11
11
  import type { VNode } from '@pyreon/core'
12
- import { afterEach, describe, expect, it } from 'vitest'
12
+ import { afterEach, describe, expect, it, vi } from 'vitest'
13
13
  import { createGlobalStyle } from '../globalStyle'
14
14
  import { sheet } from '../sheet'
15
15
  import { styled } from '../styled'
@@ -160,56 +160,57 @@ describe('hybrid injection — VNode output (no <style> in tree)', () => {
160
160
  })
161
161
  })
162
162
 
163
- describe('hybrid injection — boost option at component level', () => {
163
+ describe('hybrid injection — layer option at component level', () => {
164
164
  afterEach(() => {
165
165
  sheet.clearAll()
166
166
  })
167
167
 
168
- it('static boosted component produces doubled selector in CSSOM', () => {
169
- const Comp = styled('div', { boost: true })`
168
+ it('static layered component generates className', () => {
169
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
170
+ const Comp = styled('div', { layer: 'rocketstyle' })`
170
171
  color: red;
171
172
  `
172
173
  const vnode = Comp({}) as VNode
173
174
  const className = vnode.props.class as string
174
175
 
175
- const rules = findRulesFor(className)
176
- expect(rules.length).toBeGreaterThanOrEqual(1)
177
- // Boost doubles the selector: .pyr-abc.pyr-abc
178
- expect(rules.some((r) => r.includes(`.${className}.${className}`))).toBe(true)
176
+ // className is generated regardless of CSSOM @layer support
177
+ expect(className).toMatch(/^pyr-/)
178
+ warnSpy.mockRestore()
179
179
  })
180
180
 
181
- it('dynamic boosted component produces doubled selector in CSSOM', () => {
182
- const Comp = styled('div', { boost: true })`
181
+ it('dynamic layered component generates className', () => {
182
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
183
+ const Comp = styled('div', { layer: 'rocketstyle' })`
183
184
  color: ${(p: any) => p.$color};
184
185
  `
185
186
  const vnode = Comp({ $color: 'blue' }) as VNode
186
187
  const className = vnode.props.class as string
187
188
 
188
- const rules = findRulesFor(className)
189
- expect(rules.length).toBeGreaterThanOrEqual(1)
190
- expect(rules.some((r) => r.includes(`.${className}.${className}`))).toBe(true)
189
+ // className is generated regardless of CSSOM @layer support
190
+ expect(className).toMatch(/^pyr-/)
191
+ warnSpy.mockRestore()
191
192
  })
192
193
 
193
- it('non-boosted component produces single selector', () => {
194
+ it('default sheet component produces valid className and injects rule', () => {
194
195
  const Comp = styled('div')`
195
196
  color: green;
196
197
  `
197
198
  const vnode = Comp({}) as VNode
198
199
  const className = vnode.props.class as string
199
200
 
201
+ expect(className).toMatch(/^pyr-/)
202
+ // In environments without @layer support (happy-dom), rules are inserted
203
+ // without layer wrapping. In real browsers, they'd be in @layer base.
200
204
  const rules = findRulesFor(className)
201
205
  expect(rules.length).toBeGreaterThanOrEqual(1)
202
- // Single selector: .pyr-abc { ... } — NOT .pyr-abc.pyr-abc
203
206
  const baseRule = rules[0] as string
204
207
  expect(baseRule).toContain(`.${className}`)
205
- // Count occurrences of the className in the selector portion
206
- const selectorPart = baseRule.split('{')[0] as string
207
- const occurrences = selectorPart.split(`.${className}`).length - 1
208
- expect(occurrences).toBe(1)
208
+ expect(baseRule).toContain('color: green')
209
209
  })
210
210
 
211
- it('boosted component with @media splits correctly', () => {
212
- const Comp = styled('div', { boost: true })`
211
+ it('layered component with @media generates className', () => {
212
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
213
+ const Comp = styled('div', { layer: 'rocketstyle' })`
213
214
  color: red;
214
215
  @media (min-width: 768px) {
215
216
  font-size: 20px;
@@ -218,12 +219,7 @@ describe('hybrid injection — boost option at component level', () => {
218
219
  const vnode = Comp({}) as VNode
219
220
  const className = vnode.props.class as string
220
221
 
221
- const rules = findRulesFor(className)
222
- // Should have at least 2 rules: base + @media
223
- expect(rules.length).toBeGreaterThanOrEqual(2)
224
- // Both base and media rule should use doubled selector
225
- for (const rule of rules) {
226
- expect(rule).toContain(`.${className}.${className}`)
227
- }
222
+ expect(className).toMatch(/^pyr-/)
223
+ warnSpy.mockRestore()
228
224
  })
229
225
  })
@@ -221,25 +221,15 @@ describe('StyleSheet -- advanced features', () => {
221
221
  })
222
222
  })
223
223
 
224
- describe('boost on prepare()', () => {
225
- it('doubles selector when boost is true', () => {
224
+ describe('prepare()', () => {
225
+ it('produces single selector', () => {
226
226
  document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
227
227
  el.remove()
228
228
  })
229
229
  const s = createSheet()
230
- const result = s.prepare('color: red;', true)
230
+ const result = s.prepare('color: red;')
231
231
  expect(result.className).toMatch(/^pyr-/)
232
- // Boosted: selector is doubled
233
- expect(result.rules).toContain(`.${result.className}.${result.className}`)
234
- })
235
-
236
- it('single selector when boost is false', () => {
237
- document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
238
- el.remove()
239
- })
240
- const s = createSheet()
241
- const result = s.prepare('color: red;', false)
242
- // Non-boosted: single selector
232
+ // Single selector, no doubling
243
233
  expect(result.rules).toContain(`.${result.className}{`)
244
234
  expect(result.rules).not.toContain(`.${result.className}.${result.className}`)
245
235
  })
@@ -275,23 +265,23 @@ describe('StyleSheet -- advanced features', () => {
275
265
  })
276
266
  })
277
267
 
278
- describe('boost on insert()', () => {
268
+ describe('layer on insert()', () => {
279
269
  beforeEach(() => {
280
270
  document.querySelectorAll('style[data-pyreon-styler]').forEach((el) => {
281
271
  el.remove()
282
272
  })
283
273
  })
284
274
 
285
- it('inserts boosted rule on client side', () => {
275
+ it('inserts rule with layer on client side', () => {
286
276
  const s = createSheet()
287
- const cls = s.insert('color: red;', true)
277
+ const cls = s.insert('color: red;', false, 'rocketstyle')
288
278
  expect(cls).toMatch(/^pyr-/)
289
279
  })
290
280
 
291
- it('deduplicates boosted and non-boosted separately via insertCache key', () => {
281
+ it('deduplicates layered and non-layered separately via insertCache key', () => {
292
282
  const s = createSheet()
293
283
  const cls1 = s.insert('color: green;', false)
294
- const cls2 = s.insert('color: green;', true)
284
+ const cls2 = s.insert('color: green;', false, 'rocketstyle')
295
285
  // Same className (same hash) but both should work without error
296
286
  expect(cls1).toBe(cls2)
297
287
  })
@@ -585,8 +575,8 @@ describe('StyleSheet -- advanced features', () => {
585
575
  const styles = s.getStyles()
586
576
 
587
577
  expect(styles).toContain('@keyframes fadeIn')
588
- // Keyframes are not wrapped
589
- expect(styles).not.toMatch(/@layer.*@keyframes/)
578
+ // Keyframes are not wrapped in a layer block (layer declaration before them is fine)
579
+ expect(styles).not.toMatch(/@layer\s+\w+\s*\{[^}]*@keyframes/)
590
580
  })
591
581
 
592
582
  it('does not wrap global rules in @layer', () => {
@@ -606,11 +596,11 @@ describe('StyleSheet -- advanced features', () => {
606
596
  expect(result.rules).toContain('color: red;')
607
597
  })
608
598
 
609
- it('prepare() with boost wraps in @layer', () => {
599
+ it('prepare() wraps in @layer with layer option', () => {
610
600
  const s = createSheet({ layer: 'lib' })
611
- const result = s.prepare('color: blue;', true)
601
+ const result = s.prepare('color: blue;')
612
602
  expect(result.rules).toContain('@layer lib')
613
- expect(result.rules).toContain(`.${result.className}.${result.className}`)
603
+ expect(result.rules).toContain(`.${result.className}`)
614
604
  })
615
605
  })
616
606
 
@@ -74,16 +74,16 @@ describe('StyleSheet -- at-rule splitting', () => {
74
74
  expect(styles).toMatch(/@media \(min-width: 1024px\)\{\.pyr-[0-9a-z]+\{color: green;\}\}/)
75
75
  })
76
76
 
77
- it('boost doubles selector in both base and media rules', () => {
78
- const s = createSheet()
79
- s.insert('color: red; @media (min-width: 768px){color: blue;}', true)
77
+ it('layer wraps both base and media rules in @layer', () => {
78
+ const s = createSheet({ layer: 'rocketstyle' })
79
+ s.insert('color: red; @media (min-width: 768px){color: blue;}')
80
80
  const styles = s.getStyles()
81
81
 
82
- // Base: .pyr-xxx.pyr-xxx{color: red;}
83
- expect(styles).toMatch(/\.pyr-[0-9a-z]+\.pyr-[0-9a-z]+\{color: red;\}/)
84
- // Media: @media (...){.pyr-xxx.pyr-xxx{color: blue;}}
82
+ // Base wrapped in @layer: @layer rocketstyle{.pyr-xxx{color: red;}}
83
+ expect(styles).toMatch(/@layer rocketstyle\{\.pyr-[0-9a-z]+\{color: red;\}\}/)
84
+ // Media wrapped in @layer: @layer rocketstyle{@media (...){.pyr-xxx{color: blue;}}}
85
85
  expect(styles).toMatch(
86
- /@media \(min-width: 768px\)\{\.pyr-[0-9a-z]+\.pyr-[0-9a-z]+\{color: blue;\}\}/,
86
+ /@layer rocketstyle\{@media \(min-width: 768px\)\{\.pyr-[0-9a-z]+\{color: blue;\}\}\}/,
87
87
  )
88
88
  })
89
89
 
@@ -139,7 +139,7 @@ describe('StyleSheet -- at-rule splitting', () => {
139
139
  '@media only screen and (min-width: 48em){bottom: 0; height: 40rem;} ' +
140
140
  '@media only screen and (min-width: 62em){right: -6.25rem;} ' +
141
141
  '@media only screen and (min-width: 100em){right: initial; left: 55%;}'
142
- s.insert(cssStr, true)
142
+ s.insert(cssStr)
143
143
  const styles = s.getStyles()
144
144
 
145
145
  // Base rule has position, bottom, right, height
@@ -213,27 +213,27 @@ describe('StyleSheet -- at-rule splitting', () => {
213
213
  expect(hasMediaRule).toBe(true)
214
214
  })
215
215
 
216
- it('boosted selector appears in both base and media rules', () => {
216
+ it('single selector appears in both base and media rules', () => {
217
217
  const s = createSheet()
218
- const className = s.insert('color: red; @media (min-width: 768px){color: blue;}', true)
218
+ const className = s.insert('color: red; @media (min-width: 768px){color: blue;}')
219
219
 
220
220
  const styleEl = document.querySelector('style[data-pyreon-styler]') as HTMLStyleElement
221
221
  const sheet = styleEl.sheet
222
222
  if (!sheet) throw new Error('expected sheet')
223
- const boostedSelector = `.${className}.${className}`
223
+ const singleSelector = `.${className}`
224
224
 
225
225
  let baseFound = false
226
226
  let mediaInnerFound = false
227
227
 
228
228
  for (let i = 0; i < sheet.cssRules.length; i++) {
229
229
  const rule = sheet.cssRules[i]
230
- if (rule instanceof CSSStyleRule && rule.selectorText === boostedSelector) {
230
+ if (rule instanceof CSSStyleRule && rule.selectorText === singleSelector) {
231
231
  baseFound = true
232
232
  }
233
233
  if (rule instanceof CSSMediaRule) {
234
234
  for (let j = 0; j < rule.cssRules.length; j++) {
235
235
  const inner = rule.cssRules[j]
236
- if (inner instanceof CSSStyleRule && inner.selectorText === boostedSelector) {
236
+ if (inner instanceof CSSStyleRule && inner.selectorText === singleSelector) {
237
237
  mediaInnerFound = true
238
238
  }
239
239
  }
@@ -271,16 +271,16 @@ describe('StyleSheet -- at-rule splitting', () => {
271
271
  expect(s.cacheSize).toBeGreaterThanOrEqual(1)
272
272
  })
273
273
 
274
- it('hydrates className from boosted selectors in media rules', () => {
274
+ it('hydrates className from @layer wrapped selectors in media rules', () => {
275
275
  const el = document.createElement('style')
276
276
  el.setAttribute('data-pyreon-styler', '')
277
277
  document.head.appendChild(el)
278
278
 
279
279
  const className = `pyr-${hash('font-size: 1rem;')}`
280
280
 
281
- el.sheet?.insertRule(`.${className}.${className}{font-size: 1rem;}`, 0)
281
+ el.sheet?.insertRule(`.${className}{font-size: 1rem;}`, 0)
282
282
  el.sheet?.insertRule(
283
- `@media (min-width: 768px){.${className}.${className}{font-size: 1.5rem;}}`,
283
+ `@media (min-width: 768px){.${className}{font-size: 1.5rem;}}`,
284
284
  1,
285
285
  )
286
286
 
@@ -40,8 +40,8 @@ describe('StyleSheet', () => {
40
40
  expect(className).toMatch(/^pyr-[0-9a-z]+$/)
41
41
  })
42
42
 
43
- it('supports boost mode (doubled selector)', () => {
44
- const className = sheet.insert('color: red;', true)
43
+ it('supports layer mode (@layer wrapping)', () => {
44
+ const className = sheet.insert('color: red;', false, 'rocketstyle')
45
45
  expect(className).toMatch(/^pyr-[0-9a-z]+$/)
46
46
  })
47
47
  })
@@ -107,10 +107,11 @@ describe('StyleSheet', () => {
107
107
  expect(rules).toContain('color: red;')
108
108
  })
109
109
 
110
- it('supports boost mode', () => {
111
- const { className, rules } = sheet.prepare('color: red;', true)
112
- // Boosted selector should have doubled class
113
- expect(rules).toContain(`.${className}.${className}`)
110
+ it('produces single selector (no boost)', () => {
111
+ const { className, rules } = sheet.prepare('color: red;')
112
+ // Single selector, no doubling
113
+ expect(rules).toContain(`.${className}{`)
114
+ expect(rules).not.toContain(`.${className}.${className}`)
114
115
  })
115
116
  })
116
117
 
@@ -34,7 +34,7 @@ describe('styled -- SSR mode', () => {
34
34
 
35
35
  it('static: boost option in SSR', async () => {
36
36
  const { styled } = await import('../styled')
37
- const Comp = styled('div', { boost: true })`
37
+ const Comp = styled('div', { layer: 'rocketstyle' })`
38
38
  color: blue;
39
39
  `
40
40
  expect((Comp as any).displayName).toBe('styled(div)')
@@ -286,9 +286,9 @@ describe('styled', () => {
286
286
  })
287
287
  })
288
288
 
289
- describe('boost option', () => {
290
- it('accepts boost option without error', () => {
291
- const Comp = styled('div', { boost: true })`
289
+ describe('layer option', () => {
290
+ it('accepts layer option without error', () => {
291
+ const Comp = styled('div', { layer: 'rocketstyle' })`
292
292
  color: red;
293
293
  `
294
294
  const vnode = Comp({}) as VNode
package/src/sheet.ts CHANGED
@@ -27,6 +27,7 @@ export class StyleSheet {
27
27
  private isSSR: boolean
28
28
  private maxCacheSize: number
29
29
  private layer: string | undefined
30
+ private supportsLayer = false
30
31
 
31
32
  constructor(options: StyleSheetOptions = {}) {
32
33
  this.maxCacheSize = options.maxCacheSize ?? DEFAULT_MAX_CACHE_SIZE
@@ -49,12 +50,16 @@ export class StyleSheet {
49
50
  this.sheet = el.sheet ?? null
50
51
  }
51
52
 
52
- // Inject @layer declaration if configured
53
- if (this.layer && this.sheet) {
53
+ // Inject @layer declarations.
54
+ // 'base' is for plain styled() components, 'rocketstyle' is for
55
+ // rocketstyle wrappers. Layer order ensures rocketstyle overrides base
56
+ // without needing doubled selectors (boost).
57
+ if (this.sheet) {
54
58
  try {
55
- this.sheet.insertRule(`@layer ${this.layer};`, 0)
59
+ this.sheet.insertRule('@layer base, rocketstyle;', 0)
60
+ this.supportsLayer = true
56
61
  } catch {
57
- // skip if @layer not supported
62
+ // @layer not supported — falls back to source order
58
63
  }
59
64
  }
60
65
  }
@@ -177,12 +182,15 @@ export class StyleSheet {
177
182
  * Deduplicates: same CSS text always produces the same class name and
178
183
  * the rules are only injected once.
179
184
  *
180
- * When `boost` is true, the selector is doubled (`.pyr-abc.pyr-abc`)
181
- * to raise specificity from (0,1,0) to (0,2,0).
185
+ * @param cssText - CSS declarations to insert
186
+ * @param _unused - Reserved for backward compatibility (was `boost`)
187
+ * @param insertLayer - CSS @layer to wrap this rule in (e.g. 'rocketstyle').
188
+ * Used by rocketstyle to ensure wrapper styles override inner component styles
189
+ * via @layer order (base < rocketstyle) instead of specificity hacks.
182
190
  */
183
- insert(cssText: string, boost = false): string {
191
+ insert(cssText: string, _unused = false, insertLayer?: string): string {
184
192
  // Fast path: skip hash computation on repeated insertions of same CSS text
185
- const icKey = boost ? `${cssText}\0` : cssText
193
+ const icKey = insertLayer ? `${cssText}\0L:${insertLayer}` : cssText
186
194
  const icHit = this.insertCache.get(icKey)
187
195
  if (icHit) return icHit
188
196
 
@@ -197,7 +205,7 @@ export class StyleSheet {
197
205
  this.evictIfNeeded()
198
206
  this.cache.set(className, className)
199
207
 
200
- const selector = boost ? `.${className}.${className}` : `.${className}`
208
+ const selector = `.${className}`
201
209
 
202
210
  // Split nested at-rules into separate top-level rules
203
211
  const { base, atRules } = this.splitAtRules(cssText, selector)
@@ -206,8 +214,11 @@ export class StyleSheet {
206
214
  if (base) rules.push(`${selector}{${base}}`)
207
215
  rules.push(...atRules)
208
216
 
209
- // Apply @layer wrapping if configured
210
- const finalRules = this.layer ? rules.map((r) => `@layer ${this.layer}{${r}}`) : rules
217
+ // Apply @layer wrapping per-insert layer takes precedence over sheet-level layer.
218
+ // In SSR, always apply layers (output goes to real browsers).
219
+ // In client, skip if @layer isn't supported (e.g. happy-dom in tests).
220
+ const layerName = (this.isSSR || this.supportsLayer) ? (insertLayer ?? this.layer) : undefined
221
+ const finalRules = layerName ? rules.map((r) => `@layer ${layerName}{${r}}`) : rules
211
222
 
212
223
  if (this.isSSR) {
213
224
  for (const rule of finalRules) {
@@ -306,13 +317,17 @@ export class StyleSheet {
306
317
 
307
318
  /** Returns collected CSS for SSR as a complete `<style>` tag string. */
308
319
  getStyleTag(): string {
309
- const css = this.ssrBuffer.join('').replace(/<\/style/gi, '<\\/style')
320
+ if (this.ssrBuffer.length === 0) return `<style ${ATTR}=""></style>`
321
+ const layerDecl = this.layer ? '@layer base, rocketstyle;' : ''
322
+ const css = (layerDecl + this.ssrBuffer.join('')).replace(/<\/style/gi, '<\\/style')
310
323
  return `<style ${ATTR}="">${css}</style>`
311
324
  }
312
325
 
313
326
  /** Returns collected CSS rules as a raw string (useful for streaming SSR). */
314
327
  getStyles(): string {
315
- return this.ssrBuffer.join('')
328
+ if (this.ssrBuffer.length === 0) return ''
329
+ const layerDecl = this.layer ? '@layer base, rocketstyle;' : ''
330
+ return layerDecl + this.ssrBuffer.join('')
316
331
  }
317
332
 
318
333
  /** Reset SSR buffer and cache (call between server requests). */
@@ -348,10 +363,10 @@ export class StyleSheet {
348
363
  /**
349
364
  * Compute className and full CSS rule text without injecting.
350
365
  */
351
- prepare(cssText: string, boost = false): { className: string; rules: string } {
366
+ prepare(cssText: string): { className: string; rules: string } {
352
367
  const h = hash(cssText)
353
368
  const className = `${PREFIX}-${h}`
354
- const selector = boost ? `.${className}.${className}` : `.${className}`
369
+ const selector = `.${className}`
355
370
  const { base, atRules } = this.splitAtRules(cssText, selector)
356
371
 
357
372
  const allRules: string[] = []
@@ -375,7 +390,7 @@ export class StyleSheet {
375
390
  }
376
391
 
377
392
  /** Default singleton sheet for client-side use. */
378
- export const sheet = new StyleSheet()
393
+ export const sheet = new StyleSheet({ layer: 'base' })
379
394
 
380
395
  /**
381
396
  * Factory for creating isolated StyleSheet instances.
package/src/styled.tsx CHANGED
@@ -30,11 +30,11 @@ export interface StyledOptions {
30
30
  /** Custom prop filter. Return true to forward the prop to the DOM element. */
31
31
  shouldForwardProp?: (prop: string) => boolean
32
32
  /**
33
- * Double the class selector to raise specificity from (0,1,0) to (0,2,0).
34
- * Ensures this component's styles override inner library components
35
- * regardless of CSS source order.
33
+ * CSS @layer name. Rules are wrapped in `@layer <name> { ... }`.
34
+ * Used by rocketstyle to ensure wrapper styles override inner component
35
+ * styles via layer order (base < rocketstyle) instead of specificity hacks.
36
36
  */
37
- boost?: boolean
37
+ layer?: string
38
38
  }
39
39
 
40
40
  const getDisplayName = (tag: Tag): string =>
@@ -77,7 +77,7 @@ const createStyledComponent = (
77
77
  // Fast check: no values means no dynamic interpolations — avoids .some() scan
78
78
  const hasDynamicValues = values.length > 0 && values.some(isDynamic)
79
79
  const customFilter = options ? options.shouldForwardProp : undefined
80
- const boost = options ? (options.boost ?? false) : false
80
+ const insertLayer = options?.layer
81
81
 
82
82
  // STATIC FAST PATH: no function interpolations → compute class once at creation time
83
83
  if (!hasDynamicValues) {
@@ -86,7 +86,7 @@ const createStyledComponent = (
86
86
  const cssText = normalizeCSS(raw)
87
87
  const hasCss = cssText.length > 0
88
88
 
89
- const staticClassName = hasCss ? sheet.insert(cssText, boost) : ''
89
+ const staticClassName = hasCss ? sheet.insert(cssText, false, insertLayer) : ''
90
90
 
91
91
  const StaticStyled: ComponentFn = (rawProps: Record<string, any>): VNode | null => {
92
92
  const finalTag = rawProps.as || tag
@@ -148,7 +148,7 @@ const createStyledComponent = (
148
148
  ...(isReactiveState ? { $rocketstate: resolvedState } : {}),
149
149
  }
150
150
  const cssText = normalizeCSS(resolve(strings, values, { ...initialProps, theme }))
151
- const initialClassName = cssText.length > 0 ? sheet.insert(cssText, boost) : ''
151
+ const initialClassName = cssText.length > 0 ? sheet.insert(cssText, false, insertLayer) : ''
152
152
 
153
153
  const finalTag = rawProps.as || tag
154
154
  const isDOM = typeof finalTag === 'string'
@@ -195,7 +195,7 @@ const createStyledComponent = (
195
195
  const newCss = normalizeCSS(
196
196
  resolve(strings, values, { ...newResolvedProps, theme }),
197
197
  )
198
- const newClass = newCss.length > 0 ? sheet.insert(newCss, boost) : ''
198
+ const newClass = newCss.length > 0 ? sheet.insert(newCss, false, insertLayer) : ''
199
199
 
200
200
  if (el && newClass !== currentClassName) {
201
201
  if (currentClassName) el.classList.remove(currentClassName)