@webstudio-is/react-sdk 0.82.0 → 0.84.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 (71) hide show
  1. package/LICENSE +661 -21
  2. package/lib/cjs/component-renderer.js +125 -0
  3. package/lib/cjs/components/component-meta.js +10 -0
  4. package/lib/cjs/components/components-utils.js +1 -0
  5. package/lib/cjs/context.js +2 -1
  6. package/lib/cjs/css/index.js +0 -1
  7. package/lib/cjs/css/style-rules.js +1 -1
  8. package/lib/cjs/embed-template.js +130 -55
  9. package/lib/cjs/expression.js +47 -4
  10. package/lib/cjs/hook.js +34 -0
  11. package/lib/cjs/index.js +7 -0
  12. package/lib/cjs/instance-utils.js +65 -0
  13. package/lib/cjs/props.js +18 -3
  14. package/lib/cjs/tree/create-elements-tree.js +5 -4
  15. package/lib/cjs/tree/root.js +7 -2
  16. package/lib/cjs/tree/webstudio-component.js +26 -10
  17. package/lib/component-renderer.js +111 -0
  18. package/lib/components/component-meta.js +10 -0
  19. package/lib/components/components-utils.js +1 -0
  20. package/lib/context.js +2 -1
  21. package/lib/css/index.js +0 -1
  22. package/lib/css/style-rules.js +1 -1
  23. package/lib/embed-template.js +138 -55
  24. package/lib/expression.js +47 -4
  25. package/lib/hook.js +14 -0
  26. package/lib/index.js +10 -1
  27. package/lib/instance-utils.js +45 -0
  28. package/lib/props.js +19 -4
  29. package/lib/tree/create-elements-tree.js +8 -5
  30. package/lib/tree/root.js +14 -4
  31. package/lib/tree/webstudio-component.js +27 -11
  32. package/lib/types/app/root.d.ts +1 -2
  33. package/lib/types/component-renderer.d.ts +8 -0
  34. package/lib/types/components/component-meta.d.ts +14 -8
  35. package/lib/types/context.d.ts +3 -1
  36. package/lib/types/css/css.d.ts +19 -19
  37. package/lib/types/css/global-rules.d.ts +19 -19
  38. package/lib/types/css/index.d.ts +0 -1
  39. package/lib/types/css/normalize.d.ts +47 -47
  40. package/lib/types/embed-template.d.ts +297 -174
  41. package/lib/types/expression.d.ts +3 -2
  42. package/lib/types/hook.d.ts +31 -0
  43. package/lib/types/index.d.ts +5 -2
  44. package/lib/types/instance-utils.d.ts +16 -0
  45. package/lib/types/instance-utils.test.d.ts +1 -0
  46. package/lib/types/props.d.ts +48 -46
  47. package/lib/types/tree/create-elements-tree.d.ts +9 -6
  48. package/lib/types/tree/root.d.ts +8 -5
  49. package/lib/types/tree/webstudio-component.d.ts +16 -7
  50. package/package.json +18 -19
  51. package/src/component-renderer.tsx +117 -0
  52. package/src/components/component-meta.ts +10 -0
  53. package/src/context.tsx +4 -0
  54. package/src/css/index.ts +0 -1
  55. package/src/css/style-rules.ts +1 -1
  56. package/src/embed-template.test.ts +113 -26
  57. package/src/embed-template.ts +149 -56
  58. package/src/expression.test.ts +74 -6
  59. package/src/expression.ts +55 -2
  60. package/src/hook.ts +42 -0
  61. package/src/index.ts +5 -0
  62. package/src/instance-utils.test.ts +89 -0
  63. package/src/instance-utils.ts +65 -0
  64. package/src/props.ts +19 -2
  65. package/src/tree/create-elements-tree.tsx +25 -8
  66. package/src/tree/root.ts +22 -3
  67. package/src/tree/webstudio-component.tsx +42 -14
  68. package/lib/cjs/css/get-browser-style.js +0 -83
  69. package/lib/css/get-browser-style.js +0 -65
  70. package/lib/types/css/get-browser-style.d.ts +0 -2
  71. package/src/css/get-browser-style.ts +0 -81
package/src/expression.ts CHANGED
@@ -252,6 +252,7 @@ export const executeComputingExpressions = (
252
252
 
253
253
  export const generateEffectfulExpression = (
254
254
  code: string,
255
+ args: Set<string>,
255
256
  allowedVariables: Set<string>
256
257
  ) => {
257
258
  const inputVariables = new Set<string>();
@@ -259,6 +260,9 @@ export const generateEffectfulExpression = (
259
260
  validateExpression(code, {
260
261
  effectful: true,
261
262
  transformIdentifier: (identifier, assignee) => {
263
+ if (args.has(identifier)) {
264
+ return identifier;
265
+ }
262
266
  if (allowedVariables.has(identifier)) {
263
267
  if (assignee) {
264
268
  outputVariables.add(identifier);
@@ -274,6 +278,9 @@ export const generateEffectfulExpression = (
274
278
  // generate code computing all expressions
275
279
  let generatedCode = "";
276
280
 
281
+ for (const id of args) {
282
+ generatedCode += `let ${id} = _args.get('${id}');\n`;
283
+ }
277
284
  for (const id of inputVariables) {
278
285
  generatedCode += `let ${id} = _variables.get('${id}');\n`;
279
286
  }
@@ -296,17 +303,63 @@ export const generateEffectfulExpression = (
296
303
 
297
304
  export const executeEffectfulExpression = (
298
305
  code: string,
306
+ args: Map<string, unknown>,
299
307
  variables: Map<string, unknown>
300
308
  ) => {
301
309
  const generatedCode = generateEffectfulExpression(
302
310
  code,
311
+ new Set(args.keys()),
303
312
  new Set(variables.keys())
304
313
  );
305
- const executeFn = new Function("_variables", generatedCode);
306
- const values = executeFn(variables) as Map<string, unknown>;
314
+ const executeFn = new Function("_variables", "_args", generatedCode);
315
+ const values = executeFn(variables, args) as Map<string, unknown>;
307
316
  return values;
308
317
  };
309
318
 
319
+ const computeExpressionDependencies = (
320
+ expressions: Map<string, string>,
321
+ expressionId: string,
322
+ dependencies: Map<string, Set<string>>
323
+ ) => {
324
+ // prevent recalculating expressions over again
325
+ const depsById = dependencies.get(expressionId);
326
+ if (depsById) {
327
+ return depsById;
328
+ }
329
+ const parentDeps = new Set<string>();
330
+ const code = expressions.get(expressionId);
331
+ if (code === undefined) {
332
+ return parentDeps;
333
+ }
334
+ // write before recursive call to avoid infinite cycle
335
+ dependencies.set(expressionId, parentDeps);
336
+ validateExpression(code, {
337
+ transformIdentifier: (id) => {
338
+ parentDeps.add(id);
339
+ const childDeps = computeExpressionDependencies(
340
+ expressions,
341
+ id,
342
+ dependencies
343
+ );
344
+ for (const depId of childDeps) {
345
+ parentDeps.add(depId);
346
+ }
347
+ return id;
348
+ },
349
+ });
350
+ return parentDeps;
351
+ };
352
+
353
+ export const computeExpressionsDependencies = (
354
+ expressions: Map<string, string>
355
+ ) => {
356
+ const dependencies = new Map<string, Set<string>>();
357
+ for (const id of expressions.keys()) {
358
+ computeExpressionDependencies(expressions, id, dependencies);
359
+ }
360
+ return dependencies;
361
+ };
362
+
310
363
  type Values = Map<string, unknown>;
311
364
 
312
365
  const dataSourceVariablePrefix = "$ws$dataSource$";
package/src/hook.ts ADDED
@@ -0,0 +1,42 @@
1
+ import type { Instance, Prop } from "@webstudio-is/project-build";
2
+
3
+ /**
4
+ * Hooks are subscriptions to builder events
5
+ * with limited way to interact with it.
6
+ * Called independently from components.
7
+ */
8
+
9
+ export type HookContext = {
10
+ setPropVariable: (
11
+ instanceId: Instance["id"],
12
+ propName: Prop["name"],
13
+ value: unknown
14
+ ) => void;
15
+ };
16
+
17
+ export type InstanceSelection = Instance[];
18
+
19
+ type NavigatorEvent = {
20
+ instanceSelection: InstanceSelection;
21
+ };
22
+
23
+ export type Hook = {
24
+ onNavigatorSelect?: (context: HookContext, event: NavigatorEvent) => void;
25
+ onNavigatorUnselect?: (context: HookContext, event: NavigatorEvent) => void;
26
+ };
27
+
28
+ export const getClosestInstance = (
29
+ instanceSelection: InstanceSelection,
30
+ currentInstance: Instance,
31
+ closestComponent: Instance["component"]
32
+ ) => {
33
+ let matched = false;
34
+ for (const instance of instanceSelection) {
35
+ if (currentInstance === instance) {
36
+ matched = true;
37
+ }
38
+ if (matched && instance.component === closestComponent) {
39
+ return instance;
40
+ }
41
+ }
42
+ };
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export {
18
18
  usePropUrl,
19
19
  usePropAsset,
20
20
  getInstanceIdFromComponentProps,
21
+ getIndexWithinAncestorFromComponentProps,
21
22
  } from "./props";
22
23
  export { type Params, ReactSdkContext } from "./context";
23
24
  export {
@@ -26,8 +27,12 @@ export {
26
27
  executeComputingExpressions,
27
28
  generateEffectfulExpression,
28
29
  executeEffectfulExpression,
30
+ computeExpressionsDependencies,
29
31
  encodeDataSourceVariable,
30
32
  encodeVariablesMap,
31
33
  decodeDataSourceVariable,
32
34
  decodeVariablesMap,
33
35
  } from "./expression";
36
+ export { renderComponentTemplate } from "./component-renderer";
37
+ export { getIndexesWithinAncestors } from "./instance-utils";
38
+ export * from "./hook";
@@ -0,0 +1,89 @@
1
+ import { test, expect } from "@jest/globals";
2
+ import type { Instance, Instances } from "@webstudio-is/project-build";
3
+ import { getIndexesWithinAncestors } from "./instance-utils";
4
+ import type { WsComponentMeta } from ".";
5
+
6
+ const getIdValuePair = <T extends { id: string }>(item: T) =>
7
+ [item.id, item] as const;
8
+
9
+ const toMap = <T extends { id: string }>(list: T[]) =>
10
+ new Map(list.map(getIdValuePair));
11
+
12
+ const createInstance = (
13
+ id: Instance["id"],
14
+ component: string,
15
+ children: Instance["children"]
16
+ ): Instance => {
17
+ return { type: "instance", id, component, children };
18
+ };
19
+
20
+ const createMeta = (meta?: Partial<WsComponentMeta>) => {
21
+ return { type: "container", label: "", icon: "", ...meta } as const;
22
+ };
23
+
24
+ test("get indexes within ancestors", () => {
25
+ // body0
26
+ // tabs1
27
+ // tabs1list
28
+ // tabs1box
29
+ // tabs1trigger1
30
+ // tabs1trigger2
31
+ // tabs1content1
32
+ // tabs2
33
+ // tabs2list
34
+ // tabs2trigger1
35
+ // tabs2content1
36
+ // tabs1content2
37
+ const instances: Instances = toMap([
38
+ createInstance("body0", "Body", [{ type: "id", value: "tabs1" }]),
39
+ // tabs1
40
+ createInstance("tabs1", "Tabs", [
41
+ { type: "id", value: "tabs1list" },
42
+ { type: "id", value: "tabs1content1" },
43
+ { type: "id", value: "tabs1content2" },
44
+ ]),
45
+ createInstance("tabs1list", "TabsList", [
46
+ { type: "id", value: "tabs1box" },
47
+ ]),
48
+ createInstance("tabs1box", "Box", [
49
+ { type: "id", value: "tabs1trigger1" },
50
+ { type: "id", value: "tabs1trigger2" },
51
+ ]),
52
+ createInstance("tabs1trigger1", "TabsTrigger", []),
53
+ createInstance("tabs1trigger2", "TabsTrigger", []),
54
+ createInstance("tabs1content1", "TabsContent", [
55
+ { type: "id", value: "tabs2" },
56
+ ]),
57
+ createInstance("tabs1content2", "TabsContent", []),
58
+ // tabs2
59
+ createInstance("tabs2", "Tabs", [
60
+ { type: "id", value: "tabs2list" },
61
+ { type: "id", value: "tabs2content1" },
62
+ ]),
63
+ createInstance("tabs2list", "TabsList", [
64
+ { type: "id", value: "tabs2trigger1" },
65
+ ]),
66
+ createInstance("tabs2trigger1", "TabsTrigger", []),
67
+ createInstance("tabs2content1", "TabsContent", []),
68
+ ] satisfies Instance[]);
69
+ const metas = new Map<Instance["component"], WsComponentMeta>([
70
+ ["Body", createMeta()],
71
+ ["Box", createMeta()],
72
+ ["Tabs", createMeta()],
73
+ ["TabsList", createMeta({ indexWithinAncestor: "Tabs" })],
74
+ ["TabsTrigger", createMeta({ indexWithinAncestor: "TabsList" })],
75
+ ["TabsContent", createMeta({ indexWithinAncestor: "Tabs" })],
76
+ ]);
77
+ expect(getIndexesWithinAncestors(metas, instances, ["body0"])).toEqual(
78
+ new Map([
79
+ ["tabs1list", 0],
80
+ ["tabs1trigger1", 0],
81
+ ["tabs1trigger2", 1],
82
+ ["tabs1content1", 0],
83
+ ["tabs1content2", 1],
84
+ ["tabs2list", 0],
85
+ ["tabs2trigger1", 0],
86
+ ["tabs2content1", 0],
87
+ ])
88
+ );
89
+ });
@@ -0,0 +1,65 @@
1
+ import type { Instance, Instances } from "@webstudio-is/project-build";
2
+ import type { WsComponentMeta } from "./components/component-meta";
3
+
4
+ export type IndexesWithinAncestors = Map<Instance["id"], number>;
5
+
6
+ export const getIndexesWithinAncestors = (
7
+ metas: Map<Instance["component"], WsComponentMeta>,
8
+ instances: Instances,
9
+ rootIds: Instance["id"][]
10
+ ) => {
11
+ const ancestors = new Set<Instance["component"]>();
12
+ for (const meta of metas.values()) {
13
+ if (meta.indexWithinAncestor !== undefined) {
14
+ ancestors.add(meta.indexWithinAncestor);
15
+ }
16
+ }
17
+
18
+ const indexes: IndexesWithinAncestors = new Map();
19
+
20
+ const traverseInstances = (
21
+ instances: Instances,
22
+ instanceId: Instance["id"],
23
+ latestIndexes = new Map<
24
+ Instance["component"],
25
+ Map<Instance["component"], number>
26
+ >()
27
+ ) => {
28
+ const instance = instances.get(instanceId);
29
+ if (instance === undefined) {
30
+ return;
31
+ }
32
+ const meta = metas.get(instance.component);
33
+ if (meta === undefined) {
34
+ return;
35
+ }
36
+
37
+ if (ancestors.has(instance.component)) {
38
+ latestIndexes = new Map(latestIndexes);
39
+ latestIndexes.set(instance.component, new Map());
40
+ }
41
+
42
+ if (meta.indexWithinAncestor !== undefined) {
43
+ const ancestorIndexes = latestIndexes.get(meta.indexWithinAncestor);
44
+ if (ancestorIndexes !== undefined) {
45
+ let index = ancestorIndexes.get(instance.component) ?? -1;
46
+ index += 1;
47
+ ancestorIndexes.set(instance.component, index);
48
+ indexes.set(instance.id, index);
49
+ }
50
+ }
51
+
52
+ for (const child of instance.children) {
53
+ if (child.type === "id") {
54
+ traverseInstances(instances, child.value, latestIndexes);
55
+ }
56
+ }
57
+ };
58
+
59
+ const latestIndexes = new Map();
60
+ for (const instanceId of rootIds) {
61
+ traverseInstances(instances, instanceId, latestIndexes);
62
+ }
63
+
64
+ return indexes;
65
+ };
package/src/props.ts CHANGED
@@ -4,7 +4,7 @@ import { useStore } from "@nanostores/react";
4
4
  import type { Instance, Page, Prop, Props } from "@webstudio-is/project-build";
5
5
  import type { Asset, Assets } from "@webstudio-is/asset-uploader";
6
6
  import { ReactSdkContext } from "./context";
7
- import { idAttribute } from "./tree/webstudio-component";
7
+ import { idAttribute, indexAttribute } from "./tree/webstudio-component";
8
8
 
9
9
  export type PropsByInstanceId = Map<Instance["id"], Prop[]>;
10
10
 
@@ -32,12 +32,17 @@ export const useInstanceProps = (instanceId: Instance["id"]) => {
32
32
  executeEffectfulExpression,
33
33
  setDataSourceValues,
34
34
  renderer,
35
+ indexesWithinAncestors,
35
36
  } = useContext(ReactSdkContext);
37
+ const index = indexesWithinAncestors.get(instanceId);
36
38
  const instancePropsObjectStore = useMemo(() => {
37
39
  return computed(
38
40
  [propsByInstanceIdStore, dataSourceValuesStore],
39
41
  (propsByInstanceId, dataSourceValues) => {
40
42
  const instancePropsObject: Record<Prop["name"], unknown> = {};
43
+ if (index !== undefined) {
44
+ instancePropsObject[indexAttribute] = index.toString();
45
+ }
41
46
  const instanceProps = propsByInstanceId.get(instanceId);
42
47
  if (instanceProps === undefined) {
43
48
  return instancePropsObject;
@@ -55,15 +60,20 @@ export const useInstanceProps = (instanceId: Instance["id"]) => {
55
60
  continue;
56
61
  }
57
62
  if (prop.type === "action") {
58
- instancePropsObject[prop.name] = () => {
63
+ instancePropsObject[prop.name] = (...args: unknown[]) => {
59
64
  // prevent all actions in canvas mode
60
65
  if (renderer === "canvas") {
61
66
  return;
62
67
  }
63
68
  for (const value of prop.value) {
64
69
  if (value.type === "execute") {
70
+ const argsMap = new Map<string, unknown>();
71
+ for (const [i, name] of value.args.entries()) {
72
+ argsMap.set(name, args[i]);
73
+ }
65
74
  const newValues = executeEffectfulExpression(
66
75
  value.code,
76
+ argsMap,
67
77
  dataSourceValues
68
78
  );
69
79
  setDataSourceValues(newValues);
@@ -84,6 +94,7 @@ export const useInstanceProps = (instanceId: Instance["id"]) => {
84
94
  renderer,
85
95
  executeEffectfulExpression,
86
96
  setDataSourceValues,
97
+ index,
87
98
  ]);
88
99
  const instancePropsObject = useStore(instancePropsObjectStore);
89
100
  return instancePropsObject;
@@ -208,3 +219,9 @@ export const getInstanceIdFromComponentProps = (
208
219
  ) => {
209
220
  return props[idAttribute] as string;
210
221
  };
222
+
223
+ export const getIndexWithinAncestorFromComponentProps = (
224
+ props: Record<string, unknown>
225
+ ) => {
226
+ return props[indexAttribute] as string | undefined;
227
+ };
@@ -1,6 +1,10 @@
1
- import { type ComponentProps, Fragment } from "react";
1
+ import {
2
+ Fragment,
3
+ type ForwardRefExoticComponent,
4
+ type RefAttributes,
5
+ type ReactNode,
6
+ } from "react";
2
7
  import type { ReadableAtom } from "nanostores";
3
- import { Scripts, ScrollRestoration } from "@remix-run/react";
4
8
  import type { Assets } from "@webstudio-is/asset-uploader";
5
9
  import type { Instance, Instances } from "@webstudio-is/project-build";
6
10
  import type { Components } from "../components/components-utils";
@@ -10,7 +14,8 @@ import {
10
14
  ReactSdkContext,
11
15
  } from "../context";
12
16
  import type { Pages, PropsByInstanceId } from "../props";
13
- import type { WebstudioComponent } from "./webstudio-component";
17
+ import type { WebstudioComponentProps } from "./webstudio-component";
18
+ import type { IndexesWithinAncestors } from "../instance-utils";
14
19
 
15
20
  type InstanceSelector = Instance["id"][];
16
21
 
@@ -26,8 +31,10 @@ export const createElementsTree = ({
26
31
  dataSourceValuesStore,
27
32
  executeEffectfulExpression,
28
33
  onDataSourceUpdate,
34
+ indexesWithinAncestors,
29
35
  Component,
30
36
  components,
37
+ scripts,
31
38
  }: Params & {
32
39
  instances: Instances;
33
40
  rootInstanceId: Instance["id"];
@@ -36,12 +43,18 @@ export const createElementsTree = ({
36
43
  pagesStore: ReadableAtom<Pages>;
37
44
  executeEffectfulExpression: (
38
45
  expression: string,
46
+ args: DataSourceValues,
39
47
  values: DataSourceValues
40
48
  ) => DataSourceValues;
41
49
  dataSourceValuesStore: ReadableAtom<DataSourceValues>;
42
50
  onDataSourceUpdate: (newValues: DataSourceValues) => void;
43
- Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
51
+ indexesWithinAncestors: IndexesWithinAncestors;
52
+
53
+ Component: ForwardRefExoticComponent<
54
+ WebstudioComponentProps & RefAttributes<HTMLElement>
55
+ >;
44
56
  components: Components;
57
+ scripts?: ReactNode;
45
58
  }) => {
46
59
  const rootInstance = instances.get(rootInstanceId);
47
60
  if (rootInstance === undefined) {
@@ -63,8 +76,7 @@ export const createElementsTree = ({
63
76
  children: [
64
77
  <Fragment key="children">
65
78
  {children}
66
- <ScrollRestoration />
67
- <Scripts />
79
+ {scripts}
68
80
  </Fragment>,
69
81
  ],
70
82
  components,
@@ -79,6 +91,7 @@ export const createElementsTree = ({
79
91
  renderer,
80
92
  imageBaseUrl,
81
93
  assetBaseUrl,
94
+ indexesWithinAncestors,
82
95
  executeEffectfulExpression,
83
96
  setDataSourceValues: onDataSourceUpdate,
84
97
  setBoundDataSourceValue: (instanceId, propName, value) => {
@@ -110,7 +123,9 @@ const createInstanceChildrenElements = ({
110
123
  instances: Instances;
111
124
  instanceSelector: InstanceSelector;
112
125
  children: Instance["children"];
113
- Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
126
+ Component: ForwardRefExoticComponent<
127
+ WebstudioComponentProps & RefAttributes<HTMLElement>
128
+ >;
114
129
  components: Components;
115
130
  }) => {
116
131
  const elements = [];
@@ -152,7 +167,9 @@ const createInstanceElement = ({
152
167
  }: {
153
168
  instance: Instance;
154
169
  instanceSelector: InstanceSelector;
155
- Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
170
+ Component: ForwardRefExoticComponent<
171
+ WebstudioComponentProps & RefAttributes<HTMLElement>
172
+ >;
156
173
  children?: Array<JSX.Element | string>;
157
174
  components: Components;
158
175
  }) => {
package/src/tree/root.ts CHANGED
@@ -1,4 +1,10 @@
1
- import { useRef, type ComponentProps, useCallback } from "react";
1
+ import {
2
+ useRef,
3
+ useCallback,
4
+ type ForwardRefExoticComponent,
5
+ type RefAttributes,
6
+ type ReactNode,
7
+ } from "react";
2
8
  import {
3
9
  atom,
4
10
  computed,
@@ -8,10 +14,14 @@ import {
8
14
  import { type Build, type Page } from "@webstudio-is/project-build";
9
15
  import type { Asset } from "@webstudio-is/asset-uploader";
10
16
  import { createElementsTree } from "./create-elements-tree";
11
- import { WebstudioComponent } from "./webstudio-component";
17
+ import {
18
+ WebstudioComponent,
19
+ type WebstudioComponentProps,
20
+ } from "./webstudio-component";
12
21
  import { getPropsByInstanceId } from "../props";
13
22
  import type { Components } from "../components/components-utils";
14
23
  import type { Params, DataSourceValues } from "../context";
24
+ import type { IndexesWithinAncestors } from "../instance-utils";
15
25
 
16
26
  export type Data = {
17
27
  page: Page;
@@ -27,21 +37,28 @@ export type RootPropsData = Omit<Data, "build"> & {
27
37
 
28
38
  type RootProps = {
29
39
  data: RootPropsData;
40
+ indexesWithinAncestors: IndexesWithinAncestors;
30
41
  executeComputingExpressions: (values: DataSourceValues) => DataSourceValues;
31
42
  executeEffectfulExpression: (
32
43
  expression: string,
44
+ args: DataSourceValues,
33
45
  values: DataSourceValues
34
46
  ) => DataSourceValues;
35
- Component?: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
47
+ Component?: ForwardRefExoticComponent<
48
+ WebstudioComponentProps & RefAttributes<HTMLElement>
49
+ >;
36
50
  components: Components;
51
+ scripts?: ReactNode;
37
52
  };
38
53
 
39
54
  export const InstanceRoot = ({
40
55
  data,
56
+ indexesWithinAncestors,
41
57
  executeComputingExpressions,
42
58
  executeEffectfulExpression,
43
59
  Component,
44
60
  components,
61
+ scripts,
45
62
  }: RootProps): JSX.Element | null => {
46
63
  const dataSourceVariablesStoreRef = useRef<
47
64
  undefined | WritableAtom<DataSourceValues>
@@ -106,10 +123,12 @@ export const InstanceRoot = ({
106
123
  ),
107
124
  assetsStore: atom(new Map(data.assets.map((asset) => [asset.id, asset]))),
108
125
  pagesStore: atom(new Map(data.pages.map((page) => [page.id, page]))),
126
+ indexesWithinAncestors,
109
127
  executeEffectfulExpression,
110
128
  dataSourceValuesStore,
111
129
  onDataSourceUpdate,
112
130
  Component: Component ?? WebstudioComponent,
113
131
  components,
132
+ scripts,
114
133
  });
115
134
  };
@@ -1,4 +1,4 @@
1
- import { Fragment } from "react";
1
+ import { Fragment, forwardRef } from "react";
2
2
  import type { Instance } from "@webstudio-is/project-build";
3
3
  import type { Components } from "../components/components-utils";
4
4
  import { useInstanceProps } from "../props";
@@ -26,20 +26,18 @@ export const renderWebstudioComponentChildren = (
26
26
  });
27
27
  };
28
28
 
29
- type WebstudioComponentProps = {
29
+ export type WebstudioComponentProps = {
30
30
  instance: Instance;
31
31
  instanceSelector: Instance["id"][];
32
32
  children: Array<JSX.Element | string>;
33
33
  components: Components;
34
34
  };
35
35
 
36
- export const WebstudioComponent = ({
37
- instance,
38
- instanceSelector,
39
- children,
40
- components,
41
- ...rest
42
- }: WebstudioComponentProps) => {
36
+ // eslint-disable-next-line react/display-name
37
+ export const WebstudioComponent = forwardRef<
38
+ HTMLElement,
39
+ WebstudioComponentProps
40
+ >(({ instance, instanceSelector, children, components, ...rest }, ref) => {
43
41
  const { [showAttribute]: show = true, ...instanceProps } = useInstanceProps(
44
42
  instance.id
45
43
  );
@@ -57,13 +55,43 @@ export const WebstudioComponent = ({
57
55
  return <></>;
58
56
  }
59
57
  return (
60
- <Component {...props}>
58
+ <Component {...props} ref={ref}>
61
59
  {renderWebstudioComponentChildren(children)}
62
60
  </Component>
63
61
  );
62
+ });
63
+
64
+ export const idAttribute = "data-ws-id" as const;
65
+ export const selectorIdAttribute = "data-ws-selector" as const;
66
+ export const componentAttribute = "data-ws-component" as const;
67
+ export const showAttribute = "data-ws-show" as const;
68
+ export const indexAttribute = "data-ws-index" as const;
69
+ export const collapsedAttribute = "data-ws-collapsed" as const;
70
+
71
+ export type WebstudioAttributes = {
72
+ [idAttribute]?: string | undefined;
73
+ [selectorIdAttribute]?: string | undefined;
74
+ [componentAttribute]?: string | undefined;
75
+ [showAttribute]?: string | undefined;
76
+ [collapsedAttribute]?: string | undefined;
64
77
  };
65
78
 
66
- export const idAttribute = "data-ws-id";
67
- export const componentAttribute = "data-ws-component";
68
- export const showAttribute = "data-ws-show";
69
- export const collapsedAttribute = "data-ws-collapsed";
79
+ export const splitPropsWithWebstudioAttributes = <
80
+ P extends WebstudioAttributes,
81
+ >({
82
+ [idAttribute]: idAttributeValue,
83
+ [componentAttribute]: componentAttributeValue,
84
+ [showAttribute]: showAttributeValue,
85
+ [collapsedAttribute]: collapsedAttributeValue,
86
+ [selectorIdAttribute]: parentIdAttributeValue,
87
+ ...props
88
+ }: P): [WebstudioAttributes, Omit<P, keyof WebstudioAttributes>] => [
89
+ {
90
+ [idAttribute]: idAttributeValue,
91
+ [componentAttribute]: componentAttributeValue,
92
+ [showAttribute]: showAttributeValue,
93
+ [collapsedAttribute]: collapsedAttributeValue,
94
+ [selectorIdAttribute]: parentIdAttributeValue,
95
+ },
96
+ props,
97
+ ];