@webstudio-is/sdk 0.237.0 → 0.252.1

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.
@@ -589,6 +589,16 @@ var collectionMeta = {
589
589
  required: true,
590
590
  control: "json",
591
591
  type: "json"
592
+ },
593
+ item: {
594
+ required: false,
595
+ control: "text",
596
+ type: "string"
597
+ },
598
+ itemKey: {
599
+ required: false,
600
+ control: "text",
601
+ type: "string"
592
602
  }
593
603
  }
594
604
  };
@@ -678,6 +688,7 @@ var linkMeta = {
678
688
  )
679
689
  };
680
690
  var collectionItem = new Parameter("collectionItem");
691
+ var collectionItemKey = new Parameter("collectionItemKey");
681
692
  var collectionMeta2 = {
682
693
  category: "data",
683
694
  order: 2,
@@ -686,6 +697,7 @@ var collectionMeta2 = {
686
697
  {
687
698
  data: ["Collection Item 1", "Collection Item 2", "Collection Item 3"],
688
699
  item: collectionItem,
700
+ itemKey: collectionItemKey,
689
701
  children: /* @__PURE__ */ jsx(ws.element, { "ws:tag": "div", children: /* @__PURE__ */ jsx(ws.element, { "ws:tag": "div", children: expression`${collectionItem}` }) })
690
702
  }
691
703
  )
package/lib/index.js CHANGED
@@ -33,7 +33,13 @@ var ImageAsset = z.object({
33
33
  meta: ImageMeta,
34
34
  type: z.literal("image")
35
35
  });
36
- var Asset = z.union([FontAsset, ImageAsset]);
36
+ var FileAsset = z.object({
37
+ ...baseAsset,
38
+ format: z.string(),
39
+ meta: z.object({}),
40
+ type: z.literal("file")
41
+ });
42
+ var Asset = z.union([FontAsset, ImageAsset, FileAsset]);
37
43
  var Assets = z.map(AssetId, Asset);
38
44
 
39
45
  // src/schema/pages.ts
@@ -577,14 +583,23 @@ var Breakpoint = z8.object({
577
583
  id: BreakpointId,
578
584
  label: z8.string(),
579
585
  minWidth: z8.number().optional(),
580
- maxWidth: z8.number().optional()
581
- }).refine(({ minWidth, maxWidth }) => {
586
+ maxWidth: z8.number().optional(),
587
+ condition: z8.string().optional()
588
+ }).transform((data) => {
589
+ if (data.condition !== void 0 && data.condition.trim() === "") {
590
+ return { ...data, condition: void 0 };
591
+ }
592
+ return data;
593
+ }).refine(({ minWidth, maxWidth, condition }) => {
594
+ if (condition !== void 0) {
595
+ return minWidth === void 0 && maxWidth === void 0;
596
+ }
582
597
  return (
583
598
  // Either min or max width have to be defined
584
599
  minWidth !== void 0 && maxWidth === void 0 || minWidth === void 0 && maxWidth !== void 0 || // This is a base breakpoint
585
600
  minWidth === void 0 && maxWidth === void 0
586
601
  );
587
- }, "Either minWidth or maxWidth should be defined");
602
+ }, "Either minWidth, maxWidth, or condition should be defined, but not both");
588
603
  var Breakpoints = z8.map(BreakpointId, Breakpoint);
589
604
  var initialBreakpoints = [
590
605
  { id: "placeholder", label: "Base" },
@@ -924,6 +939,303 @@ var WsComponentMeta = z15.object({
924
939
  props: z15.record(PropMeta).optional()
925
940
  });
926
941
 
942
+ // src/assets.ts
943
+ import warnOnce from "warn-once";
944
+ var ALLOWED_FILE_TYPES = {
945
+ // Documents
946
+ pdf: "application/pdf",
947
+ doc: "application/msword",
948
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
949
+ xls: "application/vnd.ms-excel",
950
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
951
+ csv: "text/csv",
952
+ ppt: "application/vnd.ms-powerpoint",
953
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
954
+ // Code
955
+ txt: "text/plain",
956
+ md: "text/markdown",
957
+ js: "text/javascript",
958
+ css: "text/css",
959
+ json: "application/json",
960
+ html: "text/html",
961
+ xml: "application/xml",
962
+ // Archives
963
+ zip: "application/zip",
964
+ rar: "application/vnd.rar",
965
+ // Audio
966
+ mp3: "audio/mpeg",
967
+ wav: "audio/wav",
968
+ ogg: "audio/ogg",
969
+ m4a: "audio/mp4",
970
+ // Video
971
+ mp4: "video/mp4",
972
+ mov: "video/quicktime",
973
+ avi: "video/x-msvideo",
974
+ webm: "video/webm",
975
+ // Images
976
+ // Note: Cloudflare Image Resizing supports: jpg, jpeg, png, gif, webp, svg, avif
977
+ // Other formats (bmp, ico, tif, tiff) are served as-is without optimization
978
+ jpg: "image/jpeg",
979
+ jpeg: "image/jpeg",
980
+ png: "image/png",
981
+ gif: "image/gif",
982
+ svg: "image/svg+xml",
983
+ webp: "image/webp",
984
+ avif: "image/avif",
985
+ ico: "image/vnd.microsoft.icon",
986
+ // Used for favicons
987
+ bmp: "image/bmp",
988
+ // Served without optimization
989
+ tif: "image/tiff",
990
+ // Served without optimization
991
+ tiff: "image/tiff",
992
+ // Served without optimization
993
+ // Fonts
994
+ woff: "font/woff",
995
+ woff2: "font/woff2",
996
+ ttf: "font/ttf",
997
+ otf: "font/otf"
998
+ };
999
+ var ALLOWED_FILE_EXTENSIONS = new Set(
1000
+ Object.keys(ALLOWED_FILE_TYPES)
1001
+ );
1002
+ var MIME_CATEGORIES = [
1003
+ "image",
1004
+ "video",
1005
+ "audio",
1006
+ "font",
1007
+ "text",
1008
+ "application"
1009
+ ];
1010
+ var FILE_EXTENSIONS_BY_CATEGORY = (() => {
1011
+ const categories = Object.fromEntries(
1012
+ MIME_CATEGORIES.map((category) => [category, []])
1013
+ );
1014
+ Object.entries(ALLOWED_FILE_TYPES).forEach(([ext, mimeType]) => {
1015
+ const [category] = mimeType.split("/");
1016
+ if (category in categories) {
1017
+ categories[category].push(ext);
1018
+ }
1019
+ });
1020
+ return categories;
1021
+ })();
1022
+ var extensionToMime = new Map(
1023
+ Object.entries(ALLOWED_FILE_TYPES).map(([ext, mime]) => [`.${ext}`, mime])
1024
+ );
1025
+ var mimeTypes = new Set(extensionToMime.values());
1026
+ var mimePatterns = /* @__PURE__ */ new Set([
1027
+ ...mimeTypes.values(),
1028
+ ...MIME_CATEGORIES.map((category) => `${category}/*`)
1029
+ ]);
1030
+ var getCategory = (pattern) => {
1031
+ const categoryAsString = pattern.split("/")[0];
1032
+ const category = MIME_CATEGORIES.find(
1033
+ (category2) => category2 === categoryAsString
1034
+ );
1035
+ if (category === void 0) {
1036
+ throw new Error(`Invalid mime pattern: ${pattern}`);
1037
+ }
1038
+ return category;
1039
+ };
1040
+ var IMAGE_EXTENSIONS = FILE_EXTENSIONS_BY_CATEGORY.image;
1041
+ var IMAGE_MIME_TYPES = IMAGE_EXTENSIONS.map(
1042
+ (ext) => ALLOWED_FILE_TYPES[ext]
1043
+ );
1044
+ var VIDEO_EXTENSIONS = FILE_EXTENSIONS_BY_CATEGORY.video;
1045
+ var VIDEO_MIME_TYPES = VIDEO_EXTENSIONS.map(
1046
+ (ext) => ALLOWED_FILE_TYPES[ext]
1047
+ );
1048
+ var FONT_EXTENSIONS = FILE_EXTENSIONS_BY_CATEGORY.font;
1049
+ var getMimeTypeByExtension = (extension) => {
1050
+ return ALLOWED_FILE_TYPES[extension.toLowerCase()];
1051
+ };
1052
+ var getMimeTypeByFilename = (fileName) => {
1053
+ const extension = fileName.split(".").pop()?.toLowerCase();
1054
+ if (!extension) {
1055
+ return "application/octet-stream";
1056
+ }
1057
+ return getMimeTypeByExtension(extension) ?? "application/octet-stream";
1058
+ };
1059
+ var isAllowedExtension = (extension) => {
1060
+ return ALLOWED_FILE_EXTENSIONS.has(
1061
+ extension.toLowerCase()
1062
+ );
1063
+ };
1064
+ var isAllowedMimeCategory = (category) => {
1065
+ return MIME_CATEGORIES.includes(category);
1066
+ };
1067
+ var acceptToMimePatterns = (accept) => {
1068
+ const result = /* @__PURE__ */ new Set();
1069
+ if (accept === "") {
1070
+ return "*";
1071
+ }
1072
+ for (const type of accept.split(",")) {
1073
+ const trimmed = type.trim();
1074
+ if (trimmed === "*" || trimmed === "*/*") {
1075
+ return "*";
1076
+ }
1077
+ if (mimePatterns.has(trimmed)) {
1078
+ result.add(trimmed);
1079
+ continue;
1080
+ }
1081
+ const mime = extensionToMime.get(trimmed);
1082
+ if (mime === void 0) {
1083
+ warnOnce(
1084
+ true,
1085
+ `Couldn't not parse accept attribute value: ${trimmed}. Falling back to "*".`
1086
+ );
1087
+ return "*";
1088
+ }
1089
+ result.add(mime);
1090
+ }
1091
+ return result;
1092
+ };
1093
+ var acceptToMimeCategories = (accept) => {
1094
+ const patterns = acceptToMimePatterns(accept);
1095
+ if (patterns === "*") {
1096
+ return "*";
1097
+ }
1098
+ const categories = /* @__PURE__ */ new Set();
1099
+ for (const pattern of patterns) {
1100
+ categories.add(getCategory(pattern));
1101
+ }
1102
+ return categories;
1103
+ };
1104
+ var getAssetMime = ({
1105
+ type,
1106
+ format
1107
+ }) => {
1108
+ const lowerFormat = format.toLowerCase();
1109
+ const mime = `${type}/${lowerFormat}`;
1110
+ if (mimeTypes.has(mime)) {
1111
+ return mime;
1112
+ }
1113
+ const mime2 = extensionToMime.get(`.${lowerFormat}`);
1114
+ if (mime2 === void 0) {
1115
+ warnOnce(
1116
+ true,
1117
+ `Couldn't determine mime type of asset: ${type}, ${format}.`
1118
+ );
1119
+ }
1120
+ return mime2;
1121
+ };
1122
+ var doesAssetMatchMimePatterns = (asset, patterns) => {
1123
+ if (patterns === "*") {
1124
+ return true;
1125
+ }
1126
+ const mime = getAssetMime(asset);
1127
+ if (mime !== void 0) {
1128
+ if (patterns.has(mime) || patterns.has(`${getCategory(mime)}/*`)) {
1129
+ return true;
1130
+ }
1131
+ }
1132
+ if (asset.type === "file" && asset.name) {
1133
+ const extension = asset.name.split(".").pop()?.toLowerCase();
1134
+ if (extension) {
1135
+ const mimeFromExtension = extensionToMime.get(`.${extension}`);
1136
+ if (mimeFromExtension) {
1137
+ return patterns.has(mimeFromExtension) || patterns.has(`${getCategory(mimeFromExtension)}/*`);
1138
+ }
1139
+ }
1140
+ }
1141
+ return false;
1142
+ };
1143
+ var validateFileName = (fileName) => {
1144
+ const extension = fileName.split(".").pop()?.toLowerCase();
1145
+ if (!extension) {
1146
+ throw new Error("File must have an extension");
1147
+ }
1148
+ if (!isAllowedExtension(extension)) {
1149
+ throw new Error(
1150
+ `File type "${extension}" is not allowed. Allowed types: ${Array.from(
1151
+ ALLOWED_FILE_EXTENSIONS
1152
+ ).join(", ")}`
1153
+ );
1154
+ }
1155
+ const mimeType = getMimeTypeByExtension(extension);
1156
+ if (!mimeType) {
1157
+ throw new Error(
1158
+ `Could not determine MIME type for extension: ${extension}`
1159
+ );
1160
+ }
1161
+ return { extension, mimeType };
1162
+ };
1163
+ var detectAssetType = (fileName) => {
1164
+ const ext = fileName.split(".").pop()?.toLowerCase();
1165
+ if (!ext) {
1166
+ return "file";
1167
+ }
1168
+ if (IMAGE_EXTENSIONS.includes(ext)) {
1169
+ return "image";
1170
+ }
1171
+ if (FONT_EXTENSIONS.includes(ext)) {
1172
+ return "font";
1173
+ }
1174
+ if (VIDEO_EXTENSIONS.includes(ext)) {
1175
+ return "video";
1176
+ }
1177
+ return "file";
1178
+ };
1179
+ var decodePathFragment = (fragment) => {
1180
+ const decoded = decodeURIComponent(fragment);
1181
+ if (decoded.includes("..") || decoded.startsWith("/")) {
1182
+ throw new Error("Invalid file path");
1183
+ }
1184
+ return decoded;
1185
+ };
1186
+ var getAssetUrl = (asset, origin) => {
1187
+ let path;
1188
+ const assetType = detectAssetType(asset.name);
1189
+ if (assetType === "image") {
1190
+ path = `/cgi/image/${asset.name}?format=raw`;
1191
+ } else {
1192
+ path = `/cgi/asset/${asset.name}?format=raw`;
1193
+ }
1194
+ return new URL(path, origin);
1195
+ };
1196
+ var extractImageMetadata = (asset) => {
1197
+ if (asset.type !== "image") {
1198
+ return;
1199
+ }
1200
+ if (asset.meta.width && asset.meta.height) {
1201
+ return {
1202
+ width: asset.meta.width,
1203
+ height: asset.meta.height
1204
+ };
1205
+ }
1206
+ };
1207
+ var extractFontMetadata = (asset) => {
1208
+ if (asset.type !== "font") {
1209
+ return;
1210
+ }
1211
+ const metadata = {
1212
+ family: asset.meta.family
1213
+ };
1214
+ if ("style" in asset.meta) {
1215
+ metadata.style = asset.meta.style;
1216
+ metadata.weight = asset.meta.weight;
1217
+ }
1218
+ return metadata;
1219
+ };
1220
+ var extractFileMetadata = (_asset) => {
1221
+ return;
1222
+ };
1223
+ var metadataExtractors = {
1224
+ image: extractImageMetadata,
1225
+ font: extractFontMetadata,
1226
+ file: extractFileMetadata
1227
+ };
1228
+ var toRuntimeAsset = (asset, origin) => {
1229
+ const extractor = metadataExtractors[asset.type];
1230
+ const metadata = extractor(asset);
1231
+ const url = getAssetUrl(asset, origin);
1232
+ const relativeUrl = url.pathname + url.search;
1233
+ return {
1234
+ url: relativeUrl,
1235
+ ...metadata
1236
+ };
1237
+ };
1238
+
927
1239
  // src/core-metas.ts
928
1240
  import {
929
1241
  ContentBlockIcon,
@@ -1495,6 +1807,16 @@ var collectionMeta = {
1495
1807
  required: true,
1496
1808
  control: "json",
1497
1809
  type: "json"
1810
+ },
1811
+ item: {
1812
+ required: false,
1813
+ control: "text",
1814
+ type: "string"
1815
+ },
1816
+ itemKey: {
1817
+ required: false,
1818
+ control: "text",
1819
+ type: "string"
1498
1820
  }
1499
1821
  }
1500
1822
  };
@@ -1680,7 +2002,13 @@ var allowedStringMethods = /* @__PURE__ */ new Set([
1680
2002
  "toLocaleLowerCase",
1681
2003
  "toLocaleUpperCase"
1682
2004
  ]);
1683
- var allowedArrayMethods = /* @__PURE__ */ new Set(["at", "includes", "join", "slice"]);
2005
+ var allowedArrayMethods = /* @__PURE__ */ new Set([
2006
+ "at",
2007
+ "includes",
2008
+ "join",
2009
+ "slice",
2010
+ "filter"
2011
+ ]);
1684
2012
  var lintExpression = ({
1685
2013
  expression,
1686
2014
  availableVariables = /* @__PURE__ */ new Set(),
@@ -2019,6 +2347,14 @@ var tokenRegexGlobal = new RegExp(tokenRegex.source, "g");
2019
2347
  var matchPathnameParams = (pathname) => {
2020
2348
  return pathname.matchAll(tokenRegexGlobal);
2021
2349
  };
2350
+ var isAbsoluteUrl = (href) => {
2351
+ try {
2352
+ new URL(href);
2353
+ return true;
2354
+ } catch {
2355
+ return false;
2356
+ }
2357
+ };
2022
2358
 
2023
2359
  // src/page-utils.ts
2024
2360
  var ROOT_FOLDER_ID = "root";
@@ -2649,6 +2985,8 @@ ${userSheet.cssText}`,
2649
2985
  };
2650
2986
  };
2651
2987
  export {
2988
+ ALLOWED_FILE_EXTENSIONS,
2989
+ ALLOWED_FILE_TYPES,
2652
2990
  Asset,
2653
2991
  Assets,
2654
2992
  Breakpoint,
@@ -2661,16 +2999,22 @@ export {
2661
2999
  DataSources,
2662
3000
  Deployment,
2663
3001
  ExpressionChild,
3002
+ FILE_EXTENSIONS_BY_CATEGORY,
3003
+ FONT_EXTENSIONS,
3004
+ FileAsset,
2664
3005
  Folder,
2665
3006
  FolderName,
2666
3007
  FontAsset,
2667
3008
  HomePagePath,
3009
+ IMAGE_EXTENSIONS,
3010
+ IMAGE_MIME_TYPES,
2668
3011
  IdChild,
2669
3012
  ImageAsset,
2670
3013
  ImageMeta,
2671
3014
  Instance,
2672
3015
  InstanceChild,
2673
3016
  Instances,
3017
+ MIME_CATEGORIES,
2674
3018
  OldPagePath,
2675
3019
  PageName,
2676
3020
  PagePath,
@@ -2697,8 +3041,12 @@ export {
2697
3041
  Styles,
2698
3042
  Templates,
2699
3043
  TextChild,
3044
+ VIDEO_EXTENSIONS,
3045
+ VIDEO_MIME_TYPES,
2700
3046
  WebstudioFragment,
2701
3047
  WsComponentMeta,
3048
+ acceptToMimeCategories,
3049
+ acceptToMimePatterns,
2702
3050
  addFontRules,
2703
3051
  allowedArrayMethods,
2704
3052
  allowedStringMethods,
@@ -2714,8 +3062,11 @@ export {
2714
3062
  createScope,
2715
3063
  decodeDataVariableId as decodeDataSourceVariable,
2716
3064
  decodeDataVariableId,
3065
+ decodePathFragment,
2717
3066
  descendantComponent,
3067
+ detectAssetType,
2718
3068
  documentTypes,
3069
+ doesAssetMatchMimePatterns,
2719
3070
  durationUnitValueSchema,
2720
3071
  elementComponent,
2721
3072
  encodeDataVariableId as encodeDataSourceVariable,
@@ -2730,13 +3081,20 @@ export {
2730
3081
  generateObjectExpression,
2731
3082
  generatePageMeta,
2732
3083
  generateResources,
3084
+ getAssetMime,
3085
+ getAssetUrl,
2733
3086
  getExpressionIdentifiers,
2734
3087
  getIndexesWithinAncestors,
3088
+ getMimeTypeByExtension,
3089
+ getMimeTypeByFilename,
2735
3090
  getPagePath,
2736
3091
  getStaticSiteMapXml,
2737
3092
  getStyleDeclKey,
2738
3093
  initialBreakpoints,
2739
3094
  insetUnitValueSchema,
3095
+ isAbsoluteUrl,
3096
+ isAllowedExtension,
3097
+ isAllowedMimeCategory,
2740
3098
  isComponentDetachable,
2741
3099
  isCoreComponent,
2742
3100
  isLiteralExpression,
@@ -2753,6 +3111,8 @@ export {
2753
3111
  scrollAnimationSchema,
2754
3112
  systemParameter,
2755
3113
  tags,
3114
+ toRuntimeAsset,
2756
3115
  transpileExpression,
3116
+ validateFileName,
2757
3117
  viewAnimationSchema
2758
3118
  };
package/lib/runtime.js CHANGED
@@ -39,6 +39,7 @@ var isLocalResource = (pathname, resourceName) => {
39
39
  };
40
40
  var sitemapResourceUrl = `/${LOCAL_RESOURCE_PREFIX}/sitemap.xml`;
41
41
  var currentDateResourceUrl = `/${LOCAL_RESOURCE_PREFIX}/current-date`;
42
+ var assetsResourceUrl = `/${LOCAL_RESOURCE_PREFIX}/assets`;
42
43
  var loadResource = async (customFetch, resourceRequest) => {
43
44
  try {
44
45
  const { method, searchParams, headers, body } = resourceRequest;
@@ -141,6 +142,12 @@ var cachedFetch = async (namespace, input, init) => {
141
142
  // src/form-fields.ts
142
143
  var formIdFieldName = `ws--form-id`;
143
144
  var formBotFieldName = `ws--form-bot`;
145
+ var isBraveBrowser = () => {
146
+ if (typeof navigator === "undefined") {
147
+ return false;
148
+ }
149
+ return navigator.brave?.isBrave?.() === true || navigator.brave !== void 0;
150
+ };
144
151
 
145
152
  // src/runtime.ts
146
153
  var tagProperty = "data-ws-tag";
@@ -154,6 +161,7 @@ var getIndexWithinAncestorFromProps = (props) => {
154
161
  var animationCanPlayOnCanvasProperty = "data-ws-animation-can-play-on-canvas";
155
162
  export {
156
163
  animationCanPlayOnCanvasProperty,
164
+ assetsResourceUrl,
157
165
  cachedFetch,
158
166
  createJsonStringifyProxy,
159
167
  currentDateResourceUrl,
@@ -162,6 +170,7 @@ export {
162
170
  getIndexWithinAncestorFromProps,
163
171
  getTagFromProps,
164
172
  indexProperty,
173
+ isBraveBrowser,
165
174
  isLocalResource,
166
175
  isPlainObject,
167
176
  loadResource,
@@ -0,0 +1,176 @@
1
+ import type { Asset } from "./schema/assets";
2
+ /**
3
+ * Central registry of allowed file types, extensions, and MIME types
4
+ * for asset uploads and serving.
5
+ *
6
+ * IMPORTANT: For images, we only support formats that Cloudflare Image Resizing can process.
7
+ * Supported by Cloudflare: JPEG, PNG, GIF, WebP, SVG, AVIF
8
+ * See: https://developers.cloudflare.com/images/image-resizing/format-limitations/
9
+ *
10
+ * Other formats (BMP, ICO, TIFF) are allowed for upload but served as-is without optimization.
11
+ */
12
+ export declare const ALLOWED_FILE_TYPES: {
13
+ readonly pdf: "application/pdf";
14
+ readonly doc: "application/msword";
15
+ readonly docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
16
+ readonly xls: "application/vnd.ms-excel";
17
+ readonly xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
18
+ readonly csv: "text/csv";
19
+ readonly ppt: "application/vnd.ms-powerpoint";
20
+ readonly pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation";
21
+ readonly txt: "text/plain";
22
+ readonly md: "text/markdown";
23
+ readonly js: "text/javascript";
24
+ readonly css: "text/css";
25
+ readonly json: "application/json";
26
+ readonly html: "text/html";
27
+ readonly xml: "application/xml";
28
+ readonly zip: "application/zip";
29
+ readonly rar: "application/vnd.rar";
30
+ readonly mp3: "audio/mpeg";
31
+ readonly wav: "audio/wav";
32
+ readonly ogg: "audio/ogg";
33
+ readonly m4a: "audio/mp4";
34
+ readonly mp4: "video/mp4";
35
+ readonly mov: "video/quicktime";
36
+ readonly avi: "video/x-msvideo";
37
+ readonly webm: "video/webm";
38
+ readonly jpg: "image/jpeg";
39
+ readonly jpeg: "image/jpeg";
40
+ readonly png: "image/png";
41
+ readonly gif: "image/gif";
42
+ readonly svg: "image/svg+xml";
43
+ readonly webp: "image/webp";
44
+ readonly avif: "image/avif";
45
+ readonly ico: "image/vnd.microsoft.icon";
46
+ readonly bmp: "image/bmp";
47
+ readonly tif: "image/tiff";
48
+ readonly tiff: "image/tiff";
49
+ readonly woff: "font/woff";
50
+ readonly woff2: "font/woff2";
51
+ readonly ttf: "font/ttf";
52
+ readonly otf: "font/otf";
53
+ };
54
+ export type AllowedFileExtension = keyof typeof ALLOWED_FILE_TYPES;
55
+ /**
56
+ * Set of allowed file extensions for quick lookup
57
+ */
58
+ export declare const ALLOWED_FILE_EXTENSIONS: ReadonlySet<AllowedFileExtension>;
59
+ /**
60
+ * Set of allowed MIME type categories
61
+ */
62
+ export declare const MIME_CATEGORIES: readonly ["image", "video", "audio", "font", "text", "application"];
63
+ export type MimeCategory = (typeof MIME_CATEGORIES)[number];
64
+ /**
65
+ * File extensions grouped by MIME category for UI display and filtering
66
+ */
67
+ export declare const FILE_EXTENSIONS_BY_CATEGORY: Readonly<Record<MimeCategory, AllowedFileExtension[]>>;
68
+ /**
69
+ * All image file extensions
70
+ */
71
+ export declare const IMAGE_EXTENSIONS: readonly AllowedFileExtension[];
72
+ /**
73
+ * All image MIME types
74
+ */
75
+ export declare const IMAGE_MIME_TYPES: readonly string[];
76
+ /**
77
+ * All video file extensions
78
+ */
79
+ export declare const VIDEO_EXTENSIONS: readonly AllowedFileExtension[];
80
+ /**
81
+ * All video MIME types
82
+ */
83
+ export declare const VIDEO_MIME_TYPES: readonly string[];
84
+ /**
85
+ * All font file extensions
86
+ */
87
+ export declare const FONT_EXTENSIONS: readonly AllowedFileExtension[];
88
+ /**
89
+ * Get MIME type for a given file extension
90
+ */
91
+ export declare const getMimeTypeByExtension: (extension: string) => string | undefined;
92
+ /**
93
+ * Get MIME type from a filename
94
+ */
95
+ export declare const getMimeTypeByFilename: (fileName: string) => string;
96
+ /**
97
+ * Check if a file extension is allowed
98
+ */
99
+ export declare const isAllowedExtension: (extension: string) => boolean;
100
+ /**
101
+ * Check if a MIME type category is allowed
102
+ */
103
+ export declare const isAllowedMimeCategory: (category: string) => boolean;
104
+ /**
105
+ * Convert accept attribute value to MIME patterns.
106
+ * `".svg,font/otf,text/*"` -> `"image/svg+xml", "font/otf", "text/*"`
107
+ */
108
+ export declare const acceptToMimePatterns: (accept: string) => Set<string> | "*";
109
+ /**
110
+ * Convert accept attribute value to MIME categories.
111
+ * `".svg,font/otf,text/*"` -> `"image", "font", "text"`
112
+ */
113
+ export declare const acceptToMimeCategories: (accept: string) => Set<MimeCategory> | "*";
114
+ /**
115
+ * Get MIME type for an asset based on its type and format
116
+ */
117
+ export declare const getAssetMime: ({ type, format, }: {
118
+ type: "image" | "font" | "file";
119
+ format: string;
120
+ }) => string | undefined;
121
+ /**
122
+ * Check if an asset matches the given MIME patterns.
123
+ * Supports legacy assets that were incorrectly stored with type "file".
124
+ */
125
+ export declare const doesAssetMatchMimePatterns: (asset: Pick<Asset, "format" | "type" | "name">, patterns: Set<string> | "*") => boolean;
126
+ /**
127
+ * Validate a filename and return its extension and MIME type
128
+ * @throws Error if file extension is not allowed
129
+ */
130
+ export declare const validateFileName: (fileName: string) => {
131
+ extension: string;
132
+ mimeType: string;
133
+ };
134
+ /**
135
+ * Detect the asset type from a file based on its extension
136
+ */
137
+ export declare const detectAssetType: (fileName: string) => "image" | "font" | "video" | "file";
138
+ /**
139
+ * Safely decode a URL path fragment, preventing path traversal attacks
140
+ * @throws Error if the decoded path contains path traversal attempts
141
+ */
142
+ export declare const decodePathFragment: (fragment: string) => string;
143
+ /**
144
+ * Generates the appropriate URL for an asset based on its type and format.
145
+ * - Images use /cgi/image/ with format=raw
146
+ * - All other assets (videos, audio, fonts, documents) use /cgi/asset/ with format=raw
147
+ *
148
+ * @param asset - The asset to generate URL for
149
+ * @param origin - Origin to prepend (e.g., "https://example.com"). When provided, returns an absolute URL.
150
+ * @returns A URL object. Use .pathname for relative paths, .href for absolute URLs
151
+ */
152
+ export declare const getAssetUrl: (asset: Asset, origin: string) => URL;
153
+ /**
154
+ * Runtime asset data structure with only fields needed at runtime.
155
+ * This is a simplified version of the Asset type, optimized for client-side usage.
156
+ */
157
+ export type RuntimeAsset = {
158
+ url: string;
159
+ width?: number;
160
+ height?: number;
161
+ family?: string;
162
+ style?: string;
163
+ weight?: number;
164
+ };
165
+ /**
166
+ * Converts a full Asset to a minimal RuntimeAsset format.
167
+ * This reduces payload size by including only runtime-needed data.
168
+ *
169
+ * Each asset type defines its own metadata extractor to ensure we only
170
+ * include the fields that are actually needed at runtime.
171
+ *
172
+ * @param asset - The full asset object
173
+ * @param origin - Origin to use for generating the asset URL (only used for URL construction, result is always a relative path)
174
+ * @returns A minimal RuntimeAsset object with relative URL
175
+ */
176
+ export declare const toRuntimeAsset: (asset: Asset, origin: string) => RuntimeAsset;
@@ -6,3 +6,10 @@ export declare const formIdFieldName = "ws--form-id";
6
6
  * Used for simlpe protection against non js bots
7
7
  */
8
8
  export declare const formBotFieldName = "ws--form-bot";
9
+ /**
10
+ * Detects if the browser is Brave.
11
+ * Brave Shields blocks our bot protection mechanism (matchMedia fingerprinting detection),
12
+ * causing form submissions to silently fail.
13
+ * @see https://github.com/brave/brave-browser/issues/46541
14
+ */
15
+ export declare const isBraveBrowser: () => boolean;
@@ -12,6 +12,7 @@ export * from "./schema/deployment";
12
12
  export * from "./schema/webstudio";
13
13
  export * from "./schema/prop-meta";
14
14
  export * from "./schema/component-meta";
15
+ export * from "./assets";
15
16
  export * from "./core-metas";
16
17
  export * from "./instances-utils";
17
18
  export * from "./page-utils";
@@ -5,6 +5,7 @@ import type { ResourceRequest } from "./schema/resources";
5
5
  export declare const isLocalResource: (pathname: string, resourceName?: string) => boolean;
6
6
  export declare const sitemapResourceUrl = "/$resources/sitemap.xml";
7
7
  export declare const currentDateResourceUrl = "/$resources/current-date";
8
+ export declare const assetsResourceUrl = "/$resources/assets";
8
9
  export declare const loadResource: (customFetch: typeof fetch, resourceRequest: ResourceRequest) => Promise<{
9
10
  ok: boolean;
10
11
  status: number;
@@ -165,6 +165,41 @@ export declare const ImageAsset: z.ZodObject<{
165
165
  description?: string | null | undefined;
166
166
  }>;
167
167
  export type ImageAsset = z.infer<typeof ImageAsset>;
168
+ export declare const FileAsset: z.ZodObject<{
169
+ format: z.ZodString;
170
+ meta: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
171
+ type: z.ZodLiteral<"file">;
172
+ id: z.ZodString;
173
+ projectId: z.ZodString;
174
+ size: z.ZodNumber;
175
+ name: z.ZodString;
176
+ filename: z.ZodOptional<z.ZodString>;
177
+ description: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodNull]>;
178
+ createdAt: z.ZodString;
179
+ }, "strip", z.ZodTypeAny, {
180
+ type: "file";
181
+ name: string;
182
+ format: string;
183
+ meta: {};
184
+ id: string;
185
+ projectId: string;
186
+ size: number;
187
+ createdAt: string;
188
+ filename?: string | undefined;
189
+ description?: string | null | undefined;
190
+ }, {
191
+ type: "file";
192
+ name: string;
193
+ format: string;
194
+ meta: {};
195
+ id: string;
196
+ projectId: string;
197
+ size: number;
198
+ createdAt: string;
199
+ filename?: string | undefined;
200
+ description?: string | null | undefined;
201
+ }>;
202
+ export type FileAsset = z.infer<typeof FileAsset>;
168
203
  export declare const Asset: z.ZodUnion<[z.ZodObject<{
169
204
  format: z.ZodUnion<[z.ZodLiteral<"ttf">, z.ZodLiteral<"woff">, z.ZodLiteral<"woff2">]>;
170
205
  meta: z.ZodUnion<[z.ZodObject<{
@@ -316,6 +351,39 @@ export declare const Asset: z.ZodUnion<[z.ZodObject<{
316
351
  createdAt: string;
317
352
  filename?: string | undefined;
318
353
  description?: string | null | undefined;
354
+ }>, z.ZodObject<{
355
+ format: z.ZodString;
356
+ meta: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
357
+ type: z.ZodLiteral<"file">;
358
+ id: z.ZodString;
359
+ projectId: z.ZodString;
360
+ size: z.ZodNumber;
361
+ name: z.ZodString;
362
+ filename: z.ZodOptional<z.ZodString>;
363
+ description: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodNull]>;
364
+ createdAt: z.ZodString;
365
+ }, "strip", z.ZodTypeAny, {
366
+ type: "file";
367
+ name: string;
368
+ format: string;
369
+ meta: {};
370
+ id: string;
371
+ projectId: string;
372
+ size: number;
373
+ createdAt: string;
374
+ filename?: string | undefined;
375
+ description?: string | null | undefined;
376
+ }, {
377
+ type: "file";
378
+ name: string;
379
+ format: string;
380
+ meta: {};
381
+ id: string;
382
+ projectId: string;
383
+ size: number;
384
+ createdAt: string;
385
+ filename?: string | undefined;
386
+ description?: string | null | undefined;
319
387
  }>]>;
320
388
  export type Asset = z.infer<typeof Asset>;
321
389
  export declare const Assets: z.ZodMap<z.ZodString, z.ZodUnion<[z.ZodObject<{
@@ -469,5 +537,38 @@ export declare const Assets: z.ZodMap<z.ZodString, z.ZodUnion<[z.ZodObject<{
469
537
  createdAt: string;
470
538
  filename?: string | undefined;
471
539
  description?: string | null | undefined;
540
+ }>, z.ZodObject<{
541
+ format: z.ZodString;
542
+ meta: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
543
+ type: z.ZodLiteral<"file">;
544
+ id: z.ZodString;
545
+ projectId: z.ZodString;
546
+ size: z.ZodNumber;
547
+ name: z.ZodString;
548
+ filename: z.ZodOptional<z.ZodString>;
549
+ description: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodNull]>;
550
+ createdAt: z.ZodString;
551
+ }, "strip", z.ZodTypeAny, {
552
+ type: "file";
553
+ name: string;
554
+ format: string;
555
+ meta: {};
556
+ id: string;
557
+ projectId: string;
558
+ size: number;
559
+ createdAt: string;
560
+ filename?: string | undefined;
561
+ description?: string | null | undefined;
562
+ }, {
563
+ type: "file";
564
+ name: string;
565
+ format: string;
566
+ meta: {};
567
+ id: string;
568
+ projectId: string;
569
+ size: number;
570
+ createdAt: string;
571
+ filename?: string | undefined;
572
+ description?: string | null | undefined;
472
573
  }>]>>;
473
574
  export type Assets = z.infer<typeof Assets>;
@@ -1,56 +1,90 @@
1
1
  import { z } from "zod";
2
- export declare const Breakpoint: z.ZodEffects<z.ZodObject<{
2
+ export declare const Breakpoint: z.ZodEffects<z.ZodEffects<z.ZodObject<{
3
3
  id: z.ZodString;
4
4
  label: z.ZodString;
5
5
  minWidth: z.ZodOptional<z.ZodNumber>;
6
6
  maxWidth: z.ZodOptional<z.ZodNumber>;
7
+ condition: z.ZodOptional<z.ZodString>;
7
8
  }, "strip", z.ZodTypeAny, {
8
9
  id: string;
9
10
  label: string;
10
11
  maxWidth?: number | undefined;
11
12
  minWidth?: number | undefined;
13
+ condition?: string | undefined;
12
14
  }, {
13
15
  id: string;
14
16
  label: string;
15
17
  maxWidth?: number | undefined;
16
18
  minWidth?: number | undefined;
19
+ condition?: string | undefined;
17
20
  }>, {
18
21
  id: string;
19
22
  label: string;
20
23
  maxWidth?: number | undefined;
21
24
  minWidth?: number | undefined;
25
+ condition?: string | undefined;
22
26
  }, {
23
27
  id: string;
24
28
  label: string;
25
29
  maxWidth?: number | undefined;
26
30
  minWidth?: number | undefined;
31
+ condition?: string | undefined;
32
+ }>, {
33
+ id: string;
34
+ label: string;
35
+ maxWidth?: number | undefined;
36
+ minWidth?: number | undefined;
37
+ condition?: string | undefined;
38
+ }, {
39
+ id: string;
40
+ label: string;
41
+ maxWidth?: number | undefined;
42
+ minWidth?: number | undefined;
43
+ condition?: string | undefined;
27
44
  }>;
28
45
  export type Breakpoint = z.infer<typeof Breakpoint>;
29
- export declare const Breakpoints: z.ZodMap<z.ZodString, z.ZodEffects<z.ZodObject<{
46
+ export declare const Breakpoints: z.ZodMap<z.ZodString, z.ZodEffects<z.ZodEffects<z.ZodObject<{
30
47
  id: z.ZodString;
31
48
  label: z.ZodString;
32
49
  minWidth: z.ZodOptional<z.ZodNumber>;
33
50
  maxWidth: z.ZodOptional<z.ZodNumber>;
51
+ condition: z.ZodOptional<z.ZodString>;
34
52
  }, "strip", z.ZodTypeAny, {
35
53
  id: string;
36
54
  label: string;
37
55
  maxWidth?: number | undefined;
38
56
  minWidth?: number | undefined;
57
+ condition?: string | undefined;
58
+ }, {
59
+ id: string;
60
+ label: string;
61
+ maxWidth?: number | undefined;
62
+ minWidth?: number | undefined;
63
+ condition?: string | undefined;
64
+ }>, {
65
+ id: string;
66
+ label: string;
67
+ maxWidth?: number | undefined;
68
+ minWidth?: number | undefined;
69
+ condition?: string | undefined;
39
70
  }, {
40
71
  id: string;
41
72
  label: string;
42
73
  maxWidth?: number | undefined;
43
74
  minWidth?: number | undefined;
75
+ condition?: string | undefined;
44
76
  }>, {
45
77
  id: string;
46
78
  label: string;
47
79
  maxWidth?: number | undefined;
48
80
  minWidth?: number | undefined;
81
+ condition?: string | undefined;
49
82
  }, {
50
83
  id: string;
51
84
  label: string;
52
85
  maxWidth?: number | undefined;
53
86
  minWidth?: number | undefined;
87
+ condition?: string | undefined;
54
88
  }>>;
55
89
  export type Breakpoints = z.infer<typeof Breakpoints>;
56
90
  export declare const initialBreakpoints: Array<Breakpoint>;
@@ -268,6 +268,39 @@ export declare const WebstudioFragment: z.ZodObject<{
268
268
  createdAt: string;
269
269
  filename?: string | undefined;
270
270
  description?: string | null | undefined;
271
+ }>, z.ZodObject<{
272
+ format: z.ZodString;
273
+ meta: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
274
+ type: z.ZodLiteral<"file">;
275
+ id: z.ZodString;
276
+ projectId: z.ZodString;
277
+ size: z.ZodNumber;
278
+ name: z.ZodString;
279
+ filename: z.ZodOptional<z.ZodString>;
280
+ description: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodNull]>;
281
+ createdAt: z.ZodString;
282
+ }, "strip", z.ZodTypeAny, {
283
+ type: "file";
284
+ name: string;
285
+ format: string;
286
+ meta: {};
287
+ id: string;
288
+ projectId: string;
289
+ size: number;
290
+ createdAt: string;
291
+ filename?: string | undefined;
292
+ description?: string | null | undefined;
293
+ }, {
294
+ type: "file";
295
+ name: string;
296
+ format: string;
297
+ meta: {};
298
+ id: string;
299
+ projectId: string;
300
+ size: number;
301
+ createdAt: string;
302
+ filename?: string | undefined;
303
+ description?: string | null | undefined;
271
304
  }>]>, "many">;
272
305
  dataSources: z.ZodArray<z.ZodUnion<[z.ZodObject<{
273
306
  type: z.ZodLiteral<"variable">;
@@ -22936,31 +22969,48 @@ export declare const WebstudioFragment: z.ZodObject<{
22936
22969
  instanceId: string;
22937
22970
  required?: boolean | undefined;
22938
22971
  }>]>, "many">;
22939
- breakpoints: z.ZodArray<z.ZodEffects<z.ZodObject<{
22972
+ breakpoints: z.ZodArray<z.ZodEffects<z.ZodEffects<z.ZodObject<{
22940
22973
  id: z.ZodString;
22941
22974
  label: z.ZodString;
22942
22975
  minWidth: z.ZodOptional<z.ZodNumber>;
22943
22976
  maxWidth: z.ZodOptional<z.ZodNumber>;
22977
+ condition: z.ZodOptional<z.ZodString>;
22944
22978
  }, "strip", z.ZodTypeAny, {
22945
22979
  id: string;
22946
22980
  label: string;
22947
22981
  maxWidth?: number | undefined;
22948
22982
  minWidth?: number | undefined;
22983
+ condition?: string | undefined;
22949
22984
  }, {
22950
22985
  id: string;
22951
22986
  label: string;
22952
22987
  maxWidth?: number | undefined;
22953
22988
  minWidth?: number | undefined;
22989
+ condition?: string | undefined;
22954
22990
  }>, {
22955
22991
  id: string;
22956
22992
  label: string;
22957
22993
  maxWidth?: number | undefined;
22958
22994
  minWidth?: number | undefined;
22995
+ condition?: string | undefined;
22959
22996
  }, {
22960
22997
  id: string;
22961
22998
  label: string;
22962
22999
  maxWidth?: number | undefined;
22963
23000
  minWidth?: number | undefined;
23001
+ condition?: string | undefined;
23002
+ }>, {
23003
+ id: string;
23004
+ label: string;
23005
+ maxWidth?: number | undefined;
23006
+ minWidth?: number | undefined;
23007
+ condition?: string | undefined;
23008
+ }, {
23009
+ id: string;
23010
+ label: string;
23011
+ maxWidth?: number | undefined;
23012
+ minWidth?: number | undefined;
23013
+ condition?: string | undefined;
22964
23014
  }>, "many">;
22965
23015
  styleSourceSelections: z.ZodArray<z.ZodObject<{
22966
23016
  instanceId: z.ZodString;
@@ -25247,6 +25297,17 @@ export declare const WebstudioFragment: z.ZodObject<{
25247
25297
  createdAt: string;
25248
25298
  filename?: string | undefined;
25249
25299
  description?: string | null | undefined;
25300
+ } | {
25301
+ type: "file";
25302
+ name: string;
25303
+ format: string;
25304
+ meta: {};
25305
+ id: string;
25306
+ projectId: string;
25307
+ size: number;
25308
+ createdAt: string;
25309
+ filename?: string | undefined;
25310
+ description?: string | null | undefined;
25250
25311
  })[];
25251
25312
  dataSources: ({
25252
25313
  value: {
@@ -26980,6 +27041,7 @@ export declare const WebstudioFragment: z.ZodObject<{
26980
27041
  label: string;
26981
27042
  maxWidth?: number | undefined;
26982
27043
  minWidth?: number | undefined;
27044
+ condition?: string | undefined;
26983
27045
  }[];
26984
27046
  styleSourceSelections: {
26985
27047
  values: string[];
@@ -27789,6 +27851,17 @@ export declare const WebstudioFragment: z.ZodObject<{
27789
27851
  createdAt: string;
27790
27852
  filename?: string | undefined;
27791
27853
  description?: string | null | undefined;
27854
+ } | {
27855
+ type: "file";
27856
+ name: string;
27857
+ format: string;
27858
+ meta: {};
27859
+ id: string;
27860
+ projectId: string;
27861
+ size: number;
27862
+ createdAt: string;
27863
+ filename?: string | undefined;
27864
+ description?: string | null | undefined;
27792
27865
  })[];
27793
27866
  dataSources: ({
27794
27867
  value: {
@@ -29522,6 +29595,7 @@ export declare const WebstudioFragment: z.ZodObject<{
29522
29595
  label: string;
29523
29596
  maxWidth?: number | undefined;
29524
29597
  minWidth?: number | undefined;
29598
+ condition?: string | undefined;
29525
29599
  }[];
29526
29600
  styleSourceSelections: {
29527
29601
  values: string[];
@@ -1,2 +1,6 @@
1
1
  export declare const isPathnamePattern: (pathname: string) => boolean;
2
2
  export declare const matchPathnameParams: (pathname: string) => RegExpStringIterator<RegExpExecArray>;
3
+ /**
4
+ * Check if a string is an absolute URL (has a valid protocol)
5
+ */
6
+ export declare const isAbsoluteUrl: (href: string) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webstudio-is/sdk",
3
- "version": "0.237.0",
3
+ "version": "0.252.1",
4
4
  "description": "Webstudio project data schema",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
@@ -40,20 +40,21 @@
40
40
  "change-case": "^5.4.4",
41
41
  "reserved-identifiers": "^1.0.0",
42
42
  "type-fest": "^4.37.0",
43
+ "warn-once": "^0.1.1",
43
44
  "zod": "^3.24.2",
44
- "@webstudio-is/icons": "0.237.0",
45
- "@webstudio-is/fonts": "0.237.0",
46
- "@webstudio-is/css-engine": "0.237.0"
45
+ "@webstudio-is/css-engine": "0.252.1",
46
+ "@webstudio-is/fonts": "0.252.1",
47
+ "@webstudio-is/icons": "0.252.1"
47
48
  },
48
49
  "devDependencies": {
49
50
  "html-tags": "^4.0.0",
50
51
  "vitest": "^3.1.2",
51
52
  "@webstudio-is/css-data": "0.0.0",
52
- "@webstudio-is/tsconfig": "1.0.7",
53
- "@webstudio-is/template": "0.237.0"
53
+ "@webstudio-is/template": "0.252.1",
54
+ "@webstudio-is/tsconfig": "1.0.7"
54
55
  },
55
56
  "scripts": {
56
- "typecheck": "tsc",
57
+ "typecheck": "tsgo --noEmit -p tsconfig.typecheck.json",
57
58
  "test": "vitest run",
58
59
  "build:normalize.css": "tsx --conditions=webstudio ./scripts/normalize.css.ts && prettier --write src/__generated__/normalize.css.ts",
59
60
  "build": "rm -rf lib && esbuild src/index.ts src/runtime.ts src/__generated__/normalize.css.ts src/core-templates.tsx --outdir=lib --bundle --format=esm --packages=external",