Skip to content

Commit

Permalink
fix(text) Including Text Content Now Filters For Text Content (#6041)
Browse files Browse the repository at this point in the history
- Changed the parameter `withContent` for
`getCanvasRectangleFromElement` to make it clear that it means text
content.
- `getCanvasRectangleFromElement` filters for text nodes when including
text content in the dimensions.
  • Loading branch information
seanparsons authored and liady committed Dec 13, 2024
1 parent 225d188 commit 317de09
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 50 deletions.
47 changes: 25 additions & 22 deletions editor/src/components/canvas/dom-walker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ export function runDomWalker({
return getCanvasRectangleFromElement(
canvasRootContainer,
scale,
'without-content',
'without-text-content',
'nearest-half',
)
})
Expand Down Expand Up @@ -677,15 +677,15 @@ function collectMetadataForElement(
element,
scale,
containerRectLazy,
'without-content',
'without-text-content',
'nearest-half',
)
const localFrame = localRectangle(Utils.offsetRect(globalFrame, Utils.negate(parentPoint)))
const nonRoundedGlobalFrame = globalFrameForElement(
element,
scale,
containerRectLazy,
'without-content',
'without-text-content',
'no-rounding',
)

Expand Down Expand Up @@ -952,7 +952,7 @@ function getSpecialMeasurements(
element.offsetParent,
scale,
containerRectLazy,
'without-content',
'without-text-content',
'nearest-half',
)
: null
Expand All @@ -963,7 +963,7 @@ function getSpecialMeasurements(
element.parentElement,
scale,
containerRectLazy,
'without-content',
'without-text-content',
'nearest-half',
)
: null
Expand Down Expand Up @@ -1048,15 +1048,15 @@ function getSpecialMeasurements(
elementOrContainingParent,
scale,
containerRectLazy,
'without-content',
'without-text-content',
'nearest-half',
)

const globalFrameWithTextContent = globalFrameForElement(
element,
scale,
containerRectLazy,
'with-content',
'with-text-content',
'nearest-half',
)

Expand Down Expand Up @@ -1103,18 +1103,21 @@ function getSpecialMeasurements(
const textDecorationLine = elementStyle.textDecorationLine

const textBounds = elementContainsOnlyText(element)
? stretchRect(getCanvasRectangleFromElement(element, scale, 'only-content', 'nearest-half'), {
w:
maybeValueFromComputedStyle(elementStyle.paddingLeft) +
maybeValueFromComputedStyle(elementStyle.paddingRight) +
maybeValueFromComputedStyle(elementStyle.marginLeft) +
maybeValueFromComputedStyle(elementStyle.marginRight),
h:
maybeValueFromComputedStyle(elementStyle.paddingTop) +
maybeValueFromComputedStyle(elementStyle.paddingBottom) +
maybeValueFromComputedStyle(elementStyle.marginTop) +
maybeValueFromComputedStyle(elementStyle.marginBottom),
})
? stretchRect(
getCanvasRectangleFromElement(element, scale, 'only-text-content', 'nearest-half'),
{
w:
maybeValueFromComputedStyle(elementStyle.paddingLeft) +
maybeValueFromComputedStyle(elementStyle.paddingRight) +
maybeValueFromComputedStyle(elementStyle.marginLeft) +
maybeValueFromComputedStyle(elementStyle.marginRight),
h:
maybeValueFromComputedStyle(elementStyle.paddingTop) +
maybeValueFromComputedStyle(elementStyle.paddingBottom) +
maybeValueFromComputedStyle(elementStyle.marginTop) +
maybeValueFromComputedStyle(elementStyle.marginBottom),
},
)
: null

const computedStyleMap =
Expand Down Expand Up @@ -1204,7 +1207,7 @@ function globalFrameForElement(
element: HTMLElement,
scale: number,
containerRectLazy: () => CanvasRectangle,
withContent: 'without-content' | 'with-content',
withContent: 'without-text-content' | 'with-text-content',
rounding: 'nearest-half' | 'no-rounding',
) {
const elementRect = getCanvasRectangleFromElement(element, scale, withContent, rounding)
Expand Down Expand Up @@ -1339,7 +1342,7 @@ function walkSceneInner(
scene,
globalProps.scale,
globalProps.containerRectLazy,
'without-content',
'without-text-content',
'nearest-half',
)

Expand Down Expand Up @@ -1418,7 +1421,7 @@ function walkElements(
element,
globalProps.scale,
globalProps.containerRectLazy,
'without-content',
'without-text-content',
'nearest-half',
)

Expand Down
43 changes: 43 additions & 0 deletions editor/src/core/model/element-metadata.spec.browser2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1478,6 +1478,49 @@ describe('record variable values', () => {
})
})

describe('specialSizeMeasurements.globalFrameWithTextContent', () => {
it('includes the size of a contained text node', async () => {
const renderResult = await renderTestEditorWithCode(
makeTestProjectCodeWithSnippet(`
<div data-uid='div' style={{position: 'absolute', left: 0, top: 0, width: 50, height: 50}}>
THIS IS A BIG CHUNK OF TEXT WHICH SHOULD MAKE THE OVERALL SIZE A LITTLE LARGER
</div>
`),
'await-first-dom-report',
)
const divMetadata =
renderResult.getEditorState().editor.jsxMetadata[
`utopia-storyboard-uid/scene-aaa/app-entity:div`
]
expect(divMetadata.specialSizeMeasurements.globalFrameWithTextContent).toEqual({
x: 0,
y: 0,
height: 251,
width: 74.5,
})
})
it('does not include the size of a contained div', async () => {
const renderResult = await renderTestEditorWithCode(
makeTestProjectCodeWithSnippet(`
<div data-uid='div' style={{position: 'absolute', left: 0, top: 0, width: 50, height: 50}}>
<div style={{position: 'absolute', left: 0, top: 0, width: 30, height: 30}} />
</div>
`),
'await-first-dom-report',
)
const divMetadata =
renderResult.getEditorState().editor.jsxMetadata[
`utopia-storyboard-uid/scene-aaa/app-entity:div`
]
expect(divMetadata.specialSizeMeasurements.globalFrameWithTextContent).toEqual({
x: 0,
y: 0,
width: 50,
height: 50,
})
})
})

const TestProjectWithSeveralComponents = `
import * as React from 'react'
import { Scene, Storyboard } from 'utopia-api'
Expand Down
53 changes: 26 additions & 27 deletions editor/src/core/shared/dom-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ReactDOM } from 'react'
import type { CanvasRectangle, MaybeInfinityCanvasRectangle } from './math-utils'
import {
boundingRectangle,
boundingRectangleArray,
canvasRectangle,
isNotNullFiniteRectangle,
roundToNearestHalf,
Expand Down Expand Up @@ -279,7 +280,7 @@ function getRoundingFn(rounding: 'nearest-half' | 'no-rounding') {
export function getCanvasRectangleFromElement(
element: HTMLElement,
canvasScale: number,
withContent: 'without-content' | 'with-content' | 'only-content',
withContent: 'without-text-content' | 'with-text-content' | 'only-text-content',
rounding: 'nearest-half' | 'no-rounding',
): CanvasRectangle {
const scale = canvasScale < 1 ? 1 / canvasScale : 1
Expand All @@ -299,33 +300,31 @@ export function getCanvasRectangleFromElement(
)
}

const boundingRect = element.getBoundingClientRect()
const elementRect = domRectToScaledCanvasRectangle(boundingRect)
if (withContent === 'without-content') {
return elementRect
}

const range = document.createRange()
switch (withContent) {
case 'only-content':
range.selectNodeContents(element)
break
case 'with-content':
range.selectNode(element)
break
default:
assertNever(withContent)
}
const rangeBounding =
// this is needed because jsdom can throw an error on the range.getBoundingClientRect() call, see https://github.com/jsdom/jsdom/issues/3002
typeof range.getBoundingClientRect === 'function' ? range.getBoundingClientRect() : boundingRect
const contentRect = domRectToScaledCanvasRectangle(rangeBounding)

switch (withContent) {
case 'only-content':
return contentRect
case 'with-content':
return boundingRectangle(elementRect, contentRect)
case 'without-text-content': {
const boundingRect = element.getBoundingClientRect()
const elementRect = domRectToScaledCanvasRectangle(boundingRect)
return elementRect
}
case 'only-text-content':
case 'with-text-content':
let rectangles: Array<CanvasRectangle> = []
for (const childNode of element.childNodes) {
if (childNode.nodeType === Node.TEXT_NODE) {
const range = document.createRange()
// this is needed because jsdom can throw an error on the range.getBoundingClientRect() call, see https://github.com/jsdom/jsdom/issues/3002
if (typeof range.getBoundingClientRect === 'function') {
range.selectNode(childNode)
rectangles.push(domRectToScaledCanvasRectangle(range.getBoundingClientRect()))
}
}
}
if (withContent === 'with-text-content') {
rectangles.push(domRectToScaledCanvasRectangle(element.getBoundingClientRect()))
}
return (
boundingRectangleArray(rectangles) ?? canvasRectangle({ x: 0, y: 0, width: 0, height: 0 })
)
default:
assertNever(withContent)
}
Expand Down
2 changes: 1 addition & 1 deletion editor/src/utils/utils.test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ export function boundingClientRectToCanvasRectangle(
const canvasRootRectangle = getCanvasRectangleFromElement(
canvasRootContainer,
canvasScale,
'without-content',
'without-text-content',
'nearest-half',
)
const canvasBounds = offsetRect(canvasRectangle(elementBounds), negate(canvasRootRectangle))
Expand Down

0 comments on commit 317de09

Please sign in to comment.