lahama 4.0.0 → 6.0.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.
Files changed (2) hide show
  1. package/dist/lahama.js +919 -6
  2. package/package.json +1 -1
package/dist/lahama.js CHANGED
@@ -1,17 +1,158 @@
1
- import 'vitest/dist/chunks/reporters.d.OXEK7y4s.d.ts';
2
-
3
1
  function withoutNulls(arr) {
4
2
  return arr.filter((item) => item != null)
5
3
  }
4
+ function arraysDiff(oldArray, newArray) {
5
+ return {
6
+ added : newArray.filter(
7
+ (newItem) => !oldArray.includes(newItem)
8
+ ),
9
+ removed : oldArray.filter(
10
+ (oldItem) => !newArray.includes(oldItem)
11
+ )
12
+ }
13
+ }
14
+ const ARRAY_DIFF_OP = {
15
+ ADD : 'add',
16
+ REMOVE : 'remove',
17
+ MOVE : 'move',
18
+ NOOP : 'noop'
19
+ };
6
20
  const a = { };
7
21
  const b = { };
8
22
  console.log(a === b);
23
+ class ArrayWithOriginalIndices {
24
+ #array = []
25
+ #originalIndices = []
26
+ #equalsFn
27
+ constructor(array, equalsFn) {
28
+ this.#array = [...array];
29
+ this.#originalIndices = array.map((_, i) => i);
30
+ this.#equalsFn = equalsFn;
31
+ }
32
+ get length() {
33
+ return this.#array.length
34
+ }
35
+ isRemoval(index, newArray) {
36
+ if (index >= this.length) {
37
+ return false
38
+ }
39
+ const item = this.#array[index];
40
+ const indexInNewArray = newArray.findIndex((newItem) =>
41
+ this.#equalsFn(item, newItem)
42
+ );
43
+ return indexInNewArray === -1
44
+ }
45
+ removeItem(index) {
46
+ const operation = {
47
+ op : ARRAY_DIFF_OP.REMOVE,
48
+ index,
49
+ item : this.#array[index],
50
+ };
51
+ this.#array.splice(index, 1);
52
+ return operation
53
+ }
54
+ isNoop(index, newArray) {
55
+ if (index >= this.length) {
56
+ return false
57
+ }
58
+ const item = this.#array[index];
59
+ const newItem = newArray[index];
60
+ return this.#equalsFn(item, newItem)
61
+ }
62
+ originalIndexAt(index) {
63
+ return this.#originalIndices[index]
64
+ }
65
+ noopItem(index) {
66
+ return {
67
+ op : ARRAY_DIFF_OP.NOOP,
68
+ originalIndex : this.originalIndexAt(index),
69
+ item : this.#array[index],
70
+ }
71
+ }
72
+ isAddition(item, fromIdx) {
73
+ return this.findIndexFrom(item, fromIdx) === -1
74
+ }
75
+ findIndexFrom(item, fromIndex) {
76
+ for (let i = fromIndex; i < this.length; i++) {
77
+ if (this.#equalsFn(item, this.#array[i])) {
78
+ return i
79
+ }
80
+ }
81
+ return -1
82
+ }
83
+ addItem(item, index) {
84
+ const operation = {
85
+ op : ARRAY_DIFF_OP.ADD,
86
+ index,
87
+ item
88
+ };
89
+ this.#array.splice(index, 0, item);
90
+ this.#originalIndices.splice(index, 0, -1);
91
+ return operation
92
+ }
93
+ moveItem(item, toIndex) {
94
+ const fromIndex = this.findIndexFrom(item, toIndex);
95
+ const operation = {
96
+ op : ARRAY_DIFF_OP.MOVE,
97
+ originalIndex : this.originalIndexAt(fromIndex),
98
+ from : fromIndex,
99
+ index : toIndex,
100
+ item : this.#array[fromIndex]
101
+ };
102
+ const [_item] = this.#array.splice(fromIndex, 1);
103
+ this.#array.splice(toIndex, 0 , _item);
104
+ const [originalIndex] =
105
+ this.#originalIndices.splice(fromIndex, 1);
106
+ this.#originalIndices.splice(toIndex, 0, originalIndex);
107
+ return operation
108
+ }
109
+ removeItemsAfter(index) {
110
+ const operations = [];
111
+ while (this.length > index) {
112
+ operations.push(this.removeItem(index));
113
+ }
114
+ return operations
115
+ }
116
+ }
117
+ function arraysDiffSequence(
118
+ oldArray,
119
+ newArray,
120
+ equalsFn = (a, b) => a === b
121
+ ) {
122
+ const sequence = [];
123
+ const array = new ArrayWithOriginalIndices(oldArray, equalsFn);
124
+ for (let index = 0; index < newArray.length; index++) {
125
+ //!REMOVE CASE
126
+ if (array.isRemoval(index, newArray)) {
127
+ sequence.push(array.removeItem(index));
128
+ index--;
129
+ continue
130
+ }
131
+ //!noop case
132
+ if (array.isNoop(index, newArray)) {
133
+ sequence.push(array.noopItem(index));
134
+ continue
135
+ }
136
+ //!addition case
137
+ const item = newArray[index];
138
+ if (array.isAddition(item , index)) {
139
+ sequence.push(array.addItem(item, index));
140
+ continue
141
+ }
142
+ //!move case
143
+ sequence.push(array.moveItem(item, index));
144
+ }
145
+ //!remove extra items
146
+ sequence.push(...array.removeItemsAfter(newArray.length));
147
+ return sequence
148
+ }
9
149
 
10
150
  const DOM_TYPES = {
11
151
  TEXT : 'text',
12
152
  ELEMENT : 'element',
13
153
  FRAGMENT : 'fragment',
14
154
  COMPONENT : 'component',
155
+ SLOT : 'slot'
15
156
  };
16
157
  function h(tag, props = {} , children = []) {
17
158
  const type =
@@ -36,6 +177,17 @@ function hFragment(vNodes) {
36
177
  children : mapTextNodes(withoutNulls(vNodes)),
37
178
  }
38
179
  }
180
+ let hSlotCalled = false;
181
+ function didCreateSlot() {
182
+ return hSlotCalled
183
+ }
184
+ function resetDidCreateSlot() {
185
+ hSlotCalled = false;
186
+ }
187
+ function hSlot(children = []) {
188
+ hSlotCalled = true;
189
+ return { type : DOM_TYPES.SLOT, children}
190
+ }
39
191
 
40
192
  function addEventListener(
41
193
  eventName,
@@ -100,6 +252,9 @@ function setClass(el, className) {
100
252
  function setStyle(el, name, value) {
101
253
  el.style[name] = value;
102
254
  }
255
+ function removeStyle(el, name) {
256
+ el.style[name] = null;
257
+ }
103
258
  function removeAttributeCustom(el, name) {
104
259
  el[name] = null;
105
260
  el.removeAttribute(name);
@@ -146,6 +301,13 @@ function processJobs() {
146
301
  }
147
302
  isScheduled = false;
148
303
  }
304
+ function nextTick() {
305
+ scheduleUpdate();
306
+ return flushPromises()
307
+ }
308
+ function flushPromises() {
309
+ return new Promise((resolve) => setTimeout(resolve))
310
+ }
149
311
 
150
312
  function mountDom(
151
313
  vdom,
@@ -218,9 +380,11 @@ function createFragmentNodes(vdom, parentEl, index, hostComponent) {
218
380
  children.forEach((child, i) => mountDom(child, parentEl, index ? index + i : null, hostComponent));
219
381
  }
220
382
  function createComponentNode(vdom, parentEl, index, hostComponent) {
221
- const Component = vdom.tag;
383
+ const { tag : Component, children } = vdom;
222
384
  const { props, events } = extractPropsAndEvents(vdom);
223
385
  const component = new Component(props, events, hostComponent);
386
+ component.setExternalContent(children);
387
+ component.setAppContext(hostComponent?.appContext ?? {});
224
388
  component.mount(parentEl, index);
225
389
  vdom.component = component;
226
390
  vdom.el = component.firstElement;
@@ -270,10 +434,253 @@ function removeFragmentNode(vdom) {
270
434
  children.forEach(destroyDom);
271
435
  }
272
436
 
273
- function createApp(RootComponent, props = {}) {
437
+ function makeRouteMatcher(route) {
438
+ return routeHasParams(route)
439
+ ? makeMatcherWithParams(route)
440
+ : makeMatcherWithoutParams(route)
441
+ }
442
+ function routeHasParams({ path }) {
443
+ return path.includes(':')
444
+ }
445
+ const CATCH_ALL_ROUTE = '*';
446
+ function makeRouteWithoutParamsRegex( { path }) {
447
+ if (path === CATCH_ALL_ROUTE) {
448
+ return new RegExp('^.*$')
449
+ }
450
+ return new RegExp(`^${path}`)
451
+ }
452
+ function makeMatcherWithoutParams(route) {
453
+ const regex = makeRouteWithoutParamsRegex(route);
454
+ const isRedirect = typeof route.redirect === 'string';
455
+ return {
456
+ route,
457
+ isRedirect,
458
+ checkMatch(path) {
459
+ return regex.test(path)
460
+ },
461
+ extractParams() {
462
+ return {}
463
+ },
464
+ extractQuery,
465
+ }
466
+ }
467
+ function extractQuery(path) {
468
+ const queryIndex = path.indexOf('?');
469
+ if (queryIndex === -1) {
470
+ return {}
471
+ }
472
+ const search = new URLSearchParams(path.slice(queryIndex + 1));
473
+ return Object.fromEntries(search.entries())
474
+ }
475
+ function makeRouteWithParamsRegex({ path }) {
476
+ const regex = path.replace(
477
+ /:([^/]+)/g,
478
+ (_, paramName) => `(?<${paramName}>[^/]+)`
479
+ );
480
+ return new RegExp(`^${regex}$`)
481
+ }
482
+ function makeMatcherWithParams(route) {
483
+ const regex = makeRouteWithParamsRegex(route);
484
+ const isRedirect = typeof route.redirect === 'string';
485
+ return {
486
+ route,
487
+ isRedirect,
488
+ checkMatch(path) {
489
+ return regex.test(path)
490
+ },
491
+ extractParams(path) {
492
+ const { groups } = regex.exec(path);
493
+ return groups
494
+ },
495
+ extractQuery,
496
+ }
497
+ }
498
+
499
+ class Dispatcher {
500
+ #subs = new Map()
501
+ #afterHandlers = []
502
+ subscribe(commandName, handler) {
503
+ if (!this.#subs.has(commandName)) {
504
+ this.#subs.set(commandName, []);
505
+ }
506
+ const handlersArray = this.#subs.get(commandName);
507
+ if (handlersArray.includes(handler)) {
508
+ return () => {
509
+ }
510
+ }
511
+ handlersArray.push(handler);
512
+ return () => {
513
+ const idx = handlersArray.indexOf(handler);
514
+ handlersArray.splice(idx, 1);
515
+ }
516
+ }
517
+ afterEveryCommand(handler) {
518
+ this.#afterHandlers.push(handler);
519
+ return () => {
520
+ const idx = this.#afterHandlers.indexOf(handler);
521
+ this.#afterHandlers.splice(idx, 1);
522
+ }
523
+ }
524
+ dispatch(commandName, payload) {
525
+ if (this.#subs.has(commandName)) {
526
+ this.#subs.get(commandName).forEach((handler) => handler(payload));
527
+ } else {
528
+ console.warn(`No handlers for command : ${commandName}`);
529
+ }
530
+ this.#afterHandlers.forEach((handler) => handler());
531
+ }
532
+ }
533
+
534
+ const ROUTER_EVENT = 'router-event';
535
+ class HashRouter {
536
+ #matchers = []
537
+ #isInitialized = false
538
+ #onPopState = () => this.#matchCurrentRoute()
539
+ #params = {}
540
+ #query = {}
541
+ #matchedRoute = null
542
+ #dispatcher = new Dispatcher()
543
+ #subscriptions = new WeakMap()
544
+ #subscriberFns = new Set()
545
+ get params() {
546
+ return this.#params
547
+ }
548
+ get query() {
549
+ return this.#query
550
+ }
551
+ get matchedRoute() {
552
+ return this.#matchedRoute
553
+ }
554
+ constructor(routes = []) {
555
+ this.#matchers = routes.map(makeRouteMatcher);
556
+ }
557
+ get #currentRouteHash() {
558
+ const hash = document.location.hash;
559
+ if (hash === '') {
560
+ return '/'
561
+ }
562
+ return hash.slice(1)
563
+ }
564
+ async init() {
565
+ if (this.#isInitialized) {
566
+ return
567
+ }
568
+ this.#isInitialized = true;
569
+ if (document.location.hash === '') {
570
+ window.history.replaceState({}, '', '#/');
571
+ }
572
+ window.addEventListener('popstate', this.#onPopState);
573
+ }
574
+ destroy() {
575
+ if (!this.#isInitialized) {
576
+ return
577
+ }
578
+ window.removeEventListener('popstate', this.#onPopState);
579
+ Array.from(this.#subscriberFns).forEach(this.unsubscribe, this);
580
+ this.#isInitialized = false;
581
+ }
582
+ #matchCurrentRoute() {
583
+ return this.navigateTo(this.#currentRouteHash)
584
+ }
585
+ async navigateTo(path) {
586
+ const matcher = this.#matchers.find((matcher) =>
587
+ matcher.checkMatch(path)
588
+ );
589
+ if (matcher == null) {
590
+ console.warn(`[Router] No route matches path "${path}"`);
591
+ this.#matchedRoute = null;
592
+ this.#params = {};
593
+ this.#query = {};
594
+ return
595
+ }
596
+ if (matcher.isRedirect) {
597
+ return this.navigateTo(matcher.route.redirect)
598
+ }
599
+ const from = this.#matchedRoute;
600
+ const to = matcher.route;
601
+ const { shouldNavigate, shouldRedirect, redirectPath } =
602
+ await this.#canChangeRoute(from, to);
603
+ if (shouldRedirect) {
604
+ return this.navigateTo(redirectPath)
605
+ }
606
+ if (shouldNavigate) {
607
+ this.#matchedRoute = matcher.route;
608
+ this.#params = matcher.extractParams(path);
609
+ this.#query = matcher.extractQuery(path);
610
+ this.#pushState(path);
611
+ this.#dispatcher.dispatch(ROUTER_EVENT, {from, to, router: this});
612
+ }
613
+ }
614
+ #pushState(path) {
615
+ window.history.pushState({}, '', `#${path}`);
616
+ }
617
+ back() {
618
+ window.history.back();
619
+ }
620
+ forward() {
621
+ window.history.forward();
622
+ }
623
+ subscribe(handler) {
624
+ const unsubscribe = this.#dispatcher.subscribe(ROUTER_EVENT, handler);
625
+ this.#subscriptions.set(handler, unsubscribe);
626
+ this.#subscriberFns.add(handler);
627
+ }
628
+ unsubscribe(handler) {
629
+ const unsubscribe = this.#subscriptions.get(handler);
630
+ if (unsubscribe) {
631
+ unsubscribe();
632
+ this.#subscriptions.delete(handler);
633
+ this.#subscriberFns.delete(handler);
634
+ }
635
+ }
636
+ async #canChangeRoute(from, to) {
637
+ const guard = to.beforeEnter;
638
+ if (typeof guard !== 'function') {
639
+ return {
640
+ shouldRedirect : false,
641
+ shouldNavigate : true,
642
+ redirectPath : null,
643
+ }
644
+ }
645
+ const result = await guard(from?.path, to?.path);
646
+ if (result === false) {
647
+ return {
648
+ shouldRedirect : false,
649
+ shouldNavigate : false,
650
+ redirectPath: null,
651
+ }
652
+ }
653
+ if (typeof result === 'string') {
654
+ return {
655
+ shouldRedirect : true,
656
+ shouldNavigate : true,
657
+ redirectPath: result,
658
+ }
659
+ }
660
+ return {
661
+ shouldRedirect : false,
662
+ shouldNavigate : true,
663
+ redirectPath : null,
664
+ }
665
+ }
666
+ }
667
+ class NoopRouter {
668
+ init() {}
669
+ destroy() {}
670
+ navigateTo() {}
671
+ back() {}
672
+ forward() {}
673
+ subscribe() {}
674
+ unsubscribe() {}
675
+ }
676
+
677
+ function createApp(RootComponent, props = {}, options = {}) {
274
678
  let parentEl = null;
275
679
  let isMounted = false;
276
680
  let vdom = null;
681
+ const context = {
682
+ router : options.router || new NoopRouter(),
683
+ };
277
684
  function reset() {
278
685
  parentEl = null;
279
686
  isMounted = false;
@@ -286,7 +693,8 @@ function createApp(RootComponent, props = {}) {
286
693
  }
287
694
  parentEl = _parentEl;
288
695
  vdom = h(RootComponent, props);
289
- mountDom(vdom, parentEl);
696
+ mountDom(vdom, parentEl, null, { appContext : context});
697
+ context.router.init();
290
698
  isMounted = true;
291
699
  },
292
700
  unmount() {
@@ -294,9 +702,514 @@ function createApp(RootComponent, props = {}) {
294
702
  throw new Error(`The application is not mounted`)
295
703
  }
296
704
  destroyDom(vdom);
705
+ context.router.destroy();
297
706
  reset();
298
707
  }
299
708
  }
300
709
  }
301
710
 
302
- export { DOM_TYPES, createApp, h, hFragment, hString };
711
+ function areNodesEqual(nodeOne, nodeTwo) {
712
+ if (nodeOne.type !== nodeTwo.type) {
713
+ return false
714
+ }
715
+ if (nodeOne.type === DOM_TYPES.ELEMENT) {
716
+ const {
717
+ tag : tagOne,
718
+ props : { key : keyOne}
719
+ } = nodeOne;
720
+ const {
721
+ tag : tagTwo,
722
+ props : { key : keyTwo}
723
+ } = nodeTwo;
724
+ return tagOne === tagTwo && keyOne === keyTwo
725
+ }
726
+ if (nodeOne.type === DOM_TYPES.COMPONENT) {
727
+ const {
728
+ tag : componentOne,
729
+ props : { key : keyOne},
730
+ } = nodeOne;
731
+ const {
732
+ tag : componentTwo,
733
+ props : {key : keyTwo},
734
+ } = nodeTwo;
735
+ return componentOne === componentTwo && keyOne === keyTwo
736
+ }
737
+ return true
738
+ }
739
+
740
+ function objectsDiff(oldObj, newObj) {
741
+ const oldKeys = Object.keys(oldObj);
742
+ const newKeys = Object.keys(newObj);
743
+ return {
744
+ added : newKeys.filter((key) => !(key in oldObj)),
745
+ removed : oldKeys.filter((key) => !(key in newObj)),
746
+ updated : newKeys.filter(
747
+ (key) => key in oldObj && oldObj[key] !== newObj[key]
748
+ ),
749
+ }
750
+ }
751
+ function hasOwnProperty(obj, prop) {
752
+ return Object.prototype.hasOwnProperty.call(obj, prop)
753
+ }
754
+
755
+ function isNotEmptyString(str) {
756
+ return str !== ''
757
+ }
758
+ function isNotBlankOrEmptyString(str) {
759
+ return isNotEmptyString(str.trim())
760
+ }
761
+
762
+ function patchDOM(oldVdom, newVdom, parentEl, hostComponent = null) {
763
+ if (!areNodesEqual(oldVdom, newVdom)) {
764
+ const index = findIndexInParent(parentEl, oldVdom.el);
765
+ destroyDom(oldVdom);
766
+ mountDom(newVdom, parentEl, index, hostComponent);
767
+ return newVdom
768
+ }
769
+ newVdom.el = oldVdom.el;
770
+ switch (newVdom.type) {
771
+ case DOM_TYPES.TEXT: {
772
+ patchText(oldVdom, newVdom);
773
+ return newVdom
774
+ }
775
+ case DOM_TYPES.ELEMENT: {
776
+ patchElement(oldVdom, newVdom, hostComponent);
777
+ break
778
+ }
779
+ case DOM_TYPES.COMPONENT: {
780
+ patchComponent(oldVdom, newVdom);
781
+ break
782
+ }
783
+ }
784
+ patchChildren(oldVdom, newVdom, hostComponent);
785
+ return newVdom
786
+ }
787
+ function patchText(oldVdom, newVdom) {
788
+ const el = oldVdom.el;
789
+ const { value : oldText} = oldVdom;
790
+ const { value : newText} = newVdom;
791
+ if (oldText !== newText) {
792
+ el.nodeValue = newText;
793
+ }
794
+ }
795
+ function findIndexInParent(parentEl, el) {
796
+ const index = Array.from(parentEl.childNodes).indexOf(el);
797
+ if (index < 0) {
798
+ return null
799
+ }
800
+ return index
801
+ }
802
+ function patchElement(oldVdom, newVdom, hostComponent) {
803
+ const el = oldVdom.el;
804
+ const {
805
+ class : oldClass,
806
+ style : oldStyle,
807
+ on: oldEvents,
808
+ ...oldAttrs
809
+ } = oldVdom.props;
810
+ const {
811
+ class: newClass,
812
+ style: newStyle,
813
+ on: newEvents,
814
+ ...newAttrs
815
+ } = newVdom.props;
816
+ const { listeners: oldListeners } = oldVdom;
817
+ patchAttrs(el, oldAttrs, newAttrs);
818
+ patchClasses(el, oldClass, newClass);
819
+ patchStyles(el, oldStyle, newStyle);
820
+ newVdom.listeners = patchEvents(
821
+ el,
822
+ oldListeners,
823
+ oldEvents,
824
+ newEvents,
825
+ hostComponent
826
+ );
827
+ }
828
+ function patchAttrs(el, oldAttrs, newAttrs) {
829
+ const { added, removed, updated } = objectsDiff(oldAttrs, newAttrs);
830
+ for (const attr of removed) {
831
+ removeAttributeCustom(el, attr);
832
+ }
833
+ for (const attr of added.concat(updated)) {
834
+ setAttribute(el, attr, newAttrs[attr]);
835
+ }
836
+ }
837
+ function patchClasses(el, oldClass, newClass) {
838
+ const oldClasses = toClassList(oldClass);
839
+ const newClasses = toClassList(newClass);
840
+ const {added, removed} =
841
+ arraysDiff(oldClasses, newClasses);
842
+ if (removed.length > 0) {
843
+ el.classList.remove(...removed);
844
+ }
845
+ if (added.length > 0) {
846
+ el.classList.add(...added);
847
+ }
848
+ }
849
+ function toClassList(classes = '') {
850
+ return Array.isArray(classes)
851
+ ? classes.filter(isNotBlankOrEmptyString)
852
+ : classes.split(/(\s+)/)
853
+ .filter(isNotBlankOrEmptyString)
854
+ }
855
+ function patchStyles(el, oldStyle = {}, newStyle = {}) {
856
+ const {added, removed, updated } = objectsDiff(oldStyle, newStyle);
857
+ for (const style of removed) {
858
+ removeStyle(el, style);
859
+ }
860
+ for (const style of added.concat(updated)) {
861
+ setStyle(el, style, newStyle[style]);
862
+ }
863
+ }
864
+ function patchEvents(
865
+ el,
866
+ oldListeners = {},
867
+ oldEvents = {},
868
+ newEvents = {},
869
+ hostComponent
870
+ ) {
871
+ const { removed, added, updated} =
872
+ objectsDiff(oldEvents, newEvents);
873
+ for (const eventName of removed.concat(updated)) {
874
+ el.removeEventListener(eventName, oldListeners[eventName]);
875
+ }
876
+ const addedListeners = {};
877
+ for (const eventName of added.concat(updated)) {
878
+ const listener = addEventListener(
879
+ eventName,
880
+ newEvents[eventName],
881
+ el,
882
+ hostComponent
883
+ );
884
+ addedListeners[eventName] = listener;
885
+ }
886
+ return addedListeners
887
+ }
888
+ function patchComponent(oldVdom, newVdom) {
889
+ const { component } = oldVdom;
890
+ const { children } = newVdom;
891
+ const { props } = extractPropsAndEvents(newVdom);
892
+ component.setExternalContent(children);
893
+ component.updateProps(props);
894
+ newVdom.component = component;
895
+ newVdom.el = component.firstElement;
896
+ }
897
+ function extractChildren(vdom) {
898
+ if (vdom.children == null) {
899
+ return []
900
+ }
901
+ const children = [];
902
+ for (const child of vdom.children) {
903
+ if (child.type === DOM_TYPES.FRAGMENT) {
904
+ children.push(...extractChildren(child));
905
+ } else {
906
+ children.push(child);
907
+ }
908
+ }
909
+ return children
910
+ }
911
+ function patchChildren(oldVdom, newVdom, hostComponent) {
912
+ const oldChildren = extractChildren(oldVdom);
913
+ const newChildren = extractChildren(newVdom);
914
+ const parentEl = oldVdom.el;
915
+ const diffSeq = arraysDiffSequence(oldChildren, newChildren, areNodesEqual);
916
+ for (const operation of diffSeq) {
917
+ const { originalIndex, index, item} = operation;
918
+ const offset = hostComponent?.offset ?? 0;
919
+ switch (operation.op) {
920
+ case ARRAY_DIFF_OP.ADD: {
921
+ mountDom(item, parentEl, index + offset, hostComponent);
922
+ break
923
+ }
924
+ case ARRAY_DIFF_OP.REMOVE: {
925
+ destroyDom(item);
926
+ break
927
+ }
928
+ case ARRAY_DIFF_OP.MOVE: {
929
+ const oldChild = oldChildren[originalIndex];
930
+ const newChild = newChildren[index];
931
+ const el = oldChild.el;
932
+ const elAtTargetIndex = parentEl.childNodes[index + offset];
933
+ parentEl.insertBefore(el, elAtTargetIndex);
934
+ patchDOM(oldChildren[originalIndex], newChild, parentEl, hostComponent);
935
+ break
936
+ }
937
+ case ARRAY_DIFF_OP.NOOP : {
938
+ patchDOM(oldChildren[originalIndex], newChildren[index], parentEl, hostComponent);
939
+ break
940
+ }
941
+ }
942
+ }
943
+ }
944
+
945
+ function getDefaultExportFromCjs (x) {
946
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
947
+ }
948
+
949
+ var fastDeepEqual;
950
+ var hasRequiredFastDeepEqual;
951
+ function requireFastDeepEqual () {
952
+ if (hasRequiredFastDeepEqual) return fastDeepEqual;
953
+ hasRequiredFastDeepEqual = 1;
954
+ fastDeepEqual = function equal(a, b) {
955
+ if (a === b) return true;
956
+ if (a && b && typeof a == 'object' && typeof b == 'object') {
957
+ if (a.constructor !== b.constructor) return false;
958
+ var length, i, keys;
959
+ if (Array.isArray(a)) {
960
+ length = a.length;
961
+ if (length != b.length) return false;
962
+ for (i = length; i-- !== 0;)
963
+ if (!equal(a[i], b[i])) return false;
964
+ return true;
965
+ }
966
+ if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
967
+ if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
968
+ if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
969
+ keys = Object.keys(a);
970
+ length = keys.length;
971
+ if (length !== Object.keys(b).length) return false;
972
+ for (i = length; i-- !== 0;)
973
+ if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
974
+ for (i = length; i-- !== 0;) {
975
+ var key = keys[i];
976
+ if (!equal(a[key], b[key])) return false;
977
+ }
978
+ return true;
979
+ }
980
+ return a!==a && b!==b;
981
+ };
982
+ return fastDeepEqual;
983
+ }
984
+
985
+ var fastDeepEqualExports = requireFastDeepEqual();
986
+ var equal = /*@__PURE__*/getDefaultExportFromCjs(fastDeepEqualExports);
987
+
988
+ function traverseDFS(
989
+ vdom,
990
+ processNode,
991
+ shouldSkipBranch = () => false,
992
+ parentNode,
993
+ index = null
994
+ ) {
995
+ if (shouldSkipBranch(vdom)) return
996
+ processNode(vdom, parentNode, index);
997
+ if (vdom.children) {
998
+ vdom.children.forEach((child, i) =>
999
+ traverseDFS(child, processNode, shouldSkipBranch, vdom, i)
1000
+ );
1001
+ }
1002
+ }
1003
+
1004
+ function fillSlots(vdom, externalContent = []) {
1005
+ function processNode(node, parent, index) {
1006
+ insertViewInSlot(node, parent, index, externalContent);
1007
+ }
1008
+ traverseDFS(vdom, processNode, shouldSkipBranch);
1009
+ }
1010
+ function insertViewInSlot(node, parent, index, externalContent) {
1011
+ if (node.type !== DOM_TYPES.SLOT) return
1012
+ const defaultContent = node.children;
1013
+ const views = externalContent.length > 0 ? externalContent : defaultContent;
1014
+ const hasContent = views.length > 0;
1015
+ if (hasContent) {
1016
+ parent.children.splice(index, 1, hFragment(views));
1017
+ } else {
1018
+ parent.children(index, 1);
1019
+ }
1020
+ }
1021
+ function shouldSkipBranch(node) {
1022
+ return node.type === DOM_TYPES.COMPONENT
1023
+ }
1024
+
1025
+ const emptyFn = () => {};
1026
+ function defineComponent({
1027
+ render,
1028
+ state,
1029
+ onMounted = emptyFn,
1030
+ onUnmounted = emptyFn,
1031
+ ...methods
1032
+ }) {
1033
+ class Component {
1034
+ #isMounted = false
1035
+ #vdom = null
1036
+ #hostEl = null
1037
+ #eventHandlers = null
1038
+ #parentComponent = null
1039
+ #dispatcher = new Dispatcher()
1040
+ #subscriptions = []
1041
+ #appContext = null
1042
+ #children = []
1043
+ setExternalContent(children) {
1044
+ this.#children = children;
1045
+ }
1046
+ constructor(
1047
+ props = {},
1048
+ eventHandlers = {},
1049
+ parentComponent = null
1050
+ ) {
1051
+ this.props = props;
1052
+ this.state = state ? state(props) : {};
1053
+ this.#eventHandlers = eventHandlers;
1054
+ this.#parentComponent = parentComponent;
1055
+ }
1056
+ onMounted() {
1057
+ return Promise.resolve(onMounted.call(this))
1058
+ }
1059
+ onUnMounted() {
1060
+ return Promise.resolve(onUnmounted.call(this))
1061
+ }
1062
+ setAppContext(appContext) {
1063
+ this.#appContext = appContext;
1064
+ }
1065
+ get appContext() {
1066
+ return this.#appContext
1067
+ }
1068
+ get elements() {
1069
+ if (this.#vdom == null) {
1070
+ return []
1071
+ }
1072
+ if (this.#vdom.type === DOM_TYPES.FRAGMENT) {
1073
+ return extractChildren(this.#vdom).flatMap((child) => {
1074
+ if (child.type === DOM_TYPES.COMPONENT) ;
1075
+ return [child.el]
1076
+ })
1077
+ }
1078
+ return [this.#vdom.el]
1079
+ }
1080
+ get firstElement() {
1081
+ return this.elements[0]
1082
+ }
1083
+ get offset() {
1084
+ if (this.#vdom.type === DOM_TYPES.ELEMENT) {
1085
+ return Array.from(this.#hostEl.children).indexOf(this.firstElement)
1086
+ }
1087
+ return 0
1088
+ }
1089
+ emit(eventName, payload) {
1090
+ this.#dispatcher.dispatch(eventName, payload);
1091
+ }
1092
+ updateProps(props) {
1093
+ //! creates a new props object by merging the old and new props
1094
+ const newProps = { ...this.props, ...props};
1095
+ if (equal(this.props, newProps)) {
1096
+ return
1097
+ }
1098
+ this.props = newProps;
1099
+ this.#patch();
1100
+ }
1101
+ updateState(state) {
1102
+ this.state = { ...this.state, ...state};
1103
+ this.#patch();
1104
+ }
1105
+ render() {
1106
+ const vdom = render.call(this);
1107
+ if (didCreateSlot()) {
1108
+ fillSlots(vdom, this.#children);
1109
+ resetDidCreateSlot();
1110
+ }
1111
+ return vdom
1112
+ }
1113
+ mount(hostEl, index = null) {
1114
+ if (this.#isMounted) {
1115
+ throw new Error('Component is already mounted')
1116
+ }
1117
+ this.#vdom = this.render();
1118
+ mountDom(this.#vdom, hostEl, index, this); //!this - passes the component reference to the mountDOM fucntion
1119
+ this.#wireEventHandlers();
1120
+ this.#hostEl = hostEl;
1121
+ this.#isMounted = true;
1122
+ }
1123
+ #patch() {
1124
+ if (!this.#isMounted) {
1125
+ throw new Error('Component is not mounted')
1126
+ }
1127
+ const vdom = this.render();
1128
+ this.#vdom = patchDOM(this.#vdom, vdom, this.#hostEl, this);
1129
+ }
1130
+ unmount() {
1131
+ if (!this.#isMounted) {
1132
+ throw new Error(`Component is not mounted`)
1133
+ }
1134
+ destroyDom(this.#vdom);
1135
+ this.#subscriptions.forEach((unsubscribe) => unsubscribe());
1136
+ this.#vdom = null;
1137
+ this.#hostEl = null;
1138
+ this.#isMounted = false;
1139
+ this.#subscriptions = [];
1140
+ }
1141
+ #wireEventHandlers() {
1142
+ this.#subscriptions = Object.entries(this.#eventHandlers).map(
1143
+ ([eventName, handler]) => {
1144
+ this.#wireEventHandlers(eventName, handler);
1145
+ }
1146
+ );
1147
+ }
1148
+ #wireEventHandler(eventName, handler) {
1149
+ return this.#dispatcher.subscribe(eventName, (payload) => {
1150
+ if (this.#parentComponent) {
1151
+ handler.call(this.#parentComponent, payload);
1152
+ } else {
1153
+ handler(payload);
1154
+ }
1155
+ })
1156
+ }
1157
+ }
1158
+ for (const methodName in methods) {
1159
+ if (hasOwnProperty(Component, methodName)) {
1160
+ throw new Error(
1161
+ `Method "${methodName}()" already exists in the component.`
1162
+ )
1163
+ }
1164
+ Component.prototype[methodName] = methods[methodName];
1165
+ }
1166
+ return Component;
1167
+ }
1168
+
1169
+ const RouterLink = defineComponent({
1170
+ render() {
1171
+ const { to } = this.props;
1172
+ return h(
1173
+ 'a',
1174
+ {
1175
+ href : to,
1176
+ on : {
1177
+ click : (e) => {
1178
+ e.preventDefault();
1179
+ this.appContext.router.navigateTo(to);
1180
+ },
1181
+ },
1182
+ },
1183
+ [hSlot()]
1184
+ )
1185
+ },
1186
+ });
1187
+ const RouterOutlet = defineComponent({
1188
+ state() {
1189
+ return {
1190
+ matchedRoute: null,
1191
+ subscription : null,
1192
+ }
1193
+ },
1194
+ onMounted() {
1195
+ const subscription = this.appContext.router.subscribe(({ to }) => {
1196
+ this.handleRouteChange(to);
1197
+ });
1198
+ this.updateState({ subscription });
1199
+ },
1200
+ onUnmounted() {
1201
+ const { subscription } = this.state();
1202
+ this.appContext.router.unsubscribe(subscription);
1203
+ },
1204
+ handleRouteChange(matchedRoute) {
1205
+ this.updateState({ matchedRoute});
1206
+ },
1207
+ render() {
1208
+ const { matchedRoute } = this.state;
1209
+ return h('div', {id : 'router-outlet'}, [
1210
+ matchedRoute ? h(matchedRoute.component) : null,
1211
+ ])
1212
+ }
1213
+ });
1214
+
1215
+ export { DOM_TYPES, HashRouter, RouterLink, RouterOutlet, createApp, defineComponent, h, hFragment, hSlot, hString, nextTick };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lahama",
3
- "version": "4.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "",
5
5
  "main": "dist/lahama.js",
6
6
  "files": [