fix text in geo shapes not causing its container to grow (#2003)
We got things sliggghhhtly wrong in #1980. That diff was attempting to fix a bug where the text measurement element would refuse to go above the viewport size in safari. This was most obvious in the case where there was no fixed width on a text shape, and that diff fixed that case, but it was also happening when a fixed width text shape was wider than viewport - which wasn't covered by that fix. It turned out that that fix also introduced a bug where shapes would no longer grow along the y-axis - in part because the relationship between `width`, `maxWidth`, and `minWidth` is very confusing. The one-liner fix is to just use `max-content` instead of `fit-content` - that way, the div ignores the size of its container. But I also cleared up the API for text measurement to remove the `width` property entirely in favour of `maxWidth`. I think this makes things much clearer and as far as I can tell doesn't affect anything. Closes #1998 ### Change Type - [x] `patch` — Bug fix ### Test Plan 1. Create an arrow & geo shape with labels, plus a note and text shape 2. Try to break text measurement - overflow the bounds, make very wide text, experiment with fixed/auto-size text, etc.
This commit is contained in:
parent
f414548347
commit
92886e1f40
11 changed files with 34 additions and 47 deletions
|
@ -7,14 +7,13 @@ export function sleep(ms: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const measureTextOptions = {
|
const measureTextOptions = {
|
||||||
width: null,
|
maxWidth: null,
|
||||||
fontFamily: 'var(--tl-font-draw)',
|
fontFamily: 'var(--tl-font-draw)',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
lineHeight: 1.35,
|
lineHeight: 1.35,
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
fontStyle: 'normal',
|
fontStyle: 'normal',
|
||||||
padding: '0px',
|
padding: '0px',
|
||||||
maxWidth: 'auto',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const measureTextSpansOptions = {
|
const measureTextSpansOptions = {
|
||||||
|
|
|
@ -811,7 +811,7 @@ input,
|
||||||
top: -9999px;
|
top: -9999px;
|
||||||
right: -9999px;
|
right: -9999px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: fit-content;
|
width: max-content;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
line-break: normal;
|
line-break: normal;
|
||||||
|
|
|
@ -40,7 +40,9 @@ const spaceCharacterRegex = /\s/
|
||||||
export class TextManager {
|
export class TextManager {
|
||||||
constructor(public editor: Editor) {}
|
constructor(public editor: Editor) {}
|
||||||
|
|
||||||
getTextElement() {
|
private textElement: HTMLDivElement | null = null
|
||||||
|
|
||||||
|
private getTextElement() {
|
||||||
const oldElm = document.querySelector('.tl-text-measure')
|
const oldElm = document.querySelector('.tl-text-measure')
|
||||||
oldElm?.remove()
|
oldElm?.remove()
|
||||||
|
|
||||||
|
@ -64,13 +66,12 @@ export class TextManager {
|
||||||
fontSize: number
|
fontSize: number
|
||||||
lineHeight: number
|
lineHeight: number
|
||||||
/**
|
/**
|
||||||
* When width is a number, the text will be wrapped to that width. When
|
* When maxWidth is a number, the text will be wrapped to that maxWidth. When maxWidth
|
||||||
* width is null, the text will be measured without wrapping, but explicit
|
* is null, the text will be measured without wrapping, but explicit line breaks and
|
||||||
* line breaks and space are preserved.
|
* space are preserved.
|
||||||
*/
|
*/
|
||||||
width: null | number
|
maxWidth: null | number
|
||||||
minWidth?: string
|
minWidth?: string
|
||||||
maxWidth: string
|
|
||||||
padding: string
|
padding: string
|
||||||
}
|
}
|
||||||
): Box2dModel => {
|
): Box2dModel => {
|
||||||
|
@ -82,15 +83,8 @@ export class TextManager {
|
||||||
elm.style.setProperty('font-weight', opts.fontWeight)
|
elm.style.setProperty('font-weight', opts.fontWeight)
|
||||||
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
elm.style.setProperty('font-size', opts.fontSize + 'px')
|
||||||
elm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
|
elm.style.setProperty('line-height', opts.lineHeight * opts.fontSize + 'px')
|
||||||
if (opts.width === null) {
|
elm.style.setProperty('max-width', opts.maxWidth === null ? null : opts.maxWidth + 'px')
|
||||||
elm.style.setProperty('white-space', 'pre')
|
|
||||||
elm.style.setProperty('width', 'fit-content')
|
|
||||||
} else {
|
|
||||||
elm.style.setProperty('width', opts.width + 'px')
|
|
||||||
elm.style.setProperty('white-space', 'pre-wrap')
|
|
||||||
}
|
|
||||||
elm.style.setProperty('min-width', opts.minWidth ?? null)
|
elm.style.setProperty('min-width', opts.minWidth ?? null)
|
||||||
elm.style.setProperty('max-width', opts.maxWidth)
|
|
||||||
elm.style.setProperty('padding', opts.padding)
|
elm.style.setProperty('padding', opts.padding)
|
||||||
|
|
||||||
elm.textContent = normalizeTextForDom(textToMeasure)
|
elm.textContent = normalizeTextForDom(textToMeasure)
|
||||||
|
|
|
@ -289,7 +289,7 @@ export function registerDefaultExternalContentHandlers(
|
||||||
...TEXT_PROPS,
|
...TEXT_PROPS,
|
||||||
fontFamily: FONT_FAMILIES[defaultProps.font],
|
fontFamily: FONT_FAMILIES[defaultProps.font],
|
||||||
fontSize: FONT_SIZES[defaultProps.size],
|
fontSize: FONT_SIZES[defaultProps.size],
|
||||||
width: null,
|
maxWidth: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const minWidth = Math.min(
|
const minWidth = Math.min(
|
||||||
|
@ -302,7 +302,7 @@ export function registerDefaultExternalContentHandlers(
|
||||||
...TEXT_PROPS,
|
...TEXT_PROPS,
|
||||||
fontFamily: FONT_FAMILIES[defaultProps.font],
|
fontFamily: FONT_FAMILIES[defaultProps.font],
|
||||||
fontSize: FONT_SIZES[defaultProps.size],
|
fontSize: FONT_SIZES[defaultProps.size],
|
||||||
width: minWidth,
|
maxWidth: minWidth,
|
||||||
})
|
})
|
||||||
w = shrunkSize.w
|
w = shrunkSize.w
|
||||||
h = shrunkSize.h
|
h = shrunkSize.h
|
||||||
|
|
|
@ -114,7 +114,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
||||||
...TEXT_PROPS,
|
...TEXT_PROPS,
|
||||||
fontFamily: FONT_FAMILIES[shape.props.font],
|
fontFamily: FONT_FAMILIES[shape.props.font],
|
||||||
fontSize: ARROW_LABEL_FONT_SIZES[shape.props.size],
|
fontSize: ARROW_LABEL_FONT_SIZES[shape.props.size],
|
||||||
width: null,
|
maxWidth: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
let width = w
|
let width = w
|
||||||
|
@ -129,7 +129,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
||||||
...TEXT_PROPS,
|
...TEXT_PROPS,
|
||||||
fontFamily: FONT_FAMILIES[shape.props.font],
|
fontFamily: FONT_FAMILIES[shape.props.font],
|
||||||
fontSize: ARROW_LABEL_FONT_SIZES[shape.props.size],
|
fontSize: ARROW_LABEL_FONT_SIZES[shape.props.size],
|
||||||
width: width,
|
maxWidth: width,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
|
||||||
...TEXT_PROPS,
|
...TEXT_PROPS,
|
||||||
fontFamily: FONT_FAMILIES[shape.props.font],
|
fontFamily: FONT_FAMILIES[shape.props.font],
|
||||||
fontSize: ARROW_LABEL_FONT_SIZES[shape.props.size],
|
fontSize: ARROW_LABEL_FONT_SIZES[shape.props.size],
|
||||||
width: width,
|
maxWidth: width,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1053,8 +1053,7 @@ function getLabelSize(editor: Editor, shape: TLGeoShape) {
|
||||||
...TEXT_PROPS,
|
...TEXT_PROPS,
|
||||||
fontFamily: FONT_FAMILIES[shape.props.font],
|
fontFamily: FONT_FAMILIES[shape.props.font],
|
||||||
fontSize: LABEL_FONT_SIZES[shape.props.size],
|
fontSize: LABEL_FONT_SIZES[shape.props.size],
|
||||||
width: null,
|
maxWidth: 100,
|
||||||
maxWidth: '100px',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Can I get these from somewhere?
|
// TODO: Can I get these from somewhere?
|
||||||
|
@ -1069,17 +1068,15 @@ function getLabelSize(editor: Editor, shape: TLGeoShape) {
|
||||||
...TEXT_PROPS,
|
...TEXT_PROPS,
|
||||||
fontFamily: FONT_FAMILIES[shape.props.font],
|
fontFamily: FONT_FAMILIES[shape.props.font],
|
||||||
fontSize: LABEL_FONT_SIZES[shape.props.size],
|
fontSize: LABEL_FONT_SIZES[shape.props.size],
|
||||||
width: null,
|
|
||||||
minWidth: minSize.w + 'px',
|
minWidth: minSize.w + 'px',
|
||||||
maxWidth:
|
maxWidth: Math.max(
|
||||||
Math.max(
|
// Guard because a DOM nodes can't be less 0
|
||||||
// Guard because a DOM nodes can't be less 0
|
0,
|
||||||
0,
|
// A 'w' width that we're setting as the min-width
|
||||||
// A 'w' width that we're setting as the min-width
|
Math.ceil(minSize.w + sizes[shape.props.size]),
|
||||||
Math.ceil(minSize.w + sizes[shape.props.size]),
|
// The actual text size
|
||||||
// The actual text size
|
Math.ceil(shape.props.w - LABEL_PADDING * 2)
|
||||||
Math.ceil(shape.props.w - LABEL_PADDING * 2)
|
),
|
||||||
) + 'px',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -194,7 +194,7 @@ function getGrowY(editor: Editor, shape: TLNoteShape, prevGrowY = 0) {
|
||||||
...TEXT_PROPS,
|
...TEXT_PROPS,
|
||||||
fontFamily: FONT_FAMILIES[shape.props.font],
|
fontFamily: FONT_FAMILIES[shape.props.font],
|
||||||
fontSize: LABEL_FONT_SIZES[shape.props.size],
|
fontSize: LABEL_FONT_SIZES[shape.props.size],
|
||||||
width: NOTE_SIZE - PADDING * 2,
|
maxWidth: NOTE_SIZE - PADDING * 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
const nextHeight = nextTextSize.h + PADDING * 2
|
const nextHeight = nextTextSize.h + PADDING * 2
|
||||||
|
|
|
@ -7,7 +7,6 @@ export const TEXT_PROPS = {
|
||||||
fontVariant: 'normal',
|
fontVariant: 'normal',
|
||||||
fontStyle: 'normal',
|
fontStyle: 'normal',
|
||||||
padding: '0px',
|
padding: '0px',
|
||||||
maxWidth: 'auto',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
|
|
@ -369,7 +369,7 @@ export class TextShapeUtil extends ShapeUtil<TLTextShape> {
|
||||||
function getTextSize(editor: Editor, props: TLTextShape['props']) {
|
function getTextSize(editor: Editor, props: TLTextShape['props']) {
|
||||||
const { font, text, autoSize, size, w } = props
|
const { font, text, autoSize, size, w } = props
|
||||||
|
|
||||||
const minWidth = 16
|
const minWidth = autoSize ? 16 : Math.max(16, w)
|
||||||
const fontSize = FONT_SIZES[size]
|
const fontSize = FONT_SIZES[size]
|
||||||
|
|
||||||
const cw = autoSize
|
const cw = autoSize
|
||||||
|
@ -381,7 +381,7 @@ function getTextSize(editor: Editor, props: TLTextShape['props']) {
|
||||||
...TEXT_PROPS,
|
...TEXT_PROPS,
|
||||||
fontFamily: FONT_FAMILIES[font],
|
fontFamily: FONT_FAMILIES[font],
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
width: cw,
|
maxWidth: cw,
|
||||||
})
|
})
|
||||||
|
|
||||||
// // If we're autosizing the measureText will essentially `Math.floor`
|
// // If we're autosizing the measureText will essentially `Math.floor`
|
||||||
|
|
|
@ -81,8 +81,7 @@ export class TestEditor extends Editor {
|
||||||
fontFamily: string
|
fontFamily: string
|
||||||
fontSize: number
|
fontSize: number
|
||||||
lineHeight: number
|
lineHeight: number
|
||||||
width: null | number
|
maxWidth: null | number
|
||||||
maxWidth: string
|
|
||||||
}
|
}
|
||||||
): Box2dModel => {
|
): Box2dModel => {
|
||||||
const breaks = textToMeasure.split('\n')
|
const breaks = textToMeasure.split('\n')
|
||||||
|
@ -95,9 +94,9 @@ export class TestEditor extends Editor {
|
||||||
return {
|
return {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
w: opts.width === null ? w : Math.max(w, opts.width),
|
w: opts.maxWidth === null ? w : Math.max(w, opts.maxWidth),
|
||||||
h:
|
h:
|
||||||
(opts.width === null ? breaks.length : Math.ceil(w % opts.width) + breaks.length) *
|
(opts.maxWidth === null ? breaks.length : Math.ceil(w % opts.maxWidth) + breaks.length) *
|
||||||
opts.fontSize,
|
opts.fontSize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,9 +104,8 @@ export class TestEditor extends Editor {
|
||||||
this.textMeasure.measureTextSpans = (textToMeasure, opts) => {
|
this.textMeasure.measureTextSpans = (textToMeasure, opts) => {
|
||||||
const box = this.textMeasure.measureText(textToMeasure, {
|
const box = this.textMeasure.measureText(textToMeasure, {
|
||||||
...opts,
|
...opts,
|
||||||
width: opts.width,
|
maxWidth: opts.width,
|
||||||
padding: `${opts.padding}px`,
|
padding: `${opts.padding}px`,
|
||||||
maxWidth: 'auto',
|
|
||||||
})
|
})
|
||||||
return [{ box, text: textToMeasure }]
|
return [{ box, text: textToMeasure }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ exports[`Matches a snapshot: Basic SVG 1`] = `
|
||||||
>
|
>
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
d="M0, 0L100, 0,100, 100,0, 100Z"
|
d="M0, 0L100, 0,100, 428,0, 428Z"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="#1d1d1d"
|
stroke="#1d1d1d"
|
||||||
stroke-width="3.5"
|
stroke-width="3.5"
|
||||||
|
@ -108,7 +108,7 @@ exports[`Matches a snapshot: Basic SVG 1`] = `
|
||||||
<tspan
|
<tspan
|
||||||
alignment-baseline="mathematical"
|
alignment-baseline="mathematical"
|
||||||
x="16px"
|
x="16px"
|
||||||
y="-181px"
|
y="-17px"
|
||||||
>
|
>
|
||||||
Hello world
|
Hello world
|
||||||
</tspan>
|
</tspan>
|
||||||
|
@ -127,7 +127,7 @@ exports[`Matches a snapshot: Basic SVG 1`] = `
|
||||||
<tspan
|
<tspan
|
||||||
alignment-baseline="mathematical"
|
alignment-baseline="mathematical"
|
||||||
x="16px"
|
x="16px"
|
||||||
y="-181px"
|
y="-17px"
|
||||||
>
|
>
|
||||||
Hello world
|
Hello world
|
||||||
</tspan>
|
</tspan>
|
||||||
|
|
Loading…
Reference in a new issue