/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
2603010454000
src/input.js
281 строка
7 KB
Viktor Szépe
Fix typos in src/ (#39606)
12 авг 2025, 21:35
Не верифицирован
12 авг 2025, 21:35
fca692e
Код
Авторство
О чём код?
import {Observable} from '#core/data-structures/observable'; import {Services} from '#service'; import {listenOnce, listenOncePromise} from '#utils/event-helper'; import {dev} from '#utils/log'; import {registerServiceBuilder} from './service-helpers'; const TAG_ = 'Input'; const MAX_MOUSE_CONFIRM_ATTEMPTS_ = 3; const CLICK_TIMEOUT_ = 300; /** * Detects and maintains different types of input such as touch, mouse or * keyboard. */ export class Input { /** * @param {!Window} win */ constructor(win) { /** @const {!Window} */ this.win = win; /** @private {!Function} */ this.boundOnKeyDown_ = this.onKeyDown_.bind(this); /** @private {!Function} */ this.boundOnMouseDown_ = this.onMouseDown_.bind(this); /** @private {?function(!Event)} */ this.boundOnMouseMove_ = null; /** @private {?Function} */ this.boundMouseCanceled_ = null; /** @private {?Function} */ this.boundMouseConfirmed_ = null; /** @private {boolean} */ this.hasTouch_ = 'ontouchstart' in win || (win.navigator['maxTouchPoints'] !== undefined && win.navigator['maxTouchPoints'] > 0) || win['DocumentTouch'] !== undefined; dev().fine(TAG_, 'touch detected:', this.hasTouch_); /** @private {boolean} */ this.keyboardActive_ = false; this.win.document.addEventListener('keydown', this.boundOnKeyDown_); this.win.document.addEventListener('mousedown', this.boundOnMouseDown_); /** @private {boolean} */ this.hasMouse_ = true; /** @private {number} */ this.mouseConfirmAttemptCount_ = 0; /** @private {!Observable<boolean>} */ this.touchDetectedObservable_ = new Observable(); /** @private {!Observable<boolean>} */ this.mouseDetectedObservable_ = new Observable(); /** @private {!Observable<boolean>} */ this.keyboardStateObservable_ = new Observable(); // If touch available, temporarily set hasMouse to false and wait for // mouse events. if (this.hasTouch_) { this.hasMouse_ = !this.hasTouch_; this.boundOnMouseMove_ = /** @type {function(!Event)} */ ( this.onMouseMove_.bind(this) ); listenOnce(win.document, 'mousemove', this.boundOnMouseMove_); } } /** * See https://github.com/ampproject/amphtml/blob/main/docs/spec/amp-css-classes.md#input-mode-classes * @param {!./service/ampdoc-impl.AmpDoc} ampdoc */ setupInputModeClasses(ampdoc) { this.onTouchDetected((detected) => { this.toggleInputClass_(ampdoc, 'amp-mode-touch', detected); }, true); this.onMouseDetected((detected) => { this.toggleInputClass_(ampdoc, 'amp-mode-mouse', detected); }, true); this.onKeyboardStateChanged((active) => { this.toggleInputClass_(ampdoc, 'amp-mode-keyboard-active', active); }, true); } /** * Whether the touch input has been detected. * @return {boolean} */ isTouchDetected() { return this.hasTouch_; } /** * Registers an event handle in case if the touch is detected. * @param {function(boolean)} handler * @param {boolean=} opt_fireImmediately * @return {!UnlistenDef} */ onTouchDetected(handler, opt_fireImmediately) { if (opt_fireImmediately) { handler(this.isTouchDetected()); } return this.touchDetectedObservable_.add(handler); } /** * Whether the mouse input has been detected. * @return {boolean} */ isMouseDetected() { return this.hasMouse_; } /** * Registers an event handle in case if the mouse is detected. * @param {function(boolean)} handler * @param {boolean=} opt_fireImmediately * @return {!UnlistenDef} */ onMouseDetected(handler, opt_fireImmediately) { if (opt_fireImmediately) { handler(this.isMouseDetected()); } return this.mouseDetectedObservable_.add(handler); } /** * Whether the keyboard input is currently active. * @return {boolean} */ isKeyboardActive() { return this.keyboardActive_; } /** * Registers an event handle for changes in the keyboard input. * @param {function(boolean)} handler * @param {boolean=} opt_fireImmediately * @return {!UnlistenDef} */ onKeyboardStateChanged(handler, opt_fireImmediately) { if (opt_fireImmediately) { handler(this.isKeyboardActive()); } return this.keyboardStateObservable_.add(handler); } /** * @param {!./service/ampdoc-impl.AmpDoc} ampdoc * @param {string} clazz * @param {boolean} on * @private */ toggleInputClass_(ampdoc, clazz, on) { ampdoc.waitForBodyOpen().then((body) => { const vsync = Services./*OK*/ vsyncFor(this.win); vsync.mutate(() => { body.classList.toggle(clazz, on); }); }); } /** * @param {!Event} e * @private */ onKeyDown_(e) { if (this.keyboardActive_) { return; } if (e.defaultPrevented) { return; } // Ignore inputs. const {target} = e; if ( target && (target.tagName == 'INPUT' || target.tagName == 'TEXTAREA' || target.tagName == 'SELECT' || target.tagName == 'OPTION' || target.hasAttribute('contenteditable')) ) { return; } this.keyboardActive_ = true; this.keyboardStateObservable_.fire(true); dev().fine(TAG_, 'keyboard activated'); } /** @private */ onMouseDown_() { if (!this.keyboardActive_) { return; } this.keyboardActive_ = false; this.keyboardStateObservable_.fire(false); dev().fine(TAG_, 'keyboard deactivated'); } /** * @param {!Event} e * @return {!Promise|undefined} * @private */ onMouseMove_(e) { // The event explicitly states that it's a result of a touch event. if (e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents) { this.mouseCanceled_(); return undefined; } if (!this.boundMouseConfirmed_) { this.boundMouseConfirmed_ = this.mouseConfirmed_.bind(this); this.boundMouseCanceled_ = this.mouseCanceled_.bind(this); } // If "click" arrives within a timeout time, this is most likely a // touch/mouse emulation. Otherwise, if timeout exceeded, this looks // like a legitimate mouse event. let unlisten; const listenPromise = listenOncePromise( this.win.document, 'click', /* capture */ undefined, (unlistener) => { unlisten = unlistener; } ); return Services.timerFor(this.win) .timeoutPromise(CLICK_TIMEOUT_, listenPromise) .then(this.boundMouseCanceled_, () => { if (unlisten) { unlisten(); } this.boundMouseConfirmed_(); }); } /** @private */ mouseConfirmed_() { this.hasMouse_ = true; this.mouseDetectedObservable_.fire(true); dev().fine(TAG_, 'mouse detected'); } /** @private */ mouseCanceled_() { // Repeat, if attempts allow. this.mouseConfirmAttemptCount_++; if (this.mouseConfirmAttemptCount_ <= MAX_MOUSE_CONFIRM_ATTEMPTS_) { listenOnce( this.win.document, 'mousemove', /** @type {function(!Event)} */ (this.boundOnMouseMove_) ); } else { dev().fine(TAG_, 'mouse detection failed'); } } } /** * @param {!Window} win */ export function installInputService(win) { registerServiceBuilder(win, 'input', Input); }