@orion-studios/payload-studio 0.6.0-beta.56 → 0.6.0-beta.58

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.
@@ -786,9 +786,12 @@ var bindEditablePreview = (view, editor) => {
786
786
  const listIndex = Number(element.dataset.orionEditIndex);
787
787
  const listAttr = listName ? propToAttrName(listName) : "";
788
788
  element.setAttribute("title", element.getAttribute("aria-label") || element.textContent?.trim() || "Section action");
789
- element.addEventListener("click", (event) => {
789
+ const runAction = (event) => {
790
790
  event.preventDefault();
791
791
  event.stopPropagation();
792
+ if (typeof event.stopImmediatePropagation === "function") {
793
+ event.stopImmediatePropagation();
794
+ }
792
795
  editor.select?.(view.model);
793
796
  if (!listAttr) {
794
797
  return;
@@ -819,6 +822,12 @@ var bindEditablePreview = (view, editor) => {
819
822
  model: view.model
820
823
  });
821
824
  }
825
+ };
826
+ element.addEventListener("pointerdown", runAction, true);
827
+ element.addEventListener("keydown", (event) => {
828
+ if (event.key === "Enter" || event.key === " ") {
829
+ runAction(event);
830
+ }
822
831
  });
823
832
  });
824
833
  root.querySelectorAll("[data-orion-edit-field]").forEach((element) => {
@@ -944,6 +953,24 @@ var registerProjectDynamicComponents = (editor, adapter) => {
944
953
 
945
954
  // src/builder-v2/editor/GrapesPageEditor.tsx
946
955
  var import_jsx_runtime = require("react/jsx-runtime");
956
+ var decodeHtmlAttribute3 = (value) => {
957
+ if (typeof value !== "string") {
958
+ return "";
959
+ }
960
+ return value.replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
961
+ };
962
+ var parseItemsJson = (value) => {
963
+ const decoded = decodeHtmlAttribute3(value);
964
+ if (!decoded) {
965
+ return [];
966
+ }
967
+ try {
968
+ const parsed = JSON.parse(decoded);
969
+ return Array.isArray(parsed) ? parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
970
+ } catch {
971
+ return [];
972
+ }
973
+ };
947
974
  var postToParent = (payload) => {
948
975
  window.parent?.postMessage(
949
976
  {
@@ -1144,6 +1171,7 @@ function GrapesPageEditor({
1144
1171
  const editorRef = (0, import_react.useRef)(null);
1145
1172
  const autosaveTimerRef = (0, import_react.useRef)(null);
1146
1173
  const saveRef = (0, import_react.useRef)(async () => void 0);
1174
+ const selectedComponentRef = (0, import_react.useRef)(null);
1147
1175
  const [error, setError] = (0, import_react.useState)("");
1148
1176
  const [historyState, setHistoryState] = (0, import_react.useState)({ canRedo: false, canUndo: false });
1149
1177
  const [lastSavedAt, setLastSavedAt] = (0, import_react.useState)("");
@@ -1155,6 +1183,37 @@ function GrapesPageEditor({
1155
1183
  const [validationIssues, setValidationIssues] = (0, import_react.useState)([]);
1156
1184
  const editorPageBasePath = initialData?.meta?.editorPageBasePath || "/admin/pages";
1157
1185
  const pageTree = initialData?.meta?.pageTree || [];
1186
+ const summarizeSelectedComponent = (component) => {
1187
+ if (!component) {
1188
+ return null;
1189
+ }
1190
+ const attrs = component.getAttributes?.() || {};
1191
+ const componentType = String(attrs["data-orion-component"] || component.get?.("type") || "Section");
1192
+ const rawName = String(component.get?.("name") || componentType || "Section");
1193
+ const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1194
+ const items = parseItemsJson(attrs["data-orion-items-json"]).map((item, index) => ({
1195
+ index,
1196
+ title: typeof item.title === "string" && item.title.trim() ? item.title.trim() : `Item ${index + 1}`
1197
+ }));
1198
+ return {
1199
+ items: items.length > 0 ? items : void 0,
1200
+ label: label || "Section",
1201
+ type: componentType
1202
+ };
1203
+ };
1204
+ const updateSelectedItems = (updater) => {
1205
+ const component = selectedComponentRef.current;
1206
+ if (!component) {
1207
+ return;
1208
+ }
1209
+ const attrs = component.getAttributes?.() || {};
1210
+ const currentItems = parseItemsJson(attrs["data-orion-items-json"]);
1211
+ const nextItems = updater(currentItems);
1212
+ component.addAttributes?.({
1213
+ "data-orion-items-json": JSON.stringify(nextItems)
1214
+ });
1215
+ setSelectionSummary(summarizeSelectedComponent(component));
1216
+ };
1158
1217
  const updateHistoryState = (editor) => {
1159
1218
  const next = {
1160
1219
  canRedo: editor.UndoManager.hasRedo(),
@@ -1280,14 +1339,8 @@ function GrapesPageEditor({
1280
1339
  });
1281
1340
  editor.on("component:selected", (component) => {
1282
1341
  const typed = component;
1283
- const attrs = typed?.getAttributes?.() || {};
1284
- const componentType = String(attrs["data-orion-component"] || typed?.get?.("type") || "Section");
1285
- const rawName = String(typed?.get?.("name") || componentType || "Section");
1286
- const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1287
- setSelectionSummary({
1288
- label: label || "Section",
1289
- type: componentType
1290
- });
1342
+ selectedComponentRef.current = typed;
1343
+ setSelectionSummary(summarizeSelectedComponent(typed));
1291
1344
  setSaveMessage("Selection ready");
1292
1345
  });
1293
1346
  setSelectedDevice(editor.getDevice() || "desktop");
@@ -1531,6 +1584,62 @@ function GrapesPageEditor({
1531
1584
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: selectionSummary ? "Edit this section content, images, links, layout, and spacing." : "Select a section or element on the canvas to edit its settings." }),
1532
1585
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-traits" })
1533
1586
  ] }),
1587
+ selectionSummary?.items ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1588
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Items" }),
1589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Add, duplicate, remove, then click item text or images on the canvas to edit them." }),
1590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "orion-builder-v2-items", children: selectionSummary.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-item-row", children: [
1591
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item.title }),
1592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1593
+ "button",
1594
+ {
1595
+ onClick: () => {
1596
+ updateSelectedItems((items) => {
1597
+ const source = items[item.index];
1598
+ if (!source) {
1599
+ return items;
1600
+ }
1601
+ const copy = [...items];
1602
+ copy.splice(item.index + 1, 0, {
1603
+ ...source,
1604
+ title: typeof source.title === "string" ? `${source.title} copy` : source.title
1605
+ });
1606
+ return copy;
1607
+ });
1608
+ },
1609
+ type: "button",
1610
+ children: "Duplicate"
1611
+ }
1612
+ ),
1613
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1614
+ "button",
1615
+ {
1616
+ onClick: () => {
1617
+ updateSelectedItems((items) => items.filter((_, index) => index !== item.index));
1618
+ },
1619
+ type: "button",
1620
+ children: "Remove"
1621
+ }
1622
+ )
1623
+ ] }, `${item.index}-${item.title}`)) }),
1624
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1625
+ "button",
1626
+ {
1627
+ className: "orion-builder-v2-wide-action",
1628
+ onClick: () => {
1629
+ updateSelectedItems((items) => [
1630
+ ...items,
1631
+ {
1632
+ description: "Describe this feature.",
1633
+ imageURL: "",
1634
+ title: "New feature"
1635
+ }
1636
+ ]);
1637
+ },
1638
+ type: "button",
1639
+ children: "Add item"
1640
+ }
1641
+ )
1642
+ ] }) : null,
1534
1643
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1535
1644
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Design" }),
1536
1645
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Adjust spacing, layout, typography, color, borders, and shadows for the selected section." }),
@@ -662,9 +662,12 @@ var bindEditablePreview = (view, editor) => {
662
662
  const listIndex = Number(element.dataset.orionEditIndex);
663
663
  const listAttr = listName ? propToAttrName(listName) : "";
664
664
  element.setAttribute("title", element.getAttribute("aria-label") || element.textContent?.trim() || "Section action");
665
- element.addEventListener("click", (event) => {
665
+ const runAction = (event) => {
666
666
  event.preventDefault();
667
667
  event.stopPropagation();
668
+ if (typeof event.stopImmediatePropagation === "function") {
669
+ event.stopImmediatePropagation();
670
+ }
668
671
  editor.select?.(view.model);
669
672
  if (!listAttr) {
670
673
  return;
@@ -695,6 +698,12 @@ var bindEditablePreview = (view, editor) => {
695
698
  model: view.model
696
699
  });
697
700
  }
701
+ };
702
+ element.addEventListener("pointerdown", runAction, true);
703
+ element.addEventListener("keydown", (event) => {
704
+ if (event.key === "Enter" || event.key === " ") {
705
+ runAction(event);
706
+ }
698
707
  });
699
708
  });
700
709
  root.querySelectorAll("[data-orion-edit-field]").forEach((element) => {
@@ -820,6 +829,24 @@ var registerProjectDynamicComponents = (editor, adapter) => {
820
829
 
821
830
  // src/builder-v2/editor/GrapesPageEditor.tsx
822
831
  import { jsx, jsxs } from "react/jsx-runtime";
832
+ var decodeHtmlAttribute3 = (value) => {
833
+ if (typeof value !== "string") {
834
+ return "";
835
+ }
836
+ return value.replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
837
+ };
838
+ var parseItemsJson = (value) => {
839
+ const decoded = decodeHtmlAttribute3(value);
840
+ if (!decoded) {
841
+ return [];
842
+ }
843
+ try {
844
+ const parsed = JSON.parse(decoded);
845
+ return Array.isArray(parsed) ? parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
846
+ } catch {
847
+ return [];
848
+ }
849
+ };
823
850
  var postToParent = (payload) => {
824
851
  window.parent?.postMessage(
825
852
  {
@@ -1020,6 +1047,7 @@ function GrapesPageEditor({
1020
1047
  const editorRef = useRef(null);
1021
1048
  const autosaveTimerRef = useRef(null);
1022
1049
  const saveRef = useRef(async () => void 0);
1050
+ const selectedComponentRef = useRef(null);
1023
1051
  const [error, setError] = useState("");
1024
1052
  const [historyState, setHistoryState] = useState({ canRedo: false, canUndo: false });
1025
1053
  const [lastSavedAt, setLastSavedAt] = useState("");
@@ -1031,6 +1059,37 @@ function GrapesPageEditor({
1031
1059
  const [validationIssues, setValidationIssues] = useState([]);
1032
1060
  const editorPageBasePath = initialData?.meta?.editorPageBasePath || "/admin/pages";
1033
1061
  const pageTree = initialData?.meta?.pageTree || [];
1062
+ const summarizeSelectedComponent = (component) => {
1063
+ if (!component) {
1064
+ return null;
1065
+ }
1066
+ const attrs = component.getAttributes?.() || {};
1067
+ const componentType = String(attrs["data-orion-component"] || component.get?.("type") || "Section");
1068
+ const rawName = String(component.get?.("name") || componentType || "Section");
1069
+ const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1070
+ const items = parseItemsJson(attrs["data-orion-items-json"]).map((item, index) => ({
1071
+ index,
1072
+ title: typeof item.title === "string" && item.title.trim() ? item.title.trim() : `Item ${index + 1}`
1073
+ }));
1074
+ return {
1075
+ items: items.length > 0 ? items : void 0,
1076
+ label: label || "Section",
1077
+ type: componentType
1078
+ };
1079
+ };
1080
+ const updateSelectedItems = (updater) => {
1081
+ const component = selectedComponentRef.current;
1082
+ if (!component) {
1083
+ return;
1084
+ }
1085
+ const attrs = component.getAttributes?.() || {};
1086
+ const currentItems = parseItemsJson(attrs["data-orion-items-json"]);
1087
+ const nextItems = updater(currentItems);
1088
+ component.addAttributes?.({
1089
+ "data-orion-items-json": JSON.stringify(nextItems)
1090
+ });
1091
+ setSelectionSummary(summarizeSelectedComponent(component));
1092
+ };
1034
1093
  const updateHistoryState = (editor) => {
1035
1094
  const next = {
1036
1095
  canRedo: editor.UndoManager.hasRedo(),
@@ -1156,14 +1215,8 @@ function GrapesPageEditor({
1156
1215
  });
1157
1216
  editor.on("component:selected", (component) => {
1158
1217
  const typed = component;
1159
- const attrs = typed?.getAttributes?.() || {};
1160
- const componentType = String(attrs["data-orion-component"] || typed?.get?.("type") || "Section");
1161
- const rawName = String(typed?.get?.("name") || componentType || "Section");
1162
- const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1163
- setSelectionSummary({
1164
- label: label || "Section",
1165
- type: componentType
1166
- });
1218
+ selectedComponentRef.current = typed;
1219
+ setSelectionSummary(summarizeSelectedComponent(typed));
1167
1220
  setSaveMessage("Selection ready");
1168
1221
  });
1169
1222
  setSelectedDevice(editor.getDevice() || "desktop");
@@ -1407,6 +1460,62 @@ function GrapesPageEditor({
1407
1460
  /* @__PURE__ */ jsx("p", { children: selectionSummary ? "Edit this section content, images, links, layout, and spacing." : "Select a section or element on the canvas to edit its settings." }),
1408
1461
  /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-traits" })
1409
1462
  ] }),
1463
+ selectionSummary?.items ? /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
1464
+ /* @__PURE__ */ jsx("h2", { children: "Items" }),
1465
+ /* @__PURE__ */ jsx("p", { children: "Add, duplicate, remove, then click item text or images on the canvas to edit them." }),
1466
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-items", children: selectionSummary.items.map((item) => /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-item-row", children: [
1467
+ /* @__PURE__ */ jsx("span", { children: item.title }),
1468
+ /* @__PURE__ */ jsx(
1469
+ "button",
1470
+ {
1471
+ onClick: () => {
1472
+ updateSelectedItems((items) => {
1473
+ const source = items[item.index];
1474
+ if (!source) {
1475
+ return items;
1476
+ }
1477
+ const copy = [...items];
1478
+ copy.splice(item.index + 1, 0, {
1479
+ ...source,
1480
+ title: typeof source.title === "string" ? `${source.title} copy` : source.title
1481
+ });
1482
+ return copy;
1483
+ });
1484
+ },
1485
+ type: "button",
1486
+ children: "Duplicate"
1487
+ }
1488
+ ),
1489
+ /* @__PURE__ */ jsx(
1490
+ "button",
1491
+ {
1492
+ onClick: () => {
1493
+ updateSelectedItems((items) => items.filter((_, index) => index !== item.index));
1494
+ },
1495
+ type: "button",
1496
+ children: "Remove"
1497
+ }
1498
+ )
1499
+ ] }, `${item.index}-${item.title}`)) }),
1500
+ /* @__PURE__ */ jsx(
1501
+ "button",
1502
+ {
1503
+ className: "orion-builder-v2-wide-action",
1504
+ onClick: () => {
1505
+ updateSelectedItems((items) => [
1506
+ ...items,
1507
+ {
1508
+ description: "Describe this feature.",
1509
+ imageURL: "",
1510
+ title: "New feature"
1511
+ }
1512
+ ]);
1513
+ },
1514
+ type: "button",
1515
+ children: "Add item"
1516
+ }
1517
+ )
1518
+ ] }) : null,
1410
1519
  /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
1411
1520
  /* @__PURE__ */ jsx("h2", { children: "Design" }),
1412
1521
  /* @__PURE__ */ jsx("p", { children: "Adjust spacing, layout, typography, color, borders, and shadows for the selected section." }),
@@ -252,6 +252,52 @@
252
252
  color: #9f1239;
253
253
  }
254
254
 
255
+ .orion-builder-v2-items {
256
+ display: grid;
257
+ gap: 8px;
258
+ }
259
+
260
+ .orion-builder-v2-item-row {
261
+ align-items: center;
262
+ background: #fff8ef;
263
+ border: 1px solid rgba(231, 203, 185, 0.9);
264
+ border-radius: 10px;
265
+ display: grid;
266
+ gap: 6px;
267
+ grid-template-columns: minmax(0, 1fr) auto auto;
268
+ padding: 8px;
269
+ }
270
+
271
+ .orion-builder-v2-item-row span {
272
+ color: var(--builder-v2-ink);
273
+ font-size: 0.8rem;
274
+ font-weight: 900;
275
+ overflow: hidden;
276
+ text-overflow: ellipsis;
277
+ white-space: nowrap;
278
+ }
279
+
280
+ .orion-builder-v2-item-row button,
281
+ .orion-builder-v2-wide-action {
282
+ background: #ffffff;
283
+ border: 1px solid #e7cbb9;
284
+ border-radius: 999px;
285
+ color: var(--builder-v2-ink);
286
+ cursor: pointer;
287
+ font: inherit;
288
+ font-size: 0.72rem;
289
+ font-weight: 900;
290
+ min-height: 30px;
291
+ padding: 6px 9px;
292
+ }
293
+
294
+ .orion-builder-v2-wide-action {
295
+ background: var(--builder-v2-ink);
296
+ color: #ffffff;
297
+ margin-top: 2px;
298
+ width: 100%;
299
+ }
300
+
255
301
  .orion-builder-v2-runtime {
256
302
  width: 100%;
257
303
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orion-studios/payload-studio",
3
- "version": "0.6.0-beta.56",
3
+ "version": "0.6.0-beta.58",
4
4
  "description": "Base CMS, builder, and custom admin toolkit for Orion Studios websites",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",