From 095ac6ead5b03833259a5f8479fb23cba880a2e3 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 27 Oct 2018 16:29:39 -0300 Subject: [PATCH 01/66] [WIP] fix(form-textarea): Bug fixes and features --- src/components/form-textarea/form-textarea.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index 59f0da59051..2994879b2e2 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -2,9 +2,11 @@ import idMixin from '../../mixins/id' import formMixin from '../../mixins/form' import formSizeMixin from '../../mixins/form-size' import formStateMixin from '../../mixins/form-state' +import formSelectionMixin from '../../mixins/form-selection' +import formValidityMixin from '../../mixins/form-validity' export default { - mixins: [idMixin, formMixin, formSizeMixin, formStateMixin], + mixins: [idMixin, formMixin, formSizeMixin, formStateMixin, formSelectionMixin, formValidityMixin], render (h) { return h('textarea', { ref: 'input', @@ -146,6 +148,12 @@ export default { if (!this.disabled) { this.$el.focus() } + }, + blur () { + // For external handler that may want a blur method + if (!this.disabled) { + this.$el.blur() + } } } } From 7c69b2cca8dc01bea51e0775424164b250884228 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 27 Oct 2018 22:49:13 -0300 Subject: [PATCH 02/66] Add better auto rows re-size capabilities Also imported all native event listeners --- src/components/form-textarea/form-textarea.js | 161 +++++++++++++----- 1 file changed, 121 insertions(+), 40 deletions(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index 2994879b2e2..d7496d4ed2a 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -4,48 +4,62 @@ import formSizeMixin from '../../mixins/form-size' import formStateMixin from '../../mixins/form-state' import formSelectionMixin from '../../mixins/form-selection' import formValidityMixin from '../../mixins/form-validity' +import { getCS, isVisible } from '../../utils/dom' + +// Event to use for v-model updates +const MODEL_EVENT = 'update:value' export default { mixins: [idMixin, formMixin, formSizeMixin, formStateMixin, formSelectionMixin, formValidityMixin], render (h) { + const self = this return h('textarea', { ref: 'input', - class: this.inputClass, - style: this.inputStyle, + class: self.computedClass, + style: self.computedStyle, directives: [ { name: 'model', rawName: 'v-model', - value: this.localValue, + value: self.localValue, expression: 'localValue' } ], - domProps: { value: this.value }, attrs: { - id: this.safeId(), - name: this.name, - disabled: this.disabled, - placeholder: this.placeholder, - required: this.required, - autocomplete: this.autocomplete || null, - readonly: this.readonly || this.plaintext, - rows: this.rowsCount, - wrap: this.wrap || null, - 'aria-required': this.required ? 'true' : null, - 'aria-invalid': this.computedAriaInvalid + id: self.safeId(), + name: self.name, + disabled: self.disabled, + placeholder: self.placeholder, + required: self.required, + autocomplete: self.autocomplete || null, + readonly: self.readonly || self.plaintext, + // We use styles to control teh height + rows: self.computedMinRows === self.computedMaxRows ? self.computedMinRows : null, + wrap: self.wrap || null, + 'aria-required': self.required ? 'true' : null, + 'aria-invalid': self.computedAriaInvalid }, + domProps: { value: self.value }, on: { - input: (evt) => { - this.localValue = evt.target.value - } + ...self.$listeners, + input: self.onInput, + change: self.onChange } }) }, data () { return { - localValue: this.value + // We use the '==' operator here so that undefined will also equal null + // to ensure that value is always a string + localValue: this.value == null ? '' : String(this.value), + // If we cannot auto resize height + dontResize: true, } }, + model: { + prop: 'value', + event: MODEL_EVENT + }, props: { value: { type: String, @@ -73,7 +87,7 @@ export default { }, rows: { type: [Number, String], - default: null + default: 2 }, maxRows: { type: [Number, String], @@ -85,34 +99,36 @@ export default { default: 'soft' }, noResize: { + // Use CSS to disable the resize handle of textarea type: Boolean, default: false } }, + mounted () { + this.$nextTick(() => { this.dontResize = false }) + }, + activated () { + this.$nextTick(() => { this.dontResize = false }) + }, + dectivated () { + // If we are in a deactivated , dont try resizing + this.dontResize = true + }, computed: { - rowsCount () { - // A better option could be based on https://codepen.io/vsync/pen/frudD - // As linebreaks aren't added until the input is submitted - const rows = parseInt(this.rows, 10) || 1 - const maxRows = parseInt(this.maxRows, 10) || 0 - const lines = (this.localValue || '').toString().split('\n').length - return maxRows - ? Math.min(maxRows, Math.max(rows, lines)) - : Math.max(rows, lines) - }, - inputClass () { + computedClass () { return [ this.plaintext ? 'form-control-plaintext' : 'form-control', this.sizeFormClass, this.stateClass ] }, - inputStyle () { - // We set width 100% in plaintext mode to get around a shortcoming in bootstrap CSS - // setting noResize to true will disable the ability for the user to resize the textarea + computedStyle () { return { - width: this.plaintext ? '100%' : null, - resize: this.noResize ? 'none' : null + // 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.rowsMin !== this.rowsMax) || this.noResize ? 'none' : null, + // THe computed height for auto resize + height: this.computedHeight } }, computedAriaInvalid () { @@ -126,23 +142,88 @@ export default { } // Most likely a string value (which could be the string 'true') return this.ariaInvalid + }, + computedMinRows: function () { + // Ensure rows is at least 1 and positive + return Math.max(parseInt(this.rows, 10) || 1, 1) + }, + computedMaxRows: function () { + return Math.max(this.computedMinRows, parseInt(this.maxRows, 10) || 0) + }, + computedHeight () { + const el = this.$el + + if (this.$isServer || this.computedMinRows === this.computedMaxRows || !el) { + return null + } + + // We compare this.localValue to null to ensure reactivity with content changes. + // Element visibility *must* be checked last. + if (this.localValue === null || this.dontResize || !isVisible(el)) { + return null + } + + // Remember old height and reset it temporarily + const oldHeight = el.style.height + el.style.height = 'auto' + // el.style.height = 'inherit' + + // 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 = (parseFloat(computedStyle.borderTopWidth) || 0) + + (parseFloat(computedStyle.borderBottomWidth) || 0) + + (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 + // Calculate number of rows to display + const rows = Math.min(Math.max(contentRows, this.computedMinRows)), this.computedMaxRows) + + // return the new computed height in px units + return `${Math.max(Math.ceil((rows * lineHeight) + offset), minHeight)}px` } }, watch: { value (newVal, oldVal) { // Update our localValue if (newVal !== oldVal) { - this.localValue = newVal + // We use the '==' operator here so that undefined will also = null + // To ensure that value is always a string + this.localValue = newVal == null ? '' : String(newVal) } }, - localValue (newVal, oldVal) { - // update Parent value + computedRows (newVal, oldVal) { if (newVal !== oldVal) { - this.$emit('input', newVal) + // if computed rows is truthy, we disable auto resizing + this.canResize = newVal ? false : true } } }, methods: { + setValue(val) { + if (this.localValue !== val) { + // Update the v-model only if value has changed + this.localValue = val + this.$emit(MODEL_EVENT, this.localValue) + } + }, + onInput (evt) { + const val = evt.target.value + this.setValue(val) + this.$emit('input', val, evt) + }, + onChange (evt) { + const val = evt.target.value + this.setValue(val) + this.$emit('change', val, evt) + }, focus () { // For external handler that may want a focus method if (!this.disabled) { From 70f4e778efec0bb833d5eb1e917c76a8c0b486e7 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 27 Oct 2018 22:52:46 -0300 Subject: [PATCH 03/66] lint --- src/components/form-textarea/form-textarea.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index d7496d4ed2a..a41c34024fc 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -184,7 +184,7 @@ export default { // Put the old height back (needed when new height is equal to old height!) el.style.height = oldHeight // Calculate number of rows to display - const rows = Math.min(Math.max(contentRows, this.computedMinRows)), this.computedMaxRows) + const rows = Math.min(Math.max(contentRows, this.computedMinRows), this.computedMaxRows) // return the new computed height in px units return `${Math.max(Math.ceil((rows * lineHeight) + offset), minHeight)}px` From edcfe2c7723e20772da6b8e481c988d434ac9435 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 27 Oct 2018 23:04:00 -0300 Subject: [PATCH 04/66] lint --- src/components/form-textarea/form-textarea.js | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index a41c34024fc..95185e124f2 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -33,8 +33,7 @@ export default { required: self.required, autocomplete: self.autocomplete || null, readonly: self.readonly || self.plaintext, - // We use styles to control teh height - rows: self.computedMinRows === self.computedMaxRows ? self.computedMinRows : null, + rows: self.computedRows, wrap: self.wrap || null, 'aria-required': self.required ? 'true' : null, 'aria-invalid': self.computedAriaInvalid @@ -53,7 +52,7 @@ export default { // to ensure that value is always a string localValue: this.value == null ? '' : String(this.value), // If we cannot auto resize height - dontResize: true, + dontResize: true } }, model: { @@ -128,7 +127,7 @@ export default { // resize the textarea. We also disable when in auto resize mode resize: (this.rowsMin !== this.rowsMax) || this.noResize ? 'none' : null, // THe computed height for auto resize - height: this.computedHeight + height: this.computedHeight } }, computedAriaInvalid () { @@ -143,16 +142,19 @@ export default { // Most likely a string value (which could be the string 'true') return this.ariaInvalid }, - computedMinRows: function () { + computedMinRows () { // Ensure rows is at least 1 and positive return Math.max(parseInt(this.rows, 10) || 1, 1) }, - computedMaxRows: function () { + computedMaxRows () { return Math.max(this.computedMinRows, parseInt(this.maxRows, 10) || 0) }, + computedRows () { + return this.computedMinRows === this.computedMaxRows ? this.computedMinRows : null + }, computedHeight () { const el = this.$el - + if (this.$isServer || this.computedMinRows === this.computedMaxRows || !el) { return null } @@ -175,10 +177,10 @@ export default { // Minimum height for min rows (browser dependant) const minHeight = parseInt(computedStyle.height, 10) || (lineHeight * this.computedMinRows) // Calculate height of content - const offset = (parseFloat(computedStyle.borderTopWidth) || 0) - + (parseFloat(computedStyle.borderBottomWidth) || 0) - + (parseFloat(computedStyle.paddingTop) || 0) - + (parseFloat(computedStyle.paddingBottom) || 0) + const offset = (parseFloat(computedStyle.borderTopWidth) || 0) + + (parseFloat(computedStyle.borderBottomWidth) || 0) + + (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!) @@ -202,12 +204,12 @@ export default { computedRows (newVal, oldVal) { if (newVal !== oldVal) { // if computed rows is truthy, we disable auto resizing - this.canResize = newVal ? false : true + this.dontResize = Boolean(newVal) } } }, methods: { - setValue(val) { + setValue (val) { if (this.localValue !== val) { // Update the v-model only if value has changed this.localValue = val From 3a37afa4a6ceeedc45dae8c060a8b493ac40cfb1 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 27 Oct 2018 23:30:48 -0300 Subject: [PATCH 05/66] Update form-textarea.js --- src/components/form-textarea/form-textarea.js | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index 95185e124f2..bca7eb62970 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -103,6 +103,16 @@ export default { default: false } }, + watch: { + value (newVal, oldVal) { + // Update our localValue + if (newVal !== oldVal) { + // We use the '==' operator here so that undefined will also = null + // To ensure that value is always a string + this.localValue = newVal == null ? '' : String(newVal) + } + } + }, mounted () { this.$nextTick(() => { this.dontResize = false }) }, @@ -125,8 +135,8 @@ export default { 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.rowsMin !== this.rowsMax) || this.noResize ? 'none' : null, - // THe computed height for auto resize + resize: (this.computedRows || this.noResize) ? 'none' : null, + // The computed height for auto resize height: this.computedHeight } }, @@ -155,7 +165,7 @@ export default { computedHeight () { const el = this.$el - if (this.$isServer || this.computedMinRows === this.computedMaxRows || !el) { + if (this.$isServer || this.computedRows || !el) { return null } @@ -192,22 +202,6 @@ export default { return `${Math.max(Math.ceil((rows * lineHeight) + offset), minHeight)}px` } }, - watch: { - value (newVal, oldVal) { - // Update our localValue - if (newVal !== oldVal) { - // We use the '==' operator here so that undefined will also = null - // To ensure that value is always a string - this.localValue = newVal == null ? '' : String(newVal) - } - }, - computedRows (newVal, oldVal) { - if (newVal !== oldVal) { - // if computed rows is truthy, we disable auto resizing - this.dontResize = Boolean(newVal) - } - } - }, methods: { setValue (val) { if (this.localValue !== val) { From 3ed45f768bfe03f315aa95369b90f9abb7270179 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sat, 27 Oct 2018 23:39:22 -0300 Subject: [PATCH 06/66] Update form-textarea.js --- src/components/form-textarea/form-textarea.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index bca7eb62970..465cd64c8bc 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -135,7 +135,7 @@ export default { 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, + resize: (!this.computedRows || this.noResize) ? 'none' : null, // The computed height for auto resize height: this.computedHeight } @@ -165,7 +165,7 @@ export default { computedHeight () { const el = this.$el - if (this.$isServer || this.computedRows || !el) { + if (this.$isServer || !this.computedRows || !el) { return null } From 449d3c534dc5c009a23bca6e989db466e9364c22 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 00:16:20 -0300 Subject: [PATCH 07/66] Update form-textarea.js --- src/components/form-textarea/form-textarea.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index 465cd64c8bc..c0f7c20f2cf 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -177,8 +177,8 @@ export default { // Remember old height and reset it temporarily const oldHeight = el.style.height - el.style.height = 'auto' - // el.style.height = 'inherit' + // el.style.height = 'auto' + el.style.height = 'inherit' // Get current computed styles const computedStyle = getCS(el) @@ -211,11 +211,13 @@ export default { } }, onInput (evt) { + if (evt.target.isComposing) return const val = evt.target.value this.setValue(val) this.$emit('input', val, evt) }, onChange (evt) { + if (evt.target.isComposing) return const val = evt.target.value this.setValue(val) this.$emit('change', val, evt) From 1e7be124f18fe056062ea76d4ce60d2afd4ff38c Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 00:51:52 -0300 Subject: [PATCH 08/66] Update form-textarea.js --- src/components/form-textarea/form-textarea.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index c0f7c20f2cf..46fdd543471 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -165,13 +165,13 @@ export default { computedHeight () { const el = this.$el - if (this.$isServer || !this.computedRows || !el) { + // We compare this.localValue to null to ensure reactivity with content changes. + if (this.localValue === null || this.computedRows || this.dontResize || this.$isServer) { return null } - // We compare this.localValue to null to ensure reactivity with content changes. // Element visibility *must* be checked last. - if (this.localValue === null || this.dontResize || !isVisible(el)) { + if (!isVisible(el)) { return null } @@ -211,13 +211,13 @@ export default { } }, onInput (evt) { - if (evt.target.isComposing) return + if (evt.target.composing) return const val = evt.target.value this.setValue(val) this.$emit('input', val, evt) }, onChange (evt) { - if (evt.target.isComposing) return + if (evt.target.composing) return const val = evt.target.value this.setValue(val) this.$emit('change', val, evt) From 8284093be20719f336e1767945b7a534867d09a0 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 01:03:20 -0300 Subject: [PATCH 09/66] Update form-textarea.js --- src/components/form-textarea/form-textarea.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index 46fdd543471..b63066c9150 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -166,11 +166,11 @@ export default { const el = this.$el // We compare this.localValue to null to ensure reactivity with content changes. - if (this.localValue === null || this.computedRows || this.dontResize || this.$isServer) { + if (this.localValue === null || this.computedRows || this.dontResize || this.$isServer) { return null } - // Element visibility *must* be checked last. + // Element must be visible (not hidden) and in document. *Must* be checked after above. if (!isVisible(el)) { return null } From 4f4d25ad7a6964828f594c6bc53555a24f726208 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 02:41:22 -0300 Subject: [PATCH 10/66] Update form-textarea.js --- src/components/form-textarea/form-textarea.js | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index b63066c9150..45ac5827335 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -6,12 +6,10 @@ import formSelectionMixin from '../../mixins/form-selection' import formValidityMixin from '../../mixins/form-validity' import { getCS, isVisible } from '../../utils/dom' -// Event to use for v-model updates -const MODEL_EVENT = 'update:value' - export default { mixins: [idMixin, formMixin, formSizeMixin, formStateMixin, formSelectionMixin, formValidityMixin], render (h) { + // using self instead of this helps reduce code size during minification const self = this return h('textarea', { ref: 'input', @@ -38,7 +36,9 @@ export default { 'aria-required': self.required ? 'true' : null, 'aria-invalid': self.computedAriaInvalid }, - domProps: { value: self.value }, + domProps: { + value: self.value + }, on: { ...self.$listeners, input: self.onInput, @@ -57,7 +57,7 @@ export default { }, model: { prop: 'value', - event: MODEL_EVENT + event: 'update' }, props: { value: { @@ -153,8 +153,8 @@ export default { return this.ariaInvalid }, computedMinRows () { - // Ensure rows is at least 1 and positive - return Math.max(parseInt(this.rows, 10) || 1, 1) + // Ensure rows is at least 2 and positive (2 is the native textarea value) + return Math.max(parseInt(this.rows, 10) || 2, 2) }, computedMaxRows () { return Math.max(this.computedMinRows, parseInt(this.maxRows, 10) || 0) @@ -203,24 +203,24 @@ export default { } }, methods: { - setValue (val) { + updateValue (val) { if (this.localValue !== val) { // Update the v-model only if value has changed this.localValue = val - this.$emit(MODEL_EVENT, this.localValue) + this.$emit('update', this.localValue) } }, onInput (evt) { + this.$emit('input', evt) if (evt.target.composing) return - const val = evt.target.value - this.setValue(val) - this.$emit('input', val, evt) + if (evt.defaultPrevented) return + this.updateValue(evt.target.value) }, onChange (evt) { + this.$emit('change', evt) if (evt.target.composing) return - const val = evt.target.value - this.setValue(val) - this.$emit('change', val, evt) + if (evt.defaultPrevented) return + this.updateValue(evt.target.value) }, focus () { // For external handler that may want a focus method From 24193f0296fe57585345a36ce56eecd331e11ee4 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 03:02:24 -0300 Subject: [PATCH 11/66] Update package.json Document events --- src/components/form-textarea/package.json | 58 ++++++++++++++++------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/src/components/form-textarea/package.json b/src/components/form-textarea/package.json index 13e92eccd40..a59d8b196a3 100644 --- a/src/components/form-textarea/package.json +++ b/src/components/form-textarea/package.json @@ -1,20 +1,44 @@ { - "name": "@bootstrap-vue/form-textarea", - "version": "1.0.0", - "meta": { - "title": "Form Textarea", - "component": "bFormTextarea", - "events": [ - { - "event": "input", - "description": "Emitted when the textarea receives input from user.", - "args": [ - { - "arg": "value", - "description": "current value of the textarea" - } - ] - } + "name": "@bootstrap-vue/form-textarea", + "version": "1.0.0", + "meta": { + "title": "Form Textarea", + "component": "bFormTextarea", + "events": [ + { + "event": "input", + "description": "Native input event triggered by user interaction. Emitted before any formatting and before the v-model is updated. Cancelable.", + "args": [ + { + "arg": "event", + "description": "Native input event object" + } ] - } + }, + { + "event": "change", + "description": "Change event triggerd by user interaction. Emitted after any formatting and after the v-model is updated.", + "args": [ + { + "arg": "value", + "description": "the value of the input" + }, + { + "arg": "event", + "description": "Native change event object (from before any formatting)" + } + ] + }, + { + "event": "update", + "description": "Emitted to update the v-model", + "args": [ + { + "arg": "value", + "description": "Value of input, after any formatting" + } + ] + } + ] + } } From e838d339d420d23a03ccdd5b21c97e3aab308697 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 04:17:56 -0300 Subject: [PATCH 12/66] Ajust event $emit ordering --- src/components/form-textarea/form-textarea.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index 45ac5827335..5b6edadbc44 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -109,18 +109,20 @@ export default { if (newVal !== oldVal) { // We use the '==' operator here so that undefined will also = null // To ensure that value is always a string - this.localValue = newVal == null ? '' : String(newVal) + this.updateValue(newVal == null ? '' : String(newVal)) } } }, mounted () { + // Enable opt-in resizing once mounted this.$nextTick(() => { this.dontResize = false }) }, activated () { + // If we are being re-activated in , enable opt-in resizing this.$nextTick(() => { this.dontResize = false }) }, dectivated () { - // If we are in a deactivated , dont try resizing + // If we are in a deactivated , disable opt-in resizing this.dontResize = true }, computed: { @@ -177,8 +179,7 @@ export default { // Remember old height and reset it temporarily const oldHeight = el.style.height - // el.style.height = 'auto' - el.style.height = 'inherit' + el.style.height = 'auto' // Get current computed styles const computedStyle = getCS(el) @@ -195,11 +196,13 @@ export default { const contentRows = (el.scrollHeight - offset) / lineHeight // Put the old height back (needed when new height is equal to old height!) el.style.height = oldHeight - // Calculate number of rows to display + // 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) + const height = Math.max(Math.ceil((rows * lineHeight) + offset), minHeight) // return the new computed height in px units - return `${Math.max(Math.ceil((rows * lineHeight) + offset), minHeight)}px` + return `${height}px` } }, methods: { @@ -212,15 +215,12 @@ export default { }, onInput (evt) { this.$emit('input', evt) - if (evt.target.composing) return if (evt.defaultPrevented) return this.updateValue(evt.target.value) }, onChange (evt) { - this.$emit('change', evt) - if (evt.target.composing) return - if (evt.defaultPrevented) return this.updateValue(evt.target.value) + this.$emit('change', this.localValue, evt) }, focus () { // For external handler that may want a focus method From 8c1ee386bf068fbfe125b9e8f9d1a648d76b7ed2 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 04:45:15 -0300 Subject: [PATCH 13/66] Create form-formatting mixin For use with b-form-textarea and b-form-input --- src/mixins/form-formatting.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/mixins/form-formatting.js diff --git a/src/mixins/form-formatting.js b/src/mixins/form-formatting.js new file mode 100644 index 00000000000..ddd29928000 --- /dev/null +++ b/src/mixins/form-formatting.js @@ -0,0 +1,18 @@ +export default { + props: { + formatter: { + type: Function, + value: null + }, + lazyFormatter: { + type: Boolean, + value: false + } + }, + methods: { + getFormatted (value, event = null) { + value = value == null ? '' : String(value) + return this.formatter ? this.formatter(value, event) : value + } + } +} From ab9990ad580d5f61acfc63ad1007ed1e3d415274 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 05:00:48 -0300 Subject: [PATCH 14/66] Add stringify method for mixin --- src/mixins/form-formatting.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mixins/form-formatting.js b/src/mixins/form-formatting.js index ddd29928000..4a38d4d223d 100644 --- a/src/mixins/form-formatting.js +++ b/src/mixins/form-formatting.js @@ -11,8 +11,11 @@ export default { }, methods: { getFormatted (value, event = null) { - value = value == null ? '' : String(value) + value = this.stringifyValue(value) return this.formatter ? this.formatter(value, event) : value + }, + stringifyValue (value) { + return value == null ? '' : String(value) } } } From 2cbbade0834a7b9b7729ec3ba1371fd81ff1d0e4 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 06:33:34 -0300 Subject: [PATCH 15/66] Update package.json --- src/components/form-textarea/package.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/form-textarea/package.json b/src/components/form-textarea/package.json index a59d8b196a3..15bf0d1311b 100644 --- a/src/components/form-textarea/package.json +++ b/src/components/form-textarea/package.json @@ -7,8 +7,12 @@ "events": [ { "event": "input", - "description": "Native input event triggered by user interaction. Emitted before any formatting and before the v-model is updated. Cancelable.", + "description": "Input event triggered by user interaction. Emitted before any formatting and before the v-model is updated. Cancelable.", "args": [ + { + "arg": "value", + "description": "Current value of input" + }, { "arg": "event", "description": "Native input event object" @@ -25,7 +29,7 @@ }, { "arg": "event", - "description": "Native change event object (from before any formatting)" + "description": "Native change event object" } ] }, From b04338f1fa0d3eb0f12ecdd2b4e22ca1acee1374 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 06:53:55 -0300 Subject: [PATCH 16/66] Update and rename form-formatting.js to form-text,js Move common b-form-textarea/b-form-input data, props, computed, methods, watchers and lifecycle hooks to mixin --- src/mixins/form-formatting.js | 21 ------- src/mixins/form-text,js | 114 ++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 21 deletions(-) delete mode 100644 src/mixins/form-formatting.js create mode 100644 src/mixins/form-text,js diff --git a/src/mixins/form-formatting.js b/src/mixins/form-formatting.js deleted file mode 100644 index 4a38d4d223d..00000000000 --- a/src/mixins/form-formatting.js +++ /dev/null @@ -1,21 +0,0 @@ -export default { - props: { - formatter: { - type: Function, - value: null - }, - lazyFormatter: { - type: Boolean, - value: false - } - }, - methods: { - getFormatted (value, event = null) { - value = this.stringifyValue(value) - return this.formatter ? this.formatter(value, event) : value - }, - stringifyValue (value) { - return value == null ? '' : String(value) - } - } -} diff --git a/src/mixins/form-text,js b/src/mixins/form-text,js new file mode 100644 index 00000000000..42316df711a --- /dev/null +++ b/src/mixins/form-text,js @@ -0,0 +1,114 @@ +export default { + props: { + value: { + type: String, + default: '' + }, + ariaInvalid: { + type: [Boolean, String], + default: false + }, + readonly: { + type: Boolean, + default: false + }, + plaintext: { + type: Boolean, + default: false + }, + autocomplete: { + type: String, + default: null + }, + placeholder: { + type: String, + default: null + }, + formatter: { + type: Function, + value: null + }, + lazyFormatter: { + type: Boolean, + value: false + } + }, + model: { + prop: 'value', + event: 'update' + }, + data () { + return { + localValue: this.stringifyValue(this.value) + } + }, + watch: { + value (newVal, oldVal) { + this.setValue(this.lazyFormatter ? newVal : this.getFormatted(newVal, null)) + } + }, + mounted () { + this.setValue(this.lazyFormatter ? this.value : this.getFormatted(this.value, null)) + }, + computed: { + computedClass () { + return [ + this.plaintext ? 'form-control-plaintext' : 'form-control', + this.sizeFormClass, + this.stateClass + ] + }, + computedAriaInvalid () { + if (!this.ariaInvalid || this.ariaInvalid === 'false') { + // this.ariaInvalid is null or false or 'false' + return this.computedState === false ? 'true' : null + } + if (this.ariaInvalid === true) { + // User wants explicit aria-invalid=true + return 'true' + } + // Most likely a string value (which could be the string 'true') + return this.ariaInvalid + } + }, + methods: { + stringifyValue (value) { + // We use == so that undefined will also equal null + return value == null ? '' : String(value) + }, + getFormatted (value, event = null) { + value = this.stringifyValue(value) + return this.formatter ? this.formatter(value, event) : value + }, + setValue (value) { + value = this.stringifyValue(value) + if (this.localValue !== value) { + // Update the v-model only if value has changed + this.localValue = value + this.$emit('update', value) + } + }, + onInput (evt) { + this.$emit('input', evt.target.value, evt) + if (evt.defaultPrevented) return + const value = evt.target.value + this.updateValue(this.lazyFormatter ? value : this.getFormatted(value, evt)) + }, + onChange (evt) { + this.updateValue(this.getFormatted(evt.target.value, evt)) + this.$emit('change', this.localValue, evt) } + }, + focus () { + // For external handler that may want a focus method + if (!this.disabled) { + this.$el.focus() + } + }, + blur () { + // For external handler that may want a blur method + if (!this.disabled) { + this.$el.blur() + } + } + } +} From 70f72e9835144e02c86c52c6a78f56edd5bb7c5f Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 06:55:24 -0300 Subject: [PATCH 17/66] Add in formatter features Migrate common props/data/methods/computed/hooks to mixin --- src/components/form-textarea/form-textarea.js | 108 +++--------------- 1 file changed, 13 insertions(+), 95 deletions(-) diff --git a/src/components/form-textarea/form-textarea.js b/src/components/form-textarea/form-textarea.js index 5b6edadbc44..59e30bb94a3 100644 --- a/src/components/form-textarea/form-textarea.js +++ b/src/components/form-textarea/form-textarea.js @@ -2,12 +2,21 @@ import idMixin from '../../mixins/id' import formMixin from '../../mixins/form' import formSizeMixin from '../../mixins/form-size' import formStateMixin from '../../mixins/form-state' +import formTextMixin from '../../mixins/form-text' import formSelectionMixin from '../../mixins/form-selection' import formValidityMixin from '../../mixins/form-validity' import { getCS, isVisible } from '../../utils/dom' export default { - mixins: [idMixin, formMixin, formSizeMixin, formStateMixin, formSelectionMixin, formValidityMixin], + mixins: [ + idMixin, + formMixin, + formSizeMixin, + formStateMixin, + formTextMixin, + formSelectionMixin, + formValidityMixin + ], render (h) { // using self instead of this helps reduce code size during minification const self = this @@ -37,7 +46,7 @@ export default { 'aria-invalid': self.computedAriaInvalid }, domProps: { - value: self.value + value: self.localValue }, on: { ...self.$listeners, @@ -48,42 +57,10 @@ export default { }, data () { return { - // We use the '==' operator here so that undefined will also equal null - // to ensure that value is always a string - localValue: this.value == null ? '' : String(this.value), - // If we cannot auto resize height dontResize: true } }, - model: { - prop: 'value', - event: 'update' - }, props: { - value: { - type: String, - default: '' - }, - ariaInvalid: { - type: [Boolean, String], - default: false - }, - readonly: { - type: Boolean, - default: false - }, - plaintext: { - type: Boolean, - default: false - }, - autocomplete: { - type: String, - default: null - }, - placeholder: { - type: String, - default: null - }, rows: { type: [Number, String], default: 2 @@ -98,21 +75,11 @@ export default { default: 'soft' }, noResize: { - // Use CSS to disable the resize handle of textarea + // Disable the resize handle of textarea type: Boolean, default: false } }, - watch: { - value (newVal, oldVal) { - // Update our localValue - if (newVal !== oldVal) { - // We use the '==' operator here so that undefined will also = null - // To ensure that value is always a string - this.updateValue(newVal == null ? '' : String(newVal)) - } - } - }, mounted () { // Enable opt-in resizing once mounted this.$nextTick(() => { this.dontResize = false }) @@ -126,13 +93,6 @@ export default { this.dontResize = true }, computed: { - computedClass () { - return [ - this.plaintext ? 'form-control-plaintext' : 'form-control', - this.sizeFormClass, - this.stateClass - ] - }, computedStyle () { return { // setting noResize to true will disable the ability for the user to @@ -142,18 +102,6 @@ export default { height: this.computedHeight } }, - computedAriaInvalid () { - if (!this.ariaInvalid || this.ariaInvalid === 'false') { - // this.ariaInvalid is null or false or 'false' - return this.computedState === false ? 'true' : null - } - if (this.ariaInvalid === true) { - // User wants explicit aria-invalid=true - return 'true' - } - // Most likely a string value (which could be the string 'true') - return this.ariaInvalid - }, computedMinRows () { // Ensure rows is at least 2 and positive (2 is the native textarea value) return Math.max(parseInt(this.rows, 10) || 2, 2) @@ -167,7 +115,7 @@ export default { computedHeight () { const el = this.$el - // We compare this.localValue to null to ensure reactivity with content changes. + // We compare this.localValue to null to ensure reactivity of content changes. if (this.localValue === null || this.computedRows || this.dontResize || this.$isServer) { return null } @@ -204,35 +152,5 @@ export default { // return the new computed height in px units return `${height}px` } - }, - methods: { - updateValue (val) { - if (this.localValue !== val) { - // Update the v-model only if value has changed - this.localValue = val - this.$emit('update', this.localValue) - } - }, - onInput (evt) { - this.$emit('input', evt) - if (evt.defaultPrevented) return - this.updateValue(evt.target.value) - }, - onChange (evt) { - this.updateValue(evt.target.value) - this.$emit('change', this.localValue, evt) - }, - focus () { - // For external handler that may want a focus method - if (!this.disabled) { - this.$el.focus() - } - }, - blur () { - // For external handler that may want a blur method - if (!this.disabled) { - this.$el.blur() - } - } } } From e73803508b035206c8f16d4b2980fe731dc04a73 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 06:56:53 -0300 Subject: [PATCH 18/66] Rename form-text,js to form-text.js --- src/mixins/{form-text,js => form-text.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/mixins/{form-text,js => form-text.js} (100%) diff --git a/src/mixins/form-text,js b/src/mixins/form-text.js similarity index 100% rename from src/mixins/form-text,js rename to src/mixins/form-text.js From b74da4e6b187d04009071eb184f1ccd7abecca34 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 07:01:46 -0300 Subject: [PATCH 19/66] Update form-text.js --- src/mixins/form-text.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mixins/form-text.js b/src/mixins/form-text.js index 42316df711a..f798ed84ec4 100644 --- a/src/mixins/form-text.js +++ b/src/mixins/form-text.js @@ -96,7 +96,7 @@ export default { }, onChange (evt) { this.updateValue(this.getFormatted(evt.target.value, evt)) - this.$emit('change', this.localValue, evt) } + this.$emit('change', this.localValue, evt) }, focus () { // For external handler that may want a focus method From 5cddfba7e4d45c36c475b6fd0eebde2f4f408eb9 Mon Sep 17 00:00:00 2001 From: Troy Morehouse Date: Sun, 28 Oct 2018 07:43:10 -0300 Subject: [PATCH 20/66] Update README.md --- src/components/form-textarea/README.md | 97 +++++++++++++++++++++----- 1 file changed, 78 insertions(+), 19 deletions(-) diff --git a/src/components/form-textarea/README.md b/src/components/form-textarea/README.md index 0c3dc957d29..69cd747e707 100644 --- a/src/components/form-textarea/README.md +++ b/src/components/form-textarea/README.md @@ -30,29 +30,28 @@ export default { ``` ## Displayed rows +To set the height of ``, set the `rows` prop to the desired number of +rows. If no 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 two will +result in the devault of `2` being used. -`` automaticlly adjusts its height (text rows) to fit the content, -even as the user enters text. For a textarea with no content, the number of rows -starts at `1`. +### Disable resize handle +Some web browsers will allow the user to re-size the hight of the textarea. +To disable this feature, set the `no-resize` prop to `true`. + + +### Auto height +`` can also automaticlly adjust its height (text rows) to fit the content, +even as the user enters text. To set the initial minimum height (in rows), set the `rows` prop to the desired -number of lines. If no value is provided to `rows`, then it will default to `1`. +number of lines (or leave it at hte default of `2`). To limit the maximum rows that the text area will grow to (before showing a scrollbar), set the `max-rows` prop to the maximum number of lines of text. -To keep the text-area at a set height, set both `rows` and `max-rows` to the same value. - -**Note:** auto rows will only work when the user explicitly enters newlines in the textarea. - -### Disable resize - -Note that some web browsers will allow the user to re-size the hight of the textarea. -To disable this, set the `no-resize` prop to `true`. - - -## Contextual states +## Textarea contextual states Bootstrap includes validation styles for `valid` and `invalid` states on most form controls. Generally speaking, you’ll want to use a particular state for specific types of feedback: @@ -88,7 +87,6 @@ export default { ``` ### Conveying contextual state to assistive technologies and colorblind users - Using these contextual states to denote the state of a form control only provides a visual, color-based indication, which will not be conveyed to users of assistive technologies - such as screen readers - or to colorblind users. @@ -98,11 +96,10 @@ include a hint about state in the form control's `