Clipboard & copy-paste
useClipboardHandlers() returns onCopy/onCut/onPaste — wire them onto your editor's container div and copy/cut/paste behave correctly for selections within a block, across sibling blocks, or the whole document.
const { onCopy, onCut, onPaste } = useClipboardHandlers();
<div ref={containerRef} onCopy={onCopy} onCut={onCut} onPaste={onPaste}>
<BlockChildren parentId="root" />
</div>Same-editor round-trips
Copying writes a custom MIME type (APP_MIME, application/x-block-editor-blocks+json) alongside plain text/html and text/plain — pasting back into this editor (or another noteloom instance) reads that custom type first, so nothing is lost: marks, inline widgets (a select's current value, a date's ISO string), nested blocks, everything round-trips exactly.
ClipboardEvent path these handlers use — it is not guaranteed to survive the async navigator.clipboard.write()/ClipboardItem API. If you add your own toolbar "Copy" button using that API instead of a native copy event, restrict it to text/plain + text/html.Pasting from elsewhere
Pasting HTML from another app (a webpage, Google Docs, Notion, …) walks the DOM (walkDomToBlocks) and asks each registered block/inline type's own fromHTML whether it recognizes a given node, falling through to a plain paragraph of text when nothing claims it. Plain text paste (textToParagraphs) splits on blank lines into one paragraph per line.
Programmatic access
import {
APP_MIME,
serializeBlockRange,
remapSubtreeIds,
deserializeClipboard,
walkDomToBlocks,
textToParagraphs,
copyBlockRangeToClipboard,
} from 'noteloom';serializeBlockRange turns a contiguous run of sibling block ids into the same JSON shape used for the custom MIME type; remapSubtreeIds generates fresh ids for a subtree (used internally when duplicating/pasting so ids never collide); copyBlockRangeToClipboard is the same "copy a block range" primitive the block-range drag-select action menu uses, callable from your own UI too.