Shape Utils — @edv4h/usketch-shape-utils
Common helpers for writing uSketch shape plugins.
@edv4h/usketch-shape-utils provides common functions for writing uSketch shape plugins. It is a set of React-independent pure functions, so that third parties building their own plugins (e.g. @acme/usketch-plugin-shape-basic) can reuse boilerplate such as bounds calculation, hit testing, resizing, editable text, and free-position search.
Install
pnpm add @edv4h/usketch-shape-utils @edv4h/usketch-shared
Bounds
function getBounds(data: ShapeData): BoundingBox;
Returns the shape’s AABB as { x, y, width, height }. Most shapes can pass this straight to ShapeDefinition.getBounds.
Hit Test
function aabbHitTest(data: ShapeData, point: Point): boolean;
function ellipseHitTest(data: ShapeData, point: Point): boolean;
function lineHitTest(data: ShapeData, point: Point, tolerance?: number): boolean;
function pointInPolygon(point: Point, polygon: Point[]): boolean;
| Function | Purpose |
|---|---|
aabbHitTest | Rectangular AABB |
ellipseHitTest | Ellipse |
lineHitTest | Line segment (true within tolerance px, default 4) |
pointInPolygon | Arbitrary polygon (triangle, hexagon, star, etc.) |
For rotation support, wrap the hit test with withRotation(hitTest) from @edv4h/usketch-shared:
import { withRotation } from "@edv4h/usketch-shared";
import { pointInPolygon } from "@edv4h/usketch-shape-utils";
const hitTest = withRotation((data, point) =>
pointInPolygon(point, getHexagonPoints(data)),
);
Resize
function createResize(minW: number, minH: number): (
data: ShapeData,
handle: ResizeHandle,
delta: Point,
) => ShapeData;
Generates a resize function that handles all 8 resize handles (se / nw / ne / sw / e / w / n / s). Specify the minimum width and height.
import { createResize } from "@edv4h/usketch-shape-utils";
const resize = createResize(10, 10); // minimum 10x10 px
Editable Text
Helpers for wiring contentEditable-based in-place text editing into a shape plugin.
function createEditableTextController(
ctx: PluginContext,
options: EditableTextOptions,
): EditableTextController;
function editableTextProps(id: string, text: string): { /* props for a contentEditable div */ };
// Custom event names the controller listens for
const TEXT_INPUT_EVENT: string; // "usketch:text-input"
const TEXT_BLUR_EVENT: string; // "usketch:text-blur"
const TEXT_ESCAPE_EVENT: string; // "usketch:text-escape"
createEditableTextController wires up the shared editable-text state machine plus DOM/canvas listeners, returning a controller with beginEdit / isEditing / teardown. editableTextProps returns props to spread onto the editor div (a plain div for HTML shapes, or a div inside <foreignObject> for SVG shapes) and handles IME composition, Escape, blur, and one-time focus. The EditableTextOptions / EditableTextController types are also exported.
Free Position
Helpers for placing a new shape at a free spot that doesn’t overlap existing shapes.
function findFreePosition(opts: FindFreePositionOptions): BoundingBox;
function overlapsAny(box: BoundingBox, occupied: BoundingBox[]): boolean;
type FreePositionStrategy = "ring" | "push";
findFreePosition searches for a free rectangle from the desired position (desired) and the rectangles to avoid (occupied) using the "ring" (default) or "push" strategy. overlapsAny reports whether a box overlaps any of the occupied rectangles. The FindFreePositionOptions / FreePositionStrategy types are also exported.
A note on GPU Primitives
The GPU primitive helpers (rectGpuPrimitive and friends) are not part of this package. They live in @edv4h/usketch-plugin-shape-basic (plugins/usketch-plugin-shape-basic) — see that package for GPU rendering support. The ShapeDefinition.gpuPrimitive hook itself can be implemented freely by each shape definition.
Related
- Building a Shape Plugin — the basics of shape plugins
- Third-Party Plugin Authoring — full guide to using shape-utils as an external package
- Shared Types — type definitions for
ShapeData/ResizeHandle/Point, etc.