native-document 1.0.14 → 1.0.16
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 -842
- 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/form.js +1 -1
- package/src/elements/index.js +1 -0
- package/src/elements/list.js +4 -0
- package/src/router/link.js +2 -2
- 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 +106 -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,302 @@ 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
|
+
return function(target, name) {
|
|
1047
|
+
target[name] = element;
|
|
1048
|
+
return element;
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
if(property === 'unmountChildren') {
|
|
1052
|
+
return () => {
|
|
1053
|
+
for(let i = 0, length = element.children.length; i < length; i++) {
|
|
1054
|
+
let elementchildren = element.children[i];
|
|
1055
|
+
if(!elementchildren.$ndProx) {
|
|
1056
|
+
elementchildren.nd?.remove();
|
|
1057
|
+
}
|
|
1058
|
+
elementchildren = null;
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
if(property === 'remove') {
|
|
1063
|
+
return function() {
|
|
1064
|
+
element.nd.unmountChildren();
|
|
1065
|
+
lifecycle = null;
|
|
1066
|
+
element.$ndProx = null;
|
|
1067
|
+
delete element.nd?.on?.prevent;
|
|
1068
|
+
delete element.nd?.on;
|
|
1069
|
+
delete element.nd;
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
if(property === 'hasLifecycle') {
|
|
1073
|
+
return lifecycle !== null;
|
|
1074
|
+
}
|
|
1075
|
+
if(property === 'lifecycle') {
|
|
1076
|
+
if(lifecycle) {
|
|
1077
|
+
return lifecycle;
|
|
1078
|
+
}
|
|
1079
|
+
let $observer = null;
|
|
1080
|
+
lifecycle = function(states) {
|
|
1081
|
+
$observer = $observer || DocumentObserver.watch(element);
|
|
1082
|
+
|
|
1083
|
+
states.mounted && $observer.mounted(states.mounted);
|
|
1084
|
+
states.unmounted && $observer.unmounted(states.unmounted);
|
|
1085
|
+
return element;
|
|
1086
|
+
};
|
|
1087
|
+
return lifecycle;
|
|
1088
|
+
}
|
|
1089
|
+
if(property === 'mounted' || property === 'unmounted') {
|
|
1090
|
+
return function(callback) {
|
|
1091
|
+
element.nd.lifecycle({ [property]: callback});
|
|
1092
|
+
return element;
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
set(target, p, newValue, receiver) {
|
|
1079
1097
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
return
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
$observer = $observer || DocumentObserver.watch(element);
|
|
1087
|
-
$observer.unmounted(callback);
|
|
1088
|
-
return element;
|
|
1089
|
-
};
|
|
1090
|
-
};
|
|
1098
|
+
},
|
|
1099
|
+
configurable: true
|
|
1100
|
+
});
|
|
1101
|
+
return this.$ndProx;
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1091
1104
|
|
|
1092
1105
|
/**
|
|
1093
1106
|
*
|
|
@@ -1096,84 +1109,10 @@ var NativeDocument = (function (exports) {
|
|
|
1096
1109
|
*/
|
|
1097
1110
|
const createTextNode = function(value) {
|
|
1098
1111
|
return (Validator.isObservable(value))
|
|
1099
|
-
? createObservableNode(null, value)
|
|
1100
|
-
: createStaticTextNode(null, value);
|
|
1112
|
+
? ElementCreator.createObservableNode(null, value)
|
|
1113
|
+
: ElementCreator.createStaticTextNode(null, value);
|
|
1101
1114
|
};
|
|
1102
1115
|
|
|
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
1116
|
|
|
1178
1117
|
/**
|
|
1179
1118
|
*
|
|
@@ -1182,7 +1121,7 @@ var NativeDocument = (function (exports) {
|
|
|
1182
1121
|
* @returns {Function}
|
|
1183
1122
|
*/
|
|
1184
1123
|
function HtmlElementWrapper(name, customWrapper) {
|
|
1185
|
-
const $tagName = name.toLowerCase()
|
|
1124
|
+
const $tagName = name.toLowerCase();
|
|
1186
1125
|
|
|
1187
1126
|
const builder = function(attributes, children = null) {
|
|
1188
1127
|
try {
|
|
@@ -1192,11 +1131,12 @@ var NativeDocument = (function (exports) {
|
|
|
1192
1131
|
attributes = tempChildren;
|
|
1193
1132
|
}
|
|
1194
1133
|
const element = ElementCreator.createElement($tagName);
|
|
1134
|
+
const finalElement = (typeof customWrapper === 'function') ? customWrapper(element) : element;
|
|
1195
1135
|
|
|
1196
|
-
ElementCreator.processAttributes(
|
|
1197
|
-
ElementCreator.processChildren(children,
|
|
1136
|
+
ElementCreator.processAttributes(finalElement, attributes);
|
|
1137
|
+
ElementCreator.processChildren(children, finalElement);
|
|
1198
1138
|
|
|
1199
|
-
return ElementCreator.setup(
|
|
1139
|
+
return ElementCreator.setup(finalElement, attributes, customWrapper);
|
|
1200
1140
|
} catch (error) {
|
|
1201
1141
|
DebugManager.error('ElementCreation', `Error creating ${$tagName}`, error);
|
|
1202
1142
|
}
|
|
@@ -1304,51 +1244,274 @@ var NativeDocument = (function (exports) {
|
|
|
1304
1244
|
if(!Validator.isArray(argSchema)) {
|
|
1305
1245
|
throw new NativeDocumentError('withValidation : argSchema must be an array');
|
|
1306
1246
|
}
|
|
1307
|
-
return function(...args) {
|
|
1308
|
-
validateArgs(args, argSchema, fn.name || fnName);
|
|
1309
|
-
return fn.apply(this, args);
|
|
1310
|
-
};
|
|
1247
|
+
return function(...args) {
|
|
1248
|
+
validateArgs(args, argSchema, fn.name || fnName);
|
|
1249
|
+
return fn.apply(this, args);
|
|
1250
|
+
};
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
Function.prototype.args = function(...args) {
|
|
1254
|
+
return withValidation(this, args);
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
Function.prototype.errorBoundary = function(callback) {
|
|
1258
|
+
return (...args) => {
|
|
1259
|
+
try {
|
|
1260
|
+
return this.apply(this, args);
|
|
1261
|
+
} catch(e) {
|
|
1262
|
+
return callback(e);
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
String.prototype.use = function(args) {
|
|
1268
|
+
const value = this;
|
|
1269
|
+
|
|
1270
|
+
return Observable.computed(() => {
|
|
1271
|
+
return value.replace(/\$\{(.*?)}/g, (match, key) => {
|
|
1272
|
+
const data = args[key];
|
|
1273
|
+
if(Validator.isObservable(data)) {
|
|
1274
|
+
return data.val();
|
|
1275
|
+
}
|
|
1276
|
+
return data;
|
|
1277
|
+
});
|
|
1278
|
+
}, Object.values(args));
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
String.prototype.resolveObservableTemplate = function() {
|
|
1282
|
+
if(!Validator.containsObservableReference(this)) {
|
|
1283
|
+
return this;
|
|
1284
|
+
}
|
|
1285
|
+
return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
|
|
1286
|
+
if(!Validator.containsObservableReference(value)) {
|
|
1287
|
+
return value;
|
|
1288
|
+
}
|
|
1289
|
+
const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
|
|
1290
|
+
return Observable.getById(id);
|
|
1291
|
+
});
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
*
|
|
1298
|
+
* @param {Array} target
|
|
1299
|
+
* @returns {ObservableItem}
|
|
1300
|
+
*/
|
|
1301
|
+
Observable.array = function(target) {
|
|
1302
|
+
if(!Array.isArray(target)) {
|
|
1303
|
+
throw new NativeDocumentError('Observable.array : target must be an array');
|
|
1304
|
+
}
|
|
1305
|
+
const observer = Observable(target);
|
|
1306
|
+
|
|
1307
|
+
methods.forEach((method) => {
|
|
1308
|
+
observer[method] = function(...values) {
|
|
1309
|
+
const result = observer.val()[method](...values);
|
|
1310
|
+
observer.trigger({ action: method, args: values, result });
|
|
1311
|
+
return result;
|
|
1312
|
+
};
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
observer.clear = function() {
|
|
1316
|
+
observer.$value.length = 0;
|
|
1317
|
+
observer.trigger({ action: 'clear' });
|
|
1318
|
+
return true;
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1321
|
+
observer.remove = function(index) {
|
|
1322
|
+
const deleted = observer.$value.splice(index, 1);
|
|
1323
|
+
if(deleted.length === 0) {
|
|
1324
|
+
return [];
|
|
1325
|
+
}
|
|
1326
|
+
observer.trigger({ action: 'remove', args: [index], result: deleted[0] });
|
|
1327
|
+
return deleted;
|
|
1328
|
+
};
|
|
1329
|
+
|
|
1330
|
+
observer.swap = function(indexA, indexB) {
|
|
1331
|
+
const value = observer.$value;
|
|
1332
|
+
const length = value.length;
|
|
1333
|
+
if(length < indexA || length < indexB) {
|
|
1334
|
+
return false;
|
|
1335
|
+
}
|
|
1336
|
+
if(indexB < indexA) {
|
|
1337
|
+
const temp = indexA;
|
|
1338
|
+
indexA = indexB;
|
|
1339
|
+
indexB = temp;
|
|
1340
|
+
}
|
|
1341
|
+
const elementA = value[indexA];
|
|
1342
|
+
const elementB = value[indexB];
|
|
1343
|
+
|
|
1344
|
+
value[indexA] = elementB;
|
|
1345
|
+
value[indexB] = elementA;
|
|
1346
|
+
observer.trigger({ action: 'swap', args: [indexA, indexB], result: [elementA, elementB] });
|
|
1347
|
+
return true;
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1350
|
+
observer.length = function() {
|
|
1351
|
+
return observer.$value.length;
|
|
1352
|
+
};
|
|
1353
|
+
|
|
1354
|
+
const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex', 'concat'];
|
|
1355
|
+
overrideMethods.forEach((method) => {
|
|
1356
|
+
observer[method] = function(...args) {
|
|
1357
|
+
return observer.val()[method](...args);
|
|
1358
|
+
};
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
return observer;
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
*
|
|
1366
|
+
* @param {Function} callback
|
|
1367
|
+
* @returns {Function}
|
|
1368
|
+
*/
|
|
1369
|
+
Observable.batch = function(callback) {
|
|
1370
|
+
const $observer = Observable(0);
|
|
1371
|
+
const batch = function() {
|
|
1372
|
+
if(Validator.isAsyncFunction(callback)) {
|
|
1373
|
+
return (callback(...arguments)).then(() => {
|
|
1374
|
+
$observer.trigger();
|
|
1375
|
+
}).catch(error => { throw error; });
|
|
1376
|
+
}
|
|
1377
|
+
callback(...arguments);
|
|
1378
|
+
$observer.trigger();
|
|
1379
|
+
};
|
|
1380
|
+
batch.$observer = $observer;
|
|
1381
|
+
return batch;
|
|
1382
|
+
};
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
*
|
|
1386
|
+
* @param {Object} value
|
|
1387
|
+
* @returns {Proxy}
|
|
1388
|
+
*/
|
|
1389
|
+
Observable.init = function(value) {
|
|
1390
|
+
const data = {};
|
|
1391
|
+
for(const key in value) {
|
|
1392
|
+
const itemValue = value[key];
|
|
1393
|
+
if(Validator.isJson(itemValue)) {
|
|
1394
|
+
data[key] = Observable.init(itemValue);
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
else if(Validator.isArray(itemValue)) {
|
|
1398
|
+
data[key] = Observable.array(itemValue);
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
data[key] = Observable(itemValue);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
const $val = function() {
|
|
1405
|
+
const result = {};
|
|
1406
|
+
for(const key in data) {
|
|
1407
|
+
const dataItem = data[key];
|
|
1408
|
+
if(Validator.isObservable(dataItem)) {
|
|
1409
|
+
result[key] = dataItem.val();
|
|
1410
|
+
} else if(Validator.isProxy(dataItem)) {
|
|
1411
|
+
result[key] = dataItem.$value;
|
|
1412
|
+
} else {
|
|
1413
|
+
result[key] = dataItem;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return result;
|
|
1417
|
+
};
|
|
1418
|
+
const $clone = function() {
|
|
1419
|
+
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
return new Proxy(data, {
|
|
1423
|
+
get(target, property) {
|
|
1424
|
+
if(property === '__isProxy__') {
|
|
1425
|
+
return true;
|
|
1426
|
+
}
|
|
1427
|
+
if(property === '$value') {
|
|
1428
|
+
return $val();
|
|
1429
|
+
}
|
|
1430
|
+
if(property === '$clone') {
|
|
1431
|
+
return $clone;
|
|
1432
|
+
}
|
|
1433
|
+
if(target[property] !== undefined) {
|
|
1434
|
+
return target[property];
|
|
1435
|
+
}
|
|
1436
|
+
return undefined;
|
|
1437
|
+
},
|
|
1438
|
+
set(target, prop, newValue) {
|
|
1439
|
+
if(target[prop] !== undefined) {
|
|
1440
|
+
target[prop].set(newValue);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
})
|
|
1444
|
+
};
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* Get the value of an observable or an object of observables.
|
|
1448
|
+
* @param {ObservableItem|Object<ObservableItem>} data
|
|
1449
|
+
* @returns {{}|*|null}
|
|
1450
|
+
*/
|
|
1451
|
+
Observable.value = function(data) {
|
|
1452
|
+
if(Validator.isObservable(data)) {
|
|
1453
|
+
return data.val();
|
|
1454
|
+
}
|
|
1455
|
+
if(Validator.isProxy(data)) {
|
|
1456
|
+
return data.$value;
|
|
1457
|
+
}
|
|
1458
|
+
if(Validator.isArray(data)) {
|
|
1459
|
+
const result = [];
|
|
1460
|
+
data.forEach(item => {
|
|
1461
|
+
result.push(Observable.value(item));
|
|
1462
|
+
});
|
|
1463
|
+
return result;
|
|
1464
|
+
}
|
|
1465
|
+
return data;
|
|
1311
1466
|
};
|
|
1312
1467
|
|
|
1313
|
-
Function.prototype.args = function(...args) {
|
|
1314
|
-
return withValidation(this, args);
|
|
1315
|
-
};
|
|
1316
1468
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1469
|
+
Observable.update = function($target, data) {
|
|
1470
|
+
for(const key in data) {
|
|
1471
|
+
const targetItem = $target[key];
|
|
1472
|
+
const newValue = data[key];
|
|
1473
|
+
|
|
1474
|
+
if(Validator.isObservable(targetItem)) {
|
|
1475
|
+
if(Validator.isArray(newValue)) {
|
|
1476
|
+
Observable.update(targetItem, newValue);
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
targetItem.set(newValue);
|
|
1480
|
+
continue;
|
|
1323
1481
|
}
|
|
1324
|
-
|
|
1482
|
+
if(Validator.isProxy(targetItem)) {
|
|
1483
|
+
Observable.update(targetItem, newValue);
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
$target[key] = newValue;
|
|
1487
|
+
}
|
|
1325
1488
|
};
|
|
1326
1489
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1490
|
+
Observable.object = Observable.init;
|
|
1491
|
+
Observable.json = Observable.init;
|
|
1329
1492
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1493
|
+
/**
|
|
1494
|
+
*
|
|
1495
|
+
* @param {Function} callback
|
|
1496
|
+
* @param {Array|Function} dependencies
|
|
1497
|
+
* @returns {ObservableItem}
|
|
1498
|
+
*/
|
|
1499
|
+
Observable.computed = function(callback, dependencies = []) {
|
|
1500
|
+
const initialValue = callback();
|
|
1501
|
+
const observable = new ObservableItem(initialValue);
|
|
1502
|
+
const updatedValue = () => observable.set(callback());
|
|
1340
1503
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
}
|
|
1345
|
-
return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
|
|
1346
|
-
if(!Validator.containsObservableReference(value)) {
|
|
1347
|
-
return value;
|
|
1504
|
+
if(Validator.isFunction(dependencies)) {
|
|
1505
|
+
if(!Validator.isObservable(dependencies.$observer)) {
|
|
1506
|
+
throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
|
|
1348
1507
|
}
|
|
1349
|
-
|
|
1350
|
-
return
|
|
1351
|
-
}
|
|
1508
|
+
dependencies.$observer.subscribe(updatedValue);
|
|
1509
|
+
return observable;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
dependencies.forEach(dependency => dependency.subscribe(updatedValue));
|
|
1513
|
+
|
|
1514
|
+
return observable;
|
|
1352
1515
|
};
|
|
1353
1516
|
|
|
1354
1517
|
const Store = (function() {
|
|
@@ -1424,45 +1587,6 @@ var NativeDocument = (function (exports) {
|
|
|
1424
1587
|
};
|
|
1425
1588
|
}());
|
|
1426
1589
|
|
|
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
1590
|
/**
|
|
1467
1591
|
*
|
|
1468
1592
|
* @param {Array|Object|ObservableItem} data
|
|
@@ -1473,120 +1597,408 @@ var NativeDocument = (function (exports) {
|
|
|
1473
1597
|
function ForEach(data, callback, key) {
|
|
1474
1598
|
const element = new Anchor('ForEach');
|
|
1475
1599
|
const blockEnd = element.endElement();
|
|
1476
|
-
|
|
1600
|
+
element.startElement();
|
|
1477
1601
|
|
|
1478
1602
|
let cache = new Map();
|
|
1603
|
+
let lastKeyOrder = null;
|
|
1479
1604
|
const keyIds = new Set();
|
|
1480
1605
|
|
|
1606
|
+
const clear = () => {
|
|
1607
|
+
element.removeChildren();
|
|
1608
|
+
cleanCache();
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1611
|
+
const cleanCache = (parent) => {
|
|
1612
|
+
for(const [keyId, cacheItem] of cache.entries()) {
|
|
1613
|
+
if(keyIds.has(keyId)) {
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
const child = cacheItem.child?.deref();
|
|
1617
|
+
if(parent && child) {
|
|
1618
|
+
parent.removeChild(child);
|
|
1619
|
+
}
|
|
1620
|
+
cacheItem.indexObserver?.cleanup();
|
|
1621
|
+
cacheItem.child = null;
|
|
1622
|
+
cacheItem.indexObserver = null;
|
|
1623
|
+
cache.delete(cacheItem.keyId);
|
|
1624
|
+
lastKeyOrder && lastKeyOrder.delete(cacheItem.keyId);
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
|
|
1481
1628
|
const handleContentItem = (item, indexKey) => {
|
|
1482
1629
|
const keyId = getKey(item, indexKey, key);
|
|
1483
1630
|
|
|
1484
1631
|
if(cache.has(keyId)) {
|
|
1485
1632
|
const cacheItem = cache.get(keyId);
|
|
1486
|
-
cacheItem.indexObserver
|
|
1633
|
+
cacheItem.indexObserver?.set(indexKey);
|
|
1487
1634
|
cacheItem.isNew = false;
|
|
1635
|
+
if(cacheItem.child?.deref()) {
|
|
1636
|
+
return keyId;
|
|
1637
|
+
}
|
|
1638
|
+
cache.delete(keyId);
|
|
1488
1639
|
}
|
|
1489
|
-
else {
|
|
1490
1640
|
|
|
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;
|
|
1641
|
+
try {
|
|
1642
|
+
const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
|
|
1643
|
+
let child = callback(item, indexObserver);
|
|
1644
|
+
if(Validator.isStringOrObservable(child)) {
|
|
1645
|
+
child = createTextNode(child);
|
|
1501
1646
|
}
|
|
1647
|
+
cache.set(keyId, { keyId, isNew: true, child: new WeakRef(child), indexObserver});
|
|
1648
|
+
} catch (e) {
|
|
1649
|
+
DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
|
|
1650
|
+
throw e;
|
|
1502
1651
|
}
|
|
1503
1652
|
return keyId;
|
|
1504
1653
|
};
|
|
1505
1654
|
|
|
1506
|
-
const batchDOMUpdates = () => {
|
|
1655
|
+
const batchDOMUpdates = (parent) => {
|
|
1656
|
+
const fragment = document.createDocumentFragment();
|
|
1657
|
+
for(const itemKey of keyIds) {
|
|
1658
|
+
const cacheItem = cache.get(itemKey);
|
|
1659
|
+
if(!cacheItem) {
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
const child = cacheItem.child?.deref();
|
|
1663
|
+
child && fragment.appendChild(child);
|
|
1664
|
+
}
|
|
1665
|
+
parent.insertBefore(fragment, blockEnd);
|
|
1666
|
+
};
|
|
1667
|
+
|
|
1668
|
+
const diffingDOMUpdates = (parent) => {
|
|
1669
|
+
let fragment = document.createDocumentFragment();
|
|
1670
|
+
const newKeys = Array.from(keyIds);
|
|
1671
|
+
Array.from(lastKeyOrder);
|
|
1672
|
+
|
|
1673
|
+
for(const index in newKeys) {
|
|
1674
|
+
const itemKey = newKeys[index];
|
|
1675
|
+
const cacheItem = cache.get(itemKey);
|
|
1676
|
+
if(!cacheItem) {
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
const child = cacheItem.child.deref();
|
|
1680
|
+
if(!child) {
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
fragment.appendChild(child);
|
|
1684
|
+
}
|
|
1685
|
+
element.replaceContent(fragment);
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1688
|
+
const buildContent = () => {
|
|
1507
1689
|
const parent = blockEnd.parentNode;
|
|
1508
1690
|
if(!parent) {
|
|
1509
1691
|
return;
|
|
1510
1692
|
}
|
|
1511
1693
|
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
if(fragment) {
|
|
1519
|
-
elementsToInsert.push({ child: fragment, before: beforeTarget });
|
|
1520
|
-
fragment = null;
|
|
1694
|
+
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
1695
|
+
keyIds.clear();
|
|
1696
|
+
if(Array.isArray(items)) {
|
|
1697
|
+
for(let i = 0, length = items.length; i < length; i++) {
|
|
1698
|
+
const keyId= handleContentItem(items[i], i);
|
|
1699
|
+
keyIds.add(keyId);
|
|
1521
1700
|
}
|
|
1522
|
-
}
|
|
1701
|
+
} else {
|
|
1702
|
+
for(const indexKey in items) {
|
|
1703
|
+
const keyId = handleContentItem(items[indexKey], indexKey);
|
|
1704
|
+
keyIds.add(keyId);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1523
1707
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1708
|
+
if(keyIds.size === 0) {
|
|
1709
|
+
clear();
|
|
1710
|
+
lastKeyOrder?.clear();
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
cleanCache(parent);
|
|
1715
|
+
if(!lastKeyOrder || lastKeyOrder.size === 0) {
|
|
1716
|
+
batchDOMUpdates(parent);
|
|
1717
|
+
} else {
|
|
1718
|
+
diffingDOMUpdates();
|
|
1719
|
+
}
|
|
1720
|
+
lastKeyOrder?.clear();
|
|
1721
|
+
lastKeyOrder = new Set([...keyIds]);
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
buildContent();
|
|
1725
|
+
if(Validator.isObservable(data)) {
|
|
1726
|
+
data.subscribe(buildContent);
|
|
1727
|
+
}
|
|
1728
|
+
return element;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function ForEachArray(data, callback, key, configs = {}) {
|
|
1732
|
+
const element = new Anchor('ForEach Array');
|
|
1733
|
+
const blockEnd = element.endElement();
|
|
1734
|
+
const blockStart = element.startElement();
|
|
1735
|
+
|
|
1736
|
+
let cache = new Map();
|
|
1737
|
+
let nodeCacheByElement = new WeakMap();
|
|
1738
|
+
let lastNumberOfItems = 0;
|
|
1739
|
+
|
|
1740
|
+
const keysCache = new WeakMap();
|
|
1741
|
+
|
|
1742
|
+
const clear = () => {
|
|
1743
|
+
element.removeChildren();
|
|
1744
|
+
cleanCache();
|
|
1745
|
+
lastNumberOfItems = 0;
|
|
1746
|
+
};
|
|
1747
|
+
const getItemKey = (item, indexKey) => {
|
|
1748
|
+
if(keysCache.has(item)) {
|
|
1749
|
+
return keysCache.get(item);
|
|
1750
|
+
}
|
|
1751
|
+
return getKey(item, indexKey, key);
|
|
1752
|
+
};
|
|
1753
|
+
|
|
1754
|
+
const updateIndexObservers = (items, startFrom = 0) => {
|
|
1755
|
+
if(callback.length < 2) {
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
let index = startFrom;
|
|
1759
|
+
for(let i = startFrom, length = items?.length; i < length; i++) {
|
|
1760
|
+
const cacheItem = cache.get(getItemKey(items[i], i));
|
|
1528
1761
|
if(!cacheItem) {
|
|
1529
1762
|
continue;
|
|
1530
1763
|
}
|
|
1764
|
+
cacheItem.indexObserver?.deref()?.set(index);
|
|
1765
|
+
index++;
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1531
1768
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1769
|
+
const removeCacheItem = (cacheItem, removeChild = true) => {
|
|
1770
|
+
if(!cacheItem) {
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
const child = cacheItem.child?.deref();
|
|
1774
|
+
cacheItem.indexObserver?.deref()?.cleanup();
|
|
1775
|
+
cacheItem.child = null;
|
|
1776
|
+
cacheItem.indexObserver = null;
|
|
1777
|
+
nodeCacheByElement.delete(cacheItem.item);
|
|
1778
|
+
keysCache.delete(cacheItem.item);
|
|
1779
|
+
cacheItem.item = null;
|
|
1780
|
+
if(removeChild) {
|
|
1781
|
+
child?.remove();
|
|
1782
|
+
cache.delete(cacheItem.keyId);
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1785
|
+
|
|
1786
|
+
const removeCacheItemByKey = (keyId, removeChild = true) => {
|
|
1787
|
+
removeCacheItem(cache.get(keyId), removeChild);
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1790
|
+
const cleanCache = () => {
|
|
1791
|
+
for (const [keyId, cacheItem] of cache.entries()) {
|
|
1792
|
+
removeCacheItem(cacheItem, false);
|
|
1793
|
+
}
|
|
1794
|
+
cache.clear();
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
const buildItem = (item, indexKey) => {
|
|
1798
|
+
const keyId = getItemKey(item, indexKey);
|
|
1799
|
+
|
|
1800
|
+
if(cache.has(keyId)) {
|
|
1801
|
+
const cacheItem = cache.get(keyId);
|
|
1802
|
+
cacheItem.indexObserver?.deref()?.set(indexKey);
|
|
1803
|
+
cacheItem.isNew = false;
|
|
1804
|
+
const child = cacheItem.child?.deref();
|
|
1805
|
+
if(child) {
|
|
1806
|
+
return child;
|
|
1536
1807
|
}
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1808
|
+
cache.delete(keyId);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
try {
|
|
1812
|
+
const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
|
|
1813
|
+
let child = callback(item, indexObserver);
|
|
1814
|
+
if(Validator.isStringOrObservable(child)) {
|
|
1815
|
+
child = createTextNode(child);
|
|
1542
1816
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1817
|
+
cache.set(keyId, {
|
|
1818
|
+
keyId,
|
|
1819
|
+
isNew: true,
|
|
1820
|
+
item,
|
|
1821
|
+
child: new WeakRef(child),
|
|
1822
|
+
indexObserver: (indexObserver ? new WeakRef(indexObserver) : null)
|
|
1823
|
+
});
|
|
1824
|
+
keysCache.set(item, keyId);
|
|
1825
|
+
if(Validator.isObject(item)) {
|
|
1826
|
+
nodeCacheByElement.set(item, child);
|
|
1549
1827
|
}
|
|
1828
|
+
return child;
|
|
1829
|
+
} catch (e) {
|
|
1830
|
+
DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
|
|
1831
|
+
throw e;
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
const getChildByKey = function(keyId, fragment) {
|
|
1835
|
+
const cacheItem = cache.get(keyId);
|
|
1836
|
+
if(!cacheItem) {
|
|
1837
|
+
return null;
|
|
1838
|
+
}
|
|
1839
|
+
const child = cacheItem.child?.deref();
|
|
1840
|
+
if(!child) {
|
|
1841
|
+
removeCacheItem(cacheItem, false);
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
return child;
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1847
|
+
const removeByKey = function(keyId, fragment) {
|
|
1848
|
+
const cacheItem = cache.get(keyId);
|
|
1849
|
+
if(!cacheItem) {
|
|
1850
|
+
return null;
|
|
1851
|
+
}
|
|
1852
|
+
const child = cacheItem.child?.deref();
|
|
1853
|
+
if(!child) {
|
|
1854
|
+
return null;
|
|
1855
|
+
}
|
|
1550
1856
|
|
|
1551
|
-
|
|
1857
|
+
if(fragment) {
|
|
1858
|
+
fragment.appendChild(child);
|
|
1859
|
+
return;
|
|
1552
1860
|
}
|
|
1553
|
-
|
|
1861
|
+
child.remove();
|
|
1862
|
+
};
|
|
1554
1863
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1864
|
+
const Actions = {
|
|
1865
|
+
toFragment(items, startIndexFrom = 0){
|
|
1866
|
+
const fragment = document.createDocumentFragment();
|
|
1867
|
+
for(let i = 0, length = items.length; i < length; i++) {
|
|
1868
|
+
fragment.append(buildItem(items[i], lastNumberOfItems));
|
|
1869
|
+
lastNumberOfItems++;
|
|
1870
|
+
}
|
|
1871
|
+
return fragment;
|
|
1872
|
+
},
|
|
1873
|
+
add(items, delay = 0) {
|
|
1874
|
+
setTimeout(() => {
|
|
1875
|
+
element.appendElement(Actions.toFragment(items));
|
|
1876
|
+
}, delay);
|
|
1877
|
+
},
|
|
1878
|
+
replace(items) {
|
|
1879
|
+
clear();
|
|
1880
|
+
Actions.add(items);
|
|
1881
|
+
},
|
|
1882
|
+
reOrder(items) {
|
|
1883
|
+
let child = null;
|
|
1884
|
+
const fragment = document.createDocumentFragment();
|
|
1885
|
+
for(const item of items) {
|
|
1886
|
+
child = nodeCacheByElement.get(item);
|
|
1887
|
+
if(child) {
|
|
1888
|
+
fragment.appendChild(child);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
child = null;
|
|
1892
|
+
element.appendElement(fragment, blockEnd);
|
|
1893
|
+
},
|
|
1894
|
+
removeOne(element, index) {
|
|
1895
|
+
let child = nodeCacheByElement.get(element);
|
|
1896
|
+
if(child) {
|
|
1897
|
+
child.remove();
|
|
1898
|
+
nodeCacheByElement.delete(element);
|
|
1899
|
+
removeCacheItemByKey(getItemKey(element, index));
|
|
1900
|
+
}
|
|
1901
|
+
child = null;
|
|
1902
|
+
},
|
|
1903
|
+
clear,
|
|
1904
|
+
push(items) {
|
|
1905
|
+
let delay = 0;
|
|
1906
|
+
if(configs.pushDelay) {
|
|
1907
|
+
delay = configs.pushDelay(items) ?? 0;
|
|
1558
1908
|
} else {
|
|
1559
|
-
|
|
1909
|
+
delay = (items.length >= 1000) ? 10 : 0;
|
|
1560
1910
|
}
|
|
1561
|
-
|
|
1911
|
+
Actions.add(items, delay);
|
|
1912
|
+
},
|
|
1913
|
+
unshift(values){
|
|
1914
|
+
element.insertBefore(Actions.toFragment(values), blockStart.nextSibling);
|
|
1915
|
+
},
|
|
1916
|
+
splice(args, deleted) {
|
|
1917
|
+
const [start, deleteCount, ...values] = args;
|
|
1918
|
+
let elementBeforeFirst = null;
|
|
1919
|
+
const garbageFragment = document.createDocumentFragment();
|
|
1920
|
+
|
|
1921
|
+
if(deleted.length > 0) {
|
|
1922
|
+
let firstKey = getItemKey(deleted[0], start);
|
|
1923
|
+
if(deleted.length === 1) {
|
|
1924
|
+
removeByKey(firstKey, garbageFragment);
|
|
1925
|
+
} else if(deleted.length > 1) {
|
|
1926
|
+
const firstChildRemoved = getChildByKey(firstKey);
|
|
1927
|
+
elementBeforeFirst = firstChildRemoved?.previousSibling;
|
|
1928
|
+
|
|
1929
|
+
for(let i = 0; i < deleted.length; i++) {
|
|
1930
|
+
const keyId = getItemKey(deleted[i], start + i);
|
|
1931
|
+
removeByKey(keyId, garbageFragment);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
} else {
|
|
1935
|
+
elementBeforeFirst = blockEnd;
|
|
1936
|
+
}
|
|
1937
|
+
garbageFragment.replaceChildren();
|
|
1562
1938
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
saveFragment = null;
|
|
1939
|
+
if(values && values.length && elementBeforeFirst) {
|
|
1940
|
+
element.insertBefore(Actions.toFragment(values), elementBeforeFirst.nextSibling);
|
|
1941
|
+
}
|
|
1567
1942
|
|
|
1568
|
-
|
|
1943
|
+
},
|
|
1944
|
+
reverse(_, reversed) {
|
|
1945
|
+
Actions.reOrder(reversed);
|
|
1946
|
+
},
|
|
1947
|
+
sort(_, sorted) {
|
|
1948
|
+
Actions.reOrder(sorted);
|
|
1949
|
+
},
|
|
1950
|
+
remove(_, deleted) {
|
|
1951
|
+
Actions.removeOne(deleted);
|
|
1952
|
+
},
|
|
1953
|
+
pop(_, deleted) {
|
|
1954
|
+
Actions.removeOne(deleted);
|
|
1955
|
+
},
|
|
1956
|
+
shift(_, deleted) {
|
|
1957
|
+
Actions.removeOne(deleted);
|
|
1958
|
+
},
|
|
1959
|
+
swap(args, elements) {
|
|
1960
|
+
const parent = blockEnd.parentNode;
|
|
1569
1961
|
|
|
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));
|
|
1962
|
+
let childA = nodeCacheByElement.get(elements[0]);
|
|
1963
|
+
let childB = nodeCacheByElement.get(elements[1]);
|
|
1964
|
+
if(!childA || !childB) {
|
|
1965
|
+
return;
|
|
1578
1966
|
}
|
|
1967
|
+
|
|
1968
|
+
const childBNext = childB.nextSibling;
|
|
1969
|
+
parent.insertBefore(childB, childA);
|
|
1970
|
+
parent.insertBefore(childA, childBNext);
|
|
1971
|
+
childA = null;
|
|
1972
|
+
childB = null;
|
|
1579
1973
|
}
|
|
1974
|
+
};
|
|
1580
1975
|
|
|
1581
|
-
|
|
1976
|
+
const buildContent = (items, _, operations) => {
|
|
1977
|
+
if(operations.action === 'clear' || !items.length) {
|
|
1978
|
+
if(lastNumberOfItems === 0) {
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
clear();
|
|
1982
|
+
}
|
|
1582
1983
|
|
|
1583
|
-
|
|
1984
|
+
if(!operations?.action) {
|
|
1985
|
+
if(lastNumberOfItems === 0) {
|
|
1986
|
+
Actions.add(items);
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
Actions.replace(items);
|
|
1990
|
+
}
|
|
1991
|
+
else if(Actions[operations.action]) {
|
|
1992
|
+
Actions[operations.action](operations.args, operations.result);
|
|
1993
|
+
}
|
|
1994
|
+
updateIndexObservers(items, 0);
|
|
1584
1995
|
};
|
|
1585
1996
|
|
|
1586
|
-
buildContent();
|
|
1997
|
+
buildContent(data.val(), null, {action: null});
|
|
1587
1998
|
if(Validator.isObservable(data)) {
|
|
1588
1999
|
data.subscribe(buildContent);
|
|
1589
2000
|
}
|
|
2001
|
+
|
|
1590
2002
|
return element;
|
|
1591
2003
|
}
|
|
1592
2004
|
|
|
@@ -1795,7 +2207,7 @@ var NativeDocument = (function (exports) {
|
|
|
1795
2207
|
|
|
1796
2208
|
el.submit = function(action) {
|
|
1797
2209
|
if(typeof action === 'function') {
|
|
1798
|
-
el.
|
|
2210
|
+
el.onSubmit((e) => {
|
|
1799
2211
|
e.preventDefault();
|
|
1800
2212
|
action(e);
|
|
1801
2213
|
});
|
|
@@ -1921,6 +2333,10 @@ var NativeDocument = (function (exports) {
|
|
|
1921
2333
|
const UnorderedList = HtmlElementWrapper('ul');
|
|
1922
2334
|
const ListItem = HtmlElementWrapper('li');
|
|
1923
2335
|
|
|
2336
|
+
const Li = ListItem;
|
|
2337
|
+
const Ol = OrderedList;
|
|
2338
|
+
const Ul = UnorderedList;
|
|
2339
|
+
|
|
1924
2340
|
const Audio = HtmlElementWrapper('audio');
|
|
1925
2341
|
const Video = HtmlElementWrapper('video');
|
|
1926
2342
|
const Source = HtmlElementWrapper('source');
|
|
@@ -1989,6 +2405,7 @@ var NativeDocument = (function (exports) {
|
|
|
1989
2405
|
FileInput: FileInput,
|
|
1990
2406
|
Footer: Footer,
|
|
1991
2407
|
ForEach: ForEach,
|
|
2408
|
+
ForEachArray: ForEachArray,
|
|
1992
2409
|
Form: Form,
|
|
1993
2410
|
Fragment: Fragment,
|
|
1994
2411
|
H1: H1,
|
|
@@ -2009,6 +2426,7 @@ var NativeDocument = (function (exports) {
|
|
|
2009
2426
|
Label: Label,
|
|
2010
2427
|
LazyImg: LazyImg,
|
|
2011
2428
|
Legend: Legend,
|
|
2429
|
+
Li: Li,
|
|
2012
2430
|
Link: Link$1,
|
|
2013
2431
|
ListItem: ListItem,
|
|
2014
2432
|
Main: Main,
|
|
@@ -2020,6 +2438,7 @@ var NativeDocument = (function (exports) {
|
|
|
2020
2438
|
NativeDocumentFragment: Anchor,
|
|
2021
2439
|
Nav: Nav,
|
|
2022
2440
|
NumberInput: NumberInput,
|
|
2441
|
+
Ol: Ol,
|
|
2023
2442
|
Option: Option,
|
|
2024
2443
|
OrderedList: OrderedList,
|
|
2025
2444
|
Output: Output,
|
|
@@ -2065,6 +2484,7 @@ var NativeDocument = (function (exports) {
|
|
|
2065
2484
|
TimeInput: TimeInput,
|
|
2066
2485
|
Tr: Tr,
|
|
2067
2486
|
Track: Track,
|
|
2487
|
+
Ul: Ul,
|
|
2068
2488
|
UnorderedList: UnorderedList,
|
|
2069
2489
|
UrlInput: UrlInput,
|
|
2070
2490
|
Var: Var,
|
|
@@ -2732,7 +3152,7 @@ var NativeDocument = (function (exports) {
|
|
|
2732
3152
|
const target = to || href;
|
|
2733
3153
|
if(Validator.isString(target)) {
|
|
2734
3154
|
const router = Router.get();
|
|
2735
|
-
return Link$1({ ...attributes, href: target}, children).nd.
|
|
3155
|
+
return Link$1({ ...attributes, href: target}, children).nd.onPreventClick(() => {
|
|
2736
3156
|
router.push(target);
|
|
2737
3157
|
});
|
|
2738
3158
|
}
|
|
@@ -2743,7 +3163,7 @@ var NativeDocument = (function (exports) {
|
|
|
2743
3163
|
throw new RouterError('Router not found "'+routerName+'" for link "'+target.name+'"');
|
|
2744
3164
|
}
|
|
2745
3165
|
const url = router.generateUrl(target.name, target.params, target.query);
|
|
2746
|
-
return Link$1({ ...attributes, href: url }, children).nd.
|
|
3166
|
+
return Link$1({ ...attributes, href: url }, children).nd.onPreventClick(() => {
|
|
2747
3167
|
router.push(url);
|
|
2748
3168
|
});
|
|
2749
3169
|
}
|