editor: explain slot refusals on-page; auto-scroll while dragging\n\nDrag state now records the tagged slot under the pointer. When the\ndragged type does not fit it, a badge on the zone names the accepted\ntypes (from the slot schema) and the pointer chip turns refused -\npreviously a refused slot silently fell back to a container drop.\nHolding the pointer near a viewport edge scrolls the page (ramped\nrAF loop, re-targeting after each step) so long pages are droppable\nend to end. Helpers are unit-tested; harness covers refusal badge,\naccept path and the scroll step.\n\n🤖 Generated with Codebuff\nCo-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
1 parent
8e8823421d
commit
40b33f89a4
6 files changed
+154
-12
No files matched your search
+26
-3
@@ -1,5 +1,5 @@
|
||||
import { createElement, useEffect, useRef, useState } from 'react'
|
||||
import type { Block } from '../controller'
|
||||
import type { Block, Controller } from '../controller'
|
||||
import { AttrEditor } from '../editors/AttrEditor'
|
||||
import { InsertPicker } from '../editors/builtin'
|
||||
import { getIn, parsePath } from '../page/infer'
|
||||
@@ -185,8 +185,11 @@ function ActionBar({ block }: { block: Block }) {
|
||||
|
||||
function DropGhost() {
|
||||
const drag = useEditorState((s) => s.drag)!
|
||||
const ctrl = useCtrl()
|
||||
useEditorState((s) => s.tick) // re-measure the slot zone on scroll/resize/mutation
|
||||
const t = drag.target
|
||||
const vertical = !!t && t.line.h > t.line.w // a vertical line = siblings side by side
|
||||
const refused = slotRefusal(ctrl, drag)
|
||||
return (
|
||||
<>
|
||||
{t?.snaps?.map((r, i) => (
|
||||
@@ -201,16 +204,36 @@ function DropGhost() {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{refused && (
|
||||
<div className="cms-slotref" style={{ left: Math.max(4, refused.rect.left), top: Math.max(4, refused.rect.top - 30) }} title={refused.why}>
|
||||
<TypeIcon type={drag.type ?? drag.label} size={14} /> {refused.why}
|
||||
</div>
|
||||
)}
|
||||
{drag.pointer && (
|
||||
<div className={`cms-dragchip${t ? '' : ' is-refused'}`} style={{ left: drag.pointer.x + 14, top: drag.pointer.y + 14 }}>
|
||||
<div className={`cms-dragchip${!t || refused ? ' is-refused' : ''}`} style={{ left: drag.pointer.x + 14, top: drag.pointer.y + 14 }}>
|
||||
<TypeIcon type={drag.type ?? drag.label} size={14} /> {drag.label}
|
||||
{!t ? ' · not here' : drag.source === 'block' ? (drag.nesting ? ' · nesting (release Alt to sort)' : t.snaps ? ' · sorting · Alt to nest' : '') : ''}
|
||||
{!t ? ' · not here' : refused ? ` · not in ${refused.handle}` : drag.source === 'block' ? (drag.nesting ? ' · nesting (release Alt to sort)' : t.snaps ? ' · sorting · Alt to nest' : '') : ''}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/** Why the tagged slot under the pointer refuses the dragged type (null when it fits or no slot is under the pointer). */
|
||||
function slotRefusal(ctrl: Controller, drag: { slot: { parentId: string; handle: string } | null; type?: string; blueprint?: string }): { handle: string; why: string; rect: DOMRect } | null {
|
||||
if (!drag.slot || (!drag.type && !drag.blueprint)) return null
|
||||
const { parentId, handle } = drag.slot
|
||||
const zone = [...ctrl.doc.querySelectorAll<HTMLElement>('[data-cms-slot]')].find(
|
||||
(el) => el.getAttribute('data-cms-slot') === handle && el.getAttribute('data-cms-slot-parent') === parentId,
|
||||
)
|
||||
if (!zone) return null
|
||||
const rect = zone.getBoundingClientRect()
|
||||
if (drag.blueprint) return { handle, why: `${handle}: blueprints can't land in slots`, rect }
|
||||
if (ctrl.acceptsInSlot(parentId, handle, drag.type!)) return null
|
||||
const takes = ctrl.slotsFor(parentId).find((s) => s.name === handle)?.def.to
|
||||
return { handle, why: `${handle} takes: ${takes?.length ? takes.join(', ') : 'any'}`, rect }
|
||||
}
|
||||
|
||||
// --- insertion slots: "+" between a container's children (and inside an empty one) ----------------
|
||||
|
||||
function InsertSlots({ blocks, selected, hovered }: { blocks: Block[]; selected: string | null; hovered: string | null }) {
|
||||
|
||||
Reference in new issue
Block a user