@angular-wave/angular.ts 0.0.43 → 0.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -86,48 +86,17 @@ import { CACHE, EXPANDO } from "../../core/cache/cache";
86
86
  * https://github.com/angular/angular.js/issues/14251 for more information.
87
87
  *
88
88
  * @param {string|Element} element HTML string or Element to be wrapped into jQuery.
89
- * @returns {Object} jQuery object.
89
+ * @returns {JQLite} jQuery object.
90
90
  */
91
91
 
92
92
  /** @type {number} */
93
93
  let jqId = 1;
94
94
 
95
- function jqNextId() {
96
- return ++jqId;
97
- }
98
-
99
95
  const DASH_LOWERCASE_REGEXP = /-([a-z])/g;
100
96
  const UNDERSCORE_LOWERCASE_REGEXP = /_([a-z])/g;
101
97
  const MOUSE_EVENT_MAP = { mouseleave: "mouseout", mouseenter: "mouseover" };
102
98
  const JQLiteMinErr = minErr("jqLite");
103
99
 
104
- /**
105
- * @param {string} _all
106
- * @param {string} letter
107
- * @returns {string}
108
- */
109
- function fnCamelCaseReplace(_all, letter) {
110
- return letter.toUpperCase();
111
- }
112
-
113
- /**
114
- * Converts kebab-case to camelCase.
115
- * @param {string} name Name to normalize
116
- * @returns {string}
117
- */
118
- export function kebabToCamel(name) {
119
- return name.replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace);
120
- }
121
-
122
- /**
123
- * Converts sname to camelCase.
124
- * @param {string} name
125
- * @returns {string}
126
- */
127
- export function snakeToCamel(name) {
128
- return name.replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace);
129
- }
130
-
131
100
  const SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/;
132
101
  const TAG_NAME_REGEXP = /<([\w:-]+)/;
133
102
 
@@ -172,7 +141,7 @@ const BOOLEAN_ELEMENTS = {};
172
141
  */
173
142
  export function JQLite(element) {
174
143
  if (element instanceof JQLite) {
175
- return element;
144
+ return /** @type {JQLite} */ (element);
176
145
  }
177
146
 
178
147
  let argIsString = false;
@@ -202,787 +171,846 @@ export function JQLite(element) {
202
171
  }
203
172
  }
204
173
 
205
- function JQLiteOff(element, type, fn, unsupported) {
206
- if (isDefined(unsupported))
207
- throw JQLiteMinErr(
208
- "offargs",
209
- "jqLite#off() does not support the `selector` argument",
210
- );
174
+ /// ///////////////////////////////////////
175
+ // Functions which are declared directly.
176
+ /// ///////////////////////////////////////
177
+ JQLite.prototype = {
178
+ toString() {
179
+ const value = [];
180
+ forEach(this, (e) => {
181
+ value.push(`${e}`);
182
+ });
183
+ return `[${value.join(", ")}]`;
184
+ },
211
185
 
212
- const expandoStore = getExpando(element);
213
- const events = expandoStore && expandoStore.events;
214
- const handle = expandoStore && expandoStore.handle;
186
+ eq(index) {
187
+ return index >= 0 ? JQLite(this[index]) : JQLite(this[this.length + index]);
188
+ },
215
189
 
216
- if (!handle) return; // no listeners registered
190
+ length: 0,
191
+ };
217
192
 
218
- if (!type) {
219
- for (type in events) {
220
- if (type !== "$destroy") {
221
- element.removeEventListener(type, handle);
222
- }
223
- delete events[type];
193
+ /**
194
+ * Remove all child nodes of the set of matched elements from the DOM and clears CACHE data, associated with the node.
195
+ * @returns {JQLite} The current instance of JQLite.
196
+ */
197
+ JQLite.prototype.empty = function () {
198
+ for (let i = 0; i < this.length; i++) {
199
+ const element = this[i];
200
+ dealoc(element, true);
201
+ // we may run into situation where we empty a transcluded node
202
+ if (
203
+ [
204
+ Node.ELEMENT_NODE,
205
+ Node.DOCUMENT_NODE,
206
+ Node.DOCUMENT_FRAGMENT_NODE,
207
+ ].includes(element.nodeType)
208
+ ) {
209
+ element.replaceChildren();
224
210
  }
225
- } else {
226
- const removeHandler = function (type) {
227
- const listenerFns = events[type];
228
- if (isDefined(fn) && Array.isArray(listenerFns)) {
229
- arrayRemove(listenerFns, fn);
230
- }
231
- if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
232
- element.removeEventListener(type, handle);
233
- delete events[type];
211
+ }
212
+ return this;
213
+ };
214
+
215
+ /**
216
+ * Returns the `$scope` of the element.
217
+ * @returns {import("../../core/scope/scope").Scope}
218
+ */
219
+ JQLite.prototype.scope = function () {
220
+ // Can't use JQLiteData here directly so we stay compatible with jQuery!
221
+ return (
222
+ getOrSetCacheData(this[0], "$scope") ||
223
+ getInheritedData(this[0].parentNode || this[0], ["$isolateScope", "$scope"])
224
+ );
225
+ };
226
+
227
+ /**
228
+ * Returns the isolate `$scope` of the element.
229
+ * @returns {import("../../core/scope/scope").Scope}
230
+ */
231
+ JQLite.prototype.isolateScope = function () {
232
+ return (
233
+ getOrSetCacheData(this[0], "$isolateScope") ||
234
+ getOrSetCacheData(this[0], "$isolateScopeNoTemplate")
235
+ );
236
+ };
237
+
238
+ /**
239
+ * Return instance of controller attached to element
240
+ * @param {string} [name] - Controller name
241
+ * @returns {any}
242
+ */
243
+ JQLite.prototype.controller = function (name) {
244
+ return getController(this[0], name);
245
+ };
246
+
247
+ /**
248
+ * Return instance of injector attached to element
249
+ * @returns {import('../../types').InjectorService}
250
+ */
251
+ JQLite.prototype.injector = function () {
252
+ return getInheritedData(this[0], "$injector");
253
+ };
254
+
255
+ /**
256
+ * Adds an event listener to each element in the JQLite collection.
257
+ *
258
+ * @param {string} type - The event type(s) to listen for. Multiple event types can be specified, separated by a space.
259
+ * @param {Function} fn - The function to execute when the event is triggered.
260
+ * @returns {JQLite} The JQLite collection for chaining.
261
+ */
262
+ JQLite.prototype.on = function (type, fn) {
263
+ // Do not add event handlers to non-elements because they will not be cleaned up.
264
+ for (let i = 0, ii = this.length; i < ii; i++) {
265
+ const element = this[i];
266
+ if (!elementAcceptsData(element)) {
267
+ return;
268
+ }
269
+
270
+ const expandoStore = getExpando(element, true);
271
+
272
+ if (!expandoStore.handle) {
273
+ expandoStore.handle = createEventHandler(element, expandoStore.events);
274
+ }
275
+ // http://jsperf.com/string-indexof-vs-split
276
+ const types = type.indexOf(" ") >= 0 ? type.split(" ") : [type];
277
+ let j = types.length;
278
+
279
+ const addHandler = function (type, specialHandlerWrapper, noEventListener) {
280
+ let eventFns = expandoStore.events[type];
281
+
282
+ if (!eventFns) {
283
+ eventFns = expandoStore.events[type] = [];
284
+ eventFns.specialHandlerWrapper = specialHandlerWrapper;
285
+ if (type !== "$destroy" && !noEventListener) {
286
+ element.addEventListener(type, expandoStore.handle);
287
+ }
234
288
  }
289
+
290
+ eventFns.push(fn);
235
291
  };
236
292
 
237
- forEach(type.split(" "), (type) => {
238
- removeHandler(type);
293
+ while (j--) {
294
+ type = types[j];
239
295
  if (MOUSE_EVENT_MAP[type]) {
240
- removeHandler(MOUSE_EVENT_MAP[type]);
296
+ addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
297
+ addHandler(type, undefined, true);
298
+ } else {
299
+ addHandler(type);
241
300
  }
242
- });
301
+ }
243
302
  }
244
-
245
- removeIfEmptyData(element);
246
- }
303
+ return this;
304
+ };
247
305
 
248
306
  /**
249
- * Removes expando data from this element. If key is provided, only
250
- * its field is removed. If data is empty, also removes `ExpandoStore`
251
- * from cache.
252
- * @param {Element} element
253
- * @param {string} [name] - key of field to remove
307
+ * Removes an event listener to each element in JQLite collection.
308
+ *
309
+ * @param {string} type - The event type(s) to remove listener from
310
+ * @param {Function} fn - The function to remove from event type.
311
+ * @returns {JQLite}
254
312
  */
255
- export function removeElementData(element, name) {
256
- const expandoId = element[EXPANDO];
257
- const expandoStore = expandoId && CACHE.get(expandoId);
313
+ JQLite.prototype.off = function (type, fn) {
314
+ for (let i = 0, ii = this.length; i < ii; i++) {
315
+ const element = this[i];
316
+ const expandoStore = getExpando(element);
317
+ const events = expandoStore && expandoStore.events;
318
+ const handle = expandoStore && expandoStore.handle;
258
319
 
259
- if (expandoStore) {
260
- if (name) {
261
- delete expandoStore.data[name];
320
+ if (!handle) return; // no listeners registered
321
+
322
+ if (!type) {
323
+ for (type in events) {
324
+ if (type !== "$destroy") {
325
+ element.removeEventListener(type, handle);
326
+ }
327
+ delete events[type];
328
+ }
262
329
  } else {
263
- expandoStore.data = {};
330
+ const removeHandler = function (type) {
331
+ const listenerFns = events[type];
332
+ if (isDefined(fn) && Array.isArray(listenerFns)) {
333
+ arrayRemove(listenerFns, fn);
334
+ }
335
+ if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) {
336
+ element.removeEventListener(type, handle);
337
+ delete events[type];
338
+ }
339
+ };
340
+
341
+ forEach(type.split(" "), (type) => {
342
+ removeHandler(type);
343
+ if (MOUSE_EVENT_MAP[type]) {
344
+ removeHandler(MOUSE_EVENT_MAP[type]);
345
+ }
346
+ });
264
347
  }
265
348
 
266
349
  removeIfEmptyData(element);
267
350
  }
268
- }
351
+ return this;
352
+ };
269
353
 
270
354
  /**
271
- * Stores data associated with an element inside the expando property of the DOM element.
272
- *
273
- * @see {@link https://developer.mozilla.org/en-US/docs/Glossary/Expando MDN Glossary: Expando}
274
- *
275
- * @param {Element} element
276
- * @param {boolean} [createIfNecessary=false]
277
- * @returns {import("../../core/cache/cache").ExpandoStore}
355
+ * Remove data by name from cache associated with each element in JQLite collection.
356
+ * @param {string} name - The key of the data associated with element
357
+ * @returns {JQLite}
278
358
  */
279
- function getExpando(element, createIfNecessary = false) {
280
- let expandoId = element[EXPANDO];
281
- let expandoStore = expandoId && CACHE.get(expandoId);
282
-
283
- if (createIfNecessary && !expandoStore) {
284
- element[EXPANDO] = expandoId = jqNextId();
285
- expandoStore = {
286
- events: {},
287
- data: {},
288
- handle: null,
289
- };
290
- CACHE.set(expandoId, expandoStore);
359
+ JQLite.prototype.removeData = function (name) {
360
+ for (let i = 0, ii = this.length; i < ii; i++) {
361
+ const element = this[i];
362
+ removeElementData(element, name);
291
363
  }
292
-
293
- return expandoStore;
294
- }
295
-
296
- /**
297
- * Checks if the string contains HTML tags or entities.
298
- * @param {string} html
299
- * @returns {boolean} True if the string is plain text, false if it contains HTML tags or entities.
300
- */
301
- export function isTextNode(html) {
302
- return !/<|&#?\w+;/.test(html);
303
- }
364
+ return this;
365
+ };
304
366
 
305
367
  /**
306
- * Check if element can accept expando data
307
- * @param {Element} node
308
- * @returns {boolean}
368
+ * Gets or sets data on a parent element
369
+ * @param {string} name
370
+ * @param {any} value
371
+ * @returns {JQLite|any}
309
372
  */
310
- function elementAcceptsData(node) {
311
- // The window object can accept data but has no nodeType
312
- // Otherwise we are only interested in elements (1) and documents (9)
313
- switch (node.nodeType) {
314
- case Node.ELEMENT_NODE:
315
- case Node.DOCUMENT_NODE:
316
- case undefined: // window.object
317
- return true;
318
- default:
319
- return false;
373
+ JQLite.prototype.inheritedData = function (name, value) {
374
+ for (let i = 0, ii = this.length; i < ii; i++) {
375
+ const element = this[0];
376
+ let res = getInheritedData(element, name, value);
377
+ if (value) {
378
+ return this;
379
+ } else {
380
+ return res;
381
+ }
320
382
  }
321
- }
383
+ };
322
384
 
323
385
  /**
324
- * @param {string} html
325
- * @returns {DocumentFragment}
386
+ * Gets or sets innerHTML on the first element in JQLite collection
387
+ * @param {string} value
388
+ * @returns {JQLite|any|undefined}
326
389
  */
327
- export function buildFragment(html) {
328
- let tmp;
329
- let tag;
330
- let wrap;
331
- let tempFragment = document.createDocumentFragment();
332
- let nodes = [];
333
- let i;
390
+ JQLite.prototype.html = function (value) {
391
+ const element = this[0];
392
+ if (!element) return undefined;
393
+ if (isUndefined(value)) {
394
+ return element.innerHTML;
395
+ }
396
+ dealoc(element, true);
397
+ element.innerHTML = value;
398
+ return this;
399
+ };
334
400
 
335
- if (isTextNode(html)) {
336
- // Convert non-html into a text node
337
- nodes.push(document.createTextNode(html));
338
- } else {
339
- // Convert html into DOM nodes
340
- tmp = tempFragment.appendChild(document.createElement("div"));
341
- tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
401
+ /// ///////////////////////////////////////
402
+ // Functions iterating getter/setters.
403
+ // these functions return self on setter and
404
+ // value on get.
405
+ /// ///////////////////////////////////////
406
+ forEach(
407
+ {
408
+ data: getOrSetCacheData,
409
+ attr(element, name, value) {
410
+ let ret;
411
+ const { nodeType } = element;
412
+ if (
413
+ nodeType === Node.TEXT_NODE ||
414
+ nodeType === Node.ATTRIBUTE_NODE ||
415
+ nodeType === Node.COMMENT_NODE ||
416
+ !element.getAttribute
417
+ ) {
418
+ return;
419
+ }
342
420
 
343
- wrap = wrapMap[tag] || [];
421
+ const lowercasedName = lowercase(name);
422
+ const isBooleanAttr = BOOLEAN_ATTR[lowercasedName];
344
423
 
345
- // Create wrappers & descend into them
346
- i = wrap.length;
347
- while (--i > -1) {
348
- tmp.appendChild(window.document.createElement(wrap[i]));
349
- tmp = tmp.firstChild;
350
- }
351
- tmp.innerHTML = html;
424
+ if (isDefined(value)) {
425
+ // setter
352
426
 
353
- nodes = concat(nodes, tmp.childNodes);
427
+ if (value === null || (value === false && isBooleanAttr)) {
428
+ element.removeAttribute(name);
429
+ } else {
430
+ element.setAttribute(name, isBooleanAttr ? lowercasedName : value);
431
+ }
432
+ } else {
433
+ // getter
354
434
 
355
- tmp = tempFragment.firstChild;
356
- tmp.textContent = "";
357
- }
435
+ ret = element.getAttribute(name);
358
436
 
359
- let fragment = document.createDocumentFragment();
360
- fragment.append(...nodes);
361
- return fragment;
362
- }
437
+ if (isBooleanAttr && ret !== null) {
438
+ ret = lowercasedName;
439
+ }
440
+ // Normalize non-existing attributes to undefined (as jQuery).
441
+ return ret === null ? undefined : ret;
442
+ }
443
+ },
444
+ text: (function () {
445
+ getText.$dv = "";
446
+ return getText;
363
447
 
364
- /**
365
- * @param {string} html
366
- * @returns {NodeListOf<ChildNode> | HTMLElement[]}
367
- */
368
- function parseHtml(html) {
369
- let regEx = SINGLE_TAG_REGEXP.exec(html);
370
- if (regEx) {
371
- return [document.createElement(regEx[1])];
372
- }
373
- let fragment = buildFragment(html);
374
- if (fragment) {
375
- return fragment.childNodes;
376
- }
448
+ function getText(element, value) {
449
+ if (isUndefined(value)) {
450
+ const { nodeType } = element;
451
+ return nodeType === Node.ELEMENT_NODE || nodeType === Node.TEXT_NODE
452
+ ? element.textContent
453
+ : "";
454
+ }
455
+ element.textContent = value;
456
+ }
457
+ })(),
458
+ val(element, value) {
459
+ if (isUndefined(value)) {
460
+ if (element.multiple && nodeName_(element) === "select") {
461
+ const result = [];
462
+ forEach(element.options, (option) => {
463
+ if (option.selected) {
464
+ result.push(option.value || option.text);
465
+ }
466
+ });
467
+ return result;
468
+ }
469
+ return element.value;
470
+ }
471
+ element.value = value;
472
+ },
473
+ },
474
+ (fn, name) => {
475
+ /**
476
+ * Properties: writes return selection, reads return first value
477
+ */
478
+ JQLite.prototype[name] = function (arg1, arg2) {
479
+ let i;
480
+ let key;
481
+ const nodeCount = this.length;
377
482
 
378
- return [];
379
- }
483
+ // JQLiteEmpty takes no arguments but is a setter.
484
+ if (isUndefined(fn.length === 2 && fn !== getController ? arg1 : arg2)) {
485
+ if (isObject(arg1)) {
486
+ // we are a write, but the object properties are the key/values
487
+ for (i = 0; i < nodeCount; i++) {
488
+ if (fn === getOrSetCacheData) {
489
+ fn(this[i], arg1);
490
+ } else {
491
+ for (key in arg1) {
492
+ fn(this[i], key, arg1[key]);
493
+ }
494
+ }
495
+ }
496
+ // return self for chaining
497
+ return this;
498
+ }
499
+ // we are a read, so read the first child.
500
+ // TODO: do we still need this?
501
+ let value = fn.$dv;
502
+ // Only if we have $dv do we iterate over all, otherwise it is just the first element.
503
+ const jj = isUndefined(value) ? Math.min(nodeCount, 1) : nodeCount;
504
+ for (let j = 0; j < jj; j++) {
505
+ const nodeValue = fn(this[j], arg1, arg2);
506
+ value = value ? value + nodeValue : nodeValue;
507
+ }
508
+ return value;
509
+ }
510
+ // we are a write, so apply to all children
511
+ for (i = 0; i < nodeCount; i++) {
512
+ fn(this[i], arg1, arg2);
513
+ }
514
+ // return self for chaining
515
+ return this;
516
+ };
517
+ },
518
+ );
380
519
 
381
- /**
382
- * @param {Element} element
383
- * @param {boolean} [onlyDescendants]
384
- * @returns {void}
385
- */
386
- export function dealoc(element, onlyDescendants) {
387
- if (!element) return;
388
- if (!onlyDescendants && elementAcceptsData(element))
389
- cleanElementData([element]);
520
+ /// ///////////////////////////////////////
521
+ // Functions iterating traversal.
522
+ // These functions chain results into a single
523
+ // selector.
524
+ /// ///////////////////////////////////////
525
+ forEach(
526
+ {
527
+ replaceWith(element, replaceNode) {
528
+ let index;
529
+ const parent = element.parentNode;
530
+ dealoc(element);
531
+ forEach(new JQLite(replaceNode), (node) => {
532
+ if (index) {
533
+ parent.insertBefore(node, index.nextSibling);
534
+ } else {
535
+ parent.replaceChild(node, element);
536
+ }
537
+ index = node;
538
+ });
539
+ },
540
+ children(element) {
541
+ return Array.from(element.childNodes).filter(
542
+ (child) => child.nodeType === Node.ELEMENT_NODE,
543
+ );
544
+ },
545
+ append(element, node) {
546
+ const { nodeType } = element;
547
+ if (
548
+ nodeType !== Node.ELEMENT_NODE &&
549
+ nodeType !== Node.DOCUMENT_FRAGMENT_NODE
550
+ )
551
+ return;
390
552
 
391
- if (element.querySelectorAll) {
392
- cleanElementData(element.querySelectorAll("*"));
393
- }
394
- }
553
+ node = new JQLite(node);
395
554
 
396
- /**
397
- * If `ExpandoStore.data` and `ExpandoStore.events` are empty,
398
- * then delete element's `ExpandoStore` and set its `ExpandoId`
399
- * to undefined.
400
- * @param {Element} element
401
- */
402
- function removeIfEmptyData(element) {
403
- const expandoId = element[EXPANDO];
404
- const { events, data } = CACHE.get(expandoId);
555
+ for (let i = 0, ii = node.length; i < ii; i++) {
556
+ const child = node[i];
557
+ element.appendChild(child);
558
+ }
559
+ },
405
560
 
406
- if (
407
- (!data || !Object.keys(data).length) &&
408
- (!events || !Object.keys(events).length)
409
- ) {
410
- CACHE.delete(expandoId);
411
- element[EXPANDO] = undefined; // don't delete DOM expandos. Chrome don't like it
412
- }
413
- }
561
+ prepend(element, node) {
562
+ if (element.nodeType === Node.ELEMENT_NODE) {
563
+ const index = element.firstChild;
564
+ forEach(new JQLite(node), (child) => {
565
+ element.insertBefore(child, index);
566
+ });
567
+ }
568
+ },
414
569
 
415
- /**
416
- * Gets or sets cache data for a given element.
417
- *
418
- * @param {Element} element - The DOM element to get or set data on.
419
- * @param {string|Object} key - The key (as a string) to get/set or an object for mass-setting.
420
- * @param {*} [value] - The value to set. If not provided, the function acts as a getter.
421
- * @returns {*} - The retrieved data if acting as a getter. Otherwise, returns undefined.
422
- */
423
- export function getOrSetCacheData(element, key, value) {
424
- if (elementAcceptsData(element)) {
425
- let prop;
570
+ remove: removeElement,
426
571
 
427
- const isSimpleSetter = isDefined(value);
428
- const isSimpleGetter = !isSimpleSetter && key && !isObject(key);
429
- const massGetter = !key;
430
- const expandoStore = getExpando(element, !isSimpleGetter);
431
- const data = expandoStore && expandoStore.data;
572
+ detach(element) {
573
+ removeElement(element, true);
574
+ },
432
575
 
433
- if (isSimpleSetter) {
434
- data[kebabToCamel(key)] = value;
435
- } else {
436
- if (massGetter) {
437
- return data;
438
- }
439
- if (isSimpleGetter) {
440
- // don't force creation of expandoStore if it doesn't exist yet
441
- return data && data[kebabToCamel(key)];
576
+ after(element, newElement) {
577
+ let index = element;
578
+ const parent = element.parentNode;
579
+
580
+ if (parent) {
581
+ newElement = new JQLite(newElement);
582
+
583
+ for (let i = 0, ii = newElement.length; i < ii; i++) {
584
+ const node = newElement[i];
585
+ parent.insertBefore(node, index.nextSibling);
586
+ index = node;
587
+ }
442
588
  }
443
- // mass-setter: data({key1: val1, key2: val2})
444
- for (prop in key) {
445
- data[kebabToCamel(prop)] = key[prop];
589
+ },
590
+
591
+ parent(element) {
592
+ const parent = element.parentNode;
593
+ return parent && parent.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
594
+ ? parent
595
+ : null;
596
+ },
597
+
598
+ // TODO: remove after migrating tests away from JQLite
599
+ find(element, selector) {
600
+ if (element.getElementsByTagName) {
601
+ return element.getElementsByTagName(selector);
446
602
  }
447
- }
448
- } else {
449
- // TODO: check should occur perhaps prior at compilation level that this is a valid element
450
- }
451
- }
603
+ return [];
604
+ },
452
605
 
453
- /**
454
- * Adds nodes or elements to the root array-like object.
455
- *
456
- * @param {Array} root - The array-like object to which elements will be added.
457
- * @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.
458
- */
459
- function addNodes(root, elements) {
460
- // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
606
+ triggerHandler(element, event, extraParameters) {
607
+ let dummyEvent;
608
+ let eventFnsCopy;
609
+ let handlerArgs;
610
+ const eventName = event.type || event;
611
+ const expandoStore = getExpando(element);
612
+ const events = expandoStore && expandoStore.events;
613
+ const eventFns = events && events[eventName];
461
614
 
462
- if (elements) {
463
- // if a Node (the most common case)
464
- if (elements.nodeType) {
465
- root[root.length++] = elements;
466
- } else {
467
- const { length } = elements;
615
+ if (eventFns) {
616
+ // Create a dummy event to pass to the handlers
617
+ dummyEvent = {
618
+ preventDefault() {
619
+ this.defaultPrevented = true;
620
+ },
621
+ isDefaultPrevented() {
622
+ return this.defaultPrevented === true;
623
+ },
624
+ stopImmediatePropagation() {
625
+ this.immediatePropagationStopped = true;
626
+ },
627
+ isImmediatePropagationStopped() {
628
+ return this.immediatePropagationStopped === true;
629
+ },
630
+ stopPropagation: () => {},
631
+ type: eventName,
632
+ target: element,
633
+ };
468
634
 
469
- // if an Array or NodeList and not a Window
470
- if (typeof length === "number" && elements.window !== elements) {
471
- if (length) {
472
- for (let i = 0; i < length; i++) {
473
- root[root.length++] = elements[i];
474
- }
635
+ // If a custom event was provided then extend our dummy event with it
636
+ if (event.type) {
637
+ dummyEvent = extend(dummyEvent, event);
475
638
  }
476
- } else {
477
- root[root.length++] = elements;
478
- }
479
- }
480
- }
481
- }
482
639
 
483
- function JQLiteController(element, name) {
484
- return JQLiteInheritedData(element, `$${name || "ngController"}Controller`);
485
- }
640
+ // Copy event handlers in case event handlers array is modified during execution.
641
+ eventFnsCopy = shallowCopy(eventFns);
642
+ handlerArgs = extraParameters
643
+ ? [dummyEvent].concat(extraParameters)
644
+ : [dummyEvent];
486
645
 
487
- function JQLiteInheritedData(element, name, value) {
488
- // if element is the document object work with the html element instead
489
- // this makes $(document).scope() possible
490
- if (element.nodeType === Node.DOCUMENT_NODE) {
491
- element = element.documentElement;
492
- }
493
- const names = Array.isArray(name) ? name : [name];
646
+ forEach(eventFnsCopy, (fn) => {
647
+ if (!dummyEvent.isImmediatePropagationStopped()) {
648
+ fn.apply(element, handlerArgs);
649
+ }
650
+ });
651
+ }
652
+ },
653
+ },
654
+ (fn, name) => {
655
+ /**
656
+ * chaining functions
657
+ */
658
+ JQLite.prototype[name] = function (arg1, arg2, arg3) {
659
+ let value;
494
660
 
495
- while (element) {
496
- for (let i = 0, ii = names.length; i < ii; i++) {
497
- if (isDefined((value = getOrSetCacheData(element, names[i]))))
498
- return value;
499
- }
661
+ for (let i = 0, ii = this.length; i < ii; i++) {
662
+ if (isUndefined(value)) {
663
+ value = fn(this[i], arg1, arg2, arg3);
664
+ if (isDefined(value)) {
665
+ // any function which returns a value needs to be wrapped
666
+ value = JQLite(value);
667
+ }
668
+ } else {
669
+ addNodes(value, fn(this[i], arg1, arg2, arg3));
670
+ }
671
+ }
672
+ return isDefined(value) ? value : this;
673
+ };
674
+ },
675
+ );
500
676
 
501
- // If dealing with a document fragment node with a host element, and no parent, use the host
502
- // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
503
- // to lookup parent controllers.
504
- element =
505
- element.parentNode ||
506
- (element.nodeType === Node.DOCUMENT_FRAGMENT_NODE && element.host);
507
- }
508
- }
677
+ ///////////////////////////////////////////////////////////////////
678
+ //////////// HELPER FUNCTIONS /////////////////////////
679
+ ///////////////////////////////////////////////////////////////////
509
680
 
510
681
  /**
511
682
  *
512
- * @param {Element} element
513
- * @param {boolean} keepData
683
+ * @returns {number} Next unique JQInstance id
514
684
  */
515
- export function removeElement(element, keepData = false) {
516
- if (!keepData) dealoc(element);
517
- const parent = element.parentNode;
518
- if (parent) parent.removeChild(element);
685
+ function jqNextId() {
686
+ return ++jqId;
519
687
  }
520
688
 
521
689
  /**
522
- * Executea a function on `DOMContentLoaded`
523
- * @param {Function} fn
690
+ * @param {string} _all
691
+ * @param {string} letter
692
+ * @returns {string}
524
693
  */
525
- function onReady(fn) {
526
- function trigger() {
527
- window.document.removeEventListener("DOMContentLoaded", trigger);
528
- fn();
529
- }
530
- // check if document is already loaded
531
- if (window.document.readyState === "complete") {
532
- window.setTimeout(fn);
533
- } else {
534
- // We can not use JQLite since we are not done loading.
535
- window.document.addEventListener("DOMContentLoaded", trigger);
536
- }
694
+ function fnCamelCaseReplace(_all, letter) {
695
+ return letter.toUpperCase();
537
696
  }
538
697
 
539
- /// ///////////////////////////////////////
540
- // Functions which are declared directly.
541
- /// ///////////////////////////////////////
542
- JQLite.prototype = {
543
- toString() {
544
- const value = [];
545
- forEach(this, (e) => {
546
- value.push(`${e}`);
547
- });
548
- return `[${value.join(", ")}]`;
549
- },
550
-
551
- eq(index) {
552
- return index >= 0 ? JQLite(this[index]) : JQLite(this[this.length + index]);
553
- },
554
-
555
- length: 0,
556
- };
557
-
558
698
  /**
559
- * Remove all child nodes of the set of matched elements from the DOM and clears CACHE data, associated with the node.
560
- * @returns {JQLite} The current instance of JQLite.
699
+ * Converts kebab-case to camelCase.
700
+ * @param {string} name Name to normalize
701
+ * @returns {string}
561
702
  */
562
- JQLite.prototype.empty = function () {
563
- for (let i = 0; i < this.length; i++) {
564
- const element = this[i];
565
- dealoc(element, true);
566
- // we may run into situation where we empty a transcluded node
567
- if (
568
- [
569
- Node.ELEMENT_NODE,
570
- Node.DOCUMENT_NODE,
571
- Node.DOCUMENT_FRAGMENT_NODE,
572
- ].includes(element.nodeType)
573
- ) {
574
- element.replaceChildren();
575
- }
576
- }
577
- return this;
578
- };
703
+ export function kebabToCamel(name) {
704
+ return name.replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace);
705
+ }
579
706
 
580
707
  /**
581
- * Returns the `$scope` of the element.
582
- * @returns {import("../../core/scope/scope").Scope}
708
+ * Converts sname to camelCase.
709
+ * @param {string} name
710
+ * @returns {string}
583
711
  */
584
- JQLite.prototype.scope = function () {
585
- // Can't use JQLiteData here directly so we stay compatible with jQuery!
586
- return (
587
- getOrSetCacheData(this[0], "$scope") ||
588
- JQLiteInheritedData(this[0].parentNode || this[0], [
589
- "$isolateScope",
590
- "$scope",
591
- ])
592
- );
593
- };
712
+ export function snakeToCamel(name) {
713
+ return name.replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace);
714
+ }
594
715
 
595
716
  /**
596
- * Returns the isolate `$scope` of the element.
597
- * @returns {import("../../core/scope/scope").Scope}
717
+ * Removes expando data from this element. If key is provided, only
718
+ * its field is removed. If data is empty, also removes `ExpandoStore`
719
+ * from cache.
720
+ * @param {Element} element
721
+ * @param {string} [name] - key of field to remove
598
722
  */
599
- JQLite.prototype.isolateScope = function () {
600
- return (
601
- getOrSetCacheData(this[0], "$isolateScope") ||
602
- getOrSetCacheData(this[0], "$isolateScopeNoTemplate")
603
- );
604
- };
723
+ export function removeElementData(element, name) {
724
+ const expandoId = element[EXPANDO];
725
+ const expandoStore = expandoId && CACHE.get(expandoId);
605
726
 
606
- /**
607
- * Return instance of controller attached to element
608
- * @param {string} [name] - Controller name
609
- * @returns {any}
610
- */
611
- JQLite.prototype.controller = function (name) {
612
- return JQLiteController(this[0], name);
613
- };
727
+ if (expandoStore) {
728
+ if (name) {
729
+ delete expandoStore.data[name];
730
+ } else {
731
+ expandoStore.data = {};
732
+ }
614
733
 
615
- /**
616
- * Return instance of injector attached to element
617
- * @returns {import('../../types').InjectorService}
618
- */
619
- JQLite.prototype.injector = function () {
620
- return JQLiteInheritedData(this[0], "$injector");
621
- };
734
+ removeIfEmptyData(element);
735
+ }
736
+ }
622
737
 
623
738
  /**
624
- * Adds an event listener to each element in the JQLite collection.
739
+ * Stores data associated with an element inside the expando property of the DOM element.
625
740
  *
626
- * @param {string} type - The event type(s) to listen for. Multiple event types can be specified, separated by a space.
627
- * @param {Function} fn - The function to execute when the event is triggered.
628
- * @returns {JQLite} The JQLite collection for chaining.
741
+ * @see {@link https://developer.mozilla.org/en-US/docs/Glossary/Expando MDN Glossary: Expando}
742
+ *
743
+ * @param {Element} element
744
+ * @param {boolean} [createIfNecessary=false]
745
+ * @returns {import("../../core/cache/cache").ExpandoStore}
629
746
  */
630
- JQLite.prototype.on = function (type, fn) {
631
- // Do not add event handlers to non-elements because they will not be cleaned up.
632
- for (let i = 0, ii = this.length; i < ii; i++) {
633
- const element = this[i];
634
- if (!elementAcceptsData(element)) {
635
- return;
636
- }
637
-
638
- const expandoStore = getExpando(element, true);
639
-
640
- if (!expandoStore.handle) {
641
- expandoStore.handle = createEventHandler(element, expandoStore.events);
642
- }
643
- // http://jsperf.com/string-indexof-vs-split
644
- const types = type.indexOf(" ") >= 0 ? type.split(" ") : [type];
645
- let j = types.length;
646
-
647
- const addHandler = function (type, specialHandlerWrapper, noEventListener) {
648
- let eventFns = expandoStore.events[type];
649
-
650
- if (!eventFns) {
651
- eventFns = expandoStore.events[type] = [];
652
- eventFns.specialHandlerWrapper = specialHandlerWrapper;
653
- if (type !== "$destroy" && !noEventListener) {
654
- element.addEventListener(type, expandoStore.handle);
655
- }
656
- }
747
+ function getExpando(element, createIfNecessary = false) {
748
+ let expandoId = element[EXPANDO];
749
+ let expandoStore = expandoId && CACHE.get(expandoId);
657
750
 
658
- eventFns.push(fn);
751
+ if (createIfNecessary && !expandoStore) {
752
+ element[EXPANDO] = expandoId = jqNextId();
753
+ expandoStore = {
754
+ events: {},
755
+ data: {},
756
+ handle: null,
659
757
  };
660
-
661
- while (j--) {
662
- type = types[j];
663
- if (MOUSE_EVENT_MAP[type]) {
664
- addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper);
665
- addHandler(type, undefined, true);
666
- } else {
667
- addHandler(type);
668
- }
669
- }
758
+ CACHE.set(expandoId, expandoStore);
670
759
  }
671
- return this;
672
- };
673
760
 
674
- export function getBooleanAttrName(element, name) {
675
- // check dom last since we will most likely fail on name
676
- const booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
761
+ return expandoStore;
762
+ }
677
763
 
678
- // booleanAttr is here twice to minimize DOM access
679
- return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
764
+ /**
765
+ * Checks if the string contains HTML tags or entities.
766
+ * @param {string} html
767
+ * @returns {boolean} True if the string is plain text, false if it contains HTML tags or entities.
768
+ */
769
+ export function isTextNode(html) {
770
+ return !/<|&#?\w+;/.test(html);
680
771
  }
681
772
 
682
773
  /**
683
- * Takes an array of elements, calls any `$destroy` event handlers, removes any data in cache, and finally removes any
684
- * listeners.
685
- * @param {NodeListOf<Element>} nodes
774
+ * Check if element can accept expando data
775
+ * @param {Element} node
776
+ * @returns {boolean}
686
777
  */
687
- export function cleanElementData(nodes) {
688
- for (let i = 0, ii = nodes.length; i < ii; i++) {
689
- var events = (CACHE.get(nodes[i][EXPANDO]) || {}).events;
690
- if (events && events.$destroy) {
691
- JQLite(nodes[i]).triggerHandler("$destroy");
692
- }
693
- removeElementData(nodes[i]);
694
- JQLiteOff(nodes[i]);
778
+ function elementAcceptsData(node) {
779
+ // The window object can accept data but has no nodeType
780
+ // Otherwise we are only interested in elements (1) and documents (9)
781
+ switch (node.nodeType) {
782
+ case Node.ELEMENT_NODE:
783
+ case Node.DOCUMENT_NODE:
784
+ case undefined: // window.object
785
+ return true;
786
+ default:
787
+ return false;
695
788
  }
696
789
  }
697
790
 
698
- /// ///////////////////////////////////////
699
- // Functions iterating getter/setters.
700
- // these functions return self on setter and
701
- // value on get.
702
- /// ///////////////////////////////////////
703
- forEach(
704
- {
705
- data: getOrSetCacheData,
706
- inheritedData: JQLiteInheritedData,
707
- attr(element, name, value) {
708
- let ret;
709
- const { nodeType } = element;
710
- if (
711
- nodeType === Node.TEXT_NODE ||
712
- nodeType === Node.ATTRIBUTE_NODE ||
713
- nodeType === Node.COMMENT_NODE ||
714
- !element.getAttribute
715
- ) {
716
- return;
717
- }
718
-
719
- const lowercasedName = lowercase(name);
720
- const isBooleanAttr = BOOLEAN_ATTR[lowercasedName];
721
-
722
- if (isDefined(value)) {
723
- // setter
724
-
725
- if (value === null || (value === false && isBooleanAttr)) {
726
- element.removeAttribute(name);
727
- } else {
728
- element.setAttribute(name, isBooleanAttr ? lowercasedName : value);
729
- }
730
- } else {
731
- // getter
791
+ /**
792
+ * @param {string} html
793
+ * @returns {DocumentFragment}
794
+ */
795
+ export function buildFragment(html) {
796
+ let tmp;
797
+ let tag;
798
+ let wrap;
799
+ let tempFragment = document.createDocumentFragment();
800
+ let nodes = [];
801
+ let i;
732
802
 
733
- ret = element.getAttribute(name);
803
+ if (isTextNode(html)) {
804
+ // Convert non-html into a text node
805
+ nodes.push(document.createTextNode(html));
806
+ } else {
807
+ // Convert html into DOM nodes
808
+ tmp = tempFragment.appendChild(document.createElement("div"));
809
+ tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
734
810
 
735
- if (isBooleanAttr && ret !== null) {
736
- ret = lowercasedName;
737
- }
738
- // Normalize non-existing attributes to undefined (as jQuery).
739
- return ret === null ? undefined : ret;
740
- }
741
- },
742
- text: (function () {
743
- getText.$dv = "";
744
- return getText;
811
+ wrap = wrapMap[tag] || [];
745
812
 
746
- function getText(element, value) {
747
- if (isUndefined(value)) {
748
- const { nodeType } = element;
749
- return nodeType === Node.ELEMENT_NODE || nodeType === Node.TEXT_NODE
750
- ? element.textContent
751
- : "";
752
- }
753
- element.textContent = value;
754
- }
755
- })(),
756
- val(element, value) {
757
- if (isUndefined(value)) {
758
- if (element.multiple && nodeName_(element) === "select") {
759
- const result = [];
760
- forEach(element.options, (option) => {
761
- if (option.selected) {
762
- result.push(option.value || option.text);
763
- }
764
- });
765
- return result;
766
- }
767
- return element.value;
768
- }
769
- element.value = value;
770
- },
771
- html(element, value) {
772
- if (isUndefined(value)) {
773
- return element.innerHTML;
774
- }
775
- dealoc(element, true);
776
- element.innerHTML = value;
777
- },
778
- },
779
- (fn, name) => {
780
- /**
781
- * Properties: writes return selection, reads return first value
782
- */
783
- JQLite.prototype[name] = function (arg1, arg2) {
784
- let i;
785
- let key;
786
- const nodeCount = this.length;
813
+ // Create wrappers & descend into them
814
+ i = wrap.length;
815
+ while (--i > -1) {
816
+ tmp.appendChild(window.document.createElement(wrap[i]));
817
+ tmp = tmp.firstChild;
818
+ }
819
+ tmp.innerHTML = html;
787
820
 
788
- // JQLiteEmpty takes no arguments but is a setter.
789
- if (
790
- isUndefined(fn.length === 2 && fn !== JQLiteController ? arg1 : arg2)
791
- ) {
792
- if (isObject(arg1)) {
793
- // we are a write, but the object properties are the key/values
794
- for (i = 0; i < nodeCount; i++) {
795
- if (fn === getOrSetCacheData) {
796
- fn(this[i], arg1);
797
- } else {
798
- for (key in arg1) {
799
- fn(this[i], key, arg1[key]);
800
- }
801
- }
802
- }
803
- // return self for chaining
804
- return this;
805
- }
806
- // we are a read, so read the first child.
807
- // TODO: do we still need this?
808
- let value = fn.$dv;
809
- // Only if we have $dv do we iterate over all, otherwise it is just the first element.
810
- const jj = isUndefined(value) ? Math.min(nodeCount, 1) : nodeCount;
811
- for (let j = 0; j < jj; j++) {
812
- const nodeValue = fn(this[j], arg1, arg2);
813
- value = value ? value + nodeValue : nodeValue;
814
- }
815
- return value;
816
- }
817
- // we are a write, so apply to all children
818
- for (i = 0; i < nodeCount; i++) {
819
- fn(this[i], arg1, arg2);
820
- }
821
- // return self for chaining
822
- return this;
823
- };
824
- },
825
- );
821
+ nodes = concat(nodes, tmp.childNodes);
826
822
 
827
- /// ///////////////////////////////////////
828
- // Functions iterating traversal.
829
- // These functions chain results into a single
830
- // selector.
831
- /// ///////////////////////////////////////
832
- forEach(
833
- {
834
- removeData: removeElementData,
823
+ tmp = tempFragment.firstChild;
824
+ tmp.textContent = "";
825
+ }
835
826
 
836
- off: JQLiteOff,
837
- replaceWith(element, replaceNode) {
838
- let index;
839
- const parent = element.parentNode;
840
- dealoc(element);
841
- forEach(new JQLite(replaceNode), (node) => {
842
- if (index) {
843
- parent.insertBefore(node, index.nextSibling);
844
- } else {
845
- parent.replaceChild(node, element);
846
- }
847
- index = node;
848
- });
849
- },
850
- children(element) {
851
- return Array.from(element.childNodes).filter(
852
- (child) => child.nodeType === Node.ELEMENT_NODE,
853
- );
854
- },
855
- append(element, node) {
856
- const { nodeType } = element;
857
- if (
858
- nodeType !== Node.ELEMENT_NODE &&
859
- nodeType !== Node.DOCUMENT_FRAGMENT_NODE
860
- )
861
- return;
827
+ let fragment = document.createDocumentFragment();
828
+ fragment.append(...nodes);
829
+ return fragment;
830
+ }
862
831
 
863
- node = new JQLite(node);
832
+ /**
833
+ * @param {string} html
834
+ * @returns {NodeListOf<ChildNode> | HTMLElement[]}
835
+ */
836
+ function parseHtml(html) {
837
+ let regEx = SINGLE_TAG_REGEXP.exec(html);
838
+ if (regEx) {
839
+ return [document.createElement(regEx[1])];
840
+ }
841
+ let fragment = buildFragment(html);
842
+ if (fragment) {
843
+ return fragment.childNodes;
844
+ }
864
845
 
865
- for (let i = 0, ii = node.length; i < ii; i++) {
866
- const child = node[i];
867
- element.appendChild(child);
868
- }
869
- },
846
+ return [];
847
+ }
870
848
 
871
- prepend(element, node) {
872
- if (element.nodeType === Node.ELEMENT_NODE) {
873
- const index = element.firstChild;
874
- forEach(new JQLite(node), (child) => {
875
- element.insertBefore(child, index);
876
- });
877
- }
878
- },
849
+ /**
850
+ * @param {Element} element
851
+ * @param {boolean} [onlyDescendants]
852
+ * @returns {void}
853
+ */
854
+ export function dealoc(element, onlyDescendants) {
855
+ if (!element) return;
856
+ if (!onlyDescendants && elementAcceptsData(element))
857
+ cleanElementData([element]);
879
858
 
880
- remove: removeElement,
859
+ if (element.querySelectorAll) {
860
+ cleanElementData(element.querySelectorAll("*"));
861
+ }
862
+ }
881
863
 
882
- detach(element) {
883
- removeElement(element, true);
884
- },
864
+ /**
865
+ * If `ExpandoStore.data` and `ExpandoStore.events` are empty,
866
+ * then delete element's `ExpandoStore` and set its `ExpandoId`
867
+ * to undefined.
868
+ * @param {Element} element
869
+ */
870
+ function removeIfEmptyData(element) {
871
+ const expandoId = element[EXPANDO];
872
+ const { events, data } = CACHE.get(expandoId);
885
873
 
886
- after(element, newElement) {
887
- let index = element;
888
- const parent = element.parentNode;
874
+ if (
875
+ (!data || !Object.keys(data).length) &&
876
+ (!events || !Object.keys(events).length)
877
+ ) {
878
+ CACHE.delete(expandoId);
879
+ element[EXPANDO] = undefined; // don't delete DOM expandos. Chrome don't like it
880
+ }
881
+ }
889
882
 
890
- if (parent) {
891
- newElement = new JQLite(newElement);
883
+ /**
884
+ * Gets or sets cache data for a given element.
885
+ *
886
+ * @param {Element} element - The DOM element to get or set data on.
887
+ * @param {string|Object} key - The key (as a string) to get/set or an object for mass-setting.
888
+ * @param {*} [value] - The value to set. If not provided, the function acts as a getter.
889
+ * @returns {*} - The retrieved data if acting as a getter. Otherwise, returns undefined.
890
+ */
891
+ export function getOrSetCacheData(element, key, value) {
892
+ if (elementAcceptsData(element)) {
893
+ let prop;
892
894
 
893
- for (let i = 0, ii = newElement.length; i < ii; i++) {
894
- const node = newElement[i];
895
- parent.insertBefore(node, index.nextSibling);
896
- index = node;
897
- }
895
+ const isSimpleSetter = isDefined(value);
896
+ const isSimpleGetter = !isSimpleSetter && key && !isObject(key);
897
+ const massGetter = !key;
898
+ const expandoStore = getExpando(element, !isSimpleGetter);
899
+ const data = expandoStore && expandoStore.data;
900
+
901
+ if (isSimpleSetter) {
902
+ data[kebabToCamel(key)] = value;
903
+ } else {
904
+ if (massGetter) {
905
+ return data;
898
906
  }
899
- },
907
+ if (isSimpleGetter) {
908
+ // don't force creation of expandoStore if it doesn't exist yet
909
+ return data && data[kebabToCamel(key)];
910
+ }
911
+ // mass-setter: data({key1: val1, key2: val2})
912
+ for (prop in key) {
913
+ data[kebabToCamel(prop)] = key[prop];
914
+ }
915
+ }
916
+ } else {
917
+ // TODO: check should occur perhaps prior at compilation level that this is a valid element
918
+ }
919
+ }
900
920
 
901
- parent(element) {
902
- const parent = element.parentNode;
903
- return parent && parent.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
904
- ? parent
905
- : null;
906
- },
921
+ /**
922
+ * Adds nodes or elements to the root array-like object.
923
+ *
924
+ * @param {Array} root - The array-like object to which elements will be added.
925
+ * @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.
926
+ */
927
+ function addNodes(root, elements) {
928
+ // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
907
929
 
908
- // TODO: remove after migrating tests away from JQLite
909
- find(element, selector) {
910
- if (element.getElementsByTagName) {
911
- return element.getElementsByTagName(selector);
930
+ if (elements) {
931
+ // if a Node (the most common case)
932
+ if (elements.nodeType) {
933
+ root[root.length++] = elements;
934
+ } else {
935
+ const { length } = elements;
936
+
937
+ // if an Array or NodeList and not a Window
938
+ if (typeof length === "number" && elements.window !== elements) {
939
+ if (length) {
940
+ for (let i = 0; i < length; i++) {
941
+ root[root.length++] = elements[i];
942
+ }
943
+ }
944
+ } else {
945
+ root[root.length++] = elements;
912
946
  }
913
- return [];
914
- },
947
+ }
948
+ }
949
+ }
915
950
 
916
- triggerHandler(element, event, extraParameters) {
917
- let dummyEvent;
918
- let eventFnsCopy;
919
- let handlerArgs;
920
- const eventName = event.type || event;
921
- const expandoStore = getExpando(element);
922
- const events = expandoStore && expandoStore.events;
923
- const eventFns = events && events[eventName];
951
+ function getController(element, name) {
952
+ return getInheritedData(element, `$${name || "ngController"}Controller`);
953
+ }
924
954
 
925
- if (eventFns) {
926
- // Create a dummy event to pass to the handlers
927
- dummyEvent = {
928
- preventDefault() {
929
- this.defaultPrevented = true;
930
- },
931
- isDefaultPrevented() {
932
- return this.defaultPrevented === true;
933
- },
934
- stopImmediatePropagation() {
935
- this.immediatePropagationStopped = true;
936
- },
937
- isImmediatePropagationStopped() {
938
- return this.immediatePropagationStopped === true;
939
- },
940
- stopPropagation: () => {},
941
- type: eventName,
942
- target: element,
943
- };
955
+ /**
956
+ *
957
+ * @param {Element} element
958
+ * @param {string} name
959
+ * @param {any} [value]
960
+ * @returns
961
+ */
962
+ function getInheritedData(element, name, value) {
963
+ // if element is the document object work with the html element instead
964
+ // this makes $(document).scope() possible
965
+ if (element.nodeType === Node.DOCUMENT_NODE) {
966
+ // TODO Fix types
967
+ element = element.documentElement;
968
+ }
969
+ const names = Array.isArray(name) ? name : [name];
944
970
 
945
- // If a custom event was provided then extend our dummy event with it
946
- if (event.type) {
947
- dummyEvent = extend(dummyEvent, event);
948
- }
971
+ while (element) {
972
+ for (let i = 0, ii = names.length; i < ii; i++) {
973
+ if (isDefined((value = getOrSetCacheData(element, names[i]))))
974
+ return value;
975
+ }
949
976
 
950
- // Copy event handlers in case event handlers array is modified during execution.
951
- eventFnsCopy = shallowCopy(eventFns);
952
- handlerArgs = extraParameters
953
- ? [dummyEvent].concat(extraParameters)
954
- : [dummyEvent];
977
+ // If dealing with a document fragment node with a host element, and no parent, use the host
978
+ // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
979
+ // to lookup parent controllers.
980
+ element =
981
+ element.parentNode ||
982
+ (element.nodeType === Node.DOCUMENT_FRAGMENT_NODE && element.host);
983
+ }
984
+ }
955
985
 
956
- forEach(eventFnsCopy, (fn) => {
957
- if (!dummyEvent.isImmediatePropagationStopped()) {
958
- fn.apply(element, handlerArgs);
959
- }
960
- });
961
- }
962
- },
963
- },
964
- (fn, name) => {
965
- /**
966
- * chaining functions
967
- */
968
- JQLite.prototype[name] = function (arg1, arg2, arg3) {
969
- let value;
986
+ /**
987
+ *
988
+ * @param {Element} element
989
+ * @param {boolean} keepData
990
+ */
991
+ export function removeElement(element, keepData = false) {
992
+ if (!keepData) dealoc(element);
993
+ const parent = element.parentNode;
994
+ if (parent) parent.removeChild(element);
995
+ }
970
996
 
971
- for (let i = 0, ii = this.length; i < ii; i++) {
972
- if (isUndefined(value)) {
973
- value = fn(this[i], arg1, arg2, arg3);
974
- if (isDefined(value)) {
975
- // any function which returns a value needs to be wrapped
976
- value = JQLite(value);
977
- }
978
- } else {
979
- addNodes(value, fn(this[i], arg1, arg2, arg3));
980
- }
981
- }
982
- return isDefined(value) ? value : this;
983
- };
984
- },
985
- );
997
+ /**
998
+ * Executea a function on `DOMContentLoaded`
999
+ * @param {Function} fn
1000
+ */
1001
+ function onReady(fn) {
1002
+ function trigger() {
1003
+ window.document.removeEventListener("DOMContentLoaded", trigger);
1004
+ fn();
1005
+ }
1006
+ // check if document is already loaded
1007
+ if (window.document.readyState === "complete") {
1008
+ window.setTimeout(fn);
1009
+ } else {
1010
+ // We can not use JQLite since we are not done loading.
1011
+ window.document.addEventListener("DOMContentLoaded", trigger);
1012
+ }
1013
+ }
986
1014
 
987
1015
  function createEventHandler(element, events) {
988
1016
  const eventHandler = function (event, type) {
@@ -1102,3 +1130,27 @@ export function getBlockNodes(nodes) {
1102
1130
 
1103
1131
  return JQLite(blockNodes || nodes);
1104
1132
  }
1133
+
1134
+ export function getBooleanAttrName(element, name) {
1135
+ // check dom last since we will most likely fail on name
1136
+ const booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
1137
+
1138
+ // booleanAttr is here twice to minimize DOM access
1139
+ return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
1140
+ }
1141
+
1142
+ /**
1143
+ * Takes an array of elements, calls any `$destroy` event handlers, removes any data in cache, and finally removes any
1144
+ * listeners.
1145
+ * @param {NodeListOf<Element>} nodes
1146
+ */
1147
+ export function cleanElementData(nodes) {
1148
+ for (let i = 0, ii = nodes.length; i < ii; i++) {
1149
+ var events = (CACHE.get(nodes[i][EXPANDO]) || {}).events;
1150
+ if (events && events.$destroy) {
1151
+ JQLite(nodes[i]).triggerHandler("$destroy");
1152
+ }
1153
+ removeElementData(nodes[i]);
1154
+ JQLite.prototype.off.call(JQLite(nodes[i]));
1155
+ }
1156
+ }