@drawnagency/primitives 0.1.89 → 0.1.90

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.
Files changed (83) hide show
  1. package/dist/{chunk-75P6HWPN.js → chunk-5BM2Y3NJ.js} +5 -3
  2. package/dist/{chunk-VK5KNH73.js → chunk-AGWE2LJG.js} +2 -2
  3. package/dist/{chunk-2QXJ2KGB.js → chunk-KBQNJZUX.js} +1 -1
  4. package/dist/{chunk-WCBYXNAG.js → chunk-KMQZYORS.js} +1 -0
  5. package/dist/{chunk-Y24T75HT.js → chunk-MK7YB2A4.js} +1 -1
  6. package/dist/{chunk-QFV5TSPO.js → chunk-SIYQ37QY.js} +12 -4
  7. package/dist/{chunk-J75XGXYD.js → chunk-XKRQMTE4.js} +9 -0
  8. package/dist/{chunk-L2MAJKKC.js → chunk-XUIJHYKI.js} +2 -2
  9. package/dist/{chunk-MVX4ULYE.js → chunk-ZLG7S54V.js} +241 -82
  10. package/dist/components/editor/AudienceIndicator.d.ts +6 -0
  11. package/dist/components/editor/AudienceIndicator.d.ts.map +1 -1
  12. package/dist/components/editor/ChildBlockWrapper.d.ts.map +1 -1
  13. package/dist/components/editor/ColSpanControl.d.ts.map +1 -1
  14. package/dist/components/editor/DragHandle.d.ts +3 -1
  15. package/dist/components/editor/DragHandle.d.ts.map +1 -1
  16. package/dist/components/editor/SectionWrapper.d.ts.map +1 -1
  17. package/dist/components/editor/StatusIndicator.d.ts +7 -0
  18. package/dist/components/editor/StatusIndicator.d.ts.map +1 -1
  19. package/dist/components/editor/index.js +98 -76
  20. package/dist/components/primitives/index.js +3 -3
  21. package/dist/components/sections/Container/Container.d.ts +3 -2
  22. package/dist/components/sections/Container/Container.d.ts.map +1 -1
  23. package/dist/components/sections/Container/ContainerSettingsForm.d.ts +4 -2
  24. package/dist/components/sections/Container/ContainerSettingsForm.d.ts.map +1 -1
  25. package/dist/components/sections/Container/index.d.ts +8 -2
  26. package/dist/components/sections/Container/index.d.ts.map +1 -1
  27. package/dist/components/sections/SectionLayout.d.ts +3 -1
  28. package/dist/components/sections/SectionLayout.d.ts.map +1 -1
  29. package/dist/components/sections/ViewRenderer.d.ts +3 -1
  30. package/dist/components/sections/ViewRenderer.d.ts.map +1 -1
  31. package/dist/components/sections/all-sections.d.ts +8 -2
  32. package/dist/components/sections/all-sections.d.ts.map +1 -1
  33. package/dist/components/sections/brand-guide.js +3 -3
  34. package/dist/components/sections/core-sections.d.ts +8 -2
  35. package/dist/components/sections/core-sections.d.ts.map +1 -1
  36. package/dist/components/sections/register-schemas.js +6 -6
  37. package/dist/components/sections/register.js +6 -6
  38. package/dist/components/shared/Tooltip.d.ts +8 -1
  39. package/dist/components/shared/Tooltip.d.ts.map +1 -1
  40. package/dist/components/shell/AudiencePreviewMenu.d.ts +7 -0
  41. package/dist/components/shell/AudiencePreviewMenu.d.ts.map +1 -0
  42. package/dist/components/shell/AuthoringDefaultsMenu.d.ts +10 -0
  43. package/dist/components/shell/AuthoringDefaultsMenu.d.ts.map +1 -0
  44. package/dist/components/shell/EditorContext.d.ts +3 -0
  45. package/dist/components/shell/EditorContext.d.ts.map +1 -1
  46. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  47. package/dist/components/shell/SiteSettingsChatbot.d.ts.map +1 -1
  48. package/dist/hooks/useBuildStatus.d.ts.map +1 -1
  49. package/dist/index.js +6 -4
  50. package/dist/lib/authoring-defaults.d.ts +17 -0
  51. package/dist/lib/authoring-defaults.d.ts.map +1 -0
  52. package/dist/lib/container-grid.d.ts +6 -0
  53. package/dist/lib/container-grid.d.ts.map +1 -1
  54. package/dist/lib/index.js +3 -3
  55. package/dist/lib/migrate-sections-transform.js +1 -1
  56. package/dist/media/index.js +1 -1
  57. package/dist/schemas/index.js +4 -2
  58. package/dist/schemas/site-config.d.ts +6 -0
  59. package/dist/schemas/site-config.d.ts.map +1 -1
  60. package/package.json +1 -1
  61. package/src/components/editor/AudienceIndicator.tsx +50 -37
  62. package/src/components/editor/ChildBlockWrapper.tsx +8 -7
  63. package/src/components/editor/ColSpanControl.tsx +14 -11
  64. package/src/components/editor/DragHandle.tsx +5 -2
  65. package/src/components/editor/PagesModal.tsx +10 -10
  66. package/src/components/editor/SectionWrapper.tsx +32 -21
  67. package/src/components/editor/StatusIndicator.tsx +37 -24
  68. package/src/components/sections/Container/Container.tsx +185 -43
  69. package/src/components/sections/Container/ContainerSettingsForm.tsx +64 -24
  70. package/src/components/sections/Container/index.tsx +45 -20
  71. package/src/components/sections/SectionLayout.tsx +13 -3
  72. package/src/components/sections/ViewRenderer.tsx +11 -2
  73. package/src/components/shared/Tooltip.tsx +18 -2
  74. package/src/components/shell/AudiencePreviewMenu.tsx +73 -0
  75. package/src/components/shell/AuthoringDefaultsMenu.tsx +54 -0
  76. package/src/components/shell/EditorContext.tsx +5 -1
  77. package/src/components/shell/EditorShell.tsx +59 -12
  78. package/src/components/shell/SiteSettingsChatbot.tsx +2 -1
  79. package/src/hooks/useBuildStatus.ts +35 -4
  80. package/src/lib/authoring-defaults.ts +42 -0
  81. package/src/lib/container-grid.ts +14 -0
  82. package/src/lib/migrate-sections-transform.ts +5 -3
  83. package/src/schemas/site-config.ts +1 -1
@@ -76,7 +76,8 @@ function upgradeGrid(section) {
76
76
  type: "container",
77
77
  content: {
78
78
  columns: content.columns,
79
- flow: "row",
79
+ layout: "grid",
80
+ equalizeRowHeight: false,
80
81
  children: media.map((item, i) => mapRefItem(item, id, i))
81
82
  }
82
83
  };
@@ -100,7 +101,7 @@ function upgradeSplitContent(section) {
100
101
  content: { body: content.body ?? "" }
101
102
  };
102
103
  const children = options.imagePosition === "right" ? [proseChild, mediaChild] : [mediaChild, proseChild];
103
- const out = { id, type: "container", content: { columns: 2, flow: "row", children } };
104
+ const out = { id, type: "container", content: { columns: 2, layout: "grid", equalizeRowHeight: false, children } };
104
105
  if (section.layout !== void 0) out.layout = section.layout;
105
106
  return out;
106
107
  }
@@ -131,7 +132,8 @@ function upgradeDoDont(section) {
131
132
  type: "container",
132
133
  content: {
133
134
  columns: 2,
134
- flow: "row",
135
+ layout: "grid",
136
+ equalizeRowHeight: false,
135
137
  children: [
136
138
  dodontChild(doItems, id, 0, "do", childOptions),
137
139
  dodontChild(dontItems, id, 1, "dont", childOptions)
@@ -3,13 +3,13 @@ import {
3
3
  } from "./chunk-S2L3BPLS.js";
4
4
  import {
5
5
  upgradeLegacySection
6
- } from "./chunk-75P6HWPN.js";
6
+ } from "./chunk-5BM2Y3NJ.js";
7
7
  import {
8
8
  getSectionSchema
9
9
  } from "./chunk-TZQPOR5A.js";
10
10
  import {
11
11
  IndexSchema
12
- } from "./chunk-WCBYXNAG.js";
12
+ } from "./chunk-KMQZYORS.js";
13
13
  import {
14
14
  getAllSchemas
15
15
  } from "./chunk-VKAGMEKE.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  EditablePlainText
3
- } from "./chunk-J75XGXYD.js";
3
+ } from "./chunk-XKRQMTE4.js";
4
4
  import {
5
5
  Button,
6
6
  usePages
@@ -330,6 +330,7 @@ export {
330
330
  MediaItemSchema,
331
331
  ImageManifestSchema,
332
332
  MediaConfigSchema,
333
+ StatusSchema,
333
334
  SectionMetaSchema,
334
335
  RESERVED_SLUGS,
335
336
  PageStatusSchema,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HexColorSchema
3
- } from "./chunk-WCBYXNAG.js";
3
+ } from "./chunk-KMQZYORS.js";
4
4
 
5
5
  // src/schemas/audience.ts
6
6
  import { z } from "zod";
@@ -24,13 +24,21 @@ import { forwardRef } from "react";
24
24
 
25
25
  // src/components/shared/Tooltip.tsx
26
26
  import { jsx, jsxs } from "react/jsx-runtime";
27
- function Tooltip({ content, children, className }) {
27
+ var sideClasses = {
28
+ bottom: "left-1/2 top-full mt-1.5 -translate-x-1/2",
29
+ right: "left-full top-1/2 ml-1.5 -translate-y-1/2",
30
+ "bottom-clamp": "top-full mt-1.5 left-1/2 -translate-x-1/2 max-xl:left-auto max-xl:right-0 max-xl:translate-x-0"
31
+ };
32
+ function Tooltip({ content, children, className, side = "bottom" }) {
28
33
  return /* @__PURE__ */ jsxs("div", { className: cn("group/tooltip relative inline-flex", className), children: [
29
34
  children,
30
35
  /* @__PURE__ */ jsx(
31
36
  "span",
32
37
  {
33
- className: "pointer-events-none absolute left-1/2 top-full z-50 mt-1.5 -translate-x-1/2 opacity-0 transition-opacity delay-300 group-hover/tooltip:opacity-100",
38
+ className: cn(
39
+ "pointer-events-none absolute z-50 opacity-0 transition-opacity delay-300 group-hover/tooltip:opacity-100",
40
+ sideClasses[side]
41
+ ),
34
42
  role: "tooltip",
35
43
  children: /* @__PURE__ */ jsx("span", { className: "block whitespace-nowrap rounded bg-tooltip px-2 py-1 text-center text-xs leading-relaxed text-tooltip-muted", children: content })
36
44
  }
@@ -43,8 +51,8 @@ function Kbd({ children }) {
43
51
 
44
52
  // src/components/editor/DragHandle.tsx
45
53
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
46
- var DragHandle2 = forwardRef(function DragHandle3(_, ref) {
47
- return /* @__PURE__ */ jsx2(Tooltip, { content: /* @__PURE__ */ jsxs2(Fragment, { children: [
54
+ var DragHandle2 = forwardRef(function DragHandle3({ tooltipSide }, ref) {
55
+ return /* @__PURE__ */ jsx2(Tooltip, { side: tooltipSide, content: /* @__PURE__ */ jsxs2(Fragment, { children: [
48
56
  /* @__PURE__ */ jsx2(Kbd, { children: "Drag" }),
49
57
  " to move"
50
58
  ] }), children: /* @__PURE__ */ jsx2(
@@ -340,6 +340,14 @@ var colSpanClass = {
340
340
  5: { 1: "", 2: "col-span-1 @xs:col-span-2", 3: "col-span-1 @xs:col-span-2 @md:col-span-3", 4: "col-span-1 @xs:col-span-2 @md:col-span-3 @xl:col-span-4", 5: "col-span-1 @xs:col-span-2 @md:col-span-3 @xl:col-span-5" },
341
341
  6: { 1: "", 2: "col-span-1 @xs:col-span-2", 3: "col-span-1 @xs:col-span-2 @md:col-span-3", 4: "col-span-1 @xs:col-span-2 @md:col-span-3 @2xl:col-span-4", 5: "col-span-1 @xs:col-span-2 @md:col-span-3 @2xl:col-span-5", 6: "col-span-1 @xs:col-span-2 @md:col-span-3 @2xl:col-span-6" }
342
342
  };
343
+ var masonryColumnsClass = {
344
+ 1: "columns-1",
345
+ 2: "columns-1 @sm:columns-2",
346
+ 3: "columns-1 @sm:columns-2 @lg:columns-3",
347
+ 4: "columns-1 @xs:columns-2 @lg:columns-4",
348
+ 5: "columns-1 @xs:columns-2 @md:columns-3 @xl:columns-5",
349
+ 6: "columns-1 @xs:columns-2 @md:columns-3 @2xl:columns-6"
350
+ };
343
351
  var spacerHiddenClass = {
344
352
  1: "hidden",
345
353
  2: "hidden @sm:block",
@@ -705,6 +713,7 @@ export {
705
713
  MAX_CONTAINER_COLUMNS,
706
714
  containerGridClass,
707
715
  colSpanClass,
716
+ masonryColumnsClass,
708
717
  spacerHiddenClass,
709
718
  useEditableCollection,
710
719
  EditableGrid
@@ -9,7 +9,7 @@ import {
9
9
  MediaBlock,
10
10
  containerGridClass,
11
11
  useEditableCollection
12
- } from "./chunk-J75XGXYD.js";
12
+ } from "./chunk-XKRQMTE4.js";
13
13
  import {
14
14
  AddIcon,
15
15
  DeleteIcon,
@@ -30,7 +30,7 @@ import {
30
30
  import {
31
31
  HexColorSchema,
32
32
  SingleMediaReferenceSchema
33
- } from "./chunk-WCBYXNAG.js";
33
+ } from "./chunk-KMQZYORS.js";
34
34
  import {
35
35
  defineSection
36
36
  } from "./chunk-VKAGMEKE.js";
@@ -3,23 +3,25 @@ import {
3
3
  pluralize,
4
4
  stripHtmlToPlainText,
5
5
  truncate
6
- } from "./chunk-L2MAJKKC.js";
6
+ } from "./chunk-XUIJHYKI.js";
7
7
  import {
8
8
  DeleteButton,
9
9
  DragHandle,
10
+ Kbd,
10
11
  Popover,
11
12
  PopoverItem,
12
13
  SettingsButton,
13
14
  SettingsForm,
15
+ Tooltip,
14
16
  buildBlockDragData,
15
17
  buildBlockDropData,
16
18
  canDropBlock,
17
19
  isBlockDragData
18
- } from "./chunk-QFV5TSPO.js";
20
+ } from "./chunk-SIYQ37QY.js";
19
21
  import {
20
22
  EditableRichText,
21
23
  defineHeadingSection
22
- } from "./chunk-2QXJ2KGB.js";
24
+ } from "./chunk-KBQNJZUX.js";
23
25
  import {
24
26
  EditablePlainText,
25
27
  ImageDropZone,
@@ -27,8 +29,9 @@ import {
27
29
  MediaBlock,
28
30
  colSpanClass,
29
31
  containerGridClass,
32
+ masonryColumnsClass,
30
33
  spacerHiddenClass
31
- } from "./chunk-J75XGXYD.js";
34
+ } from "./chunk-XKRQMTE4.js";
32
35
  import {
33
36
  useMediaLibrary,
34
37
  useResolvedMedia
@@ -51,7 +54,7 @@ import {
51
54
  } from "./chunk-TZQPOR5A.js";
52
55
  import {
53
56
  SingleMediaReferenceSchema
54
- } from "./chunk-WCBYXNAG.js";
57
+ } from "./chunk-KMQZYORS.js";
55
58
  import {
56
59
  defineSection,
57
60
  getSection
@@ -483,14 +486,17 @@ import { CopyPlus } from "lucide-react";
483
486
  // src/components/editor/ColSpanControl.tsx
484
487
  import { useRef, useState } from "react";
485
488
  import { Columns3 } from "lucide-react";
486
- import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
489
+ import { Fragment, jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
487
490
  function ColSpanControl({ colSpan, columns, onChange }) {
488
491
  const buttonRef = useRef(null);
489
492
  const [open, setOpen] = useState(false);
490
493
  if (columns <= 1) return null;
491
494
  const current = Math.min(Math.max(colSpan, 1), columns);
492
495
  return /* @__PURE__ */ jsxs3("div", { className: "relative", children: [
493
- /* @__PURE__ */ jsx11(
496
+ /* @__PURE__ */ jsx11(Tooltip, { side: "right", content: /* @__PURE__ */ jsxs3(Fragment, { children: [
497
+ /* @__PURE__ */ jsx11(Kbd, { children: "Set" }),
498
+ " column span"
499
+ ] }), children: /* @__PURE__ */ jsx11(
494
500
  "button",
495
501
  {
496
502
  ref: buttonRef,
@@ -502,7 +508,7 @@ function ColSpanControl({ colSpan, columns, onChange }) {
502
508
  className: "pointer-events-auto inline-flex cursor-pointer items-center gap-1 rounded p-1 text-xs text-base-contrast-light hover:text-primary",
503
509
  children: /* @__PURE__ */ jsx11(Columns3, { size: 14 })
504
510
  }
505
- ),
511
+ ) }),
506
512
  /* @__PURE__ */ jsx11(Popover, { isOpen: open, onClose: () => setOpen(false), anchorRef: buttonRef, className: "min-w-28", children: /* @__PURE__ */ jsx11("ul", { role: "list", className: "py-1", children: Array.from({ length: columns }, (_, i) => i + 1).map((span) => /* @__PURE__ */ jsx11("li", { children: /* @__PURE__ */ jsxs3(
507
513
  PopoverItem,
508
514
  {
@@ -522,7 +528,7 @@ function ColSpanControl({ colSpan, columns, onChange }) {
522
528
  }
523
529
 
524
530
  // src/components/editor/ChildBlockWrapper.tsx
525
- import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
531
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
526
532
  var ChildBlockWrapper = forwardRef(
527
533
  function ChildBlockWrapper2({
528
534
  label,
@@ -559,10 +565,16 @@ var ChildBlockWrapper = forwardRef(
559
565
  ),
560
566
  children: [
561
567
  /* @__PURE__ */ jsx12("span", { className: "sr-only", children: label }),
562
- /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(DragHandle, { ref: dragHandleRef }) }),
568
+ /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(DragHandle, { ref: dragHandleRef, tooltipSide: "right" }) }),
563
569
  /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(ColSpanControl, { colSpan, columns, onChange: onColSpanChange }) }),
564
- hasSettings && onOpenSettings && /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(SettingsButton, { onClick: onOpenSettings }) }),
565
- /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(
570
+ hasSettings && onOpenSettings && /* @__PURE__ */ jsx12(Tooltip, { side: "right", className: "pointer-events-auto", content: /* @__PURE__ */ jsxs4(Fragment2, { children: [
571
+ /* @__PURE__ */ jsx12(Kbd, { children: "Edit" }),
572
+ " settings"
573
+ ] }), children: /* @__PURE__ */ jsx12(SettingsButton, { onClick: onOpenSettings }) }),
574
+ /* @__PURE__ */ jsx12(Tooltip, { side: "right", className: "pointer-events-auto", content: /* @__PURE__ */ jsxs4(Fragment2, { children: [
575
+ /* @__PURE__ */ jsx12(Kbd, { children: "Duplicate" }),
576
+ " block"
577
+ ] }), children: /* @__PURE__ */ jsx12(
566
578
  "button",
567
579
  {
568
580
  type: "button",
@@ -572,7 +584,10 @@ var ChildBlockWrapper = forwardRef(
572
584
  children: /* @__PURE__ */ jsx12(CopyPlus, { size: 16 })
573
585
  }
574
586
  ) }),
575
- /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(DeleteButton, { onDelete }) })
587
+ /* @__PURE__ */ jsx12(Tooltip, { side: "right", className: "pointer-events-auto", content: /* @__PURE__ */ jsxs4(Fragment2, { children: [
588
+ /* @__PURE__ */ jsx12(Kbd, { children: "Delete" }),
589
+ " block"
590
+ ] }), children: /* @__PURE__ */ jsx12(DeleteButton, { onDelete }) })
576
591
  ]
577
592
  }
578
593
  ),
@@ -769,7 +784,6 @@ function ContainerChild({
769
784
  index,
770
785
  child,
771
786
  columns,
772
- flow,
773
787
  cellClass,
774
788
  label,
775
789
  hasSettings,
@@ -782,7 +796,8 @@ function ContainerChild({
782
796
  onColSpanChange,
783
797
  onOpenSettings,
784
798
  onChildContentChange,
785
- openModal
799
+ openModal,
800
+ allowedEdges = ROW_EDGES
786
801
  }) {
787
802
  const isContainer = Array.isArray(child.content?.children);
788
803
  const { dragRef, handleRef, closestEdge, isDragging } = useBlockDnd({
@@ -790,7 +805,7 @@ function ContainerChild({
790
805
  containerId,
791
806
  index,
792
807
  isContainer,
793
- allowedEdges: flow === "column" ? COL_EDGES : ROW_EDGES,
808
+ allowedEdges,
794
809
  enabled: true
795
810
  });
796
811
  return /* @__PURE__ */ jsx13(
@@ -858,11 +873,37 @@ function EmptyColumnCell({
858
873
  }
859
874
  );
860
875
  }
876
+ function MasonryDropZone({
877
+ containerId,
878
+ index,
879
+ testId
880
+ }) {
881
+ const { dropRef, closestEdge } = useBlockDropZone({
882
+ containerId,
883
+ index,
884
+ allowedEdges: COL_EDGES,
885
+ enabled: true
886
+ });
887
+ const active = closestEdge !== null;
888
+ return /* @__PURE__ */ jsx13(
889
+ "div",
890
+ {
891
+ ref: dropRef,
892
+ "data-masonry-dropzone": true,
893
+ "data-testid": testId,
894
+ className: cn(
895
+ "mb-8 flex min-h-20 items-center justify-center rounded-md border-2 border-dashed break-inside-avoid text-xs text-base-contrast-light/50",
896
+ active ? "border-primary" : "border-base-200"
897
+ ),
898
+ children: index === 0 ? "Empty container \u2014 drag items here or use the + button" : "Drop here to add"
899
+ }
900
+ );
901
+ }
861
902
  function newId() {
862
903
  return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `child-${Math.random().toString(36).slice(2)}-${Date.now()}`;
863
904
  }
864
905
  function Container({ content, isEditMode, onChange, openModal }) {
865
- const { columns, flow, childDefaults, children } = content.content;
906
+ const { layout, columns, equalizeRowHeight, childDefaults, children } = content.content;
866
907
  const editable = isEditMode && !!onChange;
867
908
  const container = {
868
909
  id: content.id ?? "container",
@@ -883,8 +924,104 @@ function Container({ content, isEditMode, onChange, openModal }) {
883
924
  "grid",
884
925
  "gap-8",
885
926
  containerGridClass[columns] ?? "grid-cols-1",
886
- flow === "column" && "grid-flow-col"
927
+ equalizeRowHeight && "items-stretch"
887
928
  );
929
+ const cellStretch = equalizeRowHeight ? "h-full [&>*]:h-full" : void 0;
930
+ function buildOnOpenSettings(child, index) {
931
+ const def = getSection(child.type);
932
+ if (!def) return void 0;
933
+ const hasSettings = !!(def.settings || def.settingsForm) && !!openModal;
934
+ if (!hasSettings || !openModal) return void 0;
935
+ const childOptions = "options" in child ? child.options : void 0;
936
+ return () => {
937
+ const childOpts = childOptions ?? {};
938
+ const childContent = child.content ?? {};
939
+ const seed = { ...childContent, ...childOpts };
940
+ const applyResult = (result) => updateChild(index, {
941
+ ...child,
942
+ content: { ...child.content, ...result.content },
943
+ options: { ...childOpts, ...result.options }
944
+ });
945
+ if (def.settingsForm) {
946
+ const CustomForm = def.settingsForm;
947
+ openModal(`${def.label} Settings`, /* @__PURE__ */ jsx13(CustomForm, { ...seed, onChange: applyResult }));
948
+ } else if (def.settings) {
949
+ openModal(
950
+ `${def.label} Settings`,
951
+ /* @__PURE__ */ jsx13(
952
+ SettingsForm,
953
+ {
954
+ schema: def.settings,
955
+ values: seed,
956
+ onChange: applyResult,
957
+ tabs: def.settingsTabs
958
+ }
959
+ )
960
+ );
961
+ }
962
+ };
963
+ }
964
+ if (layout === "masonry") {
965
+ const laidOut = children.map((child, index) => ({ child, index })).filter(({ child }) => !isSpacer(child));
966
+ const masonryClass = cn("gap-8", masonryColumnsClass[columns] ?? "columns-1");
967
+ if (!editable) {
968
+ if (laidOut.length === 0) return null;
969
+ return /* @__PURE__ */ jsx13("div", { className: "@container", children: /* @__PURE__ */ jsx13("div", { "data-masonry": true, className: masonryClass, children: laidOut.map(({ child, index }) => {
970
+ const def = getSection(child.type);
971
+ if (!def) return null;
972
+ const Child = def.component;
973
+ const childOptions = "options" in child ? child.options : void 0;
974
+ const mergedOptions = { ...childDefaults ?? {}, ...childOptions ?? {} };
975
+ return /* @__PURE__ */ jsx13(
976
+ "div",
977
+ {
978
+ "data-masonry-box": true,
979
+ "data-child-index": index,
980
+ className: "mb-8 break-inside-avoid",
981
+ children: /* @__PURE__ */ jsx13(Child, { content: child, options: mergedOptions, isEditMode })
982
+ },
983
+ child.id ?? index
984
+ );
985
+ }) }) });
986
+ }
987
+ if (laidOut.length === 0) {
988
+ return /* @__PURE__ */ jsx13("div", { className: "@container", children: /* @__PURE__ */ jsx13("div", { "data-masonry": true, className: masonryClass, children: /* @__PURE__ */ jsx13(MasonryDropZone, { containerId: container.id, index: 0, testId: "masonry-empty" }) }) });
989
+ }
990
+ return /* @__PURE__ */ jsx13("div", { className: "@container", children: /* @__PURE__ */ jsxs5("div", { "data-masonry": true, className: masonryClass, children: [
991
+ laidOut.map(({ child, index }, k) => {
992
+ const def = getSection(child.type);
993
+ if (!def) return null;
994
+ const childOptions = "options" in child ? child.options : void 0;
995
+ const mergedOptions = { ...childDefaults ?? {}, ...childOptions ?? {} };
996
+ const hasSettings = !!(def.settings || def.settingsForm) && !!openModal;
997
+ return /* @__PURE__ */ jsx13("div", { "data-masonry-box": true, className: "mb-8 break-inside-avoid", children: /* @__PURE__ */ jsx13(
998
+ ContainerChild,
999
+ {
1000
+ containerId: container.id,
1001
+ index,
1002
+ child,
1003
+ columns: 1,
1004
+ cellClass: void 0,
1005
+ label: def.label,
1006
+ hasSettings,
1007
+ mergedOptions,
1008
+ isEditMode,
1009
+ clearSectionChrome: k === 0,
1010
+ ChildComp: def.component,
1011
+ onDelete: () => emit(removeChildAt(container, index).container),
1012
+ onDuplicate: () => emit(duplicateChildAt(container, index, newId)),
1013
+ onColSpanChange: () => {
1014
+ },
1015
+ onOpenSettings: buildOnOpenSettings(child, index),
1016
+ onChildContentChange: (c) => updateChild(index, c),
1017
+ openModal,
1018
+ allowedEdges: COL_EDGES
1019
+ }
1020
+ ) }, child.id ?? index);
1021
+ }),
1022
+ /* @__PURE__ */ jsx13(MasonryDropZone, { containerId: container.id, index: children.length, testId: "masonry-append" })
1023
+ ] }) });
1024
+ }
888
1025
  if (!editable) {
889
1026
  return /* @__PURE__ */ jsx13("div", { className: "@container", children: /* @__PURE__ */ jsx13("div", { className: gridClass, children: children.map((child, index) => {
890
1027
  const def = getSection(child.type);
@@ -895,7 +1032,8 @@ function Container({ content, isEditMode, onChange, openModal }) {
895
1032
  const mergedOptions = { ...childDefaults ?? {}, ...childOptions ?? {} };
896
1033
  const cellClass = cn(
897
1034
  colSpanClass[columns]?.[Math.min(span, columns)] || void 0,
898
- child.type === "spacer" && spacerHiddenClass[columns]
1035
+ child.type === "spacer" && spacerHiddenClass[columns],
1036
+ cellStretch
899
1037
  );
900
1038
  return /* @__PURE__ */ jsx13("div", { className: cellClass, children: /* @__PURE__ */ jsx13(Child, { content: child, options: mergedOptions, isEditMode }) }, child.id ?? index);
901
1039
  }) }) });
@@ -924,38 +1062,12 @@ function Container({ content, isEditMode, onChange, openModal }) {
924
1062
  const span = child.layout?.colSpan ?? 1;
925
1063
  const childOptions = "options" in child ? child.options : void 0;
926
1064
  const mergedOptions = { ...childDefaults ?? {}, ...childOptions ?? {} };
927
- const cellClass = colSpanClass[columns]?.[Math.min(span, columns)] || void 0;
1065
+ const cellClass = cn(
1066
+ colSpanClass[columns]?.[Math.min(span, columns)] || void 0,
1067
+ cellStretch
1068
+ );
928
1069
  const hasSettings = !!(def.settings || def.settingsForm) && !!openModal;
929
- const onOpenSettings = hasSettings && openModal ? () => {
930
- const childOpts = childOptions ?? {};
931
- const childContent = child.content ?? {};
932
- const seed = { ...childContent, ...childOpts };
933
- const applyResult = (result) => updateChild(index, {
934
- ...child,
935
- content: { ...child.content, ...result.content },
936
- options: { ...childOpts, ...result.options }
937
- });
938
- if (def.settingsForm) {
939
- const CustomForm = def.settingsForm;
940
- openModal(
941
- `${def.label} Settings`,
942
- /* @__PURE__ */ jsx13(CustomForm, { ...seed, onChange: applyResult })
943
- );
944
- } else if (def.settings) {
945
- openModal(
946
- `${def.label} Settings`,
947
- /* @__PURE__ */ jsx13(
948
- SettingsForm,
949
- {
950
- schema: def.settings,
951
- values: seed,
952
- onChange: applyResult,
953
- tabs: def.settingsTabs
954
- }
955
- )
956
- );
957
- }
958
- } : void 0;
1070
+ const onOpenSettings = buildOnOpenSettings(child, index);
959
1071
  return /* @__PURE__ */ jsx13(
960
1072
  ContainerChild,
961
1073
  {
@@ -963,7 +1075,6 @@ function Container({ content, isEditMode, onChange, openModal }) {
963
1075
  index,
964
1076
  child,
965
1077
  columns,
966
- flow,
967
1078
  cellClass,
968
1079
  label: def.label,
969
1080
  hasSettings,
@@ -1014,12 +1125,17 @@ function inheritableSchema(children) {
1014
1125
  }
1015
1126
  function ContainerSettingsForm({
1016
1127
  columns = 1,
1017
- flow = "row",
1128
+ layout: layoutProp = "grid",
1129
+ equalizeRowHeight = false,
1018
1130
  childDefaults = {},
1019
1131
  children = [],
1020
1132
  onChange
1021
1133
  }) {
1022
- const [layout, setLayout] = useState3({ columns, flow });
1134
+ const [layout, setLayout] = useState3({
1135
+ layout: layoutProp,
1136
+ columns,
1137
+ equalizeRowHeight
1138
+ });
1023
1139
  const [defaults, setDefaults] = useState3(childDefaults);
1024
1140
  const childSchema = inheritableSchema(children);
1025
1141
  const hasChildDefaults = Object.keys(childSchema).length > 0;
@@ -1027,27 +1143,41 @@ function ContainerSettingsForm({
1027
1143
  onChange({ content: { ...nextLayout, childDefaults: nextDefaults }, options: {} });
1028
1144
  }
1029
1145
  const layoutSchema = {
1146
+ layout: {
1147
+ type: "select",
1148
+ label: "Layout",
1149
+ default: layout.layout,
1150
+ target: "content",
1151
+ options: [
1152
+ { label: "Grid", value: "grid" },
1153
+ { label: "Masonry (image gallery)", value: "masonry" }
1154
+ ]
1155
+ },
1030
1156
  columns: {
1031
1157
  type: "select",
1032
1158
  label: "Columns",
1033
1159
  default: String(layout.columns),
1034
1160
  target: "content",
1035
1161
  coerce: "number",
1036
- options: Array.from({ length: MAX_CONTAINER_COLUMNS }, (_, i) => ({
1037
- label: String(i + 1),
1038
- value: String(i + 1)
1039
- }))
1162
+ // Masonry's CSS multi-column flow needs at least 2 columns to read as a
1163
+ // gallery (1 column would just be a plain stack, same as grid), so floor
1164
+ // the options at 2 there; grid still allows 1 (a single-column stack).
1165
+ options: Array.from(
1166
+ { length: layout.layout === "masonry" ? MAX_CONTAINER_COLUMNS - 1 : MAX_CONTAINER_COLUMNS },
1167
+ (_, i) => {
1168
+ const n = layout.layout === "masonry" ? i + 2 : i + 1;
1169
+ return { label: String(n), value: String(n) };
1170
+ }
1171
+ )
1040
1172
  },
1041
- flow: {
1042
- type: "select",
1043
- label: "Flow",
1044
- default: layout.flow,
1045
- target: "content",
1046
- options: [
1047
- { label: "Rows (wrap across)", value: "row" },
1048
- { label: "Columns (fill down)", value: "column" }
1049
- ]
1050
- }
1173
+ ...layout.layout === "grid" ? {
1174
+ equalizeRowHeight: {
1175
+ type: "checkbox",
1176
+ label: "Equalize row height",
1177
+ default: layout.equalizeRowHeight,
1178
+ target: "content"
1179
+ }
1180
+ } : {}
1051
1181
  };
1052
1182
  return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-6", children: [
1053
1183
  /* @__PURE__ */ jsxs6("fieldset", { className: "flex flex-col gap-4", children: [
@@ -1056,16 +1186,23 @@ function ContainerSettingsForm({
1056
1186
  SettingsForm,
1057
1187
  {
1058
1188
  schema: layoutSchema,
1059
- values: { columns: String(layout.columns), flow: layout.flow },
1189
+ values: {
1190
+ layout: layout.layout,
1191
+ columns: String(layout.columns),
1192
+ equalizeRowHeight: layout.equalizeRowHeight
1193
+ },
1060
1194
  onChange: (result) => {
1061
1195
  const next = {
1196
+ layout: result.content.layout ?? layout.layout,
1062
1197
  columns: Number(result.content.columns ?? layout.columns),
1063
- flow: String(result.content.flow ?? layout.flow)
1198
+ equalizeRowHeight: result.content.equalizeRowHeight ?? layout.equalizeRowHeight
1064
1199
  };
1200
+ if (next.layout === "masonry" && next.columns < 2) next.columns = 2;
1065
1201
  setLayout(next);
1066
1202
  emit(next, defaults);
1067
1203
  }
1068
- }
1204
+ },
1205
+ layout.layout
1069
1206
  )
1070
1207
  ] }),
1071
1208
  hasChildDefaults && /* @__PURE__ */ jsxs6("fieldset", { className: "flex flex-col gap-4 border-t border-base-200 pt-4", children: [
@@ -1091,33 +1228,55 @@ import { jsx as jsx15 } from "react/jsx-runtime";
1091
1228
  var schema5 = z5.object({
1092
1229
  type: z5.literal("container"),
1093
1230
  content: z5.object({
1094
- columns: z5.number().int().min(1).max(MAX_CONTAINER_COLUMNS).describe("Number of columns in the grid. Children flow across this many columns.").default(1),
1095
- flow: z5.enum(["row", "column"]).describe(
1096
- "Layout direction: 'row' (children flow left-to-right across columns) or 'column' (stacked)."
1097
- ).default("row"),
1231
+ layout: z5.enum(["grid", "masonry"]).describe(
1232
+ "Layout engine: 'grid' arranges children in fixed columns with per-child column spans; 'masonry' flows children through a CSS multi-column gallery at their natural height (no column spans)."
1233
+ ).default("grid"),
1234
+ columns: z5.number().int().min(1).max(MAX_CONTAINER_COLUMNS).describe(
1235
+ "Grid: number of columns children flow across. Masonry: number of CSS multi-columns children flow through."
1236
+ ).default(1),
1237
+ equalizeRowHeight: z5.boolean().describe(
1238
+ "Grid only: stretch every child to the height of the tallest child in its row (best-effort \u2014 applies to children whose root element can stretch, e.g. media/cards). Ignored in masonry."
1239
+ ).default(false),
1098
1240
  childDefaults: z5.record(z5.string(), z5.unknown()).describe("Optional default option values applied to child sections (inheritable settings only).").optional(),
1099
1241
  // Recursive: children are full blocks. z.lazy defers evaluation to parse
1100
1242
  // time, by which point every section schema (incl. container) is registered.
1101
1243
  children: z5.array(z5.lazy(() => getSectionSchema())).default([])
1102
- }).transform((c) => ({
1103
- ...c,
1104
- children: c.children.map(
1105
- (child) => child.layout?.colSpan && child.layout.colSpan > c.columns ? { ...child, layout: { ...child.layout, colSpan: c.columns } } : child
1106
- )
1107
- }))
1244
+ }).transform((c) => {
1245
+ if (c.layout === "masonry") {
1246
+ return {
1247
+ ...c,
1248
+ children: c.children.map((child) => {
1249
+ if (child.layout === void 0) return child;
1250
+ const { layout: _drop, ...rest } = child;
1251
+ return rest;
1252
+ })
1253
+ };
1254
+ }
1255
+ return {
1256
+ ...c,
1257
+ children: c.children.map(
1258
+ (child) => child.layout?.colSpan && child.layout.colSpan > c.columns ? { ...child, layout: { ...child.layout, colSpan: c.columns } } : child
1259
+ )
1260
+ };
1261
+ })
1108
1262
  });
1109
1263
  var Container_default = defineSection({
1110
1264
  type: "container",
1111
1265
  label: "Container",
1112
1266
  icon: /* @__PURE__ */ jsx15(LayoutTemplate, { size: 18 }),
1113
1267
  schema: schema5,
1114
- authoringNote: "A layout wrapper that arranges child sections into columns/rows. Use to place blocks side by side or build multi-column layouts; set columns and add children.",
1268
+ authoringNote: "A layout wrapper for child sections. 'grid' arranges children into a fixed number of columns (each child can span multiple columns) with an optional equal-row-height toggle; 'masonry' flows children through a CSS multi-column gallery at their natural height (no column spans). Set layout, columns, and add children.",
1115
1269
  component: ({ content, onChange, isEditMode, openModal }) => /* @__PURE__ */ jsx15(Container, { content, isEditMode: !!isEditMode, onChange, openModal }),
1116
1270
  defaults: () => ({
1117
1271
  type: "container",
1118
- content: { columns: 1, flow: "row", children: [] }
1272
+ content: { layout: "grid", columns: 1, equalizeRowHeight: false, children: [] }
1119
1273
  }),
1120
1274
  getLabel: (content) => `Container (${pluralize(content.content.children.length, "item")})`,
1275
+ // Aggregate child thumbnails so the reorder-sections modal shows a stacked media
1276
+ // preview for a container (restores the retired media_grid preview). Each child is a
1277
+ // full section block; delegate to its own type's getThumbnails. The modal caps the
1278
+ // stack at 3 + a "+N" badge and resolves each imageId → src itself.
1279
+ getThumbnails: (content) => content.content.children.flatMap((child) => getSection(child.type)?.getThumbnails?.(child) ?? []),
1121
1280
  settingsForm: ContainerSettingsForm
1122
1281
  });
1123
1282