UNPKG

adaptive-speech-recognizer

Version:

Adaptive dictation-mode speech recognizer ponyfill compatible with WebChat that gives the user time to think and stutter/stammer.

870 lines (783 loc) 21.7 kB
/** * * @see (https://npmjs.com/package/event-target-shim-es5) * @see https://github.com/mysticatea/event-target-shim/blob/HEAD/dist/event-target-shim.mjs * @license (MIT) */ /** * @author Toru Nagashima <https://github.com/mysticatea> * @copyright 2015 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ /** * @typedef {object} PrivateData * @property {EventTarget} eventTarget The event target. * @property {{type:string}} event The original event object. * @property {number} eventPhase The current event phase. * @property {EventTarget|null} currentTarget The current event target. * @property {boolean} canceled The flag to prevent default. * @property {boolean} stopped The flag to stop propagation. * @property {boolean} immediateStopped The flag to stop propagation immediately. * @property {Function|null} passiveListener The listener if the current listener is passive. Otherwise this is null. * @property {number} timeStamp The unix time. * @private */ /** * Private data for event wrappers. * @type {WeakMap<Event, PrivateData>} * @private */ const privateData = new WeakMap(); /** * Cache for wrapper classes. * @type {WeakMap<Object, Function>} * @private */ const wrappers = new WeakMap(); /** * Get private data. * @param {Event} event The event object to get private data. * @returns {PrivateData} The private data of the event. * @private */ function pd (event) { const retv = privateData.get(event); console.assert( retv != null, "'this' is expected an Event object, but got", event ); return retv; } /** * https://dom.spec.whatwg.org/#set-the-canceled-flag * @param data {PrivateData} private data. */ function setCancelFlag (data) { if (data.passiveListener != null) { if ( typeof console !== 'undefined' && typeof console.error === 'function' ) { console.error( 'Unable to preventDefault inside passive event listener invocation.', data.passiveListener ); } return; } if (!data.event.cancelable) { return; } data.canceled = true; if (typeof data.event.preventDefault === 'function') { data.event.preventDefault(); } } /** * @see https://dom.spec.whatwg.org/#interface-event * @private */ /** * The event wrapper. * @constructor * @param {EventTarget} eventTarget The event target of this dispatching. * @param {Event|{type:string}} event The original event to wrap. */ function Event (eventTarget, event) { privateData.set(this, { eventTarget, event, eventPhase: 2, currentTarget: eventTarget, canceled: false, stopped: false, immediateStopped: false, passiveListener: null, timeStamp: event.timeStamp || Date.now() }); // https://heycam.github.io/webidl/#Unforgeable Object.defineProperty(this, 'isTrusted', { value: false, enumerable: true }); // Define accessors const keys = Object.keys(event); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; if (!(key in this)) { Object.defineProperty(this, key, defineRedirectDescriptor(key)); } } } // Should be enumerable, but class methods are not enumerable. Event.prototype = { /** * The type of this event. * @type {string} */ get type () { return pd(this).event.type; }, /** * The target of this event. * @type {EventTarget} */ get target () { return pd(this).eventTarget; }, /** * The target of this event. * @type {EventTarget} */ get currentTarget () { return pd(this).currentTarget; }, /** * @returns {EventTarget[]} The composed path of this event. */ composedPath () { const currentTarget = pd(this).currentTarget; if (currentTarget == null) { return []; } return [currentTarget]; }, /** * Constant of NONE. * @type {number} */ get NONE () { return 0; }, /** * Constant of CAPTURING_PHASE. * @type {number} */ get CAPTURING_PHASE () { return 1; }, /** * Constant of AT_TARGET. * @type {number} */ get AT_TARGET () { return 2; }, /** * Constant of BUBBLING_PHASE. * @type {number} */ get BUBBLING_PHASE () { return 3; }, /** * The target of this event. * @type {number} */ get eventPhase () { return pd(this).eventPhase; }, /** * Stop event bubbling. * @returns {void} */ stopPropagation () { const data = pd(this); data.stopped = true; if (typeof data.event.stopPropagation === 'function') { data.event.stopPropagation(); } }, /** * Stop event bubbling. * @returns {void} */ stopImmediatePropagation () { const data = pd(this); data.stopped = true; data.immediateStopped = true; if (typeof data.event.stopImmediatePropagation === 'function') { data.event.stopImmediatePropagation(); } }, /** * The flag to be bubbling. * @type {boolean} */ get bubbles () { return Boolean(pd(this).event.bubbles); }, /** * The flag to be cancelable. * @type {boolean} */ get cancelable () { return Boolean(pd(this).event.cancelable); }, /** * Cancel this event. * @returns {void} */ preventDefault () { setCancelFlag(pd(this)); }, /** * The flag to indicate cancellation state. * @type {boolean} */ get defaultPrevented () { return pd(this).canceled; }, /** * The flag to be composed. * @type {boolean} */ get composed () { return Boolean(pd(this).event.composed); }, /** * The unix time of this event. * @type {number} */ get timeStamp () { return pd(this).timeStamp; }, /** * The target of this event. * @type {EventTarget} * @deprecated */ get srcElement () { return pd(this).eventTarget; }, /** * The flag to stop event bubbling. * @type {boolean} * @deprecated */ get cancelBubble () { return pd(this).stopped; }, set cancelBubble (value) { if (!value) { return; } const data = pd(this); data.stopped = true; if (typeof data.event.cancelBubble === 'boolean') { data.event.cancelBubble = true; } }, /** * The flag to indicate cancellation state. * @type {boolean} * @deprecated */ get returnValue () { return !pd(this).canceled; }, set returnValue (value) { if (!value) { setCancelFlag(pd(this)); } }, /** * Initialize this event object. But do nothing under event dispatching. * @param {string} type The event type. * @param {boolean} [bubbles=false] The flag to be possible to bubble up. * @param {boolean} [cancelable=false] The flag to be possible to cancel. * @deprecated */ initEvent () { // Do nothing. } }; // `constructor` is not enumerable. Object.defineProperty(Event.prototype, 'constructor', { value: Event, configurable: true, writable: true }); // Ensure `event instanceof window.Event` is `true`. if (typeof window !== 'undefined' && typeof window.Event !== 'undefined') { Object.setPrototypeOf(Event.prototype, window.Event.prototype); // Make association for wrappers. wrappers.set(window.Event.prototype, Event); } /** * Get the property descriptor to redirect a given property. * @param {string} key Property name to define property descriptor. * @returns {PropertyDescriptor} The property descriptor to redirect the property. * @private */ function defineRedirectDescriptor (key) { return { get () { return pd(this).event[key]; }, set (value) { pd(this).event[key] = value; }, configurable: true, enumerable: true }; } /** * Get the property descriptor to call a given method property. * @param {string} key Property name to define property descriptor. * @returns {PropertyDescriptor} The property descriptor to call the method property. * @private */ function defineCallDescriptor (key) { return { value () { const event = pd(this).event; return event[key].apply(event, arguments); }, configurable: true, enumerable: true }; } /** * Define new wrapper class. * @param {Function} BaseEvent The base wrapper class. * @param {Object} proto The prototype of the original event. * @returns {Function} The defined wrapper class. * @private */ function defineWrapper (BaseEvent, proto) { const keys = Object.keys(proto); if (keys.length === 0) { return BaseEvent; } /** CustomEvent */ function CustomEvent (eventTarget, event) { BaseEvent.call(this, eventTarget, event); } CustomEvent.prototype = Object.create(BaseEvent.prototype, { constructor: { value: CustomEvent, configurable: true, writable: true } }); // Define accessors. for (let i = 0; i < keys.length; ++i) { const key = keys[i]; if (!(key in BaseEvent.prototype)) { const descriptor = Object.getOwnPropertyDescriptor(proto, key); const isFunc = typeof descriptor.value === 'function'; Object.defineProperty( CustomEvent.prototype, key, isFunc ? defineCallDescriptor(key) : defineRedirectDescriptor(key) ); } } return CustomEvent; } /** * Get the wrapper class of a given prototype. * @param {Object} proto The prototype of the original event to get its wrapper. * @returns {Function} The wrapper class. * @private */ function getWrapper (proto) { if (proto == null || proto === Object.prototype) { return Event; } let wrapper = wrappers.get(proto); if (wrapper == null) { wrapper = defineWrapper(getWrapper(Object.getPrototypeOf(proto)), proto); wrappers.set(proto, wrapper); } return wrapper; } /** * Wrap a given event to management a dispatching. * @param {EventTarget} eventTarget The event target of this dispatching. * @param {Object} event The event to wrap. * @returns {Event} The wrapper instance. * @private */ function wrapEvent (eventTarget, event) { const Wrapper = getWrapper(Object.getPrototypeOf(event)); return new Wrapper(eventTarget, event); } /** * Get the immediateStopped flag of a given event. * @param {Event} event The event to get. * @returns {boolean} The flag to stop propagation immediately. * @private */ function isStopped (event) { return pd(event).immediateStopped; } /** * Set the current event phase of a given event. * @param {Event} event The event to set current target. * @param {number} eventPhase New event phase. * @returns {void} * @private */ function setEventPhase (event, eventPhase) { pd(event).eventPhase = eventPhase; } /** * Set the current target of a given event. * @param {Event} event The event to set current target. * @param {EventTarget|null} currentTarget New current target. * @returns {void} * @private */ function setCurrentTarget (event, currentTarget) { pd(event).currentTarget = currentTarget; } /** * Set a passive listener of a given event. * @param {Event} event The event to set current target. * @param {Function|null} passiveListener New passive listener. * @returns {void} * @private */ function setPassiveListener (event, passiveListener) { pd(event).passiveListener = passiveListener; } /** * @typedef {object} ListenerNode * @property {Function} listener * @property {1|2|3} listenerType * @property {boolean} passive * @property {boolean} once * @property {ListenerNode|null} next * @private */ /** * @type {WeakMap<object, Map<string, ListenerNode>>} * @private */ const listenersMap = new WeakMap(); // Listener types const CAPTURE = 1; const BUBBLE = 2; const ATTRIBUTE = 3; /** * Check whether a given value is an object or not. * @param {any} x The value to check. * @returns {boolean} `true` if the value is an object. */ function isObject (x) { return x !== null && typeof x === 'object'; // eslint-disable-line no-restricted-syntax } /** * Get listeners. * @param {EventTarget} eventTarget The event target to get. * @returns {Map<string, ListenerNode>} The listeners. * @private */ function getListeners (eventTarget) { const listeners = listenersMap.get(eventTarget); if (listeners == null) { throw new TypeError( "'this' is expected an EventTarget object, but got another value." ); } return listeners; } /** * Get the property descriptor for the event attribute of a given event. * @param {string} eventName The event name to get property descriptor. * @returns {PropertyDescriptor} The property descriptor. * @private */ function defineEventAttributeDescriptor (eventName) { return { get () { const listeners = getListeners(this); let node = listeners.get(eventName); while (node != null) { if (node.listenerType === ATTRIBUTE) { return node.listener; } node = node.next; } return null; }, set (listener) { if (typeof listener !== 'function' && !isObject(listener)) { listener = null; // eslint-disable-line no-param-reassign } const listeners = getListeners(this); // Traverse to the tail while removing old value. let prev = null; let node = listeners.get(eventName); while (node != null) { if (node.listenerType === ATTRIBUTE) { // Remove old value. if (prev !== null) { prev.next = node.next; } else if (node.next !== null) { listeners.set(eventName, node.next); } else { listeners.delete(eventName); } } else { prev = node; } node = node.next; } // Add new value. if (listener !== null) { const newNode = { listener, listenerType: ATTRIBUTE, passive: false, once: false, next: null }; if (prev === null) { listeners.set(eventName, newNode); } else { prev.next = newNode; } } }, configurable: true, enumerable: true }; } /** * Define an event attribute (e.g. `eventTarget.onclick`). * @param {Object} eventTargetPrototype The event target prototype to define an event attrbite. * @param {string} eventName The event name to define. * @returns {void} */ function defineEventAttribute (eventTargetPrototype, eventName) { Object.defineProperty( eventTargetPrototype, `on${eventName}`, defineEventAttributeDescriptor(eventName) ); } /** * Define a custom EventTarget with event attributes. * @param {string[]} eventNames Event names for event attributes. * @returns {EventTarget} The custom EventTarget. * @private */ function defineCustomEventTarget (eventNames) { /** CustomEventTarget */ function CustomEventTarget () { EventTarget.call(this); } CustomEventTarget.prototype = Object.create(EventTarget.prototype, { constructor: { value: CustomEventTarget, configurable: true, writable: true } }); for (let i = 0; i < eventNames.length; ++i) { defineEventAttribute(CustomEventTarget.prototype, eventNames[i]); } return CustomEventTarget; } /** * EventTarget. * * - This is constructor if no arguments. * - This is a function which returns a CustomEventTarget constructor if there are arguments. * * For example: * * class A extends EventTarget {} * class B extends EventTarget("message") {} * class C extends EventTarget("message", "error") {} * class D extends EventTarget(["message", "error"]) {} */ function EventTarget () { /* eslint-disable consistent-return */ if (this instanceof EventTarget) { listenersMap.set(this, new Map()); return; } if (arguments.length === 1 && Array.isArray(arguments[0])) { return defineCustomEventTarget(arguments[0]); } if (arguments.length > 0) { const types = new Array(arguments.length); for (let i = 0; i < arguments.length; ++i) { types[i] = arguments[i]; } return defineCustomEventTarget(types); } throw new TypeError('Cannot call a class as a function'); /* eslint-enable consistent-return */ } // Should be enumerable, but class methods are not enumerable. EventTarget.prototype = { /** * Add a given listener to this event target. * @param {string} eventName The event name to add. * @param {Function} listener The listener to add. * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener. * @returns {void} */ addEventListener (eventName, listener, options) { if (listener == null) { return; } if (typeof listener !== 'function' && !isObject(listener)) { throw new TypeError("'listener' should be a function or an object."); } const listeners = getListeners(this); const optionsIsObj = isObject(options); const capture = optionsIsObj ? Boolean(options.capture) : Boolean(options); const listenerType = capture ? CAPTURE : BUBBLE; const newNode = { listener, listenerType, passive: optionsIsObj && Boolean(options.passive), once: optionsIsObj && Boolean(options.once), next: null }; // Set it as the first node if the first node is null. let node = listeners.get(eventName); if (node === undefined) { listeners.set(eventName, newNode); return; } // Traverse to the tail while checking duplication.. let prev = null; while (node != null) { if ( node.listener === listener && node.listenerType === listenerType ) { // Should ignore duplication. return; } prev = node; node = node.next; } // Add it. prev.next = newNode; }, /** * Remove a given listener from this event target. * @param {string} eventName The event name to remove. * @param {Function} listener The listener to remove. * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener. * @returns {void} */ removeEventListener (eventName, listener, options) { if (listener == null) { return; } const listeners = getListeners(this); const capture = isObject(options) ? Boolean(options.capture) : Boolean(options); const listenerType = capture ? CAPTURE : BUBBLE; let prev = null; let node = listeners.get(eventName); while (node != null) { if ( node.listener === listener && node.listenerType === listenerType ) { if (prev !== null) { prev.next = node.next; } else if (node.next !== null) { listeners.set(eventName, node.next); } else { listeners.delete(eventName); } return; } prev = node; node = node.next; } }, /** * Dispatch a given event. * @param {Event|{type:string}} event The event to dispatch. * @returns {boolean} `false` if canceled. */ dispatchEvent (event) { if (event == null || typeof event.type !== 'string') { throw new TypeError('"event.type" should be a string.'); } // If listeners aren't registered, terminate. const listeners = getListeners(this); const eventName = event.type; let node = listeners.get(eventName); if (node == null) { return true; } // Since we cannot rewrite several properties, so wrap object. const wrappedEvent = wrapEvent(this, event); // This doesn't process capturing phase and bubbling phase. // This isn't participating in a tree. let prev = null; while (node != null) { // Remove this listener if it's once if (node.once) { if (prev !== null) { prev.next = node.next; } else if (node.next !== null) { listeners.set(eventName, node.next); } else { listeners.delete(eventName); } } else { prev = node; } // Call this listener setPassiveListener( wrappedEvent, node.passive ? node.listener : null ); if (typeof node.listener === 'function') { try { node.listener.call(this, wrappedEvent); } catch (err) { if ( typeof console !== 'undefined' && typeof console.error === 'function' ) { console.error(err); } } } else if ( node.listenerType !== ATTRIBUTE && typeof node.listener.handleEvent === 'function' ) { node.listener.handleEvent(wrappedEvent); } // Break if `event.stopImmediatePropagation` was called. if (isStopped(wrappedEvent)) { break; } node = node.next; } setPassiveListener(wrappedEvent, null); setEventPhase(wrappedEvent, 0); setCurrentTarget(wrappedEvent, null); return !wrappedEvent.defaultPrevented; } }; // `constructor` is not enumerable. Object.defineProperty(EventTarget.prototype, 'constructor', { value: EventTarget, configurable: true, writable: true }); // Ensure `eventTarget instanceof window.EventTarget` is `true`. if ( typeof window !== 'undefined' && typeof window.EventTarget !== 'undefined' ) { Object.setPrototypeOf(EventTarget.prototype, window.EventTarget.prototype); } export default EventTarget; export { defineEventAttribute, EventTarget }; // >>>># sourceMappingURL=event-target-shim.mjs.map