🚧 These docs are a work in progress — content is incomplete and may change without notice.
Skip to content

Pick and highlight ​

Use onGraphicPick(handler, options?) to handle clicks on committed graphics without knowing their internal layer. event.id is the graphic ID; the event also includes the selected feature, pixel, coordinate, and original browser event. If the user picks another graphic during an edit, TacticalDraw commits the current edit before calling your handler.

ts
const unsubscribe = td.onGraphicPick((event) => {
  const graphic = graphics.find((candidate) => candidate.id === event.id);
  if (graphic) void startEdit(graphic);
});

The returned unsubscribe() is idempotent. Pass { signal } to unsubscribe automatically when an AbortSignal fires.

For your own layers, use onPick(layerId, handler, options?). Adapter authors can recover a graphic ID with tryControlMeasureIdFromFeature(feature).

The Quick start shows a complete pick-to-edit loop.

Selection highlights ​

Some apps keep a selection without an open edit, for example a locked graphic or a row hovered in a side list. Use setHighlightedGraphics(ids) to outline those graphics:

ts
td.setHighlightedGraphics(["ambush-1", "unit-7"]);

// Clear every highlight.
td.setHighlightedGraphics([]);

The call replaces the whole highlight set, like render() replaces the graphics list. Each highlighted graphic gets a dashed, non-interactive outline that follows it through pans, zooms, and later render() calls. Highlights do not start a session or change picking.

Unknown IDs are ignored. A graphic that is currently being edited shows its edit handles instead of the highlight. Calls made in quick succession, such as while the pointer moves down a list, are combined into one update per frame.

Style the outline with the highlightOutline slot of interactionStyle on the constructor:

ts
const td = new TacticalDraw(adapter, {
  interactionStyle: {
    highlightOutline: { strokeColor: "#f59e0b", strokeWidth: 2, strokeDash: [6, 4] },
  },
});

Composing with host click handling ​

If your map clears selection on click, first check whether TacticalDraw owns the clicked pixel with ownsInteractionAt(pixel, options?):

ts
map.on("click", (e) => {
  if (td.ownsInteractionAt(e.pixel, { originalEvent: e.originalEvent })) return;
  if (td.consumedPointerCycle()) return;
  clearHostSelection();
});

The check covers committed graphics, the active preview, edit handles, and the estimated bounds of rotated or offset labels. It excludes the guide layer, which is not interactive.

Some edit presses change what is under the pointer. For example, Alt + click on a vertex deletes it, and so does a tap in delete mode. Alt + click on an option handle moves its grip to the reset position, and a label drag moves the label. The edit keeps the click that ends such a press, but the pixel can be empty by the time your handler checks it. consumedPointerCycle() returns true for that click, whether your handler runs before or after TacticalDraw's own, and stays true until the next pointer press, so only read it inside a click handler.

Pass the host event as originalEvent to use the default hit tolerance: 4 pixels for mouse or pen, and 12 for touch. Edit handles use the edit's own press tolerance instead (12 pixels for mouse or pen, 22 for touch), so a pixel the edit would press as a handle is owned by handles. Set tolerance to override both. The check is synchronous and reads the adapter cache directly, so it works from any host click handler without an onPick subscription.