# Woo Custom Events

> Listen to Woo runtime events for mini-cart drawers, analytics, cart refreshes, and checkout instrumentation.

# Woo Custom Events

Woo runtime events are browser `CustomEvent` objects. They bubble, so you can listen on a component, a nearby wrapper, or `document`.

Use events as the integration contract for work that happens after a Woo action. Do not use `data-ome-state="success"` as an integration contract; that state is visible UI feedback for the control that just finished an action.

## Which Event Should I Use?

| Goal | Event |
| --- | --- |
| Open a mini-cart, track an add-to-cart conversion, or inspect the product that was added. | `ome-woo:item-added` |
| Refresh any cart-related integration after add, quantity update, or item removal. | `ome-woo:cart-updated` |
| Track add-to-cart failures. | `ome-woo:add-to-cart-error` |
| React to quantity changes or item removal with row-level detail. | `ome-woo:item-quantity-updated` or `ome-woo:item-removed` |
| React to committed variable-product selection state inside a Product Provider. | `ome-woo:variation-selection-changed` |
| Track checkout start, completion, and checkout errors. | `ome-woo:checkout-started`, `ome-woo:checkout-complete`, `ome-woo:checkout-error` |

`ome-woo:item-added` fires first for successful add-to-cart and includes `product_id`, `quantity`, and `cart_summary`. `ome-woo:cart-updated` follows when the integration only needs to know that the cart changed.

## Event Summary

| Area | Event | Dispatch target | Detail payload |
| --- | --- | --- | --- |
| Cart boot | `ome-woo:cart-loaded` | First cart display root found, or `document.body`. | `{ cart_summary }` |
| Product add | `ome-woo:item-added` | Standalone add button or submitted `AddToCartForm`. | `{ product_id, quantity, cart_summary }` |
| Product add | `ome-woo:cart-updated` | Same target as the successful cart mutation. | `{ cart_summary }` |
| Product add | `ome-woo:add-to-cart-error` | Standalone add button or submitted `AddToCartForm`. | `{ product_id, quantity, error }` |
| Product selection | `ome-woo:variation-selection-changed` | Nearest `ProductProvider` root. | `{ product_id, selection, selected_variation, current_product }` |
| Cart item mutation | `ome-woo:item-quantity-updated` | Cart item quantity input. | `{ key, quantity, cart_summary }` |
| Cart item mutation | `ome-woo:item-removed` | Cart item remove button, or `document.body` if the button was detached first. | `{ key, cart_summary }` |
| Cart item mutation | `ome-woo:cart-error` | Cart item quantity input or remove button. | `{ action, key, quantity?, error }` |
| Checkout | `ome-woo:checkout-loaded` | Checkout provider, checkout form, checkout notices, or `document.body`. | `{ checkout }` |
| Checkout | `ome-woo:checkout-started` | Checkout form. | `{}` |
| Checkout | `ome-woo:checkout-complete` | Checkout form. | `{ checkout }` |
| Checkout | `ome-woo:checkout-error` | Checkout form. | `{ errors?, phase?, source?, error?, diagnostic? }` |

## Shared Payload Shapes

`cart_summary` is the live cart summary after the mutation:

```ts
type WooCartSummary = {
	item_count: number;
	line_count: number;
};
```

`error` is a safe Woo error summary:

```ts
type WooErrorSummary = {
	code: string;
	message: string;
	status: number;
	field_keys?: string[];
};
```

`checkout` is the current Store API checkout response. It can include fields such as `order_id`, `status`, `order_key`, `payment_method`, `payment_result`, and `__experimentalCart`.

`diagnostic` is present on payment adapter failures when the adapter can provide a safe diagnostic object. It can include `methodId`, `adapterId`, `provider`, `paymentType`, `fieldMode`, `phase`, `code`, `message`, `details`, and `sequence`.

## Product Selection Event

### `ome-woo:variation-selection-changed`

| Contract | Value |
| --- | --- |
| Dispatch target | The nearest `OmeWooProductProvider` root. Standalone `AddToCartForm` does not emit this event. |
| Bubbles | Yes. |
| Composed | Yes. |
| Cancelable | No. |
| When it fires | After Provider datasets and form mirrors have committed a new semantic variable-product selection state. Initialization emits only the final settled state. Recommitting the same state does not emit again. Simple products do not emit it. |
| Detail payload | `{ product_id, selection: { phase, attributes }, selected_variation, current_product }` |

`selection.phase` is `base`, `partial`, `resolved`, or `invalid`. `selected_variation` is the complete resolved variation object or `null`. `current_product` is the complete base product until resolution succeeds, then the complete resolved variation. Availability remains a field of the product or variation object; it is not encoded in the phase.

```ts
document.addEventListener("ome-woo:variation-selection-changed", (event) => {
	const detail = (event as CustomEvent).detail;

	if (detail.selection.phase === "resolved") {
		console.log(detail.selected_variation);
	}
});
```

## Product Add Events

### `ome-woo:item-added`

| Contract | Value |
| --- | --- |
| Dispatch target | The standalone `AddToCartButton`, or the submitted `AddToCartForm` for form-submit buttons. |
| Bubbles | Yes. |
| When it fires | After Store API `POST /cart/add-item` succeeds and the runtime has a fresh cart summary. |
| Detail payload | `{ product_id, quantity, cart_summary: { item_count, line_count } }` |

Use this event when the integration needs the product and quantity that were just added.

### `ome-woo:cart-updated`

| Contract | Value |
| --- | --- |
| Dispatch target | The same element that dispatched the successful cart mutation event. |
| Bubbles | Yes. |
| When it fires | After add-to-cart, quantity update, or item removal succeeds. |
| Detail payload | `{ cart_summary: { item_count, line_count } }` |

Use this event when the integration only needs the latest cart count or line count.

Cart mutation scheduling is intentionally not a public event stream. There is no generic transaction-lifecycle event. For local visual progress, style `data-ome-woo-mutation-phase`; for business integrations, keep using the semantic events above.

### `ome-woo:add-to-cart-error`

| Contract | Value |
| --- | --- |
| Dispatch target | The standalone `AddToCartButton`, or the submitted `AddToCartForm` for form-submit buttons. |
| Bubbles | Yes. |
| When it fires | After Store API `POST /cart/add-item` fails. |
| Detail payload | `{ product_id, quantity, error: { code, message, status, field_keys? } }` |

## Cart Item Events

### `ome-woo:cart-loaded`

| Contract | Value |
| --- | --- |
| Dispatch target | First matching cart display root: `[data-ome-woo-cart-count]`, `[data-ome-woo-cart-total-field]`, `[data-ome-woo-cart-items]`, `[data-ome-woo-cart-notices]`, or `document.body`. |
| Bubbles | Yes. |
| When it fires | After Woo cart boot loads and cart display components are bound. |
| Detail payload | `{ cart_summary: { item_count, line_count } }` |

### `ome-woo:item-quantity-updated`

| Contract | Value |
| --- | --- |
| Dispatch target | The cart item quantity input. |
| Bubbles | Yes. |
| When it fires | After each real Store API quantity response is committed. Debounced-away, cancelled, and already-satisfied no-op targets do not emit it. An intermediate response still emits this semantic event, while control-local success waits for the latest target. |
| Detail payload | `{ key, quantity, cart_summary: { item_count, line_count } }` |

### `ome-woo:item-removed`

| Contract | Value |
| --- | --- |
| Dispatch target | The cart item remove button. If that button was detached before the request finished, the event is dispatched from `document.body`. |
| Bubbles | Yes. |
| When it fires | After Store API `POST /cart/remove-item` succeeds. |
| Detail payload | `{ key, cart_summary: { item_count, line_count } }` |

### `ome-woo:cart-error`

| Contract | Value |
| --- | --- |
| Dispatch target | The cart item quantity input for quantity failures, or the remove button for removal failures. |
| Bubbles | Yes. |
| When it fires | After Store API quantity update or item removal fails. |
| Detail payload | `{ action, key, quantity?, error: { code, message, status, field_keys? } }`, where `action` is `"update-item"` or `"remove-item"`. |

## Checkout Events

### `ome-woo:checkout-loaded`

| Contract | Value |
| --- | --- |
| Dispatch target | First matching checkout root: `[data-ome-woo-checkout-provider]`, `form[data-ome-woo-checkout-form]`, `[data-ome-woo-checkout-notices]`, or `document.body`. |
| Bubbles | Yes. |
| When it fires | After checkout boot loads and checkout components are bound. |
| Detail payload | `{ checkout }` |

### `ome-woo:checkout-started`

| Contract | Value |
| --- | --- |
| Dispatch target | The checkout form. |
| Bubbles | Yes. |
| When it fires | After local submit validation passes and before payment preparation or Store API checkout submission. |
| Detail payload | `{}` |

### `ome-woo:checkout-complete`

| Contract | Value |
| --- | --- |
| Dispatch target | The checkout form. |
| Bubbles | Yes. |
| When it fires | After Store API checkout succeeds. Redirect handling can happen after this event. |
| Detail payload | `{ checkout }` |

### `ome-woo:checkout-error`

| Contract | Value |
| --- | --- |
| Dispatch target | The checkout form. |
| Bubbles | Yes. |
| When it fires | After local validation, payment preparation, payment result handling, or Store API checkout fails. |
| Detail payload | Local validation: `{ errors, phase, source: "local-validation" }`. Runtime/payment failure: `{ error, diagnostic? }`. |

`errors` is an array of checkout field errors:

```ts
type WooCheckoutFieldError = {
	field_key: string;
	code: "required" | "invalid_email" | "invalid_country" | "invalid_state";
	message: string;
};
```

`phase` is `"submit"` or `"update"`. `source` is currently `"local-validation"` when the checkout form blocks submission before Store API checkout.

## Example: Open A Mini-Cart Drawer After Add To Cart

Author the drawer and trigger normally:

<CodeExample title="Authored mini-cart drawer" language="jsx">
{`<OmeDrawerTrigger
  targeting='{{"targetDrawerId":"store-mini-cart"}}'
  content='{{"label":"Cart"}}'
/>

<OmeDrawer
  identity='{{"drawerId":"store-mini-cart"}}'
  settings='{{"direction":"right","dismissible":true}}'
>
  {#slot default}
    <OmeDrawerTitle>Shopping bag</OmeDrawerTitle>
    <OmeWooCartItems>
      {#slot default}
        <OmeWooCartItemImage />
        <OmeWooCartItemTitle />
        <OmeWooCartItemQuantity />
        <OmeWooCartItemRemove />
      {/slot}
      {#slot empty}
        Your cart is empty.
      {/slot}
    </OmeWooCartItems>
    <OmeWooCartTotalsList />
    <OmeDrawerClose>Close</OmeDrawerClose>
  {/slot}
</OmeDrawer>`}
</CodeExample>

Then click the real trigger when `ome-woo:item-added` bubbles:

<CodeExample title="Open the drawer after add-to-cart" language="js">
{`document.addEventListener("ome-woo:item-added", () => {
  const trigger = document.querySelector(
    '[data-ome-drawer-trigger][data-ome-target-drawer-id="store-mini-cart"]',
  );

  if (trigger instanceof HTMLButtonElement) {
    trigger.click();
  }
});`}
</CodeExample>

This keeps `OmeDrawerTrigger` and `OmeDrawer` as the drawer owners. The Woo event only decides when to open it.

## Example: Track Add-To-Cart Analytics

<CodeExample title="Track product adds" language="js">
{`document.addEventListener("ome-woo:item-added", (event) => {
  if (!(event instanceof CustomEvent)) {
    return;
  }

  const { product_id, quantity, cart_summary } = event.detail;

  window.dataLayer?.push({
    event: "add_to_cart",
    product_id,
    quantity,
    cart_items: cart_summary.item_count,
  });
});`}
</CodeExample>

Use `ome-woo:item-added` here because analytics usually needs the specific product and quantity.

## Example: Refresh Cart Integrations

<CodeExample title="Refresh after any cart change" language="js">
{`document.addEventListener("ome-woo:cart-updated", (event) => {
  if (!(event instanceof CustomEvent)) {
    return;
  }

  const { cart_summary } = event.detail;
  document.documentElement.dataset.cartItems = String(cart_summary.item_count);
});`}
</CodeExample>

Use `ome-woo:cart-updated` when the integration does not care whether the cart changed because of add, quantity, or removal.

## Example: Capture Checkout Errors

<CodeExample title="Listen for checkout errors" language="js">
{`document.addEventListener("ome-woo:checkout-error", (event) => {
  if (!(event instanceof CustomEvent)) {
    return;
  }

  const { errors, error, diagnostic } = event.detail;

  console.warn("Checkout failed", {
    fieldErrors: errors,
    error,
    diagnostic,
  });
});`}
</CodeExample>

Use this for logging, support tooling, or checkout instrumentation. Let `CheckoutNotices` remain responsible for visible customer messages.
