Add convenience matcher functions to dom package

- createAnySelectorMatcher mimics the signature of existing matcher creators.
- matchSelector makes the common use case easy.
diff --git a/packages/dom/src/index.ts b/packages/dom/src/index.ts
index 6969ea9..5073811 100644
--- a/packages/dom/src/index.ts
+++ b/packages/dom/src/index.ts
@@ -21,6 +21,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
+export * from './match.js';
 export * from './css.js';
 export * from './range/index.js';
 export * from './text-quote/index.js';
diff --git a/packages/dom/src/match.ts b/packages/dom/src/match.ts
new file mode 100644
index 0000000..d0038f8
--- /dev/null
+++ b/packages/dom/src/match.ts
@@ -0,0 +1,101 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-FileCopyrightText: The Apache Software Foundation
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { asArray, OneOrMore } from "@apache-annotator/annotation";
+import type {
+  CssSelector,
+  TextQuoteSelector,
+  TextPositionSelector,
+  RangeSelector,
+  Matcher,
+  Refinable,
+} from "@apache-annotator/selector";
+import { makeRefinable } from "@apache-annotator/selector";
+import { createCssSelectorMatcher } from "./css";
+import { makeCreateRangeSelectorMatcher } from "./range";
+import { createTextPositionSelectorMatcher } from "./text-position";
+import { createTextQuoteSelectorMatcher } from "./text-quote";
+
+export const supportedSelectorTypes = [
+  "CssSelector",
+  "TextQuoteSelector",
+  "TextPositionSelector",
+  "RangeSelector",
+];
+
+export type SupportedSelector = Refinable<
+  | CssSelector
+  | TextQuoteSelector
+  | TextPositionSelector
+  | RangeSelector<SupportedSelector>
+>;
+
+export type DomScope = Node | Range;
+export type DomMatch = Element | Range;
+export type DomMatcher = Matcher<DomScope, DomMatch>;
+
+const createMatcher: (
+  selector: SupportedSelector
+) => DomMatcher = makeRefinable<SupportedSelector, DomScope, DomMatch>(
+  (selector: SupportedSelector) => {
+    if (selector.type === "CssSelector")
+      return createCssSelectorMatcher(selector);
+    if (selector.type === "TextQuoteSelector")
+      return createTextQuoteSelectorMatcher(selector);
+    if (selector.type === "TextPositionSelector")
+      return createTextPositionSelectorMatcher(selector);
+    if (selector.type === "RangeSelector")
+      return makeCreateRangeSelectorMatcher(
+        // @ts-ignore (needless type error; bug in TypeScript?)
+        createMatcher
+      )(selector);
+    throw new Error(`Unsupported selector type: ${(selector as any)?.type}`);
+  }
+);
+
+export function createAnySelectorMatcher(
+  oneOrMoreSelectors: OneOrMore<SupportedSelector>
+): DomMatcher {
+  const selectors = asArray(oneOrMoreSelectors);
+  // Use the first selector we understand. (“Multiple Selectors SHOULD select the same content”)
+  // TODO Take the more precise one; retry with others if the first fails; perhaps combine e.g. Position+Quote for speedup.
+  const selector = selectors.find(
+    (selector) =>
+    selector.type && supportedSelectorTypes.includes(selector.type)
+  );
+  if (!selector) throw new Error(`Unsupported selector type: ${asArray(selectors).map(s => s.type)}`);
+  const matcher = createMatcher(selector as SupportedSelector)
+  return matcher;
+}
+
+export async function matchSelector(
+  selectors: OneOrMore<SupportedSelector>,
+  scope: DomScope = window.document
+) {
+  const matchGenerator = createAnySelectorMatcher(selectors)(scope);
+  const matches: DomMatch[] = [];
+  for await (const match of matchGenerator) {
+    matches.push(match);
+  }
+  return matches;
+}
diff --git a/web/index.js b/web/index.js
index 7917798..bdaa31c 100644
--- a/web/index.js
+++ b/web/index.js
@@ -24,14 +24,11 @@
 /* global info, module, source, target, form */
 
 import {
-  makeCreateRangeSelectorMatcher,
-  createTextQuoteSelectorMatcher,
+  matchSelector,
   describeTextQuote,
-  createTextPositionSelectorMatcher,
   describeTextPosition,
   highlightText,
 } from '@apache-annotator/dom';
-import { makeRefinable } from '@apache-annotator/selector';
 
 const EXAMPLE_SELECTORS = [
   {
@@ -99,29 +96,8 @@
   info.innerText = '';
 }
 
-const createMatcher = makeRefinable((selector) => {
-  const innerCreateMatcher = {
-    TextQuoteSelector: createTextQuoteSelectorMatcher,
-    TextPositionSelector: createTextPositionSelectorMatcher,
-    RangeSelector: makeCreateRangeSelectorMatcher(createMatcher),
-  }[selector.type];
-
-  if (!innerCreateMatcher) {
-    throw new Error(`Unsupported selector type: ${selector.type}`);
-  }
-
-  return innerCreateMatcher(selector);
-});
-
 async function anchor(selector) {
-  const matchAll = createMatcher(selector);
-  const ranges = [];
-
-  // First collect all matches, and only then highlight them; to avoid
-  // modifying the DOM while the matcher is running.
-  for await (const range of matchAll(target)) {
-    ranges.push(range);
-  }
+  const ranges = matchSelector(selector, target);
 
   for (const range of ranges) {
     const removeHighlight = highlightText(range);