@wcstack/state 1.3.15 → 1.3.17

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.
Files changed (3) hide show
  1. package/README.ja.md +22 -16
  2. package/README.md +21 -15
  3. package/package.json +1 -1
package/README.ja.md CHANGED
@@ -29,14 +29,14 @@ CDNスクリプトはカスタム要素の定義を登録するだけ — ロー
29
29
 
30
30
  ```html
31
31
  <!-- 自動初期化 — これだけで動作します -->
32
- <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state/dist/auto.js"></script>
32
+ <script type="module" src="https://esm.run/@wcstack/state/auto"></script>
33
33
  ```
34
34
 
35
35
  ### CDN(手動初期化)
36
36
 
37
37
  ```html
38
38
  <script type="module">
39
- import { bootstrapState } from 'https://cdn.jsdelivr.net/npm/@wcstack/state/dist/index.esm.js';
39
+ import { bootstrapState } from 'https://esm.run/@wcstack/state';
40
40
  bootstrapState();
41
41
  </script>
42
42
  ```
@@ -149,27 +149,33 @@ CDNスクリプトはカスタム要素の定義を登録するだけ — ロー
149
149
 
150
150
  ## 状態の更新
151
151
 
152
- 状態の変更は**プロパティへの代入**(Proxy `set` トラップ)で検知されます。リアクティブな DOM 更新をトリガーするには、状態プロパティに値を**代入**する必要があります。
152
+ `@wcstack/state` では、すべての状態は**パス**を持ちます `count`、`user.name`、`items` のように。状態をリアクティブに更新するには、**パスに代入**します:
153
153
 
154
- ### プリミティブ・オブジェクトプロパティ
154
+ ```javascript
155
+ this.count = 10; // パス "count"
156
+ this["user.name"] = "Bob"; // パス "user.name"
157
+ ```
158
+
159
+ ルールはひとつ: **パスに代入すれば、DOMは自動的に更新される。**
160
+
161
+ ### なぜ `this.user.name = "Bob"` では動かないのか
155
162
 
156
- 代入は**ドットパス記法**(ブラケット構文)を使用する必要があります。リアクティブ Proxy はトップレベルの `set` トラップのみを捕捉するため、通常のネストされたプロパティアクセスでは変更が検知されません:
163
+ `this.user.name` は、まず `this.user` `user` オブジェクトを読み取り(パスの読み取り)、そのプレーンオブジェクトの `.name` に設定します — これはパスへの代入ではないため、変更は検知されません:
157
164
 
158
165
  ```javascript
159
- // ✅ パス代入 — 変更が検知される
160
- this.count = 10;
166
+ // ✅ パスへの代入 — 変更が検知される
161
167
  this["user.name"] = "Bob";
162
168
 
163
- // ❌ 直接のネストアクセス変更が検知されない
164
- this.user.name = "Bob"; // Proxy の set トラップをバイパスする
169
+ // ❌ パスへの代入ではない変更は検知されない
170
+ this.user.name = "Bob";
165
171
  ```
166
172
 
167
173
  ### 配列
168
174
 
169
- 配列の破壊的メソッド(`push`, `splice`, `sort`, `reverse` 等)はその場で配列を変更するだけで**プロパティ代入を発生させない**ため、リアクティブシステムは変更を検知しません。代わりに、新しい配列を返す**非破壊的メソッド**を使用し、結果を代入してください:
175
+ 同じルールです: パスに新しい配列を代入します。破壊的メソッド(`push`, `splice`, `sort` 等)はパスへの代入なしに配列をその場で変更するため、非破壊的な代替メソッドを使用します:
170
176
 
171
177
  ```javascript
172
- // ✅ 非破壊的 + 代入 — 変更が検知される
178
+ // ✅ 新しい配列をパスに代入 — 変更が検知される
173
179
  this.items = this.items.concat({ id: 4, text: "New" });
174
180
  this.items = this.items.toSpliced(index, 1);
175
181
  this.items = this.items.filter(item => !item.done);
@@ -177,7 +183,7 @@ this.items = this.items.toSorted((a, b) => a.id - b.id);
177
183
  this.items = this.items.toReversed();
178
184
  this.items = this.items.with(index, newValue);
179
185
 
180
- // ❌ 破壊的代入なし、変更は検知されない
186
+ // ❌ その場での変更パスへの代入なし、変更は検知されない
181
187
  this.items.push({ id: 4, text: "New" });
182
188
  this.items.splice(index, 1);
183
189
  this.items.sort((a, b) => a.id - b.id);
@@ -550,7 +556,7 @@ export default {
550
556
  };
551
557
  ```
552
558
 
553
- 3階層のネスト、5つの仮想プロパティ — すべてが1つのフラットなオブジェクト内に並んで定義されています。各レベルは任意の深さの値を参照でき、`$getAll` による集約は下位から上位へ自然に流れます。コンポーネントベースのフレームワーク(React、Vue)で同じことを実現するには、各ネストレベルに個別のコンポーネントを作成し、算出値をツリーの上位に渡すための props バケツリレーや状態管理が必要になります。
559
+ 3階層のネスト、5つの仮想プロパティ — すべてが1つのフラットなオブジェクト内に並んで定義されています。各レベルは任意の深さの値を参照でき、`$getAll` による集約は下位から上位へ自然に流れます。コンポーネントベースのフレームワークでは、一般的に各ネストレベルに個別のコンポーネントを作成し、算出値をツリーの上位に渡す方法が採られます。パス getter は、すべての定義を1箇所にまとめるという異なるトレードオフを提供します。
554
560
 
555
561
  ### getter の戻り値のサブプロパティへのアクセス
556
562
 
@@ -834,14 +840,14 @@ export default {
834
840
 
835
841
  `@wcstack/state` は Shadow DOM または Light DOM を使用したカスタム要素との双方向状態バインディングに対応しています。
836
842
 
837
- 多くのフレームワークは状態をコンポーネントに密結合させるため、ツリーをまたぐデータ共有には props バケツリレー、Context Provider、外部ストア(Redux, Pinia)といったパターンを強いられます。`@wcstack/state` では、親子コンポーネントは**パスの契約**で接続されます。親は `data-wcs` で外部の状態パスを子コンポーネントのプロパティにバインドし、子は自身の状態を通常どおり読み書きするだけです:
843
+ 多くのフレームワークでは、コンポーネント間の状態共有に props バケツリレー、Context Provider、外部ストア(Redux, Pinia)といったパターンが用いられます。`@wcstack/state` は異なるアプローチを採ります。親子コンポーネントは**パスの契約**で接続されます。親は `data-wcs` で外部の状態パスを子コンポーネントのプロパティにバインドし、子は自身の状態を通常どおり読み書きするだけです:
838
844
 
839
845
  1. 子コンポーネントは自身の状態プロキシを通じて親の状態を参照・更新します — props もイベントも親の存在を意識する必要もありません。
840
846
  2. 親の状態が変更されると、Proxy の `set` トラップが影響するパスを参照している子のバインディングへ自動的に通知します。
841
847
  3. 結合点は**パス名のみ**であるため、双方とも疎結合を保ち、独立してテスト可能です。
842
848
  4. コストはパス解決(初回アクセス後はキャッシュにより O(1))と依存グラフを通じた変更伝播のみです。
843
849
 
844
- これは、他のフレームワークがますます複雑な抽象化で回避してきたコンポーネント間状態管理の問題に対する具体的な解法です。
850
+ これは、コンポーネントレベルの抽象化ではなくパス解決に基づく、コンポーネント間状態管理への軽量なアプローチです。
845
851
 
846
852
  ### コンポーネント定義(Shadow DOM)
847
853
 
@@ -998,7 +1004,7 @@ customElements.define("my-component", MyComponent);
998
1004
  | `$disconnectedCallback` | 要素が DOM から削除された時 | 不可(同期のみ) |
999
1005
  | `$updatedCallback(paths, indexesListByPath)` | 状態変更が適用された後に呼び出し | 戻り値は未使用(待機されない) |
1000
1006
 
1001
- リアクティブ Proxy は全てのプロパティ代入を変更として検知するため、標準の `async/await` とプロパティへの直接代入だけで非同期処理は完結します。ローディングフラグ、取得データ、エラーメッセージの更新は全てプロパティ代入です。React の Suspense や専用のローディング状態プリミティブのような抽象化は不要です。
1007
+ リアクティブ Proxy は全てのプロパティ代入を変更として検知するため、標準の `async/await` とプロパティへの直接代入だけで非同期処理は完結します。ローディングフラグ、取得データ、エラーメッセージの更新は全てプロパティ代入であり、非同期状態管理のための追加の抽象化を必要としません。
1002
1008
 
1003
1009
  - フック内の `this` は読み書き可能な状態プロキシです
1004
1010
  - `$connectedCallback` は要素が接続される**たびに**呼ばれます(削除後の再接続を含む)。再確立が必要なセットアップ処理に適しています
package/README.md CHANGED
@@ -29,14 +29,14 @@ The CDN script only registers the custom element definition — nothing else hap
29
29
 
30
30
  ```html
31
31
  <!-- Auto-initialization — this is all you need -->
32
- <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state/dist/auto.js"></script>
32
+ <script type="module" src="https://esm.run/@wcstack/state/auto"></script>
33
33
  ```
34
34
 
35
35
  ### CDN (manual initialization)
36
36
 
37
37
  ```html
38
38
  <script type="module">
39
- import { bootstrapState } from 'https://cdn.jsdelivr.net/npm/@wcstack/state/dist/index.esm.js';
39
+ import { bootstrapState } from 'https://esm.run/@wcstack/state';
40
40
  bootstrapState();
41
41
  </script>
42
42
  ```
@@ -149,27 +149,33 @@ Default name is `"default"` (no `@` needed).
149
149
 
150
150
  ## Updating State
151
151
 
152
- State changes are detected through **property assignment** (the Proxy `set` trap). To trigger reactive DOM updates, a value must be **assigned** to a state property.
152
+ In `@wcstack/state`, every piece of state has a **path** like `count`, `user.name`, or `items`. To update state reactively, **assign to the path**:
153
153
 
154
- ### Primitive and Object Properties
154
+ ```javascript
155
+ this.count = 10; // path "count"
156
+ this["user.name"] = "Bob"; // path "user.name"
157
+ ```
158
+
159
+ That's the one rule: **assign to the path, and the DOM updates automatically.**
160
+
161
+ ### Why `this.user.name = "Bob"` Doesn't Work
155
162
 
156
- Assignment must use **dot-path notation** (bracket syntax). The reactive proxy intercepts only top-level `set` traps, so standard nested property access bypasses change detection:
163
+ `this.user.name` first reads the `user` object via `this.user` (a path read), then sets `.name` on that plain object this is not a path assignment, so the change is not detected:
157
164
 
158
165
  ```javascript
159
166
  // ✅ Path assignment — change detected
160
- this.count = 10;
161
167
  this["user.name"] = "Bob";
162
168
 
163
- // ❌ Direct nested access — change NOT detected
164
- this.user.name = "Bob"; // bypasses the Proxy set trap
169
+ // ❌ Not a path assignment — change NOT detected
170
+ this.user.name = "Bob";
165
171
  ```
166
172
 
167
173
  ### Arrays
168
174
 
169
- Array mutating methods (`push`, `splice`, `sort`, `reverse`, …) modify the array in place **without triggering a property assignment**, so the reactive system does not detect the change. Instead, use **non-destructive** methods that return a new array and assign the result:
175
+ The same rule applies: assign a new array to the path. Mutating methods (`push`, `splice`, `sort`, ...) modify the array in place without path assignment, so use non-destructive alternatives:
170
176
 
171
177
  ```javascript
172
- // ✅ Non-destructive + assignment — change detected
178
+ // ✅ New array assigned to path — change detected
173
179
  this.items = this.items.concat({ id: 4, text: "New" });
174
180
  this.items = this.items.toSpliced(index, 1);
175
181
  this.items = this.items.filter(item => !item.done);
@@ -177,7 +183,7 @@ this.items = this.items.toSorted((a, b) => a.id - b.id);
177
183
  this.items = this.items.toReversed();
178
184
  this.items = this.items.with(index, newValue);
179
185
 
180
- // ❌ Mutating — no assignment, change NOT detected
186
+ // ❌ In-place mutation — no path assignment, change NOT detected
181
187
  this.items.push({ id: 4, text: "New" });
182
188
  this.items.splice(index, 1);
183
189
  this.items.sort((a, b) => a.id - b.id);
@@ -550,7 +556,7 @@ export default {
550
556
  };
551
557
  ```
552
558
 
553
- Three levels of nesting, five virtual properties — all defined side by side in a single flat object. Each level can reference values from any depth, and aggregation flows naturally from bottom to top via `$getAll`. In component-based frameworks (React, Vue), achieving the same requires creating a separate component for each nesting level, with props drilling or state management to pass computed values up the tree.
559
+ Three levels of nesting, five virtual properties — all defined side by side in a single flat object. Each level can reference values from any depth, and aggregation flows naturally from bottom to top via `$getAll`. In component-based frameworks, the typical approach is to create a separate component for each nesting level and pass computed values through the tree. Path getters offer a different trade-off by keeping all definitions in one place.
554
560
 
555
561
  ### Accessing Sub-Properties of Getter Results
556
562
 
@@ -834,14 +840,14 @@ Filters can be chained with `|`:
834
840
 
835
841
  `@wcstack/state` supports bidirectional state binding with custom elements using Shadow DOM or Light DOM.
836
842
 
837
- Most frameworks tightly couple state to components, forcing patterns like prop drilling, context providers, or external stores (Redux, Pinia) just to share data across the tree. In `@wcstack/state`, parent and child components are connected through **path contracts** — the parent binds an outer state path to an inner component property via `data-wcs`, and the child simply reads and writes its own state as usual:
843
+ Many frameworks use patterns like prop drilling, context providers, or external stores (Redux, Pinia) to share state across components. `@wcstack/state` takes a different approach: parent and child components are connected through **path contracts** — the parent binds an outer state path to an inner component property via `data-wcs`, and the child simply reads and writes its own state as usual:
838
844
 
839
845
  1. The child references and updates the parent's state through its own state proxy — no props, no events, no awareness of the parent.
840
846
  2. When the parent's state changes, the Proxy `set` trap automatically notifies any child bindings that reference the affected path.
841
847
  3. Because the only coupling is the **path name**, both sides remain loosely coupled and independently testable.
842
848
  4. The cost is path resolution (cached at O(1) after first access) plus change propagation through the dependency graph.
843
849
 
844
- This provides a concrete solution to cross-component state management that other frameworks have been working around with increasingly complex abstractions.
850
+ This provides a lightweight approach to cross-component state management based on path resolution rather than component-level abstractions.
845
851
 
846
852
  ### Component Definition (Shadow DOM)
847
853
 
@@ -998,7 +1004,7 @@ State objects can define `$connectedCallback`, `$disconnectedCallback`, and `$up
998
1004
  | `$disconnectedCallback` | When the element is removed from the DOM | No (sync only) |
999
1005
  | `$updatedCallback(paths, indexesListByPath)` | After state updates are applied | Return value is ignored (not awaited) |
1000
1006
 
1001
- Since the reactive proxy detects every property assignment as a change, standard `async/await` with direct property updates is sufficient for asynchronous operations — loading flags, fetched data, and error messages are all just property assignments. There is no need for abstractions like React Suspense or dedicated loading-state primitives.
1007
+ Since the reactive proxy detects every property assignment as a change, standard `async/await` with direct property updates is sufficient for asynchronous operations — loading flags, fetched data, and error messages are all just property assignments, without requiring additional abstractions for async state management.
1002
1008
 
1003
1009
  - `this` inside hooks is the state proxy with full read/write access
1004
1010
  - `$connectedCallback` is called **every time** the element is connected (including re-insertion after removal), making it suitable for setup that should be re-established
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wcstack/state",
3
- "version": "1.3.15",
3
+ "version": "1.3.17",
4
4
  "description": "Reactive state management with declarative data binding for Web Components. Zero dependencies, buildless.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.esm.js",