plitejs/dom is the DOM bridge for Plite editors. It owns DOM point/range
conversion, selection conversion, clipboard formatting, hotkey helpers,
contenteditable helpers, and DOM coverage metadata used by framework runtimes.
React apps normally use these APIs through plitejs/react.
Framework runtimes usually call the DOM bridge through installed editor APIs.
editor.api.dom.focus();
editor.api.dom.clipboard.insertTextData(dataTransfer);editor.api.dom.
Use direct public imports from plitejs/dom for framework integration
code that needs DOM bridge helpers without React.
import { DOMCoverage, Hotkeys, isDOMNode } from "plitejs/dom";import { DOMCoverage, Hotkeys, isDOMNode } from "plitejs/dom";dom() installs the DOM bridge plugin on a core editor.
DOMResolutionError is the thrown error type for failed assert-style DOM
resolution.
DOMCoverage.create(editor) creates a view-local session for hidden, external,
and virtualized same-root coverage metadata.
Hotkeys, isHotkey, and Key cover keyboard matching and keyboard names.
TRIPLE_CLICK is the click count Plite uses for block-level triple-click
handling.
Framework adapters can use DOMEditor, getEditorDOMRoot,
createCompiledHotkeyMatcher, isGeckoDOMHost, and
usesAppleDOMHotkeys for explicit host integration without importing private
runtime modules.
DOM utilities include shadow-aware traversal, active element/window/selection lookups, DOM type guards, DOM point normalization, plain-text paste detection, tracked mutation detection, and DOM order checks:
import {
closestShadowAware,
containsShadowAware,
getActiveElement,
getDefaultView,
getSelection,
hasShadowRoot,
isAfter,
isBefore,
isDOMElement,
isDOMNode,
isDOMSelection,
isDOMText,
isPlainTextOnlyPaste,
isTrackedMutation,
normalizeDOMPoint,
} from "plitejs/dom";import {
closestShadowAware,
containsShadowAware,
getActiveElement,
getDefaultView,
getSelection,
hasShadowRoot,
isAfter,
isBefore,
isDOMElement,
isDOMNode,
isDOMSelection,
isDOMText,
isPlainTextOnlyPaste,
isTrackedMutation,
normalizeDOMPoint,
} from "plitejs/dom";Write the native selection with writeDOMSelection(tree, points), where
tree is the editable element's root node, and pass null to clear it.
Integration code must not change the browser Selection directly. When an
editor shows several views of one document, Plite compares each later native
selection with its last write to tell its own writes from the user's, so a
direct change looks like the user's selection.
readDOMSelectionRange(selection) returns a copy of the selection's first
range, so changing the copy leaves the selection alone.
import { writeDOMSelection } from "plitejs/dom";
const tree = editable.getRootNode() as Document | ShadowRoot;
writeDOMSelection(tree, { anchor: [text, 0], focus: [text, 4] });
writeDOMSelection(tree, null);import { writeDOMSelection } from "plitejs/dom";
const tree = editable.getRootNode() as Document | ShadowRoot;
writeDOMSelection(tree, { anchor: [text, 0], focus: [text, 4] });
writeDOMSelection(tree, null);Plite node marker helpers include getElements, getNodeDataAttributeKeys,
keyToDataAttribute, isEditor, isElement, isLeaf, isNode, isString,
isText, and isVoid.
Text-diff utilities include string diff normalization, merging, point/range projection, target-range calculation, and state verification:
import {
applyStringDiff,
mergeStringDiffs,
normalizePoint,
normalizeRange,
normalizeStringDiff,
targetRange,
verifyDiffState,
} from "plitejs/dom";import {
applyStringDiff,
mergeStringDiffs,
normalizePoint,
normalizeRange,
normalizeStringDiff,
targetRange,
verifyDiffState,
} from "plitejs/dom";CAN_USE_DOM guards SSR-only initialization. Mounted DOM adapters resolve
browser behavior from their own root.
dataTransferFormats installs schema-aware browser transfer representations as
one named plugin. Each DataTransferFormat decodes or encodes one MIME type.
Callbacks receive an immutable editor-state view; decode callbacks also receive
an immutable snapshot of the incoming types, strings, and files.
import { ContentSlice, definePlugin, property, schema, target } from "plitejs";
import { dataTransferFormats } from "plitejs/dom";
const Bold = schema.textProperty("bold", property.boolean(), {
target: target.type("paragraph"),
});
const DataAttribute = schema.elementProperty(
schema.key.prefix("data_"),
property.string(),
{ target: target.group("block") }
);
const editor = useEditor({
plugins: [
definePlugin("app-schema", {
schema: { properties: [Bold, DataAttribute] },
}),
dataTransferFormats("app-html-transfer", [
{
mimeType: "text/html",
key: "app-html",
claims: [{ kind: "element", type: "paragraph" }, Bold, DataAttribute],
decode: ({ data }) =>
ContentSlice.closed([
{ type: "paragraph", children: [{ text: data }] },
]),
encode: ({ slice }) => JSON.stringify(slice),
},
]),
],
initialValue,
});import { ContentSlice, definePlugin, property, schema, target } from "plitejs";
import { dataTransferFormats } from "plitejs/dom";
const Bold = schema.textProperty("bold", property.boolean(), {
target: target.type("paragraph"),
});
const DataAttribute = schema.elementProperty(
schema.key.prefix("data_"),
property.string(),
{ target: target.group("block") }
);
const editor = useEditor({
plugins: [
definePlugin
decode returns a ContentSlice, or null to delegate the payload to the
next format. encode returns a string, or null to leave its MIME type to the
next encoder. accept returns false to skip the format before decoding.
Every declaration needs decode, encode, or both.
Decoded slices keep their open edge depths and detached roots. Use
ContentSlice.closed(content) for a closed fragment or
ContentSlice.fromJSON(payload) for validated open data. Insertion fits the
slice against the actual target. An unfit result delegates without publishing a
partial write.
Decode callbacks receive { data, mimeType, report, snapshot, state }. Encode
callbacks receive { mimeType, slice, state }. They do not receive the editor,
a live DataTransfer, a fitter, or a transaction. Formats run in reverse
configuration order, so the last configured format gets the first opportunity
for its MIME type. The built-in plain-text decoder always runs last.
Ownership claims compile against the candidate editor schema before a plugin
revision publishes. { kind: "schema" } owns the complete schema. Element
claims use stable element types. Property claims reuse the same
schema.elementProperty(...) or schema.textProperty(...) declaration in the
schema contribution and format. Matching is structural, not object-identity
based. The compiler derives one stable identity from the declaration's
placement, key, and target, so equal keys with disjoint targets remain
independently claimable. Key-only claims and raw compiled IDs are not accepted.
Duplicate keys, unknown declarations, and overlapping claims in the same MIME
type and direction reject the complete configuration revision. Decode and
encode ownership are independent.
Return null for foreign or malformed input; clipboard data is untrusted and
bad input is not a bug. Exceptions from accept, decode, or encode go to
the editor's lifecycleErrorSink with source: "data-transfer-format", the
format key, mimeType, pluginName, and phase ("accept", "decode", or
"encode"); negotiation then continues with the next format, so a failing
format cannot block paste or copy. A decoded value that is not a valid
ContentSlice, or an encoded value that is neither a string nor null, is
reported the same way. During accept or decode, call
report({ impact: "lossy", message: "Unsupported content was omitted." })
to report conversion loss. impact accepts "lossless" or "lossy".
Mounted paste delivers conversion diagnostics through Editable.onPasteResult.
Add a mapping or a format for content the current formats cannot represent.
Framework clipboard writers call
writeDataTransferFragment(editor, data, slice) to run registered encoders into
a setData-compatible sink. Each MIME type is written once: the first encoder
that returns a string wins. The function returns the written MIME types.
Applications normally use
editor.api.dom.clipboard.writeSelection(dataTransfer), which writes the native
Plite payload and registered MIME representations together.
DOM bridge types include DOMApi, DOMClipboardApi,
DOMClipboardInsertDataHandler, DOMEditorOptions, ScrollIntoViewOptions,
and ScrollIntoViewTarget.
DOM coverage types include DOMCoverageSession, DOMCoverageBoundary,
DOMCoverageSelectionPolicy, and DOMCoveragePointResult. Use these when a
framework layer needs to describe hidden or virtualized same-root content.
DOM primitive type names include DOMNode, DOMElement, DOMText, DOMPoint,
DOMRange, DOMStaticRange, and DOMSelection.
DataTransfer types include DataTransferFormat, DataTransferDecodeContext,
DataTransferEncodeContext, DataTransferSnapshot,
DataTransferSchemaClaim, and DataTransferFormatPhase.
Hotkey and diff helper types include HotkeySpec, HotkeyPlatform,
HotkeyMatchOptions, KeyboardEventLike, StringDiff, and TextDiff.
DOM coverage boundaries model same-root content whose DOM is hidden or virtualized. They keep selection, copy, find, and Plite-to-DOM conversion tied to explicit policies instead of assuming every document node is mounted.
React applications configure coverage through
slots.contentBoundary or
slots.externalText. Each Editable owns its
session and releases it on unmount.
Framework integrations create one session per mounted view:
import { DOMCoverage } from "plitejs/dom";
const coverage = DOMCoverage.create(editor);
const dispose = coverage.registerBoundary(boundary);
dispose();
coverage.destroy();import { DOMCoverage } from "plitejs/dom";
const coverage = DOMCoverage.create(editor);
const dispose = coverage.registerBoundary(boundary);
dispose();
coverage.destroy();Call query, conversion, and materialization methods on that session.
getBoundaryForPoint(point) returns the boundary that hides a point, or
null. getBoundariesForRange(range) returns every boundary a range crosses,
or an empty array. Read DOM coordinates through editor.api.dom.resolveDOMPoint
and resolveDOMRange on the same editor. The dom() plugin installs them. They
return null when an endpoint has no DOM or while the page's text differs from
the editor's. A range whose endpoints are mounted still resolves when it crosses
hidden content, so check its boundaries first:
const boundaries = coverage.getBoundariesForRange(range);
const domRange =
boundaries.length > 0 ? null : editor.api.dom.resolveDOMRange(range);const boundaries = coverage.getBoundariesForRange(range);
const domRange =
boundaries.length > 0 ? null : editor.api.dom.resolveDOMRange(range);Do not share registrations between views of the same editor. A registration's disposer cannot remove a later replacement with the same boundary id.
The /internal package subpath is reserved for sibling Plite packages in this
repo. Applications, plugin libraries, and framework adapters outside this
workspace should use the root plitejs/dom export.