@wcstack/state 1.21.0 → 1.21.2

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/README.ja.md CHANGED
@@ -324,6 +324,10 @@ property[#modifier]: path[@state][|filter[|filter(args)...]]
324
324
  | `#prevent` | イベントハンドラで `event.preventDefault()` を呼び出す |
325
325
  | `#stop` | イベントハンドラで `event.stopPropagation()` を呼び出す |
326
326
  | `#onchange` | 双方向バインディングで `input` の代わりに `change` イベントを使用 |
327
+ | `#init=<authority>` | バインディングの authority / 初期同期の向き — [バインディング authority](#バインディング-authority-init--sync) 参照 |
328
+ | `#sync=<timing>` | 要素スナップショットの読み取りタイミング — [バインディング authority](#バインディング-authority-init--sync) 参照 |
329
+
330
+ 複数の修飾子は 1 つの `#` の後にカンマ区切りで書きます: `value#ro,init=none: path`
327
331
 
328
332
  ### 双方向バインディング
329
333
 
@@ -338,6 +342,46 @@ property[#modifier]: path[@state][|filter[|filter(args)...]]
338
342
 
339
343
  `<input type="button">` は除外されます。`#ro` で無効化、`#onchange` でイベントを変更できます。
340
344
 
345
+ ### バインディング authority (`#init=` / `#sync=`)
346
+
347
+ `static wcBindable` を宣言したカスタム要素への prop バインディングは、**authority**(そのワイヤをどちら側が所有するか)を解決します。authority は初期値の出所だけでなく、**そのバインディングの生存期間全体で state→element 書き込みが有効かどうか**を決めます。既定はメンバの宣言位置から導出されます(`enableDirectionalInitialSync` で既定 ON):
348
+
349
+ | メンバの宣言位置 | 既定 authority | 効果 |
350
+ |---|---|---|
351
+ | `properties` のみ(output-only) | `element` | 要素の値が state に流れる。**state からこのメンバへは書き込まれない** |
352
+ | `inputs` のみ | `state` | state が要素に書き込む |
353
+ | `properties` + `inputs`(双方向) | `state` | 従来挙動 — state が先に書き、以後は要素イベントが state を更新 |
354
+ | —(`wcBindable` 非宣言・素の HTML 要素) | `state` | 従来と不変 |
355
+
356
+ > **作法:** settable なメンバは **`properties` と `inputs` の両方**に宣言してください。`properties` にしか宣言されていないメンバは output-only 扱いになり、state→element 書き込みがバインディングの生存期間ずっと抑止され、要素側の初期値が state 側のシード値を上書きします(`@wcstack` の I/O ノード Shell と DCC の `$bindables` はこの作法に従っています)。
357
+
358
+ authority はバインディング単位で `#init=` により上書きできます:
359
+
360
+ | 値 | authority | 使える宣言 |
361
+ |---|---|---|
362
+ | `init=state` | state 所有:state → element(双方向メンバは要素イベントも引き続き受信) | inputs のみ・双方向 |
363
+ | `init=element` | element 所有:要素のスナップショットとイベント → state。state からの書き込みは抑止 | output-only・双方向 |
364
+ | `init=auto` | state スロットが未初期化なら `element`、それ以外は `state` | 双方向 |
365
+ | `init=none` | 初期同期なし(event バインディングはこの値のみ許可) | すべて |
366
+
367
+ `#sync=` は element authority のバインディングで要素スナップショットを**いつ**読むかを制御します:
368
+
369
+ | 値 | 意味 |
370
+ |---|---|
371
+ | `sync=call`(既定) | バインディングの attach 時に即読み取り |
372
+ | `sync=connect` | 要素が document に接続されるまで読み取りを保留 |
373
+
374
+ ```html
375
+ <x-clock data-wcs="value#init=element: clock.now"></x-clock>
376
+ <x-input data-wcs="value#init=auto: form.name"></x-input>
377
+ <x-widget data-wcs="value#init=element,sync=connect: widget.snapshot"></x-widget>
378
+ ```
379
+
380
+ 注意:
381
+
382
+ - `enableDirectionalInitialSync: false`(opt-out)のとき `#init=`/`#sync=` を書くと throw します。
383
+ - **1.20 以前からの移行:** output-only メンバに対して state 側に都合のよい初期値(`value: []`、`query: ""` 等)をシードしないでください — 要素側の実初期値(多くは `null`/`undefined`)がシードを置き換えます。シードは要素の実初期値に合わせ、表示用の値は派生 getter で null ガードしてください。
384
+
341
385
  ### ラジオボタンバインディング
342
386
 
343
387
  `radio` でラジオボタングループを単一の状態値にバインドします:
@@ -1454,6 +1498,8 @@ error 時、プロパティは直前の fold 結果を保持し、エラーは `
1454
1498
 
1455
1499
  `wcBindable.inputs` は一方向のプロパティ入力(state → 要素)を宣言します。エントリに `attribute` を設定すると、フレームワークはプロパティを書き込むたびにその値を当該 HTML 属性へも書き込むため、`attributeChangedCallback`・CSS の属性セレクタ・DevTools がすべてプロパティ値と同期し続けます。
1456
1500
 
1501
+ `inputs` は属性ミラーのためだけのメタデータではありません。方向認識初期同期(既定 ON)の下では、メンバが **state から settable であること**を示すのが `inputs` です。settable なのに `properties` にしか宣言されていないメンバは output-only 扱いになり、state からの書き込みが抑止されます — [バインディング authority](#バインディング-authority-init--sync) を参照してください。
1502
+
1457
1503
  ```javascript
1458
1504
  class MyChip extends HTMLElement {
1459
1505
  static wcBindable = {
@@ -1552,7 +1598,7 @@ export default {
1552
1598
 
1553
1599
  これにより以下が生成されます:
1554
1600
 
1555
- - クラスの `static wcBindable` — フレームワークアダプタ用のプロトコルメタデータ
1601
+ - クラスの `static wcBindable` — フレームワークアダプタ用のプロトコルメタデータ。各 `$bindables` メンバは `properties` と `inputs` の両方に宣言され(双方向)、方向認識初期同期の下でも親 state → DCC の書き込みが機能します — [バインディング authority](#バインディング-authority-init--sync) 参照
1556
1602
  - プロトタイプの getter/setter — リアクティブプロキシ経由で読み書き
1557
1603
  - `CustomEvent` のディスパッチ — 値が変更されるたびに `my-counter:count-changed` が発火
1558
1604
 
@@ -1671,7 +1717,7 @@ bootstrapState({
1671
1717
  | `locale` | `'en'` | フィルタのデフォルトロケール |
1672
1718
  | `debug` | `false` | デバッグモード |
1673
1719
  | `enableMustache` | `true` | `{{ }}` 構文の有効化 |
1674
- | `enableDirectionalInitialSync` | `true` | 方向認識の初期同期(`#init=` / `#sync=` バインド modifier。例 `value#init=state: form.name`)。既定 on。`false` で opt-out |
1720
+ | `enableDirectionalInitialSync` | `true` | 方向認識のバインディング authority(`#init=` / `#sync=` バインド modifier)— [バインディング authority](#バインディング-authority-init--sync) 参照。既定 on。`false` で opt-out |
1675
1721
  | `enablePropagationContext` | `true` | バインド間の因果伝播トラッキング(echo/diamond のループ防止)。既定 on。`false` で opt-out |
1676
1722
  | `enableContractAnalyzer` | `false` | opt-in の開発時 contract analyzer(`analyzeContract` を公開) |
1677
1723
 
package/README.md CHANGED
@@ -324,6 +324,10 @@ Multiple bindings separated by `;`:
324
324
  | `#prevent` | Calls `event.preventDefault()` on event handlers |
325
325
  | `#stop` | Calls `event.stopPropagation()` on event handlers |
326
326
  | `#onchange` | Uses `change` event instead of `input` for two-way binding |
327
+ | `#init=<authority>` | Binding authority / initial sync direction — see [Binding Authority](#binding-authority-init--sync) |
328
+ | `#sync=<timing>` | Element snapshot timing — see [Binding Authority](#binding-authority-init--sync) |
329
+
330
+ Multiple modifiers are comma-separated after a single `#`: `value#ro,init=none: path`.
327
331
 
328
332
  ### Two-Way Binding
329
333
 
@@ -338,6 +342,46 @@ Automatically enabled for:
338
342
 
339
343
  `<input type="button">` is excluded. Use `#ro` to disable, `#onchange` to change the event.
340
344
 
345
+ ### Binding Authority (`#init=` / `#sync=`)
346
+
347
+ For custom elements that declare `static wcBindable`, every prop binding resolves an **authority** — which side owns the wire. The authority decides both where the initial value comes from *and* whether state→element writes are enabled, for the binding's whole lifetime. It is derived from where the member is declared (on by default via `enableDirectionalInitialSync`):
348
+
349
+ | Member declared in | Default authority | Effect |
350
+ |---|---|---|
351
+ | `properties` only (output-only) | `element` | The element's value flows into state; **state never writes this member** |
352
+ | `inputs` only | `state` | State writes the element |
353
+ | `properties` + `inputs` (two-way) | `state` | Classic behavior — state writes first, element events update state afterwards |
354
+ | — (no `wcBindable`; plain HTML elements) | `state` | Unchanged behavior |
355
+
356
+ > **Authoring rule:** declare every settable member in **both** `properties` and `inputs`. A member declared only in `properties` is output-only — state→element writes are suppressed for the life of the binding, and the element's own initial value overwrites whatever the state seeded. (`@wcstack` I/O node Shells and DCC `$bindables` follow this rule.)
357
+
358
+ Override the authority per binding with `#init=`:
359
+
360
+ | Value | Authority | Allowed on |
361
+ |---|---|---|
362
+ | `init=state` | State-owned: state → element (two-way members still receive element events) | inputs-only, two-way |
363
+ | `init=element` | Element-owned: element snapshot and events → state; state writes are suppressed | output-only, two-way |
364
+ | `init=auto` | `element` if the state slot is uninitialized, otherwise `state` | two-way |
365
+ | `init=none` | No initial sync (event bindings accept only this value) | any |
366
+
367
+ `#sync=` controls **when** the element snapshot is read for element-authority bindings:
368
+
369
+ | Value | Meaning |
370
+ |---|---|
371
+ | `sync=call` (default) | Read immediately when the binding attaches |
372
+ | `sync=connect` | Defer the read until the element is connected to the document |
373
+
374
+ ```html
375
+ <x-clock data-wcs="value#init=element: clock.now"></x-clock>
376
+ <x-input data-wcs="value#init=auto: form.name"></x-input>
377
+ <x-widget data-wcs="value#init=element,sync=connect: widget.snapshot"></x-widget>
378
+ ```
379
+
380
+ Notes:
381
+
382
+ - With `enableDirectionalInitialSync: false` (opt-out), writing `#init=`/`#sync=` throws.
383
+ - **Migrating from ≤ 1.20:** do not seed state with placeholder values (`value: []`, `query: ""`) for output-only members — the element's real initial value (often `null`/`undefined`) replaces the seed. Match the seed to the element's actual initial value and null-guard display values with a derived getter.
384
+
341
385
  ### Radio Binding
342
386
 
343
387
  Bind a radio button group to a single state value with `radio`:
@@ -1455,6 +1499,8 @@ See [docs/streams.md](docs/streams.md) for the full contract — lifecycle and o
1455
1499
 
1456
1500
  `wcBindable.inputs` declares one-way property inputs (state → element). When an entry sets `attribute`, the framework writes the value to that HTML attribute every time it writes the property, so `attributeChangedCallback`, CSS attribute selectors, and DevTools all stay in sync with the property value.
1457
1501
 
1502
+ `inputs` is not just attribute-mirroring metadata: under directional initial sync (default on), it is what marks a member as **settable from state**. A settable member declared only in `properties` becomes output-only and state writes to it are suppressed — see [Binding Authority](#binding-authority-init--sync).
1503
+
1458
1504
  ```javascript
1459
1505
  class MyChip extends HTMLElement {
1460
1506
  static wcBindable = {
@@ -1553,7 +1599,7 @@ export default {
1553
1599
 
1554
1600
  This generates:
1555
1601
 
1556
- - `static wcBindable` on the class — protocol metadata for framework adapters
1602
+ - `static wcBindable` on the class — protocol metadata for framework adapters. Each `$bindables` member is declared in both `properties` and `inputs` (two-way), so parent-state → DCC writes keep working under directional initial sync — see [Binding Authority](#binding-authority-init--sync)
1557
1603
  - Getter/setter on the prototype — reads/writes go through the reactive proxy
1558
1604
  - `CustomEvent` dispatch — `my-counter:count-changed` fires on every mutation
1559
1605
 
@@ -1672,7 +1718,7 @@ All options with defaults:
1672
1718
  | `locale` | `'en'` | Default locale for filters |
1673
1719
  | `debug` | `false` | Debug mode |
1674
1720
  | `enableMustache` | `true` | Enable `{{ }}` syntax |
1675
- | `enableDirectionalInitialSync` | `true` | Direction-aware initial sync (`#init=` / `#sync=` binding modifiers, e.g. `value#init=state: form.name`). Default on; set `false` to opt out |
1721
+ | `enableDirectionalInitialSync` | `true` | Direction-aware binding authority (`#init=` / `#sync=` binding modifiers) see [Binding Authority](#binding-authority-init--sync). Default on; set `false` to opt out |
1676
1722
  | `enablePropagationContext` | `true` | Causal propagation tracking across bindings (echo/diamond loop prevention). Default on; set `false` to opt out |
1677
1723
  | `enableContractAnalyzer` | `false` | Opt-in dev-time contract analyzer (exposes `analyzeContract`) |
1678
1724
 
package/dist/index.esm.js CHANGED
@@ -6058,7 +6058,7 @@ async function buildBindings(root) {
6058
6058
  }
6059
6059
  }
6060
6060
 
6061
- var version = "1.21.0";
6061
+ var version = "1.21.2";
6062
6062
  var pkg = {
6063
6063
  version: version};
6064
6064
 
@@ -8427,10 +8427,18 @@ function createWcBindable(tagName, bindables) {
8427
8427
  name: propName,
8428
8428
  event: `${tagName}:${propName}-changed`,
8429
8429
  }));
8430
+ // Every $bindables member gets both a getter and a setter on the DCC prototype,
8431
+ // so declare it in inputs as well — a property declared only in `properties` is
8432
+ // output-only under directional initial sync, which would permanently block
8433
+ // parent-state → DCC writes.
8434
+ const inputs = bindables.map((propName) => ({
8435
+ name: propName,
8436
+ }));
8430
8437
  return {
8431
8438
  protocol: "wc-bindable",
8432
8439
  version: 1,
8433
8440
  properties,
8441
+ inputs,
8434
8442
  };
8435
8443
  }
8436
8444
  function createBindableEventMap(tagName, bindables) {