@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.
@@ -966,11 +966,42 @@ var normalizeNumber = (value, fallback) => {
966
966
  }
967
967
  return fallback;
968
968
  };
969
+ var getRelationID = (value) => {
970
+ if (typeof value === "number" || typeof value === "string") {
971
+ return value;
972
+ }
973
+ if (!isRecord3(value)) {
974
+ return null;
975
+ }
976
+ const id = value.id;
977
+ return typeof id === "number" || typeof id === "string" ? id : null;
978
+ };
979
+ var toMediaLibraryItem = (value) => {
980
+ if (!isRecord3(value)) {
981
+ return null;
982
+ }
983
+ const id = getRelationID(value);
984
+ if (id === null) {
985
+ return null;
986
+ }
987
+ const filename = typeof value.filename === "string" ? value.filename : "";
988
+ const url = typeof value.url === "string" && value.url.length > 0 ? value.url : filename ? `/api/media/file/${encodeURIComponent(filename)}` : "";
989
+ return {
990
+ alt: typeof value.alt === "string" ? value.alt : "",
991
+ filename,
992
+ id,
993
+ url
994
+ };
995
+ };
996
+ var mediaLabel = (item) => item.filename || item.alt || `Media #${item.id}`;
969
997
  var groupLabel = (key) => commonInspectorGroups.find((group) => group.key === key)?.label || key;
970
998
  function BlockInspectorRenderer({
971
999
  block,
972
1000
  blockType,
1001
+ mediaImageControls,
1002
+ mediaSources,
973
1003
  mode,
1004
+ onMediaImageChange,
974
1005
  onModeChange,
975
1006
  onSearchQueryChange,
976
1007
  onUpdateField,
@@ -995,12 +1026,24 @@ function BlockInspectorRenderer({
995
1026
  });
996
1027
  }, [block, fields, mode, searchQuery]);
997
1028
  const groups = useMemo(() => {
998
- return commonInspectorGroups.map((group) => ({
1029
+ const baseGroups = commonInspectorGroups.map((group) => ({
999
1030
  key: group.key,
1000
1031
  label: group.label,
1001
1032
  fields: resolvedFields.filter((field) => field.group === group.key)
1002
1033
  })).filter((group) => group.fields.length > 0);
1003
- }, [resolvedFields]);
1034
+ const hasExplicitMediaControls = Boolean(mediaImageControls) || Array.isArray(mediaSources) && mediaSources.length > 0;
1035
+ if (!hasExplicitMediaControls || baseGroups.some((group) => group.key === "media")) {
1036
+ return baseGroups;
1037
+ }
1038
+ return [
1039
+ ...baseGroups,
1040
+ {
1041
+ key: "media",
1042
+ label: groupLabel("media"),
1043
+ fields: []
1044
+ }
1045
+ ];
1046
+ }, [mediaImageControls, mediaSources, resolvedFields]);
1004
1047
  if (!definition) {
1005
1048
  return /* @__PURE__ */ jsx3("div", { className: "orion-builder-settings-empty", children: "No V2 schema has been registered for this section yet." });
1006
1049
  }
@@ -1058,6 +1101,18 @@ function BlockInspectorRenderer({
1058
1101
  const mediaPositionX = normalizeNumber(getByPath(block, "settings.media.positionX"), 50);
1059
1102
  const mediaPositionY = normalizeNumber(getByPath(block, "settings.media.positionY"), 50);
1060
1103
  const mediaHeight = getByPath(block, "settings.media.height");
1104
+ const fallbackMediaPosition = mediaPosition === "top" || mediaPosition === "bottom" || mediaPosition === "left" || mediaPosition === "right" ? mediaPosition : "center";
1105
+ const fallbackMediaControls = {
1106
+ cornerStyle: mediaCornerStyle === "square" ? "square" : "rounded",
1107
+ fit: mediaFit === "contain" ? "contain" : "cover",
1108
+ height: typeof mediaHeight === "number" ? mediaHeight : null,
1109
+ position: fallbackMediaPosition,
1110
+ positionX: mediaPositionX,
1111
+ positionY: mediaPositionY
1112
+ };
1113
+ const effectiveMedia = mediaImageControls ?? fallbackMediaControls;
1114
+ const effectiveMediaSources = Array.isArray(mediaSources) ? mediaSources : [];
1115
+ const hasMediaGroupContent = group.key === "media" && (effectiveMediaSources.length > 0 || Boolean(effectiveMedia));
1061
1116
  return /* @__PURE__ */ jsx3(
1062
1117
  Accordion,
1063
1118
  {
@@ -1069,21 +1124,90 @@ function BlockInspectorRenderer({
1069
1124
  subtitle: `${group.fields.length} setting${group.fields.length === 1 ? "" : "s"}`,
1070
1125
  title: group.label,
1071
1126
  children: /* @__PURE__ */ jsxs3("div", { className: "orion-builder-settings-field-list", children: [
1072
- group.key === "media" ? /* @__PURE__ */ jsx3(
1127
+ group.key === "media" ? effectiveMediaSources.map((source) => {
1128
+ const selectedSourceMedia = toMediaLibraryItem(source.value);
1129
+ const selectedSourceMediaID = getRelationID(source.value);
1130
+ const sourceOptions = selectedSourceMedia && !source.library.some((item) => String(item.id) === String(selectedSourceMedia.id)) ? [selectedSourceMedia, ...source.library] : source.library;
1131
+ return /* @__PURE__ */ jsxs3(
1132
+ "div",
1133
+ {
1134
+ className: "orion-builder-settings-item-card",
1135
+ style: { padding: "0.56rem" },
1136
+ children: [
1137
+ source.loading ? /* @__PURE__ */ jsx3("div", { className: "orion-builder-settings-note", children: "Loading media library..." }) : null,
1138
+ source.error ? /* @__PURE__ */ jsx3("div", { className: "orion-builder-settings-error", children: source.error }) : null,
1139
+ /* @__PURE__ */ jsxs3("label", { className: "orion-builder-settings-label", children: [
1140
+ source.label,
1141
+ /* @__PURE__ */ jsxs3(
1142
+ "select",
1143
+ {
1144
+ className: "orion-builder-settings-input",
1145
+ onChange: (event) => source.onSelect(event.target.value),
1146
+ value: selectedSourceMediaID !== null ? String(selectedSourceMediaID) : "",
1147
+ children: [
1148
+ /* @__PURE__ */ jsx3("option", { value: "", children: "No image" }),
1149
+ sourceOptions.map((item) => /* @__PURE__ */ jsx3("option", { value: String(item.id), children: mediaLabel(item) }, String(item.id)))
1150
+ ]
1151
+ }
1152
+ )
1153
+ ] }),
1154
+ /* @__PURE__ */ jsx3(
1155
+ "button",
1156
+ {
1157
+ className: "orion-builder-settings-inline-btn",
1158
+ disabled: selectedSourceMediaID === null,
1159
+ onClick: source.onRemove,
1160
+ type: "button",
1161
+ children: "Remove Image"
1162
+ }
1163
+ ),
1164
+ /* @__PURE__ */ jsxs3("label", { className: "orion-builder-settings-label", children: [
1165
+ source.uploadLabel,
1166
+ /* @__PURE__ */ jsx3(
1167
+ "input",
1168
+ {
1169
+ accept: "image/*",
1170
+ className: "orion-builder-settings-input",
1171
+ disabled: source.uploadDisabled,
1172
+ onChange: (event) => {
1173
+ const file = event.currentTarget.files?.[0];
1174
+ if (file) {
1175
+ source.onUpload(file);
1176
+ }
1177
+ event.currentTarget.value = "";
1178
+ },
1179
+ type: "file"
1180
+ }
1181
+ )
1182
+ ] }),
1183
+ source.uploading ? /* @__PURE__ */ jsx3("div", { className: "orion-builder-settings-note", children: "Uploading image..." }) : null
1184
+ ]
1185
+ },
1186
+ `media-source-${source.label}`
1187
+ );
1188
+ }) : null,
1189
+ group.key === "media" && effectiveMedia ? /* @__PURE__ */ jsx3(
1073
1190
  ImageControls,
1074
1191
  {
1075
- cornerStyle: mediaCornerStyle === "square" ? "square" : "rounded",
1076
- fit: mediaFit === "contain" ? "contain" : "cover",
1077
- height: typeof mediaHeight === "number" ? mediaHeight : null,
1192
+ cornerStyle: effectiveMedia.cornerStyle,
1193
+ fit: effectiveMedia.fit,
1194
+ height: effectiveMedia.height,
1195
+ heightLabel: effectiveMedia.heightLabel,
1196
+ maxHeight: effectiveMedia.maxHeight,
1197
+ minHeight: effectiveMedia.minHeight,
1078
1198
  onChange: (field, value) => {
1199
+ if (onMediaImageChange) {
1200
+ onMediaImageChange(field, value);
1201
+ return;
1202
+ }
1079
1203
  updateForKey(`settings.media.${field}`, value);
1080
1204
  },
1081
- position: mediaPosition === "top" || mediaPosition === "bottom" || mediaPosition === "left" || mediaPosition === "right" ? mediaPosition : "center",
1082
- positionX: mediaPositionX,
1083
- positionY: mediaPositionY
1205
+ position: effectiveMedia.position,
1206
+ positionX: effectiveMedia.positionX,
1207
+ positionY: effectiveMedia.positionY
1084
1208
  }
1085
1209
  ) : null,
1086
- group.fields.filter((field) => !(group.key === "media" && field.key.startsWith("settings.media."))).map((field) => {
1210
+ (hasMediaGroupContent ? group.fields.filter((field) => !(group.key === "media" && field.key.startsWith("settings.media."))) : group.fields).map((field) => {
1087
1211
  const fieldValue = getByPath(block, field.key);
1088
1212
  if (field.type === "checkbox") {
1089
1213
  return /* @__PURE__ */ jsxs3("label", { className: "orion-builder-settings-label is-checkbox", children: [
@@ -2004,6 +2128,18 @@ function BlockFrame({
2004
2128
 
2005
2129
  // src/studio-pages/builder/renderers/renderSimpleBlockPreview.tsx
2006
2130
  import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2131
+ var parseOptionalPercentNumber = (value) => {
2132
+ if (typeof value === "number" && Number.isFinite(value)) {
2133
+ return Math.max(0, Math.min(100, value));
2134
+ }
2135
+ if (typeof value === "string" && value.trim().length > 0) {
2136
+ const parsed = Number(value);
2137
+ if (Number.isFinite(parsed)) {
2138
+ return Math.max(0, Math.min(100, parsed));
2139
+ }
2140
+ }
2141
+ return void 0;
2142
+ };
2007
2143
  function renderSimpleBlockPreview(args) {
2008
2144
  const {
2009
2145
  block,
@@ -2187,10 +2323,14 @@ function renderSimpleBlockPreview(args) {
2187
2323
  if (type === "media") {
2188
2324
  const image = resolveMedia2(block.image);
2189
2325
  const size = normalizeText3(block.size, "default");
2326
+ const imagePositionX = parseOptionalPercentNumber(block?.imagePositionX);
2327
+ const imagePositionY = parseOptionalPercentNumber(block?.imagePositionY);
2190
2328
  const imageStyle = getImagePresentationStyle2({
2191
2329
  cornerStyle: normalizeImageCornerStyle3(block?.imageCornerStyle),
2192
2330
  fit: normalizeImageFit3(block?.imageFit),
2193
- position: normalizeImagePosition3(block?.imagePosition)
2331
+ position: normalizeImagePosition3(block?.imagePosition),
2332
+ positionX: imagePositionX,
2333
+ positionY: imagePositionY
2194
2334
  });
2195
2335
  return /* @__PURE__ */ jsx8(
2196
2336
  BlockFrame,
@@ -2705,7 +2845,7 @@ var lucideIconOptions = [
2705
2845
  ];
2706
2846
  var isRecord4 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
2707
2847
  var normalizeText = (value, fallback = "") => typeof value === "string" ? value : fallback;
2708
- var getRelationID = (value) => {
2848
+ var getRelationID2 = (value) => {
2709
2849
  if (typeof value === "number" || typeof value === "string") {
2710
2850
  return value;
2711
2851
  }
@@ -2715,11 +2855,11 @@ var getRelationID = (value) => {
2715
2855
  const id = value.id;
2716
2856
  return typeof id === "number" || typeof id === "string" ? id : null;
2717
2857
  };
2718
- var toMediaLibraryItem = (value) => {
2858
+ var toMediaLibraryItem2 = (value) => {
2719
2859
  if (!isRecord4(value)) {
2720
2860
  return null;
2721
2861
  }
2722
- const id = getRelationID(value);
2862
+ const id = getRelationID2(value);
2723
2863
  if (id === null) {
2724
2864
  return null;
2725
2865
  }
@@ -2732,7 +2872,7 @@ var toMediaLibraryItem = (value) => {
2732
2872
  url
2733
2873
  };
2734
2874
  };
2735
- var mediaLabel = (item) => item.filename || item.alt || `Media #${item.id}`;
2875
+ var mediaLabel2 = (item) => item.filename || item.alt || `Media #${item.id}`;
2736
2876
  var normalizeNumber2 = (value, fallback) => {
2737
2877
  if (typeof value === "number" && Number.isFinite(value)) {
2738
2878
  return value;
@@ -2811,11 +2951,11 @@ function ArrayItemsEditor({
2811
2951
  const config = blockConfig[blockType];
2812
2952
  const normalizedQuery = searchQuery.trim().toLowerCase();
2813
2953
  const resolveSelectedMedia = (value) => {
2814
- const direct = toMediaLibraryItem(value);
2954
+ const direct = toMediaLibraryItem2(value);
2815
2955
  if (direct) {
2816
2956
  return direct;
2817
2957
  }
2818
- const relationID = getRelationID(value);
2958
+ const relationID = getRelationID2(value);
2819
2959
  if (relationID === null) {
2820
2960
  return null;
2821
2961
  }
@@ -2823,12 +2963,12 @@ function ArrayItemsEditor({
2823
2963
  };
2824
2964
  const renderMediaPicker = (item, itemIndex, field, label, uploadLabel) => {
2825
2965
  const selectedMedia = resolveSelectedMedia(item[field]);
2826
- const selectedMediaID = getRelationID(item[field]);
2827
- if (normalizedQuery && !hasQueryMatch(normalizedQuery, label, uploadLabel, "image", "media", selectedMedia ? mediaLabel(selectedMedia) : "")) {
2966
+ const selectedMediaID = getRelationID2(item[field]);
2967
+ const mediaOptions = selectedMedia && !mediaLibrary.some((libraryItem) => String(libraryItem.id) === String(selectedMedia.id)) ? [selectedMedia, ...mediaLibrary] : mediaLibrary;
2968
+ if (normalizedQuery && !hasQueryMatch(normalizedQuery, label, uploadLabel, "image", "media", selectedMedia ? mediaLabel2(selectedMedia) : "")) {
2828
2969
  return null;
2829
2970
  }
2830
2971
  return /* @__PURE__ */ jsxs11(Fragment4, { children: [
2831
- /* @__PURE__ */ jsx12("div", { className: "orion-builder-settings-note", children: selectedMedia ? `${label}: ${mediaLabel(selectedMedia)}` : `No ${label.toLowerCase()} selected.` }),
2832
2972
  /* @__PURE__ */ jsxs11("label", { className: "orion-builder-settings-label", children: [
2833
2973
  label,
2834
2974
  /* @__PURE__ */ jsxs11(
@@ -2839,7 +2979,7 @@ function ArrayItemsEditor({
2839
2979
  value: selectedMediaID !== null ? String(selectedMediaID) : "",
2840
2980
  children: [
2841
2981
  /* @__PURE__ */ jsx12("option", { value: "", children: "No image" }),
2842
- mediaLibrary.map((libraryItem) => /* @__PURE__ */ jsx12("option", { value: String(libraryItem.id), children: mediaLabel(libraryItem) }, String(libraryItem.id)))
2982
+ mediaOptions.map((libraryItem) => /* @__PURE__ */ jsx12("option", { value: String(libraryItem.id), children: mediaLabel2(libraryItem) }, String(libraryItem.id)))
2843
2983
  ]
2844
2984
  }
2845
2985
  )
@@ -3577,7 +3717,7 @@ function parseTestimonialRating(value, fallback = 5) {
3577
3717
  function parsePercentNumber(value, fallback) {
3578
3718
  return Math.max(0, Math.min(100, parsePixelNumber(value, fallback)));
3579
3719
  }
3580
- function parseOptionalPercentNumber(value) {
3720
+ function parseOptionalPercentNumber2(value) {
3581
3721
  if (typeof value === "number" && Number.isFinite(value)) {
3582
3722
  return Math.max(0, Math.min(100, value));
3583
3723
  }
@@ -3692,7 +3832,7 @@ var sectionStyleFromBlock = (block, pageDefaults) => {
3692
3832
  sectionStyle: sectionMode === "color" ? { background: sectionColor } : sectionMode === "gradient" ? { background: sectionGradient } : block.blockType === "hero" ? { background: "transparent" } : {}
3693
3833
  };
3694
3834
  };
3695
- function getRelationID2(value) {
3835
+ function getRelationID3(value) {
3696
3836
  if (typeof value === "number" || typeof value === "string") {
3697
3837
  return value;
3698
3838
  }
@@ -3725,7 +3865,7 @@ function extractUploadedMedia(value) {
3725
3865
  if (!candidate || typeof candidate !== "object") {
3726
3866
  return null;
3727
3867
  }
3728
- const id = getRelationID2(candidate);
3868
+ const id = getRelationID3(candidate);
3729
3869
  if (id === null) {
3730
3870
  return null;
3731
3871
  }
@@ -3736,11 +3876,11 @@ function extractUploadedMedia(value) {
3736
3876
  url: typeof candidate.url === "string" ? candidate.url : ""
3737
3877
  };
3738
3878
  }
3739
- function toMediaLibraryItem2(value) {
3879
+ function toMediaLibraryItem3(value) {
3740
3880
  if (!value || typeof value !== "object") {
3741
3881
  return null;
3742
3882
  }
3743
- const id = getRelationID2(value);
3883
+ const id = getRelationID3(value);
3744
3884
  if (id === null) {
3745
3885
  return null;
3746
3886
  }
@@ -3866,6 +4006,9 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
3866
4006
  const selectedBlockSettings = isRecord5(selectedBlock?.settings) ? selectedBlock.settings : {};
3867
4007
  const selectedBlockAdvancedSettings = isRecord5(selectedBlockSettings.advanced) ? selectedBlockSettings.advanced : {};
3868
4008
  const isArrayItemBlockSelected = selectedType === "featureGrid" || selectedType === "logoWall" || selectedType === "beforeAfter" || selectedType === "stats" || selectedType === "faq" || selectedType === "testimonials";
4009
+ const selectedBlockHasMediaSource = selectedType === "hero" || selectedType === "media";
4010
+ const selectedBlockMediaValue = selectedType === "hero" ? selectedBlock?.media : selectedType === "media" ? selectedBlock?.image : null;
4011
+ const selectedItemRecord = typeof selectedItemIndex === "number" && selectedItemIndex >= 0 && selectedItemIndex < selectedItems.length && isRecord5(selectedItems[selectedItemIndex]) ? selectedItems[selectedItemIndex] : null;
3869
4012
  const editCopyInPanelEnabled = Boolean(selectedBlockAdvancedSettings.editCopyInPanel);
3870
4013
  const isBlockUploadTarget = (blockIndex, kind) => selectedIndex === blockIndex && uploadingTarget?.kind === kind;
3871
4014
  const isFeatureGridItemUploading = (blockIndex, itemIndex) => selectedIndex === blockIndex && uploadingTarget?.kind === "featureGridItem" && uploadingTarget.itemIndex === itemIndex;
@@ -3886,6 +4029,7 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
3886
4029
  }
3887
4030
  return false;
3888
4031
  };
4032
+ const isSelectedBlockMediaUploading = selectedIndex !== null && (selectedType === "hero" && uploadingTarget?.kind === "hero" || selectedType === "media" && uploadingTarget?.kind === "media");
3889
4033
  const filteredSectionPresets = useMemo2(() => {
3890
4034
  const query = presetQuery.trim().toLowerCase();
3891
4035
  if (!query) {
@@ -3908,7 +4052,7 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
3908
4052
  }
3909
4053
  const json = await response.json();
3910
4054
  const docs = Array.isArray(json.docs) ? json.docs : [];
3911
- const items = docs.map((doc2) => toMediaLibraryItem2(doc2)).filter((item) => item !== null);
4055
+ const items = docs.map((doc2) => toMediaLibraryItem3(doc2)).filter((item) => item !== null);
3912
4056
  setMediaLibrary(items);
3913
4057
  } catch (error) {
3914
4058
  setMediaLibraryError(error instanceof Error ? error.message : "Could not load media library.");
@@ -4142,6 +4286,84 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4142
4286
  }
4143
4287
  updateArrayItemField(selectedIndex, "items", itemIndex, fieldName, mediaFromLibraryItem(selectedMedia));
4144
4288
  };
4289
+ const setSelectedBlockMediaValue = (value) => {
4290
+ if (selectedIndex === null) {
4291
+ return;
4292
+ }
4293
+ setLayout((current) => {
4294
+ const next = cloneBlockLayout(current);
4295
+ const block = next[selectedIndex];
4296
+ const blockType = normalizeText2(block.blockType);
4297
+ if (blockType === "hero") {
4298
+ next[selectedIndex] = migrateBlockToSettingsV2({
4299
+ ...block,
4300
+ backgroundImageURL: value ? getMediaURL(value) : "",
4301
+ media: value
4302
+ });
4303
+ return next;
4304
+ }
4305
+ if (blockType === "media") {
4306
+ next[selectedIndex] = migrateBlockToSettingsV2({
4307
+ ...block,
4308
+ image: value
4309
+ });
4310
+ }
4311
+ return next;
4312
+ });
4313
+ };
4314
+ const setSelectedBlockMediaFromLibrary = (mediaID) => {
4315
+ if (selectedIndex === null || !selectedBlockHasMediaSource) {
4316
+ return;
4317
+ }
4318
+ if (!mediaID) {
4319
+ setSelectedBlockMediaValue(null);
4320
+ return;
4321
+ }
4322
+ const selectedMedia = mediaLibrary.find((item) => String(item.id) === mediaID);
4323
+ if (!selectedMedia) {
4324
+ return;
4325
+ }
4326
+ setSelectedBlockMediaValue(mediaFromLibraryItem(selectedMedia));
4327
+ };
4328
+ const uploadSelectedBlockMediaFromV2 = (file) => {
4329
+ if (selectedType === "hero") {
4330
+ void uploadMediaForSelected({ kind: "hero" }, file);
4331
+ return;
4332
+ }
4333
+ if (selectedType === "media") {
4334
+ void uploadMediaForSelected({ kind: "media" }, file);
4335
+ }
4336
+ };
4337
+ const updateSelectedItemMediaPresentationFromInspector = (field, value) => {
4338
+ if (selectedIndex === null || typeof selectedItemIndex !== "number") {
4339
+ return;
4340
+ }
4341
+ if (field === "height") {
4342
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imageHeight", value);
4343
+ return;
4344
+ }
4345
+ if (field === "fit") {
4346
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imageFit", value);
4347
+ return;
4348
+ }
4349
+ if (field === "cornerStyle") {
4350
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imageCornerStyle", value);
4351
+ return;
4352
+ }
4353
+ if (field === "position") {
4354
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imagePosition", value);
4355
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imagePositionX", null);
4356
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imagePositionY", null);
4357
+ return;
4358
+ }
4359
+ if (field === "positionX") {
4360
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imagePositionX", value);
4361
+ return;
4362
+ }
4363
+ if (field === "positionY") {
4364
+ updateArrayItemField(selectedIndex, "items", selectedItemIndex, "imagePositionY", value);
4365
+ }
4366
+ };
4145
4367
  const uploadItemMediaFromV2 = (itemIndex, field, file) => {
4146
4368
  if (selectedType === "featureGrid" && field === "media") {
4147
4369
  void uploadMediaForSelected({ field: "media", itemIndex, kind: "featureGridItem" }, file);
@@ -4190,7 +4412,7 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4190
4412
  const nextLayout = cloneBlockLayout(layout);
4191
4413
  const block = nextLayout[selectedIndex];
4192
4414
  if (target.kind === "hero") {
4193
- const uploadedItem = toMediaLibraryItem2(uploaded);
4415
+ const uploadedItem = toMediaLibraryItem3(uploaded);
4194
4416
  nextLayout[selectedIndex] = {
4195
4417
  ...block,
4196
4418
  backgroundImageURL: uploadedItem?.url || normalizeText2(uploaded.url),
@@ -4254,13 +4476,13 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4254
4476
  const nextBlock = { ...migrateBlockToSettingsV2(block) };
4255
4477
  const blockType = normalizeText2(nextBlock.blockType);
4256
4478
  if (blockType === "hero") {
4257
- const mediaID = getRelationID2(nextBlock.media);
4479
+ const mediaID = getRelationID3(nextBlock.media);
4258
4480
  if (mediaID !== null) {
4259
4481
  nextBlock.media = mediaID;
4260
4482
  }
4261
4483
  }
4262
4484
  if (blockType === "media") {
4263
- const imageID = getRelationID2(nextBlock.image);
4485
+ const imageID = getRelationID3(nextBlock.image);
4264
4486
  if (imageID !== null) {
4265
4487
  nextBlock.image = imageID;
4266
4488
  }
@@ -4272,15 +4494,15 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4272
4494
  return rawItem;
4273
4495
  }
4274
4496
  const nextItem = { ...rawItem };
4275
- const mediaID = getRelationID2(nextItem.media);
4497
+ const mediaID = getRelationID3(nextItem.media);
4276
4498
  if (mediaID !== null) {
4277
4499
  nextItem.media = mediaID;
4278
4500
  }
4279
- const beforeMediaID = getRelationID2(nextItem.beforeMedia);
4501
+ const beforeMediaID = getRelationID3(nextItem.beforeMedia);
4280
4502
  if (beforeMediaID !== null) {
4281
4503
  nextItem.beforeMedia = beforeMediaID;
4282
4504
  }
4283
- const afterMediaID = getRelationID2(nextItem.afterMedia);
4505
+ const afterMediaID = getRelationID3(nextItem.afterMedia);
4284
4506
  if (afterMediaID !== null) {
4285
4507
  nextItem.afterMedia = afterMediaID;
4286
4508
  }
@@ -4624,6 +4846,97 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4624
4846
  setSidebarOpen(true);
4625
4847
  setActiveSidebarPanel("addSections");
4626
4848
  }, [layout.length]);
4849
+ const selectedMediaSources = selectedBlockHasMediaSource ? [
4850
+ {
4851
+ error: mediaLibraryError,
4852
+ label: selectedType === "hero" ? "Hero Image" : "Section Image",
4853
+ library: mediaLibrary,
4854
+ loading: mediaLibraryLoading,
4855
+ onRemove: () => setSelectedBlockMediaFromLibrary(""),
4856
+ onSelect: setSelectedBlockMediaFromLibrary,
4857
+ onUpload: uploadSelectedBlockMediaFromV2,
4858
+ uploadDisabled: uploadingTarget !== null,
4859
+ uploadLabel: selectedType === "hero" ? "Upload Hero Image" : "Upload Image",
4860
+ uploading: isSelectedBlockMediaUploading,
4861
+ value: selectedBlockMediaValue
4862
+ }
4863
+ ] : selectedItemRecord && typeof selectedItemIndex === "number" && selectedType === "featureGrid" ? [
4864
+ {
4865
+ error: mediaLibraryError,
4866
+ label: `Feature Image (Item ${selectedItemIndex + 1})`,
4867
+ library: mediaLibrary,
4868
+ loading: mediaLibraryLoading,
4869
+ onRemove: () => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "media", ""),
4870
+ onSelect: (mediaID) => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "media", mediaID),
4871
+ onUpload: (file) => uploadItemMediaFromV2(selectedItemIndex, "media", file),
4872
+ uploadDisabled: uploadingTarget !== null,
4873
+ uploadLabel: "Upload Feature Image",
4874
+ uploading: isSelectedItemMediaUploading(selectedItemIndex, "media"),
4875
+ value: selectedItemRecord.media
4876
+ }
4877
+ ] : selectedItemRecord && typeof selectedItemIndex === "number" && selectedType === "logoWall" ? [
4878
+ {
4879
+ error: mediaLibraryError,
4880
+ label: `Logo Image (Item ${selectedItemIndex + 1})`,
4881
+ library: mediaLibrary,
4882
+ loading: mediaLibraryLoading,
4883
+ onRemove: () => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "media", ""),
4884
+ onSelect: (mediaID) => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "media", mediaID),
4885
+ onUpload: (file) => uploadItemMediaFromV2(selectedItemIndex, "media", file),
4886
+ uploadDisabled: uploadingTarget !== null,
4887
+ uploadLabel: "Upload Logo Image",
4888
+ uploading: isSelectedItemMediaUploading(selectedItemIndex, "media"),
4889
+ value: selectedItemRecord.media
4890
+ }
4891
+ ] : selectedItemRecord && typeof selectedItemIndex === "number" && selectedType === "beforeAfter" ? [
4892
+ {
4893
+ error: mediaLibraryError,
4894
+ label: `Before Image (Item ${selectedItemIndex + 1})`,
4895
+ library: mediaLibrary,
4896
+ loading: mediaLibraryLoading,
4897
+ onRemove: () => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "beforeMedia", ""),
4898
+ onSelect: (mediaID) => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "beforeMedia", mediaID),
4899
+ onUpload: (file) => uploadItemMediaFromV2(selectedItemIndex, "beforeMedia", file),
4900
+ uploadDisabled: uploadingTarget !== null,
4901
+ uploadLabel: "Upload Before Image",
4902
+ uploading: isSelectedItemMediaUploading(selectedItemIndex, "beforeMedia"),
4903
+ value: selectedItemRecord.beforeMedia
4904
+ },
4905
+ {
4906
+ error: mediaLibraryError,
4907
+ label: `After Image (Item ${selectedItemIndex + 1})`,
4908
+ library: mediaLibrary,
4909
+ loading: mediaLibraryLoading,
4910
+ onRemove: () => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "afterMedia", ""),
4911
+ onSelect: (mediaID) => setSelectedItemMediaFieldFromLibrary(selectedItemIndex, "afterMedia", mediaID),
4912
+ onUpload: (file) => uploadItemMediaFromV2(selectedItemIndex, "afterMedia", file),
4913
+ uploadDisabled: uploadingTarget !== null,
4914
+ uploadLabel: "Upload After Image",
4915
+ uploading: isSelectedItemMediaUploading(selectedItemIndex, "afterMedia"),
4916
+ value: selectedItemRecord.afterMedia
4917
+ }
4918
+ ] : [];
4919
+ const selectedMediaImageControls = selectedItemRecord && typeof selectedItemIndex === "number" && (selectedType === "featureGrid" || selectedType === "logoWall" || selectedType === "beforeAfter") ? {
4920
+ cornerStyle: normalizeImageCornerStyle2(selectedItemRecord.imageCornerStyle),
4921
+ fit: normalizeImageFit2(selectedItemRecord.imageFit),
4922
+ height: (() => {
4923
+ if (typeof selectedItemRecord.imageHeight === "number" && Number.isFinite(selectedItemRecord.imageHeight)) {
4924
+ return selectedItemRecord.imageHeight;
4925
+ }
4926
+ if (typeof selectedItemRecord.imageHeight === "string" && selectedItemRecord.imageHeight.trim().length > 0) {
4927
+ const parsed = Number(selectedItemRecord.imageHeight);
4928
+ if (Number.isFinite(parsed)) {
4929
+ return parsed;
4930
+ }
4931
+ }
4932
+ return null;
4933
+ })(),
4934
+ maxHeight: selectedType === "logoWall" ? 200 : 600,
4935
+ minHeight: selectedType === "logoWall" ? 24 : selectedType === "beforeAfter" ? 60 : 40,
4936
+ position: normalizeImagePosition2(selectedItemRecord.imagePosition),
4937
+ positionX: parsePercentNumber(selectedItemRecord.imagePositionX, 50),
4938
+ positionY: parsePercentNumber(selectedItemRecord.imagePositionY, 50)
4939
+ } : void 0;
4627
4940
  useEffect4(() => {
4628
4941
  return;
4629
4942
  }, [layout]);
@@ -4856,6 +5169,8 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4856
5169
  block.backgroundImageFit
4857
5170
  );
4858
5171
  const backgroundImagePosition = normalizeHeroImagePosition(block.backgroundImagePosition);
5172
+ const backgroundImagePositionX = parseOptionalPercentNumber2(block.backgroundImagePositionX);
5173
+ const backgroundImagePositionY = parseOptionalPercentNumber2(block.backgroundImagePositionY);
4859
5174
  const heroHeight = normalizeHeroHeight(block.heroHeight);
4860
5175
  const heroMinHeight = heroHeight === "full" ? "100svh" : heroHeight === "md" ? resolveBuilderMediumHeroHeight(topViewportHeight) : "360px";
4861
5176
  const heroCornerRadius = getHeroImageCornerRadius(backgroundImageCornerStyle);
@@ -4865,6 +5180,10 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4865
5180
  );
4866
5181
  const backgroundImageURL = normalizeText2(block.backgroundImageURL);
4867
5182
  const backgroundImage = media?.url || backgroundImageURL;
5183
+ const backgroundPositionBase = positionPercent(backgroundImagePosition, backgroundImageFit);
5184
+ const resolvedBackgroundPositionX = typeof backgroundImagePositionX === "number" ? backgroundImagePositionX : backgroundPositionBase.x;
5185
+ const resolvedBackgroundPositionY = typeof backgroundImagePositionY === "number" ? backgroundImagePositionY : backgroundPositionBase.y;
5186
+ const backgroundImageObjectPosition = `${resolvedBackgroundPositionX}% ${resolvedBackgroundPositionY}%`;
4868
5187
  const hasCustomHeroColor = backgroundColor.length > 0 && backgroundColor.toLowerCase() !== "#124a37";
4869
5188
  const overlayModeRaw = normalizeText2(block?.backgroundOverlayMode, "none");
4870
5189
  const overlayMode = overlayModeRaw === "solid" || overlayModeRaw === "gradient" ? overlayModeRaw : "none";
@@ -4898,7 +5217,7 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
4898
5217
  const mediaStyle = backgroundImage && variant === "default" ? {
4899
5218
  ...hasCustomHeroColor ? { backgroundColor } : {},
4900
5219
  backgroundImage: overlayLayer ? `${overlayLayer}, url('${backgroundImage}')` : `url('${backgroundImage}')`,
4901
- backgroundPosition: overlayLayer ? `center center, ${backgroundImagePosition}` : backgroundImagePosition,
5220
+ backgroundPosition: overlayLayer ? `center center, ${backgroundImageObjectPosition}` : backgroundImageObjectPosition,
4902
5221
  backgroundRepeat: overlayLayer ? "no-repeat, no-repeat" : "no-repeat",
4903
5222
  backgroundSize: overlayLayer ? `cover, ${backgroundImageFit}` : backgroundImageFit,
4904
5223
  minHeight: heroMinHeight
@@ -5031,8 +5350,8 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
5031
5350
  const iconType = normalizeText2(itemRecord?.iconType, "badge");
5032
5351
  const iconBadge = normalizeText2(itemRecord?.icon);
5033
5352
  const iconLucide = normalizeText2(itemRecord?.iconLucide);
5034
- const itemPositionX = parseOptionalPercentNumber(itemRecord?.imagePositionX);
5035
- const itemPositionY = parseOptionalPercentNumber(itemRecord?.imagePositionY);
5353
+ const itemPositionX = parseOptionalPercentNumber2(itemRecord?.imagePositionX);
5354
+ const itemPositionY = parseOptionalPercentNumber2(itemRecord?.imagePositionY);
5036
5355
  const itemImageStyle = getImagePresentationStyle({
5037
5356
  cornerStyle: normalizeImageCornerStyle2(itemRecord?.imageCornerStyle),
5038
5357
  fit: normalizeImageFit2(itemRecord?.imageFit),
@@ -5146,8 +5465,8 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
5146
5465
  const iconType = normalizeText2(itemRecord?.iconType, "badge");
5147
5466
  const iconBadge = normalizeText2(itemRecord?.icon);
5148
5467
  const iconLucide = normalizeText2(itemRecord?.iconLucide);
5149
- const itemPositionX = parseOptionalPercentNumber(itemRecord?.imagePositionX);
5150
- const itemPositionY = parseOptionalPercentNumber(itemRecord?.imagePositionY);
5468
+ const itemPositionX = parseOptionalPercentNumber2(itemRecord?.imagePositionX);
5469
+ const itemPositionY = parseOptionalPercentNumber2(itemRecord?.imagePositionY);
5151
5470
  const itemImageStyle = getImagePresentationStyle({
5152
5471
  cornerStyle: normalizeImageCornerStyle2(itemRecord?.imageCornerStyle),
5153
5472
  fit: normalizeImageFit2(itemRecord?.imageFit),
@@ -5350,8 +5669,8 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
5350
5669
  const itemRecord = item;
5351
5670
  const media = resolveMedia(itemRecord?.media);
5352
5671
  const imageHeight = parsePixelNumber(itemRecord?.imageHeight, 54);
5353
- const itemPositionX = parseOptionalPercentNumber(itemRecord?.imagePositionX);
5354
- const itemPositionY = parseOptionalPercentNumber(itemRecord?.imagePositionY);
5672
+ const itemPositionX = parseOptionalPercentNumber2(itemRecord?.imagePositionX);
5673
+ const itemPositionY = parseOptionalPercentNumber2(itemRecord?.imagePositionY);
5355
5674
  const imageStyle = getImagePresentationStyle({
5356
5675
  cornerStyle: normalizeImageCornerStyle2(itemRecord?.imageCornerStyle),
5357
5676
  fit: normalizeImageFit2(itemRecord?.imageFit),
@@ -5457,8 +5776,8 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
5457
5776
  const beforeMedia = resolveMedia(itemRecord?.beforeMedia);
5458
5777
  const afterMedia = resolveMedia(itemRecord?.afterMedia);
5459
5778
  const imageHeight = parsePixelNumber(itemRecord?.imageHeight, 160);
5460
- const itemPositionX = parseOptionalPercentNumber(itemRecord?.imagePositionX);
5461
- const itemPositionY = parseOptionalPercentNumber(itemRecord?.imagePositionY);
5779
+ const itemPositionX = parseOptionalPercentNumber2(itemRecord?.imagePositionX);
5780
+ const itemPositionY = parseOptionalPercentNumber2(itemRecord?.imagePositionY);
5462
5781
  const imageStyle = getImagePresentationStyle({
5463
5782
  cornerStyle: normalizeImageCornerStyle2(itemRecord?.imageCornerStyle),
5464
5783
  fit: normalizeImageFit2(itemRecord?.imageFit),
@@ -5982,7 +6301,10 @@ function BuilderPageEditor({ featureFlags: _featureFlags, initialDoc, pageID, si
5982
6301
  {
5983
6302
  block: selectedBlock,
5984
6303
  blockType: selectedType,
6304
+ mediaImageControls: selectedMediaImageControls,
6305
+ mediaSources: selectedMediaSources,
5985
6306
  mode: settingsPanelMode,
6307
+ onMediaImageChange: selectedMediaImageControls ? updateSelectedItemMediaPresentationFromInspector : void 0,
5986
6308
  onModeChange: setSettingsPanelMode,
5987
6309
  onSearchQueryChange: setSettingsSearchQuery,
5988
6310
  onUpdateField: updateSelectedField,