native-document 1.0.13 → 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 +1297 -804
- 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 +26 -21
- 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 +100 -56
- 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,39 +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
|
-
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
* @param {*} item
|
|
12
|
-
* @param {string|null} defaultKey
|
|
13
|
-
* @param {?Function} key
|
|
14
|
-
* @returns {*}
|
|
15
|
-
*/
|
|
16
|
-
const getKey = (item, defaultKey, key) => {
|
|
17
|
-
if(Validator.isFunction(key)) return key(item, defaultKey);
|
|
18
|
-
if(Validator.isObservable(item)) {
|
|
19
|
-
const val = item.val();
|
|
20
|
-
return (val && key) ? val[key] : defaultKey;
|
|
21
|
-
}
|
|
22
|
-
return item[key] ?? defaultKey;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
* @param {Map} cache
|
|
28
|
-
* @param {Set} keyIds
|
|
29
|
-
*/
|
|
30
|
-
const cleanBlockByCache = (cache, keyIds) => {
|
|
31
|
-
for(const [key, {child}] of cache.entries()) {
|
|
32
|
-
if(keyIds.has(key)) {
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
child.remove();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
6
|
+
import DebugManager from "../../utils/debug-manager";
|
|
7
|
+
import {getKey} from "../../utils/helpers";
|
|
38
8
|
|
|
39
9
|
/**
|
|
40
10
|
*
|
|
@@ -46,62 +16,136 @@ const cleanBlockByCache = (cache, keyIds) => {
|
|
|
46
16
|
export function ForEach(data, callback, key) {
|
|
47
17
|
const element = new Anchor('ForEach');
|
|
48
18
|
const blockEnd = element.endElement();
|
|
19
|
+
const blockStart = element.startElement();
|
|
49
20
|
|
|
50
21
|
let cache = new Map();
|
|
22
|
+
let lastKeyOrder = null;
|
|
23
|
+
const keyIds = new Set();
|
|
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
|
+
};
|
|
51
46
|
|
|
52
47
|
const handleContentItem = (item, indexKey) => {
|
|
53
48
|
const keyId = getKey(item, indexKey, key);
|
|
54
49
|
|
|
55
50
|
if(cache.has(keyId)) {
|
|
56
|
-
cache.get(keyId)
|
|
51
|
+
const cacheItem = cache.get(keyId);
|
|
52
|
+
cacheItem.indexObserver?.set(indexKey);
|
|
53
|
+
cacheItem.isNew = false;
|
|
54
|
+
if(cacheItem.child?.deref()) {
|
|
55
|
+
return keyId;
|
|
56
|
+
}
|
|
57
|
+
cache.delete(keyId);
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
|
|
60
62
|
let child = callback(item, indexObserver);
|
|
61
63
|
if(Validator.isStringOrObservable(child)) {
|
|
62
64
|
child = createTextNode(child);
|
|
63
65
|
}
|
|
64
|
-
cache.set(keyId, { child, indexObserver});
|
|
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;
|
|
65
70
|
}
|
|
66
71
|
return keyId;
|
|
72
|
+
};
|
|
73
|
+
|
|
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);
|
|
83
|
+
}
|
|
84
|
+
parent.insertBefore(fragment, blockEnd);
|
|
67
85
|
}
|
|
68
|
-
|
|
86
|
+
|
|
87
|
+
const diffingDOMUpdates = (parent) => {
|
|
88
|
+
const operations = [];
|
|
89
|
+
let fragment = document.createDocumentFragment();
|
|
90
|
+
const newKeys = Array.from(keyIds);
|
|
91
|
+
const oldKeys = Array.from(lastKeyOrder);
|
|
92
|
+
|
|
93
|
+
let currentPosition = blockStart;
|
|
94
|
+
|
|
95
|
+
for(const index in newKeys) {
|
|
96
|
+
const itemKey = newKeys[index];
|
|
97
|
+
const cacheItem = cache.get(itemKey);
|
|
98
|
+
if(!cacheItem) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const child = cacheItem.child.deref();
|
|
102
|
+
if(!child) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
fragment.appendChild(child);
|
|
106
|
+
}
|
|
107
|
+
element.replaceContent(fragment);
|
|
108
|
+
};
|
|
69
109
|
|
|
70
110
|
const buildContent = () => {
|
|
71
|
-
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
72
111
|
const parent = blockEnd.parentNode;
|
|
73
112
|
if(!parent) {
|
|
74
113
|
return;
|
|
75
114
|
}
|
|
115
|
+
|
|
116
|
+
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
76
117
|
keyIds.clear();
|
|
77
118
|
if(Array.isArray(items)) {
|
|
78
|
-
|
|
119
|
+
for(let i = 0, length = items.length; i < length; i++) {
|
|
120
|
+
const keyId= handleContentItem(items[i], i);
|
|
121
|
+
keyIds.add(keyId);
|
|
122
|
+
}
|
|
79
123
|
} else {
|
|
80
124
|
for(const indexKey in items) {
|
|
81
|
-
|
|
125
|
+
const keyId = handleContentItem(items[indexKey], indexKey);
|
|
126
|
+
keyIds.add(keyId);
|
|
82
127
|
}
|
|
83
128
|
}
|
|
84
129
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if(child) {
|
|
90
|
-
if(nextElementSibling && nextElementSibling.previousSibling === child) {
|
|
91
|
-
nextElementSibling = child;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
parent.insertBefore(child, nextElementSibling);
|
|
95
|
-
nextElementSibling = child;
|
|
96
|
-
}
|
|
130
|
+
if(keyIds.size === 0) {
|
|
131
|
+
clear();
|
|
132
|
+
lastKeyOrder?.clear();
|
|
133
|
+
return;
|
|
97
134
|
}
|
|
135
|
+
|
|
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]);
|
|
98
144
|
};
|
|
99
145
|
|
|
100
146
|
buildContent();
|
|
101
147
|
if(Validator.isObservable(data)) {
|
|
102
|
-
data.subscribe(
|
|
103
|
-
buildContent(newValue, oldValue);
|
|
104
|
-
}, 50, { debounce: true }))
|
|
148
|
+
data.subscribe(buildContent)
|
|
105
149
|
}
|
|
106
150
|
return element;
|
|
107
|
-
}
|
|
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
|
}
|