camox 0.22.0 → 0.23.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.
@@ -12,6 +12,7 @@ import { useFieldSelection } from "./hooks/useFieldSelection.js";
12
12
  import { useIsEditable } from "./hooks/useIsEditable.js";
13
13
  import { useOverlayMessage } from "./hooks/useOverlayMessage.js";
14
14
  import { Type, resolveToMarkdown } from "./lib/contentType.js";
15
+ import { buildImageSrcSet, getDefaultImageSizes, getDefaultImageWidth, transformImageUrl } from "./lib/imageTransform.js";
15
16
  import { markdownToReactNodes } from "./lib/lexicalReact.js";
16
17
  import { c } from "react/compiler-runtime";
17
18
  import { Input } from "@camox/ui/input";
@@ -58,8 +59,8 @@ let hasShownEmbedLockToast = false;
58
59
  function buildInitialSeeds(properties, parentTempId, content, allSeeds, counter) {
59
60
  for (const [fieldName, fieldSchema] of Object.entries(properties)) {
60
61
  if (fieldSchema.type !== "array" || !fieldSchema.items?.properties) continue;
61
- const ait = fieldSchema.arrayItemType;
62
- if (ait === "Image" || ait === "File") continue;
62
+ const ft = fieldSchema.fieldType;
63
+ if (ft === "ImageList" || ft === "FileList") continue;
63
64
  const defaultCount = fieldSchema.defaultItems ?? fieldSchema.minItems ?? 0;
64
65
  if (defaultCount <= 0) continue;
65
66
  const itemProperties = fieldSchema.items.properties;
@@ -97,7 +98,7 @@ function buildInitialSeeds(properties, parentTempId, content, allSeeds, counter)
97
98
  function buildPeekItems(properties, blockId, parentItemId, content, allItems, counter) {
98
99
  for (const [fieldName, fieldSchema] of Object.entries(properties)) {
99
100
  if (fieldSchema.type !== "array" || !fieldSchema.items?.properties) continue;
100
- if (fieldSchema.fieldType === "MultipleAssets") continue;
101
+ if (fieldSchema.fieldType === "ImageList" || fieldSchema.fieldType === "FileList") continue;
101
102
  const defaultCount = fieldSchema.defaultItems ?? fieldSchema.minItems ?? 0;
102
103
  if (defaultCount <= 0) continue;
103
104
  const itemProperties = fieldSchema.items.properties;
@@ -158,8 +159,7 @@ function createBlock(options) {
158
159
  for (const [key, prop] of Object.entries(typeboxSchema.properties)) if ("default" in prop) {
159
160
  contentDefaults[key] = prop.default;
160
161
  const ft = prop.fieldType;
161
- const ait = prop.arrayItemType;
162
- if (ft === "Image" || ft === "File" || ft === "RepeatableItem" || ait === "Image" || ait === "File") continue;
162
+ if (ft === "Image" || ft === "File" || ft === "ImageList" || ft === "FileList" || ft === "Repeater") continue;
163
163
  contentDefaultsForStorage[key] = prop.default;
164
164
  }
165
165
  const repeatableItemDefaults = {};
@@ -282,9 +282,9 @@ function createBlock(options) {
282
282
  };
283
283
  const Embed = (t0) => {
284
284
  const $ = c(59);
285
- if ($[0] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
285
+ if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
286
286
  for (let $i = 0; $i < 59; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
287
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
287
+ $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
288
288
  }
289
289
  const { name, children } = t0;
290
290
  const blockContext = React.use(Context);
@@ -532,9 +532,9 @@ function createBlock(options) {
532
532
  };
533
533
  const Link = (t0) => {
534
534
  const $ = c(38);
535
- if ($[0] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
535
+ if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
536
536
  for (let $i = 0; $i < 38; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
537
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
537
+ $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
538
538
  }
539
539
  const { name, children } = t0;
540
540
  const blockContext = React.use(Context);
@@ -773,9 +773,9 @@ function createBlock(options) {
773
773
  };
774
774
  const Image = (t0) => {
775
775
  const $ = c(22);
776
- if ($[0] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
776
+ if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
777
777
  for (let $i = 0; $i < 22; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
778
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
778
+ $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
779
779
  }
780
780
  const { name, children } = t0;
781
781
  const blockContext = React.use(Context);
@@ -844,7 +844,12 @@ function createBlock(options) {
844
844
  } else t4 = $[10];
845
845
  const handleClick = t4;
846
846
  const imageProps = {
847
- src: fieldValue.url,
847
+ src: transformImageUrl(fieldValue.url, {
848
+ width: getDefaultImageWidth(),
849
+ mimeType: fieldValue.mimeType
850
+ }),
851
+ srcSet: buildImageSrcSet(fieldValue.url, fieldValue.mimeType),
852
+ sizes: getDefaultImageSizes(),
848
853
  alt: fieldValue.alt
849
854
  };
850
855
  if (!isContentEditable) return /* @__PURE__ */ jsx(Fragment, { children: children(imageProps, fieldValue) });
@@ -891,9 +896,9 @@ function createBlock(options) {
891
896
  };
892
897
  const File = (t0) => {
893
898
  const $ = c(9);
894
- if ($[0] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
899
+ if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
895
900
  for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
896
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
901
+ $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
897
902
  }
898
903
  const { name, children } = t0;
899
904
  const blockContext = React.use(Context);
@@ -926,16 +931,16 @@ function createBlock(options) {
926
931
  } else t2 = $[8];
927
932
  return t2;
928
933
  };
929
- const MULTIPLE_ASSETS_SELF_KEY = "__camox_asset_self__";
930
- const _MultipleAssets = ({ name, children }) => {
934
+ const ASSET_LIST_SELF_KEY = "__camox_asset_self__";
935
+ const _AssetList = ({ name, children }) => {
931
936
  const blockContext = React.use(Context);
932
- if (!blockContext) throw new Error("MultipleAssets must be used within a Block Component");
937
+ if (!blockContext) throw new Error("ImageList/FileList must be used within a Block Component");
933
938
  const parentRepeaterContext = React.use(RepeaterItemContext);
934
939
  const { filesMap } = useNormalizedData();
935
940
  const fieldName = String(name);
936
941
  const fieldSchema = parentRepeaterContext ? typeboxSchema.properties[parentRepeaterContext.arrayFieldName]?.items?.properties?.[fieldName] : typeboxSchema.properties[fieldName];
937
- const ait = fieldSchema?.arrayItemType;
938
- if (ait !== "Image" && ait !== "File") throw new Error(`MultipleAssets: "${fieldName}" is not Type.Image/File({ multiple: true })`);
942
+ const ft = fieldSchema?.fieldType;
943
+ if (ft !== "ImageList" && ft !== "FileList") throw new Error(`"${fieldName}" is not a Type.ImageList or Type.FileList field`);
939
944
  const source = parentRepeaterContext ? parentRepeaterContext.itemContent[fieldName] : blockContext.content[fieldName];
940
945
  let arr = Array.isArray(source) ? source : [];
941
946
  if (arr.length === 0) {
@@ -944,7 +949,7 @@ function createBlock(options) {
944
949
  if (defaultCount > 0 && itemDefault) arr = Array.from({ length: defaultCount }, () => itemDefault);
945
950
  }
946
951
  const resolved = arr.map((v) => isFileMarker(v) ? resolveFileMarker(v, filesMap) : v).filter((v) => v != null && typeof v === "object" && "url" in v);
947
- const Single = ait === "Image" ? Image : File;
952
+ const Single = ft === "ImageList" ? Image : File;
948
953
  return /* @__PURE__ */ jsx(RepeaterHoverProvider, {
949
954
  blockId: blockContext.blockId,
950
955
  fieldName,
@@ -952,24 +957,25 @@ function createBlock(options) {
952
957
  value: {
953
958
  arrayFieldName: fieldName,
954
959
  itemIndex: index,
955
- itemContent: { [MULTIPLE_ASSETS_SELF_KEY]: value },
960
+ itemContent: { [ASSET_LIST_SELF_KEY]: value },
956
961
  itemSettings: {},
957
962
  itemId: void 0,
958
963
  containerItemId: parentRepeaterContext?.itemId ?? parentRepeaterContext?.containerItemId
959
964
  },
960
965
  children: /* @__PURE__ */ jsx(Single, {
961
- name: MULTIPLE_ASSETS_SELF_KEY,
966
+ name: ASSET_LIST_SELF_KEY,
962
967
  children
963
968
  })
964
969
  }, index))
965
970
  });
966
971
  };
967
- const MultipleAssets = _MultipleAssets;
972
+ const ImageList = _AssetList;
973
+ const FileList = _AssetList;
968
974
  const RepeaterItemWrapper = (t0) => {
969
975
  const $ = c(9);
970
- if ($[0] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
976
+ if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
971
977
  for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
972
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
978
+ $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
973
979
  }
974
980
  const { itemId, blockId, mode, children } = t0;
975
981
  const isContentEditable = useIsEditable(mode);
@@ -1010,9 +1016,9 @@ function createBlock(options) {
1010
1016
  };
1011
1017
  const RepeaterHoverProvider = (t0) => {
1012
1018
  const $ = c(7);
1013
- if ($[0] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
1019
+ if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
1014
1020
  for (let $i = 0; $i < 7; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1015
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
1021
+ $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
1016
1022
  }
1017
1023
  const { blockId, fieldName, children } = t0;
1018
1024
  const isContentEditable = useIsEditable("site");
@@ -1043,9 +1049,9 @@ function createBlock(options) {
1043
1049
  };
1044
1050
  const Repeater = (t0) => {
1045
1051
  const $ = c(27);
1046
- if ($[0] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
1052
+ if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
1047
1053
  for (let $i = 0; $i < 27; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1048
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
1054
+ $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
1049
1055
  }
1050
1056
  const { name, children } = t0;
1051
1057
  const blockContext = React.use(Context);
@@ -1057,7 +1063,8 @@ function createBlock(options) {
1057
1063
  const ItemLink = Link;
1058
1064
  const ItemEmbed = Embed;
1059
1065
  const ItemImage = Image;
1060
- const ItemMultipleAssets = _MultipleAssets;
1066
+ const ItemImageList = _AssetList;
1067
+ const ItemFileList = _AssetList;
1061
1068
  const ItemFile = File;
1062
1069
  const ItemRepeater = Repeater;
1063
1070
  const source = parentRepeaterContext ? parentRepeaterContext.itemContent[name] : content[name];
@@ -1103,7 +1110,8 @@ function createBlock(options) {
1103
1110
  Embed: ItemEmbed,
1104
1111
  Image: ItemImage,
1105
1112
  File: ItemFile,
1106
- MultipleAssets: ItemMultipleAssets,
1113
+ ImageList: ItemImageList,
1114
+ FileList: ItemFileList,
1107
1115
  Repeater: ItemRepeater,
1108
1116
  useSetting: useItemSetting
1109
1117
  };
@@ -1168,9 +1176,9 @@ function createBlock(options) {
1168
1176
  };
1169
1177
  const BlockComponent = (t0) => {
1170
1178
  const $ = c(70);
1171
- if ($[0] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
1179
+ if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
1172
1180
  for (let $i = 0; $i < 70; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1173
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
1181
+ $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
1174
1182
  }
1175
1183
  const { blockData, mode, isFirstBlock, showAddBlockTop, showAddBlockBottom, addBlockAfterPosition } = t0;
1176
1184
  const isContentEditable = useIsEditable(mode);
@@ -1470,9 +1478,9 @@ function createBlock(options) {
1470
1478
  */
1471
1479
  const Detached = (t0) => {
1472
1480
  const $ = c(31);
1473
- if ($[0] !== "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed") {
1481
+ if ($[0] !== "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c") {
1474
1482
  for (let $i = 0; $i < 31; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
1475
- $[0] = "821b6b2593d5fb6bcf8554c667ea39ef68a4e6ee5db99025f6c1f92b6a7379ed";
1483
+ $[0] = "98134dd394fa3308ac68d447ce9bf4f882877a513e6fcb8411414b3cca25135c";
1476
1484
  }
1477
1485
  const { children } = t0;
1478
1486
  const ctx = React.use(Context);
@@ -1600,7 +1608,8 @@ function createBlock(options) {
1600
1608
  Link,
1601
1609
  Image,
1602
1610
  File,
1603
- MultipleAssets,
1611
+ ImageList,
1612
+ FileList,
1604
1613
  Repeater,
1605
1614
  useSetting,
1606
1615
  _internal: {
@@ -1624,8 +1633,7 @@ function createBlock(options) {
1624
1633
  const storageContent = {};
1625
1634
  for (const [key, prop] of Object.entries(typeboxSchema.properties)) {
1626
1635
  const ft = prop.fieldType;
1627
- const ait = prop.arrayItemType;
1628
- if (ft === "Image" || ft === "File" || ft === "RepeatableItem" || ait === "Image" || ait === "File") continue;
1636
+ if (ft === "Image" || ft === "File" || ft === "ImageList" || ft === "FileList" || ft === "Repeater") continue;
1629
1637
  if ("default" in prop) storageContent[key] = prop.default;
1630
1638
  }
1631
1639
  return {
@@ -72,26 +72,6 @@ type FileValue = {
72
72
  } & {
73
73
  readonly __brand: "FileValue";
74
74
  };
75
- declare function _imageType(options: {
76
- title?: string;
77
- multiple?: false;
78
- }): TUnsafe<ImageValue>;
79
- declare function _imageType(options: {
80
- title?: string;
81
- multiple: true;
82
- defaultItems: number;
83
- }): TArray<TUnsafe<ImageValue>>;
84
- declare function _fileType(options: {
85
- accept: string[];
86
- title?: string;
87
- multiple?: false;
88
- }): TUnsafe<FileValue>;
89
- declare function _fileType(options: {
90
- accept: string[];
91
- title?: string;
92
- multiple: true;
93
- defaultItems: number;
94
- }): TArray<TUnsafe<FileValue>>;
95
75
  /**
96
76
  * Type builders for createBlock content schemas.
97
77
  * All fields must have default values.
@@ -120,7 +100,7 @@ declare const Type$1: {
120
100
  * similar to block-level settings.
121
101
  *
122
102
  * @example
123
- * Type.RepeatableItem({
103
+ * Type.Repeater({
124
104
  * content: {
125
105
  * title: Type.String({ default: 'Item' }),
126
106
  * description: Type.String({ default: 'Description' }),
@@ -134,7 +114,7 @@ declare const Type$1: {
134
114
  * toMarkdown: (c) => [`### ${c.title}`, c.description],
135
115
  * })
136
116
  */
137
- RepeatableItem: <T extends Record<string, TSchema>, S extends Record<string, TSchema> = Record<string, never>>(options: {
117
+ Repeater: <T extends Record<string, TSchema>, S extends Record<string, TSchema> = Record<string, never>>(options: {
138
118
  content: T;
139
119
  settings?: S;
140
120
  minItems: number;
@@ -197,8 +177,46 @@ declare const Type$1: {
197
177
  };
198
178
  title?: string;
199
179
  }) => TUnsafe<LinkValue>;
200
- Image: typeof _imageType;
201
- File: typeof _fileType;
180
+ /**
181
+ * Creates an image asset field.
182
+ *
183
+ * @example
184
+ * Type.Image({ title: 'Hero' })
185
+ */
186
+ Image: (options?: {
187
+ title?: string;
188
+ }) => TUnsafe<ImageValue>;
189
+ /**
190
+ * Creates a file asset field.
191
+ *
192
+ * @example
193
+ * Type.File({ accept: ['application/pdf'], title: 'Datasheet' })
194
+ */
195
+ File: (options: {
196
+ accept: string[];
197
+ title?: string;
198
+ }) => TUnsafe<FileValue>;
199
+ /**
200
+ * Creates an array of image assets.
201
+ *
202
+ * @example
203
+ * Type.ImageList({ title: 'Gallery', defaultItems: 3 })
204
+ */
205
+ ImageList: (options?: {
206
+ title?: string;
207
+ defaultItems?: number;
208
+ }) => TArray<TUnsafe<ImageValue>>;
209
+ /**
210
+ * Creates an array of file assets.
211
+ *
212
+ * @example
213
+ * Type.FileList({ accept: ['application/pdf'], title: 'Attachments' })
214
+ */
215
+ FileList: (options: {
216
+ accept: string[];
217
+ title?: string;
218
+ defaultItems?: number;
219
+ }) => TArray<TUnsafe<FileValue>>;
202
220
  };
203
221
  //#endregion
204
222
  export { EmbedURL, FileValue, ImageValue, ItemSettingsBrand, LinkValue, ToMarkdownBuilder, Type$1 as Type };
@@ -59,14 +59,8 @@ function resolveToMarkdown(builder, settingsShape, scope) {
59
59
  else out.push(entry instanceof FieldToken ? entry.toString() : String(entry));
60
60
  return out;
61
61
  }
62
- function _imageType(options) {
63
- const imageDefault = {
64
- url: `https://placehold.co/1200x800/f4f4f5/a1a1aa.png?text=${options?.title || "image"}`,
65
- alt: "",
66
- filename: "placeholder.png",
67
- mimeType: "image/png"
68
- };
69
- const singleSchema = Type.Unsafe({
62
+ function _imageSingle(options) {
63
+ return Type.Unsafe({
70
64
  type: "object",
71
65
  properties: {
72
66
  url: { type: "string" },
@@ -75,24 +69,28 @@ function _imageType(options) {
75
69
  mimeType: { type: "string" }
76
70
  },
77
71
  accept: ["image/*"],
78
- default: imageDefault,
72
+ default: {
73
+ url: `https://placehold.co/1200x800/f4f4f5/a1a1aa.png?text=${options?.title || "image"}`,
74
+ alt: "",
75
+ filename: "placeholder.png",
76
+ mimeType: "image/png"
77
+ },
79
78
  title: options.title,
80
79
  fieldType: "Image"
81
80
  });
82
- if (!options.multiple) return singleSchema;
83
- const defaultItems = options.defaultItems ?? 0;
84
- return Type.Array(singleSchema, {
81
+ }
82
+ function _imageList(options) {
83
+ return Type.Array(_imageSingle({ title: options.title }), {
85
84
  minItems: 0,
86
85
  maxItems: 100,
87
86
  default: [],
88
- defaultItems,
87
+ defaultItems: options.defaultItems ?? 0,
89
88
  title: options.title,
90
- fieldType: "MultipleAssets",
91
- arrayItemType: "Image"
89
+ fieldType: "ImageList"
92
90
  });
93
91
  }
94
- function _fileType(options) {
95
- const singleSchema = Type.Unsafe({
92
+ function _fileSingle(options) {
93
+ return Type.Unsafe({
96
94
  type: "object",
97
95
  properties: {
98
96
  url: { type: "string" },
@@ -110,16 +108,18 @@ function _fileType(options) {
110
108
  title: options.title,
111
109
  fieldType: "File"
112
110
  });
113
- if (!options.multiple) return singleSchema;
114
- const defaultItems = options.defaultItems ?? 0;
115
- return Type.Array(singleSchema, {
111
+ }
112
+ function _fileList(options) {
113
+ return Type.Array(_fileSingle({
114
+ accept: options.accept,
115
+ title: options.title
116
+ }), {
116
117
  minItems: 0,
117
118
  maxItems: 100,
118
119
  default: [],
119
- defaultItems,
120
+ defaultItems: options.defaultItems ?? 0,
120
121
  title: options.title,
121
- fieldType: "MultipleAssets",
122
- arrayItemType: "File"
122
+ fieldType: "FileList"
123
123
  });
124
124
  }
125
125
  /**
@@ -151,7 +151,7 @@ const Type$1 = {
151
151
  * similar to block-level settings.
152
152
  *
153
153
  * @example
154
- * Type.RepeatableItem({
154
+ * Type.Repeater({
155
155
  * content: {
156
156
  * title: Type.String({ default: 'Item' }),
157
157
  * description: Type.String({ default: 'Description' }),
@@ -165,8 +165,8 @@ const Type$1 = {
165
165
  * toMarkdown: (c) => [`### ${c.title}`, c.description],
166
166
  * })
167
167
  */
168
- RepeatableItem: (options) => {
169
- if (options.minItems < 1) throw new Error("RepeatableItem requires minItems to be at least 1");
168
+ Repeater: (options) => {
169
+ if (options.minItems < 1) throw new Error("Repeater requires minItems to be at least 1");
170
170
  const objectSchema = Type.Object(options.content);
171
171
  const defaultItem = {};
172
172
  for (const [key, prop] of Object.entries(objectSchema.properties)) if ("default" in prop) defaultItem[key] = prop.default;
@@ -186,7 +186,7 @@ const Type$1 = {
186
186
  maxItems: options.maxItems,
187
187
  default: defaultArray,
188
188
  title: options.title,
189
- fieldType: "RepeatableItem",
189
+ fieldType: "Repeater",
190
190
  toMarkdown: resolveToMarkdown(options.toMarkdown, options.settings, "item"),
191
191
  itemSettingsSchema,
192
192
  defaultItemSettings: settingsTypeboxSchema ? defaultItemSettings : void 0
@@ -274,8 +274,34 @@ const Type$1 = {
274
274
  fieldType: "Link"
275
275
  });
276
276
  },
277
- Image: _imageType,
278
- File: _fileType
277
+ /**
278
+ * Creates an image asset field.
279
+ *
280
+ * @example
281
+ * Type.Image({ title: 'Hero' })
282
+ */
283
+ Image: (options = {}) => _imageSingle(options),
284
+ /**
285
+ * Creates a file asset field.
286
+ *
287
+ * @example
288
+ * Type.File({ accept: ['application/pdf'], title: 'Datasheet' })
289
+ */
290
+ File: (options) => _fileSingle(options),
291
+ /**
292
+ * Creates an array of image assets.
293
+ *
294
+ * @example
295
+ * Type.ImageList({ title: 'Gallery', defaultItems: 3 })
296
+ */
297
+ ImageList: (options = {}) => _imageList(options),
298
+ /**
299
+ * Creates an array of file assets.
300
+ *
301
+ * @example
302
+ * Type.FileList({ accept: ['application/pdf'], title: 'Attachments' })
303
+ */
304
+ FileList: (options) => _fileList(options)
279
305
  };
280
306
 
281
307
  //#endregion
@@ -27,16 +27,12 @@ const fieldTypesDictionary = {
27
27
  });
28
28
  }
29
29
  },
30
- RepeatableItem: {
31
- label: "Repeatable item",
30
+ Repeater: {
31
+ label: "Repeater",
32
32
  isScalar: false,
33
33
  isContentEditable: false,
34
34
  hasOwnView: false,
35
- getIcon: ({ arrayItemType }) => {
36
- if (arrayItemType === "Image") return (props) => /* @__PURE__ */ jsx(Images, { ...props });
37
- if (arrayItemType === "File") return (props) => /* @__PURE__ */ jsx(FileIcon, { ...props });
38
- return (props) => /* @__PURE__ */ jsx(List, { ...props });
39
- },
35
+ getIcon: () => (props) => /* @__PURE__ */ jsx(List, { ...props }),
40
36
  getLabel: (_value, { schemaTitle, fieldName }) => schemaTitle ?? fieldName,
41
37
  onTreeDoubleClick: ({ blockId }) => {
42
38
  previewStore.send({
@@ -183,23 +179,39 @@ const fieldTypesDictionary = {
183
179
  });
184
180
  }
185
181
  },
186
- MultipleAssets: {
187
- label: "Multiple assets",
182
+ ImageList: {
183
+ label: "Image list",
188
184
  isScalar: false,
189
185
  isContentEditable: false,
190
186
  hasOwnView: true,
191
- getIcon: ({ arrayItemType }) => {
192
- if (arrayItemType === "File") return (props) => /* @__PURE__ */ jsx(FileIcon, { ...props });
193
- return (props) => /* @__PURE__ */ jsx(Images, { ...props });
194
- },
187
+ getIcon: () => (props) => /* @__PURE__ */ jsx(Images, { ...props }),
188
+ getLabel: (_value, { schemaTitle, fieldName }) => schemaTitle ?? fieldName,
189
+ onTreeDoubleClick: ({ blockId, fieldName }) => {
190
+ previewStore.send({
191
+ type: "selectBlockField",
192
+ blockId,
193
+ fieldName,
194
+ fieldType: "Image"
195
+ });
196
+ previewStore.send({
197
+ type: "openBlockContentSheet",
198
+ blockId
199
+ });
200
+ }
201
+ },
202
+ FileList: {
203
+ label: "File list",
204
+ isScalar: false,
205
+ isContentEditable: false,
206
+ hasOwnView: true,
207
+ getIcon: () => (props) => /* @__PURE__ */ jsx(FileIcon, { ...props }),
195
208
  getLabel: (_value, { schemaTitle, fieldName }) => schemaTitle ?? fieldName,
196
- onTreeDoubleClick: ({ blockId, fieldName }, { arrayItemType }) => {
197
- const fieldType = arrayItemType === "File" ? "File" : "Image";
209
+ onTreeDoubleClick: ({ blockId, fieldName }) => {
198
210
  previewStore.send({
199
211
  type: "selectBlockField",
200
212
  blockId,
201
213
  fieldName,
202
- fieldType
214
+ fieldType: "File"
203
215
  });
204
216
  previewStore.send({
205
217
  type: "openBlockContentSheet",
@@ -0,0 +1,73 @@
1
+ //#region src/core/lib/imageTransform.ts
2
+ const DEFAULT_QUALITY = 85;
3
+ const DEFAULT_SRCSET_WIDTHS = [
4
+ 320,
5
+ 640,
6
+ 960,
7
+ 1280,
8
+ 1920
9
+ ];
10
+ const DEFAULT_SRC_WIDTH = 1280;
11
+ const DEFAULT_SIZES = "100vw";
12
+ function isNonProxiedHostname(hostname) {
13
+ if (hostname === "localhost") return true;
14
+ if (hostname === "127.0.0.1") return true;
15
+ if (hostname === "0.0.0.0") return true;
16
+ if (hostname === "::1") return true;
17
+ if (hostname.endsWith(".local")) return true;
18
+ if (hostname.endsWith(".localhost")) return true;
19
+ return false;
20
+ }
21
+ function parseUrl(url) {
22
+ try {
23
+ return new URL(url);
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ function isTransformableUrl(parsed) {
29
+ if (isNonProxiedHostname(parsed.hostname)) return false;
30
+ if (parsed.pathname.startsWith("/cdn-cgi/image/")) return false;
31
+ if (!parsed.pathname.startsWith("/files/serve/")) return false;
32
+ return true;
33
+ }
34
+ function isNonTransformableMimeType(mimeType) {
35
+ if (!mimeType) return false;
36
+ return mimeType === "image/svg+xml";
37
+ }
38
+ function isRasterImage(mimeType) {
39
+ if (!mimeType) return false;
40
+ if (!mimeType.startsWith("image/")) return false;
41
+ return mimeType !== "image/svg+xml";
42
+ }
43
+ function transformImageUrl(url, options = {}) {
44
+ if (!url) return url;
45
+ if (isNonTransformableMimeType(options.mimeType)) return url;
46
+ const parsed = parseUrl(url);
47
+ if (!parsed) return url;
48
+ if (!isTransformableUrl(parsed)) return url;
49
+ const parts = [
50
+ `format=auto`,
51
+ `quality=${options.quality ?? DEFAULT_QUALITY}`,
52
+ `fit=scale-down`
53
+ ];
54
+ if (options.width != null) parts.push(`width=${options.width}`);
55
+ return `${parsed.origin}/cdn-cgi/image/${parts.join(",")}${parsed.pathname}${parsed.search}`;
56
+ }
57
+ function buildImageSrcSet(url, mimeType) {
58
+ if (!url) return void 0;
59
+ if (isNonTransformableMimeType(mimeType)) return void 0;
60
+ const parsed = parseUrl(url);
61
+ if (!parsed) return void 0;
62
+ if (!isTransformableUrl(parsed)) return void 0;
63
+ return DEFAULT_SRCSET_WIDTHS.map((w) => `${transformImageUrl(url, { width: w })} ${w}w`).join(", ");
64
+ }
65
+ function getDefaultImageWidth() {
66
+ return DEFAULT_SRC_WIDTH;
67
+ }
68
+ function getDefaultImageSizes() {
69
+ return DEFAULT_SIZES;
70
+ }
71
+
72
+ //#endregion
73
+ export { buildImageSrcSet, getDefaultImageSizes, getDefaultImageWidth, isRasterImage, transformImageUrl };
@@ -20,9 +20,9 @@ import { FloatingToolbar } from "@camox/ui/floating-toolbar";
20
20
  //#region src/features/content/CamoxContent.tsx
21
21
  const CamoxContent = () => {
22
22
  const $ = c(53);
23
- if ($[0] !== "41ba2e77784594f1ce07296c7a619951f1b5765164f78c588070f4799d74d906") {
23
+ if ($[0] !== "8cb16b8f9bc49d4116b9897e9fc510ae1a05eaf98230f67c2f9664ca4f86e3bd") {
24
24
  for (let $i = 0; $i < 53; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
25
- $[0] = "41ba2e77784594f1ce07296c7a619951f1b5765164f78c588070f4799d74d906";
25
+ $[0] = "8cb16b8f9bc49d4116b9897e9fc510ae1a05eaf98230f67c2f9664ca4f86e3bd";
26
26
  }
27
27
  const projectSlug = useProjectSlug();
28
28
  let t0;
@@ -268,7 +268,7 @@ const CamoxContent = () => {
268
268
  let t22;
269
269
  if ($[48] !== t18 || $[49] !== t19 || $[50] !== t20 || $[51] !== t21) {
270
270
  t22 = /* @__PURE__ */ jsxs("div", {
271
- className: "flex flex-1 flex-row",
271
+ className: "flex min-h-0 flex-1 flex-row",
272
272
  children: [
273
273
  t11,
274
274
  t18,