@pyreon/rocketstyle 0.11.6 → 0.11.7

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
- ...$rocketstate,
180
+ ...resolvedState,
180
181
  pseudo: {
181
- ...$rocketstate?.pseudo,
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,16 @@ 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 = () => {
514
+ const { pseudo, ...mergeProps } = {
515
+ ...localCtx,
516
+ ...props
517
+ };
529
518
  const mode = themeAttrs.mode;
519
+ const rocketstate = _calculateStylingAttrs({
520
+ props: pickStyledAttrs(mergeProps, reservedPropNames),
521
+ dimensions
522
+ });
530
523
  const modeBaseHelper = ThemeManager$1.modeBaseTheme[mode];
531
524
  if (!modeBaseHelper.has(baseTheme)) modeBaseHelper.set(baseTheme, getThemeByMode(baseTheme, mode));
532
525
  const currentModeBaseTheme = modeBaseHelper.get(baseTheme);
@@ -540,6 +533,27 @@ const rocketComponent = (options) => {
540
533
  appTheme: theme
541
534
  });
542
535
  };
536
+ const $rocketstateAccessor = () => {
537
+ const { pseudo, ...mergeProps } = {
538
+ ...localCtx,
539
+ ...props
540
+ };
541
+ const pseudoRocketstate = {
542
+ ...pseudo,
543
+ ...pick(props, [...PSEUDO_KEYS, ...PSEUDO_META_KEYS])
544
+ };
545
+ return {
546
+ ..._calculateStylingAttrs({
547
+ props: pickStyledAttrs(mergeProps, reservedPropNames),
548
+ dimensions
549
+ }),
550
+ pseudo: pseudoRocketstate
551
+ };
552
+ };
553
+ const { pseudo: _pseudo, ...mergeProps } = {
554
+ ...localCtx,
555
+ ...props
556
+ };
543
557
  const finalProps = {
544
558
  ...omit(mergeProps, [
545
559
  ...RESERVED_STYLING_PROPS_KEYS,
@@ -549,14 +563,14 @@ const rocketComponent = (options) => {
549
563
  ...options.passProps ? pick(mergeProps, options.passProps) : {},
550
564
  ref: props.ref,
551
565
  $rocketstyle: $rocketstyleAccessor,
552
- $rocketstate: finalRocketstate
566
+ $rocketstate: $rocketstateAccessor
553
567
  };
554
568
  if (process.env.NODE_ENV !== "production") {
555
569
  finalProps["data-rocketstyle"] = componentName;
556
570
  if (options.DEBUG) {
557
571
  const debugPayload = {
558
572
  component: componentName,
559
- rocketstate: finalRocketstate,
573
+ rocketstate: $rocketstateAccessor(),
560
574
  rocketstyle: $rocketstyleAccessor(),
561
575
  dimensions,
562
576
  mode: themeAttrs.mode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/rocketstyle",
3
- "version": "0.11.6",
3
+ "version": "0.11.7",
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.6",
45
- "@pyreon/typescript": "^0.11.6",
44
+ "@pyreon/test-utils": "^0.11.7",
45
+ "@pyreon/typescript": "^0.11.7",
46
46
  "@vitus-labs/tools-rolldown": "^1.15.3"
47
47
  },
48
48
  "peerDependencies": {
49
- "@pyreon/core": "^0.11.6",
50
- "@pyreon/reactivity": "^0.11.6",
51
- "@pyreon/styler": "^0.11.6",
52
- "@pyreon/ui-core": "^0.11.6"
49
+ "@pyreon/core": "^0.11.7",
50
+ "@pyreon/reactivity": "^0.11.7",
51
+ "@pyreon/styler": "^0.11.7",
52
+ "@pyreon/ui-core": "^0.11.7"
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
- ...$rocketstate,
69
+ ...resolvedState,
67
70
  pseudo: {
68
- ...$rocketstate?.pseudo,
71
+ ...resolvedState?.pseudo,
69
72
  get hover() {
70
73
  return hover()
71
74
  },
@@ -110,41 +110,24 @@ const rocketComponent: RocketComponent = (options) => {
110
110
  const themeAttrs = useTheme(options)
111
111
 
112
112
  // --------------------------------------------------
113
- // Static setupruns once at component mount
114
- // --------------------------------------------------
115
- const { pseudo, ...mergeProps } = {
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 structurecached 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,28 @@ const rocketComponent: RocketComponent = (options) => {
153
136
  const RESERVED_STYLING_PROPS_KEYS = Object.keys(reservedPropNames)
154
137
 
155
138
  // --------------------------------------------------
156
- // rocketstateactive dimension values
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.
157
143
  // --------------------------------------------------
158
- const rocketstate = _calculateStylingAttrs({
159
- props: pickStyledAttrs(mergeProps, reservedPropNames),
160
- dimensions,
161
- })
144
+ const $rocketstyleAccessor = () => {
145
+ // Merge props fresh — if parent passes reactive accessors,
146
+ // spreading them here evaluates them in this reactive scope.
147
+ const { pseudo, ...mergeProps } = {
148
+ ...localCtx,
149
+ ...props,
150
+ }
162
151
 
163
- const finalRocketstate = { ...rocketstate, pseudo: pseudoRocketstate }
152
+ const mode = themeAttrs.mode // reactive: tracks mode signal
164
153
 
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.
169
- // --------------------------------------------------
170
- const $rocketstyleAccessor = () => {
171
- const mode = themeAttrs.mode // reactive read via getter
154
+ // Resolve active dimensions from current prop values
155
+ const rocketstate = _calculateStylingAttrs({
156
+ props: pickStyledAttrs(mergeProps, reservedPropNames),
157
+ dimensions,
158
+ })
172
159
 
160
+ // Resolve mode-specific theme
173
161
  const modeBaseHelper = ThemeManager.modeBaseTheme[mode]
174
162
  if (!modeBaseHelper.has(baseTheme)) {
175
163
  modeBaseHelper.set(baseTheme, getThemeByMode(baseTheme, mode))
@@ -191,6 +179,37 @@ const rocketComponent: RocketComponent = (options) => {
191
179
  })
192
180
  }
193
181
 
182
+ // --------------------------------------------------
183
+ // $rocketstate as a FUNCTION ACCESSOR — reactive on prop changes.
184
+ // Re-evaluates active dimensions + pseudo state from current props.
185
+ // --------------------------------------------------
186
+ const $rocketstateAccessor = () => {
187
+ const { pseudo, ...mergeProps } = {
188
+ ...localCtx,
189
+ ...props,
190
+ }
191
+
192
+ const pseudoRocketstate = {
193
+ ...pseudo,
194
+ ...pick(props, [...PSEUDO_KEYS, ...PSEUDO_META_KEYS]),
195
+ }
196
+
197
+ const rocketstate = _calculateStylingAttrs({
198
+ props: pickStyledAttrs(mergeProps, reservedPropNames),
199
+ dimensions,
200
+ })
201
+
202
+ return { ...rocketstate, pseudo: pseudoRocketstate }
203
+ }
204
+
205
+ // --------------------------------------------------
206
+ // Static mergeProps for final prop filtering (non-dimension props)
207
+ // --------------------------------------------------
208
+ const { pseudo: _pseudo, ...mergeProps } = {
209
+ ...localCtx,
210
+ ...props,
211
+ }
212
+
194
213
  // --------------------------------------------------
195
214
  // final props passed to WrappedComponent
196
215
  // --------------------------------------------------
@@ -202,9 +221,9 @@ const rocketComponent: RocketComponent = (options) => {
202
221
  ]),
203
222
  ...(options.passProps ? pick(mergeProps, options.passProps) : {}),
204
223
  ref: props.ref,
205
- // FUNCTION accessor — styled component resolves it reactively
224
+ // FUNCTION accessors — styled component resolves them reactively
206
225
  $rocketstyle: $rocketstyleAccessor,
207
- $rocketstate: finalRocketstate,
226
+ $rocketstate: $rocketstateAccessor,
208
227
  }
209
228
 
210
229
  // development debugging
@@ -214,7 +233,7 @@ const rocketComponent: RocketComponent = (options) => {
214
233
  if (options.DEBUG) {
215
234
  const debugPayload = {
216
235
  component: componentName,
217
- rocketstate: finalRocketstate,
236
+ rocketstate: $rocketstateAccessor(),
218
237
  rocketstyle: $rocketstyleAccessor(),
219
238
  dimensions,
220
239
  mode: themeAttrs.mode,