@wcstack/state 1.21.2 → 1.21.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/dist/index.d.ts +8 -0
- package/dist/index.esm.js +629 -137
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -83,6 +83,9 @@ function setConfig(partialConfig) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
const bindingPromiseByNode = new WeakMap();
|
|
86
|
+
// resolve 済みマーク。エントリ未生成のまま resolve されたノードは、後から
|
|
87
|
+
// wait された時に「生成して即 resolve」で追いつく。
|
|
88
|
+
const resolvedNodes = new WeakSet();
|
|
86
89
|
let id$1 = 0;
|
|
87
90
|
function getInitializeBindingPromiseByNode(node) {
|
|
88
91
|
let bindingPromise = bindingPromiseByNode.get(node) || null;
|
|
@@ -99,6 +102,9 @@ function getInitializeBindingPromiseByNode(node) {
|
|
|
99
102
|
resolve: resolveFn
|
|
100
103
|
};
|
|
101
104
|
bindingPromiseByNode.set(node, bindingPromise);
|
|
105
|
+
if (resolvedNodes.has(node)) {
|
|
106
|
+
bindingPromise.resolve();
|
|
107
|
+
}
|
|
102
108
|
return bindingPromise;
|
|
103
109
|
}
|
|
104
110
|
async function waitInitializeBinding(node) {
|
|
@@ -106,8 +112,15 @@ async function waitInitializeBinding(node) {
|
|
|
106
112
|
await bindingPromise.promise;
|
|
107
113
|
}
|
|
108
114
|
function resolveInitializedBinding(node) {
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
// ホットパス: リスト行では全 subscriber ノードがここを通るが、await する消費者
|
|
116
|
+
// (boundComponent / shadowRoot host)はほぼ居ない。既存エントリが無ければ
|
|
117
|
+
// Promise+closure を生成せず resolve 済みマークだけ残す(15 万個級の割り当て削減)。
|
|
118
|
+
const existing = bindingPromiseByNode.get(node);
|
|
119
|
+
if (typeof existing !== "undefined") {
|
|
120
|
+
existing.resolve();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
resolvedNodes.add(node);
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
const DELIMITER = '.';
|
|
@@ -575,7 +588,13 @@ function getBindingInfos(node, parseBindingTextResults) {
|
|
|
575
588
|
});
|
|
576
589
|
}
|
|
577
590
|
else {
|
|
578
|
-
|
|
591
|
+
// フラグメント登録時に事前正規化済みの Text ノードはそのまま replaceNode に
|
|
592
|
+
// 使う(node === replaceNode なら replaceToReplaceNode は no-op)。
|
|
593
|
+
// 実 DOM 上の wcs-text コメント(非フラグメント経路)は従来どおり
|
|
594
|
+
// 空 Text を生成して実行時に差し替える。
|
|
595
|
+
const replaceNode = node.nodeType === Node.TEXT_NODE
|
|
596
|
+
? node
|
|
597
|
+
: document.createTextNode('');
|
|
579
598
|
bindingInfos.push({
|
|
580
599
|
...parseBindingTextResult,
|
|
581
600
|
node: node,
|
|
@@ -3129,6 +3148,63 @@ function detachTwowayEventHandler(binding) {
|
|
|
3129
3148
|
}
|
|
3130
3149
|
}
|
|
3131
3150
|
|
|
3151
|
+
// framework 自身が detach し明示的に解体(deactivate/unmount)したノード。
|
|
3152
|
+
// BindingOwner の MutationObserver は削除サブツリー走査でこれらをスキップする。
|
|
3153
|
+
//
|
|
3154
|
+
// 根拠: 削除時の handleRemovedNode は binding を dispose するだけ(DOM 構造変更も
|
|
3155
|
+
// connect-snapshot 依存も無い)で、framework が unmount 経路で既に dispose 済みの
|
|
3156
|
+
// content に対しては純粋な冗長走査(forEachInclusive で削除サブツリー全体を歩く)に
|
|
3157
|
+
// なる。create(追加)経路は two-way の connect-time snapshot を observer に依存する
|
|
3158
|
+
// ため対象外だが、削除は依存が無いため安全に飛ばせる。
|
|
3159
|
+
//
|
|
3160
|
+
// マークは observer が削除を配送した時点で消費(削除)する。マーク〜配送の間隔は
|
|
3161
|
+
// 単一 microtask であり、その間に外部 DOM 変異は割り込めない(framework の drain は
|
|
3162
|
+
// 同期)ため、マークは framework 由来の削除にしか一致しない。
|
|
3163
|
+
const observerSkipNodes = new WeakSet();
|
|
3164
|
+
function markObserverSkipOnRemove(node) {
|
|
3165
|
+
observerSkipNodes.add(node);
|
|
3166
|
+
}
|
|
3167
|
+
// マーク済みなら true を返しつつマークを消費する。未マークなら false。
|
|
3168
|
+
function consumeObserverSkipOnRemove(node) {
|
|
3169
|
+
if (!observerSkipNodes.has(node)) {
|
|
3170
|
+
return false;
|
|
3171
|
+
}
|
|
3172
|
+
observerSkipNodes.delete(node);
|
|
3173
|
+
return true;
|
|
3174
|
+
}
|
|
3175
|
+
// framework 自身がマウント(Content.appendTo / mountAfter)したノード。
|
|
3176
|
+
// 追加サブツリー走査の実質の仕事は connect-snapshot 待ち(observationPending)の
|
|
3177
|
+
// record への配送だけで、record 自体は同期マウント(activateContent → start)で
|
|
3178
|
+
// observer flush より先に active 済み。よって待ちがグローバルに 1 つも無ければ
|
|
3179
|
+
// 追加側走査も冗長であり丸ごとスキップできる(削除側スキップの対称形)。
|
|
3180
|
+
// マーク〜配送が単一 microtask で外部変異が割り込めない前提も削除側と同じ。
|
|
3181
|
+
const observerSkipAddedNodes = new WeakSet();
|
|
3182
|
+
function markObserverSkipOnAdd(node) {
|
|
3183
|
+
observerSkipAddedNodes.add(node);
|
|
3184
|
+
}
|
|
3185
|
+
// マーク済みなら true を返しつつマークを消費する(削除側と同じ one-shot 契約)。
|
|
3186
|
+
function consumeObserverSkipOnAdd(node) {
|
|
3187
|
+
if (!observerSkipAddedNodes.has(node)) {
|
|
3188
|
+
return false;
|
|
3189
|
+
}
|
|
3190
|
+
observerSkipAddedNodes.delete(node);
|
|
3191
|
+
return true;
|
|
3192
|
+
}
|
|
3193
|
+
// connect-snapshot 待ち(two-way sync=connect で未接続のまま activate された record)の
|
|
3194
|
+
// グローバル件数。> 0 の間は追加側スキップを無効化して従来走査に戻す。
|
|
3195
|
+
// increment は settleInitialRecord、decrement は readProducerSnapshot(消化時)と
|
|
3196
|
+
// runTeardowns(未消化のまま終端した record のリーク防止)が担う。
|
|
3197
|
+
let pendingObservationCount = 0;
|
|
3198
|
+
function incrementPendingObservation() {
|
|
3199
|
+
pendingObservationCount++;
|
|
3200
|
+
}
|
|
3201
|
+
function decrementPendingObservation() {
|
|
3202
|
+
pendingObservationCount--;
|
|
3203
|
+
}
|
|
3204
|
+
function hasPendingObservation() {
|
|
3205
|
+
return pendingObservationCount > 0;
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3132
3208
|
/**
|
|
3133
3209
|
* Shares one CustomElementRegistry.whenDefined() continuation per registry/tag.
|
|
3134
3210
|
* Waiters can be removed independently, so a never-defined tag does not retain
|
|
@@ -3226,12 +3302,22 @@ function parseSyncOn(value) {
|
|
|
3226
3302
|
function hasInitialSyncModifier(binding) {
|
|
3227
3303
|
return binding.propModifiers.some((modifier) => modifier.includes("="));
|
|
3228
3304
|
}
|
|
3305
|
+
// 頻出ポリシー(修飾子なしの通常バインディング)の凍結シングルトン。リスト行では
|
|
3306
|
+
// binding ごとに resolveInitialSyncPolicy が走るため、毎回のオブジェクト割り当てを
|
|
3307
|
+
// 避ける(record.initialPolicy は読み取り専用でしか使われない)。
|
|
3308
|
+
const STATE_CALL_POLICY = Object.freeze({ authority: "state", syncOn: "call", observable: false });
|
|
3309
|
+
const NONE_CALL_POLICY = Object.freeze({ authority: "none", syncOn: "call", observable: false });
|
|
3310
|
+
function statePolicy(authority, syncOn) {
|
|
3311
|
+
if (authority === "state" && syncOn === "call")
|
|
3312
|
+
return STATE_CALL_POLICY;
|
|
3313
|
+
return { authority, syncOn, observable: false };
|
|
3314
|
+
}
|
|
3229
3315
|
function resolveInitialSyncPolicy(binding) {
|
|
3230
3316
|
if (!config.enableDirectionalInitialSync) {
|
|
3231
3317
|
if (hasInitialSyncModifier(binding)) {
|
|
3232
3318
|
raiseError("init=/sync= modifiers require enableDirectionalInitialSync.");
|
|
3233
3319
|
}
|
|
3234
|
-
return
|
|
3320
|
+
return STATE_CALL_POLICY;
|
|
3235
3321
|
}
|
|
3236
3322
|
const explicitAuthority = parseAuthority(readOption(binding, "init"));
|
|
3237
3323
|
const syncOn = parseSyncOn(readOption(binding, "sync"));
|
|
@@ -3239,7 +3325,7 @@ function resolveInitialSyncPolicy(binding) {
|
|
|
3239
3325
|
if (explicitAuthority !== null && explicitAuthority !== "none") {
|
|
3240
3326
|
raiseError("Event bindings only allow init=none.");
|
|
3241
3327
|
}
|
|
3242
|
-
return { authority: "none", syncOn, observable: false };
|
|
3328
|
+
return syncOn === "call" ? NONE_CALL_POLICY : { authority: "none", syncOn, observable: false };
|
|
3243
3329
|
}
|
|
3244
3330
|
// command.<name>: $command.<method> は命令的な command-token 配線。bindingType は
|
|
3245
3331
|
// "prop" だが propName ("command.<name>") は wcBindable property ではないため、下の
|
|
@@ -3247,17 +3333,17 @@ function resolveInitialSyncPolicy(binding) {
|
|
|
3247
3333
|
// 持たない配線なので、現行互換の "state" authority を返す(command token は従来通り
|
|
3248
3334
|
// 初期 apply で配線される)。
|
|
3249
3335
|
if (binding.propSegments[0] === "command") {
|
|
3250
|
-
return
|
|
3336
|
+
return statePolicy("state", syncOn);
|
|
3251
3337
|
}
|
|
3252
3338
|
if (binding.bindingType !== "prop") {
|
|
3253
3339
|
if (explicitAuthority !== null && explicitAuthority !== "state" && explicitAuthority !== "none") {
|
|
3254
3340
|
raiseError(`Binding type "${binding.bindingType}" does not support init=${explicitAuthority}.`);
|
|
3255
3341
|
}
|
|
3256
|
-
return
|
|
3342
|
+
return statePolicy(explicitAuthority ?? "state", syncOn);
|
|
3257
3343
|
}
|
|
3258
3344
|
const declaration = readBindableDeclaration(binding.node);
|
|
3259
3345
|
if (declaration === null) {
|
|
3260
|
-
return
|
|
3346
|
+
return statePolicy(explicitAuthority ?? "state", syncOn);
|
|
3261
3347
|
}
|
|
3262
3348
|
const hasOutput = declaration.knownProperties.has(binding.propName);
|
|
3263
3349
|
const hasInput = declaration.declaredInputs.has(binding.propName);
|
|
@@ -3332,8 +3418,50 @@ let nextRecordId = 0;
|
|
|
3332
3418
|
let nextGeneration = 0;
|
|
3333
3419
|
const recordByBinding = new WeakMap();
|
|
3334
3420
|
const sessionByRoot = new WeakMap();
|
|
3421
|
+
// binding の構造キーは不変フィールドのみから決まる。リスト行の初期化では同一 binding に
|
|
3422
|
+
// 対し remember() が2回呼ばれる(createContent 内 initializeBindingsByFragment と
|
|
3423
|
+
// activateContent の registerAddress 目的の initialize)ため、2度目の文字列生成を避けるべく
|
|
3424
|
+
// binding 単位でメモ化する。プロファイル上 bindingKey は create-10k の JS 自己時間で上位。
|
|
3425
|
+
const bindingKeyByBinding = new WeakMap();
|
|
3426
|
+
// node → その node に関心を持つ session(anchor として binding を覚えている、
|
|
3427
|
+
// または定義待ちタスクを抱えている)。BindingOwner は mutation で増減した
|
|
3428
|
+
// サブツリーを1回だけ走査し、ここに登録された session だけへ per-node 配送する。
|
|
3429
|
+
// 全 session ブロードキャストだと、リスト行の逐次 append などで
|
|
3430
|
+
// 「session 数 × 変異ノード数」の O(n²) ファンアウトになるため、その正本台帳。
|
|
3431
|
+
// 大多数の node は関心 session が1つなので単一値で持ち、2つ目から Set に昇格する。
|
|
3432
|
+
const interestedSessionsByNode = new WeakMap();
|
|
3433
|
+
function addInterestedSession(node, session) {
|
|
3434
|
+
const current = interestedSessionsByNode.get(node);
|
|
3435
|
+
if (typeof current === "undefined") {
|
|
3436
|
+
interestedSessionsByNode.set(node, session);
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3439
|
+
if (current === session)
|
|
3440
|
+
return;
|
|
3441
|
+
if (current instanceof Set) {
|
|
3442
|
+
current.add(session);
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
interestedSessionsByNode.set(node, new Set([current, session]));
|
|
3446
|
+
}
|
|
3447
|
+
function forEachInterestedSession(node, callback) {
|
|
3448
|
+
const current = interestedSessionsByNode.get(node);
|
|
3449
|
+
if (typeof current === "undefined")
|
|
3450
|
+
return;
|
|
3451
|
+
if (current instanceof Set) {
|
|
3452
|
+
for (const session of Array.from(current))
|
|
3453
|
+
callback(session);
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
callback(current);
|
|
3457
|
+
}
|
|
3335
3458
|
function forEachInclusive(root, callback) {
|
|
3336
3459
|
callback(root);
|
|
3460
|
+
// 葉ノード(fragment 一括挿入時のテキスト・空セル等が大多数)では
|
|
3461
|
+
// Array.from(childNodes) の空配列アロケーションを避ける。callback が子を
|
|
3462
|
+
// 追加しうるため firstChild は callback 後に判定する(従来と同一意味論)。
|
|
3463
|
+
if (root.firstChild === null)
|
|
3464
|
+
return;
|
|
3337
3465
|
for (const child of Array.from(root.childNodes)) {
|
|
3338
3466
|
forEachInclusive(child, callback);
|
|
3339
3467
|
}
|
|
@@ -3350,8 +3478,6 @@ function observableRootFor(node) {
|
|
|
3350
3478
|
}
|
|
3351
3479
|
class BindingOwner {
|
|
3352
3480
|
root;
|
|
3353
|
-
sessionRefs = new Set();
|
|
3354
|
-
knownSessions = new WeakSet();
|
|
3355
3481
|
observer;
|
|
3356
3482
|
constructor(root) {
|
|
3357
3483
|
this.root = root;
|
|
@@ -3361,12 +3487,6 @@ class BindingOwner {
|
|
|
3361
3487
|
: null;
|
|
3362
3488
|
this.observer?.observe(root, { childList: true, subtree: true });
|
|
3363
3489
|
}
|
|
3364
|
-
add(session) {
|
|
3365
|
-
if (this.knownSessions.has(session))
|
|
3366
|
-
return;
|
|
3367
|
-
this.knownSessions.add(session);
|
|
3368
|
-
this.sessionRefs.add(new WeakRef(session));
|
|
3369
|
-
}
|
|
3370
3490
|
handleMutations(mutations) {
|
|
3371
3491
|
const removed = [];
|
|
3372
3492
|
const added = [];
|
|
@@ -3374,14 +3494,39 @@ class BindingOwner {
|
|
|
3374
3494
|
removed.push(...Array.from(mutation.removedNodes));
|
|
3375
3495
|
added.push(...Array.from(mutation.addedNodes));
|
|
3376
3496
|
}
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3497
|
+
// 走査は owner が1回だけ行い、関心 session が居る node だけを配送・contains
|
|
3498
|
+
// 検査へ進める。contains は O(木の深さ) なので、関心の無い node で呼ばない。
|
|
3499
|
+
const reconnected = [];
|
|
3500
|
+
for (const subtree of removed) {
|
|
3501
|
+
// framework が unmount した削除サブツリーは binding を明示 dispose 済みなので
|
|
3502
|
+
// observer 側の冗長走査(forEachInclusive で全 node を歩き handleRemovedNode を
|
|
3503
|
+
// 呼ぶ)を丸ごとスキップする。clear/大量 delete のホットスポット短縮。
|
|
3504
|
+
if (consumeObserverSkipOnRemove(subtree))
|
|
3381
3505
|
continue;
|
|
3382
|
-
|
|
3383
|
-
|
|
3506
|
+
forEachInclusive(subtree, (node) => {
|
|
3507
|
+
forEachInterestedSession(node, (session) => {
|
|
3508
|
+
if (this.root.contains(node))
|
|
3509
|
+
return;
|
|
3510
|
+
session.handleRemovedNode(node);
|
|
3511
|
+
});
|
|
3512
|
+
});
|
|
3513
|
+
}
|
|
3514
|
+
for (const subtree of added) {
|
|
3515
|
+
// framework がマウントしたサブツリーは record が同期 activate 済みで、追加側
|
|
3516
|
+
// 走査の実質の仕事は connect-snapshot 待ちへの配送だけ。待ちがグローバルに
|
|
3517
|
+
// 無ければ丸ごとスキップする(待ちがあればマークだけ消費して従来走査に戻す)。
|
|
3518
|
+
if (consumeObserverSkipOnAdd(subtree) && !hasPendingObservation())
|
|
3519
|
+
continue;
|
|
3520
|
+
forEachInclusive(subtree, (node) => {
|
|
3521
|
+
forEachInterestedSession(node, (session) => {
|
|
3522
|
+
if (!this.root.contains(node))
|
|
3523
|
+
return;
|
|
3524
|
+
session.handleAddedNode(node, reconnected);
|
|
3525
|
+
});
|
|
3526
|
+
});
|
|
3384
3527
|
}
|
|
3528
|
+
if (reconnected.length > 0)
|
|
3529
|
+
applyChangeFromBindings(reconnected);
|
|
3385
3530
|
}
|
|
3386
3531
|
}
|
|
3387
3532
|
const ownerByRoot = new WeakMap();
|
|
@@ -3444,6 +3589,41 @@ class BindingSession {
|
|
|
3444
3589
|
}
|
|
3445
3590
|
return initialized.filter((binding) => this.shouldApplyState(binding));
|
|
3446
3591
|
}
|
|
3592
|
+
/**
|
|
3593
|
+
* activateContent 専用の再活性化パス。createContent 側の initialize で
|
|
3594
|
+
* remember 済みの binding 配列(bindingsByContent がそのまま保持する同一オブジェクト)
|
|
3595
|
+
* にだけ使える前提で、remember の再実行(キー照合・options マージ・興味登録)を省き、
|
|
3596
|
+
* 必要な仕事だけ行う: 初回活性化はアドレス登録+初期同期、pool 再利用(disposed)は
|
|
3597
|
+
* start による再構築、未知の binding は防御的に従来 initialize へ倒す。
|
|
3598
|
+
*/
|
|
3599
|
+
activate(bindings) {
|
|
3600
|
+
for (const binding of bindings) {
|
|
3601
|
+
const record = recordByBinding.get(binding);
|
|
3602
|
+
if (typeof record !== "undefined" && record.session === this
|
|
3603
|
+
&& record.phase !== "disposed" && record.phase !== "failed") {
|
|
3604
|
+
if (record.address === null) {
|
|
3605
|
+
// 初回活性化(mountAfter 経路では anchor が接続済みのことがあるため、
|
|
3606
|
+
// 従来 initialize と同様に owner の存在をここで保証する)
|
|
3607
|
+
this.observe(record.anchor);
|
|
3608
|
+
record.options.registerAddress = true;
|
|
3609
|
+
this.registerAddress(record);
|
|
3610
|
+
}
|
|
3611
|
+
if (record.phase === "active")
|
|
3612
|
+
this.settleInitialRecord(record);
|
|
3613
|
+
this.settleConnectedSnapshot(record);
|
|
3614
|
+
continue;
|
|
3615
|
+
}
|
|
3616
|
+
const options = this.optionsByBinding.get(binding);
|
|
3617
|
+
if (typeof options === "undefined") {
|
|
3618
|
+
// この session で remember されていない binding(防御): 従来経路
|
|
3619
|
+
this.initialize([binding], { registerAddress: true, registerPathInfo: false, applyOnReconnect: false });
|
|
3620
|
+
continue;
|
|
3621
|
+
}
|
|
3622
|
+
// pool 再利用: record は disposed。活性化要件(アドレス登録)を昇格して再構築
|
|
3623
|
+
options.registerAddress = true;
|
|
3624
|
+
this.start(binding, options);
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3447
3627
|
shouldApplyState(binding) {
|
|
3448
3628
|
if (!config.enableDirectionalInitialSync) {
|
|
3449
3629
|
if (hasInitialSyncModifier(binding))
|
|
@@ -3477,6 +3657,7 @@ class BindingSession {
|
|
|
3477
3657
|
raiseError(`CustomElementRegistry is unavailable for <${tagName}>.`);
|
|
3478
3658
|
}
|
|
3479
3659
|
this.observe(node);
|
|
3660
|
+
addInterestedSession(node, this);
|
|
3480
3661
|
const task = { node, active: true, cancel: null };
|
|
3481
3662
|
let tasks = this.deferredByNode.get(node);
|
|
3482
3663
|
if (typeof tasks === "undefined") {
|
|
@@ -3530,31 +3711,50 @@ class BindingSession {
|
|
|
3530
3711
|
this.deferredByNode.get(task.node)?.delete(task);
|
|
3531
3712
|
}
|
|
3532
3713
|
}
|
|
3714
|
+
/**
|
|
3715
|
+
* wholesale destroy(全行クリアで teardown を GC に任せる高速経路)を適用して
|
|
3716
|
+
* よいか。定義待ち(DefinitionCoordinator の waiter / deferred spread タスク)は
|
|
3717
|
+
* 強参照 Map に閉包が残り、connect-snapshot 待ちは pending カウンタが戻らなく
|
|
3718
|
+
* なるため、1 つでもあれば従来経路(teardown 実行)に倒す。
|
|
3719
|
+
*/
|
|
3720
|
+
canWholesaleDestroy() {
|
|
3721
|
+
if (this.deferred.size > 0)
|
|
3722
|
+
return false;
|
|
3723
|
+
for (const record of this.records) {
|
|
3724
|
+
if (record.pendingDefinitions > 0 || record.observationPending)
|
|
3725
|
+
return false;
|
|
3726
|
+
}
|
|
3727
|
+
return true;
|
|
3728
|
+
}
|
|
3729
|
+
/**
|
|
3730
|
+
* 全 record を teardown を走らせずに終端化する(canWholesaleDestroy が true の
|
|
3731
|
+
* content 専用)。イベント listener・アドレス台帳・loopContext はノード/binding
|
|
3732
|
+
* もろとも GC で崩壊する(recordByBinding 以下は全て弱参照)。
|
|
3733
|
+
* handlerBindingRegistry のカウンタは減らないが、残るのはキー文字列と数値のみで
|
|
3734
|
+
* 実害はない設計(handlerBindingRegistry.ts の弱参照化コメント参照)。
|
|
3735
|
+
*/
|
|
3736
|
+
destroyRecords() {
|
|
3737
|
+
for (const record of this.records) {
|
|
3738
|
+
record.phase = "disposed";
|
|
3739
|
+
record.teardowns.clear();
|
|
3740
|
+
}
|
|
3741
|
+
this.records.clear();
|
|
3742
|
+
}
|
|
3533
3743
|
observe(node) {
|
|
3534
3744
|
const root = observableRootFor(node);
|
|
3535
3745
|
if (root === null)
|
|
3536
3746
|
return;
|
|
3537
|
-
|
|
3747
|
+
// owner(root ごとの MutationObserver)の存在だけ保証する。session の配送先
|
|
3748
|
+
// 登録は node 単位(interestedSessionsByNode)で行い、owner は session を
|
|
3749
|
+
// 直接は保持しない。
|
|
3750
|
+
getBindingOwner(root);
|
|
3538
3751
|
}
|
|
3539
3752
|
handleMutations(root, removed, added) {
|
|
3540
3753
|
for (const subtree of removed) {
|
|
3541
3754
|
forEachInclusive(subtree, (node) => {
|
|
3542
3755
|
if (root.contains(node))
|
|
3543
3756
|
return;
|
|
3544
|
-
|
|
3545
|
-
if (typeof known !== "undefined") {
|
|
3546
|
-
for (const binding of known.values())
|
|
3547
|
-
this.disposeBinding(binding);
|
|
3548
|
-
}
|
|
3549
|
-
const tasks = this.deferredByNode.get(node);
|
|
3550
|
-
if (typeof tasks !== "undefined") {
|
|
3551
|
-
for (const task of Array.from(tasks)) {
|
|
3552
|
-
task.active = false;
|
|
3553
|
-
task.cancel?.();
|
|
3554
|
-
tasks.delete(task);
|
|
3555
|
-
this.deferred.delete(task);
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3757
|
+
this.handleRemovedNode(node);
|
|
3558
3758
|
});
|
|
3559
3759
|
}
|
|
3560
3760
|
const reconnected = [];
|
|
@@ -3562,42 +3762,68 @@ class BindingSession {
|
|
|
3562
3762
|
forEachInclusive(subtree, (node) => {
|
|
3563
3763
|
if (!root.contains(node))
|
|
3564
3764
|
return;
|
|
3565
|
-
|
|
3566
|
-
if (typeof known === "undefined")
|
|
3567
|
-
return;
|
|
3568
|
-
for (const binding of known.values()) {
|
|
3569
|
-
const record = recordByBinding.get(binding);
|
|
3570
|
-
if (record?.phase === "active") {
|
|
3571
|
-
this.settleConnectedSnapshot(record);
|
|
3572
|
-
continue;
|
|
3573
|
-
}
|
|
3574
|
-
if (record?.phase !== "disposed")
|
|
3575
|
-
continue;
|
|
3576
|
-
const options = this.optionsByBinding.get(binding);
|
|
3577
|
-
if (typeof options === "undefined")
|
|
3578
|
-
continue;
|
|
3579
|
-
try {
|
|
3580
|
-
this.start(binding, options);
|
|
3581
|
-
if (options.applyOnReconnect && this.shouldApplyState(binding))
|
|
3582
|
-
reconnected.push(binding);
|
|
3583
|
-
}
|
|
3584
|
-
catch {
|
|
3585
|
-
// Mutation delivery cannot surface initialization errors to a caller.
|
|
3586
|
-
}
|
|
3587
|
-
}
|
|
3765
|
+
this.handleAddedNode(node, reconnected);
|
|
3588
3766
|
});
|
|
3589
3767
|
}
|
|
3590
3768
|
if (reconnected.length > 0)
|
|
3591
3769
|
applyChangeFromBindings(reconnected);
|
|
3592
3770
|
}
|
|
3771
|
+
handleRemovedNode(node) {
|
|
3772
|
+
const known = this.knownBindingsByNode.get(node);
|
|
3773
|
+
if (typeof known !== "undefined") {
|
|
3774
|
+
for (const binding of known.values())
|
|
3775
|
+
this.disposeBinding(binding);
|
|
3776
|
+
}
|
|
3777
|
+
const tasks = this.deferredByNode.get(node);
|
|
3778
|
+
if (typeof tasks !== "undefined") {
|
|
3779
|
+
for (const task of Array.from(tasks)) {
|
|
3780
|
+
task.active = false;
|
|
3781
|
+
task.cancel?.();
|
|
3782
|
+
tasks.delete(task);
|
|
3783
|
+
this.deferred.delete(task);
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
handleAddedNode(node, reconnected) {
|
|
3788
|
+
const known = this.knownBindingsByNode.get(node);
|
|
3789
|
+
if (typeof known === "undefined")
|
|
3790
|
+
return;
|
|
3791
|
+
for (const binding of known.values()) {
|
|
3792
|
+
const record = recordByBinding.get(binding);
|
|
3793
|
+
if (record?.phase === "active") {
|
|
3794
|
+
this.settleConnectedSnapshot(record);
|
|
3795
|
+
continue;
|
|
3796
|
+
}
|
|
3797
|
+
if (record?.phase !== "disposed")
|
|
3798
|
+
continue;
|
|
3799
|
+
const options = this.optionsByBinding.get(binding);
|
|
3800
|
+
if (typeof options === "undefined")
|
|
3801
|
+
continue;
|
|
3802
|
+
try {
|
|
3803
|
+
this.start(binding, options);
|
|
3804
|
+
if (options.applyOnReconnect && this.shouldApplyState(binding))
|
|
3805
|
+
reconnected.push(binding);
|
|
3806
|
+
}
|
|
3807
|
+
catch {
|
|
3808
|
+
// Mutation delivery cannot surface initialization errors to a caller.
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3593
3812
|
remember(binding, options) {
|
|
3594
3813
|
const anchor = binding.replaceNode;
|
|
3814
|
+
// detached fragment 上でも登録しておく(node 単位の台帳なので root 非依存)。
|
|
3815
|
+
// fragment 一括マウントで後から接続された行にも mutation 配送が届くようにする。
|
|
3816
|
+
addInterestedSession(anchor, this);
|
|
3595
3817
|
let known = this.knownBindingsByNode.get(anchor);
|
|
3596
3818
|
if (typeof known === "undefined") {
|
|
3597
3819
|
known = new Map();
|
|
3598
3820
|
this.knownBindingsByNode.set(anchor, known);
|
|
3599
3821
|
}
|
|
3600
|
-
|
|
3822
|
+
let key = bindingKeyByBinding.get(binding);
|
|
3823
|
+
if (typeof key === "undefined") {
|
|
3824
|
+
key = bindingKey(binding);
|
|
3825
|
+
bindingKeyByBinding.set(binding, key);
|
|
3826
|
+
}
|
|
3601
3827
|
const remembered = known.get(key);
|
|
3602
3828
|
if (typeof remembered !== "undefined") {
|
|
3603
3829
|
const rememberedOptions = this.optionsByBinding.get(remembered);
|
|
@@ -3759,6 +3985,8 @@ class BindingSession {
|
|
|
3759
3985
|
&& record.info.node instanceof HTMLElement
|
|
3760
3986
|
&& !record.info.node.isConnected) {
|
|
3761
3987
|
record.observationPending = true;
|
|
3988
|
+
// 待ちが 1 件でもある間は追加側 observer スキップを無効化する
|
|
3989
|
+
incrementPendingObservation();
|
|
3762
3990
|
return;
|
|
3763
3991
|
}
|
|
3764
3992
|
this.readProducerSnapshot(record, policy.syncOn === "call");
|
|
@@ -3779,7 +4007,10 @@ class BindingSession {
|
|
|
3779
4007
|
return;
|
|
3780
4008
|
const sequence = record.eventSequence;
|
|
3781
4009
|
const value = target[name];
|
|
3782
|
-
record.observationPending
|
|
4010
|
+
if (record.observationPending) {
|
|
4011
|
+
record.observationPending = false;
|
|
4012
|
+
decrementPendingObservation();
|
|
4013
|
+
}
|
|
3783
4014
|
if (eventWins && record.eventSequence !== sequence)
|
|
3784
4015
|
return;
|
|
3785
4016
|
record.hasProducerValue = true;
|
|
@@ -3843,6 +4074,12 @@ class BindingSession {
|
|
|
3843
4074
|
this.records.delete(record);
|
|
3844
4075
|
}
|
|
3845
4076
|
runTeardowns(record) {
|
|
4077
|
+
// runTeardowns は record の終端(disposed / failed)でのみ呼ばれる。未消化の
|
|
4078
|
+
// connect-snapshot 待ちが残っていれば必ずカウンタを戻す(スキップ再有効化)。
|
|
4079
|
+
if (record.observationPending) {
|
|
4080
|
+
record.observationPending = false;
|
|
4081
|
+
decrementPendingObservation();
|
|
4082
|
+
}
|
|
3846
4083
|
const teardowns = Array.from(record.teardowns).reverse();
|
|
3847
4084
|
record.teardowns.clear();
|
|
3848
4085
|
for (const teardown of teardowns) {
|
|
@@ -4532,11 +4769,9 @@ function activateContent(content, loopContext, context) {
|
|
|
4532
4769
|
const bindings = getBindingsByContent(content);
|
|
4533
4770
|
const session = getBindingSessionByContent(content);
|
|
4534
4771
|
if (session !== null) {
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
applyOnReconnect: false,
|
|
4539
|
-
});
|
|
4772
|
+
// createContent 側の initialize で remember 済みの同一 binding 配列なので、
|
|
4773
|
+
// remember を再実行しない専用パスで活性化する(リスト行生成のホットパス)
|
|
4774
|
+
session.activate(bindings);
|
|
4540
4775
|
}
|
|
4541
4776
|
for (const binding of bindings) {
|
|
4542
4777
|
if (session === null) {
|
|
@@ -4623,6 +4858,10 @@ class Content {
|
|
|
4623
4858
|
}
|
|
4624
4859
|
appendTo(targetNode) {
|
|
4625
4860
|
for (const node of this._childNodeArray) {
|
|
4861
|
+
// framework 起点のマウントを observer に伝える。中間 fragment へ append する
|
|
4862
|
+
// 経路でも、後続の一括 insertBefore(fragment) の mutation record には
|
|
4863
|
+
// この top-level node が addedNodes として現れるため、ここでのマークが届く。
|
|
4864
|
+
markObserverSkipOnAdd(node);
|
|
4626
4865
|
targetNode.appendChild(node);
|
|
4627
4866
|
}
|
|
4628
4867
|
this._mounted = true;
|
|
@@ -4632,14 +4871,51 @@ class Content {
|
|
|
4632
4871
|
const nextSibling = targetNode.nextSibling;
|
|
4633
4872
|
if (parentNode) {
|
|
4634
4873
|
for (const node of this._childNodeArray) {
|
|
4874
|
+
markObserverSkipOnAdd(node);
|
|
4635
4875
|
parentNode.insertBefore(node, nextSibling);
|
|
4636
4876
|
}
|
|
4637
4877
|
}
|
|
4638
4878
|
this._mounted = true;
|
|
4639
4879
|
}
|
|
4880
|
+
tryDestroy() {
|
|
4881
|
+
const session = getBindingSessionByContent(this);
|
|
4882
|
+
// session 無し(SSR ハイドレーション産)や、定義待ち・connect-snapshot 待ちを
|
|
4883
|
+
// 抱える content は teardown 省略でリークするため従来経路に倒す。
|
|
4884
|
+
if (session === null || !session.canWholesaleDestroy()) {
|
|
4885
|
+
return false;
|
|
4886
|
+
}
|
|
4887
|
+
session.destroyRecords();
|
|
4888
|
+
for (const node of this._childNodeArray) {
|
|
4889
|
+
// unmount と同じ理由の observer 向け削除マーク(clear の一括削除でも
|
|
4890
|
+
// top-level node が mutation record の root に現れる)
|
|
4891
|
+
markObserverSkipOnRemove(node);
|
|
4892
|
+
if (node.parentNode !== null) {
|
|
4893
|
+
node.parentNode.removeChild(node);
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
const bindings = getBindingsByContent(this);
|
|
4897
|
+
for (const binding of bindings) {
|
|
4898
|
+
if (recursiveBindingTypes.has(binding.bindingType)) {
|
|
4899
|
+
const contents = getContentSetByNode(binding.node);
|
|
4900
|
+
for (const content of contents) {
|
|
4901
|
+
if (!content.tryDestroy()) {
|
|
4902
|
+
content.unmount();
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
this._mounted = false;
|
|
4908
|
+
return true;
|
|
4909
|
+
}
|
|
4640
4910
|
unmount() {
|
|
4641
4911
|
getBindingSessionByContent(this)?.dispose();
|
|
4642
4912
|
for (const node of this._childNodeArray) {
|
|
4913
|
+
// framework 起点の削除であることを observer に伝える。clear の
|
|
4914
|
+
// parentNode.textContent='' 一括削除でも、この top-level node が
|
|
4915
|
+
// 削除サブツリーの root として mutation record に現れるため、ここで
|
|
4916
|
+
// マークしておけば observer の冗長走査をスキップできる。マークは
|
|
4917
|
+
// 同期実行中に立ち、observer は次 microtask で読むので順序は保証される。
|
|
4918
|
+
markObserverSkipOnRemove(node);
|
|
4643
4919
|
if (node.parentNode !== null) {
|
|
4644
4920
|
node.parentNode.removeChild(node);
|
|
4645
4921
|
}
|
|
@@ -4804,10 +5080,10 @@ function applyChangeToFor(bindingInfo, context, newValue) {
|
|
|
4804
5080
|
const lastValue = getLastListValueByAbsoluteStateAddress(absAddress);
|
|
4805
5081
|
const diff = createListDiff(listIndex, lastValue, newValue);
|
|
4806
5082
|
context.newListValueByAbsAddress.set(absAddress, Array.isArray(newValue) ? newValue : []);
|
|
4807
|
-
|
|
5083
|
+
const fullDelete = Array.isArray(lastValue)
|
|
4808
5084
|
&& lastValue.length === diff.deleteIndexSet.size
|
|
4809
|
-
&& diff.deleteIndexSet.size > 0
|
|
4810
|
-
|
|
5085
|
+
&& diff.deleteIndexSet.size > 0;
|
|
5086
|
+
if (fullDelete && bindingInfo.node.parentNode !== null) {
|
|
4811
5087
|
let isOnlyNode = isOnlyNodeInParentContentByNode.get(bindingInfo.node);
|
|
4812
5088
|
if (typeof isOnlyNode === 'undefined') {
|
|
4813
5089
|
const lastNode = lastNodeByNode.get(bindingInfo.node) || bindingInfo.node;
|
|
@@ -4820,12 +5096,25 @@ function applyChangeToFor(bindingInfo, context, newValue) {
|
|
|
4820
5096
|
parentNode.appendChild(bindingInfo.node);
|
|
4821
5097
|
}
|
|
4822
5098
|
}
|
|
5099
|
+
// 全削除時、プールに収まらない content は再利用されないため、per-binding の
|
|
5100
|
+
// teardown(listener 解除・アドレス台帳・loopContext 掃除)を丸ごと省略して
|
|
5101
|
+
// ノードごと GC に任せる(tryDestroy)。プール行きの分だけ従来どおり解体する
|
|
5102
|
+
// (プール行は binding が生存し続けるため address キャッシュのクリアが必須)。
|
|
5103
|
+
let poolBudget = fullDelete
|
|
5104
|
+
? maxPooledContents - getPooledContents(bindingInfo).length
|
|
5105
|
+
: Number.POSITIVE_INFINITY;
|
|
4823
5106
|
for (const deleteIndex of diff.deleteIndexSet) {
|
|
4824
5107
|
const content = getContent(bindingInfo.node, deleteIndex);
|
|
4825
5108
|
if (content !== null) {
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
5109
|
+
if (poolBudget <= 0 && content.tryDestroy()) {
|
|
5110
|
+
deleteContentByNode(bindingInfo.node, content);
|
|
5111
|
+
}
|
|
5112
|
+
else {
|
|
5113
|
+
deactivateContent(content);
|
|
5114
|
+
content.unmount();
|
|
5115
|
+
setPooledContent(bindingInfo, content);
|
|
5116
|
+
poolBudget -= 1;
|
|
5117
|
+
}
|
|
4829
5118
|
setContent(bindingInfo.node, deleteIndex, null);
|
|
4830
5119
|
}
|
|
4831
5120
|
}
|
|
@@ -5406,6 +5695,10 @@ const applyChangeByBindingType = {
|
|
|
5406
5695
|
};
|
|
5407
5696
|
const fnByBinding = new WeakMap();
|
|
5408
5697
|
const deferredSelectBindingByBinding = new WeakMap();
|
|
5698
|
+
// 未 define カスタム要素チェックの確定メモ。customTag が無い、または define 済みを
|
|
5699
|
+
// 一度確認したら以後は不変(define は不可逆)なので apply 毎の getCustomElement /
|
|
5700
|
+
// registry 照会を省略できる。scoped registry を導入する場合はこの不可逆前提を再検討。
|
|
5701
|
+
const definedApplyVerifiedByBinding = new WeakMap();
|
|
5409
5702
|
function _applyChange(binding, context) {
|
|
5410
5703
|
const value = getValue(context.state, binding);
|
|
5411
5704
|
const filteredValue = getFilteredValue(value, binding.outFilters);
|
|
@@ -5491,16 +5784,20 @@ function applyChange(binding, context) {
|
|
|
5491
5784
|
if (binding.bindingType === "event") {
|
|
5492
5785
|
return;
|
|
5493
5786
|
}
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
if (
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5787
|
+
if (definedApplyVerifiedByBinding.get(binding) !== true) {
|
|
5788
|
+
const customTag = getCustomElement(binding.replaceNode);
|
|
5789
|
+
if (customTag) {
|
|
5790
|
+
if (getCustomElementRegistry()?.get(customTag) === undefined) {
|
|
5791
|
+
// 未 define のカスタム要素へは今は適用できない(accessor 未確立の要素に
|
|
5792
|
+
// 素の own property を書くと upgrade 後に class accessor を隠してしまう)。
|
|
5793
|
+
// whenDefined 後に最新 state 値で再適用する(two-way attach / deferred
|
|
5794
|
+
// spread と対称。docs/state-binding-init-races.md §2)。
|
|
5795
|
+
scheduleDeferredApply(binding, customTag);
|
|
5796
|
+
return;
|
|
5797
|
+
}
|
|
5503
5798
|
}
|
|
5799
|
+
// customTag 無し or define 済み確定 → 以後この検査を省略(不可逆)
|
|
5800
|
+
definedApplyVerifiedByBinding.set(binding, true);
|
|
5504
5801
|
}
|
|
5505
5802
|
// applyChangeFromBindings のグループ化ループが解決済みルートの一致を検証済みの
|
|
5506
5803
|
// 場合、stateName さえ一致すれば getRootNode の再解決(native 呼び出し)を省略
|
|
@@ -5853,8 +6150,24 @@ function getFragmentNodeInfos(fragment) {
|
|
|
5853
6150
|
const subscriberNodes = getSubscriberNodes(fragment);
|
|
5854
6151
|
for (const subscriberNode of subscriberNodes) {
|
|
5855
6152
|
const parseBindingTextResults = getParseBindTextResults(subscriberNode);
|
|
6153
|
+
let node = subscriberNode;
|
|
6154
|
+
// テンプレート登録時の事前正規化: text 専用の wcs-text コメントは、この時点で
|
|
6155
|
+
// 空 Text に置き換えておく。行 clone は最初から Text を持ち、getBindingInfos が
|
|
6156
|
+
// その Text を replaceNode に使うため、行ごとの createTextNode と start() 時の
|
|
6157
|
+
// replaceChild(コメント→Text 差し替え)が丸ごと不要になる。
|
|
6158
|
+
// 置換は同じ位置なので nodePath は不変。wcs-for/if 等の構造コメントは
|
|
6159
|
+
// アンカーとしてコメントのまま維持する(bindingType で判別)。
|
|
6160
|
+
// 非フラグメント経路(実 DOM 上のコメント)は従来どおり実行時に差し替える。
|
|
6161
|
+
if (subscriberNode.nodeType === Node.COMMENT_NODE
|
|
6162
|
+
&& parseBindingTextResults.length === 1
|
|
6163
|
+
&& parseBindingTextResults[0].bindingType === "text"
|
|
6164
|
+
&& subscriberNode.parentNode !== null) {
|
|
6165
|
+
const textNode = document.createTextNode("");
|
|
6166
|
+
subscriberNode.parentNode.replaceChild(textNode, subscriberNode);
|
|
6167
|
+
node = textNode;
|
|
6168
|
+
}
|
|
5856
6169
|
fragmnentNodeInfos.push({
|
|
5857
|
-
nodePath: getNodePath(
|
|
6170
|
+
nodePath: getNodePath(node),
|
|
5858
6171
|
parseBindTextResults: parseBindingTextResults,
|
|
5859
6172
|
});
|
|
5860
6173
|
}
|
|
@@ -6058,7 +6371,7 @@ async function buildBindings(root) {
|
|
|
6058
6371
|
}
|
|
6059
6372
|
}
|
|
6060
6373
|
|
|
6061
|
-
var version = "1.21.
|
|
6374
|
+
var version = "1.21.4";
|
|
6062
6375
|
var pkg = {
|
|
6063
6376
|
version: version};
|
|
6064
6377
|
|
|
@@ -8538,7 +8851,6 @@ const _cache = new Map();
|
|
|
8538
8851
|
class ResolvedAddress {
|
|
8539
8852
|
path;
|
|
8540
8853
|
segments;
|
|
8541
|
-
paths;
|
|
8542
8854
|
wildcardCount;
|
|
8543
8855
|
wildcardType;
|
|
8544
8856
|
wildcardIndexes;
|
|
@@ -8555,10 +8867,8 @@ class ResolvedAddress {
|
|
|
8555
8867
|
// Split path into individual segments
|
|
8556
8868
|
const segments = path.split(".");
|
|
8557
8869
|
const tmpPatternSegments = segments.slice();
|
|
8558
|
-
const paths = [];
|
|
8559
8870
|
let incompleteCount = 0; // Count of unresolved wildcards (*)
|
|
8560
8871
|
let completeCount = 0; // Count of resolved wildcards (numeric indexes)
|
|
8561
|
-
let lastPath = "";
|
|
8562
8872
|
let wildcardCount = 0;
|
|
8563
8873
|
let wildcardType = "none";
|
|
8564
8874
|
const wildcardIndexes = [];
|
|
@@ -8582,10 +8892,6 @@ class ResolvedAddress {
|
|
|
8582
8892
|
wildcardCount++;
|
|
8583
8893
|
}
|
|
8584
8894
|
}
|
|
8585
|
-
// Build cumulative path array
|
|
8586
|
-
lastPath += segment;
|
|
8587
|
-
paths.push(lastPath);
|
|
8588
|
-
lastPath += (i < segment.length - 1 ? "." : "");
|
|
8589
8895
|
}
|
|
8590
8896
|
// Generate pattern string with wildcards normalized
|
|
8591
8897
|
const structuredPath = tmpPatternSegments.join(".");
|
|
@@ -8607,7 +8913,6 @@ class ResolvedAddress {
|
|
|
8607
8913
|
}
|
|
8608
8914
|
this.path = path;
|
|
8609
8915
|
this.segments = segments;
|
|
8610
|
-
this.paths = paths;
|
|
8611
8916
|
this.wildcardCount = wildcardCount;
|
|
8612
8917
|
this.wildcardType = wildcardType;
|
|
8613
8918
|
this.wildcardIndexes = wildcardIndexes;
|
|
@@ -8950,6 +9255,7 @@ function _walkExpandWildcard(context, currentWildcardIndex, parentListIndex) {
|
|
|
8950
9255
|
}
|
|
8951
9256
|
}
|
|
8952
9257
|
}
|
|
9258
|
+
const EMPTY_INDEXES = [];
|
|
8953
9259
|
/**
|
|
8954
9260
|
* 静的子展開で訪問する listIndex 群を選ぶ。"diff" でも次の場合は全行に倒す:
|
|
8955
9261
|
* - diff に変化が一切見えない再代入(同一参照および内容同一コピーの再代入。
|
|
@@ -8960,26 +9266,60 @@ function _walkExpandWildcard(context, currentWildcardIndex, parentListIndex) {
|
|
|
8960
9266
|
*/
|
|
8961
9267
|
function selectExpansionIndexes(context, sourcePath, _lastValue, _newValue, listDiff) {
|
|
8962
9268
|
if (context.listExpansion === "full") {
|
|
8963
|
-
return listDiff.newIndexes;
|
|
9269
|
+
return { fullRows: listDiff.newIndexes, movedRows: null };
|
|
8964
9270
|
}
|
|
8965
9271
|
if (context.stateElement.crossRowListPaths?.has(sourcePath)) {
|
|
8966
|
-
return listDiff.newIndexes;
|
|
9272
|
+
return { fullRows: listDiff.newIndexes, movedRows: null };
|
|
8967
9273
|
}
|
|
8968
9274
|
if (listDiff.addIndexSet.size === 0 && listDiff.changeIndexSet.size === 0) {
|
|
8969
9275
|
// 追加も移動も無い。削除も無ければ「変化が見えない再代入」= リフレッシュ意図
|
|
8970
9276
|
if (listDiff.deleteIndexSet.size === 0) {
|
|
8971
|
-
return listDiff.newIndexes;
|
|
9277
|
+
return { fullRows: listDiff.newIndexes, movedRows: null };
|
|
8972
9278
|
}
|
|
8973
9279
|
// 削除のみ: 残存行は位置も値も不変なので展開しない
|
|
8974
|
-
return
|
|
9280
|
+
return { fullRows: EMPTY_INDEXES, movedRows: null };
|
|
8975
9281
|
}
|
|
8976
|
-
|
|
8977
|
-
|
|
9282
|
+
return { fullRows: listDiff.addIndexSet, movedRows: listDiff.changeIndexSet };
|
|
9283
|
+
}
|
|
9284
|
+
const EMPTY_PATH_INFOS = [];
|
|
9285
|
+
/**
|
|
9286
|
+
* 位置だけが変わった行(movedRows)で展開すべきパス群を求める。
|
|
9287
|
+
* `${listPath}.*` の静的 subtree を辿り、$1 等を読んだ実績のある getter
|
|
9288
|
+
* (indexDependentGetterPaths)だけを返す。行の同一性・listIndex は保たれ
|
|
9289
|
+
* index 以外の入力が不変なので、index を読まない getter / 値パスは再評価不要。
|
|
9290
|
+
* 戻り値:
|
|
9291
|
+
* - IPathInfo[](空可): この各パスだけを行の listIndex で展開する
|
|
9292
|
+
* - null: ネストしたワイルドカード配下に index 依存 getter がある
|
|
9293
|
+
* (listIndex の階数が合わず個別展開できない)→ 呼び出し側で行全体展開に倒す
|
|
9294
|
+
*/
|
|
9295
|
+
function getMovedRowExpansionPaths(context, wildcardPath, depPathInfo) {
|
|
9296
|
+
const indexGetters = context.stateElement.indexDependentGetterPaths;
|
|
9297
|
+
if (!indexGetters || indexGetters.size === 0) {
|
|
9298
|
+
return EMPTY_PATH_INFOS;
|
|
8978
9299
|
}
|
|
8979
|
-
|
|
8980
|
-
|
|
9300
|
+
let result = null;
|
|
9301
|
+
const queue = [wildcardPath];
|
|
9302
|
+
const seen = new Set(queue);
|
|
9303
|
+
for (let i = 0; i < queue.length; i++) {
|
|
9304
|
+
const path = queue[i];
|
|
9305
|
+
if (indexGetters.has(path)) {
|
|
9306
|
+
const pathInfo = getPathInfo(path);
|
|
9307
|
+
if (pathInfo.wildcardCount !== depPathInfo.wildcardCount) {
|
|
9308
|
+
return null;
|
|
9309
|
+
}
|
|
9310
|
+
(result ??= []).push(pathInfo);
|
|
9311
|
+
}
|
|
9312
|
+
const children = context.staticMap.get(path);
|
|
9313
|
+
if (children) {
|
|
9314
|
+
for (const child of children) {
|
|
9315
|
+
if (!seen.has(child)) {
|
|
9316
|
+
seen.add(child);
|
|
9317
|
+
queue.push(child);
|
|
9318
|
+
}
|
|
9319
|
+
}
|
|
9320
|
+
}
|
|
8981
9321
|
}
|
|
8982
|
-
return
|
|
9322
|
+
return result ?? EMPTY_PATH_INFOS;
|
|
8983
9323
|
}
|
|
8984
9324
|
function _walkDependency(context, startAddress, callback) {
|
|
8985
9325
|
const stack = [{ address: startAddress, depth: 0 }];
|
|
@@ -9013,11 +9353,35 @@ function _walkDependency(context, startAddress, callback) {
|
|
|
9013
9353
|
const absAddress = createAbsoluteStateAddress(absPathInfo, address.listIndex);
|
|
9014
9354
|
const lastValue = getLastListValueByAbsoluteStateAddress(absAddress);
|
|
9015
9355
|
const listDiff = createListDiff(address.listIndex, lastValue, newValue);
|
|
9016
|
-
|
|
9356
|
+
const selection = selectExpansionIndexes(context, sourcePath, lastValue, newValue, listDiff);
|
|
9357
|
+
for (const listIndex of selection.fullRows) {
|
|
9017
9358
|
const depAddress = createStateAddress(depPathInfo, listIndex);
|
|
9018
9359
|
context.result.add(depAddress);
|
|
9019
9360
|
nextEntries.push({ address: depAddress, depth: nextDepth });
|
|
9020
9361
|
}
|
|
9362
|
+
if (selection.movedRows !== null) {
|
|
9363
|
+
const movedPathInfos = getMovedRowExpansionPaths(context, dep, depPathInfo);
|
|
9364
|
+
if (movedPathInfos === null) {
|
|
9365
|
+
// ネスト配下に index 依存 getter: 安全側で行全体を展開(従来挙動)
|
|
9366
|
+
for (const listIndex of selection.movedRows) {
|
|
9367
|
+
const depAddress = createStateAddress(depPathInfo, listIndex);
|
|
9368
|
+
context.result.add(depAddress);
|
|
9369
|
+
nextEntries.push({ address: depAddress, depth: nextDepth });
|
|
9370
|
+
}
|
|
9371
|
+
}
|
|
9372
|
+
else if (movedPathInfos.length > 0) {
|
|
9373
|
+
// 位置のみ変わった行は index 依存 getter のパスだけを展開する
|
|
9374
|
+
for (const listIndex of selection.movedRows) {
|
|
9375
|
+
for (const pathInfo of movedPathInfos) {
|
|
9376
|
+
const depAddress = createStateAddress(pathInfo, listIndex);
|
|
9377
|
+
context.result.add(depAddress);
|
|
9378
|
+
nextEntries.push({ address: depAddress, depth: nextDepth });
|
|
9379
|
+
}
|
|
9380
|
+
}
|
|
9381
|
+
}
|
|
9382
|
+
// movedPathInfos が空: index を読む getter が subtree に無い =
|
|
9383
|
+
// 位置のみ変わった行の値は不変。展開・dirty 化とも不要。
|
|
9384
|
+
}
|
|
9021
9385
|
}
|
|
9022
9386
|
else {
|
|
9023
9387
|
const depAddress = createStateAddress(depPathInfo, address.listIndex);
|
|
@@ -9108,6 +9472,15 @@ function _walkDependency(context, startAddress, callback) {
|
|
|
9108
9472
|
}
|
|
9109
9473
|
}
|
|
9110
9474
|
function walkDependency(stateName, stateElement, startAddress, staticDependency, dynamicDependency, listPathSet, stateProxy, searchType, callback, options) {
|
|
9475
|
+
// 依存ゼロの葉パス(staticMap / dynamicMap にエントリ無し)は context や Set を
|
|
9476
|
+
// 割り当てず、開始アドレスの callback だけで完結する。リスト行の値書き込み
|
|
9477
|
+
// (update ホットパス)は set 毎にここを通る。開始アドレスへの callback は
|
|
9478
|
+
// 従来の walk 先頭と同一で、戻り値(依存アドレス群)も従来どおり空。
|
|
9479
|
+
const startPath = startAddress.pathInfo.path;
|
|
9480
|
+
if (!staticDependency.has(startPath) && !dynamicDependency.has(startPath)) {
|
|
9481
|
+
callback(startAddress);
|
|
9482
|
+
return [];
|
|
9483
|
+
}
|
|
9111
9484
|
const context = {
|
|
9112
9485
|
stateElement: stateElement,
|
|
9113
9486
|
staticMap: staticDependency,
|
|
@@ -9140,6 +9513,31 @@ function walkDependency(stateName, stateElement, startAddress, staticDependency,
|
|
|
9140
9513
|
* - finallyで必ず更新情報を登録し、再描画や依存解決に利用
|
|
9141
9514
|
* - getter/setter経由のスコープ切り替えも考慮した設計
|
|
9142
9515
|
*/
|
|
9516
|
+
// Phase 3: 書き込み時点の因果 context を update record に付与する。
|
|
9517
|
+
// binding 経由の書き込みは呼び出し元の dynamic scope から context を引き継ぎ、
|
|
9518
|
+
// binding 外からの API update は新しい transaction を開始する(設計書 §4 規則 1)。
|
|
9519
|
+
// 依存 walk で enqueue される派生アドレスも同じ書き込みの因果に属する。
|
|
9520
|
+
function notifyWrite(address, absAddress, receiver, handler) {
|
|
9521
|
+
const propagationContext = config.enablePropagationContext
|
|
9522
|
+
? (getCurrentPropagationContext() ?? beginPropagationTransaction(-1))
|
|
9523
|
+
: null;
|
|
9524
|
+
const updater = getUpdater();
|
|
9525
|
+
updater.enqueueAbsoluteAddress(absAddress, propagationContext);
|
|
9526
|
+
// 依存関係のあるキャッシュを無効化(ダーティ)、更新対象として登録
|
|
9527
|
+
walkDependency(handler.stateName, handler.stateElement, address, handler.stateElement.staticDependency, handler.stateElement.dynamicDependency, handler.stateElement.listPaths, receiver, "new", (depAddress) => {
|
|
9528
|
+
// キャッシュを無効化(ダーティ)
|
|
9529
|
+
if (depAddress === address)
|
|
9530
|
+
return;
|
|
9531
|
+
const absDepPathInfo = getAbsolutePathInfo(handler.stateElement, depAddress.pathInfo);
|
|
9532
|
+
const absDepAddress = createAbsoluteStateAddress(absDepPathInfo, depAddress.listIndex);
|
|
9533
|
+
dirtyCacheEntryByAbsoluteStateAddress(absDepAddress);
|
|
9534
|
+
// 更新対象として登録
|
|
9535
|
+
updater.enqueueAbsoluteAddress(absDepAddress, propagationContext);
|
|
9536
|
+
},
|
|
9537
|
+
// リスト置換時は追加行・位置変更行のみ展開する(未変更行の再訪を省く。
|
|
9538
|
+
// $postUpdate の手動リフレッシュは従来通り全行展開のまま)
|
|
9539
|
+
{ listExpansion: "diff" });
|
|
9540
|
+
}
|
|
9143
9541
|
function _setByAddress(target, address, absAddress, value, receiver, handler) {
|
|
9144
9542
|
try {
|
|
9145
9543
|
if (address.pathInfo.path in target) {
|
|
@@ -9174,29 +9572,7 @@ function _setByAddress(target, address, absAddress, value, receiver, handler) {
|
|
|
9174
9572
|
}
|
|
9175
9573
|
}
|
|
9176
9574
|
finally {
|
|
9177
|
-
|
|
9178
|
-
// binding 経由の書き込みは呼び出し元の dynamic scope から context を引き継ぎ、
|
|
9179
|
-
// binding 外からの API update は新しい transaction を開始する(設計書 §4 規則 1)。
|
|
9180
|
-
// 依存 walk で enqueue される派生アドレスも同じ書き込みの因果に属する。
|
|
9181
|
-
const propagationContext = config.enablePropagationContext
|
|
9182
|
-
? (getCurrentPropagationContext() ?? beginPropagationTransaction(-1))
|
|
9183
|
-
: null;
|
|
9184
|
-
const updater = getUpdater();
|
|
9185
|
-
updater.enqueueAbsoluteAddress(absAddress, propagationContext);
|
|
9186
|
-
// 依存関係のあるキャッシュを無効化(ダーティ)、更新対象として登録
|
|
9187
|
-
walkDependency(handler.stateName, handler.stateElement, address, handler.stateElement.staticDependency, handler.stateElement.dynamicDependency, handler.stateElement.listPaths, receiver, "new", (depAddress) => {
|
|
9188
|
-
// キャッシュを無効化(ダーティ)
|
|
9189
|
-
if (depAddress === address)
|
|
9190
|
-
return;
|
|
9191
|
-
const absDepPathInfo = getAbsolutePathInfo(handler.stateElement, depAddress.pathInfo);
|
|
9192
|
-
const absDepAddress = createAbsoluteStateAddress(absDepPathInfo, depAddress.listIndex);
|
|
9193
|
-
dirtyCacheEntryByAbsoluteStateAddress(absDepAddress);
|
|
9194
|
-
// 更新対象として登録
|
|
9195
|
-
updater.enqueueAbsoluteAddress(absDepAddress, propagationContext);
|
|
9196
|
-
},
|
|
9197
|
-
// リスト置換時は追加行・位置変更行のみ展開する(未変更行の再訪を省く。
|
|
9198
|
-
// $postUpdate の手動リフレッシュは従来通り全行展開のまま)
|
|
9199
|
-
{ listExpansion: "diff" });
|
|
9575
|
+
notifyWrite(address, absAddress, receiver, handler);
|
|
9200
9576
|
}
|
|
9201
9577
|
}
|
|
9202
9578
|
function _setByAddressWithSwap(target, address, absAddress, value, receiver, handler) {
|
|
@@ -9237,6 +9613,75 @@ function _setByAddressWithSwap(target, address, absAddress, value, receiver, han
|
|
|
9237
9613
|
}
|
|
9238
9614
|
function setByAddress(target, address, value, receiver, handler) {
|
|
9239
9615
|
const stateElement = handler.stateElement;
|
|
9616
|
+
const path = address.pathInfo.path;
|
|
9617
|
+
// --- fast path: 宣言済み getter/setter でも swap 対象でもない、親を持つ葉パス ---
|
|
9618
|
+
// 従来は same-value guard の値読み・hasByAddress・実書き込みがそれぞれ親チェーンを
|
|
9619
|
+
// 解決していた(キャッシュヒットでも getByAddress 呼び出しの固定費 ×3)。
|
|
9620
|
+
// 親を 1 回だけ解決し、同じ親オブジェクトに対して guard 判定と Reflect.set を行う。
|
|
9621
|
+
// 非オブジェクト親などの例外形は従来経路へ倒し、挙動差を作らない。
|
|
9622
|
+
if (!(path in target) && address.parentAddress !== null && !stateElement.elementPaths.has(path)) {
|
|
9623
|
+
const parentValue = getByAddress(target, address.parentAddress, receiver, handler);
|
|
9624
|
+
if (typeof parentValue === "object" && parentValue !== null) {
|
|
9625
|
+
// ワイルドカード末尾で listIndex が無い不正アドレスは、従来どおり
|
|
9626
|
+
// 書き込み時(enqueue 済みの try 内)に raiseError する → key は undefined のまま持ち回す
|
|
9627
|
+
const lastSegment = address.pathInfo.lastSegment;
|
|
9628
|
+
const key = lastSegment === WILDCARD
|
|
9629
|
+
? address.listIndex?.index
|
|
9630
|
+
: lastSegment;
|
|
9631
|
+
let devOldValue;
|
|
9632
|
+
let devHasOldValue = false;
|
|
9633
|
+
if (config.sameValueGuard && (value === null || typeof value !== "object")) {
|
|
9634
|
+
// hasByAddress と同じ「初期化済みスロットか」判定(undefined 格納と未初期化を区別)
|
|
9635
|
+
const has = key !== undefined && key in parentValue;
|
|
9636
|
+
const oldValue = key !== undefined ? parentValue[key] : undefined;
|
|
9637
|
+
if (has && Object.is(oldValue, value)) {
|
|
9638
|
+
return true;
|
|
9639
|
+
}
|
|
9640
|
+
devOldValue = oldValue;
|
|
9641
|
+
devHasOldValue = true;
|
|
9642
|
+
}
|
|
9643
|
+
const cacheable = address.pathInfo.wildcardCount > 0 ||
|
|
9644
|
+
stateElement.getterPaths.has(path);
|
|
9645
|
+
const absPathInfo = getAbsolutePathInfo(stateElement, address.pathInfo);
|
|
9646
|
+
const absAddress = createAbsoluteStateAddress(absPathInfo, address.listIndex);
|
|
9647
|
+
if (devtoolsSink !== null) {
|
|
9648
|
+
devtoolsSink({
|
|
9649
|
+
type: "state:write",
|
|
9650
|
+
absoluteAddress: absAddress,
|
|
9651
|
+
value,
|
|
9652
|
+
oldValue: devOldValue,
|
|
9653
|
+
hasOldValue: devHasOldValue,
|
|
9654
|
+
});
|
|
9655
|
+
}
|
|
9656
|
+
try {
|
|
9657
|
+
if (key === undefined) {
|
|
9658
|
+
raiseError(`address.listIndex?.index is undefined path: ${path}`);
|
|
9659
|
+
}
|
|
9660
|
+
return Reflect.set(parentValue, key, value);
|
|
9661
|
+
}
|
|
9662
|
+
finally {
|
|
9663
|
+
notifyWrite(address, absAddress, receiver, handler);
|
|
9664
|
+
if (cacheable) {
|
|
9665
|
+
setCacheEntryByAbsoluteStateAddress(absAddress, {
|
|
9666
|
+
value: value,
|
|
9667
|
+
dirty: false
|
|
9668
|
+
});
|
|
9669
|
+
}
|
|
9670
|
+
// DCC bindable イベントディスパッチ
|
|
9671
|
+
const eventName = stateElement.bindableEventMap[path];
|
|
9672
|
+
if (eventName) {
|
|
9673
|
+
const rootNode = stateElement.rootNode;
|
|
9674
|
+
if (rootNode instanceof ShadowRoot) {
|
|
9675
|
+
rootNode.host.dispatchEvent(new CustomEvent(eventName, {
|
|
9676
|
+
detail: value,
|
|
9677
|
+
bubbles: true,
|
|
9678
|
+
}));
|
|
9679
|
+
}
|
|
9680
|
+
}
|
|
9681
|
+
}
|
|
9682
|
+
}
|
|
9683
|
+
}
|
|
9684
|
+
// --- end fast path ---
|
|
9240
9685
|
// --- same-value guard (config.sameValueGuard・既定 ON) ---
|
|
9241
9686
|
// primitive 値かつ Object.is 同値なら、set / enqueue / walkDependency / DOM 適用 /
|
|
9242
9687
|
// $updatedCallback / DCC イベントを丸ごとスキップ(標準的なリアクティブ no-op)。
|
|
@@ -9658,13 +10103,33 @@ async function setLoopContextAsync(handler, loopContext, callback) {
|
|
|
9658
10103
|
// `$streamStatus.<name>` / `$streamError.<name>` の dotted パス判定用プレフィックス
|
|
9659
10104
|
const STREAM_STATUS_PATH_PREFIX = `${STATE_STREAM_STATUS_NAMESPACE_NAME}${DELIMITER}`;
|
|
9660
10105
|
const STREAM_ERROR_PATH_PREFIX = `${STATE_STREAM_ERROR_NAMESPACE_NAME}${DELIMITER}`;
|
|
10106
|
+
// symbol API のクロージャは handler(= proxy と 1:1、target/receiver 不変)ごとに
|
|
10107
|
+
// 使い回す。drain の getValue が binding ごとに getByAddressSymbol を引くため、
|
|
10108
|
+
// 毎回の新規クロージャ生成が GC 圧・固定費になっていた。
|
|
10109
|
+
const symbolApiCacheByHandler = new WeakMap();
|
|
10110
|
+
function getSymbolApiCache(handler) {
|
|
10111
|
+
let cache = symbolApiCacheByHandler.get(handler);
|
|
10112
|
+
if (typeof cache === "undefined") {
|
|
10113
|
+
cache = new Map();
|
|
10114
|
+
symbolApiCacheByHandler.set(handler, cache);
|
|
10115
|
+
}
|
|
10116
|
+
return cache;
|
|
10117
|
+
}
|
|
9661
10118
|
function get(target, prop, receiver, handler) {
|
|
9662
10119
|
const index = INDEX_BY_INDEX_NAME[prop];
|
|
9663
10120
|
if (typeof index !== "undefined") {
|
|
9664
10121
|
if (handler.addressStackLength === 0) {
|
|
9665
10122
|
raiseError(`No active state reference to get list index for "${prop.toString()}".`);
|
|
9666
10123
|
}
|
|
9667
|
-
const
|
|
10124
|
+
const lastAddress = handler.lastAddressStack;
|
|
10125
|
+
// getter 評価中のインデックス読み取りを記録する。位置だけが変わった行
|
|
10126
|
+
// (listDiff.changeIndexSet)は index 以外の入力が不変なので、walkDependency の
|
|
10127
|
+
// 静的子展開を「インデックスを読んだ getter の subtree」に限定できる。
|
|
10128
|
+
const lastInfo = lastAddress?.pathInfo;
|
|
10129
|
+
if (lastInfo && handler.stateElement?.getterPaths.has(lastInfo.path)) {
|
|
10130
|
+
handler.stateElement.addIndexDependentGetterPath?.(lastInfo.path);
|
|
10131
|
+
}
|
|
10132
|
+
const listIndex = lastAddress?.listIndex;
|
|
9668
10133
|
return listIndex?.indexes[index] ?? raiseError(`ListIndex not found: ${prop.toString()}`);
|
|
9669
10134
|
}
|
|
9670
10135
|
if (typeof prop === "string") {
|
|
@@ -9719,49 +10184,67 @@ function get(target, prop, receiver, handler) {
|
|
|
9719
10184
|
return getByAddress(target, stateAddress, receiver, handler);
|
|
9720
10185
|
}
|
|
9721
10186
|
else if (typeof prop === "symbol") {
|
|
10187
|
+
const cache = getSymbolApiCache(handler);
|
|
10188
|
+
const cached = cache.get(prop);
|
|
10189
|
+
if (typeof cached !== "undefined") {
|
|
10190
|
+
return cached;
|
|
10191
|
+
}
|
|
10192
|
+
let api;
|
|
9722
10193
|
switch (prop) {
|
|
9723
10194
|
case setLoopContextAsyncSymbol: {
|
|
9724
|
-
|
|
10195
|
+
api = (loopContext, callback = async () => { }) => {
|
|
9725
10196
|
return setLoopContextAsync(handler, loopContext, callback);
|
|
9726
10197
|
};
|
|
10198
|
+
break;
|
|
9727
10199
|
}
|
|
9728
10200
|
case setLoopContextSymbol: {
|
|
9729
|
-
|
|
10201
|
+
api = (loopContext, callback = () => { }) => {
|
|
9730
10202
|
return setLoopContext(handler, loopContext, callback);
|
|
9731
10203
|
};
|
|
10204
|
+
break;
|
|
9732
10205
|
}
|
|
9733
10206
|
case getByAddressSymbol: {
|
|
9734
|
-
|
|
10207
|
+
api = (address) => {
|
|
9735
10208
|
return getByAddress(target, address, receiver, handler);
|
|
9736
10209
|
};
|
|
10210
|
+
break;
|
|
9737
10211
|
}
|
|
9738
10212
|
case hasByAddressSymbol: {
|
|
9739
|
-
|
|
10213
|
+
api = (address) => {
|
|
9740
10214
|
return hasByAddress(target, address, receiver, handler);
|
|
9741
10215
|
};
|
|
10216
|
+
break;
|
|
9742
10217
|
}
|
|
9743
10218
|
case setByAddressSymbol: {
|
|
9744
|
-
|
|
10219
|
+
api = (address, value) => {
|
|
9745
10220
|
return setByAddress(target, address, value, receiver, handler);
|
|
9746
10221
|
};
|
|
10222
|
+
break;
|
|
9747
10223
|
}
|
|
9748
10224
|
case connectedCallbackSymbol: {
|
|
9749
|
-
|
|
9750
|
-
return connectedCallback(target,
|
|
10225
|
+
api = () => {
|
|
10226
|
+
return connectedCallback(target, connectedCallbackSymbol, receiver);
|
|
9751
10227
|
};
|
|
10228
|
+
break;
|
|
9752
10229
|
}
|
|
9753
10230
|
case disconnectedCallbackSymbol: {
|
|
9754
|
-
|
|
9755
|
-
return disconnectedCallback(target,
|
|
10231
|
+
api = () => {
|
|
10232
|
+
return disconnectedCallback(target, disconnectedCallbackSymbol, receiver);
|
|
9756
10233
|
};
|
|
10234
|
+
break;
|
|
9757
10235
|
}
|
|
9758
10236
|
case updatedCallbackSymbol: {
|
|
9759
|
-
|
|
10237
|
+
api = (refs) => {
|
|
9760
10238
|
return updatedCallback(target, refs, receiver, handler);
|
|
9761
10239
|
};
|
|
10240
|
+
break;
|
|
10241
|
+
}
|
|
10242
|
+
default: {
|
|
10243
|
+
return Reflect.get(target, prop, receiver);
|
|
9762
10244
|
}
|
|
9763
10245
|
}
|
|
9764
|
-
|
|
10246
|
+
cache.set(prop, api);
|
|
10247
|
+
return api;
|
|
9765
10248
|
}
|
|
9766
10249
|
}
|
|
9767
10250
|
|
|
@@ -10300,6 +10783,9 @@ class State extends HTMLElementBase {
|
|
|
10300
10783
|
// 他行を読む getter が検出されたリストパス(diff-filter 展開の全行フォールバック対象)。
|
|
10301
10784
|
// 依存マップ(static/dynamic)と同様に追加のみ・クリアしない(安全側に固定される)。
|
|
10302
10785
|
_crossRowListPaths = new Set();
|
|
10786
|
+
// $1 等のインデックスを読んだ getter パス(実行時検出)。位置のみ変わった行の
|
|
10787
|
+
// 静的子展開はこの集合の subtree に限定される。追加のみ・クリアしない(安全側)。
|
|
10788
|
+
_indexDependentGetterPaths = new Set();
|
|
10303
10789
|
_name = 'default';
|
|
10304
10790
|
_initialized = false;
|
|
10305
10791
|
_initializePromise;
|
|
@@ -10799,6 +11285,12 @@ class State extends HTMLElementBase {
|
|
|
10799
11285
|
addCrossRowListPath(path) {
|
|
10800
11286
|
this._crossRowListPaths.add(path);
|
|
10801
11287
|
}
|
|
11288
|
+
get indexDependentGetterPaths() {
|
|
11289
|
+
return this._indexDependentGetterPaths;
|
|
11290
|
+
}
|
|
11291
|
+
addIndexDependentGetterPath(path) {
|
|
11292
|
+
this._indexDependentGetterPaths.add(path);
|
|
11293
|
+
}
|
|
10802
11294
|
bindProperty(prop, desc) {
|
|
10803
11295
|
Object.defineProperty(this._state, prop, desc);
|
|
10804
11296
|
if (prop === STATE_UPDATED_CALLBACK_NAME) {
|