# 拖拽分隔条

## 使用方法

`ResizeHandle` 是两个区域之间的分隔条。尺寸由 `useResizable` 管理。把 `sidebar.props` 同时传给分隔条和面板。

```vue
<script setup lang="ts">
import { useResizable } from '@astryx-vue/core'
import { Layout, LayoutPanel } from '@astryx-vue/core/Layout'
import { ResizeHandle } from '@astryx-vue/core/Resizable'

const sidebar = useResizable({
  defaultSize: 240,
  minSize: 160,
  maxSize: 360,
})
</script>

<template>
  <Layout>
    <template #start>
      <LayoutPanel :has-divider="false" :width="sidebar.size" />
      <ResizeHandle direction="horizontal" has-divider :resizable="sidebar.props" />
    </template>
  </Layout>
</template>
```

`direction` 取 `horizontal`（改宽度）或 `vertical`（改高度）。`position` 取 `inline` 或 `overlay`。面板在结束边时用 `isReversed` 把拖拽方向反过来。`hasDivider` 画出 1px 的线，线本身就是把手。`isAlwaysVisible` 让握把在静止时也可见。`pillPlacement` 取 `auto`、`start`、`end` 或 `center`。

分隔条的角色是 `separator`。横向分隔条的 `aria-orientation` 是 `vertical`。方向键每次移动 10px，按住 Shift 移动 50px。有吸附点时，方向键改成跳到相邻的点，Shift 跳两个。Home 和 End 跳到边界；没写最大尺寸时 End 不动，读屏用的最大值改报窗口或容器尺寸。折叠后只有让面板变大的方向键会展开。Enter 和双击恢复折叠前的尺寸。

`useResizable` 可以管一个区域，也可以用 `regions` 一次管多个。`snaps` 让尺寸只停在列出的点上。离两个点一样远时，留在更靠前的那个。可收起的区域被拖到低于 `collapsedSize`（默认 40）时，尺寸变成 0。`autoSaveId` 把尺寸存在 `localStorage`，键名以 `astryx-resizable:` 开头。`percent()` 用来写带像素上下限的百分比尺寸。

## 最佳实践

| 指引 | 实践 |
| --- | --- |
| 推荐 | 用 `label` 说明这条分隔条在调整哪一块。 |
| 推荐 | 分隔条自己画线时，关掉面板上的分隔线。 |
| 避免 | 另存一份尺寸却不传 `resizable`。分隔条和面板都读 `props`。 |

## 示例

### 吸附并收起

拖动会吸附到预设宽度。拖过最小宽度后侧栏收起，点 Expand 再把它展开。

```vue
<script setup lang="ts">
import { useResizable } from '@astryx-vue/core'
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Layout, LayoutContent, LayoutPanel } from '@astryx-vue/core/Layout'
import { ResizeHandle } from '@astryx-vue/core/Resizable'
import { VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'

const sidebar = useResizable({
  defaultSize: 240,
  minSize: 160,
  maxSize: 360,
  collapsible: true,
  snaps: [200, 280],
})
</script>

<template>
  <Card :height="240" variant="muted" :width="500">
    <Layout height="fill">
      <template #start>
        <LayoutPanel :has-divider="false" :width="sidebar.size">
          <Text v-if="!sidebar.isCollapsed" color="secondary">
            {{ Math.round(sidebar.size) }}px wide
          </Text>
        </LayoutPanel>
        <ResizeHandle
          direction="horizontal"
          has-divider
          label="Resize sidebar"
          :resizable="sidebar.props"
        />
      </template>
      <LayoutContent>
        <VStack :gap="2">
          <Text color="secondary">
            Drag the handle — it snaps at 200px and 280px. Drag all the way left to collapse the sidebar.
          </Text>
          <Button
            v-if="sidebar.isCollapsed"
            label="Expand sidebar"
            size="sm"
            variant="secondary"
            @click="sidebar.expand()"
          />
        </VStack>
      </LayoutContent>
    </Layout>
  </Card>
</template>
```

## Resize Handle Props

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | `horizontal` 改宽度，`vertical` 改高度。横向分隔条的 `aria-orientation` 是 `vertical`。 |
| `position` | `'inline' \| 'overlay'` | `'inline'` | `inline` 占一格，`overlay` 贴在父级边缘。 |
| `isReversed` | `boolean` | `false` | 面板在结束边时，把拖拽方向反过来。 |
| `isDisabled` | `boolean` | `false` | 关掉指针和键盘调整。 |
| `hasDivider` | `boolean` | `false` | 画出 1px 分隔线。线本身就是把手。 |
| `isAlwaysVisible` | `boolean` | `true` | 静止时也显示握把。 |
| `pillPlacement` | `'auto' \| 'start' \| 'end' \| 'center'` | `'auto'` | 握把相对分隔线的位置。`auto` 靠面板一侧，面板收到 0 时翻到另一侧。 |
| `label` | `string` | 当前语言 | 无障碍名称。英文默认是 “Resize handle”，中文默认是 “拖拽分隔条”。 |
| `resizable` | `ResizableProps` | - | `useResizable` 返回的 `props`。 |

## 插槽

| 名称 | 说明 |
| --- | --- |
| `default` | 替换握把。分隔条角色和键盘行为仍留在把手上。 |

## useResizable

从 <code>@astryx-vue/core</code> 引入。单个区域返回 <code>{ size, isCollapsed, collapse, expand, resize, props }</code>。传入 <code>regions</code> 时，每个键各返回一份。

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `defaultSize` | `number \| string \| ResizablePercentSize` | `250` | 初始尺寸。数字是像素。有吸附点时，初值会落到最近的点上。 |
| `minSize` | `number \| string \| ResizablePercentSize` | `50` | 展开时的最小尺寸。 |
| `maxSize` | `number \| string \| ResizablePercentSize` | `Infinity` | 最大尺寸。 |
| `collapsible` | `boolean` | `false` | 允许收到 0。 |
| `collapsedSize` | `number` | `40` | 拖拽低于这个像素值时收起。 |
| `snaps` | `number[]` | - | 尺寸只会停在这些像素点上。离两个点一样远时，留在更靠前的那个。 |
| `shrinkOrder` | `number` | - | 多个区域共用 `containerRef` 时的收缩顺序。容器比这些区域更窄时，数字更小的先收到 `minSize`。拖拽仍然改的是该区域自己的目标尺寸。 |
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | 百分比尺寸读取容器的哪一条轴。 |
| `containerRef` | `Ref` | - | 用来解析百分比的元素。还没量到时按 1200 计算，并且不写入存储。 |
| `autoSaveId` | `string` | - | 把 `{ size, isCollapsed }` 存在 `astryx-resizable:` 加这个 id。多个区域会再接 `:key`。 |
| `defaultIsCollapsed` | `boolean` | `false` | `collapsible` 为 true 时，一开始就收起。 |
| `isCollapsed` | `boolean` | - | 受控的收起状态。变化通过 `onCollapseChange` 报出。 |
| `onSizeChange` | `(size: number) => void` | - | 尺寸落下之后调用。 |
| `onCollapseChange` | `(isCollapsed: boolean) => void` | - | 收起状态变化时调用。 |
| `regions` | `Record<string, ResizableRegionConfig>` | - | 一次管多个区域。每一项接受上面的尺寸字段。 |

<code>percent(value, { min })</code> 或 <code>percent(value, { max })</code> 用来写百分比尺寸。<code>min</code> 和 <code>max</code> 的形状是 <code>{ type: 'pixel', value }</code>。

方向键移动 10px，按住 Shift 移动 50px。有吸附点时改为跳到相邻吸附点，Shift 跳两个。Home、End、Enter 分别跳到最小值、配置过的最大值和折叠前的尺寸。没写最大值时 End 不动。

## 原生属性

`ResizeHandle` 渲染 `<div role="separator">`。

以下内容落到这个元素上：

- HTML 属性。
- `aria-*` 属性。
- `data-*` 属性。
- `class`。
- `style`。

`class` 会合并。
