Skip to content

feat(input): list commands, keyboard editing, and empty-item anchors (lists 4/5)#513

Open
wildseansy wants to merge 4 commits into
software-mansion:mainfrom
wildseansy:feat/list-4-commands
Open

feat(input): list commands, keyboard editing, and empty-item anchors (lists 4/5)#513
wildseansy wants to merge 4 commits into
software-mansion:mainfrom
wildseansy:feat/list-4-commands

Conversation

@wildseansy

@wildseansy wildseansy commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

The list stack (per the review ask, now including ordered lists — the shared machinery made them cheap to add):

  1. refactor(input): generalize empty-line anchor machinery beyond headings (lists 1/5) #510 — anchor machinery refactor (no behavior change)
  2. feat(input): list block types + marker rendering (lists 2/5) #511 — list block types + marker rendering
  3. feat(input): parse and serialize lists (lists 3/5) #512 — parser + serializer
  4. feat(input): list commands, keyboard editing, and empty-item anchors (lists 4/5) #513 — commands + keyboard + ZWSP/empty anchors
  5. feat(input): list state, toolbar, docs, and e2e (lists 5/5) #514 — state + toolbar + docs + e2e

GitHub can't base a fork PR on another fork branch, so every PR targets main and the diff shown for PR N is cumulative — this PR's own change is its last commit (each stage is exactly one commit on the previous branch). We'll merge bottom-up; after each merge the next PR's diff collapses to just its own commit. Checkout feat/list-5-state to test the full feature end-to-end.

This PR (last commit) — the tricky one

  • Commands: toggleUnorderedList / toggleOrderedList / indentList / outdentList. Indent preserves the item's list type and clamps to the previous item's depth + 1; outdent at depth 0 removes the marker; toggling one list type over the other replaces it.
  • Enter is handler-driven via continuesOnNewline (default false): a non-empty item continues as a same-type sibling at the same depth, an empty item exits the list. Headings keep reporting false — no per-type branching in the orchestrators.
  • Keyboard: Tab indents (hardware + software), Backspace at an item's start outdents then un-lists at depth 0, including at the document start.
  • Android ZWSP anchor: a LeadingMarginSpan applies neither indent nor marker to an empty paragraph, so an empty list line is anchored by a zero-width space — settled before span stamping (one marker per line), re-entrancy-guarded, stripped the moment the line gains content or stops being a list item, and stripped by the serializer so it never reaches markdown. iOS needs no counterpart: the paragraph style carries the indent and the layout manager draws the empty-line marker explicitly.
  • Renumbering: edits that change the line count re-stamp the whole document so ordered ordinals and the depth clamp propagate past the edited line; same-line typing keeps the scoped per-keystroke path.
  • External plain-text paste parses as markdown (closes the paste gap flagged in feat(input): heading blocks (H1-H6) on the block pipeline #497); Android's "Paste as plain text" stays literal.

🤖 Generated with Claude Code

wildseansy and others added 4 commits July 5, 2026 10:03
Introduces BlockType.ANCHORED (Android) and ENRMBlockTypePersistsWhenEmpty
(iOS) and routes the block store's adjust/normalize passes, the formatter's
zero-length anchor stamping, and the views' orphan-anchor prune through them
instead of heading-only checks. No behavior change — headings are the only
anchored type until the list block type lands on top.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
UNORDERED_LIST_ITEM and ORDERED_LIST_ITEM with 0-based nesting depth in the
range's level payload, joined to the anchored-type set. The store's
list-metadata pass clamps depths to valid ancestry (an item nests at most
one level under the previous adjacent item, so every state serializes) and
computes each ordered item's 1-based ordinal from its position among
adjacent same-depth, same-type siblings. One handler instance per type:
Android renders via LeadingMarginSpans (bullet dot/ring/square by depth;
right-aligned number for ordered) plus a LineHeightSpan for spacing; iOS
reserves the indent column via the paragraph style and draws the glyph or
number in the layout manager, with the ordinal stamped as a text attribute.
listItemSpacing prop spaces items on both platforms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Parsers map list items to their block type with depth derived from AST
UL/OL nesting (never leading spaces); iOS tags MD_BLOCK_LI itself since md4c
emits inner paragraphs only for loose lists, clipping each item to its own
first line. Serializers emit "- " / computed "N. " via the handler's
line prefix, indented a uniform three spaces per depth so ordered and bullet
nesting round-trip in any combination; an empty item's line serializes bare —
a marker-only line re-parses as a setext underline for the previous line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
toggleUnorderedList / toggleOrderedList / indentList / outdentList on both
platforms (indent preserves the item's list type and clamps to the previous
item's depth + 1; outdent at depth 0 removes the marker; toggling one list
type over the other replaces it). Enter behavior is handler-driven via
continuesOnNewline: a non-empty item continues as a same-type sibling at the
same depth, an empty item exits the list. Tab indents and Backspace at an
item's start outdents/un-lists, including at the document start. Android
anchors an empty list line with a ZWSP so its marker draws and the caret
indents (a LeadingMarginSpan cannot indent an empty paragraph); iOS drives
an explicit empty-line marker through the layout manager and keeps typing
attributes block-styled on empty block lines. Edits that change the line
count re-stamp the whole document so ordered renumbering and the depth clamp
propagate past the edited line. External plain-text paste parses as markdown.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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