@fieldnotes/core 0.1.0
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 +273 -0
- package/dist/index.cjs +2247 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +582 -0
- package/dist/index.d.ts +582 -0
- package/dist/index.js +2184 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# @fieldnotes/core
|
|
2
|
+
|
|
3
|
+
A lightweight, framework-agnostic infinite canvas SDK for the web — with first-class support for embedding interactive HTML elements.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Infinite canvas** — pan, zoom, pinch-to-zoom
|
|
8
|
+
- **Freehand drawing** — pencil tool with pressure support (Apple Pencil, Surface Pen)
|
|
9
|
+
- **Sticky notes** — editable text notes with customizable colors
|
|
10
|
+
- **Arrows** — curved bezier arrows with draggable control points
|
|
11
|
+
- **Images** — drag & drop or programmatic placement
|
|
12
|
+
- **HTML embedding** — add any DOM element as a fully interactive canvas citizen
|
|
13
|
+
- **Select & multi-select** — click, drag box, move, resize
|
|
14
|
+
- **Undo / redo** — full history stack with configurable depth
|
|
15
|
+
- **State serialization** — export/import JSON snapshots
|
|
16
|
+
- **Touch & tablet** — Pointer Events API, pinch-to-zoom, two-finger pan, stylus pressure
|
|
17
|
+
- **Zero dependencies** — vanilla TypeScript, no framework required
|
|
18
|
+
- **Tree-shakeable** — ESM + CJS output
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @fieldnotes/core
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import {
|
|
30
|
+
Viewport,
|
|
31
|
+
HandTool,
|
|
32
|
+
SelectTool,
|
|
33
|
+
PencilTool,
|
|
34
|
+
EraserTool,
|
|
35
|
+
ArrowTool,
|
|
36
|
+
NoteTool,
|
|
37
|
+
} from '@fieldnotes/core';
|
|
38
|
+
|
|
39
|
+
// Mount on any container element
|
|
40
|
+
const viewport = new Viewport(document.getElementById('canvas'), {
|
|
41
|
+
background: { pattern: 'dots', spacing: 24 },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Register tools
|
|
45
|
+
viewport.toolManager.register(new HandTool());
|
|
46
|
+
viewport.toolManager.register(new SelectTool());
|
|
47
|
+
viewport.toolManager.register(new PencilTool({ color: '#1a1a1a', width: 2 }));
|
|
48
|
+
viewport.toolManager.register(new EraserTool());
|
|
49
|
+
viewport.toolManager.register(new ArrowTool({ color: '#1a1a1a', width: 2 }));
|
|
50
|
+
viewport.toolManager.register(new NoteTool());
|
|
51
|
+
|
|
52
|
+
// Activate a tool
|
|
53
|
+
viewport.toolManager.setTool('select', viewport.toolContext);
|
|
54
|
+
|
|
55
|
+
// Clean up when done
|
|
56
|
+
viewport.destroy();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Your container element needs a defined size (width/height). The canvas fills its container.
|
|
60
|
+
|
|
61
|
+
## Embedding HTML Elements
|
|
62
|
+
|
|
63
|
+
The main differentiator — embed any DOM node as a fully interactive canvas element:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const card = document.createElement('div');
|
|
67
|
+
card.innerHTML = '<h3>My Card</h3><button>Click me</button>';
|
|
68
|
+
|
|
69
|
+
// Buttons, inputs, links — everything works
|
|
70
|
+
card.querySelector('button').addEventListener('click', () => {
|
|
71
|
+
console.log('Clicked inside the canvas!');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const elementId = viewport.addHtmlElement(card, { x: 100, y: 200 }, { w: 250, h: 150 });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
HTML elements pan, zoom, and resize with the canvas while remaining fully interactive.
|
|
78
|
+
|
|
79
|
+
## Adding Images
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Programmatic
|
|
83
|
+
viewport.addImage('https://example.com/photo.jpg', { x: 0, y: 0 });
|
|
84
|
+
|
|
85
|
+
// Drag & drop is handled automatically — drop images onto the canvas
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Camera Control
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
const { camera } = viewport;
|
|
92
|
+
|
|
93
|
+
camera.pan(100, 50); // pan by offset
|
|
94
|
+
camera.moveTo(0, 0); // jump to position
|
|
95
|
+
camera.setZoom(2); // set zoom level
|
|
96
|
+
camera.zoomAt(1.5, { x: 400, y: 300 }); // zoom toward screen point
|
|
97
|
+
|
|
98
|
+
const world = camera.screenToWorld({ x: e.clientX, y: e.clientY });
|
|
99
|
+
const screen = camera.worldToScreen({ x: 0, y: 0 });
|
|
100
|
+
|
|
101
|
+
camera.onChange(() => {
|
|
102
|
+
/* camera moved */
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Element Store
|
|
107
|
+
|
|
108
|
+
Direct access to canvas elements:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const { store } = viewport;
|
|
112
|
+
|
|
113
|
+
const all = store.getAll(); // sorted by zIndex
|
|
114
|
+
const el = store.getById('some-id');
|
|
115
|
+
const strokes = store.getElementsByType('stroke');
|
|
116
|
+
|
|
117
|
+
store.update('some-id', { locked: true });
|
|
118
|
+
store.remove('some-id');
|
|
119
|
+
|
|
120
|
+
store.on('add', (el) => console.log('added', el));
|
|
121
|
+
store.on('remove', (el) => console.log('removed', el));
|
|
122
|
+
store.on('update', ({ previous, current }) => {
|
|
123
|
+
/* ... */
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Undo / Redo
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
viewport.undo();
|
|
131
|
+
viewport.redo();
|
|
132
|
+
|
|
133
|
+
viewport.history.canUndo; // boolean
|
|
134
|
+
viewport.history.canRedo; // boolean
|
|
135
|
+
viewport.history.onChange(() => {
|
|
136
|
+
/* update UI */
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## State Serialization
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Save
|
|
144
|
+
const json = viewport.exportJSON();
|
|
145
|
+
localStorage.setItem('canvas', json);
|
|
146
|
+
|
|
147
|
+
// Load
|
|
148
|
+
viewport.loadJSON(localStorage.getItem('canvas'));
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Tool Switching
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
viewport.toolManager.setTool('pencil', viewport.toolContext);
|
|
155
|
+
viewport.toolManager.setTool('hand', viewport.toolContext);
|
|
156
|
+
|
|
157
|
+
viewport.toolManager.onChange((toolName) => {
|
|
158
|
+
console.log('switched to', toolName);
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Custom Tools
|
|
163
|
+
|
|
164
|
+
Implement the `Tool` interface to create your own tools:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import type { Tool, ToolContext, PointerState } from '@fieldnotes/core';
|
|
168
|
+
|
|
169
|
+
const myTool: Tool = {
|
|
170
|
+
name: 'my-tool',
|
|
171
|
+
|
|
172
|
+
onPointerDown(state: PointerState, ctx: ToolContext) {
|
|
173
|
+
const world = ctx.camera.screenToWorld({ x: state.x, y: state.y });
|
|
174
|
+
// state.pressure is available for stylus input (0-1)
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
onPointerMove(state: PointerState, ctx: ToolContext) {
|
|
178
|
+
// called during drag
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
onPointerUp(state: PointerState, ctx: ToolContext) {
|
|
182
|
+
// finalize action
|
|
183
|
+
ctx.store.add(myElement);
|
|
184
|
+
ctx.requestRender();
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// Optional
|
|
188
|
+
onActivate(ctx) {
|
|
189
|
+
ctx.setCursor?.('crosshair');
|
|
190
|
+
},
|
|
191
|
+
onDeactivate(ctx) {
|
|
192
|
+
ctx.setCursor?.('default');
|
|
193
|
+
},
|
|
194
|
+
renderOverlay(canvasCtx) {
|
|
195
|
+
/* draw preview on canvas */
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
viewport.toolManager.register(myTool);
|
|
200
|
+
viewport.toolManager.setTool('my-tool', viewport.toolContext);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Configuration
|
|
204
|
+
|
|
205
|
+
### Viewport Options
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
new Viewport(container, {
|
|
209
|
+
camera: {
|
|
210
|
+
minZoom: 0.1, // default: 0.1
|
|
211
|
+
maxZoom: 10, // default: 10
|
|
212
|
+
},
|
|
213
|
+
background: {
|
|
214
|
+
pattern: 'dots', // 'dots' | 'grid' | 'none' (default: 'dots')
|
|
215
|
+
spacing: 24, // grid spacing in px (default: 24)
|
|
216
|
+
color: '#d0d0d0', // dot/line color (default: '#d0d0d0')
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Tool Options
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
new PencilTool({ color: '#ff0000', width: 3 });
|
|
225
|
+
new EraserTool({ radius: 30 });
|
|
226
|
+
new ArrowTool({ color: '#333', width: 2 });
|
|
227
|
+
new NoteTool({ backgroundColor: '#fff9c4', size: { w: 200, h: 150 } });
|
|
228
|
+
new ImageTool({ size: { w: 400, h: 300 } });
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Element Types
|
|
232
|
+
|
|
233
|
+
All elements share a base shape:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
interface BaseElement {
|
|
237
|
+
id: string;
|
|
238
|
+
type: string;
|
|
239
|
+
position: { x: number; y: number };
|
|
240
|
+
zIndex: number;
|
|
241
|
+
locked: boolean;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
| Type | Key Fields |
|
|
246
|
+
| -------- | -------------------------------------- |
|
|
247
|
+
| `stroke` | `points`, `color`, `width`, `opacity` |
|
|
248
|
+
| `note` | `size`, `text`, `backgroundColor` |
|
|
249
|
+
| `arrow` | `from`, `to`, `bend`, `color`, `width` |
|
|
250
|
+
| `image` | `size`, `src` |
|
|
251
|
+
| `html` | `size` |
|
|
252
|
+
|
|
253
|
+
## Built-in Interactions
|
|
254
|
+
|
|
255
|
+
| Input | Action |
|
|
256
|
+
| -------------------- | --------------- |
|
|
257
|
+
| Scroll wheel | Zoom |
|
|
258
|
+
| Middle-click drag | Pan |
|
|
259
|
+
| Space + drag | Pan |
|
|
260
|
+
| Two-finger pinch | Zoom |
|
|
261
|
+
| Two-finger drag | Pan |
|
|
262
|
+
| Delete / Backspace | Remove selected |
|
|
263
|
+
| Ctrl+Z / Cmd+Z | Undo |
|
|
264
|
+
| Ctrl+Shift+Z / Cmd+Y | Redo |
|
|
265
|
+
| Double-click note | Edit text |
|
|
266
|
+
|
|
267
|
+
## Browser Support
|
|
268
|
+
|
|
269
|
+
Works in all modern browsers supporting Pointer Events API and HTML5 Canvas.
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
|
|
273
|
+
MIT
|