native-document 1.0.14 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/native-document.dev.js +1262 -839
- package/dist/native-document.min.js +1 -1
- package/docs/anchor.md +216 -53
- package/docs/conditional-rendering.md +25 -24
- package/docs/core-concepts.md +20 -19
- package/docs/elements.md +21 -20
- package/docs/getting-started.md +28 -27
- package/docs/lifecycle-events.md +2 -2
- package/docs/list-rendering.md +607 -0
- package/docs/memory-management.md +1 -1
- package/docs/observables.md +15 -14
- package/docs/routing.md +22 -22
- package/docs/state-management.md +8 -8
- package/docs/validation.md +0 -2
- package/index.js +6 -1
- package/package.json +1 -1
- package/readme.md +5 -4
- package/src/data/MemoryManager.js +8 -20
- package/src/data/Observable.js +2 -180
- package/src/data/ObservableChecker.js +25 -24
- package/src/data/ObservableItem.js +158 -79
- package/src/data/observable-helpers/array.js +74 -0
- package/src/data/observable-helpers/batch.js +22 -0
- package/src/data/observable-helpers/computed.js +28 -0
- package/src/data/observable-helpers/object.js +111 -0
- package/src/elements/anchor.js +54 -9
- package/src/elements/control/for-each-array.js +280 -0
- package/src/elements/control/for-each.js +87 -110
- package/src/elements/index.js +1 -0
- package/src/elements/list.js +4 -0
- package/src/utils/helpers.js +44 -21
- package/src/wrappers/AttributesWrapper.js +5 -18
- package/src/wrappers/DocumentObserver.js +58 -29
- package/src/wrappers/ElementCreator.js +114 -0
- package/src/wrappers/HtmlElementEventsWrapper.js +52 -65
- package/src/wrappers/HtmlElementWrapper.js +11 -167
- package/src/wrappers/NdPrototype.js +109 -0
|
@@ -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,35 +102,36 @@ var NativeDocument = (function (exports) {
|
|
|
193
102
|
function ObservableChecker($observable, $checker) {
|
|
194
103
|
this.observable = $observable;
|
|
195
104
|
this.checker = $checker;
|
|
196
|
-
|
|
105
|
+
this.unSubscriptions = [];
|
|
106
|
+
}
|
|
197
107
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
+
};
|
|
203
115
|
|
|
204
|
-
|
|
205
|
-
|
|
116
|
+
ObservableChecker.prototype.check = function(callback) {
|
|
117
|
+
return this.observable.check(() => callback(this.val()));
|
|
118
|
+
};
|
|
206
119
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.check = function(callback) {
|
|
211
|
-
return $observable.check(() => callback(this.val()));
|
|
212
|
-
};
|
|
120
|
+
ObservableChecker.prototype.val = function() {
|
|
121
|
+
return this.checker && this.checker(this.observable.val());
|
|
122
|
+
};
|
|
213
123
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
this.trigger = function() {
|
|
218
|
-
return $observable.trigger();
|
|
219
|
-
};
|
|
124
|
+
ObservableChecker.prototype.set = function(value) {
|
|
125
|
+
return this.observable.set(value);
|
|
126
|
+
};
|
|
220
127
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
128
|
+
ObservableChecker.prototype.trigger = function() {
|
|
129
|
+
return this.observable.trigger();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
ObservableChecker.prototype.cleanup = function() {
|
|
133
|
+
return this.observable.cleanup();
|
|
134
|
+
};
|
|
225
135
|
|
|
226
136
|
/**
|
|
227
137
|
*
|
|
@@ -236,104 +146,183 @@ var NativeDocument = (function (exports) {
|
|
|
236
146
|
throw new NativeDocumentError('ObservableItem cannot be an Observable');
|
|
237
147
|
}
|
|
238
148
|
|
|
239
|
-
|
|
149
|
+
this.$previousValue = value;
|
|
150
|
+
this.$currentValue = value;
|
|
151
|
+
this.$isCleanedUp = false;
|
|
240
152
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
let $isCleanedUp = false;
|
|
153
|
+
this.$listeners = null;
|
|
154
|
+
this.$watchers = null;
|
|
244
155
|
|
|
245
|
-
|
|
156
|
+
this.$memoryId = MemoryManager.register(this);
|
|
157
|
+
}
|
|
246
158
|
|
|
247
|
-
|
|
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
|
+
});
|
|
248
168
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
169
|
+
ObservableItem.prototype.triggerListeners = function(operations) {
|
|
170
|
+
const $listeners = this.$listeners;
|
|
171
|
+
const $previousValue = this.$previousValue;
|
|
172
|
+
const $currentValue = this.$currentValue;
|
|
173
|
+
|
|
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;
|
|
256
206
|
}
|
|
207
|
+
itemValue.else.callback();
|
|
208
|
+
itemValue.ifTrue.called = false;
|
|
257
209
|
});
|
|
210
|
+
}
|
|
211
|
+
};
|
|
258
212
|
|
|
259
|
-
|
|
213
|
+
ObservableItem.prototype.trigger = function(operations) {
|
|
214
|
+
this.triggerListeners(operations);
|
|
215
|
+
this.triggerWatchers();
|
|
216
|
+
};
|
|
260
217
|
|
|
261
|
-
|
|
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
|
+
};
|
|
262
230
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
231
|
+
ObservableItem.prototype.val = function() {
|
|
232
|
+
return this.$currentValue;
|
|
233
|
+
};
|
|
234
|
+
|
|
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();
|
|
270
246
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
+
};
|
|
275
258
|
|
|
276
|
-
|
|
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
|
+
}
|
|
277
273
|
|
|
278
|
-
this.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
};
|
|
274
|
+
this.$listeners.push(callback);
|
|
275
|
+
return () => this.unsubscribe(callback);
|
|
276
|
+
};
|
|
282
277
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
* @param {Function} callback
|
|
286
|
-
* @returns {(function(): void)}
|
|
287
|
-
*/
|
|
288
|
-
this.subscribe = (callback) => {
|
|
289
|
-
if ($isCleanedUp) {
|
|
290
|
-
DebugManager.warn('Observable subscription', '⚠️ Attempted to subscribe to a cleaned up observable.');
|
|
291
|
-
return () => {};
|
|
292
|
-
}
|
|
293
|
-
if (typeof callback !== 'function') {
|
|
294
|
-
throw new NativeDocumentError('Callback must be a function');
|
|
295
|
-
}
|
|
278
|
+
ObservableItem.prototype.on = function(value, callback, elseCallback) {
|
|
279
|
+
this.$watchers = this.$watchers ?? new Map();
|
|
296
280
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
281
|
+
let watchValueList = this.$watchers.get(value);
|
|
282
|
+
if(!watchValueList) {
|
|
283
|
+
watchValueList = new Set();
|
|
284
|
+
this.$watchers.set(value, watchValueList);
|
|
285
|
+
}
|
|
300
286
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
*/
|
|
305
|
-
this.unsubscribe = (callback) => {
|
|
306
|
-
const index = $listeners.indexOf(callback);
|
|
307
|
-
if (index > -1) {
|
|
308
|
-
$listeners.splice(index, 1);
|
|
309
|
-
}
|
|
287
|
+
let itemValue = {
|
|
288
|
+
ifTrue: { callback, called: false },
|
|
289
|
+
else: { callback: elseCallback, called: false }
|
|
310
290
|
};
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
291
|
+
watchValueList.add(itemValue);
|
|
292
|
+
return () => {
|
|
293
|
+
watchValueList?.delete(itemValue);
|
|
294
|
+
if(watchValueList.size === 0) {
|
|
295
|
+
this.$watchers?.delete(value);
|
|
296
|
+
}
|
|
297
|
+
watchValueList = null;
|
|
298
|
+
itemValue = null;
|
|
319
299
|
};
|
|
300
|
+
};
|
|
320
301
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
+
};
|
|
331
312
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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;
|
|
335
322
|
|
|
336
|
-
|
|
323
|
+
ObservableItem.prototype.toString = function() {
|
|
324
|
+
return '{{#ObItem::(' +this.$memoryId+ ')}}';
|
|
325
|
+
};
|
|
337
326
|
|
|
338
327
|
const Validator = {
|
|
339
328
|
isObservable(value) {
|
|
@@ -450,40 +439,182 @@ var NativeDocument = (function (exports) {
|
|
|
450
439
|
}
|
|
451
440
|
};
|
|
452
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
|
+
|
|
453
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'];
|
|
454
568
|
|
|
569
|
+
const invoke = function(fn, args, context) {
|
|
570
|
+
if(context) {
|
|
571
|
+
fn.apply(context, args);
|
|
572
|
+
} else {
|
|
573
|
+
fn(...args);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
455
576
|
/**
|
|
456
577
|
*
|
|
457
578
|
* @param {Function} fn
|
|
458
579
|
* @param {number} delay
|
|
459
|
-
* @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean}}options
|
|
580
|
+
* @param {{leading?:Boolean, trailing?:Boolean, debounce?:Boolean, check: Function}}options
|
|
460
581
|
* @returns {(function(...[*]): void)|*}
|
|
461
582
|
*/
|
|
462
|
-
const
|
|
583
|
+
const debounce = function(fn, delay, options = {}) {
|
|
463
584
|
let timer = null;
|
|
464
|
-
let
|
|
465
|
-
const { leading = true, trailing = true, debounce = false } = options;
|
|
585
|
+
let lastArgs = null;
|
|
466
586
|
|
|
467
587
|
return function(...args) {
|
|
468
|
-
const
|
|
469
|
-
if
|
|
470
|
-
|
|
471
|
-
clearTimeout(timer);
|
|
472
|
-
timer = setTimeout(() => fn.apply(this, args), delay);
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
if (leading && now - lastExecTime >= delay) {
|
|
476
|
-
fn.apply(this, args);
|
|
477
|
-
lastExecTime = now;
|
|
478
|
-
}
|
|
479
|
-
if (trailing && !timer) {
|
|
480
|
-
timer = setTimeout(() => {
|
|
481
|
-
fn.apply(this, args);
|
|
482
|
-
lastExecTime = Date.now();
|
|
483
|
-
timer = null;
|
|
484
|
-
}, delay - (now - lastExecTime));
|
|
588
|
+
const context = options.context === true ? this : null;
|
|
589
|
+
if(options.check) {
|
|
590
|
+
options.check(...args);
|
|
485
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;
|
|
613
|
+
}
|
|
614
|
+
if(!Validator.isObject(item)) {
|
|
615
|
+
return item;
|
|
486
616
|
}
|
|
617
|
+
return item[key] ?? defaultKey;
|
|
487
618
|
};
|
|
488
619
|
|
|
489
620
|
const trim = function(str, char) {
|
|
@@ -500,45 +631,6 @@ var NativeDocument = (function (exports) {
|
|
|
500
631
|
return new ObservableItem(value);
|
|
501
632
|
}
|
|
502
633
|
|
|
503
|
-
/**
|
|
504
|
-
*
|
|
505
|
-
* @param {Function} callback
|
|
506
|
-
* @param {Array|Function} dependencies
|
|
507
|
-
* @returns {ObservableItem}
|
|
508
|
-
*/
|
|
509
|
-
Observable.computed = function(callback, dependencies = []) {
|
|
510
|
-
const initialValue = callback();
|
|
511
|
-
const observable = new ObservableItem(initialValue);
|
|
512
|
-
const updatedValue = () => observable.set(callback());
|
|
513
|
-
|
|
514
|
-
if(Validator.isFunction(dependencies)) {
|
|
515
|
-
if(!Validator.isObservable(dependencies.$observer)) {
|
|
516
|
-
throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
|
|
517
|
-
}
|
|
518
|
-
dependencies.$observer.subscribe(updatedValue);
|
|
519
|
-
return observable;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
dependencies.forEach(dependency => dependency.subscribe(updatedValue));
|
|
523
|
-
|
|
524
|
-
return observable;
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
Observable.batch = function(callback) {
|
|
528
|
-
const $observer = Observable(0);
|
|
529
|
-
const batch = function() {
|
|
530
|
-
if(Validator.isAsyncFunction(callback)) {
|
|
531
|
-
return (callback(...arguments)).then(() => {
|
|
532
|
-
$observer.trigger();
|
|
533
|
-
}).catch(error => { throw error; });
|
|
534
|
-
}
|
|
535
|
-
callback(...arguments);
|
|
536
|
-
$observer.trigger();
|
|
537
|
-
};
|
|
538
|
-
batch.$observer = $observer;
|
|
539
|
-
return batch;
|
|
540
|
-
};
|
|
541
|
-
|
|
542
634
|
/**
|
|
543
635
|
*
|
|
544
636
|
* @param id
|
|
@@ -552,7 +644,6 @@ var NativeDocument = (function (exports) {
|
|
|
552
644
|
return item;
|
|
553
645
|
};
|
|
554
646
|
|
|
555
|
-
|
|
556
647
|
/**
|
|
557
648
|
*
|
|
558
649
|
* @param {ObservableItem} observable
|
|
@@ -561,144 +652,6 @@ var NativeDocument = (function (exports) {
|
|
|
561
652
|
observable.cleanup();
|
|
562
653
|
};
|
|
563
654
|
|
|
564
|
-
/**
|
|
565
|
-
* Get the value of an observable or an object of observables.
|
|
566
|
-
* @param {ObservableItem|Object<ObservableItem>} object
|
|
567
|
-
* @returns {{}|*|null}
|
|
568
|
-
*/
|
|
569
|
-
Observable.value = function(data) {
|
|
570
|
-
if(Validator.isObservable(data)) {
|
|
571
|
-
return data.val();
|
|
572
|
-
}
|
|
573
|
-
if(Validator.isProxy(data)) {
|
|
574
|
-
return data.$val();
|
|
575
|
-
}
|
|
576
|
-
if(Validator.isArray(data)) {
|
|
577
|
-
const result = [];
|
|
578
|
-
data.forEach(item => {
|
|
579
|
-
result.push(Observable.value(item));
|
|
580
|
-
});
|
|
581
|
-
return result;
|
|
582
|
-
}
|
|
583
|
-
return data;
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
/**
|
|
587
|
-
*
|
|
588
|
-
* @param {Object} value
|
|
589
|
-
* @returns {Proxy}
|
|
590
|
-
*/
|
|
591
|
-
Observable.init = function(value) {
|
|
592
|
-
const data = {};
|
|
593
|
-
for(const key in value) {
|
|
594
|
-
const itemValue = value[key];
|
|
595
|
-
if(Validator.isJson(itemValue)) {
|
|
596
|
-
data[key] = Observable.init(itemValue);
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
else if(Validator.isArray(itemValue)) {
|
|
600
|
-
data[key] = Observable.array(itemValue);
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
data[key] = Observable(itemValue);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
const $val = function() {
|
|
607
|
-
const result = {};
|
|
608
|
-
for(const key in data) {
|
|
609
|
-
const dataItem = data[key];
|
|
610
|
-
if(Validator.isObservable(dataItem)) {
|
|
611
|
-
result[key] = dataItem.val();
|
|
612
|
-
} else if(Validator.isProxy(dataItem)) {
|
|
613
|
-
result[key] = dataItem.$val();
|
|
614
|
-
} else {
|
|
615
|
-
result[key] = dataItem;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return result;
|
|
619
|
-
};
|
|
620
|
-
const $clone = function() {
|
|
621
|
-
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
return new Proxy(data, {
|
|
625
|
-
get(target, property) {
|
|
626
|
-
if(property === '__isProxy__') {
|
|
627
|
-
return true;
|
|
628
|
-
}
|
|
629
|
-
if(property === '$val') {
|
|
630
|
-
return $val;
|
|
631
|
-
}
|
|
632
|
-
if(property === '$clone') {
|
|
633
|
-
return $clone;
|
|
634
|
-
}
|
|
635
|
-
if(target[property] !== undefined) {
|
|
636
|
-
return target[property];
|
|
637
|
-
}
|
|
638
|
-
return undefined;
|
|
639
|
-
},
|
|
640
|
-
set(target, prop, newValue) {
|
|
641
|
-
if(target[prop] !== undefined) {
|
|
642
|
-
target[prop].set(newValue);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
})
|
|
646
|
-
};
|
|
647
|
-
|
|
648
|
-
Observable.object = Observable.init;
|
|
649
|
-
Observable.json = Observable.init;
|
|
650
|
-
Observable.update = function($target, data) {
|
|
651
|
-
for(const key in data) {
|
|
652
|
-
const targetItem = $target[key];
|
|
653
|
-
const newValue = data[key];
|
|
654
|
-
|
|
655
|
-
if(Validator.isObservable(targetItem)) {
|
|
656
|
-
if(Validator.isArray(newValue)) {
|
|
657
|
-
Observable.update(targetItem, newValue);
|
|
658
|
-
continue;
|
|
659
|
-
}
|
|
660
|
-
targetItem.set(newValue);
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
if(Validator.isProxy(targetItem)) {
|
|
664
|
-
Observable.update(targetItem, newValue);
|
|
665
|
-
continue;
|
|
666
|
-
}
|
|
667
|
-
$target[key] = newValue;
|
|
668
|
-
}
|
|
669
|
-
};
|
|
670
|
-
/**
|
|
671
|
-
*
|
|
672
|
-
* @param {Array} target
|
|
673
|
-
* @returns {ObservableItem}
|
|
674
|
-
*/
|
|
675
|
-
Observable.array = function(target) {
|
|
676
|
-
if(!Array.isArray(target)) {
|
|
677
|
-
throw new NativeDocumentError('Observable.array : target must be an array');
|
|
678
|
-
}
|
|
679
|
-
const observer = Observable(target);
|
|
680
|
-
|
|
681
|
-
const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
|
|
682
|
-
|
|
683
|
-
methods.forEach((method) => {
|
|
684
|
-
observer[method] = function(...values) {
|
|
685
|
-
const target = observer.val();
|
|
686
|
-
const result = target[method].apply(target, arguments);
|
|
687
|
-
observer.trigger();
|
|
688
|
-
return result;
|
|
689
|
-
};
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex'];
|
|
693
|
-
overrideMethods.forEach((method) => {
|
|
694
|
-
observer[method] = function(callback) {
|
|
695
|
-
return observer.val()[method](callback);
|
|
696
|
-
};
|
|
697
|
-
});
|
|
698
|
-
|
|
699
|
-
return observer;
|
|
700
|
-
};
|
|
701
|
-
|
|
702
655
|
/**
|
|
703
656
|
* Enable auto cleanup of observables.
|
|
704
657
|
* @param {Boolean} enable
|
|
@@ -717,20 +670,6 @@ var NativeDocument = (function (exports) {
|
|
|
717
670
|
setInterval(() => MemoryManager.cleanObservables(threshold), interval);
|
|
718
671
|
};
|
|
719
672
|
|
|
720
|
-
/**
|
|
721
|
-
*
|
|
722
|
-
* @param {HTMLElement} element
|
|
723
|
-
* @param {string} className
|
|
724
|
-
* @param {string} value
|
|
725
|
-
*/
|
|
726
|
-
const toggleClassItem = function(element, className, value) {
|
|
727
|
-
if(value) {
|
|
728
|
-
element.classList.add(className);
|
|
729
|
-
} else {
|
|
730
|
-
element.classList.remove(className);
|
|
731
|
-
}
|
|
732
|
-
};
|
|
733
|
-
|
|
734
673
|
/**
|
|
735
674
|
*
|
|
736
675
|
* @param {HTMLElement} element
|
|
@@ -740,11 +679,11 @@ var NativeDocument = (function (exports) {
|
|
|
740
679
|
for(let className in data) {
|
|
741
680
|
const value = data[className];
|
|
742
681
|
if(Validator.isObservable(value)) {
|
|
743
|
-
|
|
744
|
-
value.subscribe(newValue =>
|
|
682
|
+
element.classList.toggle(className, value.val());
|
|
683
|
+
value.subscribe(newValue => element.classList.toggle(className, newValue));
|
|
745
684
|
continue;
|
|
746
685
|
}
|
|
747
|
-
|
|
686
|
+
element.classList.toggle(className, value);
|
|
748
687
|
}
|
|
749
688
|
}
|
|
750
689
|
|
|
@@ -816,8 +755,8 @@ var NativeDocument = (function (exports) {
|
|
|
816
755
|
}
|
|
817
756
|
element.setAttribute(attributeName, newValue);
|
|
818
757
|
};
|
|
819
|
-
value.subscribe(applyValue);
|
|
820
758
|
applyValue(value.val());
|
|
759
|
+
value.subscribe(applyValue);
|
|
821
760
|
|
|
822
761
|
if(attributeName === 'value') {
|
|
823
762
|
element.addEventListener('input', () => value.set(element.value));
|
|
@@ -866,228 +805,305 @@ var NativeDocument = (function (exports) {
|
|
|
866
805
|
continue;
|
|
867
806
|
}
|
|
868
807
|
element.setAttribute(attributeName, value);
|
|
808
|
+
|
|
869
809
|
}
|
|
870
810
|
return element;
|
|
871
811
|
}
|
|
872
812
|
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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);
|
|
885
898
|
}
|
|
886
899
|
}
|
|
887
|
-
},
|
|
900
|
+
},
|
|
888
901
|
/**
|
|
889
902
|
*
|
|
890
903
|
* @param {HTMLElement} element
|
|
891
|
-
* @
|
|
904
|
+
* @param {Object} attributes
|
|
892
905
|
*/
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
if(
|
|
896
|
-
|
|
897
|
-
} else {
|
|
898
|
-
const inDom = document.body.contains(element);
|
|
899
|
-
data = {
|
|
900
|
-
inDom,
|
|
901
|
-
mounted: new Set(),
|
|
902
|
-
unmounted: new Set(),
|
|
903
|
-
};
|
|
904
|
-
DocumentObserver.elements.set(element, data);
|
|
906
|
+
processAttributes(element, attributes) {
|
|
907
|
+
if(Validator.isFragment(element)) return;
|
|
908
|
+
if (attributes) {
|
|
909
|
+
AttributesWrapper(element, attributes);
|
|
905
910
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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;
|
|
913
921
|
}
|
|
914
922
|
};
|
|
915
923
|
|
|
916
|
-
DocumentObserver
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
const anchorStart = document.createComment('Anchor Start : '+name);
|
|
936
|
-
const anchorEnd = document.createComment('/ Anchor End '+name);
|
|
937
|
-
|
|
938
|
-
element.appendChild(anchorStart);
|
|
939
|
-
element.appendChild(anchorEnd);
|
|
940
|
-
|
|
941
|
-
element.nativeInsertBefore = element.insertBefore;
|
|
942
|
-
element.nativeAppendChild = element.appendChild;
|
|
943
|
-
|
|
944
|
-
const insertBefore = function(parent, child, target) {
|
|
945
|
-
if(parent === element) {
|
|
946
|
-
parent.nativeInsertBefore(getChildAsNode(child), target);
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
parent.insertBefore(getChildAsNode(child), target);
|
|
950
|
-
};
|
|
951
|
-
|
|
952
|
-
element.appendChild = function(child, before = null) {
|
|
953
|
-
const parent = anchorEnd.parentNode;
|
|
954
|
-
if(!parent) {
|
|
955
|
-
DebugManager.error('Anchor', 'Anchor : parent not found', child);
|
|
956
|
-
return;
|
|
957
|
-
}
|
|
958
|
-
before = before ?? anchorEnd;
|
|
959
|
-
if(Validator.isArray(child)) {
|
|
960
|
-
child.forEach((element) => {
|
|
961
|
-
insertBefore(parent, element, before);
|
|
962
|
-
});
|
|
963
|
-
return element;
|
|
964
|
-
}
|
|
965
|
-
insertBefore(parent, child, before);
|
|
966
|
-
};
|
|
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
|
+
}
|
|
967
942
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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
|
+
}
|
|
981
957
|
}
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
const pluginsManager = (function() {
|
|
1003
|
-
|
|
1004
|
-
const $plugins = [];
|
|
1005
|
-
|
|
1006
|
-
return {
|
|
1007
|
-
list : () => $plugins,
|
|
1008
|
-
add : (plugin) => $plugins.push(plugin)
|
|
1009
|
-
};
|
|
1010
|
-
}());
|
|
1011
|
-
|
|
1012
|
-
/**
|
|
1013
|
-
*
|
|
1014
|
-
* @param {HTMLElement|DocumentFragment} parent
|
|
1015
|
-
* @param {ObservableItem} observable
|
|
1016
|
-
* @returns {Text}
|
|
1017
|
-
*/
|
|
1018
|
-
const createObservableNode = function(parent, observable) {
|
|
1019
|
-
const text = document.createTextNode('');
|
|
1020
|
-
observable.subscribe(value => text.textContent = String(value));
|
|
1021
|
-
text.textContent = observable.val();
|
|
1022
|
-
parent && parent.appendChild(text);
|
|
1023
|
-
return text;
|
|
1024
|
-
};
|
|
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
|
+
};
|
|
1025
978
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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
|
+
}
|
|
1037
993
|
};
|
|
1038
994
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
element.nd.wrap = (callback) => {
|
|
1045
|
-
if(!Validator.isFunction(callback)) {
|
|
1046
|
-
throw new NativeDocumentError('Callback must be a function');
|
|
1047
|
-
}
|
|
1048
|
-
callback && callback(element);
|
|
1049
|
-
return element;
|
|
1050
|
-
};
|
|
1051
|
-
element.nd.ref = (target, name) => {
|
|
1052
|
-
target[name] = element;
|
|
1053
|
-
return element;
|
|
1054
|
-
};
|
|
1055
|
-
|
|
1056
|
-
let $observer = null;
|
|
1057
|
-
|
|
1058
|
-
element.nd.appendChild = function(child) {
|
|
1059
|
-
if(Validator.isArray(child)) {
|
|
1060
|
-
ElementCreator.processChildren(child, element);
|
|
1061
|
-
return;
|
|
1062
|
-
}
|
|
1063
|
-
if(Validator.isFunction(child)) {
|
|
1064
|
-
child = child();
|
|
1065
|
-
ElementCreator.processChildren(child(), element);
|
|
1066
|
-
}
|
|
1067
|
-
if(Validator.isElement(child)) {
|
|
1068
|
-
ElementCreator.processChildren(child, element);
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
|
|
1072
|
-
element.nd.lifecycle = function(states) {
|
|
1073
|
-
$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
|
+
});
|
|
1074
1000
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
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) {
|
|
1079
1100
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
return
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
$observer = $observer || DocumentObserver.watch(element);
|
|
1087
|
-
$observer.unmounted(callback);
|
|
1088
|
-
return element;
|
|
1089
|
-
};
|
|
1090
|
-
};
|
|
1101
|
+
},
|
|
1102
|
+
configurable: true
|
|
1103
|
+
});
|
|
1104
|
+
return this.$ndProx;
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1091
1107
|
|
|
1092
1108
|
/**
|
|
1093
1109
|
*
|
|
@@ -1096,84 +1112,10 @@ var NativeDocument = (function (exports) {
|
|
|
1096
1112
|
*/
|
|
1097
1113
|
const createTextNode = function(value) {
|
|
1098
1114
|
return (Validator.isObservable(value))
|
|
1099
|
-
? createObservableNode(null, value)
|
|
1100
|
-
: createStaticTextNode(null, value);
|
|
1115
|
+
? ElementCreator.createObservableNode(null, value)
|
|
1116
|
+
: ElementCreator.createStaticTextNode(null, value);
|
|
1101
1117
|
};
|
|
1102
1118
|
|
|
1103
|
-
const ElementCreator = {
|
|
1104
|
-
/**
|
|
1105
|
-
*
|
|
1106
|
-
* @param {string} name
|
|
1107
|
-
* @returns {HTMLElement|DocumentFragment}
|
|
1108
|
-
*/
|
|
1109
|
-
createElement(name) {
|
|
1110
|
-
return name ? document.createElement(name) : new Anchor('Fragment');
|
|
1111
|
-
},
|
|
1112
|
-
/**
|
|
1113
|
-
*
|
|
1114
|
-
* @param {*} children
|
|
1115
|
-
* @param {HTMLElement|DocumentFragment} parent
|
|
1116
|
-
*/
|
|
1117
|
-
processChildren(children, parent) {
|
|
1118
|
-
if(children === null) return;
|
|
1119
|
-
const childrenArray = Array.isArray(children) ? children : [children];
|
|
1120
|
-
childrenArray.forEach(child => {
|
|
1121
|
-
if (child === null) return;
|
|
1122
|
-
if(Validator.isString(child) && Validator.isFunction(child.resolveObservableTemplate)) {
|
|
1123
|
-
child = child.resolveObservableTemplate();
|
|
1124
|
-
}
|
|
1125
|
-
if(Validator.isFunction(child)) {
|
|
1126
|
-
this.processChildren(child(), parent);
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
if(Validator.isArray(child)) {
|
|
1130
|
-
this.processChildren(child, parent);
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
if (Validator.isElement(child)) {
|
|
1134
|
-
parent.appendChild(child);
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
if (Validator.isObservable(child)) {
|
|
1138
|
-
createObservableNode(parent, child);
|
|
1139
|
-
return;
|
|
1140
|
-
}
|
|
1141
|
-
if (child) {
|
|
1142
|
-
createStaticTextNode(parent, child);
|
|
1143
|
-
}
|
|
1144
|
-
});
|
|
1145
|
-
},
|
|
1146
|
-
/**
|
|
1147
|
-
*
|
|
1148
|
-
* @param {HTMLElement} element
|
|
1149
|
-
* @param {Object} attributes
|
|
1150
|
-
*/
|
|
1151
|
-
processAttributes(element, attributes) {
|
|
1152
|
-
if(Validator.isFragment(element)) return;
|
|
1153
|
-
if (attributes) {
|
|
1154
|
-
AttributesWrapper(element, attributes);
|
|
1155
|
-
}
|
|
1156
|
-
},
|
|
1157
|
-
/**
|
|
1158
|
-
*
|
|
1159
|
-
* @param {HTMLElement} element
|
|
1160
|
-
* @param {Object} attributes
|
|
1161
|
-
* @param {?Function} customWrapper
|
|
1162
|
-
* @returns {HTMLElement|DocumentFragment}
|
|
1163
|
-
*/
|
|
1164
|
-
setup(element, attributes, customWrapper) {
|
|
1165
|
-
element.nd = {};
|
|
1166
|
-
HtmlElementEventsWrapper(element);
|
|
1167
|
-
const item = (typeof customWrapper === 'function') ? customWrapper(element) : element;
|
|
1168
|
-
addUtilsMethods(item);
|
|
1169
|
-
|
|
1170
|
-
pluginsManager.list().forEach(plugin => {
|
|
1171
|
-
plugin?.element?.setup && plugin.element.setup(item, attributes);
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
return item;
|
|
1175
|
-
}
|
|
1176
|
-
};
|
|
1177
1119
|
|
|
1178
1120
|
/**
|
|
1179
1121
|
*
|
|
@@ -1182,7 +1124,7 @@ var NativeDocument = (function (exports) {
|
|
|
1182
1124
|
* @returns {Function}
|
|
1183
1125
|
*/
|
|
1184
1126
|
function HtmlElementWrapper(name, customWrapper) {
|
|
1185
|
-
const $tagName = name.toLowerCase()
|
|
1127
|
+
const $tagName = name.toLowerCase();
|
|
1186
1128
|
|
|
1187
1129
|
const builder = function(attributes, children = null) {
|
|
1188
1130
|
try {
|
|
@@ -1192,11 +1134,12 @@ var NativeDocument = (function (exports) {
|
|
|
1192
1134
|
attributes = tempChildren;
|
|
1193
1135
|
}
|
|
1194
1136
|
const element = ElementCreator.createElement($tagName);
|
|
1137
|
+
const finalElement = (typeof customWrapper === 'function') ? customWrapper(element) : element;
|
|
1195
1138
|
|
|
1196
|
-
ElementCreator.processAttributes(
|
|
1197
|
-
ElementCreator.processChildren(children,
|
|
1139
|
+
ElementCreator.processAttributes(finalElement, attributes);
|
|
1140
|
+
ElementCreator.processChildren(children, finalElement);
|
|
1198
1141
|
|
|
1199
|
-
return ElementCreator.setup(
|
|
1142
|
+
return ElementCreator.setup(finalElement, attributes, customWrapper);
|
|
1200
1143
|
} catch (error) {
|
|
1201
1144
|
DebugManager.error('ElementCreation', `Error creating ${$tagName}`, error);
|
|
1202
1145
|
}
|
|
@@ -1304,51 +1247,274 @@ var NativeDocument = (function (exports) {
|
|
|
1304
1247
|
if(!Validator.isArray(argSchema)) {
|
|
1305
1248
|
throw new NativeDocumentError('withValidation : argSchema must be an array');
|
|
1306
1249
|
}
|
|
1307
|
-
return function(...args) {
|
|
1308
|
-
validateArgs(args, argSchema, fn.name || fnName);
|
|
1309
|
-
return fn.apply(this, args);
|
|
1310
|
-
};
|
|
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;
|
|
1311
1469
|
};
|
|
1312
1470
|
|
|
1313
|
-
Function.prototype.args = function(...args) {
|
|
1314
|
-
return withValidation(this, args);
|
|
1315
|
-
};
|
|
1316
1471
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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;
|
|
1323
1484
|
}
|
|
1324
|
-
|
|
1485
|
+
if(Validator.isProxy(targetItem)) {
|
|
1486
|
+
Observable.update(targetItem, newValue);
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
$target[key] = newValue;
|
|
1490
|
+
}
|
|
1325
1491
|
};
|
|
1326
1492
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1493
|
+
Observable.object = Observable.init;
|
|
1494
|
+
Observable.json = Observable.init;
|
|
1329
1495
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1496
|
+
/**
|
|
1497
|
+
*
|
|
1498
|
+
* @param {Function} callback
|
|
1499
|
+
* @param {Array|Function} dependencies
|
|
1500
|
+
* @returns {ObservableItem}
|
|
1501
|
+
*/
|
|
1502
|
+
Observable.computed = function(callback, dependencies = []) {
|
|
1503
|
+
const initialValue = callback();
|
|
1504
|
+
const observable = new ObservableItem(initialValue);
|
|
1505
|
+
const updatedValue = () => observable.set(callback());
|
|
1340
1506
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
}
|
|
1345
|
-
return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
|
|
1346
|
-
if(!Validator.containsObservableReference(value)) {
|
|
1347
|
-
return value;
|
|
1507
|
+
if(Validator.isFunction(dependencies)) {
|
|
1508
|
+
if(!Validator.isObservable(dependencies.$observer)) {
|
|
1509
|
+
throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
|
|
1348
1510
|
}
|
|
1349
|
-
|
|
1350
|
-
return
|
|
1351
|
-
}
|
|
1511
|
+
dependencies.$observer.subscribe(updatedValue);
|
|
1512
|
+
return observable;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
dependencies.forEach(dependency => dependency.subscribe(updatedValue));
|
|
1516
|
+
|
|
1517
|
+
return observable;
|
|
1352
1518
|
};
|
|
1353
1519
|
|
|
1354
1520
|
const Store = (function() {
|
|
@@ -1424,45 +1590,6 @@ var NativeDocument = (function (exports) {
|
|
|
1424
1590
|
};
|
|
1425
1591
|
}());
|
|
1426
1592
|
|
|
1427
|
-
/**
|
|
1428
|
-
*
|
|
1429
|
-
* @param {*} item
|
|
1430
|
-
* @param {string|null} defaultKey
|
|
1431
|
-
* @param {?Function} key
|
|
1432
|
-
* @returns {*}
|
|
1433
|
-
*/
|
|
1434
|
-
const getKey = (item, defaultKey, key) => {
|
|
1435
|
-
if(Validator.isFunction(key)) return key(item, defaultKey);
|
|
1436
|
-
if(Validator.isObservable(item)) {
|
|
1437
|
-
const val = item.val();
|
|
1438
|
-
return (val && key) ? val[key] : defaultKey;
|
|
1439
|
-
}
|
|
1440
|
-
return item[key] ?? defaultKey;
|
|
1441
|
-
};
|
|
1442
|
-
|
|
1443
|
-
/**
|
|
1444
|
-
*
|
|
1445
|
-
* @param {Map} cache
|
|
1446
|
-
* @param {Set} keyIds
|
|
1447
|
-
*/
|
|
1448
|
-
const cleanBlockByCache = (cache, keyIds) => {
|
|
1449
|
-
const toRemove = [];
|
|
1450
|
-
for(const [key, cacheItem] of cache.entries()) {
|
|
1451
|
-
if(keyIds.has(key)) {
|
|
1452
|
-
continue;
|
|
1453
|
-
}
|
|
1454
|
-
toRemove.push({ key, cacheItem });
|
|
1455
|
-
}
|
|
1456
|
-
if(toRemove.length === 0) {
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1459
|
-
toRemove.forEach(({ key, cacheItem }) => {
|
|
1460
|
-
cacheItem.child.remove();
|
|
1461
|
-
cacheItem.indexObserver.cleanup();
|
|
1462
|
-
cache.delete(key);
|
|
1463
|
-
});
|
|
1464
|
-
};
|
|
1465
|
-
|
|
1466
1593
|
/**
|
|
1467
1594
|
*
|
|
1468
1595
|
* @param {Array|Object|ObservableItem} data
|
|
@@ -1473,120 +1600,408 @@ var NativeDocument = (function (exports) {
|
|
|
1473
1600
|
function ForEach(data, callback, key) {
|
|
1474
1601
|
const element = new Anchor('ForEach');
|
|
1475
1602
|
const blockEnd = element.endElement();
|
|
1476
|
-
|
|
1603
|
+
element.startElement();
|
|
1477
1604
|
|
|
1478
1605
|
let cache = new Map();
|
|
1606
|
+
let lastKeyOrder = null;
|
|
1479
1607
|
const keyIds = new Set();
|
|
1480
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
|
+
};
|
|
1630
|
+
|
|
1481
1631
|
const handleContentItem = (item, indexKey) => {
|
|
1482
1632
|
const keyId = getKey(item, indexKey, key);
|
|
1483
1633
|
|
|
1484
1634
|
if(cache.has(keyId)) {
|
|
1485
1635
|
const cacheItem = cache.get(keyId);
|
|
1486
|
-
cacheItem.indexObserver
|
|
1636
|
+
cacheItem.indexObserver?.set(indexKey);
|
|
1487
1637
|
cacheItem.isNew = false;
|
|
1638
|
+
if(cacheItem.child?.deref()) {
|
|
1639
|
+
return keyId;
|
|
1640
|
+
}
|
|
1641
|
+
cache.delete(keyId);
|
|
1488
1642
|
}
|
|
1489
|
-
else {
|
|
1490
1643
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
}
|
|
1497
|
-
cache.set(keyId, { isNew: true, child, indexObserver});
|
|
1498
|
-
} catch (e) {
|
|
1499
|
-
DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
|
|
1500
|
-
throw e;
|
|
1644
|
+
try {
|
|
1645
|
+
const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
|
|
1646
|
+
let child = callback(item, indexObserver);
|
|
1647
|
+
if(Validator.isStringOrObservable(child)) {
|
|
1648
|
+
child = createTextNode(child);
|
|
1501
1649
|
}
|
|
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;
|
|
1502
1654
|
}
|
|
1503
1655
|
return keyId;
|
|
1504
1656
|
};
|
|
1505
1657
|
|
|
1506
|
-
const batchDOMUpdates = () => {
|
|
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
|
+
};
|
|
1690
|
+
|
|
1691
|
+
const buildContent = () => {
|
|
1507
1692
|
const parent = blockEnd.parentNode;
|
|
1508
1693
|
if(!parent) {
|
|
1509
1694
|
return;
|
|
1510
1695
|
}
|
|
1511
1696
|
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
if(fragment) {
|
|
1519
|
-
elementsToInsert.push({ child: fragment, before: beforeTarget });
|
|
1520
|
-
fragment = null;
|
|
1697
|
+
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
1698
|
+
keyIds.clear();
|
|
1699
|
+
if(Array.isArray(items)) {
|
|
1700
|
+
for(let i = 0, length = items.length; i < length; i++) {
|
|
1701
|
+
const keyId= handleContentItem(items[i], i);
|
|
1702
|
+
keyIds.add(keyId);
|
|
1521
1703
|
}
|
|
1522
|
-
}
|
|
1704
|
+
} else {
|
|
1705
|
+
for(const indexKey in items) {
|
|
1706
|
+
const keyId = handleContentItem(items[indexKey], indexKey);
|
|
1707
|
+
keyIds.add(keyId);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1523
1710
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
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));
|
|
1528
1764
|
if(!cacheItem) {
|
|
1529
1765
|
continue;
|
|
1530
1766
|
}
|
|
1767
|
+
cacheItem.indexObserver?.deref()?.set(index);
|
|
1768
|
+
index++;
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1531
1771
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
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;
|
|
1536
1810
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
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);
|
|
1542
1819
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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);
|
|
1549
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
|
+
}
|
|
1550
1859
|
|
|
1551
|
-
|
|
1860
|
+
if(fragment) {
|
|
1861
|
+
fragment.appendChild(child);
|
|
1862
|
+
return;
|
|
1552
1863
|
}
|
|
1553
|
-
|
|
1864
|
+
child.remove();
|
|
1865
|
+
};
|
|
1554
1866
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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);
|
|
1899
|
+
if(child) {
|
|
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;
|
|
1558
1911
|
} else {
|
|
1559
|
-
|
|
1912
|
+
delay = (items.length >= 1000) ? 10 : 0;
|
|
1560
1913
|
}
|
|
1561
|
-
|
|
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
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
} else {
|
|
1938
|
+
elementBeforeFirst = blockEnd;
|
|
1939
|
+
}
|
|
1940
|
+
garbageFragment.replaceChildren();
|
|
1562
1941
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
saveFragment = null;
|
|
1942
|
+
if(values && values.length && elementBeforeFirst) {
|
|
1943
|
+
element.insertBefore(Actions.toFragment(values), elementBeforeFirst.nextSibling);
|
|
1944
|
+
}
|
|
1567
1945
|
|
|
1568
|
-
|
|
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;
|
|
1569
1964
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
items.forEach((item, index) => keyIds.add(handleContentItem(item, index)));
|
|
1575
|
-
} else {
|
|
1576
|
-
for(const indexKey in items) {
|
|
1577
|
-
keyIds.add(handleContentItem(items[indexKey], indexKey));
|
|
1965
|
+
let childA = nodeCacheByElement.get(elements[0]);
|
|
1966
|
+
let childB = nodeCacheByElement.get(elements[1]);
|
|
1967
|
+
if(!childA || !childB) {
|
|
1968
|
+
return;
|
|
1578
1969
|
}
|
|
1970
|
+
|
|
1971
|
+
const childBNext = childB.nextSibling;
|
|
1972
|
+
parent.insertBefore(childB, childA);
|
|
1973
|
+
parent.insertBefore(childA, childBNext);
|
|
1974
|
+
childA = null;
|
|
1975
|
+
childB = null;
|
|
1579
1976
|
}
|
|
1977
|
+
};
|
|
1580
1978
|
|
|
1581
|
-
|
|
1979
|
+
const buildContent = (items, _, operations) => {
|
|
1980
|
+
if(operations.action === 'clear' || !items.length) {
|
|
1981
|
+
if(lastNumberOfItems === 0) {
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
clear();
|
|
1985
|
+
}
|
|
1582
1986
|
|
|
1583
|
-
|
|
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);
|
|
1584
1998
|
};
|
|
1585
1999
|
|
|
1586
|
-
buildContent();
|
|
2000
|
+
buildContent(data.val(), null, {action: null});
|
|
1587
2001
|
if(Validator.isObservable(data)) {
|
|
1588
2002
|
data.subscribe(buildContent);
|
|
1589
2003
|
}
|
|
2004
|
+
|
|
1590
2005
|
return element;
|
|
1591
2006
|
}
|
|
1592
2007
|
|
|
@@ -1921,6 +2336,10 @@ var NativeDocument = (function (exports) {
|
|
|
1921
2336
|
const UnorderedList = HtmlElementWrapper('ul');
|
|
1922
2337
|
const ListItem = HtmlElementWrapper('li');
|
|
1923
2338
|
|
|
2339
|
+
const Li = ListItem;
|
|
2340
|
+
const Ol = OrderedList;
|
|
2341
|
+
const Ul = UnorderedList;
|
|
2342
|
+
|
|
1924
2343
|
const Audio = HtmlElementWrapper('audio');
|
|
1925
2344
|
const Video = HtmlElementWrapper('video');
|
|
1926
2345
|
const Source = HtmlElementWrapper('source');
|
|
@@ -1989,6 +2408,7 @@ var NativeDocument = (function (exports) {
|
|
|
1989
2408
|
FileInput: FileInput,
|
|
1990
2409
|
Footer: Footer,
|
|
1991
2410
|
ForEach: ForEach,
|
|
2411
|
+
ForEachArray: ForEachArray,
|
|
1992
2412
|
Form: Form,
|
|
1993
2413
|
Fragment: Fragment,
|
|
1994
2414
|
H1: H1,
|
|
@@ -2009,6 +2429,7 @@ var NativeDocument = (function (exports) {
|
|
|
2009
2429
|
Label: Label,
|
|
2010
2430
|
LazyImg: LazyImg,
|
|
2011
2431
|
Legend: Legend,
|
|
2432
|
+
Li: Li,
|
|
2012
2433
|
Link: Link$1,
|
|
2013
2434
|
ListItem: ListItem,
|
|
2014
2435
|
Main: Main,
|
|
@@ -2020,6 +2441,7 @@ var NativeDocument = (function (exports) {
|
|
|
2020
2441
|
NativeDocumentFragment: Anchor,
|
|
2021
2442
|
Nav: Nav,
|
|
2022
2443
|
NumberInput: NumberInput,
|
|
2444
|
+
Ol: Ol,
|
|
2023
2445
|
Option: Option,
|
|
2024
2446
|
OrderedList: OrderedList,
|
|
2025
2447
|
Output: Output,
|
|
@@ -2065,6 +2487,7 @@ var NativeDocument = (function (exports) {
|
|
|
2065
2487
|
TimeInput: TimeInput,
|
|
2066
2488
|
Tr: Tr,
|
|
2067
2489
|
Track: Track,
|
|
2490
|
+
Ul: Ul,
|
|
2068
2491
|
UnorderedList: UnorderedList,
|
|
2069
2492
|
UrlInput: UrlInput,
|
|
2070
2493
|
Var: Var,
|