goodteditor-ui 1.0.95 → 1.0.97
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/package.json
CHANGED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="form-control form-control-icon-both" :class="cssClass">
|
|
3
|
+
<i
|
|
4
|
+
class="icon mdi mdi-magnify"
|
|
5
|
+
:class="{
|
|
6
|
+
'mdi-18px': size === '',
|
|
7
|
+
'h-auto w-auto': size === 'small'
|
|
8
|
+
}"></i>
|
|
9
|
+
<input
|
|
10
|
+
class="input"
|
|
11
|
+
:class="inputCssClass"
|
|
12
|
+
type="text"
|
|
13
|
+
ref="input"
|
|
14
|
+
:value="value"
|
|
15
|
+
:disabled="disabled"
|
|
16
|
+
:placeholder="$tCap(placeholder)"
|
|
17
|
+
@input="onInput"
|
|
18
|
+
@change="onChange"
|
|
19
|
+
@keydown.esc="onKeydownEsc" />
|
|
20
|
+
<i
|
|
21
|
+
v-if="value.length"
|
|
22
|
+
class="icon mdi mdi-close cursor-pointer"
|
|
23
|
+
:class="{
|
|
24
|
+
'mdi-18px': size === '',
|
|
25
|
+
'h-auto w-auto': size === 'small'
|
|
26
|
+
}"
|
|
27
|
+
@click="onClickClear"></i>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
<script>
|
|
31
|
+
const Size = {
|
|
32
|
+
NORMAL: { name: '', class: [] },
|
|
33
|
+
SMALL: { name: 'small', class: ['input-small'] }
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default {
|
|
37
|
+
props: {
|
|
38
|
+
/**
|
|
39
|
+
* @vmodel
|
|
40
|
+
*/
|
|
41
|
+
value: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: ''
|
|
44
|
+
},
|
|
45
|
+
/**
|
|
46
|
+
* @example Search something
|
|
47
|
+
*/
|
|
48
|
+
placeholder: {
|
|
49
|
+
type: String,
|
|
50
|
+
default: ''
|
|
51
|
+
},
|
|
52
|
+
/**
|
|
53
|
+
* @default normal
|
|
54
|
+
* @values small,normal
|
|
55
|
+
*/
|
|
56
|
+
size: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: Size.NORMAL.name,
|
|
59
|
+
validator: (val) =>
|
|
60
|
+
Object.values(Size)
|
|
61
|
+
.map(({ name }) => name)
|
|
62
|
+
.includes(val)
|
|
63
|
+
},
|
|
64
|
+
disabled: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: false
|
|
67
|
+
},
|
|
68
|
+
embedded: {
|
|
69
|
+
type: Boolean,
|
|
70
|
+
default: false
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
computed: {
|
|
74
|
+
cssClass() {
|
|
75
|
+
const { embedded } = this;
|
|
76
|
+
if (embedded === false) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
return ['form-control--embedded'];
|
|
80
|
+
},
|
|
81
|
+
inputCssClass() {
|
|
82
|
+
const { size } = this;
|
|
83
|
+
return Object.values(Size).find(({ name }) => name === size)?.class;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
methods: {
|
|
87
|
+
/**
|
|
88
|
+
* @param {string} val
|
|
89
|
+
*/
|
|
90
|
+
emitInput(val = '') {
|
|
91
|
+
/**
|
|
92
|
+
* @property {string} value
|
|
93
|
+
*/
|
|
94
|
+
this.$emit('input', val);
|
|
95
|
+
},
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} val
|
|
98
|
+
*/
|
|
99
|
+
emitChange(val = '') {
|
|
100
|
+
/**
|
|
101
|
+
* @property {string} value
|
|
102
|
+
*/
|
|
103
|
+
this.$emit('change', val);
|
|
104
|
+
},
|
|
105
|
+
/**
|
|
106
|
+
* @public
|
|
107
|
+
* Clears the value
|
|
108
|
+
*/
|
|
109
|
+
clear() {
|
|
110
|
+
this.emitInput();
|
|
111
|
+
this.emitChange();
|
|
112
|
+
},
|
|
113
|
+
/**
|
|
114
|
+
* @public
|
|
115
|
+
* Focuses the input
|
|
116
|
+
*/
|
|
117
|
+
focus() {
|
|
118
|
+
this.$refs.input.focus();
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* @param {InputEvent} e
|
|
122
|
+
*/
|
|
123
|
+
onInput({ target }) {
|
|
124
|
+
this.emitInput(target.value);
|
|
125
|
+
},
|
|
126
|
+
/**
|
|
127
|
+
* @param {InputEvent} e
|
|
128
|
+
*/
|
|
129
|
+
onChange({ target }) {
|
|
130
|
+
this.emitChange(target.value);
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* @param {KeyboardEvent} e
|
|
134
|
+
*/
|
|
135
|
+
onKeydownEsc(e) {
|
|
136
|
+
if (this.value.length > 0) {
|
|
137
|
+
e.stopPropagation();
|
|
138
|
+
this.clear();
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
/**
|
|
142
|
+
*/
|
|
143
|
+
onClickClear() {
|
|
144
|
+
this.clear();
|
|
145
|
+
this.focus();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
</script>
|
|
150
|
+
<style scoped lang="pcss">
|
|
151
|
+
.form-control--embedded .input {
|
|
152
|
+
border: none;
|
|
153
|
+
outline: none;
|
|
154
|
+
padding-top: 0;
|
|
155
|
+
padding-bottom: 0;
|
|
156
|
+
min-height: initial;
|
|
157
|
+
}
|
|
158
|
+
</style>
|
|
@@ -2,13 +2,26 @@
|
|
|
2
2
|
<div
|
|
3
3
|
class="ui-select form-elem"
|
|
4
4
|
:class="cssClassExt"
|
|
5
|
-
@keydown
|
|
5
|
+
@keydown="onKeyDown"
|
|
6
6
|
@click="onClick"
|
|
7
7
|
tabindex="0"
|
|
8
8
|
:data-popover="popoverTargetId">
|
|
9
9
|
<div class="ui-select-label u-select-none">
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
<!--
|
|
11
|
+
@slot search slot
|
|
12
|
+
-->
|
|
13
|
+
<slot v-if="search && popoverShow" name="label-search">
|
|
14
|
+
<ui-searchbar
|
|
15
|
+
tabindex="1"
|
|
16
|
+
v-model="searchQuery"
|
|
17
|
+
:size="size"
|
|
18
|
+
embedded
|
|
19
|
+
class="ui-select-searchbar"
|
|
20
|
+
ref="searchbar"
|
|
21
|
+
@click.native.stop></ui-searchbar>
|
|
22
|
+
</slot>
|
|
23
|
+
<template v-else-if="multiple">
|
|
24
|
+
<template v-if="optionsSelected.length > 0">
|
|
12
25
|
<!--
|
|
13
26
|
@slot Label slot for multiple mode
|
|
14
27
|
@binding {Object} option option
|
|
@@ -17,24 +30,32 @@
|
|
|
17
30
|
@binding {Function} deselectOption deselects option function(option:Object)
|
|
18
31
|
-->
|
|
19
32
|
<slot
|
|
20
|
-
name="label
|
|
21
|
-
v-for="(option, index) in optionsSelected"
|
|
33
|
+
name="label"
|
|
22
34
|
v-bind="{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
deselectOption
|
|
35
|
+
selected: optionsSelected,
|
|
36
|
+
deselectOption,
|
|
37
|
+
clear: clearOptions
|
|
27
38
|
}">
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
<slot
|
|
40
|
+
name="label-multiple"
|
|
41
|
+
v-for="(option, index) in optionsSelected"
|
|
42
|
+
v-bind="{
|
|
43
|
+
option,
|
|
44
|
+
value: getOptionValue(option),
|
|
45
|
+
label: getOptionLabel(option),
|
|
46
|
+
deselectOption
|
|
47
|
+
}">
|
|
48
|
+
<ui-badge
|
|
49
|
+
class="mar-none mar-right-2"
|
|
50
|
+
:theme="selectedBadgeTheme"
|
|
51
|
+
size="small"
|
|
52
|
+
:key="index"
|
|
53
|
+
:removable="isSelectedRemovable"
|
|
54
|
+
@click.native.stop
|
|
55
|
+
@remove="deselectOption(option)">
|
|
56
|
+
<span>{{ getOptionLabel(option) }}</span>
|
|
57
|
+
</ui-badge>
|
|
58
|
+
</slot>
|
|
38
59
|
</slot>
|
|
39
60
|
</template>
|
|
40
61
|
<div class="ui-select-placeholder" v-else>
|
|
@@ -55,14 +76,15 @@
|
|
|
55
76
|
@binding {String} label option's label
|
|
56
77
|
-->
|
|
57
78
|
<slot
|
|
79
|
+
v-if="optionsSelected.length > 0"
|
|
58
80
|
name="label"
|
|
59
81
|
v-bind="{
|
|
60
82
|
option: optionsSelected[0],
|
|
61
83
|
value: getOptionValue(optionsSelected[0]),
|
|
62
84
|
label: getOptionLabel(optionsSelected[0]),
|
|
63
|
-
index: getOptionIndex(optionsSelected[0])
|
|
64
|
-
|
|
65
|
-
|
|
85
|
+
index: getOptionIndex(optionsSelected[0]),
|
|
86
|
+
clear: clearOptions
|
|
87
|
+
}">
|
|
66
88
|
{{ getOptionLabel(optionsSelected[0]) }}
|
|
67
89
|
</slot>
|
|
68
90
|
<div class="ui-select-placeholder" v-else>
|
|
@@ -76,6 +98,21 @@
|
|
|
76
98
|
</template>
|
|
77
99
|
</div>
|
|
78
100
|
<!--
|
|
101
|
+
@slot Reset state icon slot
|
|
102
|
+
-->
|
|
103
|
+
<slot
|
|
104
|
+
v-if="clear && optionsSelected.length > 0 && (search === false || popoverShow === false)"
|
|
105
|
+
name="icon-clear"
|
|
106
|
+
v-bind="{ clear: clearOptions }">
|
|
107
|
+
<i
|
|
108
|
+
class="icon icon-clear mdi mdi-close"
|
|
109
|
+
:class="{
|
|
110
|
+
'mdi-18px': size === '',
|
|
111
|
+
'h-auto w-auto': size === 'small'
|
|
112
|
+
}"
|
|
113
|
+
@click.stop="clearOptions"></i>
|
|
114
|
+
</slot>
|
|
115
|
+
<!--
|
|
79
116
|
@slot Open state icon slot
|
|
80
117
|
-->
|
|
81
118
|
<slot name="icon-open" v-if="popoverShow">
|
|
@@ -100,10 +137,11 @@
|
|
|
100
137
|
|
|
101
138
|
<ui-popover :show.sync="popoverShow" v-bind="popoverOptions">
|
|
102
139
|
<ui-datalist
|
|
103
|
-
class="w-100 pull-left"
|
|
140
|
+
class="ui-datalist w-100 pull-left"
|
|
141
|
+
:class="{ multiple }"
|
|
104
142
|
@click.native.stop
|
|
105
143
|
@select-option="onDatalistSelectOption"
|
|
106
|
-
v-bind="{
|
|
144
|
+
v-bind="{ options: optionsInternal, ...dropdownSettings }"
|
|
107
145
|
:cursorIndex.sync="dataListCursorIndex"
|
|
108
146
|
ref="datalist">
|
|
109
147
|
<template #header>
|
|
@@ -111,6 +149,16 @@
|
|
|
111
149
|
@slot Dropdown header slot
|
|
112
150
|
-->
|
|
113
151
|
<slot name="dropdown-header"></slot>
|
|
152
|
+
<slot name="search-empty" v-if="searchQuery.length > 0 && optionsInternal.length === 0">
|
|
153
|
+
<div class="text-center pad-h-l1 pad-v-l1">
|
|
154
|
+
<i
|
|
155
|
+
class="icon mdi mdi-information-off-outline color-grey"
|
|
156
|
+
:class="{
|
|
157
|
+
'mdi-24px': size === '',
|
|
158
|
+
'mdi-18px': size === 'small'
|
|
159
|
+
}"></i>
|
|
160
|
+
</div>
|
|
161
|
+
</slot>
|
|
114
162
|
</template>
|
|
115
163
|
<template #option="{ option, index, cursorIndex }">
|
|
116
164
|
<!--
|
|
@@ -120,6 +168,7 @@
|
|
|
120
168
|
@binding {String} label option's label
|
|
121
169
|
@binding {Number} index option's index
|
|
122
170
|
@binding {Boolean} isSelected option selection status
|
|
171
|
+
@binding {Boolean} isDisabled option availability status
|
|
123
172
|
@binding {Number} cursorIndex current cursor index
|
|
124
173
|
@binding {Function} selectOption function that selects option
|
|
125
174
|
@binding {Function} deselectOption function that deselects option
|
|
@@ -134,6 +183,7 @@
|
|
|
134
183
|
index,
|
|
135
184
|
cursorIndex,
|
|
136
185
|
isSelected: isOptionSelected(option),
|
|
186
|
+
isDisabled: isOptionDisabled(option),
|
|
137
187
|
selectOption,
|
|
138
188
|
deselectOption,
|
|
139
189
|
toggleOption
|
|
@@ -145,6 +195,7 @@
|
|
|
145
195
|
@binding {String} label option's label
|
|
146
196
|
@binding {Number} index option's index
|
|
147
197
|
@binding {Boolean} isSelected option selection status
|
|
198
|
+
@binding {Boolean} isDisabled option availability status
|
|
148
199
|
@binding {Number} cursorIndex current cursor index
|
|
149
200
|
@binding {Function} selectOption function that selects option
|
|
150
201
|
@binding {Function} deselectOption function that deselects option
|
|
@@ -160,6 +211,7 @@
|
|
|
160
211
|
label: getOptionLabel(option),
|
|
161
212
|
index,
|
|
162
213
|
isSelected: isOptionSelected(option),
|
|
214
|
+
isDisabled: isOptionDisabled(option),
|
|
163
215
|
cursorIndex,
|
|
164
216
|
selectOption,
|
|
165
217
|
deselectOption,
|
|
@@ -171,7 +223,8 @@
|
|
|
171
223
|
<li
|
|
172
224
|
:class="{
|
|
173
225
|
active: isOptionSelected(option),
|
|
174
|
-
'bg-grey-lighter': index == cursorIndex
|
|
226
|
+
'bg-grey-lighter': index == cursorIndex,
|
|
227
|
+
'events-none': isOptionDisabled(option)
|
|
175
228
|
}"
|
|
176
229
|
:key="index"
|
|
177
230
|
:title="getOptionLabel(option)"
|
|
@@ -187,6 +240,7 @@
|
|
|
187
240
|
label: getOptionLabel(option),
|
|
188
241
|
index,
|
|
189
242
|
isSelected: isOptionSelected(option),
|
|
243
|
+
isDisabled: isOptionDisabled(option),
|
|
190
244
|
cursorIndex
|
|
191
245
|
}">
|
|
192
246
|
{{ getOptionLabel(option) }}
|
|
@@ -223,6 +277,7 @@
|
|
|
223
277
|
@binding {String} label option's label
|
|
224
278
|
@binding {Number} index option's index
|
|
225
279
|
@binding {Boolean} isSelected option selection status
|
|
280
|
+
@binding {Boolean} isDisabled option availability status
|
|
226
281
|
@binding {Number} cursorIndex current cursor index
|
|
227
282
|
@binding {Function} selectOption function that selects option
|
|
228
283
|
@binding {Function} deselectOption function that deselects option
|
|
@@ -236,6 +291,7 @@
|
|
|
236
291
|
label: getOptionLabel(option),
|
|
237
292
|
index,
|
|
238
293
|
isSelected: isOptionSelected(option),
|
|
294
|
+
isDisabled: isOptionDisabled(option),
|
|
239
295
|
cursorIndex,
|
|
240
296
|
selectOption,
|
|
241
297
|
deselectOption,
|
|
@@ -264,6 +320,10 @@
|
|
|
264
320
|
flex: 1 0 0;
|
|
265
321
|
}
|
|
266
322
|
|
|
323
|
+
.icon-clear {
|
|
324
|
+
cursor: pointer;
|
|
325
|
+
}
|
|
326
|
+
|
|
267
327
|
&-placeholder {
|
|
268
328
|
&-input {
|
|
269
329
|
font-size: inherit;
|
|
@@ -275,6 +335,29 @@
|
|
|
275
335
|
color: inherit;
|
|
276
336
|
}
|
|
277
337
|
}
|
|
338
|
+
|
|
339
|
+
&-searchbar {
|
|
340
|
+
margin-left: calc(-1 * var(--spacer3));
|
|
341
|
+
width: calc(100% + var(--spacer5));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.ui-datalist {
|
|
346
|
+
.active:hover {
|
|
347
|
+
background-color: var(--color-primary-hover);
|
|
348
|
+
}
|
|
349
|
+
&.multiple {
|
|
350
|
+
li + li {
|
|
351
|
+
border-top: 1px solid transparent;
|
|
352
|
+
}
|
|
353
|
+
.active + .active {
|
|
354
|
+
border-color: var(--color-primary-hover);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.popover-badges {
|
|
360
|
+
margin-top: -1px;
|
|
278
361
|
}
|
|
279
362
|
</style>
|
|
280
363
|
<script>
|
|
@@ -283,16 +366,71 @@ import UiDatalist from './Datalist.vue';
|
|
|
283
366
|
import UiPopover from './Popover.vue';
|
|
284
367
|
import FormComponent from './utils/FormComponent';
|
|
285
368
|
import WithPopover from './utils/WithPopover';
|
|
369
|
+
import UiSearchbar from './Searchbar.vue';
|
|
286
370
|
import { Key } from './utils/Helpers';
|
|
287
371
|
|
|
372
|
+
/**
|
|
373
|
+
* @typedef {{
|
|
374
|
+
* valueField?: string,
|
|
375
|
+
* labelField?: string,
|
|
376
|
+
* disabledField?: string,
|
|
377
|
+
* valueObjects?: boolean,
|
|
378
|
+
* valueOfOptionChecker?: (option: { label: string, value: any } | Record<string, any>) => boolean
|
|
379
|
+
* }} OptionSettings
|
|
380
|
+
*
|
|
381
|
+
* @typedef {{
|
|
382
|
+
* class?: string|string[],
|
|
383
|
+
* maxHeight?: null|string,
|
|
384
|
+
* maxItems?: number,
|
|
385
|
+
* itemHeight?: null|string
|
|
386
|
+
* }} DropdownSettings
|
|
387
|
+
*
|
|
388
|
+
* @typedef {{
|
|
389
|
+
* pin?: boolean
|
|
390
|
+
* }} MultipleSettings
|
|
391
|
+
*/
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* @type {DropdownSettings}
|
|
395
|
+
*/
|
|
396
|
+
const DropdownSettingsDefault = {
|
|
397
|
+
class: '',
|
|
398
|
+
maxHeight: null,
|
|
399
|
+
maxItems: 5,
|
|
400
|
+
itemHeight: null
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* @type {OptionSettings}
|
|
405
|
+
*/
|
|
406
|
+
const OptionSettingsDefault = {
|
|
407
|
+
valueField: 'value',
|
|
408
|
+
labelField: 'label',
|
|
409
|
+
disabledField: 'disabled',
|
|
410
|
+
valueObjects: false,
|
|
411
|
+
valueOfOptionChecker: null
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* @type {MultipleSettings}
|
|
416
|
+
*/
|
|
417
|
+
const MultipleSettingsDefault = {
|
|
418
|
+
pin: false
|
|
419
|
+
};
|
|
420
|
+
|
|
288
421
|
export default {
|
|
289
422
|
components: {
|
|
290
423
|
UiBadge,
|
|
291
424
|
UiDatalist,
|
|
292
|
-
UiPopover
|
|
425
|
+
UiPopover,
|
|
426
|
+
UiSearchbar
|
|
293
427
|
},
|
|
294
428
|
mixins: [FormComponent, WithPopover],
|
|
295
429
|
props: {
|
|
430
|
+
/**
|
|
431
|
+
*/
|
|
432
|
+
...FormComponent.props,
|
|
433
|
+
...WithPopover.props,
|
|
296
434
|
/**
|
|
297
435
|
* @model
|
|
298
436
|
*/
|
|
@@ -312,14 +450,51 @@ export default {
|
|
|
312
450
|
},
|
|
313
451
|
/**
|
|
314
452
|
* Allow multiple selection
|
|
453
|
+
* @type {import('vue').PropOptions<boolean | MultipleSettings>}
|
|
315
454
|
*/
|
|
316
455
|
multiple: {
|
|
456
|
+
type: [Boolean, Object],
|
|
457
|
+
default: false
|
|
458
|
+
},
|
|
459
|
+
/**
|
|
460
|
+
* Allow clear with cross (close) icon
|
|
461
|
+
*/
|
|
462
|
+
clear: {
|
|
317
463
|
type: Boolean,
|
|
318
464
|
default: false
|
|
319
465
|
},
|
|
466
|
+
/**
|
|
467
|
+
* Allow embedded search
|
|
468
|
+
* @type {import('vue').PropOptions<boolean | {
|
|
469
|
+
* clear?: boolean,
|
|
470
|
+
* }>}
|
|
471
|
+
*/
|
|
472
|
+
search: {
|
|
473
|
+
type: [Boolean, Object],
|
|
474
|
+
default: false
|
|
475
|
+
},
|
|
476
|
+
/**
|
|
477
|
+
* Allow multiple selection
|
|
478
|
+
* @type {import('vue').PropOptions<OptionSettings>}
|
|
479
|
+
*/
|
|
480
|
+
option: {
|
|
481
|
+
type: Object,
|
|
482
|
+
default() {
|
|
483
|
+
return {
|
|
484
|
+
/*
|
|
485
|
+
valueField: 'value',
|
|
486
|
+
labelField: 'label',
|
|
487
|
+
disabledField: '',
|
|
488
|
+
valueObjects: false,
|
|
489
|
+
valueOfOptionChecker: null
|
|
490
|
+
*/
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
},
|
|
320
494
|
/**
|
|
321
495
|
* Defines whether 'value' is the option value field or option object
|
|
322
496
|
* @see options
|
|
497
|
+
* @deprecated
|
|
323
498
|
*/
|
|
324
499
|
valueObjects: {
|
|
325
500
|
type: Boolean,
|
|
@@ -327,6 +502,7 @@ export default {
|
|
|
327
502
|
},
|
|
328
503
|
/**
|
|
329
504
|
* Defines the 'value' field of the option Object
|
|
505
|
+
* @deprecated
|
|
330
506
|
*/
|
|
331
507
|
valueField: {
|
|
332
508
|
type: [String, Symbol],
|
|
@@ -334,56 +510,75 @@ export default {
|
|
|
334
510
|
},
|
|
335
511
|
/**
|
|
336
512
|
* Defines the 'label' field of the option Object
|
|
513
|
+
* @deprecated
|
|
337
514
|
*/
|
|
338
515
|
labelField: {
|
|
339
516
|
type: [String, Symbol],
|
|
340
517
|
default: 'label'
|
|
341
518
|
},
|
|
342
|
-
|
|
343
|
-
|
|
519
|
+
/**
|
|
520
|
+
* Defines the 'disabled' field of the option Object
|
|
521
|
+
* @deprecated
|
|
522
|
+
*/
|
|
523
|
+
disabledField: {
|
|
524
|
+
type: [String, Symbol],
|
|
525
|
+
default: 'disabled'
|
|
344
526
|
},
|
|
345
527
|
/**
|
|
346
528
|
* Alternative function to detect if option Object selected
|
|
347
529
|
* by compare model value with option Object valueField value
|
|
530
|
+
* @deprecated
|
|
348
531
|
*/
|
|
349
532
|
valueOfOptionChecker: {
|
|
350
533
|
type: Function,
|
|
351
534
|
default: null
|
|
352
535
|
},
|
|
353
536
|
// DATALIST OPTIONS
|
|
537
|
+
/**
|
|
538
|
+
* Dropdown extra config options
|
|
539
|
+
* @type {import('vue').PropOptions<DropdownSettings>}
|
|
540
|
+
*/
|
|
541
|
+
datalist: {
|
|
542
|
+
/*
|
|
543
|
+
class: '',
|
|
544
|
+
maxHeight: null,
|
|
545
|
+
maxItems: 5,
|
|
546
|
+
itemHeight: null
|
|
547
|
+
*/
|
|
548
|
+
},
|
|
354
549
|
/**
|
|
355
550
|
* Datalist css classes (optional)
|
|
551
|
+
* @deprecated
|
|
356
552
|
*/
|
|
357
553
|
datalistCssClass: {
|
|
358
554
|
type: [String, Array],
|
|
359
555
|
default: ''
|
|
360
556
|
},
|
|
557
|
+
// Popover Options
|
|
361
558
|
/**
|
|
362
|
-
*
|
|
559
|
+
* @type {import('vue').PropOptions<boolean>}
|
|
560
|
+
* @deprecated
|
|
363
561
|
*/
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
default: null
|
|
367
|
-
},
|
|
368
|
-
/**
|
|
369
|
-
* Max visible items count
|
|
370
|
-
*/
|
|
371
|
-
maxItems: {
|
|
372
|
-
type: Number,
|
|
373
|
-
default: 5
|
|
562
|
+
autoWidth: {
|
|
563
|
+
default: true
|
|
374
564
|
},
|
|
375
565
|
/**
|
|
376
|
-
*
|
|
566
|
+
* @type {import('vue').PropOptions<import('./utils/WithPopover').PopoverOptions>}
|
|
377
567
|
*/
|
|
378
|
-
|
|
379
|
-
type:
|
|
380
|
-
default
|
|
568
|
+
popover: {
|
|
569
|
+
type: Object,
|
|
570
|
+
default() {
|
|
571
|
+
return {
|
|
572
|
+
autoWidth: true
|
|
573
|
+
};
|
|
574
|
+
}
|
|
381
575
|
}
|
|
382
576
|
},
|
|
383
577
|
data() {
|
|
384
578
|
return {
|
|
385
579
|
optionsSelected: [],
|
|
386
|
-
dataListCursorIndex: -1
|
|
580
|
+
dataListCursorIndex: -1,
|
|
581
|
+
searchQuery: ''
|
|
387
582
|
};
|
|
388
583
|
},
|
|
389
584
|
computed: {
|
|
@@ -402,6 +597,54 @@ export default {
|
|
|
402
597
|
},
|
|
403
598
|
isSelectedRemovable() {
|
|
404
599
|
return this.readonly === false && this.disabled === false;
|
|
600
|
+
},
|
|
601
|
+
optionsInternal() {
|
|
602
|
+
let {
|
|
603
|
+
searchQuery,
|
|
604
|
+
options,
|
|
605
|
+
getOptionsOrdered,
|
|
606
|
+
getOptionLabel,
|
|
607
|
+
multiple: { pin = false }
|
|
608
|
+
} = this;
|
|
609
|
+
if (pin === true) {
|
|
610
|
+
options = getOptionsOrdered();
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (searchQuery === '') {
|
|
614
|
+
return options;
|
|
615
|
+
}
|
|
616
|
+
searchQuery = searchQuery.toLowerCase();
|
|
617
|
+
return options.filter((option) => getOptionLabel(option).toLowerCase().includes(searchQuery));
|
|
618
|
+
},
|
|
619
|
+
/**
|
|
620
|
+
* @return {OptionSettings}
|
|
621
|
+
*/
|
|
622
|
+
optionSettings() {
|
|
623
|
+
const { labelField, labelValue, disabledField, labelObjects, valueOfOptionChecker, option } = this;
|
|
624
|
+
return {
|
|
625
|
+
...OptionSettingsDefault,
|
|
626
|
+
labelField,
|
|
627
|
+
labelValue,
|
|
628
|
+
disabledField,
|
|
629
|
+
labelObjects,
|
|
630
|
+
valueOfOptionChecker,
|
|
631
|
+
...option
|
|
632
|
+
};
|
|
633
|
+
},
|
|
634
|
+
/**
|
|
635
|
+
* @return {DropdownSettings & { size: string }}
|
|
636
|
+
*/
|
|
637
|
+
dropdownSettings() {
|
|
638
|
+
const { datalistCssClass, maxItems, itemHeight, maxHeight, datalist, size } = this;
|
|
639
|
+
return {
|
|
640
|
+
...DropdownSettingsDefault,
|
|
641
|
+
class: datalistCssClass,
|
|
642
|
+
maxItems,
|
|
643
|
+
itemHeight,
|
|
644
|
+
maxHeight,
|
|
645
|
+
size,
|
|
646
|
+
...datalist
|
|
647
|
+
};
|
|
405
648
|
}
|
|
406
649
|
},
|
|
407
650
|
watch: {
|
|
@@ -416,11 +659,15 @@ export default {
|
|
|
416
659
|
},
|
|
417
660
|
popoverShow(isPopoverShown) {
|
|
418
661
|
this.$emit('options-toggle', isPopoverShown);
|
|
662
|
+
if (this.search !== false) {
|
|
663
|
+
this.handleSearchbar(isPopoverShown);
|
|
664
|
+
}
|
|
419
665
|
}
|
|
420
666
|
},
|
|
421
667
|
methods: {
|
|
422
668
|
isValueOfOptionDefault(option, modelItem) {
|
|
423
|
-
const
|
|
669
|
+
const { valueObjects } = this.optionSettings;
|
|
670
|
+
const modelValue = valueObjects ? this.getOptionValue(modelItem) : modelItem;
|
|
424
671
|
const optionValue = this.getOptionValue(option);
|
|
425
672
|
return modelValue === optionValue;
|
|
426
673
|
},
|
|
@@ -430,7 +677,10 @@ export default {
|
|
|
430
677
|
* @return {boolean}
|
|
431
678
|
*/
|
|
432
679
|
isValueOfOption(option, modelItem) {
|
|
433
|
-
const {
|
|
680
|
+
const {
|
|
681
|
+
optionSettings: { valueOfOptionChecker },
|
|
682
|
+
isValueOfOptionDefault
|
|
683
|
+
} = this;
|
|
434
684
|
const isTrue = valueOfOptionChecker ?? isValueOfOptionDefault;
|
|
435
685
|
return isTrue(option, modelItem);
|
|
436
686
|
},
|
|
@@ -452,10 +702,28 @@ export default {
|
|
|
452
702
|
}
|
|
453
703
|
},
|
|
454
704
|
exportModel() {
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
705
|
+
const {
|
|
706
|
+
optionsSelected,
|
|
707
|
+
multiple,
|
|
708
|
+
optionSettings: { valueObjects }
|
|
709
|
+
} = this;
|
|
710
|
+
const model = optionsSelected.map((option) => (valueObjects ? option : this.getOptionValue(option)));
|
|
711
|
+
return multiple ? model : model[0] ?? null;
|
|
712
|
+
},
|
|
713
|
+
/**
|
|
714
|
+
* @return {Option[]}
|
|
715
|
+
*/
|
|
716
|
+
getOptionsOrdered() {
|
|
717
|
+
let { options, optionsSelected, getOptionValue } = this;
|
|
718
|
+
if (optionsSelected.length === 0) {
|
|
719
|
+
return options;
|
|
720
|
+
}
|
|
721
|
+
const selectedValues = optionsSelected.map((option) => getOptionValue(option));
|
|
722
|
+
const restOptions = options.filter((option) => {
|
|
723
|
+
const value = this.getOptionValue(option);
|
|
724
|
+
return selectedValues.every((selectedValue) => selectedValue !== value);
|
|
725
|
+
});
|
|
726
|
+
return [...optionsSelected, ...restOptions];
|
|
459
727
|
},
|
|
460
728
|
/**
|
|
461
729
|
*
|
|
@@ -475,21 +743,54 @@ export default {
|
|
|
475
743
|
*/
|
|
476
744
|
this.$emit('change', value, { cancel: rollback });
|
|
477
745
|
},
|
|
746
|
+
/**
|
|
747
|
+
* @param {Option} option
|
|
748
|
+
* @return {string}
|
|
749
|
+
*/
|
|
478
750
|
getOptionLabel(option) {
|
|
479
|
-
|
|
751
|
+
const {
|
|
752
|
+
optionSettings: { labelField }
|
|
753
|
+
} = this;
|
|
754
|
+
let label = option ? option[labelField] : null;
|
|
480
755
|
return label == null ? option : label;
|
|
481
756
|
},
|
|
757
|
+
/**
|
|
758
|
+
* @param {Option} option
|
|
759
|
+
* @return {any}
|
|
760
|
+
*/
|
|
482
761
|
getOptionValue(option) {
|
|
483
|
-
|
|
762
|
+
const {
|
|
763
|
+
optionSettings: { valueField }
|
|
764
|
+
} = this;
|
|
765
|
+
let value = option ? option[valueField] : null;
|
|
484
766
|
// @NOTE option.value might be 'null'
|
|
485
767
|
return value === undefined ? option : value;
|
|
486
768
|
},
|
|
769
|
+
/**
|
|
770
|
+
* @param {Option} option
|
|
771
|
+
* @return {number}
|
|
772
|
+
*/
|
|
487
773
|
getOptionIndex(option) {
|
|
488
|
-
return this.
|
|
774
|
+
return this.optionsInternal.indexOf(option);
|
|
489
775
|
},
|
|
776
|
+
/**
|
|
777
|
+
* @param {Option} option
|
|
778
|
+
* @return {boolean}
|
|
779
|
+
*/
|
|
490
780
|
isOptionSelected(option) {
|
|
491
781
|
return this.optionsSelected.includes(option);
|
|
492
782
|
},
|
|
783
|
+
/**
|
|
784
|
+
* @param {Option} option
|
|
785
|
+
* @return {boolean}
|
|
786
|
+
*/
|
|
787
|
+
isOptionDisabled(option) {
|
|
788
|
+
const {
|
|
789
|
+
optionSettings: { disabledField }
|
|
790
|
+
} = this;
|
|
791
|
+
let disabled = option ? option[disabledField] : null;
|
|
792
|
+
return Boolean(disabled);
|
|
793
|
+
},
|
|
493
794
|
createOptionRollback() {
|
|
494
795
|
const optionsSelected = [...this.optionsSelected];
|
|
495
796
|
return () => {
|
|
@@ -498,6 +799,9 @@ export default {
|
|
|
498
799
|
this.triggerModelChange(rollback);
|
|
499
800
|
};
|
|
500
801
|
},
|
|
802
|
+
/**
|
|
803
|
+
* @param {Option} option
|
|
804
|
+
*/
|
|
501
805
|
selectOption(option) {
|
|
502
806
|
if (this.readonly) {
|
|
503
807
|
return;
|
|
@@ -516,34 +820,57 @@ export default {
|
|
|
516
820
|
|
|
517
821
|
this.triggerModelChange(rollback);
|
|
518
822
|
},
|
|
823
|
+
/**
|
|
824
|
+
* @param {Option} option
|
|
825
|
+
*/
|
|
519
826
|
deselectOption(option) {
|
|
520
|
-
|
|
827
|
+
const { multiple, readonly, optionsSelected } = this;
|
|
828
|
+
if (readonly) {
|
|
521
829
|
return;
|
|
522
830
|
}
|
|
523
|
-
if (!
|
|
831
|
+
if (!multiple) {
|
|
524
832
|
this.popoverShow = false;
|
|
525
833
|
return;
|
|
526
834
|
}
|
|
527
835
|
const rollback = this.createOptionRollback();
|
|
528
|
-
|
|
836
|
+
optionsSelected.splice(optionsSelected.indexOf(option), 1);
|
|
529
837
|
this.triggerModelChange(rollback);
|
|
530
838
|
},
|
|
839
|
+
/**
|
|
840
|
+
* @param {Option} option
|
|
841
|
+
*/
|
|
531
842
|
toggleOption(option) {
|
|
532
|
-
|
|
533
|
-
|
|
843
|
+
const { isOptionSelected, selectOption, deselectOption } = this;
|
|
844
|
+
if (!isOptionSelected(option)) {
|
|
845
|
+
selectOption(option);
|
|
534
846
|
} else {
|
|
535
|
-
|
|
847
|
+
deselectOption(option);
|
|
536
848
|
}
|
|
537
849
|
},
|
|
850
|
+
clearOptions() {
|
|
851
|
+
const rollback = this.createOptionRollback();
|
|
852
|
+
this.optionsSelected = [];
|
|
853
|
+
this.triggerModelChange(rollback);
|
|
854
|
+
},
|
|
538
855
|
getDatalistRef() {
|
|
539
856
|
return this.$refs.datalist;
|
|
540
857
|
},
|
|
541
|
-
|
|
858
|
+
handleSearchbar(isPopoverShown) {
|
|
859
|
+
if (!isPopoverShown) {
|
|
860
|
+
this.searchQuery = '';
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
this.$nextTick(() => {
|
|
864
|
+
this.$refs.searchbar.focus();
|
|
865
|
+
});
|
|
866
|
+
},
|
|
867
|
+
onClick() {
|
|
868
|
+
const { popoverShow, optionsSelected } = this;
|
|
542
869
|
this.togglePopover();
|
|
543
870
|
this.rootHasFocus = true;
|
|
544
871
|
this.$el.focus();
|
|
545
|
-
if (
|
|
546
|
-
this.dataListCursorIndex = this.getOptionIndex(
|
|
872
|
+
if (popoverShow && optionsSelected.length > 0) {
|
|
873
|
+
this.dataListCursorIndex = this.getOptionIndex(optionsSelected[0]);
|
|
547
874
|
}
|
|
548
875
|
},
|
|
549
876
|
onDatalistSelectOption({ option }) {
|
|
@@ -553,11 +880,13 @@ export default {
|
|
|
553
880
|
let list = this.getDatalistRef();
|
|
554
881
|
if (e.key === Key.ESC) {
|
|
555
882
|
if (this.popoverShow) {
|
|
883
|
+
e.preventDefault();
|
|
556
884
|
e.stopPropagation();
|
|
557
885
|
this.popoverShow = false;
|
|
558
886
|
return;
|
|
559
887
|
}
|
|
560
888
|
if (this.rootHasFocus) {
|
|
889
|
+
e.preventDefault();
|
|
561
890
|
e.stopPropagation();
|
|
562
891
|
this.rootHasFocus = false;
|
|
563
892
|
this.$el.blur();
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { nextId, Position } from './Helpers';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{
|
|
5
|
+
* zIndex?: number,
|
|
6
|
+
* appendToBody?: boolean,
|
|
7
|
+
* position?: string,
|
|
8
|
+
* positionOffset?: [number, number],
|
|
9
|
+
* autoWidth?: boolean
|
|
10
|
+
* }} PopoverOptions
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const PopoverOptionsDefault = {
|
|
14
|
+
zIndex: 1010,
|
|
15
|
+
appendToBody: true,
|
|
16
|
+
position: `${Position.BOTTOM}-${Position.START}`,
|
|
17
|
+
positionOffset: [1, 1],
|
|
18
|
+
autoWidth: false
|
|
19
|
+
};
|
|
20
|
+
|
|
3
21
|
export default {
|
|
4
22
|
props: {
|
|
5
23
|
/**
|
|
@@ -42,6 +60,18 @@ export default {
|
|
|
42
60
|
autoWidth: {
|
|
43
61
|
type: Boolean,
|
|
44
62
|
default: false,
|
|
63
|
+
},
|
|
64
|
+
popover: {
|
|
65
|
+
type: Object,
|
|
66
|
+
default: () => ({
|
|
67
|
+
/*
|
|
68
|
+
zIndex: -1,
|
|
69
|
+
appendToBody: true,
|
|
70
|
+
position: `${Position.BOTTOM}-${Position.START}`,
|
|
71
|
+
positionOffset: [1, 1],
|
|
72
|
+
autoWidth: false
|
|
73
|
+
*/
|
|
74
|
+
}),
|
|
45
75
|
}
|
|
46
76
|
},
|
|
47
77
|
data() {
|
|
@@ -55,21 +85,24 @@ export default {
|
|
|
55
85
|
return `[data-popover=${this.popoverTargetId}]`;
|
|
56
86
|
},
|
|
57
87
|
popoverOptions() {
|
|
58
|
-
|
|
88
|
+
const {
|
|
59
89
|
zIndex,
|
|
60
90
|
appendToBody,
|
|
61
91
|
position,
|
|
62
92
|
positionOffset,
|
|
63
93
|
autoWidth,
|
|
64
|
-
popoverTarget: target
|
|
94
|
+
popoverTarget: target,
|
|
95
|
+
popover
|
|
65
96
|
} = this;
|
|
66
97
|
return {
|
|
98
|
+
...PopoverOptionsDefault,
|
|
67
99
|
zIndex,
|
|
68
100
|
appendToBody,
|
|
69
101
|
position,
|
|
70
102
|
positionOffset,
|
|
71
103
|
autoWidth,
|
|
72
|
-
target
|
|
104
|
+
target,
|
|
105
|
+
...popover
|
|
73
106
|
};
|
|
74
107
|
},
|
|
75
108
|
},
|