UNPKG

slickgrid

Version:

A lightning fast JavaScript grid/spreadsheet

263 lines (228 loc) 10.1 kB
/*** * Interactions, add basic behaviors to any element. * All the packages are written in pure vanilla JS and supports both mouse & touch events. * @module Interactions * @namespace Slick */ (function ($) { /** * Draggable Class, enables dragging functionality for any element for example cell & row selections. * Note that mouse/touch start is on the specified container element but all other events are on the document body. * code refs: * https://betterprogramming.pub/perfecting-drag-and-drop-in-pure-vanilla-javascript-a761184b797a * available optional options: * - containerElement: container DOM element, defaults to "document" * - allowDragFrom: when defined, only allow dragging from an element that matches a specific query selector * - onDragInit: drag initialized callback * - onDragStart: drag started callback * - onDrag: drag callback * - onDragEnd: drag ended callback * @param {Object} options * @returns - Draggable instance which includes destroy method * @class Draggable */ function Draggable(options) { let { containerElement, onDragInit, onDragStart, onDrag, onDragEnd } = options; let element, startX, startY, deltaX, deltaY, dragStarted; if (!containerElement) { containerElement = document; } if (!containerElement || typeof containerElement.addEventListener !== 'function') { throw new Error('[Slick.Draggable] You did not provide a valid container html element that will be used for dragging.'); } let originaldd = { dragSource: containerElement, dragHandle: null, }; if (containerElement) { containerElement.addEventListener('mousedown', userPressed); containerElement.addEventListener('touchstart', userPressed); } function executeDragCallbackWhenDefined(callback, e, dd) { if (typeof callback === 'function') { callback(e, dd); } } function destroy() { if (containerElement) { containerElement.removeEventListener('mousedown', userPressed); containerElement.removeEventListener('touchstart', userPressed); } } function userPressed(event) { element = event.target; const targetEvent = event.touches ? event.touches[0] : event; const { target } = targetEvent; if (!options.allowDragFrom || (options.allowDragFrom && element.matches(options.allowDragFrom))) { originaldd.dragHandle = element; const winScrollPos = windowScrollPosition(element); startX = winScrollPos.left + targetEvent.clientX; startY = winScrollPos.top + targetEvent.clientY; deltaX = targetEvent.clientX - targetEvent.clientX; deltaY = targetEvent.clientY - targetEvent.clientY; originaldd = Object.assign(originaldd, { deltaX, deltaY, startX, startY, target }); executeDragCallbackWhenDefined(onDragInit, event, originaldd); document.addEventListener('mousemove', userMoved); document.addEventListener('touchmove', userMoved); document.addEventListener('mouseup', userReleased); document.addEventListener('touchend', userReleased); document.addEventListener('touchcancel', userReleased); } } function userMoved(event) { const targetEvent = event.touches ? event.touches[0] : event; deltaX = targetEvent.clientX - startX; deltaY = targetEvent.clientY - startY; const { target } = targetEvent; if (!dragStarted) { originaldd = Object.assign(originaldd, { deltaX, deltaY, startX, startY, target }); executeDragCallbackWhenDefined(onDragStart, event, originaldd); dragStarted = true; } originaldd = Object.assign(originaldd, { deltaX, deltaY, startX, startY, target }); executeDragCallbackWhenDefined(onDrag, event, originaldd); } function userReleased(event) { const { target } = event; originaldd = Object.assign(originaldd, { target }); executeDragCallbackWhenDefined(onDragEnd, event, originaldd); document.removeEventListener('mousemove', userMoved); document.removeEventListener('touchmove', userMoved); document.removeEventListener('mouseup', userReleased); document.removeEventListener('touchend', userReleased); document.removeEventListener('touchcancel', userReleased); dragStarted = false; } function windowScrollPosition() { return { left: window.pageXOffset || document.documentElement.scrollLeft || 0, top: window.pageYOffset || document.documentElement.scrollTop || 0, }; } return { destroy }; } /** * MouseWheel Class, add mousewheel listeners and calculate delta values and return them in the callback function. * available optional options: * - element: optional DOM element to attach mousewheel values, if undefined we'll attach it to the "window" object * - onMouseWheel: mousewheel callback * @param {Object} options * @returns - MouseWheel instance which includes destroy method * @class MouseWheel */ function MouseWheel(options) { let { element, onMouseWheel } = options; function destroy() { element.removeEventListener('wheel', wheelHandler, false); element.removeEventListener('mousewheel', wheelHandler, false); } function init() { element.addEventListener('wheel', wheelHandler, false); element.addEventListener('mousewheel', wheelHandler, false); } // copy over the same event handler code used in jquery.mousewheel function wheelHandler(event) { const orgEvent = event || window.event let delta = 0, deltaX = 0, deltaY = 0; // Old school scrollwheel delta if (orgEvent.wheelDelta) { delta = orgEvent.wheelDelta / 120; } if (orgEvent.detail) { delta = -orgEvent.detail / 3; } // New school multidimensional scroll (touchpads) deltas deltaY = delta; // Gecko if (orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS) { deltaY = 0; deltaX = -1 * delta; } // WebKit if (orgEvent.wheelDeltaY !== undefined) { deltaY = orgEvent.wheelDeltaY / 120; } if (orgEvent.wheelDeltaX !== undefined) { deltaX = -1 * orgEvent.wheelDeltaX / 120; } if (typeof onMouseWheel === 'function') { onMouseWheel(event, delta, deltaX, deltaY); } } // initialize Slick.MouseWheel by attaching mousewheel event init(); return { destroy }; } /** * Resizable Class, enables resize functionality for any element * Code mostly comes from these 2 resources: * https://spin.atomicobject.com/2019/11/21/creating-a-resizable-html-element/ * https://htmldom.dev/make-a-resizable-element/ * available optional options: * - resizeableElement: resizable DOM element * - resizeableHandleElement: resizable DOM element * - onResizeStart: resize start callback * - onResize: resizing callback * - onResizeEnd: resize ended callback * @param {Object} options * @returns - Resizable instance which includes destroy method * @class Resizable */ function Resizable(options) { const { resizeableElement, resizeableHandleElement, onResizeStart, onResize, onResizeEnd } = options; if (!resizeableHandleElement || typeof resizeableHandleElement.addEventListener !== 'function') { throw new Error('[Slick.Resizable] You did not provide a valid html element that will be used for the handle to resize.'); } function destroy() { if (resizeableHandleElement && typeof resizeableHandleElement.removeEventListener === 'function') { resizeableHandleElement.removeEventListener('mousedown', resizeStartHandler); resizeableHandleElement.removeEventListener('touchstart', resizeStartHandler); } } function executeResizeCallbackWhenDefined(callback, e) { if (typeof callback === 'function') { callback(e, { resizeableElement, resizeableHandleElement }); } } function resizeStartHandler(e) { e.preventDefault(); const event = e.touches ? e.changedTouches[0] : e; executeResizeCallbackWhenDefined(onResizeStart, event); document.addEventListener('mousemove', resizingHandler); document.addEventListener('mouseup', resizeEndHandler); document.addEventListener('touchmove', resizingHandler); document.addEventListener('touchend', resizeEndHandler); } function resizingHandler(e) { if (e.preventDefault && e.type !== 'touchmove') { e.preventDefault(); } const event = e.touches ? e.changedTouches[0] : e; if (typeof onResize === 'function') { onResize(event, { resizeableElement, resizeableHandleElement }); onResize(event, { resizeableElement, resizeableHandleElement }); } } /** Remove all mouse/touch handlers */ function resizeEndHandler(e) { const event = e.touches ? e.changedTouches[0] : e; executeResizeCallbackWhenDefined(onResizeEnd, event); document.removeEventListener('mousemove', resizingHandler); document.removeEventListener('mouseup', resizeEndHandler); document.removeEventListener('touchmove', resizingHandler); document.removeEventListener('touchend', resizeEndHandler); } // add event listeners on the draggable element resizeableHandleElement.addEventListener('mousedown', resizeStartHandler); resizeableHandleElement.addEventListener('touchstart', resizeStartHandler); return { destroy }; } // exports $.extend(true, window, { "Slick": { "Draggable": Draggable, "MouseWheel": MouseWheel, "Resizable": Resizable, } }); })(jQuery);