ks-fwork 3.0.0 → 3.0.2
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/ks-fwork.js +754 -0
- package/package.json +4 -3
package/dist/ks-fwork.js
ADDED
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
function addEventListeners(listeners = {}, el, hostComponent = null) {
|
|
2
|
+
const addedListeners = {};
|
|
3
|
+
Object.entries(listeners).forEach(([eventName, handler]) => {
|
|
4
|
+
addedListeners[eventName] = addEventListener(eventName, handler, el, hostComponent);
|
|
5
|
+
});
|
|
6
|
+
return addedListeners;
|
|
7
|
+
}
|
|
8
|
+
function addEventListener(eventName, handler, el, hostComponent = null) {
|
|
9
|
+
function boundHandler() {
|
|
10
|
+
hostComponent ? handler.apply(hostComponent, arguments) : handler(...arguments);
|
|
11
|
+
}
|
|
12
|
+
el.addEventListener(eventName, boundHandler);
|
|
13
|
+
return boundHandler;
|
|
14
|
+
}
|
|
15
|
+
function removeEventListeners(listeners = {}, el) {
|
|
16
|
+
Object.entries(listeners).forEach(([eventName, handler]) => {
|
|
17
|
+
el.removeEventListener(eventName, handler);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function withoutNulls(arr) {
|
|
22
|
+
return arr.filter((item) => item != null);
|
|
23
|
+
}
|
|
24
|
+
function arraysDiff(oldArray, newArray) {
|
|
25
|
+
return {
|
|
26
|
+
added: newArray.filter(
|
|
27
|
+
(newItem) => !oldArray.includes(newItem)
|
|
28
|
+
),
|
|
29
|
+
removed: oldArray.filter(
|
|
30
|
+
(oldItem) => !newArray.includes(oldItem)
|
|
31
|
+
),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const ARRAY_DIFF_OP = {
|
|
35
|
+
ADD: 'add',
|
|
36
|
+
REMOVE: 'remove',
|
|
37
|
+
MOVE: 'move',
|
|
38
|
+
NOOP: 'noop'
|
|
39
|
+
};
|
|
40
|
+
class ArrayWithOriginalIndices {
|
|
41
|
+
#oldArray = [];
|
|
42
|
+
#originalIndices = [];
|
|
43
|
+
#equalsFn;
|
|
44
|
+
constructor(array, equalsFn) {
|
|
45
|
+
this.#oldArray = [...array];
|
|
46
|
+
this.#originalIndices = array.map((value, index) => index);
|
|
47
|
+
this.#equalsFn = equalsFn;
|
|
48
|
+
}
|
|
49
|
+
get length() {
|
|
50
|
+
return this.#oldArray.length;
|
|
51
|
+
}
|
|
52
|
+
originalIndexAt(index) {
|
|
53
|
+
return this.#originalIndices[index];
|
|
54
|
+
}
|
|
55
|
+
isAddition(item, startIndex) {
|
|
56
|
+
return this.findIndexFrom(item, startIndex) === -1;
|
|
57
|
+
}
|
|
58
|
+
addItem(item, index) {
|
|
59
|
+
const operation = {
|
|
60
|
+
op: ARRAY_DIFF_OP.ADD,
|
|
61
|
+
index,
|
|
62
|
+
item
|
|
63
|
+
};
|
|
64
|
+
this.#oldArray.splice(index, 0, item);
|
|
65
|
+
this.#originalIndices.splice(index, 0, -1);
|
|
66
|
+
return operation;
|
|
67
|
+
}
|
|
68
|
+
isRemoval(index, newArray) {
|
|
69
|
+
if (index >= this.length) { return false; }
|
|
70
|
+
const item = this.#oldArray[index];
|
|
71
|
+
const indexInNewArray = newArray.findIndex((newArrayItem) => this.#equalsFn(item, newArrayItem));
|
|
72
|
+
return indexInNewArray === -1;
|
|
73
|
+
}
|
|
74
|
+
removeItem(index) {
|
|
75
|
+
const operation = {
|
|
76
|
+
op: ARRAY_DIFF_OP.REMOVE,
|
|
77
|
+
index,
|
|
78
|
+
item: this.#oldArray[index],
|
|
79
|
+
};
|
|
80
|
+
this.#oldArray.splice(index, 1);
|
|
81
|
+
this.#originalIndices.splice(index, 1);
|
|
82
|
+
return operation;
|
|
83
|
+
}
|
|
84
|
+
moveItem(item, toIndex) {
|
|
85
|
+
const fromIndex = this.findIndexFrom(item, toIndex);
|
|
86
|
+
const operation = {
|
|
87
|
+
op: ARRAY_DIFF_OP.MOVE,
|
|
88
|
+
originalIndex: this.originalIndexAt(fromIndex),
|
|
89
|
+
from: fromIndex,
|
|
90
|
+
index: toIndex,
|
|
91
|
+
item: this.#oldArray[fromIndex],
|
|
92
|
+
};
|
|
93
|
+
const [itemToMove] = this.#oldArray.splice(fromIndex, 1);
|
|
94
|
+
this.#oldArray.splice(toIndex, 0, itemToMove);
|
|
95
|
+
const [originalIndex] = this.#originalIndices.splice(fromIndex, 1);
|
|
96
|
+
this.#originalIndices.splice(toIndex, 0, originalIndex);
|
|
97
|
+
return operation;
|
|
98
|
+
}
|
|
99
|
+
isNoop(index, newArray) {
|
|
100
|
+
if (index >= this.length) {return false; }
|
|
101
|
+
const item = this.#oldArray[index];
|
|
102
|
+
const newArrayItem = newArray[index];
|
|
103
|
+
return this.#equalsFn(item, newArrayItem);
|
|
104
|
+
}
|
|
105
|
+
noopItem(index) {
|
|
106
|
+
return {
|
|
107
|
+
op: ARRAY_DIFF_OP.NOOP,
|
|
108
|
+
originalIndex: this.originalIndexAt(index),
|
|
109
|
+
index,
|
|
110
|
+
item: this.#oldArray[index],
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
removeItemsAfter(index) {
|
|
114
|
+
const operations = [];
|
|
115
|
+
while (this.length > index) {
|
|
116
|
+
operations.push(this.removeItem(index));
|
|
117
|
+
}
|
|
118
|
+
return operations;
|
|
119
|
+
}
|
|
120
|
+
findIndexFrom(item, startIndex) {
|
|
121
|
+
for (let i = startIndex; i < this.length; i++) {
|
|
122
|
+
if (this.#equalsFn(item, this.#oldArray[i])) {
|
|
123
|
+
return i;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return -1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function arraysDiffSequence(oldArray, newArray, equalsFn = (a, b) => a === b) {
|
|
130
|
+
const sequence = [];
|
|
131
|
+
const array = new ArrayWithOriginalIndices(oldArray, equalsFn);
|
|
132
|
+
for (let index = 0; index < newArray.length; index++) {
|
|
133
|
+
if (array.isRemoval(index, newArray)) {
|
|
134
|
+
sequence.push(array.removeItem(index));
|
|
135
|
+
index--;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (array.isNoop(index, newArray)) {
|
|
139
|
+
sequence.push(array.noopItem(index));
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const item = newArray[index];
|
|
143
|
+
if (array.isAddition(item, index)) {
|
|
144
|
+
sequence.push(array.addItem(item, index));
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
sequence.push(array.moveItem(item, index));
|
|
148
|
+
}
|
|
149
|
+
sequence.push(...array.removeItemsAfter(newArray.length));
|
|
150
|
+
return sequence;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const DOM_TYPES = {
|
|
154
|
+
TEXT: 'text',
|
|
155
|
+
ELEMENT: 'element',
|
|
156
|
+
FRAGMENT: 'fragment',
|
|
157
|
+
COMPONENT: 'component'
|
|
158
|
+
};
|
|
159
|
+
function h(tag, props = {}, children = []) {
|
|
160
|
+
const type = typeof tag === 'string' ? DOM_TYPES.ELEMENT : DOM_TYPES.COMPONENT;
|
|
161
|
+
return {
|
|
162
|
+
tag,
|
|
163
|
+
props,
|
|
164
|
+
type,
|
|
165
|
+
children: mapTextNodes(withoutNulls(children)),
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function hString(str) {
|
|
169
|
+
return {
|
|
170
|
+
type: DOM_TYPES.TEXT,
|
|
171
|
+
value: str
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function hFragment(vNodes) {
|
|
175
|
+
return {
|
|
176
|
+
children: mapTextNodes(withoutNulls(vNodes)),
|
|
177
|
+
type: DOM_TYPES.FRAGMENT,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function mapTextNodes(children) {
|
|
181
|
+
return children.map((child) =>
|
|
182
|
+
typeof child === 'string' ? hString(child) : child
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
function extractChildren(vNode) {
|
|
186
|
+
if (vNode.children == null) return [];
|
|
187
|
+
const children = [];
|
|
188
|
+
for (const child of vNode.children) {
|
|
189
|
+
if (child.type === DOM_TYPES.FRAGMENT) {
|
|
190
|
+
children.push(...extractChildren(child));
|
|
191
|
+
} else {
|
|
192
|
+
children.push(child);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return children;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function destroyDOM(vNode) {
|
|
199
|
+
const { type } = vNode;
|
|
200
|
+
switch (type) {
|
|
201
|
+
case DOM_TYPES.TEXT: {
|
|
202
|
+
removeTextNode(vNode);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case DOM_TYPES.ELEMENT: {
|
|
206
|
+
removeElementNode(vNode);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case DOM_TYPES.FRAGMENT: {
|
|
210
|
+
removeFragmentNode(vNode);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case DOM_TYPES.COMPONENT: {
|
|
214
|
+
vNode.component.unmount();
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
default: {
|
|
218
|
+
throw new Error(`Can't destroy virtual node of type: ${vNode.type}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
delete vNode.el;
|
|
222
|
+
}
|
|
223
|
+
function removeTextNode(vNode) {
|
|
224
|
+
const { el } = vNode;
|
|
225
|
+
el.remove();
|
|
226
|
+
}
|
|
227
|
+
function removeElementNode(vNode) {
|
|
228
|
+
const { el, children, listeners } = vNode;
|
|
229
|
+
children.forEach(destroyDOM);
|
|
230
|
+
if (listeners) {
|
|
231
|
+
removeEventListeners(listeners, el);
|
|
232
|
+
delete vNode.listeners;
|
|
233
|
+
}
|
|
234
|
+
el.remove();
|
|
235
|
+
}
|
|
236
|
+
function removeFragmentNode(vNode) {
|
|
237
|
+
const { children } = vNode;
|
|
238
|
+
children.forEach(destroyDOM);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
class Dispatcher {
|
|
242
|
+
#handlersByCommand = new Map();
|
|
243
|
+
#afterHandlers = [];
|
|
244
|
+
subscribe(commandName, handler) {
|
|
245
|
+
if (!this.#handlersByCommand.has(commandName)) {
|
|
246
|
+
this.#handlersByCommand.set(commandName, []);
|
|
247
|
+
}
|
|
248
|
+
const handlers = this.#handlersByCommand.get(commandName);
|
|
249
|
+
if (handlers.includes(handler)) {
|
|
250
|
+
return () => {}
|
|
251
|
+
}
|
|
252
|
+
handlers.push(handler);
|
|
253
|
+
return () => {
|
|
254
|
+
const index = handlers.indexOf(handler);
|
|
255
|
+
handlers.splice(index, 1);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
afterEveryCommand(handler) {
|
|
259
|
+
this.#afterHandlers.push(handler);
|
|
260
|
+
return () => {
|
|
261
|
+
const index = this.#afterHandlers.indexOf(handler);
|
|
262
|
+
this.#afterHandlers.splice(index, 1);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
dispatch(commandName, payload) {
|
|
266
|
+
if (this.#handlersByCommand.has(commandName)) {
|
|
267
|
+
this.#handlersByCommand.get(commandName).forEach((handler) => handler(payload));
|
|
268
|
+
} else {
|
|
269
|
+
console.warn(`No handlers for command: ${commandName}`);
|
|
270
|
+
}
|
|
271
|
+
this.#afterHandlers.forEach((handler) => handler());
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function setAttributes(el, attrs) {
|
|
276
|
+
const { class: className, style, ...otherAttrs } = attrs;
|
|
277
|
+
if (className) {
|
|
278
|
+
setClass(el, className);
|
|
279
|
+
}
|
|
280
|
+
if (style) {
|
|
281
|
+
Object.entries(style).forEach(([prop, value]) => {
|
|
282
|
+
setStyle(el, prop, value);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
for (const [name, value] of Object.entries(otherAttrs)) {
|
|
286
|
+
setAttribute(el, name, value);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function setClass(el, className) {
|
|
290
|
+
el.className = '';
|
|
291
|
+
if (typeof className === 'string') {
|
|
292
|
+
el.className = className;
|
|
293
|
+
}
|
|
294
|
+
if (Array.isArray(className)) {
|
|
295
|
+
el.classList.add(...className);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function setStyle(el, name, value) {
|
|
299
|
+
el.style[name] = value;
|
|
300
|
+
}
|
|
301
|
+
function removeStyle(el, name) {
|
|
302
|
+
el.style[name] = null;
|
|
303
|
+
}
|
|
304
|
+
function setAttribute(el, name, value) {
|
|
305
|
+
if (value == null) {
|
|
306
|
+
removeAttribute(el, name);
|
|
307
|
+
} else if (name.startsWith('data-')) {
|
|
308
|
+
el.setAttribute(name, value);
|
|
309
|
+
} else {
|
|
310
|
+
el[name] = value;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function removeAttribute(el, name) {
|
|
314
|
+
el[name] = null;
|
|
315
|
+
el.removeAttribute(name);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function extractPropsAndEvents(vNode) {
|
|
319
|
+
const { on: events = {}, ...props } = vNode.props;
|
|
320
|
+
delete props.key;
|
|
321
|
+
return { props, events }
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function mountDOM(vNode, parentEl, index, hostComponent = null) {
|
|
325
|
+
switch (vNode.type) {
|
|
326
|
+
case DOM_TYPES.TEXT: {
|
|
327
|
+
createTextNode(vNode, parentEl, index);
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case DOM_TYPES.ELEMENT: {
|
|
331
|
+
createElementNode(vNode, parentEl, index, hostComponent);
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case DOM_TYPES.FRAGMENT: {
|
|
335
|
+
createFragmentNode(vNode, parentEl, index, hostComponent);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
case DOM_TYPES.COMPONENT: {
|
|
339
|
+
createComponentNode(vNode, parentEl, index, hostComponent);
|
|
340
|
+
}
|
|
341
|
+
default: {
|
|
342
|
+
throw new Error(`Can't mount virtual node of type: ${vNode.type}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function createTextNode(vNode, parentEl, index) {
|
|
347
|
+
const { value } = vNode;
|
|
348
|
+
const textNode = document.createTextNode(value);
|
|
349
|
+
vNode.el = textNode;
|
|
350
|
+
insert(textNode, parentEl, index);
|
|
351
|
+
}
|
|
352
|
+
function createElementNode(vNode, parentEl, index, hostComponent) {
|
|
353
|
+
const { tag, children } = vNode;
|
|
354
|
+
const el = document.createElement(tag);
|
|
355
|
+
addProps(el, vNode, hostComponent);
|
|
356
|
+
vNode.el = el;
|
|
357
|
+
children.forEach((child) => mountDOM(child, el, null, hostComponent));
|
|
358
|
+
insert(el, parentEl, index);
|
|
359
|
+
}
|
|
360
|
+
function createFragmentNode(vNode, parentEl, index, hostComponent) {
|
|
361
|
+
const { children } = vNode;
|
|
362
|
+
vNode.el = parentEl;
|
|
363
|
+
children.forEach((child, i) => {
|
|
364
|
+
mountDOM(child, parentEl, index ? index + i : null, hostComponent);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function createComponentNode(vNode, parentEl, index, hostComponent) {
|
|
368
|
+
const Component = vNode.tag;
|
|
369
|
+
const { props, events } = extractPropsAndEvents(vNode);
|
|
370
|
+
const component = new Component(props, events, hostComponent);
|
|
371
|
+
component.mount(parentEl, index);
|
|
372
|
+
vNode.component = component;
|
|
373
|
+
vNode.el = component.firstElement;
|
|
374
|
+
}
|
|
375
|
+
function addProps(el, vNode, hostComponent) {
|
|
376
|
+
const { props: attrs, events} = extractPropsAndEvents(vNode);
|
|
377
|
+
vNode.listeners = addEventListeners(events, el, hostComponent);
|
|
378
|
+
setAttributes(el, attrs);
|
|
379
|
+
}
|
|
380
|
+
function insert(el, parentEl, index) {
|
|
381
|
+
if (index == null) {
|
|
382
|
+
parentEl.append(el);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (index < 0) {
|
|
386
|
+
throw new Error(`Index must be a positive number, got ${index}`);
|
|
387
|
+
}
|
|
388
|
+
const children = parentEl.childNodes;
|
|
389
|
+
if (index >= children.length) {
|
|
390
|
+
parentEl.append(el);
|
|
391
|
+
} else {
|
|
392
|
+
parentEl.insertBefore(el, children[index]);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function areNodesEqual(nodeOne, nodeTwo) {
|
|
397
|
+
if (nodeOne.type !== nodeTwo.type) return false;
|
|
398
|
+
if (nodeOne.type === DOM_TYPES.ELEMENT) {
|
|
399
|
+
const { tag: tagOne, props: { key: keyOne } } = nodeOne;
|
|
400
|
+
const { tag: tagTwo, props: { key: keyTwo } } = nodeTwo;
|
|
401
|
+
return tagOne === tagTwo && keyOne === keyTwo;
|
|
402
|
+
}
|
|
403
|
+
if (nodeOne.type === DOM_TYPES.COMPONENT) {
|
|
404
|
+
const { tag: componentOne, props: { key: keyOne } } = nodeOne;
|
|
405
|
+
const { tag: componentTwo, props: { key: keyTwo } } = nodeTwo;
|
|
406
|
+
return componentOne === componentTwo && keyOne === keyTwo;
|
|
407
|
+
}
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function objectsDiff(oldObj, newObj) {
|
|
412
|
+
const oldKeys = Object.keys(oldObj);
|
|
413
|
+
const newKeys = Object.keys(newObj);
|
|
414
|
+
return {
|
|
415
|
+
added: newKeys.filter((newKey) => !(newKey in oldObj)),
|
|
416
|
+
removed: oldKeys.filter((oldKey) => !(oldKey in newObj)),
|
|
417
|
+
updated: newKeys.filter(
|
|
418
|
+
(key) => key in oldObj && oldObj[key] !== newObj[key]
|
|
419
|
+
),
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function hasOwnProperty(obj, prop) {
|
|
423
|
+
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function isNotEmptyString(str) {
|
|
427
|
+
return str !== '';
|
|
428
|
+
}
|
|
429
|
+
function isNotBlankOrEmptyString(str) {
|
|
430
|
+
return isNotEmptyString(str.trim());
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function patchDOM(oldVNode, newVNode, parentEl, hostComponent = null) {
|
|
434
|
+
if (!areNodesEqual(oldVNode, newVNode)) {
|
|
435
|
+
const index = findIndexInParent(parentEl, oldVNode.el);
|
|
436
|
+
destroyDOM(oldVNode);
|
|
437
|
+
mountDOM(newVNode, parentEl, index, hostComponent);
|
|
438
|
+
return newVNode;
|
|
439
|
+
}
|
|
440
|
+
newVNode.el = oldVNode.el;
|
|
441
|
+
switch (newVNode.type) {
|
|
442
|
+
case DOM_TYPES.TEXT: {
|
|
443
|
+
patchText(oldVNode, newVNode);
|
|
444
|
+
return newVNode;
|
|
445
|
+
}
|
|
446
|
+
case DOM_TYPES.ELEMENT: {
|
|
447
|
+
patchElement(oldVNode, newVNode, hostComponent);
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
case DOM_TYPES.COMPONENT: {
|
|
451
|
+
patchComponent(oldVNode, newVNode);
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
patchChildren(oldVNode, newVNode, hostComponent);
|
|
456
|
+
return newVNode;
|
|
457
|
+
}
|
|
458
|
+
function findIndexInParent(parentEl, el) {
|
|
459
|
+
const index = Array.from(parentEl.childNodes).indexOf(el);
|
|
460
|
+
if (index < 0) return null;
|
|
461
|
+
return index;
|
|
462
|
+
}
|
|
463
|
+
function patchText(oldVNode, newVNode) {
|
|
464
|
+
const el = oldVNode.el;
|
|
465
|
+
const { value: oldText } = oldVNode;
|
|
466
|
+
const { value: newText } = newVNode;
|
|
467
|
+
if (oldText !== newText) {
|
|
468
|
+
el.nodeValue = newText;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function patchElement(oldVNode, newVNode, hostComponent) {
|
|
472
|
+
const el = oldVNode.el;
|
|
473
|
+
const {
|
|
474
|
+
class: oldClass,
|
|
475
|
+
style: oldStyle,
|
|
476
|
+
on: oldEvents,
|
|
477
|
+
...oldAttrs
|
|
478
|
+
} = oldVNode.props;
|
|
479
|
+
const {
|
|
480
|
+
class: newClass,
|
|
481
|
+
style: newStyle,
|
|
482
|
+
on: newEvents,
|
|
483
|
+
...newAttrs
|
|
484
|
+
} = newVNode.props;
|
|
485
|
+
const { listeners: oldListeners } = oldVNode;
|
|
486
|
+
patchAttrs(el, oldAttrs, newAttrs);
|
|
487
|
+
patchClasses(el, oldClass, newClass);
|
|
488
|
+
patchStyles(el, oldStyle, newStyle);
|
|
489
|
+
newVNode.listeners = patchEvents(el, oldListeners, oldEvents, newEvents, hostComponent);
|
|
490
|
+
}
|
|
491
|
+
function patchComponent(oldVNode, newVNode) {
|
|
492
|
+
const { component } = oldVNode;
|
|
493
|
+
const { props } = extractPropsAndEvents(newVNode);
|
|
494
|
+
component.updateProps(props);
|
|
495
|
+
newVNode.component = component;
|
|
496
|
+
newVNode.el = component.firstElement;
|
|
497
|
+
}
|
|
498
|
+
function patchAttrs(el, oldAttrs, newAttrs) {
|
|
499
|
+
const { added, removed, updated } = objectsDiff(oldAttrs, newAttrs);
|
|
500
|
+
for (const attr of removed) {
|
|
501
|
+
removeAttribute(el, attr);
|
|
502
|
+
}
|
|
503
|
+
for (const attr of added.concat(updated)) {
|
|
504
|
+
setAttribute(el, attr, newAttrs[attr]);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function patchClasses(el, oldClass, newClass) {
|
|
508
|
+
const oldClasses = toClassList(oldClass);
|
|
509
|
+
const newClasses = toClassList(newClass);
|
|
510
|
+
const { added, removed } = arraysDiff(oldClasses, newClasses);
|
|
511
|
+
if (removed.length > 0) {
|
|
512
|
+
el.classList.remove(...removed);
|
|
513
|
+
}
|
|
514
|
+
if (added.length > 0) {
|
|
515
|
+
el.classList.add(...added);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function toClassList(classes = '') {
|
|
519
|
+
return Array.isArray(classes)
|
|
520
|
+
? classes.filter(isNotBlankOrEmptyString)
|
|
521
|
+
: classes.split(/(\s+)/).filter(isNotBlankOrEmptyString);
|
|
522
|
+
}
|
|
523
|
+
function patchStyles(el, oldStyle = {}, newStyle = {}) {
|
|
524
|
+
const { added, removed, updated} = objectsDiff(oldStyle, newStyle);
|
|
525
|
+
for (const style of removed) {
|
|
526
|
+
removeStyle(el, style);
|
|
527
|
+
}
|
|
528
|
+
for (const style of added.concat(updated)) {
|
|
529
|
+
setStyle(el, style, newStyle[style]);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function patchEvents(el, oldListeners = {}, oldEvents = {}, newEvents = {}, hostComponent) {
|
|
533
|
+
const { removed, added, updated} = objectsDiff(oldEvents, newEvents);
|
|
534
|
+
for (const eventName of removed.concat(updated)) {
|
|
535
|
+
el.removeEventListener(eventName, oldListeners[eventName]);
|
|
536
|
+
}
|
|
537
|
+
const addedListeners = {};
|
|
538
|
+
for (const eventName of added.concat(updated)) {
|
|
539
|
+
addedListeners[eventName] = addEventListener(eventName, newEvents[eventName], el, hostComponent);
|
|
540
|
+
}
|
|
541
|
+
return addedListeners;
|
|
542
|
+
}
|
|
543
|
+
function patchChildren(oldVNode, newVNode, hostComponent) {
|
|
544
|
+
const oldChildren = extractChildren(oldVNode);
|
|
545
|
+
const newChildren = extractChildren(newVNode);
|
|
546
|
+
const parentEl = oldVNode.el;
|
|
547
|
+
const diffSeq = arraysDiffSequence(oldChildren, newChildren, areNodesEqual);
|
|
548
|
+
for (const operation of diffSeq) {
|
|
549
|
+
const { originalIndex, from, index, item } = operation;
|
|
550
|
+
const offset = hostComponent?.offset ?? 0;
|
|
551
|
+
switch (operation.op) {
|
|
552
|
+
case ARRAY_DIFF_OP.ADD: {
|
|
553
|
+
mountDOM(item, parentEl, index, hostComponent);
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
case ARRAY_DIFF_OP.REMOVE: {
|
|
557
|
+
destroyDOM(item);
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
case ARRAY_DIFF_OP.MOVE: {
|
|
561
|
+
const el = oldChildren[from].el;
|
|
562
|
+
const elAtTargetIndex = parentEl.childNodes[index + offset];
|
|
563
|
+
parentEl.insertBefore(el, elAtTargetIndex);
|
|
564
|
+
patchDOM(oldChildren[from], newChildren[index], parentEl, hostComponent);
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
case ARRAY_DIFF_OP.NOOP: {
|
|
568
|
+
patchDOM(oldChildren[originalIndex], newChildren[index], parentEl, hostComponent);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function createApp(RootComponent, props = {}) {
|
|
576
|
+
let parentEl = null;
|
|
577
|
+
let rootNode = null;
|
|
578
|
+
let isMounted = false;
|
|
579
|
+
const dispatcher = new Dispatcher();
|
|
580
|
+
[dispatcher.afterEveryCommand(renderApp)];
|
|
581
|
+
function reset() {
|
|
582
|
+
parentEl = null;
|
|
583
|
+
rootNode = null;
|
|
584
|
+
isMounted = false;
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
mount(_parentEl) {
|
|
588
|
+
if (isMounted) throw new Error('The application is already mounted');
|
|
589
|
+
parentEl = _parentEl;
|
|
590
|
+
rootNode = h(RootComponent, props);
|
|
591
|
+
mountDOM(rootNode, parentEl);
|
|
592
|
+
isMounted = true;
|
|
593
|
+
},
|
|
594
|
+
unmount() {
|
|
595
|
+
if (!isMounted) throw new Error('The application is not mounted');
|
|
596
|
+
destroyDOM(rootNode);
|
|
597
|
+
reset();
|
|
598
|
+
},
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function getDefaultExportFromCjs (x) {
|
|
603
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
var fastDeepEqual;
|
|
607
|
+
var hasRequiredFastDeepEqual;
|
|
608
|
+
function requireFastDeepEqual () {
|
|
609
|
+
if (hasRequiredFastDeepEqual) return fastDeepEqual;
|
|
610
|
+
hasRequiredFastDeepEqual = 1;
|
|
611
|
+
fastDeepEqual = function equal(a, b) {
|
|
612
|
+
if (a === b) return true;
|
|
613
|
+
if (a && b && typeof a == 'object' && typeof b == 'object') {
|
|
614
|
+
if (a.constructor !== b.constructor) return false;
|
|
615
|
+
var length, i, keys;
|
|
616
|
+
if (Array.isArray(a)) {
|
|
617
|
+
length = a.length;
|
|
618
|
+
if (length != b.length) return false;
|
|
619
|
+
for (i = length; i-- !== 0;)
|
|
620
|
+
if (!equal(a[i], b[i])) return false;
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
|
|
624
|
+
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
|
|
625
|
+
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
|
626
|
+
keys = Object.keys(a);
|
|
627
|
+
length = keys.length;
|
|
628
|
+
if (length !== Object.keys(b).length) return false;
|
|
629
|
+
for (i = length; i-- !== 0;)
|
|
630
|
+
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
|
|
631
|
+
for (i = length; i-- !== 0;) {
|
|
632
|
+
var key = keys[i];
|
|
633
|
+
if (!equal(a[key], b[key])) return false;
|
|
634
|
+
}
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
return a!==a && b!==b;
|
|
638
|
+
};
|
|
639
|
+
return fastDeepEqual;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
var fastDeepEqualExports = requireFastDeepEqual();
|
|
643
|
+
var equal = /*@__PURE__*/getDefaultExportFromCjs(fastDeepEqualExports);
|
|
644
|
+
|
|
645
|
+
function defineComponent({ render, state, ...methods }) {
|
|
646
|
+
class Component {
|
|
647
|
+
#isMounted = false;
|
|
648
|
+
#vNode = null;
|
|
649
|
+
#hostEl = null;
|
|
650
|
+
#eventHandlers = null;
|
|
651
|
+
#parentComponent = null;
|
|
652
|
+
#dispatcher = new Dispatcher();
|
|
653
|
+
#subscriptions = [];
|
|
654
|
+
constructor(props = {}, eventHandlers = {}, parentComponent = null) {
|
|
655
|
+
this.props = props;
|
|
656
|
+
this.state = state ? state(props) : {};
|
|
657
|
+
this.#eventHandlers = eventHandlers;
|
|
658
|
+
this.#parentComponent = parentComponent;
|
|
659
|
+
}
|
|
660
|
+
get elements() {
|
|
661
|
+
if (this.#vNode == null) {
|
|
662
|
+
return [];
|
|
663
|
+
}
|
|
664
|
+
if (this.#vNode.type === DOM_TYPES.FRAGMENT) {
|
|
665
|
+
return extractChildren(this.#vNode).flatMap((child) => {
|
|
666
|
+
if (child.type === DOM_TYPES.COMPONENT) {
|
|
667
|
+
return child.component.elements;
|
|
668
|
+
}
|
|
669
|
+
return [child.el];
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
return [this.#vNode.el];
|
|
673
|
+
}
|
|
674
|
+
get firstElement() {
|
|
675
|
+
return this.elements[0];
|
|
676
|
+
}
|
|
677
|
+
get offset() {
|
|
678
|
+
if(this.#vNode.type === DOM_TYPES.FRAGMENT) {
|
|
679
|
+
return Array.from(this.#hostEl.children).indexOf(this.firstElement);
|
|
680
|
+
}
|
|
681
|
+
return 0;
|
|
682
|
+
}
|
|
683
|
+
updateProps(props) {
|
|
684
|
+
const newProps = { ...this.props, ...props };
|
|
685
|
+
if (equal(this.props, newProps)) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
this.props = newProps;
|
|
689
|
+
this.#patch();
|
|
690
|
+
}
|
|
691
|
+
updateState(state) {
|
|
692
|
+
this.state = { ...this.state, ...state };
|
|
693
|
+
this.#patch();
|
|
694
|
+
}
|
|
695
|
+
render() {
|
|
696
|
+
return render.call(this);
|
|
697
|
+
}
|
|
698
|
+
emit(eventName, payload) {
|
|
699
|
+
this.#dispatcher.dispatch(eventName, payload);
|
|
700
|
+
}
|
|
701
|
+
mount(hostEl, index = null) {
|
|
702
|
+
if (this.#isMounted) {
|
|
703
|
+
throw new Error('Component is already mounted');
|
|
704
|
+
}
|
|
705
|
+
this.#vNode = this.render();
|
|
706
|
+
mountDOM(this.#vNode, hostEl, index, this);
|
|
707
|
+
this.#wireEventHandlers();
|
|
708
|
+
this.#hostEl = hostEl;
|
|
709
|
+
this.#isMounted = true;
|
|
710
|
+
}
|
|
711
|
+
unmount() {
|
|
712
|
+
if (!this.#isMounted) {
|
|
713
|
+
throw new Error('Component is not mounted');
|
|
714
|
+
}
|
|
715
|
+
destroyDOM(this.#vNode);
|
|
716
|
+
this.#subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
717
|
+
this.#vNode = null;
|
|
718
|
+
this.#hostEl = null;
|
|
719
|
+
this.#isMounted = false;
|
|
720
|
+
this.#subscriptions = [];
|
|
721
|
+
}
|
|
722
|
+
#patch() {
|
|
723
|
+
if (!this.#isMounted) {
|
|
724
|
+
throw new Error('Component is not mounted');
|
|
725
|
+
}
|
|
726
|
+
const vNode = this.render();
|
|
727
|
+
this.#vNode = patchDOM(this.#vNode, vNode, this.#hostEl, this);
|
|
728
|
+
}
|
|
729
|
+
#wireEventHandlers() {
|
|
730
|
+
this.#subscriptions = Object.entries(this.#eventHandlers).map(
|
|
731
|
+
([eventName, handler]) =>
|
|
732
|
+
this.#wireEventHandler(eventName, handler)
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
#wireEventHandler(eventName, handler) {
|
|
736
|
+
return this.#dispatcher.subscribe(eventName, (payload) => {
|
|
737
|
+
if (this.#parentComponent) {
|
|
738
|
+
handler.call(this.#parentComponent, payload);
|
|
739
|
+
} else {
|
|
740
|
+
handler(payload);
|
|
741
|
+
}
|
|
742
|
+
})
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
for (const methodName in methods) {
|
|
746
|
+
if (hasOwnProperty(Component, methodName)) {
|
|
747
|
+
throw new Error(`Method "${methodName}()" already exists in the component.`);
|
|
748
|
+
}
|
|
749
|
+
Component.prototype[methodName] = methods[methodName];
|
|
750
|
+
}
|
|
751
|
+
return Component;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export { createApp, defineComponent, h, hFragment, hString };
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ks-fwork",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Demo Frontend Framework",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"main": "
|
|
9
|
-
"
|
|
8
|
+
"main": "dist/ks-fwork.js",
|
|
9
|
+
"module": "dist/ks-fwork.js",
|
|
10
|
+
"exports": "./dist/ks-fwork.js",
|
|
10
11
|
"files": [
|
|
11
12
|
"src"
|
|
12
13
|
],
|