Skip to content

Commit 3b70aaa

Browse files
author
Zaydek Michels-Gualtieri
committed
Partially extracted generic backspace handler
1 parent ab3b43d commit 3b70aaa

File tree

5 files changed

+228
-240
lines changed

5 files changed

+228
-240
lines changed

src/Editor/Editor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const Editor = ({ children }) => {
5050
domSelection.addRange(domRange)
5151
})
5252
}, [state, dispatch]),
53-
[state.shouldRenderElements],
53+
[state.shouldRender],
5454
)
5555

5656
return (

src/Editor/backspace/backspace.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import * as Cursors from "../Cursors"
2+
import * as Iterators from "../Iterators"
3+
import * as Spans from "../Spans"
4+
import dropCursors from "./dropCursors"
5+
import must from "lib/must"
6+
7+
// Returns the next right-to-left (RTL) cursor for a
8+
// boundary.
9+
function nextRTLCursor(elements, { ...rtl }, boundary) {
10+
const y = must(elements.findIndex(each => each.key === rtl.key))
11+
const substr = Spans.textContent(elements[y].props.children).slice(0, rtl.offset)
12+
if (!rtl.offset && y) {
13+
Object.assign(rtl, {
14+
key: elements[y - 1].key,
15+
offset: Spans.textContent(elements[y - 1].props.children).length,
16+
})
17+
return rtl
18+
}
19+
const runes = Iterators.RTL[boundary](substr)
20+
rtl.offset -= runes.length
21+
return rtl
22+
}
23+
24+
// Returns the next left-to-right (LTR) cursor.
25+
function nextLTRCursor(elements, { ...ltr }, boundary) {
26+
const y = must(elements.findIndex(each => each.key === ltr.key))
27+
const substr = Spans.textContent(elements[y].props.children).slice(ltr.offset)
28+
if (ltr.offset === substr.length && y + 1 < elements.length) {
29+
Object.assign(ltr, {
30+
key: elements[y + 1].key,
31+
offset: Spans.textContent(elements[y + 1].props.children).length,
32+
})
33+
return ltr
34+
}
35+
const runes = Iterators.LTR[boundary](substr)
36+
ltr.offset += runes.length
37+
return ltr
38+
}
39+
40+
// Returns the next set of cursors for a direction and a
41+
// boundary.
42+
function nextCursors(elements, cursors, dir, boundary) {
43+
if (!cursors.collapsed) {
44+
return cursors
45+
}
46+
const next = {}
47+
if (dir === "rtl" && dir !== "ltr") {
48+
const rtl = nextRTLCursor(elements, cursors[0], boundary)
49+
Object.assign(next, {
50+
...[rtl, cursors[0]],
51+
collapsed: Cursors.areEqual(rtl, cursors[0]),
52+
})
53+
} else {
54+
const ltr = nextLTRCursor(elements, cursors[0], boundary)
55+
Object.assign(next, {
56+
...[cursors[0], ltr],
57+
collapsed: Cursors.areEqual(cursors[0], ltr),
58+
})
59+
}
60+
return next
61+
}
62+
63+
// Backspace handler. Returns a collapsed set of cursors.
64+
// dir maps to "rtl" or "ltr" and boundary maps to "rune",
65+
// "word", or "line".
66+
function backspace(elements, cursors, dir, boundary) {
67+
const next = nextCursors(elements, cursors, dir, boundary)
68+
dropCursors(elements, next)
69+
return Cursors.collapse(next)
70+
}
71+
72+
export default backspace
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as Cursors from "../Cursors"
2+
import * as Spans from "../Spans"
3+
import must from "lib/must"
4+
5+
// Drops up to n-bytes from an array of spans at an offset.
6+
// Returns the number of bytes dropped.
7+
function dropBytes({ spans, offset, nbytes }) {
8+
// Compute the span and character offsets (offset):
9+
let x = 0
10+
for (; x < spans.length; x++) {
11+
if (offset - spans[x].props.children.length <= 0) {
12+
// No-op
13+
break
14+
}
15+
offset -= spans[x].props.children.length
16+
}
17+
// Drop up to n-bytes:
18+
nbytes = Math.min(nbytes, offset)
19+
spans[x].props.children = (
20+
spans[x].props.children.slice(0, offset - nbytes) +
21+
spans[x].props.children.slice(offset)
22+
)
23+
if (!spans[x].props.children) {
24+
spans.splice(x, 1)
25+
}
26+
Spans.defer(spans)
27+
return nbytes
28+
}
29+
30+
// Drops bytes between cursors.
31+
function dropCursors(elements, cursors) {
32+
let y = must(elements.findIndex(each => each.key === cursors[1].key))
33+
while (!Cursors.areEqual(cursors[0], cursors[1])) {
34+
let nbytes = cursors[1].offset - (cursors[0].key === cursors[1].key && cursors[0].offset)
35+
if (!nbytes && y) {
36+
// Read the current span (for cursors[1].offset):
37+
const textContent = Spans.textContent(elements[y - 1].props.children)
38+
// Push and defer spans:
39+
elements[y - 1].props.children.push(...elements[y].props.children)
40+
elements.splice(y, 1)
41+
Spans.defer(elements[y - 1].props.children)
42+
// Reset cursor[1]:
43+
Object.assign(cursors[1], {
44+
key: elements[y - 1].key,
45+
offset: textContent.length,
46+
})
47+
y--
48+
continue
49+
}
50+
const ref = { spans: elements[y].props.children, offset: cursors[1].offset, nbytes }
51+
nbytes = dropBytes(ref)
52+
cursors[1].offset -= nbytes
53+
}
54+
}
55+
56+
export default dropCursors

src/Editor/backspace/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from "./backspace"

0 commit comments

Comments
 (0)