"Selection.toString gets you the text. Range gets you the rectangles, the surrounding context, the anchor and focus nodes, and the ability to grow or shrink the selection programmatically. Lesson 3 is the deep cuts ClipDeck needs for highlights, screenshots, and the eventual surrounding-paragraph capture."
Selection vs Range
Two objects, related but distinct:
Selection— what the user currently has highlighted in the document. There is exactly oneSelectionper document, accessed viawindow.getSelection(). Has 0 or 1 ranges in modern browsers (multi-range was a Firefox-only feature).Range— a span between two points in the DOM tree. Independent of user selection; you can create as many as you want and use them for highlighting, scrolling, measuring, or scrolling.
The user's selection is window.getSelection().getRangeAt(0). From that Range you can read everything that matters for ClipDeck's clip metadata.
The Range Fields
range.startContainer/range.endContainer— the DOM nodes where the selection begins and ends. Often Text nodes, sometimes Element nodes.range.startOffset/range.endOffset— character offsets within Text nodes, or child indices within Element nodes.range.commonAncestorContainer— the lowest DOM node that contains both endpoints. Useful for finding the surrounding paragraph or article.range.collapsed— true when start === end (zero-length selection, a cursor blink).range.toString()— the same plain text asselection.toString().range.cloneContents()— returns aDocumentFragmentwith the selected nodes deep-cloned. Use to preserve formatting (links, bold, code spans) in the clip.range.getBoundingClientRect()— the union rect of the selection's visible position. For screenshots and floating UI placement.range.getClientRects()— array of rects, one per line. Useful when the selection spans multiple lines.
Grow to Surrounding Paragraph
The default user selection is whatever they highlighted — often mid-sentence or just a phrase. ClipDeck can offer 'expand to paragraph' as a tweak:
function expandToParagraph(range) {
const ancestor = range.commonAncestorContainer;
const para = (ancestor.nodeType === 1 ? ancestor : ancestor.parentElement)
.closest('p, li, blockquote, h1, h2, h3, h4, h5, h6, div');
if (!para) return range;
const newRange = document.createRange();
newRange.selectNodeContents(para);
return newRange;
}
Useful for 'I want the whole paragraph, not just the sentence I highlighted.' Offer in the preview-and-confirm UI (Lesson 6).
Preserve Formatting in the Clip
ClipDeck v1 stores plain text. If you want to optionally preserve formatting:
const fragment = range.cloneContents();
const tmp = document.createElement('div');
tmp.appendChild(fragment);
const htmlContent = tmp.innerHTML; // sanitized via DOMPurify before storing
Store both text (from toString) and an optional html field. The side panel renders html when present; the clipboard always gets text.
Multi-Line Selection Bounds
When the user highlights several lines of text, getBoundingClientRect() returns one large rectangle wrapping all of them — useful for screenshotting a region but inaccurate for line-by-line UI. getClientRects() returns one rect per line, which is what you want if you're drawing an exact highlight overlay:
const rects = Array.from(range.getClientRects());
rects.forEach((r) => drawHighlightOverlay(r));
Programmatic Selection
To programmatically set or restore a selection (e.g., after the user clicks 'Show me where this clip came from'):
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(rangeIRestored);
Combined with range.startContainer.scrollIntoView({ behavior: 'smooth', block: 'center' }), you can teleport the user to a clip's original location and re-highlight it. ClipDeck v2 feature.
window.getSelection() in an isolated content script does NOT see selections inside open shadow roots — those have their own Selection objects accessible via the shadow root's getSelection(). Closed shadow roots are entirely opaque. For ClipDeck v1, the limitation is rare enough to defer; v2 might walk shadow roots explicitly for sites that lean on web components.