# useIntersectionObserver

[Back to Composables README](https://github.com/NantHealth/featherk/blob/integration/packages/composables/README.md)

Observes a reactive DOM target against a lazily resolved scroll container. It is useful for grid cells, virtualized rows, and other elements that must react after they leave their visible viewport.

## Grid Containers

When observing an element rendered in a Kendo Grid, explicitly provide the grid's scrolling content element through `root`. The composable cannot infer the correct viewport from the Grid wrapper because a grid may be nested in additional scrolling layouts.

```ts
root: () => cellRef.value?.closest(".k-grid-content") ?? null,
```

Do not use the outer Grid component element unless it is the element that actually scrolls. For standard Kendo Grid layouts, use `.k-grid-content`.

## Native Tables

The composable works the same way with a regular HTML table. Place the table in a scrolling wrapper and use that wrapper as `root`; the `<tr>` is the observed target.

```vue
<template>
  <div ref="tableViewportRef" class="table-viewport">
    <table>
      <tbody>
        <tr ref="rowRef">
          <td>Product row</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useIntersectionObserver } from "@featherk/composables/observer";

// Step 1: bind refs to the native scroll wrapper and target table row.
const tableViewportRef = ref<HTMLElement | null>(null);
const rowRef = ref<HTMLTableRowElement | null>(null);

// Step 2: use the scrolling wrapper as root and react in consumer state.
useIntersectionObserver({
  target: rowRef,
  root: () => tableViewportRef.value,
  threshold: 0,
  onChange: (entry) => {
    if (!entry.isIntersecting) closeMenu();
  },
});
</script>

<style>
.table-viewport {
  height: 400px;
  overflow: auto;
}
</style>
```

## Quick Start

1. Create a ref for the element to observe.
2. Pass a lazy `root` resolver for its clipping container.
3. Handle intersection changes in the consuming component; the composable does not own menu or application state.

```ts
import { ref } from "vue";
import { useIntersectionObserver } from "@featherk/composables/observer";

// Step 1: bind this ref to the target element.
const cellRef = ref<Element | null>(null);

// Step 2: resolve the scroll root only after the target is mounted.
// Step 3: keep application-specific visibility behavior in the consumer.
useIntersectionObserver({
  target: cellRef,
  root: () => cellRef.value?.closest(".k-grid-content") ?? null,
  threshold: 0,
  onChange: (entry) => {
    if (!entry.isIntersecting) closeMenu();
  },
});
```

## Threshold

`threshold` accepts one number or an array of numbers in the inclusive interval $[0, 1]$. Values outside that range are invalid according to the browser `IntersectionObserver` API. It defaults to `1`.

- `0`: callback runs when the target enters the root and again when it fully leaves. Use this to dismiss a popup only after its trigger has fully scrolled out of view.
- `0.5`: callback runs as the visible portion crosses $50\%$.
- `1`: callback runs when the target becomes fully visible or stops being fully visible. This is the default.
- `[0, 0.5, 1]`: callback runs when the target crosses any listed visibility boundary.

`entry.isIntersecting` becomes `false` only after the target leaves the root entirely, regardless of the configured threshold. The composable disconnects automatically on component unmount and re-observes when `target` changes.