---
url: https://nsh.dev/ru/elements/dock
description: "DockArea: закрепляемое рабочее пространство из разделённых областей, групп вкладок и док-панелей у левого, правого и нижнего краёв, раскладка которого является данными, принадлежащими приложению. DockArea отвечает за перетаскивание вкладок и их сброс, изменение размеров, док-панели, масштабирование и блокировку, сохраняет содержимое каждой панели при её перемещении и адаптируется к телефонам; внешний вид и содержимое панелей остаются на стороне приложения."
demo: https://nsh.dev/play/index.html#el-dock
---

# Dock

Область dock — это рабочее пространство, которое пользователь выстраивает сам: панели в группах вкладок, группы, расположенные рядом или друг над другом, и доки на левом, правом и нижнем краях, которые открываются, закрываются и меняют размер. Вкладка, перетащенная на другую группу, присоединяется к ней, а перетащенная к краю группы делит её. Каждая панель сохраняет своё содержимое при перемещении. `DockArea` отвечает за поведение: перетаскивание вкладок и их сброс (они выполняются нативно, кадр за кадром), изменение размера разделений и доков, открытие и закрытие доков, увеличение группы и блокировку. Внешний вид остаётся за приложением: вкладки, панели вкладок, ручки, индикатор сброса и содержимое каждой панели отрисовывает и стилизует приложение, а сама область ничего не рисует.

```rust
use nsh_base::{
    dock::{DockAreaLayout, DockLayout, DockPanel, DockPlacement, DockRegion, DockTab},
    dock_area, theme,
};
use nsh_core::prelude::*;

fn workspace() -> impl IntoElement {
    let layout = signal(
        DockAreaLayout::new(
            DockLayout::h_split()
                .child(DockLayout::tabs().panel("readme").panel("main"), None)
                .child(DockLayout::tabs().panel("preview"), Some(px(320.0))),
        )
        .with_dock(DockPlacement::Left, DockRegion::new(DockLayout::tabs().panel("files")))
        .with_dock(DockPlacement::Bottom, DockRegion::new(DockLayout::tabs().panel("terminal"))),
    );
    dock_area()
        .layout(move || layout.get())
        .on_layout_change(move |next: &DockAreaLayout| layout.set(next.clone()))
        .handle_style(|s| s.bg(move || theme().border()))
        .tab_bar_style(|s| s.flex_row().border_b_1().border_color(move || theme().border()))
        .tab_style(|s| s.px_3().py_1().selected(|s| s.bg(move || theme().muted())))
        .render_tab(|tab: &DockTab| text(tab.panel.to_string()).text_sm().text_color(move || theme().foreground()))
        .render_panel(|panel: &DockPanel| {
            text(format!("{} content", panel.id)).p_4().text_color(move || theme().foreground())
        })
}
```

```tsx
import { mount, signal, Text } from "nsh";
import { DockArea, type DockAreaLayout } from "nsh/base";

const titles: Record<string, string> = {
  files: "Files", readme: "README.md", main: "main.rs", preview: "Preview", terminal: "Terminal",
};

function App() {
  const layout = signal<DockAreaLayout>({
    center: {
      type: "split",
      axis: "horizontal",
      children: [
        { type: "tabs", panels: ["readme", "main"] },
        { type: "tabs", panels: ["preview"] },
      ],
      sizes: [null, 320],
    },
    left: { layout: { type: "tabs", panels: ["files"] }, size: 240 },
    bottom: { layout: { type: "tabs", panels: ["terminal"] }, size: 180 },
  });
  return (
    <DockArea
      class="size-full bg-background"
      layout={layout()}
      onLayoutChange={(e) => layout.set(e.layout)}
      handleClass="bg-border group-active:bg-primary"
      tabBarClass="flex-row border-b border-border"
      tabClass="px-3 py-1.5 selected:bg-muted"
      renderTab={(tab) => <Text class="text-sm text-foreground">{titles[tab.panel]}</Text>}
      renderPanel={(panel) => <Text class="p-4 text-foreground">{titles[panel.id]} content</Text>}
    />
  );
}

mount(App);
```

`DockArea` и принимаемые им значения берутся из [`nsh/base`](https://nsh.dev/translations/ru/modules/nsh-base.md), crate `nsh-base` на Rust (`nsh_base::dock_area()`, значения в `nsh_base::dock`). В Rust его создаёт `dock_area()`: по одному сеттеру на каждое свойство, по одному обработчику на каждое событие и типизированные методы стилей. `DockLayout` описывает раскладку по ключам панелей (`h_split()`, `v_split()`, `tabs()`, `.child(layout, size)`, `.panel(key)`).

<a id="the-layout"></a>

## Раскладка

Раскладка — это значение `DockAreaLayout`: центр и по одному доку на левом, правом и нижнем краях, каждый из которых необязателен. Каждая область — это дерево из узлов двух видов:

| Узел | Содержит | Поля |
|---|---|---|
| `{ type: "split" }` | Другие узлы, расположенные рядом (`axis: "horizontal"`) или друг над другом (`"vertical"`) | `children` и `sizes`: длина каждого дочернего узла вдоль оси в пунктах, `null` — разделить оставшееся место |
| `{ type: "tabs" }` | Панели по ключам, показывается одна за раз | `panels` (ключи в порядке вкладок), `activeIndex` (показываемая панель, по умолчанию 0) |

Панель всегда находится в группе вкладок: область с одной панелью — это группа вкладок из одной панели. Ключ панели — это строка, которую приложение выбирает для неё (`"files"`, id документа), и каждый ключ встречается во всей раскладке только один раз. Док (`DockRegion`) — это его дерево (`layout`), его размер поперёк края (`size`, по умолчанию 200, не менее 100), признак того, открыт ли он (`open`, по умолчанию `true`), и признак того, закрывается ли он, если перетащить его край меньше минимального размера (`collapsible`, по умолчанию `true`).

У каждого узла есть `id`. В раскладке, которую пишет приложение, id можно опустить: область назначит их сама, а каждая сообщаемая ею раскладка содержит их. Если передать сообщённую раскладку обратно, каждая группа сохранит свою идентичность, по которой методы ref обращаются к группам. Область также приводит полученное в порядок: пустая группа или разделение исчезает, разделение из одного дочернего узла заменяется этим узлом, разделение внутри разделения с той же осью сливается с родителем, `activeIndex` остаётся в допустимых пределах, ключ, указанный дважды, сохраняет только первое место. Сообщаемые раскладки всегда имеют такую единую каноническую форму.

Раскладка принадлежит приложению (`layout`): каждое изменение, которое вносит пользователь (выбор, перетаскивание или закрытие вкладки, изменение размера разделения или дока, открытие или закрытие дока), сообщается как вся новая раскладка (`onLayoutChange`), а область показывает раскладку, которую приложение передаёт обратно. Само приложение меняет раскладку, передавая новое значение: восстановленную сохранённую раскладку, команду, которая перестраивает рабочее пространство. Область без `layout` хранит свою раскладку, начиная с `defaultLayout`, и по-прежнему сообщает о каждом изменении.

<a id="panels-content"></a>

## Содержимое панелей

Что показывает панель, решает приложение: `renderPanel` отрисовывает её по ключу панели (`panel.id`). Размещаются все панели группы, а показывается только отображаемая, поэтому при переключении вкладок содержимое каждой панели сохраняется. Когда панель перемещается (в другую группу, в док, в увеличенный режим и обратно), вместе с ней перемещается то же самое содержимое: созданные ею сигналы, текст поля ввода, положение прокрутки, страница веб-представления — всё это остаётся. Область отрисовывает панель один раз, при её первом появлении, и освобождает её содержимое, когда панель покидает раскладку (`onPanelRemove`).

```tsx
<DockArea
  layout={layout()}
  onLayoutChange={(e) => layout.set(e.layout)}
  renderPanel={(panel) => (panel.id === "terminal" ? <Terminal /> : <Editor file={panel.id} />)}
  onPanelActiveChange={({ panel, active }) => active && setTitle(panel)}
  onPanelRemove={({ panel }) => closeFile(panel)}
/>
```

`panels` задаёт, что разрешено каждой панели, по ключу; панель, которой нет в списке, может всё:

| Поле | По умолчанию | Описание |
|---|---|---|
| `id` | | Ключ панели. |
| `closable` | `true` | Её можно закрыть. |
| `zoomable` | `true` | Её группу можно увеличить, пока панель отображается. |
| `visible` | `true` | Она показывается. Скрытая панель сохраняет своё место и возвращается на него; группа или разделение, все панели которых скрыты, не занимают места. |

<a id="tabs"></a>

## Вкладки

У каждой группы есть панель вкладок с вкладкой для каждой видимой панели, в порядке её `panels`. `renderTab` отрисовывает то, что показывает вкладка (заголовок, значок, кнопку закрытия приложения); без него вкладка показывает ключ панели. Область сама делает это вкладкой: нажатие выбирает её, перетаскивание перемещает её панель, программы чтения с экрана воспринимают её как вкладку с состоянием выбора, а классы `selected:` у `tabClass` стилизуют отображаемую вкладку. Значение вкладки содержит всё, что приложению нужно для её отрисовки:

| Поле | Тип | Описание |
|---|---|---|
| `panel` | `string` | Ключ панели. |
| `group` | `number` | Id группы вкладок, в которой она находится. |
| `index` | `number` | Её место среди видимых вкладок группы. |
| `active` | `boolean` | Это отображаемая вкладка группы. |
| `closable` | `boolean` | Область закрыла бы её: она закрываема, область не заблокирована, и её группа может её отдать. |
| `zoomed` | `boolean` | Её группа увеличена. |

```tsx
let area!: DockAreaRef;

<DockArea
  ref={area}
  layout={layout()}
  onLayoutChange={(e) => layout.set(e.layout)}
  renderTab={(tab) => (
    <View class="flex-row items-center gap-2">
      <Text class="text-sm">{titles[tab.panel]}</Text>
      <If when={tab.closable}>
        <Button onPress={() => area.closePanel(tab.panel)} accessibilityLabel="Close" class="rounded-sm px-1 hover:bg-muted">
          <Text class="text-xs">×</Text>
        </Button>
      </If>
    </View>
  )}
  renderPanel={(panel) => <Editor file={panel.id} />}
/>
```

<a id="drag-and-drop"></a>

## Перетаскивание

Вкладка, перетаскиваемая над группой, показывает, куда она попадёт, а сброс помещает её туда:

| Куда сброшена | Что происходит с панелью |
|---|---|
| На панель вкладок | Присоединяется к этой группе между вкладками, рядом с которыми сброшена |
| На середину содержимого группы | Присоединяется к этой группе |
| На край содержимого группы (внешние 35% с каждой стороны) | Делит эту группу: новая отдельная группа с этой стороны |

Пока перетаскивание находится над группой, область показывает индикатор сброса поверх той части группы, которую займёт панель (всё содержимое при присоединении, его половину при делении). Эту часть стилизует приложение (`dropIndicatorClass`), и у неё может быть собственное содержимое, если приложение его отрисовывает (`renderDropIndicator`, получает `group`, `side`, которую она отделяет, или `index` панели вкладок). Индикатор нативно переходит на каждое новое место с плавным смещением, а при переходе на другую группу появляется с нарастанием, пока прежний исчезает (сразу, если пользователь просит уменьшить анимацию). То, что следует за указателем или пальцем, тоже определяет приложение (`renderDragPreview`). Сброс — это одно изменение раскладки, о котором сообщается один раз; сброс, который ничего бы не изменил, ничего не меняет.

Заблокированная область, увеличенная область и последняя панель группы, рядом с которой больше ничего нет, ничего не перетаскивают.

<a id="docks"></a>

## Доки

Док открывается и закрывается без потери панелей и размера: при закрытии он сохраняет своё дерево, а при открытии показывает те же группы того же размера. Граница между доком и центром — это ручка, которую пользователь перетаскивает, чтобы изменить размер дока; размер дока остаётся не менее 100 пунктов, и центр сохраняет не менее 100 пунктов, а сворачиваемый док, отпущенный на размере меньше 50 пунктов, закрывается и сохраняет свой размер до следующего открытия. Область всегда укладывается в свои границы: док показывается со своим `size` в пределах места, которое оставляет ему область, а если не помещается даже это, доки сжимаются вместе с остальным, и минимальные размеры разделений уступают. `renderDock` рисует собственное оформление дока перед его раскладкой (полосу заголовка, кнопку сворачивания), получая его `placement`, `open`, `size` и `collapsible`; `dockClass` стилизует его рамку.

Открывать, закрывать и изменять размер доков можно из приложения через ref (`toggleDock`, `setDockOpen`, `setDockSize`), которые работают в обоих режимах представления, либо записав `open` и `size` дока в передаваемую раскладку (`open` относится к полному представлению). `onDockOpenChange` сообщает каждый раз, когда док показывается или скрывается, в сравнении с `open` раскладки (область, которая изначально компактна, сообщает каждый открытый в раскладке док как скрытый), поэтому переключатель, который стартует с `open` раскладки и следует за `onDockOpenChange`, всегда показывает состояние дока:

```tsx
const terminalOpen = signal(true);

<DockArea
  ref={area}
  layout={layout()}
  onLayoutChange={(e) => layout.set(e.layout)}
  onDockOpenChange={({ placement, open }) => placement === "bottom" && terminalOpen.set(open)}
  renderPanel={(panel) => <Editor file={panel.id} />}
/>

<Button selected={terminalOpen()} onPress={() => area.toggleDock("bottom")} class="px-3 py-1.5 selected:bg-muted">
  <Text>Terminal</Text>
</Button>
```

<a id="zoom"></a>

## Развёртывание

Увеличенная группа заполняет всю область, пока её не вернут к обычному размеру или она не покинет раскладку (`area.toggleZoom(group)`, а `area.toggleZoom()` возвращает обычный размер), а `onZoomChange` сообщает отображаемую ею панель или `null`. Увеличение принадлежит группе, поэтому она остаётся увеличенной, когда её отображаемая панель закрывается и её место занимает следующая. Панель с `zoomable: false` не даёт своей группе увеличиваться, пока отображается.

<a id="locking"></a>

## Блокировка

`locked` сохраняет расположение как есть: никаких перетаскиваний вкладок, сбросов и закрытий. Выбор вкладок, изменение размера, а также открытие и закрытие доков по-прежнему работают, так что заблокированное рабочее пространство остаётся пригодным для работы.

<a id="methods"></a>

## Методы

`ref={area}` предоставляет методы области (`DockAreaRef`) для команд, сочетаний клавиш и меню приложения. О каждом изменении сообщается через `onLayoutChange`, как и об изменениях пользователя. Метод, которому передали панель, группу, индекс вкладки или док, отсутствующие в раскладке, оставляет раскладку без изменений и записывает предупреждение с названием метода и значением.

| Метод | Описание |
|---|---|
| `layout()` | `Promise<DockAreaLayout>`: показываемая раскладка. |
| `addPanel(panel, placement?, size?)` | Добавляет панель в область (`"center"`, по умолчанию, `"left"`, `"right"`, `"bottom"`) в её первую группу вкладок и отображает её; отсутствующий док создаётся, `size` в поперечнике. Панель, уже находящаяся в области, перемещается туда. |
| `removePanel(panel)` | Удаляет панель независимо от её параметров. |
| `closePanel(panel)` | Закрывает панель так, как это сделала бы её вкладка: кроме случаев, когда область заблокирована, панель не закрываема или это последняя панель группы, рядом с которой ничего нет. |
| `movePanel(panel, target)` | Перемещает панель в `target`: `{ group, side?, index?, activate?, size? }`, в группу вкладок `group` (на позицию `index`) или в новую группу на её стороне `side` (`size` пунктов вдоль нового разделения). |
| `splitAt(group, panel, side)` | Помещает панель в новую группу вкладок на одной из сторон (`"left"`, `"right"`, `"top"`, `"bottom"`) контейнера. |
| `selectPanel(panel)` | Отображает панель в её группе. |
| `selectTab(group, index)` | Отображает вкладку группы. |
| `toggleDock(placement)`, `setDockOpen(placement, open)` | Открывает или закрывает док. |
| `setDockSize(placement, size)` | Задаёт размер дока поперёк его края (не менее 100). |
| `toggleZoom(group?)` | Увеличивает группу или возвращает её обычный размер, если она увеличена; без `group` возвращает обычный размер увеличенной группы. |
| `zoomedGroup()` | `Promise<number \| null>`: id увеличенной группы. |
| `groupOf(panel)` | `Promise<number \| null>`: id группы вкладок, в которой находится панель. |

В Rust `component_ref::<DockArea>()` и `.component_ref(&r)` у области дают ref, у `r.api::<DockAreaApi>()` которого те же методы в snake case.

<a id="saving-and-restoring"></a>

## Сохранение и восстановление

Сообщённая раскладка — это обычные данные, поэтому её сохранение — это `JSON.stringify(layout)` в [хранилище](https://nsh.dev/translations/ru/modules/nsh.md#storage), а восстановление — это передача разобранного значения как `layout`. `onLayoutChange` сообщает о каждом изменении, включая изменение размера и выбор вкладки, поэтому записывайте в хранилище, когда изменения улягутся (короткий таймер, сбрасываемый при каждом сообщении).

В Rust `DockAreaLayout::to_state` и `from_state` читают и записывают `DockAreaState`, формат файла раскладки, который использует и dock в `gpui-base`.

<a id="phones-and-narrow-windows"></a>

## Телефоны и узкие окна

Dock подстраивается под собственную ширину. Область шириной 600 пунктов и больше (в окне на десктопе, в вебе, на планшете, на iPad в полноэкранном режиме, на раскрытом складном устройстве) — это полноценное рабочее пространство: разделения, группы и доки расположены рядом, как в многопанельных приложениях для планшетов. При управлении пальцем на телефоне или планшете перетаскивание вкладки начинается с долгого нажатия, как в drag and drop на iPadOS, поэтому панель вкладок по-прежнему прокручивается смахиванием, а ручки получают более широкую область касания при том же внешнем виде.

Область уже 600 пунктов (на телефоне, на iPad в Slide Over, в узком окне десктопа или браузера либо в узкой колонке широкого окна) представляет ту же раскладку для одного экрана, подобно тому как split view на iPad сворачивает свои колонки, а supporting pane в Material Design превращается в нижнюю панель:

- Центр показывает одну группу за раз, в полный размер. Его панель вкладок перечисляет все видимые панели центра, а выбор панели из другой группы делает эту группу показываемой.
- Левый и правый доки — это выдвижные панели поверх центра от своего края шириной с док, а нижний док — это шторка высотой с док. Показывается один за раз, когда его представляет приложение (`toggleDock`, `setDockOpen`, которые скрывают другой); он плавно выезжает и уезжает, а нажатие на его подложку, Escape или «назад» закрывают его. При переходе в компактное представление ни один не показывается.
- Перетаскиваемая вкладка (палец после долгого нажатия, указатель сразу) перемещается внутри панели вкладок, меняя порядок.

Каждая панель остаётся размещённой в обоих представлениях, поэтому при переключении её содержимое сохраняется. Показанная выдвижная панель или шторка принадлежит только компактному представлению: `open` раскладки относится к полному представлению, а показ или закрытие выдвижной панели не вызывает `onLayoutChange`, поэтому область, которая снова расширяется, показывает доки в точности такими, какими они были. `onDockOpenChange` сообщает об обоих случаях: о доке, у которого `open` раскладки меняется в полном представлении, о выдвижной панели или шторке, показанной или закрытой в компактном, и о каждом доке, у которого состояние показа меняется при переключении представления. В `renderDock` значение `open` означает, показывается ли док в текущем представлении. Раскладка при этом остаётся тем же значением, поэтому область, пересекающая отметку 600 пунктов (раскладывающееся складное устройство, iPad, переходящий в Split View, изменение размера окна или колонки), сохраняет каждую панель на месте, а приложение сохраняет одну раскладку. Ширина области определяет представление, как только она размещена (до этого вместо неё используется ширина окна), а `compact` выбирает представление вместо ширины. Выдвижные панели, шторка и подложка — это части, которые стилизует приложение (`drawerClass`, `sheetClass`, `backdropClass`), как и остальную область.

```tsx
<DockArea
  layout={layout()}
  onLayoutChange={(e) => layout.set(e.layout)}
  drawerClass="bg-background border-r border-border"
  sheetClass="rounded-t-(--radius-lg) bg-background"
  backdropClass="bg-black/40"
  renderTab={(tab) => <Text>{titles[tab.panel]}</Text>}
  renderPanel={(panel) => <Editor file={panel.id} />}
/>
```
## Props

| Prop | Rust | Type | Default | Description |
|---|---|---|---|---|
| `layout` | `.layout(..)` | `DockAreaLayout` | нет | Отображаемая раскладка (controlled): передайте обратно то, что сообщает `onLayoutChange`. |
| `defaultLayout` | `.default_layout(..)` | `DockAreaLayout` | пустой центр | Начальная раскладка, когда `layout` не задан; в дальнейшем область хранит собственную. |
| `locked` | `.locked(..)` | `boolean` | `false` | Вкладки нельзя перетаскивать, сбрасывать или закрывать. |
| `compact` | `.compact(..)` | `boolean` | по ширине | Представление для телефона (`true`) или полное (`false`); если не задано, компактное, пока область уже 600 пунктов. |
| `panels` | `.panels(..)` | `DockPanelOptions[]` | `[]` | Что разрешено каждой панели, по ключу: `closable`, `zoomable`, `visible`. |
| `renderPanel` | `.render_panel(\|panel\| ..)` | `(panel: DockPanel) => JSX.Element` | нет | Содержимое панели по её ключу (`panel.id`). |
| `renderTab` | `.render_tab(\|tab\| ..)` | `(tab: DockTab) => JSX.Element` | ключ | Что показывает вкладка. |
| `renderDropIndicator` | `.render_drop_indicator(\|drop\| ..)` | `(drop: DockDropIndicator) => JSX.Element` | нет | Что находится внутри индикатора сброса: `group`, `side`, `index`. |
| `renderDock` | `.render_dock(\|dock\| ..)` | `(dock: DockInfo) => JSX.Element` | нет | Собственное оформление дока, перед его раскладкой: `placement`, `open` (показывается в текущем представлении), `size`, `collapsible`. |
| `renderEmpty` | `.render_empty(\|placement\| ..)` | `(placement: DockPlacement) => JSX.Element` | нет | Что показывает пустой центр. |
| `renderDragPreview` | `.render_drag_preview(\|tab\| ..)` | `(tab: DockTab) => JSX.Element` | нет | Что следует за указателем или пальцем при перетаскивании вкладки. |
| `groupClass`, `groupStyle` | `.group_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Рамка каждой группы вкладок. |
| `tabBarClass`, `tabBarStyle` | `.tab_bar_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Каждая панель вкладок (`flex-row` для строки). |
| `tabClass`, `tabStyle` | `.tab_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Каждая вкладка; `selected:` для отображаемой. |
| `contentClass`, `contentStyle` | `.content_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Рамка содержимого каждой группы, где показываются её панели. |
| `dropIndicatorClass`, `dropIndicatorStyle` | `.drop_indicator_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Индикатор сброса. |
| `handleClass`, `handleStyle` | `.handle_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Индикатор каждого разделителя, между группами разделения и у края каждого дока; `group-hover:` и `group-active:` следуют за разделителем. Он имеет ширину 1 пункт поперёк разделителя и тянется на всю его длину, если не указано иное, поэтому `bg-border` в любом случае даёт тонкую линию; `w-[3px]` / `h-[3px]` утолщают его. |
| `handleThumbClass`, `handleThumbStyle` | `.handle_thumb_class(..)`, `.handle_thumb_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Ползунок каждого разделителя: рукоятка по центру линии, нарисованная вертикально, как на вертикальной линии, и повёрнутая на четверть оборота на горизонтальной, так что один класс подходит для обеих осей. Он остаётся тем же представлением, пока разделитель находится под курсором, нажат и перетаскивается, поэтому классы `group-hover:` и `group-active:` задают его переходы (`h-11 w-[3px] rounded-full bg-muted-foreground opacity-0 scale-y-0 transition group-hover:opacity-40 group-hover:scale-y-[0.45] group-active:opacity-90 group-active:scale-y-100`). |
| `dockClass`, `dockStyle` | `.dock_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Рамка каждого дока. |
| `drawerClass`, `drawerStyle` | `.drawer_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Компактное представление: выдвижные панели боковых доков. |
| `sheetClass`, `sheetStyle` | `.sheet_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Компактное представление: нижняя панель (sheet) нижнего дока. |
| `backdropClass`, `backdropStyle` | `.backdrop_style(\|s\| ..)` | `string`, `StyleProp<ViewStyle>` | нет | Компактное представление: подложка за открытой выдвижной или нижней панелью; нажатие на неё закрывает панель. |
| `handleAccessibilityLabel` | `.handle_accessibility_label(..)` | `string` | нет | Что говорят программы чтения с экрана для каждого разделителя, между группами разделения и у края каждого дока. |
| `ref` | `.component_ref(&r)` (`component_ref::<DockArea>()`) | `DockAreaRef` | | [Методы](#methods) области. |
| `class`, `style` | style methods | `string`, `StyleProp<ViewStyle>` | нет | Сама область: её размер в родителе (`flex-1`), её фон. Она остаётся в своих границах и не берёт минимальный размер от содержимого. |

## Events

| Event | Rust | Description |
|---|---|---|
| `onLayoutChange` | `.on_layout_change(\|layout: &DockAreaLayout\| ..)` | Раскладка изменилась (перетаскивание, выбор, изменение размера, открытие или закрытие дока, вызов метода): `{ layout }`, вся новая раскладка со всеми идентификаторами. |
| `onPanelActiveChange` | `.on_panel_active_change(\|e: &DockAreaEvent\| ..)` (`PanelActiveChange(panel, active)`) | `{ panel, active }`: панель стала или перестала быть отображаемой вкладкой своей группы (закрытый док не отображает ни одной). Один раз за изменение; панель, которая уходит, не получает `false`: `onPanelRemove` — её конец. |
| `onPanelRemove` | `.on_panel_remove(\|panel: &PanelId\| ..)` | `{ panel }`: панель окончательно покинула область: закрыта, удалена или отсутствует в новой раскладке. Панель, которая перемещается, этого никогда не получает. |
| `onZoomChange` | `.on_zoom_change(\|panel: &Option<PanelId>\| ..)` | Группа была развёрнута или свёрнута: `{ panel }`, отображаемая ею панель или `null`. |
| `onDockOpenChange` | `.on_dock_open_change(\|e: &DockAreaEvent\| ..)` (`DockOpenChange(placement, open)`) | `{ placement, open }`: док был показан или скрыт: изменился его layout `open` в полном представлении, его выдвижная или нижняя панель была показана или закрыта в компактном, либо представление переключилось. |

## Parts

| Part | Inside | Styled with | States |
|---|---|---|---|
| Область | | `class`, `style` | |
| Группа вкладок | регион | `groupClass`, `groupStyle` | |
| Панель вкладок | группа | `tabBarClass`, `tabBarStyle` | |
| Вкладка | панель вкладок | `tabClass`, `tabStyle`; `renderTab` | `selected:`, `active:`, `hover:` |
| Содержимое группы | группа | `contentClass`, `contentStyle`; `renderPanel` | |
| Индикатор разделителя | между группами разделения, у края дока | `handleClass`, `handleStyle` | `group-hover:`, `group-active:` |
| Ползунок разделителя | по центру линии разделителя | `handleThumbClass`, `handleThumbStyle` | `group-hover:`, `group-active:` (нажат до конца перетаскивания) |
| Док | край | `dockClass`, `dockStyle`; `renderDock` | |
| Индикатор сброса | над группой во время перетаскивания | `dropIndicatorClass`, `dropIndicatorStyle`; `renderDropIndicator` | |
| Превью перетаскивания | под указателем или пальцем | `renderDragPreview` | |
| Пустой центр | центр | `renderEmpty` | |
| Выдвижная панель, нижняя панель, подложка | над центром, компактное представление | `drawerClass`, `sheetClass`, `backdropClass` (и их стили) | |

## Accessibility

- Каждая вкладка для программ чтения с экрана является вкладкой с состоянием «выбрана», как и [`Tab`](https://nsh.dev/translations/ru/elements/tabs.md), а её активация выбирает
  её. Её подпись — это текст, который рисует `renderTab`.
- Каждый разделитель, между группами разделения и у края каждого дока, является регулируемым элементом с подписью
  `handleAccessibilityLabel`. Разделители между группами разделения принадлежат [Resizable](https://nsh.dev/translations/ru/elements/resizable.md): программа чтения с экрана
  сдвигает их по 10 пунктов за раз, сообщая об этом через `onLayoutChange`.
- В компактном представлении Escape и «назад» закрывают показанную выдвижную или нижнюю панель.

Общие сведения о подписях, ролях и объявлениях см. в разделе [Accessibility](https://nsh.dev/translations/ru/docs/accessibility.md).

## Platform notes

- Указатель (мышь, трекпад, перо, зависшее над экраном) начинает перетаскивание вкладки, как только сдвигается на 4 пункта
  с нажатой кнопкой, на любой платформе: iPad с трекпадом и узкое окно браузера, управляемое перетаскиванием мышью, ведут себя так же,
  как настольный компьютер. В macOS, Windows, Linux и GPUI каждое нажатие считается нажатием указателя, включая палец на сенсорном экране
  ноутбука.
- Палец в iOS, iPadOS и Android перетаскивает вкладку после долгого нажатия, как при drag and drop в iPadOS, поэтому смахивание по
  панели вкладок прокручивает её.
- Настольные платформы и веб: разделители показывают курсор изменения размера своей оси.
- Телефоны и узкие области: компактное представление с выдвижными панелями и нижней панелью (bottom sheet).
- Каждая платформа отрисовывает то, что стилизует приложение: вкладки, панели вкладок, разделители, индикатор сброса, доки, выдвижные панели и нижнюю панель.
