@praxisjs/css 0.3.4 → 0.3.5

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.
Files changed (146) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/builder/css-builder.d.ts.map +1 -1
  3. package/dist/builder/css-builder.js +68 -74
  4. package/dist/builder/css-builder.js.map +1 -1
  5. package/dist/builder/keyframes.d.ts +5 -0
  6. package/dist/builder/keyframes.d.ts.map +1 -1
  7. package/dist/builder/keyframes.js +12 -14
  8. package/dist/builder/keyframes.js.map +1 -1
  9. package/dist/cx.d.ts.map +1 -1
  10. package/dist/cx.js +23 -18
  11. package/dist/cx.js.map +1 -1
  12. package/dist/decorators/param.d.ts +1 -2
  13. package/dist/decorators/param.d.ts.map +1 -1
  14. package/dist/decorators/param.js +9 -17
  15. package/dist/decorators/param.js.map +1 -1
  16. package/dist/decorators/style.d.ts.map +1 -1
  17. package/dist/decorators/style.js +61 -33
  18. package/dist/decorators/style.js.map +1 -1
  19. package/dist/decorators/styled.d.ts.map +1 -1
  20. package/dist/decorators/styled.js +112 -95
  21. package/dist/decorators/styled.js.map +1 -1
  22. package/dist/extract.d.ts.map +1 -1
  23. package/dist/extract.js +19 -35
  24. package/dist/extract.js.map +1 -1
  25. package/dist/global.d.ts +3 -0
  26. package/dist/global.d.ts.map +1 -1
  27. package/dist/global.js +9 -4
  28. package/dist/global.js.map +1 -1
  29. package/dist/internal/kebab.d.ts +5 -0
  30. package/dist/internal/kebab.d.ts.map +1 -0
  31. package/dist/internal/kebab.js +23 -0
  32. package/dist/internal/kebab.js.map +1 -0
  33. package/dist/internal/lifecycle.d.ts +15 -0
  34. package/dist/internal/lifecycle.d.ts.map +1 -0
  35. package/dist/internal/lifecycle.js +16 -0
  36. package/dist/internal/lifecycle.js.map +1 -0
  37. package/dist/internal/registry.d.ts +5 -1
  38. package/dist/internal/registry.d.ts.map +1 -1
  39. package/dist/internal/registry.js +9 -23
  40. package/dist/internal/registry.js.map +1 -1
  41. package/dist/internal/sheet.d.ts +20 -0
  42. package/dist/internal/sheet.d.ts.map +1 -0
  43. package/dist/internal/sheet.js +56 -0
  44. package/dist/internal/sheet.js.map +1 -0
  45. package/dist/internal/static-mode.d.ts +2 -0
  46. package/dist/internal/static-mode.d.ts.map +1 -0
  47. package/dist/internal/static-mode.js +2 -0
  48. package/dist/internal/static-mode.js.map +1 -0
  49. package/dist/preflight.d.ts +3 -1
  50. package/dist/preflight.d.ts.map +1 -1
  51. package/dist/preflight.js +7 -3
  52. package/dist/preflight.js.map +1 -1
  53. package/dist/tokens/decorators.d.ts.map +1 -1
  54. package/dist/tokens/decorators.js +13 -4
  55. package/dist/tokens/decorators.js.map +1 -1
  56. package/dist/tokens/theme-instance.d.ts +5 -0
  57. package/dist/tokens/theme-instance.d.ts.map +1 -1
  58. package/dist/tokens/theme-instance.js +25 -17
  59. package/dist/tokens/theme-instance.js.map +1 -1
  60. package/dist/tokens/token-sheet.d.ts.map +1 -1
  61. package/dist/tokens/token-sheet.js +12 -9
  62. package/dist/tokens/token-sheet.js.map +1 -1
  63. package/package.json +13 -5
  64. package/src/builder/css-builder.ts +79 -83
  65. package/src/builder/keyframes.ts +12 -20
  66. package/src/cx.ts +20 -13
  67. package/src/decorators/param.ts +12 -16
  68. package/src/decorators/style.ts +79 -35
  69. package/src/decorators/styled.ts +127 -111
  70. package/src/extract.ts +18 -34
  71. package/src/global.ts +10 -9
  72. package/src/internal/kebab.ts +23 -0
  73. package/src/internal/lifecycle.ts +21 -0
  74. package/src/internal/registry.ts +9 -35
  75. package/src/internal/sheet.ts +71 -0
  76. package/src/internal/static-mode.ts +6 -0
  77. package/src/preflight.ts +8 -3
  78. package/src/tokens/decorators.ts +15 -3
  79. package/src/tokens/theme-instance.ts +26 -16
  80. package/src/tokens/token-sheet.ts +12 -12
  81. package/dist/__tests__/css-builder.test.d.ts +0 -2
  82. package/dist/__tests__/css-builder.test.d.ts.map +0 -1
  83. package/dist/__tests__/css-builder.test.js +0 -198
  84. package/dist/__tests__/css-builder.test.js.map +0 -1
  85. package/dist/__tests__/cx.test.d.ts +0 -2
  86. package/dist/__tests__/cx.test.d.ts.map +0 -1
  87. package/dist/__tests__/cx.test.js +0 -47
  88. package/dist/__tests__/cx.test.js.map +0 -1
  89. package/dist/__tests__/decorators.test.d.ts +0 -2
  90. package/dist/__tests__/decorators.test.d.ts.map +0 -1
  91. package/dist/__tests__/decorators.test.js +0 -264
  92. package/dist/__tests__/decorators.test.js.map +0 -1
  93. package/dist/__tests__/extract.test.d.ts +0 -2
  94. package/dist/__tests__/extract.test.d.ts.map +0 -1
  95. package/dist/__tests__/extract.test.js +0 -416
  96. package/dist/__tests__/extract.test.js.map +0 -1
  97. package/dist/__tests__/global.test.d.ts +0 -2
  98. package/dist/__tests__/global.test.d.ts.map +0 -1
  99. package/dist/__tests__/global.test.js +0 -136
  100. package/dist/__tests__/global.test.js.map +0 -1
  101. package/dist/__tests__/keyframes.test.d.ts +0 -2
  102. package/dist/__tests__/keyframes.test.d.ts.map +0 -1
  103. package/dist/__tests__/keyframes.test.js +0 -90
  104. package/dist/__tests__/keyframes.test.js.map +0 -1
  105. package/dist/__tests__/preflight.test.d.ts +0 -2
  106. package/dist/__tests__/preflight.test.d.ts.map +0 -1
  107. package/dist/__tests__/preflight.test.js +0 -74
  108. package/dist/__tests__/preflight.test.js.map +0 -1
  109. package/dist/__tests__/registry.test.d.ts +0 -2
  110. package/dist/__tests__/registry.test.d.ts.map +0 -1
  111. package/dist/__tests__/registry.test.js +0 -132
  112. package/dist/__tests__/registry.test.js.map +0 -1
  113. package/dist/__tests__/style.test.d.ts +0 -2
  114. package/dist/__tests__/style.test.d.ts.map +0 -1
  115. package/dist/__tests__/style.test.js +0 -281
  116. package/dist/__tests__/style.test.js.map +0 -1
  117. package/dist/__tests__/styled.test.d.ts +0 -2
  118. package/dist/__tests__/styled.test.d.ts.map +0 -1
  119. package/dist/__tests__/styled.test.js +0 -904
  120. package/dist/__tests__/styled.test.js.map +0 -1
  121. package/dist/__tests__/theme-instance.test.d.ts +0 -2
  122. package/dist/__tests__/theme-instance.test.d.ts.map +0 -1
  123. package/dist/__tests__/theme-instance.test.js +0 -309
  124. package/dist/__tests__/theme-instance.test.js.map +0 -1
  125. package/dist/__tests__/theme-uninit.test.d.ts +0 -2
  126. package/dist/__tests__/theme-uninit.test.d.ts.map +0 -1
  127. package/dist/__tests__/theme-uninit.test.js +0 -10
  128. package/dist/__tests__/theme-uninit.test.js.map +0 -1
  129. package/dist/__tests__/token-sheet.test.d.ts +0 -2
  130. package/dist/__tests__/token-sheet.test.d.ts.map +0 -1
  131. package/dist/__tests__/token-sheet.test.js +0 -74
  132. package/dist/__tests__/token-sheet.test.js.map +0 -1
  133. package/src/__tests__/css-builder.test.ts +0 -230
  134. package/src/__tests__/cx.test.ts +0 -63
  135. package/src/__tests__/decorators.test.ts +0 -127
  136. package/src/__tests__/extract.test.ts +0 -459
  137. package/src/__tests__/global.test.ts +0 -163
  138. package/src/__tests__/keyframes.test.ts +0 -103
  139. package/src/__tests__/preflight.test.ts +0 -85
  140. package/src/__tests__/registry.test.ts +0 -151
  141. package/src/__tests__/style.test.ts +0 -170
  142. package/src/__tests__/styled.test.ts +0 -443
  143. package/src/__tests__/theme-instance.test.ts +0 -374
  144. package/src/__tests__/theme-uninit.test.ts +0 -12
  145. package/src/__tests__/token-sheet.test.ts +0 -87
  146. package/tsconfig.json +0 -8
@@ -0,0 +1,56 @@
1
+ import { hashCSS } from "./hash.js";
2
+ import { isCSSBuilder } from "../builder/css-builder.js";
3
+ const PARAM_META = new WeakMap();
4
+ export function addParamName(ctor, name) {
5
+ let names = PARAM_META.get(ctor);
6
+ if (!names) {
7
+ names = new Set();
8
+ PARAM_META.set(ctor, names);
9
+ }
10
+ names.add(name);
11
+ }
12
+ export function getParamNames(ctor) {
13
+ return PARAM_META.get(ctor);
14
+ }
15
+ const cache = new WeakMap();
16
+ /**
17
+ * Builds the scoped class names and CSS of a `Stylesheet` subclass, once per class: constructing
18
+ * the template instance fires its `@Param()` initializers, which register the param names.
19
+ */
20
+ export function compileSheet(SheetClass) {
21
+ const cached = cache.get(SheetClass);
22
+ if (cached)
23
+ return cached;
24
+ const template = new SheetClass();
25
+ const paramNames = getParamNames(SheetClass);
26
+ const classMap = {};
27
+ const params = [];
28
+ const defaults = [];
29
+ let css = "";
30
+ for (const key of Object.keys(template)) {
31
+ const value = template[key];
32
+ if (key.startsWith("$")) {
33
+ const rule = typeof value === "string" ? value : isCSSBuilder(value) ? String(value) : null;
34
+ if (rule === null)
35
+ continue;
36
+ const className = `prx-${key.slice(1).replace(/[^\w-]/g, "-")}-${hashCSS(rule)}`;
37
+ classMap[key] = className;
38
+ const block = `.${className} { ${rule.trim()} }`;
39
+ css = css === "" ? block : `${css}\n${block}`;
40
+ }
41
+ else if (paramNames?.has(key) && (typeof value === "string" || typeof value === "number")) {
42
+ params.push(key);
43
+ defaults.push(value);
44
+ }
45
+ }
46
+ const compiled = {
47
+ classMap: Object.freeze(classMap),
48
+ css,
49
+ key: css ? hashCSS(css) : "",
50
+ params,
51
+ defaults,
52
+ };
53
+ cache.set(SheetClass, compiled);
54
+ return compiled;
55
+ }
56
+ //# sourceMappingURL=sheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sheet.js","sourceRoot":"","sources":["../../src/internal/sheet.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const STATIC_MODE: boolean;
2
+ //# sourceMappingURL=static-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-mode.d.ts","sourceRoot":"","sources":["../../src/internal/static-mode.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,EAAE,OAC6C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export const STATIC_MODE = typeof __PRAXIS_CSS_STATIC__ !== "undefined" && __PRAXIS_CSS_STATIC__;
2
+ //# sourceMappingURL=static-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-mode.js","sourceRoot":"","sources":["../../src/internal/static-mode.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,WAAW,GACtB,OAAO,qBAAqB,KAAK,WAAW,IAAI,qBAAqB,CAAC"}
@@ -23,5 +23,7 @@ export interface PreflightOptions {
23
23
  * import { preflight } from '@praxisjs/css'
24
24
  * preflight()
25
25
  */
26
- export declare function preflight({ layer }?: PreflightOptions): void;
26
+ export declare function preflight(options?: PreflightOptions): void;
27
+ /** The CSS `preflight()` injects — shared with build-time extraction. */
28
+ export declare function preflightCSS({ layer }?: PreflightOptions): string;
27
29
  //# sourceMappingURL=preflight.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AA6KA,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAe,EAAE,GAAE,gBAAqB,GAAG,IAAI,CAE1E"}
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AA6KA,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAE1D;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,EAAE,KAAe,EAAE,GAAE,gBAAqB,GAAG,MAAM,CAE/E"}
package/dist/preflight.js CHANGED
@@ -1,4 +1,4 @@
1
- import { globalStyle } from "./global.js";
1
+ import { globalCSS, injectGlobalCSS } from "./global.js";
2
2
  // Tailwind CSS preflight — https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/preflight.css
3
3
  // --theme() references stripped and replaced with their fallback values.
4
4
  const PREFLIGHT = `
@@ -185,7 +185,11 @@ input:where([type='button'], [type='reset'], [type='submit']),
185
185
  * import { preflight } from '@praxisjs/css'
186
186
  * preflight()
187
187
  */
188
- export function preflight({ layer = "reset" } = {}) {
189
- globalStyle(_css => PREFLIGHT, { layer });
188
+ export function preflight(options) {
189
+ injectGlobalCSS(preflightCSS(options));
190
+ }
191
+ /** The CSS `preflight()` injects — shared with build-time extraction. */
192
+ export function preflightCSS({ layer = "reset" } = {}) {
193
+ return globalCSS(() => PREFLIGHT, { layer });
190
194
  }
191
195
  //# sourceMappingURL=preflight.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,oHAAoH;AACpH,yEAAyE;AACzE,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuKjB,CAAC;AAYF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,SAAS,CAAC,EAAE,KAAK,GAAG,OAAO,KAAuB,EAAE;IAClE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"preflight.js","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEzD,oHAAoH;AACpH,yEAAyE;AACzE,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuKjB,CAAC;AAYF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,SAAS,CAAC,OAA0B;IAClD,eAAe,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAC,EAAE,KAAK,GAAG,OAAO,KAAuB,EAAE;IACrE,OAAO,SAAS,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AAC/C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/tokens/decorators.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,EACL,aAAa,EACb,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAyB7B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,MAAM,CACpB,SAAS,EAAE,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EACvD,YAAY,EAAE,UAAU,OAAO,EAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAE7B,GAAG,CAQL;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,KAAK,IAYF,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,0BAA0B,CAAC,GAAG,CAAC,KAAK,IAAI,CACvF;AAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/tokens/decorators.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,EACL,aAAa,EACb,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAoC7B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,MAAM,CACpB,SAAS,EAAE,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EACvD,YAAY,EAAE,UAAU,OAAO,EAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAE7B,GAAG,CAQL;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,KAAK,IAYF,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,0BAA0B,CAAC,GAAG,CAAC,KAAK,IAAI,CACvF;AAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,CAAC"}
@@ -1,20 +1,29 @@
1
1
  import { createClassDecorator, ClassBehavior, createFieldDecorator, } from "@praxisjs/decorators";
2
- import { createThemeInstance, theme, ThemeInstance, } from "./theme-instance.js";
2
+ import { createThemeInstance, isActiveTheme, theme, ThemeInstance, } from "./theme-instance.js";
3
3
  // ─── @Themed ─────────────────────────────────────────────────────────────────
4
+ const NO_ENHANCEMENT = Object.freeze({});
4
5
  class ThemedBehavior extends ClassBehavior {
5
6
  DefaultTheme;
6
7
  config;
8
+ #instance = null;
7
9
  constructor(DefaultTheme, config) {
8
10
  super();
9
11
  this.DefaultTheme = DefaultTheme;
10
12
  this.config = config;
11
13
  }
14
+ // Makes this class's theme the active one again, e.g. after another @Themed class replaced it
15
+ // or it was destroyed. While it is still active it is kept, so a theme the user switched to
16
+ // survives the component being instantiated again.
12
17
  create(_instance) {
13
- createThemeInstance(this.DefaultTheme, this.config);
14
- return {};
18
+ if (!isActiveTheme(this.#instance))
19
+ this.#activate();
20
+ return NO_ENHANCEMENT;
15
21
  }
16
22
  initialize(_Enhanced, _original) {
17
- createThemeInstance(this.DefaultTheme, this.config);
23
+ this.#activate();
24
+ }
25
+ #activate() {
26
+ this.#instance = createThemeInstance(this.DefaultTheme, this.config);
18
27
  }
19
28
  }
20
29
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../src/tokens/decorators.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,aAAa,EAEb,oBAAoB,GAErB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,mBAAmB,EACnB,KAAK,EACL,aAAa,GAEd,MAAM,qBAAqB,CAAC;AAE7B,gFAAgF;AAEhF,MAAM,cAAe,SAAQ,aAAa;IAErB;IACA;IAFnB,YACmB,YAA+B,EAC/B,MAA6B;QAE9C,KAAK,EAAE,CAAC;QAHS,iBAAY,GAAZ,YAAY,CAAmB;QAC/B,WAAM,GAAN,MAAM,CAAuB;IAGhD,CAAC;IAED,MAAM,CAAC,SAAwB;QAC7B,mBAAmB,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACpD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,UAAU,CACR,SAA8C,EAC9C,SAA8C;QAE9C,mBAAmB,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACtD,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,MAAM,CACpB,SAAuD,EACvD,YAA+B,EAC/B,MAA8B;IAG9B,OAAO,oBAAoB,CACzB,IAAI,cAAc,CAAC,YAAY,EAAE,MAAM,IAAI,EAAE,CAAC,CAKvC,CAAC;AACZ,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,KAAK;IACnB,OAAO,oBAAoB,CAAC;QAC1B,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,aAAa;YAClC,OAAO;gBACL,UAAU,EAAE;oBACV,GAAG,KAAoB,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC;oBACxC,gEAAgE;oBAChE,GAAG,KAAU,CAAC;iBACf;aACF,CAAC;QACJ,CAAC;QACD,8DAA8D;KAC/D,CAAqF,CAAC;AACzF,CAAC;AAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAqB,CAAC"}
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../src/tokens/decorators.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,aAAa,EAEb,oBAAoB,GAErB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,KAAK,EACL,aAAa,GAEd,MAAM,qBAAqB,CAAC;AAE7B,gFAAgF;AAEhF,MAAM,cAAc,GAAqB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAE3D,MAAM,cAAe,SAAQ,aAAa;IAIrB;IACA;IAJnB,SAAS,GAAyB,IAAI,CAAC;IAEvC,YACmB,YAA+B,EAC/B,MAA6B;QAE9C,KAAK,EAAE,CAAC;QAHS,iBAAY,GAAZ,YAAY,CAAmB;QAC/B,WAAM,GAAN,MAAM,CAAuB;IAGhD,CAAC;IAED,8FAA8F;IAC9F,4FAA4F;IAC5F,mDAAmD;IACnD,MAAM,CAAC,SAAwB;QAC7B,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;YAAE,IAAI,CAAC,SAAS,EAAE,CAAC;QACrD,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,UAAU,CACR,SAA8C,EAC9C,SAA8C;QAE9C,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;IAED,SAAS;QACP,IAAI,CAAC,SAAS,GAAG,mBAAmB,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACvE,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,MAAM,CACpB,SAAuD,EACvD,YAA+B,EAC/B,MAA8B;IAG9B,OAAO,oBAAoB,CACzB,IAAI,cAAc,CAAC,YAAY,EAAE,MAAM,IAAI,EAAE,CAAC,CAKvC,CAAC;AACZ,CAAC;AAED,iFAAiF;AAEjF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,KAAK;IACnB,OAAO,oBAAoB,CAAC;QAC1B,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,aAAa;YAClC,OAAO;gBACL,UAAU,EAAE;oBACV,GAAG,KAAoB,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC;oBACxC,gEAAgE;oBAChE,GAAG,KAAU,CAAC;iBACf;aACF,CAAC;QACJ,CAAC;QACD,8DAA8D;KAC/D,CAAqF,CAAC;AACzF,CAAC;AAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAqB,CAAC"}
@@ -1,3 +1,6 @@
1
+ /** The `--token: value` pairs of a theme instance's string and number fields. */
2
+ export declare function instanceToVars(instance: Record<string, unknown>): Record<string, string>;
3
+ export declare function varsToCSS(vars: Record<string, string>): string;
1
4
  export interface ThemedConfig {
2
5
  persist: boolean;
3
6
  syncTabs: boolean;
@@ -25,6 +28,8 @@ export declare class ThemeInstance {
25
28
  destroy(): void;
26
29
  }
27
30
  export declare function createThemeInstance(DefaultTheme: new () => unknown, config?: Partial<ThemedConfig>): ThemeInstance;
31
+ /** Whether `instance` is the app-wide theme and has not been destroyed. */
32
+ export declare function isActiveTheme(instance: ThemeInstance | null): boolean;
28
33
  /**
29
34
  * Returns the app-wide {@link ThemeInstance}.
30
35
  * Throws if `@Themed()` has not been applied to the root component.
@@ -1 +1 @@
1
- {"version":3,"file":"theme-instance.d.ts","sourceRoot":"","sources":["../../src/tokens/theme-instance.ts"],"names":[],"mappings":"AAyCA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAG,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAOD;;;;;;;GAOG;AACH,qBAAa,aAAa;;gBAKZ,YAAY,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM;IAY/E,2CAA2C;IAC3C,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;OAIG;IACH,MAAM,CAAC,UAAU,EAAE,UAAU,OAAO,GAAG,IAAI;IAS3C,sEAAsE;IACtE,OAAO,IAAI,IAAI;CA6BhB;AAMD,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,UAAU,OAAO,EAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC7B,aAAa,CAGf;AAED;;;GAGG;AACH,wBAAgB,KAAK,IAAI,aAAa,CAKrC"}
1
+ {"version":3,"file":"theme-instance.d.ts","sourceRoot":"","sources":["../../src/tokens/theme-instance.ts"],"names":[],"mappings":"AAKA,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASxF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAO9D;AAkBD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAG,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AASD;;;;;;;GAOG;AACH,qBAAa,aAAa;;gBAKZ,YAAY,EAAE,UAAU,OAAO,EAAE,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM;IAW/E,2CAA2C;IAC3C,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;;OAIG;IACH,MAAM,CAAC,UAAU,EAAE,UAAU,OAAO,GAAG,IAAI;IAS3C,sEAAsE;IACtE,OAAO,IAAI,IAAI;CA8BhB;AAMD,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,UAAU,OAAO,EAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC7B,aAAa,CAKf;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,GAAG,OAAO,CAErE;AAED;;;GAGG;AACH,wBAAgB,KAAK,IAAI,aAAa,CAKrC"}
@@ -1,21 +1,23 @@
1
1
  import { getCollector } from "../internal/collector.js";
2
+ import { toKebab } from "../internal/kebab.js";
2
3
  // ─── helpers ──────────────────────────────────────────────────────────────────
3
- function camelToKebab(s) {
4
- return s.replace(/([A-Z])/g, "-$1").toLowerCase();
5
- }
6
- function instanceToVars(instance) {
4
+ /** The `--token: value` pairs of a theme instance's string and number fields. */
5
+ export function instanceToVars(instance) {
7
6
  const result = {};
8
- for (const [key, val] of Object.entries(instance)) {
7
+ for (const key of Object.keys(instance)) {
8
+ const val = instance[key];
9
9
  if (typeof val === "string" || typeof val === "number") {
10
- result[`--${camelToKebab(key)}`] = String(val);
10
+ result[`--${toKebab(key)}`] = String(val);
11
11
  }
12
12
  }
13
13
  return result;
14
14
  }
15
- function varsToCSS(vars) {
16
- const body = Object.entries(vars)
17
- .map(([k, v]) => ` ${k}: ${v};`)
18
- .join("\n");
15
+ export function varsToCSS(vars) {
16
+ let body = "";
17
+ for (const name of Object.keys(vars)) {
18
+ const line = ` ${name}: ${vars[name]};`;
19
+ body = body === "" ? line : `${body}\n${line}`;
20
+ }
19
21
  return `:root {\n${body}\n}`;
20
22
  }
21
23
  function applyVarsToRoot(vars) {
@@ -36,6 +38,7 @@ function applyVarsToRoot(vars) {
36
38
  }
37
39
  const STORAGE_KEY = "praxis-theme";
38
40
  const BROADCAST_KEY = "praxis-theme";
41
+ const destroyed = new WeakSet();
39
42
  // ─── ThemeInstance ────────────────────────────────────────────────────────────
40
43
  /**
41
44
  * Manages the active design token theme: injects CSS custom properties on
@@ -53,11 +56,10 @@ export class ThemeInstance {
53
56
  this.#config = { persist: false, syncTabs: false, ...config };
54
57
  this.#current = new DefaultTheme();
55
58
  const saved = this.#config.persist ? this.#loadFromStorage() : null;
56
- if (typeof document !== "undefined") {
57
- applyVarsToRoot(saved ?? instanceToVars(this.#current));
58
- if (this.#config.syncTabs)
59
- this.#setupBroadcast();
60
- }
59
+ // Also reaches an active collector when there is no document (server render).
60
+ applyVarsToRoot(saved ?? instanceToVars(this.#current));
61
+ if (this.#config.syncTabs && typeof document !== "undefined")
62
+ this.#setupBroadcast();
61
63
  }
62
64
  /** The currently active theme instance. */
63
65
  get current() {
@@ -71,8 +73,7 @@ export class ThemeInstance {
71
73
  switch(ThemeClass) {
72
74
  this.#current = new ThemeClass();
73
75
  const vars = instanceToVars(this.#current);
74
- if (typeof document !== "undefined")
75
- applyVarsToRoot(vars);
76
+ applyVarsToRoot(vars);
76
77
  if (this.#config.persist)
77
78
  this.#saveToStorage(vars);
78
79
  if (this.#config.syncTabs)
@@ -80,6 +81,7 @@ export class ThemeInstance {
80
81
  }
81
82
  /** Removes the injected `<style>` and closes the BroadcastChannel. */
82
83
  destroy() {
84
+ destroyed.add(this);
83
85
  this.#channel?.close();
84
86
  this.#channel = null;
85
87
  if (typeof document !== "undefined") {
@@ -114,9 +116,15 @@ export class ThemeInstance {
114
116
  // ─── Singleton ────────────────────────────────────────────────────────────────
115
117
  let _instance = null;
116
118
  export function createThemeInstance(DefaultTheme, config) {
119
+ // The replaced instance would otherwise keep its BroadcastChannel open for the page lifetime.
120
+ _instance?.destroy();
117
121
  _instance = new ThemeInstance(DefaultTheme, config);
118
122
  return _instance;
119
123
  }
124
+ /** Whether `instance` is the app-wide theme and has not been destroyed. */
125
+ export function isActiveTheme(instance) {
126
+ return instance !== null && instance === _instance && !destroyed.has(instance);
127
+ }
120
128
  /**
121
129
  * Returns the app-wide {@link ThemeInstance}.
122
130
  * Throws if `@Themed()` has not been applied to the root component.
@@ -1 +1 @@
1
- {"version":3,"file":"theme-instance.js","sourceRoot":"","sources":["../../src/tokens/theme-instance.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"theme-instance.js","sourceRoot":"","sources":["../../src/tokens/theme-instance.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"token-sheet.d.ts","sourceRoot":"","sources":["../../src/tokens/token-sheet.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,UAAU,EAaN,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,CAAC;AAI7D;;;;;;;;;;;;;;;GAeG;AACH,KAAK,WAAW,CAAC,CAAC,SAAS,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,IAAI;IACzE,QAAQ,EAAE,CAAC,IAAI,MAAM,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK,GAAG,MAAM;CAC9E,CAAC;AAEF,wBAAgB,SAAS,CAAC,CAAC,SAAS,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAC9E,GAAG,EAAE,CAAC,GACL,WAAW,CAAC,CAAC,CAAC,CAEhB"}
1
+ {"version":3,"file":"token-sheet.d.ts","sourceRoot":"","sources":["../../src/tokens/token-sheet.ts"],"names":[],"mappings":"AAoBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,UAAU,EAcN,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,CAAC;AAI7D;;;;;;;;;;;;;;;GAeG;AACH,KAAK,WAAW,CAAC,CAAC,SAAS,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,IAAI;IACzE,QAAQ,EAAE,CAAC,IAAI,MAAM,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG,CAAC,GAAG,KAAK,GAAG,MAAM;CAC9E,CAAC;AAEF,wBAAgB,SAAS,CAAC,CAAC,SAAS,QAAQ,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAC9E,GAAG,EAAE,CAAC,GACL,WAAW,CAAC,CAAC,CAAC,CAEhB"}
@@ -1,7 +1,4 @@
1
- // ─── camelCase → --kebab-case ─────────────────────────────────────────────────
2
- function camelToKebab(s) {
3
- return s.replace(/([A-Z])/g, "-$1").toLowerCase();
4
- }
1
+ import { toKebab } from "../internal/kebab.js";
5
2
  // ─── Static props that must never be intercepted ──────────────────────────────
6
3
  const SKIP = new Set([
7
4
  "length", "name", "prototype", "constructor",
@@ -11,6 +8,8 @@ const SKIP = new Set([
11
8
  "__esModule", "then", "catch", "finally",
12
9
  ]);
13
10
  // ─── TokenSheet ───────────────────────────────────────────────────────────────
11
+ // Every static read of a token goes through the trap, so each name's reference is built once.
12
+ const refs = new Map();
14
13
  // eslint-disable-next-line @typescript-eslint/no-extraneous-class
15
14
  const _Base = class {
16
15
  };
@@ -39,11 +38,15 @@ const _Base = class {
39
38
  */
40
39
  export const TokenSheet = new Proxy(_Base, {
41
40
  get(target, prop, receiver) {
42
- if (typeof prop === "string" &&
43
- !SKIP.has(prop) &&
44
- !prop.startsWith("_") &&
45
- !prop.startsWith("$")) {
46
- return `var(--${camelToKebab(prop)})`;
41
+ if (typeof prop === "string") {
42
+ const ref = refs.get(prop);
43
+ if (ref !== undefined)
44
+ return ref;
45
+ if (!SKIP.has(prop) && !prop.startsWith("_") && !prop.startsWith("$")) {
46
+ const created = `var(--${toKebab(prop)})`;
47
+ refs.set(prop, created);
48
+ return created;
49
+ }
47
50
  }
48
51
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
49
52
  return Reflect.get(target, prop, receiver);
@@ -1 +1 @@
1
- {"version":3,"file":"token-sheet.js","sourceRoot":"","sources":["../../src/tokens/token-sheet.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;AACpD,CAAC;AAED,iFAAiF;AAEjF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa;IAC5C,UAAU,EAAE,SAAS,EAAE,gBAAgB;IACvC,gBAAgB,EAAE,eAAe,EAAE,sBAAsB;IACzD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW;IAC9C,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS;CACzC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,kEAAkE;AAClE,MAAM,KAAK,GAAG;CAAQ,CAAC;AAEvB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,KAAK,EAAE;IACzC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ;QACxB,IACE,OAAO,IAAI,KAAK,QAAQ;YACxB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YACf,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YACrB,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EACrB,CAAC;YACD,OAAO,SAAS,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC;QACxC,CAAC;QACD,+DAA+D;QAC/D,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;CACF,CAA2D,CAAC;AAwB7D,MAAM,UAAU,SAAS,CACvB,GAAM;IAEN,OAAO,GAAgC,CAAC;AAC1C,CAAC"}
1
+ {"version":3,"file":"token-sheet.js","sourceRoot":"","sources":["../../src/tokens/token-sheet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,iFAAiF;AAEjF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa;IAC5C,UAAU,EAAE,SAAS,EAAE,gBAAgB;IACvC,gBAAgB,EAAE,eAAe,EAAE,sBAAsB;IACzD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW;IAC9C,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS;CACzC,CAAC,CAAC;AAEH,iFAAiF;AAEjF,8FAA8F;AAC9F,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEvC,kEAAkE;AAClE,MAAM,KAAK,GAAG;CAAQ,CAAC;AAEvB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,KAAK,EAAE;IACzC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ;QACxB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,GAAG,KAAK,SAAS;gBAAE,OAAO,GAAG,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtE,MAAM,OAAO,GAAG,SAAS,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;gBAC1C,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBACxB,OAAO,OAAO,CAAC;YACjB,CAAC;QACH,CAAC;QACD,+DAA+D;QAC/D,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;CACF,CAA2D,CAAC;AAwB7D,MAAM,UAAU,SAAS,CACvB,GAAM;IAEN,OAAO,GAAgC,CAAC;AAC1C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisjs/css",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -18,16 +18,24 @@
18
18
  "types": "./dist/extract.d.ts"
19
19
  }
20
20
  },
21
+ "files": [
22
+ "dist",
23
+ "src",
24
+ "!dist/**/__tests__",
25
+ "!src/**/__tests__",
26
+ "CHANGELOG.md"
27
+ ],
21
28
  "devDependencies": {
22
29
  "typescript": "^6.0.3"
23
30
  },
24
31
  "dependencies": {
25
32
  "csstype": "^3.2.3",
26
- "@praxisjs/core": "2.2.0",
27
- "@praxisjs/decorators": "1.7.0"
33
+ "@praxisjs/core": "2.3.0",
34
+ "@praxisjs/decorators": "2.0.0"
28
35
  },
29
36
  "scripts": {
30
- "build": "tsc",
31
- "dev": "tsc --watch"
37
+ "build": "tsc -p tsconfig.build.json",
38
+ "dev": "tsc -p tsconfig.build.json --watch",
39
+ "typecheck": "tsc --noEmit"
32
40
  }
33
41
  }
@@ -1,3 +1,5 @@
1
+ import { declarations } from "../internal/kebab.js";
2
+
1
3
  import type { Properties } from "csstype";
2
4
 
3
5
  // ─── CSSProperties ────────────────────────────────────────────────────────────
@@ -113,92 +115,86 @@ export type CSSBuilder = string & {
113
115
 
114
116
  // ─── Runtime ──────────────────────────────────────────────────────────────────
115
117
 
116
- const CSS_BUILDER = Symbol("css-builder");
117
-
118
- export function isCSSBuilder(value: unknown): boolean {
119
- return typeof value === "object" && value !== null &&
120
- (value as Record<symbol, unknown>)[CSS_BUILDER] === true;
121
- }
122
-
123
- function toKebab(camel: string): string {
124
- return camel.replace(/([A-Z])/g, "-$1").toLowerCase();
118
+ type Props = CSSProperties;
119
+
120
+ // Every method lives once on the prototype: `this.css()` runs for each `$` field of every
121
+ // stylesheet, and a per-builder set of ~40 closures made each call allocate them all.
122
+ class CSSBuilderImpl {
123
+ readonly #parts: string[] = [];
124
+
125
+ constructor(props: Props) {
126
+ const root = declarations(props, "");
127
+ if (root) this.#parts.push(root);
128
+ }
129
+
130
+ #nest(selector: string, props: Props): this {
131
+ const css = declarations(props, "");
132
+ // Indents every line, including the continuation lines of a multi-line value.
133
+ if (css) this.#parts.push(`${selector} {\n ${css.replaceAll("\n", "\n ")}\n}`);
134
+ return this;
135
+ }
136
+
137
+ toString(): string {
138
+ return this.#parts.join("\n");
139
+ }
140
+
141
+ [Symbol.toPrimitive](): string {
142
+ return this.toString();
143
+ }
144
+
145
+ // Pseudo-classes — interaction
146
+ hover(p: Props): this { return this.#nest("&:hover", p); }
147
+ focus(p: Props): this { return this.#nest("&:focus", p); }
148
+ focusWithin(p: Props): this { return this.#nest("&:focus-within", p); }
149
+ focusVisible(p: Props): this { return this.#nest("&:focus-visible", p); }
150
+ active(p: Props): this { return this.#nest("&:active", p); }
151
+ visited(p: Props): this { return this.#nest("&:visited", p); }
152
+ target(p: Props): this { return this.#nest("&:target", p); }
153
+
154
+ // Pseudo-classes — form state
155
+ disabled(p: Props): this { return this.#nest('&:disabled, &[disabled], &[aria-disabled="true"]', p); }
156
+ enabled(p: Props): this { return this.#nest("&:enabled", p); }
157
+ checked(p: Props): this { return this.#nest("&:checked", p); }
158
+ indeterminate(p: Props): this { return this.#nest("&:indeterminate", p); }
159
+ required(p: Props): this { return this.#nest("&:required", p); }
160
+ optional(p: Props): this { return this.#nest("&:optional", p); }
161
+ valid(p: Props): this { return this.#nest("&:valid", p); }
162
+ invalid(p: Props): this { return this.#nest("&:invalid", p); }
163
+ readOnly(p: Props): this { return this.#nest("&:read-only", p); }
164
+
165
+ // Pseudo-classes — structural
166
+ first(p: Props): this { return this.#nest("&:first-child", p); }
167
+ last(p: Props): this { return this.#nest("&:last-child", p); }
168
+ nthChild(n: string | number, p: Props): this { return this.#nest(`&:nth-child(${String(n)})`, p); }
169
+ empty(p: Props): this { return this.#nest("&:empty", p); }
170
+
171
+ // Pseudo-classes — relational (Selectors Level 4)
172
+ has(sel: string, p: Props): this { return this.#nest(`&:has(${sel})`, p); }
173
+ is(sel: string, p: Props): this { return this.#nest(`&:is(${sel})`, p); }
174
+ where(sel: string, p: Props): this { return this.#nest(`&:where(${sel})`, p); }
175
+ not(sel: string, p: Props): this { return this.#nest(`&:not(${sel})`, p); }
176
+
177
+ // Pseudo-elements
178
+ before(p: Props): this { return this.#nest("&::before", p); }
179
+ after(p: Props): this { return this.#nest("&::after", p); }
180
+ placeholder(p: Props): this { return this.#nest("&::placeholder", p); }
181
+ selection(p: Props): this { return this.#nest("&::selection", p); }
182
+ firstLine(p: Props): this { return this.#nest("&::first-line", p); }
183
+ firstLetter(p: Props): this { return this.#nest("&::first-letter", p); }
184
+ marker(p: Props): this { return this.#nest("&::marker", p); }
185
+ backdrop(p: Props): this { return this.#nest("&::backdrop", p); }
186
+
187
+ // At-rules
188
+ media(q: string, p: Props): this { return this.#nest(`@media (${q})`, p); }
189
+ container(q: string, p: Props): this { return this.#nest(`@container (${q})`, p); }
190
+ supports(cond: string, p: Props): this { return this.#nest(`@supports (${cond})`, p); }
191
+ on(sel: string, p: Props): this { return this.#nest(sel, p); }
125
192
  }
126
193
 
127
- function propsToCSS(props: CSSProperties): string {
128
- return Object.entries(props)
129
- .filter(([, v]) => v != null)
130
- .map(([k, v]) => `${toKebab(k)}: ${String(v)};`)
131
- .join("\n");
194
+ export function isCSSBuilder(value: unknown): boolean {
195
+ return value instanceof CSSBuilderImpl;
132
196
  }
133
197
 
134
198
  export function createCSSBuilder(initialProps: CSSProperties): CSSBuilder {
135
- const parts: string[] = [];
136
- const rootCSS = propsToCSS(initialProps);
137
- if (rootCSS) parts.push(rootCSS);
138
-
139
- const addNested = (selector: string, props: CSSProperties): CSSBuilder => {
140
- const rawCSS = propsToCSS(props);
141
- if (!rawCSS) return builder;
142
- const inner = rawCSS.split("\n").map((l) => ` ${l}`).join("\n");
143
- parts.push(`${selector} {\n${inner}\n}`);
144
- return builder;
145
- };
146
-
147
- const toCSS = (): string => parts.join("\n");
148
-
149
- const builder = Object.assign(Object.create(null) as object, {
150
- [CSS_BUILDER]: true,
151
- toString: toCSS,
152
- [Symbol.toPrimitive]: toCSS,
153
-
154
- // Pseudo-classes — interaction
155
- hover: (p: CSSProperties) => addNested("&:hover", p),
156
- focus: (p: CSSProperties) => addNested("&:focus", p),
157
- focusWithin: (p: CSSProperties) => addNested("&:focus-within", p),
158
- focusVisible: (p: CSSProperties) => addNested("&:focus-visible", p),
159
- active: (p: CSSProperties) => addNested("&:active", p),
160
- visited: (p: CSSProperties) => addNested("&:visited", p),
161
- target: (p: CSSProperties) => addNested("&:target", p),
162
-
163
- // Pseudo-classes — form state
164
- disabled: (p: CSSProperties) => addNested('&:disabled, &[disabled], &[aria-disabled="true"]', p),
165
- enabled: (p: CSSProperties) => addNested("&:enabled", p),
166
- checked: (p: CSSProperties) => addNested("&:checked", p),
167
- indeterminate:(p: CSSProperties) => addNested("&:indeterminate", p),
168
- required: (p: CSSProperties) => addNested("&:required", p),
169
- optional: (p: CSSProperties) => addNested("&:optional", p),
170
- valid: (p: CSSProperties) => addNested("&:valid", p),
171
- invalid: (p: CSSProperties) => addNested("&:invalid", p),
172
- readOnly: (p: CSSProperties) => addNested("&:read-only", p),
173
-
174
- // Pseudo-classes — structural
175
- first: (p: CSSProperties) => addNested("&:first-child", p),
176
- last: (p: CSSProperties) => addNested("&:last-child", p),
177
- nthChild: (n: string | number, p: CSSProperties) => addNested(`&:nth-child(${String(n)})`, p),
178
- empty: (p: CSSProperties) => addNested("&:empty", p),
179
-
180
- // Pseudo-classes — relational (Selectors Level 4)
181
- has: (sel: string, p: CSSProperties) => addNested(`&:has(${sel})`, p),
182
- is: (sel: string, p: CSSProperties) => addNested(`&:is(${sel})`, p),
183
- where: (sel: string, p: CSSProperties) => addNested(`&:where(${sel})`, p),
184
- not: (sel: string, p: CSSProperties) => addNested(`&:not(${sel})`, p),
185
-
186
- // Pseudo-elements
187
- before: (p: CSSProperties) => addNested("&::before", p),
188
- after: (p: CSSProperties) => addNested("&::after", p),
189
- placeholder: (p: CSSProperties) => addNested("&::placeholder", p),
190
- selection: (p: CSSProperties) => addNested("&::selection", p),
191
- firstLine: (p: CSSProperties) => addNested("&::first-line", p),
192
- firstLetter: (p: CSSProperties) => addNested("&::first-letter", p),
193
- marker: (p: CSSProperties) => addNested("&::marker", p),
194
- backdrop: (p: CSSProperties) => addNested("&::backdrop", p),
195
-
196
- // At-rules
197
- media: (q: string, p: CSSProperties) => addNested(`@media (${q})`, p),
198
- container: (q: string, p: CSSProperties) => addNested(`@container (${q})`, p),
199
- supports: (cond: string, p: CSSProperties) => addNested(`@supports (${cond})`, p),
200
- on: (sel: string, p: CSSProperties) => addNested(sel, p),
201
- }) as unknown as CSSBuilder;
202
-
203
- return builder;
199
+ return new CSSBuilderImpl(initialProps) as unknown as CSSBuilder;
204
200
  }
@@ -1,26 +1,23 @@
1
1
  import { getCollector } from "../internal/collector.js";
2
2
  import { hashCSS } from "../internal/hash.js";
3
+ import { declarations } from "../internal/kebab.js";
4
+ import { STATIC_MODE } from "../internal/static-mode.js";
3
5
 
4
6
  import type { CSSProperties } from "./css-builder.js";
5
7
 
6
- // Defined by the Vite CSS plugin — skip DOM injection when CSS is static.
7
- declare const __PRAXIS_CSS_STATIC__: boolean | undefined;
8
- const STATIC_MODE: boolean =
9
- typeof __PRAXIS_CSS_STATIC__ !== "undefined" && __PRAXIS_CSS_STATIC__;
10
-
11
8
  type KeyframeStops = Record<string, CSSProperties>;
12
9
 
13
10
  const injected = new Set<string>();
14
11
 
15
- function toKebab(camel: string): string {
16
- return camel.replace(/([A-Z])/g, "-$1").toLowerCase();
17
- }
18
-
19
- function stopToCSS(props: CSSProperties): string {
20
- return Object.entries(props)
21
- .filter(([, v]) => v != null)
22
- .map(([k, v]) => ` ${toKebab(k)}: ${String(v)};`)
23
- .join("\n");
12
+ /** The scoped name and `@keyframes` rule for a set of stops — shared with build-time extraction. */
13
+ export function keyframesRule(name: string, stops: KeyframeStops): { name: string; css: string } {
14
+ let body = "";
15
+ for (const stop in stops) {
16
+ const block = ` ${stop} {\n${declarations(stops[stop], " ")}\n }`;
17
+ body = body === "" ? block : `${body}\n${block}`;
18
+ }
19
+ const scopedName = `prx-${name}-${hashCSS(body)}`;
20
+ return { name: scopedName, css: `@keyframes ${scopedName} {\n${body}\n}` };
24
21
  }
25
22
 
26
23
  /**
@@ -42,12 +39,7 @@ function stopToCSS(props: CSSProperties): string {
42
39
  * }
43
40
  */
44
41
  export function keyframes(name: string, stops: KeyframeStops): string {
45
- const stopsCSS = Object.entries(stops)
46
- .map(([stop, props]) => ` ${stop} {\n${stopToCSS(props)}\n }`)
47
- .join("\n");
48
-
49
- const scopedName = `prx-${name}-${hashCSS(stopsCSS)}`;
50
- const kfCSS = `@keyframes ${scopedName} {\n${stopsCSS}\n}`;
42
+ const { name: scopedName, css: kfCSS } = keyframesRule(name, stops);
51
43
 
52
44
  if (STATIC_MODE) return scopedName;
53
45