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