mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-19 14:06:33 +02:00
improve whole-page performance by scoping events handlers (#740)
* scope onMouseDown event capture * supercedes #710 * make isIE11 a const * scope keydown * scope mouseover * fix removeEventListener for keyup
This commit is contained in:
parent
fb2310cb56
commit
8775bacdd9
|
@ -36,13 +36,16 @@ import {
|
||||||
strToEl,
|
strToEl,
|
||||||
sortByScore,
|
sortByScore,
|
||||||
generateId,
|
generateId,
|
||||||
findAncestorByAttrName,
|
|
||||||
isIE11,
|
|
||||||
existsInArray,
|
existsInArray,
|
||||||
cloneObject,
|
cloneObject,
|
||||||
diff,
|
diff,
|
||||||
} from './lib/utils';
|
} from './lib/utils';
|
||||||
|
|
||||||
|
/** @see {@link http://browserhacks.com/#hack-acea075d0ac6954f275a70023906050c} */
|
||||||
|
const IS_IE11 =
|
||||||
|
'-ms-scroll-limit' in document.documentElement.style &&
|
||||||
|
'-ms-ime-align' in document.documentElement.style;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('../../types/index').Choices.Choice} Choice
|
* @typedef {import('../../types/index').Choices.Choice} Choice
|
||||||
* @typedef {import('../../types/index').Choices.Item} Item
|
* @typedef {import('../../types/index').Choices.Item} Item
|
||||||
|
@ -1226,16 +1229,24 @@ class Choices {
|
||||||
const { documentElement } = document;
|
const { documentElement } = document;
|
||||||
|
|
||||||
// capture events - can cancel event processing or propagation
|
// capture events - can cancel event processing or propagation
|
||||||
documentElement.addEventListener('keydown', this._onKeyDown, true);
|
|
||||||
documentElement.addEventListener('touchend', this._onTouchEnd, true);
|
documentElement.addEventListener('touchend', this._onTouchEnd, true);
|
||||||
documentElement.addEventListener('mousedown', this._onMouseDown, true);
|
this.containerOuter.element.addEventListener(
|
||||||
|
'keydown',
|
||||||
|
this._onKeyDown,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
this.containerOuter.element.addEventListener(
|
||||||
|
'mousedown',
|
||||||
|
this._onMouseDown,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
// passive events - doesn't call `preventDefault` or `stopPropagation`
|
// passive events - doesn't call `preventDefault` or `stopPropagation`
|
||||||
documentElement.addEventListener('click', this._onClick, { passive: true });
|
documentElement.addEventListener('click', this._onClick, { passive: true });
|
||||||
documentElement.addEventListener('touchmove', this._onTouchMove, {
|
documentElement.addEventListener('touchmove', this._onTouchMove, {
|
||||||
passive: true,
|
passive: true,
|
||||||
});
|
});
|
||||||
documentElement.addEventListener('mouseover', this._onMouseOver, {
|
this.dropdown.element.addEventListener('mouseover', this._onMouseOver, {
|
||||||
passive: true,
|
passive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1271,20 +1282,28 @@ class Choices {
|
||||||
_removeEventListeners() {
|
_removeEventListeners() {
|
||||||
const { documentElement } = document;
|
const { documentElement } = document;
|
||||||
|
|
||||||
documentElement.removeEventListener('keydown', this._onKeyDown, true);
|
|
||||||
documentElement.removeEventListener('touchend', this._onTouchEnd, true);
|
documentElement.removeEventListener('touchend', this._onTouchEnd, true);
|
||||||
documentElement.removeEventListener('mousedown', this._onMouseDown, true);
|
this.containerOuter.element.removeEventListener(
|
||||||
|
'keydown',
|
||||||
|
this._onKeyDown,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
this.containerOuter.element.removeEventListener(
|
||||||
|
'mousedown',
|
||||||
|
this._onMouseDown,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
documentElement.removeEventListener('keyup', this._onKeyUp);
|
|
||||||
documentElement.removeEventListener('click', this._onClick);
|
documentElement.removeEventListener('click', this._onClick);
|
||||||
documentElement.removeEventListener('touchmove', this._onTouchMove);
|
documentElement.removeEventListener('touchmove', this._onTouchMove);
|
||||||
documentElement.removeEventListener('mouseover', this._onMouseOver);
|
this.dropdown.element.removeEventListener('mouseover', this._onMouseOver);
|
||||||
|
|
||||||
if (this._isSelectOneElement) {
|
if (this._isSelectOneElement) {
|
||||||
this.containerOuter.element.removeEventListener('focus', this._onFocus);
|
this.containerOuter.element.removeEventListener('focus', this._onFocus);
|
||||||
this.containerOuter.element.removeEventListener('blur', this._onBlur);
|
this.containerOuter.element.removeEventListener('blur', this._onBlur);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.input.element.removeEventListener('keyup', this._onKeyUp);
|
||||||
this.input.element.removeEventListener('focus', this._onFocus);
|
this.input.element.removeEventListener('focus', this._onFocus);
|
||||||
this.input.element.removeEventListener('blur', this._onBlur);
|
this.input.element.removeEventListener('blur', this._onBlur);
|
||||||
|
|
||||||
|
@ -1295,13 +1314,13 @@ class Choices {
|
||||||
this.input.removeEventListeners();
|
this.input.removeEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {KeyboardEvent} event
|
||||||
|
*/
|
||||||
_onKeyDown(event) {
|
_onKeyDown(event) {
|
||||||
const { target, keyCode, ctrlKey, metaKey } = event;
|
const { target, keyCode, ctrlKey, metaKey } = event;
|
||||||
|
|
||||||
if (
|
if (target !== this.input.element) {
|
||||||
target !== this.input.element &&
|
|
||||||
!this.containerOuter.element.contains(target)
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1558,47 +1577,55 @@ class Choices {
|
||||||
this._wasTap = true;
|
this._wasTap = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles mousedown event in capture mode for containetOuter.element
|
||||||
|
* @param {MouseEvent} event
|
||||||
|
*/
|
||||||
_onMouseDown(event) {
|
_onMouseDown(event) {
|
||||||
const { target, shiftKey } = event;
|
const { target } = event;
|
||||||
// If we have our mouse down on the scrollbar and are on IE11...
|
if (!(target instanceof HTMLElement)) {
|
||||||
if (
|
|
||||||
this.choiceList.element.contains(target) &&
|
|
||||||
isIE11(navigator.userAgent)
|
|
||||||
) {
|
|
||||||
this._isScrollingOnIe = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!this.containerOuter.element.contains(target) ||
|
|
||||||
target === this.input.element
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { activeItems } = this._store;
|
// If we have our mouse down on the scrollbar and are on IE11...
|
||||||
const hasShiftKey = shiftKey;
|
if (IS_IE11 && this.choiceList.element.contains(target)) {
|
||||||
const buttonTarget = findAncestorByAttrName(target, 'data-button');
|
// check if click was on a scrollbar area
|
||||||
const itemTarget = findAncestorByAttrName(target, 'data-item');
|
const firstChoice = /** @type {HTMLElement} */ (this.choiceList.element
|
||||||
const choiceTarget = findAncestorByAttrName(target, 'data-choice');
|
.firstElementChild);
|
||||||
|
const isOnScrollbar =
|
||||||
if (buttonTarget) {
|
this._direction === 'ltr'
|
||||||
this._handleButtonAction(activeItems, buttonTarget);
|
? event.offsetX >= firstChoice.offsetWidth
|
||||||
} else if (itemTarget) {
|
: event.offsetX < firstChoice.offsetLeft;
|
||||||
this._handleItemAction(activeItems, itemTarget, hasShiftKey);
|
this._isScrollingOnIe = isOnScrollbar;
|
||||||
} else if (choiceTarget) {
|
|
||||||
this._handleChoiceAction(activeItems, choiceTarget);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (target === this.input.element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = target.closest('[data-button],[data-item],[data-choice]');
|
||||||
|
if (item instanceof HTMLElement) {
|
||||||
|
const hasShiftKey = event.shiftKey;
|
||||||
|
const { activeItems } = this._store;
|
||||||
|
const { dataset } = item;
|
||||||
|
|
||||||
|
if ('button' in dataset) {
|
||||||
|
this._handleButtonAction(activeItems, item);
|
||||||
|
} else if ('item' in dataset) {
|
||||||
|
this._handleItemAction(activeItems, item, hasShiftKey);
|
||||||
|
} else if ('choice' in dataset) {
|
||||||
|
this._handleChoiceAction(activeItems, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles mouseover event over this.dropdown
|
||||||
|
* @param {MouseEvent} event
|
||||||
|
*/
|
||||||
_onMouseOver({ target }) {
|
_onMouseOver({ target }) {
|
||||||
const targetWithinDropdown =
|
if (target instanceof HTMLElement && 'choice' in target.dataset) {
|
||||||
target === this.dropdown || this.dropdown.element.contains(target);
|
|
||||||
const shouldHighlightChoice =
|
|
||||||
targetWithinDropdown && target.hasAttribute('data-choice');
|
|
||||||
|
|
||||||
if (shouldHighlightChoice) {
|
|
||||||
this._highlightChoice(target);
|
this._highlightChoice(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,12 +58,6 @@ export const wrap = (element, wrapper = document.createElement('div')) => {
|
||||||
return wrapper.appendChild(element);
|
return wrapper.appendChild(element);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {HTMLElement} el
|
|
||||||
* @param {string} attr
|
|
||||||
*/
|
|
||||||
export const findAncestorByAttrName = (el, attr) => el.closest(`[${attr}]`);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Element} startEl
|
* @param {Element} startEl
|
||||||
* @param {string} selector
|
* @param {string} selector
|
||||||
|
@ -185,13 +179,6 @@ export const dispatchEvent = (element, type, customArgs = null) => {
|
||||||
return element.dispatchEvent(event);
|
return element.dispatchEvent(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} userAgent
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export const isIE11 = userAgent =>
|
|
||||||
!!(userAgent.match(/Trident/) && userAgent.match(/rv[ :]11/));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {array} array
|
* @param {array} array
|
||||||
* @param {any} value
|
* @param {any} value
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
sanitise,
|
sanitise,
|
||||||
sortByAlpha,
|
sortByAlpha,
|
||||||
sortByScore,
|
sortByScore,
|
||||||
isIE11,
|
|
||||||
existsInArray,
|
existsInArray,
|
||||||
cloneObject,
|
cloneObject,
|
||||||
dispatchEvent,
|
dispatchEvent,
|
||||||
|
@ -202,18 +201,6 @@ describe('utils', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isIE11', () => {
|
|
||||||
it('returns whether the given user agent string matches an IE11 user agent string', () => {
|
|
||||||
const IE11UserAgent =
|
|
||||||
'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko';
|
|
||||||
const firefoxUserAgent =
|
|
||||||
'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0';
|
|
||||||
|
|
||||||
expect(isIE11(IE11UserAgent)).to.equal(true);
|
|
||||||
expect(isIE11(firefoxUserAgent)).to.equal(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('existsInArray', () => {
|
describe('existsInArray', () => {
|
||||||
it('determines whether a value exists within given array', () => {
|
it('determines whether a value exists within given array', () => {
|
||||||
const values = [
|
const values = [
|
||||||
|
|
Loading…
Reference in a new issue