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/src/eye.js CHANGED
@@ -1,1203 +1,1204 @@
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
- export 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
- else false
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 = undefined;
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 out != 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 {()=>} 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
- /**
1156
- * Creates or select nodes using css selectors, offering a pack of useful functions to use around your code!
1157
- * @param {String} tag
1158
- * @param {AttrMap} attrs
1159
- * @param {Object} css CSS styles to be applied to the element.
1160
- * @returns {EyeElement}
1161
- */
1162
- function e(tag, attrs, css) {
1163
- if (typeof tag === "string" && tag.indexOf("model:") === 0 || tag === "model") {
1164
- if (!attrs) return console.error("[EyeJS] Model creation requires parameter 'attr' as prototype, none delivered!");
1165
-
1166
- tag = tag.split(":");
1167
- let cls = ["eye-model"];
1168
- if (tag[1])
1169
- cls = cls.concat(tag[1].split(" ").filter(a => a != ""));
1170
- // creating a model
1171
- let model = e("<div>", {
1172
- class: cls.join(" "),
1173
- });
1174
-
1175
- let sets = cmcl(model, attrs);
1176
-
1177
- /**
1178
- * @param {string} attrs
1179
- * @returns {ModelEyeElement}
1180
- */
1181
- return (attrs) => {
1182
- let copy = e(model.clone(attrs?.parent));
1183
- // define & attach the refresh function
1184
- copy.refresh = function (attrs = {}) {
1185
- let def = attrs.default === false ? false : true;
1186
- sets.forEach((item) => {
1187
- if (def === true || (!def && attrs.hasOwnProperty(item.name)))
1188
- item.set(copy, attrs[item.name]);
1189
- });
1190
- return this;
1191
- };
1192
- return copy.refresh(attrs);
1193
- };
1194
- } else if (tag != null) {
1195
- let ne = new EyeElement(tag, attrs, css);
1196
- return ne.length === 0 ? null : ne;
1197
- }
1198
- }
1199
-
1200
- // gloablly exposed
1201
- window.e = e;
1202
- window.EyeElement = EyeElement;
1203
- export default 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
+ export 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
+ else false
819
+ }
820
+ /**
821
+ * Set or get a css attribute
822
+ * @method EyeElement#css
823
+ * @param {string} attr
824
+ * @param {string|number} [value]
825
+ * @returns {EyeElement|string}
826
+ */
827
+ css(attr, value) {
828
+ if (attr) {
829
+ if (!unitlessProps.has(attr) && typeof value === "number" && value != 0) value = `${value}px`;
830
+ let out = null;
831
+ attr = flat(attr);
832
+ this.each((elm, idx) => {
833
+ if (value === undefined) return out = elm.style[attr];
834
+ elm.style[attr] = value;
835
+ })
836
+ return value == undefined ? out : this;
837
+ } else return console.error(`[EyeJS] missing argument "attr" in function .css`);
838
+ }
839
+ /**
840
+ * Remove current element, or find and remove a sub element of it
841
+ * @method EyeElement#remove
842
+ * @param {string?} selector
843
+ * @param {()=>void?} cb optionally execute something before removing the selected element
844
+ * @returns {EyeElement}
845
+ */
846
+ remove(selector, cb) {
847
+ this.each((elm, idx) => {
848
+ if (typeof selector === "string")
849
+ elm.querySelectorAll(selector).forEach(item => {
850
+ if (typeof cb === "function") cb(e(item));
851
+ item.remove();
852
+ });
853
+ else elm.remove();
854
+ })
855
+ return this;
856
+ }
857
+ /**
858
+ * Attach an listener/handler to specific event or events
859
+ * @method EyeElement#on
860
+ * @param {string} ev may contain multiple events separated by " "(space)
861
+ * @param {()=>} cb
862
+ * @param {{ passive: boolean, capture: boolean, once: boolean, signal: AbortSignal }} opts
863
+ * @returns {EyeElement|void}
864
+ */
865
+ on(ev, cb, opts) {
866
+ if (typeof cb !== "function")
867
+ return console.error(
868
+ "[EyeJS] .on method is missing the actuall callback `cb` or not of type function"
869
+ );
870
+
871
+ ev.split(" ")
872
+ .forEach(evt => {
873
+ this.each((elm) => {
874
+ elm.addEventListener(evt, cb, opts);
875
+ })
876
+ })
877
+
878
+ return this;
879
+ }
880
+ /**
881
+ * Attach a delegatable event listener to the subelement of this one
882
+ * @param {string} ev
883
+ * @param {string} target
884
+ * @param {(ev: Event, target: EyeElement, self: EyeElement)=>} cb
885
+ */
886
+ delegate(ev, target, cb) {
887
+ let outsider = null;
888
+ ev.split(" ")
889
+ .forEach(evt => {
890
+ if (!delegationEvents.includes(evt))
891
+ return outsider = evt; // outsider events
892
+
893
+ if (!this.#dlgListeners.has(evt))
894
+ this.#dlgListeners.set(evt, new Set());
895
+ this.#dlgListeners.get(evt).add({ callback: cb, target });
896
+ })
897
+
898
+ if (outsider !== null)
899
+ return console.warn(`[EyeJS] trying to use delegation with an inappropriate event "${outsider}"`);
900
+ }
901
+ /**
902
+ * Attach a single time handler, that get auto-removed after first call
903
+ * @param {string} ev
904
+ * @param {()=>} cb
905
+ */
906
+ once(ev, cb) {
907
+ this.on(ev, cb, { once: true }); // simply
908
+ }
909
+ /**
910
+ * Remove event listener of a specific event
911
+ * @method EyeElement#off
912
+ * @param {string} ev
913
+ * @param {function} cb
914
+ * @returns {EyeElement|void}
915
+ */
916
+ off(ev, cb) {
917
+ let _this = this,
918
+ listeners = _this.#dlgListeners;
919
+ if (typeof cb != "function")
920
+ return console.error(
921
+ "[EyeJS] .off method is missing the actuall callback `cb` or not of type function"
922
+ );
923
+ ev = ev.split(" ");
924
+
925
+ this.each((elm, idx) => {
926
+ ev.forEach(evt => elm.removeEventListener(evt, cb));
927
+ })
928
+ // now delegated events
929
+ ev.forEach(evt => {
930
+ if (listeners.has(evt)) {
931
+ let set = listeners.get(evt);
932
+ for (const item of set) {
933
+ if (cb === item.callback) {
934
+ // found it & remove it
935
+ set.delete(item);
936
+ }
937
+ }
938
+ }
939
+ })
940
+ }
941
+ /**
942
+ * Trigger specific event for this element
943
+ * @method EyeElement#trigger
944
+ * @param {string} ev
945
+ * @returns {EyeElement}
946
+ */
947
+ trigger(ev) {
948
+ this.each((elm, idx) => {
949
+ elm.dispatchEvent(ev instanceof Event ? ev : getEvent(ev));
950
+ })
951
+ return this;
952
+ }
953
+ /**
954
+ * Find one or multiple child elements by `selector`,
955
+ * and optionally execute a callback on the fly!
956
+ * @method EyeElement#find
957
+ * @param {string} selector
958
+ * @param {(elm: EyeElement, parent: EyeElement) => void} [cb]
959
+ * @returns {Array<HTMLElement>}
960
+ */
961
+ find(selector, cb) {
962
+ let found = [];
963
+ this.each((elm, idx) => {
964
+ elm.querySelectorAll(selector).forEach(res => {
965
+ if (typeof cb === "function") cb(e(res), e(elm))
966
+ found.push(res)
967
+ });
968
+ })
969
+ return found.length == 0 ? null : found;
970
+ }
971
+ /**
972
+ * Returns a clone of current selected element/s
973
+ * @method EyeElement#clone
974
+ * @param {HTMLElement} [parent] optionally append new clone to a parent
975
+ * @returns {Array<EyeElement>}
976
+ */
977
+ clone(parent) {
978
+ let list = [];
979
+ this.each((nd) => {
980
+ list.push(nd.cloneNode(true));
981
+ })
982
+ if (parent instanceof HTMLElement || parent instanceof EyeElement) list.forEach(el => parent.append(el));
983
+ return list;
984
+ }
985
+ /**
986
+ * Compute DOMRect or style declaration of current element
987
+ * @method EyeElement#compute
988
+ * @param {"bounds" | "style"} type
989
+ * @returns {DOMRect|CSSStyleDeclaration}
990
+ */
991
+ compute(type) {
992
+ type = type || "bounds";
993
+ if (type === "bounds")
994
+ return (this.#raw[0]).getBoundingClientRect();
995
+ else if (type == "style")
996
+ return getComputedStyle(this.#raw[0])
997
+ console.error(`[EyeJS] unknown type "${type}" in function .compute, possible values are "bounds" "style"`);
998
+ }
999
+
1000
+ /**
1001
+ * Get specific client informations.
1002
+ * @param {"width" | "height" | "left" | "top"} attr
1003
+ * @returns {EyeElement}
1004
+ */
1005
+ client(attr) {
1006
+ if (['width', 'height', 'left', 'top'].includes(attr))
1007
+ return this.#raw[0][`client${attr[0].toUpperCase()}${attr.substring(1, attr.length)}`];
1008
+ else console.error(`[EyeJS] Unknown client* attribute "${attr}" in .client(attr)`);
1009
+ return this;
1010
+ }
1011
+ /**
1012
+ * Activate/disactive different pointer features such as PointerLock, pointerCapture...
1013
+ * @method EyeElement#pointer
1014
+ * @param {"capture" | "lock"} action
1015
+ * @param {boolean} status
1016
+ * @param {string} [pid]
1017
+ * @returns {EyeElement}
1018
+ */
1019
+ pointer(action, status, pid) {
1020
+ this.each((elm, idx) => {
1021
+ if (action === "capture") {
1022
+ if (status === true) elm.setPointerCapture(pid);
1023
+ else elm.releasePointerCapture(pid);
1024
+ } else if (action === "lock") {
1025
+ if (status === true) elm.requestPointerLock();
1026
+ else document.exitPointerLock();
1027
+ }
1028
+ });
1029
+ return this;
1030
+ }
1031
+ /**
1032
+ * Returns the count of children for this element
1033
+ * @type {number}
1034
+ */
1035
+ get childlen() {
1036
+ return this.#raw[0].children.length;
1037
+ }
1038
+ /**
1039
+ * Select a child of this element
1040
+ * @method EyeElement#child
1041
+ * @param {number} index
1042
+ * @returns {EyeElement|null}
1043
+ */
1044
+ child(index) {
1045
+ if (index === undefined) return this.#raw[0].children.length;
1046
+ if (this.#raw[0].children[index]) return e(this.#raw[0].children[index]);
1047
+ return null;
1048
+ }
1049
+ /**
1050
+ * Set/get the value of the current element
1051
+ * @method EyeElement#val
1052
+ * @param {*} value
1053
+ * @returns
1054
+ */
1055
+ val(value) {
1056
+ if (value != undefined) this.each((a) => a.value = this.#customSet.value("set", value, a));
1057
+ else return this.#customSet.value("get", this.#raw[0].value, this.#raw[0]);
1058
+ return this;
1059
+ }
1060
+ /**
1061
+ * Serialize this element to send it over network, returns 3 formats `json`, `url` & `fd`(formData)
1062
+ * @method EyeElement#serialize
1063
+ * @param {{inputs: Array<string>}} opts
1064
+ * @returns {{json: Object, url: String, fd: FormData}}
1065
+ */
1066
+ serialize(opts) {
1067
+ opts = opts || {};
1068
+ let {
1069
+ inputs = ["input", "textarea", "select"],
1070
+ } = opts;
1071
+ let out = {
1072
+ json: {},
1073
+ url: "",
1074
+ fd: new FormData()
1075
+ };
1076
+ this.#raw[0].querySelectorAll(inputs.join(','))
1077
+ .forEach((inp, i) => {
1078
+ let name = inp.name || inp.dataset.name;
1079
+ let value = inp.value || inp.textContent;
1080
+ if (typeof opts[name] === "function") value = opts[name](inp);
1081
+
1082
+ if (inp.type == "file")
1083
+ inp.files.forEach(file => {
1084
+ out.fd.append(name, file);
1085
+ })
1086
+ else {
1087
+ out.json[name] = value;
1088
+ out.fd.append(name, value);
1089
+ }
1090
+ })
1091
+
1092
+ out.url = new URLSearchParams(out.json).toString();
1093
+ return out;
1094
+ }
1095
+ /**
1096
+ * Redefine the way `.text` or `.val` set or get data to and from this element.
1097
+ * @method EyeElement#redefine
1098
+ * @param {"text" | "value"} type
1099
+ * @param {(action: "set" | "get", value: *, elm: EyeElement) => *} process
1100
+ * @returns {EyeElement}
1101
+ */
1102
+ redefine(type, process) {
1103
+ if (["text", "value"].includes(type) && typeof process == "function")
1104
+ this.#customSet[type] = process;
1105
+ return this;
1106
+ }
1107
+ /**
1108
+ * Animate current object
1109
+ * @method EyeElement#animate
1110
+ * @param {Array<Keyframe>} keyframes
1111
+ * @param {KeyframeAnimationOptions} opts
1112
+ * @returns {Array<Animation>|Animation}
1113
+ */
1114
+ animate(keyframes, opts) {
1115
+ /**
1116
+ * @type {Array<Animation>}
1117
+ */
1118
+ let anmts = [];
1119
+ opts.duration = opts.duration || 1000;
1120
+ this.each((elm, i) => {
1121
+ anmts.push(elm.animate(keyframes, opts));
1122
+ })
1123
+ return anmts.length == 1 ? anmts[0] : anmts;
1124
+ }
1125
+
1126
+ /**
1127
+ * Find first element position within parent element
1128
+ * @method EyeElement#position
1129
+ * @returns {number}
1130
+ */
1131
+ position() {
1132
+ let pos = -1;
1133
+ for (let i = 0; i < this.#raw[0].parentNode.children.length; i++) {
1134
+ const child = this.#raw[0].parentNode.children[i];
1135
+ if (this.#raw[0].isSameNode(child)) {
1136
+ pos = i;
1137
+ break;
1138
+ }
1139
+ }
1140
+ return pos;
1141
+ }
1142
+
1143
+ /**
1144
+ * Get drawing context for canvas
1145
+ * @param {"2d"|"webgl"|"webgl2"|"bitmaprenderer"} contextId
1146
+ * @param {*} contextSettings
1147
+ * @returns {EyeElement|CanvasRenderingContext2D}
1148
+ */
1149
+ getctx(contextId, contextSettings) {
1150
+ if (typeof this.#raw[0].getContext === "function")
1151
+ return this.#raw[0].getContext(contextId, contextSettings);
1152
+ return this;
1153
+ }
1154
+ };
1155
+
1156
+ /**
1157
+ * Creates or select nodes using css selectors, offering a pack of useful functions to use around your code!
1158
+ * @param {String} tag
1159
+ * @param {AttrMap} attrs
1160
+ * @param {Object} css CSS styles to be applied to the element.
1161
+ * @returns {EyeElement}
1162
+ */
1163
+ function e(tag, attrs, css) {
1164
+ if (typeof tag === "string" && tag.indexOf("model:") === 0 || tag === "model") {
1165
+ if (!attrs) return console.error("[EyeJS] Model creation requires parameter 'attr' as prototype, none delivered!");
1166
+
1167
+ tag = tag.split(":");
1168
+ let cls = ["eye-model"];
1169
+ if (tag[1])
1170
+ cls = cls.concat(tag[1].split(" ").filter(a => a != ""));
1171
+ // creating a model
1172
+ let model = e("<div>", {
1173
+ class: cls.join(" "),
1174
+ });
1175
+
1176
+ let sets = cmcl(model, attrs);
1177
+
1178
+ /**
1179
+ * @param {string} attrs
1180
+ * @returns {ModelEyeElement}
1181
+ */
1182
+ return (attrs) => {
1183
+ let copy = e(model.clone(attrs?.parent));
1184
+ // define & attach the refresh function
1185
+ copy.refresh = function (attrs = {}) {
1186
+ let def = attrs.default === false ? false : true;
1187
+ sets.forEach((item) => {
1188
+ if (def === true || (!def && attrs.hasOwnProperty(item.name)))
1189
+ item.set(copy, attrs[item.name]);
1190
+ });
1191
+ return this;
1192
+ };
1193
+ return copy.refresh(attrs);
1194
+ };
1195
+ } else if (tag != null) {
1196
+ let ne = new EyeElement(tag, attrs, css);
1197
+ return ne.length === 0 ? null : ne;
1198
+ }
1199
+ }
1200
+
1201
+ // gloablly exposed
1202
+ window.e = e;
1203
+ window.EyeElement = EyeElement;
1204
+ export default e;