Skip to content

Drop Targets

By default, MosaicJS treats draggable nodes as the only valid places to drop another node — reordering happens among the elements matched by selectors.node. Explicit drop targets relax that: an element matching a separate selector becomes a valid drop destination in its own right, distinct from the nodes it may come to contain.

Configuration

const mosaic = new Mosaic({
root: document.getElementById("board"),
selectors: {
node: ".card",
dropTarget: ".column"
}
});
mosaic.initialize();
<div id="board">
<div class="column">
<div class="card" data-mosaic-id="a">A</div>
</div>
<div class="column"></div>
</div>
  • selectors.node — draggable nodes (unchanged meaning)
  • selectors.dropTarget — elements that are themselves valid drop destinations, in addition to nodes

When selectors.dropTarget is omitted entirely, behavior is identical to the pre-drop-target baseline: draggable nodes are the only valid drop destinations. This is the default and requires no configuration.

Resolution

Both selectors are combined into a single search: MosaicJS walks upward from whatever element is under the pointer (or under the released pointerup event) looking for the nearest match against selectors.node or selectors.dropTarget. The resolved match’s kind — "node" or "dropTarget" — is carried on the internal ResolvedTarget/TargetKind types and reflected on the public-facing hover event payload as targetType.

window.addEventListener("mosaic:hover:enter", (e) => {
const { targetId, targetType, depth } = e.detail;
// targetType is "node" or "dropTarget"
});

A node target reorders among its container’s children, same as the selector-less baseline. A drop-target match reparents the dragged node into it — appended after the drop target’s existing children.

Constraint Enforcement

A drop resolving to an element that matches neither selectors.node nor selectors.dropTarget is rejected by checkConstraints() with reason "invalid-target". This is the same rejection reason used before drop targets existed at all — it simply now also considers the dropTarget selector when deciding what counts as valid.

// checkConstraints(), roughly:
const matchesNode = target.matches(selectors.node);
const matchesDropTarget =
Boolean(selectors.dropTarget) && target.matches(selectors.dropTarget);
if (!matchesNode && !matchesDropTarget) {
return { allowed: false, reason: "invalid-target" };
}

Styling

While a valid drop target is hovered, MosaicJS applies the dropTarget CSS class (default mosaic--drop-target, overridable via MosaicOptions.cssClasses) to the hovered element. The class is removed the moment hover moves elsewhere or the drag ends — see the CSS Class Contract for the full override mechanism.

.mosaic--drop-target {
outline: 2px dashed;
}

Nesting

Drop targets may nest inside other drop targets. Each resolution carries a depth (0 at the root, incrementing per drop-target ancestor) and an ancestors chain, which MosaicOptions.maxNestingDepth can cap — a drop whose resolved depth exceeds the configured limit is rejected with reason "nesting-depth-exceeded". See Constraints Design for the full constraint-evaluation picture, including how nesting and group scoping interact.

  • Drag Lifecycle — where hover/target resolution fits in the overall pointer-event flow
  • Group Containers — a second, independent way to scope where a node may be dropped
  • Constraints Design — the full set of built-in rejection reasons and how user-defined constraints layer on top