@pyreon/styler 0.16.0 → 0.18.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.
package/lib/index.d.ts CHANGED
@@ -99,6 +99,14 @@ declare const filterProps: (props: Record<string, unknown>) => Record<string, un
99
99
  * Build final props for a styled component in a single pass.
100
100
  * Combines className merging, ref injection, and prop filtering into one
101
101
  * allocation and one iteration.
102
+ *
103
+ * Copies own property DESCRIPTORS rather than values for forwarded
104
+ * props — getter-shaped reactive props (compiler-emitted `_rp(() =>
105
+ * signal())` converted to getters by `makeReactiveProps`) survive the
106
+ * copy with their reactive subscription intact. A bare `result[key] =
107
+ * rawProps[key]` fires the getter at setup time and stores a static
108
+ * value, breaking signal-driven reactivity for any consumer that reads
109
+ * `props.x` in a reactive scope downstream.
102
110
  */
103
111
  declare const buildProps: (rawProps: Record<string, any>, generatedCls: string, isDOM: boolean, customFilter?: (prop: string) => boolean) => Record<string, any>;
104
112
  //#endregion
package/lib/index.js CHANGED
@@ -331,24 +331,36 @@ const filterProps = (props) => {
331
331
  * Build final props for a styled component in a single pass.
332
332
  * Combines className merging, ref injection, and prop filtering into one
333
333
  * allocation and one iteration.
334
+ *
335
+ * Copies own property DESCRIPTORS rather than values for forwarded
336
+ * props — getter-shaped reactive props (compiler-emitted `_rp(() =>
337
+ * signal())` converted to getters by `makeReactiveProps`) survive the
338
+ * copy with their reactive subscription intact. A bare `result[key] =
339
+ * rawProps[key]` fires the getter at setup time and stores a static
340
+ * value, breaking signal-driven reactivity for any consumer that reads
341
+ * `props.x` in a reactive scope downstream.
334
342
  */
335
343
  const buildProps = (rawProps, generatedCls, isDOM, customFilter) => {
336
344
  const result = {};
337
345
  const userCls = rawProps.class || rawProps.className;
338
346
  if (generatedCls) result.class = userCls ? `${generatedCls} ${userCls}` : generatedCls;
339
347
  else if (userCls) result.class = userCls;
348
+ const copyDescriptor = (key) => {
349
+ const d = Object.getOwnPropertyDescriptor(rawProps, key);
350
+ if (d) Object.defineProperty(result, key, d);
351
+ };
340
352
  if (!isDOM) {
341
353
  for (const key in rawProps) {
342
354
  if (key === "as" || key === "className" || key === "class") continue;
343
355
  if (key.charCodeAt(0) === 36) continue;
344
- result[key] = rawProps[key];
356
+ copyDescriptor(key);
345
357
  }
346
358
  return result;
347
359
  }
348
360
  if (customFilter) {
349
361
  for (const key in rawProps) {
350
362
  if (key === "as" || key === "className" || key === "class") continue;
351
- if (customFilter(key)) result[key] = rawProps[key];
363
+ if (customFilter(key)) copyDescriptor(key);
352
364
  }
353
365
  return result;
354
366
  }
@@ -356,10 +368,10 @@ const buildProps = (rawProps, generatedCls, isDOM, customFilter) => {
356
368
  if (key === "as" || key === "className" || key === "class") continue;
357
369
  if (key.charCodeAt(0) === 36) continue;
358
370
  if (key.startsWith("data-") || key.startsWith("aria-")) {
359
- result[key] = rawProps[key];
371
+ copyDescriptor(key);
360
372
  continue;
361
373
  }
362
- if (HTML_PROPS.has(key)) result[key] = rawProps[key];
374
+ if (HTML_PROPS.has(key)) copyDescriptor(key);
363
375
  }
364
376
  return result;
365
377
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/styler",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Lightweight CSS-in-JS engine for Pyreon",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,8 +42,8 @@
42
42
  "typecheck": "tsc --noEmit"
43
43
  },
44
44
  "devDependencies": {
45
- "@pyreon/test-utils": "^0.13.3",
46
- "@pyreon/typescript": "^0.16.0",
45
+ "@pyreon/test-utils": "^0.13.5",
46
+ "@pyreon/typescript": "^0.18.0",
47
47
  "@vitest/browser-playwright": "^4.1.4",
48
48
  "@vitus-labs/tools-rolldown": "^2.3.0"
49
49
  },
@@ -51,7 +51,7 @@
51
51
  "node": ">= 22"
52
52
  },
53
53
  "dependencies": {
54
- "@pyreon/core": "^0.16.0",
55
- "@pyreon/reactivity": "^0.16.0"
54
+ "@pyreon/core": "^0.18.0",
55
+ "@pyreon/reactivity": "^0.18.0"
56
56
  }
57
57
  }
package/src/forward.ts CHANGED
@@ -225,6 +225,14 @@ export const filterProps = (props: Record<string, unknown>): Record<string, unkn
225
225
  * Build final props for a styled component in a single pass.
226
226
  * Combines className merging, ref injection, and prop filtering into one
227
227
  * allocation and one iteration.
228
+ *
229
+ * Copies own property DESCRIPTORS rather than values for forwarded
230
+ * props — getter-shaped reactive props (compiler-emitted `_rp(() =>
231
+ * signal())` converted to getters by `makeReactiveProps`) survive the
232
+ * copy with their reactive subscription intact. A bare `result[key] =
233
+ * rawProps[key]` fires the getter at setup time and stores a static
234
+ * value, breaking signal-driven reactivity for any consumer that reads
235
+ * `props.x` in a reactive scope downstream.
228
236
  */
229
237
  export const buildProps = (
230
238
  rawProps: Record<string, any>,
@@ -234,7 +242,11 @@ export const buildProps = (
234
242
  ): Record<string, any> => {
235
243
  const result: Record<string, any> = {}
236
244
 
237
- // Merge generated + user className
245
+ // Merge generated + user className. Reading `rawProps.class` /
246
+ // `.className` synchronously is fine — `class` is consumed at this
247
+ // boundary (merged with the generated class), never forwarded
248
+ // reactively. The string we write is consumed by the DOM at apply
249
+ // time, not stored as a getter.
238
250
  const userCls = rawProps.class || rawProps.className
239
251
  if (generatedCls) {
240
252
  result.class = userCls ? `${generatedCls} ${userCls}` : generatedCls
@@ -242,12 +254,19 @@ export const buildProps = (
242
254
  result.class = userCls
243
255
  }
244
256
 
257
+ // Helper: copy a prop's OWN descriptor (preserves getters) into result.
258
+ // Falls back to a no-op if the source has no own descriptor for the key.
259
+ const copyDescriptor = (key: string): void => {
260
+ const d = Object.getOwnPropertyDescriptor(rawProps, key)
261
+ if (d) Object.defineProperty(result, key, d)
262
+ }
263
+
245
264
  // Component target — forward all props except as/className/class and $-prefixed
246
265
  if (!isDOM) {
247
266
  for (const key in rawProps) {
248
267
  if (key === 'as' || key === 'className' || key === 'class') continue
249
268
  if (key.charCodeAt(0) === 36) continue // $-prefixed transient
250
- result[key] = rawProps[key]
269
+ copyDescriptor(key)
251
270
  }
252
271
  return result
253
272
  }
@@ -256,7 +275,7 @@ export const buildProps = (
256
275
  if (customFilter) {
257
276
  for (const key in rawProps) {
258
277
  if (key === 'as' || key === 'className' || key === 'class') continue
259
- if (customFilter(key)) result[key] = rawProps[key]
278
+ if (customFilter(key)) copyDescriptor(key)
260
279
  }
261
280
  return result
262
281
  }
@@ -266,10 +285,10 @@ export const buildProps = (
266
285
  if (key === 'as' || key === 'className' || key === 'class') continue
267
286
  if (key.charCodeAt(0) === 36) continue // $-prefixed transient
268
287
  if (key.startsWith('data-') || key.startsWith('aria-')) {
269
- result[key] = rawProps[key]
288
+ copyDescriptor(key)
270
289
  continue
271
290
  }
272
- if (HTML_PROPS.has(key)) result[key] = rawProps[key]
291
+ if (HTML_PROPS.has(key)) copyDescriptor(key)
273
292
  }
274
293
  return result
275
294
  }