@valbuild/react 0.12.0 → 0.13.3

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 (60) hide show
  1. package/dist/ValProvider-52f2fa13.esm.js +731 -0
  2. package/dist/ValProvider-7364ec46.cjs.dev.js +746 -0
  3. package/dist/ValProvider-a45a47b9.worker.esm.js +726 -0
  4. package/dist/ValProvider-c7a8476b.browser.esm.js +731 -0
  5. package/dist/ValProvider-e1d5ffbe.cjs.js +7 -0
  6. package/dist/ValProvider-e1d5ffbe.cjs.prod.js +746 -0
  7. package/dist/ValUI-0fbdafd4.cjs.prod.js +398 -0
  8. package/dist/ValUI-371e9bf4.cjs.dev.js +398 -0
  9. package/dist/ValUI-51404232.browser.esm.js +396 -0
  10. package/dist/ValUI-9a3eb570.esm.js +396 -0
  11. package/dist/declarations/src/AuthStatus.d.ts +6 -0
  12. package/dist/declarations/src/ShadowRoot.d.ts +4 -0
  13. package/dist/declarations/src/ValProvider.d.ts +1 -1
  14. package/dist/declarations/src/ValProviderWrapper.d.ts +3 -0
  15. package/dist/declarations/src/ValUI.d.ts +8 -0
  16. package/dist/declarations/src/index.d.ts +1 -1
  17. package/dist/{slicedToArray-0ead6329.cjs.dev.js → slicedToArray-0eb0bcdb.cjs.prod.js} +3 -17
  18. package/dist/{slicedToArray-bf9b195a.worker.esm.js → slicedToArray-1a246338.browser.esm.js} +3 -16
  19. package/dist/{slicedToArray-236143cd.browser.esm.js → slicedToArray-9e7d1407.worker.esm.js} +3 -16
  20. package/dist/{slicedToArray-57b117df.cjs.prod.js → slicedToArray-b7cf26e0.cjs.dev.js} +3 -17
  21. package/dist/{slicedToArray-390fde8c.esm.js → slicedToArray-d846e1d2.esm.js} +3 -16
  22. package/dist/unsupportedIterableToArray-51bb61c2.esm.js +16 -0
  23. package/dist/unsupportedIterableToArray-738344ef.worker.esm.js +16 -0
  24. package/dist/unsupportedIterableToArray-9e97e24a.cjs.dev.js +18 -0
  25. package/dist/unsupportedIterableToArray-afbea1dd.cjs.prod.js +18 -0
  26. package/dist/unsupportedIterableToArray-d3087ed5.browser.esm.js +16 -0
  27. package/dist/valbuild-react.browser.esm.js +9 -1083
  28. package/dist/valbuild-react.cjs.dev.js +24 -1074
  29. package/dist/valbuild-react.cjs.prod.js +24 -1074
  30. package/dist/valbuild-react.esm.js +13 -1079
  31. package/dist/valbuild-react.worker.esm.js +13 -1079
  32. package/jest.config.js +5 -0
  33. package/jsx-dev-runtime/dist/valbuild-react-jsx-dev-runtime.browser.esm.js +2 -1
  34. package/jsx-dev-runtime/dist/valbuild-react-jsx-dev-runtime.cjs.dev.js +2 -1
  35. package/jsx-dev-runtime/dist/valbuild-react-jsx-dev-runtime.cjs.prod.js +2 -1
  36. package/jsx-dev-runtime/dist/valbuild-react-jsx-dev-runtime.esm.js +2 -1
  37. package/jsx-dev-runtime/dist/valbuild-react-jsx-dev-runtime.worker.esm.js +2 -1
  38. package/jsx-runtime/dist/valbuild-react-jsx-runtime.browser.esm.js +2 -1
  39. package/jsx-runtime/dist/valbuild-react-jsx-runtime.cjs.dev.js +2 -1
  40. package/jsx-runtime/dist/valbuild-react-jsx-runtime.cjs.prod.js +2 -1
  41. package/jsx-runtime/dist/valbuild-react-jsx-runtime.esm.js +2 -1
  42. package/jsx-runtime/dist/valbuild-react-jsx-runtime.worker.esm.js +2 -1
  43. package/package.json +9 -6
  44. package/src/AuthStatus.tsx +13 -0
  45. package/src/ShadowRoot.tsx +32 -0
  46. package/src/ValApi.ts +65 -0
  47. package/src/ValProvider.tsx +51 -0
  48. package/src/ValProviderWrapper.tsx +15 -0
  49. package/src/ValRichText.tsx +141 -0
  50. package/src/ValStore.ts +62 -0
  51. package/src/ValUI.tsx +353 -0
  52. package/src/assets.ts +124 -0
  53. package/src/hooks/useVal.test.tsx +57 -0
  54. package/src/hooks/useVal.ts +35 -0
  55. package/src/index.ts +6 -0
  56. package/src/jsx-dev-runtime.js +47 -0
  57. package/src/jsx-namespace.d.ts +46 -0
  58. package/src/jsx-runtime.d.ts +1 -0
  59. package/src/jsx-runtime.dev.d.ts +1 -0
  60. package/src/jsx-runtime.js +52 -0
package/src/ValUI.tsx ADDED
@@ -0,0 +1,353 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { useEffect, useState } from "react";
3
+ import { ValApi } from "./ValApi";
4
+ import { ValStore } from "./ValStore";
5
+ import { Inputs, Style, ValOverlay } from "@valbuild/ui";
6
+ import {
7
+ FileSource,
8
+ FILE_REF_PROP,
9
+ Internal,
10
+ RichText,
11
+ SourcePath,
12
+ VAL_EXTENSION,
13
+ } from "@valbuild/core";
14
+ import { PatchJSON } from "@valbuild/core/patch";
15
+ import { ImageMetadata } from "@valbuild/core/src/schema/image";
16
+ import { AuthStatus } from "./AuthStatus";
17
+ import { ShadowRoot } from "./ShadowRoot";
18
+
19
+ export type ValUIProps = {
20
+ valStore: ValStore;
21
+ valApi: ValApi;
22
+ };
23
+
24
+ export default function ValUI({ valApi, valStore }: ValUIProps) {
25
+ const [selectedSources, setSelectedSources] = useState<string[]>([]);
26
+ const [editMode, setEditMode] = useState(false);
27
+ const [editFormPosition, setEditFormPosition] = useState<{
28
+ left: number;
29
+ top: number;
30
+ } | null>(null);
31
+
32
+ const [authentication, setAuthentication] = useState<AuthStatus>({
33
+ status: "not-asked",
34
+ });
35
+
36
+ useEffect(() => {
37
+ if (editMode) {
38
+ valStore.updateAll();
39
+ }
40
+ }, [editMode]);
41
+ useEffect(() => {
42
+ let openValFormListener: ((e: MouseEvent) => void) | undefined = undefined;
43
+ let styleElement: HTMLStyleElement | undefined = undefined;
44
+ const editButtonClickOptions = {
45
+ capture: true,
46
+ passive: true,
47
+ };
48
+ if (editMode) {
49
+ // highlight val element by appending a new style
50
+ styleElement = document.createElement("style");
51
+ styleElement.id = "val-edit-highlight";
52
+ styleElement.innerHTML = `
53
+ .val-edit-mode >* [data-val-path] {
54
+ outline: #ffff00 solid 1px;
55
+ cursor: pointer;
56
+ }
57
+ `;
58
+ document.body.appendChild(styleElement);
59
+
60
+ // capture event clicks on data-val-path elements
61
+ openValFormListener = (e: MouseEvent) => {
62
+ if (e.target instanceof Element) {
63
+ let parent = e.target;
64
+ while (parent && parent !== document.body) {
65
+ if (parent.getAttribute("data-val-path")) {
66
+ break;
67
+ }
68
+ if (parent.parentElement) {
69
+ parent = parent.parentElement;
70
+ } else {
71
+ break;
72
+ }
73
+ }
74
+ const valSources = parent?.getAttribute("data-val-path");
75
+ if (valSources) {
76
+ e.stopPropagation();
77
+ setSelectedSources(
78
+ valSources.split(
79
+ ","
80
+ ) /* TODO: just split on commas will not work if path contains , */
81
+ );
82
+ setEditFormPosition({
83
+ left: e.pageX,
84
+ top: e.pageY,
85
+ });
86
+ // } else if (!isValElement(e.target)) {
87
+ // console.log("click outside", e.target);
88
+ // setEditFormPosition(null);
89
+ // setSelectedSources([]);
90
+ }
91
+ }
92
+ };
93
+ document.addEventListener(
94
+ "click",
95
+ openValFormListener,
96
+ editButtonClickOptions
97
+ );
98
+ }
99
+ return () => {
100
+ if (openValFormListener) {
101
+ document.removeEventListener(
102
+ "click",
103
+ openValFormListener,
104
+ editButtonClickOptions
105
+ );
106
+ }
107
+ styleElement?.remove();
108
+ };
109
+ }, [editMode]);
110
+ useEffect(() => {
111
+ if (editMode) {
112
+ document.body.classList.add("val-edit-mode");
113
+ } else {
114
+ document.body.classList.remove("val-edit-mode");
115
+ }
116
+
117
+ if (editMode) {
118
+ if (authentication.status !== "authenticated") {
119
+ valApi
120
+ .getSession()
121
+ .then(async (res) => {
122
+ if (res.status === 401) {
123
+ setAuthentication({
124
+ status: "unauthenticated",
125
+ });
126
+ } else if (res.ok) {
127
+ const data = await res.json();
128
+ if (data.mode === "local") {
129
+ setAuthentication({ status: "local" });
130
+ } else if (data.mode === "proxy") {
131
+ setAuthentication({
132
+ status: "authenticated",
133
+ });
134
+ } else {
135
+ setAuthentication({
136
+ status: "error",
137
+ message: "Unknown authentication mode",
138
+ });
139
+ }
140
+ } else {
141
+ let message = "Unknown error";
142
+ try {
143
+ message = await res.text();
144
+ } catch {
145
+ // ignore
146
+ }
147
+ setAuthentication({
148
+ status: "error",
149
+ message,
150
+ });
151
+ }
152
+ })
153
+ .catch((err) => {
154
+ console.error("Failed to fetch session", err);
155
+ setAuthentication({
156
+ status: "error",
157
+ message: "Unknown authentication mode",
158
+ });
159
+ });
160
+ }
161
+ } else {
162
+ if (authentication.status === "error") {
163
+ setAuthentication({
164
+ status: "not-asked",
165
+ });
166
+ }
167
+ }
168
+ }, [editMode, authentication.status]);
169
+
170
+ const [showEditButton, setShowEditButton] = useState(false);
171
+ useEffect(() => {
172
+ setShowEditButton(true);
173
+ }, []);
174
+
175
+ const [inputs, setInputs] = useState<Inputs>({});
176
+
177
+ useEffect(() => {
178
+ setInputs({});
179
+ for (const path of selectedSources) {
180
+ valApi.getModule(path).then((serializedModule) => {
181
+ let input: Inputs[string] | undefined;
182
+ if (
183
+ serializedModule.schema.type === "string" &&
184
+ typeof serializedModule.source === "string"
185
+ ) {
186
+ input = {
187
+ status: "completed",
188
+ type: "text",
189
+ data: serializedModule.source,
190
+ };
191
+ } else if (
192
+ serializedModule.schema.type === "richtext" &&
193
+ typeof serializedModule.source === "object"
194
+ ) {
195
+ input = {
196
+ status: "completed",
197
+ type: "richtext",
198
+ data: serializedModule.source as RichText, // TODO: validate
199
+ };
200
+ } else if (
201
+ serializedModule.schema.type === "image" &&
202
+ serializedModule.source &&
203
+ typeof serializedModule.source === "object" &&
204
+ FILE_REF_PROP in serializedModule.source &&
205
+ typeof serializedModule.source[FILE_REF_PROP] === "string" &&
206
+ VAL_EXTENSION in serializedModule.source &&
207
+ typeof serializedModule.source[VAL_EXTENSION] === "string"
208
+ ) {
209
+ input = {
210
+ status: "completed",
211
+ type: "image",
212
+ data: Internal.convertImageSource(
213
+ serializedModule.source as FileSource<ImageMetadata>
214
+ ),
215
+ };
216
+ }
217
+ console.log("input path", path);
218
+ console.log("serialized path", serializedModule.path);
219
+ if (!input) {
220
+ throw new Error(
221
+ `Unsupported module type: ${serializedModule.schema.type}`
222
+ );
223
+ }
224
+ setInputs((inputs) => {
225
+ return {
226
+ ...inputs,
227
+ [serializedModule.path]: input,
228
+ } as Inputs;
229
+ });
230
+ });
231
+ }
232
+ }, [selectedSources.join(",")]);
233
+ if (!showEditButton) {
234
+ return null;
235
+ }
236
+ return (
237
+ <ShadowRoot>
238
+ {/* TODO: */}
239
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
240
+ <link
241
+ rel="preconnect"
242
+ href="https://fonts.gstatic.com"
243
+ crossOrigin="anonymous"
244
+ />
245
+ <link
246
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,400&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,400;1,700&display=swap"
247
+ rel="stylesheet"
248
+ />
249
+ <Style />
250
+ <div data-mode="dark">
251
+ <ValOverlay
252
+ editMode={editMode}
253
+ setEditMode={setEditMode}
254
+ closeValWindow={() => {
255
+ setEditFormPosition(null);
256
+ setSelectedSources([]);
257
+ setInputs({});
258
+ }}
259
+ valWindow={
260
+ (editFormPosition && {
261
+ position: editFormPosition,
262
+ inputs,
263
+ onSubmit: (inputs) => {
264
+ Promise.all(
265
+ Object.entries(inputs).map(([path, input]) => {
266
+ if (input.status === "completed") {
267
+ const [moduleId, modulePath] =
268
+ Internal.splitModuleIdAndModulePath(path as SourcePath);
269
+ if (input.type === "text") {
270
+ const patch: PatchJSON = [
271
+ {
272
+ value: input.data,
273
+ op: "replace",
274
+ path: `/${modulePath
275
+ .split(".")
276
+ .map((p) => JSON.parse(p))
277
+ .join("/")}`,
278
+ },
279
+ ];
280
+ return valApi.patchModuleContent(moduleId, patch);
281
+ } else if (input.type === "image") {
282
+ const pathParts = modulePath
283
+ .split(".")
284
+ .map((p) => JSON.parse(p));
285
+
286
+ if (!input?.data || !("src" in input.data)) {
287
+ // TODO: We probably need to have an Output type that is different from the Input: we have a union of both cases in Input right now, and we believe we do not want that
288
+ console.warn(
289
+ "No .src on input provided - this might mean no changes was made"
290
+ );
291
+ return;
292
+ }
293
+ const patch: PatchJSON = [
294
+ {
295
+ value: input.data.src,
296
+ op: "replace",
297
+ path: `/${pathParts.slice(0, -1).join("/")}/$${
298
+ pathParts[pathParts.length - 1]
299
+ }`,
300
+ },
301
+ ];
302
+ if (input.data.metadata) {
303
+ if (input.data.addMetadata) {
304
+ patch.push({
305
+ value: input.data.metadata,
306
+ op: "add",
307
+ path: `/${pathParts.join("/")}/metadata`,
308
+ });
309
+ } else {
310
+ patch.push({
311
+ value: input.data.metadata,
312
+ op: "replace",
313
+ path: `/${pathParts.join("/")}/metadata`,
314
+ });
315
+ }
316
+ }
317
+ console.log("patch", patch);
318
+ return valApi.patchModuleContent(moduleId, patch);
319
+ } else if (input.type === "richtext") {
320
+ const patch: PatchJSON = [
321
+ {
322
+ value: input.data,
323
+ op: "replace",
324
+ path: `/${modulePath
325
+ .split(".")
326
+ .map((p) => JSON.parse(p))
327
+ .join("/")}`,
328
+ },
329
+ ];
330
+ return valApi.patchModuleContent(moduleId, patch);
331
+ }
332
+ throw new Error(
333
+ `Unsupported input type: ${(input as any).type}`
334
+ );
335
+ } else {
336
+ console.error("Submitted incomplete input, ignoring...");
337
+ return Promise.resolve();
338
+ }
339
+ })
340
+ ).then(() => {
341
+ setEditFormPosition(null);
342
+ setSelectedSources([]);
343
+ setInputs({});
344
+ });
345
+ },
346
+ }) ??
347
+ undefined
348
+ }
349
+ />
350
+ </div>
351
+ </ShadowRoot>
352
+ );
353
+ }
package/src/assets.ts ADDED
@@ -0,0 +1,124 @@
1
+ import * as base64 from "base64-arraybuffer";
2
+
3
+ function dataUrl(mimeType: string, data: string): string {
4
+ return `data:${mimeType};base64,${base64.encode(
5
+ new TextEncoder().encode(data)
6
+ )}`;
7
+ }
8
+
9
+ // TODO: stroke should be currentColor
10
+ export const editIcon = (size: number, stroke: string) =>
11
+ dataUrl(
12
+ "image/svg+xml",
13
+ `
14
+ <svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${stroke}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
15
+ `
16
+ );
17
+
18
+ export const logo = dataUrl(
19
+ "image/svg+xml",
20
+ `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
21
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
22
+
23
+ <svg
24
+ width="101.83195mm"
25
+ height="103.55328mm"
26
+ viewBox="0 0 101.83195 103.55328"
27
+ version="1.1"
28
+ id="svg974"
29
+ inkscape:export-filename="logo.svg"
30
+ inkscape:export-xdpi="96"
31
+ inkscape:export-ydpi="96"
32
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
33
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
34
+ xmlns="http://www.w3.org/2000/svg"
35
+ xmlns:svg="http://www.w3.org/2000/svg">
36
+ <sodipodi:namedview
37
+ id="namedview976"
38
+ pagecolor="#ffffff"
39
+ bordercolor="#000000"
40
+ borderopacity="0.25"
41
+ inkscape:showpageshadow="2"
42
+ inkscape:pageopacity="0.0"
43
+ inkscape:pagecheckerboard="0"
44
+ inkscape:deskcolor="#d1d1d1"
45
+ inkscape:document-units="mm"
46
+ showgrid="false"
47
+ inkscape:zoom="1.4040232"
48
+ inkscape:cx="39.173141"
49
+ inkscape:cy="503.90904"
50
+ inkscape:window-width="3832"
51
+ inkscape:window-height="2087"
52
+ inkscape:window-x="0"
53
+ inkscape:window-y="69"
54
+ inkscape:window-maximized="1"
55
+ inkscape:current-layer="layer1" />
56
+ <defs
57
+ id="defs971" />
58
+ <g
59
+ inkscape:label="Layer 1"
60
+ inkscape:groupmode="layer"
61
+ id="layer1"
62
+ transform="translate(-46.162121,-16.863144)">
63
+ <ellipse
64
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:4.41854;stroke-dasharray:none;stroke-opacity:1"
65
+ id="path2242"
66
+ ry="49.567371"
67
+ rx="48.706703"
68
+ cy="68.639786"
69
+ cx="97.078094" />
70
+ <path
71
+ style="fill:#000000;fill-opacity:1;stroke:#f2f2f2;stroke-width:9.9912;stroke-dasharray:none;stroke-opacity:1"
72
+ d="m 65.105895,44.462411 18.85692,45.934668 13.064363,-39.90188 15.829132,39.947835 23.07956,-0.1822"
73
+ id="path4245"
74
+ sodipodi:nodetypes="ccccc" />
75
+ <path
76
+ style="fill:#000000;fill-opacity:1;stroke:#f2f2f2;stroke-width:4.85097;stroke-dasharray:none;stroke-opacity:1"
77
+ d="M 108.18755,79.963752 C 101.58768,84.940963 94.021144,82.50121 86.406627,79.693345"
78
+ id="path4249"
79
+ sodipodi:nodetypes="cc" />
80
+ </g>
81
+ </svg>`
82
+ );
83
+
84
+ export const valcmsLogo = dataUrl(
85
+ "image/svg+xml",
86
+ `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
87
+ <svg
88
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
89
+ xmlns:cc="http://creativecommons.org/ns#"
90
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
91
+ xmlns:svg="http://www.w3.org/2000/svg"
92
+ xmlns="http://www.w3.org/2000/svg"
93
+ width="20"
94
+ height="20"
95
+ viewBox="0 0 52.916665 52.916668"
96
+ version="1.1"
97
+ id="svg8">
98
+ <defs
99
+ id="defs2" />
100
+ <metadata
101
+ id="metadata5">
102
+ <rdf:RDF>
103
+ <cc:Work
104
+ rdf:about="">
105
+ <dc:format>image/svg+xml</dc:format>
106
+ <dc:type
107
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
108
+ <dc:title></dc:title>
109
+ </cc:Work>
110
+ </rdf:RDF>
111
+ </metadata>
112
+ <g
113
+ id="layer1">
114
+ <path
115
+ style="fill:none;stroke:white;stroke-width:5.00377;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
116
+ d="M 3.9337727,9.883415 14.718441,43.556958 25.597715,9.9678121 35.530965,43.388176 h 15.798599 v 0 h 0.09461"
117
+ id="path10" />
118
+ <path
119
+ style="fill:none;stroke:white;stroke-width:5.00377;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
120
+ d="m 19.826972,27.859518 11.257682,0.0844 v 0 0"
121
+ id="path837" />
122
+ </g>
123
+ </svg>`
124
+ );
@@ -0,0 +1,57 @@
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ import { useVal } from "./useVal";
3
+ import { initVal, Val } from "@valbuild/core";
4
+ import { renderHook } from "@testing-library/react";
5
+ import { ValContext } from "../ValProvider";
6
+ import { ReactElement } from "react";
7
+ import { ValStore } from "../ValStore";
8
+ import { ValApi } from "../ValApi";
9
+
10
+ const valApi = new ValApi("mock");
11
+ const valStore = new ValStore(valApi);
12
+
13
+ const Providers = ({ children }: { children: ReactElement }) => (
14
+ <ValContext.Provider
15
+ value={{
16
+ valStore,
17
+ valApi,
18
+ }}
19
+ >
20
+ {children}
21
+ </ValContext.Provider>
22
+ );
23
+
24
+ // const { s, val } = initVal();
25
+
26
+ describe("useVal", () => {
27
+ test.skip("extracts ValString from string", () => {
28
+ // const mod = val.content("foo", s.string(), "bar");
29
+ // const { result } = renderHook(() => useVal(mod, "en_US"), {
30
+ // wrapper: Providers,
31
+ // });
32
+ // expect(result.current).toStrictEqual<Val<string>>({
33
+ // val: "bar",
34
+ // valSrc: "foo?en_US?",
35
+ // });
36
+ });
37
+
38
+ test.skip("extracts ValString from ValObject", () => {
39
+ // const mod = val.content("baz", s.object({ foo: s.string() }), {
40
+ // foo: "bar",
41
+ // });
42
+ // const { result } = renderHook(() => useVal(mod, "en_US"), {
43
+ // wrapper: Providers,
44
+ // });
45
+ // const vo: Val<{ foo: string }> = result.current;
46
+ // expect(vo.foo).toStrictEqual<Val<string>>({
47
+ // valSrc: `baz?en_US?."foo"`,
48
+ // val: "bar",
49
+ // });
50
+ // expect(val).toStrictEqual<ValObject<{ foo: string }>>({
51
+ // foo: {
52
+ // id: "baz.foo",
53
+ // val: "bar",
54
+ // },
55
+ // });
56
+ });
57
+ });
@@ -0,0 +1,35 @@
1
+ import {
2
+ SelectorSource,
3
+ SelectorOf,
4
+ GenericSelector,
5
+ Val,
6
+ Internal,
7
+ } from "@valbuild/core";
8
+ import { JsonOfSource } from "@valbuild/core/src/val";
9
+
10
+ export function useVal<T extends SelectorSource>(
11
+ selector: T,
12
+ locale?: string
13
+ ): SelectorOf<T> extends GenericSelector<infer S>
14
+ ? Val<JsonOfSource<S>>
15
+ : never {
16
+ // const mod = selectable.getModule();
17
+ // const valStore = useValStore();
18
+ // const remoteContent = useSyncExternalStore(
19
+ // valStore.subscribe(mod.id),
20
+ // valStore.getSnapshot(mod.id),
21
+ // valStore.getServerSnapshot(mod.id)
22
+ // );
23
+ // if (remoteContent) {
24
+ // return selectable.getVal(remoteContent.source as S, locale);
25
+ // }
26
+ // const content = mod.content;
27
+ // const validationError = content.validate();
28
+ // if (validationError) {
29
+ // throw new Error(
30
+ // `Invalid source value. Errors:\n${validationError.join("\n")}`
31
+ // );
32
+ // }
33
+
34
+ return Internal.getVal(selector, locale);
35
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ /* export { useContent } from "./useContent";
2
+ export { useText } from "./useText";
3
+ export { WithVal } from "./WithVal"; */
4
+ export { ValProviderWrapper as ValProvider } from "./ValProviderWrapper";
5
+ export { useVal } from "./hooks/useVal";
6
+ export { ValRichText } from "./ValRichText";
@@ -0,0 +1,47 @@
1
+ import { Internal } from "@valbuild/core";
2
+ import * as ReactJSXRuntimeDev from "react/jsx-dev-runtime";
3
+ export * from "react/jsx-dev-runtime";
4
+
5
+ const isIntrinsicElement = (type) => {
6
+ // TODO: think this is not correct, but good enough for now?
7
+ return typeof type === "string";
8
+ };
9
+
10
+ const devalProps = (type, props) => {
11
+ const valSources = [];
12
+
13
+ if (isIntrinsicElement(type)) {
14
+ for (const [key, value] of Object.entries(props)) {
15
+ if (typeof value === "object" && value !== null && "val" in value) {
16
+ const valPath = Internal.getValPath(value);
17
+ if (valPath) {
18
+ valSources.push(valPath);
19
+ if (typeof value.val === "string" || value.val === null) {
20
+ props[key] = value.val;
21
+ } else {
22
+ throw Error("TODO: unhandled value type");
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
28
+
29
+ if (valSources.length > 0) {
30
+ props["data-val-path"] = valSources.join(",");
31
+ }
32
+ };
33
+
34
+ export function jsxDEV(type, props, key, isStaticChildren, source, self) {
35
+ // console.log("jsxDEV", type, props, key, isStaticChildren, source, self);
36
+
37
+ devalProps(type, props);
38
+
39
+ return ReactJSXRuntimeDev.jsxDEV(
40
+ type,
41
+ props,
42
+ key,
43
+ isStaticChildren,
44
+ source,
45
+ self
46
+ );
47
+ }
@@ -0,0 +1,46 @@
1
+ import { Source, Val } from "@valbuild/core";
2
+
3
+ // unpack all here to avoid infinite self-referencing when defining our own JSX namespace
4
+ type ReactJSXElement = JSX.Element;
5
+ type ReactJSXElementClass = JSX.ElementClass;
6
+ type ReactJSXElementAttributesProperty = JSX.ElementAttributesProperty;
7
+ type ReactJSXElementChildrenAttribute = JSX.ElementChildrenAttribute;
8
+ type ReactJSXLibraryManagedAttributes<C, P> = JSX.LibraryManagedAttributes<
9
+ C,
10
+ P
11
+ >;
12
+ type ReactJSXIntrinsicAttributes = JSX.IntrinsicAttributes;
13
+ type ReactJSXIntrinsicClassAttributes<T> = JSX.IntrinsicClassAttributes<T>;
14
+ type ReactJSXIntrinsicElements = JSX.IntrinsicElements;
15
+
16
+ type MaybeVal<T> = T extends Source ? Val<T> | T : T;
17
+ type WithVal<T extends object> = {
18
+ [K in keyof T]: K extends "key" | "ref" | "className"
19
+ ? T[K]
20
+ : K extends "style"
21
+ ? WithVal<React.CSSProperties>
22
+ : T[K] extends object
23
+ ? T[K]
24
+ : MaybeVal<T[K]>;
25
+ };
26
+
27
+ export namespace ValJSX {
28
+ export type Element = ReactJSXElement;
29
+ export type ElementClass = ReactJSXElementClass;
30
+ export type ElementAttributesProperty = ReactJSXElementAttributesProperty;
31
+ export type ElementChildrenAttribute = ReactJSXElementChildrenAttribute;
32
+
33
+ export type LibraryManagedAttributes<C, P> = ReactJSXLibraryManagedAttributes<
34
+ C,
35
+ P
36
+ >;
37
+
38
+ export type IntrinsicAttributes = ReactJSXIntrinsicAttributes;
39
+ export type IntrinsicClassAttributes<T> = ReactJSXIntrinsicClassAttributes<T>;
40
+
41
+ export type IntrinsicElements = {
42
+ [K in keyof ReactJSXIntrinsicElements]: WithVal<
43
+ ReactJSXIntrinsicElements[K]
44
+ >;
45
+ };
46
+ }
@@ -0,0 +1 @@
1
+ export { ValJSX as JSX } from "./jsx-namespace";
@@ -0,0 +1 @@
1
+ export { ValJSX as JSX } from "./jsx-namespace";