# Resize Handle

## Usage

`ResizeHandle` is the separator between two regions. `useResizable` owns the size. Pass `sidebar.props` to both the handle and the panel.

```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` is `horizontal` (width) or `vertical` (height). `position` is `inline` or `overlay`. `isReversed` flips the drag when the panel sits on the end edge. `hasDivider` draws the 1px line; the line is the handle. `isAlwaysVisible` keeps the grip visible at rest. `pillPlacement` is `auto`, `start`, `end`, or `center`.

The handle is `role="separator"`. A horizontal handle sets `aria-orientation="vertical"`. Arrow keys move 10px, and Shift moves 50px. With snap points, an arrow jumps to the next point and Shift jumps two. Home and End jump to the limits. End does nothing when no maximum is set; the separator then reports the window or container size as its maximum. While collapsed, only the arrow that grows the pane reopens it. Enter and double-click restore the size from before the collapse.

`useResizable` accepts one region, or a `regions` map. `snaps` keeps the size on the nearest listed point. An equal distance stays on the earlier point. A drag below `collapsedSize` (default 40) collapses a collapsible region to 0. `autoSaveId` stores the size under `astryx-resizable:` in `localStorage`. `percent()` builds a percentage size with a pixel minimum or maximum.

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Give the handle a `label` that names the region it resizes. |
| Do | Turn the panel divider off when the handle draws one. |
| Don't | Store the size in a separate ref and forget to pass `resizable`. The handle and the panel both read `props`. |

## Examples

### Collapsible with snap points

Dragging snaps to preset widths. Dragging past the minimum collapses the sidebar, and Expand restores it.

```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

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | `horizontal` changes width. `vertical` changes height. A horizontal handle sets `aria-orientation="vertical"`. |
| `position` | `'inline' \| 'overlay'` | `'inline'` | `inline` takes a track. `overlay` sits on the parent edge. |
| `isReversed` | `boolean` | `false` | Flips the drag when the panel is on the end edge. |
| `isDisabled` | `boolean` | `false` | Turns pointer and keyboard resizing off. |
| `hasDivider` | `boolean` | `false` | Draws the 1px line. The line is the handle. |
| `isAlwaysVisible` | `boolean` | `true` | Keeps the grip visible at rest. |
| `pillPlacement` | `'auto' \| 'start' \| 'end' \| 'center'` | `'auto'` | Where the grip sits on the line. `auto` stays on the panel side, and flips when the panel size is 0. |
| `label` | `string` | locale string | Accessible name. The English default is “Resize handle”. |
| `resizable` | `ResizableProps` | - | The `props` object returned by `useResizable`. |

## Slots

| Name | Description |
| --- | --- |
| `default` | Replaces the grip. The separator role and keyboard behavior stay on the handle. |

## useResizable

Import it from <code>@astryx-vue/core</code>. A single region returns <code>{ size, isCollapsed, collapse, expand, resize, props }</code>. A <code>regions</code> map returns one of those objects per key.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultSize` | `number \| string \| ResizablePercentSize` | `250` | Starting size. A number is pixels. With snap points, the starting value moves to the nearest point. |
| `minSize` | `number \| string \| ResizablePercentSize` | `50` | Smallest expanded size. |
| `maxSize` | `number \| string \| ResizablePercentSize` | `Infinity` | Largest size. |
| `collapsible` | `boolean` | `false` | Allows the region to collapse to 0. |
| `collapsedSize` | `number` | `40` | A drag below this pixel value collapses the region. |
| `snaps` | `number[]` | - | The size stops only on these pixel points. An equal distance stays on the earlier point. |
| `shrinkOrder` | `number` | - | Cascade priority for a `regions` map that shares `containerRef`. A lower number shrinks to `minSize` first when the container is narrower than the regions. Dragging still sets that region’s preferred size. |
| `direction` | `'horizontal' \| 'vertical'` | `'horizontal'` | Which container axis the percentage sizes read. |
| `containerRef` | `Ref` | - | Element used to resolve percentages. An unmeasured container uses 1200 and does not persist. |
| `autoSaveId` | `string` | - | Stores `{ size, isCollapsed }` at `astryx-resizable:` plus this id. A regions map appends `:key`. |
| `defaultIsCollapsed` | `boolean` | `false` | Starts collapsed when `collapsible` is true. |
| `isCollapsed` | `boolean` | - | Controlled collapsed state. Changes are reported through `onCollapseChange`. |
| `onSizeChange` | `(size: number) => void` | - | Called after a resize settles on a size. |
| `onCollapseChange` | `(isCollapsed: boolean) => void` | - | Called when the collapsed state changes. |
| `regions` | `Record<string, ResizableRegionConfig>` | - | Several regions. Each entry accepts the size fields above. |

<code>percent(value, { min })</code> or <code>percent(value, { max })</code> builds a percentage size. <code>min</code> and <code>max</code> use <code>{ type: 'pixel', value }</code>.

Arrow keys move 10px. Shift moves 50px. With snap points, an arrow jumps to the next point and Shift jumps two. Home, End, and Enter jump to the minimum, a configured maximum, and the size from before collapse. End does nothing when no maximum is set.

## Native attributes

`ResizeHandle` renders a `<div role="separator">`.

The following go to that element:

- HTML attributes.
- `aria-*` attributes.
- `data-*` attributes.
- `class`.
- `style`.

`class` is merged.
