@mintjamsinc/ichigojs 0.1.1 → 0.1.3
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/ichigo.esm.js +216 -41
- package/dist/ichigo.esm.js.map +1 -1
- package/dist/ichigo.esm.min.js +1 -1
- package/dist/ichigo.esm.min.js.map +1 -1
- package/dist/ichigo.umd.js +216 -41
- package/dist/ichigo.umd.js.map +1 -1
- package/dist/ichigo.umd.min.js +1 -1
- package/dist/ichigo.umd.min.js.map +1 -1
- package/dist/types/ichigo/VBindingsInit.d.ts +5 -0
- package/dist/types/ichigo/VNode.d.ts +11 -1
- package/dist/types/ichigo/VNodeInit.d.ts +5 -0
- package/dist/types/ichigo/util/ReactiveProxy.d.ts +6 -5
- package/dist/types/ichigo/util/VLogManager.d.ts +15 -0
- package/dist/types/ichigo/util/VLogger.d.ts +31 -0
- package/package.json +2 -2
package/dist/ichigo.esm.js
CHANGED
@@ -7044,34 +7044,46 @@ class VBindDirective {
|
|
7044
7044
|
*/
|
7045
7045
|
class ReactiveProxy {
|
7046
7046
|
/**
|
7047
|
-
* A WeakMap to store the
|
7048
|
-
* This
|
7047
|
+
* A WeakMap to store the proxy for each target object and path combination.
|
7048
|
+
* This prevents creating multiple proxies for the same object accessed from different paths.
|
7049
7049
|
*/
|
7050
|
-
static
|
7050
|
+
static proxyCache = new WeakMap();
|
7051
7051
|
/**
|
7052
7052
|
* Creates a reactive proxy for the given object.
|
7053
7053
|
* The proxy will call the onChange callback whenever a property is modified.
|
7054
7054
|
*
|
7055
7055
|
* @param target The object to make reactive.
|
7056
|
-
* @param onChange Callback function to call when the object changes. Receives the changed
|
7056
|
+
* @param onChange Callback function to call when the object changes. Receives the full path of the changed property.
|
7057
|
+
* @param path The current path in the object tree (used internally for nested objects).
|
7057
7058
|
* @returns A reactive proxy of the target object.
|
7058
7059
|
*/
|
7059
|
-
static create(target, onChange) {
|
7060
|
+
static create(target, onChange, path = '') {
|
7060
7061
|
// If the target is not an object or is null, return it as-is
|
7061
7062
|
if (typeof target !== 'object' || target === null) {
|
7062
7063
|
return target;
|
7063
7064
|
}
|
7064
|
-
//
|
7065
|
-
|
7066
|
-
|
7065
|
+
// Check if we already have a proxy for this target with this path
|
7066
|
+
let pathMap = this.proxyCache.get(target);
|
7067
|
+
if (pathMap) {
|
7068
|
+
const existingProxy = pathMap.get(path);
|
7069
|
+
if (existingProxy) {
|
7070
|
+
return existingProxy;
|
7071
|
+
}
|
7072
|
+
}
|
7073
|
+
else {
|
7074
|
+
pathMap = new Map();
|
7075
|
+
this.proxyCache.set(target, pathMap);
|
7067
7076
|
}
|
7068
|
-
// Create the proxy
|
7077
|
+
// Create the proxy with path captured in closure
|
7069
7078
|
const proxy = new Proxy(target, {
|
7070
7079
|
get(obj, key) {
|
7071
7080
|
const value = Reflect.get(obj, key);
|
7072
7081
|
// If the value is an object or array, make it reactive too
|
7073
7082
|
if (typeof value === 'object' && value !== null) {
|
7074
|
-
|
7083
|
+
// Build the nested path
|
7084
|
+
const keyStr = String(key);
|
7085
|
+
const nestedPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
7086
|
+
return ReactiveProxy.create(value, onChange, nestedPath);
|
7075
7087
|
}
|
7076
7088
|
// For arrays, intercept mutation methods
|
7077
7089
|
if (Array.isArray(obj) && typeof value === 'function') {
|
@@ -7079,7 +7091,7 @@ class ReactiveProxy {
|
|
7079
7091
|
if (arrayMutationMethods.includes(key)) {
|
7080
7092
|
return function (...args) {
|
7081
7093
|
const result = value.apply(this, args);
|
7082
|
-
onChange();
|
7094
|
+
onChange(path || undefined);
|
7083
7095
|
return result;
|
7084
7096
|
};
|
7085
7097
|
}
|
@@ -7091,18 +7103,22 @@ class ReactiveProxy {
|
|
7091
7103
|
const result = Reflect.set(obj, key, value);
|
7092
7104
|
// Only trigger onChange if the value actually changed
|
7093
7105
|
if (oldValue !== value) {
|
7094
|
-
|
7106
|
+
const keyStr = String(key);
|
7107
|
+
const fullPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
7108
|
+
onChange(fullPath);
|
7095
7109
|
}
|
7096
7110
|
return result;
|
7097
7111
|
},
|
7098
7112
|
deleteProperty(obj, key) {
|
7099
7113
|
const result = Reflect.deleteProperty(obj, key);
|
7100
|
-
|
7114
|
+
const keyStr = String(key);
|
7115
|
+
const fullPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
7116
|
+
onChange(fullPath);
|
7101
7117
|
return result;
|
7102
7118
|
}
|
7103
7119
|
});
|
7104
|
-
//
|
7105
|
-
|
7120
|
+
// Cache the proxy for this path
|
7121
|
+
pathMap.set(path, proxy);
|
7106
7122
|
return proxy;
|
7107
7123
|
}
|
7108
7124
|
/**
|
@@ -7112,7 +7128,7 @@ class ReactiveProxy {
|
|
7112
7128
|
* @returns True if the object is a reactive proxy, false otherwise.
|
7113
7129
|
*/
|
7114
7130
|
static isReactive(obj) {
|
7115
|
-
return this.
|
7131
|
+
return this.proxyCache.has(obj);
|
7116
7132
|
}
|
7117
7133
|
/**
|
7118
7134
|
* Unwraps a reactive proxy to get the original object.
|
@@ -7147,10 +7163,18 @@ class VBindings {
|
|
7147
7163
|
* The change tracker, if any.
|
7148
7164
|
*/
|
7149
7165
|
#onChange;
|
7166
|
+
/**
|
7167
|
+
* The logger instance.
|
7168
|
+
*/
|
7169
|
+
#logger;
|
7150
7170
|
/**
|
7151
7171
|
* The set of changed identifiers.
|
7152
7172
|
*/
|
7153
7173
|
#changes = new Set();
|
7174
|
+
/**
|
7175
|
+
* Cache for array lengths to detect length changes when the same object reference is used.
|
7176
|
+
*/
|
7177
|
+
#lengthCache = new Map();
|
7154
7178
|
/**
|
7155
7179
|
* Creates a new instance of VBindings.
|
7156
7180
|
* @param parent The parent bindings, if any.
|
@@ -7158,6 +7182,10 @@ class VBindings {
|
|
7158
7182
|
constructor(args = {}) {
|
7159
7183
|
this.#parent = args.parent;
|
7160
7184
|
this.#onChange = args.onChange;
|
7185
|
+
this.#logger = args.vApplication?.logManager.getLogger('VBindings');
|
7186
|
+
if (this.#logger?.isDebugEnabled) {
|
7187
|
+
this.#logger.debug(`VBindings created. Parent: ${this.#parent ? 'yes' : 'no'}`);
|
7188
|
+
}
|
7161
7189
|
this.#local = new Proxy({}, {
|
7162
7190
|
get: (obj, key) => {
|
7163
7191
|
if (Reflect.has(obj, key)) {
|
@@ -7178,14 +7206,37 @@ class VBindings {
|
|
7178
7206
|
let newValue = value;
|
7179
7207
|
if (typeof value === 'object' && value !== null) {
|
7180
7208
|
// Wrap objects/arrays with reactive proxy, tracking the root key
|
7181
|
-
newValue = ReactiveProxy.create(value, () => {
|
7182
|
-
|
7183
|
-
|
7184
|
-
|
7209
|
+
newValue = ReactiveProxy.create(value, (changedPath) => {
|
7210
|
+
let path = '';
|
7211
|
+
for (const part of changedPath?.split('.') || []) {
|
7212
|
+
path = path ? `${path}.${part}` : part;
|
7213
|
+
this.#logger?.debug(`Binding changed: ${path}`);
|
7214
|
+
this.#changes.add(path);
|
7215
|
+
}
|
7216
|
+
this.#onChange?.(changedPath);
|
7217
|
+
}, key);
|
7185
7218
|
}
|
7186
7219
|
const oldValue = Reflect.get(target, key);
|
7187
7220
|
const result = Reflect.set(target, key, newValue);
|
7188
|
-
|
7221
|
+
// Detect changes
|
7222
|
+
let hasChanged = oldValue !== newValue;
|
7223
|
+
// Special handling for arrays: check length changes even if same object reference
|
7224
|
+
if (Array.isArray(newValue)) {
|
7225
|
+
const cachedLength = this.#lengthCache.get(key);
|
7226
|
+
const currentLength = newValue.length;
|
7227
|
+
if (!hasChanged && cachedLength !== undefined && cachedLength !== currentLength) {
|
7228
|
+
hasChanged = true;
|
7229
|
+
}
|
7230
|
+
this.#lengthCache.set(key, currentLength);
|
7231
|
+
}
|
7232
|
+
if (hasChanged) {
|
7233
|
+
if (this.#logger?.isDebugEnabled) {
|
7234
|
+
const oldValueString = typeof oldValue === 'string' ? `"${oldValue}"` : JSON.stringify(oldValue) || 'undefined';
|
7235
|
+
const newValueString = typeof newValue === 'string' ? `"${newValue}"` : JSON.stringify(newValue) || 'undefined';
|
7236
|
+
const oldValuePreview = oldValueString.length > 100 ? `${oldValueString.substring(0, 100)}...` : oldValueString;
|
7237
|
+
const newValuePreview = newValueString.length > 100 ? `${newValueString.substring(0, 100)}...` : newValueString;
|
7238
|
+
this.#logger.debug(`Binding set on ${target === obj ? 'local' : 'parent'}: ${key}: ${oldValuePreview} -> ${newValuePreview}`);
|
7239
|
+
}
|
7189
7240
|
this.#changes.add(key);
|
7190
7241
|
this.#onChange?.(key);
|
7191
7242
|
}
|
@@ -7193,6 +7244,7 @@ class VBindings {
|
|
7193
7244
|
},
|
7194
7245
|
deleteProperty: (obj, key) => {
|
7195
7246
|
const result = Reflect.deleteProperty(obj, key);
|
7247
|
+
this.#logger?.debug(`Binding deleted: ${key}`);
|
7196
7248
|
this.#changes.add(key);
|
7197
7249
|
this.#onChange?.(key);
|
7198
7250
|
return result;
|
@@ -7541,6 +7593,11 @@ class VNode {
|
|
7541
7593
|
* The data bindings associated with this virtual node, if any.
|
7542
7594
|
*/
|
7543
7595
|
#bindings;
|
7596
|
+
/**
|
7597
|
+
* The initial set of identifiers that this node depends on.
|
7598
|
+
* This is optional and may be undefined if there are no dependent identifiers.
|
7599
|
+
*/
|
7600
|
+
#initDependentIdentifiers;
|
7544
7601
|
/**
|
7545
7602
|
* An evaluator for text nodes that contain expressions in {{...}}.
|
7546
7603
|
* This is used to dynamically update the text content based on data bindings.
|
@@ -7585,6 +7642,7 @@ class VNode {
|
|
7585
7642
|
this.#nodeName = args.node.nodeName;
|
7586
7643
|
this.#parentVNode = args.parentVNode;
|
7587
7644
|
this.#bindings = args.bindings;
|
7645
|
+
this.#initDependentIdentifiers = args.dependentIdentifiers;
|
7588
7646
|
this.#parentVNode?.addChild(this);
|
7589
7647
|
// If the node is a text node, check for expressions and create a text evaluator
|
7590
7648
|
if (this.#nodeType === Node.TEXT_NODE) {
|
@@ -7736,6 +7794,8 @@ class VNode {
|
|
7736
7794
|
}
|
7737
7795
|
// Collect identifiers from text evaluator and directives
|
7738
7796
|
const ids = [];
|
7797
|
+
// Include initial dependent identifiers, if any
|
7798
|
+
ids.push(...this.#initDependentIdentifiers ?? []);
|
7739
7799
|
// If this is a text node with a text evaluator, include its identifiers
|
7740
7800
|
if (this.#textEvaluator) {
|
7741
7801
|
ids.push(...this.#textEvaluator.identifiers);
|
@@ -7767,6 +7827,29 @@ class VNode {
|
|
7767
7827
|
this.#preparableIdentifiers = preparableIdentifiers.length === 0 ? [] : [...new Set(preparableIdentifiers)];
|
7768
7828
|
return this.#preparableIdentifiers;
|
7769
7829
|
}
|
7830
|
+
/**
|
7831
|
+
* The DOM path of this virtual node.
|
7832
|
+
* This is a string representation of the path from the root to this node,
|
7833
|
+
* using the node names and their indices among siblings with the same name.
|
7834
|
+
* For example: "DIV[0]/SPAN[1]/#text[0]"
|
7835
|
+
* @return The DOM path as a string.
|
7836
|
+
*/
|
7837
|
+
get domPath() {
|
7838
|
+
const path = [];
|
7839
|
+
let node = this;
|
7840
|
+
while (node) {
|
7841
|
+
if (node.parentVNode && node.parentVNode.childVNodes) {
|
7842
|
+
const siblings = node.parentVNode.childVNodes.filter(v => v.nodeName === node?.nodeName);
|
7843
|
+
const index = siblings.indexOf(node);
|
7844
|
+
path.unshift(`${node.nodeName}[${index}]`);
|
7845
|
+
}
|
7846
|
+
else {
|
7847
|
+
path.unshift(node.nodeName);
|
7848
|
+
}
|
7849
|
+
node = node.parentVNode;
|
7850
|
+
}
|
7851
|
+
return path.join('/');
|
7852
|
+
}
|
7770
7853
|
/**
|
7771
7854
|
* Updates the virtual node and its children based on the current bindings.
|
7772
7855
|
* This method evaluates any expressions in text nodes and applies effectors from directives.
|
@@ -7863,24 +7946,46 @@ class VNode {
|
|
7863
7946
|
/**
|
7864
7947
|
* Adds a dependent virtual node that relies on this node's bindings.
|
7865
7948
|
* @param dependent The dependent virtual node to add.
|
7949
|
+
* @param dependentIdentifiers The identifiers that the dependent node relies on.
|
7950
|
+
* If not provided, the dependent node's own identifiers will be used.
|
7866
7951
|
* @returns A list of closers to unregister the dependency, or undefined if no dependency was added.
|
7867
7952
|
*/
|
7868
|
-
addDependent(dependent) {
|
7953
|
+
addDependent(dependent, dependentIdentifiers = undefined) {
|
7869
7954
|
// List of closers to unregister the dependency
|
7870
7955
|
const closers = [];
|
7871
|
-
//
|
7872
|
-
|
7873
|
-
|
7874
|
-
|
7956
|
+
// If dependent identifiers are not provided, use the dependent node's own identifiers
|
7957
|
+
if (!dependentIdentifiers) {
|
7958
|
+
dependentIdentifiers = [...dependent.dependentIdentifiers];
|
7959
|
+
}
|
7960
|
+
// Prepare alternative identifiers by stripping array indices (e.g., "items[0]" -> "items")
|
7961
|
+
const allDeps = new Set();
|
7962
|
+
dependentIdentifiers.forEach(id => {
|
7963
|
+
allDeps.add(id);
|
7964
|
+
const idx = id.indexOf('[');
|
7965
|
+
if (idx !== -1) {
|
7966
|
+
allDeps.add(id.substring(0, idx));
|
7967
|
+
}
|
7968
|
+
});
|
7969
|
+
// Get this node's identifiers
|
7970
|
+
const thisIds = [...this.preparableIdentifiers];
|
7971
|
+
if (this.#bindings) {
|
7972
|
+
thisIds.push(...this.#bindings?.raw ? Object.keys(this.#bindings.raw) : []);
|
7875
7973
|
}
|
7876
7974
|
// If the dependent node has an identifier in this node's identifiers, add it as a dependency
|
7877
|
-
if (
|
7975
|
+
if ([...allDeps].some(id => thisIds.includes(id))) {
|
7878
7976
|
// If the dependencies list is not initialized, create it
|
7879
7977
|
if (!this.#dependents) {
|
7880
7978
|
this.#dependents = [];
|
7881
7979
|
}
|
7882
7980
|
// Add the dependent node to the list
|
7883
7981
|
this.#dependents.push(dependent);
|
7982
|
+
// Remove the matched identifiers from the dependent node's identifiers to avoid duplicate dependencies
|
7983
|
+
thisIds.forEach(id => {
|
7984
|
+
const idx = dependentIdentifiers.indexOf(id);
|
7985
|
+
if (idx !== -1) {
|
7986
|
+
dependentIdentifiers.splice(idx, 1);
|
7987
|
+
}
|
7988
|
+
});
|
7884
7989
|
// Create a closer to unregister the dependency
|
7885
7990
|
closers.push({
|
7886
7991
|
close: () => {
|
@@ -7893,7 +7998,7 @@ class VNode {
|
|
7893
7998
|
});
|
7894
7999
|
}
|
7895
8000
|
// Recursively add the dependency to the parent node, if any
|
7896
|
-
this.#parentVNode?.addDependent(dependent)?.forEach(closer => closers.push(closer));
|
8001
|
+
this.#parentVNode?.addDependent(dependent, dependentIdentifiers)?.forEach(closer => closers.push(closer));
|
7897
8002
|
// Return a closer to unregister the dependency
|
7898
8003
|
return closers.length > 0 ? closers : undefined;
|
7899
8004
|
}
|
@@ -8565,7 +8670,8 @@ class VForDirective {
|
|
8565
8670
|
node: clone,
|
8566
8671
|
vApplication: this.#vNode.vApplication,
|
8567
8672
|
parentVNode: this.#vNode.parentVNode,
|
8568
|
-
bindings
|
8673
|
+
bindings,
|
8674
|
+
dependentIdentifiers: [`${this.#sourceName}[${context.index}]`]
|
8569
8675
|
});
|
8570
8676
|
return vNode;
|
8571
8677
|
}
|
@@ -8852,10 +8958,13 @@ class VModelDirective {
|
|
8852
8958
|
}
|
8853
8959
|
// .number modifier: convert to number
|
8854
8960
|
if (this.#modifiers.has('number')) {
|
8855
|
-
|
8856
|
-
|
8857
|
-
|
8858
|
-
|
8961
|
+
// Skip conversion if the value is empty string
|
8962
|
+
if (result !== '') {
|
8963
|
+
const parsed = Number(result);
|
8964
|
+
// Only convert if it's a valid number
|
8965
|
+
if (!isNaN(parsed)) {
|
8966
|
+
result = parsed;
|
8967
|
+
}
|
8859
8968
|
}
|
8860
8969
|
}
|
8861
8970
|
return result;
|
@@ -9383,49 +9492,105 @@ var LogLevel;
|
|
9383
9492
|
})(LogLevel || (LogLevel = {}));
|
9384
9493
|
|
9385
9494
|
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
9495
|
+
/**
|
9496
|
+
* A simple logger class for virtual applications.
|
9497
|
+
*/
|
9386
9498
|
class VLogger {
|
9499
|
+
/** The name of the logger. */
|
9387
9500
|
#name;
|
9501
|
+
/** The log manager instance. */
|
9388
9502
|
#logManager;
|
9389
9503
|
constructor(name, logManager) {
|
9390
9504
|
this.#name = name;
|
9391
9505
|
this.#logManager = logManager;
|
9392
9506
|
}
|
9507
|
+
/**
|
9508
|
+
* Indicates whether the debug level is enabled.
|
9509
|
+
*/
|
9510
|
+
get isDebugEnabled() {
|
9511
|
+
return [LogLevel.DEBUG].includes(this.#logManager.logLevel);
|
9512
|
+
}
|
9513
|
+
/**
|
9514
|
+
* Indicates whether the info level is enabled.
|
9515
|
+
*/
|
9516
|
+
get isInfoEnabled() {
|
9517
|
+
return [LogLevel.DEBUG, LogLevel.INFO].includes(this.#logManager.logLevel);
|
9518
|
+
}
|
9519
|
+
/**
|
9520
|
+
* Indicates whether the warn level is enabled.
|
9521
|
+
*/
|
9522
|
+
get isWarnEnabled() {
|
9523
|
+
return [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN].includes(this.#logManager.logLevel);
|
9524
|
+
}
|
9525
|
+
/**
|
9526
|
+
* Logs a debug message.
|
9527
|
+
* @param message The message to log.
|
9528
|
+
*/
|
9393
9529
|
debug(message) {
|
9394
|
-
if (!
|
9530
|
+
if (!this.isDebugEnabled) {
|
9395
9531
|
return;
|
9396
9532
|
}
|
9397
9533
|
console.debug(`[${this.#name}] ${LogLevel.DEBUG}: ${message}`);
|
9398
9534
|
}
|
9535
|
+
/**
|
9536
|
+
* Logs an info message.
|
9537
|
+
* @param message The message to log.
|
9538
|
+
*/
|
9399
9539
|
info(message) {
|
9400
|
-
if (!
|
9540
|
+
if (!this.isInfoEnabled) {
|
9401
9541
|
return;
|
9402
9542
|
}
|
9403
9543
|
console.info(`[${this.#name}] ${LogLevel.INFO}: ${message}`);
|
9404
9544
|
}
|
9545
|
+
/**
|
9546
|
+
* Logs a warn message.
|
9547
|
+
* @param message The message to log.
|
9548
|
+
*/
|
9405
9549
|
warn(message) {
|
9406
|
-
if (!
|
9550
|
+
if (!this.isWarnEnabled) {
|
9407
9551
|
return;
|
9408
9552
|
}
|
9409
9553
|
console.warn(`[${this.#name}] ${LogLevel.WARN}: ${message}`);
|
9410
9554
|
}
|
9555
|
+
/**
|
9556
|
+
* Logs an error message.
|
9557
|
+
* @param message The message to log.
|
9558
|
+
*/
|
9411
9559
|
error(message) {
|
9412
9560
|
console.error(`[${this.#name}] ${LogLevel.ERROR}: ${message}`);
|
9413
9561
|
}
|
9414
9562
|
}
|
9415
9563
|
|
9416
9564
|
// Copyright (c) 2025 MintJams Inc. Licensed under MIT License.
|
9565
|
+
/**
|
9566
|
+
* Manages loggers and their log levels.
|
9567
|
+
*/
|
9417
9568
|
class VLogManager {
|
9569
|
+
/** The current log level. */
|
9418
9570
|
#logLevel;
|
9571
|
+
/** A map of logger instances by name. */
|
9419
9572
|
#loggers = new Map();
|
9420
9573
|
constructor(logLevel = LogLevel.INFO) {
|
9421
9574
|
this.#logLevel = logLevel;
|
9422
9575
|
}
|
9576
|
+
/**
|
9577
|
+
* Sets the log level for all loggers.
|
9578
|
+
* @param level The log level to set.
|
9579
|
+
*/
|
9423
9580
|
set logLevel(level) {
|
9424
9581
|
this.#logLevel = level;
|
9425
9582
|
}
|
9583
|
+
/**
|
9584
|
+
* Gets the current log level.
|
9585
|
+
*/
|
9426
9586
|
get logLevel() {
|
9427
9587
|
return this.#logLevel;
|
9428
9588
|
}
|
9589
|
+
/**
|
9590
|
+
* Gets a logger by name, creating it if it doesn't exist.
|
9591
|
+
* @param name The name of the logger.
|
9592
|
+
* @returns The logger instance.
|
9593
|
+
*/
|
9429
9594
|
getLogger(name) {
|
9430
9595
|
if (this.#loggers.has(name)) {
|
9431
9596
|
return this.#loggers.get(name);
|
@@ -9595,9 +9760,10 @@ class VApplication {
|
|
9595
9760
|
#initializeBindings() {
|
9596
9761
|
// Create bindings with change tracking
|
9597
9762
|
this.#bindings = new VBindings({
|
9598
|
-
onChange: (
|
9763
|
+
onChange: (identifier) => {
|
9599
9764
|
this.#scheduleUpdate();
|
9600
|
-
}
|
9765
|
+
},
|
9766
|
+
vApplication: this
|
9601
9767
|
});
|
9602
9768
|
// Inject utility methods into bindings
|
9603
9769
|
this.#bindings.set('$nextTick', (callback) => this.#nextTick(callback));
|
@@ -9659,6 +9825,15 @@ class VApplication {
|
|
9659
9825
|
}
|
9660
9826
|
const computed = new Set();
|
9661
9827
|
const processing = new Set();
|
9828
|
+
// Gather all changed identifiers, including parent properties for array items
|
9829
|
+
const allChanges = new Set();
|
9830
|
+
this.#bindings?.changes.forEach(id => {
|
9831
|
+
allChanges.add(id);
|
9832
|
+
const idx = id.indexOf('[');
|
9833
|
+
if (idx !== -1) {
|
9834
|
+
allChanges.add(id.substring(0, idx));
|
9835
|
+
}
|
9836
|
+
});
|
9662
9837
|
// Helper function to recursively compute a property
|
9663
9838
|
const compute = (key) => {
|
9664
9839
|
// Skip if already computed in this update cycle
|
@@ -9674,7 +9849,7 @@ class VApplication {
|
|
9674
9849
|
// Get the dependencies for this computed property
|
9675
9850
|
const deps = this.#computedDependencies[key] || [];
|
9676
9851
|
// If none of the dependencies have changed, skip recomputation
|
9677
|
-
if (!deps.some(dep =>
|
9852
|
+
if (!deps.some(dep => allChanges.has(dep))) {
|
9678
9853
|
computed.add(key);
|
9679
9854
|
return;
|
9680
9855
|
}
|
@@ -9692,7 +9867,7 @@ class VApplication {
|
|
9692
9867
|
// Track if the computed value actually changed
|
9693
9868
|
if (oldValue !== newValue) {
|
9694
9869
|
this.#bindings?.set(key, newValue);
|
9695
|
-
|
9870
|
+
allChanges.add(key);
|
9696
9871
|
}
|
9697
9872
|
}
|
9698
9873
|
catch (error) {
|
@@ -9704,7 +9879,7 @@ class VApplication {
|
|
9704
9879
|
// Find all computed properties that need to be recomputed
|
9705
9880
|
for (const [key, deps] of Object.entries(this.#computedDependencies)) {
|
9706
9881
|
// Check if any dependency has changed
|
9707
|
-
if (deps.some(dep =>
|
9882
|
+
if (deps.some(dep => allChanges.has(dep))) {
|
9708
9883
|
compute(key);
|
9709
9884
|
}
|
9710
9885
|
}
|