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
|
@@ -1,84 +1,6 @@
|
|
|
1
1
|
var NativeDocument = (function (exports) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
function eventWrapper(element, name, callback) {
|
|
5
|
-
element.addEventListener(name, callback);
|
|
6
|
-
return element;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
* @param {HTMLElement} element
|
|
12
|
-
* @returns {HTMLElement}
|
|
13
|
-
*/
|
|
14
|
-
function HtmlElementEventsWrapper(element) {
|
|
15
|
-
|
|
16
|
-
if(!element.nd) {
|
|
17
|
-
element.nd = {};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @param {Object<string,Function>} events
|
|
22
|
-
*/
|
|
23
|
-
element.nd.on = function(events) {
|
|
24
|
-
for(const event in events) {
|
|
25
|
-
const callback = events[event];
|
|
26
|
-
eventWrapper(element, event, callback);
|
|
27
|
-
}
|
|
28
|
-
return element;
|
|
29
|
-
};
|
|
30
|
-
element.nd.on.prevent = function(events) {
|
|
31
|
-
for(const event in events) {
|
|
32
|
-
const callback = events[event];
|
|
33
|
-
eventWrapper(element, event, (event) => {
|
|
34
|
-
event.preventDefault();
|
|
35
|
-
callback && callback(event);
|
|
36
|
-
return element;
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
return element;
|
|
40
|
-
};
|
|
41
|
-
const events = {
|
|
42
|
-
click: (callback) => eventWrapper(element, 'click', callback),
|
|
43
|
-
focus: (callback) => eventWrapper(element, 'focus', callback),
|
|
44
|
-
blur: (callback) => eventWrapper(element, 'blur', callback),
|
|
45
|
-
input: (callback) => eventWrapper(element, 'input', callback),
|
|
46
|
-
change: (callback) => eventWrapper(element, 'change', callback),
|
|
47
|
-
keyup: (callback) => eventWrapper(element, 'keyup', callback),
|
|
48
|
-
keydown: (callback) => eventWrapper(element, 'keydown', callback),
|
|
49
|
-
beforeInput: (callback) => eventWrapper(element, 'beforeinput', callback),
|
|
50
|
-
mouseOver: (callback) => eventWrapper(element, 'mouseover', callback),
|
|
51
|
-
mouseOut: (callback) => eventWrapper(element, 'mouseout', callback),
|
|
52
|
-
mouseDown: (callback) => eventWrapper(element, 'mousedown', callback),
|
|
53
|
-
mouseUp: (callback) => eventWrapper(element, 'mouseup', callback),
|
|
54
|
-
mouseMove: (callback) => eventWrapper(element, 'mousemove', callback),
|
|
55
|
-
hover: (mouseInCallback, mouseOutCallback) => {
|
|
56
|
-
element.addEventListener('mouseover', mouseInCallback);
|
|
57
|
-
element.addEventListener('mouseout', mouseOutCallback);
|
|
58
|
-
},
|
|
59
|
-
dropped: (callback) => eventWrapper(element, 'drop', callback),
|
|
60
|
-
submit: (callback) => eventWrapper(element, 'submit', callback),
|
|
61
|
-
dragEnd: (callback) => eventWrapper(element, 'dragend', callback),
|
|
62
|
-
dragStart: (callback) => eventWrapper(element, 'dragstart', callback),
|
|
63
|
-
drop: (callback) => eventWrapper(element, 'drop', callback),
|
|
64
|
-
dragOver: (callback) => eventWrapper(element, 'dragover', callback),
|
|
65
|
-
dragEnter: (callback) => eventWrapper(element, 'dragenter', callback),
|
|
66
|
-
dragLeave: (callback) => eventWrapper(element, 'dragleave', callback),
|
|
67
|
-
};
|
|
68
|
-
for(let event in events) {
|
|
69
|
-
element.nd.on[event] = events[event];
|
|
70
|
-
element.nd.on.prevent[event] = function(callback) {
|
|
71
|
-
eventWrapper(element, event.toLowerCase(), (event) => {
|
|
72
|
-
event.preventDefault();
|
|
73
|
-
callback && callback(event);
|
|
74
|
-
});
|
|
75
|
-
return element;
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return element;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
4
|
const DebugManager = {
|
|
83
5
|
enabled: false,
|
|
84
6
|
|
|
@@ -111,38 +33,25 @@ var NativeDocument = (function (exports) {
|
|
|
111
33
|
|
|
112
34
|
const MemoryManager = (function() {
|
|
113
35
|
|
|
114
|
-
let $
|
|
36
|
+
let $nextObserverId = 0;
|
|
115
37
|
const $observables = new Map();
|
|
116
|
-
let $registry = null;
|
|
117
|
-
try {
|
|
118
|
-
$registry = new FinalizationRegistry((heldValue) => {
|
|
119
|
-
DebugManager.log('MemoryManager', '🧹 Auto-cleanup observable:', heldValue);
|
|
120
|
-
heldValue.listeners.splice(0);
|
|
121
|
-
});
|
|
122
|
-
} catch (e) {
|
|
123
|
-
DebugManager.warn('MemoryManager', 'FinalizationRegistry not supported, observables will not be cleaned automatically');
|
|
124
|
-
}
|
|
125
38
|
|
|
126
39
|
return {
|
|
127
40
|
/**
|
|
128
41
|
* Register an observable and return an id.
|
|
129
42
|
*
|
|
130
43
|
* @param {ObservableItem} observable
|
|
131
|
-
* @param {Function
|
|
44
|
+
* @param {Function} getListeners
|
|
132
45
|
* @returns {number}
|
|
133
46
|
*/
|
|
134
|
-
register(observable
|
|
135
|
-
const id = ++$
|
|
136
|
-
const heldValue = {
|
|
137
|
-
id: id,
|
|
138
|
-
listeners
|
|
139
|
-
};
|
|
140
|
-
if($registry) {
|
|
141
|
-
$registry.register(observable, heldValue);
|
|
142
|
-
}
|
|
47
|
+
register(observable) {
|
|
48
|
+
const id = ++$nextObserverId;
|
|
143
49
|
$observables.set(id, new WeakRef(observable));
|
|
144
50
|
return id;
|
|
145
51
|
},
|
|
52
|
+
unregister(id) {
|
|
53
|
+
$observables.delete(id);
|
|
54
|
+
},
|
|
146
55
|
getObservableById(id) {
|
|
147
56
|
return $observables.get(id)?.deref();
|
|
148
57
|
},
|
|
@@ -193,31 +102,36 @@ var NativeDocument = (function (exports) {
|
|
|
193
102
|
function ObservableChecker($observable, $checker) {
|
|
194
103
|
this.observable = $observable;
|
|
195
104
|
this.checker = $checker;
|
|
105
|
+
this.unSubscriptions = [];
|
|
106
|
+
}
|
|
196
107
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
108
|
+
ObservableChecker.prototype.subscribe = function(callback) {
|
|
109
|
+
const unSubscribe = this.observable.subscribe((value) => {
|
|
110
|
+
callback && callback(this.checker(value));
|
|
111
|
+
});
|
|
112
|
+
this.unSubscriptions.push(unSubscribe);
|
|
113
|
+
return unSubscribe;
|
|
114
|
+
};
|
|
202
115
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
this.check = function(callback) {
|
|
207
|
-
return $observable.check(() => callback(this.val()));
|
|
208
|
-
};
|
|
116
|
+
ObservableChecker.prototype.check = function(callback) {
|
|
117
|
+
return this.observable.check(() => callback(this.val()));
|
|
118
|
+
};
|
|
209
119
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
this.trigger = function() {
|
|
214
|
-
return $observable.trigger();
|
|
215
|
-
};
|
|
120
|
+
ObservableChecker.prototype.val = function() {
|
|
121
|
+
return this.checker && this.checker(this.observable.val());
|
|
122
|
+
};
|
|
216
123
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
124
|
+
ObservableChecker.prototype.set = function(value) {
|
|
125
|
+
return this.observable.set(value);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
ObservableChecker.prototype.trigger = function() {
|
|
129
|
+
return this.observable.trigger();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
ObservableChecker.prototype.cleanup = function() {
|
|
133
|
+
return this.observable.cleanup();
|
|
134
|
+
};
|
|
221
135
|
|
|
222
136
|
/**
|
|
223
137
|
*
|
|
@@ -232,104 +146,183 @@ var NativeDocument = (function (exports) {
|
|
|
232
146
|
throw new NativeDocumentError('ObservableItem cannot be an Observable');
|
|
233
147
|
}
|
|
234
148
|
|
|
235
|
-
|
|
149
|
+
this.$previousValue = value;
|
|
150
|
+
this.$currentValue = value;
|
|
151
|
+
this.$isCleanedUp = false;
|
|
236
152
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
let $isCleanedUp = false;
|
|
153
|
+
this.$listeners = null;
|
|
154
|
+
this.$watchers = null;
|
|
240
155
|
|
|
241
|
-
|
|
156
|
+
this.$memoryId = MemoryManager.register(this);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
Object.defineProperty(ObservableItem.prototype, '$value', {
|
|
160
|
+
get() {
|
|
161
|
+
return this.$currentValue;
|
|
162
|
+
},
|
|
163
|
+
set(value) {
|
|
164
|
+
this.set(value);
|
|
165
|
+
},
|
|
166
|
+
configurable: true,
|
|
167
|
+
});
|
|
242
168
|
|
|
243
|
-
|
|
169
|
+
ObservableItem.prototype.triggerListeners = function(operations) {
|
|
170
|
+
const $listeners = this.$listeners;
|
|
171
|
+
const $previousValue = this.$previousValue;
|
|
172
|
+
const $currentValue = this.$currentValue;
|
|
244
173
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
174
|
+
operations = operations || {};
|
|
175
|
+
if($listeners?.length) {
|
|
176
|
+
for(let i = 0, length = $listeners.length; i < length; i++) {
|
|
177
|
+
$listeners[i]($currentValue, $previousValue, operations);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
ObservableItem.prototype.triggerWatchers = function() {
|
|
183
|
+
if(!this.$watchers) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const $watchers = this.$watchers;
|
|
188
|
+
const $previousValue = this.$previousValue;
|
|
189
|
+
const $currentValue = this.$currentValue;
|
|
190
|
+
|
|
191
|
+
if($watchers.has($currentValue)) {
|
|
192
|
+
const watchValueList = $watchers.get($currentValue);
|
|
193
|
+
watchValueList.forEach(itemValue => {
|
|
194
|
+
if(itemValue.ifTrue.called) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
itemValue.ifTrue.callback();
|
|
198
|
+
itemValue.else.called = false;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if($watchers.has($previousValue)) {
|
|
202
|
+
const watchValueList = $watchers.get($previousValue);
|
|
203
|
+
watchValueList.forEach(itemValue => {
|
|
204
|
+
if(itemValue.else.called) {
|
|
205
|
+
return;
|
|
252
206
|
}
|
|
207
|
+
itemValue.else.callback();
|
|
208
|
+
itemValue.ifTrue.called = false;
|
|
253
209
|
});
|
|
210
|
+
}
|
|
211
|
+
};
|
|
254
212
|
|
|
255
|
-
|
|
213
|
+
ObservableItem.prototype.trigger = function(operations) {
|
|
214
|
+
this.triggerListeners(operations);
|
|
215
|
+
this.triggerWatchers();
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @param {*} data
|
|
220
|
+
*/
|
|
221
|
+
ObservableItem.prototype.set = function(data) {
|
|
222
|
+
const newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
|
|
223
|
+
if(this.$currentValue === newValue) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.$previousValue = this.$currentValue;
|
|
227
|
+
this.$currentValue = newValue;
|
|
228
|
+
this.trigger();
|
|
229
|
+
};
|
|
256
230
|
|
|
257
|
-
|
|
231
|
+
ObservableItem.prototype.val = function() {
|
|
232
|
+
return this.$currentValue;
|
|
233
|
+
};
|
|
258
234
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
this
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
235
|
+
ObservableItem.prototype.disconnectAll = function() {
|
|
236
|
+
this.$listeners?.splice(0);
|
|
237
|
+
this.$previousValue = null;
|
|
238
|
+
this.$currentValue = null;
|
|
239
|
+
if(this.$watchers) {
|
|
240
|
+
for (const [_, watchValueList] of this.$watchers) {
|
|
241
|
+
for (const itemValue of watchValueList) {
|
|
242
|
+
itemValue.ifTrue.callback = null;
|
|
243
|
+
itemValue.else.callback = null;
|
|
244
|
+
}
|
|
245
|
+
watchValueList.clear();
|
|
266
246
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
247
|
+
}
|
|
248
|
+
this.$watchers?.clear();
|
|
249
|
+
this.$listeners = null;
|
|
250
|
+
this.$watchers = null;
|
|
251
|
+
};
|
|
252
|
+
ObservableItem.prototype.cleanup = function() {
|
|
253
|
+
MemoryManager.unregister(this.$memoryId);
|
|
254
|
+
this.disconnectAll();
|
|
255
|
+
this.$isCleanedUp = true;
|
|
256
|
+
delete this.$value;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
*
|
|
261
|
+
* @param {Function} callback
|
|
262
|
+
* @returns {(function(): void)}
|
|
263
|
+
*/
|
|
264
|
+
ObservableItem.prototype.subscribe = function(callback) {
|
|
265
|
+
this.$listeners = this.$listeners ?? [];
|
|
266
|
+
if (this.$isCleanedUp) {
|
|
267
|
+
DebugManager.warn('Observable subscription', '⚠️ Attempted to subscribe to a cleaned up observable.');
|
|
268
|
+
return () => {};
|
|
269
|
+
}
|
|
270
|
+
if (typeof callback !== 'function') {
|
|
271
|
+
throw new NativeDocumentError('Callback must be a function');
|
|
272
|
+
}
|
|
271
273
|
|
|
272
|
-
this.
|
|
274
|
+
this.$listeners.push(callback);
|
|
275
|
+
return () => this.unsubscribe(callback);
|
|
276
|
+
};
|
|
273
277
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
$isCleanedUp = true;
|
|
277
|
-
};
|
|
278
|
+
ObservableItem.prototype.on = function(value, callback, elseCallback) {
|
|
279
|
+
this.$watchers = this.$watchers ?? new Map();
|
|
278
280
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
this.subscribe = (callback) => {
|
|
285
|
-
if ($isCleanedUp) {
|
|
286
|
-
DebugManager.warn('Observable subscription', '⚠️ Attempted to subscribe to a cleaned up observable.');
|
|
287
|
-
return () => {};
|
|
288
|
-
}
|
|
289
|
-
if (typeof callback !== 'function') {
|
|
290
|
-
throw new NativeDocumentError('Callback must be a function');
|
|
291
|
-
}
|
|
281
|
+
let watchValueList = this.$watchers.get(value);
|
|
282
|
+
if(!watchValueList) {
|
|
283
|
+
watchValueList = new Set();
|
|
284
|
+
this.$watchers.set(value, watchValueList);
|
|
285
|
+
}
|
|
292
286
|
|
|
293
|
-
|
|
294
|
-
|
|
287
|
+
let itemValue = {
|
|
288
|
+
ifTrue: { callback, called: false },
|
|
289
|
+
else: { callback: elseCallback, called: false }
|
|
295
290
|
};
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
this.unsubscribe = (callback) => {
|
|
302
|
-
const index = $listeners.indexOf(callback);
|
|
303
|
-
if (index > -1) {
|
|
304
|
-
$listeners.splice(index, 1);
|
|
291
|
+
watchValueList.add(itemValue);
|
|
292
|
+
return () => {
|
|
293
|
+
watchValueList?.delete(itemValue);
|
|
294
|
+
if(watchValueList.size === 0) {
|
|
295
|
+
this.$watchers?.delete(value);
|
|
305
296
|
}
|
|
297
|
+
watchValueList = null;
|
|
298
|
+
itemValue = null;
|
|
306
299
|
};
|
|
300
|
+
};
|
|
307
301
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
Object.defineProperty($object, '$value', {
|
|
319
|
-
get() {
|
|
320
|
-
return $object.val();
|
|
321
|
-
},
|
|
322
|
-
set(value) {
|
|
323
|
-
$object.set(value);
|
|
324
|
-
return $object;
|
|
325
|
-
}
|
|
326
|
-
});
|
|
302
|
+
/**
|
|
303
|
+
* Unsubscribe from an observable.
|
|
304
|
+
* @param {Function} callback
|
|
305
|
+
*/
|
|
306
|
+
ObservableItem.prototype.unsubscribe = function(callback) {
|
|
307
|
+
const index = this.$listeners.indexOf(callback);
|
|
308
|
+
if (index > -1) {
|
|
309
|
+
this.$listeners.splice(index, 1);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
327
312
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
313
|
+
/**
|
|
314
|
+
* Create an Observable checker instance
|
|
315
|
+
* @param callback
|
|
316
|
+
* @returns {ObservableChecker}
|
|
317
|
+
*/
|
|
318
|
+
ObservableItem.prototype.check = function(callback) {
|
|
319
|
+
return new ObservableChecker(this, callback)
|
|
320
|
+
};
|
|
321
|
+
ObservableItem.prototype.get = ObservableItem.prototype.check;
|
|
331
322
|
|
|
332
|
-
|
|
323
|
+
ObservableItem.prototype.toString = function() {
|
|
324
|
+
return '{{#ObItem::(' +this.$memoryId+ ')}}';
|
|
325
|
+
};
|
|
333
326
|
|
|
334
327
|
const Validator = {
|
|
335
328
|
isObservable(value) {
|
|
@@ -446,40 +439,182 @@ var NativeDocument = (function (exports) {
|
|
|
446
439
|
}
|
|
447
440
|
};
|
|
448
441
|
|
|
442
|
+
const getChildAsNode = (child) => {
|
|
443
|
+
if(Validator.isFunction(child)) {
|
|
444
|
+
return getChildAsNode(child());
|
|
445
|
+
}
|
|
446
|
+
if(Validator.isElement(child)) {
|
|
447
|
+
return child;
|
|
448
|
+
}
|
|
449
|
+
return createTextNode(child)
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
function Anchor(name) {
|
|
453
|
+
const element = document.createDocumentFragment();
|
|
454
|
+
|
|
455
|
+
const anchorStart = document.createComment('Anchor Start : '+name);
|
|
456
|
+
const anchorEnd = document.createComment('/ Anchor End '+name);
|
|
457
|
+
|
|
458
|
+
element.appendChild(anchorStart);
|
|
459
|
+
element.appendChild(anchorEnd);
|
|
460
|
+
|
|
461
|
+
element.nativeInsertBefore = element.insertBefore;
|
|
462
|
+
element.nativeAppendChild = element.appendChild;
|
|
463
|
+
|
|
464
|
+
const insertBefore = function(parent, child, target) {
|
|
465
|
+
if(parent === element) {
|
|
466
|
+
parent.nativeInsertBefore(getChildAsNode(child), target);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
parent.insertBefore(getChildAsNode(child), target);
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
element.appendElement = function(child, before = null) {
|
|
473
|
+
if(anchorEnd.parentNode === element) {
|
|
474
|
+
anchorEnd.parentNode.nativeInsertBefore(child, before || anchorEnd);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
anchorEnd.parentNode?.insertBefore(child, before || anchorEnd);
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
element.appendChild = function(child, before = null) {
|
|
481
|
+
const parent = anchorEnd.parentNode;
|
|
482
|
+
if(!parent) {
|
|
483
|
+
DebugManager.error('Anchor', 'Anchor : parent not found', child);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
before = before ?? anchorEnd;
|
|
487
|
+
if(Validator.isArray(child)) {
|
|
488
|
+
const fragment = document.createDocumentFragment();
|
|
489
|
+
for(let i = 0, length = child.length; i < length; i++) {
|
|
490
|
+
fragment.appendChild(getChildAsNode(child[i]));
|
|
491
|
+
}
|
|
492
|
+
insertBefore(parent, fragment, before);
|
|
493
|
+
return element;
|
|
494
|
+
}
|
|
495
|
+
insertBefore(parent, child, before);
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
element.removeChildren = function() {
|
|
499
|
+
const parent = anchorEnd.parentNode;
|
|
500
|
+
if(parent === element) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if(parent.firstChild === anchorStart && parent.lastChild === anchorEnd) {
|
|
504
|
+
parent.replaceChildren(anchorStart, anchorEnd);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let itemToRemove = anchorStart.nextSibling, tempItem;
|
|
509
|
+
const fragment = document.createDocumentFragment();
|
|
510
|
+
while(itemToRemove && itemToRemove !== anchorEnd) {
|
|
511
|
+
tempItem = itemToRemove.nextSibling;
|
|
512
|
+
fragment.append(itemToRemove);
|
|
513
|
+
itemToRemove = tempItem;
|
|
514
|
+
}
|
|
515
|
+
fragment.replaceChildren();
|
|
516
|
+
};
|
|
517
|
+
element.remove = function() {
|
|
518
|
+
const parent = anchorEnd.parentNode;
|
|
519
|
+
if(parent === element) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
let itemToRemove = anchorStart.nextSibling, tempItem;
|
|
523
|
+
while(itemToRemove !== anchorEnd) {
|
|
524
|
+
tempItem = itemToRemove.nextSibling;
|
|
525
|
+
element.nativeAppendChild(itemToRemove);
|
|
526
|
+
itemToRemove = tempItem;
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
element.removeWithAnchors = function() {
|
|
531
|
+
element.removeChildren();
|
|
532
|
+
anchorStart.remove();
|
|
533
|
+
anchorEnd.remove();
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
element.replaceContent = function(child) {
|
|
537
|
+
const parent = anchorEnd.parentNode;
|
|
538
|
+
if(!parent) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if(parent.firstChild === anchorStart && parent.lastChild === anchorEnd) {
|
|
542
|
+
parent.replaceChildren(anchorStart, child, anchorEnd);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
element.removeChildren();
|
|
546
|
+
parent.insertBefore(child, anchorEnd);
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
element.insertBefore = function(child, anchor = null) {
|
|
550
|
+
element.appendChild(child, anchor);
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
element.clear = function() {
|
|
554
|
+
element.remove();
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
element.endElement = function() {
|
|
558
|
+
return anchorEnd;
|
|
559
|
+
};
|
|
560
|
+
element.startElement = function() {
|
|
561
|
+
return anchorStart;
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
return element;
|
|
565
|
+
}
|
|
566
|
+
|
|
449
567
|
const BOOLEAN_ATTRIBUTES = ['checked', 'selected', 'disabled', 'readonly', 'required', 'autofocus', 'multiple', 'autocomplete', 'hidden', 'contenteditable', 'spellcheck', 'translate', 'draggable', 'async', 'defer', 'autoplay', 'controls', 'loop', 'muted', 'download', 'reversed', 'open', 'default', 'formnovalidate', 'novalidate', 'scoped', 'itemscope', 'allowfullscreen', 'allowpaymentrequest', 'playsinline'];
|
|
450
568
|
|
|
569
|
+
const invoke = function(fn, args, context) {
|
|
570
|
+
if(context) {
|
|
571
|
+
fn.apply(context, args);
|
|
572
|
+
} else {
|
|
573
|
+
fn(...args);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
451
576
|
/**
|
|
452
577
|
*
|
|
453
578
|
* @param {Function} fn
|
|
454
579
|
* @param {number} delay
|
|
455
|
-
* @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean}}options
|
|
580
|
+
* @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean, check: Function}}options
|
|
456
581
|
* @returns {(function(...[*]): void)|*}
|
|
457
582
|
*/
|
|
458
|
-
const
|
|
583
|
+
const debounce = function(fn, delay, options = {}) {
|
|
459
584
|
let timer = null;
|
|
460
|
-
let
|
|
461
|
-
const { leading = true, trailing = true, debounce = false } = options;
|
|
585
|
+
let lastArgs = null;
|
|
462
586
|
|
|
463
587
|
return function(...args) {
|
|
464
|
-
const
|
|
465
|
-
if
|
|
466
|
-
|
|
467
|
-
clearTimeout(timer);
|
|
468
|
-
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
if (leading && now - lastExecTime >= delay) {
|
|
472
|
-
fn.apply(this, args);
|
|
473
|
-
lastExecTime = now;
|
|
474
|
-
}
|
|
475
|
-
if (trailing && !timer) {
|
|
476
|
-
timer = setTimeout(() => {
|
|
477
|
-
fn.apply(this, args);
|
|
478
|
-
lastExecTime = Date.now();
|
|
479
|
-
timer = null;
|
|
480
|
-
}, delay - (now - lastExecTime));
|
|
588
|
+
const context = options.context === true ? this : null;
|
|
589
|
+
if(options.check) {
|
|
590
|
+
options.check(...args);
|
|
481
591
|
}
|
|
592
|
+
lastArgs = args;
|
|
593
|
+
|
|
594
|
+
// debounce mode: reset the timer for each call
|
|
595
|
+
clearTimeout(timer);
|
|
596
|
+
timer = setTimeout(() => invoke(fn, lastArgs, context), delay);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
*
|
|
603
|
+
* @param {*} item
|
|
604
|
+
* @param {string|null} defaultKey
|
|
605
|
+
* @param {?Function} key
|
|
606
|
+
* @returns {*}
|
|
607
|
+
*/
|
|
608
|
+
const getKey = (item, defaultKey, key) => {
|
|
609
|
+
if(Validator.isFunction(key)) return key(item, defaultKey);
|
|
610
|
+
if(Validator.isObservable(item)) {
|
|
611
|
+
const val = item.val();
|
|
612
|
+
return (val && key) ? val[key] : defaultKey;
|
|
482
613
|
}
|
|
614
|
+
if(!Validator.isObject(item)) {
|
|
615
|
+
return item;
|
|
616
|
+
}
|
|
617
|
+
return item[key] ?? defaultKey;
|
|
483
618
|
};
|
|
484
619
|
|
|
485
620
|
const trim = function(str, char) {
|
|
@@ -496,45 +631,6 @@ var NativeDocument = (function (exports) {
|
|
|
496
631
|
return new ObservableItem(value);
|
|
497
632
|
}
|
|
498
633
|
|
|
499
|
-
/**
|
|
500
|
-
*
|
|
501
|
-
* @param {Function} callback
|
|
502
|
-
* @param {Array|Function} dependencies
|
|
503
|
-
* @returns {ObservableItem}
|
|
504
|
-
*/
|
|
505
|
-
Observable.computed = function(callback, dependencies = []) {
|
|
506
|
-
const initialValue = callback();
|
|
507
|
-
const observable = new ObservableItem(initialValue);
|
|
508
|
-
const updatedValue = () => observable.set(callback());
|
|
509
|
-
|
|
510
|
-
if(Validator.isFunction(dependencies)) {
|
|
511
|
-
if(!Validator.isObservable(dependencies.$observer)) {
|
|
512
|
-
throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
|
|
513
|
-
}
|
|
514
|
-
dependencies.$observer.subscribe(updatedValue);
|
|
515
|
-
return observable;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
dependencies.forEach(dependency => dependency.subscribe(updatedValue));
|
|
519
|
-
|
|
520
|
-
return observable;
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
Observable.batch = function(callback) {
|
|
524
|
-
const $observer = Observable(0);
|
|
525
|
-
const batch = function() {
|
|
526
|
-
if(Validator.isAsyncFunction(callback)) {
|
|
527
|
-
return (callback(...arguments)).then(() => {
|
|
528
|
-
$observer.trigger();
|
|
529
|
-
}).catch(error => { throw error; });
|
|
530
|
-
}
|
|
531
|
-
callback(...arguments);
|
|
532
|
-
$observer.trigger();
|
|
533
|
-
};
|
|
534
|
-
batch.$observer = $observer;
|
|
535
|
-
return batch;
|
|
536
|
-
};
|
|
537
|
-
|
|
538
634
|
/**
|
|
539
635
|
*
|
|
540
636
|
* @param id
|
|
@@ -548,7 +644,6 @@ var NativeDocument = (function (exports) {
|
|
|
548
644
|
return item;
|
|
549
645
|
};
|
|
550
646
|
|
|
551
|
-
|
|
552
647
|
/**
|
|
553
648
|
*
|
|
554
649
|
* @param {ObservableItem} observable
|
|
@@ -557,144 +652,6 @@ var NativeDocument = (function (exports) {
|
|
|
557
652
|
observable.cleanup();
|
|
558
653
|
};
|
|
559
654
|
|
|
560
|
-
/**
|
|
561
|
-
* Get the value of an observable or an object of observables.
|
|
562
|
-
* @param {ObservableItem|Object<ObservableItem>} object
|
|
563
|
-
* @returns {{}|*|null}
|
|
564
|
-
*/
|
|
565
|
-
Observable.value = function(data) {
|
|
566
|
-
if(Validator.isObservable(data)) {
|
|
567
|
-
return data.val();
|
|
568
|
-
}
|
|
569
|
-
if(Validator.isProxy(data)) {
|
|
570
|
-
return data.$val();
|
|
571
|
-
}
|
|
572
|
-
if(Validator.isArray(data)) {
|
|
573
|
-
const result = [];
|
|
574
|
-
data.forEach(item => {
|
|
575
|
-
result.push(Observable.value(item));
|
|
576
|
-
});
|
|
577
|
-
return result;
|
|
578
|
-
}
|
|
579
|
-
return data;
|
|
580
|
-
};
|
|
581
|
-
|
|
582
|
-
/**
|
|
583
|
-
*
|
|
584
|
-
* @param {Object} value
|
|
585
|
-
* @returns {Proxy}
|
|
586
|
-
*/
|
|
587
|
-
Observable.init = function(value) {
|
|
588
|
-
const data = {};
|
|
589
|
-
for(const key in value) {
|
|
590
|
-
const itemValue = value[key];
|
|
591
|
-
if(Validator.isJson(itemValue)) {
|
|
592
|
-
data[key] = Observable.init(itemValue);
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
595
|
-
else if(Validator.isArray(itemValue)) {
|
|
596
|
-
data[key] = Observable.array(itemValue);
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
data[key] = Observable(itemValue);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const $val = function() {
|
|
603
|
-
const result = {};
|
|
604
|
-
for(const key in data) {
|
|
605
|
-
const dataItem = data[key];
|
|
606
|
-
if(Validator.isObservable(dataItem)) {
|
|
607
|
-
result[key] = dataItem.val();
|
|
608
|
-
} else if(Validator.isProxy(dataItem)) {
|
|
609
|
-
result[key] = dataItem.$val();
|
|
610
|
-
} else {
|
|
611
|
-
result[key] = dataItem;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
return result;
|
|
615
|
-
};
|
|
616
|
-
const $clone = function() {
|
|
617
|
-
|
|
618
|
-
};
|
|
619
|
-
|
|
620
|
-
return new Proxy(data, {
|
|
621
|
-
get(target, property) {
|
|
622
|
-
if(property === '__isProxy__') {
|
|
623
|
-
return true;
|
|
624
|
-
}
|
|
625
|
-
if(property === '$val') {
|
|
626
|
-
return $val;
|
|
627
|
-
}
|
|
628
|
-
if(property === '$clone') {
|
|
629
|
-
return $clone;
|
|
630
|
-
}
|
|
631
|
-
if(target[property] !== undefined) {
|
|
632
|
-
return target[property];
|
|
633
|
-
}
|
|
634
|
-
return undefined;
|
|
635
|
-
},
|
|
636
|
-
set(target, prop, newValue) {
|
|
637
|
-
if(target[prop] !== undefined) {
|
|
638
|
-
target[prop].set(newValue);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
})
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
Observable.object = Observable.init;
|
|
645
|
-
Observable.json = Observable.init;
|
|
646
|
-
Observable.update = function($target, data) {
|
|
647
|
-
for(const key in data) {
|
|
648
|
-
const targetItem = $target[key];
|
|
649
|
-
const newValue = data[key];
|
|
650
|
-
|
|
651
|
-
if(Validator.isObservable(targetItem)) {
|
|
652
|
-
if(Validator.isArray(newValue)) {
|
|
653
|
-
Observable.update(targetItem, newValue);
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
targetItem.set(newValue);
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
|
-
if(Validator.isProxy(targetItem)) {
|
|
660
|
-
Observable.update(targetItem, newValue);
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
$target[key] = newValue;
|
|
664
|
-
}
|
|
665
|
-
};
|
|
666
|
-
/**
|
|
667
|
-
*
|
|
668
|
-
* @param {Array} target
|
|
669
|
-
* @returns {ObservableItem}
|
|
670
|
-
*/
|
|
671
|
-
Observable.array = function(target) {
|
|
672
|
-
if(!Array.isArray(target)) {
|
|
673
|
-
throw new NativeDocumentError('Observable.array : target must be an array');
|
|
674
|
-
}
|
|
675
|
-
const observer = Observable(target);
|
|
676
|
-
|
|
677
|
-
const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
|
|
678
|
-
|
|
679
|
-
methods.forEach((method) => {
|
|
680
|
-
observer[method] = function(...values) {
|
|
681
|
-
const target = observer.val();
|
|
682
|
-
const result = target[method].apply(target, arguments);
|
|
683
|
-
observer.trigger();
|
|
684
|
-
return result;
|
|
685
|
-
};
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find'];
|
|
689
|
-
overrideMethods.forEach((method) => {
|
|
690
|
-
observer[method] = function(callback) {
|
|
691
|
-
return observer.val()[method](callback);
|
|
692
|
-
};
|
|
693
|
-
});
|
|
694
|
-
|
|
695
|
-
return observer;
|
|
696
|
-
};
|
|
697
|
-
|
|
698
655
|
/**
|
|
699
656
|
* Enable auto cleanup of observables.
|
|
700
657
|
* @param {Boolean} enable
|
|
@@ -713,20 +670,6 @@ var NativeDocument = (function (exports) {
|
|
|
713
670
|
setInterval(() => MemoryManager.cleanObservables(threshold), interval);
|
|
714
671
|
};
|
|
715
672
|
|
|
716
|
-
/**
|
|
717
|
-
*
|
|
718
|
-
* @param {HTMLElement} element
|
|
719
|
-
* @param {string} className
|
|
720
|
-
* @param {string} value
|
|
721
|
-
*/
|
|
722
|
-
const toggleClassItem = function(element, className, value) {
|
|
723
|
-
if(value) {
|
|
724
|
-
element.classList.add(className);
|
|
725
|
-
} else {
|
|
726
|
-
element.classList.remove(className);
|
|
727
|
-
}
|
|
728
|
-
};
|
|
729
|
-
|
|
730
673
|
/**
|
|
731
674
|
*
|
|
732
675
|
* @param {HTMLElement} element
|
|
@@ -736,11 +679,11 @@ var NativeDocument = (function (exports) {
|
|
|
736
679
|
for(let className in data) {
|
|
737
680
|
const value = data[className];
|
|
738
681
|
if(Validator.isObservable(value)) {
|
|
739
|
-
|
|
740
|
-
value.subscribe(newValue =>
|
|
682
|
+
element.classList.toggle(className, value.val());
|
|
683
|
+
value.subscribe(newValue => element.classList.toggle(className, newValue));
|
|
741
684
|
continue;
|
|
742
685
|
}
|
|
743
|
-
|
|
686
|
+
element.classList.toggle(className, value);
|
|
744
687
|
}
|
|
745
688
|
}
|
|
746
689
|
|
|
@@ -812,8 +755,8 @@ var NativeDocument = (function (exports) {
|
|
|
812
755
|
}
|
|
813
756
|
element.setAttribute(attributeName, newValue);
|
|
814
757
|
};
|
|
815
|
-
value.subscribe(applyValue);
|
|
816
758
|
applyValue(value.val());
|
|
759
|
+
value.subscribe(applyValue);
|
|
817
760
|
|
|
818
761
|
if(attributeName === 'value') {
|
|
819
762
|
element.addEventListener('input', () => value.set(element.value));
|
|
@@ -862,228 +805,305 @@ var NativeDocument = (function (exports) {
|
|
|
862
805
|
continue;
|
|
863
806
|
}
|
|
864
807
|
element.setAttribute(attributeName, value);
|
|
808
|
+
|
|
865
809
|
}
|
|
866
810
|
return element;
|
|
867
811
|
}
|
|
868
812
|
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
813
|
+
const $nodeCache = new Map();
|
|
814
|
+
let $textNodeCache = null;
|
|
815
|
+
|
|
816
|
+
const ElementCreator = {
|
|
817
|
+
createTextNode() {
|
|
818
|
+
if(!$textNodeCache) {
|
|
819
|
+
$textNodeCache = document.createTextNode('');
|
|
820
|
+
}
|
|
821
|
+
return $textNodeCache.cloneNode();
|
|
822
|
+
},
|
|
823
|
+
/**
|
|
824
|
+
*
|
|
825
|
+
* @param {HTMLElement|DocumentFragment} parent
|
|
826
|
+
* @param {ObservableItem} observable
|
|
827
|
+
* @returns {Text}
|
|
828
|
+
*/
|
|
829
|
+
createObservableNode(parent, observable) {
|
|
830
|
+
const text = ElementCreator.createTextNode();
|
|
831
|
+
observable.subscribe(value => text.nodeValue = String(value));
|
|
832
|
+
text.nodeValue = observable.val();
|
|
833
|
+
parent && parent.appendChild(text);
|
|
834
|
+
return text;
|
|
835
|
+
},
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
*
|
|
839
|
+
* @param {HTMLElement|DocumentFragment} parent
|
|
840
|
+
* @param {*} value
|
|
841
|
+
* @returns {Text}
|
|
842
|
+
*/
|
|
843
|
+
createStaticTextNode(parent, value) {
|
|
844
|
+
let text = ElementCreator.createTextNode();
|
|
845
|
+
text.nodeValue = String(value);
|
|
846
|
+
parent && parent.appendChild(text);
|
|
847
|
+
return text;
|
|
848
|
+
},
|
|
849
|
+
/**
|
|
850
|
+
*
|
|
851
|
+
* @param {string} name
|
|
852
|
+
* @returns {HTMLElement|DocumentFragment}
|
|
853
|
+
*/
|
|
854
|
+
createElement(name) {
|
|
855
|
+
if(name) {
|
|
856
|
+
if($nodeCache.has(name)) {
|
|
857
|
+
return $nodeCache.get(name).cloneNode();
|
|
858
|
+
}
|
|
859
|
+
const node = document.createElement(name);
|
|
860
|
+
$nodeCache.set(name, node);
|
|
861
|
+
return node.cloneNode();
|
|
862
|
+
}
|
|
863
|
+
return new Anchor('Fragment');
|
|
864
|
+
},
|
|
865
|
+
/**
|
|
866
|
+
*
|
|
867
|
+
* @param {*} children
|
|
868
|
+
* @param {HTMLElement|DocumentFragment} parent
|
|
869
|
+
*/
|
|
870
|
+
processChildren(children, parent) {
|
|
871
|
+
if(children === null) return;
|
|
872
|
+
const childrenArray = Array.isArray(children) ? children : [children];
|
|
873
|
+
|
|
874
|
+
for(let i = 0, length = childrenArray.length; i < length; i++) {
|
|
875
|
+
let child = childrenArray[i];
|
|
876
|
+
if (child === null) continue;
|
|
877
|
+
if(Validator.isString(child) && Validator.isFunction(child.resolveObservableTemplate)) {
|
|
878
|
+
child = child.resolveObservableTemplate();
|
|
879
|
+
}
|
|
880
|
+
if(Validator.isFunction(child)) {
|
|
881
|
+
this.processChildren(child(), parent);
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
if(Validator.isArray(child)) {
|
|
885
|
+
this.processChildren(child, parent);
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
if (Validator.isElement(child)) {
|
|
889
|
+
parent.appendChild(child);
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
if (Validator.isObservable(child)) {
|
|
893
|
+
ElementCreator.createObservableNode(parent, child);
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
if (child) {
|
|
897
|
+
ElementCreator.createStaticTextNode(parent, child);
|
|
881
898
|
}
|
|
882
899
|
}
|
|
883
|
-
},
|
|
900
|
+
},
|
|
884
901
|
/**
|
|
885
902
|
*
|
|
886
903
|
* @param {HTMLElement} element
|
|
887
|
-
* @
|
|
904
|
+
* @param {Object} attributes
|
|
888
905
|
*/
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
if(
|
|
892
|
-
|
|
893
|
-
} else {
|
|
894
|
-
const inDom = document.body.contains(element);
|
|
895
|
-
data = {
|
|
896
|
-
inDom,
|
|
897
|
-
mounted: new Set(),
|
|
898
|
-
unmounted: new Set(),
|
|
899
|
-
};
|
|
900
|
-
DocumentObserver.elements.set(element, data);
|
|
906
|
+
processAttributes(element, attributes) {
|
|
907
|
+
if(Validator.isFragment(element)) return;
|
|
908
|
+
if (attributes) {
|
|
909
|
+
AttributesWrapper(element, attributes);
|
|
901
910
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
911
|
+
},
|
|
912
|
+
/**
|
|
913
|
+
*
|
|
914
|
+
* @param {HTMLElement} element
|
|
915
|
+
* @param {Object} attributes
|
|
916
|
+
* @param {?Function} customWrapper
|
|
917
|
+
* @returns {HTMLElement|DocumentFragment}
|
|
918
|
+
*/
|
|
919
|
+
setup(element, attributes, customWrapper) {
|
|
920
|
+
return element;
|
|
909
921
|
}
|
|
910
922
|
};
|
|
911
923
|
|
|
912
|
-
DocumentObserver
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
const anchorStart = document.createComment('Anchor Start : '+name);
|
|
932
|
-
const anchorEnd = document.createComment('/ Anchor End '+name);
|
|
933
|
-
|
|
934
|
-
element.appendChild(anchorStart);
|
|
935
|
-
element.appendChild(anchorEnd);
|
|
936
|
-
|
|
937
|
-
element.nativeInsertBefore = element.insertBefore;
|
|
938
|
-
element.nativeAppendChild = element.appendChild;
|
|
939
|
-
|
|
940
|
-
const insertBefore = function(parent, child, target) {
|
|
941
|
-
if(parent === element) {
|
|
942
|
-
parent.nativeInsertBefore(getChildAsNode(child), target);
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
parent.insertBefore(getChildAsNode(child), target);
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
element.appendChild = function(child, before = null) {
|
|
949
|
-
const parent = anchorEnd.parentNode;
|
|
950
|
-
if(!parent) {
|
|
951
|
-
DebugManager.error('Anchor', 'Anchor : parent not found', child);
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
before = before ?? anchorEnd;
|
|
955
|
-
if(Validator.isArray(child)) {
|
|
956
|
-
child.forEach((element) => {
|
|
957
|
-
insertBefore(parent, element, before);
|
|
958
|
-
});
|
|
959
|
-
return element;
|
|
960
|
-
}
|
|
961
|
-
insertBefore(parent, child, before);
|
|
962
|
-
};
|
|
924
|
+
const DocumentObserver = {
|
|
925
|
+
mounted: new WeakMap(),
|
|
926
|
+
mountedSupposedSize: 0,
|
|
927
|
+
unmounted: new WeakMap(),
|
|
928
|
+
unmountedSupposedSize: 0,
|
|
929
|
+
observer: null,
|
|
930
|
+
checkMutation: debounce(function(mutationsList) {
|
|
931
|
+
for(const mutation of mutationsList) {
|
|
932
|
+
if(DocumentObserver.mountedSupposedSize > 0 ) {
|
|
933
|
+
for(const node of mutation.addedNodes) {
|
|
934
|
+
const data = DocumentObserver.mounted.get(node);
|
|
935
|
+
if(!data) {
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
data.inDom = true;
|
|
939
|
+
data.mounted && data.mounted(node);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
963
942
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
943
|
+
if(DocumentObserver.unmountedSupposedSize > 0 ) {
|
|
944
|
+
for(const node of mutation.removedNodes) {
|
|
945
|
+
const data = DocumentObserver.unmounted.get(node);
|
|
946
|
+
if(!data) {
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
data.inDom = false;
|
|
951
|
+
if(data.unmounted && data.unmounted(node) === true) {
|
|
952
|
+
data.disconnect();
|
|
953
|
+
node.nd?.remove();
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
977
957
|
}
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
const pluginsManager = (function() {
|
|
999
|
-
|
|
1000
|
-
const $plugins = [];
|
|
1001
|
-
|
|
1002
|
-
return {
|
|
1003
|
-
list : () => $plugins,
|
|
1004
|
-
add : (plugin) => $plugins.push(plugin)
|
|
1005
|
-
};
|
|
1006
|
-
}());
|
|
1007
|
-
|
|
1008
|
-
/**
|
|
1009
|
-
*
|
|
1010
|
-
* @param {HTMLElement|DocumentFragment} parent
|
|
1011
|
-
* @param {ObservableItem} observable
|
|
1012
|
-
* @returns {Text}
|
|
1013
|
-
*/
|
|
1014
|
-
const createObservableNode = function(parent, observable) {
|
|
1015
|
-
const text = document.createTextNode('');
|
|
1016
|
-
observable.subscribe(value => text.textContent = String(value));
|
|
1017
|
-
text.textContent = observable.val();
|
|
1018
|
-
parent && parent.appendChild(text);
|
|
1019
|
-
return text;
|
|
1020
|
-
};
|
|
958
|
+
}, 16),
|
|
959
|
+
/**
|
|
960
|
+
*
|
|
961
|
+
* @param {HTMLElement} element
|
|
962
|
+
* @param {boolean} inDom
|
|
963
|
+
* @returns {{watch: (function(): Map<any, any>), disconnect: (function(): boolean), mounted: (function(*): Set<any>), unmounted: (function(*): Set<any>)}}
|
|
964
|
+
*/
|
|
965
|
+
watch: function(element, inDom = false) {
|
|
966
|
+
let data = {
|
|
967
|
+
inDom,
|
|
968
|
+
mounted: null,
|
|
969
|
+
unmounted: null,
|
|
970
|
+
disconnect: () => {
|
|
971
|
+
DocumentObserver.mounted.delete(element);
|
|
972
|
+
DocumentObserver.unmounted.delete(element);
|
|
973
|
+
DocumentObserver.mountedSupposedSize--;
|
|
974
|
+
DocumentObserver.unmountedSupposedSize--;
|
|
975
|
+
data = null;
|
|
976
|
+
}
|
|
977
|
+
};
|
|
1021
978
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
979
|
+
return {
|
|
980
|
+
disconnect: data.disconnect,
|
|
981
|
+
mounted: (callback) => {
|
|
982
|
+
data.mounted = callback;
|
|
983
|
+
DocumentObserver.mounted.set(element, data);
|
|
984
|
+
DocumentObserver.mountedSupposedSize++;
|
|
985
|
+
},
|
|
986
|
+
unmounted: (callback) => {
|
|
987
|
+
data.unmounted = callback;
|
|
988
|
+
DocumentObserver.unmounted.set(element, data);
|
|
989
|
+
DocumentObserver.unmountedSupposedSize++;
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
}
|
|
1033
993
|
};
|
|
1034
994
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
element.nd.wrap = (callback) => {
|
|
1041
|
-
if(!Validator.isFunction(callback)) {
|
|
1042
|
-
throw new NativeDocumentError('Callback must be a function');
|
|
1043
|
-
}
|
|
1044
|
-
callback && callback(element);
|
|
1045
|
-
return element;
|
|
1046
|
-
};
|
|
1047
|
-
element.nd.ref = (target, name) => {
|
|
1048
|
-
target[name] = element;
|
|
1049
|
-
return element;
|
|
1050
|
-
};
|
|
1051
|
-
|
|
1052
|
-
let $observer = null;
|
|
1053
|
-
|
|
1054
|
-
element.nd.appendChild = function(child) {
|
|
1055
|
-
if(Validator.isArray(child)) {
|
|
1056
|
-
ElementCreator.processChildren(child, element);
|
|
1057
|
-
return;
|
|
1058
|
-
}
|
|
1059
|
-
if(Validator.isFunction(child)) {
|
|
1060
|
-
child = child();
|
|
1061
|
-
ElementCreator.processChildren(child(), element);
|
|
1062
|
-
}
|
|
1063
|
-
if(Validator.isElement(child)) {
|
|
1064
|
-
ElementCreator.processChildren(child, element);
|
|
1065
|
-
}
|
|
1066
|
-
};
|
|
1067
|
-
|
|
1068
|
-
element.nd.lifecycle = function(states) {
|
|
1069
|
-
$observer = $observer || DocumentObserver.watch(element);
|
|
995
|
+
DocumentObserver.observer = new MutationObserver(DocumentObserver.checkMutation);
|
|
996
|
+
DocumentObserver.observer.observe(document.body, {
|
|
997
|
+
childList: true,
|
|
998
|
+
subtree: true,
|
|
999
|
+
});
|
|
1070
1000
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1001
|
+
Object.defineProperty(HTMLElement.prototype, 'nd', {
|
|
1002
|
+
get() {
|
|
1003
|
+
if(this.$ndProx) {
|
|
1004
|
+
return this.$ndProx;
|
|
1005
|
+
}
|
|
1006
|
+
let element = this;
|
|
1007
|
+
let lifecycle = null;
|
|
1008
|
+
|
|
1009
|
+
this.$ndProx = new Proxy({}, {
|
|
1010
|
+
get(target, property) {
|
|
1011
|
+
if(/^on[A-Z]/.test(property)) {
|
|
1012
|
+
const event = property.replace(/^on/, '').toLowerCase();
|
|
1013
|
+
const shouldPrevent = event.toLowerCase().startsWith('prevent');
|
|
1014
|
+
let eventName = event.replace(/^prevent/i, '');
|
|
1015
|
+
const shouldStop = event.toLowerCase().startsWith('stop');
|
|
1016
|
+
eventName = eventName.replace(/^stop/i, '');
|
|
1017
|
+
|
|
1018
|
+
return function(callback) {
|
|
1019
|
+
if(shouldPrevent && !shouldStop) {
|
|
1020
|
+
element.addEventListener(eventName, function(event) {
|
|
1021
|
+
event.preventDefault();
|
|
1022
|
+
callback(event);
|
|
1023
|
+
});
|
|
1024
|
+
return element;
|
|
1025
|
+
}
|
|
1026
|
+
if(!shouldPrevent && shouldStop) {
|
|
1027
|
+
element.addEventListener(eventName, function(event) {
|
|
1028
|
+
event.stopPropagation();
|
|
1029
|
+
callback(event);
|
|
1030
|
+
});
|
|
1031
|
+
return element;
|
|
1032
|
+
}
|
|
1033
|
+
if(shouldPrevent && shouldStop) {
|
|
1034
|
+
element.addEventListener(eventName, function(event) {
|
|
1035
|
+
event.preventDefault();
|
|
1036
|
+
event.stopPropagation();
|
|
1037
|
+
callback(event);
|
|
1038
|
+
});
|
|
1039
|
+
return element;
|
|
1040
|
+
}
|
|
1041
|
+
element.addEventListener(eventName, callback);
|
|
1042
|
+
return element;
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
if(property === 'ref') {
|
|
1046
|
+
if(ref) {
|
|
1047
|
+
return ref;
|
|
1048
|
+
}
|
|
1049
|
+
return function(target, name) {
|
|
1050
|
+
target[name] = element;
|
|
1051
|
+
return element;
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
if(property === 'unmountChildren') {
|
|
1055
|
+
return () => {
|
|
1056
|
+
for(let i = 0, length = element.children.length; i < length; i++) {
|
|
1057
|
+
let elementchildren = element.children[i];
|
|
1058
|
+
if(!elementchildren.$ndProx) {
|
|
1059
|
+
elementchildren.nd?.remove();
|
|
1060
|
+
}
|
|
1061
|
+
elementchildren = null;
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
if(property === 'remove') {
|
|
1066
|
+
return function() {
|
|
1067
|
+
element.nd.unmountChildren();
|
|
1068
|
+
lifecycle = null;
|
|
1069
|
+
element.$ndProx = null;
|
|
1070
|
+
delete element.nd?.on?.prevent;
|
|
1071
|
+
delete element.nd?.on;
|
|
1072
|
+
delete element.nd;
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
if(property === 'hasLifecycle') {
|
|
1076
|
+
return lifecycle !== null;
|
|
1077
|
+
}
|
|
1078
|
+
if(property === 'lifecycle') {
|
|
1079
|
+
if(lifecycle) {
|
|
1080
|
+
return lifecycle;
|
|
1081
|
+
}
|
|
1082
|
+
let $observer = null;
|
|
1083
|
+
lifecycle = function(states) {
|
|
1084
|
+
$observer = $observer || DocumentObserver.watch(element);
|
|
1085
|
+
|
|
1086
|
+
states.mounted && $observer.mounted(states.mounted);
|
|
1087
|
+
states.unmounted && $observer.unmounted(states.unmounted);
|
|
1088
|
+
return element;
|
|
1089
|
+
};
|
|
1090
|
+
return lifecycle;
|
|
1091
|
+
}
|
|
1092
|
+
if(property === 'mounted' || property === 'unmounted') {
|
|
1093
|
+
return function(callback) {
|
|
1094
|
+
element.nd.lifecycle({ [property]: callback});
|
|
1095
|
+
return element;
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
},
|
|
1099
|
+
set(target, p, newValue, receiver) {
|
|
1075
1100
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
return
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
$observer = $observer || DocumentObserver.watch(element);
|
|
1083
|
-
$observer.unmounted(callback);
|
|
1084
|
-
return element;
|
|
1085
|
-
};
|
|
1086
|
-
};
|
|
1101
|
+
},
|
|
1102
|
+
configurable: true
|
|
1103
|
+
});
|
|
1104
|
+
return this.$ndProx;
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1087
1107
|
|
|
1088
1108
|
/**
|
|
1089
1109
|
*
|
|
@@ -1092,84 +1112,10 @@ var NativeDocument = (function (exports) {
|
|
|
1092
1112
|
*/
|
|
1093
1113
|
const createTextNode = function(value) {
|
|
1094
1114
|
return (Validator.isObservable(value))
|
|
1095
|
-
? createObservableNode(null, value)
|
|
1096
|
-
: createStaticTextNode(null, value);
|
|
1115
|
+
? ElementCreator.createObservableNode(null, value)
|
|
1116
|
+
: ElementCreator.createStaticTextNode(null, value);
|
|
1097
1117
|
};
|
|
1098
1118
|
|
|
1099
|
-
const ElementCreator = {
|
|
1100
|
-
/**
|
|
1101
|
-
*
|
|
1102
|
-
* @param {string} name
|
|
1103
|
-
* @returns {HTMLElement|DocumentFragment}
|
|
1104
|
-
*/
|
|
1105
|
-
createElement(name) {
|
|
1106
|
-
return name ? document.createElement(name) : new Anchor('Fragment');
|
|
1107
|
-
},
|
|
1108
|
-
/**
|
|
1109
|
-
*
|
|
1110
|
-
* @param {*} children
|
|
1111
|
-
* @param {HTMLElement|DocumentFragment} parent
|
|
1112
|
-
*/
|
|
1113
|
-
processChildren(children, parent) {
|
|
1114
|
-
if(children === null) return;
|
|
1115
|
-
const childrenArray = Array.isArray(children) ? children : [children];
|
|
1116
|
-
childrenArray.forEach(child => {
|
|
1117
|
-
if (child === null) return;
|
|
1118
|
-
if(Validator.isString(child) && Validator.isFunction(child.resolveObservableTemplate)) {
|
|
1119
|
-
child = child.resolveObservableTemplate();
|
|
1120
|
-
}
|
|
1121
|
-
if(Validator.isFunction(child)) {
|
|
1122
|
-
this.processChildren(child(), parent);
|
|
1123
|
-
return;
|
|
1124
|
-
}
|
|
1125
|
-
if(Validator.isArray(child)) {
|
|
1126
|
-
this.processChildren(child, parent);
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
if (Validator.isElement(child)) {
|
|
1130
|
-
parent.appendChild(child);
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
if (Validator.isObservable(child)) {
|
|
1134
|
-
createObservableNode(parent, child);
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
if (child) {
|
|
1138
|
-
createStaticTextNode(parent, child);
|
|
1139
|
-
}
|
|
1140
|
-
});
|
|
1141
|
-
},
|
|
1142
|
-
/**
|
|
1143
|
-
*
|
|
1144
|
-
* @param {HTMLElement} element
|
|
1145
|
-
* @param {Object} attributes
|
|
1146
|
-
*/
|
|
1147
|
-
processAttributes(element, attributes) {
|
|
1148
|
-
if(Validator.isFragment(element)) return;
|
|
1149
|
-
if (attributes) {
|
|
1150
|
-
AttributesWrapper(element, attributes);
|
|
1151
|
-
}
|
|
1152
|
-
},
|
|
1153
|
-
/**
|
|
1154
|
-
*
|
|
1155
|
-
* @param {HTMLElement} element
|
|
1156
|
-
* @param {Object} attributes
|
|
1157
|
-
* @param {?Function} customWrapper
|
|
1158
|
-
* @returns {HTMLElement|DocumentFragment}
|
|
1159
|
-
*/
|
|
1160
|
-
setup(element, attributes, customWrapper) {
|
|
1161
|
-
element.nd = {};
|
|
1162
|
-
HtmlElementEventsWrapper(element);
|
|
1163
|
-
const item = (typeof customWrapper === 'function') ? customWrapper(element) : element;
|
|
1164
|
-
addUtilsMethods(item);
|
|
1165
|
-
|
|
1166
|
-
pluginsManager.list().forEach(plugin => {
|
|
1167
|
-
plugin?.element?.setup && plugin.element.setup(item, attributes);
|
|
1168
|
-
});
|
|
1169
|
-
|
|
1170
|
-
return item;
|
|
1171
|
-
}
|
|
1172
|
-
};
|
|
1173
1119
|
|
|
1174
1120
|
/**
|
|
1175
1121
|
*
|
|
@@ -1178,7 +1124,7 @@ var NativeDocument = (function (exports) {
|
|
|
1178
1124
|
* @returns {Function}
|
|
1179
1125
|
*/
|
|
1180
1126
|
function HtmlElementWrapper(name, customWrapper) {
|
|
1181
|
-
const $tagName = name.toLowerCase()
|
|
1127
|
+
const $tagName = name.toLowerCase();
|
|
1182
1128
|
|
|
1183
1129
|
const builder = function(attributes, children = null) {
|
|
1184
1130
|
try {
|
|
@@ -1188,11 +1134,12 @@ var NativeDocument = (function (exports) {
|
|
|
1188
1134
|
attributes = tempChildren;
|
|
1189
1135
|
}
|
|
1190
1136
|
const element = ElementCreator.createElement($tagName);
|
|
1137
|
+
const finalElement = (typeof customWrapper === 'function') ? customWrapper(element) : element;
|
|
1191
1138
|
|
|
1192
|
-
ElementCreator.processAttributes(
|
|
1193
|
-
ElementCreator.processChildren(children,
|
|
1139
|
+
ElementCreator.processAttributes(finalElement, attributes);
|
|
1140
|
+
ElementCreator.processChildren(children, finalElement);
|
|
1194
1141
|
|
|
1195
|
-
return ElementCreator.setup(
|
|
1142
|
+
return ElementCreator.setup(finalElement, attributes, customWrapper);
|
|
1196
1143
|
} catch (error) {
|
|
1197
1144
|
DebugManager.error('ElementCreation', `Error creating ${$tagName}`, error);
|
|
1198
1145
|
}
|
|
@@ -1276,75 +1223,298 @@ var NativeDocument = (function (exports) {
|
|
|
1276
1223
|
if (!schema.optional) {
|
|
1277
1224
|
errors.push(`${fnName}: Missing required argument '${schema.name}' at position ${position}`);
|
|
1278
1225
|
}
|
|
1279
|
-
return;
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
if (!schema.validate(value)) {
|
|
1230
|
+
const valueTypeOf = value?.constructor?.name || typeof value;
|
|
1231
|
+
errors.push(`${fnName}: Invalid argument '${schema.name}' at position ${position}, expected ${schema.type}, got ${valueTypeOf}`);
|
|
1232
|
+
}
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
if (errors.length > 0) {
|
|
1236
|
+
throw new ArgTypesError(`Argument validation failed`, errors);
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* @param {Function} fn
|
|
1242
|
+
* @param {Array} argSchema
|
|
1243
|
+
* @param {string} fnName
|
|
1244
|
+
* @returns {Function}
|
|
1245
|
+
*/
|
|
1246
|
+
const withValidation = (fn, argSchema, fnName = 'Function') => {
|
|
1247
|
+
if(!Validator.isArray(argSchema)) {
|
|
1248
|
+
throw new NativeDocumentError('withValidation : argSchema must be an array');
|
|
1249
|
+
}
|
|
1250
|
+
return function(...args) {
|
|
1251
|
+
validateArgs(args, argSchema, fn.name || fnName);
|
|
1252
|
+
return fn.apply(this, args);
|
|
1253
|
+
};
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
Function.prototype.args = function(...args) {
|
|
1257
|
+
return withValidation(this, args);
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1260
|
+
Function.prototype.errorBoundary = function(callback) {
|
|
1261
|
+
return (...args) => {
|
|
1262
|
+
try {
|
|
1263
|
+
return this.apply(this, args);
|
|
1264
|
+
} catch(e) {
|
|
1265
|
+
return callback(e);
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
String.prototype.use = function(args) {
|
|
1271
|
+
const value = this;
|
|
1272
|
+
|
|
1273
|
+
return Observable.computed(() => {
|
|
1274
|
+
return value.replace(/\$\{(.*?)}/g, (match, key) => {
|
|
1275
|
+
const data = args[key];
|
|
1276
|
+
if(Validator.isObservable(data)) {
|
|
1277
|
+
return data.val();
|
|
1278
|
+
}
|
|
1279
|
+
return data;
|
|
1280
|
+
});
|
|
1281
|
+
}, Object.values(args));
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1284
|
+
String.prototype.resolveObservableTemplate = function() {
|
|
1285
|
+
if(!Validator.containsObservableReference(this)) {
|
|
1286
|
+
return this;
|
|
1287
|
+
}
|
|
1288
|
+
return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
|
|
1289
|
+
if(!Validator.containsObservableReference(value)) {
|
|
1290
|
+
return value;
|
|
1291
|
+
}
|
|
1292
|
+
const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
|
|
1293
|
+
return Observable.getById(id);
|
|
1294
|
+
});
|
|
1295
|
+
};
|
|
1296
|
+
|
|
1297
|
+
const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
*
|
|
1301
|
+
* @param {Array} target
|
|
1302
|
+
* @returns {ObservableItem}
|
|
1303
|
+
*/
|
|
1304
|
+
Observable.array = function(target) {
|
|
1305
|
+
if(!Array.isArray(target)) {
|
|
1306
|
+
throw new NativeDocumentError('Observable.array : target must be an array');
|
|
1307
|
+
}
|
|
1308
|
+
const observer = Observable(target);
|
|
1309
|
+
|
|
1310
|
+
methods.forEach((method) => {
|
|
1311
|
+
observer[method] = function(...values) {
|
|
1312
|
+
const result = observer.val()[method](...values);
|
|
1313
|
+
observer.trigger({ action: method, args: values, result });
|
|
1314
|
+
return result;
|
|
1315
|
+
};
|
|
1316
|
+
});
|
|
1317
|
+
|
|
1318
|
+
observer.clear = function() {
|
|
1319
|
+
observer.$value.length = 0;
|
|
1320
|
+
observer.trigger({ action: 'clear' });
|
|
1321
|
+
return true;
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
observer.remove = function(index) {
|
|
1325
|
+
const deleted = observer.$value.splice(index, 1);
|
|
1326
|
+
if(deleted.length === 0) {
|
|
1327
|
+
return [];
|
|
1328
|
+
}
|
|
1329
|
+
observer.trigger({ action: 'remove', args: [index], result: deleted[0] });
|
|
1330
|
+
return deleted;
|
|
1331
|
+
};
|
|
1332
|
+
|
|
1333
|
+
observer.swap = function(indexA, indexB) {
|
|
1334
|
+
const value = observer.$value;
|
|
1335
|
+
const length = value.length;
|
|
1336
|
+
if(length < indexA || length < indexB) {
|
|
1337
|
+
return false;
|
|
1338
|
+
}
|
|
1339
|
+
if(indexB < indexA) {
|
|
1340
|
+
const temp = indexA;
|
|
1341
|
+
indexA = indexB;
|
|
1342
|
+
indexB = temp;
|
|
1343
|
+
}
|
|
1344
|
+
const elementA = value[indexA];
|
|
1345
|
+
const elementB = value[indexB];
|
|
1346
|
+
|
|
1347
|
+
value[indexA] = elementB;
|
|
1348
|
+
value[indexB] = elementA;
|
|
1349
|
+
observer.trigger({ action: 'swap', args: [indexA, indexB], result: [elementA, elementB] });
|
|
1350
|
+
return true;
|
|
1351
|
+
};
|
|
1352
|
+
|
|
1353
|
+
observer.length = function() {
|
|
1354
|
+
return observer.$value.length;
|
|
1355
|
+
};
|
|
1356
|
+
|
|
1357
|
+
const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex', 'concat'];
|
|
1358
|
+
overrideMethods.forEach((method) => {
|
|
1359
|
+
observer[method] = function(...args) {
|
|
1360
|
+
return observer.val()[method](...args);
|
|
1361
|
+
};
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
return observer;
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
*
|
|
1369
|
+
* @param {Function} callback
|
|
1370
|
+
* @returns {Function}
|
|
1371
|
+
*/
|
|
1372
|
+
Observable.batch = function(callback) {
|
|
1373
|
+
const $observer = Observable(0);
|
|
1374
|
+
const batch = function() {
|
|
1375
|
+
if(Validator.isAsyncFunction(callback)) {
|
|
1376
|
+
return (callback(...arguments)).then(() => {
|
|
1377
|
+
$observer.trigger();
|
|
1378
|
+
}).catch(error => { throw error; });
|
|
1379
|
+
}
|
|
1380
|
+
callback(...arguments);
|
|
1381
|
+
$observer.trigger();
|
|
1382
|
+
};
|
|
1383
|
+
batch.$observer = $observer;
|
|
1384
|
+
return batch;
|
|
1385
|
+
};
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
*
|
|
1389
|
+
* @param {Object} value
|
|
1390
|
+
* @returns {Proxy}
|
|
1391
|
+
*/
|
|
1392
|
+
Observable.init = function(value) {
|
|
1393
|
+
const data = {};
|
|
1394
|
+
for(const key in value) {
|
|
1395
|
+
const itemValue = value[key];
|
|
1396
|
+
if(Validator.isJson(itemValue)) {
|
|
1397
|
+
data[key] = Observable.init(itemValue);
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
else if(Validator.isArray(itemValue)) {
|
|
1401
|
+
data[key] = Observable.array(itemValue);
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
data[key] = Observable(itemValue);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
const $val = function() {
|
|
1408
|
+
const result = {};
|
|
1409
|
+
for(const key in data) {
|
|
1410
|
+
const dataItem = data[key];
|
|
1411
|
+
if(Validator.isObservable(dataItem)) {
|
|
1412
|
+
result[key] = dataItem.val();
|
|
1413
|
+
} else if(Validator.isProxy(dataItem)) {
|
|
1414
|
+
result[key] = dataItem.$value;
|
|
1415
|
+
} else {
|
|
1416
|
+
result[key] = dataItem;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return result;
|
|
1420
|
+
};
|
|
1421
|
+
const $clone = function() {
|
|
1422
|
+
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
return new Proxy(data, {
|
|
1426
|
+
get(target, property) {
|
|
1427
|
+
if(property === '__isProxy__') {
|
|
1428
|
+
return true;
|
|
1429
|
+
}
|
|
1430
|
+
if(property === '$value') {
|
|
1431
|
+
return $val();
|
|
1432
|
+
}
|
|
1433
|
+
if(property === '$clone') {
|
|
1434
|
+
return $clone;
|
|
1435
|
+
}
|
|
1436
|
+
if(target[property] !== undefined) {
|
|
1437
|
+
return target[property];
|
|
1438
|
+
}
|
|
1439
|
+
return undefined;
|
|
1440
|
+
},
|
|
1441
|
+
set(target, prop, newValue) {
|
|
1442
|
+
if(target[prop] !== undefined) {
|
|
1443
|
+
target[prop].set(newValue);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
})
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* Get the value of an observable or an object of observables.
|
|
1451
|
+
* @param {ObservableItem|Object<ObservableItem>} data
|
|
1452
|
+
* @returns {{}|*|null}
|
|
1453
|
+
*/
|
|
1454
|
+
Observable.value = function(data) {
|
|
1455
|
+
if(Validator.isObservable(data)) {
|
|
1456
|
+
return data.val();
|
|
1457
|
+
}
|
|
1458
|
+
if(Validator.isProxy(data)) {
|
|
1459
|
+
return data.$value;
|
|
1460
|
+
}
|
|
1461
|
+
if(Validator.isArray(data)) {
|
|
1462
|
+
const result = [];
|
|
1463
|
+
data.forEach(item => {
|
|
1464
|
+
result.push(Observable.value(item));
|
|
1465
|
+
});
|
|
1466
|
+
return result;
|
|
1467
|
+
}
|
|
1468
|
+
return data;
|
|
1469
|
+
};
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
Observable.update = function($target, data) {
|
|
1473
|
+
for(const key in data) {
|
|
1474
|
+
const targetItem = $target[key];
|
|
1475
|
+
const newValue = data[key];
|
|
1476
|
+
|
|
1477
|
+
if(Validator.isObservable(targetItem)) {
|
|
1478
|
+
if(Validator.isArray(newValue)) {
|
|
1479
|
+
Observable.update(targetItem, newValue);
|
|
1480
|
+
continue;
|
|
1481
|
+
}
|
|
1482
|
+
targetItem.set(newValue);
|
|
1483
|
+
continue;
|
|
1280
1484
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
errors.push(`${fnName}: Invalid argument '${schema.name}' at position ${position}, expected ${schema.type}, got ${valueTypeOf}`);
|
|
1485
|
+
if(Validator.isProxy(targetItem)) {
|
|
1486
|
+
Observable.update(targetItem, newValue);
|
|
1487
|
+
continue;
|
|
1285
1488
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
if (errors.length > 0) {
|
|
1289
|
-
throw new ArgTypesError(`Argument validation failed`, errors);
|
|
1489
|
+
$target[key] = newValue;
|
|
1290
1490
|
}
|
|
1291
1491
|
};
|
|
1292
1492
|
|
|
1493
|
+
Observable.object = Observable.init;
|
|
1494
|
+
Observable.json = Observable.init;
|
|
1495
|
+
|
|
1293
1496
|
/**
|
|
1294
|
-
*
|
|
1295
|
-
* @param {
|
|
1296
|
-
* @param {
|
|
1297
|
-
* @returns {
|
|
1497
|
+
*
|
|
1498
|
+
* @param {Function} callback
|
|
1499
|
+
* @param {Array|Function} dependencies
|
|
1500
|
+
* @returns {ObservableItem}
|
|
1298
1501
|
*/
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
return function(...args) {
|
|
1304
|
-
validateArgs(args, argSchema, fn.name || fnName);
|
|
1305
|
-
return fn.apply(this, args);
|
|
1306
|
-
};
|
|
1307
|
-
};
|
|
1308
|
-
|
|
1309
|
-
Function.prototype.args = function(...args) {
|
|
1310
|
-
return withValidation(this, args);
|
|
1311
|
-
};
|
|
1502
|
+
Observable.computed = function(callback, dependencies = []) {
|
|
1503
|
+
const initialValue = callback();
|
|
1504
|
+
const observable = new ObservableItem(initialValue);
|
|
1505
|
+
const updatedValue = () => observable.set(callback());
|
|
1312
1506
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
return this.apply(this, args);
|
|
1317
|
-
} catch(e) {
|
|
1318
|
-
return callback(e);
|
|
1507
|
+
if(Validator.isFunction(dependencies)) {
|
|
1508
|
+
if(!Validator.isObservable(dependencies.$observer)) {
|
|
1509
|
+
throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
|
|
1319
1510
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
String.prototype.use = function(args) {
|
|
1324
|
-
const value = this;
|
|
1511
|
+
dependencies.$observer.subscribe(updatedValue);
|
|
1512
|
+
return observable;
|
|
1513
|
+
}
|
|
1325
1514
|
|
|
1326
|
-
|
|
1327
|
-
return value.replace(/\$\{(.*?)}/g, (match, key) => {
|
|
1328
|
-
const data = args[key];
|
|
1329
|
-
if(Validator.isObservable(data)) {
|
|
1330
|
-
return data.val();
|
|
1331
|
-
}
|
|
1332
|
-
return data;
|
|
1333
|
-
});
|
|
1334
|
-
}, Object.values(args));
|
|
1335
|
-
};
|
|
1515
|
+
dependencies.forEach(dependency => dependency.subscribe(updatedValue));
|
|
1336
1516
|
|
|
1337
|
-
|
|
1338
|
-
if(!Validator.containsObservableReference(this)) {
|
|
1339
|
-
return this;
|
|
1340
|
-
}
|
|
1341
|
-
return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
|
|
1342
|
-
if(!Validator.containsObservableReference(value)) {
|
|
1343
|
-
return value;
|
|
1344
|
-
}
|
|
1345
|
-
const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
|
|
1346
|
-
return Observable.getById(id);
|
|
1347
|
-
});
|
|
1517
|
+
return observable;
|
|
1348
1518
|
};
|
|
1349
1519
|
|
|
1350
1520
|
const Store = (function() {
|
|
@@ -1420,36 +1590,6 @@ var NativeDocument = (function (exports) {
|
|
|
1420
1590
|
};
|
|
1421
1591
|
}());
|
|
1422
1592
|
|
|
1423
|
-
/**
|
|
1424
|
-
*
|
|
1425
|
-
* @param {*} item
|
|
1426
|
-
* @param {string|null} defaultKey
|
|
1427
|
-
* @param {?Function} key
|
|
1428
|
-
* @returns {*}
|
|
1429
|
-
*/
|
|
1430
|
-
const getKey = (item, defaultKey, key) => {
|
|
1431
|
-
if(Validator.isFunction(key)) return key(item, defaultKey);
|
|
1432
|
-
if(Validator.isObservable(item)) {
|
|
1433
|
-
const val = item.val();
|
|
1434
|
-
return (val && key) ? val[key] : defaultKey;
|
|
1435
|
-
}
|
|
1436
|
-
return item[key] ?? defaultKey;
|
|
1437
|
-
};
|
|
1438
|
-
|
|
1439
|
-
/**
|
|
1440
|
-
*
|
|
1441
|
-
* @param {Map} cache
|
|
1442
|
-
* @param {Set} keyIds
|
|
1443
|
-
*/
|
|
1444
|
-
const cleanBlockByCache = (cache, keyIds) => {
|
|
1445
|
-
for(const [key, {child}] of cache.entries()) {
|
|
1446
|
-
if(keyIds.has(key)) {
|
|
1447
|
-
continue;
|
|
1448
|
-
}
|
|
1449
|
-
child.remove();
|
|
1450
|
-
}
|
|
1451
|
-
};
|
|
1452
|
-
|
|
1453
1593
|
/**
|
|
1454
1594
|
*
|
|
1455
1595
|
* @param {Array|Object|ObservableItem} data
|
|
@@ -1460,63 +1600,408 @@ var NativeDocument = (function (exports) {
|
|
|
1460
1600
|
function ForEach(data, callback, key) {
|
|
1461
1601
|
const element = new Anchor('ForEach');
|
|
1462
1602
|
const blockEnd = element.endElement();
|
|
1603
|
+
element.startElement();
|
|
1463
1604
|
|
|
1464
1605
|
let cache = new Map();
|
|
1606
|
+
let lastKeyOrder = null;
|
|
1607
|
+
const keyIds = new Set();
|
|
1608
|
+
|
|
1609
|
+
const clear = () => {
|
|
1610
|
+
element.removeChildren();
|
|
1611
|
+
cleanCache();
|
|
1612
|
+
};
|
|
1613
|
+
|
|
1614
|
+
const cleanCache = (parent) => {
|
|
1615
|
+
for(const [keyId, cacheItem] of cache.entries()) {
|
|
1616
|
+
if(keyIds.has(keyId)) {
|
|
1617
|
+
continue;
|
|
1618
|
+
}
|
|
1619
|
+
const child = cacheItem.child?.deref();
|
|
1620
|
+
if(parent && child) {
|
|
1621
|
+
parent.removeChild(child);
|
|
1622
|
+
}
|
|
1623
|
+
cacheItem.indexObserver?.cleanup();
|
|
1624
|
+
cacheItem.child = null;
|
|
1625
|
+
cacheItem.indexObserver = null;
|
|
1626
|
+
cache.delete(cacheItem.keyId);
|
|
1627
|
+
lastKeyOrder && lastKeyOrder.delete(cacheItem.keyId);
|
|
1628
|
+
}
|
|
1629
|
+
};
|
|
1465
1630
|
|
|
1466
1631
|
const handleContentItem = (item, indexKey) => {
|
|
1467
1632
|
const keyId = getKey(item, indexKey, key);
|
|
1468
1633
|
|
|
1469
1634
|
if(cache.has(keyId)) {
|
|
1470
|
-
cache.get(keyId)
|
|
1635
|
+
const cacheItem = cache.get(keyId);
|
|
1636
|
+
cacheItem.indexObserver?.set(indexKey);
|
|
1637
|
+
cacheItem.isNew = false;
|
|
1638
|
+
if(cacheItem.child?.deref()) {
|
|
1639
|
+
return keyId;
|
|
1640
|
+
}
|
|
1641
|
+
cache.delete(keyId);
|
|
1471
1642
|
}
|
|
1472
|
-
|
|
1473
|
-
|
|
1643
|
+
|
|
1644
|
+
try {
|
|
1645
|
+
const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
|
|
1474
1646
|
let child = callback(item, indexObserver);
|
|
1475
1647
|
if(Validator.isStringOrObservable(child)) {
|
|
1476
1648
|
child = createTextNode(child);
|
|
1477
1649
|
}
|
|
1478
|
-
cache.set(keyId, { child, indexObserver});
|
|
1650
|
+
cache.set(keyId, { keyId, isNew: true, child: new WeakRef(child), indexObserver});
|
|
1651
|
+
} catch (e) {
|
|
1652
|
+
DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
|
|
1653
|
+
throw e;
|
|
1479
1654
|
}
|
|
1480
1655
|
return keyId;
|
|
1481
1656
|
};
|
|
1482
|
-
|
|
1657
|
+
|
|
1658
|
+
const batchDOMUpdates = (parent) => {
|
|
1659
|
+
const fragment = document.createDocumentFragment();
|
|
1660
|
+
for(const itemKey of keyIds) {
|
|
1661
|
+
const cacheItem = cache.get(itemKey);
|
|
1662
|
+
if(!cacheItem) {
|
|
1663
|
+
continue;
|
|
1664
|
+
}
|
|
1665
|
+
const child = cacheItem.child?.deref();
|
|
1666
|
+
child && fragment.appendChild(child);
|
|
1667
|
+
}
|
|
1668
|
+
parent.insertBefore(fragment, blockEnd);
|
|
1669
|
+
};
|
|
1670
|
+
|
|
1671
|
+
const diffingDOMUpdates = (parent) => {
|
|
1672
|
+
let fragment = document.createDocumentFragment();
|
|
1673
|
+
const newKeys = Array.from(keyIds);
|
|
1674
|
+
Array.from(lastKeyOrder);
|
|
1675
|
+
|
|
1676
|
+
for(const index in newKeys) {
|
|
1677
|
+
const itemKey = newKeys[index];
|
|
1678
|
+
const cacheItem = cache.get(itemKey);
|
|
1679
|
+
if(!cacheItem) {
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
const child = cacheItem.child.deref();
|
|
1683
|
+
if(!child) {
|
|
1684
|
+
continue;
|
|
1685
|
+
}
|
|
1686
|
+
fragment.appendChild(child);
|
|
1687
|
+
}
|
|
1688
|
+
element.replaceContent(fragment);
|
|
1689
|
+
};
|
|
1483
1690
|
|
|
1484
1691
|
const buildContent = () => {
|
|
1485
|
-
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
1486
1692
|
const parent = blockEnd.parentNode;
|
|
1487
1693
|
if(!parent) {
|
|
1488
1694
|
return;
|
|
1489
1695
|
}
|
|
1696
|
+
|
|
1697
|
+
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
1490
1698
|
keyIds.clear();
|
|
1491
1699
|
if(Array.isArray(items)) {
|
|
1492
|
-
|
|
1700
|
+
for(let i = 0, length = items.length; i < length; i++) {
|
|
1701
|
+
const keyId= handleContentItem(items[i], i);
|
|
1702
|
+
keyIds.add(keyId);
|
|
1703
|
+
}
|
|
1493
1704
|
} else {
|
|
1494
1705
|
for(const indexKey in items) {
|
|
1495
|
-
|
|
1706
|
+
const keyId = handleContentItem(items[indexKey], indexKey);
|
|
1707
|
+
keyIds.add(keyId);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
if(keyIds.size === 0) {
|
|
1712
|
+
clear();
|
|
1713
|
+
lastKeyOrder?.clear();
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
cleanCache(parent);
|
|
1718
|
+
if(!lastKeyOrder || lastKeyOrder.size === 0) {
|
|
1719
|
+
batchDOMUpdates(parent);
|
|
1720
|
+
} else {
|
|
1721
|
+
diffingDOMUpdates();
|
|
1722
|
+
}
|
|
1723
|
+
lastKeyOrder?.clear();
|
|
1724
|
+
lastKeyOrder = new Set([...keyIds]);
|
|
1725
|
+
};
|
|
1726
|
+
|
|
1727
|
+
buildContent();
|
|
1728
|
+
if(Validator.isObservable(data)) {
|
|
1729
|
+
data.subscribe(buildContent);
|
|
1730
|
+
}
|
|
1731
|
+
return element;
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
function ForEachArray(data, callback, key, configs = {}) {
|
|
1735
|
+
const element = new Anchor('ForEach Array');
|
|
1736
|
+
const blockEnd = element.endElement();
|
|
1737
|
+
const blockStart = element.startElement();
|
|
1738
|
+
|
|
1739
|
+
let cache = new Map();
|
|
1740
|
+
let nodeCacheByElement = new WeakMap();
|
|
1741
|
+
let lastNumberOfItems = 0;
|
|
1742
|
+
|
|
1743
|
+
const keysCache = new WeakMap();
|
|
1744
|
+
|
|
1745
|
+
const clear = () => {
|
|
1746
|
+
element.removeChildren();
|
|
1747
|
+
cleanCache();
|
|
1748
|
+
lastNumberOfItems = 0;
|
|
1749
|
+
};
|
|
1750
|
+
const getItemKey = (item, indexKey) => {
|
|
1751
|
+
if(keysCache.has(item)) {
|
|
1752
|
+
return keysCache.get(item);
|
|
1753
|
+
}
|
|
1754
|
+
return getKey(item, indexKey, key);
|
|
1755
|
+
};
|
|
1756
|
+
|
|
1757
|
+
const updateIndexObservers = (items, startFrom = 0) => {
|
|
1758
|
+
if(callback.length < 2) {
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
let index = startFrom;
|
|
1762
|
+
for(let i = startFrom, length = items?.length; i < length; i++) {
|
|
1763
|
+
const cacheItem = cache.get(getItemKey(items[i], i));
|
|
1764
|
+
if(!cacheItem) {
|
|
1765
|
+
continue;
|
|
1766
|
+
}
|
|
1767
|
+
cacheItem.indexObserver?.deref()?.set(index);
|
|
1768
|
+
index++;
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
|
|
1772
|
+
const removeCacheItem = (cacheItem, removeChild = true) => {
|
|
1773
|
+
if(!cacheItem) {
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
const child = cacheItem.child?.deref();
|
|
1777
|
+
cacheItem.indexObserver?.deref()?.cleanup();
|
|
1778
|
+
cacheItem.child = null;
|
|
1779
|
+
cacheItem.indexObserver = null;
|
|
1780
|
+
nodeCacheByElement.delete(cacheItem.item);
|
|
1781
|
+
keysCache.delete(cacheItem.item);
|
|
1782
|
+
cacheItem.item = null;
|
|
1783
|
+
if(removeChild) {
|
|
1784
|
+
child?.remove();
|
|
1785
|
+
cache.delete(cacheItem.keyId);
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
const removeCacheItemByKey = (keyId, removeChild = true) => {
|
|
1790
|
+
removeCacheItem(cache.get(keyId), removeChild);
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
const cleanCache = () => {
|
|
1794
|
+
for (const [keyId, cacheItem] of cache.entries()) {
|
|
1795
|
+
removeCacheItem(cacheItem, false);
|
|
1796
|
+
}
|
|
1797
|
+
cache.clear();
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1800
|
+
const buildItem = (item, indexKey) => {
|
|
1801
|
+
const keyId = getItemKey(item, indexKey);
|
|
1802
|
+
|
|
1803
|
+
if(cache.has(keyId)) {
|
|
1804
|
+
const cacheItem = cache.get(keyId);
|
|
1805
|
+
cacheItem.indexObserver?.deref()?.set(indexKey);
|
|
1806
|
+
cacheItem.isNew = false;
|
|
1807
|
+
const child = cacheItem.child?.deref();
|
|
1808
|
+
if(child) {
|
|
1809
|
+
return child;
|
|
1810
|
+
}
|
|
1811
|
+
cache.delete(keyId);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
try {
|
|
1815
|
+
const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
|
|
1816
|
+
let child = callback(item, indexObserver);
|
|
1817
|
+
if(Validator.isStringOrObservable(child)) {
|
|
1818
|
+
child = createTextNode(child);
|
|
1496
1819
|
}
|
|
1820
|
+
cache.set(keyId, {
|
|
1821
|
+
keyId,
|
|
1822
|
+
isNew: true,
|
|
1823
|
+
item,
|
|
1824
|
+
child: new WeakRef(child),
|
|
1825
|
+
indexObserver: (indexObserver ? new WeakRef(indexObserver) : null)
|
|
1826
|
+
});
|
|
1827
|
+
keysCache.set(item, keyId);
|
|
1828
|
+
if(Validator.isObject(item)) {
|
|
1829
|
+
nodeCacheByElement.set(item, child);
|
|
1830
|
+
}
|
|
1831
|
+
return child;
|
|
1832
|
+
} catch (e) {
|
|
1833
|
+
DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
|
|
1834
|
+
throw e;
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
const getChildByKey = function(keyId, fragment) {
|
|
1838
|
+
const cacheItem = cache.get(keyId);
|
|
1839
|
+
if(!cacheItem) {
|
|
1840
|
+
return null;
|
|
1841
|
+
}
|
|
1842
|
+
const child = cacheItem.child?.deref();
|
|
1843
|
+
if(!child) {
|
|
1844
|
+
removeCacheItem(cacheItem, false);
|
|
1845
|
+
return null;
|
|
1846
|
+
}
|
|
1847
|
+
return child;
|
|
1848
|
+
};
|
|
1849
|
+
|
|
1850
|
+
const removeByKey = function(keyId, fragment) {
|
|
1851
|
+
const cacheItem = cache.get(keyId);
|
|
1852
|
+
if(!cacheItem) {
|
|
1853
|
+
return null;
|
|
1854
|
+
}
|
|
1855
|
+
const child = cacheItem.child?.deref();
|
|
1856
|
+
if(!child) {
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
if(fragment) {
|
|
1861
|
+
fragment.appendChild(child);
|
|
1862
|
+
return;
|
|
1497
1863
|
}
|
|
1864
|
+
child.remove();
|
|
1865
|
+
};
|
|
1498
1866
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1867
|
+
const Actions = {
|
|
1868
|
+
toFragment(items, startIndexFrom = 0){
|
|
1869
|
+
const fragment = document.createDocumentFragment();
|
|
1870
|
+
for(let i = 0, length = items.length; i < length; i++) {
|
|
1871
|
+
fragment.append(buildItem(items[i], lastNumberOfItems));
|
|
1872
|
+
lastNumberOfItems++;
|
|
1873
|
+
}
|
|
1874
|
+
return fragment;
|
|
1875
|
+
},
|
|
1876
|
+
add(items, delay = 0) {
|
|
1877
|
+
setTimeout(() => {
|
|
1878
|
+
element.appendElement(Actions.toFragment(items));
|
|
1879
|
+
}, delay);
|
|
1880
|
+
},
|
|
1881
|
+
replace(items) {
|
|
1882
|
+
clear();
|
|
1883
|
+
Actions.add(items);
|
|
1884
|
+
},
|
|
1885
|
+
reOrder(items) {
|
|
1886
|
+
let child = null;
|
|
1887
|
+
const fragment = document.createDocumentFragment();
|
|
1888
|
+
for(const item of items) {
|
|
1889
|
+
child = nodeCacheByElement.get(item);
|
|
1890
|
+
if(child) {
|
|
1891
|
+
fragment.appendChild(child);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
child = null;
|
|
1895
|
+
element.appendElement(fragment, blockEnd);
|
|
1896
|
+
},
|
|
1897
|
+
removeOne(element, index) {
|
|
1898
|
+
let child = nodeCacheByElement.get(element);
|
|
1503
1899
|
if(child) {
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1900
|
+
child.remove();
|
|
1901
|
+
nodeCacheByElement.delete(element);
|
|
1902
|
+
removeCacheItemByKey(getItemKey(element, index));
|
|
1903
|
+
}
|
|
1904
|
+
child = null;
|
|
1905
|
+
},
|
|
1906
|
+
clear,
|
|
1907
|
+
push(items) {
|
|
1908
|
+
let delay = 0;
|
|
1909
|
+
if(configs.pushDelay) {
|
|
1910
|
+
delay = configs.pushDelay(items) ?? 0;
|
|
1911
|
+
} else {
|
|
1912
|
+
delay = (items.length >= 1000) ? 10 : 0;
|
|
1913
|
+
}
|
|
1914
|
+
Actions.add(items, delay);
|
|
1915
|
+
},
|
|
1916
|
+
unshift(values){
|
|
1917
|
+
element.insertBefore(Actions.toFragment(values), blockStart.nextSibling);
|
|
1918
|
+
},
|
|
1919
|
+
splice(args, deleted) {
|
|
1920
|
+
const [start, deleteCount, ...values] = args;
|
|
1921
|
+
let elementBeforeFirst = null;
|
|
1922
|
+
const garbageFragment = document.createDocumentFragment();
|
|
1923
|
+
|
|
1924
|
+
if(deleted.length > 0) {
|
|
1925
|
+
let firstKey = getItemKey(deleted[0], start);
|
|
1926
|
+
if(deleted.length === 1) {
|
|
1927
|
+
removeByKey(firstKey, garbageFragment);
|
|
1928
|
+
} else if(deleted.length > 1) {
|
|
1929
|
+
const firstChildRemoved = getChildByKey(firstKey);
|
|
1930
|
+
elementBeforeFirst = firstChildRemoved?.previousSibling;
|
|
1931
|
+
|
|
1932
|
+
for(let i = 0; i < deleted.length; i++) {
|
|
1933
|
+
const keyId = getItemKey(deleted[i], start + i);
|
|
1934
|
+
removeByKey(keyId, garbageFragment);
|
|
1935
|
+
}
|
|
1507
1936
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1937
|
+
} else {
|
|
1938
|
+
elementBeforeFirst = blockEnd;
|
|
1939
|
+
}
|
|
1940
|
+
garbageFragment.replaceChildren();
|
|
1941
|
+
|
|
1942
|
+
if(values && values.length && elementBeforeFirst) {
|
|
1943
|
+
element.insertBefore(Actions.toFragment(values), elementBeforeFirst.nextSibling);
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
},
|
|
1947
|
+
reverse(_, reversed) {
|
|
1948
|
+
Actions.reOrder(reversed);
|
|
1949
|
+
},
|
|
1950
|
+
sort(_, sorted) {
|
|
1951
|
+
Actions.reOrder(sorted);
|
|
1952
|
+
},
|
|
1953
|
+
remove(_, deleted) {
|
|
1954
|
+
Actions.removeOne(deleted);
|
|
1955
|
+
},
|
|
1956
|
+
pop(_, deleted) {
|
|
1957
|
+
Actions.removeOne(deleted);
|
|
1958
|
+
},
|
|
1959
|
+
shift(_, deleted) {
|
|
1960
|
+
Actions.removeOne(deleted);
|
|
1961
|
+
},
|
|
1962
|
+
swap(args, elements) {
|
|
1963
|
+
const parent = blockEnd.parentNode;
|
|
1964
|
+
|
|
1965
|
+
let childA = nodeCacheByElement.get(elements[0]);
|
|
1966
|
+
let childB = nodeCacheByElement.get(elements[1]);
|
|
1967
|
+
if(!childA || !childB) {
|
|
1968
|
+
return;
|
|
1510
1969
|
}
|
|
1970
|
+
|
|
1971
|
+
const childBNext = childB.nextSibling;
|
|
1972
|
+
parent.insertBefore(childB, childA);
|
|
1973
|
+
parent.insertBefore(childA, childBNext);
|
|
1974
|
+
childA = null;
|
|
1975
|
+
childB = null;
|
|
1511
1976
|
}
|
|
1512
1977
|
};
|
|
1513
1978
|
|
|
1514
|
-
buildContent()
|
|
1979
|
+
const buildContent = (items, _, operations) => {
|
|
1980
|
+
if(operations.action === 'clear' || !items.length) {
|
|
1981
|
+
if(lastNumberOfItems === 0) {
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
clear();
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
if(!operations?.action) {
|
|
1988
|
+
if(lastNumberOfItems === 0) {
|
|
1989
|
+
Actions.add(items);
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
Actions.replace(items);
|
|
1993
|
+
}
|
|
1994
|
+
else if(Actions[operations.action]) {
|
|
1995
|
+
Actions[operations.action](operations.args, operations.result);
|
|
1996
|
+
}
|
|
1997
|
+
updateIndexObservers(items, 0);
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2000
|
+
buildContent(data.val(), null, {action: null});
|
|
1515
2001
|
if(Validator.isObservable(data)) {
|
|
1516
|
-
data.subscribe(
|
|
1517
|
-
buildContent();
|
|
1518
|
-
}, 50, { debounce: true }));
|
|
2002
|
+
data.subscribe(buildContent);
|
|
1519
2003
|
}
|
|
2004
|
+
|
|
1520
2005
|
return element;
|
|
1521
2006
|
}
|
|
1522
2007
|
|
|
@@ -1851,6 +2336,10 @@ var NativeDocument = (function (exports) {
|
|
|
1851
2336
|
const UnorderedList = HtmlElementWrapper('ul');
|
|
1852
2337
|
const ListItem = HtmlElementWrapper('li');
|
|
1853
2338
|
|
|
2339
|
+
const Li = ListItem;
|
|
2340
|
+
const Ol = OrderedList;
|
|
2341
|
+
const Ul = UnorderedList;
|
|
2342
|
+
|
|
1854
2343
|
const Audio = HtmlElementWrapper('audio');
|
|
1855
2344
|
const Video = HtmlElementWrapper('video');
|
|
1856
2345
|
const Source = HtmlElementWrapper('source');
|
|
@@ -1919,6 +2408,7 @@ var NativeDocument = (function (exports) {
|
|
|
1919
2408
|
FileInput: FileInput,
|
|
1920
2409
|
Footer: Footer,
|
|
1921
2410
|
ForEach: ForEach,
|
|
2411
|
+
ForEachArray: ForEachArray,
|
|
1922
2412
|
Form: Form,
|
|
1923
2413
|
Fragment: Fragment,
|
|
1924
2414
|
H1: H1,
|
|
@@ -1939,6 +2429,7 @@ var NativeDocument = (function (exports) {
|
|
|
1939
2429
|
Label: Label,
|
|
1940
2430
|
LazyImg: LazyImg,
|
|
1941
2431
|
Legend: Legend,
|
|
2432
|
+
Li: Li,
|
|
1942
2433
|
Link: Link$1,
|
|
1943
2434
|
ListItem: ListItem,
|
|
1944
2435
|
Main: Main,
|
|
@@ -1950,6 +2441,7 @@ var NativeDocument = (function (exports) {
|
|
|
1950
2441
|
NativeDocumentFragment: Anchor,
|
|
1951
2442
|
Nav: Nav,
|
|
1952
2443
|
NumberInput: NumberInput,
|
|
2444
|
+
Ol: Ol,
|
|
1953
2445
|
Option: Option,
|
|
1954
2446
|
OrderedList: OrderedList,
|
|
1955
2447
|
Output: Output,
|
|
@@ -1995,6 +2487,7 @@ var NativeDocument = (function (exports) {
|
|
|
1995
2487
|
TimeInput: TimeInput,
|
|
1996
2488
|
Tr: Tr,
|
|
1997
2489
|
Track: Track,
|
|
2490
|
+
Ul: Ul,
|
|
1998
2491
|
UnorderedList: UnorderedList,
|
|
1999
2492
|
UrlInput: UrlInput,
|
|
2000
2493
|
Var: Var,
|