olova 1.0.1 → 1.0.3

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.
Files changed (2) hide show
  1. package/dist/olova.js +1 -1000
  2. package/package.json +3 -13
package/dist/olova.js CHANGED
@@ -1,1000 +1 @@
1
- class olova {
2
- constructor() {
3
- this.$elements = {};
4
- this.listeners = {};
5
- this.components = {};
6
- this.plugins = {};
7
- this.refs = {};
8
- this.$refs = new Proxy(
9
- {},
10
- {
11
- get: (target, key) => {
12
- return this.refs[key];
13
- },
14
- }
15
- );
16
- this.data = {};
17
- this.dependencies = new Map();
18
- this.subscribers = new Map();
19
- this.effects = [];
20
- this.cleanupEffects = [];
21
- }
22
-
23
- registerPlugin(name, handler) {
24
- this.plugins[name] = handler;
25
- }
26
-
27
- registerComponent(name, componentFunction) {
28
- this.components[name] = componentFunction;
29
- this.defineCustomElement(name);
30
- }
31
-
32
- defineCustomElement(name) {
33
- const deshi = this;
34
- class CustomElement extends HTMLElement {
35
- connectedCallback() {
36
- this.render();
37
- }
38
-
39
- render() {
40
- const props = this.getProps();
41
- const componentFunction = deshi.components[name];
42
- const renderedContent = componentFunction(props);
43
-
44
- const tempContainer = document.createElement("div");
45
- tempContainer.innerHTML = renderedContent.trim();
46
-
47
- const parent = this.parentNode;
48
- const renderedElement = tempContainer.firstChild;
49
-
50
- if (renderedElement) {
51
- parent.replaceChild(renderedElement, this);
52
- deshi.applyDirectivesToElement(renderedElement);
53
- deshi.initReactiveSystem();
54
- deshi.runEffects();
55
- }
56
- }
57
-
58
- getProps() {
59
- const props = {};
60
- for (const attr of this.attributes) {
61
- props[attr.name] = attr.value;
62
- }
63
- return props;
64
- }
65
-
66
- static get observedAttributes() {
67
- return Object.keys(deshi.components[name]({}) || {});
68
- }
69
-
70
- attributeChangedCallback(name, oldValue, newValue) {
71
- this.render();
72
- }
73
- }
74
-
75
- customElements.define(name, CustomElement);
76
- }
77
-
78
- applyDirectivesToElement(element) {
79
- this.applyShowDirective(element);
80
- this.applyIfDirective(element);
81
- this.applyForDirective(element);
82
- this.applyModelDirective(element);
83
- this.applyAttrDirective(element);
84
- this.applyClassDirective(element);
85
- this.applyStyleDirective(element);
86
- this.applyBindDirective(element);
87
- this.applyRefDirective(element);
88
-
89
- element.querySelectorAll("*").forEach((el) => {
90
- this.applyShowDirective(el);
91
- this.applyIfDirective(el);
92
- this.applyForDirective(el);
93
- this.applyModelDirective(el);
94
- this.applyAttrDirective(el);
95
- this.applyClassDirective(el);
96
- this.applyStyleDirective(el);
97
- this.applyBindDirective(el);
98
- this.applyRefDirective(el);
99
- });
100
- }
101
-
102
- applyShowDirective(el) {
103
- if (!(el instanceof HTMLElement)) return;
104
- const showExpression = el.getAttribute("$show");
105
- if (!showExpression) return;
106
-
107
- this.effect(() => {
108
- const shouldShow = this.evaluateExpression(showExpression);
109
- el.style.display = shouldShow ? "" : "none";
110
- });
111
- }
112
-
113
- applyIfDirective(el) {
114
- if (!(el instanceof HTMLElement)) return;
115
- const key = el.getAttribute("$if");
116
- if (!key) return;
117
-
118
- const parent = el.parentNode;
119
- const comment = document.createComment("$if placeholder");
120
- parent?.insertBefore(comment, el);
121
-
122
- let elseElement = el.nextElementSibling;
123
- while (elseElement && !elseElement.hasAttribute("$else")) {
124
- elseElement = elseElement.nextElementSibling;
125
- }
126
-
127
- if (elseElement) {
128
- parent?.removeChild(elseElement);
129
- }
130
-
131
- this.effect(() => {
132
- if (this.evaluateExpression(key)) {
133
- el.style.display = "";
134
- if (el.parentNode !== parent) {
135
- parent?.insertBefore(el, comment.nextSibling);
136
- }
137
- if (elseElement) {
138
- elseElement.style.display = "none";
139
- if (elseElement.parentNode === parent) {
140
- parent?.removeChild(elseElement);
141
- }
142
- }
143
- } else {
144
- el.style.display = "none";
145
- if (el.parentNode === parent) {
146
- parent?.removeChild(el);
147
- }
148
- if (elseElement) {
149
- elseElement.style.display = "";
150
- if (elseElement.parentNode !== parent) {
151
- parent?.insertBefore(elseElement, comment.nextSibling);
152
- }
153
- }
154
- }
155
- });
156
- }
157
-
158
- applyForDirective(el) {
159
- if (!(el instanceof HTMLElement)) return;
160
- const expression = el.getAttribute("$for");
161
- if (!expression) return;
162
-
163
- const [item, arr] = expression.split(" in ");
164
- const parent = el.parentNode;
165
- const comment = document.createComment("$for placeholder");
166
- parent?.insertBefore(comment, el);
167
- el.remove();
168
-
169
- this.effect(() => {
170
- const items = this.evaluateExpression(arr);
171
- const existingElements = new Set();
172
- let currentElement = comment.nextSibling;
173
- while (currentElement && currentElement.nodeType === Node.ELEMENT_NODE) {
174
- existingElements.add(currentElement);
175
- currentElement = currentElement.nextSibling;
176
- }
177
-
178
- items.forEach((value, index) => {
179
- let element = Array.from(existingElements).find(
180
- (el) => el.getAttribute("key") === String(index)
181
- );
182
- if (!element) {
183
- element = el.cloneNode(true);
184
- element.setAttribute("key", String(index));
185
- parent?.insertBefore(element, comment.nextSibling);
186
- } else {
187
- existingElements.delete(element);
188
- }
189
- this.interpolateElement(element, { [item]: value, index });
190
- });
191
-
192
- existingElements.forEach((el) => el.remove());
193
- });
194
- }
195
-
196
- applyModelDirective(el) {
197
- if (!(el instanceof HTMLInputElement)) return;
198
- const key = el.getAttribute("$model");
199
- if (!key) return;
200
-
201
- el.addEventListener("input", (e) => {
202
- this.setExpressionValue(key, e.target.value);
203
- });
204
-
205
- this.effect(() => {
206
- el.value = this.evaluateExpression(key);
207
- });
208
- }
209
-
210
- applyAttrDirective(el) {
211
- if (!(el instanceof HTMLElement)) return;
212
- const bindings = el.getAttribute("$attr")?.split(",");
213
- if (!bindings) return;
214
-
215
- bindings.forEach((binding) => {
216
- const [attr, expression] = binding.split(":");
217
- this.effect(() => {
218
- el.setAttribute(attr, this.evaluateExpression(expression));
219
- });
220
- });
221
- }
222
-
223
- applyClassDirective(el) {
224
- if (!(el instanceof HTMLElement)) return;
225
- const classBinding = el.getAttribute("$class");
226
- if (!classBinding) return;
227
-
228
- this.effect(() => {
229
- const classObj = this.evaluateExpression(classBinding);
230
- if (typeof classObj === "object" && classObj !== null) {
231
- Object.entries(classObj).forEach(([className, condition]) => {
232
- if (condition) {
233
- el.classList.add(className);
234
- } else {
235
- el.classList.remove(className);
236
- }
237
- });
238
- } else if (typeof classObj === "string") {
239
- el.className = classObj;
240
- }
241
- });
242
- }
243
-
244
- applyStyleDirective(el) {
245
- if (!(el instanceof HTMLElement)) return;
246
- const styleBinding = el.getAttribute("$style");
247
- if (!styleBinding) return;
248
-
249
- this.effect(() => {
250
- const styleObj = this.evaluateExpression(styleBinding);
251
- if (typeof styleObj === "object" && styleObj !== null) {
252
- Object.keys(styleObj).forEach((prop) => {
253
- el.style[prop] = styleObj[prop];
254
- });
255
- } else if (typeof styleObj === "string") {
256
- el.setAttribute("style", styleObj);
257
- }
258
- });
259
- }
260
-
261
- applyBindDirective(el) {
262
- if (!(el instanceof HTMLElement)) return;
263
- const bindExpression = el.getAttribute("$bind");
264
- if (!bindExpression) return;
265
-
266
- this.effect(() => {
267
- const value = this.evaluateExpression(bindExpression);
268
- this.setElementValue(el, value);
269
- });
270
-
271
- el.addEventListener("input", (event) => {
272
- const value = this.getElementValue(event.target);
273
- this.setExpressionValue(bindExpression, value);
274
- });
275
-
276
- if (el.type === "checkbox" || el.type === "radio") {
277
- el.addEventListener("change", (event) => {
278
- const value = this.getElementValue(event.target);
279
- this.setExpressionValue(bindExpression, value);
280
- });
281
- }
282
- }
283
-
284
- applyRefDirective(el) {
285
- if (!(el instanceof HTMLElement)) return;
286
- const key = el.getAttribute("$ref");
287
- if (key) {
288
- this.registerRef(key, el);
289
- }
290
- }
291
-
292
- setElementValue(el, value) {
293
- if (el instanceof HTMLInputElement) {
294
- if (el.type === "checkbox") {
295
- el.checked = !!value;
296
- } else if (el.type === "radio") {
297
- el.checked = el.value === value;
298
- } else {
299
- el.value = value;
300
- }
301
- } else if (
302
- el instanceof HTMLTextAreaElement ||
303
- el instanceof HTMLSelectElement
304
- ) {
305
- el.value = value;
306
- }
307
- }
308
-
309
- getElementValue(el) {
310
- if (el instanceof HTMLInputElement) {
311
- if (el.type === "checkbox") {
312
- return el.checked;
313
- } else if (el.type === "radio") {
314
- return el.checked ? el.value : null;
315
- } else {
316
- return el.value;
317
- }
318
- } else if (
319
- el instanceof HTMLTextAreaElement ||
320
- el instanceof HTMLSelectElement
321
- ) {
322
- return el.value;
323
- }
324
- }
325
-
326
- useEffect(callback) {
327
- this.effects.push(callback);
328
- }
329
-
330
- runEffects() {
331
- this.effects.forEach((effect) => {
332
- const cleanup = effect();
333
- if (typeof cleanup === "function") {
334
- this.cleanupEffects.push(cleanup);
335
- }
336
- });
337
- this.effects = [];
338
- }
339
-
340
- cleanup() {
341
- this.cleanupEffects.forEach((cleanup) => cleanup());
342
- this.cleanupEffects = [];
343
- }
344
-
345
- getRef(key) {
346
- if (this.refs[key] !== undefined) {
347
- return this.refs[key];
348
- } else {
349
- console.warn(`Ref '${key}' not found.`);
350
- return null;
351
- }
352
- }
353
-
354
- registerRef(key, ref) {
355
- this.refs[key] = ref;
356
- }
357
-
358
- defineReactive(obj, key, val) {
359
- this.listeners[key] = [];
360
- Object.defineProperty(obj, key, {
361
- get: () => {
362
- this.track(key);
363
- return val;
364
- },
365
- set: (newVal) => {
366
- if (val !== newVal) {
367
- val = newVal;
368
- this.notify(key);
369
- }
370
- },
371
- });
372
- }
373
-
374
- watch(key, listener) {
375
- if (!this.listeners[key]) {
376
- this.listeners[key] = [];
377
- }
378
- this.listeners[key].push(listener);
379
- }
380
-
381
- track(key) {
382
- if (olova.activeEffect) {
383
- if (!this.dependencies.has(key)) {
384
- this.dependencies.set(key, new Set());
385
- }
386
- this.dependencies.get(key).add(olova.activeEffect);
387
- }
388
- }
389
-
390
- notify(key) {
391
- if (this.dependencies.has(key)) {
392
- this.dependencies.get(key).forEach((effect) => effect());
393
- }
394
- if (this.listeners[key]) {
395
- this.listeners[key].forEach((listener) => listener());
396
- }
397
- }
398
-
399
- effect(fn) {
400
- olova.activeEffect = fn;
401
- fn();
402
- olova.activeEffect = null;
403
- }
404
-
405
- makeReactive(obj) {
406
- for (const key in obj) {
407
- if (obj.hasOwnProperty(key)) {
408
- this.defineReactive(obj, key, obj[key]);
409
- }
410
- }
411
- }
412
-
413
- initReactiveSystem() {
414
- this.makeReactive(window.useSignal);
415
- this.initDirectives();
416
- this.initEvents();
417
- this.initDataBinding();
418
- }
419
-
420
- initDirectives() {
421
- this.initIfDirective();
422
- this.initRefDirective();
423
- this.initShowDirective();
424
- this.initForDirective();
425
- this.initModelDirective();
426
- this.initAttrDirective();
427
- this.initClassDirective();
428
- this.initStyleDirective();
429
- this.initBindDirective();
430
- this.initHtmlDirective();
431
- Object.keys(this.plugins).forEach((pluginName) => {
432
- document.querySelectorAll(`[\\$${pluginName}]`).forEach((el) => {
433
- const value = el.getAttribute(`$${pluginName}`);
434
- this.effect(() => {
435
- const result = this.evaluateExpression(value);
436
- this.plugins[pluginName](el, result, this);
437
- });
438
- });
439
- });
440
- }
441
- initHtmlDirective() {
442
- document.querySelectorAll("[\\$html]").forEach((el) => {
443
- if (!(el instanceof HTMLElement)) return;
444
- const htmlExpression = el.getAttribute("$html");
445
- if (!htmlExpression) return;
446
-
447
- this.effect(() => {
448
- const htmlContent = this.evaluateExpression(htmlExpression);
449
- el.innerHTML = htmlContent;
450
- el.removeAttribute("$html");
451
- });
452
- });
453
- }
454
- initClassDirective() {
455
- document.querySelectorAll("[\\$class]").forEach((el) => {
456
- if (!(el instanceof HTMLElement)) return;
457
- const classBinding = el.getAttribute("$class");
458
- if (!classBinding) return;
459
-
460
- this.effect(() => {
461
- const classObj = this.evaluateExpression(classBinding);
462
- if (typeof classObj === "object" && classObj !== null) {
463
- Object.entries(classObj).forEach(([className, condition]) => {
464
- if (condition) {
465
- el.classList.add(className);
466
- } else {
467
- el.classList.remove(className);
468
- }
469
- });
470
- } else if (typeof classObj === "string") {
471
- el.className = classObj;
472
- }
473
- });
474
- });
475
- }
476
-
477
- initShowDirective() {
478
- document.querySelectorAll("[\\$show]").forEach((el) => {
479
- if (!(el instanceof HTMLElement)) return;
480
- const showExpression = el.getAttribute("$show");
481
- if (!showExpression) return;
482
-
483
- this.effect(() => {
484
- const shouldShow = this.evaluateExpression(showExpression);
485
- el.style.display = shouldShow ? "" : "none";
486
- });
487
- });
488
- }
489
- initBindDirective() {
490
- document.querySelectorAll("[\\$bind]").forEach((el) => {
491
- if (!(el instanceof HTMLElement)) return;
492
- const bindExpression = el.getAttribute("$bind");
493
- if (!bindExpression) return;
494
-
495
- this.effect(() => {
496
- const value = this.evaluateExpression(bindExpression);
497
- this.setElementValue(el, value);
498
- });
499
-
500
- el.addEventListener("input", (event) => {
501
- const value = this.getElementValue(event.target);
502
- this.setExpressionValue(bindExpression, value);
503
- });
504
-
505
- if (el.type === "checkbox" || el.type === "radio") {
506
- el.addEventListener("change", (event) => {
507
- const value = this.getElementValue(event.target);
508
- this.setExpressionValue(bindExpression, value);
509
- });
510
- }
511
- });
512
- }
513
-
514
- setElementValue(el, value) {
515
- if (el instanceof HTMLInputElement) {
516
- if (el.type === "checkbox") {
517
- el.checked = !!value;
518
- } else if (el.type === "radio") {
519
- el.checked = el.value === value;
520
- } else {
521
- el.value = value;
522
- }
523
- } else if (
524
- el instanceof HTMLTextAreaElement ||
525
- el instanceof HTMLSelectElement
526
- ) {
527
- el.value = value;
528
- }
529
- }
530
-
531
- getElementValue(el) {
532
- if (el instanceof HTMLInputElement) {
533
- if (el.type === "checkbox") {
534
- return el.checked;
535
- } else if (el.type === "radio") {
536
- return el.checked ? el.value : null;
537
- } else {
538
- return el.value;
539
- }
540
- } else if (
541
- el instanceof HTMLTextAreaElement ||
542
- el instanceof HTMLSelectElement
543
- ) {
544
- return el.value;
545
- }
546
- }
547
- initStyleDirective() {
548
- document.querySelectorAll("[\\$style]").forEach((el) => {
549
- if (!(el instanceof HTMLElement)) return;
550
- const styleBinding = el.getAttribute("$style");
551
- if (!styleBinding) return;
552
-
553
- this.effect(() => {
554
- const styleObj = this.evaluateExpression(styleBinding);
555
- if (typeof styleObj === "object" && styleObj !== null) {
556
- Object.keys(styleObj).forEach((prop) => {
557
- el.style[prop] = styleObj[prop];
558
- });
559
- } else if (typeof styleObj === "string") {
560
- el.setAttribute("style", styleObj);
561
- }
562
- });
563
- });
564
- }
565
- initIfDirective() {
566
- document.querySelectorAll("[\\$if]").forEach((el) => {
567
- if (!(el instanceof HTMLElement)) return;
568
- const key = el.getAttribute("$if");
569
- if (!key) return;
570
- const parent = el.parentNode;
571
- const comment = document.createComment("$if placeholder");
572
- parent?.insertBefore(comment, el);
573
-
574
- let elseElement = el.nextElementSibling;
575
- while (elseElement && !elseElement.hasAttribute("$else")) {
576
- elseElement = elseElement.nextElementSibling;
577
- }
578
-
579
- if (elseElement) {
580
- parent?.removeChild(elseElement);
581
- }
582
-
583
- this.effect(() => {
584
- if (this.evaluateExpression(key)) {
585
- el.style.display = "";
586
- if (el.parentNode !== parent) {
587
- parent?.insertBefore(el, comment.nextSibling);
588
- }
589
- if (elseElement) {
590
- elseElement.style.display = "none";
591
- if (elseElement.parentNode === parent) {
592
- parent?.removeChild(elseElement);
593
- }
594
- }
595
- } else {
596
- el.style.display = "none";
597
- if (el.parentNode === parent) {
598
- parent?.removeChild(el);
599
- }
600
- if (elseElement) {
601
- elseElement.style.display = "";
602
- if (elseElement.parentNode !== parent) {
603
- parent?.insertBefore(elseElement, comment.nextSibling);
604
- }
605
- }
606
- }
607
- });
608
- });
609
- }
610
-
611
- initRefDirective() {
612
- document.querySelectorAll("[\\$ref]").forEach((el) => {
613
- if (!(el instanceof HTMLElement)) return;
614
- const key = el.getAttribute("$ref");
615
- if (key) {
616
- this.registerRef(key, el);
617
- }
618
- el.removeAttribute("$ref");
619
- });
620
- }
621
-
622
- initForDirective() {
623
- document.querySelectorAll("[\\$for]").forEach((el) => {
624
- if (!(el instanceof HTMLElement)) return;
625
- const expression = el.getAttribute("$for");
626
- if (!expression) return;
627
- const [item, arr] = expression.split(" in ");
628
- const parent = el.parentNode;
629
- const comment = document.createComment("$for placeholder");
630
- parent?.insertBefore(comment, el);
631
-
632
- // Remove the $for attribute
633
- el.removeAttribute("$for");
634
-
635
- const template = el.cloneNode(true);
636
- el.remove();
637
-
638
- this.effect(() => {
639
- const items = this.evaluateExpression(arr);
640
- const existingElements = new Set();
641
- let currentElement = comment.nextSibling;
642
- while (
643
- currentElement &&
644
- currentElement.nodeType === Node.ELEMENT_NODE
645
- ) {
646
- existingElements.add(currentElement);
647
- currentElement = currentElement.nextSibling;
648
- }
649
- items.forEach((value, index) => {
650
- let element = Array.from(existingElements).find(
651
- (el) => el.getAttribute("key") === String(index)
652
- );
653
- if (!element) {
654
- element = template.cloneNode(true);
655
- element.setAttribute("key", String(index));
656
- parent?.insertBefore(element, comment.nextSibling);
657
- } else {
658
- existingElements.delete(element);
659
- }
660
- this.interpolateElement(element, { [item]: value, index });
661
- });
662
- existingElements.forEach((el) => el.remove());
663
- });
664
- });
665
- }
666
- initModelDirective() {
667
- document.querySelectorAll("[\\$model]").forEach((el) => {
668
- if (!(el instanceof HTMLInputElement)) return;
669
- const key = el.getAttribute("$model");
670
- if (!key) return;
671
- el.addEventListener("input", (e) => {
672
- this.setExpressionValue(key, e.target.value);
673
- });
674
- this.effect(() => {
675
- el.value = this.evaluateExpression(key);
676
- });
677
- });
678
- }
679
-
680
- initAttrDirective() {
681
- document.querySelectorAll("[\\$attr]").forEach((el) => {
682
- if (!(el instanceof HTMLElement)) return;
683
- const bindings = el.getAttribute("$attr")?.split(",");
684
- if (!bindings) return;
685
- bindings.forEach((binding) => {
686
- const [attr, expression] = binding.split(":");
687
- this.effect(() => {
688
- el.setAttribute(attr, this.evaluateExpression(expression));
689
- });
690
- });
691
- });
692
- }
693
-
694
- initEvents() {
695
- const eventTypes = [
696
- "click",
697
- "input",
698
- "change",
699
- "mouseover",
700
- "mousemove",
701
- "mouseout",
702
- "keydown",
703
- "keyup",
704
- "mousedown",
705
- "mouseup",
706
- "dblclick",
707
- "focus",
708
- "blur",
709
- "contextmenu",
710
- "drag",
711
- "drop",
712
- "scroll",
713
- "resize",
714
- "touchstart",
715
- "touchmove",
716
- "touchend",
717
- "paste",
718
- "copy",
719
- "cut",
720
- "error",
721
- "load",
722
- "submit",
723
- "animationstart",
724
- "animationend",
725
- "animationiteration",
726
- "transitionend",
727
- "hashchange",
728
- "beforeunload",
729
- "fullscreenchange",
730
- "message",
731
- "canplay",
732
- "canplaythrough",
733
- "durationchange",
734
- "emptied",
735
- "ended",
736
- "invalid",
737
- "progress",
738
- "ratechange",
739
- "waiting",
740
- "volumechange",
741
- "wheel",
742
- "focusin",
743
- "focusout",
744
- "pointerenter",
745
- "pointerleave",
746
- "touchcancel",
747
- ];
748
-
749
- eventTypes.forEach((eventType) => {
750
- document.querySelectorAll(`[\\@${eventType}]`).forEach((el) => {
751
- if (!(el instanceof HTMLElement)) return;
752
-
753
- const handlerExpression = el.getAttribute(`@${eventType}`);
754
- if (handlerExpression) {
755
- el.addEventListener(eventType, (event) => {
756
- try {
757
- let cleanedExpression = handlerExpression.trim();
758
-
759
- // If the expression is a function reference, invoke it
760
- if (
761
- cleanedExpression in window.useSignal &&
762
- typeof window.useSignal[cleanedExpression] === "function"
763
- ) {
764
- window.useSignal[cleanedExpression](event);
765
- } else {
766
- // If it's not a direct function reference, treat it as inline JS
767
- const func = new Function(
768
- "event, $",
769
- `with($) { ${cleanedExpression} }`
770
- );
771
- func(event, window.useSignal);
772
- }
773
- } catch (error) {
774
- console.error(
775
- `Error executing handler '${handlerExpression}' for event '${eventType}':`,
776
- error
777
- );
778
- }
779
- });
780
- }
781
- });
782
- });
783
- }
784
-
785
- initDataBinding() {
786
- this.compileTextNodes(document.body);
787
- }
788
-
789
- compileTextNodes(el) {
790
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
791
- let node;
792
- const nodesToReplace = [];
793
-
794
- while ((node = walker.nextNode())) {
795
- if (node.textContent.includes("{") && node.textContent.includes("}")) {
796
- nodesToReplace.push(node);
797
- }
798
- }
799
-
800
- nodesToReplace.forEach((node) => {
801
- const parts = node.textContent.split(/(\{.*?\})/);
802
- const fragment = document.createDocumentFragment();
803
-
804
- parts.forEach((part) => {
805
- if (part.startsWith("{") && part.endsWith("}")) {
806
- const textNode = document.createTextNode("");
807
- const expression = part.slice(2, -2).trim();
808
- this.effect(() => {
809
- textNode.textContent = this.evaluateExpression(expression);
810
- });
811
- fragment.appendChild(textNode);
812
- } else {
813
- fragment.appendChild(document.createTextNode(part));
814
- }
815
- });
816
-
817
- node.parentNode.replaceChild(fragment, node);
818
- });
819
- }
820
-
821
- interpolateTextNodes(el, context) {
822
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
823
- let node;
824
- const nodesToReplace = [];
825
-
826
- while ((node = walker.nextNode())) {
827
- if (node.textContent.includes("{") && node.textContent.includes("}")) {
828
- nodesToReplace.push(node);
829
- }
830
- }
831
-
832
- nodesToReplace.forEach((node) => {
833
- const parts = node.textContent.split(/(\{.*?\})/);
834
- const fragment = document.createDocumentFragment();
835
-
836
- parts.forEach((part) => {
837
- if (part.startsWith("{") && part.endsWith("}")) {
838
- const textNode = document.createTextNode("");
839
- const expression = part.slice(2, -2).trim();
840
- this.effect(() => {
841
- textNode.textContent = this.evaluateExpression(expression, context);
842
- });
843
- fragment.appendChild(textNode);
844
- } else {
845
- fragment.appendChild(document.createTextNode(part));
846
- }
847
- });
848
-
849
- node.parentNode.replaceChild(fragment, node);
850
- });
851
- }
852
-
853
- evaluateExpression(expression, context = {}) {
854
- try {
855
- const func = new Function(
856
- "useSignal",
857
- "with(useSignal) { return " + expression + " }"
858
- );
859
- return func({ ...window.useSignal, ...context });
860
- } catch (error) {
861
- console.error(`Error evaluating expression: ${expression}`, error);
862
- return "";
863
- }
864
- }
865
-
866
- setExpressionValue(expression, value) {
867
- try {
868
- const func = new Function(
869
- "useSignal",
870
- "value",
871
- `with(useSignal) { ${expression} = value }`
872
- );
873
- func(window.useSignal, value);
874
- } catch (error) {
875
- console.error(`Error setting value for expression: ${expression}`, error);
876
- }
877
- }
878
-
879
- interpolateElement(el, context) {
880
- this.interpolateAttributes(el, context);
881
- this.interpolateTextNodes(el, context);
882
- Array.from(el.children).forEach((child) =>
883
- this.interpolateElement(child, context)
884
- );
885
- }
886
-
887
- interpolateAttributes(el, context) {
888
- Array.from(el.attributes).forEach((attr) => {
889
- if (attr.value.includes("{") && attr.value.includes("}")) {
890
- this.effect(() => {
891
- attr.value = this.interpolate(attr.value, context);
892
- });
893
- }
894
- });
895
- }
896
-
897
- interpolateTextNodes(el, context) {
898
- const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
899
- let node;
900
- const nodesToReplace = [];
901
-
902
- while ((node = walker.nextNode())) {
903
- if (node.textContent.includes("{") && node.textContent.includes("}")) {
904
- nodesToReplace.push(node);
905
- }
906
- }
907
-
908
- nodesToReplace.forEach((node) => {
909
- const fragment = document.createDocumentFragment();
910
- const parts = node.textContent.split(/(\{.*?\})/);
911
-
912
- parts.forEach((part) => {
913
- if (part.startsWith("{") && part.endsWith("}")) {
914
- const span = document.createElement("span");
915
- const expression = part.slice(2, -2).trim();
916
- this.effect(() => {
917
- span.textContent = this.evaluateExpression(expression, context);
918
- });
919
- fragment.appendChild(span);
920
- } else {
921
- fragment.appendChild(document.createTextNode(part));
922
- }
923
- });
924
-
925
- node.parentNode.replaceChild(fragment, node);
926
- });
927
- }
928
-
929
- interpolate(template, context) {
930
- return template.replace(/\{\{(.*?)\}\}/g, (match, expression) => {
931
- return this.evaluateExpression(expression, context);
932
- });
933
- }
934
-
935
- static init() {
936
- const deshi = new olova();
937
- window.$refs = deshi.$refs;
938
- window.$ = (selector) => deshi.$(selector);
939
- // Create the useSignal function
940
- const useSignalFunction = (obj = {}) => {
941
- Object.assign(window.useSignal, obj);
942
- };
943
- // Create the useSignal object with the function
944
- window.useSignal = new Proxy(useSignalFunction, {
945
- get(target, key) {
946
- if (key === "call" || key === "apply") {
947
- return Reflect.get(target, key);
948
- }
949
- deshi.track(key);
950
- return Reflect.get(target, key);
951
- },
952
- set(target, key, value) {
953
- if (Reflect.get(target, key) !== value) {
954
- Reflect.set(target, key, value);
955
- deshi.notify(key);
956
- deshi.runEffects();
957
- }
958
- return true;
959
- },
960
- });
961
-
962
- document.addEventListener("DOMContentLoaded", () => {
963
- deshi.renderTemplate();
964
- });
965
-
966
- window.addEventListener("beforeunload", () => {
967
- deshi.cleanup();
968
- });
969
-
970
- window.useEffect = (callback) => {
971
- deshi.useEffect(callback);
972
- };
973
- window.component = (name, componentFunction) => {
974
- deshi.registerComponent(name, componentFunction);
975
- };
976
-
977
- window.plugin = (name, handler) => deshi.registerPlugin(name, handler);
978
-
979
- return deshi;
980
- }
981
-
982
- renderTemplate() {
983
- const root = document.getElementById("root");
984
- const template = document.querySelector("template");
985
- if (root && template) {
986
- let content = template.innerHTML;
987
- content = content.replace(/<([a-z-]+)><\/\1>/g, "");
988
- root.innerHTML = content;
989
- template.remove();
990
- this.initReactiveSystem();
991
- this.runEffects();
992
- } else {
993
- console.error("Root element or app template not found");
994
- }
995
- }
996
- }
997
-
998
- olova.activeEffect = null;
999
-
1000
- export default olova;
1
+ let createApp=({data:e,computed:t,methods:r,watch:o,mounted:n,beforeMount:a,beforeUpdate:l,template:c,plugins:i=[],components:s={}})=>{let u,d={},f={},m={},h={},p={},E=new Map,y=new Map,b=(e,t)=>{y.set(e,t)},g=(e,t)=>{p[e]=t},A=e=>new Proxy(e,{get:(e,t)=>"object"==typeof e[t]&&null!==e[t]?A(e[t]):e[t],set:(e,t,r)=>(e[t]!==r&&(e[t]=r,N(t)),!0)});Error;class $ extends Error{constructor(e){super(e),this.name="StateError"}}let w=(e,t,r=Error)=>function(...o){try{return e.apply(this,o)}catch(e){throw new r(`${t}: ${e.message}`)}};a&&w(a,"Error in beforeMount hook",$).call(M);let v=new Set,T=e=>{d[e]&&(v.add(e),requestAnimationFrame((()=>{v.forEach((e=>{d[e].forEach(w((t=>{t.node?t.node.textContent=t.originalText.replace(/\{(.*?)\}/g,((e,t)=>Function("state",`with(state) { return ${t} }`)(M))):t.element&&t.attrName?(t.element.setAttribute(t.attrName,M[e]),t.element.removeAttribute(`:${t.attrName}`)):t.update&&t.update()}),"Error updating bindings"))})),v.clear()})))},N=e=>{l&&w(l,"Error in beforeUpdate hook").call(M,e),T(e),x(),_(e),m.__updated__&&m.__updated__.forEach((e=>e.callback.call(M)))},x=()=>{Object.keys(f).forEach((e=>delete f[e]))},_=e=>{m[e]&&m[e].forEach((t=>{t.debounce?(clearTimeout(t.timeout),t.timeout=setTimeout((()=>t.callback.call(M,M[e])),t.debounce)):t.throttle?(!t.lastCalled||Date.now()-t.lastCalled>t.throttle)&&(t.callback.call(M,M[e]),t.lastCalled=Date.now()):t.callback.call(M,M[e])}))};o&&Object.keys(o).forEach((e=>{"function"==typeof o[e]?m[e]=[{callback:o[e]}]:"object"==typeof o[e]?m[e]=[{...o[e]}]:m[e]=Array.isArray(o[e])?o[e].map((e=>({callback:e}))):[{callback:o[e]}]}));let M=A({...e,$refs:new Proxy(h,{get:(e,t)=>e[t],set(){throw Error("$refs is read-only")}})}),L={},C={};t&&Object.keys(t).forEach((e=>{let r=t[e];L[e]=[],Object.defineProperty(M,e,{get:()=>"function"==typeof r&&"AsyncFunction"===r.constructor.name?(C[e]||(f[e]=new Proxy(M,{get:(t,r)=>(L[e].includes(r)||L[e].push(r),t[r])}),C[e]=r.call(M).then((t=>(f[e]=t,delete C[e],T(e),t))).catch((t=>{console.error(`Error in async computed property "${e}":`,t)}))),C[e]):(f.hasOwnProperty(e)||(f[e]=new Proxy(M,{get:(t,r)=>(L[e].includes(r)||L[e].push(r),t[r])}),f[e]=r.call(M)),f[e]),enumerable:!0})}));let O=()=>{let e=document.createElement("div");e.innerHTML=c.trim(),u.appendChild(e);let t=e=>{e.nodeType===Node.ELEMENT_NODE&&Array.from((e=(e=>{if(e.nodeType===Node.ELEMENT_NODE){let t=e.tagName.toLowerCase();if(y.has(t)){let r=y.get(t),o=Array.from(e.attributes).reduce(((e,t)=>(e[t.name]=t.value,e)),{}),n=r(o),a=document.createElement("div");a.innerHTML=n;let l=a.firstElementChild;return e.parentNode.replaceChild(l,e),l}}return e})(e)).childNodes).forEach(t)};Array.from(u.childNodes).forEach(t),u.querySelectorAll("*").forEach((e=>{if(e instanceof HTMLElement){(e=>{Array.from(e.attributes).forEach((t=>{let r=t.name.startsWith("$")?t.name.slice(1):null;if(r&&p[r]){let o=t.value;p[r](e,o,M),e.removeAttribute(t.name)}}))})(e),[...e.childNodes].filter((e=>e.nodeType===Node.TEXT_NODE&&/\{.*?\}/.test(e.textContent||""))).forEach((e=>{let t=e.textContent||"",r=t.match(/\{(.*?)\}/g);r&&r.forEach((r=>{let o=r.replace(/\{|\}/g,"").trim();d[o]||(d[o]=[]),d[o].push({node:e,originalText:t})}))})),Array.from(e.attributes).forEach((t=>{if(t.name.startsWith("$")){let r=t.value.trim(),o=t.name.slice(1);d[r]||(d[r]=[]),d[r].push({element:e,attrName:o})}}))}}))};Object.entries(s).forEach((([e,t])=>{b(e,t)}));let S={beforeCreate:[],created:[],beforeMount:[],mounted:[],beforeUpdate:[],updated:[],beforeUnmount:[],unmounted:[]},k=(e,t)=>{S[e]&&S[e].push(t)},j=e=>{S[e]&&S[e].forEach((e=>e.call(M)))};return window.addEventListener("error",(e=>{console.error("Global error:",e)})),{mount:w((function(e){if(!(u=document.querySelector(e)))throw Error(`Element with selector "${e}" not found.`);j("beforeCreate"),j("created"),a&&a.call(M),j("beforeMount"),i.forEach((e=>{"function"==typeof e&&e({createPlugin:g,state:M,bindings:d})})),O(),u.querySelectorAll("*").forEach((e=>{Array.from(e.attributes).forEach((t=>{if(t.name.startsWith("@")){let o=t.name.slice(1),n=t.value.trim();e.addEventListener(o,(e=>{if(r&&r[n])r[n].call(M,e);else try{Function("state","event",`with(state) { ${n} }`).call(M,M,e),N()}catch(e){console.error(`Error executing inline event expression: ${n}`,e)}}))}}))})),u.querySelectorAll("[\\$style]").forEach((e=>{if(!(e instanceof HTMLElement))return;let t=e.getAttribute("$style");t&&((()=>{try{let r=Function("state",`with(state) { return ${t} }`)(M);"object"==typeof r&&null!==r?Object.entries(r).forEach((([t,r])=>{e.style[t]=r})):"string"==typeof r&&e.setAttribute("style",r)}catch(e){console.error("Error applying style directive:",e)}})(),T(t))})),u.querySelectorAll("[\\$if]").forEach((e=>{if(!(e instanceof HTMLElement))return;let t=e.getAttribute("$if");if(!t)return;let r=e.parentNode,o=document.createComment("$if placeholder");r?.insertBefore(o,e);let n=e.nextElementSibling;for(;n&&!n.hasAttribute("$else");)n=n.nextElementSibling;(()=>{try{Function("state",`with(state) { return ${t} }`)(M)?(e.style.display="",e.parentNode!==r&&r?.insertBefore(e,o.nextSibling),n&&(n.style.display="none")):(e.style.display="none",n&&(n.style.display=""))}catch(e){console.error("Error evaluating :if directive:",e)}})(),T(t)})),u.querySelectorAll("[\\$for]").forEach((e=>{if(!(e instanceof HTMLElement))return;let t=e.getAttribute("$for");if(!t)return;let r=t.match(/^\s*(\w+)\s+(?:of|in)\s+(\w+)\s*$/);if(!r)return void console.error("Invalid :for expression:",t);let[,o,n]=r,a=e.parentNode,l=document.createComment("$for placeholder");a?.insertBefore(l,e),e.removeAttribute("$for");let c=e.cloneNode(!0);a?.removeChild(e);let i=()=>{let e=document.createDocumentFragment(),t=M[n];for((Array.isArray(t)&&t.forEach(((t,r)=>{let n=c.cloneNode(!0),a=new Proxy({[o]:t,index:r},{get:(e,t)=>t in e?e[t]:M[t]}),l=e=>{if(e.nodeType===Node.TEXT_NODE){let t=e.textContent;t.includes("{")&&t.includes("}")&&(e.textContent=t.replace(/\{(.*?)\}/g,((e,t)=>Function("state",`with(state) { return ${t} }`)(a))))}else e.nodeType===Node.ELEMENT_NODE&&Array.from(e.childNodes).forEach(l)};l(n),n.querySelectorAll("*").forEach((e=>{Array.from(e.attributes).forEach((t=>{if(t.value.includes("{")&&t.value.includes("}")){let r=t.value.replace(/\{(.*?)\}/g,((e,t)=>Function("state",`with(state) { return ${t} }`)(a)));e.setAttribute(t.name,r)}}))})),e.appendChild(n)})));l.nextSibling;)a?.removeChild(l.nextSibling);a?.insertBefore(e,l.nextSibling)};i(),d[n]||(d[n]=[]),d[n].push({updateFor:i})})),u.querySelectorAll("[\\$ref]").forEach((e=>{if(!(e instanceof HTMLElement))return;let t=e.getAttribute("$ref");t&&(h[t]=e,e.removeAttribute("$ref"))})),u.querySelectorAll("[\\$model]").forEach((e=>{if(!(e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement))return;let t=e.getAttribute("$model");t&&(e.value=M[t]||"",e.addEventListener("input",(e=>{M[t]=e.target.value})),d[t]||(d[t]=[]),d[t].push({element:e,updateModel(){e.value=M[t]||""}}),e.removeAttribute("$model"))})),u.querySelectorAll("[\\$class]").forEach((e=>{if(!(e instanceof HTMLElement))return;let t=e.getAttribute("$class");if(!t)return;let r=()=>{try{let r=Function("state",`with(state) { return ${t} }`)(M);"object"==typeof r&&null!==r?Object.entries(r).forEach((([t,r])=>{r?e.classList.add(t):e.classList.remove(t)})):"string"==typeof r&&(e.className=r)}catch(e){console.error("Error applying class directive:",e)}e.removeAttribute("$class")};r(),d[t]||(d[t]=[]),d[t].push({updateClass:r})})),(e=>{Object.keys(L).forEach((t=>{L[t].includes(e)&&(delete f[t],T(t))}))})(),u.querySelectorAll("[\\$show]").forEach((e=>{e instanceof HTMLElement&&(e=>{if(!(e instanceof HTMLElement))return;let t=e.getAttribute("$show");if(!t)return;let r=()=>{try{let r=Function("state",`with(state) { return ${t} }`)(M);e.style.display=r?"":"none"}catch(e){console.error("Error evaluating $show directive:",e)}};r(),d[t]||(d[t]=[]),d[t].push({element:e,update:r})})(e)})),n&&n.call(M),j("mounted"),Object.keys(M).forEach((e=>T(e)))}),"Error mounting application"),onUpdated:e=>{if("function"==typeof e){let t=w(e,"Error in onUpdated callback");m.__updated__||(m.__updated__=[]),m.__updated__.push({callback:t})}},createPlugin:g,emit:(e,t)=>{E.has(e)||E.set(e,[]),E.get(e).forEach((e=>e(t)))},on:(e,t)=>(E.has(e)||E.set(e,[]),E.get(e).push(t),()=>{let r=E.get(e),o=r.indexOf(t);-1!==o&&r.splice(o,1)}),addLifecycleHook:k,applyMixin:e=>{e.data&&Object.assign(M,e.data()),e.methods&&Object.assign(r,e.methods),e.computed&&Object.assign(t,e.computed),e.watch&&Object.keys(e.watch).forEach((t=>{o[t]||(o[t]=[]),o[t].push(e.watch[t])})),e.mounted&&k("mounted",e.mounted)},component:b}};export{createApp};
package/package.json CHANGED
@@ -1,14 +1,8 @@
1
1
  {
2
2
  "name": "olova",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "A lightweight JavaScript framework for building reactive applications.",
5
- "main": "index.js",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "vite build",
9
- "preview": "vite preview",
10
- "test": "jest"
11
- },
5
+ "main": "olova.js",
12
6
  "keywords": [
13
7
  "javascript",
14
8
  "framework",
@@ -17,9 +11,5 @@
17
11
  ],
18
12
  "author": "Nazmul Hossain",
19
13
  "license": "MIT",
20
- "dependencies": {},
21
- "devDependencies": {
22
- "vite": "^4.0.0",
23
- "jest": "^30.1.0"
24
- }
14
+ "dependencies": {}
25
15
  }