native-document 1.0.95 → 1.0.98
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/{src/devtools/hrm → devtools}/ComponentRegistry.js +2 -2
- package/devtools/index.js +8 -0
- package/{src/devtools/plugin.js → devtools/plugin/dev-tools-plugin.js} +2 -2
- package/{src/devtools/hrm/nd-vite-hot-reload.js → devtools/transformers/nd-vite-devtools.js} +16 -6
- package/devtools/transformers/src/transformComponentForHrm.js +74 -0
- package/devtools/transformers/src/transformJsFile.js +9 -0
- package/devtools/transformers/src/utils.js +79 -0
- package/devtools/widget/Widget.js +48 -0
- package/devtools/widget/widget.css +81 -0
- package/devtools/widget.js +23 -0
- package/dist/native-document.components.min.js +1922 -1277
- package/dist/native-document.dev.js +1985 -1401
- package/dist/native-document.dev.js.map +1 -1
- package/dist/native-document.devtools.min.js +1 -1
- package/dist/native-document.min.js +1 -1
- package/docs/cache.md +1 -1
- package/docs/core-concepts.md +1 -1
- package/docs/native-document-element.md +51 -15
- package/docs/observables.md +310 -306
- package/docs/state-management.md +198 -193
- package/package.json +1 -1
- package/readme.md +1 -1
- package/src/core/data/ObservableChecker.js +2 -0
- package/src/core/data/ObservableItem.js +97 -0
- package/src/core/data/ObservableObject.js +182 -0
- package/src/core/data/Store.js +364 -34
- package/src/core/data/observable-helpers/object.js +2 -166
- package/src/core/utils/formatters.js +91 -0
- package/src/core/utils/localstorage.js +57 -0
- package/src/core/utils/validator.js +0 -2
- package/src/devtools.js +9 -0
- package/src/fetch/NativeFetch.js +5 -2
- package/types/observable.d.ts +71 -15
- package/types/plugins-manager.d.ts +1 -1
- package/types/store.d.ts +33 -6
- package/hrm.js +0 -7
- package/src/devtools/app/App.js +0 -66
- package/src/devtools/app/app.css +0 -0
- package/src/devtools/hrm/transformComponent.js +0 -129
- package/src/devtools/index.js +0 -18
- package/src/devtools/widget/DevToolsWidget.js +0 -26
- /package/{src/devtools/hrm → devtools/transformers/templates}/hrm.hook.template.js +0 -0
- /package/{src/devtools/hrm → devtools/transformers/templates}/hrm.orbservable.hook.template.js +0 -0
|
@@ -37,59 +37,6 @@ var NativeDocument = (function (exports) {
|
|
|
37
37
|
}
|
|
38
38
|
var DebugManager = DebugManager$1;
|
|
39
39
|
|
|
40
|
-
const MemoryManager = (function() {
|
|
41
|
-
|
|
42
|
-
let $nextObserverId = 0;
|
|
43
|
-
const $observables = new Map();
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
/**
|
|
47
|
-
* Register an observable and return an id.
|
|
48
|
-
*
|
|
49
|
-
* @param {ObservableItem} observable
|
|
50
|
-
* @param {Function} getListeners
|
|
51
|
-
* @returns {number}
|
|
52
|
-
*/
|
|
53
|
-
register(observable) {
|
|
54
|
-
const id = ++$nextObserverId;
|
|
55
|
-
$observables.set(id, new WeakRef(observable));
|
|
56
|
-
return id;
|
|
57
|
-
},
|
|
58
|
-
unregister(id) {
|
|
59
|
-
$observables.delete(id);
|
|
60
|
-
},
|
|
61
|
-
getObservableById(id) {
|
|
62
|
-
return $observables.get(id)?.deref();
|
|
63
|
-
},
|
|
64
|
-
cleanup() {
|
|
65
|
-
for (const [_, weakObservableRef] of $observables) {
|
|
66
|
-
const observable = weakObservableRef.deref();
|
|
67
|
-
if (observable) {
|
|
68
|
-
observable.cleanup();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
$observables.clear();
|
|
72
|
-
},
|
|
73
|
-
/**
|
|
74
|
-
* Clean observables that are not referenced anymore.
|
|
75
|
-
* @param {number} threshold
|
|
76
|
-
*/
|
|
77
|
-
cleanObservables(threshold) {
|
|
78
|
-
if($observables.size < threshold) return;
|
|
79
|
-
let cleanedCount = 0;
|
|
80
|
-
for (const [id, weakObservableRef] of $observables) {
|
|
81
|
-
if (!weakObservableRef.deref()) {
|
|
82
|
-
$observables.delete(id);
|
|
83
|
-
cleanedCount++;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (cleanedCount > 0) {
|
|
87
|
-
DebugManager.log('Memory Auto Clean', `🧹 Cleaned ${cleanedCount} orphaned observables`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
}());
|
|
92
|
-
|
|
93
40
|
class NativeDocumentError extends Error {
|
|
94
41
|
constructor(message, context = {}) {
|
|
95
42
|
super(message);
|
|
@@ -191,6 +138,181 @@ var NativeDocument = (function (exports) {
|
|
|
191
138
|
return this.observable.cleanup();
|
|
192
139
|
};
|
|
193
140
|
|
|
141
|
+
const DocumentObserver = {
|
|
142
|
+
mounted: new WeakMap(),
|
|
143
|
+
beforeUnmount: new WeakMap(),
|
|
144
|
+
mountedSupposedSize: 0,
|
|
145
|
+
unmounted: new WeakMap(),
|
|
146
|
+
unmountedSupposedSize: 0,
|
|
147
|
+
observer: null,
|
|
148
|
+
|
|
149
|
+
executeMountedCallback(node) {
|
|
150
|
+
const data = DocumentObserver.mounted.get(node);
|
|
151
|
+
if(!data) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
data.inDom = true;
|
|
155
|
+
if(!data.mounted) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if(Array.isArray(data.mounted)) {
|
|
159
|
+
for(const cb of data.mounted) {
|
|
160
|
+
cb(node);
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
data.mounted(node);
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
executeUnmountedCallback(node) {
|
|
168
|
+
const data = DocumentObserver.unmounted.get(node);
|
|
169
|
+
if(!data) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
data.inDom = false;
|
|
173
|
+
if(!data.unmounted) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let shouldRemove = false;
|
|
178
|
+
if(Array.isArray(data.unmounted)) {
|
|
179
|
+
for(const cb of data.unmounted) {
|
|
180
|
+
if(cb(node) === true) {
|
|
181
|
+
shouldRemove = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
shouldRemove = data.unmounted(node) === true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if(shouldRemove) {
|
|
189
|
+
data.disconnect();
|
|
190
|
+
node.nd?.remove();
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
checkMutation: function(mutationsList) {
|
|
195
|
+
for(const mutation of mutationsList) {
|
|
196
|
+
if(DocumentObserver.mountedSupposedSize > 0) {
|
|
197
|
+
for(const node of mutation.addedNodes) {
|
|
198
|
+
DocumentObserver.executeMountedCallback(node);
|
|
199
|
+
if(!node.querySelectorAll) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const children = node.querySelectorAll('[data--nd-mounted]');
|
|
203
|
+
for(const child of children) {
|
|
204
|
+
DocumentObserver.executeMountedCallback(child);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (DocumentObserver.unmountedSupposedSize > 0) {
|
|
210
|
+
for (const node of mutation.removedNodes) {
|
|
211
|
+
DocumentObserver.executeUnmountedCallback(node);
|
|
212
|
+
if(!node.querySelectorAll) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const children = node.querySelectorAll('[data--nd-unmounted]');
|
|
216
|
+
for(const child of children) {
|
|
217
|
+
DocumentObserver.executeUnmountedCallback(child);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @param {HTMLElement} element
|
|
226
|
+
* @param {boolean} inDom
|
|
227
|
+
* @returns {{ disconnect: Function, mounted: Function, unmounted: Function, off: Function }}
|
|
228
|
+
*/
|
|
229
|
+
watch: function(element, inDom = false) {
|
|
230
|
+
let mountedRegistered = false;
|
|
231
|
+
let unmountedRegistered = false;
|
|
232
|
+
|
|
233
|
+
let data = {
|
|
234
|
+
inDom,
|
|
235
|
+
mounted: null,
|
|
236
|
+
unmounted: null,
|
|
237
|
+
disconnect: () => {
|
|
238
|
+
if (mountedRegistered) {
|
|
239
|
+
DocumentObserver.mounted.delete(element);
|
|
240
|
+
DocumentObserver.mountedSupposedSize--;
|
|
241
|
+
}
|
|
242
|
+
if (unmountedRegistered) {
|
|
243
|
+
DocumentObserver.unmounted.delete(element);
|
|
244
|
+
DocumentObserver.unmountedSupposedSize--;
|
|
245
|
+
}
|
|
246
|
+
data = null;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const addListener = (type, callback) => {
|
|
251
|
+
if (!data[type]) {
|
|
252
|
+
data[type] = callback;
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (!Array.isArray(data[type])) {
|
|
256
|
+
data[type] = [data[type], callback];
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
data[type].push(callback);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const removeListener = (type, callback) => {
|
|
263
|
+
if(!data?.[type]) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if(Array.isArray(data[type])) {
|
|
267
|
+
const index = data[type].indexOf(callback);
|
|
268
|
+
if(index > -1) {
|
|
269
|
+
data[type].splice(index, 1);
|
|
270
|
+
}
|
|
271
|
+
if(data[type].length === 1) {
|
|
272
|
+
data[type] = data[type][0];
|
|
273
|
+
}
|
|
274
|
+
if(data[type].length === 0) {
|
|
275
|
+
data[type] = null;
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
data[type] = null;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
disconnect: () => data?.disconnect(),
|
|
284
|
+
|
|
285
|
+
mounted: (callback) => {
|
|
286
|
+
addListener('mounted', callback);
|
|
287
|
+
DocumentObserver.mounted.set(element, data);
|
|
288
|
+
if (!mountedRegistered) {
|
|
289
|
+
DocumentObserver.mountedSupposedSize++;
|
|
290
|
+
mountedRegistered = true;
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
unmounted: (callback) => {
|
|
295
|
+
addListener('unmounted', callback);
|
|
296
|
+
DocumentObserver.unmounted.set(element, data);
|
|
297
|
+
if (!unmountedRegistered) {
|
|
298
|
+
DocumentObserver.unmountedSupposedSize++;
|
|
299
|
+
unmountedRegistered = true;
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
off: (type, callback) => {
|
|
304
|
+
removeListener(type, callback);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
DocumentObserver.observer = new MutationObserver(DocumentObserver.checkMutation);
|
|
311
|
+
DocumentObserver.observer.observe(document.body, {
|
|
312
|
+
childList: true,
|
|
313
|
+
subtree: true,
|
|
314
|
+
});
|
|
315
|
+
|
|
194
316
|
let PluginsManager$1 = null;
|
|
195
317
|
|
|
196
318
|
{
|
|
@@ -271,1385 +393,1898 @@ var NativeDocument = (function (exports) {
|
|
|
271
393
|
|
|
272
394
|
var PluginsManager = PluginsManager$1;
|
|
273
395
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
396
|
+
function NDElement(element) {
|
|
397
|
+
this.$element = element;
|
|
398
|
+
this.$observer = null;
|
|
399
|
+
{
|
|
400
|
+
PluginsManager.emit('NDElementCreated', element, this);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
NDElement.prototype.__$isNDElement = true;
|
|
405
|
+
|
|
406
|
+
NDElement.prototype.valueOf = function() {
|
|
407
|
+
return this.$element;
|
|
284
408
|
};
|
|
285
409
|
|
|
286
|
-
|
|
410
|
+
NDElement.prototype.ref = function(target, name) {
|
|
411
|
+
target[name] = this.$element;
|
|
412
|
+
return this;
|
|
413
|
+
};
|
|
287
414
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
* @param {Function} callback - Function called with boolean indicating if values match
|
|
292
|
-
* @returns {Function} Unsubscribe function
|
|
293
|
-
* @example
|
|
294
|
-
* const status = Observable('idle');
|
|
295
|
-
* const isLoading = status.when('loading');
|
|
296
|
-
* isLoading.subscribe(active => console.log('Loading:', active));
|
|
297
|
-
*/
|
|
298
|
-
ObservableWhen.prototype.subscribe = function(callback) {
|
|
299
|
-
return this.$observer.on(this.$target, callback);
|
|
415
|
+
NDElement.prototype.refSelf = function(target, name) {
|
|
416
|
+
target[name] = this;
|
|
417
|
+
return this;
|
|
300
418
|
};
|
|
301
419
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
420
|
+
NDElement.prototype.unmountChildren = function() {
|
|
421
|
+
let element = this.$element;
|
|
422
|
+
for(let i = 0, length = element.children.length; i < length; i++) {
|
|
423
|
+
let elementChildren = element.children[i];
|
|
424
|
+
if(!elementChildren.$ndProx) {
|
|
425
|
+
elementChildren.nd?.remove();
|
|
426
|
+
}
|
|
427
|
+
elementChildren = null;
|
|
428
|
+
}
|
|
429
|
+
element = null;
|
|
430
|
+
return this;
|
|
309
431
|
};
|
|
310
432
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
433
|
+
NDElement.prototype.remove = function() {
|
|
434
|
+
let element = this.$element;
|
|
435
|
+
element.nd.unmountChildren();
|
|
436
|
+
element.$ndProx = null;
|
|
437
|
+
delete element.nd?.on?.prevent;
|
|
438
|
+
delete element.nd?.on;
|
|
439
|
+
delete element.nd;
|
|
440
|
+
element = null;
|
|
441
|
+
return this;
|
|
442
|
+
};
|
|
318
443
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
* Alias for val().
|
|
322
|
-
*
|
|
323
|
-
* @returns {boolean} True if observable value matches target value
|
|
324
|
-
*/
|
|
325
|
-
ObservableWhen.prototype.isActive = ObservableWhen.prototype.val;
|
|
444
|
+
NDElement.prototype.lifecycle = function(states) {
|
|
445
|
+
this.$observer = this.$observer || DocumentObserver.watch(this.$element);
|
|
326
446
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
447
|
+
if(states.mounted) {
|
|
448
|
+
this.$element.setAttribute('data--nd-mounted', '1');
|
|
449
|
+
this.$observer.mounted(states.mounted);
|
|
450
|
+
}
|
|
451
|
+
if(states.unmounted) {
|
|
452
|
+
this.$element.setAttribute('data--nd-unmounted', '1');
|
|
453
|
+
this.$observer.unmounted(states.unmounted);
|
|
454
|
+
}
|
|
455
|
+
return this;
|
|
456
|
+
};
|
|
332
457
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
pending = false;
|
|
336
|
-
});
|
|
337
|
-
};
|
|
458
|
+
NDElement.prototype.mounted = function(callback) {
|
|
459
|
+
return this.lifecycle({ mounted: callback });
|
|
338
460
|
};
|
|
339
461
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
* @param {string|null} defaultKey
|
|
344
|
-
* @param {?Function} key
|
|
345
|
-
* @returns {*}
|
|
346
|
-
*/
|
|
347
|
-
const getKey = (item, defaultKey, key) => {
|
|
348
|
-
if (Validator.isString(key)) {
|
|
349
|
-
const val = Validator.isObservable(item) ? item.val() : item;
|
|
350
|
-
const result = val?.[key];
|
|
351
|
-
return Validator.isObservable(result) ? result.val() : (result ?? defaultKey);
|
|
352
|
-
}
|
|
462
|
+
NDElement.prototype.unmounted = function(callback) {
|
|
463
|
+
return this.lifecycle({ unmounted: callback });
|
|
464
|
+
};
|
|
353
465
|
|
|
354
|
-
|
|
355
|
-
|
|
466
|
+
NDElement.prototype.beforeUnmount = function(id, callback) {
|
|
467
|
+
const el = this.$element;
|
|
468
|
+
|
|
469
|
+
if(!DocumentObserver.beforeUnmount.has(el)) {
|
|
470
|
+
DocumentObserver.beforeUnmount.set(el, new Map());
|
|
471
|
+
const originalRemove = el.remove.bind(el);
|
|
472
|
+
|
|
473
|
+
let $isUnmounting = false;
|
|
474
|
+
|
|
475
|
+
el.remove = async () => {
|
|
476
|
+
if($isUnmounting) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
$isUnmounting = true;
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
const callbacks = DocumentObserver.beforeUnmount.get(el);
|
|
483
|
+
for (const cb of callbacks.values()) {
|
|
484
|
+
await cb.call(this, el);
|
|
485
|
+
}
|
|
486
|
+
} finally {
|
|
487
|
+
originalRemove();
|
|
488
|
+
$isUnmounting = false;
|
|
489
|
+
}
|
|
490
|
+
};
|
|
356
491
|
}
|
|
357
492
|
|
|
358
|
-
|
|
359
|
-
return
|
|
493
|
+
DocumentObserver.beforeUnmount.get(el).set(id, callback);
|
|
494
|
+
return this;
|
|
360
495
|
};
|
|
361
496
|
|
|
362
|
-
|
|
363
|
-
return
|
|
497
|
+
NDElement.prototype.htmlElement = function() {
|
|
498
|
+
return this.$element;
|
|
364
499
|
};
|
|
365
500
|
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
if(window.structuredClone !== undefined) {
|
|
369
|
-
return window.structuredClone(value);
|
|
370
|
-
}
|
|
371
|
-
} catch (e){}
|
|
372
|
-
|
|
373
|
-
if (value === null || typeof value !== 'object') {
|
|
374
|
-
return value;
|
|
375
|
-
}
|
|
501
|
+
NDElement.prototype.node = NDElement.prototype.htmlElement;
|
|
376
502
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
503
|
+
NDElement.prototype.shadow = function(mode, style = null) {
|
|
504
|
+
const $element = this.$element;
|
|
505
|
+
const children = Array.from($element.childNodes);
|
|
506
|
+
const shadowRoot = $element.attachShadow({ mode });
|
|
507
|
+
if(style) {
|
|
508
|
+
const styleNode = document.createElement("style");
|
|
509
|
+
styleNode.textContent = style;
|
|
510
|
+
shadowRoot.appendChild(styleNode);
|
|
380
511
|
}
|
|
512
|
+
$element.append = shadowRoot.append.bind(shadowRoot);
|
|
513
|
+
$element.appendChild = shadowRoot.appendChild.bind(shadowRoot);
|
|
514
|
+
shadowRoot.append(...children);
|
|
381
515
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return value.map(item => deepClone(item));
|
|
385
|
-
}
|
|
516
|
+
return this;
|
|
517
|
+
};
|
|
386
518
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return value;
|
|
391
|
-
}
|
|
519
|
+
NDElement.prototype.openShadow = function(style = null) {
|
|
520
|
+
return this.shadow('open', style);
|
|
521
|
+
};
|
|
392
522
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
for (const key in value) {
|
|
396
|
-
if (Object.hasOwn(value, key)) {
|
|
397
|
-
cloned[key] = deepClone(value[key]);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
return cloned;
|
|
523
|
+
NDElement.prototype.closedShadow = function(style = null) {
|
|
524
|
+
return this.shadow('closed', style);
|
|
401
525
|
};
|
|
402
526
|
|
|
403
527
|
/**
|
|
528
|
+
* Attaches a template binding to the element by hydrating it with the specified method.
|
|
404
529
|
*
|
|
405
|
-
* @param {
|
|
406
|
-
* @param {
|
|
407
|
-
* @
|
|
530
|
+
* @param {string} methodName - Name of the hydration method to call
|
|
531
|
+
* @param {BindingHydrator} bindingHydrator - Template binding with $hydrate method
|
|
532
|
+
* @returns {HTMLElement} The underlying HTML element
|
|
533
|
+
* @example
|
|
534
|
+
* const onClick = $binder.attach((event, data) => console.log(data));
|
|
535
|
+
* element.nd.attach('onClick', onClick);
|
|
408
536
|
*/
|
|
409
|
-
function
|
|
410
|
-
|
|
537
|
+
NDElement.prototype.attach = function(methodName, bindingHydrator) {
|
|
538
|
+
bindingHydrator.$hydrate(this.$element, methodName);
|
|
539
|
+
return this.$element;
|
|
540
|
+
};
|
|
411
541
|
|
|
412
|
-
|
|
413
|
-
|
|
542
|
+
/**
|
|
543
|
+
* Extends the current NDElement instance with custom methods.
|
|
544
|
+
* Methods are bound to the instance and available for chaining.
|
|
545
|
+
*
|
|
546
|
+
* @param {Object} methods - Object containing method definitions
|
|
547
|
+
* @returns {this} The NDElement instance with added methods for chaining
|
|
548
|
+
* @example
|
|
549
|
+
* element.nd.with({
|
|
550
|
+
* highlight() {
|
|
551
|
+
* this.$element.style.background = 'yellow';
|
|
552
|
+
* return this;
|
|
553
|
+
* }
|
|
554
|
+
* }).highlight().onClick(() => console.log('Clicked'));
|
|
555
|
+
*/
|
|
556
|
+
NDElement.prototype.with = function(methods) {
|
|
557
|
+
if (!methods || typeof methods !== 'object') {
|
|
558
|
+
throw new NativeDocumentError('extend() requires an object of methods');
|
|
559
|
+
}
|
|
414
560
|
{
|
|
415
|
-
this.$
|
|
561
|
+
if (!this.$localExtensions) {
|
|
562
|
+
this.$localExtensions = new Map();
|
|
563
|
+
}
|
|
416
564
|
}
|
|
417
565
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
this.$watchers = null;
|
|
421
|
-
|
|
422
|
-
this.$memoryId = null;
|
|
566
|
+
for (const name in methods) {
|
|
567
|
+
const method = methods[name];
|
|
423
568
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
569
|
+
if (typeof method !== 'function') {
|
|
570
|
+
console.warn(`⚠️ extends(): "${name}" is not a function, skipping`);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
{
|
|
574
|
+
if (this[name] && !this.$localExtensions.has(name)) {
|
|
575
|
+
DebugManager.warn('NDElement.extend', `Method "${name}" already exists and will be overwritten`);
|
|
576
|
+
}
|
|
577
|
+
this.$localExtensions.set(name, method);
|
|
428
578
|
}
|
|
429
|
-
}
|
|
430
|
-
{
|
|
431
|
-
PluginsManager.emit('CreateObservable', this);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
579
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
return this.$currentValue;
|
|
438
|
-
},
|
|
439
|
-
set(value) {
|
|
440
|
-
this.set(value);
|
|
441
|
-
},
|
|
442
|
-
configurable: true,
|
|
443
|
-
});
|
|
580
|
+
this[name] = method.bind(this);
|
|
581
|
+
}
|
|
444
582
|
|
|
445
|
-
|
|
446
|
-
|
|
583
|
+
return this;
|
|
584
|
+
};
|
|
447
585
|
|
|
448
586
|
/**
|
|
449
|
-
*
|
|
450
|
-
*
|
|
587
|
+
* Extends the NDElement prototype with new methods available to all NDElement instances.
|
|
588
|
+
* Use this to add global methods to all NDElements.
|
|
451
589
|
*
|
|
452
|
-
* @param {
|
|
453
|
-
* @returns {
|
|
590
|
+
* @param {Object} methods - Object containing method definitions to add to prototype
|
|
591
|
+
* @returns {typeof NDElement} The NDElement constructor
|
|
592
|
+
* @throws {NativeDocumentError} If methods is not an object or contains non-function values
|
|
454
593
|
* @example
|
|
455
|
-
*
|
|
456
|
-
*
|
|
594
|
+
* NDElement.extend({
|
|
595
|
+
* fadeIn() {
|
|
596
|
+
* this.$element.style.opacity = '1';
|
|
597
|
+
* return this;
|
|
598
|
+
* }
|
|
599
|
+
* });
|
|
600
|
+
* // Now all NDElements have .fadeIn() method
|
|
601
|
+
* Div().nd.fadeIn();
|
|
457
602
|
*/
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
ObservableItem.prototype.triggerFirstListener = function(operations) {
|
|
465
|
-
this.$firstListener(this.$currentValue, this.$previousValue, operations);
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
ObservableItem.prototype.triggerListeners = function(operations) {
|
|
469
|
-
const $listeners = this.$listeners;
|
|
470
|
-
const $previousValue = this.$previousValue;
|
|
471
|
-
const $currentValue = this.$currentValue;
|
|
603
|
+
NDElement.extend = function(methods) {
|
|
604
|
+
if (!methods || typeof methods !== 'object') {
|
|
605
|
+
throw new NativeDocumentError('NDElement.extend() requires an object of methods');
|
|
606
|
+
}
|
|
472
607
|
|
|
473
|
-
|
|
474
|
-
|
|
608
|
+
if (Array.isArray(methods)) {
|
|
609
|
+
throw new NativeDocumentError('NDElement.extend() requires an object, not an array');
|
|
475
610
|
}
|
|
476
|
-
};
|
|
477
611
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
612
|
+
const protectedMethods = new Set([
|
|
613
|
+
'constructor', 'valueOf', '$element', '$observer',
|
|
614
|
+
'ref', 'remove', 'cleanup', 'with', 'extend', 'attach',
|
|
615
|
+
'lifecycle', 'mounted', 'unmounted', 'unmountChildren'
|
|
616
|
+
]);
|
|
482
617
|
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
if($previousValueCallbacks) {
|
|
489
|
-
$previousValueCallbacks(false, $currentValue, operations);
|
|
490
|
-
}
|
|
491
|
-
};
|
|
618
|
+
for (const name in methods) {
|
|
619
|
+
if (!Object.hasOwn(methods, name)) {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
492
622
|
|
|
493
|
-
|
|
494
|
-
this.triggerWatchers(operations);
|
|
495
|
-
this.triggerListeners(operations);
|
|
496
|
-
};
|
|
623
|
+
const method = methods[name];
|
|
497
624
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
625
|
+
if (typeof method !== 'function') {
|
|
626
|
+
DebugManager.warn('NDElement.extend', `"${name}" is not a function, skipping`);
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
502
629
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
this.trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
if(this.$listeners?.length) {
|
|
510
|
-
if(this.$listeners.length === 1) {
|
|
511
|
-
this.$firstListener = this.$listeners[0];
|
|
512
|
-
this.trigger = this.triggerFirstListener;
|
|
630
|
+
if (protectedMethods.has(name)) {
|
|
631
|
+
DebugManager.error('NDElement.extend', `Cannot override protected method "${name}"`);
|
|
632
|
+
throw new NativeDocumentError(`Cannot override protected method "${name}"`);
|
|
513
633
|
}
|
|
514
|
-
|
|
515
|
-
|
|
634
|
+
|
|
635
|
+
if (NDElement.prototype[name]) {
|
|
636
|
+
DebugManager.warn('NDElement.extend', `Overwriting existing prototype method "${name}"`);
|
|
516
637
|
}
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
if(this.$watchers?.size) {
|
|
520
|
-
this.trigger = this.triggerWatchers;
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
this.trigger = noneTrigger;
|
|
524
|
-
};
|
|
525
|
-
ObservableItem.prototype.trigger = noneTrigger;
|
|
526
638
|
|
|
527
|
-
|
|
528
|
-
newValue = newValue?.__$isObservable ? newValue.val() : newValue;
|
|
529
|
-
if(this.$currentValue === newValue) {
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
this.$previousValue = this.$currentValue;
|
|
533
|
-
this.$currentValue = newValue;
|
|
534
|
-
{
|
|
535
|
-
PluginsManager.emit('ObservableBeforeChange', this);
|
|
639
|
+
NDElement.prototype[name] = method;
|
|
536
640
|
}
|
|
537
|
-
this.trigger();
|
|
538
|
-
this.$previousValue = null;
|
|
539
641
|
{
|
|
540
|
-
PluginsManager.emit('
|
|
541
|
-
}
|
|
542
|
-
};
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* @param {*} data
|
|
546
|
-
*/
|
|
547
|
-
ObservableItem.prototype.$setWithInterceptor = function(data) {
|
|
548
|
-
let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
|
|
549
|
-
const result = this.$interceptor(newValue, this.$currentValue);
|
|
550
|
-
|
|
551
|
-
if (result !== undefined) {
|
|
552
|
-
newValue = result;
|
|
642
|
+
PluginsManager.emit('NDElementExtended', methods);
|
|
553
643
|
}
|
|
554
644
|
|
|
555
|
-
|
|
645
|
+
return NDElement;
|
|
556
646
|
};
|
|
557
647
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
this.$updateWithNewValue(newValue);
|
|
648
|
+
const COMMON_NODE_TYPES = {
|
|
649
|
+
ELEMENT: 1,
|
|
650
|
+
TEXT: 3,
|
|
651
|
+
COMMENT: 8,
|
|
652
|
+
DOCUMENT_FRAGMENT: 11
|
|
564
653
|
};
|
|
565
654
|
|
|
566
|
-
|
|
655
|
+
const Validator = {
|
|
656
|
+
isObservable(value) {
|
|
657
|
+
return value?.__$isObservable;
|
|
658
|
+
},
|
|
659
|
+
isTemplateBinding(value) {
|
|
660
|
+
return value?.__$isTemplateBinding;
|
|
661
|
+
},
|
|
662
|
+
isObservableWhenResult(value) {
|
|
663
|
+
return value && (value.__$isObservableWhen || (typeof value === 'object' && '$target' in value && '$observer' in value));
|
|
664
|
+
},
|
|
665
|
+
isArrayObservable(value) {
|
|
666
|
+
return value?.__$isObservableArray;
|
|
667
|
+
},
|
|
668
|
+
isProxy(value) {
|
|
669
|
+
return value?.__isProxy__
|
|
670
|
+
},
|
|
671
|
+
isObservableOrProxy(value) {
|
|
672
|
+
return Validator.isObservable(value) || Validator.isProxy(value);
|
|
673
|
+
},
|
|
674
|
+
isAnchor(value) {
|
|
675
|
+
return value?.__Anchor__
|
|
676
|
+
},
|
|
677
|
+
isObservableChecker(value) {
|
|
678
|
+
return value?.__$isObservableChecker || value instanceof ObservableChecker;
|
|
679
|
+
},
|
|
680
|
+
isArray(value) {
|
|
681
|
+
return Array.isArray(value);
|
|
682
|
+
},
|
|
683
|
+
isString(value) {
|
|
684
|
+
return typeof value === 'string';
|
|
685
|
+
},
|
|
686
|
+
isNumber(value) {
|
|
687
|
+
return typeof value === 'number';
|
|
688
|
+
},
|
|
689
|
+
isBoolean(value) {
|
|
690
|
+
return typeof value === 'boolean';
|
|
691
|
+
},
|
|
692
|
+
isFunction(value) {
|
|
693
|
+
return typeof value === 'function';
|
|
694
|
+
},
|
|
695
|
+
isAsyncFunction(value) {
|
|
696
|
+
return typeof value === 'function' && value.constructor.name === 'AsyncFunction';
|
|
697
|
+
},
|
|
698
|
+
isObject(value) {
|
|
699
|
+
return typeof value === 'object' && value !== null;
|
|
700
|
+
},
|
|
701
|
+
isJson(value) {
|
|
702
|
+
return !(typeof value !== 'object' || value === null || Array.isArray(value) || value.constructor.name !== 'Object')
|
|
703
|
+
},
|
|
704
|
+
isElement(value) {
|
|
705
|
+
return value && (
|
|
706
|
+
value.nodeType === COMMON_NODE_TYPES.ELEMENT ||
|
|
707
|
+
value.nodeType === COMMON_NODE_TYPES.TEXT ||
|
|
708
|
+
value.nodeType === COMMON_NODE_TYPES.DOCUMENT_FRAGMENT ||
|
|
709
|
+
value.nodeType === COMMON_NODE_TYPES.COMMENT
|
|
710
|
+
);
|
|
711
|
+
},
|
|
712
|
+
isFragment(value) {
|
|
713
|
+
return value?.nodeType === COMMON_NODE_TYPES.DOCUMENT_FRAGMENT;
|
|
714
|
+
},
|
|
715
|
+
isStringOrObservable(value) {
|
|
716
|
+
return this.isString(value) || this.isObservable(value);
|
|
717
|
+
},
|
|
718
|
+
isValidChild(child) {
|
|
719
|
+
return child === null ||
|
|
720
|
+
this.isElement(child) ||
|
|
721
|
+
this.isObservable(child) ||
|
|
722
|
+
this.isNDElement(child) ||
|
|
723
|
+
['string', 'number', 'boolean'].includes(typeof child);
|
|
724
|
+
},
|
|
725
|
+
isNDElement(child) {
|
|
726
|
+
return child?.__$isNDElement || child instanceof NDElement;
|
|
727
|
+
},
|
|
728
|
+
isValidChildren(children) {
|
|
729
|
+
if (!Array.isArray(children)) {
|
|
730
|
+
children = [children];
|
|
731
|
+
}
|
|
567
732
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
733
|
+
const invalid = children.filter(child => !this.isValidChild(child));
|
|
734
|
+
return invalid.length === 0;
|
|
735
|
+
},
|
|
736
|
+
validateChildren(children) {
|
|
737
|
+
if (!Array.isArray(children)) {
|
|
738
|
+
children = [children];
|
|
739
|
+
}
|
|
571
740
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
this.$currentValue = null;
|
|
576
|
-
if(this.$watchers) {
|
|
577
|
-
for (const [_, watchValueList] of this.$watchers) {
|
|
578
|
-
if(Validator.isArray(watchValueList)) {
|
|
579
|
-
watchValueList.splice(0);
|
|
580
|
-
}
|
|
741
|
+
const invalid = children.filter(child => !this.isValidChild(child));
|
|
742
|
+
if (invalid.length > 0) {
|
|
743
|
+
throw new NativeDocumentError(`Invalid children detected: ${invalid.map(i => typeof i).join(', ')}`);
|
|
581
744
|
}
|
|
582
|
-
}
|
|
583
|
-
this.$watchers?.clear();
|
|
584
|
-
this.$listeners = null;
|
|
585
|
-
this.$watchers = null;
|
|
586
|
-
this.trigger = noneTrigger;
|
|
587
|
-
};
|
|
588
745
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
ObservableItem.prototype.onCleanup = function(callback) {
|
|
600
|
-
this.$cleanupListeners = this.$cleanupListeners ?? [];
|
|
601
|
-
this.$cleanupListeners.push(callback);
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
ObservableItem.prototype.cleanup = function() {
|
|
605
|
-
if (this.$cleanupListeners) {
|
|
606
|
-
for (let i = 0; i < this.$cleanupListeners.length; i++) {
|
|
607
|
-
this.$cleanupListeners[i]();
|
|
746
|
+
return children;
|
|
747
|
+
},
|
|
748
|
+
/**
|
|
749
|
+
* Check if the data contains observables.
|
|
750
|
+
* @param {Array|Object} data
|
|
751
|
+
* @returns {boolean}
|
|
752
|
+
*/
|
|
753
|
+
containsObservables(data) {
|
|
754
|
+
if(!data) {
|
|
755
|
+
return false;
|
|
608
756
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
*
|
|
621
|
-
* @param {Function} callback
|
|
622
|
-
* @returns {(function(): void)}
|
|
623
|
-
*/
|
|
624
|
-
ObservableItem.prototype.subscribe = function(callback) {
|
|
625
|
-
{
|
|
626
|
-
if (this.$isCleanedUp) {
|
|
627
|
-
DebugManager.warn('Observable subscription', '⚠️ Attempted to subscribe to a cleaned up observable.');
|
|
628
|
-
return;
|
|
757
|
+
return Validator.isObject(data)
|
|
758
|
+
&& Object.values(data).some(value => Validator.isObservable(value));
|
|
759
|
+
},
|
|
760
|
+
/**
|
|
761
|
+
* Check if the data contains an observable reference.
|
|
762
|
+
* @param {string} data
|
|
763
|
+
* @returns {boolean}
|
|
764
|
+
*/
|
|
765
|
+
containsObservableReference(data) {
|
|
766
|
+
if(!data || typeof data !== 'string') {
|
|
767
|
+
return false;
|
|
629
768
|
}
|
|
769
|
+
return /\{\{#ObItem::\([0-9]+\)\}\}/.test(data);
|
|
770
|
+
},
|
|
771
|
+
validateAttributes(attributes) {},
|
|
772
|
+
|
|
773
|
+
validateEventCallback(callback) {
|
|
630
774
|
if (typeof callback !== 'function') {
|
|
631
|
-
throw new NativeDocumentError('
|
|
775
|
+
throw new NativeDocumentError('Event callback must be a function');
|
|
632
776
|
}
|
|
633
777
|
}
|
|
634
|
-
this.$listeners = this.$listeners ?? [];
|
|
635
|
-
|
|
636
|
-
this.$listeners.push(callback);
|
|
637
|
-
this.assocTrigger();
|
|
638
|
-
{
|
|
639
|
-
PluginsManager.emit('ObservableSubscribe', this);
|
|
640
|
-
}
|
|
641
778
|
};
|
|
779
|
+
{
|
|
780
|
+
Validator.validateAttributes = function(attributes) {
|
|
781
|
+
if (!attributes || typeof attributes !== 'object') {
|
|
782
|
+
return attributes;
|
|
783
|
+
}
|
|
642
784
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
* Creates a watcher that only triggers when the observable changes to the specified value.
|
|
646
|
-
*
|
|
647
|
-
* @param {*} value - The value to watch for
|
|
648
|
-
* @param {(value) => void|ObservableItem} callback - Callback function or observable to set when value matches
|
|
649
|
-
* @example
|
|
650
|
-
* const status = Observable('idle');
|
|
651
|
-
* status.on('loading', () => console.log('Started loading'));
|
|
652
|
-
* status.on('error', isError); // Set another observable
|
|
653
|
-
*/
|
|
654
|
-
ObservableItem.prototype.on = function(value, callback) {
|
|
655
|
-
this.$watchers = this.$watchers ?? new Map();
|
|
785
|
+
const reserved = [];
|
|
786
|
+
const foundReserved = Object.keys(attributes).filter(key => reserved.includes(key));
|
|
656
787
|
|
|
657
|
-
|
|
788
|
+
if (foundReserved.length > 0) {
|
|
789
|
+
DebugManager.warn('Validator', `Reserved attributes found: ${foundReserved.join(', ')}`);
|
|
790
|
+
}
|
|
658
791
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
792
|
+
return attributes;
|
|
793
|
+
};
|
|
794
|
+
}
|
|
662
795
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
} else if(!Validator.isArray(watchValueList.list)) {
|
|
667
|
-
watchValueList = [watchValueList, callback];
|
|
668
|
-
callback = (value) => {
|
|
669
|
-
for(let i = 0, length = watchValueList.length; i < length; i++) {
|
|
670
|
-
watchValueList[i](value);
|
|
671
|
-
}
|
|
672
|
-
};
|
|
673
|
-
callback.list = watchValueList;
|
|
674
|
-
this.$watchers.set(value, callback);
|
|
675
|
-
} else {
|
|
676
|
-
watchValueList.list.push(callback);
|
|
677
|
-
}
|
|
796
|
+
function Anchor(name, isUniqueChild = false) {
|
|
797
|
+
const anchorFragment = document.createDocumentFragment();
|
|
798
|
+
anchorFragment.__Anchor__ = true;
|
|
678
799
|
|
|
679
|
-
|
|
680
|
-
|
|
800
|
+
const anchorStart = document.createComment('Anchor Start : '+name);
|
|
801
|
+
const anchorEnd = document.createComment('/ Anchor End '+name);
|
|
681
802
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
*
|
|
685
|
-
* @param {*} value - The value to stop watching
|
|
686
|
-
* @param {Function} [callback] - Specific callback to remove. If omitted, removes all watchers for this value
|
|
687
|
-
* @example
|
|
688
|
-
* const status = Observable('idle');
|
|
689
|
-
* const handler = () => console.log('Loading');
|
|
690
|
-
* status.on('loading', handler);
|
|
691
|
-
* status.off('loading', handler); // Remove specific handler
|
|
692
|
-
* status.off('loading'); // Remove all handlers for 'loading'
|
|
693
|
-
*/
|
|
694
|
-
ObservableItem.prototype.off = function(value, callback) {
|
|
695
|
-
if(!this.$watchers) return;
|
|
803
|
+
anchorFragment.appendChild(anchorStart);
|
|
804
|
+
anchorFragment.appendChild(anchorEnd);
|
|
696
805
|
|
|
697
|
-
|
|
698
|
-
|
|
806
|
+
anchorFragment.nativeInsertBefore = anchorFragment.insertBefore;
|
|
807
|
+
anchorFragment.nativeAppendChild = anchorFragment.appendChild;
|
|
808
|
+
anchorFragment.nativeAppend = anchorFragment.append;
|
|
699
809
|
|
|
700
|
-
|
|
701
|
-
this.$watchers?.delete(value);
|
|
702
|
-
this.assocTrigger();
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
const index = watchValueList.indexOf(callback);
|
|
706
|
-
watchValueList?.splice(index, 1);
|
|
707
|
-
if(watchValueList.length === 1) {
|
|
708
|
-
this.$watchers.set(value, watchValueList[0]);
|
|
709
|
-
}
|
|
710
|
-
else if(watchValueList.length === 0) {
|
|
711
|
-
this.$watchers?.delete(value);
|
|
712
|
-
}
|
|
713
|
-
this.assocTrigger();
|
|
714
|
-
};
|
|
810
|
+
const isParentUniqueChild = (parent) => (isUniqueChild || (parent.firstChild === anchorStart && parent.lastChild === anchorEnd));
|
|
715
811
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
812
|
+
const insertBefore = function(parent, child, target) {
|
|
813
|
+
const childElement = Validator.isElement(child) ? child : ElementCreator.getChild(child);
|
|
814
|
+
if(parent === anchorFragment) {
|
|
815
|
+
parent.nativeInsertBefore(childElement, target);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if(isParentUniqueChild(parent) && target === anchorEnd) {
|
|
819
|
+
parent.append(childElement, target);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
parent.insertBefore(childElement, target);
|
|
823
|
+
};
|
|
728
824
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
825
|
+
anchorFragment.appendElement = function(child, before = null) {
|
|
826
|
+
const parentNode = anchorStart.parentNode;
|
|
827
|
+
const targetBefore = before || anchorEnd;
|
|
828
|
+
if(parentNode === anchorFragment) {
|
|
829
|
+
parentNode.nativeInsertBefore(child, targetBefore);
|
|
830
|
+
return;
|
|
733
831
|
}
|
|
832
|
+
parentNode?.insertBefore(child, targetBefore);
|
|
734
833
|
};
|
|
735
|
-
this.subscribe(handler);
|
|
736
|
-
};
|
|
737
834
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
}
|
|
748
|
-
this.assocTrigger();
|
|
749
|
-
{
|
|
750
|
-
PluginsManager.emit('ObservableUnsubscribe', this);
|
|
751
|
-
}
|
|
752
|
-
};
|
|
835
|
+
anchorFragment.appendChild = function(child, before = null) {
|
|
836
|
+
const parent = anchorEnd.parentNode;
|
|
837
|
+
if(!parent) {
|
|
838
|
+
DebugManager.error('Anchor', 'Anchor : parent not found', child);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
before = before ?? anchorEnd;
|
|
842
|
+
insertBefore(parent, child, before);
|
|
843
|
+
};
|
|
753
844
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
* @returns {ObservableChecker}
|
|
758
|
-
*/
|
|
759
|
-
ObservableItem.prototype.check = function(callback) {
|
|
760
|
-
return new ObservableChecker(this, callback)
|
|
761
|
-
};
|
|
845
|
+
anchorFragment.append = function(...args ) {
|
|
846
|
+
return anchorFragment.appendChild(args);
|
|
847
|
+
};
|
|
762
848
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
* user.get('age'); // 25 (unwrapped from observable)
|
|
773
|
-
*/
|
|
774
|
-
ObservableItem.prototype.get = function(key) {
|
|
775
|
-
const item = this.$currentValue[key];
|
|
776
|
-
return Validator.isObservable(item) ? item.val() : item;
|
|
777
|
-
};
|
|
849
|
+
anchorFragment.removeChildren = async function() {
|
|
850
|
+
const parent = anchorEnd.parentNode;
|
|
851
|
+
if(parent === anchorFragment) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
// if(isParentUniqueChild(parent)) {
|
|
855
|
+
// parent.replaceChildren(anchorStart, anchorEnd);
|
|
856
|
+
// return;
|
|
857
|
+
// }
|
|
778
858
|
|
|
859
|
+
let itemToRemove = anchorStart.nextSibling, tempItem;
|
|
860
|
+
const removes = [];
|
|
861
|
+
while(itemToRemove && itemToRemove !== anchorEnd) {
|
|
862
|
+
tempItem = itemToRemove.nextSibling;
|
|
863
|
+
removes.push(itemToRemove.remove());
|
|
864
|
+
itemToRemove = tempItem;
|
|
865
|
+
}
|
|
866
|
+
await Promise.all(removes);
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
anchorFragment.remove = async function() {
|
|
870
|
+
const parent = anchorEnd.parentNode;
|
|
871
|
+
if(parent === anchorFragment) {
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
let itemToRemove = anchorStart.nextSibling, tempItem;
|
|
875
|
+
const allItemToRemove = [];
|
|
876
|
+
const removes = [];
|
|
877
|
+
while(itemToRemove && itemToRemove !== anchorEnd) {
|
|
878
|
+
tempItem = itemToRemove.nextSibling;
|
|
879
|
+
allItemToRemove.push(itemToRemove);
|
|
880
|
+
removes.push(itemToRemove.remove());
|
|
881
|
+
itemToRemove = tempItem;
|
|
882
|
+
}
|
|
883
|
+
await Promise.all(removes);
|
|
884
|
+
anchorFragment.nativeAppend(...allItemToRemove);
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
anchorFragment.removeWithAnchors = async function() {
|
|
888
|
+
await anchorFragment.removeChildren();
|
|
889
|
+
anchorStart.remove();
|
|
890
|
+
anchorEnd.remove();
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
anchorFragment.replaceContent = async function(child) {
|
|
894
|
+
const childElement = Validator.isElement(child) ? child : ElementCreator.getChild(child);
|
|
895
|
+
const parent = anchorEnd.parentNode;
|
|
896
|
+
if(!parent) {
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
// if(isParentUniqueChild(parent)) {
|
|
900
|
+
// parent.replaceChildren(anchorStart, childElement, anchorEnd);
|
|
901
|
+
// return;
|
|
902
|
+
// }
|
|
903
|
+
await anchorFragment.removeChildren();
|
|
904
|
+
parent.insertBefore(childElement, anchorEnd);
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
anchorFragment.setContent = anchorFragment.replaceContent;
|
|
908
|
+
|
|
909
|
+
anchorFragment.insertBefore = function(child, anchor = null) {
|
|
910
|
+
anchorFragment.appendChild(child, anchor);
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
anchorFragment.endElement = function() {
|
|
915
|
+
return anchorEnd;
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
anchorFragment.startElement = function() {
|
|
919
|
+
return anchorStart;
|
|
920
|
+
};
|
|
921
|
+
anchorFragment.restore = function() {
|
|
922
|
+
anchorFragment.appendChild(anchorFragment);
|
|
923
|
+
};
|
|
924
|
+
anchorFragment.clear = anchorFragment.remove;
|
|
925
|
+
anchorFragment.detach = anchorFragment.remove;
|
|
926
|
+
|
|
927
|
+
anchorFragment.getByIndex = function(index) {
|
|
928
|
+
let currentNode = anchorStart;
|
|
929
|
+
for(let i = 0; i <= index; i++) {
|
|
930
|
+
if(!currentNode.nextSibling) {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
currentNode = currentNode.nextSibling;
|
|
934
|
+
}
|
|
935
|
+
return currentNode !== anchorStart ? currentNode : null;
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
return anchorFragment;
|
|
939
|
+
}
|
|
779
940
|
/**
|
|
780
|
-
* Creates an ObservableWhen that represents whether the observable equals a specific value.
|
|
781
|
-
* Returns an object that can be subscribed to and will emit true/false.
|
|
782
941
|
*
|
|
942
|
+
* @param {HTMLElement|DocumentFragment|Text|String|Array} children
|
|
943
|
+
* @param {{ parent?: HTMLElement, name?: String}} configs
|
|
944
|
+
* @returns {DocumentFragment}
|
|
945
|
+
*/
|
|
946
|
+
function createPortal(children, { parent, name = 'unnamed' } = {}) {
|
|
947
|
+
const anchor = Anchor('Portal '+name);
|
|
948
|
+
anchor.appendChild(ElementCreator.getChild(children));
|
|
949
|
+
|
|
950
|
+
(parent || document.body).appendChild(anchor);
|
|
951
|
+
return anchor;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
DocumentFragment.prototype.setAttribute = () => {};
|
|
955
|
+
|
|
956
|
+
const BOOLEAN_ATTRIBUTES = new Set([
|
|
957
|
+
'checked',
|
|
958
|
+
'selected',
|
|
959
|
+
'disabled',
|
|
960
|
+
'readonly',
|
|
961
|
+
'required',
|
|
962
|
+
'autofocus',
|
|
963
|
+
'multiple',
|
|
964
|
+
'autocomplete',
|
|
965
|
+
'hidden',
|
|
966
|
+
'contenteditable',
|
|
967
|
+
'spellcheck',
|
|
968
|
+
'translate',
|
|
969
|
+
'draggable',
|
|
970
|
+
'async',
|
|
971
|
+
'defer',
|
|
972
|
+
'autoplay',
|
|
973
|
+
'controls',
|
|
974
|
+
'loop',
|
|
975
|
+
'muted',
|
|
976
|
+
'download',
|
|
977
|
+
'reversed',
|
|
978
|
+
'open',
|
|
979
|
+
'default',
|
|
980
|
+
'formnovalidate',
|
|
981
|
+
'novalidate',
|
|
982
|
+
'scoped',
|
|
983
|
+
'itemscope',
|
|
984
|
+
'allowfullscreen',
|
|
985
|
+
'allowpaymentrequest',
|
|
986
|
+
'playsinline'
|
|
987
|
+
]);
|
|
988
|
+
|
|
989
|
+
const MemoryManager = (function() {
|
|
990
|
+
|
|
991
|
+
let $nextObserverId = 0;
|
|
992
|
+
const $observables = new Map();
|
|
993
|
+
|
|
994
|
+
return {
|
|
995
|
+
/**
|
|
996
|
+
* Register an observable and return an id.
|
|
997
|
+
*
|
|
998
|
+
* @param {ObservableItem} observable
|
|
999
|
+
* @param {Function} getListeners
|
|
1000
|
+
* @returns {number}
|
|
1001
|
+
*/
|
|
1002
|
+
register(observable) {
|
|
1003
|
+
const id = ++$nextObserverId;
|
|
1004
|
+
$observables.set(id, new WeakRef(observable));
|
|
1005
|
+
return id;
|
|
1006
|
+
},
|
|
1007
|
+
unregister(id) {
|
|
1008
|
+
$observables.delete(id);
|
|
1009
|
+
},
|
|
1010
|
+
getObservableById(id) {
|
|
1011
|
+
return $observables.get(id)?.deref();
|
|
1012
|
+
},
|
|
1013
|
+
cleanup() {
|
|
1014
|
+
for (const [_, weakObservableRef] of $observables) {
|
|
1015
|
+
const observable = weakObservableRef.deref();
|
|
1016
|
+
if (observable) {
|
|
1017
|
+
observable.cleanup();
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
$observables.clear();
|
|
1021
|
+
},
|
|
1022
|
+
/**
|
|
1023
|
+
* Clean observables that are not referenced anymore.
|
|
1024
|
+
* @param {number} threshold
|
|
1025
|
+
*/
|
|
1026
|
+
cleanObservables(threshold) {
|
|
1027
|
+
if($observables.size < threshold) return;
|
|
1028
|
+
let cleanedCount = 0;
|
|
1029
|
+
for (const [id, weakObservableRef] of $observables) {
|
|
1030
|
+
if (!weakObservableRef.deref()) {
|
|
1031
|
+
$observables.delete(id);
|
|
1032
|
+
cleanedCount++;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
if (cleanedCount > 0) {
|
|
1036
|
+
DebugManager.log('Memory Auto Clean', `🧹 Cleaned ${cleanedCount} orphaned observables`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
}());
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Creates an ObservableWhen that tracks whether an observable equals a specific value.
|
|
1044
|
+
*
|
|
1045
|
+
* @param {ObservableItem} observer - The observable to watch
|
|
783
1046
|
* @param {*} value - The value to compare against
|
|
784
|
-
* @
|
|
785
|
-
* @example
|
|
786
|
-
* const status = Observable('idle');
|
|
787
|
-
* const isLoading = status.when('loading');
|
|
788
|
-
* isLoading.subscribe(active => console.log('Loading:', active));
|
|
789
|
-
* status.set('loading'); // Logs: "Loading: true"
|
|
1047
|
+
* @class ObservableWhen
|
|
790
1048
|
*/
|
|
791
|
-
|
|
792
|
-
|
|
1049
|
+
const ObservableWhen = function(observer, value) {
|
|
1050
|
+
this.$target = value;
|
|
1051
|
+
this.$observer = observer;
|
|
793
1052
|
};
|
|
794
1053
|
|
|
1054
|
+
ObservableWhen.prototype.__$isObservableWhen = true;
|
|
1055
|
+
|
|
795
1056
|
/**
|
|
796
|
-
*
|
|
1057
|
+
* Subscribes to changes in the match status (true when observable equals target value).
|
|
797
1058
|
*
|
|
798
|
-
* @param {
|
|
799
|
-
* @returns {
|
|
1059
|
+
* @param {Function} callback - Function called with boolean indicating if values match
|
|
1060
|
+
* @returns {Function} Unsubscribe function
|
|
800
1061
|
* @example
|
|
801
|
-
* const
|
|
802
|
-
* const
|
|
803
|
-
*
|
|
804
|
-
* a.equals(b); // true
|
|
805
|
-
* a.equals(10); // false
|
|
1062
|
+
* const status = Observable('idle');
|
|
1063
|
+
* const isLoading = status.when('loading');
|
|
1064
|
+
* isLoading.subscribe(active => console.log('Loading:', active));
|
|
806
1065
|
*/
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
return this.$currentValue === other.$currentValue;
|
|
810
|
-
}
|
|
811
|
-
return this.$currentValue === other;
|
|
1066
|
+
ObservableWhen.prototype.subscribe = function(callback) {
|
|
1067
|
+
return this.$observer.on(this.$target, callback);
|
|
812
1068
|
};
|
|
813
1069
|
|
|
814
1070
|
/**
|
|
815
|
-
*
|
|
1071
|
+
* Returns true if the observable's current value equals the target value.
|
|
816
1072
|
*
|
|
817
|
-
* @returns {boolean}
|
|
818
|
-
* @example
|
|
819
|
-
* const count = Observable(0);
|
|
820
|
-
* count.toBool(); // false
|
|
821
|
-
* count.set(5);
|
|
822
|
-
* count.toBool(); // true
|
|
1073
|
+
* @returns {boolean} True if observable value matches target value
|
|
823
1074
|
*/
|
|
824
|
-
|
|
825
|
-
return
|
|
1075
|
+
ObservableWhen.prototype.val = function() {
|
|
1076
|
+
return this.$observer.$currentValue === this.$target;
|
|
826
1077
|
};
|
|
827
1078
|
|
|
828
1079
|
/**
|
|
829
|
-
*
|
|
1080
|
+
* Returns true if the observable's current value equals the target value.
|
|
1081
|
+
* Alias for val().
|
|
830
1082
|
*
|
|
831
|
-
* @
|
|
832
|
-
* const isOpen = Observable(false);
|
|
833
|
-
* isOpen.toggle(); // Now true
|
|
834
|
-
* isOpen.toggle(); // Now false
|
|
1083
|
+
* @returns {boolean} True if observable value matches target value
|
|
835
1084
|
*/
|
|
836
|
-
|
|
837
|
-
this.set(!this.$currentValue);
|
|
838
|
-
};
|
|
1085
|
+
ObservableWhen.prototype.isMatch = ObservableWhen.prototype.val;
|
|
839
1086
|
|
|
840
1087
|
/**
|
|
841
|
-
*
|
|
842
|
-
*
|
|
1088
|
+
* Returns true if the observable's current value equals the target value.
|
|
1089
|
+
* Alias for val().
|
|
843
1090
|
*
|
|
844
|
-
* @
|
|
845
|
-
* const count = Observable(0, { reset: true });
|
|
846
|
-
* count.set(10);
|
|
847
|
-
* count.reset(); // Back to 0
|
|
1091
|
+
* @returns {boolean} True if observable value matches target value
|
|
848
1092
|
*/
|
|
849
|
-
|
|
850
|
-
if(!this.configs?.reset) {
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
const resetValue = (Validator.isObject(this.$initialValue))
|
|
854
|
-
? deepClone(this.$initialValue, (observable) => {
|
|
855
|
-
observable.reset();
|
|
856
|
-
})
|
|
857
|
-
: this.$initialValue;
|
|
858
|
-
this.set(resetValue);
|
|
859
|
-
};
|
|
1093
|
+
ObservableWhen.prototype.isActive = ObservableWhen.prototype.val;
|
|
860
1094
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1095
|
+
const nextTick = function(fn) {
|
|
1096
|
+
let pending = false;
|
|
1097
|
+
return function(...args) {
|
|
1098
|
+
if (pending) return;
|
|
1099
|
+
pending = true;
|
|
1100
|
+
|
|
1101
|
+
Promise.resolve().then(() => {
|
|
1102
|
+
fn.apply(this, args);
|
|
1103
|
+
pending = false;
|
|
1104
|
+
});
|
|
1105
|
+
};
|
|
868
1106
|
};
|
|
869
1107
|
|
|
870
1108
|
/**
|
|
871
|
-
* Returns the primitive value of the observable (its current value).
|
|
872
|
-
* Called automatically in type coercion contexts.
|
|
873
1109
|
*
|
|
874
|
-
* @
|
|
1110
|
+
* @param {*} item
|
|
1111
|
+
* @param {string|null} defaultKey
|
|
1112
|
+
* @param {?Function} key
|
|
1113
|
+
* @returns {*}
|
|
875
1114
|
*/
|
|
876
|
-
|
|
877
|
-
|
|
1115
|
+
const getKey = (item, defaultKey, key) => {
|
|
1116
|
+
if (Validator.isString(key)) {
|
|
1117
|
+
const val = Validator.isObservable(item) ? item.val() : item;
|
|
1118
|
+
const result = val?.[key];
|
|
1119
|
+
return Validator.isObservable(result) ? result.val() : (result ?? defaultKey);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
if (Validator.isFunction(key)) {
|
|
1123
|
+
return key(item, defaultKey);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const val = Validator.isObservable(item) ? item.val() : item;
|
|
1127
|
+
return val ?? defaultKey;
|
|
878
1128
|
};
|
|
879
1129
|
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
mountedSupposedSize: 0,
|
|
884
|
-
unmounted: new WeakMap(),
|
|
885
|
-
unmountedSupposedSize: 0,
|
|
886
|
-
observer: null,
|
|
1130
|
+
const trim = function(str, char) {
|
|
1131
|
+
return str.replace(new RegExp(`^[${char}]+|[${char}]+$`, 'g'), '');
|
|
1132
|
+
};
|
|
887
1133
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
if(
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
data.inDom = true;
|
|
894
|
-
if(!data.mounted) {
|
|
895
|
-
return;
|
|
896
|
-
}
|
|
897
|
-
if(Array.isArray(data.mounted)) {
|
|
898
|
-
for(const cb of data.mounted) {
|
|
899
|
-
cb(node);
|
|
900
|
-
}
|
|
901
|
-
return;
|
|
1134
|
+
const deepClone = (value, onObservableFound) => {
|
|
1135
|
+
try {
|
|
1136
|
+
if(window.structuredClone !== undefined) {
|
|
1137
|
+
return window.structuredClone(value);
|
|
902
1138
|
}
|
|
903
|
-
|
|
904
|
-
},
|
|
1139
|
+
} catch (e){}
|
|
905
1140
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
data.inDom = false;
|
|
912
|
-
if(!data.unmounted) {
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
1141
|
+
if (value === null || typeof value !== 'object') {
|
|
1142
|
+
return value;
|
|
1143
|
+
}
|
|
915
1144
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1145
|
+
// Dates
|
|
1146
|
+
if (value instanceof Date) {
|
|
1147
|
+
return new Date(value.getTime());
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// Arrays
|
|
1151
|
+
if (Array.isArray(value)) {
|
|
1152
|
+
return value.map(item => deepClone(item));
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// Observables - keep the référence
|
|
1156
|
+
if (Validator.isObservable(value)) {
|
|
1157
|
+
onObservableFound && onObservableFound(value);
|
|
1158
|
+
return value;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// Objects
|
|
1162
|
+
const cloned = {};
|
|
1163
|
+
for (const key in value) {
|
|
1164
|
+
if (Object.hasOwn(value, key)) {
|
|
1165
|
+
cloned[key] = deepClone(value[key]);
|
|
925
1166
|
}
|
|
1167
|
+
}
|
|
1168
|
+
return cloned;
|
|
1169
|
+
};
|
|
926
1170
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1171
|
+
const LocalStorage$1 = {
|
|
1172
|
+
getJson(key) {
|
|
1173
|
+
let value = localStorage.getItem(key);
|
|
1174
|
+
try {
|
|
1175
|
+
return JSON.parse(value);
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
throw new NativeDocumentError('invalid_json:'+key);
|
|
930
1178
|
}
|
|
931
1179
|
},
|
|
1180
|
+
getNumber(key) {
|
|
1181
|
+
return Number(this.get(key));
|
|
1182
|
+
},
|
|
1183
|
+
getBool(key) {
|
|
1184
|
+
const value = this.get(key);
|
|
1185
|
+
return value === 'true' || value === '1';
|
|
1186
|
+
},
|
|
1187
|
+
setJson(key, value) {
|
|
1188
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
1189
|
+
},
|
|
1190
|
+
setBool(key, value) {
|
|
1191
|
+
localStorage.setItem(key, value ? 'true' : 'false');
|
|
1192
|
+
},
|
|
1193
|
+
get(key, defaultValue = null) {
|
|
1194
|
+
return localStorage.getItem(key) || defaultValue;
|
|
1195
|
+
},
|
|
1196
|
+
set(key, value) {
|
|
1197
|
+
return localStorage.setItem(key, value);
|
|
1198
|
+
},
|
|
1199
|
+
remove(key) {
|
|
1200
|
+
localStorage.removeItem(key);
|
|
1201
|
+
},
|
|
1202
|
+
has(key) {
|
|
1203
|
+
return localStorage.getItem(key) != null;
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
932
1206
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
}
|
|
1207
|
+
const $getFromStorage$1 = (key, value) => {
|
|
1208
|
+
if(!LocalStorage$1.has(key)) {
|
|
1209
|
+
return value;
|
|
1210
|
+
}
|
|
1211
|
+
switch (typeof value) {
|
|
1212
|
+
case 'object': return LocalStorage$1.getJson(key) ?? value;
|
|
1213
|
+
case 'boolean': return LocalStorage$1.getBool(key) ?? value;
|
|
1214
|
+
case 'number': return LocalStorage$1.getNumber(key) ?? value;
|
|
1215
|
+
default: return LocalStorage$1.get(key, value) ?? value;
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
947
1218
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
}
|
|
961
|
-
},
|
|
1219
|
+
const $saveToStorage$1 = (value) => {
|
|
1220
|
+
switch (typeof value) {
|
|
1221
|
+
case 'object': return LocalStorage$1.setJson;
|
|
1222
|
+
case 'boolean': return LocalStorage$1.setBool;
|
|
1223
|
+
default: return LocalStorage$1.set;
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
const StoreFactory = function() {
|
|
1228
|
+
|
|
1229
|
+
const $stores = new Map();
|
|
1230
|
+
const $followersCache = new Map();
|
|
962
1231
|
|
|
963
1232
|
/**
|
|
964
|
-
*
|
|
965
|
-
* @param {boolean} inDom
|
|
966
|
-
* @returns {{ disconnect: Function, mounted: Function, unmounted: Function, off: Function }}
|
|
1233
|
+
* Internal helper — retrieves a store entry or throws if not found.
|
|
967
1234
|
*/
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1235
|
+
const $getStoreOrThrow = (method, name) => {
|
|
1236
|
+
const item = $stores.get(name);
|
|
1237
|
+
if (!item) {
|
|
1238
|
+
DebugManager.error('Store', `Store.${method}('${name}') : store not found. Did you call Store.create('${name}') first?`);
|
|
1239
|
+
throw new NativeDocumentError(
|
|
1240
|
+
`Store.${method}('${name}') : store not found.`
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
return item;
|
|
1244
|
+
};
|
|
971
1245
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
if (unmountedRegistered) {
|
|
982
|
-
DocumentObserver.unmounted.delete(element);
|
|
983
|
-
DocumentObserver.unmountedSupposedSize--;
|
|
984
|
-
}
|
|
985
|
-
data = null;
|
|
986
|
-
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Internal helper — blocks write operations on a read-only observer.
|
|
1248
|
+
*/
|
|
1249
|
+
const $applyReadOnly = (observer, name, context) => {
|
|
1250
|
+
const readOnlyError = (method) => () => {
|
|
1251
|
+
DebugManager.error('Store', `Store.${context}('${name}') is read-only. '${method}()' is not allowed.`);
|
|
1252
|
+
throw new NativeDocumentError(
|
|
1253
|
+
`Store.${context}('${name}') is read-only.`
|
|
1254
|
+
);
|
|
987
1255
|
};
|
|
1256
|
+
observer.set = readOnlyError('set');
|
|
1257
|
+
observer.toggle = readOnlyError('toggle');
|
|
1258
|
+
observer.reset = readOnlyError('reset');
|
|
1259
|
+
};
|
|
988
1260
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1261
|
+
const $createObservable = (value, options = {}) => {
|
|
1262
|
+
if(Array.isArray(value)) {
|
|
1263
|
+
return Observable$1.array(value, options);
|
|
1264
|
+
}
|
|
1265
|
+
if(typeof value === 'object') {
|
|
1266
|
+
return Observable$1.object(value, options);
|
|
1267
|
+
}
|
|
1268
|
+
return Observable$1(value, options);
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
const $api = {
|
|
1272
|
+
/**
|
|
1273
|
+
* Create a new state and return the observer.
|
|
1274
|
+
* Throws if a store with the same name already exists.
|
|
1275
|
+
*
|
|
1276
|
+
* @param {string} name
|
|
1277
|
+
* @param {*} value
|
|
1278
|
+
* @returns {ObservableItem}
|
|
1279
|
+
*/
|
|
1280
|
+
create(name, value) {
|
|
1281
|
+
if ($stores.has(name)) {
|
|
1282
|
+
DebugManager.warn('Store', `Store.create('${name}') : a store with this name already exists. Use Store.get('${name}') to retrieve it.`);
|
|
1283
|
+
throw new NativeDocumentError(
|
|
1284
|
+
`Store.create('${name}') : a store with this name already exists.`
|
|
1285
|
+
);
|
|
993
1286
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1287
|
+
const observer = $createObservable(value);
|
|
1288
|
+
$stores.set(name, { observer, subscribers: new Set(), resettable: false, composed: false });
|
|
1289
|
+
return observer;
|
|
1290
|
+
},
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* Create a new resettable state and return the observer.
|
|
1294
|
+
* The store can be reset to its initial value via Store.reset(name).
|
|
1295
|
+
* Throws if a store with the same name already exists.
|
|
1296
|
+
*
|
|
1297
|
+
* @param {string} name
|
|
1298
|
+
* @param {*} value
|
|
1299
|
+
* @returns {ObservableItem}
|
|
1300
|
+
*/
|
|
1301
|
+
createResettable(name, value) {
|
|
1302
|
+
if ($stores.has(name)) {
|
|
1303
|
+
DebugManager.warn('Store', `Store.createResettable('${name}') : a store with this name already exists.`);
|
|
1304
|
+
throw new NativeDocumentError(
|
|
1305
|
+
`Store.createResettable('${name}') : a store with this name already exists.`
|
|
1306
|
+
);
|
|
997
1307
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1308
|
+
const observer = $createObservable(value, { reset: true });
|
|
1309
|
+
$stores.set(name, { observer, subscribers: new Set(), resettable: true, composed: false });
|
|
1310
|
+
return observer;
|
|
1311
|
+
},
|
|
1000
1312
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1313
|
+
/**
|
|
1314
|
+
* Create a computed store derived from other stores.
|
|
1315
|
+
* The value is automatically recalculated when any dependency changes.
|
|
1316
|
+
* This store is read-only — Store.use() and Store.set() will throw.
|
|
1317
|
+
* Throws if a store with the same name already exists.
|
|
1318
|
+
*
|
|
1319
|
+
* @param {string} name
|
|
1320
|
+
* @param {() => *} computation - Function that returns the computed value
|
|
1321
|
+
* @param {string[]} dependencies - Names of the stores to watch
|
|
1322
|
+
* @returns {ObservableItem}
|
|
1323
|
+
*
|
|
1324
|
+
* @example
|
|
1325
|
+
* Store.create('products', [{ id: 1, price: 10 }]);
|
|
1326
|
+
* Store.create('cart', [{ productId: 1, quantity: 2 }]);
|
|
1327
|
+
*
|
|
1328
|
+
* Store.createComposed('total', () => {
|
|
1329
|
+
* const products = Store.get('products').val();
|
|
1330
|
+
* const cart = Store.get('cart').val();
|
|
1331
|
+
* return cart.reduce((sum, item) => {
|
|
1332
|
+
* const product = products.find(p => p.id === item.productId);
|
|
1333
|
+
* return sum + (product.price * item.quantity);
|
|
1334
|
+
* }, 0);
|
|
1335
|
+
* }, ['products', 'cart']);
|
|
1336
|
+
*/
|
|
1337
|
+
createComposed(name, computation, dependencies) {
|
|
1338
|
+
if ($stores.has(name)) {
|
|
1339
|
+
DebugManager.warn('Store', `Store.createComposed('${name}') : a store with this name already exists.`);
|
|
1340
|
+
throw new NativeDocumentError(
|
|
1341
|
+
`Store.createComposed('${name}') : a store with this name already exists.`
|
|
1342
|
+
);
|
|
1004
1343
|
}
|
|
1005
|
-
if(
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1344
|
+
if (typeof computation !== 'function') {
|
|
1345
|
+
throw new NativeDocumentError(
|
|
1346
|
+
`Store.createComposed('${name}') : computation must be a function.`
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
if (!Array.isArray(dependencies) || dependencies.length === 0) {
|
|
1350
|
+
throw new NativeDocumentError(
|
|
1351
|
+
`Store.createComposed('${name}') : dependencies must be a non-empty array of store names.`
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// Resolve dependency observers
|
|
1356
|
+
const depObservers = dependencies.map(depName => {
|
|
1357
|
+
if(typeof depName !== 'string') {
|
|
1358
|
+
return depName;
|
|
1012
1359
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1360
|
+
const depItem = $stores.get(depName);
|
|
1361
|
+
if (!depItem) {
|
|
1362
|
+
DebugManager.error('Store', `Store.createComposed('${name}') : dependency '${depName}' not found. Create it first.`);
|
|
1363
|
+
throw new NativeDocumentError(
|
|
1364
|
+
`Store.createComposed('${name}') : dependency store '${depName}' not found.`
|
|
1365
|
+
);
|
|
1015
1366
|
}
|
|
1016
|
-
return;
|
|
1017
|
-
}
|
|
1018
|
-
data[type] = null;
|
|
1019
|
-
};
|
|
1367
|
+
return depItem.observer;
|
|
1368
|
+
});
|
|
1020
1369
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1370
|
+
// Create computed observable from dependency observers
|
|
1371
|
+
const observer = Observable$1.computed(computation, depObservers);
|
|
1023
1372
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
if (!mountedRegistered) {
|
|
1028
|
-
DocumentObserver.mountedSupposedSize++;
|
|
1029
|
-
mountedRegistered = true;
|
|
1030
|
-
}
|
|
1031
|
-
},
|
|
1373
|
+
$stores.set(name, { observer, subscribers: new Set(), resettable: false, composed: true });
|
|
1374
|
+
return observer;
|
|
1375
|
+
},
|
|
1032
1376
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1377
|
+
/**
|
|
1378
|
+
* Returns true if a store with the given name exists.
|
|
1379
|
+
*
|
|
1380
|
+
* @param {string} name
|
|
1381
|
+
* @returns {boolean}
|
|
1382
|
+
*/
|
|
1383
|
+
has(name) {
|
|
1384
|
+
return $stores.has(name);
|
|
1385
|
+
},
|
|
1041
1386
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1387
|
+
/**
|
|
1388
|
+
* Resets a resettable store to its initial value and notifies all subscribers.
|
|
1389
|
+
* Throws if the store was not created with createResettable().
|
|
1390
|
+
*
|
|
1391
|
+
* @param {string} name
|
|
1392
|
+
*/
|
|
1393
|
+
reset(name) {
|
|
1394
|
+
const item = $getStoreOrThrow('reset', name);
|
|
1395
|
+
if (item.composed) {
|
|
1396
|
+
DebugManager.error('Store', `Store.reset('${name}') : composed stores cannot be reset. Their value is derived from dependencies.`);
|
|
1397
|
+
throw new NativeDocumentError(
|
|
1398
|
+
`Store.reset('${name}') : composed stores cannot be reset.`
|
|
1399
|
+
);
|
|
1044
1400
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
});
|
|
1401
|
+
if (!item.resettable) {
|
|
1402
|
+
DebugManager.error('Store', `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`);
|
|
1403
|
+
throw new NativeDocumentError(
|
|
1404
|
+
`Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
item.observer.reset();
|
|
1408
|
+
},
|
|
1054
1409
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1410
|
+
/**
|
|
1411
|
+
* Returns a two-way synchronized follower of the store.
|
|
1412
|
+
* Writing to the follower propagates the value back to the store and all its subscribers.
|
|
1413
|
+
* Throws if called on a composed store — use Store.follow() instead.
|
|
1414
|
+
* Call follower.destroy() or follower.dispose() to unsubscribe.
|
|
1415
|
+
*
|
|
1416
|
+
* @param {string} name
|
|
1417
|
+
* @returns {ObservableItem}
|
|
1418
|
+
*/
|
|
1419
|
+
use(name) {
|
|
1420
|
+
const item = $getStoreOrThrow('use', name);
|
|
1062
1421
|
|
|
1063
|
-
|
|
1422
|
+
if (item.composed) {
|
|
1423
|
+
DebugManager.error('Store', `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`);
|
|
1424
|
+
throw new NativeDocumentError(
|
|
1425
|
+
`Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1064
1428
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
};
|
|
1429
|
+
const { observer: originalObserver, subscribers } = item;
|
|
1430
|
+
const observerFollower = $createObservable(originalObserver.val());
|
|
1068
1431
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
return this;
|
|
1072
|
-
};
|
|
1432
|
+
const onStoreChange = value => observerFollower.set(value);
|
|
1433
|
+
const onFollowerChange = value => originalObserver.set(value);
|
|
1073
1434
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
return this;
|
|
1077
|
-
};
|
|
1435
|
+
originalObserver.subscribe(onStoreChange);
|
|
1436
|
+
observerFollower.subscribe(onFollowerChange);
|
|
1078
1437
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
elementChildren = null;
|
|
1087
|
-
}
|
|
1088
|
-
element = null;
|
|
1089
|
-
return this;
|
|
1090
|
-
};
|
|
1438
|
+
observerFollower.destroy = () => {
|
|
1439
|
+
originalObserver.unsubscribe(onStoreChange);
|
|
1440
|
+
observerFollower.unsubscribe(onFollowerChange);
|
|
1441
|
+
subscribers.delete(observerFollower);
|
|
1442
|
+
observerFollower.cleanup();
|
|
1443
|
+
};
|
|
1444
|
+
observerFollower.dispose = observerFollower.destroy;
|
|
1091
1445
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
element.$ndProx = null;
|
|
1096
|
-
delete element.nd?.on?.prevent;
|
|
1097
|
-
delete element.nd?.on;
|
|
1098
|
-
delete element.nd;
|
|
1099
|
-
element = null;
|
|
1100
|
-
return this;
|
|
1101
|
-
};
|
|
1446
|
+
subscribers.add(observerFollower);
|
|
1447
|
+
return observerFollower;
|
|
1448
|
+
},
|
|
1102
1449
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1450
|
+
/**
|
|
1451
|
+
* Returns a read-only follower of the store.
|
|
1452
|
+
* The follower reflects store changes but cannot write back to the store.
|
|
1453
|
+
* Any attempt to call .set(), .toggle() or .reset() will throw.
|
|
1454
|
+
* Call follower.destroy() or follower.dispose() to unsubscribe.
|
|
1455
|
+
*
|
|
1456
|
+
* @param {string} name
|
|
1457
|
+
* @returns {ObservableItem}
|
|
1458
|
+
*/
|
|
1459
|
+
follow(name) {
|
|
1460
|
+
const { observer: originalObserver, subscribers } = $getStoreOrThrow('follow', name);
|
|
1461
|
+
const observerFollower = $createObservable(originalObserver.val());
|
|
1105
1462
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
this.$observer.mounted(states.mounted);
|
|
1109
|
-
}
|
|
1110
|
-
if(states.unmounted) {
|
|
1111
|
-
this.$element.setAttribute('data--nd-unmounted', '1');
|
|
1112
|
-
this.$observer.unmounted(states.unmounted);
|
|
1113
|
-
}
|
|
1114
|
-
return this;
|
|
1115
|
-
};
|
|
1463
|
+
const onStoreChange = value => observerFollower.set(value);
|
|
1464
|
+
originalObserver.subscribe(onStoreChange);
|
|
1116
1465
|
|
|
1117
|
-
|
|
1118
|
-
return this.lifecycle({ mounted: callback });
|
|
1119
|
-
};
|
|
1466
|
+
$applyReadOnly(observerFollower, name, 'follow');
|
|
1120
1467
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1468
|
+
observerFollower.destroy = () => {
|
|
1469
|
+
originalObserver.unsubscribe(onStoreChange);
|
|
1470
|
+
subscribers.delete(observerFollower);
|
|
1471
|
+
observerFollower.cleanup();
|
|
1472
|
+
};
|
|
1473
|
+
observerFollower.dispose = observerFollower.destroy;
|
|
1124
1474
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1475
|
+
subscribers.add(observerFollower);
|
|
1476
|
+
return observerFollower;
|
|
1477
|
+
},
|
|
1127
1478
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1479
|
+
/**
|
|
1480
|
+
* Returns the raw store observer directly (no follower, no cleanup contract).
|
|
1481
|
+
* Use this for direct read access when you don't need to unsubscribe.
|
|
1482
|
+
* WARNING : mutations on this observer impact all subscribers immediately.
|
|
1483
|
+
*
|
|
1484
|
+
* @param {string} name
|
|
1485
|
+
* @returns {ObservableItem|null}
|
|
1486
|
+
*/
|
|
1487
|
+
get(name) {
|
|
1488
|
+
const item = $stores.get(name);
|
|
1489
|
+
if (!item) {
|
|
1490
|
+
DebugManager.warn('Store', `Store.get('${name}') : store not found.`);
|
|
1491
|
+
return null;
|
|
1492
|
+
}
|
|
1493
|
+
return item.observer;
|
|
1494
|
+
},
|
|
1131
1495
|
|
|
1132
|
-
|
|
1496
|
+
/**
|
|
1497
|
+
* @param {string} name
|
|
1498
|
+
* @returns {{ observer: ObservableItem, subscribers: Set } | null}
|
|
1499
|
+
*/
|
|
1500
|
+
getWithSubscribers(name) {
|
|
1501
|
+
return $stores.get(name) ?? null;
|
|
1502
|
+
},
|
|
1133
1503
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1504
|
+
/**
|
|
1505
|
+
* Destroys a store : cleans up the observer, destroys all followers, and removes the entry.
|
|
1506
|
+
*
|
|
1507
|
+
* @param {string} name
|
|
1508
|
+
*/
|
|
1509
|
+
delete(name) {
|
|
1510
|
+
const item = $stores.get(name);
|
|
1511
|
+
if (!item) {
|
|
1512
|
+
DebugManager.warn('Store', `Store.delete('${name}') : store not found, nothing to delete.`);
|
|
1136
1513
|
return;
|
|
1137
1514
|
}
|
|
1138
|
-
|
|
1515
|
+
item.subscribers.forEach(follower => follower.destroy());
|
|
1516
|
+
item.subscribers.clear();
|
|
1517
|
+
item.observer.cleanup();
|
|
1518
|
+
$stores.delete(name);
|
|
1519
|
+
},
|
|
1520
|
+
/**
|
|
1521
|
+
* Creates an isolated store group with its own state namespace.
|
|
1522
|
+
* Each group is a fully independent StoreFactory instance —
|
|
1523
|
+
* no key conflicts, no shared state with the parent store.
|
|
1524
|
+
*
|
|
1525
|
+
* @param {string | ((group: ReturnType<typeof StoreFactory>) => void)} name - Group name for debugging, or setup callback if no name is provided
|
|
1526
|
+
* @param {((group: ReturnType<typeof StoreFactory>) => void)} [callback] - Setup function receiving the isolated store instance
|
|
1527
|
+
* @returns {ReturnType<typeof StoreFactory>}
|
|
1528
|
+
*
|
|
1529
|
+
* @example
|
|
1530
|
+
* // With name (recommended)
|
|
1531
|
+
* const EventStore = Store.group('events', (group) => {
|
|
1532
|
+
* group.create('catalog', []);
|
|
1533
|
+
* group.create('filters', { category: null, date: null });
|
|
1534
|
+
* group.createResettable('selected', null);
|
|
1535
|
+
* group.createComposed('filtered', () => {
|
|
1536
|
+
* const catalog = EventStore.get('catalog').val();
|
|
1537
|
+
* const filters = EventStore.get('filters').val();
|
|
1538
|
+
* return catalog.filter(event => {
|
|
1539
|
+
* if (filters.category && event.category !== filters.category) return false;
|
|
1540
|
+
* return true;
|
|
1541
|
+
* });
|
|
1542
|
+
* }, ['catalog', 'filters']);
|
|
1543
|
+
* });
|
|
1544
|
+
*
|
|
1545
|
+
* // Without name
|
|
1546
|
+
* const CartStore = Store.group((group) => {
|
|
1547
|
+
* group.create('items', []);
|
|
1548
|
+
* });
|
|
1549
|
+
*
|
|
1550
|
+
* // Usage
|
|
1551
|
+
* EventStore.use('catalog'); // two-way follower
|
|
1552
|
+
* EventStore.follow('filtered'); // read-only follower
|
|
1553
|
+
* EventStore.get('filters'); // raw observable
|
|
1554
|
+
*
|
|
1555
|
+
* // Cross-group composed
|
|
1556
|
+
* const OrderStore = Store.group('orders', (group) => {
|
|
1557
|
+
* group.createComposed('summary', () => {
|
|
1558
|
+
* const items = CartStore.get('items').val();
|
|
1559
|
+
* const events = EventStore.get('catalog').val();
|
|
1560
|
+
* return { items, events };
|
|
1561
|
+
* }, [CartStore.get('items'), EventStore.get('catalog')]);
|
|
1562
|
+
* });
|
|
1563
|
+
*/
|
|
1564
|
+
group(name, callback) {
|
|
1565
|
+
if (typeof name === 'function') {
|
|
1566
|
+
callback = name;
|
|
1567
|
+
name = 'anonymous';
|
|
1568
|
+
}
|
|
1569
|
+
const store = StoreFactory();
|
|
1570
|
+
callback && callback(store);
|
|
1571
|
+
return store;
|
|
1572
|
+
},
|
|
1573
|
+
createPersistent(name, value, localstorage_key) {
|
|
1574
|
+
localstorage_key = localstorage_key || name;
|
|
1575
|
+
const observer = this.create(name, $getFromStorage$1(localstorage_key, value));
|
|
1576
|
+
const saver = $saveToStorage$1(value);
|
|
1139
1577
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1578
|
+
observer.subscribe((val) => saver(localstorage_key, val));
|
|
1579
|
+
return observer;
|
|
1580
|
+
},
|
|
1581
|
+
createPersistentResettable(name, value, localstorage_key) {
|
|
1582
|
+
localstorage_key = localstorage_key || name;
|
|
1583
|
+
const observer = this.createResettable(name, $getFromStorage$1(localstorage_key, value));
|
|
1584
|
+
const saver = $saveToStorage$1(value);
|
|
1585
|
+
observer.subscribe((val) => saver(localstorage_key, val));
|
|
1586
|
+
|
|
1587
|
+
const originalReset = observer.reset.bind(observer);
|
|
1588
|
+
observer.reset = () => {
|
|
1589
|
+
LocalStorage$1.remove(localstorage_key);
|
|
1590
|
+
originalReset();
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1593
|
+
return observer;
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
|
|
1598
|
+
return new Proxy($api, {
|
|
1599
|
+
get(target, prop) {
|
|
1600
|
+
if (typeof prop === 'symbol' || prop.startsWith('$') || prop in target) {
|
|
1601
|
+
return target[prop];
|
|
1602
|
+
}
|
|
1603
|
+
if (target.has(prop)) {
|
|
1604
|
+
if ($followersCache.has(prop)) {
|
|
1605
|
+
return $followersCache.get(prop);
|
|
1144
1606
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1607
|
+
const follower = target.follow(prop);
|
|
1608
|
+
$followersCache.set(prop, follower);
|
|
1609
|
+
return follower;
|
|
1148
1610
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1611
|
+
return undefined;
|
|
1612
|
+
},
|
|
1613
|
+
set(target, prop, value) {
|
|
1614
|
+
DebugManager.error('Store', `Forbidden: You cannot overwrite the store key '${String(prop)}'. Use .use('${String(prop)}').set(value) instead.`);
|
|
1615
|
+
throw new NativeDocumentError(`Store structure is immutable. Use .set() on the observable.`);
|
|
1616
|
+
},
|
|
1617
|
+
deleteProperty(target, prop) {
|
|
1618
|
+
throw new NativeDocumentError(`Store keys cannot be deleted.`);
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1154
1621
|
};
|
|
1155
1622
|
|
|
1156
|
-
|
|
1157
|
-
return this.$element;
|
|
1158
|
-
};
|
|
1623
|
+
const Store = StoreFactory();
|
|
1159
1624
|
|
|
1160
|
-
|
|
1625
|
+
Store.create('locale', 'fr');
|
|
1161
1626
|
|
|
1162
|
-
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1627
|
+
const $parseDateParts = (value, locale) => {
|
|
1628
|
+
const d = new Date(value);
|
|
1629
|
+
return {
|
|
1630
|
+
d,
|
|
1631
|
+
parts: new Intl.DateTimeFormat(locale, {
|
|
1632
|
+
year: 'numeric',
|
|
1633
|
+
month: 'long',
|
|
1634
|
+
day: '2-digit',
|
|
1635
|
+
hour: '2-digit',
|
|
1636
|
+
minute: '2-digit',
|
|
1637
|
+
second: '2-digit',
|
|
1638
|
+
}).formatToParts(d).reduce((acc, { type, value }) => {
|
|
1639
|
+
acc[type] = value;
|
|
1640
|
+
return acc;
|
|
1641
|
+
}, {})
|
|
1642
|
+
};
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1645
|
+
const $applyDatePattern = (pattern, d, parts) => {
|
|
1646
|
+
const pad = n => String(n).padStart(2, '0');
|
|
1647
|
+
return pattern
|
|
1648
|
+
.replace('YYYY', parts.year)
|
|
1649
|
+
.replace('YY', parts.year.slice(-2))
|
|
1650
|
+
.replace('MMMM', parts.month)
|
|
1651
|
+
.replace('MMM', parts.month.slice(0, 3))
|
|
1652
|
+
.replace('MM', pad(d.getMonth() + 1))
|
|
1653
|
+
.replace('DD', pad(d.getDate()))
|
|
1654
|
+
.replace('D', d.getDate())
|
|
1655
|
+
.replace('HH', parts.hour)
|
|
1656
|
+
.replace('mm', parts.minute)
|
|
1657
|
+
.replace('ss', parts.second);
|
|
1658
|
+
};
|
|
1659
|
+
|
|
1660
|
+
const Formatters = {
|
|
1661
|
+
|
|
1662
|
+
currency: (value, locale, { currency = 'XOF', notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
|
|
1663
|
+
new Intl.NumberFormat(locale, {
|
|
1664
|
+
style: 'currency',
|
|
1665
|
+
currency,
|
|
1666
|
+
notation,
|
|
1667
|
+
minimumFractionDigits,
|
|
1668
|
+
maximumFractionDigits
|
|
1669
|
+
}).format(value),
|
|
1670
|
+
|
|
1671
|
+
number: (value, locale, { notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
|
|
1672
|
+
new Intl.NumberFormat(locale, {
|
|
1673
|
+
notation,
|
|
1674
|
+
minimumFractionDigits,
|
|
1675
|
+
maximumFractionDigits
|
|
1676
|
+
}).format(value),
|
|
1677
|
+
|
|
1678
|
+
percent: (value, locale, { decimals = 1 } = {}) =>
|
|
1679
|
+
new Intl.NumberFormat(locale, {
|
|
1680
|
+
style: 'percent',
|
|
1681
|
+
maximumFractionDigits: decimals
|
|
1682
|
+
}).format(value),
|
|
1683
|
+
|
|
1684
|
+
date: (value, locale, { format, dateStyle = 'long' } = {}) => {
|
|
1685
|
+
if (format) {
|
|
1686
|
+
const { d, parts } = $parseDateParts(value, locale);
|
|
1687
|
+
return $applyDatePattern(format, d, parts);
|
|
1688
|
+
}
|
|
1689
|
+
return new Intl.DateTimeFormat(locale, { dateStyle }).format(new Date(value));
|
|
1690
|
+
},
|
|
1174
1691
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1692
|
+
time: (value, locale, { format, hour = '2-digit', minute = '2-digit', second } = {}) => {
|
|
1693
|
+
if (format) {
|
|
1694
|
+
const { d, parts } = $parseDateParts(value, locale);
|
|
1695
|
+
return $applyDatePattern(format, d, parts);
|
|
1696
|
+
}
|
|
1697
|
+
return new Intl.DateTimeFormat(locale, { hour, minute, second }).format(new Date(value));
|
|
1698
|
+
},
|
|
1177
1699
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1700
|
+
datetime: (value, locale, { format, dateStyle = 'long', hour = '2-digit', minute = '2-digit', second } = {}) => {
|
|
1701
|
+
if (format) {
|
|
1702
|
+
const { d, parts } = $parseDateParts(value, locale);
|
|
1703
|
+
return $applyDatePattern(format, d, parts);
|
|
1704
|
+
}
|
|
1705
|
+
return new Intl.DateTimeFormat(locale, { dateStyle, hour, minute, second }).format(new Date(value));
|
|
1706
|
+
},
|
|
1181
1707
|
|
|
1182
|
-
|
|
1183
|
-
|
|
1708
|
+
relative: (value, locale, { unit = 'day', numeric = 'auto' } = {}) => {
|
|
1709
|
+
const diff = Math.round((value - Date.now()) / (1000 * 60 * 60 * 24));
|
|
1710
|
+
return new Intl.RelativeTimeFormat(locale, { numeric }).format(diff, unit);
|
|
1711
|
+
},
|
|
1712
|
+
|
|
1713
|
+
plural: (value, locale, { singular, plural } = {}) => {
|
|
1714
|
+
const rule = new Intl.PluralRules(locale).select(value);
|
|
1715
|
+
return `${value} ${rule === 'one' ? singular : plural}`;
|
|
1716
|
+
},
|
|
1184
1717
|
};
|
|
1185
1718
|
|
|
1186
1719
|
/**
|
|
1187
|
-
* Attaches a template binding to the element by hydrating it with the specified method.
|
|
1188
1720
|
*
|
|
1189
|
-
* @param {
|
|
1190
|
-
* @param {
|
|
1191
|
-
* @
|
|
1192
|
-
* @example
|
|
1193
|
-
* const onClick = $binder.attach((event, data) => console.log(data));
|
|
1194
|
-
* element.nd.attach('onClick', onClick);
|
|
1721
|
+
* @param {*} value
|
|
1722
|
+
* @param {{ propagation: boolean, reset: boolean} | null} configs
|
|
1723
|
+
* @class ObservableItem
|
|
1195
1724
|
*/
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
return this.$element;
|
|
1199
|
-
};
|
|
1725
|
+
function ObservableItem(value, configs = null) {
|
|
1726
|
+
value = Validator.isObservable(value) ? value.val() : value;
|
|
1200
1727
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
* Methods are bound to the instance and available for chaining.
|
|
1204
|
-
*
|
|
1205
|
-
* @param {Object} methods - Object containing method definitions
|
|
1206
|
-
* @returns {this} The NDElement instance with added methods for chaining
|
|
1207
|
-
* @example
|
|
1208
|
-
* element.nd.with({
|
|
1209
|
-
* highlight() {
|
|
1210
|
-
* this.$element.style.background = 'yellow';
|
|
1211
|
-
* return this;
|
|
1212
|
-
* }
|
|
1213
|
-
* }).highlight().onClick(() => console.log('Clicked'));
|
|
1214
|
-
*/
|
|
1215
|
-
NDElement.prototype.with = function(methods) {
|
|
1216
|
-
if (!methods || typeof methods !== 'object') {
|
|
1217
|
-
throw new NativeDocumentError('extend() requires an object of methods');
|
|
1218
|
-
}
|
|
1728
|
+
this.$previousValue = null;
|
|
1729
|
+
this.$currentValue = value;
|
|
1219
1730
|
{
|
|
1220
|
-
|
|
1221
|
-
this.$localExtensions = new Map();
|
|
1222
|
-
}
|
|
1731
|
+
this.$isCleanedUp = false;
|
|
1223
1732
|
}
|
|
1224
1733
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1734
|
+
this.$firstListener = null;
|
|
1735
|
+
this.$listeners = null;
|
|
1736
|
+
this.$watchers = null;
|
|
1227
1737
|
|
|
1228
|
-
|
|
1229
|
-
console.warn(`⚠️ extends(): "${name}" is not a function, skipping`);
|
|
1230
|
-
continue;
|
|
1231
|
-
}
|
|
1232
|
-
{
|
|
1233
|
-
if (this[name] && !this.$localExtensions.has(name)) {
|
|
1234
|
-
DebugManager.warn('NDElement.extend', `Method "${name}" already exists and will be overwritten`);
|
|
1235
|
-
}
|
|
1236
|
-
this.$localExtensions.set(name, method);
|
|
1237
|
-
}
|
|
1738
|
+
this.$memoryId = null;
|
|
1238
1739
|
|
|
1239
|
-
|
|
1740
|
+
if(configs) {
|
|
1741
|
+
this.configs = configs;
|
|
1742
|
+
if(configs.reset) {
|
|
1743
|
+
this.$initialValue = Validator.isObject(value) ? deepClone(value) : value;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
{
|
|
1747
|
+
PluginsManager.emit('CreateObservable', this);
|
|
1240
1748
|
}
|
|
1749
|
+
}
|
|
1241
1750
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1751
|
+
Object.defineProperty(ObservableItem.prototype, '$value', {
|
|
1752
|
+
get() {
|
|
1753
|
+
return this.$currentValue;
|
|
1754
|
+
},
|
|
1755
|
+
set(value) {
|
|
1756
|
+
this.set(value);
|
|
1757
|
+
},
|
|
1758
|
+
configurable: true,
|
|
1759
|
+
});
|
|
1760
|
+
|
|
1761
|
+
ObservableItem.prototype.__$isObservable = true;
|
|
1762
|
+
const noneTrigger = function() {};
|
|
1244
1763
|
|
|
1245
1764
|
/**
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1765
|
+
* Intercepts and transforms values before they are set on the observable.
|
|
1766
|
+
* The interceptor can modify the value or return undefined to use the original value.
|
|
1248
1767
|
*
|
|
1249
|
-
* @param {
|
|
1250
|
-
* @returns {
|
|
1251
|
-
* @throws {NativeDocumentError} If methods is not an object or contains non-function values
|
|
1768
|
+
* @param {(value) => any} callback - Interceptor function that receives (newValue, currentValue) and returns the transformed value or undefined
|
|
1769
|
+
* @returns {ObservableItem} The observable instance for chaining
|
|
1252
1770
|
* @example
|
|
1253
|
-
*
|
|
1254
|
-
*
|
|
1255
|
-
* this.$element.style.opacity = '1';
|
|
1256
|
-
* return this;
|
|
1257
|
-
* }
|
|
1258
|
-
* });
|
|
1259
|
-
* // Now all NDElements have .fadeIn() method
|
|
1260
|
-
* Div().nd.fadeIn();
|
|
1771
|
+
* const count = Observable(0);
|
|
1772
|
+
* count.intercept((newVal, oldVal) => Math.max(0, newVal)); // Prevent negative values
|
|
1261
1773
|
*/
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1774
|
+
ObservableItem.prototype.intercept = function(callback) {
|
|
1775
|
+
this.$interceptor = callback;
|
|
1776
|
+
this.set = this.$setWithInterceptor;
|
|
1777
|
+
return this;
|
|
1778
|
+
};
|
|
1266
1779
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1780
|
+
ObservableItem.prototype.triggerFirstListener = function(operations) {
|
|
1781
|
+
this.$firstListener(this.$currentValue, this.$previousValue, operations);
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1784
|
+
ObservableItem.prototype.triggerListeners = function(operations) {
|
|
1785
|
+
const $listeners = this.$listeners;
|
|
1786
|
+
const $previousValue = this.$previousValue;
|
|
1787
|
+
const $currentValue = this.$currentValue;
|
|
1788
|
+
|
|
1789
|
+
for(let i = 0, length = $listeners.length; i < length; i++) {
|
|
1790
|
+
$listeners[i]($currentValue, $previousValue, operations);
|
|
1269
1791
|
}
|
|
1792
|
+
};
|
|
1270
1793
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
]);
|
|
1794
|
+
ObservableItem.prototype.triggerWatchers = function(operations) {
|
|
1795
|
+
const $watchers = this.$watchers;
|
|
1796
|
+
const $previousValue = this.$previousValue;
|
|
1797
|
+
const $currentValue = this.$currentValue;
|
|
1276
1798
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1799
|
+
const $currentValueCallbacks = $watchers.get($currentValue);
|
|
1800
|
+
const $previousValueCallbacks = $watchers.get($previousValue);
|
|
1801
|
+
if($currentValueCallbacks) {
|
|
1802
|
+
$currentValueCallbacks(true, $previousValue, operations);
|
|
1803
|
+
}
|
|
1804
|
+
if($previousValueCallbacks) {
|
|
1805
|
+
$previousValueCallbacks(false, $currentValue, operations);
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1281
1808
|
|
|
1282
|
-
|
|
1809
|
+
ObservableItem.prototype.triggerAll = function(operations) {
|
|
1810
|
+
this.triggerWatchers(operations);
|
|
1811
|
+
this.triggerListeners(operations);
|
|
1812
|
+
};
|
|
1283
1813
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1814
|
+
ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations) {
|
|
1815
|
+
this.triggerWatchers(operations);
|
|
1816
|
+
this.triggerFirstListener(operations);
|
|
1817
|
+
};
|
|
1288
1818
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1819
|
+
ObservableItem.prototype.assocTrigger = function() {
|
|
1820
|
+
this.$firstListener = null;
|
|
1821
|
+
if(this.$watchers?.size && this.$listeners?.length) {
|
|
1822
|
+
this.trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
if(this.$listeners?.length) {
|
|
1826
|
+
if(this.$listeners.length === 1) {
|
|
1827
|
+
this.$firstListener = this.$listeners[0];
|
|
1828
|
+
this.trigger = this.triggerFirstListener;
|
|
1292
1829
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
DebugManager.warn('NDElement.extend', `Overwriting existing prototype method "${name}"`);
|
|
1830
|
+
else {
|
|
1831
|
+
this.trigger = this.triggerListeners;
|
|
1296
1832
|
}
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
if(this.$watchers?.size) {
|
|
1836
|
+
this.trigger = this.triggerWatchers;
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
this.trigger = noneTrigger;
|
|
1840
|
+
};
|
|
1841
|
+
ObservableItem.prototype.trigger = noneTrigger;
|
|
1297
1842
|
|
|
1298
|
-
|
|
1843
|
+
ObservableItem.prototype.$updateWithNewValue = function(newValue) {
|
|
1844
|
+
newValue = newValue?.__$isObservable ? newValue.val() : newValue;
|
|
1845
|
+
if(this.$currentValue === newValue) {
|
|
1846
|
+
return;
|
|
1299
1847
|
}
|
|
1848
|
+
this.$previousValue = this.$currentValue;
|
|
1849
|
+
this.$currentValue = newValue;
|
|
1300
1850
|
{
|
|
1301
|
-
PluginsManager.emit('
|
|
1851
|
+
PluginsManager.emit('ObservableBeforeChange', this);
|
|
1852
|
+
}
|
|
1853
|
+
this.trigger();
|
|
1854
|
+
this.$previousValue = null;
|
|
1855
|
+
{
|
|
1856
|
+
PluginsManager.emit('ObservableAfterChange', this);
|
|
1302
1857
|
}
|
|
1858
|
+
};
|
|
1303
1859
|
|
|
1304
|
-
|
|
1860
|
+
/**
|
|
1861
|
+
* @param {*} data
|
|
1862
|
+
*/
|
|
1863
|
+
ObservableItem.prototype.$setWithInterceptor = function(data) {
|
|
1864
|
+
let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
|
|
1865
|
+
const result = this.$interceptor(newValue, this.$currentValue);
|
|
1866
|
+
|
|
1867
|
+
if (result !== undefined) {
|
|
1868
|
+
newValue = result;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
this.$updateWithNewValue(newValue);
|
|
1305
1872
|
};
|
|
1306
1873
|
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1874
|
+
/**
|
|
1875
|
+
* @param {*} data
|
|
1876
|
+
*/
|
|
1877
|
+
ObservableItem.prototype.$basicSet = function(data) {
|
|
1878
|
+
let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
|
|
1879
|
+
this.$updateWithNewValue(newValue);
|
|
1880
|
+
};
|
|
1310
1881
|
|
|
1311
|
-
|
|
1882
|
+
ObservableItem.prototype.set = ObservableItem.prototype.$basicSet;
|
|
1312
1883
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
TEXT: 3,
|
|
1316
|
-
COMMENT: 8,
|
|
1317
|
-
DOCUMENT_FRAGMENT: 11
|
|
1884
|
+
ObservableItem.prototype.val = function() {
|
|
1885
|
+
return this.$currentValue;
|
|
1318
1886
|
};
|
|
1319
1887
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
},
|
|
1330
|
-
isArrayObservable(value) {
|
|
1331
|
-
return value?.__$isObservableArray;
|
|
1332
|
-
},
|
|
1333
|
-
isProxy(value) {
|
|
1334
|
-
return value?.__isProxy__
|
|
1335
|
-
},
|
|
1336
|
-
isObservableOrProxy(value) {
|
|
1337
|
-
return Validator.isObservable(value) || Validator.isProxy(value);
|
|
1338
|
-
},
|
|
1339
|
-
isAnchor(value) {
|
|
1340
|
-
return value?.__Anchor__
|
|
1341
|
-
},
|
|
1342
|
-
isObservableChecker(value) {
|
|
1343
|
-
return value?.__$isObservableChecker || value instanceof ObservableChecker;
|
|
1344
|
-
},
|
|
1345
|
-
isArray(value) {
|
|
1346
|
-
return Array.isArray(value);
|
|
1347
|
-
},
|
|
1348
|
-
isString(value) {
|
|
1349
|
-
return typeof value === 'string';
|
|
1350
|
-
},
|
|
1351
|
-
isNumber(value) {
|
|
1352
|
-
return typeof value === 'number';
|
|
1353
|
-
},
|
|
1354
|
-
isBoolean(value) {
|
|
1355
|
-
return typeof value === 'boolean';
|
|
1356
|
-
},
|
|
1357
|
-
isFunction(value) {
|
|
1358
|
-
return typeof value === 'function';
|
|
1359
|
-
},
|
|
1360
|
-
isAsyncFunction(value) {
|
|
1361
|
-
return typeof value === 'function' && value.constructor.name === 'AsyncFunction';
|
|
1362
|
-
},
|
|
1363
|
-
isObject(value) {
|
|
1364
|
-
return typeof value === 'object' && value !== null;
|
|
1365
|
-
},
|
|
1366
|
-
isJson(value) {
|
|
1367
|
-
return !(typeof value !== 'object' || value === null || Array.isArray(value) || value.constructor.name !== 'Object')
|
|
1368
|
-
},
|
|
1369
|
-
isElement(value) {
|
|
1370
|
-
return value && (
|
|
1371
|
-
value.nodeType === COMMON_NODE_TYPES.ELEMENT ||
|
|
1372
|
-
value.nodeType === COMMON_NODE_TYPES.TEXT ||
|
|
1373
|
-
value.nodeType === COMMON_NODE_TYPES.DOCUMENT_FRAGMENT ||
|
|
1374
|
-
value.nodeType === COMMON_NODE_TYPES.COMMENT
|
|
1375
|
-
);
|
|
1376
|
-
},
|
|
1377
|
-
isFragment(value) {
|
|
1378
|
-
return value?.nodeType === COMMON_NODE_TYPES.DOCUMENT_FRAGMENT;
|
|
1379
|
-
},
|
|
1380
|
-
isStringOrObservable(value) {
|
|
1381
|
-
return this.isString(value) || this.isObservable(value);
|
|
1382
|
-
},
|
|
1383
|
-
isValidChild(child) {
|
|
1384
|
-
return child === null ||
|
|
1385
|
-
this.isElement(child) ||
|
|
1386
|
-
this.isObservable(child) ||
|
|
1387
|
-
this.isNDElement(child) ||
|
|
1388
|
-
['string', 'number', 'boolean'].includes(typeof child);
|
|
1389
|
-
},
|
|
1390
|
-
isNDElement(child) {
|
|
1391
|
-
return child?.__$isNDElement || child instanceof NDElement;
|
|
1392
|
-
},
|
|
1393
|
-
isValidChildren(children) {
|
|
1394
|
-
if (!Array.isArray(children)) {
|
|
1395
|
-
children = [children];
|
|
1888
|
+
ObservableItem.prototype.disconnectAll = function() {
|
|
1889
|
+
this.$listeners?.splice(0);
|
|
1890
|
+
this.$previousValue = null;
|
|
1891
|
+
this.$currentValue = null;
|
|
1892
|
+
if(this.$watchers) {
|
|
1893
|
+
for (const [_, watchValueList] of this.$watchers) {
|
|
1894
|
+
if(Validator.isArray(watchValueList)) {
|
|
1895
|
+
watchValueList.splice(0);
|
|
1896
|
+
}
|
|
1396
1897
|
}
|
|
1898
|
+
}
|
|
1899
|
+
this.$watchers?.clear();
|
|
1900
|
+
this.$listeners = null;
|
|
1901
|
+
this.$watchers = null;
|
|
1902
|
+
this.trigger = noneTrigger;
|
|
1903
|
+
};
|
|
1397
1904
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1905
|
+
/**
|
|
1906
|
+
* Registers a cleanup callback that will be executed when the observable is cleaned up.
|
|
1907
|
+
* Useful for disposing resources, removing event listeners, or other cleanup tasks.
|
|
1908
|
+
*
|
|
1909
|
+
* @param {Function} callback - Cleanup function to execute on observable disposal
|
|
1910
|
+
* @example
|
|
1911
|
+
* const obs = Observable(0);
|
|
1912
|
+
* obs.onCleanup(() => console.log('Cleaned up!'));
|
|
1913
|
+
* obs.cleanup(); // Logs: "Cleaned up!"
|
|
1914
|
+
*/
|
|
1915
|
+
ObservableItem.prototype.onCleanup = function(callback) {
|
|
1916
|
+
this.$cleanupListeners = this.$cleanupListeners ?? [];
|
|
1917
|
+
this.$cleanupListeners.push(callback);
|
|
1918
|
+
};
|
|
1405
1919
|
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1920
|
+
ObservableItem.prototype.cleanup = function() {
|
|
1921
|
+
if (this.$cleanupListeners) {
|
|
1922
|
+
for (let i = 0; i < this.$cleanupListeners.length; i++) {
|
|
1923
|
+
this.$cleanupListeners[i]();
|
|
1409
1924
|
}
|
|
1925
|
+
this.$cleanupListeners = null;
|
|
1926
|
+
}
|
|
1927
|
+
MemoryManager.unregister(this.$memoryId);
|
|
1928
|
+
this.disconnectAll();
|
|
1929
|
+
{
|
|
1930
|
+
this.$isCleanedUp = true;
|
|
1931
|
+
}
|
|
1932
|
+
delete this.$value;
|
|
1933
|
+
};
|
|
1410
1934
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
return
|
|
1421
|
-
}
|
|
1422
|
-
return Validator.isObject(data)
|
|
1423
|
-
&& Object.values(data).some(value => Validator.isObservable(value));
|
|
1424
|
-
},
|
|
1425
|
-
/**
|
|
1426
|
-
* Check if the data contains an observable reference.
|
|
1427
|
-
* @param {string} data
|
|
1428
|
-
* @returns {boolean}
|
|
1429
|
-
*/
|
|
1430
|
-
containsObservableReference(data) {
|
|
1431
|
-
if(!data || typeof data !== 'string') {
|
|
1432
|
-
return false;
|
|
1935
|
+
/**
|
|
1936
|
+
*
|
|
1937
|
+
* @param {Function} callback
|
|
1938
|
+
* @returns {(function(): void)}
|
|
1939
|
+
*/
|
|
1940
|
+
ObservableItem.prototype.subscribe = function(callback) {
|
|
1941
|
+
{
|
|
1942
|
+
if (this.$isCleanedUp) {
|
|
1943
|
+
DebugManager.warn('Observable subscription', '⚠️ Attempted to subscribe to a cleaned up observable.');
|
|
1944
|
+
return;
|
|
1433
1945
|
}
|
|
1434
|
-
return /\{\{#ObItem::\([0-9]+\)\}\}/.test(data);
|
|
1435
|
-
},
|
|
1436
|
-
validateAttributes(attributes) {},
|
|
1437
|
-
|
|
1438
|
-
validateEventCallback(callback) {
|
|
1439
1946
|
if (typeof callback !== 'function') {
|
|
1440
|
-
throw new NativeDocumentError('
|
|
1947
|
+
throw new NativeDocumentError('Callback must be a function');
|
|
1441
1948
|
}
|
|
1442
1949
|
}
|
|
1443
|
-
|
|
1444
|
-
{
|
|
1445
|
-
Validator.validateAttributes = function(attributes) {
|
|
1446
|
-
if (!attributes || typeof attributes !== 'object') {
|
|
1447
|
-
return attributes;
|
|
1448
|
-
}
|
|
1950
|
+
this.$listeners = this.$listeners ?? [];
|
|
1449
1951
|
|
|
1450
|
-
|
|
1451
|
-
|
|
1952
|
+
this.$listeners.push(callback);
|
|
1953
|
+
this.assocTrigger();
|
|
1954
|
+
{
|
|
1955
|
+
PluginsManager.emit('ObservableSubscribe', this);
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1452
1958
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1959
|
+
/**
|
|
1960
|
+
* Watches for a specific value and executes callback when the observable equals that value.
|
|
1961
|
+
* Creates a watcher that only triggers when the observable changes to the specified value.
|
|
1962
|
+
*
|
|
1963
|
+
* @param {*} value - The value to watch for
|
|
1964
|
+
* @param {(value) => void|ObservableItem} callback - Callback function or observable to set when value matches
|
|
1965
|
+
* @example
|
|
1966
|
+
* const status = Observable('idle');
|
|
1967
|
+
* status.on('loading', () => console.log('Started loading'));
|
|
1968
|
+
* status.on('error', isError); // Set another observable
|
|
1969
|
+
*/
|
|
1970
|
+
ObservableItem.prototype.on = function(value, callback) {
|
|
1971
|
+
this.$watchers = this.$watchers ?? new Map();
|
|
1456
1972
|
|
|
1457
|
-
|
|
1458
|
-
};
|
|
1459
|
-
}
|
|
1973
|
+
let watchValueList = this.$watchers.get(value);
|
|
1460
1974
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1975
|
+
if(callback.__$isObservable) {
|
|
1976
|
+
callback = callback.set.bind(callback);
|
|
1977
|
+
}
|
|
1464
1978
|
|
|
1465
|
-
|
|
1466
|
-
|
|
1979
|
+
if(!watchValueList) {
|
|
1980
|
+
watchValueList = callback;
|
|
1981
|
+
this.$watchers.set(value, callback);
|
|
1982
|
+
} else if(!Validator.isArray(watchValueList.list)) {
|
|
1983
|
+
watchValueList = [watchValueList, callback];
|
|
1984
|
+
callback = (value) => {
|
|
1985
|
+
for(let i = 0, length = watchValueList.length; i < length; i++) {
|
|
1986
|
+
watchValueList[i](value);
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
callback.list = watchValueList;
|
|
1990
|
+
this.$watchers.set(value, callback);
|
|
1991
|
+
} else {
|
|
1992
|
+
watchValueList.list.push(callback);
|
|
1993
|
+
}
|
|
1467
1994
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1995
|
+
this.assocTrigger();
|
|
1996
|
+
};
|
|
1470
1997
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1998
|
+
/**
|
|
1999
|
+
* Removes a watcher for a specific value. If no callback is provided, removes all watchers for that value.
|
|
2000
|
+
*
|
|
2001
|
+
* @param {*} value - The value to stop watching
|
|
2002
|
+
* @param {Function} [callback] - Specific callback to remove. If omitted, removes all watchers for this value
|
|
2003
|
+
* @example
|
|
2004
|
+
* const status = Observable('idle');
|
|
2005
|
+
* const handler = () => console.log('Loading');
|
|
2006
|
+
* status.on('loading', handler);
|
|
2007
|
+
* status.off('loading', handler); // Remove specific handler
|
|
2008
|
+
* status.off('loading'); // Remove all handlers for 'loading'
|
|
2009
|
+
*/
|
|
2010
|
+
ObservableItem.prototype.off = function(value, callback) {
|
|
2011
|
+
if(!this.$watchers) return;
|
|
1474
2012
|
|
|
1475
|
-
const
|
|
2013
|
+
const watchValueList = this.$watchers.get(value);
|
|
2014
|
+
if(!watchValueList) return;
|
|
1476
2015
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
2016
|
+
if(!callback || !Array.isArray(watchValueList.list)) {
|
|
2017
|
+
this.$watchers?.delete(value);
|
|
2018
|
+
this.assocTrigger();
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
const index = watchValueList.indexOf(callback);
|
|
2022
|
+
watchValueList?.splice(index, 1);
|
|
2023
|
+
if(watchValueList.length === 1) {
|
|
2024
|
+
this.$watchers.set(value, watchValueList[0]);
|
|
2025
|
+
}
|
|
2026
|
+
else if(watchValueList.length === 0) {
|
|
2027
|
+
this.$watchers?.delete(value);
|
|
2028
|
+
}
|
|
2029
|
+
this.assocTrigger();
|
|
2030
|
+
};
|
|
1489
2031
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
2032
|
+
/**
|
|
2033
|
+
* Subscribes to the observable but automatically unsubscribes after the first time the predicate matches.
|
|
2034
|
+
*
|
|
2035
|
+
* @param {(value) => Boolean|any} predicate - Value to match or function that returns true when condition is met
|
|
2036
|
+
* @param {(value) => void} callback - Callback to execute when predicate matches, receives the matched value
|
|
2037
|
+
* @example
|
|
2038
|
+
* const status = Observable('loading');
|
|
2039
|
+
* status.once('ready', (val) => console.log('Ready!'));
|
|
2040
|
+
* status.once(val => val === 'error', (val) => console.log('Error occurred'));
|
|
2041
|
+
*/
|
|
2042
|
+
ObservableItem.prototype.once = function(predicate, callback) {
|
|
2043
|
+
const fn = typeof predicate === 'function' ? predicate : (v) => v === predicate;
|
|
1499
2044
|
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
return;
|
|
2045
|
+
const handler = (val) => {
|
|
2046
|
+
if (fn(val)) {
|
|
2047
|
+
this.unsubscribe(handler);
|
|
2048
|
+
callback(val);
|
|
1505
2049
|
}
|
|
1506
|
-
before = before ?? anchorEnd;
|
|
1507
|
-
insertBefore(parent, child, before);
|
|
1508
|
-
};
|
|
1509
|
-
|
|
1510
|
-
anchorFragment.append = function(...args ) {
|
|
1511
|
-
return anchorFragment.appendChild(args);
|
|
1512
2050
|
};
|
|
2051
|
+
this.subscribe(handler);
|
|
2052
|
+
};
|
|
1513
2053
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
2054
|
+
/**
|
|
2055
|
+
* Unsubscribe from an observable.
|
|
2056
|
+
* @param {Function} callback
|
|
2057
|
+
*/
|
|
2058
|
+
ObservableItem.prototype.unsubscribe = function(callback) {
|
|
2059
|
+
if(!this.$listeners) return;
|
|
2060
|
+
const index = this.$listeners.indexOf(callback);
|
|
2061
|
+
if (index > -1) {
|
|
2062
|
+
this.$listeners.splice(index, 1);
|
|
2063
|
+
}
|
|
2064
|
+
this.assocTrigger();
|
|
2065
|
+
{
|
|
2066
|
+
PluginsManager.emit('ObservableUnsubscribe', this);
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
1523
2069
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
};
|
|
2070
|
+
/**
|
|
2071
|
+
* Create an Observable checker instance
|
|
2072
|
+
* @param callback
|
|
2073
|
+
* @returns {ObservableChecker}
|
|
2074
|
+
*/
|
|
2075
|
+
ObservableItem.prototype.check = function(callback) {
|
|
2076
|
+
return new ObservableChecker(this, callback)
|
|
2077
|
+
};
|
|
1533
2078
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
}
|
|
1539
|
-
let itemToRemove = anchorStart.nextSibling, tempItem;
|
|
1540
|
-
const allItemToRemove = [];
|
|
1541
|
-
const removes = [];
|
|
1542
|
-
while(itemToRemove && itemToRemove !== anchorEnd) {
|
|
1543
|
-
tempItem = itemToRemove.nextSibling;
|
|
1544
|
-
allItemToRemove.push(itemToRemove);
|
|
1545
|
-
removes.push(itemToRemove.remove());
|
|
1546
|
-
itemToRemove = tempItem;
|
|
1547
|
-
}
|
|
1548
|
-
await Promise.all(removes);
|
|
1549
|
-
anchorFragment.nativeAppend(...allItemToRemove);
|
|
1550
|
-
};
|
|
2079
|
+
ObservableItem.prototype.transform = ObservableItem.prototype.check;
|
|
2080
|
+
ObservableItem.prototype.pluck = ObservableItem.prototype.check;
|
|
2081
|
+
ObservableItem.prototype.is = ObservableItem.prototype.check;
|
|
2082
|
+
ObservableItem.prototype.select = ObservableItem.prototype.check;
|
|
1551
2083
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
2084
|
+
/**
|
|
2085
|
+
* Gets a property value from the observable's current value.
|
|
2086
|
+
* If the property is an observable, returns its value.
|
|
2087
|
+
*
|
|
2088
|
+
* @param {string|number} key - Property key to retrieve
|
|
2089
|
+
* @returns {*} The value of the property, unwrapped if it's an observable
|
|
2090
|
+
* @example
|
|
2091
|
+
* const user = Observable({ name: 'John', age: Observable(25) });
|
|
2092
|
+
* user.get('name'); // 'John'
|
|
2093
|
+
* user.get('age'); // 25 (unwrapped from observable)
|
|
2094
|
+
*/
|
|
2095
|
+
ObservableItem.prototype.get = function(key) {
|
|
2096
|
+
const item = this.$currentValue[key];
|
|
2097
|
+
return Validator.isObservable(item) ? item.val() : item;
|
|
2098
|
+
};
|
|
1557
2099
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
2100
|
+
/**
|
|
2101
|
+
* Creates an ObservableWhen that represents whether the observable equals a specific value.
|
|
2102
|
+
* Returns an object that can be subscribed to and will emit true/false.
|
|
2103
|
+
*
|
|
2104
|
+
* @param {*} value - The value to compare against
|
|
2105
|
+
* @returns {ObservableWhen} An ObservableWhen instance that tracks when the observable equals the value
|
|
2106
|
+
* @example
|
|
2107
|
+
* const status = Observable('idle');
|
|
2108
|
+
* const isLoading = status.when('loading');
|
|
2109
|
+
* isLoading.subscribe(active => console.log('Loading:', active));
|
|
2110
|
+
* status.set('loading'); // Logs: "Loading: true"
|
|
2111
|
+
*/
|
|
2112
|
+
ObservableItem.prototype.when = function(value) {
|
|
2113
|
+
return new ObservableWhen(this, value);
|
|
2114
|
+
};
|
|
1571
2115
|
|
|
1572
|
-
|
|
2116
|
+
/**
|
|
2117
|
+
* Compares the observable's current value with another value or observable.
|
|
2118
|
+
*
|
|
2119
|
+
* @param {*|ObservableItem} other - Value or observable to compare against
|
|
2120
|
+
* @returns {boolean} True if values are equal
|
|
2121
|
+
* @example
|
|
2122
|
+
* const a = Observable(5);
|
|
2123
|
+
* const b = Observable(5);
|
|
2124
|
+
* a.equals(5); // true
|
|
2125
|
+
* a.equals(b); // true
|
|
2126
|
+
* a.equals(10); // false
|
|
2127
|
+
*/
|
|
2128
|
+
ObservableItem.prototype.equals = function(other) {
|
|
2129
|
+
if(Validator.isObservable(other)) {
|
|
2130
|
+
return this.$currentValue === other.$currentValue;
|
|
2131
|
+
}
|
|
2132
|
+
return this.$currentValue === other;
|
|
2133
|
+
};
|
|
1573
2134
|
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
2135
|
+
/**
|
|
2136
|
+
* Converts the observable's current value to a boolean.
|
|
2137
|
+
*
|
|
2138
|
+
* @returns {boolean} The boolean representation of the current value
|
|
2139
|
+
* @example
|
|
2140
|
+
* const count = Observable(0);
|
|
2141
|
+
* count.toBool(); // false
|
|
2142
|
+
* count.set(5);
|
|
2143
|
+
* count.toBool(); // true
|
|
2144
|
+
*/
|
|
2145
|
+
ObservableItem.prototype.toBool = function() {
|
|
2146
|
+
return !!this.$currentValue;
|
|
2147
|
+
};
|
|
1577
2148
|
|
|
2149
|
+
/**
|
|
2150
|
+
* Toggles the boolean value of the observable (false becomes true, true becomes false).
|
|
2151
|
+
*
|
|
2152
|
+
* @example
|
|
2153
|
+
* const isOpen = Observable(false);
|
|
2154
|
+
* isOpen.toggle(); // Now true
|
|
2155
|
+
* isOpen.toggle(); // Now false
|
|
2156
|
+
*/
|
|
2157
|
+
ObservableItem.prototype.toggle = function() {
|
|
2158
|
+
this.set(!this.$currentValue);
|
|
2159
|
+
};
|
|
1578
2160
|
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
2161
|
+
/**
|
|
2162
|
+
* Resets the observable to its initial value.
|
|
2163
|
+
* Only works if the observable was created with { reset: true } config.
|
|
2164
|
+
*
|
|
2165
|
+
* @example
|
|
2166
|
+
* const count = Observable(0, { reset: true });
|
|
2167
|
+
* count.set(10);
|
|
2168
|
+
* count.reset(); // Back to 0
|
|
2169
|
+
*/
|
|
2170
|
+
ObservableItem.prototype.reset = function() {
|
|
2171
|
+
if(!this.configs?.reset) {
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
const resetValue = (Validator.isObject(this.$initialValue))
|
|
2175
|
+
? deepClone(this.$initialValue, (observable) => {
|
|
2176
|
+
observable.reset();
|
|
2177
|
+
})
|
|
2178
|
+
: this.$initialValue;
|
|
2179
|
+
this.set(resetValue);
|
|
2180
|
+
};
|
|
1582
2181
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
2182
|
+
/**
|
|
2183
|
+
* Returns a string representation of the observable's current value.
|
|
2184
|
+
*
|
|
2185
|
+
* @returns {string} String representation of the current value
|
|
2186
|
+
*/
|
|
2187
|
+
ObservableItem.prototype.toString = function() {
|
|
2188
|
+
return String(this.$currentValue);
|
|
2189
|
+
};
|
|
2190
|
+
|
|
2191
|
+
/**
|
|
2192
|
+
* Returns the primitive value of the observable (its current value).
|
|
2193
|
+
* Called automatically in type coercion contexts.
|
|
2194
|
+
*
|
|
2195
|
+
* @returns {*} The current value of the observable
|
|
2196
|
+
*/
|
|
2197
|
+
ObservableItem.prototype.valueOf = function() {
|
|
2198
|
+
return this.$currentValue;
|
|
2199
|
+
};
|
|
1591
2200
|
|
|
1592
|
-
anchorFragment.getByIndex = function(index) {
|
|
1593
|
-
let currentNode = anchorStart;
|
|
1594
|
-
for(let i = 0; i <= index; i++) {
|
|
1595
|
-
if(!currentNode.nextSibling) {
|
|
1596
|
-
return null;
|
|
1597
|
-
}
|
|
1598
|
-
currentNode = currentNode.nextSibling;
|
|
1599
|
-
}
|
|
1600
|
-
return currentNode !== anchorStart ? currentNode : null;
|
|
1601
|
-
};
|
|
1602
2201
|
|
|
1603
|
-
return anchorFragment;
|
|
1604
|
-
}
|
|
1605
2202
|
/**
|
|
2203
|
+
* Creates a derived observable that formats the current value using Intl.
|
|
2204
|
+
* Automatically reacts to both value changes and locale changes (Store.__nd.locale).
|
|
1606
2205
|
*
|
|
1607
|
-
* @param {
|
|
1608
|
-
* @param {{
|
|
1609
|
-
* @returns {
|
|
2206
|
+
* @param {string | Function} type - Format type or custom formatter function
|
|
2207
|
+
* @param {Object} [options={}] - Options passed to the formatter
|
|
2208
|
+
* @returns {ObservableItem<string>}
|
|
2209
|
+
*
|
|
2210
|
+
* @example
|
|
2211
|
+
* // Currency
|
|
2212
|
+
* price.format('currency') // "15 000 FCFA"
|
|
2213
|
+
* price.format('currency', { currency: 'EUR' }) // "15 000,00 €"
|
|
2214
|
+
* price.format('currency', { notation: 'compact' }) // "15 K FCFA"
|
|
2215
|
+
*
|
|
2216
|
+
* // Number
|
|
2217
|
+
* count.format('number') // "15 000"
|
|
2218
|
+
*
|
|
2219
|
+
* // Percent
|
|
2220
|
+
* rate.format('percent') // "15,0 %"
|
|
2221
|
+
* rate.format('percent', { decimals: 2 }) // "15,00 %"
|
|
2222
|
+
*
|
|
2223
|
+
* // Date
|
|
2224
|
+
* date.format('date') // "3 mars 2026"
|
|
2225
|
+
* date.format('date', { dateStyle: 'full' }) // "mardi 3 mars 2026"
|
|
2226
|
+
* date.format('date', { format: 'DD/MM/YYYY' }) // "03/03/2026"
|
|
2227
|
+
* date.format('date', { format: 'DD MMM YYYY' }) // "03 mar 2026"
|
|
2228
|
+
* date.format('date', { format: 'DD MMMM YYYY' }) // "03 mars 2026"
|
|
2229
|
+
*
|
|
2230
|
+
* // Time
|
|
2231
|
+
* date.format('time') // "20:30"
|
|
2232
|
+
* date.format('time', { second: '2-digit' }) // "20:30:00"
|
|
2233
|
+
* date.format('time', { format: 'HH:mm:ss' }) // "20:30:00"
|
|
2234
|
+
*
|
|
2235
|
+
* // Datetime
|
|
2236
|
+
* date.format('datetime') // "3 mars 2026, 20:30"
|
|
2237
|
+
* date.format('datetime', { dateStyle: 'full' }) // "mardi 3 mars 2026, 20:30"
|
|
2238
|
+
* date.format('datetime', { format: 'DD/MM/YYYY HH:mm' }) // "03/03/2026 20:30"
|
|
2239
|
+
*
|
|
2240
|
+
* // Relative
|
|
2241
|
+
* date.format('relative') // "dans 11 jours"
|
|
2242
|
+
* date.format('relative', { unit: 'month' }) // "dans 1 mois"
|
|
2243
|
+
*
|
|
2244
|
+
* // Plural
|
|
2245
|
+
* count.format('plural', { singular: 'billet', plural: 'billets' }) // "3 billets"
|
|
2246
|
+
*
|
|
2247
|
+
* // Custom formatter
|
|
2248
|
+
* price.format(value => `${value.toLocaleString()} FCFA`)
|
|
2249
|
+
*
|
|
2250
|
+
* // Reacts to locale changes automatically
|
|
2251
|
+
* Store.setLocale('en-US');
|
|
1610
2252
|
*/
|
|
1611
|
-
function
|
|
1612
|
-
const
|
|
1613
|
-
anchor.appendChild(ElementCreator.getChild(children));
|
|
2253
|
+
ObservableItem.prototype.format = function(type, options = {}) {
|
|
2254
|
+
const self = this;
|
|
1614
2255
|
|
|
1615
|
-
(
|
|
1616
|
-
|
|
1617
|
-
|
|
2256
|
+
if (typeof type === 'function') {
|
|
2257
|
+
return new ObservableChecker(self, type);
|
|
2258
|
+
}
|
|
1618
2259
|
|
|
1619
|
-
|
|
2260
|
+
{
|
|
2261
|
+
if (!Formatters[type]) {
|
|
2262
|
+
throw new NativeDocumentError(
|
|
2263
|
+
`Observable.format : unknown type '${type}'. Available : ${Object.keys(Formatters).join(', ')}.`
|
|
2264
|
+
);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
1620
2267
|
|
|
1621
|
-
|
|
1622
|
-
'
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
'download',
|
|
1642
|
-
'reversed',
|
|
1643
|
-
'open',
|
|
1644
|
-
'default',
|
|
1645
|
-
'formnovalidate',
|
|
1646
|
-
'novalidate',
|
|
1647
|
-
'scoped',
|
|
1648
|
-
'itemscope',
|
|
1649
|
-
'allowfullscreen',
|
|
1650
|
-
'allowpaymentrequest',
|
|
1651
|
-
'playsinline'
|
|
1652
|
-
]);
|
|
2268
|
+
const formatter = Formatters[type];
|
|
2269
|
+
const localeObservable = Store.follow('locale');
|
|
2270
|
+
|
|
2271
|
+
return Observable$1.computed(() => formatter(self.val(), localeObservable.val(), options),
|
|
2272
|
+
[self, localeObservable]
|
|
2273
|
+
);
|
|
2274
|
+
};
|
|
2275
|
+
|
|
2276
|
+
ObservableItem.prototype.persist = function(key, options = {}) {
|
|
2277
|
+
let value = $getFromStorage$1(key, this.$currentValue);
|
|
2278
|
+
if(options.get) {
|
|
2279
|
+
value = options.get(value);
|
|
2280
|
+
}
|
|
2281
|
+
this.set(value);
|
|
2282
|
+
const saver = $saveToStorage$1(this.$currentValue);
|
|
2283
|
+
this.subscribe((newValue) => {
|
|
2284
|
+
saver(key, options.set ? options.set(newValue) : newValue);
|
|
2285
|
+
});
|
|
2286
|
+
return this;
|
|
2287
|
+
};
|
|
1653
2288
|
|
|
1654
2289
|
/**
|
|
1655
2290
|
*
|
|
@@ -1658,18 +2293,18 @@ var NativeDocument = (function (exports) {
|
|
|
1658
2293
|
* @returns {ObservableItem}
|
|
1659
2294
|
* @constructor
|
|
1660
2295
|
*/
|
|
1661
|
-
function Observable(value, configs = null) {
|
|
2296
|
+
function Observable$1(value, configs = null) {
|
|
1662
2297
|
return new ObservableItem(value, configs);
|
|
1663
2298
|
}
|
|
1664
2299
|
|
|
1665
|
-
const $ = Observable;
|
|
1666
|
-
const obs = Observable;
|
|
2300
|
+
const $ = Observable$1;
|
|
2301
|
+
const obs = Observable$1;
|
|
1667
2302
|
|
|
1668
2303
|
/**
|
|
1669
2304
|
*
|
|
1670
2305
|
* @param {string} propertyName
|
|
1671
2306
|
*/
|
|
1672
|
-
Observable.useValueProperty = function(propertyName = 'value') {
|
|
2307
|
+
Observable$1.useValueProperty = function(propertyName = 'value') {
|
|
1673
2308
|
Object.defineProperty(ObservableItem.prototype, propertyName, {
|
|
1674
2309
|
get() {
|
|
1675
2310
|
return this.$currentValue;
|
|
@@ -1687,7 +2322,7 @@ var NativeDocument = (function (exports) {
|
|
|
1687
2322
|
* @param id
|
|
1688
2323
|
* @returns {ObservableItem|null}
|
|
1689
2324
|
*/
|
|
1690
|
-
Observable.getById = function(id) {
|
|
2325
|
+
Observable$1.getById = function(id) {
|
|
1691
2326
|
const item = MemoryManager.getObservableById(parseInt(id));
|
|
1692
2327
|
if(!item) {
|
|
1693
2328
|
throw new NativeDocumentError('Observable.getById : No observable found with id ' + id);
|
|
@@ -1699,7 +2334,7 @@ var NativeDocument = (function (exports) {
|
|
|
1699
2334
|
*
|
|
1700
2335
|
* @param {ObservableItem} observable
|
|
1701
2336
|
*/
|
|
1702
|
-
Observable.cleanup = function(observable) {
|
|
2337
|
+
Observable$1.cleanup = function(observable) {
|
|
1703
2338
|
observable.cleanup();
|
|
1704
2339
|
};
|
|
1705
2340
|
|
|
@@ -1708,7 +2343,7 @@ var NativeDocument = (function (exports) {
|
|
|
1708
2343
|
* @param {Boolean} enable
|
|
1709
2344
|
* @param {{interval:Boolean, threshold:number}} options
|
|
1710
2345
|
*/
|
|
1711
|
-
Observable.autoCleanup = function(enable = false, options = {}) {
|
|
2346
|
+
Observable$1.autoCleanup = function(enable = false, options = {}) {
|
|
1712
2347
|
if(!enable) {
|
|
1713
2348
|
return;
|
|
1714
2349
|
}
|
|
@@ -1853,6 +2488,12 @@ var NativeDocument = (function (exports) {
|
|
|
1853
2488
|
return element;
|
|
1854
2489
|
}
|
|
1855
2490
|
|
|
2491
|
+
function TemplateBinding(hydrate) {
|
|
2492
|
+
this.$hydrate = hydrate;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
TemplateBinding.prototype.__$isTemplateBinding = true;
|
|
2496
|
+
|
|
1856
2497
|
String.prototype.toNdElement = function () {
|
|
1857
2498
|
const formattedChild = this.resolveObservableTemplate ? this.resolveObservableTemplate() : this;
|
|
1858
2499
|
if(Validator.isString(formattedChild)) {
|
|
@@ -2856,7 +3497,7 @@ var NativeDocument = (function (exports) {
|
|
|
2856
3497
|
String.prototype.use = function(args) {
|
|
2857
3498
|
const value = this;
|
|
2858
3499
|
|
|
2859
|
-
return Observable.computed(() => {
|
|
3500
|
+
return Observable$1.computed(() => {
|
|
2860
3501
|
return value.replace(/\$\{(.*?)}/g, (match, key) => {
|
|
2861
3502
|
const data = args[key];
|
|
2862
3503
|
if(Validator.isObservable(data)) {
|
|
@@ -2876,7 +3517,7 @@ var NativeDocument = (function (exports) {
|
|
|
2876
3517
|
return value;
|
|
2877
3518
|
}
|
|
2878
3519
|
const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
|
|
2879
|
-
return Observable.getById(id);
|
|
3520
|
+
return Observable$1.getById(id);
|
|
2880
3521
|
});
|
|
2881
3522
|
};
|
|
2882
3523
|
|
|
@@ -3569,7 +4210,7 @@ var NativeDocument = (function (exports) {
|
|
|
3569
4210
|
}
|
|
3570
4211
|
}
|
|
3571
4212
|
|
|
3572
|
-
const viewArray = Observable.array();
|
|
4213
|
+
const viewArray = Observable$1.array();
|
|
3573
4214
|
|
|
3574
4215
|
const filters = Object.entries(filterCallbacks);
|
|
3575
4216
|
const updateView = () => {
|
|
@@ -3655,7 +4296,7 @@ var NativeDocument = (function (exports) {
|
|
|
3655
4296
|
* items.push(4); // Triggers update
|
|
3656
4297
|
* items.subscribe((arr) => console.log(arr));
|
|
3657
4298
|
*/
|
|
3658
|
-
Observable.array = function(target = [], configs = null) {
|
|
4299
|
+
Observable$1.array = function(target = [], configs = null) {
|
|
3659
4300
|
return new ObservableArray(target, configs);
|
|
3660
4301
|
};
|
|
3661
4302
|
|
|
@@ -3664,8 +4305,8 @@ var NativeDocument = (function (exports) {
|
|
|
3664
4305
|
* @param {Function} callback
|
|
3665
4306
|
* @returns {Function}
|
|
3666
4307
|
*/
|
|
3667
|
-
Observable.batch = function(callback) {
|
|
3668
|
-
const $observer = Observable(0);
|
|
4308
|
+
Observable$1.batch = function(callback) {
|
|
4309
|
+
const $observer = Observable$1(0);
|
|
3669
4310
|
const batch = function() {
|
|
3670
4311
|
if(Validator.isAsyncFunction(callback)) {
|
|
3671
4312
|
return (callback(...arguments)).then(() => {
|
|
@@ -3679,10 +4320,71 @@ var NativeDocument = (function (exports) {
|
|
|
3679
4320
|
return batch;
|
|
3680
4321
|
};
|
|
3681
4322
|
|
|
3682
|
-
const
|
|
4323
|
+
const ObservableObject = function(target, configs) {
|
|
4324
|
+
ObservableItem.call(this, target);
|
|
4325
|
+
this.$observables = {};
|
|
4326
|
+
this.configs = configs;
|
|
4327
|
+
|
|
4328
|
+
this.$load(target);
|
|
4329
|
+
|
|
4330
|
+
for(const name in target) {
|
|
4331
|
+
if(!Object.hasOwn(this, name)) {
|
|
4332
|
+
Object.defineProperty(this, name, {
|
|
4333
|
+
get: () => this.$observables[name],
|
|
4334
|
+
set: (value) => this.$observables[name].set(value)
|
|
4335
|
+
});
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
};
|
|
4340
|
+
|
|
4341
|
+
ObservableObject.prototype = Object.create(ObservableItem.prototype);
|
|
4342
|
+
|
|
4343
|
+
Object.defineProperty(ObservableObject, '$value', {
|
|
4344
|
+
get() {
|
|
4345
|
+
return this.val();
|
|
4346
|
+
},
|
|
4347
|
+
set(value) {
|
|
4348
|
+
this.set(value);
|
|
4349
|
+
}
|
|
4350
|
+
});
|
|
4351
|
+
|
|
4352
|
+
ObservableObject.prototype.__$isObservableObject = true;
|
|
4353
|
+
ObservableObject.prototype.__isProxy__ = true;
|
|
4354
|
+
|
|
4355
|
+
ObservableObject.prototype.$load = function(initialValue) {
|
|
4356
|
+
const configs = this.configs;
|
|
4357
|
+
for(const key in initialValue) {
|
|
4358
|
+
const itemValue = initialValue[key];
|
|
4359
|
+
if(Array.isArray(itemValue)) {
|
|
4360
|
+
if(configs?.deep !== false) {
|
|
4361
|
+
const mappedItemValue = itemValue.map(item => {
|
|
4362
|
+
if(Validator.isJson(item)) {
|
|
4363
|
+
return Observable$1.json(item, configs);
|
|
4364
|
+
}
|
|
4365
|
+
if(Validator.isArray(item)) {
|
|
4366
|
+
return Observable$1.array(item, configs);
|
|
4367
|
+
}
|
|
4368
|
+
return Observable$1(item, configs);
|
|
4369
|
+
});
|
|
4370
|
+
this.$observables[key] = Observable$1.array(mappedItemValue, configs);
|
|
4371
|
+
continue;
|
|
4372
|
+
}
|
|
4373
|
+
this.$observables[key] = Observable$1.array(itemValue, configs);
|
|
4374
|
+
continue;
|
|
4375
|
+
}
|
|
4376
|
+
if(Validator.isObservable(itemValue) || Validator.isProxy(itemValue)) {
|
|
4377
|
+
this.$observables[key] = itemValue;
|
|
4378
|
+
continue;
|
|
4379
|
+
}
|
|
4380
|
+
this.$observables[key] = Observable$1(itemValue, configs);
|
|
4381
|
+
}
|
|
4382
|
+
};
|
|
4383
|
+
|
|
4384
|
+
ObservableObject.prototype.val = function() {
|
|
3683
4385
|
const result = {};
|
|
3684
|
-
for(const key in
|
|
3685
|
-
const dataItem =
|
|
4386
|
+
for(const key in this.$observables) {
|
|
4387
|
+
const dataItem = this.$observables[key];
|
|
3686
4388
|
if(Validator.isObservable(dataItem)) {
|
|
3687
4389
|
let value = dataItem.val();
|
|
3688
4390
|
if(Array.isArray(value)) {
|
|
@@ -3705,9 +4407,10 @@ var NativeDocument = (function (exports) {
|
|
|
3705
4407
|
}
|
|
3706
4408
|
return result;
|
|
3707
4409
|
};
|
|
4410
|
+
ObservableObject.prototype.$val = ObservableObject.prototype.val;
|
|
3708
4411
|
|
|
3709
|
-
|
|
3710
|
-
const item =
|
|
4412
|
+
ObservableObject.prototype.get = function(property) {
|
|
4413
|
+
const item = this.$observables[property];
|
|
3711
4414
|
if(Validator.isObservable(item)) {
|
|
3712
4415
|
return item.val();
|
|
3713
4416
|
}
|
|
@@ -3716,100 +4419,87 @@ var NativeDocument = (function (exports) {
|
|
|
3716
4419
|
}
|
|
3717
4420
|
return item;
|
|
3718
4421
|
};
|
|
4422
|
+
ObservableObject.prototype.$get = ObservableObject.prototype.get;
|
|
3719
4423
|
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
* user.name = 'Jane X'
|
|
3740
|
-
* user.age.subscribe(val => console.log('Age:', val));
|
|
3741
|
-
*/
|
|
3742
|
-
Observable.init = function(initialValue, configs = null) {
|
|
3743
|
-
const data = {};
|
|
3744
|
-
for(const key in initialValue) {
|
|
3745
|
-
const itemValue = initialValue[key];
|
|
3746
|
-
if(Array.isArray(itemValue)) {
|
|
3747
|
-
if(configs?.deep !== false) {
|
|
3748
|
-
const mappedItemValue = itemValue.map(item => {
|
|
3749
|
-
if(Validator.isJson(item)) {
|
|
3750
|
-
return Observable.json(item, configs);
|
|
3751
|
-
}
|
|
3752
|
-
if(Validator.isArray(item)) {
|
|
3753
|
-
return Observable.array(item, configs);
|
|
4424
|
+
ObservableObject.prototype.set = function(newData) {
|
|
4425
|
+
const data = Validator.isProxy(newData) ? newData.$value : newData;
|
|
4426
|
+
const configs = this.configs;
|
|
4427
|
+
|
|
4428
|
+
for(const key in data) {
|
|
4429
|
+
const targetItem = this.$observables[key];
|
|
4430
|
+
const newValueOrigin = newData[key];
|
|
4431
|
+
const newValue = data[key];
|
|
4432
|
+
|
|
4433
|
+
if(Validator.isObservable(targetItem)) {
|
|
4434
|
+
if(!Validator.isArray(newValue)) {
|
|
4435
|
+
targetItem.set(newValue);
|
|
4436
|
+
continue;
|
|
4437
|
+
}
|
|
4438
|
+
const firstElementFromOriginalValue = newValueOrigin.at(0);
|
|
4439
|
+
if(Validator.isObservable(firstElementFromOriginalValue) || Validator.isProxy(firstElementFromOriginalValue)) {
|
|
4440
|
+
const newValues = newValue.map(item => {
|
|
4441
|
+
if(Validator.isProxy(firstElementFromOriginalValue)) {
|
|
4442
|
+
return Observable$1.init(item, configs);
|
|
3754
4443
|
}
|
|
3755
|
-
return Observable(item, configs);
|
|
4444
|
+
return Observable$1(item, configs);
|
|
3756
4445
|
});
|
|
3757
|
-
|
|
4446
|
+
targetItem.set(newValues);
|
|
3758
4447
|
continue;
|
|
3759
4448
|
}
|
|
3760
|
-
|
|
4449
|
+
targetItem.set([...newValue]);
|
|
3761
4450
|
continue;
|
|
3762
4451
|
}
|
|
3763
|
-
if(Validator.
|
|
3764
|
-
|
|
4452
|
+
if(Validator.isProxy(targetItem)) {
|
|
4453
|
+
targetItem.update(newValue);
|
|
3765
4454
|
continue;
|
|
3766
4455
|
}
|
|
3767
|
-
|
|
4456
|
+
this[key] = newValue;
|
|
3768
4457
|
}
|
|
4458
|
+
};
|
|
4459
|
+
ObservableObject.prototype.$set = ObservableObject.prototype.set;
|
|
4460
|
+
ObservableObject.prototype.$updateWith = ObservableObject.prototype.set;
|
|
3769
4461
|
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
}
|
|
3775
|
-
};
|
|
3776
|
-
|
|
3777
|
-
const $val = () => ObservableObjectValue(data);
|
|
4462
|
+
ObservableObject.prototype.observables = function() {
|
|
4463
|
+
return Object.values(this.$observables);
|
|
4464
|
+
};
|
|
4465
|
+
ObservableObject.prototype.$observables = ObservableObject.prototype.observables;
|
|
3778
4466
|
|
|
3779
|
-
|
|
4467
|
+
ObservableObject.prototype.keys = function() {
|
|
4468
|
+
return Object.keys(this.$observables);
|
|
4469
|
+
};
|
|
4470
|
+
ObservableObject.prototype.$keys = ObservableObject.prototype.keys;
|
|
4471
|
+
ObservableObject.prototype.clone = function() {
|
|
4472
|
+
return Observable$1.init(this.val(), this.configs);
|
|
4473
|
+
};
|
|
4474
|
+
ObservableObject.prototype.$clone = ObservableObject.prototype.clone;
|
|
4475
|
+
ObservableObject.prototype.reset = function() {
|
|
4476
|
+
for(const key in this.$observables) {
|
|
4477
|
+
this.$observables[key].reset();
|
|
4478
|
+
}
|
|
4479
|
+
};
|
|
4480
|
+
ObservableObject.prototype.originalSubscribe = ObservableObject.prototype.subscribe;
|
|
4481
|
+
ObservableObject.prototype.subscribe = function(callback) {
|
|
4482
|
+
const observables = this.observables();
|
|
4483
|
+
const updatedValue = nextTick(() => {
|
|
4484
|
+
this.$currentValue = this.val();
|
|
4485
|
+
this.trigger();
|
|
4486
|
+
});
|
|
3780
4487
|
|
|
3781
|
-
|
|
3782
|
-
Observable.update(proxy, values);
|
|
3783
|
-
};
|
|
4488
|
+
this.originalSubscribe(callback);
|
|
3784
4489
|
|
|
3785
|
-
|
|
4490
|
+
for(let i = 0, length = observables.length; i < length; i++) {
|
|
4491
|
+
const observable = observables[i];
|
|
4492
|
+
observable.subscribe(updatedValue);
|
|
4493
|
+
}
|
|
4494
|
+
};
|
|
4495
|
+
ObservableObject.prototype.configs = function() {
|
|
4496
|
+
return this.configs;
|
|
4497
|
+
};
|
|
3786
4498
|
|
|
3787
|
-
|
|
3788
|
-
get(target, property) {
|
|
3789
|
-
if(property === '__isProxy__') { return true; }
|
|
3790
|
-
if(property === '$value') { return $val() }
|
|
3791
|
-
if(property === 'get' || property === '$get') { return $get; }
|
|
3792
|
-
if(property === 'val' || property === '$val') { return $val; }
|
|
3793
|
-
if(property === 'set' || property === '$set' || property === '$updateWith') { return $updateWith; }
|
|
3794
|
-
if(property === 'observables' || property === '$observables') { return Object.values(target); }
|
|
3795
|
-
if(property === 'keys'|| property === '$keys') { return Object.keys(initialValue); }
|
|
3796
|
-
if(property === 'clone' || property === '$clone') { return $clone; }
|
|
3797
|
-
if(property === 'reset') { return $reset; }
|
|
3798
|
-
if(property === 'configs') { return configs; }
|
|
3799
|
-
return target[property];
|
|
3800
|
-
},
|
|
3801
|
-
set(target, prop, newValue) {
|
|
3802
|
-
if(target[prop] !== undefined) {
|
|
3803
|
-
Validator.isObservable(newValue)
|
|
3804
|
-
? target[prop].set(newValue.val())
|
|
3805
|
-
: target[prop].set(newValue);
|
|
3806
|
-
return true;
|
|
3807
|
-
}
|
|
3808
|
-
return true;
|
|
3809
|
-
}
|
|
3810
|
-
});
|
|
4499
|
+
ObservableObject.prototype.update = ObservableObject.prototype.set;
|
|
3811
4500
|
|
|
3812
|
-
|
|
4501
|
+
Observable$1.init = function(initialValue, configs = null) {
|
|
4502
|
+
return new ObservableObject(initialValue, configs)
|
|
3813
4503
|
};
|
|
3814
4504
|
|
|
3815
4505
|
/**
|
|
@@ -3817,8 +4507,8 @@ var NativeDocument = (function (exports) {
|
|
|
3817
4507
|
* @param {any[]} data
|
|
3818
4508
|
* @return Proxy[]
|
|
3819
4509
|
*/
|
|
3820
|
-
Observable.arrayOfObject = function(data) {
|
|
3821
|
-
return data.map(item => Observable.object(item));
|
|
4510
|
+
Observable$1.arrayOfObject = function(data) {
|
|
4511
|
+
return data.map(item => Observable$1.object(item));
|
|
3822
4512
|
};
|
|
3823
4513
|
|
|
3824
4514
|
/**
|
|
@@ -3826,7 +4516,7 @@ var NativeDocument = (function (exports) {
|
|
|
3826
4516
|
* @param {ObservableItem|Object<ObservableItem>} data
|
|
3827
4517
|
* @returns {{}|*|null}
|
|
3828
4518
|
*/
|
|
3829
|
-
Observable.value = function(data) {
|
|
4519
|
+
Observable$1.value = function(data) {
|
|
3830
4520
|
if(Validator.isObservable(data)) {
|
|
3831
4521
|
return data.val();
|
|
3832
4522
|
}
|
|
@@ -3837,52 +4527,15 @@ var NativeDocument = (function (exports) {
|
|
|
3837
4527
|
const result = [];
|
|
3838
4528
|
for(let i = 0, length = data.length; i < length; i++) {
|
|
3839
4529
|
const item = data[i];
|
|
3840
|
-
result.push(Observable.value(item));
|
|
4530
|
+
result.push(Observable$1.value(item));
|
|
3841
4531
|
}
|
|
3842
4532
|
return result;
|
|
3843
4533
|
}
|
|
3844
4534
|
return data;
|
|
3845
4535
|
};
|
|
3846
4536
|
|
|
3847
|
-
|
|
3848
|
-
Observable.
|
|
3849
|
-
const data = Validator.isProxy(newData) ? newData.$value : newData;
|
|
3850
|
-
const configs = $target.configs;
|
|
3851
|
-
|
|
3852
|
-
for(const key in data) {
|
|
3853
|
-
const targetItem = $target[key];
|
|
3854
|
-
const newValueOrigin = newData[key];
|
|
3855
|
-
const newValue = data[key];
|
|
3856
|
-
|
|
3857
|
-
if(Validator.isObservable(targetItem)) {
|
|
3858
|
-
if(Validator.isArray(newValue)) {
|
|
3859
|
-
const firstElementFromOriginalValue = newValueOrigin.at(0);
|
|
3860
|
-
if(Validator.isObservable(firstElementFromOriginalValue) || Validator.isProxy(firstElementFromOriginalValue)) {
|
|
3861
|
-
const newValues = newValue.map(item => {
|
|
3862
|
-
if(Validator.isProxy(firstElementFromOriginalValue)) {
|
|
3863
|
-
return Observable.init(item, configs);
|
|
3864
|
-
}
|
|
3865
|
-
return Observable(item, configs);
|
|
3866
|
-
});
|
|
3867
|
-
targetItem.set(newValues);
|
|
3868
|
-
continue;
|
|
3869
|
-
}
|
|
3870
|
-
targetItem.set([...newValue]);
|
|
3871
|
-
continue;
|
|
3872
|
-
}
|
|
3873
|
-
targetItem.set(newValue);
|
|
3874
|
-
continue;
|
|
3875
|
-
}
|
|
3876
|
-
if(Validator.isProxy(targetItem)) {
|
|
3877
|
-
Observable.update(targetItem, newValue);
|
|
3878
|
-
continue;
|
|
3879
|
-
}
|
|
3880
|
-
$target[key] = newValue;
|
|
3881
|
-
}
|
|
3882
|
-
};
|
|
3883
|
-
|
|
3884
|
-
Observable.object = Observable.init;
|
|
3885
|
-
Observable.json = Observable.init;
|
|
4537
|
+
Observable$1.object = Observable$1.init;
|
|
4538
|
+
Observable$1.json = Observable$1.init;
|
|
3886
4539
|
|
|
3887
4540
|
/**
|
|
3888
4541
|
* Creates a computed observable that automatically updates when its dependencies change.
|
|
@@ -3903,7 +4556,7 @@ var NativeDocument = (function (exports) {
|
|
|
3903
4556
|
* const batch = Observable.batch(() => { ... });
|
|
3904
4557
|
* const computed = Observable.computed(() => { ... }, batch);
|
|
3905
4558
|
*/
|
|
3906
|
-
Observable.computed = function(callback, dependencies = []) {
|
|
4559
|
+
Observable$1.computed = function(callback, dependencies = []) {
|
|
3907
4560
|
const initialValue = callback();
|
|
3908
4561
|
const observable = new ObservableItem(initialValue);
|
|
3909
4562
|
const updatedValue = nextTick(() => observable.set(callback()));
|
|
@@ -3932,79 +4585,6 @@ var NativeDocument = (function (exports) {
|
|
|
3932
4585
|
return observable;
|
|
3933
4586
|
};
|
|
3934
4587
|
|
|
3935
|
-
const Store = (function() {
|
|
3936
|
-
|
|
3937
|
-
const $stores = new Map();
|
|
3938
|
-
|
|
3939
|
-
return {
|
|
3940
|
-
/**
|
|
3941
|
-
* Create a new state follower and return it.
|
|
3942
|
-
* @param {string} name
|
|
3943
|
-
* @returns {ObservableItem}
|
|
3944
|
-
*/
|
|
3945
|
-
use(name) {
|
|
3946
|
-
const {observer: originalObserver, subscribers } = $stores.get(name);
|
|
3947
|
-
const observerFollower = Observable(originalObserver.val());
|
|
3948
|
-
const unSubscriber = originalObserver.subscribe(value => observerFollower.set(value));
|
|
3949
|
-
const updaterUnsubscriber = observerFollower.subscribe(value => originalObserver.set(value));
|
|
3950
|
-
observerFollower.destroy = () => {
|
|
3951
|
-
unSubscriber();
|
|
3952
|
-
updaterUnsubscriber();
|
|
3953
|
-
observerFollower.cleanup();
|
|
3954
|
-
};
|
|
3955
|
-
subscribers.add(observerFollower);
|
|
3956
|
-
|
|
3957
|
-
return observerFollower;
|
|
3958
|
-
},
|
|
3959
|
-
/**
|
|
3960
|
-
* @param {string} name
|
|
3961
|
-
* @returns {ObservableItem}
|
|
3962
|
-
*/
|
|
3963
|
-
follow(name) {
|
|
3964
|
-
return this.use(name);
|
|
3965
|
-
},
|
|
3966
|
-
/**
|
|
3967
|
-
* Create a new state and return the observer.
|
|
3968
|
-
* @param {string} name
|
|
3969
|
-
* @param {*} value
|
|
3970
|
-
* @returns {ObservableItem}
|
|
3971
|
-
*/
|
|
3972
|
-
create(name, value) {
|
|
3973
|
-
const observer = Observable(value);
|
|
3974
|
-
$stores.set(name, { observer, subscribers: new Set()});
|
|
3975
|
-
return observer;
|
|
3976
|
-
},
|
|
3977
|
-
/**
|
|
3978
|
-
* Get the observer for a state.
|
|
3979
|
-
* @param {string} name
|
|
3980
|
-
* @returns {null|ObservableItem}
|
|
3981
|
-
*/
|
|
3982
|
-
get(name) {
|
|
3983
|
-
const item = $stores.get(name);
|
|
3984
|
-
return item ? item.observer : null;
|
|
3985
|
-
},
|
|
3986
|
-
/**
|
|
3987
|
-
*
|
|
3988
|
-
* @param {string} name
|
|
3989
|
-
* @returns {{observer: ObservableItem, subscribers: Set}}
|
|
3990
|
-
*/
|
|
3991
|
-
getWithSubscribers(name) {
|
|
3992
|
-
return $stores.get(name);
|
|
3993
|
-
},
|
|
3994
|
-
/**
|
|
3995
|
-
* Delete a state.
|
|
3996
|
-
* @param {string} name
|
|
3997
|
-
*/
|
|
3998
|
-
delete(name) {
|
|
3999
|
-
const item = $stores.get(name);
|
|
4000
|
-
if(!item) return;
|
|
4001
|
-
item.observer.cleanup();
|
|
4002
|
-
item.subscribers.forEach(follower => follower.destroy());
|
|
4003
|
-
item.observer.clear();
|
|
4004
|
-
}
|
|
4005
|
-
};
|
|
4006
|
-
}());
|
|
4007
|
-
|
|
4008
4588
|
/**
|
|
4009
4589
|
* Renders a list of items from an observable array or object, automatically updating when data changes.
|
|
4010
4590
|
* Efficiently manages DOM updates by tracking items with keys.
|
|
@@ -4073,7 +4653,7 @@ var NativeDocument = (function (exports) {
|
|
|
4073
4653
|
}
|
|
4074
4654
|
|
|
4075
4655
|
try {
|
|
4076
|
-
const indexObserver = callback.length >= 2 ? Observable(indexKey) : null;
|
|
4656
|
+
const indexObserver = callback.length >= 2 ? Observable$1(indexKey) : null;
|
|
4077
4657
|
let child = ElementCreator.getChild(callback(item, indexObserver));
|
|
4078
4658
|
if(!child) {
|
|
4079
4659
|
throw new NativeDocumentError("ForEach child can't be null or undefined!");
|
|
@@ -4255,7 +4835,7 @@ var NativeDocument = (function (exports) {
|
|
|
4255
4835
|
cache.delete(item);
|
|
4256
4836
|
}
|
|
4257
4837
|
|
|
4258
|
-
const indexObserver = isIndexRequired ? Observable(indexKey) : null;
|
|
4838
|
+
const indexObserver = isIndexRequired ? Observable$1(indexKey) : null;
|
|
4259
4839
|
let child = ElementCreator.getChild(callback(item, indexObserver));
|
|
4260
4840
|
if(child) {
|
|
4261
4841
|
cache.set(item, {
|
|
@@ -4497,7 +5077,7 @@ var NativeDocument = (function (exports) {
|
|
|
4497
5077
|
* HideIf(hasError, Div({}, 'Content'));
|
|
4498
5078
|
*/
|
|
4499
5079
|
const HideIf = function(condition, child, configs) {
|
|
4500
|
-
const hideCondition = Observable(!condition.val());
|
|
5080
|
+
const hideCondition = Observable$1(!condition.val());
|
|
4501
5081
|
condition.subscribe(value => hideCondition.set(!value));
|
|
4502
5082
|
|
|
4503
5083
|
return ShowIf(hideCondition, child, configs);
|
|
@@ -6430,11 +7010,14 @@ var NativeDocument = (function (exports) {
|
|
|
6430
7010
|
configs.body = params;
|
|
6431
7011
|
}
|
|
6432
7012
|
else {
|
|
6433
|
-
configs.headers['Content-Type'] = 'application/json';
|
|
6434
7013
|
if(method !== 'GET') {
|
|
7014
|
+
configs.headers['Content-Type'] = 'application/json';
|
|
6435
7015
|
configs.body = JSON.stringify(params);
|
|
6436
7016
|
} else {
|
|
6437
|
-
|
|
7017
|
+
const queryString = new URLSearchParams(params).toString();
|
|
7018
|
+
if (queryString) {
|
|
7019
|
+
endpoint = endpoint + (endpoint.includes('?') ? '&' : '?') + queryString;
|
|
7020
|
+
}
|
|
6438
7021
|
}
|
|
6439
7022
|
}
|
|
6440
7023
|
}
|
|
@@ -6501,10 +7084,11 @@ var NativeDocument = (function (exports) {
|
|
|
6501
7084
|
exports.ElementCreator = ElementCreator;
|
|
6502
7085
|
exports.HtmlElementWrapper = HtmlElementWrapper;
|
|
6503
7086
|
exports.NDElement = NDElement;
|
|
6504
|
-
exports.Observable = Observable;
|
|
7087
|
+
exports.Observable = Observable$1;
|
|
6505
7088
|
exports.PluginsManager = PluginsManager;
|
|
6506
7089
|
exports.SingletonView = SingletonView;
|
|
6507
7090
|
exports.Store = Store;
|
|
7091
|
+
exports.StoreFactory = StoreFactory;
|
|
6508
7092
|
exports.TemplateCloner = TemplateCloner;
|
|
6509
7093
|
exports.Validator = Validator;
|
|
6510
7094
|
exports.autoMemoize = autoMemoize;
|