@morphika/andami 0.2.13 → 0.2.15

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.
@@ -127,12 +127,19 @@ export default function CoverSectionRenderer({ section, pageEnterAnimation }: Co
127
127
  rowAlignMap[String(i + 1)] = row.vertical_align || "start";
128
128
  });
129
129
 
130
+ const borderRadius = s.border_radius ? `${String(s.border_radius).replace(/[a-z%]+$/i, "")}px` : undefined;
131
+
130
132
  const sectionContent = (
131
133
  <section
132
134
  style={{
133
135
  position: "relative",
134
136
  height: section.height,
135
137
  overflow: "hidden",
138
+ marginTop: s.offset_top ? `${s.offset_top}px` : undefined,
139
+ marginRight: s.offset_right ? `${s.offset_right}px` : undefined,
140
+ marginBottom: s.offset_bottom ? `${s.offset_bottom}px` : undefined,
141
+ marginLeft: s.offset_left ? `${s.offset_left}px` : undefined,
142
+ borderRadius,
136
143
  }}
137
144
  >
138
145
  {responsiveCss && <style dangerouslySetInnerHTML={{ __html: responsiveCss }} />}
@@ -221,10 +228,13 @@ export default function CoverSectionRenderer({ section, pageEnterAnimation }: Co
221
228
  style={{
222
229
  gridColumn: `${col.grid_column} / span ${col.span}`,
223
230
  gridRow: col.grid_row,
224
- alignSelf,
231
+ position: "relative",
232
+ display: "flex",
233
+ flexDirection: "column",
234
+ justifyContent: alignSelf === "center" ? "center" : alignSelf === "end" ? "flex-end" : colJustify || "flex-start",
235
+ height: "100%",
225
236
  minWidth: 0,
226
237
  overflow: "hidden",
227
- ...(colJustify ? { display: "flex", flexDirection: "column" as const, justifyContent: colJustify } : {}),
228
238
  }}
229
239
  >
230
240
  {(col.blocks || []).map((block) => {
@@ -237,9 +247,15 @@ export default function CoverSectionRenderer({ section, pageEnterAnimation }: Co
237
247
 
238
248
  const layout = (block as ContentBlock & { layout?: BlockLayout }).layout;
239
249
  const alignStyles = layout && hasBlockAlignment(layout) ? getBlockAlignmentStyles(layout) : {};
250
+ const isFillBlock = (block._type === "imageBlock" || block._type === "videoBlock") &&
251
+ (block as unknown as { width?: string }).width === "fill";
240
252
 
241
253
  const rendered = (
242
- <div key={block._key} style={alignStyles}>
254
+ <div key={block._key} style={
255
+ isFillBlock
256
+ ? { position: "absolute" as const, inset: 0, zIndex: 0 }
257
+ : { position: "relative" as const, zIndex: 1, ...alignStyles }
258
+ }>
243
259
  <BlockRenderer block={block} />
244
260
  </div>
245
261
  );
@@ -25,24 +25,26 @@ const aspectMap: Record<string, string | undefined> = {
25
25
  export default function ImageBlockRenderer({ block }: { block: ImageBlock }) {
26
26
  const resolveAsset = useAssetUrl();
27
27
  const src = resolveAsset(block.asset_path);
28
- const widthStyle = widthStyleMap[block.width ?? "full"] || widthStyleMap.full;
29
- const aspect = aspectMap[block.aspect_ratio ?? "auto"];
28
+ const isFill = block.width === "fill";
29
+ const widthStyle = isFill ? {} : (widthStyleMap[block.width ?? "full"] || widthStyleMap.full);
30
+ const aspect = isFill ? undefined : aspectMap[block.aspect_ratio ?? "auto"];
30
31
 
31
32
  // BLK-014: Strip any existing unit suffix, then validate as a number before appending px
32
33
  const rawRadius = block.border_radius ? String(block.border_radius).replace(/[a-z%]+$/i, "") : "";
33
34
  const borderRadius = rawRadius && !isNaN(Number(rawRadius)) ? `${rawRadius}px` : undefined;
34
35
 
35
- const imgStyle: React.CSSProperties = {
36
- width: "100%",
37
- display: "block",
38
- objectFit: aspect ? "cover" : undefined,
39
- aspectRatio: aspect,
40
- };
36
+ const imgStyle: React.CSSProperties = isFill
37
+ ? { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" }
38
+ : { width: "100%", display: "block", objectFit: aspect ? "cover" : undefined, aspectRatio: aspect };
41
39
 
42
40
  const imgClassName = block.shadow ? "shadow-lg" : "";
43
41
 
42
+ const figureStyle: React.CSSProperties = isFill
43
+ ? { position: "absolute", inset: 0, borderRadius, overflow: "hidden" }
44
+ : { ...widthStyle, borderRadius, overflow: "hidden" };
45
+
44
46
  return (
45
- <figure style={{ ...widthStyle, borderRadius, overflow: "hidden" }}>
47
+ <figure style={figureStyle}>
46
48
  {/* eslint-disable-next-line @next/next/no-img-element */}
47
49
  <img
48
50
  src={src}
@@ -53,7 +55,7 @@ export default function ImageBlockRenderer({ block }: { block: ImageBlock }) {
53
55
  style={imgStyle}
54
56
  className={imgClassName}
55
57
  />
56
- {block.caption && (
58
+ {!isFill && block.caption && (
57
59
  <figcaption className="mt-2 font-sans text-xs uppercase tracking-wider text-brand-muted">
58
60
  {block.caption}
59
61
  </figcaption>
@@ -268,6 +268,7 @@ export default function SectionV2Renderer({ section, pageEnterAnimation }: Secti
268
268
  style={{
269
269
  gridColumn: `${col.grid_column} / span ${col.span}`,
270
270
  gridRow: col.grid_row,
271
+ position: "relative",
271
272
  display: "flex",
272
273
  flexDirection: "column",
273
274
  ...(colJustify ? { justifyContent: colJustify } : {}),
@@ -280,8 +281,14 @@ export default function SectionV2Renderer({ section, pageEnterAnimation }: Secti
280
281
  const blockLayout = (block as unknown as Record<string, unknown>).layout as BlockLayout | undefined;
281
282
  const alignStyles = hasBlockAlignment(blockLayout) ? getBlockAlignmentStyles(blockLayout) : undefined;
282
283
  const hasHAlign = blockLayout?.align_h && blockLayout.align_h !== "left";
284
+ const isFillBlock = (block._type === "imageBlock" || block._type === "videoBlock") &&
285
+ (block as unknown as { width?: string }).width === "fill";
283
286
  return (
284
- <div key={block._key} className={`blk-wrap-${block._key}`} style={{ ...(!hasHAlign ? { width: "100%" } : { width: "auto", maxWidth: "100%" }), minWidth: 0, ...alignStyles }}>
287
+ <div key={block._key} className={`blk-wrap-${block._key}`} style={
288
+ isFillBlock
289
+ ? { position: "absolute" as const, inset: 0, zIndex: 0 }
290
+ : { ...(!hasHAlign ? { width: "100%" } : { width: "auto", maxWidth: "100%" }), minWidth: 0, position: "relative" as const, zIndex: 1, ...alignStyles }
291
+ }>
285
292
  <BlockRenderer
286
293
  block={block}
287
294
  columnEnterAnimation={col.enter_animation}
@@ -286,18 +286,23 @@ function NativeVideo({ block, paddingBottom, resolveAsset }: {
286
286
 
287
287
  export default function VideoBlockRenderer({ block }: { block: VideoBlock }) {
288
288
  const resolveAsset = useAssetUrl();
289
- const widthStyle = widthStyleMap[block.width ?? "full"] || widthStyleMap.full;
290
- const paddingBottom = aspectMap[block.aspect_ratio ?? "16:9"] || "56.25%";
289
+ const isFill = block.width === "fill";
290
+ const widthStyle = isFill ? {} : (widthStyleMap[block.width ?? "full"] || widthStyleMap.full);
291
+ const paddingBottom = isFill ? "100%" : (aspectMap[block.aspect_ratio ?? "16:9"] || "56.25%");
291
292
  const borderRadius = block.border_radius ? `${String(block.border_radius).replace(/px$/i, "")}px` : undefined;
292
293
 
294
+ const containerStyle: React.CSSProperties = isFill
295
+ ? { position: "absolute", inset: 0, borderRadius, overflow: "hidden" }
296
+ : { ...widthStyle, borderRadius, overflow: borderRadius ? "hidden" : undefined };
297
+
293
298
  return (
294
- <div style={{ ...widthStyle, borderRadius, overflow: borderRadius ? "hidden" : undefined }}>
299
+ <div style={containerStyle}>
295
300
  {block.video_type === "vimeo" ? (
296
- <VimeoEmbed block={block} paddingBottom={paddingBottom} />
301
+ <VimeoEmbed block={block} paddingBottom={isFill ? "100%" : paddingBottom} />
297
302
  ) : block.video_type === "youtube" ? (
298
- <YouTubeEmbed block={block} paddingBottom={paddingBottom} />
303
+ <YouTubeEmbed block={block} paddingBottom={isFill ? "100%" : paddingBottom} />
299
304
  ) : (
300
- <NativeVideo block={block} paddingBottom={paddingBottom} resolveAsset={resolveAsset} />
305
+ <NativeVideo block={block} paddingBottom={isFill ? "100%" : paddingBottom} resolveAsset={resolveAsset} />
301
306
  )}
302
307
  </div>
303
308
  );
@@ -233,6 +233,7 @@ export default function SectionV2Column({
233
233
  style={{
234
234
  gridColumn: `${column.grid_column} / span ${column.span}`,
235
235
  gridRow: column.grid_row,
236
+ position: "relative",
236
237
  display: "flex",
237
238
  flexDirection: "column",
238
239
  ...(colJustify ? { justifyContent: colJustify } : {}),
@@ -39,6 +39,7 @@ import {
39
39
  ParallaxGroupSettings,
40
40
  CoverSectionSettings,
41
41
  } from "./settings-panel";
42
+ import CoverSectionLayoutTab from "./settings-panel/CoverSectionLayoutTab";
42
43
 
43
44
  type SettingsTab = "settings" | "layout" | "seo" | "animation";
44
45
 
@@ -337,6 +338,8 @@ export default function SettingsPanel() {
337
338
  isCoverSectionOnly && selectedCoverSection ? (
338
339
  activeTab === "animation" ? (
339
340
  <SectionV2AnimationTab section={effectiveSectionV2!} />
341
+ ) : activeTab === "layout" ? (
342
+ <CoverSectionLayoutTab section={selectedCoverSection} />
340
343
  ) : (
341
344
  <CoverSectionSettings section={selectedCoverSection} />
342
345
  )
@@ -7,7 +7,7 @@ import { makeBlockId } from "./DndWrapper";
7
7
  import { ALL_BLOCK_INFO, isSectionBlockType } from "../../lib/builder/types";
8
8
  import type { DeviceViewport } from "../../lib/builder/types";
9
9
  import { useBuilderStore } from "../../lib/builder/store";
10
- import type { ContentBlock } from "../../lib/sanity/types";
10
+ import type { ContentBlock, ImageBlock, VideoBlock } from "../../lib/sanity/types";
11
11
  import BlockLivePreview from "./BlockLivePreview";
12
12
  import { getBlockAlignmentStyles, hasBlockAlignment } from "../../lib/builder/layout-styles";
13
13
  import type { BlockLayout } from "../../lib/sanity/types";
@@ -65,14 +65,29 @@ export default function SortableBlock({
65
65
  // Only force width:100% when no horizontal alignment — align-self needs width:auto to shrink
66
66
  const hasHAlign = blockLayout?.align_h && blockLayout.align_h !== "left";
67
67
 
68
- const style: React.CSSProperties = {
69
- transform: CSS.Transform.toString(transform),
70
- transition,
71
- opacity: isDragging ? 0.3 : 1,
72
- ...(!hasHAlign ? { width: "100%" } : {}),
73
- minWidth: 0,
74
- ...alignStyles,
75
- };
68
+ // Fill-mode blocks act as column backgrounds — absolute positioned behind other blocks
69
+ const isFillBlock =
70
+ (block._type === "imageBlock" && (block as ImageBlock).width === "fill") ||
71
+ (block._type === "videoBlock" && (block as VideoBlock).width === "fill");
72
+
73
+ const style: React.CSSProperties = isFillBlock
74
+ ? {
75
+ position: "relative",
76
+ flex: "999 1 0%",
77
+ minHeight: 0,
78
+ zIndex: 0,
79
+ opacity: isDragging ? 0.3 : 1,
80
+ transform: CSS.Transform.toString(transform),
81
+ transition,
82
+ }
83
+ : {
84
+ transform: CSS.Transform.toString(transform),
85
+ transition,
86
+ opacity: isDragging ? 0.3 : 1,
87
+ ...(!hasHAlign ? { width: "100%" } : {}),
88
+ minWidth: 0,
89
+ ...alignStyles,
90
+ };
76
91
 
77
92
  const showToolbar = isSelected || isHovered;
78
93
 
@@ -106,8 +121,8 @@ export default function SortableBlock({
106
121
  return (
107
122
  <div
108
123
  ref={setNodeRef}
109
- style={style}
110
- className={`relative transition-[opacity,box-shadow] ${
124
+ style={{ ...style, ...(!isFillBlock ? { position: "relative" as const, zIndex: 1 } : {}) }}
125
+ className={`transition-[opacity,box-shadow] ${
111
126
  isDragging
112
127
  ? "ring-2 ring-[#0d9668] ring-offset-1 ring-offset-transparent rounded"
113
128
  : ""
@@ -113,6 +113,7 @@ export default function ImageBlockEditor({ block }: Props) {
113
113
  { value: "full", label: "100%" },
114
114
  { value: "contained", label: "75%" },
115
115
  { value: "small", label: "50%" },
116
+ { value: "fill", label: "Fill" },
116
117
  ] as const
117
118
  ).map((opt) => (
118
119
  <button
@@ -192,6 +192,7 @@ export default function VideoBlockEditor({ block }: Props) {
192
192
  [
193
193
  { value: "full", label: "Full" },
194
194
  { value: "contained", label: "Contained" },
195
+ { value: "fill", label: "Fill" },
195
196
  ] as const
196
197
  ).map((opt) => (
197
198
  <button
@@ -21,8 +21,10 @@ export default function LiveImagePreview({ block }: { block: ImageBlock }) {
21
21
  const thumbSrc = adminThumbUrl(block.asset_path);
22
22
  const fullSrc = adminAssetUrl(block.asset_path);
23
23
  const src = useFallback ? fullSrc : thumbSrc;
24
- const widthStyle =
25
- block.width === "contained"
24
+ const isFill = block.width === "fill";
25
+ const widthStyle = isFill
26
+ ? "100%"
27
+ : block.width === "contained"
26
28
  ? "75%"
27
29
  : block.width === "small"
28
30
  ? "50%"
@@ -35,6 +37,23 @@ export default function LiveImagePreview({ block }: { block: ImageBlock }) {
35
37
  "21:9": "21/9",
36
38
  };
37
39
 
40
+ if (isFill) {
41
+ return (
42
+ <div style={{ width: "100%", height: "100%", overflow: "hidden", borderRadius: block.border_radius ? `${String(block.border_radius).replace(/px$/i, "")}px` : undefined }}>
43
+ {/* eslint-disable-next-line @next/next/no-img-element */}
44
+ <img
45
+ src={src}
46
+ alt={block.alt || ""}
47
+ onLoad={() => setImgLoaded(true)}
48
+ onError={() => {
49
+ if (!useFallback) { setUseFallback(true); } else { setImgError(true); }
50
+ }}
51
+ style={{ width: "100%", height: "100%", objectFit: "cover" }}
52
+ />
53
+ </div>
54
+ );
55
+ }
56
+
38
57
  return (
39
58
  <div style={{ width: widthStyle, margin: block.width !== "full" ? "0 auto" : undefined }}>
40
59
  {imgError ? (
@@ -30,8 +30,9 @@ export default function LiveVideoPreview({ block }: { block: VideoBlock }) {
30
30
  "4:3": "75%",
31
31
  auto: "56.25%",
32
32
  };
33
+ const isFill = block.width === "fill";
33
34
  const paddingBottom = aspectMap[block.aspect_ratio || "16:9"] || "56.25%";
34
- const widthStyle = block.width === "contained" ? "75%" : "100%";
35
+ const widthStyle = isFill ? "100%" : (block.width === "contained" ? "75%" : "100%");
35
36
 
36
37
  // Resolve thumbnail URL based on video type (no iframes, no streaming)
37
38
  let thumbnailUrl: string | null = null;
@@ -59,9 +60,13 @@ export default function LiveVideoPreview({ block }: { block: VideoBlock }) {
59
60
 
60
61
  const borderRadius = block.border_radius ? `${String(block.border_radius).replace(/px$/i, "")}px` : undefined;
61
62
 
63
+ const outerStyle: React.CSSProperties = isFill
64
+ ? { width: "100%", height: "100%", minWidth: 0, borderRadius, overflow: "hidden" }
65
+ : { width: widthStyle, margin: block.width === "contained" ? "0 auto" : undefined, minWidth: 0, borderRadius, overflow: borderRadius ? "hidden" : undefined };
66
+
62
67
  return (
63
- <div style={{ width: widthStyle, margin: block.width === "contained" ? "0 auto" : undefined, minWidth: 0, borderRadius, overflow: borderRadius ? "hidden" : undefined }}>
64
- <div style={{ position: "relative", paddingBottom, overflow: "hidden", background: "#000", lineHeight: 0, fontSize: 0, borderRadius: "inherit" }}>
68
+ <div style={outerStyle}>
69
+ <div style={{ position: "relative", paddingBottom: isFill ? undefined : paddingBottom, height: isFill ? "100%" : undefined, overflow: "hidden", background: "#000", lineHeight: 0, fontSize: 0, borderRadius: "inherit" }}>
65
70
  {thumbnailUrl ? (
66
71
  <>
67
72
  {/* eslint-disable-next-line @next/next/no-img-element */}
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import { useBuilderStore } from "../../../lib/builder/store";
4
+ import type { CoverSection } from "../../../lib/sanity/types";
5
+ import {
6
+ SpacingIcon,
7
+ OffsetIcon,
8
+ BorderIcon,
9
+ } from "../editors/section-icons";
10
+ import { SettingsField, SettingsSection } from "../editors/shared";
11
+ import { TRBLInputs } from "./TRBLInputs";
12
+
13
+ interface CoverSectionLayoutTabProps {
14
+ section: CoverSection;
15
+ }
16
+
17
+ export default function CoverSectionLayoutTab({ section }: CoverSectionLayoutTabProps) {
18
+ const store = useBuilderStore();
19
+ const s = section.settings;
20
+
21
+ const update = (fields: Record<string, string | undefined>) => {
22
+ store.updateCoverSettings(section._key, fields as Partial<typeof s>);
23
+ };
24
+
25
+ return (
26
+ <>
27
+ {/* Spacing (padding) */}
28
+ <SettingsSection title="Spacing" defaultOpen icon={<SpacingIcon />}>
29
+ <TRBLInputs
30
+ top={s.spacing_top || ""}
31
+ right={s.spacing_right || ""}
32
+ bottom={s.spacing_bottom || ""}
33
+ left={s.spacing_left || ""}
34
+ onChange={(field, value) => update({ [`spacing_${field}`]: value || undefined })}
35
+ />
36
+ </SettingsSection>
37
+
38
+ {/* Offset (margin) */}
39
+ <SettingsSection title="Offset" defaultOpen={false} icon={<OffsetIcon />}>
40
+ <TRBLInputs
41
+ top={s.offset_top || ""}
42
+ right={s.offset_right || ""}
43
+ bottom={s.offset_bottom || ""}
44
+ left={s.offset_left || ""}
45
+ onChange={(field, value) => update({ [`offset_${field}`]: value || undefined })}
46
+ />
47
+ </SettingsSection>
48
+
49
+ {/* Border Radius */}
50
+ <SettingsSection title="Border" defaultOpen={false} icon={<BorderIcon />}>
51
+ <SettingsField label="Radius">
52
+ <div className="flex items-center gap-2">
53
+ <input
54
+ type="range"
55
+ min={0}
56
+ max={50}
57
+ step={1}
58
+ value={parseInt(s.border_radius || "0", 10) || 0}
59
+ onMouseDown={() => store._pushSnapshot()}
60
+ onChange={(e) => update({ border_radius: e.target.value === "0" ? undefined : e.target.value })}
61
+ className="flex-1 accent-[#076bff]"
62
+ />
63
+ <span className="text-xs text-neutral-900 w-10 text-right tabular-nums">
64
+ {parseInt(s.border_radius || "0", 10) || 0}px
65
+ </span>
66
+ </div>
67
+ </SettingsField>
68
+ </SettingsSection>
69
+ </>
70
+ );
71
+ }
@@ -5,14 +5,14 @@ import { groq } from "next-sanity";
5
5
  // ============================================
6
6
 
7
7
  // Deep expansion of content_rows.
8
- // Handles PageSectionV2, CustomSectionInstance, and ParallaxGroup.
8
+ // Handles PageSectionV2, CustomSectionInstance, ParallaxGroup, and CoverSection.
9
9
  // GROQ projections are additive — fields that don't exist on an object are omitted.
10
10
  const blockExpansion = `
11
11
  content_rows[] {
12
12
  _key,
13
13
  _type,
14
14
  section_type,
15
- // ── V2 section columns ──
15
+ // ── V2 section columns (shared by PageSectionV2 and CoverSection) ──
16
16
  columns[] {
17
17
  _key,
18
18
  _type,
@@ -73,9 +73,23 @@ const blockExpansion = `
73
73
  }
74
74
  }
75
75
  },
76
- // ── Settings (V2 section settings) ──
76
+ // ── CoverSection fields (only present when _type == "coverSection") ──
77
+ background_type,
78
+ background_image,
79
+ background_video,
80
+ background_position,
81
+ background_size,
82
+ background_overlay_color,
83
+ background_overlay_opacity,
84
+ height,
85
+ cover_rows[] {
86
+ _key,
87
+ height_percent,
88
+ vertical_align
89
+ },
90
+ // ── Settings (V2 section settings + CoverSection settings) ──
77
91
  settings,
78
- // ── Responsive overrides (V2 sections store responsive at section level) ──
92
+ // ── Responsive overrides (V2 + CoverSection store responsive at section level) ──
79
93
  responsive
80
94
  }
81
95
  `;
@@ -135,7 +135,7 @@ export interface ImageBlock {
135
135
  asset_path: string;
136
136
  alt?: string;
137
137
  caption?: string;
138
- width?: "full" | "contained" | "small";
138
+ width?: "full" | "contained" | "small" | "fill";
139
139
  aspect_ratio?: "auto" | "16:9" | "4:3" | "1:1" | "21:9";
140
140
  lazy?: boolean;
141
141
  border_radius?: string;
@@ -174,7 +174,7 @@ export interface VideoBlock {
174
174
  loop?: boolean;
175
175
  muted?: boolean;
176
176
  controls?: boolean;
177
- width?: "full" | "contained";
177
+ width?: "full" | "contained" | "fill";
178
178
  aspect_ratio?: "16:9" | "21:9" | "4:3" | "auto";
179
179
  border_radius?: string;
180
180
  enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
@@ -452,15 +452,24 @@ export interface CoverRow {
452
452
  vertical_align: "start" | "center" | "end";
453
453
  }
454
454
 
455
- /** Cover Section settings (subset of SectionV2Settings — no background/border) */
455
+ /** Cover Section settings (subset of SectionV2Settings) */
456
456
  export interface CoverSectionSettings {
457
457
  grid_columns: number; // default 12
458
458
  col_gap: number;
459
459
  row_gap: number;
460
+ // Spacing (padding TRBL, px)
460
461
  spacing_top?: string;
461
462
  spacing_right?: string;
462
463
  spacing_bottom?: string;
463
464
  spacing_left?: string;
465
+ // Offset (margin TRBL, px)
466
+ offset_top?: string;
467
+ offset_right?: string;
468
+ offset_bottom?: string;
469
+ offset_left?: string;
470
+ // Border
471
+ border_radius?: string;
472
+ // Animation
464
473
  enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
465
474
  stagger?: {
466
475
  enabled?: boolean;
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.2.13";
9
+ export const ANDAMI_VERSION = "0.2.15";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphika/andami",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
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",
@@ -24,6 +24,7 @@ export const imageBlock = defineType({
24
24
  { title: "Full", value: "full" },
25
25
  { title: "Contained", value: "contained" },
26
26
  { title: "Small", value: "small" },
27
+ { title: "Fill", value: "fill" },
27
28
  ],
28
29
  },
29
30
  initialValue: "full",
@@ -45,6 +45,7 @@ export const videoBlock = defineType({
45
45
  list: [
46
46
  { title: "Full", value: "full" },
47
47
  { title: "Contained", value: "contained" },
48
+ { title: "Fill", value: "fill" },
48
49
  ],
49
50
  },
50
51
  initialValue: "full",
@@ -95,6 +95,10 @@ const responsiveSettingsFields = [
95
95
  defineField({ name: "spacing_right", type: "string", title: "Spacing Right" }),
96
96
  defineField({ name: "spacing_bottom", type: "string", title: "Spacing Bottom" }),
97
97
  defineField({ name: "spacing_left", type: "string", title: "Spacing Left" }),
98
+ defineField({ name: "offset_top", type: "string", title: "Offset Top" }),
99
+ defineField({ name: "offset_right", type: "string", title: "Offset Right" }),
100
+ defineField({ name: "offset_bottom", type: "string", title: "Offset Bottom" }),
101
+ defineField({ name: "offset_left", type: "string", title: "Offset Left" }),
98
102
  ];
99
103
 
100
104
  export default defineType({
@@ -250,6 +254,13 @@ export default defineType({
250
254
  defineField({ name: "spacing_right", title: "Spacing Right", type: "string" }),
251
255
  defineField({ name: "spacing_bottom", title: "Spacing Bottom", type: "string" }),
252
256
  defineField({ name: "spacing_left", title: "Spacing Left", type: "string" }),
257
+ // Offset (margin TRBL)
258
+ defineField({ name: "offset_top", title: "Offset Top", type: "string" }),
259
+ defineField({ name: "offset_right", title: "Offset Right", type: "string" }),
260
+ defineField({ name: "offset_bottom", title: "Offset Bottom", type: "string" }),
261
+ defineField({ name: "offset_left", title: "Offset Left", type: "string" }),
262
+ // Border
263
+ defineField({ name: "border_radius", title: "Border Radius", type: "string" }),
253
264
  // Animation
254
265
  defineField({
255
266
  name: "enter_animation",