lahama 2.1.0 → 2.3.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 +348 -3
  2. package/package.json +1 -1
package/dist/lahama.js CHANGED
@@ -1,9 +1,151 @@
1
1
  function withoutNulls(arr) {
2
2
  return arr.filter((item) => item != null)
3
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
+ };
4
20
  const a = { };
5
21
  const b = { };
6
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
+ }
7
149
 
8
150
  const DOM_TYPES = {
9
151
  TEXT : 'text',
@@ -32,14 +174,14 @@ function hFragment(vNodes) {
32
174
  }
33
175
  }
34
176
 
35
- function addEventListener(eventName, handler, el) {
177
+ function addEventListener$1(eventName, handler, el) {
36
178
  el.addEventListener(eventName, handler);
37
179
  return handler
38
180
  }
39
181
  function addEventListeners(listeners = {}, el) {
40
182
  const addedListeners = {};
41
183
  Object.entries(listeners).forEach(([eventName, handler]) => {
42
- addedListeners[eventName] = addEventListener(eventName, handler, el);
184
+ addedListeners[eventName] = addEventListener$1(eventName, handler, el);
43
185
  });
44
186
  return addedListeners
45
187
  }
@@ -114,6 +256,9 @@ function setClass(el, className) {
114
256
  function setStyle(el, name, value) {
115
257
  el.style[name] = value;
116
258
  }
259
+ function removeStyle(el, name) {
260
+ el.style[name] = null;
261
+ }
117
262
  function removeAttributeCustom(el, name) {
118
263
  el[name] = null;
119
264
  el.removeAttribute(name);
@@ -224,6 +369,206 @@ class Dispatcher {
224
369
  }
225
370
  }
226
371
 
372
+ function areNodesEqual(nodeOne, nodeTwo) {
373
+ if (nodeOne.type !== nodeTwo.type) {
374
+ return false
375
+ }
376
+ if (nodeOne.type === DOM_TYPES.ELEMENT) {
377
+ const { tag : tagOne } = nodeOne;
378
+ const { tag : tagTwo} = nodeTwo;
379
+ return tagOne === tagTwo
380
+ }
381
+ return true
382
+ }
383
+
384
+ function objectsDiff(oldObj, newObj) {
385
+ const oldKeys = Object.keys(oldObj);
386
+ const newKeys = Object.keys(newObj);
387
+ return {
388
+ added : newKeys.filter((key) => !(key in oldObj)),
389
+ removed : oldKeys.filter((key) => !(key in newObj)),
390
+ updated : newKeys.filter(
391
+ (key) => key in oldObj && oldObj[key] !== newObj[key]
392
+ ),
393
+ }
394
+ }
395
+
396
+ function isNotEmptyString(str) {
397
+ return str !== ''
398
+ }
399
+ function isNotBlankOrEmptyString(str) {
400
+ return isNotEmptyString(str.trim())
401
+ }
402
+
403
+ function patchDOM(oldVdom, newVdom, parentEl) {
404
+ if (!areNodesEqual(oldVdom, newVdom)) {
405
+ const index = findIndexInParent(parentEl, oldVdom.el);
406
+ destroyDom(oldVdom);
407
+ mountDom(newVdom, parentEl, index);
408
+ return newVdom
409
+ }
410
+ newVdom.el = oldVdom.el;
411
+ switch (newVdom.type) {
412
+ case DOM_TYPES.TEXT : {
413
+ patchText(oldVdom, newVdom);
414
+ return newVdom
415
+ }
416
+ case DOM_TYPES.ELEMENT : {
417
+ patchElement(oldVdom, newVdom);
418
+ break
419
+ }
420
+ }
421
+ patchChildren(oldVdom, newVdom);
422
+ return newVdom
423
+ }
424
+ function patchText(oldVdom, newVdom) {
425
+ const el = oldVdom.el;
426
+ const { value : oldText} = oldVdom;
427
+ const { value : newText} = newVdom;
428
+ if (oldText !== newText) {
429
+ el.nodeValue = newText;
430
+ }
431
+ }
432
+ function findIndexInParent(parentEl, el) {
433
+ const index = Array.from(parentEl.childNodes).indexOf(el);
434
+ if (index < 0) {
435
+ return null
436
+ }
437
+ return index
438
+ }
439
+ function patchElement(oldVdom, newVdom) {
440
+ const el = oldVdom.el;
441
+ const {
442
+ class : oldClass,
443
+ style : oldStyle,
444
+ on : oldEvents,
445
+ ...oldAttrs
446
+ } = oldVdom.props;
447
+ const {
448
+ class : newClass,
449
+ style : newStyle,
450
+ on : newEvents,
451
+ ...newAttrs
452
+ } = newVdom.props;
453
+ const { listeners : oldListeners } = oldVdom;
454
+ patchAttrs(el, oldAttrs, newAttrs);
455
+ patchClasses(el, oldClass, newClass);
456
+ patchStyles(el, oldStyle, newStyle);
457
+ newVdom.listeners = patchEvents(el, oldListeners, oldEvents, newEvents);
458
+ }
459
+ function patchAttrs(el, oldAttrs, newAttrs) {
460
+ const { added, removed, updated } = objectsDiff(oldAttrs, newAttrs);
461
+ for (const attr of removed) {
462
+ removeAttributeCustom(el, attr);
463
+ }
464
+ for (const attr of added.concat(updated)) {
465
+ setAttribute(el, attr, newAttrs[attr]);
466
+ }
467
+ }
468
+ function patchClasses(el, oldClass, newClass) {
469
+ const oldClasses = toClassList(oldClass);
470
+ const newClasses = toClassList(newClass);
471
+ const {added, removed} =
472
+ arraysDiff(oldClasses, newClasses);
473
+ if (removed.length > 0) {
474
+ el.classList.remove(...removed);
475
+ }
476
+ if (added.length > 0) {
477
+ el.classList.add(...added);
478
+ }
479
+ }
480
+ function toClassList(classes = '') {
481
+ return Array.isArray(classes)
482
+ ? classes.filter(isNotBlankOrEmptyString)
483
+ : classes.split(/(\s+)/)
484
+ .filter(isNotBlankOrEmptyString)
485
+ }
486
+ function patchStyles(el, oldStyle = {}, newStyle = {}) {
487
+ const {added, removed, updated } = objectsDiff(oldStyle, newStyle);
488
+ for (const style of removed) {
489
+ removeStyle(el, style);
490
+ }
491
+ for (const style of added.concat(updated)) {
492
+ setStyle(el, style, newStyle[style]);
493
+ }
494
+ }
495
+ function addEventListener(eventName, handler, el) {
496
+ function boundHandler(event) {
497
+ handler(event);
498
+ }
499
+ el.addEventListener(eventName, boundHandler);
500
+ //!el.addEventListener("click", boundHandler) {
501
+ //! Element: el
502
+ //! Event: "click"
503
+ //! Listener : boundHandler!}
504
+ return boundHandler
505
+ }
506
+ function patchEvents(
507
+ el,
508
+ oldListeners = {},
509
+ oldEvents = {},
510
+ newEvents = {},
511
+ ) {
512
+ const { removed, added, updated} =
513
+ objectsDiff(oldEvents, newEvents);
514
+ for (const eventName of removed.concat(updated)) {
515
+ el.removeEventListener(eventName, oldListeners[eventName]);
516
+ }
517
+ const addedListeners = {};
518
+ for (const eventName of added.concat(updated)) {
519
+ const listener =
520
+ addEventListener(eventName, newEvents[eventName], el);
521
+ addedListeners[eventName] = listener;
522
+ }
523
+ return addedListeners
524
+ }
525
+ function extractChildren(vdom) {
526
+ if (vdom.children == null) {
527
+ return []
528
+ }
529
+ const children = [];
530
+ for (const child of vdom.children) {
531
+ if (child.type === DOM_TYPES.FRAGMENT) {
532
+ children.push(...extractChildren(child));
533
+ } else {
534
+ children.push(child);
535
+ }
536
+ }
537
+ return children
538
+ }
539
+ function patchChildren(oldVdom, newVdom) {
540
+ const oldChildren = extractChildren(oldVdom);
541
+ const newChildren = extractChildren(newVdom);
542
+ const parentEl = oldVdom.el;
543
+ const diffSeq = arraysDiffSequence(oldChildren, newChildren, areNodesEqual);
544
+ for (const operation of diffSeq) {
545
+ const { originalIndex, index, item} = operation;
546
+ switch (operation.op) {
547
+ case ARRAY_DIFF_OP.ADD: {
548
+ mountDom(item, parentEl, index);
549
+ break
550
+ }
551
+ case ARRAY_DIFF_OP.REMOVE: {
552
+ destroyDom(item);
553
+ break
554
+ }
555
+ case ARRAY_DIFF_OP.MOVE: {
556
+ const oldChild = oldChildren[originalIndex];
557
+ const newChild = newChildren[index];
558
+ const el = oldChild.el;
559
+ const elAtTargetIndex = parentEl.childNodes[index];
560
+ parentEl.insertBefore(el, elAtTargetIndex);
561
+ patchDOM(oldChildren[originalIndex], newChild, parentEl);
562
+ break
563
+ }
564
+ case ARRAY_DIFF_OP.NOOP : {
565
+ patchDOM(oldChildren[originalIndex], newChildren[index], parentEl);
566
+ break
567
+ }
568
+ }
569
+ }
570
+ }
571
+
227
572
  function createApp({ state, view, reducers = {} }) {
228
573
  let parentEl = null;
229
574
  let vdom = null;
@@ -242,7 +587,7 @@ function createApp({ state, view, reducers = {} }) {
242
587
  }
243
588
  function renderApp() {
244
589
  const newVdom = view(state, emit);
245
- vdom = patchDom(vdom, newVdom, parentEl);
590
+ vdom = patchDOM(vdom, newVdom, parentEl);
246
591
  }
247
592
  return {
248
593
  mount(_parentEl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lahama",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "",
5
5
  "main": "dist/lahama.js",
6
6
  "files": [