Skip to content

Extend drag-and-drop to nested blocks via the content model#19

Merged
namedgraph merged 5 commits into
masterfrom
feature/nested-drag-drop
Jul 14, 2026
Merged

Extend drag-and-drop to nested blocks via the content model#19
namedgraph merged 5 commits into
masterfrom
feature/nested-drag-drop

Conversation

@namedgraph

Copy link
Copy Markdown
Member

Summary

Every %block element now carries its own drag handle wherever the XHTML content model places blocks — a region, a cm:flow container (li/dd/td/th/div/figure/figcaption) or a blockquote — so nested blocks (including an RDFa object div in a list item) can be dragged out of, into and between containers.

  • One predicate (local:draggable-block) routes every chrome/drag decision: a real %block — never a run wrapper, hr or ephemera. Chrome injection rides the existing local:init-block recursion (a one-gate change); local:ensure-chrome in after-mutation converges handles after every gesture, so mutation sites never thread their own injection.
  • Content-model-validated drop resolver: the target is the innermost draggable block under the pointer whose container accepts the dragged kind (local:deepest-block-at + local:legal-drop-level climbing to the nearest legal level — the region accepts any block, so the climb terminates and an illegal drop is clamped, never created). Gutter/gap drops stay top-level, so the left gutter lifts a nested block all the way out.
  • Cells take "into" drops: nothing can legally drop before/after a td/th, so a cell's pixels away from any block it holds mean into it — the text host becomes a container per the insert-block-at-caret flow doctrine, marked by an inset drop-into box. A list item deliberately stays a clamp to around-the-list (its pixels compete with reordering).
  • Origin repair at commit: prune-husks (an emptied quote goes — B7 doctrine) then collapse-container (a flow container that lost its last block reverts to a text host).
  • Undo fidelity: transient drag state is stripped before the snapshot; local:tidy-class kills the latent class="" residue classList.remove left behind (previously poisoned undo baselines after any completed drag); restore-snapshot strips every handle and re-converges, generalizing the table-fostering fix to nested tables.
  • Stacked handles per nesting level, each at its block's own gutter indent; the editing view indents blockquote content (plus a Markdown-style quote bar) so quote-child handles don't coincide with the quote's.
  • Demo copy, help modal, README and CLAUDE.md updated to the new doctrine.

Test plan

  • make test — 49 headless PASS, incl. new canonical (nested-chrome strip at any depth) and lint (nested object-div shapes clean) fixtures
  • make test-browser — all 15 suites green, incl. new dragnest.mjs (real-mouse gestures against tests/fixture-dragnest.html: nested handles at init, lift-out of a list item — incl. the RDFa object-div case with triples intact — drop-into a container and INTO a text-only cell, before/after precision on a cell's blocks, legality clamp, region clamp, origin collapse, split convergence, canonical cleanliness; every scenario ends in invariants + single-undo byte-exact restore)
  • Invariant I3 updated ("chrome top-level only" → "chrome only on draggable blocks") across the suites' invariant copies; old-contract assertions (noChromeOnP, noChromeInCell, …) deliberately flipped
  • Manual: demo page — quote's inner paragraph drags out of the quote; blocks drop into the "Everyday sentences" table's cells

🤖 Generated with Claude Code

namedgraph and others added 5 commits July 14, 2026 22:44
Every %block element now carries its own drag handle wherever the model
places blocks (a region, a cm:flow container or a blockquote) - lifting a
block out of a list item, quote or cell, or dropping one in, resolves to
the innermost legal level (local:deepest-block-at + local:legal-drop-level;
illegal drops clamp to the nearest legal ancestor, the gutter lifts out to
top level). The vacated origin is repaired at commit (prune-husks +
collapse-container), local:ensure-chrome converges handles after every
mutation and undo restore, and transient drag state is stripped before the
undo snapshot (local:tidy-class kills class="" residue). Handles stack per
nesting level; the editing view indents blockquote content so quote-child
handles get their own gutter column.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nothing can legally drop before or after a td/th, so a cell's pixels are
unambiguous: pointing into a cell away from any block it holds now drops
the dragged block INTO it (the text host becomes a container - runs
wrapped, block appended - per the insert-block-at-caret flow doctrine),
marked by an inset drop-into box instead of a before/after line. Hovering
a block inside a cell still places before/after that block. List items
deliberately keep clamping to around-the-list: their pixels compete with
reordering, and Tab/indent already move content into items.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The help modal's Reordering section, the demo's Try-it list (the quote's
inner paragraph now demonstrates a nested handle; cells take drops) and
the README feature list all described the old top-level-only handles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The editable-region convention bullet still said init injects chrome
top-level only; also name the into-cell drop mode in the dragnest suite
description.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A normalized blockquote > p reads as a plain paragraph without a visual
clue; the Markdown/Bootstrap-style left border (with muted text) makes
the container visible, and the slightly wider padding keeps the inner
blocks' drag handles clear of the bar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@namedgraph namedgraph merged commit 8044281 into master Jul 14, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant