diff --git a/docs/reference/slate/node.md b/docs/reference/slate/node.md
index 2e759b270..12714b30e 100644
--- a/docs/reference/slate/node.md
+++ b/docs/reference/slate/node.md
@@ -364,3 +364,10 @@ Check whether the node has a descendant node by `path` or `key`.
`hasNode(key: String) => Boolean`
Check whether a node exists in the tree by `path` or `key`.
+
+### `isNodeInRange`
+
+`isNodeInRange(path: List|Array) => Boolean`
+`isNodeInRange(key: String) => Boolean`
+
+Check whether a node is inside a `range`. This will return true for all [`Text`](./text.md) nodes inside the range and all ancestors of those [`Text`](./text.md) nodes up to this node.
diff --git a/packages/slate/src/interfaces/element.js b/packages/slate/src/interfaces/element.js
index 8d26005d0..e2d76c2fd 100644
--- a/packages/slate/src/interfaces/element.js
+++ b/packages/slate/src/interfaces/element.js
@@ -1636,6 +1636,31 @@ class ElementInterface {
return object === 'inline' && first.object !== 'inline'
}
+ /**
+ * Check whether a descendant node is inside a range. This will return true for all
+ * text nodes inside the range and all ancestors of those text nodes up to this node.
+ *
+ * @param {List|Key} path
+ * @param {Range} range
+ * @return {Node}
+ */
+
+ isNodeInRange(path, range) {
+ this.assertDescendant(path)
+ path = this.resolvePath(path)
+ range = this.resolveRange(range)
+ if (range.isUnset) return false
+
+ const toStart = PathUtils.compare(path, range.start.path)
+ const toEnd =
+ range.start.key === range.end.key
+ ? toStart
+ : PathUtils.compare(path, range.end.path)
+
+ const is = toStart !== -1 && toEnd !== 1
+ return is
+ }
+
/**
* Map all child nodes, updating them in their parents. This method is
* optimized to not return a new node if no changes are made.
diff --git a/packages/slate/test/models/node/is-node-in-range/block-above-using-key.js b/packages/slate/test/models/node/is-node-in-range/block-above-using-key.js
new file mode 100644
index 000000000..b091f70ac
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/block-above-using-key.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange('a', selection)
+}
+
+export const output = false
diff --git a/packages/slate/test/models/node/is-node-in-range/block-above.js b/packages/slate/test/models/node/is-node-in-range/block-above.js
new file mode 100644
index 000000000..9209d16b6
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/block-above.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange([0], selection)
+}
+
+export const output = false
diff --git a/packages/slate/test/models/node/is-node-in-range/block-below-using-key.js b/packages/slate/test/models/node/is-node-in-range/block-below-using-key.js
new file mode 100644
index 000000000..d8cb8decc
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/block-below-using-key.js
@@ -0,0 +1,38 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+ five
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange('k', selection)
+}
+
+export const output = false
diff --git a/packages/slate/test/models/node/is-node-in-range/block-below.js b/packages/slate/test/models/node/is-node-in-range/block-below.js
new file mode 100644
index 000000000..a6759d9b9
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/block-below.js
@@ -0,0 +1,38 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+ five
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange([4], selection)
+}
+
+export const output = false
diff --git a/packages/slate/test/models/node/is-node-in-range/first-block-inside-using-key.js b/packages/slate/test/models/node/is-node-in-range/first-block-inside-using-key.js
new file mode 100644
index 000000000..01f769fa1
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/first-block-inside-using-key.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange('c', selection)
+}
+
+export const output = true
diff --git a/packages/slate/test/models/node/is-node-in-range/first-block-inside.js b/packages/slate/test/models/node/is-node-in-range/first-block-inside.js
new file mode 100644
index 000000000..fa0fde294
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/first-block-inside.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange([1], selection)
+}
+
+export const output = true
diff --git a/packages/slate/test/models/node/is-node-in-range/first-text-inside-using-key.js b/packages/slate/test/models/node/is-node-in-range/first-text-inside-using-key.js
new file mode 100644
index 000000000..4898a0d2d
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/first-text-inside-using-key.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange('d', selection)
+}
+
+export const output = true
diff --git a/packages/slate/test/models/node/is-node-in-range/first-text-inside.js b/packages/slate/test/models/node/is-node-in-range/first-text-inside.js
new file mode 100644
index 000000000..bd15b9418
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/first-text-inside.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange([1, 0], selection)
+}
+
+export const output = true
diff --git a/packages/slate/test/models/node/is-node-in-range/last-block-inside-using-key.js b/packages/slate/test/models/node/is-node-in-range/last-block-inside-using-key.js
new file mode 100644
index 000000000..8b4968820
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/last-block-inside-using-key.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange('g', selection)
+}
+
+export const output = true
diff --git a/packages/slate/test/models/node/is-node-in-range/last-block-inside.js b/packages/slate/test/models/node/is-node-in-range/last-block-inside.js
new file mode 100644
index 000000000..6aecf2897
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/last-block-inside.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange([3], selection)
+}
+
+export const output = true
diff --git a/packages/slate/test/models/node/is-node-in-range/last-text-inside-using-key.js b/packages/slate/test/models/node/is-node-in-range/last-text-inside-using-key.js
new file mode 100644
index 000000000..8796ac2f2
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/last-text-inside-using-key.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange('j', selection)
+}
+
+export const output = true
diff --git a/packages/slate/test/models/node/is-node-in-range/last-text-inside.js b/packages/slate/test/models/node/is-node-in-range/last-text-inside.js
new file mode 100644
index 000000000..0096163ec
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/last-text-inside.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange([3, 1], selection)
+}
+
+export const output = true
diff --git a/packages/slate/test/models/node/is-node-in-range/text-above-using-key.js b/packages/slate/test/models/node/is-node-in-range/text-above-using-key.js
new file mode 100644
index 000000000..736e48a6d
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/text-above-using-key.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange('b', selection)
+}
+
+export const output = false
diff --git a/packages/slate/test/models/node/is-node-in-range/text-above.js b/packages/slate/test/models/node/is-node-in-range/text-above.js
new file mode 100644
index 000000000..f1fc3785a
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/text-above.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange([0, 0], selection)
+}
+
+export const output = false
diff --git a/packages/slate/test/models/node/is-node-in-range/text-below-using-key.js b/packages/slate/test/models/node/is-node-in-range/text-below-using-key.js
new file mode 100644
index 000000000..d56c4330f
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/text-below-using-key.js
@@ -0,0 +1,38 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+ five
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange('l', selection)
+}
+
+export const output = false
diff --git a/packages/slate/test/models/node/is-node-in-range/text-below.js b/packages/slate/test/models/node/is-node-in-range/text-below.js
new file mode 100644
index 000000000..530dee811
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/text-below.js
@@ -0,0 +1,38 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+ five
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange([4, 0], selection)
+}
+
+export const output = false
diff --git a/packages/slate/test/models/node/is-node-in-range/text-in-middle-inside-using-key.js b/packages/slate/test/models/node/is-node-in-range/text-in-middle-inside-using-key.js
new file mode 100644
index 000000000..dea69471f
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/text-in-middle-inside-using-key.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange('f', selection)
+}
+
+export const output = true
diff --git a/packages/slate/test/models/node/is-node-in-range/text-in-middle-inside.js b/packages/slate/test/models/node/is-node-in-range/text-in-middle-inside.js
new file mode 100644
index 000000000..dfde7b784
--- /dev/null
+++ b/packages/slate/test/models/node/is-node-in-range/text-in-middle-inside.js
@@ -0,0 +1,35 @@
+/** @jsx h */
+
+import h from '../../../helpers/h'
+
+export const input = (
+
+
+
+ one
+
+
+
+ two
+
+
+
+
+
+
+
+ three
+
+
+ four
+
+
+
+
+)
+
+export default function({ document, selection }) {
+ return document.isNodeInRange([2, 0], selection)
+}
+
+export const output = true