Blame view

node_modules/bootstrap-vue/src/components/form-textarea/form-textarea.js 7.76 KB
4cd4fd28   郭伟龙   feat: 初始化项目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import { extend } from '../../vue'
import { NAME_FORM_TEXTAREA } from '../../constants/components'
import { PROP_TYPE_BOOLEAN, PROP_TYPE_NUMBER_STRING, PROP_TYPE_STRING } from '../../constants/props'
import { getCS, getStyle, isVisible, requestAF, setStyle } from '../../utils/dom'
import { isNull } from '../../utils/inspect'
import { mathCeil, mathMax, mathMin } from '../../utils/math'
import { toInteger, toFloat } from '../../utils/number'
import { sortKeys } from '../../utils/object'
import { makeProp, makePropsConfigurable } from '../../utils/props'
import { formControlMixin, props as formControlProps } from '../../mixins/form-control'
import { formSelectionMixin } from '../../mixins/form-selection'
import { formSizeMixin, props as formSizeProps } from '../../mixins/form-size'
import { formStateMixin, props as formStateProps } from '../../mixins/form-state'
import { formTextMixin, props as formTextProps } from '../../mixins/form-text'
import { formValidityMixin } from '../../mixins/form-validity'
import { idMixin, props as idProps } from '../../mixins/id'
import { listenOnRootMixin } from '../../mixins/listen-on-root'
import { listenersMixin } from '../../mixins/listeners'
import { VBVisible } from '../../directives/visible/visible'

// --- Props ---

export const props = makePropsConfigurable(
  sortKeys({
    ...idProps,
    ...formControlProps,
    ...formSizeProps,
    ...formStateProps,
    ...formTextProps,
    maxRows: makeProp(PROP_TYPE_NUMBER_STRING),
    // When in auto resize mode, disable shrinking to content height
    noAutoShrink: makeProp(PROP_TYPE_BOOLEAN, false),
    // Disable the resize handle of textarea
    noResize: makeProp(PROP_TYPE_BOOLEAN, false),
    rows: makeProp(PROP_TYPE_NUMBER_STRING, 2),
    // 'soft', 'hard' or 'off'
    // Browser default is 'soft'
    wrap: makeProp(PROP_TYPE_STRING, 'soft')
  }),
  NAME_FORM_TEXTAREA
)

// --- Main component ---

// @vue/component
export const BFormTextarea = /*#__PURE__*/ extend({
  name: NAME_FORM_TEXTAREA,
  directives: {
    'b-visible': VBVisible
  },
  // Mixin order is important!
  mixins: [
    listenersMixin,
    idMixin,
    listenOnRootMixin,
    formControlMixin,
    formSizeMixin,
    formStateMixin,
    formTextMixin,
    formSelectionMixin,
    formValidityMixin
  ],
  props,
  data() {
    return {
      heightInPx: null
    }
  },
  computed: {
    type() {
      return null
    },
    computedStyle() {
      const styles = {
        // Setting `noResize` to true will disable the ability for the user to
        // manually resize the textarea. We also disable when in auto height mode
        resize: !this.computedRows || this.noResize ? 'none' : null
      }
      if (!this.computedRows) {
        // Conditionally set the computed CSS height when auto rows/height is enabled
        // We avoid setting the style to `null`, which can override user manual resize handle
        styles.height = this.heightInPx
        // We always add a vertical scrollbar to the textarea when auto-height is
        // enabled so that the computed height calculation returns a stable value
        styles.overflowY = 'scroll'
      }
      return styles
    },
    computedMinRows() {
      // 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 mathMax(toInteger(this.rows, 2), 2)
    },
    computedMaxRows() {
      return mathMax(this.computedMinRows, toInteger(this.maxRows, 0))
    },
    computedRows() {
      // This is used to set the attribute 'rows' on the textarea
      // If auto-height is enabled, then we return `null` as we use CSS to control height
      return this.computedMinRows === this.computedMaxRows ? this.computedMinRows : null
    },
    computedAttrs() {
      const { disabled, required } = this

      return {
        id: this.safeId(),
        name: this.name || null,
        form: this.form || null,
        disabled,
        placeholder: this.placeholder || null,
        required,
        autocomplete: this.autocomplete || null,
        readonly: this.readonly || this.plaintext,
        rows: this.computedRows,
        wrap: this.wrap || null,
        'aria-required': this.required ? 'true' : null,
        'aria-invalid': this.computedAriaInvalid
      }
    },
    computedListeners() {
      return {
        ...this.bvListeners,
        input: this.onInput,
        change: this.onChange,
        blur: this.onBlur
      }
    }
  },
  watch: {
    localValue() {
      this.setHeight()
    }
  },
  mounted() {
    this.setHeight()
  },
  methods: {
    // Called by intersection observer directive
    /* istanbul ignore next */
    visibleCallback(visible) {
      if (visible) {
        // We use a `$nextTick()` here just to make sure any
        // transitions or portalling have completed
        this.$nextTick(this.setHeight)
      }
    },
    setHeight() {
      this.$nextTick(() => {
        requestAF(() => {
          this.heightInPx = this.computeHeight()
        })
      })
    },
    /* istanbul ignore next: can't test getComputedStyle in JSDOM */
    computeHeight() {
      if (this.$isServer || !isNull(this.computedRows)) {
        return null
      }

      const el = this.$el

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

      // Get current computed styles
      const computedStyle = getCS(el)
      // Height of one line of text in px
      const lineHeight = toFloat(computedStyle.lineHeight, 1)
      // Calculate height of border and padding
      const border =
        toFloat(computedStyle.borderTopWidth, 0) + toFloat(computedStyle.borderBottomWidth, 0)
      const padding = toFloat(computedStyle.paddingTop, 0) + toFloat(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 = getStyle(el, 'height') || computedStyle.height
      // Probe scrollHeight by temporarily changing the height to `auto`
      setStyle(el, 'height', 'auto')
      const scrollHeight = el.scrollHeight
      // Place the original old height back on the element, just in case `computedProp`
      // returns the same value as before
      setStyle(el, 'height', oldHeight)

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

      // 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 && toFloat(oldHeight, 0) > height) {
        return oldHeight
      }

      // Return the new computed CSS height in px units
      return `${height}px`
    }
  },
  render(h) {
    return h('textarea', {
      class: this.computedClass,
      style: this.computedStyle,
      directives: [
        {
          name: 'b-visible',
          value: this.visibleCallback,
          // If textarea is within 640px of viewport, consider it visible
          modifiers: { '640': true }
        }
      ],
      attrs: this.computedAttrs,
      domProps: { value: this.localValue },
      on: this.computedListeners,
      ref: 'input'
    })
  }
})