All files / lib/touch fingers.svelte.ts

58% Statements 29/50
57.14% Branches 12/21
72.22% Functions 13/18
56.25% Lines 27/48

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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143                        12x             2x   2x   1x       7x       7x 7x 2x           2x 2x   1x 1x     1x               5x 5x   3x 3x     2x 2x               3x 3x                                                             3x                                 7x       1x       2x               2x       2x      
import { iterateDOMList } from '../utils.js';
 
export class Fingers {
	/**
	 * Store touches that are occurring
	 * resets when count == 0
	 *
	 * Used to detect pinches etc
	 */
	touches = $state<Array<{ pointerId?: number; clientX: number; clientY: number }>>([]);
 
	get count() {
		return this.touches.length;
	}
 
	/**
	 * Drop the event from {@link Fingers.touch} by matching the pointerId
	 */
	#removeTouch(event: Event & { pointerId: number }) {
		const i = this.touches.findIndex((touch) => touch.pointerId === event.pointerId);
 
		if (i === -1) return;
 
		this.touches.splice(i, 1);
	}
 
	#supportsTouchEvents() {
		return 'ontouchstart' in this.inside;
	}
 
	get #handle() {
		return (event: Event) => {
			if (this.#supportsTouchEvents()) {
				Iif (event instanceof TouchEvent) {
					this.touches = [...iterateDOMList(event.touches)];
				}
 
				// Mouse support too, cuz touch{start,end} only works
				// for touchscreens obviously
				Eif (event instanceof MouseEvent) {
					switch (event.type as `mouse${'up' | 'down'}`) {
						case 'mousedown': {
							this.touches = [event];
							break;
						}
						case 'mouseup': {
							this.touches = [];
						}
					}
				}
			} else {
				// Do dumb manual tracking too cuz TouchEvent is not supported
				// on Safari yet
				// See https://developer.mozilla.org/en-US/docs/Web/API/Element/touchstart_event#browser_compatibility
				Eif (event instanceof PointerEvent) {
					switch (event.type as `pointer${'up' | 'down'}`) {
						case 'pointerdown': {
							this.touches.push(event);
							break;
						}
						case 'pointerup': {
							this.#removeTouch(event);
							break;
						}
					}
				}
			}
		};
	}
 
	constructor(public inside: HTMLElement = document.body) {
		$effect(() => {
			const handler = this.#handle;
 
			if (this.#supportsTouchEvents()) {
				// Touchscreens
				inside.addEventListener('touchstart', handler);
				inside.addEventListener('touchend', handler);
				// Mouses
				inside.addEventListener('mousedown', handler);
				inside.addEventListener('mouseup', handler);
 
				return () => {
					inside.removeEventListener('touchstart', handler);
					inside.removeEventListener('touchend', handler);
					inside.removeEventListener('mousedown', handler);
					inside.removeEventListener('mouseup', handler);
				};
			} else {
				// Drawing tablets
				inside.addEventListener('pointerdown', handler);
				inside.addEventListener('pointerup', handler);
 
				return () => {
					inside.removeEventListener('pointerdown', handler);
					inside.removeEventListener('pointerup', handler);
				};
			}
		});
 
		// Pretty much sure that something
		// has gone wrong in the finger count tracking
		$effect(() => {
			if (this.count < 0 || this.count > 4) {
				console.warn(
					`[touch::Fingers] Something has gone wrong with the finger count tracking! reset to 0 from ${this.count}`
				);
				this.touches = [];
			}
		});
	}
 
	/**
	 * Useful if a handler further down from {@link inside}
	 * will stop propagation of a pointer event.
	 * In that case, {@link Fingers}' own handlers wont be reached
	 * so we have to manually react
	 */
	register(e: Event) {
		this.#handle(e);
	}
 
	reset() {
		this.touches = [];
	}
 
	get any() {
		return this.count > 0;
	}
 
	get none() {
		return this.count <= 0;
	}
 
	get single() {
		return this.count === 1;
	}
 
	get multiple() {
		return this.count > 1;
	}
}