@valbuild/react 0.26.0 → 0.27.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valbuild/react",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "private": false,
5
5
  "description": "Val - React internal helpers",
6
6
  "sideEffects": false,
@@ -9,8 +9,8 @@
9
9
  "test": "jest"
10
10
  },
11
11
  "dependencies": {
12
- "@valbuild/core": "~0.26.0",
13
- "@valbuild/ui": "~0.26.0",
12
+ "@valbuild/core": "~0.27.0",
13
+ "@valbuild/ui": "~0.27.0",
14
14
  "@vercel/stega": "^0.1.0",
15
15
  "base64-arraybuffer": "^1.0.2",
16
16
  "style-to-object": "^0.4.1"
@@ -88,5 +88,11 @@
88
88
  },
89
89
  "./package.json": "./package.json"
90
90
  },
91
- "types": "dist/valbuild-react.cjs.d.ts"
91
+ "types": "dist/valbuild-react.cjs.d.ts",
92
+ "files": [
93
+ "dist",
94
+ "jsx-runtime",
95
+ "jsx-dev-runtime",
96
+ "stega"
97
+ ]
92
98
  }
package/.babelrc.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "presets": [
3
- [
4
- "@babel/preset-react",
5
- {
6
- "runtime": "automatic"
7
- }
8
- ]
9
- ]
10
- }
package/CHANGELOG.md DELETED
File without changes
@@ -1,17 +0,0 @@
1
- import { TestEnvironment as JSDOMEnvironment } from "jest-environment-jsdom";
2
-
3
- /**
4
- * JSDOM currently does not support TextEncoder/TextDecoder...
5
- * @see https://github.com/jsdom/jsdom/issues/2524
6
- */
7
- class TestEnvironment extends JSDOMEnvironment {
8
- async setup() {
9
- await super.setup();
10
- if (typeof this.global.TextEncoder === "undefined") {
11
- this.global.TextEncoder = TextEncoder;
12
- this.global.TextDecoder = TextDecoder;
13
- }
14
- }
15
- }
16
-
17
- export default TestEnvironment;
package/jest.config.js DELETED
@@ -1,5 +0,0 @@
1
- /** @type {import("jest").Config} */
2
- module.exports = {
3
- preset: "../../jest.preset",
4
- testEnvironment: "./jest-environment.mjs",
5
- };
@@ -1,42 +0,0 @@
1
- import { useState, useLayoutEffect, useRef, CSSProperties } from "react";
2
- import { createPortal } from "react-dom";
3
-
4
- function ShadowContent({
5
- root,
6
- children,
7
- }: {
8
- children: React.ReactNode;
9
- root: Element | DocumentFragment;
10
- }) {
11
- return createPortal(children, root);
12
- }
13
-
14
- export const ShadowRoot = ({
15
- children,
16
- style,
17
- }: {
18
- children: React.ReactNode;
19
- style?: CSSProperties;
20
- }) => {
21
- const node = useRef<HTMLDivElement>(null);
22
- const [root, setRoot] = useState<ShadowRoot | null>(null);
23
-
24
- useLayoutEffect(() => {
25
- if (node.current) {
26
- if (node.current.shadowRoot) {
27
- setRoot(node.current.shadowRoot);
28
- } else {
29
- const root = node.current.attachShadow({
30
- mode: "open",
31
- });
32
- setRoot(root);
33
- }
34
- }
35
- }, []);
36
-
37
- return (
38
- <div ref={node} style={style} id="val-ui">
39
- {root && <ShadowContent root={root}>{children}</ShadowContent>}
40
- </div>
41
- );
42
- };
@@ -1,48 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import React, { lazy, useContext, useMemo } from "react";
3
- import { ValStore } from "./ValStore";
4
- import { ValApi } from "@valbuild/core";
5
-
6
- export function useValStore() {
7
- return useContext(ValContext).valStore;
8
- }
9
- export function useValApi() {
10
- return useContext(ValContext).valApi;
11
- }
12
-
13
- export type ValContext = {
14
- readonly valStore: ValStore;
15
- readonly valApi: ValApi;
16
- };
17
-
18
- export const ValContext = React.createContext<ValContext>({
19
- get valStore(): never {
20
- throw Error(
21
- "Val context not found. Ensure components are wrapped by ValProvider!"
22
- );
23
- },
24
- get valApi(): never {
25
- throw Error(
26
- "Val context not found. Ensure components are wrapped by ValProvider!"
27
- );
28
- },
29
- });
30
-
31
- export type ValProviderProps = {
32
- // host?: string;
33
- children?: React.ReactNode;
34
- };
35
- const ValUI =
36
- typeof window !== "undefined" ? lazy(() => import("./ValUI")) : null;
37
-
38
- export function ValProvider({ children }: ValProviderProps) {
39
- const host = "/api/val";
40
- const valApi = useMemo(() => new ValApi(host), [host]);
41
- const valStore = useMemo(() => new ValStore(valApi), [valApi]);
42
- return (
43
- <ValContext.Provider value={{ valApi, valStore }}>
44
- {children}
45
- {ValUI && <ValUI />}
46
- </ValContext.Provider>
47
- );
48
- }
@@ -1,198 +0,0 @@
1
- /* eslint-disable @typescript-eslint/ban-types */
2
- import {
3
- RichText,
4
- RichTextNode,
5
- AnyRichTextOptions,
6
- SourcePath,
7
- RichTextOptions,
8
- } from "@valbuild/core";
9
- import React from "react";
10
-
11
- // Pick is used to make sure we do not add a tag or class that is not in options:
12
- type Tags = keyof Pick<RichTextOptions, "img" | "ul" | "ol">;
13
- type Classes = keyof Pick<RichTextOptions, "bold" | "italic" | "lineThrough">;
14
-
15
- type ThemeOptions<O extends RichTextOptions> = {
16
- tags: (O["headings"] extends Array<unknown>
17
- ? {
18
- [Key in O["headings"][number]]: string;
19
- }
20
- : {}) & {
21
- [Key in Tags & keyof O as O[Key] extends true
22
- ? Key extends "ul" | "ol"
23
- ? Key | "li"
24
- : Key
25
- : never]: string;
26
- } & { p?: string };
27
- classes: {
28
- [Key in Classes & keyof O as O[Key] extends true ? Key : never]: string;
29
- };
30
- };
31
-
32
- export function ValRichText<O extends RichTextOptions>({
33
- theme,
34
- children,
35
- }: {
36
- theme: ThemeOptions<O>;
37
- children: RichText<O>;
38
- }) {
39
- const root = children as RichText<AnyRichTextOptions> & {
40
- valPath: SourcePath;
41
- };
42
- function withRenderTag(
43
- clazz: keyof ThemeOptions<AnyRichTextOptions>["tags"],
44
- current?: string
45
- ) {
46
- const renderClass = (theme as ThemeOptions<AnyRichTextOptions>).tags[clazz];
47
- if (renderClass && current) {
48
- return [current, renderClass].join(" ");
49
- }
50
- if (renderClass) {
51
- return renderClass;
52
- }
53
- return current;
54
- }
55
- function withRenderClass(
56
- clazz: keyof ThemeOptions<AnyRichTextOptions>["classes"],
57
- current?: string
58
- ) {
59
- const renderClass = (theme as ThemeOptions<AnyRichTextOptions>).classes[
60
- clazz
61
- ];
62
- if (renderClass && current) {
63
- return [current, renderClass].join(" ");
64
- }
65
- if (renderClass) {
66
- return renderClass;
67
- }
68
- return current;
69
- }
70
-
71
- function toReact(
72
- node: RichTextNode<AnyRichTextOptions>,
73
- key: number | string
74
- ): React.ReactNode {
75
- if (typeof node === "string") {
76
- return node;
77
- }
78
- if (node.tag === "p") {
79
- return (
80
- <p className={withRenderTag("p")} key={key}>
81
- {node.children.map(toReact)}
82
- </p>
83
- );
84
- }
85
- if (node.tag === "img") {
86
- return <img className={withRenderTag("img")} key={key} src={node.src} />;
87
- }
88
- if (node.tag === "ul") {
89
- return (
90
- <ul className={withRenderTag("ul")} key={key}>
91
- {node.children.map(toReact)}
92
- </ul>
93
- );
94
- }
95
- if (node.tag === "ol") {
96
- return (
97
- <ol className={withRenderTag("ol")} key={key}>
98
- {node.children.map(toReact)}
99
- </ol>
100
- );
101
- }
102
- if (node.tag === "li") {
103
- return (
104
- <li className={withRenderTag("li")} key={key}>
105
- {node.children.map(toReact)}
106
- </li>
107
- );
108
- }
109
- if (node.tag === "span") {
110
- return (
111
- <span
112
- key={key}
113
- className={node.classes
114
- .map((nodeClass) => {
115
- switch (nodeClass) {
116
- case "bold":
117
- return withRenderClass("bold");
118
- case "line-through":
119
- return withRenderClass("lineThrough");
120
- case "italic":
121
- return withRenderClass("italic");
122
- }
123
- })
124
- .join(" ")}
125
- >
126
- {node.children.map(toReact)}
127
- </span>
128
- );
129
- }
130
- if (node.tag === "h1") {
131
- return (
132
- <h1 className={withRenderTag("h1")} key={key}>
133
- {node.children.map(toReact)}
134
- </h1>
135
- );
136
- }
137
- if (node.tag === "h2") {
138
- return (
139
- <h2 className={withRenderTag("h2")} key={key}>
140
- {node.children.map(toReact)}
141
- </h2>
142
- );
143
- }
144
- if (node.tag === "h3") {
145
- return (
146
- <h3 className={withRenderTag("h3")} key={key}>
147
- {node.children.map(toReact)}
148
- </h3>
149
- );
150
- }
151
- if (node.tag === "h4") {
152
- return (
153
- <h4 className={withRenderTag("h4")} key={key}>
154
- {node.children.map(toReact)}
155
- </h4>
156
- );
157
- }
158
- if (node.tag === "h5") {
159
- return (
160
- <h5 className={withRenderTag("h5")} key={key}>
161
- {node.children.map(toReact)}
162
- </h5>
163
- );
164
- }
165
- if (node.tag === "h6") {
166
- return (
167
- <h6 className={withRenderTag("h6")} key={key}>
168
- {node.children.map(toReact)}
169
- </h6>
170
- );
171
- }
172
-
173
- if (node.tag === "br") {
174
- return <br key={key} />;
175
- }
176
- if (node.tag === "a") {
177
- return (
178
- <a href={node.href} key={key}>
179
- {node.children.map(toReact)}
180
- </a>
181
- );
182
- }
183
- console.error("Unknown tag", node.tag);
184
- const _exhaustiveCheck: never = node.tag;
185
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
- const anyNode = _exhaustiveCheck as any;
187
- if (!anyNode?.tag) {
188
- return null;
189
- }
190
- return React.createElement(anyNode.tag, {
191
- key,
192
- className: anyNode.class?.join(" "),
193
- children: anyNode.children?.map(toReact),
194
- });
195
- }
196
-
197
- return <div data-val-path={root.valPath}>{root.children.map(toReact)}</div>;
198
- }
package/src/ValStore.ts DELETED
@@ -1,110 +0,0 @@
1
- import { Json, ModuleId, ValApi } from "@valbuild/core";
2
- import { result } from "@valbuild/core/fp";
3
- import { IValStore } from "@valbuild/ui";
4
-
5
- type SubscriberId = string & {
6
- readonly _tag: unique symbol;
7
- };
8
-
9
- export class ValStore implements IValStore {
10
- private readonly subscribers: Map<SubscriberId, Record<ModuleId, Json>>; // uncertain whether this is the optimal way of returning
11
- private readonly listeners: Record<SubscriberId, (() => void)[]>;
12
-
13
- constructor(private readonly api: ValApi) {
14
- this.subscribers = new Map();
15
- this.listeners = {};
16
- }
17
-
18
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
19
- async update(moduleIds: ModuleId[]) {
20
- // TODO: update only the modules that have changed
21
- return this.updateAll();
22
- }
23
-
24
- async updateAll() {
25
- const data = await this.api.getModules({
26
- patch: true,
27
- includeSource: true,
28
- });
29
- if (result.isOk(data)) {
30
- const updatedSubscriberIds = new Map<SubscriberId, ModuleId[]>();
31
- const subscriberIds = Array.from(this.subscribers.keys());
32
-
33
- // Figure out which modules have been updated and map to updated subscribed id
34
- for (const moduleId of Object.keys(data.value.modules) as ModuleId[]) {
35
- const source = data.value.modules[moduleId].source;
36
- if (typeof source !== "undefined") {
37
- const updatedSubscriberId = subscriberIds.find(
38
- (subscriberId) => subscriberId.includes(moduleId) // NOTE: dependent on
39
- );
40
- if (updatedSubscriberId) {
41
- updatedSubscriberIds.set(
42
- updatedSubscriberId,
43
- (updatedSubscriberIds.get(updatedSubscriberId) || []).concat(
44
- moduleId
45
- )
46
- );
47
- }
48
- }
49
- }
50
-
51
- // For all updated subscribers: set new module data and emit change
52
- for (const [updatedSubscriberId, moduleIds] of Array.from(
53
- updatedSubscriberIds.entries()
54
- )) {
55
- const subscriberModules = Object.fromEntries(
56
- moduleIds.flatMap((moduleId) => {
57
- const source = data.value.modules[moduleId].source;
58
- if (!source) {
59
- return [];
60
- }
61
- return [[moduleId, source]];
62
- })
63
- );
64
- this.subscribers.set(updatedSubscriberId, subscriberModules);
65
- this.emitChange(updatedSubscriberId);
66
- }
67
- } else {
68
- console.error("Val: failed to update modules", data.error.message);
69
- }
70
- }
71
-
72
- subscribe = (moduleIds: ModuleId[]) => (listener: () => void) => {
73
- const subscriberId = createSubscriberId(moduleIds);
74
- if (!this.listeners[subscriberId]) {
75
- this.listeners[subscriberId] = [];
76
- this.subscribers.set(subscriberId, {});
77
- }
78
- this.listeners[subscriberId].push(listener);
79
-
80
- return () => {
81
- this.listeners[subscriberId].splice(
82
- this.listeners[subscriberId].indexOf(listener),
83
- 1
84
- );
85
- };
86
- };
87
-
88
- private emitChange(subscriberId: SubscriberId) {
89
- for (const listener of this.listeners[subscriberId]) {
90
- listener();
91
- }
92
- }
93
-
94
- getSnapshot = (moduleIds: ModuleId[]) => () => {
95
- return this.get(moduleIds);
96
- };
97
-
98
- getServerSnapshot = (moduleIds: ModuleId[]) => () => {
99
- return this.get(moduleIds);
100
- };
101
-
102
- get = (moduleIds: ModuleId[]): Record<ModuleId, Json> | undefined => {
103
- const subscriberId = createSubscriberId(moduleIds);
104
- return this.subscribers.get(subscriberId);
105
- };
106
- }
107
-
108
- function createSubscriberId(moduleIds: ModuleId[]): SubscriberId {
109
- return moduleIds.slice().sort().join("&") as SubscriberId;
110
- }
package/src/ValUI.tsx DELETED
@@ -1,78 +0,0 @@
1
- "use client";
2
-
3
- /* eslint-disable @typescript-eslint/no-explicit-any */
4
- import { Internal } from "@valbuild/core";
5
- import { Style, ValOverlay } from "@valbuild/ui";
6
- import { useEffect, useState } from "react";
7
- import { ShadowRoot } from "./ShadowRoot";
8
- import { useValApi, useValStore } from "./ValProvider";
9
-
10
- export default function ValUI() {
11
- const [isClient, setIsClient] = useState(false);
12
- const [enabled, setEnabled] = useState(false);
13
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
14
- const [isDraftMode, setDraftMode] = useState(false); // TODO: if enabled, but not in draft mode: show something
15
- const api = useValApi();
16
- const store = useValStore();
17
- useEffect(() => {
18
- setIsClient(true);
19
- try {
20
- const valEnabled = document.cookie?.includes(
21
- `${Internal.VAL_ENABLE_COOKIE_NAME}=true`
22
- );
23
- setEnabled(valEnabled);
24
- } catch (e) {
25
- console.warn("Could not read Val enabled state", e);
26
- }
27
- try {
28
- const valDraftMode = document.cookie?.includes(
29
- `${Internal.VAL_DRAFT_MODE_COOKIE}=true`
30
- );
31
- setDraftMode(valDraftMode);
32
- } catch (e) {
33
- console.warn("Could not read Val draft mode", e);
34
- }
35
- }, []);
36
- if (isClient && !enabled && process.env.NODE_ENV === "development") {
37
- console.log(
38
- `Val is disabled. Enable it by going here ${window.origin}${
39
- api.host
40
- }/enable?redirect_to=${encodeURIComponent(
41
- window.location.href
42
- )}. NOTE: this message appears because NODE_ENV is set to development.`
43
- );
44
- }
45
- if (!isClient || !enabled) {
46
- return null;
47
- }
48
- return (
49
- <>
50
- <ShadowRoot
51
- style={{
52
- position: "absolute",
53
- top: 0,
54
- left: 0,
55
- zIndex: 8999, // 1 less than the NextJS error z-index: 9000
56
- }}
57
- >
58
- {/* TODO: */}
59
- <link rel="preconnect" href="https://fonts.googleapis.com" />
60
- <link
61
- rel="preconnect"
62
- href="https://fonts.gstatic.com"
63
- crossOrigin="anonymous"
64
- />
65
- <link
66
- href="https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap"
67
- rel="stylesheet"
68
- />
69
- <link
70
- href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;1,100;1,300;1,400;1,500;1,700&family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap"
71
- rel="stylesheet"
72
- />
73
- <Style />
74
- <ValOverlay api={api} store={store} />
75
- </ShadowRoot>
76
- </>
77
- );
78
- }
package/src/assets.ts DELETED
@@ -1,124 +0,0 @@
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
- );
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- // NOTE: the exports of this file needs to be kept in sync with ValQuickJSRuntime
2
- export { ValProvider, useValStore, useValApi } from "./ValProvider";
3
- export { ValRichText } from "./ValRichText";
@@ -1,47 +0,0 @@
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
- }
@@ -1,46 +0,0 @@
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
- }
@@ -1 +0,0 @@
1
- export { ValJSX as JSX } from "./jsx-namespace";
@@ -1 +0,0 @@
1
- export { ValJSX as JSX } from "./jsx-namespace";
@@ -1,52 +0,0 @@
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
- }
@@ -1,81 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import jsxRuntime from "react/jsx-runtime";
3
- import jsxRuntimeDev from "react/jsx-dev-runtime";
4
- import React from "react";
5
- import {
6
- vercelStegaSplit,
7
- vercelStegaDecode,
8
- VERCEL_STEGA_REGEX,
9
- } from "@vercel/stega";
10
-
11
- const isIntrinsicElement = (type: any) => {
12
- // TODO: think this is not correct, but good enough for now?
13
- return typeof type === "string";
14
- };
15
-
16
- const addValPathIfFound = (type: any, props: any) => {
17
- const valSources: any = [];
18
- if (props && typeof props === "object") {
19
- for (const [key, value] of Object.entries(props)) {
20
- if (typeof value === "string" && value.match(VERCEL_STEGA_REGEX)) {
21
- const encodedBits = vercelStegaDecode(value);
22
- if (!encodedBits || typeof encodedBits !== "object") continue;
23
- if (
24
- "origin" in encodedBits &&
25
- "data" in encodedBits &&
26
- typeof encodedBits.data === "object" &&
27
- encodedBits.data &&
28
- "valPath" in encodedBits.data
29
- ) {
30
- const valPath = encodedBits?.data?.valPath;
31
- if (valPath) {
32
- valSources.push(valPath);
33
- props[key] = isIntrinsicElement(type)
34
- ? vercelStegaSplit(value).cleaned
35
- : value;
36
- props[`data-val-attr-${key}`] = valPath;
37
- }
38
- }
39
- }
40
- }
41
-
42
- if (valSources.length > 0) {
43
- props["data-val-path"] = valSources.join(",");
44
- }
45
- }
46
- };
47
-
48
- function WrapJsx<T>(jsx: T): T {
49
- if (typeof jsx !== "function") return jsx;
50
-
51
- return function (type: any, props: any, ...rest: any[]) {
52
- addValPathIfFound(type, props);
53
- return jsx.call(jsx, type, props, ...rest);
54
- } as any as T;
55
- }
56
-
57
- interface JsxRuntimeModule {
58
- jsx?(type: any, ...rest: any[]): unknown;
59
- jsxs?(type: any, ...rest: any[]): unknown;
60
- jsxDEV?(type: any, ...rest: any[]): unknown;
61
- }
62
-
63
- export function autoTagJSX() {
64
- const JsxPro: JsxRuntimeModule = jsxRuntime;
65
- const JsxDev: JsxRuntimeModule = jsxRuntimeDev;
66
-
67
- /**
68
- * createElement _may_ be called by jsx runtime as a fallback in certain cases,
69
- * so we need to wrap it regardless.
70
- *
71
- * The jsx exports depend on the `NODE_ENV` var to ensure the users' bundler doesn't
72
- * include both, so one of them will be set with `undefined` values.
73
- */
74
- React.createElement = WrapJsx(React.createElement);
75
- JsxDev.jsx && /* */ (JsxDev.jsx = WrapJsx(JsxDev.jsx));
76
- JsxPro.jsx && /* */ (JsxPro.jsx = WrapJsx(JsxPro.jsx));
77
- JsxDev.jsxs && /* */ (JsxDev.jsxs = WrapJsx(JsxDev.jsxs));
78
- JsxPro.jsxs && /* */ (JsxPro.jsxs = WrapJsx(JsxPro.jsxs));
79
- JsxDev.jsxDEV && /**/ (JsxDev.jsxDEV = WrapJsx(JsxDev.jsxDEV));
80
- JsxPro.jsxDEV && /**/ (JsxPro.jsxDEV = WrapJsx(JsxPro.jsxDEV));
81
- }
@@ -1,8 +0,0 @@
1
- // NOTE: the exports of this file needs to be kept in sync with ValQuickJSRuntime
2
- export { autoTagJSX } from "./autoTagJSX";
3
- export {
4
- stegaEncode,
5
- getModuleIds,
6
- type ValEncodedString,
7
- type StegaOfSource,
8
- } from "./stegaEncode";
@@ -1,154 +0,0 @@
1
- import { getModuleIds, stegaEncode } from "./stegaEncode";
2
- import { initVal } from "@valbuild/core";
3
- import { vercelStegaDecode, vercelStegaSplit } from "@vercel/stega";
4
-
5
- const { s, val } = initVal();
6
-
7
- describe("stega transform", () => {
8
- test("basic", () => {
9
- const schema = s.array(
10
- s.object({
11
- image: s.image(),
12
- text: s.richtext({}),
13
- n: s.number(),
14
- b: s.boolean(),
15
- })
16
- );
17
-
18
- const valModule = val.content("/test", schema, [
19
- {
20
- image: val.file("/public/test1.png", {
21
- sha256: "1231",
22
- width: 100,
23
- height: 100,
24
- }),
25
- text: val.richtext`Test`,
26
- n: 1,
27
- b: true,
28
- },
29
- {
30
- image: val.file("/public/test2.png", {
31
- sha256: "1232",
32
- width: 100,
33
- height: 100,
34
- }),
35
- text: val.richtext`Test`,
36
- n: 2,
37
- b: false,
38
- },
39
- ]);
40
- const transformed = stegaEncode(valModule, {});
41
-
42
- expect(transformed).toHaveLength(2);
43
-
44
- expect(vercelStegaDecode(transformed[0].image.url)).toStrictEqual({
45
- data: {
46
- valPath: '/test.0."image"',
47
- },
48
- origin: "val.build",
49
- });
50
- expect(vercelStegaDecode(transformed[1].image.url)).toStrictEqual({
51
- data: {
52
- valPath: '/test.1."image"',
53
- },
54
- origin: "val.build",
55
- });
56
- //
57
- expect(vercelStegaSplit(transformed[0].image.url).cleaned).toStrictEqual(
58
- "/test1.png?sha256=1231"
59
- );
60
- expect(vercelStegaSplit(transformed[1].image.url).cleaned).toStrictEqual(
61
- "/test2.png?sha256=1232"
62
- );
63
-
64
- expect(transformed[0].text.valPath).toStrictEqual('/test.0."text"');
65
- expect(transformed[1].text.valPath).toStrictEqual('/test.1."text"');
66
- });
67
-
68
- test("get modules", () => {
69
- const schema = s.array(s.string());
70
-
71
- expect(
72
- getModuleIds({
73
- foo: [
74
- { test: val.content("/test1", schema, ["one", "two"]) },
75
- { test: val.content("/test2", schema, ["one", "two"]) },
76
- ],
77
- test: val.content("/test3", schema, ["one", "two"]),
78
- })
79
- ).toStrictEqual(["/test1", "/test2", "/test3"]);
80
- });
81
-
82
- test("basic transform with get modules", () => {
83
- const schema = s.array(s.string());
84
- const transformed = stegaEncode(
85
- val.content("/test1", schema, ["one", "two"]),
86
- {
87
- getModule: (moduleId) => {
88
- if (moduleId === "/test1") {
89
- return ["1", "2"];
90
- }
91
- },
92
- }
93
- );
94
-
95
- expect(vercelStegaSplit(transformed[0]).cleaned).toStrictEqual("1");
96
- expect(vercelStegaDecode(transformed[0])).toStrictEqual({
97
- data: {
98
- valPath: "/test1.0",
99
- },
100
- origin: "val.build",
101
- });
102
- });
103
-
104
- test("Dont stegaEncode raw strings schema", () => {
105
- const schema = s.object({ str: s.string(), rawStr: s.string().raw() });
106
- const transformed = stegaEncode(
107
- val.content("/test1", schema, { str: "one", rawStr: "two" }),
108
- {}
109
- );
110
- //expect(transformed.str).toStrictEqual("one");
111
- expect(transformed.rawStr).toStrictEqual("two");
112
- });
113
- test("transform with get modules", () => {
114
- const schema = s.array(s.string());
115
- const transformed = stegaEncode(
116
- {
117
- foo: [
118
- { test: val.content("/test1", schema, ["one", "two"]) },
119
- { test: val.content("/test2", schema, ["one", "two"]) },
120
- ],
121
- test: val.content("/test3", schema, ["one", "two"]),
122
- },
123
- {
124
- getModule: (moduleId) => {
125
- if (moduleId === "/test2") {
126
- return ["1", "2"];
127
- }
128
- },
129
- }
130
- );
131
-
132
- expect(vercelStegaSplit(transformed.foo[0].test[0]).cleaned).toStrictEqual(
133
- "one"
134
- );
135
- expect(vercelStegaDecode(transformed.foo[0].test[0])).toStrictEqual({
136
- data: {
137
- valPath: "/test1.0",
138
- },
139
- origin: "val.build",
140
- });
141
-
142
- //
143
-
144
- expect(vercelStegaSplit(transformed.foo[1].test[0]).cleaned).toStrictEqual(
145
- "1"
146
- );
147
- expect(vercelStegaDecode(transformed.foo[1].test[0])).toStrictEqual({
148
- data: {
149
- valPath: "/test2.0",
150
- },
151
- origin: "val.build",
152
- });
153
- });
154
- });
@@ -1,247 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import {
3
- Json,
4
- Internal,
5
- RichTextSource,
6
- RichText,
7
- VAL_EXTENSION,
8
- FILE_REF_PROP,
9
- } from "@valbuild/core";
10
- import { vercelStegaCombine } from "@vercel/stega";
11
- import { FileSource, Source, SourceObject } from "@valbuild/core";
12
- import { JsonPrimitive } from "@valbuild/core";
13
- import { SourceArray } from "@valbuild/core";
14
- import { parseRichTextSource } from "@valbuild/ui";
15
- import { RawString } from "@valbuild/core/src/schema/string";
16
-
17
- declare const brand: unique symbol;
18
-
19
- /**
20
- * ValEncodedString is a string that is encoded using steganography.
21
- *
22
- * This means that there is a hidden / non-visible object embedded in the string.
23
- * This object includes a path, which is used to automatically tag
24
- * where the content comes from for contextual editing.
25
- *
26
- */
27
- export type ValEncodedString = string & {
28
- [brand]: "ValEncodedString";
29
- };
30
-
31
- export type StegaOfSource<T extends Source> = Json extends T
32
- ? Json
33
- : T extends RichTextSource<infer O>
34
- ? RichText<O>
35
- : T extends FileSource
36
- ? { url: ValEncodedString }
37
- : T extends SourceObject
38
- ? {
39
- [key in keyof T]: StegaOfSource<T[key]>;
40
- }
41
- : T extends SourceArray
42
- ? StegaOfSource<T[number]>[]
43
- : T extends RawString
44
- ? string
45
- : T extends string
46
- ? ValEncodedString
47
- : T extends JsonPrimitive
48
- ? T
49
- : never;
50
-
51
- export function stegaEncode(
52
- input: any,
53
- opts: {
54
- getModule?: (moduleId: string) => any;
55
- disabled?: boolean;
56
- }
57
- ): any {
58
- function rec(
59
- sourceOrSelector: any,
60
- recOpts?: { path: any; schema: any }
61
- ): any {
62
- if (typeof sourceOrSelector === "object") {
63
- if (!sourceOrSelector) {
64
- return null;
65
- }
66
- const selectorPath = Internal.getValPath(sourceOrSelector);
67
- if (selectorPath) {
68
- const newSchema = Internal.getSchema(sourceOrSelector);
69
- return rec(
70
- (opts.getModule && opts.getModule(selectorPath)) ||
71
- Internal.getSource(sourceOrSelector),
72
- opts.disabled ? undefined : { path: selectorPath, schema: newSchema }
73
- );
74
- }
75
-
76
- if (VAL_EXTENSION in sourceOrSelector) {
77
- if (sourceOrSelector[VAL_EXTENSION] === "richtext") {
78
- if (recOpts?.path) {
79
- return {
80
- ...parseRichTextSource(sourceOrSelector),
81
- valPath: recOpts.path,
82
- };
83
- }
84
-
85
- return parseRichTextSource(sourceOrSelector);
86
- }
87
-
88
- if (
89
- sourceOrSelector[VAL_EXTENSION] === "file" &&
90
- typeof sourceOrSelector[FILE_REF_PROP] === "string"
91
- ) {
92
- const fileSelector = Internal.convertFileSource(sourceOrSelector);
93
- return {
94
- ...fileSelector,
95
- url: rec(fileSelector.url, recOpts),
96
- };
97
- }
98
- console.error(
99
- `Encountered unexpected extension: ${sourceOrSelector[VAL_EXTENSION]}`
100
- );
101
- return sourceOrSelector;
102
- }
103
-
104
- if (Array.isArray(sourceOrSelector)) {
105
- return sourceOrSelector.map((el, i) =>
106
- rec(
107
- el,
108
- recOpts && {
109
- path: Internal.createValPathOfItem(recOpts.path, i),
110
- schema: recOpts.schema.item,
111
- }
112
- )
113
- );
114
- }
115
-
116
- if (!Array.isArray(sourceOrSelector)) {
117
- const res: Record<string, any> = {};
118
- for (const [key, value] of Object.entries(sourceOrSelector)) {
119
- res[key] = rec(
120
- value,
121
- recOpts && {
122
- path: Internal.createValPathOfItem(recOpts.path, key),
123
- schema:
124
- recOpts.schema.item || // Record
125
- recOpts.schema.items[key], // Object
126
- }
127
- );
128
- }
129
- return res;
130
- }
131
-
132
- console.error(
133
- `Could not transform source selector: ${typeof sourceOrSelector} (array: ${Array.isArray(
134
- sourceOrSelector
135
- )})`,
136
- sourceOrSelector
137
- );
138
- return sourceOrSelector;
139
- }
140
-
141
- if (typeof sourceOrSelector === "string") {
142
- if (!recOpts) {
143
- return sourceOrSelector;
144
- }
145
- if (recOpts.schema.isRaw) {
146
- return sourceOrSelector;
147
- }
148
- return vercelStegaCombine(
149
- sourceOrSelector,
150
- {
151
- origin: "val.build",
152
- data: { valPath: recOpts.path },
153
- },
154
- false // auto detection on urls and dates is disabled, isDate could be used but it is also disabled (users should use a date schema instead): isDate(sourceOrSelector) // skip = true if isDate
155
- );
156
- }
157
-
158
- if (
159
- typeof sourceOrSelector === "number" ||
160
- typeof sourceOrSelector === "boolean"
161
- ) {
162
- return sourceOrSelector;
163
- }
164
-
165
- console.error(
166
- `Unexpected type of source selector: ${typeof sourceOrSelector}`
167
- );
168
- return sourceOrSelector;
169
- }
170
- return rec(input);
171
- }
172
-
173
- export function getModuleIds(input: any): string[] {
174
- const modules: Set<string> = new Set();
175
- function rec(sourceOrSelector: any): undefined {
176
- if (typeof sourceOrSelector === "object") {
177
- if (!sourceOrSelector) {
178
- return;
179
- }
180
- const selectorPath = Internal.getValPath(sourceOrSelector);
181
- if (selectorPath) {
182
- modules.add(selectorPath);
183
- return;
184
- }
185
-
186
- if (VAL_EXTENSION in sourceOrSelector) {
187
- if (sourceOrSelector[VAL_EXTENSION] === "richtext") {
188
- return;
189
- }
190
-
191
- if (
192
- sourceOrSelector[VAL_EXTENSION] === "file" &&
193
- typeof sourceOrSelector[FILE_REF_PROP] === "string"
194
- ) {
195
- return;
196
- }
197
- console.error(
198
- `Encountered unexpected extension: ${sourceOrSelector[VAL_EXTENSION]}`
199
- );
200
- return sourceOrSelector;
201
- }
202
-
203
- if (Array.isArray(sourceOrSelector)) {
204
- sourceOrSelector.forEach(rec);
205
- return;
206
- }
207
-
208
- if (!Array.isArray(sourceOrSelector)) {
209
- for (const [, value] of Object.entries(sourceOrSelector)) {
210
- rec(value);
211
- }
212
- return;
213
- }
214
-
215
- console.error(
216
- `Could not transform source selector: ${typeof sourceOrSelector} (array: ${Array.isArray(
217
- sourceOrSelector
218
- )})`,
219
- sourceOrSelector
220
- );
221
- return;
222
- }
223
-
224
- if (typeof sourceOrSelector === "string") {
225
- return;
226
- }
227
-
228
- if (
229
- typeof sourceOrSelector === "number" ||
230
- typeof sourceOrSelector === "boolean"
231
- ) {
232
- return;
233
- }
234
-
235
- console.error(
236
- `Unexpected type of source selector: ${typeof sourceOrSelector}`
237
- );
238
- return;
239
- }
240
- rec(input);
241
- return Array.from(modules);
242
- }
243
-
244
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
245
- function isDate(s: string) {
246
- return Boolean(Date.parse(s));
247
- }
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "esModuleInterop": true,
4
- "strict": true,
5
- "allowJs": true,
6
- "isolatedModules": true,
7
- "jsx": "react-jsx",
8
- "jsxImportSource": "@valbuild/react",
9
- "lib": ["es6", "dom"],
10
- "module": "CommonJS",
11
- "noEmit": true,
12
- "target": "ES5",
13
- "outDir": "dist",
14
- "rootDir": "src",
15
- "skipLibCheck": true
16
- },
17
- "include": ["src/**/*"],
18
- "exclude": ["dist/**", "jsx-runtime/dist/**", "jsx-dev-runtime/dist/**"]
19
- }