Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/skills/floormap-expert.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ You are an expert on the **Floormap** codebase. Apply this context whenever work

## What Floormap is

A zero-dependency TypeScript toolkit for building interactive 2D editors (floor plans, seat maps, office layouts). It provides scene management, pan/zoom/selection/picking primitives, and pluggable renderers. It is a monorepo with two publishable packages: `@floormap/core` and `@floormap/svg`.
A zero-dependency TypeScript toolkit for building interactive 2D editors (floor plans, seat maps, office layouts). It provides scene management, pan/zoom/selection/picking primitives, and pluggable renderers. It is a monorepo with two publishable packages: `@floormap-tools/core` and `@floormap-tools/svg`.

---

Expand All @@ -33,7 +33,7 @@ A zero-dependency TypeScript toolkit for building interactive 2D editors (floor

## Architecture rules to enforce

1. **No production deps** — `@floormap/core` and `@floormap/svg` must stay dependency-free.
1. **No production deps** — `@floormap-tools/core` and `@floormap-tools/svg` must stay dependency-free.
2. **Core is framework-agnostic** — no DOM, no React, no browser APIs in `packages/core/src/`.
3. **Branded types everywhere** — `EntityId` and `LayerId` are branded strings; never substitute with `string`.
4. **Immutable selection** — `applySelection` returns a new `Set`, never mutates the input.
Expand Down Expand Up @@ -106,5 +106,5 @@ Test environment: **Node** for core, **jsdom** for svg. Tests live in `src/__tes
- Forgetting `selectable: true` on entities → `hitTestPoint` returns null
- Using `string` instead of `EntityId` / `LayerId` → type errors downstream
- Mutating the `Set` from `applySelection` → breaks immutability contract
- Adding DOM code to `@floormap/core` → breaks the agnostic renderer model
- Adding DOM code to `@floormap-tools/core` → breaks the agnostic renderer model
- Not updating `index.ts` after adding exports → new symbols invisible to consumers
4 changes: 2 additions & 2 deletions .github/workflows/deploy-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ jobs:
<p>Interactive 2D editor toolkit — live examples</p>
<a href="vanilla/">
Vanilla JS
<span>@floormap/core + @floormap/svg</span>
<span>@floormap-tools/core + @floormap-tools/svg</span>
</a>
<a href="react/">
React
<span>@floormap/core + @floormap/react</span>
<span>@floormap-tools/core + @floormap-tools/react</span>
</a>
</body>
</html>
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
node-version: 22
cache: pnpm
registry-url: https://npm.pkg.github.com
scope: '@floormap'
scope: '@floormap-tools'

- run: pnpm install --frozen-lockfile

Expand Down
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
@floormap:registry=https://npm.pkg.github.com
@floormap-tools:registry=https://npm.pkg.github.com
14 changes: 7 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ Floormap is a zero-dependency TypeScript toolkit for building interactive 2D edi

```
packages/
core/ @floormap/core — scene engine, viewport, selection, events, picking
svg/ @floormap/svg — SVG renderer + DOM event handlers
core/ @floormap-tools/core — scene engine, viewport, selection, events, picking
svg/ @floormap-tools/svg — SVG renderer + DOM event handlers
adapters/
react/ @floormap/react — React adapter (hooks + FloormapCanvas component)
react/ @floormap-tools/react — React adapter (hooks + FloormapCanvas component)
examples/
js-vanilla/ vanilla JS example
react/ React example
Expand Down Expand Up @@ -47,7 +47,7 @@ Run from repo root. Per-package commands work inside each `packages/*` directory

## Architecture

### @floormap/core
### @floormap-tools/core

| Module | Responsibility |
|--------|---------------|
Expand All @@ -71,7 +71,7 @@ Run from repo root. Per-package commands work inside each `packages/*` directory
- `zoomAt` delta is multiplicative: `0.1` = +10%, `−0.1` = −10%
- `panBy` takes screen-space delta; positive `x` shifts content left (pan moves right in world)

### @floormap/svg
### @floormap-tools/svg

| Module | Responsibility |
|--------|---------------|
Expand Down Expand Up @@ -142,8 +142,8 @@ Run from repo root. Per-package commands work inside each `packages/*` directory

## What NOT to do

- Do not add production dependencies to `@floormap/core` or `@floormap/svg`.
- Do not add production dependencies to `@floormap-tools/core` or `@floormap-tools/svg`.
- Do not bypass branded types with plain strings.
- Do not mutate `Set` objects returned by `applySelection`.
- Do not add framework-specific code to `@floormap/core` (keep it agnostic).
- Do not add framework-specific code to `@floormap-tools/core` (keep it agnostic).
- Do not skip typecheck — the build uses project references.
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ Floormap provides the low-level engine for pan/zoom, selection, hit testing, and

| Package | Version | Description |
|---------|---------|-------------|
| [`@floormap/core`](packages/core) | 0.1.0 | Scene engine, viewport, event bus, picking, selection |
| [`@floormap/svg`](packages/svg) | 0.1.0 | SVG renderer + DOM event handlers |
| [`@floormap/react`](packages/adapters/react) | 0.1.0 | React adapter — hooks and `FloormapCanvas` component |
| [`@floormap-tools/core`](packages/core) | 0.1.0 | Scene engine, viewport, event bus, picking, selection |
| [`@floormap-tools/svg`](packages/svg) | 0.1.0 | SVG renderer + DOM event handlers |
| [`@floormap-tools/react`](packages/adapters/react) | 0.1.0 | React adapter — hooks and `FloormapCanvas` component |

---

Expand All @@ -30,13 +30,13 @@ Floormap provides the low-level engine for pan/zoom, selection, hit testing, and
**Vanilla JS / framework-agnostic:**

```bash
npm install @floormap/core @floormap/svg
npm install @floormap-tools/core @floormap-tools/svg
```

**React:**

```bash
npm install @floormap/core @floormap/react
npm install @floormap-tools/core @floormap-tools/react
```

All packages ship dual **ESM + CJS** builds with full TypeScript declarations.
Expand All @@ -47,14 +47,14 @@ All packages ship dual **ESM + CJS** builds with full TypeScript declarations.

```tsx
import { useCallback, useEffect } from 'react';
import { createEmptyScene, addEntity } from '@floormap/core';
import type { Entity, EntityId, LayerId } from '@floormap/core';
import { createEmptyScene, addEntity } from '@floormap-tools/core';
import type { Entity, EntityId, LayerId } from '@floormap-tools/core';
import {
FloormapCanvas,
useFloormapCore,
useSelection,
useViewport,
} from '@floormap/react';
} from '@floormap-tools/react';

const LAYER = 'main' as LayerId;

Expand Down Expand Up @@ -208,9 +208,9 @@ useCoreEvent(core, 'entities:changed', ({ type, ids }) => {
## Quick start — Vanilla JS

```ts
import { createCore, createEmptyScene } from '@floormap/core';
import { mountSvgRenderer } from '@floormap/svg';
import type { EntityId, LayerId } from '@floormap/core';
import { createCore, createEmptyScene } from '@floormap-tools/core';
import { mountSvgRenderer } from '@floormap-tools/svg';
import type { EntityId, LayerId } from '@floormap-tools/core';

// 1. Define layers (bottom → top z-order)
const LAYERS = {
Expand Down Expand Up @@ -290,21 +290,21 @@ renderer.destroy();
- `fitToBounds`, `fitToScene`, `centerOn` for programmatic camera control
- Precise coordinate transforms: `worldToScreen` / `screenToWorld`

**Interaction** (via `@floormap/svg` or `@floormap/react`)
**Interaction** (via `@floormap-tools/svg` or `@floormap-tools/react`)
- Mouse wheel zoom (configurable sensitivity)
- Pointer drag-to-pan
- Pinch-to-zoom on touch and pen devices
- Click-to-select with AABB hit testing
- Multi-select: Shift (add), Ctrl/Meta (toggle)
- Drag-to-move selected entities

**Rendering** (via `@floormap/svg` or `@floormap/react`)
**Rendering** (via `@floormap-tools/svg` or `@floormap-tools/react`)
- Bring-your-own-drawEntity callback — full SVG freedom
- World-aligned background grid that stays crisp at any zoom
- Built-in selection highlight overlay with customisable style
- Automatic repaints on viewport, entity, and selection changes

**React adapter** (`@floormap/react`)
**React adapter** (`@floormap-tools/react`)
- `FloormapCanvas` component — drop in an SVG canvas with zero boilerplate
- `useFloormapCore` — lazy engine initialisation, stable across re-renders
- `useSelection` / `useViewport` — fine-grained reactive subscriptions via `useSyncExternalStore`
Expand All @@ -327,14 +327,14 @@ renderer.destroy();
└────────────────┬────────────────────────┘
│ FloormapCore API
┌───────────▼────────────┐
│ @floormap/core │
│ @floormap-tools/core │
│ scene · viewport │
│ events · picking │
│ selection · store │
└─────────┬──────────────┘
│ subscribes to events
┌─────────┴──────────────┬──────────────────────┐
│ @floormap/svg │ @floormap/react │
│ @floormap-tools/svg │ @floormap-tools/react │
│ DOM setup · renderer │ FloormapCanvas │
│ handlers · grid │ useFloormapCore │
│ selection overlay │ useSelection │
Expand All @@ -343,7 +343,7 @@ renderer.destroy();
└────────────────────┘
```

`@floormap/core` is fully framework-agnostic — no DOM, no browser APIs. `@floormap/svg` and `@floormap/react` are independent renderer/adapter layers that connect core to the browser. Use whichever fits your stack.
`@floormap-tools/core` is fully framework-agnostic — no DOM, no browser APIs. `@floormap-tools/svg` and `@floormap-tools/react` are independent renderer/adapter layers that connect core to the browser. Use whichever fits your stack.

---

Expand Down Expand Up @@ -397,7 +397,7 @@ If you find a bug or want to suggest an improvement, please open an **issue**.
For pull requests:
- Keep the PR focused on a single change or feature
- Run `pnpm typecheck && pnpm test && pnpm lint` before submitting
- Do not add production dependencies to `@floormap/core`, `@floormap/svg`, or `@floormap/react`
- Do not add production dependencies to `@floormap-tools/core`, `@floormap-tools/svg`, or `@floormap-tools/react`

---

Expand Down
6 changes: 3 additions & 3 deletions packages/adapters/react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# @floormap/react
# @floormap-tools/react

## 0.1.0

### Minor Changes

- Initial release of @floormap/core, @floormap/svg, and @floormap/react.
- Initial release of @floormap-tools/core, @floormap-tools/svg, and @floormap-tools/react.

### Patch Changes

- Updated dependencies
- @floormap/core@0.1.0
- @floormap-tools/core@0.1.0
16 changes: 8 additions & 8 deletions packages/adapters/react/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# @floormap/react
# @floormap-tools/react

React adapter for [`@floormap/core`](../../core). Provides a drop-in `<FloormapCanvas>` component and hooks for building interactive 2D editors — floor plans, seat maps, office layouts — with zero boilerplate.
React adapter for [`@floormap-tools/core`](../../core). Provides a drop-in `<FloormapCanvas>` component and hooks for building interactive 2D editors — floor plans, seat maps, office layouts — with zero boilerplate.

## Installation

```bash
npm install @floormap/core @floormap/react
npm install @floormap-tools/core @floormap-tools/react
# or
pnpm add @floormap/core @floormap/react
pnpm add @floormap-tools/core @floormap-tools/react
```

React 18 is required as a peer dependency.
Expand All @@ -16,15 +16,15 @@ React 18 is required as a peer dependency.

```tsx
import { useCallback, useEffect } from 'react';
import { createEmptyScene } from '@floormap/core';
import type { Entity, EntityId, LayerId } from '@floormap/core';
import { FloormapCanvas, useFloormapCore, useSelection, useViewport } from '@floormap/react';
import { createEmptyScene } from '@floormap-tools/core';
import type { Entity, EntityId, LayerId } from '@floormap-tools/core';
import { FloormapCanvas, useFloormapCore, useSelection, useViewport } from '@floormap-tools/react';

const LAYER = 'main' as LayerId;

const initialScene = (() => {
const scene = createEmptyScene({ width: 1200, height: 800 }, [LAYER]);
// addEntity is a store helper — import from @floormap/core if needed,
// addEntity is a store helper — import from @floormap-tools/core if needed,
// or use core.add() after the core is created.
return scene;
})();
Expand Down
6 changes: 3 additions & 3 deletions packages/adapters/react/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@floormap/react",
"name": "@floormap-tools/react",
"version": "0.1.0",
"private": false,
"type": "module",
Expand All @@ -26,7 +26,7 @@
"test:watch": "vitest"
},
"devDependencies": {
"@floormap/core": "workspace:*",
"@floormap-tools/core": "workspace:*",
"@testing-library/react": "^16.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
Expand All @@ -36,7 +36,7 @@
"vitest": "^2.0.0"
},
"peerDependencies": {
"@floormap/core": "0.x",
"@floormap-tools/core": "0.x",
"react": "^18.0.0"
}
}
2 changes: 1 addition & 1 deletion packages/adapters/react/src/FloormapCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type CSSProperties,
type ReactNode,
} from 'react';
import type { Entity, EntityId, FloormapCore, LayerId } from '@floormap/core';
import type { Entity, EntityId, FloormapCore, LayerId } from '@floormap-tools/core';
import { attachHandlers } from './internal/attachHandlers';
import { useEntities } from './internal/useEntities';
import { useViewportTransform } from './internal/useViewportTransform';
Expand Down
4 changes: 2 additions & 2 deletions packages/adapters/react/src/__tests__/FloormapCanvas.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeAll } from 'vitest';
import { render, act } from '@testing-library/react';
import { createCore, createEmptyScene } from '@floormap/core';
import type { Entity, EntityId, LayerId } from '@floormap/core';
import { createCore, createEmptyScene } from '@floormap-tools/core';
import type { Entity, EntityId, LayerId } from '@floormap-tools/core';
import { FloormapCanvas } from '../FloormapCanvas';

beforeAll(() => {
Expand Down
4 changes: 2 additions & 2 deletions packages/adapters/react/src/__tests__/hooks.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @vitest-environment jsdom
import { describe, it, expect } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { createCore, createEmptyScene } from '@floormap/core';
import type { EntityId, LayerId } from '@floormap/core';
import { createCore, createEmptyScene } from '@floormap-tools/core';
import type { EntityId, LayerId } from '@floormap-tools/core';
import { useFloormapCore, useSelection, useViewport } from '../index';

function makeCore() {
Expand Down
2 changes: 1 addition & 1 deletion packages/adapters/react/src/hooks/useCoreEvent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useRef } from 'react';
import type { FloormapCore } from '@floormap/core';
import type { FloormapCore } from '@floormap-tools/core';

export function useCoreEvent<T = unknown>(
core: FloormapCore,
Expand Down
4 changes: 2 additions & 2 deletions packages/adapters/react/src/hooks/useFloormapCore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useRef } from 'react';
import { createCore } from '@floormap/core';
import type { FloormapCore, Scene, Viewport } from '@floormap/core';
import { createCore } from '@floormap-tools/core';
import type { FloormapCore, Scene, Viewport } from '@floormap-tools/core';

export interface FloormapCoreOptions {
scene: Scene;
Expand Down
2 changes: 1 addition & 1 deletion packages/adapters/react/src/hooks/useSelection.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useRef, useSyncExternalStore } from 'react';
import type { EntityId, FloormapCore } from '@floormap/core';
import type { EntityId, FloormapCore } from '@floormap-tools/core';

export function useSelection(core: FloormapCore): ReadonlySet<EntityId> {
const snapshotRef = useRef<ReadonlySet<EntityId>>(new Set(core.selection));
Expand Down
2 changes: 1 addition & 1 deletion packages/adapters/react/src/hooks/useViewport.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useRef, useSyncExternalStore } from 'react';
import type { FloormapCore, Viewport } from '@floormap/core';
import type { FloormapCore, Viewport } from '@floormap-tools/core';

export function useViewport(core: FloormapCore): Viewport {
const snapshotRef = useRef<Viewport>({ ...core.viewport, pan: { ...core.viewport.pan } });
Expand Down
2 changes: 1 addition & 1 deletion packages/adapters/react/src/internal/GridLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { memo, useEffect, useRef } from 'react';
import type { FloormapCore, Viewport } from '@floormap/core';
import type { FloormapCore, Viewport } from '@floormap-tools/core';
import type { GridOptions } from '../types';

interface GridLayerProps {
Expand Down
2 changes: 1 addition & 1 deletion packages/adapters/react/src/internal/SelectionOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { memo } from 'react';
import type { Entity, EntityId } from '@floormap/core';
import type { Entity, EntityId } from '@floormap-tools/core';
import type { SelectionOverlayStyle } from '../types';

interface SelectionOverlayProps {
Expand Down
2 changes: 1 addition & 1 deletion packages/adapters/react/src/internal/attachHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FloormapCore, EntityId, Bounds } from '@floormap/core';
import type { FloormapCore, EntityId, Bounds } from '@floormap-tools/core';

export type AttachHandlersOptions = {
enableWheel: boolean;
Expand Down
2 changes: 1 addition & 1 deletion packages/adapters/react/src/internal/useEntities.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useRef, useSyncExternalStore } from 'react';
import type { Entity, FloormapCore } from '@floormap/core';
import type { Entity, FloormapCore } from '@floormap-tools/core';

export function useEntities(core: FloormapCore): Entity[] {
const snapshotRef = useRef<Entity[]>([...core.scene.entities.values()]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type RefObject, useEffect } from 'react';
import type { FloormapCore } from '@floormap/core';
import type { FloormapCore } from '@floormap-tools/core';

export function useViewportTransform(
core: FloormapCore,
Expand Down
2 changes: 1 addition & 1 deletion packages/adapters/react/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const __dirname = fileURLToPath(new URL('.', import.meta.url));
export default defineConfig({
resolve: {
alias: {
'@floormap/core': resolve(__dirname, '../../core/src/index.ts'),
'@floormap-tools/core': resolve(__dirname, '../../core/src/index.ts'),
},
},
test: {
Expand Down
Loading
Loading