@tschk/moonshine-react 0.3.7 → 0.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tschk/moonshine-react",
3
- "version": "0.3.7",
3
+ "version": "0.4.1",
4
4
  "description": "React renderer, islands, and signal hooks for Moonshine",
5
5
  "license": "ISC",
6
6
  "type": "module",
@@ -16,8 +16,8 @@
16
16
  "test": "bun test"
17
17
  },
18
18
  "dependencies": {
19
- "@tschk/moonshine": "^0.3.6",
20
- "@tschk/moonshine-framework": "^0.3.6",
19
+ "@tschk/moonshine": "^0.4.1",
20
+ "@tschk/moonshine-framework": "^0.4.1",
21
21
  "react": "^19.1.0",
22
22
  "react-dom": "^19.1.0"
23
23
  },
package/src/client.tsx CHANGED
@@ -118,8 +118,8 @@ export function useStore<T extends object>(store: T): T {
118
118
  }
119
119
  useSyncExternalStore(
120
120
  node.subscribe,
121
- () => store,
122
- () => store,
121
+ () => node.version,
122
+ () => node.version,
123
123
  );
124
124
  return store;
125
125
  }
package/src/islands.tsx CHANGED
@@ -62,7 +62,10 @@ export function island<P>(
62
62
  const mod = use(loader());
63
63
  const Component = ((mod as { default?: ComponentType<P> }).default ??
64
64
  mod) as FunctionComponent<P>;
65
- const children = createElement(Component as any, props as any);
65
+ const children = createElement(
66
+ Component as FunctionComponent<unknown>,
67
+ props as unknown as Record<string, unknown>,
68
+ );
66
69
  if (registry) {
67
70
  registry.entries.set(id, {
68
71
  id,
@@ -74,13 +77,14 @@ export function island<P>(
74
77
  Fragment,
75
78
  null,
76
79
  createElement("div", { "data-moonshine-island": id }, children),
77
- createElement("script", {
78
- type: "application/json",
79
- "data-moonshine-island-props": id,
80
- dangerouslySetInnerHTML: {
81
- __html: serializeIslandProps(props),
80
+ createElement(
81
+ "script",
82
+ {
83
+ type: "application/json",
84
+ "data-moonshine-island-props": id,
82
85
  },
83
- }),
86
+ serializeIslandProps(props),
87
+ ),
84
88
  );
85
89
  };
86
90
  return IslandComponent;
@@ -107,7 +111,10 @@ export function hydrateIslands(): void {
107
111
  const { createElement } = await import("react");
108
112
  const Component = ((mod as { default?: ComponentType<unknown> })
109
113
  .default ?? mod) as FunctionComponent<unknown>;
110
- hydrateRoot(marker, createElement(Component as any, props as any));
114
+ hydrateRoot(
115
+ marker,
116
+ createElement(Component, props as Record<string, unknown>),
117
+ );
111
118
  });
112
119
  }
113
120
  }
package/src/serialize.ts CHANGED
@@ -1,8 +1,7 @@
1
- const HTML_ESCAPE = /[<>&\u2028\u2029]/g;
2
-
3
- function escapeHtmlChar(char: string): string {
4
- return `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`;
5
- }
1
+ // Island props go into a `<script>` tag, the same hazard the server's data
2
+ // payload has, so both use the framework's one HTML-safe JSON serializer.
3
+ // A second copy here is a second place to forget an escape vector.
4
+ export { serializeData as serializeIslandProps } from "@tschk/moonshine-framework";
6
5
 
7
6
  function escapeHtmlValue(value: string): string {
8
7
  return value
@@ -13,30 +12,6 @@ function escapeHtmlValue(value: string): string {
13
12
  .replace(/'/g, "&#x27;");
14
13
  }
15
14
 
16
- export function serializeIslandProps(value: unknown): string {
17
- // Ancestor chain, not a visited set: a value repeated in sibling positions
18
- // is shared, not cyclic, and must still serialize.
19
- const ancestors: unknown[] = [];
20
- function replacer(this: object, _key: string, v: unknown): unknown {
21
- if (v === undefined) return undefined;
22
- const type = typeof v;
23
- if (type === "function" || type === "symbol" || type === "bigint") {
24
- throw new TypeError(`cannot serialize ${type}`);
25
- }
26
- if (v !== null && typeof v === "object") {
27
- while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
28
- ancestors.pop();
29
- }
30
- if (ancestors.includes(v)) throw new TypeError("cyclic reference");
31
- ancestors.push(v);
32
- }
33
- return v;
34
- }
35
- const raw = JSON.stringify(value, replacer);
36
- if (raw === undefined) return "null";
37
- return raw.replace(HTML_ESCAPE, escapeHtmlChar);
38
- }
39
-
40
15
  export function escapeHtml(value: string): string {
41
16
  return escapeHtmlValue(value);
42
17
  }