0 directories, 46 files

util

Home / tinai / util
/**
 * Document-level pinch gesture that steps the application UI zoom through
 * the configured zoom levels. Gestures starting on form fields, or inside
 * overlays that handle their own pinch (diagram overlay), are ignored.
 */

import { is_diagram_overlay_open } from './diagram-overlay.js';

/** Ratio of pinch distance change required to move one zoom level. */
const STEP_RATIO = 1.15;

/** Elements whose pinch gestures are not captured by the app zoom. */
const EXCLUDED_SELECTOR = 'input, textarea, select, [contenteditable]';

let app = null;
let active = false;
let last_step_dist = 0;

/**
 * Initializes the document-level pinch-zoom gesture.
 * @param {App} app_instance - The main application instance.
 */
export function init_pinch_zoom(app_instance) {
	app = app_instance;
	document.addEventListener('touchstart', on_touch_start);
	document.addEventListener('touchmove', on_touch_move, { passive: false });
	document.addEventListener('touchend', on_touch_end);
	document.addEventListener('touchcancel', on_touch_end);
}

/**
 * Starts tracking a two-finger gesture unless it begins on an excluded element.
 * @param {TouchEvent} e - The touch event.
 */
function on_touch_start(e) {
	if (e.touches.length !== 2 || is_excluded(e.target)) {
		active = false;
		return;
	}
	active = true;
	last_step_dist = get_touch_distance(e.touches);
}

/**
 * Steps the UI zoom one level in or out each time the pinch distance crosses
 * the step ratio, and prevents the browser's native page zoom while active.
 * @param {TouchEvent} e - The touch event.
 */
function on_touch_move(e) {
	if (!active || e.touches.length !== 2) {
		active = false;
		return;
	}
	e.preventDefault();
	const dist = get_touch_distance(e.touches);
	const zoom = app.options_manager.get_ui_zoom();
	if (dist > last_step_dist * STEP_RATIO) {
		step_zoom(1, zoom, dist);
	} else if (dist < last_step_dist / STEP_RATIO) {
		step_zoom(-1, zoom, dist);
	}
}

/**
 * Ends the gesture when fewer than two touches remain.
 * @param {TouchEvent} e - The touch event.
 */
function on_touch_end(e) {
	if (e.touches.length < 2) {
		active = false;
	}
}

/**
 * Moves one zoom level in the given direction and re-anchors the step distance.
 * @param {number} direction - 1 to zoom in, -1 to zoom out.
 * @param {number} zoom - Current zoom percentage.
 * @param {number} dist - Current pinch distance in pixels.
 */
function step_zoom(direction, zoom, dist) {
	const levels = [70, 90, 100, 110, 125, 150, 175, 200];
	const index = levels.indexOf(zoom) + direction;
	if (index < 0 || index >= levels.length) return;
	app.options_manager.set_ui_zoom(levels[index]);
	last_step_dist = dist;
}

/**
 * Checks whether the gesture target handles its own pinch behavior.
 * @param {EventTarget} target - The event target.
 * @returns {boolean} True if the gesture should be ignored.
 */
function is_excluded(target) {
	return is_diagram_overlay_open() || !!(target instanceof Element && target.closest(EXCLUDED_SELECTOR));
}

/**
 * Computes the distance between the first two touches.
 * @param {TouchList} touches - The active touches.
 * @returns {number} Distance in pixels.
 */
function get_touch_distance(touches) {
	return Math.hypot(touches[0].clientX - touches[1].clientX, touches[0].clientY - touches[1].clientY);
}
🌐
pinch-zoom.js ×
Type: Web, text/x-java
3.21 Kilobytes
Last Modified 2026-11-04 09:48:28
⬇ Download File