From 7813ead0a688960dc069de9d61dfd4cd820905b9 Mon Sep 17 00:00:00 2001 From: Piotr Michalak Date: Fri, 10 Nov 2023 12:19:30 +0100 Subject: [PATCH 1/4] Add option to define a selection of MouseButtons to ignore --- packages/vanilla/src/index.ts | 6 +++++- packages/vanilla/src/types.ts | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/vanilla/src/index.ts b/packages/vanilla/src/index.ts index 7f55a57..07015ce 100644 --- a/packages/vanilla/src/index.ts +++ b/packages/vanilla/src/index.ts @@ -1,5 +1,5 @@ import {EventTarget} from './EventEmitter'; -import type {AreaLocation, Coordinates, ScrollEvent, SelectionEvents, SelectionOptions, SelectionStore} from './types'; +import type { AreaLocation, Coordinates, MouseButton, ScrollEvent, SelectionEvents, SelectionOptions, SelectionStore } from './types'; import {PartialSelectionOptions} from './types'; import {css, frames, Frames, intersects, isSafariBrowser, isTouchDevice, off, on, selectAll, SelectAllSelectors, simplifyEvent} from './utils'; @@ -69,6 +69,7 @@ export default class SelectionArea extends EventTarget { behaviour: { overlap: 'invert', intersect: 'touch', + ignoredButtons: [], ...opt.behaviour, startThreshold: opt.behaviour?.startThreshold ? typeof opt.behaviour.startThreshold === 'number' ? @@ -150,6 +151,9 @@ export default class SelectionArea extends EventTarget { } _onTapStart(evt: MouseEvent | TouchEvent, silent = false): void { + if (evt instanceof MouseEvent && this._options.behaviour.ignoredButtons?.includes(evt.button as MouseButton)) + return; + const {x, y, target} = simplifyEvent(evt); const {_options} = this; const {document} = this._options; diff --git a/packages/vanilla/src/types.ts b/packages/vanilla/src/types.ts index c6b6157..fe9da61 100644 --- a/packages/vanilla/src/types.ts +++ b/packages/vanilla/src/types.ts @@ -55,6 +55,13 @@ 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 interface Scrolling { speedDivider: number; manualSpeed: number; @@ -77,6 +84,7 @@ export interface Behaviour { startThreshold: number | Coordinates; overlap: OverlapMode; scrolling: Scrolling; + ignoredButtons?: MouseButton[]; } export interface SelectionOptions { From 4d5b3c11629186196ee93726349a1dda775342e6 Mon Sep 17 00:00:00 2001 From: Piotr Michalak Date: Fri, 10 Nov 2023 12:21:38 +0100 Subject: [PATCH 2/4] Format import statement --- packages/vanilla/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vanilla/src/index.ts b/packages/vanilla/src/index.ts index 07015ce..24e2acc 100644 --- a/packages/vanilla/src/index.ts +++ b/packages/vanilla/src/index.ts @@ -1,5 +1,5 @@ import {EventTarget} from './EventEmitter'; -import type { AreaLocation, Coordinates, MouseButton, ScrollEvent, SelectionEvents, SelectionOptions, SelectionStore } from './types'; +import type {AreaLocation, Coordinates, MouseButton, ScrollEvent, SelectionEvents, SelectionOptions, SelectionStore} from './types'; import {PartialSelectionOptions} from './types'; import {css, frames, Frames, intersects, isSafariBrowser, isTouchDevice, off, on, selectAll, SelectAllSelectors, simplifyEvent} from './utils'; From af143903fe1a3d6baf7a7acafbe528b197e5090b Mon Sep 17 00:00:00 2001 From: Piotr Michalak <33657122+Pietrrrek@users.noreply.github.com> Date: Tue, 14 Nov 2023 20:44:59 +0000 Subject: [PATCH 3/4] Replace "ignoreButtons" option with "triggers" This commit makes it possible for consumers of this package to define more complex inputs that trigger the selection; e.g. Control + Shift + Left-mouse-button A mouse-button is always required while the modifiers are optional. --- packages/vanilla/src/index.ts | 12 +++--- packages/vanilla/src/types.ts | 24 ++++++++--- packages/vanilla/src/utils/index.ts | 1 + packages/vanilla/src/utils/shouldTrigger.ts | 48 +++++++++++++++++++++ 4 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 packages/vanilla/src/utils/shouldTrigger.ts diff --git a/packages/vanilla/src/index.ts b/packages/vanilla/src/index.ts index 24e2acc..01b2dda 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, MouseButton, ScrollEvent, SelectionEvents, SelectionOptions, SelectionStore} from './types'; +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,7 +69,7 @@ export default class SelectionArea extends EventTarget { behaviour: { overlap: 'invert', intersect: 'touch', - ignoredButtons: [], + triggers: [{button: 0, modifiers: ["ctrl", "alt", "meta"]}], ...opt.behaviour, startThreshold: opt.behaviour?.startThreshold ? typeof opt.behaviour.startThreshold === 'number' ? @@ -151,13 +151,13 @@ export default class SelectionArea extends EventTarget { } _onTapStart(evt: MouseEvent | TouchEvent, silent = false): void { - if (evt instanceof MouseEvent && this._options.behaviour.ignoredButtons?.includes(evt.button as MouseButton)) - return; - const {x, y, target} = simplifyEvent(evt); 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 fe9da61..76a51ad 100644 --- a/packages/vanilla/src/types.ts +++ b/packages/vanilla/src/types.ts @@ -56,11 +56,23 @@ 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 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; @@ -84,7 +96,7 @@ export interface Behaviour { startThreshold: number | Coordinates; overlap: OverlapMode; scrolling: Scrolling; - ignoredButtons?: MouseButton[]; + 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 From 0de0275dbb21e2a7d933990bebd13a73e1b9675e Mon Sep 17 00:00:00 2001 From: Piotr Michalak <33657122+Pietrrrek@users.noreply.github.com> Date: Tue, 14 Nov 2023 20:50:46 +0000 Subject: [PATCH 4/4] Use Primary (0) as default --- packages/vanilla/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vanilla/src/index.ts b/packages/vanilla/src/index.ts index 01b2dda..9fc7e34 100644 --- a/packages/vanilla/src/index.ts +++ b/packages/vanilla/src/index.ts @@ -69,7 +69,7 @@ export default class SelectionArea extends EventTarget { behaviour: { overlap: 'invert', intersect: 'touch', - triggers: [{button: 0, modifiers: ["ctrl", "alt", "meta"]}], + triggers: [0], ...opt.behaviour, startThreshold: opt.behaviour?.startThreshold ? typeof opt.behaviour.startThreshold === 'number' ?