@pyreon/styler 0.11.1 → 0.11.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.
Files changed (36) hide show
  1. package/package.json +6 -5
  2. package/src/ThemeProvider.ts +37 -0
  3. package/src/__tests__/ThemeProvider.test.ts +67 -0
  4. package/src/__tests__/benchmark.bench.ts +189 -0
  5. package/src/__tests__/composition-chain.test.ts +489 -0
  6. package/src/__tests__/css.test.ts +70 -0
  7. package/src/__tests__/forward.test.ts +282 -0
  8. package/src/__tests__/globalStyle.test.ts +72 -0
  9. package/src/__tests__/hash.test.ts +70 -0
  10. package/src/__tests__/hybrid-injection.test.ts +205 -0
  11. package/src/__tests__/index.ts +14 -0
  12. package/src/__tests__/insertion-effect.test.ts +106 -0
  13. package/src/__tests__/integration.test.ts +149 -0
  14. package/src/__tests__/keyframes.test.ts +68 -0
  15. package/src/__tests__/memory-growth.test.ts +152 -0
  16. package/src/__tests__/p3-features.test.ts +258 -0
  17. package/src/__tests__/resolve.test.ts +249 -0
  18. package/src/__tests__/shared.test.ts +73 -0
  19. package/src/__tests__/sheet-advanced.test.ts +669 -0
  20. package/src/__tests__/sheet-split-atrules.test.ts +411 -0
  21. package/src/__tests__/sheet.test.ts +164 -0
  22. package/src/__tests__/styled-ssr.test.ts +67 -0
  23. package/src/__tests__/styled.test.ts +303 -0
  24. package/src/__tests__/theme.test.ts +33 -0
  25. package/src/__tests__/useCSS.test.ts +142 -0
  26. package/src/css.ts +13 -0
  27. package/src/forward.ts +276 -0
  28. package/src/globalStyle.ts +48 -0
  29. package/src/hash.ts +30 -0
  30. package/src/index.ts +15 -0
  31. package/src/keyframes.ts +36 -0
  32. package/src/resolve.ts +172 -0
  33. package/src/shared.ts +12 -0
  34. package/src/sheet.ts +387 -0
  35. package/src/styled.tsx +277 -0
  36. package/src/useCSS.ts +20 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/styler",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/pyreon/pyreon",
@@ -24,7 +24,8 @@
24
24
  "!lib/**/*.map",
25
25
  "!lib/analysis",
26
26
  "README.md",
27
- "LICENSE"
27
+ "LICENSE",
28
+ "src"
28
29
  ],
29
30
  "engines": {
30
31
  "node": ">= 22"
@@ -43,11 +44,11 @@
43
44
  "typecheck": "tsc --noEmit"
44
45
  },
45
46
  "peerDependencies": {
46
- "@pyreon/core": "^0.11.1",
47
- "@pyreon/reactivity": "^0.11.1"
47
+ "@pyreon/core": "^0.11.2",
48
+ "@pyreon/reactivity": "^0.11.2"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@vitus-labs/tools-rolldown": "^1.15.3",
51
- "@pyreon/typescript": "^0.11.1"
52
+ "@pyreon/typescript": "^0.11.2"
52
53
  }
53
54
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Theme context for styled components.
3
+ *
4
+ * Extensible theme interface. Consumers can augment this via module
5
+ * declaration merging for full strict types:
6
+ *
7
+ * declare module '@pyreon/styler' {
8
+ * interface DefaultTheme {
9
+ * colors: { primary: string; secondary: string }
10
+ * spacing: (n: number) => string
11
+ * }
12
+ * }
13
+ */
14
+ import type { VNode, VNodeChild } from "@pyreon/core"
15
+ import { createContext, provide, useContext } from "@pyreon/core"
16
+
17
+ // biome-ignore lint/suspicious/noEmptyInterface: augmentable via module declaration merging
18
+ export interface DefaultTheme {}
19
+
20
+ type Theme = DefaultTheme & Record<string, unknown>
21
+
22
+ export const ThemeContext = createContext<Theme>({} as Theme)
23
+
24
+ /** Hook to read the current theme from the nearest ThemeProvider. */
25
+ export const useTheme = <T extends object = Theme>(): T => useContext(ThemeContext) as T
26
+
27
+ /** Provides a theme object to all nested styled components via Pyreon context. */
28
+ export function ThemeProvider({
29
+ theme,
30
+ children,
31
+ }: {
32
+ theme: Theme
33
+ children?: VNodeChild
34
+ }): VNode | null {
35
+ provide(ThemeContext, theme)
36
+ return (children ?? null) as VNode | null
37
+ }
@@ -0,0 +1,67 @@
1
+ import { popContext } from "@pyreon/core"
2
+ import { afterEach, describe, expect, it } from "vitest"
3
+ import { ThemeContext, ThemeProvider, useTheme } from "../ThemeProvider"
4
+
5
+ describe("ThemeContext", () => {
6
+ it("is a Context object with an id", () => {
7
+ expect(ThemeContext).toBeDefined()
8
+ expect(ThemeContext.id).toBeDefined()
9
+ })
10
+
11
+ it("has a symbol id for context identification", () => {
12
+ expect(typeof ThemeContext.id).toBe("symbol")
13
+ })
14
+ })
15
+
16
+ describe("ThemeProvider", () => {
17
+ afterEach(() => {
18
+ try {
19
+ popContext()
20
+ } catch {
21
+ // Ignore if no context was pushed
22
+ }
23
+ })
24
+
25
+ it("returns children when provided", () => {
26
+ const children = "Hello world"
27
+ const result = ThemeProvider({ theme: {}, children })
28
+ expect(result).toBe("Hello world")
29
+ })
30
+
31
+ it("returns null when no children are provided", () => {
32
+ const result = ThemeProvider({ theme: {} })
33
+ expect(result).toBeNull()
34
+ })
35
+
36
+ it("returns null when children is undefined", () => {
37
+ const result = ThemeProvider({ theme: {}, children: undefined })
38
+ expect(result).toBeNull()
39
+ })
40
+
41
+ it("provides theme via context (useTheme returns it)", () => {
42
+ const theme = { colors: { primary: "red" }, spacing: 8 }
43
+ ThemeProvider({ theme, children: "child" })
44
+ const result = useTheme()
45
+ expect(result).toEqual(theme)
46
+ })
47
+ })
48
+
49
+ describe("useTheme", () => {
50
+ it("is a function", () => {
51
+ expect(typeof useTheme).toBe("function")
52
+ })
53
+
54
+ it("returns the default theme (empty object) when called outside a provider", () => {
55
+ const theme = useTheme()
56
+ expect(theme).toEqual({})
57
+ })
58
+
59
+ it("can be called with a type parameter", () => {
60
+ interface MyTheme {
61
+ primary: string
62
+ spacing: number
63
+ }
64
+ const theme = useTheme<MyTheme>()
65
+ expect(theme).toBeDefined()
66
+ })
67
+ })
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Benchmark: @pyreon/styler CSS-in-JS operations
3
+ *
4
+ * Run: bun vitest bench
5
+ *
6
+ * Measures core CSS-in-JS operations:
7
+ * 1. css() tagged template creation
8
+ * 2. css() with interpolations
9
+ * 3. Template resolution to CSS string
10
+ * 4. Dynamic function interpolation
11
+ * 5. Hash function throughput
12
+ * 6. Nested css() composition
13
+ * 7. styled() component factory
14
+ * 8. normalizeCSS — Comment Stripping & Cleanup
15
+ */
16
+ import { bench, describe } from "vitest"
17
+ import { css } from "../css"
18
+ import { hash } from "../hash"
19
+ import { normalizeCSS, resolve } from "../resolve"
20
+ import { styled } from "../styled"
21
+
22
+ // ============================================================================
23
+ // 1. CSS Tagged Template — Creation Speed
24
+ // ============================================================================
25
+ describe("css() tagged template creation", () => {
26
+ bench("@pyreon/styler", () => {
27
+ css`
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ padding: 16px;
32
+ margin: 8px;
33
+ background-color: #f0f0f0;
34
+ border-radius: 4px;
35
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
36
+ `
37
+ })
38
+ })
39
+
40
+ // ============================================================================
41
+ // 2. CSS Tagged Template with Interpolations
42
+ // ============================================================================
43
+ describe("css() with interpolations", () => {
44
+ const color = "#ff0000"
45
+ const size = "16px"
46
+
47
+ bench("@pyreon/styler", () => {
48
+ css`
49
+ color: ${color};
50
+ font-size: ${size};
51
+ display: flex;
52
+ padding: 8px;
53
+ `
54
+ })
55
+ })
56
+
57
+ // ============================================================================
58
+ // 3. Template Resolution (strings + values -> CSS string)
59
+ // ============================================================================
60
+ describe("template resolution to CSS string", () => {
61
+ const strings = Object.assign(["display: flex; color: ", "; font-size: ", "; padding: 8px;"], {
62
+ raw: ["display: flex; color: ", "; font-size: ", "; padding: 8px;"],
63
+ }) as unknown as TemplateStringsArray
64
+
65
+ const values = ["red", "16px"]
66
+ const props = { theme: { primary: "blue" } }
67
+
68
+ bench("@pyreon/styler resolve()", () => {
69
+ resolve(strings, values, props)
70
+ })
71
+ })
72
+
73
+ // ============================================================================
74
+ // 4. Dynamic Interpolation (function interpolations)
75
+ // ============================================================================
76
+ describe("dynamic function interpolation", () => {
77
+ const props = { theme: { primary: "blue", size: "14px" }, active: true }
78
+
79
+ const strings = Object.assign(["color: ", "; font-size: ", "; opacity: ", ";"], {
80
+ raw: ["color: ", "; font-size: ", "; opacity: ", ";"],
81
+ }) as unknown as TemplateStringsArray
82
+
83
+ const stylerValues = [
84
+ (p: any) => p.theme.primary,
85
+ (p: any) => p.theme.size,
86
+ (p: any) => (p.active ? "1" : "0.5"),
87
+ ]
88
+
89
+ bench("@pyreon/styler resolve()", () => {
90
+ resolve(strings, stylerValues, props)
91
+ })
92
+ })
93
+
94
+ // ============================================================================
95
+ // 5. Hash Function Throughput
96
+ // ============================================================================
97
+ describe("hash function throughput", () => {
98
+ const shortCSS = "display: flex; color: red;"
99
+ const mediumCSS =
100
+ "display: flex; align-items: center; justify-content: space-between; padding: 16px 24px; margin: 0 auto; max-width: 1200px; background-color: #ffffff; border: 1px solid #e0e0e0; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.12);"
101
+ const longCSS = mediumCSS.repeat(5)
102
+
103
+ bench("@pyreon/styler FNV-1a (short)", () => {
104
+ hash(shortCSS)
105
+ })
106
+
107
+ bench("@pyreon/styler FNV-1a (medium)", () => {
108
+ hash(mediumCSS)
109
+ })
110
+
111
+ bench("@pyreon/styler FNV-1a (long)", () => {
112
+ hash(longCSS)
113
+ })
114
+ })
115
+
116
+ // ============================================================================
117
+ // 6. Nested css() Composition
118
+ // ============================================================================
119
+ describe("nested css() composition", () => {
120
+ bench("@pyreon/styler", () => {
121
+ const base = css`display: flex; padding: 8px;`
122
+ const hover = css`background: #eee;`
123
+ const result = css`
124
+ ${base};
125
+ &:hover { ${hover}; }
126
+ color: red;
127
+ `
128
+ result.toString()
129
+ })
130
+ })
131
+
132
+ // ============================================================================
133
+ // 7. Styled Component Creation (factory call)
134
+ // ============================================================================
135
+ describe("styled() component factory", () => {
136
+ bench("@pyreon/styler", () => {
137
+ styled("div")`display: flex; color: red; padding: 8px;`
138
+ })
139
+ })
140
+
141
+ // ============================================================================
142
+ // 8. normalizeCSS — Comment Stripping & Cleanup
143
+ // ============================================================================
144
+ describe("normalizeCSS", () => {
145
+ const plain =
146
+ " display: flex; align-items: center; justify-content: center; padding: 16px; margin: 8px; background-color: #f0f0f0; border-radius: 4px; "
147
+
148
+ const withBlockComments = `
149
+ /* -------------------------------------------------------- */
150
+ /* BASE STATE */
151
+ /* -------------------------------------------------------- */
152
+ display: flex; align-items: center; justify-content: center;
153
+ padding: 16px; margin: 8px; background-color: #f0f0f0;
154
+ /* -------------------------------------------------------- */
155
+ /* HOVER STATE */
156
+ /* -------------------------------------------------------- */
157
+ &:hover { color: red; background: blue; }
158
+ /* -------------------------------------------------------- */
159
+ /* ACTIVE STATE */
160
+ /* -------------------------------------------------------- */
161
+ &:active { color: green; }
162
+ `
163
+
164
+ const withLineComments = `
165
+ // base styles
166
+ display: flex; align-items: center;
167
+ // hover override
168
+ &:hover { color: red; }
169
+ background: url(https://example.com/img.png);
170
+ `
171
+
172
+ const withSemicolonJunk = " ; display: flex;; ; color: red; ; font-size: 1rem;; ; "
173
+
174
+ bench("plain CSS (no comments)", () => {
175
+ normalizeCSS(plain)
176
+ })
177
+
178
+ bench("CSS with /* */ block comments", () => {
179
+ normalizeCSS(withBlockComments)
180
+ })
181
+
182
+ bench("CSS with // line comments", () => {
183
+ normalizeCSS(withLineComments)
184
+ })
185
+
186
+ bench("CSS with semicolon junk", () => {
187
+ normalizeCSS(withSemicolonJunk)
188
+ })
189
+ })