f1ow 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/LICENSE +21 -0
- package/README.md +210 -0
- package/dist/assets/elbowWorker-CUBh1uET.js +1 -0
- package/dist/assets/exportWorker-hU9uNZXJ.js +8 -0
- package/dist/assets/syncWorker.worker-CnlpNZ3k.js +6 -0
- package/dist/components/Canvas/CanvasElement.d.ts +42 -0
- package/dist/components/Canvas/ConnectionPoints.d.ts +13 -0
- package/dist/components/Canvas/GridLayer.d.ts +13 -0
- package/dist/components/Canvas/LinearElementHandles.d.ts +17 -0
- package/dist/components/Canvas/SelectionBox.d.ts +12 -0
- package/dist/components/Canvas/SelectionTransformer.d.ts +7 -0
- package/dist/components/ContextMenu/ContextMenu.d.ts +23 -0
- package/dist/components/StylePanel/StylePanel.d.ts +7 -0
- package/dist/components/Toolbar/Toolbar.d.ts +9 -0
- package/dist/components/shapes/ArrowShape.d.ts +18 -0
- package/dist/components/shapes/DiamondShape.d.ts +24 -0
- package/dist/components/shapes/EllipseShape.d.ts +24 -0
- package/dist/components/shapes/FreeDrawShape.d.ts +24 -0
- package/dist/components/shapes/ImageShape.d.ts +24 -0
- package/dist/components/shapes/LineShape.d.ts +18 -0
- package/dist/components/shapes/RectangleShape.d.ts +24 -0
- package/dist/components/shapes/TextShape.d.ts +23 -0
- package/dist/constants/index.d.ts +44 -0
- package/dist/f1ow-canvas.js +37725 -0
- package/dist/f1ow-canvas.umd.cjs +371 -0
- package/dist/hooks/useEfficientZoom.d.ts +14 -0
- package/dist/hooks/useElbowShapeFingerprint.d.ts +21 -0
- package/dist/hooks/useElbowWorker.d.ts +23 -0
- package/dist/hooks/useKeyboardShortcuts.d.ts +7 -0
- package/dist/hooks/useProgressiveRender.d.ts +52 -0
- package/dist/hooks/useSpatialIndex.d.ts +9 -0
- package/dist/hooks/useViewportCulling.d.ts +13 -0
- package/dist/lib/FlowCanvas.d.ts +4 -0
- package/dist/lib/FlowCanvasProps.d.ts +154 -0
- package/dist/lib/index.d.ts +57 -0
- package/dist/store/useCanvasStore.d.ts +127 -0
- package/dist/store/useLinearEditStore.d.ts +25 -0
- package/dist/types/index.d.ts +257 -0
- package/dist/utils/alignment.d.ts +34 -0
- package/dist/utils/arrowheads.d.ts +20 -0
- package/dist/utils/camera.d.ts +63 -0
- package/dist/utils/clipboard.d.ts +4 -0
- package/dist/utils/clone.d.ts +29 -0
- package/dist/utils/connection.d.ts +122 -0
- package/dist/utils/crdtPrep.d.ts +97 -0
- package/dist/utils/curve.d.ts +51 -0
- package/dist/utils/elbow.d.ts +110 -0
- package/dist/utils/elbowWorkerManager.d.ts +53 -0
- package/dist/utils/export.d.ts +17 -0
- package/dist/utils/exportWorkerManager.d.ts +25 -0
- package/dist/utils/fractionalIndex.d.ts +51 -0
- package/dist/utils/geometry.d.ts +49 -0
- package/dist/utils/id.d.ts +1 -0
- package/dist/utils/image.d.ts +52 -0
- package/dist/utils/performance.d.ts +65 -0
- package/dist/utils/roughness.d.ts +104 -0
- package/dist/utils/spatialIndex.d.ts +109 -0
- package/dist/utils/spatialSoA.d.ts +82 -0
- package/dist/workers/elbowWorker.d.ts +18 -0
- package/dist/workers/exportWorker.d.ts +18 -0
- package/package.json +80 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Point, Binding, CanvasElement } from '../types';
|
|
2
|
+
export type Direction = 'up' | 'down' | 'left' | 'right';
|
|
3
|
+
/** Shape bounding box using x/y/width/height (canvas element convention) */
|
|
4
|
+
interface BBox {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Determine the preferred exit direction for elbow routing from a
|
|
12
|
+
* center (imprecise) binding.
|
|
13
|
+
*
|
|
14
|
+
* Unlike `directionFromShapeToPoint` which picks the face that
|
|
15
|
+
* geometrically faces the target (optimal for straight lines), this
|
|
16
|
+
* function prefers VERTICAL exits (up/down) for diagonal configurations.
|
|
17
|
+
*
|
|
18
|
+
* Rationale: For orthogonal/elbow routing, vertical exits produce
|
|
19
|
+
* clean C-shaped or S-shaped paths that route ABOVE or BELOW shapes.
|
|
20
|
+
* Horizontal exits (from side faces) create paths that run BETWEEN
|
|
21
|
+
* shapes at their vertical center — visually too close to objects and
|
|
22
|
+
* often requiring more bends.
|
|
23
|
+
*
|
|
24
|
+
* Only uses horizontal exits when the target is strongly horizontally
|
|
25
|
+
* aligned (>3× horizontal dominance after normalizing by shape size).
|
|
26
|
+
*
|
|
27
|
+
* This function should be used by BOTH:
|
|
28
|
+
* - The binding system (connection.ts) to compute edge points for
|
|
29
|
+
* elbow connectors with center bindings
|
|
30
|
+
* - The routing system (computeElbowPoints) to determine exit direction
|
|
31
|
+
*
|
|
32
|
+
* For PRECISE bindings, always use directionFromFixedPoint instead.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getElbowPreferredDirection(shape: {
|
|
35
|
+
x: number;
|
|
36
|
+
y: number;
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}, targetPoint: Point): Direction;
|
|
40
|
+
/**
|
|
41
|
+
* Determine the exit/entry direction from a binding's fixedPoint.
|
|
42
|
+
* fixedPoint is [fx, fy] in [0-1, 0-1] on the target shape's bbox.
|
|
43
|
+
*
|
|
44
|
+
* [0.5, 0] → top → exit upward
|
|
45
|
+
* [0.5, 1] → bottom → exit downward
|
|
46
|
+
* [0, 0.5] → left → exit leftward
|
|
47
|
+
* [1, 0.5] → right → exit rightward
|
|
48
|
+
*
|
|
49
|
+
* For corners and arbitrary positions, pick the nearest edge.
|
|
50
|
+
*/
|
|
51
|
+
export declare function directionFromFixedPoint(fp: [number, number]): Direction;
|
|
52
|
+
/**
|
|
53
|
+
* Determine direction from relative position of two points
|
|
54
|
+
* (used when there's no binding / fixedPoint).
|
|
55
|
+
* Picks the dominant axis between start and end.
|
|
56
|
+
*/
|
|
57
|
+
export declare function directionFromPoints(from: Point, to: Point): Direction;
|
|
58
|
+
/**
|
|
59
|
+
* Determine the best exit direction from a shape toward a target point.
|
|
60
|
+
* Normalizes by half-dimensions so the correct face is chosen regardless
|
|
61
|
+
* of aspect ratio.
|
|
62
|
+
*/
|
|
63
|
+
export declare function directionFromShapeToPoint(shape: BBox, targetPoint: Point): Direction;
|
|
64
|
+
/**
|
|
65
|
+
* Determine exit direction by finding which face of a shape's bounding box
|
|
66
|
+
* an edge point is closest to. Most accurate method — uses the ACTUAL
|
|
67
|
+
* computed edge point from the binding system.
|
|
68
|
+
*/
|
|
69
|
+
export declare function directionFromEdgePoint(shape: BBox, edgePoint: Point): Direction;
|
|
70
|
+
/**
|
|
71
|
+
* Compute an orthogonal route between two points using a multi-candidate
|
|
72
|
+
* grid-based shortest-path algorithm.
|
|
73
|
+
*
|
|
74
|
+
* Generates multiple obstacle inflation configurations and routes through
|
|
75
|
+
* each, then picks the route with the fewest bends (and shortest length
|
|
76
|
+
* as tiebreaker). This produces cleaner paths, especially when shapes
|
|
77
|
+
* are positioned diagonally and the standard inflation blocks direct routes.
|
|
78
|
+
*
|
|
79
|
+
* Obstacle configurations:
|
|
80
|
+
*
|
|
81
|
+
* 1. **Standard**: Each shape inflated on all sides except its exit/entry
|
|
82
|
+
* face. Works well when shapes are aligned horizontally or vertically.
|
|
83
|
+
*
|
|
84
|
+
* 2. **Relaxed**: Additionally excludes the face of each shape that faces
|
|
85
|
+
* toward the other connection point. This creates L-shaped corridors
|
|
86
|
+
* around shapes, allowing more direct paths when shapes are diagonal.
|
|
87
|
+
* For example, if start exits RIGHT and end shape is UP-RIGHT, the
|
|
88
|
+
* relaxed config also opens the LEFT face of the end shape, enabling
|
|
89
|
+
* a clean 2-bend path instead of a 4-bend detour.
|
|
90
|
+
*
|
|
91
|
+
* @param start Start connection point (world coordinates)
|
|
92
|
+
* @param end End connection point (world coordinates)
|
|
93
|
+
* @param startDir Exit direction from start shape
|
|
94
|
+
* @param endDir Exit direction from end shape (face the connector arrives at)
|
|
95
|
+
* @param startBBox Bounding box of start shape (null for unbound endpoints)
|
|
96
|
+
* @param endBBox Bounding box of end shape (null for unbound endpoints)
|
|
97
|
+
* @returns Array of Points forming the orthogonal path (world coords)
|
|
98
|
+
*/
|
|
99
|
+
export declare function computeElbowRoute(start: Point, end: Point, startDir: Direction, endDir: Direction, startBBox?: BBox | null, endBBox?: BBox | null, minStubLength?: number,
|
|
100
|
+
/** Additional obstacles (intermediate shapes) to avoid — already as BBox */
|
|
101
|
+
intermediateObstacles?: BBox[]): Point[];
|
|
102
|
+
/** Clear the route cache (call when elements change structurally) */
|
|
103
|
+
export declare function clearElbowRouteCache(): void;
|
|
104
|
+
export declare function computeElbowPoints(startWorld: Point, endWorld: Point, startBinding: Binding | null, endBinding: Binding | null, allElements: CanvasElement[], minStubLength?: number): number[];
|
|
105
|
+
/**
|
|
106
|
+
* Simplify an elbow path (flat number[] format) by removing redundant
|
|
107
|
+
* collinear points and zero-length segments.
|
|
108
|
+
*/
|
|
109
|
+
export declare function simplifyElbowPath(points: number[]): number[];
|
|
110
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { CanvasElement, Binding, Point } from '../types';
|
|
2
|
+
export interface RouteParams {
|
|
3
|
+
startWorld: Point;
|
|
4
|
+
endWorld: Point;
|
|
5
|
+
startBinding: Binding | null;
|
|
6
|
+
endBinding: Binding | null;
|
|
7
|
+
minStubLength?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class ElbowWorkerManager {
|
|
10
|
+
private worker;
|
|
11
|
+
private requestIdCounter;
|
|
12
|
+
private pending;
|
|
13
|
+
private cachedElements;
|
|
14
|
+
private workerSupported;
|
|
15
|
+
/**
|
|
16
|
+
* Get or lazily create the Worker instance.
|
|
17
|
+
* Returns null if Workers are not supported in the current environment.
|
|
18
|
+
*/
|
|
19
|
+
private _getWorker;
|
|
20
|
+
private handleMessage;
|
|
21
|
+
/**
|
|
22
|
+
* Update the Worker's element snapshot for obstacle detection.
|
|
23
|
+
* Call this whenever elements change (position, size, visibility).
|
|
24
|
+
*/
|
|
25
|
+
updateElements(elements: CanvasElement[]): void;
|
|
26
|
+
/**
|
|
27
|
+
* Compute an elbow route asynchronously via the Worker.
|
|
28
|
+
* Falls back to synchronous main-thread computation when:
|
|
29
|
+
* - Worker is not available
|
|
30
|
+
* - Element count is below WORKER_THRESHOLD
|
|
31
|
+
* - Worker is busy and request times out
|
|
32
|
+
*/
|
|
33
|
+
computeRoute(params: RouteParams): Promise<number[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Synchronous fallback computation (original behavior).
|
|
36
|
+
*/
|
|
37
|
+
computeSync(params: RouteParams): number[];
|
|
38
|
+
/**
|
|
39
|
+
* Terminate the Worker and clean up resources.
|
|
40
|
+
*/
|
|
41
|
+
dispose(): void;
|
|
42
|
+
/** Whether the Worker can be used (environment supports it) */
|
|
43
|
+
get isWorkerActive(): boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get or create the shared ElbowWorkerManager singleton.
|
|
47
|
+
* The Worker is created lazily on first access.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getElbowWorkerManager(): ElbowWorkerManager;
|
|
50
|
+
/**
|
|
51
|
+
* Dispose the shared manager (e.g. when FlowCanvas unmounts).
|
|
52
|
+
*/
|
|
53
|
+
export declare function disposeElbowWorkerManager(): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { default as Konva } from 'konva';
|
|
2
|
+
import { CanvasElement } from '../types';
|
|
3
|
+
/** Export stage to data URL (PNG) */
|
|
4
|
+
export declare function exportToDataURL(stage: Konva.Stage): string;
|
|
5
|
+
/** Export stage as downloadable PNG */
|
|
6
|
+
export declare function downloadPNG(stage: Konva.Stage, filename?: string): void;
|
|
7
|
+
/** Export elements to JSON string */
|
|
8
|
+
export declare function exportToJSON(elements: unknown[]): string;
|
|
9
|
+
/** Download JSON */
|
|
10
|
+
export declare function downloadJSON(elements: unknown[], filename?: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Export canvas elements to an SVG string.
|
|
13
|
+
* Computes tight bounding box + padding automatically.
|
|
14
|
+
*/
|
|
15
|
+
export declare function exportToSVG(elements: CanvasElement[]): string;
|
|
16
|
+
/** Download SVG file */
|
|
17
|
+
export declare function downloadSVG(elements: CanvasElement[], filename?: string): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CanvasElement } from '../types';
|
|
2
|
+
export declare class ExportWorkerManager {
|
|
3
|
+
private _worker;
|
|
4
|
+
private _pending;
|
|
5
|
+
private _workerSupported;
|
|
6
|
+
private _getWorker;
|
|
7
|
+
private _onMessage;
|
|
8
|
+
private _rejectAll;
|
|
9
|
+
/**
|
|
10
|
+
* Export elements to SVG string.
|
|
11
|
+
* Uses Worker for large sets, sync for small sets.
|
|
12
|
+
*/
|
|
13
|
+
exportSVG(elements: CanvasElement[]): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Export elements to SVG and download as file.
|
|
16
|
+
* Uses Worker for SVG generation, then triggers browser download.
|
|
17
|
+
*/
|
|
18
|
+
downloadSVG(elements: CanvasElement[], filename?: string): Promise<void>;
|
|
19
|
+
/** Terminate the Worker and clean up */
|
|
20
|
+
dispose(): void;
|
|
21
|
+
}
|
|
22
|
+
/** Get or create the shared ExportWorkerManager singleton */
|
|
23
|
+
export declare function getExportWorkerManager(): ExportWorkerManager;
|
|
24
|
+
/** Dispose the shared ExportWorkerManager singleton */
|
|
25
|
+
export declare function disposeExportWorkerManager(): void;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fractionalIndex.ts — Fractional indexing utilities for CRDT-compatible
|
|
3
|
+
* element ordering.
|
|
4
|
+
*
|
|
5
|
+
* Fractional indices allow inserting elements between any two existing
|
|
6
|
+
* elements without shifting array indices — essential for:
|
|
7
|
+
* - Conflict-free concurrent reordering (CRDT)
|
|
8
|
+
* - O(1) insert-between operations (no array splice)
|
|
9
|
+
* - Stable sort order across distributed replicas
|
|
10
|
+
*
|
|
11
|
+
* The algorithm uses string-based fractional indices. Each index is a
|
|
12
|
+
* string that can be compared lexicographically to determine order.
|
|
13
|
+
*
|
|
14
|
+
* Implementation: Uses a base-36 encoding with variable-length strings.
|
|
15
|
+
* Between any two strings "a" and "b" where a < b, we can always
|
|
16
|
+
* generate a string "c" such that a < c < b.
|
|
17
|
+
*
|
|
18
|
+
* Examples:
|
|
19
|
+
* generateKeyBetween(null, null) → "a0" (first element)
|
|
20
|
+
* generateKeyBetween("a0", null) → "a1" (after first)
|
|
21
|
+
* generateKeyBetween(null, "a0") → "Zz" (before first)
|
|
22
|
+
* generateKeyBetween("a0", "a1") → "a0V" (between)
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Generate a fractional index key between two existing keys.
|
|
26
|
+
*
|
|
27
|
+
* @param a - Lower bound (null = beginning of list)
|
|
28
|
+
* @param b - Upper bound (null = end of list)
|
|
29
|
+
* @returns A string key that sorts between a and b
|
|
30
|
+
*
|
|
31
|
+
* Invariants:
|
|
32
|
+
* - If a is null and b is null, returns a default starting key
|
|
33
|
+
* - If a is null, returns a key before b
|
|
34
|
+
* - If b is null, returns a key after a
|
|
35
|
+
* - generateKeyBetween(a, b) > a && generateKeyBetween(a, b) < b
|
|
36
|
+
*/
|
|
37
|
+
export declare function generateKeyBetween(a: string | null, b: string | null): string;
|
|
38
|
+
/**
|
|
39
|
+
* Generate N evenly-spaced keys between a and b.
|
|
40
|
+
* Useful for initial element ordering or batch inserts.
|
|
41
|
+
*/
|
|
42
|
+
export declare function generateNKeysBetween(a: string | null, b: string | null, n: number): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Validate that a fractional index is well-formed.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isValidFractionalIndex(key: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Compare two fractional index keys.
|
|
49
|
+
* Returns negative if a < b, zero if equal, positive if a > b.
|
|
50
|
+
*/
|
|
51
|
+
export declare function compareFractionalKeys(a: string, b: string): number;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Point } from '../types';
|
|
2
|
+
/** Snap a value to the nearest grid increment */
|
|
3
|
+
export declare function snapToGrid(value: number, gridSize: number): number;
|
|
4
|
+
/** Distance between two points */
|
|
5
|
+
export declare function distance(a: Point, b: Point): number;
|
|
6
|
+
/** Normalize a point pair to top-left + dimensions */
|
|
7
|
+
export declare function normalizeRect(start: Point, end: Point): {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
/** Rotate a point around a center */
|
|
14
|
+
export declare function rotatePoint(point: Point, center: Point, angle: number): Point;
|
|
15
|
+
/** Check if a point is inside a rectangle */
|
|
16
|
+
export declare function isPointInRect(point: Point, rect: {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
}): boolean;
|
|
22
|
+
/** Get diamond polygon points from bounding box */
|
|
23
|
+
export declare function getDiamondPoints(width: number, height: number): number[];
|
|
24
|
+
/**
|
|
25
|
+
* Normalize a point pair to a **symmetric** (square) bounding box.
|
|
26
|
+
* The larger of |dx| or |dy| is used for both dimensions.
|
|
27
|
+
* Origin stays at the `start` corner and expands toward the `end` direction.
|
|
28
|
+
*/
|
|
29
|
+
export declare function normalizeSymmetricRect(start: Point, end: Point): {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
width: number;
|
|
33
|
+
height: number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Constrain an endpoint so the line from `start` → `end` snaps
|
|
37
|
+
* to the nearest 45° increment (0°, 45°, 90°, 135°, …).
|
|
38
|
+
*/
|
|
39
|
+
export declare function constrainLineAngle(start: Point, end: Point): Point;
|
|
40
|
+
/**
|
|
41
|
+
* Compute bounding width/height extents from a flat [x,y,x,y,...] points array.
|
|
42
|
+
* Used by linear element shapes and handles.
|
|
43
|
+
*/
|
|
44
|
+
export declare function computePointsBounds(pts: number[]): {
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
};
|
|
48
|
+
/** Convert dash style string to Konva dash array */
|
|
49
|
+
export declare function getStrokeDash(style: 'solid' | 'dashed' | 'dotted', strokeWidth: number): number[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateId(): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ImageElement, ElementStyle } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Read a File (image) as a base64 data URL.
|
|
4
|
+
*/
|
|
5
|
+
export declare function fileToDataURL(file: File): Promise<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Load an HTMLImageElement from a src URL (data URL or external).
|
|
8
|
+
* Returns the loaded image with its natural dimensions.
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadImage(src: string): Promise<HTMLImageElement>;
|
|
11
|
+
/**
|
|
12
|
+
* Compute element dimensions that fit the image within max bounds
|
|
13
|
+
* while preserving aspect ratio.
|
|
14
|
+
*/
|
|
15
|
+
export declare function computeImageElementDimensions(naturalWidth: number, naturalHeight: number, maxWidth?: number, maxHeight?: number): {
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Create an ImageElement from a loaded image and its data URL.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createImageElement(src: string, naturalWidth: number, naturalHeight: number, x: number, y: number, style?: ElementStyle): ImageElement;
|
|
23
|
+
/**
|
|
24
|
+
* Extract image files from a DataTransfer (paste/drop).
|
|
25
|
+
*/
|
|
26
|
+
export declare function getImageFilesFromDataTransfer(dt: DataTransfer): File[];
|
|
27
|
+
/**
|
|
28
|
+
* Check synchronously whether a ClipboardEvent contains image data.
|
|
29
|
+
* Must be called during the event handler (before any await),
|
|
30
|
+
* because browsers invalidate clipboardData after the handler returns.
|
|
31
|
+
*/
|
|
32
|
+
export declare function extractImageDataFromClipboard(e: ClipboardEvent): {
|
|
33
|
+
file: File | null;
|
|
34
|
+
imgUrl: string | null;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Check synchronously whether a ClipboardEvent has image content.
|
|
38
|
+
*/
|
|
39
|
+
export declare function clipboardHasImage(e: ClipboardEvent): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the extracted clipboard image data into a data URL.
|
|
42
|
+
* This is the async part — call AFTER synchronous extraction.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveImageSource(data: {
|
|
45
|
+
file: File | null;
|
|
46
|
+
imgUrl: string | null;
|
|
47
|
+
}): Promise<string | null>;
|
|
48
|
+
/**
|
|
49
|
+
* Open a file picker dialog for images.
|
|
50
|
+
* Returns an array of selected image files.
|
|
51
|
+
*/
|
|
52
|
+
export declare function openImageFilePicker(): Promise<File[]>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { CanvasElement, ViewportState } from '../types';
|
|
2
|
+
/** Bounding box in world coordinates */
|
|
3
|
+
export interface AABB {
|
|
4
|
+
minX: number;
|
|
5
|
+
minY: number;
|
|
6
|
+
maxX: number;
|
|
7
|
+
maxY: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Compute the visible world-coordinate rectangle from the viewport
|
|
11
|
+
* state and the Stage pixel dimensions.
|
|
12
|
+
* @param viewport - current pan/zoom state
|
|
13
|
+
* @param stageWidth - pixel width of the Stage element
|
|
14
|
+
* @param stageHeight - pixel height of the Stage element
|
|
15
|
+
* @param padding - extra world-space padding around the visible area
|
|
16
|
+
* so that elements slightly outside the viewport are still rendered
|
|
17
|
+
* (avoids pop-in during fast pan). Default 200 world-units.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getVisibleBounds(viewport: ViewportState, stageWidth: number, stageHeight: number, padding?: number): AABB;
|
|
20
|
+
/**
|
|
21
|
+
* Compute the axis-aligned bounding box for a single element.
|
|
22
|
+
* Handles line/arrow elements whose visual extent depends on `points`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getElementAABB(el: CanvasElement): AABB;
|
|
25
|
+
/** Check if two AABBs overlap */
|
|
26
|
+
export declare function aabbOverlaps(a: AABB, b: AABB): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Filter elements to only those visible in the viewport.
|
|
29
|
+
* Elements currently selected are always included (for transformer handles).
|
|
30
|
+
*/
|
|
31
|
+
export declare function cullToViewport(elements: CanvasElement[], viewport: ViewportState, stageWidth: number, stageHeight: number, selectedIds: ReadonlySet<string>, padding?: number): CanvasElement[];
|
|
32
|
+
/** Build an id → element Map for O(1) lookups */
|
|
33
|
+
export declare function buildElementMap(elements: CanvasElement[]): Map<string, CanvasElement>;
|
|
34
|
+
/**
|
|
35
|
+
* Deep clone elements for history snapshots.
|
|
36
|
+
* Uses structuredClone (native, faster than JSON roundtrip)
|
|
37
|
+
* with smart handling for large image data.
|
|
38
|
+
*
|
|
39
|
+
* For ImageElements, we share the `src` string reference instead of
|
|
40
|
+
* duplicating potentially megabytes of base64 data. This is safe
|
|
41
|
+
* because src strings are immutable (never mutated in place).
|
|
42
|
+
*/
|
|
43
|
+
export declare function cloneElementsForHistory(elements: CanvasElement[]): CanvasElement[];
|
|
44
|
+
/**
|
|
45
|
+
* Create a throttled function that runs at most once per animation frame.
|
|
46
|
+
* Unlike lodash throttle, this syncs with the browser paint cycle.
|
|
47
|
+
*/
|
|
48
|
+
export declare function rafThrottle<T extends (...args: any[]) => void>(fn: T): T & {
|
|
49
|
+
cancel: () => void;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Batch multiple element updates into a single store write.
|
|
53
|
+
* Reduces re-renders during complex operations like paste or import.
|
|
54
|
+
*/
|
|
55
|
+
export declare function batchElementUpdates(elements: CanvasElement[], updates: Array<{
|
|
56
|
+
id: string;
|
|
57
|
+
changes: Partial<CanvasElement>;
|
|
58
|
+
}>): CanvasElement[];
|
|
59
|
+
/** Convert an array to a Set for O(1) membership checks */
|
|
60
|
+
export declare function toSet<T>(arr: readonly T[]): ReadonlySet<T>;
|
|
61
|
+
/**
|
|
62
|
+
* Shallow compare two arrays by reference for memoization.
|
|
63
|
+
* Useful for avoiding re-computation when selectedIds hasn't actually changed.
|
|
64
|
+
*/
|
|
65
|
+
export declare function arraysEqual<T>(a: readonly T[], b: readonly T[]): boolean;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight hand-drawn / rough rendering utilities for Konva shapes.
|
|
3
|
+
*
|
|
4
|
+
* Produces a hand-drawn appearance via configurable "Sloppiness"
|
|
5
|
+
* setting and rough.js — bézier curves with jittered control points and
|
|
6
|
+
* angle-aware corner rounding.
|
|
7
|
+
*
|
|
8
|
+
* Sloppiness presets:
|
|
9
|
+
* 0 — Architect (clean, precise lines — no rough effect)
|
|
10
|
+
* 1 — Artist (subtle hand-drawn feel, single stroke pass)
|
|
11
|
+
* 2 — Cartoonist (heavy wobble, double stroke pass)
|
|
12
|
+
*
|
|
13
|
+
* Key improvements over v1:
|
|
14
|
+
* • xorshift seeded PRNG returning [-1,1] with direct string seed
|
|
15
|
+
* • strokeWidth-proportional offset/roundness for consistent look
|
|
16
|
+
* • Angle-aware corner rounding via quadratic bézier (reduces at obtuse angles)
|
|
17
|
+
* • Segment-length clamping to prevent visual artifacts on short edges
|
|
18
|
+
* • Per-pass seeding for more organic overlapping strokes
|
|
19
|
+
* • Cubic bézier ellipse arcs for smoother curves
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Simple hash to convert any string (element ID) to a numeric seed.
|
|
23
|
+
* Uses FNV-1a–style hash.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hashString(str: string): number;
|
|
26
|
+
/**
|
|
27
|
+
* Seeded PRNG using xorshift algorithm.
|
|
28
|
+
* Accepts a string seed directly and returns values in [-1, 1].
|
|
29
|
+
*
|
|
30
|
+
* Based on a standard xorshift PRNG implementation.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createRNG(seed: string | number): () => number;
|
|
33
|
+
/**
|
|
34
|
+
* Minimal subset of Canvas 2D / Konva.Context used by rough helpers.
|
|
35
|
+
* Both `CanvasRenderingContext2D` and Konva's `Context` satisfy this.
|
|
36
|
+
*/
|
|
37
|
+
export interface RoughCtx {
|
|
38
|
+
beginPath(): void;
|
|
39
|
+
moveTo(x: number, y: number): void;
|
|
40
|
+
lineTo(x: number, y: number): void;
|
|
41
|
+
bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void;
|
|
42
|
+
quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void;
|
|
43
|
+
closePath(): void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Number of stroke passes for a given roughness level.
|
|
47
|
+
* - Architect (0): 1 pass (clean)
|
|
48
|
+
* - Artist (1): 1 pass (single hand-drawn stroke)
|
|
49
|
+
* - Cartoonist(2): 2 passes (overlapping sketchy strokes)
|
|
50
|
+
*/
|
|
51
|
+
export declare function getRoughPasses(roughness: number): number;
|
|
52
|
+
/**
|
|
53
|
+
* Draw a rough rectangle stroke (no fill).
|
|
54
|
+
*
|
|
55
|
+
* Uses angle-aware corner rounding for a natural hand-drawn look.
|
|
56
|
+
* Offset and roundness scale proportionally with strokeWidth.
|
|
57
|
+
*/
|
|
58
|
+
export declare function drawRoughRectStrokes(ctx: RoughCtx, x: number, y: number, w: number, h: number, roughness: number, rng: () => number, passes?: number, strokeWidth?: number): void;
|
|
59
|
+
/**
|
|
60
|
+
* Draw a rough ellipse stroke path using cubic bézier arcs.
|
|
61
|
+
*
|
|
62
|
+
* Uses 4 cubic bézier curves (one per quadrant) with radial jitter,
|
|
63
|
+
* producing smoother, more natural curves than the v1 quadratic approach.
|
|
64
|
+
*
|
|
65
|
+
* Builds the path into the context; caller is responsible for `ctx.strokeShape()`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function drawRoughEllipseStrokes(ctx: RoughCtx, cx: number, cy: number, rx: number, ry: number, roughness: number, rng: () => number, passes?: number, strokeWidth?: number): void;
|
|
68
|
+
/**
|
|
69
|
+
* Draw a rough diamond (rhombus) stroke with corner rounding.
|
|
70
|
+
*/
|
|
71
|
+
export declare function drawRoughDiamondStrokes(ctx: RoughCtx, w: number, h: number, roughness: number, rng: () => number, passes?: number, strokeWidth?: number): void;
|
|
72
|
+
/**
|
|
73
|
+
* Generate rough polyline points for Line/Arrow shapes.
|
|
74
|
+
*
|
|
75
|
+
* Returns a new flat array `[x0, y0, x1, y1, …]` with intermediate points
|
|
76
|
+
* jittered. First and last points are kept untouched (they may be bound).
|
|
77
|
+
*/
|
|
78
|
+
export declare function roughPolylinePoints(points: number[], roughness: number, rng: () => number, strokeWidth?: number): number[];
|
|
79
|
+
/**
|
|
80
|
+
* Draw a rough straight-line segment for Line/Arrow shapes.
|
|
81
|
+
*
|
|
82
|
+
* Instead of connecting points with a plain `<Line>`, this draws each segment
|
|
83
|
+
* as a rough bézier, giving a hand-drawn look.
|
|
84
|
+
*
|
|
85
|
+
* @param ctx Konva / Canvas 2D context
|
|
86
|
+
* @param points Flat point array `[x0, y0, x1, y1, …]`
|
|
87
|
+
*/
|
|
88
|
+
export declare function drawRoughPolyline(ctx: RoughCtx, points: number[], roughness: number, rng: () => number, passes?: number, strokeWidth?: number): void;
|
|
89
|
+
/**
|
|
90
|
+
* Draw a rough curved (quadratic bézier) line between two points with a
|
|
91
|
+
* control point. Adds wobble to the curve path.
|
|
92
|
+
*
|
|
93
|
+
* Offset scales with strokeWidth for consistent appearance.
|
|
94
|
+
*/
|
|
95
|
+
export declare function drawRoughCurve(ctx: RoughCtx, start: {
|
|
96
|
+
x: number;
|
|
97
|
+
y: number;
|
|
98
|
+
}, cp: {
|
|
99
|
+
x: number;
|
|
100
|
+
y: number;
|
|
101
|
+
}, end: {
|
|
102
|
+
x: number;
|
|
103
|
+
y: number;
|
|
104
|
+
}, roughness: number, rng: () => number, passes?: number, strokeWidth?: number): void;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { CanvasElement, ViewportState } from '../types';
|
|
2
|
+
/** An item in the R-tree: axis-aligned bounding box + element ID */
|
|
3
|
+
export interface SpatialItem {
|
|
4
|
+
minX: number;
|
|
5
|
+
minY: number;
|
|
6
|
+
maxX: number;
|
|
7
|
+
maxY: number;
|
|
8
|
+
id: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Compute AABB for a single element.
|
|
12
|
+
* Same logic as `getElementAABB` in performance.ts but returns the
|
|
13
|
+
* SpatialItem shape (with `id`) for direct R-tree insertion.
|
|
14
|
+
*/
|
|
15
|
+
export declare function elementToSpatialItem(el: CanvasElement): SpatialItem;
|
|
16
|
+
/**
|
|
17
|
+
* Wrapper around RBush providing element-aware spatial operations.
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* ```ts
|
|
21
|
+
* const index = new SpatialIndex();
|
|
22
|
+
* index.rebuild(elements);
|
|
23
|
+
* const visibleIds = index.queryViewport(viewport, stageWidth, stageHeight);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare class SpatialIndex {
|
|
27
|
+
private tree;
|
|
28
|
+
/** O(1) id → element lookup, rebuilt alongside the tree */
|
|
29
|
+
private _elementMap;
|
|
30
|
+
/**
|
|
31
|
+
* Fattened AABB entries for temporal coherence.
|
|
32
|
+
* Tracks true vs fattened bounds to skip R-tree updates
|
|
33
|
+
* when an element's true AABB still fits within its fattened AABB.
|
|
34
|
+
*/
|
|
35
|
+
private _fattenedMap;
|
|
36
|
+
/**
|
|
37
|
+
* Fattening margin in world-space pixels.
|
|
38
|
+
* Covers ~5-10 frames of typical drag movement at 60fps (2-10 px/frame).
|
|
39
|
+
* Box2D uses ~10% of element size; 50px is a good universal default
|
|
40
|
+
* that covers most interactive adjustments without triggering re-insertion.
|
|
41
|
+
*/
|
|
42
|
+
private _margin;
|
|
43
|
+
constructor(maxEntries?: number);
|
|
44
|
+
/** Get / set the fattening margin (world-space px) */
|
|
45
|
+
get margin(): number;
|
|
46
|
+
set margin(value: number);
|
|
47
|
+
/**
|
|
48
|
+
* Rebuild the entire index from scratch (bulk load).
|
|
49
|
+
* O(n log n) — ~5ms for 10K elements.
|
|
50
|
+
* All entries are freshly fattened.
|
|
51
|
+
*/
|
|
52
|
+
rebuild(elements: CanvasElement[]): void;
|
|
53
|
+
/**
|
|
54
|
+
* Update the spatial position of a single element.
|
|
55
|
+
*
|
|
56
|
+
* Uses temporal coherence: if the element's new true AABB still fits
|
|
57
|
+
* within its fattened AABB, we only update the true bounds (O(1))
|
|
58
|
+
* and skip the expensive R-tree remove + re-insert (O(log n)).
|
|
59
|
+
*
|
|
60
|
+
* Returns `true` if the R-tree was structurally modified,
|
|
61
|
+
* `false` if the update was absorbed by the fattened margin.
|
|
62
|
+
*/
|
|
63
|
+
update(element: CanvasElement): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Remove a single element from the index.
|
|
66
|
+
*/
|
|
67
|
+
remove(id: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Query all elements whose AABB overlaps the given viewport region.
|
|
70
|
+
* Returns element IDs (not full objects) for efficiency.
|
|
71
|
+
*
|
|
72
|
+
* @param viewport - current pan/zoom
|
|
73
|
+
* @param stageWidth - Stage pixel width
|
|
74
|
+
* @param stageHeight - Stage pixel height
|
|
75
|
+
* @param padding - extra world-space padding (default 200)
|
|
76
|
+
*/
|
|
77
|
+
queryViewport(viewport: ViewportState, stageWidth: number, stageHeight: number, padding?: number): string[];
|
|
78
|
+
/**
|
|
79
|
+
* Query all elements whose AABB overlaps the given rectangle.
|
|
80
|
+
* Returns SpatialItems (with id + bounds).
|
|
81
|
+
*/
|
|
82
|
+
queryRect(minX: number, minY: number, maxX: number, maxY: number): SpatialItem[];
|
|
83
|
+
/**
|
|
84
|
+
* Point query — find elements at an exact world coordinate.
|
|
85
|
+
* Useful for hit testing / click detection.
|
|
86
|
+
* Returns matching element IDs.
|
|
87
|
+
*/
|
|
88
|
+
queryPoint(wx: number, wy: number): string[];
|
|
89
|
+
/** O(1) element lookup by ID */
|
|
90
|
+
getElementById(id: string): CanvasElement | undefined;
|
|
91
|
+
/** The full element map (read-only reference) */
|
|
92
|
+
get elementMap(): ReadonlyMap<string, CanvasElement>;
|
|
93
|
+
/** Number of elements in the index */
|
|
94
|
+
get size(): number;
|
|
95
|
+
/**
|
|
96
|
+
* Get the true (non-fattened) AABB for an element.
|
|
97
|
+
* Returns undefined if the element is not in the index.
|
|
98
|
+
*/
|
|
99
|
+
getTrueAABB(id: string): {
|
|
100
|
+
minX: number;
|
|
101
|
+
minY: number;
|
|
102
|
+
maxX: number;
|
|
103
|
+
maxY: number;
|
|
104
|
+
} | undefined;
|
|
105
|
+
/** Clear all data */
|
|
106
|
+
clear(): void;
|
|
107
|
+
}
|
|
108
|
+
/** Get or create a shared SpatialIndex singleton */
|
|
109
|
+
export declare function getSharedSpatialIndex(): SpatialIndex;
|