@wcstack/state 1.11.0 → 1.11.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/README.ja.md +195 -0
- package/README.md +26 -0
- package/dist/index.esm.js +120 -105
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -1094,6 +1094,201 @@ customElements.define("my-component", MyComponent);
|
|
|
1094
1094
|
</template>
|
|
1095
1095
|
```
|
|
1096
1096
|
|
|
1097
|
+
## Command Token(メソッドバインディング)
|
|
1098
|
+
|
|
1099
|
+
プロパティバインディング(`state.message: user.name`)はコンポーネントへ流れ込むデータを扱いますが、**state からコンポーネントのメソッドを起動する**こと —— `<wcs-fetch>.fetch()`、`<wcs-dialog>.open()` など —— はカバーしません。**command token** は型付きの pub/sub チャネルでこの隙間を埋めます:
|
|
1100
|
+
|
|
1101
|
+
- 要素は `command.<methodName>: $command.<tokenName>` で購読する
|
|
1102
|
+
- state は `this.$command.<tokenName>.emit(...args)` で emit する
|
|
1103
|
+
- `emit` に渡した引数はそのまま要素のメソッドへ転送される
|
|
1104
|
+
- 1つの token は複数の要素へファンアウトでき、subscribe 順は保持される
|
|
1105
|
+
|
|
1106
|
+
これによりパス契約は保たれます。state は要素への参照を一切保持せず、要素も state から何もインポートしません。共有されるオブジェクトは token のみです。
|
|
1107
|
+
|
|
1108
|
+
### 基本的な使い方
|
|
1109
|
+
|
|
1110
|
+
```html
|
|
1111
|
+
<wcs-state>
|
|
1112
|
+
<script type="module">
|
|
1113
|
+
export default {
|
|
1114
|
+
$commandTokens: ["fetchUsers", "refreshOrders"],
|
|
1115
|
+
|
|
1116
|
+
onClickFetch() {
|
|
1117
|
+
this.$command.fetchUsers.emit("/api/users", { method: "GET" });
|
|
1118
|
+
},
|
|
1119
|
+
onClickRefresh() {
|
|
1120
|
+
this.$command.refreshOrders.emit();
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
</script>
|
|
1124
|
+
</wcs-state>
|
|
1125
|
+
|
|
1126
|
+
<!-- 購読者 — wc-bindable なカスタム要素であること -->
|
|
1127
|
+
<wcs-fetch data-wcs="command.fetch: $command.fetchUsers"></wcs-fetch>
|
|
1128
|
+
<wcs-fetch data-wcs="command.fetch: $command.refreshOrders"></wcs-fetch>
|
|
1129
|
+
|
|
1130
|
+
<button data-wcs="onclick: onClickFetch">Fetch users</button>
|
|
1131
|
+
<button data-wcs="onclick: onClickRefresh">Refresh orders</button>
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
`onClickFetch` が実行されると、`fetchUsers` token を購読しているすべての要素の `fetch(...)` メソッドが転送された引数とともに呼び出されます。
|
|
1135
|
+
|
|
1136
|
+
### `$commandTokens` 宣言
|
|
1137
|
+
|
|
1138
|
+
`$commandTokens` 配列は、state 上の `$command` 名前空間に公開するチャネルを宣言します。token は `this.$command.<name>` でアクセスでき、memo 化されます —— 同じ名前は常に同一の token インスタンスを返します。
|
|
1139
|
+
|
|
1140
|
+
```javascript
|
|
1141
|
+
export default {
|
|
1142
|
+
$commandTokens: ["fetchUsers", "refreshOrders"],
|
|
1143
|
+
|
|
1144
|
+
click() {
|
|
1145
|
+
this.$command.fetchUsers.emit("/api/users");
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
- エントリは空でない文字列であること
|
|
1151
|
+
- 重複するエントリは初期化時にエラーになる
|
|
1152
|
+
- 予約名 `$command` 自体は配列に含められない
|
|
1153
|
+
- token は `$command` 配下にまとめられるためトップレベルの state 名前空間を汚さない。token と同名のリアクティブプロパティが共存できる
|
|
1154
|
+
- `$command` 上の未宣言の名前にアクセスする(例: `this.$command.typo`)と `undefined` が返る。typo はその後の `.emit()` 呼び出しで `TypeError` として、あるいはバインディングの右辺で使った場合は「CommandToken 値が必要」エラーとしてバインディング時に表面化する
|
|
1155
|
+
|
|
1156
|
+
### `command.<methodName>:` バインディング
|
|
1157
|
+
|
|
1158
|
+
```html
|
|
1159
|
+
<wcs-fetch data-wcs="command.fetch: $command.fetchUsers"></wcs-fetch>
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
| 部位 | 説明 |
|
|
1163
|
+
|---|---|
|
|
1164
|
+
| `command.` | 固定の prefix |
|
|
1165
|
+
| `<methodName>` | 起動する要素のメソッド。名前は `static wcBindable.commands` に `{ name: "<methodName>" }` として現れること |
|
|
1166
|
+
| `$command.<tokenName>` | `CommandToken` に解決される明示的な名前空間パス。`<tokenName>` は `$commandTokens` で宣言された名前であること |
|
|
1167
|
+
|
|
1168
|
+
右辺は `$command.<tokenName>` と書く必要があります —— ベア名の省略形(`fetchUsers`)は非対応です。`$command.` 名前空間を経由することでバインディングの意図が HTML 上で明示され、トップレベルの state 名前空間を token 名で汚さずに済みます。
|
|
1169
|
+
|
|
1170
|
+
`wcBindable.commands` は wc-bindable v1 仕様の形 —— `{ name: string; async?: boolean }` の配列 —— に従います:
|
|
1171
|
+
|
|
1172
|
+
```javascript
|
|
1173
|
+
class MyFetcher extends HTMLElement {
|
|
1174
|
+
static wcBindable = {
|
|
1175
|
+
protocol: "wc-bindable", version: 1,
|
|
1176
|
+
properties: [],
|
|
1177
|
+
commands: [
|
|
1178
|
+
{ name: "fetch", async: true },
|
|
1179
|
+
{ name: "reset" },
|
|
1180
|
+
],
|
|
1181
|
+
};
|
|
1182
|
+
fetch(url) { /* ... */ }
|
|
1183
|
+
reset() { /* ... */ }
|
|
1184
|
+
}
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
> **v1.9.1 以降の破壊的変更**: `commands` フィールドは `{ name, async? }` オブジェクトの配列になりました。以前の `commands: ["fetch"]` という素の文字列形式はもう受け付けられません —— そのような宣言に対するバインディングは `Command "<name>" is not declared in wcBindable.commands` を throw します。レガシーフォールバックはありません。宣言をオブジェクト形式に更新してください。
|
|
1188
|
+
|
|
1189
|
+
検証ルール(バインディング時に強制):
|
|
1190
|
+
|
|
1191
|
+
- 要素は `protocol: "wc-bindable"` かつ `version: 1` の `static wcBindable` を公開するカスタム要素であること
|
|
1192
|
+
- `methodName` は `wcBindable.commands` に(`name` で)現れること
|
|
1193
|
+
- バインドされる値は `CommandToken` であること(token 以外の値の代入は throw する —— 例えば未宣言の名前 `$command.typo` は `undefined` に解決され、ここで拒否される)
|
|
1194
|
+
|
|
1195
|
+
### Token API
|
|
1196
|
+
|
|
1197
|
+
```typescript
|
|
1198
|
+
interface CommandToken {
|
|
1199
|
+
readonly name: string;
|
|
1200
|
+
readonly size: number; // 現在の購読者数
|
|
1201
|
+
subscribe(fn: (...args) => unknown): () => void; // unsubscribe を返す
|
|
1202
|
+
unsubscribe(fn: (...args) => unknown): boolean;
|
|
1203
|
+
emit(...args: unknown[]): unknown[]; // subscribe 順に購読者の戻り値を返す
|
|
1204
|
+
}
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
`emit` は各購読者の戻り値の配列を(subscribe 順で)返します。`Promise` を返すメソッドは `Promise.all(token.emit(...))` でラップしてすべてを待ち受けてください。
|
|
1208
|
+
|
|
1209
|
+
### 購読のライフサイクル
|
|
1210
|
+
|
|
1211
|
+
- 購読者は要素を `WeakRef` で保持するため、token の購読者セットに残っていても、取り外された要素はガベージコレクト可能
|
|
1212
|
+
- `emit` 時、WeakRef が回収済みか要素が接続されていない(`isConnected === false`)場合、購読は自動的に破棄される(lazy purge)
|
|
1213
|
+
- 所有する `<wcs-state>` が disconnect されると、token レジストリ全体がクリアされる
|
|
1214
|
+
|
|
1215
|
+
要素のメソッドは `emit` の引数で呼び出されます:
|
|
1216
|
+
|
|
1217
|
+
```javascript
|
|
1218
|
+
this.$command.fetchUsers.emit(url, options);
|
|
1219
|
+
// → すべての購読者で element.fetch(url, options)
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
### DOM イベントから command を emit する
|
|
1223
|
+
|
|
1224
|
+
command token は state コードから emit する必要はありません。DOM イベントバインディングの右辺を、state メソッド名ではなく `$command.<name>` パスに向けることで、直接 emit できます:
|
|
1225
|
+
|
|
1226
|
+
```html
|
|
1227
|
+
<button data-wcs="onclick: $command.refreshList">Refresh</button>
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
| 形式 | 右辺 | イベント時の動作 |
|
|
1231
|
+
|---|---|---|
|
|
1232
|
+
| `onclick: someMethod` | state メソッド名 | `state.someMethod(event, ...listIndexes)` |
|
|
1233
|
+
| `onclick: $command.someToken` | `$command.<name>` パス | `state.$command.someToken.emit(event, ...listIndexes)` |
|
|
1234
|
+
|
|
1235
|
+
これは純粋な配線です。イベント端点を command token 端点に接続するだけで、間にロジックは入りません。`emit` の引数はハンドラ呼び出しとまったく同じく透過されます —— まず DOM の `Event`、続いて内包するリストインデックス —— なので購読者は `(event, ...listIndexes)` を受け取ります。購読者の中で必要なものをイベントから取り出してください(`event.target.value`、`event.detail` など)。
|
|
1236
|
+
|
|
1237
|
+
- 右辺は `$command.<name>` であり、`<name>` は `$commandTokens` で宣言されていること。`CommandToken` に解決されないパス(例: typo)はイベント時に throw する。
|
|
1238
|
+
- 修飾子はそのまま機能する: `onclick#prevent: $command.someToken` は emit の前に `preventDefault()` を呼ぶ(`#stop` も同様)。
|
|
1239
|
+
- これは state が emit するのと同じ token を emit するので、`command.<method>: $command.someToken` で配線された要素の購読者は、誰がトリガを引いたかに関わらず受け取る。
|
|
1240
|
+
|
|
1241
|
+
```html
|
|
1242
|
+
<!-- click が command を全購読者へファンアウトする。state メソッドは不要 -->
|
|
1243
|
+
<button data-wcs="onclick: $command.reset">Reset all</button>
|
|
1244
|
+
<my-field data-wcs="command.clear: $command.reset"></my-field>
|
|
1245
|
+
<my-list data-wcs="command.reset: $command.reset"></my-list>
|
|
1246
|
+
```
|
|
1247
|
+
|
|
1248
|
+
## Inputs と属性ミラー
|
|
1249
|
+
|
|
1250
|
+
`wcBindable.inputs` は一方向のプロパティ入力(state → 要素)を宣言します。エントリに `attribute` を設定すると、フレームワークはプロパティを書き込むたびにその値を当該 HTML 属性へも書き込むため、`attributeChangedCallback`・CSS の属性セレクタ・DevTools がすべてプロパティ値と同期し続けます。
|
|
1251
|
+
|
|
1252
|
+
```javascript
|
|
1253
|
+
class MyChip extends HTMLElement {
|
|
1254
|
+
static wcBindable = {
|
|
1255
|
+
protocol: "wc-bindable", version: 1,
|
|
1256
|
+
properties: [],
|
|
1257
|
+
inputs: [
|
|
1258
|
+
{ name: "data", attribute: "data" }, // プロパティ名 === 属性名
|
|
1259
|
+
{ name: "labelText", attribute: "label-text" }, // kebab-case ミラー
|
|
1260
|
+
{ name: "internal" }, // ミラーなし、プロパティのみ
|
|
1261
|
+
],
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
```
|
|
1265
|
+
|
|
1266
|
+
```html
|
|
1267
|
+
<my-chip data-wcs="data: chip.payload; labelText: chip.title"></my-chip>
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
state が値を更新すると、プロパティと属性の両方が書き込まれます:
|
|
1271
|
+
|
|
1272
|
+
```text
|
|
1273
|
+
chip.payload = { id: 1 } → element.data = { id: 1 } かつ setAttribute("data", '{"id":1}')
|
|
1274
|
+
chip.title = "新着" → element.labelText = "新着" かつ setAttribute("label-text", "新着")
|
|
1275
|
+
chip.payload = null → element.data = null かつ removeAttribute("data")
|
|
1276
|
+
```
|
|
1277
|
+
|
|
1278
|
+
属性値のエンコード:
|
|
1279
|
+
|
|
1280
|
+
| 値の型 | ミラーされる属性 |
|
|
1281
|
+
|---|---|
|
|
1282
|
+
| `string` / `number` / `boolean` / `bigint` | `String(value)` |
|
|
1283
|
+
| `null` / `undefined` | 属性を削除 |
|
|
1284
|
+
| `object` / `array` | `JSON.stringify(value)`(循環参照時は `String(value)` にフォールバック) |
|
|
1285
|
+
|
|
1286
|
+
補足:
|
|
1287
|
+
|
|
1288
|
+
- `attribute` を**持たない** `inputs` エントリはプロパティのみ —— 値はプロパティに書き込まれるが属性には触れない
|
|
1289
|
+
- ミラーはベストエフォート: `setAttribute` の失敗は握りつぶされ(`debug` 警告付き)、プロパティ書き込みをブロックしない
|
|
1290
|
+
- ネイティブ HTML 要素は `inputs` を完全に無視する —— ミラーは `static wcBindable` を公開するカスタム要素でのみ有効になる
|
|
1291
|
+
|
|
1097
1292
|
## 宣言的カスタムコンポーネント (DCC)
|
|
1098
1293
|
|
|
1099
1294
|
JavaScript のクラス定義なしで、**HTML だけ**でカスタム要素を定義できます。`data-wc-definition` と Declarative Shadow DOM (`<template shadowrootmode>`) を使い、リアクティブな状態を持つ再利用可能なコンポーネントをインラインで宣言します。
|
package/README.md
CHANGED
|
@@ -1221,6 +1221,32 @@ this.$command.fetchUsers.emit(url, options);
|
|
|
1221
1221
|
// → element.fetch(url, options) on every subscriber
|
|
1222
1222
|
```
|
|
1223
1223
|
|
|
1224
|
+
### Emitting a Command from a DOM Event
|
|
1225
|
+
|
|
1226
|
+
A command token does not have to be emitted from state code. A DOM event binding can emit one directly by pointing its right-hand side at a `$command.<name>` path instead of a state method name:
|
|
1227
|
+
|
|
1228
|
+
```html
|
|
1229
|
+
<button data-wcs="onclick: $command.refreshList">Refresh</button>
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
| Form | Right-hand side | Behavior on event |
|
|
1233
|
+
|---|---|---|
|
|
1234
|
+
| `onclick: someMethod` | a state method name | `state.someMethod(event, ...listIndexes)` |
|
|
1235
|
+
| `onclick: $command.someToken` | a `$command.<name>` path | `state.$command.someToken.emit(event, ...listIndexes)` |
|
|
1236
|
+
|
|
1237
|
+
This is pure wiring: the event endpoint is connected to a command-token endpoint, with no logic in between. The `emit` arguments are passed through exactly like a handler call — the DOM `Event` first, then any enclosing list indexes — so subscribers receive `(event, ...listIndexes)`. Inside a subscriber, pull what you need from the event (`event.target.value`, `event.detail`, …).
|
|
1238
|
+
|
|
1239
|
+
- The right-hand side must be `$command.<name>` with `<name>` declared in `$commandTokens`. A path that does not resolve to a `CommandToken` (e.g. a typo) throws at event time.
|
|
1240
|
+
- Modifiers work unchanged: `onclick#prevent: $command.someToken` calls `preventDefault()` before emitting (`#stop` likewise).
|
|
1241
|
+
- This emits the same token the state emits, so element subscribers wired with `command.<method>: $command.someToken` receive it regardless of who pulled the trigger.
|
|
1242
|
+
|
|
1243
|
+
```html
|
|
1244
|
+
<!-- click fans the command out to every subscriber, no state method needed -->
|
|
1245
|
+
<button data-wcs="onclick: $command.reset">Reset all</button>
|
|
1246
|
+
<my-field data-wcs="command.clear: $command.reset"></my-field>
|
|
1247
|
+
<my-list data-wcs="command.reset: $command.reset"></my-list>
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1224
1250
|
## Inputs and Attribute Mirror
|
|
1225
1251
|
|
|
1226
1252
|
`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.
|
package/dist/index.esm.js
CHANGED
|
@@ -59,7 +59,7 @@ function setConfig(partialConfig) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
var version$1 = "1.11.
|
|
62
|
+
var version$1 = "1.11.1";
|
|
63
63
|
var pkg = {
|
|
64
64
|
version: version$1};
|
|
65
65
|
|
|
@@ -1693,6 +1693,96 @@ function processDeferredNode(entry) {
|
|
|
1693
1693
|
return result.bindings;
|
|
1694
1694
|
}
|
|
1695
1695
|
|
|
1696
|
+
const _cache$3 = new WeakMap();
|
|
1697
|
+
const _cacheNullListIndex$1 = new WeakMap();
|
|
1698
|
+
class StateAddress {
|
|
1699
|
+
pathInfo;
|
|
1700
|
+
listIndex;
|
|
1701
|
+
_parentAddress;
|
|
1702
|
+
constructor(pathInfo, listIndex) {
|
|
1703
|
+
this.pathInfo = pathInfo;
|
|
1704
|
+
this.listIndex = listIndex;
|
|
1705
|
+
}
|
|
1706
|
+
get parentAddress() {
|
|
1707
|
+
if (typeof this._parentAddress !== 'undefined') {
|
|
1708
|
+
return this._parentAddress;
|
|
1709
|
+
}
|
|
1710
|
+
const parentPathInfo = this.pathInfo.parentPathInfo;
|
|
1711
|
+
if (parentPathInfo === null) {
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
const lastSegment = this.pathInfo.segments[this.pathInfo.segments.length - 1];
|
|
1715
|
+
let parentListIndex = null;
|
|
1716
|
+
if (lastSegment === WILDCARD) {
|
|
1717
|
+
parentListIndex = this.listIndex?.parentListIndex ?? null;
|
|
1718
|
+
}
|
|
1719
|
+
else {
|
|
1720
|
+
parentListIndex = this.listIndex;
|
|
1721
|
+
}
|
|
1722
|
+
return this._parentAddress = createStateAddress(parentPathInfo, parentListIndex);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
function createStateAddress(pathInfo, listIndex) {
|
|
1726
|
+
if (listIndex === null) {
|
|
1727
|
+
let cached = _cacheNullListIndex$1.get(pathInfo);
|
|
1728
|
+
if (typeof cached !== "undefined") {
|
|
1729
|
+
return cached;
|
|
1730
|
+
}
|
|
1731
|
+
cached = new StateAddress(pathInfo, null);
|
|
1732
|
+
_cacheNullListIndex$1.set(pathInfo, cached);
|
|
1733
|
+
return cached;
|
|
1734
|
+
}
|
|
1735
|
+
else {
|
|
1736
|
+
let cacheByPathInfo = _cache$3.get(listIndex);
|
|
1737
|
+
if (typeof cacheByPathInfo === "undefined") {
|
|
1738
|
+
cacheByPathInfo = new WeakMap();
|
|
1739
|
+
_cache$3.set(listIndex, cacheByPathInfo);
|
|
1740
|
+
}
|
|
1741
|
+
let cached = cacheByPathInfo.get(pathInfo);
|
|
1742
|
+
if (typeof cached !== "undefined") {
|
|
1743
|
+
return cached;
|
|
1744
|
+
}
|
|
1745
|
+
cached = new StateAddress(pathInfo, listIndex);
|
|
1746
|
+
cacheByPathInfo.set(pathInfo, cached);
|
|
1747
|
+
return cached;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// _subscribers は Set のため挿入順を保持する。
|
|
1752
|
+
// emit() は subscribe() された順に呼び出され、戻り値配列も同じ順序で返る。
|
|
1753
|
+
class CommandToken {
|
|
1754
|
+
_name;
|
|
1755
|
+
_subscribers = new Set();
|
|
1756
|
+
constructor(name) {
|
|
1757
|
+
this._name = name;
|
|
1758
|
+
}
|
|
1759
|
+
get name() {
|
|
1760
|
+
return this._name;
|
|
1761
|
+
}
|
|
1762
|
+
get size() {
|
|
1763
|
+
return this._subscribers.size;
|
|
1764
|
+
}
|
|
1765
|
+
subscribe(fn) {
|
|
1766
|
+
this._subscribers.add(fn);
|
|
1767
|
+
return () => {
|
|
1768
|
+
this._subscribers.delete(fn);
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
unsubscribe(fn) {
|
|
1772
|
+
return this._subscribers.delete(fn);
|
|
1773
|
+
}
|
|
1774
|
+
emit(...args) {
|
|
1775
|
+
const results = [];
|
|
1776
|
+
for (const fn of this._subscribers) {
|
|
1777
|
+
results.push(fn(...args));
|
|
1778
|
+
}
|
|
1779
|
+
return results;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
function isCommandToken(value) {
|
|
1783
|
+
return value instanceof CommandToken;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1696
1786
|
const loopContextByNode = new WeakMap();
|
|
1697
1787
|
function getLoopContextByNode(node) {
|
|
1698
1788
|
let paramNode = node;
|
|
@@ -1721,13 +1811,18 @@ const connectedCallbackSymbol = Symbol("$$connectedCallback");
|
|
|
1721
1811
|
const disconnectedCallbackSymbol = Symbol("$$disconnectedCallback");
|
|
1722
1812
|
const updatedCallbackSymbol = Symbol("$$updatedCallback");
|
|
1723
1813
|
|
|
1814
|
+
// onclick: $command.<name> のように、DOM イベントから command token を直接 emit する形式かを判定する。
|
|
1815
|
+
// 右辺が $command 名前空間配下のパス($command.<token>)のときに true。
|
|
1816
|
+
function isCommandTokenPath(statePathName) {
|
|
1817
|
+
return statePathName.startsWith(STATE_COMMAND_NAMESPACE_NAME + ".");
|
|
1818
|
+
}
|
|
1724
1819
|
const handlerByHandlerKey$3 = new Map();
|
|
1725
1820
|
const bindingSetByHandlerKey$3 = new Map();
|
|
1726
1821
|
function getHandlerKey$3(binding) {
|
|
1727
1822
|
const modifierKey = binding.propModifiers.filter(m => m === 'prevent' || m === 'stop').sort().join(',');
|
|
1728
1823
|
return `${binding.stateName}::${binding.statePathName}::${modifierKey}`;
|
|
1729
1824
|
}
|
|
1730
|
-
const stateEventHandlerFunction = (stateName, handlerName, modifiers) => (event) => {
|
|
1825
|
+
const stateEventHandlerFunction = (stateName, handlerName, modifiers, statePathInfo) => (event) => {
|
|
1731
1826
|
if (modifiers.includes('prevent'))
|
|
1732
1827
|
event.preventDefault();
|
|
1733
1828
|
if (modifiers.includes('stop'))
|
|
@@ -1739,13 +1834,23 @@ const stateEventHandlerFunction = (stateName, handlerName, modifiers) => (event)
|
|
|
1739
1834
|
raiseError(`State element with name "${stateName}" not found for event handler.`);
|
|
1740
1835
|
}
|
|
1741
1836
|
const loopContext = getLoopContextByNode(node);
|
|
1837
|
+
const isCommand = isCommandTokenPath(handlerName);
|
|
1742
1838
|
stateElement.createStateAsync("writable", async (state) => {
|
|
1743
1839
|
state[setLoopContextSymbol](loopContext, () => {
|
|
1840
|
+
const indexes = loopContext?.listIndex.indexes ?? [];
|
|
1841
|
+
if (isCommand) {
|
|
1842
|
+
// command token を解決して emit。引数はハンドラ呼び出しと同じく (event, ...listIndexes) を透過する。
|
|
1843
|
+
const token = state[getByAddressSymbol](createStateAddress(statePathInfo, null));
|
|
1844
|
+
if (!isCommandToken(token)) {
|
|
1845
|
+
raiseError(`Event binding "${handlerName}" did not resolve to a CommandToken. Declare the name in $commandTokens and reference it as $command.<name>.`);
|
|
1846
|
+
}
|
|
1847
|
+
return token.emit(event, ...indexes);
|
|
1848
|
+
}
|
|
1744
1849
|
const handler = state[handlerName];
|
|
1745
1850
|
if (typeof handler !== "function") {
|
|
1746
1851
|
raiseError(`Handler "${handlerName}" is not a function on state "${stateName}".`);
|
|
1747
1852
|
}
|
|
1748
|
-
return Reflect.apply(handler, state, [event, ...
|
|
1853
|
+
return Reflect.apply(handler, state, [event, ...indexes]);
|
|
1749
1854
|
});
|
|
1750
1855
|
});
|
|
1751
1856
|
};
|
|
@@ -1756,7 +1861,7 @@ function attachEventHandler(binding) {
|
|
|
1756
1861
|
const key = getHandlerKey$3(binding);
|
|
1757
1862
|
let stateEventHandler = handlerByHandlerKey$3.get(key);
|
|
1758
1863
|
if (typeof stateEventHandler === "undefined") {
|
|
1759
|
-
stateEventHandler = stateEventHandlerFunction(binding.stateName, binding.statePathName, binding.propModifiers);
|
|
1864
|
+
stateEventHandler = stateEventHandlerFunction(binding.stateName, binding.statePathName, binding.propModifiers, binding.statePathInfo);
|
|
1760
1865
|
handlerByHandlerKey$3.set(key, stateEventHandler);
|
|
1761
1866
|
}
|
|
1762
1867
|
const eventName = binding.propName.slice(2);
|
|
@@ -1937,19 +2042,19 @@ function setLastListValueByAbsoluteStateAddress(address, value) {
|
|
|
1937
2042
|
lastListValueByAbsoluteStateAddress.set(address, value);
|
|
1938
2043
|
}
|
|
1939
2044
|
|
|
1940
|
-
const _cache$
|
|
2045
|
+
const _cache$2 = new WeakMap();
|
|
1941
2046
|
function getAbsolutePathInfo(stateElement, pathInfo) {
|
|
1942
|
-
if (_cache$
|
|
1943
|
-
const pathMap = _cache$
|
|
2047
|
+
if (_cache$2.has(stateElement)) {
|
|
2048
|
+
const pathMap = _cache$2.get(stateElement);
|
|
1944
2049
|
if (pathMap.has(pathInfo)) {
|
|
1945
2050
|
return pathMap.get(pathInfo);
|
|
1946
2051
|
}
|
|
1947
2052
|
}
|
|
1948
2053
|
else {
|
|
1949
|
-
_cache$
|
|
2054
|
+
_cache$2.set(stateElement, new WeakMap());
|
|
1950
2055
|
}
|
|
1951
2056
|
const absolutePathInfo = Object.freeze(new AbsolutePathInfo(stateElement, pathInfo));
|
|
1952
|
-
_cache$
|
|
2057
|
+
_cache$2.get(stateElement).set(pathInfo, absolutePathInfo);
|
|
1953
2058
|
return absolutePathInfo;
|
|
1954
2059
|
}
|
|
1955
2060
|
class AbsolutePathInfo {
|
|
@@ -1970,8 +2075,8 @@ class AbsolutePathInfo {
|
|
|
1970
2075
|
}
|
|
1971
2076
|
}
|
|
1972
2077
|
|
|
1973
|
-
const _cache$
|
|
1974
|
-
const _cacheNullListIndex
|
|
2078
|
+
const _cache$1 = new WeakMap();
|
|
2079
|
+
const _cacheNullListIndex = new WeakMap();
|
|
1975
2080
|
class AbsoluteStateAddress {
|
|
1976
2081
|
absolutePathInfo;
|
|
1977
2082
|
listIndex;
|
|
@@ -2001,19 +2106,19 @@ class AbsoluteStateAddress {
|
|
|
2001
2106
|
}
|
|
2002
2107
|
function createAbsoluteStateAddress(absolutePathInfo, listIndex) {
|
|
2003
2108
|
if (listIndex === null) {
|
|
2004
|
-
let cached = _cacheNullListIndex
|
|
2109
|
+
let cached = _cacheNullListIndex.get(absolutePathInfo);
|
|
2005
2110
|
if (typeof cached !== "undefined") {
|
|
2006
2111
|
return cached;
|
|
2007
2112
|
}
|
|
2008
2113
|
cached = new AbsoluteStateAddress(absolutePathInfo, null);
|
|
2009
|
-
_cacheNullListIndex
|
|
2114
|
+
_cacheNullListIndex.set(absolutePathInfo, cached);
|
|
2010
2115
|
return cached;
|
|
2011
2116
|
}
|
|
2012
2117
|
else {
|
|
2013
|
-
let cacheByAbsolutePathInfo = _cache$
|
|
2118
|
+
let cacheByAbsolutePathInfo = _cache$1.get(listIndex);
|
|
2014
2119
|
if (typeof cacheByAbsolutePathInfo === "undefined") {
|
|
2015
2120
|
cacheByAbsolutePathInfo = new WeakMap();
|
|
2016
|
-
_cache$
|
|
2121
|
+
_cache$1.set(listIndex, cacheByAbsolutePathInfo);
|
|
2017
2122
|
}
|
|
2018
2123
|
let cached = cacheByAbsolutePathInfo.get(absolutePathInfo);
|
|
2019
2124
|
if (typeof cached !== "undefined") {
|
|
@@ -2189,41 +2294,6 @@ function applyChangeToClass(binding, _context, newValue) {
|
|
|
2189
2294
|
element.classList.toggle(className, newValue);
|
|
2190
2295
|
}
|
|
2191
2296
|
|
|
2192
|
-
// _subscribers は Set のため挿入順を保持する。
|
|
2193
|
-
// emit() は subscribe() された順に呼び出され、戻り値配列も同じ順序で返る。
|
|
2194
|
-
class CommandToken {
|
|
2195
|
-
_name;
|
|
2196
|
-
_subscribers = new Set();
|
|
2197
|
-
constructor(name) {
|
|
2198
|
-
this._name = name;
|
|
2199
|
-
}
|
|
2200
|
-
get name() {
|
|
2201
|
-
return this._name;
|
|
2202
|
-
}
|
|
2203
|
-
get size() {
|
|
2204
|
-
return this._subscribers.size;
|
|
2205
|
-
}
|
|
2206
|
-
subscribe(fn) {
|
|
2207
|
-
this._subscribers.add(fn);
|
|
2208
|
-
return () => {
|
|
2209
|
-
this._subscribers.delete(fn);
|
|
2210
|
-
};
|
|
2211
|
-
}
|
|
2212
|
-
unsubscribe(fn) {
|
|
2213
|
-
return this._subscribers.delete(fn);
|
|
2214
|
-
}
|
|
2215
|
-
emit(...args) {
|
|
2216
|
-
const results = [];
|
|
2217
|
-
for (const fn of this._subscribers) {
|
|
2218
|
-
results.push(fn(...args));
|
|
2219
|
-
}
|
|
2220
|
-
return results;
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
function isCommandToken(value) {
|
|
2224
|
-
return value instanceof CommandToken;
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
2297
|
/**
|
|
2228
2298
|
* command.<methodName>: <commandToken-path> バインディングの適用ハンドラ。
|
|
2229
2299
|
*
|
|
@@ -2306,61 +2376,6 @@ function applyChangeToCommand(binding, _context, newValue) {
|
|
|
2306
2376
|
subscribedBindings.set(binding, { token, unsubscribe, elementRef });
|
|
2307
2377
|
}
|
|
2308
2378
|
|
|
2309
|
-
const _cache$1 = new WeakMap();
|
|
2310
|
-
const _cacheNullListIndex = new WeakMap();
|
|
2311
|
-
class StateAddress {
|
|
2312
|
-
pathInfo;
|
|
2313
|
-
listIndex;
|
|
2314
|
-
_parentAddress;
|
|
2315
|
-
constructor(pathInfo, listIndex) {
|
|
2316
|
-
this.pathInfo = pathInfo;
|
|
2317
|
-
this.listIndex = listIndex;
|
|
2318
|
-
}
|
|
2319
|
-
get parentAddress() {
|
|
2320
|
-
if (typeof this._parentAddress !== 'undefined') {
|
|
2321
|
-
return this._parentAddress;
|
|
2322
|
-
}
|
|
2323
|
-
const parentPathInfo = this.pathInfo.parentPathInfo;
|
|
2324
|
-
if (parentPathInfo === null) {
|
|
2325
|
-
return null;
|
|
2326
|
-
}
|
|
2327
|
-
const lastSegment = this.pathInfo.segments[this.pathInfo.segments.length - 1];
|
|
2328
|
-
let parentListIndex = null;
|
|
2329
|
-
if (lastSegment === WILDCARD) {
|
|
2330
|
-
parentListIndex = this.listIndex?.parentListIndex ?? null;
|
|
2331
|
-
}
|
|
2332
|
-
else {
|
|
2333
|
-
parentListIndex = this.listIndex;
|
|
2334
|
-
}
|
|
2335
|
-
return this._parentAddress = createStateAddress(parentPathInfo, parentListIndex);
|
|
2336
|
-
}
|
|
2337
|
-
}
|
|
2338
|
-
function createStateAddress(pathInfo, listIndex) {
|
|
2339
|
-
if (listIndex === null) {
|
|
2340
|
-
let cached = _cacheNullListIndex.get(pathInfo);
|
|
2341
|
-
if (typeof cached !== "undefined") {
|
|
2342
|
-
return cached;
|
|
2343
|
-
}
|
|
2344
|
-
cached = new StateAddress(pathInfo, null);
|
|
2345
|
-
_cacheNullListIndex.set(pathInfo, cached);
|
|
2346
|
-
return cached;
|
|
2347
|
-
}
|
|
2348
|
-
else {
|
|
2349
|
-
let cacheByPathInfo = _cache$1.get(listIndex);
|
|
2350
|
-
if (typeof cacheByPathInfo === "undefined") {
|
|
2351
|
-
cacheByPathInfo = new WeakMap();
|
|
2352
|
-
_cache$1.set(listIndex, cacheByPathInfo);
|
|
2353
|
-
}
|
|
2354
|
-
let cached = cacheByPathInfo.get(pathInfo);
|
|
2355
|
-
if (typeof cached !== "undefined") {
|
|
2356
|
-
return cached;
|
|
2357
|
-
}
|
|
2358
|
-
cached = new StateAddress(pathInfo, listIndex);
|
|
2359
|
-
cacheByPathInfo.set(pathInfo, cached);
|
|
2360
|
-
return cached;
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
|
|
2364
2379
|
const indexBindingsByContent = new WeakMap();
|
|
2365
2380
|
function getIndexBindingsByContent(content) {
|
|
2366
2381
|
return indexBindingsByContent.get(content) ?? [];
|