@webstudio-is/sdk-components-react 0.142.0 → 0.143.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/lib/components.js CHANGED
@@ -26,37 +26,102 @@ import {
26
26
  forwardRef as forwardRef3,
27
27
  useContext,
28
28
  useEffect,
29
- useRef
29
+ useRef,
30
+ useSyncExternalStore,
31
+ useState
30
32
  } from "react";
31
33
  import { mergeRefs } from "@react-aria/utils";
32
34
  import { ReactSdkContext } from "@webstudio-is/react-sdk";
33
35
  import { jsx as jsx3 } from "react/jsx-runtime";
34
- var ExecutableHtml = (props) => {
36
+ var __testing__ = {
37
+ scriptTestIdPrefix: "client-"
38
+ };
39
+ var insertScript = (sourceScript) => {
40
+ return new Promise((resolve, reject) => {
41
+ const script = document.createElement("script");
42
+ const hasSrc = sourceScript.hasAttribute("src");
43
+ for (const { name, value } of sourceScript.attributes) {
44
+ script.setAttribute(name, value);
45
+ }
46
+ if (script.dataset.testid !== void 0) {
47
+ script.dataset.testid = `${__testing__.scriptTestIdPrefix}${script.dataset.testid}`;
48
+ }
49
+ if (hasSrc) {
50
+ script.addEventListener("load", () => {
51
+ resolve(script);
52
+ });
53
+ script.addEventListener("error", reject);
54
+ } else {
55
+ script.textContent = sourceScript.innerText;
56
+ }
57
+ sourceScript.replaceWith(script);
58
+ if (hasSrc === false) {
59
+ resolve(script);
60
+ }
61
+ });
62
+ };
63
+ var execute = async (container) => {
64
+ const scripts = container.querySelectorAll("script");
65
+ const syncTasks = [];
66
+ const asyncTasks = [];
67
+ scripts.forEach((script) => {
68
+ const type = script.getAttribute("type");
69
+ if (type == null || type === "" || type === "text/javascript") {
70
+ const tasks = script.hasAttribute("async") ? asyncTasks : syncTasks;
71
+ tasks.push(() => {
72
+ return insertScript(script);
73
+ });
74
+ }
75
+ });
76
+ for (const task of asyncTasks) {
77
+ task();
78
+ }
79
+ for (const task of syncTasks) {
80
+ await task();
81
+ }
82
+ };
83
+ var Placeholder = (props) => {
84
+ const { code, innerRef, ...rest } = props;
85
+ return /* @__PURE__ */ jsx3("div", { ref: innerRef, ...rest, style: { padding: "20px" }, children: 'Open the "Settings" panel to insert HTML code' });
86
+ };
87
+ var useIsServer = () => {
88
+ const isServer = useSyncExternalStore(
89
+ () => () => {
90
+ },
91
+ () => false,
92
+ () => true
93
+ );
94
+ return isServer;
95
+ };
96
+ var ClientOnly = (props) => {
97
+ const isServer = useIsServer();
98
+ if (isServer) {
99
+ return;
100
+ }
101
+ return props.children;
102
+ };
103
+ var ClientEmbed = (props) => {
35
104
  const { code, innerRef, ...rest } = props;
36
105
  const containerRef = useRef(null);
106
+ const executeScripts = useRef(true);
37
107
  useEffect(() => {
38
108
  const container = containerRef.current;
39
- if (container === null || code === void 0) {
40
- return;
109
+ if (container && executeScripts.current) {
110
+ executeScripts.current = false;
111
+ execute(container);
41
112
  }
42
- const range = document.createRange();
43
- range.setStart(container, 0);
44
- const fragment = range.createContextualFragment(code);
45
- while (container.firstChild) {
46
- container.removeChild(container.firstChild);
47
- }
48
- container.append(fragment);
49
- }, [code]);
113
+ }, []);
50
114
  return /* @__PURE__ */ jsx3(
51
115
  "div",
52
116
  {
53
117
  ...rest,
54
118
  ref: mergeRefs(innerRef, containerRef),
55
- style: { display: "contents" }
119
+ style: { display: "contents" },
120
+ dangerouslySetInnerHTML: { __html: code ?? "" }
56
121
  }
57
122
  );
58
123
  };
59
- var InnerHtml = (props) => {
124
+ var ServerEmbed = (props) => {
60
125
  const { code, innerRef, ...rest } = props;
61
126
  return /* @__PURE__ */ jsx3(
62
127
  "div",
@@ -64,25 +129,47 @@ var InnerHtml = (props) => {
64
129
  ...rest,
65
130
  ref: innerRef,
66
131
  style: { display: "contents" },
67
- dangerouslySetInnerHTML: { __html: props.code ?? "" }
132
+ dangerouslySetInnerHTML: { __html: code ?? "" }
68
133
  }
69
134
  );
70
135
  };
71
- var Placeholder = (props) => {
72
- const { code, innerRef, ...rest } = props;
73
- return /* @__PURE__ */ jsx3("div", { ref: innerRef, ...rest, style: { padding: "20px" }, children: 'Open the "Settings" panel to insert HTML code' });
74
- };
75
- var HtmlEmbed = forwardRef3((props, ref) => {
76
- const { renderer } = useContext(ReactSdkContext);
77
- const { code, executeScriptOnCanvas, clientOnly, ...rest } = props;
78
- if (code === void 0 || String(code).trim().length === 0) {
79
- return /* @__PURE__ */ jsx3(Placeholder, { innerRef: ref, ...rest });
80
- }
81
- if (renderer === "canvas" && executeScriptOnCanvas === true || renderer === "preview" || clientOnly) {
82
- return /* @__PURE__ */ jsx3(ExecutableHtml, { innerRef: ref, code, ...rest });
136
+ var ClientEmbedWithNonExecutableScripts = ServerEmbed;
137
+ var HtmlEmbed = forwardRef3(
138
+ (props, ref) => {
139
+ const { code, executeScriptOnCanvas, clientOnly, ...rest } = props;
140
+ const { renderer } = useContext(ReactSdkContext);
141
+ const isServer = useIsServer();
142
+ const [ssrRendered] = useState(isServer);
143
+ if (code === void 0 || String(code).trim().length === 0) {
144
+ return /* @__PURE__ */ jsx3(Placeholder, { innerRef: ref, ...rest });
145
+ }
146
+ if (ssrRendered) {
147
+ if (clientOnly !== true) {
148
+ return /* @__PURE__ */ jsx3(ServerEmbed, { innerRef: ref, code, ...rest });
149
+ }
150
+ return /* @__PURE__ */ jsx3(ClientOnly, { children: /* @__PURE__ */ jsx3(ClientEmbed, { innerRef: ref, code, ...rest }) });
151
+ }
152
+ if (renderer === "canvas" && executeScriptOnCanvas !== true) {
153
+ return /* @__PURE__ */ jsx3(ClientOnly, { children: /* @__PURE__ */ jsx3(
154
+ ClientEmbedWithNonExecutableScripts,
155
+ {
156
+ innerRef: ref,
157
+ code,
158
+ ...rest
159
+ }
160
+ ) });
161
+ }
162
+ return /* @__PURE__ */ jsx3(ClientOnly, { children: /* @__PURE__ */ jsx3(
163
+ ClientEmbed,
164
+ {
165
+ innerRef: ref,
166
+ code,
167
+ ...rest
168
+ },
169
+ code
170
+ ) });
83
171
  }
84
- return /* @__PURE__ */ jsx3(InnerHtml, { innerRef: ref, code, ...rest });
85
- });
172
+ );
86
173
  HtmlEmbed.displayName = "HtmlEmbed";
87
174
 
88
175
  // src/body.tsx
@@ -340,19 +427,22 @@ RadioButton.displayName = "RadioButton";
340
427
  // src/checkbox.tsx
341
428
  import { forwardRef as forwardRef28 } from "react";
342
429
  import { jsx as jsx25 } from "react/jsx-runtime";
343
- var Checkbox = forwardRef28(({ children: _children, ...props }, ref) => /* @__PURE__ */ jsx25("input", { ...props, type: "checkbox", ref }));
430
+ var Checkbox = forwardRef28(({ children: _children, ...props }, ref) => {
431
+ return /* @__PURE__ */ jsx25("input", { ...props, type: "checkbox", ref });
432
+ });
344
433
  Checkbox.displayName = "Checkbox";
345
434
 
346
435
  // src/vimeo.tsx
347
436
  import { colord } from "colord";
348
437
  import {
349
438
  forwardRef as forwardRef29,
350
- useState,
439
+ useState as useState2,
351
440
  useRef as useRef2,
352
441
  useEffect as useEffect2,
353
442
  useContext as useContext3,
354
443
  createContext,
355
- useMemo
444
+ useMemo,
445
+ useCallback
356
446
  } from "react";
357
447
  import { ReactSdkContext as ReactSdkContext3 } from "@webstudio-is/react-sdk";
358
448
  import { shallowEqual } from "shallow-equal";
@@ -408,6 +498,9 @@ var warmConnections = () => {
408
498
  if (warmed) {
409
499
  return;
410
500
  }
501
+ if (window.matchMedia("(hover: none)").matches) {
502
+ return;
503
+ }
411
504
  preconnect(PLAYER_CDN);
412
505
  preconnect(IFRAME_CDN);
413
506
  preconnect(IMAGE_CDN);
@@ -476,9 +569,11 @@ var useVimeo = ({
476
569
  showPreview,
477
570
  loading
478
571
  }) => {
479
- const [playerStatus, setPlayerStatus] = useState("initial");
572
+ const [playerStatus, setPlayerStatus] = useState2("initial");
480
573
  const elementRef = useRef2(null);
481
- const [previewImageUrl, setPreviewImageUrl] = useState();
574
+ const [previewImageUrl, setPreviewImageUrl] = useState2();
575
+ const [isPending, setIsPending] = useState2(false);
576
+ const loadPreviewOnceRef = useRef2(false);
482
577
  useEffect2(() => {
483
578
  setPlayerStatus(
484
579
  options.autoplay && renderer !== "canvas" ? "initialized" : "initial"
@@ -489,9 +584,12 @@ var useVimeo = ({
489
584
  return;
490
585
  }
491
586
  if (showPreview) {
492
- loadPreviewImage(elementRef.current, options.url).then(
493
- setPreviewImageUrl
494
- );
587
+ if (loadPreviewOnceRef.current) {
588
+ return;
589
+ }
590
+ loadPreviewOnceRef.current = true;
591
+ loadPreviewImage(elementRef.current, options.url).then(setPreviewImageUrl).catch(() => {
592
+ });
495
593
  return;
496
594
  }
497
595
  setPreviewImageUrl(void 0);
@@ -504,14 +602,29 @@ var useVimeo = ({
504
602
  return optionsRef.current;
505
603
  }, [options]);
506
604
  useEffect2(() => {
507
- if (elementRef.current === null || playerStatus === "initial") {
605
+ if (isPending) {
606
+ setPlayerStatus(
607
+ (status2) => status2 === "initial" ? "initialized" : status2
608
+ );
609
+ }
610
+ }, [isPending]);
611
+ const toggleCreatePlayer = playerStatus !== "initial";
612
+ useEffect2(() => {
613
+ if (elementRef.current === null) {
614
+ return;
615
+ }
616
+ if (toggleCreatePlayer === false) {
508
617
  return;
509
618
  }
510
619
  return createPlayer(elementRef.current, stableOptions, { loading }, () => {
511
620
  setPlayerStatus("ready");
512
621
  });
513
- }, [stableOptions, playerStatus, loading]);
514
- return { previewImageUrl, playerStatus, setPlayerStatus, elementRef };
622
+ }, [stableOptions, toggleCreatePlayer, loading]);
623
+ const start = useCallback(() => {
624
+ setIsPending(true);
625
+ }, []);
626
+ const status = isPending ? "initialized" : playerStatus;
627
+ return { previewImageUrl, status, start, elementRef };
515
628
  };
516
629
  var Vimeo = forwardRef29(
517
630
  ({
@@ -543,7 +656,7 @@ var Vimeo = forwardRef29(
543
656
  ...rest
544
657
  }, ref) => {
545
658
  const { renderer } = useContext3(ReactSdkContext3);
546
- const { previewImageUrl, playerStatus, setPlayerStatus, elementRef } = useVimeo({
659
+ const { previewImageUrl, status, start, elementRef } = useVimeo({
547
660
  renderer,
548
661
  showPreview,
549
662
  loading,
@@ -574,11 +687,11 @@ var Vimeo = forwardRef29(
574
687
  VimeoContext.Provider,
575
688
  {
576
689
  value: {
577
- status: playerStatus,
690
+ status,
578
691
  previewImageUrl,
579
692
  onInitPlayer() {
580
693
  if (renderer !== "canvas") {
581
- setPlayerStatus("initialized");
694
+ start();
582
695
  }
583
696
  }
584
697
  },
package/lib/metas.js CHANGED
@@ -45,9 +45,14 @@ var meta3 = {
45
45
  var propsMeta = {
46
46
  props: {
47
47
  ...props,
48
+ clientOnly: {
49
+ ...props.clientOnly,
50
+ description: "Activate it for any scripts that can mutate the DOM or introduce interactivity. This only affects the published site."
51
+ },
48
52
  executeScriptOnCanvas: {
49
53
  ...props.executeScriptOnCanvas,
50
- label: "Run script on canvas"
54
+ label: "Run scripts on canvas",
55
+ description: "Dangerously allow script execution on canvas without switching to preview mode. This only affects build mode, but may result in unwanted side effects inside builder!"
51
56
  },
52
57
  code: {
53
58
  required: true,
@@ -1642,6 +1647,13 @@ var presetStyle15 = {
1642
1647
  {
1643
1648
  property: "display",
1644
1649
  value: { type: "keyword", value: "block" }
1650
+ },
1651
+ // Set image height to "auto" to reduce layout shift, improving compatibility across browsers like Safari.
1652
+ // Unlike "fit-content," "auto" preserves the aspect ratio when the width exceeds max-width. (in Safari)
1653
+ // See https://web.dev/articles/optimize-cls#best_practice_for_setting_image_dimensions
1654
+ {
1655
+ property: "height",
1656
+ value: { type: "keyword", value: "auto" }
1645
1657
  }
1646
1658
  ]
1647
1659
  };
@@ -2723,7 +2735,7 @@ var meta29 = {
2723
2735
  },
2724
2736
  {
2725
2737
  type: "instance",
2726
- component: "Box",
2738
+ component: "VimeoSpinner",
2727
2739
  label: "Spinner",
2728
2740
  styles: [
2729
2741
  {
package/lib/props.js CHANGED
@@ -27,9 +27,14 @@ var props = {
27
27
  var propsMeta3 = {
28
28
  props: {
29
29
  ...props,
30
+ clientOnly: {
31
+ ...props.clientOnly,
32
+ description: "Activate it for any scripts that can mutate the DOM or introduce interactivity. This only affects the published site."
33
+ },
30
34
  executeScriptOnCanvas: {
31
35
  ...props.executeScriptOnCanvas,
32
- label: "Run script on canvas"
36
+ label: "Run scripts on canvas",
37
+ description: "Dangerously allow script execution on canvas without switching to preview mode. This only affects build mode, but may result in unwanted side effects inside builder!"
33
38
  },
34
39
  code: {
35
40
  required: true,
@@ -8484,6 +8489,13 @@ var presetStyle8 = {
8484
8489
  {
8485
8490
  property: "display",
8486
8491
  value: { type: "keyword", value: "block" }
8492
+ },
8493
+ // Set image height to "auto" to reduce layout shift, improving compatibility across browsers like Safari.
8494
+ // Unlike "fit-content," "auto" preserves the aspect ratio when the width exceeds max-width. (in Safari)
8495
+ // See https://web.dev/articles/optimize-cls#best_practice_for_setting_image_dimensions
8496
+ {
8497
+ property: "height",
8498
+ value: { type: "keyword", value: "auto" }
8487
8499
  }
8488
8500
  ]
8489
8501
  };
@@ -13081,6 +13093,12 @@ var props24 = {
13081
13093
  type: "string",
13082
13094
  options: ["on", "off"]
13083
13095
  },
13096
+ value: {
13097
+ required: false,
13098
+ control: "text",
13099
+ type: "string",
13100
+ description: "Defines a default value which will be displayed in the element on pageload."
13101
+ },
13084
13102
  vocab: { required: false, control: "text", type: "string" },
13085
13103
  width: {
13086
13104
  required: false,
@@ -13140,7 +13158,15 @@ var meta7 = {
13140
13158
  };
13141
13159
  var propsMeta27 = {
13142
13160
  props: props24,
13143
- initialProps: ["id", "className", "name"]
13161
+ initialProps: [
13162
+ "id",
13163
+ "className",
13164
+ "name",
13165
+ "value",
13166
+ "required",
13167
+ "checked",
13168
+ "defaultChecked"
13169
+ ]
13144
13170
  };
13145
13171
 
13146
13172
  // src/checkbox.ws.ts
@@ -13808,6 +13834,12 @@ var props25 = {
13808
13834
  type: "string",
13809
13835
  options: ["on", "off"]
13810
13836
  },
13837
+ value: {
13838
+ required: false,
13839
+ control: "text",
13840
+ type: "string",
13841
+ description: "Defines a default value which will be displayed in the element on pageload."
13842
+ },
13811
13843
  vocab: { required: false, control: "text", type: "string" },
13812
13844
  width: {
13813
13845
  required: false,
@@ -13866,7 +13898,15 @@ var meta8 = {
13866
13898
  };
13867
13899
  var propsMeta28 = {
13868
13900
  props: props25,
13869
- initialProps: ["id", "className", "name"]
13901
+ initialProps: [
13902
+ "id",
13903
+ "className",
13904
+ "name",
13905
+ "value",
13906
+ "required",
13907
+ "checked",
13908
+ "defaultChecked"
13909
+ ]
13870
13910
  };
13871
13911
 
13872
13912
  // src/vimeo.ws.ts
@@ -1,3 +1,5 @@
1
1
  /// <reference types="react" />
2
2
  export declare const defaultTag = "input";
3
- export declare const Checkbox: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "type">, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
3
+ export declare const Checkbox: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "type" | "value"> & {
4
+ value?: string | undefined;
5
+ }, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -1,8 +1,11 @@
1
1
  /// <reference types="react" />
2
- type Props = {
2
+ export declare const __testing__: {
3
+ scriptTestIdPrefix: string;
4
+ };
5
+ type HtmlEmbedProps = {
3
6
  code: string;
4
7
  executeScriptOnCanvas?: boolean;
5
8
  clientOnly?: boolean;
6
9
  };
7
- export declare const HtmlEmbed: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<HTMLDivElement>>;
10
+ export declare const HtmlEmbed: import("react").ForwardRefExoticComponent<HtmlEmbedProps & import("react").RefAttributes<HTMLDivElement>>;
8
11
  export {};
@@ -0,0 +1 @@
1
+ import "@testing-library/jest-dom/jest-globals";
@@ -1,3 +1,5 @@
1
1
  /// <reference types="react" />
2
2
  export declare const defaultTag = "input";
3
- export declare const RadioButton: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "type">, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
3
+ export declare const RadioButton: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "type" | "value"> & {
4
+ value?: string | undefined;
5
+ }, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,2 @@
1
+ export declare function cartesian<A, B>(a: A[], b: B[]): [A, B][];
2
+ export declare function cartesian<A, B, C>(a: A[], b: B[], c: C[]): [A, B, C][];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webstudio-is/sdk-components-react",
3
- "version": "0.142.0",
3
+ "version": "0.143.0",
4
4
  "description": "Webstudio default library for react",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
@@ -40,29 +40,35 @@
40
40
  "@react-aria/utils": "^3.21.0",
41
41
  "colord": "^2.9.3",
42
42
  "shallow-equal": "^3.1.0",
43
- "@webstudio-is/image": "0.142.0",
44
- "@webstudio-is/react-sdk": "0.142.0",
45
- "@webstudio-is/icons": "0.142.0"
43
+ "@webstudio-is/icons": "0.143.0",
44
+ "@webstudio-is/react-sdk": "0.143.0",
45
+ "@webstudio-is/image": "0.143.0"
46
46
  },
47
47
  "devDependencies": {
48
+ "@jest/globals": "^29.7.0",
48
49
  "@storybook/react": "^7.4.0",
50
+ "@testing-library/jest-dom": "^6.4.2",
51
+ "@testing-library/react": "^14.2.2",
49
52
  "@types/react": "^18.2.21",
50
53
  "@types/react-dom": "^18.2.7",
54
+ "jest-environment-jsdom": "^29.7.0",
51
55
  "react": "^18.2.0",
52
56
  "react-dom": "^18.2.0",
53
57
  "typescript": "5.2.2",
54
- "@webstudio-is/generate-arg-types": "0.142.0",
55
- "@webstudio-is/storybook-config": "0.0.0",
56
- "@webstudio-is/tsconfig": "1.0.7"
58
+ "@webstudio-is/jest-config": "1.0.7",
59
+ "@webstudio-is/generate-arg-types": "0.143.0",
60
+ "@webstudio-is/tsconfig": "1.0.7",
61
+ "@webstudio-is/storybook-config": "0.0.0"
57
62
  },
58
63
  "scripts": {
59
64
  "dev": "rm -rf lib && esbuild 'src/**/*.ts' 'src/**/*.tsx' --outdir=lib --watch",
60
65
  "build": "rm -rf lib && esbuild src/components.ts src/metas.ts src/props.ts --outdir=lib --bundle --format=esm --packages=external",
61
66
  "build:args": "NODE_OPTIONS=--conditions=webstudio generate-arg-types './src/*.tsx !./src/*.stories.tsx !./src/*.ws.ts' && prettier --write \"**/*.props.ts\"",
62
67
  "dts": "tsc --project tsconfig.dts.json",
68
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest",
63
69
  "typecheck": "tsc",
64
70
  "checks": "pnpm typecheck",
65
- "storybook:dev": "storybook dev -p 6006",
71
+ "storybook:dev": "storybook dev -p 6006 --static-dir=./public",
66
72
  "storybook:build": "storybook build"
67
73
  }
68
74
  }