tldraw 5.4.0 → 5.5.0-canary.3870eea2f0fd

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/DOCS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tldraw Documentation
2
2
 
3
- Version: `5.4.0`
3
+ Version: `5.5.0-canary.3870eea2f0fd`
4
4
 
5
5
  This file is generated during package publishing from the tldraw docs content for this exact package version.
6
6
 
@@ -383,6 +383,7 @@ We ship an experimental `tldraw-migrate` agent skill that helps a coding agent u
383
383
 
384
384
  #### v5.x
385
385
 
386
+ - [v5.4](https://tldraw.dev/releases/v5.4.0) - A center align operation, a tleditors registry of mounted editors, faster signals and store layers, and a broad reliability pass across the SDK
386
387
  - [v5.3](https://tldraw.dev/releases/v5.3.0) - Commenting with anchored threads on the canvas, new collaboration packages, geo shape flipping, crop snapping, and faster viewport culling
387
388
  - [v5.2](https://tldraw.dev/releases/v5.2.0) - Faster freehand ink, a frame selection action, SDK-wide performance improvements, and breaking changes to the collaborator user-id types and Node support
388
389
  - [v5.1](https://tldraw.dev/releases/v5.1.0) - Page menu redesign, copy-styles shortcut, selectable locked shapes, public translation APIs, and performance improvements
@@ -20899,3 +20900,62 @@ While the tldraw SDK is [source available](https://github.com/tldraw/tldraw), it
20899
20900
  If you wish to include tldraw in an open source project, you may do so but the SDK itself must remain under its original license. This means that you and your downstream users will require their own trial, commercial, or hobby license in order to use the SDK in production. In this case, please also see our [trademark guidelines](https://tldraw.dev/legal/trademark-guidelines) that limit the use of the tldraw name and branding.
20900
20901
 
20901
20902
  The tldraw SDK is a product of a company. See [tldraw.dev](https://tldraw.dev/company) for more information about the company and the team.
20903
+
20904
+ ### Dependencies
20905
+
20906
+ {/* This page is generated by `yarn license-report --prod`. Do not edit it by hand. */}
20907
+
20908
+ The tldraw SDK itself is published under the [tldraw license](https://tldraw.dev/legal/tldraw-license). It also depends on the third-party packages listed below, each under its own license.
20909
+
20910
+ This covers the runtime dependencies that the `@tldraw/*` packages declare directly. It does not include their own transitive dependencies, our development and build tooling, or anything in the tldraw.com application. If you need a full transitive list for the exact versions you have installed, generate one from your own lockfile.
20911
+
20912
+ #### Dependencies
20913
+
20914
+ | Dependency | Version | License | Used by |
20915
+ | ------------------------------------------------------------------- | ------- | ------- | -------------------------------------------- |
20916
+ | [@tiptap/core](https://github.com/ueberdosis/tiptap) | 3.13.0 | MIT | `commenting`, `editor`, `mentions`, `tldraw` |
20917
+ | [@tiptap/extension-code](https://github.com/ueberdosis/tiptap) | 3.13.0 | MIT | `tldraw` |
20918
+ | [@tiptap/extension-highlight](https://github.com/ueberdosis/tiptap) | 3.13.0 | MIT | `tldraw` |
20919
+ | [@tiptap/extension-list](https://github.com/ueberdosis/tiptap) | 3.13.0 | MIT | `tldraw` |
20920
+ | [@tiptap/extension-mention](https://github.com/ueberdosis/tiptap) | 3.13.0 | MIT | `mentions` |
20921
+ | [@tiptap/pm](https://github.com/ueberdosis/tiptap) | 3.13.0 | MIT | `commenting`, `editor`, `mentions`, `tldraw` |
20922
+ | [@tiptap/react](https://github.com/ueberdosis/tiptap) | 3.13.0 | MIT | `commenting`, `editor`, `mentions`, `tldraw` |
20923
+ | [@tiptap/starter-kit](https://github.com/ueberdosis/tiptap) | 3.13.0 | MIT | `tldraw` |
20924
+ | [@tiptap/suggestion](https://github.com/ueberdosis/tiptap) | 3.13.0 | MIT | `mentions` |
20925
+ | [classnames](https://github.com/JedWatson/classnames) | 2.5.1 | MIT | `editor`, `tldraw` |
20926
+ | [eventemitter3](https://github.com/primus/eventemitter3) | 4.0.7 | MIT | `editor` |
20927
+ | [idb](https://github.com/jakearchibald/idb) | 7.1.1 | ISC | `editor`, `tldraw` |
20928
+ | [is-plain-object](https://github.com/jonschlinkert/is-plain-object) | 5.0.0 | MIT | `editor` |
20929
+ | [lodash.isequal](https://github.com/lodash/lodash) | 4.5.0 | MIT | `utils` |
20930
+ | [lodash.isequalwith](https://github.com/lodash/lodash) | 4.4.0 | MIT | `utils` |
20931
+ | [lodash.throttle](https://github.com/lodash/lodash) | 4.1.1 | MIT | `utils` |
20932
+ | [lodash.uniq](https://github.com/lodash/lodash) | 4.5.0 | MIT | `utils` |
20933
+ | [lz-string](https://github.com/pieroxy/lz-string) | 1.5.0 | MIT | `tldraw` |
20934
+ | [mermaid](https://github.com/mermaid-js/mermaid) | 11.16.1 | MIT | `mermaid` |
20935
+ | [nanoevents](https://github.com/ai/nanoevents) | 7.0.1 | MIT | `sync`, `sync-core` |
20936
+ | [radix-ui](https://github.com/radix-ui/primitives) | 1.4.2 | MIT | `tldraw` |
20937
+ | [rbush](https://github.com/mourner/rbush) | 3.0.1 | MIT | `editor` |
20938
+ | [ws](https://github.com/websockets/ws) | 8.21.0 | MIT | `sync`, `sync-core` |
20939
+
20940
+ #### Inline code
20941
+
20942
+ Some third-party code is included directly in our source rather than as a dependency. Each of these files carries its license in a comment at the top.
20943
+
20944
+ | File | License | Copyright |
20945
+ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
20946
+ | `packages/create-tldraw/src/group-select.ts` | [MIT](https://github.com/bombshell-dev/clack/blob/1adb270af5509ead3d22c4fa602b465f39c7a8c2/packages/prompts/LICENSE) | Nate Moore |
20947
+ | `packages/create-tldraw/src/wrap-ansi.ts` | [MIT](https://github.com/chalk/wrap-ansi/blob/163b878a6eb5d8c32b7bbea65036eeadc0e4def9/license) | Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com) |
20948
+ | `packages/editor/src/lib/hooks/useScreenBounds.ts` | [MIT](https://github.com/excalidraw/excalidraw/blob/master/LICENSE) | |
20949
+ | `packages/editor/src/lib/hooks/useViewportHeight.ts` | [BSD](https://github.com/outline/rich-markdown-editor/blob/main/LICENSE) | 2020 General Outline, Inc (https://www.getoutline.com/) and individual contributors. |
20950
+ | `packages/editor/src/lib/utils/browserCanvasMaxSize.ts` | [MIT](https://github.com/jhildenbiddle/canvas-size/blob/master/LICENSE) | John Hildenbiddle |
20951
+ | `packages/store/src/lib/ImmutableMap.ts` | [MIT](https://github.com/immutable-js/immutable-js/blob/main/LICENSE) | 2014-present, Lee Byron and other contributors. |
20952
+ | `packages/tldraw/src/lib/shapes/shared/PathBuilder.tsx` | [Apache](https://github.com/adobe-webplatform/Snap.svg/blob/master/LICENSE) | |
20953
+ | `packages/tldraw/src/lib/ui/hooks/useKeyboardShortcuts.ts` | [MIT](https://github.com/jaywcjlove/hotkeys-js/blob/master/LICENSE) | 2015-present, Kenny Wong |
20954
+ | `packages/tldraw/src/lib/ui/kbd-utils.ts` | [MIT](https://github.com/jaywcjlove/hotkeys-js/blob/master/LICENSE) | 2015-present, Kenny Wong |
20955
+ | `packages/tldraw/src/lib/utils/svg/sanitizeSvg.ts` | MIT | 2015 Mario Heiderich |
20956
+ | `packages/utils/src/lib/bind.ts` | [MIT](https://github.com/NoHomey/bind-decorator/blob/master/License) | 2016 Ivo Stratev |
20957
+ | `packages/utils/src/lib/id.ts` | [MIT](https://github.com/ai/nanoid/blob/main/LICENSE) | 2017 Andrey Sitnik <andrey@sitnik.ru> |
20958
+ | `packages/utils/src/lib/media/apng.ts` | [MIT](https://github.com/vHeemstra/is-apng/blob/main/license) | Philip van Heemstra |
20959
+ | `packages/utils/src/lib/media/gif.ts` | MIT | 2016 Józef Sokołowski <j.k.sokolowski@gmail.com> |
20960
+ | `packages/utils/src/lib/media/png.ts` | [MIT](https://github.com/alexgorbatchev/crc/blob/master/LICENSE) | 2014 Alex Gorbatchev |
20961
+ | `packages/utils/src/lib/media/webp.ts` | [MIT](https://github.com/sindresorhus/is-webp/blob/main/license) | Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com) |
package/RELEASE_NOTES.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # Releases
2
2
 
3
- Version: `5.4.0`
3
+ Version: `5.5.0-canary.3870eea2f0fd`
4
4
 
5
5
  This file is generated during package publishing from the tldraw release notes content for this exact package version.
6
6
 
7
7
  ## v5.4.0
8
8
 
9
+ [View on GitHub](https://github.com/tldraw/tldraw/releases/tag/v5.4.0)
10
+
9
11
  This release adds a `center` operation to `Editor.alignShapes`, a `tleditors` registry of mounted editors, `iterateGraphemes` to `@tldraw/utils`, and two new reactive methods on `AtomMap`. It also deprecates `useViewportHeight`, speeds up the signals and store layers, and lands a broad reliability pass across the SDK, with over a hundred fixes to undo history, selection and tool interactions, cropping, arrows, the signals and store layers, sync, and local persistence.
10
12
 
11
13
  ### API changes
@@ -32,6 +34,7 @@ This release adds a `center` operation to `Editor.alignShapes`, a `tleditors` re
32
34
  - Reduce per-pointer-event allocations in `Group2d` and `Editor` hit-testing. ([#8600](https://github.com/tldraw/tldraw/pull/8600))
33
35
  - Speed up signal reads in `@tldraw/state`. Capturing a parent used to scan the capturing signal's parent set twice, once to check for the parent and again to add it. ([#9690](https://github.com/tldraw/tldraw/pull/9690))
34
36
  - Reduce per-signal memory use and per-flush allocations in `@tldraw/state`. ([#10155](https://github.com/tldraw/tldraw/pull/10155))
37
+ - Speed up editor hot paths: set lookups replace array scans in the selection side effect and the erasing check, `getCollaborators` makes a single pass over the presence records, and `getShapeUtil` only builds its assert message when the lookup fails. ([#10358](https://github.com/tldraw/tldraw/pull/10358))
35
38
  - Reduce the work and allocations in `@tldraw/store` history bookkeeping, so squashing record diffs and filtering query history no longer build intermediate copies on every tick. ([#9689](https://github.com/tldraw/tldraw/pull/9689), [#9869](https://github.com/tldraw/tldraw/pull/9869))
36
39
  - Move the `exportBackground` default (`true`) into the `TLInstance` record type in `@tldraw/tlschema`, so every code path that creates the instance record gets the same value. ([#10534](https://github.com/tldraw/tldraw/pull/10534))
37
40
 
@@ -150,7 +153,8 @@ This release adds a `center` operation to `Editor.alignShapes`, a `tleditors` re
150
153
  - Fix the spacing around the cursor chat item in the context menu. ([#9921](https://github.com/tldraw/tldraw/pull/9921))
151
154
  - Fix the people menu's collaborator rows not lining up with your own row above them: the follow icon sat too close to the right edge, and collaborator names sat slightly left of your own. ([#9900](https://github.com/tldraw/tldraw/pull/9900))
152
155
  - Fix the comment thread popover briefly rendering with a doubled shadow the first time a thread is opened. ([#9886](https://github.com/tldraw/tldraw/pull/9886))
153
- #### Persistence, sync, and licensing
156
+
157
+ #### Persistence and sync
154
158
 
155
159
  - Fix edits made in the last 350 ms before closing or reloading a tab being lost from local persistence. ([#10526](https://github.com/tldraw/tldraw/pull/10526))
156
160
  - Fix background tabs repeatedly dropping and re-establishing their sync connection when the browser throttles timers. ([#9964](https://github.com/tldraw/tldraw/pull/9964))
package/dist-cjs/index.js CHANGED
@@ -653,7 +653,7 @@ var import_buildFromV1Document = require("./lib/utils/tldr/buildFromV1Document")
653
653
  var import_file = require("./lib/utils/tldr/file");
654
654
  (0, import_editor.registerTldrawLibraryVersion)(
655
655
  "tldraw",
656
- "5.4.0",
656
+ "5.5.0-canary.3870eea2f0fd",
657
657
  "cjs"
658
658
  );
659
659
  //# sourceMappingURL=index.js.map
@@ -325,11 +325,15 @@ function getCropBox(shape, info, opts = {}) {
325
325
  }
326
326
  };
327
327
  }
328
+ function getPositionKeepingCenter(imageShape, newW, newH) {
329
+ const delta = new import_editor.Vec((imageShape.props.w - newW) / 2, (imageShape.props.h - newH) / 2).rot(
330
+ imageShape.rotation
331
+ );
332
+ return { x: imageShape.x + delta.x, y: imageShape.y + delta.y };
333
+ }
328
334
  function calculateCropChange(imageShape, newCropWidth, newCropHeight, centerOnCurrentCrop = true, isCircle = false) {
329
335
  const { w, h } = getUncroppedSize(imageShape.props, imageShape.props.crop ?? getDefaultCrop());
330
336
  const currentCrop = imageShape.props.crop || getDefaultCrop();
331
- const imageCenterX = imageShape.x + imageShape.props.w / 2;
332
- const imageCenterY = imageShape.y + imageShape.props.h / 2;
333
337
  let cropCenterX, cropCenterY;
334
338
  if (centerOnCurrentCrop) {
335
339
  const { x, y } = getCropCenter(currentCrop);
@@ -352,8 +356,7 @@ function calculateCropChange(imageShape, newCropWidth, newCropHeight, centerOnCu
352
356
  crop: newCrop,
353
357
  w: croppedW,
354
358
  h: croppedH,
355
- x: imageCenterX - croppedW / 2,
356
- y: imageCenterY - croppedH / 2
359
+ ...getPositionKeepingCenter(imageShape, croppedW, croppedH)
357
360
  };
358
361
  }
359
362
  const MAX_ZOOM = 3;
@@ -375,10 +378,9 @@ function getCroppedImageDataWhenZooming(zoom, imageShape, maxZoom) {
375
378
  const scaleFactor = Math.min(MAX_ZOOM, oldWidth / newWidth);
376
379
  result.w *= scaleFactor;
377
380
  result.h *= scaleFactor;
378
- const imageCenterX = imageShape.x + imageShape.props.w / 2;
379
- const imageCenterY = imageShape.y + imageShape.props.h / 2;
380
- result.x = imageCenterX - result.w / 2;
381
- result.y = imageCenterY - result.h / 2;
381
+ const { x, y } = getPositionKeepingCenter(imageShape, result.w, result.h);
382
+ result.x = x;
383
+ result.y = y;
382
384
  return result;
383
385
  }
384
386
  function getCroppedImageDataForReplacedImage(imageShape, newImageWidth, newImageHeight) {
@@ -432,29 +434,21 @@ function getCroppedImageDataForReplacedImage(imageShape, newImageWidth, newImage
432
434
  currentCrop.isCircle
433
435
  );
434
436
  }
435
- const pageCenterX = imageShape.x + origDisplayW / 2;
436
- const pageCenterY = imageShape.y + origDisplayH / 2;
437
- const newX = pageCenterX - newDisplayW / 2;
438
- const newY = pageCenterY - newDisplayH / 2;
439
437
  return {
440
438
  crop,
441
439
  w: newDisplayW,
442
440
  h: newDisplayH,
443
- x: newX,
444
- y: newY
441
+ ...getPositionKeepingCenter(imageShape, newDisplayW, newDisplayH)
445
442
  };
446
443
  }
447
444
  function getCroppedImageDataForAspectRatio(aspectRatioOption, imageShape) {
448
445
  if (aspectRatioOption === "original") {
449
446
  const { w, h } = getUncroppedSize(imageShape.props, imageShape.props.crop ?? getDefaultCrop());
450
- const imageCenterX = imageShape.x + imageShape.props.w / 2;
451
- const imageCenterY = imageShape.y + imageShape.props.h / 2;
452
447
  return {
453
448
  crop: getDefaultCrop(),
454
449
  w,
455
450
  h,
456
- x: imageCenterX - w / 2,
457
- y: imageCenterY - h / 2
451
+ ...getPositionKeepingCenter(imageShape, w, h)
458
452
  };
459
453
  }
460
454
  const targetRatio = ASPECT_RATIO_TO_VALUE[aspectRatioOption];
@@ -525,16 +519,11 @@ function getCroppedImageDataForAspectRatio(aspectRatioOption, imageShape) {
525
519
  }
526
520
  const newW = baseW * currentScale;
527
521
  const newH = baseH * currentScale;
528
- const currentCenterXPage = imageShape.x + imageShape.props.w / 2;
529
- const currentCenterYPage = imageShape.y + imageShape.props.h / 2;
530
- const newX = currentCenterXPage - newW / 2;
531
- const newY = currentCenterYPage - newH / 2;
532
522
  return {
533
523
  crop: newCrop,
534
524
  w: newW,
535
525
  h: newH,
536
- x: newX,
537
- y: newY
526
+ ...getPositionKeepingCenter(imageShape, newW, newH)
538
527
  };
539
528
  }
540
529
  //# sourceMappingURL=crop.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/shapes/shared/crop.ts"],
4
- "sourcesContent": ["import {\n\tBox,\n\tShapeWithCrop,\n\tTLCropInfo,\n\tTLImageShape,\n\tTLShapeCrop,\n\tTLShapeId,\n\tVec,\n\tclamp,\n\tisEqual,\n} from '@tldraw/editor'\n\n/** @internal */\nexport const MIN_CROP_SIZE = 8\n\n/** @public */\nexport interface CropBoxOptions {\n\tminWidth?: number\n\tminHeight?: number\n}\n\n/** @public */\nexport function getDefaultCrop(): TLShapeCrop {\n\treturn {\n\t\ttopLeft: { x: 0, y: 0 },\n\t\tbottomRight: { x: 1, y: 1 },\n\t}\n}\n\n/** @public */\nexport type ASPECT_RATIO_OPTION =\n\t| 'original'\n\t| 'square'\n\t| 'circle'\n\t| 'landscape'\n\t| 'portrait'\n\t| 'wide'\n\n/** @public */\nexport const ASPECT_RATIO_OPTIONS: ASPECT_RATIO_OPTION[] = [\n\t'original',\n\t'square',\n\t'circle',\n\t'landscape',\n\t'portrait',\n\t'wide',\n]\n\n/** @public */\nexport const ASPECT_RATIO_TO_VALUE: Record<ASPECT_RATIO_OPTION, number> = {\n\toriginal: 0,\n\tsquare: 1,\n\tcircle: 1,\n\tlandscape: 4 / 3,\n\tportrait: 3 / 4,\n\twide: 16 / 9,\n}\n\n/**\n * Original (uncropped) width and height of shape.\n *\n * @public\n */\nexport function getUncroppedSize(\n\tshapeSize: { w: number; h: number },\n\tcrop: TLShapeCrop | null\n): { w: number; h: number } {\n\tif (!crop) return { w: shapeSize.w, h: shapeSize.h }\n\tconst w = shapeSize.w / (crop.bottomRight.x - crop.topLeft.x)\n\tconst h = shapeSize.h / (crop.bottomRight.y - crop.topLeft.y)\n\treturn { w, h }\n}\n\n// Utility function to get crop dimensions\nfunction getCropDimensions(crop: TLShapeCrop) {\n\treturn {\n\t\twidth: crop.bottomRight.x - crop.topLeft.x,\n\t\theight: crop.bottomRight.y - crop.topLeft.y,\n\t}\n}\n\n// Utility function to get crop center\nfunction getCropCenter(crop: TLShapeCrop) {\n\tconst { width, height } = getCropDimensions(crop)\n\treturn {\n\t\tx: crop.topLeft.x + width / 2,\n\t\ty: crop.topLeft.y + height / 2,\n\t}\n}\n\n// Utility function to create crop with specified dimensions centered on given point\nfunction createCropAroundCenter(\n\tcenterX: number,\n\tcenterY: number,\n\twidth: number,\n\theight: number,\n\tisCircle?: boolean\n) {\n\tconst topLeftX = Math.max(0, Math.min(1 - width, centerX - width / 2))\n\tconst topLeftY = Math.max(0, Math.min(1 - height, centerY - height / 2))\n\n\treturn {\n\t\ttopLeft: { x: topLeftX, y: topLeftY },\n\t\tbottomRight: { x: topLeftX + width, y: topLeftY + height },\n\t\tisCircle,\n\t}\n}\n\n/** @public */\nexport function getCropBox<T extends ShapeWithCrop>(\n\tshape: T,\n\tinfo: TLCropInfo<T>,\n\topts = {} as CropBoxOptions\n):\n\t| {\n\t\t\tid: TLShapeId\n\t\t\ttype: T['type']\n\t\t\tx: number\n\t\t\ty: number\n\t\t\tprops: ShapeWithCrop['props']\n\t }\n\t| undefined {\n\tconst { handle, change, crop, aspectRatioLocked, isResizingFromCenter } = info\n\tconst { w, h } = info.uncroppedSize\n\tconst { minWidth = MIN_CROP_SIZE, minHeight = MIN_CROP_SIZE } = opts\n\n\tif (w < minWidth || h < minHeight || (change.x === 0 && change.y === 0)) {\n\t\treturn\n\t}\n\n\t// Lets get a box here in pixel space. For simplicity, we'll do all the math in\n\t// pixel space, then convert to normalized space at the end.\n\tconst prevCropBox = new Box(\n\t\tcrop.topLeft.x * w,\n\t\tcrop.topLeft.y * h,\n\t\t(crop.bottomRight.x - crop.topLeft.x) * w,\n\t\t(crop.bottomRight.y - crop.topLeft.y) * h\n\t)\n\n\tconst targetRatio = prevCropBox.aspectRatio\n\tconst tempBox = prevCropBox.clone()\n\n\t// Which axes does the dragged handle move?\n\tconst movesLeft = handle === 'top_left' || handle === 'bottom_left' || handle === 'left'\n\tconst movesRight = handle === 'top_right' || handle === 'bottom_right' || handle === 'right'\n\tconst movesTop = handle === 'top_left' || handle === 'top_right' || handle === 'top'\n\tconst movesBottom = handle === 'bottom_left' || handle === 'bottom_right' || handle === 'bottom'\n\n\tif (isResizingFromCenter) {\n\t\t// Resizing from the center (alt/option): the crop center stays fixed and the\n\t\t// opposite edge mirrors the dragged edge, so the crop grows/shrinks symmetrically.\n\t\tconst cx = prevCropBox.midX\n\t\tconst cy = prevCropBox.midY\n\n\t\t// Desired half-extents from the dragged edge. Undragged axes keep their size.\n\t\tlet halfW = prevCropBox.w / 2\n\t\tlet halfH = prevCropBox.h / 2\n\t\tif (movesLeft) halfW = cx - (prevCropBox.x + change.x)\n\t\telse if (movesRight) halfW = prevCropBox.maxX + change.x - cx\n\t\tif (movesTop) halfH = cy - (prevCropBox.y + change.y)\n\t\telse if (movesBottom) halfH = prevCropBox.maxY + change.y - cy\n\n\t\tif (aspectRatioLocked) {\n\t\t\t// Keep the aspect ratio while staying centered. Pick the driving dimension,\n\t\t\t// derive the other from the ratio, then clamp both at once so the ratio holds.\n\t\t\tconst isCorner = (movesLeft || movesRight) && (movesTop || movesBottom)\n\t\t\tlet drivingHalfW: number\n\t\t\tif (isCorner) {\n\t\t\t\tdrivingHalfW = halfW / halfH > targetRatio ? halfW : halfH * targetRatio\n\t\t\t} else if (movesLeft || movesRight) {\n\t\t\t\tdrivingHalfW = halfW\n\t\t\t} else {\n\t\t\t\tdrivingHalfW = halfH * targetRatio\n\t\t\t}\n\n\t\t\tconst minHalfW = Math.max(minWidth / 2, (minHeight / 2) * targetRatio)\n\t\t\tconst maxHalfW = Math.min(cx, w - cx, cy * targetRatio, (h - cy) * targetRatio)\n\t\t\tif (maxHalfW < minHalfW) return\n\n\t\t\thalfW = clamp(drivingHalfW, minHalfW, maxHalfW)\n\t\t\thalfH = halfW / targetRatio\n\t\t} else {\n\t\t\t// Clamp each dragged axis independently against the image bounds and min size.\n\t\t\tif (movesLeft || movesRight) {\n\t\t\t\tconst maxHalfW = Math.min(cx, w - cx)\n\t\t\t\tif (maxHalfW < minWidth / 2) return\n\t\t\t\thalfW = clamp(halfW, minWidth / 2, maxHalfW)\n\t\t\t}\n\t\t\tif (movesTop || movesBottom) {\n\t\t\t\tconst maxHalfH = Math.min(cy, h - cy)\n\t\t\t\tif (maxHalfH < minHeight / 2) return\n\t\t\t\thalfH = clamp(halfH, minHeight / 2, maxHalfH)\n\t\t\t}\n\t\t}\n\n\t\ttempBox.x = cx - halfW\n\t\ttempBox.y = cy - halfH\n\t\ttempBox.w = halfW * 2\n\t\ttempBox.h = halfH * 2\n\t} else {\n\t\t// Basic resizing logic based on the handles\n\n\t\tif (movesLeft) {\n\t\t\ttempBox.x = clamp(tempBox.x + change.x, 0, prevCropBox.maxX - minWidth)\n\t\t\ttempBox.w = prevCropBox.maxX - tempBox.x\n\t\t} else if (movesRight) {\n\t\t\tconst tempRight = clamp(tempBox.maxX + change.x, prevCropBox.x + minWidth, w)\n\t\t\ttempBox.w = tempRight - tempBox.x\n\t\t}\n\n\t\tif (movesTop) {\n\t\t\ttempBox.y = clamp(tempBox.y + change.y, 0, prevCropBox.maxY - minHeight)\n\t\t\ttempBox.h = prevCropBox.maxY - tempBox.y\n\t\t} else if (movesBottom) {\n\t\t\tconst tempBottom = clamp(tempBox.maxY + change.y, prevCropBox.y + minHeight, h)\n\t\t\ttempBox.h = tempBottom - tempBox.y\n\t\t}\n\t}\n\n\t// Aspect ratio locked resizing logic\n\n\tif (aspectRatioLocked && !isResizingFromCenter) {\n\t\tconst isXLimiting = tempBox.aspectRatio > targetRatio\n\n\t\tif (isXLimiting) {\n\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t} else {\n\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t}\n\n\t\tswitch (handle) {\n\t\t\tcase 'top_left': {\n\t\t\t\t// preserve the bottom right corner\n\t\t\t\ttempBox.x = prevCropBox.maxX - tempBox.w\n\t\t\t\ttempBox.y = prevCropBox.maxY - tempBox.h\n\n\t\t\t\tif (tempBox.x <= 0) {\n\t\t\t\t\ttempBox.x = 0\n\t\t\t\t\ttempBox.w = prevCropBox.maxX - tempBox.x\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.y = prevCropBox.maxY - tempBox.h\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.y <= 0) {\n\t\t\t\t\ttempBox.y = 0\n\t\t\t\t\ttempBox.h = prevCropBox.maxY - tempBox.y\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.x = prevCropBox.maxX - tempBox.w\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'top_right': {\n\t\t\t\t// preserve the bottom left corner\n\t\t\t\ttempBox.x = prevCropBox.x\n\t\t\t\ttempBox.y = prevCropBox.maxY - tempBox.h\n\n\t\t\t\tif (tempBox.maxX >= w) {\n\t\t\t\t\ttempBox.w = w - prevCropBox.x\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.y = prevCropBox.maxY - tempBox.h\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.y <= 0) {\n\t\t\t\t\ttempBox.y = 0\n\t\t\t\t\ttempBox.h = prevCropBox.maxY - tempBox.y\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'bottom_left': {\n\t\t\t\t// preserve the top right corner\n\t\t\t\ttempBox.x = prevCropBox.maxX - tempBox.w\n\t\t\t\ttempBox.y = prevCropBox.y\n\n\t\t\t\tif (tempBox.x <= 0) {\n\t\t\t\t\ttempBox.x = 0\n\t\t\t\t\ttempBox.w = prevCropBox.maxX - tempBox.x\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxY >= h) {\n\t\t\t\t\ttempBox.h = h - prevCropBox.y\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.x = prevCropBox.maxX - tempBox.w\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'bottom_right': {\n\t\t\t\t// preserve the top left corner\n\t\t\t\ttempBox.x = prevCropBox.x\n\t\t\t\ttempBox.y = prevCropBox.y\n\n\t\t\t\tif (tempBox.maxX >= w) {\n\t\t\t\t\ttempBox.w = w - prevCropBox.x\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxY >= h) {\n\t\t\t\t\ttempBox.h = h - prevCropBox.y\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'top': {\n\t\t\t\t// preserve the bottom edge center\n\t\t\t\ttempBox.h = prevCropBox.maxY - tempBox.y\n\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\ttempBox.x -= (tempBox.w - prevCropBox.w) / 2\n\n\t\t\t\tif (tempBox.x <= 0) {\n\t\t\t\t\tconst leftSide = prevCropBox.midX\n\t\t\t\t\ttempBox.w = leftSide * 2\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.x = 0\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxX >= w) {\n\t\t\t\t\tconst rightSide = w - prevCropBox.midX\n\t\t\t\t\ttempBox.w = rightSide * 2\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.x = w - tempBox.w\n\t\t\t\t}\n\n\t\t\t\ttempBox.y = prevCropBox.maxY - tempBox.h\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'right': {\n\t\t\t\t// preserve the left edge center\n\t\t\t\ttempBox.w = tempBox.maxX - prevCropBox.x\n\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\ttempBox.y -= (tempBox.h - prevCropBox.h) / 2\n\n\t\t\t\tif (tempBox.y <= 0) {\n\t\t\t\t\tconst topSide = prevCropBox.midY\n\t\t\t\t\ttempBox.h = topSide * 2\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.y = 0\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxY >= h) {\n\t\t\t\t\tconst bottomSide = h - prevCropBox.midY\n\t\t\t\t\ttempBox.h = bottomSide * 2\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.y = h - tempBox.h\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'bottom': {\n\t\t\t\t// preserve the top edge center\n\t\t\t\ttempBox.h = tempBox.maxY - prevCropBox.y\n\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\ttempBox.x -= (tempBox.w - prevCropBox.w) / 2\n\n\t\t\t\tif (tempBox.x <= 0) {\n\t\t\t\t\tconst leftSide = prevCropBox.midX\n\t\t\t\t\ttempBox.w = leftSide * 2\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.x = 0\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxX >= w) {\n\t\t\t\t\tconst rightSide = w - prevCropBox.midX\n\t\t\t\t\ttempBox.w = rightSide * 2\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.x = w - tempBox.w\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'left': {\n\t\t\t\t// preserve the right edge center\n\t\t\t\ttempBox.w = prevCropBox.maxX - tempBox.x\n\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\ttempBox.y -= (tempBox.h - prevCropBox.h) / 2\n\n\t\t\t\tif (tempBox.y <= 0) {\n\t\t\t\t\tconst topSide = prevCropBox.midY\n\t\t\t\t\ttempBox.h = topSide * 2\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.y = 0\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxY >= h) {\n\t\t\t\t\tconst bottomSide = h - prevCropBox.midY\n\t\t\t\t\ttempBox.h = bottomSide * 2\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.y = h - tempBox.h\n\t\t\t\t}\n\n\t\t\t\ttempBox.x = prevCropBox.maxX - tempBox.w\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// Convert the box back to normalized space\n\tconst newCrop: TLShapeCrop = {\n\t\ttopLeft: { x: tempBox.x / w, y: tempBox.y / h },\n\t\tbottomRight: { x: tempBox.maxX / w, y: tempBox.maxY / h },\n\t}\n\tif (crop.isCircle != null) {\n\t\tnewCrop.isCircle = crop.isCircle\n\t}\n\n\t// If the crop hasn't changed, we can return early\n\tif (\n\t\tnewCrop.topLeft.x === crop.topLeft.x &&\n\t\tnewCrop.topLeft.y === crop.topLeft.y &&\n\t\tnewCrop.bottomRight.x === crop.bottomRight.x &&\n\t\tnewCrop.bottomRight.y === crop.bottomRight.y\n\t) {\n\t\treturn\n\t}\n\n\t// Adjust the shape's position to keep the crop's absolute coordinates correct\n\tconst newPoint = new Vec(tempBox.x - crop.topLeft.x * w, tempBox.y - crop.topLeft.y * h)\n\t\t.rot(shape.rotation)\n\t\t.add(shape)\n\n\treturn {\n\t\tid: shape.id,\n\t\ttype: shape.type,\n\t\tx: newPoint.x,\n\t\ty: newPoint.y,\n\t\tprops: {\n\t\t\t...shape.props,\n\t\t\tw: tempBox.w,\n\t\t\th: tempBox.h,\n\t\t\tcrop: newCrop,\n\t\t},\n\t}\n}\n\ninterface CropChange {\n\tcrop: {\n\t\ttopLeft: { x: number; y: number }\n\t\tbottomRight: { x: number; y: number }\n\t\tisCircle?: boolean\n\t}\n\tw: number\n\th: number\n\tx: number\n\ty: number\n}\n\n// Base function for calculating crop changes\nfunction calculateCropChange(\n\timageShape: TLImageShape,\n\tnewCropWidth: number,\n\tnewCropHeight: number,\n\tcenterOnCurrentCrop: boolean = true,\n\tisCircle: boolean = false\n): CropChange {\n\tconst { w, h } = getUncroppedSize(imageShape.props, imageShape.props.crop ?? getDefaultCrop())\n\tconst currentCrop = imageShape.props.crop || getDefaultCrop()\n\n\t// Calculate image and crop centers\n\tconst imageCenterX = imageShape.x + imageShape.props.w / 2\n\tconst imageCenterY = imageShape.y + imageShape.props.h / 2\n\n\tlet cropCenterX, cropCenterY\n\tif (centerOnCurrentCrop) {\n\t\tconst { x, y } = getCropCenter(currentCrop)\n\t\tcropCenterX = x\n\t\tcropCenterY = y\n\t} else {\n\t\tcropCenterX = 0.5\n\t\tcropCenterY = 0.5\n\t}\n\n\t// Create new crop\n\tconst newCrop = createCropAroundCenter(\n\t\tcropCenterX,\n\t\tcropCenterY,\n\t\tnewCropWidth,\n\t\tnewCropHeight,\n\t\tisCircle\n\t)\n\n\t// Calculate new dimensions\n\tconst croppedW = newCropWidth * w\n\tconst croppedH = newCropHeight * h\n\n\treturn {\n\t\tcrop: newCrop,\n\t\tw: croppedW,\n\t\th: croppedH,\n\t\tx: imageCenterX - croppedW / 2,\n\t\ty: imageCenterY - croppedH / 2,\n\t}\n}\n\n/** @internal */\nexport const MAX_ZOOM = 3\n\n/**\n * Calculate new crop dimensions and position when zooming\n */\nexport function getCroppedImageDataWhenZooming(\n\tzoom: number,\n\timageShape: TLImageShape,\n\tmaxZoom?: number\n): CropChange {\n\tconst oldCrop = imageShape.props.crop || getDefaultCrop()\n\tconst { width: oldWidth, height: oldHeight } = getCropDimensions(oldCrop)\n\tconst aspectRatio = oldWidth / oldHeight\n\n\t// Calculate new crop size with zoom scale\n\tconst derivedMaxZoom = maxZoom ? 1 / (1 - maxZoom) : MAX_ZOOM\n\tconst zoomScale = 1 + zoom * (derivedMaxZoom - 1)\n\tlet newWidth, newHeight\n\n\tif (aspectRatio > 1) {\n\t\tnewWidth = Math.min(1, 1 / zoomScale)\n\t\tnewHeight = newWidth / aspectRatio\n\t} else {\n\t\tnewHeight = Math.min(1, 1 / zoomScale)\n\t\tnewWidth = newHeight * aspectRatio\n\t}\n\n\t// Calculate result with base function\n\tconst result = calculateCropChange(imageShape, newWidth, newHeight, true, oldCrop.isCircle)\n\n\t// Apply zoom factor to display dimensions\n\tconst scaleFactor = Math.min(MAX_ZOOM, oldWidth / newWidth)\n\tresult.w *= scaleFactor\n\tresult.h *= scaleFactor\n\n\t// Recenter\n\tconst imageCenterX = imageShape.x + imageShape.props.w / 2\n\tconst imageCenterY = imageShape.y + imageShape.props.h / 2\n\tresult.x = imageCenterX - result.w / 2\n\tresult.y = imageCenterY - result.h / 2\n\n\treturn result\n}\n\n/**\n * Calculate new crop dimensions and position when replacing an image\n */\nexport function getCroppedImageDataForReplacedImage(\n\timageShape: TLImageShape,\n\tnewImageWidth: number,\n\tnewImageHeight: number\n): CropChange {\n\tconst defaultCrop = getDefaultCrop()\n\tconst currentCrop = imageShape.props.crop || defaultCrop\n\tconst origDisplayW = imageShape.props.w\n\tconst origDisplayH = imageShape.props.h\n\tconst newImageAspectRatio = newImageWidth / newImageHeight\n\n\tlet crop = defaultCrop\n\tlet newDisplayW = origDisplayW\n\tlet newDisplayH = origDisplayH\n\t// Compare the bounds, not the whole object: an explicit `isCircle: false` must still count\n\t// as the original crop.\n\tconst isOriginalCrop =\n\t\t!!imageShape.props.crop &&\n\t\tisEqual(imageShape.props.crop.topLeft, defaultCrop.topLeft) &&\n\t\tisEqual(imageShape.props.crop.bottomRight, defaultCrop.bottomRight) &&\n\t\t!imageShape.props.crop.isCircle\n\n\tif (isOriginalCrop) {\n\t\tnewDisplayW = origDisplayW\n\t\tnewDisplayH = (origDisplayW * newImageHeight) / newImageWidth\n\t} else {\n\t\tconst { w: uncroppedW, h: uncroppedH } = getUncroppedSize(\n\t\t\timageShape.props,\n\t\t\timageShape.props.crop || getDefaultCrop() // Use the ACTUAL current crop to correctly infer uncropped size\n\t\t)\n\t\tconst { width: cropW, height: cropH } = getCropDimensions(currentCrop)\n\t\tconst targetRatio = cropW / cropH\n\t\tconst oldImageAspectRatio = uncroppedW / uncroppedH\n\t\tlet newRelativeWidth: number\n\t\tlet newRelativeHeight: number\n\n\t\tconst currentCropCenter = getCropCenter(currentCrop)\n\n\t\t// Adjust the new crop dimensions to match the current crop zoom\n\t\tnewRelativeWidth = cropW\n\t\tconst ratioConversion = newImageAspectRatio / oldImageAspectRatio / targetRatio\n\t\tnewRelativeHeight = newRelativeWidth * ratioConversion\n\n\t\t// Check that our new crop dimensions are within the MAX_ZOOM bounds\n\t\tconst maxRatioConversion = MAX_ZOOM / (MAX_ZOOM - 1)\n\t\tif (ratioConversion > maxRatioConversion) {\n\t\t\tconst minDimension = 1 / MAX_ZOOM\n\t\t\tif (1 / newRelativeHeight < 1 / newRelativeWidth) {\n\t\t\t\tconst scale = newRelativeHeight / minDimension\n\t\t\t\tnewRelativeHeight = newRelativeHeight / scale\n\t\t\t\tnewRelativeWidth = newRelativeWidth / scale\n\t\t\t} else {\n\t\t\t\tconst scale = newRelativeWidth / minDimension\n\t\t\t\tnewRelativeWidth = newRelativeWidth / scale\n\t\t\t\tnewRelativeHeight = newRelativeHeight / scale\n\t\t\t}\n\t\t}\n\n\t\t// Ensure dimensions are within [0, 1] bounds after adjustment\n\t\tnewRelativeWidth = Math.max(0, Math.min(1, newRelativeWidth))\n\t\tnewRelativeHeight = Math.max(0, Math.min(1, newRelativeHeight))\n\n\t\t// Create the new crop object, centered around the CURRENT crop's center\n\t\tcrop = createCropAroundCenter(\n\t\t\tcurrentCropCenter.x,\n\t\t\tcurrentCropCenter.y,\n\t\t\tnewRelativeWidth,\n\t\t\tnewRelativeHeight,\n\t\t\tcurrentCrop.isCircle\n\t\t)\n\t}\n\n\t// Position so visual center stays put\n\tconst pageCenterX = imageShape.x + origDisplayW / 2\n\tconst pageCenterY = imageShape.y + origDisplayH / 2\n\n\tconst newX = pageCenterX - newDisplayW / 2\n\tconst newY = pageCenterY - newDisplayH / 2\n\n\treturn {\n\t\tcrop,\n\t\tw: newDisplayW,\n\t\th: newDisplayH,\n\t\tx: newX,\n\t\ty: newY,\n\t}\n}\n\n/**\n * Calculate new crop dimensions and position when changing aspect ratio\n */\nexport function getCroppedImageDataForAspectRatio(\n\taspectRatioOption: ASPECT_RATIO_OPTION,\n\timageShape: TLImageShape\n): CropChange {\n\t// If original aspect ratio is requested, use default crop\n\tif (aspectRatioOption === 'original') {\n\t\tconst { w, h } = getUncroppedSize(imageShape.props, imageShape.props.crop ?? getDefaultCrop())\n\t\tconst imageCenterX = imageShape.x + imageShape.props.w / 2\n\t\tconst imageCenterY = imageShape.y + imageShape.props.h / 2\n\n\t\treturn {\n\t\t\tcrop: getDefaultCrop(),\n\t\t\tw,\n\t\t\th,\n\t\t\tx: imageCenterX - w / 2,\n\t\t\ty: imageCenterY - h / 2,\n\t\t}\n\t}\n\n\t// Get target ratio and uncropped image properties\n\tconst targetRatio = ASPECT_RATIO_TO_VALUE[aspectRatioOption] // Assume valid option\n\tconst isCircle = aspectRatioOption === 'circle'\n\t// Use default crop to get uncropped size relative to the *original* image bounds\n\tconst { w: uncroppedW, h: uncroppedH } = getUncroppedSize(\n\t\timageShape.props,\n\t\timageShape.props.crop || getDefaultCrop() // Use the ACTUAL current crop to correctly infer uncropped size\n\t)\n\t// Calculate the original image aspect ratio\n\tconst imageAspectRatio = uncroppedW / uncroppedH\n\n\t// Get the current crop and its relative dimensions\n\tconst currentCrop = imageShape.props.crop || getDefaultCrop()\n\tconst { width: cropW, height: cropH } = getCropDimensions(currentCrop)\n\tconst currentCropCenter = getCropCenter(currentCrop)\n\n\t// Calculate the current crop zoom level\n\tconst currentCropZoom = Math.min(1 / cropW, 1 / cropH)\n\n\t// Calculate the relative width and height of the crop rectangle (0-1 scale)\n\t// Try to preserve the longest dimension of the current crop when changing aspect ratios\n\tlet newRelativeWidth: number\n\tlet newRelativeHeight: number\n\n\tif (imageAspectRatio === 0 || !Number.isFinite(imageAspectRatio) || targetRatio === 0) {\n\t\t// Avoid division by zero or NaN issues if image dimensions are invalid or target ratio is 0\n\t\tnewRelativeWidth = 1\n\t\tnewRelativeHeight = 1\n\t} else {\n\t\t// Get current crop dimensions in absolute units\n\t\tconst currentAbsoluteWidth = cropW * uncroppedW\n\t\tconst currentAbsoluteHeight = cropH * uncroppedH\n\n\t\t// Find the longest current dimension to preserve\n\t\tconst longestCurrentDimension = Math.max(currentAbsoluteWidth, currentAbsoluteHeight)\n\t\tconst isWidthLongest = currentAbsoluteWidth >= currentAbsoluteHeight\n\n\t\t// Calculate new dimensions preserving the longest dimension\n\t\tlet newAbsoluteWidth: number\n\t\tlet newAbsoluteHeight: number\n\n\t\tif (isWidthLongest) {\n\t\t\t// Preserve width, calculate height based on target ratio\n\t\t\tnewAbsoluteWidth = longestCurrentDimension\n\t\t\tnewAbsoluteHeight = newAbsoluteWidth / targetRatio\n\t\t} else {\n\t\t\t// Preserve height, calculate width based on target ratio\n\t\t\tnewAbsoluteHeight = longestCurrentDimension\n\t\t\tnewAbsoluteWidth = newAbsoluteHeight * targetRatio\n\t\t}\n\n\t\t// Convert back to relative coordinates\n\t\tnewRelativeWidth = newAbsoluteWidth / uncroppedW\n\t\tnewRelativeHeight = newAbsoluteHeight / uncroppedH\n\n\t\t// Clamp to image bounds and adjust if necessary\n\t\tif (newRelativeWidth > 1) {\n\t\t\t// Width exceeds bounds, clamp and recalculate height\n\t\t\tnewRelativeWidth = 1\n\t\t\tnewRelativeHeight = imageAspectRatio / targetRatio\n\t\t}\n\t\tif (newRelativeHeight > 1) {\n\t\t\t// Height exceeds bounds, clamp and recalculate width\n\t\t\tnewRelativeHeight = 1\n\t\t\tnewRelativeWidth = targetRatio / imageAspectRatio\n\t\t}\n\n\t\t// Final clamp to ensure we stay within bounds\n\t\tnewRelativeWidth = Math.max(0, Math.min(1, newRelativeWidth))\n\t\tnewRelativeHeight = Math.max(0, Math.min(1, newRelativeHeight))\n\t}\n\n\tconst newCropZoom = Math.min(1 / newRelativeWidth, 1 / newRelativeHeight)\n\t// Adjust the new crop dimensions to match the current crop zoom\n\tnewRelativeWidth *= newCropZoom / currentCropZoom\n\tnewRelativeHeight *= newCropZoom / currentCropZoom\n\n\t// Ensure dimensions are within [0, 1] bounds after adjustment\n\tnewRelativeWidth = Math.max(0, Math.min(1, newRelativeWidth))\n\tnewRelativeHeight = Math.max(0, Math.min(1, newRelativeHeight))\n\n\t// Create the new crop object, centered around the CURRENT crop's center\n\tconst newCrop = createCropAroundCenter(\n\t\tcurrentCropCenter.x,\n\t\tcurrentCropCenter.y,\n\t\tnewRelativeWidth,\n\t\tnewRelativeHeight,\n\t\tisCircle\n\t)\n\n\t// Get the actual relative dimensions from the new crop (after potential clamping)\n\tconst finalRelativeWidth = newCrop.bottomRight.x - newCrop.topLeft.x\n\tconst finalRelativeHeight = newCrop.bottomRight.y - newCrop.topLeft.y\n\n\t// Calculate the base dimensions (as if applying the new crop to the uncropped image at scale 1)\n\tconst baseW = finalRelativeWidth * uncroppedW\n\tconst baseH = finalRelativeHeight * uncroppedH\n\n\t// Determine the current effective scale of the shape\n\t// This preserves the visual size when the crop changes\n\tlet currentScale = 1.0\n\tif (cropW > 0) {\n\t\tcurrentScale = imageShape.props.w / (cropW * uncroppedW)\n\t} else if (cropH > 0) {\n\t\t// Fallback to height if width relative dimension is zero\n\t\tcurrentScale = imageShape.props.h / (cropH * uncroppedH)\n\t}\n\n\t// Apply the current scale to the base dimensions to get the final dimensions\n\tconst newW = baseW * currentScale\n\tconst newH = baseH * currentScale\n\n\t// Calculate the new top-left position (x, y) for the shape\n\t// to keep the visual center of the cropped area fixed on the page.\n\tconst currentCenterXPage = imageShape.x + imageShape.props.w / 2\n\tconst currentCenterYPage = imageShape.y + imageShape.props.h / 2\n\tconst newX = currentCenterXPage - newW / 2\n\tconst newY = currentCenterYPage - newH / 2\n\n\treturn {\n\t\tcrop: newCrop,\n\t\tw: newW,\n\t\th: newH,\n\t\tx: newX,\n\t\ty: newY,\n\t}\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n\tBox,\n\tShapeWithCrop,\n\tTLCropInfo,\n\tTLImageShape,\n\tTLShapeCrop,\n\tTLShapeId,\n\tVec,\n\tclamp,\n\tisEqual,\n} from '@tldraw/editor'\n\n/** @internal */\nexport const MIN_CROP_SIZE = 8\n\n/** @public */\nexport interface CropBoxOptions {\n\tminWidth?: number\n\tminHeight?: number\n}\n\n/** @public */\nexport function getDefaultCrop(): TLShapeCrop {\n\treturn {\n\t\ttopLeft: { x: 0, y: 0 },\n\t\tbottomRight: { x: 1, y: 1 },\n\t}\n}\n\n/** @public */\nexport type ASPECT_RATIO_OPTION =\n\t| 'original'\n\t| 'square'\n\t| 'circle'\n\t| 'landscape'\n\t| 'portrait'\n\t| 'wide'\n\n/** @public */\nexport const ASPECT_RATIO_OPTIONS: ASPECT_RATIO_OPTION[] = [\n\t'original',\n\t'square',\n\t'circle',\n\t'landscape',\n\t'portrait',\n\t'wide',\n]\n\n/** @public */\nexport const ASPECT_RATIO_TO_VALUE: Record<ASPECT_RATIO_OPTION, number> = {\n\toriginal: 0,\n\tsquare: 1,\n\tcircle: 1,\n\tlandscape: 4 / 3,\n\tportrait: 3 / 4,\n\twide: 16 / 9,\n}\n\n/**\n * Original (uncropped) width and height of shape.\n *\n * @public\n */\nexport function getUncroppedSize(\n\tshapeSize: { w: number; h: number },\n\tcrop: TLShapeCrop | null\n): { w: number; h: number } {\n\tif (!crop) return { w: shapeSize.w, h: shapeSize.h }\n\tconst w = shapeSize.w / (crop.bottomRight.x - crop.topLeft.x)\n\tconst h = shapeSize.h / (crop.bottomRight.y - crop.topLeft.y)\n\treturn { w, h }\n}\n\n// Utility function to get crop dimensions\nfunction getCropDimensions(crop: TLShapeCrop) {\n\treturn {\n\t\twidth: crop.bottomRight.x - crop.topLeft.x,\n\t\theight: crop.bottomRight.y - crop.topLeft.y,\n\t}\n}\n\n// Utility function to get crop center\nfunction getCropCenter(crop: TLShapeCrop) {\n\tconst { width, height } = getCropDimensions(crop)\n\treturn {\n\t\tx: crop.topLeft.x + width / 2,\n\t\ty: crop.topLeft.y + height / 2,\n\t}\n}\n\n// Utility function to create crop with specified dimensions centered on given point\nfunction createCropAroundCenter(\n\tcenterX: number,\n\tcenterY: number,\n\twidth: number,\n\theight: number,\n\tisCircle?: boolean\n) {\n\tconst topLeftX = Math.max(0, Math.min(1 - width, centerX - width / 2))\n\tconst topLeftY = Math.max(0, Math.min(1 - height, centerY - height / 2))\n\n\treturn {\n\t\ttopLeft: { x: topLeftX, y: topLeftY },\n\t\tbottomRight: { x: topLeftX + width, y: topLeftY + height },\n\t\tisCircle,\n\t}\n}\n\n/** @public */\nexport function getCropBox<T extends ShapeWithCrop>(\n\tshape: T,\n\tinfo: TLCropInfo<T>,\n\topts = {} as CropBoxOptions\n):\n\t| {\n\t\t\tid: TLShapeId\n\t\t\ttype: T['type']\n\t\t\tx: number\n\t\t\ty: number\n\t\t\tprops: ShapeWithCrop['props']\n\t }\n\t| undefined {\n\tconst { handle, change, crop, aspectRatioLocked, isResizingFromCenter } = info\n\tconst { w, h } = info.uncroppedSize\n\tconst { minWidth = MIN_CROP_SIZE, minHeight = MIN_CROP_SIZE } = opts\n\n\tif (w < minWidth || h < minHeight || (change.x === 0 && change.y === 0)) {\n\t\treturn\n\t}\n\n\t// Lets get a box here in pixel space. For simplicity, we'll do all the math in\n\t// pixel space, then convert to normalized space at the end.\n\tconst prevCropBox = new Box(\n\t\tcrop.topLeft.x * w,\n\t\tcrop.topLeft.y * h,\n\t\t(crop.bottomRight.x - crop.topLeft.x) * w,\n\t\t(crop.bottomRight.y - crop.topLeft.y) * h\n\t)\n\n\tconst targetRatio = prevCropBox.aspectRatio\n\tconst tempBox = prevCropBox.clone()\n\n\t// Which axes does the dragged handle move?\n\tconst movesLeft = handle === 'top_left' || handle === 'bottom_left' || handle === 'left'\n\tconst movesRight = handle === 'top_right' || handle === 'bottom_right' || handle === 'right'\n\tconst movesTop = handle === 'top_left' || handle === 'top_right' || handle === 'top'\n\tconst movesBottom = handle === 'bottom_left' || handle === 'bottom_right' || handle === 'bottom'\n\n\tif (isResizingFromCenter) {\n\t\t// Resizing from the center (alt/option): the crop center stays fixed and the\n\t\t// opposite edge mirrors the dragged edge, so the crop grows/shrinks symmetrically.\n\t\tconst cx = prevCropBox.midX\n\t\tconst cy = prevCropBox.midY\n\n\t\t// Desired half-extents from the dragged edge. Undragged axes keep their size.\n\t\tlet halfW = prevCropBox.w / 2\n\t\tlet halfH = prevCropBox.h / 2\n\t\tif (movesLeft) halfW = cx - (prevCropBox.x + change.x)\n\t\telse if (movesRight) halfW = prevCropBox.maxX + change.x - cx\n\t\tif (movesTop) halfH = cy - (prevCropBox.y + change.y)\n\t\telse if (movesBottom) halfH = prevCropBox.maxY + change.y - cy\n\n\t\tif (aspectRatioLocked) {\n\t\t\t// Keep the aspect ratio while staying centered. Pick the driving dimension,\n\t\t\t// derive the other from the ratio, then clamp both at once so the ratio holds.\n\t\t\tconst isCorner = (movesLeft || movesRight) && (movesTop || movesBottom)\n\t\t\tlet drivingHalfW: number\n\t\t\tif (isCorner) {\n\t\t\t\tdrivingHalfW = halfW / halfH > targetRatio ? halfW : halfH * targetRatio\n\t\t\t} else if (movesLeft || movesRight) {\n\t\t\t\tdrivingHalfW = halfW\n\t\t\t} else {\n\t\t\t\tdrivingHalfW = halfH * targetRatio\n\t\t\t}\n\n\t\t\tconst minHalfW = Math.max(minWidth / 2, (minHeight / 2) * targetRatio)\n\t\t\tconst maxHalfW = Math.min(cx, w - cx, cy * targetRatio, (h - cy) * targetRatio)\n\t\t\tif (maxHalfW < minHalfW) return\n\n\t\t\thalfW = clamp(drivingHalfW, minHalfW, maxHalfW)\n\t\t\thalfH = halfW / targetRatio\n\t\t} else {\n\t\t\t// Clamp each dragged axis independently against the image bounds and min size.\n\t\t\tif (movesLeft || movesRight) {\n\t\t\t\tconst maxHalfW = Math.min(cx, w - cx)\n\t\t\t\tif (maxHalfW < minWidth / 2) return\n\t\t\t\thalfW = clamp(halfW, minWidth / 2, maxHalfW)\n\t\t\t}\n\t\t\tif (movesTop || movesBottom) {\n\t\t\t\tconst maxHalfH = Math.min(cy, h - cy)\n\t\t\t\tif (maxHalfH < minHeight / 2) return\n\t\t\t\thalfH = clamp(halfH, minHeight / 2, maxHalfH)\n\t\t\t}\n\t\t}\n\n\t\ttempBox.x = cx - halfW\n\t\ttempBox.y = cy - halfH\n\t\ttempBox.w = halfW * 2\n\t\ttempBox.h = halfH * 2\n\t} else {\n\t\t// Basic resizing logic based on the handles\n\n\t\tif (movesLeft) {\n\t\t\ttempBox.x = clamp(tempBox.x + change.x, 0, prevCropBox.maxX - minWidth)\n\t\t\ttempBox.w = prevCropBox.maxX - tempBox.x\n\t\t} else if (movesRight) {\n\t\t\tconst tempRight = clamp(tempBox.maxX + change.x, prevCropBox.x + minWidth, w)\n\t\t\ttempBox.w = tempRight - tempBox.x\n\t\t}\n\n\t\tif (movesTop) {\n\t\t\ttempBox.y = clamp(tempBox.y + change.y, 0, prevCropBox.maxY - minHeight)\n\t\t\ttempBox.h = prevCropBox.maxY - tempBox.y\n\t\t} else if (movesBottom) {\n\t\t\tconst tempBottom = clamp(tempBox.maxY + change.y, prevCropBox.y + minHeight, h)\n\t\t\ttempBox.h = tempBottom - tempBox.y\n\t\t}\n\t}\n\n\t// Aspect ratio locked resizing logic\n\n\tif (aspectRatioLocked && !isResizingFromCenter) {\n\t\tconst isXLimiting = tempBox.aspectRatio > targetRatio\n\n\t\tif (isXLimiting) {\n\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t} else {\n\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t}\n\n\t\tswitch (handle) {\n\t\t\tcase 'top_left': {\n\t\t\t\t// preserve the bottom right corner\n\t\t\t\ttempBox.x = prevCropBox.maxX - tempBox.w\n\t\t\t\ttempBox.y = prevCropBox.maxY - tempBox.h\n\n\t\t\t\tif (tempBox.x <= 0) {\n\t\t\t\t\ttempBox.x = 0\n\t\t\t\t\ttempBox.w = prevCropBox.maxX - tempBox.x\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.y = prevCropBox.maxY - tempBox.h\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.y <= 0) {\n\t\t\t\t\ttempBox.y = 0\n\t\t\t\t\ttempBox.h = prevCropBox.maxY - tempBox.y\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.x = prevCropBox.maxX - tempBox.w\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'top_right': {\n\t\t\t\t// preserve the bottom left corner\n\t\t\t\ttempBox.x = prevCropBox.x\n\t\t\t\ttempBox.y = prevCropBox.maxY - tempBox.h\n\n\t\t\t\tif (tempBox.maxX >= w) {\n\t\t\t\t\ttempBox.w = w - prevCropBox.x\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.y = prevCropBox.maxY - tempBox.h\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.y <= 0) {\n\t\t\t\t\ttempBox.y = 0\n\t\t\t\t\ttempBox.h = prevCropBox.maxY - tempBox.y\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'bottom_left': {\n\t\t\t\t// preserve the top right corner\n\t\t\t\ttempBox.x = prevCropBox.maxX - tempBox.w\n\t\t\t\ttempBox.y = prevCropBox.y\n\n\t\t\t\tif (tempBox.x <= 0) {\n\t\t\t\t\ttempBox.x = 0\n\t\t\t\t\ttempBox.w = prevCropBox.maxX - tempBox.x\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxY >= h) {\n\t\t\t\t\ttempBox.h = h - prevCropBox.y\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.x = prevCropBox.maxX - tempBox.w\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'bottom_right': {\n\t\t\t\t// preserve the top left corner\n\t\t\t\ttempBox.x = prevCropBox.x\n\t\t\t\ttempBox.y = prevCropBox.y\n\n\t\t\t\tif (tempBox.maxX >= w) {\n\t\t\t\t\ttempBox.w = w - prevCropBox.x\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxY >= h) {\n\t\t\t\t\ttempBox.h = h - prevCropBox.y\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'top': {\n\t\t\t\t// preserve the bottom edge center\n\t\t\t\ttempBox.h = prevCropBox.maxY - tempBox.y\n\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\ttempBox.x -= (tempBox.w - prevCropBox.w) / 2\n\n\t\t\t\tif (tempBox.x <= 0) {\n\t\t\t\t\tconst leftSide = prevCropBox.midX\n\t\t\t\t\ttempBox.w = leftSide * 2\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.x = 0\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxX >= w) {\n\t\t\t\t\tconst rightSide = w - prevCropBox.midX\n\t\t\t\t\ttempBox.w = rightSide * 2\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.x = w - tempBox.w\n\t\t\t\t}\n\n\t\t\t\ttempBox.y = prevCropBox.maxY - tempBox.h\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'right': {\n\t\t\t\t// preserve the left edge center\n\t\t\t\ttempBox.w = tempBox.maxX - prevCropBox.x\n\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\ttempBox.y -= (tempBox.h - prevCropBox.h) / 2\n\n\t\t\t\tif (tempBox.y <= 0) {\n\t\t\t\t\tconst topSide = prevCropBox.midY\n\t\t\t\t\ttempBox.h = topSide * 2\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.y = 0\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxY >= h) {\n\t\t\t\t\tconst bottomSide = h - prevCropBox.midY\n\t\t\t\t\ttempBox.h = bottomSide * 2\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.y = h - tempBox.h\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'bottom': {\n\t\t\t\t// preserve the top edge center\n\t\t\t\ttempBox.h = tempBox.maxY - prevCropBox.y\n\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\ttempBox.x -= (tempBox.w - prevCropBox.w) / 2\n\n\t\t\t\tif (tempBox.x <= 0) {\n\t\t\t\t\tconst leftSide = prevCropBox.midX\n\t\t\t\t\ttempBox.w = leftSide * 2\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.x = 0\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxX >= w) {\n\t\t\t\t\tconst rightSide = w - prevCropBox.midX\n\t\t\t\t\ttempBox.w = rightSide * 2\n\t\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\t\ttempBox.x = w - tempBox.w\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'left': {\n\t\t\t\t// preserve the right edge center\n\t\t\t\ttempBox.w = prevCropBox.maxX - tempBox.x\n\t\t\t\ttempBox.h = tempBox.w / targetRatio\n\t\t\t\ttempBox.y -= (tempBox.h - prevCropBox.h) / 2\n\n\t\t\t\tif (tempBox.y <= 0) {\n\t\t\t\t\tconst topSide = prevCropBox.midY\n\t\t\t\t\ttempBox.h = topSide * 2\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.y = 0\n\t\t\t\t}\n\n\t\t\t\tif (tempBox.maxY >= h) {\n\t\t\t\t\tconst bottomSide = h - prevCropBox.midY\n\t\t\t\t\ttempBox.h = bottomSide * 2\n\t\t\t\t\ttempBox.w = tempBox.h * targetRatio\n\t\t\t\t\ttempBox.y = h - tempBox.h\n\t\t\t\t}\n\n\t\t\t\ttempBox.x = prevCropBox.maxX - tempBox.w\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// Convert the box back to normalized space\n\tconst newCrop: TLShapeCrop = {\n\t\ttopLeft: { x: tempBox.x / w, y: tempBox.y / h },\n\t\tbottomRight: { x: tempBox.maxX / w, y: tempBox.maxY / h },\n\t}\n\tif (crop.isCircle != null) {\n\t\tnewCrop.isCircle = crop.isCircle\n\t}\n\n\t// If the crop hasn't changed, we can return early\n\tif (\n\t\tnewCrop.topLeft.x === crop.topLeft.x &&\n\t\tnewCrop.topLeft.y === crop.topLeft.y &&\n\t\tnewCrop.bottomRight.x === crop.bottomRight.x &&\n\t\tnewCrop.bottomRight.y === crop.bottomRight.y\n\t) {\n\t\treturn\n\t}\n\n\t// Adjust the shape's position to keep the crop's absolute coordinates correct\n\tconst newPoint = new Vec(tempBox.x - crop.topLeft.x * w, tempBox.y - crop.topLeft.y * h)\n\t\t.rot(shape.rotation)\n\t\t.add(shape)\n\n\treturn {\n\t\tid: shape.id,\n\t\ttype: shape.type,\n\t\tx: newPoint.x,\n\t\ty: newPoint.y,\n\t\tprops: {\n\t\t\t...shape.props,\n\t\t\tw: tempBox.w,\n\t\t\th: tempBox.h,\n\t\t\tcrop: newCrop,\n\t\t},\n\t}\n}\n\ninterface CropChange {\n\tcrop: {\n\t\ttopLeft: { x: number; y: number }\n\t\tbottomRight: { x: number; y: number }\n\t\tisCircle?: boolean\n\t}\n\tw: number\n\th: number\n\tx: number\n\ty: number\n}\n\n/**\n * Top-left position that keeps the image's visual centre where it is after its display size\n * changes. The half-size delta is in the shape's own space, so it has to be rotated by the\n * shape's rotation; a plain `center - newSize / 2` only holds for unrotated images.\n */\nfunction getPositionKeepingCenter(imageShape: TLImageShape, newW: number, newH: number) {\n\tconst delta = new Vec((imageShape.props.w - newW) / 2, (imageShape.props.h - newH) / 2).rot(\n\t\timageShape.rotation\n\t)\n\treturn { x: imageShape.x + delta.x, y: imageShape.y + delta.y }\n}\n\nfunction calculateCropChange(\n\timageShape: TLImageShape,\n\tnewCropWidth: number,\n\tnewCropHeight: number,\n\tcenterOnCurrentCrop: boolean = true,\n\tisCircle: boolean = false\n): CropChange {\n\tconst { w, h } = getUncroppedSize(imageShape.props, imageShape.props.crop ?? getDefaultCrop())\n\tconst currentCrop = imageShape.props.crop || getDefaultCrop()\n\n\tlet cropCenterX, cropCenterY\n\tif (centerOnCurrentCrop) {\n\t\tconst { x, y } = getCropCenter(currentCrop)\n\t\tcropCenterX = x\n\t\tcropCenterY = y\n\t} else {\n\t\tcropCenterX = 0.5\n\t\tcropCenterY = 0.5\n\t}\n\n\t// Create new crop\n\tconst newCrop = createCropAroundCenter(\n\t\tcropCenterX,\n\t\tcropCenterY,\n\t\tnewCropWidth,\n\t\tnewCropHeight,\n\t\tisCircle\n\t)\n\n\t// Calculate new dimensions\n\tconst croppedW = newCropWidth * w\n\tconst croppedH = newCropHeight * h\n\n\treturn {\n\t\tcrop: newCrop,\n\t\tw: croppedW,\n\t\th: croppedH,\n\t\t...getPositionKeepingCenter(imageShape, croppedW, croppedH),\n\t}\n}\n\n/** @internal */\nexport const MAX_ZOOM = 3\n\n/**\n * Calculate new crop dimensions and position when zooming\n */\nexport function getCroppedImageDataWhenZooming(\n\tzoom: number,\n\timageShape: TLImageShape,\n\tmaxZoom?: number\n): CropChange {\n\tconst oldCrop = imageShape.props.crop || getDefaultCrop()\n\tconst { width: oldWidth, height: oldHeight } = getCropDimensions(oldCrop)\n\tconst aspectRatio = oldWidth / oldHeight\n\n\t// Calculate new crop size with zoom scale\n\tconst derivedMaxZoom = maxZoom ? 1 / (1 - maxZoom) : MAX_ZOOM\n\tconst zoomScale = 1 + zoom * (derivedMaxZoom - 1)\n\tlet newWidth, newHeight\n\n\tif (aspectRatio > 1) {\n\t\tnewWidth = Math.min(1, 1 / zoomScale)\n\t\tnewHeight = newWidth / aspectRatio\n\t} else {\n\t\tnewHeight = Math.min(1, 1 / zoomScale)\n\t\tnewWidth = newHeight * aspectRatio\n\t}\n\n\t// Calculate result with base function\n\tconst result = calculateCropChange(imageShape, newWidth, newHeight, true, oldCrop.isCircle)\n\n\t// Apply zoom factor to display dimensions\n\tconst scaleFactor = Math.min(MAX_ZOOM, oldWidth / newWidth)\n\tresult.w *= scaleFactor\n\tresult.h *= scaleFactor\n\n\t// Recenter\n\tconst { x, y } = getPositionKeepingCenter(imageShape, result.w, result.h)\n\tresult.x = x\n\tresult.y = y\n\n\treturn result\n}\n\n/**\n * Calculate new crop dimensions and position when replacing an image\n */\nexport function getCroppedImageDataForReplacedImage(\n\timageShape: TLImageShape,\n\tnewImageWidth: number,\n\tnewImageHeight: number\n): CropChange {\n\tconst defaultCrop = getDefaultCrop()\n\tconst currentCrop = imageShape.props.crop || defaultCrop\n\tconst origDisplayW = imageShape.props.w\n\tconst origDisplayH = imageShape.props.h\n\tconst newImageAspectRatio = newImageWidth / newImageHeight\n\n\tlet crop = defaultCrop\n\tlet newDisplayW = origDisplayW\n\tlet newDisplayH = origDisplayH\n\t// Compare the bounds, not the whole object: an explicit `isCircle: false` must still count\n\t// as the original crop.\n\tconst isOriginalCrop =\n\t\t!!imageShape.props.crop &&\n\t\tisEqual(imageShape.props.crop.topLeft, defaultCrop.topLeft) &&\n\t\tisEqual(imageShape.props.crop.bottomRight, defaultCrop.bottomRight) &&\n\t\t!imageShape.props.crop.isCircle\n\n\tif (isOriginalCrop) {\n\t\tnewDisplayW = origDisplayW\n\t\tnewDisplayH = (origDisplayW * newImageHeight) / newImageWidth\n\t} else {\n\t\tconst { w: uncroppedW, h: uncroppedH } = getUncroppedSize(\n\t\t\timageShape.props,\n\t\t\timageShape.props.crop || getDefaultCrop() // Use the ACTUAL current crop to correctly infer uncropped size\n\t\t)\n\t\tconst { width: cropW, height: cropH } = getCropDimensions(currentCrop)\n\t\tconst targetRatio = cropW / cropH\n\t\tconst oldImageAspectRatio = uncroppedW / uncroppedH\n\t\tlet newRelativeWidth: number\n\t\tlet newRelativeHeight: number\n\n\t\tconst currentCropCenter = getCropCenter(currentCrop)\n\n\t\t// Adjust the new crop dimensions to match the current crop zoom\n\t\tnewRelativeWidth = cropW\n\t\tconst ratioConversion = newImageAspectRatio / oldImageAspectRatio / targetRatio\n\t\tnewRelativeHeight = newRelativeWidth * ratioConversion\n\n\t\t// Check that our new crop dimensions are within the MAX_ZOOM bounds\n\t\tconst maxRatioConversion = MAX_ZOOM / (MAX_ZOOM - 1)\n\t\tif (ratioConversion > maxRatioConversion) {\n\t\t\tconst minDimension = 1 / MAX_ZOOM\n\t\t\tif (1 / newRelativeHeight < 1 / newRelativeWidth) {\n\t\t\t\tconst scale = newRelativeHeight / minDimension\n\t\t\t\tnewRelativeHeight = newRelativeHeight / scale\n\t\t\t\tnewRelativeWidth = newRelativeWidth / scale\n\t\t\t} else {\n\t\t\t\tconst scale = newRelativeWidth / minDimension\n\t\t\t\tnewRelativeWidth = newRelativeWidth / scale\n\t\t\t\tnewRelativeHeight = newRelativeHeight / scale\n\t\t\t}\n\t\t}\n\n\t\t// Ensure dimensions are within [0, 1] bounds after adjustment\n\t\tnewRelativeWidth = Math.max(0, Math.min(1, newRelativeWidth))\n\t\tnewRelativeHeight = Math.max(0, Math.min(1, newRelativeHeight))\n\n\t\t// Create the new crop object, centered around the CURRENT crop's center\n\t\tcrop = createCropAroundCenter(\n\t\t\tcurrentCropCenter.x,\n\t\t\tcurrentCropCenter.y,\n\t\t\tnewRelativeWidth,\n\t\t\tnewRelativeHeight,\n\t\t\tcurrentCrop.isCircle\n\t\t)\n\t}\n\n\treturn {\n\t\tcrop,\n\t\tw: newDisplayW,\n\t\th: newDisplayH,\n\t\t...getPositionKeepingCenter(imageShape, newDisplayW, newDisplayH),\n\t}\n}\n\n/**\n * Calculate new crop dimensions and position when changing aspect ratio\n */\nexport function getCroppedImageDataForAspectRatio(\n\taspectRatioOption: ASPECT_RATIO_OPTION,\n\timageShape: TLImageShape\n): CropChange {\n\t// If original aspect ratio is requested, use default crop\n\tif (aspectRatioOption === 'original') {\n\t\tconst { w, h } = getUncroppedSize(imageShape.props, imageShape.props.crop ?? getDefaultCrop())\n\n\t\treturn {\n\t\t\tcrop: getDefaultCrop(),\n\t\t\tw,\n\t\t\th,\n\t\t\t...getPositionKeepingCenter(imageShape, w, h),\n\t\t}\n\t}\n\n\t// Get target ratio and uncropped image properties\n\tconst targetRatio = ASPECT_RATIO_TO_VALUE[aspectRatioOption] // Assume valid option\n\tconst isCircle = aspectRatioOption === 'circle'\n\t// Use default crop to get uncropped size relative to the *original* image bounds\n\tconst { w: uncroppedW, h: uncroppedH } = getUncroppedSize(\n\t\timageShape.props,\n\t\timageShape.props.crop || getDefaultCrop() // Use the ACTUAL current crop to correctly infer uncropped size\n\t)\n\t// Calculate the original image aspect ratio\n\tconst imageAspectRatio = uncroppedW / uncroppedH\n\n\t// Get the current crop and its relative dimensions\n\tconst currentCrop = imageShape.props.crop || getDefaultCrop()\n\tconst { width: cropW, height: cropH } = getCropDimensions(currentCrop)\n\tconst currentCropCenter = getCropCenter(currentCrop)\n\n\t// Calculate the current crop zoom level\n\tconst currentCropZoom = Math.min(1 / cropW, 1 / cropH)\n\n\t// Calculate the relative width and height of the crop rectangle (0-1 scale)\n\t// Try to preserve the longest dimension of the current crop when changing aspect ratios\n\tlet newRelativeWidth: number\n\tlet newRelativeHeight: number\n\n\tif (imageAspectRatio === 0 || !Number.isFinite(imageAspectRatio) || targetRatio === 0) {\n\t\t// Avoid division by zero or NaN issues if image dimensions are invalid or target ratio is 0\n\t\tnewRelativeWidth = 1\n\t\tnewRelativeHeight = 1\n\t} else {\n\t\t// Get current crop dimensions in absolute units\n\t\tconst currentAbsoluteWidth = cropW * uncroppedW\n\t\tconst currentAbsoluteHeight = cropH * uncroppedH\n\n\t\t// Find the longest current dimension to preserve\n\t\tconst longestCurrentDimension = Math.max(currentAbsoluteWidth, currentAbsoluteHeight)\n\t\tconst isWidthLongest = currentAbsoluteWidth >= currentAbsoluteHeight\n\n\t\t// Calculate new dimensions preserving the longest dimension\n\t\tlet newAbsoluteWidth: number\n\t\tlet newAbsoluteHeight: number\n\n\t\tif (isWidthLongest) {\n\t\t\t// Preserve width, calculate height based on target ratio\n\t\t\tnewAbsoluteWidth = longestCurrentDimension\n\t\t\tnewAbsoluteHeight = newAbsoluteWidth / targetRatio\n\t\t} else {\n\t\t\t// Preserve height, calculate width based on target ratio\n\t\t\tnewAbsoluteHeight = longestCurrentDimension\n\t\t\tnewAbsoluteWidth = newAbsoluteHeight * targetRatio\n\t\t}\n\n\t\t// Convert back to relative coordinates\n\t\tnewRelativeWidth = newAbsoluteWidth / uncroppedW\n\t\tnewRelativeHeight = newAbsoluteHeight / uncroppedH\n\n\t\t// Clamp to image bounds and adjust if necessary\n\t\tif (newRelativeWidth > 1) {\n\t\t\t// Width exceeds bounds, clamp and recalculate height\n\t\t\tnewRelativeWidth = 1\n\t\t\tnewRelativeHeight = imageAspectRatio / targetRatio\n\t\t}\n\t\tif (newRelativeHeight > 1) {\n\t\t\t// Height exceeds bounds, clamp and recalculate width\n\t\t\tnewRelativeHeight = 1\n\t\t\tnewRelativeWidth = targetRatio / imageAspectRatio\n\t\t}\n\n\t\t// Final clamp to ensure we stay within bounds\n\t\tnewRelativeWidth = Math.max(0, Math.min(1, newRelativeWidth))\n\t\tnewRelativeHeight = Math.max(0, Math.min(1, newRelativeHeight))\n\t}\n\n\tconst newCropZoom = Math.min(1 / newRelativeWidth, 1 / newRelativeHeight)\n\t// Adjust the new crop dimensions to match the current crop zoom\n\tnewRelativeWidth *= newCropZoom / currentCropZoom\n\tnewRelativeHeight *= newCropZoom / currentCropZoom\n\n\t// Ensure dimensions are within [0, 1] bounds after adjustment\n\tnewRelativeWidth = Math.max(0, Math.min(1, newRelativeWidth))\n\tnewRelativeHeight = Math.max(0, Math.min(1, newRelativeHeight))\n\n\t// Create the new crop object, centered around the CURRENT crop's center\n\tconst newCrop = createCropAroundCenter(\n\t\tcurrentCropCenter.x,\n\t\tcurrentCropCenter.y,\n\t\tnewRelativeWidth,\n\t\tnewRelativeHeight,\n\t\tisCircle\n\t)\n\n\t// Get the actual relative dimensions from the new crop (after potential clamping)\n\tconst finalRelativeWidth = newCrop.bottomRight.x - newCrop.topLeft.x\n\tconst finalRelativeHeight = newCrop.bottomRight.y - newCrop.topLeft.y\n\n\t// Calculate the base dimensions (as if applying the new crop to the uncropped image at scale 1)\n\tconst baseW = finalRelativeWidth * uncroppedW\n\tconst baseH = finalRelativeHeight * uncroppedH\n\n\t// Determine the current effective scale of the shape\n\t// This preserves the visual size when the crop changes\n\tlet currentScale = 1.0\n\tif (cropW > 0) {\n\t\tcurrentScale = imageShape.props.w / (cropW * uncroppedW)\n\t} else if (cropH > 0) {\n\t\t// Fallback to height if width relative dimension is zero\n\t\tcurrentScale = imageShape.props.h / (cropH * uncroppedH)\n\t}\n\n\t// Apply the current scale to the base dimensions to get the final dimensions\n\tconst newW = baseW * currentScale\n\tconst newH = baseH * currentScale\n\n\treturn {\n\t\tcrop: newCrop,\n\t\tw: newW,\n\t\th: newH,\n\t\t...getPositionKeepingCenter(imageShape, newW, newH),\n\t}\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -22,10 +22,10 @@ __export(version_exports, {
22
22
  version: () => version
23
23
  });
24
24
  module.exports = __toCommonJS(version_exports);
25
- const version = "5.4.0";
25
+ const version = "5.5.0-canary.3870eea2f0fd";
26
26
  const publishDates = {
27
27
  major: "2026-05-06T16:28:18.473Z",
28
- minor: "2026-09-02T11:04:02.901Z",
29
- patch: "2026-09-02T11:04:02.901Z"
28
+ minor: "2026-09-02T13:24:49.132Z",
29
+ patch: "2026-09-02T13:24:49.132Z"
30
30
  };
31
31
  //# sourceMappingURL=version.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/ui/version.ts"],
4
- "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.4.0'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-09-02T11:04:02.901Z',\n\tpatch: '2026-09-02T11:04:02.901Z',\n}\n"],
4
+ "sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '5.5.0-canary.3870eea2f0fd'\nexport const publishDates = {\n\tmajor: '2026-05-06T16:28:18.473Z',\n\tminor: '2026-09-02T13:24:49.132Z',\n\tpatch: '2026-09-02T13:24:49.132Z',\n}\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -54,7 +54,7 @@ const globlikeRegExp = (input) => {
54
54
  };
55
55
  const checkHostnames = (hostnames, targetHostname) => {
56
56
  return !!hostnames.find((hostname) => {
57
- const re = new RegExp(globlikeRegExp(hostname));
57
+ const re = new RegExp(`^(?:.+\\.)?${globlikeRegExp(hostname)}$`);
58
58
  return targetHostname.match(re);
59
59
  });
60
60
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/utils/embeds/embeds.ts"],
4
- "sourcesContent": ["import { safeParseUrl } from '@tldraw/editor'\nimport { TLEmbedDefinition } from '../../defaultEmbedDefinitions'\n\n// https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js\nfunction escapeStringRegexp(string: string) {\n\tif (typeof string !== 'string') {\n\t\tthrow new TypeError('Expected a string')\n\t}\n\n\t// Escape characters with special meaning either inside or outside character sets.\n\t// Use a simple backslash escape when it\u2019s always valid, and a `\\xnn` escape when the simpler form would be disallowed by Unicode patterns\u2019 stricter grammar.\n\treturn string.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&').replace(/-/g, '\\\\x2d')\n}\n\n/** @public */\nexport function matchEmbedUrl(definitions: readonly TLEmbedDefinition[], url: string) {\n\tconst parsed = safeParseUrl(url)\n\tif (!parsed) return undefined\n\tconst host = parsed.host.replace('www.', '')\n\tfor (const localEmbedDef of definitions) {\n\t\tif (checkHostnames(localEmbedDef.hostnames, host)) {\n\t\t\tconst originalUrl = localEmbedDef.fromEmbedUrl(url)\n\t\t\tif (originalUrl) {\n\t\t\t\treturn {\n\t\t\t\t\tdefinition: localEmbedDef,\n\t\t\t\t\turl: originalUrl,\n\t\t\t\t\tembedUrl: url,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined\n}\n\nconst globlikeRegExp = (input: string) => {\n\treturn input\n\t\t.split('*')\n\t\t.map((str) => escapeStringRegexp(str))\n\t\t.join('.+')\n}\n\nconst checkHostnames = (hostnames: readonly string[], targetHostname: string) => {\n\treturn !!hostnames.find((hostname) => {\n\t\tconst re = new RegExp(globlikeRegExp(hostname))\n\t\treturn targetHostname.match(re)\n\t})\n}\n\n/** @public */\nexport function matchUrl(\n\tdefinitions: readonly TLEmbedDefinition[],\n\turl: string,\n\tembedConfig?: Record<string, unknown>\n) {\n\tconst parsed = safeParseUrl(url)\n\tif (!parsed) return undefined\n\tconst host = parsed.host.replace('www.', '')\n\tfor (const localEmbedDef of definitions) {\n\t\tif (checkHostnames(localEmbedDef.hostnames, host)) {\n\t\t\tconst embedUrl = localEmbedDef.toEmbedUrl(url, embedConfig?.[localEmbedDef.type])\n\n\t\t\tif (embedUrl) {\n\t\t\t\treturn {\n\t\t\t\t\tdefinition: localEmbedDef,\n\t\t\t\t\tembedUrl,\n\t\t\t\t\turl,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined\n}\n\n/** @public */\nexport type TLEmbedResult =\n\t| {\n\t\t\tdefinition: TLEmbedDefinition\n\t\t\turl: string\n\t\t\tembedUrl: string\n\t }\n\t| undefined\n\n/**\n * Tests whether an URL supports embedding and returns the result. If we encounter an error, we\n * return undefined.\n *\n * @param inputUrl - The URL to match\n * @param embedConfig - Optional per-embed config, keyed by embed type, passed to `toEmbedUrl`\n * @public\n */\nexport function getEmbedInfo(\n\tdefinitions: readonly TLEmbedDefinition[],\n\tinputUrl: string,\n\tembedConfig?: Record<string, unknown>\n): TLEmbedResult {\n\ttry {\n\t\treturn matchUrl(definitions, inputUrl, embedConfig) ?? matchEmbedUrl(definitions, inputUrl)\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\nconst ASPECT_RATIO_EPSILON = 0.001\n\n/**\n * Given an embed shape's current size and a newly-resolved aspect ratio, return the size it should\n * be corrected to, or `null` when no change is needed \u2014 either because there's no resolved ratio or\n * the shape is already at it. The correction preserves the box's area, so a portrait video takes the\n * same visual footprint as a landscape one instead of ballooning the shape's height.\n *\n * @param opts - The current `w`/`h` and the `resolvedRatio` (`width / height`) discovered at runtime.\n * @internal\n */\nexport function getCorrectedEmbedSize({\n\tw,\n\th,\n\tresolvedRatio,\n}: {\n\tw: number\n\th: number\n\tresolvedRatio: number | undefined\n}): { w: number; h: number } | null {\n\tif (!resolvedRatio || resolvedRatio <= 0) return null\n\n\t// Already at the resolved ratio: nothing to do.\n\tif (Math.abs(w / h - resolvedRatio) <= ASPECT_RATIO_EPSILON) return null\n\n\tconst area = w * h\n\treturn { w: Math.sqrt(area * resolvedRatio), h: Math.sqrt(area / resolvedRatio) }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6B;AAI7B,SAAS,mBAAmB,QAAgB;AAC3C,MAAI,OAAO,WAAW,UAAU;AAC/B,UAAM,IAAI,UAAU,mBAAmB;AAAA,EACxC;AAIA,SAAO,OAAO,QAAQ,uBAAuB,MAAM,EAAE,QAAQ,MAAM,OAAO;AAC3E;AAGO,SAAS,cAAc,aAA2C,KAAa;AACrF,QAAM,aAAS,4BAAa,GAAG;AAC/B,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE;AAC3C,aAAW,iBAAiB,aAAa;AACxC,QAAI,eAAe,cAAc,WAAW,IAAI,GAAG;AAClD,YAAM,cAAc,cAAc,aAAa,GAAG;AAClD,UAAI,aAAa;AAChB,eAAO;AAAA,UACN,YAAY;AAAA,UACZ,KAAK;AAAA,UACL,UAAU;AAAA,QACX;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAEA,MAAM,iBAAiB,CAAC,UAAkB;AACzC,SAAO,MACL,MAAM,GAAG,EACT,IAAI,CAAC,QAAQ,mBAAmB,GAAG,CAAC,EACpC,KAAK,IAAI;AACZ;AAEA,MAAM,iBAAiB,CAAC,WAA8B,mBAA2B;AAChF,SAAO,CAAC,CAAC,UAAU,KAAK,CAAC,aAAa;AACrC,UAAM,KAAK,IAAI,OAAO,eAAe,QAAQ,CAAC;AAC9C,WAAO,eAAe,MAAM,EAAE;AAAA,EAC/B,CAAC;AACF;AAGO,SAAS,SACf,aACA,KACA,aACC;AACD,QAAM,aAAS,4BAAa,GAAG;AAC/B,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE;AAC3C,aAAW,iBAAiB,aAAa;AACxC,QAAI,eAAe,cAAc,WAAW,IAAI,GAAG;AAClD,YAAM,WAAW,cAAc,WAAW,KAAK,cAAc,cAAc,IAAI,CAAC;AAEhF,UAAI,UAAU;AACb,eAAO;AAAA,UACN,YAAY;AAAA,UACZ;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAmBO,SAAS,aACf,aACA,UACA,aACgB;AAChB,MAAI;AACH,WAAO,SAAS,aAAa,UAAU,WAAW,KAAK,cAAc,aAAa,QAAQ;AAAA,EAC3F,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,MAAM,uBAAuB;AAWtB,SAAS,sBAAsB;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACD,GAIoC;AACnC,MAAI,CAAC,iBAAiB,iBAAiB,EAAG,QAAO;AAGjD,MAAI,KAAK,IAAI,IAAI,IAAI,aAAa,KAAK,qBAAsB,QAAO;AAEpE,QAAM,OAAO,IAAI;AACjB,SAAO,EAAE,GAAG,KAAK,KAAK,OAAO,aAAa,GAAG,GAAG,KAAK,KAAK,OAAO,aAAa,EAAE;AACjF;",
4
+ "sourcesContent": ["import { safeParseUrl } from '@tldraw/editor'\nimport { TLEmbedDefinition } from '../../defaultEmbedDefinitions'\n\n// https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js\nfunction escapeStringRegexp(string: string) {\n\tif (typeof string !== 'string') {\n\t\tthrow new TypeError('Expected a string')\n\t}\n\n\t// Escape characters with special meaning either inside or outside character sets.\n\t// Use a simple backslash escape when it\u2019s always valid, and a `\\xnn` escape when the simpler form would be disallowed by Unicode patterns\u2019 stricter grammar.\n\treturn string.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&').replace(/-/g, '\\\\x2d')\n}\n\n/** @public */\nexport function matchEmbedUrl(definitions: readonly TLEmbedDefinition[], url: string) {\n\tconst parsed = safeParseUrl(url)\n\tif (!parsed) return undefined\n\tconst host = parsed.host.replace('www.', '')\n\tfor (const localEmbedDef of definitions) {\n\t\tif (checkHostnames(localEmbedDef.hostnames, host)) {\n\t\t\tconst originalUrl = localEmbedDef.fromEmbedUrl(url)\n\t\t\tif (originalUrl) {\n\t\t\t\treturn {\n\t\t\t\t\tdefinition: localEmbedDef,\n\t\t\t\t\turl: originalUrl,\n\t\t\t\t\tembedUrl: url,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined\n}\n\nconst globlikeRegExp = (input: string) => {\n\treturn input\n\t\t.split('*')\n\t\t.map((str) => escapeStringRegexp(str))\n\t\t.join('.+')\n}\n\nconst checkHostnames = (hostnames: readonly string[], targetHostname: string) => {\n\treturn !!hostnames.find((hostname) => {\n\t\t// The host must be the pattern or a subdomain of it. Unanchored, `tldraw.com` would\n\t\t// also match `tldraw.com.evil.io`, which would then inherit that definition's relaxed\n\t\t// iframe sandbox permissions.\n\t\tconst re = new RegExp(`^(?:.+\\\\.)?${globlikeRegExp(hostname)}$`)\n\t\treturn targetHostname.match(re)\n\t})\n}\n\n/** @public */\nexport function matchUrl(\n\tdefinitions: readonly TLEmbedDefinition[],\n\turl: string,\n\tembedConfig?: Record<string, unknown>\n) {\n\tconst parsed = safeParseUrl(url)\n\tif (!parsed) return undefined\n\tconst host = parsed.host.replace('www.', '')\n\tfor (const localEmbedDef of definitions) {\n\t\tif (checkHostnames(localEmbedDef.hostnames, host)) {\n\t\t\tconst embedUrl = localEmbedDef.toEmbedUrl(url, embedConfig?.[localEmbedDef.type])\n\n\t\t\tif (embedUrl) {\n\t\t\t\treturn {\n\t\t\t\t\tdefinition: localEmbedDef,\n\t\t\t\t\tembedUrl,\n\t\t\t\t\turl,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn undefined\n}\n\n/** @public */\nexport type TLEmbedResult =\n\t| {\n\t\t\tdefinition: TLEmbedDefinition\n\t\t\turl: string\n\t\t\tembedUrl: string\n\t }\n\t| undefined\n\n/**\n * Tests whether an URL supports embedding and returns the result. If we encounter an error, we\n * return undefined.\n *\n * @param inputUrl - The URL to match\n * @param embedConfig - Optional per-embed config, keyed by embed type, passed to `toEmbedUrl`\n * @public\n */\nexport function getEmbedInfo(\n\tdefinitions: readonly TLEmbedDefinition[],\n\tinputUrl: string,\n\tembedConfig?: Record<string, unknown>\n): TLEmbedResult {\n\ttry {\n\t\treturn matchUrl(definitions, inputUrl, embedConfig) ?? matchEmbedUrl(definitions, inputUrl)\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\nconst ASPECT_RATIO_EPSILON = 0.001\n\n/**\n * Given an embed shape's current size and a newly-resolved aspect ratio, return the size it should\n * be corrected to, or `null` when no change is needed \u2014 either because there's no resolved ratio or\n * the shape is already at it. The correction preserves the box's area, so a portrait video takes the\n * same visual footprint as a landscape one instead of ballooning the shape's height.\n *\n * @param opts - The current `w`/`h` and the `resolvedRatio` (`width / height`) discovered at runtime.\n * @internal\n */\nexport function getCorrectedEmbedSize({\n\tw,\n\th,\n\tresolvedRatio,\n}: {\n\tw: number\n\th: number\n\tresolvedRatio: number | undefined\n}): { w: number; h: number } | null {\n\tif (!resolvedRatio || resolvedRatio <= 0) return null\n\n\t// Already at the resolved ratio: nothing to do.\n\tif (Math.abs(w / h - resolvedRatio) <= ASPECT_RATIO_EPSILON) return null\n\n\tconst area = w * h\n\treturn { w: Math.sqrt(area * resolvedRatio), h: Math.sqrt(area / resolvedRatio) }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6B;AAI7B,SAAS,mBAAmB,QAAgB;AAC3C,MAAI,OAAO,WAAW,UAAU;AAC/B,UAAM,IAAI,UAAU,mBAAmB;AAAA,EACxC;AAIA,SAAO,OAAO,QAAQ,uBAAuB,MAAM,EAAE,QAAQ,MAAM,OAAO;AAC3E;AAGO,SAAS,cAAc,aAA2C,KAAa;AACrF,QAAM,aAAS,4BAAa,GAAG;AAC/B,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE;AAC3C,aAAW,iBAAiB,aAAa;AACxC,QAAI,eAAe,cAAc,WAAW,IAAI,GAAG;AAClD,YAAM,cAAc,cAAc,aAAa,GAAG;AAClD,UAAI,aAAa;AAChB,eAAO;AAAA,UACN,YAAY;AAAA,UACZ,KAAK;AAAA,UACL,UAAU;AAAA,QACX;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAEA,MAAM,iBAAiB,CAAC,UAAkB;AACzC,SAAO,MACL,MAAM,GAAG,EACT,IAAI,CAAC,QAAQ,mBAAmB,GAAG,CAAC,EACpC,KAAK,IAAI;AACZ;AAEA,MAAM,iBAAiB,CAAC,WAA8B,mBAA2B;AAChF,SAAO,CAAC,CAAC,UAAU,KAAK,CAAC,aAAa;AAIrC,UAAM,KAAK,IAAI,OAAO,cAAc,eAAe,QAAQ,CAAC,GAAG;AAC/D,WAAO,eAAe,MAAM,EAAE;AAAA,EAC/B,CAAC;AACF;AAGO,SAAS,SACf,aACA,KACA,aACC;AACD,QAAM,aAAS,4BAAa,GAAG;AAC/B,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE;AAC3C,aAAW,iBAAiB,aAAa;AACxC,QAAI,eAAe,cAAc,WAAW,IAAI,GAAG;AAClD,YAAM,WAAW,cAAc,WAAW,KAAK,cAAc,cAAc,IAAI,CAAC;AAEhF,UAAI,UAAU;AACb,eAAO;AAAA,UACN,YAAY;AAAA,UACZ;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAmBO,SAAS,aACf,aACA,UACA,aACgB;AAChB,MAAI;AACH,WAAO,SAAS,aAAa,UAAU,WAAW,KAAK,cAAc,aAAa,QAAQ;AAAA,EAC3F,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAEA,MAAM,uBAAuB;AAWtB,SAAS,sBAAsB;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACD,GAIoC;AACnC,MAAI,CAAC,iBAAiB,iBAAiB,EAAG,QAAO;AAGjD,MAAI,KAAK,IAAI,IAAI,IAAI,aAAa,KAAK,qBAAsB,QAAO;AAEpE,QAAM,OAAO,IAAI;AACjB,SAAO,EAAE,GAAG,KAAK,KAAK,OAAO,aAAa,GAAG,GAAG,KAAK,KAAK,OAAO,aAAa,EAAE;AACjF;",
6
6
  "names": []
7
7
  }
@@ -637,7 +637,7 @@ import {
637
637
  } from "./lib/utils/tldr/file.mjs";
638
638
  registerTldrawLibraryVersion(
639
639
  "tldraw",
640
- "5.4.0",
640
+ "5.5.0-canary.3870eea2f0fd",
641
641
  "esm"
642
642
  );
643
643
  export {
@@ -298,11 +298,15 @@ function getCropBox(shape, info, opts = {}) {
298
298
  }
299
299
  };
300
300
  }
301
+ function getPositionKeepingCenter(imageShape, newW, newH) {
302
+ const delta = new Vec((imageShape.props.w - newW) / 2, (imageShape.props.h - newH) / 2).rot(
303
+ imageShape.rotation
304
+ );
305
+ return { x: imageShape.x + delta.x, y: imageShape.y + delta.y };
306
+ }
301
307
  function calculateCropChange(imageShape, newCropWidth, newCropHeight, centerOnCurrentCrop = true, isCircle = false) {
302
308
  const { w, h } = getUncroppedSize(imageShape.props, imageShape.props.crop ?? getDefaultCrop());
303
309
  const currentCrop = imageShape.props.crop || getDefaultCrop();
304
- const imageCenterX = imageShape.x + imageShape.props.w / 2;
305
- const imageCenterY = imageShape.y + imageShape.props.h / 2;
306
310
  let cropCenterX, cropCenterY;
307
311
  if (centerOnCurrentCrop) {
308
312
  const { x, y } = getCropCenter(currentCrop);
@@ -325,8 +329,7 @@ function calculateCropChange(imageShape, newCropWidth, newCropHeight, centerOnCu
325
329
  crop: newCrop,
326
330
  w: croppedW,
327
331
  h: croppedH,
328
- x: imageCenterX - croppedW / 2,
329
- y: imageCenterY - croppedH / 2
332
+ ...getPositionKeepingCenter(imageShape, croppedW, croppedH)
330
333
  };
331
334
  }
332
335
  const MAX_ZOOM = 3;
@@ -348,10 +351,9 @@ function getCroppedImageDataWhenZooming(zoom, imageShape, maxZoom) {
348
351
  const scaleFactor = Math.min(MAX_ZOOM, oldWidth / newWidth);
349
352
  result.w *= scaleFactor;
350
353
  result.h *= scaleFactor;
351
- const imageCenterX = imageShape.x + imageShape.props.w / 2;
352
- const imageCenterY = imageShape.y + imageShape.props.h / 2;
353
- result.x = imageCenterX - result.w / 2;
354
- result.y = imageCenterY - result.h / 2;
354
+ const { x, y } = getPositionKeepingCenter(imageShape, result.w, result.h);
355
+ result.x = x;
356
+ result.y = y;
355
357
  return result;
356
358
  }
357
359
  function getCroppedImageDataForReplacedImage(imageShape, newImageWidth, newImageHeight) {
@@ -405,29 +407,21 @@ function getCroppedImageDataForReplacedImage(imageShape, newImageWidth, newImage
405
407
  currentCrop.isCircle
406
408
  );
407
409
  }
408
- const pageCenterX = imageShape.x + origDisplayW / 2;
409
- const pageCenterY = imageShape.y + origDisplayH / 2;
410
- const newX = pageCenterX - newDisplayW / 2;
411
- const newY = pageCenterY - newDisplayH / 2;
412
410
  return {
413
411
  crop,
414
412
  w: newDisplayW,
415
413
  h: newDisplayH,
416
- x: newX,
417
- y: newY
414
+ ...getPositionKeepingCenter(imageShape, newDisplayW, newDisplayH)
418
415
  };
419
416
  }
420
417
  function getCroppedImageDataForAspectRatio(aspectRatioOption, imageShape) {
421
418
  if (aspectRatioOption === "original") {
422
419
  const { w, h } = getUncroppedSize(imageShape.props, imageShape.props.crop ?? getDefaultCrop());
423
- const imageCenterX = imageShape.x + imageShape.props.w / 2;
424
- const imageCenterY = imageShape.y + imageShape.props.h / 2;
425
420
  return {
426
421
  crop: getDefaultCrop(),
427
422
  w,
428
423
  h,
429
- x: imageCenterX - w / 2,
430
- y: imageCenterY - h / 2
424
+ ...getPositionKeepingCenter(imageShape, w, h)
431
425
  };
432
426
  }
433
427
  const targetRatio = ASPECT_RATIO_TO_VALUE[aspectRatioOption];
@@ -498,16 +492,11 @@ function getCroppedImageDataForAspectRatio(aspectRatioOption, imageShape) {
498
492
  }
499
493
  const newW = baseW * currentScale;
500
494
  const newH = baseH * currentScale;
501
- const currentCenterXPage = imageShape.x + imageShape.props.w / 2;
502
- const currentCenterYPage = imageShape.y + imageShape.props.h / 2;
503
- const newX = currentCenterXPage - newW / 2;
504
- const newY = currentCenterYPage - newH / 2;
505
495
  return {
506
496
  crop: newCrop,
507
497
  w: newW,
508
498
  h: newH,
509
- x: newX,
510
- y: newY
499
+ ...getPositionKeepingCenter(imageShape, newW, newH)
511
500
  };
512
501
  }
513
502
  export {