[improvement] set horizontal position using text alignment (#1419)

This PR makes it so that horizontal alignment in geo and sticky note
shapes also effects the position of the text within the shape.

<img width="1169" alt="image"
src="https://github.com/tldraw/tldraw/assets/23072548/96b28a7d-0f13-46ba-9ea1-82d02b4f870b">

<img width="1274" alt="image"
src="https://github.com/tldraw/tldraw/assets/23072548/fa768c71-4e9e-4cfe-ad8a-94d7700c445d">

This PR also places the shape's label at the center when there is no
text and the shape is not editing.

### Change Type

- [x] `minor` — New Feature

### Test Plan

1. Create shapes with labels
2. Confirm that their labels are positioned correctly
3. Export the shapes and verify the export

### Release Notes

- Geo shapes and sticky notes now position their labels based on their
alignment.
This commit is contained in:
Steve Ruiz 2023-05-19 12:19:11 +01:00 committed by GitHub
parent 818972f222
commit 2a7b2dcdfd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 25 additions and 27 deletions

View file

@ -1002,6 +1002,7 @@ input,
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: var(--color-text);
text-shadow: var(--tl-text-outline);
overflow: hidden;

View file

@ -672,8 +672,16 @@ export class TLGeoUtil extends TLBoxUtil<TLGeoShape> {
width: labelSize.w,
})
// yuck, include padding as magic number
textBgEl.setAttribute('transform', `translate(${(bounds.width - labelSize.w) / 2}, 0)`)
switch (shape.props.align) {
case 'middle': {
textBgEl.setAttribute('transform', `translate(${(bounds.width - labelSize.w) / 2}, 0)`)
break
}
case 'end': {
textBgEl.setAttribute('transform', `translate(${bounds.width - labelSize.w}, 0)`)
break
}
}
const textElm = textBgEl.cloneNode(true) as SVGTextElement
textElm.setAttribute('fill', colors.fill[shape.props.labelColor])

View file

@ -151,27 +151,7 @@ export class TLNoteUtil extends TLShapeUtil<TLNoteShape> {
...opts,
})
const maxWidth = lines.reduce((max, line) => {
return Math.max(
max,
this.app.textMeasure.measureText({
...TEXT_PROPS,
text: line.trim(),
fontFamily: opts.fontFamily,
fontSize: opts.fontSize,
width: 'fit-content',
padding: `0px`,
}).w
)
}, 0)
if (shape.props.align === 'start') {
opts.padding = (bounds.width - maxWidth) / 2
} else if (shape.props.align === 'end') {
opts.padding = -(bounds.width - maxWidth) / 2
} else {
opts.padding = PADDING
}
opts.padding = PADDING
opts.width = bounds.width
const textElm = getTextSvgElement(this.app, {

View file

@ -48,6 +48,8 @@ export const TextLabel = React.memo(function TextLabel<
} = useEditableText(id, type, text)
const isInteractive = isEditing || isEditableFromHover
const finalText = TextHelpers.normalizeTextForDom(text)
const hasText = finalText.trim().length > 0
return (
<div
@ -57,7 +59,14 @@ export const TextLabel = React.memo(function TextLabel<
data-hastext={!isEmpty}
data-isediting={isEditing}
data-textwrap={!!wrap}
style={{ alignItems: verticalAlign === 'middle' ? 'center' : verticalAlign }}
style={
hasText || isInteractive
? {
justifyContent: align === 'middle' ? 'center' : align,
alignItems: verticalAlign === 'middle' ? 'center' : verticalAlign,
}
: undefined
}
>
<div
className="tl-text-label__inner"
@ -70,7 +79,7 @@ export const TextLabel = React.memo(function TextLabel<
}}
>
<div className="tl-text tl-text-content" dir="ltr">
{TextHelpers.normalizeTextForDom(text)}
{finalText}
</div>
{isInteractive ? (
// Consider replacing with content-editable

View file

@ -41,6 +41,8 @@ export function getTextSvgElement(
const innerHeight = lines.length * (opts.lineHeight * opts.fontSize)
const offsetX = padding
let offsetY: number
switch (opts.verticalTextAlign) {
case 'start': {
@ -56,8 +58,6 @@ export function getTextSvgElement(
}
}
const offsetX = padding
// Create text span elements for each line
for (let i = 0; i < lines.length; i++) {
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan')