@morphika/andami 0.5.2 → 0.5.4
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 +27 -2
- package/app/admin/layout.tsx +26 -14
- package/app/admin/pages/[slug]/page.tsx +39 -22
- package/app/admin/pages/page.tsx +13 -8
- package/app/admin/projects/page.tsx +17 -8
- package/app/api/admin/assets/register/route.ts +51 -14
- package/app/api/admin/assets/registry/route.ts +4 -1
- package/app/api/admin/assets/relink/confirm/route.ts +4 -1
- package/app/api/admin/assets/relink/route.ts +4 -1
- package/app/api/admin/assets/scan/route.ts +4 -1
- package/app/api/admin/backups/restore-data/route.ts +4 -1
- package/app/api/admin/r2/connect/route.ts +4 -1
- package/app/api/admin/r2/delete/route.ts +4 -1
- package/app/api/admin/r2/rename/route.ts +4 -1
- package/app/api/admin/r2/upload-url/route.ts +4 -1
- package/app/api/admin/revalidate/route.ts +4 -1
- package/app/api/admin/storage/switch/route.ts +4 -1
- package/app/api/custom-sections/[id]/route.ts +5 -6
- package/components/admin/PublishToggle.tsx +2 -2
- package/components/admin/nav-builder/NavGridItem.tsx +4 -2
- package/components/admin/nav-builder/NavSettingsFields.tsx +10 -6
- package/components/admin/styles/ColorsEditor.tsx +7 -6
- package/components/admin/styles/FontsEditor.tsx +3 -1
- package/components/blocks/CoverSectionRenderer.tsx +7 -1
- package/components/blocks/SectionV2Renderer.tsx +8 -1
- package/components/builder/BubbleIcons.tsx +14 -0
- package/components/builder/CanvasMinimap.tsx +66 -49
- package/components/builder/CanvasToolbar.tsx +31 -41
- package/components/builder/SectionEditorBar.tsx +4 -2
- package/components/builder/SectionTypePicker.tsx +4 -2
- package/components/builder/SectionV2Column.tsx +13 -1
- package/components/builder/SettingsPanel.tsx +21 -17
- package/components/builder/SortableBlock.tsx +2 -2
- package/components/builder/SortableRow.tsx +6 -9
- package/components/builder/VirtualAssetGrid.tsx +8 -2
- package/components/builder/asset-browser/R2BrowserContent.tsx +8 -4
- package/components/builder/color-picker/EyedropperButton.tsx +7 -6
- package/components/builder/color-picker/SwatchBar.tsx +11 -6
- package/components/builder/color-picker/UnifiedColorPicker.tsx +11 -6
- package/components/builder/editors/ImageGridBlockEditor.tsx +4 -2
- package/components/builder/editors/MarqueeBlockEditor.tsx +3 -2
- package/components/builder/editors/ProjectGridEditor.tsx +12 -7
- package/components/builder/editors/SpacerBlockEditor.tsx +25 -23
- package/components/builder/editors/TextBlockEditor.tsx +19 -14
- package/components/builder/editors/shared.tsx +4 -2
- package/components/builder/live-preview/LiveImagePreview.tsx +3 -1
- package/components/builder/live-preview/ProjectCardWrapper.tsx +3 -1
- package/components/builder/live-preview/RichTextBubbleMenu.tsx +10 -6
- package/components/builder/live-preview/shared.tsx +5 -2
- package/components/builder/settings-panel/BlockLayoutTab.tsx +4 -2
- package/components/builder/settings-panel/ColumnV2LayoutTab.tsx +242 -0
- package/components/builder/settings-panel/CoverSectionSettings.tsx +4 -2
- package/components/builder/settings-panel/SectionV2Settings.tsx +13 -8
- package/components/builder/settings-panel/index.ts +1 -0
- package/components/ui/NavContentLightbox.tsx +41 -4
- package/lib/builder/serializer/normalizers.ts +14 -0
- package/lib/builder/serializer/serializers.ts +27 -0
- package/lib/builder/store-blocks.ts +15 -5
- package/lib/builder/store-cover.ts +16 -6
- package/lib/builder/store-sections.ts +151 -51
- package/lib/builder/types-slices.ts +14 -0
- package/lib/sanity/queries.ts +48 -0
- package/lib/sanity/types.ts +14 -0
- package/lib/version.ts +1 -1
- package/package.json +7 -5
- package/sanity/schemas/objects/coverSection.ts +32 -0
- package/sanity/schemas/objects/parallaxSlide.ts +32 -0
- package/sanity/schemas/pageSectionV2.ts +32 -0
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
moveColumnBetweenSectionsInState,
|
|
42
42
|
swapColumnsBetweenSectionsInState,
|
|
43
43
|
} from "./store-helpers";
|
|
44
|
+
import { pushSnapshot } from "./history";
|
|
44
45
|
|
|
45
46
|
type StoreSet = (
|
|
46
47
|
partial: Partial<BuilderState> | ((state: BuilderState) => Partial<BuilderState>)
|
|
@@ -60,7 +61,6 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
60
61
|
* the data migration in Session 165.
|
|
61
62
|
*/
|
|
62
63
|
addSection: (blockType: SectionBlockType, afterRowKey?: string | null): void => {
|
|
63
|
-
get()._pushSnapshot();
|
|
64
64
|
const block = createDefaultBlock(blockType);
|
|
65
65
|
const gridColumns = 12;
|
|
66
66
|
|
|
@@ -99,22 +99,31 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
99
99
|
} else {
|
|
100
100
|
rows.push(newSection);
|
|
101
101
|
}
|
|
102
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
rows,
|
|
104
|
+
isDirty: true,
|
|
105
|
+
selectedRowKey: newSection._key,
|
|
106
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
107
|
+
_future: [],
|
|
108
|
+
};
|
|
103
109
|
});
|
|
104
110
|
},
|
|
105
111
|
|
|
106
112
|
reorderRows: (fromIndex: number, toIndex: number): void => {
|
|
107
|
-
get()._pushSnapshot();
|
|
108
113
|
set((state) => {
|
|
109
114
|
const rows = [...state.rows];
|
|
110
115
|
const [moved] = rows.splice(fromIndex, 1);
|
|
111
116
|
rows.splice(toIndex, 0, moved);
|
|
112
|
-
return {
|
|
117
|
+
return {
|
|
118
|
+
rows,
|
|
119
|
+
isDirty: true,
|
|
120
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
121
|
+
_future: [],
|
|
122
|
+
};
|
|
113
123
|
});
|
|
114
124
|
},
|
|
115
125
|
|
|
116
126
|
deleteSection: (sectionKey: string): void => {
|
|
117
|
-
get()._pushSnapshot();
|
|
118
127
|
set((state) => ({
|
|
119
128
|
rows: state.rows.filter((item) => item._key !== sectionKey),
|
|
120
129
|
selectedRowKey:
|
|
@@ -124,11 +133,12 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
124
133
|
selectedBlockKey:
|
|
125
134
|
state.selectedRowKey === sectionKey ? null : state.selectedBlockKey,
|
|
126
135
|
isDirty: true,
|
|
136
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
137
|
+
_future: [],
|
|
127
138
|
}));
|
|
128
139
|
},
|
|
129
140
|
|
|
130
141
|
duplicateSection: (sectionKey: string): void => {
|
|
131
|
-
get()._pushSnapshot();
|
|
132
142
|
set((state) => {
|
|
133
143
|
const idx = state.rows.findIndex((item) => item._key === sectionKey);
|
|
134
144
|
if (idx === -1) return state;
|
|
@@ -173,6 +183,8 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
173
183
|
selectedColumnKey: null,
|
|
174
184
|
selectedBlockKey: null,
|
|
175
185
|
isDirty: true,
|
|
186
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
187
|
+
_future: [],
|
|
176
188
|
};
|
|
177
189
|
});
|
|
178
190
|
},
|
|
@@ -180,15 +192,19 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
180
192
|
// ---- V2 Section operations ----
|
|
181
193
|
|
|
182
194
|
addSectionV2: (preset: SectionV2Preset, afterRowKey?: string | null): void => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
195
|
+
set((state) => {
|
|
196
|
+
const result = addSectionV2InState(state.rows, preset, afterRowKey);
|
|
197
|
+
return {
|
|
198
|
+
rows: result.rows,
|
|
199
|
+
isDirty: true,
|
|
200
|
+
selectedRowKey: result.newSectionKey,
|
|
201
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
202
|
+
_future: [],
|
|
203
|
+
};
|
|
204
|
+
});
|
|
187
205
|
},
|
|
188
206
|
|
|
189
207
|
addColumnV2: (sectionKey: string, gridRow: number, gridColumn: number, span: number): void => {
|
|
190
|
-
get()._pushSnapshot();
|
|
191
|
-
|
|
192
208
|
set((state) => {
|
|
193
209
|
const path = findSectionPath(state.rows, sectionKey);
|
|
194
210
|
if (!path) return state;
|
|
@@ -216,13 +232,16 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
216
232
|
settings: { ...section.settings, preset: newPreset },
|
|
217
233
|
};
|
|
218
234
|
});
|
|
219
|
-
return {
|
|
235
|
+
return {
|
|
236
|
+
rows,
|
|
237
|
+
isDirty: true,
|
|
238
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
239
|
+
_future: [],
|
|
240
|
+
};
|
|
220
241
|
});
|
|
221
242
|
},
|
|
222
243
|
|
|
223
244
|
deleteColumnV2: (sectionKey: string, columnKey: string): void => {
|
|
224
|
-
get()._pushSnapshot();
|
|
225
|
-
|
|
226
245
|
set((state) => {
|
|
227
246
|
const path = findSectionPath(state.rows, sectionKey);
|
|
228
247
|
if (!path) return state;
|
|
@@ -251,13 +270,13 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
251
270
|
selectedColumnKey:
|
|
252
271
|
state.selectedColumnKey === columnKey ? null : state.selectedColumnKey,
|
|
253
272
|
isDirty: true,
|
|
273
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
274
|
+
_future: [],
|
|
254
275
|
};
|
|
255
276
|
});
|
|
256
277
|
},
|
|
257
278
|
|
|
258
279
|
resizeColumnV2: (sectionKey: string, columnKey: string, newSpan: number): void => {
|
|
259
|
-
get()._pushSnapshot();
|
|
260
|
-
|
|
261
280
|
set((state) => {
|
|
262
281
|
const path = findSectionPath(state.rows, sectionKey);
|
|
263
282
|
if (!path) return state;
|
|
@@ -282,7 +301,12 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
282
301
|
settings: { ...section.settings, preset: newPreset },
|
|
283
302
|
};
|
|
284
303
|
});
|
|
285
|
-
return {
|
|
304
|
+
return {
|
|
305
|
+
rows,
|
|
306
|
+
isDirty: true,
|
|
307
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
308
|
+
_future: [],
|
|
309
|
+
};
|
|
286
310
|
});
|
|
287
311
|
},
|
|
288
312
|
|
|
@@ -295,8 +319,12 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
295
319
|
);
|
|
296
320
|
if (!result) return; // Blocked by cascade or section not found
|
|
297
321
|
|
|
298
|
-
|
|
299
|
-
|
|
322
|
+
set((state) => ({
|
|
323
|
+
rows: result.rows,
|
|
324
|
+
isDirty: true,
|
|
325
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
326
|
+
_future: [],
|
|
327
|
+
}));
|
|
300
328
|
},
|
|
301
329
|
|
|
302
330
|
moveColumnV2: (sectionKey: string, columnKey: string, targetRow: number, targetColumn: number): void => {
|
|
@@ -309,8 +337,12 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
309
337
|
);
|
|
310
338
|
if (!result) return;
|
|
311
339
|
|
|
312
|
-
|
|
313
|
-
|
|
340
|
+
set((state) => ({
|
|
341
|
+
rows: result.rows,
|
|
342
|
+
isDirty: true,
|
|
343
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
344
|
+
_future: [],
|
|
345
|
+
}));
|
|
314
346
|
},
|
|
315
347
|
|
|
316
348
|
swapColumnV2: (sectionKey: string, draggedKey: string, targetKey: string): void => {
|
|
@@ -322,8 +354,12 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
322
354
|
);
|
|
323
355
|
if (!result) return;
|
|
324
356
|
|
|
325
|
-
|
|
326
|
-
|
|
357
|
+
set((state) => ({
|
|
358
|
+
rows: result.rows,
|
|
359
|
+
isDirty: true,
|
|
360
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
361
|
+
_future: [],
|
|
362
|
+
}));
|
|
327
363
|
},
|
|
328
364
|
|
|
329
365
|
moveColumnToGapV2: (sectionKey: string, columnKey: string, targetRow: number, targetColumn: number, targetSpan: number): void => {
|
|
@@ -337,8 +373,12 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
337
373
|
);
|
|
338
374
|
if (!result) return;
|
|
339
375
|
|
|
340
|
-
|
|
341
|
-
|
|
376
|
+
set((state) => ({
|
|
377
|
+
rows: result.rows,
|
|
378
|
+
isDirty: true,
|
|
379
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
380
|
+
_future: [],
|
|
381
|
+
}));
|
|
342
382
|
},
|
|
343
383
|
|
|
344
384
|
moveColumnBetweenSections: (
|
|
@@ -360,16 +400,17 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
360
400
|
);
|
|
361
401
|
if (!result) return;
|
|
362
402
|
|
|
363
|
-
get()._pushSnapshot();
|
|
364
403
|
// Clear any selection referencing the moved column in its old section —
|
|
365
404
|
// the column key remains but the "selected section" context has changed.
|
|
366
|
-
set({
|
|
405
|
+
set((state) => ({
|
|
367
406
|
rows: result.rows,
|
|
368
407
|
isDirty: true,
|
|
369
408
|
selectedColumnKey: columnKey,
|
|
370
409
|
selectedRowKey: targetSectionKey,
|
|
371
410
|
selectedBlockKey: null,
|
|
372
|
-
|
|
411
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
412
|
+
_future: [],
|
|
413
|
+
}));
|
|
373
414
|
},
|
|
374
415
|
|
|
375
416
|
swapColumnsBetweenSections: (
|
|
@@ -387,23 +428,22 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
387
428
|
);
|
|
388
429
|
if (!result) return;
|
|
389
430
|
|
|
390
|
-
get()._pushSnapshot();
|
|
391
431
|
// After swap: the dragged column now lives in the target section.
|
|
392
432
|
// Select it there (match mental model: "I just put this column here").
|
|
393
|
-
set({
|
|
433
|
+
set((state) => ({
|
|
394
434
|
rows: result.rows,
|
|
395
435
|
isDirty: true,
|
|
396
436
|
selectedColumnKey: sourceColumnKey,
|
|
397
437
|
selectedRowKey: targetSectionKey,
|
|
398
438
|
selectedBlockKey: null,
|
|
399
|
-
|
|
439
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
440
|
+
_future: [],
|
|
441
|
+
}));
|
|
400
442
|
},
|
|
401
443
|
|
|
402
444
|
applyPresetV2: (sectionKey: string, preset: SectionV2Preset): void => {
|
|
403
445
|
if (preset === "custom") return;
|
|
404
446
|
|
|
405
|
-
get()._pushSnapshot();
|
|
406
|
-
|
|
407
447
|
set((state) => {
|
|
408
448
|
const path = findSectionPath(state.rows, sectionKey);
|
|
409
449
|
if (!path) return state;
|
|
@@ -424,7 +464,12 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
424
464
|
settings: { ...section.settings, preset },
|
|
425
465
|
};
|
|
426
466
|
});
|
|
427
|
-
return {
|
|
467
|
+
return {
|
|
468
|
+
rows,
|
|
469
|
+
isDirty: true,
|
|
470
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
471
|
+
_future: [],
|
|
472
|
+
};
|
|
428
473
|
});
|
|
429
474
|
},
|
|
430
475
|
|
|
@@ -492,7 +537,6 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
492
537
|
},
|
|
493
538
|
|
|
494
539
|
addBlockV2: (sectionKey: string, columnKey: string, blockType: BlockType, insertIndex?: number): void => {
|
|
495
|
-
get()._pushSnapshot();
|
|
496
540
|
const newBlock = createDefaultBlock(blockType);
|
|
497
541
|
|
|
498
542
|
set((state) => {
|
|
@@ -514,7 +558,13 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
514
558
|
return { ...col, blocks: [...col.blocks, newBlock] };
|
|
515
559
|
}),
|
|
516
560
|
}));
|
|
517
|
-
return {
|
|
561
|
+
return {
|
|
562
|
+
rows,
|
|
563
|
+
selectedBlockKey: newBlock._key,
|
|
564
|
+
isDirty: true,
|
|
565
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
566
|
+
_future: [],
|
|
567
|
+
};
|
|
518
568
|
});
|
|
519
569
|
},
|
|
520
570
|
|
|
@@ -541,6 +591,27 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
541
591
|
});
|
|
542
592
|
},
|
|
543
593
|
|
|
594
|
+
// Update desktop-level column layout fields (spacing/background/border).
|
|
595
|
+
// Undefined values in `updates` clear that field.
|
|
596
|
+
updateColumnV2Layout: (sectionKey, colKey, updates) => {
|
|
597
|
+
set((state) => {
|
|
598
|
+
const path = findSectionPath(state.rows, sectionKey);
|
|
599
|
+
if (!path) return state;
|
|
600
|
+
const rows = updateSectionAtPath(state.rows, path, (section) => ({
|
|
601
|
+
...section,
|
|
602
|
+
columns: section.columns.map((col) => {
|
|
603
|
+
if (col._key !== colKey) return col;
|
|
604
|
+
const merged = { ...col, ...updates };
|
|
605
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
606
|
+
if (v === undefined) delete (merged as Record<string, unknown>)[k];
|
|
607
|
+
}
|
|
608
|
+
return merged;
|
|
609
|
+
}),
|
|
610
|
+
}));
|
|
611
|
+
return { rows, isDirty: true };
|
|
612
|
+
});
|
|
613
|
+
},
|
|
614
|
+
|
|
544
615
|
// ---- Custom Section Instance operations (Session 108) ----
|
|
545
616
|
|
|
546
617
|
addCustomSectionInstance: (
|
|
@@ -549,7 +620,6 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
549
620
|
title: string,
|
|
550
621
|
afterRowKey?: string | null
|
|
551
622
|
) => {
|
|
552
|
-
get()._pushSnapshot();
|
|
553
623
|
const instance: CustomSectionInstance = {
|
|
554
624
|
_type: "customSectionInstance",
|
|
555
625
|
_key: generateKey(),
|
|
@@ -570,7 +640,13 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
570
640
|
} else {
|
|
571
641
|
rows.push(instance);
|
|
572
642
|
}
|
|
573
|
-
return {
|
|
643
|
+
return {
|
|
644
|
+
rows,
|
|
645
|
+
isDirty: true,
|
|
646
|
+
selectedRowKey: instance._key,
|
|
647
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
648
|
+
_future: [],
|
|
649
|
+
};
|
|
574
650
|
});
|
|
575
651
|
},
|
|
576
652
|
|
|
@@ -578,7 +654,6 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
578
654
|
instanceKey: string,
|
|
579
655
|
sectionData: PageSectionV2
|
|
580
656
|
) => {
|
|
581
|
-
get()._pushSnapshot();
|
|
582
657
|
set((state) => ({
|
|
583
658
|
rows: state.rows.map((item) =>
|
|
584
659
|
item._key === instanceKey
|
|
@@ -586,6 +661,8 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
586
661
|
: item
|
|
587
662
|
),
|
|
588
663
|
isDirty: true,
|
|
664
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
665
|
+
_future: [],
|
|
589
666
|
}));
|
|
590
667
|
},
|
|
591
668
|
|
|
@@ -611,7 +688,6 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
611
688
|
instanceKey: string,
|
|
612
689
|
updates: Partial<SectionV2Settings>
|
|
613
690
|
) => {
|
|
614
|
-
get()._pushSnapshot();
|
|
615
691
|
set((state) => ({
|
|
616
692
|
rows: state.rows.map((item) =>
|
|
617
693
|
item._key === instanceKey && isCustomSectionInstance(item)
|
|
@@ -625,6 +701,8 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
625
701
|
: item
|
|
626
702
|
),
|
|
627
703
|
isDirty: true,
|
|
704
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
705
|
+
_future: [],
|
|
628
706
|
}));
|
|
629
707
|
},
|
|
630
708
|
|
|
@@ -640,13 +718,19 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
640
718
|
// ---- Parallax Group operations (Session 123) ----
|
|
641
719
|
|
|
642
720
|
addParallaxGroup: (afterRowKey?: string | null): void => {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
721
|
+
set((state) => {
|
|
722
|
+
const result = addParallaxGroupInState(state.rows, afterRowKey);
|
|
723
|
+
return {
|
|
724
|
+
rows: result.rows,
|
|
725
|
+
isDirty: true,
|
|
726
|
+
selectedRowKey: result.newGroupKey,
|
|
727
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
728
|
+
_future: [],
|
|
729
|
+
};
|
|
730
|
+
});
|
|
646
731
|
},
|
|
647
732
|
|
|
648
733
|
addParallaxSlide: (groupKey: string): void => {
|
|
649
|
-
get()._pushSnapshot();
|
|
650
734
|
set((state) => ({
|
|
651
735
|
rows: state.rows.map((item) => {
|
|
652
736
|
if (item._key !== groupKey || !isParallaxGroup(item)) return item;
|
|
@@ -655,11 +739,12 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
655
739
|
return { ...group, slides: [...group.slides, newSlide] } as ContentItem;
|
|
656
740
|
}),
|
|
657
741
|
isDirty: true,
|
|
742
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
743
|
+
_future: [],
|
|
658
744
|
}));
|
|
659
745
|
},
|
|
660
746
|
|
|
661
747
|
removeParallaxSlide: (groupKey: string, slideKey: string): void => {
|
|
662
|
-
get()._pushSnapshot();
|
|
663
748
|
set((state) => {
|
|
664
749
|
let changed = false;
|
|
665
750
|
const rows = state.rows.map((item) => {
|
|
@@ -673,7 +758,14 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
673
758
|
slides: group.slides.filter((s) => s._key !== slideKey),
|
|
674
759
|
} as ContentItem;
|
|
675
760
|
});
|
|
676
|
-
return changed
|
|
761
|
+
return changed
|
|
762
|
+
? {
|
|
763
|
+
rows,
|
|
764
|
+
isDirty: true,
|
|
765
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
766
|
+
_future: [],
|
|
767
|
+
}
|
|
768
|
+
: {};
|
|
677
769
|
});
|
|
678
770
|
},
|
|
679
771
|
|
|
@@ -682,7 +774,6 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
682
774
|
slideKey: string,
|
|
683
775
|
direction: "up" | "down"
|
|
684
776
|
) => {
|
|
685
|
-
get()._pushSnapshot();
|
|
686
777
|
set((state) => {
|
|
687
778
|
let changed = false;
|
|
688
779
|
const rows = state.rows.map((item) => {
|
|
@@ -697,7 +788,14 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
697
788
|
changed = true;
|
|
698
789
|
return { ...group, slides } as ContentItem;
|
|
699
790
|
});
|
|
700
|
-
return changed
|
|
791
|
+
return changed
|
|
792
|
+
? {
|
|
793
|
+
rows,
|
|
794
|
+
isDirty: true,
|
|
795
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
796
|
+
_future: [],
|
|
797
|
+
}
|
|
798
|
+
: {};
|
|
701
799
|
});
|
|
702
800
|
},
|
|
703
801
|
|
|
@@ -706,7 +804,6 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
706
804
|
slideKey: string,
|
|
707
805
|
fields: Partial<ParallaxSlideV2>
|
|
708
806
|
) => {
|
|
709
|
-
get()._pushSnapshot();
|
|
710
807
|
set((state) => ({
|
|
711
808
|
rows: state.rows.map((item) => {
|
|
712
809
|
if (item._key !== groupKey || !isParallaxGroup(item)) return item;
|
|
@@ -719,6 +816,8 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
719
816
|
} as ContentItem;
|
|
720
817
|
}),
|
|
721
818
|
isDirty: true,
|
|
819
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
820
|
+
_future: [],
|
|
722
821
|
}));
|
|
723
822
|
},
|
|
724
823
|
|
|
@@ -728,13 +827,14 @@ export function createSectionActions(set: StoreSet, get: StoreGet): SectionSlice
|
|
|
728
827
|
Pick<ParallaxGroup, "transition_effect" | "snap_enabled" | "parallax_intensity">
|
|
729
828
|
>
|
|
730
829
|
) => {
|
|
731
|
-
get()._pushSnapshot();
|
|
732
830
|
set((state) => ({
|
|
733
831
|
rows: state.rows.map((item) => {
|
|
734
832
|
if (item._key !== groupKey || !isParallaxGroup(item)) return item;
|
|
735
833
|
return { ...item, ...fields } as ContentItem;
|
|
736
834
|
}),
|
|
737
835
|
isDirty: true,
|
|
836
|
+
_history: pushSnapshot(state._history, { rows: state.rows, pageSettings: state.pageSettings }),
|
|
837
|
+
_future: [],
|
|
738
838
|
}));
|
|
739
839
|
},
|
|
740
840
|
};
|
|
@@ -31,6 +31,7 @@ import type {
|
|
|
31
31
|
PageSectionV2,
|
|
32
32
|
SectionV2Settings,
|
|
33
33
|
SectionV2Preset,
|
|
34
|
+
SectionColumn,
|
|
34
35
|
PageMetadata,
|
|
35
36
|
ColorField,
|
|
36
37
|
CoverSection,
|
|
@@ -168,6 +169,19 @@ export interface SectionSliceActions {
|
|
|
168
169
|
colKey: string,
|
|
169
170
|
config: import("../../lib/animation/enter-types").EnterAnimationConfig | undefined,
|
|
170
171
|
) => void;
|
|
172
|
+
/** Update desktop-level layout fields on a V2/cover/parallax-slide column.
|
|
173
|
+
* Columns only support background + border — spacing belongs to the
|
|
174
|
+
* section (row_gap/col_gap) or to block-level padding. */
|
|
175
|
+
updateColumnV2Layout: (
|
|
176
|
+
sectionKey: string,
|
|
177
|
+
colKey: string,
|
|
178
|
+
updates: Partial<Pick<
|
|
179
|
+
SectionColumn,
|
|
180
|
+
| "background_color" | "background_opacity" | "background_image"
|
|
181
|
+
| "background_size" | "background_position" | "background_repeat"
|
|
182
|
+
| "border_color" | "border_width" | "border_style" | "border_sides" | "border_radius"
|
|
183
|
+
>>,
|
|
184
|
+
) => void;
|
|
171
185
|
|
|
172
186
|
/** Select a V2 column — sets selectedRowKey + selectedColumnKey. */
|
|
173
187
|
selectColumnV2: (sectionKey: string | null, columnKey: string | null) => void;
|
package/lib/sanity/queries.ts
CHANGED
|
@@ -20,6 +20,18 @@ const blockExpansion = `
|
|
|
20
20
|
grid_row,
|
|
21
21
|
span,
|
|
22
22
|
enter_animation,
|
|
23
|
+
// Column-level background + border (Session 184)
|
|
24
|
+
background_color,
|
|
25
|
+
background_opacity,
|
|
26
|
+
background_image,
|
|
27
|
+
background_size,
|
|
28
|
+
background_position,
|
|
29
|
+
background_repeat,
|
|
30
|
+
border_color,
|
|
31
|
+
border_width,
|
|
32
|
+
border_style,
|
|
33
|
+
border_sides,
|
|
34
|
+
border_radius,
|
|
23
35
|
blocks[] {
|
|
24
36
|
_type,
|
|
25
37
|
_key,
|
|
@@ -60,6 +72,18 @@ const blockExpansion = `
|
|
|
60
72
|
grid_column,
|
|
61
73
|
grid_row,
|
|
62
74
|
span,
|
|
75
|
+
// Column-level background + border (Session 184)
|
|
76
|
+
background_color,
|
|
77
|
+
background_opacity,
|
|
78
|
+
background_image,
|
|
79
|
+
background_size,
|
|
80
|
+
background_position,
|
|
81
|
+
background_repeat,
|
|
82
|
+
border_color,
|
|
83
|
+
border_width,
|
|
84
|
+
border_style,
|
|
85
|
+
border_sides,
|
|
86
|
+
border_radius,
|
|
63
87
|
blocks[] {
|
|
64
88
|
_type,
|
|
65
89
|
_key,
|
|
@@ -429,6 +453,18 @@ export const customSectionBySlugQuery = groq`
|
|
|
429
453
|
grid_column,
|
|
430
454
|
grid_row,
|
|
431
455
|
span,
|
|
456
|
+
enter_animation,
|
|
457
|
+
background_color,
|
|
458
|
+
background_opacity,
|
|
459
|
+
background_image,
|
|
460
|
+
background_size,
|
|
461
|
+
background_position,
|
|
462
|
+
background_repeat,
|
|
463
|
+
border_color,
|
|
464
|
+
border_width,
|
|
465
|
+
border_style,
|
|
466
|
+
border_sides,
|
|
467
|
+
border_radius,
|
|
432
468
|
blocks[] {
|
|
433
469
|
_type,
|
|
434
470
|
_key,
|
|
@@ -456,6 +492,18 @@ export const customSectionByIdQuery = groq`
|
|
|
456
492
|
grid_column,
|
|
457
493
|
grid_row,
|
|
458
494
|
span,
|
|
495
|
+
enter_animation,
|
|
496
|
+
background_color,
|
|
497
|
+
background_opacity,
|
|
498
|
+
background_image,
|
|
499
|
+
background_size,
|
|
500
|
+
background_position,
|
|
501
|
+
background_repeat,
|
|
502
|
+
border_color,
|
|
503
|
+
border_width,
|
|
504
|
+
border_style,
|
|
505
|
+
border_sides,
|
|
506
|
+
border_radius,
|
|
459
507
|
blocks[] {
|
|
460
508
|
_type,
|
|
461
509
|
_key,
|
package/lib/sanity/types.ts
CHANGED
|
@@ -559,6 +559,20 @@ export interface SectionColumn {
|
|
|
559
559
|
blocks: ContentBlock[]; // same block types as today
|
|
560
560
|
// NEW (Session 116) — column-level enter animation for 4-level cascade
|
|
561
561
|
enter_animation?: import("../../lib/animation/enter-types").EnterAnimationConfig;
|
|
562
|
+
// Column-level layout — desktop-only for now. Background + border only
|
|
563
|
+
// (no spacing — section row_gap/col_gap + block padding cover that).
|
|
564
|
+
// Viewport overrides are TBD: the current ColumnOverride type is position-only.
|
|
565
|
+
background_color?: string;
|
|
566
|
+
background_opacity?: number;
|
|
567
|
+
background_image?: string;
|
|
568
|
+
background_size?: string;
|
|
569
|
+
background_position?: string;
|
|
570
|
+
background_repeat?: string;
|
|
571
|
+
border_color?: string;
|
|
572
|
+
border_width?: string;
|
|
573
|
+
border_style?: string;
|
|
574
|
+
border_sides?: string;
|
|
575
|
+
border_radius?: string;
|
|
562
576
|
}
|
|
563
577
|
|
|
564
578
|
export interface ColumnOverride {
|
package/lib/version.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morphika/andami",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
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",
|
|
@@ -193,7 +193,6 @@
|
|
|
193
193
|
"react-dom": ">=19.0.0"
|
|
194
194
|
},
|
|
195
195
|
"dependencies": {
|
|
196
|
-
"archiver": "^7.0.1",
|
|
197
196
|
"@aws-sdk/client-s3": "^3.1021.0",
|
|
198
197
|
"@aws-sdk/s3-request-presigner": "^3.1021.0",
|
|
199
198
|
"@dnd-kit/core": "^6.3.1",
|
|
@@ -210,10 +209,11 @@
|
|
|
210
209
|
"@tiptap/starter-kit": "^2.12.0",
|
|
211
210
|
"@types/archiver": "^6.0.3",
|
|
212
211
|
"@types/unzipper": "^0.10.10",
|
|
212
|
+
"archiver": "^7.0.1",
|
|
213
213
|
"jszip": "^3.10.1",
|
|
214
|
-
"next-sanity": "^12.
|
|
214
|
+
"next-sanity": "^12.3.0",
|
|
215
215
|
"ogl": "^1.0.8",
|
|
216
|
-
"sanity": "^5.
|
|
216
|
+
"sanity": "^5.21.0",
|
|
217
217
|
"unzipper": "^0.12.3",
|
|
218
218
|
"zustand": "^5.0.12"
|
|
219
219
|
},
|
|
@@ -228,10 +228,12 @@
|
|
|
228
228
|
"@types/react-dom": "^19",
|
|
229
229
|
"eslint": "^9",
|
|
230
230
|
"eslint-config-next": "16.2.1",
|
|
231
|
+
"framer-motion": "^12.38.0",
|
|
231
232
|
"jsdom": "^26.1.0",
|
|
232
|
-
"next": "16.2.
|
|
233
|
+
"next": "^16.2.4",
|
|
233
234
|
"react": "19.2.4",
|
|
234
235
|
"react-dom": "19.2.4",
|
|
236
|
+
"styled-components": "^6.4.0",
|
|
235
237
|
"tailwindcss": "^4",
|
|
236
238
|
"typescript": "^5",
|
|
237
239
|
"vitest": "^4.1.2"
|