Fix structure tree drag feedback: stable rows, on-row child drops

Rework the site structure tree's drag-and-drop to match Sortable's native
model instead of mid-drag drop zones:

- Drop the dashed empty-list drop zones that appeared on drag start
  (flicker, space reservation, and mis-targeted drops: the last item's
  child zone sat exactly where 'end of parent list' was).
- Every non-empty list (and the root) ends with a non-draggable  footer
  row (vuedraggable #footer slot): click to start a new page at that
  level, and while dragging it is the 'end of this list' drop target.
  It renders last even mid-drag via flex order, since Sortable appends
  end-of-list previews after it in the DOM.
- Dropping ON the lower part of a row makes the page that row's first
  child: the child list's container invisibly overlaps its own row's
  bottom (negative margin + equal padding, no layout effect) and becomes
  hit-testable only while dragging. A row's exposed top strip remains
  the 'sibling before' target. Leaf rows work too, creating a sublist.
- Indentation is structural (each nested list margin-indents itself), so
  a dragged row previews its whole subtree at the target list's depth.
  Rows get a little vertical padding to widen the drop zones.
This commit is contained in:
2026-08-16 18:05:33 +00:00
parent e38063a522
commit 3dc438f8df
5 changed files with 121 additions and 61 deletions
+11 -2
View File
@@ -100,8 +100,17 @@ not for the public pages. See `docs/design-principles.md` for the design.
the banner pen — everything saves immediately as you edit (brand/title
debounced, slug on commit since it renames the path), tree rows navigate
in place without transitions when focused, and the front page is a
root-only row whose empty slug is editable like any other (empty child
lists become drop zones while dragging). The two pens swap the docked
root-only row whose empty slug is editable like any other. Every
non-empty list (and the root) ends with a non-draggable footer row
(vuedraggable `#footer` slot): clicking it starts a new pending page at
that level, and while dragging it is the list's "end of list" drop
target. Dropping ON the lower part of a row moves the page under that
row (the child list's container invisibly overlaps its own row's bottom
via negative margin — Sortable inserts it as the first child natively),
while a row's exposed top edge inserts a sibling before it. Row
indentation is structural (each nested list margin-indents itself), so a
dragged row previews its whole subtree at the target list's depth. The
two pens swap the docked
panel for the other editor; clicking the open editor's own pen closes it. Normally dynamic-imported onto the content page by
pagerite.js when a 🖊️ edit link is clicked (the link carries
`data-editor-src`/`data-editor-css`/`data-editor-mode`); the `/_/admin`