# How Facets Work

> Target IDs, request flow, URL state, pagination, load more, reset, and map pairing.

<!-- Sources: src/Facets/FacetBlockInterceptor.php; src/Facets/FacetTemplateStore.php; src/Facets/FacetRestEndpoint.php; src/Facets/FacetRequestExecutor.php; client/src/domains/facets/service/facet-controller.ts; client/src/domains/facets/service/facet-target-registry.ts; tests/e2e/components/facets/url-params.spec.ts; tests/e2e/components/facets/pagination-facet.spec.ts; tests/e2e/components/facets/load-more-facet.spec.ts; tests/e2e/components/facets/map-facet.spec.ts -->

# How Facets Work

Facet runtime is target-centric. Every control identifies a `target`, and every `Facet Target` with the same target ID participates in that target scope.

## Render and Refresh Lifecycle

1. During the original page render, `FacetBlockInterceptor` detects `Facet Target` components.
2. The target's loop and fallback slots are normalized and stored as the refresh template.
3. Facet controls register query descriptors for the same target ID.
4. On interaction, the client builds scoped facet clauses and calls the facet REST endpoint.
5. The endpoint applies those clauses to the target loop config, re-renders the stored target template, computes pagination and availability metadata, and returns markup.
6. The client replaces the target content, updates URL state, updates option availability, applies pagination metadata, and reruns returned scripts when the target allows it.

## Target ID Rules

- The `target` string is the connection between controls and result containers.
- Controls with different target IDs do not affect each other.
- Multiple target containers can share a target ID, but they share the same scoped request state.
- Empty target IDs are not useful at runtime; the PHP components show inline builder guidance when target is missing.

## Facet Target Behavior

`Facet Target` owns the content that gets replaced. Its loop slot is the normal result state, and its fallback slot is shown when the refreshed loop produces no rendered items.

Important target props:

| Prop | Meaning |
| --- | --- |
| `target` | Shared target ID. |
| `rerun_scripts` | Allows scripts returned by refreshed markup to run again. Defaults to `true`. |
| `preview_fallback` | Shows the fallback slot in Etch preview instead of the loop slot. |
| `scroll_to_top` | Scrolls to the target after connected facet updates. |
| `used_with_map` | Marks the target as map-paired, so map and result updates coordinate correctly. |
| `load_more_on_scroll` | Adds internal offset and posts-per-page controls for infinite scroll. |
| `load_more_batch_size` | Batch size used by infinite scroll. |

Nested loops have a specific rule: if a Facet Target lives inside another Etch loop, outer loop params must be resolved during the original render pass. The current implementation freezes those params through `FacetLoopParamResolver` before the target template is stored, and the E2E `nested_loop_context` scenario verifies that refreshed results keep the correct outer-loop context.

## URL State

Facet state is serialized under the target ID. The E2E URL tests cover direct-link hydration for search, checkbox, radio, select, pagination, map state, ACF relationship, and mapped bundle filters.

Examples:

```text
ome[products][category_name]=shoes
ome[products][meta_value:price]=50
ome[products][tax_query:facet_color]=red
ome[products][acf_relationship:facet_related_posts]=123
ome[products][geo_bbox]=40.5,-74.3,40.9,-73.7
```

The exact URL key depends on the facet clause type and mode-specific options such as `meta_key`, `taxonomy`, or date `column`.

## Pagination, Load More, and Reset

- `Pagination Facet` writes pagination state for a target and receives total pages, total items, current page, per-page, and offset metadata from responses.
- `Load More Facet` writes hidden offset and posts-per-page controls, then appends another batch through the same target request path.
- `load_more_on_scroll` on `Facet Target` uses the same internal offset mechanism without requiring a visible button.
- `Reset Facet` clears all controls connected to the target and returns the target to its unfiltered state.

When any normal facet value changes, pagination resets to page 1. The multi-facet E2E scenario verifies that changing `category_name` after navigating to page 2 resets pagination before showing the narrowed result set.

## Map Pairing

`Map Facet` is a facet control with type `geo_bbox`. It sends the map viewport as a bounding box and reads Map POI elements from the connected target loop.

Use a map-paired setup when:

- the target loop renders listings,
- each listing includes a `Map POI`,
- the map and result target share the same target ID,
- the target sets `used_with_map` when map coordination is needed.

The map runtime can use detailed POI markup for smaller sets and lightweight coordinate responses for large sets.
