f1ow 0.1.3 → 0.1.5

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 CHANGED
@@ -30,6 +30,8 @@
30
30
  - **Undo / Redo** — 100-step history snapshot system.
31
31
  - **Export** — Export canvas to PNG, SVG, or JSON.
32
32
  - **Real-Time Collaboration** — Optional CRDT via Yjs (experimental) with cursor presence.
33
+ - **Plugin / Extension System** — Register custom element types with per-type validation and default values.
34
+ - **Element Validation** — Every mutation path (add, update, import) is validated; invalid elements are rejected gracefully.
33
35
  - **Fully Themeable** — Dark mode, custom colors, all via props.
34
36
  - **Zero CSS Dependencies** — No external stylesheets required. Inline styled.
35
37
  - **TypeScript** — Full type safety with strict mode.
@@ -37,14 +39,25 @@
37
39
  ## 📦 Installation
38
40
 
39
41
  ```bash
40
- npm install f1ow
41
- # or
42
- pnpm add f1ow
43
- # or
44
- yarn add f1ow
42
+ # npm
43
+ npm install f1ow konva react-konva zustand
44
+
45
+ # pnpm
46
+ pnpm add f1ow konva react-konva zustand
47
+
48
+ # yarn
49
+ yarn add f1ow konva react-konva zustand
45
50
  ```
46
51
 
47
- > **Peer dependencies:** `react` (≥17), `react-dom` (≥17), `konva` (≥9), `react-konva` (≥18), `zustand` (≥5)
52
+ > `react` and `react-dom` are assumed to already be in your project. If not, add them too:
53
+ > ```bash
54
+ > npm install react react-dom
55
+ > ```
56
+
57
+ > **Optional — Collaboration only:** install these when using the `collaboration` prop:
58
+ > ```bash
59
+ > npm install yjs y-websocket
60
+ > ```
48
61
 
49
62
  ### Next.js / Non-Vite Bundlers
50
63
 
@@ -98,6 +111,7 @@ That's it — you get a full-featured canvas editor with a toolbar, style panel,
98
111
  | `width` / `height` | `number \| string` | `'100%'` | Canvas dimensions |
99
112
  | `tools` | `ToolType[]` | all | Visible tools in toolbar |
100
113
  | `defaultTool` | `ToolType` | `'select'` | Default active tool on mount |
114
+ | `defaultStyle` | `Partial<ElementStyle>` | — | Default drawing style for new elements |
101
115
  | `toolbarPosition` | `'top' \| 'bottom' \| 'hidden'` | `'bottom'` | Position of the main toolbar |
102
116
  | `showToolbar` | `boolean` | `true` | Show toolbar (legacy, use `toolbarPosition`) |
103
117
  | `showStylePanel` | `boolean` | `true` | Show style panel |
@@ -109,6 +123,7 @@ That's it — you get a full-featured canvas editor with a toolbar, style panel,
109
123
  | `className` | `string` | — | Root container CSS class |
110
124
  | `contextMenuItems` | `ContextMenuItem[]` or `(ctx) => ContextMenuItem[]` | — | Extra context menu items |
111
125
  | `renderContextMenu` | `(ctx) => ReactNode` | — | Replace built-in context menu |
126
+ | `customElementTypes` | `CustomElementConfig[]` | — | Register custom element types ([docs](#-custom-element-types--plugins)) |
112
127
  | `collaboration` | `CollaborationConfig` | — | Enable real-time collaboration |
113
128
  | `workerConfig` | `{ elbowWorkerUrl?: string, exportWorkerUrl?: string, disabled?: boolean }` | — | Worker URLs for Next.js ([docs](docs/NEXTJS_INTEGRATION.md)) |
114
129
 
@@ -135,6 +150,7 @@ const ref = useRef<FlowCanvasRef>(null);
135
150
  | `setSelectedIds(ids)` | — | Set selection |
136
151
  | `clearSelection()` | — | Deselect all |
137
152
  | `setActiveTool(tool)` | — | Switch tool |
153
+ | `getActiveTool()` | `ToolType` | Get current active tool |
138
154
  | `undo()` / `redo()` | — | History navigation |
139
155
  | `zoomTo(scale)` | — | Set zoom level |
140
156
  | `resetView()` | — | Reset pan & zoom |
@@ -199,12 +215,23 @@ Append custom items or fully replace the built-in menu:
199
215
 
200
216
  ## 🤝 Collaboration (Experimental)
201
217
 
218
+ First install the optional peer dependencies:
219
+
220
+ ```bash
221
+ npm install yjs y-websocket
222
+ ```
223
+
224
+ Then pass a `CollaborationConfig` to the `collaboration` prop:
225
+
202
226
  ```tsx
203
227
  <FlowCanvas
204
228
  collaboration={{
205
- roomId: "my-room",
206
- wsUrl: "wss://my-yjs-server.example.com",
229
+ serverUrl: "wss://my-yjs-server.example.com",
230
+ roomName: "my-room",
207
231
  user: { id: "user-1", name: "Alice", color: "#e03131" },
232
+ // authToken: "...", // optional auth token
233
+ // syncDebounceMs: 50, // local→remote debounce (ms)
234
+ // awarenessThrottleMs: 100 // cursor sharing throttle (ms)
208
235
  }}
209
236
  />
210
237
  ```
@@ -213,7 +240,7 @@ Provides CRDT-based real-time sync with cursor presence overlay. Requires a [Yjs
213
240
 
214
241
  ## 🧩 Element Types
215
242
 
216
- `CanvasElement` is a discriminated union of 8 types:
243
+ `CanvasElement` is a discriminated union of 8 built-in types:
217
244
 
218
245
  - **Shapes** — `rectangle`, `ellipse`, `diamond`
219
246
  - **Connectors** — `line`, `arrow` (with bindings, routing, arrowheads)
@@ -221,8 +248,98 @@ Provides CRDT-based real-time sync with cursor presence overlay. Requires a [Yjs
221
248
 
222
249
  All elements share: `id`, `x`, `y`, `width`, `height`, `rotation`, `style`, `isLocked`, `isVisible`, `boundElements`, `groupIds`.
223
250
 
251
+ Custom types can be added via the plugin system — see [Custom Element Types](#-custom-element-types--plugins).
252
+
224
253
  > Full type definitions are bundled in the package `.d.ts` files.
225
254
 
255
+ ## 🔌 Custom Element Types / Plugins
256
+
257
+ f1ow supports registering custom element types. Every element passing through `addElement`, `updateElement`, `setElements`, or `importJSON` is validated — both built-in and custom types.
258
+
259
+ ### Option 1 — Global registration (before rendering)
260
+
261
+ Register once at module level so the type is available across all `<FlowCanvas>` instances:
262
+
263
+ ```ts
264
+ import { registerCustomElement } from 'f1ow';
265
+
266
+ registerCustomElement({
267
+ type: 'sticky-note',
268
+ displayName: 'Sticky Note',
269
+
270
+ // Called after base-field validation passes.
271
+ // Return true = valid, or a string = error message.
272
+ validate: (el) => typeof el.content === 'string' || 'content must be a string',
273
+
274
+ // Default field values — only fills gaps, never overwrites.
275
+ defaults: { content: '', color: '#ffeb3b' },
276
+ });
277
+ ```
278
+
279
+ ### Option 2 — Per-component registration (via prop)
280
+
281
+ Types are registered once when `<FlowCanvas>` mounts. Keep the array reference stable (module constant or `useMemo`) — changes after mount have no effect.
282
+
283
+ ```tsx
284
+ import { FlowCanvas } from 'f1ow';
285
+ import type { CustomElementConfig } from 'f1ow';
286
+
287
+ // ✅ Define outside the component (or useMemo) — stable reference
288
+ const MY_TYPES: CustomElementConfig[] = [
289
+ {
290
+ type: 'sticky-note',
291
+ displayName: 'Sticky Note',
292
+ validate: (el) => typeof el.content === 'string' || 'content must be a string',
293
+ defaults: { content: '', color: '#ffeb3b' },
294
+ },
295
+ ];
296
+
297
+ function App() {
298
+ return <FlowCanvas customElementTypes={MY_TYPES} />;
299
+ }
300
+ ```
301
+
302
+ ### `CustomElementConfig` reference
303
+
304
+ | Field | Type | Description |
305
+ | --- | --- | --- |
306
+ | `type` | `string` | **Required.** Unique type identifier (must not clash with built-ins unless `allowOverride: true`) |
307
+ | `displayName` | `string` | Human-readable name used in warnings. Defaults to `type` |
308
+ | `validate` | `(el: Record<string, unknown>) => true \| string` | Extra validation after base-field checks. Return `true` = valid, string = error message |
309
+ | `defaults` | `Partial<T>` | Default field values applied on `addElement`. Existing fields take priority |
310
+ | `allowOverride` | `boolean` | Allow replacing an existing registration. Default `false` |
311
+
312
+ ### Using the registry directly
313
+
314
+ ```ts
315
+ import { elementRegistry } from 'f1ow';
316
+
317
+ // Check if a type is registered
318
+ elementRegistry.isRegistered('sticky-note'); // true / false
319
+
320
+ // Validate any element manually
321
+ const result = elementRegistry.validateElement(myElement);
322
+ if (!result.valid) console.error(result.error);
323
+
324
+ // All registered types
325
+ elementRegistry.getRegisteredTypes();
326
+ // → ['rectangle', 'ellipse', ..., 'sticky-note']
327
+ ```
328
+
329
+ ### Built-in validation rules
330
+
331
+ Every element is validated on every write regardless of type:
332
+
333
+ | Field | Rule |
334
+ | --- | --- |
335
+ | `id` | Non-empty string |
336
+ | `type` | Must be a registered type |
337
+ | `x`, `y`, `rotation` | Finite number |
338
+ | `width`, `height` | Finite number ≥ 0 |
339
+ | `style.opacity` | Number in `[0, 1]` |
340
+ | `style.strokeWidth`, `style.fontSize` | Finite number > 0 |
341
+ | `id` / `type` in updates | Blocked — use `convertElementType` for type changes |
342
+
226
343
  ## 🛠️ Development
227
344
 
228
345
  ```bash