Skip to content

Commit

Permalink
feat(form-textarea): add noAutoShrink prop (closes #2664) (#2666)
Browse files Browse the repository at this point in the history
* fix(form-textarea): fix auto-height calculation

* Update form-textarea.js

* tweak comments

* Update form-textarea.js

* lint

* Update form-textarea.js

* Update form-textarea.js

* Update README.md

* add examples to docs

* Update README.md

* minor docs tweaks
  • Loading branch information
jacobmllr95 committed Feb 23, 2019
1 parent 0edbc17 commit a29c40c
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 39 deletions.
98 changes: 80 additions & 18 deletions src/components/form-textarea/README.md
Expand Up @@ -7,14 +7,14 @@
<template>
<div>
<b-form-textarea
id="textarea1"
id="textarea"
v-model="text"
placeholder="Enter something"
placeholder="Enter something..."
rows="3"
max-rows="6"
/>

<pre class="mt-3">{{ text }}</pre>
<pre class="mt-3 mb-0">{{ text }}</pre>
</div>
</template>

Expand All @@ -39,30 +39,30 @@ To control width, place the input inside standard Bootstrap grid column.

```html
<b-container fluid>
<b-row class="my-1">
<b-row>
<b-col sm="2">
<label for="input-small">Small:</label>
<label for="textarea-small">Small:</label>
</b-col>
<b-col sm="10">
<b-form-textarea id="input-small" size="sm" type="text" placeholder="Small Textarea" />
<b-form-textarea id="textarea-small" size="sm" placeholder="Small textarea" />
</b-col>
</b-row>

<b-row class="my-1">
<b-row class="mt-2">
<b-col sm="2">
<label for="input-default">Default:</label>
<label for="textarea-default">Default:</label>
</b-col>
<b-col sm="10">
<b-form-textarea id="input-default" type="text" placeholder="Default Textarea" />
<b-form-textarea id="textarea-default" placeholder="Default textarea" />
</b-col>
</b-row>

<b-row class="my-1">
<b-row class="mt-2">
<b-col sm="2">
<label for="input-large">Large:</label>
<label for="textarea-large">Large:</label>
</b-col>
<b-col sm="10">
<b-form-textarea id="input-large" size="lg" type="text" placeholder="Large Textarea" />
<b-form-textarea id="textarea-large" size="lg" placeholder="Large textarea" />
</b-col>
</b-row>
</b-container>
Expand All @@ -76,24 +76,86 @@ To set the height of `<b-form-textarea>`, set the `rows` prop to the desired num
value is provided to `rows`, then it will default to `2` (the browser default and minimum acceptable
value). Setting it to null or a value below 2 will result in the default of `2` being used.

```html
<div>
<b-form-textarea
id="textarea-rows"
placeholder="Tall textarea"
rows="8"
/>
</div>

<!-- b-form-textarea-rows.vue -->
```

### Disable resize handle

Some web browsers will allow the user to re-size the height of the textarea. To disable this
feature, set the `no-resize` prop to `true`.

```html
<div>
<b-form-textarea
id="textarea-no-resize"
placeholder="Fixed height textarea"
rows="3"
no-resize
/>
</div>

<!-- b-form-textarea-no-resize.vue -->
```

### Auto height

`<b-form-textarea>` can also automatically adjust its height (text rows) to fit the content, even as
the user enters text.
the user enters or deletes text. The height of the textarea will either grow or shrink to fit the
content (grow to a maximum of `max-rows` or shrink to a minimum of `rows`).

To set the initial minimum height (in rows), set the `rows` prop to the desired number of lines (or
leave it at the default of `2`), And then set maximum rows that the text area will grow to (before
showing a scrollbar) by setting the `max-rows` prop to the maximum number of lines of text.

To make the height `sticky` (i.e. never shrink), set the `no-auto-shrink` prop to `true`. The
`no-auto-shrink` props has no effect if `max-rows` is not set or is equal to or less than `rows`.

Note that the resize handle of the textarea (if supported by the browser) will automatically be
disabled in auto-height mode.

## Textarea contextual states
```html
<b-container fluid>
<b-row>
<b-col sm="2">
<label for="textarea-auto-height">Auto height:</label>
</b-col>
<b-col sm="10">
<b-form-textarea
id="textarea-auto-height"
placeholder="Auto height textarea"
rows="3"
max-rows="8" />
</b-col>
</b-row>

<b-row class="mt-2">
<b-col sm="2">
<label for="textarea-no-auto-shrink">No auto-shrink:</label>
</b-col>
<b-col sm="10">
<b-form-textarea
id="textarea-no-auto-shrink"
placeholder="Auto height (no-shrink) textarea"
rows="3"
max-rows="8"
no-auto-shrink />
</b-col>
</b-row>
</b-container>

<!-- b-form-textarea-auto-height.vue -->
```

## Contextual states

Bootstrap includes validation styles for `valid` and `invalid` states on most form controls.

Expand All @@ -115,9 +177,9 @@ To apply one of the contextual state icons on `<b-form-textarea>`, set the `stat
<template>
<div>
<b-form-textarea
id="textarea2"
:state="text.length >= 10"
id="textarea-state"
v-model="text"
:state="text.length >= 10"
placeholder="Enter at least 10 characters"
rows="3"
/>
Expand Down Expand Up @@ -147,7 +209,7 @@ Ensure that an alternative indication of state is also provided. For instance, y
hint about state in the form control's `<label>` text itself, or by providing an additional help
text block.

### ARIA `aria-invalid` attribute
### `aria-invalid` attribute

When `<b-form-textarea>` has an invalid contextual state (i.e. `'invalid'` or `false`) you may also
want to set the prop `aria-invalid` to `true`, or one of the supported values:
Expand All @@ -174,7 +236,7 @@ form field styling and preserve the correct text size, margin, padding and heigh
```html
<template>
<div>
<b-form-textarea id="textarea3" plaintext :value="text" />
<b-form-textarea id="textarea-plaintext" plaintext :value="text" />
</div>
</template>

Expand Down
69 changes: 48 additions & 21 deletions src/components/form-textarea/form-textarea.js
Expand Up @@ -37,6 +37,11 @@ export default {
// Disable the resize handle of textarea
type: Boolean,
default: false
},
noAutoShrink: {
// When in auto resize mode, disable shrinking to content height
type: Boolean,
default: false
}
},
data() {
Expand All @@ -46,41 +51,53 @@ export default {
},
computed: {
computedStyle() {
return {
// setting noResize to true will disable the ability for the user to
// resize the textarea. We also disable when in auto resize mode
resize: !this.computedRows || this.noResize ? 'none' : null,
// The computed height for auto resize
height: this.computedHeight
const styles = {
// Setting `noResize` to true will disable the ability for the user to
// manually resize the textarea. We also disable when in auto resize mode
resize: !this.computedRows || this.noResize ? 'none' : null
}
if (!this.computedRows) {
// The computed height for auto resize.
// We avoid setting the style to null, which can override user manual resize.
styles.height = this.computedHeight
}
return styles
},
computedMinRows() {
// Ensure rows is at least 2 and positive (2 is the native textarea value)
// Ensure rows is at least 2 and positive (2 is the native textarea value).
// A value of 1 can cause issues in some browsers, and most browsers only support
// 2 as the smallest value.
return Math.max(parseInt(this.rows, 10) || 2, 2)
},
computedMaxRows() {
return Math.max(this.computedMinRows, parseInt(this.maxRows, 10) || 0)
},
computedRows() {
// This is used to set the attribute 'rows' on the textarea.
// 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 */ {
const el = this.$el

if (this.isServer) {
return null
}
// We compare this.localValue to null to ensure reactivity of content changes.
if (this.localValue === null || this.computedRows || this.dontResize || this.$isServer) {
// We compare `computedRows` and `localValue` to `true`, a value
// they both can't have at any time, to ensure reactivity
if (
this.$isServer ||
this.dontResize ||
this.computedRows === true ||
this.localValue === true
) {
return null
}

// Element must be visible (not hidden) and in document. *Must* be checked after above.
const el = this.$el

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

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

Expand All @@ -97,15 +114,25 @@ export default {
(parseFloat(computedStyle.paddingTop) || 0) +
(parseFloat(computedStyle.paddingBottom) || 0)
// Calculate content height in "rows"
const contentRows = (el.scrollHeight - offset) / lineHeight
// Put the old height back (needed when new height is equal to old height!)
el.style.height = oldHeight
const contentRows = Math.max((el.scrollHeight - offset) / lineHeight, 2)
// Calculate number of rows to display (limited within min/max rows)
const rows = Math.min(Math.max(contentRows, this.computedMinRows), this.computedMaxRows)
// Calulate the required height of the textarea including border and padding (in pixels)
// Calculate the required height of the textarea including border and padding (in pixels)
const height = Math.max(Math.ceil(rows * lineHeight + offset), minHeight)

// return the new computed height in px units
// 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)
return oldHeight
}

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

0 comments on commit a29c40c

Please sign in to comment.