@magnaboy/layout-core
v0.0.1
Published
Shared canvas geometry, gestures, snapping and React interaction controls.
Downloads
131
Readme
@magnaboy/layout-core
Shared canvas mechanics extracted from the cx-toolkit and cx-live layout editors.
The root export is pure TypeScript. The optional /react export supplies pointer handlers,
keyboard shortcuts, fit measurement, selection controls, snap guides and an error boundary.
Install
pnpm add @magnaboy/layout-coreInstall React 19+ to use /react. Its components import CSS modules; use a bundler that supports
*.module.css (such as Vite). The root export does not import React or CSS.
Geometry
import { resizeRotatedSourceGesture, snapMoveDelta } from '@magnaboy/layout-core';
const resized = resizeRotatedSourceGesture(transform, 'bottom-right', dx, dy, true);
const snapped = snapMoveDelta(rect, { width: 1920, height: 1080 }, 8 / screenScale, otherRects);| Exports | Purpose |
| --- | --- |
| outerScale, screenToCanvasScale, screenDeltaToCanvas, screenPointToCanvas | Convert screen coordinates using fit scale, zoom and pan |
| resizeGesture, resizeSourceGesture, resizeRotatedSourceGesture | Resize with minimum dimensions, crop scaling and rotation anchors |
| cropGesture, rotateGesture, rotateDeltaToLocal, visibleRect | Crop, rotation and visible bounds |
| isCornerHandle, isAxisAligned, MIN_SOURCE_SIZE, NUDGE_DELTAS | Shared gesture rules |
| snapTargetRects, snapMoveDelta, snapResizeDelta | Edge/center snapping and guide positions |
Coordinates and dimensions are canvas pixels; rotations are degrees. A viewport must have positive
dimensions and zoom. Pass the rendered viewport rectangle, not the surrounding editor rectangle.
Snapping thresholds are canvas pixels; divide a screen-pixel threshold by screenToCanvasScale.
Rotated sources are excluded by snapTargetRects; callers also skip move/resize snapping for a
rotated source. Corner resizing preserves aspect ratio when requested. The minimum source size
is 10 canvas pixels.
React adapter
The host owns its source renderers, document, reducer and history. It supplies a structural
CanvasController:
import type { CanvasController } from '@magnaboy/layout-core/react';
const canvas: CanvasController = {
dispatch,
containerRef,
stateRef
};stateRef.current supplies sources, selectedSourceIds and container. Each source needs only
id, transform, locked and visible. The viewport needs width, height, zoom, panX
and panY. Product fields can remain on these objects. Keep the ref current after each host state
change. containerRef points to the rendered viewport.
The dispatch function handles CanvasAction: selectSource, removeSources, multiTransform,
rotate, crop, setZoom, setPan, beginInteraction, endInteraction, undo and redo.
Group updates between beginInteraction and endInteraction into one history entry.
import { SourceControls, useSourceDrag } from '@magnaboy/layout-core/react';
const drag = useSourceDrag({ source, isSelected, manager: canvas, onSnapGuides });
<div {...drag} style={hostSourcePlacement}>
{hostSourceRenderer}
{isSelected && !source.locked && <SourceControls source={source} manager={canvas} onSnapGuides={onSnapGuides} />}
</div>;The host source element provides positioning and rotation. The shared controls use its local
coordinate space. useSourceDrag handles locked sources; render resize controls only for unlocked
sources. Route useCanvasShortcuts to the focused canvas, excluding text inputs. Attach
usePanZoom handlers only to empty canvas areas or middle-button panning. Consume its suppressed
click after a pan so dragging does not deselect sources.
useFitScale observes the supplied root and returns a contain scale. Use scale 1 when fit is
disabled. useSnapGuides avoids redundant guide updates; SnapGuides draws them at constant screen
thickness. SourceBoundary renders nothing after a child fails and retries when resetKey changes.
Boundary
The package contains no Kick integration, network calls, source renderer registry, panels, fonts,
background presets, source properties, document serializer, renderer composition format or reducer.
The applications retain those parts and their existing file formats. It uses @magnaboy/ts-core
for numeric clamping and has no randomness.
Consumer migration after publication
Both existing editors can pass their LayoutManagerApi directly as CanvasController.
Keep local import paths as re-exports while migrating:
| Existing canvas module | Shared export |
| --- | --- |
| coords.ts, gestures.ts, snapping.ts | Named exports from @magnaboy/layout-core |
| SourceControls.tsx, SourceBoundary.tsx, SnapGuides.tsx | Named exports from @magnaboy/layout-core/react |
| useSourceDrag.ts, usePanZoom.ts, useCanvasShortcuts.ts, useFitScale.ts | Named exports from @magnaboy/layout-core/react |
The shared components carry their own handle and guide CSS. Other canvas styling stays local. The copied geometry tests and React interaction tests protect the existing behavior.
Development
From the repository root:
pnpm --filter @magnaboy/layout-core build
pnpm --filter @magnaboy/layout-core test:types
pnpm --filter @magnaboy/layout-core test:unit
pnpm --filter @magnaboy/layout-core check:publish