@wcstack/state 1.22.4 → 1.22.6

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,13 +5322,46 @@ 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
- const nextSibling = targetNode.nextSibling;
5320
5352
  if (parentNode) {
5321
- for (const node of this._childNodeArray) {
5322
- markObserverSkipOnAdd(node);
5323
- parentNode.insertBefore(node, nextSibling);
5353
+ // マウント済み content にも再突入する(if true→true 再適用・SSR
5354
+ // ハイドレーション産 content 等)。固定の nextSibling へ一括 insertBefore
5355
+ // すると、マウント済みでは nextSibling が自分の先頭ノードを指すため
5356
+ // 先頭ノードが末尾へ回転する。anchor を進めながら位置一致ノードを
5357
+ // スキップすることで冪等にする(mutation record も発生させない)。
5358
+ let anchor = targetNode;
5359
+ for (const node of this._movableNodes()) {
5360
+ if (anchor.nextSibling !== node) {
5361
+ markObserverSkipOnAdd(node);
5362
+ parentNode.insertBefore(node, anchor.nextSibling);
5363
+ }
5364
+ anchor = node;
5324
5365
  }
5325
5366
  }
5326
5367
  this._mounted = true;
@@ -5382,6 +5423,53 @@ class Content {
5382
5423
  this._mounted = false;
5383
5424
  }
5384
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
+ }
5385
5473
  /**
5386
5474
  * SSR ハイドレーション用: 既存の DOM ノード配列から Content を生成する。
5387
5475
  * テンプレートからの clone ではなく、SSR で描画済みのノードをそのまま使う。
@@ -5432,6 +5520,9 @@ function createPlanContent(bindingInfo, fragmentInfo, plan) {
5432
5520
  }
5433
5521
  }
5434
5522
  const session = initializeRowBindings(plan, bindings);
5523
+ // プラン経路の content は範囲モードにならない: compileRowPlan は bindingType が
5524
+ // text / prop / event のもの以外(= for / if / elseif / else)を含む時点で不適格に
5525
+ // するため、プラン適格なフラグメントはトップレベル構造アンカーを持ち得ない。
5435
5526
  const content = new Content(cloneFragment);
5436
5527
  setBindingSessionByContent(content, session);
5437
5528
  setBindingsByContent(content, bindings);
@@ -5448,6 +5539,8 @@ function createContent(bindingInfo) {
5448
5539
  if (!fragmentInfo) {
5449
5540
  raiseError(`Fragment with UUID "${bindingInfo.uuid}" not found.`);
5450
5541
  }
5542
+ // SSR 描画中は行ごとの @@wcs-for-start/end がレンジの正本なので終端マーカーは付けない
5543
+ const ranged = !inSsr() && resolveRanged(fragmentInfo);
5451
5544
  let plan = fragmentInfo.rowPlan;
5452
5545
  if (typeof plan === 'undefined' || (plan !== null && plan.directional !== config.enableDirectionalInitialSync)) {
5453
5546
  // 初回 or config(directional)が変わったときだけコンパイル。不適格は null を
@@ -5459,7 +5552,11 @@ function createContent(bindingInfo) {
5459
5552
  }
5460
5553
  const cloneFragment = document.importNode(fragmentInfo.fragment, true);
5461
5554
  const initialInfo = initializeBindingsByFragment(cloneFragment, fragmentInfo.nodeInfos);
5462
- 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);
5463
5560
  setBindingSessionByContent(content, initialInfo.bindingSession);
5464
5561
  setBindingsByContent(content, initialInfo.bindingInfos);
5465
5562
  const indexBindings = [];
@@ -5728,12 +5825,10 @@ function applyChangeToFor(bindingInfo, context, newValue) {
5728
5825
  }
5729
5826
  }
5730
5827
 
5731
- const lastConnectedByNode = new WeakMap();
5732
5828
  function bindingInfoText(bindingInfo) {
5733
5829
  return `${bindingInfo.bindingType} ${bindingInfo.statePathName} ${bindingInfo.outFilters.map(f => f.filterName).join('|')} ${bindingInfo.node.isConnected ? '(connected)' : '(disconnected)'}`;
5734
5830
  }
5735
5831
  function applyChangeToIf(bindingInfo, context, rawNewValue) {
5736
- const currentConnected = bindingInfo.node.isConnected;
5737
5832
  const newValue = Boolean(rawNewValue);
5738
5833
  let content;
5739
5834
  const contents = getContentSetByNode(bindingInfo.node);
@@ -5746,35 +5841,30 @@ function applyChangeToIf(bindingInfo, context, rawNewValue) {
5746
5841
  const ssrMode = inSsr();
5747
5842
  const uuid = bindingInfo.uuid ?? '';
5748
5843
  const keyword = bindingInfo.bindingType; // if, elseif, else
5749
- try {
5750
- if (!newValue) {
5751
- if (config.debug) {
5752
- console.log(`unmount if content : ${bindingInfoText(bindingInfo)}`);
5753
- }
5754
- deactivateContent(content);
5755
- content.unmount();
5756
- }
5757
- if (newValue) {
5758
- if (config.debug) {
5759
- console.log(`mount if content : ${bindingInfoText(bindingInfo)}`);
5760
- }
5761
- if (ssrMode) {
5762
- const startComment = document.createComment(`@@wcs-${keyword}-start:${uuid}:${bindingInfo.statePathName}`);
5763
- bindingInfo.node.parentNode.insertBefore(startComment, bindingInfo.node.nextSibling);
5764
- content.mountAfter(startComment);
5765
- const endComment = document.createComment(`@@wcs-${keyword}-end:${uuid}:${bindingInfo.statePathName}`);
5766
- const afterNode = content.lastNode ?? startComment;
5767
- afterNode.parentNode.insertBefore(endComment, afterNode.nextSibling);
5768
- }
5769
- else {
5770
- content.mountAfter(bindingInfo.node);
5771
- }
5772
- const loopContext = getLoopContextByNode(bindingInfo.node);
5773
- activateContent(content, loopContext, context);
5844
+ if (!newValue) {
5845
+ if (config.debug) {
5846
+ console.log(`unmount if content : ${bindingInfoText(bindingInfo)}`);
5774
5847
  }
5848
+ deactivateContent(content);
5849
+ content.unmount();
5775
5850
  }
5776
- finally {
5777
- lastConnectedByNode.set(bindingInfo.node, currentConnected);
5851
+ if (newValue) {
5852
+ if (config.debug) {
5853
+ console.log(`mount if content : ${bindingInfoText(bindingInfo)}`);
5854
+ }
5855
+ if (ssrMode) {
5856
+ const startComment = document.createComment(`@@wcs-${keyword}-start:${uuid}:${bindingInfo.statePathName}`);
5857
+ bindingInfo.node.parentNode.insertBefore(startComment, bindingInfo.node.nextSibling);
5858
+ content.mountAfter(startComment);
5859
+ const endComment = document.createComment(`@@wcs-${keyword}-end:${uuid}:${bindingInfo.statePathName}`);
5860
+ const afterNode = content.lastNode ?? startComment;
5861
+ afterNode.parentNode.insertBefore(endComment, afterNode.nextSibling);
5862
+ }
5863
+ else {
5864
+ content.mountAfter(bindingInfo.node);
5865
+ }
5866
+ const loopContext = getLoopContextByNode(bindingInfo.node);
5867
+ activateContent(content, loopContext, context);
5778
5868
  }
5779
5869
  }
5780
5870
 
@@ -6896,7 +6986,7 @@ async function buildBindings(root) {
6896
6986
  }
6897
6987
  }
6898
6988
 
6899
- var version = "1.22.4";
6989
+ var version = "1.22.6";
6900
6990
  var pkg = {
6901
6991
  version: version};
6902
6992