@bwp-web/canvas 1.0.0 → 1.1.1
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 +307 -0
- package/dist/hooks/useEditCanvas.d.ts +2 -2
- package/dist/hooks/useViewCanvas.d.ts +2 -2
- package/dist/index.cjs +199 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +204 -92
- package/dist/index.js.map +1 -1
- package/dist/overlay/FixedSizeContent.d.ts.map +1 -1
- package/dist/overlay/ObjectOverlay.d.ts +4 -0
- package/dist/overlay/ObjectOverlay.d.ts.map +1 -1
- package/dist/overlay/OverlayBadge.d.ts +6 -6
- package/dist/overlay/OverlayBadge.d.ts.map +1 -1
- package/dist/overlay/OverlayContainer.d.ts +43 -0
- package/dist/overlay/OverlayContainer.d.ts.map +1 -0
- package/dist/overlay/OverlayContent.d.ts +2 -2
- package/dist/overlay/OverlayContent.d.ts.map +1 -1
- package/dist/overlay/index.d.ts +2 -0
- package/dist/overlay/index.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# @bwp-web/canvas
|
|
2
|
+
|
|
3
|
+
Interactive canvas editor and viewer for Biamp Workplace applications. Built on Fabric.js with React hooks, it provides shape creation, selection, pan/zoom, alignment guides, serialization, and DOM overlays out of the box.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @bwp-web/canvas
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Peer Dependencies
|
|
12
|
+
|
|
13
|
+
- `react` >= 18.0.0
|
|
14
|
+
- `react-dom` >= 18.0.0
|
|
15
|
+
- `@mui/material` >= 7.0.0
|
|
16
|
+
- `@bwp-web/styles` >= 1.0.1
|
|
17
|
+
- `fabric` >= 7.2.0
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Edit Canvas
|
|
22
|
+
|
|
23
|
+
Full editing with shape creation, selection, pan/zoom, alignment, and serialization:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import {
|
|
27
|
+
Canvas,
|
|
28
|
+
useEditCanvas,
|
|
29
|
+
enableDragToCreate,
|
|
30
|
+
createRectangle,
|
|
31
|
+
} from '@bwp-web/canvas';
|
|
32
|
+
|
|
33
|
+
function Editor() {
|
|
34
|
+
const canvas = useEditCanvas();
|
|
35
|
+
|
|
36
|
+
const startDragMode = () => {
|
|
37
|
+
canvas.setMode((c, viewport) =>
|
|
38
|
+
enableDragToCreate(
|
|
39
|
+
c,
|
|
40
|
+
(c, bounds) =>
|
|
41
|
+
createRectangle(c, {
|
|
42
|
+
left: bounds.startX + bounds.width / 2,
|
|
43
|
+
top: bounds.startY + bounds.height / 2,
|
|
44
|
+
width: bounds.width,
|
|
45
|
+
height: bounds.height,
|
|
46
|
+
}),
|
|
47
|
+
{ onCreated: () => canvas.setMode(null), viewport },
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div>
|
|
54
|
+
<button onClick={startDragMode}>Draw Rectangle</button>
|
|
55
|
+
<Canvas onReady={canvas.onReady} width={800} height={600} />
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### View Canvas
|
|
62
|
+
|
|
63
|
+
Read-only display with pan/zoom and object styling:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { Canvas, useViewCanvas, loadCanvas } from '@bwp-web/canvas';
|
|
67
|
+
|
|
68
|
+
function Viewer({ savedJson }: { savedJson: object }) {
|
|
69
|
+
const canvas = useViewCanvas({
|
|
70
|
+
onReady: (c) => loadCanvas(c, savedJson),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return <Canvas onReady={canvas.onReady} width={800} height={600} />;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Hooks
|
|
78
|
+
|
|
79
|
+
### `useEditCanvas(options?)`
|
|
80
|
+
|
|
81
|
+
Full-featured editing hook with shape creation, selection, pan/zoom, alignment, vertex editing, keyboard shortcuts, and serialization.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
const canvas = useEditCanvas({
|
|
85
|
+
canvasData: savedJson, // auto-load canvas data
|
|
86
|
+
filter: (obj) => ids.includes(obj.data?.id),
|
|
87
|
+
invertBackground: isDarkMode, // reactive background inversion
|
|
88
|
+
enableAlignment: true, // object alignment guides
|
|
89
|
+
scaledStrokes: true, // zoom-independent stroke widths
|
|
90
|
+
keyboardShortcuts: true, // Delete/Backspace to remove selected
|
|
91
|
+
vertexEdit: true, // double-click polygon to edit vertices
|
|
92
|
+
panAndZoom: true, // scroll to zoom, Cmd/Ctrl+drag to pan
|
|
93
|
+
rotationSnap: { interval: 15 }, // Shift+rotate snaps to 15°
|
|
94
|
+
autoFitToBackground: true, // auto-fit viewport to background image
|
|
95
|
+
backgroundResize: true, // auto-downscale large uploaded images
|
|
96
|
+
trackChanges: true, // expose isDirty / resetDirty
|
|
97
|
+
history: true, // undo/redo support
|
|
98
|
+
onReady: (canvas) => {}, // called after canvasData load + features init
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### Return Value
|
|
103
|
+
|
|
104
|
+
| Property | Type | Description |
|
|
105
|
+
| ---------------------- | ------------------------------ | ---------------------------------------------------------- |
|
|
106
|
+
| `onReady` | `(canvas) => void` | Pass to `<Canvas onReady={...}>` |
|
|
107
|
+
| `canvasRef` | `RefObject<FabricCanvas>` | Direct access to the Fabric canvas |
|
|
108
|
+
| `zoom` | `number` | Current zoom level (reactive) |
|
|
109
|
+
| `objects` | `FabricObject[]` | Canvas objects (reactive, kept in sync) |
|
|
110
|
+
| `isLoading` | `boolean` | Whether canvas data is currently being loaded |
|
|
111
|
+
| `selected` | `FabricObject[]` | Currently selected objects (reactive) |
|
|
112
|
+
| `setMode` | `(setup \| null) => void` | Activate or deactivate an interaction mode |
|
|
113
|
+
| `setBackground` | `(url, opts?) => Promise<...>` | Load a background image |
|
|
114
|
+
| `isDirty` | `boolean` | Whether canvas has been modified since last `resetDirty()` |
|
|
115
|
+
| `resetDirty` | `() => void` | Reset the dirty flag after a successful save |
|
|
116
|
+
| `markDirty` | `() => void` | Manually mark the canvas as dirty |
|
|
117
|
+
| `undo` | `() => Promise<void>` | Undo last change (requires `history: true`) |
|
|
118
|
+
| `redo` | `() => Promise<void>` | Redo previously undone change (requires `history: true`) |
|
|
119
|
+
| `canUndo` | `boolean` | Whether undo is available (reactive) |
|
|
120
|
+
| `canRedo` | `boolean` | Whether redo is available (reactive) |
|
|
121
|
+
| `viewport.zoomIn` | `(step?) => void` | Zoom in toward center |
|
|
122
|
+
| `viewport.zoomOut` | `(step?) => void` | Zoom out from center |
|
|
123
|
+
| `viewport.reset` | `() => void` | Reset viewport |
|
|
124
|
+
| `viewport.panToObject` | `(object, options?) => void` | Pan viewport to center on an object |
|
|
125
|
+
| `viewport.zoomToFit` | `(object, options?) => void` | Zoom and pan to fit a specific object |
|
|
126
|
+
|
|
127
|
+
### `useViewCanvas(options?)`
|
|
128
|
+
|
|
129
|
+
Read-only hook. Objects cannot be selected, created, or edited. The canvas is always in pan mode.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
const canvas = useViewCanvas({
|
|
133
|
+
canvasData: savedJson,
|
|
134
|
+
filter: (obj) => objectIds.includes(obj.data?.id),
|
|
135
|
+
invertBackground: isDarkMode,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Style objects dynamically
|
|
139
|
+
canvas.setObjectStyle('room-42', { fill: '#ff0000' });
|
|
140
|
+
canvas.setObjectStyles({
|
|
141
|
+
'room-42': { fill: '#ff0000' },
|
|
142
|
+
'room-43': { opacity: 0.5 },
|
|
143
|
+
});
|
|
144
|
+
canvas.setObjectStyleByType('DESK', { fill: '#cccccc' });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Context Providers
|
|
148
|
+
|
|
149
|
+
When multiple sibling components need access to canvas state, use context providers instead of hooks directly. They expose the full API via React context with no prop drilling.
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
import {
|
|
153
|
+
EditCanvasProvider,
|
|
154
|
+
useEditCanvasContext,
|
|
155
|
+
useEditCanvasState,
|
|
156
|
+
useEditCanvasViewport,
|
|
157
|
+
Canvas,
|
|
158
|
+
} from '@bwp-web/canvas';
|
|
159
|
+
|
|
160
|
+
function App() {
|
|
161
|
+
return (
|
|
162
|
+
<EditCanvasProvider options={{ canvasData: savedJson, history: true }}>
|
|
163
|
+
<MyCanvas />
|
|
164
|
+
<SaveButton />
|
|
165
|
+
<ZoomDisplay />
|
|
166
|
+
</EditCanvasProvider>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function MyCanvas() {
|
|
171
|
+
const { onReady } = useEditCanvasContext();
|
|
172
|
+
return <Canvas onReady={onReady} />;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function SaveButton() {
|
|
176
|
+
// Does NOT re-render on zoom/scroll
|
|
177
|
+
const { isDirty, resetDirty } = useEditCanvasState();
|
|
178
|
+
return (
|
|
179
|
+
<button disabled={!isDirty} onClick={resetDirty}>
|
|
180
|
+
Save
|
|
181
|
+
</button>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function ZoomDisplay() {
|
|
186
|
+
// Does NOT re-render on selection/dirty changes
|
|
187
|
+
const { zoom } = useEditCanvasViewport();
|
|
188
|
+
return <span>{Math.round(zoom * 100)}%</span>;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`ViewCanvasProvider` / `useViewCanvasContext` follow the same pattern for view-mode canvases.
|
|
193
|
+
|
|
194
|
+
## `<Canvas>` Component
|
|
195
|
+
|
|
196
|
+
A thin React wrapper around a Fabric.js canvas. By default it fills its parent container and resizes automatically.
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
{
|
|
200
|
+
/* Auto-fill mode (default) */
|
|
201
|
+
}
|
|
202
|
+
<div style={{ width: 800, height: 600 }}>
|
|
203
|
+
<Canvas onReady={canvas.onReady} />
|
|
204
|
+
</div>;
|
|
205
|
+
|
|
206
|
+
{
|
|
207
|
+
/* Fixed-size mode */
|
|
208
|
+
}
|
|
209
|
+
<Canvas onReady={canvas.onReady} width={800} height={600} />;
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Shapes
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
import { createRectangle, createCircle, createPolygon } from '@bwp-web/canvas';
|
|
216
|
+
|
|
217
|
+
createRectangle(canvas, { left: 100, top: 100, width: 200, height: 150 });
|
|
218
|
+
createCircle(canvas, { left: 200, top: 200, radius: 50 });
|
|
219
|
+
createPolygon(canvas, {
|
|
220
|
+
points: [
|
|
221
|
+
{ x: 0, y: 0 },
|
|
222
|
+
{ x: 100, y: 0 },
|
|
223
|
+
{ x: 50, y: 100 },
|
|
224
|
+
],
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Interaction Modes
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import {
|
|
232
|
+
enableClickToCreate,
|
|
233
|
+
enableDragToCreate,
|
|
234
|
+
enableDrawToCreate,
|
|
235
|
+
enableVertexEdit,
|
|
236
|
+
} from '@bwp-web/canvas';
|
|
237
|
+
|
|
238
|
+
// Single click to create a shape
|
|
239
|
+
canvas.setMode((c) => enableClickToCreate(c, factory, options));
|
|
240
|
+
|
|
241
|
+
// Drag to define bounds
|
|
242
|
+
canvas.setMode((c, viewport) => enableDragToCreate(c, factory, { viewport }));
|
|
243
|
+
|
|
244
|
+
// Vertex-by-vertex polygon drawing (click to add points, double-click to finish)
|
|
245
|
+
canvas.setMode((c, viewport) => enableDrawToCreate(c, factory, { viewport }));
|
|
246
|
+
|
|
247
|
+
// Edit polygon vertices (double-click a polygon to activate)
|
|
248
|
+
canvas.setMode((c) => enableVertexEdit(c, options));
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Serialization
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
import { serializeCanvas, loadCanvas } from '@bwp-web/canvas';
|
|
255
|
+
|
|
256
|
+
// Save
|
|
257
|
+
const json = serializeCanvas(canvas);
|
|
258
|
+
|
|
259
|
+
// Load
|
|
260
|
+
await loadCanvas(canvas, json);
|
|
261
|
+
|
|
262
|
+
// Load with object filter
|
|
263
|
+
await loadCanvas(canvas, json, { filter: (obj) => obj.data?.type === 'DESK' });
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Utility Hooks
|
|
267
|
+
|
|
268
|
+
| Hook | Description |
|
|
269
|
+
| ----------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
270
|
+
| `useCanvasEvents(events)` | Subscribe to Fabric canvas events with automatic cleanup |
|
|
271
|
+
| `useCanvasTooltip({ getContent })` | Track hover over canvas objects, returns `{ visible, content, position, ref }` |
|
|
272
|
+
| `useCanvasClick(onClick, options?)` | Distinguish clicks from pan gestures; fires only on genuine clicks |
|
|
273
|
+
| `useObjectOverlay(canvasRef, object, options?)` | Position a DOM element over a Fabric object, kept in sync with pan/zoom/transform |
|
|
274
|
+
|
|
275
|
+
When used inside a provider, all utility hooks read `canvasRef` from context automatically — no need to pass it explicitly.
|
|
276
|
+
|
|
277
|
+
## API Reference
|
|
278
|
+
|
|
279
|
+
| Module | Contents |
|
|
280
|
+
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
281
|
+
| Hooks | `useEditCanvas`, `useViewCanvas`, `Canvas`, `useCanvasEvents`, `useCanvasTooltip`, `useCanvasClick` |
|
|
282
|
+
| Context | `EditCanvasProvider`, `ViewCanvasProvider`, `useEditCanvasContext`, `useViewCanvasContext` |
|
|
283
|
+
| Shapes | `createRectangle`, `createCircle`, `createPolygon` and point/drag variants |
|
|
284
|
+
| Interactions | `enableClickToCreate`, `enableDragToCreate`, `enableDrawToCreate`, `enableVertexEdit` |
|
|
285
|
+
| Viewport | `enablePanAndZoom`, `resetViewport`, `ViewportController` |
|
|
286
|
+
| Alignment | `enableObjectAlignment`, `snapCursorPoint`, `enableRotationSnap` |
|
|
287
|
+
| Serialization | `serializeCanvas`, `loadCanvas`, `enableScaledStrokes` |
|
|
288
|
+
| Background | `setBackgroundImage`, `fitViewportToBackground`, `getBackgroundSrc`, `setBackgroundContrast`, `setBackgroundInverted`, `resizeImageUrl` |
|
|
289
|
+
| Keyboard | `enableKeyboardShortcuts`, `deleteObjects` |
|
|
290
|
+
| Overlay | `OverlayContainer`, `ObjectOverlay`, `OverlayContent`, `FixedSizeContent`, `OverlayBadge` |
|
|
291
|
+
|
|
292
|
+
## Full Documentation
|
|
293
|
+
|
|
294
|
+
Detailed reference docs are available in the repository's [`/docs/canvas`](../../docs/canvas) folder (GitHub links):
|
|
295
|
+
|
|
296
|
+
| Document | Contents |
|
|
297
|
+
| ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
298
|
+
| [hooks.md](../../docs/canvas/hooks.md) | `useEditCanvas`, `useViewCanvas`, context providers, utility hooks — full options and return value tables |
|
|
299
|
+
| [shapes.md](../../docs/canvas/shapes.md) | `createRectangle`, `createCircle`, `createPolygon` and all point/drag variants |
|
|
300
|
+
| [interactions.md](../../docs/canvas/interactions.md) | `enableClickToCreate`, `enableDragToCreate`, `enableDrawToCreate`, `enableVertexEdit` — all options |
|
|
301
|
+
| [viewport.md](../../docs/canvas/viewport.md) | `enablePanAndZoom`, `resetViewport`, `ViewportController` — all methods and options |
|
|
302
|
+
| [alignment.md](../../docs/canvas/alignment.md) | Object alignment guides, cursor snapping, rotation snapping, snap point extractors |
|
|
303
|
+
| [serialization.md](../../docs/canvas/serialization.md) | `serializeCanvas`, `loadCanvas`, scaled strokes, scaled border radius |
|
|
304
|
+
| [background.md](../../docs/canvas/background.md) | `setBackgroundImage`, contrast, invert, resize — all options |
|
|
305
|
+
| [keyboard.md](../../docs/canvas/keyboard.md) | `enableKeyboardShortcuts`, `deleteObjects` |
|
|
306
|
+
| [styles.md](../../docs/canvas/styles.md) | Default style objects, configuration constants, Fabric type augmentation |
|
|
307
|
+
| [overlay.md](../../docs/canvas/overlay.md) | `OverlayContainer`, `ObjectOverlay`, `OverlayContent`, `FixedSizeContent`, `OverlayBadge` — full API |
|
|
@@ -139,9 +139,9 @@ export declare function useEditCanvas(options?: UseEditCanvasOptions): {
|
|
|
139
139
|
/** Zoom out from the canvas center. Default step: 0.2. */
|
|
140
140
|
zoomOut: (step?: number) => void;
|
|
141
141
|
/** Pan the viewport to center on a specific object. */
|
|
142
|
-
panToObject: (object: FabricObject, panOpts?: import("
|
|
142
|
+
panToObject: (object: FabricObject, panOpts?: import("..").PanToObjectOptions) => void;
|
|
143
143
|
/** Zoom and pan to fit a specific object in the viewport. */
|
|
144
|
-
zoomToFit: (object: FabricObject, fitOpts?: import("
|
|
144
|
+
zoomToFit: (object: FabricObject, fitOpts?: import("..").ZoomToFitOptions) => void;
|
|
145
145
|
};
|
|
146
146
|
/** Whether vertex edit mode is currently active (reactive). */
|
|
147
147
|
isEditingVertices: boolean;
|
|
@@ -84,9 +84,9 @@ export declare function useViewCanvas(options?: UseViewCanvasOptions): {
|
|
|
84
84
|
/** Zoom out from the canvas center. Default step: 0.2. */
|
|
85
85
|
zoomOut: (step?: number) => void;
|
|
86
86
|
/** Pan the viewport to center on a specific object. */
|
|
87
|
-
panToObject: (object: FabricObject, panOpts?: import("
|
|
87
|
+
panToObject: (object: FabricObject, panOpts?: import("..").PanToObjectOptions) => void;
|
|
88
88
|
/** Zoom and pan to fit a specific object in the viewport. */
|
|
89
|
-
zoomToFit: (object: FabricObject, fitOpts?: import("
|
|
89
|
+
zoomToFit: (object: FabricObject, fitOpts?: import("..").ZoomToFitOptions) => void;
|
|
90
90
|
};
|
|
91
91
|
/** Update a single object's visual style by its `data.id`. */
|
|
92
92
|
setObjectStyle: (id: string, style: ViewObjectStyle) => void;
|