/* * Copyright (c) 2010, 2026 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ import {arrays, Device, objects, Predicate, strings} from '../index'; import $ from 'jquery'; function isTouchEvent(event: JQuery.Event): event is JQuery.TouchEventBase { return event && strings.startsWith(event.type, 'touch'); } export const events = { /** * @returns the x coordinate where the event happened, works for touch events as well. */ pageX(event: JQuery.TriggeredEvent): number { if (!objects.isNullOrUndefined(event.pageX)) { return event.pageX; } // @ts-expect-error return event.originalEvent.touches[0].pageX; }, /** * @returns the y coordinate where the event happened, works for touch events as well. */ pageY(event: JQuery.TriggeredEvent): number { if (!objects.isNullOrUndefined(event.pageY)) { return event.pageY; } // @ts-expect-error return event.originalEvent.touches[0].pageY; }, touchdown(touch: boolean, suffix?: string): string { return events.touchOrMouse(touch, 'touchstart', 'mousedown', suffix); }, touchmove(touch: boolean, suffix?: string): string { return events.touchOrMouse(touch, 'touchmove', 'mousemove', suffix); }, touchendcancel(touch: boolean, suffix?: string): string { return events.touchOrMouse(touch, 'touchend touchcancel', 'mouseup', suffix); }, touchOrMouse(touch: boolean, touchEvent: string, mouseEvent: string, suffix?: string): string { suffix = suffix || ''; if (suffix) { suffix = '.' + suffix; } if (touch) { return touchEvent + suffix; } return mouseEvent + suffix; }, isTouchEvent, fixTouchEvent(event: JQuery.Event) { if (isTouchEvent(event)) { let touches = event.touches || (event.originalEvent ? event.originalEvent.touches : null); let touch = touches ? touches[0] : null; if (touch) { // Touch events may contain fractional values, while mouse events should not // - https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX // - https://www.chromestatus.com/features/6169687914184704 event.pageX = Math.round(touch.pageX) as undefined; event.pageY = Math.round(touch.pageY) as undefined; } } }, /** * @returns an object containing passive: true if the browser supports passive event listeners, otherwise returns false. */ passiveOptions(): boolean | { passive: boolean } { let options: boolean | { passive: boolean } = false; if (Device.get().supportsPassiveEventListener()) { options = { passive: true }; } return options; }, /** * Listens for scroll events and executes startHandler on first event. It then regularly checks for further scroll events and executes endHandler if no event has fired since a certain amount of time and the user has released his finger. * If he does not release his finger the endHandler won't be called even if the pane has stopped scrolling. */ onScrollStartEndDuringTouch($elem: JQuery, startHandler: () => void, endHandler: () => void) { let scrollTimeout; let started = false; let touchend = false; let scrollHandler = event => { // Execute once on first scroll event (and not as soon as user touches the pane because he might not even want to scroll) if (!started) { startHandler(); started = true; } clearTimeout(scrollTimeout); // Check some ms later if a scroll event has occurred in the meantime. // If yes it (probably) is still scrolling. If no it (probably) is not scrolling anymore -> call handler checkLater(); }; let touchEndHandler = event => { touchend = true; checkLater(); }; function checkLater() { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { if (touchend) { // Only stop processing if user released the finger removeHandlers(); endHandler(); } }, 50); } function removeHandlers() { $elem.off('scroll', scrollHandler); $elem.document(false).off('touchend touchcancel', touchEndHandler); } $elem.on('scroll', scrollHandler); // Make sure handler is executed and scroll listener removed if no scroll event occurs $elem.document(false).one('touchend touchcancel', touchEndHandler); }, /** * Forwards the event to the given target by creating a new event with the same data as the old one. * Prevents default action of the original event if preventDefault was called for the forwarded event. * Does not use jQuery to make sure the capture phase is executed as well. * *
* Important * This function only works in browsers supporting the Event constructor (e.g. KeyboardEvent: https://developer.mozilla.org/de/docs/Web/API/KeyboardEvent/KeyboardEvent). *
* * @param target the element which should receive the event * @param event the original event which should be propagated */ propagateEvent(target: EventTarget, event: Event) { if (typeof (Event) !== 'function') { return; } // @ts-expect-error let newEvent: PropagatedEvent = new event.constructor(event.type, event); newEvent.sourceEvent = event; if (!target.dispatchEvent(newEvent)) { event.preventDefault(); } }, /** * Adds an event listener for each given type to the source which propagates the events for that type to the target. * ** Important * This function only works in browsers supporting the Event constructor (e.g. KeyboardEvent: https://developer.mozilla.org/de/docs/Web/API/KeyboardEvent/KeyboardEvent). *
* * @param source the element for which the event listener should be added. * @param target the element which should receive the event. * @param types an array of event types. * @param an optional filter function which can return false if the event should not be propagated. */ addPropagationListener(source: EventTarget, target: EventTarget, types: string[], filter?: Predicate