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