@orion-studios/payload-studio 0.5.0-beta.83 → 0.5.0-beta.85

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.
@@ -992,11 +992,42 @@ var normalizeNumber = (value, fallback) => {
992
992
  }
993
993
  return fallback;
994
994
  };
995
+ var getRelationID = (value) => {
996
+ if (typeof value === "number" || typeof value === "string") {
997
+ return value;
998
+ }
999
+ if (!isRecord3(value)) {
1000
+ return null;
1001
+ }
1002
+ const id = value.id;
1003
+ return typeof id === "number" || typeof id === "string" ? id : null;
1004
+ };
1005
+ var toMediaLibraryItem = (value) => {
1006
+ if (!isRecord3(value)) {
1007
+ return null;
1008
+ }
1009
+ const id = getRelationID(value);
1010
+ if (id === null) {
1011
+ return null;
1012
+ }
1013
+ const filename = typeof value.filename === "string" ? value.filename : "";
1014
+ const url = typeof value.url === "string" && value.url.length > 0 ? value.url : filename ? `/api/media/file/${encodeURIComponent(filename)}` : "";
1015
+ return {
1016
+ alt: typeof value.alt === "string" ? value.alt : "",
1017
+ filename,
1018
+ id,
1019
+ url
1020
+ };
1021
+ };
1022
+ var mediaLabel = (item) => item.filename || item.alt || `Media #${item.id}`;
995
1023
  var groupLabel = (key) => commonInspectorGroups.find((group) => group.key === key)?.label || key;
996
1024
  function BlockInspectorRenderer({
997
1025
  block,
998
1026
  blockType,
1027
+ mediaImageControls,
1028
+ mediaSources,
999
1029
  mode,
1030
+ onMediaImageChange,
1000
1031
  onModeChange,
1001
1032
  onSearchQueryChange,
1002
1033
  onUpdateField,
@@ -1021,12 +1052,24 @@ function BlockInspectorRenderer({
1021
1052
  });
1022
1053
  }, [block, fields, mode, searchQuery]);
1023
1054
  const groups = (0, import_react.useMemo)(() => {
1024
- return commonInspectorGroups.map((group) => ({
1055
+ const baseGroups = commonInspectorGroups.map((group) => ({
1025
1056
  key: group.key,
1026
1057
  label: group.label,
1027
1058
  fields: resolvedFields.filter((field) => field.group === group.key)
1028
1059
  })).filter((group) => group.fields.length > 0);
1029
- }, [resolvedFields]);
1060
+ const hasExplicitMediaControls = Boolean(mediaImageControls) || Array.isArray(mediaSources) && mediaSources.length > 0;
1061
+ if (!hasExplicitMediaControls || baseGroups.some((group) => group.key === "media")) {
1062
+ return baseGroups;
1063
+ }
1064
+ return [
1065
+ ...baseGroups,
1066
+ {
1067
+ key: "media",
1068
+ label: groupLabel("media"),
1069
+ fields: []
1070
+ }
1071
+ ];
1072
+ }, [mediaImageControls, mediaSources, resolvedFields]);
1030
1073
  if (!definition) {
1031
1074
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "orion-builder-settings-empty", children: "No V2 schema has been registered for this section yet." });
1032
1075
  }
@@ -1084,6 +1127,18 @@ function BlockInspectorRenderer({
1084
1127
  const mediaPositionX = normalizeNumber(getByPath(block, "settings.media.positionX"), 50);
1085
1128
  const mediaPositionY = normalizeNumber(getByPath(block, "settings.media.positionY"), 50);
1086
1129
  const mediaHeight = getByPath(block, "settings.media.height");
1130
+ const fallbackMediaPosition = mediaPosition === "top" || mediaPosition === "bottom" || mediaPosition === "left" || mediaPosition === "right" ? mediaPosition : "center";
1131
+ const fallbackMediaControls = {
1132
+ cornerStyle: mediaCornerStyle === "square" ? "square" : "rounded",
1133
+ fit: mediaFit === "contain" ? "contain" : "cover",
1134
+ height: typeof mediaHeight === "number" ? mediaHeight : null,
1135
+ position: fallbackMediaPosition,
1136
+ positionX: mediaPositionX,
1137
+ positionY: mediaPositionY
1138
+ };
1139
+ const effectiveMedia = mediaImageControls ?? fallbackMediaControls;
1140
+ const effectiveMediaSources = Array.isArray(mediaSources) ? mediaSources : [];
1141
+ const hasMediaGroupContent = group.key === "media" && (effectiveMediaSources.length > 0 || Boolean(effectiveMedia));
1087
1142
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1088
1143
  Accordion,
1089
1144
  {
@@ -1095,21 +1150,90 @@ function BlockInspectorRenderer({
1095
1150
  subtitle: `${group.fields.length} setting${group.fields.length === 1 ? "" : "s"}`,
1096
1151
  title: group.label,
1097
1152
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "orion-builder-settings-field-list", children: [
1098
- group.key === "media" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1153
+ group.key === "media" ? effectiveMediaSources.map((source) => {
1154
+ const selectedSourceMedia = toMediaLibraryItem(source.value);
1155
+ const selectedSourceMediaID = getRelationID(source.value);
1156
+ const sourceOptions = selectedSourceMedia && !source.library.some((item) => String(item.id) === String(selectedSourceMedia.id)) ? [selectedSourceMedia, ...source.library] : source.library;
1157
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1158
+ "div",
1159
+ {
1160
+ className: "orion-builder-settings-item-card",
1161
+ style: { padding: "0.56rem" },
1162
+ children: [
1163
+ source.loading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "orion-builder-settings-note", children: "Loading media library..." }) : null,
1164
+ source.error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "orion-builder-settings-error", children: source.error }) : null,
1165
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "orion-builder-settings-label", children: [
1166
+ source.label,
1167
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1168
+ "select",
1169
+ {
1170
+ className: "orion-builder-settings-input",
1171
+ onChange: (event) => source.onSelect(event.target.value),
1172
+ value: selectedSourceMediaID !== null ? String(selectedSourceMediaID) : "",
1173
+ children: [
1174
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "No image" }),
1175
+ sourceOptions.map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: String(item.id), children: mediaLabel(item) }, String(item.id)))
1176
+ ]
1177
+ }
1178
+ )
1179
+ ] }),
1180
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1181
+ "button",
1182
+ {
1183
+ className: "orion-builder-settings-inline-btn",
1184
+ disabled: selectedSourceMediaID === null,
1185
+ onClick: source.onRemove,
1186
+ type: "button",
1187
+ children: "Remove Image"
1188
+ }
1189
+ ),
1190
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "orion-builder-settings-label", children: [
1191
+ source.uploadLabel,
1192
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1193
+ "input",
1194
+ {
1195
+ accept: "image/*",
1196
+ className: "orion-builder-settings-input",
1197
+ disabled: source.uploadDisabled,
1198
+ onChange: (event) => {
1199
+ const file = event.currentTarget.files?.[0];
1200
+ if (file) {
1201
+ source.onUpload(file);
1202
+ }
1203
+ event.currentTarget.value = "";
1204
+ },
1205
+ type: "file"
1206
+ }
1207
+ )
1208
+ ] }),
1209
+ source.uploading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "orion-builder-settings-note", children: "Uploading image..." }) : null
1210
+ ]
1211
+ },
1212
+ `media-source-${source.label}`
1213
+ );
1214
+ }) : null,
1215
+ group.key === "media" && effectiveMedia ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1099
1216
  ImageControls,
1100
1217
  {
1101
- cornerStyle: mediaCornerStyle === "square" ? "square" : "rounded",
1102
- fit: mediaFit === "contain" ? "contain" : "cover",
1103
- height: typeof mediaHeight === "number" ? mediaHeight : null,
1218
+ cornerStyle: effectiveMedia.cornerStyle,
1219
+ fit: effectiveMedia.fit,
1220
+ height: effectiveMedia.height,
1221
+ heightLabel: effectiveMedia.heightLabel,
1222
+ maxHeight: effectiveMedia.maxHeight,
1223
+ minHeight: effectiveMedia.minHeight,
1104
1224
  onChange: (field, value) => {
1225
+ if (onMediaImageChange) {
1226
+ onMediaImageChange(field, value);
1227
+ return;
1228
+ }
1105
1229
  updateForKey(`settings.media.${field}`, value);
1106
1230
  },
1107
- position: mediaPosition === "top" || mediaPosition === "bottom" || mediaPosition === "left" || mediaPosition === "right" ? mediaPosition : "center",
1108
- positionX: mediaPositionX,
1109
- positionY: mediaPositionY
1231
+ position: effectiveMedia.position,
1232
+ positionX: effectiveMedia.positionX,
1233
+ positionY: effectiveMedia.positionY
1110
1234
  }
1111
1235
  ) : null,
1112
- group.fields.filter((field) => !(group.key === "media" && field.key.startsWith("settings.media."))).map((field) => {
1236
+ (hasMediaGroupContent ? group.fields.filter((field) => !(group.key === "media" && field.key.startsWith("settings.media."))) : group.fields).map((field) => {
1113
1237
  const fieldValue = getByPath(block, field.key);
1114
1238
  if (field.type === "checkbox") {
1115
1239
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "orion-builder-settings-label is-checkbox", children: [
@@ -2123,6 +2247,18 @@ function BlockFrame({
2123
2247
 
2124
2248
  // src/studio-pages/builder/renderers/renderSimpleBlockPreview.tsx
2125
2249
  var import_jsx_runtime8 = require("react/jsx-runtime");
2250
+ var parseOptionalPercentNumber = (value) => {
2251
+ if (typeof value === "number" && Number.isFinite(value)) {
2252
+ return Math.max(0, Math.min(100, value));
2253
+ }
2254
+ if (typeof value === "string" && value.trim().length > 0) {
2255
+ const parsed = Number(value);
2256
+ if (Number.isFinite(parsed)) {
2257
+ return Math.max(0, Math.min(100, parsed));
2258
+ }
2259
+ }
2260
+ return void 0;
2261
+ };
2126
2262
  function renderSimpleBlockPreview(args) {
2127
2263
  const {
2128
2264
  block,
@@ -2306,10 +2442,14 @@ function renderSimpleBlockPreview(args) {
2306
2442
  if (type === "media") {
2307
2443
  const image = resolveMedia2(block.image);
2308
2444
  const size = normalizeText3(block.size, "default");
2445
+ const imagePositionX = parseOptionalPercentNumber(block?.imagePositionX);
2446
+ const imagePositionY = parseOptionalPercentNumber(block?.imagePositionY);
2309
2447
  const imageStyle = getImagePresentationStyle2({
2310
2448
  cornerStyle: normalizeImageCornerStyle3(block?.imageCornerStyle),
2311
2449
  fit: normalizeImageFit3(block?.imageFit),
2312
- position: normalizeImagePosition3(block?.imagePosition)
2450
+ position: normalizeImagePosition3(block?.imagePosition),
2451
+ positionX: imagePositionX,
2452
+ positionY: imagePositionY
2313
2453
  });
2314
2454
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2315
2455
  BlockFrame,
@@ -2824,7 +2964,7 @@ var lucideIconOptions = [
2824
2964
  ];
2825
2965
  var isRecord4 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
2826
2966
  var normalizeText = (value, fallback = "") => typeof value === "string" ? value : fallback;
2827
- var getRelationID = (value) => {
2967
+ var getRelationID2 = (value) => {
2828
2968
  if (typeof value === "number" || typeof value === "string") {
2829
2969
  return value;
2830
2970
  }
@@ -2834,11 +2974,11 @@ var getRelationID = (value) => {
2834
2974
  const id = value.id;
2835
2975
  return typeof id === "number" || typeof id === "string" ? id : null;
2836
2976
  };
2837
- var toMediaLibraryItem = (value) => {
2977
+ var toMediaLibraryItem2 = (value) => {
2838
2978
  if (!isRecord4(value)) {
2839
2979
  return null;
2840
2980
  }
2841
- const id = getRelationID(value);
2981
+ const id = getRelationID2(value);
2842
2982
  if (id === null) {
2843
2983
  return null;
2844
2984
  }
@@ -2851,7 +2991,7 @@ var toMediaLibraryItem = (value) => {
2851
2991
  url
2852
2992
  };
2853
2993
  };
2854
- var mediaLabel = (item) => item.filename || item.alt || `Media #${item.id}`;
2994
+ var mediaLabel2 = (item) => item.filename || item.alt || `Media #${item.id}`;
2855
2995
  var normalizeNumber2 = (value, fallback) => {
2856
2996
  if (typeof value === "number" && Number.isFinite(value)) {
2857
2997
  return value;
@@ -2930,11 +3070,11 @@ function ArrayItemsEditor({
2930
3070
  const config = blockConfig[blockType];
2931
3071
  const normalizedQuery = searchQuery.trim().toLowerCase();
2932
3072
  const resolveSelectedMedia = (value) => {
2933
- const direct = toMediaLibraryItem(value);
3073
+ const direct = toMediaLibraryItem2(value);
2934
3074
  if (direct) {
2935
3075
  return direct;
2936
3076
  }
2937
- const relationID = getRelationID(value);
3077
+ const relationID = getRelationID2(value);
2938
3078
  if (relationID === null) {
2939
3079
  return null;
2940
3080
  }
@@ -2942,12 +3082,12 @@ function ArrayItemsEditor({
2942
3082
  };
2943
3083
  const renderMediaPicker = (item, itemIndex, field, label, uploadLabel) => {
2944
3084
  const selectedMedia = resolveSelectedMedia(item[field]);
2945
- const selectedMediaID = getRelationID(item[field]);
2946
- if (normalizedQuery && !hasQueryMatch(normalizedQuery, label, uploadLabel, "image", "media", selectedMedia ? mediaLabel(selectedMedia) : "")) {
3085
+ const selectedMediaID = getRelationID2(item[field]);
3086
+ const mediaOptions = selectedMedia && !mediaLibrary.some((libraryItem) => String(libraryItem.id) === String(selectedMedia.id)) ? [selectedMedia, ...mediaLibrary] : mediaLibrary;
3087
+ if (normalizedQuery && !hasQueryMatch(normalizedQuery, label, uploadLabel, "image", "media", selectedMedia ? mediaLabel2(selectedMedia) : "")) {
2947
3088
  return null;
2948
3089
  }
2949
3090
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2950
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "orion-builder-settings-note", children: selectedMedia ? `${label}: ${mediaLabel(selectedMedia)}` : `No ${label.toLowerCase()} selected.` }),
2951
3091
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "orion-builder-settings-label", children: [
2952
3092
  label,
2953
3093
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
@@ -2958,7 +3098,7 @@ function ArrayItemsEditor({
2958
3098
  value: selectedMediaID !== null ? String(selectedMediaID) : "",
2959
3099
  children: [
2960
3100
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: "", children: "No image" }),
2961
- mediaLibrary.map((libraryItem) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: String(libraryItem.id), children: mediaLabel(libraryItem) }, String(libraryItem.id)))
3101
+ mediaOptions.map((libraryItem) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: String(libraryItem.id), children: mediaLabel2(libraryItem) }, String(libraryItem.id)))
2962
3102
  ]
2963
3103
  }
2964
3104
  )
@@ -3696,7 +3836,7 @@ function parseTestimonialRating(value, fallback = 5) {
3696
3836
  function parsePercentNumber(value, fallback) {
3697
3837
  return Math.max(0, Math.min(100, parsePixelNumber(value, fallback)));
3698
3838
  }
3699
- function parseOptionalPercentNumber(value) {
3839
+ function parseOptionalPercentNumber2(value) {
3700
3840
  if (typeof value === "number" && Number.isFinite(value)) {
3701
3841
  return Math.max(0, Math.min(100, value));
3702
3842
  }
@@ -3811,7 +3951,7 @@ var sectionStyleFromBlock = (block, pageDefaults) => {
3811
3951
  sectionStyle: sectionMode === "color" ? { background: sectionColor } : sectionMode === "gradient" ? { background: sectionGradient } : block.blockType === "hero" ? { background: "transparent" } : {}
3812
3952
  };
3813
3953
  };
3814
- function getRelationID2(value) {
3954
+ function getRelationID3(value) {
3815
3955
  if (typeof value === "number" || typeof value === "string") {
3816
3956
  return value;
3817
3957
  }
@@ -3844,7 +3984,7 @@ function extractUploadedMedia(value) {
3844
3984
  if (!candidate || typeof candidate !== "object") {
3845
3985
  return null;
3846
3986
  }
3847
- const id = getRelationID2(candidate);
3987
+ const id = getRelationID3(candidate);
3848
3988
  if (id === null) {
3849
3989
  return null;
3850
3990
  }
@@ -3855,11 +3995,11 @@ function extractUploadedMedia(value) {
3855
3995
  url: typeof candidate.url === "string" ? candidate.url : ""
3856
3996
  };
3857
3997
  }
3858
- function toMediaLibraryItem2(value) {
3998
+ function toMediaLibraryItem3(value) {
3859
3999
  if (!value || typeof value !== "object") {
3860
4000
  return null;
3861
4001
  }
3862
- const id = getRelationID2(value);
4002
+ const id = getRelationID3(value);
3863
4003
  if (id === null) {
3864
4004
  return null;
3865
4005
  }
@@ -3985,6 +4125,9 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
3985
4125
  const selectedBlockSettings = isRecord5(selectedBlock?.settings) ? selectedBlock.settings : {};
3986
4126
  const selectedBlockAdvancedSettings = isRecord5(selectedBlockSettings.advanced) ? selectedBlockSettings.advanced : {};
3987
4127
  const isArrayItemBlockSelected = selectedType === "featureGrid" || selectedType === "logoWall" || selectedType === "beforeAfter" || selectedType === "stats" || selectedType === "faq" || selectedType === "testimonials";
4128
+ const selectedBlockHasMediaSource = selectedType === "hero" || selectedType === "media";
4129
+ const selectedBlockMediaValue = selectedType === "hero" ? selectedBlock?.media : selectedType === "media" ? selectedBlock?.image : null;
4130
+ const selectedItemRecord = typeof selectedItemIndex === "number" && selectedItemIndex >= 0 && selectedItemIndex < selectedItems.length && isRecord5(selectedItems[selectedItemIndex]) ? selectedItems[selectedItemIndex] : null;
3988
4131
  const editCopyInPanelEnabled = Boolean(selectedBlockAdvancedSettings.editCopyInPanel);
3989
4132
  const isBlockUploadTarget = (blockIndex, kind) => selectedIndex === blockIndex && uploadingTarget?.kind === kind;
3990
4133
  const isFeatureGridItemUploading = (blockIndex, itemIndex) => selectedIndex === blockIndex && uploadingTarget?.kind === "featureGridItem" && uploadingTarget.itemIndex === itemIndex;
@@ -4005,6 +4148,7 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4005
4148
  }
4006
4149
  return false;
4007
4150
  };
4151
+ const isSelectedBlockMediaUploading = selectedIndex !== null && (selectedType === "hero" && uploadingTarget?.kind === "hero" || selectedType === "media" && uploadingTarget?.kind === "media");
4008
4152
  const filteredSectionPresets = (0, import_react5.useMemo)(() => {
4009
4153
  const query = presetQuery.trim().toLowerCase();
4010
4154
  if (!query) {
@@ -4027,7 +4171,7 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4027
4171
  }
4028
4172
  const json = await response.json();
4029
4173
  const docs = Array.isArray(json.docs) ? json.docs : [];
4030
- const items = docs.map((doc2) => toMediaLibraryItem2(doc2)).filter((item) => item !== null);
4174
+ const items = docs.map((doc2) => toMediaLibraryItem3(doc2)).filter((item) => item !== null);
4031
4175
  setMediaLibrary(items);
4032
4176
  } catch (error) {
4033
4177
  setMediaLibraryError(error instanceof Error ? error.message : "Could not load media library.");
@@ -4261,6 +4405,84 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4261
4405
  }
4262
4406
  updateArrayItemField(selectedIndex, "items", itemIndex, fieldName, mediaFromLibraryItem(selectedMedia));
4263
4407
  };
4408
+ const setSelectedBlockMediaValue = (value) => {
4409
+ if (selectedIndex === null) {
4410
+ return;
4411
+ }
4412
+ setLayout((current) => {
4413
+ const next = cloneBlockLayout(current);
4414
+ const block = next[selectedIndex];
4415
+ const blockType = normalizeText2(block.blockType);
4416
+ if (blockType === "hero") {
4417
+ next[selectedIndex] = migrateBlockToSettingsV2({
4418
+ ...block,
4419
+ backgroundImageURL: value ? getMediaURL(value) : "",
4420
+ media: value
4421
+ });
4422
+ return next;
4423
+ }
4424
+ if (blockType === "media") {
4425
+ next[selectedIndex] = migrateBlockToSettingsV2({
4426
+ ...block,
4427
+ image: value
4428
+ });
4429
+ }
4430
+ return next;
4431
+ });
4432
+ };
4433
+ const setSelectedBlockMediaFromLibrary = (mediaID) => {
4434
+ if (selectedIndex === null || !selectedBlockHasMediaSource) {
4435
+ return;
4436
+ }
4437
+ if (!mediaID) {
4438
+ setSelectedBlockMediaValue(null);
4439
+ return;
4440
+ }
4441
+ const selectedMedia = mediaLibrary.find((item) => String(item.id) === mediaID);
4442
+ if (!selectedMedia) {
4443
+ return;
4444
+ }
4445
+ setSelectedBlockMediaValue(mediaFromLibraryItem(selectedMedia));
4446
+ };
4447
+ const uploadSelectedBlockMediaFromV2 = (file) => {
4448
+ if (selectedType === "hero") {
4449
+ void uploadMediaForSelected({ kind: "hero" }, file);
4450
+ return;
4451
+ }
4452
+ if (selectedType === "media") {
4453
+ void uploadMediaForSelected({ kind: "media" }, file);
4454
+ }
4455
+ };
4456
+ const updateSelectedItemMediaPresentationFromInspector = (field, value) => {
4457
+ if (selectedIndex === null || typeof selectedItemIndex !== "number") {
4458
+ return;
4459
+ }
4460
+ if (field === "height") {
4461
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imageHeight", value);
4462
+ return;
4463
+ }
4464
+ if (field === "fit") {
4465
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imageFit", value);
4466
+ return;
4467
+ }
4468
+ if (field === "cornerStyle") {
4469
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imageCornerStyle", value);
4470
+ return;
4471
+ }
4472
+ if (field === "position") {
4473
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imagePosition", value);
4474
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imagePositionX", null);
4475
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imagePositionY", null);
4476
+ return;
4477
+ }
4478
+ if (field === "positionX") {
4479
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imagePositionX", value);
4480
+ return;
4481
+ }
4482
+ if (field === "positionY") {
4483
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imagePositionY", value);
4484
+ }
4485
+ };
4264
4486
  const uploadItemMediaFromV2 = (itemIndex, field, file) => {
4265
4487
  if (selectedType === "featureGrid" && field === "media") {
4266
4488
  void uploadMediaForSelected({ field: "media", itemIndex, kind: "featureGridItem" }, file);
@@ -4309,7 +4531,7 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4309
4531
  const nextLayout = cloneBlockLayout(layout);
4310
4532
  const block = nextLayout[selectedIndex];
4311
4533
  if (target.kind === "hero") {
4312
- const uploadedItem = toMediaLibraryItem2(uploaded);
4534
+ const uploadedItem = toMediaLibraryItem3(uploaded);
4313
4535
  nextLayout[selectedIndex] = {
4314
4536
  ...block,
4315
4537
  backgroundImageURL: uploadedItem?.url || normalizeText2(uploaded.url),
@@ -4373,13 +4595,13 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4373
4595
  const nextBlock = { ...migrateBlockToSettingsV2(block) };
4374
4596
  const blockType = normalizeText2(nextBlock.blockType);
4375
4597
  if (blockType === "hero") {
4376
- const mediaID = getRelationID2(nextBlock.media);
4598
+ const mediaID = getRelationID3(nextBlock.media);
4377
4599
  if (mediaID !== null) {
4378
4600
  nextBlock.media = mediaID;
4379
4601
  }
4380
4602
  }
4381
4603
  if (blockType === "media") {
4382
- const imageID = getRelationID2(nextBlock.image);
4604
+ const imageID = getRelationID3(nextBlock.image);
4383
4605
  if (imageID !== null) {
4384
4606
  nextBlock.image = imageID;
4385
4607
  }
@@ -4391,15 +4613,15 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4391
4613
  return rawItem;
4392
4614
  }
4393
4615
  const nextItem = { ...rawItem };
4394
- const mediaID = getRelationID2(nextItem.media);
4616
+ const mediaID = getRelationID3(nextItem.media);
4395
4617
  if (mediaID !== null) {
4396
4618
  nextItem.media = mediaID;
4397
4619
  }
4398
- const beforeMediaID = getRelationID2(nextItem.beforeMedia);
4620
+ const beforeMediaID = getRelationID3(nextItem.beforeMedia);
4399
4621
  if (beforeMediaID !== null) {
4400
4622
  nextItem.beforeMedia = beforeMediaID;
4401
4623
  }
4402
- const afterMediaID = getRelationID2(nextItem.afterMedia);
4624
+ const afterMediaID = getRelationID3(nextItem.afterMedia);
4403
4625
  if (afterMediaID !== null) {
4404
4626
  nextItem.afterMedia = afterMediaID;
4405
4627
  }
@@ -4743,6 +4965,97 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4743
4965
  setSidebarOpen(true);
4744
4966
  setActiveSidebarPanel("addSections");
4745
4967
  }, [layout.length]);
4968
+ const selectedMediaSources = selectedBlockHasMediaSource ? [
4969
+ {
4970
+ error: mediaLibraryError,
4971
+ label: selectedType === "hero" ? "Hero Image" : "Section Image",
4972
+ library: mediaLibrary,
4973
+ loading: mediaLibraryLoading,
4974
+ onRemove: () => setSelectedBlockMediaFromLibrary(""),
4975
+ onSelect: setSelectedBlockMediaFromLibrary,
4976
+ onUpload: uploadSelectedBlockMediaFromV2,
4977
+ uploadDisabled: uploadingTarget !== null,
4978
+ uploadLabel: selectedType === "hero" ? "Upload Hero Image" : "Upload Image",
4979
+ uploading: isSelectedBlockMediaUploading,
4980
+ value: selectedBlockMediaValue
4981
+ }
4982
+ ] : selectedItemRecord && typeof selectedItemIndex === "number" && selectedType === "featureGrid" ? [
4983
+ {
4984
+ error: mediaLibraryError,
4985
+ label: `Feature Image (Item ${selectedItemIndex + 1})`,
4986
+ library: mediaLibrary,
4987
+ loading: mediaLibraryLoading,
4988
+ onRemove: () => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "media", ""),
4989
+ onSelect: (mediaID) => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "media", mediaID),
4990
+ onUpload: (file) => uploadItemMediaFromV2(selectedItemIndex, "media", file),
4991
+ uploadDisabled: uploadingTarget !== null,
4992
+ uploadLabel: "Upload Feature Image",
4993
+ uploading: isSelectedItemMediaUploading(selectedItemIndex, "media"),
4994
+ value: selectedItemRecord.media
4995
+ }
4996
+ ] : selectedItemRecord && typeof selectedItemIndex === "number" && selectedType === "logoWall" ? [
4997
+ {
4998
+ error: mediaLibraryError,
4999
+ label: `Logo Image (Item ${selectedItemIndex + 1})`,
5000
+ library: mediaLibrary,
5001
+ loading: mediaLibraryLoading,
5002
+ onRemove: () => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "media", ""),
5003
+ onSelect: (mediaID) => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "media", mediaID),
5004
+ onUpload: (file) => uploadItemMediaFromV2(selectedItemIndex, "media", file),
5005
+ uploadDisabled: uploadingTarget !== null,
5006
+ uploadLabel: "Upload Logo Image",
5007
+ uploading: isSelectedItemMediaUploading(selectedItemIndex, "media"),
5008
+ value: selectedItemRecord.media
5009
+ }
5010
+ ] : selectedItemRecord && typeof selectedItemIndex === "number" && selectedType === "beforeAfter" ? [
5011
+ {
5012
+ error: mediaLibraryError,
5013
+ label: `Before Image (Item ${selectedItemIndex + 1})`,
5014
+ library: mediaLibrary,
5015
+ loading: mediaLibraryLoading,
5016
+ onRemove: () => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "beforeMedia", ""),
5017
+ onSelect: (mediaID) => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "beforeMedia", mediaID),
5018
+ onUpload: (file) => uploadItemMediaFromV2(selectedItemIndex, "beforeMedia", file),
5019
+ uploadDisabled: uploadingTarget !== null,
5020
+ uploadLabel: "Upload Before Image",
5021
+ uploading: isSelectedItemMediaUploading(selectedItemIndex, "beforeMedia"),
5022
+ value: selectedItemRecord.beforeMedia
5023
+ },
5024
+ {
5025
+ error: mediaLibraryError,
5026
+ label: `After Image (Item ${selectedItemIndex + 1})`,
5027
+ library: mediaLibrary,
5028
+ loading: mediaLibraryLoading,
5029
+ onRemove: () => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "afterMedia", ""),
5030
+ onSelect: (mediaID) => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "afterMedia", mediaID),
5031
+ onUpload: (file) => uploadItemMediaFromV2(selectedItemIndex, "afterMedia", file),
5032
+ uploadDisabled: uploadingTarget !== null,
5033
+ uploadLabel: "Upload After Image",
5034
+ uploading: isSelectedItemMediaUploading(selectedItemIndex, "afterMedia"),
5035
+ value: selectedItemRecord.afterMedia
5036
+ }
5037
+ ] : [];
5038
+ const selectedMediaImageControls = selectedItemRecord && typeof selectedItemIndex === "number" && (selectedType === "featureGrid" || selectedType === "logoWall" || selectedType === "beforeAfter") ? {
5039
+ cornerStyle: normalizeImageCornerStyle2(selectedItemRecord.imageCornerStyle),
5040
+ fit: normalizeImageFit2(selectedItemRecord.imageFit),
5041
+ height: (() => {
5042
+ if (typeof selectedItemRecord.imageHeight === "number" && Number.isFinite(selectedItemRecord.imageHeight)) {
5043
+ return selectedItemRecord.imageHeight;
5044
+ }
5045
+ if (typeof selectedItemRecord.imageHeight === "string" && selectedItemRecord.imageHeight.trim().length > 0) {
5046
+ const parsed = Number(selectedItemRecord.imageHeight);
5047
+ if (Number.isFinite(parsed)) {
5048
+ return parsed;
5049
+ }
5050
+ }
5051
+ return null;
5052
+ })(),
5053
+ maxHeight: selectedType === "logoWall" ? 200 : 600,
5054
+ minHeight: selectedType === "logoWall" ? 24 : selectedType === "beforeAfter" ? 60 : 40,
5055
+ position: normalizeImagePosition2(selectedItemRecord.imagePosition),
5056
+ positionX: parsePercentNumber(selectedItemRecord.imagePositionX, 50),
5057
+ positionY: parsePercentNumber(selectedItemRecord.imagePositionY, 50)
5058
+ } : void 0;
4746
5059
  (0, import_react5.useEffect)(() => {
4747
5060
  return;
4748
5061
  }, [layout]);
@@ -4975,6 +5288,8 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4975
5288
  block.backgroundImageFit
4976
5289
  );
4977
5290
  const backgroundImagePosition = normalizeHeroImagePosition(block.backgroundImagePosition);
5291
+ const backgroundImagePositionX = parseOptionalPercentNumber2(block.backgroundImagePositionX);
5292
+ const backgroundImagePositionY = parseOptionalPercentNumber2(block.backgroundImagePositionY);
4978
5293
  const heroHeight = normalizeHeroHeight(block.heroHeight);
4979
5294
  const heroMinHeight = heroHeight === "full" ? "100svh" : heroHeight === "md" ? resolveBuilderMediumHeroHeight(topViewportHeight) : "360px";
4980
5295
  const heroCornerRadius = getHeroImageCornerRadius(backgroundImageCornerStyle);
@@ -4984,6 +5299,10 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4984
5299
  );
4985
5300
  const backgroundImageURL = normalizeText2(block.backgroundImageURL);
4986
5301
  const backgroundImage = media?.url || backgroundImageURL;
5302
+ const backgroundPositionBase = positionPercent(backgroundImagePosition, backgroundImageFit);
5303
+ const resolvedBackgroundPositionX = typeof backgroundImagePositionX === "number" ? backgroundImagePositionX : backgroundPositionBase.x;
5304
+ const resolvedBackgroundPositionY = typeof backgroundImagePositionY === "number" ? backgroundImagePositionY : backgroundPositionBase.y;
5305
+ const backgroundImageObjectPosition = `${resolvedBackgroundPositionX}% ${resolvedBackgroundPositionY}%`;
4987
5306
  const hasCustomHeroColor = backgroundColor.length > 0 && backgroundColor.toLowerCase() !== "#124a37";
4988
5307
  const overlayModeRaw = normalizeText2(block?.backgroundOverlayMode, "none");
4989
5308
  const overlayMode = overlayModeRaw === "solid" || overlayModeRaw === "gradient" ? overlayModeRaw : "none";
@@ -5017,7 +5336,7 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
5017
5336
  const mediaStyle = backgroundImage && variant === "default" ? {
5018
5337
  ...hasCustomHeroColor ? { backgroundColor } : {},
5019
5338
  backgroundImage: overlayLayer ? `${overlayLayer}, url('${backgroundImage}')` : `url('${backgroundImage}')`,
5020
- backgroundPosition: overlayLayer ? `center center, ${backgroundImagePosition}` : backgroundImagePosition,
5339
+ backgroundPosition: overlayLayer ? `center center, ${backgroundImageObjectPosition}` : backgroundImageObjectPosition,
5021
5340
  backgroundRepeat: overlayLayer ? "no-repeat, no-repeat" : "no-repeat",
5022
5341
  backgroundSize: overlayLayer ? `cover, ${backgroundImageFit}` : backgroundImageFit,
5023
5342
  minHeight: heroMinHeight
@@ -5150,8 +5469,8 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
5150
5469
  const iconType = normalizeText2(itemRecord?.iconType, "badge");
5151
5470
  const iconBadge = normalizeText2(itemRecord?.icon);
5152
5471
  const iconLucide = normalizeText2(itemRecord?.iconLucide);
5153
- const itemPositionX = parseOptionalPercentNumber(itemRecord?.imagePositionX);
5154
- const itemPositionY = parseOptionalPercentNumber(itemRecord?.imagePositionY);
5472
+ const itemPositionX = parseOptionalPercentNumber2(itemRecord?.imagePositionX);
5473
+ const itemPositionY = parseOptionalPercentNumber2(itemRecord?.imagePositionY);
5155
5474
  const itemImageStyle = getImagePresentationStyle({
5156
5475
  cornerStyle: normalizeImageCornerStyle2(itemRecord?.imageCornerStyle),
5157
5476
  fit: normalizeImageFit2(itemRecord?.imageFit),
@@ -5265,8 +5584,8 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
5265
5584
  const iconType = normalizeText2(itemRecord?.iconType, "badge");
5266
5585
  const iconBadge = normalizeText2(itemRecord?.icon);
5267
5586
  const iconLucide = normalizeText2(itemRecord?.iconLucide);
5268
- const itemPositionX = parseOptionalPercentNumber(itemRecord?.imagePositionX);
5269
- const itemPositionY = parseOptionalPercentNumber(itemRecord?.imagePositionY);
5587
+ const itemPositionX = parseOptionalPercentNumber2(itemRecord?.imagePositionX);
5588
+ const itemPositionY = parseOptionalPercentNumber2(itemRecord?.imagePositionY);
5270
5589
  const itemImageStyle = getImagePresentationStyle({
5271
5590
  cornerStyle: normalizeImageCornerStyle2(itemRecord?.imageCornerStyle),
5272
5591
  fit: normalizeImageFit2(itemRecord?.imageFit),
@@ -5469,8 +5788,8 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
5469
5788
  const itemRecord = item;
5470
5789
  const media = resolveMedia(itemRecord?.media);
5471
5790
  const imageHeight = parsePixelNumber(itemRecord?.imageHeight, 54);
5472
- const itemPositionX = parseOptionalPercentNumber(itemRecord?.imagePositionX);
5473
- const itemPositionY = parseOptionalPercentNumber(itemRecord?.imagePositionY);
5791
+ const itemPositionX = parseOptionalPercentNumber2(itemRecord?.imagePositionX);
5792
+ const itemPositionY = parseOptionalPercentNumber2(itemRecord?.imagePositionY);
5474
5793
  const imageStyle = getImagePresentationStyle({
5475
5794
  cornerStyle: normalizeImageCornerStyle2(itemRecord?.imageCornerStyle),
5476
5795
  fit: normalizeImageFit2(itemRecord?.imageFit),
@@ -5576,8 +5895,8 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
5576
5895
  const beforeMedia = resolveMedia(itemRecord?.beforeMedia);
5577
5896
  const afterMedia = resolveMedia(itemRecord?.afterMedia);
5578
5897
  const imageHeight = parsePixelNumber(itemRecord?.imageHeight, 160);
5579
- const itemPositionX = parseOptionalPercentNumber(itemRecord?.imagePositionX);
5580
- const itemPositionY = parseOptionalPercentNumber(itemRecord?.imagePositionY);
5898
+ const itemPositionX = parseOptionalPercentNumber2(itemRecord?.imagePositionX);
5899
+ const itemPositionY = parseOptionalPercentNumber2(itemRecord?.imagePositionY);
5581
5900
  const imageStyle = getImagePresentationStyle({
5582
5901
  cornerStyle: normalizeImageCornerStyle2(itemRecord?.imageCornerStyle),
5583
5902
  fit: normalizeImageFit2(itemRecord?.imageFit),
@@ -6101,7 +6420,10 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
6101
6420
  {
6102
6421
  block: selectedBlock,
6103
6422
  blockType: selectedType,
6423
+ mediaImageControls: selectedMediaImageControls,
6424
+ mediaSources: selectedMediaSources,
6104
6425
  mode: settingsPanelMode,
6426
+ onMediaImageChange: selectedMediaImageControls ? updateSelectedItemMediaPresentationFromInspector : void 0,
6105
6427
  onModeChange: setSettingsPanelMode,
6106
6428
  onSearchQueryChange: setSettingsSearchQuery,
6107
6429
  onUpdateField: updateSelectedField,