@hilum/designer-canvas 0.1.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hilum
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @hilum/designer-canvas
2
+
3
+ Generic free-positioned canvas engine for Hilum apps. Pluggable Layer model, pan/zoom viewport, drag/resize/marquee interactions, snap guides, and a curated set of 49 generic actions (alignment, z-order, opacity, fill, transform, etc.).
4
+
5
+ The package ships **no concrete renderers** — apps register their own via the renderer registry. Advanced features (text-on-paths, custom warps, font registries, thumbnails) are exposed through optional injected services (`PathService`, `FontService`).
6
+
7
+ ```tsx
8
+ import { CanvasProvider, DesignerCanvas, DesignerFrame } from "@hilum/designer-canvas"
9
+ import { GeometryService, FontService } from "@/services"
10
+
11
+ <CanvasProvider
12
+ defaultLayers={layers}
13
+ frameSize={{ width: 800, height: 600 }}
14
+ services={{ paths: GeometryService, fonts: FontService }}
15
+ renderers={{ rect: RectRenderer, text: TextRenderer }}
16
+ >
17
+ <DesignerCanvas>
18
+ <DesignerFrame />
19
+ </DesignerCanvas>
20
+ </CanvasProvider>
21
+ ```
22
+
23
+ ## Architecture
24
+
25
+ - **State:** generic `Layer<TData>` shape with first-class typed geometry (x, y, width, height, rotation, opacity). Apps narrow `data` for their kind.
26
+ - **Selection / tool / history:** delegated to `@hilum/designer`'s ShellContext.
27
+ - **Actions:** 49 generic ones bundled (alignment, z-order, opacity, fill, transform, etc.). 6 require small abstractions (unit converter, font provider, image upload).
28
+ - **Services:** optional. Pappery injects WASM-backed `PathService` and `FontService`; apps without them use the no-op fallbacks.
29
+
30
+ See `PHASE_0_AUDIT.md` §P0.6 for the service interface and §P0.4 for the Layer shape.
@@ -0,0 +1,629 @@
1
+ import * as React from 'react';
2
+ import { Dispatch, ReactNode, ComponentType, RefObject } from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+
5
+ /**
6
+ * Generic Layer shape. Apps narrow `TData` per kind.
7
+ *
8
+ * Geometry (x, y, width, height, rotation, opacity) is first-class typed
9
+ * — Pappery used to bury these in `cssVars: Record<string,string>`. The
10
+ * package's reducer reads them directly.
11
+ *
12
+ * `type` is an open string; apps register renderers per type via the
13
+ * RendererProvider. Apps narrow it via TypeScript discriminated unions:
14
+ *
15
+ * type AppLayer =
16
+ * | Layer<TextData> & { type: 'text' }
17
+ * | Layer<ImageData> & { type: 'image' }
18
+ */
19
+ interface Layer<TData = Record<string, unknown>> {
20
+ id: string;
21
+ type: string;
22
+ name?: string;
23
+ /** Position in the artboard's coordinate system (typically pixels). */
24
+ x: number;
25
+ y: number;
26
+ width: number;
27
+ height: number;
28
+ /** Degrees, clockwise. Optional — defaults to 0. */
29
+ rotation?: number;
30
+ /** 0–1. Optional — defaults to 1. */
31
+ opacity?: number;
32
+ /** Render-time flags. */
33
+ isLocked?: boolean;
34
+ isVisible?: boolean;
35
+ /** Used by group / ungroup actions. */
36
+ groupId?: string;
37
+ /** Optional grid placement (set when canvas is in grid mode). */
38
+ gridItem?: GridItem;
39
+ /** Kind-specific fields. Apps narrow this. */
40
+ data: TData;
41
+ }
42
+ interface GridItem {
43
+ /** Column start (1-based, like CSS grid-column). */
44
+ x: number;
45
+ /** Row start. */
46
+ y: number;
47
+ /** Column span. */
48
+ w: number;
49
+ /** Row span. */
50
+ h: number;
51
+ }
52
+ interface FrameSize {
53
+ width: number;
54
+ height: number;
55
+ }
56
+ interface GridContainer {
57
+ /** Number of columns. */
58
+ cols: number;
59
+ /** Number of rows. */
60
+ rows: number;
61
+ /** Gap between cells (px). */
62
+ gap: number;
63
+ /** Padding around the grid (px). */
64
+ padding: number;
65
+ }
66
+ interface PanState {
67
+ x: number;
68
+ y: number;
69
+ }
70
+ interface LayerTypeDescriptor<TData = unknown> {
71
+ /** Layer kind identifier (matches `Layer.type`). */
72
+ type: string;
73
+ /** Display label for menus, picker UIs. */
74
+ label: string;
75
+ /** Default `data` payload when adding a new layer of this kind. */
76
+ defaultData: TData;
77
+ /** Default size when adding a new layer of this kind. */
78
+ defaultSize?: {
79
+ width: number;
80
+ height: number;
81
+ };
82
+ /** Optional icon component. */
83
+ icon?: React.ComponentType<{
84
+ size?: number;
85
+ className?: string;
86
+ }>;
87
+ }
88
+ type SurfaceTheme = "light" | "mid" | "dark";
89
+
90
+ interface TextMetrics {
91
+ ascent: number;
92
+ descent: number;
93
+ width: number;
94
+ height: number;
95
+ bounds: {
96
+ x: number;
97
+ y: number;
98
+ width: number;
99
+ height: number;
100
+ };
101
+ }
102
+ interface ShadowResult {
103
+ path: string;
104
+ shadow: string;
105
+ blur: number;
106
+ }
107
+ interface OutlineResult {
108
+ path: string;
109
+ outline: string;
110
+ width: number;
111
+ }
112
+ interface PathEffectResult {
113
+ path: string;
114
+ shadow: string;
115
+ needsBlur: boolean;
116
+ blur: number;
117
+ }
118
+ /**
119
+ * Path / geometry service. Pappery injects a WASM-backed implementation; apps
120
+ * that don't need text-on-paths or warps can omit this.
121
+ */
122
+ interface PathService {
123
+ /** Boolean union of two SVG path strings. */
124
+ union(pathA: string, pathB: string): string;
125
+ /** SVG path for text rendered in a registered font. */
126
+ getTextPath(text: string, fontName: string, fontSize: number, options?: {
127
+ letterSpacing?: number;
128
+ underline?: boolean;
129
+ strikethrough?: boolean;
130
+ }): string;
131
+ /** Detailed metrics for laid-out text. */
132
+ getTextMetrics(text: string, fontName: string, fontSize: number, options?: {
133
+ letterSpacing?: number;
134
+ }): TextMetrics;
135
+ /** Warp a path — arch, wave, flag, custom kinds. */
136
+ warpPath(pathData: string, warpType: string, amount: number): string;
137
+ /** Simple shape constructors. */
138
+ createRectangle(width: number, height: number): string;
139
+ createTriangle(width: number, height: number): string;
140
+ createCircle(radius: number): string;
141
+ /** Path effects. */
142
+ getShadow(pathData: string, offset: number, angle: number, blur: number): ShadowResult;
143
+ getOutline(pathData: string, outlineWidth: number): OutlineResult;
144
+ getPathEffect(pathData: string, shadowType: string, offset: number, angle: number, blur: number, outlineWidth: number): PathEffectResult;
145
+ /** Pattern fills. */
146
+ getDecorationPattern(pathData: string, pattern: string, lineWeight: number, lineDistance: number, baseSize: number): string;
147
+ /** Layer thumbnails (Worker-friendly). */
148
+ renderThumbnail(layersJson: string, frameWidth: number, frameHeight: number, outputWidth: number, outputHeight: number): Uint8Array | null;
149
+ }
150
+ interface FontDescriptor {
151
+ family: string;
152
+ /** App-defined source enum (Pappery: 'google'|'local'|'upload'). */
153
+ source: string;
154
+ loaded: boolean;
155
+ weights?: number[];
156
+ }
157
+ /** Font orchestration service. */
158
+ interface FontService {
159
+ fonts: FontDescriptor[];
160
+ loadFont(family: string, weight?: string | number): Promise<string>;
161
+ getAvailableWeights(family: string): number[];
162
+ isFontLoaded(family: string, weight?: string | number): boolean;
163
+ getFontKey(family: string, weight?: string | number): string;
164
+ }
165
+ /** Convert between display unit (e.g. mm) and the canvas's internal pixels. */
166
+ interface UnitConverter {
167
+ /** Display value → internal pixels. */
168
+ toPx(value: number): number;
169
+ /** Internal pixels → display value. */
170
+ fromPx(pixels: number): number;
171
+ /** Symbol (e.g. "px", "mm", "in"). Used as suffix in InputNumber. */
172
+ unit: string;
173
+ /** Decimal precision for display. Default: 0. */
174
+ precision?: number;
175
+ }
176
+ /** Pluggable image upload. Default: in-memory blob URL. */
177
+ type UploadImage = (file: File) => Promise<{
178
+ url: string;
179
+ assetId?: string;
180
+ }>;
181
+ interface CanvasServices {
182
+ paths?: PathService | null;
183
+ fonts?: FontService | null;
184
+ /** Unit display / conversion. Default: identity / "px". */
185
+ units?: UnitConverter | null;
186
+ /** Image upload. Default: blob URL. */
187
+ uploadImage?: UploadImage | null;
188
+ }
189
+
190
+ interface CanvasState<TData = Record<string, unknown>> {
191
+ /** All layers in z-order. Index 0 = bottom. */
192
+ layers: Layer<TData>[];
193
+ /** View state. */
194
+ zoom: number;
195
+ pan: PanState;
196
+ /** Artboard / frame. */
197
+ frameSize: FrameSize;
198
+ artboardName: string;
199
+ artboardColor: string;
200
+ artboardOpacity: number;
201
+ /** Whether content is clipped to the frame bounds. */
202
+ artboardClipContent: boolean;
203
+ /** Optional grid layout mode. When set, layers can use `gridItem`. */
204
+ gridContainer?: GridContainer;
205
+ /** Clipboard: layers that have been copied / cut. */
206
+ copiedLayers: Layer<TData>[] | null;
207
+ /** Theme — chrome reads from here, components reference Hilum semantic vars. */
208
+ uiColor: SurfaceTheme;
209
+ canvasColor: SurfaceTheme;
210
+ /** Editor accent (selection handles, snap guides). Defaults to Hilum brand-primary via CSS var. */
211
+ accentColor: string;
212
+ /** Renderer registry: descriptors for the layer kinds the app supports. */
213
+ layerTypes: LayerTypeDescriptor[];
214
+ /** When true, mutating actions no-op. */
215
+ readOnly: boolean;
216
+ }
217
+ declare function createInitialState<TData>(partial?: Partial<CanvasState<TData>>): CanvasState<TData>;
218
+
219
+ type CanvasAction<TData = Record<string, unknown>> = {
220
+ type: "SET_LAYERS";
221
+ payload: Layer<TData>[];
222
+ } | {
223
+ type: "ADD_LAYER";
224
+ payload: Layer<TData>;
225
+ } | {
226
+ type: "UPDATE_LAYER";
227
+ payload: {
228
+ id: string;
229
+ updates: Partial<Layer<TData>>;
230
+ };
231
+ } | {
232
+ type: "UPDATE_LAYERS";
233
+ payload: Array<{
234
+ id: string;
235
+ updates: Partial<Layer<TData>>;
236
+ }>;
237
+ } | {
238
+ type: "DELETE_LAYER";
239
+ payload: string;
240
+ } | {
241
+ type: "DELETE_LAYERS";
242
+ payload: string[];
243
+ } | {
244
+ type: "REORDER_LAYERS";
245
+ payload: {
246
+ fromIndex: number;
247
+ toIndex: number;
248
+ };
249
+ } | {
250
+ type: "NUDGE_LAYERS";
251
+ payload: {
252
+ targetLayerIds: string[];
253
+ dx: number;
254
+ dy: number;
255
+ };
256
+ } | {
257
+ type: "ALIGN_LAYERS";
258
+ payload: {
259
+ targetLayerIds: string[];
260
+ align: "left" | "center" | "right" | "top" | "middle" | "bottom";
261
+ };
262
+ } | {
263
+ type: "DISTRIBUTE_LAYERS";
264
+ payload: {
265
+ targetLayerIds: string[];
266
+ axis: "horizontal" | "vertical";
267
+ };
268
+ } | {
269
+ type: "GROUP_LAYERS";
270
+ payload: {
271
+ targetLayerIds: string[];
272
+ groupId: string;
273
+ };
274
+ } | {
275
+ type: "UNGROUP_LAYERS";
276
+ payload: {
277
+ targetLayerIds: string[];
278
+ };
279
+ } | {
280
+ type: "ARRANGE_LAYERS";
281
+ payload: {
282
+ targetLayerIds: string[];
283
+ mode: "front" | "back" | "forward" | "backward";
284
+ };
285
+ } | {
286
+ type: "TRANSFORM_LAYERS";
287
+ payload: {
288
+ targetLayerIds: string[];
289
+ mode: "flip-h" | "flip-v";
290
+ };
291
+ } | {
292
+ type: "SET_LAYER_PROPERTY";
293
+ payload: {
294
+ targetLayerIds: string[];
295
+ key: keyof Layer<TData>;
296
+ value: unknown;
297
+ };
298
+ } | {
299
+ type: "SET_ZOOM";
300
+ payload: number;
301
+ } | {
302
+ type: "SET_PAN";
303
+ payload: {
304
+ x: number;
305
+ y: number;
306
+ };
307
+ } | {
308
+ type: "SET_FRAME_SIZE";
309
+ payload: {
310
+ width: number;
311
+ height: number;
312
+ };
313
+ } | {
314
+ type: "SET_ARTBOARD_NAME";
315
+ payload: string;
316
+ } | {
317
+ type: "SET_ARTBOARD_COLOR";
318
+ payload: string;
319
+ } | {
320
+ type: "SET_ARTBOARD_OPACITY";
321
+ payload: number;
322
+ } | {
323
+ type: "SET_ARTBOARD_CLIP_CONTENT";
324
+ payload: boolean;
325
+ } | {
326
+ type: "SET_GRID_CONTAINER";
327
+ payload: GridContainer;
328
+ } | {
329
+ type: "CLEAR_GRID_CONTAINER";
330
+ } | {
331
+ type: "UPDATE_GRID_CONTAINER";
332
+ payload: Partial<GridContainer>;
333
+ } | {
334
+ type: "SET_UI_COLOR";
335
+ payload: "light" | "mid" | "dark";
336
+ } | {
337
+ type: "SET_CANVAS_COLOR";
338
+ payload: "light" | "mid" | "dark";
339
+ } | {
340
+ type: "SET_ACCENT_COLOR";
341
+ payload: string;
342
+ } | {
343
+ type: "COPY_LAYERS";
344
+ payload: {
345
+ targetLayerIds: string[];
346
+ };
347
+ } | {
348
+ type: "PASTE_LAYERS";
349
+ payload?: {
350
+ offset?: {
351
+ dx: number;
352
+ dy: number;
353
+ };
354
+ };
355
+ } | {
356
+ type: "CLEAR_CLIPBOARD";
357
+ } | {
358
+ type: "SET_READ_ONLY";
359
+ payload: boolean;
360
+ };
361
+ declare function canvasReducer<TData>(state: CanvasState<TData>, action: CanvasAction<TData>): CanvasState<TData>;
362
+ declare function alignLayers<TData>(layers: Layer<TData>[], ids: string[], align: "left" | "center" | "right" | "top" | "middle" | "bottom"): Layer<TData>[];
363
+ declare function distributeLayers<TData>(layers: Layer<TData>[], ids: string[], axis: "horizontal" | "vertical"): Layer<TData>[];
364
+ declare function arrangeLayers<TData>(layers: Layer<TData>[], ids: string[], mode: "front" | "back" | "forward" | "backward"): Layer<TData>[];
365
+ declare const __test: {
366
+ alignLayers: typeof alignLayers;
367
+ distributeLayers: typeof distributeLayers;
368
+ arrangeLayers: typeof arrangeLayers;
369
+ };
370
+
371
+ interface CanvasContextValue<TData = Record<string, unknown>> {
372
+ state: CanvasState<TData>;
373
+ dispatch: Dispatch<CanvasAction<TData>>;
374
+ services: CanvasServices;
375
+ /** Bumped on each commit. Apps can use it as a memoization key. */
376
+ revision: number;
377
+ }
378
+ declare function useCanvasContext<TData = Record<string, unknown>>(): CanvasContextValue<TData>;
379
+ declare const CanvasContextProvider: React.Provider<CanvasContextValue<unknown>>;
380
+
381
+ interface CanvasProviderProps<TData = Record<string, unknown>> {
382
+ initial?: Partial<CanvasState<TData>>;
383
+ /** Optional injected services — see PHASE_0_AUDIT.md §P0.6. */
384
+ services?: CanvasServices;
385
+ /** Read-only mode (e.g. for thumbnails). */
386
+ readOnly?: boolean;
387
+ /** Receive every state transition. Useful for syncing to external storage. */
388
+ onChange?: (state: CanvasState<TData>) => void;
389
+ children: ReactNode;
390
+ }
391
+ /**
392
+ * Mounts both ShellContext (from @hilum/designer) and CanvasContext.
393
+ * Selection lives in ShellContext; layers / viewport / artboard live here.
394
+ *
395
+ * The reducer is generic on TData; apps narrow it by passing a typed
396
+ * `initial.layerTypes` array.
397
+ */
398
+ declare function CanvasProvider<TData = Record<string, unknown>>({ initial, services, readOnly, onChange, children, }: CanvasProviderProps<TData>): react_jsx_runtime.JSX.Element;
399
+
400
+ /**
401
+ * Renderer for a single layer kind. Receives the layer + a few context
402
+ * flags. Apps register one of these per `layer.type` they support.
403
+ *
404
+ * Position / size / rotation / opacity are applied by the package via a
405
+ * wrapper element — the renderer should focus on the **content** of the
406
+ * layer (text, image, shape path, etc.). It can use `data` for kind-specific
407
+ * fields.
408
+ */
409
+ interface LayerRendererContext {
410
+ selected: boolean;
411
+ /** Current zoom — useful for scaling stroke width or rendering text crisply. */
412
+ zoom: number;
413
+ /** Read-only mode (no interactivity). */
414
+ readOnly: boolean;
415
+ }
416
+ type LayerRendererProps<TData = unknown> = {
417
+ layer: Layer<TData>;
418
+ ctx: LayerRendererContext;
419
+ };
420
+ type LayerRenderer<TData = unknown> = ComponentType<LayerRendererProps<TData>>;
421
+ /** Map of `layer.type` → renderer. */
422
+ type RendererRegistry = Record<string, LayerRenderer<unknown>>;
423
+
424
+ interface RendererProviderProps {
425
+ /** Map of layer.type → renderer. Apps register their kinds here. */
426
+ renderers: RendererRegistry;
427
+ children: ReactNode;
428
+ }
429
+ /**
430
+ * Pluggable renderer registry. Apps mount this near the canvas root and
431
+ * pass `{ [layerType]: Component }`. The package never ships concrete
432
+ * renderers — Pappery's text/image/shape renderers stay in Pappery, and
433
+ * each app supplies its own per layer kind.
434
+ */
435
+ declare function RendererProvider({ renderers, children }: RendererProviderProps): react_jsx_runtime.JSX.Element;
436
+ declare function useLayerRenderer(type: string): LayerRenderer<unknown> | undefined;
437
+ declare function useAllRenderers(): RendererRegistry;
438
+
439
+ interface DesignerProps<TData = Record<string, unknown>> {
440
+ /** Initial canvas state. Layers, frame size, theme. */
441
+ initial?: Partial<CanvasState<TData>>;
442
+ /** Renderers per layer type. */
443
+ renderers?: RendererRegistry;
444
+ /** Optional injected services (paths, fonts, units, image upload). */
445
+ services?: CanvasServices;
446
+ /** Read-only mode (e.g. for previews). */
447
+ readOnly?: boolean;
448
+ /** Receive every state transition. */
449
+ onChange?: (state: CanvasState<TData>) => void;
450
+ children: ReactNode;
451
+ }
452
+ /**
453
+ * Root provider for a canvas-editor app. Mounts ShellProvider (selection,
454
+ * tool, read-only) + CanvasProvider (layers, viewport, artboard) +
455
+ * RendererProvider (per-type renderers).
456
+ *
457
+ * Place <DesignerCanvas><DesignerFrame /></DesignerCanvas> inside, or
458
+ * compose with @hilum/designer's chrome (DesignerShell, Toolbar, etc.).
459
+ */
460
+ declare function Designer<TData = Record<string, unknown>>({ initial, renderers, services, readOnly, onChange, children, }: DesignerProps<TData>): react_jsx_runtime.JSX.Element;
461
+
462
+ interface DesignerCanvasProps {
463
+ className?: string;
464
+ children?: ReactNode;
465
+ }
466
+ /**
467
+ * Pan / zoom viewport. Wraps an inner transform layer that applies the
468
+ * canvas state's pan + zoom. Children (typically `<DesignerFrame>` and
469
+ * overlays) are rendered inside the transform.
470
+ */
471
+ declare function DesignerCanvas({ className, children }: DesignerCanvasProps): react_jsx_runtime.JSX.Element;
472
+
473
+ interface DesignerFrameProps {
474
+ className?: string;
475
+ /** Hide the selection overlay (e.g. for a custom one). */
476
+ hideSelectionOverlay?: boolean;
477
+ /** Hide the marquee selection box. */
478
+ hideMarquee?: boolean;
479
+ /** Show the grid overlay. */
480
+ showGrid?: boolean;
481
+ children?: ReactNode;
482
+ }
483
+ /**
484
+ * Interactive layer container. Renders the artboard, all layers via the
485
+ * RendererProvider, the marquee selection box, the selection handles, and
486
+ * an optional grid.
487
+ */
488
+ declare function DesignerFrame({ className, hideSelectionOverlay, hideMarquee, showGrid, children, }: DesignerFrameProps): react_jsx_runtime.JSX.Element;
489
+
490
+ interface DesignerStaticFrameProps {
491
+ className?: string;
492
+ /** Override frame size — used by thumbnails to render at a custom resolution. */
493
+ width?: number;
494
+ height?: number;
495
+ }
496
+ /**
497
+ * Read-only render of the canvas — no interactivity, no overlays. Use for
498
+ * dashboard thumbnails or print previews. The same renderers from
499
+ * <RendererProvider> are used; their `ctx.readOnly` will be true.
500
+ */
501
+ declare function DesignerStaticFrame({ className, width, height }: DesignerStaticFrameProps): react_jsx_runtime.JSX.Element;
502
+
503
+ /**
504
+ * Visual grid overlay — renders a SVG pattern based on `state.gridContainer`.
505
+ * Non-interactive; sits behind the layers but inside the artboard.
506
+ */
507
+ declare function GridOverlay(): react_jsx_runtime.JSX.Element | null;
508
+
509
+ /**
510
+ * Renders blue selection rectangles around currently-selected layers.
511
+ * Non-interactive — drag/resize handles can be added in a future iteration.
512
+ */
513
+ declare function LayerSelectionOverlay(): react_jsx_runtime.JSX.Element | null;
514
+
515
+ interface MarqueeOverlayProps {
516
+ /** Ref to the frame element this marquee is constrained to. */
517
+ containerRef: RefObject<HTMLDivElement | null>;
518
+ }
519
+ /**
520
+ * Click-and-drag marquee selection. Listens for pointerdown on the frame
521
+ * background; on drag, paints a translucent rectangle and selects all
522
+ * layers whose bounding box intersects it on pointerup.
523
+ */
524
+ declare function MarqueeOverlay({ containerRef }: MarqueeOverlayProps): react_jsx_runtime.JSX.Element | null;
525
+
526
+ declare function useLayers<TData = Record<string, unknown>>(): Layer<TData>[];
527
+ declare function useLayer<TData = Record<string, unknown>>(id: string): Layer<TData> | undefined;
528
+ declare function useSelectedLayerIds(): string[];
529
+ declare function useSelectedLayers<TData = Record<string, unknown>>(): Layer<TData>[];
530
+ declare function useSelectedLayer<TData = Record<string, unknown>>(): Layer<TData> | undefined;
531
+ declare function useIsLayerSelected(id: string): boolean;
532
+
533
+ interface UseZoomReturn {
534
+ zoom: number;
535
+ setZoom: (next: number) => void;
536
+ zoomIn: () => void;
537
+ zoomOut: () => void;
538
+ resetZoom: () => void;
539
+ /** Fit zoom to bring the artboard into view, given an outer container. */
540
+ fitZoom: (container: {
541
+ width: number;
542
+ height: number;
543
+ }, padding?: number) => void;
544
+ }
545
+ declare function useZoom(): UseZoomReturn;
546
+
547
+ interface UseDragInteractionParams {
548
+ layerId: string;
549
+ /** Pixel scale — typically `1 / zoom`. */
550
+ scale?: number;
551
+ }
552
+ /**
553
+ * Pointer-driven drag for a single layer. Returns props to spread on the
554
+ * draggable element. Holds Shift for axis-locked movement.
555
+ *
556
+ * Multi-select drag (move all selected layers) is handled by spreading
557
+ * onPointerDown on each selected frame and dispatching UPDATE_LAYERS in the
558
+ * same frame — this hook handles the single-layer case.
559
+ */
560
+ declare function useDragInteraction({ layerId, scale }: UseDragInteractionParams): {
561
+ dragging: boolean;
562
+ onPointerDown: (e: React.PointerEvent) => void;
563
+ };
564
+
565
+ /**
566
+ * Wires `@hilum/designer`'s generic `useHistory<T>` to the canvas reducer.
567
+ *
568
+ * Apps don't usually use this directly — `<Designer>` mounts it. The pattern:
569
+ * the reducer commits state on every dispatch; this hook syncs the layers
570
+ * slice into the history stack when changes are user-initiated.
571
+ *
572
+ * For now, the canvas's reducer doesn't directly track history (history lives
573
+ * in @hilum/designer's hook). Apps that want canvas-level undo/redo wire
574
+ * `useHistory<Layer[]>(initialLayers)` and dispatch `SET_LAYERS` to apply
575
+ * undo / redo results.
576
+ */
577
+ declare function useHistoryActions<TData = Record<string, unknown>>(): {
578
+ undo: () => void;
579
+ redo: () => void;
580
+ canUndo: boolean;
581
+ canRedo: boolean;
582
+ };
583
+
584
+ /**
585
+ * Renders one toolbar button per registered layer type. Apps that want a
586
+ * single + dropdown can build their own using <DropdownMenu> and the same
587
+ * dispatch call.
588
+ */
589
+ declare function ActionAddLayer(): react_jsx_runtime.JSX.Element;
590
+
591
+ /**
592
+ * Alignment + distribution toolbar group. Operates on currently-selected
593
+ * layers via ShellContext.selectedIds (passed explicitly to the reducer).
594
+ */
595
+ declare function ActionAlign(): react_jsx_runtime.JSX.Element;
596
+
597
+ /** Bring to front / back / forward / backward. */
598
+ declare function ActionArrange(): react_jsx_runtime.JSX.Element;
599
+
600
+ declare function ActionDelete(): react_jsx_runtime.JSX.Element;
601
+
602
+ declare function ActionDuplicate(): react_jsx_runtime.JSX.Element;
603
+
604
+ declare function ActionGroup(): react_jsx_runtime.JSX.Element;
605
+
606
+ declare function ActionLock(): react_jsx_runtime.JSX.Element;
607
+
608
+ interface ToolDescriptor {
609
+ id: string;
610
+ label: string;
611
+ icon: ComponentType<{
612
+ size?: number;
613
+ className?: string;
614
+ }>;
615
+ shortcut?: string;
616
+ }
617
+ interface ActionToolProps {
618
+ /** Override default tools. */
619
+ tools?: ToolDescriptor[];
620
+ }
621
+ declare function ActionTool({ tools }: ActionToolProps): react_jsx_runtime.JSX.Element;
622
+
623
+ declare function ActionTransform(): react_jsx_runtime.JSX.Element;
624
+
625
+ declare function ActionUndoRedo(): react_jsx_runtime.JSX.Element;
626
+
627
+ declare function ActionZoom(): react_jsx_runtime.JSX.Element;
628
+
629
+ export { ActionAddLayer, ActionAlign, ActionArrange, ActionDelete, ActionDuplicate, ActionGroup, ActionLock, ActionTool, type ActionToolProps, ActionTransform, ActionUndoRedo, ActionZoom, type CanvasAction, CanvasContextProvider, type CanvasContextValue, CanvasProvider, type CanvasServices, type CanvasState, Designer, DesignerCanvas, type DesignerCanvasProps, DesignerFrame, type DesignerFrameProps, type DesignerProps, DesignerStaticFrame, type DesignerStaticFrameProps, type FontDescriptor, type FontService, type FrameSize, type GridContainer, type GridItem, GridOverlay, type Layer, type LayerRenderer, type LayerRendererContext, type LayerRendererProps, LayerSelectionOverlay, type LayerTypeDescriptor, MarqueeOverlay, type MarqueeOverlayProps, type OutlineResult, type PanState, type PathEffectResult, type PathService, RendererProvider, type RendererRegistry, type ShadowResult, type SurfaceTheme, type TextMetrics, type ToolDescriptor, type UnitConverter, type UploadImage, __test, canvasReducer, createInitialState, useAllRenderers, useCanvasContext, useDragInteraction, useHistoryActions, useIsLayerSelected, useLayer, useLayerRenderer, useLayers, useSelectedLayer, useSelectedLayerIds, useSelectedLayers, useZoom };