{"_id":"@asunited/floor-planner","_rev":"3-86faa26e0a66e0c93f06922ae3d73cc3","name":"@asunited/floor-planner","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@asunited/floor-planner","version":"1.0.0","keywords":["floor-planner","react","2d","3d","canvas","three.js","blueprint","floorplan","architecture"],"author":{"name":"AS United Pte Ltd"},"license":"UNLICENSED","_id":"@asunited/floor-planner@1.0.0","maintainers":[{"name":"lawrencekim","email":"lawrencekimutai09@gmail.com"},{"name":"asunited.com.sg","email":"itsupport@asunited.com.sg"}],"homepage":"https://github.com/AS-United/asu-floorplanner#readme","bugs":{"url":"https://github.com/AS-United/asu-floorplanner/issues"},"dist":{"shasum":"76b9b56c810ba9cec8bb7bb9cc305ad55b221b82","tarball":"https://registry.npmjs.org/@asunited/floor-planner/-/floor-planner-1.0.0.tgz","fileCount":6,"integrity":"sha512-79XTaOhuHbn8zD1inFVD3fXH7L/P18+P0XhXmRoayQKwK9NmArHaM7c94XB7ppo1qyigY2KxqVfgB+azSPwOCQ==","signatures":[{"sig":"MEQCIHdXa/UqiMi688HVlrDrfsJ2MSF25+4Ib40jNiOmA8jRAiBiblSOexImeFhS34HQDz5Nktb4d+JCR6b98fh/ak0mow==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":298275},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"5478fcf90cab03816e117f54cd93234794c9b5e7","scripts":{"dev":"tsup --watch","lint":"eslint src --ext .ts,.tsx","build":"tsup","type-check":"tsc --noEmit"},"_npmUser":{"name":"asunited.com.sg","email":"itsupport@asunited.com.sg"},"repository":{"url":"git+https://github.com/AS-United/asu-floorplanner.git","type":"git"},"_npmVersion":"11.8.0","description":"A plug-and-play 2D/3D floor planner React component with wall drawing, furniture, export (PNG/SVG/JSON), undo/redo and keyboard shortcuts.","directories":{},"_nodeVersion":"22.19.0","dependencies":{"uuid":"^9.0.0","immer":"^10.1.1","three":"^0.165.0","sonner":"^1.5.0","zustand":"^4.5.2","lucide-react":"^0.383.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"^5.4.5","@types/uuid":"^9.0.8","@types/react":"^18.3.3","@types/three":"^0.165.0","@types/react-dom":"^18.3.0"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/floor-planner_1.0.0_1780033632887_0.10489535924186755","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@asunited/floor-planner","version":"1.0.1","keywords":["floor-planner","react","2d","3d","canvas","three.js","blueprint","floorplan","architecture"],"author":{"name":"AS United Pte Ltd"},"license":"UNLICENSED","_id":"@asunited/floor-planner@1.0.1","maintainers":[{"name":"lawrencekim","email":"lawrencekimutai09@gmail.com"},{"name":"asunited.com.sg","email":"itsupport@asunited.com.sg"}],"homepage":"https://github.com/AS-United/asu-floorplanner#readme","bugs":{"url":"https://github.com/AS-United/asu-floorplanner/issues"},"dist":{"shasum":"e2a5413686dea662c9d9a6ec5fc2ac805f7b2581","tarball":"https://registry.npmjs.org/@asunited/floor-planner/-/floor-planner-1.0.1.tgz","fileCount":8,"integrity":"sha512-UfzcpAiShH/xB/kZR/nMc3L/s1LnMFd6OxngBO7Sv+sHvwtCh9CYSYPC0uZu0YFwgzs4O4thCnoCpoFUdue4LA==","signatures":[{"sig":"MEYCIQCsd5fWOvDO6SWtyEQp1fKM5T1blpysB8T3H1BWBrr4ygIhALxhW2u3Qi1DoEts1j8B7zNxMUTp6Dxx79GquKWA1K+L","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":867449},"main":"./dist/index.js","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"ad588b8fbfd872dca07eb4f3faccf03ac3e5f263","scripts":{"dev":"tsup --watch","lint":"eslint src --ext .ts,.tsx","build":"tsup","type-check":"tsc --noEmit"},"_npmUser":{"name":"asunited.com.sg","email":"itsupport@asunited.com.sg"},"repository":{"url":"git+https://github.com/AS-United/asu-floorplanner.git","type":"git"},"_npmVersion":"11.8.0","description":"A plug-and-play 2D/3D floor planner React component with wall drawing, furniture, export (PNG/SVG/JSON), undo/redo and keyboard shortcuts.","directories":{},"_nodeVersion":"22.19.0","dependencies":{"uuid":"^9.0.0","immer":"^10.1.1","three":"^0.165.0","sonner":"^1.5.0","zustand":"^4.5.2","lucide-react":"^0.383.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"^5.4.5","@types/uuid":"^9.0.8","@types/react":"^18.3.3","@types/three":"^0.165.0","@types/react-dom":"^18.3.0"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/floor-planner_1.0.1_1780037964791_0.6796789032372794","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@asunited/floor-planner","version":"1.1.0","description":"A plug-and-play 2D/3D floor planner React component with wall drawing, furniture, drawing takeoff (PDF/image import, scale calibration, polygon area measurement, AI-assisted room detection), export (PNG/SVG/JSON/CSV), undo/redo and keyboard shortcuts.","main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./server":{"types":"./dist/server/index.d.ts","import":"./dist/server/index.mjs","require":"./dist/server/index.js"}},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","type-check":"tsc --noEmit","lint":"eslint src --ext .ts,.tsx"},"peerDependencies":{"pdfjs-dist":">=4.0.0","react":">=18.0.0","react-dom":">=18.0.0","sharp":">=0.33.0"},"dependencies":{"immer":"^10.1.1","lucide-react":"^1.33.0","sharp":"^0.35.3","sonner":"^1.5.0","three":"^0.165.0","uuid":"^9.0.0","zustand":"^4.5.2"},"devDependencies":{"@types/node":"^20.19.43","@types/react":"^18.3.3","@types/react-dom":"^18.3.0","@types/three":"^0.165.0","@types/uuid":"^9.0.8","pdfjs-dist":"^4.4.168","react":"^18.3.1","react-dom":"^18.3.1","tsup":"^8.5.1","typescript":"^5.4.5","vitest":"^2.1.9"},"keywords":["floor-planner","react","2d","3d","canvas","three.js","blueprint","floorplan","architecture","takeoff","quantity-surveying","waterproofing","area-measurement","pdf"],"author":{"name":"AS United Pte Ltd"},"license":"UNLICENSED","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"repository":{"type":"git","url":"git+https://github.com/AS-United/asu-floorplanner.git"},"peerDependenciesMeta":{"pdfjs-dist":{"optional":true},"sharp":{"optional":true}},"gitHead":"e8588e917e3c70215242bdf450b7ce19e5daffc1","_id":"@asunited/floor-planner@1.1.0","bugs":{"url":"https://github.com/AS-United/asu-floorplanner/issues"},"homepage":"https://github.com/AS-United/asu-floorplanner#readme","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-sxmmoNqm8zQIriQ79iqVRzfwWiygU1zpwpAiuFNjF1s+zb57Np14eXFmNczvYY/f789/TyGXULJDuAWvbOEKIQ==","shasum":"45f4bd07a3d3a6ac73d44c0d5e8e701fd8a4b947","tarball":"https://registry.npmjs.org/@asunited/floor-planner/-/floor-planner-1.1.0.tgz","fileCount":16,"unpackedSize":1749478,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBWnVJe2Iw+dtZvqHkFuaYcr3UiL5dsDGJ3KoE3LTOoRAiA/erM4QQPLuheNVJG6bwVWPXufH3Xm2wOzaabDRuiYlg=="}]},"_npmUser":{"name":"lawrencekim","email":"lawrencekimutai09@gmail.com"},"directories":{},"maintainers":[{"name":"lawrencekim","email":"lawrencekimutai09@gmail.com"},{"name":"asunited.com.sg","email":"itsupport@asunited.com.sg"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/floor-planner_1.1.0_1787898536598_0.19075827016185753"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-29T05:47:12.775Z","modified":"2026-08-28T06:28:56.920Z","1.0.0":"2026-05-29T05:47:13.035Z","1.0.1":"2026-05-29T06:59:24.950Z","1.1.0":"2026-08-28T06:28:56.727Z"},"bugs":{"url":"https://github.com/AS-United/asu-floorplanner/issues"},"author":{"name":"AS United Pte Ltd"},"license":"UNLICENSED","homepage":"https://github.com/AS-United/asu-floorplanner#readme","keywords":["floor-planner","react","2d","3d","canvas","three.js","blueprint","floorplan","architecture","takeoff","quantity-surveying","waterproofing","area-measurement","pdf"],"repository":{"type":"git","url":"git+https://github.com/AS-United/asu-floorplanner.git"},"description":"A plug-and-play 2D/3D floor planner React component with wall drawing, furniture, drawing takeoff (PDF/image import, scale calibration, polygon area measurement, AI-assisted room detection), export (PNG/SVG/JSON/CSV), undo/redo and keyboard shortcuts.","maintainers":[{"name":"lawrencekim","email":"lawrencekimutai09@gmail.com"},{"name":"asunited.com.sg","email":"itsupport@asunited.com.sg"}],"readme":"# asu-floorplanner\n\nA self-contained 2D/3D floor planner React component. Drop it into any Next.js, Vite, or Create-React-App project and pass an `onSave` callback — no API setup required inside the package.\n\n---\n\n\n## Live\n\n- Playground: https://floorplanner.asunited.com.sg\n- Backend API: https://floorplanner-api.asunited.com.sg\n- API docs: https://floorplanner-api.asunited.com.sg/docs\n\nPushing to `main` builds the package, builds the playground and deploys it to\nCloud Run in Singapore. See `.github/workflows/deploy.yml`.\n\n## Install\n\n```bash\nnpm install asu-floorplanner\n# or\npnpm add asu-floorplanner\n```\n\n---\n\n## Quick start\n\n```tsx\nimport { FloorPlanner } from 'asu-floorplanner';\n\nexport default function MyPage() {\n  return (\n    // The parent must have a defined height\n    <div style={{ height: '100vh' }}>\n      <FloorPlanner\n        onSave={async (data) => {\n          // data = { shapes, walls, version }\n          await fetch('/api/floor-plans/my-plan', {\n            method: 'PUT',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify(data),\n          });\n        }}\n      />\n    </div>\n  );\n}\n```\n\n---\n\n## Props\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `onSave` | `(data: FloorPlanData) => Promise<void> \\| void` | — | Called on Ctrl+S and auto-save. If omitted, saving is disabled. |\n| `initialData` | `{ id, name, shapes, walls }` | — | Pre-load an existing plan on mount. |\n| `autoSaveDelay` | `number` | `30000` | Auto-save debounce in milliseconds. |\n| `className` | `string` | `''` | Extra CSS classes on the wrapper div. |\n| `mode` | `'design' \\| 'takeoff'` | `'design'` | Which side panel opens first. Both are always reachable from the toggle. |\n| `onAnalyze` | `(req: AnalyzeRequest) => Promise<AnalyzeResult>` | — | Host-supplied drawing analyser. Omit to disable auto-detect. |\n| `takeoffRules` | `Partial<TakeoffRules>` | — | Upturn height, wastage, deduction minimum, variance tolerance. |\n\n---\n\n## Load an existing plan\n\n```tsx\nconst plan = await fetch('/api/floor-plans/plan-1').then(r => r.json());\n\n<FloorPlanner\n  initialData={{\n    id: plan.id,\n    name: plan.name,\n    shapes: plan.shapes,\n    walls: plan.walls,\n  }}\n  onSave={async (data) => {\n    await fetch(`/api/floor-plans/${plan.id}`, {\n      method: 'PUT',\n      body: JSON.stringify(data),\n    });\n  }}\n/>\n```\n\n---\n\n## Tailwind CSS\n\nThe package uses Tailwind utility classes. Add the package path to your `tailwind.config`:\n\n```js\n// tailwind.config.js\nmodule.exports = {\n  content: [\n    './src/**/*.{ts,tsx}',\n    './node_modules/asu-floorplanner/dist/**/*.{js,mjs}',\n  ],\n};\n```\n\nIf you are **not** using Tailwind, import the pre-built stylesheet instead:\n\n```ts\nimport 'asu-floorplanner/dist/styles.css'; // coming in v1.1\n```\n\n---\n\n## Drawing takeoff\n\nAdded in **v1.1**. Load a floor plan drawing, set the scale off a known\ndimension, trace the wet areas, and read off quantities.\n\n```tsx\n<FloorPlanner\n  mode=\"takeoff\"\n  onAnalyze={async (req) => {\n    const res = await fetch('/api/takeoff/analyse', {\n      method: 'POST',\n      body: JSON.stringify(req),\n    });\n    return res.json();\n  }}\n  takeoffRules={{ upturnHeight: 0.3, wastageFactor: 0.1, wetAreasOnly: true }}\n  onSave={save}\n/>\n```\n\n### The workflow\n\n1. **Upload** a PDF or image. Vector PDFs also give up their embedded text\n   layer, so dimension strings are read exactly — no OCR involved.\n2. **Calibrate** (`C`): click both ends of a dimension you know, type its real\n   length. Everything downstream derives from this, so it is recorded with the\n   plan and shown on every export.\n3. **Trace** (`A`): click each corner of an area. Click the first point again,\n   right-click, or press Enter to close. Concave outlines are measured properly\n   via the shoelace formula — a notched room is not billed as its bounding box.\n4. **Or auto-detect**: press *Analyse drawing*. Proposals appear dashed on the\n   canvas and queued in the panel. Nothing becomes a quantity until someone\n   accepts it, and accepted shapes record that they came from AI.\n5. **Read off** floor area, perimeter and billable area per room. Export CSV.\n\n### Accuracy, and what \"accurate\" means here\n\nThe traced polygon gives one area. The dimensions printed on the drawing give a\nsecond, independent one. When they disagree by more than `varianceTolerance`\n(2% by default) the room is flagged for review rather than billed. That\ncross-check is what makes a number defensible to a customer.\n\nTwo things are deliberate:\n\n- **A model never measures.** Geometry comes from deterministic detection;\n  the model reads labels and dimension text. Vision models return *plausible*\n  coordinates, which on an invoice is worse than none.\n- **`upturnHeight` and `wastageFactor` default to 0.** Waterproofing is\n  normally billed as floor area *plus* membrane upturn up the wall, plus an\n  overlap allowance — but until your QS confirms the basis, nothing is added on\n  an assumption. Set them explicitly.\n\nAn uncalibrated plan says so in the status bar, in the panel, and in the CSV\nfooter.\n\n### Analyser contract\n\n`onAnalyze` receives the rendered drawing, its pixel dimensions, where it sits\nin world coordinates, the current scale and any PDF text layer. It returns\n`DetectedRoom[]` with points in **image pixel coordinates** — the SDK maps them\nonto the canvas, simplifies contours, clamps confidences and drops degenerate\nrings.\n\nKeep the model call server-side. A key in the browser bundle ships to every\nconsumer of this package. See `examples/` for a working endpoint with\nOpenCV geometry, swappable Gemini/Claude adapters and the dimension cross-check.\n\n### Saved payload\n\n`getExportData()` now returns **v1.1**, which additionally carries `scale`,\n`backgroundImage` and `takeoffRules`. Reading stays backward compatible: a v1.0\nplan loads unchanged. A billed quantity is not reproducible without knowing how\nthe drawing was calibrated, so persist the whole payload.\n\n---\n\n## Advanced: use individual pieces\n\n```tsx\nimport {\n  FloorPlannerCanvas,\n  Toolbar,\n  PropertiesPanel,\n  StatusBar,\n  ViewToggle,\n  ExportModal,\n  TakeoffPanel,\n  TakeoffOverlay,\n  CalibrationModal,\n  DetectionReview,\n  useFloorPlannerStore,\n  useKeyboardShortcuts,\n  useAutoSave,\n  useDrawingImport,\n  useAnalyze,\n  summariseTakeoff,\n  takeoffToCsv,\n  polygonArea,\n} from 'asu-floorplanner';\n\nexport function CustomLayout() {\n  const { viewMode } = useFloorPlannerStore();\n  useKeyboardShortcuts();\n\n  return (\n    <div className=\"flex h-screen\">\n      <Toolbar />\n      <div className=\"flex-1 relative\">\n        {viewMode === '2d' ? <FloorPlannerCanvas /> : null}\n      </div>\n      <PropertiesPanel />\n    </div>\n  );\n}\n```\n\n---\n\n## Keyboard shortcuts\n\n| Key | Action |\n|---|---|\n| `V` / `Esc` | Select tool |\n| `R` | Room (rectangle) |\n| `W` | Wall tool |\n| `L` | Line |\n| `D` | Door |\n| `O` | Window |\n| `T` | Text label |\n| `F` | Furniture |\n| `A` | Area (trace a room outline) |\n| `C` | Calibrate scale |\n| `G` | Toggle grid |\n| `S` | Toggle snap |\n| `+` / `-` | Zoom in/out |\n| `0` | Reset view |\n| `2` / `3` | Switch 2D / 3D |\n| `Enter` | Close the area being traced |\n| `Delete` | Delete selected |\n| `Ctrl+Z` | Undo |\n| `Ctrl+Y` / `Ctrl+Shift+Z` | Redo |\n| `Ctrl+A` | Select all |\n| `Ctrl+S` | Save (calls `onSave`) |\n\n---\n\n## Export formats\n\n- **PNG** — raster snapshot of the 2D canvas  \n- **SVG** — scalable vector of all shapes and walls  \n- **JSON** — full plan data, re-importable via `importData()`\n- **CSV** — takeoff quantities per room, with calibration provenance in the footer\n\n---\n\n## TypeScript types\n\n```ts\nimport type {\n  Shape, Wall, FloorPlanData, FloorPlannerProps, ToolType,\n  Point, Scale, ScaleCalibration,\n  AnalyzeRequest, AnalyzeResult, AnalyzeFn, DetectedRoom,\n  TakeoffRules, TakeoffSummary, RoomQuantity, QuantityVariance,\n} from 'asu-floorplanner';\n```\n\n---\n\n## Peer dependencies\n\n```\nreact >= 18\nreact-dom >= 18\npdfjs-dist >= 4      (optional — only needed to import PDF drawings)\n```\n\n`pdfjs-dist` is lazy-loaded and never bundled. Without it, image upload still\nworks and PDF upload fails with an actionable message.\n\nEverything else (three.js, zustand, lucide-react, sonner, uuid, immer) is a\nregular dependency: npm installs it alongside the package. These are **not**\nbundled into `dist`, so if you link the package locally with `file:` or\n`npm link` you must make sure they resolve — see `playground/` for a working\nsetup.\n\n---\n\n## License\n\nUNLICENSED — private package for AS United Pte Ltd.\n","readmeFilename":"README.md"}