Dockable.DropZone
A place, inside or outside the layout, where a drag of the layout can be dropped for you to handle with a command.
"A place, inside or outside the layout, where a drag of the layout can be dropped for you to
handle: a trash can that closes the tab, an "open to the right" pad, a region of the page. While a
drag it takes is over it, the layout shows no outline, and a drop calls onDrop with what is
dragged instead of moving it."
<Dockable.DropZone
model={model}
accepts={(drag) => drag.kind === "tab" && model.can("tab.close", { tabId: drag.tab.id })}
onDrop={(drag) => {
if (drag.kind === "tab") model.run("tab.close", { tabId: drag.tab.id });
}}
className="opacity-50 data-drop-active:opacity-100 data-drop-over:bg-red-600"
>
Trash
</Dockable.DropZone>Nothing moves by itself: onDrop receives what is dragged, and the zone runs the command it
stands for. model.run goes through the model's middleware, like every command of the layout.
What is dragged
accepts and onDrop receive a DragSubject<T>, a union discriminated by kind:
kind | fields | what is dragged |
|---|---|---|
"tab" | tab: TabOf<T> | a tab of the layout |
"tabset" | tabset: TabsetNode<T> | a whole tabset of the layout |
"new" | tab: TabInitOf<T> | a new tab that is not in the model yet: a Dockable.DragSource, or a foreign drag accepted by onExternalDrag |
T is inferred from model: Model<T>, so drag.tab.data narrows on drag.tab.component.
Props
DropZoneProps<T> extends the common primitive props (its onDrop
replaces the element's native drop handler).
| prop | type | default | description |
|---|---|---|---|
model | Model<T> | required | the model whose drags the zone takes |
accepts | (drag: DragSubject<T>) => boolean | every drag of the model | whether the zone takes this drag |
onDrop | (drag: DragSubject<T>, event: DragEventLike) => void | required | called when the drag is dropped on the zone, with what is dragged ({ kind: "tab", tab }, { kind: "tabset", tabset }, { kind: "new", tab }). Nothing is moved: run the command you want (tab.close) |
children | ReactNode | none | the zone's content; the primitive renders no text of its own |
State
DropZoneState:
| field | type | description |
|---|---|---|
over | boolean | a drag the zone takes is over it |
active | boolean | a drag the zone would take is in progress |
Data attributes
| attribute | value |
|---|---|
data-layout-path | none: a drop zone is not part of a layout, so it has no layout path (select it by your own data-testid or role) |
data-drop-active | a drag the zone would take is in progress (show the zone, arm it) |
data-drop-over | that drag is over the zone |
Notes
- A zone can sit anywhere in the page's document: beside the layout, above it, or inside
Dockable.Rootas an overlay. It does not work across windows (a zone in the main document does not take a drag inside a popout). - A zone for a new-tab drag can create the tab itself:
model.run("tab.add", { ...drag.tab, to }). useDropZone({ model, accepts, onDrop })is the lower layer (hooks), and the core'sDragDropManager.registerDropZone(model, element, options)(DropZoneOptions<T>:accepts,onDrop,onOverChange) the one under it, on the adapter's side, for other frameworks.