/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/focus-history.js
137 строк
3 KB
Ryan Cebulko
♻️ Move files into #core, #utils to enable tsc typechecking (#36277)
07 окт 2021, 23:26
Не верифицирован
07 окт 2021, 23:26
b6d1550
Код
Авторство
О чём код?
import {Observable} from '#core/data-structures/observable'; import {isElement} from '#core/types'; import {Services} from '#service'; import {dev} from '#utils/log'; /** * FocusHistory keeps track of recent focused elements. This history can be * purged using `purgeBefore` method. */ export class FocusHistory { /** * @param {!Window} win * @param {number} purgeTimeout */ constructor(win, purgeTimeout) { /** @const {!Window} */ this.win = win; /** @private @const {number} */ this.purgeTimeout_ = purgeTimeout; /** @private @const {!Array<!{el: !Element, time: time}>} */ this.history_ = []; /** @private @const {!Observable<!Element>} */ this.observeFocus_ = new Observable(); /** * @private * @param {!Event} e */ this.captureFocus_ = (e) => { // Hack (#15079) due to Firefox firing focus events on the entire page if (isElement(e.target)) { this.pushFocus_(dev().assertElement(e.target)); } }; /** * @private * @param {*} unusedE */ this.captureBlur_ = (unusedE) => { // IFrame elements do not receive `focus` event. An alternative way is // implemented here. We wait for a blur to arrive on the main window // and after a short time check which element is active. Services.timerFor(win).delay(() => { if (this.win.document.activeElement) { this.pushFocus_(this.win.document.activeElement); } }, 500); }; this.win.document.addEventListener('focus', this.captureFocus_, true); this.win.addEventListener('blur', this.captureBlur_); } /** @visibleForTesting */ cleanup_() { this.win.document.removeEventListener('focus', this.captureFocus_, true); this.win.removeEventListener('blur', this.captureBlur_); } /** * Add a listener for focus events. * @param {function(!Element)} handler * @return {!UnlistenDef} */ onFocus(handler) { return this.observeFocus_.add(handler); } /** * @param {!Element} element * @private */ pushFocus_(element) { const now = Date.now(); if ( this.history_.length == 0 || this.history_[this.history_.length - 1].el != element ) { this.history_.push({el: element, time: now}); } else { this.history_[this.history_.length - 1].time = now; } this.purgeBefore(now - this.purgeTimeout_); this.observeFocus_.fire(element); } /** * Returns the element that was focused last. * @return {?Element} */ getLast() { if (this.history_.length == 0) { return null; } return this.history_[this.history_.length - 1].el; } /** * Removes elements from the history older than the specified time. * @param {time} time */ purgeBefore(time) { let index = this.history_.length - 1; for (let i = 0; i < this.history_.length; i++) { if (this.history_[i].time >= time) { index = i - 1; break; } } if (index != -1) { this.history_.splice(0, index + 1); } } /** * Returns `true` if the specified element contains any of the elements in * the history. * @param {!Element} element * @return {boolean} */ hasDescendantsOf(element) { if (this.win.document.activeElement) { this.pushFocus_(this.win.document.activeElement); } for (let i = 0; i < this.history_.length; i++) { if (element.contains(this.history_[i].el)) { return true; } } return false; } }