@gitlab/ui 138.0.0 → 138.1.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.
- package/README.md +3 -12
- package/dist/components/base/filtered_search/filtered_search_suggestion_list.js +1 -1
- package/dist/components/base/form/form_select/form_select.js +322 -6
- package/dist/vendor/bootstrap-vue/src/constants/components.js +1 -4
- package/package.json +3 -11
- package/src/components/base/filtered_search/filtered_search_suggestion_list.vue +2 -1
- package/src/components/base/form/form_select/form_select.vue +389 -9
- package/src/vendor/bootstrap-vue/src/constants/components.js +0 -3
- package/dist/vendor/bootstrap-vue/src/components/form-select/form-select-option-group.js +0 -51
- package/dist/vendor/bootstrap-vue/src/components/form-select/form-select-option.js +0 -41
- package/dist/vendor/bootstrap-vue/src/components/form-select/form-select.js +0 -153
- package/dist/vendor/bootstrap-vue/src/components/form-select/helpers/mixin-options.js +0 -63
- package/dist/vendor/bootstrap-vue/src/components/form-select/index.js +0 -3
- package/src/vendor/bootstrap-vue/src/components/form-select/form-select-option-group.js +0 -46
- package/src/vendor/bootstrap-vue/src/components/form-select/form-select-option.js +0 -35
- package/src/vendor/bootstrap-vue/src/components/form-select/form-select.js +0 -156
- package/src/vendor/bootstrap-vue/src/components/form-select/helpers/mixin-options.js +0 -60
- package/src/vendor/bootstrap-vue/src/components/form-select/index.js +0 -5
|
@@ -1,17 +1,185 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { isObject } from 'lodash-es';
|
|
3
|
-
import { BFormSelect } from '../../../../vendor/bootstrap-vue/src/components/form-select/form-select';
|
|
2
|
+
import { isObject, uniqueId, isPlainObject, isBoolean, isUndefined, isNull, get } from 'lodash-es';
|
|
4
3
|
import { formInputWidths } from '../../../../utils/constants';
|
|
4
|
+
import { isVisible } from '../../../../utils/utils';
|
|
5
|
+
import { SafeHtmlDirective as SafeHtml } from '../../../../directives/safe_html/safe_html';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
name: 'GlFormSelect',
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
directives: {
|
|
10
|
+
SafeHtml,
|
|
10
11
|
},
|
|
11
12
|
inheritAttrs: false,
|
|
12
13
|
props: {
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
+
* Current value of the select. Should be set to an array when the 'multiple' prop is set
|
|
16
|
+
*/
|
|
17
|
+
value: {
|
|
18
|
+
// Value can be any type!
|
|
19
|
+
type: undefined,
|
|
20
|
+
required: false,
|
|
21
|
+
default: undefined,
|
|
22
|
+
},
|
|
23
|
+
/**
|
|
24
|
+
* Array of items to render in the component
|
|
25
|
+
*/
|
|
26
|
+
options: {
|
|
27
|
+
type: [Array, Object],
|
|
28
|
+
required: false,
|
|
29
|
+
default: undefined,
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* When set to a number larger than 0, will set the number of display option rows. Note not all browser will respect this setting
|
|
33
|
+
* @deprecated Only useful with `multiple`, which is also deprecated.
|
|
34
|
+
*/
|
|
35
|
+
selectSize: {
|
|
36
|
+
type: Number,
|
|
37
|
+
required: false,
|
|
38
|
+
default: 0,
|
|
39
|
+
},
|
|
40
|
+
/**
|
|
41
|
+
* Used to set the `id` attribute on the rendered content, and used as the base to generate any additional element IDs as needed
|
|
42
|
+
*/
|
|
43
|
+
id: {
|
|
44
|
+
type: String,
|
|
45
|
+
required: false,
|
|
46
|
+
default: undefined,
|
|
47
|
+
},
|
|
48
|
+
/**
|
|
49
|
+
* Render the form control in plain mode, rather than custom styled mode
|
|
50
|
+
* @deprecated Use a native <select> element instead.
|
|
51
|
+
*/
|
|
52
|
+
plain: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
required: false,
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* Set the size of the component's appearance. 'sm', 'md' (default), or 'lg'
|
|
59
|
+
* @deprecated Has no effect. Use the `width` prop instead.
|
|
60
|
+
*/
|
|
61
|
+
size: {
|
|
62
|
+
type: String,
|
|
63
|
+
required: false,
|
|
64
|
+
default: undefined,
|
|
65
|
+
},
|
|
66
|
+
/**
|
|
67
|
+
* Optional value to set for the 'aria-invalid' attribute. Supported values are 'true' and 'false'. If not set, the 'state' prop will dictate the value
|
|
68
|
+
*/
|
|
69
|
+
ariaInvalid: {
|
|
70
|
+
type: [Boolean, String],
|
|
71
|
+
required: false,
|
|
72
|
+
default: false,
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Controls the validation state appearance of the component. `true` for valid, `false` for invalid, or `null` for no validation state
|
|
76
|
+
*/
|
|
77
|
+
state: {
|
|
78
|
+
// Tri-state prop: true, false, null (or undefined)
|
|
79
|
+
type: Boolean,
|
|
80
|
+
required: false,
|
|
81
|
+
default: null,
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* Field name in the `options` array that should be used for the value
|
|
85
|
+
*/
|
|
86
|
+
valueField: {
|
|
87
|
+
type: String,
|
|
88
|
+
required: false,
|
|
89
|
+
default: 'value',
|
|
90
|
+
},
|
|
91
|
+
/**
|
|
92
|
+
* The key to use from the option object to get the label
|
|
93
|
+
*/
|
|
94
|
+
labelField: {
|
|
95
|
+
type: String,
|
|
96
|
+
required: false,
|
|
97
|
+
default: 'label',
|
|
98
|
+
},
|
|
99
|
+
/**
|
|
100
|
+
* The key to use from the option object to get the options
|
|
101
|
+
*/
|
|
102
|
+
optionsField: {
|
|
103
|
+
type: String,
|
|
104
|
+
required: false,
|
|
105
|
+
default: 'options',
|
|
106
|
+
},
|
|
107
|
+
/**
|
|
108
|
+
* Field name in the `options` array that should be used for the text label
|
|
109
|
+
*/
|
|
110
|
+
textField: {
|
|
111
|
+
type: String,
|
|
112
|
+
required: false,
|
|
113
|
+
default: 'text',
|
|
114
|
+
},
|
|
115
|
+
/**
|
|
116
|
+
* Field name in the `options` array that should be used for the disabled state
|
|
117
|
+
*/
|
|
118
|
+
disabledField: {
|
|
119
|
+
type: String,
|
|
120
|
+
required: false,
|
|
121
|
+
default: 'disabled',
|
|
122
|
+
},
|
|
123
|
+
/**
|
|
124
|
+
* Field name in the `options` array that should be used for the html label instead of text field
|
|
125
|
+
* @deprecated Using the `.html` field (whatever its name) is potentially unsafe.
|
|
126
|
+
*/
|
|
127
|
+
htmlField: {
|
|
128
|
+
type: String,
|
|
129
|
+
required: false,
|
|
130
|
+
default: 'html',
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* When set, allows multiple options to be selected (multi-select)
|
|
134
|
+
* @deprecated Use GlCollapsibleListbox instead.
|
|
135
|
+
*/
|
|
136
|
+
multiple: {
|
|
137
|
+
type: Boolean,
|
|
138
|
+
required: false,
|
|
139
|
+
default: false,
|
|
140
|
+
},
|
|
141
|
+
/**
|
|
142
|
+
* When set to `true`, attempts to auto-focus the control when it is mounted, or re-activated when in a keep-alive. Does not set the `autofocus` attribute on the control
|
|
143
|
+
*/
|
|
144
|
+
autofocus: {
|
|
145
|
+
type: Boolean,
|
|
146
|
+
required: false,
|
|
147
|
+
default: false,
|
|
148
|
+
},
|
|
149
|
+
/**
|
|
150
|
+
* When set to `true`, disables the component's functionality and places it in a disabled state
|
|
151
|
+
*/
|
|
152
|
+
disabled: {
|
|
153
|
+
type: Boolean,
|
|
154
|
+
required: false,
|
|
155
|
+
default: false,
|
|
156
|
+
},
|
|
157
|
+
/**
|
|
158
|
+
* ID of the form that the form control belongs to. Sets the `form` attribute on the control
|
|
159
|
+
*/
|
|
160
|
+
form: {
|
|
161
|
+
type: String,
|
|
162
|
+
required: false,
|
|
163
|
+
default: undefined,
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
* Sets the value of the `name` attribute on the form control
|
|
167
|
+
*/
|
|
168
|
+
name: {
|
|
169
|
+
type: String,
|
|
170
|
+
required: false,
|
|
171
|
+
default: undefined,
|
|
172
|
+
},
|
|
173
|
+
/**
|
|
174
|
+
* Adds the `required` attribute to the form control
|
|
175
|
+
*/
|
|
176
|
+
required: {
|
|
177
|
+
type: Boolean,
|
|
178
|
+
required: false,
|
|
179
|
+
default: false,
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* Maximum width of the Select
|
|
15
183
|
*/
|
|
16
184
|
width: {
|
|
17
185
|
type: [String, Object],
|
|
@@ -32,7 +200,32 @@ export default {
|
|
|
32
200
|
default: '',
|
|
33
201
|
},
|
|
34
202
|
},
|
|
203
|
+
data() {
|
|
204
|
+
return {
|
|
205
|
+
localValue: this.value,
|
|
206
|
+
localId: null,
|
|
207
|
+
};
|
|
208
|
+
},
|
|
35
209
|
computed: {
|
|
210
|
+
computedId() {
|
|
211
|
+
return this.id || this.localId;
|
|
212
|
+
},
|
|
213
|
+
computedSelectSize() {
|
|
214
|
+
// Custom selects with a size of zero causes the arrows to be hidden,
|
|
215
|
+
// so dont render the size attribute in this case
|
|
216
|
+
return !this.plain && this.selectSize === 0 ? null : this.selectSize;
|
|
217
|
+
},
|
|
218
|
+
computedAriaInvalid() {
|
|
219
|
+
const { ariaInvalid } = this;
|
|
220
|
+
if (ariaInvalid === true || ariaInvalid === 'true' || ariaInvalid === '') {
|
|
221
|
+
return 'true';
|
|
222
|
+
}
|
|
223
|
+
return this.computedState === false ? 'true' : ariaInvalid;
|
|
224
|
+
},
|
|
225
|
+
computedState() {
|
|
226
|
+
// If not a boolean, ensure that value is null
|
|
227
|
+
return isBoolean(this.state) ? this.state : null;
|
|
228
|
+
},
|
|
36
229
|
cssClasses() {
|
|
37
230
|
if (this.width === null) {
|
|
38
231
|
return [];
|
|
@@ -54,15 +247,202 @@ export default {
|
|
|
54
247
|
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
|
|
55
248
|
return [`gl-form-select-${this.width}`];
|
|
56
249
|
},
|
|
250
|
+
stateClass() {
|
|
251
|
+
if (this.computedState === true) return 'is-valid';
|
|
252
|
+
if (this.computedState === false) return 'is-invalid';
|
|
253
|
+
return null;
|
|
254
|
+
},
|
|
255
|
+
inputClass() {
|
|
256
|
+
return [
|
|
257
|
+
'gl-form-select',
|
|
258
|
+
this.plain ? 'form-control' : 'custom-select',
|
|
259
|
+
this.size && this.plain ? `form-control-${this.size}` : null,
|
|
260
|
+
this.size && !this.plain ? `custom-select-${this.size}` : null,
|
|
261
|
+
this.stateClass,
|
|
262
|
+
this.selectClass,
|
|
263
|
+
];
|
|
264
|
+
},
|
|
265
|
+
normalizedOptions() {
|
|
266
|
+
return this.normalizeOptions(this.options);
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
watch: {
|
|
270
|
+
value(newValue) {
|
|
271
|
+
this.localValue = newValue;
|
|
272
|
+
},
|
|
273
|
+
localValue(newValue) {
|
|
274
|
+
/**
|
|
275
|
+
* Emitted when the select value changes
|
|
276
|
+
*
|
|
277
|
+
* @event input
|
|
278
|
+
* @type {any}
|
|
279
|
+
*/
|
|
280
|
+
this.$emit('input', newValue);
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
activated() {
|
|
284
|
+
this.handleAutofocus();
|
|
285
|
+
},
|
|
286
|
+
async mounted() {
|
|
287
|
+
this.handleAutofocus();
|
|
288
|
+
await this.$nextTick();
|
|
289
|
+
// Update DOM with auto-generated ID after mount
|
|
290
|
+
// to prevent SSR hydration errors
|
|
291
|
+
this.localId = uniqueId('gl-form-select-');
|
|
292
|
+
},
|
|
293
|
+
methods: {
|
|
294
|
+
focus() {
|
|
295
|
+
this.$refs.select?.focus();
|
|
296
|
+
},
|
|
297
|
+
blur() {
|
|
298
|
+
this.$refs.select?.blur();
|
|
299
|
+
},
|
|
300
|
+
normalizeOptions(options) {
|
|
301
|
+
// Normalize the given options array
|
|
302
|
+
if (Array.isArray(options)) return options.map((option) => this.normalizeOption(option));
|
|
303
|
+
|
|
304
|
+
if (isPlainObject(options)) {
|
|
305
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
306
|
+
// Deprecate the object options format
|
|
307
|
+
// eslint-disable-next-line no-console
|
|
308
|
+
console.warn(
|
|
309
|
+
'[@gitlab/ui] GlFormSelect - Setting prop "options" to an object is deprecated. Use the array format instead.',
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Normalize a `options` object to an array of options
|
|
314
|
+
return Object.keys(options).map((key) => this.normalizeOption(options[key] || {}, key));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// If not an array or object, return an empty array
|
|
318
|
+
return [];
|
|
319
|
+
},
|
|
320
|
+
normalizeOption(option, key = null) {
|
|
321
|
+
// When the option is an object, normalize it
|
|
322
|
+
if (isPlainObject(option)) {
|
|
323
|
+
const value = get(option, this.valueField);
|
|
324
|
+
const text = get(option, this.textField);
|
|
325
|
+
const options = get(option, this.optionsField, null);
|
|
326
|
+
// When it has options, create an <optgroup> object
|
|
327
|
+
if (!isNull(options)) {
|
|
328
|
+
return {
|
|
329
|
+
label: String(get(option, this.labelField) || text),
|
|
330
|
+
options: this.normalizeOptions(options),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
// Otherwise create an <option> object
|
|
334
|
+
return {
|
|
335
|
+
value: isUndefined(value) ? key || text : value,
|
|
336
|
+
text: String(isUndefined(text) ? key : text),
|
|
337
|
+
html: get(option, this.htmlField),
|
|
338
|
+
disabled: Boolean(get(option, this.disabledField)),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
// Otherwise create an <option> object from the given value
|
|
342
|
+
return {
|
|
343
|
+
value: key || option,
|
|
344
|
+
text: String(option),
|
|
345
|
+
disabled: false,
|
|
346
|
+
};
|
|
347
|
+
},
|
|
348
|
+
isOptionGroup(option) {
|
|
349
|
+
return Array.isArray(option.options);
|
|
350
|
+
},
|
|
351
|
+
async onChange(event) {
|
|
352
|
+
const { target } = event;
|
|
353
|
+
const selectedValue = Array.from(target.options)
|
|
354
|
+
.filter((option) => option.selected)
|
|
355
|
+
// NOTE: the _value property is set up by both Vue 2 and 3 explicitly for the
|
|
356
|
+
// purpose of handling non-string values on form elements.
|
|
357
|
+
//
|
|
358
|
+
// See:
|
|
359
|
+
// - https://github.com/vuejs/vue/blob/v2.7.16/src/platforms/web/runtime/modules/dom-props.ts#L42-L44
|
|
360
|
+
// - https://github.com/vuejs/vue/blob/v2.7.16/test/unit/modules/vdom/modules/dom-props.spec.ts#L38
|
|
361
|
+
// - https://github.com/vuejs/core/blob/v3.4.27/packages/runtime-dom/src/modules/props.ts#L48-L50
|
|
362
|
+
// - https://github.com/vuejs/core/blob/v3.4.27/packages/runtime-dom/__tests__/patchProps.spec.ts#L25
|
|
363
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
364
|
+
.map((option) => ('_value' in option ? option._value : option.value));
|
|
365
|
+
|
|
366
|
+
this.localValue = target.multiple ? selectedValue : selectedValue[0];
|
|
367
|
+
|
|
368
|
+
await this.$nextTick();
|
|
369
|
+
/**
|
|
370
|
+
* Emitted when the select value changes via user interaction
|
|
371
|
+
*
|
|
372
|
+
* @event change
|
|
373
|
+
* @type {any}
|
|
374
|
+
*/
|
|
375
|
+
this.$emit('change', this.localValue);
|
|
376
|
+
},
|
|
377
|
+
async handleAutofocus() {
|
|
378
|
+
await this.$nextTick();
|
|
379
|
+
window.requestAnimationFrame(() => {
|
|
380
|
+
if (this.autofocus && isVisible(this.$refs.select)) this.focus();
|
|
381
|
+
});
|
|
382
|
+
},
|
|
57
383
|
},
|
|
58
384
|
};
|
|
59
385
|
</script>
|
|
60
386
|
<template>
|
|
61
387
|
<span class="gl-form-select-wrapper" :class="cssClasses">
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
388
|
+
<select
|
|
389
|
+
:id="computedId"
|
|
390
|
+
ref="select"
|
|
391
|
+
v-model="localValue"
|
|
392
|
+
:aria-invalid="computedAriaInvalid"
|
|
393
|
+
:aria-required="required ? 'true' : null"
|
|
394
|
+
:class="inputClass"
|
|
395
|
+
:disabled="disabled"
|
|
396
|
+
:form="form"
|
|
397
|
+
:multiple="multiple"
|
|
398
|
+
:name="name"
|
|
399
|
+
:required="required"
|
|
400
|
+
:size="computedSelectSize"
|
|
401
|
+
v-bind="$attrs"
|
|
402
|
+
@change="onChange"
|
|
403
|
+
>
|
|
404
|
+
<!-- @slot Slot to place options or option groups above options provided via the 'options' prop -->
|
|
405
|
+
<slot name="first"></slot>
|
|
406
|
+
|
|
407
|
+
<template v-for="(option, index) of normalizedOptions">
|
|
408
|
+
<template v-if="isOptionGroup(option)">
|
|
409
|
+
<optgroup :key="index" :label="option.label">
|
|
410
|
+
<template v-for="(childOption, childIndex) of option.options">
|
|
411
|
+
<option
|
|
412
|
+
v-if="childOption.html"
|
|
413
|
+
:key="`${childIndex}-html`"
|
|
414
|
+
v-safe-html="childOption.html"
|
|
415
|
+
:value="childOption.value"
|
|
416
|
+
:disabled="childOption.disabled"
|
|
417
|
+
></option>
|
|
418
|
+
<option
|
|
419
|
+
v-else
|
|
420
|
+
:key="childIndex"
|
|
421
|
+
:value="childOption.value"
|
|
422
|
+
:disabled="childOption.disabled"
|
|
423
|
+
>
|
|
424
|
+
{{ childOption.text }}
|
|
425
|
+
</option>
|
|
426
|
+
</template>
|
|
427
|
+
</optgroup>
|
|
428
|
+
</template>
|
|
429
|
+
|
|
430
|
+
<template v-else>
|
|
431
|
+
<option
|
|
432
|
+
v-if="option.html"
|
|
433
|
+
:key="`${index}-html`"
|
|
434
|
+
v-safe-html="option.html"
|
|
435
|
+
:value="option.value"
|
|
436
|
+
:disabled="option.disabled"
|
|
437
|
+
></option>
|
|
438
|
+
<option v-else :key="index" :value="option.value" :disabled="option.disabled">
|
|
439
|
+
{{ option.text }}
|
|
440
|
+
</option>
|
|
441
|
+
</template>
|
|
65
442
|
</template>
|
|
66
|
-
|
|
443
|
+
|
|
444
|
+
<!-- @slot Content to place in the form select after 'options' prop options -->
|
|
445
|
+
<slot></slot>
|
|
446
|
+
</select>
|
|
67
447
|
</span>
|
|
68
448
|
</template>
|
|
@@ -14,9 +14,6 @@ export const NAME_FORM = 'BForm'
|
|
|
14
14
|
export const NAME_FORM_GROUP = 'BFormGroup'
|
|
15
15
|
export const NAME_FORM_INVALID_FEEDBACK = 'BFormInvalidFeedback'
|
|
16
16
|
export const NAME_FORM_ROW = 'BFormRow'
|
|
17
|
-
export const NAME_FORM_SELECT = 'BFormSelect'
|
|
18
|
-
export const NAME_FORM_SELECT_OPTION = 'BFormSelectOption'
|
|
19
|
-
export const NAME_FORM_SELECT_OPTION_GROUP = 'BFormSelectOptionGroup'
|
|
20
17
|
export const NAME_FORM_TEXT = 'BFormText'
|
|
21
18
|
export const NAME_FORM_VALID_FEEDBACK = 'BFormValidFeedback'
|
|
22
19
|
export const NAME_LINK = 'BLink'
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { extend } from '../../vue';
|
|
2
|
-
import { NAME_FORM_SELECT_OPTION_GROUP } from '../../constants/components';
|
|
3
|
-
import { SLOT_NAME_FIRST } from '../../constants/slots';
|
|
4
|
-
import { htmlOrText } from '../../utils/html';
|
|
5
|
-
import { sortKeys } from '../../utils/object';
|
|
6
|
-
import { props as props$1, formOptionsMixin } from '../../mixins/form-options';
|
|
7
|
-
import { normalizeSlotMixin } from '../../mixins/normalize-slot';
|
|
8
|
-
import { BFormSelectOption } from './form-select-option';
|
|
9
|
-
|
|
10
|
-
// --- Props ---
|
|
11
|
-
|
|
12
|
-
const props = sortKeys({
|
|
13
|
-
...props$1,
|
|
14
|
-
label: {
|
|
15
|
-
type: String,
|
|
16
|
-
required: true
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// --- Main component ---
|
|
21
|
-
|
|
22
|
-
// @vue/component
|
|
23
|
-
const BFormSelectOptionGroup = /*#__PURE__*/extend({
|
|
24
|
-
name: NAME_FORM_SELECT_OPTION_GROUP,
|
|
25
|
-
mixins: [normalizeSlotMixin, formOptionsMixin],
|
|
26
|
-
props,
|
|
27
|
-
render(h) {
|
|
28
|
-
const label = this.label;
|
|
29
|
-
const $options = this.formOptions.map((option, index) => {
|
|
30
|
-
const value = option.value,
|
|
31
|
-
text = option.text,
|
|
32
|
-
html = option.html,
|
|
33
|
-
disabled = option.disabled;
|
|
34
|
-
return h(BFormSelectOption, {
|
|
35
|
-
attrs: {
|
|
36
|
-
value,
|
|
37
|
-
disabled
|
|
38
|
-
},
|
|
39
|
-
domProps: htmlOrText(html, text),
|
|
40
|
-
key: `option_${index}`
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
return h('optgroup', {
|
|
44
|
-
attrs: {
|
|
45
|
-
label
|
|
46
|
-
}
|
|
47
|
-
}, [this.normalizeSlot(SLOT_NAME_FIRST), $options, this.normalizeSlot()]);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
export { BFormSelectOptionGroup, props };
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { extend, mergeData } from '../../vue';
|
|
2
|
-
import { NAME_FORM_SELECT_OPTION } from '../../constants/components';
|
|
3
|
-
|
|
4
|
-
// --- Props ---
|
|
5
|
-
|
|
6
|
-
const props = {
|
|
7
|
-
disabled: {
|
|
8
|
-
type: Boolean,
|
|
9
|
-
required: false,
|
|
10
|
-
default: false
|
|
11
|
-
},
|
|
12
|
-
value: {
|
|
13
|
-
required: true
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// --- Main component ---
|
|
18
|
-
|
|
19
|
-
// @vue/component
|
|
20
|
-
const BFormSelectOption = /*#__PURE__*/extend({
|
|
21
|
-
name: NAME_FORM_SELECT_OPTION,
|
|
22
|
-
functional: true,
|
|
23
|
-
props,
|
|
24
|
-
render(h, _ref) {
|
|
25
|
-
let props = _ref.props,
|
|
26
|
-
data = _ref.data,
|
|
27
|
-
children = _ref.children;
|
|
28
|
-
const value = props.value,
|
|
29
|
-
disabled = props.disabled;
|
|
30
|
-
return h('option', mergeData(data, {
|
|
31
|
-
attrs: {
|
|
32
|
-
disabled
|
|
33
|
-
},
|
|
34
|
-
domProps: {
|
|
35
|
-
value
|
|
36
|
-
}
|
|
37
|
-
}), children);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
export { BFormSelectOption, props };
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { extend } from '../../vue';
|
|
2
|
-
import { NAME_FORM_SELECT } from '../../constants/components';
|
|
3
|
-
import { EVENT_NAME_CHANGE, EVENT_NAME_INPUT } from '../../constants/events';
|
|
4
|
-
import { SLOT_NAME_FIRST } from '../../constants/slots';
|
|
5
|
-
import { from } from '../../utils/array';
|
|
6
|
-
import { attemptFocus, attemptBlur } from '../../utils/dom';
|
|
7
|
-
import { htmlOrText } from '../../utils/html';
|
|
8
|
-
import { isArray } from '../../utils/inspect';
|
|
9
|
-
import { sortKeys } from '../../utils/object';
|
|
10
|
-
import { props as props$2, formControlMixin } from '../../mixins/form-control';
|
|
11
|
-
import { props as props$3, formCustomMixin } from '../../mixins/form-custom';
|
|
12
|
-
import { props as props$4, formSizeMixin } from '../../mixins/form-size';
|
|
13
|
-
import { props as props$5, formStateMixin } from '../../mixins/form-state';
|
|
14
|
-
import { normalizeSlotMixin } from '../../mixins/normalize-slot';
|
|
15
|
-
import { props as props$1, idMixin } from '../../mixins/id';
|
|
16
|
-
import { optionsMixin } from './helpers/mixin-options';
|
|
17
|
-
import { BFormSelectOption } from './form-select-option';
|
|
18
|
-
import { BFormSelectOptionGroup } from './form-select-option-group';
|
|
19
|
-
|
|
20
|
-
const MODEL_PROP_NAME = 'value';
|
|
21
|
-
const MODEL_EVENT_NAME = EVENT_NAME_INPUT;
|
|
22
|
-
|
|
23
|
-
// --- Props ---
|
|
24
|
-
|
|
25
|
-
const props = sortKeys({
|
|
26
|
-
...props$1,
|
|
27
|
-
[MODEL_PROP_NAME]: {},
|
|
28
|
-
...props$2,
|
|
29
|
-
...props$3,
|
|
30
|
-
...props$4,
|
|
31
|
-
...props$5,
|
|
32
|
-
ariaInvalid: {
|
|
33
|
-
type: [Boolean, String],
|
|
34
|
-
required: false,
|
|
35
|
-
default: false
|
|
36
|
-
},
|
|
37
|
-
multiple: {
|
|
38
|
-
type: Boolean,
|
|
39
|
-
required: false,
|
|
40
|
-
default: false
|
|
41
|
-
},
|
|
42
|
-
// Browsers default size to `0`, which shows 4 rows in most browsers in multiple mode
|
|
43
|
-
// Size of `1` can bork out Firefox
|
|
44
|
-
selectSize: {
|
|
45
|
-
type: Number,
|
|
46
|
-
required: false,
|
|
47
|
-
default: 0
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// --- Main component ---
|
|
52
|
-
|
|
53
|
-
// @vue/component
|
|
54
|
-
const BFormSelect = /*#__PURE__*/extend({
|
|
55
|
-
name: NAME_FORM_SELECT,
|
|
56
|
-
mixins: [idMixin, formControlMixin, formSizeMixin, formStateMixin, formCustomMixin, optionsMixin, normalizeSlotMixin],
|
|
57
|
-
model: {
|
|
58
|
-
prop: MODEL_PROP_NAME,
|
|
59
|
-
event: MODEL_EVENT_NAME
|
|
60
|
-
},
|
|
61
|
-
props,
|
|
62
|
-
data() {
|
|
63
|
-
return {
|
|
64
|
-
localValue: this[MODEL_PROP_NAME]
|
|
65
|
-
};
|
|
66
|
-
},
|
|
67
|
-
computed: {
|
|
68
|
-
computedSelectSize() {
|
|
69
|
-
// Custom selects with a size of zero causes the arrows to be hidden,
|
|
70
|
-
// so dont render the size attribute in this case
|
|
71
|
-
return !this.plain && this.selectSize === 0 ? null : this.selectSize;
|
|
72
|
-
},
|
|
73
|
-
inputClass() {
|
|
74
|
-
return [this.plain ? 'form-control' : 'custom-select', this.size && this.plain ? `form-control-${this.size}` : null, this.size && !this.plain ? `custom-select-${this.size}` : null, this.stateClass];
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
watch: {
|
|
78
|
-
value(newValue) {
|
|
79
|
-
this.localValue = newValue;
|
|
80
|
-
},
|
|
81
|
-
localValue() {
|
|
82
|
-
this.$emit(MODEL_EVENT_NAME, this.localValue);
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
methods: {
|
|
86
|
-
focus() {
|
|
87
|
-
attemptFocus(this.$refs.input);
|
|
88
|
-
},
|
|
89
|
-
blur() {
|
|
90
|
-
attemptBlur(this.$refs.input);
|
|
91
|
-
},
|
|
92
|
-
onChange(event) {
|
|
93
|
-
const target = event.target;
|
|
94
|
-
const selectedValue = from(target.options).filter(o => o.selected).map(o => '_value' in o ? o._value : o.value);
|
|
95
|
-
this.localValue = target.multiple ? selectedValue : selectedValue[0];
|
|
96
|
-
this.$nextTick(() => {
|
|
97
|
-
this.$emit(EVENT_NAME_CHANGE, this.localValue);
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
render(h) {
|
|
102
|
-
const name = this.name,
|
|
103
|
-
disabled = this.disabled,
|
|
104
|
-
required = this.required,
|
|
105
|
-
size = this.computedSelectSize,
|
|
106
|
-
value = this.localValue;
|
|
107
|
-
const $options = this.formOptions.map((option, index) => {
|
|
108
|
-
const value = option.value,
|
|
109
|
-
label = option.label,
|
|
110
|
-
options = option.options,
|
|
111
|
-
disabled = option.disabled;
|
|
112
|
-
const key = `option_${index}`;
|
|
113
|
-
return isArray(options) ? h(BFormSelectOptionGroup, {
|
|
114
|
-
props: {
|
|
115
|
-
label,
|
|
116
|
-
options
|
|
117
|
-
},
|
|
118
|
-
key
|
|
119
|
-
}) : h(BFormSelectOption, {
|
|
120
|
-
props: {
|
|
121
|
-
value,
|
|
122
|
-
disabled
|
|
123
|
-
},
|
|
124
|
-
domProps: htmlOrText(option.html, option.text),
|
|
125
|
-
key
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
return h('select', {
|
|
129
|
-
class: this.inputClass,
|
|
130
|
-
attrs: {
|
|
131
|
-
id: this.safeId(),
|
|
132
|
-
name,
|
|
133
|
-
form: this.form || null,
|
|
134
|
-
multiple: this.multiple || null,
|
|
135
|
-
size,
|
|
136
|
-
disabled,
|
|
137
|
-
required,
|
|
138
|
-
'aria-required': required ? 'true' : null,
|
|
139
|
-
'aria-invalid': this.computedAriaInvalid
|
|
140
|
-
},
|
|
141
|
-
on: {
|
|
142
|
-
change: this.onChange
|
|
143
|
-
},
|
|
144
|
-
directives: [{
|
|
145
|
-
name: 'model',
|
|
146
|
-
value
|
|
147
|
-
}],
|
|
148
|
-
ref: 'input'
|
|
149
|
-
}, [this.normalizeSlot(SLOT_NAME_FIRST), $options, this.normalizeSlot()]);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
export { BFormSelect, props };
|