@praxisjs/css 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +21 -0
  3. package/dist/__tests__/css-builder.test.d.ts +2 -0
  4. package/dist/__tests__/css-builder.test.d.ts.map +1 -0
  5. package/dist/__tests__/css-builder.test.js +198 -0
  6. package/dist/__tests__/css-builder.test.js.map +1 -0
  7. package/dist/__tests__/cx.test.d.ts +2 -0
  8. package/dist/__tests__/cx.test.d.ts.map +1 -0
  9. package/dist/__tests__/cx.test.js +47 -0
  10. package/dist/__tests__/cx.test.js.map +1 -0
  11. package/dist/__tests__/decorators.test.d.ts +2 -0
  12. package/dist/__tests__/decorators.test.d.ts.map +1 -0
  13. package/dist/__tests__/decorators.test.js +268 -0
  14. package/dist/__tests__/decorators.test.js.map +1 -0
  15. package/dist/__tests__/extract.test.d.ts +2 -0
  16. package/dist/__tests__/extract.test.d.ts.map +1 -0
  17. package/dist/__tests__/extract.test.js +420 -0
  18. package/dist/__tests__/extract.test.js.map +1 -0
  19. package/dist/__tests__/global.test.d.ts +2 -0
  20. package/dist/__tests__/global.test.d.ts.map +1 -0
  21. package/dist/__tests__/global.test.js +117 -0
  22. package/dist/__tests__/global.test.js.map +1 -0
  23. package/dist/__tests__/keyframes.test.d.ts +2 -0
  24. package/dist/__tests__/keyframes.test.d.ts.map +1 -0
  25. package/dist/__tests__/keyframes.test.js +90 -0
  26. package/dist/__tests__/keyframes.test.js.map +1 -0
  27. package/dist/__tests__/preflight.test.d.ts +2 -0
  28. package/dist/__tests__/preflight.test.d.ts.map +1 -0
  29. package/dist/__tests__/preflight.test.js +55 -0
  30. package/dist/__tests__/preflight.test.js.map +1 -0
  31. package/dist/__tests__/registry.test.d.ts +2 -0
  32. package/dist/__tests__/registry.test.d.ts.map +1 -0
  33. package/dist/__tests__/registry.test.js +132 -0
  34. package/dist/__tests__/registry.test.js.map +1 -0
  35. package/dist/__tests__/style.test.d.ts +2 -0
  36. package/dist/__tests__/style.test.d.ts.map +1 -0
  37. package/dist/__tests__/style.test.js +257 -0
  38. package/dist/__tests__/style.test.js.map +1 -0
  39. package/dist/__tests__/styled.test.d.ts +2 -0
  40. package/dist/__tests__/styled.test.d.ts.map +1 -0
  41. package/dist/__tests__/styled.test.js +951 -0
  42. package/dist/__tests__/styled.test.js.map +1 -0
  43. package/dist/__tests__/theme-instance.test.d.ts +2 -0
  44. package/dist/__tests__/theme-instance.test.d.ts.map +1 -0
  45. package/dist/__tests__/theme-instance.test.js +317 -0
  46. package/dist/__tests__/theme-instance.test.js.map +1 -0
  47. package/dist/__tests__/theme-uninit.test.d.ts +2 -0
  48. package/dist/__tests__/theme-uninit.test.d.ts.map +1 -0
  49. package/dist/__tests__/theme-uninit.test.js +10 -0
  50. package/dist/__tests__/theme-uninit.test.js.map +1 -0
  51. package/dist/__tests__/token-sheet.test.d.ts +2 -0
  52. package/dist/__tests__/token-sheet.test.d.ts.map +1 -0
  53. package/dist/__tests__/token-sheet.test.js +74 -0
  54. package/dist/__tests__/token-sheet.test.js.map +1 -0
  55. package/dist/base/stylesheet.d.ts +30 -0
  56. package/dist/base/stylesheet.d.ts.map +1 -0
  57. package/dist/base/stylesheet.js +29 -0
  58. package/dist/base/stylesheet.js.map +1 -0
  59. package/dist/builder/css-builder.d.ts +98 -0
  60. package/dist/builder/css-builder.d.ts.map +1 -0
  61. package/dist/builder/css-builder.js +79 -0
  62. package/dist/builder/css-builder.js.map +1 -0
  63. package/dist/builder/keyframes.d.ts +23 -0
  64. package/dist/builder/keyframes.d.ts.map +1 -0
  65. package/dist/builder/keyframes.js +54 -0
  66. package/dist/builder/keyframes.js.map +1 -0
  67. package/dist/cx.d.ts +3 -0
  68. package/dist/cx.d.ts.map +1 -0
  69. package/dist/cx.js +23 -0
  70. package/dist/cx.js.map +1 -0
  71. package/dist/decorators/param.d.ts +23 -0
  72. package/dist/decorators/param.d.ts.map +1 -0
  73. package/dist/decorators/param.js +39 -0
  74. package/dist/decorators/param.js.map +1 -0
  75. package/dist/decorators/style.d.ts +15 -0
  76. package/dist/decorators/style.d.ts.map +1 -0
  77. package/dist/decorators/style.js +53 -0
  78. package/dist/decorators/style.js.map +1 -0
  79. package/dist/decorators/styled.d.ts +37 -0
  80. package/dist/decorators/styled.d.ts.map +1 -0
  81. package/dist/decorators/styled.js +138 -0
  82. package/dist/decorators/styled.js.map +1 -0
  83. package/dist/extract.d.ts +14 -0
  84. package/dist/extract.d.ts.map +1 -0
  85. package/dist/extract.js +124 -0
  86. package/dist/extract.js.map +1 -0
  87. package/dist/global.d.ts +27 -0
  88. package/dist/global.d.ts.map +1 -0
  89. package/dist/global.js +49 -0
  90. package/dist/global.js.map +1 -0
  91. package/dist/index.d.ts +11 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +10 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/internal/collector.d.ts +15 -0
  96. package/dist/internal/collector.d.ts.map +1 -0
  97. package/dist/internal/collector.js +33 -0
  98. package/dist/internal/collector.js.map +1 -0
  99. package/dist/internal/hash.d.ts +5 -0
  100. package/dist/internal/hash.d.ts.map +1 -0
  101. package/dist/internal/hash.js +11 -0
  102. package/dist/internal/hash.js.map +1 -0
  103. package/dist/internal/registry.d.ts +3 -0
  104. package/dist/internal/registry.d.ts.map +1 -0
  105. package/dist/internal/registry.js +41 -0
  106. package/dist/internal/registry.js.map +1 -0
  107. package/dist/preflight.d.ts +18 -0
  108. package/dist/preflight.d.ts.map +1 -0
  109. package/dist/preflight.js +191 -0
  110. package/dist/preflight.js.map +1 -0
  111. package/dist/server.d.ts +8 -0
  112. package/dist/server.d.ts.map +1 -0
  113. package/dist/server.js +8 -0
  114. package/dist/server.js.map +1 -0
  115. package/dist/tokens/decorators.d.ts +41 -0
  116. package/dist/tokens/decorators.d.ts.map +1 -0
  117. package/dist/tokens/decorators.js +73 -0
  118. package/dist/tokens/decorators.js.map +1 -0
  119. package/dist/tokens/index.d.ts +4 -0
  120. package/dist/tokens/index.d.ts.map +1 -0
  121. package/dist/tokens/index.js +4 -0
  122. package/dist/tokens/index.js.map +1 -0
  123. package/dist/tokens/theme-instance.d.ts +33 -0
  124. package/dist/tokens/theme-instance.d.ts.map +1 -0
  125. package/dist/tokens/theme-instance.js +130 -0
  126. package/dist/tokens/theme-instance.js.map +1 -0
  127. package/dist/tokens/token-sheet.d.ts +46 -0
  128. package/dist/tokens/token-sheet.d.ts.map +1 -0
  129. package/dist/tokens/token-sheet.js +55 -0
  130. package/dist/tokens/token-sheet.js.map +1 -0
  131. package/package.json +33 -0
  132. package/src/__tests__/css-builder.test.ts +230 -0
  133. package/src/__tests__/cx.test.ts +63 -0
  134. package/src/__tests__/decorators.test.ts +127 -0
  135. package/src/__tests__/extract.test.ts +418 -0
  136. package/src/__tests__/global.test.ts +140 -0
  137. package/src/__tests__/keyframes.test.ts +103 -0
  138. package/src/__tests__/preflight.test.ts +63 -0
  139. package/src/__tests__/registry.test.ts +151 -0
  140. package/src/__tests__/style.test.ts +153 -0
  141. package/src/__tests__/styled.test.ts +427 -0
  142. package/src/__tests__/theme-instance.test.ts +374 -0
  143. package/src/__tests__/theme-uninit.test.ts +12 -0
  144. package/src/__tests__/token-sheet.test.ts +87 -0
  145. package/src/base/stylesheet.ts +35 -0
  146. package/src/builder/css-builder.ts +204 -0
  147. package/src/builder/keyframes.ts +66 -0
  148. package/src/cx.ts +26 -0
  149. package/src/decorators/param.ts +40 -0
  150. package/src/decorators/style.ts +55 -0
  151. package/src/decorators/styled.ts +167 -0
  152. package/src/extract.ts +133 -0
  153. package/src/global.ts +60 -0
  154. package/src/index.ts +10 -0
  155. package/src/internal/collector.ts +38 -0
  156. package/src/internal/hash.ts +10 -0
  157. package/src/internal/registry.ts +51 -0
  158. package/src/preflight.ts +192 -0
  159. package/src/server.ts +12 -0
  160. package/src/tokens/decorators.ts +108 -0
  161. package/src/tokens/index.ts +3 -0
  162. package/src/tokens/theme-instance.ts +149 -0
  163. package/src/tokens/token-sheet.ts +86 -0
  164. package/tsconfig.json +8 -0
package/dist/server.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Server-side / build-time API for the `@praxisjs/vite-plugin` CSS extractor.
3
+ *
4
+ * Import this at `@praxisjs/css/server` — it is NOT part of the browser bundle.
5
+ * Using it in browser code has no effect and the tree-shaker will remove it.
6
+ */
7
+ export { setCollector, getCollector, createCollector, } from "./internal/collector.js";
8
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,eAAe,GAEhB,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { theme, ThemeInstance, type ThemedConfig } from "./theme-instance.js";
2
+ /**
3
+ * Class decorator that installs the design token system on the root component.
4
+ *
5
+ * - `skeleton` — `TokenSheet` subclass that declares token names. Its static
6
+ * properties (`AppTokens.colorPrimary`) return CSS var references.
7
+ * - `DefaultTheme` — concrete class (extends skeleton) that provides the
8
+ * initial CSS custom property values injected on `:root`.
9
+ * - `config.persist` — save the active theme to `localStorage` and restore it
10
+ * on the next page load.
11
+ * - `config.syncTabs` — broadcast theme changes to other browser tabs via
12
+ * `BroadcastChannel`.
13
+ *
14
+ * Must appear above `@Component()` in the decorator stack.
15
+ *
16
+ * @example
17
+ * @Themed(AppTokens, LightTheme, { persist: true, syncTabs: true })
18
+ * @Component()
19
+ * class App extends StatefulComponent { ... }
20
+ */
21
+ export declare function Themed(_skeleton: abstract new (...args: unknown[]) => unknown, DefaultTheme: new () => unknown, config?: Partial<ThemedConfig>): any;
22
+ /**
23
+ * Field decorator that injects the active {@link ThemeInstance} into a
24
+ * component field. Use `.switch(ThemeClass)` to change the theme.
25
+ *
26
+ * Equivalent to the programmatic {@link theme} function — choose whichever
27
+ * fits the component's style.
28
+ *
29
+ * @example
30
+ * @Component()
31
+ * class Header extends StatefulComponent {
32
+ * @Theme() theme!: ThemeInstance
33
+ *
34
+ * render() {
35
+ * return <button onClick={() => this.theme.switch(DarkTheme)}>Dark</button>
36
+ * }
37
+ * }
38
+ */
39
+ export declare function Theme(): (_value: undefined, context: ClassFieldDecoratorContext<any>) => void;
40
+ export { theme, ThemeInstance, type ThemedConfig };
41
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,73 @@
1
+ import { createClassDecorator, ClassBehavior, createFieldDecorator, } from "@praxisjs/decorators";
2
+ import { createThemeInstance, theme, ThemeInstance, } from "./theme-instance.js";
3
+ // ─── @Themed ─────────────────────────────────────────────────────────────────
4
+ class ThemedBehavior extends ClassBehavior {
5
+ constructor(DefaultTheme, config) {
6
+ super();
7
+ this.DefaultTheme = DefaultTheme;
8
+ this.config = config;
9
+ }
10
+ create(_instance) {
11
+ createThemeInstance(this.DefaultTheme, this.config);
12
+ return {};
13
+ }
14
+ initialize(_Enhanced, _original) {
15
+ createThemeInstance(this.DefaultTheme, this.config);
16
+ }
17
+ }
18
+ /**
19
+ * Class decorator that installs the design token system on the root component.
20
+ *
21
+ * - `skeleton` — `TokenSheet` subclass that declares token names. Its static
22
+ * properties (`AppTokens.colorPrimary`) return CSS var references.
23
+ * - `DefaultTheme` — concrete class (extends skeleton) that provides the
24
+ * initial CSS custom property values injected on `:root`.
25
+ * - `config.persist` — save the active theme to `localStorage` and restore it
26
+ * on the next page load.
27
+ * - `config.syncTabs` — broadcast theme changes to other browser tabs via
28
+ * `BroadcastChannel`.
29
+ *
30
+ * Must appear above `@Component()` in the decorator stack.
31
+ *
32
+ * @example
33
+ * @Themed(AppTokens, LightTheme, { persist: true, syncTabs: true })
34
+ * @Component()
35
+ * class App extends StatefulComponent { ... }
36
+ */
37
+ export function Themed(_skeleton, DefaultTheme, config) {
38
+ return createClassDecorator(new ThemedBehavior(DefaultTheme, config ?? {}));
39
+ }
40
+ // ─── @Theme ───────────────────────────────────────────────────────────────────
41
+ /**
42
+ * Field decorator that injects the active {@link ThemeInstance} into a
43
+ * component field. Use `.switch(ThemeClass)` to change the theme.
44
+ *
45
+ * Equivalent to the programmatic {@link theme} function — choose whichever
46
+ * fits the component's style.
47
+ *
48
+ * @example
49
+ * @Component()
50
+ * class Header extends StatefulComponent {
51
+ * @Theme() theme!: ThemeInstance
52
+ *
53
+ * render() {
54
+ * return <button onClick={() => this.theme.switch(DarkTheme)}>Dark</button>
55
+ * }
56
+ * }
57
+ */
58
+ export function Theme() {
59
+ return createFieldDecorator({
60
+ bind(_instance, _name, _initialValue) {
61
+ return {
62
+ descriptor: {
63
+ get() { return theme(); },
64
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
65
+ set() { },
66
+ },
67
+ };
68
+ },
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ });
71
+ }
72
+ export { theme, ThemeInstance };
73
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +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;IACxC,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"}
@@ -0,0 +1,4 @@
1
+ export { TokenSheet, tokenVars } from "./token-sheet.js";
2
+ export { ThemeInstance, theme, type ThemedConfig } from "./theme-instance.js";
3
+ export { Themed, Theme } from "./decorators.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tokens/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { TokenSheet, tokenVars } from "./token-sheet.js";
2
+ export { ThemeInstance, theme } from "./theme-instance.js";
3
+ export { Themed, Theme } from "./decorators.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tokens/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,KAAK,EAAqB,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,33 @@
1
+ export interface ThemedConfig {
2
+ persist: boolean;
3
+ syncTabs: boolean;
4
+ }
5
+ /**
6
+ * Manages the active design token theme: injects CSS custom properties on
7
+ * `:root`, persists to `localStorage`, and synchronises across browser tabs
8
+ * via `BroadcastChannel`.
9
+ *
10
+ * Obtain the app-wide instance via {@link theme}. Use {@link ThemeInstance.switch}
11
+ * to swap to any class that extends the skeleton `TokenSheet`.
12
+ */
13
+ export declare class ThemeInstance {
14
+ #private;
15
+ constructor(DefaultTheme: new () => unknown, config?: Partial<ThemedConfig>);
16
+ /** The currently active theme instance. */
17
+ get current(): unknown;
18
+ /**
19
+ * Switches the active theme. Instantiates `ThemeClass`, updates `:root` CSS
20
+ * vars, persists to `localStorage` (if `persist` is enabled), and broadcasts
21
+ * to other tabs (if `syncTabs` is enabled).
22
+ */
23
+ switch(ThemeClass: new () => unknown): void;
24
+ /** Removes the injected `<style>` and closes the BroadcastChannel. */
25
+ destroy(): void;
26
+ }
27
+ export declare function createThemeInstance(DefaultTheme: new () => unknown, config?: Partial<ThemedConfig>): ThemeInstance;
28
+ /**
29
+ * Returns the app-wide {@link ThemeInstance}.
30
+ * Throws if `@Themed()` has not been applied to the root component.
31
+ */
32
+ export declare function theme(): ThemeInstance;
33
+ //# sourceMappingURL=theme-instance.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,130 @@
1
+ import { getCollector } from "../internal/collector.js";
2
+ // ─── helpers ──────────────────────────────────────────────────────────────────
3
+ function camelToKebab(s) {
4
+ return s.replace(/([A-Z])/g, "-$1").toLowerCase();
5
+ }
6
+ function instanceToVars(instance) {
7
+ const result = {};
8
+ for (const [key, val] of Object.entries(instance)) {
9
+ if (typeof val === "string" || typeof val === "number") {
10
+ result[`--${camelToKebab(key)}`] = String(val);
11
+ }
12
+ }
13
+ return result;
14
+ }
15
+ function varsToCSS(vars) {
16
+ const body = Object.entries(vars)
17
+ .map(([k, v]) => ` ${k}: ${v};`)
18
+ .join("\n");
19
+ return `:root {\n${body}\n}`;
20
+ }
21
+ function applyVarsToRoot(vars) {
22
+ const col = getCollector();
23
+ if (col) {
24
+ col.inject("theme", varsToCSS(vars));
25
+ return;
26
+ }
27
+ if (typeof document === "undefined")
28
+ return;
29
+ let el = document.querySelector("style[data-praxis-theme]");
30
+ if (!el) {
31
+ el = document.createElement("style");
32
+ el.setAttribute("data-praxis-theme", "");
33
+ document.head.appendChild(el);
34
+ }
35
+ el.textContent = varsToCSS(vars);
36
+ }
37
+ const STORAGE_KEY = "praxis-theme";
38
+ const BROADCAST_KEY = "praxis-theme";
39
+ // ─── ThemeInstance ────────────────────────────────────────────────────────────
40
+ /**
41
+ * Manages the active design token theme: injects CSS custom properties on
42
+ * `:root`, persists to `localStorage`, and synchronises across browser tabs
43
+ * via `BroadcastChannel`.
44
+ *
45
+ * Obtain the app-wide instance via {@link theme}. Use {@link ThemeInstance.switch}
46
+ * to swap to any class that extends the skeleton `TokenSheet`.
47
+ */
48
+ export class ThemeInstance {
49
+ #current;
50
+ #config;
51
+ #channel = null;
52
+ constructor(DefaultTheme, config = {}) {
53
+ this.#config = { persist: false, syncTabs: false, ...config };
54
+ this.#current = new DefaultTheme();
55
+ 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
+ }
61
+ }
62
+ /** The currently active theme instance. */
63
+ get current() {
64
+ return this.#current;
65
+ }
66
+ /**
67
+ * Switches the active theme. Instantiates `ThemeClass`, updates `:root` CSS
68
+ * vars, persists to `localStorage` (if `persist` is enabled), and broadcasts
69
+ * to other tabs (if `syncTabs` is enabled).
70
+ */
71
+ switch(ThemeClass) {
72
+ this.#current = new ThemeClass();
73
+ const vars = instanceToVars(this.#current);
74
+ if (typeof document !== "undefined")
75
+ applyVarsToRoot(vars);
76
+ if (this.#config.persist)
77
+ this.#saveToStorage(vars);
78
+ if (this.#config.syncTabs)
79
+ this.#channel?.postMessage({ vars });
80
+ }
81
+ /** Removes the injected `<style>` and closes the BroadcastChannel. */
82
+ destroy() {
83
+ this.#channel?.close();
84
+ this.#channel = null;
85
+ if (typeof document !== "undefined") {
86
+ document.querySelector("style[data-praxis-theme]")?.remove();
87
+ }
88
+ }
89
+ #saveToStorage(vars) {
90
+ try {
91
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(vars));
92
+ }
93
+ catch { /* noop */ }
94
+ }
95
+ #loadFromStorage() {
96
+ try {
97
+ const raw = localStorage.getItem(STORAGE_KEY);
98
+ return raw ? JSON.parse(raw) : null;
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }
104
+ #setupBroadcast() {
105
+ if (typeof BroadcastChannel === "undefined")
106
+ return;
107
+ this.#channel = new BroadcastChannel(BROADCAST_KEY);
108
+ this.#channel.addEventListener("message", (e) => {
109
+ if (e.data.vars)
110
+ applyVarsToRoot(e.data.vars);
111
+ });
112
+ }
113
+ }
114
+ // ─── Singleton ────────────────────────────────────────────────────────────────
115
+ let _instance = null;
116
+ export function createThemeInstance(DefaultTheme, config) {
117
+ _instance = new ThemeInstance(DefaultTheme, config);
118
+ return _instance;
119
+ }
120
+ /**
121
+ * Returns the app-wide {@link ThemeInstance}.
122
+ * Throws if `@Themed()` has not been applied to the root component.
123
+ */
124
+ export function theme() {
125
+ if (!_instance) {
126
+ throw new Error("[PraxisJS] theme() called before @Themed() was applied to the root component.");
127
+ }
128
+ return _instance;
129
+ }
130
+ //# sourceMappingURL=theme-instance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme-instance.js","sourceRoot":"","sources":["../../src/tokens/theme-instance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Base class for token skeletons and theme value classes.
3
+ *
4
+ * Extend to declare token names (skeleton) or token values (theme).
5
+ * Static property access returns CSS custom property references at any level
6
+ * of the inheritance chain: `AppTokens.colorPrimary` → `'var(--color-primary)'`.
7
+ *
8
+ * @example — skeleton (names only)
9
+ * class AppTokens extends TokenSheet {
10
+ * colorPrimary!: string
11
+ * spaceMd!: string
12
+ * }
13
+ *
14
+ * @example — theme (values)
15
+ * class LightTheme extends AppTokens {
16
+ * colorPrimary = '#3b82f6'
17
+ * spaceMd = '16px'
18
+ * }
19
+ *
20
+ * class DarkTheme extends LightTheme {
21
+ * colorPrimary = '#60a5fa' // only overrides what changes
22
+ * }
23
+ */
24
+ export declare const TokenSheet: abstract new (...args: unknown[]) => object;
25
+ /**
26
+ * Returns a typed accessor for CSS custom property references derived from a
27
+ * {@link TokenSheet} subclass. Each key returns `'var(--token-name)'`.
28
+ *
29
+ * Use this in `Stylesheet` definitions to get full TypeScript autocomplete
30
+ * without casts.
31
+ *
32
+ * @example
33
+ * const t = tokenVars(AppTokens)
34
+ * // t.colorPrimary → 'var(--color-primary)'
35
+ * // t.spaceMd → 'var(--space-md)'
36
+ *
37
+ * class BtnStyles extends Stylesheet {
38
+ * $root = this.css({ background: t.colorPrimary, padding: t.spaceMd })
39
+ * }
40
+ */
41
+ type TokenVarMap<T extends abstract new (...args: unknown[]) => unknown> = {
42
+ readonly [K in keyof InstanceType<T> as K extends string ? K : never]: string;
43
+ };
44
+ export declare function tokenVars<T extends abstract new (...args: unknown[]) => unknown>(cls: T): TokenVarMap<T>;
45
+ export {};
46
+ //# sourceMappingURL=token-sheet.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,55 @@
1
+ // ─── camelCase → --kebab-case ─────────────────────────────────────────────────
2
+ function camelToKebab(s) {
3
+ return s.replace(/([A-Z])/g, "-$1").toLowerCase();
4
+ }
5
+ // ─── Static props that must never be intercepted ──────────────────────────────
6
+ const SKIP = new Set([
7
+ "length", "name", "prototype", "constructor",
8
+ "toString", "valueOf", "toLocaleString",
9
+ "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable",
10
+ "call", "apply", "bind", "caller", "arguments",
11
+ "__esModule", "then", "catch", "finally",
12
+ ]);
13
+ // ─── TokenSheet ───────────────────────────────────────────────────────────────
14
+ // eslint-disable-next-line @typescript-eslint/no-extraneous-class
15
+ const _Base = class {
16
+ };
17
+ /**
18
+ * Base class for token skeletons and theme value classes.
19
+ *
20
+ * Extend to declare token names (skeleton) or token values (theme).
21
+ * Static property access returns CSS custom property references at any level
22
+ * of the inheritance chain: `AppTokens.colorPrimary` → `'var(--color-primary)'`.
23
+ *
24
+ * @example — skeleton (names only)
25
+ * class AppTokens extends TokenSheet {
26
+ * colorPrimary!: string
27
+ * spaceMd!: string
28
+ * }
29
+ *
30
+ * @example — theme (values)
31
+ * class LightTheme extends AppTokens {
32
+ * colorPrimary = '#3b82f6'
33
+ * spaceMd = '16px'
34
+ * }
35
+ *
36
+ * class DarkTheme extends LightTheme {
37
+ * colorPrimary = '#60a5fa' // only overrides what changes
38
+ * }
39
+ */
40
+ export const TokenSheet = new Proxy(_Base, {
41
+ get(target, prop, receiver) {
42
+ if (typeof prop === "string" &&
43
+ !SKIP.has(prop) &&
44
+ !prop.startsWith("_") &&
45
+ !prop.startsWith("$")) {
46
+ return `var(--${camelToKebab(prop)})`;
47
+ }
48
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
49
+ return Reflect.get(target, prop, receiver);
50
+ },
51
+ });
52
+ export function tokenVars(cls) {
53
+ return cls;
54
+ }
55
+ //# sourceMappingURL=token-sheet.js.map
@@ -0,0 +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"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@praxisjs/css",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ },
12
+ "./server": {
13
+ "import": "./dist/server.js",
14
+ "types": "./dist/server.d.ts"
15
+ },
16
+ "./extract": {
17
+ "import": "./dist/extract.js",
18
+ "types": "./dist/extract.d.ts"
19
+ }
20
+ },
21
+ "devDependencies": {
22
+ "typescript": "^5.9.3"
23
+ },
24
+ "dependencies": {
25
+ "csstype": "^3.2.3",
26
+ "@praxisjs/core": "1.7.0",
27
+ "@praxisjs/decorators": "1.1.1"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "dev": "tsc --watch"
32
+ }
33
+ }