Skip to content

fix(form-textarea): improved computedHeight calculation when in auto resize mode #3012

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 5, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 33 additions & 27 deletions src/components/form-textarea/form-textarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export default {
// The computed height for auto resize.
// We avoid setting the style to null, which can override user manual resize.
styles.height = this.computedHeight
// We always add a vertical scrollbar to the textarea when auto-resize is
// enabled so that the computed height calcaultion returns a stable value.
styles.overflowY = 'scroll'
}
return styles
},
Expand All @@ -77,9 +80,10 @@ export default {
// If auto-resize is enabled, then we return null as we use CSS to control height.
return this.computedMinRows === this.computedMaxRows ? this.computedMinRows : null
},
computedHeight() /* istanbul ignore next: can't test getComputedProperties */ {
computedHeight() /* istanbul ignore next: can't test getComputedStyle in JSDOM */ {
// We compare `computedRows` and `localValue` to `true`, a value
// they both can't have at any time, to ensure reactivity
// they both can't have at any time, to ensure reactivity of this
// computed property.
if (
this.$isServer ||
this.dontResize ||
Expand All @@ -91,48 +95,50 @@ export default {

const el = this.$el

// Element must be visible (not hidden) and in document
// *Must* be checked after above checks
// Element must be visible (not hidden) and in document.
// Must be checked after above checks
if (!isVisible(el)) {
return null
}

// Remember old height (includes `px` units) and reset it temporarily to `auto`
const oldHeight = el.style.height
el.style.height = 'auto'
Copy link
Member

@tmorehouse tmorehouse Apr 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of the reason for using auto was that it would trigger an immediate content reflow of the <textarea>, as setting it to any other value may not trigger a reflow/recalculation until the next animation frame (which would generally happen after the rest of the computed property has completed executing) so the scrollHeight may not be actually be updated until well after the value is used.


// Get current computed styles
const computedStyle = getCS(el)
// Height of one line of text in px
const lineHeight = parseFloat(computedStyle.lineHeight)
// Minimum height for min rows (browser dependant)
const minHeight = parseInt(computedStyle.height, 10) || lineHeight * this.computedMinRows
// Calculate height of content
const offset =
// Calculate height of border and padding
const border =
(parseFloat(computedStyle.borderTopWidth) || 0) +
(parseFloat(computedStyle.borderBottomWidth) || 0) +
(parseFloat(computedStyle.paddingTop) || 0) +
(parseFloat(computedStyle.paddingBottom) || 0)
// Calculate content height in "rows"
const contentRows = Math.max((el.scrollHeight - offset) / lineHeight, 2)
(parseFloat(computedStyle.borderBottomWidth) || 0)
const padding =
(parseFloat(computedStyle.paddingTop) || 0) + (parseFloat(computedStyle.paddingBottom) || 0)
// Calculate offset
const offset = border + padding
// Minimum height for min rows (which must be 2 rows or greater for cross-browser support)
const minHeight = lineHeight * this.computedMinRows + offset

// Get the current style height (with `px` units)
const oldHeight = el.style.height || computedStyle.height
// Probe scrollHeight by temporarily changing the height to `auto`
el.style.height = 'auto'
const scrollHeight = el.scrollHeight
// Place the original old height back on the element, just in case this computedProp
// returns the same value as before.
el.style.height = oldHeight

// Calculate content height in "rows" (scrollHeight includes padding but not border)
const contentRows = Math.max((scrollHeight - padding) / lineHeight, 2)
// Calculate number of rows to display (limited within min/max rows)
const rows = Math.min(Math.max(contentRows, this.computedMinRows), this.computedMaxRows)
// Calculate the required height of the textarea including border and padding (in pixels)
const height = Math.max(Math.ceil(rows * lineHeight + offset), minHeight)

// Place old height back on element, just in case this computed prop returns the same value
el.style.height = oldHeight

// Value of previous height (without px units appended)
const oldHeightPx = parseFloat(oldHeight) || 0

if (this.noAutoShrink && oldHeightPx > height) {
// Computed height remains the larger of oldHeight and new height
// When height is `sticky` (no-auto-shrink is true)
// Computed height remains the larger of oldHeight and new height,
// when height is in `sticky` mode (prop `no-auto-shrink` is true)
if (this.noAutoShrink && (parseFloat(oldHeight) || 0) > height) {
return oldHeight
}

// Return the new computed height in px units
// Return the new computed CSS height in px units
return `${height}px`
}
},
Expand Down