ITADN

registerBackspaceFix and registerArrowRightFix keyboard bindings fire for all lines, not just their intended targets (IFRAME / P), breaking nested list Backspace behaviour

#49OpenPhanindraNath 创建于 2026-03-09
P
PhanindraNathcommented
### Version `@enzedonline/quill-blot-formatter2` v3.0.3 `quill` v2.0.3 ### Describe the bug `_keyboardBindings()` registers two keyboard bindings and uses `unshift()` to place them at the **front** of Quill's binding queue: 1. **`registerBackspaceFix`** (video) — intended to fire only when the cursor is on an empty `<iframe>` line (Quill bug #4364). 2. **`registerArrowRightFix`** (image) — intended to fire only at the end of a `<p>` line when navigating past an image. Both bindings include a `line.domNode.tagName` constraint (`"IFRAME"` and `"P"` respectively) to limit their scope. However, **Quill's keyboard `listen()` method does not evaluate the `line` property** — it only checks `collapsed`, `empty`, `offset`, `format`, `prefix`, and `suffix`. The `line` constraint is silently ignored. As a result: - `registerBackspaceFix` fires on **every empty line** (not just iframes), because `empty: true` alone is sufficient to match. - `registerArrowRightFix` fires at the **end of every non-empty collapsed line** (not just `<p>` lines). Because both bindings are `unshift()`-ed to the front of the queue, they run **before** Quill's built-in bindings (e.g. `'outdent backspace'`). ### Reproduction (registerBackspaceFix) 1. Set up a Quill 2 editor with `blotFormatter: {}` (default options, so `registerBackspaceFix: true`) 2. Create an ordered list: ``` 1. First item 2. Second item ← Tab to make this ql-indent-1 a. ← Press Enter to create empty sub-item, cursor here ``` 3. Press **Backspace** at the start of the empty sub-item `a.` **Expected:** The empty sub-item outdents to root level (Quill's built-in `'outdent backspace'` runs `format('indent', '-1')`) **Actual:** The empty sub-item is deleted **and** `Second item` becomes nested (`ql-indent-1`) — the previous root item absorbs the nested item's format ### Root cause In `_keyboardBindings()` ([source](https://github.com/enzedonline/quill-blot-formatter2)): ```javascript const t = { key: "Backspace", empty: true, line: { domNode: { tagName: "IFRAME" } }, // ← Quill never checks this handler: (e) => { this.quill.deleteText(e.index - 1, 1, "user"); // deletes previous item's \n } }; this.quill.keyboard.bindings.Backspace.unshift(t); // placed at front, runs first ``` Quill's `listen()` evaluates only: `collapsed`, `empty`, `offset`, `format`, `prefix`, `suffix`. The `line` key is not in the constraint evaluation logic. So `empty: true` alone triggers this handler for **all empty lines**. When backspace is pressed on an empty `ql-indent-1` list item: 1. This handler fires first (it's at position 0 via `unshift`) 2. Calls `deleteText(index - 1, 1)` — deletes the **previous item's** `\n` 3. The two lines merge; the resulting line inherits the empty item's format (`indent: 1`) 4. The previous root-level item becomes nested 5. Quill's `'outdent backspace'` never runs ### Suggested fix Either: **Option A** — guard inside the handler by checking `line.domNode.tagName` explicitly: ```javascript handler: (e, context) => { if (context.line?.domNode?.tagName !== 'IFRAME') return true; // pass to next binding this.quill.deleteText(e.index - 1, 1, "user"); } ``` **Option B** — use a `format` constraint to restrict the binding to lines that actually contain a video blot, so it never matches plain list items: ```javascript { key: "Backspace", empty: true, format: { video: true }, handler: ... } ``` ### Workaround (for users affected right now) Disable the bindings via config until a fix is released: ```typescript blotFormatter: { video: { registerBackspaceFix: false }, image: { registerArrowRightFix: false }, } ```
2 条评论