Compare commits
1 commit
main
...
github-pag
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d52773c5f |
4 changed files with 431 additions and 50 deletions
|
|
@ -32,7 +32,8 @@ import {
|
||||||
swipListener,
|
swipListener,
|
||||||
wheelListener,
|
wheelListener,
|
||||||
keyListener,
|
keyListener,
|
||||||
selectListener
|
selectListener,
|
||||||
|
touchListener
|
||||||
} from '../utils/listeners/listener'
|
} from '../utils/listeners/listener'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -214,45 +215,54 @@ const registerEvents = () => {
|
||||||
if (rendition) {
|
if (rendition) {
|
||||||
rendition.on('rendered', (section, iframe) => {
|
rendition.on('rendered', (section, iframe) => {
|
||||||
// Focus the iframe
|
// Focus the iframe
|
||||||
iframe?.iframe?.contentWindow.focus()
|
iframe?.iframe?.contentWindow.focus();
|
||||||
|
|
||||||
// Register interaction listeners
|
// Register interaction listeners
|
||||||
if (!epubOptions?.flow?.includes('scrolled')) {
|
if (!epubOptions?.flow?.includes('scrolled')) {
|
||||||
wheelListener(iframe.document, flipPage)
|
wheelListener(iframe.document, flipPage);
|
||||||
}
|
}
|
||||||
swipListener(iframe.document, flipPage)
|
|
||||||
keyListener(iframe.document, flipPage)
|
|
||||||
|
|
||||||
// Register your custom selection listener if toggleBubble is provided
|
// Use the unified touch handler instead of separate listeners
|
||||||
if (toggleBubble) {
|
touchListener(
|
||||||
selectListener(iframe.document, rendition, toggleBubble)
|
iframe.document,
|
||||||
} else if (handleTextSelected) {
|
rendition,
|
||||||
// If no toggleBubble but handleTextSelected exists, use the built-in selection event
|
flipPage, // Navigation function
|
||||||
rendition.on('selected', handleTextSelected)
|
toggleBubble || handleTextSelected // Selection function
|
||||||
}
|
);
|
||||||
|
|
||||||
|
// Register regular click listener for non-touch interactions
|
||||||
|
clickListener(iframe.document, rendition, flipPage);
|
||||||
|
|
||||||
|
// If no unified handler, fall back to separate listeners
|
||||||
|
// swipListener(iframe.document, flipPage);
|
||||||
|
// if (toggleBubble) {
|
||||||
|
// selectListener(iframe.document, rendition, toggleBubble);
|
||||||
|
// } else if (handleTextSelected) {
|
||||||
|
// rendition.on('selected', handleTextSelected);
|
||||||
|
// }
|
||||||
|
|
||||||
// Mark first content as displayed for location restoration
|
// Mark first content as displayed for location restoration
|
||||||
if (!loadingState.firstContentDisplayed) {
|
if (!loadingState.firstContentDisplayed) {
|
||||||
loadingState.firstContentDisplayed = true
|
loadingState.firstContentDisplayed = true;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// Location change tracking
|
// Other event handlers remain the same
|
||||||
rendition.on('locationChanged', onLocationChange)
|
rendition.on('locationChanged', onLocationChange);
|
||||||
|
|
||||||
rendition.on('relocated', (location: any) => {
|
rendition.on('relocated', (location) => {
|
||||||
// console.log('Book relocated to:', location)
|
console.log('Book relocated to:', location);
|
||||||
})
|
});
|
||||||
|
|
||||||
rendition.on('displayError', (err: any) => {
|
rendition.on('displayError', (err) => {
|
||||||
console.error('Display error:', err)
|
console.error('Display error:', err);
|
||||||
})
|
});
|
||||||
|
|
||||||
if (handleKeyPress) {
|
if (handleKeyPress) {
|
||||||
rendition.on('keypress', handleKeyPress)
|
rendition.on('keypress', handleKeyPress);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Function to apply saved location
|
// Function to apply saved location
|
||||||
const applyPendingLocation = () => {
|
const applyPendingLocation = () => {
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ import keyListener from './key'
|
||||||
import wheelListener from './wheel'
|
import wheelListener from './wheel'
|
||||||
import swipListener from './swip'
|
import swipListener from './swip'
|
||||||
import selectListener from './select'
|
import selectListener from './select'
|
||||||
|
import touchListener from './touch'
|
||||||
export {
|
export {
|
||||||
clickListener,
|
clickListener,
|
||||||
keyListener,
|
keyListener,
|
||||||
wheelListener,
|
wheelListener,
|
||||||
swipListener,
|
swipListener,
|
||||||
selectListener,
|
selectListener,
|
||||||
|
touchListener
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,58 @@
|
||||||
|
// src/utils/listeners/selectListener.js
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Enhanced selection listener with touch support
|
||||||
* @param {Document} document - The document object to add event
|
* @param {Document} document - The document object to add event
|
||||||
* @param {Object} rendition - The EPUBJS rendition
|
* @param {Object} rendition - The EPUBJS rendition
|
||||||
* @param {Function} fb - The listener function
|
* @param {Function} fn - The listener function
|
||||||
*/
|
*/
|
||||||
export default function selectListener(document, rendition, fn) {
|
export default function selectListener(document, rendition, fn) {
|
||||||
document.addEventListener('mousedown', () => {
|
// Track touch selection state
|
||||||
document.getSelection().removeAllRanges()
|
let touchStarted = false;
|
||||||
fn('cleared')
|
let touchSelection = false;
|
||||||
})
|
let touchStartTime = 0;
|
||||||
|
const LONG_PRESS_DURATION = 500; // ms
|
||||||
|
const TOUCH_MOVE_THRESHOLD = 10; // pixels
|
||||||
|
let startX = 0;
|
||||||
|
let startY = 0;
|
||||||
|
|
||||||
document.addEventListener('mouseup', (e) => {
|
// Check if we're on a mobile device
|
||||||
if (e.ignore) return
|
const isMobileDevice = () => {
|
||||||
e.ignore = true
|
return (('ontouchstart' in window) ||
|
||||||
|
(navigator.maxTouchPoints > 0) ||
|
||||||
|
(navigator.msMaxTouchPoints > 0));
|
||||||
|
};
|
||||||
|
|
||||||
const selection = document.getSelection()
|
// Check if we're on iOS
|
||||||
const text = selection.toString()
|
const isIOS = () => {
|
||||||
|
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||||
|
};
|
||||||
|
|
||||||
if (text === '') return
|
// Clear selection handler
|
||||||
const range = selection.getRangeAt(0)
|
const clearSelection = () => {
|
||||||
|
if (document.getSelection) {
|
||||||
|
document.getSelection().removeAllRanges();
|
||||||
|
}
|
||||||
|
fn('cleared');
|
||||||
|
};
|
||||||
|
|
||||||
const [contents] = rendition.getContents()
|
// Process text selection
|
||||||
const cfiRange = contents.cfiFromRange(range)
|
const processSelection = (e) => {
|
||||||
|
if (e.ignore) return;
|
||||||
|
e.ignore = true;
|
||||||
|
|
||||||
const SelectionReact = range.getBoundingClientRect()
|
const selection = document.getSelection();
|
||||||
const viewRect = rendition.manager.container.getBoundingClientRect()
|
const text = selection.toString();
|
||||||
|
|
||||||
|
if (text === '') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
const [contents] = rendition.getContents();
|
||||||
|
const cfiRange = contents.cfiFromRange(range);
|
||||||
|
|
||||||
|
const SelectionReact = range.getBoundingClientRect();
|
||||||
|
const viewRect = rendition.manager.container.getBoundingClientRect();
|
||||||
|
|
||||||
let react = {
|
let react = {
|
||||||
left: `${
|
left: `${
|
||||||
|
|
@ -32,7 +61,99 @@ export default function selectListener(document, rendition, fn) {
|
||||||
top: `${viewRect.y + SelectionReact.y}px`,
|
top: `${viewRect.y + SelectionReact.y}px`,
|
||||||
width: `${SelectionReact.width}px`,
|
width: `${SelectionReact.width}px`,
|
||||||
height: `${SelectionReact.height}px`,
|
height: `${SelectionReact.height}px`,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn('selected', react, text, cfiRange);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing selection:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mouse events for desktop
|
||||||
|
document.addEventListener('mousedown', clearSelection);
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', (e) => {
|
||||||
|
processSelection(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only add touch events if we're on a touch device
|
||||||
|
if (isMobileDevice()) {
|
||||||
|
// Touch events for mobile
|
||||||
|
document.addEventListener('touchstart', (e) => {
|
||||||
|
touchStarted = true;
|
||||||
|
touchSelection = false;
|
||||||
|
touchStartTime = Date.now();
|
||||||
|
|
||||||
|
// Store start position for determining if it's a tap or selection attempt
|
||||||
|
if (e.touches && e.touches[0]) {
|
||||||
|
startX = e.touches[0].clientX;
|
||||||
|
startY = e.touches[0].clientY;
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
document.addEventListener('touchmove', (e) => {
|
||||||
|
if (!touchStarted) return;
|
||||||
|
|
||||||
|
// Check if this might be a selection attempt (not just a tap)
|
||||||
|
if (e.touches && e.touches[0]) {
|
||||||
|
const moveX = Math.abs(e.touches[0].clientX - startX);
|
||||||
|
const moveY = Math.abs(e.touches[0].clientY - startY);
|
||||||
|
|
||||||
|
// If user has moved finger more than threshold, might be trying to select
|
||||||
|
if (moveX > TOUCH_MOVE_THRESHOLD || moveY > TOUCH_MOVE_THRESHOLD) {
|
||||||
|
touchSelection = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
document.addEventListener('touchend', (e) => {
|
||||||
|
if (!touchStarted) return;
|
||||||
|
touchStarted = false;
|
||||||
|
|
||||||
|
const touchDuration = Date.now() - touchStartTime;
|
||||||
|
|
||||||
|
// Check if it was a long press or a deliberate selection movement
|
||||||
|
if (touchDuration > LONG_PRESS_DURATION || touchSelection) {
|
||||||
|
// Delay processing to allow the browser to complete the selection
|
||||||
|
setTimeout(() => {
|
||||||
|
processSelection(e);
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
// It was a quick tap, clear selection
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle context menu long press on mobile
|
||||||
|
document.addEventListener('contextmenu', (e) => {
|
||||||
|
// This event fires on long-press on many mobile browsers
|
||||||
|
// Prevent the default context menu
|
||||||
|
if (isMobileDevice()) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delay to let the browser create the selection
|
||||||
|
setTimeout(() => {
|
||||||
|
processSelection(e);
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add specific handling for iOS
|
||||||
|
if (isIOS()) {
|
||||||
|
// iOS sometimes needs additional help with text selection
|
||||||
|
document.addEventListener('selectionchange', () => {
|
||||||
|
// Only process if we're in a touch selection operation
|
||||||
|
if (touchSelection || (Date.now() - touchStartTime) > LONG_PRESS_DURATION) {
|
||||||
|
// Delay to let selection complete
|
||||||
|
setTimeout(() => {
|
||||||
|
const selection = document.getSelection();
|
||||||
|
if (selection && selection.toString().length > 0) {
|
||||||
|
const e = { ignore: false };
|
||||||
|
processSelection(e);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn('selected', react, text, cfiRange)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
248
src/utils/listeners/touch.ts
Normal file
248
src/utils/listeners/touch.ts
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
/**
|
||||||
|
* Unified touch handler that supports both swipe navigation and text selection
|
||||||
|
* @param {Document} document - The document to add event listeners to
|
||||||
|
* @param {Object} rendition - The EPUBJS rendition
|
||||||
|
* @param {function} navigationFn - Function to call for navigation (swipe)
|
||||||
|
* @param {function} selectionFn - Function to call for text selection
|
||||||
|
*/
|
||||||
|
|
||||||
|
type epubEvent = TouchEvent & { ignore?: boolean };
|
||||||
|
type Direction = 'next' | 'prev' | 'up' | 'down';
|
||||||
|
|
||||||
|
export default function touchHandler(
|
||||||
|
document: Document,
|
||||||
|
rendition: any,
|
||||||
|
navigationFn: (direction: Direction) => void,
|
||||||
|
selectionFn: (type: string, rect?: any, text?: string, cfiRange?: string) => void
|
||||||
|
) {
|
||||||
|
// State tracking
|
||||||
|
let touchState = {
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
startTime: 0,
|
||||||
|
isLongPress: false,
|
||||||
|
isSelectionAttempt: false,
|
||||||
|
lastTapTime: 0,
|
||||||
|
moveCount: 0,
|
||||||
|
lastMoveX: 0,
|
||||||
|
lastMoveY: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constants for gesture detection
|
||||||
|
const SWIPE = {
|
||||||
|
threshold: 50, // Min distance for swipe
|
||||||
|
restraint: 200, // Max perpendicular movement
|
||||||
|
allowedTime: 500 // Max time for swipe
|
||||||
|
};
|
||||||
|
|
||||||
|
const SELECTION = {
|
||||||
|
longPressDuration: 500, // Time for long press
|
||||||
|
moveThreshold: 10, // Movement to trigger selection mode
|
||||||
|
selectionDelay: 50 // Delay to process selection after touch
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear selection
|
||||||
|
const clearSelection = () => {
|
||||||
|
if (document.getSelection) {
|
||||||
|
document.getSelection()?.removeAllRanges();
|
||||||
|
}
|
||||||
|
selectionFn('cleared');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Process text selection
|
||||||
|
const processSelection = () => {
|
||||||
|
const selection = document.getSelection();
|
||||||
|
if (!selection) return;
|
||||||
|
|
||||||
|
const text = selection.toString();
|
||||||
|
if (text === '') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
const [contents] = rendition.getContents();
|
||||||
|
const cfiRange = contents.cfiFromRange(range);
|
||||||
|
|
||||||
|
const selectionRect = range.getBoundingClientRect();
|
||||||
|
const viewRect = rendition.manager.container.getBoundingClientRect();
|
||||||
|
|
||||||
|
const rect = {
|
||||||
|
left: `${
|
||||||
|
viewRect.x + selectionRect.x - (rendition.manager.scrollLeft || 0)
|
||||||
|
}px`,
|
||||||
|
top: `${viewRect.y + selectionRect.y}px`,
|
||||||
|
width: `${selectionRect.width}px`,
|
||||||
|
height: `${selectionRect.height}px`,
|
||||||
|
};
|
||||||
|
|
||||||
|
selectionFn('selected', rect, text, cfiRange);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing selection:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if device is iOS
|
||||||
|
const isIOS = () => {
|
||||||
|
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Device-specific adjustments
|
||||||
|
if (isIOS()) {
|
||||||
|
SELECTION.longPressDuration = 400;
|
||||||
|
SELECTION.selectionDelay = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch start event
|
||||||
|
document.addEventListener('touchstart', (e: epubEvent) => {
|
||||||
|
if (e.ignore) return;
|
||||||
|
e.ignore = true;
|
||||||
|
|
||||||
|
// Get initial touch position
|
||||||
|
if (e.touches && e.touches[0]) {
|
||||||
|
touchState.startX = e.touches[0].pageX;
|
||||||
|
touchState.startY = e.touches[0].pageY;
|
||||||
|
touchState.lastMoveX = touchState.startX;
|
||||||
|
touchState.lastMoveY = touchState.startY;
|
||||||
|
touchState.startTime = Date.now();
|
||||||
|
touchState.isLongPress = false;
|
||||||
|
touchState.isSelectionAttempt = false;
|
||||||
|
touchState.moveCount = 0;
|
||||||
|
|
||||||
|
// Handle long press with timeout
|
||||||
|
const longPressTimeout = setTimeout(() => {
|
||||||
|
// If finger hasn't moved much, trigger long press
|
||||||
|
const currentX = touchState.lastMoveX;
|
||||||
|
const currentY = touchState.lastMoveY;
|
||||||
|
const moveX = Math.abs(currentX - touchState.startX);
|
||||||
|
const moveY = Math.abs(currentY - touchState.startY);
|
||||||
|
|
||||||
|
if (moveX < SELECTION.moveThreshold && moveY < SELECTION.moveThreshold) {
|
||||||
|
touchState.isLongPress = true;
|
||||||
|
}
|
||||||
|
}, SELECTION.longPressDuration);
|
||||||
|
|
||||||
|
// Store the timeout in a property to clear it if needed
|
||||||
|
(document as any)._longPressTimeout = longPressTimeout;
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Touch move event
|
||||||
|
document.addEventListener('touchmove', (e: epubEvent) => {
|
||||||
|
if (e.ignore) return;
|
||||||
|
e.ignore = true;
|
||||||
|
|
||||||
|
if (e.touches && e.touches[0]) {
|
||||||
|
touchState.lastMoveX = e.touches[0].pageX;
|
||||||
|
touchState.lastMoveY = e.touches[0].pageY;
|
||||||
|
touchState.moveCount++;
|
||||||
|
|
||||||
|
// Calculate movement
|
||||||
|
const moveX = Math.abs(touchState.lastMoveX - touchState.startX);
|
||||||
|
const moveY = Math.abs(touchState.lastMoveY - touchState.startY);
|
||||||
|
|
||||||
|
// Determine if this might be a selection attempt
|
||||||
|
if (moveX > SELECTION.moveThreshold || moveY > SELECTION.moveThreshold) {
|
||||||
|
// Clear long press timeout if significant movement
|
||||||
|
if ((document as any)._longPressTimeout) {
|
||||||
|
clearTimeout((document as any)._longPressTimeout);
|
||||||
|
(document as any)._longPressTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If moved a lot horizontally relative to vertically, might be a swipe
|
||||||
|
// If moved a lot vertically or in a pattern, might be selection
|
||||||
|
if ((moveY > moveX * 1.5) || touchState.moveCount > 5) {
|
||||||
|
touchState.isSelectionAttempt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Touch end event
|
||||||
|
document.addEventListener('touchend', (e: epubEvent) => {
|
||||||
|
if (e.ignore) return;
|
||||||
|
e.ignore = true;
|
||||||
|
|
||||||
|
// Clear long press timeout if it's still active
|
||||||
|
if ((document as any)._longPressTimeout) {
|
||||||
|
clearTimeout((document as any)._longPressTimeout);
|
||||||
|
(document as any)._longPressTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate touch stats
|
||||||
|
const touchEndTime = Date.now();
|
||||||
|
const elapsedTime = touchEndTime - touchState.startTime;
|
||||||
|
|
||||||
|
// Get distance traveled
|
||||||
|
const distX = e.changedTouches[0].pageX - touchState.startX;
|
||||||
|
const distY = e.changedTouches[0].pageY - touchState.startY;
|
||||||
|
|
||||||
|
// Handle selection if it was a long press or selection attempt
|
||||||
|
if (touchState.isLongPress || touchState.isSelectionAttempt) {
|
||||||
|
// Delay slightly to let the browser finish selection
|
||||||
|
setTimeout(() => {
|
||||||
|
processSelection();
|
||||||
|
}, SELECTION.selectionDelay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle swipe if it wasn't a selection attempt
|
||||||
|
if (elapsedTime <= SWIPE.allowedTime) {
|
||||||
|
// Horizontal swipe
|
||||||
|
if (Math.abs(distX) >= SWIPE.threshold && Math.abs(distY) <= SWIPE.restraint) {
|
||||||
|
// If dist traveled is negative, it indicates right swipe
|
||||||
|
navigationFn(distX < 0 ? 'next' : 'prev');
|
||||||
|
}
|
||||||
|
// Vertical swipe
|
||||||
|
else if (Math.abs(distY) >= SWIPE.threshold && Math.abs(distX) <= SWIPE.restraint) {
|
||||||
|
// If dist traveled is negative, it indicates up swipe
|
||||||
|
navigationFn(distY < 0 ? 'up' : 'down');
|
||||||
|
}
|
||||||
|
// Tap - convert to click for regular interaction
|
||||||
|
else {
|
||||||
|
clearSelection();
|
||||||
|
|
||||||
|
// Convert tap to click
|
||||||
|
document.dispatchEvent(
|
||||||
|
new MouseEvent('click', {
|
||||||
|
clientX: touchState.startX,
|
||||||
|
clientY: touchState.startY,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { passive: false });
|
||||||
|
|
||||||
|
// Handle context menu (long press on many mobile browsers)
|
||||||
|
document.addEventListener('contextmenu', (e) => {
|
||||||
|
// Only handle on mobile devices
|
||||||
|
if ('ontouchstart' in window) {
|
||||||
|
e.preventDefault();
|
||||||
|
// Delay to let the browser create the selection
|
||||||
|
setTimeout(() => {
|
||||||
|
processSelection();
|
||||||
|
}, SELECTION.selectionDelay);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add specific handling for iOS
|
||||||
|
if (isIOS()) {
|
||||||
|
// iOS sometimes needs additional help with text selection
|
||||||
|
document.addEventListener('selectionchange', () => {
|
||||||
|
// Only process if it might be from a touch operation
|
||||||
|
if (touchState.isLongPress || touchState.isSelectionAttempt) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const selection = document.getSelection();
|
||||||
|
if (selection && selection.toString().length > 0) {
|
||||||
|
processSelection();
|
||||||
|
}
|
||||||
|
}, SELECTION.selectionDelay);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular mouse events for desktop
|
||||||
|
document.addEventListener('mousedown', clearSelection);
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', () => {
|
||||||
|
processSelection();
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue