lahama 2.5.2 → 4.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/lahama.js +136 -447
- package/package.json +6 -1
package/dist/lahama.js
CHANGED
|
@@ -1,163 +1,26 @@
|
|
|
1
|
+
import 'vitest/dist/chunks/reporters.d.OXEK7y4s.d.ts';
|
|
2
|
+
|
|
1
3
|
function withoutNulls(arr) {
|
|
2
4
|
return arr.filter((item) => item != null)
|
|
3
5
|
}
|
|
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
|
-
};
|
|
20
6
|
const a = { };
|
|
21
7
|
const b = { };
|
|
22
8
|
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
|
-
}
|
|
149
9
|
|
|
150
10
|
const DOM_TYPES = {
|
|
151
11
|
TEXT : 'text',
|
|
152
12
|
ELEMENT : 'element',
|
|
153
13
|
FRAGMENT : 'fragment',
|
|
14
|
+
COMPONENT : 'component',
|
|
154
15
|
};
|
|
155
16
|
function h(tag, props = {} , children = []) {
|
|
17
|
+
const type =
|
|
18
|
+
typeof tag === 'string' ? DOM_TYPES.ELEMENT : DOM_TYPES.COMPONENT;
|
|
156
19
|
return {
|
|
157
20
|
tag,
|
|
158
21
|
props,
|
|
22
|
+
type,
|
|
159
23
|
children: mapTextNodes(withoutNulls(children)),
|
|
160
|
-
type : DOM_TYPES.ELEMENT,
|
|
161
24
|
}
|
|
162
25
|
}
|
|
163
26
|
function mapTextNodes(children) {
|
|
@@ -177,15 +40,31 @@ function hFragment(vNodes) {
|
|
|
177
40
|
function addEventListener(
|
|
178
41
|
eventName,
|
|
179
42
|
handler,
|
|
180
|
-
el
|
|
43
|
+
el,
|
|
44
|
+
hostComponent = null
|
|
181
45
|
) {
|
|
182
|
-
|
|
183
|
-
|
|
46
|
+
function boundHandler() {
|
|
47
|
+
hostComponent
|
|
48
|
+
? handler.apply(hostComponent, arguments)
|
|
49
|
+
: handler(...arguments);
|
|
50
|
+
}
|
|
51
|
+
el.addEventListener(eventName, boundHandler);
|
|
52
|
+
return boundHandler;
|
|
184
53
|
}
|
|
185
|
-
function addEventListeners(
|
|
54
|
+
function addEventListeners(
|
|
55
|
+
listeners = {},
|
|
56
|
+
el,
|
|
57
|
+
hostComponent = null
|
|
58
|
+
) {
|
|
186
59
|
const addedListeners = {};
|
|
187
60
|
Object.entries(listeners).forEach(([eventName, handler]) => {
|
|
188
|
-
|
|
61
|
+
const listener = addEventListener(
|
|
62
|
+
eventName,
|
|
63
|
+
handler,
|
|
64
|
+
el,
|
|
65
|
+
hostComponent
|
|
66
|
+
);
|
|
67
|
+
addedListeners[eventName] = listener;
|
|
189
68
|
});
|
|
190
69
|
return addedListeners
|
|
191
70
|
}
|
|
@@ -195,45 +74,6 @@ function removeEventListeners(listeners = {}, el) {
|
|
|
195
74
|
});
|
|
196
75
|
}
|
|
197
76
|
|
|
198
|
-
function destroyDom(vdom) {
|
|
199
|
-
const { type } = vdom;
|
|
200
|
-
switch (type) {
|
|
201
|
-
case DOM_TYPES.TEXT : {
|
|
202
|
-
removeTextNode(vdom);
|
|
203
|
-
break
|
|
204
|
-
}
|
|
205
|
-
case DOM_TYPES.ELEMENT : {
|
|
206
|
-
removeElementNode(vdom);
|
|
207
|
-
break
|
|
208
|
-
}
|
|
209
|
-
case DOM_TYPES.FRAGMENT : {
|
|
210
|
-
removeFragmentNode(vdom);
|
|
211
|
-
break
|
|
212
|
-
}
|
|
213
|
-
default : {
|
|
214
|
-
throw new Error(`Can't destroy DOM of type ${type}`)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
delete vdom.el;
|
|
218
|
-
}
|
|
219
|
-
function removeTextNode(vdom) {
|
|
220
|
-
const { el } = vdom;
|
|
221
|
-
el.remove();
|
|
222
|
-
}
|
|
223
|
-
function removeElementNode(vdom) {
|
|
224
|
-
const { el , children, listeners } = vdom;
|
|
225
|
-
el.remove();
|
|
226
|
-
children.forEach(destroyDom);
|
|
227
|
-
if (listeners) {
|
|
228
|
-
removeEventListeners(listeners, el);
|
|
229
|
-
delete vdom.listeners;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
function removeFragmentNode(vdom) {
|
|
233
|
-
const { children } = vdom;
|
|
234
|
-
children.forEach(destroyDom);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
77
|
function setAttributes(el, attrs) {
|
|
238
78
|
const { class : className, style, ...otherAttrs} = attrs;
|
|
239
79
|
if (className) {
|
|
@@ -260,9 +100,6 @@ function setClass(el, className) {
|
|
|
260
100
|
function setStyle(el, name, value) {
|
|
261
101
|
el.style[name] = value;
|
|
262
102
|
}
|
|
263
|
-
function removeStyle(el, name) {
|
|
264
|
-
el.style[name] = null;
|
|
265
|
-
}
|
|
266
103
|
function removeAttributeCustom(el, name) {
|
|
267
104
|
el[name] = null;
|
|
268
105
|
el.removeAttribute(name);
|
|
@@ -277,18 +114,61 @@ function setAttribute(el, name, value) {
|
|
|
277
114
|
}
|
|
278
115
|
}
|
|
279
116
|
|
|
280
|
-
function
|
|
117
|
+
function extractPropsAndEvents(vdom) {
|
|
118
|
+
const { on : events = {}, ...props } = vdom.props;
|
|
119
|
+
delete props.key;
|
|
120
|
+
return { props, events }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let isScheduled = false;
|
|
124
|
+
const jobs = [];
|
|
125
|
+
function enqueueJob(job) {
|
|
126
|
+
jobs.push(job);
|
|
127
|
+
scheduleUpdate();
|
|
128
|
+
}
|
|
129
|
+
function scheduleUpdate() {
|
|
130
|
+
if (isScheduled) return
|
|
131
|
+
isScheduled = true;
|
|
132
|
+
queueMicrotask(processJobs);
|
|
133
|
+
}
|
|
134
|
+
function processJobs() {
|
|
135
|
+
while (jobs.length > 0) {
|
|
136
|
+
const job = jobs.shift();
|
|
137
|
+
const result = job();
|
|
138
|
+
Promise.resolve(result).then(
|
|
139
|
+
() => {
|
|
140
|
+
},
|
|
141
|
+
(error) => {
|
|
142
|
+
console.error(`[scheduler]: ${error}`);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
job();
|
|
146
|
+
}
|
|
147
|
+
isScheduled = false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function mountDom(
|
|
151
|
+
vdom,
|
|
152
|
+
parentEl,
|
|
153
|
+
index,
|
|
154
|
+
hostComponent = null
|
|
155
|
+
) {
|
|
281
156
|
switch (vdom.type) {
|
|
282
157
|
case DOM_TYPES.TEXT : {
|
|
283
|
-
createTextNode(vdom, parentEl, index);
|
|
158
|
+
createTextNode(vdom, parentEl, index, );
|
|
284
159
|
break
|
|
285
160
|
}
|
|
286
161
|
case DOM_TYPES.ELEMENT : {
|
|
287
|
-
createElementNode(vdom, parentEl, index);
|
|
162
|
+
createElementNode(vdom, parentEl, index, hostComponent);
|
|
288
163
|
break
|
|
289
164
|
}
|
|
290
165
|
case DOM_TYPES.FRAGMENT : {
|
|
291
|
-
createFragmentNodes(vdom, parentEl, index);
|
|
166
|
+
createFragmentNodes(vdom, parentEl, index, hostComponent);
|
|
167
|
+
break
|
|
168
|
+
}
|
|
169
|
+
case DOM_TYPES.COMPONENT : {
|
|
170
|
+
createComponentNode(vdom, parentEl, index, hostComponent);
|
|
171
|
+
enqueueJob(() => vdom.component.onMounted());
|
|
292
172
|
break
|
|
293
173
|
}
|
|
294
174
|
default : {
|
|
@@ -318,295 +198,104 @@ function createTextNode(vdom, parentEl, index) {
|
|
|
318
198
|
vdom.el = textNode;
|
|
319
199
|
insert(textNode, parentEl, index);
|
|
320
200
|
}
|
|
321
|
-
function createElementNode(vdom, parentEl, index) {
|
|
322
|
-
const { tag,
|
|
201
|
+
function createElementNode(vdom, parentEl, index, hostComponent) {
|
|
202
|
+
const { tag, children } = vdom;
|
|
323
203
|
const element = document.createElement(tag);
|
|
324
|
-
addProps(element,
|
|
204
|
+
addProps(element, vdom, hostComponent);
|
|
325
205
|
vdom.el = element;
|
|
326
206
|
//!function mountDom(vnode, parentEl) {
|
|
327
|
-
children.forEach((child) => mountDom(child, element));
|
|
207
|
+
children.forEach((child) => mountDom(child, element, null, hostComponent));
|
|
328
208
|
insert(element, parentEl, index);
|
|
329
209
|
}
|
|
330
|
-
function addProps(el, props, vdom) {
|
|
331
|
-
const {
|
|
332
|
-
vdom.listeners = addEventListeners(events, el);
|
|
210
|
+
function addProps(el, props, vdom, hostComponent) {
|
|
211
|
+
const { props : attrs, events } = extractPropsAndEvents(vdom);
|
|
212
|
+
vdom.listeners = addEventListeners(events, el, hostComponent);
|
|
333
213
|
setAttributes(el, attrs);
|
|
334
214
|
}
|
|
335
|
-
function createFragmentNodes(vdom, parentEl, index) {
|
|
215
|
+
function createFragmentNodes(vdom, parentEl, index, hostComponent) {
|
|
336
216
|
const { children } = vdom;
|
|
337
217
|
vdom.el = parentEl;
|
|
338
|
-
children.forEach((child, i) => mountDom(child, parentEl, index ? index + i : null));
|
|
218
|
+
children.forEach((child, i) => mountDom(child, parentEl, index ? index + i : null, hostComponent));
|
|
219
|
+
}
|
|
220
|
+
function createComponentNode(vdom, parentEl, index, hostComponent) {
|
|
221
|
+
const Component = vdom.tag;
|
|
222
|
+
const { props, events } = extractPropsAndEvents(vdom);
|
|
223
|
+
const component = new Component(props, events, hostComponent);
|
|
224
|
+
component.mount(parentEl, index);
|
|
225
|
+
vdom.component = component;
|
|
226
|
+
vdom.el = component.firstElement;
|
|
339
227
|
}
|
|
340
228
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
const handlersArray = this.#subs.get(commandName);
|
|
349
|
-
if (handlersArray.includes(handler)) {
|
|
350
|
-
return () => {
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
handlersArray.push(handler);
|
|
354
|
-
return () => {
|
|
355
|
-
const idx = handlersArray.indexOf(handler);
|
|
356
|
-
handlersArray.splice(idx, 1);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
afterEveryCommand(handler) {
|
|
360
|
-
this.#afterHandlers.push(handler);
|
|
361
|
-
return () => {
|
|
362
|
-
const idx = this.#afterHandlers.indexOf(handler);
|
|
363
|
-
this.#afterHandlers.splice(idx, 1);
|
|
229
|
+
function destroyDom(vdom) {
|
|
230
|
+
const { type } = vdom;
|
|
231
|
+
switch (type) {
|
|
232
|
+
case DOM_TYPES.TEXT : {
|
|
233
|
+
removeTextNode(vdom);
|
|
234
|
+
break
|
|
364
235
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
this.#subs.get(commandName).forEach((handler) => handler(payload));
|
|
369
|
-
} else {
|
|
370
|
-
console.warn(`No handlers for command : ${commandName}`);
|
|
236
|
+
case DOM_TYPES.ELEMENT : {
|
|
237
|
+
removeElementNode(vdom);
|
|
238
|
+
break
|
|
371
239
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
function areNodesEqual(nodeOne, nodeTwo) {
|
|
377
|
-
if (!nodeOne || !nodeTwo) {
|
|
378
|
-
console.error('Invalid VDOM node', { nodeOne, nodeTwo });
|
|
379
|
-
}
|
|
380
|
-
if (!nodeOne.type || !nodeTwo.type) {
|
|
381
|
-
console.error('Invalid VDOM node', { nodeOne, nodeTwo });
|
|
382
|
-
}
|
|
383
|
-
if (nodeOne.type !== nodeTwo.type) {
|
|
384
|
-
return false
|
|
385
|
-
}
|
|
386
|
-
if (nodeOne.type === DOM_TYPES.ELEMENT) {
|
|
387
|
-
const { tag : tagOne } = nodeOne;
|
|
388
|
-
const { tag : tagTwo} = nodeTwo;
|
|
389
|
-
return tagOne === tagTwo
|
|
390
|
-
}
|
|
391
|
-
return true
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function objectsDiff(oldObj, newObj) {
|
|
395
|
-
const oldKeys = Object.keys(oldObj);
|
|
396
|
-
const newKeys = Object.keys(newObj);
|
|
397
|
-
return {
|
|
398
|
-
added : newKeys.filter((key) => !(key in oldObj)),
|
|
399
|
-
removed : oldKeys.filter((key) => !(key in newObj)),
|
|
400
|
-
updated : newKeys.filter(
|
|
401
|
-
(key) => key in oldObj && oldObj[key] !== newObj[key]
|
|
402
|
-
),
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function isNotEmptyString(str) {
|
|
407
|
-
return str !== ''
|
|
408
|
-
}
|
|
409
|
-
function isNotBlankOrEmptyString(str) {
|
|
410
|
-
return isNotEmptyString(str.trim())
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function patchDOM(oldVdom, newVdom, parentEl) {
|
|
414
|
-
if (!areNodesEqual(oldVdom, newVdom)) {
|
|
415
|
-
const index = findIndexInParent(parentEl, oldVdom.el);
|
|
416
|
-
destroyDom(oldVdom);
|
|
417
|
-
mountDom(newVdom, parentEl, index);
|
|
418
|
-
return newVdom
|
|
419
|
-
}
|
|
420
|
-
newVdom.el = oldVdom.el;
|
|
421
|
-
switch (newVdom.type) {
|
|
422
|
-
case DOM_TYPES.TEXT: {
|
|
423
|
-
patchText(oldVdom, newVdom);
|
|
424
|
-
return newVdom
|
|
240
|
+
case DOM_TYPES.FRAGMENT : {
|
|
241
|
+
removeFragmentNode(vdom);
|
|
242
|
+
break
|
|
425
243
|
}
|
|
426
|
-
case DOM_TYPES.
|
|
427
|
-
|
|
244
|
+
case DOM_TYPES.COMPONENT : {
|
|
245
|
+
vdom.component.unmount();
|
|
246
|
+
enqueueJob(() => vdom.component.onUnMounted());
|
|
428
247
|
break
|
|
429
248
|
}
|
|
249
|
+
default : {
|
|
250
|
+
throw new Error(`Can't destroy DOM of type ${type}`)
|
|
251
|
+
}
|
|
430
252
|
}
|
|
431
|
-
|
|
432
|
-
return newVdom
|
|
433
|
-
}
|
|
434
|
-
function patchText(oldVdom, newVdom) {
|
|
435
|
-
const el = oldVdom.el;
|
|
436
|
-
const { value : oldText} = oldVdom;
|
|
437
|
-
const { value : newText} = newVdom;
|
|
438
|
-
if (oldText !== newText) {
|
|
439
|
-
el.nodeValue = newText;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
function findIndexInParent(parentEl, el) {
|
|
443
|
-
const index = Array.from(parentEl.childNodes).indexOf(el);
|
|
444
|
-
if (index < 0) {
|
|
445
|
-
return null
|
|
446
|
-
}
|
|
447
|
-
return index
|
|
448
|
-
}
|
|
449
|
-
function patchElement(oldVdom, newVdom) {
|
|
450
|
-
const el = oldVdom.el;
|
|
451
|
-
const {
|
|
452
|
-
class : oldClass,
|
|
453
|
-
style : oldStyle,
|
|
454
|
-
on: oldEvents,
|
|
455
|
-
...oldAttrs
|
|
456
|
-
} = oldVdom.props;
|
|
457
|
-
const {
|
|
458
|
-
class: newClass,
|
|
459
|
-
style: newStyle,
|
|
460
|
-
on: newEvents,
|
|
461
|
-
...newAttrs
|
|
462
|
-
} = newVdom.props;
|
|
463
|
-
const { listeners: oldListeners } = oldVdom;
|
|
464
|
-
patchAttrs(el, oldAttrs, newAttrs);
|
|
465
|
-
patchClasses(el, oldClass, newClass);
|
|
466
|
-
patchStyles(el, oldStyle, newStyle);
|
|
467
|
-
newVdom.listeners = patchEvents(el, oldListeners, oldEvents, newEvents);
|
|
468
|
-
}
|
|
469
|
-
function patchAttrs(el, oldAttrs, newAttrs) {
|
|
470
|
-
const { added, removed, updated } = objectsDiff(oldAttrs, newAttrs);
|
|
471
|
-
for (const attr of removed) {
|
|
472
|
-
removeAttributeCustom(el, attr);
|
|
473
|
-
}
|
|
474
|
-
for (const attr of added.concat(updated)) {
|
|
475
|
-
setAttribute(el, attr, newAttrs[attr]);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
function patchClasses(el, oldClass, newClass) {
|
|
479
|
-
const oldClasses = toClassList(oldClass);
|
|
480
|
-
const newClasses = toClassList(newClass);
|
|
481
|
-
const {added, removed} =
|
|
482
|
-
arraysDiff(oldClasses, newClasses);
|
|
483
|
-
if (removed.length > 0) {
|
|
484
|
-
el.classList.remove(...removed);
|
|
485
|
-
}
|
|
486
|
-
if (added.length > 0) {
|
|
487
|
-
el.classList.add(...added);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
function toClassList(classes = '') {
|
|
491
|
-
return Array.isArray(classes)
|
|
492
|
-
? classes.filter(isNotBlankOrEmptyString)
|
|
493
|
-
: classes.split(/(\s+)/)
|
|
494
|
-
.filter(isNotBlankOrEmptyString)
|
|
495
|
-
}
|
|
496
|
-
function patchStyles(el, oldStyle = {}, newStyle = {}) {
|
|
497
|
-
const {added, removed, updated } = objectsDiff(oldStyle, newStyle);
|
|
498
|
-
for (const style of removed) {
|
|
499
|
-
removeStyle(el, style);
|
|
500
|
-
}
|
|
501
|
-
for (const style of added.concat(updated)) {
|
|
502
|
-
setStyle(el, style, newStyle[style]);
|
|
503
|
-
}
|
|
253
|
+
delete vdom.el;
|
|
504
254
|
}
|
|
505
|
-
function
|
|
506
|
-
el
|
|
507
|
-
|
|
508
|
-
oldEvents = {},
|
|
509
|
-
newEvents = {},
|
|
510
|
-
) {
|
|
511
|
-
const { removed, added, updated} =
|
|
512
|
-
objectsDiff(oldEvents, newEvents);
|
|
513
|
-
for (const eventName of removed.concat(updated)) {
|
|
514
|
-
el.removeEventListener(eventName, oldListeners[eventName]);
|
|
515
|
-
}
|
|
516
|
-
const addedListeners = {};
|
|
517
|
-
for (const eventName of added.concat(updated)) {
|
|
518
|
-
const listener = addEventListener(eventName, newEvents[eventName], el);
|
|
519
|
-
addedListeners[eventName] = listener;
|
|
520
|
-
}
|
|
521
|
-
return addedListeners
|
|
255
|
+
function removeTextNode(vdom) {
|
|
256
|
+
const { el } = vdom;
|
|
257
|
+
el.remove();
|
|
522
258
|
}
|
|
523
|
-
function
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
children.push(...extractChildren(child));
|
|
531
|
-
} else {
|
|
532
|
-
children.push(child);
|
|
533
|
-
}
|
|
259
|
+
function removeElementNode(vdom) {
|
|
260
|
+
const { el , children, listeners } = vdom;
|
|
261
|
+
el.remove();
|
|
262
|
+
children.forEach(destroyDom);
|
|
263
|
+
if (listeners) {
|
|
264
|
+
removeEventListeners(listeners, el);
|
|
265
|
+
delete vdom.listeners;
|
|
534
266
|
}
|
|
535
|
-
return children
|
|
536
267
|
}
|
|
537
|
-
function
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
const parentEl = oldVdom.el;
|
|
541
|
-
const diffSeq = arraysDiffSequence(oldChildren, newChildren, areNodesEqual);
|
|
542
|
-
for (const operation of diffSeq) {
|
|
543
|
-
const { originalIndex, index, item} = operation;
|
|
544
|
-
switch (operation.op) {
|
|
545
|
-
case ARRAY_DIFF_OP.ADD: {
|
|
546
|
-
mountDom(item, parentEl, index);
|
|
547
|
-
break
|
|
548
|
-
}
|
|
549
|
-
case ARRAY_DIFF_OP.REMOVE: {
|
|
550
|
-
destroyDom(item);
|
|
551
|
-
break
|
|
552
|
-
}
|
|
553
|
-
case ARRAY_DIFF_OP.MOVE: {
|
|
554
|
-
const oldChild = oldChildren[originalIndex];
|
|
555
|
-
const newChild = newChildren[index];
|
|
556
|
-
const el = oldChild.el;
|
|
557
|
-
const elAtTargetIndex = parentEl.childNodes[index];
|
|
558
|
-
parentEl.insertBefore(el, elAtTargetIndex);
|
|
559
|
-
patchDOM(oldChildren[originalIndex], newChild, parentEl);
|
|
560
|
-
break
|
|
561
|
-
}
|
|
562
|
-
case ARRAY_DIFF_OP.NOOP : {
|
|
563
|
-
patchDOM(oldChildren[originalIndex], newChildren[index], parentEl);
|
|
564
|
-
break
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
268
|
+
function removeFragmentNode(vdom) {
|
|
269
|
+
const { children } = vdom;
|
|
270
|
+
children.forEach(destroyDom);
|
|
568
271
|
}
|
|
569
272
|
|
|
570
|
-
function createApp(
|
|
273
|
+
function createApp(RootComponent, props = {}) {
|
|
571
274
|
let parentEl = null;
|
|
572
|
-
let vdom = null;
|
|
573
275
|
let isMounted = false;
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
for (const actionName in reducers) {
|
|
580
|
-
const reducer = reducers[actionName];
|
|
581
|
-
const subs = dispatcher.subscribe(actionName, (payload) => {
|
|
582
|
-
state = reducer(state, payload);
|
|
583
|
-
});
|
|
584
|
-
subscriptions.push(subs);
|
|
585
|
-
}
|
|
586
|
-
function renderApp() {
|
|
587
|
-
const newVdom = view(state, emit);
|
|
588
|
-
if (!newVdom) {
|
|
589
|
-
console.error('View returned', newVdom);
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
vdom = patchDOM(vdom, newVdom, parentEl);
|
|
276
|
+
let vdom = null;
|
|
277
|
+
function reset() {
|
|
278
|
+
parentEl = null;
|
|
279
|
+
isMounted = false;
|
|
280
|
+
vdom = null;
|
|
593
281
|
}
|
|
594
282
|
return {
|
|
595
283
|
mount(_parentEl) {
|
|
596
284
|
if (isMounted) {
|
|
597
|
-
throw new Error(
|
|
285
|
+
throw new Error(`The application is already mounted`)
|
|
598
286
|
}
|
|
599
287
|
parentEl = _parentEl;
|
|
600
|
-
vdom =
|
|
288
|
+
vdom = h(RootComponent, props);
|
|
601
289
|
mountDom(vdom, parentEl);
|
|
602
290
|
isMounted = true;
|
|
603
291
|
},
|
|
604
292
|
unmount() {
|
|
293
|
+
if (!isMounted) {
|
|
294
|
+
throw new Error(`The application is not mounted`)
|
|
295
|
+
}
|
|
605
296
|
destroyDom(vdom);
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
isMounted = false;
|
|
609
|
-
},
|
|
297
|
+
reset();
|
|
298
|
+
}
|
|
610
299
|
}
|
|
611
300
|
}
|
|
612
301
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lahama",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/lahama.js",
|
|
6
6
|
"files": [
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"type": "module",
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@eslint/js": "^9.39.0",
|
|
23
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
24
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
23
25
|
"globals": "^16.5.0",
|
|
24
26
|
"jsdom": "^27.2.0",
|
|
25
27
|
"rimraf": "^3.0.2",
|
|
@@ -27,5 +29,8 @@
|
|
|
27
29
|
"rollup-plugin-cleanup": "^3.2.1",
|
|
28
30
|
"rollup-plugin-filesize": "^10.0.0",
|
|
29
31
|
"vitest": "^4.0.15"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"fast-deep-equal": "^3.1.3"
|
|
30
35
|
}
|
|
31
36
|
}
|