@pyreon/rocketstyle 0.12.12 → 0.12.14
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/rocketstyle",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.14",
|
|
4
4
|
"description": "Multi-dimensional style composition for Pyreon components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,20 +35,23 @@
|
|
|
35
35
|
"build:watch": "bun run vl_rolldown_build-watch",
|
|
36
36
|
"lint": "oxlint .",
|
|
37
37
|
"test": "vitest run",
|
|
38
|
+
"test:browser": "vitest run --config ./vitest.browser.config.ts",
|
|
38
39
|
"test:coverage": "vitest run --coverage",
|
|
39
40
|
"test:watch": "vitest",
|
|
40
41
|
"typecheck": "tsc --noEmit"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@pyreon/test-utils": "^0.12.10",
|
|
44
|
-
"@pyreon/typescript": "^0.12.
|
|
45
|
+
"@pyreon/typescript": "^0.12.14",
|
|
46
|
+
"@pyreon/ui-core": "^0.12.14",
|
|
47
|
+
"@vitest/browser-playwright": "^4.1.4",
|
|
45
48
|
"@vitus-labs/tools-rolldown": "^1.15.3"
|
|
46
49
|
},
|
|
47
50
|
"peerDependencies": {
|
|
48
|
-
"@pyreon/core": "^0.12.
|
|
49
|
-
"@pyreon/reactivity": "^0.12.
|
|
50
|
-
"@pyreon/styler": "^0.12.
|
|
51
|
-
"@pyreon/ui-core": "^0.12.
|
|
51
|
+
"@pyreon/core": "^0.12.14",
|
|
52
|
+
"@pyreon/reactivity": "^0.12.14",
|
|
53
|
+
"@pyreon/styler": "^0.12.14",
|
|
54
|
+
"@pyreon/ui-core": "^0.12.14"
|
|
52
55
|
},
|
|
53
56
|
"engines": {
|
|
54
57
|
"node": ">= 22"
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/** @jsxImportSource @pyreon/core */
|
|
2
|
+
import type { ComponentFn, VNodeChild } from '@pyreon/core'
|
|
3
|
+
import { h } from '@pyreon/core'
|
|
4
|
+
import { signal } from '@pyreon/reactivity'
|
|
5
|
+
import { sheet } from '@pyreon/styler'
|
|
6
|
+
import { mountInBrowser } from '@pyreon/test-utils/browser'
|
|
7
|
+
import { PyreonUI } from '@pyreon/ui-core'
|
|
8
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
9
|
+
import rocketstyle from '../init'
|
|
10
|
+
|
|
11
|
+
// Real-Chromium smoke for @pyreon/rocketstyle.
|
|
12
|
+
//
|
|
13
|
+
// Production usage wraps component functions (Element/Text/etc.), not
|
|
14
|
+
// string tags — so the base is a real ComponentFn here. This also
|
|
15
|
+
// satisfies the rocketstyle `ElementType` generic without `as any`.
|
|
16
|
+
|
|
17
|
+
const Base: ComponentFn<{ id?: string; children?: VNodeChild; class?: string }> = (
|
|
18
|
+
props,
|
|
19
|
+
) => h('div', props, props.children)
|
|
20
|
+
;(Base as ComponentFn & { displayName?: string }).displayName = 'Base'
|
|
21
|
+
|
|
22
|
+
describe('@pyreon/rocketstyle in real browser', () => {
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
sheet.clearCache()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('rocketstyle(Base) with .theme() applies the authored color in Chromium', () => {
|
|
28
|
+
const Box: any = rocketstyle()({ name: 'Box', component: Base })
|
|
29
|
+
.styles(
|
|
30
|
+
(css: any) => css`
|
|
31
|
+
color: ${({ $rocketstyle }: any) => $rocketstyle.color};
|
|
32
|
+
padding: 8px;
|
|
33
|
+
`,
|
|
34
|
+
)
|
|
35
|
+
.theme({ color: 'rgb(255, 0, 0)' })
|
|
36
|
+
|
|
37
|
+
const { container, unmount } = mountInBrowser(h(Box, { id: 'rs' }))
|
|
38
|
+
const el = container.querySelector<HTMLElement>('#rs')!
|
|
39
|
+
expect(el.className).toMatch(/pyr-/)
|
|
40
|
+
expect(getComputedStyle(el).color).toBe('rgb(255, 0, 0)')
|
|
41
|
+
expect(getComputedStyle(el).padding).toBe('8px')
|
|
42
|
+
unmount()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('the `state` prop swaps the resolved $rocketstyle theme', () => {
|
|
46
|
+
const Box: any = rocketstyle()({ name: 'StateBox', component: Base })
|
|
47
|
+
.styles(
|
|
48
|
+
(css: any) => css`
|
|
49
|
+
color: ${({ $rocketstyle }: any) => $rocketstyle.color};
|
|
50
|
+
`,
|
|
51
|
+
)
|
|
52
|
+
.theme({ color: 'rgb(255, 0, 0)' })
|
|
53
|
+
.states({ danger: { color: 'rgb(0, 0, 255)' } })
|
|
54
|
+
|
|
55
|
+
const base = mountInBrowser(h(Box, { id: 'b' }))
|
|
56
|
+
const danger = mountInBrowser(h(Box, { id: 'd', state: 'danger' }))
|
|
57
|
+
expect(getComputedStyle(base.container.querySelector<HTMLElement>('#b')!).color).toBe(
|
|
58
|
+
'rgb(255, 0, 0)',
|
|
59
|
+
)
|
|
60
|
+
expect(getComputedStyle(danger.container.querySelector<HTMLElement>('#d')!).color).toBe(
|
|
61
|
+
'rgb(0, 0, 255)',
|
|
62
|
+
)
|
|
63
|
+
base.unmount()
|
|
64
|
+
danger.unmount()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('reactive mode swap: classList changes in place via styler `isReactiveRS` effect (no remount)', async () => {
|
|
68
|
+
// Exercises the load-bearing `isReactiveRS` effect in
|
|
69
|
+
// styler/src/styled.tsx — when `$rocketstyle` is a function
|
|
70
|
+
// accessor, an effect tracks it and swaps classList in place.
|
|
71
|
+
// Mode switching is the canonical reactive path: PyreonUI
|
|
72
|
+
// provides a signal-backed mode, rocketstyle's
|
|
73
|
+
// `$rocketstyleAccessor` reads `themeAttrs.mode` (a getter on a
|
|
74
|
+
// ReactiveContext), and the styler effect observes the change.
|
|
75
|
+
//
|
|
76
|
+
// (Reactive *dimension props* like `state={stateSig()}` are NOT
|
|
77
|
+
// yet end-to-end reactive through rocketstyle's HOC chain — the
|
|
78
|
+
// inner spread in `rocketstyleAttrsHoc` collapses getter props
|
|
79
|
+
// to values. Mode is the only reactive axis that survives the
|
|
80
|
+
// spread because it flows via ReactiveContext, not via props.)
|
|
81
|
+
const modeSig = signal<'light' | 'dark'>('light')
|
|
82
|
+
|
|
83
|
+
const Box: any = rocketstyle()({ name: 'ModeSwapBox', component: Base })
|
|
84
|
+
.styles(
|
|
85
|
+
(css: any) => css`
|
|
86
|
+
color: ${({ $rocketstyle }: any) => $rocketstyle.color};
|
|
87
|
+
`,
|
|
88
|
+
)
|
|
89
|
+
.theme((_t: any, m: any) => ({
|
|
90
|
+
color: m('rgb(255, 0, 0)', 'rgb(0, 0, 255)'),
|
|
91
|
+
}))
|
|
92
|
+
|
|
93
|
+
const { container, unmount } = mountInBrowser(
|
|
94
|
+
h(PyreonUI, { theme: {}, mode: modeSig }, h(Box, { id: 'rx' })),
|
|
95
|
+
)
|
|
96
|
+
const el = container.querySelector<HTMLElement>('#rx')!
|
|
97
|
+
const classBefore = el.className
|
|
98
|
+
expect(getComputedStyle(el).color).toBe('rgb(255, 0, 0)')
|
|
99
|
+
|
|
100
|
+
modeSig.set('dark')
|
|
101
|
+
await new Promise((r) => setTimeout(r, 0))
|
|
102
|
+
await new Promise((r) => requestAnimationFrame(() => r(undefined)))
|
|
103
|
+
|
|
104
|
+
const classAfter = el.className
|
|
105
|
+
expect(getComputedStyle(el).color).toBe('rgb(0, 0, 255)')
|
|
106
|
+
// Class swapped in place — not a remount (same element reference,
|
|
107
|
+
// different class). This is the styler `isReactiveRS` effect
|
|
108
|
+
// doing `el.classList.remove(old); el.classList.add(new)`.
|
|
109
|
+
expect(classAfter).not.toBe(classBefore)
|
|
110
|
+
unmount()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('the `variant` prop layers on top of state', () => {
|
|
114
|
+
const Box: any = rocketstyle()({ name: 'VariantBox', component: Base })
|
|
115
|
+
.styles(
|
|
116
|
+
(css: any) => css`
|
|
117
|
+
color: ${({ $rocketstyle }: any) => $rocketstyle.color};
|
|
118
|
+
background-color: ${({ $rocketstyle }: any) => $rocketstyle.bg};
|
|
119
|
+
`,
|
|
120
|
+
)
|
|
121
|
+
.theme({ color: 'rgb(0, 0, 0)', bg: 'rgb(240, 240, 240)' })
|
|
122
|
+
.variants({ box: { bg: 'rgb(20, 30, 40)' } })
|
|
123
|
+
|
|
124
|
+
const { container, unmount } = mountInBrowser(
|
|
125
|
+
h(Box, { id: 'v', variant: 'box' }),
|
|
126
|
+
)
|
|
127
|
+
const el = container.querySelector<HTMLElement>('#v')!
|
|
128
|
+
expect(getComputedStyle(el).color).toBe('rgb(0, 0, 0)')
|
|
129
|
+
expect(getComputedStyle(el).backgroundColor).toBe('rgb(20, 30, 40)')
|
|
130
|
+
unmount()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('the `modifier` transform derives styles from the accumulated state theme', () => {
|
|
134
|
+
const Box: any = rocketstyle()({ name: 'ModBox', component: Base })
|
|
135
|
+
.styles(
|
|
136
|
+
(css: any) => css`
|
|
137
|
+
color: ${({ $rocketstyle }: any) => $rocketstyle.color};
|
|
138
|
+
background-color: ${({ $rocketstyle }: any) => $rocketstyle.bg};
|
|
139
|
+
`,
|
|
140
|
+
)
|
|
141
|
+
.theme({ color: 'rgb(255, 255, 255)', bg: 'rgb(0, 112, 243)' })
|
|
142
|
+
.states({ danger: { color: 'rgb(255, 255, 255)', bg: 'rgb(220, 53, 69)' } })
|
|
143
|
+
.modifiers({
|
|
144
|
+
outlined: (acc: any) => ({
|
|
145
|
+
color: acc.bg,
|
|
146
|
+
bg: 'rgb(255, 255, 255)',
|
|
147
|
+
}),
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const { container, unmount } = mountInBrowser(
|
|
151
|
+
h(Box, { id: 'm', state: 'danger', modifier: 'outlined' }),
|
|
152
|
+
)
|
|
153
|
+
const el = container.querySelector<HTMLElement>('#m')!
|
|
154
|
+
expect(getComputedStyle(el).color).toBe('rgb(220, 53, 69)')
|
|
155
|
+
expect(getComputedStyle(el).backgroundColor).toBe('rgb(255, 255, 255)')
|
|
156
|
+
unmount()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('m(light, dark) theme callback resolves per PyreonUI mode', () => {
|
|
160
|
+
const Box: any = rocketstyle()({ name: 'ModeBox', component: Base })
|
|
161
|
+
.styles(
|
|
162
|
+
(css: any) => css`
|
|
163
|
+
color: ${({ $rocketstyle }: any) => $rocketstyle.color};
|
|
164
|
+
`,
|
|
165
|
+
)
|
|
166
|
+
.theme((_t: any, m: any) => ({
|
|
167
|
+
color: m('rgb(12, 34, 56)', 'rgb(210, 220, 230)'),
|
|
168
|
+
}))
|
|
169
|
+
|
|
170
|
+
const light = mountInBrowser(
|
|
171
|
+
h(PyreonUI, { theme: {}, mode: 'light' }, h(Box, { id: 'lt' })),
|
|
172
|
+
)
|
|
173
|
+
const dark = mountInBrowser(
|
|
174
|
+
h(PyreonUI, { theme: {}, mode: 'dark' }, h(Box, { id: 'dk' })),
|
|
175
|
+
)
|
|
176
|
+
expect(getComputedStyle(light.container.querySelector<HTMLElement>('#lt')!).color).toBe(
|
|
177
|
+
'rgb(12, 34, 56)',
|
|
178
|
+
)
|
|
179
|
+
expect(getComputedStyle(dark.container.querySelector<HTMLElement>('#dk')!).color).toBe(
|
|
180
|
+
'rgb(210, 220, 230)',
|
|
181
|
+
)
|
|
182
|
+
light.unmount()
|
|
183
|
+
dark.unmount()
|
|
184
|
+
})
|
|
185
|
+
})
|