@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.
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +34 -9
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +15 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/hydration-debug.ts +4 -1
- package/src/index.ts +5 -2
- package/src/mount.ts +9 -3
- package/src/nodes.ts +10 -6
- package/src/props.ts +17 -1
- package/src/template.ts +29 -1
- package/src/tests/coverage-gaps.test.ts +10 -7
- package/src/tests/mount.test.ts +69 -0
- package/src/tests/template.test.ts +55 -0
- package/src/transition.ts +7 -1
package/lib/types/index.d.ts
CHANGED
|
@@ -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
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -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":";;;;;;AAoBA;;;;;AA8BA;;;;;AAWA;;;;cAzCa,gBAAA,EAAgB,GAAA;;;;ACJ7B;iBDkCgB,iBAAA,CAAkB,SAAA;;;;;iBAWlB,eAAA,CAAgB,SAAA,EAAW,OAAA;;;;;;AAzC3C;;;;;AA8BA;;;;;AAWA;;UC7CiB,sBAAA;EACf,EAAA;EACA,IAAA;;EAEA,EAAA,EAAI,OAAA;EACJ,QAAA;EACA,QAAA;AAAA;AAAA,UAGe,cAAA;EAAA,SACN,OAAA;EACT,gBAAA,IAAoB,sBAAA;EACpB,gBAAA,IAAoB,sBAAA;EACpB,SAAA,CAAU,EAAA;EACV,gBAAA,CAAiB,EAAA,GAAK,KAAA,EAAO,sBAAA;EAC7B,kBAAA,CAAmB,EAAA,GAAK,EAAA;EATxB;EAWA,aAAA;EACA,cAAA;AAAA;;;;;;AAlBF;;;;;;;;;;;iBCqYgB,WAAA,CAAY,SAAA,EAAW,OAAA,EAAS,KAAA,EAAO,UAAA;;;;;;AFjYvD;;;;;AA8BA;;
|
|
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.
|
|
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.
|
|
46
|
-
"@pyreon/reactivity": "^0.12.
|
|
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.
|
|
51
|
-
"@pyreon/runtime-server": "^0.12.
|
|
50
|
+
"@pyreon/compiler": "^0.12.12",
|
|
51
|
+
"@pyreon/runtime-server": "^0.12.12",
|
|
52
52
|
"happy-dom": "^20.8.3"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/src/hydration-debug.ts
CHANGED
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
* enableHydrationWarnings()
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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 —
|
|
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
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
496
|
-
|
|
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
|
})
|
package/src/tests/mount.test.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|