@webstudio-is/react-sdk 0.56.0 → 0.58.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/lib/app/custom-components/image.js +6 -6
- package/lib/app/custom-components/shared/remix-link.js +7 -3
- package/lib/app/params.js +4 -2
- package/lib/cjs/app/custom-components/image.js +6 -6
- package/lib/cjs/app/custom-components/shared/remix-link.js +7 -3
- package/lib/cjs/app/params.js +4 -2
- package/lib/cjs/components/link.js +17 -8
- package/lib/cjs/css/css.js +8 -5
- package/lib/cjs/css/global-rules.js +3 -3
- package/lib/cjs/css/style-rules.js +10 -3
- package/lib/cjs/props.js +35 -12
- package/lib/cjs/pubsub/raf-queue.js +3 -6
- package/lib/cjs/tree/root.js +1 -1
- package/lib/components/link.js +17 -8
- package/lib/css/css.js +8 -5
- package/lib/css/global-rules.js +4 -7
- package/lib/css/style-rules.js +10 -3
- package/lib/props.js +35 -12
- package/lib/pubsub/raf-queue.js +3 -6
- package/lib/tree/root.js +1 -1
- package/lib/types/app/custom-components/image.d.ts +2 -2
- package/lib/types/app/custom-components/index.d.ts +8 -8
- package/lib/types/app/custom-components/link-block.d.ts +2 -2
- package/lib/types/app/custom-components/link.d.ts +2 -2
- package/lib/types/app/custom-components/rich-text-link.d.ts +2 -2
- package/lib/types/app/custom-components/shared/remix-link.d.ts +3 -3
- package/lib/types/app/params.d.ts +2 -2
- package/lib/types/components/blockquote.d.ts +1 -1
- package/lib/types/components/blockquote.stories.d.ts +2 -2
- package/lib/types/components/body.d.ts +1 -1
- package/lib/types/components/body.stories.d.ts +1 -1
- package/lib/types/components/bold.d.ts +1 -1
- package/lib/types/components/bold.stories.d.ts +2 -2
- package/lib/types/components/box.d.ts +1 -1
- package/lib/types/components/box.stories.d.ts +4 -4
- package/lib/types/components/button.d.ts +1 -1
- package/lib/types/components/button.stories.d.ts +4 -4
- package/lib/types/components/code.d.ts +1 -1
- package/lib/types/components/code.stories.d.ts +4 -4
- package/lib/types/components/components-utils.d.ts +7 -7
- package/lib/types/components/form.d.ts +1 -1
- package/lib/types/components/form.stories.d.ts +2 -2
- package/lib/types/components/heading.d.ts +1 -1
- package/lib/types/components/heading.stories.d.ts +4 -4
- package/lib/types/components/image.d.ts +1 -1
- package/lib/types/components/image.stories.d.ts +2 -2
- package/lib/types/components/input.d.ts +1 -1
- package/lib/types/components/input.stories.d.ts +2 -2
- package/lib/types/components/italic.d.ts +1 -1
- package/lib/types/components/italic.stories.d.ts +2 -2
- package/lib/types/components/link-block.stories.d.ts +4 -4
- package/lib/types/components/link.d.ts +1 -1
- package/lib/types/components/link.stories.d.ts +4 -4
- package/lib/types/components/list-item.d.ts +1 -1
- package/lib/types/components/list-item.stories.d.ts +2 -2
- package/lib/types/components/list.d.ts +1 -1
- package/lib/types/components/list.stories.d.ts +4 -4
- package/lib/types/components/paragraph.d.ts +1 -1
- package/lib/types/components/paragraph.stories.d.ts +2 -2
- package/lib/types/components/rich-text-link.stories.d.ts +4 -4
- package/lib/types/components/separator.d.ts +1 -1
- package/lib/types/components/separator.stories.d.ts +2 -2
- package/lib/types/components/span.d.ts +1 -1
- package/lib/types/components/span.stories.d.ts +2 -2
- package/lib/types/components/subscript.d.ts +1 -1
- package/lib/types/components/subscript.stories.d.ts +2 -2
- package/lib/types/components/superscript.d.ts +1 -1
- package/lib/types/components/superscript.stories.d.ts +2 -2
- package/lib/types/components/text-block.d.ts +1 -1
- package/lib/types/components/text-block.stories.d.ts +2 -2
- package/lib/types/css/css.d.ts +1 -3
- package/lib/types/css/global-rules.d.ts +2 -3
- package/lib/types/css/style-rules.d.ts +1 -0
- package/lib/types/props.d.ts +73 -12
- package/lib/types/tree/webstudio-component.d.ts +2 -2
- package/package.json +21 -21
- package/src/app/custom-components/image.tsx +6 -7
- package/src/app/custom-components/shared/remix-link.tsx +7 -3
- package/src/app/params.ts +6 -4
- package/src/components/link.tsx +21 -7
- package/src/css/css.ts +9 -7
- package/src/css/global-rules.ts +6 -10
- package/src/css/style-rules.test.ts +22 -0
- package/src/css/style-rules.ts +15 -4
- package/src/props.test.ts +85 -20
- package/src/props.ts +57 -15
- package/src/pubsub/raf-queue.ts +3 -9
- package/src/tree/create-elements-tree.tsx +3 -7
- package/src/tree/root.ts +1 -1
- package/src/tree/webstudio-component.tsx +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webstudio-is/react-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.0",
|
|
4
4
|
"description": "Webstudio JavaScript / TypeScript API",
|
|
5
5
|
"author": "Webstudio <github@webstudio.is>",
|
|
6
6
|
"homepage": "https://webstudio.is",
|
|
@@ -13,23 +13,23 @@
|
|
|
13
13
|
"@remix-run/server-runtime": "1.15.0",
|
|
14
14
|
"@storybook/react": "^6.5.16",
|
|
15
15
|
"@types/node": "^18.11.18",
|
|
16
|
-
"@types/react": "^
|
|
17
|
-
"@types/react-dom": "^
|
|
16
|
+
"@types/react": "^18.0.35",
|
|
17
|
+
"@types/react-dom": "^18.0.11",
|
|
18
18
|
"jest": "^29.3.1",
|
|
19
|
-
"react": "^
|
|
20
|
-
"react-dom": "^
|
|
19
|
+
"react": "^18.2.0",
|
|
20
|
+
"react-dom": "^18.2.0",
|
|
21
21
|
"typescript": "5.0.3",
|
|
22
22
|
"zod": "^3.19.1",
|
|
23
|
-
"@webstudio-is/jest-config": "^1.0.
|
|
23
|
+
"@webstudio-is/jest-config": "^1.0.5",
|
|
24
24
|
"@webstudio-is/scripts": "^0.0.0",
|
|
25
25
|
"@webstudio-is/storybook-config": "^0.0.0",
|
|
26
|
-
"@webstudio-is/tsconfig": "^1.0.
|
|
26
|
+
"@webstudio-is/tsconfig": "^1.0.5"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"@remix-run/react": "1.9.0",
|
|
30
30
|
"@remix-run/server-runtime": "1.9.0",
|
|
31
|
-
"react": "^
|
|
32
|
-
"react-dom": "^
|
|
31
|
+
"react": "^18.2.0",
|
|
32
|
+
"react-dom": "^18.2.0",
|
|
33
33
|
"zod": "^3.19.1"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"html-tags": "^3.2.0",
|
|
39
39
|
"nanoevents": "^7.0.1",
|
|
40
40
|
"nanostores": "^0.7.1",
|
|
41
|
-
"@webstudio-is/asset-uploader": "^0.
|
|
42
|
-
"@webstudio-is/css-data": "^0.
|
|
43
|
-
"@webstudio-is/css-engine": "^0.
|
|
44
|
-
"@webstudio-is/css-vars": "^0.
|
|
45
|
-
"@webstudio-is/fonts": "^0.
|
|
46
|
-
"@webstudio-is/generate-arg-types": "^0.
|
|
47
|
-
"@webstudio-is/icons": "^0.
|
|
48
|
-
"@webstudio-is/image": "^0.
|
|
49
|
-
"@webstudio-is/prisma-client": "^0.
|
|
50
|
-
"@webstudio-is/project-build": "^0.
|
|
41
|
+
"@webstudio-is/asset-uploader": "^0.58.0",
|
|
42
|
+
"@webstudio-is/css-data": "^0.58.0",
|
|
43
|
+
"@webstudio-is/css-engine": "^0.58.0",
|
|
44
|
+
"@webstudio-is/css-vars": "^0.58.0",
|
|
45
|
+
"@webstudio-is/fonts": "^0.58.0",
|
|
46
|
+
"@webstudio-is/generate-arg-types": "^0.58.0",
|
|
47
|
+
"@webstudio-is/icons": "^0.58.0",
|
|
48
|
+
"@webstudio-is/image": "^0.58.0",
|
|
49
|
+
"@webstudio-is/prisma-client": "^0.58.0",
|
|
50
|
+
"@webstudio-is/project-build": "^0.58.0"
|
|
51
51
|
},
|
|
52
52
|
"exports": {
|
|
53
53
|
".": {
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
"dev": "build-package --watch",
|
|
70
70
|
"build": "build-package",
|
|
71
71
|
"build:args": "generate-arg-types './src/components/*.tsx !./src/**/*.stories.tsx !./src/**/*.ws.tsx' && prettier --write \"**/*.props.ts\"",
|
|
72
|
-
"dts": "tsc --
|
|
73
|
-
"typecheck": "tsc --noEmit",
|
|
72
|
+
"dts": "tsc --declarationDir lib/types",
|
|
73
|
+
"typecheck": "tsc --noEmit --emitDeclarationOnly false",
|
|
74
74
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests",
|
|
75
75
|
"lint": "eslint ./src --ext .ts,.tsx --max-warnings 0",
|
|
76
76
|
"checks": "pnpm typecheck && pnpm lint && pnpm test",
|
|
@@ -23,16 +23,15 @@ export const Image = forwardRef<ElementRef<typeof defaultTag>, Props>(
|
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
if (asset.location === "REMOTE") {
|
|
26
|
-
return loaders.cloudflareImageLoader(
|
|
26
|
+
return loaders.cloudflareImageLoader({
|
|
27
|
+
resizeOrigin: params.resizeOrigin,
|
|
28
|
+
cdnUrl: params.assetBaseUrl,
|
|
29
|
+
});
|
|
27
30
|
}
|
|
28
|
-
return loaders.localImageLoader(params);
|
|
31
|
+
return loaders.localImageLoader({ publicPath: params.assetBaseUrl });
|
|
29
32
|
}, [asset, params]);
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (asset != null) {
|
|
34
|
-
src = asset.path;
|
|
35
|
-
}
|
|
34
|
+
const src = asset?.name ?? props.src;
|
|
36
35
|
|
|
37
36
|
if (asset == null || loader == null) {
|
|
38
37
|
return <SdkImage key={src} {...props} src={src} ref={ref} />;
|
|
@@ -11,11 +11,15 @@ export const wrapLinkComponent = (BaseLink: LinkComponent) => {
|
|
|
11
11
|
const Component: LinkComponent = forwardRef((props: LinkProps, ref) => {
|
|
12
12
|
const href = usePropUrl(getInstanceIdFromComponentProps(props), "href");
|
|
13
13
|
|
|
14
|
-
if (
|
|
15
|
-
|
|
14
|
+
if (href?.type === "page") {
|
|
15
|
+
let to = href.page.path;
|
|
16
|
+
if (href.hash !== undefined) {
|
|
17
|
+
to += `#${href.hash}`;
|
|
18
|
+
}
|
|
19
|
+
return <RemixLink {...props} to={to} ref={ref} />;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
return <
|
|
22
|
+
return <BaseLink {...props} ref={ref} />;
|
|
19
23
|
});
|
|
20
24
|
|
|
21
25
|
Component.displayName = BaseLink.displayName;
|
package/src/app/params.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
export type Params = {
|
|
2
2
|
resizeOrigin?: string;
|
|
3
|
-
|
|
3
|
+
assetBaseUrl: string;
|
|
4
4
|
};
|
|
5
5
|
|
|
6
|
-
let params:
|
|
6
|
+
let params: undefined | Params;
|
|
7
7
|
|
|
8
|
-
const emptyParams: Params = {
|
|
8
|
+
const emptyParams: Params = {
|
|
9
|
+
assetBaseUrl: "/",
|
|
10
|
+
};
|
|
9
11
|
|
|
10
12
|
export const getParams = (): Params => params ?? emptyParams;
|
|
11
13
|
|
|
12
|
-
export const setParams = (newParams:
|
|
14
|
+
export const setParams = (newParams: undefined | Params) => {
|
|
13
15
|
params = newParams;
|
|
14
16
|
};
|
package/src/components/link.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { forwardRef, type ComponentProps } from "react";
|
|
2
2
|
import { usePropUrl, getInstanceIdFromComponentProps } from "../props";
|
|
3
|
+
import { getParams } from "../app/params";
|
|
3
4
|
|
|
4
5
|
export const defaultTag = "a";
|
|
5
6
|
|
|
@@ -15,13 +16,26 @@ type Props = Omit<ComponentProps<"a">, "href" | "target"> & {
|
|
|
15
16
|
|
|
16
17
|
export const Link = forwardRef<HTMLAnchorElement, Props>((props, ref) => {
|
|
17
18
|
const href = usePropUrl(getInstanceIdFromComponentProps(props), "href");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
|
|
20
|
+
const { assetBaseUrl } = getParams();
|
|
21
|
+
|
|
22
|
+
let url = "#";
|
|
23
|
+
|
|
24
|
+
switch (href?.type) {
|
|
25
|
+
case "page":
|
|
26
|
+
url = href.page.path;
|
|
27
|
+
if (href.hash !== undefined) {
|
|
28
|
+
url += `#${href.hash}`;
|
|
29
|
+
}
|
|
30
|
+
break;
|
|
31
|
+
case "asset":
|
|
32
|
+
url = `${assetBaseUrl}${href.asset.name}`;
|
|
33
|
+
break;
|
|
34
|
+
case "string":
|
|
35
|
+
url = href.url;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return <a {...props} href={url} ref={ref} />;
|
|
25
39
|
});
|
|
26
40
|
|
|
27
41
|
Link.displayName = "Link";
|
package/src/css/css.ts
CHANGED
|
@@ -15,7 +15,7 @@ type Data = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
type CssOptions = {
|
|
18
|
-
|
|
18
|
+
assetBaseUrl: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export const createImageValueTransformer =
|
|
@@ -28,9 +28,8 @@ export const createImageValueTransformer =
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// @todo reuse image loaders and generate image-set
|
|
31
|
-
const {
|
|
32
|
-
const url =
|
|
33
|
-
asset.location === "REMOTE" ? asset.path : `${publicPath}${asset.name}`;
|
|
31
|
+
const { assetBaseUrl } = options;
|
|
32
|
+
const url = `${assetBaseUrl}${asset.name}`;
|
|
34
33
|
|
|
35
34
|
return {
|
|
36
35
|
type: "image",
|
|
@@ -53,7 +52,10 @@ export const generateCssText = (data: Data, options: CssOptions) => {
|
|
|
53
52
|
|
|
54
53
|
const engine = createCssEngine({ name: "ssr" });
|
|
55
54
|
|
|
56
|
-
addGlobalRules(engine, {
|
|
55
|
+
addGlobalRules(engine, {
|
|
56
|
+
assets,
|
|
57
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
58
|
+
});
|
|
57
59
|
|
|
58
60
|
for (const breakpoint of breakpoints.values()) {
|
|
59
61
|
engine.addMediaRule(breakpoint.id, breakpoint);
|
|
@@ -75,9 +77,9 @@ export const generateCssText = (data: Data, options: CssOptions) => {
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
const styleRules = getStyleRules(styles, styleSourceSelections);
|
|
78
|
-
for (const { breakpointId, instanceId, style } of styleRules) {
|
|
80
|
+
for (const { breakpointId, instanceId, state, style } of styleRules) {
|
|
79
81
|
engine.addStyleRule(
|
|
80
|
-
`[${idAttribute}="${instanceId}"]`,
|
|
82
|
+
`[${idAttribute}="${instanceId}"]${state ?? ""}`,
|
|
81
83
|
{
|
|
82
84
|
breakpoint: breakpointId,
|
|
83
85
|
style,
|
package/src/css/global-rules.ts
CHANGED
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
import type { CssEngine } from "@webstudio-is/css-engine";
|
|
2
2
|
import type { Assets, FontAsset } from "@webstudio-is/asset-uploader";
|
|
3
|
-
import {
|
|
4
|
-
type FontFormat,
|
|
5
|
-
FONT_FORMATS,
|
|
6
|
-
getFontFaces,
|
|
7
|
-
} from "@webstudio-is/fonts";
|
|
3
|
+
import { getFontFaces } from "@webstudio-is/fonts";
|
|
8
4
|
|
|
9
5
|
export const addGlobalRules = (
|
|
10
6
|
engine: CssEngine,
|
|
11
|
-
{ assets }: { assets: Assets }
|
|
7
|
+
{ assets, assetBaseUrl }: { assets: Assets; assetBaseUrl: string }
|
|
12
8
|
) => {
|
|
13
9
|
// @todo we need to figure out all global resets while keeping
|
|
14
10
|
// the engine aware of all of them.
|
|
15
11
|
// Ideally, the user is somehow aware and in control of the reset
|
|
16
12
|
engine.addPlaintextRule("html {margin: 0; height: 100%}");
|
|
17
13
|
|
|
18
|
-
const fontAssets:
|
|
14
|
+
const fontAssets: FontAsset[] = [];
|
|
19
15
|
for (const asset of assets.values()) {
|
|
20
|
-
if (asset
|
|
21
|
-
fontAssets.push(asset
|
|
16
|
+
if (asset?.type === "font") {
|
|
17
|
+
fontAssets.push(asset);
|
|
22
18
|
}
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
const fontFaces = getFontFaces(fontAssets);
|
|
21
|
+
const fontFaces = getFontFaces(fontAssets, { assetBaseUrl });
|
|
26
22
|
for (const fontFace of fontFaces) {
|
|
27
23
|
engine.addFontFaceRule(fontFace);
|
|
28
24
|
}
|
|
@@ -55,6 +55,13 @@ test("compute styles from different style sources", () => {
|
|
|
55
55
|
property: "color",
|
|
56
56
|
value: { type: "keyword", value: "blue" },
|
|
57
57
|
}),
|
|
58
|
+
createStyleDeclPair({
|
|
59
|
+
breakpointId: "a",
|
|
60
|
+
styleSourceId: "styleSource6",
|
|
61
|
+
state: ":hover",
|
|
62
|
+
property: "color",
|
|
63
|
+
value: { type: "keyword", value: "blue" },
|
|
64
|
+
}),
|
|
58
65
|
]);
|
|
59
66
|
const styleSourceSelections: StyleSourceSelections = new Map([
|
|
60
67
|
[
|
|
@@ -85,6 +92,7 @@ test("compute styles from different style sources", () => {
|
|
|
85
92
|
{
|
|
86
93
|
"breakpointId": "a",
|
|
87
94
|
"instanceId": "instance1",
|
|
95
|
+
"state": undefined,
|
|
88
96
|
"style": {
|
|
89
97
|
"width": {
|
|
90
98
|
"type": "unit",
|
|
@@ -96,6 +104,7 @@ test("compute styles from different style sources", () => {
|
|
|
96
104
|
{
|
|
97
105
|
"breakpointId": "a",
|
|
98
106
|
"instanceId": "instance2",
|
|
107
|
+
"state": undefined,
|
|
99
108
|
"style": {
|
|
100
109
|
"color": {
|
|
101
110
|
"type": "keyword",
|
|
@@ -110,6 +119,7 @@ test("compute styles from different style sources", () => {
|
|
|
110
119
|
{
|
|
111
120
|
"breakpointId": "b",
|
|
112
121
|
"instanceId": "instance2",
|
|
122
|
+
"state": undefined,
|
|
113
123
|
"style": {
|
|
114
124
|
"color": {
|
|
115
125
|
"type": "keyword",
|
|
@@ -120,6 +130,18 @@ test("compute styles from different style sources", () => {
|
|
|
120
130
|
{
|
|
121
131
|
"breakpointId": "a",
|
|
122
132
|
"instanceId": "instance3",
|
|
133
|
+
"state": undefined,
|
|
134
|
+
"style": {
|
|
135
|
+
"color": {
|
|
136
|
+
"type": "keyword",
|
|
137
|
+
"value": "blue",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"breakpointId": "a",
|
|
143
|
+
"instanceId": "instance3",
|
|
144
|
+
"state": ":hover",
|
|
123
145
|
"style": {
|
|
124
146
|
"color": {
|
|
125
147
|
"type": "keyword",
|
package/src/css/style-rules.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
type StyleRule = {
|
|
11
11
|
instanceId: string;
|
|
12
12
|
breakpointId: string;
|
|
13
|
+
state: undefined | string;
|
|
13
14
|
style: Style;
|
|
14
15
|
};
|
|
15
16
|
|
|
@@ -38,7 +39,10 @@ export const getStyleRules = (
|
|
|
38
39
|
|
|
39
40
|
const styleRules: StyleRule[] = [];
|
|
40
41
|
for (const { instanceId, values } of styleSourceSelections.values()) {
|
|
41
|
-
const styleRuleByBreakpointId = new Map<
|
|
42
|
+
const styleRuleByBreakpointId = new Map<
|
|
43
|
+
`${Breakpoint["id"]}:${string}`,
|
|
44
|
+
StyleRule
|
|
45
|
+
>();
|
|
42
46
|
|
|
43
47
|
for (const styleSourceId of values) {
|
|
44
48
|
const styleSourceStyles = stylesByStyleSourceId.get(styleSourceId);
|
|
@@ -46,15 +50,22 @@ export const getStyleRules = (
|
|
|
46
50
|
if (styleSourceStyles === undefined) {
|
|
47
51
|
continue;
|
|
48
52
|
}
|
|
49
|
-
for (const {
|
|
50
|
-
|
|
53
|
+
for (const {
|
|
54
|
+
breakpointId,
|
|
55
|
+
state,
|
|
56
|
+
property,
|
|
57
|
+
value,
|
|
58
|
+
} of styleSourceStyles) {
|
|
59
|
+
const key = `${breakpointId}:${state ?? ""}` as const;
|
|
60
|
+
let styleRule = styleRuleByBreakpointId.get(key);
|
|
51
61
|
if (styleRule === undefined) {
|
|
52
62
|
styleRule = {
|
|
53
63
|
instanceId,
|
|
54
64
|
breakpointId,
|
|
65
|
+
state,
|
|
55
66
|
style: {},
|
|
56
67
|
};
|
|
57
|
-
styleRuleByBreakpointId.set(
|
|
68
|
+
styleRuleByBreakpointId.set(key, styleRule);
|
|
58
69
|
}
|
|
59
70
|
styleRule.style[property] = value;
|
|
60
71
|
}
|
package/src/props.test.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { describe, test, expect } from "@jest/globals";
|
|
2
2
|
import { resolveUrlProp, type Pages, type PropsByInstanceId } from "./props";
|
|
3
3
|
import type { Page, Prop } from "@webstudio-is/project-build";
|
|
4
|
+
import type { Asset, Assets } from "@webstudio-is/asset-uploader";
|
|
4
5
|
|
|
5
6
|
const unique = () => Math.random().toString();
|
|
6
7
|
|
|
7
8
|
describe("resolveUrlProp", () => {
|
|
8
9
|
const instanceId = unique();
|
|
10
|
+
const projectId = unique();
|
|
9
11
|
|
|
10
12
|
const page1: Page = {
|
|
11
13
|
id: unique(),
|
|
@@ -25,6 +27,27 @@ describe("resolveUrlProp", () => {
|
|
|
25
27
|
rootInstanceId: "0",
|
|
26
28
|
};
|
|
27
29
|
|
|
30
|
+
const asset1: Asset = {
|
|
31
|
+
id: unique(),
|
|
32
|
+
name: unique(),
|
|
33
|
+
type: "image",
|
|
34
|
+
location: "REMOTE",
|
|
35
|
+
projectId,
|
|
36
|
+
format: "png",
|
|
37
|
+
size: 100000,
|
|
38
|
+
createdAt: new Date().toISOString(),
|
|
39
|
+
description: null,
|
|
40
|
+
meta: { width: 128, height: 180 },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const assetProp: Prop = {
|
|
44
|
+
type: "asset",
|
|
45
|
+
id: unique(),
|
|
46
|
+
instanceId,
|
|
47
|
+
name: unique(),
|
|
48
|
+
value: asset1.id,
|
|
49
|
+
};
|
|
50
|
+
|
|
28
51
|
const pageByIdProp: Prop = {
|
|
29
52
|
type: "page",
|
|
30
53
|
id: unique(),
|
|
@@ -33,6 +56,22 @@ describe("resolveUrlProp", () => {
|
|
|
33
56
|
value: page1.id,
|
|
34
57
|
};
|
|
35
58
|
|
|
59
|
+
const instnaceIdProp: Prop = {
|
|
60
|
+
type: "string",
|
|
61
|
+
id: unique(),
|
|
62
|
+
instanceId: unique(),
|
|
63
|
+
name: "id",
|
|
64
|
+
value: unique(),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const pageSectionProp: Prop = {
|
|
68
|
+
type: "page",
|
|
69
|
+
id: unique(),
|
|
70
|
+
instanceId,
|
|
71
|
+
name: unique(),
|
|
72
|
+
value: { pageId: page1.id, instanceId: instnaceIdProp.instanceId },
|
|
73
|
+
};
|
|
74
|
+
|
|
36
75
|
const pageByPathProp: Prop = {
|
|
37
76
|
type: "string",
|
|
38
77
|
id: unique(),
|
|
@@ -49,8 +88,18 @@ describe("resolveUrlProp", () => {
|
|
|
49
88
|
value: unique(),
|
|
50
89
|
};
|
|
51
90
|
|
|
52
|
-
const
|
|
53
|
-
[
|
|
91
|
+
const props: PropsByInstanceId = new Map([
|
|
92
|
+
[
|
|
93
|
+
instanceId,
|
|
94
|
+
[
|
|
95
|
+
pageByIdProp,
|
|
96
|
+
pageByPathProp,
|
|
97
|
+
arbitraryUrlProp,
|
|
98
|
+
assetProp,
|
|
99
|
+
pageSectionProp,
|
|
100
|
+
],
|
|
101
|
+
],
|
|
102
|
+
[instnaceIdProp.instanceId, [instnaceIdProp]],
|
|
54
103
|
]);
|
|
55
104
|
|
|
56
105
|
const pages: Pages = new Map([
|
|
@@ -58,38 +107,54 @@ describe("resolveUrlProp", () => {
|
|
|
58
107
|
[page2.id, page2],
|
|
59
108
|
]);
|
|
60
109
|
|
|
110
|
+
const assets: Assets = new Map([[asset1.id, asset1]]);
|
|
111
|
+
|
|
112
|
+
const stores = { props, pages, assets };
|
|
113
|
+
|
|
61
114
|
test("if instanceId is unknown returns undefined", () => {
|
|
62
115
|
expect(
|
|
63
|
-
resolveUrlProp("unknown", pageByIdProp.name,
|
|
116
|
+
resolveUrlProp("unknown", pageByIdProp.name, stores)
|
|
64
117
|
).toBeUndefined();
|
|
65
118
|
});
|
|
66
119
|
|
|
67
120
|
test("if prop name is unknown returns undefined", () => {
|
|
68
|
-
expect(
|
|
69
|
-
|
|
70
|
-
|
|
121
|
+
expect(resolveUrlProp(instanceId, "unknown", stores)).toBeUndefined();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("asset by id", () => {
|
|
125
|
+
expect(resolveUrlProp(instanceId, assetProp.name, stores)).toEqual({
|
|
126
|
+
type: "asset",
|
|
127
|
+
asset: asset1,
|
|
128
|
+
});
|
|
71
129
|
});
|
|
72
130
|
|
|
73
131
|
test("page by id", () => {
|
|
74
|
-
expect(
|
|
75
|
-
|
|
76
|
-
|
|
132
|
+
expect(resolveUrlProp(instanceId, pageByIdProp.name, stores)).toEqual({
|
|
133
|
+
type: "page",
|
|
134
|
+
page: page1,
|
|
135
|
+
});
|
|
77
136
|
});
|
|
78
137
|
|
|
79
138
|
test("page by path", () => {
|
|
80
|
-
expect(
|
|
81
|
-
|
|
82
|
-
|
|
139
|
+
expect(resolveUrlProp(instanceId, pageByPathProp.name, stores)).toEqual({
|
|
140
|
+
type: "page",
|
|
141
|
+
page: page2,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("section on a page", () => {
|
|
146
|
+
expect(resolveUrlProp(instanceId, pageSectionProp.name, stores)).toEqual({
|
|
147
|
+
type: "page",
|
|
148
|
+
page: page1,
|
|
149
|
+
instanceId: instnaceIdProp.instanceId,
|
|
150
|
+
hash: instnaceIdProp.value,
|
|
151
|
+
});
|
|
83
152
|
});
|
|
84
153
|
|
|
85
154
|
test("arbitrary url", () => {
|
|
86
|
-
expect(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
propsByInstanceId,
|
|
91
|
-
pages
|
|
92
|
-
)
|
|
93
|
-
).toBe(arbitraryUrlProp.value);
|
|
155
|
+
expect(resolveUrlProp(instanceId, arbitraryUrlProp.name, stores)).toEqual({
|
|
156
|
+
type: "string",
|
|
157
|
+
url: arbitraryUrlProp.value,
|
|
158
|
+
});
|
|
94
159
|
});
|
|
95
160
|
});
|
package/src/props.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { useContext, useMemo } from "react";
|
|
|
2
2
|
import { computed } from "nanostores";
|
|
3
3
|
import { useStore } from "@nanostores/react";
|
|
4
4
|
import type { Instance, Page, Prop, Props } from "@webstudio-is/project-build";
|
|
5
|
+
import type { Asset, Assets } from "@webstudio-is/asset-uploader";
|
|
5
6
|
import { ReactSdkContext } from "./context";
|
|
6
7
|
import { idAttribute } from "./tree/webstudio-component";
|
|
7
8
|
|
|
@@ -31,7 +32,7 @@ export const useInstanceProps = (instanceId: Instance["id"]) => {
|
|
|
31
32
|
const instancePropsObject: Record<Prop["name"], Prop["value"]> = {};
|
|
32
33
|
if (instanceProps) {
|
|
33
34
|
for (const prop of instanceProps) {
|
|
34
|
-
if (prop.type !== "asset") {
|
|
35
|
+
if (prop.type !== "asset" && prop.type !== "page") {
|
|
35
36
|
instancePropsObject[prop.name] = prop.value;
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -67,10 +68,22 @@ export const usePropAsset = (instanceId: Instance["id"], name: string) => {
|
|
|
67
68
|
export const resolveUrlProp = (
|
|
68
69
|
instanceId: Instance["id"],
|
|
69
70
|
name: string,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
{
|
|
72
|
+
props,
|
|
73
|
+
pages,
|
|
74
|
+
assets,
|
|
75
|
+
}: { props: PropsByInstanceId; pages: Pages; assets: Assets }
|
|
76
|
+
):
|
|
77
|
+
| {
|
|
78
|
+
type: "page";
|
|
79
|
+
page: Page;
|
|
80
|
+
instanceId?: Instance["id"];
|
|
81
|
+
hash?: string;
|
|
82
|
+
}
|
|
83
|
+
| { type: "asset"; asset: Asset }
|
|
84
|
+
| { type: "string"; url: string }
|
|
85
|
+
| undefined => {
|
|
86
|
+
const instanceProps = props.get(instanceId);
|
|
74
87
|
if (instanceProps === undefined) {
|
|
75
88
|
return;
|
|
76
89
|
}
|
|
@@ -80,16 +93,44 @@ export const resolveUrlProp = (
|
|
|
80
93
|
}
|
|
81
94
|
|
|
82
95
|
if (prop.type === "page") {
|
|
83
|
-
|
|
96
|
+
if (typeof prop.value === "string") {
|
|
97
|
+
const page = pages.get(prop.value);
|
|
98
|
+
return page && { type: "page", page };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const { instanceId, pageId } = prop.value;
|
|
102
|
+
|
|
103
|
+
const page = pages.get(pageId);
|
|
104
|
+
|
|
105
|
+
if (page === undefined) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const idProp = props.get(instanceId)?.find((prop) => prop.name === "id");
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
type: "page",
|
|
113
|
+
page,
|
|
114
|
+
instanceId,
|
|
115
|
+
hash:
|
|
116
|
+
idProp === undefined || idProp.type !== "string"
|
|
117
|
+
? undefined
|
|
118
|
+
: idProp.value,
|
|
119
|
+
};
|
|
84
120
|
}
|
|
85
121
|
|
|
86
122
|
if (prop.type === "string") {
|
|
87
123
|
for (const page of pages.values()) {
|
|
88
124
|
if (page.path === prop.value) {
|
|
89
|
-
return page;
|
|
125
|
+
return { type: "page", page };
|
|
90
126
|
}
|
|
91
127
|
}
|
|
92
|
-
return prop.value;
|
|
128
|
+
return { type: "string", url: prop.value };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (prop.type === "asset") {
|
|
132
|
+
const asset = assets.get(prop.value);
|
|
133
|
+
return asset && { type: "asset", asset };
|
|
93
134
|
}
|
|
94
135
|
|
|
95
136
|
return;
|
|
@@ -99,17 +140,18 @@ export const resolveUrlProp = (
|
|
|
99
140
|
// this utility is used for link component in both builder and preview
|
|
100
141
|
// so need to optimize rerenders with computed
|
|
101
142
|
export const usePropUrl = (instanceId: Instance["id"], name: string) => {
|
|
102
|
-
const { propsByInstanceIdStore, pagesStore } =
|
|
103
|
-
|
|
143
|
+
const { propsByInstanceIdStore, pagesStore, assetsStore } =
|
|
144
|
+
useContext(ReactSdkContext);
|
|
145
|
+
const store = useMemo(
|
|
104
146
|
() =>
|
|
105
147
|
computed(
|
|
106
|
-
[propsByInstanceIdStore, pagesStore],
|
|
107
|
-
(
|
|
108
|
-
resolveUrlProp(instanceId, name,
|
|
148
|
+
[propsByInstanceIdStore, pagesStore, assetsStore],
|
|
149
|
+
(props, pages, assets) =>
|
|
150
|
+
resolveUrlProp(instanceId, name, { props, pages, assets })
|
|
109
151
|
),
|
|
110
|
-
[propsByInstanceIdStore, pagesStore, instanceId, name]
|
|
152
|
+
[propsByInstanceIdStore, pagesStore, assetsStore, instanceId, name]
|
|
111
153
|
);
|
|
112
|
-
return useStore(
|
|
154
|
+
return useStore(store);
|
|
113
155
|
};
|
|
114
156
|
|
|
115
157
|
export const getInstanceIdFromComponentProps = (
|
package/src/pubsub/raf-queue.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import { unstable_batchedUpdates as batchedUpdates } from "react-dom";
|
|
2
|
-
|
|
3
1
|
type Task = () => void;
|
|
4
2
|
|
|
5
3
|
let handle: ReturnType<typeof requestAnimationFrame> | undefined;
|
|
6
4
|
let updateQueue: Task[] = [];
|
|
7
5
|
|
|
8
6
|
const processUpdates = (updates: Task[]) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
for (const update of updates) {
|
|
13
|
-
update();
|
|
14
|
-
}
|
|
15
|
-
});
|
|
7
|
+
for (const update of updates) {
|
|
8
|
+
update();
|
|
9
|
+
}
|
|
16
10
|
};
|
|
17
11
|
|
|
18
12
|
export const batchUpdate = (update: () => void) => {
|