@tsdraw/core 0.6.0 → 0.7.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/dist/index.d.cts CHANGED
@@ -11,6 +11,7 @@ interface DrawSegment {
11
11
  }
12
12
  type SizeStyle = 's' | 'm' | 'l' | 'xl';
13
13
  type DashStyle = 'draw' | 'solid' | 'dashed' | 'dotted';
14
+ type FillStyle = 'none' | 'semi' | 'solid' | 'blank';
14
15
  type ColorStyle = string;
15
16
  interface DrawShape {
16
17
  id: ShapeId;
@@ -20,6 +21,7 @@ interface DrawShape {
20
21
  props: {
21
22
  color: ColorStyle;
22
23
  dash: DashStyle;
24
+ fill?: FillStyle;
23
25
  size: SizeStyle;
24
26
  scale: number;
25
27
  isPen: boolean;
@@ -66,6 +68,7 @@ interface TsdrawSessionStateSnapshot {
66
68
  drawStyle: {
67
69
  color: ColorStyle;
68
70
  dash: DashStyle;
71
+ fill?: FillStyle;
69
72
  size: SizeStyle;
70
73
  };
71
74
  selectedShapeIds: ShapeId[];
@@ -168,11 +171,13 @@ interface IEditor {
168
171
  getCurrentDrawStyle(): {
169
172
  color: ColorStyle;
170
173
  dash: DashStyle;
174
+ fill: FillStyle;
171
175
  size: SizeStyle;
172
176
  };
173
177
  setCurrentDrawStyle(partial: Partial<{
174
178
  color: ColorStyle;
175
179
  dash: DashStyle;
180
+ fill: FillStyle;
176
181
  size: SizeStyle;
177
182
  }>): void;
178
183
  panBy(dx: number, dy: number): void;
@@ -246,9 +251,10 @@ declare class CanvasRenderer implements ICanvasRenderer {
246
251
  render(ctx: CanvasRenderingContext2D, viewport: Viewport, shapes: Shape[]): void;
247
252
  private paintStroke;
248
253
  private paintDashedStroke;
254
+ private paintClosedShapeFill;
249
255
  }
250
256
 
251
- type DefaultToolId = 'pen' | 'eraser' | 'select' | 'hand';
257
+ type DefaultToolId = 'pen' | 'eraser' | 'select' | 'hand' | 'square' | 'circle';
252
258
  type ToolId = DefaultToolId | (string & {});
253
259
  interface ToolDefinition {
254
260
  id: ToolId;
@@ -326,11 +332,13 @@ declare class Editor {
326
332
  getCurrentDrawStyle(): {
327
333
  color: ColorStyle;
328
334
  dash: DashStyle;
335
+ fill: FillStyle;
329
336
  size: SizeStyle;
330
337
  };
331
338
  setCurrentDrawStyle(partial: Partial<{
332
339
  color: ColorStyle;
333
340
  dash: DashStyle;
341
+ fill: FillStyle;
334
342
  size: SizeStyle;
335
343
  }>): void;
336
344
  setViewport(partial: Partial<Viewport>): void;
@@ -401,6 +409,76 @@ declare class PenDrawingState extends StateNode {
401
409
  private endStroke;
402
410
  }
403
411
 
412
+ declare class SquareIdleState extends StateNode {
413
+ static id: string;
414
+ onPointerDown(info?: ToolPointerDownInfo): void;
415
+ }
416
+
417
+ interface ShapeBounds {
418
+ x: number;
419
+ y: number;
420
+ width: number;
421
+ height: number;
422
+ }
423
+ declare function buildSquareBounds(anchorX: number, anchorY: number, cursorX: number, cursorY: number): ShapeBounds;
424
+ declare function buildRectangleBounds(anchorX: number, anchorY: number, cursorX: number, cursorY: number): ShapeBounds;
425
+ declare function buildDefaultCenteredRectangleBounds(centerX: number, centerY: number): ShapeBounds;
426
+ declare function buildRectangleSegments(width: number, height: number): DrawSegment[];
427
+ declare function buildCircleBounds(anchorX: number, anchorY: number, cursorX: number, cursorY: number): ShapeBounds;
428
+ declare function buildEllipseBounds(anchorX: number, anchorY: number, cursorX: number, cursorY: number): ShapeBounds;
429
+ declare function buildDefaultCenteredEllipseBounds(centerX: number, centerY: number): ShapeBounds;
430
+ declare function buildEllipseSegments(width: number, height: number): DrawSegment[];
431
+
432
+ interface GeometricDrawingStateConfig {
433
+ idleStateId: string;
434
+ buildConstrainedBounds: (anchorX: number, anchorY: number, cursorX: number, cursorY: number) => ShapeBounds;
435
+ buildUnconstrainedBounds: (anchorX: number, anchorY: number, cursorX: number, cursorY: number) => ShapeBounds;
436
+ buildDefaultBounds: (centerX: number, centerY: number) => ShapeBounds;
437
+ buildSegments: (width: number, height: number) => DrawSegment[];
438
+ }
439
+ declare abstract class GeometricDrawingState extends StateNode {
440
+ private currentShapeId;
441
+ private startedAt;
442
+ protected abstract getConfig(): GeometricDrawingStateConfig;
443
+ onEnter(info?: ToolPointerDownInfo): void;
444
+ onPointerMove(): void;
445
+ onPointerUp(): void;
446
+ onCancel(): void;
447
+ onInterrupt(): void;
448
+ onKeyDown(): void;
449
+ onKeyUp(): void;
450
+ private completeShape;
451
+ private removeCurrentShape;
452
+ private getActiveShape;
453
+ }
454
+
455
+ declare class SquareDrawingState extends GeometricDrawingState {
456
+ static id: string;
457
+ protected getConfig(): {
458
+ idleStateId: string;
459
+ buildConstrainedBounds: typeof buildSquareBounds;
460
+ buildUnconstrainedBounds: typeof buildRectangleBounds;
461
+ buildDefaultBounds: typeof buildDefaultCenteredRectangleBounds;
462
+ buildSegments: typeof buildRectangleSegments;
463
+ };
464
+ }
465
+
466
+ declare class CircleIdleState extends StateNode {
467
+ static id: string;
468
+ onPointerDown(info?: ToolPointerDownInfo): void;
469
+ }
470
+
471
+ declare class CircleDrawingState extends GeometricDrawingState {
472
+ static id: string;
473
+ protected getConfig(): {
474
+ idleStateId: string;
475
+ buildConstrainedBounds: typeof buildCircleBounds;
476
+ buildUnconstrainedBounds: typeof buildEllipseBounds;
477
+ buildDefaultBounds: typeof buildDefaultCenteredEllipseBounds;
478
+ buildSegments: typeof buildEllipseSegments;
479
+ };
480
+ }
481
+
404
482
  declare class EraserIdleState extends StateNode {
405
483
  static id: string;
406
484
  onPointerDown(info?: ToolPointerDownInfo): void;
@@ -550,4 +628,4 @@ declare function decodePathToPoints(segments: {
550
628
  y: number;
551
629
  }[];
552
630
 
553
- export { type Bounds, CanvasRenderer, type ColorStyle, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, type DashStyle, type DefaultToolId, DocumentStore, type DocumentStoreSnapshot, type DrawSegment, type DrawShape, ERASER_MARGIN, Editor, type EditorOptions, EraserErasingState, EraserIdleState, EraserPointingState, HandDraggingState, HandIdleState, type ICanvasRenderer, type IEditor, InputManager, MAX_POINTS_PER_SHAPE, type PageState, PenDrawingState, PenIdleState, type PointerInput, type ResizeHandle, STROKE_WIDTHS, type SegmentType, SelectIdleState, type SelectionBounds, type Shape, type ShapeId, type SizeStyle, StateNode, type StateNodeConstructor, type ToolDefinition, type ToolId, type ToolKeyInfo, ToolManager, type ToolPointerDownInfo, type ToolPointerMoveInfo, type ToolStateContext, type ToolStateTransitionInfo, type TransformSnapshot, type TsdrawDocumentSnapshot, type TsdrawEditorSnapshot, type TsdrawHistorySnapshot, type TsdrawPageRecord, type TsdrawPersistedRecord, type TsdrawRenderTheme, type TsdrawSessionStateSnapshot, type TsdrawShapeRecord, type Vec3, type Viewport, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, documentSnapshotToRecords, encodePoints, getSelectionBoundsPage, getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, recordsToDocumentSnapshot, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
631
+ export { type Bounds, CanvasRenderer, CircleDrawingState, CircleIdleState, type ColorStyle, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, type DashStyle, type DefaultToolId, DocumentStore, type DocumentStoreSnapshot, type DrawSegment, type DrawShape, ERASER_MARGIN, Editor, type EditorOptions, EraserErasingState, EraserIdleState, EraserPointingState, type FillStyle, HandDraggingState, HandIdleState, type ICanvasRenderer, type IEditor, InputManager, MAX_POINTS_PER_SHAPE, type PageState, PenDrawingState, PenIdleState, type PointerInput, type ResizeHandle, STROKE_WIDTHS, type SegmentType, SelectIdleState, type SelectionBounds, type Shape, type ShapeId, type SizeStyle, SquareDrawingState, SquareIdleState, StateNode, type StateNodeConstructor, type ToolDefinition, type ToolId, type ToolKeyInfo, ToolManager, type ToolPointerDownInfo, type ToolPointerMoveInfo, type ToolStateContext, type ToolStateTransitionInfo, type TransformSnapshot, type TsdrawDocumentSnapshot, type TsdrawEditorSnapshot, type TsdrawHistorySnapshot, type TsdrawPageRecord, type TsdrawPersistedRecord, type TsdrawRenderTheme, type TsdrawSessionStateSnapshot, type TsdrawShapeRecord, type Vec3, type Viewport, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, documentSnapshotToRecords, encodePoints, getSelectionBoundsPage, getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, recordsToDocumentSnapshot, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ interface DrawSegment {
11
11
  }
12
12
  type SizeStyle = 's' | 'm' | 'l' | 'xl';
13
13
  type DashStyle = 'draw' | 'solid' | 'dashed' | 'dotted';
14
+ type FillStyle = 'none' | 'semi' | 'solid' | 'blank';
14
15
  type ColorStyle = string;
15
16
  interface DrawShape {
16
17
  id: ShapeId;
@@ -20,6 +21,7 @@ interface DrawShape {
20
21
  props: {
21
22
  color: ColorStyle;
22
23
  dash: DashStyle;
24
+ fill?: FillStyle;
23
25
  size: SizeStyle;
24
26
  scale: number;
25
27
  isPen: boolean;
@@ -66,6 +68,7 @@ interface TsdrawSessionStateSnapshot {
66
68
  drawStyle: {
67
69
  color: ColorStyle;
68
70
  dash: DashStyle;
71
+ fill?: FillStyle;
69
72
  size: SizeStyle;
70
73
  };
71
74
  selectedShapeIds: ShapeId[];
@@ -168,11 +171,13 @@ interface IEditor {
168
171
  getCurrentDrawStyle(): {
169
172
  color: ColorStyle;
170
173
  dash: DashStyle;
174
+ fill: FillStyle;
171
175
  size: SizeStyle;
172
176
  };
173
177
  setCurrentDrawStyle(partial: Partial<{
174
178
  color: ColorStyle;
175
179
  dash: DashStyle;
180
+ fill: FillStyle;
176
181
  size: SizeStyle;
177
182
  }>): void;
178
183
  panBy(dx: number, dy: number): void;
@@ -246,9 +251,10 @@ declare class CanvasRenderer implements ICanvasRenderer {
246
251
  render(ctx: CanvasRenderingContext2D, viewport: Viewport, shapes: Shape[]): void;
247
252
  private paintStroke;
248
253
  private paintDashedStroke;
254
+ private paintClosedShapeFill;
249
255
  }
250
256
 
251
- type DefaultToolId = 'pen' | 'eraser' | 'select' | 'hand';
257
+ type DefaultToolId = 'pen' | 'eraser' | 'select' | 'hand' | 'square' | 'circle';
252
258
  type ToolId = DefaultToolId | (string & {});
253
259
  interface ToolDefinition {
254
260
  id: ToolId;
@@ -326,11 +332,13 @@ declare class Editor {
326
332
  getCurrentDrawStyle(): {
327
333
  color: ColorStyle;
328
334
  dash: DashStyle;
335
+ fill: FillStyle;
329
336
  size: SizeStyle;
330
337
  };
331
338
  setCurrentDrawStyle(partial: Partial<{
332
339
  color: ColorStyle;
333
340
  dash: DashStyle;
341
+ fill: FillStyle;
334
342
  size: SizeStyle;
335
343
  }>): void;
336
344
  setViewport(partial: Partial<Viewport>): void;
@@ -401,6 +409,76 @@ declare class PenDrawingState extends StateNode {
401
409
  private endStroke;
402
410
  }
403
411
 
412
+ declare class SquareIdleState extends StateNode {
413
+ static id: string;
414
+ onPointerDown(info?: ToolPointerDownInfo): void;
415
+ }
416
+
417
+ interface ShapeBounds {
418
+ x: number;
419
+ y: number;
420
+ width: number;
421
+ height: number;
422
+ }
423
+ declare function buildSquareBounds(anchorX: number, anchorY: number, cursorX: number, cursorY: number): ShapeBounds;
424
+ declare function buildRectangleBounds(anchorX: number, anchorY: number, cursorX: number, cursorY: number): ShapeBounds;
425
+ declare function buildDefaultCenteredRectangleBounds(centerX: number, centerY: number): ShapeBounds;
426
+ declare function buildRectangleSegments(width: number, height: number): DrawSegment[];
427
+ declare function buildCircleBounds(anchorX: number, anchorY: number, cursorX: number, cursorY: number): ShapeBounds;
428
+ declare function buildEllipseBounds(anchorX: number, anchorY: number, cursorX: number, cursorY: number): ShapeBounds;
429
+ declare function buildDefaultCenteredEllipseBounds(centerX: number, centerY: number): ShapeBounds;
430
+ declare function buildEllipseSegments(width: number, height: number): DrawSegment[];
431
+
432
+ interface GeometricDrawingStateConfig {
433
+ idleStateId: string;
434
+ buildConstrainedBounds: (anchorX: number, anchorY: number, cursorX: number, cursorY: number) => ShapeBounds;
435
+ buildUnconstrainedBounds: (anchorX: number, anchorY: number, cursorX: number, cursorY: number) => ShapeBounds;
436
+ buildDefaultBounds: (centerX: number, centerY: number) => ShapeBounds;
437
+ buildSegments: (width: number, height: number) => DrawSegment[];
438
+ }
439
+ declare abstract class GeometricDrawingState extends StateNode {
440
+ private currentShapeId;
441
+ private startedAt;
442
+ protected abstract getConfig(): GeometricDrawingStateConfig;
443
+ onEnter(info?: ToolPointerDownInfo): void;
444
+ onPointerMove(): void;
445
+ onPointerUp(): void;
446
+ onCancel(): void;
447
+ onInterrupt(): void;
448
+ onKeyDown(): void;
449
+ onKeyUp(): void;
450
+ private completeShape;
451
+ private removeCurrentShape;
452
+ private getActiveShape;
453
+ }
454
+
455
+ declare class SquareDrawingState extends GeometricDrawingState {
456
+ static id: string;
457
+ protected getConfig(): {
458
+ idleStateId: string;
459
+ buildConstrainedBounds: typeof buildSquareBounds;
460
+ buildUnconstrainedBounds: typeof buildRectangleBounds;
461
+ buildDefaultBounds: typeof buildDefaultCenteredRectangleBounds;
462
+ buildSegments: typeof buildRectangleSegments;
463
+ };
464
+ }
465
+
466
+ declare class CircleIdleState extends StateNode {
467
+ static id: string;
468
+ onPointerDown(info?: ToolPointerDownInfo): void;
469
+ }
470
+
471
+ declare class CircleDrawingState extends GeometricDrawingState {
472
+ static id: string;
473
+ protected getConfig(): {
474
+ idleStateId: string;
475
+ buildConstrainedBounds: typeof buildCircleBounds;
476
+ buildUnconstrainedBounds: typeof buildEllipseBounds;
477
+ buildDefaultBounds: typeof buildDefaultCenteredEllipseBounds;
478
+ buildSegments: typeof buildEllipseSegments;
479
+ };
480
+ }
481
+
404
482
  declare class EraserIdleState extends StateNode {
405
483
  static id: string;
406
484
  onPointerDown(info?: ToolPointerDownInfo): void;
@@ -550,4 +628,4 @@ declare function decodePathToPoints(segments: {
550
628
  y: number;
551
629
  }[];
552
630
 
553
- export { type Bounds, CanvasRenderer, type ColorStyle, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, type DashStyle, type DefaultToolId, DocumentStore, type DocumentStoreSnapshot, type DrawSegment, type DrawShape, ERASER_MARGIN, Editor, type EditorOptions, EraserErasingState, EraserIdleState, EraserPointingState, HandDraggingState, HandIdleState, type ICanvasRenderer, type IEditor, InputManager, MAX_POINTS_PER_SHAPE, type PageState, PenDrawingState, PenIdleState, type PointerInput, type ResizeHandle, STROKE_WIDTHS, type SegmentType, SelectIdleState, type SelectionBounds, type Shape, type ShapeId, type SizeStyle, StateNode, type StateNodeConstructor, type ToolDefinition, type ToolId, type ToolKeyInfo, ToolManager, type ToolPointerDownInfo, type ToolPointerMoveInfo, type ToolStateContext, type ToolStateTransitionInfo, type TransformSnapshot, type TsdrawDocumentSnapshot, type TsdrawEditorSnapshot, type TsdrawHistorySnapshot, type TsdrawPageRecord, type TsdrawPersistedRecord, type TsdrawRenderTheme, type TsdrawSessionStateSnapshot, type TsdrawShapeRecord, type Vec3, type Viewport, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, documentSnapshotToRecords, encodePoints, getSelectionBoundsPage, getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, recordsToDocumentSnapshot, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
631
+ export { type Bounds, CanvasRenderer, CircleDrawingState, CircleIdleState, type ColorStyle, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, type DashStyle, type DefaultToolId, DocumentStore, type DocumentStoreSnapshot, type DrawSegment, type DrawShape, ERASER_MARGIN, Editor, type EditorOptions, EraserErasingState, EraserIdleState, EraserPointingState, type FillStyle, HandDraggingState, HandIdleState, type ICanvasRenderer, type IEditor, InputManager, MAX_POINTS_PER_SHAPE, type PageState, PenDrawingState, PenIdleState, type PointerInput, type ResizeHandle, STROKE_WIDTHS, type SegmentType, SelectIdleState, type SelectionBounds, type Shape, type ShapeId, type SizeStyle, SquareDrawingState, SquareIdleState, StateNode, type StateNodeConstructor, type ToolDefinition, type ToolId, type ToolKeyInfo, ToolManager, type ToolPointerDownInfo, type ToolPointerMoveInfo, type ToolStateContext, type ToolStateTransitionInfo, type TransformSnapshot, type TsdrawDocumentSnapshot, type TsdrawEditorSnapshot, type TsdrawHistorySnapshot, type TsdrawPageRecord, type TsdrawPersistedRecord, type TsdrawRenderTheme, type TsdrawSessionStateSnapshot, type TsdrawShapeRecord, type Vec3, type Viewport, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, documentSnapshotToRecords, encodePoints, getSelectionBoundsPage, getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, recordsToDocumentSnapshot, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
package/dist/index.js CHANGED
@@ -148,6 +148,12 @@ var DocumentStore = class {
148
148
  loadSnapshot(snapshot) {
149
149
  const pageState = cloneValue(snapshot.page);
150
150
  const normalizedOrder = [...snapshot.order].filter((shapeId) => pageState.shapes[shapeId] != null);
151
+ const orderedSet = new Set(normalizedOrder);
152
+ for (const shapeId of Object.keys(pageState.shapes)) {
153
+ if (!orderedSet.has(shapeId)) {
154
+ normalizedOrder.push(shapeId);
155
+ }
156
+ }
151
157
  this.state = {
152
158
  id: pageState.id,
153
159
  shapes: pageState.shapes,
@@ -290,11 +296,16 @@ var CanvasRenderer = class {
290
296
  }
291
297
  ctx.restore();
292
298
  }
299
+ // Paints a single stroke
293
300
  paintStroke(ctx, shape) {
294
301
  const width = (STROKE_WIDTHS[shape.props.size] ?? 3.5) * shape.props.scale;
295
302
  const samples = flattenSegments(shape);
296
303
  if (samples.length === 0) return;
297
304
  const color = resolveThemeColor(shape.props.color, this.theme);
305
+ const fillStyle = shape.props.fill ?? "none";
306
+ if (shape.props.isClosed && fillStyle !== "none") {
307
+ this.paintClosedShapeFill(ctx, samples, color, fillStyle);
308
+ }
298
309
  if (shape.props.dash !== "draw") {
299
310
  this.paintDashedStroke(ctx, samples, width, color, shape.props.dash);
300
311
  return;
@@ -341,6 +352,30 @@ var CanvasRenderer = class {
341
352
  ctx.stroke();
342
353
  ctx.restore();
343
354
  }
355
+ // Closed shapes are shapes where their start and end point are the same
356
+ paintClosedShapeFill(ctx, samples, color, fillStyle) {
357
+ if (samples.length < 3) return;
358
+ ctx.save();
359
+ ctx.beginPath();
360
+ ctx.moveTo(samples[0].x, samples[0].y);
361
+ for (let i = 1; i < samples.length; i++) {
362
+ const sample = samples[i];
363
+ ctx.lineTo(sample.x, sample.y);
364
+ }
365
+ ctx.closePath();
366
+ if (fillStyle === "solid") {
367
+ ctx.fillStyle = color;
368
+ ctx.globalAlpha = 0.55;
369
+ } else if (fillStyle === "none") {
370
+ ctx.fillStyle = this.theme === "dark" ? "#0f0f0f" : "#fafafa";
371
+ ctx.globalAlpha = 1;
372
+ } else {
373
+ ctx.fillStyle = color;
374
+ ctx.globalAlpha = 0.28;
375
+ }
376
+ ctx.fill();
377
+ ctx.restore();
378
+ }
344
379
  };
345
380
  var PRESSURE_FLOOR = 0.025;
346
381
  var STYLUS_CURVE = (t) => t * 0.65 + Math.sin(t * Math.PI / 2) * 0.35;
@@ -354,7 +389,7 @@ function remap(value, inRange, outRange, clamp = false) {
354
389
  return outLo + (outHi - outLo) * clamped;
355
390
  }
356
391
  function strokeConfig(shape, width) {
357
- const done = shape.props.isComplete;
392
+ const done = shape.props.isComplete || shape.props.isClosed === true;
358
393
  if (shape.props.isPen) {
359
394
  return {
360
395
  size: 1 + width * 1.2,
@@ -863,13 +898,14 @@ var PenDrawingState = class extends StateNode {
863
898
  type: "straight",
864
899
  path: encodePoints([prevEnd, { ...anchorPt, z: pressure }])
865
900
  };
901
+ const withStraightSeg = [...segments, seg];
866
902
  this.editor.updateShapes([
867
903
  {
868
904
  id,
869
905
  type: "draw",
870
906
  props: {
871
- segments: [...segments, seg],
872
- isClosed: this.detectClosure(segments, size, scale)
907
+ segments: withStraightSeg,
908
+ isClosed: this.detectClosure(withStraightSeg, size, scale)
873
909
  }
874
910
  }
875
911
  ]);
@@ -945,7 +981,7 @@ var PenDrawingState = class extends StateNode {
945
981
  type: "draw",
946
982
  props: {
947
983
  segments: updated,
948
- isClosed: this.detectClosure(segments, size, scale)
984
+ isClosed: this.detectClosure(updated, size, scale)
949
985
  }
950
986
  }
951
987
  ]);
@@ -1026,6 +1062,236 @@ var PenDrawingState = class extends StateNode {
1026
1062
  }
1027
1063
  };
1028
1064
 
1065
+ // src/tools/square/states/SquareIdleState.ts
1066
+ var SquareIdleState = class extends StateNode {
1067
+ static id = "square_idle";
1068
+ onPointerDown(info) {
1069
+ this.ctx.transition("square_drawing", info);
1070
+ }
1071
+ };
1072
+
1073
+ // src/tools/geometric/states/GeometricDrawingState.ts
1074
+ var GeometricDrawingState = class extends StateNode {
1075
+ currentShapeId = null;
1076
+ startedAt = { point: { x: 0, y: 0, z: 0.5 } };
1077
+ onEnter(info) {
1078
+ this.startedAt = info ?? { point: { x: 0, y: 0, z: 0.5 } };
1079
+ const originPoint = this.editor.input.getOriginPagePoint();
1080
+ const drawStyle = this.editor.getCurrentDrawStyle();
1081
+ const nextShapeId = this.editor.createShapeId();
1082
+ const config = this.getConfig();
1083
+ this.editor.createShape({
1084
+ id: nextShapeId,
1085
+ type: "draw",
1086
+ x: originPoint.x,
1087
+ y: originPoint.y,
1088
+ props: {
1089
+ color: drawStyle.color,
1090
+ dash: drawStyle.dash,
1091
+ fill: drawStyle.fill,
1092
+ size: drawStyle.size,
1093
+ scale: 1,
1094
+ isPen: false,
1095
+ isComplete: false,
1096
+ isClosed: true,
1097
+ segments: config.buildSegments(1, 1)
1098
+ }
1099
+ });
1100
+ this.currentShapeId = nextShapeId;
1101
+ }
1102
+ onPointerMove() {
1103
+ const activeShape = this.getActiveShape();
1104
+ if (!activeShape) return;
1105
+ const config = this.getConfig();
1106
+ const originPoint = this.editor.input.getOriginPagePoint();
1107
+ const cursorPoint = this.editor.input.getCurrentPagePoint();
1108
+ const shapeBounds = this.editor.input.getShiftKey() ? config.buildConstrainedBounds(originPoint.x, originPoint.y, cursorPoint.x, cursorPoint.y) : config.buildUnconstrainedBounds(originPoint.x, originPoint.y, cursorPoint.x, cursorPoint.y);
1109
+ this.editor.store.updateShape(activeShape.id, {
1110
+ x: shapeBounds.x,
1111
+ y: shapeBounds.y,
1112
+ props: {
1113
+ ...activeShape.props,
1114
+ segments: config.buildSegments(shapeBounds.width, shapeBounds.height),
1115
+ isClosed: true
1116
+ }
1117
+ });
1118
+ }
1119
+ onPointerUp() {
1120
+ this.completeShape();
1121
+ }
1122
+ onCancel() {
1123
+ this.removeCurrentShape();
1124
+ this.ctx.transition(this.getConfig().idleStateId, this.startedAt);
1125
+ }
1126
+ onInterrupt() {
1127
+ this.completeShape();
1128
+ }
1129
+ onKeyDown() {
1130
+ this.onPointerMove();
1131
+ }
1132
+ onKeyUp() {
1133
+ this.onPointerMove();
1134
+ }
1135
+ completeShape() {
1136
+ const activeShape = this.getActiveShape();
1137
+ const config = this.getConfig();
1138
+ if (!activeShape) {
1139
+ this.ctx.transition(config.idleStateId, this.startedAt);
1140
+ return;
1141
+ }
1142
+ const originPoint = this.editor.input.getOriginPagePoint();
1143
+ const cursorPoint = this.editor.input.getCurrentPagePoint();
1144
+ const finalizedBounds = this.editor.input.getIsDragging() ? this.editor.input.getShiftKey() ? config.buildConstrainedBounds(originPoint.x, originPoint.y, cursorPoint.x, cursorPoint.y) : config.buildUnconstrainedBounds(originPoint.x, originPoint.y, cursorPoint.x, cursorPoint.y) : config.buildDefaultBounds(originPoint.x, originPoint.y);
1145
+ this.editor.store.updateShape(activeShape.id, {
1146
+ x: finalizedBounds.x,
1147
+ y: finalizedBounds.y,
1148
+ props: {
1149
+ ...activeShape.props,
1150
+ fill: this.editor.getCurrentDrawStyle().fill,
1151
+ isComplete: true,
1152
+ isClosed: true,
1153
+ segments: config.buildSegments(finalizedBounds.width, finalizedBounds.height)
1154
+ }
1155
+ });
1156
+ this.currentShapeId = null;
1157
+ this.ctx.transition(config.idleStateId);
1158
+ }
1159
+ removeCurrentShape() {
1160
+ if (!this.currentShapeId) return;
1161
+ this.editor.store.deleteShapes([this.currentShapeId]);
1162
+ this.currentShapeId = null;
1163
+ }
1164
+ getActiveShape() {
1165
+ if (!this.currentShapeId) return null;
1166
+ const shape = this.editor.getShape(this.currentShapeId);
1167
+ if (!shape || shape.type !== "draw") return null;
1168
+ return shape;
1169
+ }
1170
+ };
1171
+
1172
+ // src/tools/geometric/geometricShapeHelpers.ts
1173
+ var MIN_SIDE_LENGTH = 1;
1174
+ var DEFAULT_RECTANGLE_WIDTH = 180;
1175
+ var DEFAULT_RECTANGLE_HEIGHT = 120;
1176
+ var DEFAULT_ELLIPSE_WIDTH = 180;
1177
+ var DEFAULT_ELLIPSE_HEIGHT = 120;
1178
+ function toSizedBounds(anchorX, anchorY, cursorX, cursorY, forceEqualSides) {
1179
+ const rawDeltaX = cursorX - anchorX;
1180
+ const rawDeltaY = cursorY - anchorY;
1181
+ const sideLength = Math.max(Math.abs(rawDeltaX), Math.abs(rawDeltaY), MIN_SIDE_LENGTH);
1182
+ if (forceEqualSides) {
1183
+ const nextDeltaX = rawDeltaX < 0 ? -sideLength : sideLength;
1184
+ const nextDeltaY = rawDeltaY < 0 ? -sideLength : sideLength;
1185
+ return normalizeBounds(anchorX, anchorY, anchorX + nextDeltaX, anchorY + nextDeltaY);
1186
+ }
1187
+ return normalizeBounds(anchorX, anchorY, cursorX, cursorY);
1188
+ }
1189
+ function normalizeBounds(startX, startY, endX, endY) {
1190
+ const x = Math.min(startX, endX);
1191
+ const y = Math.min(startY, endY);
1192
+ const width = Math.max(Math.abs(endX - startX), MIN_SIDE_LENGTH);
1193
+ const height = Math.max(Math.abs(endY - startY), MIN_SIDE_LENGTH);
1194
+ return { x, y, width, height };
1195
+ }
1196
+ function buildSquareBounds(anchorX, anchorY, cursorX, cursorY) {
1197
+ return toSizedBounds(anchorX, anchorY, cursorX, cursorY, true);
1198
+ }
1199
+ function buildRectangleBounds(anchorX, anchorY, cursorX, cursorY) {
1200
+ return toSizedBounds(anchorX, anchorY, cursorX, cursorY, false);
1201
+ }
1202
+ function buildDefaultCenteredRectangleBounds(centerX, centerY) {
1203
+ const halfWidth = DEFAULT_RECTANGLE_WIDTH / 2;
1204
+ const halfHeight = DEFAULT_RECTANGLE_HEIGHT / 2;
1205
+ return {
1206
+ x: centerX - halfWidth,
1207
+ y: centerY - halfHeight,
1208
+ width: DEFAULT_RECTANGLE_WIDTH,
1209
+ height: DEFAULT_RECTANGLE_HEIGHT
1210
+ };
1211
+ }
1212
+ function buildRectangleSegments(width, height) {
1213
+ const topLeft = { x: 0, y: 0, z: 0.5 };
1214
+ const topRight = { x: width, y: 0, z: 0.5 };
1215
+ const bottomRight = { x: width, y: height, z: 0.5 };
1216
+ const bottomLeft = { x: 0, y: height, z: 0.5 };
1217
+ return [
1218
+ { type: "straight", path: encodePoints([topLeft, topRight]) },
1219
+ { type: "straight", path: encodePoints([topRight, bottomRight]) },
1220
+ { type: "straight", path: encodePoints([bottomRight, bottomLeft]) },
1221
+ { type: "straight", path: encodePoints([bottomLeft, topLeft]) }
1222
+ ];
1223
+ }
1224
+ function buildCircleBounds(anchorX, anchorY, cursorX, cursorY) {
1225
+ return toSizedBounds(anchorX, anchorY, cursorX, cursorY, true);
1226
+ }
1227
+ function buildEllipseBounds(anchorX, anchorY, cursorX, cursorY) {
1228
+ return toSizedBounds(anchorX, anchorY, cursorX, cursorY, false);
1229
+ }
1230
+ function buildDefaultCenteredEllipseBounds(centerX, centerY) {
1231
+ const halfWidth = DEFAULT_ELLIPSE_WIDTH / 2;
1232
+ const halfHeight = DEFAULT_ELLIPSE_HEIGHT / 2;
1233
+ return {
1234
+ x: centerX - halfWidth,
1235
+ y: centerY - halfHeight,
1236
+ width: DEFAULT_ELLIPSE_WIDTH,
1237
+ height: DEFAULT_ELLIPSE_HEIGHT
1238
+ };
1239
+ }
1240
+ function buildEllipseSegments(width, height) {
1241
+ const centerX = width / 2;
1242
+ const centerY = height / 2;
1243
+ const radiusX = width / 2;
1244
+ const radiusY = height / 2;
1245
+ const sampleCount = 64;
1246
+ const sampledPoints = [];
1247
+ for (let sampleIndex = 0; sampleIndex <= sampleCount; sampleIndex += 1) {
1248
+ const progress = sampleIndex / sampleCount;
1249
+ const angle = progress * Math.PI * 2;
1250
+ sampledPoints.push({
1251
+ x: centerX + Math.cos(angle) * radiusX,
1252
+ y: centerY + Math.sin(angle) * radiusY,
1253
+ z: 0.5
1254
+ });
1255
+ }
1256
+ return [{ type: "free", path: encodePoints(sampledPoints) }];
1257
+ }
1258
+
1259
+ // src/tools/square/states/SquareDrawingState.ts
1260
+ var SquareDrawingState = class extends GeometricDrawingState {
1261
+ static id = "square_drawing";
1262
+ getConfig() {
1263
+ return {
1264
+ idleStateId: "square_idle",
1265
+ buildConstrainedBounds: buildSquareBounds,
1266
+ buildUnconstrainedBounds: buildRectangleBounds,
1267
+ buildDefaultBounds: buildDefaultCenteredRectangleBounds,
1268
+ buildSegments: buildRectangleSegments
1269
+ };
1270
+ }
1271
+ };
1272
+
1273
+ // src/tools/circle/states/CircleIdleState.ts
1274
+ var CircleIdleState = class extends StateNode {
1275
+ static id = "circle_idle";
1276
+ onPointerDown(info) {
1277
+ this.ctx.transition("circle_drawing", info);
1278
+ }
1279
+ };
1280
+
1281
+ // src/tools/circle/states/CircleDrawingState.ts
1282
+ var CircleDrawingState = class extends GeometricDrawingState {
1283
+ static id = "circle_drawing";
1284
+ getConfig() {
1285
+ return {
1286
+ idleStateId: "circle_idle",
1287
+ buildConstrainedBounds: buildCircleBounds,
1288
+ buildUnconstrainedBounds: buildEllipseBounds,
1289
+ buildDefaultBounds: buildDefaultCenteredEllipseBounds,
1290
+ buildSegments: buildEllipseSegments
1291
+ };
1292
+ }
1293
+ };
1294
+
1029
1295
  // src/tools/eraser/states/EraserIdleState.ts
1030
1296
  var EraserIdleState = class extends StateNode {
1031
1297
  static id = "eraser_idle";
@@ -1339,6 +1605,7 @@ var Editor = class {
1339
1605
  drawStyle = {
1340
1606
  color: "black",
1341
1607
  dash: "draw",
1608
+ fill: "none",
1342
1609
  size: "m"
1343
1610
  };
1344
1611
  toolStateContext;
@@ -1398,6 +1665,8 @@ var Editor = class {
1398
1665
  getDefaultToolDefinitions() {
1399
1666
  return [
1400
1667
  { id: "pen", initialStateId: PenIdleState.id, stateConstructors: [PenIdleState, PenDrawingState] },
1668
+ { id: "square", initialStateId: SquareIdleState.id, stateConstructors: [SquareIdleState, SquareDrawingState] },
1669
+ { id: "circle", initialStateId: CircleIdleState.id, stateConstructors: [CircleIdleState, CircleDrawingState] },
1401
1670
  { id: "eraser", initialStateId: EraserIdleState.id, stateConstructors: [EraserIdleState, EraserPointingState, EraserErasingState] },
1402
1671
  { id: "select", initialStateId: SelectIdleState.id, stateConstructors: [SelectIdleState] },
1403
1672
  { id: "hand", initialStateId: HandIdleState.id, stateConstructors: [HandIdleState, HandDraggingState] }
@@ -1461,10 +1730,11 @@ var Editor = class {
1461
1730
  this.emitChange();
1462
1731
  }
1463
1732
  setViewport(partial) {
1733
+ const rawZoom = partial.zoom ?? this.viewport.zoom;
1464
1734
  this.viewport = {
1465
1735
  x: partial.x ?? this.viewport.x,
1466
1736
  y: partial.y ?? this.viewport.y,
1467
- zoom: partial.zoom ?? this.viewport.zoom
1737
+ zoom: Math.max(0.1, Math.min(4, rawZoom))
1468
1738
  };
1469
1739
  this.emitChange();
1470
1740
  }
@@ -1501,7 +1771,12 @@ var Editor = class {
1501
1771
  }
1502
1772
  loadSessionStateSnapshot(snapshot) {
1503
1773
  this.setViewport(snapshot.viewport);
1504
- this.setCurrentDrawStyle(snapshot.drawStyle);
1774
+ this.setCurrentDrawStyle({
1775
+ color: snapshot.drawStyle.color,
1776
+ dash: snapshot.drawStyle.dash,
1777
+ fill: snapshot.drawStyle.fill ?? "none",
1778
+ size: snapshot.drawStyle.size
1779
+ });
1505
1780
  if (this.tools.hasTool(snapshot.currentToolId)) {
1506
1781
  this.setCurrentTool(snapshot.currentToolId);
1507
1782
  }
@@ -1882,6 +2157,6 @@ function applyResize(editor, handle, startBounds, startShapes, pointer, lockAspe
1882
2157
  }
1883
2158
  }
1884
2159
 
1885
- export { CanvasRenderer, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, DocumentStore, ERASER_MARGIN, Editor, EraserErasingState, EraserIdleState, EraserPointingState, HandDraggingState, HandIdleState, InputManager, MAX_POINTS_PER_SHAPE, PenDrawingState, PenIdleState, STROKE_WIDTHS, SelectIdleState, StateNode, ToolManager, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, documentSnapshotToRecords, encodePoints, getSelectionBoundsPage, getShapeBounds2 as getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, recordsToDocumentSnapshot, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
2160
+ export { CanvasRenderer, CircleDrawingState, CircleIdleState, DEFAULT_COLORS, DRAG_DISTANCE_SQUARED, DocumentStore, ERASER_MARGIN, Editor, EraserErasingState, EraserIdleState, EraserPointingState, HandDraggingState, HandIdleState, InputManager, MAX_POINTS_PER_SHAPE, PenDrawingState, PenIdleState, STROKE_WIDTHS, SelectIdleState, SquareDrawingState, SquareIdleState, StateNode, ToolManager, applyMove, applyResize, applyRotation, boundsContainPoint, boundsIntersect, boundsOf, buildStartPositions, buildTransformSnapshots, closestOnSegment, createViewport, decodeFirstPoint, decodeLastPoint, decodePathToPoints, decodePoints, distance, documentSnapshotToRecords, encodePoints, getSelectionBoundsPage, getShapeBounds2 as getShapeBounds, getShapesInBounds, getTopShapeAtPoint, isSelectTool, minDistanceToPolyline, normalizeSelectionBounds, padBounds, pageToScreen, panViewport, pointHitsShape, recordsToDocumentSnapshot, resolveThemeColor, rotatePoint, screenToPage, segmentHitsShape, segmentTouchesPolyline, setViewport, shapePagePoints, sqDistance, zoomViewport };
1886
2161
  //# sourceMappingURL=index.js.map
1887
2162
  //# sourceMappingURL=index.js.map