@lab206/dom 0.1.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.
Files changed (69) hide show
  1. package/LICENSE +114 -0
  2. package/README.md +6 -0
  3. package/dist/bind.d.ts +23 -0
  4. package/dist/bind.d.ts.map +1 -0
  5. package/dist/bind.js +170 -0
  6. package/dist/bind.js.map +1 -0
  7. package/dist/component.d.ts +47 -0
  8. package/dist/component.d.ts.map +1 -0
  9. package/dist/component.js +95 -0
  10. package/dist/component.js.map +1 -0
  11. package/dist/dynamic.d.ts +17 -0
  12. package/dist/dynamic.d.ts.map +1 -0
  13. package/dist/dynamic.js +68 -0
  14. package/dist/dynamic.js.map +1 -0
  15. package/dist/fragment.d.ts +13 -0
  16. package/dist/fragment.d.ts.map +1 -0
  17. package/dist/fragment.js +41 -0
  18. package/dist/fragment.js.map +1 -0
  19. package/dist/h.d.ts +26 -0
  20. package/dist/h.d.ts.map +1 -0
  21. package/dist/h.js +164 -0
  22. package/dist/h.js.map +1 -0
  23. package/dist/index.d.ts +29 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +23 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/insert.d.ts +5 -0
  28. package/dist/insert.d.ts.map +1 -0
  29. package/dist/insert.js +10 -0
  30. package/dist/insert.js.map +1 -0
  31. package/dist/jsx-runtime.d.ts +52 -0
  32. package/dist/jsx-runtime.d.ts.map +1 -0
  33. package/dist/jsx-runtime.js +43 -0
  34. package/dist/jsx-runtime.js.map +1 -0
  35. package/dist/list.d.ts +23 -0
  36. package/dist/list.d.ts.map +1 -0
  37. package/dist/list.js +77 -0
  38. package/dist/list.js.map +1 -0
  39. package/dist/mount.d.ts +19 -0
  40. package/dist/mount.d.ts.map +1 -0
  41. package/dist/mount.js +40 -0
  42. package/dist/mount.js.map +1 -0
  43. package/dist/on.d.ts +8 -0
  44. package/dist/on.d.ts.map +1 -0
  45. package/dist/on.js +7 -0
  46. package/dist/on.js.map +1 -0
  47. package/dist/props.d.ts +54 -0
  48. package/dist/props.d.ts.map +1 -0
  49. package/dist/props.js +211 -0
  50. package/dist/props.js.map +1 -0
  51. package/dist/show.d.ts +12 -0
  52. package/dist/show.d.ts.map +1 -0
  53. package/dist/show.js +42 -0
  54. package/dist/show.js.map +1 -0
  55. package/package.json +71 -0
  56. package/src/bind.ts +195 -0
  57. package/src/component.ts +127 -0
  58. package/src/dynamic.ts +93 -0
  59. package/src/fragment.ts +43 -0
  60. package/src/h.ts +236 -0
  61. package/src/index.ts +54 -0
  62. package/src/insert.ts +14 -0
  63. package/src/jsx-runtime.ts +100 -0
  64. package/src/list.ts +111 -0
  65. package/src/mount.ts +46 -0
  66. package/src/on.ts +31 -0
  67. package/src/props.ts +259 -0
  68. package/src/show.ts +52 -0
  69. package/src/test-setup.ts +17 -0
@@ -0,0 +1,127 @@
1
+ import type { Child } from "./h.js";
2
+ import { h } from "./h.js";
3
+ import { show } from "./show.js";
4
+ import { list, type ListOptions } from "./list.js";
5
+ import { createProps, type ReactiveProps } from "./props.js";
6
+
7
+ export type ComponentProps<P extends object> = ReactiveProps<
8
+ P & { children?: unknown }
9
+ >;
10
+
11
+ export type ComponentSetup<P extends object> = (
12
+ props: ComponentProps<P>,
13
+ ) => Node | DocumentFragment | null | undefined;
14
+
15
+ export type Component<P extends object = Record<string, never>> = (
16
+ props?: P & { children?: unknown },
17
+ ) => Node | DocumentFragment | null | undefined;
18
+
19
+ /**
20
+ * Declare a function component with **reactive props**.
21
+ *
22
+ * Setup runs **once** per instance. Reading `props.x` inside effects /
23
+ * JSX reactive scopes tracks the parent's signal or accessor.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * const Hello = component((props: { name: string }) => (
28
+ * <p>{() => `Hello, ${props.name}`}</p>
29
+ * ));
30
+ *
31
+ * // Live updates — pass a signal or accessor:
32
+ * <Hello name={name} />
33
+ * <Hello name={() => user().name} />
34
+ * ```
35
+ */
36
+ export function component<P extends object>(
37
+ setup: ComponentSetup<P>,
38
+ ): Component<P> {
39
+ const Comp: Component<P> = (raw) => {
40
+ // If JSX already applied createProps, `raw` is a Proxy — still safe
41
+ // to wrap again only when we have a plain object. Detect proxy via
42
+ // missing own keys reliability: always normalize through createProps
43
+ // on a shallow bag of current keys so double-proxy still reads source.
44
+ const props = createProps(
45
+ (raw ?? {}) as P & { children?: unknown },
46
+ ) as ComponentProps<P>;
47
+ return setup(props);
48
+ };
49
+ Object.defineProperty(Comp, "name", {
50
+ value: setup.name || "Component",
51
+ configurable: true,
52
+ });
53
+ return Comp;
54
+ }
55
+
56
+ /**
57
+ * Conditional render for JSX.
58
+ * `when` may be a boolean, signal, or zero-arg accessor — unwrapped on read
59
+ * when used with reactive props / JSX.
60
+ */
61
+ export function Show(props: {
62
+ when: boolean | (() => boolean);
63
+ children: (() => Node) | Node | Child;
64
+ fallback?: (() => Node) | Node | Child;
65
+ }): HTMLElement {
66
+ // Normalize so accessors stay lazy for the show() effect.
67
+ const p = props;
68
+ const whenFn = (): boolean => {
69
+ const w = p.when;
70
+ if (typeof w === "function") return !!(w as () => boolean)();
71
+ return !!w;
72
+ };
73
+
74
+ const host = document.createElement("div");
75
+ host.style.display = "contents";
76
+
77
+ const resolve = (
78
+ value: (() => Node) | Node | Child | undefined,
79
+ ): Node => {
80
+ if (value == null || value === false || value === true) {
81
+ return document.createComment("show");
82
+ }
83
+ if (typeof value === "function") {
84
+ const result = (value as () => unknown)();
85
+ if (result instanceof Node) return result;
86
+ return h("span", { text: value as () => string | number });
87
+ }
88
+ if (typeof value === "string" || typeof value === "number") {
89
+ return document.createTextNode(String(value));
90
+ }
91
+ return value as Node;
92
+ };
93
+
94
+ show(host, whenFn, () => resolve(p.children as (() => Node) | Node));
95
+
96
+ if (p.fallback !== undefined) {
97
+ const fallbackHost = document.createElement("div");
98
+ fallbackHost.style.display = "contents";
99
+ show(fallbackHost, () => !whenFn(), () => resolve(p.fallback));
100
+ const outer = document.createElement("div");
101
+ outer.style.display = "contents";
102
+ outer.append(host, fallbackHost);
103
+ return outer;
104
+ }
105
+
106
+ return host;
107
+ }
108
+
109
+ /**
110
+ * Keyed list for JSX.
111
+ */
112
+ export function For<T>(props: {
113
+ each: readonly T[] | (() => readonly T[]);
114
+ children: (item: () => T, index: () => number) => Node;
115
+ key?: ListOptions<T>["key"];
116
+ }): HTMLElement {
117
+ const host = document.createElement("div");
118
+ host.style.display = "contents";
119
+ const getItems = (): readonly T[] => {
120
+ const e = props.each;
121
+ return typeof e === "function" ? (e as () => readonly T[])() : e;
122
+ };
123
+ list(host, getItems, props.children, {
124
+ ...(props.key ? { key: props.key } : {}),
125
+ });
126
+ return host;
127
+ }
package/src/dynamic.ts ADDED
@@ -0,0 +1,93 @@
1
+ import { effect, type Dispose } from "@lab206/core";
2
+
3
+ /**
4
+ * Values a reactive JSX child function may return.
5
+ * Nodes mount as DOM; strings/numbers become text; nullish clears the slot.
6
+ */
7
+ export type DynamicChild =
8
+ | Node
9
+ | DocumentFragment
10
+ | string
11
+ | number
12
+ | boolean
13
+ | null
14
+ | undefined
15
+ | DynamicChild[];
16
+
17
+ /**
18
+ * Mount a reactive child that may return **text or DOM nodes**.
19
+ *
20
+ * Previously function children were always treated as text via `String(value)`,
21
+ * which produced `[object HTMLParagraphElement]` when a node was returned
22
+ * (common for Tabs / Accordion / List content).
23
+ *
24
+ * Uses a `display: contents` host so layout is unaffected.
25
+ */
26
+ export function bindDynamic(
27
+ parent: ParentNode,
28
+ get: () => DynamicChild,
29
+ ): Dispose {
30
+ const host = document.createElement("span");
31
+ host.setAttribute("data-pu-dyn", "");
32
+ // contents: children participate in parent layout as if unwrapped
33
+ (host.style as CSSStyleDeclaration).display = "contents";
34
+ parent.appendChild(host);
35
+
36
+ let textNode: Text | null = null;
37
+ let mode: "empty" | "text" | "nodes" = "empty";
38
+
39
+ return effect(() => {
40
+ const value = get();
41
+
42
+ if (value == null || value === false || value === true) {
43
+ host.replaceChildren();
44
+ textNode = null;
45
+ mode = "empty";
46
+ return;
47
+ }
48
+
49
+ if (typeof value === "string" || typeof value === "number") {
50
+ const s = String(value);
51
+ if (mode === "text" && textNode) {
52
+ textNode.data = s;
53
+ } else {
54
+ host.replaceChildren();
55
+ textNode = document.createTextNode(s);
56
+ host.appendChild(textNode);
57
+ mode = "text";
58
+ }
59
+ return;
60
+ }
61
+
62
+ // Nodes / arrays — never String(object)
63
+ textNode = null;
64
+ mode = "nodes";
65
+ host.replaceChildren();
66
+ appendValue(host, value);
67
+ });
68
+ }
69
+
70
+ function appendValue(parent: ParentNode, value: DynamicChild): void {
71
+ if (value == null || value === false || value === true) return;
72
+
73
+ if (typeof value === "string" || typeof value === "number") {
74
+ parent.appendChild(document.createTextNode(String(value)));
75
+ return;
76
+ }
77
+
78
+ // Prefer nodeType over instanceof — DocumentFragment may be missing in some test envs
79
+ if (
80
+ typeof value === "object" &&
81
+ value !== null &&
82
+ "nodeType" in (value as object) &&
83
+ typeof (value as Node).nodeType === "number"
84
+ ) {
85
+ parent.appendChild(value as Node);
86
+ return;
87
+ }
88
+
89
+ if (Array.isArray(value)) {
90
+ for (const item of value) appendValue(parent, item);
91
+ }
92
+ // Non-node objects are ignored (avoids "[object HTML…]")
93
+ }
@@ -0,0 +1,43 @@
1
+ import type { Child } from "./h.js";
2
+ import { bindDynamic, type DynamicChild } from "./dynamic.js";
3
+
4
+ /** Normalize JSX children into a flat list. */
5
+ export function normalizeChildren(children: unknown): Child[] {
6
+ if (children == null || children === false || children === true) {
7
+ return [];
8
+ }
9
+ if (Array.isArray(children)) {
10
+ const out: Child[] = [];
11
+ for (const child of children) {
12
+ out.push(...normalizeChildren(child));
13
+ }
14
+ return out;
15
+ }
16
+ return [children as Child];
17
+ }
18
+
19
+ /** Append a Child to a parent (static node, reactive text, or reactive nodes). */
20
+ export function appendChild(parent: ParentNode, child: Child): void {
21
+ if (child == null || child === false || child === true) return;
22
+ if (typeof child === "function") {
23
+ bindDynamic(parent, child as () => DynamicChild);
24
+ return;
25
+ }
26
+ if (typeof child === "string" || typeof child === "number") {
27
+ parent.appendChild(document.createTextNode(String(child)));
28
+ return;
29
+ }
30
+ parent.appendChild(child);
31
+ }
32
+
33
+ /**
34
+ * JSX Fragment — groups children without a wrapper element.
35
+ * Returns a DocumentFragment (children move into the parent on insert).
36
+ */
37
+ export function Fragment(props: { children?: unknown }): DocumentFragment {
38
+ const frag = document.createDocumentFragment();
39
+ for (const child of normalizeChildren(props.children)) {
40
+ appendChild(frag, child);
41
+ }
42
+ return frag;
43
+ }
package/src/h.ts ADDED
@@ -0,0 +1,236 @@
1
+ import { bindAttr, bindClass, bindProp, bindStyle, bindText } from "./bind.js";
2
+ import { bindDynamic, type DynamicChild } from "./dynamic.js";
3
+ import { on } from "./on.js";
4
+ import { createProps } from "./props.js";
5
+
6
+ export type Child =
7
+ | Node
8
+ | DocumentFragment
9
+ | string
10
+ | number
11
+ | boolean
12
+ | null
13
+ | undefined
14
+ | (() => DynamicChild);
15
+
16
+ export type FunctionComponent<P = Record<string, unknown>> = (
17
+ props: P,
18
+ ) => Node | DocumentFragment | null | undefined;
19
+
20
+ export type Props = {
21
+ /** Static or reactive text content (sets textContent). */
22
+ text?: string | number | (() => string | number | boolean | null | undefined);
23
+ class?: string | (() => string | Record<string, boolean | undefined | null> | null | undefined);
24
+ className?: string | (() => string | Record<string, boolean | undefined | null> | null | undefined);
25
+ style?:
26
+ | Record<string, string | number | null | undefined>
27
+ | (() => Record<string, string | number | null | undefined> | null | undefined);
28
+ /** ref callback with the created element */
29
+ ref?: (el: HTMLElement) => void;
30
+ children?: unknown;
31
+ [key: string]: unknown;
32
+ };
33
+
34
+ const RESERVED = new Set([
35
+ "text",
36
+ "class",
37
+ "className",
38
+ "style",
39
+ "ref",
40
+ "children",
41
+ ]);
42
+
43
+ /**
44
+ * Create an element — or invoke a function component.
45
+ *
46
+ * Classic JSX (`jsxFactory: "h"`) compiles `<App />` to `h(App, props)`.
47
+ * Automatic runtime uses `jsx()` which also supports function types.
48
+ */
49
+ export function h<K extends keyof HTMLElementTagNameMap>(
50
+ tag: K,
51
+ props?: Props | null,
52
+ ...children: Child[]
53
+ ): HTMLElementTagNameMap[K];
54
+
55
+ export function h(
56
+ tag: string,
57
+ props?: Props | null,
58
+ ...children: Child[]
59
+ ): HTMLElement;
60
+
61
+ export function h<P extends Record<string, unknown>>(
62
+ tag: FunctionComponent<P>,
63
+ props?: (P & Props) | null,
64
+ ...children: Child[]
65
+ ): Node | DocumentFragment;
66
+
67
+ export function h(
68
+ tag: string | FunctionComponent,
69
+ props?: Props | null,
70
+ ...children: Child[]
71
+ ): Node | DocumentFragment {
72
+ // Function component (classic JSX: h(App, props, ...children))
73
+ if (typeof tag === "function") {
74
+ const raw: Record<string, unknown> = { ...(props ?? {}) };
75
+ if (children.length === 1) {
76
+ raw.children = children[0];
77
+ } else if (children.length > 1) {
78
+ raw.children = children;
79
+ }
80
+ const result = tag(createProps(raw) as never);
81
+ if (result == null) {
82
+ return document.createComment("powers");
83
+ }
84
+ return result;
85
+ }
86
+
87
+ if (typeof tag !== "string" || tag === "") {
88
+ throw new Error(
89
+ `[powers/dom] h() expected a tag name string or component function, got: ${typeof tag}`,
90
+ );
91
+ }
92
+
93
+ const el = document.createElement(tag);
94
+
95
+ if (props) {
96
+ applyProps(el, props);
97
+ }
98
+
99
+ for (const child of children) {
100
+ appendChild(el, child);
101
+ }
102
+
103
+ // Also support props.children (automatic runtime path sometimes)
104
+ if (props?.children != null && children.length === 0) {
105
+ const c = props.children;
106
+ if (Array.isArray(c)) {
107
+ for (const item of c) appendChild(el, item as Child);
108
+ } else {
109
+ appendChild(el, c as Child);
110
+ }
111
+ }
112
+
113
+ return el;
114
+ }
115
+
116
+ /** Create a reactive or static text node. */
117
+ export function text(
118
+ value: string | number | (() => string | number | boolean | null | undefined),
119
+ ): Text {
120
+ const node = document.createTextNode("");
121
+ if (typeof value === "function") {
122
+ bindText(node, value);
123
+ } else {
124
+ node.data = String(value);
125
+ }
126
+ return node;
127
+ }
128
+
129
+ function applyProps(el: HTMLElement, props: Props): void {
130
+ for (const key of Object.keys(props)) {
131
+ if (RESERVED.has(key)) continue;
132
+ const value = props[key];
133
+
134
+ if (key.startsWith("on") && key.length > 2 && typeof value === "function") {
135
+ // onClick → click, onMouseDown → mousedown
136
+ const event = key.slice(2).toLowerCase();
137
+ on(el, event, value as EventListener);
138
+ continue;
139
+ }
140
+
141
+ if (typeof value === "function") {
142
+ if (isDomProp(key)) {
143
+ bindProp(el, key, value as () => unknown);
144
+ } else {
145
+ bindAttr(
146
+ el,
147
+ toAttrName(key),
148
+ value as () => string | number | boolean | null | undefined,
149
+ );
150
+ }
151
+ continue;
152
+ }
153
+
154
+ if (isDomProp(key)) {
155
+ (el as unknown as Record<string, unknown>)[key] = value;
156
+ } else if (value === true) {
157
+ el.setAttribute(toAttrName(key), "");
158
+ } else if (value !== false && value != null) {
159
+ el.setAttribute(toAttrName(key), String(value));
160
+ }
161
+ }
162
+
163
+ const classValue = props.class ?? props.className;
164
+ if (classValue !== undefined) {
165
+ if (typeof classValue === "function") {
166
+ bindClass(el, classValue);
167
+ } else if (classValue != null) {
168
+ el.className = classValue;
169
+ }
170
+ }
171
+
172
+ if (props.style !== undefined) {
173
+ if (typeof props.style === "function") {
174
+ bindStyle(el, props.style);
175
+ } else if (props.style) {
176
+ bindStyle(el, () => props.style as Record<string, string | number>);
177
+ }
178
+ }
179
+
180
+ if (props.text !== undefined) {
181
+ if (typeof props.text === "function") {
182
+ bindText(el, props.text);
183
+ } else {
184
+ el.textContent = String(props.text);
185
+ }
186
+ }
187
+
188
+ if (typeof props.ref === "function") {
189
+ props.ref(el);
190
+ }
191
+ }
192
+
193
+ function appendChild(parent: ParentNode, child: Child): void {
194
+ if (child == null || child === false || child === true) return;
195
+ if (typeof child === "function") {
196
+ // May return text *or* DOM nodes (Tabs/Accordion content, etc.)
197
+ bindDynamic(parent, child as () => DynamicChild);
198
+ return;
199
+ }
200
+ if (typeof child === "string" || typeof child === "number") {
201
+ parent.appendChild(document.createTextNode(String(child)));
202
+ return;
203
+ }
204
+ parent.appendChild(child);
205
+ }
206
+
207
+ const DOM_PROPS = new Set([
208
+ "value",
209
+ "checked",
210
+ "selected",
211
+ "disabled",
212
+ "hidden",
213
+ "readOnly",
214
+ "multiple",
215
+ "muted",
216
+ "controls",
217
+ "defaultValue",
218
+ "defaultChecked",
219
+ "innerHTML",
220
+ "tabIndex",
221
+ "spellcheck",
222
+ ]);
223
+
224
+ function isDomProp(name: string): boolean {
225
+ return DOM_PROPS.has(name);
226
+ }
227
+
228
+ function toAttrName(key: string): string {
229
+ if (key.startsWith("aria")) {
230
+ return key.replace(/([A-Z])/g, "-$1").toLowerCase();
231
+ }
232
+ if (key.startsWith("data") && key.length > 4 && key[4] !== "-") {
233
+ return key.replace(/([A-Z])/g, "-$1").toLowerCase();
234
+ }
235
+ return key;
236
+ }
package/src/index.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @lab206/dom
3
+ *
4
+ * Thin, explicit DOM bindings over @lab206/core.
5
+ *
6
+ * Learn order:
7
+ * mount → h / JSX → component → reactive props → Show / For
8
+ *
9
+ * Form controls in @lab206/ui use `bind={signal}` for two-way state —
10
+ * `bind` is intentionally not unwrapped by createProps (see props.ts).
11
+ */
12
+
13
+ export { mount } from "./mount.js";
14
+ export type { MountResult } from "./mount.js";
15
+
16
+ export { h, text } from "./h.js";
17
+ export type { Child, Props, FunctionComponent } from "./h.js";
18
+
19
+ export { Fragment, normalizeChildren, appendChild } from "./fragment.js";
20
+
21
+ export {
22
+ bindText,
23
+ bindAttr,
24
+ bindProp,
25
+ bindClass,
26
+ bindStyle,
27
+ } from "./bind.js";
28
+
29
+ export { bindDynamic } from "./dynamic.js";
30
+ export type { DynamicChild } from "./dynamic.js";
31
+
32
+ export { on } from "./on.js";
33
+ export { show } from "./show.js";
34
+ export { list } from "./list.js";
35
+ export type { ListOptions } from "./list.js";
36
+ export { insert, remove } from "./insert.js";
37
+
38
+ export { component, Show, For } from "./component.js";
39
+ export type {
40
+ Component,
41
+ ComponentProps,
42
+ ComponentSetup,
43
+ } from "./component.js";
44
+
45
+ export {
46
+ createProps,
47
+ mergeProps,
48
+ splitProps,
49
+ unwrapProp,
50
+ getRawProp,
51
+ isSignal,
52
+ isReactiveProps,
53
+ } from "./props.js";
54
+ export type { ReactiveProps } from "./props.js";
package/src/insert.ts ADDED
@@ -0,0 +1,14 @@
1
+ /** Append or insert a node. Returns the node for chaining. */
2
+ export function insert<T extends Node>(
3
+ parent: ParentNode,
4
+ child: T,
5
+ anchor: Node | null = null,
6
+ ): T {
7
+ parent.insertBefore(child, anchor);
8
+ return child;
9
+ }
10
+
11
+ /** Remove a node from its parent if attached. */
12
+ export function remove(node: Node): void {
13
+ node.parentNode?.removeChild(node);
14
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Automatic JSX runtime for Powers.
3
+ *
4
+ * Vite / TypeScript:
5
+ * jsx: "react-jsx"
6
+ * jsxImportSource: "@lab206/dom"
7
+ *
8
+ * Function components receive **reactive props** via `createProps`
9
+ * (unless they already wrapped themselves with `component()`).
10
+ */
11
+ import { h, type Props } from "./h.js";
12
+ import { Fragment, normalizeChildren } from "./fragment.js";
13
+ import { createProps } from "./props.js";
14
+
15
+ export { Fragment };
16
+
17
+ export type FunctionComponent<P = Record<string, unknown>> = (
18
+ props: P,
19
+ ) => Node | DocumentFragment | null | undefined;
20
+
21
+ /** Marker set on components that already apply createProps. */
22
+ export const REACTIVE_PROPS = Symbol.for("powers.reactiveProps");
23
+
24
+ export function jsx(
25
+ type: string | FunctionComponent<Record<string, unknown>>,
26
+ props: (Props & { children?: unknown }) | null,
27
+ _key?: string | number,
28
+ ): Node | DocumentFragment {
29
+ return create(type, props);
30
+ }
31
+
32
+ export function jsxs(
33
+ type: string | FunctionComponent<Record<string, unknown>>,
34
+ props: (Props & { children?: unknown }) | null,
35
+ _key?: string | number,
36
+ ): Node | DocumentFragment {
37
+ return create(type, props);
38
+ }
39
+
40
+ export function jsxDEV(
41
+ type: string | FunctionComponent<Record<string, unknown>>,
42
+ props: (Props & { children?: unknown }) | null,
43
+ _key?: string | number,
44
+ ): Node | DocumentFragment {
45
+ return create(type, props);
46
+ }
47
+
48
+ function create(
49
+ type: string | FunctionComponent<Record<string, unknown>>,
50
+ props: (Props & { children?: unknown }) | null,
51
+ ): Node | DocumentFragment {
52
+ const p = props ?? {};
53
+ const { children, ...rest } = p;
54
+
55
+ if (typeof type === "function") {
56
+ // Always pass through createProps so bare function components get
57
+ // reactive field access. component() also wraps — double-wrap is OK
58
+ // (outer raw object, inner Proxy of raw; component's createProps runs first).
59
+ const reactive = createProps(p as Record<string, unknown>);
60
+ const result = type(reactive as Record<string, unknown>);
61
+ if (result == null) {
62
+ return document.createComment("powers");
63
+ }
64
+ return result;
65
+ }
66
+
67
+ const childList = normalizeChildren(children);
68
+ return h(type, rest as Props, ...childList);
69
+ }
70
+
71
+ export namespace JSX {
72
+ export type Element = Node | DocumentFragment;
73
+ export type ElementType = string | FunctionComponent<any>;
74
+
75
+ export interface ElementAttributesProperty {
76
+ props: unknown;
77
+ }
78
+ export interface ElementChildrenAttribute {
79
+ children: unknown;
80
+ }
81
+
82
+ export type LibraryManagedAttributes<C, P> = P;
83
+
84
+ export interface IntrinsicAttributes {
85
+ key?: string | number;
86
+ }
87
+
88
+ export type IntrinsicElements = {
89
+ [K in keyof HTMLElementTagNameMap]: Props & {
90
+ children?: unknown;
91
+ } & {
92
+ [attr: string]: unknown;
93
+ };
94
+ } & {
95
+ [elemName: string]: Props & {
96
+ children?: unknown;
97
+ [attr: string]: unknown;
98
+ };
99
+ };
100
+ }