@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "@jest/globals";
|
|
2
|
-
import { generateDataFromEmbedTemplate } from "./embed-template";
|
|
2
|
+
import { generateDataFromEmbedTemplate, namespaceMeta } from "./embed-template";
|
|
3
3
|
import { showAttribute } from "./tree";
|
|
4
4
|
|
|
5
5
|
const expectString = expect.any(String);
|
|
@@ -220,15 +220,14 @@ test("generate data for embedding from props bound to data source variables", ()
|
|
|
220
220
|
{
|
|
221
221
|
type: "instance",
|
|
222
222
|
component: "Box1",
|
|
223
|
+
dataSources: {
|
|
224
|
+
showOtherBoxDataSource: { type: "variable", initialValue: false },
|
|
225
|
+
},
|
|
223
226
|
props: [
|
|
224
227
|
{
|
|
225
|
-
type: "
|
|
228
|
+
type: "dataSource",
|
|
226
229
|
name: "showOtherBox",
|
|
227
|
-
|
|
228
|
-
dataSourceRef: {
|
|
229
|
-
type: "variable",
|
|
230
|
-
name: "showOtherBoxDataSource",
|
|
231
|
-
},
|
|
230
|
+
dataSourceName: "showOtherBoxDataSource",
|
|
232
231
|
},
|
|
233
232
|
],
|
|
234
233
|
children: [],
|
|
@@ -238,13 +237,9 @@ test("generate data for embedding from props bound to data source variables", ()
|
|
|
238
237
|
component: "Box2",
|
|
239
238
|
props: [
|
|
240
239
|
{
|
|
241
|
-
type: "
|
|
240
|
+
type: "dataSource",
|
|
242
241
|
name: showAttribute,
|
|
243
|
-
|
|
244
|
-
dataSourceRef: {
|
|
245
|
-
type: "variable",
|
|
246
|
-
name: "showOtherBoxDataSource",
|
|
247
|
-
},
|
|
242
|
+
dataSourceName: "showOtherBoxDataSource",
|
|
248
243
|
},
|
|
249
244
|
],
|
|
250
245
|
children: [],
|
|
@@ -302,12 +297,18 @@ test("generate data for embedding from props bound to data source expressions",
|
|
|
302
297
|
{
|
|
303
298
|
type: "instance",
|
|
304
299
|
component: "Box1",
|
|
300
|
+
dataSources: {
|
|
301
|
+
boxState: { type: "variable", initialValue: "initial" },
|
|
302
|
+
boxStateSuccess: {
|
|
303
|
+
type: "expression",
|
|
304
|
+
code: `boxState === 'success'`,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
305
307
|
props: [
|
|
306
308
|
{
|
|
307
|
-
type: "
|
|
309
|
+
type: "dataSource",
|
|
308
310
|
name: "state",
|
|
309
|
-
|
|
310
|
-
dataSourceRef: { type: "variable", name: "boxState" },
|
|
311
|
+
dataSourceName: "boxState",
|
|
311
312
|
},
|
|
312
313
|
],
|
|
313
314
|
children: [],
|
|
@@ -317,14 +318,9 @@ test("generate data for embedding from props bound to data source expressions",
|
|
|
317
318
|
component: "Box2",
|
|
318
319
|
props: [
|
|
319
320
|
{
|
|
320
|
-
type: "
|
|
321
|
+
type: "dataSource",
|
|
321
322
|
name: showAttribute,
|
|
322
|
-
|
|
323
|
-
dataSourceRef: {
|
|
324
|
-
type: "expression",
|
|
325
|
-
name: "boxStateSuccess",
|
|
326
|
-
code: `boxState === 'success'`,
|
|
327
|
-
},
|
|
323
|
+
dataSourceName: "boxStateSuccess",
|
|
328
324
|
},
|
|
329
325
|
],
|
|
330
326
|
children: [],
|
|
@@ -389,12 +385,14 @@ test("generate data for embedding from action props", () => {
|
|
|
389
385
|
{
|
|
390
386
|
type: "instance",
|
|
391
387
|
component: "Box1",
|
|
388
|
+
dataSources: {
|
|
389
|
+
boxState: { type: "variable", initialValue: "initial" },
|
|
390
|
+
},
|
|
392
391
|
props: [
|
|
393
392
|
{
|
|
394
|
-
type: "
|
|
393
|
+
type: "dataSource",
|
|
395
394
|
name: "state",
|
|
396
|
-
|
|
397
|
-
dataSourceRef: { type: "variable", name: "boxState" },
|
|
395
|
+
dataSourceName: "boxState",
|
|
398
396
|
},
|
|
399
397
|
],
|
|
400
398
|
children: [
|
|
@@ -407,6 +405,17 @@ test("generate data for embedding from action props", () => {
|
|
|
407
405
|
name: "onClick",
|
|
408
406
|
value: [{ type: "execute", code: `boxState = 'success'` }],
|
|
409
407
|
},
|
|
408
|
+
{
|
|
409
|
+
type: "action",
|
|
410
|
+
name: "onChange",
|
|
411
|
+
value: [
|
|
412
|
+
{
|
|
413
|
+
type: "execute",
|
|
414
|
+
args: ["value"],
|
|
415
|
+
code: `boxState = value`,
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
},
|
|
410
419
|
],
|
|
411
420
|
children: [],
|
|
412
421
|
},
|
|
@@ -442,10 +451,24 @@ test("generate data for embedding from action props", () => {
|
|
|
442
451
|
value: [
|
|
443
452
|
{
|
|
444
453
|
type: "execute",
|
|
454
|
+
args: [],
|
|
445
455
|
code: expect.stringMatching(/\$ws\$dataSource\$\w+ = 'success'/),
|
|
446
456
|
},
|
|
447
457
|
],
|
|
448
458
|
},
|
|
459
|
+
{
|
|
460
|
+
id: expectString,
|
|
461
|
+
instanceId: expectString,
|
|
462
|
+
type: "action",
|
|
463
|
+
name: "onChange",
|
|
464
|
+
value: [
|
|
465
|
+
{
|
|
466
|
+
type: "execute",
|
|
467
|
+
args: ["value"],
|
|
468
|
+
code: expect.stringMatching(/\$ws\$dataSource\$\w+ = value/),
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
},
|
|
449
472
|
],
|
|
450
473
|
dataSources: [
|
|
451
474
|
{
|
|
@@ -464,3 +487,67 @@ test("generate data for embedding from action props", () => {
|
|
|
464
487
|
styles: [],
|
|
465
488
|
});
|
|
466
489
|
});
|
|
490
|
+
|
|
491
|
+
test("add namespace to selected components in embed template", () => {
|
|
492
|
+
expect(
|
|
493
|
+
namespaceMeta(
|
|
494
|
+
{
|
|
495
|
+
type: "container",
|
|
496
|
+
label: "",
|
|
497
|
+
icon: "",
|
|
498
|
+
requiredAncestors: ["Button", "Box"],
|
|
499
|
+
invalidAncestors: ["Tooltip"],
|
|
500
|
+
indexWithinAncestor: "Tooltip",
|
|
501
|
+
template: [
|
|
502
|
+
{
|
|
503
|
+
type: "instance",
|
|
504
|
+
component: "Tooltip",
|
|
505
|
+
children: [
|
|
506
|
+
{ type: "text", value: "Some text" },
|
|
507
|
+
{
|
|
508
|
+
type: "instance",
|
|
509
|
+
component: "Box",
|
|
510
|
+
children: [
|
|
511
|
+
{
|
|
512
|
+
type: "instance",
|
|
513
|
+
component: "Button",
|
|
514
|
+
children: [],
|
|
515
|
+
},
|
|
516
|
+
],
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
},
|
|
520
|
+
],
|
|
521
|
+
},
|
|
522
|
+
"my-namespace",
|
|
523
|
+
new Set(["Tooltip", "Button"])
|
|
524
|
+
)
|
|
525
|
+
).toEqual({
|
|
526
|
+
type: "container",
|
|
527
|
+
label: "",
|
|
528
|
+
icon: "",
|
|
529
|
+
requiredAncestors: ["my-namespace:Button", "Box"],
|
|
530
|
+
invalidAncestors: ["my-namespace:Tooltip"],
|
|
531
|
+
indexWithinAncestor: "my-namespace:Tooltip",
|
|
532
|
+
template: [
|
|
533
|
+
{
|
|
534
|
+
type: "instance",
|
|
535
|
+
component: "my-namespace:Tooltip",
|
|
536
|
+
children: [
|
|
537
|
+
{ type: "text", value: "Some text" },
|
|
538
|
+
{
|
|
539
|
+
type: "instance",
|
|
540
|
+
component: "Box",
|
|
541
|
+
children: [
|
|
542
|
+
{
|
|
543
|
+
type: "instance",
|
|
544
|
+
component: "my-namespace:Button",
|
|
545
|
+
children: [],
|
|
546
|
+
},
|
|
547
|
+
],
|
|
548
|
+
},
|
|
549
|
+
],
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
});
|
|
553
|
+
});
|
package/src/embed-template.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { nanoid } from "nanoid";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
Instance,
|
|
5
5
|
type InstancesList,
|
|
6
6
|
PropsList,
|
|
7
7
|
StyleSourceSelectionsList,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { StyleValue, type StyleProperty } from "@webstudio-is/css-data";
|
|
14
14
|
import type { Simplify } from "type-fest";
|
|
15
15
|
import { encodeDataSourceVariable, validateExpression } from "./expression";
|
|
16
|
+
import type { WsComponentMeta } from "./components/component-meta";
|
|
16
17
|
|
|
17
18
|
const EmbedTemplateText = z.object({
|
|
18
19
|
type: z.literal("text"),
|
|
@@ -21,43 +22,48 @@ const EmbedTemplateText = z.object({
|
|
|
21
22
|
|
|
22
23
|
type EmbedTemplateText = z.infer<typeof EmbedTemplateText>;
|
|
23
24
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
const EmbedTemplateDataSource = z.union([
|
|
26
|
+
z.object({
|
|
27
|
+
type: z.literal("variable"),
|
|
28
|
+
initialValue: z.union([
|
|
29
|
+
z.string(),
|
|
30
|
+
z.number(),
|
|
31
|
+
z.boolean(),
|
|
32
|
+
z.array(z.string()),
|
|
33
|
+
]),
|
|
34
|
+
}),
|
|
31
35
|
z.object({
|
|
32
36
|
type: z.literal("expression"),
|
|
33
|
-
name: z.string(),
|
|
34
37
|
code: z.string(),
|
|
35
38
|
}),
|
|
36
39
|
]);
|
|
37
40
|
|
|
41
|
+
type EmbedTemplateDataSource = z.infer<typeof EmbedTemplateDataSource>;
|
|
42
|
+
|
|
38
43
|
const EmbedTemplateProp = z.union([
|
|
44
|
+
z.object({
|
|
45
|
+
type: z.literal("dataSource"),
|
|
46
|
+
name: z.string(),
|
|
47
|
+
dataSourceName: z.string(),
|
|
48
|
+
}),
|
|
39
49
|
z.object({
|
|
40
50
|
type: z.literal("number"),
|
|
41
51
|
name: z.string(),
|
|
42
|
-
dataSourceRef: z.optional(DataSourceRef),
|
|
43
52
|
value: z.number(),
|
|
44
53
|
}),
|
|
45
54
|
z.object({
|
|
46
55
|
type: z.literal("string"),
|
|
47
56
|
name: z.string(),
|
|
48
|
-
dataSourceRef: z.optional(DataSourceRef),
|
|
49
57
|
value: z.string(),
|
|
50
58
|
}),
|
|
51
59
|
z.object({
|
|
52
60
|
type: z.literal("boolean"),
|
|
53
61
|
name: z.string(),
|
|
54
|
-
dataSourceRef: z.optional(DataSourceRef),
|
|
55
62
|
value: z.boolean(),
|
|
56
63
|
}),
|
|
57
64
|
z.object({
|
|
58
65
|
type: z.literal("string[]"),
|
|
59
66
|
name: z.string(),
|
|
60
|
-
dataSourceRef: z.optional(DataSourceRef),
|
|
61
67
|
value: z.array(z.string()),
|
|
62
68
|
}),
|
|
63
69
|
z.object({
|
|
@@ -66,6 +72,7 @@ const EmbedTemplateProp = z.union([
|
|
|
66
72
|
value: z.array(
|
|
67
73
|
z.object({
|
|
68
74
|
type: z.literal("execute"),
|
|
75
|
+
args: z.optional(z.array(z.string())),
|
|
69
76
|
code: z.string(),
|
|
70
77
|
})
|
|
71
78
|
),
|
|
@@ -94,6 +101,7 @@ export type EmbedTemplateInstance = {
|
|
|
94
101
|
type: "instance";
|
|
95
102
|
component: string;
|
|
96
103
|
label?: string;
|
|
104
|
+
dataSources?: Record<string, EmbedTemplateDataSource>;
|
|
97
105
|
props?: EmbedTemplateProp[];
|
|
98
106
|
styles?: EmbedTemplateStyleDecl[];
|
|
99
107
|
children: Array<EmbedTemplateInstance | EmbedTemplateText>;
|
|
@@ -105,6 +113,7 @@ export const EmbedTemplateInstance: z.ZodType<EmbedTemplateInstance> = z.lazy(
|
|
|
105
113
|
type: z.literal("instance"),
|
|
106
114
|
component: z.string(),
|
|
107
115
|
label: z.optional(z.string()),
|
|
116
|
+
dataSources: z.optional(z.record(z.string(), EmbedTemplateDataSource)),
|
|
108
117
|
props: z.optional(z.array(EmbedTemplateProp)),
|
|
109
118
|
styles: z.optional(z.array(EmbedTemplateStyleDecl)),
|
|
110
119
|
children: WsEmbedTemplate,
|
|
@@ -117,6 +126,25 @@ export const WsEmbedTemplate = z.lazy(() =>
|
|
|
117
126
|
|
|
118
127
|
export type WsEmbedTemplate = z.infer<typeof WsEmbedTemplate>;
|
|
119
128
|
|
|
129
|
+
const getDataSourceValue = (
|
|
130
|
+
value: Extract<EmbedTemplateDataSource, { type: "variable" }>["initialValue"]
|
|
131
|
+
): Extract<DataSource, { type: "variable" }>["value"] => {
|
|
132
|
+
if (typeof value === "string") {
|
|
133
|
+
return { type: "string", value };
|
|
134
|
+
}
|
|
135
|
+
if (typeof value === "number") {
|
|
136
|
+
return { type: "number", value };
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === "boolean") {
|
|
139
|
+
return { type: "boolean", value };
|
|
140
|
+
}
|
|
141
|
+
if (Array.isArray(value)) {
|
|
142
|
+
return { type: "string[]", value };
|
|
143
|
+
}
|
|
144
|
+
value satisfies never;
|
|
145
|
+
throw Error("Impossible case");
|
|
146
|
+
};
|
|
147
|
+
|
|
120
148
|
const createInstancesFromTemplate = (
|
|
121
149
|
treeTemplate: WsEmbedTemplate,
|
|
122
150
|
instances: InstancesList,
|
|
@@ -132,6 +160,38 @@ const createInstancesFromTemplate = (
|
|
|
132
160
|
if (item.type === "instance") {
|
|
133
161
|
const instanceId = nanoid();
|
|
134
162
|
|
|
163
|
+
if (item.dataSources) {
|
|
164
|
+
for (const [name, dataSource] of Object.entries(item.dataSources)) {
|
|
165
|
+
if (dataSourceByRef.has(name)) {
|
|
166
|
+
throw Error(`${name} data source already defined`);
|
|
167
|
+
}
|
|
168
|
+
if (dataSource.type === "variable") {
|
|
169
|
+
dataSourceByRef.set(name, {
|
|
170
|
+
type: "variable",
|
|
171
|
+
id: nanoid(),
|
|
172
|
+
scopeInstanceId: instanceId,
|
|
173
|
+
name,
|
|
174
|
+
value: getDataSourceValue(dataSource.initialValue),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (dataSource.type === "expression") {
|
|
178
|
+
dataSourceByRef.set(name, {
|
|
179
|
+
type: "expression",
|
|
180
|
+
id: nanoid(),
|
|
181
|
+
scopeInstanceId: instanceId,
|
|
182
|
+
name,
|
|
183
|
+
// replace all references with variable names
|
|
184
|
+
code: validateExpression(dataSource.code, {
|
|
185
|
+
transformIdentifier: (ref) => {
|
|
186
|
+
const id = dataSourceByRef.get(ref)?.id ?? ref;
|
|
187
|
+
return encodeDataSourceVariable(id);
|
|
188
|
+
},
|
|
189
|
+
}),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
135
195
|
// populate props
|
|
136
196
|
if (item.props) {
|
|
137
197
|
for (const prop of item.props) {
|
|
@@ -144,12 +204,18 @@ const createInstancesFromTemplate = (
|
|
|
144
204
|
type: "action",
|
|
145
205
|
name: prop.name,
|
|
146
206
|
value: prop.value.map((value) => {
|
|
207
|
+
const args = value.args ?? [];
|
|
147
208
|
return {
|
|
148
209
|
type: "execute",
|
|
210
|
+
args,
|
|
149
211
|
// replace all references with variable names
|
|
150
212
|
code: validateExpression(value.code, {
|
|
151
213
|
effectful: true,
|
|
152
214
|
transformIdentifier: (ref) => {
|
|
215
|
+
// bypass arguments without changes
|
|
216
|
+
if (args.includes(ref)) {
|
|
217
|
+
return ref;
|
|
218
|
+
}
|
|
153
219
|
const id = dataSourceByRef.get(ref)?.id ?? ref;
|
|
154
220
|
return encodeDataSourceVariable(id);
|
|
155
221
|
},
|
|
@@ -159,51 +225,21 @@ const createInstancesFromTemplate = (
|
|
|
159
225
|
});
|
|
160
226
|
continue;
|
|
161
227
|
}
|
|
162
|
-
if (prop.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
let dataSource = dataSourceByRef.get(prop.dataSourceRef.name);
|
|
167
|
-
if (dataSource === undefined) {
|
|
168
|
-
const id = nanoid();
|
|
169
|
-
const { name: propName, dataSourceRef, ...rest } = prop;
|
|
170
|
-
if (dataSourceRef.type === "variable") {
|
|
171
|
-
dataSource = {
|
|
172
|
-
type: "variable",
|
|
173
|
-
id,
|
|
174
|
-
// the first instance where data source is appeared in becomes its scope
|
|
175
|
-
scopeInstanceId: instanceId,
|
|
176
|
-
name: dataSourceRef.name,
|
|
177
|
-
value: rest,
|
|
178
|
-
};
|
|
179
|
-
dataSourceByRef.set(dataSourceRef.name, dataSource);
|
|
180
|
-
} else if (dataSourceRef.type === "expression") {
|
|
181
|
-
dataSource = {
|
|
182
|
-
type: "expression",
|
|
183
|
-
id,
|
|
184
|
-
scopeInstanceId: instanceId,
|
|
185
|
-
name: dataSourceRef.name,
|
|
186
|
-
// replace all references with variable names
|
|
187
|
-
code: validateExpression(dataSourceRef.code, {
|
|
188
|
-
transformIdentifier: (ref) => {
|
|
189
|
-
const id = dataSourceByRef.get(ref)?.id ?? ref;
|
|
190
|
-
return encodeDataSourceVariable(id);
|
|
191
|
-
},
|
|
192
|
-
}),
|
|
193
|
-
};
|
|
194
|
-
dataSourceByRef.set(dataSourceRef.name, dataSource);
|
|
195
|
-
} else {
|
|
196
|
-
dataSourceRef satisfies never;
|
|
197
|
-
continue;
|
|
228
|
+
if (prop.type === "dataSource") {
|
|
229
|
+
const dataSource = dataSourceByRef.get(prop.dataSourceName);
|
|
230
|
+
if (dataSource === undefined) {
|
|
231
|
+
throw Error(`${prop.dataSourceName} data source is not defined`);
|
|
198
232
|
}
|
|
233
|
+
props.push({
|
|
234
|
+
id: propId,
|
|
235
|
+
instanceId,
|
|
236
|
+
type: "dataSource",
|
|
237
|
+
name: prop.name,
|
|
238
|
+
value: dataSource.id,
|
|
239
|
+
});
|
|
240
|
+
continue;
|
|
199
241
|
}
|
|
200
|
-
props.push({
|
|
201
|
-
id: propId,
|
|
202
|
-
instanceId,
|
|
203
|
-
type: "dataSource",
|
|
204
|
-
name: prop.name,
|
|
205
|
-
value: dataSource.id,
|
|
206
|
-
});
|
|
242
|
+
props.push({ id: propId, instanceId, ...prop });
|
|
207
243
|
}
|
|
208
244
|
}
|
|
209
245
|
|
|
@@ -301,3 +337,60 @@ export const generateDataFromEmbedTemplate = (
|
|
|
301
337
|
export type EmbedTemplateData = ReturnType<
|
|
302
338
|
typeof generateDataFromEmbedTemplate
|
|
303
339
|
>;
|
|
340
|
+
|
|
341
|
+
const namespaceEmbedTemplateComponents = (
|
|
342
|
+
template: WsEmbedTemplate,
|
|
343
|
+
namespace: string,
|
|
344
|
+
components: Set<EmbedTemplateInstance["component"]>
|
|
345
|
+
): WsEmbedTemplate => {
|
|
346
|
+
return template.map((item) => {
|
|
347
|
+
if (item.type === "text") {
|
|
348
|
+
return item;
|
|
349
|
+
}
|
|
350
|
+
if (item.type === "instance") {
|
|
351
|
+
const prefix = components.has(item.component) ? `${namespace}:` : "";
|
|
352
|
+
return {
|
|
353
|
+
...item,
|
|
354
|
+
component: `${prefix}${item.component}`,
|
|
355
|
+
children: namespaceEmbedTemplateComponents(
|
|
356
|
+
item.children,
|
|
357
|
+
namespace,
|
|
358
|
+
components
|
|
359
|
+
),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
item satisfies never;
|
|
363
|
+
throw Error("Impossible case");
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
export const namespaceMeta = (
|
|
368
|
+
meta: WsComponentMeta,
|
|
369
|
+
namespace: string,
|
|
370
|
+
components: Set<EmbedTemplateInstance["component"]>
|
|
371
|
+
) => {
|
|
372
|
+
const newMeta = { ...meta };
|
|
373
|
+
if (newMeta.requiredAncestors) {
|
|
374
|
+
newMeta.requiredAncestors = newMeta.requiredAncestors.map((component) =>
|
|
375
|
+
components.has(component) ? `${namespace}:${component}` : component
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
if (newMeta.invalidAncestors) {
|
|
379
|
+
newMeta.invalidAncestors = newMeta.invalidAncestors.map((component) =>
|
|
380
|
+
components.has(component) ? `${namespace}:${component}` : component
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
if (newMeta.indexWithinAncestor) {
|
|
384
|
+
newMeta.indexWithinAncestor = components.has(newMeta.indexWithinAncestor)
|
|
385
|
+
? `${namespace}:${newMeta.indexWithinAncestor}`
|
|
386
|
+
: newMeta.indexWithinAncestor;
|
|
387
|
+
}
|
|
388
|
+
if (newMeta.template) {
|
|
389
|
+
newMeta.template = namespaceEmbedTemplateComponents(
|
|
390
|
+
newMeta.template,
|
|
391
|
+
namespace,
|
|
392
|
+
components
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
return newMeta;
|
|
396
|
+
};
|
package/src/expression.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
encodeDataSourceVariable,
|
|
5
5
|
executeComputingExpressions,
|
|
6
6
|
executeEffectfulExpression,
|
|
7
|
+
computeExpressionsDependencies,
|
|
7
8
|
generateComputingExpressions,
|
|
8
9
|
generateEffectfulExpression,
|
|
9
10
|
validateExpression,
|
|
@@ -186,7 +187,11 @@ test("encode/decode variable names", () => {
|
|
|
186
187
|
|
|
187
188
|
test("generate effectful expression", () => {
|
|
188
189
|
expect(
|
|
189
|
-
generateEffectfulExpression(
|
|
190
|
+
generateEffectfulExpression(
|
|
191
|
+
`var0 = var0 + var1`,
|
|
192
|
+
new Set(),
|
|
193
|
+
new Set(["var0", "var1"])
|
|
194
|
+
)
|
|
190
195
|
).toMatchInlineSnapshot(`
|
|
191
196
|
"let var0 = _variables.get('var0');
|
|
192
197
|
let var1 = _variables.get('var1');
|
|
@@ -197,7 +202,11 @@ test("generate effectful expression", () => {
|
|
|
197
202
|
`);
|
|
198
203
|
|
|
199
204
|
expect(
|
|
200
|
-
generateEffectfulExpression(
|
|
205
|
+
generateEffectfulExpression(
|
|
206
|
+
`var0 = var1 + 1`,
|
|
207
|
+
new Set(),
|
|
208
|
+
new Set(["var0", "var1"])
|
|
209
|
+
)
|
|
201
210
|
).toMatchInlineSnapshot(`
|
|
202
211
|
"let var1 = _variables.get('var1');
|
|
203
212
|
let var0;
|
|
@@ -212,6 +221,7 @@ test("add only used variables in effectful expression", () => {
|
|
|
212
221
|
expect(
|
|
213
222
|
generateEffectfulExpression(
|
|
214
223
|
`var0 = var1 + 1`,
|
|
224
|
+
new Set(),
|
|
215
225
|
new Set(["var0", "var1", "var2"])
|
|
216
226
|
)
|
|
217
227
|
).toMatchInlineSnapshot(`
|
|
@@ -224,10 +234,34 @@ test("add only used variables in effectful expression", () => {
|
|
|
224
234
|
`);
|
|
225
235
|
});
|
|
226
236
|
|
|
227
|
-
test("
|
|
237
|
+
test("support effectful expression arguments", () => {
|
|
238
|
+
expect(
|
|
239
|
+
generateEffectfulExpression(
|
|
240
|
+
`var0 = arg0 + 1`,
|
|
241
|
+
new Set(["arg0"]),
|
|
242
|
+
new Set(["var0"])
|
|
243
|
+
)
|
|
244
|
+
).toMatchInlineSnapshot(`
|
|
245
|
+
"let arg0 = _args.get('arg0');
|
|
246
|
+
let var0;
|
|
247
|
+
var0 = arg0 + 1;
|
|
248
|
+
return new Map([
|
|
249
|
+
['var0', var0],
|
|
250
|
+
]);"
|
|
251
|
+
`);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("forbid not allowed variables or arguments in effectful expression", () => {
|
|
228
255
|
expect(() => {
|
|
229
|
-
generateEffectfulExpression(
|
|
256
|
+
generateEffectfulExpression(
|
|
257
|
+
`var0 = var0 + var1`,
|
|
258
|
+
new Set(),
|
|
259
|
+
new Set(["var0"])
|
|
260
|
+
);
|
|
230
261
|
}).toThrowError(/Unknown dependency "var1"/);
|
|
262
|
+
expect(() => {
|
|
263
|
+
generateEffectfulExpression(`var0 = arg0`, new Set(), new Set(["var0"]));
|
|
264
|
+
}).toThrowError(/Unknown dependency "arg0"/);
|
|
231
265
|
});
|
|
232
266
|
|
|
233
267
|
test("execute effectful expression", () => {
|
|
@@ -235,7 +269,41 @@ test("execute effectful expression", () => {
|
|
|
235
269
|
["var0", 2],
|
|
236
270
|
["var1", 3],
|
|
237
271
|
]);
|
|
238
|
-
expect(
|
|
239
|
-
new Map(
|
|
272
|
+
expect(
|
|
273
|
+
executeEffectfulExpression(`var0 = var0 + var1`, new Map(), variables)
|
|
274
|
+
).toEqual(new Map([["var0", 5]]));
|
|
275
|
+
|
|
276
|
+
expect(
|
|
277
|
+
executeEffectfulExpression(`arg0 = 5`, new Map([["arg0", 0]]), new Map())
|
|
278
|
+
).toEqual(new Map());
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("compute expressions dependencies", () => {
|
|
282
|
+
const expressions = new Map([
|
|
283
|
+
["exp1", `var1`],
|
|
284
|
+
["exp2", `exp1 + exp1`],
|
|
285
|
+
["exp3", `exp1 + exp2`],
|
|
286
|
+
["exp4", `var1 + exp1`],
|
|
287
|
+
]);
|
|
288
|
+
expect(computeExpressionsDependencies(expressions)).toEqual(
|
|
289
|
+
new Map([
|
|
290
|
+
["exp4", new Set(["var1", "exp1"])],
|
|
291
|
+
["exp3", new Set(["var1", "exp1", "exp2"])],
|
|
292
|
+
["exp2", new Set(["var1", "exp1"])],
|
|
293
|
+
["exp1", new Set(["var1"])],
|
|
294
|
+
])
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("handle cyclic dependencies", () => {
|
|
299
|
+
const expressions = new Map([
|
|
300
|
+
["exp1", `exp2 + var1`],
|
|
301
|
+
["exp2", `exp1 + var1`],
|
|
302
|
+
]);
|
|
303
|
+
expect(computeExpressionsDependencies(expressions)).toEqual(
|
|
304
|
+
new Map([
|
|
305
|
+
["exp2", new Set(["var1", "exp1", "exp2"])],
|
|
306
|
+
["exp1", new Set(["var1", "exp1", "exp2"])],
|
|
307
|
+
])
|
|
240
308
|
);
|
|
241
309
|
});
|