@mjml-agent-editor/editor 0.1.17 → 0.2.0

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
@@ -37,9 +37,9 @@ module.exports = __toCommonJS(canvas_exports);
37
37
 
38
38
  // src/components/editor/EmailEditor.tsx
39
39
  var import_grapesjs = __toESM(require("grapesjs"), 1);
40
- var import_react6 = __toESM(require("@grapesjs/react"), 1);
40
+ var import_react9 = __toESM(require("@grapesjs/react"), 1);
41
41
  var import_grapesjs_mjml = __toESM(require("grapesjs-mjml"), 1);
42
- var import_react7 = require("react");
42
+ var import_react10 = require("react");
43
43
  var import_core2 = require("@mjml-agent-editor/core");
44
44
 
45
45
  // src/stores.tsx
@@ -63,9 +63,201 @@ var DEFAULT_LABELS = {
63
63
  exportHtml: "Export HTML",
64
64
  exportMjml: "Export MJML",
65
65
  copied: (kind) => `${kind} copied.`,
66
+ tabBlocks: "Blocks",
67
+ tabSettings: "Settings",
68
+ tabLayers: "Layers",
66
69
  searchBlocks: "Search blocks\u2026",
70
+ noBlocksMatch: "No blocks match.",
71
+ blockCategoryLayout: "Layout",
72
+ blockCategoryContent: "Content",
73
+ blockLabels: {
74
+ "mj-1-column": "1 column",
75
+ "mj-2-columns": "2 columns",
76
+ "mj-3-columns": "3 columns",
77
+ "mj-text": "Text",
78
+ "mj-image": "Image",
79
+ "mj-button": "Button",
80
+ "mj-divider": "Divider",
81
+ "mj-spacer": "Spacer",
82
+ "mj-social-group": "Social links",
83
+ "mj-navbar": "Navigation"
84
+ },
85
+ elementNames: {
86
+ "mj-wrapper": "Section group",
87
+ "mj-section": "Section",
88
+ "mj-column": "Column",
89
+ "mj-group": "Column group",
90
+ "mj-text": "Text",
91
+ "mj-button": "Button",
92
+ "mj-image": "Image",
93
+ "mj-divider": "Divider",
94
+ "mj-spacer": "Spacer",
95
+ "mj-hero": "Hero",
96
+ "mj-navbar": "Navigation",
97
+ "mj-navbar-link": "Navigation link",
98
+ "mj-social": "Social links",
99
+ "mj-social-element": "Social link",
100
+ "mj-raw": "Raw HTML"
101
+ },
67
102
  expandAll: "Expand all",
68
103
  selectAnElement: "Select an element\u2026",
104
+ settingsEmpty: "Select something in the email to edit it.",
105
+ emailSettings: "Email settings",
106
+ loadingEditor: "Loading editor\u2026",
107
+ commentOnElement: "Comment on element / section",
108
+ groupContent: "Content",
109
+ groupLayout: "Layout",
110
+ groupSpacing: "Spacing",
111
+ groupStyle: "Style",
112
+ groupAdvanced: "Advanced",
113
+ otherAttributes: "Other attributes",
114
+ removeAttribute: "Remove",
115
+ resetToDefault: "Reset to default",
116
+ selectParent: "Select parent",
117
+ duplicate: "Duplicate",
118
+ deleteElement: "Delete",
119
+ multipleSelected: (count) => `${count} elements selected \u2014 select one to edit its settings.`,
120
+ editEachSide: "Edit each side",
121
+ increase: "Increase",
122
+ decrease: "Decrease",
123
+ changeUnit: (unit) => `Unit: ${unit}. Click to change`,
124
+ noUnit: "\xD7",
125
+ sidesVertical: "Top and bottom",
126
+ sidesHorizontal: "Left and right",
127
+ sideTop: "Top",
128
+ sideRight: "Right",
129
+ sideBottom: "Bottom",
130
+ sideLeft: "Left",
131
+ linkTypeProfile: "Your profile",
132
+ linkTypeShare: "Share this page",
133
+ allScreenSizes: "Changes apply to every screen size.",
134
+ columnWidths: "Column widths",
135
+ columnRatio: (ratio) => `Columns ${ratio}`,
136
+ socialNetworks: "Networks",
137
+ addNetwork: "Add network",
138
+ navbarLinks: "Links",
139
+ addLink: "Add link",
140
+ newLinkText: "Link",
141
+ inspectorLabels: {
142
+ // Fields
143
+ contentWidth: "Width",
144
+ pageBackground: "Page background",
145
+ "pageBackground.help": "Fills the area around and behind the email.",
146
+ fullWidth: "Full-width background",
147
+ "fullWidth.help": "The background reaches the window edges; the content keeps the email width.",
148
+ padding: "Padding",
149
+ backgroundColor: "Background",
150
+ backgroundImage: "Background image",
151
+ backgroundSize: "Image size",
152
+ backgroundRepeat: "Repeat image",
153
+ backgroundPosition: "Position",
154
+ cornerRadius: "Corners",
155
+ gap: "Space between sections",
156
+ border: "Border",
157
+ alignColumns: "Align columns",
158
+ "alignColumns.help": "Where the columns sit when they don't fill the row.",
159
+ reverseColumns: "Reverse column order",
160
+ "reverseColumns.help": "Swaps the columns on desktop; on phones they still stack in their original order.",
161
+ verticalAlign: "Vertical",
162
+ width: "Width",
163
+ innerBackground: "Inner background",
164
+ "innerBackground.help": "Fills only the area inside the padding, so it needs padding to show.",
165
+ innerCornerRadius: "Inner corners",
166
+ innerBorder: "Inner border",
167
+ align: "Alignment",
168
+ font: "Font",
169
+ fontSize: "Text size",
170
+ fontWeight: "Weight",
171
+ textColor: "Text color",
172
+ lineHeight: "Line height",
173
+ letterSpacing: "Letter spacing",
174
+ textTransform: "Capitalization",
175
+ blockBackground: "Block background",
176
+ "blockBackground.help": "Fills the whole block, including its padding.",
177
+ link: "Link",
178
+ buttonColor: "Button color",
179
+ buttonPadding: "Button padding",
180
+ "buttonPadding.help": "Space between the text and the button's edge; this sets the button's size.",
181
+ spaceAround: "Space around",
182
+ "spaceAround.help": "Empty space outside the button, between it and what is next to it.",
183
+ opensIn: "Opens in",
184
+ tooltip: "Tooltip",
185
+ rel: "Link relationship",
186
+ image: "Image",
187
+ altText: "Alt text",
188
+ "altText.help": "Read by screen readers and shown when images are blocked.",
189
+ fluidOnMobile: "Full width on mobile",
190
+ "fluidOnMobile.help": "On phones the image stretches across the screen, whatever its width here.",
191
+ lineColor: "Line color",
192
+ thickness: "Thickness",
193
+ lineStyle: "Line style",
194
+ height: "Height",
195
+ socialMode: "Direction",
196
+ iconSize: "Icon size",
197
+ iconSpacing: "Space around each icon",
198
+ network: "Network",
199
+ // Brand names are the same in every language and are not keys; these two are words.
200
+ networkWebsite: "Website",
201
+ networkCustom: "Custom",
202
+ customIcon: "Custom icon",
203
+ iconColor: "Icon color",
204
+ iconPosition: "Icon side",
205
+ heroMode: "Sizing",
206
+ "heroMode.help": "Fixed keeps the height you set; Fit image follows the background image's proportions.",
207
+ backgroundWidth: "Image width",
208
+ "backgroundWidth.help": "The background image's real size in pixels, so the hero keeps its proportions.",
209
+ backgroundHeight: "Image height",
210
+ fallbackColor: "Fallback color",
211
+ "fallbackColor.help": "Shown while the image loads, or when images are blocked.",
212
+ contentPadding: "Content padding",
213
+ contentBackground: "Content background",
214
+ hamburger: "Collapse into a menu on mobile",
215
+ "hamburger.help": "On phones the links fold into a menu button.",
216
+ menuIconColor: "Menu icon color",
217
+ menuIconSize: "Menu icon size",
218
+ menuIconAlign: "Menu icon align",
219
+ baseUrl: "Link prefix",
220
+ "baseUrl.help": "Added in front of every link in the bar, full addresses included, so links then give only the path.",
221
+ // Options
222
+ alignLeft: "Left",
223
+ alignCenter: "Center",
224
+ alignRight: "Right",
225
+ alignJustify: "Justify",
226
+ alignTop: "Top",
227
+ alignMiddle: "Middle",
228
+ alignBottom: "Bottom",
229
+ weightRegular: "Regular",
230
+ weightMedium: "Medium",
231
+ weightSemibold: "Semibold",
232
+ weightBold: "Bold",
233
+ caseNone: "As typed",
234
+ caseUpper: "UPPERCASE",
235
+ caseLower: "lowercase",
236
+ caseCapitalize: "Capitalize Each Word",
237
+ targetNewTab: "New tab",
238
+ targetSameTab: "Same tab",
239
+ sizeCover: "Fill",
240
+ sizeContain: "Fit",
241
+ sizeAuto: "Original",
242
+ repeatTile: "Tile",
243
+ repeatOnce: "Once",
244
+ positionTopLeft: "Top left",
245
+ positionTopCenter: "Top",
246
+ positionTopRight: "Top right",
247
+ positionCenterLeft: "Left",
248
+ positionCenter: "Center",
249
+ positionCenterRight: "Right",
250
+ positionBottomLeft: "Bottom left",
251
+ positionBottomCenter: "Bottom",
252
+ positionBottomRight: "Bottom right",
253
+ lineSolid: "Solid",
254
+ lineDashed: "Dashed",
255
+ lineDotted: "Dotted",
256
+ modeHorizontal: "In a row",
257
+ modeVertical: "Stacked",
258
+ heroFixed: "Fixed",
259
+ heroFluid: "Fit image"
260
+ },
69
261
  attributes: "Attributes",
70
262
  style: "Style",
71
263
  rteFont: "Font",
@@ -132,7 +324,10 @@ function mergeLabels(overrides) {
132
324
  return {
133
325
  ...DEFAULT_LABELS,
134
326
  ...overrides,
135
- toolLabels: { ...DEFAULT_LABELS.toolLabels, ...overrides.toolLabels }
327
+ toolLabels: { ...DEFAULT_LABELS.toolLabels, ...overrides.toolLabels },
328
+ blockLabels: { ...DEFAULT_LABELS.blockLabels, ...overrides.blockLabels },
329
+ elementNames: { ...DEFAULT_LABELS.elementNames, ...overrides.elementNames },
330
+ inspectorLabels: { ...DEFAULT_LABELS.inspectorLabels, ...overrides.inspectorLabels }
136
331
  };
137
332
  }
138
333
 
@@ -255,11 +450,58 @@ function Input({ className, type, ...props }) {
255
450
  );
256
451
  }
257
452
 
453
+ // src/components/ui/tooltip.tsx
454
+ var React3 = require("react");
455
+ var import_radix_ui2 = require("radix-ui");
456
+ var import_jsx_runtime4 = require("react/jsx-runtime");
457
+ function TooltipProvider({
458
+ delayDuration = 0,
459
+ ...props
460
+ }) {
461
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
462
+ import_radix_ui2.Tooltip.Provider,
463
+ {
464
+ "data-slot": "tooltip-provider",
465
+ delayDuration,
466
+ ...props
467
+ }
468
+ );
469
+ }
470
+ function Tooltip({ ...props }) {
471
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.Tooltip.Root, { "data-slot": "tooltip", ...props });
472
+ }
473
+ function TooltipTrigger({ ...props }) {
474
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
475
+ }
476
+ function TooltipContent({
477
+ className,
478
+ sideOffset = 0,
479
+ children,
480
+ ...props
481
+ }) {
482
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.Tooltip.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
483
+ import_radix_ui2.Tooltip.Content,
484
+ {
485
+ "data-slot": "tooltip-content",
486
+ sideOffset,
487
+ className: cn(
488
+ "z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
489
+ className
490
+ ),
491
+ ...props,
492
+ children: [
493
+ children,
494
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_radix_ui2.Tooltip.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })
495
+ ]
496
+ }
497
+ ) });
498
+ }
499
+
258
500
  // src/components/ui/spinner.tsx
259
501
  var import_lucide_react = require("lucide-react");
260
- var import_jsx_runtime4 = require("react/jsx-runtime");
502
+ var import_jsx_runtime5 = require("react/jsx-runtime");
261
503
  function Spinner({ className, ...props }) {
262
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
504
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
263
505
  import_lucide_react.Loader2Icon,
264
506
  {
265
507
  "data-slot": "spinner",
@@ -272,17 +514,17 @@ function Spinner({ className, ...props }) {
272
514
  }
273
515
 
274
516
  // src/components/ui/popover.tsx
275
- var React3 = require("react");
276
- var import_radix_ui2 = require("radix-ui");
277
- var import_jsx_runtime5 = require("react/jsx-runtime");
517
+ var React4 = require("react");
518
+ var import_radix_ui3 = require("radix-ui");
519
+ var import_jsx_runtime6 = require("react/jsx-runtime");
278
520
  function Popover({ ...props }) {
279
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_radix_ui2.Popover.Root, { "data-slot": "popover", ...props });
521
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Popover.Root, { "data-slot": "popover", ...props });
280
522
  }
281
523
  function PopoverTrigger({ ...props }) {
282
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_radix_ui2.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
524
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
283
525
  }
284
526
  function PopoverAnchor({ ...props }) {
285
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_radix_ui2.Popover.Anchor, { "data-slot": "popover-anchor", ...props });
527
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Popover.Anchor, { "data-slot": "popover-anchor", ...props });
286
528
  }
287
529
  function PopoverContent({
288
530
  className,
@@ -290,8 +532,8 @@ function PopoverContent({
290
532
  sideOffset = 4,
291
533
  ...props
292
534
  }) {
293
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_radix_ui2.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
294
- import_radix_ui2.Popover.Content,
535
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
536
+ import_radix_ui3.Popover.Content,
295
537
  {
296
538
  "data-slot": "popover-content",
297
539
  align,
@@ -306,22 +548,22 @@ function PopoverContent({
306
548
  }
307
549
 
308
550
  // src/components/ui/dialog.tsx
309
- var React4 = require("react");
310
- var import_radix_ui3 = require("radix-ui");
551
+ var React5 = require("react");
552
+ var import_radix_ui4 = require("radix-ui");
311
553
  var import_lucide_react2 = require("lucide-react");
312
- var import_jsx_runtime6 = require("react/jsx-runtime");
554
+ var import_jsx_runtime7 = require("react/jsx-runtime");
313
555
  function Dialog({ ...props }) {
314
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Dialog.Root, { "data-slot": "dialog", ...props });
556
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui4.Dialog.Root, { "data-slot": "dialog", ...props });
315
557
  }
316
558
  function DialogPortal({ ...props }) {
317
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Dialog.Portal, { "data-slot": "dialog-portal", ...props });
559
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui4.Dialog.Portal, { "data-slot": "dialog-portal", ...props });
318
560
  }
319
561
  function DialogOverlay({
320
562
  className,
321
563
  ...props
322
564
  }) {
323
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
324
- import_radix_ui3.Dialog.Overlay,
565
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
566
+ import_radix_ui4.Dialog.Overlay,
325
567
  {
326
568
  "data-slot": "dialog-overlay",
327
569
  className: cn(
@@ -338,10 +580,10 @@ function DialogContent({
338
580
  showCloseButton = true,
339
581
  ...props
340
582
  }) {
341
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(DialogPortal, { children: [
342
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DialogOverlay, {}),
343
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
344
- import_radix_ui3.Dialog.Content,
583
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(DialogPortal, { children: [
584
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DialogOverlay, {}),
585
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
586
+ import_radix_ui4.Dialog.Content,
345
587
  {
346
588
  "data-slot": "dialog-content",
347
589
  className: cn(
@@ -351,9 +593,9 @@ function DialogContent({
351
593
  ...props,
352
594
  children: [
353
595
  children,
354
- showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_radix_ui3.Dialog.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Button, { variant: "ghost", className: "absolute top-2 right-2", size: "icon-sm", children: [
355
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.XIcon, {}),
356
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "sr-only", children: "Close" })
596
+ showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_radix_ui4.Dialog.Close, { "data-slot": "dialog-close", asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Button, { variant: "ghost", className: "absolute top-2 right-2", size: "icon-sm", children: [
597
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_lucide_react2.XIcon, {}),
598
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "sr-only", children: "Close" })
357
599
  ] }) })
358
600
  ]
359
601
  }
@@ -361,11 +603,11 @@ function DialogContent({
361
603
  ] });
362
604
  }
363
605
  function DialogHeader({ className, ...props }) {
364
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-2", className), ...props });
606
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-2", className), ...props });
365
607
  }
366
608
  function DialogTitle({ className, ...props }) {
367
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
368
- import_radix_ui3.Dialog.Title,
609
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
610
+ import_radix_ui4.Dialog.Title,
369
611
  {
370
612
  "data-slot": "dialog-title",
371
613
  className: cn("font-heading text-base leading-none font-medium", className),
@@ -377,8 +619,8 @@ function DialogDescription({
377
619
  className,
378
620
  ...props
379
621
  }) {
380
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
381
- import_radix_ui3.Dialog.Description,
622
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
623
+ import_radix_ui4.Dialog.Description,
382
624
  {
383
625
  "data-slot": "dialog-description",
384
626
  className: cn(
@@ -412,10 +654,10 @@ function saveToastId(docId) {
412
654
  }
413
655
 
414
656
  // src/components/ui/textarea.tsx
415
- var React5 = require("react");
416
- var import_jsx_runtime7 = require("react/jsx-runtime");
657
+ var React6 = require("react");
658
+ var import_jsx_runtime8 = require("react/jsx-runtime");
417
659
  function Textarea({ className, ...props }) {
418
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
660
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
419
661
  "textarea",
420
662
  {
421
663
  "data-slot": "textarea",
@@ -432,7 +674,7 @@ function Textarea({ className, ...props }) {
432
674
  var import_react2 = require("@grapesjs/react");
433
675
  var import_react3 = require("react");
434
676
  var import_lucide_react3 = require("lucide-react");
435
- var import_jsx_runtime8 = require("react/jsx-runtime");
677
+ var import_jsx_runtime9 = require("react/jsx-runtime");
436
678
  var keyOf = (c) => c.objectId ? `obj-${c.objectId}` : `sec-${c.sectionId}`;
437
679
  var keyOfTarget = (t) => t.objectId ? `obj-${t.objectId}` : `sec-${t.sectionId}`;
438
680
  function CanvasComments({
@@ -586,11 +828,11 @@ function CanvasComments({
586
828
  console.error(e);
587
829
  }
588
830
  };
589
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { ref: containerRef, className: "pointer-events-none absolute inset-0 z-20 overflow-hidden", children: [
831
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { ref: containerRef, className: "pointer-events-none absolute inset-0 z-20 overflow-hidden", children: [
590
832
  pins.map(({ key, list }) => {
591
833
  const pos = positions[key];
592
834
  if (!pos) return null;
593
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
835
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
594
836
  "button",
595
837
  {
596
838
  type: "button",
@@ -606,8 +848,8 @@ function CanvasComments({
606
848
  key
607
849
  );
608
850
  }),
609
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Popover, { open: !!activeKey && !!activePos, onOpenChange: (o) => !o && closePopover(), children: [
610
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
851
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Popover, { open: !!activeKey && !!activePos, onOpenChange: (o) => !o && closePopover(), children: [
852
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
611
853
  "div",
612
854
  {
613
855
  style: {
@@ -619,45 +861,45 @@ function CanvasComments({
619
861
  }
620
862
  }
621
863
  ) }),
622
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
864
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
623
865
  PopoverContent,
624
866
  {
625
867
  side: "right",
626
868
  align: "start",
627
869
  className: "pointer-events-auto w-80 p-0 data-[state=closed]:!animate-none data-[state=closed]:!duration-0",
628
870
  children: [
629
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-start justify-between gap-2 border-b px-3 py-2 text-xs text-muted-foreground", children: [
630
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "min-w-0", children: activeTarget?.objectId ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
871
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-start justify-between gap-2 border-b px-3 py-2 text-xs text-muted-foreground", children: [
872
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "min-w-0", children: activeTarget?.objectId ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
631
873
  "Comment on",
632
874
  " ",
633
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "font-medium text-foreground", children: activeTarget.objectLabel })
634
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
875
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "font-medium text-foreground", children: activeTarget.objectLabel })
876
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
635
877
  labels.commentOnSection,
636
878
  " ",
637
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { className: "font-mono", children: activeTarget?.sectionId })
879
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { className: "font-mono", children: activeTarget?.sectionId })
638
880
  ] }) }),
639
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
881
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
640
882
  "button",
641
883
  {
642
884
  type: "button",
643
885
  onClick: closePopover,
644
886
  "aria-label": labels.closeComments,
645
887
  className: "-mr-1 -mt-0.5 shrink-0 rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
646
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.X, { className: "size-4" })
888
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.X, { className: "size-4" })
647
889
  }
648
890
  )
649
891
  ] }),
650
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "max-h-64 space-y-2 overflow-y-auto p-3", children: [
651
- activeComments.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-xs text-muted-foreground", children: labels.noCommentsYet }),
652
- activeComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "rounded-md border bg-muted/40 p-2 text-sm", children: [
653
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "whitespace-pre-wrap", children: c.body }),
654
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
892
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "max-h-64 space-y-2 overflow-y-auto p-3", children: [
893
+ activeComments.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-xs text-muted-foreground", children: labels.noCommentsYet }),
894
+ activeComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "rounded-md border bg-muted/40 p-2 text-sm", children: [
895
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "whitespace-pre-wrap", children: c.body }),
896
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
655
897
  "button",
656
898
  {
657
899
  onClick: () => void resolve(c.id),
658
900
  className: "mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground",
659
901
  children: [
660
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.Check, { className: "size-3" }),
902
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.Check, { className: "size-3" }),
661
903
  " ",
662
904
  labels.resolveComment
663
905
  ]
@@ -665,8 +907,8 @@ function CanvasComments({
665
907
  )
666
908
  ] }, c.id))
667
909
  ] }),
668
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "border-t p-3", children: [
669
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
910
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "border-t p-3", children: [
911
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
670
912
  Textarea,
671
913
  {
672
914
  autoFocus: true,
@@ -682,8 +924,8 @@ function CanvasComments({
682
924
  }
683
925
  }
684
926
  ),
685
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Button, { size: "sm", disabled: !body.trim(), onClick: () => void submit(), children: [
686
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.MessageSquarePlus, {}),
927
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Button, { size: "sm", disabled: !body.trim(), onClick: () => void submit(), children: [
928
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react3.MessageSquarePlus, {}),
687
929
  " ",
688
930
  labels.addComment
689
931
  ] }) })
@@ -724,21 +966,29 @@ function createSaveQueue(save, delayMs) {
724
966
  };
725
967
  }
726
968
 
969
+ // src/lib/shortcuts.ts
970
+ function opensBlockSearch(e) {
971
+ if (e.key !== "/" || e.metaKey || e.ctrlKey || e.altKey || e.isComposing) return false;
972
+ const target = e.target;
973
+ if (target?.isContentEditable) return false;
974
+ return !["INPUT", "TEXTAREA", "SELECT", "IFRAME"].includes(target?.tagName ?? "");
975
+ }
976
+
727
977
  // src/components/editor/EmailEditor.tsx
728
978
  var import_sonner2 = require("sonner");
729
- var import_lucide_react7 = require("lucide-react");
979
+ var import_lucide_react10 = require("lucide-react");
730
980
 
731
981
  // src/components/ui/switch.tsx
732
- var React6 = require("react");
733
- var import_radix_ui4 = require("radix-ui");
734
- var import_jsx_runtime9 = require("react/jsx-runtime");
982
+ var React7 = require("react");
983
+ var import_radix_ui5 = require("radix-ui");
984
+ var import_jsx_runtime10 = require("react/jsx-runtime");
735
985
  function Switch({
736
986
  className,
737
987
  size = "default",
738
988
  ...props
739
989
  }) {
740
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
741
- import_radix_ui4.Switch.Root,
990
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
991
+ import_radix_ui5.Switch.Root,
742
992
  {
743
993
  "data-slot": "switch",
744
994
  "data-size": size,
@@ -747,8 +997,8 @@ function Switch({
747
997
  className
748
998
  ),
749
999
  ...props,
750
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
751
- import_radix_ui4.Switch.Thumb,
1000
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1001
+ import_radix_ui5.Switch.Thumb,
752
1002
  {
753
1003
  "data-slot": "switch-thumb",
754
1004
  className: "pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
@@ -759,13 +1009,13 @@ function Switch({
759
1009
  }
760
1010
 
761
1011
  // src/components/editor/EmailEditor.tsx
762
- var import_lucide_react8 = require("lucide-react");
1012
+ var import_lucide_react11 = require("lucide-react");
763
1013
 
764
1014
  // src/components/editor/ImagePicker.tsx
765
1015
  var import_react4 = require("react");
766
1016
  var import_lucide_react4 = require("lucide-react");
767
1017
  var import_sonner = require("sonner");
768
- var import_jsx_runtime10 = require("react/jsx-runtime");
1018
+ var import_jsx_runtime11 = require("react/jsx-runtime");
769
1019
  function ImagePicker({
770
1020
  open,
771
1021
  currentUrl,
@@ -857,13 +1107,13 @@ function ImagePicker({
857
1107
  setDragging(false);
858
1108
  if (!full) void upload(e.dataTransfer.files);
859
1109
  };
860
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DialogContent, { className: "editor-dark gap-0 overflow-hidden border border-panel-border bg-panel p-0 text-panel-fg ring-0 sm:max-w-3xl", children: [
861
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(DialogHeader, { className: "border-b border-panel-border px-5 py-4", children: [
862
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DialogTitle, { children: labels.imagePickerTitle }),
863
- quota && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DialogDescription, { className: "text-panel-muted-fg", children: labels.imagePickerQuota(quota.used, quota.limit) })
1110
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogContent, { className: "editor-dark gap-0 overflow-hidden border border-panel-border bg-panel p-0 text-panel-fg ring-0 sm:max-w-3xl", children: [
1111
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogHeader, { className: "border-b border-panel-border px-5 py-4", children: [
1112
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogTitle, { children: labels.imagePickerTitle }),
1113
+ quota && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogDescription, { className: "text-panel-muted-fg", children: labels.imagePickerQuota(quota.used, quota.limit) })
864
1114
  ] }),
865
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex max-h-[70vh] flex-col gap-4 overflow-y-auto p-5", children: [
866
- library && (full ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "rounded-lg border border-panel-border bg-panel-elevated px-4 py-3 text-sm text-panel-muted-fg", children: labels.imagePickerQuotaFull }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1115
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex max-h-[70vh] flex-col gap-4 overflow-y-auto p-5", children: [
1116
+ library && (full ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "rounded-lg border border-panel-border bg-panel-elevated px-4 py-3 text-sm text-panel-muted-fg", children: labels.imagePickerQuotaFull }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
867
1117
  "div",
868
1118
  {
869
1119
  onDragOver: (e) => {
@@ -877,10 +1127,10 @@ function ImagePicker({
877
1127
  dragging ? "border-brand bg-brand-soft" : "border-panel-border bg-panel-elevated"
878
1128
  ),
879
1129
  children: [
880
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.ImageUp, { className: "size-8 text-panel-muted-fg" }),
881
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-2 text-sm text-panel-muted-fg", children: [
882
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: labels.imagePickerDrop }),
883
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1130
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.ImageUp, { className: "size-8 text-panel-muted-fg" }),
1131
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-2 text-sm text-panel-muted-fg", children: [
1132
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: labels.imagePickerDrop }),
1133
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
884
1134
  Button,
885
1135
  {
886
1136
  type: "button",
@@ -891,7 +1141,7 @@ function ImagePicker({
891
1141
  }
892
1142
  )
893
1143
  ] }),
894
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1144
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
895
1145
  "input",
896
1146
  {
897
1147
  ref: fileInput,
@@ -908,7 +1158,7 @@ function ImagePicker({
908
1158
  ]
909
1159
  }
910
1160
  )),
911
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1161
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
912
1162
  "form",
913
1163
  {
914
1164
  className: "flex gap-2",
@@ -918,9 +1168,9 @@ function ImagePicker({
918
1168
  if (value) onSelect(value);
919
1169
  },
920
1170
  children: [
921
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "relative flex-1", children: [
922
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Link2, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-panel-muted-fg" }),
923
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1171
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative flex-1", children: [
1172
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Link2, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-panel-muted-fg" }),
1173
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
924
1174
  Input,
925
1175
  {
926
1176
  value: url,
@@ -930,7 +1180,7 @@ function ImagePicker({
930
1180
  }
931
1181
  )
932
1182
  ] }),
933
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1183
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
934
1184
  Button,
935
1185
  {
936
1186
  type: "submit",
@@ -944,22 +1194,22 @@ function ImagePicker({
944
1194
  ]
945
1195
  }
946
1196
  ),
947
- library && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { children: [
948
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "mb-2 text-xs font-medium tracking-wide text-panel-muted-fg uppercase", children: labels.imagePickerGallery }),
949
- loading && images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Spinner, {}) }) : images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "py-6 text-center text-sm text-panel-muted-fg", children: labels.imagePickerEmpty }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("ul", { className: "grid grid-cols-3 gap-3 sm:grid-cols-4", children: [
950
- Array.from({ length: pending }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1197
+ library && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { children: [
1198
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { className: "mb-2 text-xs font-medium tracking-wide text-panel-muted-fg uppercase", children: labels.imagePickerGallery }),
1199
+ loading && images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, {}) }) : images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "py-6 text-center text-sm text-panel-muted-fg", children: labels.imagePickerEmpty }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("ul", { className: "grid grid-cols-3 gap-3 sm:grid-cols-4", children: [
1200
+ Array.from({ length: pending }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
951
1201
  "li",
952
1202
  {
953
1203
  className: "flex aspect-[4/3] items-center justify-center rounded-lg border border-panel-border bg-panel-elevated",
954
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Spinner, {})
1204
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, {})
955
1205
  },
956
1206
  `pending-${i}`
957
1207
  )),
958
1208
  images.map((image) => {
959
1209
  const selected = image.url === currentUrl;
960
1210
  const confirming = confirmId === image.id;
961
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { className: "group relative", children: [
962
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1211
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { className: "group relative", children: [
1212
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
963
1213
  "button",
964
1214
  {
965
1215
  type: "button",
@@ -969,7 +1219,7 @@ function ImagePicker({
969
1219
  "block aspect-[4/3] w-full overflow-hidden rounded-lg border bg-black/25 transition outline-none focus-visible:ring-2 focus-visible:ring-brand",
970
1220
  selected ? "border-brand ring-2 ring-brand" : "border-panel-border hover:border-panel-muted-fg"
971
1221
  ),
972
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1222
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
973
1223
  "img",
974
1224
  {
975
1225
  src: image.url,
@@ -980,9 +1230,9 @@ function ImagePicker({
980
1230
  )
981
1231
  }
982
1232
  ),
983
- selected && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "pointer-events-none absolute top-1.5 left-1.5 flex size-5 items-center justify-center rounded-full bg-brand text-brand-fg", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Check, { className: "size-3" }) }),
984
- image.name && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "pointer-events-none absolute inset-x-0 bottom-0 truncate rounded-b-lg bg-black/60 px-2 py-1 text-[11px] text-white opacity-0 transition group-hover:opacity-100", children: image.name }),
985
- library.remove && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1233
+ selected && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "pointer-events-none absolute top-1.5 left-1.5 flex size-5 items-center justify-center rounded-full bg-brand text-brand-fg", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Check, { className: "size-3" }) }),
1234
+ image.name && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "pointer-events-none absolute inset-x-0 bottom-0 truncate rounded-b-lg bg-black/60 px-2 py-1 text-[11px] text-white opacity-0 transition group-hover:opacity-100", children: image.name }),
1235
+ library.remove && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
986
1236
  "button",
987
1237
  {
988
1238
  type: "button",
@@ -995,7 +1245,7 @@ function ImagePicker({
995
1245
  confirming ? "bg-red-600 opacity-100" : "bg-black/60 opacity-0 group-hover:opacity-100 hover:bg-black/80"
996
1246
  ),
997
1247
  children: [
998
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Trash2, { className: "size-3.5" }),
1248
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Trash2, { className: "size-3.5" }),
999
1249
  confirming && labels.imagePickerConfirmDelete
1000
1250
  ]
1001
1251
  }
@@ -1017,11 +1267,1025 @@ function matchesAccept(file, accept) {
1017
1267
  });
1018
1268
  }
1019
1269
 
1270
+ // src/components/editor/inspector/Inspector.tsx
1271
+ var import_react7 = require("react");
1272
+ var import_react8 = require("@grapesjs/react");
1273
+ var import_lucide_react9 = require("lucide-react");
1274
+
1020
1275
  // src/components/editor/ColorField.tsx
1021
- var import_react5 = require("react");
1276
+ var import_react6 = require("react");
1022
1277
  var import_react_colorful = require("react-colorful");
1278
+ var import_lucide_react6 = require("lucide-react");
1279
+
1280
+ // src/components/editor/inspector/controls.tsx
1281
+ var import_react5 = require("react");
1023
1282
  var import_lucide_react5 = require("lucide-react");
1024
- var import_jsx_runtime11 = require("react/jsx-runtime");
1283
+
1284
+ // src/components/editor/inspector/values.ts
1285
+ var SIDES = ["top", "right", "bottom", "left"];
1286
+ function expandBox(value) {
1287
+ const parts = value.trim().split(/\s+/).filter(Boolean);
1288
+ if (parts.length === 0 || parts.length > 4) return null;
1289
+ const [t, r = t, b = t, l = r] = parts;
1290
+ return [t, r, b, l];
1291
+ }
1292
+ function readBox(attributes, attr) {
1293
+ const short = expandBox(attributes[attr] ?? "") ?? ["", "", "", ""];
1294
+ return SIDES.map((side, i) => attributes[`${attr}-${side}`] ?? short[i]);
1295
+ }
1296
+ function boxShorthand(sides) {
1297
+ const [t, r, b, l] = sides.map((s) => s.trim() || "0");
1298
+ if (t === b && r === l) return t === r ? t : `${t} ${r}`;
1299
+ if (r === l) return `${t} ${r} ${b}`;
1300
+ return `${t} ${r} ${b} ${l}`;
1301
+ }
1302
+ function parseLength(value, units) {
1303
+ const match = /^(-?\d*\.?\d+)\s*(px|%|em)?$/.exec(value.trim());
1304
+ if (!match) return null;
1305
+ const unit = match[2] ?? "";
1306
+ return units.includes(unit) ? { amount: match[1], unit } : null;
1307
+ }
1308
+ var NO_SHARE = "-noshare";
1309
+ function parseNetwork(name) {
1310
+ return name.endsWith(NO_SHARE) ? { network: name.slice(0, -NO_SHARE.length), share: false } : { network: name, share: name !== "custom" && name !== "" };
1311
+ }
1312
+ function networkName(network, share) {
1313
+ return share || network === "custom" ? network : `${network}${NO_SHARE}`;
1314
+ }
1315
+ var LINK_SCHEMES = /* @__PURE__ */ new Set(["http", "https", "mailto", "tel", "sms"]);
1316
+ function isSafeLink(value) {
1317
+ const decoded = value.replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16))).replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/:/g, ":").replace(/&(Tab|NewLine);/g, "").replace(/[\u0000-\u0020]/g, "");
1318
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(decoded)?.[1];
1319
+ return !scheme || LINK_SCHEMES.has(scheme.toLowerCase());
1320
+ }
1321
+ function isSafeNavbarLink(baseUrl, href) {
1322
+ return isSafeLink(baseUrl) && isSafeLink(href) && isSafeLink(baseUrl + href);
1323
+ }
1324
+
1325
+ // src/components/editor/inspector/controls.tsx
1326
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1327
+ var FIELD = "h-7 w-full min-w-0 rounded-md border border-panel-border bg-panel-elevated px-2 text-xs text-panel-fg outline-none placeholder:text-panel-muted-fg/70 focus:border-brand";
1328
+ var FieldLabel = (0, import_react5.createContext)({});
1329
+ var useFieldLabel = () => {
1330
+ const { label, help } = (0, import_react5.useContext)(FieldLabel);
1331
+ return { "aria-labelledby": label, "aria-describedby": help };
1332
+ };
1333
+ function Row({
1334
+ label,
1335
+ help,
1336
+ own,
1337
+ wide,
1338
+ inline,
1339
+ labels,
1340
+ onReset,
1341
+ children
1342
+ }) {
1343
+ const id = (0, import_react5.useId)();
1344
+ const helpId = (0, import_react5.useId)();
1345
+ const [helpOpen, setHelpOpen] = (0, import_react5.useState)(false);
1346
+ const control = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(FieldLabel.Provider, { value: { label: id, help: help ? helpId : void 0 }, children });
1347
+ const caption = /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex min-w-0 items-center gap-1.5 text-[11px] text-panel-muted-fg", children: [
1348
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1349
+ "span",
1350
+ {
1351
+ "aria-hidden": true,
1352
+ className: cn("size-1.5 shrink-0 rounded-full", own ? "bg-brand" : "bg-transparent")
1353
+ }
1354
+ ),
1355
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { id, className: "truncate", children: label }),
1356
+ help && // Its own provider: a host need not wrap the editor in one, as only EditorHeader asked.
1357
+ // Opened by a tap as well: Radix opens a tooltip on hover and focus, not on touch.
1358
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Tooltip, { open: helpOpen, onOpenChange: setHelpOpen, children: [
1359
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1360
+ "button",
1361
+ {
1362
+ type: "button",
1363
+ "aria-label": help,
1364
+ onClick: () => setHelpOpen((open) => !open),
1365
+ className: "-m-0.5 shrink-0 rounded p-0.5 text-panel-muted-fg/70 hover:text-panel-fg focus-visible:text-panel-fg",
1366
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.CircleHelp, { className: "size-3" })
1367
+ }
1368
+ ) }),
1369
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TooltipContent, { side: "top", className: "max-w-56 leading-snug", children: help })
1370
+ ] }) }),
1371
+ own && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1372
+ "button",
1373
+ {
1374
+ type: "button",
1375
+ onClick: onReset,
1376
+ title: labels.resetToDefault,
1377
+ "aria-label": `${labels.resetToDefault}: ${label}`,
1378
+ className: "ml-auto rounded p-0.5 text-panel-muted-fg opacity-0 group-hover/row:opacity-100 hover:text-panel-fg focus-visible:opacity-100",
1379
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.RotateCcw, { className: "size-3" })
1380
+ }
1381
+ )
1382
+ ] });
1383
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "group/row px-3 py-1", children: [
1384
+ wide ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-1", children: [
1385
+ caption,
1386
+ control
1387
+ ] }) : inline ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
1388
+ caption,
1389
+ control
1390
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "grid grid-cols-[92px_minmax(0,1fr)] items-center gap-2", children: [
1391
+ caption,
1392
+ control
1393
+ ] }),
1394
+ help && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { id: helpId, hidden: true, children: help })
1395
+ ] });
1396
+ }
1397
+ function useDraft(value, commit, source = value) {
1398
+ const [draft, setDraft] = (0, import_react5.useState)(value);
1399
+ (0, import_react5.useEffect)(() => setDraft(value), [value, source]);
1400
+ const done = () => {
1401
+ if (draft.trim() !== value) commit(draft.trim());
1402
+ };
1403
+ const onKeyDown = (e) => {
1404
+ if (e.key === "Enter") e.currentTarget.blur();
1405
+ if (e.key === "Escape") {
1406
+ setDraft(value);
1407
+ requestAnimationFrame(() => e.target.blur());
1408
+ }
1409
+ };
1410
+ return { draft, setDraft, done, onKeyDown };
1411
+ }
1412
+ function TextInput({
1413
+ value,
1414
+ placeholder,
1415
+ accept,
1416
+ onCommit
1417
+ }) {
1418
+ const [invalid, setInvalid] = (0, import_react5.useState)(false);
1419
+ const field = useFieldLabel();
1420
+ const { draft, setDraft, done, onKeyDown } = useDraft(value, (next) => {
1421
+ const ok = !accept || accept(next);
1422
+ setInvalid(!ok);
1423
+ if (ok) onCommit(next);
1424
+ });
1425
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1426
+ "input",
1427
+ {
1428
+ className: cn(FIELD, invalid && "border-red-400"),
1429
+ ...field,
1430
+ "aria-invalid": invalid,
1431
+ value: draft,
1432
+ placeholder,
1433
+ onChange: (e) => setDraft(e.target.value),
1434
+ onBlur: done,
1435
+ onKeyDown
1436
+ }
1437
+ );
1438
+ }
1439
+ var BARE_NUMBER = /^-?\d*\.?\d+$/;
1440
+ function normaliseLength(text, units, negative = false, unit = units[0]) {
1441
+ if (text === "") return "";
1442
+ const parsed = BARE_NUMBER.test(text) ? { amount: text, unit } : parseLength(text, units);
1443
+ if (!parsed || !negative && parsed.amount.startsWith("-")) return null;
1444
+ return `${parsed.amount}${parsed.unit}`;
1445
+ }
1446
+ function stepAmount(amount, delta, negative = false) {
1447
+ const moved = Math.round((Number(amount) + delta) * 100) / 100;
1448
+ const next = String(negative ? moved : Math.max(0, moved));
1449
+ return BARE_NUMBER.test(next) ? next : amount;
1450
+ }
1451
+ var REPEAT_AFTER_MS = 400;
1452
+ var REPEAT_EVERY_MS = 60;
1453
+ function LengthInput({
1454
+ value,
1455
+ placeholder,
1456
+ units,
1457
+ negative = false,
1458
+ compact = false,
1459
+ labelledBy,
1460
+ labels,
1461
+ onCommit
1462
+ }) {
1463
+ const fieldLabel = useFieldLabel();
1464
+ const own = parseLength(value, units);
1465
+ const fallback = parseLength(placeholder ?? "", units);
1466
+ const [picked, setPicked] = (0, import_react5.useState)(null);
1467
+ const unit = own?.unit ?? picked ?? fallback?.unit ?? units[0];
1468
+ const [invalid, setInvalid] = (0, import_react5.useState)(false);
1469
+ const commit = (0, import_react5.useRef)(onCommit);
1470
+ commit.current = onCommit;
1471
+ const endHold = (0, import_react5.useRef)(null);
1472
+ (0, import_react5.useEffect)(() => () => endHold.current?.(), []);
1473
+ const { draft, setDraft, done, onKeyDown } = useDraft(
1474
+ own ? own.amount : value,
1475
+ (next) => {
1476
+ endHold.current?.();
1477
+ const normal = normaliseLength(next, units, negative, unit);
1478
+ setInvalid(normal === null);
1479
+ if (normal !== null) commit.current(normal);
1480
+ },
1481
+ value
1482
+ );
1483
+ const from = () => {
1484
+ const text = draft.trim();
1485
+ if (BARE_NUMBER.test(text)) return { amount: text, unit };
1486
+ if (text === "") return { amount: fallback?.amount ?? "0", unit };
1487
+ return parseLength(text, units);
1488
+ };
1489
+ const stepOnce = (delta) => {
1490
+ endHold.current?.();
1491
+ const start = from();
1492
+ if (!start) return setInvalid(true);
1493
+ const amount = stepAmount(start.amount, delta, negative);
1494
+ setInvalid(false);
1495
+ setDraft(amount);
1496
+ commit.current(`${amount}${start.unit}`);
1497
+ };
1498
+ const onKey = (e) => {
1499
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return onKeyDown(e);
1500
+ e.preventDefault();
1501
+ stepOnce((e.key === "ArrowUp" ? 1 : -1) * (e.shiftKey ? 10 : 1));
1502
+ };
1503
+ const press = (direction) => (e) => {
1504
+ if (e.button !== 0 || endHold.current) return;
1505
+ e.preventDefault();
1506
+ const start = from();
1507
+ if (!start) return setInvalid(true);
1508
+ const delta = direction * (e.shiftKey ? 10 : 1);
1509
+ let amount = stepAmount(start.amount, delta, negative);
1510
+ const show = () => {
1511
+ setInvalid(false);
1512
+ setDraft(amount);
1513
+ commit.current(`${amount}${start.unit}`, true);
1514
+ };
1515
+ show();
1516
+ let timer = window.setTimeout(function repeat() {
1517
+ amount = stepAmount(amount, delta, negative);
1518
+ show();
1519
+ timer = window.setTimeout(repeat, REPEAT_EVERY_MS);
1520
+ }, REPEAT_AFTER_MS);
1521
+ const button = e.currentTarget;
1522
+ try {
1523
+ button.setPointerCapture(e.pointerId);
1524
+ } catch {
1525
+ }
1526
+ const events = ["pointerup", "pointercancel", "lostpointercapture"];
1527
+ const pointerId = e.pointerId;
1528
+ const end = () => {
1529
+ window.clearTimeout(timer);
1530
+ for (const type of events) button.removeEventListener(type, release);
1531
+ window.removeEventListener("pointerup", release);
1532
+ window.removeEventListener("blur", end);
1533
+ endHold.current = null;
1534
+ commit.current(`${amount}${start.unit}`);
1535
+ };
1536
+ const release = (ev) => {
1537
+ if (ev.pointerId === void 0 || ev.pointerId === pointerId)
1538
+ end();
1539
+ };
1540
+ endHold.current = end;
1541
+ for (const type of events) button.addEventListener(type, release);
1542
+ window.addEventListener("pointerup", release);
1543
+ window.addEventListener("blur", end);
1544
+ };
1545
+ const click = (direction) => (e) => {
1546
+ if (e.detail === 0) stepOnce(direction);
1547
+ };
1548
+ const pickUnit = () => {
1549
+ endHold.current?.();
1550
+ const next = units[(units.indexOf(unit) + 1) % units.length];
1551
+ const text = draft.trim();
1552
+ if (!BARE_NUMBER.test(text)) return setPicked(next);
1553
+ const normal = normaliseLength(text, units, negative, next);
1554
+ if (normal === null) return setInvalid(true);
1555
+ setPicked(next);
1556
+ commit.current(normal);
1557
+ };
1558
+ const unitName = (u) => u || labels.noUnit;
1559
+ const readable = draft.trim() === "" || BARE_NUMBER.test(draft.trim());
1560
+ const arrow = "flex h-1/2 w-3.5 items-center justify-center text-panel-muted-fg hover:bg-panel-hover hover:text-panel-fg";
1561
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1562
+ "div",
1563
+ {
1564
+ className: cn(
1565
+ "group/length flex h-7 w-full min-w-0 items-center overflow-hidden rounded-md border border-panel-border bg-panel-elevated focus-within:border-brand",
1566
+ invalid && "border-red-400"
1567
+ ),
1568
+ children: [
1569
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1570
+ "input",
1571
+ {
1572
+ className: "h-full w-full min-w-0 flex-1 bg-transparent pl-2 font-mono text-xs text-panel-fg outline-none placeholder:text-panel-muted-fg/70",
1573
+ value: draft,
1574
+ placeholder: fallback ? fallback.amount : placeholder,
1575
+ inputMode: "decimal",
1576
+ ...fieldLabel,
1577
+ "aria-labelledby": labelledBy ?? fieldLabel["aria-labelledby"],
1578
+ "aria-invalid": invalid,
1579
+ onChange: (e) => {
1580
+ endHold.current?.();
1581
+ setDraft(e.target.value);
1582
+ },
1583
+ onBlur: done,
1584
+ onKeyDown: onKey
1585
+ }
1586
+ ),
1587
+ readable && (units.length > 1 && !compact ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1588
+ "button",
1589
+ {
1590
+ type: "button",
1591
+ onClick: pickUnit,
1592
+ title: labels.changeUnit(unitName(unit)),
1593
+ "aria-label": labels.changeUnit(unitName(unit)),
1594
+ className: "shrink-0 rounded px-0.5 font-mono text-[10px] text-panel-muted-fg underline decoration-dotted underline-offset-2 hover:text-panel-fg",
1595
+ children: unitName(unit)
1596
+ }
1597
+ ) : (
1598
+ // Narrow, the unit gives its place to the arrows while they show; a unit is typed.
1599
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1600
+ "span",
1601
+ {
1602
+ "aria-hidden": true,
1603
+ className: cn(
1604
+ "shrink-0 px-0.5 font-mono text-[10px] text-panel-muted-fg",
1605
+ compact && "group-focus-within/length:hidden group-hover/length:hidden"
1606
+ ),
1607
+ children: unitName(unit)
1608
+ }
1609
+ )
1610
+ )),
1611
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1612
+ "span",
1613
+ {
1614
+ className: cn(
1615
+ "h-full shrink-0 flex-col border-l border-panel-border",
1616
+ compact ? "hidden group-focus-within/length:flex group-hover/length:flex" : "flex"
1617
+ ),
1618
+ children: [
1619
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1620
+ "button",
1621
+ {
1622
+ type: "button",
1623
+ tabIndex: -1,
1624
+ "aria-label": labels.increase,
1625
+ onPointerDown: press(1),
1626
+ onClick: click(1),
1627
+ className: arrow,
1628
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.ChevronUp, { className: "size-2.5" })
1629
+ }
1630
+ ),
1631
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1632
+ "button",
1633
+ {
1634
+ type: "button",
1635
+ tabIndex: -1,
1636
+ "aria-label": labels.decrease,
1637
+ onPointerDown: press(-1),
1638
+ onClick: click(-1),
1639
+ className: arrow,
1640
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.ChevronDown, { className: "size-2.5" })
1641
+ }
1642
+ )
1643
+ ]
1644
+ }
1645
+ )
1646
+ ]
1647
+ }
1648
+ );
1649
+ }
1650
+ function BoxInput({
1651
+ attributes,
1652
+ attr,
1653
+ placeholder,
1654
+ units,
1655
+ labels,
1656
+ onCommit
1657
+ }) {
1658
+ const sides = readBox(attributes, attr);
1659
+ const defaults = readBox(placeholder ? { [attr]: placeholder } : {}, attr);
1660
+ const symmetric = sides[0] === sides[2] && sides[1] === sides[3];
1661
+ const [each, setEach] = (0, import_react5.useState)(!symmetric);
1662
+ (0, import_react5.useEffect)(() => {
1663
+ if (!symmetric) setEach(true);
1664
+ }, [symmetric]);
1665
+ const set = (indices, v, partial) => {
1666
+ const next = sides.map((s, i) => s || defaults[i]);
1667
+ for (const i of indices) next[i] = v || defaults[i];
1668
+ onCommit(next.every((s, i) => s === defaults[i]) ? "" : boxShorthand(next), partial);
1669
+ };
1670
+ const fieldLabel = useFieldLabel();
1671
+ const sideId = (0, import_react5.useId)();
1672
+ const sideNames = [labels.sideTop, labels.sideRight, labels.sideBottom, labels.sideLeft];
1673
+ const pairs = each ? SIDES.map((_, i) => [sideNames[i], [i]]) : [
1674
+ [labels.sidesVertical, [0, 2]],
1675
+ [labels.sidesHorizontal, [1, 3]]
1676
+ ];
1677
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-start gap-1.5", children: [
1678
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: cn("grid flex-1 gap-1.5", each ? "grid-cols-4" : "grid-cols-2"), children: pairs.map(([name, indices]) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex min-w-0 flex-col gap-0.5", title: name, children: [
1679
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1680
+ LengthInput,
1681
+ {
1682
+ value: sides[indices[0]],
1683
+ placeholder: defaults[indices[0]] || "0",
1684
+ units,
1685
+ compact: each,
1686
+ labels,
1687
+ labelledBy: [fieldLabel["aria-labelledby"], `${sideId}-${indices[0]}`].filter(Boolean).join(" "),
1688
+ onCommit: (v, partial) => set(indices, v, partial)
1689
+ }
1690
+ ),
1691
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1692
+ "span",
1693
+ {
1694
+ id: `${sideId}-${indices[0]}`,
1695
+ className: "truncate text-center text-[10px] text-panel-muted-fg/80",
1696
+ children: name
1697
+ }
1698
+ )
1699
+ ] }, name)) }),
1700
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1701
+ "button",
1702
+ {
1703
+ type: "button",
1704
+ onClick: () => setEach((v) => !v),
1705
+ disabled: each && !symmetric,
1706
+ "aria-pressed": each,
1707
+ title: labels.editEachSide,
1708
+ "aria-label": labels.editEachSide,
1709
+ className: cn(
1710
+ "flex size-7 shrink-0 items-center justify-center rounded-md border border-panel-border text-panel-muted-fg hover:text-panel-fg disabled:opacity-40",
1711
+ each && "border-brand text-panel-fg"
1712
+ ),
1713
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.Grid2x2, { className: "size-3.5" })
1714
+ }
1715
+ )
1716
+ ] });
1717
+ }
1718
+ var ALIGN_ICONS = {
1719
+ left: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.AlignLeft, { className: "size-3.5" }),
1720
+ center: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.AlignCenter, { className: "size-3.5" }),
1721
+ right: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.AlignRight, { className: "size-3.5" }),
1722
+ justify: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react5.AlignJustify, { className: "size-3.5" })
1723
+ };
1724
+ function Segmented({
1725
+ value,
1726
+ fallback,
1727
+ options,
1728
+ name,
1729
+ onCommit
1730
+ }) {
1731
+ const current = value || fallback;
1732
+ const iconsOnly = options.every((o) => o.value in ALIGN_ICONS);
1733
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex h-7 overflow-hidden rounded-md border border-panel-border bg-panel-elevated", children: options.map((o) => {
1734
+ const active = current === o.value;
1735
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1736
+ "button",
1737
+ {
1738
+ type: "button",
1739
+ "aria-pressed": active,
1740
+ title: name(o),
1741
+ "aria-label": name(o),
1742
+ onClick: () => o.value !== value && onCommit(o.value),
1743
+ className: cn(
1744
+ "flex min-w-0 flex-1 items-center justify-center px-1 text-[11px] text-panel-muted-fg transition-colors hover:text-panel-fg",
1745
+ active && (value ? "bg-brand text-brand-fg hover:text-brand-fg" : "bg-panel-hover text-panel-fg")
1746
+ ),
1747
+ children: iconsOnly ? ALIGN_ICONS[o.value] : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "truncate", children: name(o) })
1748
+ },
1749
+ o.value
1750
+ );
1751
+ }) });
1752
+ }
1753
+ function Select({
1754
+ value,
1755
+ fallback,
1756
+ options,
1757
+ name,
1758
+ onCommit
1759
+ }) {
1760
+ const known = !value || options.some((o) => o.value === value);
1761
+ const fallbackOption = options.find((o) => o.value === fallback);
1762
+ const field = useFieldLabel();
1763
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1764
+ "select",
1765
+ {
1766
+ className: cn(FIELD, "px-1.5"),
1767
+ ...field,
1768
+ value: value || "",
1769
+ onChange: (e) => onCommit(e.target.value),
1770
+ children: [
1771
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: "", children: fallbackOption ? name(fallbackOption) : "\u2014" }),
1772
+ !known && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value, children: value }),
1773
+ options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: o.value, children: name(o) }, o.value))
1774
+ ]
1775
+ }
1776
+ );
1777
+ }
1778
+ function Toggle({
1779
+ checked,
1780
+ label,
1781
+ onCommit
1782
+ }) {
1783
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1784
+ Switch,
1785
+ {
1786
+ checked,
1787
+ onCheckedChange: onCommit,
1788
+ "aria-label": label,
1789
+ className: "justify-self-end data-checked:bg-brand data-unchecked:bg-panel-border"
1790
+ }
1791
+ );
1792
+ }
1793
+
1794
+ // src/components/editor/mjml-attributes.ts
1795
+ var ALLOWED_ATTRIBUTES = {
1796
+ "mj-body": [
1797
+ "background-color",
1798
+ "width"
1799
+ ],
1800
+ "mj-wrapper": [
1801
+ "background-color",
1802
+ "background-position",
1803
+ "background-position-x",
1804
+ "background-position-y",
1805
+ "background-repeat",
1806
+ "background-size",
1807
+ "background-url",
1808
+ "border",
1809
+ "border-bottom",
1810
+ "border-left",
1811
+ "border-radius",
1812
+ "border-right",
1813
+ "border-top",
1814
+ "direction",
1815
+ "full-width",
1816
+ "gap",
1817
+ "padding",
1818
+ "padding-bottom",
1819
+ "padding-left",
1820
+ "padding-right",
1821
+ "padding-top",
1822
+ "text-align",
1823
+ "text-padding"
1824
+ ],
1825
+ "mj-section": [
1826
+ "background-color",
1827
+ "background-position",
1828
+ "background-position-x",
1829
+ "background-position-y",
1830
+ "background-repeat",
1831
+ "background-size",
1832
+ "background-url",
1833
+ "border",
1834
+ "border-bottom",
1835
+ "border-left",
1836
+ "border-radius",
1837
+ "border-right",
1838
+ "border-top",
1839
+ "direction",
1840
+ "full-width",
1841
+ "padding",
1842
+ "padding-bottom",
1843
+ "padding-left",
1844
+ "padding-right",
1845
+ "padding-top",
1846
+ "text-align",
1847
+ "text-padding"
1848
+ ],
1849
+ "mj-column": [
1850
+ "background-color",
1851
+ "border",
1852
+ "border-bottom",
1853
+ "border-left",
1854
+ "border-radius",
1855
+ "border-right",
1856
+ "border-top",
1857
+ "direction",
1858
+ "inner-background-color",
1859
+ "inner-border",
1860
+ "inner-border-bottom",
1861
+ "inner-border-left",
1862
+ "inner-border-radius",
1863
+ "inner-border-right",
1864
+ "inner-border-top",
1865
+ "padding",
1866
+ "padding-bottom",
1867
+ "padding-left",
1868
+ "padding-right",
1869
+ "padding-top",
1870
+ "vertical-align",
1871
+ "width"
1872
+ ],
1873
+ "mj-group": [
1874
+ "background-color",
1875
+ "direction",
1876
+ "vertical-align",
1877
+ "width"
1878
+ ],
1879
+ "mj-text": [
1880
+ "align",
1881
+ "background-color",
1882
+ "color",
1883
+ "container-background-color",
1884
+ "font-family",
1885
+ "font-size",
1886
+ "font-style",
1887
+ "font-weight",
1888
+ "height",
1889
+ "letter-spacing",
1890
+ "line-height",
1891
+ "padding",
1892
+ "padding-bottom",
1893
+ "padding-left",
1894
+ "padding-right",
1895
+ "padding-top",
1896
+ "text-decoration",
1897
+ "text-transform",
1898
+ "vertical-align"
1899
+ ],
1900
+ "mj-button": [
1901
+ "align",
1902
+ "background-color",
1903
+ "border",
1904
+ "border-bottom",
1905
+ "border-left",
1906
+ "border-radius",
1907
+ "border-right",
1908
+ "border-top",
1909
+ "color",
1910
+ "container-background-color",
1911
+ "font-family",
1912
+ "font-size",
1913
+ "font-style",
1914
+ "font-weight",
1915
+ "height",
1916
+ "href",
1917
+ "inner-padding",
1918
+ "letter-spacing",
1919
+ "line-height",
1920
+ "name",
1921
+ "padding",
1922
+ "padding-bottom",
1923
+ "padding-left",
1924
+ "padding-right",
1925
+ "padding-top",
1926
+ "rel",
1927
+ "target",
1928
+ "text-align",
1929
+ "text-decoration",
1930
+ "text-transform",
1931
+ "title",
1932
+ "vertical-align",
1933
+ "width"
1934
+ ],
1935
+ "mj-image": [
1936
+ "align",
1937
+ "alt",
1938
+ "border",
1939
+ "border-bottom",
1940
+ "border-left",
1941
+ "border-radius",
1942
+ "border-right",
1943
+ "border-top",
1944
+ "container-background-color",
1945
+ "fluid-on-mobile",
1946
+ "font-size",
1947
+ "height",
1948
+ "href",
1949
+ "max-height",
1950
+ "name",
1951
+ "padding",
1952
+ "padding-bottom",
1953
+ "padding-left",
1954
+ "padding-right",
1955
+ "padding-top",
1956
+ "rel",
1957
+ "sizes",
1958
+ "src",
1959
+ "srcset",
1960
+ "target",
1961
+ "title",
1962
+ "usemap",
1963
+ "width"
1964
+ ],
1965
+ "mj-divider": [
1966
+ "align",
1967
+ "border-color",
1968
+ "border-style",
1969
+ "border-width",
1970
+ "container-background-color",
1971
+ "padding",
1972
+ "padding-bottom",
1973
+ "padding-left",
1974
+ "padding-right",
1975
+ "padding-top",
1976
+ "width"
1977
+ ],
1978
+ "mj-spacer": [
1979
+ "border",
1980
+ "border-bottom",
1981
+ "border-left",
1982
+ "border-right",
1983
+ "border-top",
1984
+ "container-background-color",
1985
+ "height",
1986
+ "padding",
1987
+ "padding-bottom",
1988
+ "padding-left",
1989
+ "padding-right",
1990
+ "padding-top"
1991
+ ],
1992
+ "mj-social": [
1993
+ "align",
1994
+ "border-radius",
1995
+ "color",
1996
+ "container-background-color",
1997
+ "font-family",
1998
+ "font-size",
1999
+ "font-style",
2000
+ "font-weight",
2001
+ "icon-height",
2002
+ "icon-padding",
2003
+ "icon-size",
2004
+ "inner-padding",
2005
+ "line-height",
2006
+ "mode",
2007
+ "padding",
2008
+ "padding-bottom",
2009
+ "padding-left",
2010
+ "padding-right",
2011
+ "padding-top",
2012
+ "table-layout",
2013
+ "text-decoration",
2014
+ "text-padding",
2015
+ "vertical-align"
2016
+ ],
2017
+ "mj-social-element": [
2018
+ "align",
2019
+ "alt",
2020
+ "background-color",
2021
+ "border-radius",
2022
+ "color",
2023
+ "font-family",
2024
+ "font-size",
2025
+ "font-style",
2026
+ "font-weight",
2027
+ "href",
2028
+ "icon-height",
2029
+ "icon-padding",
2030
+ "icon-position",
2031
+ "icon-size",
2032
+ "line-height",
2033
+ "name",
2034
+ "padding",
2035
+ "padding-bottom",
2036
+ "padding-left",
2037
+ "padding-right",
2038
+ "padding-top",
2039
+ "rel",
2040
+ "sizes",
2041
+ "src",
2042
+ "srcset",
2043
+ "target",
2044
+ "text-decoration",
2045
+ "text-padding",
2046
+ "title",
2047
+ "vertical-align"
2048
+ ],
2049
+ "mj-hero": [
2050
+ "background-color",
2051
+ "background-height",
2052
+ "background-position",
2053
+ "background-url",
2054
+ "background-width",
2055
+ "border-radius",
2056
+ "container-background-color",
2057
+ "height",
2058
+ "inner-background-color",
2059
+ "inner-padding",
2060
+ "inner-padding-bottom",
2061
+ "inner-padding-left",
2062
+ "inner-padding-right",
2063
+ "inner-padding-top",
2064
+ "mode",
2065
+ "padding",
2066
+ "padding-bottom",
2067
+ "padding-left",
2068
+ "padding-right",
2069
+ "padding-top",
2070
+ "vertical-align"
2071
+ ],
2072
+ "mj-navbar": [
2073
+ "align",
2074
+ "base-url",
2075
+ "hamburger",
2076
+ "ico-align",
2077
+ "ico-close",
2078
+ "ico-color",
2079
+ "ico-font-family",
2080
+ "ico-font-size",
2081
+ "ico-line-height",
2082
+ "ico-open",
2083
+ "ico-padding",
2084
+ "ico-padding-bottom",
2085
+ "ico-padding-left",
2086
+ "ico-padding-right",
2087
+ "ico-padding-top",
2088
+ "ico-text-decoration",
2089
+ "ico-text-transform",
2090
+ "padding",
2091
+ "padding-bottom",
2092
+ "padding-left",
2093
+ "padding-right",
2094
+ "padding-top"
2095
+ ],
2096
+ "mj-navbar-link": [
2097
+ "color",
2098
+ "font-family",
2099
+ "font-size",
2100
+ "font-style",
2101
+ "font-weight",
2102
+ "href",
2103
+ "letter-spacing",
2104
+ "line-height",
2105
+ "name",
2106
+ "padding",
2107
+ "padding-bottom",
2108
+ "padding-left",
2109
+ "padding-right",
2110
+ "padding-top",
2111
+ "rel",
2112
+ "target",
2113
+ "text-decoration",
2114
+ "text-transform"
2115
+ ]
2116
+ };
2117
+ var ALWAYS_ALLOWED = /* @__PURE__ */ new Set(["mj-class", "css-class", "id", "style"]);
2118
+ var START_TAG = /<[a-z][^\s/>]*(?:[^>"']|"[^"]*"|'[^']*')*>/gi;
2119
+ var ATTRIBUTE = /\s+([^\s"'>/=]+)(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]+))?/g;
2120
+ function withoutEditorProps(mjml) {
2121
+ return mjml.replace(
2122
+ START_TAG,
2123
+ (tag) => tag.replace(
2124
+ ATTRIBUTE,
2125
+ (attribute, name) => name.toLowerCase().startsWith("data-gjs-") ? "" : attribute
2126
+ )
2127
+ );
2128
+ }
2129
+ function illegalAttributes(tag, attributes) {
2130
+ const allowed = ALLOWED_ATTRIBUTES[tag];
2131
+ if (!allowed) return [];
2132
+ return Object.keys(attributes).filter((a) => !ALWAYS_ALLOWED.has(a) && !allowed.includes(a));
2133
+ }
2134
+ var NAMED_COLORS = /* @__PURE__ */ new Set([
2135
+ "aliceblue",
2136
+ "antiquewhite",
2137
+ "aqua",
2138
+ "aquamarine",
2139
+ "azure",
2140
+ "beige",
2141
+ "bisque",
2142
+ "black",
2143
+ "blanchedalmond",
2144
+ "blue",
2145
+ "blueviolet",
2146
+ "brown",
2147
+ "burlywood",
2148
+ "cadetblue",
2149
+ "chartreuse",
2150
+ "chocolate",
2151
+ "coral",
2152
+ "cornflowerblue",
2153
+ "cornsilk",
2154
+ "crimson",
2155
+ "cyan",
2156
+ "darkblue",
2157
+ "darkcyan",
2158
+ "darkgoldenrod",
2159
+ "darkgray",
2160
+ "darkgreen",
2161
+ "darkgrey",
2162
+ "darkkhaki",
2163
+ "darkmagenta",
2164
+ "darkolivegreen",
2165
+ "darkorange",
2166
+ "darkorchid",
2167
+ "darkred",
2168
+ "darksalmon",
2169
+ "darkseagreen",
2170
+ "darkslateblue",
2171
+ "darkslategray",
2172
+ "darkslategrey",
2173
+ "darkturquoise",
2174
+ "darkviolet",
2175
+ "deeppink",
2176
+ "deepskyblue",
2177
+ "dimgray",
2178
+ "dimgrey",
2179
+ "dodgerblue",
2180
+ "firebrick",
2181
+ "floralwhite",
2182
+ "forestgreen",
2183
+ "fuchsia",
2184
+ "gainsboro",
2185
+ "ghostwhite",
2186
+ "gold",
2187
+ "goldenrod",
2188
+ "gray",
2189
+ "green",
2190
+ "greenyellow",
2191
+ "grey",
2192
+ "honeydew",
2193
+ "hotpink",
2194
+ "indianred",
2195
+ "indigo",
2196
+ "inherit",
2197
+ "ivory",
2198
+ "khaki",
2199
+ "lavender",
2200
+ "lavenderblush",
2201
+ "lawngreen",
2202
+ "lemonchiffon",
2203
+ "lightblue",
2204
+ "lightcoral",
2205
+ "lightcyan",
2206
+ "lightgoldenrodyellow",
2207
+ "lightgray",
2208
+ "lightgreen",
2209
+ "lightgrey",
2210
+ "lightpink",
2211
+ "lightsalmon",
2212
+ "lightseagreen",
2213
+ "lightskyblue",
2214
+ "lightslategray",
2215
+ "lightslategrey",
2216
+ "lightsteelblue",
2217
+ "lightyellow",
2218
+ "lime",
2219
+ "limegreen",
2220
+ "linen",
2221
+ "magenta",
2222
+ "maroon",
2223
+ "mediumaquamarine",
2224
+ "mediumblue",
2225
+ "mediumorchid",
2226
+ "mediumpurple",
2227
+ "mediumseagreen",
2228
+ "mediumslateblue",
2229
+ "mediumspringgreen",
2230
+ "mediumturquoise",
2231
+ "mediumvioletred",
2232
+ "midnightblue",
2233
+ "mintcream",
2234
+ "mistyrose",
2235
+ "moccasin",
2236
+ "navajowhite",
2237
+ "navy",
2238
+ "oldlace",
2239
+ "olive",
2240
+ "olivedrab",
2241
+ "orange",
2242
+ "orangered",
2243
+ "orchid",
2244
+ "palegoldenrod",
2245
+ "palegreen",
2246
+ "paleturquoise",
2247
+ "palevioletred",
2248
+ "papayawhip",
2249
+ "peachpuff",
2250
+ "peru",
2251
+ "pink",
2252
+ "plum",
2253
+ "powderblue",
2254
+ "purple",
2255
+ "rebeccapurple",
2256
+ "red",
2257
+ "rosybrown",
2258
+ "royalblue",
2259
+ "saddlebrown",
2260
+ "salmon",
2261
+ "sandybrown",
2262
+ "seagreen",
2263
+ "seashell",
2264
+ "sienna",
2265
+ "silver",
2266
+ "skyblue",
2267
+ "slateblue",
2268
+ "slategray",
2269
+ "slategrey",
2270
+ "snow",
2271
+ "springgreen",
2272
+ "steelblue",
2273
+ "tan",
2274
+ "teal",
2275
+ "thistle",
2276
+ "tomato",
2277
+ "transparent",
2278
+ "turquoise",
2279
+ "violet",
2280
+ "wheat",
2281
+ "white",
2282
+ "whitesmoke",
2283
+ "yellow",
2284
+ "yellowgreen"
2285
+ ]);
2286
+
2287
+ // src/components/editor/ColorField.tsx
2288
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1025
2289
  var PRESETS = [
1026
2290
  "#000000",
1027
2291
  "#374151",
@@ -1042,32 +2306,33 @@ var eyeDropper = () => globalThis.EyeDropper;
1042
2306
  function ColorField({
1043
2307
  value,
1044
2308
  placeholder,
1045
- documentColors,
2309
+ documentColors: documentColors2,
1046
2310
  labels,
1047
2311
  onChange
1048
2312
  }) {
1049
- const [text, setText] = (0, import_react5.useState)(value);
1050
- const [open, setOpen] = (0, import_react5.useState)(false);
1051
- const [used, setUsed] = (0, import_react5.useState)([]);
1052
- const pressed = (0, import_react5.useRef)(false);
1053
- const dragged = (0, import_react5.useRef)(null);
1054
- const fieldRef = (0, import_react5.useRef)(null);
1055
- const anchor = (0, import_react5.useRef)({
2313
+ const field = useFieldLabel();
2314
+ const [text, setText] = (0, import_react6.useState)(value);
2315
+ const [open, setOpen] = (0, import_react6.useState)(false);
2316
+ const [used, setUsed] = (0, import_react6.useState)([]);
2317
+ const pressed = (0, import_react6.useRef)(false);
2318
+ const dragged = (0, import_react6.useRef)(null);
2319
+ const fieldRef = (0, import_react6.useRef)(null);
2320
+ const anchor = (0, import_react6.useRef)({
1056
2321
  getBoundingClientRect: () => {
1057
- const field = fieldRef.current?.getBoundingClientRect() ?? new DOMRect();
2322
+ const field2 = fieldRef.current?.getBoundingClientRect() ?? new DOMRect();
1058
2323
  const panel = fieldRef.current?.closest(".editor-dark")?.getBoundingClientRect();
1059
- return new DOMRect(panel?.right ?? field.right, field.top, 0, field.height);
2324
+ return new DOMRect(panel?.right ?? field2.right, field2.top, 0, field2.height);
1060
2325
  }
1061
2326
  });
1062
- (0, import_react5.useEffect)(() => setText(value), [value]);
1063
- const latestOnChange = (0, import_react5.useRef)(onChange);
2327
+ (0, import_react6.useEffect)(() => setText(value), [value]);
2328
+ const latestOnChange = (0, import_react6.useRef)(onChange);
1064
2329
  latestOnChange.current = onChange;
1065
- const release = (0, import_react5.useCallback)(() => {
2330
+ const release = (0, import_react6.useCallback)(() => {
1066
2331
  pressed.current = false;
1067
2332
  if (dragged.current !== null) latestOnChange.current(dragged.current, false);
1068
2333
  dragged.current = null;
1069
2334
  }, []);
1070
- (0, import_react5.useEffect)(() => {
2335
+ (0, import_react6.useEffect)(() => {
1071
2336
  document.addEventListener("lostpointercapture", release);
1072
2337
  return () => {
1073
2338
  document.removeEventListener("lostpointercapture", release);
@@ -1076,12 +2341,16 @@ function ColorField({
1076
2341
  }, [release]);
1077
2342
  const hex = toHex(value) ?? toHex(placeholder ?? "") ?? "#000000";
1078
2343
  const translucent = hex.length === 9;
1079
- const empty = !value;
2344
+ const shown = value || placeholder || "";
1080
2345
  const commit = (next) => {
1081
2346
  dragged.current = null;
1082
- onChange(next, false);
2347
+ const color = mjmlColor(next);
2348
+ if (color === null) return setText(value);
2349
+ setText(color);
2350
+ onChange(color, false);
1083
2351
  };
1084
- const drag = (next) => {
2352
+ const drag = (picked) => {
2353
+ const next = mjmlColor(picked) ?? picked;
1085
2354
  setText(next);
1086
2355
  if (!pressed.current) return commit(next);
1087
2356
  dragged.current = next;
@@ -1096,29 +2365,30 @@ function ColorField({
1096
2365
  } catch {
1097
2366
  }
1098
2367
  };
1099
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2368
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1100
2369
  Popover,
1101
2370
  {
1102
2371
  open,
1103
2372
  onOpenChange: (next) => {
1104
- if (next) setUsed(documentColors());
2373
+ if (next) setUsed(documentColors2());
1105
2374
  else if (text.trim() !== value) commit(text.trim());
1106
2375
  setOpen(next);
1107
2376
  },
1108
2377
  children: [
1109
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PopoverAnchor, { virtualRef: anchor }),
1110
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2378
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverAnchor, { virtualRef: anchor }),
2379
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1111
2380
  "div",
1112
2381
  {
1113
2382
  ref: fieldRef,
1114
2383
  className: "flex h-8 w-full items-center gap-1.5 rounded-md border border-panel-border bg-panel-elevated pr-1.5 pl-2 focus-within:border-brand",
1115
2384
  children: [
1116
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2385
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1117
2386
  "input",
1118
2387
  {
1119
2388
  value: text,
1120
2389
  placeholder,
1121
2390
  spellCheck: false,
2391
+ ...field,
1122
2392
  onChange: (e) => setText(e.target.value),
1123
2393
  onBlur: () => text !== value && commit(text.trim()),
1124
2394
  onKeyDown: (e) => {
@@ -1128,7 +2398,7 @@ function ColorField({
1128
2398
  className: "min-w-0 flex-1 bg-transparent font-mono text-xs text-panel-fg outline-none placeholder:text-panel-muted-fg"
1129
2399
  }
1130
2400
  ),
1131
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2401
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1132
2402
  "button",
1133
2403
  {
1134
2404
  type: "button",
@@ -1136,15 +2406,15 @@ function ColorField({
1136
2406
  "aria-label": labels.colorPickerOpen,
1137
2407
  className: "relative size-5 shrink-0 overflow-hidden rounded border border-white/20 outline-none focus-visible:ring-2 focus-visible:ring-brand",
1138
2408
  children: [
1139
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Checkerboard, {}),
1140
- !empty && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "absolute inset-0", style: { background: value } })
2409
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Checkerboard, {}),
2410
+ mjmlColor(shown) && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "absolute inset-0", style: { background: shown } })
1141
2411
  ]
1142
2412
  }
1143
2413
  ) })
1144
2414
  ]
1145
2415
  }
1146
2416
  ),
1147
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2417
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1148
2418
  PopoverContent,
1149
2419
  {
1150
2420
  side: "right",
@@ -1152,36 +2422,37 @@ function ColorField({
1152
2422
  sideOffset: 8,
1153
2423
  className: "editor-dark flex w-52 flex-col gap-2.5 border border-panel-border bg-panel p-2.5 text-panel-fg",
1154
2424
  children: [
1155
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2425
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1156
2426
  "div",
1157
2427
  {
1158
2428
  onPointerDownCapture: (e) => {
1159
2429
  pressed.current = true;
1160
2430
  e.target.setPointerCapture(e.pointerId);
1161
2431
  },
1162
- children: translucent ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_colorful.HexAlphaColorPicker, { color: hex, onChange: drag, className: "color-field-picker" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_colorful.HexColorPicker, { color: hex, onChange: drag, className: "color-field-picker" })
2432
+ children: translucent ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_colorful.HexAlphaColorPicker, { color: hex, onChange: drag, className: "color-field-picker" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_colorful.HexColorPicker, { color: hex, onChange: drag, className: "color-field-picker" })
1163
2433
  }
1164
2434
  ),
1165
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1.5", children: [
1166
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2435
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2436
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1167
2437
  "span",
1168
2438
  {
1169
2439
  className: "size-6 shrink-0 rounded border border-white/20",
1170
2440
  style: { background: hex }
1171
2441
  }
1172
2442
  ),
1173
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2443
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1174
2444
  "input",
1175
2445
  {
1176
2446
  value: text,
1177
2447
  spellCheck: false,
2448
+ ...field,
1178
2449
  onChange: (e) => setText(e.target.value),
1179
2450
  onKeyDown: (e) => e.key === "Enter" && commit(text.trim()),
1180
2451
  onBlur: () => text !== value && commit(text.trim()),
1181
2452
  className: "h-6 min-w-0 flex-1 rounded border border-panel-border bg-panel-elevated px-1.5 font-mono text-[11px] outline-none focus:border-brand"
1182
2453
  }
1183
2454
  ),
1184
- eyeDropper() && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2455
+ eyeDropper() && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1185
2456
  "button",
1186
2457
  {
1187
2458
  type: "button",
@@ -1189,11 +2460,11 @@ function ColorField({
1189
2460
  title: labels.colorPickerEyedropper,
1190
2461
  "aria-label": labels.colorPickerEyedropper,
1191
2462
  className: "flex size-6 shrink-0 items-center justify-center rounded border border-panel-border bg-panel-elevated text-panel-muted-fg hover:text-panel-fg",
1192
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react5.Pipette, { className: "size-3" })
2463
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react6.Pipette, { className: "size-3" })
1193
2464
  }
1194
2465
  )
1195
2466
  ] }),
1196
- used.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2467
+ used.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1197
2468
  Swatches,
1198
2469
  {
1199
2470
  title: labels.colorPickerDocument,
@@ -1202,7 +2473,7 @@ function ColorField({
1202
2473
  onPick: commit
1203
2474
  }
1204
2475
  ),
1205
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2476
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1206
2477
  Swatches,
1207
2478
  {
1208
2479
  title: labels.colorPickerPresets,
@@ -1226,10 +2497,10 @@ function Swatches({
1226
2497
  onPick,
1227
2498
  transparentLabel
1228
2499
  }) {
1229
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
1230
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "mb-1 text-[10px] font-medium tracking-wide text-panel-muted-fg uppercase", children: title }),
1231
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "grid grid-cols-8 gap-1", children: [
1232
- colors.map((color) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2500
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { children: [
2501
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "mb-1 text-[10px] font-medium tracking-wide text-panel-muted-fg uppercase", children: title }),
2502
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "grid grid-cols-8 gap-1", children: [
2503
+ colors.map((color) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1233
2504
  "button",
1234
2505
  {
1235
2506
  type: "button",
@@ -1243,21 +2514,21 @@ function Swatches({
1243
2514
  },
1244
2515
  color
1245
2516
  )),
1246
- transparentLabel && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2517
+ transparentLabel && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1247
2518
  "button",
1248
2519
  {
1249
2520
  type: "button",
1250
2521
  title: transparentLabel,
1251
2522
  onClick: () => onPick("transparent"),
1252
2523
  className: "relative aspect-square overflow-hidden rounded border border-white/15 outline-none hover:scale-110 focus-visible:ring-2 focus-visible:ring-brand",
1253
- children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Checkerboard, {})
2524
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Checkerboard, {})
1254
2525
  }
1255
2526
  )
1256
2527
  ] })
1257
2528
  ] });
1258
2529
  }
1259
2530
  function Checkerboard() {
1260
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2531
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1261
2532
  "span",
1262
2533
  {
1263
2534
  className: "absolute inset-0",
@@ -1268,6 +2539,16 @@ function Checkerboard() {
1268
2539
  }
1269
2540
  );
1270
2541
  }
2542
+ function mjmlColor(value) {
2543
+ const v = value.trim();
2544
+ if (v === "") return "";
2545
+ if (NAMED_COLORS.has(v.toLowerCase())) return v.toLowerCase();
2546
+ const hex = toHex(v);
2547
+ if (!hex) return null;
2548
+ if (hex.length === 7) return hex;
2549
+ const [r, g, b, a] = [1, 3, 5, 7].map((i) => parseInt(hex.slice(i, i + 2), 16));
2550
+ return `rgba(${r},${g},${b},${Number((a / 255).toFixed(2))})`;
2551
+ }
1271
2552
  function toHex(value, { opaque = false } = {}) {
1272
2553
  const v = value.trim().toLowerCase();
1273
2554
  const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/.exec(v);
@@ -1286,8 +2567,8 @@ function toHex(value, { opaque = false } = {}) {
1286
2567
  }
1287
2568
 
1288
2569
  // src/components/editor/ImageField.tsx
1289
- var import_lucide_react6 = require("lucide-react");
1290
- var import_jsx_runtime12 = require("react/jsx-runtime");
2570
+ var import_lucide_react7 = require("lucide-react");
2571
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1291
2572
  function ImageField({
1292
2573
  value,
1293
2574
  labels,
@@ -1295,8 +2576,8 @@ function ImageField({
1295
2576
  onClear
1296
2577
  }) {
1297
2578
  const url = imageUrl(value);
1298
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "relative", children: [
1299
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2579
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "relative", children: [
2580
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1300
2581
  "button",
1301
2582
  {
1302
2583
  type: "button",
@@ -1304,15 +2585,15 @@ function ImageField({
1304
2585
  title: labels.chooseImage,
1305
2586
  className: "group relative block w-full overflow-hidden rounded-md border border-panel-border bg-panel-elevated outline-none focus-visible:ring-2 focus-visible:ring-brand",
1306
2587
  children: [
1307
- url ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("img", { src: url, alt: "", className: "h-24 w-full bg-black/25 object-contain" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "flex h-16 flex-col items-center justify-center gap-1 text-xs text-panel-muted-fg", children: [
1308
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.ImageIcon, { className: "size-5" }),
2588
+ url ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("img", { src: url, alt: "", className: "h-24 w-full bg-black/25 object-contain" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "flex h-16 flex-col items-center justify-center gap-1 text-xs text-panel-muted-fg", children: [
2589
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react7.ImageIcon, { className: "size-5" }),
1309
2590
  labels.chooseImage
1310
2591
  ] }),
1311
- url && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "absolute inset-0 flex items-center justify-center bg-black/50 text-xs font-medium text-white opacity-0 transition group-hover:opacity-100", children: labels.chooseImage })
2592
+ url && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "absolute inset-0 flex items-center justify-center bg-black/50 text-xs font-medium text-white opacity-0 transition group-hover:opacity-100", children: labels.chooseImage })
1312
2593
  ]
1313
2594
  }
1314
2595
  ),
1315
- value && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2596
+ value && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1316
2597
  "button",
1317
2598
  {
1318
2599
  type: "button",
@@ -1320,7 +2601,7 @@ function ImageField({
1320
2601
  title: labels.imageFieldRemove,
1321
2602
  "aria-label": labels.imageFieldRemove,
1322
2603
  className: "absolute top-1 right-1 flex size-5 items-center justify-center rounded bg-black/60 text-white hover:bg-black/80",
1323
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.X, { className: "size-3" })
2604
+ children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react7.X, { className: "size-3" })
1324
2605
  }
1325
2606
  )
1326
2607
  ] });
@@ -1333,44 +2614,1246 @@ function imageUrl(value) {
1333
2614
  return /^[a-z-]+\(/i.test(v) ? null : v;
1334
2615
  }
1335
2616
 
1336
- // src/components/editor/EmailEditor.tsx
1337
- var import_client = require("react-dom/client");
1338
- var import_jsx_runtime13 = require("react/jsx-runtime");
1339
- var SEC_ID_RE = /\bsec-([A-Za-z0-9_-]+)\b/;
1340
- var OBJ_ID_RE = /\bobj-([A-Za-z0-9_-]+)\b/;
1341
- var TYPE_LABEL = {
1342
- "mj-text": "Text",
1343
- "mj-button": "Button",
1344
- "mj-image": "Image",
1345
- "mj-column": "Column"
1346
- };
1347
- function tagOf(c) {
1348
- return String(c.get("tagName") || c.get("type") || "");
1349
- }
1350
- var SPARKLES_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></svg>';
1351
- var IMAGE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10l-3.1-3.1a2 2 0 0 0-2.814.014L6 21"/><path d="m14 19.5 3-3 3 3"/><path d="M17 22v-5.5"/><circle cx="9" cy="9" r="2"/></svg>';
1352
- function isSection(c) {
1353
- return tagOf(c) === "mj-section";
2617
+ // src/components/editor/inspector/schema.ts
2618
+ var PX = ["px"];
2619
+ var PX_PCT = ["px", "%"];
2620
+ var LINE_HEIGHT = ["", "px", "%"];
2621
+ var LETTER_SPACING = ["px", "em"];
2622
+ var ALIGN = [
2623
+ { value: "left", label: "alignLeft" },
2624
+ { value: "center", label: "alignCenter" },
2625
+ { value: "right", label: "alignRight" }
2626
+ ];
2627
+ var VERTICAL_ALIGN = [
2628
+ { value: "top", label: "alignTop" },
2629
+ { value: "middle", label: "alignMiddle" },
2630
+ { value: "bottom", label: "alignBottom" }
2631
+ ];
2632
+ var FONTS = [
2633
+ { value: "Ubuntu, Helvetica, Arial, sans-serif", label: "Ubuntu" },
2634
+ { value: "Arial, Helvetica, sans-serif", label: "Arial" },
2635
+ { value: "Helvetica, Arial, sans-serif", label: "Helvetica" },
2636
+ { value: "Verdana, Geneva, sans-serif", label: "Verdana" },
2637
+ { value: "Tahoma, Geneva, sans-serif", label: "Tahoma" },
2638
+ { value: "'Trebuchet MS', Helvetica, sans-serif", label: "Trebuchet MS" },
2639
+ { value: "Georgia, 'Times New Roman', serif", label: "Georgia" },
2640
+ { value: "'Times New Roman', Times, serif", label: "Times New Roman" },
2641
+ { value: "'Courier New', Courier, monospace", label: "Courier New" }
2642
+ ];
2643
+ var WEIGHTS = [
2644
+ { value: "normal", label: "weightRegular" },
2645
+ { value: "500", label: "weightMedium" },
2646
+ { value: "600", label: "weightSemibold" },
2647
+ { value: "bold", label: "weightBold" }
2648
+ ];
2649
+ var TEXT_TRANSFORM = [
2650
+ { value: "none", label: "caseNone" },
2651
+ { value: "uppercase", label: "caseUpper" },
2652
+ { value: "lowercase", label: "caseLower" },
2653
+ { value: "capitalize", label: "caseCapitalize" }
2654
+ ];
2655
+ var TARGET = [
2656
+ { value: "_blank", label: "targetNewTab" },
2657
+ { value: "_self", label: "targetSameTab" }
2658
+ ];
2659
+ var BACKGROUND_POSITION = [
2660
+ { value: "top left", label: "positionTopLeft" },
2661
+ { value: "top center", label: "positionTopCenter" },
2662
+ { value: "top right", label: "positionTopRight" },
2663
+ { value: "center left", label: "positionCenterLeft" },
2664
+ { value: "center center", label: "positionCenter" },
2665
+ { value: "center right", label: "positionCenterRight" },
2666
+ { value: "bottom left", label: "positionBottomLeft" },
2667
+ { value: "bottom center", label: "positionBottomCenter" },
2668
+ { value: "bottom right", label: "positionBottomRight" }
2669
+ ];
2670
+ var NETWORKS = [
2671
+ { value: "facebook", label: "Facebook" },
2672
+ { value: "instagram", label: "Instagram" },
2673
+ { value: "x", label: "X" },
2674
+ { value: "linkedin", label: "LinkedIn" },
2675
+ { value: "youtube", label: "YouTube" },
2676
+ { value: "pinterest", label: "Pinterest" },
2677
+ { value: "github", label: "GitHub" },
2678
+ { value: "web", label: "networkWebsite" },
2679
+ { value: "snapchat", label: "Snapchat" },
2680
+ { value: "vimeo", label: "Vimeo" },
2681
+ { value: "medium", label: "Medium" },
2682
+ { value: "dribbble", label: "Dribbble" },
2683
+ { value: "soundcloud", label: "SoundCloud" },
2684
+ { value: "tumblr", label: "Tumblr" },
2685
+ { value: "xing", label: "Xing" },
2686
+ { value: "custom", label: "networkCustom" }
2687
+ ];
2688
+ var hasBackgroundImage = (a) => !!a["background-url"];
2689
+ var SECTION_FIELDS = [
2690
+ {
2691
+ attr: "full-width",
2692
+ label: "fullWidth",
2693
+ group: "layout",
2694
+ control: { type: "toggle", on: "full-width" }
2695
+ },
2696
+ {
2697
+ attr: "padding",
2698
+ label: "padding",
2699
+ group: "spacing",
2700
+ control: { type: "box", units: PX_PCT },
2701
+ default: "20px 0"
2702
+ },
2703
+ {
2704
+ attr: "background-color",
2705
+ label: "backgroundColor",
2706
+ group: "style",
2707
+ control: { type: "color" }
2708
+ },
2709
+ { attr: "background-url", label: "backgroundImage", group: "style", control: { type: "image" } },
2710
+ {
2711
+ attr: "background-size",
2712
+ label: "backgroundSize",
2713
+ group: "style",
2714
+ control: {
2715
+ type: "segmented",
2716
+ options: [
2717
+ { value: "cover", label: "sizeCover" },
2718
+ { value: "contain", label: "sizeContain" },
2719
+ { value: "auto", label: "sizeAuto" }
2720
+ ]
2721
+ },
2722
+ default: "auto",
2723
+ when: hasBackgroundImage
2724
+ },
2725
+ {
2726
+ attr: "background-repeat",
2727
+ label: "backgroundRepeat",
2728
+ group: "style",
2729
+ control: {
2730
+ type: "segmented",
2731
+ options: [
2732
+ { value: "repeat", label: "repeatTile" },
2733
+ { value: "no-repeat", label: "repeatOnce" }
2734
+ ]
2735
+ },
2736
+ default: "repeat",
2737
+ when: hasBackgroundImage
2738
+ },
2739
+ {
2740
+ attr: "background-position",
2741
+ label: "backgroundPosition",
2742
+ group: "style",
2743
+ control: { type: "select", options: BACKGROUND_POSITION },
2744
+ default: "top center",
2745
+ when: hasBackgroundImage
2746
+ },
2747
+ {
2748
+ attr: "border-radius",
2749
+ label: "cornerRadius",
2750
+ group: "style",
2751
+ control: { type: "length", units: PX_PCT }
2752
+ }
2753
+ ];
2754
+ var SCHEMA = {
2755
+ "mj-body": [
2756
+ { attr: "width", label: "contentWidth", group: "layout", control: { type: "length", units: PX }, default: "600px" },
2757
+ { attr: "background-color", label: "pageBackground", group: "style", control: { type: "color" } }
2758
+ ],
2759
+ "mj-wrapper": [
2760
+ ...SECTION_FIELDS,
2761
+ { attr: "gap", label: "gap", group: "advanced", control: { type: "length", units: PX } },
2762
+ { attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
2763
+ ],
2764
+ "mj-section": [
2765
+ ...SECTION_FIELDS,
2766
+ { attr: "text-align", label: "alignColumns", group: "advanced", control: { type: "segmented", options: ALIGN }, default: "center" },
2767
+ { attr: "direction", label: "reverseColumns", group: "advanced", control: { type: "toggle", on: "rtl" } },
2768
+ { attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
2769
+ ],
2770
+ "mj-column": [
2771
+ { attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN }, default: "top" },
2772
+ { attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT } },
2773
+ { attr: "background-color", label: "backgroundColor", group: "style", control: { type: "color" } },
2774
+ { attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
2775
+ { attr: "width", label: "width", group: "advanced", control: { type: "length", units: PX_PCT } },
2776
+ { attr: "inner-background-color", label: "innerBackground", group: "advanced", control: { type: "color" } },
2777
+ { attr: "inner-border-radius", label: "innerCornerRadius", group: "advanced", control: { type: "length", units: PX_PCT } },
2778
+ { attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } },
2779
+ { attr: "inner-border", label: "innerBorder", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" } }
2780
+ ],
2781
+ "mj-text": [
2782
+ {
2783
+ attr: "align",
2784
+ label: "align",
2785
+ group: "layout",
2786
+ control: { type: "segmented", options: [...ALIGN, { value: "justify", label: "alignJustify" }] },
2787
+ default: "left"
2788
+ },
2789
+ { attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
2790
+ { attr: "font-family", label: "font", group: "style", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
2791
+ { attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
2792
+ { attr: "font-weight", label: "fontWeight", group: "style", control: { type: "select", options: WEIGHTS } },
2793
+ { attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#000000" },
2794
+ { attr: "line-height", label: "lineHeight", group: "style", control: { type: "length", units: LINE_HEIGHT }, default: "1" },
2795
+ { attr: "container-background-color", label: "blockBackground", group: "style", control: { type: "color" } },
2796
+ { attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
2797
+ { attr: "text-transform", label: "textTransform", group: "advanced", control: { type: "select", options: TEXT_TRANSFORM } }
2798
+ ],
2799
+ "mj-button": [
2800
+ { attr: "href", label: "link", group: "content", control: { type: "link" } },
2801
+ { attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
2802
+ { attr: "inner-padding", label: "buttonPadding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
2803
+ { attr: "padding", label: "spaceAround", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
2804
+ { attr: "background-color", label: "buttonColor", group: "style", control: { type: "color" }, default: "#414141" },
2805
+ { attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#ffffff" },
2806
+ { attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
2807
+ { attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT }, default: "3px" },
2808
+ { attr: "width", label: "width", group: "advanced", control: { type: "length", units: PX_PCT } },
2809
+ { attr: "font-family", label: "font", group: "advanced", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
2810
+ { attr: "font-weight", label: "fontWeight", group: "advanced", control: { type: "select", options: WEIGHTS }, default: "normal" },
2811
+ { attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
2812
+ { attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
2813
+ { attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
2814
+ { attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } },
2815
+ { attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" }, default: "none" },
2816
+ { attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
2817
+ { attr: "text-transform", label: "textTransform", group: "advanced", control: { type: "select", options: TEXT_TRANSFORM }, default: "none" },
2818
+ { attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "120%" }
2819
+ ],
2820
+ "mj-image": [
2821
+ { attr: "src", label: "image", group: "content", control: { type: "image" } },
2822
+ { attr: "alt", label: "altText", group: "content", control: { type: "text" } },
2823
+ { attr: "href", label: "link", group: "content", control: { type: "link" } },
2824
+ { attr: "width", label: "width", group: "layout", control: { type: "length", units: PX } },
2825
+ { attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
2826
+ { attr: "fluid-on-mobile", label: "fluidOnMobile", group: "layout", control: { type: "toggle", on: "true" } },
2827
+ { attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
2828
+ { attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
2829
+ { attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
2830
+ { attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
2831
+ { attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
2832
+ { attr: "border", label: "border", group: "advanced", control: { type: "text", placeholder: "1px solid #cccccc" }, default: "0" },
2833
+ { attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
2834
+ ],
2835
+ "mj-divider": [
2836
+ { attr: "width", label: "width", group: "layout", control: { type: "length", units: PX_PCT }, default: "100%" },
2837
+ { attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
2838
+ { attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
2839
+ { attr: "border-color", label: "lineColor", group: "style", control: { type: "color" }, default: "#000000" },
2840
+ { attr: "border-width", label: "thickness", group: "style", control: { type: "length", units: PX }, default: "4px" },
2841
+ {
2842
+ attr: "border-style",
2843
+ label: "lineStyle",
2844
+ group: "style",
2845
+ control: {
2846
+ type: "segmented",
2847
+ options: [
2848
+ { value: "solid", label: "lineSolid" },
2849
+ { value: "dashed", label: "lineDashed" },
2850
+ { value: "dotted", label: "lineDotted" }
2851
+ ]
2852
+ },
2853
+ default: "solid"
2854
+ },
2855
+ { attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
2856
+ ],
2857
+ "mj-spacer": [
2858
+ { attr: "height", label: "height", group: "layout", control: { type: "length", units: PX }, default: "20px" },
2859
+ { attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
2860
+ ],
2861
+ "mj-social": [
2862
+ {
2863
+ attr: "mode",
2864
+ label: "socialMode",
2865
+ group: "layout",
2866
+ control: {
2867
+ type: "segmented",
2868
+ options: [
2869
+ { value: "horizontal", label: "modeHorizontal" },
2870
+ { value: "vertical", label: "modeVertical" }
2871
+ ]
2872
+ },
2873
+ default: "horizontal"
2874
+ },
2875
+ { attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
2876
+ { attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "10px 25px" },
2877
+ { attr: "inner-padding", label: "iconSpacing", group: "spacing", control: { type: "box", units: PX_PCT } },
2878
+ { attr: "icon-size", label: "iconSize", group: "style", control: { type: "length", units: PX_PCT }, default: "20px" },
2879
+ // A single value: mj-social's border-radius is not a 4-side shorthand.
2880
+ { attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT }, default: "3px" },
2881
+ { attr: "font-family", label: "font", group: "advanced", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
2882
+ { attr: "font-size", label: "fontSize", group: "advanced", control: { type: "length", units: PX }, default: "13px" },
2883
+ { attr: "color", label: "textColor", group: "advanced", control: { type: "color" }, default: "#333333" },
2884
+ { attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "22px" },
2885
+ { attr: "container-background-color", label: "blockBackground", group: "advanced", control: { type: "color" } }
2886
+ ],
2887
+ "mj-social-element": [
2888
+ { attr: "name", label: "network", group: "content", control: { type: "network", networks: NETWORKS } },
2889
+ { attr: "href", label: "link", group: "content", control: { type: "link" } },
2890
+ {
2891
+ attr: "src",
2892
+ label: "customIcon",
2893
+ group: "content",
2894
+ control: { type: "image" },
2895
+ when: (a) => (a.name ?? "").startsWith("custom") || !!a.src
2896
+ },
2897
+ { attr: "alt", label: "altText", group: "content", control: { type: "text" } },
2898
+ { attr: "background-color", label: "iconColor", group: "style", control: { type: "color" } },
2899
+ { attr: "icon-size", label: "iconSize", group: "advanced", control: { type: "length", units: PX_PCT } },
2900
+ { attr: "border-radius", label: "cornerRadius", group: "advanced", control: { type: "length", units: PX }, default: "3px" },
2901
+ { attr: "padding", label: "padding", group: "advanced", control: { type: "box", units: PX_PCT }, default: "4px" },
2902
+ { attr: "icon-position", label: "iconPosition", group: "advanced", control: { type: "segmented", options: [{ value: "left", label: "alignLeft" }, { value: "right", label: "alignRight" }] }, default: "left" },
2903
+ { attr: "title", label: "tooltip", group: "advanced", control: { type: "text" } },
2904
+ { attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
2905
+ { attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } }
2906
+ ],
2907
+ "mj-hero": [
2908
+ { attr: "background-url", label: "backgroundImage", group: "content", control: { type: "image" } },
2909
+ {
2910
+ attr: "mode",
2911
+ label: "heroMode",
2912
+ group: "content",
2913
+ control: {
2914
+ type: "segmented",
2915
+ options: [
2916
+ { value: "fixed-height", label: "heroFixed" },
2917
+ { value: "fluid-height", label: "heroFluid" }
2918
+ ]
2919
+ },
2920
+ default: "fixed-height"
2921
+ },
2922
+ {
2923
+ attr: "height",
2924
+ label: "height",
2925
+ group: "layout",
2926
+ control: { type: "length", units: PX_PCT },
2927
+ default: "0px",
2928
+ when: (a) => (a.mode ?? "fixed-height") === "fixed-height"
2929
+ },
2930
+ {
2931
+ attr: "background-width",
2932
+ label: "backgroundWidth",
2933
+ group: "layout",
2934
+ control: { type: "length", units: PX_PCT },
2935
+ when: (a) => a.mode === "fluid-height"
2936
+ },
2937
+ {
2938
+ attr: "background-height",
2939
+ label: "backgroundHeight",
2940
+ group: "layout",
2941
+ control: { type: "length", units: PX_PCT },
2942
+ when: (a) => a.mode === "fluid-height"
2943
+ },
2944
+ { attr: "vertical-align", label: "verticalAlign", group: "layout", control: { type: "segmented", options: VERTICAL_ALIGN }, default: "top" },
2945
+ { attr: "background-position", label: "backgroundPosition", group: "layout", control: { type: "select", options: BACKGROUND_POSITION }, default: "center center" },
2946
+ { attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "0px" },
2947
+ { attr: "background-color", label: "fallbackColor", group: "style", control: { type: "color" }, default: "#ffffff" },
2948
+ { attr: "border-radius", label: "cornerRadius", group: "style", control: { type: "length", units: PX_PCT } },
2949
+ { attr: "inner-padding", label: "contentPadding", group: "advanced", control: { type: "box", units: PX_PCT } },
2950
+ { attr: "inner-background-color", label: "contentBackground", group: "advanced", control: { type: "color" } }
2951
+ ],
2952
+ "mj-navbar": [
2953
+ { attr: "align", label: "align", group: "layout", control: { type: "segmented", options: ALIGN }, default: "center" },
2954
+ { attr: "hamburger", label: "hamburger", group: "layout", control: { type: "toggle", on: "hamburger" } },
2955
+ { attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT } },
2956
+ { attr: "ico-color", label: "menuIconColor", group: "advanced", control: { type: "color" }, default: "#000000", when: (a) => !!a.hamburger },
2957
+ { attr: "ico-font-size", label: "menuIconSize", group: "advanced", control: { type: "length", units: PX_PCT }, default: "30px", when: (a) => !!a.hamburger },
2958
+ { attr: "ico-align", label: "menuIconAlign", group: "advanced", control: { type: "segmented", options: ALIGN }, default: "center", when: (a) => !!a.hamburger },
2959
+ { attr: "base-url", label: "baseUrl", group: "advanced", control: { type: "link" } }
2960
+ ],
2961
+ "mj-navbar-link": [
2962
+ { attr: "href", label: "link", group: "content", control: { type: "link" } },
2963
+ { attr: "padding", label: "padding", group: "spacing", control: { type: "box", units: PX_PCT }, default: "15px 10px" },
2964
+ { attr: "color", label: "textColor", group: "style", control: { type: "color" }, default: "#000000" },
2965
+ { attr: "font-family", label: "font", group: "style", control: { type: "select", options: FONTS }, default: "Ubuntu, Helvetica, Arial, sans-serif" },
2966
+ { attr: "font-size", label: "fontSize", group: "style", control: { type: "length", units: PX }, default: "13px" },
2967
+ { attr: "font-weight", label: "fontWeight", group: "style", control: { type: "select", options: WEIGHTS }, default: "normal" },
2968
+ { attr: "text-transform", label: "textTransform", group: "style", control: { type: "select", options: TEXT_TRANSFORM }, default: "uppercase" },
2969
+ { attr: "target", label: "opensIn", group: "advanced", control: { type: "select", options: TARGET }, default: "_blank" },
2970
+ { attr: "rel", label: "rel", group: "advanced", control: { type: "text", placeholder: "noopener" } },
2971
+ { attr: "letter-spacing", label: "letterSpacing", group: "advanced", control: { type: "length", units: LETTER_SPACING, negative: true } },
2972
+ { attr: "line-height", label: "lineHeight", group: "advanced", control: { type: "length", units: LINE_HEIGHT }, default: "22px" }
2973
+ ]
2974
+ };
2975
+
2976
+ // src/components/editor/layers.ts
2977
+ var svg = (paths) => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${paths}</svg>`;
2978
+ var ICONS = {
2979
+ "mj-wrapper": svg(
2980
+ '<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><rect width="10" height="10" x="7" y="7" rx="1"/>'
2981
+ ),
2982
+ "mj-section": svg('<rect width="20" height="12" x="2" y="6" rx="2"/>'),
2983
+ "mj-hero": svg('<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/>'),
2984
+ "mj-group": svg(
2985
+ '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M15 3v18"/>'
2986
+ ),
2987
+ "mj-column": svg('<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M12 3v18"/>'),
2988
+ "mj-text": svg('<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>'),
2989
+ "mj-image": svg(
2990
+ '<rect width="18" height="18" x="3" y="3" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>'
2991
+ ),
2992
+ "mj-button": svg('<rect width="20" height="10" x="2" y="7" rx="3"/><path d="M8 12h8"/>'),
2993
+ "mj-divider": svg('<path d="M5 12h14"/>'),
2994
+ "mj-spacer": svg('<path d="M12 3v18"/><path d="m8 17 4 4 4-4"/><path d="m8 7 4-4 4 4"/>'),
2995
+ "mj-social": svg(
2996
+ '<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="m8.59 13.51 6.83 3.98"/><path d="m15.41 6.51-6.82 3.98"/>'
2997
+ ),
2998
+ "mj-social-element": svg(
2999
+ '<circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8"/>'
3000
+ ),
3001
+ "mj-navbar": svg('<path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h16"/>'),
3002
+ "mj-navbar-link": svg(
3003
+ '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>'
3004
+ ),
3005
+ "mj-raw": svg('<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>')
3006
+ };
3007
+ function tagOf(c) {
3008
+ return String(c.get("tagName") || c.get("type") || "");
1354
3009
  }
1355
- function isCommentable(c) {
1356
- const t = tagOf(c);
1357
- return t === "mj-section" || t in TYPE_LABEL;
3010
+ function textOf(c) {
3011
+ const template = document.createElement("template");
3012
+ template.innerHTML = c.getInnerHTML();
3013
+ template.content.querySelectorAll("br, p, div, li, tr, h1, h2, h3, h4, h5, h6").forEach((el) => el.after("\n"));
3014
+ const lines = (template.content.textContent ?? "").split("\n");
3015
+ const first = lines.map((l) => l.replace(/\s+/g, " ").trim()).find(Boolean) ?? "";
3016
+ return first.slice(0, 60);
1358
3017
  }
1359
- function classMatch(c, re) {
1360
- const cssClass = String(c.getAttributes()["css-class"] ?? "");
1361
- return cssClass.match(re)?.[1] ?? null;
3018
+ function escapeHtml(s) {
3019
+ return s.replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
3020
+ }
3021
+ function layerName(c, labels) {
3022
+ const tag = tagOf(c);
3023
+ const kind = labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
3024
+ const attributes = c.getAttributes();
3025
+ switch (tag) {
3026
+ case "mj-text":
3027
+ case "mj-button":
3028
+ case "mj-navbar-link":
3029
+ return textOf(c) || kind;
3030
+ case "mj-image":
3031
+ return attributes.alt?.trim() || kind;
3032
+ case "mj-social-element": {
3033
+ const { network } = parseNetwork(String(attributes.name ?? ""));
3034
+ const option = NETWORKS.find((o) => o.value === network && network !== "custom");
3035
+ return textOf(c) || option && (labels.inspectorLabels[option.label] ?? option.label) || kind;
3036
+ }
3037
+ case "mj-section":
3038
+ case "mj-wrapper":
3039
+ case "mj-hero": {
3040
+ let words = "";
3041
+ c.onAll((d) => {
3042
+ if (!words && d !== c && ["mj-text", "mj-button"].includes(tagOf(d))) words = textOf(d);
3043
+ });
3044
+ return words || kind;
3045
+ }
3046
+ case "mj-column": {
3047
+ const columns = c.parent()?.components().filter((s) => tagOf(s) === "mj-column") ?? [];
3048
+ return columns.length > 1 ? `${kind} ${columns.indexOf(c) + 1}` : kind;
3049
+ }
3050
+ default:
3051
+ return kind;
3052
+ }
3053
+ }
3054
+ var CONTENT = /* @__PURE__ */ new Set(["mj-text", "mj-button", "mj-navbar-link", "mj-social-element", "mj-raw"]);
3055
+ function badgeLabel(labels) {
3056
+ return (c) => {
3057
+ const tag = tagOf(c);
3058
+ const icon = ICONS[tag] ? `<div class="gjs-badge__icon">${ICONS[tag]}</div>` : "";
3059
+ const kind = labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
3060
+ return `${icon}<div class="gjs-badge__name">${escapeHtml(kind)}</div>`;
3061
+ };
3062
+ }
3063
+ function setupLayerNames(editor, labels) {
3064
+ const hideContent = (c) => c.onAll((d) => d !== c && d.set("layerable", false, { avoidStore: true }));
3065
+ for (const type of editor.Components.getTypes()) {
3066
+ if (!type.id.startsWith("mj-")) continue;
3067
+ editor.Components.addType(type.id, {
3068
+ model: {
3069
+ // Ours whatever the element says: GrapesJS writes it into the row's markup as it is.
3070
+ getIcon() {
3071
+ return ICONS[type.id] ?? "";
3072
+ },
3073
+ // GrapesJS writes the name into the row's markup unescaped, and it is document text.
3074
+ getName() {
3075
+ return escapeHtml(layerName(this, labels));
3076
+ },
3077
+ ...CONTENT.has(type.id) && {
3078
+ // Editing a text replaces its children, so they are hidden again as they arrive.
3079
+ init() {
3080
+ hideContent(this);
3081
+ this.listenTo(this.components(), "add reset", () => hideContent(this));
3082
+ }
3083
+ }
3084
+ },
3085
+ // Runs the plugin's own `init` as well.
3086
+ extendFn: ["init"]
3087
+ });
3088
+ }
3089
+ let pending = false;
3090
+ editor.on("update component:add component:remove", () => {
3091
+ if (pending) return;
3092
+ pending = true;
3093
+ requestAnimationFrame(() => {
3094
+ pending = false;
3095
+ editor.Layers.getRoot()?.onAll((c) => {
3096
+ const view = c.viewLayer;
3097
+ const el = view?.getInputName?.();
3098
+ const name = layerName(c, labels);
3099
+ if (!el || el.textContent === name) return;
3100
+ el.textContent = name;
3101
+ el.parentElement?.setAttribute("title", name);
3102
+ });
3103
+ });
3104
+ });
3105
+ }
3106
+
3107
+ // src/components/editor/inspector/StructureSection.tsx
3108
+ var import_lucide_react8 = require("lucide-react");
3109
+
3110
+ // src/components/editor/inspector/structure.ts
3111
+ function tagOf2(c) {
3112
+ return String(c.get("tagName") || c.get("type") || "");
3113
+ }
3114
+ var COLUMN_RATIOS = {
3115
+ 2: [
3116
+ [1, 1],
3117
+ [1, 2],
3118
+ [2, 1]
3119
+ ],
3120
+ 3: [
3121
+ [1, 1, 1],
3122
+ [1, 2, 1]
3123
+ ]
3124
+ };
3125
+ function columnsOf(section) {
3126
+ const children = section.components().models;
3127
+ return children.length > 0 && children.every((c) => tagOf2(c) === "mj-column") ? children : null;
3128
+ }
3129
+ function ratioWidths(ratio) {
3130
+ if (ratio.every((r) => r === ratio[0])) return null;
3131
+ const total = ratio.reduce((a, b) => a + b, 0);
3132
+ return ratio.map((r) => `${Math.round(r / total * 1e4) / 100}%`);
3133
+ }
3134
+ function currentRatio(columns) {
3135
+ const widths = columns.map((c) => String(c.getAttributes().width ?? ""));
3136
+ return COLUMN_RATIOS[columns.length]?.find((ratio) => {
3137
+ const want = ratioWidths(ratio);
3138
+ if (!want) return widths.every((w) => w === "");
3139
+ return widths.every(
3140
+ (w, i) => /%$/.test(w) && Math.abs(parseFloat(w) - parseFloat(want[i])) < 0.5
3141
+ );
3142
+ }) ?? null;
3143
+ }
3144
+ function setRatio(columns, ratio) {
3145
+ const widths = ratioWidths(ratio);
3146
+ columns.forEach((c, i) => {
3147
+ if (widths) c.addAttributes({ width: widths[i] });
3148
+ else c.removeAttributes(["width"]);
3149
+ });
3150
+ }
3151
+ var PROFILE_URLS = {
3152
+ facebook: "https://facebook.com/",
3153
+ instagram: "https://instagram.com/",
3154
+ x: "https://x.com/",
3155
+ linkedin: "https://linkedin.com/",
3156
+ youtube: "https://youtube.com/",
3157
+ pinterest: "https://pinterest.com/",
3158
+ github: "https://github.com/"
3159
+ };
3160
+ function addNetwork(social) {
3161
+ const present = new Set(
3162
+ social.components().map((c) => String(c.getAttributes().name ?? "").replace(/-noshare$/, ""))
3163
+ );
3164
+ const network = Object.keys(PROFILE_URLS).find((n) => !present.has(n)) ?? "facebook";
3165
+ return social.append(
3166
+ `<mj-social-element name="${network}-noshare" href="${PROFILE_URLS[network]}"></mj-social-element>`
3167
+ )[0];
3168
+ }
3169
+ function addNavbarLink(navbar, text) {
3170
+ return navbar.append(`<mj-navbar-link>${escapeHtml2(text)}</mj-navbar-link>`)[0];
3171
+ }
3172
+ function escapeHtml2(s) {
3173
+ return s.replace(/[&<>"']/g, (ch) => `&#${ch.charCodeAt(0)};`);
3174
+ }
3175
+
3176
+ // src/components/editor/inspector/StructureSection.tsx
3177
+ var import_jsx_runtime15 = require("react/jsx-runtime");
3178
+ function tagOf3(c) {
3179
+ return String(c.get("tagName") || c.get("type") || "");
3180
+ }
3181
+ function StructureSection({
3182
+ editor,
3183
+ component,
3184
+ labels,
3185
+ locked
3186
+ }) {
3187
+ switch (tagOf3(component)) {
3188
+ case "mj-section":
3189
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ColumnWidths, { component, labels, locked });
3190
+ case "mj-social":
3191
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3192
+ Children,
3193
+ {
3194
+ editor,
3195
+ component,
3196
+ labels,
3197
+ locked,
3198
+ title: labels.socialNetworks,
3199
+ add: labels.addNetwork,
3200
+ icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.AtSign, { className: "size-3.5 shrink-0" }),
3201
+ onAdd: () => editor.select(addNetwork(component))
3202
+ }
3203
+ );
3204
+ case "mj-navbar":
3205
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3206
+ Children,
3207
+ {
3208
+ editor,
3209
+ component,
3210
+ labels,
3211
+ locked,
3212
+ title: labels.navbarLinks,
3213
+ add: labels.addLink,
3214
+ icon: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.Link2, { className: "size-3.5 shrink-0" }),
3215
+ onAdd: () => editor.select(addNavbarLink(component, labels.newLinkText))
3216
+ }
3217
+ );
3218
+ default:
3219
+ return null;
3220
+ }
3221
+ }
3222
+ function Heading({ children }) {
3223
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h3", { className: "px-3 pb-1 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase", children });
3224
+ }
3225
+ function ColumnWidths({
3226
+ component,
3227
+ labels,
3228
+ locked
3229
+ }) {
3230
+ const columns = columnsOf(component);
3231
+ const ratios = columns && COLUMN_RATIOS[columns.length];
3232
+ if (!columns || !ratios) return null;
3233
+ const current = currentRatio(columns);
3234
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("section", { className: "border-b border-panel-border/60 py-2", children: [
3235
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Heading, { children: labels.columnWidths }),
3236
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex gap-1.5 px-3 py-1", children: ratios.map((ratio) => {
3237
+ const name = labels.columnRatio(ratio.join(":"));
3238
+ const active = current?.join() === ratio.join();
3239
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3240
+ "button",
3241
+ {
3242
+ type: "button",
3243
+ disabled: locked,
3244
+ "aria-pressed": active,
3245
+ title: name,
3246
+ "aria-label": name,
3247
+ onClick: () => !active && setRatio(columns, ratio),
3248
+ className: cn(
3249
+ "flex h-8 flex-1 items-center gap-0.5 rounded-md border px-1.5 transition-colors",
3250
+ active ? "border-brand bg-brand/15" : "border-panel-border bg-panel-elevated hover:border-panel-muted-fg"
3251
+ ),
3252
+ children: ratio.map((part, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3253
+ "span",
3254
+ {
3255
+ style: { flexGrow: part },
3256
+ className: cn(
3257
+ "h-4 basis-0 rounded-sm",
3258
+ active ? "bg-brand" : "bg-panel-muted-fg/50"
3259
+ )
3260
+ },
3261
+ i
3262
+ ))
3263
+ },
3264
+ ratio.join()
3265
+ );
3266
+ }) })
3267
+ ] });
3268
+ }
3269
+ function Children({
3270
+ editor,
3271
+ component,
3272
+ labels,
3273
+ locked,
3274
+ title,
3275
+ add,
3276
+ icon,
3277
+ onAdd
3278
+ }) {
3279
+ const children = component.components().models;
3280
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("section", { className: "border-b border-panel-border/60 py-2", children: [
3281
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Heading, { children: title }),
3282
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ul", { className: "flex flex-col px-2", children: children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3283
+ "button",
3284
+ {
3285
+ type: "button",
3286
+ onClick: () => editor.select(child),
3287
+ className: "group flex w-full items-center gap-2 rounded-md px-1.5 py-1 text-left text-xs text-panel-fg hover:bg-panel-hover",
3288
+ children: [
3289
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-panel-muted-fg", children: icon }),
3290
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "min-w-0 flex-1 truncate", children: layerName(child, labels) }),
3291
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.ChevronRight, { className: "size-3 shrink-0 text-panel-muted-fg opacity-0 group-hover:opacity-100" })
3292
+ ]
3293
+ }
3294
+ ) }, child.cid)) }),
3295
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
3296
+ "button",
3297
+ {
3298
+ type: "button",
3299
+ disabled: locked,
3300
+ onClick: onAdd,
3301
+ className: "mx-3 mt-1 flex items-center gap-1.5 rounded-md px-1 py-1 text-xs text-panel-muted-fg hover:text-panel-fg disabled:opacity-50",
3302
+ children: [
3303
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.Plus, { className: "size-3.5" }),
3304
+ add
3305
+ ]
3306
+ }
3307
+ )
3308
+ ] });
3309
+ }
3310
+
3311
+ // src/components/editor/inspector/write.ts
3312
+ function same(a, b) {
3313
+ const keys = Object.keys(a);
3314
+ return keys.length === Object.keys(b).length && keys.every((k) => k in b && a[k] === b[k]);
3315
+ }
3316
+ function createWriter(editor, component, blocked = () => false) {
3317
+ const isImage = String(component.get("tagName") || component.get("type")) === "mj-image";
3318
+ const current = () => {
3319
+ const { __p: _marker, style: _style, ...rest } = component.get("attributes");
3320
+ return rest;
3321
+ };
3322
+ let drag = null;
3323
+ const record = (start, end) => {
3324
+ component.setAttributes(start, { avoidStore: true });
3325
+ if (!same(end, start)) component.setAttributes(end);
3326
+ delete component.get("attributes").__p;
3327
+ };
3328
+ return async (attr, value, { partial, remove = [] } = {}) => {
3329
+ if (editor.getEditing() === component) {
3330
+ const view = component.getView();
3331
+ await view?.syncContent?.();
3332
+ }
3333
+ if (blocked()) return;
3334
+ if (drag && drag.attr !== attr) {
3335
+ const { start: start2 } = drag;
3336
+ drag = null;
3337
+ record(start2, current());
3338
+ }
3339
+ if (attr === "src" && isImage) return void component.set({ src: value });
3340
+ const next = current();
3341
+ for (const name of remove) delete next[name];
3342
+ if (value === "") delete next[attr];
3343
+ else next[attr] = value;
3344
+ if (partial) {
3345
+ drag ??= { attr, start: current() };
3346
+ component.setAttributes(next, { avoidStore: true });
3347
+ return;
3348
+ }
3349
+ const start = drag?.start ?? current();
3350
+ drag = null;
3351
+ record(start, next);
3352
+ };
3353
+ }
3354
+
3355
+ // src/components/editor/inspector/Inspector.tsx
3356
+ var import_jsx_runtime16 = require("react/jsx-runtime");
3357
+ var GROUPS = ["content", "layout", "spacing", "style"];
3358
+ var HIDDEN = /* @__PURE__ */ new Set(["css-class", "mj-class", "id", "style"]);
3359
+ function tagOf4(c) {
3360
+ return String(c.get("tagName") || c.get("type") || "");
3361
+ }
3362
+ function attributesOf(c) {
3363
+ const out = {};
3364
+ for (const [k, v] of Object.entries(c.getAttributes())) {
3365
+ if (v !== void 0 && v !== null && v !== false) out[k] = String(v);
3366
+ }
3367
+ if (tagOf4(c) === "mj-image") out.src = String(c.get("src") ?? "");
3368
+ return out;
3369
+ }
3370
+ function documentColors(editor) {
3371
+ const counts = /* @__PURE__ */ new Map();
3372
+ const colorValue = /(?:color|background|border)[\w-]*\s*[:=]\s*["']?[^"';>{}]*?(#[0-9a-f]{3,8}\b|rgba?\([^)]*\))/gi;
3373
+ for (const match of editor.getHtml().matchAll(colorValue)) {
3374
+ const hex = toHex(match[1], { opaque: true });
3375
+ if (hex) counts.set(hex, (counts.get(hex) ?? 0) + 1);
3376
+ }
3377
+ return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 14).map(([hex]) => hex);
3378
+ }
3379
+ function Inspector({
3380
+ onEmailSettings,
3381
+ empty
3382
+ }) {
3383
+ const editor = (0, import_react8.useEditorMaybe)();
3384
+ const labels = useLabels();
3385
+ const { locked } = useAgentTurn();
3386
+ const lockedNow = (0, import_react7.useRef)(locked);
3387
+ (0, import_react7.useLayoutEffect)(() => {
3388
+ lockedNow.current = locked;
3389
+ });
3390
+ const [, refresh] = (0, import_react7.useReducer)((n) => n + 1, 0);
3391
+ const root = (0, import_react7.useRef)(null);
3392
+ const refocus = (0, import_react7.useRef)(false);
3393
+ (0, import_react7.useEffect)(() => {
3394
+ if (!editor) return;
3395
+ const events = "component:update change:device";
3396
+ const toggled = () => {
3397
+ if (root.current?.contains(document.activeElement)) refocus.current = true;
3398
+ refresh();
3399
+ };
3400
+ editor.on(events, refresh);
3401
+ editor.on("component:toggled", toggled);
3402
+ return () => {
3403
+ editor.off(events, refresh);
3404
+ editor.off("component:toggled", toggled);
3405
+ };
3406
+ }, [editor]);
3407
+ (0, import_react7.useEffect)(() => {
3408
+ if (!refocus.current) return;
3409
+ refocus.current = false;
3410
+ root.current?.querySelector("[data-focus-target]")?.focus();
3411
+ });
3412
+ if (!editor) return null;
3413
+ const selected = editor.getSelectedAll();
3414
+ const component = selected[0];
3415
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: root, children: selected.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { "data-focus-target": true, tabIndex: -1, className: "outline-none", children: empty }) : selected.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3416
+ "p",
3417
+ {
3418
+ "data-focus-target": true,
3419
+ tabIndex: -1,
3420
+ className: "px-4 py-8 text-center text-xs text-panel-muted-fg outline-none",
3421
+ children: labels.multipleSelected(selected.length)
3422
+ }
3423
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3424
+ ElementSettings,
3425
+ {
3426
+ editor,
3427
+ component,
3428
+ labels,
3429
+ locked,
3430
+ lockedNow,
3431
+ onEmailSettings
3432
+ },
3433
+ component.cid
3434
+ ) });
3435
+ }
3436
+ function ElementSettings({
3437
+ editor,
3438
+ component,
3439
+ labels,
3440
+ locked,
3441
+ lockedNow,
3442
+ onEmailSettings
3443
+ }) {
3444
+ const [advancedOpen, setAdvancedOpen] = (0, import_react7.useState)(false);
3445
+ const tag = tagOf4(component);
3446
+ const fields = SCHEMA[tag] ?? [];
3447
+ const attributes = attributesOf(component);
3448
+ const visible = fields.filter((f) => !f.when || f.when(attributes));
3449
+ const t = (key) => labels.inspectorLabels[key] ?? key;
3450
+ const optionName = (o) => labels.inspectorLabels[o.label] ?? o.label;
3451
+ const [writer] = (0, import_react7.useState)(() => createWriter(editor, component, () => lockedNow.current));
3452
+ const write = (attr, value, options) => lockedNow.current ? Promise.resolve() : writer(attr, value, options);
3453
+ const writeBox = (attr, shorthand, partial) => write(attr, shorthand, { remove: SIDES.map((s) => `${attr}-${s}`), partial });
3454
+ const chooseImage = (attr) => {
3455
+ if (attr === "src" && tag === "mj-image") return void editor.runCommand("open-image-picker");
3456
+ const am = editor.AssetManager;
3457
+ am.open({
3458
+ types: ["image"],
3459
+ current: imageUrl(attributes[attr] ?? "") ?? void 0,
3460
+ select(asset, complete) {
3461
+ void write(attr, typeof asset === "string" ? asset : asset.getSrc());
3462
+ if (complete) am.close();
3463
+ }
3464
+ });
3465
+ };
3466
+ const safeLink = (attr) => (link) => {
3467
+ if (tag === "mj-navbar-link" && attr === "href") {
3468
+ return isSafeNavbarLink(String(component.parent()?.getAttributes()["base-url"] ?? ""), link);
3469
+ }
3470
+ if (tag === "mj-navbar" && attr === "base-url") {
3471
+ return component.components().every((c) => isSafeNavbarLink(link, String(c.getAttributes().href ?? "")));
3472
+ }
3473
+ return isSafeLink(link);
3474
+ };
3475
+ const control = (f) => {
3476
+ const value = attributes[f.attr] ?? "";
3477
+ const c = f.control;
3478
+ switch (c.type) {
3479
+ case "text":
3480
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3481
+ TextInput,
3482
+ {
3483
+ value,
3484
+ placeholder: c.placeholder ?? f.default,
3485
+ onCommit: (v) => write(f.attr, v)
3486
+ }
3487
+ );
3488
+ case "link":
3489
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3490
+ TextInput,
3491
+ {
3492
+ value,
3493
+ placeholder: "https://",
3494
+ accept: safeLink(f.attr),
3495
+ onCommit: (v) => write(f.attr, v)
3496
+ }
3497
+ );
3498
+ case "color":
3499
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3500
+ ColorField,
3501
+ {
3502
+ value,
3503
+ placeholder: f.default,
3504
+ documentColors: () => documentColors(editor),
3505
+ labels,
3506
+ onChange: (v, partial) => write(f.attr, v, { partial })
3507
+ }
3508
+ );
3509
+ case "image":
3510
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3511
+ ImageField,
3512
+ {
3513
+ value,
3514
+ labels,
3515
+ onChoose: () => chooseImage(f.attr),
3516
+ onClear: () => write(f.attr, "")
3517
+ }
3518
+ );
3519
+ case "length":
3520
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3521
+ LengthInput,
3522
+ {
3523
+ value,
3524
+ placeholder: f.default,
3525
+ units: c.units,
3526
+ negative: c.negative,
3527
+ labels,
3528
+ onCommit: (v, partial) => write(f.attr, v, { partial })
3529
+ }
3530
+ );
3531
+ case "box":
3532
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3533
+ BoxInput,
3534
+ {
3535
+ attributes,
3536
+ attr: f.attr,
3537
+ placeholder: f.default,
3538
+ units: c.units,
3539
+ labels,
3540
+ onCommit: (v, partial) => writeBox(f.attr, v, partial)
3541
+ }
3542
+ );
3543
+ case "segmented":
3544
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3545
+ Segmented,
3546
+ {
3547
+ value,
3548
+ fallback: f.default,
3549
+ options: c.options,
3550
+ name: optionName,
3551
+ onCommit: (v) => write(f.attr, v)
3552
+ }
3553
+ );
3554
+ case "select":
3555
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3556
+ Select,
3557
+ {
3558
+ value,
3559
+ fallback: f.default,
3560
+ options: c.options,
3561
+ name: optionName,
3562
+ onCommit: (v) => write(f.attr, v)
3563
+ }
3564
+ );
3565
+ case "toggle":
3566
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3567
+ Toggle,
3568
+ {
3569
+ checked: value === c.on,
3570
+ label: t(f.label),
3571
+ onCommit: (on) => write(f.attr, on ? c.on : "")
3572
+ }
3573
+ );
3574
+ case "network": {
3575
+ const { network, share } = parseNetwork(value);
3576
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-col gap-1.5", children: [
3577
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3578
+ Select,
3579
+ {
3580
+ value: network,
3581
+ options: c.networks,
3582
+ name: optionName,
3583
+ onCommit: (n) => write(f.attr, n ? networkName(n, share) : "")
3584
+ }
3585
+ ),
3586
+ network && network !== "custom" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3587
+ Segmented,
3588
+ {
3589
+ value: share ? "share" : "profile",
3590
+ options: [
3591
+ { value: "profile", label: labels.linkTypeProfile },
3592
+ { value: "share", label: labels.linkTypeShare }
3593
+ ],
3594
+ name: (o) => o.label,
3595
+ onCommit: (v) => write(f.attr, networkName(network, v === "share"))
3596
+ }
3597
+ )
3598
+ ] });
3599
+ }
3600
+ }
3601
+ };
3602
+ const own = (f) => f.control.type === "box" ? [f.attr, ...SIDES.map((s) => `${f.attr}-${s}`)].some((a) => attributes[a] !== void 0) : !!attributes[f.attr];
3603
+ const reset = (f) => f.control.type === "box" ? writeBox(f.attr, "") : write(f.attr, "");
3604
+ const wide = (f) => ["color", "image", "link", "text", "box", "network"].includes(f.control.type);
3605
+ const row = (f) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3606
+ Row,
3607
+ {
3608
+ label: t(f.label),
3609
+ help: labels.inspectorLabels[`${f.label}.help`],
3610
+ own: own(f),
3611
+ wide: wide(f),
3612
+ inline: f.control.type === "toggle",
3613
+ labels,
3614
+ onReset: () => void reset(f),
3615
+ children: control(f)
3616
+ },
3617
+ f.attr
3618
+ );
3619
+ const named = new Set(
3620
+ fields.flatMap(
3621
+ (f) => f.control.type === "box" ? [f.attr, ...SIDES.map((s) => `${f.attr}-${s}`)] : [f.attr]
3622
+ )
3623
+ );
3624
+ const others = Object.entries(attributes).filter(([k]) => !named.has(k) && !HIDDEN.has(k));
3625
+ const advanced = visible.filter((f) => f.group === "advanced");
3626
+ const advancedCount = advanced.length + others.length;
3627
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "pb-6", children: [
3628
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3629
+ Header,
3630
+ {
3631
+ editor,
3632
+ component,
3633
+ labels,
3634
+ onEmailSettings
3635
+ }
3636
+ ),
3637
+ editor.getDevice() !== "Desktop" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "mx-3 mt-2 flex items-center gap-1.5 rounded-md bg-panel-elevated px-2 py-1.5 text-[11px] text-panel-muted-fg", children: [
3638
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.Monitor, { className: "size-3.5 shrink-0" }),
3639
+ labels.allScreenSizes
3640
+ ] }),
3641
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StructureSection, { editor, component, labels, locked }),
3642
+ GROUPS.map((group) => {
3643
+ const inGroup = visible.filter((f) => f.group === group);
3644
+ if (inGroup.length === 0) return null;
3645
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("section", { className: "border-b border-panel-border/60 py-2", children: [
3646
+ /* @__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) }),
3647
+ inGroup.map(row)
3648
+ ] }, group);
3649
+ }),
3650
+ advancedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("section", { className: "py-1", children: [
3651
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3652
+ "button",
3653
+ {
3654
+ type: "button",
3655
+ onClick: () => setAdvancedOpen((o) => !o),
3656
+ "aria-expanded": advancedOpen,
3657
+ className: "flex w-full items-center gap-1 px-3 py-2 text-[10px] font-semibold tracking-wider text-panel-muted-fg/80 uppercase hover:text-panel-fg",
3658
+ children: [
3659
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3660
+ import_lucide_react9.ChevronRight,
3661
+ {
3662
+ className: cn("size-3 transition-transform", advancedOpen && "rotate-90")
3663
+ }
3664
+ ),
3665
+ labels.groupAdvanced,
3666
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("span", { className: "font-normal normal-case", children: [
3667
+ "(",
3668
+ advancedCount,
3669
+ ")"
3670
+ ] })
3671
+ ]
3672
+ }
3673
+ ),
3674
+ advancedOpen && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3675
+ advanced.map(row),
3676
+ others.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "px-3 pt-2", children: [
3677
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "pb-1 text-[11px] text-panel-muted-fg", children: labels.otherAttributes }),
3678
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "flex flex-col gap-1", children: others.map(([k, v]) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
3679
+ "li",
3680
+ {
3681
+ className: "flex items-center gap-2 rounded-md bg-panel-elevated px-2 py-1 font-mono text-[11px]",
3682
+ children: [
3683
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-panel-muted-fg", children: k }),
3684
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "min-w-0 flex-1 truncate text-panel-fg", title: v, children: v }),
3685
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3686
+ "button",
3687
+ {
3688
+ type: "button",
3689
+ onClick: () => void write(k, ""),
3690
+ title: labels.removeAttribute,
3691
+ "aria-label": `${labels.removeAttribute}: ${k}`,
3692
+ className: "text-panel-muted-fg hover:text-panel-fg",
3693
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.X, { className: "size-3" })
3694
+ }
3695
+ )
3696
+ ]
3697
+ },
3698
+ k
3699
+ )) })
3700
+ ] })
3701
+ ] })
3702
+ ] })
3703
+ ] });
1362
3704
  }
3705
+ function groupName(group, labels) {
3706
+ return {
3707
+ content: labels.groupContent,
3708
+ layout: labels.groupLayout,
3709
+ spacing: labels.groupSpacing,
3710
+ style: labels.groupStyle
3711
+ }[group];
3712
+ }
3713
+ function Header({
3714
+ editor,
3715
+ component,
3716
+ labels,
3717
+ onEmailSettings
3718
+ }) {
3719
+ const tag = tagOf4(component);
3720
+ const isBody = tag === "mj-body";
3721
+ const name = isBody ? labels.emailSettings : labels.elementNames[tag] ?? tag.replace(/^mj-/, "");
3722
+ const words = tag === "mj-column" ? "" : layerName(component, labels);
3723
+ const excerpt = words === name ? "" : words;
3724
+ const parent = component.parent();
3725
+ const action = "flex size-7 items-center justify-center rounded-md text-panel-muted-fg hover:bg-panel-hover hover:text-panel-fg";
3726
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "sticky top-0 z-10 flex items-center gap-2 border-b border-panel-border bg-panel px-3 py-2", children: [
3727
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "min-w-0 flex-1", children: [
3728
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3729
+ "p",
3730
+ {
3731
+ "data-focus-target": true,
3732
+ tabIndex: -1,
3733
+ className: "truncate text-sm font-medium text-panel-fg outline-none",
3734
+ children: name
3735
+ }
3736
+ ),
3737
+ excerpt && !isBody && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "truncate text-[11px] text-panel-muted-fg", children: [
3738
+ "\u201C",
3739
+ excerpt,
3740
+ "\u201D"
3741
+ ] })
3742
+ ] }),
3743
+ !isBody && parent && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3744
+ "button",
3745
+ {
3746
+ type: "button",
3747
+ className: action,
3748
+ title: labels.selectParent,
3749
+ "aria-label": labels.selectParent,
3750
+ onClick: () => tagOf4(parent) === "mj-body" ? onEmailSettings() : editor.select(parent),
3751
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.ArrowUp, { className: "size-3.5" })
3752
+ }
3753
+ ),
3754
+ !isBody && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
3755
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3756
+ "button",
3757
+ {
3758
+ type: "button",
3759
+ className: action,
3760
+ title: labels.duplicate,
3761
+ "aria-label": labels.duplicate,
3762
+ onClick: () => editor.runCommand("tlb-clone"),
3763
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.Copy, { className: "size-3.5" })
3764
+ }
3765
+ ),
3766
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3767
+ "button",
3768
+ {
3769
+ type: "button",
3770
+ className: action,
3771
+ title: labels.deleteElement,
3772
+ "aria-label": labels.deleteElement,
3773
+ onClick: () => editor.runCommand("tlb-delete"),
3774
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react9.Trash2, { className: "size-3.5" })
3775
+ }
3776
+ )
3777
+ ] })
3778
+ ] });
3779
+ }
3780
+
3781
+ // src/components/editor/ids.ts
3782
+ var ID_RE = { sec: /\bsec-([A-Za-z0-9_-]+)\b/, obj: /\bobj-([A-Za-z0-9_-]+)\b/ };
3783
+ function idOf(c, prefix) {
3784
+ return String(c.getAttributes()["css-class"] ?? "").match(ID_RE[prefix])?.[1] ?? null;
3785
+ }
3786
+ var newId = () => Math.random().toString(36).slice(2, 10);
1363
3787
  function sectionIdOf(c) {
1364
- return classMatch(c, SEC_ID_RE);
3788
+ return idOf(c, "sec");
1365
3789
  }
1366
3790
  function addIdClass(c, prefix) {
1367
- const existing = classMatch(c, prefix === "sec" ? SEC_ID_RE : OBJ_ID_RE);
3791
+ const existing = idOf(c, prefix);
1368
3792
  if (existing) return existing;
1369
3793
  const cssClass = String(c.getAttributes()["css-class"] ?? "");
1370
- const id = Math.random().toString(36).slice(2, 10);
3794
+ const id = newId();
1371
3795
  c.addAttributes({ "css-class": `${cssClass} ${prefix}-${id}`.trim() });
1372
3796
  return id;
1373
3797
  }
3798
+ function freshenCopiedIds(root, added) {
3799
+ const used = (prefix, id, except) => {
3800
+ let found = false;
3801
+ root?.onAll((o) => {
3802
+ if (o !== except && idOf(o, prefix) === id) found = true;
3803
+ });
3804
+ return found;
3805
+ };
3806
+ added.onAll((c) => {
3807
+ for (const prefix of ["sec", "obj"]) {
3808
+ const id = idOf(c, prefix);
3809
+ if (!id || !used(prefix, id, c)) continue;
3810
+ const cssClass = String(c.getAttributes()["css-class"]);
3811
+ c.addAttributes({ "css-class": cssClass.replace(`${prefix}-${id}`, `${prefix}-${newId()}`) });
3812
+ }
3813
+ });
3814
+ }
3815
+
3816
+ // src/components/editor/plugin-types.ts
3817
+ function fixPluginTypes(editor) {
3818
+ for (const type of editor.Components.getTypes()) {
3819
+ if (!type.id.startsWith("mj-")) continue;
3820
+ const base = type.model.prototype.getAttrToHTML;
3821
+ editor.Components.addType(type.id, {
3822
+ model: {
3823
+ defaults: { "style-default": {} },
3824
+ getAttrToHTML() {
3825
+ const attributes = base.call(this);
3826
+ delete attributes.__p;
3827
+ for (const [name, value] of Object.entries(attributes)) {
3828
+ if (typeof value === "string") attributes[name] = value.replace(/"/g, "&quot;");
3829
+ }
3830
+ return attributes;
3831
+ }
3832
+ }
3833
+ });
3834
+ }
3835
+ }
3836
+
3837
+ // src/components/editor/EmailEditor.tsx
3838
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3839
+ var TYPE_LABEL = {
3840
+ "mj-text": "Text",
3841
+ "mj-button": "Button",
3842
+ "mj-image": "Image",
3843
+ "mj-column": "Column"
3844
+ };
3845
+ function tagOf5(c) {
3846
+ return String(c.get("tagName") || c.get("type") || "");
3847
+ }
3848
+ var SPARKLES_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></svg>';
3849
+ var IMAGE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10l-3.1-3.1a2 2 0 0 0-2.814.014L6 21"/><path d="m14 19.5 3-3 3 3"/><path d="M17 22v-5.5"/><circle cx="9" cy="9" r="2"/></svg>';
3850
+ function isSection(c) {
3851
+ return tagOf5(c) === "mj-section";
3852
+ }
3853
+ function isCommentable(c) {
3854
+ const t = tagOf5(c);
3855
+ return t === "mj-section" || t in TYPE_LABEL;
3856
+ }
1374
3857
  function closestSection(c) {
1375
3858
  let cur = c;
1376
3859
  while (cur) {
@@ -1379,27 +3862,30 @@ function closestSection(c) {
1379
3862
  }
1380
3863
  return null;
1381
3864
  }
1382
- var CRUMB_NAME = {
1383
- "mj-section": "Section",
1384
- "mj-column": "Column",
1385
- "mj-group": "Group",
1386
- "mj-text": "Text",
1387
- "mj-button": "Button",
1388
- "mj-image": "Image",
1389
- "mj-divider": "Divider",
1390
- "mj-spacer": "Spacer",
1391
- "mj-hero": "Hero",
1392
- "mj-navbar": "Navbar",
1393
- "mj-social": "Social",
1394
- "mj-social-element": "Social",
1395
- "mj-wrapper": "Wrapper"
1396
- };
1397
- function crumbName(c) {
1398
- const t = tagOf(c);
1399
- return CRUMB_NAME[t] ?? t.replace(/^mj-/, "") ?? "Element";
3865
+ function crumbName(c, labels) {
3866
+ const t = tagOf5(c);
3867
+ return labels.elementNames[t] ?? t.replace(/^mj-/, "");
1400
3868
  }
3869
+ var BLOCKS = [
3870
+ "mj-1-column",
3871
+ "mj-2-columns",
3872
+ "mj-3-columns",
3873
+ "mj-text",
3874
+ "mj-image",
3875
+ "mj-button",
3876
+ "mj-divider",
3877
+ "mj-spacer",
3878
+ "mj-social-group",
3879
+ "mj-navbar"
3880
+ ];
3881
+ var LAYOUT_BLOCKS = /* @__PURE__ */ new Set(["mj-1-column", "mj-2-columns", "mj-3-columns"]);
3882
+ var SOCIAL_BLOCK = `<mj-social icon-size="24px" mode="horizontal">
3883
+ <mj-social-element name="facebook-noshare" href="https://facebook.com/"></mj-social-element>
3884
+ <mj-social-element name="instagram-noshare" href="https://instagram.com/"></mj-social-element>
3885
+ <mj-social-element name="x-noshare" href="https://x.com/"></mj-social-element>
3886
+ </mj-social>`;
1401
3887
  function objectLabel(c) {
1402
- const base = TYPE_LABEL[tagOf(c)] ?? tagOf(c);
3888
+ const base = TYPE_LABEL[tagOf5(c)] ?? tagOf5(c);
1403
3889
  const text = String(c.getEl?.()?.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 30);
1404
3890
  return text ? `${base}: \u201C${text}\u201D` : base;
1405
3891
  }
@@ -1431,75 +3917,6 @@ function wrapSelectionStyle(el, style) {
1431
3917
  } catch {
1432
3918
  }
1433
3919
  }
1434
- function setupStyleFields(editor, labels) {
1435
- const sm = editor.StyleManager;
1436
- const base = sm.getType("base").view;
1437
- const documentColors = () => {
1438
- const source = `${editor.getHtml()} ${editor.getCss() ?? ""}`;
1439
- const counts = /* @__PURE__ */ new Map();
1440
- const colorValue = /(?:color|background|border)[\w-]*\s*[:=]\s*["']?[^"';>{}]*?(#[0-9a-f]{3,8}\b|rgba?\([^)]*\))/gi;
1441
- for (const match of source.matchAll(colorValue)) {
1442
- const hex = toHex(match[1], { opaque: true });
1443
- if (hex) counts.set(hex, (counts.get(hex) ?? 0) + 1);
1444
- }
1445
- return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 14).map(([hex]) => hex);
1446
- };
1447
- const ownValue = (property) => property.hasValue?.({ noParent: true }) ? String(property.getValue?.() ?? "") : "";
1448
- const reactView = (field) => base.extend({
1449
- create({ property }) {
1450
- const el = document.createElement("div");
1451
- el.addEventListener("change", (event) => event.stopPropagation());
1452
- const root = (0, import_client.createRoot)(el);
1453
- this.__root = root;
1454
- this.__render = () => root.render(field(property, ownValue(property)));
1455
- this.__render();
1456
- return el;
1457
- },
1458
- update() {
1459
- this.__render?.();
1460
- },
1461
- destroy() {
1462
- const root = this.__root;
1463
- this.__root = void 0;
1464
- this.__render = void 0;
1465
- setTimeout(() => root?.unmount());
1466
- }
1467
- });
1468
- sm.addType("color", {
1469
- view: reactView((property, value) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1470
- ColorField,
1471
- {
1472
- value,
1473
- placeholder: String(property.getDefaultValue?.() ?? ""),
1474
- documentColors,
1475
- labels,
1476
- onChange: (next, partial) => property.upValue(next, { partial })
1477
- }
1478
- ))
1479
- });
1480
- sm.addType("file", {
1481
- view: reactView((property, value) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1482
- ImageField,
1483
- {
1484
- value,
1485
- labels,
1486
- onClear: () => property.upValue(""),
1487
- onChoose: () => {
1488
- const am = editor.AssetManager;
1489
- am.open({
1490
- types: ["image"],
1491
- current: imageUrl(value) ?? void 0,
1492
- select(asset, complete) {
1493
- const url = typeof asset === "string" ? asset : asset.getSrc();
1494
- property.upValue(url, { partial: !complete });
1495
- if (complete) am.close();
1496
- }
1497
- });
1498
- }
1499
- }
1500
- ))
1501
- });
1502
- }
1503
3920
  function setupRichText(editor, labels) {
1504
3921
  const rte = editor.RichTextEditor;
1505
3922
  if (!rte || rte.__customActions) return;
@@ -1539,23 +3956,29 @@ function setupRichText(editor, labels) {
1539
3956
  function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1540
3957
  const documents = useDocumentStore();
1541
3958
  const labels = useLabels();
1542
- const loadingRef = (0, import_react7.useRef)(false);
1543
- const saveStatusRef = (0, import_react7.useRef)("idle");
1544
- const stateListeners = (0, import_react7.useRef)(/* @__PURE__ */ new Set());
1545
- const [loading, setLoading] = (0, import_react7.useState)(true);
1546
- const [sidebarView, setSidebarView] = (0, import_react7.useState)("blocks");
1547
- const [composeTarget, setComposeTarget] = (0, import_react7.useState)(null);
1548
- const selectionSource = (0, import_react7.useRef)("other");
1549
- const editorRef = (0, import_react7.useRef)(null);
3959
+ const loadingRef = (0, import_react10.useRef)(false);
3960
+ const saveStatusRef = (0, import_react10.useRef)("idle");
3961
+ const stateListeners = (0, import_react10.useRef)(/* @__PURE__ */ new Set());
3962
+ const [loading, setLoading] = (0, import_react10.useState)(true);
3963
+ const [sidebarView, setSidebarView] = (0, import_react10.useState)("blocks");
3964
+ const blockSearchRef = (0, import_react10.useRef)(null);
3965
+ const onBlockSearchKey = (e) => {
3966
+ if (readOnlyRef.current || !opensBlockSearch(e)) return;
3967
+ e.preventDefault();
3968
+ setSidebarView("blocks");
3969
+ requestAnimationFrame(() => blockSearchRef.current?.focus());
3970
+ };
3971
+ const [composeTarget, setComposeTarget] = (0, import_react10.useState)(null);
3972
+ const selectionSource = (0, import_react10.useRef)("other");
3973
+ const editorRef = (0, import_react10.useRef)(null);
1550
3974
  const { running: turnRunning, locked: readOnly, setLocked } = useAgentTurn();
1551
- const readOnlyRef = (0, import_react7.useRef)(false);
1552
- const applyReadOnly = (0, import_react7.useRef)(async () => {
3975
+ const readOnlyRef = (0, import_react10.useRef)(false);
3976
+ const applyReadOnly = (0, import_react10.useRef)(async () => {
1553
3977
  });
1554
- (0, import_react7.useEffect)(() => void applyReadOnly.current(turnRunning), [turnRunning]);
1555
- (0, import_react7.useEffect)(() => () => setLocked(false), [setLocked]);
1556
- const [crumbs, setCrumbs] = (0, import_react7.useState)([]);
1557
- const [selectedImageSrc, setSelectedImageSrc] = (0, import_react7.useState)(null);
1558
- const [picker, setPicker] = (0, import_react7.useState)(
3978
+ (0, import_react10.useEffect)(() => void applyReadOnly.current(turnRunning), [turnRunning]);
3979
+ (0, import_react10.useEffect)(() => () => setLocked(false), [setLocked]);
3980
+ const [crumbs, setCrumbs] = (0, import_react10.useState)([]);
3981
+ const [picker, setPicker] = (0, import_react10.useState)(
1559
3982
  null
1560
3983
  );
1561
3984
  const onEditor = async (editor) => {
@@ -1564,7 +3987,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1564
3987
  const chain = [];
1565
3988
  let cur = c;
1566
3989
  while (cur) {
1567
- const t = tagOf(cur);
3990
+ const t = tagOf5(cur);
1568
3991
  if (!t || t === "mj-body" || t === "wrapper" || t === "Wrapper") break;
1569
3992
  chain.unshift(cur);
1570
3993
  cur = cur.parent() ?? void 0;
@@ -1613,7 +4036,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1613
4036
  if (isSection(c)) addIdClass(c, "sec");
1614
4037
  if (!isCommentable(c)) return;
1615
4038
  const toolbar = [...c.get("toolbar") ?? []];
1616
- if (tagOf(c) === "mj-image" && !toolbar.some((t) => t.command === "open-image-picker")) {
4039
+ if (tagOf5(c) === "mj-image" && !toolbar.some((t) => t.command === "open-image-picker")) {
1617
4040
  toolbar.push({
1618
4041
  command: "open-image-picker",
1619
4042
  label: IMAGE_SVG,
@@ -1621,11 +4044,11 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1621
4044
  });
1622
4045
  c.set("toolbar", toolbar);
1623
4046
  }
1624
- if (!toolbar.some((t) => t.command === "open-comments")) {
4047
+ if (closestSection(c) && !toolbar.some((t) => t.command === "open-comments")) {
1625
4048
  toolbar.push({
1626
4049
  command: "open-comments",
1627
4050
  label: SPARKLES_SVG,
1628
- attributes: { title: "Comment on element / section", "data-role": "sparkles" }
4051
+ attributes: { title: labels.commentOnElement, "data-role": "sparkles" }
1629
4052
  });
1630
4053
  c.set("toolbar", toolbar);
1631
4054
  }
@@ -1633,7 +4056,7 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1633
4056
  editor.Commands.add("open-image-picker", {
1634
4057
  run(ed) {
1635
4058
  const target = ed.getSelected();
1636
- if (!target || tagOf(target) !== "mj-image") return;
4059
+ if (!target || tagOf5(target) !== "mj-image") return;
1637
4060
  const am = ed.AssetManager;
1638
4061
  am.open({
1639
4062
  target,
@@ -1652,21 +4075,28 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1652
4075
  }
1653
4076
  });
1654
4077
  setupRichText(editor, labels);
1655
- setupStyleFields(editor, labels);
1656
- const blockCategory = (label) => {
1657
- const l = label.toLowerCase();
1658
- if (/(column|section)/.test(l)) return "Layout";
1659
- if (/(text|image|button|divider|spacer|social)/.test(l)) return "Basic";
1660
- return "Advanced";
4078
+ fixPluginTypes(editor);
4079
+ setupLayerNames(editor, labels);
4080
+ const STRUCTURE = /* @__PURE__ */ new Set(["mj-wrapper", "mj-section", "mj-column"]);
4081
+ const showBodyInLayers = () => {
4082
+ const body = findByTag("mj-body");
4083
+ if (!body) return;
4084
+ editor.Layers.setRoot(body);
4085
+ body.onAll((c) => {
4086
+ if (STRUCTURE.has(tagOf5(c))) editor.Layers.setOpen(c, true);
4087
+ });
1661
4088
  };
1662
- const bm = editor.BlockManager;
1663
- bm.getAll().forEach(
1664
- (b) => {
1665
- b.set("category", blockCategory(b.get("label") || ""));
4089
+ const keepStructureOpen = () => {
4090
+ const layers = editor.Layers;
4091
+ const opened = layers.model?.get("opened") ?? {};
4092
+ for (const [cid, c] of Object.entries(opened)) {
4093
+ if (STRUCTURE.has(tagOf5(c))) delete opened[cid];
1666
4094
  }
1667
- );
1668
- bm.render();
1669
- editor.on("component:add", decorate);
4095
+ };
4096
+ editor.on("component:add", (c) => {
4097
+ if (!loadingRef.current) freshenCopiedIds(editor.getWrapper(), c);
4098
+ decorate(c);
4099
+ });
1670
4100
  editor.on("load", () => {
1671
4101
  const body = editor.Canvas.getBody();
1672
4102
  body?.addEventListener(
@@ -1676,34 +4106,17 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1676
4106
  },
1677
4107
  true
1678
4108
  );
4109
+ editor.Canvas.getDocument()?.addEventListener("keydown", onBlockSearchKey);
4110
+ showBodyInLayers();
1679
4111
  });
1680
4112
  editor.on("component:selected", (c) => {
4113
+ keepStructureOpen();
1681
4114
  if (selectionSource.current === "canvas") setSidebarView("settings");
1682
4115
  selectionSource.current = "other";
1683
4116
  buildCrumbs(c);
1684
- setSelectedImageSrc(c && tagOf(c) === "mj-image" ? String(c.get("src") ?? "") : null);
1685
- if (c?.get("type") === "mj-image" && !c.getTrait("src")) {
1686
- c.addTrait(
1687
- {
1688
- type: "text",
1689
- name: "src",
1690
- label: "Image (URL)",
1691
- placeholder: "https://\u2026",
1692
- changeProp: true
1693
- },
1694
- { at: 0 }
1695
- );
1696
- }
1697
4117
  });
1698
4118
  editor.on("component:deselected", () => {
1699
- const rest = editor.getSelected();
1700
- buildCrumbs(rest);
1701
- setSelectedImageSrc(
1702
- rest && tagOf(rest) === "mj-image" ? String(rest.get("src") ?? "") : null
1703
- );
1704
- });
1705
- editor.on("component:update:src", (c) => {
1706
- if (c === editor.getSelected()) setSelectedImageSrc(String(c.get("src") ?? ""));
4119
+ buildCrumbs(editor.getSelected());
1707
4120
  });
1708
4121
  let stored = "";
1709
4122
  const save = async () => {
@@ -1777,17 +4190,30 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1777
4190
  });
1778
4191
  const loadMjml = (mjml) => {
1779
4192
  loadingRef.current = true;
4193
+ let repaired = false;
1780
4194
  try {
1781
4195
  queue.cancel();
1782
4196
  stored = mjml;
1783
- editor.setComponents(mjml || import_core2.STARTER_MJML);
4197
+ const source = withoutEditorProps(mjml || import_core2.STARTER_MJML);
4198
+ repaired = source !== (mjml || import_core2.STARTER_MJML);
4199
+ editor.setComponents(source);
1784
4200
  editor.getWrapper()?.find("mj-section").forEach(decorate);
1785
4201
  for (const t of Object.keys(TYPE_LABEL)) {
1786
4202
  editor.getWrapper()?.find(t).forEach(decorate);
1787
4203
  }
4204
+ editor.getWrapper()?.onAll((c) => {
4205
+ const illegal = illegalAttributes(tagOf5(c), c.getAttributes());
4206
+ if (illegal.length === 0) return;
4207
+ c.removeAttributes(illegal);
4208
+ repaired = true;
4209
+ });
4210
+ showBodyInLayers();
4211
+ editor.UndoManager.clear();
1788
4212
  } finally {
1789
4213
  loadingRef.current = false;
1790
4214
  }
4215
+ notifyState();
4216
+ if (repaired && !readOnlyRef.current) queue.schedule();
1791
4217
  };
1792
4218
  const ensureHighlightStyles = () => {
1793
4219
  const cdoc = editor.Canvas.getDocument();
@@ -1874,8 +4300,8 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1874
4300
  }
1875
4301
  });
1876
4302
  };
1877
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1878
- import_react6.default,
4303
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4304
+ import_react9.default,
1879
4305
  {
1880
4306
  grapesjs: import_grapesjs.default,
1881
4307
  className: "h-full",
@@ -1889,9 +4315,9 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1889
4315
  fromElement: false,
1890
4316
  panels: { defaults: [] },
1891
4317
  blockManager: { appendTo: "#gjs-blocks" },
1892
- styleManager: { appendTo: "#gjs-styles" },
1893
- traitManager: { appendTo: "#gjs-traits" },
1894
- layerManager: { appendTo: "#gjs-layers" },
4318
+ // The eye hides a layer by writing `display="none"` into the MJML, which is illegal.
4319
+ // The root row is mj-body, which the sidebar reaches as "Email settings" instead.
4320
+ layerManager: { appendTo: "#gjs-layers", hidable: false, showWrapper: false },
1895
4321
  assetManager: {
1896
4322
  // Never inline files: mail clients block `data:` images (see ImageLibrary).
1897
4323
  embedAsBase64: false,
@@ -1905,110 +4331,106 @@ function EmailEditor({ docId, onReady, commentsRefresh, onOpenCountChange }) {
1905
4331
  close: () => setPicker(null)
1906
4332
  }
1907
4333
  },
4334
+ canvas: { customBadgeLabel: badgeLabel(labels) },
1908
4335
  deviceManager: {
1909
4336
  devices: [
1910
4337
  { id: "desktop", name: "Desktop", width: "" },
1911
4338
  { id: "mobile", name: "Mobile", width: "375px", widthMedia: "480px" }
1912
4339
  ]
1913
4340
  },
1914
- plugins: [import_grapesjs_mjml.default]
4341
+ plugins: [
4342
+ (editor) => (0, import_grapesjs_mjml.default)(editor, {
4343
+ blocks: BLOCKS,
4344
+ block: (id) => ({
4345
+ label: labels.blockLabels[id] ?? id,
4346
+ // By id: a regex on the label broke as soon as a host translated it.
4347
+ category: LAYOUT_BLOCKS.has(id) ? labels.blockCategoryLayout : labels.blockCategoryContent,
4348
+ ...id === "mj-social-group" && { content: SOCIAL_BLOCK }
4349
+ })
4350
+ })
4351
+ ]
1915
4352
  },
1916
4353
  onEditor,
1917
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "relative flex h-full min-h-0 flex-1", inert: readOnly, children: [
1918
- readOnly && /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsxs)("span", { className: "flex items-center gap-2 rounded-full bg-panel px-3 py-1 text-xs text-panel-fg shadow", children: [
1919
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Spinner, {}),
1920
- " ",
1921
- labels.agentWorking
1922
- ] }) }),
1923
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1924
- LeftSidebar,
1925
- {
1926
- view: sidebarView,
1927
- onViewChange: setSidebarView,
1928
- markLayersSource: () => selectionSource.current = "layers",
1929
- selectedImageSrc,
1930
- onChooseImage: () => editorRef.current?.runCommand("open-image-picker")
1931
- }
1932
- ),
1933
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col bg-surface-muted", children: [
1934
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "relative min-h-0 flex-1", children: [
1935
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react6.Canvas, { className: "h-full" }),
1936
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1937
- CanvasComments,
4354
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4355
+ "div",
4356
+ {
4357
+ className: "relative flex h-full min-h-0 flex-1",
4358
+ inert: readOnly,
4359
+ onKeyDown: (e) => onBlockSearchKey(e.nativeEvent),
4360
+ children: [
4361
+ 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: [
4362
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, {}),
4363
+ " ",
4364
+ labels.agentWorking
4365
+ ] }) }),
4366
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4367
+ LeftSidebar,
1938
4368
  {
1939
- docId,
1940
- composeTarget,
1941
- onComposeConsumed: () => setComposeTarget(null),
1942
- refreshSignal: commentsRefresh,
1943
- onOpenCountChange
4369
+ view: sidebarView,
4370
+ onViewChange: setSidebarView,
4371
+ markLayersSource: () => selectionSource.current = "layers",
4372
+ blockSearchRef
1944
4373
  }
1945
4374
  ),
1946
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1947
- ImagePicker,
1948
- {
1949
- open: picker !== null,
1950
- currentUrl: picker?.current,
1951
- onSelect: (url) => {
1952
- if (!readOnlyRef.current) picker?.select(url);
1953
- setPicker(null);
1954
- },
1955
- onClose: () => {
1956
- setPicker(null);
1957
- editorRef.current?.AssetManager.close();
1958
- }
1959
- }
1960
- ),
1961
- loading && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "absolute inset-0 z-10 flex items-center justify-center gap-2 bg-white/70 text-sm text-zinc-500", children: [
1962
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Spinner, {}),
1963
- " Loading editor\u2026"
4375
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col bg-surface-muted", children: [
4376
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "relative min-h-0 flex-1", children: [
4377
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react9.Canvas, { className: "h-full" }),
4378
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4379
+ CanvasComments,
4380
+ {
4381
+ docId,
4382
+ composeTarget,
4383
+ onComposeConsumed: () => setComposeTarget(null),
4384
+ refreshSignal: commentsRefresh,
4385
+ onOpenCountChange
4386
+ }
4387
+ ),
4388
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4389
+ ImagePicker,
4390
+ {
4391
+ open: picker !== null,
4392
+ currentUrl: picker?.current,
4393
+ onSelect: (url) => {
4394
+ if (!readOnlyRef.current) picker?.select(url);
4395
+ setPicker(null);
4396
+ },
4397
+ onClose: () => {
4398
+ setPicker(null);
4399
+ editorRef.current?.AssetManager.close();
4400
+ }
4401
+ }
4402
+ ),
4403
+ 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: [
4404
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Spinner, {}),
4405
+ " ",
4406
+ labels.loadingEditor
4407
+ ] })
4408
+ ] }),
4409
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Breadcrumb, { crumbs, onSelect: (c) => editorRef.current?.select(c) })
1964
4410
  ] })
1965
- ] }),
1966
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Breadcrumb, { crumbs, onSelect: (c) => editorRef.current?.select(c) })
1967
- ] })
1968
- ] })
4411
+ ]
4412
+ }
4413
+ )
1969
4414
  }
1970
4415
  );
1971
4416
  }
1972
- function CollapseSection({
1973
- title,
1974
- defaultOpen = true,
1975
- children
1976
- }) {
1977
- const [open, setOpen] = (0, import_react7.useState)(defaultOpen);
1978
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "border-b border-panel-border", children: [
1979
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1980
- "button",
1981
- {
1982
- type: "button",
1983
- onClick: () => setOpen((o) => !o),
1984
- className: "flex w-full items-center gap-1.5 px-3 py-2 text-xs font-semibold tracking-wide text-panel-muted-fg uppercase hover:text-panel-fg",
1985
- children: [
1986
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ChevronRight, { className: cn("size-3.5 transition-transform", open && "rotate-90") }),
1987
- title
1988
- ]
1989
- }
1990
- ),
1991
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn(!open && "hidden"), children })
1992
- ] });
1993
- }
1994
4417
  function LeftSidebar({
1995
4418
  view,
1996
4419
  onViewChange,
1997
4420
  markLayersSource,
1998
- selectedImageSrc,
1999
- onChooseImage
4421
+ blockSearchRef
2000
4422
  }) {
2001
- const editor = (0, import_react6.useEditorMaybe)();
4423
+ const editor = (0, import_react9.useEditorMaybe)();
2002
4424
  const labels = useLabels();
2003
- const [layersExpanded, setLayersExpanded] = (0, import_react7.useState)(false);
2004
- const [blockSearch, setBlockSearch] = (0, import_react7.useState)("");
2005
- const changeView = (v) => {
2006
- if (v === "settings" && editor && !editor.getSelected()) {
2007
- const wrapper = editor.getWrapper();
2008
- const body = wrapper?.find("mj-body")[0] ?? wrapper;
2009
- if (body) editor.select(body);
2010
- }
2011
- onViewChange(v);
4425
+ const [layersExpanded, setLayersExpanded] = (0, import_react10.useState)(false);
4426
+ const [blockSearch, setBlockSearch] = (0, import_react10.useState)("");
4427
+ const [noBlockMatches, setNoBlockMatches] = (0, import_react10.useState)(false);
4428
+ const selectBody = () => {
4429
+ let body;
4430
+ editor?.getWrapper()?.onAll((c) => {
4431
+ if (!body && tagOf5(c) === "mj-body") body = c;
4432
+ });
4433
+ if (body) editor?.select(body);
2012
4434
  };
2013
4435
  const toggleLayers = (expand) => {
2014
4436
  setLayersExpanded(expand);
@@ -2029,17 +4451,22 @@ function LeftSidebar({
2029
4451
  );
2030
4452
  cat.style.display = anyVisible ? "" : "none";
2031
4453
  });
4454
+ setNoBlockMatches(
4455
+ [...root.querySelectorAll(".gjs-block")].every(
4456
+ (b) => b.style.display === "none"
4457
+ )
4458
+ );
2032
4459
  };
2033
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "editor-dark flex w-64 shrink-0 flex-col border-r border-panel-border bg-panel text-panel-fg", children: [
2034
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex border-b border-panel-border", children: [
2035
- ["blocks", "Blocks"],
2036
- ["settings", "Settings"],
2037
- ["layers", "Layers"]
2038
- ].map(([value, label]) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4460
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "editor-dark flex w-64 shrink-0 flex-col border-r border-panel-border bg-panel text-panel-fg", children: [
4461
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex border-b border-panel-border", children: [
4462
+ ["blocks", labels.tabBlocks],
4463
+ ["settings", labels.tabSettings],
4464
+ ["layers", labels.tabLayers]
4465
+ ].map(([value, label]) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2039
4466
  "button",
2040
4467
  {
2041
4468
  type: "button",
2042
- onClick: () => changeView(value),
4469
+ onClick: () => onViewChange(value),
2043
4470
  className: cn(
2044
4471
  "relative flex-1 px-3 py-2.5 text-sm transition-colors",
2045
4472
  "after:absolute after:inset-x-0 after:-bottom-px after:h-0.5 after:transition-colors",
@@ -2049,70 +4476,57 @@ function LeftSidebar({
2049
4476
  },
2050
4477
  value
2051
4478
  )) }),
2052
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: cn("flex min-h-0 flex-1 flex-col", view !== "blocks" && "hidden"), children: [
2053
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "relative border-b border-panel-border p-2", children: [
2054
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react8.Search, { className: "pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-panel-muted-fg" }),
2055
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4479
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: cn("flex min-h-0 flex-1 flex-col", view !== "blocks" && "hidden"), children: [
4480
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "relative border-b border-panel-border p-2", children: [
4481
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react11.Search, { className: "pointer-events-none absolute top-1/2 left-4 size-3.5 -translate-y-1/2 text-panel-muted-fg" }),
4482
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2056
4483
  Input,
2057
4484
  {
4485
+ ref: blockSearchRef,
2058
4486
  value: blockSearch,
2059
4487
  onChange: (e) => filterBlocks(e.target.value),
2060
4488
  placeholder: labels.searchBlocks,
2061
4489
  className: "h-8 border-panel-border bg-panel-elevated pr-10 pl-7 text-sm text-panel-fg placeholder:text-panel-muted-fg"
2062
4490
  }
2063
4491
  ),
2064
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("kbd", { className: "pointer-events-none absolute top-1/2 right-4 -translate-y-1/2 rounded border border-panel-border bg-panel px-1 py-0.5 font-mono text-[10px] text-panel-muted-fg", children: "\u2318K" })
2065
- ] }),
2066
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: "gjs-blocks", className: "min-h-0 flex-1 overflow-y-auto" })
2067
- ] }),
2068
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: cn("min-h-0 flex-1 overflow-y-auto", view !== "settings" && "hidden"), children: [
2069
- selectedImageSrc !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "border-b border-panel-border p-3", children: [
2070
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2071
- "button",
2072
- {
2073
- type: "button",
2074
- onClick: onChooseImage,
2075
- title: labels.chooseImage,
2076
- className: "group relative block w-full overflow-hidden rounded-lg border border-panel-border bg-panel-elevated",
2077
- children: [
2078
- selectedImageSrc ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2079
- "img",
2080
- {
2081
- src: selectedImageSrc,
2082
- alt: "",
2083
- className: "h-32 w-full bg-black/25 object-contain"
2084
- }
2085
- ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "flex h-32 items-center justify-center text-panel-muted-fg", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ImageIcon, { className: "size-8" }) }),
2086
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "absolute inset-0 flex items-center justify-center bg-black/50 text-sm font-medium text-white opacity-0 transition group-hover:opacity-100", children: labels.chooseImage })
2087
- ]
2088
- }
2089
- ),
2090
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2091
- Button,
2092
- {
2093
- type: "button",
2094
- size: "sm",
2095
- className: "mt-2 w-full bg-brand text-brand-fg hover:bg-brand/90",
2096
- onClick: onChooseImage,
2097
- children: [
2098
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ImageUp, {}),
2099
- labels.chooseImage
2100
- ]
2101
- }
2102
- )
4492
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("kbd", { className: "pointer-events-none absolute top-1/2 right-4 -translate-y-1/2 rounded border border-panel-border bg-panel px-1 py-0.5 font-mono text-[10px] text-panel-muted-fg", children: "/" })
2103
4493
  ] }),
2104
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CollapseSection, { title: labels.attributes, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: "gjs-traits" }) }),
2105
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(CollapseSection, { title: labels.style, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: "gjs-styles" }) })
4494
+ noBlockMatches && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "px-3 py-6 text-center text-xs text-panel-muted-fg", children: labels.noBlocksMatch }),
4495
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { id: "gjs-blocks", className: "min-h-0 flex-1 overflow-y-auto" })
2106
4496
  ] }),
2107
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
4497
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: cn("min-h-0 flex-1 overflow-y-auto", view !== "settings" && "hidden"), children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4498
+ Inspector,
4499
+ {
4500
+ onEmailSettings: selectBody,
4501
+ empty: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-col items-center gap-3 px-6 py-10 text-center", children: [
4502
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react10.MousePointerClick, { className: "size-6 text-panel-muted-fg" }),
4503
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-sm text-panel-muted-fg", children: labels.settingsEmpty }),
4504
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
4505
+ Button,
4506
+ {
4507
+ type: "button",
4508
+ size: "sm",
4509
+ variant: "outline",
4510
+ className: "border-panel-border bg-panel-elevated text-panel-fg hover:bg-panel-hover hover:text-panel-fg",
4511
+ onClick: selectBody,
4512
+ children: [
4513
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react10.Mail, {}),
4514
+ labels.emailSettings
4515
+ ]
4516
+ }
4517
+ )
4518
+ ] })
4519
+ }
4520
+ ) }),
4521
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2108
4522
  "div",
2109
4523
  {
2110
4524
  className: cn("flex min-h-0 flex-1 flex-col", view !== "layers" && "hidden"),
2111
4525
  onMouseDownCapture: markLayersSource,
2112
4526
  children: [
2113
- /* @__PURE__ */ (0, import_jsx_runtime13.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: [
2114
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: labels.expandAll }),
2115
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4527
+ /* @__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: [
4528
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { children: labels.expandAll }),
4529
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2116
4530
  Switch,
2117
4531
  {
2118
4532
  checked: layersExpanded,
@@ -2121,7 +4535,16 @@ function LeftSidebar({
2121
4535
  }
2122
4536
  )
2123
4537
  ] }),
2124
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: "gjs-layers", className: "min-h-0 flex-1 overflow-y-auto" })
4538
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4539
+ "div",
4540
+ {
4541
+ id: "gjs-layers",
4542
+ className: "min-h-0 flex-1 overflow-y-auto",
4543
+ onDoubleClickCapture: (e) => {
4544
+ if (e.target.closest("[data-name]")) e.stopPropagation();
4545
+ }
4546
+ }
4547
+ )
2125
4548
  ]
2126
4549
  }
2127
4550
  )
@@ -2132,9 +4555,9 @@ function Breadcrumb({
2132
4555
  onSelect
2133
4556
  }) {
2134
4557
  const labels = useLabels();
2135
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex h-9 shrink-0 items-center gap-0.5 border-t border-border bg-surface px-3 text-xs text-muted-foreground", children: crumbs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-muted-foreground/60", children: labels.selectAnElement }) : crumbs.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "flex items-center gap-0.5", children: [
2136
- i > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ChevronRight, { className: "size-3 opacity-40" }),
2137
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4558
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex h-9 shrink-0 items-center gap-0.5 border-t border-border bg-surface px-3 text-xs text-muted-foreground", children: crumbs.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-muted-foreground/60", children: labels.selectAnElement }) : crumbs.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("span", { className: "flex items-center gap-0.5", children: [
4559
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react10.ChevronRight, { className: "size-3 opacity-40" }),
4560
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2138
4561
  "button",
2139
4562
  {
2140
4563
  type: "button",
@@ -2143,7 +4566,7 @@ function Breadcrumb({
2143
4566
  "rounded px-1.5 py-0.5 hover:bg-muted hover:text-foreground",
2144
4567
  i === crumbs.length - 1 && "font-medium text-brand"
2145
4568
  ),
2146
- children: crumbName(c)
4569
+ children: crumbName(c, labels)
2147
4570
  }
2148
4571
  )
2149
4572
  ] }, i)) });