native-document 1.0.117 โ†’ 1.0.119

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.
@@ -363,16 +363,16 @@ var NativeComponents = (function (exports) {
363
363
  // });
364
364
  };
365
365
 
366
- let DebugManager$1 = {};
366
+ let DebugManager$2 = {};
367
367
  {
368
- DebugManager$1 = {
368
+ DebugManager$2 = {
369
369
  log() {},
370
370
  warn() {},
371
371
  error() {},
372
372
  disable() {}
373
373
  };
374
374
  }
375
- var DebugManager = DebugManager$1;
375
+ var DebugManager$1 = DebugManager$2;
376
376
 
377
377
  /**
378
378
  *
@@ -846,17 +846,17 @@ var NativeComponents = (function (exports) {
846
846
  const method = methods[name];
847
847
 
848
848
  if (typeof method !== 'function') {
849
- DebugManager.warn('NDElement.extend', `"${name}" is not a function, skipping`);
849
+ DebugManager$1.warn('NDElement.extend', `"${name}" is not a function, skipping`);
850
850
  continue;
851
851
  }
852
852
 
853
853
  if (protectedMethods.has(name)) {
854
- DebugManager.error('NDElement.extend', `Cannot override protected method "${name}"`);
854
+ DebugManager$1.error('NDElement.extend', `Cannot override protected method "${name}"`);
855
855
  throw new NativeDocumentError(`Cannot override protected method "${name}"`);
856
856
  }
857
857
 
858
858
  if (NDElement.prototype[name]) {
859
- DebugManager.warn('NDElement.extend', `Overwriting existing prototype method "${name}"`);
859
+ DebugManager$1.warn('NDElement.extend', `Overwriting existing prototype method "${name}"`);
860
860
  }
861
861
 
862
862
  NDElement.prototype[name] = method;
@@ -1081,7 +1081,7 @@ var NativeComponents = (function (exports) {
1081
1081
  }
1082
1082
  }
1083
1083
  if (cleanedCount > 0) {
1084
- DebugManager.log('Memory Auto Clean', `๐Ÿงน Cleaned ${cleanedCount} orphaned observables`);
1084
+ DebugManager$1.log('Memory Auto Clean', `๐Ÿงน Cleaned ${cleanedCount} orphaned observables`);
1085
1085
  }
1086
1086
  }
1087
1087
  };
@@ -1221,6 +1221,97 @@ var NativeComponents = (function (exports) {
1221
1221
  return cloned;
1222
1222
  };
1223
1223
 
1224
+ const $parseDateParts = (value, locale) => {
1225
+ const d = new Date(value);
1226
+ return {
1227
+ d,
1228
+ parts: new Intl.DateTimeFormat(locale, {
1229
+ year: 'numeric',
1230
+ month: 'long',
1231
+ day: '2-digit',
1232
+ hour: '2-digit',
1233
+ minute: '2-digit',
1234
+ second: '2-digit',
1235
+ }).formatToParts(d).reduce((acc, { type, value }) => {
1236
+ acc[type] = value;
1237
+ return acc;
1238
+ }, {})
1239
+ };
1240
+ };
1241
+
1242
+ const $applyDatePattern = (pattern, d, parts) => {
1243
+ const pad = n => String(n).padStart(2, '0');
1244
+ return pattern
1245
+ .replace('YYYY', parts.year)
1246
+ .replace('YY', parts.year.slice(-2))
1247
+ .replace('MMMM', parts.month)
1248
+ .replace('MMM', parts.month.slice(0, 3))
1249
+ .replace('MM', pad(d.getMonth() + 1))
1250
+ .replace('DD', pad(d.getDate()))
1251
+ .replace('D', d.getDate())
1252
+ .replace('HH', parts.hour)
1253
+ .replace('mm', parts.minute)
1254
+ .replace('ss', parts.second);
1255
+ };
1256
+
1257
+ const Formatters = {
1258
+ currency: (value, locale, { currency = 'XOF', notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
1259
+ new Intl.NumberFormat(locale, {
1260
+ style: 'currency',
1261
+ currency,
1262
+ notation,
1263
+ minimumFractionDigits,
1264
+ maximumFractionDigits
1265
+ }).format(value),
1266
+
1267
+ number: (value, locale, { notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
1268
+ new Intl.NumberFormat(locale, {
1269
+ notation,
1270
+ minimumFractionDigits,
1271
+ maximumFractionDigits
1272
+ }).format(value),
1273
+
1274
+ percent: (value, locale, { decimals = 1 } = {}) =>
1275
+ new Intl.NumberFormat(locale, {
1276
+ style: 'percent',
1277
+ maximumFractionDigits: decimals
1278
+ }).format(value),
1279
+
1280
+ date: (value, locale, { format, dateStyle = 'long' } = {}) => {
1281
+ if (format) {
1282
+ const { d, parts } = $parseDateParts(value, locale);
1283
+ return $applyDatePattern(format, d, parts);
1284
+ }
1285
+ return new Intl.DateTimeFormat(locale, { dateStyle }).format(new Date(value));
1286
+ },
1287
+
1288
+ time: (value, locale, { format, hour = '2-digit', minute = '2-digit', second } = {}) => {
1289
+ if (format) {
1290
+ const { d, parts } = $parseDateParts(value, locale);
1291
+ return $applyDatePattern(format, d, parts);
1292
+ }
1293
+ return new Intl.DateTimeFormat(locale, { hour, minute, second }).format(new Date(value));
1294
+ },
1295
+
1296
+ datetime: (value, locale, { format, dateStyle = 'long', hour = '2-digit', minute = '2-digit', second } = {}) => {
1297
+ if (format) {
1298
+ const { d, parts } = $parseDateParts(value, locale);
1299
+ return $applyDatePattern(format, d, parts);
1300
+ }
1301
+ return new Intl.DateTimeFormat(locale, { dateStyle, hour, minute, second }).format(new Date(value));
1302
+ },
1303
+
1304
+ relative: (value, locale, { unit = 'day', numeric = 'auto' } = {}) => {
1305
+ const diff = Math.round((value - Date.now()) / (1000 * 60 * 60 * 24));
1306
+ return new Intl.RelativeTimeFormat(locale, { numeric }).format(diff, unit);
1307
+ },
1308
+
1309
+ plural: (value, locale, { singular, plural } = {}) => {
1310
+ const rule = new Intl.PluralRules(locale).select(value);
1311
+ return `${value} ${rule === 'one' ? singular : plural}`;
1312
+ },
1313
+ };
1314
+
1224
1315
  const LocalStorage = {
1225
1316
  getJson(key) {
1226
1317
  let value = localStorage.getItem(key);
@@ -1277,1087 +1368,596 @@ var NativeComponents = (function (exports) {
1277
1368
  }
1278
1369
  };
1279
1370
 
1280
- const StoreFactory = function() {
1371
+ /**
1372
+ *
1373
+ * @param {*} value
1374
+ * @param {{ propagation: boolean, reset: boolean} | null} configs
1375
+ * @class ObservableItem
1376
+ */
1377
+ function ObservableItem(value, configs = null) {
1378
+ value = Validator.isObservable(value) ? value.val() : value;
1281
1379
 
1282
- const $stores = new Map();
1283
- const $followersCache = new Map();
1380
+ this.$previousValue = null;
1381
+ this.$currentValue = value;
1284
1382
 
1285
- /**
1286
- * Internal helper โ€” retrieves a store entry or throws if not found.
1287
- */
1288
- const $getStoreOrThrow = (method, name) => {
1289
- const item = $stores.get(name);
1290
- if (!item) {
1291
- DebugManager.error('Store', `Store.${method}('${name}') : store not found. Did you call Store.create('${name}') first?`);
1292
- throw new NativeDocumentError(
1293
- `Store.${method}('${name}') : store not found.`
1294
- );
1295
- }
1296
- return item;
1297
- };
1383
+ this.$firstListener = null;
1384
+ this.$listeners = null;
1385
+ this.$watchers = null;
1298
1386
 
1299
- /**
1300
- * Internal helper โ€” blocks write operations on a read-only observer.
1301
- */
1302
- const $applyReadOnly = (observer, name, context) => {
1303
- const readOnlyError = (method) => () => {
1304
- DebugManager.error('Store', `Store.${context}('${name}') is read-only. '${method}()' is not allowed.`);
1305
- throw new NativeDocumentError(
1306
- `Store.${context}('${name}') is read-only.`
1307
- );
1308
- };
1309
- observer.set = readOnlyError('set');
1310
- observer.toggle = readOnlyError('toggle');
1311
- observer.reset = readOnlyError('reset');
1312
- };
1387
+ this.$memoryId = null;
1313
1388
 
1314
- const $createObservable = (value, options = {}) => {
1315
- if(Array.isArray(value)) {
1316
- return Observable.array(value, options);
1317
- }
1318
- if(typeof value === 'object') {
1319
- return Observable.object(value, options);
1389
+ if(configs) {
1390
+ this.configs = configs;
1391
+ if(configs.reset) {
1392
+ this.$initialValue = Validator.isObject(value) ? deepClone(value) : value;
1320
1393
  }
1321
- return Observable(value, options);
1322
- };
1394
+ }
1395
+ }
1323
1396
 
1324
- const $api = {
1325
- /**
1326
- * Create a new state and return the observer.
1327
- * Throws if a store with the same name already exists.
1328
- *
1329
- * @param {string} name
1330
- * @param {*} value
1331
- * @returns {ObservableItem}
1332
- */
1333
- create(name, value) {
1334
- if ($stores.has(name)) {
1335
- DebugManager.warn('Store', `Store.create('${name}') : a store with this name already exists. Use Store.get('${name}') to retrieve it.`);
1336
- throw new NativeDocumentError(
1337
- `Store.create('${name}') : a store with this name already exists.`
1338
- );
1339
- }
1340
- const observer = $createObservable(value);
1341
- $stores.set(name, { observer, subscribers: new Set(), resettable: false, composed: false });
1342
- return observer;
1343
- },
1397
+ Object.defineProperty(ObservableItem.prototype, '$value', {
1398
+ get() {
1399
+ return this.$currentValue;
1400
+ },
1401
+ set(value) {
1402
+ this.set(value);
1403
+ },
1404
+ configurable: true,
1405
+ });
1344
1406
 
1345
- /**
1346
- * Create a new resettable state and return the observer.
1347
- * The store can be reset to its initial value via Store.reset(name).
1348
- * Throws if a store with the same name already exists.
1349
- *
1350
- * @param {string} name
1351
- * @param {*} value
1352
- * @returns {ObservableItem}
1353
- */
1354
- createResettable(name, value) {
1355
- if ($stores.has(name)) {
1356
- DebugManager.warn('Store', `Store.createResettable('${name}') : a store with this name already exists.`);
1357
- throw new NativeDocumentError(
1358
- `Store.createResettable('${name}') : a store with this name already exists.`
1359
- );
1360
- }
1361
- const observer = $createObservable(value, { reset: true });
1362
- $stores.set(name, { observer, subscribers: new Set(), resettable: true, composed: false });
1363
- return observer;
1364
- },
1407
+ ObservableItem.prototype.__$isObservable = true;
1408
+ const noneTrigger = function() {};
1365
1409
 
1366
- /**
1367
- * Create a computed store derived from other stores.
1368
- * The value is automatically recalculated when any dependency changes.
1369
- * This store is read-only โ€” Store.use() and Store.set() will throw.
1370
- * Throws if a store with the same name already exists.
1371
- *
1372
- * @param {string} name
1373
- * @param {() => *} computation - Function that returns the computed value
1374
- * @param {string[]} dependencies - Names of the stores to watch
1375
- * @returns {ObservableItem}
1376
- *
1377
- * @example
1378
- * Store.create('products', [{ id: 1, price: 10 }]);
1379
- * Store.create('cart', [{ productId: 1, quantity: 2 }]);
1380
- *
1381
- * Store.createComposed('total', () => {
1382
- * const products = Store.get('products').val();
1383
- * const cart = Store.get('cart').val();
1384
- * return cart.reduce((sum, item) => {
1385
- * const product = products.find(p => p.id === item.productId);
1386
- * return sum + (product.price * item.quantity);
1387
- * }, 0);
1388
- * }, ['products', 'cart']);
1389
- */
1390
- createComposed(name, computation, dependencies) {
1391
- if ($stores.has(name)) {
1392
- DebugManager.warn('Store', `Store.createComposed('${name}') : a store with this name already exists.`);
1393
- throw new NativeDocumentError(
1394
- `Store.createComposed('${name}') : a store with this name already exists.`
1395
- );
1396
- }
1397
- if (typeof computation !== 'function') {
1398
- throw new NativeDocumentError(
1399
- `Store.createComposed('${name}') : computation must be a function.`
1400
- );
1401
- }
1402
- if (!Array.isArray(dependencies) || dependencies.length === 0) {
1403
- throw new NativeDocumentError(
1404
- `Store.createComposed('${name}') : dependencies must be a non-empty array of store names.`
1405
- );
1406
- }
1410
+ /**
1411
+ * Intercepts and transforms values before they are set on the observable.
1412
+ * The interceptor can modify the value or return undefined to use the original value.
1413
+ *
1414
+ * @param {(value) => any} callback - Interceptor function that receives (newValue, currentValue) and returns the transformed value or undefined
1415
+ * @returns {ObservableItem} The observable instance for chaining
1416
+ * @example
1417
+ * const count = Observable(0);
1418
+ * count.intercept((newVal, oldVal) => Math.max(0, newVal)); // Prevent negative values
1419
+ */
1420
+ ObservableItem.prototype.intercept = function(callback) {
1421
+ this.$interceptor = callback;
1422
+ this.set = this.$setWithInterceptor;
1423
+ return this;
1424
+ };
1407
1425
 
1408
- // Resolve dependency observers
1409
- const depObservers = dependencies.map(depName => {
1410
- if(typeof depName !== 'string') {
1411
- return depName;
1412
- }
1413
- const depItem = $stores.get(depName);
1414
- if (!depItem) {
1415
- DebugManager.error('Store', `Store.createComposed('${name}') : dependency '${depName}' not found. Create it first.`);
1416
- throw new NativeDocumentError(
1417
- `Store.createComposed('${name}') : dependency store '${depName}' not found.`
1418
- );
1419
- }
1420
- return depItem.observer;
1421
- });
1426
+ ObservableItem.prototype.triggerFirstListener = function(operations) {
1427
+ this.$firstListener(this.$currentValue, this.$previousValue, operations);
1428
+ };
1422
1429
 
1423
- // Create computed observable from dependency observers
1424
- const observer = Observable.computed(computation, depObservers);
1430
+ ObservableItem.prototype.triggerListeners = function(operations) {
1431
+ const $listeners = this.$listeners;
1432
+ const $previousValue = this.$previousValue;
1433
+ const $currentValue = this.$currentValue;
1425
1434
 
1426
- $stores.set(name, { observer, subscribers: new Set(), resettable: false, composed: true });
1427
- return observer;
1428
- },
1435
+ for(let i = 0, length = $listeners.length; i < length; i++) {
1436
+ $listeners[i]($currentValue, $previousValue, operations);
1437
+ }
1438
+ };
1429
1439
 
1430
- /**
1431
- * Returns true if a store with the given name exists.
1432
- *
1433
- * @param {string} name
1434
- * @returns {boolean}
1435
- */
1436
- has(name) {
1437
- return $stores.has(name);
1438
- },
1440
+ ObservableItem.prototype.triggerWatchers = function(operations) {
1441
+ const $watchers = this.$watchers;
1442
+ const $previousValue = this.$previousValue;
1443
+ const $currentValue = this.$currentValue;
1439
1444
 
1440
- /**
1441
- * Resets a resettable store to its initial value and notifies all subscribers.
1442
- * Throws if the store was not created with createResettable().
1443
- *
1444
- * @param {string} name
1445
- */
1446
- reset(name) {
1447
- const item = $getStoreOrThrow('reset', name);
1448
- if (item.composed) {
1449
- DebugManager.error('Store', `Store.reset('${name}') : composed stores cannot be reset. Their value is derived from dependencies.`);
1450
- throw new NativeDocumentError(
1451
- `Store.reset('${name}') : composed stores cannot be reset.`
1452
- );
1453
- }
1454
- if (!item.resettable) {
1455
- DebugManager.error('Store', `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`);
1456
- throw new NativeDocumentError(
1457
- `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`
1458
- );
1459
- }
1460
- item.observer.reset();
1461
- },
1445
+ const $currentValueCallbacks = $watchers.get($currentValue);
1446
+ const $previousValueCallbacks = $watchers.get($previousValue);
1447
+ if($currentValueCallbacks) {
1448
+ $currentValueCallbacks(true, $previousValue, operations);
1449
+ }
1450
+ if($previousValueCallbacks) {
1451
+ $previousValueCallbacks(false, $currentValue, operations);
1452
+ }
1453
+ };
1462
1454
 
1463
- /**
1464
- * Returns a two-way synchronized follower of the store.
1465
- * Writing to the follower propagates the value back to the store and all its subscribers.
1466
- * Throws if called on a composed store โ€” use Store.follow() instead.
1467
- * Call follower.destroy() or follower.dispose() to unsubscribe.
1468
- *
1469
- * @param {string} name
1470
- * @returns {ObservableItem}
1471
- */
1472
- use(name) {
1473
- const item = $getStoreOrThrow('use', name);
1455
+ ObservableItem.prototype.triggerAll = function(operations) {
1456
+ this.triggerWatchers(operations);
1457
+ this.triggerListeners(operations);
1458
+ };
1474
1459
 
1475
- if (item.composed) {
1476
- DebugManager.error('Store', `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`);
1477
- throw new NativeDocumentError(
1478
- `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`
1479
- );
1480
- }
1460
+ ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations) {
1461
+ this.triggerWatchers(operations);
1462
+ this.triggerFirstListener(operations);
1463
+ };
1481
1464
 
1482
- const { observer: originalObserver, subscribers } = item;
1483
- const observerFollower = $createObservable(originalObserver.val());
1465
+ ObservableItem.prototype.assocTrigger = function() {
1466
+ this.$firstListener = null;
1467
+ if(this.$watchers?.size && this.$listeners?.length) {
1468
+ this.trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
1469
+ return;
1470
+ }
1471
+ if(this.$listeners?.length) {
1472
+ if(this.$listeners.length === 1) {
1473
+ this.$firstListener = this.$listeners[0];
1474
+ this.trigger = this.$firstListener.length === 0 ? this.$firstListener : this.triggerFirstListener;
1475
+ }
1476
+ else {
1477
+ this.trigger = this.triggerListeners;
1478
+ }
1479
+ return;
1480
+ }
1481
+ if(this.$watchers?.size) {
1482
+ this.trigger = this.triggerWatchers;
1483
+ return;
1484
+ }
1485
+ this.trigger = noneTrigger;
1486
+ };
1487
+ ObservableItem.prototype.trigger = noneTrigger;
1484
1488
 
1485
- const onStoreChange = value => observerFollower.set(value);
1486
- const onFollowerChange = value => originalObserver.set(value);
1489
+ ObservableItem.prototype.$updateWithNewValue = function(newValue) {
1490
+ newValue = newValue?.__$isObservable ? newValue.val() : newValue;
1491
+ if(this.$currentValue === newValue) {
1492
+ return;
1493
+ }
1494
+ this.$previousValue = this.$currentValue;
1495
+ this.$currentValue = newValue;
1496
+ this.trigger();
1497
+ this.$previousValue = null;
1498
+ };
1487
1499
 
1488
- originalObserver.subscribe(onStoreChange);
1489
- observerFollower.subscribe(onFollowerChange);
1500
+ /**
1501
+ * @param {*} data
1502
+ */
1503
+ ObservableItem.prototype.$setWithInterceptor = function(data) {
1504
+ let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
1505
+ const result = this.$interceptor(newValue, this.$currentValue);
1490
1506
 
1491
- observerFollower.destroy = () => {
1492
- originalObserver.unsubscribe(onStoreChange);
1493
- observerFollower.unsubscribe(onFollowerChange);
1494
- subscribers.delete(observerFollower);
1495
- observerFollower.cleanup();
1496
- };
1497
- observerFollower.dispose = observerFollower.destroy;
1507
+ if (result !== undefined) {
1508
+ newValue = result;
1509
+ }
1498
1510
 
1499
- subscribers.add(observerFollower);
1500
- return observerFollower;
1501
- },
1511
+ this.$updateWithNewValue(newValue);
1512
+ };
1502
1513
 
1503
- /**
1504
- * Returns a read-only follower of the store.
1505
- * The follower reflects store changes but cannot write back to the store.
1506
- * Any attempt to call .set(), .toggle() or .reset() will throw.
1507
- * Call follower.destroy() or follower.dispose() to unsubscribe.
1508
- *
1509
- * @param {string} name
1510
- * @returns {ObservableItem}
1511
- */
1512
- follow(name) {
1513
- const { observer: originalObserver, subscribers } = $getStoreOrThrow('follow', name);
1514
- const observerFollower = $createObservable(originalObserver.val());
1514
+ /**
1515
+ * @param {*} data
1516
+ */
1517
+ ObservableItem.prototype.$basicSet = function(data) {
1518
+ let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
1519
+ this.$updateWithNewValue(newValue);
1520
+ };
1515
1521
 
1516
- const onStoreChange = value => observerFollower.set(value);
1517
- originalObserver.subscribe(onStoreChange);
1522
+ ObservableItem.prototype.set = ObservableItem.prototype.$basicSet;
1518
1523
 
1519
- $applyReadOnly(observerFollower, name, 'follow');
1524
+ ObservableItem.prototype.val = function() {
1525
+ return this.$currentValue;
1526
+ };
1520
1527
 
1521
- observerFollower.destroy = () => {
1522
- originalObserver.unsubscribe(onStoreChange);
1523
- subscribers.delete(observerFollower);
1524
- observerFollower.cleanup();
1525
- };
1526
- observerFollower.dispose = observerFollower.destroy;
1528
+ ObservableItem.prototype.disconnectAll = function() {
1529
+ this.$previousValue = null;
1530
+ this.$currentValue = null;
1531
+ this.$listeners = null;
1532
+ this.$watchers = null;
1533
+ this.trigger = noneTrigger;
1534
+ };
1527
1535
 
1528
- subscribers.add(observerFollower);
1529
- return observerFollower;
1530
- },
1536
+ /**
1537
+ * Registers a cleanup callback that will be executed when the observable is cleaned up.
1538
+ * Useful for disposing resources, removing event listeners, or other cleanup tasks.
1539
+ *
1540
+ * @param {Function} callback - Cleanup function to execute on observable disposal
1541
+ * @example
1542
+ * const obs = Observable(0);
1543
+ * obs.onCleanup(() => console.log('Cleaned up!'));
1544
+ * obs.cleanup(); // Logs: "Cleaned up!"
1545
+ */
1546
+ ObservableItem.prototype.onCleanup = function(callback) {
1547
+ this.$cleanupListeners = this.$cleanupListeners ?? [];
1548
+ this.$cleanupListeners.push(callback);
1549
+ };
1531
1550
 
1532
- /**
1533
- * Returns the raw store observer directly (no follower, no cleanup contract).
1534
- * Use this for direct read access when you don't need to unsubscribe.
1535
- * WARNING : mutations on this observer impact all subscribers immediately.
1536
- *
1537
- * @param {string} name
1538
- * @returns {ObservableItem|null}
1539
- */
1540
- get(name) {
1541
- const item = $stores.get(name);
1542
- if (!item) {
1543
- DebugManager.warn('Store', `Store.get('${name}') : store not found.`);
1544
- return null;
1545
- }
1546
- return item.observer;
1547
- },
1551
+ ObservableItem.prototype.cleanup = function() {
1552
+ if (this.$cleanupListeners) {
1553
+ for (let i = 0; i < this.$cleanupListeners.length; i++) {
1554
+ this.$cleanupListeners[i]();
1555
+ }
1556
+ this.$cleanupListeners = null;
1557
+ }
1558
+ MemoryManager.unregister(this.$memoryId);
1559
+ this.disconnectAll();
1560
+ delete this.$value;
1561
+ };
1548
1562
 
1549
- /**
1550
- * @param {string} name
1551
- * @returns {{ observer: ObservableItem, subscribers: Set } | null}
1552
- */
1553
- getWithSubscribers(name) {
1554
- return $stores.get(name) ?? null;
1555
- },
1563
+ /**
1564
+ *
1565
+ * @param {Function} callback
1566
+ * @returns {(function(): void)}
1567
+ */
1568
+ ObservableItem.prototype.subscribe = function(callback) {
1569
+ this.$listeners = this.$listeners ?? [];
1556
1570
 
1557
- /**
1558
- * Destroys a store : cleans up the observer, destroys all followers, and removes the entry.
1559
- *
1560
- * @param {string} name
1561
- */
1562
- delete(name) {
1563
- const item = $stores.get(name);
1564
- if (!item) {
1565
- DebugManager.warn('Store', `Store.delete('${name}') : store not found, nothing to delete.`);
1566
- return;
1567
- }
1568
- item.subscribers.forEach(follower => follower.destroy());
1569
- item.subscribers.clear();
1570
- item.observer.cleanup();
1571
- $stores.delete(name);
1572
- },
1573
- /**
1574
- * Creates an isolated store group with its own state namespace.
1575
- * Each group is a fully independent StoreFactory instance โ€”
1576
- * no key conflicts, no shared state with the parent store.
1577
- *
1578
- * @param {string | ((group: ReturnType<typeof StoreFactory>) => void)} name - Group name for debugging, or setup callback if no name is provided
1579
- * @param {((group: ReturnType<typeof StoreFactory>) => void)} [callback] - Setup function receiving the isolated store instance
1580
- * @returns {ReturnType<typeof StoreFactory>}
1581
- *
1582
- * @example
1583
- * // With name (recommended)
1584
- * const EventStore = Store.group('events', (group) => {
1585
- * group.create('catalog', []);
1586
- * group.create('filters', { category: null, date: null });
1587
- * group.createResettable('selected', null);
1588
- * group.createComposed('filtered', () => {
1589
- * const catalog = EventStore.get('catalog').val();
1590
- * const filters = EventStore.get('filters').val();
1591
- * return catalog.filter(event => {
1592
- * if (filters.category && event.category !== filters.category) return false;
1593
- * return true;
1594
- * });
1595
- * }, ['catalog', 'filters']);
1596
- * });
1597
- *
1598
- * // Without name
1599
- * const CartStore = Store.group((group) => {
1600
- * group.create('items', []);
1601
- * });
1602
- *
1603
- * // Usage
1604
- * EventStore.use('catalog'); // two-way follower
1605
- * EventStore.follow('filtered'); // read-only follower
1606
- * EventStore.get('filters'); // raw observable
1607
- *
1608
- * // Cross-group composed
1609
- * const OrderStore = Store.group('orders', (group) => {
1610
- * group.createComposed('summary', () => {
1611
- * const items = CartStore.get('items').val();
1612
- * const events = EventStore.get('catalog').val();
1613
- * return { items, events };
1614
- * }, [CartStore.get('items'), EventStore.get('catalog')]);
1615
- * });
1616
- */
1617
- group(name, callback) {
1618
- if (typeof name === 'function') {
1619
- callback = name;
1620
- name = 'anonymous';
1621
- }
1622
- const store = StoreFactory();
1623
- callback && callback(store);
1624
- return store;
1625
- },
1626
- createPersistent(name, value, localstorage_key) {
1627
- localstorage_key = localstorage_key || name;
1628
- const observer = this.create(name, $getFromStorage(localstorage_key, value));
1629
- const saver = $saveToStorage(value);
1630
-
1631
- observer.subscribe((val) => saver(localstorage_key, val));
1632
- return observer;
1633
- },
1634
- createPersistentResettable(name, value, localstorage_key) {
1635
- localstorage_key = localstorage_key || name;
1636
- const observer = this.createResettable(name, $getFromStorage(localstorage_key, value));
1637
- const saver = $saveToStorage(value);
1638
- observer.subscribe((val) => saver(localstorage_key, val));
1571
+ this.$listeners.push(callback);
1572
+ this.assocTrigger();
1573
+ };
1639
1574
 
1640
- const originalReset = observer.reset.bind(observer);
1641
- observer.reset = () => {
1642
- LocalStorage.remove(localstorage_key);
1643
- originalReset();
1644
- };
1575
+ /**
1576
+ * Watches for a specific value and executes callback when the observable equals that value.
1577
+ * Creates a watcher that only triggers when the observable changes to the specified value.
1578
+ *
1579
+ * @param {*} value - The value to watch for
1580
+ * @param {(value) => void|ObservableItem} callback - Callback function or observable to set when value matches
1581
+ * @example
1582
+ * const status = Observable('idle');
1583
+ * status.on('loading', () => console.log('Started loading'));
1584
+ * status.on('error', isError); // Set another observable
1585
+ */
1586
+ ObservableItem.prototype.on = function(value, callback) {
1587
+ this.$watchers = this.$watchers ?? new Map();
1645
1588
 
1646
- return observer;
1647
- }
1648
- };
1589
+ let watchValueList = this.$watchers.get(value);
1649
1590
 
1591
+ if(callback.__$isObservable) {
1592
+ callback = callback.set.bind(callback);
1593
+ }
1650
1594
 
1651
- return new Proxy($api, {
1652
- get(target, prop) {
1653
- if (typeof prop === 'symbol' || prop.startsWith('$') || prop in target) {
1654
- return target[prop];
1655
- }
1656
- if (target.has(prop)) {
1657
- if ($followersCache.has(prop)) {
1658
- return $followersCache.get(prop);
1659
- }
1660
- const follower = target.follow(prop);
1661
- $followersCache.set(prop, follower);
1662
- return follower;
1595
+ if(!watchValueList) {
1596
+ watchValueList = callback;
1597
+ this.$watchers.set(value, callback);
1598
+ } else if(!Validator.isArray(watchValueList.list)) {
1599
+ watchValueList = [watchValueList, callback];
1600
+ callback = (value) => {
1601
+ for(let i = 0, length = watchValueList.length; i < length; i++) {
1602
+ watchValueList[i](value);
1663
1603
  }
1664
- return undefined;
1665
- },
1666
- set(target, prop, value) {
1667
- DebugManager.error('Store', `Forbidden: You cannot overwrite the store key '${String(prop)}'. Use .use('${String(prop)}').set(value) instead.`);
1668
- throw new NativeDocumentError(`Store structure is immutable. Use .set() on the observable.`);
1669
- },
1670
- deleteProperty(target, prop) {
1671
- throw new NativeDocumentError(`Store keys cannot be deleted.`);
1672
- }
1673
- });
1604
+ };
1605
+ callback.list = watchValueList;
1606
+ this.$watchers.set(value, callback);
1607
+ } else {
1608
+ watchValueList.list.push(callback);
1609
+ }
1610
+
1611
+ this.assocTrigger();
1674
1612
  };
1675
1613
 
1676
- const Store = StoreFactory();
1614
+ /**
1615
+ * Removes a watcher for a specific value. If no callback is provided, removes all watchers for that value.
1616
+ *
1617
+ * @param {*} value - The value to stop watching
1618
+ * @param {Function} [callback] - Specific callback to remove. If omitted, removes all watchers for this value
1619
+ * @example
1620
+ * const status = Observable('idle');
1621
+ * const handler = () => console.log('Loading');
1622
+ * status.on('loading', handler);
1623
+ * status.off('loading', handler); // Remove specific handler
1624
+ * status.off('loading'); // Remove all handlers for 'loading'
1625
+ */
1626
+ ObservableItem.prototype.off = function(value, callback) {
1627
+ if(!this.$watchers) return;
1677
1628
 
1678
- Store.create('locale', navigator.language.split('-')[0] || 'en');
1629
+ const watchValueList = this.$watchers.get(value);
1630
+ if(!watchValueList) return;
1679
1631
 
1680
- const $parseDateParts = (value, locale) => {
1681
- const d = new Date(value);
1682
- return {
1683
- d,
1684
- parts: new Intl.DateTimeFormat(locale, {
1685
- year: 'numeric',
1686
- month: 'long',
1687
- day: '2-digit',
1688
- hour: '2-digit',
1689
- minute: '2-digit',
1690
- second: '2-digit',
1691
- }).formatToParts(d).reduce((acc, { type, value }) => {
1692
- acc[type] = value;
1693
- return acc;
1694
- }, {})
1632
+ if(!callback || !Array.isArray(watchValueList.list)) {
1633
+ this.$watchers?.delete(value);
1634
+ this.assocTrigger();
1635
+ return;
1636
+ }
1637
+ const index = watchValueList.indexOf(callback);
1638
+ watchValueList?.splice(index, 1);
1639
+ if(watchValueList.length === 1) {
1640
+ this.$watchers.set(value, watchValueList[0]);
1641
+ }
1642
+ else if(watchValueList.length === 0) {
1643
+ this.$watchers?.delete(value);
1644
+ }
1645
+ this.assocTrigger();
1646
+ };
1647
+
1648
+ /**
1649
+ * Subscribes to the observable but automatically unsubscribes after the first time the predicate matches.
1650
+ *
1651
+ * @param {(value) => Boolean|any} predicate - Value to match or function that returns true when condition is met
1652
+ * @param {(value) => void} callback - Callback to execute when predicate matches, receives the matched value
1653
+ * @example
1654
+ * const status = Observable('loading');
1655
+ * status.once('ready', (val) => console.log('Ready!'));
1656
+ * status.once(val => val === 'error', (val) => console.log('Error occurred'));
1657
+ */
1658
+ ObservableItem.prototype.once = function(predicate, callback) {
1659
+ const fn = typeof predicate === 'function' ? predicate : (v) => v === predicate;
1660
+
1661
+ const handler = (val) => {
1662
+ if (fn(val)) {
1663
+ this.unsubscribe(handler);
1664
+ callback(val);
1665
+ }
1695
1666
  };
1667
+ this.subscribe(handler);
1696
1668
  };
1697
1669
 
1698
- const $applyDatePattern = (pattern, d, parts) => {
1699
- const pad = n => String(n).padStart(2, '0');
1700
- return pattern
1701
- .replace('YYYY', parts.year)
1702
- .replace('YY', parts.year.slice(-2))
1703
- .replace('MMMM', parts.month)
1704
- .replace('MMM', parts.month.slice(0, 3))
1705
- .replace('MM', pad(d.getMonth() + 1))
1706
- .replace('DD', pad(d.getDate()))
1707
- .replace('D', d.getDate())
1708
- .replace('HH', parts.hour)
1709
- .replace('mm', parts.minute)
1710
- .replace('ss', parts.second);
1670
+ /**
1671
+ * Unsubscribe from an observable.
1672
+ * @param {Function} callback
1673
+ */
1674
+ ObservableItem.prototype.unsubscribe = function(callback) {
1675
+ if(!this.$listeners) return;
1676
+ const index = this.$listeners.indexOf(callback);
1677
+ if (index > -1) {
1678
+ this.$listeners.splice(index, 1);
1679
+ }
1680
+ this.assocTrigger();
1711
1681
  };
1712
1682
 
1713
- const Formatters = {
1714
- currency: (value, locale, { currency = 'XOF', notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
1715
- new Intl.NumberFormat(locale, {
1716
- style: 'currency',
1717
- currency,
1718
- notation,
1719
- minimumFractionDigits,
1720
- maximumFractionDigits
1721
- }).format(value),
1683
+ /**
1684
+ * Create an Observable checker instance
1685
+ * @param callback
1686
+ * @returns {ObservableChecker}
1687
+ */
1688
+ ObservableItem.prototype.check = function(callback) {
1689
+ return new ObservableChecker(this, callback)
1690
+ };
1722
1691
 
1723
- number: (value, locale, { notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
1724
- new Intl.NumberFormat(locale, {
1725
- notation,
1726
- minimumFractionDigits,
1727
- maximumFractionDigits
1728
- }).format(value),
1729
-
1730
- percent: (value, locale, { decimals = 1 } = {}) =>
1731
- new Intl.NumberFormat(locale, {
1732
- style: 'percent',
1733
- maximumFractionDigits: decimals
1734
- }).format(value),
1735
-
1736
- date: (value, locale, { format, dateStyle = 'long' } = {}) => {
1737
- if (format) {
1738
- const { d, parts } = $parseDateParts(value, locale);
1739
- return $applyDatePattern(format, d, parts);
1740
- }
1741
- return new Intl.DateTimeFormat(locale, { dateStyle }).format(new Date(value));
1742
- },
1743
-
1744
- time: (value, locale, { format, hour = '2-digit', minute = '2-digit', second } = {}) => {
1745
- if (format) {
1746
- const { d, parts } = $parseDateParts(value, locale);
1747
- return $applyDatePattern(format, d, parts);
1748
- }
1749
- return new Intl.DateTimeFormat(locale, { hour, minute, second }).format(new Date(value));
1750
- },
1751
-
1752
- datetime: (value, locale, { format, dateStyle = 'long', hour = '2-digit', minute = '2-digit', second } = {}) => {
1753
- if (format) {
1754
- const { d, parts } = $parseDateParts(value, locale);
1755
- return $applyDatePattern(format, d, parts);
1756
- }
1757
- return new Intl.DateTimeFormat(locale, { dateStyle, hour, minute, second }).format(new Date(value));
1758
- },
1759
-
1760
- relative: (value, locale, { unit = 'day', numeric = 'auto' } = {}) => {
1761
- const diff = Math.round((value - Date.now()) / (1000 * 60 * 60 * 24));
1762
- return new Intl.RelativeTimeFormat(locale, { numeric }).format(diff, unit);
1763
- },
1764
-
1765
- plural: (value, locale, { singular, plural } = {}) => {
1766
- const rule = new Intl.PluralRules(locale).select(value);
1767
- return `${value} ${rule === 'one' ? singular : plural}`;
1768
- },
1769
- };
1692
+ ObservableItem.prototype.transform = ObservableItem.prototype.check;
1693
+ ObservableItem.prototype.pluck = ObservableItem.prototype.check;
1694
+ ObservableItem.prototype.is = ObservableItem.prototype.check;
1695
+ ObservableItem.prototype.select = ObservableItem.prototype.check;
1770
1696
 
1771
1697
  /**
1698
+ * Gets a property value from the observable's current value.
1699
+ * If the property is an observable, returns its value.
1772
1700
  *
1773
- * @param {*} value
1774
- * @param {{ propagation: boolean, reset: boolean} | null} configs
1775
- * @class ObservableItem
1701
+ * @param {string|number} key - Property key to retrieve
1702
+ * @returns {*} The value of the property, unwrapped if it's an observable
1703
+ * @example
1704
+ * const user = Observable({ name: 'John', age: Observable(25) });
1705
+ * user.get('name'); // 'John'
1706
+ * user.get('age'); // 25 (unwrapped from observable)
1776
1707
  */
1777
- function ObservableItem(value, configs = null) {
1778
- value = Validator.isObservable(value) ? value.val() : value;
1779
-
1780
- this.$previousValue = null;
1781
- this.$currentValue = value;
1782
-
1783
- this.$firstListener = null;
1784
- this.$listeners = null;
1785
- this.$watchers = null;
1786
-
1787
- this.$memoryId = null;
1788
-
1789
- if(configs) {
1790
- this.configs = configs;
1791
- if(configs.reset) {
1792
- this.$initialValue = Validator.isObject(value) ? deepClone(value) : value;
1793
- }
1794
- }
1795
- }
1796
-
1797
- Object.defineProperty(ObservableItem.prototype, '$value', {
1798
- get() {
1799
- return this.$currentValue;
1800
- },
1801
- set(value) {
1802
- this.set(value);
1803
- },
1804
- configurable: true,
1805
- });
1806
-
1807
- ObservableItem.prototype.__$isObservable = true;
1808
- const noneTrigger = function() {};
1708
+ ObservableItem.prototype.get = function(key) {
1709
+ const item = this.$currentValue[key];
1710
+ return Validator.isObservable(item) ? item.val() : item;
1711
+ };
1809
1712
 
1810
1713
  /**
1811
- * Intercepts and transforms values before they are set on the observable.
1812
- * The interceptor can modify the value or return undefined to use the original value.
1714
+ * Creates an ObservableWhen that represents whether the observable equals a specific value.
1715
+ * Returns an object that can be subscribed to and will emit true/false.
1813
1716
  *
1814
- * @param {(value) => any} callback - Interceptor function that receives (newValue, currentValue) and returns the transformed value or undefined
1815
- * @returns {ObservableItem} The observable instance for chaining
1717
+ * @param {*} value - The value to compare against
1718
+ * @returns {ObservableWhen} An ObservableWhen instance that tracks when the observable equals the value
1816
1719
  * @example
1817
- * const count = Observable(0);
1818
- * count.intercept((newVal, oldVal) => Math.max(0, newVal)); // Prevent negative values
1720
+ * const status = Observable('idle');
1721
+ * const isLoading = status.when('loading');
1722
+ * isLoading.subscribe(active => console.log('Loading:', active));
1723
+ * status.set('loading'); // Logs: "Loading: true"
1819
1724
  */
1820
- ObservableItem.prototype.intercept = function(callback) {
1821
- this.$interceptor = callback;
1822
- this.set = this.$setWithInterceptor;
1823
- return this;
1824
- };
1825
-
1826
- ObservableItem.prototype.triggerFirstListener = function(operations) {
1827
- this.$firstListener(this.$currentValue, this.$previousValue, operations);
1828
- };
1829
-
1830
- ObservableItem.prototype.triggerListeners = function(operations) {
1831
- const $listeners = this.$listeners;
1832
- const $previousValue = this.$previousValue;
1833
- const $currentValue = this.$currentValue;
1834
-
1835
- for(let i = 0, length = $listeners.length; i < length; i++) {
1836
- $listeners[i]($currentValue, $previousValue, operations);
1837
- }
1725
+ ObservableItem.prototype.when = function(value) {
1726
+ return new ObservableWhen(this, value);
1838
1727
  };
1839
1728
 
1840
- ObservableItem.prototype.triggerWatchers = function(operations) {
1841
- const $watchers = this.$watchers;
1842
- const $previousValue = this.$previousValue;
1843
- const $currentValue = this.$currentValue;
1844
-
1845
- const $currentValueCallbacks = $watchers.get($currentValue);
1846
- const $previousValueCallbacks = $watchers.get($previousValue);
1847
- if($currentValueCallbacks) {
1848
- $currentValueCallbacks(true, $previousValue, operations);
1849
- }
1850
- if($previousValueCallbacks) {
1851
- $previousValueCallbacks(false, $currentValue, operations);
1729
+ /**
1730
+ * Compares the observable's current value with another value or observable.
1731
+ *
1732
+ * @param {*|ObservableItem} other - Value or observable to compare against
1733
+ * @returns {boolean} True if values are equal
1734
+ * @example
1735
+ * const a = Observable(5);
1736
+ * const b = Observable(5);
1737
+ * a.equals(5); // true
1738
+ * a.equals(b); // true
1739
+ * a.equals(10); // false
1740
+ */
1741
+ ObservableItem.prototype.equals = function(other) {
1742
+ if(Validator.isObservable(other)) {
1743
+ return this.$currentValue === other.$currentValue;
1852
1744
  }
1745
+ return this.$currentValue === other;
1853
1746
  };
1854
1747
 
1855
- ObservableItem.prototype.triggerAll = function(operations) {
1856
- this.triggerWatchers(operations);
1857
- this.triggerListeners(operations);
1858
- };
1859
-
1860
- ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations) {
1861
- this.triggerWatchers(operations);
1862
- this.triggerFirstListener(operations);
1748
+ /**
1749
+ * Converts the observable's current value to a boolean.
1750
+ *
1751
+ * @returns {boolean} The boolean representation of the current value
1752
+ * @example
1753
+ * const count = Observable(0);
1754
+ * count.toBool(); // false
1755
+ * count.set(5);
1756
+ * count.toBool(); // true
1757
+ */
1758
+ ObservableItem.prototype.toBool = function() {
1759
+ return !!this.$currentValue;
1863
1760
  };
1864
1761
 
1865
- ObservableItem.prototype.assocTrigger = function() {
1866
- this.$firstListener = null;
1867
- if(this.$watchers?.size && this.$listeners?.length) {
1868
- this.trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
1869
- return;
1870
- }
1871
- if(this.$listeners?.length) {
1872
- if(this.$listeners.length === 1) {
1873
- this.$firstListener = this.$listeners[0];
1874
- this.trigger = this.triggerFirstListener;
1875
- }
1876
- else {
1877
- this.trigger = this.triggerListeners;
1878
- }
1879
- return;
1880
- }
1881
- if(this.$watchers?.size) {
1882
- this.trigger = this.triggerWatchers;
1883
- return;
1884
- }
1885
- this.trigger = noneTrigger;
1762
+ /**
1763
+ * Toggles the boolean value of the observable (false becomes true, true becomes false).
1764
+ *
1765
+ * @example
1766
+ * const isOpen = Observable(false);
1767
+ * isOpen.toggle(); // Now true
1768
+ * isOpen.toggle(); // Now false
1769
+ */
1770
+ ObservableItem.prototype.toggle = function() {
1771
+ this.set(!this.$currentValue);
1886
1772
  };
1887
- ObservableItem.prototype.trigger = noneTrigger;
1888
1773
 
1889
- ObservableItem.prototype.$updateWithNewValue = function(newValue) {
1890
- newValue = newValue?.__$isObservable ? newValue.val() : newValue;
1891
- if(this.$currentValue === newValue) {
1774
+ /**
1775
+ * Resets the observable to its initial value.
1776
+ * Only works if the observable was created with { reset: true } config.
1777
+ *
1778
+ * @example
1779
+ * const count = Observable(0, { reset: true });
1780
+ * count.set(10);
1781
+ * count.reset(); // Back to 0
1782
+ */
1783
+ ObservableItem.prototype.reset = function() {
1784
+ if(!this.configs?.reset) {
1892
1785
  return;
1893
1786
  }
1894
- this.$previousValue = this.$currentValue;
1895
- this.$currentValue = newValue;
1896
- this.trigger();
1897
- this.$previousValue = null;
1787
+ const resetValue = (Validator.isObject(this.$initialValue))
1788
+ ? deepClone(this.$initialValue, (observable) => {
1789
+ observable.reset();
1790
+ })
1791
+ : this.$initialValue;
1792
+ this.set(resetValue);
1898
1793
  };
1899
1794
 
1900
1795
  /**
1901
- * @param {*} data
1796
+ * Returns a string representation of the observable's current value.
1797
+ *
1798
+ * @returns {string} String representation of the current value
1902
1799
  */
1903
- ObservableItem.prototype.$setWithInterceptor = function(data) {
1904
- let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
1905
- const result = this.$interceptor(newValue, this.$currentValue);
1906
-
1907
- if (result !== undefined) {
1908
- newValue = result;
1909
- }
1910
-
1911
- this.$updateWithNewValue(newValue);
1800
+ ObservableItem.prototype.toString = function() {
1801
+ return String(this.$currentValue);
1912
1802
  };
1913
1803
 
1914
1804
  /**
1915
- * @param {*} data
1805
+ * Returns the primitive value of the observable (its current value).
1806
+ * Called automatically in type coercion contexts.
1807
+ *
1808
+ * @returns {*} The current value of the observable
1916
1809
  */
1917
- ObservableItem.prototype.$basicSet = function(data) {
1918
- let newValue = (typeof data === 'function') ? data(this.$currentValue) : data;
1919
- this.$updateWithNewValue(newValue);
1920
- };
1921
-
1922
- ObservableItem.prototype.set = ObservableItem.prototype.$basicSet;
1923
-
1924
- ObservableItem.prototype.val = function() {
1810
+ ObservableItem.prototype.valueOf = function() {
1925
1811
  return this.$currentValue;
1926
1812
  };
1927
1813
 
1928
- ObservableItem.prototype.disconnectAll = function() {
1929
- this.$previousValue = null;
1930
- this.$currentValue = null;
1931
- this.$listeners = null;
1932
- this.$watchers = null;
1933
- this.trigger = noneTrigger;
1934
- };
1935
1814
 
1936
1815
  /**
1937
- * Registers a cleanup callback that will be executed when the observable is cleaned up.
1938
- * Useful for disposing resources, removing event listeners, or other cleanup tasks.
1816
+ * Creates a derived observable that formats the current value using Intl.
1817
+ * Automatically reacts to both value changes and locale changes (Store.__nd.locale).
1818
+ *
1819
+ * @param {string | Function} type - Format type or custom formatter function
1820
+ * @param {Object} [options={}] - Options passed to the formatter
1821
+ * @returns {ObservableItem<string>}
1939
1822
  *
1940
- * @param {Function} callback - Cleanup function to execute on observable disposal
1941
1823
  * @example
1942
- * const obs = Observable(0);
1943
- * obs.onCleanup(() => console.log('Cleaned up!'));
1944
- * obs.cleanup(); // Logs: "Cleaned up!"
1824
+ * // Currency
1825
+ * price.format('currency') // "15 000 FCFA"
1826
+ * price.format('currency', { currency: 'EUR' }) // "15 000,00 โ‚ฌ"
1827
+ * price.format('currency', { notation: 'compact' }) // "15 K FCFA"
1828
+ *
1829
+ * // Number
1830
+ * count.format('number') // "15 000"
1831
+ *
1832
+ * // Percent
1833
+ * rate.format('percent') // "15,0 %"
1834
+ * rate.format('percent', { decimals: 2 }) // "15,00 %"
1835
+ *
1836
+ * // Date
1837
+ * date.format('date') // "3 mars 2026"
1838
+ * date.format('date', { dateStyle: 'full' }) // "mardi 3 mars 2026"
1839
+ * date.format('date', { format: 'DD/MM/YYYY' }) // "03/03/2026"
1840
+ * date.format('date', { format: 'DD MMM YYYY' }) // "03 mar 2026"
1841
+ * date.format('date', { format: 'DD MMMM YYYY' }) // "03 mars 2026"
1842
+ *
1843
+ * // Time
1844
+ * date.format('time') // "20:30"
1845
+ * date.format('time', { second: '2-digit' }) // "20:30:00"
1846
+ * date.format('time', { format: 'HH:mm:ss' }) // "20:30:00"
1847
+ *
1848
+ * // Datetime
1849
+ * date.format('datetime') // "3 mars 2026, 20:30"
1850
+ * date.format('datetime', { dateStyle: 'full' }) // "mardi 3 mars 2026, 20:30"
1851
+ * date.format('datetime', { format: 'DD/MM/YYYY HH:mm' }) // "03/03/2026 20:30"
1852
+ *
1853
+ * // Relative
1854
+ * date.format('relative') // "dans 11 jours"
1855
+ * date.format('relative', { unit: 'month' }) // "dans 1 mois"
1856
+ *
1857
+ * // Plural
1858
+ * count.format('plural', { singular: 'billet', plural: 'billets' }) // "3 billets"
1859
+ *
1860
+ * // Custom formatter
1861
+ * price.format(value => `${value.toLocaleString()} FCFA`)
1862
+ *
1863
+ * // Reacts to locale changes automatically
1864
+ * Store.setLocale('en-US');
1945
1865
  */
1946
- ObservableItem.prototype.onCleanup = function(callback) {
1947
- this.$cleanupListeners = this.$cleanupListeners ?? [];
1948
- this.$cleanupListeners.push(callback);
1866
+ ObservableItem.prototype.format = function(type, options = {}) {
1867
+ const self = this;
1868
+
1869
+ if (typeof type === 'function') {
1870
+ return new ObservableChecker(self, type);
1871
+ }
1872
+
1873
+ const formatter = Formatters[type];
1874
+ const localeObservable = Formatters.locale;
1875
+
1876
+ return Observable.computed(() => formatter(self.val(), localeObservable.val(), options),
1877
+ [self, localeObservable]
1878
+ );
1949
1879
  };
1950
1880
 
1951
- ObservableItem.prototype.cleanup = function() {
1952
- if (this.$cleanupListeners) {
1953
- for (let i = 0; i < this.$cleanupListeners.length; i++) {
1954
- this.$cleanupListeners[i]();
1955
- }
1956
- this.$cleanupListeners = null;
1881
+ ObservableItem.prototype.persist = function(key, options = {}) {
1882
+ let value = $getFromStorage(key, this.$currentValue);
1883
+ if(options.get) {
1884
+ value = options.get(value);
1957
1885
  }
1958
- MemoryManager.unregister(this.$memoryId);
1959
- this.disconnectAll();
1960
- delete this.$value;
1886
+ this.set(value);
1887
+ const saver = $saveToStorage(this.$currentValue);
1888
+ this.subscribe((newValue) => {
1889
+ saver(key, options.set ? options.set(newValue) : newValue);
1890
+ });
1891
+ return this;
1961
1892
  };
1962
1893
 
1963
1894
  /**
1964
1895
  *
1965
- * @param {Function} callback
1966
- * @returns {(function(): void)}
1896
+ * @param {*} value
1897
+ * @param {{ propagation: boolean, reset: boolean} | null} configs
1898
+ * @returns {ObservableItem}
1899
+ * @constructor
1967
1900
  */
1968
- ObservableItem.prototype.subscribe = function(callback) {
1969
- this.$listeners = this.$listeners ?? [];
1901
+ function Observable(value, configs = null) {
1902
+ return new ObservableItem(value, configs);
1903
+ }
1970
1904
 
1971
- this.$listeners.push(callback);
1972
- this.assocTrigger();
1973
- };
1905
+ const $$1 = Observable;
1974
1906
 
1975
1907
  /**
1976
- * Watches for a specific value and executes callback when the observable equals that value.
1977
- * Creates a watcher that only triggers when the observable changes to the specified value.
1978
1908
  *
1979
- * @param {*} value - The value to watch for
1980
- * @param {(value) => void|ObservableItem} callback - Callback function or observable to set when value matches
1981
- * @example
1982
- * const status = Observable('idle');
1983
- * status.on('loading', () => console.log('Started loading'));
1984
- * status.on('error', isError); // Set another observable
1909
+ * @param {string} propertyName
1985
1910
  */
1986
- ObservableItem.prototype.on = function(value, callback) {
1987
- this.$watchers = this.$watchers ?? new Map();
1988
-
1989
- let watchValueList = this.$watchers.get(value);
1990
-
1991
- if(callback.__$isObservable) {
1992
- callback = callback.set.bind(callback);
1993
- }
1994
-
1995
- if(!watchValueList) {
1996
- watchValueList = callback;
1997
- this.$watchers.set(value, callback);
1998
- } else if(!Validator.isArray(watchValueList.list)) {
1999
- watchValueList = [watchValueList, callback];
2000
- callback = (value) => {
2001
- for(let i = 0, length = watchValueList.length; i < length; i++) {
2002
- watchValueList[i](value);
2003
- }
2004
- };
2005
- callback.list = watchValueList;
2006
- this.$watchers.set(value, callback);
2007
- } else {
2008
- watchValueList.list.push(callback);
2009
- }
2010
-
2011
- this.assocTrigger();
1911
+ Observable.useValueProperty = function(propertyName = 'value') {
1912
+ Object.defineProperty(ObservableItem.prototype, propertyName, {
1913
+ get() {
1914
+ return this.$currentValue;
1915
+ },
1916
+ set(value) {
1917
+ this.set(value);
1918
+ },
1919
+ configurable: true,
1920
+ });
2012
1921
  };
2013
1922
 
1923
+
2014
1924
  /**
2015
- * Removes a watcher for a specific value. If no callback is provided, removes all watchers for that value.
2016
1925
  *
2017
- * @param {*} value - The value to stop watching
2018
- * @param {Function} [callback] - Specific callback to remove. If omitted, removes all watchers for this value
2019
- * @example
2020
- * const status = Observable('idle');
2021
- * const handler = () => console.log('Loading');
2022
- * status.on('loading', handler);
2023
- * status.off('loading', handler); // Remove specific handler
2024
- * status.off('loading'); // Remove all handlers for 'loading'
1926
+ * @param id
1927
+ * @returns {ObservableItem|null}
2025
1928
  */
2026
- ObservableItem.prototype.off = function(value, callback) {
2027
- if(!this.$watchers) return;
2028
-
2029
- const watchValueList = this.$watchers.get(value);
2030
- if(!watchValueList) return;
2031
-
2032
- if(!callback || !Array.isArray(watchValueList.list)) {
2033
- this.$watchers?.delete(value);
2034
- this.assocTrigger();
2035
- return;
2036
- }
2037
- const index = watchValueList.indexOf(callback);
2038
- watchValueList?.splice(index, 1);
2039
- if(watchValueList.length === 1) {
2040
- this.$watchers.set(value, watchValueList[0]);
2041
- }
2042
- else if(watchValueList.length === 0) {
2043
- this.$watchers?.delete(value);
1929
+ Observable.getById = function(id) {
1930
+ const item = MemoryManager.getObservableById(parseInt(id));
1931
+ if(!item) {
1932
+ throw new NativeDocumentError('Observable.getById : No observable found with id ' + id);
2044
1933
  }
2045
- this.assocTrigger();
1934
+ return item;
2046
1935
  };
2047
1936
 
2048
1937
  /**
2049
- * Subscribes to the observable but automatically unsubscribes after the first time the predicate matches.
2050
1938
  *
2051
- * @param {(value) => Boolean|any} predicate - Value to match or function that returns true when condition is met
2052
- * @param {(value) => void} callback - Callback to execute when predicate matches, receives the matched value
2053
- * @example
2054
- * const status = Observable('loading');
2055
- * status.once('ready', (val) => console.log('Ready!'));
2056
- * status.once(val => val === 'error', (val) => console.log('Error occurred'));
1939
+ * @param {ObservableItem} observable
2057
1940
  */
2058
- ObservableItem.prototype.once = function(predicate, callback) {
2059
- const fn = typeof predicate === 'function' ? predicate : (v) => v === predicate;
2060
-
2061
- const handler = (val) => {
2062
- if (fn(val)) {
2063
- this.unsubscribe(handler);
2064
- callback(val);
2065
- }
2066
- };
2067
- this.subscribe(handler);
1941
+ Observable.cleanup = function(observable) {
1942
+ observable.cleanup();
2068
1943
  };
2069
1944
 
2070
1945
  /**
2071
- * Unsubscribe from an observable.
2072
- * @param {Function} callback
1946
+ * Enable auto cleanup of observables.
1947
+ * @param {Boolean} enable
1948
+ * @param {{interval:Boolean, threshold:number}} options
2073
1949
  */
2074
- ObservableItem.prototype.unsubscribe = function(callback) {
2075
- if(!this.$listeners) return;
2076
- const index = this.$listeners.indexOf(callback);
2077
- if (index > -1) {
2078
- this.$listeners.splice(index, 1);
1950
+ Observable.autoCleanup = function(enable = false, options = {}) {
1951
+ if(!enable) {
1952
+ return;
2079
1953
  }
2080
- this.assocTrigger();
2081
- };
2082
-
2083
- /**
2084
- * Create an Observable checker instance
2085
- * @param callback
2086
- * @returns {ObservableChecker}
2087
- */
2088
- ObservableItem.prototype.check = function(callback) {
2089
- return new ObservableChecker(this, callback)
2090
- };
1954
+ const { interval = 60000, threshold = 100 } = options;
2091
1955
 
2092
- ObservableItem.prototype.transform = ObservableItem.prototype.check;
2093
- ObservableItem.prototype.pluck = ObservableItem.prototype.check;
2094
- ObservableItem.prototype.is = ObservableItem.prototype.check;
2095
- ObservableItem.prototype.select = ObservableItem.prototype.check;
1956
+ window.addEventListener('beforeunload', () => {
1957
+ MemoryManager.cleanup();
1958
+ });
2096
1959
 
2097
- /**
2098
- * Gets a property value from the observable's current value.
2099
- * If the property is an observable, returns its value.
2100
- *
2101
- * @param {string|number} key - Property key to retrieve
2102
- * @returns {*} The value of the property, unwrapped if it's an observable
2103
- * @example
2104
- * const user = Observable({ name: 'John', age: Observable(25) });
2105
- * user.get('name'); // 'John'
2106
- * user.get('age'); // 25 (unwrapped from observable)
2107
- */
2108
- ObservableItem.prototype.get = function(key) {
2109
- const item = this.$currentValue[key];
2110
- return Validator.isObservable(item) ? item.val() : item;
2111
- };
2112
-
2113
- /**
2114
- * Creates an ObservableWhen that represents whether the observable equals a specific value.
2115
- * Returns an object that can be subscribed to and will emit true/false.
2116
- *
2117
- * @param {*} value - The value to compare against
2118
- * @returns {ObservableWhen} An ObservableWhen instance that tracks when the observable equals the value
2119
- * @example
2120
- * const status = Observable('idle');
2121
- * const isLoading = status.when('loading');
2122
- * isLoading.subscribe(active => console.log('Loading:', active));
2123
- * status.set('loading'); // Logs: "Loading: true"
2124
- */
2125
- ObservableItem.prototype.when = function(value) {
2126
- return new ObservableWhen(this, value);
2127
- };
2128
-
2129
- /**
2130
- * Compares the observable's current value with another value or observable.
2131
- *
2132
- * @param {*|ObservableItem} other - Value or observable to compare against
2133
- * @returns {boolean} True if values are equal
2134
- * @example
2135
- * const a = Observable(5);
2136
- * const b = Observable(5);
2137
- * a.equals(5); // true
2138
- * a.equals(b); // true
2139
- * a.equals(10); // false
2140
- */
2141
- ObservableItem.prototype.equals = function(other) {
2142
- if(Validator.isObservable(other)) {
2143
- return this.$currentValue === other.$currentValue;
2144
- }
2145
- return this.$currentValue === other;
2146
- };
2147
-
2148
- /**
2149
- * Converts the observable's current value to a boolean.
2150
- *
2151
- * @returns {boolean} The boolean representation of the current value
2152
- * @example
2153
- * const count = Observable(0);
2154
- * count.toBool(); // false
2155
- * count.set(5);
2156
- * count.toBool(); // true
2157
- */
2158
- ObservableItem.prototype.toBool = function() {
2159
- return !!this.$currentValue;
2160
- };
2161
-
2162
- /**
2163
- * Toggles the boolean value of the observable (false becomes true, true becomes false).
2164
- *
2165
- * @example
2166
- * const isOpen = Observable(false);
2167
- * isOpen.toggle(); // Now true
2168
- * isOpen.toggle(); // Now false
2169
- */
2170
- ObservableItem.prototype.toggle = function() {
2171
- this.set(!this.$currentValue);
2172
- };
2173
-
2174
- /**
2175
- * Resets the observable to its initial value.
2176
- * Only works if the observable was created with { reset: true } config.
2177
- *
2178
- * @example
2179
- * const count = Observable(0, { reset: true });
2180
- * count.set(10);
2181
- * count.reset(); // Back to 0
2182
- */
2183
- ObservableItem.prototype.reset = function() {
2184
- if(!this.configs?.reset) {
2185
- return;
2186
- }
2187
- const resetValue = (Validator.isObject(this.$initialValue))
2188
- ? deepClone(this.$initialValue, (observable) => {
2189
- observable.reset();
2190
- })
2191
- : this.$initialValue;
2192
- this.set(resetValue);
2193
- };
2194
-
2195
- /**
2196
- * Returns a string representation of the observable's current value.
2197
- *
2198
- * @returns {string} String representation of the current value
2199
- */
2200
- ObservableItem.prototype.toString = function() {
2201
- return String(this.$currentValue);
2202
- };
2203
-
2204
- /**
2205
- * Returns the primitive value of the observable (its current value).
2206
- * Called automatically in type coercion contexts.
2207
- *
2208
- * @returns {*} The current value of the observable
2209
- */
2210
- ObservableItem.prototype.valueOf = function() {
2211
- return this.$currentValue;
2212
- };
2213
-
2214
-
2215
- /**
2216
- * Creates a derived observable that formats the current value using Intl.
2217
- * Automatically reacts to both value changes and locale changes (Store.__nd.locale).
2218
- *
2219
- * @param {string | Function} type - Format type or custom formatter function
2220
- * @param {Object} [options={}] - Options passed to the formatter
2221
- * @returns {ObservableItem<string>}
2222
- *
2223
- * @example
2224
- * // Currency
2225
- * price.format('currency') // "15 000 FCFA"
2226
- * price.format('currency', { currency: 'EUR' }) // "15 000,00 โ‚ฌ"
2227
- * price.format('currency', { notation: 'compact' }) // "15 K FCFA"
2228
- *
2229
- * // Number
2230
- * count.format('number') // "15 000"
2231
- *
2232
- * // Percent
2233
- * rate.format('percent') // "15,0 %"
2234
- * rate.format('percent', { decimals: 2 }) // "15,00 %"
2235
- *
2236
- * // Date
2237
- * date.format('date') // "3 mars 2026"
2238
- * date.format('date', { dateStyle: 'full' }) // "mardi 3 mars 2026"
2239
- * date.format('date', { format: 'DD/MM/YYYY' }) // "03/03/2026"
2240
- * date.format('date', { format: 'DD MMM YYYY' }) // "03 mar 2026"
2241
- * date.format('date', { format: 'DD MMMM YYYY' }) // "03 mars 2026"
2242
- *
2243
- * // Time
2244
- * date.format('time') // "20:30"
2245
- * date.format('time', { second: '2-digit' }) // "20:30:00"
2246
- * date.format('time', { format: 'HH:mm:ss' }) // "20:30:00"
2247
- *
2248
- * // Datetime
2249
- * date.format('datetime') // "3 mars 2026, 20:30"
2250
- * date.format('datetime', { dateStyle: 'full' }) // "mardi 3 mars 2026, 20:30"
2251
- * date.format('datetime', { format: 'DD/MM/YYYY HH:mm' }) // "03/03/2026 20:30"
2252
- *
2253
- * // Relative
2254
- * date.format('relative') // "dans 11 jours"
2255
- * date.format('relative', { unit: 'month' }) // "dans 1 mois"
2256
- *
2257
- * // Plural
2258
- * count.format('plural', { singular: 'billet', plural: 'billets' }) // "3 billets"
2259
- *
2260
- * // Custom formatter
2261
- * price.format(value => `${value.toLocaleString()} FCFA`)
2262
- *
2263
- * // Reacts to locale changes automatically
2264
- * Store.setLocale('en-US');
2265
- */
2266
- ObservableItem.prototype.format = function(type, options = {}) {
2267
- const self = this;
2268
-
2269
- if (typeof type === 'function') {
2270
- return new ObservableChecker(self, type);
2271
- }
2272
-
2273
- const formatter = Formatters[type];
2274
- const localeObservable = Formatters.locale;
2275
-
2276
- return Observable.computed(() => formatter(self.val(), localeObservable.val(), options),
2277
- [self, localeObservable]
2278
- );
2279
- };
2280
-
2281
- ObservableItem.prototype.persist = function(key, options = {}) {
2282
- let value = $getFromStorage(key, this.$currentValue);
2283
- if(options.get) {
2284
- value = options.get(value);
2285
- }
2286
- this.set(value);
2287
- const saver = $saveToStorage(this.$currentValue);
2288
- this.subscribe((newValue) => {
2289
- saver(key, options.set ? options.set(newValue) : newValue);
2290
- });
2291
- return this;
2292
- };
2293
-
2294
- /**
2295
- *
2296
- * @param {*} value
2297
- * @param {{ propagation: boolean, reset: boolean} | null} configs
2298
- * @returns {ObservableItem}
2299
- * @constructor
2300
- */
2301
- function Observable(value, configs = null) {
2302
- return new ObservableItem(value, configs);
2303
- }
2304
-
2305
- const $$1 = Observable;
2306
-
2307
- /**
2308
- *
2309
- * @param {string} propertyName
2310
- */
2311
- Observable.useValueProperty = function(propertyName = 'value') {
2312
- Object.defineProperty(ObservableItem.prototype, propertyName, {
2313
- get() {
2314
- return this.$currentValue;
2315
- },
2316
- set(value) {
2317
- this.set(value);
2318
- },
2319
- configurable: true,
2320
- });
2321
- };
2322
-
2323
-
2324
- /**
2325
- *
2326
- * @param id
2327
- * @returns {ObservableItem|null}
2328
- */
2329
- Observable.getById = function(id) {
2330
- const item = MemoryManager.getObservableById(parseInt(id));
2331
- if(!item) {
2332
- throw new NativeDocumentError('Observable.getById : No observable found with id ' + id);
2333
- }
2334
- return item;
2335
- };
2336
-
2337
- /**
2338
- *
2339
- * @param {ObservableItem} observable
2340
- */
2341
- Observable.cleanup = function(observable) {
2342
- observable.cleanup();
2343
- };
2344
-
2345
- /**
2346
- * Enable auto cleanup of observables.
2347
- * @param {Boolean} enable
2348
- * @param {{interval:Boolean, threshold:number}} options
2349
- */
2350
- Observable.autoCleanup = function(enable = false, options = {}) {
2351
- if(!enable) {
2352
- return;
2353
- }
2354
- const { interval = 60000, threshold = 100 } = options;
2355
-
2356
- window.addEventListener('beforeunload', () => {
2357
- MemoryManager.cleanup();
2358
- });
2359
-
2360
- setInterval(() => MemoryManager.cleanObservables(threshold), interval);
1960
+ setInterval(() => MemoryManager.cleanObservables(threshold), interval);
2361
1961
  };
2362
1962
 
2363
1963
  /**
@@ -2384,7 +1984,6 @@ var NativeComponents = (function (exports) {
2384
1984
  }
2385
1985
  element.classes.toggle(className, value);
2386
1986
  }
2387
- data = null;
2388
1987
  };
2389
1988
 
2390
1989
  /**
@@ -2494,6 +2093,10 @@ var NativeComponents = (function (exports) {
2494
2093
  return ElementCreator.createStaticTextNode(null, this);
2495
2094
  };
2496
2095
 
2096
+ Number.prototype.toNdElement = function () {
2097
+ return ElementCreator.createStaticTextNode(null, this.toString());
2098
+ };
2099
+
2497
2100
  Element.prototype.toNdElement = function () {
2498
2101
  return this;
2499
2102
  };
@@ -2752,23 +2355,117 @@ var NativeComponents = (function (exports) {
2752
2355
  processStyleAttribute: bindStyleAttribute,
2753
2356
  };
2754
2357
 
2755
- function Anchor(name, isUniqueChild = false) {
2756
- const anchorFragment = document.createDocumentFragment();
2757
- anchorFragment.__Anchor__ = true;
2358
+ function AnchorWithSentinel(name) {
2359
+ const instance = Reflect.construct(DocumentFragment, [], AnchorWithSentinel);
2360
+ const sentinel = document.createComment((name || '') + ' Anchor Sentinel');
2361
+ const events = {};
2758
2362
 
2759
- const anchorStart = document.createComment('Anchor Start : '+name);
2760
- const anchorEnd = document.createComment('/ Anchor End '+name);
2363
+ instance.appendChild(sentinel);
2761
2364
 
2762
- anchorFragment.appendChild(anchorStart);
2763
- anchorFragment.appendChild(anchorEnd);
2365
+ const observer = new MutationObserver(() => {
2366
+ if (sentinel.parentNode !== instance && !(sentinel.parentNode instanceof DocumentFragment)) {
2367
+ events.connected && events.connected(sentinel.parentNode);
2368
+ }
2369
+ });
2370
+
2371
+ observer.observe(document, { childList: true, subtree: true });
2372
+
2373
+
2374
+ instance.$sentinel = sentinel;
2375
+ instance.$observer = observer;
2376
+ instance.$events = events;
2377
+
2378
+ return instance;
2379
+ }
2380
+
2381
+ AnchorWithSentinel.prototype = Object.create(DocumentFragment.prototype);
2382
+ AnchorWithSentinel.prototype.constructor = AnchorWithSentinel;
2383
+
2384
+ AnchorWithSentinel.prototype.onConnected = function(callback) {
2385
+ this.$events.connected = callback;
2386
+ return this;
2387
+ };
2388
+
2389
+ AnchorWithSentinel.prototype.onConnectedOnce = function(callback) {
2390
+ this.$events.connected = (parent) => {
2391
+ callback(parent);
2392
+ this.$observer.disconnect();
2393
+ this.$events.connectedOnce = null;
2394
+ };
2395
+ };
2396
+
2397
+ function oneChildAnchorOverwriting(anchor, parent) {
2398
+
2399
+ anchor.remove = () => {
2400
+ anchor.append.apply(anchor, parent.childNodes);
2401
+ };
2402
+
2403
+ anchor.appendChild = (child) => {
2404
+ child = Validator.isElement(child) ? child : ElementCreator.getChild(child);
2405
+ parent.appendChild(child);
2406
+ };
2407
+
2408
+ anchor.appendElement = anchor.appendChild;
2409
+
2410
+ anchor.removeChildren = () => {
2411
+ parent.replaceChildren();
2412
+ };
2413
+
2414
+ anchor.replaceContent = function(content) {
2415
+ const child = Validator.isElement(content) ? content : ElementCreator.getChild(content);
2416
+ parent.replaceChildren(child);
2417
+ };
2418
+ anchor.setContent = anchor.replaceContent;
2419
+
2420
+ anchor.insertBefore = (child, anchor) => {
2421
+ child = Validator.isElement(child) ? child : ElementCreator.getChild(child);
2422
+ parent.insertBefore(child, anchor);
2423
+ };
2424
+ anchor.appendChildBefore = anchor.insertBefore;
2425
+
2426
+ anchor.clear = anchor.remove;
2427
+ anchor.detach = anchor.remove;
2428
+
2429
+ anchor.replaceChildren = function() {
2430
+ parent.replaceChildren(...arguments);
2431
+ };
2432
+
2433
+ anchor.getByIndex = (index) => {
2434
+ return parent.childNodes[index];
2435
+ };
2436
+ }
2437
+
2438
+ function Anchor(name, isUniqueChild = false) {
2439
+ const anchorFragment = new AnchorWithSentinel(name);
2440
+
2441
+ /**
2442
+ * State :
2443
+ * 1. Not injected in the DOM
2444
+ * 2. Injected in the DOM and should be the only child of parent
2445
+ * 3. Injected in the DOM and the parent may have other children
2446
+ */
2447
+
2448
+ anchorFragment.onConnectedOnce((parent) => {
2449
+ if(isUniqueChild) {
2450
+ console.log('Lets overwrite some functions with parent ', parent);
2451
+ oneChildAnchorOverwriting(anchorFragment, parent);
2452
+ }
2453
+ });
2454
+
2455
+ anchorFragment.__Anchor__ = true;
2456
+
2457
+ const anchorStart = document.createComment('Anchor Start : '+name);
2458
+ const anchorEnd = document.createComment('/ Anchor End '+name);
2459
+
2460
+ anchorFragment.appendChild(anchorStart);
2461
+ anchorFragment.appendChild(anchorEnd);
2764
2462
 
2765
2463
  anchorFragment.nativeInsertBefore = anchorFragment.insertBefore;
2766
2464
  anchorFragment.nativeAppendChild = anchorFragment.appendChild;
2767
2465
  anchorFragment.nativeAppend = anchorFragment.append;
2768
2466
 
2769
2467
  const isParentUniqueChild = isUniqueChild
2770
- ? () => true
2771
- : (parent) => (parent.firstChild === anchorStart && parent.lastChild === anchorEnd);
2468
+ ? () => true: (parent) => (parent.firstChild === anchorStart && parent.lastChild === anchorEnd);
2772
2469
 
2773
2470
  const insertBefore = function(parent, child, target) {
2774
2471
  const childElement = Validator.isElement(child) ? child : ElementCreator.getChild(child);
@@ -2783,14 +2480,13 @@ var NativeComponents = (function (exports) {
2783
2480
  parent.insertBefore(childElement, target);
2784
2481
  };
2785
2482
 
2786
- anchorFragment.appendElement = function(child, before = null) {
2483
+ anchorFragment.appendElement = function(child) {
2787
2484
  const parentNode = anchorStart.parentNode;
2788
- const targetBefore = before || anchorEnd;
2789
2485
  if(parentNode === anchorFragment) {
2790
- parentNode.nativeInsertBefore(child, targetBefore);
2486
+ parentNode.nativeInsertBefore(child, anchorEnd);
2791
2487
  return;
2792
2488
  }
2793
- parentNode?.insertBefore(child, targetBefore);
2489
+ parentNode.insertBefore(child, anchorEnd);
2794
2490
  };
2795
2491
 
2796
2492
  anchorFragment.appendChild = function(child, before = null) {
@@ -2803,8 +2499,8 @@ var NativeComponents = (function (exports) {
2803
2499
  insertBefore(parent, child, before);
2804
2500
  };
2805
2501
 
2806
- anchorFragment.append = function(...args ) {
2807
- return anchorFragment.appendChild(args);
2502
+ anchorFragment.append = function() {
2503
+ return anchorFragment.appendChild(Array.from(arguments));
2808
2504
  };
2809
2505
 
2810
2506
  anchorFragment.removeChildren = function() {
@@ -2831,6 +2527,7 @@ var NativeComponents = (function (exports) {
2831
2527
  return;
2832
2528
  }
2833
2529
  if(isParentUniqueChild(parent)) {
2530
+ anchorFragment.append.apply(anchorFragment, parent.childNodes);
2834
2531
  parent.replaceChildren(anchorStart, anchorEnd);
2835
2532
  return;
2836
2533
  }
@@ -2875,9 +2572,11 @@ var NativeComponents = (function (exports) {
2875
2572
  anchorFragment.startElement = function() {
2876
2573
  return anchorStart;
2877
2574
  };
2575
+
2878
2576
  anchorFragment.restore = function() {
2879
2577
  anchorFragment.appendChild(anchorFragment);
2880
2578
  };
2579
+
2881
2580
  anchorFragment.clear = anchorFragment.remove;
2882
2581
  anchorFragment.detach = anchorFragment.remove;
2883
2582
 
@@ -3205,9 +2904,10 @@ var NativeComponents = (function (exports) {
3205
2904
  * @returns {Text}
3206
2905
  */
3207
2906
  const createTextNode = (value) => {
3208
- return (Validator.isObservable(value))
3209
- ? ElementCreator.createObservableNode(null, value)
3210
- : ElementCreator.createStaticTextNode(null, value);
2907
+ if(value) {
2908
+ return value.toNdElement();
2909
+ }
2910
+ return ElementCreator.createTextNode();
3211
2911
  };
3212
2912
 
3213
2913
 
@@ -3298,29 +2998,72 @@ var NativeComponents = (function (exports) {
3298
2998
  }
3299
2999
  const steps = [];
3300
3000
  if(this.$ndMethods) {
3301
- steps.push((clonedNode, data) => {
3302
- for(const methodName in this.$ndMethods) {
3303
- clonedNode.nd[methodName](this.$ndMethods[methodName].bind(clonedNode, ...data));
3304
- }
3305
- });
3001
+ const methods = Object.keys(this.$ndMethods);
3002
+ if(methods.length === 1) {
3003
+ const methodName = methods[0];
3004
+ const callback = this.$ndMethods[methodName];
3005
+ steps.push((clonedNode, data) => {
3006
+ clonedNode.nd[methodName](callback.bind(clonedNode, ...data));
3007
+ });
3008
+ } else {
3009
+ steps.push((clonedNode, data) => {
3010
+ const nd = clonedNode.nd;
3011
+ for(const methodName in this.$ndMethods) {
3012
+ nd[methodName](this.$ndMethods[methodName].bind(clonedNode, ...data));
3013
+ }
3014
+ });
3015
+ }
3306
3016
  }
3307
3017
  if(this.$classes) {
3308
3018
  const cache = {};
3309
- steps.push((clonedNode, data) => {
3310
- ElementCreator.processClassAttribute(clonedNode, buildProperties(cache, this.$classes, data));
3311
- });
3019
+ const keys = Object.keys(this.$classes);
3020
+
3021
+ if(keys.length === 1) {
3022
+ const key = keys[0];
3023
+ const callback = this.$classes[key];
3024
+ steps.push((clonedNode, data) => {
3025
+ cache[key] = callback.apply(null, data);
3026
+ ElementCreator.processClassAttribute(clonedNode, cache);
3027
+ });
3028
+ } else {
3029
+ steps.push((clonedNode, data) => {
3030
+ ElementCreator.processClassAttribute(clonedNode, buildProperties(cache, this.$classes, data));
3031
+ });
3032
+ }
3312
3033
  }
3313
3034
  if(this.$styles) {
3314
3035
  const cache = {};
3315
- steps.push((clonedNode, data) => {
3316
- ElementCreator.processStyleAttribute(clonedNode, buildProperties(cache, this.$styles, data));
3317
- });
3036
+ const keys = Object.keys(this.$styles);
3037
+
3038
+ if(keys.length === 1) {
3039
+ const key = keys[0];
3040
+ const callback = this.$styles[key];
3041
+ steps.push((clonedNode, data) => {
3042
+ cache[key] = callback.apply(null, data);
3043
+ ElementCreator.processStyleAttribute(clonedNode, cache);
3044
+ });
3045
+ } else {
3046
+ steps.push((clonedNode, data) => {
3047
+ ElementCreator.processStyleAttribute(clonedNode, buildProperties(cache, this.$styles, data));
3048
+ });
3049
+ }
3318
3050
  }
3319
3051
  if(this.$attrs) {
3320
3052
  const cache = {};
3321
- steps.push((clonedNode, data) => {
3322
- ElementCreator.processAttributes(clonedNode, buildProperties(cache, this.$attrs, data));
3323
- });
3053
+ const keys = Object.keys(this.$attrs);
3054
+
3055
+ if(keys.length === 1) {
3056
+ const key = keys[0];
3057
+ const callback = this.$attrs[key];
3058
+ steps.push((clonedNode, data) => {
3059
+ cache[key] = callback.apply(null, data);
3060
+ ElementCreator.processAttributes(clonedNode, cache);
3061
+ });
3062
+ } else {
3063
+ steps.push((clonedNode, data) => {
3064
+ ElementCreator.processAttributes(clonedNode, buildProperties(cache, this.$attrs, data));
3065
+ });
3066
+ }
3324
3067
  }
3325
3068
 
3326
3069
  const stepsCount = steps.length;
@@ -4135,6 +3878,406 @@ var NativeComponents = (function (exports) {
4135
3878
  return observable;
4136
3879
  };
4137
3880
 
3881
+ const StoreFactory = function() {
3882
+
3883
+ const $stores = new Map();
3884
+ const $followersCache = new Map();
3885
+
3886
+ /**
3887
+ * Internal helper โ€” retrieves a store entry or throws if not found.
3888
+ */
3889
+ const $getStoreOrThrow = (method, name) => {
3890
+ const item = $stores.get(name);
3891
+ if (!item) {
3892
+ DebugManager$1.error('Store', `Store.${method}('${name}') : store not found. Did you call Store.create('${name}') first?`);
3893
+ throw new NativeDocumentError(
3894
+ `Store.${method}('${name}') : store not found.`
3895
+ );
3896
+ }
3897
+ return item;
3898
+ };
3899
+
3900
+ /**
3901
+ * Internal helper โ€” blocks write operations on a read-only observer.
3902
+ */
3903
+ const $applyReadOnly = (observer, name, context) => {
3904
+ const readOnlyError = (method) => () => {
3905
+ DebugManager$1.error('Store', `Store.${context}('${name}') is read-only. '${method}()' is not allowed.`);
3906
+ throw new NativeDocumentError(
3907
+ `Store.${context}('${name}') is read-only.`
3908
+ );
3909
+ };
3910
+ observer.set = readOnlyError('set');
3911
+ observer.toggle = readOnlyError('toggle');
3912
+ observer.reset = readOnlyError('reset');
3913
+ };
3914
+
3915
+ const $createObservable = (value, options = {}) => {
3916
+ if(Array.isArray(value)) {
3917
+ return Observable.array(value, options);
3918
+ }
3919
+ if(typeof value === 'object') {
3920
+ return Observable.object(value, options);
3921
+ }
3922
+ return Observable(value, options);
3923
+ };
3924
+
3925
+ const $api = {
3926
+ /**
3927
+ * Create a new state and return the observer.
3928
+ * Throws if a store with the same name already exists.
3929
+ *
3930
+ * @param {string} name
3931
+ * @param {*} value
3932
+ * @returns {ObservableItem}
3933
+ */
3934
+ create(name, value) {
3935
+ if ($stores.has(name)) {
3936
+ DebugManager$1.warn('Store', `Store.create('${name}') : a store with this name already exists. Use Store.get('${name}') to retrieve it.`);
3937
+ throw new NativeDocumentError(
3938
+ `Store.create('${name}') : a store with this name already exists.`
3939
+ );
3940
+ }
3941
+ const observer = $createObservable(value);
3942
+ $stores.set(name, { observer, subscribers: new Set(), resettable: false, composed: false });
3943
+ return observer;
3944
+ },
3945
+
3946
+ /**
3947
+ * Create a new resettable state and return the observer.
3948
+ * The store can be reset to its initial value via Store.reset(name).
3949
+ * Throws if a store with the same name already exists.
3950
+ *
3951
+ * @param {string} name
3952
+ * @param {*} value
3953
+ * @returns {ObservableItem}
3954
+ */
3955
+ createResettable(name, value) {
3956
+ if ($stores.has(name)) {
3957
+ DebugManager$1.warn('Store', `Store.createResettable('${name}') : a store with this name already exists.`);
3958
+ throw new NativeDocumentError(
3959
+ `Store.createResettable('${name}') : a store with this name already exists.`
3960
+ );
3961
+ }
3962
+ const observer = $createObservable(value, { reset: true });
3963
+ $stores.set(name, { observer, subscribers: new Set(), resettable: true, composed: false });
3964
+ return observer;
3965
+ },
3966
+
3967
+ /**
3968
+ * Create a computed store derived from other stores.
3969
+ * The value is automatically recalculated when any dependency changes.
3970
+ * This store is read-only โ€” Store.use() and Store.set() will throw.
3971
+ * Throws if a store with the same name already exists.
3972
+ *
3973
+ * @param {string} name
3974
+ * @param {() => *} computation - Function that returns the computed value
3975
+ * @param {string[]} dependencies - Names of the stores to watch
3976
+ * @returns {ObservableItem}
3977
+ *
3978
+ * @example
3979
+ * Store.create('products', [{ id: 1, price: 10 }]);
3980
+ * Store.create('cart', [{ productId: 1, quantity: 2 }]);
3981
+ *
3982
+ * Store.createComposed('total', () => {
3983
+ * const products = Store.get('products').val();
3984
+ * const cart = Store.get('cart').val();
3985
+ * return cart.reduce((sum, item) => {
3986
+ * const product = products.find(p => p.id === item.productId);
3987
+ * return sum + (product.price * item.quantity);
3988
+ * }, 0);
3989
+ * }, ['products', 'cart']);
3990
+ */
3991
+ createComposed(name, computation, dependencies) {
3992
+ if ($stores.has(name)) {
3993
+ DebugManager$1.warn('Store', `Store.createComposed('${name}') : a store with this name already exists.`);
3994
+ throw new NativeDocumentError(
3995
+ `Store.createComposed('${name}') : a store with this name already exists.`
3996
+ );
3997
+ }
3998
+ if (typeof computation !== 'function') {
3999
+ throw new NativeDocumentError(
4000
+ `Store.createComposed('${name}') : computation must be a function.`
4001
+ );
4002
+ }
4003
+ if (!Array.isArray(dependencies) || dependencies.length === 0) {
4004
+ throw new NativeDocumentError(
4005
+ `Store.createComposed('${name}') : dependencies must be a non-empty array of store names.`
4006
+ );
4007
+ }
4008
+
4009
+ // Resolve dependency observers
4010
+ const depObservers = dependencies.map(depName => {
4011
+ if(typeof depName !== 'string') {
4012
+ return depName;
4013
+ }
4014
+ const depItem = $stores.get(depName);
4015
+ if (!depItem) {
4016
+ DebugManager$1.error('Store', `Store.createComposed('${name}') : dependency '${depName}' not found. Create it first.`);
4017
+ throw new NativeDocumentError(
4018
+ `Store.createComposed('${name}') : dependency store '${depName}' not found.`
4019
+ );
4020
+ }
4021
+ return depItem.observer;
4022
+ });
4023
+
4024
+ // Create computed observable from dependency observers
4025
+ const observer = Observable.computed(computation, depObservers);
4026
+
4027
+ $stores.set(name, { observer, subscribers: new Set(), resettable: false, composed: true });
4028
+ return observer;
4029
+ },
4030
+
4031
+ /**
4032
+ * Returns true if a store with the given name exists.
4033
+ *
4034
+ * @param {string} name
4035
+ * @returns {boolean}
4036
+ */
4037
+ has(name) {
4038
+ return $stores.has(name);
4039
+ },
4040
+
4041
+ /**
4042
+ * Resets a resettable store to its initial value and notifies all subscribers.
4043
+ * Throws if the store was not created with createResettable().
4044
+ *
4045
+ * @param {string} name
4046
+ */
4047
+ reset(name) {
4048
+ const item = $getStoreOrThrow('reset', name);
4049
+ if (item.composed) {
4050
+ DebugManager$1.error('Store', `Store.reset('${name}') : composed stores cannot be reset. Their value is derived from dependencies.`);
4051
+ throw new NativeDocumentError(
4052
+ `Store.reset('${name}') : composed stores cannot be reset.`
4053
+ );
4054
+ }
4055
+ if (!item.resettable) {
4056
+ DebugManager$1.error('Store', `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`);
4057
+ throw new NativeDocumentError(
4058
+ `Store.reset('${name}') : this store is not resettable. Use Store.createResettable('${name}', value) instead of Store.create().`
4059
+ );
4060
+ }
4061
+ item.observer.reset();
4062
+ },
4063
+
4064
+ /**
4065
+ * Returns a two-way synchronized follower of the store.
4066
+ * Writing to the follower propagates the value back to the store and all its subscribers.
4067
+ * Throws if called on a composed store โ€” use Store.follow() instead.
4068
+ * Call follower.destroy() or follower.dispose() to unsubscribe.
4069
+ *
4070
+ * @param {string} name
4071
+ * @returns {ObservableItem}
4072
+ */
4073
+ use(name) {
4074
+ const item = $getStoreOrThrow('use', name);
4075
+
4076
+ if (item.composed) {
4077
+ DebugManager$1.error('Store', `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`);
4078
+ throw new NativeDocumentError(
4079
+ `Store.use('${name}') : composed stores are read-only. Use Store.follow('${name}') instead.`
4080
+ );
4081
+ }
4082
+
4083
+ const { observer: originalObserver, subscribers } = item;
4084
+ const observerFollower = $createObservable(originalObserver.val());
4085
+
4086
+ const onStoreChange = value => observerFollower.set(value);
4087
+ const onFollowerChange = value => originalObserver.set(value);
4088
+
4089
+ originalObserver.subscribe(onStoreChange);
4090
+ observerFollower.subscribe(onFollowerChange);
4091
+
4092
+ observerFollower.destroy = () => {
4093
+ originalObserver.unsubscribe(onStoreChange);
4094
+ observerFollower.unsubscribe(onFollowerChange);
4095
+ subscribers.delete(observerFollower);
4096
+ observerFollower.cleanup();
4097
+ };
4098
+ observerFollower.dispose = observerFollower.destroy;
4099
+
4100
+ subscribers.add(observerFollower);
4101
+ return observerFollower;
4102
+ },
4103
+
4104
+ /**
4105
+ * Returns a read-only follower of the store.
4106
+ * The follower reflects store changes but cannot write back to the store.
4107
+ * Any attempt to call .set(), .toggle() or .reset() will throw.
4108
+ * Call follower.destroy() or follower.dispose() to unsubscribe.
4109
+ *
4110
+ * @param {string} name
4111
+ * @returns {ObservableItem}
4112
+ */
4113
+ follow(name) {
4114
+ const { observer: originalObserver, subscribers } = $getStoreOrThrow('follow', name);
4115
+ const observerFollower = $createObservable(originalObserver.val());
4116
+
4117
+ const onStoreChange = value => observerFollower.set(value);
4118
+ originalObserver.subscribe(onStoreChange);
4119
+
4120
+ $applyReadOnly(observerFollower, name, 'follow');
4121
+
4122
+ observerFollower.destroy = () => {
4123
+ originalObserver.unsubscribe(onStoreChange);
4124
+ subscribers.delete(observerFollower);
4125
+ observerFollower.cleanup();
4126
+ };
4127
+ observerFollower.dispose = observerFollower.destroy;
4128
+
4129
+ subscribers.add(observerFollower);
4130
+ return observerFollower;
4131
+ },
4132
+
4133
+ /**
4134
+ * Returns the raw store observer directly (no follower, no cleanup contract).
4135
+ * Use this for direct read access when you don't need to unsubscribe.
4136
+ * WARNING : mutations on this observer impact all subscribers immediately.
4137
+ *
4138
+ * @param {string} name
4139
+ * @returns {ObservableItem|null}
4140
+ */
4141
+ get(name) {
4142
+ const item = $stores.get(name);
4143
+ if (!item) {
4144
+ DebugManager$1.warn('Store', `Store.get('${name}') : store not found.`);
4145
+ return null;
4146
+ }
4147
+ return item.observer;
4148
+ },
4149
+
4150
+ /**
4151
+ * @param {string} name
4152
+ * @returns {{ observer: ObservableItem, subscribers: Set } | null}
4153
+ */
4154
+ getWithSubscribers(name) {
4155
+ return $stores.get(name) ?? null;
4156
+ },
4157
+
4158
+ /**
4159
+ * Destroys a store : cleans up the observer, destroys all followers, and removes the entry.
4160
+ *
4161
+ * @param {string} name
4162
+ */
4163
+ delete(name) {
4164
+ const item = $stores.get(name);
4165
+ if (!item) {
4166
+ DebugManager$1.warn('Store', `Store.delete('${name}') : store not found, nothing to delete.`);
4167
+ return;
4168
+ }
4169
+ item.subscribers.forEach(follower => follower.destroy());
4170
+ item.subscribers.clear();
4171
+ item.observer.cleanup();
4172
+ $stores.delete(name);
4173
+ },
4174
+ /**
4175
+ * Creates an isolated store group with its own state namespace.
4176
+ * Each group is a fully independent StoreFactory instance โ€”
4177
+ * no key conflicts, no shared state with the parent store.
4178
+ *
4179
+ * @param {string | ((group: ReturnType<typeof StoreFactory>) => void)} name - Group name for debugging, or setup callback if no name is provided
4180
+ * @param {((group: ReturnType<typeof StoreFactory>) => void)} [callback] - Setup function receiving the isolated store instance
4181
+ * @returns {ReturnType<typeof StoreFactory>}
4182
+ *
4183
+ * @example
4184
+ * // With name (recommended)
4185
+ * const EventStore = Store.group('events', (group) => {
4186
+ * group.create('catalog', []);
4187
+ * group.create('filters', { category: null, date: null });
4188
+ * group.createResettable('selected', null);
4189
+ * group.createComposed('filtered', () => {
4190
+ * const catalog = EventStore.get('catalog').val();
4191
+ * const filters = EventStore.get('filters').val();
4192
+ * return catalog.filter(event => {
4193
+ * if (filters.category && event.category !== filters.category) return false;
4194
+ * return true;
4195
+ * });
4196
+ * }, ['catalog', 'filters']);
4197
+ * });
4198
+ *
4199
+ * // Without name
4200
+ * const CartStore = Store.group((group) => {
4201
+ * group.create('items', []);
4202
+ * });
4203
+ *
4204
+ * // Usage
4205
+ * EventStore.use('catalog'); // two-way follower
4206
+ * EventStore.follow('filtered'); // read-only follower
4207
+ * EventStore.get('filters'); // raw observable
4208
+ *
4209
+ * // Cross-group composed
4210
+ * const OrderStore = Store.group('orders', (group) => {
4211
+ * group.createComposed('summary', () => {
4212
+ * const items = CartStore.get('items').val();
4213
+ * const events = EventStore.get('catalog').val();
4214
+ * return { items, events };
4215
+ * }, [CartStore.get('items'), EventStore.get('catalog')]);
4216
+ * });
4217
+ */
4218
+ group(name, callback) {
4219
+ if (typeof name === 'function') {
4220
+ callback = name;
4221
+ name = 'anonymous';
4222
+ }
4223
+ const store = StoreFactory();
4224
+ callback && callback(store);
4225
+ return store;
4226
+ },
4227
+ createPersistent(name, value, localstorage_key) {
4228
+ localstorage_key = localstorage_key || name;
4229
+ const observer = this.create(name, $getFromStorage(localstorage_key, value));
4230
+ const saver = $saveToStorage(value);
4231
+
4232
+ observer.subscribe((val) => saver(localstorage_key, val));
4233
+ return observer;
4234
+ },
4235
+ createPersistentResettable(name, value, localstorage_key) {
4236
+ localstorage_key = localstorage_key || name;
4237
+ const observer = this.createResettable(name, $getFromStorage(localstorage_key, value));
4238
+ const saver = $saveToStorage(value);
4239
+ observer.subscribe((val) => saver(localstorage_key, val));
4240
+
4241
+ const originalReset = observer.reset.bind(observer);
4242
+ observer.reset = () => {
4243
+ LocalStorage.remove(localstorage_key);
4244
+ originalReset();
4245
+ };
4246
+
4247
+ return observer;
4248
+ }
4249
+ };
4250
+
4251
+
4252
+ return new Proxy($api, {
4253
+ get(target, prop) {
4254
+ if (typeof prop === 'symbol' || prop.startsWith('$') || prop in target) {
4255
+ return target[prop];
4256
+ }
4257
+ if (target.has(prop)) {
4258
+ if ($followersCache.has(prop)) {
4259
+ return $followersCache.get(prop);
4260
+ }
4261
+ const follower = target.follow(prop);
4262
+ $followersCache.set(prop, follower);
4263
+ return follower;
4264
+ }
4265
+ return undefined;
4266
+ },
4267
+ set(target, prop, value) {
4268
+ DebugManager$1.error('Store', `Forbidden: You cannot overwrite the store key '${String(prop)}'. Use .use('${String(prop)}').set(value) instead.`);
4269
+ throw new NativeDocumentError(`Store structure is immutable. Use .set() on the observable.`);
4270
+ },
4271
+ deleteProperty(target, prop) {
4272
+ throw new NativeDocumentError(`Store keys cannot be deleted.`);
4273
+ }
4274
+ });
4275
+ };
4276
+
4277
+ const Store = StoreFactory();
4278
+
4279
+ Store.create('locale', navigator.language.split('-')[0] || 'en');
4280
+
4138
4281
  /**
4139
4282
  * Creates a `<button>` element.
4140
4283
  * @type {function(ButtonAttributes=, NdChild|NdChild[]=): HTMLButtonElement}