> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# イベント

> Aftersell Cart SDK の全イベント: それぞれの発火タイミング、渡されるもの、用途、そして無限ループを引き起こすミス。

イベントを使うと、カートで**何かが起こったとき**にコードを実行できます。`window.aftersell.cart.events` の下にあります。

購読はセットアップの呼び出しなので、`ready()` を待つ必要はなく、スクリプトの冒頭で安全に行えます。

<div id="available-events">
  ## 利用可能なイベント
</div>

| イベント                                          | ペイロード                                                 | 発火タイミング                |
| --------------------------------------------- | ----------------------------------------------------- | ---------------------- |
| [`cart_loaded`](#cart_loaded)                 | [`AftersellCart`](/ja/aftersell/cart/sdk-cart-object) | カートの読み込み時。ページごとに一度。    |
| [`cart_updated`](#cart_updated)               | [`AftersellCart`](/ja/aftersell/cart/sdk-cart-object) | 初回読み込み後、カートの内容が変わったとき。 |
| [`item_added`](#item_added)                   | `{ item }`                                            | カートに新しいラインが現れたとき。      |
| [`item_removed`](#item_removed)               | `{ item }`                                            | カートからラインが消えたとき。        |
| [`cart_opened`](#cart_opened-and-cart_closed) | なし                                                    | ドロワーが開いたとき。            |
| [`cart_closed`](#cart_opened-and-cart_closed) | なし                                                    | ドロワーが閉じたとき。            |
| [`checkout`](#checkout)                       | なし                                                    | チェックアウトボタンがクリックされたとき。  |

<div id="subscribing">
  ## 購読
</div>

`events.on(event, handler)` はハンドラを登録し、**購読を解除する関数を返します**:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const off = window.aftersell.cart.events.on('cart_updated', (state) => {
  console.log('Cart total is now', state.totalPrice);
});

// later, to stop listening:
off();
```

* `events.once(event, handler)`: 一度だけ発火し、その後自動的に購読解除されます。
* `events.off(event, handler)`: 特定のハンドラを削除します。

throw したハンドラは隔離され、コンソールにログが記録されます。他のハンドラは引き続き実行されます。

***

<div id="the-two-rules">
  ## 2 つのルール
</div>

イベントに関するバグのほぼすべては、次のどちらかに行き着きます。

<div id="dont-change-the-cart-from-cart_updated-without-a-guard">
  ### ガードなしで `cart_updated` からカートを変更しない
</div>

`cart_updated` ハンドラの中でカートを変更すると、`cart_updated` が再び発火します。そのハンドラが再びカートを変更すると、無限ループになります。買い物客はカートが暴れ回るのを眺め、ページは Shopify を叩き続けます。

<Warning>
  **`cart_updated` や `cart_loaded` から無条件にアクションを呼び出してはいけません。** これから作ろうとする状態のチェックでガードし、2 回目のパスでは何もしないようにしてください。
</Warning>

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// ❌ Loops forever: every add triggers an update, which triggers another add.
window.aftersell.cart.events.on('cart_updated', (state) => {
  window.aftersell.cart.actions.addItem(GIFT_VARIANT_ID, 1);
});

// ✅ Guarded: once the gift is present, the condition is false and it stops.
window.aftersell.cart.events.on('cart_updated', (state) => {
  const hasGift = state.items.some((line) => line.variantId === GIFT_VARIANT_ID);
  if (state.totalPrice >= 5000 && !hasGift) {
    window.aftersell.cart.actions.addItem(GIFT_VARIANT_ID, 1);
  }
});
```

カートには 1 つのセーフティネットがあります。**同一の**カートを生む更新は何も発行しないため、何も変わらない再取得がサイクルを再開させることはありません。これは意図しない no-op ループからは守ってくれます。ただし、毎回本当にカートを変更するハンドラからは守って**くれません**。

<div id="treat-the-payload-as-read-only">
  ### ペイロードは読み取り専用として扱う
</div>

1 つのイベントのすべてのハンドラは*同じ*オブジェクトを受け取ります。それを書き換えると、あなたの後に実行されるハンドラ（ストア上の他のアプリのハンドラを含む）が見るものが変わってしまいます。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// ❌ Corrupts the payload for every later handler.
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items = state.items.filter((line) => line.finalLinePrice > 0);
});

// ✅ Copy first.
window.aftersell.cart.events.on('cart_updated', (state) => {
  const paidItems = state.items.filter((line) => line.finalLinePrice > 0);
});
```

実際にカートを変更するには[アクション](/ja/aftersell/cart/sdk-actions)を使ってください。ラインのレンダリング方法を変えるには [`registerLineTransform`](/ja/aftersell/cart/sdk-hooks#registerlinetransform) を使ってください。

***

<div id="cart_loaded">
  ## cart\_loaded
</div>

ページ上でカートが最初に読み込まれたときに**一度だけ**発火します。ペイロードは完全な[カートオブジェクト](/ja/aftersell/cart/sdk-cart-object)です。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_loaded', (state) => {
  console.log('Page loaded with', state.itemCount, 'items');
});
```

**用途:** カートの初期状態に対して実行する必要があるすべての処理。無料ギフトの調整、ウィジェットの初期化、ページ読み込み時のカート内容のアナリティクスへの送信などです。

**`cart_loaded` は遅れて購読した場合にも再送されます。** カートの読み込み後に購読した場合、ハンドラは現在のカートを引数に即座に呼び出されます。購読の順序は問題にならないため、スクリプトがカートより先に実行されたかどうかを気にする必要はありません。

<Tip>
  ページ読み込み時にもその後の変更ごとにも正しく動く必要があるロジックは、同じ関数で `cart_loaded` と `cart_updated` の**両方**を購読してください。これが「X をカートと同期させ続ける」ための標準パターンです。
</Tip>

<div id="cart_updated">
  ## cart\_updated
</div>

初回読み込み**後**、カートの内容が変わるたびに発火します。ドロワーから、あなた自身のアクションから、テーマから、あるいは別のアプリからの変更であっても同様です。ペイロードは完全な[カートオブジェクト](/ja/aftersell/cart/sdk-cart-object)です。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_updated', (state) => {
  document.querySelector('#my-total').textContent =
    window.aftersell.cart.actions.formatMoney(state.totalPrice);
});
```

**用途:** カートの外にあるもの（カスタムの合計表示、進捗バー、ヘッダーのバッジ、変更ごとのアナリティクスイベントなど）を同期させ続けることです。

同一のカートを生む更新は何も発行しません。ドロワーを開き直す、タブを戻す、同じ内容を返す再取得では発火しません。

<Warning>
  この中でアクションを呼ぶ前に、[2 つのルール](#the-two-rules)を読み直してください。
</Warning>

<div id="item_added">
  ## item\_added
</div>

カートに**新しいライン**が現れたときに発火します。ペイロードは `{ item }` で、`item` は[カートライン](/ja/aftersell/cart/sdk-cart-object#cart-lines)です。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('item_added', (payload) => {
  myAnalytics.track('Added to cart', {
    id: payload.item.variantId,
    title: payload.item.title,
    quantity: payload.item.quantity,
  });
});
```

**用途:** サードパーティのアナリティクスツールでのカート追加トラッキング。SDK の最も一般的な用途がこれです。[カート追加のトラッキング](/ja/aftersell/cart/sdk-use-case-analytics)を参照してください。

このイベントの導出方法について知っておくべき 2 点:

<Warning>
  **数量の変更は追加ではありません。** カートは数量ではなく*ライン*の差分から追加と削除を判定します。買い物客がラインを 1 から 3 に増やすと、`item_added` ではなく `cart_updated` が発火します。数量の増加も捕捉する必要がある場合は、`cart_updated` ハンドラ内で以前の状態と比較してください。
</Warning>

また、ページ読み込み時点ですでにカートにあったアイテムに対しては発火しません。それらは `cart_loaded` 経由で届きます。複数の異なる商品を一度に追加すると、イベントはラインごとに 1 回ずつ発火します。

<div id="item_removed">
  ## item\_removed
</div>

カートからラインが消えたときに発火します。ペイロードは `{ item }` で、消える直前のラインなので、`key`、`variantId`、`title` を引き続き読み取れます。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('item_removed', (payload) => {
  console.log('Removed', payload.item.title);
});
```

**用途:** 追加時に行った処理を元に戻すことです。フラグのクリア、買い物客が断ったオファーの再表示、削除のアナリティクスへの送信などです。

`item_added` と同じ注意点があります。ゼロにならずに数量を減らすことは削除ではありません。

<div id="cart_opened-and-cart_closed">
  ## cart\_opened と cart\_closed
</div>

ドロワーが開いたとき、閉じたときに発火します。ペイロードはありません。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_opened', () => {
  myAnalytics.track('Cart viewed');
});

window.aftersell.cart.events.on('cart_closed', () => {
  document.body.classList.remove('cart-is-open');
});
```

**用途:** 閲覧トラッキング、ドロワーの背後にある動画やカルーセルの一時停止、ページのクラスの切り替えなどです。

どちらも初回のページ読み込みでは発火せず、実際の開閉時にのみ発火します。

<div id="checkout">
  ## checkout
</div>

買い物客がチェックアウトボタンをクリックしたとき、ブラウザが遷移する直前に発火します。ペイロードはありません。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('checkout', () => {
  myAnalytics.track('Checkout started');
});
```

**用途:** チェックアウト意図のトラッキング。

<Warning>
  **このハンドラからチェックアウトをキャンセルすることはできません。** このイベントはゲートではなく通知です。コードが何をしようと、遷移は行われます。ハンドラは高速かつ同期的に保ってください。`await` や遅いネットワーク呼び出しは、ページがアンロードされる前に完了しない可能性があります。確実に送信する必要があるものには [`navigator.sendBeacon`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon) を使ってください。
</Warning>

***

<div id="listening-from-outside-the-sdk">
  ## SDK の外からのリッスン
</div>

すべてのイベントは、`window` 上の DOM `CustomEvent` としてもディスパッチされるため、`window.aftersell.cart` に触れずにリッスンできます。テーマファイル、サードパーティアプリ、カートとは独立して読み込まれるスクリプトから使う場合に便利です。

| バスのイベント        | DOM イベント                      |
| -------------- | ----------------------------- |
| `cart_loaded`  | `aftersell:cart:cart-loaded`  |
| `cart_updated` | `aftersell:cart:cart-updated` |
| `item_added`   | `aftersell:cart:item-added`   |
| `item_removed` | `aftersell:cart:item-removed` |
| `cart_opened`  | `aftersell:cart:cart-opened`  |
| `cart_closed`  | `aftersell:cart:cart-closed`  |
| `checkout`     | `aftersell:cart:checkout`     |

命名に注意してください。バスは `snake_case` を使い、DOM イベントは `aftersell:cart:` プレフィックスの後に `kebab-case` を使います。

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.addEventListener('aftersell:cart:cart-updated', (event) => {
  console.log('Cart total is now', event.detail.totalPrice);
});
```

ペイロードは `event.detail` に届き、[カートオブジェクト](/ja/aftersell/cart/sdk-cart-object)と一致します。イベントは `window` 上でディスパッチされるため、ページ上のどこにあるリスナーでも受信できます。カートは shadow root 内でレンダリングされますが、shadow の境界がイベントの経路に入ることはありません。ディスパッチごとにペイロードはクローンされるため、`event.detail` を書き換えるリスナーが他に影響を与えることはなく、throw するリスナーが SDK を妨げることもありません。

<Warning>
  **`cart-loaded` は DOM 上では再送されません。** バスは遅れて購読した場合に `cart_loaded` を再送しますが、その経路は DOM ディスパッチをバイパスするため、カートの読み込み後に登録された `window.addEventListener('aftersell:cart:cart-loaded')` は決して発火しません。スクリプトの読み込み順序が保証されない場合は、再送が行われる `window.aftersell.cart.events.on('cart_loaded', …)` を使うか、あわせて `aftersell:cart:cart-updated` もリッスンしてください。
</Warning>

<div id="shopify-standard-cart-events">
  ### Shopify 標準カートイベント
</div>

それとは別に、カートは自身がカートを変更するたびに Shopify の[標準カートイベント](https://shopify.dev/docs/storefronts/themes/best-practices/standard-events)を `document` 上で発行します。これにより、テーマのコードや他のアプリは、テーマの変更に反応するのと同じ方法で Aftersell の変更に反応できます:

| イベント                           | イベントインスタンス上のペイロード                                                              |
| ------------------------------ | ------------------------------------------------------------------------------ |
| `shopify:cart:lines-update`    | `action: 'add' \| 'update' \| 'remove'`、`context: 'cart' \| 'product'`、`lines` |
| `shopify:cart:note-update`     | `context: 'cart'`、`note`                                                       |
| `shopify:cart:discount-update` | `discountCodes: [{ code }]`                                                    |

<Warning>
  **ペイロードは `event.detail` にはありません。** `detail` は `{ source: 'aftersell' }` のみを保持します。これは、カートがループする代わりに自身のイベントを無視するためのタグです。上の表のすべては、イベントオブジェクトに直接代入されているため、`event.detail.action` ではなく `event.action` を読んでください。
</Warning>

各イベントは、Shopify の標準に合わせて、基盤となる書き込みが完了したときに Aftersell が確定させる `promise` も持ちます。これは await するもので、resolve するものではありません。これらは `document` 上でディスパッチされてバブルするため、`window` のリスナーでも受信できます。

<div id="where-to-go-next">
  ## 次のステップ
</div>

* **[カートオブジェクト](/ja/aftersell/cart/sdk-cart-object)**: 上記のペイロードの完全な構造。
* **[アクション](/ja/aftersell/cart/sdk-actions)**: ハンドラからカートを変更する方法。
* **[フック](/ja/aftersell/cart/sdk-hooks)**: カートに反応するのではなく、レンダリング方法を変更するためのもの。
* **[ユースケース](/ja/aftersell/cart/sdk-use-cases)**: アナリティクストラッキング、無料ギフトなどの完全な例。
