Skip to content

Deleting a range is silently ignored when an inline content control sits in a paragraph the range fully covers #4043

Description

@furilo

What happened?

Deleting a selection is silently ignored when an inline content control sits in a paragraph that the selection covers completely, i.e. any paragraph between the first and the last paragraph of the selection. Nothing is removed: Backspace, Delete, Cut and Select All + Backspace all leave the document unchanged. With a real document the editor sometimes reports onException { code: "edit-rejected", error: "This edit couldn't be completed. Adjust the selection and try again." }, but usually there is no feedback at all.

Expected: the range is deleted, including the content controls inside it, as Word does. A deleted contentLocked control should go away with the range; only sdtLocked should protect the control itself.

This is the follow-up I promised in #3974 ("deleting part of the content becomes impossible" after inserting content controls). For us it means users cannot delete several paragraphs of a document that uses template variables.

Select All + Backspace on a body with these paragraphs (★ = paragraph containing one inline content control):

Paragraphs Result
★P1, ★P2 deleted
★P1, Mid, ★P2 deleted
★P1, Last deleted
★P1, ★P2, ★P3 nothing deleted
Lead, ★P1, ★P2 nothing deleted
Lead, ★P1, Last nothing deleted
★P1, Mid, ★P2, Last nothing deleted

The same holds for partial keyboard selections: selecting from the start of ★P1 to the start of the paragraph after ★P2 (Shift+Down twice) and pressing Backspace does nothing, while selecting a single paragraph that contains a control deletes it. The lock mode doesn't matter: contentLocked and unlocked controls behave the same. Paragraphs without controls delete fine, and deleting character by character through a control works.

Steps to reproduce

Plain SuperDoc, no collaboration, no extensions: new SuperDoc({ selector, ui: { toolbar: { container } } }) with the bundled blank document.

  1. Wait for ready and run in the console:
const doc = superdoc.activeEditor.doc;
await doc.replace({
  target: { kind: "story", storyType: "body" },
  type: "markdown",
  value: "Lead\n\nParagraph with a control\n\nLast",
});
const { blocks } = await doc.blocks.list({ includeText: true });
const block = blocks[1];
const point = { kind: "text", blockId: block.nodeId, offset: block.text.length };
await doc.create.contentControl({
  kind: "inline",
  controlType: "text",
  at: { kind: "selection", start: point, end: point },
  tag: "var-1",
  alias: "Variable 1",
  content: " value 1",
  lockMode: "contentLocked",
});
  1. Click in the document, press Cmd/Ctrl+A, then Backspace.
  2. await doc.blocks.list({ includeText: true }) still returns the three paragraphs and await doc.contentControls.list() still returns the control.

Running the same snippet with value: "Paragraph with a control\n\nLast" (the control paragraph is now the first paragraph of the selection) and repeating step 2 empties the document as expected.

SuperDoc version

2.19.0 (@superdoc/docx-engine 0.18.0). Also reproduced on 2.18.0 (docx-engine 0.17.0).

Browser

Chrome

Additional context

Reproduced without collaboration, and also seen in a v2 Hocuspocus room with a real document. I drove the cases with real keyboard input through the Chrome DevTools Protocol (click on a .superdoc-line, then Input.dispatchKeyEvent) against the stock package: no custom extensions, stock CSS, telemetry off.

Related: #3974 (content control operations in collaboration), #4013 (caret placement next to locked inline controls).

Investigation or proposed fix

The failure depends only on where the controls sit relative to the selection's end paragraphs, not on how many there are: a control in the first or last paragraph of the range is fine, a control in any fully covered paragraph blocks the whole deletion. My guess is that the multi-block delete path handles inline SDTs in the edge paragraphs (partial text ranges) but refuses whole interior paragraphs whose runs include an SDT.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: triageNew report awaiting initial assessment.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions