@morphika/andami 0.4.1 → 0.5.0
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 +2 -1
- package/components/builder/ColumnDragContext.tsx +5 -0
- package/components/builder/ColumnDragOverlay.tsx +59 -17
- package/components/builder/InsertionLines.tsx +9 -1
- package/components/builder/SectionV2Canvas.tsx +13 -3
- package/components/builder/hooks/useColumnDrag.ts +269 -142
- package/lib/builder/store-blocks.ts +2 -2
- package/lib/builder/store-canvas.ts +2 -2
- package/lib/builder/store-cover.ts +2 -2
- package/lib/builder/store-helpers.ts +345 -1
- package/lib/builder/store-sections.ts +62 -2
- package/lib/builder/types-slices.ts +414 -0
- package/lib/builder/types.ts +77 -225
- package/lib/sanity/types.ts +17 -0
- package/lib/version.ts +1 -1
- package/package.json +1 -1
package/lib/builder/types.ts
CHANGED
|
@@ -182,231 +182,83 @@ export const DEFAULT_GRID_SETTINGS: GridSettings = {
|
|
|
182
182
|
gutter_phone: "16",
|
|
183
183
|
};
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// Content — V2 sections, custom sections, parallax groups
|
|
198
|
-
rows: ContentItem[];
|
|
199
|
-
|
|
200
|
-
// Selection
|
|
201
|
-
selectedRowKey: string | null;
|
|
202
|
-
selectedColumnKey: string | null;
|
|
203
|
-
selectedBlockKey: string | null;
|
|
204
|
-
/** Sub-selection: which project card is selected within a ProjectGrid block */
|
|
205
|
-
selectedProjectCardKey: string | null;
|
|
206
|
-
|
|
207
|
-
// UI state
|
|
208
|
-
isDirty: boolean;
|
|
209
|
-
isSaving: boolean;
|
|
210
|
-
saveError: string | null;
|
|
211
|
-
lastSavedAt: string | null;
|
|
212
|
-
|
|
213
|
-
// Editor mode
|
|
214
|
-
previewMode: boolean;
|
|
215
|
-
|
|
216
|
-
// Custom section editor mode (Session 107)
|
|
217
|
-
editorMode: "page" | "customSection";
|
|
218
|
-
customSectionSlug: string | null;
|
|
219
|
-
customSectionTitle: string | null;
|
|
220
|
-
/** Stashed page state while editing a custom section */
|
|
221
|
-
savedPageState: { rows: ContentItem[]; selectedKey: string | null } | null;
|
|
222
|
-
|
|
223
|
-
// Page-level settings
|
|
224
|
-
pageSettings: PageSettings;
|
|
225
|
-
|
|
226
|
-
// Grid settings (from Customize, ephemeral — NOT saved per page)
|
|
227
|
-
gridSettings: GridSettings;
|
|
228
|
-
|
|
229
|
-
// Canvas state (ephemeral — NOT saved to Sanity)
|
|
230
|
-
canvasZoom: number;
|
|
231
|
-
canvasPanX: number;
|
|
232
|
-
canvasPanY: number;
|
|
233
|
-
canvasTool: CanvasTool;
|
|
234
|
-
activeViewport: DeviceViewport;
|
|
235
|
-
|
|
236
|
-
// BUG-014 fix: Track whether the Sanity document had page_settings.
|
|
237
|
-
// When true, applyGlobalStyles() won't overwrite user-chosen colors.
|
|
238
|
-
_hasDocumentPageSettings: boolean;
|
|
239
|
-
|
|
240
|
-
// History (Undo/Redo) — BUG-010 fix: snapshots include pageSettings
|
|
241
|
-
_history: import("./history").HistorySnapshot[];
|
|
242
|
-
_future: import("./history").HistorySnapshot[];
|
|
243
|
-
|
|
244
|
-
/** Cache of fetched custom section base settings, keyed by custom_section_id.
|
|
245
|
-
* Populated by CustomSectionInstanceCard/ReadOnlyCustomSection on fetch.
|
|
246
|
-
* Used by SortableRow to merge base settings with per-instance overrides. */
|
|
247
|
-
_customSectionCache: Record<string, import("../../lib/sanity/types").SectionV2Settings>;
|
|
248
|
-
|
|
249
|
-
/** Counter incremented after a custom section is saved via the section editor.
|
|
250
|
-
* Used as a useEffect dependency in CustomSectionInstanceCard to trigger refetch. */
|
|
251
|
-
_customSectionRefetchTick: number;
|
|
252
|
-
|
|
253
|
-
/** Live preview overlay from color picker — shown on canvas without persisting to Sanity.
|
|
254
|
-
* Set while user is dragging in the color picker; cleared on close. */
|
|
255
|
-
colorPickerPreview: {
|
|
256
|
-
blockKey?: string;
|
|
257
|
-
sectionKey?: string;
|
|
258
|
-
field: string;
|
|
259
|
-
value: ColorField;
|
|
260
|
-
} | null;
|
|
261
|
-
}
|
|
185
|
+
// ============================================
|
|
186
|
+
// BuilderState & BuilderActions — composed from slices
|
|
187
|
+
// ============================================
|
|
188
|
+
// Session 183 (Fase 1 / Nivel A): state and actions are declared as
|
|
189
|
+
// intersections of per-slice types defined in `./types-slices.ts`.
|
|
190
|
+
// The runtime shape is still a flat object — no `state.blocks.rows`
|
|
191
|
+
// indirection. This split is purely a type-level organization that:
|
|
192
|
+
// 1. Documents which slice owns which state field / action
|
|
193
|
+
// 2. Provides a clear template for adding a new slice
|
|
194
|
+
// 3. Prepares for Fase 2 (actual shape split) without forcing it now
|
|
195
|
+
// ============================================
|
|
262
196
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
/** Move a column to an empty gap, adopting the gap's span */
|
|
330
|
-
moveColumnToGapV2: (sectionKey: string, columnKey: string, targetRow: number, targetColumn: number, targetSpan: number) => void;
|
|
331
|
-
/** Apply a layout preset to a V2 section (replaces columns + updates preset atomically) */
|
|
332
|
-
applyPresetV2: (sectionKey: string, preset: SectionV2Preset) => void;
|
|
333
|
-
/** Update settings for a V2 section */
|
|
334
|
-
updateSectionV2Settings: (sectionKey: string, settings: Partial<SectionV2Settings>) => void;
|
|
335
|
-
/** Update responsive overrides for a V2 section */
|
|
336
|
-
updateSectionV2Responsive: (sectionKey: string, responsive: PageSectionV2["responsive"]) => void;
|
|
337
|
-
/** Add a block to a V2 section column, optionally at a specific index */
|
|
338
|
-
addBlockV2: (sectionKey: string, columnKey: string, blockType: BlockType, insertIndex?: number) => void;
|
|
339
|
-
/** Select a V2 column */
|
|
340
|
-
selectColumnV2: (sectionKey: string | null, columnKey: string | null) => void;
|
|
341
|
-
/** Update enter animation for a V2 column (Session 117) */
|
|
342
|
-
updateColumnEnterAnimation: (sectionKey: string, colKey: string, config: import("../../lib/animation/enter-types").EnterAnimationConfig | undefined) => void;
|
|
343
|
-
|
|
344
|
-
// Debounced update (no snapshot per keystroke)
|
|
345
|
-
updateBlockDebounced: (blockKey: string, updates: Partial<ContentBlock>) => void;
|
|
346
|
-
|
|
347
|
-
// Editor mode
|
|
348
|
-
togglePreviewMode: () => void;
|
|
349
|
-
setPreviewMode: (preview: boolean) => void;
|
|
350
|
-
|
|
351
|
-
// Custom section editor mode (Session 107)
|
|
352
|
-
/** Enter section editor mode — stashes current page rows, replaces with a single V2 section */
|
|
353
|
-
enterSectionEditor: (slug: string | null, title: string | null, sectionData: PageSectionV2 | null) => void;
|
|
354
|
-
/** Exit section editor mode — restores page rows from stash. Pass wasSaved=true to mark page dirty. */
|
|
355
|
-
exitSectionEditor: (wasSaved?: boolean) => void;
|
|
356
|
-
/** Save the custom section being edited, then exit editor mode.
|
|
357
|
-
* Returns { id, slug, title } on success (for instance insertion after creation). */
|
|
358
|
-
saveSectionEditor: (title: string) => Promise<{ id: string; slug: string; title: string } | null>;
|
|
359
|
-
/** Insert a custom section instance reference into the page rows */
|
|
360
|
-
addCustomSectionInstance: (id: string, slug: string, title: string, afterRowKey?: string | null) => void;
|
|
361
|
-
/** Detach a custom section instance — replaces it with a copy of the section data (inline V2 section) */
|
|
362
|
-
detachCustomSectionInstance: (instanceKey: string, sectionData: PageSectionV2) => void;
|
|
363
|
-
/** Update the cached title on a custom section instance (cosmetic sync, Session 110) */
|
|
364
|
-
updateCustomSectionInstanceTitle: (instanceKey: string, newTitle: string) => void;
|
|
365
|
-
/** Update per-instance section settings overrides (Session 130) */
|
|
366
|
-
updateCustomSectionInstanceSettings: (instanceKey: string, updates: Partial<SectionV2Settings>) => void;
|
|
367
|
-
/** Cache base settings from a fetched custom section (for SortableRow merge) */
|
|
368
|
-
cacheCustomSectionSettings: (sectionId: string, settings: SectionV2Settings) => void;
|
|
369
|
-
|
|
370
|
-
// ---- Parallax Group operations (Session 123) ----
|
|
371
|
-
/** Add a new ParallaxGroup with 1 empty slide */
|
|
372
|
-
addParallaxGroup: (afterRowKey?: string | null) => void;
|
|
373
|
-
/** Add a new empty slide to a parallax group */
|
|
374
|
-
addParallaxSlide: (groupKey: string) => void;
|
|
375
|
-
/** Remove a slide from a parallax group (minimum 1 enforced) */
|
|
376
|
-
removeParallaxSlide: (groupKey: string, slideKey: string) => void;
|
|
377
|
-
/** Reorder a slide up or down within its group */
|
|
378
|
-
moveParallaxSlide: (groupKey: string, slideKey: string, direction: "up" | "down") => void;
|
|
379
|
-
/** Update background fields on a parallax slide */
|
|
380
|
-
updateParallaxSlideBackground: (groupKey: string, slideKey: string, fields: Partial<import("../../lib/sanity/types").ParallaxSlideV2>) => void;
|
|
381
|
-
/** Update group-level settings (transition effect, snap, etc.) */
|
|
382
|
-
updateParallaxGroupSettings: (groupKey: string, fields: Partial<Pick<import("../../lib/sanity/types").ParallaxGroup, "transition_effect" | "snap_enabled" | "parallax_intensity">>) => void;
|
|
383
|
-
|
|
384
|
-
// ---- Cover Section operations (Session 176) ----
|
|
385
|
-
addCoverSection: (afterRowKey?: string | null) => void;
|
|
386
|
-
addCoverRow: (sectionKey: string) => void;
|
|
387
|
-
removeCoverRow: (sectionKey: string, rowKey: string) => void;
|
|
388
|
-
resizeCoverRow: (sectionKey: string, handleIndex: number, deltaPercent: number, startAbove: number, startBelow: number) => void;
|
|
389
|
-
updateCoverRowAlign: (sectionKey: string, rowKey: string, align: CoverRow["vertical_align"]) => void;
|
|
390
|
-
updateCoverBackground: (sectionKey: string, fields: Partial<Pick<CoverSection, "background_type" | "background_color" | "background_image" | "background_video" | "background_position" | "background_size" | "background_overlay_color" | "background_overlay_opacity" | "nav_color">>) => void;
|
|
391
|
-
updateCoverSettings: (sectionKey: string, settings: Partial<CoverSectionSettings>) => void;
|
|
392
|
-
updateCoverHeight: (sectionKey: string, height: CoverSection["height"]) => void;
|
|
393
|
-
|
|
394
|
-
// Page-level settings
|
|
395
|
-
updatePageSettings: (settings: Partial<PageSettings>) => void;
|
|
396
|
-
applyGlobalStyles: () => Promise<void>;
|
|
397
|
-
|
|
398
|
-
// Canvas
|
|
399
|
-
setCanvasZoom: (zoom: number) => void;
|
|
400
|
-
setCanvasPan: (x: number, y: number) => void;
|
|
401
|
-
setCanvasTool: (tool: CanvasTool) => void;
|
|
402
|
-
setActiveViewport: (viewport: DeviceViewport) => void;
|
|
403
|
-
zoomToFit: (viewportWidth: number, viewportHeight: number) => void;
|
|
404
|
-
zoomToPoint: (newZoom: number, cursorX: number, cursorY: number) => void;
|
|
405
|
-
zoomToFrame: (device: DeviceViewport, viewportWidth: number, viewportHeight: number) => void;
|
|
406
|
-
|
|
407
|
-
// Color picker live preview (Phase 4)
|
|
408
|
-
setColorPickerPreview: (preview: BuilderState["colorPickerPreview"]) => void;
|
|
409
|
-
clearColorPickerPreview: () => void;
|
|
410
|
-
}
|
|
197
|
+
import type {
|
|
198
|
+
MetaSlice,
|
|
199
|
+
MetaSliceState,
|
|
200
|
+
MetaSliceActions,
|
|
201
|
+
SectionSlice,
|
|
202
|
+
SectionSliceState,
|
|
203
|
+
SectionSliceActions,
|
|
204
|
+
BlockSlice,
|
|
205
|
+
BlockSliceActions,
|
|
206
|
+
CanvasSlice,
|
|
207
|
+
CanvasSliceState,
|
|
208
|
+
CanvasSliceActions,
|
|
209
|
+
CoverSlice,
|
|
210
|
+
CoverSliceActions,
|
|
211
|
+
SelectionSlice,
|
|
212
|
+
SelectionSliceState,
|
|
213
|
+
SelectionSliceActions,
|
|
214
|
+
HistorySlice,
|
|
215
|
+
HistorySliceState,
|
|
216
|
+
HistorySliceActions,
|
|
217
|
+
} from "./types-slices";
|
|
218
|
+
|
|
219
|
+
export type {
|
|
220
|
+
MetaSlice,
|
|
221
|
+
MetaSliceState,
|
|
222
|
+
MetaSliceActions,
|
|
223
|
+
SectionSlice,
|
|
224
|
+
SectionSliceState,
|
|
225
|
+
SectionSliceActions,
|
|
226
|
+
BlockSlice,
|
|
227
|
+
BlockSliceActions,
|
|
228
|
+
CanvasSlice,
|
|
229
|
+
CanvasSliceState,
|
|
230
|
+
CanvasSliceActions,
|
|
231
|
+
CoverSlice,
|
|
232
|
+
CoverSliceActions,
|
|
233
|
+
SelectionSlice,
|
|
234
|
+
SelectionSliceState,
|
|
235
|
+
SelectionSliceActions,
|
|
236
|
+
HistorySlice,
|
|
237
|
+
HistorySliceState,
|
|
238
|
+
HistorySliceActions,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Flat state object composed of all slice states.
|
|
243
|
+
* Runtime shape is unchanged from pre-Session 183 — a single level
|
|
244
|
+
* of properties. See `./types-slices.ts` for slice boundaries.
|
|
245
|
+
*/
|
|
246
|
+
export type BuilderState = MetaSliceState &
|
|
247
|
+
SectionSliceState &
|
|
248
|
+
CanvasSliceState &
|
|
249
|
+
SelectionSliceState &
|
|
250
|
+
HistorySliceState;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* All store actions, composed from per-slice action types.
|
|
254
|
+
* Runtime shape is unchanged. See `./types-slices.ts` for slice owners.
|
|
255
|
+
*/
|
|
256
|
+
export type BuilderActions = MetaSliceActions &
|
|
257
|
+
SectionSliceActions &
|
|
258
|
+
BlockSliceActions &
|
|
259
|
+
CanvasSliceActions &
|
|
260
|
+
CoverSliceActions &
|
|
261
|
+
SelectionSliceActions &
|
|
262
|
+
HistorySliceActions;
|
|
411
263
|
|
|
412
264
|
export type BuilderStore = BuilderState & BuilderActions;
|
package/lib/sanity/types.ts
CHANGED
|
@@ -596,6 +596,23 @@ export function isCoverSection(item: ContentItem): item is CoverSection {
|
|
|
596
596
|
return (item as CoverSection)._type === "coverSection";
|
|
597
597
|
}
|
|
598
598
|
|
|
599
|
+
/**
|
|
600
|
+
* Type guard: check if a content item supports column drag-and-drop as a
|
|
601
|
+
* source or target — i.e. it has a top-level `columns` array with the same
|
|
602
|
+
* V2 grid semantics. Used by cross-section column DnD (Session 183).
|
|
603
|
+
*
|
|
604
|
+
* Eligible: PageSectionV2, CoverSection
|
|
605
|
+
* NOT eligible:
|
|
606
|
+
* - ParallaxGroup (columns live per-slide, cross-slide DnD not supported)
|
|
607
|
+
* - CustomSectionInstance (references an external document, mutating it
|
|
608
|
+
* from the page would break the reference contract)
|
|
609
|
+
*/
|
|
610
|
+
export function isColumnarSection(
|
|
611
|
+
item: ContentItem,
|
|
612
|
+
): item is PageSectionV2 | CoverSection {
|
|
613
|
+
return isPageSectionV2(item) || isCoverSection(item);
|
|
614
|
+
}
|
|
615
|
+
|
|
599
616
|
// ============================================
|
|
600
617
|
// Shared references
|
|
601
618
|
// ============================================
|
package/lib/version.ts
CHANGED
package/package.json
CHANGED