@tapcue/extension-sdk 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.
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The automatic-JSX factory. `tsconfig` points `jsxImportSource` here, so `<Detail/>` compiles
3
+ * to `jsx(Detail, props)` and this builds a `ViewNode` (view.ts). This is not React: there is no
4
+ * component instance, no VDOM, no reconciler — the factory reads each component's `__tag`,
5
+ * walks props through `toWire`, and returns a serializable tree of bindings.
6
+ */
7
+
8
+ import { For, Fragment, Show } from "./components.js";
9
+ import { itemIndex, itemProxy, toWire } from "./reactive.js";
10
+ import type { PropValue, ViewNode } from "./view.js";
11
+
12
+ export { Fragment };
13
+
14
+ function tagOf(type: unknown): string | null {
15
+ if ((typeof type === "function" || typeof type === "object") && type !== null) {
16
+ const tag = (type as { __tag?: unknown }).__tag;
17
+ if (typeof tag === "string") return tag;
18
+ }
19
+ return null;
20
+ }
21
+
22
+ function isViewNode(value: unknown): value is ViewNode {
23
+ return typeof value === "object" && value !== null && typeof (value as ViewNode).type === "string";
24
+ }
25
+
26
+ function normalizeChildren(children: unknown): ViewNode[] {
27
+ const out: ViewNode[] = [];
28
+ const visit = (child: unknown): void => {
29
+ if (child === null || child === undefined || child === false || child === true) return;
30
+ if (Array.isArray(child)) {
31
+ for (const nested of child) visit(nested);
32
+ return;
33
+ }
34
+ if (isViewNode(child)) {
35
+ // A fragment carries no node of its own: splice its children into the parent.
36
+ if (child.type === "fragment") {
37
+ for (const grandchild of child.children ?? []) out.push(grandchild);
38
+ return;
39
+ }
40
+ out.push(child);
41
+ }
42
+ // Raw strings/numbers have no semantic home as children (content is a prop, e.g.
43
+ // `<Markdown text=…/>`); they are dropped rather than guessed at.
44
+ };
45
+ visit(children);
46
+ return out;
47
+ }
48
+
49
+ function withKey(node: ViewNode, key: unknown): ViewNode {
50
+ if (typeof key === "string" && key.length > 0) node.key = key;
51
+ return node;
52
+ }
53
+
54
+ function createNode(type: unknown, rawProps: Record<string, unknown> | null, key: unknown): ViewNode {
55
+ const props = rawProps ?? {};
56
+ const tag = tagOf(type);
57
+ if (tag === null) {
58
+ throw new Error("JSX type is not a Tapcue component — only the exported component set may be used");
59
+ }
60
+
61
+ if (type === Fragment || tag === "fragment") {
62
+ return { type: "fragment", children: normalizeChildren(props.children) };
63
+ }
64
+
65
+ // `<For each={…}>{(item, index) => …}</For>`: the child render function is called once with a
66
+ // proxy item so the shell can instantiate the template per element without waking the isolate.
67
+ if (type === For) {
68
+ const render = props.children as ((item: unknown, index: number) => ViewNode) | undefined;
69
+ if (typeof render !== "function") {
70
+ throw new Error("<For> expects a single render-function child");
71
+ }
72
+ const template = render(itemProxy(), itemIndex);
73
+ return withKey({ type: "for", props: { each: toWire(props.each) }, children: [template] }, key);
74
+ }
75
+
76
+ // `<Show when={…} fallback={…}>…</Show>`: `when` is a bound boolean; the fallback is a subtree,
77
+ // carried in `slots` rather than wired as a value.
78
+ if (type === Show) {
79
+ const node: ViewNode = { type: "show", props: { when: toWire(props.when) } };
80
+ const children = normalizeChildren(props.children);
81
+ if (children.length > 0) node.children = children;
82
+ if (isViewNode(props.fallback)) node.slots = { fallback: props.fallback };
83
+ return withKey(node, key);
84
+ }
85
+
86
+ const { children, fallback: _fallback, ...rest } = props;
87
+ const wired: Record<string, PropValue> = {};
88
+ for (const [name, value] of Object.entries(rest)) {
89
+ if (value === undefined) continue;
90
+ wired[name] = toWire(value);
91
+ }
92
+ const node: ViewNode = { type: tag };
93
+ if (Object.keys(wired).length > 0) node.props = wired;
94
+ const kids = normalizeChildren(children);
95
+ if (kids.length > 0) node.children = kids;
96
+ return withKey(node, key);
97
+ }
98
+
99
+ export function jsx(type: unknown, props: Record<string, unknown>, key?: unknown): ViewNode {
100
+ return createNode(type, props, key);
101
+ }
102
+
103
+ export const jsxs = jsx;
104
+
105
+ /** The development runtime entry (`jsx: "react-jsxdev"`, which is what Vite/Vitest default to). */
106
+ export function jsxDEV(type: unknown, props: Record<string, unknown>, key?: unknown): ViewNode {
107
+ return createNode(type, props, key);
108
+ }
109
+
110
+ // eslint-disable-next-line @typescript-eslint/no-namespace
111
+ export namespace JSX {
112
+ export type Element = ViewNode;
113
+ export interface ElementChildrenAttribute {
114
+ children: Record<string, never>;
115
+ }
116
+ // Only the exported component set is renderable — no lowercase intrinsic tags.
117
+ export type IntrinsicElements = Record<string, never>;
118
+ }