All files / app/component/map single-click.ts

100% Statements 28/28
100% Branches 11/11
100% Functions 7/7
100% Lines 27/27

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63    5x     5x                                     6x 6x 6x 6x 6x   6x 4x 4x 4x   6x 9x 2x 2x   7x 9x 3x 3x   9x   6x 6x 6x 2x 2x 2x               9x    
import type { Map, MapMouseEvent } from 'maplibre-gl';
 
/**
 * Time to wait for a second click before handling a click on the map.
 */
export const DOUBLE_CLICK_DELAY = 300;
 
export interface SingleClickOptions {
  delay?: number;
  /**
   * Called right away on a click, so it can be shown before it is handled.
   */
  preview?: (e: MapMouseEvent) => void;
  /**
   * Called when a previewed click turns out to be part of a double click.
   */
  cancel?: () => void;
}
 
/**
 * Handle clicks on the map that are not part of a double click, so double
 * clicking still zooms in. Returns a function to remove the handler.
 */
export function onSingleClick(map: Map, handler: (e: MapMouseEvent) => void, options: SingleClickOptions = {}): () => void {
  const delay = options.delay ?? DOUBLE_CLICK_DELAY;
  const pending = new Set<ReturnType<typeof setTimeout>>();
  const clear = () => {
    pending.forEach(clearTimeout);
    pending.clear();
  };
  const cancel = () => {
    const previewed = pending.size > 0;
    clear();
    if (previewed) options.cancel?.();
  };
  const click = (e: MapMouseEvent) => {
    if (isRepeatClick(e.originalEvent)) {
      cancel();
      return;
    }
    options.preview?.(e);
    const timer = setTimeout(() => {
      pending.delete(timer);
      handler(e);
    }, delay);
    pending.add(timer);
  };
  map.on('click', click);
  map.on('dblclick', cancel);
  return () => {
    clear();
    map.off('click', click);
    map.off('dblclick', cancel);
  };
}
 
/**
 * The second or later click of a double click.
 */
export function isRepeatClick(e?: Event) {
  return !!e && 'detail' in e && e.type === 'click' && (e as MouseEvent).detail > 1;
}