@wcstack/state 1.22.5 → 1.23.0

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.esm.js CHANGED
@@ -5289,8 +5289,16 @@ class Content {
5289
5289
  _firstNode = null;
5290
5290
  _lastNode = null;
5291
5291
  _mounted = false;
5292
- constructor(content) {
5292
+ /**
5293
+ * 範囲モード。トップレベルに構造ディレクティブを持つ content でのみ true。
5294
+ * この content の実レンジは自分の childNodeArray より広い(ネストした
5295
+ * if/for が自分のアンカー直後に実ノードを挿すため)ので、移動は
5296
+ * firstNode..lastNode の DOM レンジで行う。
5297
+ */
5298
+ _ranged = false;
5299
+ constructor(content, ranged = false) {
5293
5300
  this._content = content;
5301
+ this._ranged = ranged;
5294
5302
  this._childNodeArray = Array.from(this._content.childNodes);
5295
5303
  this._firstNode = this._childNodeArray.length > 0 ? this._childNodeArray[0] : null;
5296
5304
  this._lastNode = this._childNodeArray.length > 0 ? this._childNodeArray[this._childNodeArray.length - 1] : null;
@@ -5314,6 +5322,31 @@ class Content {
5314
5322
  }
5315
5323
  this._mounted = true;
5316
5324
  }
5325
+ /**
5326
+ * 移動対象ノード列。通常は自分のトップレベルノードそのもの(同一参照を返すので
5327
+ * 追加アロケーション無し)。範囲モードでマウント中のときだけ、ネストした構造
5328
+ * ディレクティブが挿した実ノードを含む DOM レンジを収集する。
5329
+ */
5330
+ _movableNodes() {
5331
+ if (!this._ranged || !this._mounted) {
5332
+ return this._childNodeArray;
5333
+ }
5334
+ const first = this._firstNode;
5335
+ const last = this._lastNode;
5336
+ if (first === null || last === null || first.parentNode === null) {
5337
+ return this._childNodeArray;
5338
+ }
5339
+ // 移動でsiblingが変わるため、先に列を確定させてから動かす
5340
+ const nodes = [];
5341
+ for (let node = first; node !== null; node = node.nextSibling) {
5342
+ nodes.push(node);
5343
+ if (node === last) {
5344
+ return nodes;
5345
+ }
5346
+ }
5347
+ // last へ到達しない(想定外の不連続)→ 従来どおり自分のノードだけ動かす
5348
+ return this._childNodeArray;
5349
+ }
5317
5350
  mountAfter(targetNode) {
5318
5351
  const parentNode = targetNode.parentNode;
5319
5352
  if (parentNode) {
@@ -5323,7 +5356,7 @@ class Content {
5323
5356
  // 先頭ノードが末尾へ回転する。anchor を進めながら位置一致ノードを
5324
5357
  // スキップすることで冪等にする(mutation record も発生させない)。
5325
5358
  let anchor = targetNode;
5326
- for (const node of this._childNodeArray) {
5359
+ for (const node of this._movableNodes()) {
5327
5360
  if (anchor.nextSibling !== node) {
5328
5361
  markObserverSkipOnAdd(node);
5329
5362
  parentNode.insertBefore(node, anchor.nextSibling);
@@ -5390,6 +5423,53 @@ class Content {
5390
5423
  this._mounted = false;
5391
5424
  }
5392
5425
  }
5426
+ /**
5427
+ * トップレベルに構造ディレクティブのプレースホルダを持つか。
5428
+ *
5429
+ * これを持つ content は、ネストした if/for が「自分のアンカー直後」に実ノードを
5430
+ * 挿すため、自分の childNodeArray が実レンジより狭くなる。その状態だと
5431
+ * (1) 呼び出し側の位置追跡(applyChangeToFor の lastNode)が実ノードの手前で
5432
+ * 止まり後続行が割り込む、(2) 移動時にネスト分を置き去りにする、の 2 つが起きる。
5433
+ * 該当テンプレートにだけ終端マーカーを持たせて実レンジを閉じる。
5434
+ */
5435
+ function hasTopLevelStructural(fragment) {
5436
+ // config は setConfig で差し替わりうるので都度組む。テンプレート単位に一度しか
5437
+ // 走らない判定なので、この生成コストが行あたりに乗ることはない。
5438
+ const structural = new RegExp(`^@@(?:${config.commentForPrefix}|${config.commentIfPrefix}`
5439
+ + `|${config.commentElseIfPrefix}|${config.commentElsePrefix}):`);
5440
+ for (let node = fragment.firstChild; node !== null; node = node.nextSibling) {
5441
+ if (node.nodeType !== Node.COMMENT_NODE) {
5442
+ continue;
5443
+ }
5444
+ // Comment の data は常に文字列(textContent の null 分岐を持ち込まない)
5445
+ if (structural.test(node.data)) {
5446
+ return true;
5447
+ }
5448
+ }
5449
+ return false;
5450
+ }
5451
+ /**
5452
+ * 範囲モードの要否をテンプレート単位で一度だけ判定してキャッシュする。
5453
+ * 大多数のテンプレート(トップレベルが素の要素)は false を引くだけで、
5454
+ * 行ごとの追加コストはゼロ。
5455
+ */
5456
+ function resolveRanged(fragmentInfo) {
5457
+ let ranged = fragmentInfo.topLevelStructural;
5458
+ if (typeof ranged === 'undefined') {
5459
+ ranged = fragmentInfo.topLevelStructural = hasTopLevelStructural(fragmentInfo.fragment);
5460
+ }
5461
+ return ranged;
5462
+ }
5463
+ const ROW_END_PREFIX = 'wcs-row-end';
5464
+ /**
5465
+ * 終端マーカーの付与。バインディング初期化の後に足すので nodePath 解決には影響しない
5466
+ * (末尾追加なので既存 index もずれない)。SSR 描画中は付けない — SSR は行ごとに
5467
+ * `@@wcs-for-start/end` を出力しており、そちらがレンジの正本かつハイドレーション
5468
+ * が読む対象なので、余計なコメントを混ぜない。
5469
+ */
5470
+ function appendRowEndMarker(cloneFragment, uuid) {
5471
+ cloneFragment.appendChild(document.createComment(`${ROW_END_PREFIX}:${uuid}`));
5472
+ }
5393
5473
  /**
5394
5474
  * SSR ハイドレーション用: 既存の DOM ノード配列から Content を生成する。
5395
5475
  * テンプレートからの clone ではなく、SSR で描画済みのノードをそのまま使う。
@@ -5440,6 +5520,9 @@ function createPlanContent(bindingInfo, fragmentInfo, plan) {
5440
5520
  }
5441
5521
  }
5442
5522
  const session = initializeRowBindings(plan, bindings);
5523
+ // プラン経路の content は範囲モードにならない: compileRowPlan は bindingType が
5524
+ // text / prop / event のもの以外(= for / if / elseif / else)を含む時点で不適格に
5525
+ // するため、プラン適格なフラグメントはトップレベル構造アンカーを持ち得ない。
5443
5526
  const content = new Content(cloneFragment);
5444
5527
  setBindingSessionByContent(content, session);
5445
5528
  setBindingsByContent(content, bindings);
@@ -5456,6 +5539,8 @@ function createContent(bindingInfo) {
5456
5539
  if (!fragmentInfo) {
5457
5540
  raiseError(`Fragment with UUID "${bindingInfo.uuid}" not found.`);
5458
5541
  }
5542
+ // SSR 描画中は行ごとの @@wcs-for-start/end がレンジの正本なので終端マーカーは付けない
5543
+ const ranged = !inSsr() && resolveRanged(fragmentInfo);
5459
5544
  let plan = fragmentInfo.rowPlan;
5460
5545
  if (typeof plan === 'undefined' || (plan !== null && plan.directional !== config.enableDirectionalInitialSync)) {
5461
5546
  // 初回 or config(directional)が変わったときだけコンパイル。不適格は null を
@@ -5467,7 +5552,11 @@ function createContent(bindingInfo) {
5467
5552
  }
5468
5553
  const cloneFragment = document.importNode(fragmentInfo.fragment, true);
5469
5554
  const initialInfo = initializeBindingsByFragment(cloneFragment, fragmentInfo.nodeInfos);
5470
- const content = new Content(cloneFragment);
5555
+ if (ranged) {
5556
+ // uuid は冒頭のガードで非 null が確定している(raiseError は never を返す)
5557
+ appendRowEndMarker(cloneFragment, bindingInfo.uuid);
5558
+ }
5559
+ const content = new Content(cloneFragment, ranged);
5471
5560
  setBindingSessionByContent(content, initialInfo.bindingSession);
5472
5561
  setBindingsByContent(content, initialInfo.bindingInfos);
5473
5562
  const indexBindings = [];
@@ -6897,7 +6986,7 @@ async function buildBindings(root) {
6897
6986
  }
6898
6987
  }
6899
6988
 
6900
- var version = "1.22.5";
6989
+ var version = "1.23.0";
6901
6990
  var pkg = {
6902
6991
  version: version};
6903
6992