@pyreon/elements 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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Provider, alignContent, extendCss, makeItResponsive, value } from "@pyreon/unistyle";
2
- import { Fragment, Portal, createContext, nativeCompat, onMount, onUnmount, provide, splitProps, useContext } from "@pyreon/core";
2
+ import { Fragment, Portal, createContext, h, nativeCompat, onMount, onUnmount, provide, splitProps, useContext } from "@pyreon/core";
3
3
  import { config, isEmpty, omit, pick, render, throttle } from "@pyreon/ui-core";
4
4
  import { Fragment as Fragment$1, jsx, jsxs } from "@pyreon/core/jsx-runtime";
5
5
  import { signal } from "@pyreon/reactivity";
@@ -331,6 +331,34 @@ const isWebFixNeeded = (tag) => {
331
331
  * support `display: flex` consistently across browsers.
332
332
  */
333
333
  const DEV_PROPS = IS_DEVELOPMENT ? { "data-pyr-element": "Element" } : {};
334
+ /**
335
+ * Build a props object for `h(Styled, ...)` by copying own property
336
+ * DESCRIPTORS from `rest`, then layering the additional fields. Compiler-
337
+ * emitted reactive props (`_rp(() => signal())` converted to getters by
338
+ * `makeReactiveProps`) survive end-to-end with their getter intact.
339
+ *
340
+ * Why we bypass JSX spread here: the standard JSX automatic-runtime
341
+ * compilation lowers `<Styled {...rest} foo={x}>` to roughly
342
+ * `jsx(Styled, { ...rest, foo: x })`. That `{...rest, foo: x}` object
343
+ * literal is evaluated at JS level — it fires every getter on `rest` and
344
+ * stores the resolved value before `jsx()` ever sees the object. No
345
+ * amount of in-runtime descriptor preservation can recover the getters
346
+ * once they've been collapsed by the surface-level spread. The fix is
347
+ * structural: don't use JSX spread for reactive-prop forwarding. Build
348
+ * the props object with descriptor preservation and pass it to `h()`
349
+ * directly — `h()` stores props as-is on the vnode, no copy, getters
350
+ * survive into mount.
351
+ */
352
+ const buildStyledProps = (rest, refValue, asTag, extras) => {
353
+ const result = {};
354
+ const descriptors = Object.getOwnPropertyDescriptors(rest);
355
+ for (const key in descriptors) Object.defineProperty(result, key, descriptors[key]);
356
+ for (const key in DEV_PROPS) result[key] = DEV_PROPS[key];
357
+ result.ref = refValue;
358
+ result.as = asTag;
359
+ for (const key in extras) result[key] = extras[key];
360
+ return result;
361
+ };
334
362
  const OWN_KEYS = [
335
363
  "children",
336
364
  "tag",
@@ -346,12 +374,6 @@ const OWN_KEYS = [
346
374
  ];
347
375
  const Component$8 = (props) => {
348
376
  const [own, rest] = splitProps(props, OWN_KEYS);
349
- const commonProps = {
350
- ...rest,
351
- ...DEV_PROPS,
352
- ref: own.ref,
353
- as: own.tag
354
- };
355
377
  const needsFix = !own.dangerouslySetInnerHTML && isWebFixNeeded(own.tag);
356
378
  const isVoidTag = !own.dangerouslySetInnerHTML && getShouldBeEmpty(own.tag);
357
379
  const innerHTML = own.dangerouslySetInnerHTML;
@@ -364,20 +386,15 @@ const Component$8 = (props) => {
364
386
  equalCols: own.equalCols,
365
387
  extraStyles: own.extendCss
366
388
  });
367
- if (isVoidTag) return /* @__PURE__ */ jsx(styled_default$1, {
368
- ...commonProps,
369
- $element: bundle
370
- });
371
- if (innerHTML) return /* @__PURE__ */ jsx(styled_default$1, {
372
- ...commonProps,
389
+ if (isVoidTag) return h(styled_default$1, buildStyledProps(rest, own.ref, own.tag, { $element: bundle }));
390
+ if (innerHTML) return h(styled_default$1, buildStyledProps(rest, own.ref, own.tag, {
373
391
  $element: bundle,
374
392
  dangerouslySetInnerHTML: innerHTML
375
- });
376
- return /* @__PURE__ */ jsx(styled_default$1, {
377
- ...commonProps,
393
+ }));
394
+ return h(styled_default$1, buildStyledProps(rest, own.ref, own.tag, {
378
395
  $element: bundle,
379
396
  children: own.children
380
- });
397
+ }));
381
398
  }
382
399
  const asTag = own.isInline ? "span" : "div";
383
400
  const parentBundle = internElementBundle({
@@ -392,26 +409,24 @@ const Component$8 = (props) => {
392
409
  alignY: own.alignY,
393
410
  equalCols: own.equalCols
394
411
  });
395
- if (innerHTML) return /* @__PURE__ */ jsx(styled_default$1, {
396
- ...commonProps,
412
+ if (innerHTML) return h(styled_default$1, buildStyledProps(rest, own.ref, own.tag, {
397
413
  $element: parentBundle,
398
- children: /* @__PURE__ */ jsx(styled_default$1, {
414
+ children: h(styled_default$1, {
399
415
  as: asTag,
400
416
  $childFix: true,
401
417
  $element: childBundle,
402
418
  dangerouslySetInnerHTML: innerHTML
403
419
  })
404
- });
405
- return /* @__PURE__ */ jsx(styled_default$1, {
406
- ...commonProps,
420
+ }));
421
+ return h(styled_default$1, buildStyledProps(rest, own.ref, own.tag, {
407
422
  $element: parentBundle,
408
- children: /* @__PURE__ */ jsx(styled_default$1, {
423
+ children: h(styled_default$1, {
409
424
  as: asTag,
410
425
  $childFix: true,
411
426
  $element: childBundle,
412
427
  children: own.children
413
428
  })
414
- });
429
+ }));
415
430
  };
416
431
 
417
432
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/elements",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Foundational UI components for Pyreon",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,11 +42,11 @@
42
42
  "typecheck": "tsc --noEmit"
43
43
  },
44
44
  "devDependencies": {
45
- "@pyreon/core": "^0.16.0",
46
- "@pyreon/reactivity": "^0.16.0",
47
- "@pyreon/runtime-dom": "^0.16.0",
48
- "@pyreon/test-utils": "^0.13.3",
49
- "@pyreon/typescript": "^0.16.0",
45
+ "@pyreon/core": "^0.18.0",
46
+ "@pyreon/reactivity": "^0.18.0",
47
+ "@pyreon/runtime-dom": "^0.18.0",
48
+ "@pyreon/test-utils": "^0.13.5",
49
+ "@pyreon/typescript": "^0.18.0",
50
50
  "@vitest/browser-playwright": "^4.1.4",
51
51
  "@vitus-labs/tools-rolldown": "^2.3.0"
52
52
  },
@@ -54,9 +54,9 @@
54
54
  "node": ">= 22"
55
55
  },
56
56
  "dependencies": {
57
- "@pyreon/core": "^0.16.0",
58
- "@pyreon/reactivity": "^0.16.0",
59
- "@pyreon/ui-core": "^0.16.0",
60
- "@pyreon/unistyle": "^0.16.0"
57
+ "@pyreon/core": "^0.18.0",
58
+ "@pyreon/reactivity": "^0.18.0",
59
+ "@pyreon/ui-core": "^0.18.0",
60
+ "@pyreon/unistyle": "^0.18.0"
61
61
  }
62
62
  }
@@ -4,7 +4,7 @@
4
4
  * fix (parent + child Styled) because these HTML elements do not natively
5
5
  * support `display: flex` consistently across browsers.
6
6
  */
7
- import { splitProps } from '@pyreon/core'
7
+ import { h, splitProps } from '@pyreon/core'
8
8
  import { getShouldBeEmpty } from '../../Element/utils'
9
9
  import { IS_DEVELOPMENT } from '../../utils'
10
10
  import { internElementBundle } from '../internElementBundle'
@@ -14,6 +14,46 @@ import { isWebFixNeeded } from './utils'
14
14
 
15
15
  const DEV_PROPS: Record<string, string> = IS_DEVELOPMENT ? { 'data-pyr-element': 'Element' } : {}
16
16
 
17
+ /**
18
+ * Build a props object for `h(Styled, ...)` by copying own property
19
+ * DESCRIPTORS from `rest`, then layering the additional fields. Compiler-
20
+ * emitted reactive props (`_rp(() => signal())` converted to getters by
21
+ * `makeReactiveProps`) survive end-to-end with their getter intact.
22
+ *
23
+ * Why we bypass JSX spread here: the standard JSX automatic-runtime
24
+ * compilation lowers `<Styled {...rest} foo={x}>` to roughly
25
+ * `jsx(Styled, { ...rest, foo: x })`. That `{...rest, foo: x}` object
26
+ * literal is evaluated at JS level — it fires every getter on `rest` and
27
+ * stores the resolved value before `jsx()` ever sees the object. No
28
+ * amount of in-runtime descriptor preservation can recover the getters
29
+ * once they've been collapsed by the surface-level spread. The fix is
30
+ * structural: don't use JSX spread for reactive-prop forwarding. Build
31
+ * the props object with descriptor preservation and pass it to `h()`
32
+ * directly — `h()` stores props as-is on the vnode, no copy, getters
33
+ * survive into mount.
34
+ */
35
+ const buildStyledProps = (
36
+ rest: Record<string, unknown>,
37
+ refValue: unknown,
38
+ asTag: unknown,
39
+ extras: Record<string, unknown>,
40
+ ): Record<string, unknown> => {
41
+ const result: Record<string, unknown> = {}
42
+ const descriptors = Object.getOwnPropertyDescriptors(rest)
43
+ for (const key in descriptors) {
44
+ Object.defineProperty(result, key, descriptors[key]!)
45
+ }
46
+ for (const key in DEV_PROPS) {
47
+ result[key] = DEV_PROPS[key]
48
+ }
49
+ result.ref = refValue
50
+ result.as = asTag
51
+ for (const key in extras) {
52
+ result[key] = extras[key]
53
+ }
54
+ return result
55
+ }
56
+
17
57
  // Layout / ref keys consumed by Wrapper itself. Everything else is forwarded
18
58
  // onto the underlying DOM node. Listed as a tuple so `splitProps` narrows
19
59
  // `own` correctly while preserving reactive prop tracking on both halves.
@@ -34,13 +74,6 @@ const OWN_KEYS: Array<keyof Props | 'ref'> = [
34
74
  const Component = (props: Partial<Props> & { ref?: unknown }) => {
35
75
  const [own, rest] = splitProps(props, OWN_KEYS)
36
76
 
37
- const commonProps = {
38
- ...rest,
39
- ...DEV_PROPS,
40
- ref: own.ref,
41
- as: own.tag,
42
- }
43
-
44
77
  const needsFix = !own.dangerouslySetInnerHTML && isWebFixNeeded(own.tag)
45
78
 
46
79
  // Void HTML elements (hr, input, img, br, …) cannot have children. Even
@@ -69,15 +102,28 @@ const Component = (props: Partial<Props> & { ref?: unknown }) => {
69
102
  extraStyles: own.extendCss,
70
103
  })
71
104
  if (isVoidTag) {
72
- return <Styled {...commonProps} $element={bundle} />
105
+ return h(
106
+ Styled,
107
+ buildStyledProps(rest as unknown as Record<string, unknown>, own.ref, own.tag, {
108
+ $element: bundle,
109
+ }),
110
+ )
73
111
  }
74
112
  if (innerHTML) {
75
- return <Styled {...commonProps} $element={bundle} dangerouslySetInnerHTML={innerHTML} />
113
+ return h(
114
+ Styled,
115
+ buildStyledProps(rest as unknown as Record<string, unknown>, own.ref, own.tag, {
116
+ $element: bundle,
117
+ dangerouslySetInnerHTML: innerHTML,
118
+ }),
119
+ )
76
120
  }
77
- return (
78
- <Styled {...commonProps} $element={bundle}>
79
- {own.children}
80
- </Styled>
121
+ return h(
122
+ Styled,
123
+ buildStyledProps(rest as unknown as Record<string, unknown>, own.ref, own.tag, {
124
+ $element: bundle,
125
+ children: own.children,
126
+ }),
81
127
  )
82
128
  }
83
129
 
@@ -103,24 +149,31 @@ const Component = (props: Partial<Props> & { ref?: unknown }) => {
103
149
  // the defensive forwarding so the contract is robust against future
104
150
  // refactors of the needsFix gate.
105
151
  if (innerHTML) {
106
- return (
107
- <Styled {...commonProps} $element={parentBundle}>
108
- <Styled
109
- as={asTag}
110
- $childFix
111
- $element={childBundle}
112
- dangerouslySetInnerHTML={innerHTML}
113
- />
114
- </Styled>
152
+ return h(
153
+ Styled,
154
+ buildStyledProps(rest as unknown as Record<string, unknown>, own.ref, own.tag, {
155
+ $element: parentBundle,
156
+ children: h(Styled, {
157
+ as: asTag,
158
+ $childFix: true,
159
+ $element: childBundle,
160
+ dangerouslySetInnerHTML: innerHTML,
161
+ }),
162
+ }),
115
163
  )
116
164
  }
117
165
 
118
- return (
119
- <Styled {...commonProps} $element={parentBundle}>
120
- <Styled as={asTag} $childFix $element={childBundle}>
121
- {own.children}
122
- </Styled>
123
- </Styled>
166
+ return h(
167
+ Styled,
168
+ buildStyledProps(rest as unknown as Record<string, unknown>, own.ref, own.tag, {
169
+ $element: parentBundle,
170
+ children: h(Styled, {
171
+ as: asTag,
172
+ $childFix: true,
173
+ $element: childBundle,
174
+ children: own.children,
175
+ }),
176
+ }),
124
177
  )
125
178
  }
126
179