@angular/core 16.2.3 → 16.2.4
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/esm2022/src/linker/view_container_ref.mjs +35 -1
- package/esm2022/src/render3/component.mjs +4 -3
- package/esm2022/src/render3/di.mjs +1 -1
- package/esm2022/src/render3/instructions/change_detection.mjs +4 -4
- package/esm2022/src/render3/instructions/shared.mjs +20 -14
- package/esm2022/src/render3/interfaces/injector.mjs +1 -1
- package/esm2022/src/render3/interfaces/styling.mjs +4 -7
- package/esm2022/src/render3/node_manipulation.mjs +4 -3
- package/esm2022/src/render3/reactive_lview_consumer.mjs +25 -45
- package/esm2022/src/render3/reactivity/effect.mjs +8 -8
- package/esm2022/src/render3/util/injector_utils.mjs +1 -1
- package/esm2022/src/signals/index.mjs +4 -4
- package/esm2022/src/signals/src/api.mjs +2 -11
- package/esm2022/src/signals/src/computed.mjs +43 -93
- package/esm2022/src/signals/src/graph.mjs +238 -162
- package/esm2022/src/signals/src/signal.mjs +59 -79
- package/esm2022/src/signals/src/watch.mjs +38 -52
- package/esm2022/src/signals/src/weak_ref.mjs +2 -29
- package/esm2022/src/util/security/trusted_type_defs.mjs +1 -1
- package/esm2022/src/util/security/trusted_types.mjs +1 -1
- package/esm2022/src/version.mjs +1 -1
- package/esm2022/src/zone/ng_zone.mjs +16 -1
- package/esm2022/testing/src/logger.mjs +3 -3
- package/fesm2022/core.mjs +476 -483
- package/fesm2022/core.mjs.map +1 -1
- package/fesm2022/rxjs-interop.mjs +373 -413
- package/fesm2022/rxjs-interop.mjs.map +1 -1
- package/fesm2022/testing.mjs +474 -482
- package/fesm2022/testing.mjs.map +1 -1
- package/index.d.ts +112 -102
- package/package.json +1 -1
- package/rxjs-interop/index.d.ts +1 -1
- package/schematics/ng-generate/standalone-migration/bundle.js +9 -9
- package/schematics/ng-generate/standalone-migration/bundle.js.map +1 -1
- package/testing/index.d.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @license Angular v16.2.
|
|
2
|
+
* @license Angular v16.2.4
|
|
3
3
|
* (c) 2010-2022 Google LLC. https://angular.io/
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
@@ -131,15 +131,6 @@ const SIGNAL = Symbol('SIGNAL');
|
|
|
131
131
|
function isSignal(value) {
|
|
132
132
|
return typeof value === 'function' && value[SIGNAL] !== undefined;
|
|
133
133
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Converts `fn` into a marked signal function (where `isSignal(fn)` will be `true`), and
|
|
136
|
-
* potentially add some set of extra properties (passed as an object record `extraApi`).
|
|
137
|
-
*/
|
|
138
|
-
function createSignalFromFunction(node, fn, extraApi = {}) {
|
|
139
|
-
fn[SIGNAL] = node;
|
|
140
|
-
// Copy properties from `extraApi` to `fn` to complete the desired API of the `Signal`.
|
|
141
|
-
return Object.assign(fn, extraApi);
|
|
142
|
-
}
|
|
143
134
|
/**
|
|
144
135
|
* The default equality function used for `signal` and `computed`, which treats objects and arrays
|
|
145
136
|
* as never equal, and all other primitive values using identity semantics.
|
|
@@ -234,216 +225,265 @@ function initNgDevMode() {
|
|
|
234
225
|
|
|
235
226
|
// Required as the signals library is in a separate package, so we need to explicitly ensure the
|
|
236
227
|
/**
|
|
237
|
-
*
|
|
238
|
-
*
|
|
228
|
+
* The currently active consumer `ReactiveNode`, if running code in a reactive context.
|
|
229
|
+
*
|
|
230
|
+
* Change this via `setActiveConsumer`.
|
|
239
231
|
*/
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
232
|
+
let activeConsumer = null;
|
|
233
|
+
let inNotificationPhase = false;
|
|
234
|
+
function setActiveConsumer(consumer) {
|
|
235
|
+
const prev = activeConsumer;
|
|
236
|
+
activeConsumer = consumer;
|
|
237
|
+
return prev;
|
|
247
238
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
239
|
+
const REACTIVE_NODE = {
|
|
240
|
+
version: 0,
|
|
241
|
+
dirty: false,
|
|
242
|
+
producerNode: undefined,
|
|
243
|
+
producerLastReadVersion: undefined,
|
|
244
|
+
producerIndexOfThis: undefined,
|
|
245
|
+
nextProducerIndex: 0,
|
|
246
|
+
liveConsumerNode: undefined,
|
|
247
|
+
liveConsumerIndexOfThis: undefined,
|
|
248
|
+
consumerAllowSignalWrites: false,
|
|
249
|
+
consumerIsAlwaysLive: false,
|
|
250
|
+
producerMustRecompute: () => false,
|
|
251
|
+
producerRecomputeValue: () => { },
|
|
252
|
+
consumerMarkedDirty: () => { },
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Called by implementations when a producer's signal is read.
|
|
256
|
+
*/
|
|
257
|
+
function producerAccessed(node) {
|
|
258
|
+
if (inNotificationPhase) {
|
|
259
|
+
throw new Error(typeof ngDevMode !== 'undefined' && ngDevMode ?
|
|
260
|
+
`Assertion error: signal read during notification phase` :
|
|
261
|
+
'');
|
|
262
|
+
}
|
|
263
|
+
if (activeConsumer === null) {
|
|
264
|
+
// Accessed outside of a reactive context, so nothing to record.
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// This producer is the `idx`th dependency of `activeConsumer`.
|
|
268
|
+
const idx = activeConsumer.nextProducerIndex++;
|
|
269
|
+
assertConsumerNode(activeConsumer);
|
|
270
|
+
if (idx < activeConsumer.producerNode.length && activeConsumer.producerNode[idx] !== node) {
|
|
271
|
+
// There's been a change in producers since the last execution of `activeConsumer`.
|
|
272
|
+
// `activeConsumer.producerNode[idx]` holds a stale dependency which will be be removed and
|
|
273
|
+
// replaced with `this`.
|
|
274
|
+
//
|
|
275
|
+
// If `activeConsumer` isn't live, then this is a no-op, since we can replace the producer in
|
|
276
|
+
// `activeConsumer.producerNode` directly. However, if `activeConsumer` is live, then we need
|
|
277
|
+
// to remove it from the stale producer's `liveConsumer`s.
|
|
278
|
+
if (consumerIsLive(activeConsumer)) {
|
|
279
|
+
const staleProducer = activeConsumer.producerNode[idx];
|
|
280
|
+
producerRemoveLiveConsumerAtIndex(staleProducer, activeConsumer.producerIndexOfThis[idx]);
|
|
281
|
+
// At this point, the only record of `staleProducer` is the reference at
|
|
282
|
+
// `activeConsumer.producerNode[idx]` which will be overwritten below.
|
|
283
|
+
}
|
|
255
284
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
285
|
+
if (activeConsumer.producerNode[idx] !== node) {
|
|
286
|
+
// We're a new dependency of the consumer (at `idx`).
|
|
287
|
+
activeConsumer.producerNode[idx] = node;
|
|
288
|
+
// If the active consumer is live, then add it as a live consumer. If not, then use 0 as a
|
|
289
|
+
// placeholder value.
|
|
290
|
+
activeConsumer.producerIndexOfThis[idx] =
|
|
291
|
+
consumerIsLive(activeConsumer) ? producerAddLiveConsumer(node, activeConsumer, idx) : 0;
|
|
292
|
+
}
|
|
293
|
+
activeConsumer.producerLastReadVersion[idx] = node.version;
|
|
261
294
|
}
|
|
262
|
-
|
|
263
|
-
// Required as the signals library is in a separate package, so we need to explicitly ensure the
|
|
264
295
|
/**
|
|
265
|
-
*
|
|
296
|
+
* Ensure this producer's `version` is up-to-date.
|
|
266
297
|
*/
|
|
267
|
-
|
|
298
|
+
function producerUpdateValueVersion(node) {
|
|
299
|
+
if (consumerIsLive(node) && !node.dirty) {
|
|
300
|
+
// A live consumer will be marked dirty by producers, so a clean state means that its version
|
|
301
|
+
// is guaranteed to be up-to-date.
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (!node.producerMustRecompute(node) && !consumerPollProducersForChange(node)) {
|
|
305
|
+
// None of our producers report a change since the last time they were read, so no
|
|
306
|
+
// recomputation of our value is necessary, and we can consider ourselves clean.
|
|
307
|
+
node.dirty = false;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
node.producerRecomputeValue(node);
|
|
311
|
+
// After recomputing the value, we're no longer dirty.
|
|
312
|
+
node.dirty = false;
|
|
313
|
+
}
|
|
268
314
|
/**
|
|
269
|
-
*
|
|
270
|
-
* consumer).
|
|
315
|
+
* Propagate a dirty notification to live consumers of this producer.
|
|
271
316
|
*/
|
|
272
|
-
|
|
317
|
+
function producerNotifyConsumers(node) {
|
|
318
|
+
if (node.liveConsumerNode === undefined) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
// Prevent signal reads when we're updating the graph
|
|
322
|
+
const prev = inNotificationPhase;
|
|
323
|
+
inNotificationPhase = true;
|
|
324
|
+
try {
|
|
325
|
+
for (const consumer of node.liveConsumerNode) {
|
|
326
|
+
if (!consumer.dirty) {
|
|
327
|
+
consumerMarkDirty(consumer);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
finally {
|
|
332
|
+
inNotificationPhase = prev;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
273
335
|
/**
|
|
274
|
-
* Whether
|
|
336
|
+
* Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates,
|
|
337
|
+
* based on the current consumer context.
|
|
275
338
|
*/
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
339
|
+
function producerUpdatesAllowed() {
|
|
340
|
+
return activeConsumer?.consumerAllowSignalWrites !== false;
|
|
341
|
+
}
|
|
342
|
+
function consumerMarkDirty(node) {
|
|
343
|
+
node.dirty = true;
|
|
344
|
+
producerNotifyConsumers(node);
|
|
345
|
+
node.consumerMarkedDirty?.(node);
|
|
281
346
|
}
|
|
282
347
|
/**
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
* Nodes can be producers of reactive values, consumers of other reactive values, or both.
|
|
286
|
-
*
|
|
287
|
-
* Producers are nodes that produce values, and can be depended upon by consumer nodes.
|
|
288
|
-
*
|
|
289
|
-
* Producers expose a monotonic `valueVersion` counter, and are responsible for incrementing this
|
|
290
|
-
* version when their value semantically changes. Some producers may produce their values lazily and
|
|
291
|
-
* thus at times need to be polled for potential updates to their value (and by extension their
|
|
292
|
-
* `valueVersion`). This is accomplished via the `onProducerUpdateValueVersion` method for
|
|
293
|
-
* implemented by producers, which should perform whatever calculations are necessary to ensure
|
|
294
|
-
* `valueVersion` is up to date.
|
|
295
|
-
*
|
|
296
|
-
* Consumers are nodes that depend on the values of producers and are notified when those values
|
|
297
|
-
* might have changed.
|
|
298
|
-
*
|
|
299
|
-
* Consumers do not wrap the reads they consume themselves, but rather can be set as the active
|
|
300
|
-
* reader via `setActiveConsumer`. Reads of producers that happen while a consumer is active will
|
|
301
|
-
* result in those producers being added as dependencies of that consumer node.
|
|
348
|
+
* Prepare this consumer to run a computation in its reactive context.
|
|
302
349
|
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
|
|
350
|
+
* Must be called by subclasses which represent reactive computations, before those computations
|
|
351
|
+
* begin.
|
|
352
|
+
*/
|
|
353
|
+
function consumerBeforeComputation(node) {
|
|
354
|
+
node && (node.nextProducerIndex = 0);
|
|
355
|
+
return setActiveConsumer(node);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Finalize this consumer's state after a reactive computation has run.
|
|
306
359
|
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* comparing the consumer's `trackingVersion` to the version at which the dependency was
|
|
310
|
-
* last observed.
|
|
360
|
+
* Must be called by subclasses which represent reactive computations, after those computations
|
|
361
|
+
* have finished.
|
|
311
362
|
*/
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* Edges to consumers on which this node depends (in its producer capacity).
|
|
325
|
-
*/
|
|
326
|
-
this.consumers = new Map();
|
|
327
|
-
/**
|
|
328
|
-
* Monotonically increasing counter representing a version of this `Consumer`'s
|
|
329
|
-
* dependencies.
|
|
330
|
-
*/
|
|
331
|
-
this.trackingVersion = 0;
|
|
332
|
-
/**
|
|
333
|
-
* Monotonically increasing counter which increases when the value of this `Producer`
|
|
334
|
-
* semantically changes.
|
|
335
|
-
*/
|
|
336
|
-
this.valueVersion = 0;
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Polls dependencies of a consumer to determine if they have actually changed.
|
|
340
|
-
*
|
|
341
|
-
* If this returns `false`, then even though the consumer may have previously been notified of a
|
|
342
|
-
* change, the values of its dependencies have not actually changed and the consumer should not
|
|
343
|
-
* rerun any reactions.
|
|
344
|
-
*/
|
|
345
|
-
consumerPollProducersForChange() {
|
|
346
|
-
for (const [producerId, edge] of this.producers) {
|
|
347
|
-
const producer = edge.producerNode.deref();
|
|
348
|
-
// On Safari < 16.1 deref can return null, we need to check for null also.
|
|
349
|
-
// See https://github.com/WebKit/WebKit/commit/44c15ba58912faab38b534fef909dd9e13e095e0
|
|
350
|
-
if (producer == null || edge.atTrackingVersion !== this.trackingVersion) {
|
|
351
|
-
// This dependency edge is stale, so remove it.
|
|
352
|
-
this.producers.delete(producerId);
|
|
353
|
-
producer?.consumers.delete(this.id);
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
if (producer.producerPollStatus(edge.seenValueVersion)) {
|
|
357
|
-
// One of the dependencies reports a real value change.
|
|
358
|
-
return true;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
// No dependency reported a real value change, so the `Consumer` has also not been
|
|
362
|
-
// impacted.
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* Notify all consumers of this producer that its value may have changed.
|
|
367
|
-
*/
|
|
368
|
-
producerMayHaveChanged() {
|
|
369
|
-
// Prevent signal reads when we're updating the graph
|
|
370
|
-
const prev = inNotificationPhase;
|
|
371
|
-
inNotificationPhase = true;
|
|
372
|
-
try {
|
|
373
|
-
for (const [consumerId, edge] of this.consumers) {
|
|
374
|
-
const consumer = edge.consumerNode.deref();
|
|
375
|
-
// On Safari < 16.1 deref can return null, we need to check for null also.
|
|
376
|
-
// See https://github.com/WebKit/WebKit/commit/44c15ba58912faab38b534fef909dd9e13e095e0
|
|
377
|
-
if (consumer == null || consumer.trackingVersion !== edge.atTrackingVersion) {
|
|
378
|
-
this.consumers.delete(consumerId);
|
|
379
|
-
consumer?.producers.delete(this.id);
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
consumer.onConsumerDependencyMayHaveChanged();
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
finally {
|
|
386
|
-
inNotificationPhase = prev;
|
|
363
|
+
function consumerAfterComputation(node, prevConsumer) {
|
|
364
|
+
setActiveConsumer(prevConsumer);
|
|
365
|
+
if (!node || node.producerNode === undefined || node.producerIndexOfThis === undefined ||
|
|
366
|
+
node.producerLastReadVersion === undefined) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (consumerIsLive(node)) {
|
|
370
|
+
// For live consumers, we need to remove the producer -> consumer edge for any stale producers
|
|
371
|
+
// which weren't dependencies after the recomputation.
|
|
372
|
+
for (let i = node.nextProducerIndex; i < node.producerNode.length; i++) {
|
|
373
|
+
producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
|
|
387
374
|
}
|
|
388
375
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
376
|
+
// Truncate the producer tracking arrays.
|
|
377
|
+
for (let i = node.nextProducerIndex; i < node.producerNode.length; i++) {
|
|
378
|
+
node.producerNode.pop();
|
|
379
|
+
node.producerLastReadVersion.pop();
|
|
380
|
+
node.producerIndexOfThis.pop();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Determine whether this consumer has any dependencies which have changed since the last time
|
|
385
|
+
* they were read.
|
|
386
|
+
*/
|
|
387
|
+
function consumerPollProducersForChange(node) {
|
|
388
|
+
assertConsumerNode(node);
|
|
389
|
+
// Poll producers for change.
|
|
390
|
+
for (let i = 0; i < node.producerNode.length; i++) {
|
|
391
|
+
const producer = node.producerNode[i];
|
|
392
|
+
const seenVersion = node.producerLastReadVersion[i];
|
|
393
|
+
// First check the versions. A mismatch means that the producer's value is known to have
|
|
394
|
+
// changed since the last time we read it.
|
|
395
|
+
if (seenVersion !== producer.version) {
|
|
396
|
+
return true;
|
|
397
397
|
}
|
|
398
|
-
|
|
399
|
-
|
|
398
|
+
// The producer's version is the same as the last time we read it, but it might itself be
|
|
399
|
+
// stale. Force the producer to recompute its version (calculating a new value if necessary).
|
|
400
|
+
producerUpdateValueVersion(producer);
|
|
401
|
+
// Now when we do this check, `producer.version` is guaranteed to be up to date, so if the
|
|
402
|
+
// versions still match then it has not changed since the last time we read it.
|
|
403
|
+
if (seenVersion !== producer.version) {
|
|
404
|
+
return true;
|
|
400
405
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
406
|
+
}
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Disconnect this consumer from the graph.
|
|
411
|
+
*/
|
|
412
|
+
function consumerDestroy(node) {
|
|
413
|
+
assertConsumerNode(node);
|
|
414
|
+
if (consumerIsLive(node)) {
|
|
415
|
+
// Drop all connections from the graph to this node.
|
|
416
|
+
for (let i = 0; i < node.producerNode.length; i++) {
|
|
417
|
+
producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
|
|
412
418
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
419
|
+
}
|
|
420
|
+
// Truncate all the arrays to drop all connection from this node to the graph.
|
|
421
|
+
node.producerNode.length = node.producerLastReadVersion.length = node.producerIndexOfThis.length =
|
|
422
|
+
0;
|
|
423
|
+
if (node.liveConsumerNode) {
|
|
424
|
+
node.liveConsumerNode.length = node.liveConsumerIndexOfThis.length = 0;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Add `consumer` as a live consumer of this node.
|
|
429
|
+
*
|
|
430
|
+
* Note that this operation is potentially transitive. If this node becomes live, then it becomes
|
|
431
|
+
* a live consumer of all of its current producers.
|
|
432
|
+
*/
|
|
433
|
+
function producerAddLiveConsumer(node, consumer, indexOfThis) {
|
|
434
|
+
assertProducerNode(node);
|
|
435
|
+
assertConsumerNode(node);
|
|
436
|
+
if (node.liveConsumerNode.length === 0) {
|
|
437
|
+
// When going from 0 to 1 live consumers, we become a live consumer to our producers.
|
|
438
|
+
for (let i = 0; i < node.producerNode.length; i++) {
|
|
439
|
+
node.producerIndexOfThis[i] = producerAddLiveConsumer(node.producerNode[i], node, i);
|
|
416
440
|
}
|
|
417
441
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
* last seen at a specific version by a `Consumer` which recorded a dependency on
|
|
434
|
-
* this `Producer`.
|
|
435
|
-
*/
|
|
436
|
-
producerPollStatus(lastSeenValueVersion) {
|
|
437
|
-
// `producer.valueVersion` may be stale, but a mismatch still means that the value
|
|
438
|
-
// last seen by the `Consumer` is also stale.
|
|
439
|
-
if (this.valueVersion !== lastSeenValueVersion) {
|
|
440
|
-
return true;
|
|
442
|
+
node.liveConsumerIndexOfThis.push(indexOfThis);
|
|
443
|
+
return node.liveConsumerNode.push(consumer) - 1;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Remove the live consumer at `idx`.
|
|
447
|
+
*/
|
|
448
|
+
function producerRemoveLiveConsumerAtIndex(node, idx) {
|
|
449
|
+
assertProducerNode(node);
|
|
450
|
+
assertConsumerNode(node);
|
|
451
|
+
if (node.liveConsumerNode.length === 1) {
|
|
452
|
+
// When removing the last live consumer, we will no longer be live. We need to remove
|
|
453
|
+
// ourselves from our producers' tracking (which may cause consumer-producers to lose
|
|
454
|
+
// liveness as well).
|
|
455
|
+
for (let i = 0; i < node.producerNode.length; i++) {
|
|
456
|
+
producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
|
|
441
457
|
}
|
|
442
|
-
// Trigger the `Producer` to update its `valueVersion` if necessary.
|
|
443
|
-
this.onProducerUpdateValueVersion();
|
|
444
|
-
// At this point, we can trust `producer.valueVersion`.
|
|
445
|
-
return this.valueVersion !== lastSeenValueVersion;
|
|
446
458
|
}
|
|
459
|
+
// Move the last value of `liveConsumers` into `idx`. Note that if there's only a single
|
|
460
|
+
// live consumer, this is a no-op.
|
|
461
|
+
const lastIdx = node.liveConsumerNode.length - 1;
|
|
462
|
+
node.liveConsumerNode[idx] = node.liveConsumerNode[lastIdx];
|
|
463
|
+
node.liveConsumerIndexOfThis[idx] = node.liveConsumerIndexOfThis[lastIdx];
|
|
464
|
+
// Truncate the array.
|
|
465
|
+
node.liveConsumerNode.length--;
|
|
466
|
+
node.liveConsumerIndexOfThis.length--;
|
|
467
|
+
// If the index is still valid, then we need to fix the index pointer from the producer to this
|
|
468
|
+
// consumer, and update it from `lastIdx` to `idx` (accounting for the move above).
|
|
469
|
+
if (idx < node.liveConsumerNode.length) {
|
|
470
|
+
const idxProducer = node.liveConsumerIndexOfThis[idx];
|
|
471
|
+
const consumer = node.liveConsumerNode[idx];
|
|
472
|
+
assertConsumerNode(consumer);
|
|
473
|
+
consumer.producerIndexOfThis[idxProducer] = idx;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function consumerIsLive(node) {
|
|
477
|
+
return node.consumerIsAlwaysLive || (node?.liveConsumerNode?.length ?? 0) > 0;
|
|
478
|
+
}
|
|
479
|
+
function assertConsumerNode(node) {
|
|
480
|
+
node.producerNode ??= [];
|
|
481
|
+
node.producerIndexOfThis ??= [];
|
|
482
|
+
node.producerLastReadVersion ??= [];
|
|
483
|
+
}
|
|
484
|
+
function assertProducerNode(node) {
|
|
485
|
+
node.liveConsumerNode ??= [];
|
|
486
|
+
node.liveConsumerIndexOfThis ??= [];
|
|
447
487
|
}
|
|
448
488
|
|
|
449
489
|
/**
|
|
@@ -452,10 +492,21 @@ class ReactiveNode {
|
|
|
452
492
|
* @developerPreview
|
|
453
493
|
*/
|
|
454
494
|
function computed(computation, options) {
|
|
455
|
-
const node =
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
495
|
+
const node = Object.create(COMPUTED_NODE);
|
|
496
|
+
node.computation = computation;
|
|
497
|
+
options?.equal && (node.equal = options.equal);
|
|
498
|
+
const computed = () => {
|
|
499
|
+
// Check if the value needs updating before returning it.
|
|
500
|
+
producerUpdateValueVersion(node);
|
|
501
|
+
// Record that someone looked at this signal.
|
|
502
|
+
producerAccessed(node);
|
|
503
|
+
if (node.value === ERRORED) {
|
|
504
|
+
throw node.error;
|
|
505
|
+
}
|
|
506
|
+
return node.value;
|
|
507
|
+
};
|
|
508
|
+
computed[SIGNAL] = node;
|
|
509
|
+
return computed;
|
|
459
510
|
}
|
|
460
511
|
/**
|
|
461
512
|
* A dedicated symbol used before a computed value has been calculated for the first time.
|
|
@@ -474,108 +525,47 @@ const COMPUTING = Symbol('COMPUTING');
|
|
|
474
525
|
* Explicitly typed as `any` so we can use it as signal's value.
|
|
475
526
|
*/
|
|
476
527
|
const ERRORED = Symbol('ERRORED');
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
* This can also be one of the special values `UNSET`, `COMPUTING`, or `ERRORED`.
|
|
491
|
-
*/
|
|
492
|
-
this.value = UNSET;
|
|
493
|
-
/**
|
|
494
|
-
* If `value` is `ERRORED`, the error caught from the last computation attempt which will
|
|
495
|
-
* be re-thrown.
|
|
496
|
-
*/
|
|
497
|
-
this.error = null;
|
|
498
|
-
/**
|
|
499
|
-
* Flag indicating that the computation is currently stale, meaning that one of the
|
|
500
|
-
* dependencies has notified of a potential change.
|
|
501
|
-
*
|
|
502
|
-
* It's possible that no dependency has _actually_ changed, in which case the `stale`
|
|
503
|
-
* state can be resolved without recomputing the value.
|
|
504
|
-
*/
|
|
505
|
-
this.stale = true;
|
|
506
|
-
this.consumerAllowSignalWrites = false;
|
|
507
|
-
}
|
|
508
|
-
onConsumerDependencyMayHaveChanged() {
|
|
509
|
-
if (this.stale) {
|
|
510
|
-
// We've already notified consumers that this value has potentially changed.
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
// Record that the currently cached value may be stale.
|
|
514
|
-
this.stale = true;
|
|
515
|
-
// Notify any consumers about the potential change.
|
|
516
|
-
this.producerMayHaveChanged();
|
|
517
|
-
}
|
|
518
|
-
onProducerUpdateValueVersion() {
|
|
519
|
-
if (!this.stale) {
|
|
520
|
-
// The current value and its version are already up to date.
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
// The current value is stale. Check whether we need to produce a new one.
|
|
524
|
-
if (this.value !== UNSET && this.value !== COMPUTING &&
|
|
525
|
-
!this.consumerPollProducersForChange()) {
|
|
526
|
-
// Even though we were previously notified of a potential dependency update, all of
|
|
527
|
-
// our dependencies report that they have not actually changed in value, so we can
|
|
528
|
-
// resolve the stale state without needing to recompute the current value.
|
|
529
|
-
this.stale = false;
|
|
530
|
-
return;
|
|
531
|
-
}
|
|
532
|
-
// The current value is stale, and needs to be recomputed. It still may not change -
|
|
533
|
-
// that depends on whether the newly computed value is equal to the old.
|
|
534
|
-
this.recomputeValue();
|
|
535
|
-
}
|
|
536
|
-
recomputeValue() {
|
|
537
|
-
if (this.value === COMPUTING) {
|
|
528
|
+
const COMPUTED_NODE = {
|
|
529
|
+
...REACTIVE_NODE,
|
|
530
|
+
value: UNSET,
|
|
531
|
+
dirty: true,
|
|
532
|
+
error: null,
|
|
533
|
+
equal: defaultEquals,
|
|
534
|
+
producerMustRecompute(node) {
|
|
535
|
+
// Force a recomputation if there's no current value, or if the current value is in the process
|
|
536
|
+
// of being calculated (which should throw an error).
|
|
537
|
+
return node.value === UNSET || node.value === COMPUTING;
|
|
538
|
+
},
|
|
539
|
+
producerRecomputeValue(node) {
|
|
540
|
+
if (node.value === COMPUTING) {
|
|
538
541
|
// Our computation somehow led to a cyclic read of itself.
|
|
539
542
|
throw new Error('Detected cycle in computations.');
|
|
540
543
|
}
|
|
541
|
-
const oldValue =
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
this.trackingVersion++;
|
|
545
|
-
const prevConsumer = setActiveConsumer(this);
|
|
544
|
+
const oldValue = node.value;
|
|
545
|
+
node.value = COMPUTING;
|
|
546
|
+
const prevConsumer = consumerBeforeComputation(node);
|
|
546
547
|
let newValue;
|
|
547
548
|
try {
|
|
548
|
-
newValue =
|
|
549
|
+
newValue = node.computation();
|
|
549
550
|
}
|
|
550
551
|
catch (err) {
|
|
551
552
|
newValue = ERRORED;
|
|
552
|
-
|
|
553
|
+
node.error = err;
|
|
553
554
|
}
|
|
554
555
|
finally {
|
|
555
|
-
|
|
556
|
+
consumerAfterComputation(node, prevConsumer);
|
|
556
557
|
}
|
|
557
|
-
this.stale = false;
|
|
558
558
|
if (oldValue !== UNSET && oldValue !== ERRORED && newValue !== ERRORED &&
|
|
559
|
-
|
|
559
|
+
node.equal(oldValue, newValue)) {
|
|
560
560
|
// No change to `valueVersion` - old and new values are
|
|
561
561
|
// semantically equivalent.
|
|
562
|
-
|
|
562
|
+
node.value = oldValue;
|
|
563
563
|
return;
|
|
564
564
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// Check if the value needs updating before returning it.
|
|
570
|
-
this.onProducerUpdateValueVersion();
|
|
571
|
-
// Record that someone looked at this signal.
|
|
572
|
-
this.producerAccessed();
|
|
573
|
-
if (this.value === ERRORED) {
|
|
574
|
-
throw this.error;
|
|
575
|
-
}
|
|
576
|
-
return this.value;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
565
|
+
node.value = newValue;
|
|
566
|
+
node.version++;
|
|
567
|
+
},
|
|
568
|
+
};
|
|
579
569
|
|
|
580
570
|
function defaultThrowError() {
|
|
581
571
|
throw new Error();
|
|
@@ -595,88 +585,24 @@ function setThrowInvalidWriteToSignalError(fn) {
|
|
|
595
585
|
* of setting a signal.
|
|
596
586
|
*/
|
|
597
587
|
let postSignalSetFn = null;
|
|
598
|
-
class WritableSignalImpl extends ReactiveNode {
|
|
599
|
-
constructor(value, equal) {
|
|
600
|
-
super();
|
|
601
|
-
this.value = value;
|
|
602
|
-
this.equal = equal;
|
|
603
|
-
this.consumerAllowSignalWrites = false;
|
|
604
|
-
}
|
|
605
|
-
onConsumerDependencyMayHaveChanged() {
|
|
606
|
-
// This never happens for writable signals as they're not consumers.
|
|
607
|
-
}
|
|
608
|
-
onProducerUpdateValueVersion() {
|
|
609
|
-
// Writable signal value versions are always up to date.
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Directly update the value of the signal to a new value, which may or may not be
|
|
613
|
-
* equal to the previous.
|
|
614
|
-
*
|
|
615
|
-
* In the event that `newValue` is semantically equal to the current value, `set` is
|
|
616
|
-
* a no-op.
|
|
617
|
-
*/
|
|
618
|
-
set(newValue) {
|
|
619
|
-
if (!this.producerUpdatesAllowed) {
|
|
620
|
-
throwInvalidWriteToSignalError();
|
|
621
|
-
}
|
|
622
|
-
if (!this.equal(this.value, newValue)) {
|
|
623
|
-
this.value = newValue;
|
|
624
|
-
this.valueVersion++;
|
|
625
|
-
this.producerMayHaveChanged();
|
|
626
|
-
postSignalSetFn?.();
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Derive a new value for the signal from its current value using the `updater` function.
|
|
631
|
-
*
|
|
632
|
-
* This is equivalent to calling `set` on the result of running `updater` on the current
|
|
633
|
-
* value.
|
|
634
|
-
*/
|
|
635
|
-
update(updater) {
|
|
636
|
-
if (!this.producerUpdatesAllowed) {
|
|
637
|
-
throwInvalidWriteToSignalError();
|
|
638
|
-
}
|
|
639
|
-
this.set(updater(this.value));
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Calls `mutator` on the current value and assumes that it has been mutated.
|
|
643
|
-
*/
|
|
644
|
-
mutate(mutator) {
|
|
645
|
-
if (!this.producerUpdatesAllowed) {
|
|
646
|
-
throwInvalidWriteToSignalError();
|
|
647
|
-
}
|
|
648
|
-
// Mutate bypasses equality checks as it's by definition changing the value.
|
|
649
|
-
mutator(this.value);
|
|
650
|
-
this.valueVersion++;
|
|
651
|
-
this.producerMayHaveChanged();
|
|
652
|
-
postSignalSetFn?.();
|
|
653
|
-
}
|
|
654
|
-
asReadonly() {
|
|
655
|
-
if (this.readonlySignal === undefined) {
|
|
656
|
-
this.readonlySignal = createSignalFromFunction(this, () => this.signal());
|
|
657
|
-
}
|
|
658
|
-
return this.readonlySignal;
|
|
659
|
-
}
|
|
660
|
-
signal() {
|
|
661
|
-
this.producerAccessed();
|
|
662
|
-
return this.value;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
588
|
/**
|
|
666
589
|
* Create a `Signal` that can be set or updated directly.
|
|
667
590
|
*
|
|
668
591
|
* @developerPreview
|
|
669
592
|
*/
|
|
670
593
|
function signal(initialValue, options) {
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
594
|
+
const node = Object.create(SIGNAL_NODE);
|
|
595
|
+
node.value = initialValue;
|
|
596
|
+
options?.equal && (node.equal = options.equal);
|
|
597
|
+
function signalFn() {
|
|
598
|
+
producerAccessed(node);
|
|
599
|
+
return node.value;
|
|
600
|
+
}
|
|
601
|
+
signalFn.set = signalSetFn;
|
|
602
|
+
signalFn.update = signalUpdateFn;
|
|
603
|
+
signalFn.mutate = signalMutateFn;
|
|
604
|
+
signalFn.asReadonly = signalAsReadonlyFn;
|
|
605
|
+
signalFn[SIGNAL] = node;
|
|
680
606
|
return signalFn;
|
|
681
607
|
}
|
|
682
608
|
function setPostSignalSetFn(fn) {
|
|
@@ -684,6 +610,50 @@ function setPostSignalSetFn(fn) {
|
|
|
684
610
|
postSignalSetFn = fn;
|
|
685
611
|
return prev;
|
|
686
612
|
}
|
|
613
|
+
const SIGNAL_NODE = {
|
|
614
|
+
...REACTIVE_NODE,
|
|
615
|
+
equal: defaultEquals,
|
|
616
|
+
readonlyFn: undefined,
|
|
617
|
+
};
|
|
618
|
+
function signalValueChanged(node) {
|
|
619
|
+
node.version++;
|
|
620
|
+
producerNotifyConsumers(node);
|
|
621
|
+
postSignalSetFn?.();
|
|
622
|
+
}
|
|
623
|
+
function signalSetFn(newValue) {
|
|
624
|
+
const node = this[SIGNAL];
|
|
625
|
+
if (!producerUpdatesAllowed()) {
|
|
626
|
+
throwInvalidWriteToSignalError();
|
|
627
|
+
}
|
|
628
|
+
if (!node.equal(node.value, newValue)) {
|
|
629
|
+
node.value = newValue;
|
|
630
|
+
signalValueChanged(node);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function signalUpdateFn(updater) {
|
|
634
|
+
if (!producerUpdatesAllowed()) {
|
|
635
|
+
throwInvalidWriteToSignalError();
|
|
636
|
+
}
|
|
637
|
+
signalSetFn.call(this, updater(this[SIGNAL].value));
|
|
638
|
+
}
|
|
639
|
+
function signalMutateFn(mutator) {
|
|
640
|
+
const node = this[SIGNAL];
|
|
641
|
+
if (!producerUpdatesAllowed()) {
|
|
642
|
+
throwInvalidWriteToSignalError();
|
|
643
|
+
}
|
|
644
|
+
// Mutate bypasses equality checks as it's by definition changing the value.
|
|
645
|
+
mutator(node.value);
|
|
646
|
+
signalValueChanged(node);
|
|
647
|
+
}
|
|
648
|
+
function signalAsReadonlyFn() {
|
|
649
|
+
const node = this[SIGNAL];
|
|
650
|
+
if (node.readonlyFn === undefined) {
|
|
651
|
+
const readonlyFn = () => this();
|
|
652
|
+
readonlyFn[SIGNAL] = node;
|
|
653
|
+
node.readonlyFn = readonlyFn;
|
|
654
|
+
}
|
|
655
|
+
return node.readonlyFn;
|
|
656
|
+
}
|
|
687
657
|
|
|
688
658
|
/**
|
|
689
659
|
* Execute an arbitrary function in a non-reactive (non-tracking) context. The executed function
|
|
@@ -703,63 +673,53 @@ function untracked(nonReactiveReadsFn) {
|
|
|
703
673
|
}
|
|
704
674
|
}
|
|
705
675
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
this.dirty = false;
|
|
720
|
-
this.cleanupFn = NOOP_CLEANUP_FN;
|
|
721
|
-
this.registerOnCleanup = (cleanupFn) => {
|
|
722
|
-
this.cleanupFn = cleanupFn;
|
|
723
|
-
};
|
|
724
|
-
this.consumerAllowSignalWrites = allowSignalWrites;
|
|
725
|
-
}
|
|
726
|
-
notify() {
|
|
727
|
-
if (!this.dirty) {
|
|
728
|
-
this.schedule(this);
|
|
729
|
-
}
|
|
730
|
-
this.dirty = true;
|
|
731
|
-
}
|
|
732
|
-
onConsumerDependencyMayHaveChanged() {
|
|
733
|
-
this.notify();
|
|
734
|
-
}
|
|
735
|
-
onProducerUpdateValueVersion() {
|
|
736
|
-
// Watches are not producers.
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* Execute the reactive expression in the context of this `Watch` consumer.
|
|
740
|
-
*
|
|
741
|
-
* Should be called by the user scheduling algorithm when the provided
|
|
742
|
-
* `schedule` hook is called by `Watch`.
|
|
743
|
-
*/
|
|
744
|
-
run() {
|
|
745
|
-
this.dirty = false;
|
|
746
|
-
if (this.trackingVersion !== 0 && !this.consumerPollProducersForChange()) {
|
|
676
|
+
function watch(fn, schedule, allowSignalWrites) {
|
|
677
|
+
const node = Object.create(WATCH_NODE);
|
|
678
|
+
if (allowSignalWrites) {
|
|
679
|
+
node.consumerAllowSignalWrites = true;
|
|
680
|
+
}
|
|
681
|
+
node.fn = fn;
|
|
682
|
+
node.schedule = schedule;
|
|
683
|
+
const registerOnCleanup = (cleanupFn) => {
|
|
684
|
+
node.cleanupFn = cleanupFn;
|
|
685
|
+
};
|
|
686
|
+
const run = () => {
|
|
687
|
+
node.dirty = false;
|
|
688
|
+
if (node.hasRun && !consumerPollProducersForChange(node)) {
|
|
747
689
|
return;
|
|
748
690
|
}
|
|
749
|
-
|
|
750
|
-
|
|
691
|
+
node.hasRun = true;
|
|
692
|
+
const prevConsumer = consumerBeforeComputation(node);
|
|
751
693
|
try {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
694
|
+
node.cleanupFn();
|
|
695
|
+
node.cleanupFn = NOOP_CLEANUP_FN;
|
|
696
|
+
node.fn(registerOnCleanup);
|
|
755
697
|
}
|
|
756
698
|
finally {
|
|
757
|
-
|
|
699
|
+
consumerAfterComputation(node, prevConsumer);
|
|
758
700
|
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
701
|
+
};
|
|
702
|
+
node.ref = {
|
|
703
|
+
notify: () => consumerMarkDirty(node),
|
|
704
|
+
run,
|
|
705
|
+
cleanup: () => node.cleanupFn(),
|
|
706
|
+
};
|
|
707
|
+
return node.ref;
|
|
708
|
+
}
|
|
709
|
+
const NOOP_CLEANUP_FN = () => { };
|
|
710
|
+
const WATCH_NODE = {
|
|
711
|
+
...REACTIVE_NODE,
|
|
712
|
+
consumerIsAlwaysLive: true,
|
|
713
|
+
consumerAllowSignalWrites: false,
|
|
714
|
+
consumerMarkedDirty: (node) => {
|
|
715
|
+
node.schedule(node.ref);
|
|
716
|
+
},
|
|
717
|
+
hasRun: false,
|
|
718
|
+
cleanupFn: NOOP_CLEANUP_FN,
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
function setAlternateWeakRefImpl(impl) {
|
|
722
|
+
// TODO: remove this function
|
|
763
723
|
}
|
|
764
724
|
|
|
765
725
|
function toSignal(source, options) {
|