Repository URL to install this package:
|
Version:
3.0.0 ▾
|
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.dawaAutocomplete = global.dawaAutocomplete || {})));
}(this, (function (exports) { 'use strict';
var global$1 = typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};
// shim for using process in browser
// based off https://github.com/defunctzombie/node-process/blob/master/browser.js
function defaultSetTimout() {
throw new Error('setTimeout has not been defined');
}
function defaultClearTimeout() {
throw new Error('clearTimeout has not been defined');
}
var cachedSetTimeout = defaultSetTimout;
var cachedClearTimeout = defaultClearTimeout;
if (typeof global$1.setTimeout === 'function') {
cachedSetTimeout = setTimeout;
}
if (typeof global$1.clearTimeout === 'function') {
cachedClearTimeout = clearTimeout;
}
function runTimeout(fun) {
if (cachedSetTimeout === setTimeout) {
//normal enviroments in sane situations
return setTimeout(fun, 0);
}
// if setTimeout wasn't available but was latter defined
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
cachedSetTimeout = setTimeout;
return setTimeout(fun, 0);
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedSetTimeout(fun, 0);
} catch (e) {
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedSetTimeout.call(null, fun, 0);
} catch (e) {
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
return cachedSetTimeout.call(this, fun, 0);
}
}
}
function runClearTimeout(marker) {
if (cachedClearTimeout === clearTimeout) {
//normal enviroments in sane situations
return clearTimeout(marker);
}
// if clearTimeout wasn't available but was latter defined
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
cachedClearTimeout = clearTimeout;
return clearTimeout(marker);
}
try {
// when when somebody has screwed with setTimeout but no I.E. maddness
return cachedClearTimeout(marker);
} catch (e) {
try {
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
return cachedClearTimeout.call(null, marker);
} catch (e) {
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
// Some versions of I.E. have different rules for clearTimeout vs setTimeout
return cachedClearTimeout.call(this, marker);
}
}
}
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;
function cleanUpNextTick() {
if (!draining || !currentQueue) {
return;
}
draining = false;
if (currentQueue.length) {
queue = currentQueue.concat(queue);
} else {
queueIndex = -1;
}
if (queue.length) {
drainQueue();
}
}
function drainQueue() {
if (draining) {
return;
}
var timeout = runTimeout(cleanUpNextTick);
draining = true;
var len = queue.length;
while (len) {
currentQueue = queue;
queue = [];
while (++queueIndex < len) {
if (currentQueue) {
currentQueue[queueIndex].run();
}
}
queueIndex = -1;
len = queue.length;
}
currentQueue = null;
draining = false;
runClearTimeout(timeout);
}
function nextTick(fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
}
queue.push(new Item(fun, args));
if (queue.length === 1 && !draining) {
runTimeout(drainQueue);
}
}
// v8 likes predictible objects
function Item(fun, array) {
this.fun = fun;
this.array = array;
}
Item.prototype.run = function () {
this.fun.apply(null, this.array);
};
var title = 'browser';
var platform = 'browser';
var browser = true;
var env = {};
var argv = [];
var version = ''; // empty string to avoid regexp issues
var versions = {};
var release = {};
var config = {};
function noop() {}
var on = noop;
var addListener = noop;
var once = noop;
var off = noop;
var removeListener = noop;
var removeAllListeners = noop;
var emit = noop;
function binding(name) {
throw new Error('process.binding is not supported');
}
function cwd() {
return '/';
}
function chdir(dir) {
throw new Error('process.chdir is not supported');
}
function umask() {
return 0;
}
// from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js
var performance = global$1.performance || {};
var performanceNow = performance.now || performance.mozNow || performance.msNow || performance.oNow || performance.webkitNow || function () {
return new Date().getTime();
};
// generate timestamp or delta
// see http://nodejs.org/api/process.html#process_process_hrtime
function hrtime(previousTimestamp) {
var clocktime = performanceNow.call(performance) * 1e-3;
var seconds = Math.floor(clocktime);
var nanoseconds = Math.floor(clocktime % 1 * 1e9);
if (previousTimestamp) {
seconds = seconds - previousTimestamp[0];
nanoseconds = nanoseconds - previousTimestamp[1];
if (nanoseconds < 0) {
seconds--;
nanoseconds += 1e9;
}
}
return [seconds, nanoseconds];
}
var startTime = new Date();
function uptime() {
var currentTime = new Date();
var dif = currentTime - startTime;
return dif / 1000;
}
var process = {
nextTick: nextTick,
title: title,
browser: browser,
env: env,
argv: argv,
version: version,
versions: versions,
on: on,
addListener: addListener,
once: once,
off: off,
removeListener: removeListener,
removeAllListeners: removeAllListeners,
emit: emit,
binding: binding,
cwd: cwd,
chdir: chdir,
umask: umask,
hrtime: hrtime,
platform: platform,
release: release,
config: config,
uptime: uptime
};
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
/**
* Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A cached reference to the hasOwnProperty function.
*/
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* A constructor function that will create blank objects.
* @constructor
*/
function Blank() {}
Blank.prototype = Object.create(null);
/**
* Used to prevent property collisions between our "map" and its prototype.
* @param {!Object<string, *>} map The map to check.
* @param {string} property The property to check.
* @return {boolean} Whether map has property.
*/
var has = function has(map, property) {
return hasOwnProperty.call(map, property);
};
/**
* Creates an map object without a prototype.
* @return {!Object}
*/
var createMap = function createMap() {
return new Blank();
};
/**
* Keeps track of information needed to perform diffs for a given DOM node.
* @param {!string} nodeName
* @param {?string=} key
* @constructor
*/
function NodeData(nodeName, key) {
/**
* The attributes and their values.
* @const {!Object<string, *>}
*/
this.attrs = createMap();
/**
* An array of attribute name/value pairs, used for quickly diffing the
* incomming attributes to see if the DOM node's attributes need to be
* updated.
* @const {Array<*>}
*/
this.attrsArr = [];
/**
* The incoming attributes for this Node, before they are updated.
* @const {!Object<string, *>}
*/
this.newAttrs = createMap();
/**
* Whether or not the statics have been applied for the node yet.
* {boolean}
*/
this.staticsApplied = false;
/**
* The key used to identify this node, used to preserve DOM nodes when they
* move within their parent.
* @const
*/
this.key = key;
/**
* Keeps track of children within this node by their key.
* {!Object<string, !Element>}
*/
this.keyMap = createMap();
/**
* Whether or not the keyMap is currently valid.
* @type {boolean}
*/
this.keyMapValid = true;
/**
* Whether or the associated node is, or contains, a focused Element.
* @type {boolean}
*/
this.focused = false;
/**
* The node name for this node.
* @const {string}
*/
this.nodeName = nodeName;
/**
* @type {?string}
*/
this.text = null;
}
/**
* Initializes a NodeData object for a Node.
*
* @param {Node} node The node to initialize data for.
* @param {string} nodeName The node name of node.
* @param {?string=} key The key that identifies the node.
* @return {!NodeData} The newly initialized data object
*/
var initData = function initData(node, nodeName, key) {
var data = new NodeData(nodeName, key);
node['__incrementalDOMData'] = data;
return data;
};
/**
* Retrieves the NodeData object for a Node, creating it if necessary.
*
* @param {?Node} node The Node to retrieve the data for.
* @return {!NodeData} The NodeData for this Node.
*/
var getData = function getData(node) {
importNode(node);
return node['__incrementalDOMData'];
};
/**
* Imports node and its subtree, initializing caches.
*
* @param {?Node} node The Node to import.
*/
var importNode = function importNode(node) {
if (node['__incrementalDOMData']) {
return;
}
var isElement = node instanceof Element;
var nodeName = isElement ? node.localName : node.nodeName;
var key = isElement ? node.getAttribute('key') : null;
var data = initData(node, nodeName, key);
if (key) {
getData(node.parentNode).keyMap[key] = node;
}
if (isElement) {
var attributes = node.attributes;
var attrs = data.attrs;
var newAttrs = data.newAttrs;
var attrsArr = data.attrsArr;
for (var i = 0; i < attributes.length; i += 1) {
var attr = attributes[i];
var name = attr.name;
var value = attr.value;
attrs[name] = value;
newAttrs[name] = undefined;
attrsArr.push(name);
attrsArr.push(value);
}
}
for (var child = node.firstChild; child; child = child.nextSibling) {
importNode(child);
}
};
/**
* Gets the namespace to create an element (of a given tag) in.
* @param {string} tag The tag to get the namespace for.
* @param {?Node} parent
* @return {?string} The namespace to create the tag in.
*/
var getNamespaceForTag = function getNamespaceForTag(tag, parent) {
if (tag === 'svg') {
return 'http://www.w3.org/2000/svg';
}
if (getData(parent).nodeName === 'foreignObject') {
return null;
}
return parent.namespaceURI;
};
/**
* Creates an Element.
* @param {Document} doc The document with which to create the Element.
* @param {?Node} parent
* @param {string} tag The tag for the Element.
* @param {?string=} key A key to identify the Element.
* @return {!Element}
*/
var createElement = function createElement(doc, parent, tag, key) {
var namespace = getNamespaceForTag(tag, parent);
var el = undefined;
if (namespace) {
el = doc.createElementNS(namespace, tag);
} else {
el = doc.createElement(tag);
}
initData(el, tag, key);
return el;
};
/**
* Creates a Text Node.
* @param {Document} doc The document with which to create the Element.
* @return {!Text}
*/
var createText = function createText(doc) {
var node = doc.createTextNode('');
initData(node, '#text', null);
return node;
};
/**
* Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @const */
var notifications = {
/**
* Called after patch has compleated with any Nodes that have been created
* and added to the DOM.
* @type {?function(Array<!Node>)}
*/
nodesCreated: null,
/**
* Called after patch has compleated with any Nodes that have been removed
* from the DOM.
* Note it's an applications responsibility to handle any childNodes.
* @type {?function(Array<!Node>)}
*/
nodesDeleted: null
};
/**
* Keeps track of the state of a patch.
* @constructor
*/
function Context() {
/**
* @type {(Array<!Node>|undefined)}
*/
this.created = notifications.nodesCreated && [];
/**
* @type {(Array<!Node>|undefined)}
*/
this.deleted = notifications.nodesDeleted && [];
}
/**
* @param {!Node} node
*/
Context.prototype.markCreated = function (node) {
if (this.created) {
this.created.push(node);
}
};
/**
* @param {!Node} node
*/
Context.prototype.markDeleted = function (node) {
if (this.deleted) {
this.deleted.push(node);
}
};
/**
* Notifies about nodes that were created during the patch opearation.
*/
Context.prototype.notifyChanges = function () {
if (this.created && this.created.length > 0) {
notifications.nodesCreated(this.created);
}
if (this.deleted && this.deleted.length > 0) {
notifications.nodesDeleted(this.deleted);
}
};
/**
* Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Keeps track whether or not we are in an attributes declaration (after
* elementOpenStart, but before elementOpenEnd).
* @type {boolean}
*/
var inAttributes = false;
/**
* Keeps track whether or not we are in an element that should not have its
* children cleared.
* @type {boolean}
*/
var inSkip = false;
/**
* Makes sure that a patch closes every node that it opened.
* @param {?Node} openElement
* @param {!Node|!DocumentFragment} root
*/
var assertNoUnclosedTags = function assertNoUnclosedTags(openElement, root) {
if (openElement === root) {
return;
}
var currentElement = openElement;
var openTags = [];
while (currentElement && currentElement !== root) {
openTags.push(currentElement.nodeName.toLowerCase());
currentElement = currentElement.parentNode;
}
throw new Error('One or more tags were not closed:\n' + openTags.join('\n'));
};
/**
* Makes sure that the caller is not where attributes are expected.
* @param {string} functionName
*/
var assertNotInAttributes = function assertNotInAttributes(functionName) {
if (inAttributes) {
throw new Error(functionName + '() can not be called between ' + 'elementOpenStart() and elementOpenEnd().');
}
};
/**
* Makes sure that the caller is not inside an element that has declared skip.
* @param {string} functionName
*/
var assertNotInSkip = function assertNotInSkip(functionName) {
if (inSkip) {
throw new Error(functionName + '() may not be called inside an element ' + 'that has called skip().');
}
};
/**
* Makes sure the patch closes virtual attributes call
*/
var assertVirtualAttributesClosed = function assertVirtualAttributesClosed() {
if (inAttributes) {
throw new Error('elementOpenEnd() must be called after calling ' + 'elementOpenStart().');
}
};
/**
* Makes sure that tags are correctly nested.
* @param {string} nodeName
* @param {string} tag
*/
var assertCloseMatchesOpenTag = function assertCloseMatchesOpenTag(nodeName, tag) {
if (nodeName !== tag) {
throw new Error('Received a call to close "' + tag + '" but "' + nodeName + '" was open.');
}
};
/**
* Updates the state of being in an attribute declaration.
* @param {boolean} value
* @return {boolean} the previous value.
*/
var setInAttributes = function setInAttributes(value) {
var previous = inAttributes;
inAttributes = value;
return previous;
};
/**
* Updates the state of being in a skip element.
* @param {boolean} value
* @return {boolean} the previous value.
*/
var setInSkip = function setInSkip(value) {
var previous = inSkip;
inSkip = value;
return previous;
};
/**
* Copyright 2016 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @param {!Node} node
* @return {boolean} True if the node the root of a document, false otherwise.
*/
var isDocumentRoot = function isDocumentRoot(node) {
// For ShadowRoots, check if they are a DocumentFragment instead of if they
// are a ShadowRoot so that this can work in 'use strict' if ShadowRoots are
// not supported.
return node instanceof Document || node instanceof DocumentFragment;
};
/**
* @param {!Node} node The node to start at, inclusive.
* @param {?Node} root The root ancestor to get until, exclusive.
* @return {!Array<!Node>} The ancestry of DOM nodes.
*/
var getAncestry = function getAncestry(node, root) {
var ancestry = [];
var cur = node;
while (cur !== root) {
ancestry.push(cur);
cur = cur.parentNode;
}
return ancestry;
};
/**
* @param {!Node} node
* @return {!Node} The root node of the DOM tree that contains node.
*/
var getRoot = function getRoot(node) {
var cur = node;
var prev = cur;
while (cur) {
prev = cur;
cur = cur.parentNode;
}
return prev;
};
/**
* @param {!Node} node The node to get the activeElement for.
* @return {?Element} The activeElement in the Document or ShadowRoot
* corresponding to node, if present.
*/
var getActiveElement = function getActiveElement(node) {
var root = getRoot(node);
return isDocumentRoot(root) ? root.activeElement : null;
};
/**
* Gets the path of nodes that contain the focused node in the same document as
* a reference node, up until the root.
* @param {!Node} node The reference node to get the activeElement for.
* @param {?Node} root The root to get the focused path until.
* @return {!Array<Node>}
*/
var getFocusedPath = function getFocusedPath(node, root) {
var activeElement = getActiveElement(node);
if (!activeElement || !node.contains(activeElement)) {
return [];
}
return getAncestry(activeElement, root);
};
/**
* Like insertBefore, but instead instead of moving the desired node, instead
* moves all the other nodes after.
* @param {?Node} parentNode
* @param {!Node} node
* @param {?Node} referenceNode
*/
var moveBefore = function moveBefore(parentNode, node, referenceNode) {
var insertReferenceNode = node.nextSibling;
var cur = referenceNode;
while (cur !== node) {
var next = cur.nextSibling;
parentNode.insertBefore(cur, insertReferenceNode);
cur = next;
}
};
/** @type {?Context} */
var context = null;
/** @type {?Node} */
var currentNode = null;
/** @type {?Node} */
var currentParent = null;
/** @type {?Document} */
var doc = null;
/**
* @param {!Array<Node>} focusPath The nodes to mark.
* @param {boolean} focused Whether or not they are focused.
*/
var markFocused = function markFocused(focusPath, focused) {
for (var i = 0; i < focusPath.length; i += 1) {
getData(focusPath[i]).focused = focused;
}
};
/**
* Returns a patcher function that sets up and restores a patch context,
* running the run function with the provided data.
* @param {function((!Element|!DocumentFragment),!function(T),T=): ?Node} run
* @return {function((!Element|!DocumentFragment),!function(T),T=): ?Node}
* @template T
*/
var patchFactory = function patchFactory(run) {
/**
* TODO(moz): These annotations won't be necessary once we switch to Closure
* Compiler's new type inference. Remove these once the switch is done.
*
* @param {(!Element|!DocumentFragment)} node
* @param {!function(T)} fn
* @param {T=} data
* @return {?Node} node
* @template T
*/
var f = function f(node, fn, data) {
var prevContext = context;
var prevDoc = doc;
var prevCurrentNode = currentNode;
var prevCurrentParent = currentParent;
var previousInAttributes = false;
var previousInSkip = false;
context = new Context();
doc = node.ownerDocument;
currentParent = node.parentNode;
if (process.env.NODE_ENV !== 'production') {
previousInAttributes = setInAttributes(false);
previousInSkip = setInSkip(false);
}
var focusPath = getFocusedPath(node, currentParent);
markFocused(focusPath, true);
var retVal = run(node, fn, data);
markFocused(focusPath, false);
if (process.env.NODE_ENV !== 'production') {
assertVirtualAttributesClosed();
setInAttributes(previousInAttributes);
setInSkip(previousInSkip);
}
context.notifyChanges();
context = prevContext;
doc = prevDoc;
currentNode = prevCurrentNode;
currentParent = prevCurrentParent;
return retVal;
};
return f;
};
/**
* Patches the document starting at node with the provided function. This
* function may be called during an existing patch operation.
* @param {!Element|!DocumentFragment} node The Element or Document
* to patch.
* @param {!function(T)} fn A function containing elementOpen/elementClose/etc.
* calls that describe the DOM.
* @param {T=} data An argument passed to fn to represent DOM state.
* @return {!Node} The patched node.
* @template T
*/
var patchInner = patchFactory(function (node, fn, data) {
currentNode = node;
enterNode();
fn(data);
exitNode();
if (process.env.NODE_ENV !== 'production') {
assertNoUnclosedTags(currentNode, node);
}
return node;
});
/**
* Checks whether or not the current node matches the specified nodeName and
* key.
*
* @param {!Node} matchNode A node to match the data to.
* @param {?string} nodeName The nodeName for this node.
* @param {?string=} key An optional key that identifies a node.
* @return {boolean} True if the node matches, false otherwise.
*/
var matches = function matches(matchNode, nodeName, key) {
var data = getData(matchNode);
// Key check is done using double equals as we want to treat a null key the
// same as undefined. This should be okay as the only values allowed are
// strings, null and undefined so the == semantics are not too weird.
return nodeName === data.nodeName && key == data.key;
};
/**
* Aligns the virtual Element definition with the actual DOM, moving the
* corresponding DOM node to the correct location or creating it if necessary.
* @param {string} nodeName For an Element, this should be a valid tag string.
* For a Text, this should be #text.
* @param {?string=} key The key used to identify this element.
*/
var alignWithDOM = function alignWithDOM(nodeName, key) {
if (currentNode && matches(currentNode, nodeName, key)) {
return;
}
var parentData = getData(currentParent);
var currentNodeData = currentNode && getData(currentNode);
var keyMap = parentData.keyMap;
var node = undefined;
// Check to see if the node has moved within the parent.
if (key) {
var keyNode = keyMap[key];
if (keyNode) {
if (matches(keyNode, nodeName, key)) {
node = keyNode;
} else if (keyNode === currentNode) {
context.markDeleted(keyNode);
} else {
removeChild(currentParent, keyNode, keyMap);
}
}
}
// Create the node if it doesn't exist.
if (!node) {
if (nodeName === '#text') {
node = createText(doc);
} else {
node = createElement(doc, currentParent, nodeName, key);
}
if (key) {
keyMap[key] = node;
}
context.markCreated(node);
}
// Re-order the node into the right position, preserving focus if either
// node or currentNode are focused by making sure that they are not detached
// from the DOM.
if (getData(node).focused) {
// Move everything else before the node.
moveBefore(currentParent, node, currentNode);
} else if (currentNodeData && currentNodeData.key && !currentNodeData.focused) {
// Remove the currentNode, which can always be added back since we hold a
// reference through the keyMap. This prevents a large number of moves when
// a keyed item is removed or moved backwards in the DOM.
currentParent.replaceChild(node, currentNode);
parentData.keyMapValid = false;
} else {
currentParent.insertBefore(node, currentNode);
}
currentNode = node;
};
/**
* @param {?Node} node
* @param {?Node} child
* @param {?Object<string, !Element>} keyMap
*/
var removeChild = function removeChild(node, child, keyMap) {
node.removeChild(child);
context.markDeleted( /** @type {!Node}*/child);
var key = getData(child).key;
if (key) {
delete keyMap[key];
}
};
/**
* Clears out any unvisited Nodes, as the corresponding virtual element
* functions were never called for them.
*/
var clearUnvisitedDOM = function clearUnvisitedDOM() {
var node = currentParent;
var data = getData(node);
var keyMap = data.keyMap;
var keyMapValid = data.keyMapValid;
var child = node.lastChild;
var key = undefined;
if (child === currentNode && keyMapValid) {
return;
}
while (child !== currentNode) {
removeChild(node, child, keyMap);
child = node.lastChild;
}
// Clean the keyMap, removing any unusued keys.
if (!keyMapValid) {
for (key in keyMap) {
child = keyMap[key];
if (child.parentNode !== node) {
context.markDeleted(child);
delete keyMap[key];
}
}
data.keyMapValid = true;
}
};
/**
* Changes to the first child of the current node.
*/
var enterNode = function enterNode() {
currentParent = currentNode;
currentNode = null;
};
/**
* @return {?Node} The next Node to be patched.
*/
var getNextNode = function getNextNode() {
if (currentNode) {
return currentNode.nextSibling;
} else {
return currentParent.firstChild;
}
};
/**
* Changes to the next sibling of the current node.
*/
var nextNode = function nextNode() {
currentNode = getNextNode();
};
/**
* Changes to the parent of the current node, removing any unvisited children.
*/
var exitNode = function exitNode() {
clearUnvisitedDOM();
currentNode = currentParent;
currentParent = currentParent.parentNode;
};
/**
* Makes sure that the current node is an Element with a matching tagName and
* key.
*
* @param {string} tag The element's tag.
* @param {?string=} key The key used to identify this element. This can be an
* empty string, but performance may be better if a unique value is used
* when iterating over an array of items.
* @return {!Element} The corresponding Element.
*/
var coreElementOpen = function coreElementOpen(tag, key) {
nextNode();
alignWithDOM(tag, key);
enterNode();
return (/** @type {!Element} */currentParent
);
};
/**
* Closes the currently open Element, removing any unvisited children if
* necessary.
*
* @return {!Element} The corresponding Element.
*/
var coreElementClose = function coreElementClose() {
if (process.env.NODE_ENV !== 'production') {
setInSkip(false);
}
exitNode();
return (/** @type {!Element} */currentNode
);
};
/**
* Makes sure the current node is a Text node and creates a Text node if it is
* not.
*
* @return {!Text} The corresponding Text Node.
*/
var coreText = function coreText() {
nextNode();
alignWithDOM('#text', null);
return (/** @type {!Text} */currentNode
);
};
/**
* Copyright 2015 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @const */
var symbols = {
default: '__default'
};
/**
* @param {string} name
* @return {string|undefined} The namespace to use for the attribute.
*/
var getNamespace = function getNamespace(name) {
if (name.lastIndexOf('xml:', 0) === 0) {
return 'http://www.w3.org/XML/1998/namespace';
}
if (name.lastIndexOf('xlink:', 0) === 0) {
return 'http://www.w3.org/1999/xlink';
}
};
/**
* Applies an attribute or property to a given Element. If the value is null
* or undefined, it is removed from the Element. Otherwise, the value is set
* as an attribute.
* @param {!Element} el
* @param {string} name The attribute's name.
* @param {?(boolean|number|string)=} value The attribute's value.
*/
var applyAttr = function applyAttr(el, name, value) {
if (value == null) {
el.removeAttribute(name);
} else {
var attrNS = getNamespace(name);
if (attrNS) {
el.setAttributeNS(attrNS, name, value);
} else {
el.setAttribute(name, value);
}
}
};
/**
* Applies a property to a given Element.
* @param {!Element} el
* @param {string} name The property's name.
* @param {*} value The property's value.
*/
var applyProp = function applyProp(el, name, value) {
el[name] = value;
};
/**
* Applies a value to a style declaration. Supports CSS custom properties by
* setting properties containing a dash using CSSStyleDeclaration.setProperty.
* @param {CSSStyleDeclaration} style
* @param {!string} prop
* @param {*} value
*/
var setStyleValue = function setStyleValue(style, prop, value) {
if (prop.indexOf('-') >= 0) {
style.setProperty(prop, /** @type {string} */value);
} else {
style[prop] = value;
}
};
/**
* Applies a style to an Element. No vendor prefix expansion is done for
* property names/values.
* @param {!Element} el
* @param {string} name The attribute's name.
* @param {*} style The style to set. Either a string of css or an object
* containing property-value pairs.
*/
var applyStyle = function applyStyle(el, name, style) {
if (typeof style === 'string') {
el.style.cssText = style;
} else {
el.style.cssText = '';
var elStyle = el.style;
var obj = /** @type {!Object<string,string>} */style;
for (var prop in obj) {
if (has(obj, prop)) {
setStyleValue(elStyle, prop, obj[prop]);
}
}
}
};
/**
* Updates a single attribute on an Element.
* @param {!Element} el
* @param {string} name The attribute's name.
* @param {*} value The attribute's value. If the value is an object or
* function it is set on the Element, otherwise, it is set as an HTML
* attribute.
*/
var applyAttributeTyped = function applyAttributeTyped(el, name, value) {
var type = typeof value === 'undefined' ? 'undefined' : _typeof(value);
if (type === 'object' || type === 'function') {
applyProp(el, name, value);
} else {
applyAttr(el, name, /** @type {?(boolean|number|string)} */value);
}
};
/**
* Calls the appropriate attribute mutator for this attribute.
* @param {!Element} el
* @param {string} name The attribute's name.
* @param {*} value The attribute's value.
*/
var updateAttribute = function updateAttribute(el, name, value) {
var data = getData(el);
var attrs = data.attrs;
if (attrs[name] === value) {
return;
}
var mutator = attributes[name] || attributes[symbols.default];
mutator(el, name, value);
attrs[name] = value;
};
/**
* A publicly mutable object to provide custom mutators for attributes.
* @const {!Object<string, function(!Element, string, *)>}
*/
var attributes = createMap();
// Special generic mutator that's called for any attribute that does not
// have a specific mutator.
attributes[symbols.default] = applyAttributeTyped;
attributes['style'] = applyStyle;
/**
* The offset in the virtual element declaration where the attributes are
* specified.
* @const
*/
var ATTRIBUTES_OFFSET = 3;
/**
* @param {string} tag The element's tag.
* @param {?string=} key The key used to identify this element. This can be an
* empty string, but performance may be better if a unique value is used
* when iterating over an array of items.
* @param {?Array<*>=} statics An array of attribute name/value pairs of the
* static attributes for the Element. These will only be set once when the
* Element is created.
* @param {...*} var_args, Attribute name/value pairs of the dynamic attributes
* for the Element.
* @return {!Element} The corresponding Element.
*/
var elementOpen = function elementOpen(tag, key, statics, var_args) {
if (process.env.NODE_ENV !== 'production') {
assertNotInAttributes('elementOpen');
assertNotInSkip('elementOpen');
}
var node = coreElementOpen(tag, key);
var data = getData(node);
if (!data.staticsApplied) {
if (statics) {
for (var _i = 0; _i < statics.length; _i += 2) {
var name = /** @type {string} */statics[_i];
var value = statics[_i + 1];
updateAttribute(node, name, value);
}
}
// Down the road, we may want to keep track of the statics array to use it
// as an additional signal about whether a node matches or not. For now,
// just use a marker so that we do not reapply statics.
data.staticsApplied = true;
}
/*
* Checks to see if one or more attributes have changed for a given Element.
* When no attributes have changed, this is much faster than checking each
* individual argument. When attributes have changed, the overhead of this is
* minimal.
*/
var attrsArr = data.attrsArr;
var newAttrs = data.newAttrs;
var isNew = !attrsArr.length;
var i = ATTRIBUTES_OFFSET;
var j = 0;
for (; i < arguments.length; i += 2, j += 2) {
var _attr = arguments[i];
if (isNew) {
attrsArr[j] = _attr;
newAttrs[_attr] = undefined;
} else if (attrsArr[j] !== _attr) {
break;
}
var value = arguments[i + 1];
if (isNew || attrsArr[j + 1] !== value) {
attrsArr[j + 1] = value;
updateAttribute(node, _attr, value);
}
}
if (i < arguments.length || j < attrsArr.length) {
for (; i < arguments.length; i += 1, j += 1) {
attrsArr[j] = arguments[i];
}
if (j < attrsArr.length) {
attrsArr.length = j;
}
/*
* Actually perform the attribute update.
*/
for (i = 0; i < attrsArr.length; i += 2) {
var name = /** @type {string} */attrsArr[i];
var value = attrsArr[i + 1];
newAttrs[name] = value;
}
for (var _attr2 in newAttrs) {
updateAttribute(node, _attr2, newAttrs[_attr2]);
newAttrs[_attr2] = undefined;
}
}
return node;
};
/**
* Closes an open virtual Element.
*
* @param {string} tag The element's tag.
* @return {!Element} The corresponding Element.
*/
var elementClose = function elementClose(tag) {
if (process.env.NODE_ENV !== 'production') {
assertNotInAttributes('elementClose');
}
var node = coreElementClose();
if (process.env.NODE_ENV !== 'production') {
assertCloseMatchesOpenTag(getData(node).nodeName, tag);
}
return node;
};
/**
* Declares a virtual Element at the current location in the document that has
* no children.
* @param {string} tag The element's tag.
* @param {?string=} key The key used to identify this element. This can be an
* empty string, but performance may be better if a unique value is used
* when iterating over an array of items.
* @param {?Array<*>=} statics An array of attribute name/value pairs of the
* static attributes for the Element. These will only be set once when the
* Element is created.
* @param {...*} var_args Attribute name/value pairs of the dynamic attributes
* for the Element.
* @return {!Element} The corresponding Element.
*/
var elementVoid = function elementVoid(tag, key, statics, var_args) {
elementOpen.apply(null, arguments);
return elementClose(tag);
};
/**
* Declares a virtual Text at this point in the document.
*
* @param {string|number|boolean} value The value of the Text.
* @param {...(function((string|number|boolean)):string)} var_args
* Functions to format the value which are called only when the value has
* changed.
* @return {!Text} The corresponding text node.
*/
var text = function text(value, var_args) {
if (process.env.NODE_ENV !== 'production') {
assertNotInAttributes('text');
assertNotInSkip('text');
}
var node = coreText();
var data = getData(node);
if (data.text !== value) {
data.text = /** @type {string} */value;
var formatted = value;
for (var i = 1; i < arguments.length; i += 1) {
/*
* Call the formatter function directly to prevent leaking arguments.
* https://github.com/google/incremental-dom/pull/204#issuecomment-178223574
*/
var fn = arguments[i];
formatted = fn(formatted);
}
node.data = formatted;
}
return node;
};
var patch = patchInner;
var elementVoid_1 = elementVoid;
var elementOpen_1 = elementOpen;
var elementClose_1 = elementClose;
var text_1 = text;
var attributes_1 = attributes;
var applyProp_1 = applyProp;
attributes_1.caretpos = function (element, name, value) {
element.setSelectionRange(value, value);
};
attributes_1.value = applyProp_1;
var renderIncrementalDOM = function renderIncrementalDOM(data, onSelect, multiline) {
if (data.suggestions.length > 0 && data.focused) {
elementOpen_1('ul', '', ['class', 'dawa-autocomplete-suggestions', 'role', 'listbox']);
var _loop = function _loop(i) {
var suggestion = data.suggestions[i];
var className = 'dawa-autocomplete-suggestion';
if (data.selected === i) {
className += ' dawa-selected';
}
elementOpen_1('li', '', ['role', 'option'], 'class', className, 'onmousedown', function (e) {
onSelect(i);
e.preventDefault();
});
var rows = suggestion.forslagstekst.split('\n');
rows = rows.map(function (row) {
return row.replace(/ /g, '\xA0');
});
if (multiline) {
text_1(rows[0]);
for (var _i = 1; _i < rows.length; ++_i) {
elementVoid_1('br');
text_1(rows[_i]);
}
} else {
text_1(rows.join(', '));
}
elementClose_1('li');
};
for (var i = 0; i < data.suggestions.length; ++i) {
_loop(i);
}
elementClose_1('ul');
}
};
var defaultRender = function defaultRender(containerElm, data, onSelect, multiline) {
patch(containerElm, function () {
renderIncrementalDOM(data, onSelect, multiline);
});
};
var autocompleteUi = function autocompleteUi(inputElm, options) {
var onSelect = options.onSelect;
var onTextChange = options.onTextChange;
var render = options.render || defaultRender;
var destroyed = false;
var lastEmittedText = '';
var lastEmittedCaretpos = 0;
var suggestionContainerElm = document.createElement('div');
inputElm.parentNode.insertBefore(suggestionContainerElm, inputElm.nextSibling);
var emitTextChange = function emitTextChange(newText, newCaretpos) {
if (lastEmittedText !== newText || lastEmittedCaretpos !== newCaretpos) {
onTextChange(newText, newCaretpos);
lastEmittedText = newText;
lastEmittedCaretpos = newCaretpos;
}
};
var data = {
caretpos: 2,
inputText: '',
selected: 0,
focused: document.activeElement === inputElm,
suggestions: []
};
var handleInputChanged = function handleInputChanged(inputElm) {
var newText = inputElm.value;
var newCaretPos = inputElm.selectionStart;
data.caretpos = newCaretPos;
data.inputText = newText;
emitTextChange(newText, newCaretPos);
};
var update = void 0;
var selectSuggestion = function selectSuggestion(index) {
var selectedSuggestion = data.suggestions[index];
data.inputText = selectedSuggestion.tekst;
data.caretpos = selectedSuggestion.caretpos;
data.suggestions = [];
onSelect(selectedSuggestion);
update(true);
};
var keydownHandler = function keydownHandler(e) {
var key = window.event ? e.keyCode : e.which;
if (data.suggestions.length > 0 && data.focused) {
// down (40)
if (key === 40) {
data.selected = (data.selected + 1) % data.suggestions.length;
update();
}
//up (38)
else if (key === 38) {
data.selected = (data.selected - 1 + data.suggestions.length) % data.suggestions.length;
update();
}
// enter
else if (key === 13 || key === 9) {
selectSuggestion(data.selected);
} else {
return true;
}
e.preventDefault();
return false;
}
};
var focusHandler = function focusHandler() {
data.focused = true;
update();
};
var blurHandler = function blurHandler() {
data.focused = false;
update();
return false;
};
var inputChangeHandler = function inputChangeHandler(e) {
handleInputChanged(e.target);
};
var inputMouseUpHandler = function inputMouseUpHandler(e) {
return handleInputChanged(e.target);
};
var updateScheduled = false;
var updateInput = false;
update = function update(shouldUpdateInput) {
if (shouldUpdateInput) {
updateInput = true;
}
if (!updateScheduled) {
updateScheduled = true;
requestAnimationFrame(function () {
if (destroyed) {
return;
}
updateScheduled = false;
if (updateInput) {
inputElm.value = data.inputText;
inputElm.setSelectionRange(data.caretpos, data.caretpos);
}
updateInput = false;
render(suggestionContainerElm, data, function (i) {
return selectSuggestion(i);
}, options.multiline);
});
}
};
update();
var destroy = function destroy() {
destroyed = true;
inputElm.removeEventListener('keydown', keydownHandler);
inputElm.removeEventListener('blur', blurHandler);
inputElm.removeEventListener('focus', focusHandler);
inputElm.removeEventListener('input', inputChangeHandler);
inputElm.removeEventListener('mouseup', inputMouseUpHandler);
patch(suggestionContainerElm, function () {});
suggestionContainerElm.remove();
};
var setSuggestions = function setSuggestions(suggestions) {
data.suggestions = suggestions;
data.selected = 0;
update();
};
var selectAndClose = function selectAndClose(text) {
data.inputText = text;
data.caretpos = text.length;
data.suggestions = [];
data.selected = 0;
update(true);
};
inputElm.addEventListener('keydown', keydownHandler);
inputElm.addEventListener('blur', blurHandler);
inputElm.addEventListener('focus', focusHandler);
inputElm.addEventListener('input', inputChangeHandler);
inputElm.addEventListener('mouseup', inputMouseUpHandler);
inputElm.setAttribute('aria-autocomplete', 'list');
if (!inputElm.hasAttribute('autocomplete')) {
inputElm.setAttribute('autocomplete', 'off');
}
return {
destroy: destroy,
setSuggestions: setSuggestions,
selectAndClose: selectAndClose
};
};
var formatParams = function formatParams(params) {
return Object.keys(params).map(function (paramName) {
var paramValue = params[paramName];
return paramName + '=' + encodeURIComponent(paramValue);
}).join('&');
};
var delay = function delay(ms) {
return new Promise(function (resolve) {
return setTimeout(resolve, ms);
});
};
var defaultOptions = {
params: {},
minLength: 2,
retryDelay: 500,
renderCallback: function renderCallback() {
/*eslint no-console: 0*/
console.error('No renderCallback supplied');
},
initialRenderCallback: function initialRenderCallback() {
/*eslint no-console: 0*/
console.error('No initialRenderCallback supplied');
},
type: 'adresse',
baseUrl: 'https://dawa.aws.dk',
adgangsadresserOnly: false,
stormodtagerpostnumre: true,
supplerendebynavn: true,
fuzzy: true,
fetchImpl: function fetchImpl(url, params) {
return fetch(url + '?' + formatParams(params), {
mode: 'cors'
}).then(function (result) {
return result.json();
});
}
};
var AutocompleteController = function () {
function AutocompleteController(options) {
classCallCheck(this, AutocompleteController);
options = Object.assign({}, defaultOptions, options || {});
this.options = options;
this.state = {
currentRequest: null,
pendingRequest: null
};
this.selected = null;
}
createClass(AutocompleteController, [{
key: '_getAutocompleteResponse',
value: function _getAutocompleteResponse(text, caretpos, skipVejnavn, adgangsadresseid, supplerendebynavn, stormodtagerpostnumre) {
var params = Object.assign({}, this.options.params, {
q: text,
type: this.options.type,
caretpos: caretpos,
supplerendebynavn: supplerendebynavn,
stormodtagerpostnumre: stormodtagerpostnumre,
multilinje: true
});
if (this.options.fuzzy) {
params.fuzzy = '';
}
if (adgangsadresseid) {
params.adgangsadresseid = adgangsadresseid;
}
if (skipVejnavn) {
params.startfra = 'adgangsadresse';
}
return this.options.fetchImpl(this.options.baseUrl + '/autocomplete', params);
}
}, {
key: '_scheduleRequest',
value: function _scheduleRequest(request) {
if (this.state.currentRequest !== null) {
this.state.pendingRequest = request;
} else {
this.state.currentRequest = request;
this._executeRequest();
}
}
}, {
key: '_executeRequest',
value: function _executeRequest() {
var _this = this;
var request = this.state.currentRequest;
var adgangsadresseid = null;
var skipVejnavn = false;
var text = void 0,
caretpos = void 0;
if (request.selected) {
var item = request.selected;
if (item.type !== this.options.type) {
adgangsadresseid = item.type === 'adgangsadresse' ? item.data.id : null;
skipVejnavn = item.type === 'vejnavn';
text = item.tekst;
caretpos = item.caretpos;
} else {
this.options.selectCallback(item);
this.selected = item;
this._requestCompleted();
return;
}
} else {
text = request.text;
caretpos = request.caretpos;
}
if (request.selectedId) {
var params = {
id: request.selectedId,
type: this.options.type
};
return this.options.fetchImpl(this.options.baseUrl + '/autocomplete', params).then(function (result) {
return _this._handleResponse(request, result);
}, function (error) {
return _this._handleFailedRequest(request, error);
});
} else if (request.selected || request.text.length >= this.options.minLength) {
this._getAutocompleteResponse(text, caretpos, skipVejnavn, adgangsadresseid, this.options.supplerendebynavn, this.options.stormodtagerpostnumre).then(function (result) {
return _this._handleResponse(request, result);
}, function (error) {
return _this._handleFailedRequest(request, error);
});
} else {
this._handleResponse(request, []);
}
}
}, {
key: '_handleFailedRequest',
value: function _handleFailedRequest(request, error) {
var _this2 = this;
console.error('DAWA request failed', error);
return delay(this.options.retryDelay).then(function () {
if (!_this2.state.pendingRequest) {
_this2._scheduleRequest(request);
}
_this2._requestCompleted();
});
}
}, {
key: '_handleResponse',
value: function _handleResponse(request, result) {
if (request.selected) {
if (result.length === 1) {
var item = result[0];
if (item.type === this.options.type) {
this.options.selectCallback(item);
} else {
if (!this.state.pendingRequest) {
this.state.pendingRequest = {
selected: item
};
}
}
} else if (this.options.renderCallback) {
this.options.renderCallback(result);
}
} else if (request.selectedId) {
if (result.length === 1) {
this.selected = result[0];
this.options.initialRenderCallback(result[0].tekst);
}
} else {
if (this.options.renderCallback) {
this.options.renderCallback(result);
}
}
this._requestCompleted();
}
}, {
key: '_requestCompleted',
value: function _requestCompleted() {
this.state.currentRequest = this.state.pendingRequest;
this.state.pendingRequest = null;
if (this.state.currentRequest) {
this._executeRequest();
}
}
}, {
key: 'setRenderCallback',
value: function setRenderCallback(renderCallback) {
this.options.renderCallback = renderCallback;
}
}, {
key: 'setInitialRenderCallback',
value: function setInitialRenderCallback(renderCallback) {
this.options.initialRenderCallback = renderCallback;
}
}, {
key: 'setSelectCallback',
value: function setSelectCallback(selectCallback) {
this.options.selectCallback = selectCallback;
}
}, {
key: 'update',
value: function update(text, caretpos) {
var request = {
text: text,
caretpos: caretpos
};
this._scheduleRequest(request);
}
}, {
key: 'select',
value: function select(item) {
var request = {
selected: item
};
this._scheduleRequest(request);
}
}, {
key: 'selectInitial',
value: function selectInitial(id) {
var request = {
selectedId: id
};
this._scheduleRequest(request);
}
}, {
key: 'destroy',
value: function destroy() {}
}]);
return AutocompleteController;
}();
function dawaAutocomplete(inputElm, options) {
options = Object.assign({ select: function select() {
return null;
} }, options);
var controllerOptions = ['baseUrl', 'minLength', 'params', 'fuzzy', 'stormodtagerpostnumre', 'supplerendebynavn'].reduce(function (memo, optionName) {
if (options.hasOwnProperty(optionName)) {
memo[optionName] = options[optionName];
}
return memo;
}, {});
if (options.adgangsadresserOnly) {
controllerOptions.type = 'adgangsadresse';
} else {
controllerOptions.type = 'adresse';
}
var controller = new AutocompleteController(controllerOptions);
var ui = autocompleteUi(inputElm, {
onSelect: function onSelect(suggestion) {
controller.select(suggestion);
},
onTextChange: function onTextChange(newText, newCaretpos) {
controller.update(newText, newCaretpos);
},
render: options.render,
multiline: options.multiline || false
});
controller.setRenderCallback(function (suggestions) {
return ui.setSuggestions(suggestions);
});
controller.setSelectCallback(function (selected) {
ui.selectAndClose(selected.tekst);
options.select(selected);
});
controller.setInitialRenderCallback(function (text) {
return ui.selectAndClose(text);
});
if (options.id) {
controller.selectInitial(options.id);
}
return {
id: function id(_id) {
return controller.selectInitial(_id);
},
destroy: function destroy() {
return ui.destroy();
},
selected: function selected() {
return controller.selected;
}
};
}
exports.dawaAutocomplete = dawaAutocomplete;
Object.defineProperty(exports, '__esModule', { value: true });
})));