ks-fwork 4.1.0 → 4.1.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 +572 -791
- package/package.json +4 -7
- package/src/app.js +0 -36
- package/src/attributes.js +0 -62
- package/src/component.js +0 -159
- package/src/destroy-dom.js +0 -64
- package/src/dispatcher.js +0 -56
- package/src/events.js +0 -28
- package/src/h.js +0 -74
- package/src/index.js +0 -4
- package/src/mount-dom.js +0 -108
- package/src/nodes-equal.js +0 -23
- package/src/patch-dom.js +0 -212
- package/src/scheduler.js +0 -37
- package/src/utils/arrays.js +0 -203
- package/src/utils/objects.js +0 -16
- package/src/utils/props.js +0 -6
- package/src/utils/strings.js +0 -7
package/src/patch-dom.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import { areNodesEqual } from "./nodes-equal.js";
|
|
2
|
-
import { destroyDOM } from "./destroy-dom.js";
|
|
3
|
-
import { mountDOM } from "./mount-dom.js";
|
|
4
|
-
import { DOM_TYPES, extractChildren } from "./h.js";
|
|
5
|
-
import { objectsDiff } from "./utils/objects.js";
|
|
6
|
-
import { removeAttribute, removeStyle, setAttribute, setStyle } from "./attributes.js";
|
|
7
|
-
import { ARRAY_DIFF_OP, arraysDiff, arraysDiffSequence } from "./utils/arrays.js";
|
|
8
|
-
import { isNotBlankOrEmptyString } from "./utils/strings.js";
|
|
9
|
-
import { addEventListener } from "./events.js";
|
|
10
|
-
import { extractPropsAndEvents } from "./utils/props.js";
|
|
11
|
-
|
|
12
|
-
export function patchDOM(oldVNode, newVNode, parentEl, hostComponent = null) {
|
|
13
|
-
// Nodes are too different to reuse/patch.
|
|
14
|
-
if (!areNodesEqual(oldVNode, newVNode)) {
|
|
15
|
-
// Remember where the old node was located inside the parent so the replacement lands in the same spot.
|
|
16
|
-
const index = findIndexInParent(parentEl, oldVNode.el);
|
|
17
|
-
|
|
18
|
-
// Destroy the old node and its whole subtree.
|
|
19
|
-
destroyDOM(oldVNode);
|
|
20
|
-
|
|
21
|
-
// Mount the new node and its whole subtree at the index of the old node.
|
|
22
|
-
mountDOM(newVNode, parentEl, index, hostComponent);
|
|
23
|
-
|
|
24
|
-
return newVNode;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Copy over the DOM element reference
|
|
28
|
-
newVNode.el = oldVNode.el;
|
|
29
|
-
|
|
30
|
-
switch (newVNode.type) {
|
|
31
|
-
case DOM_TYPES.TEXT: {
|
|
32
|
-
patchText(oldVNode, newVNode);
|
|
33
|
-
return newVNode;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
case DOM_TYPES.ELEMENT: {
|
|
37
|
-
patchElement(oldVNode, newVNode, hostComponent);
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
case DOM_TYPES.COMPONENT: {
|
|
42
|
-
patchComponent(oldVNode, newVNode);
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
patchChildren(oldVNode, newVNode, hostComponent);
|
|
48
|
-
|
|
49
|
-
return newVNode;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Get index of the element inside the parent element
|
|
53
|
-
function findIndexInParent(parentEl, el) {
|
|
54
|
-
const index = Array.from(parentEl.childNodes).indexOf(el);
|
|
55
|
-
if (index < 0) return null;
|
|
56
|
-
|
|
57
|
-
return index;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function patchText(oldVNode, newVNode) {
|
|
61
|
-
const el = oldVNode.el;
|
|
62
|
-
const { value: oldText } = oldVNode;
|
|
63
|
-
const { value: newText } = newVNode;
|
|
64
|
-
|
|
65
|
-
if (oldText !== newText) {
|
|
66
|
-
el.nodeValue = newText;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function patchElement(oldVNode, newVNode, hostComponent) {
|
|
71
|
-
const el = oldVNode.el;
|
|
72
|
-
|
|
73
|
-
const {
|
|
74
|
-
class: oldClass,
|
|
75
|
-
style: oldStyle,
|
|
76
|
-
on: oldEvents,
|
|
77
|
-
...oldAttrs
|
|
78
|
-
} = oldVNode.props;
|
|
79
|
-
|
|
80
|
-
const {
|
|
81
|
-
class: newClass,
|
|
82
|
-
style: newStyle,
|
|
83
|
-
on: newEvents,
|
|
84
|
-
...newAttrs
|
|
85
|
-
} = newVNode.props;
|
|
86
|
-
|
|
87
|
-
const { listeners: oldListeners } = oldVNode;
|
|
88
|
-
|
|
89
|
-
patchAttrs(el, oldAttrs, newAttrs);
|
|
90
|
-
patchClasses(el, oldClass, newClass);
|
|
91
|
-
patchStyles(el, oldStyle, newStyle);
|
|
92
|
-
newVNode.listeners = patchEvents(el, oldListeners, oldEvents, newEvents, hostComponent);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function patchComponent(oldVNode, newVNode) {
|
|
96
|
-
const { component } = oldVNode;
|
|
97
|
-
const { props } = extractPropsAndEvents(newVNode);
|
|
98
|
-
|
|
99
|
-
component.updateProps(props);
|
|
100
|
-
|
|
101
|
-
newVNode.component = component;
|
|
102
|
-
newVNode.el = component.firstElement;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function patchAttrs(el, oldAttrs, newAttrs) {
|
|
106
|
-
const { added, removed, updated } = objectsDiff(oldAttrs, newAttrs);
|
|
107
|
-
|
|
108
|
-
for (const attr of removed) {
|
|
109
|
-
removeAttribute(el, attr);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Concatenate added and updated attributes and set them to element.
|
|
113
|
-
for (const attr of added.concat(updated)) {
|
|
114
|
-
setAttribute(el, attr, newAttrs[attr]);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function patchClasses(el, oldClass, newClass) {
|
|
119
|
-
const oldClasses = toClassList(oldClass);
|
|
120
|
-
const newClasses = toClassList(newClass);
|
|
121
|
-
|
|
122
|
-
const { added, removed } = arraysDiff(oldClasses, newClasses);
|
|
123
|
-
|
|
124
|
-
if (removed.length > 0) {
|
|
125
|
-
el.classList.remove(...removed);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (added.length > 0) {
|
|
129
|
-
el.classList.add(...added);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function toClassList(classes = '') {
|
|
134
|
-
return Array.isArray(classes)
|
|
135
|
-
? classes.filter(isNotBlankOrEmptyString)
|
|
136
|
-
: classes.split(/(\s+)/).filter(isNotBlankOrEmptyString);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function patchStyles(el, oldStyle = {}, newStyle = {}) {
|
|
140
|
-
const { added, removed, updated} = objectsDiff(oldStyle, newStyle);
|
|
141
|
-
|
|
142
|
-
for (const style of removed) {
|
|
143
|
-
removeStyle(el, style);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
for (const style of added.concat(updated)) {
|
|
147
|
-
setStyle(el, style, newStyle[style]);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function patchEvents(el, oldListeners = {}, oldEvents = {}, newEvents = {}, hostComponent) {
|
|
152
|
-
const { removed, added, updated} = objectsDiff(oldEvents, newEvents);
|
|
153
|
-
|
|
154
|
-
for (const eventName of removed.concat(updated)) {
|
|
155
|
-
el.removeEventListener(eventName, oldListeners[eventName]);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const addedListeners = {};
|
|
159
|
-
|
|
160
|
-
for (const eventName of added.concat(updated)) {
|
|
161
|
-
addedListeners[eventName] = addEventListener(eventName, newEvents[eventName], el, hostComponent);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return addedListeners;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Reconcile a node's child lists.
|
|
168
|
-
function patchChildren(oldVNode, newVNode, hostComponent) {
|
|
169
|
-
const oldChildren = extractChildren(oldVNode);
|
|
170
|
-
const newChildren = extractChildren(newVNode);
|
|
171
|
-
const parentEl = oldVNode.el;
|
|
172
|
-
|
|
173
|
-
// Create the sequence of operations to match the note's child lists.
|
|
174
|
-
const diffSeq = arraysDiffSequence(oldChildren, newChildren, areNodesEqual);
|
|
175
|
-
|
|
176
|
-
// Loop over the operations and perform the operations on the DOM.
|
|
177
|
-
for (const operation of diffSeq) {
|
|
178
|
-
const { originalIndex, from, index, item } = operation;
|
|
179
|
-
// Get the host component’s offset, if there is one; offset is 0 otherwise
|
|
180
|
-
const offset = hostComponent?.offset ?? 0;
|
|
181
|
-
|
|
182
|
-
switch (operation.op) {
|
|
183
|
-
// New node with no old counterpart: build its subtree and insert it at `index`
|
|
184
|
-
case ARRAY_DIFF_OP.ADD: {
|
|
185
|
-
mountDOM(item, parentEl, index, hostComponent)
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Old node gone from the new list: destroy it and its subtree
|
|
190
|
-
case ARRAY_DIFF_OP.REMOVE: {
|
|
191
|
-
destroyDOM(item);
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Node still present but at a different position: move the existing node to its new spot, then patch it in place for any inner changes
|
|
196
|
-
case ARRAY_DIFF_OP.MOVE: {
|
|
197
|
-
const el = oldChildren[from].el;
|
|
198
|
-
const elAtTargetIndex = parentEl.childNodes[index + offset];
|
|
199
|
-
parentEl.insertBefore(el, elAtTargetIndex);
|
|
200
|
-
|
|
201
|
-
patchDOM(oldChildren[from], newChildren[index], parentEl, hostComponent);
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Same node, same position, but its contents may still differ, so recurse
|
|
206
|
-
case ARRAY_DIFF_OP.NOOP: {
|
|
207
|
-
patchDOM(oldChildren[originalIndex], newChildren[index], parentEl, hostComponent);
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
package/src/scheduler.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
let isScheduled = false;
|
|
2
|
-
const jobs = [];
|
|
3
|
-
|
|
4
|
-
export function enqueueJob(job) {
|
|
5
|
-
jobs.push(job);
|
|
6
|
-
scheduleUpdate();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function scheduleUpdate() {
|
|
10
|
-
if (isScheduled) return;
|
|
11
|
-
|
|
12
|
-
isScheduled = true;
|
|
13
|
-
queueMicrotask(processJobs);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function processJobs() {
|
|
17
|
-
while (jobs.length > 0) {
|
|
18
|
-
const job = jobs.shift();
|
|
19
|
-
try {
|
|
20
|
-
Promise.resolve(job()).catch((error) => {
|
|
21
|
-
console.error(`[scheduler]: ${error}`);
|
|
22
|
-
});
|
|
23
|
-
} catch (error) {
|
|
24
|
-
console.error(`[scheduler]: ${error}`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
isScheduled = false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function nextTick() {
|
|
31
|
-
scheduleUpdate();
|
|
32
|
-
return flushPromises();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function flushPromises() {
|
|
36
|
-
return new Promise((resolve) => setTimeout(resolve));
|
|
37
|
-
}
|
package/src/utils/arrays.js
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
// Filter out null values from the array.
|
|
2
|
-
export function withoutNulls(arr) {
|
|
3
|
-
return arr.filter((item) => item != null);
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function arraysDiff(oldArray, newArray) {
|
|
7
|
-
return {
|
|
8
|
-
added: newArray.filter(
|
|
9
|
-
(newItem) => !oldArray.includes(newItem)
|
|
10
|
-
),
|
|
11
|
-
removed: oldArray.filter(
|
|
12
|
-
(oldItem) => !newArray.includes(oldItem)
|
|
13
|
-
),
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const ARRAY_DIFF_OP = {
|
|
18
|
-
ADD: 'add',
|
|
19
|
-
REMOVE: 'remove',
|
|
20
|
-
MOVE: 'move',
|
|
21
|
-
NOOP: 'noop'
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
class ArrayWithOriginalIndices {
|
|
25
|
-
#oldArray = [];
|
|
26
|
-
#originalIndices = [];
|
|
27
|
-
#equalsFn;
|
|
28
|
-
|
|
29
|
-
constructor(array, equalsFn) {
|
|
30
|
-
this.#oldArray = [...array];
|
|
31
|
-
this.#originalIndices = array.map((value, index) => index);
|
|
32
|
-
this.#equalsFn = equalsFn;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
get length() {
|
|
36
|
-
return this.#oldArray.length;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
originalIndexAt(index) {
|
|
40
|
-
return this.#originalIndices[index];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ADD
|
|
44
|
-
isAddition(item, startIndex) {
|
|
45
|
-
return this.findIndexFrom(item, startIndex) === -1;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
addItem(item, index) {
|
|
49
|
-
const operation = {
|
|
50
|
-
op: ARRAY_DIFF_OP.ADD,
|
|
51
|
-
index,
|
|
52
|
-
item
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
this.#oldArray.splice(index, 0, item);
|
|
56
|
-
this.#originalIndices.splice(index, 0, -1);
|
|
57
|
-
|
|
58
|
-
return operation;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// REMOVE
|
|
62
|
-
isRemoval(index, newArray) {
|
|
63
|
-
// Bail out if index is past the end of the old array.
|
|
64
|
-
if (index >= this.length) { return false; }
|
|
65
|
-
|
|
66
|
-
const item = this.#oldArray[index];
|
|
67
|
-
const indexInNewArray = newArray.findIndex((newArrayItem) => this.#equalsFn(item, newArrayItem))
|
|
68
|
-
|
|
69
|
-
return indexInNewArray === -1;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
removeItem(index) {
|
|
73
|
-
const operation = {
|
|
74
|
-
op: ARRAY_DIFF_OP.REMOVE,
|
|
75
|
-
index,
|
|
76
|
-
item: this.#oldArray[index],
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
this.#oldArray.splice(index, 1);
|
|
80
|
-
this.#originalIndices.splice(index, 1);
|
|
81
|
-
|
|
82
|
-
return operation;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// MOVE
|
|
86
|
-
moveItem(item, toIndex) {
|
|
87
|
-
const fromIndex = this.findIndexFrom(item, toIndex);
|
|
88
|
-
|
|
89
|
-
const operation = {
|
|
90
|
-
op: ARRAY_DIFF_OP.MOVE,
|
|
91
|
-
originalIndex: this.originalIndexAt(fromIndex),
|
|
92
|
-
from: fromIndex,
|
|
93
|
-
index: toIndex,
|
|
94
|
-
item: this.#oldArray[fromIndex],
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const [itemToMove] = this.#oldArray.splice(fromIndex, 1);
|
|
98
|
-
this.#oldArray.splice(toIndex, 0, itemToMove);
|
|
99
|
-
|
|
100
|
-
const [originalIndex] = this.#originalIndices.splice(fromIndex, 1);
|
|
101
|
-
this.#originalIndices.splice(toIndex, 0, originalIndex);
|
|
102
|
-
|
|
103
|
-
return operation;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// NOOP
|
|
107
|
-
isNoop(index, newArray) {
|
|
108
|
-
// Bail out if index is past the end of the old array.
|
|
109
|
-
if (index >= this.length) {return false; }
|
|
110
|
-
|
|
111
|
-
const item = this.#oldArray[index];
|
|
112
|
-
const newArrayItem = newArray[index];
|
|
113
|
-
|
|
114
|
-
return this.#equalsFn(item, newArrayItem);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
noopItem(index) {
|
|
118
|
-
return {
|
|
119
|
-
op: ARRAY_DIFF_OP.NOOP,
|
|
120
|
-
originalIndex: this.originalIndexAt(index),
|
|
121
|
-
index,
|
|
122
|
-
item: this.#oldArray[index],
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Remove all items after specified index
|
|
127
|
-
removeItemsAfter(index) {
|
|
128
|
-
const operations = [];
|
|
129
|
-
|
|
130
|
-
while (this.length > index) {
|
|
131
|
-
operations.push(this.removeItem(index))
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return operations;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Return the first index at or after startIndex where the item matches, or -1 if item is not found.
|
|
138
|
-
findIndexFrom(item, startIndex) {
|
|
139
|
-
for (let i = startIndex; i < this.length; i++) {
|
|
140
|
-
if (this.#equalsFn(item, this.#oldArray[i])) {
|
|
141
|
-
return i;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return -1;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function arraysDiffSequence(oldArray, newArray, equalsFn = (a, b) => a === b) {
|
|
150
|
-
const sequence = [];
|
|
151
|
-
|
|
152
|
-
// Store old array with original indices preserved.
|
|
153
|
-
const array = new ArrayWithOriginalIndices(oldArray, equalsFn);
|
|
154
|
-
|
|
155
|
-
for (let index = 0; index < newArray.length; index++) {
|
|
156
|
-
if (array.isRemoval(index, newArray)) {
|
|
157
|
-
sequence.push(array.removeItem(index));
|
|
158
|
-
index--
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (array.isNoop(index, newArray)) {
|
|
163
|
-
sequence.push(array.noopItem(index));
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const item = newArray[index];
|
|
168
|
-
|
|
169
|
-
if (array.isAddition(item, index)) {
|
|
170
|
-
sequence.push(array.addItem(item, index));
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
sequence.push(array.moveItem(item, index));
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
sequence.push(...array.removeItemsAfter(newArray.length));
|
|
178
|
-
|
|
179
|
-
return sequence;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function applyArraysDiffSequence(oldArray, diffSequence) {
|
|
183
|
-
return diffSequence.reduce((array, { op, item, index, from }) => {
|
|
184
|
-
switch (op) {
|
|
185
|
-
// Insert an item at the specified index.
|
|
186
|
-
case 'add':
|
|
187
|
-
array.splice(index, 0, item)
|
|
188
|
-
break
|
|
189
|
-
|
|
190
|
-
// Remove an element at the specified index.
|
|
191
|
-
case 'remove':
|
|
192
|
-
array.splice(index, 1)
|
|
193
|
-
break
|
|
194
|
-
|
|
195
|
-
// Remove element at index 'from' and move it to specified index.
|
|
196
|
-
case 'move':
|
|
197
|
-
array.splice(index, 0, array.splice(from, 1)[0])
|
|
198
|
-
break
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return array
|
|
202
|
-
}, oldArray)
|
|
203
|
-
}
|
package/src/utils/objects.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export function objectsDiff(oldObj, newObj) {
|
|
2
|
-
const oldKeys = Object.keys(oldObj);
|
|
3
|
-
const newKeys = Object.keys(newObj);
|
|
4
|
-
|
|
5
|
-
return {
|
|
6
|
-
added: newKeys.filter((newKey) => !(newKey in oldObj)),
|
|
7
|
-
removed: oldKeys.filter((oldKey) => !(oldKey in newObj)),
|
|
8
|
-
updated: newKeys.filter(
|
|
9
|
-
(key) => key in oldObj && oldObj[key] !== newObj[key]
|
|
10
|
-
),
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function hasOwnProperty(obj, prop) {
|
|
15
|
-
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
16
|
-
}
|
package/src/utils/props.js
DELETED