@valbuild/react 0.12.0 → 0.13.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.
- package/jest.config.js +5 -0
- package/package.json +3 -3
- package/src/ValApi.ts +65 -0
- package/src/ValProvider.tsx +424 -0
- package/src/ValRichText.tsx +141 -0
- package/src/ValStore.ts +62 -0
- package/src/assets.ts +124 -0
- package/src/hooks/useVal.test.tsx +57 -0
- package/src/hooks/useVal.ts +35 -0
- package/src/index.ts +6 -0
- package/src/jsx-dev-runtime.js +47 -0
- package/src/jsx-namespace.d.ts +46 -0
- package/src/jsx-runtime.d.ts +1 -0
- package/src/jsx-runtime.dev.d.ts +1 -0
- package/src/jsx-runtime.js +52 -0
package/jest.config.js
ADDED
package/package.json
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
{
|
2
2
|
"name": "@valbuild/react",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.13.0",
|
4
4
|
"sideEffects": false,
|
5
5
|
"scripts": {
|
6
6
|
"typecheck": "tsc --noEmit",
|
7
7
|
"test": "jest"
|
8
8
|
},
|
9
9
|
"dependencies": {
|
10
|
-
"@valbuild/core": "~0.
|
11
|
-
"@valbuild/ui": "~0.
|
10
|
+
"@valbuild/core": "~0.13.0",
|
11
|
+
"@valbuild/ui": "~0.13.0",
|
12
12
|
"base64-arraybuffer": "^1.0.2",
|
13
13
|
"react-shadow": "^20.0.0",
|
14
14
|
"style-to-object": "^0.4.1"
|
package/src/ValApi.ts
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
import { SerializedModule } from "@valbuild/core";
|
2
|
+
import { PatchJSON } from "@valbuild/core/patch";
|
3
|
+
|
4
|
+
export class ValApi {
|
5
|
+
constructor(readonly host: string) {}
|
6
|
+
|
7
|
+
async getModule(sourcePath: string): Promise<SerializedModule> {
|
8
|
+
const res = await fetch(`${this.host}/ids${sourcePath}`);
|
9
|
+
if (res.ok) {
|
10
|
+
const serializedVal = await res.json(); // TODO: validate
|
11
|
+
return serializedVal;
|
12
|
+
} else {
|
13
|
+
throw Error(
|
14
|
+
`Failed to get content of module "${sourcePath}". Status: ${
|
15
|
+
res.status
|
16
|
+
}. Error: ${await res.text()}`
|
17
|
+
);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
async patchModuleContent(
|
22
|
+
moduleId: string,
|
23
|
+
patch: PatchJSON
|
24
|
+
): Promise<SerializedModule> {
|
25
|
+
const res = await fetch(`${this.host}/ids${moduleId}`, {
|
26
|
+
method: "PATCH",
|
27
|
+
headers: {
|
28
|
+
"Content-Type": "application/json-patch+json",
|
29
|
+
},
|
30
|
+
body: JSON.stringify(patch),
|
31
|
+
});
|
32
|
+
if (res.ok) {
|
33
|
+
return res.json(); // TODO: validate
|
34
|
+
} else {
|
35
|
+
throw Error(
|
36
|
+
`Failed to patch content of module "${moduleId}". Error: ${await res.text()}`
|
37
|
+
);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
async commit(): Promise<void> {
|
42
|
+
const res = await fetch(`${this.host}/commit`, {
|
43
|
+
method: "POST",
|
44
|
+
});
|
45
|
+
if (res.ok) {
|
46
|
+
return;
|
47
|
+
} else {
|
48
|
+
throw Error(`Failed to commit. Error: ${await res.text()}`);
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
getSession() {
|
53
|
+
return fetch(`${this.host}/session`);
|
54
|
+
}
|
55
|
+
|
56
|
+
loginUrl() {
|
57
|
+
return `${this.host}/authorize?redirect_to=${encodeURIComponent(
|
58
|
+
location.href
|
59
|
+
)}`;
|
60
|
+
}
|
61
|
+
|
62
|
+
logout() {
|
63
|
+
return fetch(`${this.host}/logout`);
|
64
|
+
}
|
65
|
+
}
|
@@ -0,0 +1,424 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
import React, { useContext, useEffect, useMemo, useState } from "react";
|
3
|
+
import { ValApi } from "./ValApi";
|
4
|
+
import { ValStore } from "./ValStore";
|
5
|
+
import { Inputs, Style, ValOverlay } from "@valbuild/ui";
|
6
|
+
import root from "react-shadow"; // TODO: remove dependency on react-shadow here?
|
7
|
+
import {
|
8
|
+
FileSource,
|
9
|
+
FILE_REF_PROP,
|
10
|
+
Internal,
|
11
|
+
RichText,
|
12
|
+
SourcePath,
|
13
|
+
VAL_EXTENSION,
|
14
|
+
} from "@valbuild/core";
|
15
|
+
import { PatchJSON } from "@valbuild/core/patch";
|
16
|
+
import { ImageMetadata } from "@valbuild/core/src/schema/image";
|
17
|
+
|
18
|
+
export function useValStore() {
|
19
|
+
return useContext(ValContext).valStore;
|
20
|
+
}
|
21
|
+
export function useValApi() {
|
22
|
+
return useContext(ValContext).valApi;
|
23
|
+
}
|
24
|
+
|
25
|
+
export type ValContext = {
|
26
|
+
readonly valStore: ValStore;
|
27
|
+
readonly valApi: ValApi;
|
28
|
+
};
|
29
|
+
|
30
|
+
export const ValContext = React.createContext<ValContext>({
|
31
|
+
get valStore(): never {
|
32
|
+
throw Error(
|
33
|
+
"Val context not found. Ensure components are wrapped by ValProvider!"
|
34
|
+
);
|
35
|
+
},
|
36
|
+
get valApi(): never {
|
37
|
+
throw Error(
|
38
|
+
"Val context not found. Ensure components are wrapped by ValProvider!"
|
39
|
+
);
|
40
|
+
},
|
41
|
+
});
|
42
|
+
|
43
|
+
export type ValProviderProps = {
|
44
|
+
host?: string;
|
45
|
+
children?: React.ReactNode;
|
46
|
+
};
|
47
|
+
|
48
|
+
type AuthStatus =
|
49
|
+
| {
|
50
|
+
status:
|
51
|
+
| "not-asked"
|
52
|
+
| "authenticated"
|
53
|
+
| "unauthenticated"
|
54
|
+
| "loading"
|
55
|
+
| "local";
|
56
|
+
}
|
57
|
+
| {
|
58
|
+
status: "error";
|
59
|
+
message: string;
|
60
|
+
};
|
61
|
+
|
62
|
+
export function ValProvider({ host = "/api/val", children }: ValProviderProps) {
|
63
|
+
const [selectedSources, setSelectedSources] = useState<string[]>([]);
|
64
|
+
const [editMode, setEditMode] = useState(false);
|
65
|
+
const [editFormPosition, setEditFormPosition] = useState<{
|
66
|
+
left: number;
|
67
|
+
top: number;
|
68
|
+
} | null>(null);
|
69
|
+
|
70
|
+
const [authentication, setAuthentication] = useState<AuthStatus>({
|
71
|
+
status: "not-asked",
|
72
|
+
});
|
73
|
+
const valApi = useMemo(() => new ValApi(host), [host]);
|
74
|
+
const valStore = useMemo(() => new ValStore(valApi), [valApi]);
|
75
|
+
|
76
|
+
useEffect(() => {
|
77
|
+
if (editMode) {
|
78
|
+
valStore.updateAll();
|
79
|
+
}
|
80
|
+
}, [editMode]);
|
81
|
+
useEffect(() => {
|
82
|
+
let openValFormListener: ((e: MouseEvent) => void) | undefined = undefined;
|
83
|
+
let styleElement: HTMLStyleElement | undefined = undefined;
|
84
|
+
const editButtonClickOptions = {
|
85
|
+
capture: true,
|
86
|
+
passive: true,
|
87
|
+
};
|
88
|
+
if (editMode) {
|
89
|
+
// highlight val element by appending a new style
|
90
|
+
styleElement = document.createElement("style");
|
91
|
+
styleElement.id = "val-edit-highlight";
|
92
|
+
styleElement.innerHTML = `
|
93
|
+
.val-edit-mode >* [data-val-path] {
|
94
|
+
outline: black solid 2px;
|
95
|
+
outline-offset: 4px;
|
96
|
+
cursor: pointer;
|
97
|
+
}
|
98
|
+
`;
|
99
|
+
document.body.appendChild(styleElement);
|
100
|
+
|
101
|
+
// capture event clicks on data-val-path elements
|
102
|
+
openValFormListener = (e: MouseEvent) => {
|
103
|
+
if (e.target instanceof Element) {
|
104
|
+
let parent = e.target;
|
105
|
+
while (parent && parent !== document.body) {
|
106
|
+
if (parent.getAttribute("data-val-path")) {
|
107
|
+
break;
|
108
|
+
}
|
109
|
+
if (parent.parentElement) {
|
110
|
+
parent = parent.parentElement;
|
111
|
+
} else {
|
112
|
+
break;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
const valSources = parent?.getAttribute("data-val-path");
|
116
|
+
if (valSources) {
|
117
|
+
e.stopPropagation();
|
118
|
+
setSelectedSources(
|
119
|
+
valSources.split(
|
120
|
+
","
|
121
|
+
) /* TODO: just split on commas will not work if path contains , */
|
122
|
+
);
|
123
|
+
setEditFormPosition({
|
124
|
+
left: e.pageX,
|
125
|
+
top: e.pageY,
|
126
|
+
});
|
127
|
+
// } else if (!isValElement(e.target)) {
|
128
|
+
// console.log("click outside", e.target);
|
129
|
+
// setEditFormPosition(null);
|
130
|
+
// setSelectedSources([]);
|
131
|
+
}
|
132
|
+
}
|
133
|
+
};
|
134
|
+
document.addEventListener(
|
135
|
+
"click",
|
136
|
+
openValFormListener,
|
137
|
+
editButtonClickOptions
|
138
|
+
);
|
139
|
+
}
|
140
|
+
return () => {
|
141
|
+
if (openValFormListener) {
|
142
|
+
document.removeEventListener(
|
143
|
+
"click",
|
144
|
+
openValFormListener,
|
145
|
+
editButtonClickOptions
|
146
|
+
);
|
147
|
+
}
|
148
|
+
styleElement?.remove();
|
149
|
+
};
|
150
|
+
}, [editMode]);
|
151
|
+
|
152
|
+
// useEffect(() => {
|
153
|
+
// const requestAuth = !(
|
154
|
+
// authentication.status === "authenticated" ||
|
155
|
+
// authentication.status === "local"
|
156
|
+
// );
|
157
|
+
// if (requestAuth) {
|
158
|
+
// setSelectedSources([]);
|
159
|
+
// console.log("request auth");
|
160
|
+
// setEditFormPosition(null);
|
161
|
+
// }
|
162
|
+
// if (!editMode) {
|
163
|
+
// // reset state when disabled
|
164
|
+
// setSelectedSources([]);
|
165
|
+
// console.log("reset state");
|
166
|
+
// setEditFormPosition(null);
|
167
|
+
// }
|
168
|
+
// }, [editMode, selectedSources.length, authentication.status]);
|
169
|
+
|
170
|
+
useEffect(() => {
|
171
|
+
if (editMode) {
|
172
|
+
document.body.classList.add("val-edit-mode");
|
173
|
+
} else {
|
174
|
+
document.body.classList.remove("val-edit-mode");
|
175
|
+
}
|
176
|
+
|
177
|
+
if (editMode) {
|
178
|
+
if (authentication.status !== "authenticated") {
|
179
|
+
valApi
|
180
|
+
.getSession()
|
181
|
+
.then(async (res) => {
|
182
|
+
if (res.status === 401) {
|
183
|
+
setAuthentication({
|
184
|
+
status: "unauthenticated",
|
185
|
+
});
|
186
|
+
} else if (res.ok) {
|
187
|
+
const data = await res.json();
|
188
|
+
if (data.mode === "local") {
|
189
|
+
setAuthentication({ status: "local" });
|
190
|
+
} else if (data.mode === "proxy") {
|
191
|
+
setAuthentication({
|
192
|
+
status: "authenticated",
|
193
|
+
});
|
194
|
+
} else {
|
195
|
+
setAuthentication({
|
196
|
+
status: "error",
|
197
|
+
message: "Unknown authentication mode",
|
198
|
+
});
|
199
|
+
}
|
200
|
+
} else {
|
201
|
+
let message = "Unknown error";
|
202
|
+
try {
|
203
|
+
message = await res.text();
|
204
|
+
} catch {
|
205
|
+
// ignore
|
206
|
+
}
|
207
|
+
setAuthentication({
|
208
|
+
status: "error",
|
209
|
+
message,
|
210
|
+
});
|
211
|
+
}
|
212
|
+
})
|
213
|
+
.catch((err) => {
|
214
|
+
console.error("Failed to fetch session", err);
|
215
|
+
setAuthentication({
|
216
|
+
status: "error",
|
217
|
+
message: "Unknown authentication mode",
|
218
|
+
});
|
219
|
+
});
|
220
|
+
}
|
221
|
+
} else {
|
222
|
+
if (authentication.status === "error") {
|
223
|
+
setAuthentication({
|
224
|
+
status: "not-asked",
|
225
|
+
});
|
226
|
+
}
|
227
|
+
}
|
228
|
+
}, [editMode, authentication.status]);
|
229
|
+
|
230
|
+
const [showEditButton, setShowEditButton] = useState(false);
|
231
|
+
useEffect(() => {
|
232
|
+
setShowEditButton(true);
|
233
|
+
}, []);
|
234
|
+
|
235
|
+
const [inputs, setInputs] = useState<Inputs>({});
|
236
|
+
|
237
|
+
useEffect(() => {
|
238
|
+
setInputs({});
|
239
|
+
for (const path of selectedSources) {
|
240
|
+
valApi.getModule(path).then((serializedModule) => {
|
241
|
+
let input: Inputs[string] | undefined;
|
242
|
+
if (
|
243
|
+
serializedModule.schema.type === "string" &&
|
244
|
+
typeof serializedModule.source === "string"
|
245
|
+
) {
|
246
|
+
input = {
|
247
|
+
status: "completed",
|
248
|
+
type: "text",
|
249
|
+
data: serializedModule.source,
|
250
|
+
};
|
251
|
+
} else if (
|
252
|
+
serializedModule.schema.type === "richtext" &&
|
253
|
+
typeof serializedModule.source === "object"
|
254
|
+
) {
|
255
|
+
input = {
|
256
|
+
status: "completed",
|
257
|
+
type: "richtext",
|
258
|
+
data: serializedModule.source as RichText, // TODO: validate
|
259
|
+
};
|
260
|
+
} else if (
|
261
|
+
serializedModule.schema.type === "image" &&
|
262
|
+
serializedModule.source &&
|
263
|
+
typeof serializedModule.source === "object" &&
|
264
|
+
FILE_REF_PROP in serializedModule.source &&
|
265
|
+
typeof serializedModule.source[FILE_REF_PROP] === "string" &&
|
266
|
+
VAL_EXTENSION in serializedModule.source &&
|
267
|
+
typeof serializedModule.source[VAL_EXTENSION] === "string"
|
268
|
+
) {
|
269
|
+
input = {
|
270
|
+
status: "completed",
|
271
|
+
type: "image",
|
272
|
+
data: Internal.convertImageSource(
|
273
|
+
serializedModule.source as FileSource<ImageMetadata>
|
274
|
+
),
|
275
|
+
};
|
276
|
+
}
|
277
|
+
console.log("input path", path);
|
278
|
+
console.log("serialized path", serializedModule.path);
|
279
|
+
if (!input) {
|
280
|
+
throw new Error(
|
281
|
+
`Unsupported module type: ${serializedModule.schema.type}`
|
282
|
+
);
|
283
|
+
}
|
284
|
+
setInputs((inputs) => {
|
285
|
+
return {
|
286
|
+
...inputs,
|
287
|
+
[serializedModule.path]: input,
|
288
|
+
} as Inputs;
|
289
|
+
});
|
290
|
+
});
|
291
|
+
}
|
292
|
+
}, [selectedSources.join(",")]);
|
293
|
+
return (
|
294
|
+
<ValContext.Provider
|
295
|
+
value={{
|
296
|
+
valApi,
|
297
|
+
valStore,
|
298
|
+
}}
|
299
|
+
>
|
300
|
+
{children}
|
301
|
+
{showEditButton && (
|
302
|
+
<root.div>
|
303
|
+
{/* TODO: */}
|
304
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
305
|
+
<link
|
306
|
+
rel="preconnect"
|
307
|
+
href="https://fonts.gstatic.com"
|
308
|
+
crossOrigin="anonymous"
|
309
|
+
/>
|
310
|
+
<link
|
311
|
+
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"
|
312
|
+
rel="stylesheet"
|
313
|
+
/>
|
314
|
+
<Style />
|
315
|
+
<div data-mode="dark">
|
316
|
+
<ValOverlay
|
317
|
+
editMode={editMode}
|
318
|
+
setEditMode={setEditMode}
|
319
|
+
closeValWindow={() => {
|
320
|
+
setEditFormPosition(null);
|
321
|
+
setSelectedSources([]);
|
322
|
+
setInputs({});
|
323
|
+
}}
|
324
|
+
valWindow={
|
325
|
+
(editFormPosition && {
|
326
|
+
position: editFormPosition,
|
327
|
+
inputs,
|
328
|
+
onSubmit: (inputs) => {
|
329
|
+
Promise.all(
|
330
|
+
Object.entries(inputs).map(([path, input]) => {
|
331
|
+
if (input.status === "completed") {
|
332
|
+
const [moduleId, modulePath] =
|
333
|
+
Internal.splitModuleIdAndModulePath(
|
334
|
+
path as SourcePath
|
335
|
+
);
|
336
|
+
if (input.type === "text") {
|
337
|
+
const patch: PatchJSON = [
|
338
|
+
{
|
339
|
+
value: input.data,
|
340
|
+
op: "replace",
|
341
|
+
path: `/${modulePath
|
342
|
+
.split(".")
|
343
|
+
.map((p) => JSON.parse(p))
|
344
|
+
.join("/")}`,
|
345
|
+
},
|
346
|
+
];
|
347
|
+
return valApi.patchModuleContent(moduleId, patch);
|
348
|
+
} else if (input.type === "image") {
|
349
|
+
const pathParts = modulePath
|
350
|
+
.split(".")
|
351
|
+
.map((p) => JSON.parse(p));
|
352
|
+
|
353
|
+
if (!input?.data || !("src" in input.data)) {
|
354
|
+
// 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
|
355
|
+
console.warn(
|
356
|
+
"No .src on input provided - this might mean no changes was made"
|
357
|
+
);
|
358
|
+
return;
|
359
|
+
}
|
360
|
+
const patch: PatchJSON = [
|
361
|
+
{
|
362
|
+
value: input.data.src,
|
363
|
+
op: "replace",
|
364
|
+
path: `/${pathParts.slice(0, -1).join("/")}/$${
|
365
|
+
pathParts[pathParts.length - 1]
|
366
|
+
}`,
|
367
|
+
},
|
368
|
+
];
|
369
|
+
if (input.data.metadata) {
|
370
|
+
if (input.data.addMetadata) {
|
371
|
+
patch.push({
|
372
|
+
value: input.data.metadata,
|
373
|
+
op: "add",
|
374
|
+
path: `/${pathParts.join("/")}/metadata`,
|
375
|
+
});
|
376
|
+
} else {
|
377
|
+
patch.push({
|
378
|
+
value: input.data.metadata,
|
379
|
+
op: "replace",
|
380
|
+
path: `/${pathParts.join("/")}/metadata`,
|
381
|
+
});
|
382
|
+
}
|
383
|
+
}
|
384
|
+
console.log("patch", patch);
|
385
|
+
return valApi.patchModuleContent(moduleId, patch);
|
386
|
+
} else if (input.type === "richtext") {
|
387
|
+
const patch: PatchJSON = [
|
388
|
+
{
|
389
|
+
value: input.data,
|
390
|
+
op: "replace",
|
391
|
+
path: `/${modulePath
|
392
|
+
.split(".")
|
393
|
+
.map((p) => JSON.parse(p))
|
394
|
+
.join("/")}`,
|
395
|
+
},
|
396
|
+
];
|
397
|
+
return valApi.patchModuleContent(moduleId, patch);
|
398
|
+
}
|
399
|
+
throw new Error(
|
400
|
+
`Unsupported input type: ${(input as any).type}`
|
401
|
+
);
|
402
|
+
} else {
|
403
|
+
console.error(
|
404
|
+
"Submitted incomplete input, ignoring..."
|
405
|
+
);
|
406
|
+
return Promise.resolve();
|
407
|
+
}
|
408
|
+
})
|
409
|
+
).then(() => {
|
410
|
+
setEditFormPosition(null);
|
411
|
+
setSelectedSources([]);
|
412
|
+
setInputs({});
|
413
|
+
});
|
414
|
+
},
|
415
|
+
}) ??
|
416
|
+
undefined
|
417
|
+
}
|
418
|
+
/>
|
419
|
+
</div>
|
420
|
+
</root.div>
|
421
|
+
)}
|
422
|
+
</ValContext.Provider>
|
423
|
+
);
|
424
|
+
}
|
@@ -0,0 +1,141 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
import {
|
3
|
+
HeadingNode,
|
4
|
+
ListItemNode,
|
5
|
+
ListNode,
|
6
|
+
ParagraphNode,
|
7
|
+
RichText,
|
8
|
+
TextNode,
|
9
|
+
Val,
|
10
|
+
} from "@valbuild/core";
|
11
|
+
import { Internal } from "@valbuild/core";
|
12
|
+
import { createElement } from "react";
|
13
|
+
import parse from "style-to-object";
|
14
|
+
|
15
|
+
const getValPath = Internal.getValPath;
|
16
|
+
export function ValRichText({ children }: { children: Val<RichText> }) {
|
17
|
+
return (
|
18
|
+
<div data-val-path={getValPath(children)}>
|
19
|
+
{children.children.map((child) => {
|
20
|
+
switch (child.type.val) {
|
21
|
+
case "heading":
|
22
|
+
return (
|
23
|
+
<HeadingNodeComponent
|
24
|
+
key={getValPath(child)}
|
25
|
+
node={child as Val<HeadingNode>}
|
26
|
+
/>
|
27
|
+
);
|
28
|
+
case "paragraph":
|
29
|
+
return (
|
30
|
+
<ParagraphNodeComponent
|
31
|
+
key={getValPath(child)}
|
32
|
+
node={child as Val<ParagraphNode>}
|
33
|
+
/>
|
34
|
+
);
|
35
|
+
case "list":
|
36
|
+
return (
|
37
|
+
<ListNodeComponent
|
38
|
+
key={getValPath(child)}
|
39
|
+
node={child as Val<ListNode>}
|
40
|
+
/>
|
41
|
+
);
|
42
|
+
default:
|
43
|
+
throw Error("Unknown node type: " + (child as any)?.type);
|
44
|
+
}
|
45
|
+
})}
|
46
|
+
</div>
|
47
|
+
);
|
48
|
+
}
|
49
|
+
|
50
|
+
function TextNodeComponent({ node }: { node: Val<TextNode> }) {
|
51
|
+
const actualVal = node.val;
|
52
|
+
const styleProps = actualVal.style ? parse(actualVal.style) ?? {} : {};
|
53
|
+
// TODO: Ugly! We should do this before serializing instead
|
54
|
+
if (styleProps["font-family"]) {
|
55
|
+
styleProps["fontFamily"] = styleProps["font-family"];
|
56
|
+
delete styleProps["font-family"];
|
57
|
+
}
|
58
|
+
if (styleProps["font-size"]) {
|
59
|
+
styleProps["fontSize"] = styleProps["font-size"];
|
60
|
+
delete styleProps["font-size"];
|
61
|
+
}
|
62
|
+
const bitmask = actualVal.format.toString(2);
|
63
|
+
const bitmaskOffset = bitmask.length - 1;
|
64
|
+
function isBitOne(bit: number) {
|
65
|
+
return (
|
66
|
+
bitmask.length >= bitmaskOffset - bit &&
|
67
|
+
bitmask[bitmaskOffset - bit] === "1"
|
68
|
+
);
|
69
|
+
}
|
70
|
+
if (isBitOne(0)) {
|
71
|
+
styleProps["fontWeight"] = "bold";
|
72
|
+
}
|
73
|
+
if (isBitOne(1)) {
|
74
|
+
styleProps["fontStyle"] = "italic";
|
75
|
+
}
|
76
|
+
if (isBitOne(2)) {
|
77
|
+
if (!styleProps["textDecoration"]) {
|
78
|
+
styleProps["textDecoration"] = "line-through";
|
79
|
+
} else {
|
80
|
+
styleProps["textDecoration"] += " line-through";
|
81
|
+
}
|
82
|
+
}
|
83
|
+
if (isBitOne(3)) {
|
84
|
+
if (!styleProps["textDecoration"]) {
|
85
|
+
styleProps["textDecoration"] = "underline";
|
86
|
+
} else {
|
87
|
+
styleProps["textDecoration"] += " underline";
|
88
|
+
}
|
89
|
+
}
|
90
|
+
return <span style={styleProps}>{actualVal.text}</span>;
|
91
|
+
}
|
92
|
+
|
93
|
+
function HeadingNodeComponent({ node }: { node: Val<HeadingNode> }) {
|
94
|
+
return createElement(
|
95
|
+
node.tag.val,
|
96
|
+
{},
|
97
|
+
node.children.map((child) => (
|
98
|
+
<TextNodeComponent key={getValPath(child)} node={child} />
|
99
|
+
))
|
100
|
+
);
|
101
|
+
}
|
102
|
+
|
103
|
+
function ParagraphNodeComponent({ node }: { node: Val<ParagraphNode> }) {
|
104
|
+
return (
|
105
|
+
<p>
|
106
|
+
{node.children.map((child) => {
|
107
|
+
switch (child.type.val) {
|
108
|
+
case "text":
|
109
|
+
return <TextNodeComponent key={getValPath(child)} node={child} />;
|
110
|
+
default:
|
111
|
+
throw Error("Unknown node type: " + (child as any)?.type);
|
112
|
+
}
|
113
|
+
})}
|
114
|
+
</p>
|
115
|
+
);
|
116
|
+
}
|
117
|
+
|
118
|
+
function ListNodeComponent({ node }: { node: Val<ListNode> }) {
|
119
|
+
return createElement(
|
120
|
+
node.val.tag,
|
121
|
+
{},
|
122
|
+
node.children.map((child) => (
|
123
|
+
<ListItemComponent key={getValPath(child)} node={child} />
|
124
|
+
))
|
125
|
+
);
|
126
|
+
}
|
127
|
+
|
128
|
+
function ListItemComponent({ node }: { node: Val<ListItemNode> }) {
|
129
|
+
return (
|
130
|
+
<li>
|
131
|
+
{node.children.map((child, i) => {
|
132
|
+
switch (child.val.type) {
|
133
|
+
case "text":
|
134
|
+
return <TextNodeComponent key={i} node={child} />;
|
135
|
+
default:
|
136
|
+
throw Error("Unknown node type: " + (child as any)?.type);
|
137
|
+
}
|
138
|
+
})}
|
139
|
+
</li>
|
140
|
+
);
|
141
|
+
}
|
package/src/ValStore.ts
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
import { ValModule, SelectorSource } from "@valbuild/core";
|
2
|
+
import { ValApi } from "./ValApi";
|
3
|
+
|
4
|
+
export class ValStore {
|
5
|
+
private readonly vals: Map<string, ValModule<SelectorSource>>;
|
6
|
+
private readonly listeners: { [moduleId: string]: (() => void)[] };
|
7
|
+
|
8
|
+
constructor(private readonly api: ValApi) {
|
9
|
+
this.vals = new Map();
|
10
|
+
this.listeners = {};
|
11
|
+
}
|
12
|
+
|
13
|
+
async updateAll() {
|
14
|
+
await Promise.all(
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
16
|
+
Object.keys(this.listeners).map(async (moduleId) => {
|
17
|
+
// this.set(
|
18
|
+
// moduleId,
|
19
|
+
// await this.api.getModule(moduleId)
|
20
|
+
// // ModuleContent.deserialize(await this.api.getModule(moduleId))
|
21
|
+
// );
|
22
|
+
})
|
23
|
+
);
|
24
|
+
}
|
25
|
+
|
26
|
+
subscribe = (moduleId: string) => (listener: () => void) => {
|
27
|
+
const listeners = (this.listeners[moduleId] =
|
28
|
+
moduleId in this.listeners ? this.listeners[moduleId] : []);
|
29
|
+
listeners.push(listener);
|
30
|
+
return () => {
|
31
|
+
listeners.splice(listeners.indexOf(listener), 1);
|
32
|
+
if (listeners.length === 0) {
|
33
|
+
delete this.listeners[moduleId];
|
34
|
+
}
|
35
|
+
};
|
36
|
+
};
|
37
|
+
|
38
|
+
set(moduleId: string, val: ValModule<SelectorSource>) {
|
39
|
+
this.vals.set(moduleId, val);
|
40
|
+
this.emitChange(moduleId);
|
41
|
+
}
|
42
|
+
|
43
|
+
get(moduleId: string) {
|
44
|
+
return this.vals.get(moduleId);
|
45
|
+
}
|
46
|
+
|
47
|
+
emitChange(moduleId: string) {
|
48
|
+
const listeners = this.listeners[moduleId];
|
49
|
+
if (typeof listeners === "undefined") return;
|
50
|
+
for (const listener of listeners) {
|
51
|
+
listener();
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
getSnapshot = (moduleId: string) => () => {
|
56
|
+
return this.vals.get(moduleId);
|
57
|
+
};
|
58
|
+
|
59
|
+
getServerSnapshot = (moduleId: string) => () => {
|
60
|
+
return this.vals.get(moduleId);
|
61
|
+
};
|
62
|
+
}
|
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,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";
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import { Internal } from "@valbuild/core";
|
2
|
+
import * as ReactJSXRuntime from "react/jsx-runtime";
|
3
|
+
export * from "react/jsx-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 jsx(type, props, key) {
|
35
|
+
// console.log("jsx", type, props, key);
|
36
|
+
|
37
|
+
devalProps(type, props);
|
38
|
+
|
39
|
+
return ReactJSXRuntime.jsx(type, props, key);
|
40
|
+
}
|
41
|
+
|
42
|
+
export function jsxs(type, props, key) {
|
43
|
+
// console.log("jsxs", type, props, key);
|
44
|
+
|
45
|
+
if (key === "key") {
|
46
|
+
console.log("jsxDEV", type, props, key, self);
|
47
|
+
}
|
48
|
+
|
49
|
+
devalProps(type, props);
|
50
|
+
|
51
|
+
return ReactJSXRuntime.jsxs(type, props, key);
|
52
|
+
}
|