@webstudio-is/react-sdk 0.83.0 → 0.85.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 (54) hide show
  1. package/lib/cjs/component-renderer.js +125 -0
  2. package/lib/cjs/components/component-meta.js +5 -0
  3. package/lib/cjs/context.js +2 -1
  4. package/lib/cjs/css/style-rules.js +1 -1
  5. package/lib/cjs/embed-template.js +101 -55
  6. package/lib/cjs/hook.js +34 -0
  7. package/lib/cjs/index.js +6 -0
  8. package/lib/cjs/instance-utils.js +65 -0
  9. package/lib/cjs/props.js +12 -2
  10. package/lib/cjs/tree/create-elements-tree.js +5 -4
  11. package/lib/cjs/tree/root.js +6 -2
  12. package/lib/cjs/tree/webstudio-component.js +2 -0
  13. package/lib/component-renderer.js +111 -0
  14. package/lib/components/component-meta.js +5 -0
  15. package/lib/context.js +2 -1
  16. package/lib/css/style-rules.js +1 -1
  17. package/lib/embed-template.js +101 -55
  18. package/lib/hook.js +14 -0
  19. package/lib/index.js +8 -1
  20. package/lib/instance-utils.js +45 -0
  21. package/lib/props.js +13 -3
  22. package/lib/tree/create-elements-tree.js +5 -4
  23. package/lib/tree/root.js +6 -2
  24. package/lib/tree/webstudio-component.js +2 -0
  25. package/lib/types/component-renderer.d.ts +8 -0
  26. package/lib/types/components/component-meta.d.ts +9 -6
  27. package/lib/types/context.d.ts +2 -0
  28. package/lib/types/css/css.d.ts +19 -19
  29. package/lib/types/css/global-rules.d.ts +19 -19
  30. package/lib/types/css/normalize.d.ts +47 -47
  31. package/lib/types/embed-template.d.ts +291 -181
  32. package/lib/types/hook.d.ts +31 -0
  33. package/lib/types/index.d.ts +4 -1
  34. package/lib/types/instance-utils.d.ts +16 -0
  35. package/lib/types/instance-utils.test.d.ts +1 -0
  36. package/lib/types/props.d.ts +47 -46
  37. package/lib/types/tree/create-elements-tree.d.ts +5 -2
  38. package/lib/types/tree/root.d.ts +5 -2
  39. package/lib/types/tree/webstudio-component.d.ts +1 -0
  40. package/package.json +11 -11
  41. package/src/component-renderer.tsx +117 -0
  42. package/src/components/component-meta.ts +5 -0
  43. package/src/context.tsx +3 -0
  44. package/src/css/style-rules.ts +1 -1
  45. package/src/embed-template.test.ts +81 -70
  46. package/src/embed-template.ts +116 -56
  47. package/src/hook.ts +42 -0
  48. package/src/index.ts +4 -0
  49. package/src/instance-utils.test.ts +89 -0
  50. package/src/instance-utils.ts +65 -0
  51. package/src/props.ts +13 -1
  52. package/src/tree/create-elements-tree.tsx +8 -3
  53. package/src/tree/root.ts +8 -0
  54. package/src/tree/webstudio-component.tsx +1 -0
@@ -0,0 +1,31 @@
1
+ import type { Instance, Prop } from "@webstudio-is/project-build";
2
+ /**
3
+ * Hooks are subscriptions to builder events
4
+ * with limited way to interact with it.
5
+ * Called independently from components.
6
+ */
7
+ export type HookContext = {
8
+ setPropVariable: (instanceId: Instance["id"], propName: Prop["name"], value: unknown) => void;
9
+ };
10
+ export type InstanceSelection = Instance[];
11
+ type NavigatorEvent = {
12
+ instanceSelection: InstanceSelection;
13
+ };
14
+ export type Hook = {
15
+ onNavigatorSelect?: (context: HookContext, event: NavigatorEvent) => void;
16
+ onNavigatorUnselect?: (context: HookContext, event: NavigatorEvent) => void;
17
+ };
18
+ export declare const getClosestInstance: (instanceSelection: InstanceSelection, currentInstance: Instance, closestComponent: Instance["component"]) => {
19
+ type: "instance";
20
+ id: string;
21
+ component: string;
22
+ children: ({
23
+ type: "text";
24
+ value: string;
25
+ } | {
26
+ type: "id";
27
+ value: string;
28
+ })[];
29
+ label?: string | undefined;
30
+ } | undefined;
31
+ export {};
@@ -5,6 +5,9 @@ export * from "./app";
5
5
  export * from "./components/components-utils";
6
6
  export { type WsComponentPropsMeta, type WsComponentMeta, type ComponentState, type PresetStyle, componentCategories, stateCategories, defaultStates, } from "./components/component-meta";
7
7
  export * from "./embed-template";
8
- export { useInstanceProps, usePropUrl, usePropAsset, getInstanceIdFromComponentProps, } from "./props";
8
+ export { useInstanceProps, usePropUrl, usePropAsset, getInstanceIdFromComponentProps, getIndexWithinAncestorFromComponentProps, } from "./props";
9
9
  export { type Params, ReactSdkContext } from "./context";
10
10
  export { validateExpression, generateComputingExpressions, executeComputingExpressions, generateEffectfulExpression, executeEffectfulExpression, computeExpressionsDependencies, encodeDataSourceVariable, encodeVariablesMap, decodeDataSourceVariable, decodeVariablesMap, } from "./expression";
11
+ export { renderComponentTemplate } from "./component-renderer";
12
+ export { getIndexesWithinAncestors } from "./instance-utils";
13
+ export * from "./hook";
@@ -0,0 +1,16 @@
1
+ import type { Instance } from "@webstudio-is/project-build";
2
+ import type { WsComponentMeta } from "./components/component-meta";
3
+ export type IndexesWithinAncestors = Map<Instance["id"], number>;
4
+ export declare const getIndexesWithinAncestors: (metas: Map<Instance["component"], WsComponentMeta>, instances: Map<string, {
5
+ type: "instance";
6
+ id: string;
7
+ component: string;
8
+ children: ({
9
+ type: "text";
10
+ value: string;
11
+ } | {
12
+ type: "id";
13
+ value: string;
14
+ })[];
15
+ label?: string | undefined;
16
+ }>, rootIds: Instance["id"][]) => IndexesWithinAncestors;
@@ -0,0 +1 @@
1
+ export {};
@@ -3,35 +3,34 @@ import type { Asset, Assets } from "@webstudio-is/asset-uploader";
3
3
  export type PropsByInstanceId = Map<Instance["id"], Prop[]>;
4
4
  export type Pages = Map<Page["id"], Page>;
5
5
  export declare const getPropsByInstanceId: (props: Map<string, {
6
- name: string;
7
6
  type: "number";
8
7
  value: number;
9
8
  id: string;
9
+ name: string;
10
10
  instanceId: string;
11
11
  required?: boolean | undefined;
12
12
  } | {
13
- name: string;
14
13
  type: "string";
15
14
  value: string;
16
15
  id: string;
16
+ name: string;
17
17
  instanceId: string;
18
18
  required?: boolean | undefined;
19
19
  } | {
20
- name: string;
21
20
  type: "boolean";
22
21
  value: boolean;
23
22
  id: string;
23
+ name: string;
24
24
  instanceId: string;
25
25
  required?: boolean | undefined;
26
26
  } | {
27
- name: string;
28
27
  type: "asset";
29
28
  value: string;
30
29
  id: string;
30
+ name: string;
31
31
  instanceId: string;
32
32
  required?: boolean | undefined;
33
33
  } | {
34
- name: string;
35
34
  type: "page";
36
35
  value: (string | {
37
36
  instanceId: string;
@@ -41,24 +40,24 @@ export declare const getPropsByInstanceId: (props: Map<string, {
41
40
  pageId: string;
42
41
  } | undefined);
43
42
  id: string;
43
+ name: string;
44
44
  instanceId: string;
45
45
  required?: boolean | undefined;
46
46
  } | {
47
- name: string;
48
47
  type: "string[]";
49
48
  value: string[];
50
49
  id: string;
50
+ name: string;
51
51
  instanceId: string;
52
52
  required?: boolean | undefined;
53
53
  } | {
54
- name: string;
55
54
  type: "dataSource";
56
55
  value: string;
57
56
  id: string;
57
+ name: string;
58
58
  instanceId: string;
59
59
  required?: boolean | undefined;
60
60
  } | {
61
- name: string;
62
61
  type: "action";
63
62
  value: {
64
63
  code: string;
@@ -66,108 +65,109 @@ export declare const getPropsByInstanceId: (props: Map<string, {
66
65
  args: string[];
67
66
  }[];
68
67
  id: string;
68
+ name: string;
69
69
  instanceId: string;
70
70
  required?: boolean | undefined;
71
71
  }>) => PropsByInstanceId;
72
72
  export declare const useInstanceProps: (instanceId: Instance["id"]) => Record<string, unknown>;
73
73
  export declare const usePropAsset: (instanceId: Instance["id"], name: string) => {
74
- name: string;
75
74
  type: "font";
76
- format: "ttf" | "woff" | "woff2" | "otf";
77
75
  id: string;
78
- projectId: string;
79
- size: number;
80
- description: string | null;
81
- createdAt: string;
76
+ name: string;
82
77
  meta: ({
83
- style: "normal" | "italic" | "oblique";
84
78
  weight: number;
79
+ style: "normal" | "italic" | "oblique";
85
80
  family: string;
86
81
  } | {
87
82
  variationAxes: Partial<Record<"wght" | "wdth" | "slnt" | "opsz" | "ital" | "GRAD" | "XTRA" | "XOPQ" | "YOPQ" | "YTLC" | "YTUC" | "YTAS" | "YTDE" | "YTFI", {
88
- name: string;
89
83
  min: number;
90
- default: number;
91
84
  max: number;
85
+ name: string;
86
+ default: number;
92
87
  }>>;
93
88
  family: string;
94
89
  }) & ({
95
- style: "normal" | "italic" | "oblique";
96
90
  weight: number;
91
+ style: "normal" | "italic" | "oblique";
97
92
  family: string;
98
93
  } | {
99
94
  variationAxes: Partial<Record<"wght" | "wdth" | "slnt" | "opsz" | "ital" | "GRAD" | "XTRA" | "XOPQ" | "YOPQ" | "YTLC" | "YTUC" | "YTAS" | "YTDE" | "YTFI", {
100
- name: string;
101
95
  min: number;
102
- default: number;
103
96
  max: number;
97
+ name: string;
98
+ default: number;
104
99
  }>>;
105
100
  family: string;
106
101
  } | undefined);
107
- } | {
108
- name: string;
109
- type: "image";
110
- format: string;
111
- id: string;
112
- projectId: string;
113
102
  size: number;
114
103
  description: string | null;
104
+ format: "ttf" | "woff" | "woff2" | "otf";
105
+ projectId: string;
115
106
  createdAt: string;
107
+ } | {
108
+ type: "image";
109
+ id: string;
110
+ name: string;
116
111
  meta: {
117
- width: number;
118
112
  height: number;
113
+ width: number;
119
114
  };
115
+ size: number;
116
+ description: string | null;
117
+ format: string;
118
+ projectId: string;
119
+ createdAt: string;
120
120
  } | undefined;
121
121
  export declare const resolveUrlProp: (instanceId: Instance["id"], name: string, { props, pages, assets, }: {
122
122
  props: PropsByInstanceId;
123
123
  pages: Pages;
124
124
  assets: Map<string, {
125
- name: string;
126
125
  type: "font";
127
- format: "ttf" | "woff" | "woff2" | "otf";
128
126
  id: string;
129
- projectId: string;
130
- size: number;
131
- description: string | null;
132
- createdAt: string;
127
+ name: string;
133
128
  meta: ({
134
- style: "normal" | "italic" | "oblique";
135
129
  weight: number;
130
+ style: "normal" | "italic" | "oblique";
136
131
  family: string;
137
132
  } | {
138
133
  variationAxes: Partial<Record<"wght" | "wdth" | "slnt" | "opsz" | "ital" | "GRAD" | "XTRA" | "XOPQ" | "YOPQ" | "YTLC" | "YTUC" | "YTAS" | "YTDE" | "YTFI", {
139
- name: string;
140
134
  min: number;
141
- default: number;
142
135
  max: number;
136
+ name: string;
137
+ default: number;
143
138
  }>>;
144
139
  family: string;
145
140
  }) & ({
146
- style: "normal" | "italic" | "oblique";
147
141
  weight: number;
142
+ style: "normal" | "italic" | "oblique";
148
143
  family: string;
149
144
  } | {
150
145
  variationAxes: Partial<Record<"wght" | "wdth" | "slnt" | "opsz" | "ital" | "GRAD" | "XTRA" | "XOPQ" | "YOPQ" | "YTLC" | "YTUC" | "YTAS" | "YTDE" | "YTFI", {
151
- name: string;
152
146
  min: number;
153
- default: number;
154
147
  max: number;
148
+ name: string;
149
+ default: number;
155
150
  }>>;
156
151
  family: string;
157
152
  } | undefined);
158
- } | {
159
- name: string;
160
- type: "image";
161
- format: string;
162
- id: string;
163
- projectId: string;
164
153
  size: number;
165
154
  description: string | null;
155
+ format: "ttf" | "woff" | "woff2" | "otf";
156
+ projectId: string;
166
157
  createdAt: string;
158
+ } | {
159
+ type: "image";
160
+ id: string;
161
+ name: string;
167
162
  meta: {
168
- width: number;
169
163
  height: number;
164
+ width: number;
170
165
  };
166
+ size: number;
167
+ description: string | null;
168
+ format: string;
169
+ projectId: string;
170
+ createdAt: string;
171
171
  } | undefined>;
172
172
  }) => {
173
173
  type: "page";
@@ -194,3 +194,4 @@ export declare const usePropUrl: (instanceId: Instance["id"], name: string) => {
194
194
  url: string;
195
195
  } | undefined;
196
196
  export declare const getInstanceIdFromComponentProps: (props: Record<string, unknown>) => string;
197
+ export declare const getIndexWithinAncestorFromComponentProps: (props: Record<string, unknown>) => string | undefined;
@@ -1,4 +1,4 @@
1
- import { type ForwardRefExoticComponent, type RefAttributes } from "react";
1
+ import { type ForwardRefExoticComponent, type RefAttributes, type ReactNode } from "react";
2
2
  import type { ReadableAtom } from "nanostores";
3
3
  import type { Assets } from "@webstudio-is/asset-uploader";
4
4
  import type { Instance, Instances } from "@webstudio-is/project-build";
@@ -6,7 +6,8 @@ import type { Components } from "../components/components-utils";
6
6
  import { type Params, type DataSourceValues } from "../context";
7
7
  import type { Pages, PropsByInstanceId } from "../props";
8
8
  import type { WebstudioComponentProps } from "./webstudio-component";
9
- export declare const createElementsTree: ({ renderer, imageBaseUrl, assetBaseUrl, instances, rootInstanceId, propsByInstanceIdStore, assetsStore, pagesStore, dataSourceValuesStore, executeEffectfulExpression, onDataSourceUpdate, Component, components, }: Params & {
9
+ import type { IndexesWithinAncestors } from "../instance-utils";
10
+ export declare const createElementsTree: ({ renderer, imageBaseUrl, assetBaseUrl, instances, rootInstanceId, propsByInstanceIdStore, assetsStore, pagesStore, dataSourceValuesStore, executeEffectfulExpression, onDataSourceUpdate, indexesWithinAncestors, Component, components, scripts, }: Params & {
10
11
  instances: Map<string, {
11
12
  type: "instance";
12
13
  id: string;
@@ -27,6 +28,8 @@ export declare const createElementsTree: ({ renderer, imageBaseUrl, assetBaseUrl
27
28
  executeEffectfulExpression: (expression: string, args: DataSourceValues, values: DataSourceValues) => DataSourceValues;
28
29
  dataSourceValuesStore: ReadableAtom<DataSourceValues>;
29
30
  onDataSourceUpdate: (newValues: DataSourceValues) => void;
31
+ indexesWithinAncestors: IndexesWithinAncestors;
30
32
  Component: ForwardRefExoticComponent<WebstudioComponentProps & RefAttributes<HTMLElement>>;
31
33
  components: Components;
34
+ scripts?: ReactNode;
32
35
  }) => import("react/jsx-runtime").JSX.Element | null;
@@ -1,9 +1,10 @@
1
- import { type ForwardRefExoticComponent, type RefAttributes } from "react";
1
+ import { type ForwardRefExoticComponent, type RefAttributes, type ReactNode } from "react";
2
2
  import { type Build, type Page } from "@webstudio-is/project-build";
3
3
  import type { Asset } from "@webstudio-is/asset-uploader";
4
4
  import { type WebstudioComponentProps } from "./webstudio-component";
5
5
  import type { Components } from "../components/components-utils";
6
6
  import type { Params, DataSourceValues } from "../context";
7
+ import type { IndexesWithinAncestors } from "../instance-utils";
7
8
  export type Data = {
8
9
  page: Page;
9
10
  pages: Array<Page>;
@@ -16,10 +17,12 @@ export type RootPropsData = Omit<Data, "build"> & {
16
17
  };
17
18
  type RootProps = {
18
19
  data: RootPropsData;
20
+ indexesWithinAncestors: IndexesWithinAncestors;
19
21
  executeComputingExpressions: (values: DataSourceValues) => DataSourceValues;
20
22
  executeEffectfulExpression: (expression: string, args: DataSourceValues, values: DataSourceValues) => DataSourceValues;
21
23
  Component?: ForwardRefExoticComponent<WebstudioComponentProps & RefAttributes<HTMLElement>>;
22
24
  components: Components;
25
+ scripts?: ReactNode;
23
26
  };
24
- export declare const InstanceRoot: ({ data, executeComputingExpressions, executeEffectfulExpression, Component, components, }: RootProps) => JSX.Element | null;
27
+ export declare const InstanceRoot: ({ data, indexesWithinAncestors, executeComputingExpressions, executeEffectfulExpression, Component, components, scripts, }: RootProps) => JSX.Element | null;
25
28
  export {};
@@ -13,6 +13,7 @@ export declare const idAttribute: "data-ws-id";
13
13
  export declare const selectorIdAttribute: "data-ws-selector";
14
14
  export declare const componentAttribute: "data-ws-component";
15
15
  export declare const showAttribute: "data-ws-show";
16
+ export declare const indexAttribute: "data-ws-index";
16
17
  export declare const collapsedAttribute: "data-ws-collapsed";
17
18
  export type WebstudioAttributes = {
18
19
  [idAttribute]?: string | undefined;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@webstudio-is/react-sdk",
3
- "version": "0.83.0",
3
+ "version": "0.85.0",
4
4
  "description": "Webstudio JavaScript / TypeScript API",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
7
7
  "type": "module",
8
8
  "devDependencies": {
9
9
  "@jest/globals": "^29.6.2",
10
- "@remix-run/react": "^1.18.1",
10
+ "@remix-run/react": "^1.19.2",
11
11
  "@types/react": "^18.2.16",
12
12
  "@types/react-dom": "^18.2.7",
13
13
  "jest": "^29.6.2",
@@ -16,12 +16,12 @@
16
16
  "type-fest": "^3.7.1",
17
17
  "typescript": "5.1.6",
18
18
  "zod": "^3.21.4",
19
- "@webstudio-is/jest-config": "^1.0.6",
19
+ "@webstudio-is/jest-config": "^1.0.7",
20
20
  "@webstudio-is/scripts": "^0.0.0",
21
- "@webstudio-is/tsconfig": "^1.0.6"
21
+ "@webstudio-is/tsconfig": "^1.0.7"
22
22
  },
23
23
  "peerDependencies": {
24
- "@remix-run/react": "^1.18.0",
24
+ "@remix-run/react": "^1.19.1",
25
25
  "react": "^18.2.0",
26
26
  "react-dom": "^18.2.0",
27
27
  "zod": "^3.19.1"
@@ -34,12 +34,12 @@
34
34
  "nanoevents": "^8.0.0",
35
35
  "nanoid": "^4.0.2",
36
36
  "nanostores": "^0.9.3",
37
- "@webstudio-is/asset-uploader": "^0.83.0",
38
- "@webstudio-is/css-data": "^0.83.0",
39
- "@webstudio-is/css-engine": "^0.83.0",
40
- "@webstudio-is/fonts": "^0.83.0",
41
- "@webstudio-is/generate-arg-types": "^0.83.0",
42
- "@webstudio-is/project-build": "^0.83.0"
37
+ "@webstudio-is/asset-uploader": "^0.85.0",
38
+ "@webstudio-is/css-data": "^0.85.0",
39
+ "@webstudio-is/css-engine": "^0.85.0",
40
+ "@webstudio-is/fonts": "^0.85.0",
41
+ "@webstudio-is/generate-arg-types": "^0.85.0",
42
+ "@webstudio-is/project-build": "^0.85.0"
43
43
  },
44
44
  "exports": {
45
45
  ".": {
@@ -0,0 +1,117 @@
1
+ import type { ExoticComponent } from "react";
2
+ import type { Instance } from "@webstudio-is/project-build";
3
+ import { getStyleDeclKey } from "@webstudio-is/project-build";
4
+ import type { WsComponentMeta } from "./components/component-meta";
5
+ import { generateDataFromEmbedTemplate } from "./embed-template";
6
+ import { generateCssText } from "./css";
7
+ import { InstanceRoot, WebstudioComponent } from "./tree";
8
+ import {
9
+ decodeVariablesMap,
10
+ encodeDataSourceVariable,
11
+ encodeVariablesMap,
12
+ executeComputingExpressions,
13
+ executeEffectfulExpression,
14
+ } from "./expression";
15
+ import { getIndexesWithinAncestors } from "./instance-utils";
16
+
17
+ export const renderComponentTemplate = ({
18
+ name,
19
+ metas: metasRecord,
20
+ components,
21
+ }: {
22
+ name: Instance["component"];
23
+ metas: Record<string, WsComponentMeta>;
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ components: Record<string, ExoticComponent<any>>;
26
+ }) => {
27
+ const metas = new Map(Object.entries(metasRecord));
28
+ const data = generateDataFromEmbedTemplate(
29
+ metas.get(name)?.template ?? [
30
+ {
31
+ type: "instance",
32
+ component: name,
33
+ children: [],
34
+ },
35
+ ],
36
+ "base"
37
+ );
38
+ const instances: [Instance["id"], Instance][] = [
39
+ [
40
+ "root",
41
+ {
42
+ type: "instance",
43
+ id: "root",
44
+ component: "Box",
45
+ children: data.children,
46
+ },
47
+ ],
48
+ ...data.instances.map(
49
+ (instance) => [instance.id, instance] satisfies [Instance["id"], Instance]
50
+ ),
51
+ ];
52
+ return (
53
+ <>
54
+ <style>
55
+ {generateCssText(
56
+ {
57
+ assets: [],
58
+ breakpoints: [["base", { id: "base", label: "base" }]],
59
+ styles: data.styles.map((item) => [getStyleDeclKey(item), item]),
60
+ styleSourceSelections: data.styleSourceSelections.map((item) => [
61
+ item.instanceId,
62
+ item,
63
+ ]),
64
+ componentMetas: metas,
65
+ },
66
+ { assetBaseUrl: "/" }
67
+ )}
68
+ </style>
69
+ <InstanceRoot
70
+ data={{
71
+ page: {
72
+ path: "",
73
+ id: "",
74
+ name: "",
75
+ title: "",
76
+ meta: {},
77
+ rootInstanceId: "root",
78
+ },
79
+ pages: [],
80
+ assets: [],
81
+ build: {
82
+ instances,
83
+ props: data.props.map((prop) => [prop.id, prop]),
84
+ dataSources: data.dataSources.map((dataSource) => [
85
+ dataSource.id,
86
+ dataSource,
87
+ ]),
88
+ },
89
+ }}
90
+ executeComputingExpressions={(values) => {
91
+ const expressions = new Map<string, string>();
92
+ for (const dataSource of data.dataSources) {
93
+ const name = encodeDataSourceVariable(dataSource.id);
94
+ if (dataSource.type === "expression") {
95
+ expressions.set(name, dataSource.code);
96
+ }
97
+ }
98
+ return decodeVariablesMap(
99
+ executeComputingExpressions(expressions, encodeVariablesMap(values))
100
+ );
101
+ }}
102
+ executeEffectfulExpression={(code, args, values) => {
103
+ return decodeVariablesMap(
104
+ executeEffectfulExpression(code, args, encodeVariablesMap(values))
105
+ );
106
+ }}
107
+ Component={WebstudioComponent}
108
+ components={new Map(Object.entries(components))}
109
+ indexesWithinAncestors={getIndexesWithinAncestors(
110
+ metas,
111
+ new Map(instances),
112
+ ["root"]
113
+ )}
114
+ />
115
+ </>
116
+ );
117
+ };
@@ -53,6 +53,11 @@ const WsComponentMeta = z.object({
53
53
  type: z.enum(["container", "control", "embed", "rich-text-child"]),
54
54
  requiredAncestors: z.optional(z.array(z.string())),
55
55
  invalidAncestors: z.optional(z.array(z.string())),
56
+ // when this field is specified component receives
57
+ // prop with index of same components withiin specified ancestor
58
+ // important to automatically enumerate collections without
59
+ // naming every item manually
60
+ indexWithinAncestor: z.optional(z.string()),
56
61
  stylable: z.optional(z.boolean()),
57
62
  // specifies whether the instance can be deleted,
58
63
  // copied or dragged out of its parent instance
package/src/context.tsx CHANGED
@@ -3,6 +3,7 @@ import { createContext } from "react";
3
3
  import type { Assets } from "@webstudio-is/asset-uploader";
4
4
  import type { DataSource, Instance, Prop } from "@webstudio-is/project-build";
5
5
  import type { Pages, PropsByInstanceId } from "./props";
6
+ import type { IndexesWithinAncestors } from "./instance-utils";
6
7
 
7
8
  export type Params = {
8
9
  renderer?: "canvas" | "preview";
@@ -50,6 +51,7 @@ export const ReactSdkContext = createContext<
50
51
  prop: Prop["name"],
51
52
  value: unknown
52
53
  ) => void;
54
+ indexesWithinAncestors: IndexesWithinAncestors;
53
55
  }
54
56
  >({
55
57
  imageBaseUrl: "/",
@@ -67,4 +69,5 @@ export const ReactSdkContext = createContext<
67
69
  setBoundDataSourceValue: () => {
68
70
  throw Error("React SDK setBoundDataSourceValue is not implemented");
69
71
  },
72
+ indexesWithinAncestors: new Map(),
70
73
  });
@@ -86,7 +86,7 @@ export const getPresetStyleRules = (
86
86
  const presetStyleRules = new Map<string, Style>();
87
87
  for (const [tag, styles] of Object.entries(presetStyle)) {
88
88
  for (const styleDecl of styles) {
89
- const selector = `${tag}:where([${componentAttribute}=${component}])${
89
+ const selector = `${tag}:where([${componentAttribute}="${component}"])${
90
90
  styleDecl.state ?? ""
91
91
  }`;
92
92
  let rule = presetStyleRules.get(selector);