@wcstack/state 4.2.0 → 4.3.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.
Files changed (41) hide show
  1. package/README.ja.md +7 -6
  2. package/README.md +7 -6
  3. package/dist/auto.min.js +1 -1
  4. package/dist/auto.min.js.map +1 -1
  5. package/dist/index.esm.js +1 -1
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/manifest.esm.js +5 -4
  8. package/dist/split/auto.js +1 -1
  9. package/dist/split/chunks/{chunk-JK3B3LLB.js → chunk-5Q4M6A3C.js} +2 -2
  10. package/dist/split/chunks/{chunk-UPSLR4FS.js → chunk-6I7GGFNJ.js} +2 -2
  11. package/dist/split/chunks/chunk-BKLFDVOP.js +2 -0
  12. package/dist/split/chunks/chunk-BKLFDVOP.js.map +1 -0
  13. package/dist/split/chunks/{chunk-BYQBV3ZC.js → chunk-DOWWBZVS.js} +2 -2
  14. package/dist/split/chunks/chunk-F3DVWVH3.js +2 -0
  15. package/dist/split/chunks/chunk-F3DVWVH3.js.map +1 -0
  16. package/dist/split/chunks/chunk-M67C46SN.js +2 -0
  17. package/dist/split/chunks/{chunk-OKBVNGFK.js.map → chunk-M67C46SN.js.map} +1 -1
  18. package/dist/split/chunks/chunk-NMOSSDKJ.js +2 -0
  19. package/dist/split/chunks/chunk-NMOSSDKJ.js.map +1 -0
  20. package/dist/split/core.js +1 -1
  21. package/dist/split/features/devtools.js +1 -1
  22. package/dist/split/features/diagnostics.js +1 -1
  23. package/dist/split/features/diagnostics.js.map +1 -1
  24. package/dist/split/features/list-keys.js +1 -1
  25. package/dist/split/features/recursion.js +1 -1
  26. package/dist/split/features/scopes.js +1 -1
  27. package/dist/split/features/ssr.js +1 -1
  28. package/dist/split/features/ssr.js.map +1 -1
  29. package/dist/split/features/temporal.js +1 -1
  30. package/dist/wcs-manifest.json +4 -8
  31. package/package.json +1 -1
  32. package/dist/split/chunks/chunk-3Q7LGONS.js +0 -2
  33. package/dist/split/chunks/chunk-3Q7LGONS.js.map +0 -1
  34. package/dist/split/chunks/chunk-4ECL4ITF.js +0 -2
  35. package/dist/split/chunks/chunk-4ECL4ITF.js.map +0 -1
  36. package/dist/split/chunks/chunk-BI4645UG.js +0 -2
  37. package/dist/split/chunks/chunk-BI4645UG.js.map +0 -1
  38. package/dist/split/chunks/chunk-OKBVNGFK.js +0 -2
  39. /package/dist/split/chunks/{chunk-JK3B3LLB.js.map → chunk-5Q4M6A3C.js.map} +0 -0
  40. /package/dist/split/chunks/{chunk-UPSLR4FS.js.map → chunk-6I7GGFNJ.js.map} +0 -0
  41. /package/dist/split/chunks/{chunk-BYQBV3ZC.js.map → chunk-DOWWBZVS.js.map} +0 -0
package/README.ja.md CHANGED
@@ -191,9 +191,9 @@
191
191
  <script type="importmap">
192
192
  {
193
193
  "imports": {
194
- "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.2.0/dist/split/core.js",
195
- "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.2.0/dist/split/features/temporal.js",
196
- "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.2.0/dist/split/features/scopes.js"
194
+ "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.3.0/dist/split/core.js",
195
+ "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.3.0/dist/split/features/temporal.js",
196
+ "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.3.0/dist/split/features/scopes.js"
197
197
  }
198
198
  }
199
199
  </script>
@@ -253,7 +253,7 @@
253
253
  ページが名指したアドオンを、同じビルドから読み込みます。
254
254
 
255
255
  ```html
256
- <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.2.0/dist/split/auto.js"></script>
256
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.3.0/dist/split/auto.js"></script>
257
257
  <wcs-state features="scopes diagnostics">
258
258
  <script type="module">
259
259
  export default {
@@ -760,6 +760,7 @@ export default {
760
760
  - **値は決してマークアップにならない。** バインディングがページに入れるもの —— `textContent:` や `innerHTML:` の値、カスタム要素がバインドされた値から描画する子 —— がバインディングとして読まれることはありません: その中の `{{ }}`・`data-wcs`・コメントバインディングは、ただのテキストのまま残ります。
761
761
  - バインディングが中身を設定する要素(`textContent:`・`text:`・`innerText:`・`innerHTML:`・`html:`)の子はバインドされません。ページレベルでもテンプレートの中でも同じで、`#init=element` や `#init=none` によって書いた子がそのまま残る場合も同じです。`outerHTML:` / `outerText:` をバインドしたページレベルの要素の子もバインドされません。バインディングが必要なマークアップは別の要素に置いてください。
762
762
  - `<script>`・`<style>`・`<noscript>`・`<iframe>` の中身は走査しません。その要素自身の `data-wcs`(`srcdoc:`、`attr.src:`)はバインドされます。
763
+ - **`data-wcs-ignore` の付いた要素はまったく走査しません。** その要素自身の `data-wcs` も中身も — そこにある `{{ }}`・`data-wcs`・コメントバインディングは、ページレベルでも、テンプレートの中でも、router が渡す中身でも読みません。ページが書いていないマークアップのための印です([セキュリティ](#セキュリティ))。属性名はバインド属性の名前に `-ignore` を付けたものなので、`bindAttributeName` に従います。4.3 から。
763
764
  - `<wcs-state>` 要素の内側に書いたマークアップはページの一部です。Light DOM コンポーネントの中身は、ページではなくそのコンポーネント自身のエンジンがバインドします。
764
765
  - ページレベルでは、要素の子が要素自身のバインディングより先にバインドされます。そのため、カスタム要素が最初のプロパティ書き込みを受け取るのは子がバインドされた後で、`$errorCallback` も子の失敗から先に受け取ります。テンプレートの中では文書順です。
765
766
  - `for:` の行と `if:` の枝は、`data-wcs` 属性を除いたテンプレートの複製です。そのため `[data-wcs…]` に対する CSS ルールやテストのセレクタはそれらの要素にマッチしません —— クラスや自前の `data-*` 属性を使ってください。
@@ -1410,7 +1411,7 @@ export default {
1410
1411
  };
1411
1412
  ```
1412
1413
 
1413
- **同じオブジェクトが 2 つの位置にあるとき。** 1 つのリストの 2 つの位置に同じオブジェクトがあると(`items: [o, o, …]`)、片方の行の下のキーへの書き込み(`this["items.0.name"] = "z"`)でオブジェクトは変わりますが、もう片方の行のバインディングと行 getter は古い値を表示し続けます: 1 つのリストの中では、書き込みはそれを通した行にしか届かないためです。素の読み取り・ルートの getter・`$getAll` には新しい値が見えます。普通のキーの 2 つの配列が同じオブジェクトを持つ場合(`backup = items; items = items.filter(…)` で両方を描画する形)も同じです。こうした書き込みの後に `$postUpdate("items")` を呼ぶと、そのリストのすべての行がオブジェクトを表示し直します。外側の 2 つの行が、同じオブジェクトを持つ別々の配列を持つ場合(`groups: [{ items: a }, { items: [...a] }]`)も同じで、もう片方の一覧(`"groups.1.items"`)か外側の一覧(`"groups"`)の `$postUpdate` を呼んでください。これはこのバージョンの既知の制約です。getter が配列を結び付ける場合 —— TodoMVC 風の絞り込み(`todos` を絞り込んだコピーを返す `get shown()` を `for: shown` で描画する形)、並べ替え、切り出し —— は、どちら側から書いても、オブジェクトを持つすべての行と `todos.*` を読むものに届き、そのオブジェクトを配列に持つ getter は評価し直されます(チェックした行は未完了の絞り込みから抜け、名前を変えた行は並べ替えた表示で動きます)。書いたオブジェクトを配列に持たない getter は評価し直しません: 配列を読んだのであって、オブジェクトは読んでいないためです —— どの行にも追従させるには、その中で `$getAll("todos.*.done")` を読んでください。
1414
+ **同じオブジェクトが 2 つの位置にあるとき。** 1 つのリストの 2 つの位置に同じオブジェクトがあると(`items: [o, o, …]`)、片方の行の下のキーへの書き込み(`this["items.0.name"] = "z"`)は、そのオブジェクトを持つすべての行、その行 getter、`$getAll` に届きます(4.3 から)。配列はそうした書き込みのために 1 回だけ調べ、その要素がその場で書かれると調べ直します。普通のキーの 2 つの配列が同じオブジェクトを持つ場合(`backup = items; items = items.filter(…)` で両方を描画する形)は、こうは結び付きません: 片方の行の下への書き込みはもう片方の行に届きません(素の読み取り・ルートの getter・`$getAll` には新しい値が見えます)。こうした書き込みの後に、もう片方のリストの `$postUpdate`(`$postUpdate("backup")`)を呼ぶと、そのリストのすべての行がオブジェクトを表示し直します。外側の 2 つの行が、同じオブジェクトを持つ別々の配列を持つ場合(`groups: [{ items: a }, { items: [...a] }]`)も同じで、もう片方の一覧(`"groups.1.items"`)か外側の一覧(`"groups"`)の `$postUpdate` を呼んでください。これはこのバージョンの既知の制約です。getter が配列を結び付ける場合 —— TodoMVC 風の絞り込み(`todos` を絞り込んだコピーを返す `get shown()` を `for: shown` で描画する形)、並べ替え、切り出し —— は、どちら側から書いても、オブジェクトを持つすべての行と `todos.*` を読むものに届き、そのオブジェクトを配列に持つ getter は評価し直されます(チェックした行は未完了の絞り込みから抜け、名前を変えた行は並べ替えた表示で動きます)。書いたオブジェクトを配列に持たない getter は評価し直しません: 配列を読んだのであって、オブジェクトは読んでいないためです —— どの行にも追従させるには、その中で `$getAll("todos.*.done")` を読んでください。
1414
1415
 
1415
1416
  ## 再帰パス(`$recursion`)
1416
1417
 
@@ -3162,7 +3163,7 @@ this.$getAll("matrix.*.*", [row]);
3162
3163
 
3163
3164
  `@wcstack/state` は値を DOM に入れます。値をサニタイズはせず、値をマークアップとして読むこともありません。マークアップとして読むのはページそのものです —— そのため、規則は「何がページに届くか」と「何をどこにバインドするか」についてのものになります。
3164
3165
 
3165
- **自分で書いていないマークアップをページ走査に届かせてはいけません。** `<wcs-state>` は自分のルートをバインドするとき、マークアップの中のすべての `{{ … }}`、すべての `data-wcs` 属性、すべてのコメントバインディング(`<!--@@: …-->`)を読みます([ページ走査が何をバインドするか](#ページ走査が何をバインドするか))。サーバーのテンプレートやページのコードが、state ではなくページのマークアップへ書き込んだユーザーの内容も同じように読まれます: `{{ apiKey }}` と入力されたユーザー名は state の `apiKey` を描画し、投稿されたメッセージの中の `data-wcs` 属性はバインドされます —— クライアントサイドテンプレートインジェクション(CSTI)です。コメントバインディングは `$behavior.enableMustache: false` でもバインドされるので、`{{ }}` を OFF にしても防げません。4.0 には除外用の属性はありません(`data-wcs-ignore` はありません)。代わりに:
3166
+ **自分で書いていないマークアップをページ走査に届かせてはいけません。** `<wcs-state>` は自分のルートをバインドするとき、マークアップの中のすべての `{{ … }}`、すべての `data-wcs` 属性、すべてのコメントバインディング(`<!--@@: …-->`)を読みます([ページ走査が何をバインドするか](#ページ走査が何をバインドするか))。サーバーのテンプレートやページのコードが、state ではなくページのマークアップへ書き込んだユーザーの内容も同じように読まれます: `{{ apiKey }}` と入力されたユーザー名は state の `apiKey` を描画し、投稿されたメッセージの中の `data-wcs` 属性はバインドされます —— クライアントサイドテンプレートインジェクション(CSTI)です。コメントバインディングは `$behavior.enableMustache: false` でもバインドされるので、`{{ }}` を OFF にしても防げません。4.3 からは、そうした内容を持つ要素に `data-wcs-ignore` を付けてください(`<div class="comment" data-wcs-ignore><%= comment %></div>`): 走査はその中を何も読みません([ページ走査が何をバインドするか](#ページ走査が何をバインドするか))。さらによいのは、ユーザーの内容をマークアップに入れないことです:
3166
3167
 
3167
3168
  - ユーザーの内容は **state** に入れ、`textContent:`(または、テキストである `{{ }}` / コメントバインディング)でバインドしてください —— 値がマークアップとして読まれることはありません。
3168
3169
  - ユーザーの HTML を `innerHTML:` / `html:` でバインドするのは、サニタイズした後だけにしてください。HTML のシンク(`innerHTML:`・`outerHTML:`・`srcdoc:`・`html:`)は、すでに `TrustedHTML` であるものを除くすべての値を、設置したポリシー(`setTrustedTypesPolicy()`、または `globalThis[Symbol.for("wcstack.trustedTypes.policy")]`)に通します。ブラウザに Trusted Types があってもなくても同じなので、設置したサニタイザは必ず走ります。このパッケージが identity ポリシーで値に署名することはありません([docs/csp.ja.md §7](../../docs/csp.ja.md))。
package/README.md CHANGED
@@ -192,9 +192,9 @@ leaves features out, it can compose them instead:
192
192
  <script type="importmap">
193
193
  {
194
194
  "imports": {
195
- "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.2.0/dist/split/core.js",
196
- "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.2.0/dist/split/features/temporal.js",
197
- "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.2.0/dist/split/features/scopes.js"
195
+ "@wcstack/state/core": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.3.0/dist/split/core.js",
196
+ "@wcstack/state/features/temporal": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.3.0/dist/split/features/temporal.js",
197
+ "@wcstack/state/features/scopes": "https://cdn.jsdelivr.net/npm/@wcstack/state@4.3.0/dist/split/features/scopes.js"
198
198
  }
199
199
  }
200
200
  </script>
@@ -254,7 +254,7 @@ receptacle the owning add-on fills on install).
254
254
  core, and the add-ons the page names from the same build.
255
255
 
256
256
  ```html
257
- <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.2.0/dist/split/auto.js"></script>
257
+ <script type="module" src="https://cdn.jsdelivr.net/npm/@wcstack/state@4.3.0/dist/split/auto.js"></script>
258
258
  <wcs-state features="scopes diagnostics">
259
259
  <script type="module">
260
260
  export default {
@@ -766,6 +766,7 @@ When a `<wcs-state>` binds its root (the document, a shadow root, or a Light DOM
766
766
  - **A value is never markup.** What a binding puts into the page — a `textContent:` or `innerHTML:` value, the children a custom element renders from a bound value — is never read as bindings: `{{ }}`, `data-wcs` and comment bindings in it stay text.
767
767
  - The children of an element whose content a binding sets (`textContent:`, `text:`, `innerText:`, `innerHTML:`, `html:`) are not bound, at page level or inside templates — also when `#init=element` or `#init=none` leaves the children you wrote in place. Nor are the children of a page-level element bound with `outerHTML:` / `outerText:`. Put markup that needs bindings in a separate element.
768
768
  - The content of `<script>`, `<style>`, `<noscript>` and `<iframe>` is not walked; the element's own `data-wcs` (`srcdoc:`, `attr.src:`) still binds.
769
+ - **An element marked `data-wcs-ignore` is not walked at all**, neither its own `data-wcs` nor anything in it: no `{{ }}`, `data-wcs` or comment binding there is read, at page level, inside templates, or in content the router hands over. It is the mark for markup the page did not write ([Security](#security)). The attribute is the bind attribute's name plus `-ignore`, so it follows `bindAttributeName`. Since 4.3.
769
770
  - Markup written inside a `<wcs-state>` element is part of the page. A Light DOM component's content is bound by the component's own engine, not by the page.
770
771
  - At page level an element's children are bound before the element's own bindings, so a custom element receives its first property writes after its children are bound, and `$errorCallback` receives failures children first. Inside templates the order is document order.
771
772
  - `for:` rows and `if:` branches are clones of their templates without the `data-wcs` attribute, so a CSS rule or a test selector on `[data-wcs…]` does not match those elements — use a class or a `data-*` attribute of your own.
@@ -1416,7 +1417,7 @@ export default {
1416
1417
  };
1417
1418
  ```
1418
1419
 
1419
- **One object at two positions.** When the same object sits at two positions of one list (`items: [o, o, …]`), a write to a key below one of those rows (`this["items.0.name"] = "z"`) changes the object, but the other row's bindings and row getters keep showing the old value: within one list, a write reaches only the row it was made through. Plain reads, root getters and `$getAll` see the new value. The same holds for two arrays at plain keys that share an object (`backup = items; items = items.filter(…)`, both rendered). Call `$postUpdate("items")` after such a write, and every row of that list shows the object again. So do two outer rows holding different arrays that share an object (`groups: [{ items: a }, { items: [...a] }]`): call `$postUpdate` on the other list (`"groups.1.items"`) or on the outer one (`"groups"`). This is a known limitation of this version. When a getter relates the arrays — a TodoMVC-style filter (`get shown()` returning a filtered copy of `todos`, rendered with `for: shown`), a sort, a slice — a write from either side reaches every row holding the object and the `todos.*` readers, and the getters whose array holds it are evaluated again (a checked row leaves an "active" filter, a renamed row moves in a sorted view). A getter whose array does not hold the written object is not evaluated again: it read the array, not the object — read `$getAll("todos.*.done")` in it to follow every row.
1420
+ **One object at two positions.** When the same object sits at two positions of one list (`items: [o, o, …]`), a write to a key below one of those rows (`this["items.0.name"] = "z"`) reaches every row holding it, their row getters and `$getAll` (since 4.3). The array is looked at once for such a write, and again after an element of it is written in place. Two arrays at plain keys that share an object (`backup = items; items = items.filter(…)`, both rendered) are not related that way: a write below a row of one does not reach the other's row, although plain reads, root getters and `$getAll` see the new value. Call `$postUpdate` on the other list (`$postUpdate("backup")`) after such a write, and every row of that list shows the object again. So do two outer rows holding different arrays that share an object (`groups: [{ items: a }, { items: [...a] }]`): call `$postUpdate` on the other list (`"groups.1.items"`) or on the outer one (`"groups"`). This is a known limitation of this version. When a getter relates the arrays — a TodoMVC-style filter (`get shown()` returning a filtered copy of `todos`, rendered with `for: shown`), a sort, a slice — a write from either side reaches every row holding the object and the `todos.*` readers, and the getters whose array holds it are evaluated again (a checked row leaves an "active" filter, a renamed row moves in a sorted view). A getter whose array does not hold the written object is not evaluated again: it read the array, not the object — read `$getAll("todos.*.done")` in it to follow every row.
1420
1421
 
1421
1422
  ## Recursive Paths (`$recursion`)
1422
1423
 
@@ -3172,7 +3173,7 @@ In short:
3172
3173
 
3173
3174
  `@wcstack/state` puts values into the DOM; it does not sanitize them, and it never reads a value as markup. What it does read as markup is the page itself — so the rules are about what reaches the page, and what you bind where.
3174
3175
 
3175
- **Markup you did not write must not reach the page walk.** When a `<wcs-state>` binds its root, it reads every `{{ … }}`, every `data-wcs` attribute and every comment binding (`<!--@@: …-->`) in the markup ([What the page walk binds](#what-the-page-walk-binds)). User content that a server template or your page code writes into the page's markup, rather than into state, is read the same way: a user name typed as `{{ apiKey }}` renders the state's `apiKey`, and a `data-wcs` attribute in a posted message binds — client-side template injection (CSTI). Comment bindings are bound even with `$behavior.enableMustache: false`, so turning `{{ }}` off does not protect against it. There is no opt-out attribute in 4.0 (no `data-wcs-ignore`). Instead:
3176
+ **Markup you did not write must not reach the page walk.** When a `<wcs-state>` binds its root, it reads every `{{ … }}`, every `data-wcs` attribute and every comment binding (`<!--@@: …-->`) in the markup ([What the page walk binds](#what-the-page-walk-binds)). User content that a server template or your page code writes into the page's markup, rather than into state, is read the same way: a user name typed as `{{ apiKey }}` renders the state's `apiKey`, and a `data-wcs` attribute in a posted message binds — client-side template injection (CSTI). Comment bindings are bound even with `$behavior.enableMustache: false`, so turning `{{ }}` off does not protect against it. Since 4.3, mark the element that holds such content with `data-wcs-ignore` (`<div class="comment" data-wcs-ignore><%= comment %></div>`): the walk reads nothing in it ([What the page walk binds](#what-the-page-walk-binds)). Better still, keep user content out of the markup:
3176
3177
 
3177
3178
  - Put user content in **state**, and bind it with `textContent:` (or `{{ }}` / a comment binding, which are text) — a value is never read as markup.
3178
3179
  - Bind user HTML with `innerHTML:` / `html:` only after sanitizing it. The HTML sinks (`innerHTML:`, `outerHTML:`, `srcdoc:`, `html:`) pass every value that is not already a `TrustedHTML` through the policy you install (`setTrustedTypesPolicy()`, or `globalThis[Symbol.for("wcstack.trustedTypes.policy")]`), with or without Trusted Types in the browser — so an installed sanitizer always runs. This package never signs a value with an identity policy ([docs/csp.md §7](../../docs/csp.md)).