Engineering Journal
Table Formatter
Table Formatter

Selection Handles That Never Touch the DOM They Decorate

2026-07-16

TLDR

Injecting selection handle nodes into <td> elements pollutes table HTML trees, breaking row counters and export logic. Mounting a single position: fixed overlay to document.body with pointer-events: none on its interior allows clicks to pass through to cells while handles capture gestures cleanly.
Selection Chrome ApproachDOM Tree ImpactExport CleanlinessHit-Test Resolution
Injected Handle SpansPollutes <td> inner HTMLFails (Requires manual stripping)DOM node event target
Fixed Body OverlayZero DOM Tree Contamination100% Clean Data Exportsdocument.elementFromPoint()

Appending selection elements directly inside table cells corrupts data exports

Adding drag handles, resize nodes, or fill boxes to editable table cells seems straightforward. However, appending helper <span> elements directly into <td> nodes pollutes table DOM structures.

As a result, every exporter, undo snapshot engine, and spatial grid mapper must continuously filter out helper chrome elements to prevent invalid HTML output.


Isolated body overlays with pointer-event layering shield table DOMs

We separated selection handles from table content by rendering a single body-level overlay positioned over target bounding boxes:

/ Overlay container passes clicks through to underlying cells /
.sel-overlay {
    position: fixed;
    pointer-events: none;
}
/ Handles intercept drag interactions /
.sel-overlay .fill-handle,
.sel-overlay .edge-node {
    pointer-events: auto;
}

During drag operations, the overlay switches temporarily to pointer-events: none, allowing document.elementFromPoint() to resolve underlying cell elements directly:

// Resolve target cell beneath active cursor during drag operations
export function getCellUnderCursor(clientX, clientY, activeTableEl) {
  // Temporary pointer-events bypass enables direct browser hit testing
  const targetEl = document.elementFromPoint(clientX, clientY);
  const cell = targetEl ? targetEl.closest('td, th') : null;
  return cell && cell.closest('table') === activeTableEl ? cell : null;
}

Positioning calculations update on window scroll and resize events, keeping the DOM tree clean and export-ready.

Layered View:
[Cursor] --(Click/Drag)
   |
   v
+-----------------------------------------------------------+
| Overlay Layer (position: fixed, pointer-events: none)     |
|  - Fill Handle / Edge Node (pointer-events: auto) <=======|=== Stops cursor events here (gestures captured)
+-----------------------------------------------------------+
   | (passes through rest of overlay)
   v
+-----------------------------------------------------------+
| Table Layer (Clean DOM)                                   |
|  - Cell 1 | Cell 2 | Cell 3                               |
+-----------------------------------------------------------+
Rule of thumb: Render selection chrome in an isolated body-level overlay using CSS pointer-events layering.
Read this post in the full Engineering Journal →