react-zeugma 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,119 +15,241 @@ Combines the tree-based, arbitrary splitting of [react-mosaic](https://github.co
15
15
 
16
16
  ---
17
17
 
18
- ## Features
19
-
20
- - **Recursive Tree Structure** Nest split rows and columns to any depth with a clean `TreeNode` data model.
21
- - **5-Zone Drag & Drop** — Drag panes onto top / bottom / left / right edges to split, or onto the center to swap.
22
- - **Flexbox Resizing** — Drag split bars to resize panes dynamically without heavyweight layout libraries.
23
- - **Declarative API** — State is stored in a serializable `TreeNode` object, making persistence and programmatic updates trivial.
24
- - **Built with dnd-kit** — Leverages the modern, accessible, and performant `@dnd-kit` drag-and-drop toolkit.
25
- - **Fullscreen Mode** — Any pane can expand to fill the entire viewport and collapse back seamlessly.
18
+ ## Introduction
19
+
20
+ **react-zeugma** is a recursive drag-and-drop dashboard layout engine for React. It combines the tree-based, arbitrary splitting of _react-mosaic_ with the declarative, state-driven API of _react-grid-layout_, built on top of [`@dnd-kit`](https://dndkit.com).
21
+
22
+ > **Headless Design System** — react-zeugma is entirely style-agnostic and relies on your class name configurations for styling visual states. You bring your own CSS/Tailwind rules, and we handle the complex drag-and-drop mechanics, resize handle math, and layout tree calculations.
23
+
24
+ ### Core Features
25
+
26
+ - **Recursive Split Trees** — Nest rows and columns to any depth using a simple serialized JSON node structure.
27
+ - **5-Zone Docking Previews** — Drag panels on the top, bottom, left, or right edges of another pane to split it, or onto the center to swap their positions.
28
+ - **Native Flexbox Resizers** — Fluid, non-blocking split handles built on pointer events.
29
+ - **Accessible Drag-and-Drop** — Built on top of the performant and accessible [`@dnd-kit`](https://dndkit.com) toolkit.
30
+ - **Fullscreen Zoom Toggle** — Programmatically expand any pane to cover the entire viewport and snap it back instantly.
26
31
  - **Tree-shakeable & Tiny** — ESM-first with zero runtime CSS. Bring your own styles.
27
32
 
28
33
  ---
29
34
 
30
35
  ## Installation
31
36
 
37
+ Install the package into your React project using your preferred package manager.
38
+
32
39
  ```bash
33
40
  npm install react-zeugma
34
41
  ```
35
42
 
36
- > **Peer dependencies:** React 18+ or 19+
43
+ > **Peer Dependencies:** react-zeugma is compatible with both **React 18** and **React 19** (along with matching `react-dom`).
37
44
 
38
45
  ---
39
46
 
40
47
  ## Quick Start
41
48
 
49
+ Import the core components and configure the layout state inside your React application.
50
+
42
51
  ```tsx
43
- import { useState } from 'react';
44
- import { DashboardProvider, PaneTree, Pane, DragHandle, TreeNode } from 'react-zeugma';
52
+ import { useState } from 'react'
53
+ import { DashboardProvider, PaneTree, Pane, DragHandle, TreeNode } from 'react-zeugma'
45
54
 
46
55
  const initialLayout: TreeNode = {
47
56
  type: 'split',
48
57
  direction: 'row',
49
- splitPercentage: 50,
50
- first: { type: 'pane', paneId: 'editor' },
58
+ splitPercentage: 20,
59
+ first: { type: 'pane', paneId: 'explorer' },
51
60
  second: {
52
61
  type: 'split',
53
- direction: 'column',
54
- splitPercentage: 60,
55
- first: { type: 'pane', paneId: 'preview' },
56
- second: { type: 'pane', paneId: 'console' },
62
+ direction: 'row',
63
+ splitPercentage: 50,
64
+ first: { type: 'pane', paneId: 'editor' },
65
+ second: { type: 'pane', paneId: 'preview' },
57
66
  },
58
- };
67
+ }
59
68
 
60
69
  function MyPane({ id }: { id: string }) {
61
70
  return (
62
71
  <Pane id={id}>
63
- {({ isDragging }) => (
64
- <div style={{ opacity: isDragging ? 0.5 : 1, height: '100%' }}>
72
+ {({ isDragging, remove }) => (
73
+ <div className={`h-full flex flex-col bg-[#18181b] ${isDragging ? 'opacity-30' : ''}`}>
65
74
  <DragHandle>
66
- <div style={{ padding: 8, cursor: 'grab', background: '#1e1e2e', color: '#cdd6f4' }}>
67
- {id}
75
+ <div className="px-3 py-2 bg-[#27272a] border-b border-[#3f3f46] flex items-center justify-between cursor-grab">
76
+ <span className="text-xs uppercase text-zinc-300 font-bold">{id}</span>
77
+ <button onClick={remove} className="text-zinc-500 hover:text-rose-400 text-xs">
78
+ ×
79
+ </button>
68
80
  </div>
69
81
  </DragHandle>
70
- <div style={{ padding: 16 }}>Content for {id}</div>
82
+ <div className="flex-1 p-4 text-sm text-zinc-400">Content for {id}</div>
71
83
  </div>
72
84
  )}
73
85
  </Pane>
74
- );
86
+ )
75
87
  }
76
88
 
77
89
  export default function Dashboard() {
78
- const [layout, setLayout] = useState<TreeNode | null>(initialLayout);
90
+ const [layout, setLayout] = useState<TreeNode | null>(initialLayout)
79
91
 
80
92
  return (
81
93
  <DashboardProvider layout={layout} onChange={setLayout} renderPane={(id) => <MyPane id={id} />}>
82
- <div style={{ width: '100vw', height: '100vh' }}>
94
+ <div className="w-screen h-screen">
83
95
  <PaneTree />
84
96
  </div>
85
97
  </DashboardProvider>
86
- );
98
+ )
87
99
  }
88
100
  ```
89
101
 
90
102
  ---
91
103
 
92
- ## API
104
+ ## API Reference
105
+
106
+ ### `<DashboardProvider>`
107
+
108
+ The context provider that sets up the drag-and-drop state machine, monitors active drags, and registers layout change notifications.
109
+
110
+ | Prop | Type | Required | Description |
111
+ | -------------------- | ------------------------------------ | -------- | ----------------------------------------------------------------------- |
112
+ | `layout` | `TreeNode \| null` | Yes | The serializable tree layout schema. |
113
+ | `onChange` | `(layout: TreeNode \| null) => void` | Yes | Fires when resizes, splits, swaps, or removes modify the tree. |
114
+ | `renderPane` | `(paneId: string) => ReactNode` | Yes | Renderer function lookup that returns a `<Pane>` structure. |
115
+ | `renderDragOverlay` | `(activeId: string) => ReactNode` | No | Renders a custom cursor-following drag preview. |
116
+ | `classNames` | `ZeugmaClassNames` | No | Custom classes for overriding pane, resizer, and drop preview overlays. |
117
+ | `fullscreenPaneId` | `string \| null` | No | Active ID of the pane taking full viewport coverage. |
118
+ | `onFullscreenChange` | `(paneId: string \| null) => void` | No | Callback triggered when a pane enters/leaves fullscreen. |
119
+ | `onRemove` | `(paneId: string) => void` | No | Callback triggered when a pane is closed/removed. |
120
+
121
+ ### `<PaneTree>`
122
+
123
+ Recursively renders the split nodes and pane nodes. Must be placed inside `<DashboardProvider>`.
124
+
125
+ | Prop | Type | Required | Description |
126
+ | ------------- | ------------------ | -------- | ------------------------------------------------------------------- |
127
+ | `tree` | `TreeNode \| null` | No | Custom subtree to render. Defaults to the provider's root `layout`. |
128
+ | `resizerSize` | `number` | No | Thickness of the split resizer bars in pixels. Defaults to `4`. |
93
129
 
94
- | Component | Description |
95
- | --------------------- | ------------------------------------------------------------------------------------------------- |
96
- | `<DashboardProvider>` | Root context provider. Accepts `layout`, `onChange`, `renderPane`, and optional class overrides. |
97
- | `<PaneTree />` | Recursively renders the tree layout. Place inside `DashboardProvider`. |
98
- | `<Pane id>` | Wraps individual pane content. Provides render props like `isDragging`, `isFullscreen`, `remove`. |
99
- | `<DragHandle>` | Designates the draggable area within a pane (typically the header/title bar). |
130
+ ### `<Pane id>`
100
131
 
101
- ### Utilities
132
+ Wraps the individual pane components inside the renderer. Utilizes a render prop passing active layout attributes.
102
133
 
103
- | Function | Description |
104
- | -------------------------- | ---------------------------------------------------- |
105
- | `removePane(tree, paneId)` | Returns a new tree with the specified pane removed. |
106
- | `splitPane(tree, ...)` | Programmatically splits a pane in a given direction. |
107
- | `swapPanes(tree, ...)` | Swaps two panes by their IDs. |
134
+ | Prop | Type | Required | Description |
135
+ | ---------- | --------------------------------------- | -------- | ------------------------------------------------------- |
136
+ | `id` | `string` | Yes | The unique ID corresponding to a `PaneNode`'s `paneId`. |
137
+ | `children` | `(props: PaneRenderProps) => ReactNode` | Yes | Render prop function. |
108
138
 
109
- ### Types
139
+ #### Render Props: `PaneRenderProps`
110
140
 
111
- | Type | Description |
112
- | ------------------ | ------------------------------------------------------------------ |
113
- | `TreeNode` | Union of `SplitNode \| PaneNode`. The core layout data structure. |
114
- | `SplitNode` | `{ type: 'split', direction, splitPercentage, first, second }` |
115
- | `PaneNode` | `{ type: 'pane', paneId }` |
116
- | `ZeugmaClassNames` | Optional CSS class overrides for pane, resizer, drop preview, etc. |
141
+ | Parameter | Type | Description |
142
+ | ------------------ | ------------ | ------------------------------------------------------------- |
143
+ | `isDragging` | `boolean` | Returns `true` if the node wrapper is actively being dragged. |
144
+ | `isFullscreen` | `boolean` | Returns `true` if the pane is zoomed/fullscreen. |
145
+ | `toggleFullscreen` | `() => void` | Callback to toggle fullscreen viewport coverage. |
146
+ | `remove` | `() => void` | Triggers removal of this pane from the layout tree. |
147
+
148
+ ### `<DragHandle>`
149
+
150
+ Defines the interactive drag region inside a `<Pane>`. **Must be placed inside a `<Pane>` component.**
151
+
152
+ | Prop | Type | Required | Description |
153
+ | ----------- | --------------------- | -------- | ---------------------------------------------------------------- |
154
+ | `children` | `React.ReactNode` | Yes | Element(s) that function as the drag handle (e.g., pane header). |
155
+ | `className` | `string` | No | Custom CSS class for the drag handle wrapper. |
156
+ | `style` | `React.CSSProperties` | No | Inline styles for the drag handle wrapper. |
117
157
 
118
158
  ---
119
159
 
120
- ## Documentation
160
+ ## Tree Utilities
121
161
 
122
- Run the interactive demo or Storybook locally:
162
+ react-zeugma exposes serializable tree utility functions for programmatically mutating layout schemas.
123
163
 
124
- ```bash
125
- npm run demo # Vite demo app
126
- npm run storybook # component docs & examples
164
+ #### `removePane(tree: TreeNode | null, id: string): TreeNode | null`
165
+
166
+ Recursively scans the layout tree, removes the targeted pane node, and collapses redundant split boundaries.
167
+
168
+ #### `addPane(tree: TreeNode | null, paneToAdd: string): TreeNode`
169
+
170
+ Recursively matches the bottommost/rightmost pane leaf in the tree, splits it, and inserts the target `paneToAdd`.
171
+
172
+ #### `swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null`
173
+
174
+ Swaps the positions of `idA` and `idB` nodes directly inside the tree structure.
175
+
176
+ #### `splitPane(tree, targetId, direction, splitType, paneToAdd)`
177
+
178
+ Splits the targeted `targetId` pane inside the tree with `direction` (_row_ / _column_) and type (_left_, _right_, _top_, _bottom_) to insert `paneToAdd`.
179
+
180
+ ---
181
+
182
+ ## Custom Styling
183
+
184
+ Use custom CSS or styling rules to style resizers, dragging states, drop previews, or active nodes by overriding `classNames` in the provider.
185
+
186
+ ```tsx
187
+ <DashboardProvider
188
+ layout={layout}
189
+ onChange={setLayout}
190
+ renderPane={renderPane}
191
+ classNames={{
192
+ // resizer handles
193
+ resizer:
194
+ 'bg-transparent hover:bg-indigo-500/50 active:bg-indigo-500 transition-colors duration-150',
195
+ // split previews
196
+ dropPreview: 'bg-indigo-500/10 border-2 border-dashed border-indigo-500/50 backdrop-blur-xs',
197
+ // swap previews
198
+ swapPreview: 'bg-amber-500/10 border-2 border-dashed border-amber-500/50 backdrop-blur-xs',
199
+ }}
200
+ >
201
+ <PaneTree />
202
+ </DashboardProvider>
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Types Reference
208
+
209
+ Full TypeScript type definitions utilized in the dashboard layout configuration.
210
+
211
+ ```ts
212
+ export type SplitDirection = 'row' | 'column'
213
+
214
+ export interface SplitNode {
215
+ type: 'split'
216
+ direction: SplitDirection
217
+ first: TreeNode
218
+ second: TreeNode
219
+ splitPercentage: number
220
+ }
221
+
222
+ export interface PaneNode {
223
+ type: 'pane'
224
+ paneId: string
225
+ }
226
+
227
+ export type TreeNode = SplitNode | PaneNode
228
+
229
+ export interface ZeugmaClassNames {
230
+ pane?: string
231
+ dropPreview?: string
232
+ swapPreview?: string
233
+ dragOverlay?: string
234
+ resizer?: string
235
+ }
236
+
237
+ export interface PaneRenderProps {
238
+ isDragging: boolean
239
+ isFullscreen: boolean
240
+ toggleFullscreen: () => void
241
+ remove: () => void
242
+ }
127
243
  ```
128
244
 
129
245
  ---
130
246
 
247
+ ## SKILL.md
248
+
249
+ A comprehensive developer skill configuration is published alongside the docs for AI agents and reference integrations. Download it from the [documentation site](https://react-zeugma.com/docs#skill-md).
250
+
251
+ ---
252
+
131
253
  ## Local Development
132
254
 
133
255
  ```bash
@@ -160,9 +282,10 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on how to get started.
160
282
 
161
283
  ---
162
284
 
163
- <div align="center">
285
+ ## The Story of Zeugma
164
286
 
165
- _Zeugma_ is an ancient Greco-Roman city on the Euphrates in Gaziantep, Turkey world-renowned for its breathtaking mosaic panels unearthed during excavations.
166
- This library draws its name from those mosaics: **many tiles, one masterpiece.**
287
+ _Zeugma_ is an ancient city of Commagene, located in modern-day **Gaziantep, Turkey**. Positioned along a critical crossing point of the Euphrates river, Zeugma became a central hub of trade and cultural exchanges.
167
288
 
168
- </div>
289
+ During modern excavation efforts, archeologists discovered some of the most breathtaking Greco-Roman mosaic panels in history, now housed inside the **Zeugma Mosaic Museum** in Gaziantep. The famous _"Gypsy Girl" (Çingene Kızı)_ mosaic, with her hauntingly detailed eyes, has become a global icon of the city.
290
+
291
+ > _"We chose the name Zeugma because of this ancient craftsmanship. Mosaics are assembled from hundreds of tiny, individual tesserae tiles to form a magnificent, cohesive picture. In the same spirit, react-zeugma lets you build beautiful, customized application workspaces from simple, individual components. Many tiles, one masterpiece."_
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var react=require('react'),core=require('@dnd-kit/core'),jsxRuntime=require('react/jsx-runtime');var M=react.createContext(void 0),D=()=>{let e=react.useContext(M);if(!e)throw new Error("useDashboard must be used within a DashboardProvider");return e};function y(e,t){if(e===null)return null;if(e.type==="pane")return e.paneId===t?null:e;let n=y(e.first,t),o=y(e.second,t);return n===null?o:o===null?n:{...e,first:n,second:o}}function E(e,t,n,o,r){if(e===null)return {type:"pane",paneId:r};if(e.type==="pane"){if(e.paneId===t){let i={type:"pane",paneId:r},c={type:"pane",paneId:t},s=o==="left"||o==="top";return {type:"split",direction:n,first:s?i:c,second:s?c:i,splitPercentage:50}}return e}return {...e,first:E(e.first,t,n,o,r)||e.first,second:E(e.second,t,n,o,r)||e.second}}function z(e,t,n){return e===null?null:e.type==="pane"?e.paneId===t?{...e,paneId:n}:e.paneId===n?{...e,paneId:t}:e:{...e,first:z(e.first,t,n)||e.first,second:z(e.second,t,n)||e.second}}var _=({activeId:e,render:t,className:n})=>{let o=react.useRef(null);return react.useEffect(()=>{let r=i=>{o.current&&(o.current.style.transform=`translate(${i.clientX+12}px, ${i.clientY+12}px)`);};return document.addEventListener("pointermove",r),()=>document.removeEventListener("pointermove",r)},[]),jsxRuntime.jsx("div",{ref:o,className:n,style:{position:"fixed",top:0,left:0,zIndex:9999,pointerEvents:"none"},children:t(e)})},B=({layout:e,onChange:t,renderPane:n,renderDragOverlay:o,classNames:r={},fullscreenPaneId:i=null,onFullscreenChange:c,onRemove:s,children:a})=>{let[v,m]=react.useState(null),x=core.useSensors(core.useSensor(core.PointerSensor,{activationConstraint:{distance:8}})),p=u=>{m(u.active.id.toString());},l=u=>{m(null);let{active:f,over:d}=u;if(!d)return;let g=f.id.toString(),w=d.id.toString(),I=w.match(/^drop-center-(.+)$/);if(I){let[,S]=I;g!==S&&t(z(e,g,S));return}let R=w.match(/^drop-(left|right|top|bottom)-(.+)$/);if(!R)return;let[,P,N]=R;if(g===N)return;let C=P==="left"||P==="right"?"row":"column",L=y(e,g),F=E(L,N,C,P,g);t(F);};return jsxRuntime.jsxs(M.Provider,{value:{layout:e,onLayoutChange:t,renderPane:n,activeId:v,fullscreenPaneId:i,classNames:r,onRemove:s,onFullscreenChange:c},children:[jsxRuntime.jsx(core.DndContext,{sensors:x,collisionDetection:core.pointerWithin,onDragStart:p,onDragEnd:l,children:a}),v&&o&&jsxRuntime.jsx(_,{activeId:v,render:o,className:r.dragOverlay})]})};function $(e,t,n){return e===null?null:e===t?{...e,splitPercentage:n}:e.type==="split"?{...e,first:$(e.first,t,n)||e.first,second:$(e.second,t,n)||e.second}:e}var Z=({tree:e,resizerSize:t=4})=>{let{layout:n,onLayoutChange:o,renderPane:r,fullscreenPaneId:i,classNames:c}=D(),s=react.useRef(null);if(i&&!e)return jsxRuntime.jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(i)});let a=e!==void 0?e:n;if(!a)return null;if(a.type==="pane")return jsxRuntime.jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(a.paneId)});let{direction:v,first:m,second:x,splitPercentage:p}=a,l=v==="row",u=f=>{f.preventDefault();let d=s.current;if(!d)return;let g=d.getBoundingClientRect(),w=f.clientX,I=f.clientY,R=p,P=C=>{let L=l?(C.clientX-w)/g.width*100:(C.clientY-I)/g.height*100,F=Math.max(5,Math.min(95,R+L)),S=$(n,a,F);o(S);},N=()=>{document.removeEventListener("pointermove",P),document.removeEventListener("pointerup",N);};document.addEventListener("pointermove",P),document.addEventListener("pointerup",N);};return jsxRuntime.jsxs("div",{ref:s,style:{display:"flex",flexDirection:l?"row":"column",width:"100%",height:"100%",overflow:"hidden"},children:[jsxRuntime.jsx("div",{style:{flex:`${p} 1 0%`,overflow:"hidden"},children:jsxRuntime.jsx(Z,{tree:m,resizerSize:t})}),jsxRuntime.jsx("div",{className:c.resizer,style:{width:l?`${t}px`:"100%",height:l?"100%":`${t}px`,cursor:l?"col-resize":"row-resize",position:"relative",zIndex:10,userSelect:"none",boxSizing:"border-box",flexShrink:0},onPointerDown:u,role:"separator","aria-valuenow":p,"aria-valuemin":5,"aria-valuemax":95}),jsxRuntime.jsx("div",{style:{flex:`${100-p} 1 0%`,overflow:"hidden"},children:jsxRuntime.jsx(Z,{tree:x,resizerSize:t})})]})};var X=react.createContext(null),ie={top:{position:"absolute",top:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},bottom:{position:"absolute",bottom:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},left:{position:"absolute",top:0,bottom:0,left:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},right:{position:"absolute",top:0,bottom:0,right:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},center:{position:"absolute",top:"25%",left:"25%",width:"50%",height:"50%",zIndex:20,pointerEvents:"auto"}},se={top:{position:"absolute",top:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},bottom:{position:"absolute",bottom:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},left:{position:"absolute",top:0,bottom:0,left:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},right:{position:"absolute",top:0,bottom:0,right:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},center:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:21,pointerEvents:"none",boxSizing:"border-box"}},O=({id:e,position:t,activeClassName:n})=>{let{setNodeRef:o,isOver:r}=core.useDroppable({id:e});return jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx("div",{ref:o,style:ie[t]}),r&&jsxRuntime.jsx("div",{className:n,style:se[t]})]})},ae=({id:e,children:t,style:n})=>{let{activeId:o,classNames:r,fullscreenPaneId:i,onRemove:c,onFullscreenChange:s}=D(),a=o!==null&&o!==e,{attributes:v,listeners:m,setNodeRef:x,isDragging:p}=core.useDraggable({id:e}),l=o===e||p,u=i===e,f={isDragging:l,isFullscreen:u,toggleFullscreen:()=>s?.(u?null:e),remove:()=>c?.(e)};return jsxRuntime.jsx(X.Provider,{value:{...m,...v},children:jsxRuntime.jsxs("div",{ref:x,className:r.pane,style:{position:"relative",width:"100%",height:"100%",...n},children:[t(f),a&&jsxRuntime.jsxs("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:15,pointerEvents:"none"},children:[["top","bottom","left","right"].map(d=>jsxRuntime.jsx(O,{id:`drop-${d}-${e}`,position:d,activeClassName:r.dropPreview},d)),jsxRuntime.jsx(O,{id:`drop-center-${e}`,position:"center",activeClassName:r.swapPreview})]})]})})},le=({children:e,className:t,style:n})=>{let o=react.useContext(X);if(!o)throw new Error("<DragHandle> must be used inside a <Pane>");return jsxRuntime.jsx("div",{className:t,style:{cursor:"grab",userSelect:"none",...n},...o,children:e})};exports.DashboardProvider=B;exports.DragHandle=le;exports.Pane=ae;exports.PaneTree=Z;exports.removePane=y;exports.splitPane=E;exports.swapPanes=z;exports.useDashboard=D;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';var react=require('react'),core=require('@dnd-kit/core'),jsxRuntime=require('react/jsx-runtime');var O=react.createContext(void 0),I=()=>{let e=react.useContext(O);if(!e)throw new Error("useDashboard must be used within a DashboardProvider");return e};function S(e,t){if(e===null)return null;if(e.type==="pane")return e.paneId===t?null:e;let n=S(e.first,t),o=S(e.second,t);return n===null?o:o===null?n:{...e,first:n,second:o}}function C(e,t,n,o,r){if(e===null)return {type:"pane",paneId:r};if(e.type==="pane"){if(e.paneId===t){let i={type:"pane",paneId:r},c={type:"pane",paneId:t},s=o==="left"||o==="top";return {type:"split",direction:n,first:s?i:c,second:s?c:i,splitPercentage:50}}return e}return {...e,first:C(e.first,t,n,o,r)||e.first,second:C(e.second,t,n,o,r)||e.second}}function E(e,t,n){return e===null?null:e.type==="pane"?e.paneId===t?{...e,paneId:n}:e.paneId===n?{...e,paneId:t}:e:{...e,first:E(e.first,t,n)||e.first,second:E(e.second,t,n)||e.second}}function B(e,t){if(e===null)return {type:"pane",paneId:t};function n(o,r){return o.type==="pane"?{type:"split",direction:r==="row"?"column":"row",splitPercentage:50,first:o,second:{type:"pane",paneId:t}}:{...o,second:n(o.second,o.direction)}}return n(e,null)}var j=({activeId:e,render:t,className:n})=>{let o=react.useRef(null);return react.useEffect(()=>{let r=i=>{o.current&&(o.current.style.transform=`translate(${i.clientX+12}px, ${i.clientY+12}px)`);};return document.addEventListener("pointermove",r),()=>document.removeEventListener("pointermove",r)},[]),jsxRuntime.jsx("div",{ref:o,className:n,style:{position:"fixed",top:0,left:0,zIndex:9999,pointerEvents:"none"},children:t(e)})},A=({layout:e,onChange:t,renderPane:n,renderDragOverlay:o,classNames:r={},fullscreenPaneId:i=null,onFullscreenChange:c,onRemove:s,dragActivationDistance:a=8,children:x})=>{let[g,h]=react.useState(null),p=core.useSensors(core.useSensor(core.PointerSensor,{activationConstraint:{distance:a}})),l=d=>{h(d.active.id.toString());},f=d=>{h(null);let{active:u,over:N}=d;if(!N)return;let v=u.id.toString(),y=N.id.toString(),R=y.match(/^drop-center-(.+)$/);if(R){let[,M]=R;v!==M&&t(E(e,v,M));return}let D=y.match(/^drop-(left|right|top|bottom)-(.+)$/);if(!D)return;let[,P,w]=D;if(v===w)return;let z=P==="left"||P==="right"?"row":"column",L=S(e,v),T=C(L,w,z,P,v);t(T);};return jsxRuntime.jsxs(O.Provider,{value:{layout:e,onLayoutChange:t,renderPane:n,activeId:g,fullscreenPaneId:i,classNames:r,onRemove:s,onFullscreenChange:c},children:[jsxRuntime.jsx(core.DndContext,{sensors:p,collisionDetection:core.pointerWithin,onDragStart:l,onDragEnd:f,children:x}),g&&o&&jsxRuntime.jsx(j,{activeId:g,render:o,className:r.dragOverlay})]})};function $(e,t,n){return e===null?null:e===t?{...e,splitPercentage:n}:e.type==="split"?{...e,first:$(e.first,t,n)||e.first,second:$(e.second,t,n)||e.second}:e}var Z=({tree:e,resizerSize:t=4})=>{let{layout:n,onLayoutChange:o,renderPane:r,fullscreenPaneId:i,classNames:c}=I(),s=react.useRef(null);if(i&&!e)return jsxRuntime.jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(i)});let a=e!==void 0?e:n;if(!a)return null;if(a.type==="pane")return jsxRuntime.jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(a.paneId)});let{direction:x,first:g,second:h,splitPercentage:p}=a,l=x==="row",f=d=>{d.preventDefault();let u=s.current;if(!u)return;document.body.classList.add("zeugma-resizing");let N=u.getBoundingClientRect(),v=d.clientX,y=d.clientY,R=p,D=w=>{let z=l?(w.clientX-v)/N.width*100:(w.clientY-y)/N.height*100,L=Math.max(5,Math.min(95,R+z)),T=$(n,a,L);o(T);},P=()=>{document.body.classList.remove("zeugma-resizing"),document.removeEventListener("pointermove",D),document.removeEventListener("pointerup",P);};document.addEventListener("pointermove",D),document.addEventListener("pointerup",P);};return jsxRuntime.jsxs("div",{ref:s,style:{display:"flex",flexDirection:l?"row":"column",width:"100%",height:"100%",overflow:"hidden"},children:[jsxRuntime.jsx("div",{style:{flex:`${p} 1 0%`,overflow:"hidden"},children:jsxRuntime.jsx(Z,{tree:g,resizerSize:t})}),jsxRuntime.jsx("div",{className:c.resizer,style:{width:l?`${t}px`:"100%",height:l?"100%":`${t}px`,cursor:l?"col-resize":"row-resize",position:"relative",zIndex:10,userSelect:"none",boxSizing:"border-box",flexShrink:0},onPointerDown:f,role:"separator","aria-valuenow":p,"aria-valuemin":5,"aria-valuemax":95}),jsxRuntime.jsx("div",{style:{flex:`${100-p} 1 0%`,overflow:"hidden"},children:jsxRuntime.jsx(Z,{tree:h,resizerSize:t})})]})};var Y=react.createContext(null),ae={top:{position:"absolute",top:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},bottom:{position:"absolute",bottom:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},left:{position:"absolute",top:0,bottom:0,left:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},right:{position:"absolute",top:0,bottom:0,right:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},center:{position:"absolute",top:"25%",left:"25%",width:"50%",height:"50%",zIndex:20,pointerEvents:"auto"}},le={top:{position:"absolute",top:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},bottom:{position:"absolute",bottom:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},left:{position:"absolute",top:0,bottom:0,left:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},right:{position:"absolute",top:0,bottom:0,right:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},center:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:21,pointerEvents:"none",boxSizing:"border-box"}},X=({id:e,position:t,activeClassName:n})=>{let{setNodeRef:o,isOver:r}=core.useDroppable({id:e});return jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx("div",{ref:o,style:ae[t]}),r&&jsxRuntime.jsx("div",{className:n,style:le[t]})]})},de=({id:e,children:t,style:n})=>{let{activeId:o,classNames:r,fullscreenPaneId:i,onRemove:c,onFullscreenChange:s}=I(),a=o!==null&&o!==e,{attributes:x,listeners:g,setNodeRef:h,isDragging:p}=core.useDraggable({id:e}),l=o===e||p,f=i===e,d={isDragging:l,isFullscreen:f,toggleFullscreen:()=>s?.(f?null:e),remove:()=>{f&&s?.(null),c?.(e);}};return jsxRuntime.jsx(Y.Provider,{value:{...g,...x},children:jsxRuntime.jsxs("div",{ref:h,className:r.pane,style:{position:"relative",width:"100%",height:"100%",...n},children:[t(d),a&&jsxRuntime.jsxs("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:15,pointerEvents:"none"},children:[["top","bottom","left","right"].map(u=>jsxRuntime.jsx(X,{id:`drop-${u}-${e}`,position:u,activeClassName:r.dropPreview},u)),jsxRuntime.jsx(X,{id:`drop-center-${e}`,position:"center",activeClassName:r.swapPreview})]})]})})},ce=({children:e,className:t,style:n})=>{let o=react.useContext(Y);if(!o)throw new Error("<DragHandle> must be used inside a <Pane>");return jsxRuntime.jsx("div",{className:t,style:{cursor:"grab",userSelect:"none",...n},...o,children:e})};exports.DashboardProvider=A;exports.DragHandle=ce;exports.Pane=de;exports.PaneTree=Z;exports.addPane=B;exports.removePane=S;exports.splitPane=C;exports.swapPanes=E;exports.useDashboard=I;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/dashboard-provider.tsx","../src/components/pane-tree.tsx","../src/components/pane.tsx"],"names":["DashboardContext","createContext","useDashboard","context","useContext","removePane","tree","idToRemove","newFirst","newSecond","splitPane","targetId","direction","splitType","paneToAdd","addedNode","originalNode","isFirst","swapPanes","idA","idB","CursorOverlay","activeId","render","className","ref","useRef","useEffect","handleMove","e","jsx","DashboardProvider","layout","onChange","renderPane","renderDragOverlay","classNames","fullscreenPaneId","onFullscreenChange","onRemove","children","setActiveId","useState","sensors","useSensors","useSensor","PointerSensor","handleDragStart","event","handleDragEnd","active","over","draggingId","overIdStr","swapMatch","match","dropZone","treeWithoutDragging","newLayout","jsxs","DndContext","pointerWithin","updateSplitPercentage","target","newPercentage","PaneTree","resizerSize","onLayoutChange","containerRef","currentNode","first","second","splitPercentage","isRow","handlePointerDown","container","rect","startX","startY","startPercentage","handlePointerMove","moveEvent","delta","handlePointerUp","DragListenersCtx","activationPositions","previewPositions","DropZone","id","position","activeClassName","setNodeRef","isOver","useDroppable","Fragment","Pane","style","showDropZones","attributes","listeners","isDragging","useDraggable","dragging","isFullscreen","renderProps","pos","DragHandle","dragProps"],"mappings":"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","file":"index.cjs","sourcesContent":["import React, { createContext, useContext, useState, useEffect, useRef, ReactNode } from 'react';\nimport {\n DndContext,\n useSensor,\n useSensors,\n PointerSensor,\n DragStartEvent,\n DragEndEvent,\n pointerWithin,\n} from '@dnd-kit/core';\nimport { TreeNode, SplitDirection, PaneNode } from '../types';\n\nexport interface ZeugmaClassNames {\n pane?: string;\n dropPreview?: string;\n swapPreview?: string;\n dragOverlay?: string;\n resizer?: string;\n}\n\nexport interface DashboardContextValue {\n layout: TreeNode | null;\n onLayoutChange: (newLayout: TreeNode | null) => void;\n renderPane: (paneId: string) => ReactNode;\n activeId: string | null;\n fullscreenPaneId: string | null;\n classNames: ZeugmaClassNames;\n onRemove?: (paneId: string) => void;\n onFullscreenChange?: (paneId: string | null) => void;\n}\n\nexport const DashboardContext = createContext<DashboardContextValue | undefined>(undefined);\n\nexport const useDashboard = () => {\n const context = useContext(DashboardContext);\n if (!context) {\n throw new Error('useDashboard must be used within a DashboardProvider');\n }\n return context;\n};\n\n// Tree Helper: Remove a pane and consolidate the tree\nexport function removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null {\n if (tree === null) return null;\n if (tree.type === 'pane') {\n return tree.paneId === idToRemove ? null : tree;\n }\n const newFirst = removePane(tree.first, idToRemove);\n const newSecond = removePane(tree.second, idToRemove);\n if (newFirst === null) return newSecond;\n if (newSecond === null) return newFirst;\n return { ...tree, first: newFirst, second: newSecond };\n}\n\n// Tree Helper: Insert a pane by splitting an existing target\nexport function splitPane(\n tree: TreeNode | null,\n targetId: string,\n direction: SplitDirection,\n splitType: 'left' | 'right' | 'top' | 'bottom',\n paneToAdd: string\n): TreeNode | null {\n if (tree === null) return { type: 'pane', paneId: paneToAdd };\n if (tree.type === 'pane') {\n if (tree.paneId === targetId) {\n const addedNode: PaneNode = { type: 'pane', paneId: paneToAdd };\n const originalNode: PaneNode = { type: 'pane', paneId: targetId };\n const isFirst = splitType === 'left' || splitType === 'top';\n return {\n type: 'split',\n direction,\n first: isFirst ? addedNode : originalNode,\n second: isFirst ? originalNode : addedNode,\n splitPercentage: 50,\n };\n }\n return tree;\n }\n return {\n ...tree,\n first: splitPane(tree.first, targetId, direction, splitType, paneToAdd) || tree.first,\n second: splitPane(tree.second, targetId, direction, splitType, paneToAdd) || tree.second,\n };\n}\n\n// Tree Helper: Swap two pane positions in the tree\nexport function swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null {\n if (tree === null) return null;\n if (tree.type === 'pane') {\n if (tree.paneId === idA) return { ...tree, paneId: idB };\n if (tree.paneId === idB) return { ...tree, paneId: idA };\n return tree;\n }\n return {\n ...tree,\n first: swapPanes(tree.first, idA, idB) || tree.first,\n second: swapPanes(tree.second, idA, idB) || tree.second,\n };\n}\n\n/** Cursor-following overlay rendered via portal */\nconst CursorOverlay: React.FC<{ activeId: string; render: (id: string) => ReactNode; className?: string }> = ({\n activeId,\n render,\n className,\n}) => {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const handleMove = (e: PointerEvent) => {\n if (ref.current) {\n ref.current.style.transform = `translate(${e.clientX + 12}px, ${e.clientY + 12}px)`;\n }\n };\n document.addEventListener('pointermove', handleMove);\n return () => document.removeEventListener('pointermove', handleMove);\n }, []);\n\n return (\n <div\n ref={ref}\n className={className}\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n zIndex: 9999,\n pointerEvents: 'none',\n }}\n >\n {render(activeId)}\n </div>\n );\n};\n\ninterface DashboardProviderProps {\n layout: TreeNode | null;\n onChange: (newLayout: TreeNode | null) => void;\n renderPane: (paneId: string) => ReactNode;\n renderDragOverlay?: (activeId: string) => ReactNode;\n classNames?: ZeugmaClassNames;\n fullscreenPaneId?: string | null;\n onFullscreenChange?: (paneId: string | null) => void;\n onRemove?: (paneId: string) => void;\n children: ReactNode;\n}\n\nexport const DashboardProvider: React.FC<DashboardProviderProps> = ({\n layout,\n onChange,\n renderPane,\n renderDragOverlay,\n classNames = {},\n fullscreenPaneId = null,\n onFullscreenChange,\n onRemove,\n children,\n}) => {\n const [activeId, setActiveId] = useState<string | null>(null);\n\n const sensors = useSensors(\n useSensor(PointerSensor, {\n activationConstraint: { distance: 8 },\n })\n );\n\n const handleDragStart = (event: DragStartEvent) => {\n setActiveId(event.active.id.toString());\n };\n\n const handleDragEnd = (event: DragEndEvent) => {\n setActiveId(null);\n const { active, over } = event;\n if (!over) return;\n\n const draggingId = active.id.toString();\n const overIdStr = over.id.toString();\n\n // Check for center (swap) drop\n const swapMatch = overIdStr.match(/^drop-center-(.+)$/);\n if (swapMatch) {\n const [, targetId] = swapMatch;\n if (draggingId !== targetId) {\n onChange(swapPanes(layout, draggingId, targetId));\n }\n return;\n }\n\n // Check for edge (split) drop\n const match = overIdStr.match(/^drop-(left|right|top|bottom)-(.+)$/);\n if (!match) return;\n\n const [, dropZone, targetId] = match;\n if (draggingId === targetId) return;\n\n const direction: SplitDirection = (dropZone === 'left' || dropZone === 'right') ? 'row' : 'column';\n const treeWithoutDragging = removePane(layout, draggingId);\n\n const newLayout = splitPane(\n treeWithoutDragging,\n targetId,\n direction,\n dropZone as 'left' | 'right' | 'top' | 'bottom',\n draggingId\n );\n onChange(newLayout);\n };\n\n return (\n <DashboardContext.Provider\n value={{\n layout,\n onLayoutChange: onChange,\n renderPane,\n activeId,\n fullscreenPaneId,\n classNames,\n onRemove,\n onFullscreenChange,\n }}\n >\n <DndContext sensors={sensors} collisionDetection={pointerWithin} onDragStart={handleDragStart} onDragEnd={handleDragEnd}>\n {children}\n </DndContext>\n {activeId && renderDragOverlay && (\n <CursorOverlay activeId={activeId} render={renderDragOverlay} className={classNames.dragOverlay} />\n )}\n </DashboardContext.Provider>\n );\n};\n","import React, { useRef } from 'react';\nimport { useDashboard } from './dashboard-provider';\nimport { TreeNode, SplitNode } from '../types';\n\ninterface PaneTreeProps {\n tree?: TreeNode | null;\n /** Size of the resizer in pixels (default 4) */\n resizerSize?: number;\n}\n\nfunction updateSplitPercentage(tree: TreeNode | null, target: SplitNode, newPercentage: number): TreeNode | null {\n if (tree === null) return null;\n if (tree === target) {\n return { ...tree, splitPercentage: newPercentage } as SplitNode;\n }\n if (tree.type === 'split') {\n return {\n ...tree,\n first: updateSplitPercentage(tree.first, target, newPercentage) || tree.first,\n second: updateSplitPercentage(tree.second, target, newPercentage) || tree.second,\n };\n }\n return tree;\n}\n\nexport const PaneTree: React.FC<PaneTreeProps> = ({ tree, resizerSize = 4 }) => {\n const { layout, onLayoutChange, renderPane, fullscreenPaneId, classNames } = useDashboard();\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Fullscreen bypass\n if (fullscreenPaneId && !tree) {\n return (\n <div style={{ width: '100%', height: '100%', position: 'relative' }}>\n {renderPane(fullscreenPaneId)}\n </div>\n );\n }\n\n const currentNode = tree !== undefined ? tree : layout;\n\n if (!currentNode) return null;\n\n if (currentNode.type === 'pane') {\n return (\n <div style={{ width: '100%', height: '100%', position: 'relative' }}>\n {renderPane(currentNode.paneId)}\n </div>\n );\n }\n\n const { direction, first, second, splitPercentage } = currentNode;\n const isRow = direction === 'row';\n\n const handlePointerDown = (e: React.PointerEvent) => {\n e.preventDefault();\n const container = containerRef.current;\n if (!container) return;\n\n const rect = container.getBoundingClientRect();\n const startX = e.clientX;\n const startY = e.clientY;\n const startPercentage = splitPercentage;\n\n const handlePointerMove = (moveEvent: PointerEvent) => {\n const delta = isRow\n ? ((moveEvent.clientX - startX) / rect.width) * 100\n : ((moveEvent.clientY - startY) / rect.height) * 100;\n const newPercentage = Math.max(5, Math.min(95, startPercentage + delta));\n const newLayout = updateSplitPercentage(layout, currentNode, newPercentage);\n onLayoutChange(newLayout);\n };\n\n const handlePointerUp = () => {\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n };\n\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n };\n\n return (\n <div\n ref={containerRef}\n style={{ display: 'flex', flexDirection: isRow ? 'row' : 'column', width: '100%', height: '100%', overflow: 'hidden' }}\n >\n <div style={{ flex: `${splitPercentage} 1 0%`, overflow: 'hidden' }}>\n <PaneTree tree={first} resizerSize={resizerSize} />\n </div>\n <div\n className={classNames.resizer}\n style={{\n width: isRow ? `${resizerSize}px` : '100%',\n height: isRow ? '100%' : `${resizerSize}px`,\n cursor: isRow ? 'col-resize' : 'row-resize',\n position: 'relative',\n zIndex: 10,\n userSelect: 'none',\n boxSizing: 'border-box',\n flexShrink: 0,\n }}\n onPointerDown={handlePointerDown}\n role=\"separator\"\n aria-valuenow={splitPercentage}\n aria-valuemin={5}\n aria-valuemax={95}\n />\n <div style={{ flex: `${100 - splitPercentage} 1 0%`, overflow: 'hidden' }}>\n <PaneTree tree={second} resizerSize={resizerSize} />\n </div>\n </div>\n );\n};\n","import React, { createContext, useContext } from 'react';\nimport { useDraggable, useDroppable } from '@dnd-kit/core';\nimport { useDashboard } from './dashboard-provider';\n\n// Internal context for drag listeners\nconst DragListenersCtx = createContext<Record<string, unknown> | null>(null);\n\ninterface DropZoneProps {\n id: string;\n position: 'top' | 'bottom' | 'left' | 'right' | 'center';\n activeClassName?: string;\n}\n\nconst activationPositions: Record<string, React.CSSProperties> = {\n top: { position: 'absolute', top: 0, left: '25%', width: '50%', height: '25%', zIndex: 20, pointerEvents: 'auto' },\n bottom: { position: 'absolute', bottom: 0, left: '25%', width: '50%', height: '25%', zIndex: 20, pointerEvents: 'auto' },\n left: { position: 'absolute', top: 0, bottom: 0, left: 0, width: '25%', height: '100%', zIndex: 20, pointerEvents: 'auto' },\n right: { position: 'absolute', top: 0, bottom: 0, right: 0, width: '25%', height: '100%', zIndex: 20, pointerEvents: 'auto' },\n center: { position: 'absolute', top: '25%', left: '25%', width: '50%', height: '50%', zIndex: 20, pointerEvents: 'auto' },\n};\n\nconst previewPositions: Record<string, React.CSSProperties> = {\n top: { position: 'absolute', top: 0, left: 0, right: 0, height: '50%', zIndex: 21, pointerEvents: 'none', boxSizing: 'border-box' },\n bottom: { position: 'absolute', bottom: 0, left: 0, right: 0, height: '50%', zIndex: 21, pointerEvents: 'none', boxSizing: 'border-box' },\n left: { position: 'absolute', top: 0, bottom: 0, left: 0, width: '50%', zIndex: 21, pointerEvents: 'none', boxSizing: 'border-box' },\n right: { position: 'absolute', top: 0, bottom: 0, right: 0, width: '50%', zIndex: 21, pointerEvents: 'none', boxSizing: 'border-box' },\n center: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, zIndex: 21, pointerEvents: 'none', boxSizing: 'border-box' },\n};\n\nconst DropZone: React.FC<DropZoneProps> = ({ id, position, activeClassName }) => {\n const { setNodeRef, isOver } = useDroppable({ id });\n return (\n <>\n <div ref={setNodeRef} style={activationPositions[position]} />\n {isOver && <div className={activeClassName} style={previewPositions[position]} />}\n </>\n );\n};\n\nexport interface PaneRenderProps {\n isDragging: boolean;\n isFullscreen: boolean;\n toggleFullscreen: () => void;\n remove: () => void;\n}\n\ninterface PaneProps {\n id: string;\n children: (props: PaneRenderProps) => React.ReactNode;\n style?: React.CSSProperties;\n}\n\nexport const Pane: React.FC<PaneProps> = ({ id, children, style }) => {\n const { activeId, classNames, fullscreenPaneId, onRemove, onFullscreenChange } = useDashboard();\n const showDropZones = activeId !== null && activeId !== id;\n\n const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id });\n const dragging = activeId === id || isDragging;\n const isFullscreen = fullscreenPaneId === id;\n\n const renderProps: PaneRenderProps = {\n isDragging: dragging,\n isFullscreen,\n toggleFullscreen: () => onFullscreenChange?.(isFullscreen ? null : id),\n remove: () => onRemove?.(id),\n };\n\n return (\n <DragListenersCtx.Provider value={{ ...listeners, ...attributes }}>\n <div\n ref={setNodeRef}\n className={classNames.pane}\n style={{ position: 'relative', width: '100%', height: '100%', ...style }}\n >\n {children(renderProps)}\n\n {showDropZones && (\n <div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, zIndex: 15, pointerEvents: 'none' }}>\n {(['top', 'bottom', 'left', 'right'] as const).map((pos) => (\n <DropZone\n key={pos}\n id={`drop-${pos}-${id}`}\n position={pos}\n activeClassName={classNames.dropPreview}\n />\n ))}\n <DropZone\n id={`drop-center-${id}`}\n position=\"center\"\n activeClassName={classNames.swapPreview}\n />\n </div>\n )}\n </div>\n </DragListenersCtx.Provider>\n );\n};\n\n/**\n * Place inside a Pane to make an element the drag handle.\n */\ninterface DragHandleProps {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport const DragHandle: React.FC<DragHandleProps> = ({ children, className, style }) => {\n const dragProps = useContext(DragListenersCtx);\n if (!dragProps) {\n throw new Error('<DragHandle> must be used inside a <Pane>');\n }\n return (\n <div className={className} style={{ cursor: 'grab', userSelect: 'none', ...style }} {...dragProps}>\n {children}\n </div>\n );\n};\n"]}
1
+ {"version":3,"sources":["../src/components/dashboard-provider.tsx","../src/components/pane-tree.tsx","../src/components/pane.tsx"],"names":["DashboardContext","createContext","useDashboard","context","useContext","removePane","tree","idToRemove","newFirst","newSecond","splitPane","targetId","direction","splitType","paneToAdd","addedNode","originalNode","isFirst","swapPanes","idA","idB","addPane","insert","node","parentDirection","CursorOverlay","activeId","render","className","ref","useRef","useEffect","handleMove","e","jsx","DashboardProvider","layout","onChange","renderPane","renderDragOverlay","classNames","fullscreenPaneId","onFullscreenChange","onRemove","dragActivationDistance","children","setActiveId","useState","sensors","useSensors","useSensor","PointerSensor","handleDragStart","event","handleDragEnd","active","over","draggingId","overIdStr","swapMatch","match","dropZone","treeWithoutDragging","newLayout","jsxs","DndContext","pointerWithin","updateSplitPercentage","target","newPercentage","PaneTree","resizerSize","onLayoutChange","containerRef","currentNode","first","second","splitPercentage","isRow","handlePointerDown","container","rect","startX","startY","startPercentage","handlePointerMove","moveEvent","delta","handlePointerUp","DragListenersCtx","activationPositions","previewPositions","DropZone","id","position","activeClassName","setNodeRef","isOver","useDroppable","Fragment","Pane","style","showDropZones","attributes","listeners","isDragging","useDraggable","dragging","isFullscreen","renderProps","pos","DragHandle","dragProps"],"mappings":"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","file":"index.cjs","sourcesContent":["import React, { createContext, useContext, useState, useEffect, useRef, ReactNode } from 'react'\nimport {\n DndContext,\n useSensor,\n useSensors,\n PointerSensor,\n DragStartEvent,\n DragEndEvent,\n pointerWithin,\n} from '@dnd-kit/core'\nimport { TreeNode, SplitDirection, PaneNode } from '../types'\n\nexport interface ZeugmaClassNames {\n pane?: string\n dropPreview?: string\n swapPreview?: string\n dragOverlay?: string\n resizer?: string\n}\n\nexport interface DashboardContextValue {\n layout: TreeNode | null\n onLayoutChange: (newLayout: TreeNode | null) => void\n renderPane: (paneId: string) => ReactNode\n activeId: string | null\n fullscreenPaneId: string | null\n classNames: ZeugmaClassNames\n onRemove?: (paneId: string) => void\n onFullscreenChange?: (paneId: string | null) => void\n}\n\nexport const DashboardContext = createContext<DashboardContextValue | undefined>(undefined)\n\nexport const useDashboard = () => {\n const context = useContext(DashboardContext)\n if (!context) {\n throw new Error('useDashboard must be used within a DashboardProvider')\n }\n return context\n}\n\n// Tree Helper: Remove a pane and consolidate the tree\nexport function removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n return tree.paneId === idToRemove ? null : tree\n }\n const newFirst = removePane(tree.first, idToRemove)\n const newSecond = removePane(tree.second, idToRemove)\n if (newFirst === null) return newSecond\n if (newSecond === null) return newFirst\n return { ...tree, first: newFirst, second: newSecond }\n}\n\n// Tree Helper: Insert a pane by splitting an existing target\nexport function splitPane(\n tree: TreeNode | null,\n targetId: string,\n direction: SplitDirection,\n splitType: 'left' | 'right' | 'top' | 'bottom',\n paneToAdd: string,\n): TreeNode | null {\n if (tree === null) return { type: 'pane', paneId: paneToAdd }\n if (tree.type === 'pane') {\n if (tree.paneId === targetId) {\n const addedNode: PaneNode = { type: 'pane', paneId: paneToAdd }\n const originalNode: PaneNode = { type: 'pane', paneId: targetId }\n const isFirst = splitType === 'left' || splitType === 'top'\n return {\n type: 'split',\n direction,\n first: isFirst ? addedNode : originalNode,\n second: isFirst ? originalNode : addedNode,\n splitPercentage: 50,\n }\n }\n return tree\n }\n return {\n ...tree,\n first: splitPane(tree.first, targetId, direction, splitType, paneToAdd) || tree.first,\n second: splitPane(tree.second, targetId, direction, splitType, paneToAdd) || tree.second,\n }\n}\n\n// Tree Helper: Swap two pane positions in the tree\nexport function swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n if (tree.paneId === idA) return { ...tree, paneId: idB }\n if (tree.paneId === idB) return { ...tree, paneId: idA }\n return tree\n }\n return {\n ...tree,\n first: swapPanes(tree.first, idA, idB) || tree.first,\n second: swapPanes(tree.second, idA, idB) || tree.second,\n }\n}\n\n// Tree Helper: Add a pane by recursively splitting the rightmost/bottommost pane in the tree\nexport function addPane(tree: TreeNode | null, paneToAdd: string): TreeNode {\n if (tree === null) {\n return { type: 'pane', paneId: paneToAdd }\n }\n\n function insert(node: TreeNode, parentDirection: SplitDirection | null): TreeNode {\n if (node.type === 'pane') {\n const direction: SplitDirection = parentDirection === 'row' ? 'column' : 'row'\n return {\n type: 'split',\n direction,\n splitPercentage: 50,\n first: node,\n second: { type: 'pane', paneId: paneToAdd },\n }\n }\n\n return {\n ...node,\n second: insert(node.second, node.direction),\n }\n }\n\n return insert(tree, null)\n}\n\n/** Cursor-following overlay rendered via portal */\nconst CursorOverlay: React.FC<{\n activeId: string\n render: (id: string) => ReactNode\n className?: string\n}> = ({ activeId, render, className }) => {\n const ref = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n const handleMove = (e: PointerEvent) => {\n if (ref.current) {\n ref.current.style.transform = `translate(${e.clientX + 12}px, ${e.clientY + 12}px)`\n }\n }\n document.addEventListener('pointermove', handleMove)\n return () => document.removeEventListener('pointermove', handleMove)\n }, [])\n\n return (\n <div\n ref={ref}\n className={className}\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n zIndex: 9999,\n pointerEvents: 'none',\n }}\n >\n {render(activeId)}\n </div>\n )\n}\n\ninterface DashboardProviderProps {\n layout: TreeNode | null\n onChange: (newLayout: TreeNode | null) => void\n renderPane: (paneId: string) => ReactNode\n renderDragOverlay?: (activeId: string) => ReactNode\n classNames?: ZeugmaClassNames\n fullscreenPaneId?: string | null\n onFullscreenChange?: (paneId: string | null) => void\n onRemove?: (paneId: string) => void\n dragActivationDistance?: number\n children: ReactNode\n}\n\nexport const DashboardProvider: React.FC<DashboardProviderProps> = ({\n layout,\n onChange,\n renderPane,\n renderDragOverlay,\n classNames = {},\n fullscreenPaneId = null,\n onFullscreenChange,\n onRemove,\n dragActivationDistance = 8,\n children,\n}) => {\n const [activeId, setActiveId] = useState<string | null>(null)\n\n const sensors = useSensors(\n useSensor(PointerSensor, {\n activationConstraint: { distance: dragActivationDistance },\n }),\n )\n\n const handleDragStart = (event: DragStartEvent) => {\n setActiveId(event.active.id.toString())\n }\n\n const handleDragEnd = (event: DragEndEvent) => {\n setActiveId(null)\n const { active, over } = event\n if (!over) return\n\n const draggingId = active.id.toString()\n const overIdStr = over.id.toString()\n\n // Check for center (swap) drop\n const swapMatch = overIdStr.match(/^drop-center-(.+)$/)\n if (swapMatch) {\n const [, targetId] = swapMatch\n if (draggingId !== targetId) {\n onChange(swapPanes(layout, draggingId, targetId))\n }\n return\n }\n\n // Check for edge (split) drop\n const match = overIdStr.match(/^drop-(left|right|top|bottom)-(.+)$/)\n if (!match) return\n\n const [, dropZone, targetId] = match\n if (draggingId === targetId) return\n\n const direction: SplitDirection = dropZone === 'left' || dropZone === 'right' ? 'row' : 'column'\n const treeWithoutDragging = removePane(layout, draggingId)\n\n const newLayout = splitPane(\n treeWithoutDragging,\n targetId,\n direction,\n dropZone as 'left' | 'right' | 'top' | 'bottom',\n draggingId,\n )\n onChange(newLayout)\n }\n\n return (\n <DashboardContext.Provider\n value={{\n layout,\n onLayoutChange: onChange,\n renderPane,\n activeId,\n fullscreenPaneId,\n classNames,\n onRemove,\n onFullscreenChange,\n }}\n >\n <DndContext\n sensors={sensors}\n collisionDetection={pointerWithin}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n >\n {children}\n </DndContext>\n {activeId && renderDragOverlay && (\n <CursorOverlay\n activeId={activeId}\n render={renderDragOverlay}\n className={classNames.dragOverlay}\n />\n )}\n </DashboardContext.Provider>\n )\n}\n","import React, { useRef } from 'react'\nimport { useDashboard } from './dashboard-provider'\nimport { TreeNode, SplitNode } from '../types'\n\ninterface PaneTreeProps {\n tree?: TreeNode | null\n /** Size of the resizer in pixels (default 4) */\n resizerSize?: number\n}\n\nfunction updateSplitPercentage(\n tree: TreeNode | null,\n target: SplitNode,\n newPercentage: number,\n): TreeNode | null {\n if (tree === null) return null\n if (tree === target) {\n return { ...tree, splitPercentage: newPercentage } as SplitNode\n }\n if (tree.type === 'split') {\n return {\n ...tree,\n first: updateSplitPercentage(tree.first, target, newPercentage) || tree.first,\n second: updateSplitPercentage(tree.second, target, newPercentage) || tree.second,\n }\n }\n return tree\n}\n\nexport const PaneTree: React.FC<PaneTreeProps> = ({ tree, resizerSize = 4 }) => {\n const { layout, onLayoutChange, renderPane, fullscreenPaneId, classNames } = useDashboard()\n const containerRef = useRef<HTMLDivElement>(null)\n\n // Fullscreen bypass\n if (fullscreenPaneId && !tree) {\n return (\n <div style={{ width: '100%', height: '100%', position: 'relative' }}>\n {renderPane(fullscreenPaneId)}\n </div>\n )\n }\n\n const currentNode = tree !== undefined ? tree : layout\n\n if (!currentNode) return null\n\n if (currentNode.type === 'pane') {\n return (\n <div style={{ width: '100%', height: '100%', position: 'relative' }}>\n {renderPane(currentNode.paneId)}\n </div>\n )\n }\n\n const { direction, first, second, splitPercentage } = currentNode\n const isRow = direction === 'row'\n\n const handlePointerDown = (e: React.PointerEvent) => {\n e.preventDefault()\n const container = containerRef.current\n if (!container) return\n\n document.body.classList.add('zeugma-resizing')\n\n const rect = container.getBoundingClientRect()\n const startX = e.clientX\n const startY = e.clientY\n const startPercentage = splitPercentage\n\n const handlePointerMove = (moveEvent: PointerEvent) => {\n const delta = isRow\n ? ((moveEvent.clientX - startX) / rect.width) * 100\n : ((moveEvent.clientY - startY) / rect.height) * 100\n const newPercentage = Math.max(5, Math.min(95, startPercentage + delta))\n const newLayout = updateSplitPercentage(layout, currentNode, newPercentage)\n onLayoutChange(newLayout)\n }\n\n const handlePointerUp = () => {\n document.body.classList.remove('zeugma-resizing')\n document.removeEventListener('pointermove', handlePointerMove)\n document.removeEventListener('pointerup', handlePointerUp)\n }\n\n document.addEventListener('pointermove', handlePointerMove)\n document.addEventListener('pointerup', handlePointerUp)\n }\n\n return (\n <div\n ref={containerRef}\n style={{\n display: 'flex',\n flexDirection: isRow ? 'row' : 'column',\n width: '100%',\n height: '100%',\n overflow: 'hidden',\n }}\n >\n <div style={{ flex: `${splitPercentage} 1 0%`, overflow: 'hidden' }}>\n <PaneTree tree={first} resizerSize={resizerSize} />\n </div>\n <div\n className={classNames.resizer}\n style={{\n width: isRow ? `${resizerSize}px` : '100%',\n height: isRow ? '100%' : `${resizerSize}px`,\n cursor: isRow ? 'col-resize' : 'row-resize',\n position: 'relative',\n zIndex: 10,\n userSelect: 'none',\n boxSizing: 'border-box',\n flexShrink: 0,\n }}\n onPointerDown={handlePointerDown}\n role=\"separator\"\n aria-valuenow={splitPercentage}\n aria-valuemin={5}\n aria-valuemax={95}\n />\n <div style={{ flex: `${100 - splitPercentage} 1 0%`, overflow: 'hidden' }}>\n <PaneTree tree={second} resizerSize={resizerSize} />\n </div>\n </div>\n )\n}\n","import React, { createContext, useContext } from 'react'\nimport { useDraggable, useDroppable } from '@dnd-kit/core'\nimport { useDashboard } from './dashboard-provider'\n\n// Internal context for drag listeners\nconst DragListenersCtx = createContext<Record<string, unknown> | null>(null)\n\ninterface DropZoneProps {\n id: string\n position: 'top' | 'bottom' | 'left' | 'right' | 'center'\n activeClassName?: string\n}\n\nconst activationPositions: Record<string, React.CSSProperties> = {\n top: {\n position: 'absolute',\n top: 0,\n left: '25%',\n width: '50%',\n height: '25%',\n zIndex: 20,\n pointerEvents: 'auto',\n },\n bottom: {\n position: 'absolute',\n bottom: 0,\n left: '25%',\n width: '50%',\n height: '25%',\n zIndex: 20,\n pointerEvents: 'auto',\n },\n left: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n left: 0,\n width: '25%',\n height: '100%',\n zIndex: 20,\n pointerEvents: 'auto',\n },\n right: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n right: 0,\n width: '25%',\n height: '100%',\n zIndex: 20,\n pointerEvents: 'auto',\n },\n center: {\n position: 'absolute',\n top: '25%',\n left: '25%',\n width: '50%',\n height: '50%',\n zIndex: 20,\n pointerEvents: 'auto',\n },\n}\n\nconst previewPositions: Record<string, React.CSSProperties> = {\n top: {\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n height: '50%',\n zIndex: 21,\n pointerEvents: 'none',\n boxSizing: 'border-box',\n },\n bottom: {\n position: 'absolute',\n bottom: 0,\n left: 0,\n right: 0,\n height: '50%',\n zIndex: 21,\n pointerEvents: 'none',\n boxSizing: 'border-box',\n },\n left: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n left: 0,\n width: '50%',\n zIndex: 21,\n pointerEvents: 'none',\n boxSizing: 'border-box',\n },\n right: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n right: 0,\n width: '50%',\n zIndex: 21,\n pointerEvents: 'none',\n boxSizing: 'border-box',\n },\n center: {\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 21,\n pointerEvents: 'none',\n boxSizing: 'border-box',\n },\n}\n\nconst DropZone: React.FC<DropZoneProps> = ({ id, position, activeClassName }) => {\n const { setNodeRef, isOver } = useDroppable({ id })\n return (\n <>\n <div ref={setNodeRef} style={activationPositions[position]} />\n {isOver && <div className={activeClassName} style={previewPositions[position]} />}\n </>\n )\n}\n\nexport interface PaneRenderProps {\n isDragging: boolean\n isFullscreen: boolean\n toggleFullscreen: () => void\n remove: () => void\n}\n\ninterface PaneProps {\n id: string\n children: (props: PaneRenderProps) => React.ReactNode\n style?: React.CSSProperties\n}\n\nexport const Pane: React.FC<PaneProps> = ({ id, children, style }) => {\n const { activeId, classNames, fullscreenPaneId, onRemove, onFullscreenChange } = useDashboard()\n const showDropZones = activeId !== null && activeId !== id\n\n const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id })\n const dragging = activeId === id || isDragging\n const isFullscreen = fullscreenPaneId === id\n\n const renderProps: PaneRenderProps = {\n isDragging: dragging,\n isFullscreen,\n toggleFullscreen: () => onFullscreenChange?.(isFullscreen ? null : id),\n remove: () => {\n if (isFullscreen) {\n onFullscreenChange?.(null)\n }\n onRemove?.(id)\n },\n }\n\n return (\n <DragListenersCtx.Provider value={{ ...listeners, ...attributes }}>\n <div\n ref={setNodeRef}\n className={classNames.pane}\n style={{ position: 'relative', width: '100%', height: '100%', ...style }}\n >\n {children(renderProps)}\n\n {showDropZones && (\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 15,\n pointerEvents: 'none',\n }}\n >\n {(['top', 'bottom', 'left', 'right'] as const).map((pos) => (\n <DropZone\n key={pos}\n id={`drop-${pos}-${id}`}\n position={pos}\n activeClassName={classNames.dropPreview}\n />\n ))}\n <DropZone\n id={`drop-center-${id}`}\n position=\"center\"\n activeClassName={classNames.swapPreview}\n />\n </div>\n )}\n </div>\n </DragListenersCtx.Provider>\n )\n}\n\n/**\n * Place inside a Pane to make an element the drag handle.\n */\ninterface DragHandleProps {\n children: React.ReactNode\n className?: string\n style?: React.CSSProperties\n}\n\nexport const DragHandle: React.FC<DragHandleProps> = ({ children, className, style }) => {\n const dragProps = useContext(DragListenersCtx)\n if (!dragProps) {\n throw new Error('<DragHandle> must be used inside a <Pane>')\n }\n return (\n <div\n className={className}\n style={{ cursor: 'grab', userSelect: 'none', ...style }}\n {...dragProps}\n >\n {children}\n </div>\n )\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -35,6 +35,7 @@ declare const useDashboard: () => DashboardContextValue;
35
35
  declare function removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null;
36
36
  declare function splitPane(tree: TreeNode | null, targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string): TreeNode | null;
37
37
  declare function swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null;
38
+ declare function addPane(tree: TreeNode | null, paneToAdd: string): TreeNode;
38
39
  interface DashboardProviderProps {
39
40
  layout: TreeNode | null;
40
41
  onChange: (newLayout: TreeNode | null) => void;
@@ -44,6 +45,7 @@ interface DashboardProviderProps {
44
45
  fullscreenPaneId?: string | null;
45
46
  onFullscreenChange?: (paneId: string | null) => void;
46
47
  onRemove?: (paneId: string) => void;
48
+ dragActivationDistance?: number;
47
49
  children: ReactNode;
48
50
  }
49
51
  declare const DashboardProvider: React.FC<DashboardProviderProps>;
@@ -77,4 +79,4 @@ interface DragHandleProps {
77
79
  }
78
80
  declare const DragHandle: React.FC<DragHandleProps>;
79
81
 
80
- export { DashboardProvider, DragHandle, Pane, type PaneNode, type PaneRenderProps, PaneTree, type SplitDirection, type SplitNode, type TreeNode, type ZeugmaClassNames, removePane, splitPane, swapPanes, useDashboard };
82
+ export { DashboardProvider, DragHandle, Pane, type PaneNode, type PaneRenderProps, PaneTree, type SplitDirection, type SplitNode, type TreeNode, type ZeugmaClassNames, addPane, removePane, splitPane, swapPanes, useDashboard };
package/dist/index.d.ts CHANGED
@@ -35,6 +35,7 @@ declare const useDashboard: () => DashboardContextValue;
35
35
  declare function removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null;
36
36
  declare function splitPane(tree: TreeNode | null, targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string): TreeNode | null;
37
37
  declare function swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null;
38
+ declare function addPane(tree: TreeNode | null, paneToAdd: string): TreeNode;
38
39
  interface DashboardProviderProps {
39
40
  layout: TreeNode | null;
40
41
  onChange: (newLayout: TreeNode | null) => void;
@@ -44,6 +45,7 @@ interface DashboardProviderProps {
44
45
  fullscreenPaneId?: string | null;
45
46
  onFullscreenChange?: (paneId: string | null) => void;
46
47
  onRemove?: (paneId: string) => void;
48
+ dragActivationDistance?: number;
47
49
  children: ReactNode;
48
50
  }
49
51
  declare const DashboardProvider: React.FC<DashboardProviderProps>;
@@ -77,4 +79,4 @@ interface DragHandleProps {
77
79
  }
78
80
  declare const DragHandle: React.FC<DragHandleProps>;
79
81
 
80
- export { DashboardProvider, DragHandle, Pane, type PaneNode, type PaneRenderProps, PaneTree, type SplitDirection, type SplitNode, type TreeNode, type ZeugmaClassNames, removePane, splitPane, swapPanes, useDashboard };
82
+ export { DashboardProvider, DragHandle, Pane, type PaneNode, type PaneRenderProps, PaneTree, type SplitDirection, type SplitNode, type TreeNode, type ZeugmaClassNames, addPane, removePane, splitPane, swapPanes, useDashboard };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import {createContext,useContext,useState,useRef,useEffect}from'react';import {useSensors,useSensor,PointerSensor,DndContext,pointerWithin,useDraggable,useDroppable}from'@dnd-kit/core';import {jsxs,jsx,Fragment}from'react/jsx-runtime';var M=createContext(void 0),D=()=>{let e=useContext(M);if(!e)throw new Error("useDashboard must be used within a DashboardProvider");return e};function y(e,t){if(e===null)return null;if(e.type==="pane")return e.paneId===t?null:e;let n=y(e.first,t),o=y(e.second,t);return n===null?o:o===null?n:{...e,first:n,second:o}}function E(e,t,n,o,r){if(e===null)return {type:"pane",paneId:r};if(e.type==="pane"){if(e.paneId===t){let i={type:"pane",paneId:r},c={type:"pane",paneId:t},s=o==="left"||o==="top";return {type:"split",direction:n,first:s?i:c,second:s?c:i,splitPercentage:50}}return e}return {...e,first:E(e.first,t,n,o,r)||e.first,second:E(e.second,t,n,o,r)||e.second}}function z(e,t,n){return e===null?null:e.type==="pane"?e.paneId===t?{...e,paneId:n}:e.paneId===n?{...e,paneId:t}:e:{...e,first:z(e.first,t,n)||e.first,second:z(e.second,t,n)||e.second}}var _=({activeId:e,render:t,className:n})=>{let o=useRef(null);return useEffect(()=>{let r=i=>{o.current&&(o.current.style.transform=`translate(${i.clientX+12}px, ${i.clientY+12}px)`);};return document.addEventListener("pointermove",r),()=>document.removeEventListener("pointermove",r)},[]),jsx("div",{ref:o,className:n,style:{position:"fixed",top:0,left:0,zIndex:9999,pointerEvents:"none"},children:t(e)})},B=({layout:e,onChange:t,renderPane:n,renderDragOverlay:o,classNames:r={},fullscreenPaneId:i=null,onFullscreenChange:c,onRemove:s,children:a})=>{let[v,m]=useState(null),x=useSensors(useSensor(PointerSensor,{activationConstraint:{distance:8}})),p=u=>{m(u.active.id.toString());},l=u=>{m(null);let{active:f,over:d}=u;if(!d)return;let g=f.id.toString(),w=d.id.toString(),I=w.match(/^drop-center-(.+)$/);if(I){let[,S]=I;g!==S&&t(z(e,g,S));return}let R=w.match(/^drop-(left|right|top|bottom)-(.+)$/);if(!R)return;let[,P,N]=R;if(g===N)return;let C=P==="left"||P==="right"?"row":"column",L=y(e,g),F=E(L,N,C,P,g);t(F);};return jsxs(M.Provider,{value:{layout:e,onLayoutChange:t,renderPane:n,activeId:v,fullscreenPaneId:i,classNames:r,onRemove:s,onFullscreenChange:c},children:[jsx(DndContext,{sensors:x,collisionDetection:pointerWithin,onDragStart:p,onDragEnd:l,children:a}),v&&o&&jsx(_,{activeId:v,render:o,className:r.dragOverlay})]})};function $(e,t,n){return e===null?null:e===t?{...e,splitPercentage:n}:e.type==="split"?{...e,first:$(e.first,t,n)||e.first,second:$(e.second,t,n)||e.second}:e}var Z=({tree:e,resizerSize:t=4})=>{let{layout:n,onLayoutChange:o,renderPane:r,fullscreenPaneId:i,classNames:c}=D(),s=useRef(null);if(i&&!e)return jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(i)});let a=e!==void 0?e:n;if(!a)return null;if(a.type==="pane")return jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(a.paneId)});let{direction:v,first:m,second:x,splitPercentage:p}=a,l=v==="row",u=f=>{f.preventDefault();let d=s.current;if(!d)return;let g=d.getBoundingClientRect(),w=f.clientX,I=f.clientY,R=p,P=C=>{let L=l?(C.clientX-w)/g.width*100:(C.clientY-I)/g.height*100,F=Math.max(5,Math.min(95,R+L)),S=$(n,a,F);o(S);},N=()=>{document.removeEventListener("pointermove",P),document.removeEventListener("pointerup",N);};document.addEventListener("pointermove",P),document.addEventListener("pointerup",N);};return jsxs("div",{ref:s,style:{display:"flex",flexDirection:l?"row":"column",width:"100%",height:"100%",overflow:"hidden"},children:[jsx("div",{style:{flex:`${p} 1 0%`,overflow:"hidden"},children:jsx(Z,{tree:m,resizerSize:t})}),jsx("div",{className:c.resizer,style:{width:l?`${t}px`:"100%",height:l?"100%":`${t}px`,cursor:l?"col-resize":"row-resize",position:"relative",zIndex:10,userSelect:"none",boxSizing:"border-box",flexShrink:0},onPointerDown:u,role:"separator","aria-valuenow":p,"aria-valuemin":5,"aria-valuemax":95}),jsx("div",{style:{flex:`${100-p} 1 0%`,overflow:"hidden"},children:jsx(Z,{tree:x,resizerSize:t})})]})};var X=createContext(null),ie={top:{position:"absolute",top:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},bottom:{position:"absolute",bottom:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},left:{position:"absolute",top:0,bottom:0,left:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},right:{position:"absolute",top:0,bottom:0,right:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},center:{position:"absolute",top:"25%",left:"25%",width:"50%",height:"50%",zIndex:20,pointerEvents:"auto"}},se={top:{position:"absolute",top:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},bottom:{position:"absolute",bottom:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},left:{position:"absolute",top:0,bottom:0,left:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},right:{position:"absolute",top:0,bottom:0,right:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},center:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:21,pointerEvents:"none",boxSizing:"border-box"}},O=({id:e,position:t,activeClassName:n})=>{let{setNodeRef:o,isOver:r}=useDroppable({id:e});return jsxs(Fragment,{children:[jsx("div",{ref:o,style:ie[t]}),r&&jsx("div",{className:n,style:se[t]})]})},ae=({id:e,children:t,style:n})=>{let{activeId:o,classNames:r,fullscreenPaneId:i,onRemove:c,onFullscreenChange:s}=D(),a=o!==null&&o!==e,{attributes:v,listeners:m,setNodeRef:x,isDragging:p}=useDraggable({id:e}),l=o===e||p,u=i===e,f={isDragging:l,isFullscreen:u,toggleFullscreen:()=>s?.(u?null:e),remove:()=>c?.(e)};return jsx(X.Provider,{value:{...m,...v},children:jsxs("div",{ref:x,className:r.pane,style:{position:"relative",width:"100%",height:"100%",...n},children:[t(f),a&&jsxs("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:15,pointerEvents:"none"},children:[["top","bottom","left","right"].map(d=>jsx(O,{id:`drop-${d}-${e}`,position:d,activeClassName:r.dropPreview},d)),jsx(O,{id:`drop-center-${e}`,position:"center",activeClassName:r.swapPreview})]})]})})},le=({children:e,className:t,style:n})=>{let o=useContext(X);if(!o)throw new Error("<DragHandle> must be used inside a <Pane>");return jsx("div",{className:t,style:{cursor:"grab",userSelect:"none",...n},...o,children:e})};export{B as DashboardProvider,le as DragHandle,ae as Pane,Z as PaneTree,y as removePane,E as splitPane,z as swapPanes,D as useDashboard};//# sourceMappingURL=index.js.map
1
+ import {createContext,useContext,useState,useRef,useEffect}from'react';import {useSensors,useSensor,PointerSensor,DndContext,pointerWithin,useDraggable,useDroppable}from'@dnd-kit/core';import {jsxs,jsx,Fragment}from'react/jsx-runtime';var O=createContext(void 0),I=()=>{let e=useContext(O);if(!e)throw new Error("useDashboard must be used within a DashboardProvider");return e};function S(e,t){if(e===null)return null;if(e.type==="pane")return e.paneId===t?null:e;let n=S(e.first,t),o=S(e.second,t);return n===null?o:o===null?n:{...e,first:n,second:o}}function C(e,t,n,o,r){if(e===null)return {type:"pane",paneId:r};if(e.type==="pane"){if(e.paneId===t){let i={type:"pane",paneId:r},c={type:"pane",paneId:t},s=o==="left"||o==="top";return {type:"split",direction:n,first:s?i:c,second:s?c:i,splitPercentage:50}}return e}return {...e,first:C(e.first,t,n,o,r)||e.first,second:C(e.second,t,n,o,r)||e.second}}function E(e,t,n){return e===null?null:e.type==="pane"?e.paneId===t?{...e,paneId:n}:e.paneId===n?{...e,paneId:t}:e:{...e,first:E(e.first,t,n)||e.first,second:E(e.second,t,n)||e.second}}function B(e,t){if(e===null)return {type:"pane",paneId:t};function n(o,r){return o.type==="pane"?{type:"split",direction:r==="row"?"column":"row",splitPercentage:50,first:o,second:{type:"pane",paneId:t}}:{...o,second:n(o.second,o.direction)}}return n(e,null)}var j=({activeId:e,render:t,className:n})=>{let o=useRef(null);return useEffect(()=>{let r=i=>{o.current&&(o.current.style.transform=`translate(${i.clientX+12}px, ${i.clientY+12}px)`);};return document.addEventListener("pointermove",r),()=>document.removeEventListener("pointermove",r)},[]),jsx("div",{ref:o,className:n,style:{position:"fixed",top:0,left:0,zIndex:9999,pointerEvents:"none"},children:t(e)})},A=({layout:e,onChange:t,renderPane:n,renderDragOverlay:o,classNames:r={},fullscreenPaneId:i=null,onFullscreenChange:c,onRemove:s,dragActivationDistance:a=8,children:x})=>{let[g,h]=useState(null),p=useSensors(useSensor(PointerSensor,{activationConstraint:{distance:a}})),l=d=>{h(d.active.id.toString());},f=d=>{h(null);let{active:u,over:N}=d;if(!N)return;let v=u.id.toString(),y=N.id.toString(),R=y.match(/^drop-center-(.+)$/);if(R){let[,M]=R;v!==M&&t(E(e,v,M));return}let D=y.match(/^drop-(left|right|top|bottom)-(.+)$/);if(!D)return;let[,P,w]=D;if(v===w)return;let z=P==="left"||P==="right"?"row":"column",L=S(e,v),T=C(L,w,z,P,v);t(T);};return jsxs(O.Provider,{value:{layout:e,onLayoutChange:t,renderPane:n,activeId:g,fullscreenPaneId:i,classNames:r,onRemove:s,onFullscreenChange:c},children:[jsx(DndContext,{sensors:p,collisionDetection:pointerWithin,onDragStart:l,onDragEnd:f,children:x}),g&&o&&jsx(j,{activeId:g,render:o,className:r.dragOverlay})]})};function $(e,t,n){return e===null?null:e===t?{...e,splitPercentage:n}:e.type==="split"?{...e,first:$(e.first,t,n)||e.first,second:$(e.second,t,n)||e.second}:e}var Z=({tree:e,resizerSize:t=4})=>{let{layout:n,onLayoutChange:o,renderPane:r,fullscreenPaneId:i,classNames:c}=I(),s=useRef(null);if(i&&!e)return jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(i)});let a=e!==void 0?e:n;if(!a)return null;if(a.type==="pane")return jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(a.paneId)});let{direction:x,first:g,second:h,splitPercentage:p}=a,l=x==="row",f=d=>{d.preventDefault();let u=s.current;if(!u)return;document.body.classList.add("zeugma-resizing");let N=u.getBoundingClientRect(),v=d.clientX,y=d.clientY,R=p,D=w=>{let z=l?(w.clientX-v)/N.width*100:(w.clientY-y)/N.height*100,L=Math.max(5,Math.min(95,R+z)),T=$(n,a,L);o(T);},P=()=>{document.body.classList.remove("zeugma-resizing"),document.removeEventListener("pointermove",D),document.removeEventListener("pointerup",P);};document.addEventListener("pointermove",D),document.addEventListener("pointerup",P);};return jsxs("div",{ref:s,style:{display:"flex",flexDirection:l?"row":"column",width:"100%",height:"100%",overflow:"hidden"},children:[jsx("div",{style:{flex:`${p} 1 0%`,overflow:"hidden"},children:jsx(Z,{tree:g,resizerSize:t})}),jsx("div",{className:c.resizer,style:{width:l?`${t}px`:"100%",height:l?"100%":`${t}px`,cursor:l?"col-resize":"row-resize",position:"relative",zIndex:10,userSelect:"none",boxSizing:"border-box",flexShrink:0},onPointerDown:f,role:"separator","aria-valuenow":p,"aria-valuemin":5,"aria-valuemax":95}),jsx("div",{style:{flex:`${100-p} 1 0%`,overflow:"hidden"},children:jsx(Z,{tree:h,resizerSize:t})})]})};var Y=createContext(null),ae={top:{position:"absolute",top:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},bottom:{position:"absolute",bottom:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},left:{position:"absolute",top:0,bottom:0,left:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},right:{position:"absolute",top:0,bottom:0,right:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},center:{position:"absolute",top:"25%",left:"25%",width:"50%",height:"50%",zIndex:20,pointerEvents:"auto"}},le={top:{position:"absolute",top:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},bottom:{position:"absolute",bottom:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},left:{position:"absolute",top:0,bottom:0,left:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},right:{position:"absolute",top:0,bottom:0,right:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},center:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:21,pointerEvents:"none",boxSizing:"border-box"}},X=({id:e,position:t,activeClassName:n})=>{let{setNodeRef:o,isOver:r}=useDroppable({id:e});return jsxs(Fragment,{children:[jsx("div",{ref:o,style:ae[t]}),r&&jsx("div",{className:n,style:le[t]})]})},de=({id:e,children:t,style:n})=>{let{activeId:o,classNames:r,fullscreenPaneId:i,onRemove:c,onFullscreenChange:s}=I(),a=o!==null&&o!==e,{attributes:x,listeners:g,setNodeRef:h,isDragging:p}=useDraggable({id:e}),l=o===e||p,f=i===e,d={isDragging:l,isFullscreen:f,toggleFullscreen:()=>s?.(f?null:e),remove:()=>{f&&s?.(null),c?.(e);}};return jsx(Y.Provider,{value:{...g,...x},children:jsxs("div",{ref:h,className:r.pane,style:{position:"relative",width:"100%",height:"100%",...n},children:[t(d),a&&jsxs("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:15,pointerEvents:"none"},children:[["top","bottom","left","right"].map(u=>jsx(X,{id:`drop-${u}-${e}`,position:u,activeClassName:r.dropPreview},u)),jsx(X,{id:`drop-center-${e}`,position:"center",activeClassName:r.swapPreview})]})]})})},ce=({children:e,className:t,style:n})=>{let o=useContext(Y);if(!o)throw new Error("<DragHandle> must be used inside a <Pane>");return jsx("div",{className:t,style:{cursor:"grab",userSelect:"none",...n},...o,children:e})};export{A as DashboardProvider,ce as DragHandle,de as Pane,Z as PaneTree,B as addPane,S as removePane,C as splitPane,E as swapPanes,I as useDashboard};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/dashboard-provider.tsx","../src/components/pane-tree.tsx","../src/components/pane.tsx"],"names":["DashboardContext","createContext","useDashboard","context","useContext","removePane","tree","idToRemove","newFirst","newSecond","splitPane","targetId","direction","splitType","paneToAdd","addedNode","originalNode","isFirst","swapPanes","idA","idB","CursorOverlay","activeId","render","className","ref","useRef","useEffect","handleMove","e","jsx","DashboardProvider","layout","onChange","renderPane","renderDragOverlay","classNames","fullscreenPaneId","onFullscreenChange","onRemove","children","setActiveId","useState","sensors","useSensors","useSensor","PointerSensor","handleDragStart","event","handleDragEnd","active","over","draggingId","overIdStr","swapMatch","match","dropZone","treeWithoutDragging","newLayout","jsxs","DndContext","pointerWithin","updateSplitPercentage","target","newPercentage","PaneTree","resizerSize","onLayoutChange","containerRef","currentNode","first","second","splitPercentage","isRow","handlePointerDown","container","rect","startX","startY","startPercentage","handlePointerMove","moveEvent","delta","handlePointerUp","DragListenersCtx","activationPositions","previewPositions","DropZone","id","position","activeClassName","setNodeRef","isOver","useDroppable","Fragment","Pane","style","showDropZones","attributes","listeners","isDragging","useDraggable","dragging","isFullscreen","renderProps","pos","DragHandle","dragProps"],"mappings":"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","file":"index.js","sourcesContent":["import React, { createContext, useContext, useState, useEffect, useRef, ReactNode } from 'react';\nimport {\n DndContext,\n useSensor,\n useSensors,\n PointerSensor,\n DragStartEvent,\n DragEndEvent,\n pointerWithin,\n} from '@dnd-kit/core';\nimport { TreeNode, SplitDirection, PaneNode } from '../types';\n\nexport interface ZeugmaClassNames {\n pane?: string;\n dropPreview?: string;\n swapPreview?: string;\n dragOverlay?: string;\n resizer?: string;\n}\n\nexport interface DashboardContextValue {\n layout: TreeNode | null;\n onLayoutChange: (newLayout: TreeNode | null) => void;\n renderPane: (paneId: string) => ReactNode;\n activeId: string | null;\n fullscreenPaneId: string | null;\n classNames: ZeugmaClassNames;\n onRemove?: (paneId: string) => void;\n onFullscreenChange?: (paneId: string | null) => void;\n}\n\nexport const DashboardContext = createContext<DashboardContextValue | undefined>(undefined);\n\nexport const useDashboard = () => {\n const context = useContext(DashboardContext);\n if (!context) {\n throw new Error('useDashboard must be used within a DashboardProvider');\n }\n return context;\n};\n\n// Tree Helper: Remove a pane and consolidate the tree\nexport function removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null {\n if (tree === null) return null;\n if (tree.type === 'pane') {\n return tree.paneId === idToRemove ? null : tree;\n }\n const newFirst = removePane(tree.first, idToRemove);\n const newSecond = removePane(tree.second, idToRemove);\n if (newFirst === null) return newSecond;\n if (newSecond === null) return newFirst;\n return { ...tree, first: newFirst, second: newSecond };\n}\n\n// Tree Helper: Insert a pane by splitting an existing target\nexport function splitPane(\n tree: TreeNode | null,\n targetId: string,\n direction: SplitDirection,\n splitType: 'left' | 'right' | 'top' | 'bottom',\n paneToAdd: string\n): TreeNode | null {\n if (tree === null) return { type: 'pane', paneId: paneToAdd };\n if (tree.type === 'pane') {\n if (tree.paneId === targetId) {\n const addedNode: PaneNode = { type: 'pane', paneId: paneToAdd };\n const originalNode: PaneNode = { type: 'pane', paneId: targetId };\n const isFirst = splitType === 'left' || splitType === 'top';\n return {\n type: 'split',\n direction,\n first: isFirst ? addedNode : originalNode,\n second: isFirst ? originalNode : addedNode,\n splitPercentage: 50,\n };\n }\n return tree;\n }\n return {\n ...tree,\n first: splitPane(tree.first, targetId, direction, splitType, paneToAdd) || tree.first,\n second: splitPane(tree.second, targetId, direction, splitType, paneToAdd) || tree.second,\n };\n}\n\n// Tree Helper: Swap two pane positions in the tree\nexport function swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null {\n if (tree === null) return null;\n if (tree.type === 'pane') {\n if (tree.paneId === idA) return { ...tree, paneId: idB };\n if (tree.paneId === idB) return { ...tree, paneId: idA };\n return tree;\n }\n return {\n ...tree,\n first: swapPanes(tree.first, idA, idB) || tree.first,\n second: swapPanes(tree.second, idA, idB) || tree.second,\n };\n}\n\n/** Cursor-following overlay rendered via portal */\nconst CursorOverlay: React.FC<{ activeId: string; render: (id: string) => ReactNode; className?: string }> = ({\n activeId,\n render,\n className,\n}) => {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const handleMove = (e: PointerEvent) => {\n if (ref.current) {\n ref.current.style.transform = `translate(${e.clientX + 12}px, ${e.clientY + 12}px)`;\n }\n };\n document.addEventListener('pointermove', handleMove);\n return () => document.removeEventListener('pointermove', handleMove);\n }, []);\n\n return (\n <div\n ref={ref}\n className={className}\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n zIndex: 9999,\n pointerEvents: 'none',\n }}\n >\n {render(activeId)}\n </div>\n );\n};\n\ninterface DashboardProviderProps {\n layout: TreeNode | null;\n onChange: (newLayout: TreeNode | null) => void;\n renderPane: (paneId: string) => ReactNode;\n renderDragOverlay?: (activeId: string) => ReactNode;\n classNames?: ZeugmaClassNames;\n fullscreenPaneId?: string | null;\n onFullscreenChange?: (paneId: string | null) => void;\n onRemove?: (paneId: string) => void;\n children: ReactNode;\n}\n\nexport const DashboardProvider: React.FC<DashboardProviderProps> = ({\n layout,\n onChange,\n renderPane,\n renderDragOverlay,\n classNames = {},\n fullscreenPaneId = null,\n onFullscreenChange,\n onRemove,\n children,\n}) => {\n const [activeId, setActiveId] = useState<string | null>(null);\n\n const sensors = useSensors(\n useSensor(PointerSensor, {\n activationConstraint: { distance: 8 },\n })\n );\n\n const handleDragStart = (event: DragStartEvent) => {\n setActiveId(event.active.id.toString());\n };\n\n const handleDragEnd = (event: DragEndEvent) => {\n setActiveId(null);\n const { active, over } = event;\n if (!over) return;\n\n const draggingId = active.id.toString();\n const overIdStr = over.id.toString();\n\n // Check for center (swap) drop\n const swapMatch = overIdStr.match(/^drop-center-(.+)$/);\n if (swapMatch) {\n const [, targetId] = swapMatch;\n if (draggingId !== targetId) {\n onChange(swapPanes(layout, draggingId, targetId));\n }\n return;\n }\n\n // Check for edge (split) drop\n const match = overIdStr.match(/^drop-(left|right|top|bottom)-(.+)$/);\n if (!match) return;\n\n const [, dropZone, targetId] = match;\n if (draggingId === targetId) return;\n\n const direction: SplitDirection = (dropZone === 'left' || dropZone === 'right') ? 'row' : 'column';\n const treeWithoutDragging = removePane(layout, draggingId);\n\n const newLayout = splitPane(\n treeWithoutDragging,\n targetId,\n direction,\n dropZone as 'left' | 'right' | 'top' | 'bottom',\n draggingId\n );\n onChange(newLayout);\n };\n\n return (\n <DashboardContext.Provider\n value={{\n layout,\n onLayoutChange: onChange,\n renderPane,\n activeId,\n fullscreenPaneId,\n classNames,\n onRemove,\n onFullscreenChange,\n }}\n >\n <DndContext sensors={sensors} collisionDetection={pointerWithin} onDragStart={handleDragStart} onDragEnd={handleDragEnd}>\n {children}\n </DndContext>\n {activeId && renderDragOverlay && (\n <CursorOverlay activeId={activeId} render={renderDragOverlay} className={classNames.dragOverlay} />\n )}\n </DashboardContext.Provider>\n );\n};\n","import React, { useRef } from 'react';\nimport { useDashboard } from './dashboard-provider';\nimport { TreeNode, SplitNode } from '../types';\n\ninterface PaneTreeProps {\n tree?: TreeNode | null;\n /** Size of the resizer in pixels (default 4) */\n resizerSize?: number;\n}\n\nfunction updateSplitPercentage(tree: TreeNode | null, target: SplitNode, newPercentage: number): TreeNode | null {\n if (tree === null) return null;\n if (tree === target) {\n return { ...tree, splitPercentage: newPercentage } as SplitNode;\n }\n if (tree.type === 'split') {\n return {\n ...tree,\n first: updateSplitPercentage(tree.first, target, newPercentage) || tree.first,\n second: updateSplitPercentage(tree.second, target, newPercentage) || tree.second,\n };\n }\n return tree;\n}\n\nexport const PaneTree: React.FC<PaneTreeProps> = ({ tree, resizerSize = 4 }) => {\n const { layout, onLayoutChange, renderPane, fullscreenPaneId, classNames } = useDashboard();\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Fullscreen bypass\n if (fullscreenPaneId && !tree) {\n return (\n <div style={{ width: '100%', height: '100%', position: 'relative' }}>\n {renderPane(fullscreenPaneId)}\n </div>\n );\n }\n\n const currentNode = tree !== undefined ? tree : layout;\n\n if (!currentNode) return null;\n\n if (currentNode.type === 'pane') {\n return (\n <div style={{ width: '100%', height: '100%', position: 'relative' }}>\n {renderPane(currentNode.paneId)}\n </div>\n );\n }\n\n const { direction, first, second, splitPercentage } = currentNode;\n const isRow = direction === 'row';\n\n const handlePointerDown = (e: React.PointerEvent) => {\n e.preventDefault();\n const container = containerRef.current;\n if (!container) return;\n\n const rect = container.getBoundingClientRect();\n const startX = e.clientX;\n const startY = e.clientY;\n const startPercentage = splitPercentage;\n\n const handlePointerMove = (moveEvent: PointerEvent) => {\n const delta = isRow\n ? ((moveEvent.clientX - startX) / rect.width) * 100\n : ((moveEvent.clientY - startY) / rect.height) * 100;\n const newPercentage = Math.max(5, Math.min(95, startPercentage + delta));\n const newLayout = updateSplitPercentage(layout, currentNode, newPercentage);\n onLayoutChange(newLayout);\n };\n\n const handlePointerUp = () => {\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n };\n\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n };\n\n return (\n <div\n ref={containerRef}\n style={{ display: 'flex', flexDirection: isRow ? 'row' : 'column', width: '100%', height: '100%', overflow: 'hidden' }}\n >\n <div style={{ flex: `${splitPercentage} 1 0%`, overflow: 'hidden' }}>\n <PaneTree tree={first} resizerSize={resizerSize} />\n </div>\n <div\n className={classNames.resizer}\n style={{\n width: isRow ? `${resizerSize}px` : '100%',\n height: isRow ? '100%' : `${resizerSize}px`,\n cursor: isRow ? 'col-resize' : 'row-resize',\n position: 'relative',\n zIndex: 10,\n userSelect: 'none',\n boxSizing: 'border-box',\n flexShrink: 0,\n }}\n onPointerDown={handlePointerDown}\n role=\"separator\"\n aria-valuenow={splitPercentage}\n aria-valuemin={5}\n aria-valuemax={95}\n />\n <div style={{ flex: `${100 - splitPercentage} 1 0%`, overflow: 'hidden' }}>\n <PaneTree tree={second} resizerSize={resizerSize} />\n </div>\n </div>\n );\n};\n","import React, { createContext, useContext } from 'react';\nimport { useDraggable, useDroppable } from '@dnd-kit/core';\nimport { useDashboard } from './dashboard-provider';\n\n// Internal context for drag listeners\nconst DragListenersCtx = createContext<Record<string, unknown> | null>(null);\n\ninterface DropZoneProps {\n id: string;\n position: 'top' | 'bottom' | 'left' | 'right' | 'center';\n activeClassName?: string;\n}\n\nconst activationPositions: Record<string, React.CSSProperties> = {\n top: { position: 'absolute', top: 0, left: '25%', width: '50%', height: '25%', zIndex: 20, pointerEvents: 'auto' },\n bottom: { position: 'absolute', bottom: 0, left: '25%', width: '50%', height: '25%', zIndex: 20, pointerEvents: 'auto' },\n left: { position: 'absolute', top: 0, bottom: 0, left: 0, width: '25%', height: '100%', zIndex: 20, pointerEvents: 'auto' },\n right: { position: 'absolute', top: 0, bottom: 0, right: 0, width: '25%', height: '100%', zIndex: 20, pointerEvents: 'auto' },\n center: { position: 'absolute', top: '25%', left: '25%', width: '50%', height: '50%', zIndex: 20, pointerEvents: 'auto' },\n};\n\nconst previewPositions: Record<string, React.CSSProperties> = {\n top: { position: 'absolute', top: 0, left: 0, right: 0, height: '50%', zIndex: 21, pointerEvents: 'none', boxSizing: 'border-box' },\n bottom: { position: 'absolute', bottom: 0, left: 0, right: 0, height: '50%', zIndex: 21, pointerEvents: 'none', boxSizing: 'border-box' },\n left: { position: 'absolute', top: 0, bottom: 0, left: 0, width: '50%', zIndex: 21, pointerEvents: 'none', boxSizing: 'border-box' },\n right: { position: 'absolute', top: 0, bottom: 0, right: 0, width: '50%', zIndex: 21, pointerEvents: 'none', boxSizing: 'border-box' },\n center: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, zIndex: 21, pointerEvents: 'none', boxSizing: 'border-box' },\n};\n\nconst DropZone: React.FC<DropZoneProps> = ({ id, position, activeClassName }) => {\n const { setNodeRef, isOver } = useDroppable({ id });\n return (\n <>\n <div ref={setNodeRef} style={activationPositions[position]} />\n {isOver && <div className={activeClassName} style={previewPositions[position]} />}\n </>\n );\n};\n\nexport interface PaneRenderProps {\n isDragging: boolean;\n isFullscreen: boolean;\n toggleFullscreen: () => void;\n remove: () => void;\n}\n\ninterface PaneProps {\n id: string;\n children: (props: PaneRenderProps) => React.ReactNode;\n style?: React.CSSProperties;\n}\n\nexport const Pane: React.FC<PaneProps> = ({ id, children, style }) => {\n const { activeId, classNames, fullscreenPaneId, onRemove, onFullscreenChange } = useDashboard();\n const showDropZones = activeId !== null && activeId !== id;\n\n const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id });\n const dragging = activeId === id || isDragging;\n const isFullscreen = fullscreenPaneId === id;\n\n const renderProps: PaneRenderProps = {\n isDragging: dragging,\n isFullscreen,\n toggleFullscreen: () => onFullscreenChange?.(isFullscreen ? null : id),\n remove: () => onRemove?.(id),\n };\n\n return (\n <DragListenersCtx.Provider value={{ ...listeners, ...attributes }}>\n <div\n ref={setNodeRef}\n className={classNames.pane}\n style={{ position: 'relative', width: '100%', height: '100%', ...style }}\n >\n {children(renderProps)}\n\n {showDropZones && (\n <div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, zIndex: 15, pointerEvents: 'none' }}>\n {(['top', 'bottom', 'left', 'right'] as const).map((pos) => (\n <DropZone\n key={pos}\n id={`drop-${pos}-${id}`}\n position={pos}\n activeClassName={classNames.dropPreview}\n />\n ))}\n <DropZone\n id={`drop-center-${id}`}\n position=\"center\"\n activeClassName={classNames.swapPreview}\n />\n </div>\n )}\n </div>\n </DragListenersCtx.Provider>\n );\n};\n\n/**\n * Place inside a Pane to make an element the drag handle.\n */\ninterface DragHandleProps {\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n}\n\nexport const DragHandle: React.FC<DragHandleProps> = ({ children, className, style }) => {\n const dragProps = useContext(DragListenersCtx);\n if (!dragProps) {\n throw new Error('<DragHandle> must be used inside a <Pane>');\n }\n return (\n <div className={className} style={{ cursor: 'grab', userSelect: 'none', ...style }} {...dragProps}>\n {children}\n </div>\n );\n};\n"]}
1
+ {"version":3,"sources":["../src/components/dashboard-provider.tsx","../src/components/pane-tree.tsx","../src/components/pane.tsx"],"names":["DashboardContext","createContext","useDashboard","context","useContext","removePane","tree","idToRemove","newFirst","newSecond","splitPane","targetId","direction","splitType","paneToAdd","addedNode","originalNode","isFirst","swapPanes","idA","idB","addPane","insert","node","parentDirection","CursorOverlay","activeId","render","className","ref","useRef","useEffect","handleMove","e","jsx","DashboardProvider","layout","onChange","renderPane","renderDragOverlay","classNames","fullscreenPaneId","onFullscreenChange","onRemove","dragActivationDistance","children","setActiveId","useState","sensors","useSensors","useSensor","PointerSensor","handleDragStart","event","handleDragEnd","active","over","draggingId","overIdStr","swapMatch","match","dropZone","treeWithoutDragging","newLayout","jsxs","DndContext","pointerWithin","updateSplitPercentage","target","newPercentage","PaneTree","resizerSize","onLayoutChange","containerRef","currentNode","first","second","splitPercentage","isRow","handlePointerDown","container","rect","startX","startY","startPercentage","handlePointerMove","moveEvent","delta","handlePointerUp","DragListenersCtx","activationPositions","previewPositions","DropZone","id","position","activeClassName","setNodeRef","isOver","useDroppable","Fragment","Pane","style","showDropZones","attributes","listeners","isDragging","useDraggable","dragging","isFullscreen","renderProps","pos","DragHandle","dragProps"],"mappings":"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","file":"index.js","sourcesContent":["import React, { createContext, useContext, useState, useEffect, useRef, ReactNode } from 'react'\nimport {\n DndContext,\n useSensor,\n useSensors,\n PointerSensor,\n DragStartEvent,\n DragEndEvent,\n pointerWithin,\n} from '@dnd-kit/core'\nimport { TreeNode, SplitDirection, PaneNode } from '../types'\n\nexport interface ZeugmaClassNames {\n pane?: string\n dropPreview?: string\n swapPreview?: string\n dragOverlay?: string\n resizer?: string\n}\n\nexport interface DashboardContextValue {\n layout: TreeNode | null\n onLayoutChange: (newLayout: TreeNode | null) => void\n renderPane: (paneId: string) => ReactNode\n activeId: string | null\n fullscreenPaneId: string | null\n classNames: ZeugmaClassNames\n onRemove?: (paneId: string) => void\n onFullscreenChange?: (paneId: string | null) => void\n}\n\nexport const DashboardContext = createContext<DashboardContextValue | undefined>(undefined)\n\nexport const useDashboard = () => {\n const context = useContext(DashboardContext)\n if (!context) {\n throw new Error('useDashboard must be used within a DashboardProvider')\n }\n return context\n}\n\n// Tree Helper: Remove a pane and consolidate the tree\nexport function removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n return tree.paneId === idToRemove ? null : tree\n }\n const newFirst = removePane(tree.first, idToRemove)\n const newSecond = removePane(tree.second, idToRemove)\n if (newFirst === null) return newSecond\n if (newSecond === null) return newFirst\n return { ...tree, first: newFirst, second: newSecond }\n}\n\n// Tree Helper: Insert a pane by splitting an existing target\nexport function splitPane(\n tree: TreeNode | null,\n targetId: string,\n direction: SplitDirection,\n splitType: 'left' | 'right' | 'top' | 'bottom',\n paneToAdd: string,\n): TreeNode | null {\n if (tree === null) return { type: 'pane', paneId: paneToAdd }\n if (tree.type === 'pane') {\n if (tree.paneId === targetId) {\n const addedNode: PaneNode = { type: 'pane', paneId: paneToAdd }\n const originalNode: PaneNode = { type: 'pane', paneId: targetId }\n const isFirst = splitType === 'left' || splitType === 'top'\n return {\n type: 'split',\n direction,\n first: isFirst ? addedNode : originalNode,\n second: isFirst ? originalNode : addedNode,\n splitPercentage: 50,\n }\n }\n return tree\n }\n return {\n ...tree,\n first: splitPane(tree.first, targetId, direction, splitType, paneToAdd) || tree.first,\n second: splitPane(tree.second, targetId, direction, splitType, paneToAdd) || tree.second,\n }\n}\n\n// Tree Helper: Swap two pane positions in the tree\nexport function swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null {\n if (tree === null) return null\n if (tree.type === 'pane') {\n if (tree.paneId === idA) return { ...tree, paneId: idB }\n if (tree.paneId === idB) return { ...tree, paneId: idA }\n return tree\n }\n return {\n ...tree,\n first: swapPanes(tree.first, idA, idB) || tree.first,\n second: swapPanes(tree.second, idA, idB) || tree.second,\n }\n}\n\n// Tree Helper: Add a pane by recursively splitting the rightmost/bottommost pane in the tree\nexport function addPane(tree: TreeNode | null, paneToAdd: string): TreeNode {\n if (tree === null) {\n return { type: 'pane', paneId: paneToAdd }\n }\n\n function insert(node: TreeNode, parentDirection: SplitDirection | null): TreeNode {\n if (node.type === 'pane') {\n const direction: SplitDirection = parentDirection === 'row' ? 'column' : 'row'\n return {\n type: 'split',\n direction,\n splitPercentage: 50,\n first: node,\n second: { type: 'pane', paneId: paneToAdd },\n }\n }\n\n return {\n ...node,\n second: insert(node.second, node.direction),\n }\n }\n\n return insert(tree, null)\n}\n\n/** Cursor-following overlay rendered via portal */\nconst CursorOverlay: React.FC<{\n activeId: string\n render: (id: string) => ReactNode\n className?: string\n}> = ({ activeId, render, className }) => {\n const ref = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n const handleMove = (e: PointerEvent) => {\n if (ref.current) {\n ref.current.style.transform = `translate(${e.clientX + 12}px, ${e.clientY + 12}px)`\n }\n }\n document.addEventListener('pointermove', handleMove)\n return () => document.removeEventListener('pointermove', handleMove)\n }, [])\n\n return (\n <div\n ref={ref}\n className={className}\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n zIndex: 9999,\n pointerEvents: 'none',\n }}\n >\n {render(activeId)}\n </div>\n )\n}\n\ninterface DashboardProviderProps {\n layout: TreeNode | null\n onChange: (newLayout: TreeNode | null) => void\n renderPane: (paneId: string) => ReactNode\n renderDragOverlay?: (activeId: string) => ReactNode\n classNames?: ZeugmaClassNames\n fullscreenPaneId?: string | null\n onFullscreenChange?: (paneId: string | null) => void\n onRemove?: (paneId: string) => void\n dragActivationDistance?: number\n children: ReactNode\n}\n\nexport const DashboardProvider: React.FC<DashboardProviderProps> = ({\n layout,\n onChange,\n renderPane,\n renderDragOverlay,\n classNames = {},\n fullscreenPaneId = null,\n onFullscreenChange,\n onRemove,\n dragActivationDistance = 8,\n children,\n}) => {\n const [activeId, setActiveId] = useState<string | null>(null)\n\n const sensors = useSensors(\n useSensor(PointerSensor, {\n activationConstraint: { distance: dragActivationDistance },\n }),\n )\n\n const handleDragStart = (event: DragStartEvent) => {\n setActiveId(event.active.id.toString())\n }\n\n const handleDragEnd = (event: DragEndEvent) => {\n setActiveId(null)\n const { active, over } = event\n if (!over) return\n\n const draggingId = active.id.toString()\n const overIdStr = over.id.toString()\n\n // Check for center (swap) drop\n const swapMatch = overIdStr.match(/^drop-center-(.+)$/)\n if (swapMatch) {\n const [, targetId] = swapMatch\n if (draggingId !== targetId) {\n onChange(swapPanes(layout, draggingId, targetId))\n }\n return\n }\n\n // Check for edge (split) drop\n const match = overIdStr.match(/^drop-(left|right|top|bottom)-(.+)$/)\n if (!match) return\n\n const [, dropZone, targetId] = match\n if (draggingId === targetId) return\n\n const direction: SplitDirection = dropZone === 'left' || dropZone === 'right' ? 'row' : 'column'\n const treeWithoutDragging = removePane(layout, draggingId)\n\n const newLayout = splitPane(\n treeWithoutDragging,\n targetId,\n direction,\n dropZone as 'left' | 'right' | 'top' | 'bottom',\n draggingId,\n )\n onChange(newLayout)\n }\n\n return (\n <DashboardContext.Provider\n value={{\n layout,\n onLayoutChange: onChange,\n renderPane,\n activeId,\n fullscreenPaneId,\n classNames,\n onRemove,\n onFullscreenChange,\n }}\n >\n <DndContext\n sensors={sensors}\n collisionDetection={pointerWithin}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n >\n {children}\n </DndContext>\n {activeId && renderDragOverlay && (\n <CursorOverlay\n activeId={activeId}\n render={renderDragOverlay}\n className={classNames.dragOverlay}\n />\n )}\n </DashboardContext.Provider>\n )\n}\n","import React, { useRef } from 'react'\nimport { useDashboard } from './dashboard-provider'\nimport { TreeNode, SplitNode } from '../types'\n\ninterface PaneTreeProps {\n tree?: TreeNode | null\n /** Size of the resizer in pixels (default 4) */\n resizerSize?: number\n}\n\nfunction updateSplitPercentage(\n tree: TreeNode | null,\n target: SplitNode,\n newPercentage: number,\n): TreeNode | null {\n if (tree === null) return null\n if (tree === target) {\n return { ...tree, splitPercentage: newPercentage } as SplitNode\n }\n if (tree.type === 'split') {\n return {\n ...tree,\n first: updateSplitPercentage(tree.first, target, newPercentage) || tree.first,\n second: updateSplitPercentage(tree.second, target, newPercentage) || tree.second,\n }\n }\n return tree\n}\n\nexport const PaneTree: React.FC<PaneTreeProps> = ({ tree, resizerSize = 4 }) => {\n const { layout, onLayoutChange, renderPane, fullscreenPaneId, classNames } = useDashboard()\n const containerRef = useRef<HTMLDivElement>(null)\n\n // Fullscreen bypass\n if (fullscreenPaneId && !tree) {\n return (\n <div style={{ width: '100%', height: '100%', position: 'relative' }}>\n {renderPane(fullscreenPaneId)}\n </div>\n )\n }\n\n const currentNode = tree !== undefined ? tree : layout\n\n if (!currentNode) return null\n\n if (currentNode.type === 'pane') {\n return (\n <div style={{ width: '100%', height: '100%', position: 'relative' }}>\n {renderPane(currentNode.paneId)}\n </div>\n )\n }\n\n const { direction, first, second, splitPercentage } = currentNode\n const isRow = direction === 'row'\n\n const handlePointerDown = (e: React.PointerEvent) => {\n e.preventDefault()\n const container = containerRef.current\n if (!container) return\n\n document.body.classList.add('zeugma-resizing')\n\n const rect = container.getBoundingClientRect()\n const startX = e.clientX\n const startY = e.clientY\n const startPercentage = splitPercentage\n\n const handlePointerMove = (moveEvent: PointerEvent) => {\n const delta = isRow\n ? ((moveEvent.clientX - startX) / rect.width) * 100\n : ((moveEvent.clientY - startY) / rect.height) * 100\n const newPercentage = Math.max(5, Math.min(95, startPercentage + delta))\n const newLayout = updateSplitPercentage(layout, currentNode, newPercentage)\n onLayoutChange(newLayout)\n }\n\n const handlePointerUp = () => {\n document.body.classList.remove('zeugma-resizing')\n document.removeEventListener('pointermove', handlePointerMove)\n document.removeEventListener('pointerup', handlePointerUp)\n }\n\n document.addEventListener('pointermove', handlePointerMove)\n document.addEventListener('pointerup', handlePointerUp)\n }\n\n return (\n <div\n ref={containerRef}\n style={{\n display: 'flex',\n flexDirection: isRow ? 'row' : 'column',\n width: '100%',\n height: '100%',\n overflow: 'hidden',\n }}\n >\n <div style={{ flex: `${splitPercentage} 1 0%`, overflow: 'hidden' }}>\n <PaneTree tree={first} resizerSize={resizerSize} />\n </div>\n <div\n className={classNames.resizer}\n style={{\n width: isRow ? `${resizerSize}px` : '100%',\n height: isRow ? '100%' : `${resizerSize}px`,\n cursor: isRow ? 'col-resize' : 'row-resize',\n position: 'relative',\n zIndex: 10,\n userSelect: 'none',\n boxSizing: 'border-box',\n flexShrink: 0,\n }}\n onPointerDown={handlePointerDown}\n role=\"separator\"\n aria-valuenow={splitPercentage}\n aria-valuemin={5}\n aria-valuemax={95}\n />\n <div style={{ flex: `${100 - splitPercentage} 1 0%`, overflow: 'hidden' }}>\n <PaneTree tree={second} resizerSize={resizerSize} />\n </div>\n </div>\n )\n}\n","import React, { createContext, useContext } from 'react'\nimport { useDraggable, useDroppable } from '@dnd-kit/core'\nimport { useDashboard } from './dashboard-provider'\n\n// Internal context for drag listeners\nconst DragListenersCtx = createContext<Record<string, unknown> | null>(null)\n\ninterface DropZoneProps {\n id: string\n position: 'top' | 'bottom' | 'left' | 'right' | 'center'\n activeClassName?: string\n}\n\nconst activationPositions: Record<string, React.CSSProperties> = {\n top: {\n position: 'absolute',\n top: 0,\n left: '25%',\n width: '50%',\n height: '25%',\n zIndex: 20,\n pointerEvents: 'auto',\n },\n bottom: {\n position: 'absolute',\n bottom: 0,\n left: '25%',\n width: '50%',\n height: '25%',\n zIndex: 20,\n pointerEvents: 'auto',\n },\n left: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n left: 0,\n width: '25%',\n height: '100%',\n zIndex: 20,\n pointerEvents: 'auto',\n },\n right: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n right: 0,\n width: '25%',\n height: '100%',\n zIndex: 20,\n pointerEvents: 'auto',\n },\n center: {\n position: 'absolute',\n top: '25%',\n left: '25%',\n width: '50%',\n height: '50%',\n zIndex: 20,\n pointerEvents: 'auto',\n },\n}\n\nconst previewPositions: Record<string, React.CSSProperties> = {\n top: {\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n height: '50%',\n zIndex: 21,\n pointerEvents: 'none',\n boxSizing: 'border-box',\n },\n bottom: {\n position: 'absolute',\n bottom: 0,\n left: 0,\n right: 0,\n height: '50%',\n zIndex: 21,\n pointerEvents: 'none',\n boxSizing: 'border-box',\n },\n left: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n left: 0,\n width: '50%',\n zIndex: 21,\n pointerEvents: 'none',\n boxSizing: 'border-box',\n },\n right: {\n position: 'absolute',\n top: 0,\n bottom: 0,\n right: 0,\n width: '50%',\n zIndex: 21,\n pointerEvents: 'none',\n boxSizing: 'border-box',\n },\n center: {\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 21,\n pointerEvents: 'none',\n boxSizing: 'border-box',\n },\n}\n\nconst DropZone: React.FC<DropZoneProps> = ({ id, position, activeClassName }) => {\n const { setNodeRef, isOver } = useDroppable({ id })\n return (\n <>\n <div ref={setNodeRef} style={activationPositions[position]} />\n {isOver && <div className={activeClassName} style={previewPositions[position]} />}\n </>\n )\n}\n\nexport interface PaneRenderProps {\n isDragging: boolean\n isFullscreen: boolean\n toggleFullscreen: () => void\n remove: () => void\n}\n\ninterface PaneProps {\n id: string\n children: (props: PaneRenderProps) => React.ReactNode\n style?: React.CSSProperties\n}\n\nexport const Pane: React.FC<PaneProps> = ({ id, children, style }) => {\n const { activeId, classNames, fullscreenPaneId, onRemove, onFullscreenChange } = useDashboard()\n const showDropZones = activeId !== null && activeId !== id\n\n const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ id })\n const dragging = activeId === id || isDragging\n const isFullscreen = fullscreenPaneId === id\n\n const renderProps: PaneRenderProps = {\n isDragging: dragging,\n isFullscreen,\n toggleFullscreen: () => onFullscreenChange?.(isFullscreen ? null : id),\n remove: () => {\n if (isFullscreen) {\n onFullscreenChange?.(null)\n }\n onRemove?.(id)\n },\n }\n\n return (\n <DragListenersCtx.Provider value={{ ...listeners, ...attributes }}>\n <div\n ref={setNodeRef}\n className={classNames.pane}\n style={{ position: 'relative', width: '100%', height: '100%', ...style }}\n >\n {children(renderProps)}\n\n {showDropZones && (\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 15,\n pointerEvents: 'none',\n }}\n >\n {(['top', 'bottom', 'left', 'right'] as const).map((pos) => (\n <DropZone\n key={pos}\n id={`drop-${pos}-${id}`}\n position={pos}\n activeClassName={classNames.dropPreview}\n />\n ))}\n <DropZone\n id={`drop-center-${id}`}\n position=\"center\"\n activeClassName={classNames.swapPreview}\n />\n </div>\n )}\n </div>\n </DragListenersCtx.Provider>\n )\n}\n\n/**\n * Place inside a Pane to make an element the drag handle.\n */\ninterface DragHandleProps {\n children: React.ReactNode\n className?: string\n style?: React.CSSProperties\n}\n\nexport const DragHandle: React.FC<DragHandleProps> = ({ children, className, style }) => {\n const dragProps = useContext(DragListenersCtx)\n if (!dragProps) {\n throw new Error('<DragHandle> must be used inside a <Pane>')\n }\n return (\n <div\n className={className}\n style={{ cursor: 'grab', userSelect: 'none', ...style }}\n {...dragProps}\n >\n {children}\n </div>\n )\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-zeugma",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Recursive drag-and-drop dashboard layout engine for React — combining the tree-based splitting of react-mosaic with the declarative API of react-grid-layout.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -56,9 +56,10 @@
56
56
  "typecheck": "tsc --noEmit",
57
57
  "format": "prettier --write \"src/**/*.{ts,tsx}\" \"docs/**/*.{ts,tsx,mdx}\"",
58
58
  "format:check": "prettier --check \"src/**/*.{ts,tsx}\" \"docs/**/*.{ts,tsx,mdx}\"",
59
- "demo": "npm run dev --workspace=demo",
59
+ "demo": "npm run dev --workspace=homepage",
60
+ "build:demo": "npm run build && npm run build --workspace=homepage",
60
61
  "storybook": "storybook dev -p 6006",
61
- "build-storybook": "storybook build -o storybook-static",
62
+ "build:storybook": "storybook build -o storybook-static",
62
63
  "prepare": "husky",
63
64
  "version": "changeset version",
64
65
  "release": "npm run build && changeset publish"
@@ -69,7 +70,7 @@
69
70
  },
70
71
  "workspaces": [
71
72
  ".",
72
- "demo"
73
+ "homepage"
73
74
  ],
74
75
  "dependencies": {
75
76
  "@dnd-kit/core": "^6.3.1",