@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.js CHANGED
@@ -1957,6 +1957,13 @@ var SCHEMA = {
1957
1957
  { attr: "direction", label: "reverseColumns", group: "advanced", control: { type: "toggle", on: "rtl" } },
1958
1958
  { attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
1959
1959
  ],
1960
+ // MJML allows a group these and `width` only: padding or a border fails strict. Its width is
1961
+ // not offered, as the canvas does not draw it: the plugin's group drops MJML's rule for it.
1962
+ "mj-group": [
1963
+ { attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN } },
1964
+ { attr: "background-color", label: "backgroundColor", group: "style", control: { type: "color" } },
1965
+ { attr: "direction", label: "reverseColumns", group: "advanced", control: { type: "toggle", on: "rtl" } }
1966
+ ],
1960
1967
  "mj-column": [
1961
1968
  { attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN }, default: "top" },
1962
1969
  { attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT } },
@@ -2315,8 +2322,12 @@ var COLUMN_RATIOS = {
2315
2322
  [1, 2, 1]
2316
2323
  ]
2317
2324
  };
2318
- function columnsOf(section) {
2319
- const children = section.components().models;
2325
+ function columnsOf(container) {
2326
+ let children = container.components().models;
2327
+ const only = children.length === 1 ? children[0] : null;
2328
+ if (tagOf2(container) === "mj-section" && only && tagOf2(only) === "mj-group") {
2329
+ children = only.components().models;
2330
+ }
2320
2331
  return children.length > 0 && children.every((c) => tagOf2(c) === "mj-column") ? children : null;
2321
2332
  }
2322
2333
  function ratioWidths(ratio) {
@@ -2379,6 +2390,7 @@ function StructureSection({
2379
2390
  }) {
2380
2391
  switch (tagOf3(component)) {
2381
2392
  case "mj-section":
2393
+ case "mj-group":
2382
2394
  return /* @__PURE__ */ jsx6(ColumnWidths, { component, labels, locked });
2383
2395
  case "mj-social":
2384
2396
  return /* @__PURE__ */ jsx6(
@@ -2833,11 +2845,11 @@ function ElementSettings({
2833
2845
  ] }),
2834
2846
  /* @__PURE__ */ jsx7(StructureSection, { editor, component, labels, locked }),
2835
2847
  GROUPS.map((group) => {
2836
- const inGroup = visible.filter((f) => f.group === group);
2837
- if (inGroup.length === 0) return null;
2848
+ const inGroup2 = visible.filter((f) => f.group === group);
2849
+ if (inGroup2.length === 0) return null;
2838
2850
  return /* @__PURE__ */ jsxs6("section", { className: "border-b border-panel-border/60 py-2", children: [
2839
2851
  /* @__PURE__ */ jsx7("h3", { className: "px-3 pb-1 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase", children: groupName(group, labels) }),
2840
- inGroup.map(row)
2852
+ inGroup2.map(row)
2841
2853
  ] }, group);
2842
2854
  }),
2843
2855
  advancedCount > 0 && /* @__PURE__ */ jsxs6("section", { className: "py-1", children: [
@@ -3146,7 +3158,9 @@ function registerBlocks(editor, blocks, category, media) {
3146
3158
  category,
3147
3159
  media: block.media ?? media,
3148
3160
  content: withClosedTags(block.mjml),
3149
- attributes: tileAttributes(block.id)
3161
+ attributes: tileAttributes(block.id),
3162
+ // Selected once dropped, as grapesjs-mjml's own blocks are, so its Settings open.
3163
+ select: true
3150
3164
  });
3151
3165
  }
3152
3166
  }
@@ -3189,6 +3203,15 @@ function builtinBlock(labels) {
3189
3203
  }
3190
3204
  var CANVAS_CSS = 'img:not([src]), img[src=""] { min-height: 120px; background: #f4f4f5; outline: 1px dashed #a1a1aa; outline-offset: -1px; }';
3191
3205
 
3206
+ // src/components/editor/settings-follow.ts
3207
+ function clickShowsSettings(selected, target) {
3208
+ return !!selected && !!target && selected.contains(target);
3209
+ }
3210
+ function dropShowsSettings(added, selected) {
3211
+ const first = Array.isArray(added) ? added[0] : added;
3212
+ return first !== void 0 && first === selected;
3213
+ }
3214
+
3192
3215
  // src/components/editor/table-type.ts
3193
3216
  import { isSafeLink as isSafeLink2 } from "@mjml-agent-editor/core";
3194
3217
  var DROPPED = "script, style, link, iframe, frame, object, embed, applet, base, meta, animate, set, svg, math, template";
@@ -3284,12 +3307,17 @@ function fixPluginTypes(editor) {
3284
3307
  for (const type of editor.Components.getTypes()) {
3285
3308
  if (!type.id.startsWith("mj-") || type.id === "mj-table") continue;
3286
3309
  const base = type.model.prototype.getAttrToHTML;
3310
+ const view = type.view.prototype;
3287
3311
  editor.Components.addType(type.id, {
3312
+ ...type.id === "mj-column" && { view: groupedColumnView(view) },
3313
+ ...type.id === "mj-group" && { view: redrawnGroupView(view) },
3288
3314
  model: {
3289
3315
  defaults: {
3290
3316
  "style-default": {},
3291
3317
  ...type.id === "mj-column" && {
3292
- droppable: (source) => COLUMN_CHILDREN.has(String(source.get("tagName")))
3318
+ droppable: (source) => COLUMN_CHILDREN.has(String(source.get("tagName"))),
3319
+ // The plugin moves a column only within sections, so a group's could not even swap.
3320
+ draggable: '[data-gjs-type="mj-section"], [data-gjs-type="mj-group"]'
3293
3321
  },
3294
3322
  // GrapesJS's own is an SVG's markup, which the canvas wrote into `src` and saved.
3295
3323
  ...type.id === "mj-image" && { src: "" }
@@ -3306,6 +3334,41 @@ function fixPluginTypes(editor) {
3306
3334
  });
3307
3335
  }
3308
3336
  }
3337
+ var inGroup = (column) => column.parent()?.get("type") === "mj-group";
3338
+ function groupedColumnView(base) {
3339
+ return {
3340
+ getMjmlTemplate() {
3341
+ if (!inGroup(this.model)) return base.getMjmlTemplate.call(this);
3342
+ const siblings = "<mj-column></mj-column>".repeat(
3343
+ Math.max((this.model.collection?.length ?? 1) - 1, 0)
3344
+ );
3345
+ const width = (c) => {
3346
+ const value = c?.getAttributes().width;
3347
+ return typeof value === "string" && value ? ` width="${value.replace(/"/g, "")}"` : "";
3348
+ };
3349
+ let body = this.model;
3350
+ while (body && body.get("type") !== "mj-body") body = body.parent();
3351
+ return {
3352
+ start: `<mjml><mj-body${width(body)}><mj-section><mj-group${width(this.model.parent())}>`,
3353
+ end: `${siblings}</mj-group></mj-section></mj-body></mjml>`
3354
+ };
3355
+ },
3356
+ getTemplateFromEl(container) {
3357
+ if (!inGroup(this.model)) return base.getTemplateFromEl.call(this, container);
3358
+ return container.firstChild?.querySelector(
3359
+ "div > table > tbody > tr > td > div > div"
3360
+ ) ?? null;
3361
+ }
3362
+ };
3363
+ }
3364
+ function redrawnGroupView(base) {
3365
+ return {
3366
+ init() {
3367
+ base.init.call(this);
3368
+ this.listenTo(this.model.components(), "add remove", this.render);
3369
+ }
3370
+ };
3371
+ }
3309
3372
 
3310
3373
  // src/components/editor/EmailEditor.tsx
3311
3374
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
@@ -3431,7 +3494,6 @@ function EmailEditor({
3431
3494
  requestAnimationFrame(() => blockSearchRef.current?.focus());
3432
3495
  };
3433
3496
  const [composeTarget, setComposeTarget] = useState5(null);
3434
- const selectionSource = useRef5("other");
3435
3497
  const editorRef = useRef5(null);
3436
3498
  const { running: turnRunning, locked: readOnly, setLocked } = useAgentTurn();
3437
3499
  const readOnlyRef = useRef5(false);
@@ -3446,9 +3508,10 @@ function EmailEditor({
3446
3508
  setLocked(false);
3447
3509
  };
3448
3510
  }, [turn, setLocked]);
3511
+ const inert = readOnly || loading;
3449
3512
  const wasLocked = useRef5(false);
3450
3513
  useEffect5(() => {
3451
- if (readOnly) {
3514
+ if (inert) {
3452
3515
  wasLocked.current = true;
3453
3516
  return;
3454
3517
  }
@@ -3463,7 +3526,7 @@ function EmailEditor({
3463
3526
  cancelAnimationFrame(frame);
3464
3527
  root.style.overflow = overflow;
3465
3528
  };
3466
- }, [readOnly]);
3529
+ }, [inert]);
3467
3530
  const [crumbs, setCrumbs] = useState5([]);
3468
3531
  const [picker, setPicker] = useState5(
3469
3532
  null
@@ -3565,7 +3628,7 @@ function EmailEditor({
3565
3628
  fixPluginTypes(editor);
3566
3629
  registerBlocks(editor, blocks, labels.blockCategorySections, SECTION_SVG);
3567
3630
  setupLayerNames(editor, labels);
3568
- const STRUCTURE = /* @__PURE__ */ new Set(["mj-wrapper", "mj-section", "mj-column"]);
3631
+ const STRUCTURE = /* @__PURE__ */ new Set(["mj-wrapper", "mj-section", "mj-group", "mj-column"]);
3569
3632
  const showBodyInLayers = () => {
3570
3633
  const body = findByTag("mj-body");
3571
3634
  if (!body) return;
@@ -3586,23 +3649,24 @@ function EmailEditor({
3586
3649
  decorate(c);
3587
3650
  });
3588
3651
  editor.on("load", () => {
3589
- const body = editor.Canvas.getBody();
3590
- body?.addEventListener(
3591
- "mousedown",
3592
- () => {
3593
- selectionSource.current = "canvas";
3594
- },
3595
- true
3652
+ editor.Canvas.getBody()?.addEventListener(
3653
+ "click",
3654
+ (e) => setTimeout(() => {
3655
+ if (clickShowsSettings(editor.getSelected()?.getEl(), e.target)) {
3656
+ setSidebarView("settings");
3657
+ }
3658
+ })
3596
3659
  );
3597
3660
  editor.Canvas.getDocument()?.addEventListener("keydown", onBlockSearchKey);
3598
3661
  showBodyInLayers();
3599
3662
  });
3600
3663
  editor.on("component:selected", (c) => {
3601
3664
  keepStructureOpen();
3602
- if (selectionSource.current === "canvas") setSidebarView("settings");
3603
- selectionSource.current = "other";
3604
3665
  buildCrumbs(c);
3605
3666
  });
3667
+ editor.on("block:drag:stop", (added) => {
3668
+ if (dropShowsSettings(added, editor.getSelected())) setSidebarView("settings");
3669
+ });
3606
3670
  editor.on("component:deselected", () => {
3607
3671
  buildCrumbs(editor.getSelected());
3608
3672
  });
@@ -3851,9 +3915,14 @@ function EmailEditor({
3851
3915
  "div",
3852
3916
  {
3853
3917
  className: "relative flex h-full min-h-0 flex-1",
3854
- inert: readOnly,
3918
+ inert,
3855
3919
  onKeyDown: (e) => onBlockSearchKey(e.nativeEvent),
3856
3920
  children: [
3921
+ loading && /* @__PURE__ */ jsxs7("div", { className: "absolute inset-0 z-30 flex items-center justify-center gap-2 bg-white/70 text-sm text-zinc-500", children: [
3922
+ /* @__PURE__ */ jsx8(Spinner, {}),
3923
+ " ",
3924
+ labels.loadingEditor
3925
+ ] }),
3857
3926
  readOnly && /* @__PURE__ */ jsx8("div", { className: "absolute inset-0 z-30 flex cursor-not-allowed items-start justify-center bg-white/20 pt-3", children: /* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-2 rounded-full bg-panel px-3 py-1 text-xs text-panel-fg shadow", children: [
3858
3927
  /* @__PURE__ */ jsx8(Spinner, {}),
3859
3928
  " ",
@@ -3864,7 +3933,6 @@ function EmailEditor({
3864
3933
  {
3865
3934
  view: sidebarView,
3866
3935
  onViewChange: setSidebarView,
3867
- markLayersSource: () => selectionSource.current = "layers",
3868
3936
  blockSearchRef
3869
3937
  }
3870
3938
  ),
@@ -3895,12 +3963,7 @@ function EmailEditor({
3895
3963
  editorRef.current?.AssetManager.close();
3896
3964
  }
3897
3965
  }
3898
- ),
3899
- loading && /* @__PURE__ */ jsxs7("div", { className: "absolute inset-0 z-10 flex items-center justify-center gap-2 bg-white/70 text-sm text-zinc-500", children: [
3900
- /* @__PURE__ */ jsx8(Spinner, {}),
3901
- " ",
3902
- labels.loadingEditor
3903
- ] })
3966
+ )
3904
3967
  ] }),
3905
3968
  /* @__PURE__ */ jsx8(Breadcrumb, { crumbs, onSelect: (c) => editorRef.current?.select(c) })
3906
3969
  ] })
@@ -3913,7 +3976,6 @@ function EmailEditor({
3913
3976
  function LeftSidebar({
3914
3977
  view,
3915
3978
  onViewChange,
3916
- markLayersSource,
3917
3979
  blockSearchRef
3918
3980
  }) {
3919
3981
  const editor = useEditorMaybe2();
@@ -4021,36 +4083,29 @@ function LeftSidebar({
4021
4083
  ] })
4022
4084
  }
4023
4085
  ) }),
4024
- /* @__PURE__ */ jsxs7(
4025
- "div",
4026
- {
4027
- className: cn("flex min-h-0 flex-1 flex-col", view !== "layers" && "hidden"),
4028
- onMouseDownCapture: markLayersSource,
4029
- children: [
4030
- /* @__PURE__ */ jsxs7("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: [
4031
- /* @__PURE__ */ jsx8("span", { children: labels.expandAll }),
4032
- /* @__PURE__ */ jsx8(
4033
- Switch,
4034
- {
4035
- checked: layersExpanded,
4036
- onCheckedChange: toggleLayers,
4037
- className: "data-[state=checked]:bg-brand data-[state=unchecked]:bg-panel-border"
4038
- }
4039
- )
4040
- ] }),
4041
- /* @__PURE__ */ jsx8(
4042
- "div",
4043
- {
4044
- id: "gjs-layers",
4045
- className: "min-h-0 flex-1 overflow-y-auto",
4046
- onDoubleClickCapture: (e) => {
4047
- if (e.target.closest("[data-name]")) e.stopPropagation();
4048
- }
4049
- }
4050
- )
4051
- ]
4052
- }
4053
- )
4086
+ /* @__PURE__ */ jsxs7("div", { className: cn("flex min-h-0 flex-1 flex-col", view !== "layers" && "hidden"), children: [
4087
+ /* @__PURE__ */ jsxs7("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: [
4088
+ /* @__PURE__ */ jsx8("span", { children: labels.expandAll }),
4089
+ /* @__PURE__ */ jsx8(
4090
+ Switch,
4091
+ {
4092
+ checked: layersExpanded,
4093
+ onCheckedChange: toggleLayers,
4094
+ className: "data-[state=checked]:bg-brand data-[state=unchecked]:bg-panel-border"
4095
+ }
4096
+ )
4097
+ ] }),
4098
+ /* @__PURE__ */ jsx8(
4099
+ "div",
4100
+ {
4101
+ id: "gjs-layers",
4102
+ className: "min-h-0 flex-1 overflow-y-auto",
4103
+ onDoubleClickCapture: (e) => {
4104
+ if (e.target.closest("[data-name]")) e.stopPropagation();
4105
+ }
4106
+ }
4107
+ )
4108
+ ] })
4054
4109
  ] });
4055
4110
  }
4056
4111
  function Breadcrumb({