Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5d97386
docs(v2): audit of v1 and design proposal for v2
akash-aman Oct 4, 2026
dc654bd
test(core): characterization fixtures for v1 behaviour
akash-aman Oct 4, 2026
653f2d1
feat(core): immutable layout model and pure tree functions
akash-aman Oct 4, 2026
4556546
feat(core): pure geometry and splitter math
akash-aman Oct 4, 2026
005b03c
feat(core): drop target and drop indicator calculation
akash-aman Oct 4, 2026
31400f8
feat(core): v2 serialization and migrateLayoutFromV1
akash-aman Oct 4, 2026
14d54d8
feat(core): createLayout instance with store and snapshots
akash-aman Oct 4, 2026
5082d2f
docs(v2): record bugs found by the fixtures and weight rules learned …
akash-aman Oct 4, 2026
2542920
build(core, react): emit declarations for types-only modules
akash-aman Oct 4, 2026
0a03b7a
feat(core): name TabBarMeasurement and accept readonly v1 input
akash-aman Oct 4, 2026
d95b341
feat(react)!: rebuild on createLayout with context, headless hooks an…
akash-aman Oct 4, 2026
3bcbf9e
docs(v2): correct the audit on whether the React smoke test ran in CI
akash-aman Oct 4, 2026
ea6da8b
refactor(solid)!: port the Solid adapter to createLayout
akash-aman Oct 4, 2026
c70ae9f
refactor(core)!: remove DynamixLayoutCore, static caches, Queue, bann…
akash-aman Oct 4, 2026
ada9854
fix(react, solid): keep @dynamix-layout/core imports in published types
akash-aman Oct 4, 2026
4bfc656
docs(v2): record broken published type declarations (B30)
akash-aman Oct 4, 2026
407000d
fix(react): position slots without the stylesheet
akash-aman Oct 4, 2026
7ed495f
docs(examples): React examples for each API level
akash-aman Oct 4, 2026
3636b10
build: exports maps, peer deps, CJS types, use client banner and a Re…
akash-aman Oct 4, 2026
a31dc4c
chore: changesets for 2.0.0 of core, react and solid
akash-aman Oct 4, 2026
12fa5e5
refactor(react): split layout parts and keyboard move mode; add cover…
akash-aman Oct 4, 2026
a5b9317
feat(solid): re-export LayoutJSON, LayoutTreeV1 and the migration hel…
akash-aman Oct 4, 2026
c0a1508
docs: v2 READMEs, repository links and site install snippets
akash-aman Oct 4, 2026
574121f
fix(core): report invalid weights found while migrating v1 layouts
akash-aman Oct 5, 2026
2b5f17a
docs: getting started, API reference, guides and the v1 to v2 migrati…
akash-aman Oct 5, 2026
274b3b3
fix(react): keep getRootProps stable across the first measurement
akash-aman Oct 5, 2026
3f6a93c
docs(core, react): examples and tags in the TSDoc of every public export
akash-aman Oct 5, 2026
177ec54
build: let consumers on Node 18+ install the packages; drop a dead tu…
akash-aman Oct 5, 2026
7c0da84
docs(v2): mark the design as implemented; note Node 18 support in the…
akash-aman Oct 5, 2026
e57a13d
Merge remote-tracking branch 'origin/main' into v2
akash-aman Oct 5, 2026
22c7ddd
feat(solid): reactive tabs, closable tabs and onReady
akash-aman Oct 5, 2026
7c08008
docs(examples): React showcase with every feature on one page
akash-aman Oct 5, 2026
da54b6b
docs(examples): Solid showcase with every feature
akash-aman Oct 5, 2026
eb05597
docs(examples): plain JavaScript showcase on the core
akash-aman Oct 5, 2026
02f0e59
docs: link the showcase examples, document new Solid props
akash-aman Oct 5, 2026
570bf83
docs(examples): Solid showcase adds a tab at a target
akash-aman Oct 5, 2026
da78c49
docs(examples): Next.js all-features showcase at /showcase
akash-aman Oct 5, 2026
3aa635f
docs(examples): SolidStart all-features showcase at /showcase
akash-aman Oct 5, 2026
d105ea3
Merge origin/main (core 1.2.0, react 1.1.0, solid 1.1.0) into v2
akash-aman Oct 5, 2026
38c3c71
build: keep the MIT licence comment in every bundle
akash-aman Oct 5, 2026
37a6e44
feat(solid): updateJSON receives the change reason
akash-aman Oct 5, 2026
a0c59e9
docs(examples): showcases restore added and closed tabs on reload
akash-aman Oct 5, 2026
46b6fe7
chore: ignore .DS_Store
akash-aman Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .changeset/v2-core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@dynamix-layout/core': major
---

**v2: an instance-based, pure layout engine.** See the migration guide for every rename.

- **`createLayout(options)` replaces `new DynamixLayoutCore()`.** Each instance is independent: there is no module-level state, no static caches, no timers, and nothing touches the DOM. Two layouts on a page, or one per server request, no longer interfere.
- **Immutable snapshots** (`getSnapshot()` / `subscribe()`) share unchanged parts by reference: `tabsets`, `tabs`, `splitters`, `rects` and `drag`.
- **Typed actions** that return whether anything changed and never throw: `moveTab`, `moveTabset`, `selectTab`, `addTab`, `removeTab`, `setTabs`, `resizeSplitter`, `moveSplitterBy`, `maximize`/`restore`, `fold`/`unfold`, plus one drag model for tabs and splitters (`startDrag`, `updateDrag`, `endDrag`, `cancelDrag`) and hit testing (`getDropTarget`, `listDropTargets`).
- **`onLayoutChange(layout, reason)`** reports committed changes.
- **New saved format, `LayoutJSON`** (`version: 2`): readable names, explicit directions, and tab ids as the stable identity.
- **Old layouts:** v1 trees load automatically, or convert them with `migrateLayoutFromV1`. They render pixel-identically, as checked against layouts recorded from v1.
- **Deterministic ids**, safe for server rendering; no `crypto.randomUUID()`, which threw outside secure contexts.
- **Rects are relative to the layout root.** v1 misplaced layouts that weren't at the viewport's top-left.
- **No import-time side effects:** the console banner and `window.__DYNAMIX_LAYOUT__` are gone. Use `version` instead.
- **Fixed:**
- nested rows left behind after some moves;
- stale sizes when every open panel had weight 0;
- a row stuck at its minimum next to a folded tabset;
- the root staying oversized after the container shrank;
- center drops inserting before the last tab (they now append).
- **Packaging:** the CommonJS build loads with `require()` (it was ESM under a `.cjs.js` name), exports maps give each condition its own types, and the package installs on Node 18 and later (it required Node 22).

**Breaking:**
- `DynamixLayoutCore`, `Node`, `Bond`, `Node.cache`, `Queue`, `createReactiveState`, the comparators, the drop-preview and DOM helpers, and the `LayoutTree`/`NodeOptions` types are removed.
- `updateTree(src, des, area)` becomes `moveTab`/`moveTabset(id, target)`, with `'contain'` renamed to `'center'`.
- `updateDimension` becomes `setContainerRect`, and the debounce arguments are gone; throttle in the adapter instead.
32 changes: 32 additions & 0 deletions .changeset/v2-react.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
'@dynamix-layout/react': major
---

**v2: a small component API, headless hooks, accessibility and SSR.** See the migration guide for every rename.

- **`<DynamixLayout>`**:
- `tabs` is a list of objects (`{ id, title, content, closable, target }`);
- `defaultLayout` (uncontrolled) or `layout` (controlled) takes v1 or v2 JSON, and `onLayoutChange(layout, { reason })` reports changes;
- `components`, `classNames` and `styles` replace the wrapper and styling props;
- `ref` gives an imperative handle.
- **The engine is created once and never rebuilt by prop changes.** An inline `tabs` array with unchanged ids does no engine work and remounts nothing, and moving a tab never remounts its content.
- **Headless API:** `useDynamixLayout` + `DynamixLayoutProvider`, `useTabset`, `useTab`, `useSplitter`, `useLayoutState` (re-renders only for the selected value), `useLayoutActions` and `useDragState`. Prop getters merge your refs, classes, styles and handlers.
- **Pointer-event dragging** for tabs, tabsets and splitters: touch long-press, Escape cancels, and it works over iframes. Drags and resizes never re-render React.
- **ResizeObserver** follows the container, so sidebars and flex parents resize the layout.
- **Accessibility:** WAI-ARIA tabs and window splitter patterns, keyboard resizing, keyboard move mode (Mod+Shift+M), and a live region.
- **SSR-safe and deterministic**, with a `'use client'` entry for the Next.js App Router. Works with React 18 and 19; the bundle no longer inlines React 19's JSX runtime.
- **Fixed:** callback refs are honoured, `layoutTree` changes are no longer ignored, and duplicate DOM ids are gone.

- **Installs on Node 18 and later** (1.x required Node 22).

**Breaking:**
- Removed:
- the v1 props and wrapper components (`WrapTab*`, `SliderElement`, `HoverElement`, `RootSplitterHoverEl`);
- `getTabOutput`;
- the v1 `useDynamixLayout` return shape.
- `updateJSON` becomes `onLayoutChange`, which receives v2 JSON and is not called on mount.
- `ref` returns a `DynamixLayoutHandle` (`handle.element` is the root).
- Keyboard shortcuts only act when focus is inside the layout.
- Default classes are `.dx-*`, and data attributes are `data-dx-*`.
- The stylesheet is `@dynamix-layout/react/styles.css` (`style.css` still works).
- React 17 is no longer supported.
20 changes: 20 additions & 0 deletions .changeset/v2-solid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@dynamix-layout/solid': major
---

**Runs on the v2 core.** `<DynamixLayout>` keeps its props, default components and drag-and-drop, but each component now has its own engine instance. Two Solid layouts on a page no longer share state, and a layout away from the viewport's top-left is placed correctly.

- **Installs on Node 18 and later** (1.x required Node 22).

**New:**
- `tabs` is reactive: add an entry to open a tab, remove it to close one. Existing tabs keep their content mounted.
- Closable tabs: a third tuple element `{ title, closable }` and an `onTabClose` prop.
- `onReady(layout)` gives the engine for actions from code (`maximize`, `fold`, `selectTab`, `moveTab`, `reset`, `toJSON`, …).
- `updateJSON(layout, reason)` also says why it was called: `'move'`, `'resize'`, `'select'`, `'fold'`, `'maximize'`, `'tabs'`, `'reset'`, or `'mount'` for the first call (type `LayoutUpdateReason`).

**Breaking:**
- `layoutTree` accepts v2 JSON (v1 trees are migrated automatically), and `updateJSON` receives v2 `LayoutJSON`.
- `getTabOutput` and the `TabEntry`/`TabInput`/`TabOutput` types are removed, and tab ids are the tab labels.
- `useDynamixLayout` returns a new shape.
- The slider timeout options no longer apply; splitters update once per frame.
- `solid-js` is a peer dependency.
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,21 @@ jobs:
- uses: actions/checkout@v7
- uses: ./.github/actions/ci-setup
- uses: ./.github/actions/ci-checks

react-18:
# The React package must work with React 18 as well as 19: install the
# packed packages into a project that only has React 18.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/ci-setup
- name: Build and pack
run: |
pnpm --filter @dynamix-layout/core --filter @dynamix-layout/react build
mkdir -p /tmp/pack
pnpm --filter @dynamix-layout/core --filter @dynamix-layout/react exec pnpm pack --pack-destination /tmp/pack
- name: Smoke test with React 18
working-directory: scripts/react18-smoke
run: |
npm install --no-audit --no-fund --no-save react@18 react-dom@18 jsdom@26 /tmp/pack/*.tgz
npm test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pids
lib-cov
react.html
core.html
solid.html
# Coverage directory used by tools like istanbul
coverage
*.lcov
Expand Down Expand Up @@ -131,3 +132,6 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# macOS
.DS_Store
195 changes: 64 additions & 131 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

# 🧩 Dynamix Layout

**Create dynamic, dockable, and resizable layouts with ease, similar to editors like VS Code.**
**Dockable, resizable tab layouts like VS Code's, for React and Solid, built on a framework-agnostic core.**

</div>

<p align="center">
<a href="https://www.npmjs.com/package/@dynamix-layout/solid">
<img src="https://img.shields.io/npm/v/@dynamix-layout/solid?style=for-the-badge&label=Solid" alt="NPM">
</a>
<a href="https://www.npmjs.com/package/@dynamix-layout/react">
<img src="https://img.shields.io/npm/v/@dynamix-layout/react?style=for-the-badge&label=React" alt="NPM">
<img src="https://img.shields.io/npm/v/@dynamix-layout/react?style=for-the-badge&label=React" alt="npm: @dynamix-layout/react">
</a>
<a href="https://www.npmjs.com/package/@dynamix-layout/solid">
<img src="https://img.shields.io/npm/v/@dynamix-layout/solid?style=for-the-badge&label=Solid" alt="npm: @dynamix-layout/solid">
</a>
<a href="https://www.npmjs.com/package/@dynamix-layout/core">
<img src="https://img.shields.io/npm/v/@dynamix-layout/core?style=for-the-badge&label=Core" alt="NPM">
<img src="https://img.shields.io/npm/v/@dynamix-layout/core?style=for-the-badge&label=Core" alt="npm: @dynamix-layout/core">
</a>
<img src="https://img.shields.io/github/license/xcode-studio/dynamix-layout?style=for-the-badge" alt="License">
</p>
Expand All @@ -31,156 +31,89 @@
</a>
</p>

## Overview

Dynamix Layout is a powerful JavaScript library designed to help you build complex, multi-panel user interfaces. It provides a core engine for managing the layout logic and a dedicated React wrapper for seamless integration into your React applications. If you've ever wanted to create a user experience with draggable tabs and resizable panels, Dynamix Layout is the tool for you.

---

### Made with ❤️ by [Akash Aman](https://linktr.ee/akash_aman)

---

<p align="center">
<a href="https://dx.xcode.cx" target="_blank" rel="noopener noreferrer" style="text-decoration: none;">
<a href="https://dx.xcode.cx" target="_blank" rel="noopener noreferrer">
<img src="https://img.shields.io/badge/🌐%20Live%20Demo-Visit%20Now-4CAF50?style=for-the-badge&logo=vercel&logoColor=white" alt="Live Demo"/>
</a>
</p>

---

![Demo](https://raw.githubusercontent.com/xcode-studio/dynamix-layout/main/assets/demo1.gif)

---

## ✨ Key Features

- **Draggable Tabs**: Easily drag and drop tabs to rearrange them or create new panel splits.
- **Resizable Panels**: Users can click and drag the space between panels to resize them.
- **Dynamic Splits**: Split any panel horizontally or vertically by dropping a tab onto its edge.
- **Save & Restore**: Serialize the entire layout state to JSON and restore it later.
- **Framework-Agnostic Core**: The core logic is written in pure TypeScript with zero dependencies.
- **Official React Wrapper**: A feature-rich React component (`<Layout />`) and hook (`useLayout`) for easy integration.

---

## 📚 Packages in this Monorepo

This repository contains the following packages:

### │ 📦 @dynamix-layout/core

The core, framework-agnostic layout engine. It handles all the complex logic of tree management, dimension calculation, and state updates. You can use this package to integrate Dynamix Layout with any framework (Vue, Svelte, Angular, etc.) or with vanilla JavaScript.
![Dragging tabs between panels and resizing them](https://raw.githubusercontent.com/xcode-studio/dynamix-layout/main/assets/demo1.gif)

➡️ **[View the detailed `@dynamix-layout/core` README](./packages/core/README.md)**
Your tabs live in **tabsets**, arranged in **rows** and separated by draggable **splitters**. Users drag tabs to rearrange or split panels, resize them, and maximize or fold a panel. The layout is plain JSON you can save and restore.

### │ 🚀 @dynamix-layout/react
## Features

The official React component library for Dynamix Layout. It provides a simple-to-use `<DynamixLayout />` component and an advanced `useDynamixLayout` hook that handles all the rendering, state management, and event binding for you. This is the recommended package for all React developers.
- **Drag tabs** onto another tab bar, onto a panel's side to split it, or onto a layout edge. Works with mouse, pen and touch.
- **Resize** with splitters that respect minimum sizes. Only the two neighbouring panels move.
- **Maximize** a panel, or **fold** it to a strip, and get back the exact previous size.
- **Tab content never remounts** when tabs move, so editors, terminals and iframes keep their state.
- **Save and restore** the layout as versioned JSON. Layouts saved by v1 load automatically.
- **Accessible**: WAI-ARIA tabs and window splitter patterns, keyboard resizing, and a keyboard move mode.
- **SSR-safe and fast**: deterministic server markup, and drags and resizes never re-render React.
- **Three levels of API**: one component, headless hooks, or the framework-agnostic core.

➡️ **[View the detailed `@dynamix-layout/react` README](./packages/react/README.md)**
## Packages

### │ 🚀 @dynamix-layout/solid
| Package | |
|---|---|
| [`@dynamix-layout/react`](./packages/react) | `<DynamixLayout>` and headless hooks for React 18 and 19 |
| [`@dynamix-layout/solid`](./packages/solid) | `<DynamixLayout>` for SolidJS |
| [`@dynamix-layout/core`](./packages/core) | The engine: tree, geometry, drag-and-drop and serialization, with no framework |

The official SolidJS wrapper for `@dynamix-layout/core`. It provides a flexible `<DynamixLayout />` component and an advanced `useDynamixLayout` hook to create fully dynamic, resizable, and draggable tab-based layouts with ease.

➡️ **[View the detailed `@dynamix-layout/solid` README](./packages/solid/README.md)**

---

## 📖 Examples

This repository contains the following examples:

| Example | Source Link | Live Demo Link |
|----------------|---------------------------|-------------------------------|
| React | [React](./examples/react) | [dx.xcode.cx](https://dx.xcode.cx) |
| Next.js | [Next.js](./examples/nextjs) | [nextjs.dx.xcode.cx](https://nextjs.dx.xcode.cx) |
| SolidJS | [SolidJS](./examples/solid) | [solidjs.dx.xcode.cx](https://solidjs.dx.xcode.cx) |
| SolidStart | [SolidStart](./examples/solidstart)| [solidstart.dx.xcode.cx](https://solidstart.dx.xcode.cx) |

---

## 📦 Installation

For React applications, you will need both the `react` and `core` packages.

```bash
npm install @dynamix-layout/react @dynamix-layout/core
```

For Solid applications, you will need both the `solid` and `core` packages.
## Quick start (React)

```bash
npm install @dynamix-layout/solid @dynamix-layout/core
npm install @dynamix-layout/react
```

---

## 🏁 Basic Usage

Here's how easy it is to get started with the React component:

```jsx
```tsx
import { DynamixLayout } from '@dynamix-layout/react'
import '@dynamix-layout/react/style.css'
import '@dynamix-layout/react/styles.css'

function App() {

const tabList = [
['editor', <div style={{ background: '#c0ca33', height: '100%' }}>Editor</div>],
['preview', <div style={{ background: '#66bb6a', height: '100%' }}>Preview</div>],
['terminal', <div style={{ background: '#ffc400', height: '100%' }}>Terminal</div>],
]
const tabs = [
{ id: 'editor', title: 'Editor', content: <Editor /> },
{ id: 'terminal', title: 'Terminal', content: <Terminal /> },
{ id: 'preview', title: 'Preview', content: <Preview /> },
]

export function App() {
return (
<DynamixLayout tabs={tabList} style={{ height: '100vh', width: '100vw' }} />
<div style={{ height: '100vh' }}>
<DynamixLayout tabs={tabs} />
</div>
)
}

export default App
```

Here's how easy it is to get started with the SolidJS component:

```jsx
import { DynamixLayout } from '@dynamix-layout/solid'
import '@dynamix-layout/solid/style.css'

function App() {

const tabList = [
['editor', <div style={{ background: '#c0ca33', height: '100%' }}>Editor</div>],
['preview', <div style={{ background: '#66bb6a', height: '100%' }}>Preview</div>],
['terminal', <div style={{ background: '#ffc400', height: '100%' }}>Terminal</div>],
];

return (
<DynamixLayout tabs={tabList} style={{ height: '100vh', width: '100vw' }} />
)
}

export default App
```
To save the layout, store what `onLayoutChange` gives you and pass it back as `defaultLayout`; see [Persisting layouts](./docs/guides/persisting-layouts.md).

## Documentation

- [Getting started](./docs/getting-started.md): concepts, the first layout, and saving and restoring.
- [API reference](./docs/api/README.md): every component, hook and function.
- Guides:
- [Custom tabs and splitters](./docs/guides/custom-components.md)
- [Theming](./docs/guides/theming.md)
- [Persisting layouts](./docs/guides/persisting-layouts.md)
- [Next.js and SSR](./docs/guides/nextjs-and-ssr.md)
- [Keyboard and accessibility](./docs/guides/accessibility.md)
- [Multiple layouts](./docs/guides/multiple-layouts.md)
- [Using the core without React](./docs/guides/core-without-react.md)
- [Performance](./docs/guides/performance.md)
- [Migrating from v1](./docs/migration-v1-to-v2.md).
- [Examples](./examples): every one has an **all-features showcase** (drag, split, resize, close, add, fold, maximize, restore, persist, reset):
- [React](./examples/react) (plus custom components, headless and controlled demos)
- [Next.js](./examples/nextjs)
- [Solid](./examples/solid)
- [SolidStart](./examples/solidstart)
- [Plain JavaScript](./examples/vanilla) on `@dynamix-layout/core`, no framework

## Contributing

Issues and pull requests are welcome; see [CONTRIBUTING.md](./CONTRIBUTING.md) and the [issue tracker](https://github.com/xcode-studio/dynamix-layout/issues).

---

## 🤝 Contributing

Contributions, issues, and feature requests are welcome\! Feel free to check the [issues page](https://github.com/xcode-studio/dynamix-layout/issues).

## 📝 License

This project is [MIT](./LICENSE) licensed.

---

<div align="center">

[![Patreon](https://img.shields.io/badge/Patreon-Support-F96854?style=for-the-badge&logo=patreon)](https://www.patreon.com/akashaman)
[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Donate-FFDD00?style=for-the-badge&logo=buy-me-a-coffee)](https://www.buymeacoffee.com/akashaman)
[![Hire Me](https://img.shields.io/badge/Hire%20Me-Email-blue?style=for-the-badge&logo=gmail)](mailto:sir.akashaman@gmail.com)

### Made with ❤️ by [Akash Aman](https://linktr.ee/akash_aman)

</div>
[MIT License](./LICENSE)
Loading
Loading