UNPKG

typewrite-simple

Version:
846 lines (811 loc) 35.8 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Typewrite = factory()); })(this, (function () { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var raf$1 = {exports: {}}; var performanceNow$1 = {exports: {}}; var performanceNow = performanceNow$1.exports; var hasRequiredPerformanceNow; function requirePerformanceNow () { if (hasRequiredPerformanceNow) return performanceNow$1.exports; hasRequiredPerformanceNow = 1; // Generated by CoffeeScript 1.12.2 (function() { var getNanoSeconds, hrtime, loadTime, moduleLoadTime, nodeLoadTime, upTime; if ((typeof performance !== "undefined" && performance !== null) && performance.now) { performanceNow$1.exports = function() { return performance.now(); }; } else if ((typeof process !== "undefined" && process !== null) && process.hrtime) { performanceNow$1.exports = function() { return (getNanoSeconds() - nodeLoadTime) / 1e6; }; hrtime = process.hrtime; getNanoSeconds = function() { var hr; hr = hrtime(); return hr[0] * 1e9 + hr[1]; }; moduleLoadTime = getNanoSeconds(); upTime = process.uptime() * 1e9; nodeLoadTime = moduleLoadTime - upTime; } else if (Date.now) { performanceNow$1.exports = function() { return Date.now() - loadTime; }; loadTime = Date.now(); } else { performanceNow$1.exports = function() { return new Date().getTime() - loadTime; }; loadTime = new Date().getTime(); } }).call(performanceNow); return performanceNow$1.exports; } var hasRequiredRaf; function requireRaf () { if (hasRequiredRaf) return raf$1.exports; hasRequiredRaf = 1; var now = requirePerformanceNow() , root = typeof window === 'undefined' ? commonjsGlobal : window , vendors = ['moz', 'webkit'] , suffix = 'AnimationFrame' , raf = root['request' + suffix] , caf = root['cancel' + suffix] || root['cancelRequest' + suffix]; for(var i = 0; !raf && i < vendors.length; i++) { raf = root[vendors[i] + 'Request' + suffix]; caf = root[vendors[i] + 'Cancel' + suffix] || root[vendors[i] + 'CancelRequest' + suffix]; } // Some versions of FF have rAF but not cAF if(!raf || !caf) { var last = 0 , id = 0 , queue = [] , frameDuration = 1000 / 60; raf = function(callback) { if(queue.length === 0) { var _now = now() , next = Math.max(0, frameDuration - (_now - last)); last = next + _now; setTimeout(function() { var cp = queue.slice(0); // Clear queue here to prevent // callbacks from appending listeners // to the current frame's queue queue.length = 0; for(var i = 0; i < cp.length; i++) { if(!cp[i].cancelled) { try{ cp[i].callback(last); } catch(e) { setTimeout(function() { throw e }, 0); } } } }, Math.round(next)); } queue.push({ handle: ++id, callback: callback, cancelled: false }); return id }; caf = function(handle) { for(var i = 0; i < queue.length; i++) { if(queue[i].handle === handle) { queue[i].cancelled = true; } } }; } raf$1.exports = function(fn) { // Wrap in a new function to prevent // `cancel` potentially being assigned // to the native rAF function return raf.call(root, fn) }; raf$1.exports.cancel = function() { caf.apply(root, arguments); }; raf$1.exports.polyfill = function(object) { if (!object) { object = root; } object.requestAnimationFrame = raf; object.cancelAnimationFrame = caf; }; return raf$1.exports; } var rafExports = requireRaf(); var raf = /*@__PURE__*/getDefaultExportFromCjs(rafExports); /** * Check if a string contains a HTML tag or not * * @param {String} string String to check for HTML tag * @return {Boolean} True|False * */ var doesStringContainHTMLTag = function (string) { var regexp = new RegExp(/<[a-z][\s\S]*>/i); return regexp.test(string); }; /** * Get the DOM element from a string * - Create temporary div element * - Change innerHTML of div element to the string * - Return the first child of the temporary div element * * @param {String} string String to convert into a DOM node */ var getDOMElementFromString = function (string) { var div = document.createElement('div'); div.innerHTML = string; return div.childNodes; }; /** * Return a random integer between min/max values * * @param {Number} min Minimum number to generate * @param {Number} max Maximum number to generate */ var getRandomInteger = function (min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }; /** * Add styles to document head * * @param {String} styles CSS styles to add * @returns {void} */ var addStyles = function (styles) { var styleBlock = document.createElement('style'); styleBlock.appendChild(document.createTextNode(styles)); document.head.appendChild(styleBlock); }; var EVENT_NAMES = { TYPE_CHARACTER: 'TYPE_CHARACTER', REMOVE_CHARACTER: 'REMOVE_CHARACTER', REMOVE_ALL: 'REMOVE_ALL', REMOVE_LAST_VISIBLE_NODE: 'REMOVE_LAST_VISIBLE_NODE', PAUSE_FOR: 'PAUSE_FOR', CALL_FUNCTION: 'CALL_FUNCTION', ADD_HTML_TAG_ELEMENT: 'ADD_HTML_TAG_ELEMENT', CHANGE_DELETE_SPEED: 'CHANGE_DELETE_SPEED', CHANGE_DELAY: 'CHANGE_DELAY', CHANGE_CURSOR: 'CHANGE_CURSOR', PASTE_STRING: 'PASTE_STRING' }; var VISIBLE_NODE_TYPES = { HTML_TAG: 'HTML_TAG', TEXT_NODE: 'TEXT_NODE' }; var STYLES = ".Typewrite__cursor{-webkit-animation:Typewrite-cursor 1s infinite;animation:Typewrite-cursor 1s infinite;margin-left:1px}@-webkit-keyframes Typewrite-cursor{0%{opacity:0}50%{opacity:1}100%{opacity:0}}@keyframes Typewrite-cursor{0%{opacity:0}50%{opacity:1}100%{opacity:0}}"; var Typewrite = /** @class */ (function () { function Typewrite(container, options) { var _this = this; this.state = { cursorAnimation: null, lastFrameTime: null, pauseUntil: null, eventQueue: [], eventLoop: null, eventLoopPaused: false, reverseCalledEvents: [], calledEvents: [], visibleNodes: [], initialOptions: null, elements: { container: null, wrapper: document.createElement('span'), cursor: document.createElement('span') } }; this.options = { strings: '', cursor: '|', delay: 'natural', pauseFor: 1500, deleteSpeed: 'natural', loop: false, autoStart: false, devMode: false, skipAddStyles: false, wrapperClassName: 'Typewrite__wrapper', cursorClassName: 'Typewrite__cursor', stringSplitter: null, onCreateTextNode: null, onRemoveNode: null, onStep: null }; /** * Replace all child nodes of provided element with * state wrapper element used for typewrite effect */ this.setupWrapperElement = function () { if (!_this.state.elements.container) { return; } _this.state.elements.wrapper.className = _this.options.wrapperClassName; _this.state.elements.cursor.className = _this.options.cursorClassName; _this.state.elements.cursor.innerHTML = _this.options.cursor; _this.state.elements.container.innerHTML = ''; _this.state.elements.container.appendChild(_this.state.elements.wrapper); _this.state.elements.container.appendChild(_this.state.elements.cursor); }; /** * Start typewrite effect */ this.start = function () { _this.state.eventLoopPaused = false; _this.runEventLoop(); return _this; }; /** * Pause the event loop */ this.pause = function () { _this.state.eventLoopPaused = true; return _this; }; /** * Destroy current running instance */ this.stop = function () { if (_this.state.eventLoop) { rafExports.cancel(_this.state.eventLoop); _this.state.eventLoop = null; } return _this; }; /** * Add pause event to queue for ms provided * * @param {Number} ms Time in ms to pause for * @return {Typewrite} */ this.pauseFor = function (ms) { _this.addEventToQueue(EVENT_NAMES.PAUSE_FOR, { ms: ms }); return _this; }; /** * Start typewrite effect by typing * out all strings provided * * @return {Typewrite} */ this.typeOutAllStrings = function () { if (typeof _this.options.strings === 'string') { _this.typeString(_this.options.strings).pauseFor(_this.options.pauseFor); return _this; } _this.options.strings.forEach(function (string) { _this.typeString(string).pauseFor(_this.options.pauseFor).deleteAll(_this.options.deleteSpeed); }); return _this; }; /** * Adds string characters to event queue for typing * * @param {String} string String to type * @param {HTMLElement} node Node to add character inside of * @return {Typewrite} */ this.typeString = function (string, node) { if (node === void 0) { node = null; } if (doesStringContainHTMLTag(string)) { return _this.typeOutHTMLString(string, node); } if (string) { var stringSplitter = (_this.options || {}).stringSplitter; var characters = typeof stringSplitter === 'function' ? stringSplitter(string) : string.split(''); _this.typeCharacters(characters, node); } return _this; }; /** * Adds entire strings to event queue for paste effect * * @param {String} string String to paste * @param {HTMLElement} node Node to add string inside of * @return {Typewrite} */ this.pasteString = function (string, node) { if (node === void 0) { node = null; } if (doesStringContainHTMLTag(string)) { return _this.typeOutHTMLString(string, node, true); } if (string) { _this.addEventToQueue(EVENT_NAMES.PASTE_STRING, { character: string, node: node }); } return _this; }; /** * Type out a string which is wrapper around HTML tag * * @param {String} string String to type * @param {HTMLElement} parentNode Node to add inner nodes to * @return {Typewrite} */ this.typeOutHTMLString = function (string, parentNode, pasteEffect) { if (parentNode === void 0) { parentNode = null; } var childNodes = getDOMElementFromString(string); if (childNodes.length > 0) { for (var i = 0; i < childNodes.length; i++) { var node = childNodes[i]; var nodeHTML = node.innerHTML; if (node && node.nodeType !== 3) { // Reset innerText of HTML element node.innerHTML = ''; // Add event queue item to insert HTML tag before typing characters _this.addEventToQueue(EVENT_NAMES.ADD_HTML_TAG_ELEMENT, { node: node, parentNode: parentNode }); pasteEffect ? _this.pasteString(nodeHTML, node) : _this.typeString(nodeHTML, node); } else { if (node.textContent) { pasteEffect ? _this.pasteString(node.textContent, parentNode) : _this.typeString(node.textContent, parentNode); } } } } return _this; }; /** * Add delete all characters to event queue * @param {ISpeed} speed to delete all visibles nodes, can be number or 'natural' * @return {Typewrite} */ this.deleteAll = function (speed) { if (speed === void 0) { speed = 'natural'; } _this.addEventToQueue(EVENT_NAMES.REMOVE_ALL, { speed: speed }); return _this; }; /** * Change delete speed * * @param {Number} speed Speed to use for deleting characters * @return {Typewrite} */ this.changeDeleteSpeed = function (speed) { if (!speed) { throw new Error('Must provide new delete speed'); } _this.addEventToQueue(EVENT_NAMES.CHANGE_DELETE_SPEED, { speed: speed }); return _this; }; /** * Change delay when typing * * @param {Number} delay Delay when typing out characters * @return {Typewrite} */ this.changeDelay = function (delay) { if (!delay) { throw new Error('Must provide new delay'); } _this.addEventToQueue(EVENT_NAMES.CHANGE_DELAY, { delay: delay }); return _this; }; /** * Change cursor * @param {String} cursor /string to represent as cursor * @return {Typewrite} */ this.changeCursor = function (cursor) { if (!cursor) { throw new Error('Must provide new cursor'); } _this.addEventToQueue(EVENT_NAMES.CHANGE_CURSOR, { cursor: cursor }); return _this; }; /** * Add delete character to event queue for amount of characters provided * * @param {Number} amount Number of characters to remove * @return {Typewrite} */ this.deleteChars = function (amount) { if (!amount) { throw new Error('Must provide amount of characters to delete'); } for (var i = 0; i < amount; i++) { _this.addEventToQueue(EVENT_NAMES.REMOVE_CHARACTER); } return _this; }; /** * Add an event item to call a callback function * * @param {cb} cb Callback function to call * @param {Object} thisArg thisArg to use when calling function * @return {Typewrite} */ this.callFunction = function (cb, thisArg) { if (!cb || typeof cb !== 'function') { throw new Error('Callback must be a function'); } _this.addEventToQueue(EVENT_NAMES.CALL_FUNCTION, { cb: cb, thisArg: thisArg }); return _this; }; /** * Add type character event for each character * * @param {Array} characters Array of characters * @param {HTMLElement} node Node to add character inside of * @return {Typewrite} */ this.typeCharacters = function (characters, node) { if (node === void 0) { node = null; } if (!characters || !Array.isArray(characters)) { throw new Error('Characters must be an array'); } characters.forEach(function (character) { _this.addEventToQueue(EVENT_NAMES.TYPE_CHARACTER, { character: character, node: node }); }); return _this; }; /** * Add remove character event for each character * * @param {Array} characters Array of characters * @return {Typewrite} */ this.removeCharacters = function (characters) { if (!characters || !Array.isArray(characters)) { throw new Error('Characters must be an array'); } characters.forEach(function () { _this.addEventToQueue(EVENT_NAMES.REMOVE_CHARACTER); }); return _this; }; /** * Add an event to the event queue * * @param {String} eventName Name of the event * @param {Object} eventArgs Arguments to pass to event callback * @param {Boolean} prepend Prepend to begining of event queue * @return {Typewrite} */ this.addEventToQueue = function (eventName, eventArgs, prepend) { return _this.addEventToStateProperty(eventName, eventArgs, prepend, 'eventQueue'); }; /** * Add an event to reverse called events used for looping * * @param {String} eventName Name of the event * @param {Object} eventArgs Arguments to pass to event callback * @param {Boolean} prepend Prepend to begining of event queue * @return {Typewrite} */ this.addReverseCalledEvent = function (eventName, eventArgs, prepend) { if (prepend === void 0) { prepend = false; } var loop = _this.options.loop; if (!loop) { return _this; } return _this.addEventToStateProperty(eventName, eventArgs, prepend, 'reverseCalledEvents'); }; /** * Add an event to correct state property * * @param {String} eventName Name of the event * @param {Object} eventArgs Arguments to pass to event callback * @param {Boolean} prepend Prepend to begining of event queue * @param {String} property Property name of state object * @return {Typewrite} */ this.addEventToStateProperty = function (eventName, eventArgs, prepend, property) { var eventItem = { eventName: eventName, eventArgs: eventArgs || {} }; if (prepend) { // @ts-ignore _this.state[property] = __spreadArray([eventItem], _this.state[property], true); } else { // @ts-ignore _this.state[property] = __spreadArray(__spreadArray([], _this.state[property], true), [eventItem], false); } return _this; }; /** * Run the event loop and do anything inside of the queue */ this.runEventLoop = function () { var _a, _b; if (!_this.state.lastFrameTime) { _this.state.lastFrameTime = Date.now(); } // Setup variables to calculate if this frame should run var nowTime = Date.now(); var delta = nowTime - _this.state.lastFrameTime; if (!_this.state.eventQueue.length) { if (!_this.options.loop) { return; } // Reset event queue if we are looping _this.state.eventQueue = __spreadArray([], _this.state.calledEvents, true); _this.state.calledEvents = []; _this.options = __assign({}, _this.state.initialOptions); } // Request next frame _this.state.eventLoop = raf(_this.runEventLoop); // Check if event loop is paused if (_this.state.eventLoopPaused) { return; } // Check if state has pause until time if (_this.state.pauseUntil) { // Check if event loop should be paused if (nowTime < _this.state.pauseUntil) { return; } // Reset pause time _this.state.pauseUntil = null; } // Make a clone of event queue var eventQueue = __spreadArray([], _this.state.eventQueue, true); // Get first event from queue var currentEvent = eventQueue.shift(); // Setup delay variable var delay = 0; // Check if frame should run or be // skipped based on fps interval if (currentEvent.eventName === EVENT_NAMES.REMOVE_LAST_VISIBLE_NODE || currentEvent.eventName === EVENT_NAMES.REMOVE_CHARACTER) { delay = _this.options.deleteSpeed === 'natural' ? getRandomInteger(40, 80) : _this.options.deleteSpeed; } else { delay = _this.options.delay === 'natural' ? getRandomInteger(120, 160) : _this.options.delay; } if (delta <= delay) { return; } // Get current event args var eventName = currentEvent.eventName, eventArgs = currentEvent.eventArgs; _this.logInDevMode({ currentEvent: currentEvent, state: _this.state, delay: delay }); (_b = (_a = _this.options).onStep) === null || _b === void 0 ? void 0 : _b.call(_a, currentEvent); // Run item from event loop switch (eventName) { case EVENT_NAMES.PASTE_STRING: case EVENT_NAMES.TYPE_CHARACTER: { var character = eventArgs.character, node = eventArgs.node; var textNode = document.createTextNode(character); var textNodeToUse = textNode; if (_this.options.onCreateTextNode && typeof _this.options.onCreateTextNode === 'function') { textNodeToUse = _this.options.onCreateTextNode(character, textNode); } if (textNodeToUse) { if (node) { node.appendChild(textNodeToUse); } else { _this.state.elements.wrapper.appendChild(textNodeToUse); } } _this.state.visibleNodes = __spreadArray(__spreadArray([], _this.state.visibleNodes, true), [ { type: VISIBLE_NODE_TYPES.TEXT_NODE, character: character, node: textNodeToUse } ], false); break; } case EVENT_NAMES.REMOVE_CHARACTER: { eventQueue.unshift({ eventName: EVENT_NAMES.REMOVE_LAST_VISIBLE_NODE, eventArgs: { removingCharacterNode: true } }); break; } case EVENT_NAMES.PAUSE_FOR: { var ms = currentEvent.eventArgs.ms; _this.state.pauseUntil = Date.now() + parseInt(ms); break; } case EVENT_NAMES.CALL_FUNCTION: { var _c = currentEvent.eventArgs, cb = _c.cb, thisArg = _c.thisArg; cb.call(thisArg, { elements: _this.state.elements }); break; } case EVENT_NAMES.ADD_HTML_TAG_ELEMENT: { var _d = currentEvent.eventArgs, node = _d.node, parentNode = _d.parentNode; if (!parentNode) { _this.state.elements.wrapper.appendChild(node); } else { parentNode.appendChild(node); } _this.state.visibleNodes = __spreadArray(__spreadArray([], _this.state.visibleNodes, true), [ { type: VISIBLE_NODE_TYPES.HTML_TAG, node: node, parentNode: parentNode || _this.state.elements.wrapper } ], false); break; } case EVENT_NAMES.REMOVE_ALL: { var visibleNodes = _this.state.visibleNodes; var speed = eventArgs.speed; var removeAllEventItems = []; // Change speed before deleteing if (speed) { removeAllEventItems.push({ eventName: EVENT_NAMES.CHANGE_DELETE_SPEED, eventArgs: { speed: speed, temp: true } }); } for (var i = 0, length_1 = visibleNodes.length; i < length_1; i++) { removeAllEventItems.push({ eventName: EVENT_NAMES.REMOVE_LAST_VISIBLE_NODE, eventArgs: { removingCharacterNode: false } }); } // Change speed back to normal after deleteing if (speed) { removeAllEventItems.push({ eventName: EVENT_NAMES.CHANGE_DELETE_SPEED, eventArgs: { speed: _this.options.deleteSpeed, temp: true } }); } eventQueue.unshift.apply(eventQueue, removeAllEventItems); break; } case EVENT_NAMES.REMOVE_LAST_VISIBLE_NODE: { var removingCharacterNode = currentEvent.eventArgs.removingCharacterNode; if (_this.state.visibleNodes.length) { var _e = _this.state.visibleNodes.pop(), type = _e.type, node = _e.node, character = _e.character; if (_this.options.onRemoveNode && typeof _this.options.onRemoveNode === 'function') { _this.options.onRemoveNode({ node: node, character: character }); } if (node) { node.parentNode.removeChild(node); } // Remove extra node as current deleted one is just an empty wrapper node if (type === VISIBLE_NODE_TYPES.HTML_TAG && removingCharacterNode) { eventQueue.unshift({ eventName: EVENT_NAMES.REMOVE_LAST_VISIBLE_NODE, eventArgs: {} }); } } break; } case EVENT_NAMES.CHANGE_DELETE_SPEED: { _this.options.deleteSpeed = currentEvent.eventArgs.speed; break; } case EVENT_NAMES.CHANGE_DELAY: { _this.options.delay = currentEvent.eventArgs.delay; break; } case EVENT_NAMES.CHANGE_CURSOR: { _this.options.cursor = currentEvent.eventArgs.cursor; _this.state.elements.cursor.innerHTML = currentEvent.eventArgs.cursor; break; } } // Add que item to called queue if we are looping if (_this.options.loop) { if (currentEvent.eventName !== EVENT_NAMES.REMOVE_LAST_VISIBLE_NODE && !(currentEvent.eventArgs && currentEvent.eventArgs.temp)) { _this.state.calledEvents = __spreadArray(__spreadArray([], _this.state.calledEvents, true), [currentEvent], false); } } // Replace state event queue with cloned queue _this.state.eventQueue = eventQueue; // Set last frame time so it can be used to calculate next frame _this.state.lastFrameTime = nowTime; }; /** * Clear all characters to event queue * * @return {Typewrite} */ this.clearEventQueue = function () { _this.state.eventQueue = []; return _this; }; if (container) { if (typeof container === 'string') { var containerElement = document.querySelector(container); if (!containerElement) { throw new Error('Could not find container element'); } this.state.elements.container = containerElement; } else { this.state.elements.container = container; } } if (options) { this.options = __assign(__assign({}, this.options), options); } // Make a copy of the options used to reset options when looping this.state.initialOptions = __assign({}, this.options); this.init(); } Typewrite.prototype.init = function () { this.setupWrapperElement(); this.addEventToQueue(EVENT_NAMES.CHANGE_CURSOR, { cursor: this.options.cursor }, true); this.addEventToQueue(EVENT_NAMES.REMOVE_ALL, null, true); if (window && !window.___TYPEWRITE_JS_STYLES_ADDED___ && !this.options.skipAddStyles) { addStyles(STYLES); window.___TYPEWRITE_JS_STYLES_ADDED___ = true; } if (this.options.autoStart === true && this.options.strings) { this.typeOutAllStrings().start(); } }; /** * Log a message in development mode * * @param {IMessage} message Message or item to console.log */ Typewrite.prototype.logInDevMode = function (message) { if (this.options.devMode) { console.log(message); } }; return Typewrite; }()); return Typewrite; }));