react-zeugma 0.1.1
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/LICENSE +21 -0
- package/README.md +180 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +80 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +102 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 yusufarsln98
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# react-zeugma
|
|
4
|
+
|
|
5
|
+
**Recursive drag-and-drop dashboard layout engine for React**
|
|
6
|
+
|
|
7
|
+
Combines the tree-based, arbitrary splitting of [react-mosaic](https://github.com/nomcopter/react-mosaic) with the declarative, state-driven API of [react-grid-layout](https://github.com/react-grid-layout/react-grid-layout).
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/react-zeugma)
|
|
10
|
+
[](https://bundlephobia.com/package/react-zeugma)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
[](https://www.typescriptlang.org)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
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.
|
|
26
|
+
- **Tree-shakeable & Tiny** — ESM-first with zero runtime CSS. Bring your own styles.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install react-zeugma @dnd-kit/core @dnd-kit/utilities @dnd-kit/sortable
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> **Peer dependencies:** React 18+ or 19+
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { useState } from 'react';
|
|
44
|
+
import { DashboardProvider, PaneTree, Pane, DragHandle, TreeNode } from 'react-zeugma';
|
|
45
|
+
|
|
46
|
+
const initialLayout: TreeNode = {
|
|
47
|
+
type: 'split',
|
|
48
|
+
direction: 'row',
|
|
49
|
+
splitPercentage: 50,
|
|
50
|
+
first: { type: 'pane', paneId: 'editor' },
|
|
51
|
+
second: {
|
|
52
|
+
type: 'split',
|
|
53
|
+
direction: 'column',
|
|
54
|
+
splitPercentage: 60,
|
|
55
|
+
first: { type: 'pane', paneId: 'preview' },
|
|
56
|
+
second: { type: 'pane', paneId: 'console' },
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function MyPane({ id }: { id: string }) {
|
|
61
|
+
return (
|
|
62
|
+
<Pane id={id}>
|
|
63
|
+
{({ isDragging }) => (
|
|
64
|
+
<div style={{ opacity: isDragging ? 0.5 : 1, height: '100%' }}>
|
|
65
|
+
<DragHandle>
|
|
66
|
+
<div style={{ padding: 8, cursor: 'grab', background: '#1e1e2e', color: '#cdd6f4' }}>
|
|
67
|
+
{id}
|
|
68
|
+
</div>
|
|
69
|
+
</DragHandle>
|
|
70
|
+
<div style={{ padding: 16 }}>Content for {id}</div>
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</Pane>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default function Dashboard() {
|
|
78
|
+
const [layout, setLayout] = useState<TreeNode | null>(initialLayout);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<DashboardProvider layout={layout} onChange={setLayout} renderPane={(id) => <MyPane id={id} />}>
|
|
82
|
+
<div style={{ width: '100vw', height: '100vh' }}>
|
|
83
|
+
<PaneTree />
|
|
84
|
+
</div>
|
|
85
|
+
</DashboardProvider>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## API
|
|
93
|
+
|
|
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). |
|
|
100
|
+
|
|
101
|
+
### Utilities
|
|
102
|
+
|
|
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. |
|
|
108
|
+
|
|
109
|
+
### Types
|
|
110
|
+
|
|
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. |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Documentation
|
|
121
|
+
|
|
122
|
+
📖 **[Interactive Docs & Examples →](https://yusufarsln98.github.io/react-zeugma/)**
|
|
123
|
+
|
|
124
|
+
### Hosting the live demo
|
|
125
|
+
|
|
126
|
+
You do **not** need a personal site at `https://yusufarsln98.github.io/`. That URL only exists if you create a repo literally named `yusufarsln98.github.io`.
|
|
127
|
+
|
|
128
|
+
This project uses a **project site** (one repo → one subpath):
|
|
129
|
+
|
|
130
|
+
**https://yusufarsln98.github.io/react-zeugma/**
|
|
131
|
+
|
|
132
|
+
CI pushes the built demo to the `gh-pages` branch. GitHub will not serve it until Pages is turned on once:
|
|
133
|
+
|
|
134
|
+
1. Open [Settings → Pages](https://github.com/yusufarsln98/react-zeugma/settings/pages)
|
|
135
|
+
2. **Build and deployment → Source:** **Deploy from a branch**
|
|
136
|
+
3. **Branch:** `gh-pages` / **`/ (root)`** → **Save**
|
|
137
|
+
4. Wait 1–2 minutes, then open the URL above
|
|
138
|
+
|
|
139
|
+
If the workflow is green but the site is 404, Pages is almost always still disabled or pointed at the wrong branch.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Local Development
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Clone & install
|
|
147
|
+
git clone https://github.com/yusufarsln98/react-zeugma.git
|
|
148
|
+
cd react-zeugma
|
|
149
|
+
npm install
|
|
150
|
+
|
|
151
|
+
# Run the interactive demo
|
|
152
|
+
npm run demo
|
|
153
|
+
|
|
154
|
+
# Run Storybook docs
|
|
155
|
+
npm run storybook
|
|
156
|
+
|
|
157
|
+
# Build the library
|
|
158
|
+
npm run build
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Contributing
|
|
164
|
+
|
|
165
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on how to get started.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
[MIT](./LICENSE) © yusufarsln98
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
<div align="center">
|
|
176
|
+
|
|
177
|
+
_Zeugma_ is an ancient Greco-Roman city on the Euphrates in Gaziantep, Turkey — world-renowned for its breathtaking mosaic panels unearthed during excavations.
|
|
178
|
+
This library draws its name from those mosaics: **many tiles, one masterpiece.**
|
|
179
|
+
|
|
180
|
+
</div>
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var react=require('react'),core=require('@dnd-kit/core'),jsxRuntime=require('react/jsx-runtime');var M=react.createContext(void 0),D=()=>{let e=react.useContext(M);if(!e)throw new Error("useDashboard must be used within a DashboardProvider");return e};function y(e,t){if(e===null)return null;if(e.type==="pane")return e.paneId===t?null:e;let n=y(e.first,t),o=y(e.second,t);return n===null?o:o===null?n:{...e,first:n,second:o}}function E(e,t,n,o,r){if(e===null)return {type:"pane",paneId:r};if(e.type==="pane"){if(e.paneId===t){let i={type:"pane",paneId:r},c={type:"pane",paneId:t},s=o==="left"||o==="top";return {type:"split",direction:n,first:s?i:c,second:s?c:i,splitPercentage:50}}return e}return {...e,first:E(e.first,t,n,o,r)||e.first,second:E(e.second,t,n,o,r)||e.second}}function z(e,t,n){return e===null?null:e.type==="pane"?e.paneId===t?{...e,paneId:n}:e.paneId===n?{...e,paneId:t}:e:{...e,first:z(e.first,t,n)||e.first,second:z(e.second,t,n)||e.second}}var _=({activeId:e,render:t,className:n})=>{let o=react.useRef(null);return react.useEffect(()=>{let r=i=>{o.current&&(o.current.style.transform=`translate(${i.clientX+12}px, ${i.clientY+12}px)`);};return document.addEventListener("pointermove",r),()=>document.removeEventListener("pointermove",r)},[]),jsxRuntime.jsx("div",{ref:o,className:n,style:{position:"fixed",top:0,left:0,zIndex:9999,pointerEvents:"none"},children:t(e)})},B=({layout:e,onChange:t,renderPane:n,renderDragOverlay:o,classNames:r={},fullscreenPaneId:i=null,onFullscreenChange:c,onRemove:s,children:a})=>{let[v,m]=react.useState(null),x=core.useSensors(core.useSensor(core.PointerSensor,{activationConstraint:{distance:8}})),p=u=>{m(u.active.id.toString());},l=u=>{m(null);let{active:f,over:d}=u;if(!d)return;let g=f.id.toString(),w=d.id.toString(),I=w.match(/^drop-center-(.+)$/);if(I){let[,S]=I;g!==S&&t(z(e,g,S));return}let R=w.match(/^drop-(left|right|top|bottom)-(.+)$/);if(!R)return;let[,P,N]=R;if(g===N)return;let C=P==="left"||P==="right"?"row":"column",L=y(e,g),F=E(L,N,C,P,g);t(F);};return jsxRuntime.jsxs(M.Provider,{value:{layout:e,onLayoutChange:t,renderPane:n,activeId:v,fullscreenPaneId:i,classNames:r,onRemove:s,onFullscreenChange:c},children:[jsxRuntime.jsx(core.DndContext,{sensors:x,collisionDetection:core.pointerWithin,onDragStart:p,onDragEnd:l,children:a}),v&&o&&jsxRuntime.jsx(_,{activeId:v,render:o,className:r.dragOverlay})]})};function $(e,t,n){return e===null?null:e===t?{...e,splitPercentage:n}:e.type==="split"?{...e,first:$(e.first,t,n)||e.first,second:$(e.second,t,n)||e.second}:e}var Z=({tree:e,resizerSize:t=4})=>{let{layout:n,onLayoutChange:o,renderPane:r,fullscreenPaneId:i,classNames:c}=D(),s=react.useRef(null);if(i&&!e)return jsxRuntime.jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(i)});let a=e!==void 0?e:n;if(!a)return null;if(a.type==="pane")return jsxRuntime.jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(a.paneId)});let{direction:v,first:m,second:x,splitPercentage:p}=a,l=v==="row",u=f=>{f.preventDefault();let d=s.current;if(!d)return;let g=d.getBoundingClientRect(),w=f.clientX,I=f.clientY,R=p,P=C=>{let L=l?(C.clientX-w)/g.width*100:(C.clientY-I)/g.height*100,F=Math.max(5,Math.min(95,R+L)),S=$(n,a,F);o(S);},N=()=>{document.removeEventListener("pointermove",P),document.removeEventListener("pointerup",N);};document.addEventListener("pointermove",P),document.addEventListener("pointerup",N);};return jsxRuntime.jsxs("div",{ref:s,style:{display:"flex",flexDirection:l?"row":"column",width:"100%",height:"100%",overflow:"hidden"},children:[jsxRuntime.jsx("div",{style:{flex:`${p} 1 0%`,overflow:"hidden"},children:jsxRuntime.jsx(Z,{tree:m,resizerSize:t})}),jsxRuntime.jsx("div",{className:c.resizer,style:{width:l?`${t}px`:"100%",height:l?"100%":`${t}px`,cursor:l?"col-resize":"row-resize",position:"relative",zIndex:10,userSelect:"none",boxSizing:"border-box",flexShrink:0},onPointerDown:u,role:"separator","aria-valuenow":p,"aria-valuemin":5,"aria-valuemax":95}),jsxRuntime.jsx("div",{style:{flex:`${100-p} 1 0%`,overflow:"hidden"},children:jsxRuntime.jsx(Z,{tree:x,resizerSize:t})})]})};var X=react.createContext(null),ie={top:{position:"absolute",top:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},bottom:{position:"absolute",bottom:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},left:{position:"absolute",top:0,bottom:0,left:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},right:{position:"absolute",top:0,bottom:0,right:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},center:{position:"absolute",top:"25%",left:"25%",width:"50%",height:"50%",zIndex:20,pointerEvents:"auto"}},se={top:{position:"absolute",top:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},bottom:{position:"absolute",bottom:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},left:{position:"absolute",top:0,bottom:0,left:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},right:{position:"absolute",top:0,bottom:0,right:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},center:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:21,pointerEvents:"none",boxSizing:"border-box"}},O=({id:e,position:t,activeClassName:n})=>{let{setNodeRef:o,isOver:r}=core.useDroppable({id:e});return jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx("div",{ref:o,style:ie[t]}),r&&jsxRuntime.jsx("div",{className:n,style:se[t]})]})},ae=({id:e,children:t,style:n})=>{let{activeId:o,classNames:r,fullscreenPaneId:i,onRemove:c,onFullscreenChange:s}=D(),a=o!==null&&o!==e,{attributes:v,listeners:m,setNodeRef:x,isDragging:p}=core.useDraggable({id:e}),l=o===e||p,u=i===e,f={isDragging:l,isFullscreen:u,toggleFullscreen:()=>s?.(u?null:e),remove:()=>c?.(e)};return jsxRuntime.jsx(X.Provider,{value:{...m,...v},children:jsxRuntime.jsxs("div",{ref:x,className:r.pane,style:{position:"relative",width:"100%",height:"100%",...n},children:[t(f),a&&jsxRuntime.jsxs("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:15,pointerEvents:"none"},children:[["top","bottom","left","right"].map(d=>jsxRuntime.jsx(O,{id:`drop-${d}-${e}`,position:d,activeClassName:r.dropPreview},d)),jsxRuntime.jsx(O,{id:`drop-center-${e}`,position:"center",activeClassName:r.swapPreview})]})]})})},le=({children:e,className:t,style:n})=>{let o=react.useContext(X);if(!o)throw new Error("<DragHandle> must be used inside a <Pane>");return jsxRuntime.jsx("div",{className:t,style:{cursor:"grab",userSelect:"none",...n},...o,children:e})};exports.DashboardProvider=B;exports.DragHandle=le;exports.Pane=ae;exports.PaneTree=Z;exports.removePane=y;exports.splitPane=E;exports.swapPanes=z;exports.useDashboard=D;//# sourceMappingURL=index.cjs.map
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
type SplitDirection = 'row' | 'column';
|
|
4
|
+
interface SplitNode {
|
|
5
|
+
type: 'split';
|
|
6
|
+
direction: SplitDirection;
|
|
7
|
+
first: TreeNode;
|
|
8
|
+
second: TreeNode;
|
|
9
|
+
splitPercentage: number;
|
|
10
|
+
}
|
|
11
|
+
interface PaneNode {
|
|
12
|
+
type: 'pane';
|
|
13
|
+
paneId: string;
|
|
14
|
+
}
|
|
15
|
+
type TreeNode = SplitNode | PaneNode;
|
|
16
|
+
|
|
17
|
+
interface ZeugmaClassNames {
|
|
18
|
+
pane?: string;
|
|
19
|
+
dropPreview?: string;
|
|
20
|
+
swapPreview?: string;
|
|
21
|
+
dragOverlay?: string;
|
|
22
|
+
resizer?: string;
|
|
23
|
+
}
|
|
24
|
+
interface DashboardContextValue {
|
|
25
|
+
layout: TreeNode | null;
|
|
26
|
+
onLayoutChange: (newLayout: TreeNode | null) => void;
|
|
27
|
+
renderPane: (paneId: string) => ReactNode;
|
|
28
|
+
activeId: string | null;
|
|
29
|
+
fullscreenPaneId: string | null;
|
|
30
|
+
classNames: ZeugmaClassNames;
|
|
31
|
+
onRemove?: (paneId: string) => void;
|
|
32
|
+
onFullscreenChange?: (paneId: string | null) => void;
|
|
33
|
+
}
|
|
34
|
+
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;
|
|
38
|
+
interface DashboardProviderProps {
|
|
39
|
+
layout: TreeNode | null;
|
|
40
|
+
onChange: (newLayout: TreeNode | null) => void;
|
|
41
|
+
renderPane: (paneId: string) => ReactNode;
|
|
42
|
+
renderDragOverlay?: (activeId: string) => ReactNode;
|
|
43
|
+
classNames?: ZeugmaClassNames;
|
|
44
|
+
fullscreenPaneId?: string | null;
|
|
45
|
+
onFullscreenChange?: (paneId: string | null) => void;
|
|
46
|
+
onRemove?: (paneId: string) => void;
|
|
47
|
+
children: ReactNode;
|
|
48
|
+
}
|
|
49
|
+
declare const DashboardProvider: React.FC<DashboardProviderProps>;
|
|
50
|
+
|
|
51
|
+
interface PaneTreeProps {
|
|
52
|
+
tree?: TreeNode | null;
|
|
53
|
+
/** Size of the resizer in pixels (default 4) */
|
|
54
|
+
resizerSize?: number;
|
|
55
|
+
}
|
|
56
|
+
declare const PaneTree: React.FC<PaneTreeProps>;
|
|
57
|
+
|
|
58
|
+
interface PaneRenderProps {
|
|
59
|
+
isDragging: boolean;
|
|
60
|
+
isFullscreen: boolean;
|
|
61
|
+
toggleFullscreen: () => void;
|
|
62
|
+
remove: () => void;
|
|
63
|
+
}
|
|
64
|
+
interface PaneProps {
|
|
65
|
+
id: string;
|
|
66
|
+
children: (props: PaneRenderProps) => React.ReactNode;
|
|
67
|
+
style?: React.CSSProperties;
|
|
68
|
+
}
|
|
69
|
+
declare const Pane: React.FC<PaneProps>;
|
|
70
|
+
/**
|
|
71
|
+
* Place inside a Pane to make an element the drag handle.
|
|
72
|
+
*/
|
|
73
|
+
interface DragHandleProps {
|
|
74
|
+
children: React.ReactNode;
|
|
75
|
+
className?: string;
|
|
76
|
+
style?: React.CSSProperties;
|
|
77
|
+
}
|
|
78
|
+
declare const DragHandle: React.FC<DragHandleProps>;
|
|
79
|
+
|
|
80
|
+
export { DashboardProvider, DragHandle, Pane, type PaneNode, type PaneRenderProps, PaneTree, type SplitDirection, type SplitNode, type TreeNode, type ZeugmaClassNames, removePane, splitPane, swapPanes, useDashboard };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
type SplitDirection = 'row' | 'column';
|
|
4
|
+
interface SplitNode {
|
|
5
|
+
type: 'split';
|
|
6
|
+
direction: SplitDirection;
|
|
7
|
+
first: TreeNode;
|
|
8
|
+
second: TreeNode;
|
|
9
|
+
splitPercentage: number;
|
|
10
|
+
}
|
|
11
|
+
interface PaneNode {
|
|
12
|
+
type: 'pane';
|
|
13
|
+
paneId: string;
|
|
14
|
+
}
|
|
15
|
+
type TreeNode = SplitNode | PaneNode;
|
|
16
|
+
|
|
17
|
+
interface ZeugmaClassNames {
|
|
18
|
+
pane?: string;
|
|
19
|
+
dropPreview?: string;
|
|
20
|
+
swapPreview?: string;
|
|
21
|
+
dragOverlay?: string;
|
|
22
|
+
resizer?: string;
|
|
23
|
+
}
|
|
24
|
+
interface DashboardContextValue {
|
|
25
|
+
layout: TreeNode | null;
|
|
26
|
+
onLayoutChange: (newLayout: TreeNode | null) => void;
|
|
27
|
+
renderPane: (paneId: string) => ReactNode;
|
|
28
|
+
activeId: string | null;
|
|
29
|
+
fullscreenPaneId: string | null;
|
|
30
|
+
classNames: ZeugmaClassNames;
|
|
31
|
+
onRemove?: (paneId: string) => void;
|
|
32
|
+
onFullscreenChange?: (paneId: string | null) => void;
|
|
33
|
+
}
|
|
34
|
+
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;
|
|
38
|
+
interface DashboardProviderProps {
|
|
39
|
+
layout: TreeNode | null;
|
|
40
|
+
onChange: (newLayout: TreeNode | null) => void;
|
|
41
|
+
renderPane: (paneId: string) => ReactNode;
|
|
42
|
+
renderDragOverlay?: (activeId: string) => ReactNode;
|
|
43
|
+
classNames?: ZeugmaClassNames;
|
|
44
|
+
fullscreenPaneId?: string | null;
|
|
45
|
+
onFullscreenChange?: (paneId: string | null) => void;
|
|
46
|
+
onRemove?: (paneId: string) => void;
|
|
47
|
+
children: ReactNode;
|
|
48
|
+
}
|
|
49
|
+
declare const DashboardProvider: React.FC<DashboardProviderProps>;
|
|
50
|
+
|
|
51
|
+
interface PaneTreeProps {
|
|
52
|
+
tree?: TreeNode | null;
|
|
53
|
+
/** Size of the resizer in pixels (default 4) */
|
|
54
|
+
resizerSize?: number;
|
|
55
|
+
}
|
|
56
|
+
declare const PaneTree: React.FC<PaneTreeProps>;
|
|
57
|
+
|
|
58
|
+
interface PaneRenderProps {
|
|
59
|
+
isDragging: boolean;
|
|
60
|
+
isFullscreen: boolean;
|
|
61
|
+
toggleFullscreen: () => void;
|
|
62
|
+
remove: () => void;
|
|
63
|
+
}
|
|
64
|
+
interface PaneProps {
|
|
65
|
+
id: string;
|
|
66
|
+
children: (props: PaneRenderProps) => React.ReactNode;
|
|
67
|
+
style?: React.CSSProperties;
|
|
68
|
+
}
|
|
69
|
+
declare const Pane: React.FC<PaneProps>;
|
|
70
|
+
/**
|
|
71
|
+
* Place inside a Pane to make an element the drag handle.
|
|
72
|
+
*/
|
|
73
|
+
interface DragHandleProps {
|
|
74
|
+
children: React.ReactNode;
|
|
75
|
+
className?: string;
|
|
76
|
+
style?: React.CSSProperties;
|
|
77
|
+
}
|
|
78
|
+
declare const DragHandle: React.FC<DragHandleProps>;
|
|
79
|
+
|
|
80
|
+
export { DashboardProvider, DragHandle, Pane, type PaneNode, type PaneRenderProps, PaneTree, type SplitDirection, type SplitNode, type TreeNode, type ZeugmaClassNames, removePane, splitPane, swapPanes, useDashboard };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {createContext,useContext,useState,useRef,useEffect}from'react';import {useSensors,useSensor,PointerSensor,DndContext,pointerWithin,useDraggable,useDroppable}from'@dnd-kit/core';import {jsxs,jsx,Fragment}from'react/jsx-runtime';var M=createContext(void 0),D=()=>{let e=useContext(M);if(!e)throw new Error("useDashboard must be used within a DashboardProvider");return e};function y(e,t){if(e===null)return null;if(e.type==="pane")return e.paneId===t?null:e;let n=y(e.first,t),o=y(e.second,t);return n===null?o:o===null?n:{...e,first:n,second:o}}function E(e,t,n,o,r){if(e===null)return {type:"pane",paneId:r};if(e.type==="pane"){if(e.paneId===t){let i={type:"pane",paneId:r},c={type:"pane",paneId:t},s=o==="left"||o==="top";return {type:"split",direction:n,first:s?i:c,second:s?c:i,splitPercentage:50}}return e}return {...e,first:E(e.first,t,n,o,r)||e.first,second:E(e.second,t,n,o,r)||e.second}}function z(e,t,n){return e===null?null:e.type==="pane"?e.paneId===t?{...e,paneId:n}:e.paneId===n?{...e,paneId:t}:e:{...e,first:z(e.first,t,n)||e.first,second:z(e.second,t,n)||e.second}}var _=({activeId:e,render:t,className:n})=>{let o=useRef(null);return useEffect(()=>{let r=i=>{o.current&&(o.current.style.transform=`translate(${i.clientX+12}px, ${i.clientY+12}px)`);};return document.addEventListener("pointermove",r),()=>document.removeEventListener("pointermove",r)},[]),jsx("div",{ref:o,className:n,style:{position:"fixed",top:0,left:0,zIndex:9999,pointerEvents:"none"},children:t(e)})},B=({layout:e,onChange:t,renderPane:n,renderDragOverlay:o,classNames:r={},fullscreenPaneId:i=null,onFullscreenChange:c,onRemove:s,children:a})=>{let[v,m]=useState(null),x=useSensors(useSensor(PointerSensor,{activationConstraint:{distance:8}})),p=u=>{m(u.active.id.toString());},l=u=>{m(null);let{active:f,over:d}=u;if(!d)return;let g=f.id.toString(),w=d.id.toString(),I=w.match(/^drop-center-(.+)$/);if(I){let[,S]=I;g!==S&&t(z(e,g,S));return}let R=w.match(/^drop-(left|right|top|bottom)-(.+)$/);if(!R)return;let[,P,N]=R;if(g===N)return;let C=P==="left"||P==="right"?"row":"column",L=y(e,g),F=E(L,N,C,P,g);t(F);};return jsxs(M.Provider,{value:{layout:e,onLayoutChange:t,renderPane:n,activeId:v,fullscreenPaneId:i,classNames:r,onRemove:s,onFullscreenChange:c},children:[jsx(DndContext,{sensors:x,collisionDetection:pointerWithin,onDragStart:p,onDragEnd:l,children:a}),v&&o&&jsx(_,{activeId:v,render:o,className:r.dragOverlay})]})};function $(e,t,n){return e===null?null:e===t?{...e,splitPercentage:n}:e.type==="split"?{...e,first:$(e.first,t,n)||e.first,second:$(e.second,t,n)||e.second}:e}var Z=({tree:e,resizerSize:t=4})=>{let{layout:n,onLayoutChange:o,renderPane:r,fullscreenPaneId:i,classNames:c}=D(),s=useRef(null);if(i&&!e)return jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(i)});let a=e!==void 0?e:n;if(!a)return null;if(a.type==="pane")return jsx("div",{style:{width:"100%",height:"100%",position:"relative"},children:r(a.paneId)});let{direction:v,first:m,second:x,splitPercentage:p}=a,l=v==="row",u=f=>{f.preventDefault();let d=s.current;if(!d)return;let g=d.getBoundingClientRect(),w=f.clientX,I=f.clientY,R=p,P=C=>{let L=l?(C.clientX-w)/g.width*100:(C.clientY-I)/g.height*100,F=Math.max(5,Math.min(95,R+L)),S=$(n,a,F);o(S);},N=()=>{document.removeEventListener("pointermove",P),document.removeEventListener("pointerup",N);};document.addEventListener("pointermove",P),document.addEventListener("pointerup",N);};return jsxs("div",{ref:s,style:{display:"flex",flexDirection:l?"row":"column",width:"100%",height:"100%",overflow:"hidden"},children:[jsx("div",{style:{flex:`${p} 1 0%`,overflow:"hidden"},children:jsx(Z,{tree:m,resizerSize:t})}),jsx("div",{className:c.resizer,style:{width:l?`${t}px`:"100%",height:l?"100%":`${t}px`,cursor:l?"col-resize":"row-resize",position:"relative",zIndex:10,userSelect:"none",boxSizing:"border-box",flexShrink:0},onPointerDown:u,role:"separator","aria-valuenow":p,"aria-valuemin":5,"aria-valuemax":95}),jsx("div",{style:{flex:`${100-p} 1 0%`,overflow:"hidden"},children:jsx(Z,{tree:x,resizerSize:t})})]})};var X=createContext(null),ie={top:{position:"absolute",top:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},bottom:{position:"absolute",bottom:0,left:"25%",width:"50%",height:"25%",zIndex:20,pointerEvents:"auto"},left:{position:"absolute",top:0,bottom:0,left:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},right:{position:"absolute",top:0,bottom:0,right:0,width:"25%",height:"100%",zIndex:20,pointerEvents:"auto"},center:{position:"absolute",top:"25%",left:"25%",width:"50%",height:"50%",zIndex:20,pointerEvents:"auto"}},se={top:{position:"absolute",top:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},bottom:{position:"absolute",bottom:0,left:0,right:0,height:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},left:{position:"absolute",top:0,bottom:0,left:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},right:{position:"absolute",top:0,bottom:0,right:0,width:"50%",zIndex:21,pointerEvents:"none",boxSizing:"border-box"},center:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:21,pointerEvents:"none",boxSizing:"border-box"}},O=({id:e,position:t,activeClassName:n})=>{let{setNodeRef:o,isOver:r}=useDroppable({id:e});return jsxs(Fragment,{children:[jsx("div",{ref:o,style:ie[t]}),r&&jsx("div",{className:n,style:se[t]})]})},ae=({id:e,children:t,style:n})=>{let{activeId:o,classNames:r,fullscreenPaneId:i,onRemove:c,onFullscreenChange:s}=D(),a=o!==null&&o!==e,{attributes:v,listeners:m,setNodeRef:x,isDragging:p}=useDraggable({id:e}),l=o===e||p,u=i===e,f={isDragging:l,isFullscreen:u,toggleFullscreen:()=>s?.(u?null:e),remove:()=>c?.(e)};return jsx(X.Provider,{value:{...m,...v},children:jsxs("div",{ref:x,className:r.pane,style:{position:"relative",width:"100%",height:"100%",...n},children:[t(f),a&&jsxs("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0,zIndex:15,pointerEvents:"none"},children:[["top","bottom","left","right"].map(d=>jsx(O,{id:`drop-${d}-${e}`,position:d,activeClassName:r.dropPreview},d)),jsx(O,{id:`drop-center-${e}`,position:"center",activeClassName:r.swapPreview})]})]})})},le=({children:e,className:t,style:n})=>{let o=useContext(X);if(!o)throw new Error("<DragHandle> must be used inside a <Pane>");return jsx("div",{className:t,style:{cursor:"grab",userSelect:"none",...n},...o,children:e})};export{B as DashboardProvider,le as DragHandle,ae as Pane,Z as PaneTree,y as removePane,E as splitPane,z as swapPanes,D as useDashboard};//# sourceMappingURL=index.js.map
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-zeugma",
|
|
3
|
+
"version": "0.1.1",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"author": "yusufarsln98",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/yusufarsln98/react-zeugma.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/yusufarsln98/react-zeugma/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://yusufarsln98.github.io/react-zeugma/",
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public",
|
|
34
|
+
"registry": "https://registry.npmjs.org/"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"react",
|
|
38
|
+
"drag-and-drop",
|
|
39
|
+
"dashboard",
|
|
40
|
+
"grid",
|
|
41
|
+
"mosaic",
|
|
42
|
+
"zeugma",
|
|
43
|
+
"dnd-kit",
|
|
44
|
+
"layout",
|
|
45
|
+
"split-pane",
|
|
46
|
+
"recursive",
|
|
47
|
+
"tree",
|
|
48
|
+
"panel",
|
|
49
|
+
"resizable",
|
|
50
|
+
"widget"
|
|
51
|
+
],
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup",
|
|
54
|
+
"dev": "tsup --watch",
|
|
55
|
+
"lint": "eslint src --ext ts,tsx",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\" \"docs/**/*.{ts,tsx,mdx}\"",
|
|
58
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx}\" \"docs/**/*.{ts,tsx,mdx}\"",
|
|
59
|
+
"demo": "npm run dev --workspace=demo",
|
|
60
|
+
"storybook": "storybook dev -p 6006",
|
|
61
|
+
"build-storybook": "storybook build -o storybook-static",
|
|
62
|
+
"prepare": "husky",
|
|
63
|
+
"release": "changeset publish",
|
|
64
|
+
"publish:npm": "sh -c 'set -a && [ -f .env ] && . ./.env; set +a; if [ -z \"${NPM_TOKEN:-}\" ]; then echo \"Error: NPM_TOKEN is not set. Add it to .env (see .env.example).\"; exit 1; fi; OTP_ARG=\"\"; [ -n \"${NPM_OTP:-}\" ] && OTP_ARG=\"--otp=${NPM_OTP}\"; npm publish --registry https://registry.npmjs.org/ --auth-type=legacy $OTP_ARG'"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
68
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
69
|
+
},
|
|
70
|
+
"workspaces": [
|
|
71
|
+
"demo"
|
|
72
|
+
],
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"@dnd-kit/core": "^6.3.1",
|
|
75
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
76
|
+
"@dnd-kit/utilities": "^3.2.2"
|
|
77
|
+
},
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@changesets/cli": "^2.31.0",
|
|
80
|
+
"@storybook/addon-docs": "^10.4.1",
|
|
81
|
+
"@storybook/react-vite": "^10.4.1",
|
|
82
|
+
"@types/node": "^25.9.1",
|
|
83
|
+
"@types/react": "^19.2.15",
|
|
84
|
+
"@types/react-dom": "^19.2.3",
|
|
85
|
+
"eslint": "^10.4.1",
|
|
86
|
+
"eslint-plugin-storybook": "^10.4.1",
|
|
87
|
+
"husky": "^9.1.7",
|
|
88
|
+
"lint-staged": "^16.4.0",
|
|
89
|
+
"prettier": "^3.8.3",
|
|
90
|
+
"react": "^19.2.6",
|
|
91
|
+
"react-dom": "^19.2.6",
|
|
92
|
+
"storybook": "^10.4.1",
|
|
93
|
+
"tsup": "^8.5.1",
|
|
94
|
+
"typescript": "^6.0.3",
|
|
95
|
+
"typescript-eslint": "^8.60.0"
|
|
96
|
+
},
|
|
97
|
+
"eslintConfig": {
|
|
98
|
+
"extends": [
|
|
99
|
+
"plugin:storybook/recommended"
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
}
|