@gitlab/ui 132.2.1 → 133.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,241 +0,0 @@
1
- import { extend } from '../../vue'
2
- import { NAME_FORM_TEXTAREA } from '../../constants/components'
3
- import { getCS, getStyle, isVisible, requestAF, setStyle } from '../../utils/dom'
4
- import { isNull } from '../../utils/inspect'
5
- import { mathCeil, mathMax, mathMin } from '../../utils/math'
6
- import { toInteger, toFloat } from '../../utils/number'
7
- import { sortKeys } from '../../utils/object'
8
- import { formControlMixin, props as formControlProps } from '../../mixins/form-control'
9
- import { formSelectionMixin } from '../../mixins/form-selection'
10
- import { formSizeMixin, props as formSizeProps } from '../../mixins/form-size'
11
- import { formStateMixin, props as formStateProps } from '../../mixins/form-state'
12
- import { formTextMixin, props as formTextProps } from '../../mixins/form-text'
13
- import { formValidityMixin } from '../../mixins/form-validity'
14
- import { idMixin, props as idProps } from '../../mixins/id'
15
- import { listenOnRootMixin } from '../../mixins/listen-on-root'
16
- import { listenersMixin } from '../../mixins/listeners'
17
- import { VBVisible } from '../../directives/visible/visible'
18
-
19
- // --- Props ---
20
-
21
- export const props = sortKeys({
22
- ...idProps,
23
- ...formControlProps,
24
- ...formSizeProps,
25
- ...formStateProps,
26
- ...formTextProps,
27
- maxRows: {
28
- type: [Number, String],
29
- required: false,
30
- default: undefined
31
- },
32
- // When in auto resize mode, disable shrinking to content height
33
- noAutoShrink: {
34
- type: Boolean,
35
- required: false,
36
- default: false
37
- },
38
- // Disable the resize handle of textarea
39
- noResize: {
40
- type: Boolean,
41
- required: false,
42
- default: false
43
- },
44
- rows: {
45
- type: [Number, String],
46
- required: false,
47
- default: 2
48
- },
49
- // 'soft', 'hard' or 'off'
50
- // Browser default is 'soft'
51
- wrap: {
52
- type: String,
53
- required: false,
54
- default: 'soft'
55
- }
56
- })
57
-
58
- // --- Main component ---
59
-
60
- // @vue/component
61
- export const BFormTextarea = /*#__PURE__*/ extend({
62
- name: NAME_FORM_TEXTAREA,
63
- directives: {
64
- 'b-visible': VBVisible
65
- },
66
- // Mixin order is important!
67
- mixins: [
68
- listenersMixin,
69
- idMixin,
70
- listenOnRootMixin,
71
- formControlMixin,
72
- formSizeMixin,
73
- formStateMixin,
74
- formTextMixin,
75
- formSelectionMixin,
76
- formValidityMixin
77
- ],
78
- props,
79
- data() {
80
- return {
81
- heightInPx: null
82
- }
83
- },
84
- computed: {
85
- type() {
86
- return null
87
- },
88
- computedStyle() {
89
- const styles = {
90
- // Setting `noResize` to true will disable the ability for the user to
91
- // manually resize the textarea. We also disable when in auto height mode
92
- resize: !this.computedRows || this.noResize ? 'none' : null
93
- }
94
- if (!this.computedRows) {
95
- // Conditionally set the computed CSS height when auto rows/height is enabled
96
- // We avoid setting the style to `null`, which can override user manual resize handle
97
- styles.height = this.heightInPx
98
- // We always add a vertical scrollbar to the textarea when auto-height is
99
- // enabled so that the computed height calculation returns a stable value
100
- styles.overflowY = 'scroll'
101
- }
102
- return styles
103
- },
104
- computedMinRows() {
105
- // Ensure rows is at least 2 and positive (2 is the native textarea value)
106
- // A value of 1 can cause issues in some browsers, and most browsers
107
- // only support 2 as the smallest value
108
- return mathMax(toInteger(this.rows, 2), 2)
109
- },
110
- computedMaxRows() {
111
- return mathMax(this.computedMinRows, toInteger(this.maxRows, 0))
112
- },
113
- computedRows() {
114
- // This is used to set the attribute 'rows' on the textarea
115
- // If auto-height is enabled, then we return `null` as we use CSS to control height
116
- return this.computedMinRows === this.computedMaxRows ? this.computedMinRows : null
117
- },
118
- computedAttrs() {
119
- const { disabled, required } = this
120
-
121
- return {
122
- id: this.safeId(),
123
- name: this.name || null,
124
- form: this.form || null,
125
- disabled,
126
- placeholder: this.placeholder || null,
127
- required,
128
- autocomplete: this.autocomplete || null,
129
- readonly: this.readonly || this.plaintext,
130
- rows: this.computedRows,
131
- wrap: this.wrap || null,
132
- 'aria-required': this.required ? 'true' : null,
133
- 'aria-invalid': this.computedAriaInvalid
134
- }
135
- },
136
- computedListeners() {
137
- return {
138
- ...this.bvListeners,
139
- input: this.onInput,
140
- change: this.onChange,
141
- blur: this.onBlur
142
- }
143
- }
144
- },
145
- watch: {
146
- localValue() {
147
- this.setHeight()
148
- }
149
- },
150
- mounted() {
151
- this.setHeight()
152
- },
153
- methods: {
154
- // Called by intersection observer directive
155
- /* istanbul ignore next */
156
- visibleCallback(visible) {
157
- if (visible) {
158
- // We use a `$nextTick()` here just to make sure any
159
- // transitions or portalling have completed
160
- this.$nextTick(this.setHeight)
161
- }
162
- },
163
- setHeight() {
164
- this.$nextTick(() => {
165
- requestAF(() => {
166
- this.heightInPx = this.computeHeight()
167
- })
168
- })
169
- },
170
- /* istanbul ignore next: can't test getComputedStyle in JSDOM */
171
- computeHeight() {
172
- if (this.$isServer || !isNull(this.computedRows)) {
173
- return null
174
- }
175
-
176
- const el = this.$el
177
-
178
- // Element must be visible (not hidden) and in document
179
- // Must be checked after above checks
180
- if (!isVisible(el)) {
181
- return null
182
- }
183
-
184
- // Get current computed styles
185
- const computedStyle = getCS(el)
186
- // Height of one line of text in px
187
- const lineHeight = toFloat(computedStyle.lineHeight, 1)
188
- // Calculate height of border and padding
189
- const border =
190
- toFloat(computedStyle.borderTopWidth, 0) + toFloat(computedStyle.borderBottomWidth, 0)
191
- const padding = toFloat(computedStyle.paddingTop, 0) + toFloat(computedStyle.paddingBottom, 0)
192
- // Calculate offset
193
- const offset = border + padding
194
- // Minimum height for min rows (which must be 2 rows or greater for cross-browser support)
195
- const minHeight = lineHeight * this.computedMinRows + offset
196
-
197
- // Get the current style height (with `px` units)
198
- const oldHeight = getStyle(el, 'height') || computedStyle.height
199
- // Probe scrollHeight by temporarily changing the height to `auto`
200
- setStyle(el, 'height', 'auto')
201
- const scrollHeight = el.scrollHeight
202
- // Place the original old height back on the element, just in case `computedProp`
203
- // returns the same value as before
204
- setStyle(el, 'height', oldHeight)
205
-
206
- // Calculate content height in 'rows' (scrollHeight includes padding but not border)
207
- const contentRows = mathMax((scrollHeight - padding) / lineHeight, 2)
208
- // Calculate number of rows to display (limited within min/max rows)
209
- const rows = mathMin(mathMax(contentRows, this.computedMinRows), this.computedMaxRows)
210
- // Calculate the required height of the textarea including border and padding (in pixels)
211
- const height = mathMax(mathCeil(rows * lineHeight + offset), minHeight)
212
-
213
- // Computed height remains the larger of `oldHeight` and new `height`,
214
- // when height is in `sticky` mode (prop `no-auto-shrink` is true)
215
- if (this.noAutoShrink && toFloat(oldHeight, 0) > height) {
216
- return oldHeight
217
- }
218
-
219
- // Return the new computed CSS height in px units
220
- return `${height}px`
221
- }
222
- },
223
- render(h) {
224
- return h('textarea', {
225
- class: this.computedClass,
226
- style: this.computedStyle,
227
- directives: [
228
- {
229
- name: 'b-visible',
230
- value: this.visibleCallback,
231
- // If textarea is within 640px of viewport, consider it visible
232
- modifiers: { '640': true }
233
- }
234
- ],
235
- attrs: this.computedAttrs,
236
- domProps: { value: this.localValue },
237
- on: this.computedListeners,
238
- ref: 'input'
239
- })
240
- }
241
- })
@@ -1,3 +0,0 @@
1
- import { BFormTextarea } from './form-textarea'
2
-
3
- export { BFormTextarea }
@@ -1,60 +0,0 @@
1
- import { extend } from '../vue'
2
-
3
- // @vue/component
4
- export const formSelectionMixin = extend({
5
- computed: {
6
- selectionStart: {
7
- // Expose selectionStart for formatters, etc
8
- cache: false,
9
- /* istanbul ignore next */
10
- get() {
11
- return this.$refs.input.selectionStart
12
- },
13
- /* istanbul ignore next */
14
- set(val) {
15
- this.$refs.input.selectionStart = val
16
- }
17
- },
18
- selectionEnd: {
19
- // Expose selectionEnd for formatters, etc
20
- cache: false,
21
- /* istanbul ignore next */
22
- get() {
23
- return this.$refs.input.selectionEnd
24
- },
25
- /* istanbul ignore next */
26
- set(val) {
27
- this.$refs.input.selectionEnd = val
28
- }
29
- },
30
- selectionDirection: {
31
- // Expose selectionDirection for formatters, etc
32
- cache: false,
33
- /* istanbul ignore next */
34
- get() {
35
- return this.$refs.input.selectionDirection
36
- },
37
- /* istanbul ignore next */
38
- set(val) {
39
- this.$refs.input.selectionDirection = val
40
- }
41
- }
42
- },
43
- methods: {
44
- /* istanbul ignore next */
45
- select() {
46
- // For external handler that may want a select() method
47
- this.$refs.input.select(...arguments)
48
- },
49
- /* istanbul ignore next */
50
- setSelectionRange() {
51
- // For external handler that may want a setSelectionRange(a,b,c) method
52
- this.$refs.input.setSelectionRange(...arguments)
53
- },
54
- /* istanbul ignore next */
55
- setRangeText() {
56
- // For external handler that may want a setRangeText(a,b,c) method
57
- this.$refs.input.setRangeText(...arguments)
58
- }
59
- }
60
- })
@@ -1,280 +0,0 @@
1
- import { extend } from '../vue'
2
- import {
3
- EVENT_NAME_BLUR,
4
- EVENT_NAME_CHANGE,
5
- EVENT_NAME_INPUT,
6
- EVENT_NAME_UPDATE
7
- } from '../constants/events'
8
- import { attemptBlur, attemptFocus } from '../utils/dom'
9
- import { stopEvent } from '../utils/events'
10
- import { mathMax } from '../utils/math'
11
- import { toInteger, toFloat } from '../utils/number'
12
- import { sortKeys } from '../utils/object'
13
- import { isFunction } from '../utils/inspect'
14
- import { toString } from '../utils/string'
15
-
16
- // --- Constants ---
17
-
18
- const MODEL_PROP_NAME = 'value'
19
- const MODEL_EVENT_NAME = EVENT_NAME_UPDATE
20
-
21
- // --- Props ---
22
-
23
- export const props = sortKeys({
24
- [MODEL_PROP_NAME]: {
25
- type: [Number, String],
26
- default: ''
27
- },
28
- ariaInvalid: {
29
- type: [Boolean, String],
30
- required: false,
31
- default: false
32
- },
33
- autocomplete: {
34
- type: String,
35
- required: false,
36
- default: undefined
37
- },
38
- // Debounce timeout (in ms). Not applicable with `lazy` prop
39
- debounce: {
40
- type: [Number, String],
41
- required: false,
42
- default: 0
43
- },
44
- formatter: {
45
- type: Function,
46
- required: false,
47
- default: undefined
48
- },
49
- // Only update the `v-model` on blur/change events
50
- lazy: {
51
- type: Boolean,
52
- required: false,
53
- default: false
54
- },
55
- lazyFormatter: {
56
- type: Boolean,
57
- required: false,
58
- default: false
59
- },
60
- number: {
61
- type: Boolean,
62
- required: false,
63
- default: false
64
- },
65
- placeholder: {
66
- type: String,
67
- required: false,
68
- default: undefined
69
- },
70
- plaintext: {
71
- type: Boolean,
72
- required: false,
73
- default: false
74
- },
75
- readonly: {
76
- type: Boolean,
77
- required: false,
78
- default: false
79
- },
80
- trim: {
81
- type: Boolean,
82
- required: false,
83
- default: false
84
- }
85
- })
86
-
87
- // --- Mixin ---
88
-
89
- // @vue/component
90
- export const formTextMixin = extend({
91
- model: {
92
- prop: MODEL_PROP_NAME,
93
- event: MODEL_EVENT_NAME
94
- },
95
- props,
96
- data() {
97
- const value = this[MODEL_PROP_NAME]
98
- return {
99
- localValue: toString(value),
100
- vModelValue: this.modifyValue(value)
101
- }
102
- },
103
- computed: {
104
- computedClass() {
105
- const { plaintext, type } = this
106
- const isRange = type === 'range'
107
- const isColor = type === 'color'
108
-
109
- return [
110
- {
111
- // Range input needs class `custom-range`
112
- 'custom-range': isRange,
113
- // `plaintext` not supported by `type="range"` or `type="color"`
114
- 'form-control-plaintext': plaintext && !isRange && !isColor,
115
- // `form-control` not used by `type="range"` or `plaintext`
116
- // Always used by `type="color"`
117
- 'form-control': isColor || (!plaintext && !isRange)
118
- },
119
- this.sizeFormClass,
120
- this.stateClass
121
- ]
122
- },
123
- computedDebounce() {
124
- // Ensure we have a positive number equal to or greater than 0
125
- return mathMax(toInteger(this.debounce, 0), 0)
126
- },
127
- hasFormatter() {
128
- // `formatter` is an optional Function prop with no default
129
- return isFunction(this.formatter)
130
- }
131
- },
132
- watch: {
133
- [MODEL_PROP_NAME](newValue) {
134
- const stringifyValue = toString(newValue)
135
- const modifiedValue = this.modifyValue(newValue)
136
- if (stringifyValue !== this.localValue || modifiedValue !== this.vModelValue) {
137
- // Clear any pending debounce timeout, as we are overwriting the user input
138
- this.clearDebounce()
139
- // Update the local values
140
- this.localValue = stringifyValue
141
- this.vModelValue = modifiedValue
142
- }
143
- }
144
- },
145
- created() {
146
- // Create private non-reactive props
147
- this.$_inputDebounceTimer = null
148
- },
149
- beforeDestroy() {
150
- this.clearDebounce()
151
- },
152
- methods: {
153
- clearDebounce() {
154
- clearTimeout(this.$_inputDebounceTimer)
155
- this.$_inputDebounceTimer = null
156
- },
157
- formatValue(value, event, force = false) {
158
- value = toString(value)
159
- if (this.hasFormatter && (!this.lazyFormatter || force)) {
160
- value = this.formatter(value, event)
161
- }
162
- return value
163
- },
164
- modifyValue(value) {
165
- value = toString(value)
166
- // Emulate `.trim` modifier behaviour
167
- if (this.trim) {
168
- value = value.trim()
169
- }
170
- // Emulate `.number` modifier behaviour
171
- if (this.number) {
172
- value = toFloat(value, value)
173
- }
174
- return value
175
- },
176
- updateValue(value, force = false) {
177
- const { lazy } = this
178
- if (lazy && !force) {
179
- return
180
- }
181
- // Make sure to always clear the debounce when `updateValue()`
182
- // is called, even when the v-model hasn't changed
183
- this.clearDebounce()
184
- // Define the shared update logic in a method to be able to use
185
- // it for immediate and debounced value changes
186
- const doUpdate = () => {
187
- value = this.modifyValue(value)
188
- if (value !== this.vModelValue) {
189
- this.vModelValue = value
190
- this.$emit(MODEL_EVENT_NAME, value)
191
- } else if (this.hasFormatter) {
192
- // When the `vModelValue` hasn't changed but the actual input value
193
- // is out of sync, make sure to change it to the given one
194
- // Usually caused by browser autocomplete and how it triggers the
195
- // change or input event, or depending on the formatter function
196
- // https://github.com/bootstrap-vue/bootstrap-vue/issues/2657
197
- // https://github.com/bootstrap-vue/bootstrap-vue/issues/3498
198
- /* istanbul ignore next: hard to test */
199
- const $input = this.$refs.input
200
- /* istanbul ignore if: hard to test out of sync value */
201
- if ($input && value !== $input.value) {
202
- $input.value = value
203
- }
204
- }
205
- }
206
- // Only debounce the value update when a value greater than `0`
207
- // is set and we are not in lazy mode or this is a forced update
208
- const debounce = this.computedDebounce
209
- if (debounce > 0 && !lazy && !force) {
210
- this.$_inputDebounceTimer = setTimeout(doUpdate, debounce)
211
- } else {
212
- // Immediately update the v-model
213
- doUpdate()
214
- }
215
- },
216
- onInput(event) {
217
- // `event.target.composing` is set by Vue
218
- // https://github.com/vuejs/vue/blob/dev/src/platforms/web/runtime/directives/model.js
219
- // TODO: Is this needed now with the latest Vue?
220
- /* istanbul ignore if: hard to test composition events */
221
- if (event.target.composing) {
222
- return
223
- }
224
- const { value } = event.target
225
- const formattedValue = this.formatValue(value, event)
226
- // Exit when the `formatter` function strictly returned `false`
227
- // or prevented the input event
228
- /* istanbul ignore next */
229
- if (formattedValue === false || event.defaultPrevented) {
230
- stopEvent(event, { propagation: false })
231
- return
232
- }
233
- this.localValue = formattedValue
234
- this.updateValue(formattedValue)
235
- this.$emit(EVENT_NAME_INPUT, formattedValue)
236
- },
237
- onChange(event) {
238
- const { value } = event.target
239
- const formattedValue = this.formatValue(value, event)
240
- // Exit when the `formatter` function strictly returned `false`
241
- // or prevented the input event
242
- /* istanbul ignore next */
243
- if (formattedValue === false || event.defaultPrevented) {
244
- stopEvent(event, { propagation: false })
245
- return
246
- }
247
- this.localValue = formattedValue
248
- this.updateValue(formattedValue, true)
249
- this.$emit(EVENT_NAME_CHANGE, formattedValue)
250
- },
251
- onBlur(event) {
252
- // Apply the `localValue` on blur to prevent cursor jumps
253
- // on mobile browsers (e.g. caused by autocomplete)
254
- const { value } = event.target
255
- const formattedValue = this.formatValue(value, event, true)
256
- if (formattedValue !== false) {
257
- // We need to use the modified value here to apply the
258
- // `.trim` and `.number` modifiers properly
259
- this.localValue = toString(this.modifyValue(formattedValue))
260
- // We pass the formatted value here since the `updateValue` method
261
- // handles the modifiers itself
262
- this.updateValue(formattedValue, true)
263
- }
264
- // Emit native blur event
265
- this.$emit(EVENT_NAME_BLUR, event)
266
- },
267
- focus() {
268
- // For external handler that may want a focus method
269
- if (!this.disabled) {
270
- attemptFocus(this.$el)
271
- }
272
- },
273
- blur() {
274
- // For external handler that may want a blur method
275
- if (!this.disabled) {
276
- attemptBlur(this.$el)
277
- }
278
- }
279
- }
280
- })
@@ -1,48 +0,0 @@
1
- import { extend } from '../vue'
2
-
3
- // @vue/component
4
- export const formValidityMixin = extend({
5
- computed: {
6
- validity: {
7
- // Expose validity property
8
- cache: false,
9
- /* istanbul ignore next */
10
- get() {
11
- return this.$refs.input.validity
12
- }
13
- },
14
- validationMessage: {
15
- // Expose validationMessage property
16
- cache: false,
17
- /* istanbul ignore next */
18
- get() {
19
- return this.$refs.input.validationMessage
20
- }
21
- },
22
- willValidate: {
23
- // Expose willValidate property
24
- cache: false,
25
- /* istanbul ignore next */
26
- get() {
27
- return this.$refs.input.willValidate
28
- }
29
- }
30
- },
31
- methods: {
32
- /* istanbul ignore next */
33
- setCustomValidity() {
34
- // For external handler that may want a setCustomValidity(...) method
35
- return this.$refs.input.setCustomValidity(...arguments)
36
- },
37
- /* istanbul ignore next */
38
- checkValidity() {
39
- // For external handler that may want a checkValidity(...) method
40
- return this.$refs.input.checkValidity(...arguments)
41
- },
42
- /* istanbul ignore next */
43
- reportValidity() {
44
- // For external handler that may want a reportValidity(...) method
45
- return this.$refs.input.reportValidity(...arguments)
46
- }
47
- }
48
- })