react-zeugma 0.1.2 → 0.3.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,242 @@ 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
+ | `dragActivationDistance` | `number` | No | Minimum pointer drag distance (in pixels) required to activate dragging. Defaults to `8`. |
121
+
122
+ ### `<PaneTree>`
123
+
124
+ Recursively renders the split nodes and pane nodes. Must be placed inside `<DashboardProvider>`.
125
+
126
+ | Prop | Type | Required | Description |
127
+ | ------------- | ------------------ | -------- | ------------------------------------------------------------------- |
128
+ | `tree` | `TreeNode \| null` | No | Custom subtree to render. Defaults to the provider's root `layout`. |
129
+ | `resizerSize` | `number` | No | Thickness of the split resizer bars in pixels. Defaults to `4`. |
93
130
 
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). |
131
+ ### `<Pane id>`
100
132
 
101
- ### Utilities
133
+ Wraps the individual pane components inside the renderer. Utilizes a render prop passing active layout attributes.
102
134
 
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. |
135
+ | Prop | Type | Required | Description |
136
+ | ---------- | --------------------------------------- | -------- | ------------------------------------------------------- |
137
+ | `id` | `string` | Yes | The unique ID corresponding to a `PaneNode`'s `paneId`. |
138
+ | `children` | `(props: PaneRenderProps) => ReactNode` | Yes | Render prop function. |
108
139
 
109
- ### Types
140
+ #### Render Props: `PaneRenderProps`
110
141
 
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. |
142
+ | Parameter | Type | Description |
143
+ | ------------------ | ------------ | ------------------------------------------------------------- |
144
+ | `isDragging` | `boolean` | Returns `true` if the node wrapper is actively being dragged. |
145
+ | `isFullscreen` | `boolean` | Returns `true` if the pane is zoomed/fullscreen. |
146
+ | `toggleFullscreen` | `() => void` | Callback to toggle fullscreen viewport coverage. |
147
+ | `remove` | `() => void` | Triggers removal of this pane from the layout tree. |
148
+
149
+ ### `<DragHandle>`
150
+
151
+ Defines the interactive drag region inside a `<Pane>`. **Must be placed inside a `<Pane>` component.**
152
+
153
+ | Prop | Type | Required | Description |
154
+ | ----------- | --------------------- | -------- | ---------------------------------------------------------------- |
155
+ | `children` | `React.ReactNode` | Yes | Element(s) that function as the drag handle (e.g., pane header). |
156
+ | `className` | `string` | No | Custom CSS class for the drag handle wrapper. |
157
+ | `style` | `React.CSSProperties` | No | Inline styles for the drag handle wrapper. |
117
158
 
118
159
  ---
119
160
 
120
- ## Documentation
161
+ ## Tree Utilities
121
162
 
122
- Run the interactive demo or Storybook locally:
163
+ react-zeugma exposes serializable tree utility functions for programmatically mutating layout schemas.
123
164
 
124
- ```bash
125
- npm run demo # Vite demo app
126
- npm run storybook # component docs & examples
165
+ #### `removePane(tree: TreeNode | null, id: string): TreeNode | null`
166
+
167
+ Recursively scans the layout tree, removes the targeted pane node, and collapses redundant split boundaries.
168
+
169
+ #### `addPane(tree: TreeNode | null, paneToAdd: string): TreeNode`
170
+
171
+ Recursively matches the bottommost/rightmost pane leaf in the tree, splits it, and inserts the target `paneToAdd`.
172
+
173
+ #### `swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null`
174
+
175
+ Swaps the positions of `idA` and `idB` nodes directly inside the tree structure.
176
+
177
+ #### `splitPane(tree, targetId, direction, splitType, paneToAdd)`
178
+
179
+ Splits the targeted `targetId` pane inside the tree with `direction` (_row_ / _column_) and type (_left_, _right_, _top_, _bottom_) to insert `paneToAdd`.
180
+
181
+ ---
182
+
183
+ ## Custom Styling
184
+
185
+ Use custom CSS or styling rules to style resizers, dragging states, drop previews, or active nodes by overriding `classNames` in the provider.
186
+
187
+ ```tsx
188
+ <DashboardProvider
189
+ layout={layout}
190
+ onChange={setLayout}
191
+ renderPane={renderPane}
192
+ classNames={{
193
+ // resizer handles
194
+ resizer:
195
+ 'bg-transparent hover:bg-indigo-500/50 active:bg-indigo-500 transition-colors duration-150',
196
+ // split previews
197
+ dropPreview: 'bg-indigo-500/10 border-2 border-dashed border-indigo-500/50 backdrop-blur-xs',
198
+ // swap previews
199
+ swapPreview: 'bg-amber-500/10 border-2 border-dashed border-amber-500/50 backdrop-blur-xs',
200
+ }}
201
+ >
202
+ <PaneTree />
203
+ </DashboardProvider>
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Types Reference
209
+
210
+ Full TypeScript type definitions utilized in the dashboard layout configuration.
211
+
212
+ ```ts
213
+ export type SplitDirection = 'row' | 'column'
214
+
215
+ export interface SplitNode {
216
+ type: 'split'
217
+ direction: SplitDirection
218
+ first: TreeNode
219
+ second: TreeNode
220
+ splitPercentage: number
221
+ }
222
+
223
+ export interface PaneNode {
224
+ type: 'pane'
225
+ paneId: string
226
+ }
227
+
228
+ export type TreeNode = SplitNode | PaneNode
229
+
230
+ export interface ZeugmaClassNames {
231
+ pane?: string
232
+ dropPreview?: string
233
+ swapPreview?: string
234
+ dragOverlay?: string
235
+ resizer?: string
236
+ }
237
+
238
+ export interface PaneRenderProps {
239
+ isDragging: boolean
240
+ isFullscreen: boolean
241
+ toggleFullscreen: () => void
242
+ remove: () => void
243
+ }
127
244
  ```
128
245
 
129
246
  ---
130
247
 
248
+ ## SKILL.md
249
+
250
+ 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).
251
+
252
+ ---
253
+
131
254
  ## Local Development
132
255
 
133
256
  ```bash
@@ -160,9 +283,10 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on how to get started.
160
283
 
161
284
  ---
162
285
 
163
- <div align="center">
286
+ ## The Story of Zeugma
164
287
 
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.**
288
+ _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
289
 
168
- </div>
290
+ 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.
291
+
292
+ > _"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,7 @@
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 L=react.createContext(void 0);var N=()=>{let e=react.useContext(L);if(!e)throw new Error("useDashboard must be used within a DashboardProvider");return e};function z(e,t){if(e===null)return null;if(e.type==="pane")return e.paneId===t?null:e;let n=z(e.first,t),o=z(e.second,t);return n===null?o:o===null?n:{...e,first:n,second:o}}function F(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},d={type:"pane",paneId:t},s=o==="left"||o==="top";return {type:"split",direction:n,first:s?i:d,second:s?d:i,splitPercentage:50}}return e}return {...e,first:F(e.first,t,n,o,r)||e.first,second:F(e.second,t,n,o,r)||e.second}}function A(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:A(e.first,t,n)||e.first,second:A(e.second,t,n)||e.second}}function we(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)}function H(e,t,n){return e===null?null:e===t?{...e,splitPercentage:n}:e.type==="split"?{...e,first:H(e.first,t,n)||e.first,second:H(e.second,t,n)||e.second}:e}var q=8,B=8,Le=4;var ie=({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)})},se=({layout:e,onChange:t,renderPane:n,renderDragOverlay:o,classNames:r={},fullscreenPaneId:i=null,onFullscreenChange:d,onRemove:s,dragActivationDistance:c=8,snapThreshold:u=8,children:m})=>{let[l,a]=react.useState(null),R=core.useSensors(core.useSensor(core.PointerSensor,{activationConstraint:{distance:c}})),b=g=>{a(g.active.id.toString());},S=g=>{a(null);let{active:Z,over:y}=g;if(!y)return;let h=Z.id.toString(),p=y.id.toString(),f=p.match(/^drop-center-(.+)$/);if(f){let[,T]=f;h!==T&&t(A(e,h,T));return}let x=p.match(/^drop-(left|right|top|bottom)-(.+)$/);if(!x)return;let[,I,E]=x;if(h===E)return;let D=I==="left"||I==="right"?"row":"column",w=z(e,h),_=F(w,E,D,I,h);t(_);},P=react.useMemo(()=>({layout:e,onLayoutChange:t,renderPane:n,activeId:l,fullscreenPaneId:i,classNames:r,onRemove:s,onFullscreenChange:d,snapThreshold:u}),[e,t,n,l,i,r,s,d,u]);return jsxRuntime.jsxs(L.Provider,{value:P,children:[jsxRuntime.jsx(core.DndContext,{sensors:R,collisionDetection:core.pointerWithin,onDragStart:b,onDragEnd:S,children:m}),l&&o&&jsxRuntime.jsx(ie,{activeId:l,render:o,className:r.dragOverlay})]})};function U({containerRef:e,isRow:t,direction:n,splitPercentage:o,resizerSize:r,snapThreshold:i,layout:d,currentNode:s,onLayoutChange:c}){return react.useCallback(u=>{u.preventDefault();let m=e.current;if(!m)return;document.body.classList.add("zeugma-resizing");let l=document.createElement("style");l.id="zeugma-global-cursor-style",l.textContent=`
2
+ * {
3
+ cursor: ${t?"col-resize":"row-resize"} !important;
4
+ user-select: none !important;
5
+ }
6
+ `,document.head.appendChild(l);let a=m.getBoundingClientRect(),R=u.clientX,b=u.clientY,S=o,P=u.currentTarget;P.setAttribute("data-resizing","true");let Z=Array.from(document.querySelectorAll('div[role="separator"][data-direction]')).filter(p=>p!==P&&p.getAttribute("data-direction")===n).map(p=>{let f=p.getBoundingClientRect();return t?f.left+f.width/2:f.top+f.height/2}),y=p=>{let f=t?(p.clientX-R)/a.width*100:(p.clientY-b)/a.height*100,x=S+f,I=t?a.left+(a.width-r)*(x/100)+r/2:a.top+(a.height-r)*(x/100)+r/2,E=1/0,D=null;for(let X of Z){let $=Math.abs(I-X);$<i&&$<E&&(E=$,D=X);}let w=x;D!==null&&(w=t?(D-r/2-a.left)/(a.width-r)*100:(D-r/2-a.top)/(a.height-r)*100);let _=Math.max(5,Math.min(95,w)),T=H(d,s,_);c(T);},h=()=>{document.body.classList.remove("zeugma-resizing"),P.removeAttribute("data-resizing");let p=document.getElementById("zeugma-global-cursor-style");p&&p.remove(),document.removeEventListener("pointermove",y),document.removeEventListener("pointerup",h);};document.addEventListener("pointermove",y),document.addEventListener("pointerup",h);},[e,t,n,o,r,i,d,s,c])}var ce=({currentNode:e,resizerSize:t,snapThreshold:n})=>{let{layout:o,onLayoutChange:r,classNames:i}=N(),d=react.useRef(null),{direction:s,first:c,second:u,splitPercentage:m}=e,l=s==="row",a=U({containerRef:d,isRow:l,direction:s,splitPercentage:m,resizerSize:t,snapThreshold:n??8,layout:o,currentNode:e,onLayoutChange:r});return jsxRuntime.jsxs("div",{ref:d,style:{display:"flex",flexDirection:l?"row":"column",width:"100%",height:"100%",overflow:"hidden"},children:[jsxRuntime.jsx("div",{style:{flex:`${m} 1 0%`,overflow:"hidden"},children:jsxRuntime.jsx(V,{tree:c,resizerSize:t,snapThreshold:n})}),jsxRuntime.jsx("div",{className:i.resizer,"data-direction":s,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:a,role:"separator","aria-valuenow":m,"aria-valuemin":5,"aria-valuemax":95}),jsxRuntime.jsx("div",{style:{flex:`${100-m} 1 0%`,overflow:"hidden"},children:jsxRuntime.jsx(V,{tree:u,resizerSize:t,snapThreshold:n})})]})},V=({tree:e,resizerSize:t=4,snapThreshold:n})=>{let{layout:o,renderPane:r,fullscreenPaneId:i,snapThreshold:d}=N(),s=n!==void 0?n:d;if(i&&!e)return jsxRuntime.jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(i)});let c=e!==void 0?e:o;return c?c.type==="pane"?jsxRuntime.jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(c.paneId)}):jsxRuntime.jsx(ce,{currentNode:c,resizerSize:t,snapThreshold:s}):null};var M=react.createContext(null);var he={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"}},ve={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"}},Y=({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:he[t]}),r&&jsxRuntime.jsx("div",{className:n,style:ve[t]})]})},be=({id:e,children:t,style:n})=>{let{activeId:o,classNames:r,fullscreenPaneId:i,onRemove:d,onFullscreenChange:s}=N(),c=o!==null&&o!==e,{attributes:u,listeners:m,setNodeRef:l,isDragging:a}=core.useDraggable({id:e}),R=o===e||a,b=i===e,S={isDragging:R,isFullscreen:b,toggleFullscreen:()=>s?.(b?null:e),remove:()=>{b&&s?.(null),d?.(e);}},P=react.useMemo(()=>({...m,...u}),[m,u]);return jsxRuntime.jsx(M.Provider,{value:P,children:jsxRuntime.jsxs("div",{ref:l,className:r.pane,style:{position:"relative",width:"100%",height:"100%",...n},children:[t(S),c&&jsxRuntime.jsxs("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:15,pointerEvents:"none"},children:[["top","bottom","left","right"].map(g=>jsxRuntime.jsx(Y,{id:`drop-${g}-${e}`,position:g,activeClassName:r.dropPreview},g)),jsxRuntime.jsx(Y,{id:`drop-center-${e}`,position:"center",activeClassName:r.swapPreview})]})]})})};var De=({children:e,className:t,style:n})=>{let o=react.useContext(M);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.DEFAULT_DRAG_ACTIVATION_DISTANCE=B;exports.DEFAULT_RESIZER_SIZE=Le;exports.DEFAULT_SNAP_THRESHOLD=q;exports.DashboardProvider=se;exports.DragHandle=De;exports.Pane=be;exports.PaneTree=V;exports.addPane=we;exports.removePane=z;exports.splitPane=F;exports.swapPanes=A;exports.updateSplitPercentage=H;exports.useDashboard=N;exports.useResizer=U;//# sourceMappingURL=index.cjs.map
2
7
  //# 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/entities/dashboard/model/context.ts","../src/entities/dashboard/model/hooks.ts","../src/shared/lib/tree/tree-helpers.ts","../src/shared/config/constants.ts","../src/entities/dashboard/ui/DashboardProvider.tsx","../src/features/resize-pane/hooks/useResizer.ts","../src/widgets/pane-tree/ui/PaneTree.tsx","../src/entities/pane/model/context.ts","../src/entities/pane/ui/Pane.tsx","../src/entities/pane/ui/DragHandle.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","updateSplitPercentage","target","newPercentage","DEFAULT_SNAP_THRESHOLD","DEFAULT_DRAG_ACTIVATION_DISTANCE","DEFAULT_RESIZER_SIZE","CursorOverlay","activeId","render","className","ref","useRef","useEffect","handleMove","e","jsx","DashboardProvider","layout","onChange","renderPane","renderDragOverlay","classNames","fullscreenPaneId","onFullscreenChange","onRemove","dragActivationDistance","snapThreshold","children","setActiveId","useState","sensors","useSensors","useSensor","PointerSensor","handleDragStart","event","handleDragEnd","active","over","draggingId","overIdStr","swapMatch","match","dropZone","treeWithoutDragging","newLayout","contextValue","useMemo","jsxs","DndContext","pointerWithin","useResizer","containerRef","isRow","splitPercentage","resizerSize","currentNode","onLayoutChange","useCallback","container","styleEl","rect","startX","startY","startPercentage","resizerEl","otherPositions","el","r","handlePointerMove","moveEvent","delta","proposedPercentage","proposedPos","closestDistance","bestTarget","pos","dist","snappedPercentage","finalPercentage","handlePointerUp","globalStyle","PaneSplit","first","second","handlePointerDown","PaneTree","propSnapThreshold","contextSnapThreshold","DragListenersCtx","activationPositions","previewPositions","DropZone","id","position","activeClassName","setNodeRef","isOver","useDroppable","Fragment","Pane","style","showDropZones","attributes","listeners","isDragging","useDraggable","dragging","isFullscreen","renderProps","DragHandle","dragProps"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createContext, ReactNode } from 'react'\nimport { TreeNode } from '../../../shared/model'\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 snapThreshold?: number\n}\n\nexport const DashboardContext = createContext<DashboardContextValue | undefined>(undefined)\n","import { useContext } from 'react'\nimport { DashboardContext } from './context'\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","import { TreeNode, SplitNode, SplitDirection, PaneNode } from '../../model'\n\n/**\n * Tree Helper: Remove a pane and consolidate the tree structure.\n */\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/**\n * Tree Helper: Insert a pane by splitting an existing target node.\n */\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/**\n * Tree Helper: Swap the position of two panes in the tree structure.\n */\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/**\n * Tree Helper: Add a pane by recursively splitting the rightmost/bottommost pane in the tree.\n */\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/**\n * Tree Helper: Update split percentage recursively.\n */\nexport function 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","export const DEFAULT_SNAP_THRESHOLD = 8 // px\nexport const DEFAULT_DRAG_ACTIVATION_DISTANCE = 8 // px\nexport const DEFAULT_RESIZER_SIZE = 4 // px\n","import React, { useState, useEffect, useRef, ReactNode, useMemo } 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 } from '../../../shared/model'\nimport { removePane, splitPane, swapPanes } from '../../../shared/lib/tree'\nimport { DEFAULT_DRAG_ACTIVATION_DISTANCE, DEFAULT_SNAP_THRESHOLD } from '../../../shared/config'\nimport { DashboardContext, ZeugmaClassNames } from '../model/context'\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 snapThreshold?: 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 = DEFAULT_DRAG_ACTIVATION_DISTANCE,\n snapThreshold = DEFAULT_SNAP_THRESHOLD,\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 // Best practice: Memoize context value to prevent unnecessary re-renders of context consumers.\n const contextValue = useMemo(\n () => ({\n layout,\n onLayoutChange: onChange,\n renderPane,\n activeId,\n fullscreenPaneId,\n classNames,\n onRemove,\n onFullscreenChange,\n snapThreshold,\n }),\n [\n layout,\n onChange,\n renderPane,\n activeId,\n fullscreenPaneId,\n classNames,\n onRemove,\n onFullscreenChange,\n snapThreshold,\n ],\n )\n\n return (\n <DashboardContext.Provider value={contextValue}>\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, { useCallback } from 'react'\nimport { TreeNode, SplitNode, SplitDirection } from '../../../shared/model'\nimport { updateSplitPercentage } from '../../../shared/lib/tree'\n\ninterface UseResizerProps {\n containerRef: React.RefObject<HTMLDivElement | null>\n isRow: boolean\n direction: SplitDirection\n splitPercentage: number\n resizerSize: number\n snapThreshold: number\n layout: TreeNode | null\n currentNode: SplitNode\n onLayoutChange: (newLayout: TreeNode | null) => void\n}\n\nexport function useResizer({\n containerRef,\n isRow,\n direction,\n splitPercentage,\n resizerSize,\n snapThreshold,\n layout,\n currentNode,\n onLayoutChange,\n}: UseResizerProps) {\n return useCallback(\n (e: React.PointerEvent<HTMLDivElement>) => {\n e.preventDefault()\n const container = containerRef.current\n if (!container) return\n\n document.body.classList.add('zeugma-resizing')\n\n // Inject global cursor style to keep resizing cursor active across the entire page during drag\n const styleEl = document.createElement('style')\n styleEl.id = 'zeugma-global-cursor-style'\n styleEl.textContent = `\n * {\n cursor: ${isRow ? 'col-resize' : 'row-resize'} !important;\n user-select: none !important;\n }\n `\n document.head.appendChild(styleEl)\n\n const rect = container.getBoundingClientRect()\n const startX = e.clientX\n const startY = e.clientY\n const startPercentage = splitPercentage\n\n // Cache other resizers of the same direction once at drag-start to prevent layout thrashing on move\n const resizerEl = e.currentTarget\n resizerEl.setAttribute('data-resizing', 'true')\n\n const otherResizers = Array.from(\n document.querySelectorAll('div[role=\"separator\"][data-direction]'),\n ).filter((el) => el !== resizerEl && el.getAttribute('data-direction') === direction)\n\n const otherPositions = otherResizers.map((el) => {\n const r = el.getBoundingClientRect()\n return isRow ? r.left + r.width / 2 : r.top + r.height / 2\n })\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 proposedPercentage = startPercentage + delta\n\n // Find physical position corresponding to proposed percentage\n const proposedPos = isRow\n ? rect.left + (rect.width - resizerSize) * (proposedPercentage / 100) + resizerSize / 2\n : rect.top + (rect.height - resizerSize) * (proposedPercentage / 100) + resizerSize / 2\n\n let closestDistance = Infinity\n let bestTarget: number | null = null\n\n for (const pos of otherPositions) {\n const dist = Math.abs(proposedPos - pos)\n if (dist < snapThreshold && dist < closestDistance) {\n closestDistance = dist\n bestTarget = pos\n }\n }\n\n let snappedPercentage = proposedPercentage\n if (bestTarget !== null) {\n snappedPercentage = isRow\n ? ((bestTarget - resizerSize / 2 - rect.left) / (rect.width - resizerSize)) * 100\n : ((bestTarget - resizerSize / 2 - rect.top) / (rect.height - resizerSize)) * 100\n }\n\n const finalPercentage = Math.max(5, Math.min(95, snappedPercentage))\n const newLayout = updateSplitPercentage(layout, currentNode, finalPercentage)\n onLayoutChange(newLayout)\n }\n\n const handlePointerUp = () => {\n document.body.classList.remove('zeugma-resizing')\n resizerEl.removeAttribute('data-resizing')\n\n const globalStyle = document.getElementById('zeugma-global-cursor-style')\n if (globalStyle) {\n globalStyle.remove()\n }\n\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 containerRef,\n isRow,\n direction,\n splitPercentage,\n resizerSize,\n snapThreshold,\n layout,\n currentNode,\n onLayoutChange,\n ],\n )\n}\n","import React, { useRef } from 'react'\nimport { useDashboard } from '../../../entities/dashboard'\nimport { useResizer } from '../../../features/resize-pane'\nimport { TreeNode, SplitNode } from '../../../shared/model'\n\nexport interface PaneTreeProps {\n tree?: TreeNode | null\n /** Size of the resizer in pixels (default 4) */\n resizerSize?: number\n /** Threshold in pixels to snap to adjacent resizer edges (default 8) */\n snapThreshold?: number\n}\n\ninterface PaneSplitProps {\n currentNode: SplitNode\n resizerSize: number\n snapThreshold?: number\n}\n\nconst PaneSplit: React.FC<PaneSplitProps> = ({ currentNode, resizerSize, snapThreshold }) => {\n const { layout, onLayoutChange, classNames } = useDashboard()\n\n const containerRef = useRef<HTMLDivElement>(null)\n const { direction, first, second, splitPercentage } = currentNode\n const isRow = direction === 'row'\n\n const handlePointerDown = useResizer({\n containerRef,\n isRow,\n direction,\n splitPercentage,\n resizerSize,\n snapThreshold: snapThreshold ?? 8,\n layout,\n currentNode,\n onLayoutChange,\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} snapThreshold={snapThreshold} />\n </div>\n <div\n className={classNames.resizer}\n data-direction={direction}\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} snapThreshold={snapThreshold} />\n </div>\n </div>\n )\n}\n\nexport const PaneTree: React.FC<PaneTreeProps> = ({\n tree,\n resizerSize = 4,\n snapThreshold: propSnapThreshold,\n}) => {\n const {\n layout,\n renderPane,\n fullscreenPaneId,\n snapThreshold: contextSnapThreshold,\n } = useDashboard()\n\n const snapThreshold = propSnapThreshold !== undefined ? propSnapThreshold : contextSnapThreshold\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 return (\n <PaneSplit currentNode={currentNode} resizerSize={resizerSize} snapThreshold={snapThreshold} />\n )\n}\n","import { createContext } from 'react'\n\nexport const DragListenersCtx = createContext<Record<string, unknown> | null>(null)\n","import React, { useMemo } from 'react'\nimport { useDraggable, useDroppable } from '@dnd-kit/core'\nimport { useDashboard } from '../../dashboard'\nimport { DragListenersCtx } from '../model/context'\nimport { PaneRenderProps } from '../model/types'\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\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 // Best practice: Memoize drag context value to prevent unnecessary re-renders of the drag handle.\n const contextValue = useMemo(\n () => ({\n ...listeners,\n ...attributes,\n }),\n [listeners, attributes],\n )\n\n return (\n <DragListenersCtx.Provider value={contextValue}>\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","import React, { useContext } from 'react'\nimport { DragListenersCtx } from '../model/context'\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
@@ -30,11 +30,11 @@ interface DashboardContextValue {
30
30
  classNames: ZeugmaClassNames;
31
31
  onRemove?: (paneId: string) => void;
32
32
  onFullscreenChange?: (paneId: string | null) => void;
33
+ snapThreshold?: number;
33
34
  }
35
+
34
36
  declare const useDashboard: () => DashboardContextValue;
35
- declare function removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null;
36
- declare function splitPane(tree: TreeNode | null, targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string): TreeNode | null;
37
- declare function swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null;
37
+
38
38
  interface DashboardProviderProps {
39
39
  layout: TreeNode | null;
40
40
  onChange: (newLayout: TreeNode | null) => void;
@@ -44,14 +44,31 @@ interface DashboardProviderProps {
44
44
  fullscreenPaneId?: string | null;
45
45
  onFullscreenChange?: (paneId: string | null) => void;
46
46
  onRemove?: (paneId: string) => void;
47
+ dragActivationDistance?: number;
48
+ snapThreshold?: number;
47
49
  children: ReactNode;
48
50
  }
49
51
  declare const DashboardProvider: React.FC<DashboardProviderProps>;
50
52
 
53
+ interface UseResizerProps {
54
+ containerRef: React.RefObject<HTMLDivElement | null>;
55
+ isRow: boolean;
56
+ direction: SplitDirection;
57
+ splitPercentage: number;
58
+ resizerSize: number;
59
+ snapThreshold: number;
60
+ layout: TreeNode | null;
61
+ currentNode: SplitNode;
62
+ onLayoutChange: (newLayout: TreeNode | null) => void;
63
+ }
64
+ declare function useResizer({ containerRef, isRow, direction, splitPercentage, resizerSize, snapThreshold, layout, currentNode, onLayoutChange, }: UseResizerProps): (e: React.PointerEvent<HTMLDivElement>) => void;
65
+
51
66
  interface PaneTreeProps {
52
67
  tree?: TreeNode | null;
53
68
  /** Size of the resizer in pixels (default 4) */
54
69
  resizerSize?: number;
70
+ /** Threshold in pixels to snap to adjacent resizer edges (default 8) */
71
+ snapThreshold?: number;
55
72
  }
56
73
  declare const PaneTree: React.FC<PaneTreeProps>;
57
74
 
@@ -61,15 +78,14 @@ interface PaneRenderProps {
61
78
  toggleFullscreen: () => void;
62
79
  remove: () => void;
63
80
  }
81
+
64
82
  interface PaneProps {
65
83
  id: string;
66
84
  children: (props: PaneRenderProps) => React.ReactNode;
67
85
  style?: React.CSSProperties;
68
86
  }
69
87
  declare const Pane: React.FC<PaneProps>;
70
- /**
71
- * Place inside a Pane to make an element the drag handle.
72
- */
88
+
73
89
  interface DragHandleProps {
74
90
  children: React.ReactNode;
75
91
  className?: string;
@@ -77,4 +93,29 @@ interface DragHandleProps {
77
93
  }
78
94
  declare const DragHandle: React.FC<DragHandleProps>;
79
95
 
80
- export { DashboardProvider, DragHandle, Pane, type PaneNode, type PaneRenderProps, PaneTree, type SplitDirection, type SplitNode, type TreeNode, type ZeugmaClassNames, removePane, splitPane, swapPanes, useDashboard };
96
+ declare const DEFAULT_SNAP_THRESHOLD = 8;
97
+ declare const DEFAULT_DRAG_ACTIVATION_DISTANCE = 8;
98
+ declare const DEFAULT_RESIZER_SIZE = 4;
99
+
100
+ /**
101
+ * Tree Helper: Remove a pane and consolidate the tree structure.
102
+ */
103
+ declare function removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null;
104
+ /**
105
+ * Tree Helper: Insert a pane by splitting an existing target node.
106
+ */
107
+ declare function splitPane(tree: TreeNode | null, targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string): TreeNode | null;
108
+ /**
109
+ * Tree Helper: Swap the position of two panes in the tree structure.
110
+ */
111
+ declare function swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null;
112
+ /**
113
+ * Tree Helper: Add a pane by recursively splitting the rightmost/bottommost pane in the tree.
114
+ */
115
+ declare function addPane(tree: TreeNode | null, paneToAdd: string): TreeNode;
116
+ /**
117
+ * Tree Helper: Update split percentage recursively.
118
+ */
119
+ declare function updateSplitPercentage(tree: TreeNode | null, target: SplitNode, newPercentage: number): TreeNode | null;
120
+
121
+ export { DEFAULT_DRAG_ACTIVATION_DISTANCE, DEFAULT_RESIZER_SIZE, DEFAULT_SNAP_THRESHOLD, DashboardProvider, DragHandle, Pane, type PaneNode, type PaneRenderProps, PaneTree, type SplitDirection, type SplitNode, type TreeNode, type ZeugmaClassNames, addPane, removePane, splitPane, swapPanes, updateSplitPercentage, useDashboard, useResizer };
package/dist/index.d.ts CHANGED
@@ -30,11 +30,11 @@ interface DashboardContextValue {
30
30
  classNames: ZeugmaClassNames;
31
31
  onRemove?: (paneId: string) => void;
32
32
  onFullscreenChange?: (paneId: string | null) => void;
33
+ snapThreshold?: number;
33
34
  }
35
+
34
36
  declare const useDashboard: () => DashboardContextValue;
35
- declare function removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null;
36
- declare function splitPane(tree: TreeNode | null, targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string): TreeNode | null;
37
- declare function swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null;
37
+
38
38
  interface DashboardProviderProps {
39
39
  layout: TreeNode | null;
40
40
  onChange: (newLayout: TreeNode | null) => void;
@@ -44,14 +44,31 @@ interface DashboardProviderProps {
44
44
  fullscreenPaneId?: string | null;
45
45
  onFullscreenChange?: (paneId: string | null) => void;
46
46
  onRemove?: (paneId: string) => void;
47
+ dragActivationDistance?: number;
48
+ snapThreshold?: number;
47
49
  children: ReactNode;
48
50
  }
49
51
  declare const DashboardProvider: React.FC<DashboardProviderProps>;
50
52
 
53
+ interface UseResizerProps {
54
+ containerRef: React.RefObject<HTMLDivElement | null>;
55
+ isRow: boolean;
56
+ direction: SplitDirection;
57
+ splitPercentage: number;
58
+ resizerSize: number;
59
+ snapThreshold: number;
60
+ layout: TreeNode | null;
61
+ currentNode: SplitNode;
62
+ onLayoutChange: (newLayout: TreeNode | null) => void;
63
+ }
64
+ declare function useResizer({ containerRef, isRow, direction, splitPercentage, resizerSize, snapThreshold, layout, currentNode, onLayoutChange, }: UseResizerProps): (e: React.PointerEvent<HTMLDivElement>) => void;
65
+
51
66
  interface PaneTreeProps {
52
67
  tree?: TreeNode | null;
53
68
  /** Size of the resizer in pixels (default 4) */
54
69
  resizerSize?: number;
70
+ /** Threshold in pixels to snap to adjacent resizer edges (default 8) */
71
+ snapThreshold?: number;
55
72
  }
56
73
  declare const PaneTree: React.FC<PaneTreeProps>;
57
74
 
@@ -61,15 +78,14 @@ interface PaneRenderProps {
61
78
  toggleFullscreen: () => void;
62
79
  remove: () => void;
63
80
  }
81
+
64
82
  interface PaneProps {
65
83
  id: string;
66
84
  children: (props: PaneRenderProps) => React.ReactNode;
67
85
  style?: React.CSSProperties;
68
86
  }
69
87
  declare const Pane: React.FC<PaneProps>;
70
- /**
71
- * Place inside a Pane to make an element the drag handle.
72
- */
88
+
73
89
  interface DragHandleProps {
74
90
  children: React.ReactNode;
75
91
  className?: string;
@@ -77,4 +93,29 @@ interface DragHandleProps {
77
93
  }
78
94
  declare const DragHandle: React.FC<DragHandleProps>;
79
95
 
80
- export { DashboardProvider, DragHandle, Pane, type PaneNode, type PaneRenderProps, PaneTree, type SplitDirection, type SplitNode, type TreeNode, type ZeugmaClassNames, removePane, splitPane, swapPanes, useDashboard };
96
+ declare const DEFAULT_SNAP_THRESHOLD = 8;
97
+ declare const DEFAULT_DRAG_ACTIVATION_DISTANCE = 8;
98
+ declare const DEFAULT_RESIZER_SIZE = 4;
99
+
100
+ /**
101
+ * Tree Helper: Remove a pane and consolidate the tree structure.
102
+ */
103
+ declare function removePane(tree: TreeNode | null, idToRemove: string): TreeNode | null;
104
+ /**
105
+ * Tree Helper: Insert a pane by splitting an existing target node.
106
+ */
107
+ declare function splitPane(tree: TreeNode | null, targetId: string, direction: SplitDirection, splitType: 'left' | 'right' | 'top' | 'bottom', paneToAdd: string): TreeNode | null;
108
+ /**
109
+ * Tree Helper: Swap the position of two panes in the tree structure.
110
+ */
111
+ declare function swapPanes(tree: TreeNode | null, idA: string, idB: string): TreeNode | null;
112
+ /**
113
+ * Tree Helper: Add a pane by recursively splitting the rightmost/bottommost pane in the tree.
114
+ */
115
+ declare function addPane(tree: TreeNode | null, paneToAdd: string): TreeNode;
116
+ /**
117
+ * Tree Helper: Update split percentage recursively.
118
+ */
119
+ declare function updateSplitPercentage(tree: TreeNode | null, target: SplitNode, newPercentage: number): TreeNode | null;
120
+
121
+ export { DEFAULT_DRAG_ACTIVATION_DISTANCE, DEFAULT_RESIZER_SIZE, DEFAULT_SNAP_THRESHOLD, DashboardProvider, DragHandle, Pane, type PaneNode, type PaneRenderProps, PaneTree, type SplitDirection, type SplitNode, type TreeNode, type ZeugmaClassNames, addPane, removePane, splitPane, swapPanes, updateSplitPercentage, useDashboard, useResizer };
package/dist/index.js CHANGED
@@ -1,2 +1,7 @@
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,useMemo,useCallback,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 L=createContext(void 0);var N=()=>{let e=useContext(L);if(!e)throw new Error("useDashboard must be used within a DashboardProvider");return e};function z(e,t){if(e===null)return null;if(e.type==="pane")return e.paneId===t?null:e;let n=z(e.first,t),o=z(e.second,t);return n===null?o:o===null?n:{...e,first:n,second:o}}function F(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},d={type:"pane",paneId:t},s=o==="left"||o==="top";return {type:"split",direction:n,first:s?i:d,second:s?d:i,splitPercentage:50}}return e}return {...e,first:F(e.first,t,n,o,r)||e.first,second:F(e.second,t,n,o,r)||e.second}}function A(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:A(e.first,t,n)||e.first,second:A(e.second,t,n)||e.second}}function we(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)}function H(e,t,n){return e===null?null:e===t?{...e,splitPercentage:n}:e.type==="split"?{...e,first:H(e.first,t,n)||e.first,second:H(e.second,t,n)||e.second}:e}var q=8,B=8,Le=4;var ie=({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)})},se=({layout:e,onChange:t,renderPane:n,renderDragOverlay:o,classNames:r={},fullscreenPaneId:i=null,onFullscreenChange:d,onRemove:s,dragActivationDistance:c=8,snapThreshold:u=8,children:m})=>{let[l,a]=useState(null),R=useSensors(useSensor(PointerSensor,{activationConstraint:{distance:c}})),b=g=>{a(g.active.id.toString());},S=g=>{a(null);let{active:Z,over:y}=g;if(!y)return;let h=Z.id.toString(),p=y.id.toString(),f=p.match(/^drop-center-(.+)$/);if(f){let[,T]=f;h!==T&&t(A(e,h,T));return}let x=p.match(/^drop-(left|right|top|bottom)-(.+)$/);if(!x)return;let[,I,E]=x;if(h===E)return;let D=I==="left"||I==="right"?"row":"column",w=z(e,h),_=F(w,E,D,I,h);t(_);},P=useMemo(()=>({layout:e,onLayoutChange:t,renderPane:n,activeId:l,fullscreenPaneId:i,classNames:r,onRemove:s,onFullscreenChange:d,snapThreshold:u}),[e,t,n,l,i,r,s,d,u]);return jsxs(L.Provider,{value:P,children:[jsx(DndContext,{sensors:R,collisionDetection:pointerWithin,onDragStart:b,onDragEnd:S,children:m}),l&&o&&jsx(ie,{activeId:l,render:o,className:r.dragOverlay})]})};function U({containerRef:e,isRow:t,direction:n,splitPercentage:o,resizerSize:r,snapThreshold:i,layout:d,currentNode:s,onLayoutChange:c}){return useCallback(u=>{u.preventDefault();let m=e.current;if(!m)return;document.body.classList.add("zeugma-resizing");let l=document.createElement("style");l.id="zeugma-global-cursor-style",l.textContent=`
2
+ * {
3
+ cursor: ${t?"col-resize":"row-resize"} !important;
4
+ user-select: none !important;
5
+ }
6
+ `,document.head.appendChild(l);let a=m.getBoundingClientRect(),R=u.clientX,b=u.clientY,S=o,P=u.currentTarget;P.setAttribute("data-resizing","true");let Z=Array.from(document.querySelectorAll('div[role="separator"][data-direction]')).filter(p=>p!==P&&p.getAttribute("data-direction")===n).map(p=>{let f=p.getBoundingClientRect();return t?f.left+f.width/2:f.top+f.height/2}),y=p=>{let f=t?(p.clientX-R)/a.width*100:(p.clientY-b)/a.height*100,x=S+f,I=t?a.left+(a.width-r)*(x/100)+r/2:a.top+(a.height-r)*(x/100)+r/2,E=1/0,D=null;for(let X of Z){let $=Math.abs(I-X);$<i&&$<E&&(E=$,D=X);}let w=x;D!==null&&(w=t?(D-r/2-a.left)/(a.width-r)*100:(D-r/2-a.top)/(a.height-r)*100);let _=Math.max(5,Math.min(95,w)),T=H(d,s,_);c(T);},h=()=>{document.body.classList.remove("zeugma-resizing"),P.removeAttribute("data-resizing");let p=document.getElementById("zeugma-global-cursor-style");p&&p.remove(),document.removeEventListener("pointermove",y),document.removeEventListener("pointerup",h);};document.addEventListener("pointermove",y),document.addEventListener("pointerup",h);},[e,t,n,o,r,i,d,s,c])}var ce=({currentNode:e,resizerSize:t,snapThreshold:n})=>{let{layout:o,onLayoutChange:r,classNames:i}=N(),d=useRef(null),{direction:s,first:c,second:u,splitPercentage:m}=e,l=s==="row",a=U({containerRef:d,isRow:l,direction:s,splitPercentage:m,resizerSize:t,snapThreshold:n??8,layout:o,currentNode:e,onLayoutChange:r});return jsxs("div",{ref:d,style:{display:"flex",flexDirection:l?"row":"column",width:"100%",height:"100%",overflow:"hidden"},children:[jsx("div",{style:{flex:`${m} 1 0%`,overflow:"hidden"},children:jsx(V,{tree:c,resizerSize:t,snapThreshold:n})}),jsx("div",{className:i.resizer,"data-direction":s,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:a,role:"separator","aria-valuenow":m,"aria-valuemin":5,"aria-valuemax":95}),jsx("div",{style:{flex:`${100-m} 1 0%`,overflow:"hidden"},children:jsx(V,{tree:u,resizerSize:t,snapThreshold:n})})]})},V=({tree:e,resizerSize:t=4,snapThreshold:n})=>{let{layout:o,renderPane:r,fullscreenPaneId:i,snapThreshold:d}=N(),s=n!==void 0?n:d;if(i&&!e)return jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(i)});let c=e!==void 0?e:o;return c?c.type==="pane"?jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(c.paneId)}):jsx(ce,{currentNode:c,resizerSize:t,snapThreshold:s}):null};var M=createContext(null);var he={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"}},ve={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"}},Y=({id:e,position:t,activeClassName:n})=>{let{setNodeRef:o,isOver:r}=useDroppable({id:e});return jsxs(Fragment,{children:[jsx("div",{ref:o,style:he[t]}),r&&jsx("div",{className:n,style:ve[t]})]})},be=({id:e,children:t,style:n})=>{let{activeId:o,classNames:r,fullscreenPaneId:i,onRemove:d,onFullscreenChange:s}=N(),c=o!==null&&o!==e,{attributes:u,listeners:m,setNodeRef:l,isDragging:a}=useDraggable({id:e}),R=o===e||a,b=i===e,S={isDragging:R,isFullscreen:b,toggleFullscreen:()=>s?.(b?null:e),remove:()=>{b&&s?.(null),d?.(e);}},P=useMemo(()=>({...m,...u}),[m,u]);return jsx(M.Provider,{value:P,children:jsxs("div",{ref:l,className:r.pane,style:{position:"relative",width:"100%",height:"100%",...n},children:[t(S),c&&jsxs("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:15,pointerEvents:"none"},children:[["top","bottom","left","right"].map(g=>jsx(Y,{id:`drop-${g}-${e}`,position:g,activeClassName:r.dropPreview},g)),jsx(Y,{id:`drop-center-${e}`,position:"center",activeClassName:r.swapPreview})]})]})})};var De=({children:e,className:t,style:n})=>{let o=useContext(M);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 DEFAULT_DRAG_ACTIVATION_DISTANCE,Le as DEFAULT_RESIZER_SIZE,q as DEFAULT_SNAP_THRESHOLD,se as DashboardProvider,De as DragHandle,be as Pane,V as PaneTree,we as addPane,z as removePane,F as splitPane,A as swapPanes,H as updateSplitPercentage,N as useDashboard,U as useResizer};//# sourceMappingURL=index.js.map
2
7
  //# 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/entities/dashboard/model/context.ts","../src/entities/dashboard/model/hooks.ts","../src/shared/lib/tree/tree-helpers.ts","../src/shared/config/constants.ts","../src/entities/dashboard/ui/DashboardProvider.tsx","../src/features/resize-pane/hooks/useResizer.ts","../src/widgets/pane-tree/ui/PaneTree.tsx","../src/entities/pane/model/context.ts","../src/entities/pane/ui/Pane.tsx","../src/entities/pane/ui/DragHandle.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","updateSplitPercentage","target","newPercentage","DEFAULT_SNAP_THRESHOLD","DEFAULT_DRAG_ACTIVATION_DISTANCE","DEFAULT_RESIZER_SIZE","CursorOverlay","activeId","render","className","ref","useRef","useEffect","handleMove","e","jsx","DashboardProvider","layout","onChange","renderPane","renderDragOverlay","classNames","fullscreenPaneId","onFullscreenChange","onRemove","dragActivationDistance","snapThreshold","children","setActiveId","useState","sensors","useSensors","useSensor","PointerSensor","handleDragStart","event","handleDragEnd","active","over","draggingId","overIdStr","swapMatch","match","dropZone","treeWithoutDragging","newLayout","contextValue","useMemo","jsxs","DndContext","pointerWithin","useResizer","containerRef","isRow","splitPercentage","resizerSize","currentNode","onLayoutChange","useCallback","container","styleEl","rect","startX","startY","startPercentage","resizerEl","otherPositions","el","r","handlePointerMove","moveEvent","delta","proposedPercentage","proposedPos","closestDistance","bestTarget","pos","dist","snappedPercentage","finalPercentage","handlePointerUp","globalStyle","PaneSplit","first","second","handlePointerDown","PaneTree","propSnapThreshold","contextSnapThreshold","DragListenersCtx","activationPositions","previewPositions","DropZone","id","position","activeClassName","setNodeRef","isOver","useDroppable","Fragment","Pane","style","showDropZones","attributes","listeners","isDragging","useDraggable","dragging","isFullscreen","renderProps","DragHandle","dragProps"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, ReactNode } from 'react'\nimport { TreeNode } from '../../../shared/model'\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 snapThreshold?: number\n}\n\nexport const DashboardContext = createContext<DashboardContextValue | undefined>(undefined)\n","import { useContext } from 'react'\nimport { DashboardContext } from './context'\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","import { TreeNode, SplitNode, SplitDirection, PaneNode } from '../../model'\n\n/**\n * Tree Helper: Remove a pane and consolidate the tree structure.\n */\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/**\n * Tree Helper: Insert a pane by splitting an existing target node.\n */\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/**\n * Tree Helper: Swap the position of two panes in the tree structure.\n */\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/**\n * Tree Helper: Add a pane by recursively splitting the rightmost/bottommost pane in the tree.\n */\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/**\n * Tree Helper: Update split percentage recursively.\n */\nexport function 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","export const DEFAULT_SNAP_THRESHOLD = 8 // px\nexport const DEFAULT_DRAG_ACTIVATION_DISTANCE = 8 // px\nexport const DEFAULT_RESIZER_SIZE = 4 // px\n","import React, { useState, useEffect, useRef, ReactNode, useMemo } 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 } from '../../../shared/model'\nimport { removePane, splitPane, swapPanes } from '../../../shared/lib/tree'\nimport { DEFAULT_DRAG_ACTIVATION_DISTANCE, DEFAULT_SNAP_THRESHOLD } from '../../../shared/config'\nimport { DashboardContext, ZeugmaClassNames } from '../model/context'\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 snapThreshold?: 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 = DEFAULT_DRAG_ACTIVATION_DISTANCE,\n snapThreshold = DEFAULT_SNAP_THRESHOLD,\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 // Best practice: Memoize context value to prevent unnecessary re-renders of context consumers.\n const contextValue = useMemo(\n () => ({\n layout,\n onLayoutChange: onChange,\n renderPane,\n activeId,\n fullscreenPaneId,\n classNames,\n onRemove,\n onFullscreenChange,\n snapThreshold,\n }),\n [\n layout,\n onChange,\n renderPane,\n activeId,\n fullscreenPaneId,\n classNames,\n onRemove,\n onFullscreenChange,\n snapThreshold,\n ],\n )\n\n return (\n <DashboardContext.Provider value={contextValue}>\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, { useCallback } from 'react'\nimport { TreeNode, SplitNode, SplitDirection } from '../../../shared/model'\nimport { updateSplitPercentage } from '../../../shared/lib/tree'\n\ninterface UseResizerProps {\n containerRef: React.RefObject<HTMLDivElement | null>\n isRow: boolean\n direction: SplitDirection\n splitPercentage: number\n resizerSize: number\n snapThreshold: number\n layout: TreeNode | null\n currentNode: SplitNode\n onLayoutChange: (newLayout: TreeNode | null) => void\n}\n\nexport function useResizer({\n containerRef,\n isRow,\n direction,\n splitPercentage,\n resizerSize,\n snapThreshold,\n layout,\n currentNode,\n onLayoutChange,\n}: UseResizerProps) {\n return useCallback(\n (e: React.PointerEvent<HTMLDivElement>) => {\n e.preventDefault()\n const container = containerRef.current\n if (!container) return\n\n document.body.classList.add('zeugma-resizing')\n\n // Inject global cursor style to keep resizing cursor active across the entire page during drag\n const styleEl = document.createElement('style')\n styleEl.id = 'zeugma-global-cursor-style'\n styleEl.textContent = `\n * {\n cursor: ${isRow ? 'col-resize' : 'row-resize'} !important;\n user-select: none !important;\n }\n `\n document.head.appendChild(styleEl)\n\n const rect = container.getBoundingClientRect()\n const startX = e.clientX\n const startY = e.clientY\n const startPercentage = splitPercentage\n\n // Cache other resizers of the same direction once at drag-start to prevent layout thrashing on move\n const resizerEl = e.currentTarget\n resizerEl.setAttribute('data-resizing', 'true')\n\n const otherResizers = Array.from(\n document.querySelectorAll('div[role=\"separator\"][data-direction]'),\n ).filter((el) => el !== resizerEl && el.getAttribute('data-direction') === direction)\n\n const otherPositions = otherResizers.map((el) => {\n const r = el.getBoundingClientRect()\n return isRow ? r.left + r.width / 2 : r.top + r.height / 2\n })\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 proposedPercentage = startPercentage + delta\n\n // Find physical position corresponding to proposed percentage\n const proposedPos = isRow\n ? rect.left + (rect.width - resizerSize) * (proposedPercentage / 100) + resizerSize / 2\n : rect.top + (rect.height - resizerSize) * (proposedPercentage / 100) + resizerSize / 2\n\n let closestDistance = Infinity\n let bestTarget: number | null = null\n\n for (const pos of otherPositions) {\n const dist = Math.abs(proposedPos - pos)\n if (dist < snapThreshold && dist < closestDistance) {\n closestDistance = dist\n bestTarget = pos\n }\n }\n\n let snappedPercentage = proposedPercentage\n if (bestTarget !== null) {\n snappedPercentage = isRow\n ? ((bestTarget - resizerSize / 2 - rect.left) / (rect.width - resizerSize)) * 100\n : ((bestTarget - resizerSize / 2 - rect.top) / (rect.height - resizerSize)) * 100\n }\n\n const finalPercentage = Math.max(5, Math.min(95, snappedPercentage))\n const newLayout = updateSplitPercentage(layout, currentNode, finalPercentage)\n onLayoutChange(newLayout)\n }\n\n const handlePointerUp = () => {\n document.body.classList.remove('zeugma-resizing')\n resizerEl.removeAttribute('data-resizing')\n\n const globalStyle = document.getElementById('zeugma-global-cursor-style')\n if (globalStyle) {\n globalStyle.remove()\n }\n\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 containerRef,\n isRow,\n direction,\n splitPercentage,\n resizerSize,\n snapThreshold,\n layout,\n currentNode,\n onLayoutChange,\n ],\n )\n}\n","import React, { useRef } from 'react'\nimport { useDashboard } from '../../../entities/dashboard'\nimport { useResizer } from '../../../features/resize-pane'\nimport { TreeNode, SplitNode } from '../../../shared/model'\n\nexport interface PaneTreeProps {\n tree?: TreeNode | null\n /** Size of the resizer in pixels (default 4) */\n resizerSize?: number\n /** Threshold in pixels to snap to adjacent resizer edges (default 8) */\n snapThreshold?: number\n}\n\ninterface PaneSplitProps {\n currentNode: SplitNode\n resizerSize: number\n snapThreshold?: number\n}\n\nconst PaneSplit: React.FC<PaneSplitProps> = ({ currentNode, resizerSize, snapThreshold }) => {\n const { layout, onLayoutChange, classNames } = useDashboard()\n\n const containerRef = useRef<HTMLDivElement>(null)\n const { direction, first, second, splitPercentage } = currentNode\n const isRow = direction === 'row'\n\n const handlePointerDown = useResizer({\n containerRef,\n isRow,\n direction,\n splitPercentage,\n resizerSize,\n snapThreshold: snapThreshold ?? 8,\n layout,\n currentNode,\n onLayoutChange,\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} snapThreshold={snapThreshold} />\n </div>\n <div\n className={classNames.resizer}\n data-direction={direction}\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} snapThreshold={snapThreshold} />\n </div>\n </div>\n )\n}\n\nexport const PaneTree: React.FC<PaneTreeProps> = ({\n tree,\n resizerSize = 4,\n snapThreshold: propSnapThreshold,\n}) => {\n const {\n layout,\n renderPane,\n fullscreenPaneId,\n snapThreshold: contextSnapThreshold,\n } = useDashboard()\n\n const snapThreshold = propSnapThreshold !== undefined ? propSnapThreshold : contextSnapThreshold\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 return (\n <PaneSplit currentNode={currentNode} resizerSize={resizerSize} snapThreshold={snapThreshold} />\n )\n}\n","import { createContext } from 'react'\n\nexport const DragListenersCtx = createContext<Record<string, unknown> | null>(null)\n","import React, { useMemo } from 'react'\nimport { useDraggable, useDroppable } from '@dnd-kit/core'\nimport { useDashboard } from '../../dashboard'\nimport { DragListenersCtx } from '../model/context'\nimport { PaneRenderProps } from '../model/types'\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\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 // Best practice: Memoize drag context value to prevent unnecessary re-renders of the drag handle.\n const contextValue = useMemo(\n () => ({\n ...listeners,\n ...attributes,\n }),\n [listeners, attributes],\n )\n\n return (\n <DragListenersCtx.Provider value={contextValue}>\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","import React, { useContext } from 'react'\nimport { DragListenersCtx } from '../model/context'\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.3.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,
@@ -28,7 +28,7 @@
28
28
  "bugs": {
29
29
  "url": "https://github.com/yusufarsln98/react-zeugma/issues"
30
30
  },
31
- "homepage": "https://github.com/yusufarsln98/react-zeugma#readme",
31
+ "homepage": "https://react-zeugma.vercel.app",
32
32
  "publishConfig": {
33
33
  "access": "public",
34
34
  "registry": "https://registry.npmjs.org/"
@@ -56,9 +56,8 @@
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",
60
59
  "storybook": "storybook dev -p 6006",
61
- "build-storybook": "storybook build -o storybook-static",
60
+ "build:storybook": "storybook build -o storybook-static",
62
61
  "prepare": "husky",
63
62
  "version": "changeset version",
64
63
  "release": "npm run build && changeset publish"
@@ -67,10 +66,6 @@
67
66
  "react": "^18.0.0 || ^19.0.0",
68
67
  "react-dom": "^18.0.0 || ^19.0.0"
69
68
  },
70
- "workspaces": [
71
- ".",
72
- "demo"
73
- ],
74
69
  "dependencies": {
75
70
  "@dnd-kit/core": "^6.3.1",
76
71
  "@dnd-kit/sortable": "^10.0.0",