@orion-studios/payload-studio 0.6.0-beta.57 → 0.6.0-beta.59

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.
@@ -839,6 +839,9 @@ var bindEditablePreview = (view, editor) => {
839
839
  const placeholder = element.dataset.orionPlaceholder || "";
840
840
  element.setAttribute("title", isImage ? "Click to replace image" : "Click and type to edit");
841
841
  element.style.cursor = "text";
842
+ element.addEventListener("pointerdown", () => {
843
+ editor.select?.(view.model);
844
+ }, true);
842
845
  element.addEventListener("click", (event) => {
843
846
  event.stopPropagation();
844
847
  editor.select?.(view.model);
@@ -953,6 +956,24 @@ var registerProjectDynamicComponents = (editor, adapter) => {
953
956
 
954
957
  // src/builder-v2/editor/GrapesPageEditor.tsx
955
958
  var import_jsx_runtime = require("react/jsx-runtime");
959
+ var decodeHtmlAttribute3 = (value) => {
960
+ if (typeof value !== "string") {
961
+ return "";
962
+ }
963
+ return value.replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
964
+ };
965
+ var parseItemsJson = (value) => {
966
+ const decoded = decodeHtmlAttribute3(value);
967
+ if (!decoded) {
968
+ return [];
969
+ }
970
+ try {
971
+ const parsed = JSON.parse(decoded);
972
+ return Array.isArray(parsed) ? parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
973
+ } catch {
974
+ return [];
975
+ }
976
+ };
956
977
  var postToParent = (payload) => {
957
978
  window.parent?.postMessage(
958
979
  {
@@ -1153,6 +1174,7 @@ function GrapesPageEditor({
1153
1174
  const editorRef = (0, import_react.useRef)(null);
1154
1175
  const autosaveTimerRef = (0, import_react.useRef)(null);
1155
1176
  const saveRef = (0, import_react.useRef)(async () => void 0);
1177
+ const selectedComponentRef = (0, import_react.useRef)(null);
1156
1178
  const [error, setError] = (0, import_react.useState)("");
1157
1179
  const [historyState, setHistoryState] = (0, import_react.useState)({ canRedo: false, canUndo: false });
1158
1180
  const [lastSavedAt, setLastSavedAt] = (0, import_react.useState)("");
@@ -1164,6 +1186,37 @@ function GrapesPageEditor({
1164
1186
  const [validationIssues, setValidationIssues] = (0, import_react.useState)([]);
1165
1187
  const editorPageBasePath = initialData?.meta?.editorPageBasePath || "/admin/pages";
1166
1188
  const pageTree = initialData?.meta?.pageTree || [];
1189
+ const summarizeSelectedComponent = (component) => {
1190
+ if (!component) {
1191
+ return null;
1192
+ }
1193
+ const attrs = component.getAttributes?.() || {};
1194
+ const componentType = String(attrs["data-orion-component"] || component.get?.("type") || "Section");
1195
+ const rawName = String(component.get?.("name") || componentType || "Section");
1196
+ const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1197
+ const items = parseItemsJson(attrs["data-orion-items-json"]).map((item, index) => ({
1198
+ index,
1199
+ title: typeof item.title === "string" && item.title.trim() ? item.title.trim() : `Item ${index + 1}`
1200
+ }));
1201
+ return {
1202
+ items: items.length > 0 ? items : void 0,
1203
+ label: label || "Section",
1204
+ type: componentType
1205
+ };
1206
+ };
1207
+ const updateSelectedItems = (updater) => {
1208
+ const component = selectedComponentRef.current;
1209
+ if (!component) {
1210
+ return;
1211
+ }
1212
+ const attrs = component.getAttributes?.() || {};
1213
+ const currentItems = parseItemsJson(attrs["data-orion-items-json"]);
1214
+ const nextItems = updater(currentItems);
1215
+ component.addAttributes?.({
1216
+ "data-orion-items-json": JSON.stringify(nextItems)
1217
+ });
1218
+ setSelectionSummary(summarizeSelectedComponent(component));
1219
+ };
1167
1220
  const updateHistoryState = (editor) => {
1168
1221
  const next = {
1169
1222
  canRedo: editor.UndoManager.hasRedo(),
@@ -1289,14 +1342,8 @@ function GrapesPageEditor({
1289
1342
  });
1290
1343
  editor.on("component:selected", (component) => {
1291
1344
  const typed = component;
1292
- const attrs = typed?.getAttributes?.() || {};
1293
- const componentType = String(attrs["data-orion-component"] || typed?.get?.("type") || "Section");
1294
- const rawName = String(typed?.get?.("name") || componentType || "Section");
1295
- const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1296
- setSelectionSummary({
1297
- label: label || "Section",
1298
- type: componentType
1299
- });
1345
+ selectedComponentRef.current = typed;
1346
+ setSelectionSummary(summarizeSelectedComponent(typed));
1300
1347
  setSaveMessage("Selection ready");
1301
1348
  });
1302
1349
  setSelectedDevice(editor.getDevice() || "desktop");
@@ -1540,6 +1587,62 @@ function GrapesPageEditor({
1540
1587
  /* @__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." }),
1541
1588
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "orion-builder-v2-traits" })
1542
1589
  ] }),
1590
+ selectionSummary?.items ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1591
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Items" }),
1592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Add, duplicate, remove, then click item text or images on the canvas to edit them." }),
1593
+ /* @__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: [
1594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item.title }),
1595
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1596
+ "button",
1597
+ {
1598
+ onClick: () => {
1599
+ updateSelectedItems((items) => {
1600
+ const source = items[item.index];
1601
+ if (!source) {
1602
+ return items;
1603
+ }
1604
+ const copy = [...items];
1605
+ copy.splice(item.index + 1, 0, {
1606
+ ...source,
1607
+ title: typeof source.title === "string" ? `${source.title} copy` : source.title
1608
+ });
1609
+ return copy;
1610
+ });
1611
+ },
1612
+ type: "button",
1613
+ children: "Duplicate"
1614
+ }
1615
+ ),
1616
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1617
+ "button",
1618
+ {
1619
+ onClick: () => {
1620
+ updateSelectedItems((items) => items.filter((_, index) => index !== item.index));
1621
+ },
1622
+ type: "button",
1623
+ children: "Remove"
1624
+ }
1625
+ )
1626
+ ] }, `${item.index}-${item.title}`)) }),
1627
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1628
+ "button",
1629
+ {
1630
+ className: "orion-builder-v2-wide-action",
1631
+ onClick: () => {
1632
+ updateSelectedItems((items) => [
1633
+ ...items,
1634
+ {
1635
+ description: "Describe this feature.",
1636
+ imageURL: "",
1637
+ title: "New feature"
1638
+ }
1639
+ ]);
1640
+ },
1641
+ type: "button",
1642
+ children: "Add item"
1643
+ }
1644
+ )
1645
+ ] }) : null,
1543
1646
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "orion-builder-v2-panel", children: [
1544
1647
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Design" }),
1545
1648
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Adjust spacing, layout, typography, color, borders, and shadows for the selected section." }),
@@ -715,6 +715,9 @@ var bindEditablePreview = (view, editor) => {
715
715
  const placeholder = element.dataset.orionPlaceholder || "";
716
716
  element.setAttribute("title", isImage ? "Click to replace image" : "Click and type to edit");
717
717
  element.style.cursor = "text";
718
+ element.addEventListener("pointerdown", () => {
719
+ editor.select?.(view.model);
720
+ }, true);
718
721
  element.addEventListener("click", (event) => {
719
722
  event.stopPropagation();
720
723
  editor.select?.(view.model);
@@ -829,6 +832,24 @@ var registerProjectDynamicComponents = (editor, adapter) => {
829
832
 
830
833
  // src/builder-v2/editor/GrapesPageEditor.tsx
831
834
  import { jsx, jsxs } from "react/jsx-runtime";
835
+ var decodeHtmlAttribute3 = (value) => {
836
+ if (typeof value !== "string") {
837
+ return "";
838
+ }
839
+ return value.replace(/&amp;/g, "&").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
840
+ };
841
+ var parseItemsJson = (value) => {
842
+ const decoded = decodeHtmlAttribute3(value);
843
+ if (!decoded) {
844
+ return [];
845
+ }
846
+ try {
847
+ const parsed = JSON.parse(decoded);
848
+ return Array.isArray(parsed) ? parsed.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))) : [];
849
+ } catch {
850
+ return [];
851
+ }
852
+ };
832
853
  var postToParent = (payload) => {
833
854
  window.parent?.postMessage(
834
855
  {
@@ -1029,6 +1050,7 @@ function GrapesPageEditor({
1029
1050
  const editorRef = useRef(null);
1030
1051
  const autosaveTimerRef = useRef(null);
1031
1052
  const saveRef = useRef(async () => void 0);
1053
+ const selectedComponentRef = useRef(null);
1032
1054
  const [error, setError] = useState("");
1033
1055
  const [historyState, setHistoryState] = useState({ canRedo: false, canUndo: false });
1034
1056
  const [lastSavedAt, setLastSavedAt] = useState("");
@@ -1040,6 +1062,37 @@ function GrapesPageEditor({
1040
1062
  const [validationIssues, setValidationIssues] = useState([]);
1041
1063
  const editorPageBasePath = initialData?.meta?.editorPageBasePath || "/admin/pages";
1042
1064
  const pageTree = initialData?.meta?.pageTree || [];
1065
+ const summarizeSelectedComponent = (component) => {
1066
+ if (!component) {
1067
+ return null;
1068
+ }
1069
+ const attrs = component.getAttributes?.() || {};
1070
+ const componentType = String(attrs["data-orion-component"] || component.get?.("type") || "Section");
1071
+ const rawName = String(component.get?.("name") || componentType || "Section");
1072
+ const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1073
+ const items = parseItemsJson(attrs["data-orion-items-json"]).map((item, index) => ({
1074
+ index,
1075
+ title: typeof item.title === "string" && item.title.trim() ? item.title.trim() : `Item ${index + 1}`
1076
+ }));
1077
+ return {
1078
+ items: items.length > 0 ? items : void 0,
1079
+ label: label || "Section",
1080
+ type: componentType
1081
+ };
1082
+ };
1083
+ const updateSelectedItems = (updater) => {
1084
+ const component = selectedComponentRef.current;
1085
+ if (!component) {
1086
+ return;
1087
+ }
1088
+ const attrs = component.getAttributes?.() || {};
1089
+ const currentItems = parseItemsJson(attrs["data-orion-items-json"]);
1090
+ const nextItems = updater(currentItems);
1091
+ component.addAttributes?.({
1092
+ "data-orion-items-json": JSON.stringify(nextItems)
1093
+ });
1094
+ setSelectionSummary(summarizeSelectedComponent(component));
1095
+ };
1043
1096
  const updateHistoryState = (editor) => {
1044
1097
  const next = {
1045
1098
  canRedo: editor.UndoManager.hasRedo(),
@@ -1165,14 +1218,8 @@ function GrapesPageEditor({
1165
1218
  });
1166
1219
  editor.on("component:selected", (component) => {
1167
1220
  const typed = component;
1168
- const attrs = typed?.getAttributes?.() || {};
1169
- const componentType = String(attrs["data-orion-component"] || typed?.get?.("type") || "Section");
1170
- const rawName = String(typed?.get?.("name") || componentType || "Section");
1171
- const label = rawName.replace(/^orion-/, "").replace(/^xo/, "XO ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/-/g, " ").trim();
1172
- setSelectionSummary({
1173
- label: label || "Section",
1174
- type: componentType
1175
- });
1221
+ selectedComponentRef.current = typed;
1222
+ setSelectionSummary(summarizeSelectedComponent(typed));
1176
1223
  setSaveMessage("Selection ready");
1177
1224
  });
1178
1225
  setSelectedDevice(editor.getDevice() || "desktop");
@@ -1416,6 +1463,62 @@ function GrapesPageEditor({
1416
1463
  /* @__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." }),
1417
1464
  /* @__PURE__ */ jsx("div", { id: "orion-builder-v2-traits" })
1418
1465
  ] }),
1466
+ selectionSummary?.items ? /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
1467
+ /* @__PURE__ */ jsx("h2", { children: "Items" }),
1468
+ /* @__PURE__ */ jsx("p", { children: "Add, duplicate, remove, then click item text or images on the canvas to edit them." }),
1469
+ /* @__PURE__ */ jsx("div", { className: "orion-builder-v2-items", children: selectionSummary.items.map((item) => /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-item-row", children: [
1470
+ /* @__PURE__ */ jsx("span", { children: item.title }),
1471
+ /* @__PURE__ */ jsx(
1472
+ "button",
1473
+ {
1474
+ onClick: () => {
1475
+ updateSelectedItems((items) => {
1476
+ const source = items[item.index];
1477
+ if (!source) {
1478
+ return items;
1479
+ }
1480
+ const copy = [...items];
1481
+ copy.splice(item.index + 1, 0, {
1482
+ ...source,
1483
+ title: typeof source.title === "string" ? `${source.title} copy` : source.title
1484
+ });
1485
+ return copy;
1486
+ });
1487
+ },
1488
+ type: "button",
1489
+ children: "Duplicate"
1490
+ }
1491
+ ),
1492
+ /* @__PURE__ */ jsx(
1493
+ "button",
1494
+ {
1495
+ onClick: () => {
1496
+ updateSelectedItems((items) => items.filter((_, index) => index !== item.index));
1497
+ },
1498
+ type: "button",
1499
+ children: "Remove"
1500
+ }
1501
+ )
1502
+ ] }, `${item.index}-${item.title}`)) }),
1503
+ /* @__PURE__ */ jsx(
1504
+ "button",
1505
+ {
1506
+ className: "orion-builder-v2-wide-action",
1507
+ onClick: () => {
1508
+ updateSelectedItems((items) => [
1509
+ ...items,
1510
+ {
1511
+ description: "Describe this feature.",
1512
+ imageURL: "",
1513
+ title: "New feature"
1514
+ }
1515
+ ]);
1516
+ },
1517
+ type: "button",
1518
+ children: "Add item"
1519
+ }
1520
+ )
1521
+ ] }) : null,
1419
1522
  /* @__PURE__ */ jsxs("div", { className: "orion-builder-v2-panel", children: [
1420
1523
  /* @__PURE__ */ jsx("h2", { children: "Design" }),
1421
1524
  /* @__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.57",
3
+ "version": "0.6.0-beta.59",
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",