@01.software/sdk 0.33.0 → 0.35.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 (76) hide show
  1. package/README.md +214 -22
  2. package/dist/analytics/react.cjs.map +1 -1
  3. package/dist/analytics/react.js.map +1 -1
  4. package/dist/analytics.cjs.map +1 -1
  5. package/dist/analytics.js.map +1 -1
  6. package/dist/client.cjs +342 -27
  7. package/dist/client.cjs.map +1 -1
  8. package/dist/client.d.cts +7 -6
  9. package/dist/client.d.ts +7 -6
  10. package/dist/client.js +342 -27
  11. package/dist/client.js.map +1 -1
  12. package/dist/{collection-client-De6eKW1J.d.cts → collection-client-CR2B8c1v.d.cts} +7 -3
  13. package/dist/{collection-client-B6SlhzIP.d.ts → collection-client-DkREjhQ9.d.ts} +7 -3
  14. package/dist/{const-sPR2IkCe.d.cts → const-BTvdrXtY.d.cts} +4 -4
  15. package/dist/{const-DwmSDeWq.d.ts → const-CdqCauHQ.d.ts} +4 -4
  16. package/dist/errors.cjs +300 -0
  17. package/dist/errors.cjs.map +1 -0
  18. package/dist/{index-BGEhoDUs.d.cts → errors.d.cts} +13 -1
  19. package/dist/{index-BGEhoDUs.d.ts → errors.d.ts} +13 -1
  20. package/dist/errors.js +277 -0
  21. package/dist/errors.js.map +1 -0
  22. package/dist/index-CjA3U6X3.d.cts +186 -0
  23. package/dist/index-DK8_NXkh.d.ts +186 -0
  24. package/dist/index.cjs +1402 -177
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +75 -10
  27. package/dist/index.d.ts +75 -10
  28. package/dist/index.js +1402 -177
  29. package/dist/index.js.map +1 -1
  30. package/dist/{payload-types-dkeQyrDC.d.cts → payload-types-C7tb7Xbs.d.cts} +208 -52
  31. package/dist/{payload-types-dkeQyrDC.d.ts → payload-types-C7tb7Xbs.d.ts} +208 -52
  32. package/dist/query.cjs +195 -36
  33. package/dist/query.cjs.map +1 -1
  34. package/dist/query.d.cts +45 -18
  35. package/dist/query.d.ts +45 -18
  36. package/dist/query.js +195 -36
  37. package/dist/query.js.map +1 -1
  38. package/dist/realtime.cjs.map +1 -1
  39. package/dist/realtime.d.cts +2 -2
  40. package/dist/realtime.d.ts +2 -2
  41. package/dist/realtime.js.map +1 -1
  42. package/dist/{server-CrsPyqEc.d.cts → server-nXOezi4b.d.cts} +22 -6
  43. package/dist/{server-CrsPyqEc.d.ts → server-nXOezi4b.d.ts} +22 -6
  44. package/dist/server.cjs +440 -33
  45. package/dist/server.cjs.map +1 -1
  46. package/dist/server.d.cts +11 -179
  47. package/dist/server.d.ts +11 -179
  48. package/dist/server.js +440 -33
  49. package/dist/server.js.map +1 -1
  50. package/dist/{types-Cel_4L9t.d.ts → types-1ylMrCuW.d.ts} +1 -1
  51. package/dist/{types-B3YT092I.d.cts → types-Bx558PU6.d.cts} +1 -1
  52. package/dist/{types-BHh0YLmq.d.ts → types-Byo_Rty4.d.ts} +705 -69
  53. package/dist/{types-BZKxss8Y.d.cts → types-DDhtZI6E.d.cts} +705 -69
  54. package/dist/ui/canvas/server.cjs +231 -38
  55. package/dist/ui/canvas/server.cjs.map +1 -1
  56. package/dist/ui/canvas/server.d.cts +1 -1
  57. package/dist/ui/canvas/server.d.ts +1 -1
  58. package/dist/ui/canvas/server.js +221 -38
  59. package/dist/ui/canvas/server.js.map +1 -1
  60. package/dist/ui/canvas.cjs +320 -257
  61. package/dist/ui/canvas.cjs.map +1 -1
  62. package/dist/ui/canvas.d.cts +5 -19
  63. package/dist/ui/canvas.d.ts +5 -19
  64. package/dist/ui/canvas.js +323 -260
  65. package/dist/ui/canvas.js.map +1 -1
  66. package/dist/ui/form.d.cts +1 -1
  67. package/dist/ui/form.d.ts +1 -1
  68. package/dist/ui/video.d.cts +1 -1
  69. package/dist/ui/video.d.ts +1 -1
  70. package/dist/webhook.cjs +2 -1
  71. package/dist/webhook.cjs.map +1 -1
  72. package/dist/webhook.d.cts +20 -179
  73. package/dist/webhook.d.ts +20 -179
  74. package/dist/webhook.js +2 -1
  75. package/dist/webhook.js.map +1 -1
  76. package/package.json +12 -3
package/dist/ui/canvas.js CHANGED
@@ -8,44 +8,6 @@ function isFrameNode(node) {
8
8
  return node.type === "frame";
9
9
  }
10
10
 
11
- // src/ui/Canvas/built-in-node-types.ts
12
- var BUILT_IN_NODE_TYPES = [
13
- {
14
- slug: "text",
15
- name: "Text",
16
- color: "#e5e7eb",
17
- defaultSize: { width: 200, height: 200 },
18
- fields: [{ name: "body", label: "Body", fieldType: "textarea" }]
19
- },
20
- {
21
- slug: "image",
22
- name: "Image",
23
- color: "#e5e7eb",
24
- transparentBackground: true,
25
- defaultSize: { width: 200, height: 200 },
26
- fields: [
27
- { name: "image", label: "Image", fieldType: "image" },
28
- { name: "alt", label: "Alt Text", fieldType: "text" },
29
- { name: "caption", label: "Caption", fieldType: "text" }
30
- ]
31
- }
32
- ];
33
-
34
- // src/ui/Canvas/built-in-edge-types.ts
35
- var BUILT_IN_EDGE_TYPES = [
36
- {
37
- slug: "default",
38
- name: "Default",
39
- color: "",
40
- strokeWidth: 2,
41
- animated: false,
42
- lineStyle: "default",
43
- markerStart: "none",
44
- markerEnd: "arrow",
45
- fields: []
46
- }
47
- ];
48
-
49
11
  // src/ui/Canvas/useCanvas.ts
50
12
  import { useQuery } from "@tanstack/react-query";
51
13
  import { useMemo } from "react";
@@ -99,7 +61,7 @@ function edgeTypesQueryOptions(client) {
99
61
  };
100
62
  }
101
63
 
102
- // src/ui/Canvas/useCanvas.ts
64
+ // src/ui/Canvas/catalog-mappers.ts
103
65
  function toNodeTypeDef(doc) {
104
66
  return {
105
67
  slug: String(doc.slug ?? ""),
@@ -110,7 +72,10 @@ function toNodeTypeDef(doc) {
110
72
  height: 200
111
73
  },
112
74
  fields: Array.isArray(doc.fields) ? doc.fields : [],
113
- transparentBackground: Boolean(doc.transparentBackground),
75
+ editorIntent: doc.editorIntent === "text" || doc.editorIntent === "image" ? doc.editorIntent : void 0,
76
+ transparentBackground: Boolean(
77
+ doc.hasTransparentBackground ?? doc.transparentBackground
78
+ ),
114
79
  template: doc.template ?? null,
115
80
  customCSS: doc.customCSS ?? null
116
81
  };
@@ -121,13 +86,16 @@ function toEdgeTypeDef(doc) {
121
86
  name: String(doc.title ?? ""),
122
87
  color: String(doc.color ?? ""),
123
88
  strokeWidth: doc.strokeWidth ?? 2,
124
- animated: doc.animated ?? false,
89
+ animated: doc.isAnimated ?? doc.animated ?? false,
125
90
  lineStyle: String(doc.lineStyle ?? "default"),
126
91
  markerStart: String(doc.markerStart ?? "none"),
127
92
  markerEnd: String(doc.markerEnd ?? "arrow"),
128
- fields: Array.isArray(doc.fields) ? doc.fields : []
93
+ fields: Array.isArray(doc.fields) ? doc.fields : [],
94
+ isDefaultEdge: Boolean(doc.isDefaultEdge)
129
95
  };
130
96
  }
97
+
98
+ // src/ui/Canvas/useCanvas.ts
131
99
  function useCanvas(options) {
132
100
  const { client, queryClient, slug, id, enabled = true } = options;
133
101
  const hasIdentifier = !!(slug || id);
@@ -153,16 +121,10 @@ function useCanvas(options) {
153
121
  queryClient
154
122
  );
155
123
  const nodeTypeDefs = useMemo(() => {
156
- const apiDefs = (nodeTypesQuery.data ?? []).map(toNodeTypeDef);
157
- const builtInSlugs = new Set(BUILT_IN_NODE_TYPES.map((t) => t.slug));
158
- const customDefs = apiDefs.filter((d) => !builtInSlugs.has(d.slug));
159
- return [...BUILT_IN_NODE_TYPES, ...customDefs];
124
+ return (nodeTypesQuery.data ?? []).map(toNodeTypeDef);
160
125
  }, [nodeTypesQuery.data]);
161
126
  const edgeTypeDefs = useMemo(() => {
162
- const apiDefs = (edgeTypesQuery.data ?? []).map(toEdgeTypeDef);
163
- const builtInSlugs = new Set(BUILT_IN_EDGE_TYPES.map((t) => t.slug));
164
- const customDefs = apiDefs.filter((d) => !builtInSlugs.has(d.slug));
165
- return [...BUILT_IN_EDGE_TYPES, ...customDefs];
127
+ return (edgeTypesQuery.data ?? []).map(toEdgeTypeDef);
166
128
  }, [edgeTypesQuery.data]);
167
129
  const canvasDoc = canvasQuery.data;
168
130
  const canvasData = canvasDoc?.canvas;
@@ -194,11 +156,11 @@ import { useMemo as useMemo2 } from "react";
194
156
  function useCanvasData(options) {
195
157
  const { data, nodeTypeDefs: inputNodeDefs, edgeTypeDefs: inputEdgeDefs } = options;
196
158
  const nodeTypeDefsMap = useMemo2(() => {
197
- const allDefs = inputNodeDefs ?? BUILT_IN_NODE_TYPES;
159
+ const allDefs = inputNodeDefs ?? [];
198
160
  return new Map(allDefs.map((d) => [d.slug, d]));
199
161
  }, [inputNodeDefs]);
200
162
  const edgeTypeDefsMap = useMemo2(() => {
201
- const allDefs = inputEdgeDefs ?? BUILT_IN_EDGE_TYPES;
163
+ const allDefs = inputEdgeDefs ?? [];
202
164
  return new Map(allDefs.map((d) => [d.slug, d]));
203
165
  }, [inputEdgeDefs]);
204
166
  const nodes = useMemo2(() => data?.nodes ?? [], [data?.nodes]);
@@ -630,9 +592,176 @@ function clearTemplateCache() {
630
592
  }
631
593
 
632
594
  // src/ui/Canvas/node-renderers.tsx
633
- import React2 from "react";
595
+ import React3 from "react";
596
+
597
+ // src/ui/Image/index.tsx
598
+ import React2, { useCallback, useRef, useState } from "react";
599
+
600
+ // src/utils/image.ts
601
+ var IMAGE_SIZES = [384, 768, 1536];
602
+ function getImageSrcSet(image) {
603
+ const parts = [];
604
+ const sizes = image.sizes;
605
+ if (sizes) {
606
+ for (const size of IMAGE_SIZES) {
607
+ const entry = sizes[String(size)];
608
+ if (entry?.url && entry.width) {
609
+ parts.push(`${entry.url} ${entry.width}w`);
610
+ }
611
+ }
612
+ }
613
+ if (image.url && image.width) {
614
+ parts.push(`${image.url} ${image.width}w`);
615
+ }
616
+ return parts.join(", ");
617
+ }
618
+ function getImagePlaceholderStyle(image, options) {
619
+ const type = options?.type ?? "blur";
620
+ const paletteColor = options?.paletteColor ?? "muted";
621
+ if (type === "none") return {};
622
+ const color = image.palette?.[paletteColor];
623
+ if (type === "blur") {
624
+ const lqip = image.lqip;
625
+ if (lqip) {
626
+ return {
627
+ backgroundImage: `url(${lqip})`,
628
+ backgroundSize: "cover",
629
+ backgroundPosition: "center"
630
+ };
631
+ }
632
+ if (color) {
633
+ return { backgroundColor: color };
634
+ }
635
+ return {};
636
+ }
637
+ if (color) {
638
+ return { backgroundColor: color };
639
+ }
640
+ return {};
641
+ }
642
+
643
+ // src/ui/Image/index.tsx
644
+ function Image({
645
+ image,
646
+ width,
647
+ dpr = 1,
648
+ placeholder: placeholderProp,
649
+ className,
650
+ style,
651
+ imgClassName,
652
+ imgStyle,
653
+ sizes,
654
+ loading: loadingProp,
655
+ onLoad,
656
+ objectFit = "cover",
657
+ priority = false,
658
+ fill = false,
659
+ imageRendering,
660
+ alt: altProp
661
+ }) {
662
+ const [loaded, setLoaded] = useState(false);
663
+ const firedRef = useRef(false);
664
+ const isPixelRendering = imageRendering === "pixelated" || imageRendering === "crisp-edges";
665
+ const placeholder = placeholderProp ?? (isPixelRendering ? "none" : "blur");
666
+ const loading = priority ? "eager" : loadingProp ?? "lazy";
667
+ const aspectRatio = !fill && image.width && image.height ? `${image.width} / ${image.height}` : void 0;
668
+ const srcSet = getImageSrcSet(image);
669
+ const src = image.url ?? void 0;
670
+ const hasLqip = placeholder === "blur" && !!image.lqip;
671
+ const placeholderStyle = getImagePlaceholderStyle(image, {
672
+ type: placeholder
673
+ });
674
+ const placeholderColor = !hasLqip && "backgroundColor" in placeholderStyle ? placeholderStyle.backgroundColor : void 0;
675
+ const fireLoad = useCallback(() => {
676
+ if (firedRef.current) return;
677
+ firedRef.current = true;
678
+ setLoaded(true);
679
+ onLoad?.();
680
+ }, [onLoad]);
681
+ const imgRef = useCallback(
682
+ (node) => {
683
+ if (node && node.complete && node.naturalWidth > 0) {
684
+ fireLoad();
685
+ }
686
+ },
687
+ [fireLoad]
688
+ );
689
+ const containerStyle = {
690
+ position: "relative",
691
+ overflow: "hidden",
692
+ ...fill ? { width: "100%", height: "100%" } : {},
693
+ ...aspectRatio ? { aspectRatio } : {},
694
+ ...style
695
+ };
696
+ const overlayBase = {
697
+ position: "absolute",
698
+ top: 0,
699
+ left: 0,
700
+ width: "100%",
701
+ height: "100%",
702
+ opacity: loaded ? 0 : 1,
703
+ transition: "opacity 0.3s ease",
704
+ pointerEvents: "none"
705
+ };
706
+ const mainImgStyle = {
707
+ display: "block",
708
+ width: "100%",
709
+ height: "100%",
710
+ objectFit,
711
+ ...imageRendering ? { imageRendering } : {},
712
+ opacity: loaded ? 1 : 0,
713
+ transition: "opacity 0.3s ease",
714
+ ...imgStyle
715
+ };
716
+ return /* @__PURE__ */ React2.createElement("div", { className, style: containerStyle }, hasLqip && /* @__PURE__ */ React2.createElement(
717
+ "img",
718
+ {
719
+ "aria-hidden": true,
720
+ alt: "",
721
+ src: image.lqip,
722
+ style: {
723
+ ...overlayBase,
724
+ display: "block",
725
+ objectFit,
726
+ filter: "blur(20px)",
727
+ transform: "scale(1.1)"
728
+ }
729
+ }
730
+ ), placeholderColor && /* @__PURE__ */ React2.createElement(
731
+ "div",
732
+ {
733
+ "aria-hidden": true,
734
+ style: {
735
+ ...overlayBase,
736
+ backgroundColor: placeholderColor
737
+ }
738
+ }
739
+ ), /* @__PURE__ */ React2.createElement(
740
+ "img",
741
+ {
742
+ ref: imgRef,
743
+ alt: altProp ?? image.alt ?? "",
744
+ src,
745
+ srcSet: srcSet || void 0,
746
+ sizes,
747
+ width: width ? width * dpr : void 0,
748
+ loading,
749
+ decoding: "async",
750
+ fetchPriority: priority ? "high" : void 0,
751
+ onLoad: fireLoad,
752
+ className: imgClassName,
753
+ style: mainImgStyle
754
+ }
755
+ ));
756
+ }
757
+
758
+ // src/ui/Canvas/node-renderers.tsx
634
759
  function sanitizeUrl(url) {
635
760
  if (!url) return url;
761
+ if (url !== url.trim()) return void 0;
762
+ if (url.startsWith("/") && !url.startsWith("//") || url.startsWith("./")) {
763
+ return url;
764
+ }
636
765
  try {
637
766
  const parsed = new URL(url);
638
767
  if (parsed.protocol === "http:" || parsed.protocol === "https:") return url;
@@ -641,6 +770,18 @@ function sanitizeUrl(url) {
641
770
  return void 0;
642
771
  }
643
772
  }
773
+ function sanitizeImageData(image, safeUrl) {
774
+ return {
775
+ ...image,
776
+ url: safeUrl,
777
+ sizes: image.sizes ? Object.fromEntries(
778
+ Object.entries(image.sizes).map(([name, size]) => [
779
+ name,
780
+ size ? { ...size, url: sanitizeUrl(size.url ?? void 0) } : size
781
+ ])
782
+ ) : image.sizes
783
+ };
784
+ }
644
785
  function renderFieldValue(key, val, fieldDef) {
645
786
  if (val == null || val === "") return null;
646
787
  const fieldType = fieldDef?.fieldType;
@@ -648,7 +789,19 @@ function renderFieldValue(key, val, fieldDef) {
648
789
  const imgUrl = typeof val === "string" ? val : val?.url;
649
790
  const safeUrl = sanitizeUrl(imgUrl);
650
791
  if (!safeUrl) return null;
651
- return /* @__PURE__ */ React2.createElement(
792
+ if (fieldType === "image" && typeof val === "object" && val !== null) {
793
+ return /* @__PURE__ */ React3.createElement(
794
+ Image,
795
+ {
796
+ key,
797
+ image: sanitizeImageData(val, safeUrl),
798
+ fill: true,
799
+ objectFit: "contain",
800
+ style: { flex: 1, minHeight: 0, width: "100%" }
801
+ }
802
+ );
803
+ }
804
+ return /* @__PURE__ */ React3.createElement(
652
805
  "img",
653
806
  {
654
807
  key,
@@ -659,7 +812,7 @@ function renderFieldValue(key, val, fieldDef) {
659
812
  }
660
813
  );
661
814
  }
662
- return /* @__PURE__ */ React2.createElement(
815
+ return /* @__PURE__ */ React3.createElement(
663
816
  "div",
664
817
  {
665
818
  key,
@@ -676,7 +829,7 @@ function renderFieldValue(key, val, fieldDef) {
676
829
  }
677
830
  function DefaultDynamicNode({ data }) {
678
831
  const d = data;
679
- return /* @__PURE__ */ React2.createElement(
832
+ return /* @__PURE__ */ React3.createElement(
680
833
  "div",
681
834
  {
682
835
  style: {
@@ -689,7 +842,7 @@ function DefaultDynamicNode({ data }) {
689
842
  d.fields && Object.entries(d.fields).filter(([, v]) => v != null && v !== "").map(([key, val]) => renderFieldValue(key, val))
690
843
  );
691
844
  }
692
- var TemplateErrorBoundary = class extends React2.Component {
845
+ var TemplateErrorBoundary = class extends React3.Component {
693
846
  constructor() {
694
847
  super(...arguments);
695
848
  this.state = { error: null };
@@ -704,7 +857,7 @@ var TemplateErrorBoundary = class extends React2.Component {
704
857
  }
705
858
  render() {
706
859
  if (this.state.error) {
707
- return /* @__PURE__ */ React2.createElement("div", { style: { padding: 8, fontSize: 11, color: "#ef4444" } }, /* @__PURE__ */ React2.createElement("strong", null, "Render error"), /* @__PURE__ */ React2.createElement("pre", { style: { fontSize: 10, whiteSpace: "pre-wrap" } }, process.env.NODE_ENV === "development" ? this.state.error.message : "Template render failed"));
860
+ return /* @__PURE__ */ React3.createElement("div", { style: { padding: 8, fontSize: 11, color: "#ef4444" } }, /* @__PURE__ */ React3.createElement("strong", null, "Render error"), /* @__PURE__ */ React3.createElement("pre", { style: { fontSize: 10, whiteSpace: "pre-wrap" } }, process.env.NODE_ENV === "development" ? this.state.error.message : "Template render failed"));
708
861
  }
709
862
  return this.props.children;
710
863
  }
@@ -719,13 +872,13 @@ function EnhancedDynamicNode({
719
872
  if (typeDef.template) {
720
873
  const Component = compileTemplate(typeDef.template, typeDef.slug);
721
874
  if (Component) {
722
- return /* @__PURE__ */ React2.createElement(
875
+ return /* @__PURE__ */ React3.createElement(
723
876
  "div",
724
877
  {
725
878
  className: `flow-node flow-node--${typeDef.slug}${typeDef.transparentBackground ? " flow-node--transparent-bg" : ""}`,
726
879
  style: { width: "100%", height: "100%" }
727
880
  },
728
- /* @__PURE__ */ React2.createElement(TemplateErrorBoundary, { resetKey: typeDef.template }, /* @__PURE__ */ React2.createElement(
881
+ /* @__PURE__ */ React3.createElement(TemplateErrorBoundary, { resetKey: typeDef.template }, /* @__PURE__ */ React3.createElement(
729
882
  Component,
730
883
  {
731
884
  fields: data.fields,
@@ -739,7 +892,7 @@ function EnhancedDynamicNode({
739
892
  );
740
893
  }
741
894
  }
742
- return /* @__PURE__ */ React2.createElement(
895
+ return /* @__PURE__ */ React3.createElement(
743
896
  "div",
744
897
  {
745
898
  className: `flow-node flow-node--${typeDef.slug}`,
@@ -767,12 +920,14 @@ function DefaultFrameNode({ data }) {
767
920
  const m = baseColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
768
921
  if (m) return `rgba(${m[1]},${m[2]},${m[3]},${opacity})`;
769
922
  const h6 = baseColor.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
770
- if (h6) return `rgba(${parseInt(h6[1], 16)},${parseInt(h6[2], 16)},${parseInt(h6[3], 16)},${opacity})`;
923
+ if (h6)
924
+ return `rgba(${parseInt(h6[1], 16)},${parseInt(h6[2], 16)},${parseInt(h6[3], 16)},${opacity})`;
771
925
  const h3 = baseColor.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i);
772
- if (h3) return `rgba(${parseInt(h3[1] + h3[1], 16)},${parseInt(h3[2] + h3[2], 16)},${parseInt(h3[3] + h3[3], 16)},${opacity})`;
926
+ if (h3)
927
+ return `rgba(${parseInt(h3[1] + h3[1], 16)},${parseInt(h3[2] + h3[2], 16)},${parseInt(h3[3] + h3[3], 16)},${opacity})`;
773
928
  return baseColor;
774
929
  })();
775
- return /* @__PURE__ */ React2.createElement(
930
+ return /* @__PURE__ */ React3.createElement(
776
931
  "div",
777
932
  {
778
933
  style: {
@@ -783,7 +938,7 @@ function DefaultFrameNode({ data }) {
783
938
  border: borderStyle === "none" ? "none" : `2px ${borderStyle} rgba(128,128,128,0.3)`
784
939
  }
785
940
  },
786
- /* @__PURE__ */ React2.createElement(
941
+ /* @__PURE__ */ React3.createElement(
787
942
  "div",
788
943
  {
789
944
  style: {
@@ -820,21 +975,64 @@ function getBooleanField(fields, name) {
820
975
 
821
976
  // src/ui/Canvas/css-sanitizer.ts
822
977
  import postcss from "postcss";
823
- var ALLOWED_AT_RULES = /* @__PURE__ */ new Set(["keyframes", "media", "supports", "container", "layer"]);
824
- var BLOCKED_VALUE_PATTERNS = [/url\s*\(/i, /expression\s*\(/i, /paint\s*\(/i, /-moz-binding/i];
978
+ var ALLOWED_AT_RULES = /* @__PURE__ */ new Set([
979
+ "keyframes",
980
+ "media",
981
+ "supports",
982
+ "container",
983
+ "layer"
984
+ ]);
985
+ var BLOCKED_VALUE_PATTERNS = [
986
+ /url\s*\(/i,
987
+ /expression\s*\(/i,
988
+ /paint\s*\(/i,
989
+ /-moz-binding/i
990
+ ];
825
991
  var DANGEROUS_SELECTOR = /(?:^|[\s,])(?::root|html|body)\b/;
826
992
  var BLOCKED_PROPERTY_VALUES = {
827
993
  // absolute is allowed — contained by nearest positioned ancestor (node wrapper)
828
994
  position: /fixed|sticky/i
829
995
  };
996
+ var TENANT_CSS_BLOCKED_PATTERNS = [
997
+ { pattern: /<\/style/i, label: "</style" },
998
+ { pattern: /<script/i, label: "<script" },
999
+ { pattern: /@import/i, label: "@import" },
1000
+ { pattern: /url\s*\(/i, label: "url(" }
1001
+ ];
830
1002
  var Z_INDEX_MAX = 9998;
1003
+ function normalizeCSSEscapes(value) {
1004
+ return value.replace(
1005
+ /\\(?:([0-9a-fA-F]{1,6})(?:\r\n|[ \t\r\n\f])?|([^\r\n\f]))/g,
1006
+ (match, hex, escaped) => {
1007
+ if (hex) {
1008
+ const cp = parseInt(hex, 16);
1009
+ if (Number.isNaN(cp) || cp > 1114111) return match;
1010
+ try {
1011
+ return String.fromCodePoint(cp);
1012
+ } catch {
1013
+ return match;
1014
+ }
1015
+ }
1016
+ return escaped ?? match;
1017
+ }
1018
+ );
1019
+ }
831
1020
  function sanitizeCSS(css, scopeClass) {
1021
+ return sanitizeCSSWithStats(css, scopeClass).css;
1022
+ }
1023
+ function sanitizeCSSWithStats(css, scopeClass) {
832
1024
  let root;
833
1025
  try {
834
1026
  root = postcss.parse(css);
835
1027
  } catch {
836
- return "";
1028
+ return { css: "", removedRules: 0, removedDecls: 0, removedAtRules: 0 };
837
1029
  }
1030
+ const stats = {
1031
+ css: "",
1032
+ removedRules: 0,
1033
+ removedDecls: 0,
1034
+ removedAtRules: 0
1035
+ };
838
1036
  const safeScopeClass = scopeClass ? scopeClass.replace(/[{}()[\];,'"\\<>\s]/g, "") : "";
839
1037
  const keyframeNameMap = /* @__PURE__ */ new Map();
840
1038
  if (safeScopeClass) {
@@ -847,7 +1045,13 @@ function sanitizeCSS(css, scopeClass) {
847
1045
  });
848
1046
  }
849
1047
  if (safeScopeClass && keyframeNameMap.size > 0) {
850
- const ANIM_KEYWORDS = /* @__PURE__ */ new Set(["none", "initial", "inherit", "unset", "revert"]);
1048
+ const ANIM_KEYWORDS = /* @__PURE__ */ new Set([
1049
+ "none",
1050
+ "initial",
1051
+ "inherit",
1052
+ "unset",
1053
+ "revert"
1054
+ ]);
851
1055
  const replaceAnimName = (token) => {
852
1056
  const t = token.trim().replace(/^['"]|['"]$/g, "");
853
1057
  return ANIM_KEYWORDS.has(t) ? token : keyframeNameMap.get(t) ?? token;
@@ -863,23 +1067,24 @@ function sanitizeCSS(css, scopeClass) {
863
1067
  }
864
1068
  root.walkAtRules((node) => {
865
1069
  if (!ALLOWED_AT_RULES.has(node.name.toLowerCase())) {
1070
+ stats.removedAtRules++;
866
1071
  node.remove();
867
1072
  }
868
1073
  });
869
1074
  root.walkDecls((node) => {
870
- const normalizedValue = node.value.replace(/\/\*[\s\S]*?\*\//g, "");
1075
+ const normalizedValue = normalizeCSSEscapes(
1076
+ node.value.replace(/\/\*[\s\S]*?\*\//g, "")
1077
+ );
871
1078
  if (BLOCKED_VALUE_PATTERNS.some((p) => p.test(normalizedValue))) {
1079
+ stats.removedDecls++;
872
1080
  node.remove();
873
1081
  return;
874
1082
  }
875
1083
  const propLower = node.prop.toLowerCase();
876
1084
  const blockedValuePattern = BLOCKED_PROPERTY_VALUES[propLower];
877
1085
  if (blockedValuePattern) {
878
- const deescaped = normalizedValue.replace(
879
- /\\([0-9a-fA-F]{1,6})\s?/g,
880
- (_, hex) => String.fromCodePoint(parseInt(hex, 16))
881
- );
882
- if (blockedValuePattern.test(deescaped)) {
1086
+ if (blockedValuePattern.test(normalizedValue)) {
1087
+ stats.removedDecls++;
883
1088
  node.remove();
884
1089
  return;
885
1090
  }
@@ -890,6 +1095,7 @@ function sanitizeCSS(css, scopeClass) {
890
1095
  } else {
891
1096
  const val = parseInt(zv, 10);
892
1097
  if (isNaN(val) || String(val) !== zv || val > Z_INDEX_MAX) {
1098
+ stats.removedDecls++;
893
1099
  node.remove();
894
1100
  return;
895
1101
  }
@@ -897,13 +1103,21 @@ function sanitizeCSS(css, scopeClass) {
897
1103
  }
898
1104
  });
899
1105
  root.walkRules((rule) => {
900
- rule.selectors = rule.selectors.filter((s) => !DANGEROUS_SELECTOR.test(s));
901
- if (!rule.selectors.length) {
1106
+ const safeSelectors = rule.selectors.filter(
1107
+ (s) => s.trim() && !DANGEROUS_SELECTOR.test(s)
1108
+ );
1109
+ if (!safeSelectors.length) {
1110
+ stats.removedRules++;
902
1111
  rule.remove();
903
1112
  return;
904
1113
  }
1114
+ if (safeSelectors.length !== rule.selectors.length) {
1115
+ stats.removedRules++;
1116
+ }
1117
+ rule.selectors = safeSelectors;
905
1118
  if (scopeClass) {
906
1119
  if (!safeScopeClass) {
1120
+ stats.removedRules++;
907
1121
  rule.remove();
908
1122
  return;
909
1123
  }
@@ -914,7 +1128,34 @@ function sanitizeCSS(css, scopeClass) {
914
1128
  rule.selectors = rule.selectors.map((sel) => `.${safeScopeClass} ${sel}`);
915
1129
  }
916
1130
  });
917
- return root.toString().replace(/<\/style/gi, "<\\/style");
1131
+ stats.css = root.toString().replace(/<\/style/gi, "<\\/style");
1132
+ return stats;
1133
+ }
1134
+ function validateTenantCSS(css) {
1135
+ const trimmed = css.trim();
1136
+ if (!trimmed) return null;
1137
+ const deescapedCSS = normalizeCSSEscapes(css);
1138
+ for (const { pattern, label } of TENANT_CSS_BLOCKED_PATTERNS) {
1139
+ if (pattern.test(deescapedCSS)) {
1140
+ return `CSS contains blocked pattern: ${label}`;
1141
+ }
1142
+ }
1143
+ try {
1144
+ postcss.parse(css);
1145
+ } catch {
1146
+ return "CSS parse error";
1147
+ }
1148
+ const sanitized = sanitizeCSSWithStats(
1149
+ css,
1150
+ "flow-node--tenant-css-validation"
1151
+ );
1152
+ if (!sanitized.css.trim()) {
1153
+ return "CSS must contain safe rules";
1154
+ }
1155
+ if (sanitized.removedRules > 0 || sanitized.removedDecls > 0 || sanitized.removedAtRules > 0) {
1156
+ return "CSS contains unsafe rules or selectors";
1157
+ }
1158
+ return null;
918
1159
  }
919
1160
 
920
1161
  // src/ui/Canvas/CanvasRenderer.tsx
@@ -929,169 +1170,6 @@ import {
929
1170
 
930
1171
  // src/ui/Canvas/node-types-factory.tsx
931
1172
  import React4 from "react";
932
-
933
- // src/ui/Image/index.tsx
934
- import React3, { useCallback, useRef, useState } from "react";
935
-
936
- // src/utils/image.ts
937
- var IMAGE_SIZES = [384, 768, 1536];
938
- function getImageSrcSet(image) {
939
- const parts = [];
940
- const sizes = image.sizes;
941
- if (sizes) {
942
- for (const size of IMAGE_SIZES) {
943
- const entry = sizes[String(size)];
944
- if (entry?.url && entry.width) {
945
- parts.push(`${entry.url} ${entry.width}w`);
946
- }
947
- }
948
- }
949
- if (image.url && image.width) {
950
- parts.push(`${image.url} ${image.width}w`);
951
- }
952
- return parts.join(", ");
953
- }
954
- function getImagePlaceholderStyle(image, options) {
955
- const type = options?.type ?? "blur";
956
- const paletteColor = options?.paletteColor ?? "muted";
957
- if (type === "none") return {};
958
- const color = image.palette?.[paletteColor];
959
- if (type === "blur") {
960
- const lqip = image.lqip;
961
- if (lqip) {
962
- return {
963
- backgroundImage: `url(${lqip})`,
964
- backgroundSize: "cover",
965
- backgroundPosition: "center"
966
- };
967
- }
968
- if (color) {
969
- return { backgroundColor: color };
970
- }
971
- return {};
972
- }
973
- if (color) {
974
- return { backgroundColor: color };
975
- }
976
- return {};
977
- }
978
-
979
- // src/ui/Image/index.tsx
980
- function Image({
981
- image,
982
- width,
983
- dpr = 1,
984
- placeholder: placeholderProp,
985
- className,
986
- style,
987
- imgClassName,
988
- imgStyle,
989
- sizes,
990
- loading: loadingProp,
991
- onLoad,
992
- objectFit = "cover",
993
- priority = false,
994
- fill = false,
995
- imageRendering,
996
- alt: altProp
997
- }) {
998
- const [loaded, setLoaded] = useState(false);
999
- const firedRef = useRef(false);
1000
- const isPixelRendering = imageRendering === "pixelated" || imageRendering === "crisp-edges";
1001
- const placeholder = placeholderProp ?? (isPixelRendering ? "none" : "blur");
1002
- const loading = priority ? "eager" : loadingProp ?? "lazy";
1003
- const aspectRatio = !fill && image.width && image.height ? `${image.width} / ${image.height}` : void 0;
1004
- const srcSet = getImageSrcSet(image);
1005
- const src = image.url ?? void 0;
1006
- const hasLqip = placeholder === "blur" && !!image.lqip;
1007
- const placeholderStyle = getImagePlaceholderStyle(image, {
1008
- type: placeholder
1009
- });
1010
- const placeholderColor = !hasLqip && "backgroundColor" in placeholderStyle ? placeholderStyle.backgroundColor : void 0;
1011
- const fireLoad = useCallback(() => {
1012
- if (firedRef.current) return;
1013
- firedRef.current = true;
1014
- setLoaded(true);
1015
- onLoad?.();
1016
- }, [onLoad]);
1017
- const imgRef = useCallback(
1018
- (node) => {
1019
- if (node && node.complete && node.naturalWidth > 0) {
1020
- fireLoad();
1021
- }
1022
- },
1023
- [fireLoad]
1024
- );
1025
- const containerStyle = {
1026
- position: "relative",
1027
- overflow: "hidden",
1028
- ...fill ? { width: "100%", height: "100%" } : {},
1029
- ...aspectRatio ? { aspectRatio } : {},
1030
- ...style
1031
- };
1032
- const overlayBase = {
1033
- position: "absolute",
1034
- top: 0,
1035
- left: 0,
1036
- width: "100%",
1037
- height: "100%",
1038
- opacity: loaded ? 0 : 1,
1039
- transition: "opacity 0.3s ease",
1040
- pointerEvents: "none"
1041
- };
1042
- const mainImgStyle = {
1043
- display: "block",
1044
- width: "100%",
1045
- height: "100%",
1046
- objectFit,
1047
- ...imageRendering ? { imageRendering } : {},
1048
- opacity: loaded ? 1 : 0,
1049
- transition: "opacity 0.3s ease",
1050
- ...imgStyle
1051
- };
1052
- return /* @__PURE__ */ React3.createElement("div", { className, style: containerStyle }, hasLqip && /* @__PURE__ */ React3.createElement(
1053
- "img",
1054
- {
1055
- "aria-hidden": true,
1056
- alt: "",
1057
- src: image.lqip,
1058
- style: {
1059
- ...overlayBase,
1060
- display: "block",
1061
- objectFit,
1062
- filter: "blur(20px)",
1063
- transform: "scale(1.1)"
1064
- }
1065
- }
1066
- ), placeholderColor && /* @__PURE__ */ React3.createElement(
1067
- "div",
1068
- {
1069
- "aria-hidden": true,
1070
- style: {
1071
- ...overlayBase,
1072
- backgroundColor: placeholderColor
1073
- }
1074
- }
1075
- ), /* @__PURE__ */ React3.createElement(
1076
- "img",
1077
- {
1078
- ref: imgRef,
1079
- alt: altProp ?? image.alt ?? "",
1080
- src,
1081
- srcSet: srcSet || void 0,
1082
- sizes,
1083
- width: width ? width * dpr : void 0,
1084
- loading,
1085
- decoding: "async",
1086
- fetchPriority: priority ? "high" : void 0,
1087
- onLoad: fireLoad,
1088
- className: imgClassName,
1089
- style: mainImgStyle
1090
- }
1091
- ));
1092
- }
1093
-
1094
- // src/ui/Canvas/node-types-factory.tsx
1095
1173
  function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrapper, renderNode) {
1096
1174
  const types = {};
1097
1175
  types.dynamic = ((props) => {
@@ -1121,22 +1199,6 @@ function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrap
1121
1199
  let content;
1122
1200
  if (CustomRenderer) {
1123
1201
  content = /* @__PURE__ */ React4.createElement(CustomRenderer, { ...slotProps });
1124
- } else if (d.nodeTypeSlug === "image") {
1125
- const imageVal = d.fields?.image;
1126
- if (imageVal && typeof imageVal === "object" && "url" in imageVal) {
1127
- content = /* @__PURE__ */ React4.createElement(
1128
- Image,
1129
- {
1130
- image: imageVal,
1131
- width: props.width,
1132
- fill: true,
1133
- priority: true,
1134
- objectFit: "contain"
1135
- }
1136
- );
1137
- } else {
1138
- content = defaultRender;
1139
- }
1140
1202
  } else {
1141
1203
  content = defaultRender;
1142
1204
  }
@@ -1621,8 +1683,6 @@ function CanvasFrame({
1621
1683
  );
1622
1684
  }
1623
1685
  export {
1624
- BUILT_IN_EDGE_TYPES,
1625
- BUILT_IN_NODE_TYPES,
1626
1686
  CanvasFrame,
1627
1687
  CanvasRenderer,
1628
1688
  clearTemplateCache,
@@ -1639,8 +1699,11 @@ export {
1639
1699
  prefetchCanvas,
1640
1700
  renderFieldValue,
1641
1701
  sanitizeCSS,
1702
+ toEdgeTypeDef,
1703
+ toNodeTypeDef,
1642
1704
  useCanvas,
1643
1705
  useCanvasData,
1644
- useQuickJS
1706
+ useQuickJS,
1707
+ validateTenantCSS
1645
1708
  };
1646
1709
  //# sourceMappingURL=canvas.js.map