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 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 class="main"></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 mainElem = document.querySelector('.main')
72
+ const containerElem = document.getElementById('container')
60
73
 
61
74
  const rectflow = new Rectflow({
62
- container: mainElem,
75
+ container: containerElem,
63
76
  layout: {
64
- rows: '50px auto 50px',
65
- columns: '50px auto 100px',
66
- gap: 5,
77
+ rows: '60px auto 60px',
78
+ columns: '60px 240px auto 60px',
79
+ gap: 6,
67
80
  areas: [
68
- ['tool tool tool'],
69
- ['drawing chart widget'],
70
- ['drawing base widget']
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 listens to container resize events and automatically recalculates layout:
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
- No manual resize handling required.
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(config)`
153
+ ### `new Rectflow(options)`
133
154
 
134
- #### config
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
- ### `registerArea(name, element)`
183
+ ### `getArea(name)`
151
184
 
152
- Registers an existing DOM element for an area.
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.registerArea('chart', chartElement)
188
+ rectflow.getArea('A')
156
189
  ```
157
190
 
191
+ ---
192
+ ## 🚫 Non-Goals
158
193
  ---
159
194
 
160
- ### `layout()`
195
+ Rectflow is not intended to:
161
196
 
162
- Calculates layout and applies styles.
197
+ - Replace CSS Grid for static layouts
198
+ - Handle animations or transitions
199
+ - Manage application state
163
200
 
164
- ```ts
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
  ---
@@ -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;
@@ -1,105 +1,750 @@
1
- class g {
2
- constructor(e) {
3
- this.config = e;
4
- }
5
- compute(e) {
6
- 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 = {};
7
- for (let s = 0; s < l.length; s++)
8
- for (let o = 0; o < l[s].length; o++) {
9
- const u = l[s][o];
10
- if (u !== ".")
11
- if (!h[u])
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
- normalizeAreas(e) {
26
- return e.map((t) => t.length === 1 ? t[0].trim().split(/\s+/) : t);
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
- parseTracks(e, t) {
29
- const r = e.split(/\s+/), i = this.config.gap ?? 0;
30
- let n = 0, a = 0;
31
- for (const s of r)
32
- s.endsWith("px") ? n += parseFloat(s) : s.endsWith("fr") ? a += parseFloat(s) : s === "auto" && (a += 1);
33
- const l = t - n - (r.length - 1) * i, h = a > 0 ? l / a : 0;
34
- return r.map((s) => s.endsWith("px") ? parseFloat(s) : s.endsWith("fr") ? parseFloat(s) * h : s === "auto" ? h : 0);
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
- accumulate(e, t) {
37
+ computeBoundaries(t, e, o, s, i) {
37
38
  const r = [];
38
- let i = 0;
39
- for (const n of e)
40
- r.push(i), i += n + t;
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 p {
45
- constructor(e) {
46
- this.config = e, this.engine = new g(e.layout);
47
- }
48
- areas = /* @__PURE__ */ new Map();
49
- engine;
50
- registerArea(e, t) {
51
- const r = this.areas.get(e);
52
- r?.auto && r.elem.remove(), t.style.position = "absolute", this.areas.set(e, {
53
- elem: t,
54
- auto: !1
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
- layout() {
58
- const e = {
59
- x: 0,
60
- y: 0,
61
- width: this.config.container.clientWidth,
62
- height: this.config.container.clientHeight
63
- }, t = this.engine.compute(e);
64
- for (const r in t) {
65
- const i = this.ensureArea(r), n = t[r];
66
- Object.assign(i.style, {
67
- left: `${n.x}px`,
68
- top: `${n.y}px`,
69
- width: `${n.width}px`,
70
- height: `${n.height}px`
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
- ensureArea(e) {
75
- function t() {
76
- return `hsl(${Math.floor(Math.random() * 360)}, 70%, 70%)`;
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
- clearArea() {
84
- this.areas.clear();
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 d {
706
+ class _ {
707
+ optionsStore;
708
+ areaTopology;
709
+ layoutEngine;
88
710
  areaRenderer;
89
- observer;
90
- constructor(e) {
91
- e.container.style.position = "relative", this.areaRenderer = new p(e), this.observer = new ResizeObserver(() => this.layout()), this.observer.observe(e.container);
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
- registerArea(e, t) {
94
- this.areaRenderer.registerArea(e, t);
738
+ initLayout() {
739
+ this.context.areaTopology.calculate(), this.context.layoutEngine.initTracks();
95
740
  }
96
- layout() {
97
- this.areaRenderer.layout();
741
+ applyLayout() {
742
+ this.context.areaTopology.calculate(), this.context.layoutEngine.calculate(), this.context.areaRenderer.apply(), this.context.resizeManager.apply();
98
743
  }
99
- destroy() {
100
- this.observer.disconnect(), this.areaRenderer.clearArea();
744
+ getArea(t) {
745
+ return this.context.areaRenderer.getAreaElement(t);
101
746
  }
102
747
  }
103
748
  export {
104
- d as Rectflow
749
+ U as Rectflow
105
750
  };
@@ -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.1.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",
@@ -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>;
@@ -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
- }
@@ -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';