Make all functions also accept a Node as scope
diff --git a/packages/dom/src/range/match.ts b/packages/dom/src/range/match.ts
index 061eb75..98cf250 100644
--- a/packages/dom/src/range/match.ts
+++ b/packages/dom/src/range/match.ts
@@ -24,11 +24,11 @@
   Selector,
 } from '@apache-annotator/selector';
 import { ownerDocument } from '../owner-document';
+import { toRange } from '../range-node-conversion';
 import { cartesian } from './cartesian';
 
 /**
- * Find the range(s) corresponding to the given {@link
- * RangeSelector}.
+ * Find the range(s) corresponding to the given {@link RangeSelector}.
  *
  * As a RangeSelector itself nests two further selectors, one needs to pass a
  * `createMatcher` function that will be used to process those nested selectors.
@@ -36,13 +36,12 @@
  * The function is curried, taking first the `createMatcher` function, then the
  * selector, and then the scope.
  *
- * As there may be multiple matches for a given selector, the matcher will
- * return an (async) generator that produces each match in the order they are
- * found in the text. If both its nested selectors produce multiple matches, the
- * RangeSelector matches each possible pair among those in which the order of
- * start and end are respected. *(Note this behaviour is a rather free
- * interpretation — the Web Annotation Data Model spec is silent about multiple
- * matches for RangeSelectors)*
+ * As there may be multiple matches for the start & end selectors, the resulting
+ * matcher will return an (async) iterable, that produces a match for each
+ * possible pair of matches of the nested selectors (except those where its end
+ * would precede its start). *(Note that this behaviour is a rather free
+ * interpretation of the Web Annotation Data Model spec, which is silent about
+ * the possibility of multiple matches for RangeSelectors)*
  *
  * @example
  * By using a matcher for {@link TextQuoteSelector}s, one
@@ -88,15 +87,15 @@
  * ```
  *
  * @param createMatcher - The function used to process nested selectors.
- * @returns A function that, given a RangeSelector, creates a {@link
- * Matcher} function that applies it to a given {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
- * | Range}
+ * @returns A function that, given a RangeSelector `selector`, creates a {@link
+ * Matcher} function that can apply it to a given `scope`.
  *
  * @public
  */
 export function makeCreateRangeSelectorMatcher(
-  createMatcher: <T extends Selector>(selector: T) => Matcher<Range, Range>,
-): (selector: RangeSelector) => Matcher<Range, Range> {
+  createMatcher: <T extends Selector, TMatch extends Node | Range>(selector: T)
+    => Matcher<Node | Range, TMatch>,
+): (selector: RangeSelector) => Matcher<Node | Range, Range> {
   return function createRangeSelectorMatcher(selector) {
     const startMatcher = createMatcher(selector.startSelector);
     const endMatcher = createMatcher(selector.endSelector);
@@ -107,10 +106,14 @@
 
       const pairs = cartesian(startMatches, endMatches);
 
-      for await (const [start, end] of pairs) {
-        const result = ownerDocument(scope).createRange();
+      for await (let [start, end] of pairs) {
+        start = toRange(start);
+        end = toRange(end);
 
+        const result = ownerDocument(scope).createRange();
         result.setStart(start.startContainer, start.startOffset);
+        // Note that a RangeSelector’s match *excludes* the endSelector’s match,
+        // hence we take the end’s startContainer & startOffset.
         result.setEnd(end.startContainer, end.startOffset);
 
         if (!result.collapsed) yield result;
diff --git a/packages/dom/src/text-node-chunker.ts b/packages/dom/src/text-node-chunker.ts
index dc916a6..6981d1c 100644
--- a/packages/dom/src/text-node-chunker.ts
+++ b/packages/dom/src/text-node-chunker.ts
@@ -21,6 +21,7 @@
 import type { Chunk, Chunker, ChunkRange } from '@apache-annotator/selector';
 import { normalizeRange } from './normalize-range';
 import { ownerDocument } from './owner-document';
+import { toRange } from './range-node-conversion';
 
 export interface PartialTextNode extends Chunk<string> {
   readonly node: Text;
@@ -44,6 +45,7 @@
 }
 
 export class TextNodeChunker implements Chunker<PartialTextNode> {
+  private scope: Range;
   private iter: NodeIterator;
 
   get currentChunk(): PartialTextNode {
@@ -116,13 +118,14 @@
   /**
    * @param scope A Range that overlaps with at least one text node.
    */
-  constructor(private scope: Range) {
+  constructor(scope: Node | Range) {
+    this.scope = toRange(scope);
     this.iter = ownerDocument(scope).createNodeIterator(
-      scope.commonAncestorContainer,
+      this.scope.commonAncestorContainer,
       NodeFilter.SHOW_TEXT,
       {
-        acceptNode(node: Text) {
-          return scope.intersectsNode(node)
+        acceptNode: (node: Text) => {
+          return this.scope.intersectsNode(node)
             ? NodeFilter.FILTER_ACCEPT
             : NodeFilter.FILTER_REJECT;
         },
diff --git a/packages/dom/src/text-position/describe.ts b/packages/dom/src/text-position/describe.ts
index bd2f42b..f793129 100644
--- a/packages/dom/src/text-position/describe.ts
+++ b/packages/dom/src/text-position/describe.ts
@@ -21,6 +21,7 @@
 import type { TextPositionSelector } from '@apache-annotator/selector';
 import { describeTextPosition as abstractDescribeTextPosition } from '@apache-annotator/selector';
 import { ownerDocument } from '../owner-document';
+import { toRange } from '../range-node-conversion';
 import { TextNodeChunker } from '../text-node-chunker';
 
 /**
@@ -45,32 +46,24 @@
  * // }
  * ```
  *
- * @param range - The range of characters that the selector should describe
- * @param maybeScope - A {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
- * | Range} that serves as the ‘document’ for purposes of finding occurrences
- * and determining prefix and suffix. Defaults to span the full Document
- * containing the range.
+ * @param range - The {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
+ * | Range} whose text content will be described.
+ * @param scope - A Node or Range that serves as the ‘document’ for purposes of
+ * finding occurrences and determining prefix and suffix. Defaults to span the
+ * full Document that contains the range.
  * @returns The selector describing `range` within `scope`.
  *
  * @public
  */
 export async function describeTextPosition(
   range: Range,
-  maybeScope?: Range,
+  scope?: Node | Range,
 ): Promise<TextPositionSelector> {
-  // Default to search in the whole document.
-  let scope: Range;
-  if (maybeScope !== undefined) {
-    scope = maybeScope;
-  } else {
-    const document = ownerDocument(range);
-    scope = document.createRange();
-    scope.selectNodeContents(document);
-  }
+  scope = toRange(scope ?? ownerDocument(range))
 
   const textChunks = new TextNodeChunker(scope);
   if (textChunks.currentChunk === null)
-    throw new RangeError('Range does not contain any Text nodes.');
+    throw new RangeError('Scope does not contain any Text nodes.');
 
   return await abstractDescribeTextPosition(
     textChunks.rangeToChunkRange(range),
diff --git a/packages/dom/src/text-position/match.ts b/packages/dom/src/text-position/match.ts
index 089def8..ac26caf 100644
--- a/packages/dom/src/text-position/match.ts
+++ b/packages/dom/src/text-position/match.ts
@@ -39,29 +39,22 @@
  * @example
  * ```
  * const selector = { type: 'TextPositionSelector', start: 702, end: 736 };
- *
- * // Search in the whole document.
- * const scope = document.createRange();
- * scope.selectNodeContents(document);
- *
+ * const scope = document.body;
  * const matches = textQuoteSelectorMatcher(selector)(scope);
  * const match = (await matches.next()).value;
- *
  * // ⇒ Range { startContainer: #text, startOffset: 64, endContainer: #text,
  * //   endOffset: 98, … }
  * ```
  *
- * @param selector - The {@link TextPositionSelector}
- * to be anchored
- * @returns A {@link Matcher} function that applies
- * `selector` to a given {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
- * | Range}
+ * @param selector - The {@link TextPositionSelector} to be anchored.
+ * @returns A {@link Matcher} function that applies `selector` within a given
+ * `scope`.
  *
  * @public
  */
 export function createTextPositionSelectorMatcher(
   selector: TextPositionSelector,
-): Matcher<Range, Range> {
+): Matcher<Node | Range, Range> {
   const abstractMatcher = abstractTextPositionSelectorMatcher(selector);
 
   return async function* matchAll(scope) {
diff --git a/packages/dom/src/text-quote/describe.ts b/packages/dom/src/text-quote/describe.ts
index 8eca8bf..d1fa6e3 100644
--- a/packages/dom/src/text-quote/describe.ts
+++ b/packages/dom/src/text-quote/describe.ts
@@ -24,6 +24,7 @@
 } from '@apache-annotator/selector';
 import { describeTextQuote as abstractDescribeTextQuote } from '@apache-annotator/selector';
 import { ownerDocument } from '../owner-document';
+import { toRange } from '../range-node-conversion';
 import { TextNodeChunker } from '../text-node-chunker';
 
 /**
@@ -52,10 +53,9 @@
  *
  * @param range - The {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
  * | Range} whose text content will be described
- * @param maybeScope - A {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
- * | Range} that serves as the ‘document’ for purposes of finding occurrences
- * and determining prefix and suffix. Defaults to span the full Document
- * containing the range.
+ * @param scope - A Node or Range that serves as the ‘document’ for purposes of
+ * finding occurrences and determining prefix and suffix. Defaults to span the
+ * full Document that contains the range.
  * @param options - Options to fine-tune the function’s behaviour.
  * @returns The selector unambiguously describing `range` within `scope`.
  *
@@ -63,24 +63,16 @@
  */
 export async function describeTextQuote(
   range: Range,
-  maybeScope?: Range,
+  scope?: Node | Range,
   options: DescribeTextQuoteOptions = {},
 ): Promise<TextQuoteSelector> {
-  // Default to search in the whole document.
-  let scope: Range;
-  if (maybeScope !== undefined) {
-    scope = maybeScope;
-  } else {
-    const document = ownerDocument(range);
-    scope = document.createRange();
-    scope.selectNodeContents(document);
-  }
+  const scopeAsRange = toRange(scope ?? ownerDocument(range));
 
-  const chunker = new TextNodeChunker(scope);
+  const chunker = new TextNodeChunker(scopeAsRange);
 
   return await abstractDescribeTextQuote(
     chunker.rangeToChunkRange(range),
-    () => new TextNodeChunker(scope),
+    () => new TextNodeChunker(scopeAsRange),
     options,
   );
 }
diff --git a/packages/dom/src/text-quote/match.ts b/packages/dom/src/text-quote/match.ts
index a923586..2b29df6 100644
--- a/packages/dom/src/text-quote/match.ts
+++ b/packages/dom/src/text-quote/match.ts
@@ -44,10 +44,7 @@
  * ```
  * // Find the word ‘banana’.
  * const selector = { type: 'TextQuoteSelector', exact: 'banana' };
- *
- * // Search in the document body.
- * const scope = document.createRange();
- * scope.selectNodeContents(document.body);
+ * const scope = document.body;
  *
  * // Read all matches.
  * const matches = textQuoteSelectorMatcher(selector)(scope);
@@ -58,17 +55,15 @@
  * //   endOffset: 637, … }
  * ```
  *
- * @param selector - The {@link TextQuoteSelector}
- * to be anchored
- * @returns a {@link Matcher} function that applies
- * `selector` to a given {@link https://developer.mozilla.org/en-US/docs/Web/API/Range
- * | Range}
+ * @param selector - The {@link TextQuoteSelector} to be anchored.
+ * @returns A {@link Matcher} function that applies `selector` within a given
+ * `scope`.
  *
  * @public
  */
 export function createTextQuoteSelectorMatcher(
   selector: TextQuoteSelector,
-): Matcher<Range, Range> {
+): Matcher<Node | Range, Range> {
   const abstractMatcher = abstractTextQuoteSelectorMatcher(selector);
 
   return async function* matchAll(scope) {