rectflow 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -30
- package/dist/rectflow.cjs.js +1 -1
- package/dist/rectflow.es.js +726 -81
- package/dist/rectflow.umd.js +1 -1
- package/package.json +9 -1
- package/dist/AreaRenderer.d.ts +0 -11
- package/dist/Grid.d.ts +0 -16
- package/dist/LayoutEngine.d.ts +0 -9
- package/dist/Rectflow.d.ts +0 -13
- package/dist/index.d.ts +0 -1
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Rectflow lets you define rows, columns, gaps, and named areas — then calculate
|
|
|
18
18
|
|
|
19
19
|
- Automatic DOM creation for missing areas
|
|
20
20
|
- Resize-aware (re-layout on container resize)
|
|
21
|
+
- Interactive area resizing using draggable gutters
|
|
21
22
|
- CDN-ready bundle + npm package
|
|
22
23
|
|
|
23
24
|
---
|
|
@@ -50,29 +51,52 @@ import { Rectflow } from 'rectflow'
|
|
|
50
51
|
### HTML
|
|
51
52
|
|
|
52
53
|
```html
|
|
53
|
-
<div
|
|
54
|
+
<div id="container"></div>
|
|
54
55
|
```
|
|
55
56
|
|
|
57
|
+
### CSS
|
|
58
|
+
|
|
59
|
+
```css
|
|
60
|
+
#container {
|
|
61
|
+
width: 100vw;
|
|
62
|
+
height: 100vh;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
⚠️ Important:
|
|
67
|
+
If the container does not have a defined height, Rectflow will not be able to calculate or render the layout correctly.
|
|
68
|
+
|
|
56
69
|
### JavaScript
|
|
57
70
|
|
|
58
71
|
```ts
|
|
59
|
-
const
|
|
72
|
+
const containerElem = document.getElementById('container')
|
|
60
73
|
|
|
61
74
|
const rectflow = new Rectflow({
|
|
62
|
-
container:
|
|
75
|
+
container: containerElem,
|
|
63
76
|
layout: {
|
|
64
|
-
rows: '
|
|
65
|
-
columns: '
|
|
66
|
-
gap:
|
|
77
|
+
rows: '60px auto 60px',
|
|
78
|
+
columns: '60px 240px auto 60px',
|
|
79
|
+
gap: 6,
|
|
67
80
|
areas: [
|
|
68
|
-
['
|
|
69
|
-
['
|
|
70
|
-
['
|
|
81
|
+
['A A A A'],
|
|
82
|
+
['B E C D'],
|
|
83
|
+
['B E F D'],
|
|
71
84
|
],
|
|
72
|
-
|
|
85
|
+
resize: {
|
|
86
|
+
handles: [
|
|
87
|
+
{ between: ['C', 'F'] },
|
|
88
|
+
{ between: ['E', 'C'] },
|
|
89
|
+
],
|
|
90
|
+
gutter: {
|
|
91
|
+
size: 4,
|
|
92
|
+
style: {
|
|
93
|
+
hoverColor: 'rgba(0, 0, 0, 0.1)',
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
}
|
|
73
98
|
})
|
|
74
99
|
|
|
75
|
-
rectflow.layout()
|
|
76
100
|
```
|
|
77
101
|
|
|
78
102
|
---
|
|
@@ -118,20 +142,17 @@ This makes rapid prototyping easy.
|
|
|
118
142
|
|
|
119
143
|
## 🔁 Resize Handling
|
|
120
144
|
|
|
121
|
-
Rectflow
|
|
122
|
-
|
|
123
|
-
- Uses `ResizeObserver`
|
|
124
|
-
- Re-applies positions when size changes
|
|
145
|
+
Rectflow automatically recalculates the layout when the container size changes.
|
|
125
146
|
|
|
126
|
-
|
|
147
|
+
It also supports **interactive resizing of individual areas** using draggable gutters, allowing users to dynamically adjust the layout at runtime.
|
|
127
148
|
|
|
128
149
|
---
|
|
129
150
|
|
|
130
151
|
## ⚙️ API Reference
|
|
131
152
|
|
|
132
|
-
### `new Rectflow(
|
|
153
|
+
### `new Rectflow(options)`
|
|
133
154
|
|
|
134
|
-
####
|
|
155
|
+
#### options
|
|
135
156
|
|
|
136
157
|
```ts
|
|
137
158
|
{
|
|
@@ -139,34 +160,47 @@ No manual resize handling required.
|
|
|
139
160
|
layout: {
|
|
140
161
|
rows: string
|
|
141
162
|
columns: string
|
|
142
|
-
gap?: number
|
|
143
163
|
areas: string[][]
|
|
164
|
+
gap?: number
|
|
165
|
+
resize: {
|
|
166
|
+
handles: {
|
|
167
|
+
between: string[]
|
|
168
|
+
}[]
|
|
169
|
+
gutter: {
|
|
170
|
+
size: number
|
|
171
|
+
color?: string
|
|
172
|
+
hoverColor?: string
|
|
173
|
+
activeColor?: string
|
|
174
|
+
}
|
|
175
|
+
}
|
|
144
176
|
}
|
|
145
177
|
}
|
|
146
178
|
```
|
|
147
179
|
|
|
148
|
-
|
|
180
|
+
Resize handles are automatically resolved as horizontal or vertical based on the shared boundary between areas.
|
|
181
|
+
|
|
149
182
|
|
|
150
|
-
### `
|
|
183
|
+
### `getArea(name)`
|
|
151
184
|
|
|
152
|
-
|
|
185
|
+
Returns the HTML element for a given area and lets you perform your own custom logic on it.
|
|
153
186
|
|
|
154
187
|
```ts
|
|
155
|
-
rectflow.
|
|
188
|
+
rectflow.getArea('A')
|
|
156
189
|
```
|
|
157
190
|
|
|
191
|
+
---
|
|
192
|
+
## 🚫 Non-Goals
|
|
158
193
|
---
|
|
159
194
|
|
|
160
|
-
|
|
195
|
+
Rectflow is not intended to:
|
|
161
196
|
|
|
162
|
-
|
|
197
|
+
- Replace CSS Grid for static layouts
|
|
198
|
+
- Handle animations or transitions
|
|
199
|
+
- Manage application state
|
|
163
200
|
|
|
164
|
-
|
|
165
|
-
rectflow.layout()
|
|
166
|
-
```
|
|
201
|
+
It focuses solely on **layout calculation and positioning**.
|
|
167
202
|
|
|
168
203
|
---
|
|
169
|
-
|
|
170
204
|
## 📄 License
|
|
171
205
|
|
|
172
206
|
MIT
|
|
@@ -178,8 +212,9 @@ MIT
|
|
|
178
212
|
Inspired by:
|
|
179
213
|
|
|
180
214
|
- CSS Grid
|
|
181
|
-
- Game UI layout systems
|
|
182
215
|
- Charting & trading platforms
|
|
216
|
+
- Game UI layout systems
|
|
217
|
+
- Code Editors
|
|
183
218
|
- Dashboards
|
|
184
219
|
|
|
185
220
|
---
|
package/dist/rectflow.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class g{constructor(e){this.config=e}compute(e){const t=this.parseTracks(this.config.rows,e.height),r=this.parseTracks(this.config.columns,e.width),i=this.config.gap??0,n=this.accumulate(t,i),a=this.accumulate(r,i),l=this.normalizeAreas(this.config.areas),h={};for(let s=0;s<l.length;s++)for(let o=0;o<l[s].length;o++){const u=l[s][o];if(u!==".")if(!h[u])h[u]={x:a[o],y:n[s],width:r[o],height:t[s]};else{const c=h[u];a[o]+r[o]>c.x+c.width&&(c.width=a[o]+r[o]-c.x),n[s]+t[s]>c.y+c.height&&(c.height=n[s]+t[s]-c.y)}}return h}normalizeAreas(e){return e.map(t=>t.length===1?t[0].trim().split(/\s+/):t)}parseTracks(e,t){const r=e.split(/\s+/),i=this.config.gap??0;let n=0,a=0;for(const s of r)s.endsWith("px")?n+=parseFloat(s):s.endsWith("fr")?a+=parseFloat(s):s==="auto"&&(a+=1);const l=t-n-(r.length-1)*i,h=a>0?l/a:0;return r.map(s=>s.endsWith("px")?parseFloat(s):s.endsWith("fr")?parseFloat(s)*h:s==="auto"?h:0)}accumulate(e,t){const r=[];let i=0;for(const n of e)r.push(i),i+=n+t;return r}}class d{constructor(e){this.config=e,this.engine=new g(e.layout)}areas=new Map;engine;registerArea(e,t){const r=this.areas.get(e);r?.auto&&r.elem.remove(),t.style.position="absolute",this.areas.set(e,{elem:t,auto:!1})}layout(){const e={x:0,y:0,width:this.config.container.clientWidth,height:this.config.container.clientHeight},t=this.engine.compute(e);for(const r in t){const i=this.ensureArea(r),n=t[r];Object.assign(i.style,{left:`${n.x}px`,top:`${n.y}px`,width:`${n.width}px`,height:`${n.height}px`})}}ensureArea(e){function t(){return`hsl(${Math.floor(Math.random()*360)}, 70%, 70%)`}const r=this.areas.get(e);if(r)return r.elem;const i=document.createElement("div");return i.dataset.rectflowArea=e,i.style.background=t(),i.style.position="absolute",this.config.container.appendChild(i),this.areas.set(e,{elem:i,auto:!0}),i}clearArea(){this.areas.clear()}}class p{areaRenderer;observer;constructor(e){e.container.style.position="relative",this.areaRenderer=new d(e),this.observer=new ResizeObserver(()=>this.layout()),this.observer.observe(e.container)}registerArea(e,t){this.areaRenderer.registerArea(e,t)}layout(){this.areaRenderer.layout()}destroy(){this.observer.disconnect(),this.areaRenderer.clearArea()}}exports.Rectflow=p;
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class L{layoutAreas;cells=[];boxes={};horizontalBoundary=[];verticalBoundary=[];constructor(t){this.layoutAreas=t.options.layout.areas}calculate(){this.cells=[],this.boxes={},this.horizontalBoundary=[],this.verticalBoundary=[],this.buildCells(),this.calculateBoxes(),this.computeHorizontalBoundaries(),this.computeVerticalBoundaries()}buildCells(){for(let t=0;t<this.layoutAreas.length;t++)for(let e=0;e<this.layoutAreas[t].length;e++)this.cells.push({area:this.layoutAreas[t][e],row:t,col:e})}calculateBoxes(){for(const t of this.cells)if(!this.boxes[t.area])this.boxes[t.area]={area:t.area,rowStart:t.row,rowEnd:t.row,colStart:t.col,colEnd:t.col};else{const e=this.boxes[t.area];e.rowStart=Math.min(e.rowStart,t.row),e.rowEnd=Math.max(e.rowEnd,t.row),e.colStart=Math.min(e.colStart,t.col),e.colEnd=Math.max(e.colEnd,t.col)}}computeBoundaries(t,e,o,s,i){const r=[];let n=1/0,a=-1/0;for(const l of t)n=Math.min(n,l[s]),a=Math.max(a,l[i]);const d=new Set;for(const l of t)d.add(l[e]);for(const l of d){let f=null;for(let p=n;p<=a;p++){const y=t.find(m=>m[e]===l&&p>=m[s]&&p<=m[i])?.area,v=t.find(m=>m[o]===l+1&&p>=m[s]&&p<=m[i])?.area;if(!y||!v||y===v){f&&(r.push(f),f=null);continue}f||(f={first:[],second:[]}),f.first.includes(y)||f.first.push(y),f.second.includes(v)||f.second.push(v)}f&&r.push(f)}return r}computeHorizontalBoundaries(){this.horizontalBoundary=this.computeBoundaries(Object.values(this.boxes),"rowEnd","rowStart","colStart","colEnd")}computeVerticalBoundaries(){this.verticalBoundary=this.computeBoundaries(Object.values(this.boxes),"colEnd","colStart","rowStart","rowEnd")}}class w{x;y;width;height;constructor(t){this.x=t.x,this.y=t.y,this.width=t.width,this.height=t.height}moveX(t){this.x+=t}resizeWidth(t){this.width+=t}growFromLeft(t){this.x-=t,this.width+=t}shrinkFromLeft(t){this.x+=t,this.width-=t}growFromRight(t){this.width+=t}shrinkFromRight(t){this.width-=t}moveY(t){this.y+=t}resizeHeight(t){this.height+=t}growFromTop(t){this.y-=t,this.height+=t}shrinkFromTop(t){this.y+=t,this.height-=t}growFromBottom(t){this.height+=t}shrinkFromBottom(t){this.height-=t}clone(){return new w({x:this.x,y:this.y,width:this.width,height:this.height})}}class I{unit;baseValue;size=0;delta=0;min=0;max=1/0;constructor(t,e){this.unit=t,this.baseValue=e}effectiveSize(){return Math.max(this.min,Math.min(this.size+this.delta,this.max))}clampDelta(t){const e=this.effectiveSize();return t>0?Math.min(t,this.max-e):Math.max(t,this.min-e)}applyDelta(t){this.delta+=t}}class O{constructor(t){this.context=t}computedRect={};rowTracks=[];colTracks=[];tracksBuilt=!1;tracksResolved=!1;calculate(){this.initTracks(),this.computedRect={};const t=this.context.options.container,e=this.context.options.layout,o=this.context.areaTopology.boxes,s=e.gap??0;this.tracksResolved?(this.scaleTracks(this.rowTracks,t.clientHeight,s),this.scaleTracks(this.colTracks,t.clientWidth,s)):(this.resolveTracks(this.rowTracks,t.clientHeight,s),this.resolveTracks(this.colTracks,t.clientWidth,s),this.tracksResolved=!0);const i=this.accumulateTracks(this.rowTracks,s),r=this.accumulateTracks(this.colTracks,s);for(const n in o){const a=o[n],d=r[a.colStart],l=i[a.rowStart];let f=0;for(let y=a.colStart;y<=a.colEnd;y++)f+=this.colTracks[y].effectiveSize(),y<a.colEnd&&(f+=s);let p=0;for(let y=a.rowStart;y<=a.rowEnd;y++)p+=this.rowTracks[y].effectiveSize(),y<a.rowEnd&&(p+=s);this.computedRect[n]=new w({x:d,y:l,width:f,height:p})}}initTracks(){if(this.tracksBuilt)return;const t=this.context.options.layout;this.rowTracks=this.buildTracks(t.rows),this.colTracks=this.buildTracks(t.columns),this.tracksBuilt=!0,this.tracksResolved=!1}buildTracks(t){return t.split(/\s+/).map(e=>{if(e.endsWith("px"))return new I("px",parseFloat(e));if(e.endsWith("fr"))return new I("fr",parseFloat(e));if(e==="auto")return new I("auto",1);throw new Error(`Invalid track: ${e}`)})}resolveTracks(t,e,o){const s=o*(t.length-1),i=e-s;let r=0,n=0;for(const l of t)l.unit==="px"?r+=l.baseValue:l.unit==="fr"?n+=l.baseValue:l.unit==="auto"&&(n+=1);const a=Math.max(0,i-r),d=n>0?a/n:0;for(const l of t)l.size===0&&(l.unit==="px"?l.size=l.baseValue:l.unit==="fr"?l.size=l.baseValue*d:l.unit==="auto"&&(l.size=d))}accumulateTracks(t,e){const o=[];let s=0;for(const i of t)o.push(s),s+=i.effectiveSize()+e;return o}scaleTracks(t,e,o){const s=o*(t.length-1),i=e-s,r=t.filter(f=>f.unit==="px").reduce((f,p)=>f+p.effectiveSize(),0),n=t.filter(f=>f.unit!=="px"),a=n.reduce((f,p)=>f+p.effectiveSize(),0);if(a===0)return;const l=Math.max(0,i-r)/a;for(const f of n)f.size*=l,f.delta*=l}resizeRow(t,e){const o=this.rowTracks[t],s=this.rowTracks[t+1];if(!o||!s)return 0;const i=o.clampDelta(e),r=-s.clampDelta(-e),n=Math.abs(i)<Math.abs(r)?i:r;return n===0?0:(o.applyDelta(n),s.applyDelta(-n),n)}resizeColumn(t,e){const o=this.colTracks[t],s=this.colTracks[t+1];if(!o||!s)return 0;const i=o.clampDelta(e),r=-s.clampDelta(-e),n=Math.abs(i)<Math.abs(r)?i:r;return n===0?0:(o.applyDelta(n),s.applyDelta(-n),n)}}class x{elem;rect;constructor(t){this.elem=document.createElement("div"),this.elem.style.position="absolute",t instanceof w?this.rect=t:this.rect=new w(t),this.applyRect()}update(t){t instanceof w?this.rect=t:this.rect=new w(t),this.applyRect()}applyRect(){this.style({left:`${this.rect.x}px`,top:`${this.rect.y}px`,width:`${this.rect.width}px`,height:`${this.rect.height}px`})}style(t){Object.assign(this.elem.style,t)}mount(t){this.elem.parentElement||t.appendChild(this.elem)}remove(){this.elem.remove()}}class R extends x{constructor(t,e){super(e),this.name=t}isArea(t){return this.name===t}}class N{constructor(t){this.context=t}areas={};apply(){const t=this.context.options.container,e=this.context.layoutEngine.computedRect;for(const o in this.areas)e[o]||(this.areas[o].remove(),delete this.areas[o]);for(const o in e){const s=e[o];let i=this.areas[o];i?i.update(s):(i=new R(o,s),this.areas[o]=i,i.elem.style.overflow="hidden",i.elem.dataset.rectflowArea=o,i.elem.style.background="white",i.mount(t))}}getView(t){return this.areas[t]}getAreaElement(t){return this.areas[t].elem}clear(){Object.values(this.areas).forEach(t=>t.remove()),this.areas={}}}class D extends x{state="idle";config;direction;boundary;constructor(t,e){super(e),this.config=t.config,this.direction=t.direction,this.boundary=t.boundary,this.init()}init(){const t=this.elem;t.style.cursor=this.direction==="horizontal"?"row-resize":"col-resize",t.style.transition="background 0.2s",t.style.borderRadius="3px",this.applyIdleStyle()}setState(t){if(this.state!==t)switch(this.state=t,t){case"idle":this.applyIdleStyle();break;case"hover":this.applyHoverStyle();break;case"active":this.applyActiveStyle();break}}applyIdleStyle(){this.elem.style.background=this.config.style?.color}applyHoverStyle(){console.log("hover"),this.elem.style.background=this.config.style?.hoverColor??"red"}applyActiveStyle(){this.elem.style.background=this.config.style?.activeColor}}class k{constructor(t){this.context=t,this.areaTopology=t.areaTopology,this.gutter=t.options.layout.resize?.gutter,this.layoutGap=t.options.layout.gap??0,this.initHandles()}areaTopology;handles;gutter;layoutGap;horizontalGutters=new Map;verticalGutters=new Map;resizeObserver;initHandles(){const t=this.context.options.layout.resize?.handles;t&&(this.handles=t.map(e=>{const[o,s]=e.between;return{between:[o,s],min:e.min,max:e.max}}))}apply(){this.resizeObserver||this.addContainerResize(),this.handles.length>0&&(this.createHorizontalGutters(),this.createVerticalGutters())}addContainerResize(){const t=this.context.options.container;this.resizeObserver=new ResizeObserver(()=>{this.context.onLayoutChange?.()}),this.resizeObserver.observe(t)}getBoundary(t,e){const[o,s]=t.between;for(const i of e)if(i.first.includes(o)&&i.second.includes(s)||i.first.includes(s)&&i.second.includes(o))return i}getBoundaryKey(t){return t.first.join("|")+"::"+t.second.join("|")}createHorizontalGutters(){const t=this.context.layoutEngine.computedRect,e=new Set;for(const o of this.handles){const s=this.getBoundary(o,this.areaTopology.horizontalBoundary);if(!s)continue;const i=this.getBoundaryKey(s);e.add(i);let r={...this.computeGutterSpan(s,"x")};const n=s.first[0],a=t[n];r.y=a.y+a.height+this.layoutGap/2-this.gutter.size/2,r.height=this.gutter.size,this.createOrUpdateGutterView(this.horizontalGutters,i,r,"horizontal",s)}this.cleanupInactiveGutters(this.horizontalGutters,e)}createVerticalGutters(){const t=this.context.layoutEngine.computedRect,e=new Set;for(const o of this.handles){const s=this.getBoundary(o,this.areaTopology.verticalBoundary);if(!s)continue;const i=this.getBoundaryKey(s);e.add(i);let r={...this.computeGutterSpan(s,"y")};const n=s.first[0],a=t[n];r.x=a.x+a.width+this.layoutGap/2-this.gutter.size/2,r.width=this.gutter.size,this.createOrUpdateGutterView(this.verticalGutters,i,r,"vertical",s)}this.cleanupInactiveGutters(this.verticalGutters,e)}createOrUpdateGutterView(t,e,o,s,i){let r=t.get(e);r?r.update(new w(o)):(r=new D({config:this.context.options.layout.resize?.gutter,direction:s,boundary:i},new w(o)),r.mount(this.context.options.container),t.set(e,r),this.attachGutterDrag(r,i,s))}cleanupInactiveGutters(t,e){for(const[o,s]of t)e.has(o)||(s.remove(),t.delete(o))}getRowIndexFromBoundary(t){const e=t.first[0];return this.context.areaTopology.boxes[e].rowEnd}getColIndexFromBoundary(t){const e=t.first[0];return this.context.areaTopology.boxes[e].colEnd}attachGutterDrag(t,e,o){let s=0,i=!1,r=!1,n=null,a=!1;const d=this.context.options.layout.resize?.gutter?.delay,l=g=>o==="horizontal"?g.clientY:g.clientX,f=g=>{if(!r)return;const b=l(g)-s;if(b===0)return;let A=0;if(o==="horizontal"){const T=this.getRowIndexFromBoundary(e);A=this.context.layoutEngine.resizeRow(T,b)}else{const T=this.getColIndexFromBoundary(e);A=this.context.layoutEngine.resizeColumn(T,b)}A!==0&&(s+=A,this.context.onLayoutChange?.())},p=g=>{g.preventDefault(),r=!0,s=l(g),n!==null&&(clearTimeout(n),n=null),a=!1,t.setState("active"),document.addEventListener("mousemove",f),document.addEventListener("mouseup",y)},y=()=>{r=!1,i&&a?t.setState("hover"):t.setState("idle"),document.removeEventListener("mousemove",f),document.removeEventListener("mouseup",y)},v=()=>{i=!0,a=!1,n=window.setTimeout(()=>{!r&&i&&(t.setState("hover"),a=!0)},d)},m=()=>{i=!1,n!==null&&(clearTimeout(n),n=null),a=!1,r||t.setState("idle")};t.elem.addEventListener("mouseenter",v),t.elem.addEventListener("mouseleave",m),t.elem.addEventListener("mousedown",p)}computeGutterSpan(t,e){const o=t.first;let s=1/0,i=-1/0;for(const r of o){const{x:n,y:a,width:d,height:l}=this.context.layoutEngine.computedRect[r];e==="x"?(s=Math.min(s,n),i=Math.max(i,n+d)):(s=Math.min(s,a),i=Math.max(i,a+l))}return e==="x"?{x:s,width:i-s}:{y:s,height:i-s}}relayoutGutters(){for(const t of this.horizontalGutters.values()){const e=t.boundary,o=this.computeGutterSpan(e,"x");t.rect.x=o.x,t.rect.width=o.width;const s=this.context.areaRenderer.getView(e.first[0]).rect;t.rect.y=s.y+s.height+this.layoutGap/2-this.gutter.size/2,t.applyRect()}for(const t of this.verticalGutters.values()){const e=t.boundary,o=this.computeGutterSpan(e,"y");t.rect.y=o.y,t.rect.height=o.height;const s=this.context.areaRenderer.getView(e.first[0]).rect;t.rect.x=s.x+s.width+this.layoutGap/2-this.gutter.size/2,t.applyRect()}}}const E={layout:{gap:0,areas:[],resize:{handles:[],gutter:{size:6,delay:150,style:{color:"transparent",hoverColor:"rgba(0,0,0,0.2)",activeColor:"rgba(0,0,0,0.45)"}}}},strict:!0};class c extends Error{code;constructor(t,e){super(`[Rectflow] ${t}`),this.name="RectflowError",this.code=e}}var h=(u=>(u.CONTAINER_NOT_FOUND="CONTAINER_NOT_FOUND",u.INVALID_CONTAINER="INVALID_CONTAINER",u.INVALID_GRID_CONFIG="INVALID_GRID_CONFIG",u.INVALID_GRID_AREAS="INVALID_GRID_AREAS",u.COMPUTE_FAILED="COMPUTE_FAILED",u.INVALID_LAYOUT="INVALID_LAYOUT",u))(h||{});function z(u,t){if(t==null)return structuredClone(u);if(typeof u!="object"||u===null)return t;const e=structuredClone(u);for(const o in t){const s=t[o],i=e[o];s!==null&&typeof s=="object"&&!Array.isArray(s)?e[o]=z(i,s):s!==void 0&&(e[o]=s)}return e}class S{options;resolve(t){return this.options=t,this.normalizeContainer(),this.normalizeAreas(),this.normalizeGutter(),this.validateContainer(),this.validateRowsCols(),this.validateAreas(),this.validateResizeHandles(),this.validateGutter(),z(E,t)}normalizeContainer(){const t=this.options.container;if(typeof t=="string"){const e=document.querySelector(t);if(!e)throw new c(`Container selector "${t}" does not match any element in HTML`,h.CONTAINER_NOT_FOUND);this.options.container=e}}normalizeAreas(){const{areas:t}=this.options.layout;if(!t)throw new c("layout.areas is required",h.INVALID_LAYOUT);if(!Array.isArray(t))throw new c("layout.areas must be an array of rows (string[][])",h.INVALID_LAYOUT);this.options.layout.areas=t.map((e,o)=>{if(!Array.isArray(e))throw new c(`layout.areas[${o}] must be an array`,h.INVALID_LAYOUT);if(e.length===1){const s=e[0];if(typeof s!="string")throw new c(`layout.areas[${o}][0] must be a string`,h.INVALID_LAYOUT);const i=s.trim().split(/\s+/);if(i.length===0||i.some(r=>!r))throw new c(`layout.areas[${o}] contains invalid area names`,h.INVALID_LAYOUT);return i}return e.map((s,i)=>{if(typeof s!="string")throw new c(`layout.areas[${o}][${i}] must be a string`,h.INVALID_LAYOUT);if(!s.trim())throw new c(`layout.areas[${o}][${i}] cannot be empty`,h.INVALID_LAYOUT);return s.trim()})})}normalizeGutter(){if(!this.options.layout.resize)return;typeof this.options.layout.resize.gutter=="number"&&(this.options.layout.resize.gutter={size:6})}validateContainer(){const t=this.options.container;if(!t)throw new c("Container is null or undefined",h.CONTAINER_NOT_FOUND);if(!(t instanceof HTMLElement))throw new c("Provided container is not a valid HTMLElement",h.INVALID_CONTAINER)}validateRowsCols(){const t=(e,o)=>{if(e==null)throw new c(`layout.${o} is required`,h.INVALID_LAYOUT);if(typeof e!="string")throw new c(`layout.${o} must be a string`,h.INVALID_LAYOUT);if(!e.trim())throw new c(`layout.${o} cannot be empty`,h.INVALID_LAYOUT);const s=/^(auto|\d+(\.\d+)?(px|fr))$/,i=e.trim().split(/\s+/);for(const r of i)if(!s.test(r))throw new c(`Invalid layout.${o} track value "${r}". Allowed values: px (e.g. 60px), fr (e.g. 1fr), or auto.`,h.INVALID_LAYOUT)};t(this.options.layout.rows,"rows"),t(this.options.layout.columns,"columns")}validateAreas(){const t=this.options.layout.areas,e=this.options.layout.rows,o=this.options.layout.columns,s=e.trim().split(/\s+/).length,i=o.trim().split(/\s+/).length;if(t.length!==s)throw new c(`Areas row count (${t.length}) does not match rows definition (${s})`,h.INVALID_LAYOUT);for(let r=0;r<t.length;r++){const n=t[r];if(n.length!==i)throw new c(`Areas column count mismatch at row ${r}. Expected ${i}, got ${n.length}`,h.INVALID_LAYOUT)}}validateResizeHandles(){const t=this.options.layout.resize;if(!t||!t.handles)return;const{handles:e}=t;if(!Array.isArray(e))throw new c("resize.handles must be an array",h.INVALID_LAYOUT);function o(i){const r=new Set;for(const n of i)for(const a of n)r.add(a);return r}const s=o(this.options.layout.areas);e.forEach((i,r)=>{if(typeof i!="object"||i===null)throw new c(`resize.handles[${r}] must be an object`,h.INVALID_LAYOUT);if(!("between"in i))throw new c(`resize.handles[${r}] is missing required property "between"`,h.INVALID_LAYOUT);const n=i.between;if(!Array.isArray(n))throw new c(`resize.handles[${r}].between must be an array`,h.INVALID_LAYOUT);if(n.length!==2)throw new c(`resize.handles[${r}].between must contain exactly 2 area names`,h.INVALID_LAYOUT);const[a,d]=n;if(typeof a!="string"||typeof d!="string")throw new c(`resize.handles[${r}].between values must be strings`,h.INVALID_LAYOUT);if(!a.trim()||!d.trim())throw new c(`resize.handles[${r}].between cannot contain empty strings`,h.INVALID_LAYOUT);if(a===d)throw new c(`resize.handles[${r}].between must reference two different areas`,h.INVALID_LAYOUT);if(!s.has(a)||!s.has(d))throw new c(`resize.handles[${r}] references unknown areas: ${a}, ${d}`,h.INVALID_LAYOUT)})}validateGutter(){const t=this.options.layout.resize;if(!t||t.gutter==null)return;const{gutter:e}=t;if(typeof e=="number"){if(!Number.isFinite(e)||e<0)throw new c("resize.gutter must be a non-negative number",h.INVALID_LAYOUT);return}if(typeof e!="object")throw new c("resize.gutter must be a number or an object",h.INVALID_LAYOUT);const{size:o,delay:s,style:i}=e;if(s!==void 0&&(typeof s!="number"||!Number.isFinite(s)||s<0))throw new c("resize.gutter.delay must be a non-negative number",h.INVALID_LAYOUT);if(o!==void 0&&(typeof o!="number"||!Number.isFinite(o)||o<0))throw new c("resize.gutter.size must be a non-negative number",h.INVALID_LAYOUT);function r(a){const d=new Option().style;return d.color=a,d.color!==""}const n=(a,d)=>{if(typeof a!="string")throw new c(`${d} must be a string`,h.INVALID_LAYOUT);if(!r(a))throw new c(`${d} is not a valid CSS color`,h.INVALID_LAYOUT)};if(i!==void 0){if(typeof i!="object"||i===null)throw new c("resize.gutter.style must be an object",h.INVALID_LAYOUT);const{color:a,hoverColor:d,activeColor:l}=i;a!==void 0&&n(a,"resize.gutter.style.color"),d!==void 0&&n(d,"resize.gutter.style.hoverColor"),l!==void 0&&n(l,"resize.gutter.style.activeColor")}}}class V{_options;resolver;constructor(t,e=new S){this.resolver=e,this._options=Object.freeze(this.resolver.resolve(t))}get options(){return this._options}}class _{optionsStore;areaTopology;layoutEngine;areaRenderer;resizeManager;onLayoutChange;constructor(t){this.optionsStore=new V(t),this.areaTopology=new L(this),this.layoutEngine=new O(this),this.areaRenderer=new N(this),this.resizeManager=new k(this)}get options(){return this.optionsStore.options}}const C=(u,t=!0)=>{if(t)throw u;u instanceof c?console.error(u.message,u.code):console.error("[Rectflow] Unknown error",u)};class G{context;constructor(t){this.context=new _(t);const{container:e}=this.context.options;e.style.position="absolute",this.context.onLayoutChange=()=>{this.context.layoutEngine.calculate(),this.context.areaRenderer.apply(),this.context.resizeManager.relayoutGutters()};try{this.initLayout(),this.applyLayout()}catch(o){C(o,this.context.options.strict)}}initLayout(){this.context.areaTopology.calculate(),this.context.layoutEngine.initTracks()}applyLayout(){this.context.areaTopology.calculate(),this.context.layoutEngine.calculate(),this.context.areaRenderer.apply(),this.context.resizeManager.apply()}getArea(t){return this.context.areaRenderer.getAreaElement(t)}}exports.Rectflow=G;
|
package/dist/rectflow.es.js
CHANGED
|
@@ -1,105 +1,750 @@
|
|
|
1
|
-
class
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
h[u] = {
|
|
13
|
-
x: a[o],
|
|
14
|
-
y: n[s],
|
|
15
|
-
width: r[o],
|
|
16
|
-
height: t[s]
|
|
17
|
-
};
|
|
18
|
-
else {
|
|
19
|
-
const c = h[u];
|
|
20
|
-
a[o] + r[o] > c.x + c.width && (c.width = a[o] + r[o] - c.x), n[s] + t[s] > c.y + c.height && (c.height = n[s] + t[s] - c.y);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return h;
|
|
1
|
+
class L {
|
|
2
|
+
layoutAreas;
|
|
3
|
+
cells = [];
|
|
4
|
+
boxes = {};
|
|
5
|
+
horizontalBoundary = [];
|
|
6
|
+
verticalBoundary = [];
|
|
7
|
+
constructor(t) {
|
|
8
|
+
this.layoutAreas = t.options.layout.areas;
|
|
9
|
+
}
|
|
10
|
+
calculate() {
|
|
11
|
+
this.cells = [], this.boxes = {}, this.horizontalBoundary = [], this.verticalBoundary = [], this.buildCells(), this.calculateBoxes(), this.computeHorizontalBoundaries(), this.computeVerticalBoundaries();
|
|
24
12
|
}
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
buildCells() {
|
|
14
|
+
for (let t = 0; t < this.layoutAreas.length; t++)
|
|
15
|
+
for (let e = 0; e < this.layoutAreas[t].length; e++)
|
|
16
|
+
this.cells.push({
|
|
17
|
+
area: this.layoutAreas[t][e],
|
|
18
|
+
row: t,
|
|
19
|
+
col: e
|
|
20
|
+
});
|
|
27
21
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
calculateBoxes() {
|
|
23
|
+
for (const t of this.cells)
|
|
24
|
+
if (!this.boxes[t.area])
|
|
25
|
+
this.boxes[t.area] = {
|
|
26
|
+
area: t.area,
|
|
27
|
+
rowStart: t.row,
|
|
28
|
+
rowEnd: t.row,
|
|
29
|
+
colStart: t.col,
|
|
30
|
+
colEnd: t.col
|
|
31
|
+
};
|
|
32
|
+
else {
|
|
33
|
+
const e = this.boxes[t.area];
|
|
34
|
+
e.rowStart = Math.min(e.rowStart, t.row), e.rowEnd = Math.max(e.rowEnd, t.row), e.colStart = Math.min(e.colStart, t.col), e.colEnd = Math.max(e.colEnd, t.col);
|
|
35
|
+
}
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
computeBoundaries(t, e, o, s, i) {
|
|
37
38
|
const r = [];
|
|
38
|
-
let
|
|
39
|
-
for (const
|
|
40
|
-
|
|
39
|
+
let n = 1 / 0, a = -1 / 0;
|
|
40
|
+
for (const l of t)
|
|
41
|
+
n = Math.min(n, l[s]), a = Math.max(a, l[i]);
|
|
42
|
+
const d = /* @__PURE__ */ new Set();
|
|
43
|
+
for (const l of t) d.add(l[e]);
|
|
44
|
+
for (const l of d) {
|
|
45
|
+
let f = null;
|
|
46
|
+
for (let p = n; p <= a; p++) {
|
|
47
|
+
const y = t.find(
|
|
48
|
+
(m) => m[e] === l && p >= m[s] && p <= m[i]
|
|
49
|
+
)?.area, v = t.find(
|
|
50
|
+
(m) => m[o] === l + 1 && p >= m[s] && p <= m[i]
|
|
51
|
+
)?.area;
|
|
52
|
+
if (!y || !v || y === v) {
|
|
53
|
+
f && (r.push(f), f = null);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
f || (f = { first: [], second: [] }), f.first.includes(y) || f.first.push(y), f.second.includes(v) || f.second.push(v);
|
|
57
|
+
}
|
|
58
|
+
f && r.push(f);
|
|
59
|
+
}
|
|
41
60
|
return r;
|
|
42
61
|
}
|
|
62
|
+
computeHorizontalBoundaries() {
|
|
63
|
+
this.horizontalBoundary = this.computeBoundaries(
|
|
64
|
+
Object.values(this.boxes),
|
|
65
|
+
"rowEnd",
|
|
66
|
+
"rowStart",
|
|
67
|
+
"colStart",
|
|
68
|
+
"colEnd"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
computeVerticalBoundaries() {
|
|
72
|
+
this.verticalBoundary = this.computeBoundaries(
|
|
73
|
+
Object.values(this.boxes),
|
|
74
|
+
"colEnd",
|
|
75
|
+
"colStart",
|
|
76
|
+
"rowStart",
|
|
77
|
+
"rowEnd"
|
|
78
|
+
);
|
|
79
|
+
}
|
|
43
80
|
}
|
|
44
|
-
class
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
81
|
+
class w {
|
|
82
|
+
x;
|
|
83
|
+
y;
|
|
84
|
+
width;
|
|
85
|
+
height;
|
|
86
|
+
constructor(t) {
|
|
87
|
+
this.x = t.x, this.y = t.y, this.width = t.width, this.height = t.height;
|
|
88
|
+
}
|
|
89
|
+
moveX(t) {
|
|
90
|
+
this.x += t;
|
|
91
|
+
}
|
|
92
|
+
resizeWidth(t) {
|
|
93
|
+
this.width += t;
|
|
94
|
+
}
|
|
95
|
+
growFromLeft(t) {
|
|
96
|
+
this.x -= t, this.width += t;
|
|
97
|
+
}
|
|
98
|
+
shrinkFromLeft(t) {
|
|
99
|
+
this.x += t, this.width -= t;
|
|
100
|
+
}
|
|
101
|
+
growFromRight(t) {
|
|
102
|
+
this.width += t;
|
|
103
|
+
}
|
|
104
|
+
shrinkFromRight(t) {
|
|
105
|
+
this.width -= t;
|
|
106
|
+
}
|
|
107
|
+
moveY(t) {
|
|
108
|
+
this.y += t;
|
|
109
|
+
}
|
|
110
|
+
resizeHeight(t) {
|
|
111
|
+
this.height += t;
|
|
112
|
+
}
|
|
113
|
+
growFromTop(t) {
|
|
114
|
+
this.y -= t, this.height += t;
|
|
115
|
+
}
|
|
116
|
+
shrinkFromTop(t) {
|
|
117
|
+
this.y += t, this.height -= t;
|
|
118
|
+
}
|
|
119
|
+
growFromBottom(t) {
|
|
120
|
+
this.height += t;
|
|
121
|
+
}
|
|
122
|
+
shrinkFromBottom(t) {
|
|
123
|
+
this.height -= t;
|
|
124
|
+
}
|
|
125
|
+
clone() {
|
|
126
|
+
return new w({
|
|
127
|
+
x: this.x,
|
|
128
|
+
y: this.y,
|
|
129
|
+
width: this.width,
|
|
130
|
+
height: this.height
|
|
55
131
|
});
|
|
56
132
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
133
|
+
}
|
|
134
|
+
class I {
|
|
135
|
+
unit;
|
|
136
|
+
baseValue;
|
|
137
|
+
size = 0;
|
|
138
|
+
delta = 0;
|
|
139
|
+
min = 0;
|
|
140
|
+
max = 1 / 0;
|
|
141
|
+
constructor(t, e) {
|
|
142
|
+
this.unit = t, this.baseValue = e;
|
|
143
|
+
}
|
|
144
|
+
effectiveSize() {
|
|
145
|
+
return Math.max(this.min, Math.min(this.size + this.delta, this.max));
|
|
146
|
+
}
|
|
147
|
+
clampDelta(t) {
|
|
148
|
+
const e = this.effectiveSize();
|
|
149
|
+
return t > 0 ? Math.min(t, this.max - e) : Math.max(t, this.min - e);
|
|
150
|
+
}
|
|
151
|
+
applyDelta(t) {
|
|
152
|
+
this.delta += t;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
class O {
|
|
156
|
+
constructor(t) {
|
|
157
|
+
this.context = t;
|
|
158
|
+
}
|
|
159
|
+
computedRect = {};
|
|
160
|
+
rowTracks = [];
|
|
161
|
+
colTracks = [];
|
|
162
|
+
tracksBuilt = !1;
|
|
163
|
+
tracksResolved = !1;
|
|
164
|
+
// public setLayout(layout: LayoutConfig) {
|
|
165
|
+
// this.context.options
|
|
166
|
+
// this.context.options.layout = layout
|
|
167
|
+
// this.tracksBuilt = false
|
|
168
|
+
// this.tracksResolved = false
|
|
169
|
+
// this.calculate()
|
|
170
|
+
// }
|
|
171
|
+
calculate() {
|
|
172
|
+
this.initTracks(), this.computedRect = {};
|
|
173
|
+
const t = this.context.options.container, e = this.context.options.layout, o = this.context.areaTopology.boxes, s = e.gap ?? 0;
|
|
174
|
+
this.tracksResolved ? (this.scaleTracks(this.rowTracks, t.clientHeight, s), this.scaleTracks(this.colTracks, t.clientWidth, s)) : (this.resolveTracks(this.rowTracks, t.clientHeight, s), this.resolveTracks(this.colTracks, t.clientWidth, s), this.tracksResolved = !0);
|
|
175
|
+
const i = this.accumulateTracks(this.rowTracks, s), r = this.accumulateTracks(this.colTracks, s);
|
|
176
|
+
for (const n in o) {
|
|
177
|
+
const a = o[n], d = r[a.colStart], l = i[a.rowStart];
|
|
178
|
+
let f = 0;
|
|
179
|
+
for (let y = a.colStart; y <= a.colEnd; y++)
|
|
180
|
+
f += this.colTracks[y].effectiveSize(), y < a.colEnd && (f += s);
|
|
181
|
+
let p = 0;
|
|
182
|
+
for (let y = a.rowStart; y <= a.rowEnd; y++)
|
|
183
|
+
p += this.rowTracks[y].effectiveSize(), y < a.rowEnd && (p += s);
|
|
184
|
+
this.computedRect[n] = new w({ x: d, y: l, width: f, height: p });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
initTracks() {
|
|
188
|
+
if (this.tracksBuilt) return;
|
|
189
|
+
const t = this.context.options.layout;
|
|
190
|
+
this.rowTracks = this.buildTracks(t.rows), this.colTracks = this.buildTracks(t.columns), this.tracksBuilt = !0, this.tracksResolved = !1;
|
|
191
|
+
}
|
|
192
|
+
buildTracks(t) {
|
|
193
|
+
return t.split(/\s+/).map((e) => {
|
|
194
|
+
if (e.endsWith("px")) return new I("px", parseFloat(e));
|
|
195
|
+
if (e.endsWith("fr")) return new I("fr", parseFloat(e));
|
|
196
|
+
if (e === "auto") return new I("auto", 1);
|
|
197
|
+
throw new Error(`Invalid track: ${e}`);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
resolveTracks(t, e, o) {
|
|
201
|
+
const s = o * (t.length - 1), i = e - s;
|
|
202
|
+
let r = 0, n = 0;
|
|
203
|
+
for (const l of t)
|
|
204
|
+
l.unit === "px" ? r += l.baseValue : l.unit === "fr" ? n += l.baseValue : l.unit === "auto" && (n += 1);
|
|
205
|
+
const a = Math.max(0, i - r), d = n > 0 ? a / n : 0;
|
|
206
|
+
for (const l of t)
|
|
207
|
+
l.size === 0 && (l.unit === "px" ? l.size = l.baseValue : l.unit === "fr" ? l.size = l.baseValue * d : l.unit === "auto" && (l.size = d));
|
|
208
|
+
}
|
|
209
|
+
accumulateTracks(t, e) {
|
|
210
|
+
const o = [];
|
|
211
|
+
let s = 0;
|
|
212
|
+
for (const i of t)
|
|
213
|
+
o.push(s), s += i.effectiveSize() + e;
|
|
214
|
+
return o;
|
|
215
|
+
}
|
|
216
|
+
scaleTracks(t, e, o) {
|
|
217
|
+
const s = o * (t.length - 1), i = e - s, r = t.filter((f) => f.unit === "px").reduce((f, p) => f + p.effectiveSize(), 0), n = t.filter((f) => f.unit !== "px"), a = n.reduce((f, p) => f + p.effectiveSize(), 0);
|
|
218
|
+
if (a === 0) return;
|
|
219
|
+
const l = Math.max(0, i - r) / a;
|
|
220
|
+
for (const f of n)
|
|
221
|
+
f.size *= l, f.delta *= l;
|
|
222
|
+
}
|
|
223
|
+
resizeRow(t, e) {
|
|
224
|
+
const o = this.rowTracks[t], s = this.rowTracks[t + 1];
|
|
225
|
+
if (!o || !s) return 0;
|
|
226
|
+
const i = o.clampDelta(e), r = -s.clampDelta(-e), n = Math.abs(i) < Math.abs(r) ? i : r;
|
|
227
|
+
return n === 0 ? 0 : (o.applyDelta(n), s.applyDelta(-n), n);
|
|
228
|
+
}
|
|
229
|
+
resizeColumn(t, e) {
|
|
230
|
+
const o = this.colTracks[t], s = this.colTracks[t + 1];
|
|
231
|
+
if (!o || !s) return 0;
|
|
232
|
+
const i = o.clampDelta(e), r = -s.clampDelta(-e), n = Math.abs(i) < Math.abs(r) ? i : r;
|
|
233
|
+
return n === 0 ? 0 : (o.applyDelta(n), s.applyDelta(-n), n);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
class x {
|
|
237
|
+
elem;
|
|
238
|
+
rect;
|
|
239
|
+
constructor(t) {
|
|
240
|
+
this.elem = document.createElement("div"), this.elem.style.position = "absolute", t instanceof w ? this.rect = t : this.rect = new w(t), this.applyRect();
|
|
241
|
+
}
|
|
242
|
+
update(t) {
|
|
243
|
+
t instanceof w ? this.rect = t : this.rect = new w(t), this.applyRect();
|
|
244
|
+
}
|
|
245
|
+
applyRect() {
|
|
246
|
+
this.style({
|
|
247
|
+
left: `${this.rect.x}px`,
|
|
248
|
+
top: `${this.rect.y}px`,
|
|
249
|
+
width: `${this.rect.width}px`,
|
|
250
|
+
height: `${this.rect.height}px`
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
style(t) {
|
|
254
|
+
Object.assign(this.elem.style, t);
|
|
255
|
+
}
|
|
256
|
+
mount(t) {
|
|
257
|
+
this.elem.parentElement || t.appendChild(this.elem);
|
|
258
|
+
}
|
|
259
|
+
remove() {
|
|
260
|
+
this.elem.remove();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
class N extends x {
|
|
264
|
+
constructor(t, e) {
|
|
265
|
+
super(e), this.name = t;
|
|
266
|
+
}
|
|
267
|
+
isArea(t) {
|
|
268
|
+
return this.name === t;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
class R {
|
|
272
|
+
constructor(t) {
|
|
273
|
+
this.context = t;
|
|
274
|
+
}
|
|
275
|
+
areas = {};
|
|
276
|
+
apply() {
|
|
277
|
+
const t = this.context.options.container, e = this.context.layoutEngine.computedRect;
|
|
278
|
+
for (const o in this.areas)
|
|
279
|
+
e[o] || (this.areas[o].remove(), delete this.areas[o]);
|
|
280
|
+
for (const o in e) {
|
|
281
|
+
const s = e[o];
|
|
282
|
+
let i = this.areas[o];
|
|
283
|
+
i ? i.update(s) : (i = new N(o, s), this.areas[o] = i, i.elem.style.overflow = "hidden", i.elem.dataset.rectflowArea = o, i.elem.style.background = "white", i.mount(t));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
getView(t) {
|
|
287
|
+
return this.areas[t];
|
|
288
|
+
}
|
|
289
|
+
getAreaElement(t) {
|
|
290
|
+
return this.areas[t].elem;
|
|
291
|
+
}
|
|
292
|
+
clear() {
|
|
293
|
+
Object.values(this.areas).forEach((t) => t.remove()), this.areas = {};
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
class D extends x {
|
|
297
|
+
state = "idle";
|
|
298
|
+
config;
|
|
299
|
+
direction;
|
|
300
|
+
boundary;
|
|
301
|
+
constructor(t, e) {
|
|
302
|
+
super(e), this.config = t.config, this.direction = t.direction, this.boundary = t.boundary, this.init();
|
|
303
|
+
}
|
|
304
|
+
init() {
|
|
305
|
+
const t = this.elem;
|
|
306
|
+
t.style.cursor = this.direction === "horizontal" ? "row-resize" : "col-resize", t.style.transition = "background 0.2s", t.style.borderRadius = "3px", this.applyIdleStyle();
|
|
307
|
+
}
|
|
308
|
+
setState(t) {
|
|
309
|
+
if (this.state !== t)
|
|
310
|
+
switch (this.state = t, t) {
|
|
311
|
+
case "idle":
|
|
312
|
+
this.applyIdleStyle();
|
|
313
|
+
break;
|
|
314
|
+
case "hover":
|
|
315
|
+
this.applyHoverStyle();
|
|
316
|
+
break;
|
|
317
|
+
case "active":
|
|
318
|
+
this.applyActiveStyle();
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
applyIdleStyle() {
|
|
323
|
+
this.elem.style.background = this.config.style?.color;
|
|
324
|
+
}
|
|
325
|
+
applyHoverStyle() {
|
|
326
|
+
console.log("hover"), this.elem.style.background = this.config.style?.hoverColor ?? "red";
|
|
327
|
+
}
|
|
328
|
+
applyActiveStyle() {
|
|
329
|
+
this.elem.style.background = this.config.style?.activeColor;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
class k {
|
|
333
|
+
constructor(t) {
|
|
334
|
+
this.context = t, this.areaTopology = t.areaTopology, this.gutter = t.options.layout.resize?.gutter, this.layoutGap = t.options.layout.gap ?? 0, this.initHandles();
|
|
335
|
+
}
|
|
336
|
+
areaTopology;
|
|
337
|
+
handles;
|
|
338
|
+
gutter;
|
|
339
|
+
layoutGap;
|
|
340
|
+
horizontalGutters = /* @__PURE__ */ new Map();
|
|
341
|
+
verticalGutters = /* @__PURE__ */ new Map();
|
|
342
|
+
resizeObserver;
|
|
343
|
+
initHandles() {
|
|
344
|
+
const t = this.context.options.layout.resize?.handles;
|
|
345
|
+
t && (this.handles = t.map((e) => {
|
|
346
|
+
const [o, s] = e.between;
|
|
347
|
+
return {
|
|
348
|
+
between: [o, s],
|
|
349
|
+
min: e.min,
|
|
350
|
+
max: e.max
|
|
351
|
+
};
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
354
|
+
apply() {
|
|
355
|
+
this.resizeObserver || this.addContainerResize(), this.handles.length > 0 && (this.createHorizontalGutters(), this.createVerticalGutters());
|
|
356
|
+
}
|
|
357
|
+
addContainerResize() {
|
|
358
|
+
const t = this.context.options.container;
|
|
359
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
360
|
+
this.context.onLayoutChange?.();
|
|
361
|
+
}), this.resizeObserver.observe(t);
|
|
362
|
+
}
|
|
363
|
+
getBoundary(t, e) {
|
|
364
|
+
const [o, s] = t.between;
|
|
365
|
+
for (const i of e)
|
|
366
|
+
if (i.first.includes(o) && i.second.includes(s) || i.first.includes(s) && i.second.includes(o))
|
|
367
|
+
return i;
|
|
368
|
+
}
|
|
369
|
+
getBoundaryKey(t) {
|
|
370
|
+
return t.first.join("|") + "::" + t.second.join("|");
|
|
371
|
+
}
|
|
372
|
+
createHorizontalGutters() {
|
|
373
|
+
const t = this.context.layoutEngine.computedRect, e = /* @__PURE__ */ new Set();
|
|
374
|
+
for (const o of this.handles) {
|
|
375
|
+
const s = this.getBoundary(o, this.areaTopology.horizontalBoundary);
|
|
376
|
+
if (!s) continue;
|
|
377
|
+
const i = this.getBoundaryKey(s);
|
|
378
|
+
e.add(i);
|
|
379
|
+
let r = { ...this.computeGutterSpan(s, "x") };
|
|
380
|
+
const n = s.first[0], a = t[n];
|
|
381
|
+
r.y = a.y + a.height + this.layoutGap / 2 - this.gutter.size / 2, r.height = this.gutter.size, this.createOrUpdateGutterView(this.horizontalGutters, i, r, "horizontal", s);
|
|
382
|
+
}
|
|
383
|
+
this.cleanupInactiveGutters(this.horizontalGutters, e);
|
|
384
|
+
}
|
|
385
|
+
createVerticalGutters() {
|
|
386
|
+
const t = this.context.layoutEngine.computedRect, e = /* @__PURE__ */ new Set();
|
|
387
|
+
for (const o of this.handles) {
|
|
388
|
+
const s = this.getBoundary(o, this.areaTopology.verticalBoundary);
|
|
389
|
+
if (!s) continue;
|
|
390
|
+
const i = this.getBoundaryKey(s);
|
|
391
|
+
e.add(i);
|
|
392
|
+
let r = { ...this.computeGutterSpan(s, "y") };
|
|
393
|
+
const n = s.first[0], a = t[n];
|
|
394
|
+
r.x = a.x + a.width + this.layoutGap / 2 - this.gutter.size / 2, r.width = this.gutter.size, this.createOrUpdateGutterView(this.verticalGutters, i, r, "vertical", s);
|
|
395
|
+
}
|
|
396
|
+
this.cleanupInactiveGutters(this.verticalGutters, e);
|
|
397
|
+
}
|
|
398
|
+
createOrUpdateGutterView(t, e, o, s, i) {
|
|
399
|
+
let r = t.get(e);
|
|
400
|
+
r ? r.update(new w(o)) : (r = new D(
|
|
401
|
+
{
|
|
402
|
+
config: this.context.options.layout.resize?.gutter,
|
|
403
|
+
direction: s,
|
|
404
|
+
boundary: i
|
|
405
|
+
},
|
|
406
|
+
new w(o)
|
|
407
|
+
), r.mount(this.context.options.container), t.set(e, r), this.attachGutterDrag(r, i, s));
|
|
408
|
+
}
|
|
409
|
+
cleanupInactiveGutters(t, e) {
|
|
410
|
+
for (const [o, s] of t)
|
|
411
|
+
e.has(o) || (s.remove(), t.delete(o));
|
|
412
|
+
}
|
|
413
|
+
getRowIndexFromBoundary(t) {
|
|
414
|
+
const e = t.first[0];
|
|
415
|
+
return this.context.areaTopology.boxes[e].rowEnd;
|
|
416
|
+
}
|
|
417
|
+
getColIndexFromBoundary(t) {
|
|
418
|
+
const e = t.first[0];
|
|
419
|
+
return this.context.areaTopology.boxes[e].colEnd;
|
|
420
|
+
}
|
|
421
|
+
attachGutterDrag(t, e, o) {
|
|
422
|
+
let s = 0, i = !1, r = !1, n = null, a = !1;
|
|
423
|
+
const d = this.context.options.layout.resize?.gutter?.delay, l = (g) => o === "horizontal" ? g.clientY : g.clientX, f = (g) => {
|
|
424
|
+
if (!r) return;
|
|
425
|
+
const b = l(g) - s;
|
|
426
|
+
if (b === 0) return;
|
|
427
|
+
let A = 0;
|
|
428
|
+
if (o === "horizontal") {
|
|
429
|
+
const T = this.getRowIndexFromBoundary(e);
|
|
430
|
+
A = this.context.layoutEngine.resizeRow(T, b);
|
|
431
|
+
} else {
|
|
432
|
+
const T = this.getColIndexFromBoundary(e);
|
|
433
|
+
A = this.context.layoutEngine.resizeColumn(T, b);
|
|
434
|
+
}
|
|
435
|
+
A !== 0 && (s += A, this.context.onLayoutChange?.());
|
|
436
|
+
}, p = (g) => {
|
|
437
|
+
g.preventDefault(), r = !0, s = l(g), n !== null && (clearTimeout(n), n = null), a = !1, t.setState("active"), document.addEventListener("mousemove", f), document.addEventListener("mouseup", y);
|
|
438
|
+
}, y = () => {
|
|
439
|
+
r = !1, i && a ? t.setState("hover") : t.setState("idle"), document.removeEventListener("mousemove", f), document.removeEventListener("mouseup", y);
|
|
440
|
+
}, v = () => {
|
|
441
|
+
i = !0, a = !1, n = window.setTimeout(() => {
|
|
442
|
+
!r && i && (t.setState("hover"), a = !0);
|
|
443
|
+
}, d);
|
|
444
|
+
}, m = () => {
|
|
445
|
+
i = !1, n !== null && (clearTimeout(n), n = null), a = !1, r || t.setState("idle");
|
|
446
|
+
};
|
|
447
|
+
t.elem.addEventListener("mouseenter", v), t.elem.addEventListener("mouseleave", m), t.elem.addEventListener("mousedown", p);
|
|
448
|
+
}
|
|
449
|
+
computeGutterSpan(t, e) {
|
|
450
|
+
const o = t.first;
|
|
451
|
+
let s = 1 / 0, i = -1 / 0;
|
|
452
|
+
for (const r of o) {
|
|
453
|
+
const { x: n, y: a, width: d, height: l } = this.context.layoutEngine.computedRect[r];
|
|
454
|
+
e === "x" ? (s = Math.min(s, n), i = Math.max(i, n + d)) : (s = Math.min(s, a), i = Math.max(i, a + l));
|
|
455
|
+
}
|
|
456
|
+
return e === "x" ? { x: s, width: i - s } : { y: s, height: i - s };
|
|
457
|
+
}
|
|
458
|
+
relayoutGutters() {
|
|
459
|
+
for (const t of this.horizontalGutters.values()) {
|
|
460
|
+
const e = t.boundary, o = this.computeGutterSpan(e, "x");
|
|
461
|
+
t.rect.x = o.x, t.rect.width = o.width;
|
|
462
|
+
const s = this.context.areaRenderer.getView(e.first[0]).rect;
|
|
463
|
+
t.rect.y = s.y + s.height + this.layoutGap / 2 - this.gutter.size / 2, t.applyRect();
|
|
464
|
+
}
|
|
465
|
+
for (const t of this.verticalGutters.values()) {
|
|
466
|
+
const e = t.boundary, o = this.computeGutterSpan(e, "y");
|
|
467
|
+
t.rect.y = o.y, t.rect.height = o.height;
|
|
468
|
+
const s = this.context.areaRenderer.getView(e.first[0]).rect;
|
|
469
|
+
t.rect.x = s.x + s.width + this.layoutGap / 2 - this.gutter.size / 2, t.applyRect();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const E = {
|
|
474
|
+
layout: {
|
|
475
|
+
gap: 0,
|
|
476
|
+
areas: [],
|
|
477
|
+
resize: {
|
|
478
|
+
handles: [],
|
|
479
|
+
gutter: {
|
|
480
|
+
size: 6,
|
|
481
|
+
delay: 150,
|
|
482
|
+
style: {
|
|
483
|
+
color: "transparent",
|
|
484
|
+
hoverColor: "rgba(0,0,0,0.2)",
|
|
485
|
+
activeColor: "rgba(0,0,0,0.45)"
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
strict: !0
|
|
491
|
+
};
|
|
492
|
+
class c extends Error {
|
|
493
|
+
code;
|
|
494
|
+
constructor(t, e) {
|
|
495
|
+
super(`[Rectflow] ${t}`), this.name = "RectflowError", this.code = e;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
var h = /* @__PURE__ */ ((u) => (u.CONTAINER_NOT_FOUND = "CONTAINER_NOT_FOUND", u.INVALID_CONTAINER = "INVALID_CONTAINER", u.INVALID_GRID_CONFIG = "INVALID_GRID_CONFIG", u.INVALID_GRID_AREAS = "INVALID_GRID_AREAS", u.COMPUTE_FAILED = "COMPUTE_FAILED", u.INVALID_LAYOUT = "INVALID_LAYOUT", u))(h || {});
|
|
499
|
+
function z(u, t) {
|
|
500
|
+
if (t == null)
|
|
501
|
+
return structuredClone(u);
|
|
502
|
+
if (typeof u != "object" || u === null)
|
|
503
|
+
return t;
|
|
504
|
+
const e = structuredClone(u);
|
|
505
|
+
for (const o in t) {
|
|
506
|
+
const s = t[o], i = e[o];
|
|
507
|
+
s !== null && typeof s == "object" && !Array.isArray(s) ? e[o] = z(i, s) : s !== void 0 && (e[o] = s);
|
|
508
|
+
}
|
|
509
|
+
return e;
|
|
510
|
+
}
|
|
511
|
+
class S {
|
|
512
|
+
options;
|
|
513
|
+
resolve(t) {
|
|
514
|
+
return this.options = t, this.normalizeContainer(), this.normalizeAreas(), this.normalizeGutter(), this.validateContainer(), this.validateRowsCols(), this.validateAreas(), this.validateResizeHandles(), this.validateGutter(), z(E, t);
|
|
515
|
+
}
|
|
516
|
+
normalizeContainer() {
|
|
517
|
+
const t = this.options.container;
|
|
518
|
+
if (typeof t == "string") {
|
|
519
|
+
const e = document.querySelector(t);
|
|
520
|
+
if (!e)
|
|
521
|
+
throw new c(
|
|
522
|
+
`Container selector "${t}" does not match any element in HTML`,
|
|
523
|
+
h.CONTAINER_NOT_FOUND
|
|
524
|
+
);
|
|
525
|
+
this.options.container = e;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
normalizeAreas() {
|
|
529
|
+
const { areas: t } = this.options.layout;
|
|
530
|
+
if (!t)
|
|
531
|
+
throw new c("layout.areas is required", h.INVALID_LAYOUT);
|
|
532
|
+
if (!Array.isArray(t))
|
|
533
|
+
throw new c("layout.areas must be an array of rows (string[][])", h.INVALID_LAYOUT);
|
|
534
|
+
this.options.layout.areas = t.map((e, o) => {
|
|
535
|
+
if (!Array.isArray(e))
|
|
536
|
+
throw new c(`layout.areas[${o}] must be an array`, h.INVALID_LAYOUT);
|
|
537
|
+
if (e.length === 1) {
|
|
538
|
+
const s = e[0];
|
|
539
|
+
if (typeof s != "string")
|
|
540
|
+
throw new c(`layout.areas[${o}][0] must be a string`, h.INVALID_LAYOUT);
|
|
541
|
+
const i = s.trim().split(/\s+/);
|
|
542
|
+
if (i.length === 0 || i.some((r) => !r))
|
|
543
|
+
throw new c(
|
|
544
|
+
`layout.areas[${o}] contains invalid area names`,
|
|
545
|
+
h.INVALID_LAYOUT
|
|
546
|
+
);
|
|
547
|
+
return i;
|
|
548
|
+
}
|
|
549
|
+
return e.map((s, i) => {
|
|
550
|
+
if (typeof s != "string")
|
|
551
|
+
throw new c(
|
|
552
|
+
`layout.areas[${o}][${i}] must be a string`,
|
|
553
|
+
h.INVALID_LAYOUT
|
|
554
|
+
);
|
|
555
|
+
if (!s.trim())
|
|
556
|
+
throw new c(
|
|
557
|
+
`layout.areas[${o}][${i}] cannot be empty`,
|
|
558
|
+
h.INVALID_LAYOUT
|
|
559
|
+
);
|
|
560
|
+
return s.trim();
|
|
71
561
|
});
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
normalizeGutter() {
|
|
565
|
+
if (!this.options.layout.resize) return;
|
|
566
|
+
typeof this.options.layout.resize.gutter == "number" && (this.options.layout.resize.gutter = { size: 6 });
|
|
567
|
+
}
|
|
568
|
+
validateContainer() {
|
|
569
|
+
const t = this.options.container;
|
|
570
|
+
if (!t)
|
|
571
|
+
throw new c("Container is null or undefined", h.CONTAINER_NOT_FOUND);
|
|
572
|
+
if (!(t instanceof HTMLElement))
|
|
573
|
+
throw new c("Provided container is not a valid HTMLElement", h.INVALID_CONTAINER);
|
|
574
|
+
}
|
|
575
|
+
validateRowsCols() {
|
|
576
|
+
const t = (e, o) => {
|
|
577
|
+
if (e == null)
|
|
578
|
+
throw new c(`layout.${o} is required`, h.INVALID_LAYOUT);
|
|
579
|
+
if (typeof e != "string")
|
|
580
|
+
throw new c(`layout.${o} must be a string`, h.INVALID_LAYOUT);
|
|
581
|
+
if (!e.trim())
|
|
582
|
+
throw new c(`layout.${o} cannot be empty`, h.INVALID_LAYOUT);
|
|
583
|
+
const s = /^(auto|\d+(\.\d+)?(px|fr))$/, i = e.trim().split(/\s+/);
|
|
584
|
+
for (const r of i)
|
|
585
|
+
if (!s.test(r))
|
|
586
|
+
throw new c(
|
|
587
|
+
`Invalid layout.${o} track value "${r}". Allowed values: px (e.g. 60px), fr (e.g. 1fr), or auto.`,
|
|
588
|
+
h.INVALID_LAYOUT
|
|
589
|
+
);
|
|
590
|
+
};
|
|
591
|
+
t(this.options.layout.rows, "rows"), t(this.options.layout.columns, "columns");
|
|
592
|
+
}
|
|
593
|
+
validateAreas() {
|
|
594
|
+
const t = this.options.layout.areas, e = this.options.layout.rows, o = this.options.layout.columns, s = e.trim().split(/\s+/).length, i = o.trim().split(/\s+/).length;
|
|
595
|
+
if (t.length !== s)
|
|
596
|
+
throw new c(
|
|
597
|
+
`Areas row count (${t.length}) does not match rows definition (${s})`,
|
|
598
|
+
h.INVALID_LAYOUT
|
|
599
|
+
);
|
|
600
|
+
for (let r = 0; r < t.length; r++) {
|
|
601
|
+
const n = t[r];
|
|
602
|
+
if (n.length !== i)
|
|
603
|
+
throw new c(
|
|
604
|
+
`Areas column count mismatch at row ${r}. Expected ${i}, got ${n.length}`,
|
|
605
|
+
h.INVALID_LAYOUT
|
|
606
|
+
);
|
|
72
607
|
}
|
|
73
608
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
609
|
+
validateResizeHandles() {
|
|
610
|
+
const t = this.options.layout.resize;
|
|
611
|
+
if (!t || !t.handles) return;
|
|
612
|
+
const { handles: e } = t;
|
|
613
|
+
if (!Array.isArray(e))
|
|
614
|
+
throw new c("resize.handles must be an array", h.INVALID_LAYOUT);
|
|
615
|
+
function o(i) {
|
|
616
|
+
const r = /* @__PURE__ */ new Set();
|
|
617
|
+
for (const n of i)
|
|
618
|
+
for (const a of n)
|
|
619
|
+
r.add(a);
|
|
620
|
+
return r;
|
|
621
|
+
}
|
|
622
|
+
const s = o(this.options.layout.areas);
|
|
623
|
+
e.forEach((i, r) => {
|
|
624
|
+
if (typeof i != "object" || i === null)
|
|
625
|
+
throw new c(`resize.handles[${r}] must be an object`, h.INVALID_LAYOUT);
|
|
626
|
+
if (!("between" in i))
|
|
627
|
+
throw new c(
|
|
628
|
+
`resize.handles[${r}] is missing required property "between"`,
|
|
629
|
+
h.INVALID_LAYOUT
|
|
630
|
+
);
|
|
631
|
+
const n = i.between;
|
|
632
|
+
if (!Array.isArray(n))
|
|
633
|
+
throw new c(`resize.handles[${r}].between must be an array`, h.INVALID_LAYOUT);
|
|
634
|
+
if (n.length !== 2)
|
|
635
|
+
throw new c(
|
|
636
|
+
`resize.handles[${r}].between must contain exactly 2 area names`,
|
|
637
|
+
h.INVALID_LAYOUT
|
|
638
|
+
);
|
|
639
|
+
const [a, d] = n;
|
|
640
|
+
if (typeof a != "string" || typeof d != "string")
|
|
641
|
+
throw new c(
|
|
642
|
+
`resize.handles[${r}].between values must be strings`,
|
|
643
|
+
h.INVALID_LAYOUT
|
|
644
|
+
);
|
|
645
|
+
if (!a.trim() || !d.trim())
|
|
646
|
+
throw new c(
|
|
647
|
+
`resize.handles[${r}].between cannot contain empty strings`,
|
|
648
|
+
h.INVALID_LAYOUT
|
|
649
|
+
);
|
|
650
|
+
if (a === d)
|
|
651
|
+
throw new c(
|
|
652
|
+
`resize.handles[${r}].between must reference two different areas`,
|
|
653
|
+
h.INVALID_LAYOUT
|
|
654
|
+
);
|
|
655
|
+
if (!s.has(a) || !s.has(d))
|
|
656
|
+
throw new c(
|
|
657
|
+
`resize.handles[${r}] references unknown areas: ${a}, ${d}`,
|
|
658
|
+
h.INVALID_LAYOUT
|
|
659
|
+
);
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
validateGutter() {
|
|
663
|
+
const t = this.options.layout.resize;
|
|
664
|
+
if (!t || t.gutter == null) return;
|
|
665
|
+
const { gutter: e } = t;
|
|
666
|
+
if (typeof e == "number") {
|
|
667
|
+
if (!Number.isFinite(e) || e < 0)
|
|
668
|
+
throw new c("resize.gutter must be a non-negative number", h.INVALID_LAYOUT);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
if (typeof e != "object")
|
|
672
|
+
throw new c("resize.gutter must be a number or an object", h.INVALID_LAYOUT);
|
|
673
|
+
const { size: o, delay: s, style: i } = e;
|
|
674
|
+
if (s !== void 0 && (typeof s != "number" || !Number.isFinite(s) || s < 0))
|
|
675
|
+
throw new c("resize.gutter.delay must be a non-negative number", h.INVALID_LAYOUT);
|
|
676
|
+
if (o !== void 0 && (typeof o != "number" || !Number.isFinite(o) || o < 0))
|
|
677
|
+
throw new c("resize.gutter.size must be a non-negative number", h.INVALID_LAYOUT);
|
|
678
|
+
function r(a) {
|
|
679
|
+
const d = new Option().style;
|
|
680
|
+
return d.color = a, d.color !== "";
|
|
681
|
+
}
|
|
682
|
+
const n = (a, d) => {
|
|
683
|
+
if (typeof a != "string")
|
|
684
|
+
throw new c(`${d} must be a string`, h.INVALID_LAYOUT);
|
|
685
|
+
if (!r(a))
|
|
686
|
+
throw new c(`${d} is not a valid CSS color`, h.INVALID_LAYOUT);
|
|
687
|
+
};
|
|
688
|
+
if (i !== void 0) {
|
|
689
|
+
if (typeof i != "object" || i === null)
|
|
690
|
+
throw new c("resize.gutter.style must be an object", h.INVALID_LAYOUT);
|
|
691
|
+
const { color: a, hoverColor: d, activeColor: l } = i;
|
|
692
|
+
a !== void 0 && n(a, "resize.gutter.style.color"), d !== void 0 && n(d, "resize.gutter.style.hoverColor"), l !== void 0 && n(l, "resize.gutter.style.activeColor");
|
|
77
693
|
}
|
|
78
|
-
const r = this.areas.get(e);
|
|
79
|
-
if (r) return r.elem;
|
|
80
|
-
const i = document.createElement("div");
|
|
81
|
-
return i.dataset.rectflowArea = e, i.style.background = t(), i.style.position = "absolute", this.config.container.appendChild(i), this.areas.set(e, { elem: i, auto: !0 }), i;
|
|
82
694
|
}
|
|
83
|
-
|
|
84
|
-
|
|
695
|
+
}
|
|
696
|
+
class V {
|
|
697
|
+
_options;
|
|
698
|
+
resolver;
|
|
699
|
+
constructor(t, e = new S()) {
|
|
700
|
+
this.resolver = e, this._options = Object.freeze(this.resolver.resolve(t));
|
|
701
|
+
}
|
|
702
|
+
get options() {
|
|
703
|
+
return this._options;
|
|
85
704
|
}
|
|
86
705
|
}
|
|
87
|
-
class
|
|
706
|
+
class _ {
|
|
707
|
+
optionsStore;
|
|
708
|
+
areaTopology;
|
|
709
|
+
layoutEngine;
|
|
88
710
|
areaRenderer;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
711
|
+
resizeManager;
|
|
712
|
+
onLayoutChange;
|
|
713
|
+
constructor(t) {
|
|
714
|
+
this.optionsStore = new V(t), this.areaTopology = new L(this), this.layoutEngine = new O(this), this.areaRenderer = new R(this), this.resizeManager = new k(this);
|
|
715
|
+
}
|
|
716
|
+
get options() {
|
|
717
|
+
return this.optionsStore.options;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const C = (u, t = !0) => {
|
|
721
|
+
if (t) throw u;
|
|
722
|
+
u instanceof c ? console.error(u.message, u.code) : console.error("[Rectflow] Unknown error", u);
|
|
723
|
+
};
|
|
724
|
+
class U {
|
|
725
|
+
context;
|
|
726
|
+
constructor(t) {
|
|
727
|
+
this.context = new _(t);
|
|
728
|
+
const { container: e } = this.context.options;
|
|
729
|
+
e.style.position = "absolute", this.context.onLayoutChange = () => {
|
|
730
|
+
this.context.layoutEngine.calculate(), this.context.areaRenderer.apply(), this.context.resizeManager.relayoutGutters();
|
|
731
|
+
};
|
|
732
|
+
try {
|
|
733
|
+
this.initLayout(), this.applyLayout();
|
|
734
|
+
} catch (o) {
|
|
735
|
+
C(o, this.context.options.strict);
|
|
736
|
+
}
|
|
92
737
|
}
|
|
93
|
-
|
|
94
|
-
this.
|
|
738
|
+
initLayout() {
|
|
739
|
+
this.context.areaTopology.calculate(), this.context.layoutEngine.initTracks();
|
|
95
740
|
}
|
|
96
|
-
|
|
97
|
-
this.areaRenderer.
|
|
741
|
+
applyLayout() {
|
|
742
|
+
this.context.areaTopology.calculate(), this.context.layoutEngine.calculate(), this.context.areaRenderer.apply(), this.context.resizeManager.apply();
|
|
98
743
|
}
|
|
99
|
-
|
|
100
|
-
this.
|
|
744
|
+
getArea(t) {
|
|
745
|
+
return this.context.areaRenderer.getAreaElement(t);
|
|
101
746
|
}
|
|
102
747
|
}
|
|
103
748
|
export {
|
|
104
|
-
|
|
749
|
+
U as Rectflow
|
|
105
750
|
};
|
package/dist/rectflow.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(l,u){typeof exports=="object"&&typeof module<"u"?u(exports):typeof define=="function"&&define.amd?define(["exports"],u):(l=typeof globalThis<"u"?globalThis:l||self,u(l.Rectflow={}))})(this,(function(l){"use strict";class u{constructor(e){this.config=e}compute(e){const t=this.parseTracks(this.config.rows,e.height),r=this.parseTracks(this.config.columns,e.width),i=this.config.gap??0,n=this.accumulate(t,i),a=this.accumulate(r,i),f=this.normalizeAreas(this.config.areas),h={};for(let s=0;s<f.length;s++)for(let o=0;o<f[s].length;o++){const d=f[s][o];if(d!==".")if(!h[d])h[d]={x:a[o],y:n[s],width:r[o],height:t[s]};else{const c=h[d];a[o]+r[o]>c.x+c.width&&(c.width=a[o]+r[o]-c.x),n[s]+t[s]>c.y+c.height&&(c.height=n[s]+t[s]-c.y)}}return h}normalizeAreas(e){return e.map(t=>t.length===1?t[0].trim().split(/\s+/):t)}parseTracks(e,t){const r=e.split(/\s+/),i=this.config.gap??0;let n=0,a=0;for(const s of r)s.endsWith("px")?n+=parseFloat(s):s.endsWith("fr")?a+=parseFloat(s):s==="auto"&&(a+=1);const f=t-n-(r.length-1)*i,h=a>0?f/a:0;return r.map(s=>s.endsWith("px")?parseFloat(s):s.endsWith("fr")?parseFloat(s)*h:s==="auto"?h:0)}accumulate(e,t){const r=[];let i=0;for(const n of e)r.push(i),i+=n+t;return r}}class p{constructor(e){this.config=e,this.engine=new u(e.layout)}areas=new Map;engine;registerArea(e,t){const r=this.areas.get(e);r?.auto&&r.elem.remove(),t.style.position="absolute",this.areas.set(e,{elem:t,auto:!1})}layout(){const e={x:0,y:0,width:this.config.container.clientWidth,height:this.config.container.clientHeight},t=this.engine.compute(e);for(const r in t){const i=this.ensureArea(r),n=t[r];Object.assign(i.style,{left:`${n.x}px`,top:`${n.y}px`,width:`${n.width}px`,height:`${n.height}px`})}}ensureArea(e){function t(){return`hsl(${Math.floor(Math.random()*360)}, 70%, 70%)`}const r=this.areas.get(e);if(r)return r.elem;const i=document.createElement("div");return i.dataset.rectflowArea=e,i.style.background=t(),i.style.position="absolute",this.config.container.appendChild(i),this.areas.set(e,{elem:i,auto:!0}),i}clearArea(){this.areas.clear()}}class m{areaRenderer;observer;constructor(e){e.container.style.position="relative",this.areaRenderer=new p(e),this.observer=new ResizeObserver(()=>this.layout()),this.observer.observe(e.container)}registerArea(e,t){this.areaRenderer.registerArea(e,t)}layout(){this.areaRenderer.layout()}destroy(){this.observer.disconnect(),this.areaRenderer.clearArea()}}l.Rectflow=m,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(v,b){typeof exports=="object"&&typeof module<"u"?b(exports):typeof define=="function"&&define.amd?define(["exports"],b):(v=typeof globalThis<"u"?globalThis:v||self,b(v.Rectflow={}))})(this,(function(v){"use strict";class b{layoutAreas;cells=[];boxes={};horizontalBoundary=[];verticalBoundary=[];constructor(t){this.layoutAreas=t.options.layout.areas}calculate(){this.cells=[],this.boxes={},this.horizontalBoundary=[],this.verticalBoundary=[],this.buildCells(),this.calculateBoxes(),this.computeHorizontalBoundaries(),this.computeVerticalBoundaries()}buildCells(){for(let t=0;t<this.layoutAreas.length;t++)for(let e=0;e<this.layoutAreas[t].length;e++)this.cells.push({area:this.layoutAreas[t][e],row:t,col:e})}calculateBoxes(){for(const t of this.cells)if(!this.boxes[t.area])this.boxes[t.area]={area:t.area,rowStart:t.row,rowEnd:t.row,colStart:t.col,colEnd:t.col};else{const e=this.boxes[t.area];e.rowStart=Math.min(e.rowStart,t.row),e.rowEnd=Math.max(e.rowEnd,t.row),e.colStart=Math.min(e.colStart,t.col),e.colEnd=Math.max(e.colEnd,t.col)}}computeBoundaries(t,e,o,s,i){const r=[];let n=1/0,a=-1/0;for(const l of t)n=Math.min(n,l[s]),a=Math.max(a,l[i]);const d=new Set;for(const l of t)d.add(l[e]);for(const l of d){let f=null;for(let y=n;y<=a;y++){const p=t.find(w=>w[e]===l&&y>=w[s]&&y<=w[i])?.area,A=t.find(w=>w[o]===l+1&&y>=w[s]&&y<=w[i])?.area;if(!p||!A||p===A){f&&(r.push(f),f=null);continue}f||(f={first:[],second:[]}),f.first.includes(p)||f.first.push(p),f.second.includes(A)||f.second.push(A)}f&&r.push(f)}return r}computeHorizontalBoundaries(){this.horizontalBoundary=this.computeBoundaries(Object.values(this.boxes),"rowEnd","rowStart","colStart","colEnd")}computeVerticalBoundaries(){this.verticalBoundary=this.computeBoundaries(Object.values(this.boxes),"colEnd","colStart","rowStart","rowEnd")}}class m{x;y;width;height;constructor(t){this.x=t.x,this.y=t.y,this.width=t.width,this.height=t.height}moveX(t){this.x+=t}resizeWidth(t){this.width+=t}growFromLeft(t){this.x-=t,this.width+=t}shrinkFromLeft(t){this.x+=t,this.width-=t}growFromRight(t){this.width+=t}shrinkFromRight(t){this.width-=t}moveY(t){this.y+=t}resizeHeight(t){this.height+=t}growFromTop(t){this.y-=t,this.height+=t}shrinkFromTop(t){this.y+=t,this.height-=t}growFromBottom(t){this.height+=t}shrinkFromBottom(t){this.height-=t}clone(){return new m({x:this.x,y:this.y,width:this.width,height:this.height})}}class I{unit;baseValue;size=0;delta=0;min=0;max=1/0;constructor(t,e){this.unit=t,this.baseValue=e}effectiveSize(){return Math.max(this.min,Math.min(this.size+this.delta,this.max))}clampDelta(t){const e=this.effectiveSize();return t>0?Math.min(t,this.max-e):Math.max(t,this.min-e)}applyDelta(t){this.delta+=t}}class R{constructor(t){this.context=t}computedRect={};rowTracks=[];colTracks=[];tracksBuilt=!1;tracksResolved=!1;calculate(){this.initTracks(),this.computedRect={};const t=this.context.options.container,e=this.context.options.layout,o=this.context.areaTopology.boxes,s=e.gap??0;this.tracksResolved?(this.scaleTracks(this.rowTracks,t.clientHeight,s),this.scaleTracks(this.colTracks,t.clientWidth,s)):(this.resolveTracks(this.rowTracks,t.clientHeight,s),this.resolveTracks(this.colTracks,t.clientWidth,s),this.tracksResolved=!0);const i=this.accumulateTracks(this.rowTracks,s),r=this.accumulateTracks(this.colTracks,s);for(const n in o){const a=o[n],d=r[a.colStart],l=i[a.rowStart];let f=0;for(let p=a.colStart;p<=a.colEnd;p++)f+=this.colTracks[p].effectiveSize(),p<a.colEnd&&(f+=s);let y=0;for(let p=a.rowStart;p<=a.rowEnd;p++)y+=this.rowTracks[p].effectiveSize(),p<a.rowEnd&&(y+=s);this.computedRect[n]=new m({x:d,y:l,width:f,height:y})}}initTracks(){if(this.tracksBuilt)return;const t=this.context.options.layout;this.rowTracks=this.buildTracks(t.rows),this.colTracks=this.buildTracks(t.columns),this.tracksBuilt=!0,this.tracksResolved=!1}buildTracks(t){return t.split(/\s+/).map(e=>{if(e.endsWith("px"))return new I("px",parseFloat(e));if(e.endsWith("fr"))return new I("fr",parseFloat(e));if(e==="auto")return new I("auto",1);throw new Error(`Invalid track: ${e}`)})}resolveTracks(t,e,o){const s=o*(t.length-1),i=e-s;let r=0,n=0;for(const l of t)l.unit==="px"?r+=l.baseValue:l.unit==="fr"?n+=l.baseValue:l.unit==="auto"&&(n+=1);const a=Math.max(0,i-r),d=n>0?a/n:0;for(const l of t)l.size===0&&(l.unit==="px"?l.size=l.baseValue:l.unit==="fr"?l.size=l.baseValue*d:l.unit==="auto"&&(l.size=d))}accumulateTracks(t,e){const o=[];let s=0;for(const i of t)o.push(s),s+=i.effectiveSize()+e;return o}scaleTracks(t,e,o){const s=o*(t.length-1),i=e-s,r=t.filter(f=>f.unit==="px").reduce((f,y)=>f+y.effectiveSize(),0),n=t.filter(f=>f.unit!=="px"),a=n.reduce((f,y)=>f+y.effectiveSize(),0);if(a===0)return;const l=Math.max(0,i-r)/a;for(const f of n)f.size*=l,f.delta*=l}resizeRow(t,e){const o=this.rowTracks[t],s=this.rowTracks[t+1];if(!o||!s)return 0;const i=o.clampDelta(e),r=-s.clampDelta(-e),n=Math.abs(i)<Math.abs(r)?i:r;return n===0?0:(o.applyDelta(n),s.applyDelta(-n),n)}resizeColumn(t,e){const o=this.colTracks[t],s=this.colTracks[t+1];if(!o||!s)return 0;const i=o.clampDelta(e),r=-s.clampDelta(-e),n=Math.abs(i)<Math.abs(r)?i:r;return n===0?0:(o.applyDelta(n),s.applyDelta(-n),n)}}class L{elem;rect;constructor(t){this.elem=document.createElement("div"),this.elem.style.position="absolute",t instanceof m?this.rect=t:this.rect=new m(t),this.applyRect()}update(t){t instanceof m?this.rect=t:this.rect=new m(t),this.applyRect()}applyRect(){this.style({left:`${this.rect.x}px`,top:`${this.rect.y}px`,width:`${this.rect.width}px`,height:`${this.rect.height}px`})}style(t){Object.assign(this.elem.style,t)}mount(t){this.elem.parentElement||t.appendChild(this.elem)}remove(){this.elem.remove()}}class N extends L{constructor(t,e){super(e),this.name=t}isArea(t){return this.name===t}}class D{constructor(t){this.context=t}areas={};apply(){const t=this.context.options.container,e=this.context.layoutEngine.computedRect;for(const o in this.areas)e[o]||(this.areas[o].remove(),delete this.areas[o]);for(const o in e){const s=e[o];let i=this.areas[o];i?i.update(s):(i=new N(o,s),this.areas[o]=i,i.elem.style.overflow="hidden",i.elem.dataset.rectflowArea=o,i.elem.style.background="white",i.mount(t))}}getView(t){return this.areas[t]}getAreaElement(t){return this.areas[t].elem}clear(){Object.values(this.areas).forEach(t=>t.remove()),this.areas={}}}class k extends L{state="idle";config;direction;boundary;constructor(t,e){super(e),this.config=t.config,this.direction=t.direction,this.boundary=t.boundary,this.init()}init(){const t=this.elem;t.style.cursor=this.direction==="horizontal"?"row-resize":"col-resize",t.style.transition="background 0.2s",t.style.borderRadius="3px",this.applyIdleStyle()}setState(t){if(this.state!==t)switch(this.state=t,t){case"idle":this.applyIdleStyle();break;case"hover":this.applyHoverStyle();break;case"active":this.applyActiveStyle();break}}applyIdleStyle(){this.elem.style.background=this.config.style?.color}applyHoverStyle(){console.log("hover"),this.elem.style.background=this.config.style?.hoverColor??"red"}applyActiveStyle(){this.elem.style.background=this.config.style?.activeColor}}class E{constructor(t){this.context=t,this.areaTopology=t.areaTopology,this.gutter=t.options.layout.resize?.gutter,this.layoutGap=t.options.layout.gap??0,this.initHandles()}areaTopology;handles;gutter;layoutGap;horizontalGutters=new Map;verticalGutters=new Map;resizeObserver;initHandles(){const t=this.context.options.layout.resize?.handles;t&&(this.handles=t.map(e=>{const[o,s]=e.between;return{between:[o,s],min:e.min,max:e.max}}))}apply(){this.resizeObserver||this.addContainerResize(),this.handles.length>0&&(this.createHorizontalGutters(),this.createVerticalGutters())}addContainerResize(){const t=this.context.options.container;this.resizeObserver=new ResizeObserver(()=>{this.context.onLayoutChange?.()}),this.resizeObserver.observe(t)}getBoundary(t,e){const[o,s]=t.between;for(const i of e)if(i.first.includes(o)&&i.second.includes(s)||i.first.includes(s)&&i.second.includes(o))return i}getBoundaryKey(t){return t.first.join("|")+"::"+t.second.join("|")}createHorizontalGutters(){const t=this.context.layoutEngine.computedRect,e=new Set;for(const o of this.handles){const s=this.getBoundary(o,this.areaTopology.horizontalBoundary);if(!s)continue;const i=this.getBoundaryKey(s);e.add(i);let r={...this.computeGutterSpan(s,"x")};const n=s.first[0],a=t[n];r.y=a.y+a.height+this.layoutGap/2-this.gutter.size/2,r.height=this.gutter.size,this.createOrUpdateGutterView(this.horizontalGutters,i,r,"horizontal",s)}this.cleanupInactiveGutters(this.horizontalGutters,e)}createVerticalGutters(){const t=this.context.layoutEngine.computedRect,e=new Set;for(const o of this.handles){const s=this.getBoundary(o,this.areaTopology.verticalBoundary);if(!s)continue;const i=this.getBoundaryKey(s);e.add(i);let r={...this.computeGutterSpan(s,"y")};const n=s.first[0],a=t[n];r.x=a.x+a.width+this.layoutGap/2-this.gutter.size/2,r.width=this.gutter.size,this.createOrUpdateGutterView(this.verticalGutters,i,r,"vertical",s)}this.cleanupInactiveGutters(this.verticalGutters,e)}createOrUpdateGutterView(t,e,o,s,i){let r=t.get(e);r?r.update(new m(o)):(r=new k({config:this.context.options.layout.resize?.gutter,direction:s,boundary:i},new m(o)),r.mount(this.context.options.container),t.set(e,r),this.attachGutterDrag(r,i,s))}cleanupInactiveGutters(t,e){for(const[o,s]of t)e.has(o)||(s.remove(),t.delete(o))}getRowIndexFromBoundary(t){const e=t.first[0];return this.context.areaTopology.boxes[e].rowEnd}getColIndexFromBoundary(t){const e=t.first[0];return this.context.areaTopology.boxes[e].colEnd}attachGutterDrag(t,e,o){let s=0,i=!1,r=!1,n=null,a=!1;const d=this.context.options.layout.resize?.gutter?.delay,l=g=>o==="horizontal"?g.clientY:g.clientX,f=g=>{if(!r)return;const x=l(g)-s;if(x===0)return;let T=0;if(o==="horizontal"){const z=this.getRowIndexFromBoundary(e);T=this.context.layoutEngine.resizeRow(z,x)}else{const z=this.getColIndexFromBoundary(e);T=this.context.layoutEngine.resizeColumn(z,x)}T!==0&&(s+=T,this.context.onLayoutChange?.())},y=g=>{g.preventDefault(),r=!0,s=l(g),n!==null&&(clearTimeout(n),n=null),a=!1,t.setState("active"),document.addEventListener("mousemove",f),document.addEventListener("mouseup",p)},p=()=>{r=!1,i&&a?t.setState("hover"):t.setState("idle"),document.removeEventListener("mousemove",f),document.removeEventListener("mouseup",p)},A=()=>{i=!0,a=!1,n=window.setTimeout(()=>{!r&&i&&(t.setState("hover"),a=!0)},d)},w=()=>{i=!1,n!==null&&(clearTimeout(n),n=null),a=!1,r||t.setState("idle")};t.elem.addEventListener("mouseenter",A),t.elem.addEventListener("mouseleave",w),t.elem.addEventListener("mousedown",y)}computeGutterSpan(t,e){const o=t.first;let s=1/0,i=-1/0;for(const r of o){const{x:n,y:a,width:d,height:l}=this.context.layoutEngine.computedRect[r];e==="x"?(s=Math.min(s,n),i=Math.max(i,n+d)):(s=Math.min(s,a),i=Math.max(i,a+l))}return e==="x"?{x:s,width:i-s}:{y:s,height:i-s}}relayoutGutters(){for(const t of this.horizontalGutters.values()){const e=t.boundary,o=this.computeGutterSpan(e,"x");t.rect.x=o.x,t.rect.width=o.width;const s=this.context.areaRenderer.getView(e.first[0]).rect;t.rect.y=s.y+s.height+this.layoutGap/2-this.gutter.size/2,t.applyRect()}for(const t of this.verticalGutters.values()){const e=t.boundary,o=this.computeGutterSpan(e,"y");t.rect.y=o.y,t.rect.height=o.height;const s=this.context.areaRenderer.getView(e.first[0]).rect;t.rect.x=s.x+s.width+this.layoutGap/2-this.gutter.size/2,t.applyRect()}}}const S={layout:{gap:0,areas:[],resize:{handles:[],gutter:{size:6,delay:150,style:{color:"transparent",hoverColor:"rgba(0,0,0,0.2)",activeColor:"rgba(0,0,0,0.45)"}}}},strict:!0};class c extends Error{code;constructor(t,e){super(`[Rectflow] ${t}`),this.name="RectflowError",this.code=e}}var h=(u=>(u.CONTAINER_NOT_FOUND="CONTAINER_NOT_FOUND",u.INVALID_CONTAINER="INVALID_CONTAINER",u.INVALID_GRID_CONFIG="INVALID_GRID_CONFIG",u.INVALID_GRID_AREAS="INVALID_GRID_AREAS",u.COMPUTE_FAILED="COMPUTE_FAILED",u.INVALID_LAYOUT="INVALID_LAYOUT",u))(h||{});function O(u,t){if(t==null)return structuredClone(u);if(typeof u!="object"||u===null)return t;const e=structuredClone(u);for(const o in t){const s=t[o],i=e[o];s!==null&&typeof s=="object"&&!Array.isArray(s)?e[o]=O(i,s):s!==void 0&&(e[o]=s)}return e}class V{options;resolve(t){return this.options=t,this.normalizeContainer(),this.normalizeAreas(),this.normalizeGutter(),this.validateContainer(),this.validateRowsCols(),this.validateAreas(),this.validateResizeHandles(),this.validateGutter(),O(S,t)}normalizeContainer(){const t=this.options.container;if(typeof t=="string"){const e=document.querySelector(t);if(!e)throw new c(`Container selector "${t}" does not match any element in HTML`,h.CONTAINER_NOT_FOUND);this.options.container=e}}normalizeAreas(){const{areas:t}=this.options.layout;if(!t)throw new c("layout.areas is required",h.INVALID_LAYOUT);if(!Array.isArray(t))throw new c("layout.areas must be an array of rows (string[][])",h.INVALID_LAYOUT);this.options.layout.areas=t.map((e,o)=>{if(!Array.isArray(e))throw new c(`layout.areas[${o}] must be an array`,h.INVALID_LAYOUT);if(e.length===1){const s=e[0];if(typeof s!="string")throw new c(`layout.areas[${o}][0] must be a string`,h.INVALID_LAYOUT);const i=s.trim().split(/\s+/);if(i.length===0||i.some(r=>!r))throw new c(`layout.areas[${o}] contains invalid area names`,h.INVALID_LAYOUT);return i}return e.map((s,i)=>{if(typeof s!="string")throw new c(`layout.areas[${o}][${i}] must be a string`,h.INVALID_LAYOUT);if(!s.trim())throw new c(`layout.areas[${o}][${i}] cannot be empty`,h.INVALID_LAYOUT);return s.trim()})})}normalizeGutter(){if(!this.options.layout.resize)return;typeof this.options.layout.resize.gutter=="number"&&(this.options.layout.resize.gutter={size:6})}validateContainer(){const t=this.options.container;if(!t)throw new c("Container is null or undefined",h.CONTAINER_NOT_FOUND);if(!(t instanceof HTMLElement))throw new c("Provided container is not a valid HTMLElement",h.INVALID_CONTAINER)}validateRowsCols(){const t=(e,o)=>{if(e==null)throw new c(`layout.${o} is required`,h.INVALID_LAYOUT);if(typeof e!="string")throw new c(`layout.${o} must be a string`,h.INVALID_LAYOUT);if(!e.trim())throw new c(`layout.${o} cannot be empty`,h.INVALID_LAYOUT);const s=/^(auto|\d+(\.\d+)?(px|fr))$/,i=e.trim().split(/\s+/);for(const r of i)if(!s.test(r))throw new c(`Invalid layout.${o} track value "${r}". Allowed values: px (e.g. 60px), fr (e.g. 1fr), or auto.`,h.INVALID_LAYOUT)};t(this.options.layout.rows,"rows"),t(this.options.layout.columns,"columns")}validateAreas(){const t=this.options.layout.areas,e=this.options.layout.rows,o=this.options.layout.columns,s=e.trim().split(/\s+/).length,i=o.trim().split(/\s+/).length;if(t.length!==s)throw new c(`Areas row count (${t.length}) does not match rows definition (${s})`,h.INVALID_LAYOUT);for(let r=0;r<t.length;r++){const n=t[r];if(n.length!==i)throw new c(`Areas column count mismatch at row ${r}. Expected ${i}, got ${n.length}`,h.INVALID_LAYOUT)}}validateResizeHandles(){const t=this.options.layout.resize;if(!t||!t.handles)return;const{handles:e}=t;if(!Array.isArray(e))throw new c("resize.handles must be an array",h.INVALID_LAYOUT);function o(i){const r=new Set;for(const n of i)for(const a of n)r.add(a);return r}const s=o(this.options.layout.areas);e.forEach((i,r)=>{if(typeof i!="object"||i===null)throw new c(`resize.handles[${r}] must be an object`,h.INVALID_LAYOUT);if(!("between"in i))throw new c(`resize.handles[${r}] is missing required property "between"`,h.INVALID_LAYOUT);const n=i.between;if(!Array.isArray(n))throw new c(`resize.handles[${r}].between must be an array`,h.INVALID_LAYOUT);if(n.length!==2)throw new c(`resize.handles[${r}].between must contain exactly 2 area names`,h.INVALID_LAYOUT);const[a,d]=n;if(typeof a!="string"||typeof d!="string")throw new c(`resize.handles[${r}].between values must be strings`,h.INVALID_LAYOUT);if(!a.trim()||!d.trim())throw new c(`resize.handles[${r}].between cannot contain empty strings`,h.INVALID_LAYOUT);if(a===d)throw new c(`resize.handles[${r}].between must reference two different areas`,h.INVALID_LAYOUT);if(!s.has(a)||!s.has(d))throw new c(`resize.handles[${r}] references unknown areas: ${a}, ${d}`,h.INVALID_LAYOUT)})}validateGutter(){const t=this.options.layout.resize;if(!t||t.gutter==null)return;const{gutter:e}=t;if(typeof e=="number"){if(!Number.isFinite(e)||e<0)throw new c("resize.gutter must be a non-negative number",h.INVALID_LAYOUT);return}if(typeof e!="object")throw new c("resize.gutter must be a number or an object",h.INVALID_LAYOUT);const{size:o,delay:s,style:i}=e;if(s!==void 0&&(typeof s!="number"||!Number.isFinite(s)||s<0))throw new c("resize.gutter.delay must be a non-negative number",h.INVALID_LAYOUT);if(o!==void 0&&(typeof o!="number"||!Number.isFinite(o)||o<0))throw new c("resize.gutter.size must be a non-negative number",h.INVALID_LAYOUT);function r(a){const d=new Option().style;return d.color=a,d.color!==""}const n=(a,d)=>{if(typeof a!="string")throw new c(`${d} must be a string`,h.INVALID_LAYOUT);if(!r(a))throw new c(`${d} is not a valid CSS color`,h.INVALID_LAYOUT)};if(i!==void 0){if(typeof i!="object"||i===null)throw new c("resize.gutter.style must be an object",h.INVALID_LAYOUT);const{color:a,hoverColor:d,activeColor:l}=i;a!==void 0&&n(a,"resize.gutter.style.color"),d!==void 0&&n(d,"resize.gutter.style.hoverColor"),l!==void 0&&n(l,"resize.gutter.style.activeColor")}}}class _{_options;resolver;constructor(t,e=new V){this.resolver=e,this._options=Object.freeze(this.resolver.resolve(t))}get options(){return this._options}}class C{optionsStore;areaTopology;layoutEngine;areaRenderer;resizeManager;onLayoutChange;constructor(t){this.optionsStore=new _(t),this.areaTopology=new b(this),this.layoutEngine=new R(this),this.areaRenderer=new D(this),this.resizeManager=new E(this)}get options(){return this.optionsStore.options}}const G=(u,t=!0)=>{if(t)throw u;u instanceof c?console.error(u.message,u.code):console.error("[Rectflow] Unknown error",u)};class U{context;constructor(t){this.context=new C(t);const{container:e}=this.context.options;e.style.position="absolute",this.context.onLayoutChange=()=>{this.context.layoutEngine.calculate(),this.context.areaRenderer.apply(),this.context.resizeManager.relayoutGutters()};try{this.initLayout(),this.applyLayout()}catch(o){G(o,this.context.options.strict)}}initLayout(){this.context.areaTopology.calculate(),this.context.layoutEngine.initTracks()}applyLayout(){this.context.areaTopology.calculate(),this.context.layoutEngine.calculate(),this.context.areaRenderer.apply(),this.context.resizeManager.apply()}getArea(t){return this.context.areaRenderer.getAreaElement(t)}}v.Rectflow=U,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rectflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Absolute-position layout engine with grid-like DSL",
|
|
6
6
|
"main": "./dist/rectflow.cjs.js",
|
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
"panel"
|
|
27
27
|
],
|
|
28
28
|
"author": "Danish Ahmed Khan",
|
|
29
|
+
"homepage": "https://github.com/DanishAhmedKhan/rectflow",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/DanishAhmedKhan/rectflow"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/DanishAhmedKhan/rectflow/issues"
|
|
36
|
+
},
|
|
29
37
|
"license": "MIT",
|
|
30
38
|
"devDependencies": {
|
|
31
39
|
"@types/node": "^25.0.3",
|
package/dist/AreaRenderer.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { RectflowConfig } from './Rectflow';
|
|
2
|
-
export declare class AreaRenderer {
|
|
3
|
-
private readonly config;
|
|
4
|
-
private areas;
|
|
5
|
-
private engine;
|
|
6
|
-
constructor(config: RectflowConfig);
|
|
7
|
-
registerArea(name: string, elem: HTMLElement): void;
|
|
8
|
-
layout(): void;
|
|
9
|
-
private ensureArea;
|
|
10
|
-
clearArea(): void;
|
|
11
|
-
}
|
package/dist/Grid.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type AreaName = string;
|
|
2
|
-
export type TrackSize = number | `${number}px` | `${number}fr` | 'auto';
|
|
3
|
-
export type GridAreas = string[][];
|
|
4
|
-
export interface GridConfig {
|
|
5
|
-
rows: string;
|
|
6
|
-
columns: string;
|
|
7
|
-
gap?: number;
|
|
8
|
-
areas: GridAreas;
|
|
9
|
-
}
|
|
10
|
-
export interface Rect {
|
|
11
|
-
x: number;
|
|
12
|
-
y: number;
|
|
13
|
-
width: number;
|
|
14
|
-
height: number;
|
|
15
|
-
}
|
|
16
|
-
export type ComputedLayout = Record<AreaName, Rect>;
|
package/dist/LayoutEngine.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { Rect, ComputedLayout, GridConfig } from './Grid';
|
|
2
|
-
export declare class LayoutEngine {
|
|
3
|
-
private config;
|
|
4
|
-
constructor(config: GridConfig);
|
|
5
|
-
compute(container: Rect): ComputedLayout;
|
|
6
|
-
private normalizeAreas;
|
|
7
|
-
private parseTracks;
|
|
8
|
-
private accumulate;
|
|
9
|
-
}
|
package/dist/Rectflow.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { GridConfig } from './Grid';
|
|
2
|
-
export type RectflowConfig = {
|
|
3
|
-
container: HTMLElement;
|
|
4
|
-
layout: GridConfig;
|
|
5
|
-
};
|
|
6
|
-
export declare class Rectflow {
|
|
7
|
-
private areaRenderer;
|
|
8
|
-
private observer;
|
|
9
|
-
constructor(config: RectflowConfig);
|
|
10
|
-
registerArea(area: string, elem: HTMLElement): void;
|
|
11
|
-
layout(): void;
|
|
12
|
-
destroy(): void;
|
|
13
|
-
}
|
package/dist/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './Rectflow';
|