> For the complete documentation index, see [llms.txt](https://docs.acpt.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.acpt.io/canvas-ui/canvas-ui-reference.md).

# Canvas UI reference

Technical reference for the CanvasLayout component and its APIs.

This page is intended for developers who need to understand or extend the Canvas UI component.

## Component location

```
assets/src/App/components/CanvasLayout/
├── index.jsx            # Main CanvasLayout component
├── CanvasUIPreferences.jsx  # Toolbar preference toggles
└── CanvasPreviewItem.jsx    # Drag-overlay preview element
```

## CanvasLayout props

| Prop                 | Type      | Required | Description                                                                                                                                                                                                  |
| -------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `sensors`            | object    | Yes      | `@dnd-kit` sensor configuration (pointer + keyboard).                                                                                                                                                        |
| `onDragEnd`          | function  | Yes      | Called with the drag result when an item is dropped.                                                                                                                                                         |
| `collisionDetection` | function  | No       | `@dnd-kit` collision-detection strategy used to determine valid drop targets. Defaults to `rectIntersection`; `pointerWithin` is also available for contexts that need more precise pointer-based targeting. |
| `sidebar`            | ReactNode | Yes      | Content rendered in the sidebar panel (left by default; see [Sidebar position](#sidebar-position)).                                                                                                          |
| `canvas`             | ReactNode | Yes      | Content rendered in the canvas panel.                                                                                                                                                                        |
| `dragOverlay`        | ReactNode | Yes      | Element rendered as the drag ghost.                                                                                                                                                                          |
| `id`                 | string    | No       | Unique identifier for this canvas instance.                                                                                                                                                                  |
| `preferenceId`       | string    | No       | Raw entity ID used as the `localStorage` key for layout preferences. Defaults to `id`.                                                                                                                       |
| `wrapperRef`         | ref       | No       | Ref attached to the outer wrapper element.                                                                                                                                                                   |
| `toolbarRef`         | ref       | No       | Ref attached to the toolbar element.                                                                                                                                                                         |
| `extraOptions`       | array     | No       | Additional toolbar toggle options (see below).                                                                                                                                                               |
| `sidebarDefaultSize` | number    | No       | Default sidebar width as a percentage (default: 450px-equivalent).                                                                                                                                           |
| `sidebarMinSize`     | number    | No       | Minimum sidebar width in pixels (default: 400).                                                                                                                                                              |
| `sidebarMaxSize`     | number    | No       | Maximum sidebar width in pixels (default: 700).                                                                                                                                                              |

### `extraOptions` shape

```js
[
  {
    id: 'simple_mode',           // unique key
    label: 'Simple mode',        // toolbar label
    preferenceKey: 'simple_mode', // localStorage key suffix
    defaultValue: false,
    onToggle: (isEnabled) => {   // optional Redux sync
      dispatch(setSimpleMode(isEnabled));
    },
  }
]
```

The **No-distraction** toggle is always present and does not need to be included in `extraOptions`.

## useUIPreference hook

Each toggle in the toolbar is backed by the `useUIPreference` hook, which reads and writes to `localStorage`:

```js
import { useUIPreference } from '../../hooks/useUIPreference';

const [isSimpleMode, setSimpleMode] = useUIPreference(
  `${preferenceId}_simple_mode`, // storage key
  false                           // default value
);
```

The hook returns `[value, setter]` — identical signature to `useState`.

## Drag-and-drop internals

`CanvasLayout` wraps its children in a `@dnd-kit` `DndContext`. Each draggable item must use `useSortable` from `@dnd-kit/sortable`:

```js
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';

const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: item.id });

const style = {
  transform: CSS.Transform.toString(transform),
  transition,
};
```

The `onDragEnd` prop receives the standard `@dnd-kit` drag-end event. Use `arrayMove` from `@dnd-kit/sortable` to reorder the item array:

```js
import { arrayMove } from '@dnd-kit/sortable';

const handleDragEnd = ({ active, over }) => {
  if (active.id !== over?.id) {
    setItems(items => {
      const oldIndex = items.findIndex(i => i.id === active.id);
      const newIndex = items.findIndex(i => i.id === over.id);
      return arrayMove(items, oldIndex, newIndex);
    });
  }
};
```

### Click-to-add activation distance

Sidebar items can be added to the canvas either by dragging or by a plain click. A pointer-sensor activation-distance threshold (a few pixels of movement) is what allows this: it distinguishes a click from the start of a drag, so a click fires the "add" handler instead of starting a drag operation. Consumers wiring up their own sidebar items should apply the same activation-distance constraint on their `MouseSensor` if they want click-to-add alongside drag-to-add.

## Sidebar position

A `SidebarPositionButton` toggle pair (in `CanvasUIPreferences.jsx`) lets the user dock the sidebar on the left or right of the canvas. Like the other toolbar toggles, the choice is persisted via the `useUIPreference` hook, keyed off `preferenceId`, so it's remembered per entity.

## Inline editing (`nameSlot`)

`CanvasPreviewItem` exposes a `nameSlot` render prop so consumers can inject inline-editable controls into the card header instead of relying solely on the settings panel. Known consumers include the form builder's inline Label editor, and equivalent inline name/type editors for meta fields, dynamic blocks, and CCT table columns — each lives alongside its own page (not inside the shared `CanvasLayout/` directory above).

A field/column-level `isReusable` flag governs two things: whether its name is inline-editable at all, and whether it can be re-added to the canvas while already present. It's `false` for items bound to an existing meta field, CCT column, or WordPress core field, so that binding can't be broken by a rename or duplicate.

## Resizable panels

The sidebar/canvas split uses `react-resizable-panels`. The divider position is persisted in `localStorage` under the key `acpt_canvas_panel_{preferenceId}`. The sidebar `Panel` also has an explicit, stable `id`, which fixes a bug where saved panel widths could get mixed up after switching the sidebar from one side to the other.

## Dependencies

| Package                  | Version | Role                           |
| ------------------------ | ------- | ------------------------------ |
| `@dnd-kit/core`          | 6.x     | Drag-and-drop context          |
| `@dnd-kit/sortable`      | 7.x     | Sortable list abstraction      |
| `@dnd-kit/utilities`     | 3.x     | CSS transform helpers          |
| `react-resizable-panels` | 4.x     | Resizable sidebar/canvas split |
