native-document 1.0.14 → 1.0.15
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/native-document.dev.js +1262 -839
- package/dist/native-document.min.js +1 -1
- package/docs/anchor.md +216 -53
- package/docs/conditional-rendering.md +25 -24
- package/docs/core-concepts.md +20 -19
- package/docs/elements.md +21 -20
- package/docs/getting-started.md +28 -27
- package/docs/lifecycle-events.md +2 -2
- package/docs/list-rendering.md +607 -0
- package/docs/memory-management.md +1 -1
- package/docs/observables.md +15 -14
- package/docs/routing.md +22 -22
- package/docs/state-management.md +8 -8
- package/docs/validation.md +0 -2
- package/index.js +6 -1
- package/package.json +1 -1
- package/readme.md +5 -4
- package/src/data/MemoryManager.js +8 -20
- package/src/data/Observable.js +2 -180
- package/src/data/ObservableChecker.js +25 -24
- package/src/data/ObservableItem.js +158 -79
- package/src/data/observable-helpers/array.js +74 -0
- package/src/data/observable-helpers/batch.js +22 -0
- package/src/data/observable-helpers/computed.js +28 -0
- package/src/data/observable-helpers/object.js +111 -0
- package/src/elements/anchor.js +54 -9
- package/src/elements/control/for-each-array.js +280 -0
- package/src/elements/control/for-each.js +87 -110
- package/src/elements/index.js +1 -0
- package/src/elements/list.js +4 -0
- package/src/utils/helpers.js +44 -21
- package/src/wrappers/AttributesWrapper.js +5 -18
- package/src/wrappers/DocumentObserver.js +58 -29
- package/src/wrappers/ElementCreator.js +114 -0
- package/src/wrappers/HtmlElementEventsWrapper.js +52 -65
- package/src/wrappers/HtmlElementWrapper.js +11 -167
- package/src/wrappers/NdPrototype.js +109 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import Anchor from "../anchor";
|
|
2
|
+
import {Observable} from "../../data/Observable";
|
|
3
|
+
import Validator from "../../utils/validator";
|
|
4
|
+
import {createTextNode} from "../../wrappers/HtmlElementWrapper";
|
|
5
|
+
import DebugManager from "../../utils/debug-manager";
|
|
6
|
+
import {getKey} from "../../utils/helpers";
|
|
7
|
+
|
|
8
|
+
export function ForEachArray(data, callback, key, configs = {}) {
|
|
9
|
+
const element = new Anchor('ForEach Array');
|
|
10
|
+
const blockEnd = element.endElement();
|
|
11
|
+
const blockStart = element.startElement();
|
|
12
|
+
|
|
13
|
+
let cache = new Map();
|
|
14
|
+
let nodeCacheByElement = new WeakMap();
|
|
15
|
+
let lastNumberOfItems = 0;
|
|
16
|
+
|
|
17
|
+
const keysCache = new WeakMap();
|
|
18
|
+
|
|
19
|
+
const clear = () => {
|
|
20
|
+
element.removeChildren();
|
|
21
|
+
cleanCache();
|
|
22
|
+
lastNumberOfItems = 0;
|
|
23
|
+
};
|
|
24
|
+
const getItemKey = (item, indexKey) => {
|
|
25
|
+
if(keysCache.has(item)) {
|
|
26
|
+
return keysCache.get(item);
|
|
27
|
+
}
|
|
28
|
+
return getKey(item, indexKey, key);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const updateIndexObservers = (items, startFrom = 0) => {
|
|
32
|
+
if(callback.length < 2) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let index = startFrom;
|
|
36
|
+
for(let i = startFrom, length = items?.length; i < length; i++) {
|
|
37
|
+
const cacheItem = cache.get(getItemKey(items[i], i));
|
|
38
|
+
if(!cacheItem) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
cacheItem.indexObserver?.deref()?.set(index);
|
|
42
|
+
index++;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const removeCacheItem = (cacheItem, removeChild = true) => {
|
|
47
|
+
if(!cacheItem) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const child = cacheItem.child?.deref();
|
|
51
|
+
cacheItem.indexObserver?.deref()?.cleanup();
|
|
52
|
+
cacheItem.child = null;
|
|
53
|
+
cacheItem.indexObserver = null;
|
|
54
|
+
nodeCacheByElement.delete(cacheItem.item);
|
|
55
|
+
keysCache.delete(cacheItem.item);
|
|
56
|
+
cacheItem.item = null;
|
|
57
|
+
if(removeChild) {
|
|
58
|
+
child?.remove();
|
|
59
|
+
cache.delete(cacheItem.keyId);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const removeCacheItemByKey = (keyId, removeChild = true) => {
|
|
64
|
+
removeCacheItem(cache.get(keyId), removeChild);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const cleanCache = () => {
|
|
68
|
+
for (const [keyId, cacheItem] of cache.entries()) {
|
|
69
|
+
removeCacheItem(cacheItem, false);
|
|
70
|
+
}
|
|
71
|
+
cache.clear();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const buildItem = (item, indexKey) => {
|
|
75
|
+
const keyId = getItemKey(item, indexKey);
|
|
76
|
+
|
|
77
|
+
if(cache.has(keyId)) {
|
|
78
|
+
const cacheItem = cache.get(keyId);
|
|
79
|
+
cacheItem.indexObserver?.deref()?.set(indexKey);
|
|
80
|
+
cacheItem.isNew = false;
|
|
81
|
+
const child = cacheItem.child?.deref();
|
|
82
|
+
if(child) {
|
|
83
|
+
return child;
|
|
84
|
+
}
|
|
85
|
+
cache.delete(keyId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
|
|
90
|
+
let child = callback(item, indexObserver);
|
|
91
|
+
if(Validator.isStringOrObservable(child)) {
|
|
92
|
+
child = createTextNode(child);
|
|
93
|
+
}
|
|
94
|
+
cache.set(keyId, {
|
|
95
|
+
keyId,
|
|
96
|
+
isNew: true,
|
|
97
|
+
item,
|
|
98
|
+
child: new WeakRef(child),
|
|
99
|
+
indexObserver: (indexObserver ? new WeakRef(indexObserver) : null)
|
|
100
|
+
});
|
|
101
|
+
keysCache.set(item, keyId);
|
|
102
|
+
if(Validator.isObject(item)) {
|
|
103
|
+
nodeCacheByElement.set(item, child);
|
|
104
|
+
}
|
|
105
|
+
return child;
|
|
106
|
+
} catch (e) {
|
|
107
|
+
DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
|
|
108
|
+
throw e;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const getChildByKey = function(keyId, fragment) {
|
|
112
|
+
const cacheItem = cache.get(keyId);
|
|
113
|
+
if(!cacheItem) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const child = cacheItem.child?.deref();
|
|
117
|
+
if(!child) {
|
|
118
|
+
removeCacheItem(cacheItem, false);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return child;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const removeByKey = function(keyId, fragment) {
|
|
125
|
+
const cacheItem = cache.get(keyId);
|
|
126
|
+
if(!cacheItem) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const child = cacheItem.child?.deref();
|
|
130
|
+
if(!child) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if(fragment) {
|
|
135
|
+
fragment.appendChild(child);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
child.remove();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const Actions = {
|
|
142
|
+
toFragment(items, startIndexFrom = 0){
|
|
143
|
+
const fragment = document.createDocumentFragment();
|
|
144
|
+
for(let i = 0, length = items.length; i < length; i++) {
|
|
145
|
+
fragment.append(buildItem(items[i], lastNumberOfItems));
|
|
146
|
+
lastNumberOfItems++;
|
|
147
|
+
}
|
|
148
|
+
return fragment;
|
|
149
|
+
},
|
|
150
|
+
add(items, delay = 0) {
|
|
151
|
+
setTimeout(() => {
|
|
152
|
+
element.appendElement(Actions.toFragment(items))
|
|
153
|
+
}, delay);
|
|
154
|
+
},
|
|
155
|
+
replace(items) {
|
|
156
|
+
clear();
|
|
157
|
+
Actions.add(items);
|
|
158
|
+
},
|
|
159
|
+
reOrder(items) {
|
|
160
|
+
let child = null;
|
|
161
|
+
const fragment = document.createDocumentFragment();
|
|
162
|
+
for(const item of items) {
|
|
163
|
+
child = nodeCacheByElement.get(item);
|
|
164
|
+
if(child) {
|
|
165
|
+
fragment.appendChild(child);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
child = null;
|
|
169
|
+
element.appendElement(fragment, blockEnd);
|
|
170
|
+
},
|
|
171
|
+
removeOne(element, index) {
|
|
172
|
+
let child = nodeCacheByElement.get(element);
|
|
173
|
+
if(child) {
|
|
174
|
+
child.remove();
|
|
175
|
+
nodeCacheByElement.delete(element);
|
|
176
|
+
removeCacheItemByKey(getItemKey(element, index));
|
|
177
|
+
}
|
|
178
|
+
child = null;
|
|
179
|
+
},
|
|
180
|
+
clear,
|
|
181
|
+
push(items) {
|
|
182
|
+
let delay = 0;
|
|
183
|
+
if(configs.pushDelay) {
|
|
184
|
+
delay = configs.pushDelay(items) ?? 0;
|
|
185
|
+
} else {
|
|
186
|
+
delay = (items.length >= 1000) ? 10 : 0;
|
|
187
|
+
}
|
|
188
|
+
Actions.add(items, delay);
|
|
189
|
+
},
|
|
190
|
+
unshift(values){
|
|
191
|
+
element.insertBefore(Actions.toFragment(values), blockStart.nextSibling);
|
|
192
|
+
},
|
|
193
|
+
splice(args, deleted) {
|
|
194
|
+
const [start, deleteCount, ...values] = args;
|
|
195
|
+
let elementBeforeFirst = null;
|
|
196
|
+
const garbageFragment = document.createDocumentFragment();
|
|
197
|
+
|
|
198
|
+
if(deleted.length > 0) {
|
|
199
|
+
let firstKey = getItemKey(deleted[0], start);
|
|
200
|
+
if(deleted.length === 1) {
|
|
201
|
+
removeByKey(firstKey, garbageFragment);
|
|
202
|
+
} else if(deleted.length > 1) {
|
|
203
|
+
const firstChildRemoved = getChildByKey(firstKey);
|
|
204
|
+
elementBeforeFirst = firstChildRemoved?.previousSibling;
|
|
205
|
+
|
|
206
|
+
for(let i = 0; i < deleted.length; i++) {
|
|
207
|
+
const keyId = getItemKey(deleted[i], start + i, key);
|
|
208
|
+
removeByKey(keyId, garbageFragment);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
elementBeforeFirst = blockEnd;
|
|
213
|
+
}
|
|
214
|
+
garbageFragment.replaceChildren();
|
|
215
|
+
|
|
216
|
+
if(values && values.length && elementBeforeFirst) {
|
|
217
|
+
element.insertBefore(Actions.toFragment(values), elementBeforeFirst.nextSibling);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
},
|
|
221
|
+
reverse(_, reversed) {
|
|
222
|
+
Actions.reOrder(reversed);
|
|
223
|
+
},
|
|
224
|
+
sort(_, sorted) {
|
|
225
|
+
Actions.reOrder(sorted);
|
|
226
|
+
},
|
|
227
|
+
remove(_, deleted) {
|
|
228
|
+
Actions.removeOne(deleted);
|
|
229
|
+
},
|
|
230
|
+
pop(_, deleted) {
|
|
231
|
+
Actions.removeOne(deleted);
|
|
232
|
+
},
|
|
233
|
+
shift(_, deleted) {
|
|
234
|
+
Actions.removeOne(deleted);
|
|
235
|
+
},
|
|
236
|
+
swap(args, elements) {
|
|
237
|
+
const parent = blockEnd.parentNode;
|
|
238
|
+
|
|
239
|
+
let childA = nodeCacheByElement.get(elements[0]);
|
|
240
|
+
let childB = nodeCacheByElement.get(elements[1]);
|
|
241
|
+
if(!childA || !childB) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const childBNext = childB.nextSibling;
|
|
246
|
+
parent.insertBefore(childB, childA);
|
|
247
|
+
parent.insertBefore(childA, childBNext);
|
|
248
|
+
childA = null;
|
|
249
|
+
childB = null;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const buildContent = (items, _, operations) => {
|
|
254
|
+
if(operations.action === 'clear' || !items.length) {
|
|
255
|
+
if(lastNumberOfItems === 0) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
clear();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if(!operations?.action) {
|
|
262
|
+
if(lastNumberOfItems === 0) {
|
|
263
|
+
Actions.add(items);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
Actions.replace(items);
|
|
267
|
+
}
|
|
268
|
+
else if(Actions[operations.action]) {
|
|
269
|
+
Actions[operations.action](operations.args, operations.result);
|
|
270
|
+
}
|
|
271
|
+
updateIndexObservers(items, 0);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
buildContent(data.val(), null, {action: null});
|
|
275
|
+
if(Validator.isObservable(data)) {
|
|
276
|
+
data.subscribe(buildContent);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return element;
|
|
280
|
+
}
|
|
@@ -2,49 +2,9 @@ import ObservableItem from "../../data/ObservableItem";
|
|
|
2
2
|
import {Observable} from "../../data/Observable";
|
|
3
3
|
import {createTextNode} from "../../wrappers/HtmlElementWrapper";
|
|
4
4
|
import Validator from "../../utils/validator";
|
|
5
|
-
import {throttle} from "../../utils/helpers.js";
|
|
6
5
|
import Anchor from "../anchor";
|
|
7
6
|
import DebugManager from "../../utils/debug-manager";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
*
|
|
12
|
-
* @param {*} item
|
|
13
|
-
* @param {string|null} defaultKey
|
|
14
|
-
* @param {?Function} key
|
|
15
|
-
* @returns {*}
|
|
16
|
-
*/
|
|
17
|
-
const getKey = (item, defaultKey, key) => {
|
|
18
|
-
if(Validator.isFunction(key)) return key(item, defaultKey);
|
|
19
|
-
if(Validator.isObservable(item)) {
|
|
20
|
-
const val = item.val();
|
|
21
|
-
return (val && key) ? val[key] : defaultKey;
|
|
22
|
-
}
|
|
23
|
-
return item[key] ?? defaultKey;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
* @param {Map} cache
|
|
29
|
-
* @param {Set} keyIds
|
|
30
|
-
*/
|
|
31
|
-
const cleanBlockByCache = (cache, keyIds) => {
|
|
32
|
-
const toRemove = [];
|
|
33
|
-
for(const [key, cacheItem] of cache.entries()) {
|
|
34
|
-
if(keyIds.has(key)) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
toRemove.push({ key, cacheItem });
|
|
38
|
-
}
|
|
39
|
-
if(toRemove.length === 0) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
toRemove.forEach(({ key, cacheItem }) => {
|
|
43
|
-
cacheItem.child.remove();
|
|
44
|
-
cacheItem.indexObserver.cleanup();
|
|
45
|
-
cache.delete(key);
|
|
46
|
-
});
|
|
47
|
-
}
|
|
7
|
+
import {getKey} from "../../utils/helpers";
|
|
48
8
|
|
|
49
9
|
/**
|
|
50
10
|
*
|
|
@@ -59,111 +19,128 @@ export function ForEach(data, callback, key) {
|
|
|
59
19
|
const blockStart = element.startElement();
|
|
60
20
|
|
|
61
21
|
let cache = new Map();
|
|
22
|
+
let lastKeyOrder = null;
|
|
62
23
|
const keyIds = new Set();
|
|
63
24
|
|
|
25
|
+
const clear = () => {
|
|
26
|
+
element.removeChildren();
|
|
27
|
+
cleanCache();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const cleanCache = (parent) => {
|
|
31
|
+
for(const [keyId, cacheItem] of cache.entries()) {
|
|
32
|
+
if(keyIds.has(keyId)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const child = cacheItem.child?.deref();
|
|
36
|
+
if(parent && child) {
|
|
37
|
+
parent.removeChild(child);
|
|
38
|
+
}
|
|
39
|
+
cacheItem.indexObserver?.cleanup();
|
|
40
|
+
cacheItem.child = null;
|
|
41
|
+
cacheItem.indexObserver = null;
|
|
42
|
+
cache.delete(cacheItem.keyId);
|
|
43
|
+
lastKeyOrder && lastKeyOrder.delete(cacheItem.keyId);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
64
47
|
const handleContentItem = (item, indexKey) => {
|
|
65
48
|
const keyId = getKey(item, indexKey, key);
|
|
66
49
|
|
|
67
50
|
if(cache.has(keyId)) {
|
|
68
51
|
const cacheItem = cache.get(keyId);
|
|
69
|
-
cacheItem.indexObserver
|
|
52
|
+
cacheItem.indexObserver?.set(indexKey);
|
|
70
53
|
cacheItem.isNew = false;
|
|
54
|
+
if(cacheItem.child?.deref()) {
|
|
55
|
+
return keyId;
|
|
56
|
+
}
|
|
57
|
+
cache.delete(keyId);
|
|
71
58
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
child = createTextNode(child);
|
|
79
|
-
}
|
|
80
|
-
cache.set(keyId, { isNew: true, child, indexObserver});
|
|
81
|
-
} catch (e) {
|
|
82
|
-
DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
|
|
83
|
-
throw e;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
|
|
62
|
+
let child = callback(item, indexObserver);
|
|
63
|
+
if(Validator.isStringOrObservable(child)) {
|
|
64
|
+
child = createTextNode(child);
|
|
84
65
|
}
|
|
66
|
+
cache.set(keyId, { keyId, isNew: true, child: new WeakRef(child), indexObserver});
|
|
67
|
+
} catch (e) {
|
|
68
|
+
DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
|
|
69
|
+
throw e;
|
|
85
70
|
}
|
|
86
71
|
return keyId;
|
|
87
72
|
};
|
|
88
73
|
|
|
89
|
-
const batchDOMUpdates = () => {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
74
|
+
const batchDOMUpdates = (parent) => {
|
|
75
|
+
const fragment = document.createDocumentFragment();
|
|
76
|
+
for(const itemKey of keyIds) {
|
|
77
|
+
const cacheItem = cache.get(itemKey);
|
|
78
|
+
if(!cacheItem) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const child = cacheItem.child?.deref();
|
|
82
|
+
child && fragment.appendChild(child);
|
|
93
83
|
}
|
|
84
|
+
parent.insertBefore(fragment, blockEnd);
|
|
85
|
+
}
|
|
94
86
|
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
const diffingDOMUpdates = (parent) => {
|
|
88
|
+
const operations = [];
|
|
89
|
+
let fragment = document.createDocumentFragment();
|
|
90
|
+
const newKeys = Array.from(keyIds);
|
|
91
|
+
const oldKeys = Array.from(lastKeyOrder);
|
|
99
92
|
|
|
100
|
-
let
|
|
101
|
-
if(fragment) {
|
|
102
|
-
elementsToInsert.push({ child: fragment, before: beforeTarget });
|
|
103
|
-
fragment = null;
|
|
104
|
-
}
|
|
105
|
-
};
|
|
93
|
+
let currentPosition = blockStart;
|
|
106
94
|
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
const itemKey = keyIdsArray[i];
|
|
95
|
+
for(const index in newKeys) {
|
|
96
|
+
const itemKey = newKeys[index];
|
|
110
97
|
const cacheItem = cache.get(itemKey);
|
|
111
98
|
if(!cacheItem) {
|
|
112
99
|
continue;
|
|
113
100
|
}
|
|
114
|
-
|
|
115
|
-
if(
|
|
116
|
-
previousElementSibling = cacheItem.child;
|
|
117
|
-
saveFragment(cacheItem.child);
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if(cacheItem.isNew) {
|
|
121
|
-
fragment = fragment || document.createDocumentFragment();
|
|
122
|
-
fragment.append(cacheItem.child);
|
|
123
|
-
cacheItem.isNew = false;
|
|
101
|
+
const child = cacheItem.child.deref();
|
|
102
|
+
if(!child) {
|
|
124
103
|
continue;
|
|
125
104
|
}
|
|
126
|
-
|
|
127
|
-
const nextChild = cache.get(keyIdsArray[i + 1])?.child;
|
|
128
|
-
if(nextChild) {
|
|
129
|
-
if(cacheItem.child.nextSibling !== nextChild) {
|
|
130
|
-
elementsToMove.push({ child: cacheItem.child, before: nextChild });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
previousElementSibling = cacheItem.child;
|
|
105
|
+
fragment.appendChild(child);
|
|
135
106
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
elementsToInsert.forEach(({ child, before }) => {
|
|
139
|
-
if(before) {
|
|
140
|
-
parent.insertBefore(child, before);
|
|
141
|
-
} else {
|
|
142
|
-
element.appendChild(child);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
elementsToMove.forEach(({ child, before }) => {
|
|
147
|
-
parent.insertBefore(child, before);
|
|
148
|
-
})
|
|
149
|
-
saveFragment = null;
|
|
150
|
-
|
|
107
|
+
element.replaceContent(fragment);
|
|
151
108
|
};
|
|
152
109
|
|
|
153
110
|
const buildContent = () => {
|
|
111
|
+
const parent = blockEnd.parentNode;
|
|
112
|
+
if(!parent) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
154
116
|
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
155
117
|
keyIds.clear();
|
|
156
118
|
if(Array.isArray(items)) {
|
|
157
|
-
|
|
119
|
+
for(let i = 0, length = items.length; i < length; i++) {
|
|
120
|
+
const keyId= handleContentItem(items[i], i);
|
|
121
|
+
keyIds.add(keyId);
|
|
122
|
+
}
|
|
158
123
|
} else {
|
|
159
124
|
for(const indexKey in items) {
|
|
160
|
-
|
|
125
|
+
const keyId = handleContentItem(items[indexKey], indexKey);
|
|
126
|
+
keyIds.add(keyId);
|
|
161
127
|
}
|
|
162
128
|
}
|
|
163
129
|
|
|
164
|
-
|
|
130
|
+
if(keyIds.size === 0) {
|
|
131
|
+
clear();
|
|
132
|
+
lastKeyOrder?.clear();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
165
135
|
|
|
166
|
-
|
|
136
|
+
cleanCache(parent);
|
|
137
|
+
if(!lastKeyOrder || lastKeyOrder.size === 0) {
|
|
138
|
+
batchDOMUpdates(parent);
|
|
139
|
+
} else {
|
|
140
|
+
diffingDOMUpdates(parent);
|
|
141
|
+
}
|
|
142
|
+
lastKeyOrder?.clear();
|
|
143
|
+
lastKeyOrder = new Set([...keyIds]);
|
|
167
144
|
};
|
|
168
145
|
|
|
169
146
|
buildContent();
|
|
@@ -171,4 +148,4 @@ export function ForEach(data, callback, key) {
|
|
|
171
148
|
data.subscribe(buildContent)
|
|
172
149
|
}
|
|
173
150
|
return element;
|
|
174
|
-
}
|
|
151
|
+
}
|
package/src/elements/index.js
CHANGED
package/src/elements/list.js
CHANGED
package/src/utils/helpers.js
CHANGED
|
@@ -1,35 +1,58 @@
|
|
|
1
|
+
import Validator from "./validator";
|
|
2
|
+
|
|
3
|
+
const invoke = function(fn, args, context) {
|
|
4
|
+
if(context) {
|
|
5
|
+
fn.apply(context, args);
|
|
6
|
+
} else {
|
|
7
|
+
fn(...args);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
1
10
|
/**
|
|
2
11
|
*
|
|
3
12
|
* @param {Function} fn
|
|
4
13
|
* @param {number} delay
|
|
5
|
-
* @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean}}options
|
|
14
|
+
* @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean, check: Function}}options
|
|
6
15
|
* @returns {(function(...[*]): void)|*}
|
|
7
16
|
*/
|
|
8
|
-
export const
|
|
17
|
+
export const debounce = function(fn, delay, options = {}) {
|
|
9
18
|
let timer = null;
|
|
10
|
-
let
|
|
11
|
-
const { leading = true, trailing = true, debounce = false } = options;
|
|
19
|
+
let lastArgs = null;
|
|
12
20
|
|
|
13
21
|
return function(...args) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (leading && now - lastExecTime >= delay) {
|
|
22
|
-
fn.apply(this, args);
|
|
23
|
-
lastExecTime = now;
|
|
24
|
-
}
|
|
25
|
-
if (trailing && !timer) {
|
|
26
|
-
timer = setTimeout(() => {
|
|
27
|
-
fn.apply(this, args);
|
|
28
|
-
lastExecTime = Date.now();
|
|
29
|
-
timer = null;
|
|
30
|
-
}, delay - (now - lastExecTime));
|
|
22
|
+
const context = options.context === true ? this : null;
|
|
23
|
+
let scopeDelay = delay;
|
|
24
|
+
if(options.check) {
|
|
25
|
+
const response = options.check(...args);
|
|
26
|
+
if(typeof response === 'number') {
|
|
27
|
+
scopeDelay = response;
|
|
28
|
+
}
|
|
31
29
|
}
|
|
30
|
+
lastArgs = args;
|
|
31
|
+
|
|
32
|
+
// debounce mode: reset the timer for each call
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
timer = setTimeout(() => invoke(fn, lastArgs, context), delay);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param {*} item
|
|
42
|
+
* @param {string|null} defaultKey
|
|
43
|
+
* @param {?Function} key
|
|
44
|
+
* @returns {*}
|
|
45
|
+
*/
|
|
46
|
+
export const getKey = (item, defaultKey, key) => {
|
|
47
|
+
if(Validator.isFunction(key)) return key(item, defaultKey);
|
|
48
|
+
if(Validator.isObservable(item)) {
|
|
49
|
+
const val = item.val();
|
|
50
|
+
return (val && key) ? val[key] : defaultKey;
|
|
51
|
+
}
|
|
52
|
+
if(!Validator.isObject(item)) {
|
|
53
|
+
return item;
|
|
32
54
|
}
|
|
55
|
+
return item[key] ?? defaultKey;
|
|
33
56
|
};
|
|
34
57
|
|
|
35
58
|
export const trim = function(str, char) {
|
|
@@ -3,20 +3,6 @@ import NativeDocumentError from "../errors/NativeDocumentError";
|
|
|
3
3
|
import {BOOLEAN_ATTRIBUTES} from "./constants.js";
|
|
4
4
|
import {Observable} from "../data/Observable";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
*
|
|
8
|
-
* @param {HTMLElement} element
|
|
9
|
-
* @param {string} className
|
|
10
|
-
* @param {string} value
|
|
11
|
-
*/
|
|
12
|
-
const toggleClassItem = function(element, className, value) {
|
|
13
|
-
if(value) {
|
|
14
|
-
element.classList.add(className);
|
|
15
|
-
} else {
|
|
16
|
-
element.classList.remove(className);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
6
|
/**
|
|
21
7
|
*
|
|
22
8
|
* @param {HTMLElement} element
|
|
@@ -26,11 +12,11 @@ function bindClassAttribute(element, data) {
|
|
|
26
12
|
for(let className in data) {
|
|
27
13
|
const value = data[className];
|
|
28
14
|
if(Validator.isObservable(value)) {
|
|
29
|
-
|
|
30
|
-
value.subscribe(newValue =>
|
|
15
|
+
element.classList.toggle(className, value.val());
|
|
16
|
+
value.subscribe(newValue => element.classList.toggle(className, newValue));
|
|
31
17
|
continue;
|
|
32
18
|
}
|
|
33
|
-
|
|
19
|
+
element.classList.toggle(className, value)
|
|
34
20
|
}
|
|
35
21
|
}
|
|
36
22
|
|
|
@@ -102,8 +88,8 @@ function bindAttributeWithObservable(element, attributeName, value) {
|
|
|
102
88
|
}
|
|
103
89
|
element.setAttribute(attributeName, newValue);
|
|
104
90
|
};
|
|
91
|
+
applyValue(value.val());
|
|
105
92
|
value.subscribe(applyValue);
|
|
106
|
-
applyValue(value.val())
|
|
107
93
|
|
|
108
94
|
if(attributeName === 'value') {
|
|
109
95
|
element.addEventListener('input', () => value.set(element.value));
|
|
@@ -152,6 +138,7 @@ export default function AttributesWrapper(element, attributes) {
|
|
|
152
138
|
continue;
|
|
153
139
|
}
|
|
154
140
|
element.setAttribute(attributeName, value);
|
|
141
|
+
|
|
155
142
|
}
|
|
156
143
|
return element;
|
|
157
144
|
}
|