@webstudio-is/react-sdk 0.81.0 → 0.83.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 (49) hide show
  1. package/LICENSE +661 -21
  2. package/lib/cjs/components/component-meta.js +5 -0
  3. package/lib/cjs/components/components-utils.js +1 -0
  4. package/lib/cjs/css/index.js +0 -1
  5. package/lib/cjs/embed-template.js +30 -1
  6. package/lib/cjs/expression.js +47 -4
  7. package/lib/cjs/index.js +1 -0
  8. package/lib/cjs/props.js +6 -1
  9. package/lib/cjs/tree/root.js +1 -0
  10. package/lib/cjs/tree/webstudio-component.js +24 -10
  11. package/lib/components/component-meta.js +5 -0
  12. package/lib/components/components-utils.js +1 -0
  13. package/lib/css/index.js +0 -1
  14. package/lib/embed-template.js +38 -1
  15. package/lib/expression.js +47 -4
  16. package/lib/index.js +2 -0
  17. package/lib/props.js +6 -1
  18. package/lib/tree/create-elements-tree.js +3 -1
  19. package/lib/tree/root.js +8 -2
  20. package/lib/tree/webstudio-component.js +25 -11
  21. package/lib/types/app/root.d.ts +1 -2
  22. package/lib/types/components/component-meta.d.ts +16 -8
  23. package/lib/types/context.d.ts +1 -1
  24. package/lib/types/css/index.d.ts +0 -1
  25. package/lib/types/css/normalize.d.ts +47 -47
  26. package/lib/types/embed-template.d.ts +14 -1
  27. package/lib/types/expression.d.ts +3 -2
  28. package/lib/types/index.d.ts +1 -1
  29. package/lib/types/props.d.ts +1 -0
  30. package/lib/types/tree/create-elements-tree.d.ts +5 -5
  31. package/lib/types/tree/root.d.ts +4 -4
  32. package/lib/types/tree/webstudio-component.d.ts +15 -7
  33. package/package.json +14 -15
  34. package/src/components/component-meta.ts +5 -0
  35. package/src/context.tsx +1 -0
  36. package/src/css/index.ts +0 -1
  37. package/src/embed-template.test.ts +77 -1
  38. package/src/embed-template.ts +34 -1
  39. package/src/expression.test.ts +74 -6
  40. package/src/expression.ts +55 -2
  41. package/src/index.ts +1 -0
  42. package/src/props.ts +6 -1
  43. package/src/tree/create-elements-tree.tsx +17 -5
  44. package/src/tree/root.ts +14 -3
  45. package/src/tree/webstudio-component.tsx +41 -14
  46. package/lib/cjs/css/get-browser-style.js +0 -83
  47. package/lib/css/get-browser-style.js +0 -65
  48. package/lib/types/css/get-browser-style.d.ts +0 -2
  49. 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/index.ts CHANGED
@@ -26,6 +26,7 @@ export {
26
26
  executeComputingExpressions,
27
27
  generateEffectfulExpression,
28
28
  executeEffectfulExpression,
29
+ computeExpressionsDependencies,
29
30
  encodeDataSourceVariable,
30
31
  encodeVariablesMap,
31
32
  decodeDataSourceVariable,
package/src/props.ts CHANGED
@@ -55,15 +55,20 @@ export const useInstanceProps = (instanceId: Instance["id"]) => {
55
55
  continue;
56
56
  }
57
57
  if (prop.type === "action") {
58
- instancePropsObject[prop.name] = () => {
58
+ instancePropsObject[prop.name] = (...args: unknown[]) => {
59
59
  // prevent all actions in canvas mode
60
60
  if (renderer === "canvas") {
61
61
  return;
62
62
  }
63
63
  for (const value of prop.value) {
64
64
  if (value.type === "execute") {
65
+ const argsMap = new Map<string, unknown>();
66
+ for (const [i, name] of value.args.entries()) {
67
+ argsMap.set(name, args[i]);
68
+ }
65
69
  const newValues = executeEffectfulExpression(
66
70
  value.code,
71
+ argsMap,
67
72
  dataSourceValues
68
73
  );
69
74
  setDataSourceValues(newValues);
@@ -1,4 +1,8 @@
1
- import { type ComponentProps, Fragment } from "react";
1
+ import {
2
+ Fragment,
3
+ type ForwardRefExoticComponent,
4
+ type RefAttributes,
5
+ } from "react";
2
6
  import type { ReadableAtom } from "nanostores";
3
7
  import { Scripts, ScrollRestoration } from "@remix-run/react";
4
8
  import type { Assets } from "@webstudio-is/asset-uploader";
@@ -10,7 +14,7 @@ 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";
14
18
 
15
19
  type InstanceSelector = Instance["id"][];
16
20
 
@@ -36,11 +40,15 @@ export const createElementsTree = ({
36
40
  pagesStore: ReadableAtom<Pages>;
37
41
  executeEffectfulExpression: (
38
42
  expression: string,
43
+ args: DataSourceValues,
39
44
  values: DataSourceValues
40
45
  ) => DataSourceValues;
41
46
  dataSourceValuesStore: ReadableAtom<DataSourceValues>;
42
47
  onDataSourceUpdate: (newValues: DataSourceValues) => void;
43
- Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
48
+
49
+ Component: ForwardRefExoticComponent<
50
+ WebstudioComponentProps & RefAttributes<HTMLElement>
51
+ >;
44
52
  components: Components;
45
53
  }) => {
46
54
  const rootInstance = instances.get(rootInstanceId);
@@ -110,7 +118,9 @@ const createInstanceChildrenElements = ({
110
118
  instances: Instances;
111
119
  instanceSelector: InstanceSelector;
112
120
  children: Instance["children"];
113
- Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
121
+ Component: ForwardRefExoticComponent<
122
+ WebstudioComponentProps & RefAttributes<HTMLElement>
123
+ >;
114
124
  components: Components;
115
125
  }) => {
116
126
  const elements = [];
@@ -152,7 +162,9 @@ const createInstanceElement = ({
152
162
  }: {
153
163
  instance: Instance;
154
164
  instanceSelector: InstanceSelector;
155
- Component: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
165
+ Component: ForwardRefExoticComponent<
166
+ WebstudioComponentProps & RefAttributes<HTMLElement>
167
+ >;
156
168
  children?: Array<JSX.Element | string>;
157
169
  components: Components;
158
170
  }) => {
package/src/tree/root.ts CHANGED
@@ -1,4 +1,9 @@
1
- import { useRef, type ComponentProps, useCallback } from "react";
1
+ import {
2
+ useRef,
3
+ useCallback,
4
+ type ForwardRefExoticComponent,
5
+ type RefAttributes,
6
+ } from "react";
2
7
  import {
3
8
  atom,
4
9
  computed,
@@ -8,7 +13,10 @@ import {
8
13
  import { type Build, type Page } from "@webstudio-is/project-build";
9
14
  import type { Asset } from "@webstudio-is/asset-uploader";
10
15
  import { createElementsTree } from "./create-elements-tree";
11
- import { WebstudioComponent } from "./webstudio-component";
16
+ import {
17
+ WebstudioComponent,
18
+ type WebstudioComponentProps,
19
+ } from "./webstudio-component";
12
20
  import { getPropsByInstanceId } from "../props";
13
21
  import type { Components } from "../components/components-utils";
14
22
  import type { Params, DataSourceValues } from "../context";
@@ -30,9 +38,12 @@ type RootProps = {
30
38
  executeComputingExpressions: (values: DataSourceValues) => DataSourceValues;
31
39
  executeEffectfulExpression: (
32
40
  expression: string,
41
+ args: DataSourceValues,
33
42
  values: DataSourceValues
34
43
  ) => DataSourceValues;
35
- Component?: (props: ComponentProps<typeof WebstudioComponent>) => JSX.Element;
44
+ Component?: ForwardRefExoticComponent<
45
+ WebstudioComponentProps & RefAttributes<HTMLElement>
46
+ >;
36
47
  components: Components;
37
48
  };
38
49
 
@@ -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,42 @@ 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 collapsedAttribute = "data-ws-collapsed" as const;
69
+
70
+ export type WebstudioAttributes = {
71
+ [idAttribute]?: string | undefined;
72
+ [selectorIdAttribute]?: string | undefined;
73
+ [componentAttribute]?: string | undefined;
74
+ [showAttribute]?: string | undefined;
75
+ [collapsedAttribute]?: string | undefined;
64
76
  };
65
77
 
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";
78
+ export const splitPropsWithWebstudioAttributes = <
79
+ P extends WebstudioAttributes,
80
+ >({
81
+ [idAttribute]: idAttributeValue,
82
+ [componentAttribute]: componentAttributeValue,
83
+ [showAttribute]: showAttributeValue,
84
+ [collapsedAttribute]: collapsedAttributeValue,
85
+ [selectorIdAttribute]: parentIdAttributeValue,
86
+ ...props
87
+ }: P): [WebstudioAttributes, Omit<P, keyof WebstudioAttributes>] => [
88
+ {
89
+ [idAttribute]: idAttributeValue,
90
+ [componentAttribute]: componentAttributeValue,
91
+ [showAttribute]: showAttributeValue,
92
+ [collapsedAttribute]: collapsedAttributeValue,
93
+ [selectorIdAttribute]: parentIdAttributeValue,
94
+ },
95
+ props,
96
+ ];
@@ -1,83 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
- var get_browser_style_exports = {};
20
- __export(get_browser_style_exports, {
21
- getBrowserStyle: () => getBrowserStyle
22
- });
23
- module.exports = __toCommonJS(get_browser_style_exports);
24
- var import_detect_font = require("detect-font");
25
- var import_css_data = require("@webstudio-is/css-data");
26
- var import_css_data2 = require("@webstudio-is/css-data");
27
- const unitsList = Object.values(import_css_data2.units).flat();
28
- const unitRegex = new RegExp(`${unitsList.join("|")}`);
29
- const parseValue = (property, value) => {
30
- const number = Number.parseFloat(value);
31
- const parsedUnit = unitRegex.exec(value);
32
- if (value === "rgba(0, 0, 0, 0)") {
33
- value = "transparent";
34
- }
35
- if (Number.isNaN(number)) {
36
- const values = import_css_data.keywordValues[property];
37
- if (values?.includes(value)) {
38
- return {
39
- type: "keyword",
40
- value
41
- };
42
- }
43
- return {
44
- type: "unparsed",
45
- value
46
- };
47
- }
48
- if (number === 0 && property in import_css_data2.properties) {
49
- return import_css_data2.properties[property].initial;
50
- }
51
- if (parsedUnit === null) {
52
- return {
53
- type: "unit",
54
- unit: "number",
55
- value: number
56
- };
57
- }
58
- return {
59
- type: "unit",
60
- unit: parsedUnit[0],
61
- value: number
62
- };
63
- };
64
- const getBrowserStyle = (element) => {
65
- const browserStyle = {};
66
- if (element === void 0) {
67
- return browserStyle;
68
- }
69
- let knownProperty;
70
- const computedStyle = getComputedStyle(element);
71
- for (knownProperty in import_css_data2.properties) {
72
- if (knownProperty in computedStyle === false) {
73
- continue;
74
- }
75
- const computedValue = computedStyle[knownProperty];
76
- browserStyle[knownProperty] = parseValue(knownProperty, computedValue);
77
- }
78
- browserStyle.fontFamily = {
79
- type: "fontFamily",
80
- value: [(0, import_detect_font.detectFont)(element)]
81
- };
82
- return browserStyle;
83
- };
@@ -1,65 +0,0 @@
1
- import { detectFont } from "detect-font";
2
- import {
3
- keywordValues
4
- } from "@webstudio-is/css-data";
5
- import { properties, units } from "@webstudio-is/css-data";
6
- const unitsList = Object.values(units).flat();
7
- const unitRegex = new RegExp(`${unitsList.join("|")}`);
8
- const parseValue = (property, value) => {
9
- const number = Number.parseFloat(value);
10
- const parsedUnit = unitRegex.exec(value);
11
- if (value === "rgba(0, 0, 0, 0)") {
12
- value = "transparent";
13
- }
14
- if (Number.isNaN(number)) {
15
- const values = keywordValues[property];
16
- if (values?.includes(value)) {
17
- return {
18
- type: "keyword",
19
- value
20
- };
21
- }
22
- return {
23
- type: "unparsed",
24
- value
25
- };
26
- }
27
- if (number === 0 && property in properties) {
28
- return properties[property].initial;
29
- }
30
- if (parsedUnit === null) {
31
- return {
32
- type: "unit",
33
- unit: "number",
34
- value: number
35
- };
36
- }
37
- return {
38
- type: "unit",
39
- unit: parsedUnit[0],
40
- value: number
41
- };
42
- };
43
- const getBrowserStyle = (element) => {
44
- const browserStyle = {};
45
- if (element === void 0) {
46
- return browserStyle;
47
- }
48
- let knownProperty;
49
- const computedStyle = getComputedStyle(element);
50
- for (knownProperty in properties) {
51
- if (knownProperty in computedStyle === false) {
52
- continue;
53
- }
54
- const computedValue = computedStyle[knownProperty];
55
- browserStyle[knownProperty] = parseValue(knownProperty, computedValue);
56
- }
57
- browserStyle.fontFamily = {
58
- type: "fontFamily",
59
- value: [detectFont(element)]
60
- };
61
- return browserStyle;
62
- };
63
- export {
64
- getBrowserStyle
65
- };
@@ -1,2 +0,0 @@
1
- import { type Style } from "@webstudio-is/css-data";
2
- export declare const getBrowserStyle: (element?: Element) => Style;
@@ -1,81 +0,0 @@
1
- import { detectFont } from "detect-font";
2
- import {
3
- type Style,
4
- type StyleValue,
5
- type Unit,
6
- keywordValues,
7
- } from "@webstudio-is/css-data";
8
- import { properties, units } from "@webstudio-is/css-data";
9
-
10
- const unitsList = Object.values(units).flat();
11
- const unitRegex = new RegExp(`${unitsList.join("|")}`);
12
-
13
- // @todo use a parser
14
- const parseValue = (
15
- property: keyof typeof properties,
16
- value: string
17
- ): StyleValue => {
18
- const number = Number.parseFloat(value);
19
- const parsedUnit = unitRegex.exec(value);
20
- if (value === "rgba(0, 0, 0, 0)") {
21
- value = "transparent";
22
- }
23
- if (Number.isNaN(number)) {
24
- const values = keywordValues[
25
- property as keyof typeof keywordValues
26
- ] as ReadonlyArray<string>;
27
-
28
- if (values?.includes(value)) {
29
- return {
30
- type: "keyword",
31
- value: value,
32
- };
33
- }
34
-
35
- return {
36
- type: "unparsed",
37
- value: value,
38
- };
39
- }
40
-
41
- if (number === 0 && property in properties) {
42
- return properties[property].initial;
43
- }
44
-
45
- if (parsedUnit === null) {
46
- return {
47
- type: "unit",
48
- unit: "number",
49
- value: number,
50
- };
51
- }
52
-
53
- return {
54
- type: "unit",
55
- unit: parsedUnit[0] as Unit,
56
- value: number,
57
- };
58
- };
59
-
60
- export const getBrowserStyle = (element?: Element): Style => {
61
- const browserStyle: Style = {};
62
- if (element === undefined) {
63
- return browserStyle;
64
- }
65
- let knownProperty: keyof typeof properties;
66
- const computedStyle = getComputedStyle(element);
67
- for (knownProperty in properties) {
68
- if (knownProperty in computedStyle === false) {
69
- continue;
70
- }
71
- // Typescript doesn't know we can access CSSStyleDeclaration properties by keys
72
- const computedValue = computedStyle[knownProperty as unknown as number];
73
- browserStyle[knownProperty] = parseValue(knownProperty, computedValue);
74
- }
75
- // We need a single font-family that is actually rendered. Computed style will return a list of potential fonts.
76
- browserStyle.fontFamily = {
77
- type: "fontFamily",
78
- value: [detectFont(element)],
79
- };
80
- return browserStyle;
81
- };