@morphika/andami 0.4.0 → 0.4.2

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/README.md CHANGED
@@ -7,7 +7,8 @@ A reusable Visual Page Builder framework for Next.js. Build custom websites with
7
7
  ## Features
8
8
 
9
9
  - **Visual Page Builder** — Infinite canvas editor with device previews (desktop, tablet, phone)
10
- - **7 Content Blocks** — Text, Image, Image Grid, Video, Spacer, Button, Project Grid
10
+ - **6 Content Blocks** — Text, Image, Image Grid, Video, Spacer, Button (added via "+ Add Block" inside columns)
11
+ - **2 Section-level Blocks** — Project Grid (masonry) and Project Carousel (horizontal "keep browsing" at end of project pages). Added via "+ Add Section"
11
12
  - **Cover Sections** — Full-viewport hero sections with proportional rows, background media, and drag-to-resize
12
13
  - **V2 Grid System** — 12-column CSS grid with push cascade engine and responsive overrides
13
14
  - **Custom Sections** — Create reusable sections with per-instance setting overrides
@@ -17,14 +17,8 @@ import EnterAnimationWrapper from "./EnterAnimationWrapper";
17
17
  import HoverAnimationWrapper from "./HoverAnimationWrapper";
18
18
  import TypewriterWrapper from "./TypewriterWrapper";
19
19
 
20
- import TextBlockRenderer, { getTextBlockStyles } from "./TextBlockRenderer";
21
- import ImageBlockRenderer from "./ImageBlockRenderer";
22
- import ImageGridBlockRenderer from "./ImageGridBlockRenderer";
23
- import VideoBlockRenderer from "./VideoBlockRenderer";
24
- import SpacerBlockRenderer from "./SpacerBlockRenderer";
25
- import ButtonBlockRenderer from "./ButtonBlockRenderer";
26
- import ProjectGridBlockRenderer from "./ProjectGridBlockRenderer";
27
- import ProjectCarouselBlockRenderer from "./ProjectCarouselBlockRenderer";
20
+ import { getTextBlockStyles } from "./TextBlockRenderer";
21
+ import { getBlockRegistration } from "../../lib/builder/registry";
28
22
 
29
23
  // ── BLK-003: Error Boundary for block renderers ──
30
24
  // Prevents a single broken block from crashing the entire page.
@@ -289,44 +283,25 @@ export default function BlockRenderer({
289
283
 
290
284
  let content: React.ReactNode;
291
285
 
292
- switch (resolved._type) {
293
- case "textBlock":
294
- content = <TextBlockRenderer block={resolved} />;
295
- break;
296
- case "imageBlock":
297
- content = <ImageBlockRenderer block={resolved} />;
298
- break;
299
- case "imageGridBlock":
300
- content = <ImageGridBlockRenderer block={resolved} />;
301
- break;
302
- case "videoBlock":
303
- content = <VideoBlockRenderer block={resolved} />;
304
- break;
305
- case "spacerBlock":
306
- content = <SpacerBlockRenderer block={resolved} />;
307
- break;
308
- case "buttonBlock":
309
- content = <ButtonBlockRenderer block={resolved} />;
310
- break;
311
- case "projectGridBlock":
312
- content = <ProjectGridBlockRenderer block={resolved as import("../../lib/sanity/types").ProjectGridBlock} />;
313
- break;
314
- case "projectCarouselBlock":
315
- content = <ProjectCarouselBlockRenderer block={resolved as import("../../lib/sanity/types").ProjectCarouselBlock} />;
316
- break;
317
- default: {
318
- const unknownBlock = resolved as ContentBlock;
319
- if (process.env.NODE_ENV === "development") {
320
- content = (
321
- <div className="border border-dashed border-brand-secondary/50 p-4 font-mono text-xs text-brand-secondary">
322
- Unknown block type: {unknownBlock._type}
323
- </div>
324
- );
325
- } else {
326
- // BLK-004: Log unknown block types in production for debugging
327
- console.warn(`[BlockRenderer] Unknown block type "${unknownBlock._type}" (key: ${unknownBlock._key}) — skipped`);
328
- return null;
329
- }
286
+ // Registry-driven dispatch. Same result as the former switch-case — the
287
+ // Session B consistency tests guarantee registry.renderer === the imported
288
+ // component that was previously inlined in each case branch.
289
+ const registration = getBlockRegistration(resolved._type);
290
+ if (registration) {
291
+ const Renderer = registration.renderer as React.ComponentType<{ block: ContentBlock }>;
292
+ content = <Renderer block={resolved} />;
293
+ } else {
294
+ const unknownBlock = resolved as ContentBlock;
295
+ if (process.env.NODE_ENV === "development") {
296
+ content = (
297
+ <div className="border border-dashed border-brand-secondary/50 p-4 font-mono text-xs text-brand-secondary">
298
+ Unknown block type: {unknownBlock._type}
299
+ </div>
300
+ );
301
+ } else {
302
+ // BLK-004: Log unknown block types in production for debugging
303
+ console.warn(`[BlockRenderer] Unknown block type "${unknownBlock._type}" (key: ${unknownBlock._key}) — skipped`);
304
+ return null;
330
305
  }
331
306
  }
332
307
 
@@ -14,26 +14,8 @@ import { memo } from "react";
14
14
  import { resolveBlock } from "../../lib/builder/responsive";
15
15
  import { getBlockLayoutStyles, hasBlockLayout } from "../../lib/builder/layout-styles";
16
16
  import type { DeviceViewport } from "../../lib/builder/types";
17
- import type {
18
- ContentBlock,
19
- TextBlock,
20
- ImageBlock,
21
- ImageGridBlock,
22
- VideoBlock,
23
- SpacerBlock,
24
- ButtonBlock,
25
- ProjectGridBlock,
26
- ProjectCarouselBlock,
27
- } from "../../lib/sanity/types";
28
-
29
- import { LiveTextEditor } from "./live-preview";
30
- import { LiveImagePreview } from "./live-preview";
31
- import { LiveImageGridPreview } from "./live-preview";
32
- import { LiveVideoPreview } from "./live-preview";
33
- import { LiveSpacerPreview } from "./live-preview";
34
- import { LiveButtonPreview } from "./live-preview";
35
- import { LiveProjectGridPreview } from "./live-preview";
36
- import { LiveProjectCarouselPreview } from "./live-preview";
17
+ import type { ContentBlock } from "../../lib/sanity/types";
18
+ import { getBlockRegistration } from "../../lib/builder/registry";
37
19
  import { LivePlaceholder } from "./live-preview";
38
20
 
39
21
  // ============================================
@@ -54,33 +36,22 @@ function BlockLivePreviewInner({ block, viewport = "desktop", editable = false }
54
36
 
55
37
  let content: React.ReactNode;
56
38
 
57
- switch (resolved._type) {
58
- case "textBlock":
59
- content = <LiveTextEditor block={resolved as TextBlock} editable={editable} />;
60
- break;
61
- case "imageBlock":
62
- content = <LiveImagePreview block={resolved as ImageBlock} />;
63
- break;
64
- case "imageGridBlock":
65
- content = <LiveImageGridPreview block={resolved as ImageGridBlock} />;
66
- break;
67
- case "videoBlock":
68
- content = <LiveVideoPreview block={resolved as VideoBlock} />;
69
- break;
70
- case "spacerBlock":
71
- content = <LiveSpacerPreview block={resolved as SpacerBlock} />;
72
- break;
73
- case "buttonBlock":
74
- content = <LiveButtonPreview block={resolved as ButtonBlock} />;
75
- break;
76
- case "projectGridBlock":
77
- content = <LiveProjectGridPreview block={resolved as ProjectGridBlock} viewport={viewport} />;
78
- break;
79
- case "projectCarouselBlock":
80
- content = <LiveProjectCarouselPreview block={resolved as ProjectCarouselBlock} viewport={viewport} />;
81
- break;
82
- default:
83
- content = <LivePlaceholder type={(resolved as ContentBlock)._type} />;
39
+ // Registry-driven dispatch. The registration's `livePreview` component
40
+ // is guaranteed (by Session B consistency tests) to be the same as the
41
+ // component previously imported and inlined in each switch case. Each
42
+ // live preview accepts an overlapping props subset — we pass the full
43
+ // set (block, viewport, editable) and individual previews ignore what
44
+ // they don't need.
45
+ const registration = getBlockRegistration(resolved._type);
46
+ if (registration) {
47
+ const LivePreview = registration.livePreview as React.ComponentType<{
48
+ block: ContentBlock;
49
+ viewport?: DeviceViewport;
50
+ editable?: boolean;
51
+ }>;
52
+ content = <LivePreview block={resolved} viewport={viewport} editable={editable} />;
53
+ } else {
54
+ content = <LivePlaceholder type={(resolved as ContentBlock)._type} />;
84
55
  }
85
56
 
86
57
  // Wrap in layout div if block has layout properties set (spacing, background, border, etc.)
@@ -3,8 +3,8 @@
3
3
  import { memo } from "react";
4
4
  import { createPortal } from "react-dom";
5
5
  import { useBuilderStore } from "../../lib/builder/store";
6
- import type { PageSectionV2 } from "../../lib/sanity/types";
7
- import { isPageSectionV2 } from "../../lib/sanity/types";
6
+ import type { PageSectionV2, CoverSection, SectionColumn } from "../../lib/sanity/types";
7
+ import { isPageSectionV2, isCoverSection } from "../../lib/sanity/types";
8
8
  import { BUILDER_BLUE } from "../../lib/builder/constants";
9
9
 
10
10
  interface ColumnDragOverlayProps {
@@ -20,14 +20,29 @@ const ColumnDragOverlay = memo(function ColumnDragOverlay({
20
20
  }: ColumnDragOverlayProps) {
21
21
  const rows = useBuilderStore((s) => s.rows);
22
22
  const item = rows.find((r) => r._key === sectionKey);
23
- if (!item || !isPageSectionV2(item)) return null;
23
+ if (!item) return null;
24
24
 
25
- const v2Section = item as PageSectionV2;
26
- const col = v2Section.columns?.find((c) => c._key === columnKey);
25
+ // Accept both PageSectionV2 and CoverSection — both expose `columns:
26
+ // SectionColumn[]` and `settings.grid_columns`. Other section types
27
+ // (parallax, custom instance) are not column-draggable targets.
28
+ let columns: SectionColumn[] | undefined;
29
+ let gridColumns = 12;
30
+ if (isPageSectionV2(item)) {
31
+ const v2 = item as PageSectionV2;
32
+ columns = v2.columns;
33
+ gridColumns = v2.settings?.grid_columns || 12;
34
+ } else if (isCoverSection(item)) {
35
+ const cover = item as CoverSection;
36
+ columns = cover.columns;
37
+ gridColumns = cover.settings?.grid_columns || 12;
38
+ } else {
39
+ return null;
40
+ }
41
+
42
+ const col = columns?.find((c) => c._key === columnKey);
27
43
  if (!col) return null;
28
44
 
29
45
  const blockCount = (col.blocks || []).length;
30
- const gridColumns = v2Section.settings.grid_columns || 12;
31
46
 
32
47
  const overlay = (
33
48
  <div
@@ -2,12 +2,68 @@
2
2
 
3
3
  import { useState, useCallback, useRef, useEffect } from "react";
4
4
  import { useBuilderStore } from "../../../lib/builder/store";
5
- import type { PageSectionV2 } from "../../../lib/sanity/types";
6
- import { isPageSectionV2 } from "../../../lib/sanity/types";
5
+ import type { PageSectionV2, CoverSection, ContentItem } from "../../../lib/sanity/types";
6
+ import { isPageSectionV2, isCoverSection } from "../../../lib/sanity/types";
7
7
  import { getEffectiveColumnsV2, buildColumnV2Overrides } from "../settings-panel/responsive-helpers";
8
8
  import { moveColumn as cascadeMoveColumn } from "../../../lib/builder/cascade";
9
9
  import type { DeviceViewport } from "../../../lib/builder/types";
10
10
 
11
+ /**
12
+ * View a Cover section as a PageSectionV2 for DnD purposes.
13
+ *
14
+ * Cover and V2 share the same column shape, grid_columns, and
15
+ * `responsive[vp].columns` shape — the fields read/written by column
16
+ * DnD. This shim lets us reuse getEffectiveColumnsV2/buildColumnV2Overrides
17
+ * without duplicating logic. Only `columns`, `settings.grid_columns`, and
18
+ * `responsive[vp].columns` are exposed; Cover-specific fields
19
+ * (`cover_rows`, Cover-specific `settings`) are hidden from the V2 helpers.
20
+ *
21
+ * The write-back path (`updateSectionV2Responsive` → `updateSectionAtPath`)
22
+ * merges column overrides back into the cover, preserving Cover-specific
23
+ * fields. See `store-helpers.ts :: updateSectionAtPath` cover branch.
24
+ */
25
+ function coverAsV2(cover: CoverSection): PageSectionV2 {
26
+ return {
27
+ _type: "pageSectionV2",
28
+ _key: cover._key,
29
+ section_type: "empty-v2",
30
+ columns: cover.columns,
31
+ settings: {
32
+ preset: "custom",
33
+ grid_columns: cover.settings.grid_columns,
34
+ col_gap: cover.settings.col_gap,
35
+ row_gap: cover.settings.row_gap,
36
+ },
37
+ responsive:
38
+ cover.responsive?.tablet?.columns || cover.responsive?.phone?.columns
39
+ ? {
40
+ ...(cover.responsive.tablet?.columns
41
+ ? { tablet: { columns: cover.responsive.tablet.columns } }
42
+ : {}),
43
+ ...(cover.responsive.phone?.columns
44
+ ? { phone: { columns: cover.responsive.phone.columns } }
45
+ : {}),
46
+ }
47
+ : undefined,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Find the section by key and return a V2-shaped view of it (PageSectionV2
53
+ * directly, or Cover adapted via `coverAsV2`). Returns null if the section
54
+ * doesn't exist or is an unsupported type (parallax, custom instance).
55
+ */
56
+ function findColumnarSection(
57
+ rows: ContentItem[],
58
+ sectionKey: string,
59
+ ): PageSectionV2 | null {
60
+ const section = rows.find((r) => r._key === sectionKey);
61
+ if (!section) return null;
62
+ if (isPageSectionV2(section)) return section as PageSectionV2;
63
+ if (isCoverSection(section)) return coverAsV2(section as CoverSection);
64
+ return null;
65
+ }
66
+
11
67
  // ============================================
12
68
  // Types
13
69
  // ============================================
@@ -57,9 +113,8 @@ function executeResponsiveSwap(
57
113
  updateSectionV2Responsive: (sectionKey: string, responsive: PageSectionV2["responsive"]) => void
58
114
  ): void {
59
115
  const rows = useBuilderStore.getState().rows;
60
- const section = rows.find((r) => r._key === sectionKey);
61
- if (!section || !isPageSectionV2(section)) return;
62
- const v2Section = section as PageSectionV2;
116
+ const v2Section = findColumnarSection(rows, sectionKey);
117
+ if (!v2Section) return;
63
118
 
64
119
  const effectiveCols = getEffectiveColumnsV2(v2Section, viewport);
65
120
  const draggedCol = effectiveCols.find((c) => c._key === draggedKey);
@@ -109,9 +164,8 @@ function executeResponsiveGapMove(
109
164
  updateSectionV2Responsive: (sectionKey: string, responsive: PageSectionV2["responsive"]) => void
110
165
  ): void {
111
166
  const rows = useBuilderStore.getState().rows;
112
- const section = rows.find((r) => r._key === sectionKey);
113
- if (!section || !isPageSectionV2(section)) return;
114
- const v2Section = section as PageSectionV2;
167
+ const v2Section = findColumnarSection(rows, sectionKey);
168
+ if (!v2Section) return;
115
169
 
116
170
  const effectiveCols = getEffectiveColumnsV2(v2Section, viewport);
117
171
  const columnOverrides = effectiveCols.map((c) => {
@@ -147,9 +201,8 @@ function executeResponsiveInsert(
147
201
  updateSectionV2Responsive: (sectionKey: string, responsive: PageSectionV2["responsive"]) => void
148
202
  ): void {
149
203
  const rows = useBuilderStore.getState().rows;
150
- const section = rows.find((r) => r._key === sectionKey);
151
- if (!section || !isPageSectionV2(section)) return;
152
- const v2Section = section as PageSectionV2;
204
+ const v2Section = findColumnarSection(rows, sectionKey);
205
+ if (!v2Section) return;
153
206
 
154
207
  const effectiveCols = getEffectiveColumnsV2(v2Section, viewport);
155
208
  const cascadeResult = cascadeMoveColumn(effectiveCols, columnKey, targetRow, targetCol, v2Section.settings.grid_columns);
@@ -4,19 +4,13 @@
4
4
  * BlockSettings — Delegates to type-specific block editors.
5
5
  *
6
6
  * Session 64: Extracted from SettingsPanel.tsx.
7
+ * Session 181 (C): Switched from explicit switch-case dispatch to a
8
+ * registry lookup — the editor component for each block type is
9
+ * registered in `lib/builder/block-registrations.ts`.
7
10
  */
8
11
 
9
12
  import type { ContentBlock } from "../../../lib/sanity/types";
10
- import {
11
- TextBlockEditor,
12
- ImageBlockEditor,
13
- ImageGridBlockEditor,
14
- VideoBlockEditor,
15
- SpacerBlockEditor,
16
- ButtonBlockEditor,
17
- ProjectGridEditor,
18
- ProjectCarouselBlockEditor,
19
- } from "../editors";
13
+ import { getBlockRegistration } from "../../../lib/builder/registry";
20
14
 
21
15
  export default function BlockSettings({
22
16
  block,
@@ -27,68 +21,22 @@ export default function BlockSettings({
27
21
  }
28
22
 
29
23
  // ============================================
30
- // Block Type Editor Router
24
+ // Block Type Editor Router (registry-driven)
31
25
  // ============================================
32
26
 
33
27
  function BlockTypeEditor({ block }: { block: ContentBlock }) {
34
- switch (block._type) {
35
- case "textBlock":
36
- return (
37
- <TextBlockEditor
38
- block={block as import("../../../lib/sanity/types").TextBlock}
39
- />
40
- );
41
- case "imageBlock":
42
- return (
43
- <ImageBlockEditor
44
- block={block as import("../../../lib/sanity/types").ImageBlock}
45
- />
46
- );
47
- case "imageGridBlock":
48
- return (
49
- <ImageGridBlockEditor
50
- block={block as import("../../../lib/sanity/types").ImageGridBlock}
51
- />
52
- );
53
- case "videoBlock":
54
- return (
55
- <VideoBlockEditor
56
- block={block as import("../../../lib/sanity/types").VideoBlock}
57
- />
58
- );
59
- case "spacerBlock":
60
- return (
61
- <SpacerBlockEditor
62
- block={block as import("../../../lib/sanity/types").SpacerBlock}
63
- />
64
- );
65
- case "buttonBlock":
66
- return (
67
- <ButtonBlockEditor
68
- block={block as import("../../../lib/sanity/types").ButtonBlock}
69
- />
70
- );
71
- case "projectGridBlock":
72
- return (
73
- <ProjectGridEditor
74
- block={block as import("../../../lib/sanity/types").ProjectGridBlock}
75
- />
76
- );
77
- case "projectCarouselBlock":
78
- return (
79
- <ProjectCarouselBlockEditor
80
- block={block as import("../../../lib/sanity/types").ProjectCarouselBlock}
81
- />
82
- );
83
- default:
84
- return (
85
- <div className="p-4">
86
- <div className="rounded-lg bg-[#f5f5f5] p-3">
87
- <p className="text-xs text-neutral-400">
88
- No editor available for this block type.
89
- </p>
90
- </div>
91
- </div>
92
- );
28
+ const registration = getBlockRegistration(block._type);
29
+ if (registration) {
30
+ const Editor = registration.editor as React.ComponentType<{ block: ContentBlock }>;
31
+ return <Editor block={block} />;
93
32
  }
33
+ return (
34
+ <div className="p-4">
35
+ <div className="rounded-lg bg-[#f5f5f5] p-3">
36
+ <p className="text-xs text-neutral-400">
37
+ No editor available for this block type.
38
+ </p>
39
+ </div>
40
+ </div>
41
+ );
94
42
  }
@@ -18,10 +18,8 @@
18
18
  */
19
19
 
20
20
  import { registerBlockType } from "./block-registry";
21
- import { createDefaultBlock } from "./defaults";
22
21
 
23
22
  import type {
24
- ContentBlock,
25
23
  TextBlock,
26
24
  ImageBlock,
27
25
  ImageGridBlock,
@@ -107,24 +105,15 @@ import {
107
105
  } from "../../components/builder/blockStyles";
108
106
 
109
107
  // ────────────────────────────────────────────────────────────────────
110
- // Helper: delegate to `createDefaultBlock` but override the generated
111
- // `_key` so callers can supply deterministic keys (tests, fixtures).
112
- // ────────────────────────────────────────────────────────────────────
113
-
114
- function factory<T extends ContentBlock>(
115
- type: T["_type"],
116
- ): (key: string) => T {
117
- return (key: string) => {
118
- const block = createDefaultBlock(
119
- type as Parameters<typeof createDefaultBlock>[0],
120
- );
121
- return { ...block, _key: key } as T;
122
- };
123
- }
124
-
125
- // ────────────────────────────────────────────────────────────────────
126
- // Registrations (order matches the existing BLOCK_TYPE_REGISTRY +
127
- // section blocks appended at the end, so iteration order is stable).
108
+ // Registrations.
109
+ //
110
+ // Each `defaultFactory` is inlined here (rather than delegating to
111
+ // `createDefaultBlock` in `./defaults.ts`) to avoid a circular dependency:
112
+ // defaults.ts -> block-registry.getBlockRegistration
113
+ // defaults.ts -> side-effect: ./block-registrations
114
+ // block-registrations.ts -> (no longer imports ./defaults)
115
+ // Order matches the existing BLOCK_TYPE_REGISTRY + section blocks appended
116
+ // at the end, so iteration order is stable.
128
117
  // ────────────────────────────────────────────────────────────────────
129
118
 
130
119
  // ── Content blocks ──
@@ -136,7 +125,12 @@ registerBlockType<TextBlock>({
136
125
  category: "content",
137
126
  iconGlyph: "T",
138
127
  schema: textBlock,
139
- defaultFactory: factory<TextBlock>("textBlock"),
128
+ defaultFactory: (key) => ({
129
+ _type: "textBlock",
130
+ _key: key,
131
+ text: [],
132
+ style: { fontSize: 14, alignment: "left", fontWeight: "400" },
133
+ }),
140
134
  renderer: TextBlockRenderer as React.ComponentType<{ block: TextBlock }>,
141
135
  livePreview: LiveTextEditor as unknown as React.ComponentType<{ block: TextBlock; viewport?: import("./types").DeviceViewport; editable?: boolean }>,
142
136
  editor: TextBlockEditor as React.ComponentType<{ block: TextBlock }>,
@@ -153,7 +147,17 @@ registerBlockType<ImageBlock>({
153
147
  category: "content",
154
148
  iconGlyph: "🖼",
155
149
  schema: imageBlock,
156
- defaultFactory: factory<ImageBlock>("imageBlock"),
150
+ defaultFactory: (key) => ({
151
+ _type: "imageBlock",
152
+ _key: key,
153
+ asset_path: "",
154
+ alt: "",
155
+ width: "full",
156
+ aspect_ratio: "auto",
157
+ lazy: true,
158
+ shadow: false,
159
+ border_radius: "",
160
+ }),
157
161
  renderer: ImageBlockRenderer as React.ComponentType<{ block: ImageBlock }>,
158
162
  livePreview: LiveImagePreview as unknown as React.ComponentType<{ block: ImageBlock; viewport?: import("./types").DeviceViewport; editable?: boolean }>,
159
163
  editor: ImageBlockEditor as React.ComponentType<{ block: ImageBlock }>,
@@ -170,7 +174,18 @@ registerBlockType<ImageGridBlock>({
170
174
  category: "content",
171
175
  iconGlyph: "⊞",
172
176
  schema: imageGridBlock,
173
- defaultFactory: factory<ImageGridBlock>("imageGridBlock"),
177
+ defaultFactory: (key) => ({
178
+ _type: "imageGridBlock",
179
+ _key: key,
180
+ images: [],
181
+ h_gutter: 10,
182
+ v_gutter: 10,
183
+ images_per_row: 2,
184
+ random_grid: "disabled",
185
+ random_seed: 1,
186
+ lightbox: false,
187
+ object_fit: "cover",
188
+ }),
174
189
  renderer: ImageGridBlockRenderer as React.ComponentType<{ block: ImageGridBlock }>,
175
190
  livePreview: LiveImageGridPreview as unknown as React.ComponentType<{ block: ImageGridBlock; viewport?: import("./types").DeviceViewport; editable?: boolean }>,
176
191
  editor: ImageGridBlockEditor as React.ComponentType<{ block: ImageGridBlock }>,
@@ -187,7 +202,17 @@ registerBlockType<VideoBlock>({
187
202
  category: "content",
188
203
  iconGlyph: "▶",
189
204
  schema: videoBlock,
190
- defaultFactory: factory<VideoBlock>("videoBlock"),
205
+ defaultFactory: (key) => ({
206
+ _type: "videoBlock",
207
+ _key: key,
208
+ video_type: "vimeo",
209
+ url_or_path: "",
210
+ autoplay: false,
211
+ loop: false,
212
+ muted: true,
213
+ controls: true,
214
+ aspect_ratio: "16:9",
215
+ }),
191
216
  renderer: VideoBlockRenderer as React.ComponentType<{ block: VideoBlock }>,
192
217
  livePreview: LiveVideoPreview as unknown as React.ComponentType<{ block: VideoBlock; viewport?: import("./types").DeviceViewport; editable?: boolean }>,
193
218
  editor: VideoBlockEditor as React.ComponentType<{ block: VideoBlock }>,
@@ -204,7 +229,11 @@ registerBlockType<SpacerBlock>({
204
229
  category: "content",
205
230
  iconGlyph: "↕",
206
231
  schema: spacerBlock,
207
- defaultFactory: factory<SpacerBlock>("spacerBlock"),
232
+ defaultFactory: (key) => ({
233
+ _type: "spacerBlock",
234
+ _key: key,
235
+ height: "medium",
236
+ }),
208
237
  renderer: SpacerBlockRenderer as React.ComponentType<{ block: SpacerBlock }>,
209
238
  livePreview: LiveSpacerPreview as unknown as React.ComponentType<{ block: SpacerBlock; viewport?: import("./types").DeviceViewport; editable?: boolean }>,
210
239
  editor: SpacerBlockEditor as React.ComponentType<{ block: SpacerBlock }>,
@@ -221,7 +250,14 @@ registerBlockType<ButtonBlock>({
221
250
  category: "content",
222
251
  iconGlyph: "▣",
223
252
  schema: buttonBlock,
224
- defaultFactory: factory<ButtonBlock>("buttonBlock"),
253
+ defaultFactory: (key) => ({
254
+ _type: "buttonBlock",
255
+ _key: key,
256
+ text: "Button",
257
+ url: "#",
258
+ style: "primary",
259
+ size: "medium",
260
+ }),
225
261
  renderer: ButtonBlockRenderer as React.ComponentType<{ block: ButtonBlock }>,
226
262
  livePreview: LiveButtonPreview as unknown as React.ComponentType<{ block: ButtonBlock; viewport?: import("./types").DeviceViewport; editable?: boolean }>,
227
263
  editor: ButtonBlockEditor as React.ComponentType<{ block: ButtonBlock }>,
@@ -240,7 +276,19 @@ registerBlockType<ProjectGridBlock>({
240
276
  category: "section",
241
277
  iconGlyph: "⬡",
242
278
  schema: projectGridBlock,
243
- defaultFactory: factory<ProjectGridBlock>("projectGridBlock"),
279
+ defaultFactory: (key) => ({
280
+ _type: "projectGridBlock",
281
+ _key: key,
282
+ columns: 3,
283
+ aspect_ratios: ["16/9"],
284
+ gap_v: 16,
285
+ gap_h: 16,
286
+ hover_effect: "scale",
287
+ show_subtitle: true,
288
+ border_radius: 0,
289
+ video_mode: "off",
290
+ projects: [],
291
+ }),
244
292
  renderer: ProjectGridBlockRenderer as React.ComponentType<{ block: ProjectGridBlock }>,
245
293
  livePreview: LiveProjectGridPreview as unknown as React.ComponentType<{ block: ProjectGridBlock; viewport?: import("./types").DeviceViewport; editable?: boolean }>,
246
294
  editor: ProjectGridEditor as React.ComponentType<{ block: ProjectGridBlock }>,
@@ -257,7 +305,26 @@ registerBlockType<ProjectCarouselBlock>({
257
305
  category: "section",
258
306
  iconGlyph: "▸",
259
307
  schema: projectCarouselBlock,
260
- defaultFactory: factory<ProjectCarouselBlock>("projectCarouselBlock"),
308
+ defaultFactory: (key) => ({
309
+ _type: "projectCarouselBlock",
310
+ _key: key,
311
+ source_mode: "auto_latest",
312
+ max_projects: 8,
313
+ exclude_current: true,
314
+ cards_per_view_desktop: 3.5,
315
+ cards_per_view_tablet: 2.2,
316
+ cards_per_view_phone: 1.2,
317
+ gap: 16,
318
+ aspect_ratio: "4/3",
319
+ show_title: true,
320
+ show_subtitle: false,
321
+ border_radius: 0,
322
+ hover_effect: "scale",
323
+ video_mode: "off",
324
+ show_arrows: true,
325
+ show_dots: false,
326
+ snap_scroll: true,
327
+ }),
261
328
  renderer: ProjectCarouselBlockRenderer as React.ComponentType<{ block: ProjectCarouselBlock }>,
262
329
  livePreview: LiveProjectCarouselPreview as unknown as React.ComponentType<{ block: ProjectCarouselBlock; viewport?: import("./types").DeviceViewport; editable?: boolean }>,
263
330
  editor: ProjectCarouselBlockEditor as React.ComponentType<{ block: ProjectCarouselBlock }>,