From 983c533eef577f295a5aa2f27cb067b2973c7c93 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Wed, 1 Mar 2023 11:07:52 +0100 Subject: [PATCH] fix(form): disable scroll wheel on number inputs --- .../controllers/application_controller.ts | 19 +++++++++---------- .../controllers/number_input_controller.ts | 16 ++++++++++++++++ app/views/layouts/application.html.haml | 2 +- 3 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 app/javascript/controllers/number_input_controller.ts diff --git a/app/javascript/controllers/application_controller.ts b/app/javascript/controllers/application_controller.ts index ada9f6a78..7ce170352 100644 --- a/app/javascript/controllers/application_controller.ts +++ b/app/javascript/controllers/application_controller.ts @@ -6,6 +6,7 @@ export type Detail = Record; // see: https://www.quirksmode.org/blog/archives/2008/04/delegating_the.html const FOCUS_EVENTS = ['focus', 'blur']; +const ACTIVE_EVENTS = ['wheel']; export class ApplicationController extends Controller { #debounced = new Map<() => void, ReturnType>(); @@ -58,12 +59,7 @@ export class ApplicationController extends Controller { ): void { if (typeof targetOrEventName == 'string') { invariant(typeof eventNameOrHandler != 'string', 'handler is required'); - this.onTarget( - this.element, - targetOrEventName, - eventNameOrHandler, - FOCUS_EVENTS.includes(targetOrEventName) - ); + this.onTarget(this.element, targetOrEventName, eventNameOrHandler); } else { invariant( typeof eventNameOrHandler == 'string', @@ -84,16 +80,19 @@ export class ApplicationController extends Controller { private onTarget( target: EventTarget, eventName: string, - handler: (event: HandlerEvent) => void, - capture?: boolean + handler: (event: HandlerEvent) => void ): void { const disconnect = this.disconnect; const callback = (event: Event): void => { handler(event as HandlerEvent); }; - target.addEventListener(eventName, callback, capture); + const options = { + capture: FOCUS_EVENTS.includes(eventName), + passive: ACTIVE_EVENTS.includes(eventName) ? false : undefined + }; + target.addEventListener(eventName, callback, options); this.disconnect = () => { - target.removeEventListener(eventName, callback, capture); + target.removeEventListener(eventName, callback, options); disconnect.call(this); }; } diff --git a/app/javascript/controllers/number_input_controller.ts b/app/javascript/controllers/number_input_controller.ts new file mode 100644 index 000000000..c79629a72 --- /dev/null +++ b/app/javascript/controllers/number_input_controller.ts @@ -0,0 +1,16 @@ +import { isInputElement } from '@coldwired/utils'; +import { ApplicationController } from './application_controller'; + +export class NumberInputController extends ApplicationController { + connect() { + this.onGlobal('wheel', (event) => { + if ( + isInputElement(event.target) && + event.target.type == 'number' && + document.activeElement == event.target + ) { + event.preventDefault(); + } + }); + } +} diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 74b523894..f885c6223 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -35,7 +35,7 @@ = yield(:invisible_captcha_styles) - %body{ { id: content_for(:page_id), class: browser.platform.ios? ? 'ios' : nil, data: { controller: 'turbo' } }.compact } + %body{ { id: content_for(:page_id), class: browser.platform.ios? ? 'ios' : nil, data: { controller: 'turbo number-input' } }.compact } = render partial: 'layouts/skiplinks' .page-wrapper - if feature_enabled?(:team_on_strike)