f1ow 0.1.5 → 1.0.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.
@@ -2,6 +2,7 @@ import { CanvasElement, ElementStyle, ToolType } from '../types';
2
2
  import { ContextMenuItem } from '../components/ContextMenu/ContextMenu';
3
3
  import { CollaborationConfig } from '../collaboration/types';
4
4
  import { CustomElementConfig } from '../utils/elementRegistry';
5
+ import { RenderAnnotationFn } from '../components/Canvas/AnnotationsOverlay';
5
6
  export type { ContextMenuItem };
6
7
  /** Context passed to custom context menu renderers */
7
8
  export interface ContextMenuContext {
@@ -69,6 +70,42 @@ export interface FlowCanvasProps {
69
70
  readOnly?: boolean;
70
71
  /** Additional CSS class for the root container */
71
72
  className?: string;
73
+ /**
74
+ * Render custom annotations, badges, or status indicators on top of canvas elements.
75
+ *
76
+ * The callback receives an `AnnotationContext` with:
77
+ * - `element` — the canvas element being annotated
78
+ * - `screenBounds` — pre-computed screen-space `{ x, y, width, height }`
79
+ * - `scale` — current viewport zoom level
80
+ *
81
+ * Return a React node to render, or `null` to skip.
82
+ * The node is positioned inside a `div` that matches the element's
83
+ * screen bounding box. Use `position: absolute` to place content
84
+ * relative to the element (e.g. `top: -10, right: -10` for a badge).
85
+ *
86
+ * **Important:** The entire overlay is `pointerEvents: 'none'`.
87
+ * Add `pointerEvents: 'auto'` on interactive nodes (buttons, badges).
88
+ *
89
+ * @example
90
+ * ```tsx
91
+ * <FlowCanvas
92
+ * renderAnnotation={({ element, scale }) => {
93
+ * if (element.type !== 'rectangle') return null;
94
+ * return (
95
+ * <div style={{
96
+ * position: 'absolute', top: -10, right: -10,
97
+ * pointerEvents: 'auto',
98
+ * // Scale-aware badge sizing:
99
+ * transform: `scale(${1 / scale})`, transformOrigin: 'top right',
100
+ * }}>
101
+ * 🔴
102
+ * </div>
103
+ * );
104
+ * }}
105
+ * />
106
+ * ```
107
+ */
108
+ renderAnnotation?: RenderAnnotationFn;
72
109
  /**
73
110
  * Additional context menu items to append after the built-in items.
74
111
  * Can be static items or a function that receives selection context.
@@ -1,6 +1,7 @@
1
1
  export { default as FlowCanvas } from './FlowCanvas';
2
2
  export type { FlowCanvasProps, FlowCanvasRef, FlowCanvasTheme, ContextMenuItem, ContextMenuContext, } from './FlowCanvasProps';
3
3
  export { DEFAULT_THEME } from './FlowCanvasProps';
4
+ export type { AnnotationContext, AnnotationScreenBounds, RenderAnnotationFn, } from '../components/Canvas/AnnotationsOverlay';
4
5
  export type { CanvasElement, RectangleElement, EllipseElement, DiamondElement, LineElement, ArrowElement, FreeDrawElement, TextElement, ImageElement, BaseElement, ElementStyle, ElementType, ToolType, Point, ViewportState, ConnectionAnchor, BoundElement, Binding, SnapTarget, Arrowhead, LineType, TextAlign, VerticalAlign, ImageScaleMode, ImageCrop, ElementMeta, CanvasOperation, } from '../types';
5
6
  export { useCanvasStore } from '../store/useCanvasStore';
6
7
  export { DEFAULT_STYLE, STROKE_COLORS, FILL_COLORS, STROKE_WIDTHS, TOOLS, ARROWHEAD_TYPES, LINE_TYPES, ROUGHNESS_CONFIGS } from '../constants';
@@ -9,6 +10,7 @@ export { distance, normalizeRect, rotatePoint, isPointInRect, getDiamondPoints,
9
10
  export { exportToDataURL, downloadPNG, exportToJSON, downloadJSON, exportToSVG, downloadSVG } from '../utils/export';
10
11
  export { drawArrowhead, arrowheadSize, flatToPoints } from '../utils/arrowheads';
11
12
  export { computeCurveControlPoint, quadBezierAt, quadBezierTangent, curveArrowPrev, CURVE_RATIO } from '../utils/curve';
13
+ export { LABEL_PADDING_H, LABEL_PADDING_V, LABEL_CORNER, LABEL_LINE_HEIGHT, LABEL_MIN_WIDTH, measureLabelText, computePillSize } from '../utils/labelMetrics';
12
14
  export { elementRegistry, registerCustomElement } from '../utils/elementRegistry';
13
15
  export type { CustomElementConfig, ValidationResult } from '../utils/elementRegistry';
14
16
  export { computeElbowPoints, computeElbowRoute, simplifyElbowPath, clearElbowRouteCache, directionFromFixedPoint, directionFromPoints, directionFromShapeToPoint, directionFromEdgePoint, getElbowPreferredDirection, } from '../utils/elbow';
@@ -1,7 +1,7 @@
1
1
  import { ViewportState, CanvasElement } from '../types';
2
2
  import { AABB } from './performance';
3
3
  /** Predefined zoom steps for smooth discrete zooming */
4
- export declare const ZOOM_STEPS: readonly [0.1, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 4, 5];
4
+ export declare const ZOOM_STEPS: readonly [0.1, 0.25, 0.33, 0.5, 0.67, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4, 5];
5
5
  /** Default animation duration in ms */
6
6
  export declare const DEFAULT_ANIMATION_DURATION = 280;
7
7
  export interface ZoomAtPointOptions {
@@ -1,4 +1,4 @@
1
- import { CanvasElement, Point, ConnectionAnchor, Binding, SnapTarget, ArrowElement, LineElement, BoundElement } from '../types';
1
+ import { CanvasElement, Point, ConnectionAnchor, Binding, SnapTarget, ArrowElement, LineElement, TextElement, BoundElement } from '../types';
2
2
  /** Whether an element can be a connection target */
3
3
  export declare function isConnectable(el: CanvasElement): boolean;
4
4
  /** Get the 5 named anchor positions for a bounding-box shape */
@@ -120,3 +120,28 @@ export declare function removeBoundElement(shape: CanvasElement, refId: string):
120
120
  * @param updateElement Callback to persist the shape update
121
121
  */
122
122
  export declare function syncBoundElements(connectorId: string, connectorType: 'arrow' | 'line', oldBinding: Binding | null, newBinding: Binding | null, elements: CanvasElement[], updateElement: (id: string, updates: Partial<CanvasElement>) => void): void;
123
+ /**
124
+ * Compute the midpoint position for a text label on a connector (arrow/line).
125
+ * Uses the connector's current points and lineType (sharp/curved/elbow)
126
+ * to find the visual midpoint, then centers the label around it.
127
+ *
128
+ * @param connector - The connector element (ArrowElement | LineElement)
129
+ * @param textWidth - Current label text width (px)
130
+ * @param textHeight - Current label text height (px)
131
+ * @returns `{ x, y }` in world coordinates for the text element.
132
+ */
133
+ export declare function computeConnectorLabelPosition(connector: LineElement | ArrowElement, textWidth: number, textHeight: number): {
134
+ x: number;
135
+ y: number;
136
+ };
137
+ /**
138
+ * Sync bound text labels for a list of connector elements.
139
+ * Returns an array of text element updates to batch-apply.
140
+ *
141
+ * @param connectorIds - IDs of connectors whose labels need syncing
142
+ * @param elMap - O(1) element lookup map
143
+ */
144
+ export declare function syncConnectorLabels(connectorIds: Iterable<string>, elMap: Map<string, CanvasElement>): Array<{
145
+ id: string;
146
+ updates: Partial<TextElement>;
147
+ }>;
@@ -0,0 +1,50 @@
1
+ import { CanvasElement } from '../types';
2
+ /** Shared padding between shape containers and their bound text. */
3
+ export declare const BOUND_TEXT_PADDING = 4;
4
+ /** Shape types that can contain bound text. */
5
+ export declare const CONTAINER_TYPES: ReadonlySet<string>;
6
+ /**
7
+ * Compute the stored position for bound text inside a shape container.
8
+ *
9
+ * @param container The container shape (needs x, y, width, height)
10
+ * @param text The text element (needs height, optionally verticalAlign)
11
+ * @returns `{ x, y, width }` updates to apply on the text element
12
+ */
13
+ export declare function computeBoundTextPosition(container: {
14
+ x: number;
15
+ y: number;
16
+ width: number;
17
+ height: number;
18
+ }, text: {
19
+ height: number;
20
+ verticalAlign?: string;
21
+ }): {
22
+ x: number;
23
+ y: number;
24
+ width: number;
25
+ };
26
+ export interface DragSyncResult {
27
+ /** Batched updates to apply to the store in a single write. */
28
+ updates: Array<{
29
+ id: string;
30
+ updates: Partial<CanvasElement>;
31
+ }>;
32
+ /** IDs of connectors that were recomputed (for dedup / further processing). */
33
+ processedConnectorIds: Set<string>;
34
+ }
35
+ /**
36
+ * After one or more elements are moved / resized, recompute:
37
+ *
38
+ * 1. Connector bound-points for all attached connectors
39
+ * 2. Bound text positions for shape containers
40
+ * 3. Connector-label positions (depend on updated connector geometry)
41
+ *
42
+ * Returns all updates as a flat array — the caller is responsible for
43
+ * applying them to the store (single `batchUpdateElements` call).
44
+ *
45
+ * @param movedIds IDs of elements that were moved / resized
46
+ * @param elements Current **full** element list (post position-write)
47
+ * @param skipIds Optional set of IDs to skip (e.g. group-internal
48
+ * elements that already moved together)
49
+ */
50
+ export declare function syncAfterDrag(movedIds: Iterable<string>, elements: CanvasElement[], skipIds?: ReadonlySet<string>): DragSyncResult;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * labelMetrics.ts
3
+ *
4
+ * Single source of truth for connector label sizing — shared by both
5
+ * the Konva display path (TextShape render) and the DOM textarea editor.
6
+ *
7
+ * By measuring text with the same Canvas 2D API that Konva uses internally,
8
+ * both modes produce identical dimensions → no visual "jump" between
9
+ * display and editing.
10
+ *
11
+ * @see docs/CONNECTOR_LABEL_DESIGN.md
12
+ */
13
+ /** Horizontal padding inside the pill background (px, canvas-space) */
14
+ export declare const LABEL_PADDING_H = 8;
15
+ /** Vertical padding inside the pill background (px, canvas-space) */
16
+ export declare const LABEL_PADDING_V = 4;
17
+ /** Corner radius of the pill background (px, canvas-space) */
18
+ export declare const LABEL_CORNER = 4;
19
+ /** Line-height multiplier — must match Konva <Text lineHeight> */
20
+ export declare const LABEL_LINE_HEIGHT = 1.18;
21
+ /** Minimum text content width to avoid zero-width pill */
22
+ export declare const LABEL_MIN_WIDTH = 10;
23
+ /**
24
+ * Measure text width/height using Canvas 2D — the same engine Konva uses.
25
+ *
26
+ * This function is the **single measurement source** for connector labels.
27
+ * Both the Konva `<Text>` node and the DOM `<textarea>` editor derive
28
+ * their dimensions from these numbers.
29
+ *
30
+ * @param text - The label string (single-line; newlines ignored)
31
+ * @param fontSize - Font size in canvas-space pixels
32
+ * @param fontFamily - CSS font-family string
33
+ * @returns `{ width, height }` in canvas-space pixels (not screen pixels)
34
+ */
35
+ export declare function measureLabelText(text: string, fontSize: number, fontFamily: string): {
36
+ width: number;
37
+ height: number;
38
+ };
39
+ /**
40
+ * Compute the full pill (background rect) dimensions for a connector label.
41
+ *
42
+ * @param textWidth - Measured text content width (from `measureLabelText`)
43
+ * @param textHeight - Measured text content height (from `measureLabelText`)
44
+ * @returns `{ width, height }` of the pill in canvas-space pixels
45
+ */
46
+ export declare function computePillSize(textWidth: number, textHeight: number): {
47
+ width: number;
48
+ height: number;
49
+ };
package/package.json CHANGED
@@ -1,99 +1,107 @@
1
- {
2
- "name": "f1ow",
3
- "version": "0.1.5",
4
- "type": "module",
5
- "description": "Interactive canvas drawing toolkit built on KonvaJS — drop-in React component for diagrams, sketches & whiteboards",
6
- "author": "Nuumz <info@nuumz.com>",
7
- "homepage": "https://github.com/nuumz/f1ow-canvas#readme",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/nuumz/f1ow-canvas.git"
11
- },
12
- "bugs": {
13
- "url": "https://github.com/nuumz/f1ow-canvas/issues"
14
- },
15
- "main": "./dist/f1ow.umd.cjs",
16
- "module": "./dist/f1ow.js",
17
- "types": "./dist/lib/index.d.ts",
18
- "exports": {
19
- ".": {
20
- "types": "./dist/lib/index.d.ts",
21
- "import": "./dist/f1ow.js",
22
- "require": "./dist/f1ow.umd.cjs"
23
- }
24
- },
25
- "files": [
26
- "dist",
27
- "LICENSE",
28
- "README.md"
29
- ],
30
- "sideEffects": false,
31
- "peerDependencies": {
32
- "react": ">=17.0.0",
33
- "react-dom": ">=17.0.0",
34
- "konva": ">=9.0.0",
35
- "react-konva": ">=18.0.0",
36
- "zustand": ">=5.0.0",
37
- "yjs": ">=13.0.0",
38
- "y-websocket": ">=2.0.0"
39
- },
40
- "peerDependenciesMeta": {
41
- "konva": {
42
- "optional": false
43
- },
44
- "react-konva": {
45
- "optional": false
46
- },
47
- "zustand": {
48
- "optional": false
49
- },
50
- "yjs": {
51
- "optional": true
52
- },
53
- "y-websocket": {
54
- "optional": true
55
- }
56
- },
57
- "devDependencies": {
58
- "@types/node": "^25.2.1",
59
- "@types/rbush": "^4.0.0",
60
- "@types/react": "^18.3.18",
61
- "@types/react-dom": "^18.3.5",
62
- "@vitejs/plugin-react": "^4.3.4",
63
- "konva": "^9.3.18",
64
- "lucide-react": "^0.468.0",
65
- "nanoid": "^5.0.9",
66
- "rbush": "^4.0.1",
67
- "react": "^18.3.1",
68
- "react-dom": "^18.3.1",
69
- "react-konva": "^18.2.10",
70
- "typescript": "^5.7.3",
71
- "vite": "^6.0.7",
72
- "vite-plugin-dts": "^4.3.0",
73
- "y-websocket": "^3.0.0",
74
- "yjs": "^13.6.29",
75
- "zustand": "^5.0.3"
76
- },
77
- "keywords": [
78
- "canvas",
79
- "drawing",
80
- "konvajs",
81
- "react",
82
- "whiteboard",
83
- "diagram",
84
- "flowchart",
85
- "react-component",
86
- "konva",
87
- "collaborative",
88
- "vector",
89
- "sketch"
90
- ],
91
- "license": "MIT",
92
- "scripts": {
93
- "dev": "vite",
94
- "build": "vite build",
95
- "build:lib": "vite build --mode lib",
96
- "preview": "vite preview",
97
- "typecheck": "tsc --noEmit"
98
- }
99
- }
1
+ {
2
+ "name": "f1ow",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Interactive canvas drawing toolkit built on KonvaJS — drop-in React component for diagrams, sketches & whiteboards",
6
+ "author": "Nuumz <info@nuumz.com>",
7
+ "homepage": "https://github.com/nuumz/f1ow-canvas#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/nuumz/f1ow-canvas.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/nuumz/f1ow-canvas/issues"
14
+ },
15
+ "main": "./dist/f1ow.umd.cjs",
16
+ "module": "./dist/f1ow.js",
17
+ "types": "./dist/lib/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/lib/index.d.ts",
21
+ "import": "./dist/f1ow.js",
22
+ "require": "./dist/f1ow.umd.cjs"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "LICENSE",
28
+ "README.md"
29
+ ],
30
+ "sideEffects": false,
31
+ "scripts": {
32
+ "dev": "vite",
33
+ "build": "vite build",
34
+ "build:lib": "vite build --mode lib",
35
+ "preview": "vite preview",
36
+ "typecheck": "tsc --noEmit",
37
+ "prepublishOnly": "npm run typecheck && npm run build:lib"
38
+ },
39
+ "peerDependencies": {
40
+ "react": ">=17.0.0",
41
+ "react-dom": ">=17.0.0",
42
+ "konva": ">=9.0.0",
43
+ "react-konva": ">=18.0.0",
44
+ "zustand": ">=5.0.0",
45
+ "yjs": ">=13.0.0",
46
+ "y-websocket": ">=2.0.0"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "konva": {
50
+ "optional": false
51
+ },
52
+ "react-konva": {
53
+ "optional": false
54
+ },
55
+ "zustand": {
56
+ "optional": false
57
+ },
58
+ "yjs": {
59
+ "optional": true
60
+ },
61
+ "y-websocket": {
62
+ "optional": true
63
+ }
64
+ },
65
+ "devDependencies": {
66
+ "@types/node": "^25.2.1",
67
+ "@types/rbush": "^4.0.0",
68
+ "@types/react": "^18.3.18",
69
+ "@types/react-dom": "^18.3.5",
70
+ "@vitejs/plugin-react": "^4.3.4",
71
+ "konva": "^9.3.18",
72
+ "lucide-react": "^0.468.0",
73
+ "nanoid": "^5.0.9",
74
+ "rbush": "^4.0.1",
75
+ "react": "^18.3.1",
76
+ "react-dom": "^18.3.1",
77
+ "react-konva": "^18.2.10",
78
+ "typescript": "^5.7.3",
79
+ "vite": "^6.0.7",
80
+ "vite-plugin-dts": "^4.3.0",
81
+ "y-websocket": "^3.0.0",
82
+ "yjs": "^13.6.29",
83
+ "zustand": "^5.0.3"
84
+ },
85
+ "keywords": [
86
+ "canvas",
87
+ "drawing",
88
+ "konvajs",
89
+ "react",
90
+ "whiteboard",
91
+ "diagram",
92
+ "flowchart",
93
+ "react-component",
94
+ "konva",
95
+ "collaborative",
96
+ "vector",
97
+ "sketch"
98
+ ],
99
+ "license": "MIT",
100
+ "pnpm": {
101
+ "overrides": {
102
+ "minimatch@9": "9.0.7",
103
+ "minimatch@10": "10.2.1",
104
+ "ajv": "8.18.0"
105
+ }
106
+ }
107
+ }