{"_id":"@canwork/boxwood","_rev":"4-96690aa2db7018397f707e0999d755d5","name":"@canwork/boxwood","dist-tags":{"latest":"1.2.1"},"versions":{"1.0.0":{"name":"@canwork/boxwood","version":"1.0.0","keywords":["layout-engine","svg-layout","canvas-layout","vector-graphics","absolute-coordinates","box-model","declarative-layout","remotion"],"author":{"name":"Canwork Studios"},"license":"MIT","_id":"@canwork/boxwood@1.0.0","maintainers":[{"name":"bonzaicarn","email":"bonzai2carn@gmail.com"}],"homepage":"https://github.com/carnworkstudios/boxwood#readme","bugs":{"url":"https://github.com/carnworkstudios/boxwood/issues"},"dist":{"shasum":"e89287b9b511c81e597c17b8713cec53a7909c1d","tarball":"https://registry.npmjs.org/@canwork/boxwood/-/boxwood-1.0.0.tgz","fileCount":18,"integrity":"sha512-OEwfHZbwZVRVlB+qPyyauzEA/38nRmUZyi6djggsY/4+hbcmd+vChC/a14H3b12hpgnN9aQi76d7ML+WoV4sEg==","signatures":[{"sig":"MEUCIDPhQt19QLM2inS8+AtPhL3WW5P1rZ7tPI64+DdWz2FmAiEAqKxTFTrU2iLKweXIr+P9gt1M4z8a0IQ6FBFh3V0LPro=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":41104},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","unpkg":"dist/boxwood.global.js","gitHead":"948d244f47351730b47069500e46f5a88f4067e3","scripts":{"build":"tsc","build:all":"npm run build && npm run build:bundle","build:bundle":"esbuild index.ts --bundle --minify --format=iife --global-name=Boxwood --outfile=dist/boxwood.global.js","prepublishOnly":"npm run build:all"},"_npmUser":{"name":"bonzaicarn","email":"bonzai2carn@gmail.com"},"jsdelivr":"dist/boxwood.global.js","repository":{"url":"git+https://github.com/carnworkstudios/boxwood.git","type":"git"},"_npmVersion":"11.11.0","description":"A Declarative Box-Split and Collision Engine for Vector Graphics. It provides a CSS-like layout grammar (padding, gap, cols, rows, auto-height), but outputs a clean JSON coordinate tree that any visual canvas (SVG, <canvas>, WebGL) can use to draw pixels,","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"esbuild":"^0.28.1","typescript":"^5.4.5"},"_npmOperationalInternal":{"tmp":"tmp/boxwood_1.0.0_1782527043778_0.07918537492909383","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@canwork/boxwood","version":"1.1.0","keywords":["layout-engine","svg-layout","canvas-layout","vector-graphics","absolute-coordinates","box-model","declarative-layout","remotion"],"author":{"name":"Canwork Studios"},"license":"MIT","_id":"@canwork/boxwood@1.1.0","maintainers":[{"name":"bonzaicarn","email":"bonzai2carn@gmail.com"}],"homepage":"https://github.com/carnworkstudios/boxwood#readme","bugs":{"url":"https://github.com/carnworkstudios/boxwood/issues"},"dist":{"shasum":"9585e88aad6d28ecb8c7c2034d4baf52c4fa7714","tarball":"https://registry.npmjs.org/@canwork/boxwood/-/boxwood-1.1.0.tgz","fileCount":18,"integrity":"sha512-ZdgBEer26q1B8PY6t8+X0R2YOocjjCI/e3HBfi/vbNmK1t0dXGPrceDgk1uMdnkzk3J3iFBwas3uM+4RhDvi3A==","signatures":[{"sig":"MEUCIFDGAIbChVeCSvNnU8mwfLoOUOzGjuVvtibdhq2XEhtoAiEAmUReVZMm8nkCbBB9yco1yTMXK73LG1zzS/5BdcmvCiA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":47271},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","unpkg":"dist/boxwood.global.js","gitHead":"e25f0043b1f4b9793d11f643154bc72bba81cdd9","scripts":{"test":"npx tsx __tests__/resolve.test.mts","build":"tsc","examples":"tsx examples/build.ts","build:all":"npm run build && npm run build:bundle","build:bundle":"esbuild index.ts --bundle --minify --format=iife --global-name=Boxwood --outfile=dist/boxwood.global.js","prepublishOnly":"npm run build:all"},"_npmUser":{"name":"bonzaicarn","email":"bonzai2carn@gmail.com"},"jsdelivr":"dist/boxwood.global.js","repository":{"url":"git+https://github.com/carnworkstudios/boxwood.git","type":"git"},"_npmVersion":"11.11.0","description":"A Declarative Box-Split and Collision Engine for Vector Graphics. It provides a CSS-like layout grammar (padding, gap, cols, rows, auto-height), but outputs a clean JSON coordinate tree that any visual canvas (SVG, <canvas>, WebGL) can use to draw pixels,","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","esbuild":"^0.28.1","typescript":"^5.4.5"},"_npmOperationalInternal":{"tmp":"tmp/boxwood_1.1.0_1782651048794_0.7165524100854048","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@canwork/boxwood","version":"1.2.0","keywords":["layout-engine","svg-layout","canvas-layout","vector-graphics","absolute-coordinates","box-model","declarative-layout","remotion"],"author":{"name":"Canwork Studios"},"license":"MIT","_id":"@canwork/boxwood@1.2.0","maintainers":[{"name":"bonzaicarn","email":"bonzai2carn@gmail.com"}],"homepage":"https://github.com/carnworkstudios/boxwood#readme","bugs":{"url":"https://github.com/carnworkstudios/boxwood/issues"},"dist":{"shasum":"77dfa86b45b8508f3b04cf1aec988dc7510bd58a","tarball":"https://registry.npmjs.org/@canwork/boxwood/-/boxwood-1.2.0.tgz","fileCount":42,"integrity":"sha512-egNEGRrHH8Psl0QCac2dUjzjjVrkb0+kN0WzceLxeQdckNe6erXuxhBhGo20ZTo9o6eQutgLYbjWOTa0Wxxfuw==","signatures":[{"sig":"MEYCIQCiw9Kgu3qgq/GK2Yp1drzW1L86TvnCHs41KoDJyO0ZGQIhAJAn08/ERgR7uImcDcuBkAEaHi3YQZaRdFvwcxkIqmiJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":151108},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","unpkg":"dist/boxwood.global.js","exports":{".":{"types":"./dist/index.d.ts","import":"./index.js","default":"./dist/boxwood.global.js"},"./adapters":{"types":"./dist/adapters/index.d.ts","import":"./adapters/index.js"}},"gitHead":"db0e524876f69064a579454b0c02cdbb448f2d7b","scripts":{"test":"npx tsx __tests__/resolve.test.mts && npx tsx __tests__/features.test.mts && npx tsx __tests__/interaction.test.mts","build":"tsc","examples":"npx tsx examples/build.ts","build:all":"npm run build && npm run build:bundle","typecheck":"tsc --noEmit","build:bundle":"esbuild index.ts --bundle --minify --format=iife --global-name=Boxwood --outfile=dist/boxwood.global.js","prepublishOnly":"npm run build:all"},"_npmUser":{"name":"bonzaicarn","email":"bonzai2carn@gmail.com"},"jsdelivr":"dist/boxwood.global.js","repository":{"url":"git+https://github.com/carnworkstudios/boxwood.git","type":"git"},"_npmVersion":"11.11.0","description":"A Declarative Box-Split and Collision Engine for Vector Graphics. It provides a CSS-like layout grammar (padding, gap, cols, rows, auto-height), but outputs a clean JSON coordinate tree that any visual canvas (SVG, <canvas>, WebGL) can use to draw pixels,","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","esbuild":"^0.28.1","typescript":"^5.4.5"},"_npmOperationalInternal":{"tmp":"tmp/boxwood_1.2.0_1785417994268_0.1054248747330262","host":"s3://npm-registry-packages-npm-production"}},"1.2.1":{"name":"@canwork/boxwood","version":"1.2.1","description":"A Declarative Box-Split and Collision Engine for Vector Graphics. It provides a CSS-like layout grammar (padding, gap, cols, rows, auto-height), but outputs a clean JSON coordinate tree that any visual canvas (SVG, <canvas>, WebGL) can use to draw pixels,","main":"dist/index.js","types":"dist/index.d.ts","unpkg":"dist/boxwood.global.js","jsdelivr":"dist/boxwood.global.js","sideEffects":false,"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/boxwood.global.js"},"./adapters":{"types":"./dist/adapters/index.d.ts","import":"./dist/adapters/index.js"}},"type":"module","scripts":{"test":"npx tsx __tests__/resolve.test.mts && npx tsx __tests__/features.test.mts && npx tsx __tests__/interaction.test.mts","typecheck":"tsc --noEmit","build":"tsc","build:bundle":"esbuild index.ts --bundle --minify --format=iife --global-name=Boxwood --outfile=dist/boxwood.global.js","build:all":"npm run build && npm run build:bundle","prepublishOnly":"npm run build:all","examples":"npx tsx examples/build.ts","reel":"npx tsx examples/build.ts && npx tsx examples/capture.ts","reel:capture":"npx tsx examples/capture.ts"},"keywords":["layout-engine","svg-layout","canvas-layout","vector-graphics","absolute-coordinates","box-model","declarative-layout","remotion"],"repository":{"type":"git","url":"git+https://github.com/carnworkstudios/boxwood.git"},"bugs":{"url":"https://github.com/carnworkstudios/boxwood/issues"},"homepage":"https://github.com/carnworkstudios/boxwood#readme","author":{"name":"Canwork Studios"},"license":"MIT","devDependencies":{"esbuild":"^0.28.1","tsx":"^4.19.0","typescript":"^5.4.5"},"gitHead":"32cb6072e4b468a6858af400704c3edb3350749c","_id":"@canwork/boxwood@1.2.1","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-3s7QQfblzLYFffrT7dXp42WVibVKz6mvnnyLCAKy/AqgMxU3HB3USbolSkkNY08f5yNBo4PN/KqPnphvVzL01A==","shasum":"f0b264d052e5e3743e7f5a80cf591955b05ae31c","tarball":"https://registry.npmjs.org/@canwork/boxwood/-/boxwood-1.2.1.tgz","fileCount":42,"unpackedSize":151241,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDJfkVR/Hxj+P7gUCQLuzefpNDR9bfydxQouh6QT0hwCAiEA4fPLPjE+Cq/Lk8aJ4YZ0GCJ4OcltYuwSOHhMFKJw5IE="}]},"_npmUser":{"name":"bonzaicarn","email":"bonzai2carn@gmail.com"},"directories":{},"maintainers":[{"name":"bonzaicarn","email":"bonzai2carn@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/boxwood_1.2.1_1786713102958_0.5539008316600111"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-27T02:24:03.609Z","modified":"2026-08-14T13:11:43.312Z","1.0.0":"2026-06-27T02:24:03.928Z","1.1.0":"2026-06-28T12:50:48.944Z","1.2.0":"2026-07-30T13:26:34.409Z","1.2.1":"2026-08-14T13:11:43.122Z"},"bugs":{"url":"https://github.com/carnworkstudios/boxwood/issues"},"author":{"name":"Canwork Studios"},"license":"MIT","homepage":"https://github.com/carnworkstudios/boxwood#readme","keywords":["layout-engine","svg-layout","canvas-layout","vector-graphics","absolute-coordinates","box-model","declarative-layout","remotion"],"repository":{"type":"git","url":"git+https://github.com/carnworkstudios/boxwood.git"},"description":"A Declarative Box-Split and Collision Engine for Vector Graphics. It provides a CSS-like layout grammar (padding, gap, cols, rows, auto-height), but outputs a clean JSON coordinate tree that any visual canvas (SVG, <canvas>, WebGL) can use to draw pixels,","maintainers":[{"name":"bonzaicarn","email":"bonzai2carn@gmail.com"}],"readme":"# Boxwood\n\n[![npm version](https://img.shields.io/npm/v/@canwork/boxwood?style=for-the-badge)](https://www.npmjs.com/package/@canwork/boxwood) [![License](https://img.shields.io/npm/l/@canwork/boxwood?style=for-the-badge)](LICENSE)\n\n**The Geometry Backbone for Interactive Canvas & Diagram Apps.**\n\nBoxwood is a zero-dependency typescript geometry framework that unifies **Layout Resolution**, **Spatial Collision Separation**, and **Obstacle-Aware Connector Routing (A*)** into a single coherent system. Built specifically for diagramming tools, node-based schema editors, Figma/Miro competitors, and custom vector canvases (SVG, 2D Canvas, WebGL, Three.js, PixiJS).\n\n---\n\n## Why Boxwood?\n\nStandard layout engines (like Yoga) calculate static node coordinates, but leave connector wire routing and node collision handling as manual exercises for the developer.\n\nBoxwood solves all three as a single unified engine:\n1. **Declarative Layout Grammar**: Flex splits (`cols`, `rows`, `grid`, `zones`), padding, gaps, auto-height text wrapping, and constraints (`minW`, `maxW`, `aspectRatio`).\n2. **Spatial Hash Collision Solver**: $O(N)$ Minimum Translation Vector (MTV) separation algorithm that resolves box overlaps in dense canvases without jitter, with `pinned` boxes for drag interactions.\n3. **A\\* Grid Connector Pathfinder**: Real obstacle-aware orthogonal and Manhattan wire routing with per-side ports, rounded corners, and path sampling for labels and arrowheads.\n4. **Interaction Geometry**: Snapping with alignment guides (`snapBox`), align/distribute primitives, marquee selection (`findInRect`), and paint-order hit-testing (`findAt`, `findAllAt`).\n5. **Canvas-Scale Utilities**: Viewport culling (`getVisibleBounds`), content bounds (`boundsOf`), a shared spatial index, layout interpolation (`interpolateLayout`), and renderer adapters (Canvas, SVG, Three.js, PixiJS).\n\nSee [CHANGELOG.md](CHANGELOG.md) for what each version added and why.\n\n---\n\n## Quick Start\n\n```bash\nnpm install @canwork/boxwood\n```\n\n```ts\nimport { resolveLayout, defaultMeasure, LNode } from \"@canwork/boxwood\";\n\nconst tree: LNode = {\n  style: { padding: 20 },\n  split: { cols: [\"60%\", \"40%\"], gap: 15 },\n  children: [\n    { id: \"nodeA\", style: { w: \"100%\", h: 200 } },\n    { id: \"nodeB\", style: { w: \"100%\", h: 200 } },\n  ],\n};\n\nconst boundary = { x: 0, y: 0, w: 1000, h: 600 };\nconst result = resolveLayout(tree, boundary);\n\n// 1. Spatial Hit-Test\nconst clickedNode = result.findAt(150, 150);\n\n// 2. Bound Obstacle-Aware Wire Routing (A* Pathfinding around other boxes)\nconst wire = result.routeConnector(\"nodeA\", \"nodeB\", {\n  mode: \"orthogonal\",\n  cornerRadius: 8,\n});\n\nconsole.log(\"SVG Wire Path:\", wire.svgPath);\n```\n\n### CDN (No Build Step)\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@canwork/boxwood/dist/boxwood.global.js\"></script>\n<script>\n  const { resolveLayout } = Boxwood;\n  const result = resolveLayout(tree, boundary);\n</script>\n```\n\n---\n\n## The Coherent Feature Suite\n\n### 1. Spatial Hit-Testing (`findAt` & `findAllAt`)\n\nInstantly query spatial coordinates against resolved layout geometry:\n\n```ts\n// Finds the deepest leaf node under point (x, y)\nconst leaf = result.findAt(250, 180);\n\n// Returns full ancestor hierarchy chain [root, parent, leaf]\nconst stack = result.findAllAt(250, 180);\n```\n\n### 2. Obstacle-Aware A* Wire Routing (`routeConnector`)\n\nAutomatically snakes connector lines around node boxes without passing through obstacles:\n\n```ts\nconst routed = result.routeConnector(\"nodeA\", \"nodeB\", {\n  mode: \"curved\", // A* around obstacles, drawn as a flowing spline\n  tension: 0.8,   // 0 hugs the path, 1 is rounder\n  padding: 16,    // Safety obstacle padding\n});\n\n// routed.svgPath -> \"M 200 100 C 232 100, 260 118, 260 150 ...\"\n```\n\n| `mode` | Avoids obstacles | Shape |\n| --- | --- | --- |\n| `\"curved\"` | **yes** (A\\*) | continuous spline — the usual look for node editors |\n| `\"orthogonal\"` / `\"manhattan\"` | **yes** (A\\*) | square corners, optionally rounded via `cornerRadius` |\n| `\"bezier\"` | no | one curve straight between the endpoints |\n| `\"straight\"` | no | a direct line |\n\n`\"bezier\"` and `\"straight\"` are direct-connection modes and ignore obstacles by design —\nreach for `\"curved\"` when a curved wire must respect box boundaries. The default is\n`\"orthogonal\"`.\n\n### 3. Spatial Collision Separation (`collide.ts`)\n\nPost-layout separation engine that automatically prevents node overlapping:\n\n```ts\nimport { resolveCollisions } from \"@canwork/boxwood\";\n\n// Resolves overlapping nodes within world bounds using Spatial Hashing & MTV\nresolveCollisions(result.boxes, worldBoundary);\n```\n\n### 4. Layout State Interpolation (`interpolateLayout`)\n\nFluidly animate transitions between two layout trees using customizable easing curves:\n\n```ts\nimport { interpolateLayout, easings } from \"@canwork/boxwood\";\n\n// Tween layout state at 50% progress with Spring easing\nconst tweened = interpolateLayout(layoutA, layoutB, 0.5, easings.spring);\n```\n\n### 5. Camera Viewport Controller (`Viewport`)\n\nPan, zoom, and screen-to-world coordinate transformations for interactive canvas viewports:\n\n```ts\nimport { Viewport } from \"@canwork/boxwood\";\n\nconst vp = new Viewport({ screenWidth: 1200, screenHeight: 800, zoom: 1 });\nvp.zoomAt({ x: 400, y: 300 }, 1.25);\n\nconst worldPt = vp.screenToWorld({ x: 400, y: 300 });\n```\n\n### 6. Snapping & Alignment Guides (`snapBox`)\n\nSnap a dragged box to its neighbours' edges and centres, to gaps already present between\nneighbours, or to a grid — and get back the guide lines to draw:\n\n```ts\nimport { snapBox } from \"@canwork/boxwood\";\n\nconst { box, guides } = snapBox(draggedBox, {\n  targets: siblings,  // boxes to align against\n  grid: 8,            // fallback grid\n  threshold: 8,       // snap distance in world units\n});\n\n// guides: [{ axis: \"x\", position: 240, kind: \"edge\", start: 100, end: 460 }]\n```\n\nPlus the toolbar primitives: `alignBoxes(boxes, \"centerX\")` and\n`distributeEvenly(boxes, \"x\")`.\n\n### 7. Marquee Selection & Culling\n\n```ts\n// Drag-select: everything touching the rubber band\nconst selected = result.findInRect(marqueeRect, { includeContainers: false });\n\n// Render only what's on screen\nconst visible = result.findInRect(vp.getVisibleBounds(200));\n\n// Zoom to fit actual content (root.box is NOT the content extent)\nvp.fitBounds(result.bounds());\n```\n\nHit-testing and rect queries share a spatial index built on first use. If you mutate\nboxes in place (dragging, or re-running `resolveCollisions`), call\n`invalidateQueryIndex(result)` so queries don't answer from stale geometry.\n\n### 8. Drag-Aware Collisions & Connector Ports\n\n```ts\n// The node under the cursor holds still; the others get out of its way.\n{ id: \"dragging\", style: { pinned: true, z: 10 } }\n\n// Fan parallel wires out along a side instead of stacking them on one point.\nconst wire = result.routeConnector(\"a\", \"b\", {\n  ports: { from: { index: 0, count: 3 }, to: { index: 1, count: 2 } },\n});\n\n// Sample the path for labels, arrowheads, or particles.\nconst labelAt = wire.midPoint;\nconst quarter = wire.pointAt(0.25);\n```\n\n### 9. Declarative Text\n\n```ts\n// `node.text` is measured with the layout-wide `measure` hook, so auto-height works\n// without hand-writing a measurement closure for every node.\nconst tree: LNode = { id: \"para\", text: \"Long copy…\", style: { fontSize: 16 } };\nconst result = resolveLayout(tree, world, { measure: myCanvasMeasure });\n```\n\n---\n\n## Examples\n\n```bash\nnpm run examples     # regenerates the static scene pages\n```\n\n| Example | What it shows |\n| --- | --- |\n| `05-interaction-playground.html` | **New in 1.2.0.** A live node editor: drag with snapping and alignment guides, marquee-select, align/distribute, pan/zoom with a culling counter, pinned collisions, and fanned connector ports. Open it directly — it loads `../dist/boxwood.global.js`, so run `npm run build:all` first. |\n| `05-ports-and-zones.html` | **New in 1.2.0.** Six wires sharing four sides via ports, length labels at each `midPoint`, `node.text` auto-height through the measure hook, and a percentage-based `zones` footer. Resize the panel. |\n| `04-dynamic-reflow-overflow.html` | Auto-height, `OverflowSignal` reporting, and collision separation. |\n| `03-absolute-to-responsive.html` | Absolute PDF spans converted to percentage columns that reflow. |\n| `02-vector-connections.html` | A pipeline whose split flips columns→rows, with connectors that re-anchor. |\n| `01-bento-grid.html` | A weighted dashboard grid with per-tile `shrinkToFit()`. |\n| `boxwood-2.0-showcase.html` | The 1.1.0 feature suite in one page. |\n\n## License\n\nMIT © Canwork Studios\n","readmeFilename":"README.md"}