@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
@@ -31,8 +31,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/ui/Canvas/index.tsx
32
32
  var Canvas_exports = {};
33
33
  __export(Canvas_exports, {
34
- BUILT_IN_EDGE_TYPES: () => BUILT_IN_EDGE_TYPES,
35
- BUILT_IN_NODE_TYPES: () => BUILT_IN_NODE_TYPES,
36
34
  CanvasFrame: () => CanvasFrame,
37
35
  CanvasRenderer: () => CanvasRenderer,
38
36
  clearTemplateCache: () => clearTemplateCache,
@@ -49,9 +47,12 @@ __export(Canvas_exports, {
49
47
  prefetchCanvas: () => prefetchCanvas,
50
48
  renderFieldValue: () => renderFieldValue,
51
49
  sanitizeCSS: () => sanitizeCSS,
50
+ toEdgeTypeDef: () => toEdgeTypeDef,
51
+ toNodeTypeDef: () => toNodeTypeDef,
52
52
  useCanvas: () => useCanvas,
53
53
  useCanvasData: () => useCanvasData,
54
- useQuickJS: () => useQuickJS
54
+ useQuickJS: () => useQuickJS,
55
+ validateTenantCSS: () => validateTenantCSS
55
56
  });
56
57
  module.exports = __toCommonJS(Canvas_exports);
57
58
 
@@ -63,44 +64,6 @@ function isFrameNode(node) {
63
64
  return node.type === "frame";
64
65
  }
65
66
 
66
- // src/ui/Canvas/built-in-node-types.ts
67
- var BUILT_IN_NODE_TYPES = [
68
- {
69
- slug: "text",
70
- name: "Text",
71
- color: "#e5e7eb",
72
- defaultSize: { width: 200, height: 200 },
73
- fields: [{ name: "body", label: "Body", fieldType: "textarea" }]
74
- },
75
- {
76
- slug: "image",
77
- name: "Image",
78
- color: "#e5e7eb",
79
- transparentBackground: true,
80
- defaultSize: { width: 200, height: 200 },
81
- fields: [
82
- { name: "image", label: "Image", fieldType: "image" },
83
- { name: "alt", label: "Alt Text", fieldType: "text" },
84
- { name: "caption", label: "Caption", fieldType: "text" }
85
- ]
86
- }
87
- ];
88
-
89
- // src/ui/Canvas/built-in-edge-types.ts
90
- var BUILT_IN_EDGE_TYPES = [
91
- {
92
- slug: "default",
93
- name: "Default",
94
- color: "",
95
- strokeWidth: 2,
96
- animated: false,
97
- lineStyle: "default",
98
- markerStart: "none",
99
- markerEnd: "arrow",
100
- fields: []
101
- }
102
- ];
103
-
104
67
  // src/ui/Canvas/useCanvas.ts
105
68
  var import_react_query = require("@tanstack/react-query");
106
69
  var import_react = require("react");
@@ -154,7 +117,7 @@ function edgeTypesQueryOptions(client) {
154
117
  };
155
118
  }
156
119
 
157
- // src/ui/Canvas/useCanvas.ts
120
+ // src/ui/Canvas/catalog-mappers.ts
158
121
  function toNodeTypeDef(doc) {
159
122
  return {
160
123
  slug: String(doc.slug ?? ""),
@@ -165,7 +128,10 @@ function toNodeTypeDef(doc) {
165
128
  height: 200
166
129
  },
167
130
  fields: Array.isArray(doc.fields) ? doc.fields : [],
168
- transparentBackground: Boolean(doc.transparentBackground),
131
+ editorIntent: doc.editorIntent === "text" || doc.editorIntent === "image" ? doc.editorIntent : void 0,
132
+ transparentBackground: Boolean(
133
+ doc.hasTransparentBackground ?? doc.transparentBackground
134
+ ),
169
135
  template: doc.template ?? null,
170
136
  customCSS: doc.customCSS ?? null
171
137
  };
@@ -176,13 +142,16 @@ function toEdgeTypeDef(doc) {
176
142
  name: String(doc.title ?? ""),
177
143
  color: String(doc.color ?? ""),
178
144
  strokeWidth: doc.strokeWidth ?? 2,
179
- animated: doc.animated ?? false,
145
+ animated: doc.isAnimated ?? doc.animated ?? false,
180
146
  lineStyle: String(doc.lineStyle ?? "default"),
181
147
  markerStart: String(doc.markerStart ?? "none"),
182
148
  markerEnd: String(doc.markerEnd ?? "arrow"),
183
- fields: Array.isArray(doc.fields) ? doc.fields : []
149
+ fields: Array.isArray(doc.fields) ? doc.fields : [],
150
+ isDefaultEdge: Boolean(doc.isDefaultEdge)
184
151
  };
185
152
  }
153
+
154
+ // src/ui/Canvas/useCanvas.ts
186
155
  function useCanvas(options) {
187
156
  const { client, queryClient, slug, id, enabled = true } = options;
188
157
  const hasIdentifier = !!(slug || id);
@@ -208,16 +177,10 @@ function useCanvas(options) {
208
177
  queryClient
209
178
  );
210
179
  const nodeTypeDefs = (0, import_react.useMemo)(() => {
211
- const apiDefs = (nodeTypesQuery.data ?? []).map(toNodeTypeDef);
212
- const builtInSlugs = new Set(BUILT_IN_NODE_TYPES.map((t) => t.slug));
213
- const customDefs = apiDefs.filter((d) => !builtInSlugs.has(d.slug));
214
- return [...BUILT_IN_NODE_TYPES, ...customDefs];
180
+ return (nodeTypesQuery.data ?? []).map(toNodeTypeDef);
215
181
  }, [nodeTypesQuery.data]);
216
182
  const edgeTypeDefs = (0, import_react.useMemo)(() => {
217
- const apiDefs = (edgeTypesQuery.data ?? []).map(toEdgeTypeDef);
218
- const builtInSlugs = new Set(BUILT_IN_EDGE_TYPES.map((t) => t.slug));
219
- const customDefs = apiDefs.filter((d) => !builtInSlugs.has(d.slug));
220
- return [...BUILT_IN_EDGE_TYPES, ...customDefs];
183
+ return (edgeTypesQuery.data ?? []).map(toEdgeTypeDef);
221
184
  }, [edgeTypesQuery.data]);
222
185
  const canvasDoc = canvasQuery.data;
223
186
  const canvasData = canvasDoc?.canvas;
@@ -249,11 +212,11 @@ var import_react2 = require("react");
249
212
  function useCanvasData(options) {
250
213
  const { data, nodeTypeDefs: inputNodeDefs, edgeTypeDefs: inputEdgeDefs } = options;
251
214
  const nodeTypeDefsMap = (0, import_react2.useMemo)(() => {
252
- const allDefs = inputNodeDefs ?? BUILT_IN_NODE_TYPES;
215
+ const allDefs = inputNodeDefs ?? [];
253
216
  return new Map(allDefs.map((d) => [d.slug, d]));
254
217
  }, [inputNodeDefs]);
255
218
  const edgeTypeDefsMap = (0, import_react2.useMemo)(() => {
256
- const allDefs = inputEdgeDefs ?? BUILT_IN_EDGE_TYPES;
219
+ const allDefs = inputEdgeDefs ?? [];
257
220
  return new Map(allDefs.map((d) => [d.slug, d]));
258
221
  }, [inputEdgeDefs]);
259
222
  const nodes = (0, import_react2.useMemo)(() => data?.nodes ?? [], [data?.nodes]);
@@ -685,9 +648,176 @@ function clearTemplateCache() {
685
648
  }
686
649
 
687
650
  // src/ui/Canvas/node-renderers.tsx
651
+ var import_react6 = __toESM(require("react"), 1);
652
+
653
+ // src/ui/Image/index.tsx
688
654
  var import_react5 = __toESM(require("react"), 1);
655
+
656
+ // src/utils/image.ts
657
+ var IMAGE_SIZES = [384, 768, 1536];
658
+ function getImageSrcSet(image) {
659
+ const parts = [];
660
+ const sizes = image.sizes;
661
+ if (sizes) {
662
+ for (const size of IMAGE_SIZES) {
663
+ const entry = sizes[String(size)];
664
+ if (entry?.url && entry.width) {
665
+ parts.push(`${entry.url} ${entry.width}w`);
666
+ }
667
+ }
668
+ }
669
+ if (image.url && image.width) {
670
+ parts.push(`${image.url} ${image.width}w`);
671
+ }
672
+ return parts.join(", ");
673
+ }
674
+ function getImagePlaceholderStyle(image, options) {
675
+ const type = options?.type ?? "blur";
676
+ const paletteColor = options?.paletteColor ?? "muted";
677
+ if (type === "none") return {};
678
+ const color = image.palette?.[paletteColor];
679
+ if (type === "blur") {
680
+ const lqip = image.lqip;
681
+ if (lqip) {
682
+ return {
683
+ backgroundImage: `url(${lqip})`,
684
+ backgroundSize: "cover",
685
+ backgroundPosition: "center"
686
+ };
687
+ }
688
+ if (color) {
689
+ return { backgroundColor: color };
690
+ }
691
+ return {};
692
+ }
693
+ if (color) {
694
+ return { backgroundColor: color };
695
+ }
696
+ return {};
697
+ }
698
+
699
+ // src/ui/Image/index.tsx
700
+ function Image({
701
+ image,
702
+ width,
703
+ dpr = 1,
704
+ placeholder: placeholderProp,
705
+ className,
706
+ style,
707
+ imgClassName,
708
+ imgStyle,
709
+ sizes,
710
+ loading: loadingProp,
711
+ onLoad,
712
+ objectFit = "cover",
713
+ priority = false,
714
+ fill = false,
715
+ imageRendering,
716
+ alt: altProp
717
+ }) {
718
+ const [loaded, setLoaded] = (0, import_react5.useState)(false);
719
+ const firedRef = (0, import_react5.useRef)(false);
720
+ const isPixelRendering = imageRendering === "pixelated" || imageRendering === "crisp-edges";
721
+ const placeholder = placeholderProp ?? (isPixelRendering ? "none" : "blur");
722
+ const loading = priority ? "eager" : loadingProp ?? "lazy";
723
+ const aspectRatio = !fill && image.width && image.height ? `${image.width} / ${image.height}` : void 0;
724
+ const srcSet = getImageSrcSet(image);
725
+ const src = image.url ?? void 0;
726
+ const hasLqip = placeholder === "blur" && !!image.lqip;
727
+ const placeholderStyle = getImagePlaceholderStyle(image, {
728
+ type: placeholder
729
+ });
730
+ const placeholderColor = !hasLqip && "backgroundColor" in placeholderStyle ? placeholderStyle.backgroundColor : void 0;
731
+ const fireLoad = (0, import_react5.useCallback)(() => {
732
+ if (firedRef.current) return;
733
+ firedRef.current = true;
734
+ setLoaded(true);
735
+ onLoad?.();
736
+ }, [onLoad]);
737
+ const imgRef = (0, import_react5.useCallback)(
738
+ (node) => {
739
+ if (node && node.complete && node.naturalWidth > 0) {
740
+ fireLoad();
741
+ }
742
+ },
743
+ [fireLoad]
744
+ );
745
+ const containerStyle = {
746
+ position: "relative",
747
+ overflow: "hidden",
748
+ ...fill ? { width: "100%", height: "100%" } : {},
749
+ ...aspectRatio ? { aspectRatio } : {},
750
+ ...style
751
+ };
752
+ const overlayBase = {
753
+ position: "absolute",
754
+ top: 0,
755
+ left: 0,
756
+ width: "100%",
757
+ height: "100%",
758
+ opacity: loaded ? 0 : 1,
759
+ transition: "opacity 0.3s ease",
760
+ pointerEvents: "none"
761
+ };
762
+ const mainImgStyle = {
763
+ display: "block",
764
+ width: "100%",
765
+ height: "100%",
766
+ objectFit,
767
+ ...imageRendering ? { imageRendering } : {},
768
+ opacity: loaded ? 1 : 0,
769
+ transition: "opacity 0.3s ease",
770
+ ...imgStyle
771
+ };
772
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className, style: containerStyle }, hasLqip && /* @__PURE__ */ import_react5.default.createElement(
773
+ "img",
774
+ {
775
+ "aria-hidden": true,
776
+ alt: "",
777
+ src: image.lqip,
778
+ style: {
779
+ ...overlayBase,
780
+ display: "block",
781
+ objectFit,
782
+ filter: "blur(20px)",
783
+ transform: "scale(1.1)"
784
+ }
785
+ }
786
+ ), placeholderColor && /* @__PURE__ */ import_react5.default.createElement(
787
+ "div",
788
+ {
789
+ "aria-hidden": true,
790
+ style: {
791
+ ...overlayBase,
792
+ backgroundColor: placeholderColor
793
+ }
794
+ }
795
+ ), /* @__PURE__ */ import_react5.default.createElement(
796
+ "img",
797
+ {
798
+ ref: imgRef,
799
+ alt: altProp ?? image.alt ?? "",
800
+ src,
801
+ srcSet: srcSet || void 0,
802
+ sizes,
803
+ width: width ? width * dpr : void 0,
804
+ loading,
805
+ decoding: "async",
806
+ fetchPriority: priority ? "high" : void 0,
807
+ onLoad: fireLoad,
808
+ className: imgClassName,
809
+ style: mainImgStyle
810
+ }
811
+ ));
812
+ }
813
+
814
+ // src/ui/Canvas/node-renderers.tsx
689
815
  function sanitizeUrl(url) {
690
816
  if (!url) return url;
817
+ if (url !== url.trim()) return void 0;
818
+ if (url.startsWith("/") && !url.startsWith("//") || url.startsWith("./")) {
819
+ return url;
820
+ }
691
821
  try {
692
822
  const parsed = new URL(url);
693
823
  if (parsed.protocol === "http:" || parsed.protocol === "https:") return url;
@@ -696,6 +826,18 @@ function sanitizeUrl(url) {
696
826
  return void 0;
697
827
  }
698
828
  }
829
+ function sanitizeImageData(image, safeUrl) {
830
+ return {
831
+ ...image,
832
+ url: safeUrl,
833
+ sizes: image.sizes ? Object.fromEntries(
834
+ Object.entries(image.sizes).map(([name, size]) => [
835
+ name,
836
+ size ? { ...size, url: sanitizeUrl(size.url ?? void 0) } : size
837
+ ])
838
+ ) : image.sizes
839
+ };
840
+ }
699
841
  function renderFieldValue(key, val, fieldDef) {
700
842
  if (val == null || val === "") return null;
701
843
  const fieldType = fieldDef?.fieldType;
@@ -703,7 +845,19 @@ function renderFieldValue(key, val, fieldDef) {
703
845
  const imgUrl = typeof val === "string" ? val : val?.url;
704
846
  const safeUrl = sanitizeUrl(imgUrl);
705
847
  if (!safeUrl) return null;
706
- return /* @__PURE__ */ import_react5.default.createElement(
848
+ if (fieldType === "image" && typeof val === "object" && val !== null) {
849
+ return /* @__PURE__ */ import_react6.default.createElement(
850
+ Image,
851
+ {
852
+ key,
853
+ image: sanitizeImageData(val, safeUrl),
854
+ fill: true,
855
+ objectFit: "contain",
856
+ style: { flex: 1, minHeight: 0, width: "100%" }
857
+ }
858
+ );
859
+ }
860
+ return /* @__PURE__ */ import_react6.default.createElement(
707
861
  "img",
708
862
  {
709
863
  key,
@@ -714,7 +868,7 @@ function renderFieldValue(key, val, fieldDef) {
714
868
  }
715
869
  );
716
870
  }
717
- return /* @__PURE__ */ import_react5.default.createElement(
871
+ return /* @__PURE__ */ import_react6.default.createElement(
718
872
  "div",
719
873
  {
720
874
  key,
@@ -731,7 +885,7 @@ function renderFieldValue(key, val, fieldDef) {
731
885
  }
732
886
  function DefaultDynamicNode({ data }) {
733
887
  const d = data;
734
- return /* @__PURE__ */ import_react5.default.createElement(
888
+ return /* @__PURE__ */ import_react6.default.createElement(
735
889
  "div",
736
890
  {
737
891
  style: {
@@ -744,7 +898,7 @@ function DefaultDynamicNode({ data }) {
744
898
  d.fields && Object.entries(d.fields).filter(([, v]) => v != null && v !== "").map(([key, val]) => renderFieldValue(key, val))
745
899
  );
746
900
  }
747
- var TemplateErrorBoundary = class extends import_react5.default.Component {
901
+ var TemplateErrorBoundary = class extends import_react6.default.Component {
748
902
  constructor() {
749
903
  super(...arguments);
750
904
  this.state = { error: null };
@@ -759,7 +913,7 @@ var TemplateErrorBoundary = class extends import_react5.default.Component {
759
913
  }
760
914
  render() {
761
915
  if (this.state.error) {
762
- return /* @__PURE__ */ import_react5.default.createElement("div", { style: { padding: 8, fontSize: 11, color: "#ef4444" } }, /* @__PURE__ */ import_react5.default.createElement("strong", null, "Render error"), /* @__PURE__ */ import_react5.default.createElement("pre", { style: { fontSize: 10, whiteSpace: "pre-wrap" } }, process.env.NODE_ENV === "development" ? this.state.error.message : "Template render failed"));
916
+ return /* @__PURE__ */ import_react6.default.createElement("div", { style: { padding: 8, fontSize: 11, color: "#ef4444" } }, /* @__PURE__ */ import_react6.default.createElement("strong", null, "Render error"), /* @__PURE__ */ import_react6.default.createElement("pre", { style: { fontSize: 10, whiteSpace: "pre-wrap" } }, process.env.NODE_ENV === "development" ? this.state.error.message : "Template render failed"));
763
917
  }
764
918
  return this.props.children;
765
919
  }
@@ -774,13 +928,13 @@ function EnhancedDynamicNode({
774
928
  if (typeDef.template) {
775
929
  const Component = compileTemplate(typeDef.template, typeDef.slug);
776
930
  if (Component) {
777
- return /* @__PURE__ */ import_react5.default.createElement(
931
+ return /* @__PURE__ */ import_react6.default.createElement(
778
932
  "div",
779
933
  {
780
934
  className: `flow-node flow-node--${typeDef.slug}${typeDef.transparentBackground ? " flow-node--transparent-bg" : ""}`,
781
935
  style: { width: "100%", height: "100%" }
782
936
  },
783
- /* @__PURE__ */ import_react5.default.createElement(TemplateErrorBoundary, { resetKey: typeDef.template }, /* @__PURE__ */ import_react5.default.createElement(
937
+ /* @__PURE__ */ import_react6.default.createElement(TemplateErrorBoundary, { resetKey: typeDef.template }, /* @__PURE__ */ import_react6.default.createElement(
784
938
  Component,
785
939
  {
786
940
  fields: data.fields,
@@ -794,7 +948,7 @@ function EnhancedDynamicNode({
794
948
  );
795
949
  }
796
950
  }
797
- return /* @__PURE__ */ import_react5.default.createElement(
951
+ return /* @__PURE__ */ import_react6.default.createElement(
798
952
  "div",
799
953
  {
800
954
  className: `flow-node flow-node--${typeDef.slug}`,
@@ -822,12 +976,14 @@ function DefaultFrameNode({ data }) {
822
976
  const m = baseColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
823
977
  if (m) return `rgba(${m[1]},${m[2]},${m[3]},${opacity})`;
824
978
  const h6 = baseColor.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
825
- if (h6) return `rgba(${parseInt(h6[1], 16)},${parseInt(h6[2], 16)},${parseInt(h6[3], 16)},${opacity})`;
979
+ if (h6)
980
+ return `rgba(${parseInt(h6[1], 16)},${parseInt(h6[2], 16)},${parseInt(h6[3], 16)},${opacity})`;
826
981
  const h3 = baseColor.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i);
827
- if (h3) return `rgba(${parseInt(h3[1] + h3[1], 16)},${parseInt(h3[2] + h3[2], 16)},${parseInt(h3[3] + h3[3], 16)},${opacity})`;
982
+ if (h3)
983
+ return `rgba(${parseInt(h3[1] + h3[1], 16)},${parseInt(h3[2] + h3[2], 16)},${parseInt(h3[3] + h3[3], 16)},${opacity})`;
828
984
  return baseColor;
829
985
  })();
830
- return /* @__PURE__ */ import_react5.default.createElement(
986
+ return /* @__PURE__ */ import_react6.default.createElement(
831
987
  "div",
832
988
  {
833
989
  style: {
@@ -838,7 +994,7 @@ function DefaultFrameNode({ data }) {
838
994
  border: borderStyle === "none" ? "none" : `2px ${borderStyle} rgba(128,128,128,0.3)`
839
995
  }
840
996
  },
841
- /* @__PURE__ */ import_react5.default.createElement(
997
+ /* @__PURE__ */ import_react6.default.createElement(
842
998
  "div",
843
999
  {
844
1000
  style: {
@@ -875,21 +1031,64 @@ function getBooleanField(fields, name) {
875
1031
 
876
1032
  // src/ui/Canvas/css-sanitizer.ts
877
1033
  var import_postcss = __toESM(require("postcss"), 1);
878
- var ALLOWED_AT_RULES = /* @__PURE__ */ new Set(["keyframes", "media", "supports", "container", "layer"]);
879
- var BLOCKED_VALUE_PATTERNS = [/url\s*\(/i, /expression\s*\(/i, /paint\s*\(/i, /-moz-binding/i];
1034
+ var ALLOWED_AT_RULES = /* @__PURE__ */ new Set([
1035
+ "keyframes",
1036
+ "media",
1037
+ "supports",
1038
+ "container",
1039
+ "layer"
1040
+ ]);
1041
+ var BLOCKED_VALUE_PATTERNS = [
1042
+ /url\s*\(/i,
1043
+ /expression\s*\(/i,
1044
+ /paint\s*\(/i,
1045
+ /-moz-binding/i
1046
+ ];
880
1047
  var DANGEROUS_SELECTOR = /(?:^|[\s,])(?::root|html|body)\b/;
881
1048
  var BLOCKED_PROPERTY_VALUES = {
882
1049
  // absolute is allowed — contained by nearest positioned ancestor (node wrapper)
883
1050
  position: /fixed|sticky/i
884
1051
  };
1052
+ var TENANT_CSS_BLOCKED_PATTERNS = [
1053
+ { pattern: /<\/style/i, label: "</style" },
1054
+ { pattern: /<script/i, label: "<script" },
1055
+ { pattern: /@import/i, label: "@import" },
1056
+ { pattern: /url\s*\(/i, label: "url(" }
1057
+ ];
885
1058
  var Z_INDEX_MAX = 9998;
1059
+ function normalizeCSSEscapes(value) {
1060
+ return value.replace(
1061
+ /\\(?:([0-9a-fA-F]{1,6})(?:\r\n|[ \t\r\n\f])?|([^\r\n\f]))/g,
1062
+ (match, hex, escaped) => {
1063
+ if (hex) {
1064
+ const cp = parseInt(hex, 16);
1065
+ if (Number.isNaN(cp) || cp > 1114111) return match;
1066
+ try {
1067
+ return String.fromCodePoint(cp);
1068
+ } catch {
1069
+ return match;
1070
+ }
1071
+ }
1072
+ return escaped ?? match;
1073
+ }
1074
+ );
1075
+ }
886
1076
  function sanitizeCSS(css, scopeClass) {
1077
+ return sanitizeCSSWithStats(css, scopeClass).css;
1078
+ }
1079
+ function sanitizeCSSWithStats(css, scopeClass) {
887
1080
  let root;
888
1081
  try {
889
1082
  root = import_postcss.default.parse(css);
890
1083
  } catch {
891
- return "";
1084
+ return { css: "", removedRules: 0, removedDecls: 0, removedAtRules: 0 };
892
1085
  }
1086
+ const stats = {
1087
+ css: "",
1088
+ removedRules: 0,
1089
+ removedDecls: 0,
1090
+ removedAtRules: 0
1091
+ };
893
1092
  const safeScopeClass = scopeClass ? scopeClass.replace(/[{}()[\];,'"\\<>\s]/g, "") : "";
894
1093
  const keyframeNameMap = /* @__PURE__ */ new Map();
895
1094
  if (safeScopeClass) {
@@ -902,7 +1101,13 @@ function sanitizeCSS(css, scopeClass) {
902
1101
  });
903
1102
  }
904
1103
  if (safeScopeClass && keyframeNameMap.size > 0) {
905
- const ANIM_KEYWORDS = /* @__PURE__ */ new Set(["none", "initial", "inherit", "unset", "revert"]);
1104
+ const ANIM_KEYWORDS = /* @__PURE__ */ new Set([
1105
+ "none",
1106
+ "initial",
1107
+ "inherit",
1108
+ "unset",
1109
+ "revert"
1110
+ ]);
906
1111
  const replaceAnimName = (token) => {
907
1112
  const t = token.trim().replace(/^['"]|['"]$/g, "");
908
1113
  return ANIM_KEYWORDS.has(t) ? token : keyframeNameMap.get(t) ?? token;
@@ -918,23 +1123,24 @@ function sanitizeCSS(css, scopeClass) {
918
1123
  }
919
1124
  root.walkAtRules((node) => {
920
1125
  if (!ALLOWED_AT_RULES.has(node.name.toLowerCase())) {
1126
+ stats.removedAtRules++;
921
1127
  node.remove();
922
1128
  }
923
1129
  });
924
1130
  root.walkDecls((node) => {
925
- const normalizedValue = node.value.replace(/\/\*[\s\S]*?\*\//g, "");
1131
+ const normalizedValue = normalizeCSSEscapes(
1132
+ node.value.replace(/\/\*[\s\S]*?\*\//g, "")
1133
+ );
926
1134
  if (BLOCKED_VALUE_PATTERNS.some((p) => p.test(normalizedValue))) {
1135
+ stats.removedDecls++;
927
1136
  node.remove();
928
1137
  return;
929
1138
  }
930
1139
  const propLower = node.prop.toLowerCase();
931
1140
  const blockedValuePattern = BLOCKED_PROPERTY_VALUES[propLower];
932
1141
  if (blockedValuePattern) {
933
- const deescaped = normalizedValue.replace(
934
- /\\([0-9a-fA-F]{1,6})\s?/g,
935
- (_, hex) => String.fromCodePoint(parseInt(hex, 16))
936
- );
937
- if (blockedValuePattern.test(deescaped)) {
1142
+ if (blockedValuePattern.test(normalizedValue)) {
1143
+ stats.removedDecls++;
938
1144
  node.remove();
939
1145
  return;
940
1146
  }
@@ -945,6 +1151,7 @@ function sanitizeCSS(css, scopeClass) {
945
1151
  } else {
946
1152
  const val = parseInt(zv, 10);
947
1153
  if (isNaN(val) || String(val) !== zv || val > Z_INDEX_MAX) {
1154
+ stats.removedDecls++;
948
1155
  node.remove();
949
1156
  return;
950
1157
  }
@@ -952,13 +1159,21 @@ function sanitizeCSS(css, scopeClass) {
952
1159
  }
953
1160
  });
954
1161
  root.walkRules((rule) => {
955
- rule.selectors = rule.selectors.filter((s) => !DANGEROUS_SELECTOR.test(s));
956
- if (!rule.selectors.length) {
1162
+ const safeSelectors = rule.selectors.filter(
1163
+ (s) => s.trim() && !DANGEROUS_SELECTOR.test(s)
1164
+ );
1165
+ if (!safeSelectors.length) {
1166
+ stats.removedRules++;
957
1167
  rule.remove();
958
1168
  return;
959
1169
  }
1170
+ if (safeSelectors.length !== rule.selectors.length) {
1171
+ stats.removedRules++;
1172
+ }
1173
+ rule.selectors = safeSelectors;
960
1174
  if (scopeClass) {
961
1175
  if (!safeScopeClass) {
1176
+ stats.removedRules++;
962
1177
  rule.remove();
963
1178
  return;
964
1179
  }
@@ -969,178 +1184,42 @@ function sanitizeCSS(css, scopeClass) {
969
1184
  rule.selectors = rule.selectors.map((sel) => `.${safeScopeClass} ${sel}`);
970
1185
  }
971
1186
  });
972
- return root.toString().replace(/<\/style/gi, "<\\/style");
1187
+ stats.css = root.toString().replace(/<\/style/gi, "<\\/style");
1188
+ return stats;
973
1189
  }
974
-
975
- // src/ui/Canvas/CanvasRenderer.tsx
976
- var import_react11 = __toESM(require("react"), 1);
977
- var import_react12 = require("@xyflow/react");
978
-
979
- // src/ui/Canvas/node-types-factory.tsx
980
- var import_react7 = __toESM(require("react"), 1);
981
-
982
- // src/ui/Image/index.tsx
983
- var import_react6 = __toESM(require("react"), 1);
984
-
985
- // src/utils/image.ts
986
- var IMAGE_SIZES = [384, 768, 1536];
987
- function getImageSrcSet(image) {
988
- const parts = [];
989
- const sizes = image.sizes;
990
- if (sizes) {
991
- for (const size of IMAGE_SIZES) {
992
- const entry = sizes[String(size)];
993
- if (entry?.url && entry.width) {
994
- parts.push(`${entry.url} ${entry.width}w`);
995
- }
1190
+ function validateTenantCSS(css) {
1191
+ const trimmed = css.trim();
1192
+ if (!trimmed) return null;
1193
+ const deescapedCSS = normalizeCSSEscapes(css);
1194
+ for (const { pattern, label } of TENANT_CSS_BLOCKED_PATTERNS) {
1195
+ if (pattern.test(deescapedCSS)) {
1196
+ return `CSS contains blocked pattern: ${label}`;
996
1197
  }
997
1198
  }
998
- if (image.url && image.width) {
999
- parts.push(`${image.url} ${image.width}w`);
1199
+ try {
1200
+ import_postcss.default.parse(css);
1201
+ } catch {
1202
+ return "CSS parse error";
1000
1203
  }
1001
- return parts.join(", ");
1002
- }
1003
- function getImagePlaceholderStyle(image, options) {
1004
- const type = options?.type ?? "blur";
1005
- const paletteColor = options?.paletteColor ?? "muted";
1006
- if (type === "none") return {};
1007
- const color = image.palette?.[paletteColor];
1008
- if (type === "blur") {
1009
- const lqip = image.lqip;
1010
- if (lqip) {
1011
- return {
1012
- backgroundImage: `url(${lqip})`,
1013
- backgroundSize: "cover",
1014
- backgroundPosition: "center"
1015
- };
1016
- }
1017
- if (color) {
1018
- return { backgroundColor: color };
1019
- }
1020
- return {};
1204
+ const sanitized = sanitizeCSSWithStats(
1205
+ css,
1206
+ "flow-node--tenant-css-validation"
1207
+ );
1208
+ if (!sanitized.css.trim()) {
1209
+ return "CSS must contain safe rules";
1021
1210
  }
1022
- if (color) {
1023
- return { backgroundColor: color };
1211
+ if (sanitized.removedRules > 0 || sanitized.removedDecls > 0 || sanitized.removedAtRules > 0) {
1212
+ return "CSS contains unsafe rules or selectors";
1024
1213
  }
1025
- return {};
1214
+ return null;
1026
1215
  }
1027
1216
 
1028
- // src/ui/Image/index.tsx
1029
- function Image({
1030
- image,
1031
- width,
1032
- dpr = 1,
1033
- placeholder: placeholderProp,
1034
- className,
1035
- style,
1036
- imgClassName,
1037
- imgStyle,
1038
- sizes,
1039
- loading: loadingProp,
1040
- onLoad,
1041
- objectFit = "cover",
1042
- priority = false,
1043
- fill = false,
1044
- imageRendering,
1045
- alt: altProp
1046
- }) {
1047
- const [loaded, setLoaded] = (0, import_react6.useState)(false);
1048
- const firedRef = (0, import_react6.useRef)(false);
1049
- const isPixelRendering = imageRendering === "pixelated" || imageRendering === "crisp-edges";
1050
- const placeholder = placeholderProp ?? (isPixelRendering ? "none" : "blur");
1051
- const loading = priority ? "eager" : loadingProp ?? "lazy";
1052
- const aspectRatio = !fill && image.width && image.height ? `${image.width} / ${image.height}` : void 0;
1053
- const srcSet = getImageSrcSet(image);
1054
- const src = image.url ?? void 0;
1055
- const hasLqip = placeholder === "blur" && !!image.lqip;
1056
- const placeholderStyle = getImagePlaceholderStyle(image, {
1057
- type: placeholder
1058
- });
1059
- const placeholderColor = !hasLqip && "backgroundColor" in placeholderStyle ? placeholderStyle.backgroundColor : void 0;
1060
- const fireLoad = (0, import_react6.useCallback)(() => {
1061
- if (firedRef.current) return;
1062
- firedRef.current = true;
1063
- setLoaded(true);
1064
- onLoad?.();
1065
- }, [onLoad]);
1066
- const imgRef = (0, import_react6.useCallback)(
1067
- (node) => {
1068
- if (node && node.complete && node.naturalWidth > 0) {
1069
- fireLoad();
1070
- }
1071
- },
1072
- [fireLoad]
1073
- );
1074
- const containerStyle = {
1075
- position: "relative",
1076
- overflow: "hidden",
1077
- ...fill ? { width: "100%", height: "100%" } : {},
1078
- ...aspectRatio ? { aspectRatio } : {},
1079
- ...style
1080
- };
1081
- const overlayBase = {
1082
- position: "absolute",
1083
- top: 0,
1084
- left: 0,
1085
- width: "100%",
1086
- height: "100%",
1087
- opacity: loaded ? 0 : 1,
1088
- transition: "opacity 0.3s ease",
1089
- pointerEvents: "none"
1090
- };
1091
- const mainImgStyle = {
1092
- display: "block",
1093
- width: "100%",
1094
- height: "100%",
1095
- objectFit,
1096
- ...imageRendering ? { imageRendering } : {},
1097
- opacity: loaded ? 1 : 0,
1098
- transition: "opacity 0.3s ease",
1099
- ...imgStyle
1100
- };
1101
- return /* @__PURE__ */ import_react6.default.createElement("div", { className, style: containerStyle }, hasLqip && /* @__PURE__ */ import_react6.default.createElement(
1102
- "img",
1103
- {
1104
- "aria-hidden": true,
1105
- alt: "",
1106
- src: image.lqip,
1107
- style: {
1108
- ...overlayBase,
1109
- display: "block",
1110
- objectFit,
1111
- filter: "blur(20px)",
1112
- transform: "scale(1.1)"
1113
- }
1114
- }
1115
- ), placeholderColor && /* @__PURE__ */ import_react6.default.createElement(
1116
- "div",
1117
- {
1118
- "aria-hidden": true,
1119
- style: {
1120
- ...overlayBase,
1121
- backgroundColor: placeholderColor
1122
- }
1123
- }
1124
- ), /* @__PURE__ */ import_react6.default.createElement(
1125
- "img",
1126
- {
1127
- ref: imgRef,
1128
- alt: altProp ?? image.alt ?? "",
1129
- src,
1130
- srcSet: srcSet || void 0,
1131
- sizes,
1132
- width: width ? width * dpr : void 0,
1133
- loading,
1134
- decoding: "async",
1135
- fetchPriority: priority ? "high" : void 0,
1136
- onLoad: fireLoad,
1137
- className: imgClassName,
1138
- style: mainImgStyle
1139
- }
1140
- ));
1141
- }
1217
+ // src/ui/Canvas/CanvasRenderer.tsx
1218
+ var import_react11 = __toESM(require("react"), 1);
1219
+ var import_react12 = require("@xyflow/react");
1142
1220
 
1143
1221
  // src/ui/Canvas/node-types-factory.tsx
1222
+ var import_react7 = __toESM(require("react"), 1);
1144
1223
  function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrapper, renderNode) {
1145
1224
  const types = {};
1146
1225
  types.dynamic = ((props) => {
@@ -1170,22 +1249,6 @@ function createNodeTypes(nodeRenderers, nodeTypeDefsMap, frameRenderer, nodeWrap
1170
1249
  let content;
1171
1250
  if (CustomRenderer) {
1172
1251
  content = /* @__PURE__ */ import_react7.default.createElement(CustomRenderer, { ...slotProps });
1173
- } else if (d.nodeTypeSlug === "image") {
1174
- const imageVal = d.fields?.image;
1175
- if (imageVal && typeof imageVal === "object" && "url" in imageVal) {
1176
- content = /* @__PURE__ */ import_react7.default.createElement(
1177
- Image,
1178
- {
1179
- image: imageVal,
1180
- width: props.width,
1181
- fill: true,
1182
- priority: true,
1183
- objectFit: "contain"
1184
- }
1185
- );
1186
- } else {
1187
- content = defaultRender;
1188
- }
1189
1252
  } else {
1190
1253
  content = defaultRender;
1191
1254
  }