ziex 0.1.0-dev.460 → 0.1.0-dev.522

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/README.md CHANGED
@@ -89,9 +89,10 @@ const zx = @import("zx");
89
89
  - [x] Virtual DOM and diffing
90
90
  - [x] Rendering only changed nodes
91
91
  - [x] `on`event handler
92
- - [ ] State managment
93
- - [ ] Hydration
92
+ - [x] State managment
93
+ - [x] Hydration
94
94
  - [ ] Lifecycle hook
95
+ - [ ] Server Actions
95
96
  - [x] Client Side Rendering (CSR) via React
96
97
  - [x] Routing
97
98
  - [x] File-system Routing
@@ -116,7 +117,7 @@ const zx = @import("zx");
116
117
  - [x] HTML (optimized by default)
117
118
  - [ ] Middleware (_cancalled_)
118
119
  - [ ] Caching (configurable)
119
- - [ ] Component
120
+ - [x] Component
120
121
  - [ ] Layout
121
122
  - [x] Page
122
123
  - [ ] Assets
@@ -144,8 +145,8 @@ const zx = @import("zx");
144
145
  - [x] `update` Update the version of ZX dependency
145
146
  - [x] `upgrade` Upgrade the version of ZX CLI
146
147
  - [ ] Platform
147
- - [x] Web Server
148
- - [x] Web Browser
148
+ - [x] Server
149
+ - [x] Browser
149
150
  - [ ] iOS
150
151
  - [ ] Android
151
152
  - [ ] macOS
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/index.ts
2
2
  var zx = {
3
3
  name: "zx",
4
- version: "0.1.0-dev.460",
4
+ version: "0.1.0-dev.522",
5
5
  description: "ZX is a framework for building web applications with Zig.",
6
6
  repository: "https://github.com/nurulhudaapon/zx",
7
7
  fingerprint: 14616285862371232000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ziex",
3
- "version": "0.1.0-dev.460",
3
+ "version": "0.1.0-dev.522",
4
4
  "description": "ZX is a framework for building web applications with Zig.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -12,7 +12,7 @@
12
12
  "homepage": "https://github.com/nurulhudaapon/zx",
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "git+https://github.com/nurulhudaapon/zx.git"
15
+ "url": "https://github.com/nurulhudaapon/zx.git"
16
16
  },
17
17
  "keywords": [
18
18
  "zx",
@@ -30,4 +30,4 @@
30
30
  "license": "MIT",
31
31
  "module": "index.js",
32
32
  "types": "index.d.ts"
33
- }
33
+ }
package/react/dom.d.ts CHANGED
@@ -8,14 +8,15 @@ export type PreparedComponent = {
8
8
  /**
9
9
  * The HTML element where the component should be rendered.
10
10
  *
11
- * This is the DOM node that was server-rendered by ZX with the component's unique ID.
12
- * The element already exists in the DOM and contains the server-rendered fallback content.
13
- * React will hydrate this element, replacing its contents with the interactive component.
11
+ * This is a container element created between the comment markers. The server-rendered
12
+ * content is moved into this container, and React will hydrate it with the interactive component.
14
13
  *
15
14
  * @example
16
15
  * ```tsx
17
- * // The DOM node corresponds to HTML like:
18
- * // <div id="zx-dcde04c415da9d1b15ca2690d8b497ae" data-props="...">...</div>
16
+ * // Server-rendered HTML with comment markers:
17
+ * // <!--$zx-abc123-0 CounterComponent {"max_count":10}-->
18
+ * // <button>0</button>
19
+ * // <!--/$zx-abc123-0-->
19
20
  *
20
21
  * const { domNode } = await prepareComponent(component);
21
22
  * createRoot(domNode).render(<Component {...props} />);
@@ -23,42 +24,28 @@ export type PreparedComponent = {
23
24
  */
24
25
  domNode: HTMLElement;
25
26
  /**
26
- * Component props parsed from the server-rendered HTML.
27
+ * Component props parsed from the comment marker.
27
28
  *
28
- * Props are extracted from the `data-props` attribute (JSON-encoded) on the component's
29
- * container element. If the component has children from server side then they are automatically converted to
30
- * `dangerouslySetInnerHTML` for React compatibility.
29
+ * Props are extracted from the start comment marker content. The comment format is:
30
+ * `<!--$id name props-->` where props is JSON-encoded.
31
31
  *
32
32
  * @example
33
33
  * ```tsx
34
34
  * // Server-rendered HTML:
35
- * // <div data-props='{"max_count":10,"label":"Counter"}' data-children="<span>0</span>">...</div>
35
+ * // <!--$zx-abc123-0 CounterComponent {"max_count":10,"label":"Counter"}-->
36
+ * // <button>0</button>
37
+ * // <!--/$zx-abc123-0-->
36
38
  *
37
39
  * const { props } = await prepareComponent(component);
38
- * // props = {
39
- * // max_count: 10,
40
- * // label: "Counter",
41
- * // dangerouslySetInnerHTML: { __html: "<span>0</span>" }
42
- * // }
40
+ * // props = { max_count: 10, label: "Counter" }
43
41
  * ```
44
42
  */
45
43
  props: Record<string, any> & {
46
44
  /**
47
45
  * React's special prop for setting inner HTML directly.
48
46
  *
49
- * Automatically added when the component has children in the ZX file. The HTML string
50
- * is extracted from the `data-children` attribute on the server-rendered element.
51
- *
52
- * @example
53
- * ```tsx
54
- * // In ZX file:
55
- * <MyComponent @rendering={.react}>
56
- * <p>Child content</p>
57
- * </MyComponent>
58
- *
59
- * // Results in:
60
- * // props.dangerouslySetInnerHTML = { __html: "<p>Child content</p>" }
61
- * ```
47
+ * May be used when the component has server-rendered children that should
48
+ * be preserved during hydration.
62
49
  */
63
50
  dangerouslySetInnerHTML?: {
64
51
  __html: string;
@@ -85,12 +72,12 @@ export type PreparedComponent = {
85
72
  Component: (props: any) => React.ReactElement;
86
73
  };
87
74
  /**
88
- * Prepares a client-side component for hydration by locating its DOM container, extracting
89
- * props and children from server-rendered HTML attributes, and lazy-loading the component module.
75
+ * Prepares a client-side component for hydration by locating its comment markers, extracting
76
+ * props from the marker content, and lazy-loading the component module.
90
77
  *
91
78
  * This function bridges server-rendered HTML (from ZX's Zig transpiler) and client-side React
92
- * components. It reads data attributes (`data-props`, `data-children`) from the DOM element
93
- * with the component's unique ID, then lazy-loads the component module for rendering.
79
+ * components. It searches for comment markers in the format `<!--$id name props-->...<!--/$id-->`
80
+ * and extracts the component data from the marker content.
94
81
  *
95
82
  * @param component - The component metadata containing ID, import function, and other metadata
96
83
  * needed to locate and load the component
@@ -98,8 +85,8 @@ export type PreparedComponent = {
98
85
  * @returns A Promise that resolves to a `PreparedComponent` object containing the DOM node,
99
86
  * parsed props, and the loaded React component function
100
87
  *
101
- * @throws {Error} If the component's container element cannot be found in the DOM. This typically
102
- * happens if the component ID doesn't match any element, the script runs before
88
+ * @throws {Error} If the component's comment markers cannot be found in the DOM. This typically
89
+ * happens if the component ID doesn't match any marker, the script runs before
103
90
  * the HTML is loaded, or there's a mismatch between server and client metadata
104
91
  *
105
92
  * @example
@@ -118,18 +105,58 @@ export type PreparedComponent = {
118
105
  *
119
106
  * @example
120
107
  * ```tsx
121
- * // With async/await:
122
- * async function hydrateComponent(component: ComponentMetadata) {
123
- * try {
124
- * const { domNode, Component, props } = await prepareComponent(component);
125
- * createRoot(domNode).render(<Component {...props} />);
126
- * } catch (error) {
127
- * console.error(`Failed to hydrate ${component.name}:`, error);
128
- * }
129
- * }
130
- *
131
- * Promise.all(components.map(hydrateComponent));
108
+ * // Server-rendered HTML with comment markers:
109
+ * // <!--$zx-abc123-0 CounterComponent {"max_count":10}-->
110
+ * // <button>0</button>
111
+ * // <!--/$zx-abc123-0-->
132
112
  * ```
133
113
  */
134
114
  export declare function prepareComponent(component: ComponentMetadata): Promise<PreparedComponent>;
135
115
  export declare function filterComponents(components: ComponentMetadata[]): ComponentMetadata[];
116
+ /**
117
+ * Discovered component from DOM traversal.
118
+ * Contains all metadata needed to hydrate the component.
119
+ */
120
+ export type DiscoveredComponent = {
121
+ id: string;
122
+ name: string;
123
+ props: Record<string, any>;
124
+ container: HTMLElement;
125
+ };
126
+ /**
127
+ * Finds all React component markers in the DOM and returns their metadata.
128
+ *
129
+ * This is a DOM-first approach that:
130
+ * 1. Walks the DOM once to find all `<!--$id-->` markers
131
+ * 2. Reads metadata from companion `<script data-zx="id">` elements
132
+ * 3. Creates containers for React to render into
133
+ *
134
+ * @returns Array of discovered components with their containers and props
135
+ */
136
+ export declare function discoverComponents(): DiscoveredComponent[];
137
+ /**
138
+ * Component registry mapping component names to their import functions.
139
+ */
140
+ export type ComponentRegistry = Record<string, () => Promise<(props: any) => React.ReactElement>>;
141
+ /**
142
+ * Hydrates all React components found in the DOM.
143
+ *
144
+ * This is the simplest way to hydrate React islands - it automatically:
145
+ * 1. Discovers all component markers in the DOM
146
+ * 2. Looks up components by name in the registry
147
+ * 3. Renders each component into its container
148
+ *
149
+ * @param registry - Map of component names to import functions
150
+ * @param render - Function to render a component (e.g., `(el, Component, props) => createRoot(el).render(<Component {...props} />)`)
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * import { hydrateAll } from "ziex/react";
155
+ *
156
+ * hydrateAll({
157
+ * CounterComponent: () => import("./Counter"),
158
+ * ToggleComponent: () => import("./Toggle"),
159
+ * });
160
+ * ```
161
+ */
162
+ export declare function hydrateAll(registry: ComponentRegistry, render: (container: HTMLElement, Component: (props: any) => React.ReactElement, props: Record<string, any>) => void): Promise<void>;
package/react/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { prepareComponent, filterComponents, type PreparedComponent } from "./dom";
1
+ export { prepareComponent, filterComponents, discoverComponents, hydrateAll, type PreparedComponent, type DiscoveredComponent, type ComponentRegistry, } from "./dom";
2
2
  export type { ComponentMetadata } from "./types";
package/react/index.js CHANGED
@@ -1,13 +1,56 @@
1
1
  // src/react/dom.ts
2
+ function findCommentMarker(id) {
3
+ const startMarker = `$${id}`;
4
+ const endMarker = `/$${id}`;
5
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);
6
+ let startComment = null;
7
+ let endComment = null;
8
+ let node;
9
+ while (node = walker.nextNode()) {
10
+ const text = node.textContent?.trim() || "";
11
+ if (text === startMarker) {
12
+ startComment = node;
13
+ }
14
+ if (text === endMarker) {
15
+ endComment = node;
16
+ break;
17
+ }
18
+ }
19
+ if (startComment && endComment) {
20
+ return { startComment, endComment };
21
+ }
22
+ return null;
23
+ }
24
+ function getComponentMetadata(id) {
25
+ const script = document.querySelector(`script[data-zx="${id}"]`);
26
+ if (script?.textContent) {
27
+ try {
28
+ const data = JSON.parse(script.textContent);
29
+ return { name: data.name || "", props: data.props || {} };
30
+ } catch {}
31
+ }
32
+ return { name: "", props: {} };
33
+ }
34
+ function createContainerBetweenMarkers(startComment, endComment) {
35
+ const container = document.createElement("div");
36
+ container.style.display = "contents";
37
+ let current = startComment.nextSibling;
38
+ while (current && current !== endComment) {
39
+ const next = current.nextSibling;
40
+ container.appendChild(current);
41
+ current = next;
42
+ }
43
+ endComment.parentNode?.insertBefore(container, endComment);
44
+ return container;
45
+ }
2
46
  async function prepareComponent(component) {
3
- const domNode = document.getElementById(component.id);
4
- if (!domNode)
5
- throw new Error(`Root element ${component.id} not found`, { cause: component });
6
- const props = JSON.parse(domNode.getAttribute("data-props") || "{}");
7
- const htmlChildren = domNode.getAttribute("data-children") ?? undefined;
8
- if (htmlChildren) {
9
- props.dangerouslySetInnerHTML = { __html: htmlChildren };
47
+ const marker = findCommentMarker(component.id);
48
+ if (!marker) {
49
+ throw new Error(`Comment marker for ${component.id} not found`, { cause: component });
10
50
  }
51
+ const metadata = getComponentMetadata(component.id);
52
+ const props = metadata.props;
53
+ const domNode = createContainerBetweenMarkers(marker.startComment, marker.endComment);
11
54
  const Component = await component.import();
12
55
  return { domNode, props, Component };
13
56
  }
@@ -15,7 +58,49 @@ function filterComponents(components) {
15
58
  const currentPath = window.location.pathname;
16
59
  return components.filter((component) => component.route === currentPath || !component.route);
17
60
  }
61
+ function discoverComponents() {
62
+ const components = [];
63
+ const scripts = Array.from(document.querySelectorAll("script[data-zx]"));
64
+ for (const script of scripts) {
65
+ const id = script.getAttribute("data-zx");
66
+ if (!id)
67
+ continue;
68
+ let name = "";
69
+ let props = {};
70
+ try {
71
+ const data = JSON.parse(script.textContent || "{}");
72
+ name = data.name || "";
73
+ props = data.props || {};
74
+ } catch {
75
+ continue;
76
+ }
77
+ const marker = findCommentMarker(id);
78
+ if (!marker)
79
+ continue;
80
+ const container = createContainerBetweenMarkers(marker.startComment, marker.endComment);
81
+ components.push({ id, name, props, container });
82
+ }
83
+ return components;
84
+ }
85
+ async function hydrateAll(registry, render) {
86
+ const components = discoverComponents();
87
+ await Promise.all(components.map(async ({ name, props, container }) => {
88
+ const importer = registry[name];
89
+ if (!importer) {
90
+ console.warn(`Component "${name}" not found in registry`);
91
+ return;
92
+ }
93
+ try {
94
+ const Component = await importer();
95
+ render(container, Component, props);
96
+ } catch (error) {
97
+ console.error(`Failed to hydrate "${name}":`, error);
98
+ }
99
+ }));
100
+ }
18
101
  export {
19
102
  prepareComponent,
20
- filterComponents
103
+ hydrateAll,
104
+ filterComponents,
105
+ discoverComponents
21
106
  };
package/react/types.d.ts CHANGED
@@ -85,17 +85,16 @@ export type ComponentMetadata = {
85
85
  */
86
86
  route: string | null;
87
87
  /**
88
- * A unique HTML element identifier for the component's root DOM node.
88
+ * A unique identifier for the component's hydration boundary.
89
89
  *
90
90
  * This ID is generated by hashing the component's path and name using MD5, then formatting it
91
- * as a hex string with the "zx-" prefix. The ID is used to locate the component's container
92
- * element in the DOM during client-side hydration.
91
+ * as a hex string with the "zx-" prefix and a counter suffix. The ID is used to locate the
92
+ * component's comment markers in the DOM during client-side hydration.
93
93
  *
94
- * The ID format is: `zx-{32 hex characters}` (e.g., `zx-dcde04c415da9d1b15ca2690d8b497ae`)
94
+ * The ID format is: `zx-{32 hex characters}-{counter}` (e.g., `zx-dcde04c415da9d1b15ca2690d8b497ae-0`)
95
95
  *
96
- * When the same component is used multiple times on a page, each instance gets the same base ID
97
- * since they share the same path and name. The ZX runtime uses this ID along with `data-props`
98
- * and `data-children` attributes to hydrate the component with the correct props and children.
96
+ * The ZX runtime renders components with comment markers in the format:
97
+ * `<!--$id name props-->...<!--/$id-->`
99
98
  *
100
99
  * @example
101
100
  * ```tsx
@@ -103,15 +102,13 @@ export type ComponentMetadata = {
103
102
  * {
104
103
  * name: "CounterComponent",
105
104
  * path: "./components/Counter.tsx",
106
- * id: "zx-dcde04c415da9d1b15ca2690d8b497ae"
105
+ * id: "zx-dcde04c415da9d1b15ca2690d8b497ae-0"
107
106
  * }
108
107
  *
109
- * // Generated HTML:
110
- * <div id="zx-dcde04c415da9d1b15ca2690d8b497ae"
111
- * data-props='{"max_count":10}'
112
- * data-children="...">
113
- * <!-- Server-rendered content -->
114
- * </div>
108
+ * // Generated HTML with comment markers:
109
+ * <!--$zx-dcde04c415da9d1b15ca2690d8b497ae-0 CounterComponent {"max_count":10}-->
110
+ * <button>0</button>
111
+ * <!--/$zx-dcde04c415da9d1b15ca2690d8b497ae-0-->
115
112
  * ```
116
113
  */
117
114
  id: string;
package/wasm/index.d.ts CHANGED
@@ -1,38 +1,52 @@
1
- declare class ZXInstance {
1
+ import { ZigJS } from "../../../../vendor/jsz/js/src";
2
+ /**
3
+ * ZX Client Bridge - Unified JS↔WASM communication layer
4
+ * Handles events, fetch, timers, and other async callbacks using jsz
5
+ */
6
+ export declare const CallbackType: {
7
+ readonly Event: 0;
8
+ readonly FetchSuccess: 1;
9
+ readonly FetchError: 2;
10
+ readonly Timeout: 3;
11
+ readonly Interval: 4;
12
+ };
13
+ export type CallbackTypeValue = typeof CallbackType[keyof typeof CallbackType];
14
+ export declare const jsz: ZigJS;
15
+ /** Store a value using jsz.storeValue and get the 64-bit reference. */
16
+ export declare function storeValueGetRef(val: any): bigint;
17
+ /** ZX Bridge - provides JS APIs that callback into WASM */
18
+ export declare class ZxBridge {
2
19
  #private;
3
- exports: WebAssembly.Exports;
4
- events: Event[];
5
- constructor({ exports, events }: ZXInstanceOptions);
6
- addEvent(event: Event): number;
7
- /**
8
- * Initialize event delegation on a root element
9
- * This attaches a single event listener for each event type at the root,
10
- * and uses __zx_ref to look up the corresponding VElement in WASM
11
- */
12
- initEventDelegation(rootSelector?: string): void;
13
- /** Get the VElement ID from a DOM element */
14
- getZxRef(element: HTMLElement): number | undefined;
20
+ constructor(exports: WebAssembly.Exports);
21
+ /** Fetch a URL and callback with the response */
22
+ fetch(urlPtr: number, urlLen: number, callbackId: bigint): void;
23
+ /** Set a timeout and callback when it fires */
24
+ setTimeout(callbackId: bigint, delayMs: number): void;
25
+ /** Set an interval and callback each time it fires */
26
+ setInterval(callbackId: bigint, intervalMs: number): void;
27
+ /** Clear an interval */
28
+ clearInterval(callbackId: bigint): void;
29
+ /** Handle a DOM event (called by event delegation) */
30
+ eventbridge(velementId: bigint, eventTypeId: number, event: Event): void;
31
+ /** Create the import object for WASM instantiation */
32
+ static createImportObject(bridgeRef: {
33
+ current: ZxBridge | null;
34
+ }): WebAssembly.Imports;
15
35
  }
16
- export declare function init(options?: InitOptions): Promise<void>;
36
+ /** Initialize event delegation */
37
+ export declare function initEventDelegation(bridge: ZxBridge, rootSelector?: string): void;
17
38
  export type InitOptions = {
18
- /** URL to the WASM file (default: /assets/main.wasm) */
19
39
  url?: string;
20
- /** CSS selector for the event delegation root element (default: 'body') */
21
40
  eventDelegationRoot?: string;
41
+ importObject?: WebAssembly.Imports;
22
42
  };
23
- type ZXInstanceOptions = {
24
- exports: ZXInstance['exports'];
25
- events?: ZXInstance['events'];
26
- };
43
+ /** Initialize WASM with the ZX Bridge */
44
+ export declare function init(options?: InitOptions): Promise<{
45
+ source: WebAssembly.WebAssemblyInstantiatedSource;
46
+ bridge: ZxBridge;
47
+ }>;
27
48
  declare global {
28
- interface Window {
29
- _zx: ZXInstance;
30
- }
31
49
  interface HTMLElement {
32
- /**
33
- * The VElement ID of the element
34
- */
35
50
  __zx_ref?: number;
36
51
  }
37
52
  }
38
- export {};
package/wasm/index.js CHANGED
@@ -168,8 +168,102 @@ class ZigJS {
168
168
  }
169
169
  }
170
170
  // src/wasm/index.ts
171
- var DEFAULT_URL = "/assets/main.wasm";
172
- var MAX_EVENTS = 100;
171
+ var CallbackType = {
172
+ Event: 0,
173
+ FetchSuccess: 1,
174
+ FetchError: 2,
175
+ Timeout: 3,
176
+ Interval: 4
177
+ };
178
+ var jsz = new ZigJS;
179
+ var tempRefBuffer = new ArrayBuffer(8);
180
+ var tempRefView = new DataView(tempRefBuffer);
181
+ function storeValueGetRef(val) {
182
+ const originalMemory = jsz.memory;
183
+ jsz.memory = { buffer: tempRefBuffer };
184
+ jsz.storeValue(0, val);
185
+ jsz.memory = originalMemory;
186
+ return tempRefView.getBigUint64(0, true);
187
+ }
188
+ function readString(ptr, len) {
189
+ const memory = new Uint8Array(jsz.memory.buffer);
190
+ return new TextDecoder().decode(memory.slice(ptr, ptr + len));
191
+ }
192
+
193
+ class ZxBridge {
194
+ #exports;
195
+ #nextCallbackId = BigInt(1);
196
+ #intervals = new Map;
197
+ constructor(exports) {
198
+ this.#exports = exports;
199
+ }
200
+ get #handler() {
201
+ return this.#exports.__zx_cb;
202
+ }
203
+ #getNextId() {
204
+ return this.#nextCallbackId++;
205
+ }
206
+ #invoke(type, id, data) {
207
+ const handler = this.#handler;
208
+ if (!handler) {
209
+ console.warn("__zx_cb not exported from WASM");
210
+ return;
211
+ }
212
+ const dataRef = storeValueGetRef(data);
213
+ handler(type, id, dataRef);
214
+ }
215
+ fetch(urlPtr, urlLen, callbackId) {
216
+ const url = readString(urlPtr, urlLen);
217
+ fetch(url).then((response) => response.text()).then((text) => {
218
+ this.#invoke(CallbackType.FetchSuccess, callbackId, text);
219
+ }).catch((error) => {
220
+ this.#invoke(CallbackType.FetchError, callbackId, error.message ?? "Fetch failed");
221
+ });
222
+ }
223
+ setTimeout(callbackId, delayMs) {
224
+ setTimeout(() => {
225
+ this.#invoke(CallbackType.Timeout, callbackId, null);
226
+ }, delayMs);
227
+ }
228
+ setInterval(callbackId, intervalMs) {
229
+ const handle = setInterval(() => {
230
+ this.#invoke(CallbackType.Interval, callbackId, null);
231
+ }, intervalMs);
232
+ this.#intervals.set(callbackId, handle);
233
+ }
234
+ clearInterval(callbackId) {
235
+ const handle = this.#intervals.get(callbackId);
236
+ if (handle !== undefined) {
237
+ clearInterval(handle);
238
+ this.#intervals.delete(callbackId);
239
+ }
240
+ }
241
+ eventbridge(velementId, eventTypeId, event) {
242
+ const eventRef = storeValueGetRef(event);
243
+ const eventbridge = this.#exports.__zx_eventbridge;
244
+ if (eventbridge)
245
+ eventbridge(velementId, eventTypeId, eventRef);
246
+ }
247
+ static createImportObject(bridgeRef) {
248
+ return {
249
+ ...jsz.importObject(),
250
+ __zx: {
251
+ _fetch: (urlPtr, urlLen, callbackId) => {
252
+ bridgeRef.current?.fetch(urlPtr, urlLen, callbackId);
253
+ },
254
+ _setTimeout: (callbackId, delayMs) => {
255
+ bridgeRef.current?.setTimeout(callbackId, delayMs);
256
+ },
257
+ _setInterval: (callbackId, intervalMs) => {
258
+ bridgeRef.current?.setInterval(callbackId, intervalMs);
259
+ },
260
+ _clearInterval: (callbackId) => {
261
+ bridgeRef.current?.clearInterval(callbackId);
262
+ }
263
+ }
264
+ };
265
+ }
266
+ }
173
267
  var DELEGATED_EVENTS = [
174
268
  "click",
175
269
  "dblclick",
@@ -212,73 +306,45 @@ var EVENT_TYPE_MAP = {
212
306
  touchmove: 17,
213
307
  scroll: 18
214
308
  };
215
- var jsz = new ZigJS;
216
- var importObject = {
217
- module: {},
218
- env: {},
219
- ...jsz.importObject()
220
- };
221
-
222
- class ZXInstance {
223
- exports;
224
- events;
225
- #eventDelegationInitialized = false;
226
- constructor({ exports, events = [] }) {
227
- this.exports = exports;
228
- this.events = events;
229
- }
230
- addEvent(event) {
231
- if (this.events.length >= MAX_EVENTS)
232
- this.events.length = 0;
233
- const idx = this.events.push(event);
234
- return idx - 1;
235
- }
236
- initEventDelegation(rootSelector = "body") {
237
- if (this.#eventDelegationInitialized)
238
- return;
239
- const root = document.querySelector(rootSelector);
240
- if (!root) {
241
- console.warn(`[ZX] Event delegation root "${rootSelector}" not found`);
242
- return;
243
- }
244
- for (const eventType of DELEGATED_EVENTS) {
245
- root.addEventListener(eventType, (event) => {
246
- this.#handleDelegatedEvent(eventType, event);
247
- }, { passive: eventType.startsWith("touch") || eventType === "scroll" });
248
- }
249
- this.#eventDelegationInitialized = true;
250
- console.debug("[ZX] Event delegation initialized on", rootSelector);
251
- }
252
- #handleDelegatedEvent(eventType, event) {
253
- let target = event.target;
254
- while (target && target !== document.body) {
255
- const zxRef = target.__zx_ref;
256
- if (zxRef !== undefined) {
257
- const eventId = this.addEvent(event);
258
- const handleEvent = this.exports.handleEvent;
259
- if (typeof handleEvent === "function") {
260
- const eventTypeId = EVENT_TYPE_MAP[eventType] ?? 0;
261
- handleEvent(BigInt(zxRef), eventTypeId, BigInt(eventId));
309
+ function initEventDelegation(bridge, rootSelector = "body") {
310
+ const root = document.querySelector(rootSelector);
311
+ if (!root)
312
+ return;
313
+ for (const eventType of DELEGATED_EVENTS) {
314
+ root.addEventListener(eventType, (event) => {
315
+ let target = event.target;
316
+ while (target && target !== document.body) {
317
+ const zxRef = target.__zx_ref;
318
+ if (zxRef !== undefined) {
319
+ bridge.eventbridge(BigInt(zxRef), EVENT_TYPE_MAP[eventType] ?? 0, event);
320
+ break;
262
321
  }
263
- break;
322
+ target = target.parentElement;
264
323
  }
265
- target = target.parentElement;
266
- }
267
- }
268
- getZxRef(element) {
269
- return element.__zx_ref;
324
+ }, { passive: eventType.startsWith("touch") || eventType === "scroll" });
270
325
  }
271
326
  }
327
+ var DEFAULT_URL = "/assets/main.wasm";
272
328
  async function init(options = {}) {
273
- const url = options?.url ?? DEFAULT_URL;
274
- const { instance } = await WebAssembly.instantiateStreaming(fetch(url), importObject);
329
+ const url = options.url ?? DEFAULT_URL;
330
+ const bridgeRef = { current: null };
331
+ const importObject = Object.assign({}, ZxBridge.createImportObject(bridgeRef), options.importObject);
332
+ const source = await WebAssembly.instantiateStreaming(fetch(url), importObject);
333
+ const { instance } = source;
275
334
  jsz.memory = instance.exports.memory;
276
- window._zx = new ZXInstance({ exports: instance.exports });
277
- window._zx.initEventDelegation(options.eventDelegationRoot ?? "body");
335
+ const bridge = new ZxBridge(instance.exports);
336
+ bridgeRef.current = bridge;
337
+ initEventDelegation(bridge, options.eventDelegationRoot ?? "body");
278
338
  const main = instance.exports.mainClient;
279
339
  if (typeof main === "function")
280
340
  main();
341
+ return { source, bridge };
281
342
  }
282
343
  export {
283
- init
344
+ storeValueGetRef,
345
+ jsz,
346
+ initEventDelegation,
347
+ init,
348
+ ZxBridge,
349
+ CallbackType
284
350
  };