native-document 1.0.153 → 1.0.155

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.
@@ -312,6 +312,19 @@ var NativeComponents = (function (exports) {
312
312
  }
313
313
  };
314
314
 
315
+ const nextTick = function(fn) {
316
+ let pending = false;
317
+ return function(...args) {
318
+ if (pending) return;
319
+ pending = true;
320
+
321
+ Promise.resolve().then(() => {
322
+ fn.apply(this, args);
323
+ pending = false;
324
+ });
325
+ };
326
+ };
327
+
315
328
  const deepClone = (value, onObservableFound) => {
316
329
  try {
317
330
  if(window.structuredClone !== undefined) {
@@ -443,6 +456,7 @@ var NativeComponents = (function (exports) {
443
456
 
444
457
  ObservableItem.prototype.__$Observable = true;
445
458
  ObservableItem.prototype.__$isObservable = true;
459
+ ObservableItem.computed = () => {};
446
460
  const noneTrigger = function() {};
447
461
 
448
462
  /**
@@ -794,72 +808,1198 @@ var NativeComponents = (function (exports) {
794
808
  };
795
809
 
796
810
  /**
797
- * Resets the observable to its initial value.
798
- * Only works if the observable was created with { reset: true } config.
811
+ * Resets the observable to its initial value.
812
+ * Only works if the observable was created with { reset: true } config.
813
+ *
814
+ * @example
815
+ * const count = Observable(0, { reset: true });
816
+ * count.set(10);
817
+ * count.reset(); // Back to 0
818
+ */
819
+ ObservableItem.prototype.reset = function() {
820
+ if(!this.configs?.reset) {
821
+ return;
822
+ }
823
+ const resetValue = (Validator.isObject(this.$initialValue))
824
+ ? deepClone(this.$initialValue, (observable) => {
825
+ observable.reset();
826
+ })
827
+ : this.$initialValue;
828
+ this.set(resetValue);
829
+ };
830
+
831
+ /**
832
+ * Returns a string representation of the observable's current value.
833
+ *
834
+ * @returns {string} String representation of the current value
835
+ */
836
+ ObservableItem.prototype.toString = function() {
837
+ return String(this.$currentValue);
838
+ };
839
+
840
+ /**
841
+ * Returns the primitive value of the observable (its current value).
842
+ * Called automatically in type coercion contexts.
843
+ *
844
+ * @returns {*} The current value of the observable
845
+ */
846
+ ObservableItem.prototype.valueOf = function() {
847
+ return this.$currentValue;
848
+ };
849
+
850
+
851
+
852
+ ObservableItem.prototype.persist = function(key, options = {}) {
853
+ let value = $getFromStorage(key, this.$currentValue);
854
+ if(options.get) {
855
+ value = options.get(value);
856
+ }
857
+ this.set(value);
858
+ const saver = $saveToStorage(this.$currentValue);
859
+ this.subscribe((newValue) => {
860
+ saver(key, options.set ? options.set(newValue) : newValue);
861
+ });
862
+ return this;
863
+ };
864
+
865
+ ObservableItem.prototype.clone = function() {
866
+ let clonedValue = this.$currentValue;
867
+
868
+ if(clonedValue && typeof clonedValue === 'object') {
869
+ if(typeof clonedValue.clone === 'function') {
870
+ clonedValue = clonedValue.clone();
871
+ } else {
872
+ clonedValue = structuredClone(clonedValue);
873
+ }
874
+ }
875
+
876
+ return new ObservableItem(clonedValue);
877
+ };
878
+
879
+ function createMultiSourceFilter(sources, callbackFn){
880
+ const observables = sources.filter(Validator.isObservable);
881
+
882
+ const getValues = () => sources.map(src =>
883
+ Validator.isObservable(src) ? src.val() : src
884
+ );
885
+
886
+ return {
887
+ dependencies: observables.length > 0 ? observables : null,
888
+ callback: (value) => callbackFn(value, getValues())
889
+ };
890
+ }
891
+
892
+ function match(patternObservableOrValue, asRegexObservableOrValue = true, flagsObservableOrValue = ''){
893
+ return createMultiSourceFilter(
894
+ [patternObservableOrValue, asRegexObservableOrValue, flagsObservableOrValue],
895
+ (value, [pattern, asRegex, flags]) => {
896
+ if (!pattern) return true;
897
+
898
+ if (asRegex){
899
+ try {
900
+ const regex = new RegExp(pattern, flags);
901
+ return regex.test(String(value));
902
+ } catch (error){
903
+ DebugManager$1.warn('Invalid regex pattern:', pattern, error);
904
+ return false;
905
+ }
906
+ }
907
+
908
+ if (!flags || flags === ''){
909
+ return String(value).toLowerCase().includes(String(pattern).toLowerCase());
910
+ }
911
+ return String(value).includes(String(pattern));
912
+ }
913
+ );
914
+ }
915
+
916
+ const mutationMethods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
917
+ const noMutationMethods = ['map', 'forEach', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex', 'concat', 'includes', 'indexOf'];
918
+
919
+ /**
920
+ *
921
+ * @param target
922
+ * @param {{propagation: boolean, deep: boolean, reset: boolean}|null} configs
923
+ * @constructor
924
+ */
925
+ const ObservableArray = function (target, configs = null) {
926
+ if(!Array.isArray(target)) {
927
+ throw new NativeDocumentError('Observable.array : target must be an array');
928
+ }
929
+
930
+ ObservableItem.call(this, target, configs);
931
+ };
932
+
933
+ ObservableArray.prototype = Object.create(ObservableItem.prototype);
934
+ ObservableArray.prototype.constructor = ObservableArray;
935
+ ObservableArray.prototype.__$isObservableArray = true;
936
+
937
+
938
+ Object.defineProperty(ObservableArray.prototype, 'length', {
939
+ get() {
940
+ return this.$currentValue.length;
941
+ }
942
+ });
943
+
944
+
945
+ ObservableArray.prototype.$mutate = function(action, args, mutateFn) {
946
+ if(this.$mutationInterceptor) {
947
+ const value = this.$mutationInterceptor(args, { action });
948
+ if(args !== undefined) {
949
+ args = value;
950
+ }
951
+ }
952
+ mutateFn(args);
953
+ };
954
+
955
+ mutationMethods.forEach((method) => {
956
+ ObservableArray.prototype[method] = function(...values) {
957
+ return this.$mutate(method, values, (argsToUse) => {
958
+ const result = this.$currentValue[method].apply(this.$currentValue, argsToUse);
959
+ this.trigger({ action: method, args: argsToUse, result });
960
+ return result;
961
+ })
962
+ };
963
+ });
964
+
965
+ noMutationMethods.forEach((method) => {
966
+ ObservableArray.prototype[method] = function(...values) {
967
+ return this.$currentValue[method].apply(this.$currentValue, values);
968
+ };
969
+ });
970
+
971
+
972
+ const $clearEvent = { action: 'clear' };
973
+
974
+ /**
975
+ * Removes all items from the array and triggers an update.
976
+ *
977
+ * @returns {boolean} True if array was cleared, false if it was already empty
978
+ * @example
979
+ * const items = Observable.array([1, 2, 3]);
980
+ * items.clear(); // []
981
+ */
982
+ ObservableArray.prototype.clear = function() {
983
+ if(this.$currentValue.length === 0) {
984
+ return;
985
+ }
986
+ this.$mutate('clear', [], () => {
987
+ this.$currentValue.length = 0;
988
+ this.trigger($clearEvent);
989
+ });
990
+ return true;
991
+ };
992
+
993
+ /**
994
+ * Returns the element at the specified index in the array.
995
+ *
996
+ * @param {number} index - Zero-based index of the element to retrieve
997
+ * @returns {*} The element at the specified index
998
+ * @example
999
+ * const items = Observable.array(['a', 'b', 'c']);
1000
+ * items.at(1); // 'b'
1001
+ */
1002
+ ObservableArray.prototype.at = function(index) {
1003
+ return this.$currentValue[index];
1004
+ };
1005
+
1006
+
1007
+ /**
1008
+ * Merges multiple values into the array and triggers an update.
1009
+ * Similar to push but with a different operation name.
1010
+ *
1011
+ * @param {Array} values - Array of values to merge
1012
+ * @example
1013
+ * const items = Observable.array([1, 2]);
1014
+ * items.merge([3, 4]); // [1, 2, 3, 4]
1015
+ */
1016
+ ObservableArray.prototype.merge = function(values) {
1017
+ this.$mutate('merge', values, (valuesToMerge) => {
1018
+ this.$currentValue.push.apply(this.$currentValue, valuesToMerge);
1019
+ this.trigger({ action: 'merge', args: valuesToMerge });
1020
+ });
1021
+ };
1022
+
1023
+ /**
1024
+ * Counts the number of elements that satisfy the provided condition.
1025
+ *
1026
+ * @param {(value: *, index: number) => Boolean} condition - Function that tests each element (item, index) => boolean
1027
+ * @returns {number} The count of elements that satisfy the condition
1028
+ * @example
1029
+ * const numbers = Observable.array([1, 2, 3, 4, 5]);
1030
+ * numbers.count(n => n > 3); // 2
1031
+ */
1032
+ ObservableArray.prototype.count = function(condition) {
1033
+ let count = 0;
1034
+ this.$currentValue.forEach((item, index) => {
1035
+ if(condition(item, index)) {
1036
+ count++;
1037
+ }
1038
+ });
1039
+ return count;
1040
+ };
1041
+
1042
+ /**
1043
+ * Swaps two elements at the specified indices and triggers an update.
1044
+ *
1045
+ * @param {number} indexA - Index of the first element
1046
+ * @param {number} indexB - Index of the second element
1047
+ * @returns {boolean} True if swap was successful, false if indices are out of bounds
1048
+ * @example
1049
+ * const items = Observable.array(['a', 'b', 'c']);
1050
+ * items.swap(0, 2); // ['c', 'b', 'a']
1051
+ */
1052
+ ObservableArray.prototype.swap = function(indexA, indexB) {
1053
+ this.$mutate('swap', [indexA, indexB], ([indexA, indexB]) => {
1054
+ const value = this.$currentValue;
1055
+ const length = value.length;
1056
+ if(indexB < indexA) {
1057
+ const temp = indexA;
1058
+ indexA = indexB;
1059
+ indexB = temp;
1060
+ }
1061
+ if(length < indexA || length < indexB) {
1062
+ return false;
1063
+ }
1064
+ const elementA = value[indexA];
1065
+ const elementB = value[indexB];
1066
+
1067
+ value[indexA] = elementB;
1068
+ value[indexB] = elementA;
1069
+ this.trigger({ action: 'swap', args: [indexA, indexB], result: [elementA, elementB] });
1070
+ });
1071
+ return true;
1072
+ };
1073
+
1074
+ ObservableArray.prototype.swapItems = function(itemA, itemB) {
1075
+ const indexA = this.$currentValue.indexOf(itemA);
1076
+ const indexB = this.$currentValue.indexOf(itemB);
1077
+
1078
+ return this.swap(indexA, indexB);
1079
+ };
1080
+
1081
+ ObservableArray.prototype.insertAfter = function(data, target) {
1082
+ const targetIndex = this.$currentValue.indexOf(target);
1083
+ return this.splice(targetIndex + 1, 0, data);
1084
+ };
1085
+
1086
+ /**
1087
+ * Removes the element at the specified index and triggers an update.
1088
+ *
1089
+ * @param {number} index - Index of the element to remove
1090
+ * @returns {Array} Array containing the removed element, or empty array if index is invalid
1091
+ * @example
1092
+ * const items = Observable.array(['a', 'b', 'c']);
1093
+ * items.remove(1); // ['b'] - Array is now ['a', 'c']
1094
+ */
1095
+ ObservableArray.prototype.remove = function(index) {
1096
+ let deleted = [];
1097
+ this.$mutate('remove', [index], ([idx]) => {
1098
+ deleted = this.$currentValue.splice(idx, 1);
1099
+ if(deleted.length === 0) {
1100
+ return;
1101
+ }
1102
+ this.trigger({action: 'remove', args: [idx], result: deleted[0]});
1103
+ });
1104
+ return deleted;
1105
+ };
1106
+
1107
+ /**
1108
+ * Removes the first occurrence of the specified item from the array.
1109
+ *
1110
+ * @param {*} item - The item to remove
1111
+ * @returns {Array} Array containing the removed element, or empty array if item not found
1112
+ * @example
1113
+ * const items = Observable.array(['a', 'b', 'c']);
1114
+ * items.removeItem('b'); // ['b'] - Array is now ['a', 'c']
1115
+ */
1116
+ ObservableArray.prototype.removeItem = function(item) {
1117
+ const indexOfItem = this.$currentValue.indexOf(item);
1118
+ if(indexOfItem === -1) {
1119
+ return [];
1120
+ }
1121
+ return this.remove(indexOfItem);
1122
+ };
1123
+
1124
+ /**
1125
+ * Checks if the array is empty.
1126
+ *
1127
+ * @returns {boolean} True if array has no elements
1128
+ * @example
1129
+ * const items = Observable.array([]);
1130
+ * items.isEmpty(); // true
1131
+ */
1132
+ ObservableArray.prototype.empty = function() {
1133
+ return this.$currentValue.length === 0;
1134
+ };
1135
+
1136
+ /**
1137
+ * Triggers a populate operation with the current array, iteration count, and callback.
1138
+ * Used internally for rendering optimizations.
1139
+ *
1140
+ * @param {number} iteration - Iteration count for rendering
1141
+ * @param {Function} callback - Callback function for rendering items
1142
+ */
1143
+ ObservableArray.prototype.populateAndRender = function(iteration, callback) {
1144
+ this.trigger({ action: 'populate', args: [this.$currentValue, iteration, callback] });
1145
+ };
1146
+
1147
+
1148
+ /**
1149
+ * Creates a filtered view of the array based on predicates.
1150
+ * The filtered array updates automatically when source data or predicates change.
1151
+ *
1152
+ * @param {Object} predicates - Object mapping property names to filter conditions or functions
1153
+ * @returns {ObservableArray} A new observable array containing filtered items
1154
+ * @example
1155
+ * const users = Observable.array([
1156
+ * { name: 'John', age: 25 },
1157
+ * { name: 'Jane', age: 30 }
1158
+ * ]);
1159
+ *
1160
+ * const adults = users.where({ age: (val) => val >= 18 });
1161
+ */
1162
+ ObservableArray.prototype.where = function(predicates) {
1163
+ if(typeof predicates === 'function') {
1164
+ predicates = { _: predicates };
1165
+ }
1166
+ const sourceArray = this;
1167
+ const observableDependencies = [sourceArray];
1168
+ const filterCallbacks = {};
1169
+
1170
+ for (const [key, rawPredicate] of Object.entries(predicates)) {
1171
+ const predicate = Validator.isObservable(rawPredicate) ? match(rawPredicate, false) : rawPredicate;
1172
+ if (predicate && typeof predicate === 'object' && 'callback' in predicate) {
1173
+ filterCallbacks[key] = predicate.callback;
1174
+
1175
+ if (predicate.dependencies) {
1176
+ const deps = Array.isArray(predicate.dependencies)
1177
+ ? predicate.dependencies
1178
+ : [predicate.dependencies];
1179
+ observableDependencies.push.apply(observableDependencies, deps);
1180
+ }
1181
+ } else if(typeof predicate === 'function') {
1182
+ filterCallbacks[key] = predicate;
1183
+ } else {
1184
+ filterCallbacks[key] = (value) => value === predicate;
1185
+ }
1186
+ }
1187
+
1188
+ const viewArray = new ObservableArray([]);
1189
+
1190
+ const filters = Object.entries(filterCallbacks);
1191
+ const updateView = () => {
1192
+ const filtered = sourceArray.val().filter(item => {
1193
+ for (const [key, callback] of filters) {
1194
+ if(key === '_') {
1195
+ if (!callback(item)) return false;
1196
+ } else {
1197
+ if (!callback(item[key])) return false;
1198
+ }
1199
+ }
1200
+ return true;
1201
+ });
1202
+
1203
+ viewArray.set(filtered);
1204
+ };
1205
+
1206
+ observableDependencies.forEach(dep => dep.subscribe(updateView));
1207
+
1208
+ updateView();
1209
+
1210
+ return viewArray;
1211
+ };
1212
+
1213
+ /**
1214
+ * Creates a filtered view where at least one of the specified fields matches the filter.
1215
+ *
1216
+ * @param {Array<string>} fields - Array of field names to check
1217
+ * @param {FilterResult} filter - Filter condition with callback and dependencies
1218
+ * @returns {ObservableArray} A new observable array containing filtered items
1219
+ * @example
1220
+ * const products = Observable.array([
1221
+ * { name: 'Apple', category: 'Fruit' },
1222
+ * { name: 'Carrot', category: 'Vegetable' }
1223
+ * ]);
1224
+ * const searchTerm = Observable('App');
1225
+ * const filtered = products.whereSome(['name', 'category'], match(searchTerm));
1226
+ */
1227
+ ObservableArray.prototype.whereSome = function(fields, filter) {
1228
+ return this.where({
1229
+ _: {
1230
+ dependencies: filter.dependencies,
1231
+ callback: (item) => fields.some(field => filter.callback(item[field]))
1232
+ }
1233
+ });
1234
+ };
1235
+
1236
+ /**
1237
+ * Creates a filtered view where all specified fields match the filter.
1238
+ *
1239
+ * @param {Array<string>} fields - Array of field names to check
1240
+ * @param {FilterResult} filter - Filter condition with callback and dependencies
1241
+ * @returns {ObservableArray} A new observable array containing filtered items
1242
+ * @example
1243
+ * const items = Observable.array([
1244
+ * { status: 'active', verified: true },
1245
+ * { status: 'active', verified: false }
1246
+ * ]);
1247
+ * const activeFilter = equals('active');
1248
+ * const filtered = items.whereEvery(['status', 'verified'], activeFilter);
1249
+ */
1250
+ ObservableArray.prototype.whereEvery = function(fields, filter) {
1251
+ return this.where({
1252
+ _: {
1253
+ dependencies: filter.dependencies,
1254
+ callback: (item) => fields.every(field => filter.callback(item[field]))
1255
+ }
1256
+ });
1257
+ };
1258
+
1259
+ ObservableArray.prototype.deepSubscribe = function(callback) {
1260
+ const updatedValue = nextTick(() => callback(this.val()));
1261
+ const $listeners = new WeakMap();
1262
+
1263
+ const bindItem = (item) => {
1264
+ if ($listeners.has(item)) {
1265
+ return;
1266
+ }
1267
+ if (item?.__$isObservableArray) {
1268
+ $listeners.set(item, item.deepSubscribe(updatedValue));
1269
+ return;
1270
+ }
1271
+ if (item?.__$isObservable) {
1272
+ item.subscribe(updatedValue);
1273
+ $listeners.set(item, () => item.unsubscribe(updatedValue));
1274
+ }
1275
+ };
1276
+
1277
+ const unbindItem = (item) => {
1278
+ const unsub = $listeners.get(item);
1279
+ if (unsub) {
1280
+ unsub();
1281
+ $listeners.delete(item);
1282
+ }
1283
+ };
1284
+
1285
+ this.$currentValue.forEach(bindItem);
1286
+ this.subscribe(updatedValue);
1287
+
1288
+ this.subscribe((items, _, operations) => {
1289
+ switch (operations?.action) {
1290
+ case 'push':
1291
+ case 'unshift':
1292
+ operations.args.forEach(bindItem);
1293
+ break;
1294
+
1295
+ case 'splice': {
1296
+ const [start, deleteCount, ...newItems] = operations.args;
1297
+ operations.result?.forEach(unbindItem);
1298
+ newItems.forEach(bindItem);
1299
+ break;
1300
+ }
1301
+
1302
+ case 'remove':
1303
+ unbindItem(operations.result);
1304
+ break;
1305
+
1306
+ case 'merge':
1307
+ operations.args.forEach(bindItem);
1308
+ break;
1309
+
1310
+ case 'clear':
1311
+ this.$currentValue.forEach(unbindItem);
1312
+ break;
1313
+ }
1314
+ });
1315
+
1316
+ return () => {
1317
+ this.$currentValue.forEach(unbindItem);
1318
+ };
1319
+ };
1320
+
1321
+
1322
+ ObservableArray.prototype.sync = function(targetObservable) {
1323
+ if (!targetObservable || !targetObservable.__$isObservableArray) {
1324
+ throw new NativeDocumentError('ObservableArray.sync : target must be an ObservableArray');
1325
+ }
1326
+
1327
+ targetObservable.set([...this.$currentValue]);
1328
+
1329
+ const sync = (currentValue, _, operations) => {
1330
+ if (!operations) {
1331
+ targetObservable.set([...currentValue]);
1332
+ return;
1333
+ }
1334
+
1335
+ const { action, args } = operations;
1336
+ targetObservable[action].apply(targetObservable, args);
1337
+ };
1338
+ this.subscribe(sync);
1339
+
1340
+ return () => this.unsubscribe(sync);
1341
+ };
1342
+
1343
+ ObservableArray.prototype.clone = function() {
1344
+ return new ObservableArray(this.resolve());
1345
+ };
1346
+
1347
+ const ObservableObject = function(target, configs) {
1348
+ ObservableItem.call(this, target);
1349
+ this.$observables = {};
1350
+ this.configs = configs;
1351
+
1352
+ this.$load(target);
1353
+
1354
+ for(const name in target) {
1355
+ if(!Object.hasOwn(this, name)) {
1356
+ Object.defineProperty(this, name, {
1357
+ get: () => this.$observables[name],
1358
+ set: (value) => this.$observables[name].set(value)
1359
+ });
1360
+ }
1361
+ }
1362
+
1363
+ };
1364
+
1365
+ ObservableObject.prototype = Object.create(ObservableItem.prototype);
1366
+
1367
+ Object.defineProperty(ObservableObject, '$value', {
1368
+ get() {
1369
+ return this.val();
1370
+ },
1371
+ set(value) {
1372
+ this.set(value);
1373
+ }
1374
+ });
1375
+
1376
+ ObservableObject.prototype.__$isObservableObject = true;
1377
+ ObservableObject.prototype.__isProxy__ = true;
1378
+
1379
+ ObservableObject.prototype.$load = function(initialValue) {
1380
+ const configs = this.configs;
1381
+ for(const key in initialValue) {
1382
+ const itemValue = initialValue[key];
1383
+ if(Array.isArray(itemValue)) {
1384
+ if(configs?.deep !== false) {
1385
+ const mappedItemValue = itemValue.map(item => {
1386
+ if(Validator.isJson(item)) {
1387
+ return new ObservableObject(item, configs);
1388
+ }
1389
+ if(Validator.isArray(item)) {
1390
+ return new ObservableArray(item, configs);
1391
+ }
1392
+ return new ObservableItem(item, configs);
1393
+ });
1394
+ this.$observables[key] = new ObservableArray(mappedItemValue, configs);
1395
+ continue;
1396
+ }
1397
+ this.$observables[key] = new ObservableArray(itemValue, configs);
1398
+ continue;
1399
+ }
1400
+ if(Validator.isObservable(itemValue) || Validator.isProxy(itemValue)) {
1401
+ this.$observables[key] = itemValue;
1402
+ continue;
1403
+ }
1404
+ this.$observables[key] = (typeof itemValue === 'object') ? new ObservableObject(itemValue, configs) : new ObservableItem(itemValue, configs);
1405
+ }
1406
+ };
1407
+
1408
+ ObservableObject.prototype.val = function() {
1409
+ const result = {};
1410
+ for(const key in this.$observables) {
1411
+ const dataItem = this.$observables[key];
1412
+ if(Validator.isObservable(dataItem)) {
1413
+ let value = dataItem.val();
1414
+ if(Array.isArray(value)) {
1415
+ value = value.map(item => {
1416
+ if(Validator.isObservable(item)) {
1417
+ return item.val();
1418
+ }
1419
+ if(Validator.isProxy(item)) {
1420
+ return item.$value;
1421
+ }
1422
+ return item;
1423
+ });
1424
+ }
1425
+ result[key] = value;
1426
+ } else if(Validator.isProxy(dataItem)) {
1427
+ result[key] = dataItem.$value;
1428
+ } else {
1429
+ result[key] = dataItem;
1430
+ }
1431
+ }
1432
+ return result;
1433
+ };
1434
+ ObservableObject.prototype.$val = ObservableObject.prototype.val;
1435
+
1436
+ ObservableObject.prototype.get = function(property) {
1437
+ const item = this.$observables[property];
1438
+ if(Validator.isObservable(item)) {
1439
+ return item.val();
1440
+ }
1441
+ if(Validator.isProxy(item)) {
1442
+ return item.$value;
1443
+ }
1444
+ return item;
1445
+ };
1446
+ ObservableObject.prototype.$get = ObservableObject.prototype.get;
1447
+
1448
+ ObservableObject.prototype.set = function(newData) {
1449
+ const data = Validator.isProxy(newData) ? newData.$value : newData;
1450
+ const configs = this.configs;
1451
+
1452
+ for(const key in data) {
1453
+ const targetItem = this.$observables[key];
1454
+ const newValueOrigin = newData[key];
1455
+ const newValue = data[key];
1456
+
1457
+ if(Validator.isObservable(targetItem)) {
1458
+ if(!Validator.isArray(newValue)) {
1459
+ targetItem.set(newValue);
1460
+ continue;
1461
+ }
1462
+ const firstElementFromOriginalValue = newValueOrigin.at(0);
1463
+ if(Validator.isObservable(firstElementFromOriginalValue) || Validator.isProxy(firstElementFromOriginalValue)) {
1464
+ const newValues = newValue.map(item => {
1465
+ if(Validator.isProxy(firstElementFromOriginalValue)) {
1466
+ return new ObservableObject(item, configs);
1467
+ }
1468
+ return ObservableItem(item, configs);
1469
+ });
1470
+ targetItem.set(newValues);
1471
+ continue;
1472
+ }
1473
+ targetItem.set([...newValue]);
1474
+ continue;
1475
+ }
1476
+ if(Validator.isProxy(targetItem)) {
1477
+ targetItem.update(newValue);
1478
+ continue;
1479
+ }
1480
+ this[key] = newValue;
1481
+ }
1482
+ };
1483
+ ObservableObject.prototype.$set = ObservableObject.prototype.set;
1484
+ ObservableObject.prototype.$updateWith = ObservableObject.prototype.set;
1485
+
1486
+ ObservableObject.prototype.observables = function() {
1487
+ return Object.values(this.$observables);
1488
+ };
1489
+ ObservableObject.prototype.$observables = ObservableObject.prototype.observables;
1490
+
1491
+ ObservableObject.prototype.keys = function() {
1492
+ return Object.keys(this.$observables);
1493
+ };
1494
+ ObservableObject.prototype.$keys = ObservableObject.prototype.keys;
1495
+ ObservableObject.prototype.clone = function() {
1496
+ return new ObservableObject(this.val(), this.configs);
1497
+ };
1498
+ ObservableObject.prototype.$clone = ObservableObject.prototype.clone;
1499
+ ObservableObject.prototype.reset = function() {
1500
+ for(const key in this.$observables) {
1501
+ this.$observables[key].reset();
1502
+ }
1503
+ };
1504
+ ObservableObject.prototype.originalSubscribe = ObservableObject.prototype.subscribe;
1505
+ ObservableObject.prototype.subscribe = function(callback) {
1506
+ const observables = this.observables();
1507
+ const updatedValue = nextTick(() => this.trigger());
1508
+
1509
+ this.originalSubscribe(callback);
1510
+
1511
+ for (let i = 0, length = observables.length; i < length; i++) {
1512
+ const observable = observables[i];
1513
+ if (observable.__$isObservableArray) {
1514
+ observable.deepSubscribe(updatedValue);
1515
+ continue
1516
+ }
1517
+ observable.subscribe(updatedValue);
1518
+ }
1519
+ };
1520
+ ObservableObject.prototype.configs = function() {
1521
+ return this.configs;
1522
+ };
1523
+
1524
+ ObservableObject.prototype.update = ObservableObject.prototype.set;
1525
+
1526
+ const $computed = (fn, dependencies) => ObservableItem.computed(fn, dependencies);
1527
+ const $checker = (obs, fn) => obs.transform(fn);
1528
+
1529
+ //
1530
+ // is... -> ObservableChecker<boolean>
1531
+ //
1532
+
1533
+ ObservableItem.prototype.isEqualTo = function (value) {
1534
+ if (value?.__$Observable) {
1535
+ return $computed((a, b) => a === b, [this, value]);
1536
+ }
1537
+ return $checker(this, x => x === value);
1538
+ };
1539
+
1540
+ ObservableItem.prototype.isNotEqualTo = function (value) {
1541
+ if (value?.__$Observable) {
1542
+ return $computed((a, b) => a !== b, [this, value]);
1543
+ }
1544
+ return $checker(this, x => x !== value);
1545
+ };
1546
+
1547
+ ObservableItem.prototype.isGreaterThan = function (value) {
1548
+ if (value?.__$Observable) {
1549
+ return $computed((a, b) => a > b, [this, value]);
1550
+ }
1551
+ return $checker(this, x => x > value);
1552
+ };
1553
+
1554
+ ObservableItem.prototype.isGreaterThanOrEqualTo = function (value) {
1555
+ if (value?.__$Observable) {
1556
+ return $computed((a, b) => a >= b, [this, value]);
1557
+ }
1558
+ return $checker(this, x => x >= value);
1559
+ };
1560
+
1561
+ ObservableItem.prototype.isLessThan = function (value) {
1562
+ if (value?.__$Observable) {
1563
+ return $computed((a, b) => a < b, [this, value]);
1564
+ }
1565
+ return $checker(this, x => x < value);
1566
+ };
1567
+
1568
+ ObservableItem.prototype.isLessThanOrEqualTo = function (value) {
1569
+ if (value?.__$Observable) {
1570
+ return $computed((a, b) => a <= b, [this, value]);
1571
+ }
1572
+ return $checker(this, x => x <= value);
1573
+ };
1574
+
1575
+ ObservableItem.prototype.isBetween = function (min, max) {
1576
+ if (min.__$Observable && max.__$Observable) {
1577
+ return $computed((x, a, b) => x >= a && x <= b, [this, min, max]);
1578
+ }
1579
+ if (min.__$Observable) {
1580
+ return $computed((x, a) => x >= a && x <= max, [this, min]);
1581
+ }
1582
+ if (max.__$Observable) {
1583
+ return $computed((x, b) => x >= min && x <= b, [this, max]);
1584
+ }
1585
+ return $checker(this, x => x >= min && x <= max);
1586
+ };
1587
+
1588
+ ObservableItem.prototype.isNull = function () {
1589
+ return $checker(this, x => x == null);
1590
+ };
1591
+
1592
+ ObservableItem.prototype.isTruthy = function () {
1593
+ return $checker(this, x => !!x);
1594
+ };
1595
+
1596
+ ObservableItem.prototype.isFalsy = function () {
1597
+ return $checker(this, x => !x);
1598
+ };
1599
+
1600
+ ObservableItem.prototype.isStartingWith = function (str) {
1601
+ if (str?.__$Observable) {
1602
+ return $computed((a, b) => String(a).startsWith(b), [this, str]);
1603
+ }
1604
+ return $checker(this, x => String(x).startsWith(str));
1605
+ };
1606
+
1607
+ ObservableItem.prototype.isEndingWith = function (str) {
1608
+ if (str?.__$Observable) {
1609
+ return $computed((a, b) => String(a).endsWith(b), [this, str]);
1610
+ }
1611
+ return $checker(this, x => String(x).endsWith(str));
1612
+ };
1613
+
1614
+ ObservableItem.prototype.isMatchingPattern = function (regex) {
1615
+ if (regex?.__$Observable) {
1616
+ return $computed((a, b) => new RegExp(b).test(String(a)), [this, regex]);
1617
+ }
1618
+ return $checker(this, x => regex.test(String(x)));
1619
+ };
1620
+
1621
+ ObservableItem.prototype.isEmpty = function () {
1622
+ return $checker(this, x => x == null || x === '' || (Array.isArray(x) && x.length === 0));
1623
+ };
1624
+
1625
+ ObservableItem.prototype.isNotEmpty = function () {
1626
+ return $checker(this, x => x == null || x === '' || (Array.isArray(x) && x.length !== 0));
1627
+ };
1628
+
1629
+ ObservableItem.prototype.isIncludes = function (value) {
1630
+ if (value?.__$Observable) {
1631
+ return $computed((a, b) => {
1632
+ if (Array.isArray(a)) return a.includes(b);
1633
+ return String(a).includes(String(b));
1634
+ }, [this, value]);
1635
+ }
1636
+ return $checker(this, x => {
1637
+ if (Array.isArray(x)) return x.includes(value);
1638
+ return String(x).includes(String(value));
1639
+ });
1640
+ };
1641
+
1642
+ ObservableItem.prototype.isIncludedIn = function (array) {
1643
+ if (array?.__$Observable) {
1644
+ return $computed((a, b) => b.includes(a), [this, array]);
1645
+ }
1646
+ return $checker(this, x => array.includes(x));
1647
+ };
1648
+
1649
+ ObservableItem.prototype.isOneOf = ObservableItem.prototype.isIncludedIn;
1650
+
1651
+ ObservableItem.prototype.isHaving = function (key) {
1652
+ if (key?.__$Observable) {
1653
+ return $computed((a, b) => b in Object(a), [this, key]);
1654
+ }
1655
+ return $checker(this, x => key in Object(x));
1656
+ };
1657
+
1658
+ //
1659
+ // to... -> ObservableChecker<any>
1660
+ //
1661
+
1662
+ ObservableItem.prototype.toUpperCase = function () {
1663
+ return $checker(this, x => String(x).toUpperCase());
1664
+ };
1665
+
1666
+ ObservableItem.prototype.toLowerCase = function () {
1667
+ return $checker(this, x => String(x).toLowerCase());
1668
+ };
1669
+
1670
+ ObservableItem.prototype.toTrimmed = function () {
1671
+ return $checker(this, x => String(x).trim());
1672
+ };
1673
+
1674
+ ObservableItem.prototype.toBoolean = function () {
1675
+ return $checker(this, x => !!x);
1676
+ };
1677
+
1678
+ ObservableItem.prototype.toLiteral = function (template, placeholder = '${v}') {
1679
+ return $checker(this, x => template.replace(placeholder, x));
1680
+ };
1681
+
1682
+ ObservableItem.prototype.toFormatted = ObservableItem.prototype.toLiteral;
1683
+
1684
+ ObservableItem.prototype.toProperty = function (key) {
1685
+ const keys = key.split('.');
1686
+ return $checker(this, x => {
1687
+ let value = x;
1688
+ for (const k of keys) {
1689
+ if (value == null) return undefined;
1690
+ value = value[k];
1691
+ }
1692
+ return value;
1693
+ });
1694
+ };
1695
+
1696
+ ObservableItem.prototype.toLength = function () {
1697
+ return $checker(this, x => (x == null ? 0 : x.length));
1698
+ };
1699
+
1700
+ ObservableItem.prototype.toClamped = function (min, max) {
1701
+ if (min.__$Observable && max.__$Observable) {
1702
+ return $computed((x, a, b) => Math.min(Math.max(x, a), b), [this, min, max]);
1703
+ }
1704
+ if (min.__$Observable) {
1705
+ return $computed((x, a) => Math.min(Math.max(x, a), max), [this, min]);
1706
+ }
1707
+ if (max.__$Observable) {
1708
+ return $computed((x, b) => Math.min(Math.max(x, min), b), [this, max]);
1709
+ }
1710
+ return $checker(this, x => Math.min(Math.max(x, min), max));
1711
+ };
1712
+
1713
+ ObservableItem.prototype.toPercent = function (total) {
1714
+ if (total?.__$Observable) {
1715
+ return $computed((a, b) => (b === 0 ? 0 : (a / b) * 100), [this, total]);
1716
+ }
1717
+ return $checker(this, x => (total === 0 ? 0 : (x / total) * 100));
1718
+ };
1719
+
1720
+ /**
1721
+ * Creates an ObservableWhen that tracks whether an observable equals a specific value.
1722
+ *
1723
+ * @param {ObservableItem} observer - The observable to watch
1724
+ * @param {*} value - The value to compare against
1725
+ * @class ObservableWhen
1726
+ */
1727
+ const ObservableWhen = function(observer, value) {
1728
+ this.$target = value;
1729
+ this.$observer = observer;
1730
+ };
1731
+
1732
+ ObservableWhen.prototype.__$Observable = true;
1733
+ ObservableWhen.prototype.__$isObservableWhen = true;
1734
+
1735
+ /**
1736
+ * Subscribes to changes in the match status (true when observable equals target value).
1737
+ *
1738
+ * @param {Function} callback - Function called with boolean indicating if values match
1739
+ * @returns {Function} Unsubscribe function
1740
+ * @example
1741
+ * const status = Observable('idle');
1742
+ * const isLoading = status.when('loading');
1743
+ * isLoading.subscribe(active => console.log('Loading:', active));
1744
+ */
1745
+ ObservableWhen.prototype.subscribe = function(callback) {
1746
+ return this.$observer.on(this.$target, callback);
1747
+ };
1748
+
1749
+ /**
1750
+ * Returns true if the observable's current value equals the target value.
1751
+ *
1752
+ * @returns {boolean} True if observable value matches target value
1753
+ */
1754
+ ObservableWhen.prototype.val = function() {
1755
+ return this.$observer.$currentValue === this.$target;
1756
+ };
1757
+
1758
+ /**
1759
+ * Returns true if the observable's current value equals the target value.
1760
+ * Alias for val().
1761
+ *
1762
+ * @returns {boolean} True if observable value matches target value
1763
+ */
1764
+ ObservableWhen.prototype.isMatch = ObservableWhen.prototype.val;
1765
+
1766
+ /**
1767
+ * Returns true if the observable's current value equals the target value.
1768
+ * Alias for val().
1769
+ *
1770
+ * @returns {boolean} True if observable value matches target value
1771
+ */
1772
+ ObservableWhen.prototype.isActive = ObservableWhen.prototype.val;
1773
+
1774
+ /**
1775
+ *
1776
+ * @param {ObservableItem} $observable
1777
+ * @param {Function} $checker
1778
+ * @class ObservableChecker
1779
+ */
1780
+ function ObservableChecker($observable, $checker) {
1781
+ this.observable = $observable;
1782
+
1783
+ ObservableItem.call(this);
1784
+
1785
+ this.$mutation = $checker;
1786
+
1787
+ $observable.subscribe((newValue) => {
1788
+ this.$updateWithMutation(newValue);
1789
+ });
1790
+
1791
+ this.$updateWithMutation($observable.val());
1792
+ }
1793
+
1794
+ ObservableChecker.prototype = Object.create(ObservableItem.prototype);
1795
+ ObservableChecker.prototype.constructor = ObservableChecker;
1796
+ ObservableChecker.prototype.__$Observable = true;
1797
+ ObservableChecker.prototype.__$isObservableChecker = true;
1798
+
1799
+
1800
+ const ObservablePipe = ObservableChecker;
1801
+ ObservablePipe.prototype.constructor = ObservablePipe;
1802
+
1803
+ ObservableChecker.prototype.$updateWithMutation = function(newValue) {
1804
+ newValue = this.$mutation(newValue);
1805
+ return this.set(newValue);
1806
+ };
1807
+
1808
+ const $parseDateParts = (value, locale) => {
1809
+ const d = new Date(value);
1810
+ return {
1811
+ d,
1812
+ parts: new Intl.DateTimeFormat(locale, {
1813
+ year: 'numeric',
1814
+ month: 'long',
1815
+ day: '2-digit',
1816
+ hour: '2-digit',
1817
+ minute: '2-digit',
1818
+ second: '2-digit',
1819
+ }).formatToParts(d).reduce((acc, { type, value }) => {
1820
+ acc[type] = value;
1821
+ return acc;
1822
+ }, {})
1823
+ };
1824
+ };
1825
+
1826
+ const $applyDatePattern = (pattern, d, parts) => {
1827
+ const pad = n => String(n).padStart(2, '0');
1828
+ return pattern
1829
+ .replace('YYYY', parts.year)
1830
+ .replace('YY', parts.year.slice(-2))
1831
+ .replace('MMMM', parts.month)
1832
+ .replace('MMM', parts.month.slice(0, 3))
1833
+ .replace('MM', pad(d.getMonth() + 1))
1834
+ .replace('DD', pad(d.getDate()))
1835
+ .replace('D', d.getDate())
1836
+ .replace('HH', parts.hour)
1837
+ .replace('mm', parts.minute)
1838
+ .replace('ss', parts.second);
1839
+ };
1840
+
1841
+ const Formatters = {
1842
+ currency: (value, locale, { currency = 'XOF', notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
1843
+ new Intl.NumberFormat(locale, {
1844
+ style: 'currency',
1845
+ currency,
1846
+ notation,
1847
+ minimumFractionDigits,
1848
+ maximumFractionDigits
1849
+ }).format(value),
1850
+
1851
+ number: (value, locale, { notation, minimumFractionDigits, maximumFractionDigits } = {}) =>
1852
+ new Intl.NumberFormat(locale, {
1853
+ notation,
1854
+ minimumFractionDigits,
1855
+ maximumFractionDigits
1856
+ }).format(value),
1857
+
1858
+ percent: (value, locale, { decimals = 1 } = {}) =>
1859
+ new Intl.NumberFormat(locale, {
1860
+ style: 'percent',
1861
+ maximumFractionDigits: decimals
1862
+ }).format(value),
1863
+
1864
+ date: (value, locale, { format, dateStyle = 'long' } = {}) => {
1865
+ if (format) {
1866
+ const { d, parts } = $parseDateParts(value, locale);
1867
+ return $applyDatePattern(format, d, parts);
1868
+ }
1869
+ return new Intl.DateTimeFormat(locale, { dateStyle }).format(new Date(value));
1870
+ },
1871
+
1872
+ time: (value, locale, { format, hour = '2-digit', minute = '2-digit', second } = {}) => {
1873
+ if (format) {
1874
+ const { d, parts } = $parseDateParts(value, locale);
1875
+ return $applyDatePattern(format, d, parts);
1876
+ }
1877
+ return new Intl.DateTimeFormat(locale, { hour, minute, second }).format(new Date(value));
1878
+ },
1879
+
1880
+ datetime: (value, locale, { format, dateStyle = 'long', hour = '2-digit', minute = '2-digit', second } = {}) => {
1881
+ if (format) {
1882
+ const { d, parts } = $parseDateParts(value, locale);
1883
+ return $applyDatePattern(format, d, parts);
1884
+ }
1885
+ return new Intl.DateTimeFormat(locale, { dateStyle, hour, minute, second }).format(new Date(value));
1886
+ },
1887
+
1888
+ relative: (value, locale, { unit = 'day', numeric = 'auto' } = {}) => {
1889
+ const diff = Math.round((value - Date.now()) / (1000 * 60 * 60 * 24));
1890
+ return new Intl.RelativeTimeFormat(locale, { numeric }).format(diff, unit);
1891
+ },
1892
+
1893
+ plural: (value, locale, { singular, plural } = {}) => {
1894
+ const rule = new Intl.PluralRules(locale).select(value);
1895
+ return `${value} ${rule === 'one' ? singular : plural}`;
1896
+ },
1897
+ };
1898
+
1899
+ /**
1900
+ * Creates an ObservableWhen that represents whether the observable equals a specific value.
1901
+ * Returns an object that can be subscribed to and will emit true/false.
799
1902
  *
1903
+ * @param {*} value - The value to compare against
1904
+ * @returns {ObservableWhen} An ObservableWhen instance that tracks when the observable equals the value
800
1905
  * @example
801
- * const count = Observable(0, { reset: true });
802
- * count.set(10);
803
- * count.reset(); // Back to 0
1906
+ * const status = Observable('idle');
1907
+ * const isLoading = status.when('loading');
1908
+ * isLoading.subscribe(active => console.log('Loading:', active));
1909
+ * status.set('loading'); // Logs: "Loading: true"
804
1910
  */
805
- ObservableItem.prototype.reset = function() {
806
- if(!this.configs?.reset) {
807
- return;
808
- }
809
- const resetValue = (Validator.isObject(this.$initialValue))
810
- ? deepClone(this.$initialValue, (observable) => {
811
- observable.reset();
812
- })
813
- : this.$initialValue;
814
- this.set(resetValue);
815
- };
816
1911
 
817
- /**
818
- * Returns a string representation of the observable's current value.
819
- *
820
- * @returns {string} String representation of the current value
821
- */
822
- ObservableItem.prototype.toString = function() {
823
- return String(this.$currentValue);
1912
+ ObservableItem.prototype.when = function(value) {
1913
+ return new ObservableWhen(this, value);
824
1914
  };
825
1915
 
1916
+
1917
+
826
1918
  /**
827
- * Returns the primitive value of the observable (its current value).
828
- * Called automatically in type coercion contexts.
829
- *
830
- * @returns {*} The current value of the observable
1919
+ * Create an Observable checker instance
1920
+ * @param callback
1921
+ * @returns {ObservableChecker}
831
1922
  */
832
- ObservableItem.prototype.valueOf = function() {
833
- return this.$currentValue;
1923
+ ObservableItem.prototype.check = function(callback) {
1924
+ return new ObservableChecker(this, callback)
834
1925
  };
835
1926
 
836
-
837
-
838
- ObservableItem.prototype.persist = function(key, options = {}) {
839
- let value = $getFromStorage(key, this.$currentValue);
840
- if(options.get) {
841
- value = options.get(value);
1927
+ ObservableItem.prototype.transform = ObservableItem.prototype.check;
1928
+ ObservableItem.prototype.pluck = function(property) {
1929
+ return new ObservableChecker(this, (value) => value[property]);
1930
+ };
1931
+ ObservableItem.prototype.is = function(callbackOrValue) {
1932
+ if(typeof callbackOrValue === 'function') {
1933
+ return new ObservableChecker(this, callbackOrValue);
842
1934
  }
843
- this.set(value);
844
- const saver = $saveToStorage(this.$currentValue);
845
- this.subscribe((newValue) => {
846
- saver(key, options.set ? options.set(newValue) : newValue);
847
- });
848
- return this;
1935
+ return new ObservableChecker(this, (value) => value === callbackOrValue);
849
1936
  };
1937
+ ObservableItem.prototype.select = ObservableItem.prototype.check;
850
1938
 
851
- ObservableItem.prototype.clone = function() {
852
- let clonedValue = this.$currentValue;
1939
+ /**
1940
+ * Creates a derived observable that formats the current value using Intl.
1941
+ * Automatically reacts to both value changes and locale changes (Store.__nd.locale).
1942
+ *
1943
+ * @param {string | Function} type - Format type or custom formatter function
1944
+ * @param {Object} [options={}] - Options passed to the formatter
1945
+ * @returns {ObservableItem<string>}
1946
+ *
1947
+ * @example
1948
+ * // Currency
1949
+ * price.format('currency') // "15 000 FCFA"
1950
+ * price.format('currency', { currency: 'EUR' }) // "15 000,00 €"
1951
+ * price.format('currency', { notation: 'compact' }) // "15 K FCFA"
1952
+ *
1953
+ * // Number
1954
+ * count.format('number') // "15 000"
1955
+ *
1956
+ * // Percent
1957
+ * rate.format('percent') // "15,0 %"
1958
+ * rate.format('percent', { decimals: 2 }) // "15,00 %"
1959
+ *
1960
+ * // Date
1961
+ * date.format('date') // "3 mars 2026"
1962
+ * date.format('date', { dateStyle: 'full' }) // "mardi 3 mars 2026"
1963
+ * date.format('date', { format: 'DD/MM/YYYY' }) // "03/03/2026"
1964
+ * date.format('date', { format: 'DD MMM YYYY' }) // "03 mar 2026"
1965
+ * date.format('date', { format: 'DD MMMM YYYY' }) // "03 mars 2026"
1966
+ *
1967
+ * // Time
1968
+ * date.format('time') // "20:30"
1969
+ * date.format('time', { second: '2-digit' }) // "20:30:00"
1970
+ * date.format('time', { format: 'HH:mm:ss' }) // "20:30:00"
1971
+ *
1972
+ * // Datetime
1973
+ * date.format('datetime') // "3 mars 2026, 20:30"
1974
+ * date.format('datetime', { dateStyle: 'full' }) // "mardi 3 mars 2026, 20:30"
1975
+ * date.format('datetime', { format: 'DD/MM/YYYY HH:mm' }) // "03/03/2026 20:30"
1976
+ *
1977
+ * // Relative
1978
+ * date.format('relative') // "dans 11 jours"
1979
+ * date.format('relative', { unit: 'month' }) // "dans 1 mois"
1980
+ *
1981
+ * // Plural
1982
+ * count.format('plural', { singular: 'billet', plural: 'billets' }) // "3 billets"
1983
+ *
1984
+ * // Custom formatter
1985
+ * price.format(value => `${value.toLocaleString()} FCFA`)
1986
+ *
1987
+ * // Reacts to locale changes automatically
1988
+ * Store.setLocale('en-US');
1989
+ */
1990
+ ObservableItem.prototype.format = function(type, options = {}) {
1991
+ const self = this;
853
1992
 
854
- if(clonedValue && typeof clonedValue === 'object') {
855
- if(typeof clonedValue.clone === 'function') {
856
- clonedValue = clonedValue.clone();
857
- } else {
858
- clonedValue = structuredClone(clonedValue);
859
- }
1993
+ if (typeof type === 'function') {
1994
+ return new ObservableChecker(self, type);
860
1995
  }
861
1996
 
862
- return new ObservableItem(clonedValue);
1997
+ const formatter = Formatters[type];
1998
+ const localeObservable = Formatters.locale;
1999
+
2000
+ return ObservableItem.computed(() => formatter(self.val(), localeObservable.val(), options),
2001
+ [self, localeObservable]
2002
+ );
863
2003
  };
864
2004
 
865
2005
  /**
@@ -931,6 +2071,138 @@ var NativeComponents = (function (exports) {
931
2071
  setInterval(() => MemoryManager.cleanObservables(threshold), interval);
932
2072
  };
933
2073
 
2074
+
2075
+ /**
2076
+ * Creates an observable array with reactive array methods.
2077
+ * All mutations trigger updates automatically.
2078
+ *
2079
+ * @param {Array} [target=[]] - Initial array value
2080
+ * @param {Object|null} [configs=null] - Configuration options
2081
+ * // @param {boolean} [configs.propagation=true] - Whether to propagate changes to parent observables
2082
+ * // @param {boolean} [configs.deep=false] - Whether to make nested objects observable
2083
+ * @param {boolean} [configs.reset=false] - Whether to store initial value for reset()
2084
+ * @returns {ObservableArray} An observable array with reactive methods
2085
+ * @example
2086
+ * const items = Observable.array([1, 2, 3]);
2087
+ * items.push(4); // Triggers update
2088
+ * items.subscribe((arr) => console.log(arr));
2089
+ */
2090
+ Observable.array = function(target = [], configs = null) {
2091
+ return new ObservableArray(target, configs);
2092
+ };
2093
+
2094
+ /**
2095
+ *
2096
+ * @param {Function} callback
2097
+ * @returns {Function}
2098
+ */
2099
+ Observable.batch = function(callback) {
2100
+ const $observer = Observable(0);
2101
+ const batch = function() {
2102
+ if(Validator.isAsyncFunction(callback)) {
2103
+ return (callback(...arguments)).then(() => {
2104
+ $observer.trigger();
2105
+ }).catch(error => { throw error; });
2106
+ }
2107
+ callback(...arguments);
2108
+ $observer.trigger();
2109
+ };
2110
+ batch.$observer = $observer;
2111
+ return batch;
2112
+ };
2113
+
2114
+
2115
+ /**
2116
+ * Creates a computed observable that automatically updates when its dependencies change.
2117
+ * The callback is re-executed whenever any dependency observable changes.
2118
+ *
2119
+ * @param {Function} callback - Function that returns the computed value
2120
+ * @param {Array<ObservableItem|ObservableChecker|ObservableProxy>|Function} [dependencies=[]] - Array of observables to watch, or batch function
2121
+ * @returns {ObservableItem} A new observable that updates automatically
2122
+ * @example
2123
+ * const firstName = Observable('John');
2124
+ * const lastName = Observable('Doe');
2125
+ * const fullName = Observable.computed(
2126
+ * () => `${firstName.val()} ${lastName.val()}`,
2127
+ * [firstName, lastName]
2128
+ * );
2129
+ *
2130
+ * // With batch function
2131
+ * const batch = Observable.batch(() => { ... });
2132
+ * const computed = Observable.computed(() => { ... }, batch);
2133
+ */
2134
+ Observable.computed = function(callback, dependencies = []) {
2135
+ const initialValue = callback();
2136
+ const observable = new ObservableItem(initialValue);
2137
+ const getValues = () => dependencies.map((item) => item.val());
2138
+ const updatedValue = nextTick(() => observable.set(callback(...getValues())));
2139
+
2140
+ if(Validator.isFunction(dependencies)) {
2141
+ if(!Validator.isObservable(dependencies.$observer)) {
2142
+ throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
2143
+ }
2144
+ dependencies.$observer.subscribe(updatedValue);
2145
+ return observable;
2146
+ }
2147
+
2148
+ dependencies.forEach(dependency => {
2149
+ if(Validator.isProxy(dependency)) {
2150
+ dependency.$observables.forEach((observable) => {
2151
+ observable.subscribe(updatedValue);
2152
+ });
2153
+ return;
2154
+ }
2155
+ dependency.subscribe(updatedValue);
2156
+ });
2157
+
2158
+ return observable;
2159
+ };
2160
+ ObservableItem.computed = Observable.computed;
2161
+
2162
+
2163
+ Observable.init = function(initialValue, configs = null) {
2164
+ return new ObservableObject(initialValue, configs)
2165
+ };
2166
+
2167
+ /**
2168
+ *
2169
+ * @param {any[]} data
2170
+ * @return Proxy[]
2171
+ */
2172
+ Observable.arrayOfObject = function(data) {
2173
+ return data.map(item => Observable.object(item));
2174
+ };
2175
+
2176
+ /**
2177
+ * Get the value of an observable or an object of observables.
2178
+ * @param {ObservableItem|Object<ObservableItem>} data
2179
+ * @returns {{}|*|null}
2180
+ */
2181
+ Observable.value = function(data) {
2182
+ if(data?.__$isObservableArray) {
2183
+ const result = [];
2184
+ for(let i = 0, length = data.length; i < length; i++) {
2185
+ const item = data.at(i);
2186
+ result.push(Observable.value(item));
2187
+ }
2188
+ return result;
2189
+ }
2190
+ if(data?.__$Observable) {
2191
+ return data.val();
2192
+ }
2193
+ if(Validator.isProxy(data)) {
2194
+ return data.$value;
2195
+ }
2196
+ return data;
2197
+ };
2198
+
2199
+ ObservableItem.prototype.resolve = function () {
2200
+ return Observable.value(this);
2201
+ };
2202
+
2203
+ Observable.object = Observable.init;
2204
+ Observable.json = Observable.init;
2205
+
934
2206
  const BOOLEAN_ATTRIBUTES = new Set([
935
2207
  'checked',
936
2208
  'selected',
@@ -10442,6 +11714,18 @@ var NativeComponents = (function (exports) {
10442
11714
  const Row = HStack;
10443
11715
  const Col = VStack;
10444
11716
 
11717
+ let withValidation = (fn) => fn;
11718
+
11719
+ const normalizeComponentArgs = function(props, children = null) {
11720
+ if(props && children) {
11721
+ return { props, children };
11722
+ }
11723
+ if(typeof props !== 'object' || Array.isArray(props) || props === null || props.constructor.name !== 'Object' || props.$hydrate) { // IF it's not a JSON
11724
+ return { props: children, children: props }
11725
+ }
11726
+ return { props, children };
11727
+ };
11728
+
10445
11729
  const EVENTS = [
10446
11730
  "Click",
10447
11731
  "DblClick",
@@ -10723,26 +12007,188 @@ var NativeComponents = (function (exports) {
10723
12007
  }
10724
12008
  };
10725
12009
 
10726
- Object.defineProperty(HTMLElement.prototype, 'classes', {
10727
- configurable: true,
10728
- get() {
10729
- return {
10730
- $element: this,
10731
- ...classListMethods
10732
- };
10733
- }
10734
- });
12010
+ Object.defineProperty(HTMLElement.prototype, 'classes', {
12011
+ configurable: true,
12012
+ get() {
12013
+ return {
12014
+ $element: this,
12015
+ ...classListMethods
12016
+ };
12017
+ }
12018
+ });
12019
+
12020
+ DocumentFragment.prototype.__IS_FRAGMENT = true;
12021
+
12022
+ Function.prototype.args = function(...args) {
12023
+ return withValidation(this);
12024
+ };
12025
+
12026
+ Function.prototype.errorBoundary = function(callback) {
12027
+ const handler = (...args) => {
12028
+ try {
12029
+ return this.apply(this, args);
12030
+ } catch(e) {
12031
+ return callback(e, {caller: handler, args: args });
12032
+ }
12033
+ };
12034
+ return handler;
12035
+ };
12036
+
12037
+ NDElement.$getChild = ElementCreator.getChild;
12038
+
12039
+ String.prototype.toNdElement = function () {
12040
+ return ElementCreator.createStaticTextNode(null, this);
12041
+ };
12042
+
12043
+ Number.prototype.toNdElement = function () {
12044
+ return ElementCreator.createStaticTextNode(null, this.toString());
12045
+ };
12046
+
12047
+ Element.prototype.toNdElement = function () {
12048
+ return this;
12049
+ };
12050
+ Text.prototype.toNdElement = function () {
12051
+ return this;
12052
+ };
12053
+ Comment.prototype.toNdElement = function () {
12054
+ return this;
12055
+ };
12056
+ Document.prototype.toNdElement = function () {
12057
+ return this;
12058
+ };
12059
+ DocumentFragment.prototype.toNdElement = function () {
12060
+ return this;
12061
+ };
12062
+
12063
+ ObservableItem.prototype.toNdElement = function () {
12064
+ return ElementCreator.createObservableNode(null, this);
12065
+ };
12066
+
12067
+ ObservableChecker.prototype.toNdElement = ObservableItem.prototype.toNdElement;
12068
+
12069
+ NDElement.prototype.toNdElement = function () {
12070
+ const element = this.$element ?? this.$build?.() ?? this.build?.() ?? null;
12071
+ if(this.$attachements) {
12072
+ if(!this.$attachements.contains(this.$element)) {
12073
+ this.$attachements.append(this.$element);
12074
+ }
12075
+ return this.$attachements;
12076
+ }
12077
+ return element;
12078
+ };
12079
+
12080
+ Array.prototype.toNdElement = function () {
12081
+ const fragment = document.createDocumentFragment();
12082
+ for(let i = 0, length = this.length; i < length; i++) {
12083
+ const child = ElementCreator.getChild(this[i]);
12084
+ if(child === null) continue;
12085
+ fragment.appendChild(child);
12086
+ }
12087
+ return fragment;
12088
+ };
12089
+
12090
+ Function.prototype.toNdElement = function () {
12091
+ const child = this;
12092
+ return ElementCreator.getChild(child());
12093
+ };
12094
+
12095
+ /**
12096
+ * @param {HTMLElement} el
12097
+ * @param {number} timeout
12098
+ */
12099
+ const waitForVisualEnd = (el, timeout = 1000) => {
12100
+ return new Promise((resolve) => {
12101
+ let isResolved = false;
12102
+
12103
+ const cleanupAndResolve = (e) => {
12104
+ if (e && e.target !== el) return;
12105
+ if (isResolved) return;
12106
+
12107
+ isResolved = true;
12108
+ el.removeEventListener('transitionend', cleanupAndResolve);
12109
+ el.removeEventListener('animationend', cleanupAndResolve);
12110
+ clearTimeout(timer);
12111
+ resolve();
12112
+ };
12113
+
12114
+ el.addEventListener('transitionend', cleanupAndResolve);
12115
+ el.addEventListener('animationend', cleanupAndResolve);
12116
+
12117
+ const timer = setTimeout(cleanupAndResolve, timeout);
12118
+
12119
+ const style = window.getComputedStyle(el);
12120
+ const hasTransition = style.transitionDuration !== '0s';
12121
+ const hasAnimation = style.animationDuration !== '0s';
12122
+
12123
+ if (!hasTransition && !hasAnimation) {
12124
+ cleanupAndResolve();
12125
+ }
12126
+ });
12127
+ };
12128
+
12129
+ NDElement.prototype.transitionOut = function(transitionName) {
12130
+ const exitClass = transitionName + '-exit';
12131
+ const el = this.$element;
12132
+ this.beforeUnmount('transition-exit', async function() {
12133
+ el.classes.add(exitClass);
12134
+ await waitForVisualEnd(el);
12135
+ el.classes.remove(exitClass);
12136
+ });
12137
+ return this;
12138
+ };
12139
+
12140
+ NDElement.prototype.transitionIn = function(transitionName) {
12141
+ const startClass = transitionName + '-enter-from';
12142
+ const endClass = transitionName + '-enter-to';
10735
12143
 
10736
- const normalizeComponentArgs = function(props, children = null) {
10737
- if(props && children) {
10738
- return { props, children };
10739
- }
10740
- if(typeof props !== 'object' || Array.isArray(props) || props === null || props.constructor.name !== 'Object' || props.$hydrate) { // IF it's not a JSON
10741
- return { props: children, children: props }
12144
+ const el = this.$element;
12145
+
12146
+ el.classes.add(startClass);
12147
+
12148
+ this.mounted(() => {
12149
+ requestAnimationFrame(() => {
12150
+ requestAnimationFrame(() => {
12151
+ el.classes.remove(startClass);
12152
+ el.classes.add(endClass);
12153
+
12154
+ waitForVisualEnd(el).then(() => {
12155
+ el.classes.remove(endClass);
12156
+ });
12157
+ });
12158
+ });
12159
+ });
12160
+ return this;
12161
+ };
12162
+
12163
+
12164
+ NDElement.prototype.transition = function (transitionName) {
12165
+ this.transitionIn(transitionName);
12166
+ this.transitionOut(transitionName);
12167
+ return this;
12168
+ };
12169
+
12170
+ NDElement.prototype.animate = function(animationName) {
12171
+ const el = this.$element;
12172
+ el.classes.add(animationName);
12173
+
12174
+ waitForVisualEnd(el).then(() => {
12175
+ el.classes.remove(animationName);
12176
+ });
12177
+
12178
+ return this;
12179
+ };
12180
+
12181
+ ObservableItem.prototype.handleNdAttribute = function(element, attributeName) {
12182
+ if(BOOLEAN_ATTRIBUTES.has(attributeName)) {
12183
+ bindBooleanAttribute(element, attributeName, this);
12184
+ return;
10742
12185
  }
10743
- return { props, children };
12186
+
12187
+ bindAttributeWithObservable(element, attributeName, this);
10744
12188
  };
10745
12189
 
12190
+ ObservableChecker.prototype.handleNdAttribute = ObservableItem.prototype.handleNdAttribute;
12191
+
10746
12192
  const createHtmlElement = (element, _attributes, _children = null) => {
10747
12193
  let { props: attributes, children = null } = normalizeComponentArgs(_attributes, _children);
10748
12194
 
@@ -12274,195 +13720,6 @@ var NativeComponents = (function (exports) {
12274
13720
  return this;
12275
13721
  };
12276
13722
 
12277
- /**
12278
- *
12279
- * @param {ObservableItem} $observable
12280
- * @param {Function} $checker
12281
- * @class ObservableChecker
12282
- */
12283
- function ObservableChecker($observable, $checker) {
12284
- this.observable = $observable;
12285
-
12286
- ObservableItem.call(this);
12287
-
12288
- this.$mutation = $checker;
12289
-
12290
- $observable.subscribe((newValue) => {
12291
- this.$updateWithMutation(newValue);
12292
- });
12293
-
12294
- this.$updateWithMutation($observable.val());
12295
- }
12296
-
12297
- ObservableChecker.prototype = Object.create(ObservableItem.prototype);
12298
- ObservableChecker.prototype.constructor = ObservableChecker;
12299
- ObservableChecker.prototype.__$Observable = true;
12300
- ObservableChecker.prototype.__$isObservableChecker = true;
12301
-
12302
-
12303
- const ObservablePipe = ObservableChecker;
12304
- ObservablePipe.prototype.constructor = ObservablePipe;
12305
-
12306
- ObservableChecker.prototype.$updateWithMutation = function(newValue) {
12307
- newValue = this.$mutation(newValue);
12308
- return this.set(newValue);
12309
- };
12310
-
12311
- NDElement.$getChild = ElementCreator.getChild;
12312
-
12313
- String.prototype.toNdElement = function () {
12314
- return ElementCreator.createStaticTextNode(null, this);
12315
- };
12316
-
12317
- Number.prototype.toNdElement = function () {
12318
- return ElementCreator.createStaticTextNode(null, this.toString());
12319
- };
12320
-
12321
- Element.prototype.toNdElement = function () {
12322
- return this;
12323
- };
12324
- Text.prototype.toNdElement = function () {
12325
- return this;
12326
- };
12327
- Comment.prototype.toNdElement = function () {
12328
- return this;
12329
- };
12330
- Document.prototype.toNdElement = function () {
12331
- return this;
12332
- };
12333
- DocumentFragment.prototype.toNdElement = function () {
12334
- return this;
12335
- };
12336
-
12337
- ObservableItem.prototype.toNdElement = function () {
12338
- return ElementCreator.createObservableNode(null, this);
12339
- };
12340
-
12341
- ObservableChecker.prototype.toNdElement = ObservableItem.prototype.toNdElement;
12342
-
12343
- NDElement.prototype.toNdElement = function () {
12344
- const element = this.$element ?? this.$build?.() ?? this.build?.() ?? null;
12345
- if(this.$attachements) {
12346
- if(!this.$attachements.contains(this.$element)) {
12347
- this.$attachements.append(this.$element);
12348
- }
12349
- return this.$attachements;
12350
- }
12351
- return element;
12352
- };
12353
-
12354
- Array.prototype.toNdElement = function () {
12355
- const fragment = document.createDocumentFragment();
12356
- for(let i = 0, length = this.length; i < length; i++) {
12357
- const child = ElementCreator.getChild(this[i]);
12358
- if(child === null) continue;
12359
- fragment.appendChild(child);
12360
- }
12361
- return fragment;
12362
- };
12363
-
12364
- Function.prototype.toNdElement = function () {
12365
- const child = this;
12366
- return ElementCreator.getChild(child());
12367
- };
12368
-
12369
- /**
12370
- * @param {HTMLElement} el
12371
- * @param {number} timeout
12372
- */
12373
- const waitForVisualEnd = (el, timeout = 1000) => {
12374
- return new Promise((resolve) => {
12375
- let isResolved = false;
12376
-
12377
- const cleanupAndResolve = (e) => {
12378
- if (e && e.target !== el) return;
12379
- if (isResolved) return;
12380
-
12381
- isResolved = true;
12382
- el.removeEventListener('transitionend', cleanupAndResolve);
12383
- el.removeEventListener('animationend', cleanupAndResolve);
12384
- clearTimeout(timer);
12385
- resolve();
12386
- };
12387
-
12388
- el.addEventListener('transitionend', cleanupAndResolve);
12389
- el.addEventListener('animationend', cleanupAndResolve);
12390
-
12391
- const timer = setTimeout(cleanupAndResolve, timeout);
12392
-
12393
- const style = window.getComputedStyle(el);
12394
- const hasTransition = style.transitionDuration !== '0s';
12395
- const hasAnimation = style.animationDuration !== '0s';
12396
-
12397
- if (!hasTransition && !hasAnimation) {
12398
- cleanupAndResolve();
12399
- }
12400
- });
12401
- };
12402
-
12403
- NDElement.prototype.transitionOut = function(transitionName) {
12404
- const exitClass = transitionName + '-exit';
12405
- const el = this.$element;
12406
- this.beforeUnmount('transition-exit', async function() {
12407
- el.classes.add(exitClass);
12408
- await waitForVisualEnd(el);
12409
- el.classes.remove(exitClass);
12410
- });
12411
- return this;
12412
- };
12413
-
12414
- NDElement.prototype.transitionIn = function(transitionName) {
12415
- const startClass = transitionName + '-enter-from';
12416
- const endClass = transitionName + '-enter-to';
12417
-
12418
- const el = this.$element;
12419
-
12420
- el.classes.add(startClass);
12421
-
12422
- this.mounted(() => {
12423
- requestAnimationFrame(() => {
12424
- requestAnimationFrame(() => {
12425
- el.classes.remove(startClass);
12426
- el.classes.add(endClass);
12427
-
12428
- waitForVisualEnd(el).then(() => {
12429
- el.classes.remove(endClass);
12430
- });
12431
- });
12432
- });
12433
- });
12434
- return this;
12435
- };
12436
-
12437
-
12438
- NDElement.prototype.transition = function (transitionName) {
12439
- this.transitionIn(transitionName);
12440
- this.transitionOut(transitionName);
12441
- return this;
12442
- };
12443
-
12444
- NDElement.prototype.animate = function(animationName) {
12445
- const el = this.$element;
12446
- el.classes.add(animationName);
12447
-
12448
- waitForVisualEnd(el).then(() => {
12449
- el.classes.remove(animationName);
12450
- });
12451
-
12452
- return this;
12453
- };
12454
-
12455
- ObservableItem.prototype.handleNdAttribute = function(element, attributeName) {
12456
- if(BOOLEAN_ATTRIBUTES.has(attributeName)) {
12457
- bindBooleanAttribute(element, attributeName, this);
12458
- return;
12459
- }
12460
-
12461
- bindAttributeWithObservable(element, attributeName, this);
12462
- };
12463
-
12464
- ObservableChecker.prototype.handleNdAttribute = ObservableItem.prototype.handleNdAttribute;
12465
-
12466
13723
  function ColumnGroup(title, props = {}) {
12467
13724
  this.$description = {
12468
13725
  header: title,