Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Commit

Permalink
fix: move applyPassive to dom package for use in text-field (#4747)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Goo authored Jun 14, 2019
1 parent ecdefca commit ce0b1c5
Show file tree
Hide file tree
Showing 17 changed files with 171 additions and 91 deletions.
20 changes: 12 additions & 8 deletions packages/mdc-base/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,24 @@ export class MDCComponent<FoundationType extends MDCFoundation> {
* Wrapper method to add an event listener to the component's root element. This is most useful when
* listening for custom events.
*/
listen<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void;
listen<E extends Event>(evtType: string, handler: CustomEventListener<E>): void;
listen(evtType: string, handler: EventListener) {
this.root_.addEventListener(evtType, handler);
listen<K extends EventType>(
evtType: K, handler: SpecificEventListener<K>, options?: AddEventListenerOptions | boolean): void;
listen<E extends Event>(
evtType: string, handler: CustomEventListener<E>, options?: AddEventListenerOptions | boolean): void;
listen(evtType: string, handler: EventListener, options?: AddEventListenerOptions | boolean) {
this.root_.addEventListener(evtType, handler, options);
}

/**
* Wrapper method to remove an event listener to the component's root element. This is most useful when
* unlistening for custom events.
*/
unlisten<K extends EventType>(evtType: K, handler: SpecificEventListener<K>): void;
unlisten<E extends Event>(evtType: string, handler: CustomEventListener<E>): void;
unlisten(evtType: string, handler: EventListener) {
this.root_.removeEventListener(evtType, handler);
unlisten<K extends EventType>(
evtType: K, handler: SpecificEventListener<K>, options?: AddEventListenerOptions | boolean): void;
unlisten<E extends Event>(
evtType: string, handler: CustomEventListener<E>, options?: AddEventListenerOptions | boolean): void;
unlisten(evtType: string, handler: EventListener, options?: AddEventListenerOptions | boolean) {
this.root_.removeEventListener(evtType, handler, options);
}

/**
Expand Down
7 changes: 5 additions & 2 deletions packages/mdc-checkbox/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import {getCorrectEventName} from '@material/animation/util';
import {MDCComponent} from '@material/base/component';
import {applyPassive} from '@material/dom/events';
import {matches} from '@material/dom/ponyfill';
import {MDCRippleAdapter} from '@material/ripple/adapter';
import {MDCRipple} from '@material/ripple/component';
Expand Down Expand Up @@ -125,10 +126,12 @@ export class MDCCheckbox extends MDCComponent<MDCCheckboxFoundation> implements
// To ensure we don't accidentally omit any methods, we need a separate, strongly typed adapter variable.
const adapter: MDCRippleAdapter = {
...MDCRipple.createAdapter(this),
deregisterInteractionHandler: (evtType, handler) => this.nativeControl_.removeEventListener(evtType, handler),
deregisterInteractionHandler: (evtType, handler) => this.nativeControl_.removeEventListener(
evtType, handler, applyPassive()),
isSurfaceActive: () => matches(this.nativeControl_, ':active'),
isUnbounded: () => true,
registerInteractionHandler: (evtType, handler) => this.nativeControl_.addEventListener(evtType, handler),
registerInteractionHandler: (evtType, handler) => this.nativeControl_.addEventListener(
evtType, handler, applyPassive()),
};
return new MDCRipple(this.root_, new MDCRippleFoundation(adapter));
}
Expand Down
10 changes: 10 additions & 0 deletions packages/mdc-dom/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,13 @@ Function Signature | Description
--- | ---
`closest(element: Element, selector: string) => ?Element` | Returns the ancestor of the given element matching the given selector (which may be the element itself if it matches), or `null` if no matching ancestor is found.
`matches(element: Element, selector: string) => boolean` | Returns true if the given element matches the given CSS selector.

### Event Functions

External frameworks and libraries can use the following event utility methods.

Method Signature | Description
--- | ---
`util.applyPassive(globalObj = window, forceRefresh = false) => object` | Determine whether the current browser supports passive event listeners

> _NOTE_: The function `util.applyPassive` cache its results; `forceRefresh` will force recomputation, but is used mainly for testing and should not be necessary in normal use.
52 changes: 52 additions & 0 deletions packages/mdc-dom/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @license
* Copyright 2019 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

/**
* Stores result from applyPassive to avoid redundant processing to detect
* passive event listener support.
*/
let supportsPassive_: boolean | undefined;

/**
* Determine whether the current browser supports passive event listeners, and
* if so, use them.
*/
export function applyPassive(globalObj: Window = window, forceRefresh = false):
boolean | EventListenerOptions {
if (supportsPassive_ === undefined || forceRefresh) {
let isSupported = false;
try {
globalObj.document.addEventListener('test', () => undefined, {
get passive() {
isSupported = true;
return isSupported;
},
});
} catch (e) {
} // tslint:disable-line:no-empty cannot throw error due to tests. tslint also disables console.log.

supportsPassive_ = isSupported;
}

return supportsPassive_ ? {passive: true} as EventListenerOptions : false;
}
3 changes: 2 additions & 1 deletion packages/mdc-dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
* THE SOFTWARE.
*/

import * as events from './events';
import * as ponyfill from './ponyfill';

export {ponyfill};
export {events, ponyfill};
7 changes: 5 additions & 2 deletions packages/mdc-radio/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*/

import {MDCComponent} from '@material/base/component';
import {applyPassive} from '@material/dom/events';
import {MDCRippleAdapter} from '@material/ripple/adapter';
import {MDCRipple} from '@material/ripple/component';
import {MDCRippleFoundation} from '@material/ripple/foundation';
Expand Down Expand Up @@ -89,8 +90,10 @@ export class MDCRadio extends MDCComponent<MDCRadioFoundation> implements MDCRip
// tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface.
const adapter: MDCRippleAdapter = {
...MDCRipple.createAdapter(this),
registerInteractionHandler: (evtType, handler) => this.nativeControl_.addEventListener(evtType, handler),
deregisterInteractionHandler: (evtType, handler) => this.nativeControl_.removeEventListener(evtType, handler),
registerInteractionHandler: (evtType, handler) => this.nativeControl_.addEventListener(
evtType, handler, applyPassive()),
deregisterInteractionHandler: (evtType, handler) => this.nativeControl_.removeEventListener(
evtType, handler, applyPassive()),
// Radio buttons technically go "active" whenever there is *any* keyboard interaction.
// This is not the UI we desire.
isSurfaceActive: () => false,
Expand Down
1 change: 1 addition & 0 deletions packages/mdc-radio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"dependencies": {
"@material/animation": "^1.0.0",
"@material/base": "^1.0.0",
"@material/dom": "^1.1.0",
"@material/feature-targeting": "^0.44.1",
"@material/ripple": "^2.3.0",
"@material/theme": "^1.1.0",
Expand Down
3 changes: 1 addition & 2 deletions packages/mdc-ripple/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,9 @@ External frameworks and libraries can use the following utility methods when int
Method Signature | Description
--- | ---
`util.supportsCssVariables(windowObj, forceRefresh = false) => Boolean` | Determine whether the current browser supports CSS variables (custom properties)
`util.applyPassive(globalObj = window, forceRefresh = false) => object` | Determine whether the current browser supports passive event listeners
`util.getNormalizedEventCoords(ev, pageOffset, clientRect) => object` | Determines X/Y coordinates of an event normalized for touch events and ripples

> _NOTE_: The functions `util.supportsCssVariables` and `util.applyPassive` cache their results; `forceRefresh` will force recomputation, but is used mainly for testing and should not be necessary in normal use.
> _NOTE_: The function `util.supportsCssVariables` cache its results; `forceRefresh` will force recomputation, but is used mainly for testing and should not be necessary in normal use.
## Caveats

Expand Down
9 changes: 5 additions & 4 deletions packages/mdc-ripple/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*/

import {MDCComponent} from '@material/base/component';
import {applyPassive} from '@material/dom/events';
import {matches} from '@material/dom/ponyfill';
import {MDCRippleAdapter} from './adapter';
import {MDCRippleFoundation} from './foundation';
Expand All @@ -47,18 +48,18 @@ export class MDCRipple extends MDCComponent<MDCRippleFoundation> implements MDCR
computeBoundingRect: () => instance.root_.getBoundingClientRect(),
containsEventTarget: (target) => instance.root_.contains(target as Node),
deregisterDocumentInteractionHandler: (evtType, handler) =>
document.documentElement.removeEventListener(evtType, handler, util.applyPassive()),
document.documentElement.removeEventListener(evtType, handler, applyPassive()),
deregisterInteractionHandler: (evtType, handler) =>
instance.root_.removeEventListener(evtType, handler, util.applyPassive()),
instance.root_.removeEventListener(evtType, handler, applyPassive()),
deregisterResizeHandler: (handler) => window.removeEventListener('resize', handler),
getWindowPageOffset: () => ({x: window.pageXOffset, y: window.pageYOffset}),
isSurfaceActive: () => matches(instance.root_, ':active'),
isSurfaceDisabled: () => Boolean(instance.disabled),
isUnbounded: () => Boolean(instance.unbounded),
registerDocumentInteractionHandler: (evtType, handler) =>
document.documentElement.addEventListener(evtType, handler, util.applyPassive()),
document.documentElement.addEventListener(evtType, handler, applyPassive()),
registerInteractionHandler: (evtType, handler) =>
instance.root_.addEventListener(evtType, handler, util.applyPassive()),
instance.root_.addEventListener(evtType, handler, applyPassive()),
registerResizeHandler: (handler) => window.addEventListener('resize', handler),
removeClass: (className) => instance.root_.classList.remove(className),
updateCssVariable: (varName, value) => (instance.root_ as HTMLElement).style.setProperty(varName, value),
Expand Down
30 changes: 0 additions & 30 deletions packages/mdc-ripple/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ import {MDCRipplePoint} from './types';
*/
let supportsCssVariables_: boolean | undefined;

/**
* Stores result from applyPassive to avoid redundant processing to detect
* passive event listener support.
*/
let supportsPassive_: boolean | undefined;

function detectEdgePseudoVarBug(windowObj: Window): boolean {
// Detect versions of Edge with buggy var() support
// See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11495448/
Expand Down Expand Up @@ -86,30 +80,6 @@ export function supportsCssVariables(windowObj: Window, forceRefresh = false): b
return supportsCssVars;
}

/**
* Determine whether the current browser supports passive event listeners, and
* if so, use them.
*/
export function applyPassive(globalObj: Window = window, forceRefresh = false):
boolean | EventListenerOptions {
if (supportsPassive_ === undefined || forceRefresh) {
let isSupported = false;
try {
globalObj.document.addEventListener('test', () => undefined, {
get passive() {
isSupported = true;
return isSupported;
},
});
} catch (e) {
} // tslint:disable-line:no-empty cannot throw error due to tests. tslint also disables console.log.

supportsPassive_ = isSupported;
}

return supportsPassive_ ? {passive: true} as EventListenerOptions : false;
}

export function getNormalizedEventCoords(evt: Event | undefined, pageOffset: MDCRipplePoint, clientRect: ClientRect):
MDCRipplePoint {
if (!evt) {
Expand Down
9 changes: 5 additions & 4 deletions packages/mdc-slider/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*/

import {MDCComponent} from '@material/base/component';
import {applyPassive} from '@material/dom/events';
import {MDCSliderAdapter} from './adapter';
import {strings} from './constants';
import {MDCSliderFoundation} from './foundation';
Expand Down Expand Up @@ -98,13 +99,13 @@ export class MDCSlider extends MDCComponent<MDCSliderFoundation> {
removeAttribute: (name) => this.root_.removeAttribute(name),
computeBoundingRect: () => this.root_.getBoundingClientRect(),
getTabIndex: () => this.root_.tabIndex,
registerInteractionHandler: (evtType, handler) => this.listen(evtType, handler),
deregisterInteractionHandler: (evtType, handler) => this.unlisten(evtType, handler),
registerInteractionHandler: (evtType, handler) => this.listen(evtType, handler, applyPassive()),
deregisterInteractionHandler: (evtType, handler) => this.unlisten(evtType, handler, applyPassive()),
registerThumbContainerInteractionHandler: (evtType, handler) => {
this.thumbContainer_.addEventListener(evtType, handler);
this.thumbContainer_.addEventListener(evtType, handler, applyPassive());
},
deregisterThumbContainerInteractionHandler: (evtType, handler) => {
this.thumbContainer_.removeEventListener(evtType, handler);
this.thumbContainer_.removeEventListener(evtType, handler, applyPassive());
},
registerBodyInteractionHandler: (evtType, handler) => document.body.addEventListener(evtType, handler),
deregisterBodyInteractionHandler: (evtType, handler) => document.body.removeEventListener(evtType, handler),
Expand Down
1 change: 1 addition & 0 deletions packages/mdc-slider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"dependencies": {
"@material/animation": "^1.0.0",
"@material/base": "^1.0.0",
"@material/dom": "^1.1.0",
"@material/rtl": "^0.42.0",
"@material/theme": "^1.1.0",
"@material/typography": "^2.3.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/mdc-switch/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import {MDCComponent} from '@material/base/component';
import {EventType} from '@material/base/types';
import {applyPassive} from '@material/dom/events';
import {matches} from '@material/dom/ponyfill';
import {MDCRippleAdapter} from '@material/ripple/adapter';
import {MDCRipple} from '@material/ripple/component';
Expand Down Expand Up @@ -103,12 +104,12 @@ export class MDCSwitch extends MDCComponent<MDCSwitchFoundation> implements MDCR
addClass: (className: string) => rippleSurface.classList.add(className),
computeBoundingRect: () => rippleSurface.getBoundingClientRect(),
deregisterInteractionHandler: (evtType: EventType, handler: EventListener) => {
this.nativeControl_.removeEventListener(evtType, handler);
this.nativeControl_.removeEventListener(evtType, handler, applyPassive());
},
isSurfaceActive: () => matches(this.nativeControl_, ':active'),
isUnbounded: () => true,
registerInteractionHandler: (evtType: EventType, handler: EventListener) => {
this.nativeControl_.addEventListener(evtType, handler);
this.nativeControl_.addEventListener(evtType, handler, applyPassive());
},
removeClass: (className: string) => rippleSurface.classList.remove(className),
updateCssVariable: (varName: string, value: string) => {
Expand Down
21 changes: 11 additions & 10 deletions packages/mdc-tab-scroller/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import {MDCComponent} from '@material/base/component';
import {SpecificEventListener} from '@material/base/types';
import {applyPassive} from '@material/dom/events';
import {matches} from '@material/dom/ponyfill';
import {MDCTabScrollerAdapter} from './adapter';
import {MDCTabScrollerFoundation} from './foundation';
Expand Down Expand Up @@ -51,22 +52,22 @@ export class MDCTabScroller extends MDCComponent<MDCTabScrollerFoundation> {
this.handleInteraction_ = () => this.foundation_.handleInteraction();
this.handleTransitionEnd_ = (evt) => this.foundation_.handleTransitionEnd(evt);

this.area_.addEventListener('wheel', this.handleInteraction_);
this.area_.addEventListener('touchstart', this.handleInteraction_);
this.area_.addEventListener('pointerdown', this.handleInteraction_);
this.area_.addEventListener('mousedown', this.handleInteraction_);
this.area_.addEventListener('keydown', this.handleInteraction_);
this.area_.addEventListener('wheel', this.handleInteraction_, applyPassive());
this.area_.addEventListener('touchstart', this.handleInteraction_, applyPassive());
this.area_.addEventListener('pointerdown', this.handleInteraction_, applyPassive());
this.area_.addEventListener('mousedown', this.handleInteraction_, applyPassive());
this.area_.addEventListener('keydown', this.handleInteraction_, applyPassive());
this.content_.addEventListener('transitionend', this.handleTransitionEnd_);
}

destroy() {
super.destroy();

this.area_.removeEventListener('wheel', this.handleInteraction_);
this.area_.removeEventListener('touchstart', this.handleInteraction_);
this.area_.removeEventListener('pointerdown', this.handleInteraction_);
this.area_.removeEventListener('mousedown', this.handleInteraction_);
this.area_.removeEventListener('keydown', this.handleInteraction_);
this.area_.removeEventListener('wheel', this.handleInteraction_, applyPassive());
this.area_.removeEventListener('touchstart', this.handleInteraction_, applyPassive());
this.area_.removeEventListener('pointerdown', this.handleInteraction_, applyPassive());
this.area_.removeEventListener('mousedown', this.handleInteraction_, applyPassive());
this.area_.removeEventListener('keydown', this.handleInteraction_, applyPassive());
this.content_.removeEventListener('transitionend', this.handleTransitionEnd_);
}

Expand Down
12 changes: 8 additions & 4 deletions packages/mdc-textfield/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*/

import {MDCComponent} from '@material/base/component';
import {applyPassive} from '@material/dom/events';
import * as ponyfill from '@material/dom/ponyfill';
import {MDCFloatingLabel, MDCFloatingLabelFactory} from '@material/floating-label/component';
import {MDCLineRipple, MDCLineRippleFactory} from '@material/line-ripple/component';
Expand Down Expand Up @@ -384,8 +385,10 @@ export class MDCTextField extends MDCComponent<MDCTextFieldFoundation> implement
return {
getNativeInput: () => this.input_,
isFocused: () => document.activeElement === this.input_,
registerInputInteractionHandler: (evtType, handler) => this.input_.addEventListener(evtType, handler),
deregisterInputInteractionHandler: (evtType, handler) => this.input_.removeEventListener(evtType, handler),
registerInputInteractionHandler: (evtType, handler) =>
this.input_.addEventListener(evtType, handler, applyPassive()),
deregisterInputInteractionHandler: (evtType, handler) =>
this.input_.removeEventListener(evtType, handler, applyPassive()),
};
// tslint:enable:object-literal-sort-keys
}
Expand Down Expand Up @@ -453,8 +456,9 @@ export class MDCTextField extends MDCComponent<MDCTextFieldFoundation> implement
const adapter: MDCRippleAdapter = {
...MDCRipple.createAdapter(this),
isSurfaceActive: () => ponyfill.matches(this.input_, ':active'),
registerInteractionHandler: (evtType, handler) => this.input_.addEventListener(evtType, handler),
deregisterInteractionHandler: (evtType, handler) => this.input_.removeEventListener(evtType, handler),
registerInteractionHandler: (evtType, handler) => this.input_.addEventListener(evtType, handler, applyPassive()),
deregisterInteractionHandler: (evtType, handler) =>
this.input_.removeEventListener(evtType, handler, applyPassive()),
};
// tslint:enable:object-literal-sort-keys
return rippleFactory(this.root_, new MDCRippleFoundation(adapter));
Expand Down
Loading

0 comments on commit ce0b1c5

Please sign in to comment.