@angular-wave/angular.ts 0.0.44 → 0.0.46

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.
@@ -10,7 +10,7 @@ import {
10
10
  isString,
11
11
  isUndefined,
12
12
  lowercase,
13
- nodeName_,
13
+ getNodeName,
14
14
  shallowCopy,
15
15
  } from "../../shared/utils";
16
16
  import { CACHE, EXPANDO } from "../../core/cache/cache";
@@ -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,867 @@ 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; i < this.length; 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; i < this.length; 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; i < this.length; i++) {
361
+ const element = this[i];
362
+ removeElementData(element, name);
291
363
  }
292
-
293
- return expandoStore;
294
- }
364
+ return this;
365
+ };
295
366
 
296
367
  /**
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.
368
+ * Gets or sets data on a parent element
369
+ * @param {string} name
370
+ * @param {any} value
371
+ * @returns {JQLite|any}
300
372
  */
301
- export function isTextNode(html) {
302
- return !/<|&#?\w+;/.test(html);
303
- }
373
+ JQLite.prototype.inheritedData = function (name, value) {
374
+ for (let i = 0; i < this.length; 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
+ }
382
+ }
383
+ };
304
384
 
305
385
  /**
306
- * Check if element can accept expando data
307
- * @param {Element} node
308
- * @returns {boolean}
386
+ * Gets or sets innerHTML on the first element in JQLite collection
387
+ * @param {string} value
388
+ * @returns {JQLite|any|undefined}
309
389
  */
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;
390
+ JQLite.prototype.html = function (value) {
391
+ const element = this[0];
392
+ if (!element) return undefined;
393
+ if (isUndefined(value)) {
394
+ return element.innerHTML;
320
395
  }
321
- }
396
+ dealoc(element, true);
397
+ element.innerHTML = value;
398
+ return this;
399
+ };
322
400
 
323
401
  /**
324
- * @param {string} html
325
- * @returns {DocumentFragment}
402
+ * Get the combined text contents of each element in the JQLite collection
403
+ * or set the text contents of all elements.
404
+ * @param {string} [value]
405
+ * @returns {JQLite|string}
326
406
  */
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;
407
+ JQLite.prototype.text = function (value) {
408
+ let res = "";
409
+ for (let i = 0; i < this.length; i++) {
410
+ const element = this[i];
411
+ if (isUndefined(value)) {
412
+ // read
413
+ const { nodeType } = element;
414
+ res +=
415
+ nodeType === Node.ELEMENT_NODE || nodeType === Node.TEXT_NODE
416
+ ? element.textContent
417
+ : "";
418
+ } else {
419
+ // write
420
+ element.textContent = value;
421
+ }
422
+ }
334
423
 
335
- if (isTextNode(html)) {
336
- // Convert non-html into a text node
337
- nodes.push(document.createTextNode(html));
424
+ if (isUndefined(value)) {
425
+ return res;
338
426
  } else {
339
- // Convert html into DOM nodes
340
- tmp = tempFragment.appendChild(document.createElement("div"));
341
- tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
342
-
343
- wrap = wrapMap[tag] || [];
344
-
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;
352
-
353
- nodes = concat(nodes, tmp.childNodes);
354
-
355
- tmp = tempFragment.firstChild;
356
- tmp.textContent = "";
427
+ return this;
357
428
  }
358
-
359
- let fragment = document.createDocumentFragment();
360
- fragment.append(...nodes);
361
- return fragment;
362
- }
429
+ };
363
430
 
364
431
  /**
365
- * @param {string} html
366
- * @returns {NodeListOf<ChildNode> | HTMLElement[]}
432
+ * Gets or sets the values of form elements such as input, select and textarea in a JQLite collection.
433
+ * @param {any} value
434
+ * @returns {JQLite|any}
367
435
  */
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;
436
+ JQLite.prototype.val = function (value) {
437
+ // We can only get or set a value of
438
+ for (let i = 0; i < this.length; i++) {
439
+ const element = this[i];
440
+ if (isUndefined(value)) {
441
+ // read
442
+ if (element.multiple && getNodeName(element) === "select") {
443
+ const result = [];
444
+ forEach(element.options, (option) => {
445
+ if (option.selected) {
446
+ result.push(option.value || option.text);
447
+ }
448
+ });
449
+ return result;
450
+ }
451
+ return element.value;
452
+ } else {
453
+ // write
454
+ element.value = value;
455
+ return this;
456
+ }
376
457
  }
377
-
378
- return [];
379
- }
458
+ };
380
459
 
381
460
  /**
382
- * @param {Element} element
383
- * @param {boolean} [onlyDescendants]
384
- * @returns {void}
461
+ * @param {string|Object} name
462
+ * @param {any} value
463
+ * @returns
385
464
  */
386
- export function dealoc(element, onlyDescendants) {
387
- if (!element) return;
388
- if (!onlyDescendants && elementAcceptsData(element))
389
- cleanElementData([element]);
465
+ JQLite.prototype.attr = function (name, value) {
466
+ for (let i = 0; i < this.length; i++) {
467
+ const element = this[i];
468
+ let ret;
469
+ const { nodeType } = element;
470
+ if (
471
+ nodeType === Node.TEXT_NODE ||
472
+ nodeType === Node.ATTRIBUTE_NODE ||
473
+ nodeType === Node.COMMENT_NODE ||
474
+ !element.getAttribute
475
+ ) {
476
+ continue;
477
+ }
390
478
 
391
- if (element.querySelectorAll) {
392
- cleanElementData(element.querySelectorAll("*"));
393
- }
394
- }
479
+ const lowercasedName = lowercase(name);
480
+ const isBooleanAttr = BOOLEAN_ATTR[lowercasedName];
395
481
 
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);
482
+ if (isObject(name)) {
483
+ for (let key in name) {
484
+ element.setAttribute(key, isBooleanAttr ? lowercasedName : name[key]);
485
+ }
486
+ } else if (isDefined(value)) {
487
+ // setter
405
488
 
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
489
+ if (value === null || (value === false && isBooleanAttr)) {
490
+ element.removeAttribute(name);
491
+ } else {
492
+ element.setAttribute(name, isBooleanAttr ? lowercasedName : value);
493
+ }
494
+ } else {
495
+ // getter
496
+ ret = element.getAttribute(name);
497
+ if (isBooleanAttr && ret !== null) {
498
+ ret = lowercasedName;
499
+ }
500
+ // Normalize non-existing attributes to undefined (as jQuery).
501
+ return ret === null ? undefined : ret;
502
+ }
412
503
  }
413
- }
504
+
505
+ if (isDefined(value) || isObject(name)) {
506
+ return this;
507
+ }
508
+ };
414
509
 
415
510
  /**
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.
511
+ * @param {string|any} key - The key (as a string) to get/set or an object for mass-setting.
512
+ * @param {any} [value] - The value to set. If not provided, the function acts as a getter.
513
+ * @returns {JQLite|any} - The retrieved data if acting as a getter. Otherwise, returns undefined.
422
514
  */
423
- export function getOrSetCacheData(element, key, value) {
424
- if (elementAcceptsData(element)) {
425
- let prop;
515
+ JQLite.prototype.data = function (key, value) {
516
+ let i;
517
+ const nodeCount = this.length;
518
+ if (isUndefined(value)) {
519
+ if (isObject(key)) {
520
+ // we are a write, but the object properties are the key/values
521
+ for (i = 0; i < nodeCount; i++) {
522
+ getOrSetCacheData(this[i], key);
523
+ }
524
+ return this;
525
+ }
526
+ // we are a read, so read the first child.
527
+ const jj = isUndefined(value) ? Math.min(nodeCount, 1) : nodeCount;
528
+ for (let j = 0; j < jj; j++) {
529
+ const nodeValue = getOrSetCacheData(this[j], key, value);
530
+ value = value ? value + nodeValue : nodeValue;
531
+ }
532
+ return value;
533
+ }
534
+ // we are a write, so apply to all children
535
+ for (i = 0; i < nodeCount; i++) {
536
+ getOrSetCacheData(this[i], key, value);
537
+ }
538
+ return this;
539
+ };
426
540
 
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;
541
+ /// ///////////////////////////////////////
542
+ // Functions iterating traversal.
543
+ // These functions chain results into a single
544
+ // selector.
545
+ /// ///////////////////////////////////////
546
+ forEach(
547
+ {
548
+ replaceWith(element, replaceNode) {
549
+ let index;
550
+ const parent = element.parentNode;
551
+ dealoc(element);
552
+ forEach(new JQLite(replaceNode), (node) => {
553
+ if (index) {
554
+ parent.insertBefore(node, index.nextSibling);
555
+ } else {
556
+ parent.replaceChild(node, element);
557
+ }
558
+ index = node;
559
+ });
560
+ },
561
+ children(element) {
562
+ return Array.from(element.childNodes).filter(
563
+ (child) => child.nodeType === Node.ELEMENT_NODE,
564
+ );
565
+ },
566
+ append(element, node) {
567
+ const { nodeType } = element;
568
+ if (
569
+ nodeType !== Node.ELEMENT_NODE &&
570
+ nodeType !== Node.DOCUMENT_FRAGMENT_NODE
571
+ )
572
+ return;
432
573
 
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)];
574
+ node = new JQLite(node);
575
+
576
+ for (let i = 0, ii = node.length; i < ii; i++) {
577
+ const child = node[i];
578
+ element.appendChild(child);
442
579
  }
443
- // mass-setter: data({key1: val1, key2: val2})
444
- for (prop in key) {
445
- data[kebabToCamel(prop)] = key[prop];
580
+ },
581
+
582
+ prepend(element, node) {
583
+ if (element.nodeType === Node.ELEMENT_NODE) {
584
+ const index = element.firstChild;
585
+ forEach(new JQLite(node), (child) => {
586
+ element.insertBefore(child, index);
587
+ });
446
588
  }
447
- }
448
- } else {
449
- // TODO: check should occur perhaps prior at compilation level that this is a valid element
450
- }
451
- }
589
+ },
452
590
 
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.
591
+ remove: removeElement,
461
592
 
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;
593
+ detach(element) {
594
+ removeElement(element, true);
595
+ },
468
596
 
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
- }
597
+ after(element, newElement) {
598
+ let index = element;
599
+ const parent = element.parentNode;
600
+
601
+ if (parent) {
602
+ newElement = new JQLite(newElement);
603
+
604
+ for (let i = 0, ii = newElement.length; i < ii; i++) {
605
+ const node = newElement[i];
606
+ parent.insertBefore(node, index.nextSibling);
607
+ index = node;
475
608
  }
476
- } else {
477
- root[root.length++] = elements;
478
609
  }
479
- }
480
- }
481
- }
610
+ },
482
611
 
483
- function JQLiteController(element, name) {
484
- return JQLiteInheritedData(element, `$${name || "ngController"}Controller`);
485
- }
612
+ parent(element) {
613
+ const parent = element.parentNode;
614
+ return parent && parent.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
615
+ ? parent
616
+ : null;
617
+ },
486
618
 
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];
619
+ // TODO: remove after migrating tests away from JQLite
620
+ find(element, selector) {
621
+ if (element.getElementsByTagName) {
622
+ return element.getElementsByTagName(selector);
623
+ }
624
+ return [];
625
+ },
494
626
 
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
- }
627
+ triggerHandler(element, event, extraParameters) {
628
+ let dummyEvent;
629
+ let eventFnsCopy;
630
+ let handlerArgs;
631
+ const eventName = event.type || event;
632
+ const expandoStore = getExpando(element);
633
+ const events = expandoStore && expandoStore.events;
634
+ const eventFns = events && events[eventName];
500
635
 
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
- }
636
+ if (eventFns) {
637
+ // Create a dummy event to pass to the handlers
638
+ dummyEvent = {
639
+ preventDefault() {
640
+ this.defaultPrevented = true;
641
+ },
642
+ isDefaultPrevented() {
643
+ return this.defaultPrevented === true;
644
+ },
645
+ stopImmediatePropagation() {
646
+ this.immediatePropagationStopped = true;
647
+ },
648
+ isImmediatePropagationStopped() {
649
+ return this.immediatePropagationStopped === true;
650
+ },
651
+ stopPropagation: () => {},
652
+ type: eventName,
653
+ target: element,
654
+ };
509
655
 
510
- /**
511
- *
512
- * @param {Element} element
513
- * @param {boolean} keepData
514
- */
515
- export function removeElement(element, keepData = false) {
516
- if (!keepData) dealoc(element);
517
- const parent = element.parentNode;
518
- if (parent) parent.removeChild(element);
519
- }
656
+ // If a custom event was provided then extend our dummy event with it
657
+ if (event.type) {
658
+ dummyEvent = extend(dummyEvent, event);
659
+ }
520
660
 
521
- /**
522
- * Executea a function on `DOMContentLoaded`
523
- * @param {Function} fn
524
- */
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
- }
537
- }
661
+ // Copy event handlers in case event handlers array is modified during execution.
662
+ eventFnsCopy = shallowCopy(eventFns);
663
+ handlerArgs = extraParameters
664
+ ? [dummyEvent].concat(extraParameters)
665
+ : [dummyEvent];
538
666
 
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(", ")}]`;
667
+ forEach(eventFnsCopy, (fn) => {
668
+ if (!dummyEvent.isImmediatePropagationStopped()) {
669
+ fn.apply(element, handlerArgs);
670
+ }
671
+ });
672
+ }
673
+ },
549
674
  },
675
+ (fn, name) => {
676
+ /**
677
+ * chaining functions
678
+ */
679
+ JQLite.prototype[name] = function (arg1, arg2, arg3) {
680
+ let value;
550
681
 
551
- eq(index) {
552
- return index >= 0 ? JQLite(this[index]) : JQLite(this[this.length + index]);
682
+ for (let i = 0, ii = this.length; i < ii; i++) {
683
+ if (isUndefined(value)) {
684
+ value = fn(this[i], arg1, arg2, arg3);
685
+ if (isDefined(value)) {
686
+ // any function which returns a value needs to be wrapped
687
+ value = JQLite(value);
688
+ }
689
+ } else {
690
+ addNodes(value, fn(this[i], arg1, arg2, arg3));
691
+ }
692
+ }
693
+ return isDefined(value) ? value : this;
694
+ };
553
695
  },
696
+ );
554
697
 
555
- length: 0,
556
- };
698
+ ///////////////////////////////////////////////////////////////////
699
+ //////////// HELPER FUNCTIONS /////////////////////////
700
+ ///////////////////////////////////////////////////////////////////
557
701
 
558
702
  /**
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.
561
- */
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
- };
579
-
580
- /**
581
- * Returns the `$scope` of the element.
582
- * @returns {import("../../core/scope/scope").Scope}
703
+ *
704
+ * @returns {number} Next unique JQInstance id
583
705
  */
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
- };
706
+ function jqNextId() {
707
+ return ++jqId;
708
+ }
594
709
 
595
710
  /**
596
- * Returns the isolate `$scope` of the element.
597
- * @returns {import("../../core/scope/scope").Scope}
711
+ * @param {string} _all
712
+ * @param {string} letter
713
+ * @returns {string}
598
714
  */
599
- JQLite.prototype.isolateScope = function () {
600
- return (
601
- getOrSetCacheData(this[0], "$isolateScope") ||
602
- getOrSetCacheData(this[0], "$isolateScopeNoTemplate")
603
- );
604
- };
715
+ function fnCamelCaseReplace(_all, letter) {
716
+ return letter.toUpperCase();
717
+ }
605
718
 
606
719
  /**
607
- * Return instance of controller attached to element
608
- * @param {string} [name] - Controller name
609
- * @returns {any}
720
+ * Converts kebab-case to camelCase.
721
+ * @param {string} name Name to normalize
722
+ * @returns {string}
610
723
  */
611
- JQLite.prototype.controller = function (name) {
612
- return JQLiteController(this[0], name);
613
- };
724
+ export function kebabToCamel(name) {
725
+ return name.replace(DASH_LOWERCASE_REGEXP, fnCamelCaseReplace);
726
+ }
614
727
 
615
728
  /**
616
- * Return instance of injector attached to element
617
- * @returns {import('../../types').InjectorService}
729
+ * Converts sname to camelCase.
730
+ * @param {string} name
731
+ * @returns {string}
618
732
  */
619
- JQLite.prototype.injector = function () {
620
- return JQLiteInheritedData(this[0], "$injector");
621
- };
733
+ export function snakeToCamel(name) {
734
+ return name.replace(UNDERSCORE_LOWERCASE_REGEXP, fnCamelCaseReplace);
735
+ }
622
736
 
623
737
  /**
624
- * Adds an event listener to each element in the JQLite collection.
625
- *
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.
738
+ * Removes expando data from this element. If key is provided, only
739
+ * its field is removed. If data is empty, also removes `ExpandoStore`
740
+ * from cache.
741
+ * @param {Element} element
742
+ * @param {string} [name] - key of field to remove
629
743
  */
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);
744
+ export function removeElementData(element, name) {
745
+ const expandoId = element[EXPANDO];
746
+ const expandoStore = expandoId && CACHE.get(expandoId);
639
747
 
640
- if (!expandoStore.handle) {
641
- expandoStore.handle = createEventHandler(element, expandoStore.events);
748
+ if (expandoStore) {
749
+ if (name) {
750
+ delete expandoStore.data[name];
751
+ } else {
752
+ expandoStore.data = {};
642
753
  }
643
- // http://jsperf.com/string-indexof-vs-split
644
- const types = type.indexOf(" ") >= 0 ? type.split(" ") : [type];
645
- let j = types.length;
646
754
 
647
- const addHandler = function (type, specialHandlerWrapper, noEventListener) {
648
- let eventFns = expandoStore.events[type];
755
+ removeIfEmptyData(element);
756
+ }
757
+ }
649
758
 
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
- }
759
+ /**
760
+ * Stores data associated with an element inside the expando property of the DOM element.
761
+ *
762
+ * @see {@link https://developer.mozilla.org/en-US/docs/Glossary/Expando MDN Glossary: Expando}
763
+ *
764
+ * @param {Element} element
765
+ * @param {boolean} [createIfNecessary=false]
766
+ * @returns {import("../../core/cache/cache").ExpandoStore}
767
+ */
768
+ function getExpando(element, createIfNecessary = false) {
769
+ let expandoId = element[EXPANDO];
770
+ let expandoStore = expandoId && CACHE.get(expandoId);
657
771
 
658
- eventFns.push(fn);
772
+ if (createIfNecessary && !expandoStore) {
773
+ element[EXPANDO] = expandoId = jqNextId();
774
+ expandoStore = {
775
+ events: {},
776
+ data: {},
777
+ handle: null,
659
778
  };
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
- }
779
+ CACHE.set(expandoId, expandoStore);
670
780
  }
671
- return this;
672
- };
673
781
 
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()];
782
+ return expandoStore;
783
+ }
677
784
 
678
- // booleanAttr is here twice to minimize DOM access
679
- return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
785
+ /**
786
+ * Checks if the string contains HTML tags or entities.
787
+ * @param {string} html
788
+ * @returns {boolean} True if the string is plain text, false if it contains HTML tags or entities.
789
+ */
790
+ export function isTextNode(html) {
791
+ return !/<|&#?\w+;/.test(html);
680
792
  }
681
793
 
682
794
  /**
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
795
+ * Check if element can accept expando data
796
+ * @param {Element} node
797
+ * @returns {boolean}
686
798
  */
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]);
799
+ function elementAcceptsData(node) {
800
+ // The window object can accept data but has no nodeType
801
+ // Otherwise we are only interested in elements (1) and documents (9)
802
+ switch (node.nodeType) {
803
+ case Node.ELEMENT_NODE:
804
+ case Node.DOCUMENT_NODE:
805
+ case undefined: // window.object
806
+ return true;
807
+ default:
808
+ return false;
695
809
  }
696
810
  }
697
811
 
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
812
+ /**
813
+ * @param {string} html
814
+ * @returns {DocumentFragment}
815
+ */
816
+ export function buildFragment(html) {
817
+ let tmp;
818
+ let tag;
819
+ let wrap;
820
+ let tempFragment = document.createDocumentFragment();
821
+ let nodes = [];
822
+ let i;
724
823
 
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
824
+ if (isTextNode(html)) {
825
+ // Convert non-html into a text node
826
+ nodes.push(document.createTextNode(html));
827
+ } else {
828
+ // Convert html into DOM nodes
829
+ tmp = tempFragment.appendChild(document.createElement("div"));
830
+ tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
732
831
 
733
- ret = element.getAttribute(name);
832
+ wrap = wrapMap[tag] || [];
734
833
 
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;
834
+ // Create wrappers & descend into them
835
+ i = wrap.length;
836
+ while (--i > -1) {
837
+ tmp.appendChild(window.document.createElement(wrap[i]));
838
+ tmp = tmp.firstChild;
839
+ }
840
+ tmp.innerHTML = html;
745
841
 
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;
842
+ nodes = concat(nodes, tmp.childNodes);
787
843
 
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
- );
844
+ tmp = tempFragment.firstChild;
845
+ tmp.textContent = "";
846
+ }
826
847
 
827
- /// ///////////////////////////////////////
828
- // Functions iterating traversal.
829
- // These functions chain results into a single
830
- // selector.
831
- /// ///////////////////////////////////////
832
- forEach(
833
- {
834
- removeData: removeElementData,
848
+ let fragment = document.createDocumentFragment();
849
+ fragment.append(...nodes);
850
+ return fragment;
851
+ }
835
852
 
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;
853
+ /**
854
+ * @param {string} html
855
+ * @returns {NodeListOf<ChildNode> | HTMLElement[]}
856
+ */
857
+ function parseHtml(html) {
858
+ let regEx = SINGLE_TAG_REGEXP.exec(html);
859
+ if (regEx) {
860
+ return [document.createElement(regEx[1])];
861
+ }
862
+ let fragment = buildFragment(html);
863
+ if (fragment) {
864
+ return fragment.childNodes;
865
+ }
862
866
 
863
- node = new JQLite(node);
867
+ return [];
868
+ }
864
869
 
865
- for (let i = 0, ii = node.length; i < ii; i++) {
866
- const child = node[i];
867
- element.appendChild(child);
868
- }
869
- },
870
+ /**
871
+ * @param {Element} element
872
+ * @param {boolean} [onlyDescendants]
873
+ * @returns {void}
874
+ */
875
+ export function dealoc(element, onlyDescendants) {
876
+ if (!element) return;
877
+ if (!onlyDescendants && elementAcceptsData(element))
878
+ cleanElementData([element]);
870
879
 
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
- },
880
+ if (element.querySelectorAll) {
881
+ cleanElementData(element.querySelectorAll("*"));
882
+ }
883
+ }
879
884
 
880
- remove: removeElement,
885
+ /**
886
+ * If `ExpandoStore.data` and `ExpandoStore.events` are empty,
887
+ * then delete element's `ExpandoStore` and set its `ExpandoId`
888
+ * to undefined.
889
+ * @param {Element} element
890
+ */
891
+ function removeIfEmptyData(element) {
892
+ const expandoId = element[EXPANDO];
893
+ const { events, data } = CACHE.get(expandoId);
881
894
 
882
- detach(element) {
883
- removeElement(element, true);
884
- },
895
+ if (
896
+ (!data || !Object.keys(data).length) &&
897
+ (!events || !Object.keys(events).length)
898
+ ) {
899
+ CACHE.delete(expandoId);
900
+ element[EXPANDO] = undefined; // don't delete DOM expandos. Chrome don't like it
901
+ }
902
+ }
885
903
 
886
- after(element, newElement) {
887
- let index = element;
888
- const parent = element.parentNode;
904
+ /**
905
+ * Gets or sets cache data for a given element.
906
+ *
907
+ * @param {Element} element - The DOM element to get or set data on.
908
+ * @param {string|Object} key - The key (as a string) to get/set or an object for mass-setting.
909
+ * @param {*} [value] - The value to set. If not provided, the function acts as a getter.
910
+ * @returns {*} - The retrieved data if acting as a getter. Otherwise, returns undefined.
911
+ */
912
+ export function getOrSetCacheData(element, key, value) {
913
+ if (elementAcceptsData(element)) {
914
+ let prop;
889
915
 
890
- if (parent) {
891
- newElement = new JQLite(newElement);
916
+ const isSimpleSetter = isDefined(value);
917
+ const isSimpleGetter = !isSimpleSetter && key && !isObject(key);
918
+ const massGetter = !key;
919
+ const expandoStore = getExpando(element, !isSimpleGetter);
920
+ const data = expandoStore && expandoStore.data;
892
921
 
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
- }
922
+ if (isSimpleSetter) {
923
+ data[kebabToCamel(key)] = value;
924
+ } else {
925
+ if (massGetter) {
926
+ return data;
898
927
  }
899
- },
928
+ if (isSimpleGetter) {
929
+ // don't force creation of expandoStore if it doesn't exist yet
930
+ return data && data[kebabToCamel(key)];
931
+ }
932
+ // mass-setter: data({key1: val1, key2: val2})
933
+ for (prop in key) {
934
+ data[kebabToCamel(prop)] = key[prop];
935
+ }
936
+ }
937
+ } else {
938
+ // TODO: check should occur perhaps prior at compilation level that this is a valid element
939
+ }
940
+ }
900
941
 
901
- parent(element) {
902
- const parent = element.parentNode;
903
- return parent && parent.nodeType !== Node.DOCUMENT_FRAGMENT_NODE
904
- ? parent
905
- : null;
906
- },
942
+ /**
943
+ * Adds nodes or elements to the root array-like object.
944
+ *
945
+ * @param {Array} root - The array-like object to which elements will be added.
946
+ * @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.
947
+ */
948
+ function addNodes(root, elements) {
949
+ // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
907
950
 
908
- // TODO: remove after migrating tests away from JQLite
909
- find(element, selector) {
910
- if (element.getElementsByTagName) {
911
- return element.getElementsByTagName(selector);
951
+ if (elements) {
952
+ // if a Node (the most common case)
953
+ if (elements.nodeType) {
954
+ root[root.length++] = elements;
955
+ } else {
956
+ const { length } = elements;
957
+
958
+ // if an Array or NodeList and not a Window
959
+ if (typeof length === "number" && elements.window !== elements) {
960
+ if (length) {
961
+ for (let i = 0; i < length; i++) {
962
+ root[root.length++] = elements[i];
963
+ }
964
+ }
965
+ } else {
966
+ root[root.length++] = elements;
912
967
  }
913
- return [];
914
- },
968
+ }
969
+ }
970
+ }
915
971
 
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];
972
+ function getController(element, name) {
973
+ return getInheritedData(element, `$${name || "ngController"}Controller`);
974
+ }
924
975
 
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
- };
976
+ /**
977
+ *
978
+ * @param {Element} element
979
+ * @param {string|string[]} name
980
+ * @param {any} [value]
981
+ * @returns
982
+ */
983
+ function getInheritedData(element, name, value) {
984
+ // if element is the document object work with the html element instead
985
+ // this makes $(document).scope() possible
986
+ if (element.nodeType === Node.DOCUMENT_NODE) {
987
+ // TODO Fix types
988
+ element = element.documentElement;
989
+ }
990
+ const names = Array.isArray(name) ? name : [name];
944
991
 
945
- // If a custom event was provided then extend our dummy event with it
946
- if (event.type) {
947
- dummyEvent = extend(dummyEvent, event);
948
- }
992
+ while (element) {
993
+ for (let i = 0, ii = names.length; i < ii; i++) {
994
+ if (isDefined((value = getOrSetCacheData(element, names[i]))))
995
+ return value;
996
+ }
949
997
 
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];
998
+ // If dealing with a document fragment node with a host element, and no parent, use the host
999
+ // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
1000
+ // to lookup parent controllers.
1001
+ element =
1002
+ element.parentNode ||
1003
+ (element.nodeType === Node.DOCUMENT_FRAGMENT_NODE && element.host);
1004
+ }
1005
+ }
955
1006
 
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;
1007
+ /**
1008
+ *
1009
+ * @param {Element} element
1010
+ * @param {boolean} keepData
1011
+ */
1012
+ export function removeElement(element, keepData = false) {
1013
+ if (!keepData) dealoc(element);
1014
+ const parent = element.parentNode;
1015
+ if (parent) parent.removeChild(element);
1016
+ }
970
1017
 
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
- );
1018
+ /**
1019
+ * Executea a function on `DOMContentLoaded`
1020
+ * @param {Function} fn
1021
+ */
1022
+ function onReady(fn) {
1023
+ function trigger() {
1024
+ window.document.removeEventListener("DOMContentLoaded", trigger);
1025
+ fn();
1026
+ }
1027
+ // check if document is already loaded
1028
+ if (window.document.readyState === "complete") {
1029
+ window.setTimeout(fn);
1030
+ } else {
1031
+ // We can not use JQLite since we are not done loading.
1032
+ window.document.addEventListener("DOMContentLoaded", trigger);
1033
+ }
1034
+ }
986
1035
 
987
1036
  function createEventHandler(element, events) {
988
1037
  const eventHandler = function (event, type) {
@@ -1102,3 +1151,27 @@ export function getBlockNodes(nodes) {
1102
1151
 
1103
1152
  return JQLite(blockNodes || nodes);
1104
1153
  }
1154
+
1155
+ export function getBooleanAttrName(element, name) {
1156
+ // check dom last since we will most likely fail on name
1157
+ const booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
1158
+
1159
+ // booleanAttr is here twice to minimize DOM access
1160
+ return booleanAttr && BOOLEAN_ELEMENTS[getNodeName(element)] && booleanAttr;
1161
+ }
1162
+
1163
+ /**
1164
+ * Takes an array of elements, calls any `$destroy` event handlers, removes any data in cache, and finally removes any
1165
+ * listeners.
1166
+ * @param {NodeListOf<Element>|Element[]} nodes
1167
+ */
1168
+ export function cleanElementData(nodes) {
1169
+ for (let i = 0, ii = nodes.length; i < ii; i++) {
1170
+ var events = (CACHE.get(nodes[i][EXPANDO]) || {}).events;
1171
+ if (events && events.$destroy) {
1172
+ JQLite(nodes[i]).triggerHandler("$destroy");
1173
+ }
1174
+ removeElementData(nodes[i]);
1175
+ JQLite.prototype.off.call(JQLite(nodes[i]));
1176
+ }
1177
+ }