react-email-studio 3.8.2 → 3.8.3

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.
package/dist/index.js CHANGED
@@ -827,6 +827,65 @@ function countBlocksInDesign(rows) {
827
827
  }, 0);
828
828
  return rows.reduce((sum, r) => sum + r.cells.reduce((s, c) => s + colBlocks(c), 0), 0);
829
829
  }
830
+ function isSingleBlockInRow(row) {
831
+ if (!row?.cells) return false;
832
+ let count = 0;
833
+ for (const col of row.cells) {
834
+ if (!Array.isArray(col)) continue;
835
+ count += col.length;
836
+ if (count > 1) return false;
837
+ }
838
+ return count === 1;
839
+ }
840
+ function resolveBlockMovePlan(rows, rowId, cellIdx, blockIndex, inner) {
841
+ const rowIndex = rows.findIndex((r) => r.id === rowId);
842
+ const row = rowIndex >= 0 ? rows[rowIndex] : void 0;
843
+ if (!row) return null;
844
+ if (inner) {
845
+ const loc = toColumnLoc(rowId, cellIdx, {
846
+ parentBlockIdx: inner.parentBlockIdx,
847
+ innerCellIdx: inner.cellIdx
848
+ });
849
+ const col2 = getColumnBlocks(rows, loc);
850
+ const idx = inner.contentIdx;
851
+ return {
852
+ mode: "column",
853
+ blockIndex: idx,
854
+ canMoveUp: idx > 0,
855
+ canMoveDown: idx < col2.length - 1
856
+ };
857
+ }
858
+ const col = row.cells?.[cellIdx];
859
+ const colLen = Array.isArray(col) ? col.length : 0;
860
+ if (colLen > 1) {
861
+ return {
862
+ mode: "column",
863
+ blockIndex,
864
+ canMoveUp: blockIndex > 0,
865
+ canMoveDown: blockIndex < colLen - 1
866
+ };
867
+ }
868
+ if (colLen === 1 && (isRootContentRow(row) || isSingleBlockInRow(row))) {
869
+ return {
870
+ mode: "row",
871
+ canMoveUp: rowIndex > 0,
872
+ canMoveDown: rowIndex < rows.length - 1
873
+ };
874
+ }
875
+ if (colLen === 1) {
876
+ return {
877
+ mode: "row",
878
+ canMoveUp: rowIndex > 0,
879
+ canMoveDown: rowIndex < rows.length - 1
880
+ };
881
+ }
882
+ return {
883
+ mode: "column",
884
+ blockIndex,
885
+ canMoveUp: false,
886
+ canMoveDown: false
887
+ };
888
+ }
830
889
 
831
890
  // src/lib/htmlUtils.ts
832
891
  function escHtmlAttr(s) {
@@ -7444,50 +7503,64 @@ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
7444
7503
  function ContextMenu({ x, y, items, onClose, C }) {
7445
7504
  const ref = useRef4(null);
7446
7505
  useEffect5(() => {
7447
- const h = () => onClose();
7448
- window.addEventListener("mousedown", h);
7449
- window.addEventListener("keydown", h);
7506
+ const onPointerDown = (ev) => {
7507
+ if (ref.current?.contains(ev.target)) return;
7508
+ onClose();
7509
+ };
7510
+ const onKeyDown = (ev) => {
7511
+ if (ev.key === "Escape") onClose();
7512
+ };
7513
+ window.addEventListener("pointerdown", onPointerDown);
7514
+ window.addEventListener("keydown", onKeyDown);
7450
7515
  return () => {
7451
- window.removeEventListener("mousedown", h);
7452
- window.removeEventListener("keydown", h);
7516
+ window.removeEventListener("pointerdown", onPointerDown);
7517
+ window.removeEventListener("keydown", onKeyDown);
7453
7518
  };
7454
7519
  }, [onClose]);
7455
- return /* @__PURE__ */ jsx8("div", { ref, onMouseDown: (e) => e.stopPropagation(), style: { position: "fixed", left: x, top: y, zIndex: 9999, background: C.sidebar, border: `1px solid ${C.border}`, borderRadius: 7, padding: "4px 0", boxShadow: `0 8px 32px rgba(0,0,0,.4)`, minWidth: 160 }, children: items.map((item, i) => item.sep ? /* @__PURE__ */ jsx8("div", { style: { height: 1, background: C.border, margin: "3px 0" } }, i) : /* @__PURE__ */ jsxs6(
7456
- "button",
7520
+ return /* @__PURE__ */ jsx8(
7521
+ "div",
7457
7522
  {
7458
- type: "button",
7459
- disabled: !!item.disabled,
7460
- onClick: () => {
7461
- if (item.disabled) return;
7462
- item.action();
7463
- onClose();
7464
- },
7465
- style: {
7466
- display: "block",
7467
- width: "100%",
7468
- background: "none",
7469
- border: "none",
7470
- padding: "7px 14px",
7471
- textAlign: "left",
7472
- cursor: item.disabled ? "not-allowed" : "pointer",
7473
- fontSize: 12,
7474
- color: item.disabled ? C.muted : item.danger ? C.danger : C.text,
7475
- fontWeight: item.bold ? 700 : 400,
7476
- opacity: item.disabled ? 0.45 : 1
7477
- },
7478
- onMouseEnter: (e) => {
7479
- if (!item.disabled) e.currentTarget.style.background = C.surface;
7480
- },
7481
- onMouseLeave: (e) => {
7482
- e.currentTarget.style.background = "none";
7483
- },
7484
- children: [
7485
- item.icon && /* @__PURE__ */ jsx8("span", { style: { marginRight: 8 }, children: item.icon }),
7486
- item.label
7487
- ]
7488
- },
7489
- i
7490
- )) });
7523
+ ref,
7524
+ onPointerDown: (e) => e.stopPropagation(),
7525
+ style: { position: "fixed", left: x, top: y, zIndex: 9999, background: C.sidebar, border: `1px solid ${C.border}`, borderRadius: 7, padding: "4px 0", boxShadow: `0 8px 32px rgba(0,0,0,.4)`, minWidth: 160 },
7526
+ children: items.map((item, i) => item.sep ? /* @__PURE__ */ jsx8("div", { style: { height: 1, background: C.border, margin: "3px 0" } }, i) : /* @__PURE__ */ jsxs6(
7527
+ "button",
7528
+ {
7529
+ type: "button",
7530
+ disabled: !!item.disabled,
7531
+ onClick: () => {
7532
+ if (item.disabled) return;
7533
+ item.action();
7534
+ onClose();
7535
+ },
7536
+ style: {
7537
+ display: "block",
7538
+ width: "100%",
7539
+ background: "none",
7540
+ border: "none",
7541
+ padding: "7px 14px",
7542
+ textAlign: "left",
7543
+ cursor: item.disabled ? "not-allowed" : "pointer",
7544
+ fontSize: 12,
7545
+ color: item.disabled ? C.muted : item.danger ? C.danger : C.text,
7546
+ fontWeight: item.bold ? 700 : 400,
7547
+ opacity: item.disabled ? 0.45 : 1
7548
+ },
7549
+ onMouseEnter: (e) => {
7550
+ if (!item.disabled) e.currentTarget.style.background = C.surface;
7551
+ },
7552
+ onMouseLeave: (e) => {
7553
+ e.currentTarget.style.background = "none";
7554
+ },
7555
+ children: [
7556
+ item.icon && /* @__PURE__ */ jsx8("span", { style: { marginRight: 8 }, children: item.icon }),
7557
+ item.label
7558
+ ]
7559
+ },
7560
+ i
7561
+ ))
7562
+ }
7563
+ );
7491
7564
  }
7492
7565
 
7493
7566
  // src/editor/overlays/ConfirmDialog.tsx
@@ -8091,10 +8164,27 @@ var ReactEmailEditorComponent = forwardRef(
8091
8164
  else dropContent(rowId, cellIdx, { kind: "duplicate", fromIdx: contentIdx });
8092
8165
  }
8093
8166
  };
8167
+ const shiftBlockFromMenu = (rowId, cellIdx, ci, dir, inner = null) => {
8168
+ const blockIndex = inner ? inner.contentIdx : ci;
8169
+ const plan = resolveBlockMovePlan(rowsRef.current, rowId, cellIdx, blockIndex, inner);
8170
+ if (!plan) return;
8171
+ if (dir < 0 && !plan.canMoveUp || dir > 0 && !plan.canMoveDown) return;
8172
+ if (plan.mode === "row") {
8173
+ moveRow(rowId, dir);
8174
+ return;
8175
+ }
8176
+ moveContent(rowId, cellIdx, plan.blockIndex, dir, inner);
8177
+ };
8094
8178
  const handleContextMenu = (e, rowId, cellIdx, ci, inner = null) => {
8179
+ e.preventDefault();
8180
+ e.stopPropagation();
8181
+ const blockIndex = inner ? inner.contentIdx : ci;
8182
+ const plan = resolveBlockMovePlan(rowsRef.current, rowId, cellIdx, blockIndex, inner);
8183
+ const canMoveUp = plan?.canMoveUp ?? false;
8184
+ const canMoveDown = plan?.canMoveDown ?? false;
8095
8185
  setCtxMenu({ x: e.clientX, y: e.clientY, items: [
8096
- { icon: "\u2191", label: "Move Up", action: () => moveContent(rowId, cellIdx, ci, -1, inner) },
8097
- { icon: "\u2193", label: "Move Down", action: () => moveContent(rowId, cellIdx, ci, 1, inner) },
8186
+ { icon: "\u2191", label: "Move Up", disabled: !canMoveUp, action: () => shiftBlockFromMenu(rowId, cellIdx, ci, -1, inner) },
8187
+ { icon: "\u2193", label: "Move Down", disabled: !canMoveDown, action: () => shiftBlockFromMenu(rowId, cellIdx, ci, 1, inner) },
8098
8188
  { sep: true },
8099
8189
  { icon: "\u29C9", label: "Duplicate", action: () => dropContent(rowId, cellIdx, inner ? { kind: "duplicate", fromIdx: inner.contentIdx, fromNested: { parentBlockIdx: inner.parentBlockIdx, innerCellIdx: inner.cellIdx } } : { kind: "duplicate", fromIdx: ci }) },
8100
8190
  { sep: true },
@@ -8104,17 +8194,14 @@ var ReactEmailEditorComponent = forwardRef(
8104
8194
  const handleLayoutContextMenu = (e, rowId) => {
8105
8195
  e.preventDefault();
8106
8196
  e.stopPropagation();
8107
- const ri = rows.findIndex((r) => r.id === rowId);
8197
+ const currentRows = rowsRef.current;
8198
+ const ri = currentRows.findIndex((r) => r.id === rowId);
8108
8199
  setSelectedRowId(rowId);
8109
8200
  setSelContentId(null);
8110
8201
  setSelMeta(null);
8111
8202
  setCtxMenu({ x: e.clientX, y: e.clientY, items: [
8112
- { icon: "\u2191", label: "Move row up", disabled: ri <= 0, action: () => {
8113
- if (ri > 0) moveRow(rowId, -1);
8114
- } },
8115
- { icon: "\u2193", label: "Move row down", disabled: ri < 0 || ri >= rows.length - 1, action: () => {
8116
- if (ri >= 0 && ri < rows.length - 1) moveRow(rowId, 1);
8117
- } },
8203
+ { icon: "\u2191", label: "Move row up", disabled: ri <= 0, action: () => moveRow(rowId, -1) },
8204
+ { icon: "\u2193", label: "Move row down", disabled: ri < 0 || ri >= currentRows.length - 1, action: () => moveRow(rowId, 1) },
8118
8205
  { sep: true },
8119
8206
  { icon: "\u29C9", label: "Duplicate row", action: () => dupRow(rowId) },
8120
8207
  { sep: true },