@drawnagency/primitives 0.1.71 → 0.1.73

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 (90) hide show
  1. package/dist/{chunk-RTYJZAKR.js → chunk-7T2WQSWF.js} +12 -12
  2. package/dist/chunk-IFHRHV7S.js +117 -0
  3. package/dist/{chunk-2BHPWO7M.js → chunk-N6AE6NBB.js} +158 -32
  4. package/dist/{chunk-WXVKX6PY.js → chunk-NKVZGPR6.js} +41 -95
  5. package/dist/{chunk-3DCG4DNU.js → chunk-PZJL2S3Z.js} +8 -4
  6. package/dist/chunk-RBTSUR5V.js +37 -0
  7. package/dist/{chunk-6UDYJQL6.js → chunk-S55OZ7F2.js} +9 -1
  8. package/dist/{chunk-NR7UB6DZ.js → chunk-SFKYUKVN.js} +1 -1
  9. package/dist/{chunk-YMVYTYGQ.js → chunk-SWIHUIFT.js} +28 -16
  10. package/dist/{chunk-KWBM27UR.js → chunk-T7MP7ANP.js} +13 -64
  11. package/dist/{chunk-MU3CDERQ.js → chunk-XTRTK26E.js} +276 -96
  12. package/dist/{chunk-GKV77AA6.js → chunk-ZRWGV5O6.js} +3 -3
  13. package/dist/chunk-ZSPCE6MZ.js +70 -0
  14. package/dist/components/editor/SettingsForm.d.ts.map +1 -1
  15. package/dist/components/editor/index.js +6 -3
  16. package/dist/components/primitives/ResolvedMedia.d.ts.map +1 -1
  17. package/dist/components/primitives/index.js +7 -7
  18. package/dist/components/sections/Media/MediaBlock.d.ts.map +1 -1
  19. package/dist/components/sections/Video/VideoBlock.d.ts +31 -0
  20. package/dist/components/sections/Video/VideoBlock.d.ts.map +1 -0
  21. package/dist/components/sections/Video/index.d.ts +28 -0
  22. package/dist/components/sections/Video/index.d.ts.map +1 -0
  23. package/dist/components/sections/all-sections.d.ts +25 -0
  24. package/dist/components/sections/all-sections.d.ts.map +1 -1
  25. package/dist/components/sections/brand-guide.js +7 -7
  26. package/dist/components/sections/core-sections.d.ts +25 -0
  27. package/dist/components/sections/core-sections.d.ts.map +1 -1
  28. package/dist/components/sections/register-schemas.js +13 -12
  29. package/dist/components/sections/register.js +13 -12
  30. package/dist/components/shared/Tabs.d.ts +1 -1
  31. package/dist/components/shared/VideoSourceField.d.ts +8 -0
  32. package/dist/components/shared/VideoSourceField.d.ts.map +1 -0
  33. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  34. package/dist/components/shell/MediaLibraryModal.d.ts +4 -1
  35. package/dist/components/shell/MediaLibraryModal.d.ts.map +1 -1
  36. package/dist/hooks/useEditorPublish.d.ts +4 -1
  37. package/dist/hooks/useEditorPublish.d.ts.map +1 -1
  38. package/dist/hooks/useMediaPipeline.d.ts +9 -1
  39. package/dist/hooks/useMediaPipeline.d.ts.map +1 -1
  40. package/dist/index.js +28 -24
  41. package/dist/lib/index.js +10 -10
  42. package/dist/lib/registry.d.ts +6 -0
  43. package/dist/lib/registry.d.ts.map +1 -1
  44. package/dist/lib/save-chunks.d.ts +28 -0
  45. package/dist/lib/save-chunks.d.ts.map +1 -0
  46. package/dist/lib/video-url.d.ts +7 -0
  47. package/dist/lib/video-url.d.ts.map +1 -0
  48. package/dist/lib/video-url.js +9 -0
  49. package/dist/media/index.js +6 -2
  50. package/dist/media/resolve.d.ts +14 -0
  51. package/dist/media/resolve.d.ts.map +1 -1
  52. package/dist/media/videoPoster.d.ts +1 -0
  53. package/dist/media/videoPoster.d.ts.map +1 -1
  54. package/dist/schemas/index.js +4 -4
  55. package/dist/schemas/link.d.ts.map +1 -1
  56. package/dist/schemas/link.js +1 -1
  57. package/dist/schemas/media.d.ts +13 -0
  58. package/dist/schemas/media.d.ts.map +1 -1
  59. package/dist/schemas/shared.d.ts.map +1 -1
  60. package/dist/schemas/site-config.d.ts +1 -0
  61. package/dist/schemas/site-config.d.ts.map +1 -1
  62. package/dist/storage/index.d.ts +1 -1
  63. package/dist/storage/index.d.ts.map +1 -1
  64. package/dist/storage/types.d.ts +26 -0
  65. package/dist/storage/types.d.ts.map +1 -1
  66. package/package.json +5 -1
  67. package/src/components/editor/SettingsForm.tsx +11 -0
  68. package/src/components/primitives/ResolvedMedia.tsx +10 -5
  69. package/src/components/sections/Media/MediaBlock.tsx +2 -1
  70. package/src/components/sections/Video/VideoBlock.tsx +149 -0
  71. package/src/components/sections/Video/index.tsx +111 -0
  72. package/src/components/sections/core-sections.ts +2 -0
  73. package/src/components/shared/Tabs.tsx +2 -2
  74. package/src/components/shared/VideoSourceField.tsx +144 -0
  75. package/src/components/shell/EditorShell.tsx +16 -2
  76. package/src/components/shell/MediaLibraryModal.tsx +47 -1
  77. package/src/hooks/useEditorPublish.ts +137 -50
  78. package/src/hooks/useMediaPipeline.ts +188 -25
  79. package/src/lib/registry.ts +7 -0
  80. package/src/lib/save-chunks.ts +53 -0
  81. package/src/lib/video-url.ts +39 -0
  82. package/src/media/resolve.ts +18 -0
  83. package/src/media/videoPoster.ts +2 -2
  84. package/src/schemas/link.ts +15 -4
  85. package/src/schemas/media.ts +12 -0
  86. package/src/schemas/shared.ts +23 -14
  87. package/src/storage/index.ts +1 -1
  88. package/src/storage/types.ts +23 -0
  89. package/dist/chunk-4E66N6UV.js +0 -10
  90. package/dist/chunk-I5737FJY.js +0 -62
@@ -1,9 +1,25 @@
1
+ import {
2
+ EditableRichText,
3
+ defineHeadingSection
4
+ } from "./chunk-NKVZGPR6.js";
5
+ import {
6
+ embedUrl
7
+ } from "./chunk-RBTSUR5V.js";
1
8
  import {
2
9
  brandGuideSectionDefs,
3
10
  pluralize,
4
11
  stripHtmlToPlainText,
5
12
  truncate
6
- } from "./chunk-RTYJZAKR.js";
13
+ } from "./chunk-7T2WQSWF.js";
14
+ import {
15
+ EditablePlainText,
16
+ ImageDropZone,
17
+ MAX_CONTAINER_COLUMNS,
18
+ MediaBlock,
19
+ colSpanClass,
20
+ containerGridClass,
21
+ spacerHiddenClass
22
+ } from "./chunk-T7MP7ANP.js";
7
23
  import {
8
24
  DeleteButton,
9
25
  DragHandle,
@@ -15,23 +31,15 @@ import {
15
31
  buildBlockDropData,
16
32
  canDropBlock,
17
33
  isBlockDragData
18
- } from "./chunk-2BHPWO7M.js";
34
+ } from "./chunk-N6AE6NBB.js";
19
35
  import {
20
- EditableRichText,
21
- defineHeadingSection
22
- } from "./chunk-WXVKX6PY.js";
23
- import {
24
- EditablePlainText,
25
- MAX_CONTAINER_COLUMNS,
26
- MediaBlock,
27
- colSpanClass,
28
- containerGridClass,
29
- spacerHiddenClass
30
- } from "./chunk-KWBM27UR.js";
36
+ useMediaLibrary,
37
+ useResolvedMedia
38
+ } from "./chunk-IFHRHV7S.js";
31
39
  import {
32
40
  DEFAULT_LINK,
33
41
  LinkValueSchema
34
- } from "./chunk-3DCG4DNU.js";
42
+ } from "./chunk-PZJL2S3Z.js";
35
43
  import {
36
44
  sanitizeHtml
37
45
  } from "./chunk-53DKVAB6.js";
@@ -41,13 +49,13 @@ import {
41
49
  import {
42
50
  getSectionSchema
43
51
  } from "./chunk-TZQPOR5A.js";
44
- import {
45
- SingleMediaReferenceSchema
46
- } from "./chunk-YMVYTYGQ.js";
47
52
  import {
48
53
  defineSection,
49
54
  getSection
50
55
  } from "./chunk-VKAGMEKE.js";
56
+ import {
57
+ SingleMediaReferenceSchema
58
+ } from "./chunk-SWIHUIFT.js";
51
59
 
52
60
  // src/components/sections/LinkHeading/index.tsx
53
61
  import { Heading1 } from "lucide-react";
@@ -209,20 +217,191 @@ var Media_default = defineSection({
209
217
  ]
210
218
  });
211
219
 
212
- // src/components/sections/Button/index.tsx
220
+ // src/components/sections/Video/index.tsx
213
221
  import { z as z3 } from "zod";
222
+ import { Clapperboard } from "lucide-react";
223
+
224
+ // src/components/sections/Video/VideoBlock.tsx
225
+ import { jsx as jsx7, jsxs } from "react/jsx-runtime";
226
+ function VideoBlock({ source, caption, options, isEditMode, onSourceChange, onCaptionChange }) {
227
+ const mediaLibrary = useMediaLibrary();
228
+ const imageId = source.type === "library" ? source.ref.imageId : void 0;
229
+ const posterId = source.type === "library" ? source.poster?.imageId : void 0;
230
+ const resolved = useResolvedMedia(imageId);
231
+ const posterOverride = useResolvedMedia(posterId);
232
+ const playback = options.playback ?? "click";
233
+ const editable = Boolean(isEditMode && onSourceChange);
234
+ const captionEditable = Boolean(isEditMode && onCaptionChange);
235
+ const showCaptionBlock = Boolean(options.showCaption && (captionEditable || caption));
236
+ const replaceWithLibraryVideo = editable ? (newImageId) => onSourceChange({ type: "library", ref: { imageId: newImageId } }) : void 0;
237
+ let media;
238
+ let aspect;
239
+ if (source.type === "external") {
240
+ aspect = `${source.width} / ${source.height}`;
241
+ media = /* @__PURE__ */ jsx7(
242
+ "iframe",
243
+ {
244
+ src: embedUrl(source),
245
+ title: caption || "Embedded video",
246
+ loading: "lazy",
247
+ allow: "autoplay; fullscreen; picture-in-picture; encrypted-media",
248
+ allowFullScreen: true,
249
+ className: "absolute inset-0 h-full w-full border-0"
250
+ }
251
+ );
252
+ } else {
253
+ const baked = source.ref;
254
+ const bakedSrc = typeof baked.src === "string" ? baked.src : void 0;
255
+ const bakedPoster = typeof baked.poster === "string" ? baked.poster : void 0;
256
+ const bakedWidth = typeof baked.width === "number" ? baked.width : void 0;
257
+ const bakedHeight = typeof baked.height === "number" ? baked.height : void 0;
258
+ const bakedPosterOverride = source.poster;
259
+ const bakedPosterOverrideSrc = typeof bakedPosterOverride?.src === "string" ? bakedPosterOverride.src : void 0;
260
+ const src = bakedSrc ?? resolved.src;
261
+ const poster = bakedPosterOverrideSrc ?? posterOverride.src ?? bakedPoster ?? resolved.poster;
262
+ const width = bakedWidth ?? resolved.width;
263
+ const height = bakedHeight ?? resolved.height;
264
+ aspect = width && height ? `${width} / ${height}` : void 0;
265
+ const playbackProps = playback === "ambient" ? { autoPlay: true, muted: true, loop: true, playsInline: true } : { controls: true, preload: "metadata" };
266
+ const videoEl = imageId ? /* @__PURE__ */ jsx7(
267
+ "video",
268
+ {
269
+ src,
270
+ poster,
271
+ width,
272
+ height,
273
+ className: "absolute inset-0 h-full w-full object-contain",
274
+ ...playbackProps
275
+ }
276
+ ) : /* @__PURE__ */ jsx7("div", { className: "absolute inset-0 flex items-center justify-center bg-base-accent text-base-contrast-light/50", children: /* @__PURE__ */ jsx7("span", { className: "text-sm", children: "No video selected" }) });
277
+ media = replaceWithLibraryVideo ? /* @__PURE__ */ jsx7(ImageDropZone, { onImageSelected: replaceWithLibraryVideo, className: "absolute inset-0 h-full w-full", children: videoEl }) : videoEl;
278
+ }
279
+ const onSurfaceClick = replaceWithLibraryVideo && mediaLibrary ? () => mediaLibrary.openSelectModal((id) => replaceWithLibraryVideo(id)) : void 0;
280
+ return /* @__PURE__ */ jsxs("figure", { className: "w-full", children: [
281
+ /* @__PURE__ */ jsx7(
282
+ "div",
283
+ {
284
+ "data-testid": "video-open-surface",
285
+ className: cn("relative w-full overflow-hidden", onSurfaceClick && "cursor-pointer"),
286
+ style: { aspectRatio: aspect ?? "16 / 9" },
287
+ onClick: onSurfaceClick,
288
+ onPointerDown: onSurfaceClick ? (e) => e.stopPropagation() : void 0,
289
+ children: media
290
+ }
291
+ ),
292
+ showCaptionBlock && /* @__PURE__ */ jsx7("figcaption", { className: "mt-2 min-h-[1em] text-sm text-base-contrast-light/70", children: captionEditable ? /* @__PURE__ */ jsx7(
293
+ EditablePlainText,
294
+ {
295
+ tag: "span",
296
+ value: caption ?? "",
297
+ onChange: onCaptionChange,
298
+ isEditMode: true,
299
+ placeholder: "Caption",
300
+ className: "block min-h-[1em]"
301
+ }
302
+ ) : caption })
303
+ ] });
304
+ }
305
+
306
+ // src/components/sections/Video/index.tsx
307
+ import { jsx as jsx8 } from "react/jsx-runtime";
308
+ var LibrarySource = z3.object({
309
+ type: z3.literal("library"),
310
+ ref: z3.object({
311
+ imageId: z3.string().default("").describe("Media-library id of the video asset. Required for library videos.")
312
+ }),
313
+ // Kept as a SIBLING of `ref` (not nested inside it): the SSR ref-baker
314
+ // (resolveManifestReferences) matches objects structurally by a top-level
315
+ // `imageId` key and merges the resolved video's own poster onto that same
316
+ // object. Nesting the override inside `ref` would collide with that bake
317
+ // and get silently overwritten on the published viewer. As a sibling with
318
+ // its own `imageId`, it gets baked independently.
319
+ poster: z3.object({
320
+ imageId: z3.string().describe("Media-library image id used as the poster frame instead of the auto-generated one.")
321
+ }).optional().describe("Optional poster-image override for a library video.")
322
+ });
323
+ var ExternalSource = z3.object({
324
+ type: z3.literal("external"),
325
+ provider: z3.enum(["youtube", "vimeo"]).describe("Hosting provider, derived from the pasted URL."),
326
+ id: z3.string().describe("Provider video id parsed from the pasted URL."),
327
+ url: z3.string().describe("The original video page URL as pasted."),
328
+ width: z3.number().positive().describe("Native width used for the aspect-ratio box (from oEmbed; 16 when unknown)."),
329
+ height: z3.number().positive().describe("Native height used for the aspect-ratio box (from oEmbed; 9 when unknown).")
330
+ });
331
+ var schema3 = z3.object({
332
+ type: z3.literal("video"),
333
+ content: z3.object({
334
+ source: z3.discriminatedUnion("type", [LibrarySource, ExternalSource]).describe("Where the video comes from: a media-library upload or an external YouTube/Vimeo URL."),
335
+ caption: z3.string().optional().describe("Optional caption rendered beneath the player when Show caption is on.")
336
+ }),
337
+ options: z3.object({
338
+ playback: z3.enum(["ambient", "click"]).describe("'click' (default): poster + native controls with audio. 'ambient': autoplays muted in a loop with no controls.").optional(),
339
+ showCaption: z3.boolean().describe("When true, render the caption beneath the player.").optional()
340
+ }).default({})
341
+ });
342
+ var Video_default = defineSection({
343
+ type: "video",
344
+ label: "Video",
345
+ icon: /* @__PURE__ */ jsx8(Clapperboard, { size: 18 }),
346
+ schema: schema3,
347
+ authoringNote: "A watchable video player \u2014 self-hosted from the media library or embedded from YouTube/Vimeo. Use playback 'click' for content viewers press play on (controls + audio) and 'ambient' for silent looping footage. For decorative loops inside grids prefer the Media block; large self-hosted files require the site's asset storage.",
348
+ component: ({ content, options, onChange }) => /* @__PURE__ */ jsx8(
349
+ VideoBlock,
350
+ {
351
+ source: content.content.source,
352
+ caption: content.content.caption,
353
+ options: options ?? {},
354
+ isEditMode: !!onChange,
355
+ onSourceChange: onChange ? (source) => onChange({ ...content, content: { ...content.content, source } }) : void 0,
356
+ onCaptionChange: onChange ? (caption) => onChange({ ...content, content: { ...content.content, caption: caption || void 0 } }) : void 0
357
+ }
358
+ ),
359
+ defaults: () => ({
360
+ type: "video",
361
+ content: { source: { type: "library", ref: { imageId: "" } } },
362
+ options: {}
363
+ }),
364
+ getLabel: () => "Video",
365
+ getThumbnails: (content) => content.content.source.type === "library" && content.content.source.ref.imageId ? [{ type: "image", src: content.content.source.ref.imageId }] : [],
366
+ settings: {
367
+ source: {
368
+ type: "video-source",
369
+ label: "Source",
370
+ target: "content",
371
+ default: { type: "library", ref: { imageId: "" } }
372
+ },
373
+ playback: {
374
+ type: "select",
375
+ label: "Playback",
376
+ default: "click",
377
+ options: [
378
+ { label: "Click to play (controls + audio)", value: "click" },
379
+ { label: "Ambient loop (muted, no controls)", value: "ambient" }
380
+ ]
381
+ },
382
+ showCaption: { type: "checkbox", label: "Show caption", default: false }
383
+ },
384
+ inheritableSettings: ["playback", "showCaption"],
385
+ settingsTabs: [
386
+ { label: "Source", fields: ["source"] },
387
+ { label: "Display", fields: ["playback", "showCaption"] }
388
+ ]
389
+ });
390
+
391
+ // src/components/sections/Button/index.tsx
392
+ import { z as z4 } from "zod";
214
393
  import { RectangleHorizontal } from "lucide-react";
215
394
 
216
395
  // src/components/sections/Button/CTAButton.tsx
217
396
  import { Download } from "lucide-react";
218
- import { jsx as jsx7, jsxs } from "react/jsx-runtime";
397
+ import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
219
398
  function Button({ text, link, download, onChange }) {
220
399
  const base = "inline-block rounded-md px-6 py-3 font-bold transition-colors";
221
400
  const variant = "border-2 border-primary text-primary hover:bg-primary hover:text-primary-contrast";
222
401
  if (onChange) {
223
- return /* @__PURE__ */ jsxs("span", { className: cn(base, variant, "inline-flex items-center gap-2"), children: [
224
- download && /* @__PURE__ */ jsx7(Download, { size: 16, className: "shrink-0" }),
225
- /* @__PURE__ */ jsx7(
402
+ return /* @__PURE__ */ jsxs2("span", { className: cn(base, variant, "inline-flex items-center gap-2"), children: [
403
+ download && /* @__PURE__ */ jsx9(Download, { size: 16, className: "shrink-0" }),
404
+ /* @__PURE__ */ jsx9(
226
405
  EditablePlainText,
227
406
  {
228
407
  tag: "span",
@@ -237,7 +416,7 @@ function Button({ text, link, download, onChange }) {
237
416
  const href = link && link.kind === "external" ? link.href : "";
238
417
  const target = link?.target;
239
418
  if (href) {
240
- return /* @__PURE__ */ jsxs(
419
+ return /* @__PURE__ */ jsxs2(
241
420
  "a",
242
421
  {
243
422
  href,
@@ -245,34 +424,34 @@ function Button({ text, link, download, onChange }) {
245
424
  download: download ? "" : void 0,
246
425
  className: cn(base, variant, download && "inline-flex items-center gap-2"),
247
426
  children: [
248
- download && /* @__PURE__ */ jsx7(Download, { size: 16, className: "shrink-0" }),
427
+ download && /* @__PURE__ */ jsx9(Download, { size: 16, className: "shrink-0" }),
249
428
  text
250
429
  ]
251
430
  }
252
431
  );
253
432
  }
254
- return /* @__PURE__ */ jsx7("button", { className: cn("cursor-pointer", base, variant), children: text });
433
+ return /* @__PURE__ */ jsx9("button", { className: cn("cursor-pointer", base, variant), children: text });
255
434
  }
256
435
 
257
436
  // src/components/sections/Button/index.tsx
258
- import { jsx as jsx8 } from "react/jsx-runtime";
259
- var schema3 = z3.object({
260
- type: z3.literal("button"),
261
- content: z3.object({
262
- text: z3.string().describe("The button label. Plain text, short (a few words)."),
437
+ import { jsx as jsx10 } from "react/jsx-runtime";
438
+ var schema4 = z4.object({
439
+ type: z4.literal("button"),
440
+ content: z4.object({
441
+ text: z4.string().describe("The button label. Plain text, short (a few words)."),
263
442
  link: LinkValueSchema.describe(
264
443
  "Where the button points \u2014 an internal page or a safe external URL."
265
444
  ).optional(),
266
- download: z3.boolean().describe("When true, the link is treated as a file download rather than navigation.").optional()
445
+ download: z4.boolean().describe("When true, the link is treated as a file download rather than navigation.").optional()
267
446
  })
268
447
  });
269
448
  var Button_default = defineSection({
270
449
  type: "button",
271
450
  label: "Button",
272
- icon: /* @__PURE__ */ jsx8(RectangleHorizontal, { size: 18 }),
273
- schema: schema3,
451
+ icon: /* @__PURE__ */ jsx10(RectangleHorizontal, { size: 18 }),
452
+ schema: schema4,
274
453
  authoringNote: "A call-to-action button linking to an internal page or external URL, optionally as a file download. Use for a single prominent action.",
275
- component: ({ content, onChange }) => /* @__PURE__ */ jsx8(
454
+ component: ({ content, onChange }) => /* @__PURE__ */ jsx10(
276
455
  Button,
277
456
  {
278
457
  text: content.content.text,
@@ -290,7 +469,7 @@ var Button_default = defineSection({
290
469
  });
291
470
 
292
471
  // src/components/sections/Container/index.tsx
293
- import { z as z4 } from "zod";
472
+ import { z as z5 } from "zod";
294
473
  import { LayoutTemplate } from "lucide-react";
295
474
 
296
475
  // src/components/sections/Container/Container.tsx
@@ -303,14 +482,14 @@ import { CopyPlus } from "lucide-react";
303
482
  // src/components/editor/ColSpanControl.tsx
304
483
  import { useRef, useState } from "react";
305
484
  import { Columns3 } from "lucide-react";
306
- import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
485
+ import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
307
486
  function ColSpanControl({ colSpan, columns, onChange }) {
308
487
  const buttonRef = useRef(null);
309
488
  const [open, setOpen] = useState(false);
310
489
  if (columns <= 1) return null;
311
490
  const current = Math.min(Math.max(colSpan, 1), columns);
312
- return /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
313
- /* @__PURE__ */ jsx9(
491
+ return /* @__PURE__ */ jsxs3("div", { className: "relative", children: [
492
+ /* @__PURE__ */ jsx11(
314
493
  "button",
315
494
  {
316
495
  ref: buttonRef,
@@ -320,10 +499,10 @@ function ColSpanControl({ colSpan, columns, onChange }) {
320
499
  "aria-expanded": open,
321
500
  onClick: () => setOpen((v) => !v),
322
501
  className: "pointer-events-auto inline-flex cursor-pointer items-center gap-1 rounded p-1 text-xs text-base-contrast-light hover:text-primary",
323
- children: /* @__PURE__ */ jsx9(Columns3, { size: 14 })
502
+ children: /* @__PURE__ */ jsx11(Columns3, { size: 14 })
324
503
  }
325
504
  ),
326
- /* @__PURE__ */ jsx9(Popover, { isOpen: open, onClose: () => setOpen(false), anchorRef: buttonRef, className: "min-w-28", children: /* @__PURE__ */ jsx9("ul", { role: "list", className: "py-1", children: Array.from({ length: columns }, (_, i) => i + 1).map((span) => /* @__PURE__ */ jsx9("li", { children: /* @__PURE__ */ jsxs2(
505
+ /* @__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(
327
506
  PopoverItem,
328
507
  {
329
508
  "aria-current": span === current,
@@ -342,7 +521,7 @@ function ColSpanControl({ colSpan, columns, onChange }) {
342
521
  }
343
522
 
344
523
  // src/components/editor/ChildBlockWrapper.tsx
345
- import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
524
+ import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
346
525
  var ChildBlockWrapper = forwardRef(
347
526
  function ChildBlockWrapper2({
348
527
  label,
@@ -358,7 +537,7 @@ var ChildBlockWrapper = forwardRef(
358
537
  onOpenSettings,
359
538
  children
360
539
  }, ref) {
361
- return /* @__PURE__ */ jsxs3(
540
+ return /* @__PURE__ */ jsxs4(
362
541
  "div",
363
542
  {
364
543
  ref,
@@ -368,39 +547,39 @@ var ChildBlockWrapper = forwardRef(
368
547
  "hover:outline hover:outline-2 hover:outline-primary/30"
369
548
  ),
370
549
  children: [
371
- /* @__PURE__ */ jsxs3("div", { className: "absolute top-0 left-0 -translate-x-full z-20 flex flex-col items-center gap-1 opacity-0 transition-opacity group-hover/childblock:opacity-100 group-focus-within/childblock:opacity-100 pointer-events-none", children: [
372
- /* @__PURE__ */ jsx10("span", { className: "sr-only", children: label }),
373
- /* @__PURE__ */ jsx10("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx10(DragHandle, { ref: dragHandleRef }) }),
374
- /* @__PURE__ */ jsx10("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx10(ColSpanControl, { colSpan, columns, onChange: onColSpanChange }) }),
375
- hasSettings && onOpenSettings && /* @__PURE__ */ jsx10("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx10(SettingsButton, { onClick: onOpenSettings }) }),
376
- /* @__PURE__ */ jsx10("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx10(
550
+ /* @__PURE__ */ jsxs4("div", { className: "absolute top-0 left-0 -translate-x-full z-20 flex flex-col items-center gap-1 opacity-0 transition-opacity group-hover/childblock:opacity-100 group-focus-within/childblock:opacity-100 pointer-events-none", children: [
551
+ /* @__PURE__ */ jsx12("span", { className: "sr-only", children: label }),
552
+ /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(DragHandle, { ref: dragHandleRef }) }),
553
+ /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(ColSpanControl, { colSpan, columns, onChange: onColSpanChange }) }),
554
+ hasSettings && onOpenSettings && /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(SettingsButton, { onClick: onOpenSettings }) }),
555
+ /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(
377
556
  "button",
378
557
  {
379
558
  type: "button",
380
559
  "aria-label": "Duplicate block",
381
560
  onClick: onDuplicate,
382
561
  className: "cursor-pointer rounded p-1 text-base-contrast-light hover:text-primary",
383
- children: /* @__PURE__ */ jsx10(CopyPlus, { size: 16 })
562
+ children: /* @__PURE__ */ jsx12(CopyPlus, { size: 16 })
384
563
  }
385
564
  ) }),
386
- /* @__PURE__ */ jsx10("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx10(DeleteButton, { onDelete }) })
565
+ /* @__PURE__ */ jsx12("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx12(DeleteButton, { onDelete }) })
387
566
  ] }),
388
- closestEdge === "left" && /* @__PURE__ */ jsx10(
567
+ closestEdge === "left" && /* @__PURE__ */ jsx12(
389
568
  "div",
390
569
  {
391
570
  "data-drop-edge": "left",
392
571
  className: "absolute bottom-0 left-0 top-0 z-10 w-0.5 -translate-x-1 bg-primary"
393
572
  }
394
573
  ),
395
- closestEdge === "right" && /* @__PURE__ */ jsx10(
574
+ closestEdge === "right" && /* @__PURE__ */ jsx12(
396
575
  "div",
397
576
  {
398
577
  "data-drop-edge": "right",
399
578
  className: "absolute bottom-0 right-0 top-0 z-10 w-0.5 translate-x-1 bg-primary"
400
579
  }
401
580
  ),
402
- closestEdge === "top" && /* @__PURE__ */ jsx10("div", { "data-drop-edge": "top", className: "absolute left-0 right-0 top-0 z-10 h-0.5 -translate-y-1 bg-primary" }),
403
- closestEdge === "bottom" && /* @__PURE__ */ jsx10("div", { "data-drop-edge": "bottom", className: "absolute left-0 right-0 bottom-0 z-10 h-0.5 translate-y-1 bg-primary" }),
581
+ closestEdge === "top" && /* @__PURE__ */ jsx12("div", { "data-drop-edge": "top", className: "absolute left-0 right-0 top-0 z-10 h-0.5 -translate-y-1 bg-primary" }),
582
+ closestEdge === "bottom" && /* @__PURE__ */ jsx12("div", { "data-drop-edge": "bottom", className: "absolute left-0 right-0 bottom-0 z-10 h-0.5 translate-y-1 bg-primary" }),
404
583
  children
405
584
  ]
406
585
  }
@@ -570,7 +749,7 @@ function trimTrailingSpacers(container) {
570
749
  }
571
750
 
572
751
  // src/components/sections/Container/Container.tsx
573
- import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
752
+ import { jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
574
753
  var ROW_EDGES = ["left", "right"];
575
754
  var COL_EDGES = ["top", "bottom"];
576
755
  function ContainerChild({
@@ -601,14 +780,14 @@ function ContainerChild({
601
780
  allowedEdges: flow === "column" ? COL_EDGES : ROW_EDGES,
602
781
  enabled: true
603
782
  });
604
- return /* @__PURE__ */ jsx11(
783
+ return /* @__PURE__ */ jsx13(
605
784
  "div",
606
785
  {
607
786
  ref: dragRef,
608
787
  className: cellClass,
609
788
  "data-child-index": index,
610
789
  "data-child-id": child.id,
611
- children: /* @__PURE__ */ jsx11(
790
+ children: /* @__PURE__ */ jsx13(
612
791
  ChildBlockWrapper,
613
792
  {
614
793
  label,
@@ -622,7 +801,7 @@ function ContainerChild({
622
801
  onDuplicate,
623
802
  onColSpanChange,
624
803
  onOpenSettings,
625
- children: /* @__PURE__ */ jsx11(
804
+ children: /* @__PURE__ */ jsx13(
626
805
  ChildComp,
627
806
  {
628
807
  content: child,
@@ -650,7 +829,7 @@ function EmptyColumnCell({
650
829
  enabled: true
651
830
  });
652
831
  const active = closestEdge !== null;
653
- return /* @__PURE__ */ jsx11(
832
+ return /* @__PURE__ */ jsx13(
654
833
  "div",
655
834
  {
656
835
  ref: dropRef,
@@ -688,7 +867,7 @@ function Container({ content, isEditMode, onChange, openModal }) {
688
867
  flow === "column" && "grid-flow-col"
689
868
  );
690
869
  if (!editable) {
691
- return /* @__PURE__ */ jsx11("div", { className: "@container", children: /* @__PURE__ */ jsx11("div", { className: gridClass, children: children.map((child, index) => {
870
+ return /* @__PURE__ */ jsx13("div", { className: "@container", children: /* @__PURE__ */ jsx13("div", { className: gridClass, children: children.map((child, index) => {
692
871
  const def = getSection(child.type);
693
872
  if (!def) return null;
694
873
  const Child = def.component;
@@ -699,7 +878,7 @@ function Container({ content, isEditMode, onChange, openModal }) {
699
878
  colSpanClass[columns]?.[Math.min(span, columns)] || void 0,
700
879
  child.type === "spacer" && spacerHiddenClass[columns]
701
880
  );
702
- return /* @__PURE__ */ jsx11("div", { className: cellClass, children: /* @__PURE__ */ jsx11(Child, { content: child, options: mergedOptions, isEditMode }) }, child.id ?? index);
881
+ return /* @__PURE__ */ jsx13("div", { className: cellClass, children: /* @__PURE__ */ jsx13(Child, { content: child, options: mergedOptions, isEditMode }) }, child.id ?? index);
703
882
  }) }) });
704
883
  }
705
884
  function columnStartOf(index) {
@@ -713,7 +892,7 @@ function Container({ content, isEditMode, onChange, openModal }) {
713
892
  const def = getSection(child.type);
714
893
  if (!def) return null;
715
894
  if (isSpacer(child)) {
716
- return /* @__PURE__ */ jsx11(
895
+ return /* @__PURE__ */ jsx13(
717
896
  EmptyColumnCell,
718
897
  {
719
898
  containerId: container.id,
@@ -741,12 +920,12 @@ function Container({ content, isEditMode, onChange, openModal }) {
741
920
  const CustomForm = def.settingsForm;
742
921
  openModal(
743
922
  `${def.label} Settings`,
744
- /* @__PURE__ */ jsx11(CustomForm, { ...seed, onChange: applyResult })
923
+ /* @__PURE__ */ jsx13(CustomForm, { ...seed, onChange: applyResult })
745
924
  );
746
925
  } else if (def.settings) {
747
926
  openModal(
748
927
  `${def.label} Settings`,
749
- /* @__PURE__ */ jsx11(
928
+ /* @__PURE__ */ jsx13(
750
929
  SettingsForm,
751
930
  {
752
931
  schema: def.settings,
@@ -758,7 +937,7 @@ function Container({ content, isEditMode, onChange, openModal }) {
758
937
  );
759
938
  }
760
939
  } : void 0;
761
- return /* @__PURE__ */ jsx11(
940
+ return /* @__PURE__ */ jsx13(
762
941
  ContainerChild,
763
942
  {
764
943
  containerId: container.id,
@@ -784,9 +963,9 @@ function Container({ content, isEditMode, onChange, openModal }) {
784
963
  }
785
964
  const occupied = occupiedColumns(children, columns);
786
965
  const emptyCols = occupied < columns ? columns - occupied : 0;
787
- return /* @__PURE__ */ jsx11("div", { className: "@container", children: /* @__PURE__ */ jsxs4("div", { className: gridClass, children: [
966
+ return /* @__PURE__ */ jsx13("div", { className: "@container", children: /* @__PURE__ */ jsxs5("div", { className: gridClass, children: [
788
967
  children.map(renderEditChild),
789
- Array.from({ length: emptyCols }, (_, i) => /* @__PURE__ */ jsx11(
968
+ Array.from({ length: emptyCols }, (_, i) => /* @__PURE__ */ jsx13(
790
969
  EmptyColumnCell,
791
970
  {
792
971
  containerId: container.id,
@@ -800,7 +979,7 @@ function Container({ content, isEditMode, onChange, openModal }) {
800
979
 
801
980
  // src/components/sections/Container/ContainerSettingsForm.tsx
802
981
  import { useState as useState3 } from "react";
803
- import { jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
982
+ import { jsx as jsx14, jsxs as jsxs6 } from "react/jsx-runtime";
804
983
  function inheritableSchema(children) {
805
984
  const out = {};
806
985
  for (const child of children) {
@@ -850,10 +1029,10 @@ function ContainerSettingsForm({
850
1029
  ]
851
1030
  }
852
1031
  };
853
- return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-6", children: [
854
- /* @__PURE__ */ jsxs5("fieldset", { className: "flex flex-col gap-4", children: [
855
- /* @__PURE__ */ jsx12("legend", { className: "mb-2 text-sm font-semibold text-base-contrast", children: "Layout" }),
856
- /* @__PURE__ */ jsx12(
1032
+ return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-6", children: [
1033
+ /* @__PURE__ */ jsxs6("fieldset", { className: "flex flex-col gap-4", children: [
1034
+ /* @__PURE__ */ jsx14("legend", { className: "mb-2 text-sm font-semibold text-base-contrast", children: "Layout" }),
1035
+ /* @__PURE__ */ jsx14(
857
1036
  SettingsForm,
858
1037
  {
859
1038
  schema: layoutSchema,
@@ -869,9 +1048,9 @@ function ContainerSettingsForm({
869
1048
  }
870
1049
  )
871
1050
  ] }),
872
- hasChildDefaults && /* @__PURE__ */ jsxs5("fieldset", { className: "flex flex-col gap-4 border-t border-base-200 pt-4", children: [
873
- /* @__PURE__ */ jsx12("legend", { className: "mb-2 text-sm font-semibold text-base-contrast", children: "Apply to all items" }),
874
- /* @__PURE__ */ jsx12(
1051
+ hasChildDefaults && /* @__PURE__ */ jsxs6("fieldset", { className: "flex flex-col gap-4 border-t border-base-200 pt-4", children: [
1052
+ /* @__PURE__ */ jsx14("legend", { className: "mb-2 text-sm font-semibold text-base-contrast", children: "Apply to all items" }),
1053
+ /* @__PURE__ */ jsx14(
875
1054
  SettingsForm,
876
1055
  {
877
1056
  schema: childSchema,
@@ -888,18 +1067,18 @@ function ContainerSettingsForm({
888
1067
  }
889
1068
 
890
1069
  // src/components/sections/Container/index.tsx
891
- import { jsx as jsx13 } from "react/jsx-runtime";
892
- var schema4 = z4.object({
893
- type: z4.literal("container"),
894
- content: z4.object({
895
- columns: z4.number().int().min(1).max(MAX_CONTAINER_COLUMNS).describe("Number of columns in the grid. Children flow across this many columns.").default(1),
896
- flow: z4.enum(["row", "column"]).describe(
1070
+ import { jsx as jsx15 } from "react/jsx-runtime";
1071
+ var schema5 = z5.object({
1072
+ type: z5.literal("container"),
1073
+ content: z5.object({
1074
+ 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),
1075
+ flow: z5.enum(["row", "column"]).describe(
897
1076
  "Layout direction: 'row' (children flow left-to-right across columns) or 'column' (stacked)."
898
1077
  ).default("row"),
899
- childDefaults: z4.record(z4.string(), z4.unknown()).describe("Optional default option values applied to child sections (inheritable settings only).").optional(),
1078
+ childDefaults: z5.record(z5.string(), z5.unknown()).describe("Optional default option values applied to child sections (inheritable settings only).").optional(),
900
1079
  // Recursive: children are full blocks. z.lazy defers evaluation to parse
901
1080
  // time, by which point every section schema (incl. container) is registered.
902
- children: z4.array(z4.lazy(() => getSectionSchema())).default([])
1081
+ children: z5.array(z5.lazy(() => getSectionSchema())).default([])
903
1082
  }).transform((c) => ({
904
1083
  ...c,
905
1084
  children: c.children.map(
@@ -910,10 +1089,10 @@ var schema4 = z4.object({
910
1089
  var Container_default = defineSection({
911
1090
  type: "container",
912
1091
  label: "Container",
913
- icon: /* @__PURE__ */ jsx13(LayoutTemplate, { size: 18 }),
914
- schema: schema4,
1092
+ icon: /* @__PURE__ */ jsx15(LayoutTemplate, { size: 18 }),
1093
+ schema: schema5,
915
1094
  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.",
916
- component: ({ content, onChange, isEditMode, openModal }) => /* @__PURE__ */ jsx13(Container, { content, isEditMode: !!isEditMode, onChange, openModal }),
1095
+ component: ({ content, onChange, isEditMode, openModal }) => /* @__PURE__ */ jsx15(Container, { content, isEditMode: !!isEditMode, onChange, openModal }),
917
1096
  defaults: () => ({
918
1097
  type: "container",
919
1098
  content: { columns: 1, flow: "row", children: [] }
@@ -923,28 +1102,28 @@ var Container_default = defineSection({
923
1102
  });
924
1103
 
925
1104
  // src/components/sections/Spacer/index.tsx
926
- import { z as z5 } from "zod";
1105
+ import { z as z6 } from "zod";
927
1106
  import { SquareDashed } from "lucide-react";
928
1107
 
929
1108
  // src/components/sections/Spacer/Spacer.tsx
930
- import { jsx as jsx14 } from "react/jsx-runtime";
1109
+ import { jsx as jsx16 } from "react/jsx-runtime";
931
1110
  function Spacer() {
932
- return /* @__PURE__ */ jsx14("div", { "data-spacer": true, "aria-hidden": "true", className: "h-full" });
1111
+ return /* @__PURE__ */ jsx16("div", { "data-spacer": true, "aria-hidden": "true", className: "h-full" });
933
1112
  }
934
1113
 
935
1114
  // src/components/sections/Spacer/index.tsx
936
- import { jsx as jsx15 } from "react/jsx-runtime";
937
- var schema5 = z5.object({
938
- type: z5.literal("spacer"),
939
- content: z5.object({}).default({})
1115
+ import { jsx as jsx17 } from "react/jsx-runtime";
1116
+ var schema6 = z6.object({
1117
+ type: z6.literal("spacer"),
1118
+ content: z6.object({}).default({})
940
1119
  });
941
1120
  var Spacer_default = defineSection({
942
1121
  type: "spacer",
943
1122
  label: "Spacer",
944
- icon: /* @__PURE__ */ jsx15(SquareDashed, { size: 18 }),
945
- schema: schema5,
1123
+ icon: /* @__PURE__ */ jsx17(SquareDashed, { size: 18 }),
1124
+ schema: schema6,
946
1125
  authoringNote: "Vertical whitespace between sections. Use sparingly to add breathing room where default section spacing is insufficient.",
947
- component: () => /* @__PURE__ */ jsx15(Spacer, {}),
1126
+ component: () => /* @__PURE__ */ jsx17(Spacer, {}),
948
1127
  defaults: () => ({ type: "spacer", content: {} })
949
1128
  });
950
1129
 
@@ -955,6 +1134,7 @@ var coreSectionDefs = [
955
1134
  SubSubHeading_default,
956
1135
  Prose_default,
957
1136
  Media_default,
1137
+ Video_default,
958
1138
  Button_default,
959
1139
  Container_default,
960
1140
  Spacer_default