Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
import {
minErr,
arrayRemove,
concat,
extend,
isDefined,
isFunction,
isObject,
isString,
isUndefined,
lowercase,
getNodeName,
shallowCopy,
} from "../utils";
import { CACHE, EXPANDO } from "../../core/cache/cache";
/** @type {number} */
let jqId = 1;
const DASH_LOWERCASE_REGEXP = /-([a-z])/g;
const UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g;
const MOUSE_EVENT_MAP = { mouseleave: "mouseout", mouseenter: "mouseover" };
const JQLiteMinErr = minErr("jqLite");
const SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
const TAG_NAME_REGEXP = /<([\w:-]+)/;
// Table parts need to be wrapped with `
` or they're
// stripped to their contents when put in a div.
// XHTML parsers do not magically insert elements in the
// same way that tag soup parsers do, so we cannot shorten
// this by omitting or other required elements.
const wrapMap = {
thead: ["table"],
col: ["colgroup", "table"],
tr: ["tbody", "table"],
td: ["tr", "tbody", "table"],
};
wrapMap.tbody =
wrapMap.tfoot =
wrapMap.colgroup =
wrapMap.caption =
wrapMap.thead;
wrapMap.th = wrapMap.td;
export const BOOLEAN_ATTR = {};
"multiple,selected,checked,disabled,readOnly,required,open"
.split(",")
.forEach((value) => {
BOOLEAN_ATTR[lowercase(value)] = value;
});
const BOOLEAN_ELEMENTS = {};
"input,select,option,textarea,button,form,details"
.split(",")
.forEach((value) => {
BOOLEAN_ELEMENTS[value] = true;
});
/**
* JQLite is both a function and an array-like data structure for manipulation of DOM, linking elements to expando cache,
* and execution of chains of functions.
*
* @param {string|Node|Node[]|NodeList|JQLite|ArrayLike|(() => void)|Window} element
* @returns {JQLite}
*/
export function JQLite(element) {
if (element instanceof JQLite) {
return /** @type {JQLite} */ (element);
}
let argIsString = false;
if (isString(element)) {
element = /** @type {string} */ (element).trim();
argIsString = true;
}
if (!(this instanceof JQLite)) {
if (argIsString && /** @type {string} */ (element).charAt(0) !== "<") {
throw JQLiteMinErr(
"nosel",
"Looking up elements via selectors is not supported by JQLite! See: http://docs.angularjs.org/api/angular.element",
);
}
return new JQLite(element);
}
if (argIsString) {
const parsed = parseHtml(/** @type {string} */ (element));
addNodes(this, parsed);
} else if (isFunction(element)) {
onReady(/** @type {Function} */ (element));
} else {
addNodes(this, element);
}
}
/// ///////////////////////////////////////
// Functions which are declared directly.
/// ///////////////////////////////////////
JQLite.prototype = {
toString() {
const value = [];
for (var i = 0; i < this.length; i++) {
value.push(`${this[i]}`);
}
return `[${value.join(", ")}]`;
},
eq(index) {
return index >= 0 ? JQLite(this[index]) : JQLite(this[this.length + index]);
},
length: 0,
};
/**
* @returns {Element[]}
*/
JQLite.prototype.elements = function () {
/** @type {Element[]} */
let elements = [];
for (let index = 0; index < this.length; index++) {
elements.push(this[index]);
}
return elements;
};
/**
* Remove all child nodes of the set of matched elements from the DOM and clears CACHE data, associated with the node.
* @returns {JQLite} The current instance of JQLite.
*/
JQLite.prototype.empty = function () {
for (let i = 0; i < this.length; i++) {
const element = this[i];
dealoc(element, true);
// we may run into situation where we empty a transcluded node
if (
[
Node.ELEMENT_NODE,
Node.DOCUMENT_NODE,
Node.DOCUMENT_FRAGMENT_NODE,
].includes(element.nodeType)
) {
element.replaceChildren();
}
}
return this;
};
/**
* Returns the `$scope` of the element.
* @returns {import("../../core/scope/scope").Scope}
*/
JQLite.prototype.scope = function () {
// Can't use JQLiteData here directly so we stay compatible with jQuery!
return (
getOrSetCacheData(this[0], "$scope") ||
getInheritedData(this[0].parentNode || this[0], ["$isolateScope", "$scope"])
);
};
/**
* Returns the isolate `$scope` of the element.
* @returns {import("../../core/scope/scope").Scope}
*/
JQLite.prototype.isolateScope = function () {
return (
getOrSetCacheData(this[0], "$isolateScope") ||
getOrSetCacheData(this[0], "$isolateScopeNoTemplate")
);
};
/**
* Return instance of controller attached to element
* @param {string} [name] - Controller name
* @returns {any}
*/
JQLite.prototype.controller = function (name) {
return getController(this[0], name);
};
/**
* Return instance of injector attached to element
* @returns {import('../../core/di/internal-injector').InjectorService}
*/
JQLite.prototype.injector = function () {
return getInheritedData(this[0], "$injector");
};
/**
* Adds an event listener to each element in the JQLite collection.
*
* @param {string} type - The event type(s) to listen for. Multiple event types can be specified, separated by a space.
* @param {Function} fn - The function to execute when the event is triggered.
* @returns {JQLite} The JQLite collection for chaining.
*/
JQLite.prototype.on = function (type, fn) {
// Do not add event handlers to non-elements because they will not be cleaned up.
for (let i = 0; i < this.length; i++) {
const element = this[i];
if (!elementAcceptsData(element)) {
return;
}
const expandoStore = getExpando(element, true);
if (!expandoStore.handle) {
expandoStore.handle = createEventHandler(element, expandoStore.events);
}
// http://jsperf.com/string-indexof-vs-split
const types = type.indexOf(" ") >= 0 ? type.split(" ") : [type];
let j = types.length;
const addHandler = function (type, specialHandlerWrapper, noEventListener) {
let eventFns = expandoStore.events[type];
if (!eventFns) {
eventFns = expandoStore.events[type] = [];
eventFns.specialHandlerWrapper = specialHandlerWrapper;
if (type !== "$destroy" && !noEventListener) {
element.addEventListener(type, expandoStore.handle);
}
}
eventFns.push(fn);
};
while (j--) {
type = types[j];
if (MOUSE_EVENT_MAP[type]) {
addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
addHandler(type, undefined, true);
} else {
addHandler(type);
}
}
}
return this;
};
/**
* Removes an event listener to each element in JQLite collection.
*
* @param {string} type - The event type(s) to remove listener from
* @param {Function} [fn] - The function to remove from event type.
* @returns {JQLite}
*/
JQLite.prototype.off = function (type, fn) {
for (let i = 0; i < this.length; i++) {
const element = this[i];
const expandoStore = getExpando(element);
const events = expandoStore && expandoStore.events;
const handle = expandoStore && expandoStore.handle;
if (!handle) return; // no listeners registered
if (!type) {
for (type in events) {
if (type !== "$destroy") {
element.removeEventListener(type, handle);
}
delete events[type];
}
} else {
const removeHandler = function (type) {
const listenerFns = events[type];
if (isDefined(fn) && Array.isArray(listenerFns)) {
arrayRemove(listenerFns, fn);
}
if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
element.removeEventListener(type, handle);
delete events[type];
}
};
type.split(" ").forEach((type) => {
removeHandler(type);
if (MOUSE_EVENT_MAP[type]) {
removeHandler(MOUSE_EVENT_MAP[type]);
}
});
}
removeIfEmptyData(element);
}
return this;
};
/**
* Remove data by name from cache associated with each element in JQLite collection.
* @param {string} name - The key of the data associated with element
* @returns {JQLite}
*/
JQLite.prototype.removeData = function (name) {
for (let i = 0; i < this.length; i++) {
const element = this[i];
removeElementData(element, name);
}
return this;
};
/**
* Gets or sets data on a parent element
* @param {string} name
* @param {any} [value]
* @returns {JQLite|any}
*/
JQLite.prototype.inheritedData = function (name, value) {
for (let i = 0; i < this.length; i++) {
const element = this[0];
let res = getInheritedData(element, name, value);
if (value) {
return this;
} else {
return res;
}
}
};
/**
* Gets or sets innerHTML on the first element in JQLite collection
* @param {string} [value]
* @returns {JQLite|any|undefined}
*/
JQLite.prototype.html = function (value) {
const element = this[0];
if (!element) return undefined;
if (isUndefined(value)) {
return element.innerHTML;
}
dealoc(element, true);
element.innerHTML = value;
return this;
};
/**
* Get the combined text contents of each element in the JQLite collection
* or set the text contents of all elements.
* @param {string} [value]
* @returns {JQLite|string}
*/
JQLite.prototype.text = function (value) {
let res = "";
for (let i = 0; i < this.length; i++) {
const element = this[i];
if (isUndefined(value)) {
// read
const { nodeType } = element;
res +=
nodeType === Node.ELEMENT_NODE || nodeType === Node.TEXT_NODE
? element.textContent
: "";
} else {
// write
element.textContent = value;
}
}
if (isUndefined(value)) {
return res;
} else {
return this;
}
};
/**
* Gets or sets the values of form elements such as input, select and textarea in a JQLite collection.
* @param {any} value
* @returns {JQLite|any}
*/
JQLite.prototype.val = function (value) {
// We can only get or set a value of
for (let i = 0; i < this.length; i++) {
const element = this[i];
if (isUndefined(value)) {
// read
if (element.multiple && getNodeName(element) === "select") {
const result = [];
Array.from(element.options).forEach((option) => {
if (option.selected) {
result.push(option.value || option.text);
}
});
return result;
}
return element.value;
} else {
// write
element.value = value;
return this;
}
}
};
/**
* @param {string|Object} name
* @param {any} value
* @returns
*/
JQLite.prototype.attr = function (name, value) {
for (let i = 0; i < this.length; i++) {
const element = this[i];
let ret;
const { nodeType } = element;
if (
nodeType === Node.TEXT_NODE ||
nodeType === Node.ATTRIBUTE_NODE ||
nodeType === Node.COMMENT_NODE ||
!element.getAttribute
) {
continue;
}
const lowercasedName = lowercase(name);
const isBooleanAttr = BOOLEAN_ATTR[lowercasedName];
if (isObject(name)) {
for (let key in name) {
element.setAttribute(key, isBooleanAttr ? lowercasedName : name[key]);
}
} else if (isDefined(value)) {
// setter
if (value === null || (value === false && isBooleanAttr)) {
element.removeAttribute(name);
} else {
element.setAttribute(name, isBooleanAttr ? lowercasedName : value);
}
} else {
// getter
ret = element.getAttribute(name);
if (isBooleanAttr && ret !== null) {
ret = lowercasedName;
}
// Normalize non-existing attributes to undefined (as jQuery).
return ret === null ? undefined : ret;
}
}
if (isDefined(value) || isObject(name)) {
return this;
}
};
/**
* @param {string|any} key - The key (as a string) to get/set or an object for mass-setting.
* @param {any} [value] - The value to set. If not provided, the function acts as a getter.
* @returns {JQLite|any} - The retrieved data if acting as a getter. Otherwise, returns undefined.
*/
JQLite.prototype.data = function (key, value) {
let i;
const nodeCount = this.length;
if (isUndefined(value)) {
if (isObject(key)) {
// we are a write, but the object properties are the key/values
for (i = 0; i < nodeCount; i++) {
getOrSetCacheData(this[i], key);
}
return this;
}
// we are a read, so read the first child.
const jj = isUndefined(value) ? Math.min(nodeCount, 1) : nodeCount;
for (let j = 0; j < jj; j++) {
const nodeValue = getOrSetCacheData(this[j], key, value);
value = value ? value + nodeValue : nodeValue;
}
return value;
}
// we are a write, so apply to all children
for (i = 0; i < nodeCount; i++) {
getOrSetCacheData(this[i], key, value);
}
return this;
};
JQLite.prototype.replaceWith = function (arg1) {
let value;
let fn = function (element, replaceNode) {
let index;
const parent = element.parentNode;
dealoc(element);
const nodes = new JQLite(replaceNode);
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (index) {
parent.insertBefore(node, index.nextSibling);
} else {
parent.replaceChild(node, element);
}
index = node;
}
};
for (let i = 0; i < this.length; i++) {
addNodes(value, fn(this[i], arg1));
}
return isDefined(value) ? value : this;
};
JQLite.prototype.children = function () {
let value;
let fn = (element) =>
Array.from(element.childNodes).filter(
(child) => child.nodeType === Node.ELEMENT_NODE,
);
for (let i = 0; i < this.length; i++) {
value = JQLite(fn(this[i]));
}
return isDefined(value) ? value : this;
};
/**
* @param {string|JQLite} node
* @returns {JQLite}
*/
JQLite.prototype.append = function (node) {
for (let i = 0; i < this.length; i++) {
const element = this[i];
const { nodeType } = element;
if (
nodeType !== Node.ELEMENT_NODE &&
nodeType !== Node.DOCUMENT_FRAGMENT_NODE
)
return this;
let newNode = new JQLite(node);
for (let i = 0; i < newNode.length; i++) {
const child = newNode[i];
element.appendChild(child);
}
}
return this;
};
/**
* @param {string|JQLite} node
* @returns {JQLite}
*/
JQLite.prototype.prepend = function (node) {
for (let i = 0; i < this.length; i++) {
const element = this[i];
if (element.nodeType === Node.ELEMENT_NODE) {
const index = element.firstChild;
const el = new JQLite(node);
for (let i = 0; i < el.length; i++) {
element.insertBefore(el[i], index);
}
}
}
return this;
};
/**
* @param {string} newElement
* @returns {JQLite}
*/
JQLite.prototype.after = function (newElement) {
for (let i = 0; i < this.length; i++) {
const element = this[i];
let index = element;
const parent = element.parentNode;
if (parent) {
let el = new JQLite(newElement);
for (let i = 0, ii = el.length; i < ii; i++) {
const node = el[i];
parent.insertBefore(node, index.nextSibling);
index = node;
}
}
}
return this;
};
/**
* @param {boolean} [keepData]
* @returns
*/
JQLite.prototype.remove = function (keepData = false) {
for (let i = 0; i < this.length; i++) {
const element = this[i];
removeElement(element, keepData);
}
return this;
};
JQLite.prototype.detach = function () {
for (let i = 0; i < this.length; i++) {
const element = this[i];
removeElement(element, true);
}
return this;
};
JQLite.prototype.parent = function () {
let value;
let fn = (element) => {
const parent = element.parentNode;
return parent && parent.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
? parent
: null;
};
for (let i = 0, ii = this.length; i < ii; i++) {
if (isUndefined(value)) {
value = fn(this[i]);
if (isDefined(value)) {
value = JQLite(value);
}
} else {
addNodes(value, fn(this[i]));
}
}
return isDefined(value) ? value : this;
};
JQLite.prototype.find = function (selector) {
let value;
for (let i = 0, ii = this.length; i < ii; i++) {
const element = this[i];
if (isUndefined(value)) {
if (element.getElementsByTagName) {
value = element.getElementsByTagName(selector);
} else {
value = [];
}
if (isDefined(value)) {
// any function which returns a value needs to be wrapped
value = JQLite(value);
}
} else {
if (element.getElementsByTagName) {
addNodes(value, element.getElementsByTagName(selector));
}
}
}
return isDefined(value) ? value : this;
};
/**
* TODO: REMOVE! This function being used ONLY in tests!
*/
JQLite.prototype.triggerHandler = function (event, extraParameters) {
let value;
let fn = function (element, event, extraParameters) {
let dummyEvent;
let eventFnsCopy;
let handlerArgs;
const eventName = event.type || event;
const expandoStore = getExpando(element);
const events = expandoStore && expandoStore.events;
const eventFns = events && events[eventName];
if (eventFns) {
// Create a dummy event to pass to the handlers
dummyEvent = {
preventDefault() {
this.defaultPrevented = true;
},
isDefaultPrevented() {
return this.defaultPrevented === true;
},
stopImmediatePropagation() {
this.immediatePropagationStopped = true;
},
isImmediatePropagationStopped() {
return this.immediatePropagationStopped === true;
},
stopPropagation: () => {},
type: eventName,
target: element,
};
// If a custom event was provided then extend our dummy event with it
if (event.type) {
dummyEvent = extend(dummyEvent, event);
}
// Copy event handlers in case event handlers array is modified during execution.
eventFnsCopy = shallowCopy(eventFns);
handlerArgs = extraParameters
? [dummyEvent].concat(extraParameters)
: [dummyEvent];
eventFnsCopy.forEach((fn) => {
if (!dummyEvent.isImmediatePropagationStopped()) {
fn.apply(element, handlerArgs);
}
});
}
};
for (let i = 0, ii = this.length; i < ii; i++) {
if (isUndefined(value)) {
fn(this[i], event, extraParameters);
} else {
addNodes(value, fn(this[i], event, extraParameters));
}
}
return isDefined(value) ? value : this;
};
///////////////////////////////////////////////////////////////////
//////////// HELPER FUNCTIONS /////////////////////////
///////////////////////////////////////////////////////////////////
/**
*
* @returns {number} Next unique JQInstance id
*/
function jqNextId() {
return ++jqId;
}
/**
* @param {string} _all
* @param {string} letter
* @returns {string}
*/
function fnCamelCaseReplace(_all, letter) {
return letter.toUpperCase();
}
/**
* Converts kebab-case to camelCase.
* @param {string} name Name to normalize
* @returns {string}
*/
export function kebabToCamel(name) {
return name.replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace);
}
/**
* Converts sname to camelCase.
* @param {string} name
* @returns {string}
*/
export function snakeToCamel(name) {
return name.replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace);
}
/**
* Removes expando data from this element. If key is provided, only
* its field is removed. If data is empty, also removes `ExpandoStore`
* from cache.
* @param {Element} element
* @param {string} [name] - key of field to remove
*/
export function removeElementData(element, name) {
const expandoId = element[EXPANDO];
const expandoStore = expandoId && CACHE.get(expandoId);
if (expandoStore) {
if (name) {
delete expandoStore.data[name];
} else {
expandoStore.data = {};
}
removeIfEmptyData(element);
}
}
/**
* Stores data associated with an element inside the expando property of the DOM element.
*
* @see {@link https://developer.mozilla.org/en-US/docs/Glossary/Expando MDN Glossary: Expando}
*
* @param {Element} element
* @param {boolean} [createIfNecessary=false]
* @returns {import("../../core/cache/cache").ExpandoStore}
*/
function getExpando(element, createIfNecessary = false) {
let expandoId = element[EXPANDO];
let expandoStore = expandoId && CACHE.get(expandoId);
if (createIfNecessary && !expandoStore) {
element[EXPANDO] = expandoId = jqNextId();
expandoStore = {
events: {},
data: {},
handle: null,
};
CACHE.set(expandoId, expandoStore);
}
return expandoStore;
}
/**
* Checks if the string contains HTML tags or entities.
* @param {string} html
* @returns {boolean} True if the string is plain text, false if it contains HTML tags or entities.
*/
export function isTextNode(html) {
return !/<|?\w+;/.test(html);
}
/**
* Check if element can accept expando data
* @param {Element} node
* @returns {boolean}
*/
function elementAcceptsData(node) {
// The window object can accept data but has no nodeType
// Otherwise we are only interested in elements (1) and documents (9)
switch (node.nodeType) {
case Node.ELEMENT_NODE:
case Node.DOCUMENT_NODE:
case undefined: // window.object
return true;
default:
return false;
}
}
/**
* @param {string} html
* @returns {DocumentFragment}
*/
export function buildFragment(html) {
/** @type {HTMLDivElement} */
let tmp;
let tag;
let wrap;
let tempFragment = document.createDocumentFragment();
let nodes = [];
let i;
if (isTextNode(html)) {
// Convert non-html into a text node
nodes.push(document.createTextNode(html));
} else {
// Convert html into DOM nodes
tmp = tempFragment.appendChild(document.createElement("div"));
tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
wrap = wrapMap[tag] || [];
// Create wrappers & descend into them
i = wrap.length;
while (--i > -1) {
tmp.appendChild(document.createElement(wrap[i]));
tmp = /** @type {HTMLDivElement} */ (tmp.firstChild);
}
tmp.innerHTML = html;
nodes = concat(nodes, tmp.childNodes);
tmp = /** @type {HTMLDivElement} */ (tempFragment.firstChild);
tmp.textContent = "";
}
let fragment = document.createDocumentFragment();
fragment.append(...nodes);
return fragment;
}
/**
* @param {string} html
* @returns {NodeListOf | HTMLElement[]}
*/
function parseHtml(html) {
let regEx = SINGLE_TAG_REGEXP.exec(html);
if (regEx) {
return [document.createElement(regEx[1])];
}
let fragment = buildFragment(html);
if (fragment) {
return fragment.childNodes;
}
return [];
}
/**
* @param {Element} element
* @param {boolean} [onlyDescendants]
* @returns {void}
*/
export function dealoc(element, onlyDescendants) {
if (!element) return;
if (!onlyDescendants && elementAcceptsData(element))
cleanElementData([element]);
if (element.querySelectorAll) {
cleanElementData(element.querySelectorAll("*"));
}
}
/**
* If `ExpandoStore.data` and `ExpandoStore.events` are empty,
* then delete element's `ExpandoStore` and set its `ExpandoId`
* to undefined.
* @param {Element} element
*/
function removeIfEmptyData(element) {
const expandoId = element[EXPANDO];
const { events, data } = CACHE.get(expandoId);
if (
(!data || !Object.keys(data).length) &&
(!events || !Object.keys(events).length)
) {
CACHE.delete(expandoId);
element[EXPANDO] = undefined; // don't delete DOM expandos. Chrome don't like it
}
}
/**
* Gets or sets cache data for a given element.
*
* @param {Element} element - The DOM element to get or set data on.
* @param {string|Object} key - The key (as a string) to get/set or an object for mass-setting.
* @param {*} [value] - The value to set. If not provided, the function acts as a getter.
* @returns {*} - The retrieved data if acting as a getter. Otherwise, returns undefined.
*/
export function getOrSetCacheData(element, key, value) {
if (elementAcceptsData(element)) {
let prop;
const isSimpleSetter = isDefined(value);
const isSimpleGetter = !isSimpleSetter && key && !isObject(key);
const massGetter = !key;
const expandoStore = getExpando(element, !isSimpleGetter);
const data = expandoStore && expandoStore.data;
if (isSimpleSetter) {
data[kebabToCamel(key)] = value;
} else {
if (massGetter) {
return data;
}
if (isSimpleGetter) {
// don't force creation of expandoStore if it doesn't exist yet
return data && data[kebabToCamel(key)];
}
// mass-setter: data({key1: val1, key2: val2})
for (prop in key) {
data[kebabToCamel(prop)] = key[prop];
}
}
} else {
// TODO: check should occur perhaps prior at compilation level that this is a valid element
}
}
/**
* Adds nodes or elements to the root array-like object.
*
* @param {JQLite} root - The array-like object to which elements will be added.
* @param {(Node|Array|NodeList|Object)} elements - The elements to add to the root. This can be a single DOM node, an array-like object (such as an Array or NodeList), or any other object.
*/
function addNodes(root, elements) {
if (!elements) return;
// If a Node (the most common case)
if (elements.nodeType) {
root[root.length++] = elements;
return;
}
const length = elements.length;
// If elements is an Array or NodeList, but not a Window object
if (typeof length === "number" && elements.window !== elements) {
for (let i = 0; i < length; i++) {
root[root.length++] = elements[i];
}
} else {
root[root.length++] = elements;
}
}
/**
* @param {Node} element
* @param {string} name
* @returns
*/
function getController(element, name) {
return getInheritedData(element, `$${name || "ngController"}Controller`);
}
/**
*
* @param {Node} element
* @param {string|string[]} name
* @param {any} [value]
* @returns
*/
function getInheritedData(element, name, value) {
// if element is the document object work with the html element instead
// this makes $(document).scope() possible
if (element.nodeType === Node.DOCUMENT_NODE) {
element = /** @type {Document} */ (element).documentElement;
}
const names = Array.isArray(name) ? name : [name];
while (element) {
for (let i = 0, ii = names.length; i < ii; i++) {
if (
isDefined(
(value = getOrSetCacheData(
/** @type {Element} */ (element),
names[i],
)),
)
)
return value;
}
// If dealing with a document fragment node with a host element, and no parent, use the host
// element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
// to lookup parent controllers.
element =
element.parentNode ||
(element.nodeType === Node.DOCUMENT_FRAGMENT_NODE &&
/** @type {ShadowRoot} */ (element).host);
}
}
/**
*
* @param {Element} element
* @param {boolean} keepData
*/
export function removeElement(element, keepData = false) {
if (!keepData) dealoc(element);
const parent = element.parentNode;
if (parent) parent.removeChild(element);
}
/**
* Execute a function on `DOMContentLoaded`
* @param {Function} fn
*/
function onReady(fn) {
function trigger() {
document.removeEventListener("DOMContentLoaded", trigger);
fn();
}
// check if document is already loaded
if (document.readyState === "complete") {
setTimeout(fn);
} else {
// We can not use JQLite since we are not done loading.
document.addEventListener("DOMContentLoaded", trigger);
}
}
function createEventHandler(element, events) {
const eventHandler = function (event, type) {
// jQuery specific api
event.isDefaultPrevented = function () {
return event.defaultPrevented;
};
let eventFns = events[type || event.type];
const eventFnsLength = eventFns ? eventFns.length : 0;
if (!eventFnsLength) return;
if (isUndefined(event.immediatePropagationStopped)) {
const originalStopImmediatePropagation = event.stopImmediatePropagation;
event.stopImmediatePropagation = function () {
event.immediatePropagationStopped = true;
if (event.stopPropagation) {
event.stopPropagation();
}
if (originalStopImmediatePropagation) {
originalStopImmediatePropagation.call(event);
}
};
}
event.isImmediatePropagationStopped = function () {
return event.immediatePropagationStopped === true;
};
// Some events have special handlers that wrap the real handler
const handlerWrapper =
eventFns.specialHandlerWrapper || defaultHandlerWrapper;
// Copy event handlers in case event handlers array is modified during execution.
if (eventFnsLength > 1) {
eventFns = shallowCopy(eventFns);
}
for (let i = 0; i < eventFnsLength; i++) {
if (!event.isImmediatePropagationStopped()) {
handlerWrapper(element, event, eventFns[i]);
}
}
};
return eventHandler;
}
function defaultHandlerWrapper(element, event, handler) {
handler.call(element, event);
}
/**
* @param {Node} target
* @param {*} event
* @param {*} handler
*/
function specialMouseHandlerWrapper(target, event, handler) {
// Refer to jQuery's implementation of mouseenter & mouseleave
// Read about mouseenter and mouseleave:
// http://www.quirksmode.org/js/events_mouse.html#link8
const related = event.relatedTarget;
// For mousenter/leave call the handler if related is outside the target.
// NB: No relatedTarget if the mouse left/entered the browser window
if (!related || (related !== target && !target.contains(related))) {
handler.call(target, event);
}
}
/**
* @param {string|JQLite} elementStr
* @returns {string} Returns the string representation of the element.
*/
export function startingTag(elementStr) {
const clone = JQLite(elementStr)[0].cloneNode(true);
const element = JQLite(clone).empty();
const elemHtml = JQLite("").append(element[0]).html();
try {
return element[0].nodeType === Node.TEXT_NODE
? lowercase(elemHtml)
: elemHtml
.match(/^(<[^>]+>)/)[1]
.replace(/^<([\w-]+)/, function (match, nodeName) {
return "<" + lowercase(nodeName);
});
} catch (e) {
return lowercase(elemHtml);
}
}
/**
* Return the DOM siblings between the first and last node in the given array.
* @param {JQLite|Array} nodes An array-like object
* @returns {JQLite} the inputted object or a JQLite collection containing the nodes
*/
export function getBlockNodes(nodes) {
// TODO(perf): update `nodes` instead of creating a new object?
let node = nodes[0];
const endNode = nodes[nodes.length - 1];
let blockNodes;
for (let i = 1; node !== endNode && (node = node.nextSibling); i++) {
if (blockNodes || nodes[i] !== node) {
if (!blockNodes) {
// use element to avoid circular dependency
blockNodes = Array.prototype.slice.call(nodes, 0, i);
}
blockNodes.push(node);
}
}
return JQLite(blockNodes || nodes);
}
export function getBooleanAttrName(element, name) {
// check dom last since we will most likely fail on name
const booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
// booleanAttr is here twice to minimize DOM access
return booleanAttr && BOOLEAN_ELEMENTS[getNodeName(element)] && booleanAttr;
}
/**
* Takes an array of elements, calls any `$destroy` event handlers, removes any data in cache, and finally removes any
* listeners.
* @param {NodeListOf|Element[]} nodes
*/
export function cleanElementData(nodes) {
for (let i = 0, ii = nodes.length; i < ii; i++) {
const events = (CACHE.get(nodes[i][EXPANDO]) || {}).events;
if (events && events.$destroy) {
JQLite(nodes[i]).triggerHandler("$destroy");
}
removeElementData(nodes[i]);
JQLite.prototype.off.call(JQLite(nodes[i]));
}
}