diff --git a/packages/editor/api-report.md b/packages/editor/api-report.md
index 6b305fb5c..ad1170e83 100644
--- a/packages/editor/api-report.md
+++ b/packages/editor/api-report.md
@@ -328,6 +328,9 @@ export function clamp(n: number, min: number, max: number): number;
// @public
export function clampRadians(r: number): number;
+// @public
+export function clockwiseAngleDist(a0: number, a1: number): number;
+
export { computed }
// @public (undocumented)
diff --git a/packages/editor/editor.css b/packages/editor/editor.css
index 7c7bc7a3a..842c9b5a8 100644
--- a/packages/editor/editor.css
+++ b/packages/editor/editor.css
@@ -1029,22 +1029,17 @@ input,
/* ---------------- Geo shape ---------------- */
.tl-text-label {
- width: 100%;
- height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: var(--color-text);
text-shadow: var(--tl-text-outline);
line-height: inherit;
- position: relative;
+ position: absolute;
+ inset: 0px;
z-index: 10;
}
-.tl-text-label[data-isediting='true'] {
- outline: calc(var(--tl-scale) * 1.5px) solid var(--color-selected);
-}
-
.tl-text-label[data-isediting='true'] .tl-text-content {
opacity: 0;
}
@@ -1168,7 +1163,6 @@ input,
.tl-arrow-label[data-isediting='true'] > .tl-arrow-label__inner {
background-color: var(--color-background);
- border: calc(var(--tl-scale) * 1.5px) solid var(--color-selected);
}
.tl-arrow-label__inner {
@@ -1355,7 +1349,6 @@ input,
.tl-frame-label__editing {
color: transparent;
- outline: 1.5px solid var(--color-selection-stroke);
white-space: pre;
width: auto;
overflow: visible;
diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts
index f8e4c33e0..452815232 100644
--- a/packages/editor/src/index.ts
+++ b/packages/editor/src/index.ts
@@ -297,6 +297,7 @@ export {
canonicalizeRotation,
clamp,
clampRadians,
+ clockwiseAngleDist,
degreesToRadians,
getArcLength,
getPointOnCircle,
diff --git a/packages/editor/src/lib/components/Canvas.tsx b/packages/editor/src/lib/components/Canvas.tsx
index a4902072c..17219988d 100644
--- a/packages/editor/src/lib/components/Canvas.tsx
+++ b/packages/editor/src/lib/components/Canvas.tsx
@@ -322,6 +322,7 @@ function SelectedIdIndicators() {
'select.idle',
'select.brushing',
'select.scribble_brushing',
+ 'select.editing_shape',
'select.pointing_shape',
'select.pointing_selection',
'select.pointing_handle'
diff --git a/packages/editor/src/lib/components/Shape.tsx b/packages/editor/src/lib/components/Shape.tsx
index ef7f8b8c9..e9f948739 100644
--- a/packages/editor/src/lib/components/Shape.tsx
+++ b/packages/editor/src/lib/components/Shape.tsx
@@ -6,6 +6,7 @@ import { nearestMultiple } from '../hooks/useDPRMultiple'
import { useEditor } from '../hooks/useEditor'
import { useEditorComponents } from '../hooks/useEditorComponents'
import { Matrix2d } from '../primitives/Matrix2d'
+import { toDomPrecision } from '../primitives/utils'
import { OptionalErrorBoundary } from './ErrorBoundary'
/*
@@ -170,9 +171,11 @@ const CulledShape = React.memo(
)
diff --git a/packages/editor/src/lib/primitives/utils.ts b/packages/editor/src/lib/primitives/utils.ts
index 0f7e6f2cc..00f8fd1be 100644
--- a/packages/editor/src/lib/primitives/utils.ts
+++ b/packages/editor/src/lib/primitives/utils.ts
@@ -109,6 +109,22 @@ export function canonicalizeRotation(a: number) {
return a
}
+/**
+ * Get the clockwise angle distance between two angles.
+ *
+ * @param a0 - The first angle.
+ * @param a1 - The second angle.
+ * @public
+ */
+export function clockwiseAngleDist(a0: number, a1: number): number {
+ a0 = canonicalizeRotation(a0)
+ a1 = canonicalizeRotation(a1)
+ if (a0 > a1) {
+ a1 += PI2
+ }
+ return a1 - a0
+}
+
/**
* Get the short angle distance between two angles.
*
@@ -270,12 +286,27 @@ export function getPointOnCircle(cx: number, cy: number, r: number, a: number) {
export function getPolygonVertices(width: number, height: number, sides: number) {
const cx = width / 2
const cy = height / 2
- const pointsOnPerimeter = []
+ const pointsOnPerimeter: Vec2d[] = []
+ let minX = Infinity
+ let minY = Infinity
for (let i = 0; i < sides; i++) {
const step = PI2 / sides
const t = -TAU + i * step
- pointsOnPerimeter.push(new Vec2d(cx + cx * Math.cos(t), cy + cy * Math.sin(t)))
+ const x = cx + cx * Math.cos(t)
+ const y = cy + cy * Math.sin(t)
+ if (x < minX) minX = x
+ if (y < minY) minY = y
+ pointsOnPerimeter.push(new Vec2d(x, y))
}
+
+ if (minX !== 0 || minY !== 0) {
+ for (let i = 0; i < pointsOnPerimeter.length; i++) {
+ const pt = pointsOnPerimeter[i]
+ pt.x -= minX
+ pt.y -= minY
+ }
+ }
+
return pointsOnPerimeter
}
diff --git a/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx
index 502376b58..cd5ce2559 100644
--- a/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx
+++ b/packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.tsx
@@ -34,6 +34,7 @@ import {
getSolidStraightArrowPath,
getStraightArrowHandlePath,
toDomPrecision,
+ useIsEditing,
} from '@tldraw/editor'
import React from 'react'
import { ShapeFill, getShapeFillSvg, useDefaultColorTheme } from '../shared/ShapeFill'
@@ -692,6 +693,22 @@ export class ArrowShapeUtil extends ShapeUtil {
const maskId = (shape.id + '_clip').replace(':', '_')
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const isEditing = useIsEditing(shape.id)
+
+ if (isEditing && labelGeometry) {
+ return (
+
+ )
+ }
+
return (
{includeMask && (
diff --git a/packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx b/packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx
index 24566347b..49ae4da34 100644
--- a/packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx
+++ b/packages/tldraw/src/lib/shapes/geo/GeoShapeUtil.tsx
@@ -532,6 +532,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil {
text={text}
labelColor={labelColor}
wrap
+ bounds={props.geo === 'cloud' ? this.getGeometry(shape).bounds : undefined}
/>
{shape.props.url && (
@@ -813,10 +814,12 @@ export class GeoShapeUtil extends BaseBoxShapeUtil {
override onResize: TLOnResizeHandler = (
shape,
- { initialBounds, handle, newPoint, scaleX, scaleY }
+ { handle, newPoint, scaleX, scaleY, initialShape }
) => {
- let w = initialBounds.width * scaleX
- let h = initialBounds.height * scaleY
+ // use the w/h from props here instead of the initialBounds here,
+ // since cloud shapes calculated bounds can differ from the props w/h.
+ let w = initialShape.props.w * scaleX
+ let h = (initialShape.props.h + initialShape.props.growY) * scaleY
let overShrinkX = 0
let overShrinkY = 0
diff --git a/packages/tldraw/src/lib/shapes/geo/cloudOutline.ts b/packages/tldraw/src/lib/shapes/geo/cloudOutline.ts
index 09cd4e15d..c2db93980 100644
--- a/packages/tldraw/src/lib/shapes/geo/cloudOutline.ts
+++ b/packages/tldraw/src/lib/shapes/geo/cloudOutline.ts
@@ -3,9 +3,10 @@ import {
TLDefaultSizeStyle,
Vec2d,
Vec2dModel,
+ clockwiseAngleDist,
getPointOnCircle,
rng,
- shortAngleDist,
+ toDomPrecision,
} from '@tldraw/editor'
function getPillCircumference(width: number, height: number) {
@@ -200,8 +201,8 @@ export function getCloudArcs(
const radius = Vec2d.Dist(center, leftWigglePoint)
arcs.push({
- leftPoint: leftWigglePoint,
- rightPoint: rightWigglePoint,
+ leftPoint: leftWigglePoint.clone(),
+ rightPoint: rightWigglePoint.clone(),
arcPoint,
center,
radius,
@@ -265,11 +266,13 @@ export function cloudSvgPath(
size: TLDefaultSizeStyle
) {
const arcs = getCloudArcs(width, height, seed, size)
- let path = `M${arcs[0].leftPoint.x},${arcs[0].leftPoint.y}`
+ let path = `M${toDomPrecision(arcs[0].leftPoint.x)},${toDomPrecision(arcs[0].leftPoint.y)}`
// now draw arcs for each circle, starting where it intersects the previous circle, and ending where it intersects the next circle
for (const { rightPoint, radius } of arcs) {
- path += ` A${radius},${radius} 0 0,1 ${rightPoint.x},${rightPoint.y}`
+ path += ` A${toDomPrecision(radius)},${toDomPrecision(radius)} 0 0,1 ${toDomPrecision(
+ rightPoint.x
+ )},${toDomPrecision(rightPoint.y)}`
}
path += ' Z'
@@ -289,18 +292,22 @@ export function inkyCloudSvgPath(
}
const mutPoint = (p: Vec2d) => new Vec2d(mut(p.x), mut(p.y))
const arcs = getCloudArcs(width, height, seed, size)
- let pathA = `M${arcs[0].leftPoint.x},${arcs[0].leftPoint.y}`
+ let pathA = `M${toDomPrecision(arcs[0].leftPoint.x)},${toDomPrecision(arcs[0].leftPoint.y)}`
let leftMutPoint = mutPoint(arcs[0].leftPoint)
- let pathB = `M${leftMutPoint.x},${leftMutPoint.y}`
+ let pathB = `M${toDomPrecision(leftMutPoint.x)},${toDomPrecision(leftMutPoint.y)}`
for (const { rightPoint, radius, arcPoint } of arcs) {
- pathA += ` A${radius},${radius} 0 0,1 ${rightPoint.x},${rightPoint.y}`
+ pathA += ` A${toDomPrecision(radius)},${toDomPrecision(radius)} 0 0,1 ${toDomPrecision(
+ rightPoint.x
+ )},${toDomPrecision(rightPoint.y)}`
const rightMutPoint = mutPoint(rightPoint)
const mutArcPoint = mutPoint(arcPoint)
const mutCenter = getCenterOfCircleGivenThreePoints(leftMutPoint, rightMutPoint, mutArcPoint)
const mutRadius = Math.abs(Vec2d.Dist(mutCenter, leftMutPoint))
- pathB += ` A${mutRadius},${mutRadius} 0 0,1 ${rightMutPoint.x},${rightMutPoint.y}`
+ pathB += ` A${toDomPrecision(mutRadius)},${toDomPrecision(mutRadius)} 0 0,1 ${toDomPrecision(
+ rightMutPoint.x
+ )},${toDomPrecision(rightMutPoint.y)}`
leftMutPoint = rightMutPoint
}
@@ -319,7 +326,7 @@ export function pointsOnArc(
const startAngle = Vec2d.Angle(center, startPoint)
const endAngle = Vec2d.Angle(center, endPoint)
- const l = shortAngleDist(startAngle, endAngle)
+ const l = clockwiseAngleDist(startAngle, endAngle)
for (let i = 0; i < numPoints; i++) {
const t = i / (numPoints - 1)
diff --git a/packages/tldraw/src/lib/shapes/geo/components/DashStyleEllipse.tsx b/packages/tldraw/src/lib/shapes/geo/components/DashStyleEllipse.tsx
index d5f545e2c..ff3666cb7 100644
--- a/packages/tldraw/src/lib/shapes/geo/components/DashStyleEllipse.tsx
+++ b/packages/tldraw/src/lib/shapes/geo/components/DashStyleEllipse.tsx
@@ -28,8 +28,8 @@ export const DashStyleEllipse = React.memo(function DashStyleEllipse({
const theme = useDefaultColorTheme()
const cx = w / 2
const cy = h / 2
- const rx = Math.max(0, cx - sw / 2)
- const ry = Math.max(0, cy - sw / 2)
+ const rx = Math.max(0, cx)
+ const ry = Math.max(0, cy)
const perimeter = perimeterOfEllipse(rx, ry)
diff --git a/packages/tldraw/src/lib/shapes/shared/TextLabel.tsx b/packages/tldraw/src/lib/shapes/shared/TextLabel.tsx
index 033f2ca2d..ca76ed2ae 100644
--- a/packages/tldraw/src/lib/shapes/shared/TextLabel.tsx
+++ b/packages/tldraw/src/lib/shapes/shared/TextLabel.tsx
@@ -1,4 +1,5 @@
import {
+ Box2d,
TLDefaultColorStyle,
TLDefaultFillStyle,
TLDefaultFontStyle,
@@ -27,6 +28,7 @@ export const TextLabel = React.memo(function TextLabel<
align,
verticalAlign,
wrap,
+ bounds,
}: {
id: T['id']
type: T['type']
@@ -38,6 +40,7 @@ export const TextLabel = React.memo(function TextLabel<
wrap?: boolean
text: string
labelColor: TLDefaultColorStyle
+ bounds?: Box2d
}) {
const {
rInput,
@@ -71,48 +74,59 @@ export const TextLabel = React.memo(function TextLabel<
style={{
justifyContent: align === 'middle' || legacyAlign ? 'center' : align,
alignItems: verticalAlign === 'middle' ? 'center' : verticalAlign,
+ ...(bounds
+ ? {
+ top: bounds.minY,
+ left: bounds.minX,
+ width: bounds.width,
+ height: bounds.height,
+ position: 'absolute',
+ }
+ : {}),
}}
>
-
-
- {finalText}
+ {isEmpty && !isInteractive ? null : (
+
+
+ {finalText}
+
+ {isInteractive && (
+
+ )}
- {isInteractive && (
-
- )}
-
+ )}
)
})