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.
- package/dist/native-document.components.min.js +1154 -1011
- package/dist/native-document.dev.js +1322 -1179
- package/dist/native-document.dev.js.map +1 -1
- package/dist/native-document.min.js +1 -1
- package/docs/lifecycle-events.md +1 -1
- package/elements.js +2 -2
- package/package.json +1 -1
- package/src/core/data/ObservableItem.js +1 -2
- package/src/core/elements/anchor/anchor-with-sentinel.js +41 -0
- package/src/core/elements/{anchor.js โ anchor/anchor.js} +29 -13
- package/src/core/elements/anchor/one-child-anchor-overwriting.js +44 -0
- package/src/core/elements/control/for-each-array.js +33 -31
- package/src/core/elements/control/for-each.js +1 -1
- package/src/core/elements/control/show-if.js +1 -1
- package/src/core/elements/control/switch.js +1 -1
- package/src/core/wrappers/AttributesWrapper.js +0 -1
- package/src/core/wrappers/ElementCreator.js +1 -1
- package/src/core/wrappers/HtmlElementWrapper.js +5 -4
- package/src/core/wrappers/SingletonView.js +1 -1
- package/src/core/wrappers/prototypes/nd-element-extensions.js +4 -0
- package/src/core/wrappers/template-cloner/NodeCloner.js +57 -14
- package/src/core/wrappers/template-cloner/TemplateCloner.js +2 -4
|
@@ -363,16 +363,16 @@ var NativeComponents = (function (exports) {
|
|
|
363
363
|
// });
|
|
364
364
|
};
|
|
365
365
|
|
|
366
|
-
let DebugManager$
|
|
366
|
+
let DebugManager$2 = {};
|
|
367
367
|
{
|
|
368
|
-
DebugManager$
|
|
368
|
+
DebugManager$2 = {
|
|
369
369
|
log() {},
|
|
370
370
|
warn() {},
|
|
371
371
|
error() {},
|
|
372
372
|
disable() {}
|
|
373
373
|
};
|
|
374
374
|
}
|
|
375
|
-
var DebugManager = DebugManager$
|
|
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
|
-
|
|
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
|
-
|
|
1283
|
-
|
|
1380
|
+
this.$previousValue = null;
|
|
1381
|
+
this.$currentValue = value;
|
|
1284
1382
|
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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
|
-
|
|
1322
|
-
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1323
1396
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
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
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
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
|
-
|
|
1424
|
-
|
|
1430
|
+
ObservableItem.prototype.triggerListeners = function(operations) {
|
|
1431
|
+
const $listeners = this.$listeners;
|
|
1432
|
+
const $previousValue = this.$previousValue;
|
|
1433
|
+
const $currentValue = this.$currentValue;
|
|
1425
1434
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1435
|
+
for(let i = 0, length = $listeners.length; i < length; i++) {
|
|
1436
|
+
$listeners[i]($currentValue, $previousValue, operations);
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1429
1439
|
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
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
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
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
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
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
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
);
|
|
1480
|
-
}
|
|
1460
|
+
ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations) {
|
|
1461
|
+
this.triggerWatchers(operations);
|
|
1462
|
+
this.triggerFirstListener(operations);
|
|
1463
|
+
};
|
|
1481
1464
|
|
|
1482
|
-
|
|
1483
|
-
|
|
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
|
-
|
|
1486
|
-
|
|
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
|
-
|
|
1489
|
-
|
|
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
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
subscribers.delete(observerFollower);
|
|
1495
|
-
observerFollower.cleanup();
|
|
1496
|
-
};
|
|
1497
|
-
observerFollower.dispose = observerFollower.destroy;
|
|
1507
|
+
if (result !== undefined) {
|
|
1508
|
+
newValue = result;
|
|
1509
|
+
}
|
|
1498
1510
|
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
},
|
|
1511
|
+
this.$updateWithNewValue(newValue);
|
|
1512
|
+
};
|
|
1502
1513
|
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
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
|
-
|
|
1517
|
-
originalObserver.subscribe(onStoreChange);
|
|
1522
|
+
ObservableItem.prototype.set = ObservableItem.prototype.$basicSet;
|
|
1518
1523
|
|
|
1519
|
-
|
|
1524
|
+
ObservableItem.prototype.val = function() {
|
|
1525
|
+
return this.$currentValue;
|
|
1526
|
+
};
|
|
1520
1527
|
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
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
|
-
|
|
1529
|
-
|
|
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
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
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
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
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
|
-
|
|
1665
|
-
|
|
1666
|
-
set(
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1629
|
+
const watchValueList = this.$watchers.get(value);
|
|
1630
|
+
if(!watchValueList) return;
|
|
1679
1631
|
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
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
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
.
|
|
1707
|
-
|
|
1708
|
-
|
|
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
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
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
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
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 {
|
|
1774
|
-
* @
|
|
1775
|
-
* @
|
|
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
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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
|
-
*
|
|
1812
|
-
*
|
|
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 {
|
|
1815
|
-
* @returns {
|
|
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
|
|
1818
|
-
*
|
|
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.
|
|
1821
|
-
this
|
|
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
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
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
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
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
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
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
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
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
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
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
|
-
*
|
|
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
|
|
1904
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
1938
|
-
*
|
|
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
|
-
*
|
|
1943
|
-
*
|
|
1944
|
-
*
|
|
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.
|
|
1947
|
-
|
|
1948
|
-
|
|
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.
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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
|
-
|
|
1959
|
-
this
|
|
1960
|
-
|
|
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 {
|
|
1966
|
-
* @
|
|
1896
|
+
* @param {*} value
|
|
1897
|
+
* @param {{ propagation: boolean, reset: boolean} | null} configs
|
|
1898
|
+
* @returns {ObservableItem}
|
|
1899
|
+
* @constructor
|
|
1967
1900
|
*/
|
|
1968
|
-
|
|
1969
|
-
|
|
1901
|
+
function Observable(value, configs = null) {
|
|
1902
|
+
return new ObservableItem(value, configs);
|
|
1903
|
+
}
|
|
1970
1904
|
|
|
1971
|
-
|
|
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 {
|
|
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
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
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
|
|
2018
|
-
* @
|
|
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
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
2059
|
-
|
|
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
|
-
*
|
|
2072
|
-
* @param {
|
|
1946
|
+
* Enable auto cleanup of observables.
|
|
1947
|
+
* @param {Boolean} enable
|
|
1948
|
+
* @param {{interval:Boolean, threshold:number}} options
|
|
2073
1949
|
*/
|
|
2074
|
-
|
|
2075
|
-
if(!
|
|
2076
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
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
|
|
2756
|
-
const
|
|
2757
|
-
|
|
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
|
-
|
|
2760
|
-
const anchorEnd = document.createComment('/ Anchor End '+name);
|
|
2363
|
+
instance.appendChild(sentinel);
|
|
2761
2364
|
|
|
2762
|
-
|
|
2763
|
-
|
|
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
|
|
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,
|
|
2486
|
+
parentNode.nativeInsertBefore(child, anchorEnd);
|
|
2791
2487
|
return;
|
|
2792
2488
|
}
|
|
2793
|
-
parentNode
|
|
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(
|
|
2807
|
-
return anchorFragment.appendChild(
|
|
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
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
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
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
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
|
-
|
|
3310
|
-
|
|
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
|
-
|
|
3316
|
-
|
|
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
|
-
|
|
3322
|
-
|
|
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}
|