editor: drop into tagged slot dropzones ([data-cms-slot])
Templates mark visual edge slots with data-cms-slot/-parent. The drag engine now resolves those zones first (slot-scoped index, acceptsSlot filter), sorts within the dragged block's own slot, and the overlay shows a slot add-button so empty zones are discoverable. dnd unit tests cover slot hit/index/refusal/same-slot sort. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
1 parent
33bd5269fd
commit
8e8823421d
5 files changed
+196
-8
No files matched your search
@@ -36,6 +36,7 @@ export function Overlay() {
|
||||
))}
|
||||
{idle && sel[0] && <BoxHandles block={sel[0]} />}
|
||||
{idle && <InsertSlots blocks={blocks} selected={selected} hovered={hovered} />}
|
||||
{idle && <SlotAdds selected={selected} hovered={hovered} />}
|
||||
{idle && sel[0] && <AttrChips />}
|
||||
{idle && sel[0] && <ActionBar block={sel[0]} />}
|
||||
{drag && <DropGhost />}
|
||||
@@ -246,6 +247,42 @@ function InsertSlots({ blocks, selected, hovered }: { blocks: Block[]; selected:
|
||||
)
|
||||
}
|
||||
|
||||
// --- slot add buttons: "+" on each tagged dropzone of the selected / hovered block ------------------
|
||||
// The outline already lists every schema slot with an add button; this is the
|
||||
// on-page equivalent so e.g. an empty `3d-accessories` zone on a PDP is
|
||||
// discoverable where the accessories render.
|
||||
|
||||
function SlotAdds({ selected, hovered }: { selected: string | null; hovered: string | null }) {
|
||||
const ctrl = useCtrl()
|
||||
useEditorState((s) => s.tick)
|
||||
useEditorState((s) => s.schemasVersion)
|
||||
const parents = [selected, hovered].filter((id, i, a): id is string => !!id && a.indexOf(id) === i)
|
||||
if (!parents.length) return null
|
||||
// NB: doc.querySelectorAll does not pierce the editor's shadow root, so
|
||||
// editor UI is never included (unlike block scans, no host filter needed).
|
||||
const zones = [...ctrl.doc.querySelectorAll<HTMLElement>('[data-cms-slot]')].filter((el) =>
|
||||
parents.includes(el.getAttribute('data-cms-slot-parent') ?? ''),
|
||||
)
|
||||
const open = (parentId: string, handle: string, x: number, y: number) => {
|
||||
let key = 0
|
||||
key = ctrl.openDialog({ id: `insert:${parentId}:${handle}`, title: `Add to ${handle}`, size: 'sm', anchor: { x, y }, content: createElement(InsertPicker, { parentId, handle, ctrl, close: () => ctrl.closeDialog(key) }) })
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{zones.map((el) => {
|
||||
const parentId = el.getAttribute('data-cms-slot-parent')!
|
||||
const handle = el.getAttribute('data-cms-slot')!
|
||||
const r = rectOf(el)
|
||||
return (
|
||||
<button key={`${parentId}:${handle}`} className="cms-slot" style={{ left: Math.max(4, r.right - 14), top: Math.max(4, r.top + 2) }} aria-label={`Add to ${handle}`} title={`Add to ${handle}`} onClick={() => open(parentId, handle, r.right, r.top)}>
|
||||
<Icon name="add" size={14} />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// --- attribute chips: href / src / alt … of the selected block, editable where they are used ------------
|
||||
|
||||
const ATTR_ICON: Record<string, string> = { href: 'link', src: 'image', alt: 'info', title: 'info' }
|
||||
|
||||
Reference in new issue
Block a user