rectflow 0.1.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 ADDED
@@ -0,0 +1,187 @@
1
+ # Rectflow
2
+
3
+ A lightweight **JavaScript layout engine** inspired by CSS Grid, designed for **programmatic layouts**, canvas-heavy apps, charting tools, editors, and environments where CSS Grid is not flexible enough.
4
+
5
+ Rectflow lets you define rows, columns, gaps, and named areas — then calculates and applies absolute positions automatically.
6
+
7
+ ---
8
+
9
+ ## ✨ Features
10
+
11
+ - CSS-Grid–like API (rows, columns, areas)
12
+ - Works **without CSS Grid** (pure JS layout engine)
13
+ - Supports:
14
+
15
+ - `fr`, `px`, `auto` tracks
16
+ - Gaps
17
+ - Named areas
18
+
19
+ - Automatic DOM creation for missing areas
20
+ - Resize-aware (re-layout on container resize)
21
+ - CDN-ready bundle + npm package
22
+
23
+ ---
24
+
25
+ ## 📦 Installation
26
+
27
+ ### Using npm
28
+
29
+ ```bash
30
+ npm install rectflow
31
+ ```
32
+
33
+ ```ts
34
+ import { Rectflow } from 'rectflow'
35
+ ```
36
+
37
+ ### Using CDN
38
+
39
+ ```html
40
+ <script src="https://unpkg.com/rectflow/dist/rectflow.umd.js"></script>
41
+ <script>
42
+ const rectflow = new Rectflow(...)
43
+ </script>
44
+ ```
45
+
46
+ ---
47
+
48
+ ## 🚀 Basic Usage
49
+
50
+ ### HTML
51
+
52
+ ```html
53
+ <div class="main"></div>
54
+ ```
55
+
56
+ ### JavaScript
57
+
58
+ ```ts
59
+ const mainElem = document.querySelector('.main')
60
+
61
+ const rectflow = new Rectflow({
62
+ container: mainElem,
63
+ layout: {
64
+ rows: '50px auto 50px',
65
+ columns: '50px auto 100px',
66
+ gap: 5,
67
+ areas: [
68
+ ['tool tool tool'],
69
+ ['drawing chart widget'],
70
+ ['drawing base widget']
71
+ ],
72
+ },
73
+ })
74
+
75
+ rectflow.layout()
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 🧩 Areas Syntax
81
+
82
+ Rectflow supports **two area formats**:
83
+
84
+ ### Shortcut (recommended)
85
+
86
+ ```ts
87
+ areas: [
88
+ ['tool tool'],
89
+ ['drawing chart']
90
+ ]
91
+ ```
92
+
93
+ ### Expanded form
94
+
95
+ ```ts
96
+ areas: [
97
+ ['tool', 'tool'],
98
+ ['drawing', 'chart'],
99
+ ]
100
+ ```
101
+
102
+ Both are equivalent.
103
+
104
+ ---
105
+
106
+ ## 🧠 Automatic Area Creation
107
+
108
+ If an area is defined in `areas` but **not registered**, Rectflow will:
109
+
110
+ - Automatically create a `<div>`
111
+ - Assign it the area name
112
+ - Insert it into the container
113
+ - Apply a random background color (for debugging)
114
+
115
+ This makes rapid prototyping easy.
116
+
117
+ ---
118
+
119
+ ## 🔁 Resize Handling
120
+
121
+ Rectflow listens to container resize events and automatically recalculates layout:
122
+
123
+ - Uses `ResizeObserver`
124
+ - Re-applies positions when size changes
125
+
126
+ No manual resize handling required.
127
+
128
+ ---
129
+
130
+ ## ⚙️ API Reference
131
+
132
+ ### `new Rectflow(config)`
133
+
134
+ #### config
135
+
136
+ ```ts
137
+ {
138
+ container: HTMLElement
139
+ layout: {
140
+ rows: string
141
+ columns: string
142
+ gap?: number
143
+ areas: string[][]
144
+ }
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ### `registerArea(name, element)`
151
+
152
+ Registers an existing DOM element for an area.
153
+
154
+ ```ts
155
+ rectflow.registerArea('chart', chartElement)
156
+ ```
157
+
158
+ ---
159
+
160
+ ### `layout()`
161
+
162
+ Calculates layout and applies styles.
163
+
164
+ ```ts
165
+ rectflow.layout()
166
+ ```
167
+
168
+ ---
169
+
170
+ ## 📄 License
171
+
172
+ MIT
173
+
174
+ ---
175
+
176
+ ## ❤️ Inspiration
177
+
178
+ Inspired by:
179
+
180
+ - CSS Grid
181
+ - Game UI layout systems
182
+ - Charting & trading platforms
183
+ - Dashboards
184
+
185
+ ---
186
+
187
+ If you’re building editors, dashboards, charting tools, or canvas-heavy apps — Rectflow is built for you.
@@ -0,0 +1,11 @@
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 ADDED
@@ -0,0 +1,16 @@
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>;
@@ -0,0 +1,9 @@
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
+ }
@@ -0,0 +1,13 @@
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
+ }
@@ -0,0 +1 @@
1
+ export * from './Rectflow';
@@ -0,0 +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;
@@ -0,0 +1,105 @@
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;
24
+ }
25
+ normalizeAreas(e) {
26
+ return e.map((t) => t.length === 1 ? t[0].trim().split(/\s+/) : t);
27
+ }
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);
35
+ }
36
+ accumulate(e, t) {
37
+ const r = [];
38
+ let i = 0;
39
+ for (const n of e)
40
+ r.push(i), i += n + t;
41
+ return r;
42
+ }
43
+ }
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
55
+ });
56
+ }
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`
71
+ });
72
+ }
73
+ }
74
+ ensureArea(e) {
75
+ function t() {
76
+ return `hsl(${Math.floor(Math.random() * 360)}, 70%, 70%)`;
77
+ }
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
+ }
83
+ clearArea() {
84
+ this.areas.clear();
85
+ }
86
+ }
87
+ class d {
88
+ 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);
92
+ }
93
+ registerArea(e, t) {
94
+ this.areaRenderer.registerArea(e, t);
95
+ }
96
+ layout() {
97
+ this.areaRenderer.layout();
98
+ }
99
+ destroy() {
100
+ this.observer.disconnect(), this.areaRenderer.clearArea();
101
+ }
102
+ }
103
+ export {
104
+ d as Rectflow
105
+ };
@@ -0,0 +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"})}));
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "rectflow",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Absolute-position layout engine with grid-like DSL",
6
+ "main": "./dist/rectflow.cjs.js",
7
+ "module": "./dist/rectflow.es.js",
8
+ "unpkg": "./dist/rectflow.umd.js",
9
+ "jsdelivr": "./dist/rectflow.umd.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "dev": "vite",
16
+ "build": "tsc && vite build",
17
+ "preview": "vite preview"
18
+ },
19
+ "keywords": [
20
+ "layout",
21
+ "grid",
22
+ "dashboard",
23
+ "absolute-layout",
24
+ "ui-engine",
25
+ "dock",
26
+ "panel"
27
+ ],
28
+ "author": "Danish Ahmed Khan",
29
+ "license": "MIT",
30
+ "devDependencies": {
31
+ "@types/node": "^25.0.3",
32
+ "typescript": "~5.9.3",
33
+ "vite": "^7.2.4"
34
+ }
35
+ }