@bromscandium/runtime 1.0.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/LICENSE +21 -0
- package/README.md +91 -0
- package/dist/hooks.d.ts +99 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +145 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-runtime.d.ts +77 -0
- package/dist/jsx-runtime.d.ts.map +1 -0
- package/dist/jsx-runtime.js +90 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/lifecycle.d.ts +87 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +116 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/renderer.d.ts +65 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +539 -0
- package/dist/renderer.js.map +1 -0
- package/dist/vnode.d.ts +111 -0
- package/dist/vnode.d.ts.map +1 -0
- package/dist/vnode.js +83 -0
- package/dist/vnode.js.map +1 -0
- package/package.json +49 -0
- package/src/env.d.ts +11 -0
- package/src/hooks.ts +166 -0
- package/src/index.ts +56 -0
- package/src/jsx-runtime.ts +132 -0
- package/src/jsx.d.ts +373 -0
- package/src/lifecycle.ts +133 -0
- package/src/renderer.ts +655 -0
- package/src/vnode.ts +159 -0
package/dist/vnode.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Virtual DOM node types and utilities.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
/** Symbol used to identify Fragment nodes (multiple children without a wrapper) */
|
|
6
|
+
export const Fragment = Symbol('Fragment');
|
|
7
|
+
/** Symbol used to identify Text nodes */
|
|
8
|
+
export const Text = Symbol('Text');
|
|
9
|
+
/**
|
|
10
|
+
* Creates a virtual DOM node.
|
|
11
|
+
*
|
|
12
|
+
* @param type - The node type (tag name, component, Fragment, or Text)
|
|
13
|
+
* @param props - Properties/attributes for the node
|
|
14
|
+
* @param children - Child nodes
|
|
15
|
+
* @returns A new virtual node
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const vnode = createVNode('div', { className: 'container' }, [
|
|
20
|
+
* createVNode('span', null, ['Hello']),
|
|
21
|
+
* createVNode('span', null, ['World'])
|
|
22
|
+
* ]);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function createVNode(type, props, children = []) {
|
|
26
|
+
const normalizedChildren = normalizeChildren(children);
|
|
27
|
+
return {
|
|
28
|
+
type,
|
|
29
|
+
props: props || {},
|
|
30
|
+
children: normalizedChildren,
|
|
31
|
+
key: props?.key ?? null,
|
|
32
|
+
el: null,
|
|
33
|
+
component: null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Normalizes children into a flat array, filtering out invalid values.
|
|
38
|
+
* Handles nested arrays and removes nullish/boolean values from conditional rendering.
|
|
39
|
+
*
|
|
40
|
+
* @param children - The children to normalize
|
|
41
|
+
* @returns A flat array of valid children
|
|
42
|
+
*/
|
|
43
|
+
export function normalizeChildren(children) {
|
|
44
|
+
if (children == null) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
if (!Array.isArray(children)) {
|
|
48
|
+
children = [children];
|
|
49
|
+
}
|
|
50
|
+
return children.flat(Infinity).filter(child => {
|
|
51
|
+
return child != null && child !== false && child !== true && child !== '';
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Creates a text virtual node.
|
|
56
|
+
*
|
|
57
|
+
* @param text - The text content
|
|
58
|
+
* @returns A virtual node representing text
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const textNode = createTextVNode('Hello, world!');
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export function createTextVNode(text) {
|
|
66
|
+
return {
|
|
67
|
+
type: Text,
|
|
68
|
+
props: {},
|
|
69
|
+
children: [String(text)],
|
|
70
|
+
key: null,
|
|
71
|
+
el: null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Checks if a value is a virtual node.
|
|
76
|
+
*
|
|
77
|
+
* @param value - The value to check
|
|
78
|
+
* @returns True if the value is a VNode
|
|
79
|
+
*/
|
|
80
|
+
export function isVNode(value) {
|
|
81
|
+
return value != null && typeof value === 'object' && 'type' in value;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=vnode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vnode.js","sourceRoot":"","sources":["../src/vnode.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mFAAmF;AACnF,MAAM,CAAC,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;AAE3C,yCAAyC;AACzC,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;AAkEnC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CACzB,IAAe,EACf,KAAiC,EACjC,WAA0B,EAAE;IAE5B,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAEvD,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,KAAK,IAAI,EAAE;QAClB,QAAQ,EAAE,kBAAkB;QAC5B,GAAG,EAAE,KAAK,EAAE,GAAG,IAAI,IAAI;QACvB,EAAE,EAAE,IAAI;QACR,SAAS,EAAE,IAAI;KAChB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAuB;IACvD,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;QACrB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,QAAQ,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;QAC5C,OAAO,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;IAC5E,CAAC,CAAiB,CAAC;AACrB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,IAAqB;IACnD,OAAO;QACL,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,GAAG,EAAE,IAAI;QACT,EAAE,EAAE,IAAI;KACT,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,KAAU;IAChC,OAAO,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,CAAC;AACvE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bromscandium/runtime",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "BromiumJS runtime - JSX and rendering",
|
|
5
|
+
"author": "bromscandium",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/bromscandium/bromiumjs.git",
|
|
10
|
+
"directory": "packages/runtime"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/bromscandium/bromiumjs#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/bromscandium/bromiumjs/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["bromium", "bromiumjs", "jsx", "runtime", "rendering"],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"./jsx-runtime": {
|
|
27
|
+
"import": "./dist/jsx-runtime.js",
|
|
28
|
+
"types": "./dist/jsx-runtime.d.ts"
|
|
29
|
+
},
|
|
30
|
+
"./jsx-dev-runtime": {
|
|
31
|
+
"import": "./dist/jsx-runtime.js",
|
|
32
|
+
"types": "./dist/jsx-runtime.d.ts"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"src"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"dev": "tsc --watch"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@bromscandium/core": "^1.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/env.d.ts
ADDED
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React-style hooks for state persistence across re-renders.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ref, Ref } from '@bromscandium/core';
|
|
7
|
+
import { getCurrentInstance } from './lifecycle.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a reactive state variable that persists across component re-renders.
|
|
11
|
+
* Similar to React's useState but returns a Ref for reactivity integration.
|
|
12
|
+
*
|
|
13
|
+
* @param initialValue - The initial value, or a function that returns the initial value
|
|
14
|
+
* @returns A reactive ref containing the state value
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* function Counter() {
|
|
19
|
+
* const count = useState(0);
|
|
20
|
+
*
|
|
21
|
+
* return (
|
|
22
|
+
* <button onClick={() => count.value++}>
|
|
23
|
+
* Count: {count.value}
|
|
24
|
+
* </button>
|
|
25
|
+
* );
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* // With lazy initialization
|
|
29
|
+
* const expensive = useState(() => computeExpensiveValue());
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function useState<T>(initialValue: T | (() => T)): Ref<T> {
|
|
33
|
+
const instance = getCurrentInstance();
|
|
34
|
+
|
|
35
|
+
if (!instance) {
|
|
36
|
+
const value = typeof initialValue === 'function'
|
|
37
|
+
? (initialValue as () => T)()
|
|
38
|
+
: initialValue;
|
|
39
|
+
return ref(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const hookIndex = instance.hookIndex++;
|
|
43
|
+
|
|
44
|
+
if (hookIndex >= instance.hooks.length) {
|
|
45
|
+
const value = typeof initialValue === 'function'
|
|
46
|
+
? (initialValue as () => T)()
|
|
47
|
+
: initialValue;
|
|
48
|
+
const stateRef = ref(value);
|
|
49
|
+
instance.hooks.push(stateRef);
|
|
50
|
+
return stateRef;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return instance.hooks[hookIndex];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates a mutable ref object that persists across re-renders without triggering updates.
|
|
58
|
+
* Useful for storing DOM references or mutable values that don't affect rendering.
|
|
59
|
+
*
|
|
60
|
+
* @param initialValue - The initial value for the ref
|
|
61
|
+
* @returns An object with a `current` property containing the value
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* function TextInput() {
|
|
66
|
+
* const inputRef = useRef<HTMLInputElement>(null);
|
|
67
|
+
*
|
|
68
|
+
* onMounted(() => {
|
|
69
|
+
* inputRef.current?.focus();
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* return <input ref={inputRef} />;
|
|
73
|
+
* }
|
|
74
|
+
*
|
|
75
|
+
* // Storing mutable values
|
|
76
|
+
* const renderCount = useRef(0);
|
|
77
|
+
* renderCount.current++; // Doesn't trigger re-render
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function useRef<T>(initialValue: T): { current: T } {
|
|
81
|
+
const instance = getCurrentInstance();
|
|
82
|
+
|
|
83
|
+
if (!instance) {
|
|
84
|
+
return { current: initialValue };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const hookIndex = instance.hookIndex++;
|
|
88
|
+
|
|
89
|
+
if (hookIndex >= instance.hooks.length) {
|
|
90
|
+
instance.hooks.push({ current: initialValue });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return instance.hooks[hookIndex];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Memoizes an expensive computation, recalculating only when dependencies change.
|
|
98
|
+
*
|
|
99
|
+
* @param factory - A function that computes and returns the memoized value
|
|
100
|
+
* @param deps - An array of dependencies that trigger recalculation when changed
|
|
101
|
+
* @returns The memoized value
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* function FilteredList({ items, filter }) {
|
|
106
|
+
* const filtered = useMemo(
|
|
107
|
+
* () => items.filter(item => item.includes(filter)),
|
|
108
|
+
* [items, filter]
|
|
109
|
+
* );
|
|
110
|
+
*
|
|
111
|
+
* return <ul>{filtered.map(item => <li>{item}</li>)}</ul>;
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export function useMemo<T>(factory: () => T, deps: any[]): T {
|
|
116
|
+
const instance = getCurrentInstance();
|
|
117
|
+
|
|
118
|
+
if (!instance) {
|
|
119
|
+
return factory();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const hookIndex = instance.hookIndex++;
|
|
123
|
+
|
|
124
|
+
if (hookIndex >= instance.hooks.length) {
|
|
125
|
+
instance.hooks.push({ value: factory(), deps });
|
|
126
|
+
return instance.hooks[hookIndex].value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const hook = instance.hooks[hookIndex];
|
|
130
|
+
const depsChanged = !hook.deps || deps.some((dep, i) => dep !== hook.deps[i]);
|
|
131
|
+
|
|
132
|
+
if (depsChanged) {
|
|
133
|
+
hook.value = factory();
|
|
134
|
+
hook.deps = deps;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return hook.value;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Memoizes a callback function, returning the same reference until dependencies change.
|
|
142
|
+
* Useful for passing stable callbacks to child components to prevent unnecessary re-renders.
|
|
143
|
+
*
|
|
144
|
+
* @param callback - The callback function to memoize
|
|
145
|
+
* @param deps - An array of dependencies that trigger creating a new callback when changed
|
|
146
|
+
* @returns The memoized callback function
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* function Parent() {
|
|
151
|
+
* const [count, setCount] = useState(0);
|
|
152
|
+
*
|
|
153
|
+
* const handleClick = useCallback(() => {
|
|
154
|
+
* console.log('Clicked with count:', count);
|
|
155
|
+
* }, [count]);
|
|
156
|
+
*
|
|
157
|
+
* return <Child onClick={handleClick} />;
|
|
158
|
+
* }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function useCallback<T extends (...args: any[]) => any>(
|
|
162
|
+
callback: T,
|
|
163
|
+
deps: any[]
|
|
164
|
+
): T {
|
|
165
|
+
return useMemo(() => callback, deps);
|
|
166
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Runtime package exports
|
|
2
|
+
|
|
3
|
+
// Type exports from vnode
|
|
4
|
+
export type {
|
|
5
|
+
VNode,
|
|
6
|
+
VNodeChild,
|
|
7
|
+
VNodeChildren,
|
|
8
|
+
VNodeType,
|
|
9
|
+
ComponentFunction,
|
|
10
|
+
ComponentInstance,
|
|
11
|
+
} from './vnode.js';
|
|
12
|
+
|
|
13
|
+
// Value exports from vnode
|
|
14
|
+
export {
|
|
15
|
+
Fragment,
|
|
16
|
+
Text,
|
|
17
|
+
createVNode,
|
|
18
|
+
normalizeChildren,
|
|
19
|
+
createTextVNode,
|
|
20
|
+
isVNode,
|
|
21
|
+
} from './vnode.js';
|
|
22
|
+
|
|
23
|
+
// Value exports from jsx-runtime
|
|
24
|
+
export {
|
|
25
|
+
jsx,
|
|
26
|
+
jsxs,
|
|
27
|
+
jsxDEV,
|
|
28
|
+
createElement,
|
|
29
|
+
h,
|
|
30
|
+
} from './jsx-runtime.js';
|
|
31
|
+
|
|
32
|
+
// Type exports from jsx-runtime
|
|
33
|
+
export type { JSXElement } from './jsx-runtime.js';
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
render,
|
|
37
|
+
createApp,
|
|
38
|
+
} from './renderer.js';
|
|
39
|
+
|
|
40
|
+
export type { AppConfig } from './renderer.js';
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
onMounted,
|
|
44
|
+
onUnmounted,
|
|
45
|
+
onUpdated,
|
|
46
|
+
getCurrentInstance,
|
|
47
|
+
setCurrentInstance,
|
|
48
|
+
} from './lifecycle.js';
|
|
49
|
+
|
|
50
|
+
// Hooks for state persistence
|
|
51
|
+
export {
|
|
52
|
+
useState,
|
|
53
|
+
useRef,
|
|
54
|
+
useMemo,
|
|
55
|
+
useCallback,
|
|
56
|
+
} from './hooks.js';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX Runtime for TypeScript/Vite.
|
|
3
|
+
* Configure in tsconfig.json: "jsx": "react-jsx", "jsxImportSource": "@bromscandium/runtime"
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {createVNode, Fragment, VNode, VNodeType} from './vnode.js';
|
|
8
|
+
|
|
9
|
+
export { Fragment };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* JSX element type alias for VNode.
|
|
13
|
+
*/
|
|
14
|
+
export interface JSXElement extends VNode {}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a virtual node from JSX syntax (React 17+ automatic runtime).
|
|
18
|
+
* This is called automatically by the TypeScript/Babel JSX transform.
|
|
19
|
+
*
|
|
20
|
+
* @param type - Element type (tag name or component function)
|
|
21
|
+
* @param props - Props object including children
|
|
22
|
+
* @param key - Optional key for list reconciliation
|
|
23
|
+
* @returns A virtual node
|
|
24
|
+
*/
|
|
25
|
+
export function jsx(
|
|
26
|
+
type: VNodeType,
|
|
27
|
+
props: Record<string, any> | null,
|
|
28
|
+
key?: string | number | null
|
|
29
|
+
): VNode {
|
|
30
|
+
const { children, ...restProps } = props || {};
|
|
31
|
+
|
|
32
|
+
return createVNode(
|
|
33
|
+
type,
|
|
34
|
+
{...restProps, key: key ?? restProps?.key},
|
|
35
|
+
children
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a virtual node with static children (optimization hint).
|
|
41
|
+
* Functionally identical to jsx() but indicates children won't change.
|
|
42
|
+
*
|
|
43
|
+
* @param type - Element type (tag name or component function)
|
|
44
|
+
* @param props - Props object including children
|
|
45
|
+
* @param key - Optional key for list reconciliation
|
|
46
|
+
* @returns A virtual node
|
|
47
|
+
*/
|
|
48
|
+
export function jsxs(
|
|
49
|
+
type: VNodeType,
|
|
50
|
+
props: Record<string, any> | null,
|
|
51
|
+
key?: string | number | null
|
|
52
|
+
): VNode {
|
|
53
|
+
return jsx(type, props, key);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Development version of jsx() with extra validation and warnings.
|
|
58
|
+
* Only performs validation when import.meta.env.DEV is true.
|
|
59
|
+
*
|
|
60
|
+
* @param type - Element type (tag name or component function)
|
|
61
|
+
* @param props - Props object including children
|
|
62
|
+
* @param key - Optional key for list reconciliation
|
|
63
|
+
* @param _isStatic - Unused static children hint
|
|
64
|
+
* @param _source - Source location for error messages
|
|
65
|
+
* @param _self - Component reference for error messages
|
|
66
|
+
* @returns A virtual node
|
|
67
|
+
*/
|
|
68
|
+
export function jsxDEV(
|
|
69
|
+
type: VNodeType,
|
|
70
|
+
props: Record<string, any> | null,
|
|
71
|
+
key?: string | number | null,
|
|
72
|
+
_isStatic?: boolean,
|
|
73
|
+
_source?: { fileName: string; lineNumber: number; columnNumber: number },
|
|
74
|
+
_self?: any
|
|
75
|
+
): VNode {
|
|
76
|
+
if (import.meta.env?.DEV) {
|
|
77
|
+
if (typeof type === 'string' && type !== type.toLowerCase() && !type.includes('-')) {
|
|
78
|
+
console.warn(
|
|
79
|
+
`Invalid element type: "${type}". HTML elements must be lowercase. ` +
|
|
80
|
+
`Did you mean to use a component? Components should start with uppercase.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (props) {
|
|
85
|
+
if ('class' in props && !('className' in props)) {
|
|
86
|
+
console.warn(
|
|
87
|
+
`Invalid prop "class" detected. Use "className" instead.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return jsx(type, props, key);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Creates a virtual node using classic createElement syntax.
|
|
98
|
+
* Use this for manual VNode creation without JSX.
|
|
99
|
+
*
|
|
100
|
+
* @param type - Element type (tag name or component function)
|
|
101
|
+
* @param props - Props object (without children)
|
|
102
|
+
* @param children - Child elements as rest parameters
|
|
103
|
+
* @returns A virtual node
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const vnode = createElement('div', { className: 'app' },
|
|
108
|
+
* createElement('h1', null, 'Hello'),
|
|
109
|
+
* createElement('p', null, 'World')
|
|
110
|
+
* );
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function createElement(
|
|
114
|
+
type: VNodeType,
|
|
115
|
+
props: Record<string, any> | null,
|
|
116
|
+
...children: any[]
|
|
117
|
+
): VNode {
|
|
118
|
+
const normalizedProps = props || {};
|
|
119
|
+
const flatChildren = children.length === 1 ? children[0] : children;
|
|
120
|
+
|
|
121
|
+
return createVNode(type, normalizedProps, flatChildren);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Alias for createElement, providing a shorter hyperscript-style API.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* const vnode = h('div', { className: 'app' }, h('span', null, 'Hello'));
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export const h = createElement;
|