@wcstack/state 1.19.0 → 1.19.1

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
@@ -1,10 +1,12 @@
1
- let _inSsrCache = null;
2
1
  function inSsr() {
3
- if (_inSsrCache !== null)
4
- return _inSsrCache;
5
- const html = document.querySelector('html');
6
- _inSsrCache = html ? html.hasAttribute('data-wcs-server') : false;
7
- return _inSsrCache;
2
+ // キャッシュしない: SSR モードはプロセスの属性ではなく「現在の document」の
3
+ // 属性。@wcstack/server はグローバル document を差し替えてサーバーレンダリング
4
+ // した後、同一プロセスでクライアント側ハイドレーションが走る(SSR→hydrate
5
+ // e2e が該当)。サーバーフェーズの判定をキャッシュするとクライアントフェーズが
6
+ // SSR モード扱いになり、hydrateBindings の代わりに buildBindings が選ばれて
7
+ // connectedCallbackPromise が永久に未解決になる。
8
+ const html = document.documentElement;
9
+ return html ? html.hasAttribute('data-wcs-server') : false;
8
10
  }
9
11
  const _config = {
10
12
  bindAttributeName: 'data-wcs',
@@ -63,7 +65,7 @@ function setConfig(partialConfig) {
63
65
  }
64
66
  }
65
67
 
66
- var version$1 = "1.19.0";
68
+ var version$1 = "1.19.1";
67
69
  var pkg = {
68
70
  version: version$1};
69
71
 
@@ -2788,6 +2790,20 @@ function isSameList(oldList, newList) {
2788
2790
  }
2789
2791
  return true;
2790
2792
  }
2793
+ /**
2794
+ * Aligns each list index's .index with its position in the new list.
2795
+ * A diff only becomes the rendered state once the updater applies it: an
2796
+ * earlier diff in the same batch (two replacements in one microtask) may have
2797
+ * moved shared indexes toward a list that never got applied, and a cache hit
2798
+ * skips recomputation entirely — so every createListDiff return re-aligns.
2799
+ */
2800
+ function syncListIndexes(newIndexes) {
2801
+ for (let i = 0; i < newIndexes.length; i++) {
2802
+ if (newIndexes[i].index !== i) {
2803
+ newIndexes[i].index = i;
2804
+ }
2805
+ }
2806
+ }
2791
2807
  /**
2792
2808
  * Creates or updates list indexes by comparing old and new lists.
2793
2809
  * Optimizes by reusing existing list indexes when values match.
@@ -2798,6 +2814,11 @@ function isSameList(oldList, newList) {
2798
2814
  * @returns Array of list indexes for the new list
2799
2815
  */
2800
2816
  function createListDiff(parentListIndex, rawOldList, rawNewList) {
2817
+ const diff = computeListDiff(parentListIndex, rawOldList, rawNewList);
2818
+ syncListIndexes(diff.newIndexes);
2819
+ return diff;
2820
+ }
2821
+ function computeListDiff(parentListIndex, rawOldList, rawNewList) {
2801
2822
  // Normalize inputs to arrays (handles null/undefined)
2802
2823
  const oldList = (Array.isArray(rawOldList) && rawOldList.length > 0) ? rawOldList : EMPTY_LIST;
2803
2824
  const newList = (Array.isArray(rawNewList) && rawNewList.length > 0) ? rawNewList : EMPTY_LIST;
@@ -2846,6 +2867,10 @@ function createListDiff(parentListIndex, rawOldList, rawNewList) {
2846
2867
  addIndexSet: EMPTY_SET$1,
2847
2868
  };
2848
2869
  }
2870
+ if (newIndexes !== null) {
2871
+ return calcDiffIndexes(oldIndexes, newIndexes);
2872
+ }
2873
+ newIndexes = [];
2849
2874
  // Use index-based map for efficiency
2850
2875
  // Supports duplicate values by storing array of indexes
2851
2876
  const indexByValue = new Map();
@@ -2858,10 +2883,6 @@ function createListDiff(parentListIndex, rawOldList, rawNewList) {
2858
2883
  }
2859
2884
  indexes.push(i);
2860
2885
  }
2861
- if (newIndexes !== null) {
2862
- return calcDiffIndexes(oldList, newList, oldIndexes, newIndexes, indexByValue);
2863
- }
2864
- newIndexes = [];
2865
2886
  // Build new indexes array by matching values with old list
2866
2887
  const changeIndexSet = new Set();
2867
2888
  const addIndexSet = new Set();
@@ -2878,9 +2899,11 @@ function createListDiff(parentListIndex, rawOldList, rawNewList) {
2878
2899
  else {
2879
2900
  // Reuse existing element
2880
2901
  const existingListIndex = oldIndexes[oldIndex];
2881
- // Update index if position changed
2882
- if (existingListIndex.index !== i) {
2883
- existingListIndex.index = i;
2902
+ // Judge position change against the old list's order (oldIndexes array
2903
+ // order), not the mutable .index an earlier diff in the same batch
2904
+ // may have already moved .index toward a list that was never applied.
2905
+ // The .index itself is re-aligned by syncListIndexes on return.
2906
+ if (oldIndex !== i) {
2884
2907
  changeIndexSet.add(existingListIndex);
2885
2908
  }
2886
2909
  newIndexes.push(existingListIndex);
@@ -2902,22 +2925,35 @@ function createListDiff(parentListIndex, rawOldList, rawNewList) {
2902
2925
  }
2903
2926
  }
2904
2927
  }
2905
- function calcDiffIndexes(oldList, newList, oldIndexes, newIndexes, indexByValue) {
2928
+ /**
2929
+ * Diff between two lists whose listIndex ledgers both already exist.
2930
+ * Rows are joined by listIndex identity — the same key applyChangeToFor and
2931
+ * walkDependency consume the result sets with. A value-based join could mark
2932
+ * oldIndexes-side objects that are absent from newIndexes (ledgers built along
2933
+ * unconnected diff chains hold different objects for the same value); such
2934
+ * orphan markers never match the consumers' has() lookups and only pollute
2935
+ * the dirty set. Rows without shared identity are represented as add+delete.
2936
+ */
2937
+ function calcDiffIndexes(oldIndexes, newIndexes) {
2906
2938
  const newIndexSet = new Set(newIndexes);
2907
2939
  const oldIndexSet = new Set(oldIndexes);
2908
2940
  const changeIndexSet = new Set();
2909
2941
  const addIndexSet = newIndexSet.difference(oldIndexSet);
2910
2942
  const deleteIndexSet = oldIndexSet.difference(newIndexSet);
2911
- for (let i = 0; i < newList.length; i++) {
2912
- const newValue = newList[i];
2913
- const existingIndexes = indexByValue.get(newValue);
2914
- const oldIndex = existingIndexes && existingIndexes.length > 0 ? existingIndexes.shift() : undefined;
2915
- if (typeof oldIndex !== "undefined") {
2916
- const existingListIndex = oldIndexes[oldIndex];
2917
- if (existingListIndex.index !== i) {
2918
- // 位置が違うことだけを記録
2919
- changeIndexSet.add(existingListIndex);
2920
- }
2943
+ // Old positions come from the oldIndexes array order (.index may have been
2944
+ // mutated by an unapplied diff in the same batch).
2945
+ const oldPosByIndex = new Map();
2946
+ for (let i = 0; i < oldIndexes.length; i++) {
2947
+ oldPosByIndex.set(oldIndexes[i], i);
2948
+ }
2949
+ for (let i = 0; i < newIndexes.length; i++) {
2950
+ const index = newIndexes[i];
2951
+ if (addIndexSet.has(index)) {
2952
+ continue;
2953
+ }
2954
+ if (oldPosByIndex.get(index) !== i) {
2955
+ // 位置が違うことだけを記録
2956
+ changeIndexSet.add(index);
2921
2957
  }
2922
2958
  }
2923
2959
  return {
@@ -5666,8 +5702,14 @@ class LoopContextStack {
5666
5702
  }
5667
5703
  }
5668
5704
  else {
5669
- if (loopContext.pathInfo.wildcardCount !== 1) {
5670
- raiseError(`Cannot push loop context for a list with wildcard positions when there is no active loop context.`);
5705
+ // With no active loop context the address must be self-contained: the
5706
+ // listIndex chain supplies one index per wildcard. Top-level lists
5707
+ // (wildcardCount 1) always satisfy this. A nested list re-rendered
5708
+ // directly (e.g. replaced via $resolve from outside the loop) also
5709
+ // satisfies it — the for binding's listIndex carries the full ancestor
5710
+ // chain.
5711
+ if (loopContext.listIndex.length !== loopContext.pathInfo.wildcardCount) {
5712
+ raiseError(`Cannot push loop context when there is no active loop context: the list index chain (length ${loopContext.listIndex.length}) does not cover the wildcard path (wildcard count ${loopContext.pathInfo.wildcardCount}).`);
5671
5713
  }
5672
5714
  }
5673
5715
  this._loopContextStack[this._length] = loopContext;