# useScrollableArea

## 使用方法

给调用方自己搭的结构加上一套标准的、跟着书写方向走的滚动行为。一个轴要真的能用，得同时满足两件事：这一轴算出来的 `overflow` 是能滚的，并且内容和盒子差得超过共用的 1px 容差。视口和内容盒两块都在观察范围里。

把 `viewport` 当函数 ref 绑到滚动的那块上，把 `content` 绑到装内容的盒子上。`viewportAttrs` 里是 `role`、`aria-label`、`tabindex`，还有按当前测量结果写出的 `data-scroll-*`；`viewportStyle` 里是 `overflow` 和 `overscroll-behavior`。名字和角色用 `label`、`role` 给，或者用 `keyboardAccess` 说清键盘从哪里进来。量出来的状态会登记成「滚动归属」，嵌套的滚动区域和焦点委托靠它分清哪一层在滚。视口藏起来、尺寸为 0 时量不出来，保留上一次的结果。选项是按响应式读取的，所以组件的 `props` 也可以直接传进来，后面变了也会跟着更新。

```ts
import { useScrollableArea } from '@astryx-vue/core'
```

```vue
<script setup lang="ts">
import { useScrollableArea } from '@astryx-vue/core'

const { content, viewport, viewportAttrs, viewportStyle } = useScrollableArea({
  axis: 'block',
  label: '更新日志',
})
</script>

<template>
  <div :ref="viewport" v-bind="viewportAttrs" :style="viewportStyle">
    <div :ref="content">
      很长很长的内容
    </div>
  </div>
</template>
```

## 建议

| 指引 | 做法 |
| --- | --- |
| Do | `viewport` 和 `content` 两个都绑上。内容盒是真的在观察的，溢出变化才跟得上。 |
| Do | 给这块区域一个简短的 `label`。视口只有在真的能滚的时候才进键盘顺序。 |
| Do | `axis` 写你真正想滚的方向，只有两个方向都要滚时才用 `both`。 |
| Do | `overscroll` 保持 `allow`，除非手势滑到头就该停在这个盒子上。 |
| Don't | 自己在视口上写 `overflow`。`viewportStyle` 会按测量结果在 `clip` 和 `auto` 之间挑。 |
| Don't | 再套一层 overflow 容器。一块视口应该独占滚动。 |

## 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `options` | `UseScrollableAreaOptions` | — | 想滚哪个方向、越界手势怎么处理、Sticky 包含关系怎么给。 |
| `options.axis` | `'inline' \| 'block' \| 'both'` | `'block'` | 哪个逻辑轴可以滚。 |
| `options.keyboardAccess` | `ScrollKeyboardAccess` | `{ owner: 'viewport', label, role }` | 能滚动时键盘从哪里进来。`{ owner: 'content' }`：内容自己有可聚焦的东西，视口不进 Tab 顺序、不带名字。`{ owner: 'viewport', label, role? }`：视口能滚时成为 Tab 停靠点。`{ owner: 'contentOrViewport', label, role? }`：同上，但 Tab 进来时如果内容里第一个就是普通按钮或链接，焦点直接交给它，方向键滚动仍归浏览器。 |
| `options.label` | `string` | — | 视口的无障碍名称。没传 `keyboardAccess` 时用。 |
| `options.role` | `'group' \| 'region'` | `'group'` | 视口的角色。 |
| `options.overscroll` | `'allow' \| 'contain'` | `'allow'` | 对真的能滚的轴，越界手势怎么处理。 |
| `options.stickyContainment` | `'whenScrollable' \| 'always'` | `'whenScrollable'` | 视口是只在能滚的时候当 Sticky 包含块，还是一直当。 |

## 返回值

| 名称 | 类型 | 说明 |
| --- | --- | --- |
| `viewport` | `(element: unknown) => void` | 视口元素的函数 ref。滚动监听和 ResizeObserver 都在这里挂上。 |
| `content` | `Ref` | 内容盒的模板 ref。它的尺寸变化也在观察范围里。 |
| `viewportAttrs` | `ComputedRef<Record<string, string \| number \| undefined>>` | 按当前测量结果算出的 `role`、`aria-label`、`tabindex` 和 `data-scroll-*`。 |
| `viewportStyle` | `ComputedRef` | 两个轴的 `overflow`，以及所请求轴上的 `overscroll-behavior`。 |
| `state` | `ComputedRef` | `inline` 和 `block` 两个轴，每个都有 `isScrollable`、`atStart`、`atEnd`。 |
