[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:
parent
818972f222
commit
2a7b2dcdfd
5 changed files with 25 additions and 27 deletions
|
@ -1002,6 +1002,7 @@ input,
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
text-shadow: var(--tl-text-outline);
|
text-shadow: var(--tl-text-outline);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -672,8 +672,16 @@ export class TLGeoUtil extends TLBoxUtil<TLGeoShape> {
|
||||||
width: labelSize.w,
|
width: labelSize.w,
|
||||||
})
|
})
|
||||||
|
|
||||||
// yuck, include padding as magic number
|
switch (shape.props.align) {
|
||||||
textBgEl.setAttribute('transform', `translate(${(bounds.width - labelSize.w) / 2}, 0)`)
|
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
|
const textElm = textBgEl.cloneNode(true) as SVGTextElement
|
||||||
textElm.setAttribute('fill', colors.fill[shape.props.labelColor])
|
textElm.setAttribute('fill', colors.fill[shape.props.labelColor])
|
||||||
|
|
|
@ -151,27 +151,7 @@ export class TLNoteUtil extends TLShapeUtil<TLNoteShape> {
|
||||||
...opts,
|
...opts,
|
||||||
})
|
})
|
||||||
|
|
||||||
const maxWidth = lines.reduce((max, line) => {
|
opts.padding = PADDING
|
||||||
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.width = bounds.width
|
opts.width = bounds.width
|
||||||
|
|
||||||
const textElm = getTextSvgElement(this.app, {
|
const textElm = getTextSvgElement(this.app, {
|
||||||
|
|
|
@ -48,6 +48,8 @@ export const TextLabel = React.memo(function TextLabel<
|
||||||
} = useEditableText(id, type, text)
|
} = useEditableText(id, type, text)
|
||||||
|
|
||||||
const isInteractive = isEditing || isEditableFromHover
|
const isInteractive = isEditing || isEditableFromHover
|
||||||
|
const finalText = TextHelpers.normalizeTextForDom(text)
|
||||||
|
const hasText = finalText.trim().length > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -57,7 +59,14 @@ export const TextLabel = React.memo(function TextLabel<
|
||||||
data-hastext={!isEmpty}
|
data-hastext={!isEmpty}
|
||||||
data-isediting={isEditing}
|
data-isediting={isEditing}
|
||||||
data-textwrap={!!wrap}
|
data-textwrap={!!wrap}
|
||||||
style={{ alignItems: verticalAlign === 'middle' ? 'center' : verticalAlign }}
|
style={
|
||||||
|
hasText || isInteractive
|
||||||
|
? {
|
||||||
|
justifyContent: align === 'middle' ? 'center' : align,
|
||||||
|
alignItems: verticalAlign === 'middle' ? 'center' : verticalAlign,
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="tl-text-label__inner"
|
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">
|
<div className="tl-text tl-text-content" dir="ltr">
|
||||||
{TextHelpers.normalizeTextForDom(text)}
|
{finalText}
|
||||||
</div>
|
</div>
|
||||||
{isInteractive ? (
|
{isInteractive ? (
|
||||||
// Consider replacing with content-editable
|
// Consider replacing with content-editable
|
||||||
|
|
|
@ -41,6 +41,8 @@ export function getTextSvgElement(
|
||||||
|
|
||||||
const innerHeight = lines.length * (opts.lineHeight * opts.fontSize)
|
const innerHeight = lines.length * (opts.lineHeight * opts.fontSize)
|
||||||
|
|
||||||
|
const offsetX = padding
|
||||||
|
|
||||||
let offsetY: number
|
let offsetY: number
|
||||||
switch (opts.verticalTextAlign) {
|
switch (opts.verticalTextAlign) {
|
||||||
case 'start': {
|
case 'start': {
|
||||||
|
@ -56,8 +58,6 @@ export function getTextSvgElement(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const offsetX = padding
|
|
||||||
|
|
||||||
// Create text span elements for each line
|
// Create text span elements for each line
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan')
|
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan')
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue