@webstudio-is/react-sdk 0.74.0 → 0.76.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 (47) hide show
  1. package/lib/cjs/components/component-meta.js +1 -0
  2. package/lib/cjs/context.js +5 -1
  3. package/lib/cjs/css/global-rules.js +1 -1
  4. package/lib/cjs/css/normalize.js +10 -13
  5. package/lib/cjs/embed-template.js +68 -3
  6. package/lib/cjs/expression.js +191 -0
  7. package/lib/cjs/index.js +7 -1
  8. package/lib/cjs/props.js +28 -10
  9. package/lib/cjs/tree/create-elements-tree.js +14 -1
  10. package/lib/cjs/tree/root.js +55 -0
  11. package/lib/cjs/tree/webstudio-component.js +9 -2
  12. package/lib/components/component-meta.js +1 -0
  13. package/lib/context.js +5 -1
  14. package/lib/css/global-rules.js +1 -1
  15. package/lib/css/normalize.js +10 -13
  16. package/lib/embed-template.js +68 -3
  17. package/lib/expression.js +161 -0
  18. package/lib/index.js +13 -1
  19. package/lib/props.js +28 -10
  20. package/lib/tree/create-elements-tree.js +14 -1
  21. package/lib/tree/root.js +63 -1
  22. package/lib/tree/webstudio-component.js +9 -2
  23. package/lib/types/components/component-meta.d.ts +115 -0
  24. package/lib/types/context.d.ts +3 -0
  25. package/lib/types/css/normalize.d.ts +1316 -0
  26. package/lib/types/embed-template.d.ts +512 -0
  27. package/lib/types/expression.d.ts +6 -0
  28. package/lib/types/expression.test.d.ts +1 -0
  29. package/lib/types/index.d.ts +2 -1
  30. package/lib/types/props.d.ts +8 -7
  31. package/lib/types/tree/create-elements-tree.d.ts +4 -2
  32. package/lib/types/tree/root.d.ts +3 -3
  33. package/lib/types/tree/webstudio-component.d.ts +1 -0
  34. package/package.json +15 -15
  35. package/src/components/component-meta.ts +1 -0
  36. package/src/context.tsx +11 -0
  37. package/src/css/global-rules.ts +2 -1
  38. package/src/css/normalize.ts +10 -13
  39. package/src/embed-template.test.ts +177 -1
  40. package/src/embed-template.ts +73 -2
  41. package/src/expression.test.ts +122 -0
  42. package/src/expression.ts +183 -0
  43. package/src/index.ts +7 -0
  44. package/src/props.ts +29 -10
  45. package/src/tree/create-elements-tree.tsx +20 -1
  46. package/src/tree/root.ts +81 -4
  47. package/src/tree/webstudio-component.tsx +7 -1
@@ -0,0 +1,6 @@
1
+ type TransformIdentifier = (id: string) => string;
2
+ export declare const validateExpression: (code: string, transformIdentifier?: TransformIdentifier) => string;
3
+ export declare const executeExpressions: (variables: Map<string, unknown>, expressions: Map<string, string>) => Map<string, unknown>;
4
+ export declare const encodeDataSourceVariable: (id: string) => string;
5
+ export declare const decodeDataSourceVariable: (name: string) => string | undefined;
6
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -5,5 +5,6 @@ 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 { usePropUrl, usePropAsset, getInstanceIdFromComponentProps, } from "./props";
8
+ export { useInstanceProps, usePropUrl, usePropAsset, getInstanceIdFromComponentProps, } from "./props";
9
9
  export { type Params, ReactSdkContext } from "./context";
10
+ export { validateExpression, executeExpressions, encodeDataSourceVariable, decodeDataSourceVariable, } from "./expression";
@@ -50,14 +50,15 @@ export declare const getPropsByInstanceId: (props: Map<string, {
50
50
  id: string;
51
51
  instanceId: string;
52
52
  required?: boolean | undefined;
53
- }>) => PropsByInstanceId;
54
- export declare const useInstanceProps: (instanceId: Instance["id"]) => Record<string, number | boolean | string[] | ((string | {
55
- instanceId: string;
56
- pageId: string;
57
- }) & (string | {
53
+ } | {
54
+ name: string;
55
+ type: "dataSource";
56
+ value: string;
57
+ id: string;
58
58
  instanceId: string;
59
- pageId: string;
60
- } | undefined))>;
59
+ required?: boolean | undefined;
60
+ }>) => PropsByInstanceId;
61
+ export declare const useInstanceProps: (instanceId: Instance["id"]) => Record<string, unknown>;
61
62
  export declare const usePropAsset: (instanceId: Instance["id"], name: string) => {
62
63
  name: string;
63
64
  type: "font";
@@ -1,12 +1,12 @@
1
1
  import { type ComponentProps } from "react";
2
2
  import type { ReadableAtom } from "nanostores";
3
3
  import type { Assets } from "@webstudio-is/asset-uploader";
4
- import type { Instance, Instances } from "@webstudio-is/project-build";
4
+ import type { DataSource, Instance, Instances } from "@webstudio-is/project-build";
5
5
  import type { Components } from "../components/components-utils";
6
6
  import { type Params } from "../context";
7
7
  import type { Pages, PropsByInstanceId } from "../props";
8
8
  import type { WebstudioComponent } from "./webstudio-component";
9
- export declare const createElementsTree: ({ renderer, imageBaseUrl, assetBaseUrl, instances, rootInstanceId, propsByInstanceIdStore, assetsStore, pagesStore, Component, components, }: Params & {
9
+ export declare const createElementsTree: ({ renderer, imageBaseUrl, assetBaseUrl, instances, rootInstanceId, propsByInstanceIdStore, assetsStore, pagesStore, dataSourceValuesStore, onDataSourceUpdate, Component, components, }: Params & {
10
10
  instances: Map<string, {
11
11
  type: "instance";
12
12
  id: string;
@@ -24,6 +24,8 @@ export declare const createElementsTree: ({ renderer, imageBaseUrl, assetBaseUrl
24
24
  propsByInstanceIdStore: ReadableAtom<PropsByInstanceId>;
25
25
  assetsStore: ReadableAtom<Assets>;
26
26
  pagesStore: ReadableAtom<Pages>;
27
+ dataSourceValuesStore: ReadableAtom<Map<DataSource["id"], unknown>>;
28
+ onDataSourceUpdate: (dataSourceId: DataSource["id"], value: unknown) => void;
27
29
  Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
28
30
  components: Components;
29
31
  }) => JSX.Element | null;
@@ -1,5 +1,5 @@
1
- import type { ComponentProps } from "react";
2
- import type { Build, Page } from "@webstudio-is/project-build";
1
+ import { type ComponentProps } from "react";
2
+ import { type Build, type Page } from "@webstudio-is/project-build";
3
3
  import type { Asset } from "@webstudio-is/asset-uploader";
4
4
  import { WebstudioComponent } from "./webstudio-component";
5
5
  import type { Components } from "../components/components-utils";
@@ -12,7 +12,7 @@ export type Data = {
12
12
  params?: Params;
13
13
  };
14
14
  export type RootPropsData = Omit<Data, "build"> & {
15
- build: Pick<Data["build"], "instances" | "props">;
15
+ build: Pick<Data["build"], "instances" | "props" | "dataSources">;
16
16
  };
17
17
  type RootProps = {
18
18
  data: RootPropsData;
@@ -11,5 +11,6 @@ type WebstudioComponentProps = {
11
11
  export declare const WebstudioComponent: ({ instance, instanceSelector, children, components, ...rest }: WebstudioComponentProps) => JSX.Element;
12
12
  export declare const idAttribute = "data-ws-id";
13
13
  export declare const componentAttribute = "data-ws-component";
14
+ export declare const showAttribute = "data-ws-show";
14
15
  export declare const collapsedAttribute = "data-ws-collapsed";
15
16
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webstudio-is/react-sdk",
3
- "version": "0.74.0",
3
+ "version": "0.76.0",
4
4
  "description": "Webstudio JavaScript / TypeScript API",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
@@ -30,28 +30,29 @@
30
30
  "@nanostores/react": "^0.4.1",
31
31
  "detect-font": "^0.1.5",
32
32
  "html-tags": "^3.2.0",
33
+ "jsep": "^1.3.8",
33
34
  "nanoevents": "^7.0.1",
34
- "nanoid": "^3.2.0",
35
+ "nanoid": "^3.3.6",
35
36
  "nanostores": "^0.7.1",
36
- "@webstudio-is/asset-uploader": "^0.74.0",
37
- "@webstudio-is/css-data": "^0.74.0",
38
- "@webstudio-is/css-engine": "^0.74.0",
39
- "@webstudio-is/fonts": "^0.74.0",
40
- "@webstudio-is/generate-arg-types": "^0.74.0",
41
- "@webstudio-is/project-build": "^0.74.0"
37
+ "@webstudio-is/asset-uploader": "^0.76.0",
38
+ "@webstudio-is/css-data": "^0.76.0",
39
+ "@webstudio-is/css-engine": "^0.76.0",
40
+ "@webstudio-is/fonts": "^0.76.0",
41
+ "@webstudio-is/generate-arg-types": "^0.76.0",
42
+ "@webstudio-is/project-build": "^0.76.0"
42
43
  },
43
44
  "exports": {
44
45
  ".": {
45
46
  "source": "./src/index.ts",
47
+ "types": "./lib/types/index.d.ts",
46
48
  "import": "./lib/index.js",
47
- "require": "./lib/cjs/index.js",
48
- "types": "./lib/types/index.d.ts"
49
+ "require": "./lib/cjs/index.js"
49
50
  },
50
51
  "./css-normalize": {
51
52
  "source": "./src/css/normalize.ts",
53
+ "types": "./lib/types/css/normalize.d.ts",
52
54
  "import": "./lib/css/normalize.js",
53
- "require": "./lib/cjs/css/normalize.js",
54
- "types": "./lib/types/css/normalize.d.ts"
55
+ "require": "./lib/cjs/css/normalize.js"
55
56
  }
56
57
  },
57
58
  "files": [
@@ -66,10 +67,9 @@
66
67
  "dev": "build-package --watch",
67
68
  "build": "build-package",
68
69
  "build:args": "generate-arg-types './src/components/*.tsx ./src/app/custom-components/*.tsx !./src/**/*.stories.tsx !./src/**/*.ws.tsx' && prettier --write \"**/*.props.ts\"",
69
- "dts": "tsc --declarationDir lib/types",
70
+ "dts": "tsc --project tsconfig.dts.json",
70
71
  "typecheck": "tsc --noEmit --emitDeclarationOnly false",
71
72
  "test": "NODE_OPTIONS=--experimental-vm-modules jest --passWithNoTests",
72
- "lint": "eslint ./src --ext .ts,.tsx --max-warnings 0",
73
- "checks": "pnpm typecheck && pnpm lint && pnpm test"
73
+ "checks": "pnpm typecheck && pnpm test"
74
74
  }
75
75
  }
@@ -54,6 +54,7 @@ const WsComponentMeta = z.object({
54
54
  invalidAncestors: z.optional(z.array(z.string())),
55
55
  stylable: z.optional(z.boolean()),
56
56
  label: z.string(),
57
+ description: z.string().optional(),
57
58
  icon: z.string(),
58
59
  presetStyle: z.optional(z.record(z.string(), EmbedTemplateStyleDecl)),
59
60
  states: z.optional(z.array(ComponentState)),
package/src/context.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import { type ReadableAtom, atom } from "nanostores";
2
2
  import { createContext } from "react";
3
3
  import type { Assets } from "@webstudio-is/asset-uploader";
4
+ import type { DataSource, Instance, Prop } from "@webstudio-is/project-build";
4
5
  import type { Pages, PropsByInstanceId } from "./props";
5
6
 
6
7
  export type Params = {
@@ -35,6 +36,12 @@ export const ReactSdkContext = createContext<
35
36
  propsByInstanceIdStore: ReadableAtom<PropsByInstanceId>;
36
37
  assetsStore: ReadableAtom<Assets>;
37
38
  pagesStore: ReadableAtom<Pages>;
39
+ dataSourceValuesStore: ReadableAtom<Map<DataSource["id"], unknown>>;
40
+ setDataSourceValue: (
41
+ instanceId: Instance["id"],
42
+ prop: Prop["name"],
43
+ value: unknown
44
+ ) => void;
38
45
  }
39
46
  >({
40
47
  imageBaseUrl: "/",
@@ -42,4 +49,8 @@ export const ReactSdkContext = createContext<
42
49
  propsByInstanceIdStore: atom(new Map()),
43
50
  assetsStore: atom(new Map()),
44
51
  pagesStore: atom(new Map()),
52
+ dataSourceValuesStore: atom(new Map()),
53
+ setDataSourceValue: () => {
54
+ throw Error("React SDK setDataSourceValue is not implemented");
55
+ },
45
56
  });
@@ -9,7 +9,8 @@ export const addGlobalRules = (
9
9
  // @todo we need to figure out all global resets while keeping
10
10
  // the engine aware of all of them.
11
11
  // Ideally, the user is somehow aware and in control of the reset
12
- engine.addPlaintextRule("html {margin: 0; height: 100%}");
12
+ // Layout source https://twitter.com/ChallengesCss/status/1471128244720181258
13
+ engine.addPlaintextRule("html {margin: 0; display: grid; min-height: 100%}");
13
14
 
14
15
  const fontAssets: FontAsset[] = [];
15
16
  for (const asset of assets.values()) {
@@ -63,18 +63,7 @@ export const i = baseStyle;
63
63
 
64
64
  export const img = baseStyle;
65
65
 
66
- export const a = [
67
- ...baseStyle,
68
- {
69
- property: "color",
70
- value: { type: "rgb", r: 0, g: 0, b: 238, alpha: 1 },
71
- },
72
- {
73
- state: ":visited",
74
- property: "color",
75
- value: { type: "rgb", r: 85, g: 26, b: 139, alpha: 1 },
76
- },
77
- ] satisfies Styles;
66
+ export const a = baseStyle;
78
67
  export const li = baseStyle;
79
68
  export const ul = baseStyle;
80
69
  export const ol = baseStyle;
@@ -135,9 +124,17 @@ export const body = [
135
124
  property: "fontFamily",
136
125
  value: {
137
126
  type: "keyword",
138
- value: `system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'`,
127
+ value: "Arial, sans-serif",
139
128
  },
140
129
  },
130
+ {
131
+ property: "fontSize",
132
+ value: { type: "unit", unit: "px", value: 16 },
133
+ },
134
+ {
135
+ property: "lineHeight",
136
+ value: { type: "unit", unit: "number", value: 1.2 },
137
+ },
141
138
  boxSizing,
142
139
  ...borders,
143
140
  ] satisfies Styles;
@@ -1,7 +1,8 @@
1
1
  import { expect, test } from "@jest/globals";
2
2
  import { generateDataFromEmbedTemplate } from "./embed-template";
3
+ import { showAttribute } from "./tree";
3
4
 
4
- const expectString = expect.any(String) as unknown as string;
5
+ const expectString = expect.any(String);
5
6
 
6
7
  const defaultBreakpointId = "base";
7
8
 
@@ -44,6 +45,7 @@ test("generate data for embedding from instances and text", () => {
44
45
  },
45
46
  ],
46
47
  props: [],
48
+ dataSources: [],
47
49
  styleSourceSelections: [],
48
50
  styleSources: [],
49
51
  styles: [],
@@ -112,6 +114,7 @@ test("generate data for embedding from props", () => {
112
114
  value: "value3",
113
115
  },
114
116
  ],
117
+ dataSources: [],
115
118
  styleSourceSelections: [],
116
119
  styleSources: [],
117
120
  styles: [],
@@ -163,6 +166,7 @@ test("generate data for embedding from styles", () => {
163
166
  },
164
167
  ],
165
168
  props: [],
169
+ dataSources: [],
166
170
  styleSourceSelections: [
167
171
  {
168
172
  instanceId: expectString,
@@ -208,3 +212,175 @@ test("generate data for embedding from styles", () => {
208
212
  ],
209
213
  });
210
214
  });
215
+
216
+ test("generate data for embedding from props bound to data source variables", () => {
217
+ expect(
218
+ generateDataFromEmbedTemplate(
219
+ [
220
+ {
221
+ type: "instance",
222
+ component: "Box1",
223
+ props: [
224
+ {
225
+ type: "boolean",
226
+ name: "showOtherBox",
227
+ value: false,
228
+ dataSourceRef: {
229
+ type: "variable",
230
+ name: "showOtherBoxDataSource",
231
+ },
232
+ },
233
+ ],
234
+ children: [],
235
+ },
236
+ {
237
+ type: "instance",
238
+ component: "Box2",
239
+ props: [
240
+ {
241
+ type: "boolean",
242
+ name: showAttribute,
243
+ value: false,
244
+ dataSourceRef: {
245
+ type: "variable",
246
+ name: "showOtherBoxDataSource",
247
+ },
248
+ },
249
+ ],
250
+ children: [],
251
+ },
252
+ ],
253
+ defaultBreakpointId
254
+ )
255
+ ).toEqual({
256
+ children: [
257
+ { type: "id", value: expectString },
258
+ { type: "id", value: expectString },
259
+ ],
260
+ instances: [
261
+ { type: "instance", id: expectString, component: "Box1", children: [] },
262
+ { type: "instance", id: expectString, component: "Box2", children: [] },
263
+ ],
264
+ props: [
265
+ {
266
+ id: expectString,
267
+ instanceId: expectString,
268
+ type: "dataSource",
269
+ name: "showOtherBox",
270
+ value: expectString,
271
+ },
272
+ {
273
+ id: expectString,
274
+ instanceId: expectString,
275
+ type: "dataSource",
276
+ name: showAttribute,
277
+ value: expectString,
278
+ },
279
+ ],
280
+ dataSources: [
281
+ {
282
+ type: "variable",
283
+ id: expectString,
284
+ scopeInstanceId: expectString,
285
+ name: "showOtherBoxDataSource",
286
+ value: {
287
+ type: "boolean",
288
+ value: false,
289
+ },
290
+ },
291
+ ],
292
+ styleSourceSelections: [],
293
+ styleSources: [],
294
+ styles: [],
295
+ });
296
+ });
297
+
298
+ test("generate data for embedding from props bound to data source expressions", () => {
299
+ expect(
300
+ generateDataFromEmbedTemplate(
301
+ [
302
+ {
303
+ type: "instance",
304
+ component: "Box1",
305
+ props: [
306
+ {
307
+ type: "string",
308
+ name: "state",
309
+ value: "initial",
310
+ dataSourceRef: {
311
+ type: "variable",
312
+ name: "boxState",
313
+ },
314
+ },
315
+ ],
316
+ children: [],
317
+ },
318
+ {
319
+ type: "instance",
320
+ component: "Box2",
321
+ props: [
322
+ {
323
+ type: "boolean",
324
+ name: showAttribute,
325
+ value: false,
326
+ dataSourceRef: {
327
+ type: "expression",
328
+ name: "boxStateSuccess",
329
+ code: `boxState === 'success'`,
330
+ },
331
+ },
332
+ ],
333
+ children: [],
334
+ },
335
+ ],
336
+ defaultBreakpointId
337
+ )
338
+ ).toEqual({
339
+ children: [
340
+ { type: "id", value: expectString },
341
+ { type: "id", value: expectString },
342
+ ],
343
+ instances: [
344
+ { type: "instance", id: expectString, component: "Box1", children: [] },
345
+ { type: "instance", id: expectString, component: "Box2", children: [] },
346
+ ],
347
+ props: [
348
+ {
349
+ id: expectString,
350
+ instanceId: expectString,
351
+ type: "dataSource",
352
+ name: "state",
353
+ value: expectString,
354
+ },
355
+ {
356
+ id: expectString,
357
+ instanceId: expectString,
358
+ type: "dataSource",
359
+ name: showAttribute,
360
+ value: expectString,
361
+ },
362
+ ],
363
+ dataSources: [
364
+ {
365
+ type: "variable",
366
+ id: expectString,
367
+ scopeInstanceId: expectString,
368
+ name: "boxState",
369
+ value: {
370
+ type: "string",
371
+ value: "initial",
372
+ },
373
+ },
374
+ {
375
+ type: "expression",
376
+ id: expectString,
377
+ scopeInstanceId: expectString,
378
+ name: "boxStateSuccess",
379
+ code: expect.stringMatching(/\$ws\$dataSource\$\w+ === 'success'/),
380
+ },
381
+ ],
382
+ styleSourceSelections: [],
383
+ styleSources: [],
384
+ styles: [],
385
+ });
386
+ });
@@ -8,9 +8,11 @@ import {
8
8
  StyleSourcesList,
9
9
  StylesList,
10
10
  Breakpoint,
11
+ DataSource,
11
12
  } from "@webstudio-is/project-build";
12
13
  import { StyleValue, type StyleProperty } from "@webstudio-is/css-data";
13
14
  import type { Simplify } from "type-fest";
15
+ import { encodeDataSourceVariable, validateExpression } from "./expression";
14
16
 
15
17
  const EmbedTemplateText = z.object({
16
18
  type: z.literal("text"),
@@ -19,25 +21,41 @@ const EmbedTemplateText = z.object({
19
21
 
20
22
  type EmbedTemplateText = z.infer<typeof EmbedTemplateText>;
21
23
 
24
+ const DataSourceRef = z.union([
25
+ z.object({
26
+ type: z.literal("variable"),
27
+ name: z.string(),
28
+ }),
29
+ z.object({
30
+ type: z.literal("expression"),
31
+ name: z.string(),
32
+ code: z.string(),
33
+ }),
34
+ ]);
35
+
22
36
  const EmbedTemplateProp = z.union([
23
37
  z.object({
24
38
  type: z.literal("number"),
25
39
  name: z.string(),
40
+ dataSourceRef: z.optional(DataSourceRef),
26
41
  value: z.number(),
27
42
  }),
28
43
  z.object({
29
44
  type: z.literal("string"),
30
45
  name: z.string(),
46
+ dataSourceRef: z.optional(DataSourceRef),
31
47
  value: z.string(),
32
48
  }),
33
49
  z.object({
34
50
  type: z.literal("boolean"),
35
51
  name: z.string(),
52
+ dataSourceRef: z.optional(DataSourceRef),
36
53
  value: z.boolean(),
37
54
  }),
38
55
  z.object({
39
56
  type: z.literal("string[]"),
40
57
  name: z.string(),
58
+ dataSourceRef: z.optional(DataSourceRef),
41
59
  value: z.array(z.string()),
42
60
  }),
43
61
  ]);
@@ -91,6 +109,7 @@ const createInstancesFromTemplate = (
91
109
  treeTemplate: WsEmbedTemplate,
92
110
  instances: InstancesList,
93
111
  props: PropsList,
112
+ dataSourceByRef: Map<string, DataSource>,
94
113
  styleSourceSelections: StyleSourceSelectionsList,
95
114
  styleSources: StyleSourcesList,
96
115
  styles: StylesList,
@@ -104,10 +123,45 @@ const createInstancesFromTemplate = (
104
123
  // populate props
105
124
  if (item.props) {
106
125
  for (const prop of item.props) {
126
+ const propId = nanoid();
127
+ if (prop.dataSourceRef === undefined) {
128
+ props.push({ id: propId, instanceId, ...prop });
129
+ continue;
130
+ }
131
+ let dataSource = dataSourceByRef.get(prop.dataSourceRef.name);
132
+ if (dataSource === undefined) {
133
+ const id = nanoid();
134
+ const { name: propName, dataSourceRef, ...rest } = prop;
135
+ if (dataSourceRef.type === "variable") {
136
+ dataSource = {
137
+ type: "variable",
138
+ id,
139
+ // the first instance where data source is appeared in becomes its scope
140
+ scopeInstanceId: instanceId,
141
+ name: dataSourceRef.name,
142
+ value: rest,
143
+ };
144
+ dataSourceByRef.set(dataSourceRef.name, dataSource);
145
+ } else if (dataSourceRef.type === "expression") {
146
+ dataSource = {
147
+ type: "expression",
148
+ id,
149
+ scopeInstanceId: instanceId,
150
+ name: dataSourceRef.name,
151
+ code: dataSourceRef.code,
152
+ };
153
+ dataSourceByRef.set(dataSourceRef.name, dataSource);
154
+ } else {
155
+ dataSourceRef satisfies never;
156
+ continue;
157
+ }
158
+ }
107
159
  props.push({
108
- id: nanoid(),
160
+ id: propId,
109
161
  instanceId,
110
- ...prop,
162
+ type: "dataSource",
163
+ name: prop.name,
164
+ value: dataSource.id,
111
165
  });
112
166
  }
113
167
  }
@@ -148,6 +202,7 @@ const createInstancesFromTemplate = (
148
202
  item.children,
149
203
  instances,
150
204
  props,
205
+ dataSourceByRef,
151
206
  styleSourceSelections,
152
207
  styleSources,
153
208
  styles,
@@ -175,6 +230,7 @@ export const generateDataFromEmbedTemplate = (
175
230
  ) => {
176
231
  const instances: InstancesList = [];
177
232
  const props: PropsList = [];
233
+ const dataSourceByRef = new Map<string, DataSource>();
178
234
  const styleSourceSelections: StyleSourceSelectionsList = [];
179
235
  const styleSources: StyleSourcesList = [];
180
236
  const styles: StylesList = [];
@@ -183,15 +239,30 @@ export const generateDataFromEmbedTemplate = (
183
239
  treeTemplate,
184
240
  instances,
185
241
  props,
242
+ dataSourceByRef,
186
243
  styleSourceSelections,
187
244
  styleSources,
188
245
  styles,
189
246
  defaultBreakpointId
190
247
  );
248
+
249
+ // replace all references with variable names
250
+ const dataSources: DataSource[] = [];
251
+ for (const dataSource of dataSourceByRef.values()) {
252
+ if (dataSource.type === "expression") {
253
+ dataSource.code = validateExpression(dataSource.code, (ref) => {
254
+ const id = dataSourceByRef.get(ref)?.id ?? ref;
255
+ return encodeDataSourceVariable(id);
256
+ });
257
+ }
258
+ dataSources.push(dataSource);
259
+ }
260
+
191
261
  return {
192
262
  children,
193
263
  instances,
194
264
  props,
265
+ dataSources,
195
266
  styleSourceSelections,
196
267
  styleSources,
197
268
  styles,