semajsx 0.5.2 → 0.6.1

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 (67) hide show
  1. package/dist/client-CButR91p.mjs +740 -0
  2. package/dist/client-CButR91p.mjs.map +1 -0
  3. package/dist/dom/index.d.mts +2 -2
  4. package/dist/dom/jsx-dev-runtime.d.mts +3 -3
  5. package/dist/dom/jsx-runtime.d.mts +3 -3
  6. package/dist/{helpers-CfRDJgcP.d.mts → helpers-C8GKdDrJ.d.mts} +3 -3
  7. package/dist/{helpers-CfRDJgcP.d.mts.map → helpers-C8GKdDrJ.d.mts.map} +1 -1
  8. package/dist/{index-Ch9GwToI.d.mts → index-D_FIlSk3.d.mts} +3 -3
  9. package/dist/{index-Ch9GwToI.d.mts.map → index-D_FIlSk3.d.mts.map} +1 -1
  10. package/dist/{index-B1pjI-Su.d.mts → index-PYr1aNIz.d.mts} +2 -2
  11. package/dist/{index-B1pjI-Su.d.mts.map → index-PYr1aNIz.d.mts.map} +1 -1
  12. package/dist/index.d.mts +6 -6
  13. package/dist/{island-marker-BJIO07Vj.d.mts → island-marker-Dne5tuWe.d.mts} +1 -1
  14. package/dist/island-marker-Dne5tuWe.d.mts.map +1 -0
  15. package/dist/{jsx-fNlLjLou.d.mts → jsx-CFnuxPMI.d.mts} +2 -2
  16. package/dist/{jsx-fNlLjLou.d.mts.map → jsx-CFnuxPMI.d.mts.map} +1 -1
  17. package/dist/{jsx-runtime-BFuFPDzn.d.mts → jsx-runtime-Dc77fsnM.d.mts} +3 -3
  18. package/dist/{jsx-runtime-BFuFPDzn.d.mts.map → jsx-runtime-Dc77fsnM.d.mts.map} +1 -1
  19. package/dist/{jsx-runtime-BBi9E0Hz.d.mts → jsx-runtime-tIuFmhTh.d.mts} +4 -4
  20. package/dist/{jsx-runtime-BBi9E0Hz.d.mts.map → jsx-runtime-tIuFmhTh.d.mts.map} +1 -1
  21. package/dist/{lucide-CVtHepGM.mjs → lucide-C5BghhSl.mjs} +1 -1
  22. package/dist/{lucide-CVtHepGM.mjs.map → lucide-C5BghhSl.mjs.map} +1 -1
  23. package/dist/{resource-BQI6AeJ0.d.mts → resource-CNwiNxJX.d.mts} +2 -2
  24. package/dist/{resource-BQI6AeJ0.d.mts.map → resource-CNwiNxJX.d.mts.map} +1 -1
  25. package/dist/signal/index.d.mts +2 -2
  26. package/dist/{signal-BwxUlXKs.d.mts → signal-BcaF-fWG.d.mts} +1 -1
  27. package/dist/{signal-BwxUlXKs.d.mts.map → signal-BcaF-fWG.d.mts.map} +1 -1
  28. package/dist/{src-L88LbwEv.mjs → src-75qcxwT_.mjs} +2 -2
  29. package/dist/{src-L88LbwEv.mjs.map → src-75qcxwT_.mjs.map} +1 -1
  30. package/dist/{src-DuSN6go_.mjs → src-B4VBiHa8.mjs} +116 -4
  31. package/dist/src-B4VBiHa8.mjs.map +1 -0
  32. package/dist/ssg/index.d.mts +2 -2
  33. package/dist/ssg/index.mjs +2 -2
  34. package/dist/ssg/plugins/docs-theme.d.mts +7 -4
  35. package/dist/ssg/plugins/docs-theme.d.mts.map +1 -1
  36. package/dist/ssg/plugins/docs-theme.mjs +170 -46
  37. package/dist/ssg/plugins/docs-theme.mjs.map +1 -1
  38. package/dist/ssg/plugins/fonts/MAPLE_MONO_LICENSE.txt +93 -0
  39. package/dist/ssg/plugins/fonts/MapleMono-NF-CN-Regular.woff2 +0 -0
  40. package/dist/ssg/plugins/lucide.d.mts +2 -2
  41. package/dist/ssg/plugins/lucide.mjs +1 -1
  42. package/dist/ssr/client.d.mts +7 -6
  43. package/dist/ssr/client.d.mts.map +1 -1
  44. package/dist/ssr/client.mjs +4 -682
  45. package/dist/ssr/index.d.mts +2 -2
  46. package/dist/ssr/index.d.mts.map +1 -1
  47. package/dist/ssr/index.mjs +1 -1
  48. package/dist/style/index.d.mts +2 -2
  49. package/dist/style/react.d.mts +2 -2
  50. package/dist/style/vue.d.mts +2 -2
  51. package/dist/terminal/index.d.mts +4 -4
  52. package/dist/terminal/jsx-dev-runtime.d.mts +4 -4
  53. package/dist/terminal/jsx-runtime.d.mts +4 -4
  54. package/dist/{types-D0jRO840.d.mts → types-Bj5q5x2Q.d.mts} +1 -1
  55. package/dist/{types-D0jRO840.d.mts.map → types-Bj5q5x2Q.d.mts.map} +1 -1
  56. package/dist/{types-C9fiRu6l.d.mts → types-BmDIxXiP.d.mts} +2 -2
  57. package/dist/{types-C9fiRu6l.d.mts.map → types-BmDIxXiP.d.mts.map} +1 -1
  58. package/dist/{types-CZMcXQTW.d.mts → types-C83YtOen.d.mts} +2 -2
  59. package/dist/{types-CZMcXQTW.d.mts.map → types-C83YtOen.d.mts.map} +1 -1
  60. package/dist/{types-BlaUrkq0.d.mts → types-CVPg8ByY.d.mts} +2 -2
  61. package/dist/{types-BlaUrkq0.d.mts.map → types-CVPg8ByY.d.mts.map} +1 -1
  62. package/dist/{types-DucvOZQ2.d.mts → types-ii0bAipe.d.mts} +2 -2
  63. package/dist/{types-DucvOZQ2.d.mts.map → types-ii0bAipe.d.mts.map} +1 -1
  64. package/package.json +2 -2
  65. package/dist/island-marker-BJIO07Vj.d.mts.map +0 -1
  66. package/dist/src-DuSN6go_.mjs.map +0 -1
  67. package/dist/ssr/client.mjs.map +0 -1
@@ -0,0 +1,740 @@
1
+ import { t as isSignal } from "./utils-DbTAs943.mjs";
2
+ import { h, v as Fragment } from "./src-DW3tIczg.mjs";
3
+ import { d as setProperty, u as render } from "./src-BqX3sryB.mjs";
4
+
5
+ //#region ../ssr/src/client/hydrate.ts
6
+ /**
7
+ * Type guard for async iterators
8
+ */
9
+ function isAsyncIterator(value) {
10
+ if (!value || typeof value !== "object") return false;
11
+ const obj = value;
12
+ return typeof obj[Symbol.asyncIterator] === "function" || typeof obj.next === "function" && typeof obj.return === "function";
13
+ }
14
+ /**
15
+ * Hydrate a server-rendered DOM tree with client-side interactivity
16
+ * Unlike render(), this preserves existing DOM and only attaches event listeners
17
+ *
18
+ * @param vnode - The VNode to hydrate
19
+ * @param container - The DOM container with server-rendered content
20
+ * @returns The hydrated root node
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * const vnode = <Counter initial={5} />
25
+ * const container = document.querySelector('[data-island-id="island-0"]')
26
+ * hydrate(vnode, container)
27
+ * ```
28
+ */
29
+ function hydrate(vnode, container) {
30
+ const nodeToHydrate = container.firstChild;
31
+ if (!nodeToHydrate) {
32
+ console.warn("[Hydrate] Container is empty, falling back to render");
33
+ const rendered = renderNode(vnode, container);
34
+ if (rendered) container.appendChild(rendered);
35
+ return rendered;
36
+ }
37
+ try {
38
+ hydrateNode(vnode, nodeToHydrate, container);
39
+ return nodeToHydrate;
40
+ } catch (error) {
41
+ console.error("[Hydrate] Error during hydration:", error);
42
+ console.warn("[Hydrate] Falling back to client-side rendering");
43
+ container.innerHTML = "";
44
+ return renderNode(vnode, container);
45
+ }
46
+ }
47
+ /**
48
+ * Hydrate a VNode onto an existing DOM node
49
+ */
50
+ function hydrateNode(vnode, domNode, parentElement) {
51
+ if (vnode == null) return;
52
+ if (isSignal(vnode)) {
53
+ hydrateSignalNode(vnode, domNode, parentElement);
54
+ return;
55
+ }
56
+ if (typeof vnode === "string" || typeof vnode === "number") {
57
+ if (domNode.nodeType === Node.TEXT_NODE) {
58
+ const expectedText = String(vnode);
59
+ if (domNode.textContent !== expectedText) {
60
+ console.warn("[Hydrate] Text mismatch, updating:", domNode.textContent, "->", expectedText);
61
+ domNode.textContent = expectedText;
62
+ }
63
+ }
64
+ return;
65
+ }
66
+ if (Array.isArray(vnode)) {
67
+ let currentDomNode = domNode;
68
+ for (const child of vnode) if (currentDomNode) {
69
+ hydrateNode(child, currentDomNode, parentElement);
70
+ currentDomNode = currentDomNode.nextSibling;
71
+ }
72
+ return;
73
+ }
74
+ if (typeof vnode !== "object" || !("type" in vnode)) return;
75
+ const vnodeTyped = vnode;
76
+ if (vnodeTyped.type === "#signal") {
77
+ const signal = vnodeTyped.props?.signal;
78
+ if (signal && isSignal(signal)) hydrateSignalNode(signal, domNode, parentElement);
79
+ return;
80
+ }
81
+ if (vnodeTyped.type === Fragment) {
82
+ let currentDomNode = domNode;
83
+ for (const child of vnodeTyped.children) if (currentDomNode) {
84
+ hydrateNode(child, currentDomNode, parentElement);
85
+ currentDomNode = currentDomNode.nextSibling;
86
+ }
87
+ return;
88
+ }
89
+ if (typeof vnodeTyped.type === "function") {
90
+ const props = vnodeTyped.children && vnodeTyped.children.length > 0 ? {
91
+ ...vnodeTyped.props,
92
+ children: vnodeTyped.children
93
+ } : vnodeTyped.props || {};
94
+ let result = vnodeTyped.type(props);
95
+ if (result instanceof Promise) {
96
+ result.then((resolved) => hydrateNode(resolved, domNode, parentElement));
97
+ return;
98
+ }
99
+ if (isAsyncIterator(result)) {
100
+ result.next().then(({ value }) => {
101
+ hydrateNode(value, domNode, parentElement);
102
+ });
103
+ return;
104
+ }
105
+ hydrateNode(result, domNode, parentElement);
106
+ return;
107
+ }
108
+ if (typeof vnodeTyped.type === "string") {
109
+ if (domNode.nodeType === Node.TEXT_NODE) return;
110
+ if (domNode.nodeType !== Node.ELEMENT_NODE) {
111
+ console.warn("[Hydrate] Expected element, got:", domNode.nodeType);
112
+ return;
113
+ }
114
+ const element = domNode;
115
+ if (element.tagName.toLowerCase() !== vnodeTyped.type.toLowerCase()) {
116
+ console.warn("[Hydrate] Tag mismatch:", element.tagName, "vs", vnodeTyped.type);
117
+ return;
118
+ }
119
+ hydrateProperties(element, vnodeTyped.props || {});
120
+ hydrateChildren(element, vnodeTyped.children);
121
+ return;
122
+ }
123
+ }
124
+ /**
125
+ * Hydrate properties onto an element
126
+ * This is where we attach event listeners and set up reactive properties
127
+ */
128
+ function hydrateProperties(element, props) {
129
+ for (const [key, value] of Object.entries(props)) {
130
+ if (key === "children" || key === "key" || key === "ref") continue;
131
+ if (key.startsWith("on")) {
132
+ const eventName = key.slice(2).toLowerCase();
133
+ if (typeof value === "function") element.addEventListener(eventName, value);
134
+ continue;
135
+ }
136
+ if (isSignal(value)) {
137
+ setProperty(element, key, value.value);
138
+ value.subscribe((newValue) => {
139
+ setProperty(element, key, newValue);
140
+ });
141
+ continue;
142
+ }
143
+ }
144
+ if (props.ref) {
145
+ if (typeof props.ref === "function") props.ref(element);
146
+ else if (typeof props.ref === "object" && props.ref !== null) props.ref.current = element;
147
+ }
148
+ }
149
+ /**
150
+ * Hydrate children elements
151
+ */
152
+ function hydrateChildren(element, children) {
153
+ let currentDomNode = element.firstChild;
154
+ for (const child of children) {
155
+ if (!currentDomNode) {
156
+ console.warn("[Hydrate] Missing DOM node for child, appending");
157
+ const newNode = renderNode(child, element);
158
+ if (newNode) element.appendChild(newNode);
159
+ continue;
160
+ }
161
+ hydrateNode(child, currentDomNode, element);
162
+ currentDomNode = currentDomNode.nextSibling;
163
+ }
164
+ }
165
+ /**
166
+ * Hydrate a signal VNode
167
+ * Set up reactivity to replace content when signal changes
168
+ */
169
+ function hydrateSignalNode(signal, domNode, parentElement) {
170
+ const currentValue = signal.value;
171
+ if (currentValue == null || currentValue === false || Array.isArray(currentValue) && currentValue.length === 0) if (domNode.nodeType === Node.COMMENT_NODE) {} else console.warn("[Hydrate] Expected comment marker for empty signal, got:", domNode.nodeType);
172
+ else if (typeof currentValue === "string" || typeof currentValue === "number") {
173
+ if (domNode.nodeType === Node.TEXT_NODE) {
174
+ const expectedText = String(currentValue);
175
+ if (domNode.textContent !== expectedText) {
176
+ console.warn("[Hydrate] Signal text mismatch:", domNode.textContent, "->", expectedText);
177
+ domNode.textContent = expectedText;
178
+ }
179
+ }
180
+ } else hydrateNode(currentValue, domNode, parentElement);
181
+ let anchor;
182
+ let currentNodes = [];
183
+ if (domNode.nodeType === Node.COMMENT_NODE) anchor = domNode;
184
+ else {
185
+ anchor = document.createComment("signal-anchor");
186
+ if (domNode.parentNode) domNode.parentNode.insertBefore(anchor, domNode);
187
+ currentNodes = [domNode];
188
+ }
189
+ signal.subscribe((newValue) => {
190
+ const parent = anchor.parentNode;
191
+ if (!parent) return;
192
+ for (const node of currentNodes) if (node.parentNode) node.parentNode.removeChild(node);
193
+ currentNodes = [];
194
+ const newNode = renderNode(newValue, parentElement);
195
+ if (newNode) if (newNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
196
+ const fragment = newNode;
197
+ const children = Array.from(fragment.childNodes);
198
+ let insertAfter = anchor;
199
+ for (const child of children) {
200
+ parent.insertBefore(child, insertAfter.nextSibling);
201
+ insertAfter = child;
202
+ currentNodes.push(child);
203
+ }
204
+ } else {
205
+ parent.insertBefore(newNode, anchor.nextSibling);
206
+ currentNodes = [newNode];
207
+ }
208
+ });
209
+ }
210
+ /**
211
+ * Render a VNode to a DOM node (fallback when hydration fails)
212
+ * This is a simplified version of render() just for hydration fallback
213
+ */
214
+ function renderNode(vnode, parentElement) {
215
+ if (vnode == null || vnode === false || vnode === true) return document.createComment("empty");
216
+ if (isSignal(vnode)) return renderNode(vnode.value, parentElement);
217
+ if (typeof vnode === "string" || typeof vnode === "number") return document.createTextNode(String(vnode));
218
+ if (Array.isArray(vnode)) {
219
+ if (vnode.length === 0) return document.createComment("empty");
220
+ const fragment = document.createDocumentFragment();
221
+ for (const child of vnode) {
222
+ const node = renderNode(child, parentElement);
223
+ if (node) fragment.appendChild(node);
224
+ }
225
+ return fragment;
226
+ }
227
+ if (typeof vnode === "object" && "type" in vnode) {
228
+ const vnodeTyped = vnode;
229
+ if (vnodeTyped.type === "#text") return document.createTextNode(String(vnodeTyped.props?.nodeValue || ""));
230
+ if (vnodeTyped.type === "#signal") {
231
+ const signal = vnodeTyped.props?.signal;
232
+ if (signal && isSignal(signal)) return renderNode(signal.value, parentElement);
233
+ return document.createTextNode("");
234
+ }
235
+ if (vnodeTyped.type === Fragment) {
236
+ const fragment = document.createDocumentFragment();
237
+ for (const child of vnodeTyped.children) {
238
+ const node = renderNode(child, parentElement);
239
+ if (node) fragment.appendChild(node);
240
+ }
241
+ return fragment;
242
+ }
243
+ if (typeof vnodeTyped.type === "function") return renderNode(vnodeTyped.type(vnodeTyped.props || {}), parentElement);
244
+ if (typeof vnodeTyped.type === "string") {
245
+ const element = document.createElement(vnodeTyped.type);
246
+ const props = vnodeTyped.props || {};
247
+ for (const [key, value] of Object.entries(props)) {
248
+ if (key === "children" || key === "key") continue;
249
+ setProperty(element, key, value);
250
+ }
251
+ for (const child of vnodeTyped.children) {
252
+ const childNode = renderNode(child, element);
253
+ if (childNode) element.appendChild(childNode);
254
+ }
255
+ return element;
256
+ }
257
+ }
258
+ return null;
259
+ }
260
+ /**
261
+ * Hydrate an island by ID
262
+ * Handles both single-element islands (with data-island-id) and fragment islands (with comment markers)
263
+ *
264
+ * @param islandId - The island ID to hydrate
265
+ * @param Component - The component function to render
266
+ * @param markHydrated - Callback to mark the island as hydrated
267
+ *
268
+ * @example
269
+ * ```tsx
270
+ * import { hydrateIsland, markIslandHydrated } from '@semajsx/ssr/client';
271
+ * import Counter from './Counter';
272
+ *
273
+ * hydrateIsland('counter-0', Counter, markIslandHydrated);
274
+ * ```
275
+ */
276
+ function hydrateIsland(islandId, Component, markHydrated) {
277
+ const element = document.querySelector(`[data-island-id="${islandId}"]`);
278
+ if (element) {
279
+ const props = JSON.parse(element.getAttribute("data-island-props") || "{}");
280
+ const parent = element.parentNode;
281
+ if (!parent) return;
282
+ const vnode = {
283
+ type: Component,
284
+ props,
285
+ children: []
286
+ };
287
+ const temp = document.createElement("div");
288
+ render(vnode, temp);
289
+ const children = Array.from(temp.childNodes);
290
+ for (const child of children) parent.insertBefore(child, element);
291
+ parent.removeChild(element);
292
+ markHydrated(islandId);
293
+ return;
294
+ }
295
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);
296
+ let startComment = null;
297
+ let comment;
298
+ while (comment = walker.nextNode()) if (comment.textContent === `island:${islandId}`) {
299
+ startComment = comment;
300
+ break;
301
+ }
302
+ if (startComment) {
303
+ const script = document.querySelector(`script[data-island="${islandId}"]`);
304
+ const props = script ? JSON.parse(script.textContent || "{}") : {};
305
+ const nodesToRemove = [];
306
+ let sibling = startComment.nextSibling;
307
+ let endComment = null;
308
+ while (sibling) {
309
+ if (sibling.nodeType === Node.COMMENT_NODE && sibling.textContent === `/island:${islandId}`) {
310
+ endComment = sibling;
311
+ break;
312
+ }
313
+ nodesToRemove.push(sibling);
314
+ sibling = sibling.nextSibling;
315
+ }
316
+ for (const node of nodesToRemove) node.parentNode?.removeChild(node);
317
+ const vnode = {
318
+ type: Component,
319
+ props,
320
+ children: []
321
+ };
322
+ const parent = startComment.parentNode;
323
+ if (parent) {
324
+ const temp = document.createElement("div");
325
+ render(vnode, temp);
326
+ const children = Array.from(temp.childNodes);
327
+ for (const child of children) parent.insertBefore(child, endComment);
328
+ }
329
+ startComment.parentNode?.removeChild(startComment);
330
+ if (endComment) endComment.parentNode?.removeChild(endComment);
331
+ if (script) script.parentNode?.removeChild(script);
332
+ markHydrated(islandId);
333
+ }
334
+ }
335
+ /**
336
+ * Find all islands on the page (both element and fragment types)
337
+ */
338
+ function findAllIslands() {
339
+ const islands = [];
340
+ const elements = document.querySelectorAll("[data-island-id]");
341
+ for (const el of elements) {
342
+ const id = el.getAttribute("data-island-id");
343
+ const propsStr = el.getAttribute("data-island-props");
344
+ if (id) islands.push({
345
+ id,
346
+ props: propsStr ? JSON.parse(propsStr) : {},
347
+ element: el
348
+ });
349
+ }
350
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);
351
+ let comment;
352
+ while (comment = walker.nextNode()) {
353
+ const match = comment.textContent?.match(/^island:(.+)$/);
354
+ if (match && match[1]) {
355
+ const id = match[1];
356
+ let endComment = null;
357
+ let sibling = comment.nextSibling;
358
+ while (sibling) {
359
+ if (sibling.nodeType === Node.COMMENT_NODE && sibling.textContent === `/island:${id}`) {
360
+ endComment = sibling;
361
+ break;
362
+ }
363
+ sibling = sibling.nextSibling;
364
+ }
365
+ const script = document.querySelector(`script[type="application/json"][data-island="${id}"]`);
366
+ const props = script ? JSON.parse(script.textContent || "{}") : {};
367
+ islands.push({
368
+ id,
369
+ props,
370
+ startComment: comment,
371
+ endComment: endComment || void 0
372
+ });
373
+ }
374
+ }
375
+ return islands;
376
+ }
377
+ /**
378
+ * Hydrate all islands on the page
379
+ * This function is typically called once after the page loads
380
+ *
381
+ * @example
382
+ * ```tsx
383
+ * // In your client entry point
384
+ * import { hydrateIslands } from '@semajsx/ssr/client'
385
+ *
386
+ * // Wait for DOM to be ready
387
+ * if (document.readyState === 'loading') {
388
+ * document.addEventListener('DOMContentLoaded', hydrateIslands)
389
+ * } else {
390
+ * hydrateIslands()
391
+ * }
392
+ * ```
393
+ */
394
+ async function hydrateIslands() {
395
+ const islands = findAllIslands();
396
+ if (islands.length === 0) return;
397
+ console.log(`[SemaJSX] Found ${islands.length} islands to hydrate`);
398
+ const hydrations = islands.map((island) => waitForIslandScript(island));
399
+ await Promise.all(hydrations);
400
+ console.log(`[SemaJSX] All islands hydrated`);
401
+ }
402
+ /**
403
+ * Wait for an island's script to load and hydrate it
404
+ * The actual hydration is performed by the island's entry point script
405
+ * This function just waits for it to complete
406
+ */
407
+ async function waitForIslandScript(island) {
408
+ const { id: islandId, element, startComment } = island;
409
+ if (element?.hasAttribute("data-hydrated")) return;
410
+ if (startComment?.parentElement?.querySelector(`[data-island-hydrated="${islandId}"]`)) return;
411
+ return new Promise((resolve) => {
412
+ const maxAttempts = 200;
413
+ let attempts = 0;
414
+ const checkInterval = setInterval(() => {
415
+ if (element ? element.hasAttribute("data-hydrated") : document.querySelector(`[data-island-hydrated="${islandId}"]`) !== null) {
416
+ clearInterval(checkInterval);
417
+ resolve();
418
+ } else if (++attempts >= maxAttempts) {
419
+ clearInterval(checkInterval);
420
+ console.warn(`[SemaJSX] Island ${islandId} hydration timeout`);
421
+ resolve();
422
+ }
423
+ }, 50);
424
+ });
425
+ }
426
+ /**
427
+ * Get island info by ID
428
+ */
429
+ function getIslandInfo(islandId) {
430
+ const element = document.querySelector(`[data-island-id="${islandId}"]`);
431
+ if (element) {
432
+ const propsStr = element.getAttribute("data-island-props");
433
+ return {
434
+ id: islandId,
435
+ props: propsStr ? JSON.parse(propsStr) : {},
436
+ element
437
+ };
438
+ }
439
+ const script = document.querySelector(`script[type="application/json"][data-island="${islandId}"]`);
440
+ if (script) {
441
+ const props = JSON.parse(script.textContent || "{}");
442
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);
443
+ let comment;
444
+ while (comment = walker.nextNode()) if (comment.textContent === `island:${islandId}`) return {
445
+ id: islandId,
446
+ props,
447
+ startComment: comment
448
+ };
449
+ }
450
+ return null;
451
+ }
452
+ /**
453
+ * Manual hydration for a specific island
454
+ * Useful for lazy-loading islands on interaction
455
+ *
456
+ * @param islandId - The island ID to hydrate
457
+ *
458
+ * @example
459
+ * ```tsx
460
+ * // Lazy load an island on click
461
+ * button.addEventListener('click', () => {
462
+ * hydrateIslandById('island-0')
463
+ * })
464
+ * ```
465
+ */
466
+ async function hydrateIslandById(islandId) {
467
+ const island = getIslandInfo(islandId);
468
+ if (!island) {
469
+ console.error(`[SemaJSX] Island not found: ${islandId}`);
470
+ return;
471
+ }
472
+ await waitForIslandScript(island);
473
+ }
474
+ /**
475
+ * Check if islands are present on the page
476
+ */
477
+ function hasIslands() {
478
+ if (document.querySelectorAll("[data-island-id]").length > 0) return true;
479
+ return document.querySelectorAll("script[type=\"application/json\"][data-island]").length > 0;
480
+ }
481
+ /**
482
+ * Get all island IDs on the page
483
+ */
484
+ function getIslandIds() {
485
+ const ids = [];
486
+ const elements = document.querySelectorAll("[data-island-id]");
487
+ for (const el of elements) {
488
+ const id = el.getAttribute("data-island-id");
489
+ if (id) ids.push(id);
490
+ }
491
+ const scripts = document.querySelectorAll("script[type=\"application/json\"][data-island]");
492
+ for (const script of scripts) {
493
+ const id = script.getAttribute("data-island");
494
+ if (id) ids.push(id);
495
+ }
496
+ return ids;
497
+ }
498
+ /**
499
+ * Mark an island as hydrated
500
+ * This should be called by the island entry point after hydration completes
501
+ */
502
+ function markIslandHydrated(islandId) {
503
+ const element = document.querySelector(`[data-island-id="${islandId}"]`);
504
+ if (element) {
505
+ element.setAttribute("data-hydrated", "true");
506
+ return;
507
+ }
508
+ const script = document.querySelector(`script[type="application/json"][data-island="${islandId}"]`);
509
+ if (script) {
510
+ script.setAttribute("data-island-hydrated", islandId);
511
+ script.remove();
512
+ }
513
+ }
514
+ /**
515
+ * Reconstruct VNode children from serialized JSON data.
516
+ *
517
+ * Uses the island module's exports as a registry to resolve component names
518
+ * (prefixed with "$") back to their actual functions.
519
+ *
520
+ * @param serialized - Serialized children array from SSR
521
+ * @param registry - Module exports mapping component names to functions
522
+ */
523
+ function reconstructChildren(serialized, registry) {
524
+ const result = [];
525
+ for (const node of serialized) {
526
+ if (node === null) continue;
527
+ if (typeof node === "string") {
528
+ result.push(node);
529
+ continue;
530
+ }
531
+ if (Array.isArray(node) && node.length === 3 && typeof node[0] === "string") {
532
+ const [type, props, children] = node;
533
+ if (type === "$island") {
534
+ result.push(null);
535
+ continue;
536
+ }
537
+ const resolvedChildren = children ? reconstructChildren(children, registry) : [];
538
+ if (type.startsWith("$")) {
539
+ const name = type.slice(1);
540
+ const component = registry[name];
541
+ if (!component || typeof component !== "function") {
542
+ console.warn(`[Hydrate] Unknown component "${name}" in island children`);
543
+ continue;
544
+ }
545
+ result.push(h(component, props || {}, ...resolvedChildren));
546
+ } else result.push(h(type, props || {}, ...resolvedChildren));
547
+ }
548
+ }
549
+ return result;
550
+ }
551
+ /**
552
+ * Hydrate all islands with a given component source
553
+ * Finds all elements with data-island-src and hydrates them
554
+ *
555
+ * @param componentSrc - The component source key (e.g., "components/Counter")
556
+ * @param Component - The component function to render
557
+ * @param registry - Optional module exports for reconstructing island children
558
+ *
559
+ * @example
560
+ * ```tsx
561
+ * import { hydrateAllIslands } from '@semajsx/ssr/client';
562
+ * import * as CounterModule from './Counter';
563
+ *
564
+ * hydrateAllIslands('components/Counter', CounterModule.Counter, CounterModule);
565
+ * ```
566
+ */
567
+ function hydrateAllIslands(componentSrc, Component, registry) {
568
+ const elements = document.querySelectorAll(`[data-island-src="${componentSrc}"]`);
569
+ const scripts = document.querySelectorAll(`script[type="application/json"][data-island-src="${componentSrc}"]`);
570
+ elements.forEach((element) => {
571
+ const islandId = element.getAttribute("data-island-id");
572
+ if (!islandId) return;
573
+ if (element.hasAttribute("data-hydrated")) return;
574
+ try {
575
+ const props = JSON.parse(element.getAttribute("data-island-props") || "{}");
576
+ if (registry) {
577
+ const childrenScript = document.querySelector(`script[type="application/json"][data-island-children="${islandId}"]`);
578
+ if (childrenScript) {
579
+ try {
580
+ props.children = reconstructChildren(JSON.parse(childrenScript.textContent || "[]"), registry);
581
+ } catch (e) {
582
+ console.warn("[Hydrate] Failed to reconstruct island children:", e);
583
+ }
584
+ childrenScript.remove();
585
+ }
586
+ }
587
+ hydrateNode({
588
+ type: Component,
589
+ props,
590
+ children: []
591
+ }, element, element.parentNode);
592
+ element.setAttribute("data-hydrated", "true");
593
+ } catch (error) {
594
+ console.error(`[Hydrate] Island "${islandId}" hydration failed:`, error);
595
+ element.setAttribute("data-hydration-error", "true");
596
+ }
597
+ });
598
+ scripts.forEach((script) => {
599
+ const islandId = script.getAttribute("data-island");
600
+ if (!islandId) return;
601
+ if (script.hasAttribute("data-island-hydrated")) return;
602
+ try {
603
+ const props = JSON.parse(script.textContent || "{}");
604
+ if (registry) {
605
+ const childrenScript = document.querySelector(`script[type="application/json"][data-island-children="${islandId}"]`);
606
+ if (childrenScript) {
607
+ try {
608
+ props.children = reconstructChildren(JSON.parse(childrenScript.textContent || "[]"), registry);
609
+ } catch (e) {
610
+ console.warn("[Hydrate] Failed to reconstruct island children:", e);
611
+ }
612
+ childrenScript.remove();
613
+ }
614
+ }
615
+ const startMarker = `island:${islandId}`;
616
+ const endMarker = `/island:${islandId}`;
617
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT, null);
618
+ let startNode = null;
619
+ let endNode = null;
620
+ let node;
621
+ while (node = walker.nextNode()) if (node.nodeValue === startMarker) startNode = node;
622
+ else if (node.nodeValue === endMarker) {
623
+ endNode = node;
624
+ break;
625
+ }
626
+ if (startNode && endNode && startNode.parentNode) {
627
+ const parent = startNode.parentNode;
628
+ const container = document.createElement("div");
629
+ let current = startNode.nextSibling;
630
+ while (current && current !== endNode) {
631
+ const next = current.nextSibling;
632
+ container.appendChild(current);
633
+ current = next;
634
+ }
635
+ hydrateNode({
636
+ type: Component,
637
+ props,
638
+ children: []
639
+ }, container, container);
640
+ while (container.firstChild) parent.insertBefore(container.firstChild, endNode);
641
+ script.setAttribute("data-island-hydrated", islandId);
642
+ script.remove();
643
+ }
644
+ } catch (error) {
645
+ console.error(`[Hydrate] Fragment island "${islandId}" hydration failed:`, error);
646
+ }
647
+ });
648
+ }
649
+
650
+ //#endregion
651
+ //#region ../ssr/src/client/client-resource.ts
652
+ let _manifest = null;
653
+ const loadedStyles = /* @__PURE__ */ new Set();
654
+ /**
655
+ * Set the client manifest (called during initialization)
656
+ */
657
+ function setManifest(manifest) {
658
+ _manifest = manifest;
659
+ }
660
+ /**
661
+ * Get the current manifest
662
+ */
663
+ function getManifest() {
664
+ return _manifest;
665
+ }
666
+ /**
667
+ * Resolve a CSS path using the manifest
668
+ */
669
+ function resolveCSS(href) {
670
+ if (!_manifest) return href;
671
+ const lookupPath = href.startsWith("/") ? href.slice(1) : href;
672
+ return _manifest.css[lookupPath] || href;
673
+ }
674
+ /**
675
+ * Resolve an asset path using the manifest
676
+ */
677
+ function resolveAsset(src) {
678
+ if (!_manifest) return src;
679
+ const lookupPath = src.startsWith("/") ? src.slice(1) : src;
680
+ return _manifest.assets[lookupPath] || src;
681
+ }
682
+ /**
683
+ * Dynamically load a stylesheet
684
+ */
685
+ function loadStylesheet(href) {
686
+ const resolvedHref = resolveCSS(href);
687
+ if (loadedStyles.has(resolvedHref)) return Promise.resolve();
688
+ return new Promise((resolve, reject) => {
689
+ if (document.querySelector(`link[href="${resolvedHref}"]`)) {
690
+ loadedStyles.add(resolvedHref);
691
+ resolve();
692
+ return;
693
+ }
694
+ const link = document.createElement("link");
695
+ link.rel = "stylesheet";
696
+ link.href = resolvedHref;
697
+ link.onload = () => {
698
+ loadedStyles.add(resolvedHref);
699
+ resolve();
700
+ };
701
+ link.onerror = () => {
702
+ reject(/* @__PURE__ */ new Error(`Failed to load stylesheet: ${resolvedHref}`));
703
+ };
704
+ document.head.appendChild(link);
705
+ });
706
+ }
707
+ /**
708
+ * Create client-side resource tools
709
+ *
710
+ * @example
711
+ * ```tsx
712
+ * import { clientResource } from '@semajsx/ssr/client';
713
+ *
714
+ * const { Style, url } = clientResource();
715
+ *
716
+ * export default function Counter() {
717
+ * return (
718
+ * <>
719
+ * <Style href="./counter.css" />
720
+ * <img src={url('./icon.png')} />
721
+ * </>
722
+ * );
723
+ * }
724
+ * ```
725
+ */
726
+ function clientResource() {
727
+ return {
728
+ Style({ href }) {
729
+ if (typeof document !== "undefined") loadStylesheet(href);
730
+ return null;
731
+ },
732
+ url(path) {
733
+ return resolveAsset(path);
734
+ }
735
+ };
736
+ }
737
+
738
+ //#endregion
739
+ export { resolveCSS as a, getIslandInfo as c, hydrateAllIslands as d, hydrateIsland as f, markIslandHydrated as h, resolveAsset as i, hasIslands as l, hydrateIslands as m, getManifest as n, setManifest as o, hydrateIslandById as p, loadStylesheet as r, getIslandIds as s, clientResource as t, hydrate as u };
740
+ //# sourceMappingURL=client-CButR91p.mjs.map