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