registerBackspaceFix and registerArrowRightFix keyboard bindings fire for all lines, not just their intended targets (IFRAME / P), breaking nested list Backspace behaviour
### 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 条评论