npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@asunited/floor-planner

v1.1.0

Published

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.

Readme

asu-floorplanner

A 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.


Live

  • Playground: https://floorplanner.asunited.com.sg
  • Backend API: https://floorplanner-api.asunited.com.sg
  • API docs: https://floorplanner-api.asunited.com.sg/docs

Pushing to main builds the package, builds the playground and deploys it to Cloud Run in Singapore. See .github/workflows/deploy.yml.

Install

npm install asu-floorplanner
# or
pnpm add asu-floorplanner

Quick start

import { FloorPlanner } from 'asu-floorplanner';

export default function MyPage() {
  return (
    // The parent must have a defined height
    <div style={{ height: '100vh' }}>
      <FloorPlanner
        onSave={async (data) => {
          // data = { shapes, walls, version }
          await fetch('/api/floor-plans/my-plan', {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data),
          });
        }}
      />
    </div>
  );
}

Props

| Prop | Type | Default | Description | |---|---|---|---| | onSave | (data: FloorPlanData) => Promise<void> \| void | — | Called on Ctrl+S and auto-save. If omitted, saving is disabled. | | initialData | { id, name, shapes, walls } | — | Pre-load an existing plan on mount. | | autoSaveDelay | number | 30000 | Auto-save debounce in milliseconds. | | className | string | '' | Extra CSS classes on the wrapper div. | | mode | 'design' \| 'takeoff' | 'design' | Which side panel opens first. Both are always reachable from the toggle. | | onAnalyze | (req: AnalyzeRequest) => Promise<AnalyzeResult> | — | Host-supplied drawing analyser. Omit to disable auto-detect. | | takeoffRules | Partial<TakeoffRules> | — | Upturn height, wastage, deduction minimum, variance tolerance. |


Load an existing plan

const plan = await fetch('/api/floor-plans/plan-1').then(r => r.json());

<FloorPlanner
  initialData={{
    id: plan.id,
    name: plan.name,
    shapes: plan.shapes,
    walls: plan.walls,
  }}
  onSave={async (data) => {
    await fetch(`/api/floor-plans/${plan.id}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    });
  }}
/>

Tailwind CSS

The package uses Tailwind utility classes. Add the package path to your tailwind.config:

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{ts,tsx}',
    './node_modules/asu-floorplanner/dist/**/*.{js,mjs}',
  ],
};

If you are not using Tailwind, import the pre-built stylesheet instead:

import 'asu-floorplanner/dist/styles.css'; // coming in v1.1

Drawing takeoff

Added in v1.1. Load a floor plan drawing, set the scale off a known dimension, trace the wet areas, and read off quantities.

<FloorPlanner
  mode="takeoff"
  onAnalyze={async (req) => {
    const res = await fetch('/api/takeoff/analyse', {
      method: 'POST',
      body: JSON.stringify(req),
    });
    return res.json();
  }}
  takeoffRules={{ upturnHeight: 0.3, wastageFactor: 0.1, wetAreasOnly: true }}
  onSave={save}
/>

The workflow

  1. Upload a PDF or image. Vector PDFs also give up their embedded text layer, so dimension strings are read exactly — no OCR involved.
  2. Calibrate (C): click both ends of a dimension you know, type its real length. Everything downstream derives from this, so it is recorded with the plan and shown on every export.
  3. Trace (A): click each corner of an area. Click the first point again, right-click, or press Enter to close. Concave outlines are measured properly via the shoelace formula — a notched room is not billed as its bounding box.
  4. Or auto-detect: press Analyse drawing. Proposals appear dashed on the canvas and queued in the panel. Nothing becomes a quantity until someone accepts it, and accepted shapes record that they came from AI.
  5. Read off floor area, perimeter and billable area per room. Export CSV.

Accuracy, and what "accurate" means here

The traced polygon gives one area. The dimensions printed on the drawing give a second, independent one. When they disagree by more than varianceTolerance (2% by default) the room is flagged for review rather than billed. That cross-check is what makes a number defensible to a customer.

Two things are deliberate:

  • A model never measures. Geometry comes from deterministic detection; the model reads labels and dimension text. Vision models return plausible coordinates, which on an invoice is worse than none.
  • upturnHeight and wastageFactor default to 0. Waterproofing is normally billed as floor area plus membrane upturn up the wall, plus an overlap allowance — but until your QS confirms the basis, nothing is added on an assumption. Set them explicitly.

An uncalibrated plan says so in the status bar, in the panel, and in the CSV footer.

Analyser contract

onAnalyze receives the rendered drawing, its pixel dimensions, where it sits in world coordinates, the current scale and any PDF text layer. It returns DetectedRoom[] with points in image pixel coordinates — the SDK maps them onto the canvas, simplifies contours, clamps confidences and drops degenerate rings.

Keep the model call server-side. A key in the browser bundle ships to every consumer of this package. See examples/ for a working endpoint with OpenCV geometry, swappable Gemini/Claude adapters and the dimension cross-check.

Saved payload

getExportData() now returns v1.1, which additionally carries scale, backgroundImage and takeoffRules. Reading stays backward compatible: a v1.0 plan loads unchanged. A billed quantity is not reproducible without knowing how the drawing was calibrated, so persist the whole payload.


Advanced: use individual pieces

import {
  FloorPlannerCanvas,
  Toolbar,
  PropertiesPanel,
  StatusBar,
  ViewToggle,
  ExportModal,
  TakeoffPanel,
  TakeoffOverlay,
  CalibrationModal,
  DetectionReview,
  useFloorPlannerStore,
  useKeyboardShortcuts,
  useAutoSave,
  useDrawingImport,
  useAnalyze,
  summariseTakeoff,
  takeoffToCsv,
  polygonArea,
} from 'asu-floorplanner';

export function CustomLayout() {
  const { viewMode } = useFloorPlannerStore();
  useKeyboardShortcuts();

  return (
    <div className="flex h-screen">
      <Toolbar />
      <div className="flex-1 relative">
        {viewMode === '2d' ? <FloorPlannerCanvas /> : null}
      </div>
      <PropertiesPanel />
    </div>
  );
}

Keyboard shortcuts

| Key | Action | |---|---| | V / Esc | Select tool | | R | Room (rectangle) | | W | Wall tool | | L | Line | | D | Door | | O | Window | | T | Text label | | F | Furniture | | A | Area (trace a room outline) | | C | Calibrate scale | | G | Toggle grid | | S | Toggle snap | | + / - | Zoom in/out | | 0 | Reset view | | 2 / 3 | Switch 2D / 3D | | Enter | Close the area being traced | | Delete | Delete selected | | Ctrl+Z | Undo | | Ctrl+Y / Ctrl+Shift+Z | Redo | | Ctrl+A | Select all | | Ctrl+S | Save (calls onSave) |


Export formats

  • PNG — raster snapshot of the 2D canvas
  • SVG — scalable vector of all shapes and walls
  • JSON — full plan data, re-importable via importData()
  • CSV — takeoff quantities per room, with calibration provenance in the footer

TypeScript types

import type {
  Shape, Wall, FloorPlanData, FloorPlannerProps, ToolType,
  Point, Scale, ScaleCalibration,
  AnalyzeRequest, AnalyzeResult, AnalyzeFn, DetectedRoom,
  TakeoffRules, TakeoffSummary, RoomQuantity, QuantityVariance,
} from 'asu-floorplanner';

Peer dependencies

react >= 18
react-dom >= 18
pdfjs-dist >= 4      (optional — only needed to import PDF drawings)

pdfjs-dist is lazy-loaded and never bundled. Without it, image upload still works and PDF upload fails with an actionable message.

Everything else (three.js, zustand, lucide-react, sonner, uuid, immer) is a regular dependency: npm installs it alongside the package. These are not bundled into dist, so if you link the package locally with file: or npm link you must make sure they resolve — see playground/ for a working setup.


License

UNLICENSED — private package for AS United Pte Ltd.