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

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
package/dist/index.js CHANGED
@@ -360,7 +360,7 @@
360
360
  }
361
361
  const cache = new Cache();
362
362
 
363
- var version = "7.0.1-beta23";
363
+ var version = "7.0.1-beta24";
364
364
 
365
365
  // use this syntax so babel plugin see this import here
366
366
  const VERSION = version;
@@ -9269,6 +9269,406 @@
9269
9269
  return controls;
9270
9270
  };
9271
9271
 
9272
+ /**
9273
+ * Expansion state for AI outpainting
9274
+ */
9275
+
9276
+ /**
9277
+ * Direction(s) that an expand control affects
9278
+ */
9279
+
9280
+ /**
9281
+ * Styling constants for expand controls
9282
+ */
9283
+ const EXPAND_HANDLE_FILL = '#ffffff';
9284
+ const EXPAND_HANDLE_STROKE = '#8b5cf6'; // Purple
9285
+ const EXPAND_PREVIEW_FILL = 'rgba(139, 92, 246, 0.1)';
9286
+ const EXPAND_PREVIEW_STROKE = '#8b5cf6';
9287
+ const EXPAND_PREVIEW_DASH = [6, 4];
9288
+
9289
+ // Handle sizes - similar to default Fabric controls
9290
+ const EDGE_HANDLE_WIDTH = 6;
9291
+ const EDGE_HANDLE_HEIGHT = 20;
9292
+ const CORNER_HANDLE_SIZE = 10;
9293
+ const HANDLE_RADIUS = 2;
9294
+
9295
+ /**
9296
+ * Default expansion state
9297
+ */
9298
+ const createDefaultExpansion = () => ({
9299
+ expandLeft: 0,
9300
+ expandRight: 0,
9301
+ expandTop: 0,
9302
+ expandBottom: 0
9303
+ });
9304
+
9305
+ /**
9306
+ * Get expansion state from object, initializing if needed
9307
+ */
9308
+ function getExpansion(obj) {
9309
+ if (!obj.expansion) {
9310
+ obj.expansion = createDefaultExpansion();
9311
+ }
9312
+ return obj.expansion;
9313
+ }
9314
+
9315
+ /**
9316
+ * Set expansion state on object
9317
+ */
9318
+ function setExpansion(obj, expansion) {
9319
+ const current = getExpansion(obj);
9320
+ Object.assign(current, expansion);
9321
+ }
9322
+
9323
+ /**
9324
+ * Custom control for AI expansion handles
9325
+ */
9326
+ class ExpandControl extends Control {
9327
+ /**
9328
+ * Which direction(s) this control affects
9329
+ */
9330
+
9331
+ constructor(options) {
9332
+ super(options);
9333
+ this.expandDirections = options.expandDirections;
9334
+ this.actionName = 'expand';
9335
+ }
9336
+
9337
+ /**
9338
+ * Position handler for expand controls
9339
+ * Positions controls at the expansion boundary, not the object boundary
9340
+ * Note: expansion values are stored in screen pixels (already scaled)
9341
+ */
9342
+ positionHandler(dim, finalMatrix, fabricObject, currentControl) {
9343
+ const expansion = getExpansion(fabricObject);
9344
+ const {
9345
+ expandLeft,
9346
+ expandRight,
9347
+ expandTop,
9348
+ expandBottom
9349
+ } = expansion;
9350
+
9351
+ // dim already includes scale, so use it directly for base size
9352
+ // expansion values are also in screen pixels, so use directly
9353
+ const halfWidth = dim.x / 2;
9354
+ const halfHeight = dim.y / 2;
9355
+
9356
+ // Calculate the expanded half dimensions
9357
+ const expandedHalfWidth = halfWidth + (expandLeft + expandRight) / 2;
9358
+ const expandedHalfHeight = halfHeight + (expandTop + expandBottom) / 2;
9359
+
9360
+ // Calculate offset from center based on asymmetric expansion
9361
+ const centerOffsetX = (expandRight - expandLeft) / 2;
9362
+ const centerOffsetY = (expandBottom - expandTop) / 2;
9363
+ let posX;
9364
+ let posY;
9365
+
9366
+ // Position based on which side this control is on
9367
+ if (this.x < 0) {
9368
+ // Left side controls
9369
+ posX = -expandedHalfWidth + centerOffsetX;
9370
+ } else if (this.x > 0) {
9371
+ // Right side controls
9372
+ posX = expandedHalfWidth + centerOffsetX;
9373
+ } else {
9374
+ // Center (top/bottom only)
9375
+ posX = centerOffsetX;
9376
+ }
9377
+ if (this.y < 0) {
9378
+ // Top side controls
9379
+ posY = -expandedHalfHeight + centerOffsetY;
9380
+ } else if (this.y > 0) {
9381
+ // Bottom side controls
9382
+ posY = expandedHalfHeight + centerOffsetY;
9383
+ } else {
9384
+ // Center (left/right only)
9385
+ posY = centerOffsetY;
9386
+ }
9387
+ return new Point(posX, posY).transform(finalMatrix);
9388
+ }
9389
+
9390
+ /**
9391
+ * Custom render for expand handles - purple border with white fill
9392
+ */
9393
+ render(ctx, left, top, styleOverride, fabricObject) {
9394
+ ctx.save();
9395
+ ctx.translate(left, top);
9396
+ const angle = fabricObject.getTotalAngle();
9397
+ ctx.rotate(degreesToRadians(angle));
9398
+
9399
+ // Add subtle shadow
9400
+ ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
9401
+ ctx.shadowBlur = 3;
9402
+ ctx.shadowOffsetX = 0;
9403
+ ctx.shadowOffsetY = 1;
9404
+ ctx.fillStyle = EXPAND_HANDLE_FILL;
9405
+ ctx.strokeStyle = EXPAND_HANDLE_STROKE;
9406
+ ctx.lineWidth = 2;
9407
+
9408
+ // Determine if this is a corner or edge control
9409
+ const isCorner = this.x !== 0 && this.y !== 0;
9410
+ if (isCorner) {
9411
+ // Corner: draw a circle
9412
+ ctx.beginPath();
9413
+ ctx.arc(0, 0, CORNER_HANDLE_SIZE / 2, 0, Math.PI * 2);
9414
+ ctx.fill();
9415
+ ctx.stroke();
9416
+ } else if (this.y === 0) {
9417
+ // Horizontal edge (left/right): vertical pill
9418
+ ctx.beginPath();
9419
+ ctx.roundRect(-EDGE_HANDLE_WIDTH / 2, -EDGE_HANDLE_HEIGHT / 2, EDGE_HANDLE_WIDTH, EDGE_HANDLE_HEIGHT, HANDLE_RADIUS);
9420
+ ctx.fill();
9421
+ ctx.stroke();
9422
+ } else {
9423
+ // Vertical edge (top/bottom): horizontal pill
9424
+ ctx.beginPath();
9425
+ ctx.roundRect(-EDGE_HANDLE_HEIGHT / 2, -EDGE_HANDLE_WIDTH / 2, EDGE_HANDLE_HEIGHT, EDGE_HANDLE_WIDTH, HANDLE_RADIUS);
9426
+ ctx.fill();
9427
+ ctx.stroke();
9428
+ }
9429
+ ctx.restore();
9430
+ }
9431
+ }
9432
+
9433
+ /**
9434
+ * Action handler for expanding left edge
9435
+ */
9436
+ const expandLeftHandler = (eventData, transform, x, y) => {
9437
+ const {
9438
+ target
9439
+ } = transform;
9440
+ const expansion = getExpansion(target);
9441
+
9442
+ // Use Fabric's getLocalPoint with center origin (returns scaled coordinates)
9443
+ const localPoint = getLocalPoint(transform, CENTER, CENTER, x, y);
9444
+
9445
+ // Use SCALED half width since localPoint is in scaled coords
9446
+ const scaleX = target.scaleX || 1;
9447
+ const scaledHalfWidth = target.width * scaleX / 2;
9448
+
9449
+ // Calculate new expansion (how far left of the object's left edge)
9450
+ // localPoint.x is negative when left of center
9451
+ // Expansion starts when localPoint.x < -scaledHalfWidth
9452
+ const newExpandLeft = Math.max(0, -scaledHalfWidth - localPoint.x);
9453
+ if (Math.abs(newExpandLeft - expansion.expandLeft) > 0.5) {
9454
+ var _target$canvas;
9455
+ expansion.expandLeft = newExpandLeft;
9456
+ target.setCoords();
9457
+ (_target$canvas = target.canvas) === null || _target$canvas === void 0 || _target$canvas.requestRenderAll();
9458
+ return true;
9459
+ }
9460
+ return false;
9461
+ };
9462
+
9463
+ /**
9464
+ * Action handler for expanding right edge
9465
+ */
9466
+ const expandRightHandler = (eventData, transform, x, y) => {
9467
+ const {
9468
+ target
9469
+ } = transform;
9470
+ const expansion = getExpansion(target);
9471
+
9472
+ // Use Fabric's getLocalPoint with center origin (returns scaled coordinates)
9473
+ const localPoint = getLocalPoint(transform, CENTER, CENTER, x, y);
9474
+
9475
+ // Use SCALED half width since localPoint is in scaled coords
9476
+ const scaleX = target.scaleX || 1;
9477
+ const scaledHalfWidth = target.width * scaleX / 2;
9478
+
9479
+ // Calculate new expansion (how far right of the object's right edge)
9480
+ // localPoint.x is positive when right of center
9481
+ // Expansion starts when localPoint.x > scaledHalfWidth
9482
+ const newExpandRight = Math.max(0, localPoint.x - scaledHalfWidth);
9483
+ if (Math.abs(newExpandRight - expansion.expandRight) > 0.5) {
9484
+ var _target$canvas2;
9485
+ expansion.expandRight = newExpandRight;
9486
+ target.setCoords();
9487
+ (_target$canvas2 = target.canvas) === null || _target$canvas2 === void 0 || _target$canvas2.requestRenderAll();
9488
+ return true;
9489
+ }
9490
+ return false;
9491
+ };
9492
+
9493
+ /**
9494
+ * Action handler for expanding top edge
9495
+ */
9496
+ const expandTopHandler = (eventData, transform, x, y) => {
9497
+ const {
9498
+ target
9499
+ } = transform;
9500
+ const expansion = getExpansion(target);
9501
+
9502
+ // Use Fabric's getLocalPoint with center origin (returns scaled coordinates)
9503
+ const localPoint = getLocalPoint(transform, CENTER, CENTER, x, y);
9504
+
9505
+ // Use SCALED half height since localPoint is in scaled coords
9506
+ const scaleY = target.scaleY || 1;
9507
+ const scaledHalfHeight = target.height * scaleY / 2;
9508
+
9509
+ // Calculate new expansion (how far above the object's top edge)
9510
+ const newExpandTop = Math.max(0, -scaledHalfHeight - localPoint.y);
9511
+ if (Math.abs(newExpandTop - expansion.expandTop) > 0.5) {
9512
+ var _target$canvas3;
9513
+ expansion.expandTop = newExpandTop;
9514
+ target.setCoords();
9515
+ (_target$canvas3 = target.canvas) === null || _target$canvas3 === void 0 || _target$canvas3.requestRenderAll();
9516
+ return true;
9517
+ }
9518
+ return false;
9519
+ };
9520
+
9521
+ /**
9522
+ * Action handler for expanding bottom edge
9523
+ */
9524
+ const expandBottomHandler = (eventData, transform, x, y) => {
9525
+ const {
9526
+ target
9527
+ } = transform;
9528
+ const expansion = getExpansion(target);
9529
+
9530
+ // Use Fabric's getLocalPoint with center origin (returns scaled coordinates)
9531
+ const localPoint = getLocalPoint(transform, CENTER, CENTER, x, y);
9532
+
9533
+ // Use SCALED half height since localPoint is in scaled coords
9534
+ const scaleY = target.scaleY || 1;
9535
+ const scaledHalfHeight = target.height * scaleY / 2;
9536
+
9537
+ // Calculate new expansion (how far below the object's bottom edge)
9538
+ const newExpandBottom = Math.max(0, localPoint.y - scaledHalfHeight);
9539
+ if (Math.abs(newExpandBottom - expansion.expandBottom) > 0.5) {
9540
+ var _target$canvas4;
9541
+ expansion.expandBottom = newExpandBottom;
9542
+ target.setCoords();
9543
+ (_target$canvas4 = target.canvas) === null || _target$canvas4 === void 0 || _target$canvas4.requestRenderAll();
9544
+ return true;
9545
+ }
9546
+ return false;
9547
+ };
9548
+
9549
+ /**
9550
+ * Combined handler for corner controls (affects two directions)
9551
+ */
9552
+ function createCornerExpandHandler(horizontalHandler, verticalHandler) {
9553
+ return (eventData, transform, x, y) => {
9554
+ const h = horizontalHandler(eventData, transform, x, y);
9555
+ const v = verticalHandler(eventData, transform, x, y);
9556
+ return h || v;
9557
+ };
9558
+ }
9559
+
9560
+ /**
9561
+ * Cursor style handler for expand controls
9562
+ */
9563
+ function expandCursorStyleHandler(eventData, control, fabricObject) {
9564
+ const expandControl = control;
9565
+ const directions = expandControl.expandDirections;
9566
+
9567
+ // Determine cursor based on direction
9568
+ if (directions.includes('left') && directions.includes('right')) {
9569
+ return 'ew-resize';
9570
+ }
9571
+ if (directions.includes('top') && directions.includes('bottom')) {
9572
+ return 'ns-resize';
9573
+ }
9574
+ if (directions.includes('left') || directions.includes('right')) {
9575
+ return 'ew-resize';
9576
+ }
9577
+ if (directions.includes('top') || directions.includes('bottom')) {
9578
+ return 'ns-resize';
9579
+ }
9580
+ if (directions.length === 2) {
9581
+ // Corner
9582
+ if (directions.includes('left') && directions.includes('top') || directions.includes('right') && directions.includes('bottom')) {
9583
+ return 'nwse-resize';
9584
+ }
9585
+ return 'nesw-resize';
9586
+ }
9587
+ return 'move';
9588
+ }
9589
+
9590
+ /**
9591
+ * Create the set of expand controls for an object
9592
+ */
9593
+ function createExpandControls() {
9594
+ return {
9595
+ // Edge controls
9596
+ ml: new ExpandControl({
9597
+ x: -0.5,
9598
+ y: 0,
9599
+ expandDirections: ['left'],
9600
+ actionHandler: expandLeftHandler,
9601
+ cursorStyleHandler: expandCursorStyleHandler,
9602
+ sizeX: EDGE_HANDLE_WIDTH,
9603
+ sizeY: EDGE_HANDLE_HEIGHT
9604
+ }),
9605
+ mr: new ExpandControl({
9606
+ x: 0.5,
9607
+ y: 0,
9608
+ expandDirections: ['right'],
9609
+ actionHandler: expandRightHandler,
9610
+ cursorStyleHandler: expandCursorStyleHandler,
9611
+ sizeX: EDGE_HANDLE_WIDTH,
9612
+ sizeY: EDGE_HANDLE_HEIGHT
9613
+ }),
9614
+ mt: new ExpandControl({
9615
+ x: 0,
9616
+ y: -0.5,
9617
+ expandDirections: ['top'],
9618
+ actionHandler: expandTopHandler,
9619
+ cursorStyleHandler: expandCursorStyleHandler,
9620
+ sizeX: EDGE_HANDLE_HEIGHT,
9621
+ sizeY: EDGE_HANDLE_WIDTH
9622
+ }),
9623
+ mb: new ExpandControl({
9624
+ x: 0,
9625
+ y: 0.5,
9626
+ expandDirections: ['bottom'],
9627
+ actionHandler: expandBottomHandler,
9628
+ cursorStyleHandler: expandCursorStyleHandler,
9629
+ sizeX: EDGE_HANDLE_HEIGHT,
9630
+ sizeY: EDGE_HANDLE_WIDTH
9631
+ }),
9632
+ // Corner controls
9633
+ tl: new ExpandControl({
9634
+ x: -0.5,
9635
+ y: -0.5,
9636
+ expandDirections: ['left', 'top'],
9637
+ actionHandler: createCornerExpandHandler(expandLeftHandler, expandTopHandler),
9638
+ cursorStyleHandler: expandCursorStyleHandler,
9639
+ sizeX: CORNER_HANDLE_SIZE,
9640
+ sizeY: CORNER_HANDLE_SIZE
9641
+ }),
9642
+ tr: new ExpandControl({
9643
+ x: 0.5,
9644
+ y: -0.5,
9645
+ expandDirections: ['right', 'top'],
9646
+ actionHandler: createCornerExpandHandler(expandRightHandler, expandTopHandler),
9647
+ cursorStyleHandler: expandCursorStyleHandler,
9648
+ sizeX: CORNER_HANDLE_SIZE,
9649
+ sizeY: CORNER_HANDLE_SIZE
9650
+ }),
9651
+ bl: new ExpandControl({
9652
+ x: -0.5,
9653
+ y: 0.5,
9654
+ expandDirections: ['left', 'bottom'],
9655
+ actionHandler: createCornerExpandHandler(expandLeftHandler, expandBottomHandler),
9656
+ cursorStyleHandler: expandCursorStyleHandler,
9657
+ sizeX: CORNER_HANDLE_SIZE,
9658
+ sizeY: CORNER_HANDLE_SIZE
9659
+ }),
9660
+ br: new ExpandControl({
9661
+ x: 0.5,
9662
+ y: 0.5,
9663
+ expandDirections: ['right', 'bottom'],
9664
+ actionHandler: createCornerExpandHandler(expandRightHandler, expandBottomHandler),
9665
+ cursorStyleHandler: expandCursorStyleHandler,
9666
+ sizeX: CORNER_HANDLE_SIZE,
9667
+ sizeY: CORNER_HANDLE_SIZE
9668
+ })
9669
+ };
9670
+ }
9671
+
9272
9672
  class InteractiveFabricObject extends FabricObject$1 {
9273
9673
  static getDefaults() {
9274
9674
  return {
@@ -9285,6 +9685,9 @@
9285
9685
  super();
9286
9686
  Object.assign(this, this.constructor.createControls(), InteractiveFabricObject.ownDefaults);
9287
9687
  this.setOptions(options);
9688
+ // Initialize expansion state
9689
+ this.expandMode = false;
9690
+ this.expansion = createDefaultExpansion();
9288
9691
  }
9289
9692
 
9290
9693
  /**
@@ -9567,6 +9970,11 @@
9567
9970
  } else {
9568
9971
  size = this._calculateCurrentDimensions().scalarAdd(this.borderScaleFactor);
9569
9972
  }
9973
+
9974
+ // Draw expansion preview if in expand mode or has expansion
9975
+ if (this.expandMode || this.hasExpansion()) {
9976
+ this.drawExpandPreview(ctx, size);
9977
+ }
9570
9978
  this._drawBorders(ctx, size, styleOverride);
9571
9979
  }
9572
9980
 
@@ -9819,6 +10227,192 @@
9819
10227
  renderDropTargetEffect(_e) {
9820
10228
  // for subclasses
9821
10229
  }
10230
+
10231
+ // ==================== EXPAND MODE API ====================
10232
+
10233
+ /**
10234
+ * Saved lock state before expand mode
10235
+ * @private
10236
+ */
10237
+
10238
+ /**
10239
+ * Enter expand mode - switches controls to expansion handles
10240
+ * for defining AI outpainting areas
10241
+ */
10242
+ enterExpandMode() {
10243
+ var _this$canvas2;
10244
+ if (this.expandMode) return;
10245
+
10246
+ // Save current controls
10247
+ this._savedControls = this.controls;
10248
+
10249
+ // Save and lock movement/scaling to prevent default behaviors
10250
+ this._savedLockMovementX = this.lockMovementX;
10251
+ this._savedLockMovementY = this.lockMovementY;
10252
+ this._savedLockScalingX = this.lockScalingX;
10253
+ this._savedLockScalingY = this.lockScalingY;
10254
+ this.lockMovementX = true;
10255
+ this.lockMovementY = true;
10256
+ this.lockScalingX = true;
10257
+ this.lockScalingY = true;
10258
+
10259
+ // Switch to expand controls
10260
+ this.controls = createExpandControls();
10261
+ this.expandMode = true;
10262
+
10263
+ // Recalculate control positions
10264
+ this.setCoords();
10265
+ (_this$canvas2 = this.canvas) === null || _this$canvas2 === void 0 || _this$canvas2.requestRenderAll();
10266
+ }
10267
+
10268
+ /**
10269
+ * Exit expand mode - restores normal controls
10270
+ */
10271
+ exitExpandMode() {
10272
+ var _this$canvas3;
10273
+ if (!this.expandMode) return;
10274
+
10275
+ // Restore saved controls
10276
+ if (this._savedControls) {
10277
+ this.controls = this._savedControls;
10278
+ this._savedControls = undefined;
10279
+ } else {
10280
+ // Fallback to default controls
10281
+ this.controls = createObjectDefaultControls();
10282
+ }
10283
+
10284
+ // Restore lock states
10285
+ if (this._savedLockMovementX !== undefined) {
10286
+ this.lockMovementX = this._savedLockMovementX;
10287
+ this._savedLockMovementX = undefined;
10288
+ }
10289
+ if (this._savedLockMovementY !== undefined) {
10290
+ this.lockMovementY = this._savedLockMovementY;
10291
+ this._savedLockMovementY = undefined;
10292
+ }
10293
+ if (this._savedLockScalingX !== undefined) {
10294
+ this.lockScalingX = this._savedLockScalingX;
10295
+ this._savedLockScalingX = undefined;
10296
+ }
10297
+ if (this._savedLockScalingY !== undefined) {
10298
+ this.lockScalingY = this._savedLockScalingY;
10299
+ this._savedLockScalingY = undefined;
10300
+ }
10301
+ this.expandMode = false;
10302
+
10303
+ // Recalculate control positions
10304
+ this.setCoords();
10305
+ (_this$canvas3 = this.canvas) === null || _this$canvas3 === void 0 || _this$canvas3.requestRenderAll();
10306
+ }
10307
+
10308
+ /**
10309
+ * Toggle expand mode
10310
+ */
10311
+ toggleExpandMode() {
10312
+ if (this.expandMode) {
10313
+ this.exitExpandMode();
10314
+ } else {
10315
+ this.enterExpandMode();
10316
+ }
10317
+ }
10318
+
10319
+ /**
10320
+ * Get the current expansion state
10321
+ * @returns ExpansionState with expandLeft, expandRight, expandTop, expandBottom
10322
+ */
10323
+ getExpansion() {
10324
+ return {
10325
+ ...this.expansion
10326
+ };
10327
+ }
10328
+
10329
+ /**
10330
+ * Set expansion values
10331
+ * @param expansion Partial expansion state to merge
10332
+ */
10333
+ setExpansion(expansion) {
10334
+ var _this$canvas4;
10335
+ Object.assign(this.expansion, expansion);
10336
+ this.setCoords();
10337
+ (_this$canvas4 = this.canvas) === null || _this$canvas4 === void 0 || _this$canvas4.requestRenderAll();
10338
+ }
10339
+
10340
+ /**
10341
+ * Reset expansion to zero in all directions
10342
+ */
10343
+ resetExpansion() {
10344
+ var _this$canvas5;
10345
+ this.expansion = createDefaultExpansion();
10346
+ this.setCoords();
10347
+ (_this$canvas5 = this.canvas) === null || _this$canvas5 === void 0 || _this$canvas5.requestRenderAll();
10348
+ }
10349
+
10350
+ /**
10351
+ * Check if object has any expansion set
10352
+ */
10353
+ hasExpansion() {
10354
+ const {
10355
+ expandLeft,
10356
+ expandRight,
10357
+ expandTop,
10358
+ expandBottom
10359
+ } = this.expansion;
10360
+ return expandLeft > 0 || expandRight > 0 || expandTop > 0 || expandBottom > 0;
10361
+ }
10362
+
10363
+ /**
10364
+ * Get the total expanded dimensions
10365
+ * @returns Object with expandedWidth and expandedHeight
10366
+ */
10367
+ getExpandedDimensions() {
10368
+ const {
10369
+ expandLeft,
10370
+ expandRight,
10371
+ expandTop,
10372
+ expandBottom
10373
+ } = this.expansion;
10374
+ return {
10375
+ expandedWidth: this.width + expandLeft + expandRight,
10376
+ expandedHeight: this.height + expandTop + expandBottom
10377
+ };
10378
+ }
10379
+
10380
+ /**
10381
+ * Draw the expansion preview area
10382
+ * Called during border rendering when in expand mode
10383
+ * @param ctx Canvas rendering context
10384
+ * @param size Object dimensions (already includes scale)
10385
+ * Note: expansion values are stored in screen pixels (already scaled)
10386
+ */
10387
+ drawExpandPreview(ctx, size) {
10388
+ if (!this.expandMode && !this.hasExpansion()) return;
10389
+ const {
10390
+ expandLeft,
10391
+ expandRight,
10392
+ expandTop,
10393
+ expandBottom
10394
+ } = this.expansion;
10395
+
10396
+ // Expansion values are already in screen pixels, use directly
10397
+ const expandedWidth = size.x + expandLeft + expandRight;
10398
+ const expandedHeight = size.y + expandTop + expandBottom;
10399
+
10400
+ // Offset to account for asymmetric expansion
10401
+ const offsetX = (expandRight - expandLeft) / 2;
10402
+ const offsetY = (expandBottom - expandTop) / 2;
10403
+ ctx.save();
10404
+
10405
+ // Draw expansion area fill
10406
+ ctx.fillStyle = EXPAND_PREVIEW_FILL;
10407
+ ctx.fillRect(-expandedWidth / 2 + offsetX, -expandedHeight / 2 + offsetY, expandedWidth, expandedHeight);
10408
+
10409
+ // Draw expansion border
10410
+ ctx.strokeStyle = EXPAND_PREVIEW_STROKE;
10411
+ ctx.lineWidth = 1;
10412
+ ctx.setLineDash(EXPAND_PREVIEW_DASH);
10413
+ ctx.strokeRect(-expandedWidth / 2 + offsetX, -expandedHeight / 2 + offsetY, expandedWidth, expandedHeight);
10414
+ ctx.restore();
10415
+ }
9822
10416
  }
9823
10417
  /**
9824
10418
  * The object's controls' position in viewport coordinates
@@ -9855,6 +10449,16 @@
9855
10449
  * @TODO use git blame to investigate why it was added
9856
10450
  * DON'T USE IT. WE WILL TRY TO REMOVE IT
9857
10451
  */
10452
+ /**
10453
+ * Whether the object is in expand mode (for AI outpainting)
10454
+ */
10455
+ /**
10456
+ * Expansion state for AI outpainting (pixels to expand in each direction)
10457
+ */
10458
+ /**
10459
+ * Saved controls when switching to expand mode
10460
+ * @private
10461
+ */
9858
10462
  _defineProperty(InteractiveFabricObject, "ownDefaults", interactiveObjectDefaultValues);
9859
10463
 
9860
10464
  /***
@@ -30414,6 +31018,11 @@
30414
31018
  * @private
30415
31019
  */
30416
31020
  _defineProperty(this, "_editModeClipPath", void 0);
31021
+ // ==================== CONTENT EXPAND MODE ====================
31022
+ /**
31023
+ * Whether the content image is in expand mode
31024
+ */
31025
+ _defineProperty(this, "_contentExpandMode", false);
30417
31026
  Object.assign(this, Frame.ownDefaults);
30418
31027
 
30419
31028
  // Apply user options
@@ -31156,60 +31765,69 @@
31156
31765
 
31157
31766
  // Draw edit mode overlay if in edit mode
31158
31767
  if (this.isEditMode && this._editModeClipPath) {
31159
- this._renderEditModeOverlay(ctx);
31768
+ this._renderEditModeOverlay(ctx, false);
31769
+
31770
+ // In expand mode, re-render the content image's controls ON TOP of the overlay
31771
+ if (this._contentExpandMode && this._contentImage) {
31772
+ this._contentImage._renderControls(ctx);
31773
+ }
31160
31774
  }
31161
31775
  }
31162
31776
 
31163
31777
  /**
31164
31778
  * Renders the edit mode overlay - dims area outside frame, shows frame border
31165
31779
  * @private
31780
+ * @param skipDimOverlay - if true, skip the dark overlay (for expand mode)
31166
31781
  */
31167
31782
  _renderEditModeOverlay(ctx) {
31783
+ let skipDimOverlay = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
31168
31784
  ctx.save();
31169
31785
 
31170
31786
  // Apply the group's transform
31171
31787
  const m = this.calcTransformMatrix();
31172
31788
  ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
31173
31789
 
31174
- // Draw semi-transparent overlay on the OUTSIDE of the frame
31175
- // We do this by drawing a large rect and cutting out the frame shape
31176
- ctx.beginPath();
31177
-
31178
- // Large outer rectangle (covers the whole image area)
31179
- const padding = 2000; // Large enough to cover any overflow
31180
- ctx.rect(-padding, -padding, padding * 2, padding * 2);
31790
+ // Draw semi-transparent overlay on the OUTSIDE of the frame (skip in expand mode)
31791
+ if (!skipDimOverlay) {
31792
+ // We do this by drawing a large rect and cutting out the frame shape
31793
+ ctx.beginPath();
31181
31794
 
31182
- // Cut out the frame shape (counter-clockwise to create hole)
31183
- if (this.frameShape === 'circle') {
31184
- const radius = Math.min(this.frameWidth, this.frameHeight) / 2;
31185
- ctx.moveTo(radius, 0);
31186
- ctx.arc(0, 0, radius, 0, Math.PI * 2, true);
31187
- } else if (this.frameShape === 'rounded-rect') {
31188
- const w = this.frameWidth / 2;
31189
- const h = this.frameHeight / 2;
31190
- const r = Math.min(this.frameBorderRadius, w, h);
31191
- ctx.moveTo(w, h - r);
31192
- ctx.arcTo(w, -h, w - r, -h, r);
31193
- ctx.arcTo(-w, -h, -w, -h + r, r);
31194
- ctx.arcTo(-w, h, -w + r, h, r);
31195
- ctx.arcTo(w, h, w, h - r, r);
31196
- ctx.closePath();
31197
- } else {
31198
- // Rectangle
31199
- const w = this.frameWidth / 2;
31200
- const h = this.frameHeight / 2;
31201
- ctx.moveTo(w, -h);
31202
- ctx.lineTo(-w, -h);
31203
- ctx.lineTo(-w, h);
31204
- ctx.lineTo(w, h);
31205
- ctx.closePath();
31795
+ // Large outer rectangle (covers the whole image area)
31796
+ const padding = 2000; // Large enough to cover any overflow
31797
+ ctx.rect(-padding, -padding, padding * 2, padding * 2);
31798
+
31799
+ // Cut out the frame shape (counter-clockwise to create hole)
31800
+ if (this.frameShape === 'circle') {
31801
+ const radius = Math.min(this.frameWidth, this.frameHeight) / 2;
31802
+ ctx.moveTo(radius, 0);
31803
+ ctx.arc(0, 0, radius, 0, Math.PI * 2, true);
31804
+ } else if (this.frameShape === 'rounded-rect') {
31805
+ const w = this.frameWidth / 2;
31806
+ const h = this.frameHeight / 2;
31807
+ const r = Math.min(this.frameBorderRadius, w, h);
31808
+ ctx.moveTo(w, h - r);
31809
+ ctx.arcTo(w, -h, w - r, -h, r);
31810
+ ctx.arcTo(-w, -h, -w, -h + r, r);
31811
+ ctx.arcTo(-w, h, -w + r, h, r);
31812
+ ctx.arcTo(w, h, w, h - r, r);
31813
+ ctx.closePath();
31814
+ } else {
31815
+ // Rectangle
31816
+ const w = this.frameWidth / 2;
31817
+ const h = this.frameHeight / 2;
31818
+ ctx.moveTo(w, -h);
31819
+ ctx.lineTo(-w, -h);
31820
+ ctx.lineTo(-w, h);
31821
+ ctx.lineTo(w, h);
31822
+ ctx.closePath();
31823
+ }
31824
+
31825
+ // Fill with semi-transparent dark overlay
31826
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
31827
+ ctx.fill('evenodd');
31206
31828
  }
31207
31829
 
31208
- // Fill with semi-transparent dark overlay
31209
- ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
31210
- ctx.fill('evenodd');
31211
-
31212
- // Draw frame border
31830
+ // Draw frame border (always visible)
31213
31831
  ctx.beginPath();
31214
31832
  if (this.frameShape === 'circle') {
31215
31833
  const radius = Math.min(this.frameWidth, this.frameHeight) / 2;
@@ -31249,6 +31867,11 @@
31249
31867
  if (!this._contentImage || !this.isEditMode) {
31250
31868
  return;
31251
31869
  }
31870
+
31871
+ // Exit content expand mode first if active
31872
+ if (this._contentExpandMode) {
31873
+ this.exitContentExpandMode();
31874
+ }
31252
31875
  this.isEditMode = false;
31253
31876
 
31254
31877
  // Remove constraint handlers
@@ -31337,6 +31960,96 @@
31337
31960
  this.enterEditMode();
31338
31961
  }
31339
31962
  }
31963
+ /**
31964
+ * Enter expand mode for the content image (for AI outpainting)
31965
+ * Must be in edit mode first
31966
+ */
31967
+ enterContentExpandMode() {
31968
+ if (!this._contentImage || !this.isEditMode) {
31969
+ console.warn('Frame: Must be in edit mode with content to enter expand mode');
31970
+ return;
31971
+ }
31972
+ if (this._contentExpandMode) return;
31973
+ this._contentExpandMode = true;
31974
+
31975
+ // Remove constraints - in expand mode we don't enforce image covering frame
31976
+ this._removeEditModeConstraints();
31977
+
31978
+ // Enter expand mode on the content image
31979
+ this._contentImage.enterExpandMode();
31980
+ if (this.canvas) {
31981
+ this.canvas.requestRenderAll();
31982
+ }
31983
+ }
31984
+
31985
+ /**
31986
+ * Exit expand mode for the content image
31987
+ */
31988
+ exitContentExpandMode() {
31989
+ if (!this._contentImage || !this._contentExpandMode) return;
31990
+ this._contentExpandMode = false;
31991
+
31992
+ // Exit expand mode on the content image
31993
+ this._contentImage.exitExpandMode();
31994
+
31995
+ // Re-add constraints if still in edit mode
31996
+ if (this.isEditMode) {
31997
+ this._setupEditModeConstraints();
31998
+ }
31999
+ if (this.canvas) {
32000
+ this.canvas.requestRenderAll();
32001
+ }
32002
+ }
32003
+
32004
+ /**
32005
+ * Toggle expand mode for content image
32006
+ */
32007
+ toggleContentExpandMode() {
32008
+ if (this._contentExpandMode) {
32009
+ this.exitContentExpandMode();
32010
+ } else {
32011
+ this.enterContentExpandMode();
32012
+ }
32013
+ }
32014
+
32015
+ /**
32016
+ * Check if content is in expand mode
32017
+ */
32018
+ isContentExpandMode() {
32019
+ return this._contentExpandMode;
32020
+ }
32021
+
32022
+ /**
32023
+ * Get expansion values from the content image
32024
+ */
32025
+ getContentExpansion() {
32026
+ if (!this._contentImage) return null;
32027
+ return this._contentImage.getExpansion();
32028
+ }
32029
+
32030
+ /**
32031
+ * Set expansion values on the content image
32032
+ */
32033
+ setContentExpansion(expansion) {
32034
+ if (!this._contentImage) return;
32035
+ this._contentImage.setExpansion(expansion);
32036
+ }
32037
+
32038
+ /**
32039
+ * Reset content expansion to zero
32040
+ */
32041
+ resetContentExpansion() {
32042
+ if (!this._contentImage) return;
32043
+ this._contentImage.resetExpansion();
32044
+ }
32045
+
32046
+ /**
32047
+ * Check if content has any expansion
32048
+ */
32049
+ hasContentExpansion() {
32050
+ if (!this._contentImage) return false;
32051
+ return this._contentImage.hasExpansion();
32052
+ }
31340
32053
 
31341
32054
  /**
31342
32055
  * Resizes the frame to new dimensions (Canva-like behavior)
@@ -32743,7 +33456,15 @@
32743
33456
 
32744
33457
  var index = /*#__PURE__*/Object.freeze({
32745
33458
  __proto__: null,
33459
+ EXPAND_HANDLE_FILL: EXPAND_HANDLE_FILL,
33460
+ EXPAND_HANDLE_STROKE: EXPAND_HANDLE_STROKE,
33461
+ EXPAND_PREVIEW_DASH: EXPAND_PREVIEW_DASH,
33462
+ EXPAND_PREVIEW_FILL: EXPAND_PREVIEW_FILL,
33463
+ EXPAND_PREVIEW_STROKE: EXPAND_PREVIEW_STROKE,
33464
+ ExpandControl: ExpandControl,
32746
33465
  changeWidth: changeWidth,
33466
+ createDefaultExpansion: createDefaultExpansion,
33467
+ createExpandControls: createExpandControls,
32747
33468
  createObjectDefaultControls: createObjectDefaultControls,
32748
33469
  createPathControls: createPathControls,
32749
33470
  createPolyActionHandler: createPolyActionHandler,
@@ -32752,7 +33473,12 @@
32752
33473
  createResizeControls: createResizeControls,
32753
33474
  createTextboxDefaultControls: createTextboxDefaultControls,
32754
33475
  dragHandler: dragHandler,
33476
+ expandBottomHandler: expandBottomHandler,
33477
+ expandLeftHandler: expandLeftHandler,
33478
+ expandRightHandler: expandRightHandler,
33479
+ expandTopHandler: expandTopHandler,
32755
33480
  factoryPolyActionHandler: factoryPolyActionHandler,
33481
+ getExpansion: getExpansion,
32756
33482
  getLocalPoint: getLocalPoint,
32757
33483
  polyActionHandler: polyActionHandler,
32758
33484
  renderCircleControl: renderCircleControl,
@@ -32767,6 +33493,7 @@
32767
33493
  scalingXOrSkewingY: scalingXOrSkewingY,
32768
33494
  scalingY: scalingY,
32769
33495
  scalingYOrSkewingX: scalingYOrSkewingX,
33496
+ setExpansion: setExpansion,
32770
33497
  skewCursorStyleHandler: skewCursorStyleHandler,
32771
33498
  skewHandlerX: skewHandlerX,
32772
33499
  skewHandlerY: skewHandlerY,