goodteditor-ui 1.0.96 → 1.0.98
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
|
<!--
|
|
@@ -176,7 +224,7 @@
|
|
|
176
224
|
:class="{
|
|
177
225
|
active: isOptionSelected(option),
|
|
178
226
|
'bg-grey-lighter': index == cursorIndex,
|
|
179
|
-
|
|
227
|
+
disabled: isOptionDisabled(option)
|
|
180
228
|
}"
|
|
181
229
|
:key="index"
|
|
182
230
|
:title="getOptionLabel(option)"
|
|
@@ -272,6 +320,10 @@
|
|
|
272
320
|
flex: 1 0 0;
|
|
273
321
|
}
|
|
274
322
|
|
|
323
|
+
.icon-clear {
|
|
324
|
+
cursor: pointer;
|
|
325
|
+
}
|
|
326
|
+
|
|
275
327
|
&-placeholder {
|
|
276
328
|
&-input {
|
|
277
329
|
font-size: inherit;
|
|
@@ -283,6 +335,29 @@
|
|
|
283
335
|
color: inherit;
|
|
284
336
|
}
|
|
285
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;
|
|
286
361
|
}
|
|
287
362
|
</style>
|
|
288
363
|
<script>
|
|
@@ -291,16 +366,71 @@ import UiDatalist from './Datalist.vue';
|
|
|
291
366
|
import UiPopover from './Popover.vue';
|
|
292
367
|
import FormComponent from './utils/FormComponent';
|
|
293
368
|
import WithPopover from './utils/WithPopover';
|
|
369
|
+
import UiSearchbar from './Searchbar.vue';
|
|
294
370
|
import { Key } from './utils/Helpers';
|
|
295
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
|
+
|
|
296
421
|
export default {
|
|
297
422
|
components: {
|
|
298
423
|
UiBadge,
|
|
299
424
|
UiDatalist,
|
|
300
|
-
UiPopover
|
|
425
|
+
UiPopover,
|
|
426
|
+
UiSearchbar
|
|
301
427
|
},
|
|
302
428
|
mixins: [FormComponent, WithPopover],
|
|
303
429
|
props: {
|
|
430
|
+
/**
|
|
431
|
+
*/
|
|
432
|
+
...FormComponent.props,
|
|
433
|
+
...WithPopover.props,
|
|
304
434
|
/**
|
|
305
435
|
* @model
|
|
306
436
|
*/
|
|
@@ -320,14 +450,51 @@ export default {
|
|
|
320
450
|
},
|
|
321
451
|
/**
|
|
322
452
|
* Allow multiple selection
|
|
453
|
+
* @type {import('vue').PropOptions<boolean | MultipleSettings>}
|
|
323
454
|
*/
|
|
324
455
|
multiple: {
|
|
456
|
+
type: [Boolean, Object],
|
|
457
|
+
default: false
|
|
458
|
+
},
|
|
459
|
+
/**
|
|
460
|
+
* Allow clear with cross (close) icon
|
|
461
|
+
*/
|
|
462
|
+
clear: {
|
|
325
463
|
type: Boolean,
|
|
326
464
|
default: false
|
|
327
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
|
+
},
|
|
328
494
|
/**
|
|
329
495
|
* Defines whether 'value' is the option value field or option object
|
|
330
496
|
* @see options
|
|
497
|
+
* @deprecated
|
|
331
498
|
*/
|
|
332
499
|
valueObjects: {
|
|
333
500
|
type: Boolean,
|
|
@@ -335,6 +502,7 @@ export default {
|
|
|
335
502
|
},
|
|
336
503
|
/**
|
|
337
504
|
* Defines the 'value' field of the option Object
|
|
505
|
+
* @deprecated
|
|
338
506
|
*/
|
|
339
507
|
valueField: {
|
|
340
508
|
type: [String, Symbol],
|
|
@@ -342,6 +510,7 @@ export default {
|
|
|
342
510
|
},
|
|
343
511
|
/**
|
|
344
512
|
* Defines the 'label' field of the option Object
|
|
513
|
+
* @deprecated
|
|
345
514
|
*/
|
|
346
515
|
labelField: {
|
|
347
516
|
type: [String, Symbol],
|
|
@@ -349,56 +518,67 @@ export default {
|
|
|
349
518
|
},
|
|
350
519
|
/**
|
|
351
520
|
* Defines the 'disabled' field of the option Object
|
|
521
|
+
* @deprecated
|
|
352
522
|
*/
|
|
353
523
|
disabledField: {
|
|
354
524
|
type: [String, Symbol],
|
|
355
525
|
default: 'disabled'
|
|
356
526
|
},
|
|
357
|
-
autoWidth: {
|
|
358
|
-
default: true
|
|
359
|
-
},
|
|
360
527
|
/**
|
|
361
528
|
* Alternative function to detect if option Object selected
|
|
362
529
|
* by compare model value with option Object valueField value
|
|
530
|
+
* @deprecated
|
|
363
531
|
*/
|
|
364
532
|
valueOfOptionChecker: {
|
|
365
533
|
type: Function,
|
|
366
534
|
default: null
|
|
367
535
|
},
|
|
368
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
|
+
},
|
|
369
549
|
/**
|
|
370
550
|
* Datalist css classes (optional)
|
|
551
|
+
* @deprecated
|
|
371
552
|
*/
|
|
372
553
|
datalistCssClass: {
|
|
373
554
|
type: [String, Array],
|
|
374
555
|
default: ''
|
|
375
556
|
},
|
|
557
|
+
// Popover Options
|
|
376
558
|
/**
|
|
377
|
-
*
|
|
559
|
+
* @type {import('vue').PropOptions<boolean>}
|
|
560
|
+
* @deprecated
|
|
378
561
|
*/
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
default: null
|
|
382
|
-
},
|
|
383
|
-
/**
|
|
384
|
-
* Max visible items count
|
|
385
|
-
*/
|
|
386
|
-
maxItems: {
|
|
387
|
-
type: Number,
|
|
388
|
-
default: 5
|
|
562
|
+
autoWidth: {
|
|
563
|
+
default: true
|
|
389
564
|
},
|
|
390
565
|
/**
|
|
391
|
-
*
|
|
566
|
+
* @type {import('vue').PropOptions<import('./utils/WithPopover').PopoverOptions>}
|
|
392
567
|
*/
|
|
393
|
-
|
|
394
|
-
type:
|
|
395
|
-
default
|
|
568
|
+
popover: {
|
|
569
|
+
type: Object,
|
|
570
|
+
default() {
|
|
571
|
+
return {
|
|
572
|
+
autoWidth: true
|
|
573
|
+
};
|
|
574
|
+
}
|
|
396
575
|
}
|
|
397
576
|
},
|
|
398
577
|
data() {
|
|
399
578
|
return {
|
|
400
579
|
optionsSelected: [],
|
|
401
|
-
dataListCursorIndex: -1
|
|
580
|
+
dataListCursorIndex: -1,
|
|
581
|
+
searchQuery: ''
|
|
402
582
|
};
|
|
403
583
|
},
|
|
404
584
|
computed: {
|
|
@@ -417,6 +597,54 @@ export default {
|
|
|
417
597
|
},
|
|
418
598
|
isSelectedRemovable() {
|
|
419
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
|
+
};
|
|
420
648
|
}
|
|
421
649
|
},
|
|
422
650
|
watch: {
|
|
@@ -431,11 +659,15 @@ export default {
|
|
|
431
659
|
},
|
|
432
660
|
popoverShow(isPopoverShown) {
|
|
433
661
|
this.$emit('options-toggle', isPopoverShown);
|
|
662
|
+
if (this.search !== false) {
|
|
663
|
+
this.handleSearchbar(isPopoverShown);
|
|
664
|
+
}
|
|
434
665
|
}
|
|
435
666
|
},
|
|
436
667
|
methods: {
|
|
437
668
|
isValueOfOptionDefault(option, modelItem) {
|
|
438
|
-
const
|
|
669
|
+
const { valueObjects } = this.optionSettings;
|
|
670
|
+
const modelValue = valueObjects ? this.getOptionValue(modelItem) : modelItem;
|
|
439
671
|
const optionValue = this.getOptionValue(option);
|
|
440
672
|
return modelValue === optionValue;
|
|
441
673
|
},
|
|
@@ -445,7 +677,10 @@ export default {
|
|
|
445
677
|
* @return {boolean}
|
|
446
678
|
*/
|
|
447
679
|
isValueOfOption(option, modelItem) {
|
|
448
|
-
const {
|
|
680
|
+
const {
|
|
681
|
+
optionSettings: { valueOfOptionChecker },
|
|
682
|
+
isValueOfOptionDefault
|
|
683
|
+
} = this;
|
|
449
684
|
const isTrue = valueOfOptionChecker ?? isValueOfOptionDefault;
|
|
450
685
|
return isTrue(option, modelItem);
|
|
451
686
|
},
|
|
@@ -467,10 +702,28 @@ export default {
|
|
|
467
702
|
}
|
|
468
703
|
},
|
|
469
704
|
exportModel() {
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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];
|
|
474
727
|
},
|
|
475
728
|
/**
|
|
476
729
|
*
|
|
@@ -490,23 +743,52 @@ export default {
|
|
|
490
743
|
*/
|
|
491
744
|
this.$emit('change', value, { cancel: rollback });
|
|
492
745
|
},
|
|
746
|
+
/**
|
|
747
|
+
* @param {Option} option
|
|
748
|
+
* @return {string}
|
|
749
|
+
*/
|
|
493
750
|
getOptionLabel(option) {
|
|
494
|
-
|
|
751
|
+
const {
|
|
752
|
+
optionSettings: { labelField }
|
|
753
|
+
} = this;
|
|
754
|
+
let label = option ? option[labelField] : null;
|
|
495
755
|
return label == null ? option : label;
|
|
496
756
|
},
|
|
757
|
+
/**
|
|
758
|
+
* @param {Option} option
|
|
759
|
+
* @return {any}
|
|
760
|
+
*/
|
|
497
761
|
getOptionValue(option) {
|
|
498
|
-
|
|
762
|
+
const {
|
|
763
|
+
optionSettings: { valueField }
|
|
764
|
+
} = this;
|
|
765
|
+
let value = option ? option[valueField] : null;
|
|
499
766
|
// @NOTE option.value might be 'null'
|
|
500
767
|
return value === undefined ? option : value;
|
|
501
768
|
},
|
|
769
|
+
/**
|
|
770
|
+
* @param {Option} option
|
|
771
|
+
* @return {number}
|
|
772
|
+
*/
|
|
502
773
|
getOptionIndex(option) {
|
|
503
|
-
return this.
|
|
774
|
+
return this.optionsInternal.indexOf(option);
|
|
504
775
|
},
|
|
776
|
+
/**
|
|
777
|
+
* @param {Option} option
|
|
778
|
+
* @return {boolean}
|
|
779
|
+
*/
|
|
505
780
|
isOptionSelected(option) {
|
|
506
781
|
return this.optionsSelected.includes(option);
|
|
507
782
|
},
|
|
783
|
+
/**
|
|
784
|
+
* @param {Option} option
|
|
785
|
+
* @return {boolean}
|
|
786
|
+
*/
|
|
508
787
|
isOptionDisabled(option) {
|
|
509
|
-
|
|
788
|
+
const {
|
|
789
|
+
optionSettings: { disabledField }
|
|
790
|
+
} = this;
|
|
791
|
+
let disabled = option ? option[disabledField] : null;
|
|
510
792
|
return Boolean(disabled);
|
|
511
793
|
},
|
|
512
794
|
createOptionRollback() {
|
|
@@ -517,6 +799,9 @@ export default {
|
|
|
517
799
|
this.triggerModelChange(rollback);
|
|
518
800
|
};
|
|
519
801
|
},
|
|
802
|
+
/**
|
|
803
|
+
* @param {Option} option
|
|
804
|
+
*/
|
|
520
805
|
selectOption(option) {
|
|
521
806
|
if (this.readonly) {
|
|
522
807
|
return;
|
|
@@ -535,34 +820,57 @@ export default {
|
|
|
535
820
|
|
|
536
821
|
this.triggerModelChange(rollback);
|
|
537
822
|
},
|
|
823
|
+
/**
|
|
824
|
+
* @param {Option} option
|
|
825
|
+
*/
|
|
538
826
|
deselectOption(option) {
|
|
539
|
-
|
|
827
|
+
const { multiple, readonly, optionsSelected } = this;
|
|
828
|
+
if (readonly) {
|
|
540
829
|
return;
|
|
541
830
|
}
|
|
542
|
-
if (!
|
|
831
|
+
if (!multiple) {
|
|
543
832
|
this.popoverShow = false;
|
|
544
833
|
return;
|
|
545
834
|
}
|
|
546
835
|
const rollback = this.createOptionRollback();
|
|
547
|
-
|
|
836
|
+
optionsSelected.splice(optionsSelected.indexOf(option), 1);
|
|
548
837
|
this.triggerModelChange(rollback);
|
|
549
838
|
},
|
|
839
|
+
/**
|
|
840
|
+
* @param {Option} option
|
|
841
|
+
*/
|
|
550
842
|
toggleOption(option) {
|
|
551
|
-
|
|
552
|
-
|
|
843
|
+
const { isOptionSelected, selectOption, deselectOption } = this;
|
|
844
|
+
if (!isOptionSelected(option)) {
|
|
845
|
+
selectOption(option);
|
|
553
846
|
} else {
|
|
554
|
-
|
|
847
|
+
deselectOption(option);
|
|
555
848
|
}
|
|
556
849
|
},
|
|
850
|
+
clearOptions() {
|
|
851
|
+
const rollback = this.createOptionRollback();
|
|
852
|
+
this.optionsSelected = [];
|
|
853
|
+
this.triggerModelChange(rollback);
|
|
854
|
+
},
|
|
557
855
|
getDatalistRef() {
|
|
558
856
|
return this.$refs.datalist;
|
|
559
857
|
},
|
|
560
|
-
|
|
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;
|
|
561
869
|
this.togglePopover();
|
|
562
870
|
this.rootHasFocus = true;
|
|
563
871
|
this.$el.focus();
|
|
564
|
-
if (
|
|
565
|
-
this.dataListCursorIndex = this.getOptionIndex(
|
|
872
|
+
if (popoverShow && optionsSelected.length > 0) {
|
|
873
|
+
this.dataListCursorIndex = this.getOptionIndex(optionsSelected[0]);
|
|
566
874
|
}
|
|
567
875
|
},
|
|
568
876
|
onDatalistSelectOption({ option }) {
|
|
@@ -572,11 +880,13 @@ export default {
|
|
|
572
880
|
let list = this.getDatalistRef();
|
|
573
881
|
if (e.key === Key.ESC) {
|
|
574
882
|
if (this.popoverShow) {
|
|
883
|
+
e.preventDefault();
|
|
575
884
|
e.stopPropagation();
|
|
576
885
|
this.popoverShow = false;
|
|
577
886
|
return;
|
|
578
887
|
}
|
|
579
888
|
if (this.rootHasFocus) {
|
|
889
|
+
e.preventDefault();
|
|
580
890
|
e.stopPropagation();
|
|
581
891
|
this.rootHasFocus = false;
|
|
582
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
|
},
|