@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.
- package/components/blocks/BlockRenderer.tsx +21 -46
- package/components/builder/BlockLivePreview.tsx +18 -47
- package/components/builder/settings-panel/BlockSettings.tsx +18 -70
- package/lib/builder/block-registrations.ts +95 -28
- package/lib/builder/defaults.ts +22 -102
- package/lib/version.ts +1 -1
- package/package.json +1 -1
|
@@ -17,14 +17,8 @@ import EnterAnimationWrapper from "./EnterAnimationWrapper";
|
|
|
17
17
|
import HoverAnimationWrapper from "./HoverAnimationWrapper";
|
|
18
18
|
import TypewriterWrapper from "./TypewriterWrapper";
|
|
19
19
|
|
|
20
|
-
import
|
|
21
|
-
import
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 }>,
|
package/lib/builder/defaults.ts
CHANGED
|
@@ -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
|
|
122
|
-
|
|
123
|
-
|
|
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
package/package.json
CHANGED