eyjs 8.0.0 → 8.1.0

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.
package/dist/eye.umd.js CHANGED
@@ -4,1205 +4,1206 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.eye = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
- /**
8
- * @typedef {Object} AttrMap
9
- * @property {HTMLDivElement} parent - append newly created element to a parent
10
- * @property {Array<String>|String} class - class or array of classes to set
11
- * @property {String} id - set element ID
12
- * @property {Object} data - dataset values to set
13
- * @property {String} text - set element text
14
- * @property {String} html - set element html
15
- */
16
-
17
- const unitlessProps = new Set([
18
- "opacity", "zIndex", "fontWeight", "lineHeight",
19
- "flex", "flexGrow", "flexShrink", "order"
20
- ]);
21
-
22
- /**
23
- * Returns the associated class event of that event
24
- * example: for click event it returns new MouseEvent("click")
25
- * @param {string} ev
26
- */
27
- function getEvent(type, options = {}) {
28
- switch (type) {
29
- // Mouse Events
30
- case 'click':
31
- case 'dblclick':
32
- case 'mousedown':
33
- case 'mouseup':
34
- case 'mousemove':
35
- case 'mouseenter':
36
- case 'mouseleave':
37
- case 'mouseover':
38
- case 'mouseout':
39
- return new MouseEvent(type, options);
40
-
41
- // Pointer Events
42
- case 'pointerdown':
43
- case 'pointerup':
44
- case 'pointermove':
45
- case 'pointerenter':
46
- case 'pointerleave':
47
- case 'pointerover':
48
- case 'pointerout':
49
- return new PointerEvent(type, options);
50
-
51
- // Keyboard Events
52
- case 'keydown':
53
- case 'keyup':
54
- case 'keypress':
55
- return new KeyboardEvent(type, options);
56
-
57
- // Focus Events
58
- case 'focus':
59
- case 'blur':
60
- case 'focusin':
61
- case 'focusout':
62
- return new FocusEvent(type, options);
63
-
64
- // Input & Form Events
65
- case 'input':
66
- case 'change':
67
- case 'submit':
68
- case 'reset':
69
- return new Event(type, options);
70
-
71
- // Wheel
72
- case 'wheel':
73
- return new WheelEvent(type, options);
74
-
75
- // Clipboard
76
- case 'copy':
77
- case 'cut':
78
- case 'paste':
79
- return new ClipboardEvent(type, options);
80
-
81
- // UI
82
- case 'scroll':
83
- case 'resize':
84
- return new UIEvent(type, options);
85
-
86
- // Default: fallback to generic Event
87
- default:
88
- return new CustomEvent(type, options);
89
- }
90
- }
91
-
92
- /**
93
- * @typedef {EyeElement & {
94
- * refresh: (attrs: AttrMap) => ModelEyeElement
95
- * }} ModelEyeElement eye element definition for model elements
96
- */
97
-
98
- const events = [
99
- // Mouse Events
100
- "click",
101
- "dblclick",
102
- "mousedown",
103
- "mouseup",
104
- "mousemove",
105
- "mouseenter",
106
- "mouseleave",
107
- "mouseover",
108
- "mouseout",
109
- "contextmenu",
110
-
111
- // Keyboard Events
112
- "keydown",
113
- "keypress", // Deprecated
114
- "keyup",
115
-
116
- // Focus Events
117
- "focus",
118
- "blur",
119
- "focusin",
120
- "focusout",
121
-
122
- // Form Events
123
- "submit",
124
- "change",
125
- "input",
126
- "reset",
127
- "select",
128
-
129
- // Touch Events (for mobile)
130
- "touchstart",
131
- "touchend",
132
- "touchmove",
133
- "touchcancel",
134
-
135
- // Pointer Events
136
- "pointerdown",
137
- "pointerup",
138
- "pointermove",
139
- "pointerenter",
140
- "pointerleave",
141
- "pointercancel",
142
-
143
- // Drag and Drop Events
144
- "dragstart",
145
- "dragend",
146
- "dragenter",
147
- "dragover",
148
- "dragleave",
149
- "drop",
150
-
151
- // Window/Document Events
152
- "resize",
153
- "scroll",
154
- "load",
155
- "beforeunload",
156
- "unload",
157
-
158
- // Media Events
159
- "play",
160
- "pause",
161
- "ended",
162
- "volumechange",
163
- "timeupdate",
164
-
165
- // Clipboard Events
166
- "copy",
167
- "cut",
168
- "paste",
169
-
170
- // Animation and Transition Events
171
- "animationstart",
172
- "animationend",
173
- "animationiteration",
174
- "transitionstart",
175
- "transitionend",
176
-
177
- // Mutation Events
178
- "DOMSubtreeModified",
179
- "DOMNodeInserted",
180
- "DOMNodeRemoved",
181
-
182
- // Other Events
183
- "error",
184
- "hashchange",
185
- "popstate",
186
- ];
187
-
188
- const htmlElements = [
189
- // Metadata
190
- "<base>",
191
- "<head>",
192
- "<link>",
193
- "<meta>",
194
- "<style>",
195
- "<title>",
196
-
197
- // Sections
198
- "<body>",
199
- "<address>",
200
- "<article>",
201
- "<aside>",
202
- "<footer>",
203
- "<header>",
204
- "<h1>",
205
- "<h2>",
206
- "<h3>",
207
- "<h4>",
208
- "<h5>",
209
- "<h6>",
210
- "<main>",
211
- "<nav>",
212
- "<section>",
213
-
214
- // Text content
215
- "<blockquote>",
216
- "<dd>",
217
- "<div>",
218
- "<dl>",
219
- "<dt>",
220
- "<figcaption>",
221
- "<figure>",
222
- "<hr>",
223
- "<li>",
224
- "<ol>",
225
- "<p>",
226
- "<pre>",
227
- "<ul>",
228
-
229
- // Inline text semantics
230
- "<a>",
231
- "<abbr>",
232
- "<b>",
233
- "<bdi>",
234
- "<bdo>",
235
- "<br>",
236
- "<cite>",
237
- "<code>",
238
- "<data>",
239
- "<dfn>",
240
- "<em>",
241
- "<i>",
242
- "<kbd>",
243
- "<mark>",
244
- "<q>",
245
- "<rp>",
246
- "<rt>",
247
- "<ruby>",
248
- "<s>",
249
- "<samp>",
250
- "<small>",
251
- "<span>",
252
- "<strong>",
253
- "<sub>",
254
- "<sup>",
255
- "<time>",
256
- "<u>",
257
- "<var>",
258
- "<wbr>",
259
-
260
- // Image and multimedia
261
- "<area>",
262
- "<audio>",
263
- "<img>",
264
- "<map>",
265
- "<track>",
266
- "<video>",
267
-
268
- // Embedded content
269
- "<embed>",
270
- "<iframe>",
271
- "<object>",
272
- "<picture>",
273
- "<portal>",
274
- "<source>",
275
-
276
- // Scripting
277
- "<canvas>",
278
- "<noscript>",
279
- "<script>",
280
-
281
- // Demarcating edits
282
- "<del>",
283
- "<ins>",
284
-
285
- // Table content
286
- "<caption>",
287
- "<col>",
288
- "<colgroup>",
289
- "<table>",
290
- "<tbody>",
291
- "<td>",
292
- "<tfoot>",
293
- "<th>",
294
- "<thead>",
295
- "<tr>",
296
-
297
- // Forms
298
- "<button>",
299
- "<datalist>",
300
- "<fieldset>",
301
- "<form>",
302
- "<input>",
303
- "<label>",
304
- "<legend>",
305
- "<meter>",
306
- "<optgroup>",
307
- "<option>",
308
- "<output>",
309
- "<progress>",
310
- "<select>",
311
- "<textarea>",
312
-
313
- // Interactive elements
314
- "<details>",
315
- "<dialog>",
316
- "<summary>",
317
-
318
- // Web components / scripting base
319
- "<slot>",
320
- "<template>",
321
- ];
322
-
323
- const quickexec = ["click", "focus", "blur", "select", "submit", "reset"];
324
-
325
- function flat(word) {
326
- let n = "";
327
- for (let i = 0; i < word.length; i++) {
328
- const t = word[i];
329
- if (t === t.toUpperCase() && t !== t.toLowerCase()) n += "-" + t;
330
- else n += t;
331
- }
332
- return n.toLowerCase();
333
- }
334
-
335
- const localdata = new WeakMap();
336
-
337
- /**
338
- * cmcl stands for Create Model Children Layers, recursively creates model layers one by one
339
- * @param {EyeElement} parent
340
- * @param {Object} layer
341
- * @returns {Array<{name: string,set: (parent: EyeElement, value: String) =>}>}
342
- */
343
- function cmcl(parent, layer) {
344
- let obj = [];
345
- for (const key in layer) {
346
- const subcontent = layer[key];
347
- const [def, _set] = key.split(":");
348
- const [tagName, ...cls] = def.split(".");
349
- let [_set_name = null, _set_default = null] = (_set || "")
350
- .split("-")
351
- .map((a) => a.trim());
352
-
353
- let elm = e(tagName.trim(), {
354
- class: cls,
355
- parent,
356
- data: _set ? { value: _set_name } : undefined,
357
- });
358
-
359
- if (_set && _set_name) {
360
- obj.push({
361
- name: _set_name,
362
- set(parent, value) {
363
- let elm = parent.find(`[data-value="${_set_name}"]`);
364
- if (elm) elm[0].textContent = value ?? _set_default;
365
- }
366
- });
367
- }
368
-
369
- // recursive
370
- if (
371
- subcontent &&
372
- typeof subcontent === "object" &&
373
- !(subcontent instanceof Array)
374
- )
375
- obj = obj.concat(cmcl(elm, subcontent));
376
- }
377
- return obj;
378
- }
379
-
380
- let delegationEvents = ["click", "dblclick", "submit", "input", "change", "keydown", "keyup", "keypress", "focusin", "focusout", "mousedown", "mouseup", "mousemove", "contextmenu", "auxclick", "wheel", "mouseover", "mouseout", "pointerdown", "pointerup", "pointermove", "pointerover", "pointerout", "gotpointercapture", "lostpointercapture", "pointercancel"];
381
- let normalSetterGetter = (action, v, elm) => v;
382
-
383
- /**
384
- * Eye wrapper offers a subset of functions that ease DOM minipulation! Power of JQuery with
385
- * some a modern design and a bunch of new functions.
386
- * @author Yousef Neji
387
- */
388
- class EyeElement {
389
- /**
390
- * Raw html element
391
- * @type {Array<HTMLElement>}
392
- */
393
- #raw = null;
394
-
395
- /**
396
- * Used to store delegated events listeners
397
- * @type {Map<String,Set<{callback, target: string}>>}
398
- */
399
- #dlgListeners = new Map();
400
-
401
- /**
402
- * Custom way or modifier that redefine the way you set/get
403
- * this element `textContent` or `value`:
404
- * - access this feature from `.redefine` method.
405
- */
406
- #customSet = {
407
- value: normalSetterGetter,
408
- text: normalSetterGetter
409
- };
410
-
411
- /**
412
- * Called internally to initiate the main EyeElement functionalities
413
- * @method EyeElement#init
414
- * @param {string|HTMLElement} selector
415
- * @param {AttrMap} attrs
416
- * @param {Object} css
417
- * @returns {EyeElement}
418
- */
419
- constructor(selector, attrs, css) {
420
- let _this = this;
421
- if (selector instanceof HTMLElement) {
422
- this.#raw = [selector];
423
- } else if (htmlElements.includes(selector)) {
424
- // creating a new element
425
- this.#raw = [document.createElement(selector.substring(1, selector.length - 1))];
426
- } else if (Array.isArray(selector)) {
427
- this.#raw = [];
428
- selector.forEach((a) => {
429
- if (a instanceof EyeElement) _this.#raw = _this.#raw.concat(...a.raw);
430
- else if (a instanceof HTMLElement) _this.#raw.push(a);
431
- });
432
- } else {
433
- // selecting
434
- let s = selector.slice(-1) === "!";
435
- this.#raw = [...document.querySelectorAll(s ? selector.slice(0, -1) : selector)];
436
- }
437
-
438
- /**
439
- * Handler used to integrate delegation concept/algorithme
440
- * @param {Event} e
441
- */
442
- function handler(e) {
443
- let name = e.type,
444
- listeners = _this.#dlgListeners,
445
- _etarget = e.target,
446
- me = this; // refers to the element being listening to the event
447
-
448
- if (listeners.has(name)) {
449
- let cbs = listeners.get(name);
450
- cbs?.forEach(({ callback, target }) => {
451
- if (_etarget.closest(target)) {
452
- // we hitting the target
453
- callback(e, me);
454
- }
455
- });
456
- }
457
- }
458
-
459
- this.each((elm, idx) => {
460
- let parentElm = null;
461
- if (attrs)
462
- for (const key in attrs) {
463
- const value = attrs[key];
464
- if (key == "class")
465
- elm.classList.add.apply(
466
- elm.classList,
467
- (value instanceof Array ? value : value.split(" ")).filter(a => a != "")
468
- );
469
- else if (key == "text") elm.textContent = value;
470
- else if (key == "html") elm.innerHTML = value;
471
- else if (key == "data") for (const k in value) elm.dataset[k] = value[k];
472
- else if (key == "parent") parentElm = value;
473
- else if (key in elm) elm[key] = value;
474
- else if (key[0] != "_") elm.setAttribute(key, value); // we must ignore _ started keys 'cause they are used by models
475
- }
476
- if (css)
477
- for (const key in css)
478
- if (key.indexOf("-") != -1) elm.style[key] = css[key];
479
- else elm.style[flat(key)] = css[key];
480
- if (parentElm instanceof EyeElement || parentElm instanceof HTMLElement) parentElm.append(elm);
481
-
482
- // creating the delegation handling model
483
- delegationEvents.forEach(ev => {
484
- elm.addEventListener(ev, handler);
485
- });
486
- });
487
-
488
- // creating/initiating events functions
489
- events.forEach(ev => {
490
- _this[ev] = function (cb) {
491
- if (cb) {
492
- if (typeof cb == "function") _this.on(ev, cb);
493
- } else if (quickexec.includes(ev))
494
- _this.each(elm => elm[ev]());
495
- else _this.trigger(ev);
496
- return _this;
497
- };
498
- });
499
-
500
- return this;
501
- }
502
-
503
- /**
504
- * Length of current selection
505
- * @type {Number}
506
- */
507
- get length() {
508
- return this.#raw.length;
509
- }
510
-
511
- /**
512
- * Raw html element
513
- * @type {HTMLElement}
514
- */
515
- get raw() {
516
- return this.#raw;
517
- }
518
-
519
- /**
520
- * Run(loop) through selected NodeList, or run a single call for one single element
521
- * @method EyeElement#each
522
- * @param {(elm: HTMLElement, index: number, current: EyeElement)=>} cb
523
- * @returns {EyeElement}
524
- */
525
- each(cb) {
526
- for (let i = 0; i < this.#raw.length; i++) {
527
- const elm = this.#raw[i];
528
- let exit = cb(elm, i, this);
529
- if (exit === false) break;
530
- }
531
- return this;
532
- }
533
- /**
534
- * Run(loop) through selected NodeList's children.
535
- * @param {(elm: HTMLElement, index: number, parent: HTMLElement, Object: EyeElement)=>{}} cb
536
- * @returns {EyeElement}
537
- */
538
- eachChild(cb) {
539
- let _this = this;
540
- return this.each(function (elm, i, parent) {
541
- let exit = true;
542
- for (let j = 0; j < elm.children.length; j++) {
543
- exit = cb(elm.children[j], j, elm, _this);
544
- if (exit === false) break;
545
- }
546
- if (exit === false) return;
547
- })
548
- }
549
- /**
550
- * Set or get element html
551
- * @method EyeElement#html
552
- * @param {string} [html]
553
- * @returns {EyeElement|string}
554
- */
555
- html(html) {
556
- let out = undefined;
557
- this.each((elm, idx) => {
558
- if (html === undefined) return out = elm.innerHTML;// getting the first one and exiting
559
- elm.innerHTML = html;
560
- });
561
- return out != undefined ? out : this;
562
- }
563
- /**
564
- * Set or get element text
565
- * @method EyeElement#text
566
- * @param {string} [text]
567
- * @returns {EyeElement|string}
568
- */
569
- text(text) {
570
- let out = undefined;
571
- this.each((elm, idx) => {
572
- if (text === undefined) return out = this.#customSet.text("get", elm.textContent, elm);
573
- elm.textContent = this.#customSet.text("set", text, elm);
574
- });
575
- return out != undefined ? out : this;
576
- }
577
- /**
578
- * Set or get element's data values
579
- * @method EyeElement#data
580
- * @param {string} key
581
- * @param {*} [value]
582
- * @returns {EyeElement|string}
583
- */
584
- data(key, value) {
585
- if (!localdata.has(this)) localdata.set(this, {});
586
- if (key) {
587
- if (value != undefined) localdata.get(this)[key] = value;
588
- else return localdata.get(this)[key];
589
- }
590
- return this;
591
- }
592
-
593
- /**
594
- * Set or get an attribute value
595
- * @method EyeElement#attr
596
- * @param {string} name
597
- * @param {*} [value]
598
- * @returns {EyeElement|string}
599
- */
600
- attr(name, value) {
601
- let out = "";
602
- this.each((elm, idx) => {
603
- if (name.indexOf("data-") === 0) {
604
- let [key, val] = name.split("-").map((a) => a.trim());
605
- // modify data
606
- if (value == undefined) return out = elm.dataset[val];
607
- elm.dataset[val] = value;
608
- } else {
609
- if (name in elm) {
610
- if (value == undefined) return out = elm[name];
611
- elm[name] = value;
612
- } else if (name[0] != "_") {
613
- if (value == undefined) return out = elm.getAttribute(name)
614
- elm.setAttribute(name, value);
615
- }
616
- }
617
- });
618
- return out ? out : this;
619
- }
620
- /**
621
- * Removing attribute of this element
622
- * @type {string} attrName
623
- * @returns {EyeElement}
624
- */
625
- rAttr(attrName) {
626
- if (attrName) {
627
- this.each((elm, idx) => {
628
- elm.removeAttribute(attrName);
629
- });
630
- }
631
- return this;
632
- }
633
- /**
634
- * Super fancy class function that allows to modify class with different methods in one as follow:
635
- * - `"classname"`: add classname to the element.
636
- * - `"-classname"`: remove classname from class list.
637
- * - `"%classname"`: toggle classname existing.
638
- * - `"?classname"`: check classname existing in class list.
639
- * - `"classnameA/classnameB"`: replace classnameA by classnameB
640
- * @method EyeElement#class
641
- * @param {string} actions
642
- * @returns {EyeElement|string}
643
- */
644
- class(actions) {
645
- let out = undefined;
646
- this.each((elm, idx) => {
647
- if (typeof actions === "number") return out = elm.classList.item(actions);
648
-
649
- actions.split(" ").forEach((action) => {
650
- if (action[0] == "-") {
651
- elm.classList.remove(action.substring(1, action.length));
652
- } else if (action[0] == "%") {
653
- elm.classList.toggle(action.substring(1, action.length));
654
- } else if (action[0] == "?") {
655
- out = elm.classList.contains(action.substring(1, action.length));
656
- } else if (action.indexOf("/") != -1) {
657
- let [v1, v2] = action.split("/");
658
- elm.classList.replace(v1, v2);
659
- } else {
660
- elm.classList.add(action);
661
- }
662
- });
663
- if (out) return;
664
- });
665
-
666
- return out != undefined ? out : this;
667
- }
668
- /**
669
- * Show/display the element
670
- * @method EyeElement#show
671
- * @param {string} cls
672
- * @returns {EyeElement}
673
- */
674
- show(cls) {
675
- this.each((elm, idx) => {
676
- elm.style.display = cls ?? "inline-block";
677
- });
678
- return this;
679
- }
680
- /**
681
- * Hide the element
682
- * @method EyeElement#hide
683
- * @param {boolean} opacity whether to hide by making invisible?
684
- * @returns {EyeElement}
685
- */
686
- hide(opacity) {
687
- this.each((elm, idx) => {
688
- if (opacity) elm.style.opacity = 0;
689
- else elm.style.display = "none";
690
- });
691
- return this;
692
- }
693
- /**
694
- * Append one or more elements to the current element, only effect the first element in the selected list
695
- * @method EyeElement#append
696
- * @param {HTMLElement|Array<Node|EyeElement>} elm
697
- * @param {"next" | "after" | "previous" | "before" | "first" | "afterbegin" | "last" | "beforeend"} [pos] [optional]
698
- * @returns {EyeElement}
699
- */
700
- append(elm, pos) {
701
- (Array.isArray(elm) ? elm : [elm]).forEach(item => {
702
- let nodes = [];
703
- if (item instanceof EyeElement) nodes = [...item.raw];
704
- else if (item instanceof HTMLElement) nodes = [item];
705
-
706
- nodes.forEach(node => {
707
- switch (pos) {
708
- case "next":
709
- case "after":
710
- this.#raw[0].after(node);
711
- break;
712
- case "previous":
713
- case "before":
714
- this.#raw[0].before(node);
715
- break;
716
- case "afterbegin":
717
- case "first":
718
- this.#raw[0].prepend(node);
719
- break;
720
- case "beforeend":
721
- case "last":
722
- default:
723
- this.#raw[0].append(node);
724
- }
725
- });
726
- });
727
- return this;
728
- }
729
- /**
730
- * Insert element after this one, or return the one lies there
731
- * @method EyeElement#after
732
- * @param {EyeElement|HTMLElement|AttrMap} elm
733
- * @returns {EyeElement|HTMLElement}
734
- */
735
- after(elm) {
736
- if (elm instanceof HTMLElement || elm instanceof EyeElement || elm instanceof Node)
737
- this.#raw[0].after(elm instanceof EyeElement ? elm.raw[0] : elm);
738
- else return this.#raw[0].nextElementSibling ? e(this.#raw[0].nextElementSibling, elm) : null;
739
- return this;
740
- }
741
- /**
742
- * Insert element before this one, or return the one lies there
743
- * @method EyeElement#before
744
- * @param {EyeElement|HTMLElement|AttrMap} elm
745
- * @returns {EyeElement|HTMLElement}
746
- */
747
- before(elm) {
748
- if (elm instanceof HTMLElement || elm instanceof EyeElement || elm instanceof Node)
749
- this.#raw[0].before(elm instanceof EyeElement ? elm.raw[0] : elm);
750
- else return this.#raw[0].previousElementSibling ? e(this.#raw[0].previousElementSibling, elm) : null
751
- return this;
752
- }
753
- /**
754
- * Replace current element with the new element, or multiple elements with multiple selected elements
755
- * @method EyeElement#replaceWith
756
- * @param {...HTMLElement|EyeElement} elms
757
- * @param {string} [pos] [optional]
758
- * @returns {EyeElement}
759
- */
760
- replaceWith(...elms) {
761
- let nodes = [];
762
- (Array.isArray(elms) ? elms : [elms]).forEach(item => {
763
- if (item instanceof EyeElement) nodes = nodes.concat(...item.raw);
764
- else if (item instanceof HTMLElement) nodes.push(item);
765
- });
766
- this.#raw[0].replaceWith(...nodes);
767
- return this;
768
- }
769
- /**
770
- * Get current element parent or append it to one
771
- * @method EyeElement#parent
772
- * @param {HTMLElement|EyeElement} parent
773
- * @param {boolean} clone [true] append clones of the elements
774
- * @returns {EyeElement}
775
- */
776
- parent(parent, clone) {
777
- if (parent) {
778
- if (!(parent instanceof HTMLElement) && !(parent instanceof EyeElement))
779
- throw new Error(
780
- "[EyeJS] Unable to append current element to parent because it's not HTMLElement"
781
- );
782
- this.each(elm => {
783
- parent.append(clone === true ? elm.cloneNode(true) : elm);
784
- });
785
- return this;
786
- }
787
- return e(this.#raw[0].parentElement);
788
- }
789
- /**
790
- * Returns whether current node is the same/equal (depending on `check`) as the passed node or not
791
- * @method EyeElement#is
792
- * @param {HTMLElement|EyeElement} node
793
- * @param {"connected" | "same" | "equal"} [check] check type `same`, `equal`
794
- * @returns {boolean}
795
- */
796
- is(node, check) {
797
- node = node instanceof EyeElement ? node.#raw[0] : node;
798
- if (node === "connected") return this.#raw[0].isConnected;
799
- switch (check) {
800
- case "same":
801
- return this.#raw[0].isSameNode(node);
802
- case "equal":
803
- return this.#raw[0].isEqualNode(node);
804
- default:
805
- console.error(
806
- `[EyeJS] Unknown check "${check}", possible values are ["same","equal","connected"]`
807
- );
808
- return false;
809
- }
810
- }
811
- /**
812
- * Cheap check for previous/next/child elements by selector
813
- * @param {"next"|"previous"|string} desc
814
- * @returns {boolean}
815
- */
816
- has(selector) {
817
- if (typeof selector === "string")
818
- switch (selector) {
819
- case "next": return this.#raw[0].nextElementSibling != null
820
- case "previous": return this.#raw[0].previousElementSibling != null
821
- default: return this.#raw[0].querySelector(selector) != null
822
- }
823
- }
824
- /**
825
- * Set or get a css attribute
826
- * @method EyeElement#css
827
- * @param {string} attr
828
- * @param {string|number} [value]
829
- * @returns {EyeElement|string}
830
- */
831
- css(attr, value) {
832
- if (attr) {
833
- if (!unitlessProps.has(attr) && typeof value === "number" && value != 0) value = `${value}px`;
834
- let out = undefined;
835
- attr = flat(attr);
836
- this.each((elm, idx) => {
837
- if (value === undefined) return out = elm.style[attr];
838
- elm.style[attr] = value;
839
- });
840
- return out != undefined ? out : this;
841
- } else return console.error(`[EyeJS] missing argument "attr" in function .css`);
842
- }
843
- /**
844
- * Remove current element, or find and remove a sub element of it
845
- * @method EyeElement#remove
846
- * @param {string?} selector
847
- * @param {()=>void?} cb optionally execute something before removing the selected element
848
- * @returns {EyeElement}
849
- */
850
- remove(selector, cb) {
851
- this.each((elm, idx) => {
852
- if (typeof selector === "string")
853
- elm.querySelectorAll(selector).forEach(item => {
854
- if (typeof cb === "function") cb(e(item));
855
- item.remove();
856
- });
857
- else elm.remove();
858
- });
859
- return this;
860
- }
861
- /**
862
- * Attach an listener/handler to specific event or events
863
- * @method EyeElement#on
864
- * @param {string} ev may contain multiple events separated by " "(space)
865
- * @param {()=>} cb
866
- * @param {{ passive: boolean, capture: boolean, once: boolean, signal: AbortSignal }} opts
867
- * @returns {EyeElement|void}
868
- */
869
- on(ev, cb, opts) {
870
- if (typeof cb !== "function")
871
- return console.error(
872
- "[EyeJS] .on method is missing the actuall callback `cb` or not of type function"
873
- );
874
-
875
- ev.split(" ")
876
- .forEach(evt => {
877
- this.each((elm) => {
878
- elm.addEventListener(evt, cb, opts);
879
- });
880
- });
881
-
882
- return this;
883
- }
884
- /**
885
- * Attach a delegatable event listener to the subelement of this one
886
- * @param {string} ev
887
- * @param {string} target
888
- * @param {()=>} cb
889
- */
890
- delegate(ev, target, cb) {
891
- let outsider = null;
892
- ev.split(" ")
893
- .forEach(evt => {
894
- if (!delegationEvents.includes(evt))
895
- return outsider = evt; // outsider events
896
-
897
- if (!_this.#dlgListeners.has(evt))
898
- _this.#dlgListeners.set(evt, new Set());
899
- _this.#dlgListeners.get(evt).add({ callback: cb, target });
900
- });
901
-
902
- if (outsider !== null)
903
- return console.warn(`[EyeJS] trying to use delegation with an inappropriate event "${outsider}"`);
904
- }
905
- /**
906
- * Attach a single time handler, that get auto-removed after first call
907
- * @param {string} ev
908
- * @param {()=>} cb
909
- */
910
- once(ev, cb) {
911
- this.on(ev, cb, { once: true }); // simply
912
- }
913
- /**
914
- * Remove event listener of a specific event
915
- * @method EyeElement#off
916
- * @param {string} ev
917
- * @param {function} cb
918
- * @returns {EyeElement|void}
919
- */
920
- off(ev, cb) {
921
- let _this = this,
922
- listeners = _this.#dlgListeners;
923
- if (typeof cb != "function")
924
- return console.error(
925
- "[EyeJS] .off method is missing the actuall callback `cb` or not of type function"
926
- );
927
- ev = ev.split(" ");
928
-
929
- this.each((elm, idx) => {
930
- ev.forEach(evt => elm.removeEventListener(evt, cb));
931
- });
932
- // now delegated events
933
- ev.forEach(evt => {
934
- if (listeners.has(evt)) {
935
- let set = listeners.get(evt);
936
- for (const item of set) {
937
- if (cb === item.callback) {
938
- // found it & remove it
939
- set.delete(item);
940
- }
941
- }
942
- }
943
- });
944
- }
945
- /**
946
- * Trigger specific event for this element
947
- * @method EyeElement#trigger
948
- * @param {string} ev
949
- * @returns {EyeElement}
950
- */
951
- trigger(ev) {
952
- this.each((elm, idx) => {
953
- elm.dispatchEvent(ev instanceof Event ? ev : getEvent(ev));
954
- });
955
- return this;
956
- }
957
- /**
958
- * Find one or multiple child elements by `selector`,
959
- * and optionally execute a callback on the fly!
960
- * @method EyeElement#find
961
- * @param {string} selector
962
- * @param {(elm: EyeElement, parent: EyeElement) => void} [cb]
963
- * @returns {Array<HTMLElement>}
964
- */
965
- find(selector, cb) {
966
- let found = [];
967
- this.each((elm, idx) => {
968
- elm.querySelectorAll(selector).forEach(res => {
969
- if (typeof cb === "function") cb(e(res), e(elm));
970
- found.push(res);
971
- });
972
- });
973
- return found.length == 0 ? null : found;
974
- }
975
- /**
976
- * Returns a clone of current selected element/s
977
- * @method EyeElement#clone
978
- * @param {HTMLElement} [parent] optionally append new clone to a parent
979
- * @returns {Array<EyeElement>}
980
- */
981
- clone(parent) {
982
- let list = [];
983
- this.each((nd) => {
984
- list.push(nd.cloneNode(true));
985
- });
986
- if (parent instanceof HTMLElement || parent instanceof EyeElement) list.forEach(el => parent.append(el));
987
- return list;
988
- }
989
- /**
990
- * Compute DOMRect or style declaration of current element
991
- * @method EyeElement#compute
992
- * @param {"bounds" | "style"} type
993
- * @returns {DOMRect|CSSStyleDeclaration}
994
- */
995
- compute(type) {
996
- type = type || "bounds";
997
- if (type === "bounds")
998
- return (this.#raw[0]).getBoundingClientRect();
999
- else if (type == "style")
1000
- return getComputedStyle(this.#raw[0])
1001
- console.error(`[EyeJS] unknown type "${type}" in function .compute, possible values are "bounds" "style"`);
1002
- }
1003
-
1004
- /**
1005
- * Get specific client informations.
1006
- * @param {"width" | "height" | "left" | "top"} attr
1007
- * @returns {EyeElement}
1008
- */
1009
- client(attr) {
1010
- if (['width', 'height', 'left', 'top'].includes(attr))
1011
- return this.#raw[0][`client${attr[0].toUpperCase()}${attr.substring(1, attr.length)}`];
1012
- else console.error(`[EyeJS] Unknown client* attribute "${attr}" in .client(attr)`);
1013
- return this;
1014
- }
1015
- /**
1016
- * Activate/disactive different pointer features such as PointerLock, pointerCapture...
1017
- * @method EyeElement#pointer
1018
- * @param {"capture" | "lock"} action
1019
- * @param {boolean} status
1020
- * @param {string} [pid]
1021
- * @returns {EyeElement}
1022
- */
1023
- pointer(action, status, pid) {
1024
- this.each((elm, idx) => {
1025
- if (action === "capture") {
1026
- if (status === true) elm.setPointerCapture(pid);
1027
- else elm.releasePointerCapture(pid);
1028
- } else if (action === "lock") {
1029
- if (status === true) elm.requestPointerLock();
1030
- else document.exitPointerLock();
1031
- }
1032
- });
1033
- return this;
1034
- }
1035
- /**
1036
- * Returns the count of children for this element
1037
- * @type {number}
1038
- */
1039
- get childlen() {
1040
- return this.#raw[0].children.length;
1041
- }
1042
- /**
1043
- * Select a child of this element
1044
- * @method EyeElement#child
1045
- * @param {number} index
1046
- * @returns {EyeElement|null}
1047
- */
1048
- child(index) {
1049
- if (index === undefined) return this.#raw[0].children.length;
1050
- if (this.#raw[0].children[index]) return e(this.#raw[0].children[index]);
1051
- return null;
1052
- }
1053
- /**
1054
- * Set/get the value of the current element
1055
- * @method EyeElement#val
1056
- * @param {*} value
1057
- * @returns
1058
- */
1059
- val(value) {
1060
- if (value != undefined) this.each((a) => a.value = this.#customSet.value("set", value, a));
1061
- else return this.#customSet.value("get", this.#raw[0].value, this.#raw[0]);
1062
- return this;
1063
- }
1064
- /**
1065
- * Serialize this element to send it over network, returns 3 formats `json`, `url` & `fd`(formData)
1066
- * @method EyeElement#serialize
1067
- * @param {{inputs: Array<string>}} opts
1068
- * @returns {{json: Object, url: String, fd: FormData}}
1069
- */
1070
- serialize(opts) {
1071
- opts = opts || {};
1072
- let {
1073
- inputs = ["input", "textarea", "select"],
1074
- } = opts;
1075
- let out = {
1076
- json: {},
1077
- url: "",
1078
- fd: new FormData()
1079
- };
1080
- this.#raw[0].querySelectorAll(inputs.join(','))
1081
- .forEach((inp, i) => {
1082
- let name = inp.name || inp.dataset.name;
1083
- let value = inp.value || inp.textContent;
1084
- if (typeof opts[name] === "function") value = opts[name](inp);
1085
-
1086
- if (inp.type == "file")
1087
- inp.files.forEach(file => {
1088
- out.fd.append(name, file);
1089
- });
1090
- else {
1091
- out.json[name] = value;
1092
- out.fd.append(name, value);
1093
- }
1094
- });
1095
-
1096
- out.url = new URLSearchParams(out.json).toString();
1097
- return out;
1098
- }
1099
- /**
1100
- * Redefine the way `.text` or `.val` set or get data to and from this element.
1101
- * @method EyeElement#redefine
1102
- * @param {"text" | "value"} type
1103
- * @param {(action: "set" | "get", value: *, elm: EyeElement) => *} process
1104
- * @returns {EyeElement}
1105
- */
1106
- redefine(type, process) {
1107
- if (["text", "value"].includes(type) && typeof process == "function")
1108
- this.#customSet[type] = process;
1109
- return this;
1110
- }
1111
- /**
1112
- * Animate current object
1113
- * @method EyeElement#animate
1114
- * @param {Array<Keyframe>} keyframes
1115
- * @param {KeyframeAnimationOptions} opts
1116
- * @returns {Array<Animation>|Animation}
1117
- */
1118
- animate(keyframes, opts) {
1119
- /**
1120
- * @type {Array<Animation>}
1121
- */
1122
- let anmts = [];
1123
- opts.duration = opts.duration || 1000;
1124
- this.each((elm, i) => {
1125
- anmts.push(elm.animate(keyframes, opts));
1126
- });
1127
- return anmts.length == 1 ? anmts[0] : anmts;
1128
- }
1129
-
1130
- /**
1131
- * Find first element position within parent element
1132
- * @method EyeElement#position
1133
- * @returns {number}
1134
- */
1135
- position() {
1136
- let pos = -1;
1137
- for (let i = 0; i < this.#raw[0].parentNode.children.length; i++) {
1138
- const child = this.#raw[0].parentNode.children[i];
1139
- if (this.#raw[0].isSameNode(child)) {
1140
- pos = i;
1141
- break;
1142
- }
1143
- }
1144
- return pos;
1145
- }
1146
-
1147
- /**
1148
- * Get drawing context for canvas
1149
- * @param {"2d"|"webgl"|"webgl2"|"bitmaprenderer"} contextId
1150
- * @param {*} contextSettings
1151
- * @returns {EyeElement|CanvasRenderingContext2D}
1152
- */
1153
- getctx(contextId, contextSettings) {
1154
- if (typeof this.#raw[0].getContext === "function")
1155
- return this.#raw[0].getContext(contextId, contextSettings);
1156
- return this;
1157
- }
1158
- }
1159
- /**
1160
- * Creates or select nodes using css selectors, offering a pack of useful functions to use around your code!
1161
- * @param {String} tag
1162
- * @param {AttrMap} attrs
1163
- * @param {Object} css CSS styles to be applied to the element.
1164
- * @returns {EyeElement}
1165
- */
1166
- function e(tag, attrs, css) {
1167
- if (typeof tag === "string" && tag.indexOf("model:") === 0 || tag === "model") {
1168
- if (!attrs) return console.error("[EyeJS] Model creation requires parameter 'attr' as prototype, none delivered!");
1169
-
1170
- tag = tag.split(":");
1171
- let cls = ["eye-model"];
1172
- if (tag[1])
1173
- cls = cls.concat(tag[1].split(" ").filter(a => a != ""));
1174
- // creating a model
1175
- let model = e("<div>", {
1176
- class: cls.join(" "),
1177
- });
1178
-
1179
- let sets = cmcl(model, attrs);
1180
-
1181
- /**
1182
- * @param {string} attrs
1183
- * @returns {ModelEyeElement}
1184
- */
1185
- return (attrs) => {
1186
- let copy = e(model.clone(attrs?.parent));
1187
- // define & attach the refresh function
1188
- copy.refresh = function (attrs = {}) {
1189
- let def = attrs.default === false ? false : true;
1190
- sets.forEach((item) => {
1191
- if (def === true || (!def && attrs.hasOwnProperty(item.name)))
1192
- item.set(copy, attrs[item.name]);
1193
- });
1194
- return this;
1195
- };
1196
- return copy.refresh(attrs);
1197
- };
1198
- } else if (tag != null) {
1199
- let ne = new EyeElement(tag, attrs, css);
1200
- return ne.length === 0 ? null : ne;
1201
- }
1202
- }
1203
-
1204
- // gloablly exposed
1205
- window.e = e;
7
+ /**
8
+ * @typedef {Object} AttrMap
9
+ * @property {HTMLDivElement} parent - append newly created element to a parent
10
+ * @property {Array<String>|String} class - class or array of classes to set
11
+ * @property {String} id - set element ID
12
+ * @property {Object} data - dataset values to set
13
+ * @property {String} text - set element text
14
+ * @property {String} html - set element html
15
+ */
16
+
17
+ const unitlessProps = new Set([
18
+ "opacity", "zIndex", "fontWeight", "lineHeight",
19
+ "flex", "flexGrow", "flexShrink", "order"
20
+ ]);
21
+
22
+ /**
23
+ * Returns the associated class event of that event
24
+ * example: for click event it returns new MouseEvent("click")
25
+ * @param {string} ev
26
+ */
27
+ function getEvent(type, options = {}) {
28
+ switch (type) {
29
+ // Mouse Events
30
+ case 'click':
31
+ case 'dblclick':
32
+ case 'mousedown':
33
+ case 'mouseup':
34
+ case 'mousemove':
35
+ case 'mouseenter':
36
+ case 'mouseleave':
37
+ case 'mouseover':
38
+ case 'mouseout':
39
+ return new MouseEvent(type, options);
40
+
41
+ // Pointer Events
42
+ case 'pointerdown':
43
+ case 'pointerup':
44
+ case 'pointermove':
45
+ case 'pointerenter':
46
+ case 'pointerleave':
47
+ case 'pointerover':
48
+ case 'pointerout':
49
+ return new PointerEvent(type, options);
50
+
51
+ // Keyboard Events
52
+ case 'keydown':
53
+ case 'keyup':
54
+ case 'keypress':
55
+ return new KeyboardEvent(type, options);
56
+
57
+ // Focus Events
58
+ case 'focus':
59
+ case 'blur':
60
+ case 'focusin':
61
+ case 'focusout':
62
+ return new FocusEvent(type, options);
63
+
64
+ // Input & Form Events
65
+ case 'input':
66
+ case 'change':
67
+ case 'submit':
68
+ case 'reset':
69
+ return new Event(type, options);
70
+
71
+ // Wheel
72
+ case 'wheel':
73
+ return new WheelEvent(type, options);
74
+
75
+ // Clipboard
76
+ case 'copy':
77
+ case 'cut':
78
+ case 'paste':
79
+ return new ClipboardEvent(type, options);
80
+
81
+ // UI
82
+ case 'scroll':
83
+ case 'resize':
84
+ return new UIEvent(type, options);
85
+
86
+ // Default: fallback to generic Event
87
+ default:
88
+ return new CustomEvent(type, options);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * @typedef {EyeElement & {
94
+ * refresh: (attrs: AttrMap) => ModelEyeElement
95
+ * }} ModelEyeElement eye element definition for model elements
96
+ */
97
+
98
+ const events = [
99
+ // Mouse Events
100
+ "click",
101
+ "dblclick",
102
+ "mousedown",
103
+ "mouseup",
104
+ "mousemove",
105
+ "mouseenter",
106
+ "mouseleave",
107
+ "mouseover",
108
+ "mouseout",
109
+ "contextmenu",
110
+
111
+ // Keyboard Events
112
+ "keydown",
113
+ "keypress", // Deprecated
114
+ "keyup",
115
+
116
+ // Focus Events
117
+ "focus",
118
+ "blur",
119
+ "focusin",
120
+ "focusout",
121
+
122
+ // Form Events
123
+ "submit",
124
+ "change",
125
+ "input",
126
+ "reset",
127
+ "select",
128
+
129
+ // Touch Events (for mobile)
130
+ "touchstart",
131
+ "touchend",
132
+ "touchmove",
133
+ "touchcancel",
134
+
135
+ // Pointer Events
136
+ "pointerdown",
137
+ "pointerup",
138
+ "pointermove",
139
+ "pointerenter",
140
+ "pointerleave",
141
+ "pointercancel",
142
+
143
+ // Drag and Drop Events
144
+ "dragstart",
145
+ "dragend",
146
+ "dragenter",
147
+ "dragover",
148
+ "dragleave",
149
+ "drop",
150
+
151
+ // Window/Document Events
152
+ "resize",
153
+ "scroll",
154
+ "load",
155
+ "beforeunload",
156
+ "unload",
157
+
158
+ // Media Events
159
+ "play",
160
+ "pause",
161
+ "ended",
162
+ "volumechange",
163
+ "timeupdate",
164
+
165
+ // Clipboard Events
166
+ "copy",
167
+ "cut",
168
+ "paste",
169
+
170
+ // Animation and Transition Events
171
+ "animationstart",
172
+ "animationend",
173
+ "animationiteration",
174
+ "transitionstart",
175
+ "transitionend",
176
+
177
+ // Mutation Events
178
+ "DOMSubtreeModified",
179
+ "DOMNodeInserted",
180
+ "DOMNodeRemoved",
181
+
182
+ // Other Events
183
+ "error",
184
+ "hashchange",
185
+ "popstate",
186
+ ];
187
+
188
+ const htmlElements = [
189
+ // Metadata
190
+ "<base>",
191
+ "<head>",
192
+ "<link>",
193
+ "<meta>",
194
+ "<style>",
195
+ "<title>",
196
+
197
+ // Sections
198
+ "<body>",
199
+ "<address>",
200
+ "<article>",
201
+ "<aside>",
202
+ "<footer>",
203
+ "<header>",
204
+ "<h1>",
205
+ "<h2>",
206
+ "<h3>",
207
+ "<h4>",
208
+ "<h5>",
209
+ "<h6>",
210
+ "<main>",
211
+ "<nav>",
212
+ "<section>",
213
+
214
+ // Text content
215
+ "<blockquote>",
216
+ "<dd>",
217
+ "<div>",
218
+ "<dl>",
219
+ "<dt>",
220
+ "<figcaption>",
221
+ "<figure>",
222
+ "<hr>",
223
+ "<li>",
224
+ "<ol>",
225
+ "<p>",
226
+ "<pre>",
227
+ "<ul>",
228
+
229
+ // Inline text semantics
230
+ "<a>",
231
+ "<abbr>",
232
+ "<b>",
233
+ "<bdi>",
234
+ "<bdo>",
235
+ "<br>",
236
+ "<cite>",
237
+ "<code>",
238
+ "<data>",
239
+ "<dfn>",
240
+ "<em>",
241
+ "<i>",
242
+ "<kbd>",
243
+ "<mark>",
244
+ "<q>",
245
+ "<rp>",
246
+ "<rt>",
247
+ "<ruby>",
248
+ "<s>",
249
+ "<samp>",
250
+ "<small>",
251
+ "<span>",
252
+ "<strong>",
253
+ "<sub>",
254
+ "<sup>",
255
+ "<time>",
256
+ "<u>",
257
+ "<var>",
258
+ "<wbr>",
259
+
260
+ // Image and multimedia
261
+ "<area>",
262
+ "<audio>",
263
+ "<img>",
264
+ "<map>",
265
+ "<track>",
266
+ "<video>",
267
+
268
+ // Embedded content
269
+ "<embed>",
270
+ "<iframe>",
271
+ "<object>",
272
+ "<picture>",
273
+ "<portal>",
274
+ "<source>",
275
+
276
+ // Scripting
277
+ "<canvas>",
278
+ "<noscript>",
279
+ "<script>",
280
+
281
+ // Demarcating edits
282
+ "<del>",
283
+ "<ins>",
284
+
285
+ // Table content
286
+ "<caption>",
287
+ "<col>",
288
+ "<colgroup>",
289
+ "<table>",
290
+ "<tbody>",
291
+ "<td>",
292
+ "<tfoot>",
293
+ "<th>",
294
+ "<thead>",
295
+ "<tr>",
296
+
297
+ // Forms
298
+ "<button>",
299
+ "<datalist>",
300
+ "<fieldset>",
301
+ "<form>",
302
+ "<input>",
303
+ "<label>",
304
+ "<legend>",
305
+ "<meter>",
306
+ "<optgroup>",
307
+ "<option>",
308
+ "<output>",
309
+ "<progress>",
310
+ "<select>",
311
+ "<textarea>",
312
+
313
+ // Interactive elements
314
+ "<details>",
315
+ "<dialog>",
316
+ "<summary>",
317
+
318
+ // Web components / scripting base
319
+ "<slot>",
320
+ "<template>",
321
+ ];
322
+
323
+ const quickexec = ["click", "focus", "blur", "select", "submit", "reset"];
324
+
325
+ function flat(word) {
326
+ let n = "";
327
+ for (let i = 0; i < word.length; i++) {
328
+ const t = word[i];
329
+ if (t === t.toUpperCase() && t !== t.toLowerCase()) n += "-" + t;
330
+ else n += t;
331
+ }
332
+ return n.toLowerCase();
333
+ }
334
+
335
+ const localdata = new WeakMap();
336
+
337
+ /**
338
+ * cmcl stands for Create Model Children Layers, recursively creates model layers one by one
339
+ * @param {EyeElement} parent
340
+ * @param {Object} layer
341
+ * @returns {Array<{name: string,set: (parent: EyeElement, value: String) =>}>}
342
+ */
343
+ function cmcl(parent, layer) {
344
+ let obj = [];
345
+ for (const key in layer) {
346
+ const subcontent = layer[key];
347
+ const [def, _set] = key.split(":");
348
+ const [tagName, ...cls] = def.split(".");
349
+ let [_set_name = null, _set_default = null] = (_set || "")
350
+ .split("-")
351
+ .map((a) => a.trim());
352
+
353
+ let elm = e(tagName.trim(), {
354
+ class: cls,
355
+ parent,
356
+ data: _set ? { value: _set_name } : undefined,
357
+ });
358
+
359
+ if (_set && _set_name) {
360
+ obj.push({
361
+ name: _set_name,
362
+ set(parent, value) {
363
+ let elm = parent.find(`[data-value="${_set_name}"]`);
364
+ if (elm) elm[0].textContent = value ?? _set_default;
365
+ }
366
+ });
367
+ }
368
+
369
+ // recursive
370
+ if (
371
+ subcontent &&
372
+ typeof subcontent === "object" &&
373
+ !(subcontent instanceof Array)
374
+ )
375
+ obj = obj.concat(cmcl(elm, subcontent));
376
+ }
377
+ return obj;
378
+ }
379
+
380
+ let delegationEvents = ["click", "dblclick", "submit", "input", "change", "keydown", "keyup", "keypress", "focusin", "focusout", "mousedown", "mouseup", "mousemove", "contextmenu", "auxclick", "wheel", "mouseover", "mouseout", "pointerdown", "pointerup", "pointermove", "pointerover", "pointerout", "gotpointercapture", "lostpointercapture", "pointercancel"];
381
+ let normalSetterGetter = (action, v, elm) => v;
382
+
383
+ /**
384
+ * Eye wrapper offers a subset of functions that ease DOM minipulation! Power of JQuery with
385
+ * some a modern design and a bunch of new functions.
386
+ * @author Yousef Neji
387
+ */
388
+ class EyeElement {
389
+ /**
390
+ * Raw html element
391
+ * @type {Array<HTMLElement>}
392
+ */
393
+ #raw = null;
394
+
395
+ /**
396
+ * Used to store delegated events listeners
397
+ * @type {Map<String,Set<{callback, target: string}>>}
398
+ */
399
+ #dlgListeners = new Map();
400
+
401
+ /**
402
+ * Custom way or modifier that redefine the way you set/get
403
+ * this element `textContent` or `value`:
404
+ * - access this feature from `.redefine` method.
405
+ */
406
+ #customSet = {
407
+ value: normalSetterGetter,
408
+ text: normalSetterGetter
409
+ };
410
+
411
+ /**
412
+ * Called internally to initiate the main EyeElement functionalities
413
+ * @method EyeElement#init
414
+ * @param {string|HTMLElement} selector
415
+ * @param {AttrMap} attrs
416
+ * @param {Object} css
417
+ * @returns {EyeElement}
418
+ */
419
+ constructor(selector, attrs, css) {
420
+ let _this = this;
421
+ if (selector instanceof HTMLElement) {
422
+ this.#raw = [selector];
423
+ } else if (htmlElements.includes(selector)) {
424
+ // creating a new element
425
+ this.#raw = [document.createElement(selector.substring(1, selector.length - 1))];
426
+ } else if (Array.isArray(selector)) {
427
+ this.#raw = [];
428
+ selector.forEach((a) => {
429
+ if (a instanceof EyeElement) _this.#raw = _this.#raw.concat(...a.raw);
430
+ else if (a instanceof HTMLElement) _this.#raw.push(a);
431
+ });
432
+ } else {
433
+ // selecting
434
+ let s = selector.slice(-1) === "!";
435
+ this.#raw = [...document.querySelectorAll(s ? selector.slice(0, -1) : selector)];
436
+ }
437
+
438
+ /**
439
+ * Handler used to integrate delegation concept/algorithme
440
+ * @param {Event} ev
441
+ */
442
+ function handler(ev) {
443
+ let name = ev.type,
444
+ listeners = _this.#dlgListeners,
445
+ _etarget = ev.target,
446
+ me = _this; // refers to the element being listening to the event
447
+
448
+ if (listeners.has(name)) {
449
+ let cbs = listeners.get(name);
450
+ cbs?.forEach(({ callback, target }) => {
451
+ if (_etarget.closest(target)) {
452
+ const targetElement = e(_etarget.closest(target));
453
+ // we hitting the target
454
+ callback.call(_etarget.closest(target), ev, targetElement, me);
455
+ }
456
+ });
457
+ }
458
+ }
459
+
460
+ this.each((elm, idx) => {
461
+ let parentElm = null;
462
+ if (attrs)
463
+ for (const key in attrs) {
464
+ const value = attrs[key];
465
+ if (key == "class")
466
+ elm.classList.add.apply(
467
+ elm.classList,
468
+ (value instanceof Array ? value : value.split(" ")).filter(a => a != "")
469
+ );
470
+ else if (key == "text") elm.textContent = value;
471
+ else if (key == "html") elm.innerHTML = value;
472
+ else if (key == "data") for (const k in value) elm.dataset[k] = value[k];
473
+ else if (key == "parent") parentElm = value;
474
+ else if (key in elm) elm[key] = value;
475
+ else if (key[0] != "_") elm.setAttribute(key, value); // we must ignore _ started keys 'cause they are used by models
476
+ }
477
+ if (css)
478
+ for (const key in css)
479
+ if (key.indexOf("-") != -1) elm.style[key] = css[key];
480
+ else elm.style[flat(key)] = css[key];
481
+ if (parentElm instanceof EyeElement || parentElm instanceof HTMLElement) parentElm.append(elm);
482
+
483
+ // creating the delegation handling model
484
+ delegationEvents.forEach(ev => {
485
+ elm.addEventListener(ev, handler);
486
+ });
487
+ });
488
+
489
+ // creating/initiating events functions
490
+ events.forEach(ev => {
491
+ _this[ev] = function (cb) {
492
+ if (cb) {
493
+ if (typeof cb == "function") _this.on(ev, cb);
494
+ } else if (quickexec.includes(ev))
495
+ _this.each(elm => elm[ev]());
496
+ else _this.trigger(ev);
497
+ return _this;
498
+ };
499
+ });
500
+
501
+ return this;
502
+ }
503
+
504
+ /**
505
+ * Length of current selection
506
+ * @type {Number}
507
+ */
508
+ get length() {
509
+ return this.#raw.length;
510
+ }
511
+
512
+ /**
513
+ * Raw html element
514
+ * @type {HTMLElement}
515
+ */
516
+ get raw() {
517
+ return this.#raw;
518
+ }
519
+
520
+ /**
521
+ * Run(loop) through selected NodeList, or run a single call for one single element
522
+ * @method EyeElement#each
523
+ * @param {(elm: HTMLElement, index: number, current: EyeElement)=>} cb
524
+ * @returns {EyeElement}
525
+ */
526
+ each(cb) {
527
+ for (let i = 0; i < this.#raw.length; i++) {
528
+ const elm = this.#raw[i];
529
+ let exit = cb(elm, i, this);
530
+ if (exit === false) break;
531
+ }
532
+ return this;
533
+ }
534
+ /**
535
+ * Run(loop) through selected NodeList's children.
536
+ * @param {(elm: HTMLElement, index: number, parent: HTMLElement, Object: EyeElement)=>{}} cb
537
+ * @returns {EyeElement}
538
+ */
539
+ eachChild(cb) {
540
+ let _this = this;
541
+ return this.each(function (elm, i, parent) {
542
+ let exit = true;
543
+ for (let j = 0; j < elm.children.length; j++) {
544
+ exit = cb(elm.children[j], j, elm, _this);
545
+ if (exit === false) break;
546
+ }
547
+ if (exit === false) return;
548
+ })
549
+ }
550
+ /**
551
+ * Set or get element html
552
+ * @method EyeElement#html
553
+ * @param {string} [html]
554
+ * @returns {EyeElement|string}
555
+ */
556
+ html(html) {
557
+ let out = "";
558
+ this.each((elm, idx) => {
559
+ if (html === undefined) return out = elm.innerHTML;// getting the first one and exiting
560
+ elm.innerHTML = html;
561
+ });
562
+ return html == undefined ? out : this;
563
+ }
564
+ /**
565
+ * Set or get element text
566
+ * @method EyeElement#text
567
+ * @param {string} [text]
568
+ * @returns {EyeElement|string}
569
+ */
570
+ text(text) {
571
+ let out = "";
572
+ this.each((elm, idx) => {
573
+ if (text === undefined) return out = this.#customSet.text("get", elm.textContent, elm);
574
+ elm.textContent = this.#customSet.text("set", text, elm);
575
+ });
576
+ return text == undefined ? out : this;
577
+ }
578
+ /**
579
+ * Set or get element's data values
580
+ * @method EyeElement#data
581
+ * @param {string} key
582
+ * @param {*} [value]
583
+ * @returns {EyeElement|string}
584
+ */
585
+ data(key, value) {
586
+ if (!localdata.has(this)) localdata.set(this, {});
587
+ if (key) {
588
+ if (value != undefined) localdata.get(this)[key] = value;
589
+ else return localdata.get(this)[key];
590
+ }
591
+ return this;
592
+ }
593
+
594
+ /**
595
+ * Set or get an attribute value
596
+ * @method EyeElement#attr
597
+ * @param {string} name
598
+ * @param {*} [value]
599
+ * @returns {EyeElement|string}
600
+ */
601
+ attr(name, value) {
602
+ let out = null;
603
+ this.each((elm, idx) => {
604
+ if (name.indexOf("data-") === 0) {
605
+ let [key, val] = name.split("-").map((a) => a.trim());
606
+ // modify data
607
+ if (value == undefined) return out = elm.dataset[val];
608
+ elm.dataset[val] = value;
609
+ } else {
610
+ if (name in elm) {
611
+ if (value == undefined) return out = elm[name];
612
+ elm[name] = value;
613
+ } else if (name[0] != "_") {
614
+ if (value == undefined) return out = elm.getAttribute(name)
615
+ elm.setAttribute(name, value);
616
+ }
617
+ }
618
+ });
619
+ return value == undefined ? out : this;
620
+ }
621
+ /**
622
+ * Removing attribute of this element
623
+ * @type {string} attrName
624
+ * @returns {EyeElement}
625
+ */
626
+ rAttr(attrName) {
627
+ if (attrName) {
628
+ this.each((elm, idx) => {
629
+ elm.removeAttribute(attrName);
630
+ });
631
+ }
632
+ return this;
633
+ }
634
+ /**
635
+ * Super fancy class function that allows to modify class with different methods in one as follow:
636
+ * - `"classname"`: add classname to the element.
637
+ * - `"-classname"`: remove classname from class list.
638
+ * - `"%classname"`: toggle classname existing.
639
+ * - `"?classname"`: check classname existing in class list.
640
+ * - `"classnameA/classnameB"`: replace classnameA by classnameB
641
+ * @method EyeElement#class
642
+ * @param {string} actions
643
+ * @returns {EyeElement|string}
644
+ */
645
+ class(actions) {
646
+ let out = undefined;
647
+ this.each((elm, idx) => {
648
+ if (typeof actions === "number") return out = elm.classList.item(actions);
649
+
650
+ actions.split(" ").forEach((action) => {
651
+ if (action[0] == "-") {
652
+ elm.classList.remove(action.substring(1, action.length));
653
+ } else if (action[0] == "%") {
654
+ elm.classList.toggle(action.substring(1, action.length));
655
+ } else if (action[0] == "?") {
656
+ out = elm.classList.contains(action.substring(1, action.length));
657
+ } else if (action.indexOf("/") != -1) {
658
+ let [v1, v2] = action.split("/");
659
+ elm.classList.replace(v1, v2);
660
+ } else {
661
+ elm.classList.add(action);
662
+ }
663
+ });
664
+ if (out) return;
665
+ });
666
+
667
+ return out != undefined ? out : this;
668
+ }
669
+ /**
670
+ * Show/display the element
671
+ * @method EyeElement#show
672
+ * @param {string} cls
673
+ * @returns {EyeElement}
674
+ */
675
+ show(cls) {
676
+ this.each((elm, idx) => {
677
+ elm.style.display = cls ?? "inline-block";
678
+ });
679
+ return this;
680
+ }
681
+ /**
682
+ * Hide the element
683
+ * @method EyeElement#hide
684
+ * @param {boolean} opacity whether to hide by making invisible?
685
+ * @returns {EyeElement}
686
+ */
687
+ hide(opacity) {
688
+ this.each((elm, idx) => {
689
+ if (opacity) elm.style.opacity = 0;
690
+ else elm.style.display = "none";
691
+ });
692
+ return this;
693
+ }
694
+ /**
695
+ * Append one or more elements to the current element, only effect the first element in the selected list
696
+ * @method EyeElement#append
697
+ * @param {HTMLElement|Array<Node|EyeElement>} elm
698
+ * @param {"next" | "after" | "previous" | "before" | "first" | "afterbegin" | "last" | "beforeend"} [pos] [optional]
699
+ * @returns {EyeElement}
700
+ */
701
+ append(elm, pos) {
702
+ (Array.isArray(elm) ? elm : [elm]).forEach(item => {
703
+ let nodes = [];
704
+ if (item instanceof EyeElement) nodes = [...item.raw];
705
+ else if (item instanceof HTMLElement) nodes = [item];
706
+
707
+ nodes.forEach(node => {
708
+ switch (pos) {
709
+ case "next":
710
+ case "after":
711
+ this.#raw[0].after(node);
712
+ break;
713
+ case "previous":
714
+ case "before":
715
+ this.#raw[0].before(node);
716
+ break;
717
+ case "afterbegin":
718
+ case "first":
719
+ this.#raw[0].prepend(node);
720
+ break;
721
+ case "beforeend":
722
+ case "last":
723
+ default:
724
+ this.#raw[0].append(node);
725
+ }
726
+ });
727
+ });
728
+ return this;
729
+ }
730
+ /**
731
+ * Insert element after this one, or return the one lies there
732
+ * @method EyeElement#after
733
+ * @param {EyeElement|HTMLElement|AttrMap} elm
734
+ * @returns {EyeElement|HTMLElement}
735
+ */
736
+ after(elm) {
737
+ if (elm instanceof HTMLElement || elm instanceof EyeElement || elm instanceof Node)
738
+ this.#raw[0].after(elm instanceof EyeElement ? elm.raw[0] : elm);
739
+ else return this.#raw[0].nextElementSibling ? e(this.#raw[0].nextElementSibling, elm) : null;
740
+ return this;
741
+ }
742
+ /**
743
+ * Insert element before this one, or return the one lies there
744
+ * @method EyeElement#before
745
+ * @param {EyeElement|HTMLElement|AttrMap} elm
746
+ * @returns {EyeElement|HTMLElement}
747
+ */
748
+ before(elm) {
749
+ if (elm instanceof HTMLElement || elm instanceof EyeElement || elm instanceof Node)
750
+ this.#raw[0].before(elm instanceof EyeElement ? elm.raw[0] : elm);
751
+ else return this.#raw[0].previousElementSibling ? e(this.#raw[0].previousElementSibling, elm) : null
752
+ return this;
753
+ }
754
+ /**
755
+ * Replace current element with the new element, or multiple elements with multiple selected elements
756
+ * @method EyeElement#replaceWith
757
+ * @param {...HTMLElement|EyeElement} elms
758
+ * @param {string} [pos] [optional]
759
+ * @returns {EyeElement}
760
+ */
761
+ replaceWith(...elms) {
762
+ let nodes = [];
763
+ (Array.isArray(elms) ? elms : [elms]).forEach(item => {
764
+ if (item instanceof EyeElement) nodes = nodes.concat(...item.raw);
765
+ else if (item instanceof HTMLElement) nodes.push(item);
766
+ });
767
+ this.#raw[0].replaceWith(...nodes);
768
+ return this;
769
+ }
770
+ /**
771
+ * Get current element parent or append it to one
772
+ * @method EyeElement#parent
773
+ * @param {HTMLElement|EyeElement} parent
774
+ * @param {boolean} clone [true] append clones of the elements
775
+ * @returns {EyeElement}
776
+ */
777
+ parent(parent, clone) {
778
+ if (parent) {
779
+ if (!(parent instanceof HTMLElement) && !(parent instanceof EyeElement))
780
+ throw new Error(
781
+ "[EyeJS] Unable to append current element to parent because it's not HTMLElement"
782
+ );
783
+ this.each(elm => {
784
+ parent.append(clone === true ? elm.cloneNode(true) : elm);
785
+ });
786
+ return this;
787
+ }
788
+ return e(this.#raw[0].parentElement);
789
+ }
790
+ /**
791
+ * Returns whether current node is the same/equal (depending on `check`) as the passed node or not
792
+ * @method EyeElement#is
793
+ * @param {HTMLElement|EyeElement} node
794
+ * @param {"connected" | "same" | "equal"} [check] check type `same`, `equal`
795
+ * @returns {boolean}
796
+ */
797
+ is(node, check) {
798
+ node = node instanceof EyeElement ? node.#raw[0] : node;
799
+ if (node === "connected") return this.#raw[0].isConnected;
800
+ switch (check) {
801
+ case "same":
802
+ return this.#raw[0].isSameNode(node);
803
+ case "equal":
804
+ return this.#raw[0].isEqualNode(node);
805
+ default:
806
+ console.error(
807
+ `[EyeJS] Unknown check "${check}", possible values are ["same","equal","connected"]`
808
+ );
809
+ return false;
810
+ }
811
+ }
812
+ /**
813
+ * Cheap check for previous/next/child elements by selector
814
+ * @param {"next"|"previous"|string} desc
815
+ * @returns {boolean}
816
+ */
817
+ has(selector) {
818
+ if (typeof selector === "string")
819
+ switch (selector) {
820
+ case "next": return this.#raw[0].nextElementSibling != null
821
+ case "previous": return this.#raw[0].previousElementSibling != null
822
+ default: return this.#raw[0].querySelector(selector) != null
823
+ }
824
+ }
825
+ /**
826
+ * Set or get a css attribute
827
+ * @method EyeElement#css
828
+ * @param {string} attr
829
+ * @param {string|number} [value]
830
+ * @returns {EyeElement|string}
831
+ */
832
+ css(attr, value) {
833
+ if (attr) {
834
+ if (!unitlessProps.has(attr) && typeof value === "number" && value != 0) value = `${value}px`;
835
+ let out = null;
836
+ attr = flat(attr);
837
+ this.each((elm, idx) => {
838
+ if (value === undefined) return out = elm.style[attr];
839
+ elm.style[attr] = value;
840
+ });
841
+ return value == undefined ? out : this;
842
+ } else return console.error(`[EyeJS] missing argument "attr" in function .css`);
843
+ }
844
+ /**
845
+ * Remove current element, or find and remove a sub element of it
846
+ * @method EyeElement#remove
847
+ * @param {string?} selector
848
+ * @param {()=>void?} cb optionally execute something before removing the selected element
849
+ * @returns {EyeElement}
850
+ */
851
+ remove(selector, cb) {
852
+ this.each((elm, idx) => {
853
+ if (typeof selector === "string")
854
+ elm.querySelectorAll(selector).forEach(item => {
855
+ if (typeof cb === "function") cb(e(item));
856
+ item.remove();
857
+ });
858
+ else elm.remove();
859
+ });
860
+ return this;
861
+ }
862
+ /**
863
+ * Attach an listener/handler to specific event or events
864
+ * @method EyeElement#on
865
+ * @param {string} ev may contain multiple events separated by " "(space)
866
+ * @param {()=>} cb
867
+ * @param {{ passive: boolean, capture: boolean, once: boolean, signal: AbortSignal }} opts
868
+ * @returns {EyeElement|void}
869
+ */
870
+ on(ev, cb, opts) {
871
+ if (typeof cb !== "function")
872
+ return console.error(
873
+ "[EyeJS] .on method is missing the actuall callback `cb` or not of type function"
874
+ );
875
+
876
+ ev.split(" ")
877
+ .forEach(evt => {
878
+ this.each((elm) => {
879
+ elm.addEventListener(evt, cb, opts);
880
+ });
881
+ });
882
+
883
+ return this;
884
+ }
885
+ /**
886
+ * Attach a delegatable event listener to the subelement of this one
887
+ * @param {string} ev
888
+ * @param {string} target
889
+ * @param {(ev: Event, target: EyeElement, self: EyeElement)=>} cb
890
+ */
891
+ delegate(ev, target, cb) {
892
+ let outsider = null;
893
+ ev.split(" ")
894
+ .forEach(evt => {
895
+ if (!delegationEvents.includes(evt))
896
+ return outsider = evt; // outsider events
897
+
898
+ if (!this.#dlgListeners.has(evt))
899
+ this.#dlgListeners.set(evt, new Set());
900
+ this.#dlgListeners.get(evt).add({ callback: cb, target });
901
+ });
902
+
903
+ if (outsider !== null)
904
+ return console.warn(`[EyeJS] trying to use delegation with an inappropriate event "${outsider}"`);
905
+ }
906
+ /**
907
+ * Attach a single time handler, that get auto-removed after first call
908
+ * @param {string} ev
909
+ * @param {()=>} cb
910
+ */
911
+ once(ev, cb) {
912
+ this.on(ev, cb, { once: true }); // simply
913
+ }
914
+ /**
915
+ * Remove event listener of a specific event
916
+ * @method EyeElement#off
917
+ * @param {string} ev
918
+ * @param {function} cb
919
+ * @returns {EyeElement|void}
920
+ */
921
+ off(ev, cb) {
922
+ let _this = this,
923
+ listeners = _this.#dlgListeners;
924
+ if (typeof cb != "function")
925
+ return console.error(
926
+ "[EyeJS] .off method is missing the actuall callback `cb` or not of type function"
927
+ );
928
+ ev = ev.split(" ");
929
+
930
+ this.each((elm, idx) => {
931
+ ev.forEach(evt => elm.removeEventListener(evt, cb));
932
+ });
933
+ // now delegated events
934
+ ev.forEach(evt => {
935
+ if (listeners.has(evt)) {
936
+ let set = listeners.get(evt);
937
+ for (const item of set) {
938
+ if (cb === item.callback) {
939
+ // found it & remove it
940
+ set.delete(item);
941
+ }
942
+ }
943
+ }
944
+ });
945
+ }
946
+ /**
947
+ * Trigger specific event for this element
948
+ * @method EyeElement#trigger
949
+ * @param {string} ev
950
+ * @returns {EyeElement}
951
+ */
952
+ trigger(ev) {
953
+ this.each((elm, idx) => {
954
+ elm.dispatchEvent(ev instanceof Event ? ev : getEvent(ev));
955
+ });
956
+ return this;
957
+ }
958
+ /**
959
+ * Find one or multiple child elements by `selector`,
960
+ * and optionally execute a callback on the fly!
961
+ * @method EyeElement#find
962
+ * @param {string} selector
963
+ * @param {(elm: EyeElement, parent: EyeElement) => void} [cb]
964
+ * @returns {Array<HTMLElement>}
965
+ */
966
+ find(selector, cb) {
967
+ let found = [];
968
+ this.each((elm, idx) => {
969
+ elm.querySelectorAll(selector).forEach(res => {
970
+ if (typeof cb === "function") cb(e(res), e(elm));
971
+ found.push(res);
972
+ });
973
+ });
974
+ return found.length == 0 ? null : found;
975
+ }
976
+ /**
977
+ * Returns a clone of current selected element/s
978
+ * @method EyeElement#clone
979
+ * @param {HTMLElement} [parent] optionally append new clone to a parent
980
+ * @returns {Array<EyeElement>}
981
+ */
982
+ clone(parent) {
983
+ let list = [];
984
+ this.each((nd) => {
985
+ list.push(nd.cloneNode(true));
986
+ });
987
+ if (parent instanceof HTMLElement || parent instanceof EyeElement) list.forEach(el => parent.append(el));
988
+ return list;
989
+ }
990
+ /**
991
+ * Compute DOMRect or style declaration of current element
992
+ * @method EyeElement#compute
993
+ * @param {"bounds" | "style"} type
994
+ * @returns {DOMRect|CSSStyleDeclaration}
995
+ */
996
+ compute(type) {
997
+ type = type || "bounds";
998
+ if (type === "bounds")
999
+ return (this.#raw[0]).getBoundingClientRect();
1000
+ else if (type == "style")
1001
+ return getComputedStyle(this.#raw[0])
1002
+ console.error(`[EyeJS] unknown type "${type}" in function .compute, possible values are "bounds" "style"`);
1003
+ }
1004
+
1005
+ /**
1006
+ * Get specific client informations.
1007
+ * @param {"width" | "height" | "left" | "top"} attr
1008
+ * @returns {EyeElement}
1009
+ */
1010
+ client(attr) {
1011
+ if (['width', 'height', 'left', 'top'].includes(attr))
1012
+ return this.#raw[0][`client${attr[0].toUpperCase()}${attr.substring(1, attr.length)}`];
1013
+ else console.error(`[EyeJS] Unknown client* attribute "${attr}" in .client(attr)`);
1014
+ return this;
1015
+ }
1016
+ /**
1017
+ * Activate/disactive different pointer features such as PointerLock, pointerCapture...
1018
+ * @method EyeElement#pointer
1019
+ * @param {"capture" | "lock"} action
1020
+ * @param {boolean} status
1021
+ * @param {string} [pid]
1022
+ * @returns {EyeElement}
1023
+ */
1024
+ pointer(action, status, pid) {
1025
+ this.each((elm, idx) => {
1026
+ if (action === "capture") {
1027
+ if (status === true) elm.setPointerCapture(pid);
1028
+ else elm.releasePointerCapture(pid);
1029
+ } else if (action === "lock") {
1030
+ if (status === true) elm.requestPointerLock();
1031
+ else document.exitPointerLock();
1032
+ }
1033
+ });
1034
+ return this;
1035
+ }
1036
+ /**
1037
+ * Returns the count of children for this element
1038
+ * @type {number}
1039
+ */
1040
+ get childlen() {
1041
+ return this.#raw[0].children.length;
1042
+ }
1043
+ /**
1044
+ * Select a child of this element
1045
+ * @method EyeElement#child
1046
+ * @param {number} index
1047
+ * @returns {EyeElement|null}
1048
+ */
1049
+ child(index) {
1050
+ if (index === undefined) return this.#raw[0].children.length;
1051
+ if (this.#raw[0].children[index]) return e(this.#raw[0].children[index]);
1052
+ return null;
1053
+ }
1054
+ /**
1055
+ * Set/get the value of the current element
1056
+ * @method EyeElement#val
1057
+ * @param {*} value
1058
+ * @returns
1059
+ */
1060
+ val(value) {
1061
+ if (value != undefined) this.each((a) => a.value = this.#customSet.value("set", value, a));
1062
+ else return this.#customSet.value("get", this.#raw[0].value, this.#raw[0]);
1063
+ return this;
1064
+ }
1065
+ /**
1066
+ * Serialize this element to send it over network, returns 3 formats `json`, `url` & `fd`(formData)
1067
+ * @method EyeElement#serialize
1068
+ * @param {{inputs: Array<string>}} opts
1069
+ * @returns {{json: Object, url: String, fd: FormData}}
1070
+ */
1071
+ serialize(opts) {
1072
+ opts = opts || {};
1073
+ let {
1074
+ inputs = ["input", "textarea", "select"],
1075
+ } = opts;
1076
+ let out = {
1077
+ json: {},
1078
+ url: "",
1079
+ fd: new FormData()
1080
+ };
1081
+ this.#raw[0].querySelectorAll(inputs.join(','))
1082
+ .forEach((inp, i) => {
1083
+ let name = inp.name || inp.dataset.name;
1084
+ let value = inp.value || inp.textContent;
1085
+ if (typeof opts[name] === "function") value = opts[name](inp);
1086
+
1087
+ if (inp.type == "file")
1088
+ inp.files.forEach(file => {
1089
+ out.fd.append(name, file);
1090
+ });
1091
+ else {
1092
+ out.json[name] = value;
1093
+ out.fd.append(name, value);
1094
+ }
1095
+ });
1096
+
1097
+ out.url = new URLSearchParams(out.json).toString();
1098
+ return out;
1099
+ }
1100
+ /**
1101
+ * Redefine the way `.text` or `.val` set or get data to and from this element.
1102
+ * @method EyeElement#redefine
1103
+ * @param {"text" | "value"} type
1104
+ * @param {(action: "set" | "get", value: *, elm: EyeElement) => *} process
1105
+ * @returns {EyeElement}
1106
+ */
1107
+ redefine(type, process) {
1108
+ if (["text", "value"].includes(type) && typeof process == "function")
1109
+ this.#customSet[type] = process;
1110
+ return this;
1111
+ }
1112
+ /**
1113
+ * Animate current object
1114
+ * @method EyeElement#animate
1115
+ * @param {Array<Keyframe>} keyframes
1116
+ * @param {KeyframeAnimationOptions} opts
1117
+ * @returns {Array<Animation>|Animation}
1118
+ */
1119
+ animate(keyframes, opts) {
1120
+ /**
1121
+ * @type {Array<Animation>}
1122
+ */
1123
+ let anmts = [];
1124
+ opts.duration = opts.duration || 1000;
1125
+ this.each((elm, i) => {
1126
+ anmts.push(elm.animate(keyframes, opts));
1127
+ });
1128
+ return anmts.length == 1 ? anmts[0] : anmts;
1129
+ }
1130
+
1131
+ /**
1132
+ * Find first element position within parent element
1133
+ * @method EyeElement#position
1134
+ * @returns {number}
1135
+ */
1136
+ position() {
1137
+ let pos = -1;
1138
+ for (let i = 0; i < this.#raw[0].parentNode.children.length; i++) {
1139
+ const child = this.#raw[0].parentNode.children[i];
1140
+ if (this.#raw[0].isSameNode(child)) {
1141
+ pos = i;
1142
+ break;
1143
+ }
1144
+ }
1145
+ return pos;
1146
+ }
1147
+
1148
+ /**
1149
+ * Get drawing context for canvas
1150
+ * @param {"2d"|"webgl"|"webgl2"|"bitmaprenderer"} contextId
1151
+ * @param {*} contextSettings
1152
+ * @returns {EyeElement|CanvasRenderingContext2D}
1153
+ */
1154
+ getctx(contextId, contextSettings) {
1155
+ if (typeof this.#raw[0].getContext === "function")
1156
+ return this.#raw[0].getContext(contextId, contextSettings);
1157
+ return this;
1158
+ }
1159
+ }
1160
+ /**
1161
+ * Creates or select nodes using css selectors, offering a pack of useful functions to use around your code!
1162
+ * @param {String} tag
1163
+ * @param {AttrMap} attrs
1164
+ * @param {Object} css CSS styles to be applied to the element.
1165
+ * @returns {EyeElement}
1166
+ */
1167
+ function e(tag, attrs, css) {
1168
+ if (typeof tag === "string" && tag.indexOf("model:") === 0 || tag === "model") {
1169
+ if (!attrs) return console.error("[EyeJS] Model creation requires parameter 'attr' as prototype, none delivered!");
1170
+
1171
+ tag = tag.split(":");
1172
+ let cls = ["eye-model"];
1173
+ if (tag[1])
1174
+ cls = cls.concat(tag[1].split(" ").filter(a => a != ""));
1175
+ // creating a model
1176
+ let model = e("<div>", {
1177
+ class: cls.join(" "),
1178
+ });
1179
+
1180
+ let sets = cmcl(model, attrs);
1181
+
1182
+ /**
1183
+ * @param {string} attrs
1184
+ * @returns {ModelEyeElement}
1185
+ */
1186
+ return (attrs) => {
1187
+ let copy = e(model.clone(attrs?.parent));
1188
+ // define & attach the refresh function
1189
+ copy.refresh = function (attrs = {}) {
1190
+ let def = attrs.default === false ? false : true;
1191
+ sets.forEach((item) => {
1192
+ if (def === true || (!def && attrs.hasOwnProperty(item.name)))
1193
+ item.set(copy, attrs[item.name]);
1194
+ });
1195
+ return this;
1196
+ };
1197
+ return copy.refresh(attrs);
1198
+ };
1199
+ } else if (tag != null) {
1200
+ let ne = new EyeElement(tag, attrs, css);
1201
+ return ne.length === 0 ? null : ne;
1202
+ }
1203
+ }
1204
+
1205
+ // gloablly exposed
1206
+ window.e = e;
1206
1207
  window.EyeElement = EyeElement;
1207
1208
 
1208
1209
  exports.EyeElement = EyeElement;