@mjml-agent-editor/editor 0.4.3 → 0.5.1

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/canvas.cjs CHANGED
@@ -2846,6 +2846,13 @@ var SCHEMA = {
2846
2846
  { attr: "direction", label: "reverseColumns", group: "advanced", control: { type: "toggle", on: "rtl" } },
2847
2847
  { attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
2848
2848
  ],
2849
+ // MJML allows a group these and `width` only: padding or a border fails strict. Its width is
2850
+ // not offered, as the canvas does not draw it: the plugin's group drops MJML's rule for it.
2851
+ "mj-group": [
2852
+ { attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN } },
2853
+ { attr: "background-color", label: "backgroundColor", group: "style", control: { type: "color" } },
2854
+ { attr: "direction", label: "reverseColumns", group: "advanced", control: { type: "toggle", on: "rtl" } }
2855
+ ],
2849
2856
  "mj-column": [
2850
2857
  { attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN }, default: "top" },
2851
2858
  { attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT } },
@@ -3204,8 +3211,12 @@ var COLUMN_RATIOS = {
3204
3211
  [1, 2, 1]
3205
3212
  ]
3206
3213
  };
3207
- function columnsOf(section) {
3208
- const children = section.components().models;
3214
+ function columnsOf(container) {
3215
+ let children = container.components().models;
3216
+ const only = children.length === 1 ? children[0] : null;
3217
+ if (tagOf2(container) === "mj-section" && only && tagOf2(only) === "mj-group") {
3218
+ children = only.components().models;
3219
+ }
3209
3220
  return children.length > 0 && children.every((c) => tagOf2(c) === "mj-column") ? children : null;
3210
3221
  }
3211
3222
  function ratioWidths(ratio) {
@@ -3268,6 +3279,7 @@ function StructureSection({
3268
3279
  }) {
3269
3280
  switch (tagOf3(component)) {
3270
3281
  case "mj-section":
3282
+ case "mj-group":
3271
3283
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ColumnWidths, { component, labels, locked });
3272
3284
  case "mj-social":
3273
3285
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
@@ -3722,11 +3734,11 @@ function ElementSettings({
3722
3734
  ] }),
3723
3735
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StructureSection, { editor, component, labels, locked }),
3724
3736
  GROUPS.map((group) => {
3725
- const inGroup = visible.filter((f) => f.group === group);
3726
- if (inGroup.length === 0) return null;
3737
+ const inGroup2 = visible.filter((f) => f.group === group);
3738
+ if (inGroup2.length === 0) return null;
3727
3739
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("section", { className: "border-b border-panel-border/60 py-2", children: [
3728
3740
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("h3", { className: "px-3 pb-1 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase", children: groupName(group, labels) }),
3729
- inGroup.map(row)
3741
+ inGroup2.map(row)
3730
3742
  ] }, group);
3731
3743
  }),
3732
3744
  advancedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("section", { className: "py-1", children: [
@@ -4035,7 +4047,9 @@ function registerBlocks(editor, blocks, category, media) {
4035
4047
  category,
4036
4048
  media: block.media ?? media,
4037
4049
  content: withClosedTags(block.mjml),
4038
- attributes: tileAttributes(block.id)
4050
+ attributes: tileAttributes(block.id),
4051
+ // Selected once dropped, as grapesjs-mjml's own blocks are, so its Settings open.
4052
+ select: true
4039
4053
  });
4040
4054
  }
4041
4055
  }
@@ -4078,6 +4092,15 @@ function builtinBlock(labels) {
4078
4092
  }
4079
4093
  var CANVAS_CSS = 'img:not([src]), img[src=""] { min-height: 120px; background: #f4f4f5; outline: 1px dashed #a1a1aa; outline-offset: -1px; }';
4080
4094
 
4095
+ // src/components/editor/settings-follow.ts
4096
+ function clickShowsSettings(selected, target) {
4097
+ return !!selected && !!target && selected.contains(target);
4098
+ }
4099
+ function dropShowsSettings(added, selected) {
4100
+ const first = Array.isArray(added) ? added[0] : added;
4101
+ return first !== void 0 && first === selected;
4102
+ }
4103
+
4081
4104
  // src/components/editor/table-type.ts
4082
4105
  var import_core3 = require("@mjml-agent-editor/core");
4083
4106
  var DROPPED = "script, style, link, iframe, frame, object, embed, applet, base, meta, animate, set, svg, math, template";
@@ -4173,12 +4196,17 @@ function fixPluginTypes(editor) {
4173
4196
  for (const type of editor.Components.getTypes()) {
4174
4197
  if (!type.id.startsWith("mj-") || type.id === "mj-table") continue;
4175
4198
  const base = type.model.prototype.getAttrToHTML;
4199
+ const view = type.view.prototype;
4176
4200
  editor.Components.addType(type.id, {
4201
+ ...type.id === "mj-column" && { view: groupedColumnView(view) },
4202
+ ...type.id === "mj-group" && { view: redrawnGroupView(view) },
4177
4203
  model: {
4178
4204
  defaults: {
4179
4205
  "style-default": {},
4180
4206
  ...type.id === "mj-column" && {
4181
- droppable: (source) => COLUMN_CHILDREN.has(String(source.get("tagName")))
4207
+ droppable: (source) => COLUMN_CHILDREN.has(String(source.get("tagName"))),
4208
+ // The plugin moves a column only within sections, so a group's could not even swap.
4209
+ draggable: '[data-gjs-type="mj-section"], [data-gjs-type="mj-group"]'
4182
4210
  },
4183
4211
  // GrapesJS's own is an SVG's markup, which the canvas wrote into `src` and saved.
4184
4212
  ...type.id === "mj-image" && { src: "" }
@@ -4195,6 +4223,41 @@ function fixPluginTypes(editor) {
4195
4223
  });
4196
4224
  }
4197
4225
  }
4226
+ var inGroup = (column) => column.parent()?.get("type") === "mj-group";
4227
+ function groupedColumnView(base) {
4228
+ return {
4229
+ getMjmlTemplate() {
4230
+ if (!inGroup(this.model)) return base.getMjmlTemplate.call(this);
4231
+ const siblings = "<mj-column></mj-column>".repeat(
4232
+ Math.max((this.model.collection?.length ?? 1) - 1, 0)
4233
+ );
4234
+ const width = (c) => {
4235
+ const value = c?.getAttributes().width;
4236
+ return typeof value === "string" && value ? ` width="${value.replace(/"/g, "")}"` : "";
4237
+ };
4238
+ let body = this.model;
4239
+ while (body && body.get("type") !== "mj-body") body = body.parent();
4240
+ return {
4241
+ start: `<mjml><mj-body${width(body)}><mj-section><mj-group${width(this.model.parent())}>`,
4242
+ end: `${siblings}</mj-group></mj-section></mj-body></mjml>`
4243
+ };
4244
+ },
4245
+ getTemplateFromEl(container) {
4246
+ if (!inGroup(this.model)) return base.getTemplateFromEl.call(this, container);
4247
+ return container.firstChild?.querySelector(
4248
+ "div > table > tbody > tr > td > div > div"
4249
+ ) ?? null;
4250
+ }
4251
+ };
4252
+ }
4253
+ function redrawnGroupView(base) {
4254
+ return {
4255
+ init() {
4256
+ base.init.call(this);
4257
+ this.listenTo(this.model.components(), "add remove", this.render);
4258
+ }
4259
+ };
4260
+ }
4198
4261
 
4199
4262
  // src/components/editor/EmailEditor.tsx
4200
4263
  var import_jsx_runtime17 = require("react/jsx-runtime");
@@ -4320,7 +4383,6 @@ function EmailEditor({
4320
4383
  requestAnimationFrame(() => blockSearchRef.current?.focus());
4321
4384
  };
4322
4385
  const [composeTarget, setComposeTarget] = (0, import_react10.useState)(null);
4323
- const selectionSource = (0, import_react10.useRef)("other");
4324
4386
  const editorRef = (0, import_react10.useRef)(null);
4325
4387
  const { running: turnRunning, locked: readOnly, setLocked } = useAgentTurn();
4326
4388
  const readOnlyRef = (0, import_react10.useRef)(false);
@@ -4335,9 +4397,10 @@ function EmailEditor({
4335
4397
  setLocked(false);
4336
4398
  };
4337
4399
  }, [turn, setLocked]);
4400
+ const inert = readOnly || loading;
4338
4401
  const wasLocked = (0, import_react10.useRef)(false);
4339
4402
  (0, import_react10.useEffect)(() => {
4340
- if (readOnly) {
4403
+ if (inert) {
4341
4404
  wasLocked.current = true;
4342
4405
  return;
4343
4406
  }
@@ -4352,7 +4415,7 @@ function EmailEditor({
4352
4415
  cancelAnimationFrame(frame);
4353
4416
  root.style.overflow = overflow;
4354
4417
  };
4355
- }, [readOnly]);
4418
+ }, [inert]);
4356
4419
  const [crumbs, setCrumbs] = (0, import_react10.useState)([]);
4357
4420
  const [picker, setPicker] = (0, import_react10.useState)(
4358
4421
  null
@@ -4454,7 +4517,7 @@ function EmailEditor({
4454
4517
  fixPluginTypes(editor);
4455
4518
  registerBlocks(editor, blocks, labels.blockCategorySections, SECTION_SVG);
4456
4519
  setupLayerNames(editor, labels);
4457
- const STRUCTURE = /* @__PURE__ */ new Set(["mj-wrapper", "mj-section", "mj-column"]);
4520
+ const STRUCTURE = /* @__PURE__ */ new Set(["mj-wrapper", "mj-section", "mj-group", "mj-column"]);
4458
4521
  const showBodyInLayers = () => {
4459
4522
  const body = findByTag("mj-body");
4460
4523
  if (!body) return;
@@ -4475,23 +4538,24 @@ function EmailEditor({
4475
4538
  decorate(c);
4476
4539
  });
4477
4540
  editor.on("load", () => {
4478
- const body = editor.Canvas.getBody();
4479
- body?.addEventListener(
4480
- "mousedown",
4481
- () => {
4482
- selectionSource.current = "canvas";
4483
- },
4484
- true
4541
+ editor.Canvas.getBody()?.addEventListener(
4542
+ "click",
4543
+ (e) => setTimeout(() => {
4544
+ if (clickShowsSettings(editor.getSelected()?.getEl(), e.target)) {
4545
+ setSidebarView("settings");
4546
+ }
4547
+ })
4485
4548
  );
4486
4549
  editor.Canvas.getDocument()?.addEventListener("keydown", onBlockSearchKey);
4487
4550
  showBodyInLayers();
4488
4551
  });
4489
4552
  editor.on("component:selected", (c) => {
4490
4553
  keepStructureOpen();
4491
- if (selectionSource.current === "canvas") setSidebarView("settings");
4492
- selectionSource.current = "other";
4493
4554
  buildCrumbs(c);
4494
4555
  });
4556
+ editor.on("block:drag:stop", (added) => {
4557
+ if (dropShowsSettings(added, editor.getSelected())) setSidebarView("settings");
4558
+ });
4495
4559
  editor.on("component:deselected", () => {
4496
4560
  buildCrumbs(editor.getSelected());
4497
4561
  });
@@ -4740,9 +4804,14 @@ function EmailEditor({
4740
4804
  "div",
4741
4805
  {
4742
4806
  className: "relative flex h-full min-h-0 flex-1",
4743
- inert: readOnly,
4807
+ inert,
4744
4808
  onKeyDown: (e) => onBlockSearchKey(e.nativeEvent),
4745
4809
  children: [
4810
+ loading && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "absolute inset-0 z-30 flex items-center justify-center gap-2 bg-white/70 text-sm text-zinc-500", children: [
4811
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, {}),
4812
+ " ",
4813
+ labels.loadingEditor
4814
+ ] }),
4746
4815
  readOnly && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "absolute inset-0 z-30 flex cursor-not-allowed items-start justify-center bg-white/20 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "flex items-center gap-2 rounded-full bg-panel px-3 py-1 text-xs text-panel-fg shadow", children: [
4747
4816
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, {}),
4748
4817
  " ",
@@ -4753,7 +4822,6 @@ function EmailEditor({
4753
4822
  {
4754
4823
  view: sidebarView,
4755
4824
  onViewChange: setSidebarView,
4756
- markLayersSource: () => selectionSource.current = "layers",
4757
4825
  blockSearchRef
4758
4826
  }
4759
4827
  ),
@@ -4784,12 +4852,7 @@ function EmailEditor({
4784
4852
  editorRef.current?.AssetManager.close();
4785
4853
  }
4786
4854
  }
4787
- ),
4788
- loading && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "absolute inset-0 z-10 flex items-center justify-center gap-2 bg-white/70 text-sm text-zinc-500", children: [
4789
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, {}),
4790
- " ",
4791
- labels.loadingEditor
4792
- ] })
4855
+ )
4793
4856
  ] }),
4794
4857
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Breadcrumb, { crumbs, onSelect: (c) => editorRef.current?.select(c) })
4795
4858
  ] })
@@ -4802,7 +4865,6 @@ function EmailEditor({
4802
4865
  function LeftSidebar({
4803
4866
  view,
4804
4867
  onViewChange,
4805
- markLayersSource,
4806
4868
  blockSearchRef
4807
4869
  }) {
4808
4870
  const editor = (0, import_react9.useEditorMaybe)();
@@ -4910,36 +4972,29 @@ function LeftSidebar({
4910
4972
  ] })
4911
4973
  }
4912
4974
  ) }),
4913
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4914
- "div",
4915
- {
4916
- className: cn("flex min-h-0 flex-1 flex-col", view !== "layers" && "hidden"),
4917
- onMouseDownCapture: markLayersSource,
4918
- children: [
4919
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center justify-between gap-2 border-b border-panel-border px-3 py-2 text-xs text-panel-muted-fg", children: [
4920
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: labels.expandAll }),
4921
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4922
- Switch,
4923
- {
4924
- checked: layersExpanded,
4925
- onCheckedChange: toggleLayers,
4926
- className: "data-[state=checked]:bg-brand data-[state=unchecked]:bg-panel-border"
4927
- }
4928
- )
4929
- ] }),
4930
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4931
- "div",
4932
- {
4933
- id: "gjs-layers",
4934
- className: "min-h-0 flex-1 overflow-y-auto",
4935
- onDoubleClickCapture: (e) => {
4936
- if (e.target.closest("[data-name]")) e.stopPropagation();
4937
- }
4938
- }
4939
- )
4940
- ]
4941
- }
4942
- )
4975
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: cn("flex min-h-0 flex-1 flex-col", view !== "layers" && "hidden"), children: [
4976
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center justify-between gap-2 border-b border-panel-border px-3 py-2 text-xs text-panel-muted-fg", children: [
4977
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: labels.expandAll }),
4978
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4979
+ Switch,
4980
+ {
4981
+ checked: layersExpanded,
4982
+ onCheckedChange: toggleLayers,
4983
+ className: "data-[state=checked]:bg-brand data-[state=unchecked]:bg-panel-border"
4984
+ }
4985
+ )
4986
+ ] }),
4987
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4988
+ "div",
4989
+ {
4990
+ id: "gjs-layers",
4991
+ className: "min-h-0 flex-1 overflow-y-auto",
4992
+ onDoubleClickCapture: (e) => {
4993
+ if (e.target.closest("[data-name]")) e.stopPropagation();
4994
+ }
4995
+ }
4996
+ )
4997
+ ] })
4943
4998
  ] });
4944
4999
  }
4945
5000
  function Breadcrumb({