@mintjamsinc/ichigojs 0.1.67 → 0.1.69
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 +293 -5
- package/dist/ichigo.cjs +213 -31
- package/dist/ichigo.cjs.map +1 -1
- package/dist/ichigo.esm.js +213 -31
- 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 +213 -31
- package/dist/ichigo.umd.js.map +1 -1
- package/dist/ichigo.umd.min.js +1 -1
- package/dist/types/ichigo/VBindings.d.ts +7 -0
- package/dist/types/ichigo/util/ReactiveProxy.d.ts +47 -1
- package/package.json +1 -1
package/dist/ichigo.esm.js
CHANGED
|
@@ -7975,6 +7975,13 @@ class ReactiveProxy {
|
|
|
7975
7975
|
* This allows retrieving the source path of an object for computed property mapping.
|
|
7976
7976
|
*/
|
|
7977
7977
|
static proxyPaths = new WeakMap();
|
|
7978
|
+
/**
|
|
7979
|
+
* Dispatchers per (target, path). Every target reached while walking a proxy
|
|
7980
|
+
* subtree registers an entry pointing at the same dispatcher as the outermost
|
|
7981
|
+
* proxy of that subtree, so callers can look up the dispatcher from any
|
|
7982
|
+
* intermediate proxy when subscribing.
|
|
7983
|
+
*/
|
|
7984
|
+
static dispatchers = new WeakMap();
|
|
7978
7985
|
/**
|
|
7979
7986
|
* A Map to store path aliases.
|
|
7980
7987
|
* Key: alias path (e.g., "editingNestedStep.steps")
|
|
@@ -7989,9 +7996,10 @@ class ReactiveProxy {
|
|
|
7989
7996
|
* @param target The object to make reactive.
|
|
7990
7997
|
* @param onChange Callback function to call when the object changes. Receives the full path of the changed property.
|
|
7991
7998
|
* @param path The current path in the object tree (used internally for nested objects).
|
|
7999
|
+
* @param inheritedDispatcher Internal: the dispatcher inherited from an enclosing create() call when wrapping a nested target. External callers must omit this.
|
|
7992
8000
|
* @returns A reactive proxy of the target object.
|
|
7993
8001
|
*/
|
|
7994
|
-
static create(target, onChange, path = '') {
|
|
8002
|
+
static create(target, onChange, path = '', inheritedDispatcher) {
|
|
7995
8003
|
// If the target is not an object or is null, return it as-is
|
|
7996
8004
|
if (typeof target !== 'object' || target === null) {
|
|
7997
8005
|
return target;
|
|
@@ -8011,6 +8019,13 @@ class ReactiveProxy {
|
|
|
8011
8019
|
if (this.proxyToTarget.has(target)) {
|
|
8012
8020
|
return target;
|
|
8013
8021
|
}
|
|
8022
|
+
// Resolve (and register) the dispatcher for this (target, path).
|
|
8023
|
+
// Nested create() calls inherit the dispatcher from the enclosing call so
|
|
8024
|
+
// that a single dispatcher fans out changes at any depth of the subtree.
|
|
8025
|
+
const dispatcher = this.resolveDispatcher(target, path, inheritedDispatcher);
|
|
8026
|
+
if (onChange) {
|
|
8027
|
+
dispatcher.listeners.add(onChange);
|
|
8028
|
+
}
|
|
8014
8029
|
// Check if we already have a proxy for this target with this path
|
|
8015
8030
|
let pathMap = this.proxyCache.get(target);
|
|
8016
8031
|
if (pathMap) {
|
|
@@ -8042,7 +8057,7 @@ class ReactiveProxy {
|
|
|
8042
8057
|
// Build the nested path
|
|
8043
8058
|
const keyStr = String(key);
|
|
8044
8059
|
const nestedPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
|
8045
|
-
return ReactiveProxy.create(value,
|
|
8060
|
+
return ReactiveProxy.create(value, undefined, nestedPath, dispatcher);
|
|
8046
8061
|
}
|
|
8047
8062
|
// If the value is a function, we need to wrap it to ensure that any mutations it performs also trigger onChange
|
|
8048
8063
|
if (typeof value === 'function') {
|
|
@@ -8054,7 +8069,7 @@ class ReactiveProxy {
|
|
|
8054
8069
|
}
|
|
8055
8070
|
return function (...args) {
|
|
8056
8071
|
const result = value.apply(this === receiver ? obj : this, args);
|
|
8057
|
-
|
|
8072
|
+
ReactiveProxy.dispatch(dispatcher, path || undefined);
|
|
8058
8073
|
return result;
|
|
8059
8074
|
};
|
|
8060
8075
|
}
|
|
@@ -8064,7 +8079,7 @@ class ReactiveProxy {
|
|
|
8064
8079
|
return function (...args) {
|
|
8065
8080
|
const result = value.apply(this === receiver ? obj : this, args);
|
|
8066
8081
|
if (mapMutationMethods.includes(key)) {
|
|
8067
|
-
|
|
8082
|
+
ReactiveProxy.dispatch(dispatcher, path || undefined);
|
|
8068
8083
|
}
|
|
8069
8084
|
return result;
|
|
8070
8085
|
};
|
|
@@ -8075,7 +8090,7 @@ class ReactiveProxy {
|
|
|
8075
8090
|
return function (...args) {
|
|
8076
8091
|
const result = value.apply(this === receiver ? obj : this, args);
|
|
8077
8092
|
if (setMutationMethods.includes(key)) {
|
|
8078
|
-
|
|
8093
|
+
ReactiveProxy.dispatch(dispatcher, path || undefined);
|
|
8079
8094
|
}
|
|
8080
8095
|
return result;
|
|
8081
8096
|
};
|
|
@@ -8090,7 +8105,7 @@ class ReactiveProxy {
|
|
|
8090
8105
|
if (oldValue !== value) {
|
|
8091
8106
|
const keyStr = String(key);
|
|
8092
8107
|
const fullPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
|
8093
|
-
|
|
8108
|
+
ReactiveProxy.dispatch(dispatcher, fullPath);
|
|
8094
8109
|
}
|
|
8095
8110
|
return result;
|
|
8096
8111
|
},
|
|
@@ -8098,7 +8113,7 @@ class ReactiveProxy {
|
|
|
8098
8113
|
const result = Reflect.deleteProperty(obj, key);
|
|
8099
8114
|
const keyStr = String(key);
|
|
8100
8115
|
const fullPath = path ? (Array.isArray(obj) ? `${path}[${keyStr}]` : `${path}.${keyStr}`) : keyStr;
|
|
8101
|
-
|
|
8116
|
+
ReactiveProxy.dispatch(dispatcher, fullPath);
|
|
8102
8117
|
return result;
|
|
8103
8118
|
}
|
|
8104
8119
|
});
|
|
@@ -8112,6 +8127,74 @@ class ReactiveProxy {
|
|
|
8112
8127
|
}
|
|
8113
8128
|
return proxy;
|
|
8114
8129
|
}
|
|
8130
|
+
/**
|
|
8131
|
+
* Looks up the dispatcher associated with (target, path), or installs a new one.
|
|
8132
|
+
* When called for a nested target during proxy walking, the enclosing dispatcher
|
|
8133
|
+
* is reused so a single subtree fans out changes through one notification path.
|
|
8134
|
+
*/
|
|
8135
|
+
static resolveDispatcher(target, path, inheritedDispatcher) {
|
|
8136
|
+
let pathMap = this.dispatchers.get(target);
|
|
8137
|
+
if (!pathMap) {
|
|
8138
|
+
pathMap = new Map();
|
|
8139
|
+
this.dispatchers.set(target, pathMap);
|
|
8140
|
+
}
|
|
8141
|
+
const existing = pathMap.get(path);
|
|
8142
|
+
if (existing) {
|
|
8143
|
+
return existing;
|
|
8144
|
+
}
|
|
8145
|
+
const dispatcher = inheritedDispatcher ?? { listeners: new Set() };
|
|
8146
|
+
pathMap.set(path, dispatcher);
|
|
8147
|
+
return dispatcher;
|
|
8148
|
+
}
|
|
8149
|
+
/**
|
|
8150
|
+
* Invokes every listener attached to the dispatcher.
|
|
8151
|
+
* Iterates a snapshot of the listener set so unsubscribing during dispatch is safe.
|
|
8152
|
+
*/
|
|
8153
|
+
static dispatch(dispatcher, changedPath) {
|
|
8154
|
+
if (dispatcher.listeners.size === 0) {
|
|
8155
|
+
return;
|
|
8156
|
+
}
|
|
8157
|
+
const snapshot = Array.from(dispatcher.listeners);
|
|
8158
|
+
for (const listener of snapshot) {
|
|
8159
|
+
listener(changedPath);
|
|
8160
|
+
}
|
|
8161
|
+
}
|
|
8162
|
+
/**
|
|
8163
|
+
* Subscribes a listener to changes inside the subtree of an existing reactive proxy.
|
|
8164
|
+
*
|
|
8165
|
+
* The listener is scoped by the proxy's source path: only changes at or below that
|
|
8166
|
+
* path are delivered, which lets a child component receive notifications when the
|
|
8167
|
+
* nested contents of a prop change even though the prop reference itself is unchanged.
|
|
8168
|
+
*
|
|
8169
|
+
* @param proxyOrTarget A proxy returned from create(), or the underlying target object.
|
|
8170
|
+
* @param listener Called with the full source path of every relevant change.
|
|
8171
|
+
* @returns A function that removes the subscription.
|
|
8172
|
+
*/
|
|
8173
|
+
static subscribe(proxyOrTarget, listener) {
|
|
8174
|
+
if (typeof proxyOrTarget !== 'object' || proxyOrTarget === null) {
|
|
8175
|
+
return () => { };
|
|
8176
|
+
}
|
|
8177
|
+
const target = (this.proxyToTarget.get(proxyOrTarget) ?? proxyOrTarget);
|
|
8178
|
+
const scopePath = this.proxyPaths.get(proxyOrTarget) ?? '';
|
|
8179
|
+
const pathMap = this.dispatchers.get(target);
|
|
8180
|
+
const dispatcher = pathMap?.get(scopePath);
|
|
8181
|
+
if (!dispatcher) {
|
|
8182
|
+
return () => { };
|
|
8183
|
+
}
|
|
8184
|
+
const wrapper = scopePath
|
|
8185
|
+
? (changedPath) => {
|
|
8186
|
+
if (changedPath === scopePath ||
|
|
8187
|
+
(typeof changedPath === 'string' && (changedPath.startsWith(scopePath + '.') ||
|
|
8188
|
+
changedPath.startsWith(scopePath + '[')))) {
|
|
8189
|
+
listener(changedPath);
|
|
8190
|
+
}
|
|
8191
|
+
}
|
|
8192
|
+
: listener;
|
|
8193
|
+
dispatcher.listeners.add(wrapper);
|
|
8194
|
+
return () => {
|
|
8195
|
+
dispatcher.listeners.delete(wrapper);
|
|
8196
|
+
};
|
|
8197
|
+
}
|
|
8115
8198
|
/**
|
|
8116
8199
|
* Checks if the given object is a reactive proxy.
|
|
8117
8200
|
*
|
|
@@ -8302,6 +8385,14 @@ class VBindings {
|
|
|
8302
8385
|
* is updated by the recompute cycle through `setSilent`, which bypasses this routing.
|
|
8303
8386
|
*/
|
|
8304
8387
|
#writableComputeds = new Map();
|
|
8388
|
+
/**
|
|
8389
|
+
* Unsubscribe handles for external reactive proxies received as binding values
|
|
8390
|
+
* (typically component props). Each entry keeps the child bindings notified when
|
|
8391
|
+
* nested properties of a shared object change even though the prop reference itself
|
|
8392
|
+
* stays the same. The previous subscription is released whenever the binding value
|
|
8393
|
+
* is replaced, the binding is set to null/undefined, or the bindings are destroyed.
|
|
8394
|
+
*/
|
|
8395
|
+
#externalSubscriptions = new Map();
|
|
8305
8396
|
/**
|
|
8306
8397
|
* Creates a new instance of VBindings.
|
|
8307
8398
|
* @param parent The parent bindings, if any.
|
|
@@ -8339,6 +8430,7 @@ class VBindings {
|
|
|
8339
8430
|
}
|
|
8340
8431
|
}
|
|
8341
8432
|
let newValue = value;
|
|
8433
|
+
let receivedExternalProxy = false;
|
|
8342
8434
|
if (typeof value === 'object' && value !== null) {
|
|
8343
8435
|
// Check if the value already has a path (it's an existing reactive proxy reference)
|
|
8344
8436
|
const existingPath = ReactiveProxy.getPath(value);
|
|
@@ -8348,6 +8440,7 @@ class VBindings {
|
|
|
8348
8440
|
this.#logger?.debug(`Path alias registered: ${key} -> ${existingPath}`);
|
|
8349
8441
|
// Keep the existing proxy as-is to preserve reactivity chain
|
|
8350
8442
|
newValue = value;
|
|
8443
|
+
receivedExternalProxy = true;
|
|
8351
8444
|
}
|
|
8352
8445
|
else {
|
|
8353
8446
|
// Before wrapping, check if any properties are existing ReactiveProxies
|
|
@@ -8391,6 +8484,34 @@ class VBindings {
|
|
|
8391
8484
|
}
|
|
8392
8485
|
const oldValue = Reflect.get(target, key);
|
|
8393
8486
|
const result = Reflect.set(target, key, newValue);
|
|
8487
|
+
// Manage external subscription to a reactive proxy received as the value.
|
|
8488
|
+
// When the reference changes, drop the previous subscription. When a new
|
|
8489
|
+
// reactive proxy is installed, subscribe so nested changes propagate to
|
|
8490
|
+
// this bindings instance even though the proxy reference itself is stable.
|
|
8491
|
+
if (oldValue !== newValue) {
|
|
8492
|
+
const prevUnsubscribe = this.#externalSubscriptions.get(key);
|
|
8493
|
+
if (prevUnsubscribe) {
|
|
8494
|
+
prevUnsubscribe();
|
|
8495
|
+
this.#externalSubscriptions.delete(key);
|
|
8496
|
+
}
|
|
8497
|
+
}
|
|
8498
|
+
if (receivedExternalProxy && !this.#externalSubscriptions.has(key)) {
|
|
8499
|
+
const unsubscribe = ReactiveProxy.subscribe(newValue, (changedPath) => {
|
|
8500
|
+
if (!changedPath) {
|
|
8501
|
+
return;
|
|
8502
|
+
}
|
|
8503
|
+
let path = '';
|
|
8504
|
+
for (const part of changedPath.split('.')) {
|
|
8505
|
+
path = path ? `${path}.${part}` : part;
|
|
8506
|
+
this.#logger?.debug(`Binding changed (external): ${path}`);
|
|
8507
|
+
this.#changes.add(path);
|
|
8508
|
+
}
|
|
8509
|
+
if (!this.#suppressOnChange) {
|
|
8510
|
+
this.#onChange?.(changedPath);
|
|
8511
|
+
}
|
|
8512
|
+
});
|
|
8513
|
+
this.#externalSubscriptions.set(key, unsubscribe);
|
|
8514
|
+
}
|
|
8394
8515
|
// Detect changes
|
|
8395
8516
|
let hasChanged = oldValue !== newValue;
|
|
8396
8517
|
// Special handling for arrays: check length changes even if same object reference
|
|
@@ -8419,6 +8540,11 @@ class VBindings {
|
|
|
8419
8540
|
},
|
|
8420
8541
|
deleteProperty: (obj, key) => {
|
|
8421
8542
|
const result = Reflect.deleteProperty(obj, key);
|
|
8543
|
+
const prevUnsubscribe = this.#externalSubscriptions.get(key);
|
|
8544
|
+
if (prevUnsubscribe) {
|
|
8545
|
+
prevUnsubscribe();
|
|
8546
|
+
this.#externalSubscriptions.delete(key);
|
|
8547
|
+
}
|
|
8422
8548
|
this.#logger?.debug(`Binding deleted: ${key}`);
|
|
8423
8549
|
this.#changes.add(key);
|
|
8424
8550
|
this.#onChange?.(key);
|
|
@@ -8500,6 +8626,18 @@ class VBindings {
|
|
|
8500
8626
|
remove(key) {
|
|
8501
8627
|
delete this.#local[key];
|
|
8502
8628
|
}
|
|
8629
|
+
/**
|
|
8630
|
+
* Releases all external proxy subscriptions held by these bindings.
|
|
8631
|
+
* Should be called when the owning application is unmounted so the parent
|
|
8632
|
+
* application's reactive objects do not keep references to listener closures
|
|
8633
|
+
* (and through them, this bindings instance) alive.
|
|
8634
|
+
*/
|
|
8635
|
+
destroy() {
|
|
8636
|
+
for (const unsubscribe of this.#externalSubscriptions.values()) {
|
|
8637
|
+
unsubscribe();
|
|
8638
|
+
}
|
|
8639
|
+
this.#externalSubscriptions.clear();
|
|
8640
|
+
}
|
|
8503
8641
|
/**
|
|
8504
8642
|
* Sets a binding value without triggering onChange callback.
|
|
8505
8643
|
* This is useful for internal updates that shouldn't trigger reactivity.
|
|
@@ -10126,9 +10264,18 @@ class VForDirective {
|
|
|
10126
10264
|
#sourceName;
|
|
10127
10265
|
#useOfSyntax = false; // Track if 'of' syntax was used
|
|
10128
10266
|
/**
|
|
10129
|
-
*
|
|
10267
|
+
* Ordered list of currently rendered items.
|
|
10268
|
+
*
|
|
10269
|
+
* This is intentionally an ordered array of { key, vNode } entries rather
|
|
10270
|
+
* than a Map<key, VNode>. The :key attribute is a *reconciliation hint*
|
|
10271
|
+
* used to identify and reuse the same logical row across re-renders — it is
|
|
10272
|
+
* not the identity of the rendered set. Keying the rendered set by a Map
|
|
10273
|
+
* would make it structurally impossible to hold two rows that resolve to
|
|
10274
|
+
* the same key, which would silently drop application data. The most
|
|
10275
|
+
* fundamental invariant of a list directive is "N items in => N rows out",
|
|
10276
|
+
* so the rendered set must be a positional list that can carry duplicates.
|
|
10130
10277
|
*/
|
|
10131
|
-
#renderedItems =
|
|
10278
|
+
#renderedItems = [];
|
|
10132
10279
|
/**
|
|
10133
10280
|
* Previous iterations to detect changes
|
|
10134
10281
|
*/
|
|
@@ -10251,11 +10398,11 @@ class VForDirective {
|
|
|
10251
10398
|
destroy() {
|
|
10252
10399
|
// Clean up all rendered items
|
|
10253
10400
|
// First destroy all VNodes (calls @unmount hooks), then remove from DOM
|
|
10254
|
-
for (const vNode of this.#renderedItems
|
|
10401
|
+
for (const { vNode } of this.#renderedItems) {
|
|
10255
10402
|
vNode.destroy();
|
|
10256
10403
|
}
|
|
10257
10404
|
// Then remove DOM nodes
|
|
10258
|
-
for (const vNode of this.#renderedItems
|
|
10405
|
+
for (const { vNode } of this.#renderedItems) {
|
|
10259
10406
|
const range = vNode.fragmentRange;
|
|
10260
10407
|
if (range) {
|
|
10261
10408
|
range.remove();
|
|
@@ -10266,7 +10413,7 @@ class VForDirective {
|
|
|
10266
10413
|
vNode.node.parentNode.removeChild(vNode.node);
|
|
10267
10414
|
}
|
|
10268
10415
|
}
|
|
10269
|
-
this.#renderedItems
|
|
10416
|
+
this.#renderedItems = [];
|
|
10270
10417
|
this.#previousIterations = [];
|
|
10271
10418
|
}
|
|
10272
10419
|
/**
|
|
@@ -10307,7 +10454,24 @@ class VForDirective {
|
|
|
10307
10454
|
this.#previousIterations = iterations;
|
|
10308
10455
|
}
|
|
10309
10456
|
/**
|
|
10310
|
-
* Key-based diffing for efficient DOM updates
|
|
10457
|
+
* Key-based diffing for efficient DOM updates.
|
|
10458
|
+
*
|
|
10459
|
+
* Reconciliation model
|
|
10460
|
+
* --------------------
|
|
10461
|
+
* The :key attribute is treated as a *hint* for reusing the same logical
|
|
10462
|
+
* row across re-renders, not as the identity of the rendered set. The
|
|
10463
|
+
* previously rendered rows are placed into a pool keyed by :key (a queue
|
|
10464
|
+
* per key, so equal keys can hold more than one row). Each incoming
|
|
10465
|
+
* iteration then claims a row from its key's queue when one is available,
|
|
10466
|
+
* otherwise a fresh row is created. Whatever stays in the pool at the end
|
|
10467
|
+
* is genuinely gone and is destroyed and removed.
|
|
10468
|
+
*
|
|
10469
|
+
* This guarantees the directive's most fundamental invariant — "N items in
|
|
10470
|
+
* => N rows out" — even when the application supplies duplicate keys. We
|
|
10471
|
+
* still warn on duplicates because they make reuse ambiguous (reordering
|
|
10472
|
+
* becomes positional rather than identity-stable), but we never silently
|
|
10473
|
+
* drop the application's data, and no row is ever orphaned: every old row
|
|
10474
|
+
* is either reused or explicitly removed.
|
|
10311
10475
|
*/
|
|
10312
10476
|
#updateList(newIterations) {
|
|
10313
10477
|
const parent = this.#vNode.anchorNode?.parentNode;
|
|
@@ -10315,22 +10479,38 @@ class VForDirective {
|
|
|
10315
10479
|
if (!parent || !anchor) {
|
|
10316
10480
|
throw new Error('v-for element must have a parent and anchor');
|
|
10317
10481
|
}
|
|
10318
|
-
|
|
10319
|
-
//
|
|
10320
|
-
|
|
10482
|
+
// Build a reuse pool from the currently rendered rows. A queue per key
|
|
10483
|
+
// (FIFO) lets duplicate keys reuse multiple rows: the first incoming
|
|
10484
|
+
// occurrence claims the first existing row, the second claims the next,
|
|
10485
|
+
// and so on.
|
|
10486
|
+
const pool = new Map();
|
|
10487
|
+
for (const { key, vNode } of this.#renderedItems) {
|
|
10488
|
+
let queue = pool.get(key);
|
|
10489
|
+
if (!queue) {
|
|
10490
|
+
queue = [];
|
|
10491
|
+
pool.set(key, queue);
|
|
10492
|
+
}
|
|
10493
|
+
queue.push(vNode);
|
|
10494
|
+
}
|
|
10495
|
+
// Decide, for each incoming iteration in order, whether it reuses an
|
|
10496
|
+
// existing row or needs a new one. Reused rows are taken out of the
|
|
10497
|
+
// pool so that what remains afterwards is exactly the set to remove.
|
|
10321
10498
|
const seenKeys = new Set();
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
|
|
10499
|
+
const plan = [];
|
|
10500
|
+
for (const context of newIterations) {
|
|
10501
|
+
if (seenKeys.has(context.key)) {
|
|
10502
|
+
console.warn(`[ichigo.js] Duplicate key detected in v-for: "${context.key}". All entries are still rendered, but reordering may be unstable. Keys should be unique.`);
|
|
10325
10503
|
}
|
|
10326
|
-
seenKeys.add(
|
|
10327
|
-
|
|
10504
|
+
seenKeys.add(context.key);
|
|
10505
|
+
const queue = pool.get(context.key);
|
|
10506
|
+
const reused = queue && queue.length ? queue.shift() : undefined;
|
|
10507
|
+
plan.push({ context, reused });
|
|
10328
10508
|
}
|
|
10329
|
-
// Remove
|
|
10509
|
+
// Remove rows that were not reused.
|
|
10330
10510
|
// First destroy VNodes (calls @unmount hooks while DOM is still accessible)
|
|
10331
10511
|
const nodesToRemove = [];
|
|
10332
|
-
for (const
|
|
10333
|
-
|
|
10512
|
+
for (const queue of pool.values()) {
|
|
10513
|
+
for (const vNode of queue) {
|
|
10334
10514
|
nodesToRemove.push(vNode);
|
|
10335
10515
|
vNode.destroy();
|
|
10336
10516
|
}
|
|
@@ -10349,11 +10529,12 @@ class VForDirective {
|
|
|
10349
10529
|
parentOfNode.removeChild(vNode.node);
|
|
10350
10530
|
}
|
|
10351
10531
|
}
|
|
10352
|
-
// Add or reorder
|
|
10532
|
+
// Add or reorder rows, building the new ordered rendered set.
|
|
10533
|
+
const newRenderedItems = [];
|
|
10353
10534
|
let prevNode = anchor;
|
|
10354
|
-
for (const context of
|
|
10535
|
+
for (const { context, reused } of plan) {
|
|
10355
10536
|
const { key } = context;
|
|
10356
|
-
let vNode =
|
|
10537
|
+
let vNode = reused;
|
|
10357
10538
|
if (!vNode) {
|
|
10358
10539
|
// Create new item
|
|
10359
10540
|
const clone = this.#cloneNode();
|
|
@@ -10385,7 +10566,7 @@ class VForDirective {
|
|
|
10385
10566
|
if (clone.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
10386
10567
|
const range = VFragmentRange.insert(parent, prevNode.nextSibling, 'vfor-fragment', clone);
|
|
10387
10568
|
vNode.fragmentRange = range;
|
|
10388
|
-
newRenderedItems.
|
|
10569
|
+
newRenderedItems.push({ key, vNode });
|
|
10389
10570
|
vNode.forceUpdate();
|
|
10390
10571
|
prevNode = range.lastNode;
|
|
10391
10572
|
continue;
|
|
@@ -10399,12 +10580,12 @@ class VForDirective {
|
|
|
10399
10580
|
else {
|
|
10400
10581
|
parent.appendChild(nodeToInsert);
|
|
10401
10582
|
}
|
|
10402
|
-
newRenderedItems.
|
|
10583
|
+
newRenderedItems.push({ key, vNode });
|
|
10403
10584
|
vNode.forceUpdate();
|
|
10404
10585
|
}
|
|
10405
10586
|
else {
|
|
10406
10587
|
// Reuse existing item
|
|
10407
|
-
newRenderedItems.
|
|
10588
|
+
newRenderedItems.push({ key, vNode });
|
|
10408
10589
|
// Update bindings
|
|
10409
10590
|
this.#updateItemBindings(vNode, context);
|
|
10410
10591
|
// For fragment-backed iterations, move the entire range atomically.
|
|
@@ -10430,7 +10611,7 @@ class VForDirective {
|
|
|
10430
10611
|
// Advance prevNode to this iteration's last DOM node
|
|
10431
10612
|
prevNode = vNode.fragmentRange?.lastNode ?? vNode.anchorNode ?? vNode.node;
|
|
10432
10613
|
}
|
|
10433
|
-
// Update rendered
|
|
10614
|
+
// Update the ordered rendered set
|
|
10434
10615
|
this.#renderedItems = newRenderedItems;
|
|
10435
10616
|
}
|
|
10436
10617
|
/**
|
|
@@ -13574,6 +13755,7 @@ class VApplication {
|
|
|
13574
13755
|
this.#vNode.destroy();
|
|
13575
13756
|
this.#vNode = undefined;
|
|
13576
13757
|
}
|
|
13758
|
+
this.#bindings?.destroy();
|
|
13577
13759
|
this.#logger.info('Application unmounted.');
|
|
13578
13760
|
}
|
|
13579
13761
|
/**
|