@nasser-sw/fabric 7.0.1-beta24 → 7.0.1-beta25

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.
Files changed (42) hide show
  1. package/.claude/settings.local.json +8 -7
  2. package/.history/package_20251227124512.json +164 -0
  3. package/dist/index.js +765 -38
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +765 -38
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/index.node.cjs +765 -38
  8. package/dist/index.node.cjs.map +1 -1
  9. package/dist/index.node.mjs +765 -38
  10. package/dist/index.node.mjs.map +1 -1
  11. package/dist/package.json.mjs +1 -1
  12. package/dist/src/controls/expandControl.d.ts +81 -0
  13. package/dist/src/controls/expandControl.d.ts.map +1 -0
  14. package/dist/src/controls/expandControl.mjs +408 -0
  15. package/dist/src/controls/expandControl.mjs.map +1 -0
  16. package/dist/src/controls/index.d.ts +1 -0
  17. package/dist/src/controls/index.d.ts.map +1 -1
  18. package/dist/src/controls/index.mjs +1 -0
  19. package/dist/src/controls/index.mjs.map +1 -1
  20. package/dist/src/controls/skew.mjs +1 -1
  21. package/dist/src/shapes/Frame.d.ts +48 -0
  22. package/dist/src/shapes/Frame.d.ts.map +1 -1
  23. package/dist/src/shapes/Frame.mjs +146 -37
  24. package/dist/src/shapes/Frame.mjs.map +1 -1
  25. package/dist/src/shapes/Object/InteractiveObject.d.ts +69 -0
  26. package/dist/src/shapes/Object/InteractiveObject.d.ts.map +1 -1
  27. package/dist/src/shapes/Object/InteractiveObject.mjs +205 -0
  28. package/dist/src/shapes/Object/InteractiveObject.mjs.map +1 -1
  29. package/dist-extensions/src/controls/expandControl.d.ts +81 -0
  30. package/dist-extensions/src/controls/expandControl.d.ts.map +1 -0
  31. package/dist-extensions/src/controls/index.d.ts +1 -0
  32. package/dist-extensions/src/controls/index.d.ts.map +1 -1
  33. package/dist-extensions/src/shapes/Frame.d.ts +48 -0
  34. package/dist-extensions/src/shapes/Frame.d.ts.map +1 -1
  35. package/dist-extensions/src/shapes/Object/InteractiveObject.d.ts +69 -0
  36. package/dist-extensions/src/shapes/Object/InteractiveObject.d.ts.map +1 -1
  37. package/docs/EXPAND_MODE_API.md +585 -0
  38. package/package.json +1 -1
  39. package/src/controls/expandControl.ts +491 -0
  40. package/src/controls/index.ts +1 -0
  41. package/src/shapes/Frame.ts +153 -37
  42. package/src/shapes/Object/InteractiveObject.ts +219 -0
@@ -410,7 +410,7 @@ class Cache {
410
410
  }
411
411
  const cache = new Cache();
412
412
 
413
- var version = "7.0.1-beta23";
413
+ var version = "7.0.1-beta24";
414
414
 
415
415
  // use this syntax so babel plugin see this import here
416
416
  const VERSION = version;
@@ -9319,6 +9319,406 @@ const createTextboxDefaultControls = () => {
9319
9319
  return controls;
9320
9320
  };
9321
9321
 
9322
+ /**
9323
+ * Expansion state for AI outpainting
9324
+ */
9325
+
9326
+ /**
9327
+ * Direction(s) that an expand control affects
9328
+ */
9329
+
9330
+ /**
9331
+ * Styling constants for expand controls
9332
+ */
9333
+ const EXPAND_HANDLE_FILL = '#ffffff';
9334
+ const EXPAND_HANDLE_STROKE = '#8b5cf6'; // Purple
9335
+ const EXPAND_PREVIEW_FILL = 'rgba(139, 92, 246, 0.1)';
9336
+ const EXPAND_PREVIEW_STROKE = '#8b5cf6';
9337
+ const EXPAND_PREVIEW_DASH = [6, 4];
9338
+
9339
+ // Handle sizes - similar to default Fabric controls
9340
+ const EDGE_HANDLE_WIDTH = 6;
9341
+ const EDGE_HANDLE_HEIGHT = 20;
9342
+ const CORNER_HANDLE_SIZE = 10;
9343
+ const HANDLE_RADIUS = 2;
9344
+
9345
+ /**
9346
+ * Default expansion state
9347
+ */
9348
+ const createDefaultExpansion = () => ({
9349
+ expandLeft: 0,
9350
+ expandRight: 0,
9351
+ expandTop: 0,
9352
+ expandBottom: 0
9353
+ });
9354
+
9355
+ /**
9356
+ * Get expansion state from object, initializing if needed
9357
+ */
9358
+ function getExpansion(obj) {
9359
+ if (!obj.expansion) {
9360
+ obj.expansion = createDefaultExpansion();
9361
+ }
9362
+ return obj.expansion;
9363
+ }
9364
+
9365
+ /**
9366
+ * Set expansion state on object
9367
+ */
9368
+ function setExpansion(obj, expansion) {
9369
+ const current = getExpansion(obj);
9370
+ Object.assign(current, expansion);
9371
+ }
9372
+
9373
+ /**
9374
+ * Custom control for AI expansion handles
9375
+ */
9376
+ class ExpandControl extends Control {
9377
+ /**
9378
+ * Which direction(s) this control affects
9379
+ */
9380
+
9381
+ constructor(options) {
9382
+ super(options);
9383
+ this.expandDirections = options.expandDirections;
9384
+ this.actionName = 'expand';
9385
+ }
9386
+
9387
+ /**
9388
+ * Position handler for expand controls
9389
+ * Positions controls at the expansion boundary, not the object boundary
9390
+ * Note: expansion values are stored in screen pixels (already scaled)
9391
+ */
9392
+ positionHandler(dim, finalMatrix, fabricObject, currentControl) {
9393
+ const expansion = getExpansion(fabricObject);
9394
+ const {
9395
+ expandLeft,
9396
+ expandRight,
9397
+ expandTop,
9398
+ expandBottom
9399
+ } = expansion;
9400
+
9401
+ // dim already includes scale, so use it directly for base size
9402
+ // expansion values are also in screen pixels, so use directly
9403
+ const halfWidth = dim.x / 2;
9404
+ const halfHeight = dim.y / 2;
9405
+
9406
+ // Calculate the expanded half dimensions
9407
+ const expandedHalfWidth = halfWidth + (expandLeft + expandRight) / 2;
9408
+ const expandedHalfHeight = halfHeight + (expandTop + expandBottom) / 2;
9409
+
9410
+ // Calculate offset from center based on asymmetric expansion
9411
+ const centerOffsetX = (expandRight - expandLeft) / 2;
9412
+ const centerOffsetY = (expandBottom - expandTop) / 2;
9413
+ let posX;
9414
+ let posY;
9415
+
9416
+ // Position based on which side this control is on
9417
+ if (this.x < 0) {
9418
+ // Left side controls
9419
+ posX = -expandedHalfWidth + centerOffsetX;
9420
+ } else if (this.x > 0) {
9421
+ // Right side controls
9422
+ posX = expandedHalfWidth + centerOffsetX;
9423
+ } else {
9424
+ // Center (top/bottom only)
9425
+ posX = centerOffsetX;
9426
+ }
9427
+ if (this.y < 0) {
9428
+ // Top side controls
9429
+ posY = -expandedHalfHeight + centerOffsetY;
9430
+ } else if (this.y > 0) {
9431
+ // Bottom side controls
9432
+ posY = expandedHalfHeight + centerOffsetY;
9433
+ } else {
9434
+ // Center (left/right only)
9435
+ posY = centerOffsetY;
9436
+ }
9437
+ return new Point(posX, posY).transform(finalMatrix);
9438
+ }
9439
+
9440
+ /**
9441
+ * Custom render for expand handles - purple border with white fill
9442
+ */
9443
+ render(ctx, left, top, styleOverride, fabricObject) {
9444
+ ctx.save();
9445
+ ctx.translate(left, top);
9446
+ const angle = fabricObject.getTotalAngle();
9447
+ ctx.rotate(degreesToRadians(angle));
9448
+
9449
+ // Add subtle shadow
9450
+ ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
9451
+ ctx.shadowBlur = 3;
9452
+ ctx.shadowOffsetX = 0;
9453
+ ctx.shadowOffsetY = 1;
9454
+ ctx.fillStyle = EXPAND_HANDLE_FILL;
9455
+ ctx.strokeStyle = EXPAND_HANDLE_STROKE;
9456
+ ctx.lineWidth = 2;
9457
+
9458
+ // Determine if this is a corner or edge control
9459
+ const isCorner = this.x !== 0 && this.y !== 0;
9460
+ if (isCorner) {
9461
+ // Corner: draw a circle
9462
+ ctx.beginPath();
9463
+ ctx.arc(0, 0, CORNER_HANDLE_SIZE / 2, 0, Math.PI * 2);
9464
+ ctx.fill();
9465
+ ctx.stroke();
9466
+ } else if (this.y === 0) {
9467
+ // Horizontal edge (left/right): vertical pill
9468
+ ctx.beginPath();
9469
+ ctx.roundRect(-EDGE_HANDLE_WIDTH / 2, -EDGE_HANDLE_HEIGHT / 2, EDGE_HANDLE_WIDTH, EDGE_HANDLE_HEIGHT, HANDLE_RADIUS);
9470
+ ctx.fill();
9471
+ ctx.stroke();
9472
+ } else {
9473
+ // Vertical edge (top/bottom): horizontal pill
9474
+ ctx.beginPath();
9475
+ ctx.roundRect(-EDGE_HANDLE_HEIGHT / 2, -EDGE_HANDLE_WIDTH / 2, EDGE_HANDLE_HEIGHT, EDGE_HANDLE_WIDTH, HANDLE_RADIUS);
9476
+ ctx.fill();
9477
+ ctx.stroke();
9478
+ }
9479
+ ctx.restore();
9480
+ }
9481
+ }
9482
+
9483
+ /**
9484
+ * Action handler for expanding left edge
9485
+ */
9486
+ const expandLeftHandler = (eventData, transform, x, y) => {
9487
+ const {
9488
+ target
9489
+ } = transform;
9490
+ const expansion = getExpansion(target);
9491
+
9492
+ // Use Fabric's getLocalPoint with center origin (returns scaled coordinates)
9493
+ const localPoint = getLocalPoint(transform, CENTER, CENTER, x, y);
9494
+
9495
+ // Use SCALED half width since localPoint is in scaled coords
9496
+ const scaleX = target.scaleX || 1;
9497
+ const scaledHalfWidth = target.width * scaleX / 2;
9498
+
9499
+ // Calculate new expansion (how far left of the object's left edge)
9500
+ // localPoint.x is negative when left of center
9501
+ // Expansion starts when localPoint.x < -scaledHalfWidth
9502
+ const newExpandLeft = Math.max(0, -scaledHalfWidth - localPoint.x);
9503
+ if (Math.abs(newExpandLeft - expansion.expandLeft) > 0.5) {
9504
+ var _target$canvas;
9505
+ expansion.expandLeft = newExpandLeft;
9506
+ target.setCoords();
9507
+ (_target$canvas = target.canvas) === null || _target$canvas === void 0 || _target$canvas.requestRenderAll();
9508
+ return true;
9509
+ }
9510
+ return false;
9511
+ };
9512
+
9513
+ /**
9514
+ * Action handler for expanding right edge
9515
+ */
9516
+ const expandRightHandler = (eventData, transform, x, y) => {
9517
+ const {
9518
+ target
9519
+ } = transform;
9520
+ const expansion = getExpansion(target);
9521
+
9522
+ // Use Fabric's getLocalPoint with center origin (returns scaled coordinates)
9523
+ const localPoint = getLocalPoint(transform, CENTER, CENTER, x, y);
9524
+
9525
+ // Use SCALED half width since localPoint is in scaled coords
9526
+ const scaleX = target.scaleX || 1;
9527
+ const scaledHalfWidth = target.width * scaleX / 2;
9528
+
9529
+ // Calculate new expansion (how far right of the object's right edge)
9530
+ // localPoint.x is positive when right of center
9531
+ // Expansion starts when localPoint.x > scaledHalfWidth
9532
+ const newExpandRight = Math.max(0, localPoint.x - scaledHalfWidth);
9533
+ if (Math.abs(newExpandRight - expansion.expandRight) > 0.5) {
9534
+ var _target$canvas2;
9535
+ expansion.expandRight = newExpandRight;
9536
+ target.setCoords();
9537
+ (_target$canvas2 = target.canvas) === null || _target$canvas2 === void 0 || _target$canvas2.requestRenderAll();
9538
+ return true;
9539
+ }
9540
+ return false;
9541
+ };
9542
+
9543
+ /**
9544
+ * Action handler for expanding top edge
9545
+ */
9546
+ const expandTopHandler = (eventData, transform, x, y) => {
9547
+ const {
9548
+ target
9549
+ } = transform;
9550
+ const expansion = getExpansion(target);
9551
+
9552
+ // Use Fabric's getLocalPoint with center origin (returns scaled coordinates)
9553
+ const localPoint = getLocalPoint(transform, CENTER, CENTER, x, y);
9554
+
9555
+ // Use SCALED half height since localPoint is in scaled coords
9556
+ const scaleY = target.scaleY || 1;
9557
+ const scaledHalfHeight = target.height * scaleY / 2;
9558
+
9559
+ // Calculate new expansion (how far above the object's top edge)
9560
+ const newExpandTop = Math.max(0, -scaledHalfHeight - localPoint.y);
9561
+ if (Math.abs(newExpandTop - expansion.expandTop) > 0.5) {
9562
+ var _target$canvas3;
9563
+ expansion.expandTop = newExpandTop;
9564
+ target.setCoords();
9565
+ (_target$canvas3 = target.canvas) === null || _target$canvas3 === void 0 || _target$canvas3.requestRenderAll();
9566
+ return true;
9567
+ }
9568
+ return false;
9569
+ };
9570
+
9571
+ /**
9572
+ * Action handler for expanding bottom edge
9573
+ */
9574
+ const expandBottomHandler = (eventData, transform, x, y) => {
9575
+ const {
9576
+ target
9577
+ } = transform;
9578
+ const expansion = getExpansion(target);
9579
+
9580
+ // Use Fabric's getLocalPoint with center origin (returns scaled coordinates)
9581
+ const localPoint = getLocalPoint(transform, CENTER, CENTER, x, y);
9582
+
9583
+ // Use SCALED half height since localPoint is in scaled coords
9584
+ const scaleY = target.scaleY || 1;
9585
+ const scaledHalfHeight = target.height * scaleY / 2;
9586
+
9587
+ // Calculate new expansion (how far below the object's bottom edge)
9588
+ const newExpandBottom = Math.max(0, localPoint.y - scaledHalfHeight);
9589
+ if (Math.abs(newExpandBottom - expansion.expandBottom) > 0.5) {
9590
+ var _target$canvas4;
9591
+ expansion.expandBottom = newExpandBottom;
9592
+ target.setCoords();
9593
+ (_target$canvas4 = target.canvas) === null || _target$canvas4 === void 0 || _target$canvas4.requestRenderAll();
9594
+ return true;
9595
+ }
9596
+ return false;
9597
+ };
9598
+
9599
+ /**
9600
+ * Combined handler for corner controls (affects two directions)
9601
+ */
9602
+ function createCornerExpandHandler(horizontalHandler, verticalHandler) {
9603
+ return (eventData, transform, x, y) => {
9604
+ const h = horizontalHandler(eventData, transform, x, y);
9605
+ const v = verticalHandler(eventData, transform, x, y);
9606
+ return h || v;
9607
+ };
9608
+ }
9609
+
9610
+ /**
9611
+ * Cursor style handler for expand controls
9612
+ */
9613
+ function expandCursorStyleHandler(eventData, control, fabricObject) {
9614
+ const expandControl = control;
9615
+ const directions = expandControl.expandDirections;
9616
+
9617
+ // Determine cursor based on direction
9618
+ if (directions.includes('left') && directions.includes('right')) {
9619
+ return 'ew-resize';
9620
+ }
9621
+ if (directions.includes('top') && directions.includes('bottom')) {
9622
+ return 'ns-resize';
9623
+ }
9624
+ if (directions.includes('left') || directions.includes('right')) {
9625
+ return 'ew-resize';
9626
+ }
9627
+ if (directions.includes('top') || directions.includes('bottom')) {
9628
+ return 'ns-resize';
9629
+ }
9630
+ if (directions.length === 2) {
9631
+ // Corner
9632
+ if (directions.includes('left') && directions.includes('top') || directions.includes('right') && directions.includes('bottom')) {
9633
+ return 'nwse-resize';
9634
+ }
9635
+ return 'nesw-resize';
9636
+ }
9637
+ return 'move';
9638
+ }
9639
+
9640
+ /**
9641
+ * Create the set of expand controls for an object
9642
+ */
9643
+ function createExpandControls() {
9644
+ return {
9645
+ // Edge controls
9646
+ ml: new ExpandControl({
9647
+ x: -0.5,
9648
+ y: 0,
9649
+ expandDirections: ['left'],
9650
+ actionHandler: expandLeftHandler,
9651
+ cursorStyleHandler: expandCursorStyleHandler,
9652
+ sizeX: EDGE_HANDLE_WIDTH,
9653
+ sizeY: EDGE_HANDLE_HEIGHT
9654
+ }),
9655
+ mr: new ExpandControl({
9656
+ x: 0.5,
9657
+ y: 0,
9658
+ expandDirections: ['right'],
9659
+ actionHandler: expandRightHandler,
9660
+ cursorStyleHandler: expandCursorStyleHandler,
9661
+ sizeX: EDGE_HANDLE_WIDTH,
9662
+ sizeY: EDGE_HANDLE_HEIGHT
9663
+ }),
9664
+ mt: new ExpandControl({
9665
+ x: 0,
9666
+ y: -0.5,
9667
+ expandDirections: ['top'],
9668
+ actionHandler: expandTopHandler,
9669
+ cursorStyleHandler: expandCursorStyleHandler,
9670
+ sizeX: EDGE_HANDLE_HEIGHT,
9671
+ sizeY: EDGE_HANDLE_WIDTH
9672
+ }),
9673
+ mb: new ExpandControl({
9674
+ x: 0,
9675
+ y: 0.5,
9676
+ expandDirections: ['bottom'],
9677
+ actionHandler: expandBottomHandler,
9678
+ cursorStyleHandler: expandCursorStyleHandler,
9679
+ sizeX: EDGE_HANDLE_HEIGHT,
9680
+ sizeY: EDGE_HANDLE_WIDTH
9681
+ }),
9682
+ // Corner controls
9683
+ tl: new ExpandControl({
9684
+ x: -0.5,
9685
+ y: -0.5,
9686
+ expandDirections: ['left', 'top'],
9687
+ actionHandler: createCornerExpandHandler(expandLeftHandler, expandTopHandler),
9688
+ cursorStyleHandler: expandCursorStyleHandler,
9689
+ sizeX: CORNER_HANDLE_SIZE,
9690
+ sizeY: CORNER_HANDLE_SIZE
9691
+ }),
9692
+ tr: new ExpandControl({
9693
+ x: 0.5,
9694
+ y: -0.5,
9695
+ expandDirections: ['right', 'top'],
9696
+ actionHandler: createCornerExpandHandler(expandRightHandler, expandTopHandler),
9697
+ cursorStyleHandler: expandCursorStyleHandler,
9698
+ sizeX: CORNER_HANDLE_SIZE,
9699
+ sizeY: CORNER_HANDLE_SIZE
9700
+ }),
9701
+ bl: new ExpandControl({
9702
+ x: -0.5,
9703
+ y: 0.5,
9704
+ expandDirections: ['left', 'bottom'],
9705
+ actionHandler: createCornerExpandHandler(expandLeftHandler, expandBottomHandler),
9706
+ cursorStyleHandler: expandCursorStyleHandler,
9707
+ sizeX: CORNER_HANDLE_SIZE,
9708
+ sizeY: CORNER_HANDLE_SIZE
9709
+ }),
9710
+ br: new ExpandControl({
9711
+ x: 0.5,
9712
+ y: 0.5,
9713
+ expandDirections: ['right', 'bottom'],
9714
+ actionHandler: createCornerExpandHandler(expandRightHandler, expandBottomHandler),
9715
+ cursorStyleHandler: expandCursorStyleHandler,
9716
+ sizeX: CORNER_HANDLE_SIZE,
9717
+ sizeY: CORNER_HANDLE_SIZE
9718
+ })
9719
+ };
9720
+ }
9721
+
9322
9722
  class InteractiveFabricObject extends FabricObject$1 {
9323
9723
  static getDefaults() {
9324
9724
  return {
@@ -9335,6 +9735,9 @@ class InteractiveFabricObject extends FabricObject$1 {
9335
9735
  super();
9336
9736
  Object.assign(this, this.constructor.createControls(), InteractiveFabricObject.ownDefaults);
9337
9737
  this.setOptions(options);
9738
+ // Initialize expansion state
9739
+ this.expandMode = false;
9740
+ this.expansion = createDefaultExpansion();
9338
9741
  }
9339
9742
 
9340
9743
  /**
@@ -9617,6 +10020,11 @@ class InteractiveFabricObject extends FabricObject$1 {
9617
10020
  } else {
9618
10021
  size = this._calculateCurrentDimensions().scalarAdd(this.borderScaleFactor);
9619
10022
  }
10023
+
10024
+ // Draw expansion preview if in expand mode or has expansion
10025
+ if (this.expandMode || this.hasExpansion()) {
10026
+ this.drawExpandPreview(ctx, size);
10027
+ }
9620
10028
  this._drawBorders(ctx, size, styleOverride);
9621
10029
  }
9622
10030
 
@@ -9869,6 +10277,192 @@ class InteractiveFabricObject extends FabricObject$1 {
9869
10277
  renderDropTargetEffect(_e) {
9870
10278
  // for subclasses
9871
10279
  }
10280
+
10281
+ // ==================== EXPAND MODE API ====================
10282
+
10283
+ /**
10284
+ * Saved lock state before expand mode
10285
+ * @private
10286
+ */
10287
+
10288
+ /**
10289
+ * Enter expand mode - switches controls to expansion handles
10290
+ * for defining AI outpainting areas
10291
+ */
10292
+ enterExpandMode() {
10293
+ var _this$canvas2;
10294
+ if (this.expandMode) return;
10295
+
10296
+ // Save current controls
10297
+ this._savedControls = this.controls;
10298
+
10299
+ // Save and lock movement/scaling to prevent default behaviors
10300
+ this._savedLockMovementX = this.lockMovementX;
10301
+ this._savedLockMovementY = this.lockMovementY;
10302
+ this._savedLockScalingX = this.lockScalingX;
10303
+ this._savedLockScalingY = this.lockScalingY;
10304
+ this.lockMovementX = true;
10305
+ this.lockMovementY = true;
10306
+ this.lockScalingX = true;
10307
+ this.lockScalingY = true;
10308
+
10309
+ // Switch to expand controls
10310
+ this.controls = createExpandControls();
10311
+ this.expandMode = true;
10312
+
10313
+ // Recalculate control positions
10314
+ this.setCoords();
10315
+ (_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 || _this$canvas2.requestRenderAll();
10316
+ }
10317
+
10318
+ /**
10319
+ * Exit expand mode - restores normal controls
10320
+ */
10321
+ exitExpandMode() {
10322
+ var _this$canvas3;
10323
+ if (!this.expandMode) return;
10324
+
10325
+ // Restore saved controls
10326
+ if (this._savedControls) {
10327
+ this.controls = this._savedControls;
10328
+ this._savedControls = undefined;
10329
+ } else {
10330
+ // Fallback to default controls
10331
+ this.controls = createObjectDefaultControls();
10332
+ }
10333
+
10334
+ // Restore lock states
10335
+ if (this._savedLockMovementX !== undefined) {
10336
+ this.lockMovementX = this._savedLockMovementX;
10337
+ this._savedLockMovementX = undefined;
10338
+ }
10339
+ if (this._savedLockMovementY !== undefined) {
10340
+ this.lockMovementY = this._savedLockMovementY;
10341
+ this._savedLockMovementY = undefined;
10342
+ }
10343
+ if (this._savedLockScalingX !== undefined) {
10344
+ this.lockScalingX = this._savedLockScalingX;
10345
+ this._savedLockScalingX = undefined;
10346
+ }
10347
+ if (this._savedLockScalingY !== undefined) {
10348
+ this.lockScalingY = this._savedLockScalingY;
10349
+ this._savedLockScalingY = undefined;
10350
+ }
10351
+ this.expandMode = false;
10352
+
10353
+ // Recalculate control positions
10354
+ this.setCoords();
10355
+ (_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
10356
+ }
10357
+
10358
+ /**
10359
+ * Toggle expand mode
10360
+ */
10361
+ toggleExpandMode() {
10362
+ if (this.expandMode) {
10363
+ this.exitExpandMode();
10364
+ } else {
10365
+ this.enterExpandMode();
10366
+ }
10367
+ }
10368
+
10369
+ /**
10370
+ * Get the current expansion state
10371
+ * @returns ExpansionState with expandLeft, expandRight, expandTop, expandBottom
10372
+ */
10373
+ getExpansion() {
10374
+ return {
10375
+ ...this.expansion
10376
+ };
10377
+ }
10378
+
10379
+ /**
10380
+ * Set expansion values
10381
+ * @param expansion Partial expansion state to merge
10382
+ */
10383
+ setExpansion(expansion) {
10384
+ var _this$canvas4;
10385
+ Object.assign(this.expansion, expansion);
10386
+ this.setCoords();
10387
+ (_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.requestRenderAll();
10388
+ }
10389
+
10390
+ /**
10391
+ * Reset expansion to zero in all directions
10392
+ */
10393
+ resetExpansion() {
10394
+ var _this$canvas5;
10395
+ this.expansion = createDefaultExpansion();
10396
+ this.setCoords();
10397
+ (_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.requestRenderAll();
10398
+ }
10399
+
10400
+ /**
10401
+ * Check if object has any expansion set
10402
+ */
10403
+ hasExpansion() {
10404
+ const {
10405
+ expandLeft,
10406
+ expandRight,
10407
+ expandTop,
10408
+ expandBottom
10409
+ } = this.expansion;
10410
+ return expandLeft > 0 || expandRight > 0 || expandTop > 0 || expandBottom > 0;
10411
+ }
10412
+
10413
+ /**
10414
+ * Get the total expanded dimensions
10415
+ * @returns Object with expandedWidth and expandedHeight
10416
+ */
10417
+ getExpandedDimensions() {
10418
+ const {
10419
+ expandLeft,
10420
+ expandRight,
10421
+ expandTop,
10422
+ expandBottom
10423
+ } = this.expansion;
10424
+ return {
10425
+ expandedWidth: this.width + expandLeft + expandRight,
10426
+ expandedHeight: this.height + expandTop + expandBottom
10427
+ };
10428
+ }
10429
+
10430
+ /**
10431
+ * Draw the expansion preview area
10432
+ * Called during border rendering when in expand mode
10433
+ * @param ctx Canvas rendering context
10434
+ * @param size Object dimensions (already includes scale)
10435
+ * Note: expansion values are stored in screen pixels (already scaled)
10436
+ */
10437
+ drawExpandPreview(ctx, size) {
10438
+ if (!this.expandMode && !this.hasExpansion()) return;
10439
+ const {
10440
+ expandLeft,
10441
+ expandRight,
10442
+ expandTop,
10443
+ expandBottom
10444
+ } = this.expansion;
10445
+
10446
+ // Expansion values are already in screen pixels, use directly
10447
+ const expandedWidth = size.x + expandLeft + expandRight;
10448
+ const expandedHeight = size.y + expandTop + expandBottom;
10449
+
10450
+ // Offset to account for asymmetric expansion
10451
+ const offsetX = (expandRight - expandLeft) / 2;
10452
+ const offsetY = (expandBottom - expandTop) / 2;
10453
+ ctx.save();
10454
+
10455
+ // Draw expansion area fill
10456
+ ctx.fillStyle = EXPAND_PREVIEW_FILL;
10457
+ ctx.fillRect(-expandedWidth / 2 + offsetX, -expandedHeight / 2 + offsetY, expandedWidth, expandedHeight);
10458
+
10459
+ // Draw expansion border
10460
+ ctx.strokeStyle = EXPAND_PREVIEW_STROKE;
10461
+ ctx.lineWidth = 1;
10462
+ ctx.setLineDash(EXPAND_PREVIEW_DASH);
10463
+ ctx.strokeRect(-expandedWidth / 2 + offsetX, -expandedHeight / 2 + offsetY, expandedWidth, expandedHeight);
10464
+ ctx.restore();
10465
+ }
9872
10466
  }
9873
10467
  /**
9874
10468
  * The object's controls' position in viewport coordinates
@@ -9905,6 +10499,16 @@ class InteractiveFabricObject extends FabricObject$1 {
9905
10499
  * @TODO use git blame to investigate why it was added
9906
10500
  * DON'T USE IT. WE WILL TRY TO REMOVE IT
9907
10501
  */
10502
+ /**
10503
+ * Whether the object is in expand mode (for AI outpainting)
10504
+ */
10505
+ /**
10506
+ * Expansion state for AI outpainting (pixels to expand in each direction)
10507
+ */
10508
+ /**
10509
+ * Saved controls when switching to expand mode
10510
+ * @private
10511
+ */
9908
10512
  _defineProperty(InteractiveFabricObject, "ownDefaults", interactiveObjectDefaultValues);
9909
10513
 
9910
10514
  /***
@@ -30464,6 +31068,11 @@ class Frame extends Group {
30464
31068
  * @private
30465
31069
  */
30466
31070
  _defineProperty(this, "_editModeClipPath", void 0);
31071
+ // ==================== CONTENT EXPAND MODE ====================
31072
+ /**
31073
+ * Whether the content image is in expand mode
31074
+ */
31075
+ _defineProperty(this, "_contentExpandMode", false);
30467
31076
  Object.assign(this, Frame.ownDefaults);
30468
31077
 
30469
31078
  // Apply user options
@@ -31206,60 +31815,69 @@ class Frame extends Group {
31206
31815
 
31207
31816
  // Draw edit mode overlay if in edit mode
31208
31817
  if (this.isEditMode && this._editModeClipPath) {
31209
- this._renderEditModeOverlay(ctx);
31818
+ this._renderEditModeOverlay(ctx, false);
31819
+
31820
+ // In expand mode, re-render the content image's controls ON TOP of the overlay
31821
+ if (this._contentExpandMode && this._contentImage) {
31822
+ this._contentImage._renderControls(ctx);
31823
+ }
31210
31824
  }
31211
31825
  }
31212
31826
 
31213
31827
  /**
31214
31828
  * Renders the edit mode overlay - dims area outside frame, shows frame border
31215
31829
  * @private
31830
+ * @param skipDimOverlay - if true, skip the dark overlay (for expand mode)
31216
31831
  */
31217
31832
  _renderEditModeOverlay(ctx) {
31833
+ let skipDimOverlay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
31218
31834
  ctx.save();
31219
31835
 
31220
31836
  // Apply the group's transform
31221
31837
  const m = this.calcTransformMatrix();
31222
31838
  ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
31223
31839
 
31224
- // Draw semi-transparent overlay on the OUTSIDE of the frame
31225
- // We do this by drawing a large rect and cutting out the frame shape
31226
- ctx.beginPath();
31227
-
31228
- // Large outer rectangle (covers the whole image area)
31229
- const padding = 2000; // Large enough to cover any overflow
31230
- ctx.rect(-padding, -padding, padding * 2, padding * 2);
31840
+ // Draw semi-transparent overlay on the OUTSIDE of the frame (skip in expand mode)
31841
+ if (!skipDimOverlay) {
31842
+ // We do this by drawing a large rect and cutting out the frame shape
31843
+ ctx.beginPath();
31231
31844
 
31232
- // Cut out the frame shape (counter-clockwise to create hole)
31233
- if (this.frameShape === 'circle') {
31234
- const radius = Math.min(this.frameWidth, this.frameHeight) / 2;
31235
- ctx.moveTo(radius, 0);
31236
- ctx.arc(0, 0, radius, 0, Math.PI * 2, true);
31237
- } else if (this.frameShape === 'rounded-rect') {
31238
- const w = this.frameWidth / 2;
31239
- const h = this.frameHeight / 2;
31240
- const r = Math.min(this.frameBorderRadius, w, h);
31241
- ctx.moveTo(w, h - r);
31242
- ctx.arcTo(w, -h, w - r, -h, r);
31243
- ctx.arcTo(-w, -h, -w, -h + r, r);
31244
- ctx.arcTo(-w, h, -w + r, h, r);
31245
- ctx.arcTo(w, h, w, h - r, r);
31246
- ctx.closePath();
31247
- } else {
31248
- // Rectangle
31249
- const w = this.frameWidth / 2;
31250
- const h = this.frameHeight / 2;
31251
- ctx.moveTo(w, -h);
31252
- ctx.lineTo(-w, -h);
31253
- ctx.lineTo(-w, h);
31254
- ctx.lineTo(w, h);
31255
- ctx.closePath();
31845
+ // Large outer rectangle (covers the whole image area)
31846
+ const padding = 2000; // Large enough to cover any overflow
31847
+ ctx.rect(-padding, -padding, padding * 2, padding * 2);
31848
+
31849
+ // Cut out the frame shape (counter-clockwise to create hole)
31850
+ if (this.frameShape === 'circle') {
31851
+ const radius = Math.min(this.frameWidth, this.frameHeight) / 2;
31852
+ ctx.moveTo(radius, 0);
31853
+ ctx.arc(0, 0, radius, 0, Math.PI * 2, true);
31854
+ } else if (this.frameShape === 'rounded-rect') {
31855
+ const w = this.frameWidth / 2;
31856
+ const h = this.frameHeight / 2;
31857
+ const r = Math.min(this.frameBorderRadius, w, h);
31858
+ ctx.moveTo(w, h - r);
31859
+ ctx.arcTo(w, -h, w - r, -h, r);
31860
+ ctx.arcTo(-w, -h, -w, -h + r, r);
31861
+ ctx.arcTo(-w, h, -w + r, h, r);
31862
+ ctx.arcTo(w, h, w, h - r, r);
31863
+ ctx.closePath();
31864
+ } else {
31865
+ // Rectangle
31866
+ const w = this.frameWidth / 2;
31867
+ const h = this.frameHeight / 2;
31868
+ ctx.moveTo(w, -h);
31869
+ ctx.lineTo(-w, -h);
31870
+ ctx.lineTo(-w, h);
31871
+ ctx.lineTo(w, h);
31872
+ ctx.closePath();
31873
+ }
31874
+
31875
+ // Fill with semi-transparent dark overlay
31876
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
31877
+ ctx.fill('evenodd');
31256
31878
  }
31257
31879
 
31258
- // Fill with semi-transparent dark overlay
31259
- ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
31260
- ctx.fill('evenodd');
31261
-
31262
- // Draw frame border
31880
+ // Draw frame border (always visible)
31263
31881
  ctx.beginPath();
31264
31882
  if (this.frameShape === 'circle') {
31265
31883
  const radius = Math.min(this.frameWidth, this.frameHeight) / 2;
@@ -31299,6 +31917,11 @@ class Frame extends Group {
31299
31917
  if (!this._contentImage || !this.isEditMode) {
31300
31918
  return;
31301
31919
  }
31920
+
31921
+ // Exit content expand mode first if active
31922
+ if (this._contentExpandMode) {
31923
+ this.exitContentExpandMode();
31924
+ }
31302
31925
  this.isEditMode = false;
31303
31926
 
31304
31927
  // Remove constraint handlers
@@ -31387,6 +32010,96 @@ class Frame extends Group {
31387
32010
  this.enterEditMode();
31388
32011
  }
31389
32012
  }
32013
+ /**
32014
+ * Enter expand mode for the content image (for AI outpainting)
32015
+ * Must be in edit mode first
32016
+ */
32017
+ enterContentExpandMode() {
32018
+ if (!this._contentImage || !this.isEditMode) {
32019
+ console.warn('Frame: Must be in edit mode with content to enter expand mode');
32020
+ return;
32021
+ }
32022
+ if (this._contentExpandMode) return;
32023
+ this._contentExpandMode = true;
32024
+
32025
+ // Remove constraints - in expand mode we don't enforce image covering frame
32026
+ this._removeEditModeConstraints();
32027
+
32028
+ // Enter expand mode on the content image
32029
+ this._contentImage.enterExpandMode();
32030
+ if (this.canvas) {
32031
+ this.canvas.requestRenderAll();
32032
+ }
32033
+ }
32034
+
32035
+ /**
32036
+ * Exit expand mode for the content image
32037
+ */
32038
+ exitContentExpandMode() {
32039
+ if (!this._contentImage || !this._contentExpandMode) return;
32040
+ this._contentExpandMode = false;
32041
+
32042
+ // Exit expand mode on the content image
32043
+ this._contentImage.exitExpandMode();
32044
+
32045
+ // Re-add constraints if still in edit mode
32046
+ if (this.isEditMode) {
32047
+ this._setupEditModeConstraints();
32048
+ }
32049
+ if (this.canvas) {
32050
+ this.canvas.requestRenderAll();
32051
+ }
32052
+ }
32053
+
32054
+ /**
32055
+ * Toggle expand mode for content image
32056
+ */
32057
+ toggleContentExpandMode() {
32058
+ if (this._contentExpandMode) {
32059
+ this.exitContentExpandMode();
32060
+ } else {
32061
+ this.enterContentExpandMode();
32062
+ }
32063
+ }
32064
+
32065
+ /**
32066
+ * Check if content is in expand mode
32067
+ */
32068
+ isContentExpandMode() {
32069
+ return this._contentExpandMode;
32070
+ }
32071
+
32072
+ /**
32073
+ * Get expansion values from the content image
32074
+ */
32075
+ getContentExpansion() {
32076
+ if (!this._contentImage) return null;
32077
+ return this._contentImage.getExpansion();
32078
+ }
32079
+
32080
+ /**
32081
+ * Set expansion values on the content image
32082
+ */
32083
+ setContentExpansion(expansion) {
32084
+ if (!this._contentImage) return;
32085
+ this._contentImage.setExpansion(expansion);
32086
+ }
32087
+
32088
+ /**
32089
+ * Reset content expansion to zero
32090
+ */
32091
+ resetContentExpansion() {
32092
+ if (!this._contentImage) return;
32093
+ this._contentImage.resetExpansion();
32094
+ }
32095
+
32096
+ /**
32097
+ * Check if content has any expansion
32098
+ */
32099
+ hasContentExpansion() {
32100
+ if (!this._contentImage) return false;
32101
+ return this._contentImage.hasExpansion();
32102
+ }
31390
32103
 
31391
32104
  /**
31392
32105
  * Resizes the frame to new dimensions (Canva-like behavior)
@@ -32793,7 +33506,15 @@ function createPathControls(path) {
32793
33506
 
32794
33507
  var index = /*#__PURE__*/Object.freeze({
32795
33508
  __proto__: null,
33509
+ EXPAND_HANDLE_FILL: EXPAND_HANDLE_FILL,
33510
+ EXPAND_HANDLE_STROKE: EXPAND_HANDLE_STROKE,
33511
+ EXPAND_PREVIEW_DASH: EXPAND_PREVIEW_DASH,
33512
+ EXPAND_PREVIEW_FILL: EXPAND_PREVIEW_FILL,
33513
+ EXPAND_PREVIEW_STROKE: EXPAND_PREVIEW_STROKE,
33514
+ ExpandControl: ExpandControl,
32796
33515
  changeWidth: changeWidth,
33516
+ createDefaultExpansion: createDefaultExpansion,
33517
+ createExpandControls: createExpandControls,
32797
33518
  createObjectDefaultControls: createObjectDefaultControls,
32798
33519
  createPathControls: createPathControls,
32799
33520
  createPolyActionHandler: createPolyActionHandler,
@@ -32802,7 +33523,12 @@ var index = /*#__PURE__*/Object.freeze({
32802
33523
  createResizeControls: createResizeControls,
32803
33524
  createTextboxDefaultControls: createTextboxDefaultControls,
32804
33525
  dragHandler: dragHandler,
33526
+ expandBottomHandler: expandBottomHandler,
33527
+ expandLeftHandler: expandLeftHandler,
33528
+ expandRightHandler: expandRightHandler,
33529
+ expandTopHandler: expandTopHandler,
32805
33530
  factoryPolyActionHandler: factoryPolyActionHandler,
33531
+ getExpansion: getExpansion,
32806
33532
  getLocalPoint: getLocalPoint,
32807
33533
  polyActionHandler: polyActionHandler,
32808
33534
  renderCircleControl: renderCircleControl,
@@ -32817,6 +33543,7 @@ var index = /*#__PURE__*/Object.freeze({
32817
33543
  scalingXOrSkewingY: scalingXOrSkewingY,
32818
33544
  scalingY: scalingY,
32819
33545
  scalingYOrSkewingX: scalingYOrSkewingX,
33546
+ setExpansion: setExpansion,
32820
33547
  skewCursorStyleHandler: skewCursorStyleHandler,
32821
33548
  skewHandlerX: skewHandlerX,
32822
33549
  skewHandlerY: skewHandlerY,