my-framework-almaz 1.0.0 → 2.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.
- package/dist/my-framework-almaz.js +362 -19
- package/package.json +1 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
function addEventListener(eventName, handler, el) {
|
|
2
|
-
|
|
1
|
+
function addEventListener$1(eventName, handler, el) {
|
|
2
|
+
function boundHandler(event) {
|
|
3
|
+
handler(event);
|
|
4
|
+
}
|
|
5
|
+
el.addEventListener(eventName, boundHandler);
|
|
3
6
|
return handler
|
|
4
7
|
}
|
|
5
8
|
function addEventListeners(listeners = {}, el) {
|
|
6
9
|
const addedListeners = {};
|
|
7
10
|
Object.entries(listeners).forEach(([eventName, handler]) => {
|
|
8
|
-
const listener = addEventListener(eventName, handler, el);
|
|
11
|
+
const listener = addEventListener$1(eventName, handler, el);
|
|
9
12
|
addedListeners[eventName] = listener;
|
|
10
13
|
});
|
|
11
14
|
return addedListeners
|
|
@@ -19,6 +22,138 @@ function removeEventListeners(listeners, el) {
|
|
|
19
22
|
function withoutNulls(arr) {
|
|
20
23
|
return arr.filter((item) => item != null)
|
|
21
24
|
}
|
|
25
|
+
function arraysDiff(oldArray, newArray) {
|
|
26
|
+
return {
|
|
27
|
+
added: newArray.filter((newItem) => !oldArray.includes(newItem)),
|
|
28
|
+
removed: oldArray.filter((oldItem) => !newArray.includes(oldItem)),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const ARRAY_DIFF_OP = {
|
|
32
|
+
ADD: 'add',
|
|
33
|
+
REMOVE: 'remove',
|
|
34
|
+
MOVE: 'move',
|
|
35
|
+
NOOP: 'noop',
|
|
36
|
+
};
|
|
37
|
+
class ArrayWithOriginalIndices {
|
|
38
|
+
#array = []
|
|
39
|
+
#originalIndices = []
|
|
40
|
+
#equalsFn
|
|
41
|
+
constructor(array, equalsFn) {
|
|
42
|
+
this.#array = [...array];
|
|
43
|
+
this.#originalIndices = array.map((_, i) => i);
|
|
44
|
+
this.#equalsFn = equalsFn;
|
|
45
|
+
}
|
|
46
|
+
get length() {
|
|
47
|
+
return this.#array.length
|
|
48
|
+
}
|
|
49
|
+
isRemoval(index, newArry) {
|
|
50
|
+
if (index >= this.length) {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
const item = this.#array[index];
|
|
54
|
+
const indexInNewArray = newArry.findIndex((newItem) =>
|
|
55
|
+
this.#equalsFn(item, newItem)
|
|
56
|
+
);
|
|
57
|
+
return indexInNewArray === -1
|
|
58
|
+
}
|
|
59
|
+
removeItem(index) {
|
|
60
|
+
const operation = {
|
|
61
|
+
op: ARRAY_DIFF_OP.REMOVE,
|
|
62
|
+
index,
|
|
63
|
+
item: this.#array[index],
|
|
64
|
+
};
|
|
65
|
+
this.#array.splice(index, 1);
|
|
66
|
+
this.#originalIndices.splice(index, 1);
|
|
67
|
+
return operation
|
|
68
|
+
}
|
|
69
|
+
isNoop(index, newArray) {
|
|
70
|
+
if (index >= this.length) return false
|
|
71
|
+
const item = this.#array[index];
|
|
72
|
+
const newItem = newArray[index];
|
|
73
|
+
return this.#equalsFn(item, newItem)
|
|
74
|
+
}
|
|
75
|
+
originalIndexAt(index) {
|
|
76
|
+
return this.#originalIndices[index]
|
|
77
|
+
}
|
|
78
|
+
noopItem(index) {
|
|
79
|
+
return {
|
|
80
|
+
op: ARRAY_DIFF_OP.NOOP,
|
|
81
|
+
originalIndex: originalIndexAt(index),
|
|
82
|
+
index,
|
|
83
|
+
item: this.#array[index],
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
isAddition(item, fromIdx) {
|
|
87
|
+
return this.findIndexForm(item, fromIdx) === -1
|
|
88
|
+
}
|
|
89
|
+
findIndexForm(item, fromIndex) {
|
|
90
|
+
for (let i = fromIndex; i < this.length; i++) {
|
|
91
|
+
if (this.#equalsFn(item, this.#array[i])) {
|
|
92
|
+
return i
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return -1
|
|
96
|
+
}
|
|
97
|
+
addItem(item, index) {
|
|
98
|
+
const operation = {
|
|
99
|
+
op: ARRAY_DIFF_OP.ADD,
|
|
100
|
+
index,
|
|
101
|
+
item,
|
|
102
|
+
};
|
|
103
|
+
this.#array.splice(index, 0, item);
|
|
104
|
+
this.#originalIndices.splice(index, 0, -1);
|
|
105
|
+
return operation
|
|
106
|
+
}
|
|
107
|
+
moveItem(item, toIndex) {
|
|
108
|
+
const fromIndex = findIndexForm(item, toIndex);
|
|
109
|
+
const operation = {
|
|
110
|
+
op: ARRAY_DIFF_OP.MOVE,
|
|
111
|
+
originalIndex: this.originalIndexAt(fromIndex),
|
|
112
|
+
index: toIndex,
|
|
113
|
+
fromIndex: fromIndex,
|
|
114
|
+
item,
|
|
115
|
+
};
|
|
116
|
+
const [_item] = this.#array.splice(fromIndex, 1);
|
|
117
|
+
this.#array.splice(toIndex, 0, _item);
|
|
118
|
+
const [originalIndex] = this.#originalIndices.splice(fromIndex, 1);
|
|
119
|
+
this.#originalIndices.splice(toIndex, 0, originalIndex);
|
|
120
|
+
return operation
|
|
121
|
+
}
|
|
122
|
+
removeItemAfter(index) {
|
|
123
|
+
const operations = [];
|
|
124
|
+
while (this.length > index) {
|
|
125
|
+
operations.push(this.removeItem(index));
|
|
126
|
+
}
|
|
127
|
+
return operations
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function arraysDiffSequence(
|
|
131
|
+
oldArray,
|
|
132
|
+
newArray,
|
|
133
|
+
equalsFn = (a, b) => a === b
|
|
134
|
+
) {
|
|
135
|
+
const sequence = [];
|
|
136
|
+
const array = new ArrayWithOriginalIndices(oldArray, equalsFn);
|
|
137
|
+
for (let index = 0; index < newArray.length; index++) {
|
|
138
|
+
if (array.isRemoval(index, newArray)) {
|
|
139
|
+
sequence.push(array.removeItem(index));
|
|
140
|
+
index--;
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
if (array.isNoop(index, newArray)) {
|
|
144
|
+
sequence.push(array.noopItem(index));
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
const item = newArray[index];
|
|
148
|
+
if (array.isAddition(item, index)) {
|
|
149
|
+
sequence.push(array.addItem(item, index));
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
sequence.push(array.moveItem(item, index));
|
|
153
|
+
}
|
|
154
|
+
sequence.push(...array.removeItemAfter(newArray.length));
|
|
155
|
+
return sequence
|
|
156
|
+
}
|
|
22
157
|
|
|
23
158
|
const DOM_TYPES = {
|
|
24
159
|
TEXT: 'text',
|
|
@@ -47,6 +182,20 @@ function mapTextNodes(children) {
|
|
|
47
182
|
typeof child === 'string' ? hString(child) : child
|
|
48
183
|
)
|
|
49
184
|
}
|
|
185
|
+
function extraChildren(vdom) {
|
|
186
|
+
if (vdom.children == null) {
|
|
187
|
+
return []
|
|
188
|
+
}
|
|
189
|
+
const children = [];
|
|
190
|
+
for (const child of vdom.children) {
|
|
191
|
+
if (child.type === DOM_TYPES.FRAGMENT) {
|
|
192
|
+
children.push(...extraChildren(child));
|
|
193
|
+
} else {
|
|
194
|
+
children.push(child);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return children
|
|
198
|
+
}
|
|
50
199
|
|
|
51
200
|
function destroyDOM(vdom) {
|
|
52
201
|
const { type } = vdom;
|
|
@@ -147,6 +296,9 @@ function setClass(el, className) {
|
|
|
147
296
|
function setStyle(el, name, value) {
|
|
148
297
|
el.style[name] = value;
|
|
149
298
|
}
|
|
299
|
+
function removeStyle(el, name) {
|
|
300
|
+
el.style[name] = null;
|
|
301
|
+
}
|
|
150
302
|
function setAttribute(el, name, value) {
|
|
151
303
|
if (value === null) {
|
|
152
304
|
removeAttribute(el, value);
|
|
@@ -161,18 +313,18 @@ function removeAttribute(el, name) {
|
|
|
161
313
|
el.removeAttribute(name);
|
|
162
314
|
}
|
|
163
315
|
|
|
164
|
-
function mountDOM(vdom, parentEl) {
|
|
316
|
+
function mountDOM(vdom, parentEl, index) {
|
|
165
317
|
switch (vdom.type) {
|
|
166
318
|
case DOM_TYPES.TEXT: {
|
|
167
|
-
createTextNode(vdom, parentEl);
|
|
319
|
+
createTextNode(vdom, parentEl, index);
|
|
168
320
|
break
|
|
169
321
|
}
|
|
170
322
|
case DOM_TYPES.ELEMENT: {
|
|
171
|
-
createElementNode(vdom, parentEl);
|
|
323
|
+
createElementNode(vdom, parentEl, index);
|
|
172
324
|
break
|
|
173
325
|
}
|
|
174
326
|
case DOM_TYPES.FRAGMENT: {
|
|
175
|
-
createFragmentNodes(vdom, parentEl);
|
|
327
|
+
createFragmentNodes(vdom, parentEl, index);
|
|
176
328
|
break
|
|
177
329
|
}
|
|
178
330
|
default: {
|
|
@@ -180,24 +332,41 @@ function mountDOM(vdom, parentEl) {
|
|
|
180
332
|
}
|
|
181
333
|
}
|
|
182
334
|
}
|
|
183
|
-
function
|
|
335
|
+
function insert(el, parentEl, index) {
|
|
336
|
+
if (index == null) {
|
|
337
|
+
parentEl.append(el);
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
if (index < 0) {
|
|
341
|
+
throw new Error(`Index must be a positive integer, got ${index}`)
|
|
342
|
+
}
|
|
343
|
+
const children = parentEl.childNodes;
|
|
344
|
+
if (index >= children.length) {
|
|
345
|
+
parentEl.append(el);
|
|
346
|
+
} else {
|
|
347
|
+
parentEl.insertBefore(el, children[index]);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function createTextNode(vdom, parentEl, index) {
|
|
184
351
|
const { value } = vdom;
|
|
185
352
|
const textNode = document.createTextNode(value);
|
|
186
353
|
vdom.el = textNode;
|
|
187
|
-
|
|
354
|
+
insert(textNode, parentEl, index);
|
|
188
355
|
}
|
|
189
|
-
function createFragmentNodes(vdom, parentEl) {
|
|
356
|
+
function createFragmentNodes(vdom, parentEl, index) {
|
|
190
357
|
const { children } = vdom;
|
|
191
358
|
vdom.el = parentEl;
|
|
192
|
-
children.forEach((child) =>
|
|
359
|
+
children.forEach((child, i) =>
|
|
360
|
+
mountDOM(child, parentEl, index ? index + i : null)
|
|
361
|
+
);
|
|
193
362
|
}
|
|
194
|
-
function createElementNode(vdom, parentEl) {
|
|
363
|
+
function createElementNode(vdom, parentEl, index) {
|
|
195
364
|
const { tag, props, children } = vdom;
|
|
196
365
|
const element = document.createElement(tag);
|
|
197
366
|
addProps(element, props, vdom);
|
|
198
367
|
vdom.el = element;
|
|
199
368
|
children.forEach((child) => mountDOM(child, element));
|
|
200
|
-
|
|
369
|
+
insert(element, parentEl, index);
|
|
201
370
|
}
|
|
202
371
|
function addProps(el, props, vdom) {
|
|
203
372
|
const { on: events, ...attrs } = props;
|
|
@@ -205,6 +374,181 @@ function addProps(el, props, vdom) {
|
|
|
205
374
|
setAttributes(el, attrs);
|
|
206
375
|
}
|
|
207
376
|
|
|
377
|
+
function areNodesEqual(nodeOne, nodeTwo) {
|
|
378
|
+
if (nodeOne.type !== nodeTwo.type) {
|
|
379
|
+
return false
|
|
380
|
+
}
|
|
381
|
+
if (nodeOne.type === DOM_TYPES.ELEMENT) {
|
|
382
|
+
const { tag: tagOne } = nodeOne;
|
|
383
|
+
const { tag: tagTwo } = nodeTwo;
|
|
384
|
+
return tagOne === tagTwo
|
|
385
|
+
}
|
|
386
|
+
return true
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function objectsDiff(oldObj, newObj) {
|
|
390
|
+
const oldKeys = Object.keys(oldObj);
|
|
391
|
+
const newKeys = Object.keys(newObj);
|
|
392
|
+
return {
|
|
393
|
+
added: newKeys.filter((key) => !(key in oldObj)),
|
|
394
|
+
removed: oldKeys.filter((key) => !(key in newObj)),
|
|
395
|
+
updated: newKeys.filter(
|
|
396
|
+
(key) => key in oldObj && oldObj[key] !== newObj[key]
|
|
397
|
+
),
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function isNotEmptyString(str) {
|
|
402
|
+
return str !== ''
|
|
403
|
+
}
|
|
404
|
+
function isNotBlankOrEmptyString(str) {
|
|
405
|
+
isNotEmptyString(str.trim());
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function patchDOM(oldVdom, newVdom, parentEl) {
|
|
409
|
+
if (!areNodesEqual(oldVdom, newVdom)) {
|
|
410
|
+
const index = findIndexInParent(parentEl, oldVdom.el);
|
|
411
|
+
destroyDOM(oldVdom);
|
|
412
|
+
mountDOM(newVdom, parentEl, index);
|
|
413
|
+
return newVdom
|
|
414
|
+
}
|
|
415
|
+
newVdom.el = oldVdom.el;
|
|
416
|
+
switch (newVdom.type) {
|
|
417
|
+
case DOM_TYPES.TEXT: {
|
|
418
|
+
patchText(oldVdom, newVdom);
|
|
419
|
+
return newVdom
|
|
420
|
+
}
|
|
421
|
+
case DOM_TYPES.ELEMENT: {
|
|
422
|
+
patchElement(oldVdom, newVdom);
|
|
423
|
+
break
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
patchChildren(oldVdom, newVdom);
|
|
427
|
+
return newVdom
|
|
428
|
+
}
|
|
429
|
+
function findIndexInParent(parentEl, el) {
|
|
430
|
+
const index = Array.from(parentEl.childNodes).indexOf(el);
|
|
431
|
+
if (index < 0) {
|
|
432
|
+
return null
|
|
433
|
+
}
|
|
434
|
+
return index
|
|
435
|
+
}
|
|
436
|
+
function patchText(oldVdom, newVdom) {
|
|
437
|
+
const el = oldVdom.el;
|
|
438
|
+
const { value: oldText } = oldVdom;
|
|
439
|
+
const { value: newText } = newVdom;
|
|
440
|
+
if (oldText !== newText) {
|
|
441
|
+
el.nodeValue = newText;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function patchElement(oldVdom, newVdom) {
|
|
445
|
+
const el = oldVdom.el;
|
|
446
|
+
const {
|
|
447
|
+
class: oldCalss,
|
|
448
|
+
style: oldStyle,
|
|
449
|
+
on: oldEvents,
|
|
450
|
+
...oldAttrs
|
|
451
|
+
} = oldVdom.props;
|
|
452
|
+
const {
|
|
453
|
+
class: newCalss,
|
|
454
|
+
style: newStyle,
|
|
455
|
+
on: newEvents,
|
|
456
|
+
...newAttrs
|
|
457
|
+
} = newVdom.props;
|
|
458
|
+
const { listeners: oldListeners } = vdom;
|
|
459
|
+
patchAttrs(el, oldAttrs, newAttrs);
|
|
460
|
+
patchCalsses(el, oldCalss, newCalss);
|
|
461
|
+
patchStyles(el, oldStyle, newStyle);
|
|
462
|
+
newVdom.listeners = patchEvents(el, oldListeners, oldEvents, newEvents);
|
|
463
|
+
}
|
|
464
|
+
function patchAttrs(el, oldAttrs, newAttrs) {
|
|
465
|
+
const { added, removed, updated } = objectsDiff(oldAttrs, newAttrs);
|
|
466
|
+
for (const attr of removed) {
|
|
467
|
+
removeAttribute(el, attr);
|
|
468
|
+
}
|
|
469
|
+
for (const attr of added.concat(updated)) {
|
|
470
|
+
setAttribute(el, attr, newAttrs[attr]);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function patchCalsses(el, oldClass, newClass) {
|
|
474
|
+
const oldClasses = toClassList(oldClass);
|
|
475
|
+
const newClasses = toClassList(newClass);
|
|
476
|
+
const { added, removed } = arraysDiff(oldClasses, newClasses);
|
|
477
|
+
if (removed.length > 0) {
|
|
478
|
+
el.classList.remove(...removed);
|
|
479
|
+
}
|
|
480
|
+
if (added.length > 0) {
|
|
481
|
+
el.classList.add(...added);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function patchStyles(el, oldStyle = {}, newStyle = {}) {
|
|
485
|
+
const { added, removed, updated } = objectsDiff(oldStyle, newStyle);
|
|
486
|
+
for (const style of removed) {
|
|
487
|
+
removeStyle(el, style);
|
|
488
|
+
}
|
|
489
|
+
for (const style of added.concat(updated)) {
|
|
490
|
+
setStyle(el, style, newStyle[style]);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function patchEvents(
|
|
494
|
+
el,
|
|
495
|
+
oldListeners = {},
|
|
496
|
+
oldEvents = {},
|
|
497
|
+
newEvents = {}
|
|
498
|
+
) {
|
|
499
|
+
const { removed, added, updated } = objectsDiff(oldEvents, newEvents);
|
|
500
|
+
for (const eventName of removed.concat(updated)) {
|
|
501
|
+
el.removeEventListener(eventName, oldListeners[eventName]);
|
|
502
|
+
}
|
|
503
|
+
const addedListeneres = {};
|
|
504
|
+
for (const eventName of added.concat(updated)) {
|
|
505
|
+
const listener = addEventListener(eventName, newEvents[eventName], el);
|
|
506
|
+
addedListeneres[eventName] = listener;
|
|
507
|
+
}
|
|
508
|
+
return addedListeneres
|
|
509
|
+
}
|
|
510
|
+
function toClassList(classes = '') {
|
|
511
|
+
return Array.isArray(classes)
|
|
512
|
+
? classes.filter(isNotBlankOrEmptyString)
|
|
513
|
+
: classes.split(/(\s+)/).filter(isNotBlankOrEmptyString)
|
|
514
|
+
}
|
|
515
|
+
function patchChildren(oldVdom, newVdom) {
|
|
516
|
+
const oldChildren = extraChildren(oldVdom);
|
|
517
|
+
const newChildren = extraChildren(newVdom);
|
|
518
|
+
const parentEl = oldVdom.el;
|
|
519
|
+
const diffSeq = arraysDiffSequence(
|
|
520
|
+
oldChildren,
|
|
521
|
+
newChildren,
|
|
522
|
+
areNodesEqual
|
|
523
|
+
);
|
|
524
|
+
for (const operation of diffSeq) {
|
|
525
|
+
const { originalIndex, index, item } = operation;
|
|
526
|
+
switch (operation.op) {
|
|
527
|
+
case ARRAY_DIFF_OP.ADD: {
|
|
528
|
+
mountDOM(item, parentEl, index);
|
|
529
|
+
break
|
|
530
|
+
}
|
|
531
|
+
case ARRAY_DIFF_OP.REMOVE: {
|
|
532
|
+
destroyDOM(item);
|
|
533
|
+
break
|
|
534
|
+
}
|
|
535
|
+
case ARRAY_DIFF_OP.MOVE: {
|
|
536
|
+
const oldChild = oldChildren[originalIndex];
|
|
537
|
+
const newChild = newChildren[index];
|
|
538
|
+
const el = oldChild.el;
|
|
539
|
+
const elAtTargetIndex = parentEl.childNodes[index];
|
|
540
|
+
parentEl.insertBefore(el, elAtTargetIndex);
|
|
541
|
+
patchDOM(oldChild, newChild, parentEl);
|
|
542
|
+
break
|
|
543
|
+
}
|
|
544
|
+
case ARRAY_DIFF_OP.NOOP: {
|
|
545
|
+
patchDOM(oldChildren[originalIndex], newChildren[index], parentEl);
|
|
546
|
+
break
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
208
552
|
function createApp({ state, view, reducers = {} }) {
|
|
209
553
|
let parentEl = null;
|
|
210
554
|
let vdom = null;
|
|
@@ -221,16 +565,15 @@ function createApp({ state, view, reducers = {} }) {
|
|
|
221
565
|
subscriptions.push(subs);
|
|
222
566
|
}
|
|
223
567
|
function renderApp() {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
vdom = view(state, emit);
|
|
228
|
-
mountDOM(vdom, parentEl);
|
|
568
|
+
const newVdom = view(state, emit);
|
|
569
|
+
vdom = patchDOM(vdom, newVdom, parentEl);
|
|
229
570
|
}
|
|
230
571
|
return {
|
|
231
572
|
mount(_parentEl) {
|
|
232
573
|
parentEl = _parentEl;
|
|
233
|
-
|
|
574
|
+
if (vdom) return
|
|
575
|
+
vdom = view(state, emit);
|
|
576
|
+
mountDOM(vdom, parentEl);
|
|
234
577
|
},
|
|
235
578
|
unmount() {
|
|
236
579
|
destroyDOM(vdom);
|