@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.
- package/LICENSE +661 -21
- package/lib/cjs/component-renderer.js +125 -0
- package/lib/cjs/components/component-meta.js +10 -0
- package/lib/cjs/components/components-utils.js +1 -0
- package/lib/cjs/context.js +2 -1
- package/lib/cjs/css/index.js +0 -1
- package/lib/cjs/css/style-rules.js +1 -1
- package/lib/cjs/embed-template.js +130 -55
- package/lib/cjs/expression.js +47 -4
- package/lib/cjs/hook.js +34 -0
- package/lib/cjs/index.js +7 -0
- package/lib/cjs/instance-utils.js +65 -0
- package/lib/cjs/props.js +18 -3
- package/lib/cjs/tree/create-elements-tree.js +5 -4
- package/lib/cjs/tree/root.js +7 -2
- package/lib/cjs/tree/webstudio-component.js +26 -10
- package/lib/component-renderer.js +111 -0
- package/lib/components/component-meta.js +10 -0
- package/lib/components/components-utils.js +1 -0
- package/lib/context.js +2 -1
- package/lib/css/index.js +0 -1
- package/lib/css/style-rules.js +1 -1
- package/lib/embed-template.js +138 -55
- package/lib/expression.js +47 -4
- package/lib/hook.js +14 -0
- package/lib/index.js +10 -1
- package/lib/instance-utils.js +45 -0
- package/lib/props.js +19 -4
- package/lib/tree/create-elements-tree.js +8 -5
- package/lib/tree/root.js +14 -4
- package/lib/tree/webstudio-component.js +27 -11
- package/lib/types/app/root.d.ts +1 -2
- package/lib/types/component-renderer.d.ts +8 -0
- package/lib/types/components/component-meta.d.ts +14 -8
- package/lib/types/context.d.ts +3 -1
- package/lib/types/css/css.d.ts +19 -19
- package/lib/types/css/global-rules.d.ts +19 -19
- package/lib/types/css/index.d.ts +0 -1
- package/lib/types/css/normalize.d.ts +47 -47
- package/lib/types/embed-template.d.ts +297 -174
- package/lib/types/expression.d.ts +3 -2
- package/lib/types/hook.d.ts +31 -0
- package/lib/types/index.d.ts +5 -2
- package/lib/types/instance-utils.d.ts +16 -0
- package/lib/types/instance-utils.test.d.ts +1 -0
- package/lib/types/props.d.ts +48 -46
- package/lib/types/tree/create-elements-tree.d.ts +9 -6
- package/lib/types/tree/root.d.ts +8 -5
- package/lib/types/tree/webstudio-component.d.ts +16 -7
- package/package.json +18 -19
- package/src/component-renderer.tsx +117 -0
- package/src/components/component-meta.ts +10 -0
- package/src/context.tsx +4 -0
- package/src/css/index.ts +0 -1
- package/src/css/style-rules.ts +1 -1
- package/src/embed-template.test.ts +113 -26
- package/src/embed-template.ts +149 -56
- package/src/expression.test.ts +74 -6
- package/src/expression.ts +55 -2
- package/src/hook.ts +42 -0
- package/src/index.ts +5 -0
- package/src/instance-utils.test.ts +89 -0
- package/src/instance-utils.ts +65 -0
- package/src/props.ts +19 -2
- package/src/tree/create-elements-tree.tsx +25 -8
- package/src/tree/root.ts +22 -3
- package/src/tree/webstudio-component.tsx +42 -14
- package/lib/cjs/css/get-browser-style.js +0 -83
- package/lib/css/get-browser-style.js +0 -65
- package/lib/types/css/get-browser-style.d.ts +0 -2
- 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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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 {
|
|
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 {
|
|
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?:
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
+
];
|