UNPKG

@eclipse-scout/core

Version:
277 lines (246 loc) 9.8 kB
/* * Copyright (c) 2010, 2023 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'; 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. * * <p> * <b>Important</b> * This function only works in browsers supporting the Event constructor (e.g. KeyboardEvent: https://developer.mozilla.org/de/docs/Web/API/KeyboardEvent/KeyboardEvent). * </p> * * @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. * * <p> * <b>Important</b> * This function only works in browsers supporting the Event constructor (e.g. KeyboardEvent: https://developer.mozilla.org/de/docs/Web/API/KeyboardEvent/KeyboardEvent). * </p> * * @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<Event>) { types = arrays.ensure(types); types.forEach(type => { source.addEventListener(type, event => { if (filter && !filter(event)) { return; } events.propagateEvent(target, event); }); }); }, /** * Adds swipe event listeners to the element given. * * @param $element The element on which the listeners should be attached. * @param id An event listener id used to be registered on the window object. * @param [onDown] Callback to be invoked when the swipe is started (mouse or touch down). * @param [onMove] Callback to be invoked when mouse (or finger if touch) is moved (while being down). * @param [onUp] Callback to be invoked when the swipe is ended (mouse or finger released). */ onSwipe($element: JQuery, id: string, onDown?: (event: SwipeCallbackEvent) => boolean, onMove?: (event: SwipeCallbackEvent) => number, onUp?: (event: SwipeCallbackEvent) => void) { let $window = $element.window(false); let touch = Device.get().supportsOnlyTouch(); $element.on('touchmove', event => event.preventDefault()); // prevent scrolling the background when swiping (iOS) $element.on('remove', event => $window.off('.' + id)); $element.on(events.touchdown(touch), event => { let origPosLeft = $element.position().left; let acceptDown = !onDown || !!onDown({originalEvent: event, originalLeft: origPosLeft, deltaX: 0, newLeft: origPosLeft, direction: 0}); if (!acceptDown) { return; } let dragging = true; let origPageX = events.pageX(event); let curPosLeft = origPosLeft; let direction = 0; $window.on(events.touchmove(touch, id), event => { let pageX = events.pageX(event); let deltaX = pageX - origPageX; let newLeft = origPosLeft + deltaX; if (newLeft !== curPosLeft) { // only update swipe direction if it actually changed direction = Math.sign(newLeft - curPosLeft); } if (onMove) { let l = onMove({originalEvent: event, originalLeft: origPosLeft, deltaX: deltaX, newLeft: newLeft, direction: direction}); curPosLeft = typeof l === 'number' ? l : newLeft; } else { curPosLeft = newLeft; } }); $window.on(events.touchendcancel(touch, id), event => { if (!dragging) { // On iOS touchcancel and touchend are fired right after each other when swiping twice very fast -> Ignore the second event return; } dragging = false; $window.off('.' + id); if (onUp) { onUp({originalEvent: event, originalLeft: origPosLeft, deltaX: curPosLeft - origPosLeft, newLeft: curPosLeft, direction: direction}); } }); }); } }; export interface SwipeCallbackEvent { /** * The original event received from the browser. */ originalEvent: JQuery.TriggeredEvent; /** * The left position of the element at the moment the swipe was started. */ originalLeft: number; /** * The horizontal delta the swipe has already moved (negative values mean to the left of the original left position). */ deltaX: number; /** * The current left position of the element. */ newLeft: number; /** * -1 if the move is to the left, 1 if the move is to the right, 0 or -0 if it is not moved yet */ direction: number; } export interface PropagatedEvent extends Event { sourceEvent: Event; }