@morphika/andami 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.)
@@ -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 }>,
@@ -4,6 +4,12 @@ import { generateKey } from "./utils";
4
4
  import type { EnterAnimationConfig, TypewriterConfig } from "../../lib/animation/enter-types";
5
5
  import type { HoverEffectConfig } from "../../lib/animation/hover-effect-types";
6
6
 
7
+ // Side-effect import: populates the block registry with all built-in block
8
+ // types so `createDefaultBlock` can delegate to each registration's
9
+ // `defaultFactory`. See `./block-registry.ts` for the stability contract.
10
+ import "./block-registrations";
11
+ import { getBlockRegistration } from "./block-registry";
12
+
7
13
  // ============================================
8
14
  // Parallax slide defaults (Session 128)
9
15
  // ============================================
@@ -116,109 +122,23 @@ export const DEFAULT_TYPEWRITER_CONFIG: Required<TypewriterConfig> = {
116
122
 
117
123
  /**
118
124
  * Create a new block with sensible defaults for the given block type.
125
+ *
126
+ * Session 181 (C): switched from a giant switch-case to a registry lookup.
127
+ * Each block type now declares its default shape in
128
+ * `./block-registrations.ts` via the `defaultFactory` field. The
129
+ * registration module is loaded eagerly here (side-effect import) so the
130
+ * registry is populated before any `createDefaultBlock` call.
131
+ *
132
+ * Unknown block types fall back to the minimal `{ _type, _key }` shape —
133
+ * same behavior as the previous switch-case's `default` branch.
119
134
  */
120
135
  export function createDefaultBlock(blockType: BlockType): ContentBlock {
121
- const _key = generateKey();
122
-
123
- switch (blockType) {
124
- case "textBlock":
125
- return {
126
- _type: "textBlock",
127
- _key,
128
- text: [],
129
- style: { fontSize: 14, alignment: "left", fontWeight: "400" },
130
- };
131
- case "imageBlock":
132
- return {
133
- _type: "imageBlock",
134
- _key,
135
- asset_path: "",
136
- alt: "",
137
- width: "full",
138
- aspect_ratio: "auto",
139
- lazy: true,
140
- shadow: false,
141
- border_radius: "",
142
- };
143
- case "imageGridBlock":
144
- return {
145
- _type: "imageGridBlock",
146
- _key,
147
- images: [],
148
- h_gutter: 10,
149
- v_gutter: 10,
150
- images_per_row: 2,
151
- random_grid: "disabled",
152
- random_seed: 1,
153
- lightbox: false,
154
- object_fit: "cover",
155
- };
156
- case "videoBlock":
157
- return {
158
- _type: "videoBlock",
159
- _key,
160
- video_type: "vimeo",
161
- url_or_path: "",
162
- autoplay: false,
163
- loop: false,
164
- muted: true,
165
- controls: true,
166
- aspect_ratio: "16:9",
167
- };
168
- case "spacerBlock":
169
- return {
170
- _type: "spacerBlock",
171
- _key,
172
- height: "medium",
173
- };
174
- case "buttonBlock":
175
- return {
176
- _type: "buttonBlock",
177
- _key,
178
- text: "Button",
179
- url: "#",
180
- style: "primary",
181
- size: "medium",
182
- };
183
- case "projectGridBlock":
184
- return {
185
- _type: "projectGridBlock",
186
- _key,
187
- columns: 3,
188
- aspect_ratios: ["16/9"],
189
- gap_v: 16,
190
- gap_h: 16,
191
- hover_effect: "scale",
192
- show_subtitle: true,
193
- border_radius: 0,
194
- video_mode: "off",
195
- projects: [],
196
- };
197
- case "projectCarouselBlock":
198
- return {
199
- _type: "projectCarouselBlock",
200
- _key,
201
- source_mode: "auto_latest",
202
- max_projects: 8,
203
- exclude_current: true,
204
- cards_per_view_desktop: 3.5,
205
- cards_per_view_tablet: 2.2,
206
- cards_per_view_phone: 1.2,
207
- gap: 16,
208
- aspect_ratio: "4/3",
209
- show_title: true,
210
- show_subtitle: false,
211
- border_radius: 0,
212
- hover_effect: "scale",
213
- video_mode: "off",
214
- show_arrows: true,
215
- show_dots: false,
216
- snap_scroll: true,
217
- };
218
- default:
219
- return {
220
- _type: blockType,
221
- _key,
222
- } as ContentBlock;
136
+ const registration = getBlockRegistration(blockType);
137
+ if (registration) {
138
+ return registration.defaultFactory(generateKey());
223
139
  }
140
+ return {
141
+ _type: blockType,
142
+ _key: generateKey(),
143
+ } as ContentBlock;
224
144
  }
package/lib/version.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  * Exposed as a plain constant so it can be imported without reading
7
7
  * package.json at runtime.
8
8
  */
9
- export const ANDAMI_VERSION = "0.4.0";
9
+ export const ANDAMI_VERSION = "0.4.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphika/andami",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Visual Page Builder — core library. A reusable website builder with visual editing, CMS integration, and asset management.",
5
5
  "type": "module",
6
6
  "license": "MIT",