// deck.gl-community // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors /** @jsxImportSource preact */ import {Component, type ComponentChildren} from 'preact'; const REPEAT_DELAY_MS = 300; const REPEAT_INTERVAL_MS = 100; export type LongPressButtonProps = { onClick: () => void; children: ComponentChildren; }; export class LongPressButton extends Component { buttonPressTimer: ReturnType | null = null; usingPointerEvents = false; private stopEvent(event: Event) { event.stopPropagation(); if (typeof (event as any).stopImmediatePropagation === 'function') { (event as any).stopImmediatePropagation(); } if (typeof (event as any).preventDefault === 'function') { (event as any).preventDefault(); } } private repeat = () => { if (this.buttonPressTimer) { this.props.onClick(); this.buttonPressTimer = setTimeout(this.repeat, REPEAT_INTERVAL_MS); } }; private startPress(event: Event) { this.stopEvent(event); this.props.onClick(); this.buttonPressTimer = setTimeout(this.repeat, REPEAT_DELAY_MS); } private endPress(event?: Event) { if (event) { this.stopEvent(event); } if (this.buttonPressTimer) { clearTimeout(this.buttonPressTimer); } this.buttonPressTimer = null; } private handlePointerDown = (event: PointerEvent) => { this.usingPointerEvents = true; (event.currentTarget as HTMLElement | null)?.setPointerCapture?.(event.pointerId); this.startPress(event); }; private handlePointerUp = (event: PointerEvent) => { (event.currentTarget as HTMLElement | null)?.releasePointerCapture?.(event.pointerId); this.endPress(event); }; private handlePointerCancel = (event: PointerEvent) => { (event.currentTarget as HTMLElement | null)?.releasePointerCapture?.(event.pointerId); this.endPress(event); }; private handleMouseDown = (event: MouseEvent) => { if (this.usingPointerEvents) { return; } this.startPress(event); document.addEventListener('mouseup', this.handleMouseUp, {once: true}); }; private handleMouseUp = (event: MouseEvent) => { if (this.usingPointerEvents) { return; } this.endPress(event); }; private handleTouchStart = (event: TouchEvent) => { if (this.usingPointerEvents) { return; } this.startPress(event); document.addEventListener('touchend', this.handleTouchEnd, {once: true}); document.addEventListener('touchcancel', this.handleTouchEnd, {once: true}); }; private handleTouchEnd = (event: TouchEvent) => { if (this.usingPointerEvents) { return; } this.endPress(event); }; render() { return (
this.stopEvent(event)} onPointerLeave={this.handlePointerCancel} onPointerOut={this.handlePointerCancel} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} onMouseMove={(event) => this.stopEvent(event)} onTouchStart={this.handleTouchStart} onTouchEnd={this.handleTouchEnd} onTouchMove={(event) => this.stopEvent(event)} onContextMenu={(event) => event.preventDefault()} onWheel={(event) => this.stopEvent(event)} onClick={(event) => this.stopEvent(event)} > {this.props.children}
); } }