native-document 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/native-document.dev.js +133 -31
- package/dist/native-document.min.js +1 -1
- package/docs/observables.md +280 -0
- package/package.json +1 -1
- package/readme.md +27 -0
- package/src/data/Observable.js +30 -2
- package/src/data/ObservableChecker.js +8 -4
- package/src/elements/control/for-each.js +94 -27
- package/src/utils/validator.js +3 -0
|
@@ -193,11 +193,15 @@ var NativeDocument = (function (exports) {
|
|
|
193
193
|
function ObservableChecker($observable, $checker) {
|
|
194
194
|
this.observable = $observable;
|
|
195
195
|
this.checker = $checker;
|
|
196
|
+
const $unSubscriptions = [];
|
|
196
197
|
|
|
197
198
|
this.subscribe = function(callback) {
|
|
198
|
-
|
|
199
|
+
const unSubscribe = $observable.subscribe((value) => {
|
|
199
200
|
callback && callback($checker(value));
|
|
200
201
|
});
|
|
202
|
+
$unSubscriptions.push(unSubscribe);
|
|
203
|
+
|
|
204
|
+
return unSubscribe;
|
|
201
205
|
};
|
|
202
206
|
|
|
203
207
|
this.val = function() {
|
|
@@ -215,7 +219,7 @@ var NativeDocument = (function (exports) {
|
|
|
215
219
|
};
|
|
216
220
|
|
|
217
221
|
this.cleanup = function() {
|
|
218
|
-
|
|
222
|
+
$unSubscriptions.forEach(unSubscription => unSubscription());
|
|
219
223
|
};
|
|
220
224
|
}
|
|
221
225
|
|
|
@@ -356,6 +360,9 @@ var NativeDocument = (function (exports) {
|
|
|
356
360
|
isFunction(value) {
|
|
357
361
|
return typeof value === 'function';
|
|
358
362
|
},
|
|
363
|
+
isAsyncFunction(value) {
|
|
364
|
+
return typeof value === 'function' && value.constructor.name === 'AsyncFunction';
|
|
365
|
+
},
|
|
359
366
|
isObject(value) {
|
|
360
367
|
return typeof value === 'object';
|
|
361
368
|
},
|
|
@@ -493,17 +500,45 @@ var NativeDocument = (function (exports) {
|
|
|
493
500
|
return new ObservableItem(value);
|
|
494
501
|
}
|
|
495
502
|
|
|
503
|
+
/**
|
|
504
|
+
*
|
|
505
|
+
* @param {Function} callback
|
|
506
|
+
* @param {Array|Function} dependencies
|
|
507
|
+
* @returns {ObservableItem}
|
|
508
|
+
*/
|
|
496
509
|
Observable.computed = function(callback, dependencies = []) {
|
|
497
510
|
const initialValue = callback();
|
|
498
511
|
const observable = new ObservableItem(initialValue);
|
|
499
|
-
|
|
500
512
|
const updatedValue = () => observable.set(callback());
|
|
501
513
|
|
|
514
|
+
if(Validator.isFunction(dependencies)) {
|
|
515
|
+
if(!Validator.isObservable(dependencies.$observer)) {
|
|
516
|
+
throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
|
|
517
|
+
}
|
|
518
|
+
dependencies.$observer.subscribe(updatedValue);
|
|
519
|
+
return observable;
|
|
520
|
+
}
|
|
521
|
+
|
|
502
522
|
dependencies.forEach(dependency => dependency.subscribe(updatedValue));
|
|
503
523
|
|
|
504
524
|
return observable;
|
|
505
525
|
};
|
|
506
526
|
|
|
527
|
+
Observable.batch = function(callback) {
|
|
528
|
+
const $observer = Observable(0);
|
|
529
|
+
const batch = function() {
|
|
530
|
+
if(Validator.isAsyncFunction(callback)) {
|
|
531
|
+
return (callback(...arguments)).then(() => {
|
|
532
|
+
$observer.trigger();
|
|
533
|
+
}).catch(error => { throw error; });
|
|
534
|
+
}
|
|
535
|
+
callback(...arguments);
|
|
536
|
+
$observer.trigger();
|
|
537
|
+
};
|
|
538
|
+
batch.$observer = $observer;
|
|
539
|
+
return batch;
|
|
540
|
+
};
|
|
541
|
+
|
|
507
542
|
/**
|
|
508
543
|
*
|
|
509
544
|
* @param id
|
|
@@ -654,7 +689,7 @@ var NativeDocument = (function (exports) {
|
|
|
654
689
|
};
|
|
655
690
|
});
|
|
656
691
|
|
|
657
|
-
const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find'];
|
|
692
|
+
const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex'];
|
|
658
693
|
overrideMethods.forEach((method) => {
|
|
659
694
|
observer[method] = function(callback) {
|
|
660
695
|
return observer.val()[method](callback);
|
|
@@ -891,7 +926,7 @@ var NativeDocument = (function (exports) {
|
|
|
891
926
|
if(Validator.isElement(child)) {
|
|
892
927
|
return child;
|
|
893
928
|
}
|
|
894
|
-
return createTextNode(
|
|
929
|
+
return createTextNode(child)
|
|
895
930
|
};
|
|
896
931
|
|
|
897
932
|
function Anchor(name) {
|
|
@@ -1411,12 +1446,21 @@ var NativeDocument = (function (exports) {
|
|
|
1411
1446
|
* @param {Set} keyIds
|
|
1412
1447
|
*/
|
|
1413
1448
|
const cleanBlockByCache = (cache, keyIds) => {
|
|
1414
|
-
|
|
1449
|
+
const toRemove = [];
|
|
1450
|
+
for(const [key, cacheItem] of cache.entries()) {
|
|
1415
1451
|
if(keyIds.has(key)) {
|
|
1416
1452
|
continue;
|
|
1417
1453
|
}
|
|
1418
|
-
|
|
1454
|
+
toRemove.push({ key, cacheItem });
|
|
1419
1455
|
}
|
|
1456
|
+
if(toRemove.length === 0) {
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
toRemove.forEach(({ key, cacheItem }) => {
|
|
1460
|
+
cacheItem.child.remove();
|
|
1461
|
+
cacheItem.indexObserver.cleanup();
|
|
1462
|
+
cache.delete(key);
|
|
1463
|
+
});
|
|
1420
1464
|
};
|
|
1421
1465
|
|
|
1422
1466
|
/**
|
|
@@ -1429,33 +1473,102 @@ var NativeDocument = (function (exports) {
|
|
|
1429
1473
|
function ForEach(data, callback, key) {
|
|
1430
1474
|
const element = new Anchor('ForEach');
|
|
1431
1475
|
const blockEnd = element.endElement();
|
|
1476
|
+
const blockStart = element.startElement();
|
|
1432
1477
|
|
|
1433
1478
|
let cache = new Map();
|
|
1479
|
+
const keyIds = new Set();
|
|
1434
1480
|
|
|
1435
1481
|
const handleContentItem = (item, indexKey) => {
|
|
1436
1482
|
const keyId = getKey(item, indexKey, key);
|
|
1437
1483
|
|
|
1438
1484
|
if(cache.has(keyId)) {
|
|
1439
|
-
cache.get(keyId)
|
|
1485
|
+
const cacheItem = cache.get(keyId);
|
|
1486
|
+
cacheItem.indexObserver.set(indexKey);
|
|
1487
|
+
cacheItem.isNew = false;
|
|
1440
1488
|
}
|
|
1441
1489
|
else {
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
child =
|
|
1490
|
+
|
|
1491
|
+
try {
|
|
1492
|
+
const indexObserver = Observable(indexKey);
|
|
1493
|
+
let child = callback(item, indexObserver);
|
|
1494
|
+
if(Validator.isStringOrObservable(child)) {
|
|
1495
|
+
child = createTextNode(child);
|
|
1496
|
+
}
|
|
1497
|
+
cache.set(keyId, { isNew: true, child, indexObserver});
|
|
1498
|
+
} catch (e) {
|
|
1499
|
+
DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
|
|
1500
|
+
throw e;
|
|
1446
1501
|
}
|
|
1447
|
-
cache.set(keyId, { child, indexObserver});
|
|
1448
1502
|
}
|
|
1449
1503
|
return keyId;
|
|
1450
1504
|
};
|
|
1451
|
-
const keyIds = new Set();
|
|
1452
1505
|
|
|
1453
|
-
const
|
|
1454
|
-
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
1506
|
+
const batchDOMUpdates = () => {
|
|
1455
1507
|
const parent = blockEnd.parentNode;
|
|
1456
1508
|
if(!parent) {
|
|
1457
1509
|
return;
|
|
1458
1510
|
}
|
|
1511
|
+
|
|
1512
|
+
let previousElementSibling = blockStart;
|
|
1513
|
+
const elementsToInsert = [];
|
|
1514
|
+
const elementsToMove = [];
|
|
1515
|
+
let fragment = null;
|
|
1516
|
+
|
|
1517
|
+
let saveFragment = (beforeTarget) => {
|
|
1518
|
+
if(fragment) {
|
|
1519
|
+
elementsToInsert.push({ child: fragment, before: beforeTarget });
|
|
1520
|
+
fragment = null;
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
|
|
1524
|
+
const keyIdsArray = Array.from(keyIds);
|
|
1525
|
+
for(let i = 0; i < keyIdsArray.length; i++) {
|
|
1526
|
+
const itemKey = keyIdsArray[i];
|
|
1527
|
+
const cacheItem = cache.get(itemKey);
|
|
1528
|
+
if(!cacheItem) {
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
if(previousElementSibling && previousElementSibling.nextSibling === cacheItem.child) {
|
|
1533
|
+
previousElementSibling = cacheItem.child;
|
|
1534
|
+
saveFragment(cacheItem.child);
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
if(cacheItem.isNew) {
|
|
1538
|
+
fragment = fragment || document.createDocumentFragment();
|
|
1539
|
+
fragment.append(cacheItem.child);
|
|
1540
|
+
cacheItem.isNew = false;
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
saveFragment(cacheItem.child);
|
|
1544
|
+
const nextChild = cache.get(keyIdsArray[i + 1])?.child;
|
|
1545
|
+
if(nextChild) {
|
|
1546
|
+
if(cacheItem.child.nextSibling !== nextChild) {
|
|
1547
|
+
elementsToMove.push({ child: cacheItem.child, before: nextChild });
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
previousElementSibling = cacheItem.child;
|
|
1552
|
+
}
|
|
1553
|
+
saveFragment(blockEnd);
|
|
1554
|
+
|
|
1555
|
+
elementsToInsert.forEach(({ child, before }) => {
|
|
1556
|
+
if(before) {
|
|
1557
|
+
parent.insertBefore(child, before);
|
|
1558
|
+
} else {
|
|
1559
|
+
element.appendChild(child);
|
|
1560
|
+
}
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
elementsToMove.forEach(({ child, before }) => {
|
|
1564
|
+
parent.insertBefore(child, before);
|
|
1565
|
+
});
|
|
1566
|
+
saveFragment = null;
|
|
1567
|
+
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
const buildContent = () => {
|
|
1571
|
+
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
1459
1572
|
keyIds.clear();
|
|
1460
1573
|
if(Array.isArray(items)) {
|
|
1461
1574
|
items.forEach((item, index) => keyIds.add(handleContentItem(item, index)));
|
|
@@ -1466,25 +1579,13 @@ var NativeDocument = (function (exports) {
|
|
|
1466
1579
|
}
|
|
1467
1580
|
|
|
1468
1581
|
cleanBlockByCache(cache, keyIds);
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
const { child } = cache.get(item);
|
|
1472
|
-
if(child) {
|
|
1473
|
-
if(nextElementSibling && nextElementSibling.previousSibling === child) {
|
|
1474
|
-
nextElementSibling = child;
|
|
1475
|
-
continue;
|
|
1476
|
-
}
|
|
1477
|
-
parent.insertBefore(child, nextElementSibling);
|
|
1478
|
-
nextElementSibling = child;
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1582
|
+
|
|
1583
|
+
batchDOMUpdates();
|
|
1481
1584
|
};
|
|
1482
1585
|
|
|
1483
1586
|
buildContent();
|
|
1484
1587
|
if(Validator.isObservable(data)) {
|
|
1485
|
-
data.subscribe(
|
|
1486
|
-
buildContent();
|
|
1487
|
-
}, 50, { debounce: true }));
|
|
1588
|
+
data.subscribe(buildContent);
|
|
1488
1589
|
}
|
|
1489
1590
|
return element;
|
|
1490
1591
|
}
|
|
@@ -1916,6 +2017,7 @@ var NativeDocument = (function (exports) {
|
|
|
1916
2017
|
Menu: Menu,
|
|
1917
2018
|
Meter: Meter,
|
|
1918
2019
|
MonthInput: MonthInput,
|
|
2020
|
+
NativeDocumentFragment: Anchor,
|
|
1919
2021
|
Nav: Nav,
|
|
1920
2022
|
NumberInput: NumberInput,
|
|
1921
2023
|
Option: Option,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var NativeDocument=function(t){"use strict";function n(e,t,n){return e.addEventListener(t,n),e}const r={enabled:!1,enable(){this.enabled=!0,console.log("🔍 NativeDocument Debug Mode enabled")},disable(){this.enabled=!1},log(e,t,n){this.enabled&&(console.group(`🔍 [${e}] ${t}`),n&&console.log(n),console.trace(),console.groupEnd())},warn(e,t,n){this.enabled&&console.warn(`⚠️ [${e}] ${t}`,n)},error(e,t,n){console.error(`❌ [${e}] ${t}`,n)}},o=function(){let e=0;const t=new Map;let n=null;try{n=new FinalizationRegistry(e=>{r.log("MemoryManager","🧹 Auto-cleanup observable:",e),e.listeners.splice(0)})}catch(e){r.warn("MemoryManager","FinalizationRegistry not supported, observables will not be cleaned automatically")}return{register(r,o){const s=++e,i={id:s,listeners:o};return n&&n.register(r,i),t.set(s,new WeakRef(r)),s},getObservableById:e=>t.get(e)?.deref(),cleanup(){for(const[e,n]of t){const e=n.deref();e&&e.cleanup()}t.clear()},cleanObservables(e){if(t.size<e)return;let n=0;for(const[e,r]of t)r.deref()||(t.delete(e),n++);n>0&&r.log("Memory Auto Clean",`🧹 Cleaned ${n} orphaned observables`)}}}();class s extends Error{constructor(e,t={}){super(e),this.name="NativeDocumentError",this.context=t,this.timestamp=(new Date).toISOString()}}function i(e,t){this.observable=e,this.checker=t,this.subscribe=function(n){return e.subscribe(e=>{n&&n(t(e))})},this.val=function(){return t&&t(e.val())},this.check=function(t){return e.check(()=>t(this.val()))},this.set=function(t){return e.set(t)},this.trigger=function(){return e.trigger()},this.cleanup=function(){return e.cleanup()}}function a(e){if(void 0===e)throw new s("ObservableItem requires an initial value");if(e instanceof a)throw new s("ObservableItem cannot be an Observable");const t="object"==typeof e?JSON.parse(JSON.stringify(e)):e;let n=e,u=e,c=!1;const l=[],d=o.register(this,l);this.trigger=()=>{l.forEach(e=>{try{e(u,n)}catch(t){r.error("Listener Undefined","Error in observable listener:",t),this.unsubscribe(e)}})},this.originalValue=()=>t,this.set=e=>{const t="function"==typeof e?e(u):e;u!==t&&(n=u,u=t,this.trigger())},this.val=()=>u,this.cleanup=function(){l.splice(0),c=!0},this.subscribe=e=>{if(c)return r.warn("Observable subscription","⚠️ Attempted to subscribe to a cleaned up observable."),()=>{};if("function"!=typeof e)throw new s("Callback must be a function");return l.push(e),()=>this.unsubscribe(e)},this.unsubscribe=e=>{const t=l.indexOf(e);t>-1&&l.splice(t,1)},this.check=function(e){return new i(this,e)};const p=this;Object.defineProperty(p,"$value",{get:()=>p.val(),set:e=>(p.set(e),p)}),this.toString=function(){return"{{#ObItem::("+d+")}}"}}const u={isObservable:e=>e instanceof a||e instanceof i,isProxy:e=>e?.__isProxy__,isObservableChecker:e=>e instanceof i,isArray:e=>Array.isArray(e),isString:e=>"string"==typeof e,isNumber:e=>"number"==typeof e,isBoolean:e=>"boolean"==typeof e,isFunction:e=>"function"==typeof e,isObject:e=>"object"==typeof e,isJson:e=>"object"==typeof e&&null!==e&&"Object"===e.constructor.name&&!Array.isArray(e),isElement:e=>e instanceof HTMLElement||e instanceof DocumentFragment||e instanceof Text,isFragment:e=>e instanceof DocumentFragment,isStringOrObservable(e){return this.isString(e)||this.isObservable(e)},isValidChild(e){return null===e||this.isElement(e)||this.isObservable(e)||["string","number","boolean"].includes(typeof e)},isValidChildren(e){Array.isArray(e)||(e=[e]);return 0===e.filter(e=>!this.isValidChild(e)).length},validateChildren(e){Array.isArray(e)||(e=[e]);const t=e.filter(e=>!this.isValidChild(e));if(t.length>0)throw new s(`Invalid children detected: ${t.map(e=>typeof e).join(", ")}`);return e},containsObservables:e=>!!e&&(u.isObject(e)&&Object.values(e).some(e=>u.isObservable(e))),containsObservableReference:e=>!(!e||"string"!=typeof e)&&/\{\{#ObItem::\([0-9]+\)\}\}/.test(e),validateAttributes(e){if(!e||"object"!=typeof e)return e;const t=[],n=Object.keys(e).filter(e=>t.includes(e));return n.length>0&&r.warn("Validator",`Reserved attributes found: ${n.join(", ")}`),e},validateEventCallback(e){if("function"!=typeof e)throw new s("Event callback must be a function")}},c=["checked","selected","disabled","readonly","required","autofocus","multiple","autocomplete","hidden","contenteditable","spellcheck","translate","draggable","async","defer","autoplay","controls","loop","muted","download","reversed","open","default","formnovalidate","novalidate","scoped","itemscope","allowfullscreen","allowpaymentrequest","playsinline"],l=function(e,t,n={}){let r=null,o=0;const{leading:s=!0,trailing:i=!0,debounce:a=!1}=n;return function(...n){const u=Date.now();if(a)return clearTimeout(r),void(r=setTimeout(()=>e.apply(this,n),t));s&&u-o>=t&&(e.apply(this,n),o=u),i&&!r&&(r=setTimeout(()=>{e.apply(this,n),o=Date.now(),r=null},t-(u-o)))}},d=function(e,t){return e.replace(new RegExp(`^[${t}]+|[${t}]+$`,"g"),"")};function p(e){return new a(e)}p.computed=function(e,t=[]){const n=new a(e()),r=()=>n.set(e());return t.forEach(e=>e.subscribe(r)),n},p.getById=function(e){const t=o.getObservableById(parseInt(e));if(!t)throw new s("Observable.getById : No observable found with id "+e);return t},p.cleanup=function(e){e.cleanup()},p.value=function(e){if(u.isObservable(e))return e.val();if(u.isProxy(e))return e.$val();if(u.isArray(e)){const t=[];return e.forEach(e=>{t.push(p.value(e))}),t}return e},p.init=function(e){const t={};for(const n in e){const r=e[n];u.isJson(r)?t[n]=p.init(r):u.isArray(r)?t[n]=p.array(r):t[n]=p(r)}const n=function(){const e={};for(const n in t){const r=t[n];u.isObservable(r)?e[n]=r.val():u.isProxy(r)?e[n]=r.$val():e[n]=r}return e},r=function(){};return new Proxy(t,{get:(e,t)=>"__isProxy__"===t||("$val"===t?n:"$clone"===t?r:void 0!==e[t]?e[t]:void 0),set(e,t,n){void 0!==e[t]&&e[t].set(n)}})},p.object=p.init,p.json=p.init,p.update=function(e,t){for(const n in t){const r=e[n],o=t[n];if(u.isObservable(r)){if(u.isArray(o)){p.update(r,o);continue}r.set(o)}else u.isProxy(r)?p.update(r,o):e[n]=o}},p.array=function(e){if(!Array.isArray(e))throw new s("Observable.array : target must be an array");const t=p(e);["push","pop","shift","unshift","reverse","sort","splice"].forEach(e=>{t[e]=function(...n){const r=t.val(),o=r[e].apply(r,arguments);return t.trigger(),o}});return["map","filter","reduce","some","every","find"].forEach(e=>{t[e]=function(n){return t.val()[e](n)}}),t},p.autoCleanup=function(e=!1,t={}){if(!e)return;const{interval:n=6e4,threshold:r=100}=t;window.addEventListener("beforeunload",()=>{o.cleanup()}),setInterval(()=>o.cleanObservables(r),n)};const h=function(e,t,n){n?e.classList.add(t):e.classList.remove(t)};function f(e,t){for(let n in t){const r=t[n];u.isObservable(r)?(h(e,n,r.val()),r.subscribe(t=>h(e,n,t))):h(e,n,r)}}function b(e,t){for(let n in t){const r=t[n];u.isObservable(r)?(e.style[n]=r.val(),r.subscribe(t=>{e.style[n]=t})):e.style[n]=r}}function m(e,t,n){const r=u.isObservable(n)?n.val():n;u.isBoolean(r)?e[t]=r:e[t]=r===e.value,u.isObservable(n)&&(["checked"].includes(t)&&e.addEventListener("input",()=>{u.isBoolean(r)?n.set(e[t]):n.set(e.value)}),n.subscribe(n=>{u.isBoolean(n)?e[t]=n:e[t]=n===e.value}))}function v(e,t,n){const r=n=>{"value"!==t?e.setAttribute(t,n):e.value=n};n.subscribe(r),r(n.val()),"value"===t&&e.addEventListener("input",()=>n.set(e.value))}const y={elements:new Map,observer:null,checkMutation:l(function(){for(const[e,t]of y.elements.entries()){const n=document.body.contains(e);n&&!t.inDom?(t.inDom=!0,t.mounted.forEach(t=>t(e))):!n&&t.inDom&&(t.inDom=!1,t.unmounted.forEach(t=>t(e)))}},10,{debounce:!0}),watch:function(e){let t={};if(y.elements.has(e))t=y.elements.get(e);else{const n=document.body.contains(e);t={inDom:n,mounted:new Set,unmounted:new Set},y.elements.set(e,t)}return{watch:()=>y.elements.set(e,t),disconnect:()=>y.elements.delete(e),mounted:e=>t.mounted.add(e),unmounted:e=>t.unmounted.add(e)}}};y.observer=new MutationObserver(y.checkMutation),y.observer.observe(document.body,{childList:!0,subtree:!0});const g=e=>u.isFunction(e)?g(e()):u.isElement(e)?e:S(String(e));function w(e){const t=document.createDocumentFragment(),n=document.createComment("Anchor Start : "+e),o=document.createComment("/ Anchor End "+e);t.appendChild(n),t.appendChild(o),t.nativeInsertBefore=t.insertBefore,t.nativeAppendChild=t.appendChild;const s=function(e,n,r){e!==t?e.insertBefore(g(n),r):e.nativeInsertBefore(g(n),r)};return t.appendChild=function(e,n=null){const i=o.parentNode;if(i){if(n=n??o,u.isArray(e))return e.forEach(e=>{s(i,e,n)}),t;s(i,e,n)}else r.error("Anchor","Anchor : parent not found",e)},t.remove=function(e){if(o.parentNode===t)return;let r,s=n.nextSibling;for(;s!==o;)r=s.nextSibling,e?s.remove():t.nativeAppendChild(s),s=r;e&&(o.remove(),n.remove())},t.insertBefore=function(e,n=null){t.appendChild(e,n)},t.clear=function(){t.remove()},t.endElement=function(){return o},t.startElement=function(){return n},t}const O=function(){const e=[];return{list:()=>e,add:t=>e.push(t)}}(),C=function(e,t){const n=document.createTextNode("");return t.subscribe(e=>n.textContent=String(e)),n.textContent=t.val(),e&&e.appendChild(n),n},E=function(e,t){const n=document.createTextNode("");return n.textContent=String(t),e&&e.appendChild(n),n},S=function(e){return u.isObservable(e)?C(null,e):E(null,e)},A={createElement:e=>e?document.createElement(e):new w("Fragment"),processChildren(e,t){if(null===e)return;(Array.isArray(e)?e:[e]).forEach(e=>{null!==e&&(u.isString(e)&&u.isFunction(e.resolveObservableTemplate)&&(e=e.resolveObservableTemplate()),u.isFunction(e)?this.processChildren(e(),t):u.isArray(e)?this.processChildren(e,t):u.isElement(e)?t.appendChild(e):u.isObservable(e)?C(t,e):e&&E(t,e))})},processAttributes(e,t){u.isFragment(e)||t&&function(e,t){if(u.validateAttributes(t),!u.isObject(t))throw new s("Attributes must be an object");for(let n in t){const r=n.toLowerCase();let o=t[r];if(u.isString(o)&&u.isFunction(o.resolveObservableTemplate)&&(o=o.resolveObservableTemplate(),u.isArray(o))){const e=o.filter(e=>u.isObservable(e));o=p.computed(()=>o.map(e=>u.isObservable(e)?e.val():e).join(" ")||" ",e)}c.includes(r)?m(e,r,o):u.isObservable(o)?v(e,r,o):"class"===r&&u.isJson(o)?f(e,o):"style"===r&&u.isJson(o)?b(e,o):e.setAttribute(r,o)}}(e,t)},setup(e,t,r){e.nd={},function(e){e.nd||(e.nd={}),e.nd.on=function(t){for(const r in t){const o=t[r];n(e,r,o)}return e},e.nd.on.prevent=function(t){for(const r in t){const o=t[r];n(e,r,t=>(t.preventDefault(),o&&o(t),e))}return e};const t={click:t=>n(e,"click",t),focus:t=>n(e,"focus",t),blur:t=>n(e,"blur",t),input:t=>n(e,"input",t),change:t=>n(e,"change",t),keyup:t=>n(e,"keyup",t),keydown:t=>n(e,"keydown",t),beforeInput:t=>n(e,"beforeinput",t),mouseOver:t=>n(e,"mouseover",t),mouseOut:t=>n(e,"mouseout",t),mouseDown:t=>n(e,"mousedown",t),mouseUp:t=>n(e,"mouseup",t),mouseMove:t=>n(e,"mousemove",t),hover:(t,n)=>{e.addEventListener("mouseover",t),e.addEventListener("mouseout",n)},dropped:t=>n(e,"drop",t),submit:t=>n(e,"submit",t),dragEnd:t=>n(e,"dragend",t),dragStart:t=>n(e,"dragstart",t),drop:t=>n(e,"drop",t),dragOver:t=>n(e,"dragover",t),dragEnter:t=>n(e,"dragenter",t),dragLeave:t=>n(e,"dragleave",t)};for(let r in t)e.nd.on[r]=t[r],e.nd.on.prevent[r]=function(t){return n(e,r.toLowerCase(),e=>{e.preventDefault(),t&&t(e)}),e}}(e);const o="function"==typeof r?r(e):e;return function(e){e.nd.wrap=t=>{if(!u.isFunction(t))throw new s("Callback must be a function");return t&&t(e),e},e.nd.ref=(t,n)=>(t[n]=e,e);let t=null;e.nd.appendChild=function(t){u.isArray(t)?A.processChildren(t,e):(u.isFunction(t)&&(t=t(),A.processChildren(t(),e)),u.isElement(t)&&A.processChildren(t,e))},e.nd.lifecycle=function(n){return t=t||y.watch(e),n.mounted&&t.mounted(n.mounted),n.unmounted&&t.unmounted(n.unmounted),e},e.nd.mounted=n=>(t=t||y.watch(e),t.mounted(n),e),e.nd.unmounted=n=>(t=t||y.watch(e),t.unmounted(n),e)}(o),O.list().forEach(e=>{e?.element?.setup&&e.element.setup(o,t)}),o}};function I(e,t){const n=e.toLowerCase().trim(),o=function(e,o=null){try{if(u.isValidChildren(e)){const t=o;o=e,e=t}const r=A.createElement(n);return A.processAttributes(r,e),A.processChildren(o,r),A.setup(r,e,t)}catch(e){r.error("ElementCreation",`Error creating ${n}`,e)}};return o.hold=(e,t)=>()=>o(e,t),o}class k extends Error{constructor(e,t){super(`${e}\n\n${t.join("\n")}\n\n`)}}const $={string:e=>({name:e,type:"string",validate:e=>u.isString(e)}),number:e=>({name:e,type:"number",validate:e=>u.isNumber(e)}),boolean:e=>({name:e,type:"boolean",validate:e=>u.isBoolean(e)}),observable:e=>({name:e,type:"observable",validate:e=>u.isObservable(e)}),element:e=>({name:e,type:"element",validate:e=>u.isElement(e)}),function:e=>({name:e,type:"function",validate:e=>u.isFunction(e)}),object:e=>({name:e,type:"object",validate:e=>u.isObject(e)}),objectNotNull:e=>({name:e,type:"object",validate:e=>u.isObject(e)&&null!==e}),children:e=>({name:e,type:"children",validate:e=>u.validateChildren(e)}),attributes:e=>({name:e,type:"attributes",validate:e=>u.validateAttributes(e)}),optional:e=>({...e,optional:!0}),oneOf:(e,...t)=>({name:e,type:"oneOf",types:t,validate:e=>t.some(t=>t.validate(e))})},R=(e,t,n="Function")=>{if(!u.isArray(t))throw new s("withValidation : argSchema must be an array");return function(...r){return((e,t,n="Function")=>{if(!t)return;const r=[],o=t.filter(e=>!e.optional).length;if(e.length<o&&r.push(`${n}: Expected at least ${o} arguments, got ${e.length}`),t.forEach((t,o)=>{const s=o+1,i=e[o];if(void 0!==i){if(!t.validate(i)){const e=i?.constructor?.name||typeof i;r.push(`${n}: Invalid argument '${t.name}' at position ${s}, expected ${t.type}, got ${e}`)}}else t.optional||r.push(`${n}: Missing required argument '${t.name}' at position ${s}`)}),r.length>0)throw new k("Argument validation failed",r)})(r,t,e.name||n),e.apply(this,r)}};Function.prototype.args=function(...e){return R(this,e)},Function.prototype.errorBoundary=function(e){return(...t)=>{try{return this.apply(this,t)}catch(t){return e(t)}}},String.prototype.use=function(e){const t=this;return p.computed(()=>t.replace(/\$\{(.*?)}/g,(t,n)=>{const r=e[n];return u.isObservable(r)?r.val():r}),Object.values(e))},String.prototype.resolveObservableTemplate=function(){return u.containsObservableReference(this)?this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map(e=>{if(!u.containsObservableReference(e))return e;const[t,n]=e.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);return p.getById(n)}):this};const F=function(){const e=new Map;return{use(t){const{observer:n,subscribers:r}=e.get(t),o=p(n.val()),s=n.subscribe(e=>o.set(e)),i=o.subscribe(e=>n.set(e));return o.destroy=()=>{s(),i(),o.cleanup()},r.add(o),o},follow(e){return this.use(e)},create(t,n){const r=p(n);return e.set(t,{observer:r,subscribers:new Set}),r},get(t){const n=e.get(t);return n?n.observer:null},getWithSubscribers:t=>e.get(t),delete(t){const n=e.get(t);n&&(n.observer.cleanup(),n.subscribers.forEach(e=>e.destroy()),n.observer.clear())}}}();const q=function(e,t,n=null){if(!u.isObservable(e))return r.warn("ShowIf","ShowIf : condition must be an Observable / "+n,e);const o=new w("Show if : "+(n||""));let s=null;const i=()=>s||(s="function"==typeof t?t():t,u.isStringOrObservable(s)&&(s=S(s)),s);return e.val()&&o.appendChild(i()),e.subscribe(e=>{e?o.appendChild(i()):o.remove()}),o},x=function(e,t){if(!u.isObservable(e))throw new s("Toggle : condition must be an Observable");const n=new w,r=new Map,o=function(e){if(r.has(e))return r.get(e);let n=t[e];return n?(u.isFunction(n)&&(n=n()),r.set(e,n),n):null},i=e.val(),a=o(i);return a&&n.appendChild(a),e.subscribe(e=>{const t=o(e);n.remove(),t&&n.appendChild(t)}),n},T=function(e,t,n){if(!u.isObservable(e))throw new s("Toggle : condition must be an Observable");return x(e,{true:t,false:n})},D=I("div"),j=I("span"),L=I("label"),B=I("p"),M=B,H=I("strong"),P=I("h1"),N=I("h2"),_=I("h3"),V=I("h4"),U=I("h5"),z=I("h6"),J=I("br"),W=I("a"),K=I("pre"),Q=I("code"),G=I("blockquote"),X=I("hr"),Y=I("em"),Z=I("small"),ee=I("mark"),te=I("del"),ne=I("ins"),re=I("sub"),oe=I("sup"),se=I("abbr"),ie=I("cite"),ae=I("q"),ue=I("dl"),ce=I("dt"),le=I("dd"),de=I("form",function(e){return e.submit=function(t){return"function"==typeof t?(e.on.submit(e=>{e.preventDefault(),t(e)}),e):(this.setAttribute("action",t),e)},e.multipartFormData=function(){return this.setAttribute("enctype","multipart/form-data"),e},e.post=function(t){return this.setAttribute("method","post"),this.setAttribute("action",t),e},e.get=function(e){this.setAttribute("method","get"),this.setAttribute("action",e)},e}),pe=I("input"),he=I("textarea"),fe=he,be=I("select"),me=I("fieldset"),ve=I("option"),ye=I("legend"),ge=I("datalist"),we=I("output"),Oe=I("progress"),Ce=I("meter"),Ee=I("button"),Se=I("main"),Ae=I("section"),Ie=I("article"),ke=I("aside"),$e=I("nav"),Re=I("figure"),Fe=I("figcaption"),qe=I("header"),xe=I("footer"),Te=I("img"),De=function(e,t){return Te({src:e,...t})},je=I("details"),Le=I("summary"),Be=I("dialog"),Me=I("menu"),He=I("ol"),Pe=I("ul"),Ne=I("li"),_e=I("audio"),Ve=I("video"),Ue=I("source"),ze=I("track"),Je=I("canvas"),We=I("svg"),Ke=I("time"),Qe=I("data"),Ge=I("address"),Xe=I("kbd"),Ye=I("samp"),Ze=I("var"),et=I("wbr"),tt=I("caption"),nt=I("table"),rt=I("thead"),ot=I("tfoot"),st=I("tbody"),it=I("tr"),at=it,ut=I("th"),ct=ut,lt=ut,dt=I("td"),pt=dt,ht=I("");var ft=Object.freeze({__proto__:null,Abbr:se,Address:Ge,Anchor:w,Article:Ie,Aside:ke,AsyncImg:function(e,t,n,r){const o=De(t||e,n),i=new Image;return i.onload=()=>{u.isFunction(r)&&r(null,o),o.src=e},i.onerror=()=>{u.isFunction(r)&&r(new s("Image not found"))},u.isObservable(e)&&e.subscribe(e=>{i.src=e}),i.src=e,o},Audio:_e,BaseImage:Te,Blockquote:G,Br:J,Button:Ee,Canvas:Je,Caption:tt,Checkbox:e=>pe({type:"checkbox",...e}),Cite:ie,Code:Q,ColorInput:e=>pe({type:"color",...e}),Data:Qe,Datalist:ge,DateInput:e=>pe({type:"date",...e}),DateTimeInput:e=>pe({type:"datetime-local",...e}),Dd:le,Del:te,Details:je,Dialog:Be,Div:D,Dl:ue,Dt:ce,Em:Y,EmailInput:e=>pe({type:"email",...e}),FieldSet:me,FigCaption:Fe,Figure:Re,FileInput:e=>pe({type:"file",...e}),Footer:xe,ForEach:function(e,t,n){const r=new w("ForEach"),o=r.endElement();let s=new Map;const i=(e,r)=>{const o=((e,t,n)=>{if(u.isFunction(n))return n(e,t);if(u.isObservable(e)){const r=e.val();return r&&n?r[n]:t}return e[n]??t})(e,r,n);if(s.has(o))s.get(o).indexObserver.set(r);else{const n=p(r);let i=t(e,n);u.isStringOrObservable(i)&&(i=S(i)),s.set(o,{child:i,indexObserver:n})}return o},a=new Set,c=()=>{const t=u.isObservable(e)?e.val():e,n=o.parentNode;if(!n)return;if(a.clear(),Array.isArray(t))t.forEach((e,t)=>a.add(i(e,t)));else for(const e in t)a.add(i(t[e],e));((e,t)=>{for(const[n,{child:r}]of e.entries())t.has(n)||r.remove()})(s,a);let r=o;for(const e of[...a].reverse()){const{child:t}=s.get(e);if(t){if(r&&r.previousSibling===t){r=t;continue}n.insertBefore(t,r),r=t}}};return c(),u.isObservable(e)&&e.subscribe(l((e,t)=>{c()},50,{debounce:!0})),r},Form:de,Fragment:ht,H1:P,H2:N,H3:_,H4:V,H5:U,H6:z,Header:qe,HiddenInput:e=>pe({type:"hidden",...e}),HideIf:function(e,t,n){const r=p(!e.val());return e.subscribe(e=>r.set(!e)),q(r,t,n)},HideIfNot:function(e,t,n){return q(e,t,n)},Hr:X,Img:De,Input:pe,Ins:ne,Kbd:Xe,Label:L,LazyImg:function(e,t){return De(e,{...t,loading:"lazy"})},Legend:ye,Link:W,ListItem:Ne,Main:Se,Mark:ee,Match:x,Menu:Me,Meter:Ce,MonthInput:e=>pe({type:"month",...e}),Nav:$e,NumberInput:e=>pe({type:"number",...e}),Option:ve,OrderedList:He,Output:we,P:B,Paragraph:M,PasswordInput:e=>pe({type:"password",...e}),Pre:K,Progress:Oe,Quote:ae,Radio:e=>pe({type:"radio",...e}),RangeInput:e=>pe({type:"range",...e}),ReadonlyInput:e=>pe({readonly:!0,...e}),Samp:Ye,SearchInput:e=>pe({type:"search",...e}),Section:Ae,Select:be,ShowIf:q,SimpleButton:(e,t)=>Ee(e,{type:"button",...t}),Small:Z,Source:Ue,Span:j,Strong:H,Sub:re,SubmitButton:(e,t)=>Ee(e,{type:"submit",...t}),Summary:Le,Sup:oe,Svg:We,Switch:T,TBody:st,TBodyCell:pt,TFoot:ot,TFootCell:lt,THead:rt,THeadCell:ct,TRow:at,Table:nt,Td:dt,TelInput:e=>pe({type:"tel",...e}),TextArea:he,TextInput:fe,Th:ut,Time:Ke,TimeInput:e=>pe({type:"time",...e}),Tr:it,Track:ze,UnorderedList:Pe,UrlInput:e=>pe({type:"url",...e}),Var:Ze,Video:Ve,Wbr:et,WeekInput:e=>pe({type:"week",...e}),When:function(e){if(!u.isObservable(e))throw new s("When : condition must be an Observable");let t=null,n=null;return{show(e){return t=e,this},otherwise:r=>(n=r,T(e,t,n))}}});const bt={};function mt(e,t,n={}){e="/"+d(e,"/");let r=null,o=n.name||null;const s=n.middlewares||[],i=n.shouldRebuild||!1,a=n.with||{},u={},c=[],l=e=>{if(!e)return null;const[t,n]=e.split(":");let r=a[t];return!r&&n&&(r=bt[n]),r||(r="[^/]+"),r=r.replace("(","(?:"),{name:t,pattern:`(${r})`}},p=()=>{if(r)return r;const t=e.replace(/\{(.*?)}/gi,(e,t)=>{const n=l(t);return n&&n.pattern?(u[n.name]=n.pattern,c.push(n.name),n.pattern):e});return r=new RegExp("^"+t+"$"),r};this.name=()=>o,this.component=()=>t,this.middlewares=()=>s,this.shouldRebuild=()=>i,this.path=()=>e,this.match=function(e){e="/"+d(e,"/");if(!p().exec(e))return!1;const t={};return p().exec(e).forEach((e,n)=>{if(n<1)return;const r=c[n-1];t[r]=e}),t},this.url=function(t){const n=e.replace(/\{(.*?)}/gi,(e,n)=>{const r=l(n);if(t.params&&t.params[r.name])return t.params[r.name];throw new Error(`Missing parameter '${r.name}'`)}),r="object"==typeof t.query?new URLSearchParams(t.query).toString():null;return(t.basePath?t.basePath:"")+(r?`${n}?${r}`:n)}}class vt extends Error{constructor(e,t){super(e),this.context=t}}const yt=(e,t)=>{const n=[];return e.forEach(e=>{n.push(d(e.suffix,"/"))}),n.push(d(t,"/")),n.join("/")},gt=(e,t)=>{const n=[];return e.forEach(e=>{e.options.middlewares&&n.push(...e.options.middlewares)}),t&&n.push(...t),n},wt=(e,t)=>{const n=[];return e.forEach(e=>{e.options?.name&&n.push(e.options.name)}),t&&n.push(t),n.join(".")};function Ot(){const e=[];let t=0;const n=n=>{const o=t+n;if(!e[o])return;t=o;const{route:s,params:i,query:a,path:u}=e[o];r(u)},r=e=>{window.location.replace(`${window.location.pathname}${window.location.search}#${e}`)},o=()=>window.location.hash.slice(1);this.push=function(n){const{route:s,params:i,query:a,path:u}=this.resolve(n);u!==o()&&(e.splice(t+1),e.push({route:s,params:i,query:a,path:u}),t++,r(u))},this.replace=function(n){const{route:r,params:s,query:i,path:a}=this.resolve(n);a!==o()&&(e[t]={route:r,params:s,query:i,path:a})},this.forward=function(){return t<e.length-1&&n(1)},this.back=function(){return t>0&&n(-1)},this.init=function(n){window.addEventListener("hashchange",()=>{const{route:e,params:t,query:n,path:r}=this.resolve(o());this.handleRouteChange(e,t,n,r)});const{route:r,params:s,query:i,path:a}=this.resolve(n||o());e.push({route:r,params:s,query:i,path:a}),t=0,this.handleRouteChange(r,s,i,a)}}function Ct(){this.push=function(e){try{const{route:t,path:n,params:r,query:o}=this.resolve(e);if(window.history.state&&window.history.state.path===n)return;window.history.pushState({name:t.name(),params:r,path:n},t.name()||n,n),this.handleRouteChange(t,r,o,n)}catch(e){r.error("HistoryRouter","Error in pushState",e)}},this.replace=function(e){const{route:t,path:n,params:o}=this.resolve(e);try{window.history.replaceState({name:t.name(),params:o,path:n},t.name()||n,n),this.handleRouteChange(t,o,{},n)}catch(e){r.error("HistoryRouter","Error in replaceState",e)}},this.forward=function(){window.history.forward()},this.back=function(){window.history.back()},this.init=function(e){window.addEventListener("popstate",e=>{try{if(!e.state||!e.state.path)return;const t=e.state.path,{route:n,params:r,query:o,path:s}=this.resolve(t);if(!n)return;this.handleRouteChange(n,r,o,s)}catch(e){r.error("HistoryRouter","Error in popstate event",e)}});const{route:t,params:n,query:o,path:s}=this.resolve(e||window.location.pathname+window.location.search);this.handleRouteChange(t,n,o,s)}}function Et(){const e=[];let t=0;const n=n=>{const r=t+n;if(!e[r])return;t=r;const{route:o,params:s,query:i,path:a}=e[r];this.handleRouteChange(o,s,i,a)};this.push=function(n){const{route:r,params:o,query:s,path:i}=this.resolve(n);e[t]&&e[t].path===i||(e.splice(t+1),e.push({route:r,params:o,query:s,path:i}),t++,this.handleRouteChange(r,o,s,i))},this.replace=function(n){const{route:r,params:o,query:s,path:i}=this.resolve(n);e[t]={route:r,params:o,query:s,path:i},this.handleRouteChange(r,o,s,i)},this.forward=function(){return t<e.length-1&&n(1)},this.back=function(){return t>0&&n(-1)},this.init=function(n){const r=n||window.location.pathname+window.location.search,{route:o,params:s,query:i,path:a}=this.resolve(r);e.push({route:o,params:s,query:i,path:a}),t=0,this.handleRouteChange(o,s,i,a)}}const St="default";function At(e={}){const t=[],n={},o=[],s=[],i={route:null,params:null,query:null,path:null,hash:null};if("hash"===e.mode)Ot.apply(this,[]);else if("history"===e.mode)Ct.apply(this,[]);else{if("memory"!==e.mode)throw new vt("Invalid router mode "+e.mode);Et.apply(this,[])}const a=function(e,t){for(const n of s)try{n(e),t&&t(e)}catch(e){r.warn("Route Listener","Error in listener:",e)}};this.routes=()=>[...t],this.currentState=()=>({...i}),this.add=function(e,r,s){const i=new mt(yt(o,e),r,{...s,middlewares:gt(o,s?.middlewares||[]),name:s?.name?wt(o,s.name):null});return t.push(i),i.name()&&(n[i.name()]=i),this},this.group=function(e,t,n){if(!u.isFunction(n))throw new vt("Callback must be a function");return o.push({suffix:e,options:t}),n(),o.pop(),this},this.generateUrl=function(e,t={},r={}){const o=n[e];if(!o)throw new vt(`Route not found for name: ${e}`);return o.url({params:t,query:r})},this.resolve=function(e){if(u.isJson(e)){const t=n[e.name];if(!t)throw new vt(`Route not found for name: ${e.name}`);return{route:t,params:e.params,query:e.query,path:t.url({...e})}}const[r,o]=e.split("?"),s="/"+d(r,"/");let i,a=null;for(const e of t)if(i=e.match(s),i){a=e;break}if(!a)throw new vt(`Route not found for url: ${r}`);const c={};if(o){const e=new URLSearchParams(o).entries();for(const[t,n]of e)c[t]=n}return{route:a,params:i,query:c,path:e}},this.subscribe=function(e){if(!u.isFunction(e))throw new vt("Listener must be a function");return s.push(e),()=>{s.splice(s.indexOf(e),1)}},this.handleRouteChange=function(e,t,n,r){i.route=e,i.params=t,i.query=n,i.path=r;const o=[...e.middlewares(),a];let s=0;const u={...i},c=e=>{if(s++,!(s>=o.length))return o[s](e||u,c)};return o[s](u,c)}}function It(e,t){const{to:n,href:r,...o}=e,s=n||r;if(u.isString(s)){const e=At.get();return W({...o,href:s},t).nd.on.prevent.click(()=>{e.push(s)})}const i=s.router||St,a=At.get(i);if(console.log(i),!a)throw new vt('Router not found "'+i+'" for link "'+s.name+'"');const c=a.generateUrl(s.name,s.params,s.query);return W({...o,href:c},t).nd.on.prevent.click(()=>{a.push(c)})}At.routers={},At.create=function(t,n){if(!u.isFunction(n))throw r.error("Router","Callback must be a function",e),new vt("Callback must be a function");const o=new At(t);return At.routers[t.name||St]=o,n(o),o.init(t.entry),o.mount=function(e){if(u.isString(e)){const t=document.querySelector(e);if(!t)throw new vt(`Container not found for selector: ${e}`);e=t}else if(!u.isElement(e))throw new vt("Container must be a string or an Element");return function(e,t){const n=new Map,r=function(e){t.innerHTML="",t.appendChild(e)},o=function(e){if(!e.route)return;const{route:t,params:o,query:s,path:i}=e;if(n.has(i)){const e=n.get(i);return void r(e)}const a=t.component()({params:o,query:s});n.set(i,a),r(a)};return e.subscribe(o),o(e.currentState()),t}(o,e)},o},At.get=function(e){const t=At.routers[e||St];if(!t)throw new vt(`Router not found for name: ${e}`);return t},At.push=function(e,t=null){return At.get(t).push(e)},At.replace=function(e,t=null){return At.get(t).replace(e)},At.forward=function(e=null){return At.get(e).forward()},At.back=function(e=null){return At.get(e).back()},It.blank=function(e,t){return W({...e,target:"_blank"},t)};var kt=Object.freeze({__proto__:null,Link:It,RouteParamPatterns:bt,Router:At});return t.ArgTypes=$,t.ElementCreator=A,t.HtmlElementWrapper=I,t.Observable=p,t.Store=F,t.elements=ft,t.router=kt,t.withValidation=R,t}({});
|
|
1
|
+
var NativeDocument=function(t){"use strict";function n(e,t,n){return e.addEventListener(t,n),e}const r={enabled:!1,enable(){this.enabled=!0,console.log("🔍 NativeDocument Debug Mode enabled")},disable(){this.enabled=!1},log(e,t,n){this.enabled&&(console.group(`🔍 [${e}] ${t}`),n&&console.log(n),console.trace(),console.groupEnd())},warn(e,t,n){this.enabled&&console.warn(`⚠️ [${e}] ${t}`,n)},error(e,t,n){console.error(`❌ [${e}] ${t}`,n)}},o=function(){let e=0;const t=new Map;let n=null;try{n=new FinalizationRegistry(e=>{r.log("MemoryManager","🧹 Auto-cleanup observable:",e),e.listeners.splice(0)})}catch(e){r.warn("MemoryManager","FinalizationRegistry not supported, observables will not be cleaned automatically")}return{register(r,o){const s=++e,i={id:s,listeners:o};return n&&n.register(r,i),t.set(s,new WeakRef(r)),s},getObservableById:e=>t.get(e)?.deref(),cleanup(){for(const[e,n]of t){const e=n.deref();e&&e.cleanup()}t.clear()},cleanObservables(e){if(t.size<e)return;let n=0;for(const[e,r]of t)r.deref()||(t.delete(e),n++);n>0&&r.log("Memory Auto Clean",`🧹 Cleaned ${n} orphaned observables`)}}}();class s extends Error{constructor(e,t={}){super(e),this.name="NativeDocumentError",this.context=t,this.timestamp=(new Date).toISOString()}}function i(e,t){this.observable=e,this.checker=t;const n=[];this.subscribe=function(r){const o=e.subscribe(e=>{r&&r(t(e))});return n.push(o),o},this.val=function(){return t&&t(e.val())},this.check=function(t){return e.check(()=>t(this.val()))},this.set=function(t){return e.set(t)},this.trigger=function(){return e.trigger()},this.cleanup=function(){n.forEach(e=>e())}}function a(e){if(void 0===e)throw new s("ObservableItem requires an initial value");if(e instanceof a)throw new s("ObservableItem cannot be an Observable");const t="object"==typeof e?JSON.parse(JSON.stringify(e)):e;let n=e,u=e,c=!1;const l=[],d=o.register(this,l);this.trigger=()=>{l.forEach(e=>{try{e(u,n)}catch(t){r.error("Listener Undefined","Error in observable listener:",t),this.unsubscribe(e)}})},this.originalValue=()=>t,this.set=e=>{const t="function"==typeof e?e(u):e;u!==t&&(n=u,u=t,this.trigger())},this.val=()=>u,this.cleanup=function(){l.splice(0),c=!0},this.subscribe=e=>{if(c)return r.warn("Observable subscription","⚠️ Attempted to subscribe to a cleaned up observable."),()=>{};if("function"!=typeof e)throw new s("Callback must be a function");return l.push(e),()=>this.unsubscribe(e)},this.unsubscribe=e=>{const t=l.indexOf(e);t>-1&&l.splice(t,1)},this.check=function(e){return new i(this,e)};const h=this;Object.defineProperty(h,"$value",{get:()=>h.val(),set:e=>(h.set(e),h)}),this.toString=function(){return"{{#ObItem::("+d+")}}"}}const u={isObservable:e=>e instanceof a||e instanceof i,isProxy:e=>e?.__isProxy__,isObservableChecker:e=>e instanceof i,isArray:e=>Array.isArray(e),isString:e=>"string"==typeof e,isNumber:e=>"number"==typeof e,isBoolean:e=>"boolean"==typeof e,isFunction:e=>"function"==typeof e,isAsyncFunction:e=>"function"==typeof e&&"AsyncFunction"===e.constructor.name,isObject:e=>"object"==typeof e,isJson:e=>"object"==typeof e&&null!==e&&"Object"===e.constructor.name&&!Array.isArray(e),isElement:e=>e instanceof HTMLElement||e instanceof DocumentFragment||e instanceof Text,isFragment:e=>e instanceof DocumentFragment,isStringOrObservable(e){return this.isString(e)||this.isObservable(e)},isValidChild(e){return null===e||this.isElement(e)||this.isObservable(e)||["string","number","boolean"].includes(typeof e)},isValidChildren(e){Array.isArray(e)||(e=[e]);return 0===e.filter(e=>!this.isValidChild(e)).length},validateChildren(e){Array.isArray(e)||(e=[e]);const t=e.filter(e=>!this.isValidChild(e));if(t.length>0)throw new s(`Invalid children detected: ${t.map(e=>typeof e).join(", ")}`);return e},containsObservables:e=>!!e&&(u.isObject(e)&&Object.values(e).some(e=>u.isObservable(e))),containsObservableReference:e=>!(!e||"string"!=typeof e)&&/\{\{#ObItem::\([0-9]+\)\}\}/.test(e),validateAttributes(e){if(!e||"object"!=typeof e)return e;const t=[],n=Object.keys(e).filter(e=>t.includes(e));return n.length>0&&r.warn("Validator",`Reserved attributes found: ${n.join(", ")}`),e},validateEventCallback(e){if("function"!=typeof e)throw new s("Event callback must be a function")}},c=["checked","selected","disabled","readonly","required","autofocus","multiple","autocomplete","hidden","contenteditable","spellcheck","translate","draggable","async","defer","autoplay","controls","loop","muted","download","reversed","open","default","formnovalidate","novalidate","scoped","itemscope","allowfullscreen","allowpaymentrequest","playsinline"],l=function(e,t){return e.replace(new RegExp(`^[${t}]+|[${t}]+$`,"g"),"")};function d(e){return new a(e)}d.computed=function(e,t=[]){const n=new a(e()),r=()=>n.set(e());if(u.isFunction(t)){if(!u.isObservable(t.$observer))throw new s("Observable.computed : dependencies must be valid batch function");return t.$observer.subscribe(r),n}return t.forEach(e=>e.subscribe(r)),n},d.batch=function(e){const t=d(0),n=function(){if(u.isAsyncFunction(e))return e(...arguments).then(()=>{t.trigger()}).catch(e=>{throw e});e(...arguments),t.trigger()};return n.$observer=t,n},d.getById=function(e){const t=o.getObservableById(parseInt(e));if(!t)throw new s("Observable.getById : No observable found with id "+e);return t},d.cleanup=function(e){e.cleanup()},d.value=function(e){if(u.isObservable(e))return e.val();if(u.isProxy(e))return e.$val();if(u.isArray(e)){const t=[];return e.forEach(e=>{t.push(d.value(e))}),t}return e},d.init=function(e){const t={};for(const n in e){const r=e[n];u.isJson(r)?t[n]=d.init(r):u.isArray(r)?t[n]=d.array(r):t[n]=d(r)}const n=function(){const e={};for(const n in t){const r=t[n];u.isObservable(r)?e[n]=r.val():u.isProxy(r)?e[n]=r.$val():e[n]=r}return e},r=function(){};return new Proxy(t,{get:(e,t)=>"__isProxy__"===t||("$val"===t?n:"$clone"===t?r:void 0!==e[t]?e[t]:void 0),set(e,t,n){void 0!==e[t]&&e[t].set(n)}})},d.object=d.init,d.json=d.init,d.update=function(e,t){for(const n in t){const r=e[n],o=t[n];if(u.isObservable(r)){if(u.isArray(o)){d.update(r,o);continue}r.set(o)}else u.isProxy(r)?d.update(r,o):e[n]=o}},d.array=function(e){if(!Array.isArray(e))throw new s("Observable.array : target must be an array");const t=d(e);["push","pop","shift","unshift","reverse","sort","splice"].forEach(e=>{t[e]=function(...n){const r=t.val(),o=r[e].apply(r,arguments);return t.trigger(),o}});return["map","filter","reduce","some","every","find","findIndex"].forEach(e=>{t[e]=function(n){return t.val()[e](n)}}),t},d.autoCleanup=function(e=!1,t={}){if(!e)return;const{interval:n=6e4,threshold:r=100}=t;window.addEventListener("beforeunload",()=>{o.cleanup()}),setInterval(()=>o.cleanObservables(r),n)};const h=function(e,t,n){n?e.classList.add(t):e.classList.remove(t)};function p(e,t){for(let n in t){const r=t[n];u.isObservable(r)?(h(e,n,r.val()),r.subscribe(t=>h(e,n,t))):h(e,n,r)}}function f(e,t){for(let n in t){const r=t[n];u.isObservable(r)?(e.style[n]=r.val(),r.subscribe(t=>{e.style[n]=t})):e.style[n]=r}}function b(e,t,n){const r=u.isObservable(n)?n.val():n;u.isBoolean(r)?e[t]=r:e[t]=r===e.value,u.isObservable(n)&&(["checked"].includes(t)&&e.addEventListener("input",()=>{u.isBoolean(r)?n.set(e[t]):n.set(e.value)}),n.subscribe(n=>{u.isBoolean(n)?e[t]=n:e[t]=n===e.value}))}function m(e,t,n){const r=n=>{"value"!==t?e.setAttribute(t,n):e.value=n};n.subscribe(r),r(n.val()),"value"===t&&e.addEventListener("input",()=>n.set(e.value))}const v={elements:new Map,observer:null,checkMutation:function(e,t,n={}){let r=null,o=0;const{leading:s=!0,trailing:i=!0,debounce:a=!1}=n;return function(...n){const u=Date.now();if(a)return clearTimeout(r),void(r=setTimeout(()=>e.apply(this,n),t));s&&u-o>=t&&(e.apply(this,n),o=u),i&&!r&&(r=setTimeout(()=>{e.apply(this,n),o=Date.now(),r=null},t-(u-o)))}}(function(){for(const[e,t]of v.elements.entries()){const n=document.body.contains(e);n&&!t.inDom?(t.inDom=!0,t.mounted.forEach(t=>t(e))):!n&&t.inDom&&(t.inDom=!1,t.unmounted.forEach(t=>t(e)))}},10,{debounce:!0}),watch:function(e){let t={};if(v.elements.has(e))t=v.elements.get(e);else{const n=document.body.contains(e);t={inDom:n,mounted:new Set,unmounted:new Set},v.elements.set(e,t)}return{watch:()=>v.elements.set(e,t),disconnect:()=>v.elements.delete(e),mounted:e=>t.mounted.add(e),unmounted:e=>t.unmounted.add(e)}}};v.observer=new MutationObserver(v.checkMutation),v.observer.observe(document.body,{childList:!0,subtree:!0});const y=e=>u.isFunction(e)?y(e()):u.isElement(e)?e:E(e);function g(e){const t=document.createDocumentFragment(),n=document.createComment("Anchor Start : "+e),o=document.createComment("/ Anchor End "+e);t.appendChild(n),t.appendChild(o),t.nativeInsertBefore=t.insertBefore,t.nativeAppendChild=t.appendChild;const s=function(e,n,r){e!==t?e.insertBefore(y(n),r):e.nativeInsertBefore(y(n),r)};return t.appendChild=function(e,n=null){const i=o.parentNode;if(i){if(n=n??o,u.isArray(e))return e.forEach(e=>{s(i,e,n)}),t;s(i,e,n)}else r.error("Anchor","Anchor : parent not found",e)},t.remove=function(e){if(o.parentNode===t)return;let r,s=n.nextSibling;for(;s!==o;)r=s.nextSibling,e?s.remove():t.nativeAppendChild(s),s=r;e&&(o.remove(),n.remove())},t.insertBefore=function(e,n=null){t.appendChild(e,n)},t.clear=function(){t.remove()},t.endElement=function(){return o},t.startElement=function(){return n},t}const w=function(){const e=[];return{list:()=>e,add:t=>e.push(t)}}(),O=function(e,t){const n=document.createTextNode("");return t.subscribe(e=>n.textContent=String(e)),n.textContent=t.val(),e&&e.appendChild(n),n},C=function(e,t){const n=document.createTextNode("");return n.textContent=String(t),e&&e.appendChild(n),n},E=function(e){return u.isObservable(e)?O(null,e):C(null,e)},S={createElement:e=>e?document.createElement(e):new g("Fragment"),processChildren(e,t){if(null===e)return;(Array.isArray(e)?e:[e]).forEach(e=>{null!==e&&(u.isString(e)&&u.isFunction(e.resolveObservableTemplate)&&(e=e.resolveObservableTemplate()),u.isFunction(e)?this.processChildren(e(),t):u.isArray(e)?this.processChildren(e,t):u.isElement(e)?t.appendChild(e):u.isObservable(e)?O(t,e):e&&C(t,e))})},processAttributes(e,t){u.isFragment(e)||t&&function(e,t){if(u.validateAttributes(t),!u.isObject(t))throw new s("Attributes must be an object");for(let n in t){const r=n.toLowerCase();let o=t[r];if(u.isString(o)&&u.isFunction(o.resolveObservableTemplate)&&(o=o.resolveObservableTemplate(),u.isArray(o))){const e=o.filter(e=>u.isObservable(e));o=d.computed(()=>o.map(e=>u.isObservable(e)?e.val():e).join(" ")||" ",e)}c.includes(r)?b(e,r,o):u.isObservable(o)?m(e,r,o):"class"===r&&u.isJson(o)?p(e,o):"style"===r&&u.isJson(o)?f(e,o):e.setAttribute(r,o)}}(e,t)},setup(e,t,r){e.nd={},function(e){e.nd||(e.nd={}),e.nd.on=function(t){for(const r in t){const o=t[r];n(e,r,o)}return e},e.nd.on.prevent=function(t){for(const r in t){const o=t[r];n(e,r,t=>(t.preventDefault(),o&&o(t),e))}return e};const t={click:t=>n(e,"click",t),focus:t=>n(e,"focus",t),blur:t=>n(e,"blur",t),input:t=>n(e,"input",t),change:t=>n(e,"change",t),keyup:t=>n(e,"keyup",t),keydown:t=>n(e,"keydown",t),beforeInput:t=>n(e,"beforeinput",t),mouseOver:t=>n(e,"mouseover",t),mouseOut:t=>n(e,"mouseout",t),mouseDown:t=>n(e,"mousedown",t),mouseUp:t=>n(e,"mouseup",t),mouseMove:t=>n(e,"mousemove",t),hover:(t,n)=>{e.addEventListener("mouseover",t),e.addEventListener("mouseout",n)},dropped:t=>n(e,"drop",t),submit:t=>n(e,"submit",t),dragEnd:t=>n(e,"dragend",t),dragStart:t=>n(e,"dragstart",t),drop:t=>n(e,"drop",t),dragOver:t=>n(e,"dragover",t),dragEnter:t=>n(e,"dragenter",t),dragLeave:t=>n(e,"dragleave",t)};for(let r in t)e.nd.on[r]=t[r],e.nd.on.prevent[r]=function(t){return n(e,r.toLowerCase(),e=>{e.preventDefault(),t&&t(e)}),e}}(e);const o="function"==typeof r?r(e):e;return function(e){e.nd.wrap=t=>{if(!u.isFunction(t))throw new s("Callback must be a function");return t&&t(e),e},e.nd.ref=(t,n)=>(t[n]=e,e);let t=null;e.nd.appendChild=function(t){u.isArray(t)?S.processChildren(t,e):(u.isFunction(t)&&(t=t(),S.processChildren(t(),e)),u.isElement(t)&&S.processChildren(t,e))},e.nd.lifecycle=function(n){return t=t||v.watch(e),n.mounted&&t.mounted(n.mounted),n.unmounted&&t.unmounted(n.unmounted),e},e.nd.mounted=n=>(t=t||v.watch(e),t.mounted(n),e),e.nd.unmounted=n=>(t=t||v.watch(e),t.unmounted(n),e)}(o),w.list().forEach(e=>{e?.element?.setup&&e.element.setup(o,t)}),o}};function A(e,t){const n=e.toLowerCase().trim(),o=function(e,o=null){try{if(u.isValidChildren(e)){const t=o;o=e,e=t}const r=S.createElement(n);return S.processAttributes(r,e),S.processChildren(o,r),S.setup(r,e,t)}catch(e){r.error("ElementCreation",`Error creating ${n}`,e)}};return o.hold=(e,t)=>()=>o(e,t),o}class I extends Error{constructor(e,t){super(`${e}\n\n${t.join("\n")}\n\n`)}}const k={string:e=>({name:e,type:"string",validate:e=>u.isString(e)}),number:e=>({name:e,type:"number",validate:e=>u.isNumber(e)}),boolean:e=>({name:e,type:"boolean",validate:e=>u.isBoolean(e)}),observable:e=>({name:e,type:"observable",validate:e=>u.isObservable(e)}),element:e=>({name:e,type:"element",validate:e=>u.isElement(e)}),function:e=>({name:e,type:"function",validate:e=>u.isFunction(e)}),object:e=>({name:e,type:"object",validate:e=>u.isObject(e)}),objectNotNull:e=>({name:e,type:"object",validate:e=>u.isObject(e)&&null!==e}),children:e=>({name:e,type:"children",validate:e=>u.validateChildren(e)}),attributes:e=>({name:e,type:"attributes",validate:e=>u.validateAttributes(e)}),optional:e=>({...e,optional:!0}),oneOf:(e,...t)=>({name:e,type:"oneOf",types:t,validate:e=>t.some(t=>t.validate(e))})},$=(e,t,n="Function")=>{if(!u.isArray(t))throw new s("withValidation : argSchema must be an array");return function(...r){return((e,t,n="Function")=>{if(!t)return;const r=[],o=t.filter(e=>!e.optional).length;if(e.length<o&&r.push(`${n}: Expected at least ${o} arguments, got ${e.length}`),t.forEach((t,o)=>{const s=o+1,i=e[o];if(void 0!==i){if(!t.validate(i)){const e=i?.constructor?.name||typeof i;r.push(`${n}: Invalid argument '${t.name}' at position ${s}, expected ${t.type}, got ${e}`)}}else t.optional||r.push(`${n}: Missing required argument '${t.name}' at position ${s}`)}),r.length>0)throw new I("Argument validation failed",r)})(r,t,e.name||n),e.apply(this,r)}};Function.prototype.args=function(...e){return $(this,e)},Function.prototype.errorBoundary=function(e){return(...t)=>{try{return this.apply(this,t)}catch(t){return e(t)}}},String.prototype.use=function(e){const t=this;return d.computed(()=>t.replace(/\$\{(.*?)}/g,(t,n)=>{const r=e[n];return u.isObservable(r)?r.val():r}),Object.values(e))},String.prototype.resolveObservableTemplate=function(){return u.containsObservableReference(this)?this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map(e=>{if(!u.containsObservableReference(e))return e;const[t,n]=e.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);return d.getById(n)}):this};const F=function(){const e=new Map;return{use(t){const{observer:n,subscribers:r}=e.get(t),o=d(n.val()),s=n.subscribe(e=>o.set(e)),i=o.subscribe(e=>n.set(e));return o.destroy=()=>{s(),i(),o.cleanup()},r.add(o),o},follow(e){return this.use(e)},create(t,n){const r=d(n);return e.set(t,{observer:r,subscribers:new Set}),r},get(t){const n=e.get(t);return n?n.observer:null},getWithSubscribers:t=>e.get(t),delete(t){const n=e.get(t);n&&(n.observer.cleanup(),n.subscribers.forEach(e=>e.destroy()),n.observer.clear())}}}();const x=function(e,t,n=null){if(!u.isObservable(e))return r.warn("ShowIf","ShowIf : condition must be an Observable / "+n,e);const o=new g("Show if : "+(n||""));let s=null;const i=()=>s||(s="function"==typeof t?t():t,u.isStringOrObservable(s)&&(s=E(s)),s);return e.val()&&o.appendChild(i()),e.subscribe(e=>{e?o.appendChild(i()):o.remove()}),o},R=function(e,t){if(!u.isObservable(e))throw new s("Toggle : condition must be an Observable");const n=new g,r=new Map,o=function(e){if(r.has(e))return r.get(e);let n=t[e];return n?(u.isFunction(n)&&(n=n()),r.set(e,n),n):null},i=e.val(),a=o(i);return a&&n.appendChild(a),e.subscribe(e=>{const t=o(e);n.remove(),t&&n.appendChild(t)}),n},q=function(e,t,n){if(!u.isObservable(e))throw new s("Toggle : condition must be an Observable");return R(e,{true:t,false:n})},T=A("div"),D=A("span"),j=A("label"),L=A("p"),B=L,M=A("strong"),N=A("h1"),H=A("h2"),P=A("h3"),_=A("h4"),V=A("h5"),U=A("h6"),z=A("br"),J=A("a"),W=A("pre"),K=A("code"),Q=A("blockquote"),G=A("hr"),X=A("em"),Y=A("small"),Z=A("mark"),ee=A("del"),te=A("ins"),ne=A("sub"),re=A("sup"),oe=A("abbr"),se=A("cite"),ie=A("q"),ae=A("dl"),ue=A("dt"),ce=A("dd"),le=A("form",function(e){return e.submit=function(t){return"function"==typeof t?(e.on.submit(e=>{e.preventDefault(),t(e)}),e):(this.setAttribute("action",t),e)},e.multipartFormData=function(){return this.setAttribute("enctype","multipart/form-data"),e},e.post=function(t){return this.setAttribute("method","post"),this.setAttribute("action",t),e},e.get=function(e){this.setAttribute("method","get"),this.setAttribute("action",e)},e}),de=A("input"),he=A("textarea"),pe=he,fe=A("select"),be=A("fieldset"),me=A("option"),ve=A("legend"),ye=A("datalist"),ge=A("output"),we=A("progress"),Oe=A("meter"),Ce=A("button"),Ee=A("main"),Se=A("section"),Ae=A("article"),Ie=A("aside"),ke=A("nav"),$e=A("figure"),Fe=A("figcaption"),xe=A("header"),Re=A("footer"),qe=A("img"),Te=function(e,t){return qe({src:e,...t})},De=A("details"),je=A("summary"),Le=A("dialog"),Be=A("menu"),Me=A("ol"),Ne=A("ul"),He=A("li"),Pe=A("audio"),_e=A("video"),Ve=A("source"),Ue=A("track"),ze=A("canvas"),Je=A("svg"),We=A("time"),Ke=A("data"),Qe=A("address"),Ge=A("kbd"),Xe=A("samp"),Ye=A("var"),Ze=A("wbr"),et=A("caption"),tt=A("table"),nt=A("thead"),rt=A("tfoot"),ot=A("tbody"),st=A("tr"),it=st,at=A("th"),ut=at,ct=at,lt=A("td"),dt=lt,ht=A("");var pt=Object.freeze({__proto__:null,Abbr:oe,Address:Qe,Anchor:g,Article:Ae,Aside:Ie,AsyncImg:function(e,t,n,r){const o=Te(t||e,n),i=new Image;return i.onload=()=>{u.isFunction(r)&&r(null,o),o.src=e},i.onerror=()=>{u.isFunction(r)&&r(new s("Image not found"))},u.isObservable(e)&&e.subscribe(e=>{i.src=e}),i.src=e,o},Audio:Pe,BaseImage:qe,Blockquote:Q,Br:z,Button:Ce,Canvas:ze,Caption:et,Checkbox:e=>de({type:"checkbox",...e}),Cite:se,Code:K,ColorInput:e=>de({type:"color",...e}),Data:Ke,Datalist:ye,DateInput:e=>de({type:"date",...e}),DateTimeInput:e=>de({type:"datetime-local",...e}),Dd:ce,Del:ee,Details:De,Dialog:Le,Div:T,Dl:ae,Dt:ue,Em:X,EmailInput:e=>de({type:"email",...e}),FieldSet:be,FigCaption:Fe,Figure:$e,FileInput:e=>de({type:"file",...e}),Footer:Re,ForEach:function(e,t,n){const o=new g("ForEach"),s=o.endElement(),i=o.startElement();let a=new Map;const c=new Set,l=(e,o)=>{const s=((e,t,n)=>{if(u.isFunction(n))return n(e,t);if(u.isObservable(e)){const r=e.val();return r&&n?r[n]:t}return e[n]??t})(e,o,n);if(a.has(s)){const e=a.get(s);e.indexObserver.set(o),e.isNew=!1}else try{const n=d(o);let r=t(e,n);u.isStringOrObservable(r)&&(r=E(r)),a.set(s,{isNew:!0,child:r,indexObserver:n})}catch(e){throw r.error("ForEach",`Error creating element for key ${s}`,e),e}return s},h=()=>{const t=u.isObservable(e)?e.val():e;if(c.clear(),Array.isArray(t))t.forEach((e,t)=>c.add(l(e,t)));else for(const e in t)c.add(l(t[e],e));((e,t)=>{const n=[];for(const[r,o]of e.entries())t.has(r)||n.push({key:r,cacheItem:o});0!==n.length&&n.forEach(({key:t,cacheItem:n})=>{n.child.remove(),n.indexObserver.cleanup(),e.delete(t)})})(a,c),(()=>{const e=s.parentNode;if(!e)return;let t=i;const n=[],r=[];let u=null,l=e=>{u&&(n.push({child:u,before:e}),u=null)};const d=Array.from(c);for(let e=0;e<d.length;e++){const n=d[e],o=a.get(n);if(!o)continue;if(t&&t.nextSibling===o.child){t=o.child,l(o.child);continue}if(o.isNew){u=u||document.createDocumentFragment(),u.append(o.child),o.isNew=!1;continue}l(o.child);const s=a.get(d[e+1])?.child;s&&o.child.nextSibling!==s&&r.push({child:o.child,before:s}),t=o.child}l(s),n.forEach(({child:t,before:n})=>{n?e.insertBefore(t,n):o.appendChild(t)}),r.forEach(({child:t,before:n})=>{e.insertBefore(t,n)}),l=null})()};return h(),u.isObservable(e)&&e.subscribe(h),o},Form:le,Fragment:ht,H1:N,H2:H,H3:P,H4:_,H5:V,H6:U,Header:xe,HiddenInput:e=>de({type:"hidden",...e}),HideIf:function(e,t,n){const r=d(!e.val());return e.subscribe(e=>r.set(!e)),x(r,t,n)},HideIfNot:function(e,t,n){return x(e,t,n)},Hr:G,Img:Te,Input:de,Ins:te,Kbd:Ge,Label:j,LazyImg:function(e,t){return Te(e,{...t,loading:"lazy"})},Legend:ve,Link:J,ListItem:He,Main:Ee,Mark:Z,Match:R,Menu:Be,Meter:Oe,MonthInput:e=>de({type:"month",...e}),NativeDocumentFragment:g,Nav:ke,NumberInput:e=>de({type:"number",...e}),Option:me,OrderedList:Me,Output:ge,P:L,Paragraph:B,PasswordInput:e=>de({type:"password",...e}),Pre:W,Progress:we,Quote:ie,Radio:e=>de({type:"radio",...e}),RangeInput:e=>de({type:"range",...e}),ReadonlyInput:e=>de({readonly:!0,...e}),Samp:Xe,SearchInput:e=>de({type:"search",...e}),Section:Se,Select:fe,ShowIf:x,SimpleButton:(e,t)=>Ce(e,{type:"button",...t}),Small:Y,Source:Ve,Span:D,Strong:M,Sub:ne,SubmitButton:(e,t)=>Ce(e,{type:"submit",...t}),Summary:je,Sup:re,Svg:Je,Switch:q,TBody:ot,TBodyCell:dt,TFoot:rt,TFootCell:ct,THead:nt,THeadCell:ut,TRow:it,Table:tt,Td:lt,TelInput:e=>de({type:"tel",...e}),TextArea:he,TextInput:pe,Th:at,Time:We,TimeInput:e=>de({type:"time",...e}),Tr:st,Track:Ue,UnorderedList:Ne,UrlInput:e=>de({type:"url",...e}),Var:Ye,Video:_e,Wbr:Ze,WeekInput:e=>de({type:"week",...e}),When:function(e){if(!u.isObservable(e))throw new s("When : condition must be an Observable");let t=null,n=null;return{show(e){return t=e,this},otherwise:r=>(n=r,q(e,t,n))}}});const ft={};function bt(e,t,n={}){e="/"+l(e,"/");let r=null,o=n.name||null;const s=n.middlewares||[],i=n.shouldRebuild||!1,a=n.with||{},u={},c=[],d=e=>{if(!e)return null;const[t,n]=e.split(":");let r=a[t];return!r&&n&&(r=ft[n]),r||(r="[^/]+"),r=r.replace("(","(?:"),{name:t,pattern:`(${r})`}},h=()=>{if(r)return r;const t=e.replace(/\{(.*?)}/gi,(e,t)=>{const n=d(t);return n&&n.pattern?(u[n.name]=n.pattern,c.push(n.name),n.pattern):e});return r=new RegExp("^"+t+"$"),r};this.name=()=>o,this.component=()=>t,this.middlewares=()=>s,this.shouldRebuild=()=>i,this.path=()=>e,this.match=function(e){e="/"+l(e,"/");if(!h().exec(e))return!1;const t={};return h().exec(e).forEach((e,n)=>{if(n<1)return;const r=c[n-1];t[r]=e}),t},this.url=function(t){const n=e.replace(/\{(.*?)}/gi,(e,n)=>{const r=d(n);if(t.params&&t.params[r.name])return t.params[r.name];throw new Error(`Missing parameter '${r.name}'`)}),r="object"==typeof t.query?new URLSearchParams(t.query).toString():null;return(t.basePath?t.basePath:"")+(r?`${n}?${r}`:n)}}class mt extends Error{constructor(e,t){super(e),this.context=t}}const vt=(e,t)=>{const n=[];return e.forEach(e=>{n.push(l(e.suffix,"/"))}),n.push(l(t,"/")),n.join("/")},yt=(e,t)=>{const n=[];return e.forEach(e=>{e.options.middlewares&&n.push(...e.options.middlewares)}),t&&n.push(...t),n},gt=(e,t)=>{const n=[];return e.forEach(e=>{e.options?.name&&n.push(e.options.name)}),t&&n.push(t),n.join(".")};function wt(){const e=[];let t=0;const n=n=>{const o=t+n;if(!e[o])return;t=o;const{route:s,params:i,query:a,path:u}=e[o];r(u)},r=e=>{window.location.replace(`${window.location.pathname}${window.location.search}#${e}`)},o=()=>window.location.hash.slice(1);this.push=function(n){const{route:s,params:i,query:a,path:u}=this.resolve(n);u!==o()&&(e.splice(t+1),e.push({route:s,params:i,query:a,path:u}),t++,r(u))},this.replace=function(n){const{route:r,params:s,query:i,path:a}=this.resolve(n);a!==o()&&(e[t]={route:r,params:s,query:i,path:a})},this.forward=function(){return t<e.length-1&&n(1)},this.back=function(){return t>0&&n(-1)},this.init=function(n){window.addEventListener("hashchange",()=>{const{route:e,params:t,query:n,path:r}=this.resolve(o());this.handleRouteChange(e,t,n,r)});const{route:r,params:s,query:i,path:a}=this.resolve(n||o());e.push({route:r,params:s,query:i,path:a}),t=0,this.handleRouteChange(r,s,i,a)}}function Ot(){this.push=function(e){try{const{route:t,path:n,params:r,query:o}=this.resolve(e);if(window.history.state&&window.history.state.path===n)return;window.history.pushState({name:t.name(),params:r,path:n},t.name()||n,n),this.handleRouteChange(t,r,o,n)}catch(e){r.error("HistoryRouter","Error in pushState",e)}},this.replace=function(e){const{route:t,path:n,params:o}=this.resolve(e);try{window.history.replaceState({name:t.name(),params:o,path:n},t.name()||n,n),this.handleRouteChange(t,o,{},n)}catch(e){r.error("HistoryRouter","Error in replaceState",e)}},this.forward=function(){window.history.forward()},this.back=function(){window.history.back()},this.init=function(e){window.addEventListener("popstate",e=>{try{if(!e.state||!e.state.path)return;const t=e.state.path,{route:n,params:r,query:o,path:s}=this.resolve(t);if(!n)return;this.handleRouteChange(n,r,o,s)}catch(e){r.error("HistoryRouter","Error in popstate event",e)}});const{route:t,params:n,query:o,path:s}=this.resolve(e||window.location.pathname+window.location.search);this.handleRouteChange(t,n,o,s)}}function Ct(){const e=[];let t=0;const n=n=>{const r=t+n;if(!e[r])return;t=r;const{route:o,params:s,query:i,path:a}=e[r];this.handleRouteChange(o,s,i,a)};this.push=function(n){const{route:r,params:o,query:s,path:i}=this.resolve(n);e[t]&&e[t].path===i||(e.splice(t+1),e.push({route:r,params:o,query:s,path:i}),t++,this.handleRouteChange(r,o,s,i))},this.replace=function(n){const{route:r,params:o,query:s,path:i}=this.resolve(n);e[t]={route:r,params:o,query:s,path:i},this.handleRouteChange(r,o,s,i)},this.forward=function(){return t<e.length-1&&n(1)},this.back=function(){return t>0&&n(-1)},this.init=function(n){const r=n||window.location.pathname+window.location.search,{route:o,params:s,query:i,path:a}=this.resolve(r);e.push({route:o,params:s,query:i,path:a}),t=0,this.handleRouteChange(o,s,i,a)}}const Et="default";function St(e={}){const t=[],n={},o=[],s=[],i={route:null,params:null,query:null,path:null,hash:null};if("hash"===e.mode)wt.apply(this,[]);else if("history"===e.mode)Ot.apply(this,[]);else{if("memory"!==e.mode)throw new mt("Invalid router mode "+e.mode);Ct.apply(this,[])}const a=function(e,t){for(const n of s)try{n(e),t&&t(e)}catch(e){r.warn("Route Listener","Error in listener:",e)}};this.routes=()=>[...t],this.currentState=()=>({...i}),this.add=function(e,r,s){const i=new bt(vt(o,e),r,{...s,middlewares:yt(o,s?.middlewares||[]),name:s?.name?gt(o,s.name):null});return t.push(i),i.name()&&(n[i.name()]=i),this},this.group=function(e,t,n){if(!u.isFunction(n))throw new mt("Callback must be a function");return o.push({suffix:e,options:t}),n(),o.pop(),this},this.generateUrl=function(e,t={},r={}){const o=n[e];if(!o)throw new mt(`Route not found for name: ${e}`);return o.url({params:t,query:r})},this.resolve=function(e){if(u.isJson(e)){const t=n[e.name];if(!t)throw new mt(`Route not found for name: ${e.name}`);return{route:t,params:e.params,query:e.query,path:t.url({...e})}}const[r,o]=e.split("?"),s="/"+l(r,"/");let i,a=null;for(const e of t)if(i=e.match(s),i){a=e;break}if(!a)throw new mt(`Route not found for url: ${r}`);const c={};if(o){const e=new URLSearchParams(o).entries();for(const[t,n]of e)c[t]=n}return{route:a,params:i,query:c,path:e}},this.subscribe=function(e){if(!u.isFunction(e))throw new mt("Listener must be a function");return s.push(e),()=>{s.splice(s.indexOf(e),1)}},this.handleRouteChange=function(e,t,n,r){i.route=e,i.params=t,i.query=n,i.path=r;const o=[...e.middlewares(),a];let s=0;const u={...i},c=e=>{if(s++,!(s>=o.length))return o[s](e||u,c)};return o[s](u,c)}}function At(e,t){const{to:n,href:r,...o}=e,s=n||r;if(u.isString(s)){const e=St.get();return J({...o,href:s},t).nd.on.prevent.click(()=>{e.push(s)})}const i=s.router||Et,a=St.get(i);if(console.log(i),!a)throw new mt('Router not found "'+i+'" for link "'+s.name+'"');const c=a.generateUrl(s.name,s.params,s.query);return J({...o,href:c},t).nd.on.prevent.click(()=>{a.push(c)})}St.routers={},St.create=function(t,n){if(!u.isFunction(n))throw r.error("Router","Callback must be a function",e),new mt("Callback must be a function");const o=new St(t);return St.routers[t.name||Et]=o,n(o),o.init(t.entry),o.mount=function(e){if(u.isString(e)){const t=document.querySelector(e);if(!t)throw new mt(`Container not found for selector: ${e}`);e=t}else if(!u.isElement(e))throw new mt("Container must be a string or an Element");return function(e,t){const n=new Map,r=function(e){t.innerHTML="",t.appendChild(e)},o=function(e){if(!e.route)return;const{route:t,params:o,query:s,path:i}=e;if(n.has(i)){const e=n.get(i);return void r(e)}const a=t.component()({params:o,query:s});n.set(i,a),r(a)};return e.subscribe(o),o(e.currentState()),t}(o,e)},o},St.get=function(e){const t=St.routers[e||Et];if(!t)throw new mt(`Router not found for name: ${e}`);return t},St.push=function(e,t=null){return St.get(t).push(e)},St.replace=function(e,t=null){return St.get(t).replace(e)},St.forward=function(e=null){return St.get(e).forward()},St.back=function(e=null){return St.get(e).back()},At.blank=function(e,t){return J({...e,target:"_blank"},t)};var It=Object.freeze({__proto__:null,Link:At,RouteParamPatterns:ft,Router:St});return t.ArgTypes=k,t.ElementCreator=S,t.HtmlElementWrapper=A,t.Observable=d,t.Store=F,t.elements=pt,t.router=It,t.withValidation=$,t}({});
|
package/docs/observables.md
CHANGED
|
@@ -170,6 +170,286 @@ const app = Div({ class: "counter" }, [
|
|
|
170
170
|
]);
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
+
# Batching Operations
|
|
174
|
+
|
|
175
|
+
Batching is a performance optimization technique that delays notifications to **dependent observers** (like computed observables) until the end of a batch operation. Individual observable subscribers still receive their notifications immediately, but computed observables that depend on the batch function are only triggered once at the end.
|
|
176
|
+
|
|
177
|
+
### Understanding Batch Behavior
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
const name = Observable("John");
|
|
181
|
+
const age = Observable(25);
|
|
182
|
+
|
|
183
|
+
// Direct subscribers always get immediate notifications
|
|
184
|
+
name.subscribe(value => console.log("Name changed to:", value));
|
|
185
|
+
age.subscribe(value => console.log("Age changed to:", value));
|
|
186
|
+
|
|
187
|
+
const updateProfile = Observable.batch(() => {
|
|
188
|
+
name.set("Alice"); // Logs: "Name changed to: Alice"
|
|
189
|
+
age.set(30); // Logs: "Age changed to: 30"
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
updateProfile(); // Individual subscribers are notified immediately
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Batching with Computed Dependencies
|
|
196
|
+
|
|
197
|
+
The real power of batching shows when computed observables depend on the batch function:
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
const firstName = Observable("John");
|
|
201
|
+
const lastName = Observable("Doe");
|
|
202
|
+
|
|
203
|
+
// Direct subscribers get immediate notifications
|
|
204
|
+
firstName.subscribe(name => console.log("First name:", name));
|
|
205
|
+
lastName.subscribe(name => console.log("Last name:", name));
|
|
206
|
+
|
|
207
|
+
// Batch function for name updates
|
|
208
|
+
const updateName = Observable.batch((first, last) => {
|
|
209
|
+
firstName.set(first); // Logs: "First name: Alice"
|
|
210
|
+
lastName.set(last); // Logs: "Last name: Smith"
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Computed that depends on the BATCH FUNCTION (not individual observables)
|
|
214
|
+
const fullName = Observable.computed(() => {
|
|
215
|
+
return `${firstName.val()} ${lastName.val()}`;
|
|
216
|
+
}, updateName); // ← Depends on the batch function
|
|
217
|
+
|
|
218
|
+
fullName.subscribe(name => console.log("Full name:", name));
|
|
219
|
+
|
|
220
|
+
// When we call the batch:
|
|
221
|
+
updateName("Alice", "Smith");
|
|
222
|
+
// Logs:
|
|
223
|
+
// "First name: Alice" ← immediate notification
|
|
224
|
+
// "Last name: Smith" ← immediate notification
|
|
225
|
+
// "Full name: Alice Smith" ← single notification at the end
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Comparison: Normal vs Batch Dependencies
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
const score = Observable(0);
|
|
232
|
+
const lives = Observable(3);
|
|
233
|
+
|
|
234
|
+
// Method 1: Computed depends on individual observables
|
|
235
|
+
const gameStatus1 = Observable.computed(() => {
|
|
236
|
+
return `Score: ${score.val()}, Lives: ${lives.val()}`;
|
|
237
|
+
}, [score, lives]); // ← Depends on individual observables
|
|
238
|
+
|
|
239
|
+
// Method 2: Computed depends on batch function
|
|
240
|
+
const updateGame = Observable.batch(() => {
|
|
241
|
+
score.set(score.val() + 100);
|
|
242
|
+
lives.set(lives.val() - 1);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const gameStatus2 = Observable.computed(() => {
|
|
246
|
+
return `Score: ${score.val()}, Lives: ${lives.val()}`;
|
|
247
|
+
}, updateGame); // ← Depends on the batch function
|
|
248
|
+
|
|
249
|
+
// Without batching - gameStatus1 recalculates twice:
|
|
250
|
+
score.set(100); // gameStatus1 recalculates
|
|
251
|
+
lives.set(2); // gameStatus1 recalculates again
|
|
252
|
+
|
|
253
|
+
// With batching - gameStatus2 recalculates only once:
|
|
254
|
+
updateGame(); // gameStatus2 recalculates only at the end
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Practical Example: Shopping Cart
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
const items = Observable.array([]);
|
|
261
|
+
const discount = Observable(0);
|
|
262
|
+
const shippingCost = Observable(0);
|
|
263
|
+
|
|
264
|
+
// Individual subscribers for immediate UI updates
|
|
265
|
+
items.subscribe(items => {
|
|
266
|
+
console.log('Items count : '+items.length);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
discount.subscribe(discount => {
|
|
270
|
+
console.log(`Discount: ${discount}%`);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Batch function for cart operations
|
|
274
|
+
const updateCart = Observable.batch((cartData) => {
|
|
275
|
+
items.splice(0); // Clear current items
|
|
276
|
+
cartData.items.forEach(item => items.push(item));
|
|
277
|
+
discount.set(cartData.discount);
|
|
278
|
+
shippingCost.set(cartData.shipping);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Expensive calculation that should only run after complete cart updates
|
|
282
|
+
const cartTotal = Observable.computed(() => {
|
|
283
|
+
const itemsTotal = items.val().reduce((sum, item) => sum + (item.price * item.quantity), 0);
|
|
284
|
+
const discountAmount = itemsTotal * (discount.val() / 100);
|
|
285
|
+
return itemsTotal - discountAmount + shippingCost.val();
|
|
286
|
+
}, updateCart); // ← Only recalculates when updateCart() is called
|
|
287
|
+
|
|
288
|
+
// Example usage
|
|
289
|
+
updateCart({
|
|
290
|
+
items: [
|
|
291
|
+
{ name: "Product A", price: 29.99, quantity: 2 },
|
|
292
|
+
{ name: "Product B", price: 19.99, quantity: 1 }
|
|
293
|
+
],
|
|
294
|
+
discount: 10,
|
|
295
|
+
shipping: 5.99
|
|
296
|
+
});
|
|
297
|
+
// Individual subscribers fire immediately, cartTotal calculates once at the end
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Async Batching
|
|
301
|
+
|
|
302
|
+
Batch functions handle asynchronous operations, delaying dependent notifications until the promise resolves:
|
|
303
|
+
|
|
304
|
+
```javascript
|
|
305
|
+
const isLoading = Observable(false);
|
|
306
|
+
const userData = Observable(null);
|
|
307
|
+
const error = Observable(null);
|
|
308
|
+
|
|
309
|
+
// These subscribe immediately to loading states
|
|
310
|
+
isLoading.subscribe(loading => {
|
|
311
|
+
console.log('Loading.....');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const fetchUser = Observable.batch(async (userId) => {
|
|
315
|
+
isLoading.set(true); // Immediate notification
|
|
316
|
+
error.set(null); // Immediate notification
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
const response = await fetch(`/api/users/${userId}`);
|
|
320
|
+
const data = await response.json();
|
|
321
|
+
userData.set(data); // Immediate notification
|
|
322
|
+
} catch (err) {
|
|
323
|
+
error.set(err.message); // Immediate notification
|
|
324
|
+
} finally {
|
|
325
|
+
isLoading.set(false); // Immediate notification
|
|
326
|
+
}
|
|
327
|
+
// Dependent computed observables are notified HERE
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// This computed depends on the batch function
|
|
331
|
+
const userDisplay = Observable.computed(() => {
|
|
332
|
+
if (isLoading.val()) return "Loading...";
|
|
333
|
+
if (error.val()) return `Error: ${error.val()}`;
|
|
334
|
+
if (userData.val()) return `Hello ${userData.val().name}`;
|
|
335
|
+
return "No user";
|
|
336
|
+
}, fetchUser); // ← Only updates when fetchUser() completes
|
|
337
|
+
|
|
338
|
+
await fetchUser(123);
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Single Batch Dependency Only
|
|
342
|
+
|
|
343
|
+
**Important**: Computed observables can only depend on **one batch function**, not multiple:
|
|
344
|
+
|
|
345
|
+
```javascript
|
|
346
|
+
const user = Observable.object({ name: "", email: "" });
|
|
347
|
+
const settings = Observable.object({ theme: "light", lang: "en" });
|
|
348
|
+
|
|
349
|
+
const updateProfile = Observable.batch((profileData) => {
|
|
350
|
+
user.name.set(profileData.name);
|
|
351
|
+
user.email.set(profileData.email);
|
|
352
|
+
settings.theme.set(profileData.theme);
|
|
353
|
+
settings.lang.set(profileData.lang);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// ✅ Correct: Single batch dependency
|
|
357
|
+
const profileSummary = Observable.computed(() => {
|
|
358
|
+
return {
|
|
359
|
+
user: user.$val(),
|
|
360
|
+
settings: settings.$val(),
|
|
361
|
+
lastUpdated: Date.now()
|
|
362
|
+
};
|
|
363
|
+
}, updateProfile); // ← Single batch function
|
|
364
|
+
|
|
365
|
+
// ❌ This is NOT supported:
|
|
366
|
+
// Observable.computed(callback, [batch1, batch2])
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Performance Benefits
|
|
370
|
+
|
|
371
|
+
```javascript
|
|
372
|
+
const items = Observable.array([]);
|
|
373
|
+
|
|
374
|
+
// Expensive computed operation
|
|
375
|
+
const expensiveCalculation = Observable.computed(() => {
|
|
376
|
+
console.log("🔄 Recalculating..."); // This helps visualize when it runs
|
|
377
|
+
return items.val()
|
|
378
|
+
.filter(item => item.active)
|
|
379
|
+
.map(item => item.price * item.quantity)
|
|
380
|
+
.reduce((sum, total) => sum + total, 0);
|
|
381
|
+
}, [items]); // ← Depends on individual observable
|
|
382
|
+
|
|
383
|
+
const batchUpdateItems = Observable.batch(() => {
|
|
384
|
+
items.push({ active: true, price: 10, quantity: 2 });
|
|
385
|
+
items.push({ active: true, price: 15, quantity: 1 });
|
|
386
|
+
items.push({ active: false, price: 20, quantity: 3 });
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const optimizedCalculation = Observable.computed(() => {
|
|
390
|
+
console.log("✅ Optimized recalculation");
|
|
391
|
+
return items.val()
|
|
392
|
+
.filter(item => item.active)
|
|
393
|
+
.map(item => item.price * item.quantity)
|
|
394
|
+
.reduce((sum, total) => sum + total, 0);
|
|
395
|
+
}, batchUpdateItems); // ← Depends on batch function
|
|
396
|
+
|
|
397
|
+
// Without batching:
|
|
398
|
+
items.push({ active: true, price: 10, quantity: 2 }); // 🔄 Recalculating...
|
|
399
|
+
items.push({ active: true, price: 15, quantity: 1 }); // 🔄 Recalculating...
|
|
400
|
+
items.push({ active: false, price: 20, quantity: 3 }); // 🔄 Recalculating...
|
|
401
|
+
|
|
402
|
+
// With batching:
|
|
403
|
+
batchUpdateItems(); // ✅ Optimized recalculation (only once!)
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Best Practices
|
|
407
|
+
|
|
408
|
+
1. **Use batch dependencies for expensive computations**: When you have costly computed observables that shouldn't recalculate on every individual change
|
|
409
|
+
|
|
410
|
+
2. **Keep individual subscribers for immediate feedback**: UI feedback like input validation should use direct subscriptions
|
|
411
|
+
|
|
412
|
+
3. **Batch related operations**: Group logically connected updates that should trigger dependent computations together
|
|
413
|
+
|
|
414
|
+
4. **Don't over-batch**: Only use batching when you have computed observables that benefit from delayed updates
|
|
415
|
+
|
|
416
|
+
### Common Patterns
|
|
417
|
+
|
|
418
|
+
### State Machine with Batched Transitions
|
|
419
|
+
```javascript
|
|
420
|
+
const gameState = Observable.object({
|
|
421
|
+
level: 1,
|
|
422
|
+
score: 0,
|
|
423
|
+
lives: 3
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Individual subscribers for immediate UI updates
|
|
427
|
+
gameState.score.subscribe(score => updateScoreDisplay(score));
|
|
428
|
+
gameState.lives.subscribe(lives => updateLivesDisplay(lives));
|
|
429
|
+
|
|
430
|
+
// Batch function for state transitions
|
|
431
|
+
const levelUp = Observable.batch(() => {
|
|
432
|
+
gameState.level.set(gameState.level.val() + 1);
|
|
433
|
+
gameState.score.set(gameState.score.val() + 1000);
|
|
434
|
+
gameState.lives.set(gameState.lives.val() + 1);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Complex computed that should only run after complete transitions
|
|
438
|
+
const gameStatusMessage = Observable.computed(() => {
|
|
439
|
+
const state = gameState.$val();
|
|
440
|
+
return `Level ${state.level}: ${state.score} points, ${state.lives} lives remaining`;
|
|
441
|
+
}, levelUp); // ← Only updates when levelUp() is called
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### When NOT to Use Batch Dependencies
|
|
445
|
+
|
|
446
|
+
- **Real-time updates**: When computed observables need to update immediately
|
|
447
|
+
- **Simple computations**: When the computational cost is minimal
|
|
448
|
+
- **Debugging**: Batching can make the flow harder to debug
|
|
449
|
+
- **Single observable changes**: No benefit when only one observable changes
|
|
450
|
+
|
|
451
|
+
The key insight is that batching in NativeDocument is about **controlling when dependent computed observables recalculate**, not about suppressing individual observable notifications.
|
|
452
|
+
|
|
173
453
|
## String Templates with Observables
|
|
174
454
|
|
|
175
455
|
### The .use() Method
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -260,6 +260,33 @@ npm run dev
|
|
|
260
260
|
|
|
261
261
|
MIT © [AfroCodeur](https://github.com/afrocodeur)
|
|
262
262
|
|
|
263
|
+
## ❤️ Support the Project
|
|
264
|
+
|
|
265
|
+
NativeDocument is developed and maintained in my spare time.
|
|
266
|
+
If it helps you build better applications, consider supporting its development:
|
|
267
|
+
|
|
268
|
+
[](https://ko-fi.com/native_document)
|
|
269
|
+
|
|
270
|
+
You can also support the project via crypto donations:
|
|
271
|
+
|
|
272
|
+
- **USDT (TRC20)**
|
|
273
|
+
- **USDT (BSC)**
|
|
274
|
+
- **USDC (Base)**
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
0xCe426776DDb07256aBd58c850dd57041BC85Ea7D
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Your support helps me:
|
|
281
|
+
|
|
282
|
+
- Maintain and improve NativeDocument
|
|
283
|
+
- Write better documentation and examples
|
|
284
|
+
- Fix bugs and ship new features
|
|
285
|
+
- Produce tutorials and learning content
|
|
286
|
+
|
|
287
|
+
Thanks for your support! 🙏
|
|
288
|
+
|
|
289
|
+
|
|
263
290
|
## Acknowledgments
|
|
264
291
|
|
|
265
292
|
Thanks to all contributors and the JavaScript community for inspiration.
|
package/src/data/Observable.js
CHANGED
|
@@ -14,17 +14,45 @@ export function Observable(value) {
|
|
|
14
14
|
return new ObservableItem(value);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param {Function} callback
|
|
20
|
+
* @param {Array|Function} dependencies
|
|
21
|
+
* @returns {ObservableItem}
|
|
22
|
+
*/
|
|
17
23
|
Observable.computed = function(callback, dependencies = []) {
|
|
18
24
|
const initialValue = callback();
|
|
19
25
|
const observable = new ObservableItem(initialValue);
|
|
20
|
-
|
|
21
26
|
const updatedValue = () => observable.set(callback());
|
|
22
27
|
|
|
28
|
+
if(Validator.isFunction(dependencies)) {
|
|
29
|
+
if(!Validator.isObservable(dependencies.$observer)) {
|
|
30
|
+
throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
|
|
31
|
+
}
|
|
32
|
+
dependencies.$observer.subscribe(updatedValue);
|
|
33
|
+
return observable;
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
dependencies.forEach(dependency => dependency.subscribe(updatedValue));
|
|
24
37
|
|
|
25
38
|
return observable;
|
|
26
39
|
};
|
|
27
40
|
|
|
41
|
+
Observable.batch = function(callback) {
|
|
42
|
+
const $observer = Observable(0);
|
|
43
|
+
const batch = function() {
|
|
44
|
+
if(Validator.isAsyncFunction(callback)) {
|
|
45
|
+
return (callback(...arguments)).then(() => {
|
|
46
|
+
$observer.trigger();
|
|
47
|
+
}).catch(error => { throw error; });
|
|
48
|
+
}
|
|
49
|
+
callback(...arguments);
|
|
50
|
+
$observer.trigger();
|
|
51
|
+
};
|
|
52
|
+
batch.$observer = $observer;
|
|
53
|
+
return batch;
|
|
54
|
+
}
|
|
55
|
+
|
|
28
56
|
/**
|
|
29
57
|
*
|
|
30
58
|
* @param id
|
|
@@ -175,7 +203,7 @@ Observable.array = function(target) {
|
|
|
175
203
|
};
|
|
176
204
|
});
|
|
177
205
|
|
|
178
|
-
const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find'];
|
|
206
|
+
const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex'];
|
|
179
207
|
overrideMethods.forEach((method) => {
|
|
180
208
|
observer[method] = function(callback) {
|
|
181
209
|
return observer.val()[method](callback);
|
|
@@ -7,11 +7,15 @@
|
|
|
7
7
|
export default function ObservableChecker($observable, $checker) {
|
|
8
8
|
this.observable = $observable;
|
|
9
9
|
this.checker = $checker;
|
|
10
|
+
const $unSubscriptions = [];
|
|
10
11
|
|
|
11
12
|
this.subscribe = function(callback) {
|
|
12
|
-
|
|
13
|
+
const unSubscribe = $observable.subscribe((value) => {
|
|
13
14
|
callback && callback($checker(value));
|
|
14
15
|
});
|
|
16
|
+
$unSubscriptions.push(unSubscribe);
|
|
17
|
+
|
|
18
|
+
return unSubscribe;
|
|
15
19
|
};
|
|
16
20
|
|
|
17
21
|
this.val = function() {
|
|
@@ -26,9 +30,9 @@ export default function ObservableChecker($observable, $checker) {
|
|
|
26
30
|
};
|
|
27
31
|
this.trigger = function() {
|
|
28
32
|
return $observable.trigger();
|
|
29
|
-
}
|
|
33
|
+
};
|
|
30
34
|
|
|
31
35
|
this.cleanup = function() {
|
|
32
|
-
|
|
33
|
-
}
|
|
36
|
+
$unSubscriptions.forEach(unSubscription => unSubscription());
|
|
37
|
+
};
|
|
34
38
|
}
|
|
@@ -4,6 +4,7 @@ import {createTextNode} from "../../wrappers/HtmlElementWrapper";
|
|
|
4
4
|
import Validator from "../../utils/validator";
|
|
5
5
|
import {throttle} from "../../utils/helpers.js";
|
|
6
6
|
import Anchor from "../anchor";
|
|
7
|
+
import DebugManager from "../../utils/debug-manager";
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -28,12 +29,21 @@ const getKey = (item, defaultKey, key) => {
|
|
|
28
29
|
* @param {Set} keyIds
|
|
29
30
|
*/
|
|
30
31
|
const cleanBlockByCache = (cache, keyIds) => {
|
|
31
|
-
|
|
32
|
+
const toRemove = [];
|
|
33
|
+
for(const [key, cacheItem] of cache.entries()) {
|
|
32
34
|
if(keyIds.has(key)) {
|
|
33
35
|
continue;
|
|
34
36
|
}
|
|
35
|
-
|
|
37
|
+
toRemove.push({ key, cacheItem });
|
|
36
38
|
}
|
|
39
|
+
if(toRemove.length === 0) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
toRemove.forEach(({ key, cacheItem }) => {
|
|
43
|
+
cacheItem.child.remove();
|
|
44
|
+
cacheItem.indexObserver.cleanup();
|
|
45
|
+
cache.delete(key);
|
|
46
|
+
});
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
/**
|
|
@@ -46,33 +56,102 @@ const cleanBlockByCache = (cache, keyIds) => {
|
|
|
46
56
|
export function ForEach(data, callback, key) {
|
|
47
57
|
const element = new Anchor('ForEach');
|
|
48
58
|
const blockEnd = element.endElement();
|
|
59
|
+
const blockStart = element.startElement();
|
|
49
60
|
|
|
50
61
|
let cache = new Map();
|
|
62
|
+
const keyIds = new Set();
|
|
51
63
|
|
|
52
64
|
const handleContentItem = (item, indexKey) => {
|
|
53
65
|
const keyId = getKey(item, indexKey, key);
|
|
54
66
|
|
|
55
67
|
if(cache.has(keyId)) {
|
|
56
|
-
cache.get(keyId)
|
|
68
|
+
const cacheItem = cache.get(keyId);
|
|
69
|
+
cacheItem.indexObserver.set(indexKey);
|
|
70
|
+
cacheItem.isNew = false;
|
|
57
71
|
}
|
|
58
72
|
else {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
child =
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const indexObserver = Observable(indexKey);
|
|
76
|
+
let child = callback(item, indexObserver);
|
|
77
|
+
if(Validator.isStringOrObservable(child)) {
|
|
78
|
+
child = createTextNode(child);
|
|
79
|
+
}
|
|
80
|
+
cache.set(keyId, { isNew: true, child, indexObserver});
|
|
81
|
+
} catch (e) {
|
|
82
|
+
DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
|
|
83
|
+
throw e;
|
|
63
84
|
}
|
|
64
|
-
cache.set(keyId, { child, indexObserver});
|
|
65
85
|
}
|
|
66
86
|
return keyId;
|
|
67
|
-
}
|
|
68
|
-
const keyIds = new Set();
|
|
87
|
+
};
|
|
69
88
|
|
|
70
|
-
const
|
|
71
|
-
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
89
|
+
const batchDOMUpdates = () => {
|
|
72
90
|
const parent = blockEnd.parentNode;
|
|
73
91
|
if(!parent) {
|
|
74
92
|
return;
|
|
75
93
|
}
|
|
94
|
+
|
|
95
|
+
let previousElementSibling = blockStart;
|
|
96
|
+
const elementsToInsert = [];
|
|
97
|
+
const elementsToMove = [];
|
|
98
|
+
let fragment = null;
|
|
99
|
+
|
|
100
|
+
let saveFragment = (beforeTarget) => {
|
|
101
|
+
if(fragment) {
|
|
102
|
+
elementsToInsert.push({ child: fragment, before: beforeTarget });
|
|
103
|
+
fragment = null;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const keyIdsArray = Array.from(keyIds);
|
|
108
|
+
for(let i = 0; i < keyIdsArray.length; i++) {
|
|
109
|
+
const itemKey = keyIdsArray[i];
|
|
110
|
+
const cacheItem = cache.get(itemKey);
|
|
111
|
+
if(!cacheItem) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if(previousElementSibling && previousElementSibling.nextSibling === cacheItem.child) {
|
|
116
|
+
previousElementSibling = cacheItem.child;
|
|
117
|
+
saveFragment(cacheItem.child);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if(cacheItem.isNew) {
|
|
121
|
+
fragment = fragment || document.createDocumentFragment();
|
|
122
|
+
fragment.append(cacheItem.child);
|
|
123
|
+
cacheItem.isNew = false;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
saveFragment(cacheItem.child);
|
|
127
|
+
const nextChild = cache.get(keyIdsArray[i + 1])?.child;
|
|
128
|
+
if(nextChild) {
|
|
129
|
+
if(cacheItem.child.nextSibling !== nextChild) {
|
|
130
|
+
elementsToMove.push({ child: cacheItem.child, before: nextChild });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
previousElementSibling = cacheItem.child;
|
|
135
|
+
}
|
|
136
|
+
saveFragment(blockEnd);
|
|
137
|
+
|
|
138
|
+
elementsToInsert.forEach(({ child, before }) => {
|
|
139
|
+
if(before) {
|
|
140
|
+
parent.insertBefore(child, before);
|
|
141
|
+
} else {
|
|
142
|
+
element.appendChild(child);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
elementsToMove.forEach(({ child, before }) => {
|
|
147
|
+
parent.insertBefore(child, before);
|
|
148
|
+
})
|
|
149
|
+
saveFragment = null;
|
|
150
|
+
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const buildContent = () => {
|
|
154
|
+
const items = (Validator.isObservable(data)) ? data.val() : data;
|
|
76
155
|
keyIds.clear();
|
|
77
156
|
if(Array.isArray(items)) {
|
|
78
157
|
items.forEach((item, index) => keyIds.add(handleContentItem(item, index)));
|
|
@@ -83,25 +162,13 @@ export function ForEach(data, callback, key) {
|
|
|
83
162
|
}
|
|
84
163
|
|
|
85
164
|
cleanBlockByCache(cache, keyIds);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const { child } = cache.get(item);
|
|
89
|
-
if(child) {
|
|
90
|
-
if(nextElementSibling && nextElementSibling.previousSibling === child) {
|
|
91
|
-
nextElementSibling = child;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
parent.insertBefore(child, nextElementSibling);
|
|
95
|
-
nextElementSibling = child;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
165
|
+
|
|
166
|
+
batchDOMUpdates();
|
|
98
167
|
};
|
|
99
168
|
|
|
100
169
|
buildContent();
|
|
101
170
|
if(Validator.isObservable(data)) {
|
|
102
|
-
data.subscribe(
|
|
103
|
-
buildContent(newValue, oldValue);
|
|
104
|
-
}, 50, { debounce: true }))
|
|
171
|
+
data.subscribe(buildContent)
|
|
105
172
|
}
|
|
106
173
|
return element;
|
|
107
174
|
}
|
package/src/utils/validator.js
CHANGED
|
@@ -28,6 +28,9 @@ const Validator = {
|
|
|
28
28
|
isFunction(value) {
|
|
29
29
|
return typeof value === 'function';
|
|
30
30
|
},
|
|
31
|
+
isAsyncFunction(value) {
|
|
32
|
+
return typeof value === 'function' && value.constructor.name === 'AsyncFunction';
|
|
33
|
+
},
|
|
31
34
|
isObject(value) {
|
|
32
35
|
return typeof value === 'object';
|
|
33
36
|
},
|