@pyreon/runtime-dom 0.12.10 → 0.12.12

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.
@@ -269,6 +269,20 @@ declare function _bindDirect(source: {
269
269
  * })
270
270
  */
271
271
  declare function _tpl(html: string, bind: (el: HTMLElement) => (() => void) | null): NativeItem;
272
+ /**
273
+ * Mount a children slot inside a template.
274
+ *
275
+ * Compiler emits this instead of `createTextNode()` when it detects a
276
+ * children expression (`props.children`, `own.children`). Unlike text nodes,
277
+ * children can be VNodes, arrays, or reactive accessors — all handled by
278
+ * `mountChild()`.
279
+ *
280
+ * @param children - The children value (VNode, string, array, or accessor)
281
+ * @param parent - The parent element in the cloned template
282
+ * @param placeholder - The comment placeholder node to replace
283
+ * @returns Cleanup function
284
+ */
285
+ declare function _mountSlot(children: VNodeChild | VNodeChild[], parent: Node, placeholder: Node): (() => void) | null;
272
286
  //#endregion
273
287
  //#region src/transition.d.ts
274
288
  interface TransitionProps {
@@ -396,5 +410,5 @@ declare function mount(root: VNodeChild, container: Element): () => void;
396
410
  /** Alias for `mount` */
397
411
  declare const render: typeof mount;
398
412
  //#endregion
399
- export { DELEGATED_EVENTS, type DevtoolsComponentEntry, KeepAlive, type KeepAliveProps, type PyreonDevtools, type SanitizeFn, Transition, TransitionGroup, type TransitionGroupProps, type TransitionProps, applyProps as _applyProps, applyProps, _bindDirect, _bindText, _tpl, applyProp, createTemplate, delegatedPropName, disableHydrationWarnings, enableHydrationWarnings, hydrateRoot, mount, mountChild, render, sanitizeHtml, setSanitizer, setupDelegation };
413
+ export { DELEGATED_EVENTS, type DevtoolsComponentEntry, KeepAlive, type KeepAliveProps, type PyreonDevtools, type SanitizeFn, Transition, TransitionGroup, type TransitionGroupProps, type TransitionProps, applyProps as _applyProps, applyProps, _bindDirect, _bindText, _mountSlot, _tpl, applyProp, createTemplate, delegatedPropName, disableHydrationWarnings, enableHydrationWarnings, hydrateRoot, mount, mountChild, render, sanitizeHtml, setSanitizer, setupDelegation };
400
414
  //# sourceMappingURL=index2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/delegate.ts","../../../src/devtools.ts","../../../src/hydrate.ts","../../../src/hydration-debug.ts","../../../src/keep-alive.ts","../../../src/mount.ts","../../../src/props.ts","../../../src/template.ts","../../../src/transition.ts","../../../src/transition-group.ts","../../../src/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/delegate.ts","../../../src/devtools.ts","../../../src/hydrate.ts","../../../src/hydration-debug.ts","../../../src/keep-alive.ts","../../../src/mount.ts","../../../src/props.ts","../../../src/template.ts","../../../src/transition.ts","../../../src/transition-group.ts","../../../src/index.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/runtime-dom",
3
- "version": "0.12.10",
3
+ "version": "0.12.12",
4
4
  "description": "DOM renderer for Pyreon",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/runtime-dom#readme",
6
6
  "bugs": {
@@ -42,13 +42,13 @@
42
42
  "prepublishOnly": "bun run build"
43
43
  },
44
44
  "dependencies": {
45
- "@pyreon/core": "^0.12.10",
46
- "@pyreon/reactivity": "^0.12.10"
45
+ "@pyreon/core": "^0.12.12",
46
+ "@pyreon/reactivity": "^0.12.12"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@happy-dom/global-registrator": "^20.8.9",
50
- "@pyreon/compiler": "^0.12.10",
51
- "@pyreon/runtime-server": "^0.12.10",
50
+ "@pyreon/compiler": "^0.12.12",
51
+ "@pyreon/runtime-server": "^0.12.12",
52
52
  "happy-dom": "^20.8.3"
53
53
  }
54
54
  }
@@ -9,7 +9,10 @@
9
9
  * enableHydrationWarnings()
10
10
  */
11
11
 
12
- const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
12
+ // Dev-mode gate: see `pyreon/no-process-dev-gate` lint rule for why this
13
+ // uses `import.meta.env.DEV` instead of `typeof process !== 'undefined'`.
14
+ // @ts-ignore — `import.meta.env.DEV` is provided by Vite/Rolldown at build time
15
+ const __DEV__ = import.meta.env?.DEV === true
13
16
 
14
17
  let _enabled = __DEV__
15
18
 
package/src/index.ts CHANGED
@@ -9,7 +9,7 @@ export { KeepAlive } from './keep-alive'
9
9
  export { mountChild } from './mount'
10
10
  export type { SanitizeFn } from './props'
11
11
  export { applyProp, applyProps, applyProps as _applyProps, sanitizeHtml, setSanitizer } from './props'
12
- export { _bindDirect, _bindText, _tpl, createTemplate } from './template'
12
+ export { _bindDirect, _bindText, _mountSlot, _tpl, createTemplate } from './template'
13
13
  export type { TransitionProps } from './transition'
14
14
  export { Transition } from './transition'
15
15
  export type { TransitionGroupProps } from './transition-group'
@@ -20,7 +20,10 @@ import { setupDelegation } from './delegate'
20
20
  import { installDevTools } from './devtools'
21
21
  import { mountChild } from './mount'
22
22
 
23
- const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
23
+ // Dev-mode gate: see `pyreon/no-process-dev-gate` lint rule for why this
24
+ // uses `import.meta.env.DEV` instead of `typeof process !== 'undefined'`.
25
+ // @ts-ignore — `import.meta.env.DEV` is provided by Vite/Rolldown at build time
26
+ const __DEV__ = import.meta.env?.DEV === true
24
27
 
25
28
  /**
26
29
  * Mount a VNode tree into a container element.
package/src/mount.ts CHANGED
@@ -23,7 +23,10 @@ import { registerComponent, unregisterComponent } from './devtools'
23
23
  import { mountFor, mountKeyedList, mountReactive } from './nodes'
24
24
  import { applyProps } from './props'
25
25
 
26
- const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
26
+ // Dev-mode gate: see `pyreon/no-process-dev-gate` lint rule for why this
27
+ // uses `import.meta.env.DEV` instead of `typeof process !== 'undefined'`.
28
+ // @ts-ignore — `import.meta.env.DEV` is provided by Vite/Rolldown at build time
29
+ const __DEV__ = import.meta.env?.DEV === true
27
30
 
28
31
  type Cleanup = () => void
29
32
  const noop: Cleanup = () => {
@@ -353,9 +356,12 @@ function mountComponent(
353
356
  'Components must be synchronous — use lazy() + Suspense for async loading, ' +
354
357
  'or fetch data in onMount and store it in a signal.',
355
358
  )
356
- } else if (!('type' in output)) {
359
+ } else if (!('type' in output) && !Array.isArray(output) && !((output as any).__isNative)) {
360
+ // Objects without `type` that are NOT arrays (valid VNodeChild[])
361
+ // and NOT NativeItems (from _tpl()) are invalid. Arrays come from
362
+ // Fragment returns, NativeItems come from compiled templates.
357
363
  console.warn(
358
- `[Pyreon] Component <${componentName}> returned an invalid value. Components must return a VNode, string, null, or function.`,
364
+ `[Pyreon] Component <${componentName}> returned an invalid value. Components must return a VNode, string, null, function, or array.`,
359
365
  )
360
366
  }
361
367
  }
package/src/nodes.ts CHANGED
@@ -5,7 +5,10 @@ type MountFn = (child: VNodeChild, parent: Node, anchor: Node | null) => Cleanup
5
5
 
6
6
  import { effect, runUntracked } from '@pyreon/reactivity'
7
7
 
8
- const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
8
+ // Dev-mode gate: see `pyreon/no-process-dev-gate` lint rule for why this
9
+ // uses `import.meta.env.DEV` instead of `typeof process !== 'undefined'`.
10
+ // @ts-ignore — `import.meta.env.DEV` is provided by Vite/Rolldown at build time
11
+ const __DEV__ = import.meta.env?.DEV === true
9
12
 
10
13
  type Cleanup = () => void
11
14
 
@@ -68,11 +71,12 @@ export function mountReactive(
68
71
  /* noop */
69
72
  }
70
73
  const value = accessor()
71
- if (__DEV__ && typeof value === 'function') {
72
- console.warn(
73
- '[Pyreon] Reactive accessor returned a function instead of a value. Did you forget to call the signal?',
74
- )
75
- }
74
+ // Note: typeof value === 'function' is a VALID return from a reactive
75
+ // accessor — it represents a nested `() => VNodeChild` accessor (the
76
+ // conditional rendering pattern: `{() => show() ? <A /> : null}`).
77
+ // mountChild handles function children by calling them reactively.
78
+ // Do NOT warn on function returns — they are handled correctly at
79
+ // runtime by mountChild's function branch (line 58 above).
76
80
  if (value != null && value !== false) {
77
81
  // Mount children UNTRACKED — signal reads during child component
78
82
  // setup (useContext, useTheme, etc.) must NOT subscribe this
package/src/props.ts CHANGED
@@ -6,7 +6,10 @@ import { DELEGATED_EVENTS, delegatedPropName } from './delegate'
6
6
 
7
7
  type Cleanup = () => void
8
8
 
9
- const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
9
+ // Dev-mode gate: see `pyreon/no-process-dev-gate` lint rule for why this
10
+ // uses `import.meta.env.DEV` instead of `typeof process !== 'undefined'`.
11
+ // @ts-ignore — `import.meta.env.DEV` is provided by Vite/Rolldown at build time
12
+ const __DEV__ = import.meta.env?.DEV === true
10
13
 
11
14
  // ─── Configurable sanitizer ──────────────────────────────────────────────────
12
15
 
@@ -316,6 +319,19 @@ function setStaticProp(el: Element, key: string, value: unknown): void {
316
319
  return
317
320
  }
318
321
 
322
+ // SVG and MathML elements: ALWAYS use setAttribute. Many of their
323
+ // properties are read-only `SVGAnimated*` getters (e.g.
324
+ // `SVGMarkerElement.refX`, `SVGMarkerElement.markerWidth`,
325
+ // `SVGRectElement.x`, etc.). Trying `el[key] = value` on those
326
+ // crashes with "Cannot set property X of [object Object] which has
327
+ // only a getter". The standard React/Vue/Solid behavior is to
328
+ // skip the property assignment optimization for non-HTML elements
329
+ // and always go through setAttribute.
330
+ if (el.namespaceURI && el.namespaceURI !== 'http://www.w3.org/1999/xhtml') {
331
+ el.setAttribute(key, String(value))
332
+ return
333
+ }
334
+
319
335
  if (key in el) {
320
336
  ;(el as unknown as Record<string, unknown>)[key] = value
321
337
  return
package/src/template.ts CHANGED
@@ -1,5 +1,6 @@
1
- import type { NativeItem } from '@pyreon/core'
1
+ import type { NativeItem, VNodeChild } from '@pyreon/core'
2
2
  import { renderEffect } from '@pyreon/reactivity'
3
+ import { mountChild } from './mount'
3
4
 
4
5
  /**
5
6
  * Creates a row/item factory backed by HTML template cloning.
@@ -151,3 +152,30 @@ export function _tpl(html: string, bind: (el: HTMLElement) => (() => void) | nul
151
152
  const cleanup = bind(el)
152
153
  return { __isNative: true, el, cleanup }
153
154
  }
155
+
156
+ /**
157
+ * Mount a children slot inside a template.
158
+ *
159
+ * Compiler emits this instead of `createTextNode()` when it detects a
160
+ * children expression (`props.children`, `own.children`). Unlike text nodes,
161
+ * children can be VNodes, arrays, or reactive accessors — all handled by
162
+ * `mountChild()`.
163
+ *
164
+ * @param children - The children value (VNode, string, array, or accessor)
165
+ * @param parent - The parent element in the cloned template
166
+ * @param placeholder - The comment placeholder node to replace
167
+ * @returns Cleanup function
168
+ */
169
+ export function _mountSlot(
170
+ children: VNodeChild | VNodeChild[],
171
+ parent: Node,
172
+ placeholder: Node,
173
+ ): (() => void) | null {
174
+ if (children == null || children === false || children === true) {
175
+ parent.removeChild(placeholder)
176
+ return null
177
+ }
178
+ const cleanup = mountChild(children, parent, placeholder)
179
+ parent.removeChild(placeholder)
180
+ return cleanup
181
+ }
@@ -484,17 +484,20 @@ describe('nodes.ts — LIS array growth and dev warnings', () => {
484
484
  el.remove()
485
485
  })
486
486
 
487
- test('mountReactive — __DEV__ warning when accessor returns function', () => {
487
+ test('mountReactive — accessor returning function is valid (no warning)', () => {
488
488
  const el = container()
489
489
  const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
490
490
 
491
- // Reactive accessor that returns a function (not a value) dev warning
492
- const badAccessor = () => (() => 'oops') as unknown as VNodeChild
493
- mount(h('div', null, badAccessor as VNodeChild), el)
491
+ // Reactive accessor that returns a function this IS valid in Pyreon.
492
+ // Functions are a valid VNodeChild form (() => VNodeChildAtom) and are
493
+ // handled by mountChild's function branch recursively. The previous
494
+ // warning was a false positive that fired on legitimate conditional
495
+ // rendering patterns like {() => show() ? <A /> : null}.
496
+ const accessor = () => (() => 'hello') as unknown as VNodeChild
497
+ mount(h('div', null, accessor as VNodeChild), el)
494
498
 
495
- expect(warnSpy).toHaveBeenCalledWith(
496
- expect.stringContaining('returned a function instead of a value'),
497
- )
499
+ // No warning should fire — function returns are valid
500
+ expect(warnSpy).not.toHaveBeenCalled()
498
501
  warnSpy.mockRestore()
499
502
  el.remove()
500
503
  })
@@ -3247,6 +3247,75 @@ describe('mount — SVG namespace', () => {
3247
3247
  expect(rect.namespaceURI).toBe(SVG_NS)
3248
3248
  })
3249
3249
 
3250
+ test('SVG marker mounts cleanly with read-only animated-length attributes', () => {
3251
+ // Regression test for the bug fixed via the SVG namespace
3252
+ // special case in setStaticProp: SVGMarkerElement properties
3253
+ // like markerWidth, markerHeight, refX, refY are read-only
3254
+ // SVGAnimatedLength getters. Before the fix, the runtime tried
3255
+ // `el[key] = value` and crashed with `Cannot set property
3256
+ // markerWidth of [object Object] which has only a getter`.
3257
+ // Same for SVGRectElement.x/y/width/height and many others.
3258
+ //
3259
+ // The fix: SVG and MathML elements always go through
3260
+ // setAttribute() instead of property assignment. This test
3261
+ // mounts a marker with all the offending attributes and asserts
3262
+ // they were applied via setAttribute.
3263
+ const el = container()
3264
+ mount(
3265
+ h(
3266
+ 'svg',
3267
+ null,
3268
+ h(
3269
+ 'defs',
3270
+ null,
3271
+ h(
3272
+ 'marker',
3273
+ {
3274
+ id: 'arrowhead',
3275
+ markerWidth: '10',
3276
+ markerHeight: '7',
3277
+ refX: '10',
3278
+ refY: '3.5',
3279
+ orient: 'auto',
3280
+ },
3281
+ h('polygon', { points: '0 0, 10 3.5, 0 7', fill: '#999' }),
3282
+ ),
3283
+ ),
3284
+ ),
3285
+ el,
3286
+ )
3287
+
3288
+ const marker = el.querySelector('marker#arrowhead')
3289
+ expect(marker).not.toBeNull()
3290
+ expect(marker?.getAttribute('markerWidth')).toBe('10')
3291
+ expect(marker?.getAttribute('markerHeight')).toBe('7')
3292
+ expect(marker?.getAttribute('refX')).toBe('10')
3293
+ expect(marker?.getAttribute('refY')).toBe('3.5')
3294
+ expect(marker?.getAttribute('orient')).toBe('auto')
3295
+ })
3296
+
3297
+ test('SVG rect mounts cleanly with read-only x/y/width/height attributes', () => {
3298
+ // Same regression class as the marker test above. SVGRectElement
3299
+ // exposes x, y, width, height as read-only SVGAnimatedLength.
3300
+ const el = container()
3301
+ mount(
3302
+ h(
3303
+ 'svg',
3304
+ null,
3305
+ h('rect', { x: '5', y: '10', width: '100', height: '50', fill: 'red' }),
3306
+ ),
3307
+ el,
3308
+ )
3309
+
3310
+ const rect = el.querySelector('rect')
3311
+ expect(rect).not.toBeNull()
3312
+ expect(rect?.getAttribute('x')).toBe('5')
3313
+ expect(rect?.getAttribute('y')).toBe('10')
3314
+ expect(rect?.getAttribute('width')).toBe('100')
3315
+ expect(rect?.getAttribute('height')).toBe('50')
3316
+ expect(rect?.getAttribute('fill')).toBe('red')
3317
+ })
3318
+
3250
3319
  test('elements outside svg do not get SVG namespace', () => {
3251
3320
  const el = container()
3252
3321
  mount(
@@ -192,3 +192,58 @@ describe('_bindDirect', () => {
192
192
  expect(el.getAttribute('data-num')).toBe('1')
193
193
  })
194
194
  })
195
+
196
+ // ─── _mountSlot ────────────────────────────────────────────────────────────
197
+
198
+ describe('_mountSlot', async () => {
199
+ const { _mountSlot } = await import('../template')
200
+ const { h } = await import('@pyreon/core')
201
+
202
+ test('mounts a string child as text', () => {
203
+ const parent = document.createElement('div')
204
+ const placeholder = document.createComment('')
205
+ parent.appendChild(placeholder)
206
+
207
+ _mountSlot('hello', parent, placeholder)
208
+ expect(parent.textContent).toBe('hello')
209
+ })
210
+
211
+ test('mounts a VNode child as DOM element', () => {
212
+ const parent = document.createElement('div')
213
+ const placeholder = document.createComment('')
214
+ parent.appendChild(placeholder)
215
+
216
+ const vnode = h('span', { class: 'test' }, 'content')
217
+ _mountSlot(vnode, parent, placeholder)
218
+ expect(parent.querySelector('span')).not.toBeNull()
219
+ expect(parent.querySelector('span')?.textContent).toBe('content')
220
+ expect(parent.querySelector('span')?.className).toBe('test')
221
+ })
222
+
223
+ test('mounts an array of children', () => {
224
+ const parent = document.createElement('div')
225
+ const placeholder = document.createComment('')
226
+ parent.appendChild(placeholder)
227
+
228
+ _mountSlot(['first', ' ', 'second'], parent, placeholder)
229
+ expect(parent.textContent).toBe('first second')
230
+ })
231
+
232
+ test('handles null/undefined children', () => {
233
+ const parent = document.createElement('div')
234
+ const placeholder = document.createComment('')
235
+ parent.appendChild(placeholder)
236
+
237
+ _mountSlot(null, parent, placeholder)
238
+ expect(parent.childNodes.length).toBe(0)
239
+ })
240
+
241
+ test('handles false/true children', () => {
242
+ const parent = document.createElement('div')
243
+ const placeholder = document.createComment('')
244
+ parent.appendChild(placeholder)
245
+
246
+ _mountSlot(false, parent, placeholder)
247
+ expect(parent.childNodes.length).toBe(0)
248
+ })
249
+ })
package/src/transition.ts CHANGED
@@ -2,7 +2,13 @@ import type { Props, VNode, VNodeChild } from '@pyreon/core'
2
2
  import { createRef, Fragment, h, onUnmount } from '@pyreon/core'
3
3
  import { effect, runUntracked, signal } from '@pyreon/reactivity'
4
4
 
5
- const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
5
+ // Dev-mode gate: `import.meta.env.DEV` is the Vite/Rolldown standard,
6
+ // literal-replaced at build time. The previous `typeof process !== 'undefined'`
7
+ // pattern was dead code in real Vite browser bundles because Vite does not
8
+ // polyfill `process` for the client — every wrapped warning silently never
9
+ // fired in dev. Enforced by the `pyreon/no-process-dev-gate` lint rule.
10
+ // @ts-ignore — `import.meta.env.DEV` is provided by Vite/Rolldown at build time
11
+ const __DEV__ = import.meta.env?.DEV === true
6
12
 
7
13
  export interface TransitionProps {
8
14
  /**