{"_id":"@atelier83/layouts","_rev":"3-aacf0e016e1e3fed842bb68a014ac798","name":"@atelier83/layouts","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@atelier83/layouts","version":"0.1.0","keywords":["layout","panels","dockable","split","resizable","tabs","drag-and-drop","headless","react"],"author":{"name":"atelier83"},"license":"MIT","_id":"@atelier83/layouts@0.1.0","maintainers":[{"name":"andrevenancio","email":"info@andrevenancio.com"}],"homepage":"https://github.com/atelier83/layouts#readme","bugs":{"url":"https://github.com/atelier83/layouts/issues"},"dist":{"shasum":"9ce3f5e25439bc40b229774e70254ae9f18f7963","tarball":"https://registry.npmjs.org/@atelier83/layouts/-/layouts-0.1.0.tgz","fileCount":41,"integrity":"sha512-Noj5Qy4WYoC4jCUfEMlQHfVk3Wnid02LQJcXh5RV9hGoKTxrDsql7OBFai/Jk36RHKBdfvHOIZVy3waksrF/Ig==","signatures":[{"sig":"MEUCIBGydB5+ALmmqUX+f30Wn7g5isZxVS8pda35B+KtK/7HAiEA+XmAoHpQ5SI7XUQdeCqLYJDhnntzCcH0NrkwVCroR8U=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":107216},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"},"./theme.css":"./dist/theme.css"},"gitHead":"3d7b5256b6354e8d164a82925d4ff4479201ebd1","scripts":{"dev":"vite","test":"vitest run","build":"tsc -p tsconfig.build.json && cp src/theme.css dist/theme.css","build:demo":"vite build","test:watch":"vitest","check-types":"tsc --noEmit","test:coverage":"vitest run --coverage","prepublishOnly":"npm run test && npm run build"},"_npmUser":{"name":"andrevenancio","email":"info@andrevenancio.com"},"repository":{"url":"git+https://github.com/atelier83/layouts.git","type":"git"},"_npmVersion":"10.9.3","description":"Headless dockable panel layouts: split tree, tabs, resize, drag-to-dock. Vanilla TS core with optional React bindings.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"22.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.33.0","devDependencies":{"vite":"^6.0.0","jsdom":"^29.1.1","react":"^19.0.0","vitest":"^3.0.0","react-dom":"^19.0.0","typescript":"^5.6.0","@types/react":"^19.0.0","@types/react-dom":"^19.0.0","@vitest/coverage-v8":"^3.2.6","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":">=18","react-dom":">=18"},"peerDependenciesMeta":{"react":{"optional":true},"react-dom":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/layouts_0.1.0_1780935131785_0.30380472109143275","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@atelier83/layouts","version":"0.2.0","keywords":["layout","panels","dockable","split","resizable","tabs","drag-and-drop","headless","react"],"author":{"name":"atelier83"},"license":"MIT","_id":"@atelier83/layouts@0.2.0","maintainers":[{"name":"andrevenancio","email":"info@andrevenancio.com"}],"homepage":"https://github.com/atelier83/layouts#readme","bugs":{"url":"https://github.com/atelier83/layouts/issues"},"dist":{"shasum":"3622c92ecba13644146ec069f65e3f2f948fc4ac","tarball":"https://registry.npmjs.org/@atelier83/layouts/-/layouts-0.2.0.tgz","fileCount":41,"integrity":"sha512-vnZAU6vig3jrfcEWOLvqPS4iTsFjs9WopNHS82YgeBKJhDQxSbSIqvQNWa5e9USf5fh+6eWifzJ0vkjNwUNTpg==","signatures":[{"sig":"MEYCIQC7ibfmQAMh2gWOU/8AlgwD3OQ6s1lsF97UG2CmYfJ5MQIhANJPfmsLctjNhin8CXxLHbR22g0Pvq+4Gv5uFUZHUAOQ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":106720},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"},"./theme.css":"./dist/theme.css"},"gitHead":"c2fd338b29f6bda1e5e0a3929bba68702a8feea6","scripts":{"dev":"vite","test":"vitest run","build":"tsc -p tsconfig.build.json && cp src/theme.css dist/theme.css","build:demo":"vite build","test:watch":"vitest","check-types":"tsc --noEmit","test:coverage":"vitest run --coverage","prepublishOnly":"pnpm test && pnpm build"},"_npmUser":{"name":"andrevenancio","email":"info@andrevenancio.com"},"repository":{"url":"git+https://github.com/atelier83/layouts.git","type":"git"},"_npmVersion":"10.9.3","description":"Headless dockable panel layouts: split tree, tabs, resize, drag-to-dock. Vanilla TS core with optional React bindings.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"22.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.33.0","devDependencies":{"vite":"^6.0.0","jsdom":"^29.1.1","react":"^19.0.0","vitest":"^3.0.0","react-dom":"^19.0.0","typescript":"^5.6.0","@types/react":"^19.0.0","@types/react-dom":"^19.0.0","@vitest/coverage-v8":"^3.2.6","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":">=18","react-dom":">=18"},"peerDependenciesMeta":{"react":{"optional":true},"react-dom":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/layouts_0.2.0_1781143735202_0.3991499370837104","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@atelier83/layouts","version":"0.2.1","description":"Headless dockable panel layouts: split tree, tabs, resize, drag-to-dock. Vanilla TS core with optional React bindings.","license":"MIT","author":{"name":"atelier83"},"homepage":"https://github.com/atelier83/layouts#readme","repository":{"type":"git","url":"git+https://github.com/atelier83/layouts.git"},"bugs":{"url":"https://github.com/atelier83/layouts/issues"},"keywords":["layout","panels","dockable","split","resizable","tabs","drag-and-drop","headless","react"],"type":"module","packageManager":"pnpm@10.33.0","publishConfig":{"access":"public"},"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react/index.d.ts","import":"./dist/react/index.js"},"./theme.css":"./dist/theme.css"},"sideEffects":["**/*.css"],"scripts":{"dev":"vite","build":"tsc -p tsconfig.build.json && cp src/theme.css dist/theme.css","build:demo":"vite build","check-types":"tsc --noEmit","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","changeset":"changeset","version":"changeset version","release":"pnpm test && pnpm build && pnpm publish","prepublishOnly":"pnpm test && pnpm build"},"peerDependencies":{"react":">=18","react-dom":">=18"},"peerDependenciesMeta":{"react":{"optional":true},"react-dom":{"optional":true}},"devDependencies":{"@changesets/cli":"^2.31.0","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@types/react":"^19.0.0","@types/react-dom":"^19.0.0","@vitest/coverage-v8":"^3.2.6","jsdom":"^29.1.1","react":"^19.0.0","react-dom":"^19.0.0","typescript":"^5.6.0","vite":"^6.0.0","vitest":"^3.0.0"},"engines":{"node":">=20"},"_id":"@atelier83/layouts@0.2.1","gitHead":"92451b7191cf84475f8118c1fd1522ec1006d9ff","_nodeVersion":"22.19.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-spexol3jtXzixgS8UoSS5F13VO8BKO3wzdnKFKqm4XiSimnx6KOqLYm7/65aaglLlzlqSxqXKzCuqO57xKAYBw==","shasum":"ec3965a2f166e965a5dfddef7c44f73013b8a1e6","tarball":"https://registry.npmjs.org/@atelier83/layouts/-/layouts-0.2.1.tgz","fileCount":41,"unpackedSize":107056,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD+SgMtSprM9wTnoSfbgp73Dd9A5DyWyZ5eX2UMcXmMggIgOs46m/y2NNeKolW4r82bxFxdZfJdbiTohIp9H9Qfi60="}]},"_npmUser":{"name":"andrevenancio","email":"info@andrevenancio.com"},"directories":{},"maintainers":[{"name":"andrevenancio","email":"info@andrevenancio.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/layouts_0.2.1_1781229623799_0.7762334025312323"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-08T16:12:11.608Z","modified":"2026-06-12T02:00:24.093Z","0.1.0":"2026-06-08T16:12:11.968Z","0.2.0":"2026-06-11T02:08:55.404Z","0.2.1":"2026-06-12T02:00:23.953Z"},"bugs":{"url":"https://github.com/atelier83/layouts/issues"},"author":{"name":"atelier83"},"license":"MIT","homepage":"https://github.com/atelier83/layouts#readme","keywords":["layout","panels","dockable","split","resizable","tabs","drag-and-drop","headless","react"],"repository":{"type":"git","url":"git+https://github.com/atelier83/layouts.git"},"description":"Headless dockable panel layouts: split tree, tabs, resize, drag-to-dock. Vanilla TS core with optional React bindings.","maintainers":[{"name":"andrevenancio","email":"info@andrevenancio.com"}],"readme":"# @atelier83/layouts\n\n> Headless dockable panel layouts for the web — split trees, tabs, resizing, and drag-to-dock, with zero visual styling.\n\n[![npm](https://img.shields.io/npm/v/@atelier83/layouts.svg)](https://www.npmjs.com/package/@atelier83/layouts)\n[![bundle size](https://img.shields.io/bundlephobia/minzip/@atelier83/layouts.svg)](https://bundlephobia.com/package/@atelier83/layouts)\n[![CI](https://github.com/atelier83/layouts/actions/workflows/ci.yml/badge.svg)](https://github.com/atelier83/layouts/actions/workflows/ci.yml)\n[![license](https://img.shields.io/npm/l/@atelier83/layouts.svg)](./LICENSE)\n\nThe kind of panel layout you get in VS Code, Photoshop, or Blender. You describe the layout as a nested split tree; the library positions the DOM containers, handles resizing, and runs drag-to-dock. It renders almost nothing of its own (a bare tab bar, and only when a region holds 2+ panels) and ships zero visual styling — you bring the panel content and the CSS.\n\n## Features\n\n- **Headless** — structural CSS only (flex, sizing, cursors). No theme imposed.\n- **Framework-agnostic core** — vanilla TypeScript, plus optional thin React bindings.\n- **Drag-to-dock** — five drop zones per region (left / right / top / bottom / merge-as-tab).\n- **Resizable splits** — draggable dividers with `min` / `max` clamps.\n- **Tabs** — regions with 2+ panels get a tab bar; one panel renders chromeless.\n- **Accessible** — real tablist/tab/tabpanel roles, separator dividers, keyboard resize.\n- **Tiny & SSR-safe** — no runtime dependencies; panel DOM survives moves and remounts.\n\n## Contents\n\n- [Install](#install)\n- [Why headless?](#why-headless)\n- [Concepts](#concepts)\n- [Config](#config)\n- [Dragging](#dragging)\n- [Vanilla](#vanilla)\n- [React](#react)\n- [Styling](#styling)\n- [Accessibility](#accessibility)\n- [API reference](#api-reference)\n- [When not to use this](#when-not-to-use-this)\n- [Development](#development)\n- [License](#license)\n\n## Install\n\n```bash\nnpm install @atelier83/layouts\n# or: pnpm add @atelier83/layouts\n```\n\n`react` and `react-dom` (>=18) are optional peer dependencies, only needed if you use `@atelier83/layouts/react`.\n\n## Why headless?\n\nMost layout libraries ship a look. That look is the first thing you fight when it doesn't match your app. `layouts` takes the opposite stance: it owns the hard, invisible parts — the split-tree math, divider dragging, tab management, and drag-to-dock hit-testing — and hands you plain DOM elements with stable `layouts-*` classes and `data-*` state attributes. Style them with anything (plain CSS, CSS Modules, Tailwind), or opt into the bundled theme when you just want something that works. Nothing is themed unless you ask for it.\n\n## Concepts\n\n- **Split**: lays children out along `row` or `column` with draggable dividers.\n- **Region** (group): a leaf holding one or more panels. One panel is chromeless; 2+ show a tab bar.\n- **Panel**: your content plus an optional `fixed` flag. You get a DOM element and fill it.\n\n## Config\n\nAuthor a layout as a nested config. Identity and behaviour live inline on each panel, so there's no separate `panels` array. Omit `size` and the node flexes to fill what's left.\n\n```ts\nconst layout = {\n  direction: \"column\",\n  children: [\n    { id: \"header\", title: \"Header\", size: 48, fixed: true },\n    {\n      direction: \"row\",\n      children: [\n        { id: \"sidebar\", title: \"Sidebar\", size: 250, min: 160, max: 400 },\n        { id: \"content\", title: \"Content\" }, // flexes\n        { id: \"inspector\", title: \"Inspector\", size: 300, min: 200 },\n      ],\n    },\n    { id: \"terminal\", title: \"Terminal\", size: 180, min: 80, max: 400 },\n  ],\n};\n```\n\n| field         | meaning                                              |\n| ------------- | ---------------------------------------------------- |\n| `id`          | panel identifier (used to fetch its content element) |\n| `title`       | tab label                                            |\n| `size`        | initial px size; omit for a flexible node            |\n| `min` / `max` | resize clamps in px (defaults: 24 / unbounded)       |\n| `fixed`       | no move, no drops, no divider                        |\n\nStart panels pre-tabbed with a group:\n\n```ts\n{\n  group: [\n    { id: \"panel1\", title: \"Panel 1\" },\n    { id: \"panel2\", title: \"Panel 2\" },\n  ],\n  active: \"panel1\",\n  size: 300, min: 200, max: 400,\n}\n```\n\n## Dragging\n\nPress and hold a panel body to pick it up, then drag (tabs start dragging on the first move). Over a region you get five drop zones:\n\n- **left / right**: dock side-by-side in a row split.\n- **top / bottom**: stack in a column split.\n- **middle**: merge in as a tab.\n\nA `fixed` panel can't be dragged out, won't accept drops, and has no divider. Use it for chrome (headers, menu bars, icon rails). Empty regions collapse on their own.\n\n## Vanilla\n\n```ts\nimport { createLayout } from \"@atelier83/layouts\";\n\nconst engine = createLayout({ layout });\n\nengine.getPanelElement(\"sidebar\").innerHTML = `<div>...sidebar...</div>`;\n\nengine.mount(document.getElementById(\"app\")!);\n```\n\n## React\n\n```tsx\nimport { LayoutProvider, Panel } from \"@atelier83/layouts/react\";\n\nfunction App() {\n  return (\n    <LayoutProvider layout={layout} style={{ height: \"100vh\" }}>\n      <Panel id=\"sidebar\">\n        <MyFileTree />\n      </Panel>\n      <Panel id=\"content\">\n        <MyEditor />\n      </Panel>\n    </LayoutProvider>\n  );\n}\n```\n\nHooks available inside a `<LayoutProvider>`:\n\n- `useLayout()` → `{ tree, engine }`, re-rendering on every layout change (the reactive entry point).\n- `useEngine()` → the `LayoutEngine` itself, for imperative calls without subscribing.\n- `usePanel(id)` → the panel's content element, if you want to portal into it yourself instead of using `<Panel>`.\n\n## Styling\n\nThe library only emits structural CSS (flex, sizing, cursors) and the tab bar markup. Nothing is themed out of the box, so you have two options.\n\n### Option 1: the default theme\n\nOpt into a ready-made look:\n\n```ts\nimport \"@atelier83/layouts/theme.css\";\n```\n\nThe bundled theme is **dark**. It defines the shared `@atelier83` design tokens (`--a83-*`) and the skin rules that read them — no colours are hard-coded into the rules, and there's no built-in light/dark switching.\n\nReskin by overriding the tokens. `@atelier83/layouts` and [`@atelier83/timeline`](https://www.npmjs.com/package/@atelier83/timeline) read the **same** `--a83-*` tokens, so defining them once on a common ancestor (or `:root`) themes both packages together — they share one palette by design:\n\n```css\n:root {\n  --a83-bg: #262626;       /* active tab + panel content background */\n  --a83-surface: #323232;  /* tab bar background */\n  --a83-border: #323232;   /* dividers + tab separators */\n  --a83-border-strong: #3d3d3d; /* divider hover */\n  --a83-text: #c8c8c8;     /* active tab label */\n  --a83-text-muted: #8c8c8c; /* inactive tab label */\n  --a83-accent: #e6e6e6;   /* focus ring */\n  --a83-overlay: rgba(255, 255, 255, 0.03); /* drop-target fill */\n  --a83-highlight: rgba(255, 255, 255, 0.5); /* drag outline + drop border */\n  --a83-font: system-ui, sans-serif; /* tab bar font */\n  --a83-radius-sm: 2px;    /* drop overlay corner radius */\n}\n```\n\nTokens: `--a83-bg`, `--a83-surface`, `--a83-border`, `--a83-border-strong`, `--a83-text`, `--a83-text-muted`, `--a83-accent`, `--a83-overlay`, `--a83-highlight`, `--a83-font`, `--a83-radius-sm`.\n\nThere are no fallbacks and no mode switching baked in: the rules always read `var(--a83-*)`, and the theme decides the values. **Light/dark/system is your app's job** — redefine the tokens under your own `prefers-color-scheme` media query or `[data-theme]` rules.\n\n### Option 2: style it yourself\n\nEvery rendered part carries a base `layouts-*` class plus `data-*` attributes for state. The three examples below all style the same small layout:\n\n```ts\nconst layout = {\n  direction: \"row\",\n  children: [\n    { id: \"sidebar\", title: \"Sidebar\", size: 240, min: 160 },\n    {\n      group: [\n        { id: \"editor\", title: \"Editor\" },\n        { id: \"preview\", title: \"Preview\" },\n      ],\n    },\n  ],\n};\n```\n\n**Plain CSS / SCSS** — a global stylesheet targets the base classes and state attributes:\n\n```tsx\nimport \"./layout.css\";\n<LayoutProvider layout={layout} style={{ height: \"100vh\" }}>\n  {/* panels */}\n</LayoutProvider>;\n```\n\n```css\n/* layout.css */\n.layouts-tabbar {\n  background: #f4f4f5;\n}\n.layouts-tab {\n  padding: 5px 14px;\n  color: #71717a;\n}\n.layouts-tab[data-state=\"active\"] {\n  color: #18181b;\n  background: #fff;\n}\n.layouts-divider {\n  background: #e4e4e7;\n}\n.layouts-divider[data-active]:hover {\n  background: #c4c4cc;\n}\n.layouts-drop-overlay {\n  border: 1px dashed rgba(0, 0, 0, 0.35);\n}\n```\n\n**CSS Modules** — the parts are created inside the engine, so pass scoped classes through `classNames`. Style state with `data-*` inside the module:\n\n```tsx\nimport s from \"./layout.module.css\";\n\n<LayoutProvider\n  layout={layout}\n  classNames={{ tabbar: s.tabbar, tab: s.tab, divider: s.divider }}\n/>;\n```\n\n```css\n/* layout.module.css */\n.tabbar {\n  background: #f4f4f5;\n}\n.tab {\n  padding: 5px 14px;\n  color: #71717a;\n}\n.tab[data-state=\"active\"] {\n  color: #18181b;\n  background: #fff;\n}\n.divider {\n  background: #e4e4e7;\n}\n.divider[data-active]:hover {\n  background: #c4c4cc;\n}\n```\n\n**Tailwind** — utility strings, with `data-[...]` variants for state:\n\n```tsx\n<LayoutProvider\n  layout={layout}\n  classNames={{\n    tabbar: \"bg-zinc-100\",\n    tab: \"px-3 py-1 text-zinc-500 data-[state=active]:text-zinc-900 data-[state=active]:bg-white\",\n    divider: \"bg-zinc-200 data-[active]:hover:bg-zinc-300\",\n    dropOverlay:\n      \"border border-dashed border-black/25 data-[zone=middle]:border-solid data-[zone=middle]:border-blue-600\",\n  }}\n/>\n```\n\n`classNames` slots: `root`, `split`, `group`, `tabbar`, `tab`, `panelContent`, `divider`, `dropOverlay`. (Vanilla: pass the same map to `createLayout({ layout, classNames })`.)\n\nState hooks available on each part:\n\n| part                     | base class               | state attributes                                 |\n| ------------------------ | ------------------------ | ------------------------------------------------ |\n| split container          | `.layouts-split`         | `[data-orientation=\"row\\|column\"]`               |\n| region                   | `.layouts-group`         | `[data-dragging]` while dragged from             |\n| tab bar (2+ panels)      | `.layouts-tabbar`        | —                                                |\n| tab button               | `.layouts-tab`           | `[data-state=\"active\\|inactive\"]`                |\n| panel content (you fill) | `.layouts-panel-content` | —                                                |\n| divider                  | `.layouts-divider`       | `[data-orientation]`, `[data-active]`            |\n| drop highlight           | `.layouts-drop-overlay`  | `[data-zone=\"left\\|right\\|top\\|bottom\\|middle\"]` |\n\n## Accessibility\n\nTabs render as real `<button>`s with `role=\"tab\"` / `aria-selected` inside a `role=\"tablist\"`, and the active panel is a `role=\"tabpanel\"` labelled by its tab. Tabs use a roving `tabindex`: focus the strip, then `ArrowLeft` / `ArrowRight` move (and activate) tabs, with `Home` / `End` jumping to the ends.\n\nResizable dividers are `role=\"separator\"` with `aria-orientation` and `aria-valuenow` / `aria-valuemin` / `aria-valuemax`. They're focusable and resize with the arrow keys (`ArrowLeft` / `ArrowRight` for vertical splits, `ArrowUp` / `ArrowDown` for horizontal ones).\n\nThe theme adds a `:focus-visible` ring and honours `prefers-reduced-motion`. Drag-to-dock remains pointer-only; keyboard docking (moving a panel between regions) is the one gesture still on the roadmap.\n\n## API reference\n\n### `createLayout(options) → LayoutEngine`\n\n`options`: `{ layout: LayoutConfig, classNames?: LayoutClassNames }`.\n\nThe engine is the source of truth. All mutators return synchronously and notify subscribers.\n\n| method                                   | description                                                             |\n| ---------------------------------------- | ----------------------------------------------------------------------- |\n| `mount(root)`                            | render into a host element and attach drag/resize interactions          |\n| `getPanelElement(id)`                    | the content element for a panel, created on first ask (you fill it)     |\n| `getPanelDef(id)`                        | the panel's `{ id, title?, fixed? }`, or `undefined`                    |\n| `getSnapshot()`                          | the current `{ tree }` snapshot                                         |\n| `subscribe(listener)`                    | register a change listener; returns an unsubscribe function             |\n| `setLayout(config)`                      | replace the whole layout (merges in any new panel defs)                 |\n| `resize(splitId, dividerIndex, deltaPx)` | move a divider by a pixel delta                                         |\n| `setActiveTab(groupId, panelId)`         | select the active tab in a region                                       |\n| `moveTab(panelId, { groupId, index? })`  | move a panel into a region, optionally at a tab index                   |\n| `attachPanel(panelId, groupId, side)`    | dock a panel beside a region (`\"left\" \\| \"right\" \\| \"top\" \\| \"bottom\"`) |\n| `dispose()`                              | detach interactions and clear the host (panel elements are kept)        |\n\n### React (`@atelier83/layouts/react`)\n\n| export             | description                                               |\n| ------------------ | --------------------------------------------------------- |\n| `<LayoutProvider>` | builds the engine, mounts it, and provides it via context |\n| `<Panel id>`       | portals its children into that panel's content element    |\n| `useLayout()`      | `{ tree, engine }`, re-renders on layout changes          |\n| `useEngine()`      | the `LayoutEngine`, no subscription                       |\n| `usePanel(id)`     | the panel's content element                               |\n\n`LayoutProvider` props: `layout` (initial config only — call `engine.setLayout` to change it later), `classNames`, `className`, `style`, `children`.\n\n## When not to use this\n\n`layouts` is intentionally small and unopinionated. Reach for something else if you need:\n\n- a batteries-included, pre-styled UI out of the box — try [dockview](https://dockview.dev/) or [FlexLayout](https://github.com/caplin/FlexLayout);\n- floating/pop-out windows or serialized workspaces with persistence baked in — [golden-layout](https://golden-layout.com/) covers that ground;\n- a tiling-only window manager with no tabs or docking — [react-mosaic](https://github.com/nomcopter/react-mosaic) is leaner for that.\n\nIt's a good fit when you want full control of the look, a vanilla core you can wrap in any framework, and a tiny dependency footprint.\n\n## Development\n\n```bash\npnpm install\npnpm dev          # live playground (the playground/ page) at http://localhost:5173\npnpm test         # run the test suite once\npnpm test:watch   # watch mode\npnpm check-types  # type-check without emitting\npnpm build        # build the library to dist/\npnpm build:demo   # bundle the playground into demo-dist/ for hosting\n```\n\nThe `playground/` page imports the library source directly. `pnpm build:demo` bundles it into `demo-dist/`, which you can deploy to any static host.\n\n## License\n\n[MIT](./LICENSE) © atelier83\n","readmeFilename":"README.md"}