diff --git a/packages/vanilla/src/index.ts b/packages/vanilla/src/index.ts index 7f55a57..9fc7e34 100644 --- a/packages/vanilla/src/index.ts +++ b/packages/vanilla/src/index.ts @@ -1,7 +1,7 @@ import {EventTarget} from './EventEmitter'; import type {AreaLocation, Coordinates, ScrollEvent, SelectionEvents, SelectionOptions, SelectionStore} from './types'; import {PartialSelectionOptions} from './types'; -import {css, frames, Frames, intersects, isSafariBrowser, isTouchDevice, off, on, selectAll, SelectAllSelectors, simplifyEvent} from './utils'; +import {css, frames, Frames, intersects, isSafariBrowser, isTouchDevice, off, on, selectAll, SelectAllSelectors, simplifyEvent, shouldTrigger} from './utils'; // Re-export types export * from './types'; @@ -69,6 +69,7 @@ export default class SelectionArea extends EventTarget { behaviour: { overlap: 'invert', intersect: 'touch', + triggers: [0], ...opt.behaviour, startThreshold: opt.behaviour?.startThreshold ? typeof opt.behaviour.startThreshold === 'number' ? @@ -154,6 +155,9 @@ export default class SelectionArea extends EventTarget { const {_options} = this; const {document} = this._options; const targetBoundingClientRect = target.getBoundingClientRect(); + + if (evt instanceof MouseEvent && !shouldTrigger(evt, _options.behaviour.triggers)) + return; // Find start-areas and boundaries const startAreas = selectAll(_options.startAreas, _options.document); diff --git a/packages/vanilla/src/types.ts b/packages/vanilla/src/types.ts index c6b6157..76a51ad 100644 --- a/packages/vanilla/src/types.ts +++ b/packages/vanilla/src/types.ts @@ -55,6 +55,25 @@ export interface Coordinates { export type TapMode = 'touch' | 'native'; export type OverlapMode = 'keep' | 'drop' | 'invert'; +// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button#value +export type MouseButton = 0 // Main + | 1 // Auxiliary + | 2 // Secondary + | 3 // Fourth + | 4; // Fifth + +export type Modifier = 'ctrl' + | 'meta' + | 'alt' + | 'shift'; + +export type Trigger = MouseButton | MouseButtonWithModifiers; + +export type MouseButtonWithModifiers = { + button: MouseButton, + modifiers: Modifier[] +}; + export interface Scrolling { speedDivider: number; manualSpeed: number; @@ -77,6 +96,7 @@ export interface Behaviour { startThreshold: number | Coordinates; overlap: OverlapMode; scrolling: Scrolling; + triggers: Trigger[]; } export interface SelectionOptions { diff --git a/packages/vanilla/src/utils/index.ts b/packages/vanilla/src/utils/index.ts index c00b35d..c271d26 100644 --- a/packages/vanilla/src/utils/index.ts +++ b/packages/vanilla/src/utils/index.ts @@ -4,3 +4,4 @@ export * from './intersects'; export * from './selectAll'; export * from './constants'; export * from './frames'; +export * from './shouldTrigger'; diff --git a/packages/vanilla/src/utils/shouldTrigger.ts b/packages/vanilla/src/utils/shouldTrigger.ts new file mode 100644 index 0000000..34db795 --- /dev/null +++ b/packages/vanilla/src/utils/shouldTrigger.ts @@ -0,0 +1,48 @@ +import { Modifier, Trigger } from "../types"; + +type MouseEventModifierProperty = Extract; + +/** + * Determines whether a MouseEvent should execute until completion depending on + * which button and modifier(s) are active for the MouseEvent. + * The Event will execute to completion if ANY of the triggers "matches" + * @param event MouseEvent that should be checked + * @param triggers A list of Triggers that signify that the event should execute until completion + * @returns Whether the MouseEvent should execute until completion + */ +export function shouldTrigger(event: MouseEvent, triggers: Trigger[]): boolean { + for (const trigger of triggers) { + // The trigger requires only a specific button to be pressed + if (typeof trigger === "number") { + if (event.button === trigger) return true; + } + + // The trigger requires a specific button to be pressed AND some modifiers + if (typeof trigger === "object") { + const reqButtonIsPressed = trigger.button === event.button; + const allReqModifiersArePressed = trigger.modifiers.reduce((doActivate, modifier) => { + const prop = modifierToMouseEventProperty(modifier); + + if (prop === null) return false; + + const modifierIsPressed = event[prop]; + + return doActivate && modifierIsPressed; + }, true); + + if (reqButtonIsPressed && allReqModifiersArePressed) return true; + } + } + + // By default we do not process the event + return false; +} + +function modifierToMouseEventProperty(modifier: Modifier): MouseEventModifierProperty | null { + if (modifier === "alt") return "altKey"; + if (modifier === "ctrl") return "ctrlKey"; + if (modifier === "meta") return "metaKey"; + if (modifier === "shift") return "shiftKey"; + + return null; +} \ No newline at end of file