@pyreon/rocketstyle 0.11.6 → 0.11.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js
CHANGED
|
@@ -175,10 +175,11 @@ const createLocalProvider = (WrappedComponent) => {
|
|
|
175
175
|
if (onBlur) onBlur(e);
|
|
176
176
|
}
|
|
177
177
|
};
|
|
178
|
+
const resolvedState = typeof $rocketstate === "function" ? $rocketstate() : $rocketstate;
|
|
178
179
|
const updatedState = {
|
|
179
|
-
|
|
180
|
+
...resolvedState,
|
|
180
181
|
pseudo: {
|
|
181
|
-
|
|
182
|
+
...resolvedState?.pseudo,
|
|
182
183
|
get hover() {
|
|
183
184
|
return hover();
|
|
184
185
|
},
|
|
@@ -497,14 +498,6 @@ const rocketComponent = (options) => {
|
|
|
497
498
|
const EnhancedComponent = (props) => {
|
|
498
499
|
const localCtx = useLocalContext(options.consumer);
|
|
499
500
|
const themeAttrs = useThemeAttrs(options);
|
|
500
|
-
const { pseudo, ...mergeProps } = {
|
|
501
|
-
...localCtx,
|
|
502
|
-
...props
|
|
503
|
-
};
|
|
504
|
-
const pseudoRocketstate = {
|
|
505
|
-
...pseudo,
|
|
506
|
-
...pick(props, [...PSEUDO_KEYS, ...PSEUDO_META_KEYS])
|
|
507
|
-
};
|
|
508
501
|
const theme = themeAttrs.theme;
|
|
509
502
|
const baseThemeHelper = ThemeManager$1.baseTheme;
|
|
510
503
|
if (!baseThemeHelper.has(theme)) baseThemeHelper.set(theme, getThemeFromChain(options.theme, theme));
|
|
@@ -517,16 +510,12 @@ const rocketComponent = (options) => {
|
|
|
517
510
|
useBooleans: options.useBooleans
|
|
518
511
|
});
|
|
519
512
|
const RESERVED_STYLING_PROPS_KEYS = Object.keys(reservedPropNames);
|
|
520
|
-
const rocketstate = _calculateStylingAttrs({
|
|
521
|
-
props: pickStyledAttrs(mergeProps, reservedPropNames),
|
|
522
|
-
dimensions
|
|
523
|
-
});
|
|
524
|
-
const finalRocketstate = {
|
|
525
|
-
...rocketstate,
|
|
526
|
-
pseudo: pseudoRocketstate
|
|
527
|
-
};
|
|
528
513
|
const $rocketstyleAccessor = () => {
|
|
529
514
|
const mode = themeAttrs.mode;
|
|
515
|
+
const rocketstate = _calculateStylingAttrs({
|
|
516
|
+
props: pickStyledAttrs(props, reservedPropNames),
|
|
517
|
+
dimensions
|
|
518
|
+
});
|
|
530
519
|
const modeBaseHelper = ThemeManager$1.modeBaseTheme[mode];
|
|
531
520
|
if (!modeBaseHelper.has(baseTheme)) modeBaseHelper.set(baseTheme, getThemeByMode(baseTheme, mode));
|
|
532
521
|
const currentModeBaseTheme = modeBaseHelper.get(baseTheme);
|
|
@@ -540,6 +529,24 @@ const rocketComponent = (options) => {
|
|
|
540
529
|
appTheme: theme
|
|
541
530
|
});
|
|
542
531
|
};
|
|
532
|
+
const localPseudo = localCtx?.pseudo;
|
|
533
|
+
const propPseudo = pick(props, [...PSEUDO_KEYS, ...PSEUDO_META_KEYS]);
|
|
534
|
+
const $rocketstateAccessor = () => {
|
|
535
|
+
return {
|
|
536
|
+
..._calculateStylingAttrs({
|
|
537
|
+
props: pickStyledAttrs(props, reservedPropNames),
|
|
538
|
+
dimensions
|
|
539
|
+
}),
|
|
540
|
+
pseudo: {
|
|
541
|
+
...localPseudo,
|
|
542
|
+
...propPseudo
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
const { pseudo: _pseudo, ...mergeProps } = {
|
|
547
|
+
...localCtx,
|
|
548
|
+
...props
|
|
549
|
+
};
|
|
543
550
|
const finalProps = {
|
|
544
551
|
...omit(mergeProps, [
|
|
545
552
|
...RESERVED_STYLING_PROPS_KEYS,
|
|
@@ -549,14 +556,14 @@ const rocketComponent = (options) => {
|
|
|
549
556
|
...options.passProps ? pick(mergeProps, options.passProps) : {},
|
|
550
557
|
ref: props.ref,
|
|
551
558
|
$rocketstyle: $rocketstyleAccessor,
|
|
552
|
-
$rocketstate:
|
|
559
|
+
$rocketstate: $rocketstateAccessor
|
|
553
560
|
};
|
|
554
561
|
if (process.env.NODE_ENV !== "production") {
|
|
555
562
|
finalProps["data-rocketstyle"] = componentName;
|
|
556
563
|
if (options.DEBUG) {
|
|
557
564
|
const debugPayload = {
|
|
558
565
|
component: componentName,
|
|
559
|
-
rocketstate:
|
|
566
|
+
rocketstate: $rocketstateAccessor(),
|
|
560
567
|
rocketstyle: $rocketstyleAccessor(),
|
|
561
568
|
dimensions,
|
|
562
569
|
mode: themeAttrs.mode,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/rocketstyle",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.8",
|
|
4
4
|
"description": "Multi-dimensional style composition for Pyreon components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
"typecheck": "tsc --noEmit"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@pyreon/test-utils": "^0.11.
|
|
45
|
-
"@pyreon/typescript": "^0.11.
|
|
44
|
+
"@pyreon/test-utils": "^0.11.8",
|
|
45
|
+
"@pyreon/typescript": "^0.11.8",
|
|
46
46
|
"@vitus-labs/tools-rolldown": "^1.15.3"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@pyreon/core": "^0.11.
|
|
50
|
-
"@pyreon/reactivity": "^0.11.
|
|
51
|
-
"@pyreon/styler": "^0.11.
|
|
52
|
-
"@pyreon/ui-core": "^0.11.
|
|
49
|
+
"@pyreon/core": "^0.11.8",
|
|
50
|
+
"@pyreon/reactivity": "^0.11.8",
|
|
51
|
+
"@pyreon/styler": "^0.11.8",
|
|
52
|
+
"@pyreon/ui-core": "^0.11.8"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">= 22"
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Unlike the React version which tested CSS injection in the DOM,
|
|
8
8
|
* this Pyreon version tests the computed $rocketstyle output directly.
|
|
9
9
|
*/
|
|
10
|
-
import { ThemeCapture, getComputedTheme, initTestConfig } from '@pyreon/test-utils'
|
|
10
|
+
import { ThemeCapture, getComputedTheme, initTestConfig, withThemeContext } from '@pyreon/test-utils'
|
|
11
11
|
import rocketstyle from '../init'
|
|
12
12
|
|
|
13
13
|
let cleanup: () => void
|
|
@@ -234,3 +234,67 @@ describe('e2e: rocketstyle theme computation', () => {
|
|
|
234
234
|
expect(theme.sawStep).toBe('one')
|
|
235
235
|
})
|
|
236
236
|
})
|
|
237
|
+
|
|
238
|
+
// ── Reactive dimension props ──────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
describe('reactive $rocketstyle accessor', () => {
|
|
241
|
+
it('$rocketstyleAccessor resolves different themes for different dimension props', () => {
|
|
242
|
+
const Comp: any = rocketstyle()({
|
|
243
|
+
name: 'ReactiveComp',
|
|
244
|
+
component: ThemeCapture,
|
|
245
|
+
})
|
|
246
|
+
.theme({ color: 'black', bg: 'white' })
|
|
247
|
+
.states({
|
|
248
|
+
primary: { color: 'blue' },
|
|
249
|
+
secondary: { color: 'green' },
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// First call with state=primary
|
|
253
|
+
const theme1 = getComputedTheme(Comp, { state: 'primary' })
|
|
254
|
+
expect(theme1.color).toBe('blue')
|
|
255
|
+
|
|
256
|
+
// Second call with state=secondary — should produce different theme
|
|
257
|
+
const theme2 = getComputedTheme(Comp, { state: 'secondary' })
|
|
258
|
+
expect(theme2.color).toBe('green')
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('$rocketstyleAccessor is a function, not a plain object', () => {
|
|
262
|
+
const Comp: any = rocketstyle()({
|
|
263
|
+
name: 'AccessorComp',
|
|
264
|
+
component: ThemeCapture,
|
|
265
|
+
}).theme({ color: 'red' })
|
|
266
|
+
|
|
267
|
+
const vnode = withThemeContext(() => Comp({}))
|
|
268
|
+
// ThemeCapture resolves the accessor — result should be the theme object
|
|
269
|
+
expect(vnode.$rocketstyle).toBeDefined()
|
|
270
|
+
expect(vnode.$rocketstyle.color).toBe('red')
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('$rocketstateAccessor resolves active dimensions', () => {
|
|
274
|
+
const Comp: any = rocketstyle()({
|
|
275
|
+
name: 'StateAccessorComp',
|
|
276
|
+
component: ThemeCapture,
|
|
277
|
+
}).states({
|
|
278
|
+
primary: { color: 'blue' },
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
const vnode = withThemeContext(() => Comp({ state: 'primary' }))
|
|
282
|
+
expect(vnode.$rocketstate).toBeDefined()
|
|
283
|
+
expect(vnode.$rocketstate.state).toBe('primary')
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('mode change produces different theme via accessor', () => {
|
|
287
|
+
const Comp: any = rocketstyle()({
|
|
288
|
+
name: 'ModeReactiveComp',
|
|
289
|
+
component: ThemeCapture,
|
|
290
|
+
}).theme((t: any, m: any) => ({
|
|
291
|
+
color: m('light-color', 'dark-color'),
|
|
292
|
+
}))
|
|
293
|
+
|
|
294
|
+
const lightTheme = getComputedTheme(Comp, {}, { mode: 'light' })
|
|
295
|
+
expect(lightTheme.color).toBe('light-color')
|
|
296
|
+
|
|
297
|
+
const darkTheme = getComputedTheme(Comp, {}, { mode: 'dark' })
|
|
298
|
+
expect(darkTheme.color).toBe('dark-color')
|
|
299
|
+
})
|
|
300
|
+
})
|
|
@@ -23,8 +23,8 @@ const BaseComponent: any = ({ children, $rocketstyle, $rocketstate, ...rest }: a
|
|
|
23
23
|
props: rest,
|
|
24
24
|
children,
|
|
25
25
|
key: null,
|
|
26
|
-
$rocketstyle,
|
|
27
|
-
$rocketstate,
|
|
26
|
+
$rocketstyle: typeof $rocketstyle === 'function' ? $rocketstyle() : $rocketstyle,
|
|
27
|
+
$rocketstate: typeof $rocketstate === 'function' ? $rocketstate() : $rocketstate,
|
|
28
28
|
})
|
|
29
29
|
BaseComponent.displayName = 'BaseComponent'
|
|
30
30
|
|
|
@@ -62,10 +62,13 @@ const createLocalProvider = (WrappedComponent: ComponentFn<any>) => {
|
|
|
62
62
|
// Without getters, hover()/focus()/pressed() reads here would register
|
|
63
63
|
// as dependencies of any parent effect, causing cascading re-renders
|
|
64
64
|
// on every mouse event.
|
|
65
|
+
// Resolve $rocketstate if it's a function accessor (from EnhancedComponent)
|
|
66
|
+
const resolvedState =
|
|
67
|
+
typeof $rocketstate === 'function' ? $rocketstate() : $rocketstate
|
|
65
68
|
const updatedState = {
|
|
66
|
-
|
|
69
|
+
...resolvedState,
|
|
67
70
|
pseudo: {
|
|
68
|
-
|
|
71
|
+
...resolvedState?.pseudo,
|
|
69
72
|
get hover() {
|
|
70
73
|
return hover()
|
|
71
74
|
},
|
package/src/rocketstyle.ts
CHANGED
|
@@ -110,41 +110,24 @@ const rocketComponent: RocketComponent = (options) => {
|
|
|
110
110
|
const themeAttrs = useTheme(options)
|
|
111
111
|
|
|
112
112
|
// --------------------------------------------------
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
...localCtx,
|
|
117
|
-
...props,
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const pseudoRocketstate = {
|
|
121
|
-
...pseudo,
|
|
122
|
-
...pick(props, [...PSEUDO_KEYS, ...PSEUDO_META_KEYS]),
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// --------------------------------------------------
|
|
126
|
-
// Static theme structure — computed once at mount, doesn't change with mode.
|
|
127
|
-
// Only the mode-dependent resolution is reactive (via $rocketstyle accessor).
|
|
113
|
+
// Theme structure — cached by theme object identity.
|
|
114
|
+
// Theme object itself doesn't change (enrichTheme produces a stable ref),
|
|
115
|
+
// only mode switches change which mode-variant is resolved.
|
|
128
116
|
// --------------------------------------------------
|
|
129
117
|
const theme = themeAttrs.theme
|
|
130
118
|
|
|
131
|
-
// BASE / DEFAULT THEME Object (cached by theme identity)
|
|
132
119
|
const baseThemeHelper = ThemeManager.baseTheme
|
|
133
120
|
if (!baseThemeHelper.has(theme)) {
|
|
134
121
|
baseThemeHelper.set(theme, getThemeFromChain(options.theme, theme))
|
|
135
122
|
}
|
|
136
123
|
const baseTheme = baseThemeHelper.get(theme)
|
|
137
124
|
|
|
138
|
-
// DIMENSION(S) THEMES Object (cached by theme identity)
|
|
139
125
|
const dimHelper = ThemeManager.dimensionsThemes
|
|
140
126
|
if (!dimHelper.has(theme)) {
|
|
141
127
|
dimHelper.set(theme, getDimensionThemes(theme, options))
|
|
142
128
|
}
|
|
143
129
|
const themes = dimHelper.get(theme)
|
|
144
130
|
|
|
145
|
-
// --------------------------------------------------
|
|
146
|
-
// dimension map & reserved prop names
|
|
147
|
-
// --------------------------------------------------
|
|
148
131
|
const { keysMap: dimensions, keywords: reservedPropNames } = getDimensionsMap({
|
|
149
132
|
themes,
|
|
150
133
|
useBooleans: options.useBooleans,
|
|
@@ -153,23 +136,26 @@ const rocketComponent: RocketComponent = (options) => {
|
|
|
153
136
|
const RESERVED_STYLING_PROPS_KEYS = Object.keys(reservedPropNames)
|
|
154
137
|
|
|
155
138
|
// --------------------------------------------------
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
dimensions,
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
const finalRocketstate = { ...rocketstate, pseudo: pseudoRocketstate }
|
|
164
|
-
|
|
165
|
-
// --------------------------------------------------
|
|
166
|
-
// $rocketstyle as a FUNCTION ACCESSOR — reactive on mode changes.
|
|
167
|
-
// The styled component calls this inside its own effect() to track
|
|
168
|
-
// the mode dependency. Only the CSS class swaps — no VNode remount.
|
|
139
|
+
// $rocketstyle as a FUNCTION ACCESSOR — fully reactive.
|
|
140
|
+
// Re-evaluates when mode OR dimension props change.
|
|
141
|
+
// Props are resolved fresh each call so reactive prop accessors
|
|
142
|
+
// (signals, getters) produce updated dimension values.
|
|
169
143
|
// --------------------------------------------------
|
|
170
144
|
const $rocketstyleAccessor = () => {
|
|
171
|
-
|
|
145
|
+
// Only read mode and dimension props — NOT pseudo state.
|
|
146
|
+
// Pseudo state (hover, focus, pressed) is read by .styles()
|
|
147
|
+
// via $rocketstate inside runUntracked(). Reading pseudo signals
|
|
148
|
+
// here would subscribe this accessor to hover/focus/pressed,
|
|
149
|
+
// causing CSS recomputation on every mouse event.
|
|
150
|
+
const mode = themeAttrs.mode // reactive: tracks mode signal
|
|
151
|
+
|
|
152
|
+
// Resolve active dimensions from props (not localCtx which has pseudo getters)
|
|
153
|
+
const rocketstate = _calculateStylingAttrs({
|
|
154
|
+
props: pickStyledAttrs(props as Record<string, unknown>, reservedPropNames),
|
|
155
|
+
dimensions,
|
|
156
|
+
})
|
|
172
157
|
|
|
158
|
+
// Resolve mode-specific theme
|
|
173
159
|
const modeBaseHelper = ThemeManager.modeBaseTheme[mode]
|
|
174
160
|
if (!modeBaseHelper.has(baseTheme)) {
|
|
175
161
|
modeBaseHelper.set(baseTheme, getThemeByMode(baseTheme, mode))
|
|
@@ -191,6 +177,38 @@ const rocketComponent: RocketComponent = (options) => {
|
|
|
191
177
|
})
|
|
192
178
|
}
|
|
193
179
|
|
|
180
|
+
// --------------------------------------------------
|
|
181
|
+
// $rocketstate as a FUNCTION ACCESSOR — reactive on prop changes.
|
|
182
|
+
// Re-evaluates active dimensions + pseudo state from current props.
|
|
183
|
+
// --------------------------------------------------
|
|
184
|
+
// Capture pseudo from localCtx once at setup — pseudo properties are
|
|
185
|
+
// getters (from createLocalProvider) that read signals lazily.
|
|
186
|
+
// Passing them through preserves reactivity without subscribing here.
|
|
187
|
+
const localPseudo = localCtx?.pseudo
|
|
188
|
+
const propPseudo = pick(props, [...PSEUDO_KEYS, ...PSEUDO_META_KEYS])
|
|
189
|
+
|
|
190
|
+
const $rocketstateAccessor = () => {
|
|
191
|
+
const rocketstate = _calculateStylingAttrs({
|
|
192
|
+
props: pickStyledAttrs(props as Record<string, unknown>, reservedPropNames),
|
|
193
|
+
dimensions,
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
// Pseudo state uses getter properties — they're evaluated lazily
|
|
197
|
+
// by .styles() inside runUntracked(), not here.
|
|
198
|
+
return {
|
|
199
|
+
...rocketstate,
|
|
200
|
+
pseudo: { ...localPseudo, ...propPseudo },
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// --------------------------------------------------
|
|
205
|
+
// Static mergeProps for final prop filtering (non-dimension props)
|
|
206
|
+
// --------------------------------------------------
|
|
207
|
+
const { pseudo: _pseudo, ...mergeProps } = {
|
|
208
|
+
...localCtx,
|
|
209
|
+
...props,
|
|
210
|
+
}
|
|
211
|
+
|
|
194
212
|
// --------------------------------------------------
|
|
195
213
|
// final props passed to WrappedComponent
|
|
196
214
|
// --------------------------------------------------
|
|
@@ -202,9 +220,9 @@ const rocketComponent: RocketComponent = (options) => {
|
|
|
202
220
|
]),
|
|
203
221
|
...(options.passProps ? pick(mergeProps, options.passProps) : {}),
|
|
204
222
|
ref: props.ref,
|
|
205
|
-
// FUNCTION
|
|
223
|
+
// FUNCTION accessors — styled component resolves them reactively
|
|
206
224
|
$rocketstyle: $rocketstyleAccessor,
|
|
207
|
-
$rocketstate:
|
|
225
|
+
$rocketstate: $rocketstateAccessor,
|
|
208
226
|
}
|
|
209
227
|
|
|
210
228
|
// development debugging
|
|
@@ -214,7 +232,7 @@ const rocketComponent: RocketComponent = (options) => {
|
|
|
214
232
|
if (options.DEBUG) {
|
|
215
233
|
const debugPayload = {
|
|
216
234
|
component: componentName,
|
|
217
|
-
rocketstate:
|
|
235
|
+
rocketstate: $rocketstateAccessor(),
|
|
218
236
|
rocketstyle: $rocketstyleAccessor(),
|
|
219
237
|
dimensions,
|
|
220
238
|
mode: themeAttrs.mode,
|