@webstudio-is/react-sdk 0.49.0 → 0.51.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.
Files changed (165) hide show
  1. package/lib/app/custom-components/image.js +11 -13
  2. package/lib/app/custom-components/shared/remix-link.js +7 -24
  3. package/lib/app/handle-request.server.js +1 -4
  4. package/lib/app/root.js +11 -29
  5. package/lib/cjs/app/custom-components/image.cjs +10 -12
  6. package/lib/cjs/app/custom-components/shared/remix-link.cjs +6 -23
  7. package/lib/cjs/app/handle-request.server.cjs +1 -4
  8. package/lib/cjs/app/root.cjs +11 -29
  9. package/lib/cjs/components/__generated__/fragment.props.cjs +24 -0
  10. package/lib/cjs/components/__generated__/link-block.props.cjs +5 -5
  11. package/lib/cjs/components/__generated__/link.props.cjs +6 -6
  12. package/lib/cjs/components/__generated__/rich-text-link.props.cjs +5 -5
  13. package/lib/cjs/components/__generated__/slot.props.cjs +24 -0
  14. package/lib/cjs/components/blockquote.ws.cjs +1 -0
  15. package/lib/cjs/components/body.cjs +1 -4
  16. package/lib/cjs/components/body.ws.cjs +1 -1
  17. package/lib/cjs/components/bold.cjs +1 -4
  18. package/lib/cjs/components/box.ws.cjs +1 -0
  19. package/lib/cjs/components/button.cjs +1 -6
  20. package/lib/cjs/components/button.ws.cjs +1 -0
  21. package/lib/cjs/components/code.ws.cjs +1 -0
  22. package/lib/cjs/components/{component-type.cjs → component-meta.cjs} +22 -4
  23. package/lib/cjs/components/components-utils.cjs +6 -0
  24. package/lib/cjs/components/components.cjs +4 -0
  25. package/lib/cjs/components/form.cjs +1 -4
  26. package/lib/cjs/components/form.ws.cjs +1 -0
  27. package/lib/cjs/components/fragment.cjs +29 -0
  28. package/lib/cjs/components/fragment.ws.cjs +32 -0
  29. package/lib/cjs/components/heading.ws.cjs +1 -0
  30. package/lib/cjs/components/image.cjs +8 -5
  31. package/lib/cjs/components/image.ws.cjs +4 -0
  32. package/lib/cjs/components/index.cjs +8 -0
  33. package/lib/cjs/components/input.cjs +1 -4
  34. package/lib/cjs/components/input.ws.cjs +1 -0
  35. package/lib/cjs/components/italic.cjs +1 -4
  36. package/lib/cjs/components/link-block.cjs +1 -4
  37. package/lib/cjs/components/link-block.ws.cjs +7 -2
  38. package/lib/cjs/components/link.cjs +10 -7
  39. package/lib/cjs/components/link.ws.cjs +9 -1
  40. package/lib/cjs/components/list-item.ws.cjs +1 -0
  41. package/lib/cjs/components/list.ws.cjs +1 -0
  42. package/lib/cjs/components/paragraph.cjs +1 -4
  43. package/lib/cjs/components/paragraph.ws.cjs +1 -0
  44. package/lib/cjs/components/rich-text-link.cjs +1 -4
  45. package/lib/cjs/components/rich-text-link.ws.cjs +7 -3
  46. package/lib/cjs/components/separator.ws.cjs +1 -0
  47. package/lib/cjs/components/slot.cjs +36 -0
  48. package/lib/cjs/components/slot.ws.cjs +34 -0
  49. package/lib/cjs/components/span.cjs +1 -4
  50. package/lib/cjs/components/subscript.cjs +1 -4
  51. package/lib/cjs/components/superscript.cjs +1 -4
  52. package/lib/cjs/components/text-block.cjs +1 -4
  53. package/lib/cjs/components/text-block.ws.cjs +1 -0
  54. package/lib/cjs/context.cjs +2 -1
  55. package/lib/cjs/css/categories.cjs +6 -0
  56. package/lib/cjs/index.cjs +2 -0
  57. package/lib/cjs/props.cjs +43 -2
  58. package/lib/cjs/pubsub/create.cjs +13 -0
  59. package/lib/cjs/tree/create-elements-tree.cjs +33 -22
  60. package/lib/cjs/tree/root.cjs +1 -0
  61. package/lib/cjs/tree/session-storage-polyfill.cjs +7 -4
  62. package/lib/cjs/tree/webstudio-component.cjs +5 -10
  63. package/lib/components/__generated__/fragment.props.js +4 -0
  64. package/lib/components/__generated__/link-block.props.js +5 -5
  65. package/lib/components/__generated__/link.props.js +6 -6
  66. package/lib/components/__generated__/rich-text-link.props.js +5 -5
  67. package/lib/components/__generated__/slot.props.js +4 -0
  68. package/lib/components/blockquote.ws.js +1 -0
  69. package/lib/components/body.js +1 -4
  70. package/lib/components/body.ws.js +1 -1
  71. package/lib/components/bold.js +1 -4
  72. package/lib/components/box.ws.js +1 -0
  73. package/lib/components/button.js +1 -6
  74. package/lib/components/button.ws.js +1 -0
  75. package/lib/components/code.ws.js +1 -0
  76. package/lib/components/component-meta.js +34 -0
  77. package/lib/components/components-utils.js +2 -0
  78. package/lib/components/components.js +4 -0
  79. package/lib/components/form.js +1 -4
  80. package/lib/components/form.ws.js +1 -0
  81. package/lib/components/fragment.js +9 -0
  82. package/lib/components/fragment.ws.js +12 -0
  83. package/lib/components/heading.ws.js +1 -0
  84. package/lib/components/image.js +8 -5
  85. package/lib/components/image.ws.js +4 -0
  86. package/lib/components/index.js +8 -0
  87. package/lib/components/input.js +1 -4
  88. package/lib/components/input.ws.js +1 -0
  89. package/lib/components/italic.js +1 -4
  90. package/lib/components/link-block.js +1 -4
  91. package/lib/components/link-block.ws.js +7 -2
  92. package/lib/components/link.js +10 -7
  93. package/lib/components/link.ws.js +9 -1
  94. package/lib/components/list-item.ws.js +1 -0
  95. package/lib/components/list.ws.js +1 -0
  96. package/lib/components/paragraph.js +1 -4
  97. package/lib/components/paragraph.ws.js +1 -0
  98. package/lib/components/rich-text-link.js +1 -4
  99. package/lib/components/rich-text-link.ws.js +8 -4
  100. package/lib/components/separator.ws.js +1 -0
  101. package/lib/components/slot.js +16 -0
  102. package/lib/components/slot.ws.js +14 -0
  103. package/lib/components/span.js +1 -4
  104. package/lib/components/subscript.js +1 -4
  105. package/lib/components/superscript.js +1 -4
  106. package/lib/components/text-block.js +1 -4
  107. package/lib/components/text-block.ws.js +1 -0
  108. package/lib/context.js +2 -1
  109. package/lib/css/categories.js +6 -0
  110. package/lib/index.js +4 -0
  111. package/lib/props.js +43 -2
  112. package/lib/pubsub/create.js +9 -0
  113. package/lib/tree/create-elements-tree.js +33 -22
  114. package/lib/tree/root.js +1 -0
  115. package/lib/tree/session-storage-polyfill.js +7 -4
  116. package/lib/tree/webstudio-component.js +5 -10
  117. package/package.json +12 -16
  118. package/src/app/custom-components/image.tsx +4 -7
  119. package/src/app/custom-components/index.ts +1 -1
  120. package/src/app/custom-components/shared/remix-link.tsx +12 -48
  121. package/src/components/__generated__/fragment.props.ts +3 -0
  122. package/src/components/__generated__/link-block.props.ts +5 -5
  123. package/src/components/__generated__/link.props.ts +6 -6
  124. package/src/components/__generated__/rich-text-link.props.ts +5 -5
  125. package/src/components/__generated__/slot.props.ts +3 -0
  126. package/src/components/blockquote.ws.tsx +2 -1
  127. package/src/components/body.ws.tsx +2 -2
  128. package/src/components/bold.ws.tsx +1 -1
  129. package/src/components/box.ws.ts +2 -1
  130. package/src/components/button.ws.tsx +2 -1
  131. package/src/components/code.ws.tsx +2 -1
  132. package/src/components/{component-type.ts → component-meta.ts} +21 -23
  133. package/src/components/components-utils.ts +4 -0
  134. package/src/components/components.ts +2 -0
  135. package/src/components/form.ws.tsx +2 -1
  136. package/src/components/fragment.tsx +11 -0
  137. package/src/components/fragment.ws.ts +11 -0
  138. package/src/components/heading.ws.tsx +2 -1
  139. package/src/components/image.ws.tsx +2 -1
  140. package/src/components/index.ts +11 -1
  141. package/src/components/input.ws.tsx +2 -1
  142. package/src/components/italic.ws.tsx +1 -1
  143. package/src/components/link-block.ws.tsx +8 -3
  144. package/src/components/link.tsx +12 -6
  145. package/src/components/link.ws.tsx +10 -2
  146. package/src/components/list-item.ws.tsx +2 -1
  147. package/src/components/list.ws.tsx +2 -1
  148. package/src/components/paragraph.ws.tsx +2 -1
  149. package/src/components/rich-text-link.ws.tsx +10 -5
  150. package/src/components/separator.ws.tsx +2 -1
  151. package/src/components/slot.stories.tsx +16 -0
  152. package/src/components/slot.tsx +17 -0
  153. package/src/components/slot.ws.ts +13 -0
  154. package/src/components/span.ws.tsx +1 -1
  155. package/src/components/subscript.ws.tsx +1 -1
  156. package/src/components/superscript.ws.tsx +1 -1
  157. package/src/components/text-block.ws.tsx +2 -1
  158. package/src/context.tsx +3 -1
  159. package/src/index.ts +5 -4
  160. package/src/props.test.ts +95 -0
  161. package/src/props.ts +59 -3
  162. package/src/tree/create-elements-tree.tsx +30 -12
  163. package/src/tree/root.ts +2 -0
  164. package/src/tree/webstudio-component.tsx +1 -0
  165. package/lib/components/component-type.js +0 -20
package/src/context.tsx CHANGED
@@ -1,11 +1,13 @@
1
1
  import { type ReadableAtom, atom } from "nanostores";
2
2
  import { createContext } from "react";
3
- import type { Assets, PropsByInstanceId } from "./props";
3
+ import type { Assets, Pages, PropsByInstanceId } from "./props";
4
4
 
5
5
  export const ReactSdkContext = createContext<{
6
6
  propsByInstanceIdStore: ReadableAtom<PropsByInstanceId>;
7
7
  assetsStore: ReadableAtom<Assets>;
8
+ pagesStore: ReadableAtom<Pages>;
8
9
  }>({
9
10
  propsByInstanceIdStore: atom(new Map()),
10
11
  assetsStore: atom(new Map()),
12
+ pagesStore: atom(new Map()),
11
13
  });
package/src/index.ts CHANGED
@@ -10,7 +10,8 @@ export {
10
10
  customComponentMetas,
11
11
  customComponentPropsMetas,
12
12
  } from "./app/custom-components";
13
- export type {
14
- WsComponentPropsMeta,
15
- WsComponentMeta,
16
- } from "./components/component-type";
13
+ export {
14
+ type WsComponentPropsMeta,
15
+ type WsComponentMeta,
16
+ componentCategories,
17
+ } from "./components/component-meta";
@@ -0,0 +1,95 @@
1
+ import { describe, test, expect } from "@jest/globals";
2
+ import { resolveUrlProp, type Pages, type PropsByInstanceId } from "./props";
3
+ import type { Page, Prop } from "@webstudio-is/project-build";
4
+
5
+ const unique = () => Math.random().toString();
6
+
7
+ describe("resolveUrlProp", () => {
8
+ const instanceId = unique();
9
+
10
+ const page1: Page = {
11
+ id: unique(),
12
+ path: `/${unique()}`,
13
+ name: "",
14
+ title: "",
15
+ meta: {},
16
+ rootInstanceId: "0",
17
+ };
18
+
19
+ const page2: Page = {
20
+ id: unique(),
21
+ path: `/${unique()}`,
22
+ name: "",
23
+ title: "",
24
+ meta: {},
25
+ rootInstanceId: "0",
26
+ };
27
+
28
+ const pageByIdProp: Prop = {
29
+ type: "page",
30
+ id: unique(),
31
+ instanceId,
32
+ name: unique(),
33
+ value: page1.id,
34
+ };
35
+
36
+ const pageByPathProp: Prop = {
37
+ type: "string",
38
+ id: unique(),
39
+ instanceId,
40
+ name: unique(),
41
+ value: page2.path,
42
+ };
43
+
44
+ const arbitraryUrlProp: Prop = {
45
+ type: "string",
46
+ id: unique(),
47
+ instanceId,
48
+ name: unique(),
49
+ value: unique(),
50
+ };
51
+
52
+ const propsByInstanceId: PropsByInstanceId = new Map([
53
+ [instanceId, [pageByIdProp, pageByPathProp, arbitraryUrlProp]],
54
+ ]);
55
+
56
+ const pages: Pages = new Map([
57
+ [page1.id, page1],
58
+ [page2.id, page2],
59
+ ]);
60
+
61
+ test("if instanceId is unknown returns undefined", () => {
62
+ expect(
63
+ resolveUrlProp("unknown", pageByIdProp.name, propsByInstanceId, pages)
64
+ ).toBeUndefined();
65
+ });
66
+
67
+ test("if prop name is unknown returns undefined", () => {
68
+ expect(
69
+ resolveUrlProp(instanceId, "unknown", propsByInstanceId, pages)
70
+ ).toBeUndefined();
71
+ });
72
+
73
+ test("page by id", () => {
74
+ expect(
75
+ resolveUrlProp(instanceId, pageByIdProp.name, propsByInstanceId, pages)
76
+ ).toBe(page1);
77
+ });
78
+
79
+ test("page by path", () => {
80
+ expect(
81
+ resolveUrlProp(instanceId, pageByPathProp.name, propsByInstanceId, pages)
82
+ ).toBe(page2);
83
+ });
84
+
85
+ test("arbitrary url", () => {
86
+ expect(
87
+ resolveUrlProp(
88
+ instanceId,
89
+ arbitraryUrlProp.name,
90
+ propsByInstanceId,
91
+ pages
92
+ )
93
+ ).toBe(arbitraryUrlProp.value);
94
+ });
95
+ });
package/src/props.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  import { useContext, useMemo } from "react";
2
2
  import { computed } from "nanostores";
3
3
  import { useStore } from "@nanostores/react";
4
- import type { Instance, Prop, Props } from "@webstudio-is/project-build";
4
+ import type { Instance, Page, Prop, Props } from "@webstudio-is/project-build";
5
5
  import type { Asset } from "@webstudio-is/asset-uploader";
6
6
  import { ReactSdkContext } from "./context";
7
+ import { idAttribute } from "./tree/webstudio-component";
7
8
 
8
9
  export type PropsByInstanceId = Map<Instance["id"], Prop[]>;
9
10
 
10
11
  export type Assets = Map<Asset["id"], Asset>;
12
+ export type Pages = Map<Page["id"], Page>;
11
13
 
12
14
  export const getPropsByInstanceId = (props: Props) => {
13
15
  const propsByInstanceId: PropsByInstanceId = new Map();
@@ -39,7 +41,7 @@ export const useInstanceProps = (instanceId: Instance["id"]) => {
39
41
  return instancePropsObject;
40
42
  };
41
43
 
42
- // this utility is be used for image component in both builder and preview
44
+ // this utility is used for image component in both builder and preview
43
45
  // so need to optimize rerenders with computed
44
46
  export const usePropAsset = (instanceId: Instance["id"], name: string) => {
45
47
  const { propsByInstanceIdStore, assetsStore } = useContext(ReactSdkContext);
@@ -49,7 +51,7 @@ export const usePropAsset = (instanceId: Instance["id"], name: string) => {
49
51
  (propsByInstanceId, assets) => {
50
52
  const instanceProps = propsByInstanceId.get(instanceId);
51
53
  if (instanceProps === undefined) {
52
- return undefined;
54
+ return;
53
55
  }
54
56
  for (const prop of instanceProps) {
55
57
  if (prop.type === "asset" && prop.name === name) {
@@ -63,3 +65,57 @@ export const usePropAsset = (instanceId: Instance["id"], name: string) => {
63
65
  const asset = useStore(assetStore);
64
66
  return asset;
65
67
  };
68
+
69
+ export const resolveUrlProp = (
70
+ instanceId: Instance["id"],
71
+ name: string,
72
+ propsByInstanceId: PropsByInstanceId,
73
+ pages: Pages
74
+ ): Page | string | undefined => {
75
+ const instanceProps = propsByInstanceId.get(instanceId);
76
+ if (instanceProps === undefined) {
77
+ return;
78
+ }
79
+ for (const prop of instanceProps) {
80
+ if (prop.name !== name) {
81
+ continue;
82
+ }
83
+
84
+ if (prop.type === "page") {
85
+ return pages.get(prop.value);
86
+ }
87
+
88
+ if (prop.type === "string") {
89
+ for (const page of pages.values()) {
90
+ if (page.path === prop.value) {
91
+ return page;
92
+ }
93
+ }
94
+ return prop.value;
95
+ }
96
+
97
+ return;
98
+ }
99
+ };
100
+
101
+ // this utility is used for link component in both builder and preview
102
+ // so need to optimize rerenders with computed
103
+ export const usePropUrl = (instanceId: Instance["id"], name: string) => {
104
+ const { propsByInstanceIdStore, pagesStore } = useContext(ReactSdkContext);
105
+ const pageStore = useMemo(
106
+ () =>
107
+ computed(
108
+ [propsByInstanceIdStore, pagesStore],
109
+ (propsByInstanceId, pages) =>
110
+ resolveUrlProp(instanceId, name, propsByInstanceId, pages)
111
+ ),
112
+ [propsByInstanceIdStore, pagesStore, instanceId, name]
113
+ );
114
+ return useStore(pageStore);
115
+ };
116
+
117
+ export const getInstanceIdFromComponentProps = (
118
+ props: Record<string, unknown>
119
+ ) => {
120
+ return props[idAttribute] as string;
121
+ };
@@ -4,15 +4,18 @@ import { Scripts, ScrollRestoration } from "@remix-run/react";
4
4
  import type { Instance } from "@webstudio-is/project-build";
5
5
  import type { GetComponent } from "../components/components-utils";
6
6
  import { ReactSdkContext } from "../context";
7
- import type { Assets, PropsByInstanceId } from "../props";
7
+ import type { Assets, Pages, PropsByInstanceId } from "../props";
8
8
  import type { WebstudioComponent } from "./webstudio-component";
9
9
  import { SessionStoragePolyfill } from "./session-storage-polyfill";
10
10
 
11
+ type InstanceSelector = Instance["id"][];
12
+
11
13
  export const createElementsTree = ({
12
14
  sandbox,
13
15
  instance,
14
16
  propsByInstanceIdStore,
15
17
  assetsStore,
18
+ pagesStore,
16
19
  Component,
17
20
  getComponent,
18
21
  }: {
@@ -20,17 +23,21 @@ export const createElementsTree = ({
20
23
  instance: Instance;
21
24
  propsByInstanceIdStore: ReadableAtom<PropsByInstanceId>;
22
25
  assetsStore: ReadableAtom<Assets>;
26
+ pagesStore: ReadableAtom<Pages>;
23
27
  Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
24
28
  getComponent: GetComponent;
25
29
  }) => {
30
+ const rootInstanceSelector = [instance.id];
26
31
  const children = createInstanceChildrenElements({
32
+ instanceSelector: rootInstanceSelector,
27
33
  Component,
28
34
  children: instance.children,
29
35
  getComponent,
30
36
  });
31
- const body = createInstanceElement({
37
+ const root = createInstanceElement({
32
38
  Component,
33
39
  instance,
40
+ instanceSelector: rootInstanceSelector,
34
41
  children: [
35
42
  <Fragment key="children">
36
43
  {children}
@@ -42,17 +49,21 @@ export const createElementsTree = ({
42
49
  getComponent,
43
50
  });
44
51
  return (
45
- <ReactSdkContext.Provider value={{ propsByInstanceIdStore, assetsStore }}>
46
- {body}
52
+ <ReactSdkContext.Provider
53
+ value={{ propsByInstanceIdStore, assetsStore, pagesStore }}
54
+ >
55
+ {root}
47
56
  </ReactSdkContext.Provider>
48
57
  );
49
58
  };
50
59
 
51
60
  const createInstanceChildrenElements = ({
61
+ instanceSelector,
52
62
  children,
53
63
  Component,
54
64
  getComponent,
55
65
  }: {
66
+ instanceSelector: InstanceSelector;
56
67
  children: Instance["children"];
57
68
  Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
58
69
  getComponent: GetComponent;
@@ -63,13 +74,16 @@ const createInstanceChildrenElements = ({
63
74
  elements.push(child.value);
64
75
  continue;
65
76
  }
77
+ const childInstanceSelector = [child.id, ...instanceSelector];
66
78
  const children = createInstanceChildrenElements({
79
+ instanceSelector: childInstanceSelector,
67
80
  children: child.children,
68
81
  Component,
69
82
  getComponent,
70
83
  });
71
84
  const element = createInstanceElement({
72
85
  instance: child,
86
+ instanceSelector: childInstanceSelector,
73
87
  Component,
74
88
  children,
75
89
  getComponent,
@@ -82,20 +96,24 @@ const createInstanceChildrenElements = ({
82
96
  const createInstanceElement = ({
83
97
  Component,
84
98
  instance,
99
+ instanceSelector,
85
100
  children = [],
86
101
  getComponent,
87
102
  }: {
88
103
  instance: Instance;
104
+ instanceSelector: InstanceSelector;
89
105
  Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
90
106
  children?: Array<JSX.Element | string>;
91
107
  getComponent: GetComponent;
92
108
  }) => {
93
- const props = {
94
- instance,
95
- children,
96
- key: instance.id,
97
- getComponent,
98
- };
99
-
100
- return <Component {...props} />;
109
+ return (
110
+ <Component
111
+ key={instance.id}
112
+ instance={instance}
113
+ instanceSelector={instanceSelector}
114
+ getComponent={getComponent}
115
+ >
116
+ {children}
117
+ </Component>
118
+ );
101
119
  };
package/src/tree/root.ts CHANGED
@@ -18,6 +18,7 @@ import type { GetComponent } from "../components/components-utils";
18
18
 
19
19
  export type Data = {
20
20
  page: Page;
21
+ pages: Array<Page>;
21
22
  build: Build;
22
23
  assets: Array<Asset>;
23
24
  params?: Params;
@@ -86,6 +87,7 @@ export const InstanceRoot = ({
86
87
  getPropsByInstanceId(new Map(data.build.props))
87
88
  ),
88
89
  assetsStore: atom(new Map(data.assets.map((asset) => [asset.id, asset]))),
90
+ pagesStore: atom(new Map(data.pages.map((page) => [page.id, page]))),
89
91
  Component: Component ?? WebstudioComponent,
90
92
  getComponent,
91
93
  });
@@ -28,6 +28,7 @@ export const renderWebstudioComponentChildren = (
28
28
 
29
29
  type WebstudioComponentProps = {
30
30
  instance: Instance;
31
+ instanceSelector: Instance["id"][];
31
32
  children: Array<JSX.Element | string>;
32
33
  getComponent: GetComponent;
33
34
  };
@@ -1,20 +0,0 @@
1
- import { z } from "zod";
2
- import { PropMeta } from "@webstudio-is/generate-arg-types";
3
- const WsComponentPropsMeta = z.object({
4
- props: z.record(PropMeta),
5
- initialProps: z.array(z.string()).optional()
6
- });
7
- const WsComponentMeta = z.object({
8
- type: z.enum([
9
- "body",
10
- "container",
11
- "control",
12
- "embed",
13
- "rich-text",
14
- "rich-text-child"
15
- ]),
16
- label: z.string(),
17
- Icon: z.any(),
18
- presetStyle: z.optional(z.any()),
19
- children: z.optional(z.array(z.string()))
20
- });