@mintjamsinc/ichigojs 0.1.88 → 0.1.90
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/README.md +21 -0
- package/dist/ichigo.cjs +290 -61
- package/dist/ichigo.cjs.map +1 -1
- package/dist/ichigo.esm.js +290 -61
- package/dist/ichigo.esm.js.map +1 -1
- package/dist/ichigo.esm.min.js +1 -1
- package/dist/ichigo.min.cjs +1 -1
- package/dist/ichigo.umd.js +290 -61
- package/dist/ichigo.umd.js.map +1 -1
- package/dist/ichigo.umd.min.js +1 -1
- package/dist/types/ichigo/VBindings.d.ts +10 -1
- package/dist/types/ichigo/VNode.d.ts +9 -0
- package/dist/types/ichigo/VTextEvaluator.d.ts +3 -3
- package/dist/types/ichigo/util/ExpressionEvaluator.d.ts +9 -8
- package/dist/types/ichigo/util/ExpressionUtils.d.ts +10 -0
- package/dist/types/ichigo/util/ReactiveProxy.d.ts +42 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -219,6 +219,27 @@ first), and a computed whose recomputed value is unchanged does not trigger DOM
|
|
|
219
219
|
updates or watchers that depend on it. Computed→computed chains resolve
|
|
220
220
|
automatically and independently of declaration order.
|
|
221
221
|
|
|
222
|
+
**Dependencies:**
|
|
223
|
+
|
|
224
|
+
A computed property depends on the properties its source names (`this.firstName`,
|
|
225
|
+
including those read by the methods it calls) and on everything it actually read
|
|
226
|
+
the last time it ran. Reads that only happen at run time, such as a property
|
|
227
|
+
looked up by a key, are therefore followed too, and the record is replaced on
|
|
228
|
+
every run, so a computed that switches between branches follows the branch it
|
|
229
|
+
took:
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
computed: {
|
|
233
|
+
selected() {
|
|
234
|
+
// `this[key]` names no property in the source; the reads are recorded when it runs
|
|
235
|
+
return ['upper', 'lower', 'digit'].filter(key => this[key]);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The same holds for template bindings that call a method, such as
|
|
241
|
+
`:disabled="isLast('upper')"` where `isLast` reads `this[key]`.
|
|
242
|
+
|
|
222
243
|
A computed property can also be defined as an object with both a `get` and a
|
|
223
244
|
`set` function. This makes it writable, so it can be used as a `v-model` target
|
|
224
245
|
or assigned to directly. Reads go through `get`, while assignments are routed
|
package/dist/ichigo.cjs
CHANGED
|
@@ -6813,6 +6813,13 @@
|
|
|
6813
6813
|
* through a reactive proxy. Nested calls record into the innermost set only;
|
|
6814
6814
|
* the previous set is restored when fn returns or throws.
|
|
6815
6815
|
*
|
|
6816
|
+
* The set is kept closed under ancestors: whenever a path is recorded, every
|
|
6817
|
+
* path above it ("items" and "items[2]" for "items[2].name") is in the set as
|
|
6818
|
+
* well. A read reached through a proxy records them on its way down; a read
|
|
6819
|
+
* that starts partway (a proxy a computed returned, a v-for item) has them
|
|
6820
|
+
* added when it is recorded. changeAffectsRead relies on this to answer with
|
|
6821
|
+
* one lookup.
|
|
6822
|
+
*
|
|
6816
6823
|
* @param paths The set that receives the paths read.
|
|
6817
6824
|
* @param fn The function to run.
|
|
6818
6825
|
* @returns The value fn returns.
|
|
@@ -6827,6 +6834,57 @@
|
|
|
6827
6834
|
this.tracker = previous;
|
|
6828
6835
|
}
|
|
6829
6836
|
}
|
|
6837
|
+
/**
|
|
6838
|
+
* Records a read made outside any proxy while track() runs: a binding read by
|
|
6839
|
+
* name (`this.flag`, `this[key]`), which the bindings report here. Reads below
|
|
6840
|
+
* a binding are recorded by the proxy itself.
|
|
6841
|
+
*
|
|
6842
|
+
* @param path The binding name read.
|
|
6843
|
+
*/
|
|
6844
|
+
static recordRead(path) {
|
|
6845
|
+
this.tracker?.add(path);
|
|
6846
|
+
}
|
|
6847
|
+
/**
|
|
6848
|
+
* Records a read made through a proxy while track() runs: the member `key` of
|
|
6849
|
+
* the object at `containerPath`. The container's own path and its ancestors are
|
|
6850
|
+
* added first when they are missing, which happens when the proxy was not
|
|
6851
|
+
* reached from a root binding during this run (see track()). A container that
|
|
6852
|
+
* is already recorded has its ancestors recorded too, so the common case is one
|
|
6853
|
+
* lookup.
|
|
6854
|
+
*
|
|
6855
|
+
* @param containerPath The source path of the object read from ("" for a root).
|
|
6856
|
+
* @param key The property key read.
|
|
6857
|
+
*/
|
|
6858
|
+
static recordProxyRead(containerPath, key) {
|
|
6859
|
+
const tracker = this.tracker;
|
|
6860
|
+
if (containerPath && !tracker.has(containerPath)) {
|
|
6861
|
+
for (let i = 1; i < containerPath.length; i++) {
|
|
6862
|
+
const c = containerPath[i];
|
|
6863
|
+
if (c === '.' || c === '[') {
|
|
6864
|
+
tracker.add(containerPath.substring(0, i));
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
tracker.add(containerPath);
|
|
6868
|
+
}
|
|
6869
|
+
tracker.add(this.joinPath(containerPath, key));
|
|
6870
|
+
}
|
|
6871
|
+
/**
|
|
6872
|
+
* Checks whether a change touches something read during a tracked run (see
|
|
6873
|
+
* track()): the path read was written, or an object or array holding it was
|
|
6874
|
+
* replaced. A change below a path read does not count, since a read of the
|
|
6875
|
+
* changed member itself would have been recorded too; this keeps a read of a
|
|
6876
|
+
* whole collection ("items") from matching a write to one element.
|
|
6877
|
+
*
|
|
6878
|
+
* The set holds the ancestors of every path read, so a replaced container is
|
|
6879
|
+
* found by the same lookup as the path itself.
|
|
6880
|
+
*
|
|
6881
|
+
* @param changePath The path that was changed (e.g., "items[2].name")
|
|
6882
|
+
* @param readPaths The paths read during the tracked run
|
|
6883
|
+
* @returns True if the change touches a path read
|
|
6884
|
+
*/
|
|
6885
|
+
static changeAffectsRead(changePath, readPaths) {
|
|
6886
|
+
return readPaths.has(changePath);
|
|
6887
|
+
}
|
|
6830
6888
|
/**
|
|
6831
6889
|
* The `Object.prototype.toString` tags of the *only* value types we deeply
|
|
6832
6890
|
* proxy. Everything else is returned untouched (raw).
|
|
@@ -6918,7 +6976,7 @@
|
|
|
6918
6976
|
// Function-valued members (push, Map#get, …) are accesses to behaviour,
|
|
6919
6977
|
// not data, so they are left out.
|
|
6920
6978
|
if (ReactiveProxy.tracker && typeof key !== 'symbol' && typeof value !== 'function') {
|
|
6921
|
-
ReactiveProxy.
|
|
6979
|
+
ReactiveProxy.recordProxyRead(path, key);
|
|
6922
6980
|
}
|
|
6923
6981
|
// If the value is an object or array, make it reactive too
|
|
6924
6982
|
if (typeof value === 'object' && value !== null) {
|
|
@@ -7337,6 +7395,18 @@
|
|
|
7337
7395
|
});
|
|
7338
7396
|
return Array.from(identifiers);
|
|
7339
7397
|
}
|
|
7398
|
+
/**
|
|
7399
|
+
* Whether an expression refers to one of the application's methods, judged by the
|
|
7400
|
+
* identifiers extractIdentifiers returned for it. A called method may read bindings
|
|
7401
|
+
* the source analysis cannot name (e.g. `this[key]`), so such an expression records
|
|
7402
|
+
* its reads when it runs.
|
|
7403
|
+
* @param identifiers The identifiers extracted from the expression.
|
|
7404
|
+
* @param functionDependencies The application's methods and their dependencies.
|
|
7405
|
+
* @returns True if an identifier names a method.
|
|
7406
|
+
*/
|
|
7407
|
+
static callsFunction(identifiers, functionDependencies) {
|
|
7408
|
+
return identifiers.some(id => Object.prototype.hasOwnProperty.call(functionDependencies, id));
|
|
7409
|
+
}
|
|
7340
7410
|
/**
|
|
7341
7411
|
* Returns the binding name an identifier chain starts with
|
|
7342
7412
|
* (e.g. "items" for "items[0].name" or "user.name").
|
|
@@ -7735,26 +7805,27 @@
|
|
|
7735
7805
|
*/
|
|
7736
7806
|
additionalContext;
|
|
7737
7807
|
/**
|
|
7738
|
-
* Whether
|
|
7739
|
-
*
|
|
7808
|
+
* Whether the paths read during evaluation are recorded: when an identifier has an
|
|
7809
|
+
* index known only at run time ("items[*].name"), or when the expression calls a
|
|
7810
|
+
* method, whose reads (`this[key]` among them) the source analysis may not see.
|
|
7740
7811
|
*/
|
|
7741
|
-
|
|
7812
|
+
tracksReads;
|
|
7742
7813
|
/**
|
|
7743
|
-
* The source paths read
|
|
7744
|
-
*
|
|
7814
|
+
* The source paths read during the last evaluation, or undefined before the
|
|
7815
|
+
* first evaluation and when nothing is recorded.
|
|
7745
7816
|
*/
|
|
7746
7817
|
trackedPathSet;
|
|
7747
7818
|
/**
|
|
7748
7819
|
* Private constructor. Use ExpressionEvaluator.create() to create instances.
|
|
7749
7820
|
*/
|
|
7750
|
-
constructor(expression, bindings, identifiers, originalIdentifiers, evaluatorFunc, additionalContext) {
|
|
7821
|
+
constructor(expression, bindings, identifiers, originalIdentifiers, evaluatorFunc, callsMethod, additionalContext) {
|
|
7751
7822
|
this.expression = expression;
|
|
7752
7823
|
this.bindings = bindings;
|
|
7753
7824
|
this.identifiers = identifiers;
|
|
7754
7825
|
this.originalIdentifiers = originalIdentifiers;
|
|
7755
7826
|
this.evaluatorFunc = evaluatorFunc;
|
|
7756
7827
|
this.additionalContext = additionalContext;
|
|
7757
|
-
this.
|
|
7828
|
+
this.tracksReads = callsMethod || originalIdentifiers.some(id => id.includes('[*]'));
|
|
7758
7829
|
}
|
|
7759
7830
|
/**
|
|
7760
7831
|
* Creates an ExpressionEvaluator instance.
|
|
@@ -7796,7 +7867,7 @@
|
|
|
7796
7867
|
// Cache the compiled function
|
|
7797
7868
|
this.cache.set(cacheKey, evaluatorFunc);
|
|
7798
7869
|
}
|
|
7799
|
-
return new ExpressionEvaluator(expression, bindings, baseIdentifiers, extractedIdentifiers, evaluatorFunc, options?.additionalContext);
|
|
7870
|
+
return new ExpressionEvaluator(expression, bindings, baseIdentifiers, extractedIdentifiers, evaluatorFunc, ExpressionUtils.callsFunction(extractedIdentifiers, functionDependencies), options?.additionalContext);
|
|
7800
7871
|
}
|
|
7801
7872
|
/**
|
|
7802
7873
|
* Compiles an expression into a function.
|
|
@@ -7851,9 +7922,11 @@
|
|
|
7851
7922
|
values.push(...Object.values(this.additionalContext));
|
|
7852
7923
|
}
|
|
7853
7924
|
// Call the compiled function with the gathered values. When an index is
|
|
7854
|
-
// known only at run time, record which concrete
|
|
7855
|
-
//
|
|
7856
|
-
|
|
7925
|
+
// known only at run time or a method is called, record which concrete
|
|
7926
|
+
// paths this evaluation read (see trackedPaths). The values above are
|
|
7927
|
+
// gathered outside the tracked call on purpose: a binding passed in by
|
|
7928
|
+
// name is already among the identifiers.
|
|
7929
|
+
if (!this.tracksReads) {
|
|
7857
7930
|
return this.evaluatorFunc(...values);
|
|
7858
7931
|
}
|
|
7859
7932
|
const paths = new Set();
|
|
@@ -7868,11 +7941,11 @@
|
|
|
7868
7941
|
}
|
|
7869
7942
|
}
|
|
7870
7943
|
/**
|
|
7871
|
-
* The source paths read
|
|
7872
|
-
*
|
|
7944
|
+
* The source paths read during the last evaluation, recorded only for an
|
|
7945
|
+
* expression with a run-time index ("items[i].name") or a method call.
|
|
7873
7946
|
* Each evaluation replaces the previous set, so a path read for a former
|
|
7874
7947
|
* index value is dropped as soon as the expression runs with the new one.
|
|
7875
|
-
* Undefined before the first evaluation and for expressions
|
|
7948
|
+
* Undefined before the first evaluation and for other expressions.
|
|
7876
7949
|
*/
|
|
7877
7950
|
get trackedPaths() {
|
|
7878
7951
|
return this.trackedPathSet;
|
|
@@ -8664,7 +8737,14 @@
|
|
|
8664
8737
|
this.#resolveComputedIfDirty(key);
|
|
8665
8738
|
}
|
|
8666
8739
|
if (Reflect.has(obj, key)) {
|
|
8667
|
-
|
|
8740
|
+
const value = Reflect.get(obj, key);
|
|
8741
|
+
// Record the read for a computed property or expression being tracked
|
|
8742
|
+
// (see ReactiveProxy.track). Methods are behaviour, not data; a binding of a
|
|
8743
|
+
// parent scope is recorded by the parent's own trap.
|
|
8744
|
+
if (typeof key === 'string' && typeof value !== 'function') {
|
|
8745
|
+
ReactiveProxy.recordRead(key);
|
|
8746
|
+
}
|
|
8747
|
+
return value;
|
|
8668
8748
|
}
|
|
8669
8749
|
return this.#parent?.raw[key];
|
|
8670
8750
|
},
|
|
@@ -9009,6 +9089,13 @@
|
|
|
9009
9089
|
markComputedDirty(key) {
|
|
9010
9090
|
this.#dirtyComputeds.add(key);
|
|
9011
9091
|
}
|
|
9092
|
+
/**
|
|
9093
|
+
* Indicates whether a computed property is marked dirty and not yet recomputed.
|
|
9094
|
+
* @param key The computed property name.
|
|
9095
|
+
*/
|
|
9096
|
+
isComputedDirty(key) {
|
|
9097
|
+
return this.#dirtyComputeds.has(key);
|
|
9098
|
+
}
|
|
9012
9099
|
/**
|
|
9013
9100
|
* Reads the currently cached value of a computed property without triggering pull-based
|
|
9014
9101
|
* re-evaluation. Used by the recompute routine to obtain the previous value for change
|
|
@@ -9128,10 +9215,14 @@
|
|
|
9128
9215
|
* path fits the identifier (not evaluated yet, a raw object, a short-circuited read),
|
|
9129
9216
|
* the identifier keeps matching every index.
|
|
9130
9217
|
*
|
|
9218
|
+
* The recorded paths also count as dependencies of their own: they include what a
|
|
9219
|
+
* method called by the expression read, such as `this[key]`, which the identifiers
|
|
9220
|
+
* found in the source cannot name.
|
|
9221
|
+
*
|
|
9131
9222
|
* @param changes The change paths of the current update cycle.
|
|
9132
9223
|
* @param identifiers The identifiers the expression depends on.
|
|
9133
9224
|
* @param trackedPaths The paths read during the expression's last evaluation, if recorded.
|
|
9134
|
-
* @returns True if some change matches some identifier.
|
|
9225
|
+
* @returns True if some change matches some identifier or recorded path.
|
|
9135
9226
|
*/
|
|
9136
9227
|
isAffected(changes, identifiers, trackedPaths) {
|
|
9137
9228
|
if (changes.length === 0) {
|
|
@@ -9145,6 +9236,9 @@
|
|
|
9145
9236
|
}
|
|
9146
9237
|
}
|
|
9147
9238
|
}
|
|
9239
|
+
if (trackedPaths && trackedPaths.size > 0) {
|
|
9240
|
+
return changes.some(change => ReactiveProxy.changeAffectsRead(change, trackedPaths));
|
|
9241
|
+
}
|
|
9148
9242
|
return false;
|
|
9149
9243
|
}
|
|
9150
9244
|
/**
|
|
@@ -10211,8 +10305,8 @@
|
|
|
10211
10305
|
*/
|
|
10212
10306
|
#evaluate;
|
|
10213
10307
|
/**
|
|
10214
|
-
* The source paths read
|
|
10215
|
-
*
|
|
10308
|
+
* The source paths read during the last evaluation, recorded only when some
|
|
10309
|
+
* expression has a run-time index ("items[i].name") or calls a method.
|
|
10216
10310
|
*/
|
|
10217
10311
|
#trackedPaths;
|
|
10218
10312
|
/**
|
|
@@ -10255,10 +10349,15 @@
|
|
|
10255
10349
|
func: new Function(args, funcBody)
|
|
10256
10350
|
};
|
|
10257
10351
|
});
|
|
10258
|
-
// Only an expression with an index known at run time
|
|
10259
|
-
|
|
10260
|
-
//
|
|
10261
|
-
const
|
|
10352
|
+
// Only an expression with an index known at run time, or one that calls a method
|
|
10353
|
+
// (whose reads, such as `this[key]`, the source analysis may not see), needs its
|
|
10354
|
+
// reads recorded
|
|
10355
|
+
const tracksReads = this.#identifiers.some(id => id.includes('[*]')) ||
|
|
10356
|
+
ExpressionUtils.callsFunction(this.#identifiers, functionDependencies);
|
|
10357
|
+
// Create a function to reconstruct the text with evaluated expressions. The values
|
|
10358
|
+
// are gathered outside the tracked call: a binding passed in by name is already
|
|
10359
|
+
// among the identifiers.
|
|
10360
|
+
const evaluateText = (bindings, paths) => {
|
|
10262
10361
|
let result = text;
|
|
10263
10362
|
evaluators.forEach((evaluator, i) => {
|
|
10264
10363
|
// Build values for baseArgs (no dots) in the same order as function parameters
|
|
@@ -10272,15 +10371,16 @@
|
|
|
10272
10371
|
});
|
|
10273
10372
|
// Evaluate the expression and replace {{...}} in the text.
|
|
10274
10373
|
// A function replacement keeps `$`-patterns in the value literal.
|
|
10275
|
-
|
|
10374
|
+
const value = paths ? ReactiveProxy.track(paths, () => evaluator.func(...values)) : evaluator.func(...values);
|
|
10375
|
+
result = result.replace(matches[i][0], () => toDisplayString(value));
|
|
10276
10376
|
});
|
|
10277
10377
|
return result;
|
|
10278
10378
|
};
|
|
10279
|
-
this.#evaluate =
|
|
10379
|
+
this.#evaluate = tracksReads
|
|
10280
10380
|
? (bindings) => {
|
|
10281
10381
|
const paths = new Set();
|
|
10282
10382
|
this.#trackedPaths = undefined;
|
|
10283
|
-
const result =
|
|
10383
|
+
const result = evaluateText(bindings, paths);
|
|
10284
10384
|
this.#trackedPaths = paths;
|
|
10285
10385
|
return result;
|
|
10286
10386
|
}
|
|
@@ -10302,9 +10402,9 @@
|
|
|
10302
10402
|
return this.#identifiers;
|
|
10303
10403
|
}
|
|
10304
10404
|
/**
|
|
10305
|
-
* The source paths read
|
|
10306
|
-
*
|
|
10307
|
-
* undefined before the first evaluation and for texts
|
|
10405
|
+
* The source paths read during the last evaluation. Recorded only when an
|
|
10406
|
+
* expression has a run-time index ("items[i].name") or calls a method;
|
|
10407
|
+
* undefined before the first evaluation and for other texts.
|
|
10308
10408
|
*/
|
|
10309
10409
|
get trackedPaths() {
|
|
10310
10410
|
return this.#trackedPaths;
|
|
@@ -10725,6 +10825,29 @@
|
|
|
10725
10825
|
this.#dependentIdentifiers = [...new Set(ids)];
|
|
10726
10826
|
return this.#dependentIdentifiers;
|
|
10727
10827
|
}
|
|
10828
|
+
/**
|
|
10829
|
+
* Whether a change of the current update cycle concerns this node, so that a node it
|
|
10830
|
+
* depends on should update it: a change matches one of its identifiers, or touches a
|
|
10831
|
+
* path one of its bindings read the last time it ran. The recorded reads cover what a
|
|
10832
|
+
* called method read, such as `this[key]`, which the identifiers cannot name. Each
|
|
10833
|
+
* binding decides again for itself when the node updates.
|
|
10834
|
+
* @param changes The change paths of the current update cycle.
|
|
10835
|
+
*/
|
|
10836
|
+
isAffectedBy(changes) {
|
|
10837
|
+
if (changes.length === 0) {
|
|
10838
|
+
return false;
|
|
10839
|
+
}
|
|
10840
|
+
if (this.dependentIdentifiers.some(id => changes.some(change => this.bindings.doesChangeMatchIdentifier(change, id)))) {
|
|
10841
|
+
return true;
|
|
10842
|
+
}
|
|
10843
|
+
const recorded = [
|
|
10844
|
+
this.#textEvaluator?.trackedPaths,
|
|
10845
|
+
...(this.#directiveManager?.bindingsPreparers ?? []).map(preparer => preparer.trackedPaths),
|
|
10846
|
+
...(this.#directiveManager?.domUpdaters ?? []).map(updater => updater.trackedPaths),
|
|
10847
|
+
];
|
|
10848
|
+
return recorded.some(paths => paths !== undefined && paths.size > 0 &&
|
|
10849
|
+
changes.some(change => ReactiveProxy.changeAffectsRead(change, paths)));
|
|
10850
|
+
}
|
|
10728
10851
|
get preparableIdentifiers() {
|
|
10729
10852
|
// If already computed, return the cached preparable identifiers
|
|
10730
10853
|
if (this.#preparableIdentifiers) {
|
|
@@ -10801,8 +10924,7 @@
|
|
|
10801
10924
|
const changed = this.bindings.isAffected(changes, this.#textEvaluator.identifiers, this.#textEvaluator.trackedPaths);
|
|
10802
10925
|
// If the text node has changed, update its content
|
|
10803
10926
|
if (changed) {
|
|
10804
|
-
|
|
10805
|
-
text.data = this.#textEvaluator.evaluate(this.bindings);
|
|
10927
|
+
this.#renderText();
|
|
10806
10928
|
}
|
|
10807
10929
|
}
|
|
10808
10930
|
else if (this.#nodeType === Node.ELEMENT_NODE) {
|
|
@@ -10823,7 +10945,7 @@
|
|
|
10823
10945
|
for (const preparer of this.#directiveManager.bindingsPreparers) {
|
|
10824
10946
|
const changed = this.bindings.isAffected(changes, preparer.dependentIdentifiers, preparer.trackedPaths);
|
|
10825
10947
|
if (changed) {
|
|
10826
|
-
preparer.prepareBindings();
|
|
10948
|
+
this.#renderSafely(() => preparer.prepareBindings());
|
|
10827
10949
|
}
|
|
10828
10950
|
}
|
|
10829
10951
|
}
|
|
@@ -10832,14 +10954,13 @@
|
|
|
10832
10954
|
for (const updater of this.#directiveManager.domUpdaters) {
|
|
10833
10955
|
const changed = this.bindings.isAffected(changes, updater.dependentIdentifiers, updater.trackedPaths);
|
|
10834
10956
|
if (changed) {
|
|
10835
|
-
updater.applyToDOM();
|
|
10957
|
+
this.#renderSafely(() => updater.applyToDOM());
|
|
10836
10958
|
}
|
|
10837
10959
|
}
|
|
10838
10960
|
}
|
|
10839
10961
|
// Recursively update dependent virtual nodes
|
|
10840
10962
|
this.#dependents?.forEach(dependentNode => {
|
|
10841
|
-
|
|
10842
|
-
if (changed) {
|
|
10963
|
+
if (dependentNode.isAffectedBy(changes)) {
|
|
10843
10964
|
dependentNode.update();
|
|
10844
10965
|
}
|
|
10845
10966
|
});
|
|
@@ -10854,8 +10975,7 @@
|
|
|
10854
10975
|
// For document fragments (e.g. from <template v-if>), propagate updates
|
|
10855
10976
|
// to dependent virtual nodes so that child bindings stay reactive.
|
|
10856
10977
|
this.#dependents?.forEach(dependentNode => {
|
|
10857
|
-
|
|
10858
|
-
if (changed) {
|
|
10978
|
+
if (dependentNode.isAffectedBy(changes)) {
|
|
10859
10979
|
dependentNode.update();
|
|
10860
10980
|
}
|
|
10861
10981
|
});
|
|
@@ -10868,10 +10988,58 @@
|
|
|
10868
10988
|
* This is useful when an immediate update is needed, bypassing the usual change detection.
|
|
10869
10989
|
*/
|
|
10870
10990
|
forceUpdate() {
|
|
10991
|
+
// One pass per outermost call; a call made while a pass runs (a directive rendering
|
|
10992
|
+
// a fresh clone, a component mounting) joins it. The node is stamped with the pass,
|
|
10993
|
+
// so that the recursion over the children below skips a child a directive has
|
|
10994
|
+
// already rendered in this pass (see #forceChildren).
|
|
10995
|
+
const outermost = VNode.#forcePass === 0;
|
|
10996
|
+
if (outermost) {
|
|
10997
|
+
VNode.#forcePass = ++VNode.#forcePassCounter;
|
|
10998
|
+
}
|
|
10999
|
+
this.#forcedInPass = VNode.#forcePass;
|
|
11000
|
+
try {
|
|
11001
|
+
this.#forceUpdate();
|
|
11002
|
+
}
|
|
11003
|
+
finally {
|
|
11004
|
+
if (outermost) {
|
|
11005
|
+
VNode.#forcePass = 0;
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
}
|
|
11009
|
+
/**
|
|
11010
|
+
* The pass counter behind {@link forceUpdate}: the last pass number handed out.
|
|
11011
|
+
*/
|
|
11012
|
+
static #forcePassCounter = 0;
|
|
11013
|
+
/**
|
|
11014
|
+
* The pass that is running, or 0 between passes.
|
|
11015
|
+
*/
|
|
11016
|
+
static #forcePass = 0;
|
|
11017
|
+
/**
|
|
11018
|
+
* The pass this node was last forced in, or 0 if never.
|
|
11019
|
+
*/
|
|
11020
|
+
#forcedInPass = 0;
|
|
11021
|
+
/**
|
|
11022
|
+
* Forces the children that have not been forced in the running pass. A v-if / v-for /
|
|
11023
|
+
* slot clone is a child of its host, and the host's directive renders it with its own
|
|
11024
|
+
* forceUpdate() call as soon as it is created, so recursing into it here would render it a
|
|
11025
|
+
* second time. A clone that already existed, and that the directive updated with
|
|
11026
|
+
* update(), has not been forced in this pass and is still forced here.
|
|
11027
|
+
*/
|
|
11028
|
+
#forceChildren() {
|
|
11029
|
+
// A copy: an update can destroy a v-if / v-for clone, which removes it from this list.
|
|
11030
|
+
this.#childVNodes?.slice().forEach(childVNode => {
|
|
11031
|
+
if (childVNode.#forcedInPass !== VNode.#forcePass) {
|
|
11032
|
+
childVNode.forceUpdate();
|
|
11033
|
+
}
|
|
11034
|
+
});
|
|
11035
|
+
}
|
|
11036
|
+
/**
|
|
11037
|
+
* The body of {@link forceUpdate}, run within a pass.
|
|
11038
|
+
*/
|
|
11039
|
+
#forceUpdate() {
|
|
10871
11040
|
if (this.#nodeType === Node.TEXT_NODE && this.#textEvaluator) {
|
|
10872
11041
|
// If this is a text node with a text evaluator, update its content if needed
|
|
10873
|
-
|
|
10874
|
-
text.data = this.#textEvaluator.evaluate(this.bindings);
|
|
11042
|
+
this.#renderText();
|
|
10875
11043
|
}
|
|
10876
11044
|
else if (this.#nodeType === Node.ELEMENT_NODE) {
|
|
10877
11045
|
// If this is an element node, update directives and child nodes
|
|
@@ -10889,20 +11057,17 @@
|
|
|
10889
11057
|
}
|
|
10890
11058
|
// Prepare bindings for each preparer if relevant identifiers have changed
|
|
10891
11059
|
for (const preparer of this.#directiveManager.bindingsPreparers) {
|
|
10892
|
-
preparer.prepareBindings();
|
|
11060
|
+
this.#renderSafely(() => preparer.prepareBindings());
|
|
10893
11061
|
}
|
|
10894
11062
|
}
|
|
10895
11063
|
// Apply DOM updaters from directives, if any
|
|
10896
11064
|
if (this.#directiveManager?.domUpdaters) {
|
|
10897
11065
|
for (const updater of this.#directiveManager.domUpdaters) {
|
|
10898
|
-
updater.applyToDOM();
|
|
11066
|
+
this.#renderSafely(() => updater.applyToDOM());
|
|
10899
11067
|
}
|
|
10900
11068
|
}
|
|
10901
|
-
// Recursively update child virtual nodes
|
|
10902
|
-
|
|
10903
|
-
this.#childVNodes?.slice().forEach(childVNode => {
|
|
10904
|
-
childVNode.forceUpdate();
|
|
10905
|
-
});
|
|
11069
|
+
// Recursively update child virtual nodes
|
|
11070
|
+
this.#forceChildren();
|
|
10906
11071
|
// If no directive requires template preservation, call onUpdated for directives that do not templatize
|
|
10907
11072
|
if (!this.#templatized) {
|
|
10908
11073
|
this.#directiveManager?.directives?.forEach(d => {
|
|
@@ -10912,10 +11077,40 @@
|
|
|
10912
11077
|
}
|
|
10913
11078
|
else if (this.#nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
10914
11079
|
// For document fragments, recursively force update child VNodes
|
|
10915
|
-
this.#
|
|
10916
|
-
|
|
10917
|
-
|
|
11080
|
+
this.#forceChildren();
|
|
11081
|
+
}
|
|
11082
|
+
}
|
|
11083
|
+
/**
|
|
11084
|
+
* Evaluates this text node's expressions into its content.
|
|
11085
|
+
*/
|
|
11086
|
+
#renderText() {
|
|
11087
|
+
const text = this.#node;
|
|
11088
|
+
this.#renderSafely(() => {
|
|
11089
|
+
text.data = this.#textEvaluator.evaluate(this.bindings);
|
|
11090
|
+
});
|
|
11091
|
+
}
|
|
11092
|
+
/**
|
|
11093
|
+
* Runs one binding's render step. A binding whose expression throws (for example one that
|
|
11094
|
+
* reads a member of a value that is still undefined) is logged and left as it was, so that
|
|
11095
|
+
* it does not stop the rest of the tree from rendering.
|
|
11096
|
+
* @param render The render step.
|
|
11097
|
+
*/
|
|
11098
|
+
#renderSafely(render) {
|
|
11099
|
+
try {
|
|
11100
|
+
render();
|
|
11101
|
+
}
|
|
11102
|
+
catch (error) {
|
|
11103
|
+
this.#vApplication.logManager.getLogger(this.constructor.name).error(`Error rendering ${this.#describe()}: ${error}`);
|
|
11104
|
+
}
|
|
11105
|
+
}
|
|
11106
|
+
/**
|
|
11107
|
+
* Describes this node for a log message: a text node by its text, an element by its tag.
|
|
11108
|
+
*/
|
|
11109
|
+
#describe() {
|
|
11110
|
+
if (this.#nodeType === Node.TEXT_NODE) {
|
|
11111
|
+
return `text "${(this.#node.textContent ?? '').trim()}"`;
|
|
10918
11112
|
}
|
|
11113
|
+
return `<${this.#nodeName.toLowerCase()}>`;
|
|
10919
11114
|
}
|
|
10920
11115
|
/**
|
|
10921
11116
|
* Adds a child virtual node to this virtual node.
|
|
@@ -15453,6 +15648,13 @@
|
|
|
15453
15648
|
* A dictionary mapping computed property names to their dependencies.
|
|
15454
15649
|
*/
|
|
15455
15650
|
#computedDependencies;
|
|
15651
|
+
/**
|
|
15652
|
+
* The paths each computed property read the last time it was evaluated (see
|
|
15653
|
+
* ReactiveProxy.track). A change to one of them marks the computed dirty as well as
|
|
15654
|
+
* a change to the dependencies found in its source, since a read the source does not
|
|
15655
|
+
* spell out, such as `this[key]`, is seen only here.
|
|
15656
|
+
*/
|
|
15657
|
+
#computedReads = new Map();
|
|
15456
15658
|
/**
|
|
15457
15659
|
* A map tracking source paths for computed property values.
|
|
15458
15660
|
* Maps source path (e.g., "model.elements[0]") to computed property name (e.g., "selectedElement").
|
|
@@ -15643,8 +15845,11 @@
|
|
|
15643
15845
|
vApplication: this,
|
|
15644
15846
|
bindings: this.#bindings
|
|
15645
15847
|
});
|
|
15646
|
-
// Initial rendering
|
|
15647
|
-
|
|
15848
|
+
// Initial rendering. Every binding is applied, not only those whose identifiers were
|
|
15849
|
+
// recorded as changed while the data was set up: a value that starts undefined is never
|
|
15850
|
+
// recorded (undefined -> undefined is no change), and an expression over globals only
|
|
15851
|
+
// has no identifier to record at all.
|
|
15852
|
+
this.#vNode.forceUpdate();
|
|
15648
15853
|
// Expand the components in the tree. Last, so every component element
|
|
15649
15854
|
// has been compiled and has had its prop values delivered before its
|
|
15650
15855
|
// template is cloned in. See VNode.expandComponents.
|
|
@@ -16019,20 +16224,33 @@
|
|
|
16019
16224
|
}
|
|
16020
16225
|
/**
|
|
16021
16226
|
* Marks computed properties as dirty (pull-based invalidation) when a dependency changes.
|
|
16022
|
-
*
|
|
16023
|
-
* dependencies
|
|
16024
|
-
*
|
|
16025
|
-
*
|
|
16227
|
+
* A computed depends on what its source names (statically analyzed, with computed→computed and
|
|
16228
|
+
* computed→method dependencies flattened to their underlying reactive paths) and on what it read
|
|
16229
|
+
* the last time it ran (#computedReads). A computed marked dirty is itself a change for the
|
|
16230
|
+
* computeds that read it, so marking follows those reads too. A computed already dirty is
|
|
16231
|
+
* skipped: the computeds that read it were marked when it was, and a computed cannot be
|
|
16232
|
+
* recomputed without reading — and so resolving — the dirty ones it depends on. The actual
|
|
16233
|
+
* recomputation is deferred until the value is read (see #recomputeOne).
|
|
16026
16234
|
* @param identifier The changed identifier reported by the bindings change tracker.
|
|
16027
16235
|
*/
|
|
16028
16236
|
#markDirtyComputeds(identifier) {
|
|
16029
16237
|
if (!this.#options.computed || !this.#bindings) {
|
|
16030
16238
|
return;
|
|
16031
16239
|
}
|
|
16032
|
-
|
|
16033
|
-
|
|
16034
|
-
|
|
16035
|
-
|
|
16240
|
+
const pending = [identifier];
|
|
16241
|
+
while (pending.length > 0) {
|
|
16242
|
+
const changed = pending.pop();
|
|
16243
|
+
for (const key of Object.keys(this.#computedDependencies)) {
|
|
16244
|
+
if (this.#bindings.isComputedDirty(key)) {
|
|
16245
|
+
continue;
|
|
16246
|
+
}
|
|
16247
|
+
const deps = this.#computedDependencies[key] || [];
|
|
16248
|
+
const reads = this.#computedReads.get(key);
|
|
16249
|
+
if (deps.some(dep => this.#dependencyAffectedBy(dep, changed)) ||
|
|
16250
|
+
(reads !== undefined && ReactiveProxy.changeAffectsRead(changed, reads))) {
|
|
16251
|
+
this.#bindings.markComputedDirty(key);
|
|
16252
|
+
pending.push(key);
|
|
16253
|
+
}
|
|
16036
16254
|
}
|
|
16037
16255
|
}
|
|
16038
16256
|
}
|
|
@@ -16092,10 +16310,21 @@
|
|
|
16092
16310
|
return;
|
|
16093
16311
|
}
|
|
16094
16312
|
const computedFn = VApplication.#getComputedGetter(def);
|
|
16313
|
+
const bindings = this.#bindings;
|
|
16095
16314
|
try {
|
|
16096
16315
|
// Read the previous value without triggering re-resolution, for change detection.
|
|
16097
|
-
const oldValue =
|
|
16098
|
-
|
|
16316
|
+
const oldValue = bindings.peekComputed(key);
|
|
16317
|
+
// Record what the getter reads, replacing the previous record: the reads can differ
|
|
16318
|
+
// from run to run (a branch, a dynamic key). Kept even when the getter throws, so a
|
|
16319
|
+
// change to what it read before failing retries it.
|
|
16320
|
+
const reads = new Set();
|
|
16321
|
+
let newValue;
|
|
16322
|
+
try {
|
|
16323
|
+
newValue = ReactiveProxy.track(reads, () => computedFn.call(bindings.raw));
|
|
16324
|
+
}
|
|
16325
|
+
finally {
|
|
16326
|
+
this.#computedReads.set(key, reads);
|
|
16327
|
+
}
|
|
16099
16328
|
// Check if the value actually changed.
|
|
16100
16329
|
let hasChanged = oldValue !== newValue;
|
|
16101
16330
|
// For arrays, always treat as changed (VBindings detects length changes via its length
|