goodteditor-ui 1.0.12 → 1.0.13
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/.eslintrc.js +7 -7
- package/.prettierrc +14 -14
- package/README.md +35 -35
- package/babel.config.js +5 -5
- package/dist/js.png +0 -0
- package/index.js +51 -51
- package/jsconfig.json +13 -13
- package/package.json +57 -57
- package/src/App.vue +36 -36
- package/src/components/ui/Avatar.md +68 -68
- package/src/components/ui/Avatar.vue +177 -177
- package/src/components/ui/Badge.md +20 -20
- package/src/components/ui/Badge.vue +75 -75
- package/src/components/ui/Collapse.md +90 -90
- package/src/components/ui/Collapse.vue +86 -86
- package/src/components/ui/ColorPicker/Alpha.vue +114 -114
- package/src/components/ui/ColorPicker/Colors.vue +117 -117
- package/src/components/ui/ColorPicker/Hue.vue +113 -113
- package/src/components/ui/ColorPicker/Preview.vue +55 -55
- package/src/components/ui/ColorPicker/Saturation.vue +123 -123
- package/src/components/ui/ColorPicker/mixin.js +105 -105
- package/src/components/ui/ColorPicker.md +17 -17
- package/src/components/ui/ColorPicker.vue +314 -314
- package/src/components/ui/Datalist.md +41 -41
- package/src/components/ui/Datalist.vue +157 -157
- package/src/components/ui/DatePicker.md +168 -168
- package/src/components/ui/DatePicker.vue +527 -527
- package/src/components/ui/FileSelector.md +105 -105
- package/src/components/ui/FileSelector.vue +82 -82
- package/src/components/ui/Grid.md +130 -130
- package/src/components/ui/Grid.vue +92 -92
- package/src/components/ui/Image.md +59 -59
- package/src/components/ui/Image.vue +57 -57
- package/src/components/ui/InputAutocomplete.md +115 -115
- package/src/components/ui/InputAutocomplete.vue +341 -341
- package/src/components/ui/InputColorPicker.md +51 -51
- package/src/components/ui/InputColorPicker.vue +151 -151
- package/src/components/ui/InputDatePicker.md +121 -121
- package/src/components/ui/InputDatePicker.vue +310 -310
- package/src/components/ui/InputTags.md +51 -51
- package/src/components/ui/InputTags.vue +184 -184
- package/src/components/ui/InputTimePicker.md +25 -25
- package/src/components/ui/InputTimePicker.vue +253 -253
- package/src/components/ui/InputUnits.md +20 -20
- package/src/components/ui/InputUnits.vue +257 -257
- package/src/components/ui/Lazy.md +37 -37
- package/src/components/ui/Lazy.vue +92 -92
- package/src/components/ui/Pagination.md +74 -74
- package/src/components/ui/Pagination.vue +138 -138
- package/src/components/ui/Paginator.md +34 -34
- package/src/components/ui/Paginator.vue +83 -83
- package/src/components/ui/Popover.md +34 -34
- package/src/components/ui/Popover.vue +258 -209
- package/src/components/ui/Popup.md +59 -59
- package/src/components/ui/Popup.vue +150 -150
- package/src/components/ui/ResponsiveContainer.md +58 -58
- package/src/components/ui/ResponsiveContainer.vue +99 -99
- package/src/components/ui/Select.md +187 -187
- package/src/components/ui/Select.vue +420 -420
- package/src/components/ui/TimePicker.md +50 -50
- package/src/components/ui/TimePicker.vue +252 -252
- package/src/components/ui/Tooltip.md +114 -52
- package/src/components/ui/Tooltip.vue +113 -113
- package/src/components/ui/utils/FormComponent.js +107 -107
- package/src/components/ui/utils/Helpers.js +84 -61
- package/src/components/ui/utils/WithPopover.js +99 -81
- package/src/main.js +8 -8
- package/styleguide.config.js +37 -37
- package/vue.config.js +8 -8
- package/.idea/codeStyles/Project.xml +0 -51
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/goodt-ui.iml +0 -12
- package/.idea/inspectionProfiles/Project_Default.xml +0 -6
- package/.idea/modules.xml +0 -8
- package/.idea/vcs.xml +0 -6
|
@@ -1,420 +1,420 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="ui-select form-elem"
|
|
4
|
-
:class="cssClassExt"
|
|
5
|
-
@keydown.prevent="onKeyDown"
|
|
6
|
-
@click="onClick"
|
|
7
|
-
tabindex="0"
|
|
8
|
-
:data-popover="popoverTargetId"
|
|
9
|
-
>
|
|
10
|
-
<div class="ui-select-label u-select-none">
|
|
11
|
-
<template v-if="multiple">
|
|
12
|
-
<template v-if="optionsSelected.length">
|
|
13
|
-
<!--
|
|
14
|
-
@slot Label slot for multiple mode
|
|
15
|
-
@binding {Object} option option
|
|
16
|
-
@binding {any} value option's value
|
|
17
|
-
@binding {String} label option's label
|
|
18
|
-
@binding {Function} deselectOption deselects option function(option:Object)
|
|
19
|
-
-->
|
|
20
|
-
<slot
|
|
21
|
-
name="label-multiple"
|
|
22
|
-
v-for="(option, index) in optionsSelected"
|
|
23
|
-
v-bind="{
|
|
24
|
-
option,
|
|
25
|
-
value: getOptionValue(option),
|
|
26
|
-
label: getOptionLabel(option),
|
|
27
|
-
deselectOption,
|
|
28
|
-
}"
|
|
29
|
-
>
|
|
30
|
-
<ui-badge
|
|
31
|
-
class="mar-none mar-right-2"
|
|
32
|
-
theme="primary"
|
|
33
|
-
size="small"
|
|
34
|
-
:key="index"
|
|
35
|
-
removable
|
|
36
|
-
@click.native.stop
|
|
37
|
-
@remove="deselectOption(option)"
|
|
38
|
-
>
|
|
39
|
-
<span>{{ getOptionLabel(option) }}</span>
|
|
40
|
-
</ui-badge>
|
|
41
|
-
</slot>
|
|
42
|
-
</template>
|
|
43
|
-
<div class="ui-select-placeholder events-none" v-else>
|
|
44
|
-
<!--
|
|
45
|
-
@slot Placeholder slot
|
|
46
|
-
-->
|
|
47
|
-
<slot name="placeholder">
|
|
48
|
-
<input class="w-100" :placeholder="placeholder" />
|
|
49
|
-
</slot>
|
|
50
|
-
</div>
|
|
51
|
-
</template>
|
|
52
|
-
|
|
53
|
-
<template v-else>
|
|
54
|
-
<!--
|
|
55
|
-
@slot Label slot for single mode
|
|
56
|
-
@binding {Object} option option
|
|
57
|
-
@binding {any} value option's value
|
|
58
|
-
@binding {String} label option's label
|
|
59
|
-
-->
|
|
60
|
-
<slot
|
|
61
|
-
name="label"
|
|
62
|
-
v-bind="{
|
|
63
|
-
option: optionsSelected[0],
|
|
64
|
-
value: getOptionValue(optionsSelected[0]),
|
|
65
|
-
label: getOptionLabel(optionsSelected[0]),
|
|
66
|
-
}"
|
|
67
|
-
v-if="optionsSelected.length"
|
|
68
|
-
>
|
|
69
|
-
{{ getOptionLabel(optionsSelected[0]) }}
|
|
70
|
-
</slot>
|
|
71
|
-
<div class="ui-select-placeholder events-none" v-else>
|
|
72
|
-
<!--
|
|
73
|
-
@slot Placeholder slot
|
|
74
|
-
-->
|
|
75
|
-
<slot name="placeholder">
|
|
76
|
-
<input class="w-100" :placeholder="placeholder" />
|
|
77
|
-
</slot>
|
|
78
|
-
</div>
|
|
79
|
-
</template>
|
|
80
|
-
</div>
|
|
81
|
-
<!--
|
|
82
|
-
@slot Open state icon slot
|
|
83
|
-
-->
|
|
84
|
-
<slot name="icon-open" v-if="popoverShow">
|
|
85
|
-
<div class="icon w-auto h-auto mar-left-2 events-none">
|
|
86
|
-
<i class="mdi mdi-chevron-up"></i>
|
|
87
|
-
</div>
|
|
88
|
-
</slot>
|
|
89
|
-
<!--
|
|
90
|
-
@slot Close state icon slot
|
|
91
|
-
-->
|
|
92
|
-
<slot name="icon-close" v-else>
|
|
93
|
-
<div class="icon w-auto h-auto mar-left-2 events-none">
|
|
94
|
-
<i class="mdi mdi-chevron-down"></i>
|
|
95
|
-
</div>
|
|
96
|
-
</slot>
|
|
97
|
-
|
|
98
|
-
<ui-popover :show.sync="popoverShow" v-bind="popoverOptions">
|
|
99
|
-
<ui-datalist
|
|
100
|
-
class="w-100 pull-left"
|
|
101
|
-
@click.native.stop
|
|
102
|
-
@select-option="onDatalistSelectOption"
|
|
103
|
-
v-bind="{ size, options }"
|
|
104
|
-
:cursorIndex.sync="dataListCursorIndex"
|
|
105
|
-
ref="datalist"
|
|
106
|
-
>
|
|
107
|
-
<template #header>
|
|
108
|
-
<!--
|
|
109
|
-
@slot Dropdown header slot
|
|
110
|
-
-->
|
|
111
|
-
<slot name="dropdown-header"></slot>
|
|
112
|
-
</template>
|
|
113
|
-
<template #option="{ option, index, cursorIndex }">
|
|
114
|
-
<!--
|
|
115
|
-
@slot Label slot for single mode
|
|
116
|
-
@binding {Object} option option
|
|
117
|
-
@binding {any} value option's value
|
|
118
|
-
@binding {String} label option's label
|
|
119
|
-
@binding {Number} index option's index
|
|
120
|
-
@binding {Boolean} isSelected option selection status
|
|
121
|
-
@binding {Number} cursorIndex current cursor index
|
|
122
|
-
@binding {Function} selectOption function that selects option
|
|
123
|
-
@binding {Function} deselectOption function that deselects option
|
|
124
|
-
@binding {Function} toggleOption function that toggles option selection
|
|
125
|
-
@binding {Number} optionIndex option's index
|
|
126
|
-
@binding {Boolean} isOptionSelected option selection status
|
|
127
|
-
-->
|
|
128
|
-
<slot
|
|
129
|
-
name="option"
|
|
130
|
-
v-bind="{
|
|
131
|
-
option,
|
|
132
|
-
value: getOptionValue(option),
|
|
133
|
-
label: getOptionLabel(option),
|
|
134
|
-
index,
|
|
135
|
-
isSelected: isOptionSelected(option),
|
|
136
|
-
cursorIndex,
|
|
137
|
-
selectOption,
|
|
138
|
-
deselectOption,
|
|
139
|
-
toggleOption,
|
|
140
|
-
// legacy
|
|
141
|
-
optionIndex: index,
|
|
142
|
-
isOptionSelected: isOptionSelected(option),
|
|
143
|
-
}"
|
|
144
|
-
>
|
|
145
|
-
<li
|
|
146
|
-
:class="{
|
|
147
|
-
active: isOptionSelected(option),
|
|
148
|
-
'bg-grey-lighter': index == cursorIndex,
|
|
149
|
-
}"
|
|
150
|
-
:key="index"
|
|
151
|
-
@click="toggleOption(option)"
|
|
152
|
-
>
|
|
153
|
-
<div class="row row-collapse" v-if="multiple">
|
|
154
|
-
<div class="col col-vmid text-truncate">
|
|
155
|
-
<div
|
|
156
|
-
class="text-truncate"
|
|
157
|
-
style="min-height: calc(var(--line-height)*1em)"
|
|
158
|
-
>
|
|
159
|
-
{{ getOptionLabel(option) }}
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
<div class="col col-auto col-vmid" v-if="isOptionSelected(option)">
|
|
163
|
-
<i class="mdi mdi-check" style="line-height:1"></i>
|
|
164
|
-
</div>
|
|
165
|
-
</div>
|
|
166
|
-
<div
|
|
167
|
-
class="text-truncate"
|
|
168
|
-
style="min-height: calc(var(--line-height)*1rem)"
|
|
169
|
-
v-else
|
|
170
|
-
>
|
|
171
|
-
{{ getOptionLabel(option) }}
|
|
172
|
-
</div>
|
|
173
|
-
</li>
|
|
174
|
-
</slot>
|
|
175
|
-
</template>
|
|
176
|
-
<template #footer>
|
|
177
|
-
<!--
|
|
178
|
-
@slot Dropdown footer slot
|
|
179
|
-
-->
|
|
180
|
-
<slot name="dropdown-footer"></slot>
|
|
181
|
-
</template>
|
|
182
|
-
</ui-datalist>
|
|
183
|
-
</ui-popover>
|
|
184
|
-
</div>
|
|
185
|
-
</template>
|
|
186
|
-
<style lang="less" scoped>
|
|
187
|
-
.ui-select {
|
|
188
|
-
display: inline-flex;
|
|
189
|
-
align-items: center;
|
|
190
|
-
&-label {
|
|
191
|
-
white-space: nowrap;
|
|
192
|
-
text-overflow: ellipsis;
|
|
193
|
-
overflow: hidden;
|
|
194
|
-
flex: 1 0 0;
|
|
195
|
-
}
|
|
196
|
-
&-placeholder {
|
|
197
|
-
input {
|
|
198
|
-
border: none;
|
|
199
|
-
margin: 0;
|
|
200
|
-
padding: 0;
|
|
201
|
-
color: inherit;
|
|
202
|
-
background: transparent;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
</style>
|
|
207
|
-
<script>
|
|
208
|
-
import UiBadge from './Badge.vue';
|
|
209
|
-
import UiDatalist from './Datalist.vue';
|
|
210
|
-
import UiPopover from './Popover.vue';
|
|
211
|
-
import FormComponent from './utils/FormComponent';
|
|
212
|
-
import WithPopover from './utils/WithPopover';
|
|
213
|
-
import { Key } from './utils/Helpers';
|
|
214
|
-
|
|
215
|
-
export default {
|
|
216
|
-
components: {
|
|
217
|
-
UiBadge,
|
|
218
|
-
UiDatalist,
|
|
219
|
-
UiPopover,
|
|
220
|
-
},
|
|
221
|
-
mixins: [FormComponent, WithPopover],
|
|
222
|
-
props: {
|
|
223
|
-
/**
|
|
224
|
-
* @model
|
|
225
|
-
*/
|
|
226
|
-
value: {
|
|
227
|
-
default() {
|
|
228
|
-
return null;
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
/**
|
|
232
|
-
* Options. Array of Objects (option objects)
|
|
233
|
-
*/
|
|
234
|
-
options: {
|
|
235
|
-
type: Array,
|
|
236
|
-
default() {
|
|
237
|
-
return [];
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
/**
|
|
241
|
-
* Allow multiple selection
|
|
242
|
-
*/
|
|
243
|
-
multiple: {
|
|
244
|
-
type: Boolean,
|
|
245
|
-
default: false,
|
|
246
|
-
},
|
|
247
|
-
/**
|
|
248
|
-
* Defines whether 'value' is the option value field or option object
|
|
249
|
-
* @see options
|
|
250
|
-
*/
|
|
251
|
-
valueObjects: {
|
|
252
|
-
type: Boolean,
|
|
253
|
-
default: false,
|
|
254
|
-
},
|
|
255
|
-
/**
|
|
256
|
-
* Defines the 'value' field of the option Object
|
|
257
|
-
*/
|
|
258
|
-
valueField: {
|
|
259
|
-
type: String,
|
|
260
|
-
default: 'value',
|
|
261
|
-
},
|
|
262
|
-
/**
|
|
263
|
-
* Defines the 'label' field of the option Object
|
|
264
|
-
*/
|
|
265
|
-
labelField: {
|
|
266
|
-
type: String,
|
|
267
|
-
default: 'label',
|
|
268
|
-
},
|
|
269
|
-
autoWidth: {
|
|
270
|
-
default: true,
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
data() {
|
|
274
|
-
return {
|
|
275
|
-
optionsSelected: [],
|
|
276
|
-
dataListCursorIndex: -1,
|
|
277
|
-
};
|
|
278
|
-
},
|
|
279
|
-
computed: {
|
|
280
|
-
/**
|
|
281
|
-
* @return {object}
|
|
282
|
-
*/
|
|
283
|
-
cssClassExt() {
|
|
284
|
-
let obj = { 'u-select-none': this.readonly };
|
|
285
|
-
return { ...this.cssClass, ...obj };
|
|
286
|
-
},
|
|
287
|
-
},
|
|
288
|
-
watch: {
|
|
289
|
-
value: {
|
|
290
|
-
handler(model) {
|
|
291
|
-
this.importModel(model);
|
|
292
|
-
},
|
|
293
|
-
immediate: true,
|
|
294
|
-
},
|
|
295
|
-
options() {
|
|
296
|
-
this.importModel(this.value);
|
|
297
|
-
},
|
|
298
|
-
},
|
|
299
|
-
methods: {
|
|
300
|
-
importModel(model) {
|
|
301
|
-
let ci = -1;
|
|
302
|
-
let tmp = [];
|
|
303
|
-
model = this.multiple ? (Array.isArray(model) ? model : [model]) : [model];
|
|
304
|
-
model.forEach(modelItem => {
|
|
305
|
-
let modelItemValue = this.valueObjects ? this.getOptionValue(modelItem) : modelItem;
|
|
306
|
-
let optionIndex = this.options.findIndex(optionItem => {
|
|
307
|
-
let optionItemValue = this.getOptionValue(optionItem);
|
|
308
|
-
return optionItemValue === modelItemValue;
|
|
309
|
-
});
|
|
310
|
-
if (optionIndex >= 0) {
|
|
311
|
-
ci = ci < 0 ? optionIndex : ci;
|
|
312
|
-
tmp.push(this.options[optionIndex]);
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
this.dataListCursorIndex = ci;
|
|
316
|
-
this.optionsSelected = tmp;
|
|
317
|
-
},
|
|
318
|
-
exportModel() {
|
|
319
|
-
let model = this.optionsSelected.map(option =>
|
|
320
|
-
this.valueObjects ? option : this.getOptionValue(option)
|
|
321
|
-
);
|
|
322
|
-
if (this.multiple) {
|
|
323
|
-
return model;
|
|
324
|
-
}
|
|
325
|
-
return model && model.length ? model[0] : null;
|
|
326
|
-
},
|
|
327
|
-
triggerModelChange() {
|
|
328
|
-
let value = this.exportModel();
|
|
329
|
-
/**
|
|
330
|
-
* Input event
|
|
331
|
-
* @property {any} value
|
|
332
|
-
*/
|
|
333
|
-
this.$emit('input', value);
|
|
334
|
-
/**
|
|
335
|
-
* Change event
|
|
336
|
-
* @property {any} model
|
|
337
|
-
* @property {Array} meta
|
|
338
|
-
*/
|
|
339
|
-
this.$emit('change', value);
|
|
340
|
-
},
|
|
341
|
-
getOptionLabel(option) {
|
|
342
|
-
let label = option ? option[this.labelField] : null;
|
|
343
|
-
return label == null ? option : label;
|
|
344
|
-
},
|
|
345
|
-
getOptionValue(option) {
|
|
346
|
-
let value = option ? option[this.valueField] : null;
|
|
347
|
-
// @NOTE option.value might be 'null'
|
|
348
|
-
return value === undefined ? option : value;
|
|
349
|
-
},
|
|
350
|
-
getOptionIndex(option) {
|
|
351
|
-
return this.options.findIndex(
|
|
352
|
-
o => this.getOptionValue(o) === this.getOptionValue(option)
|
|
353
|
-
);
|
|
354
|
-
},
|
|
355
|
-
isOptionSelected(option) {
|
|
356
|
-
return !!this.optionsSelected.find(
|
|
357
|
-
o => this.getOptionValue(o) === this.getOptionValue(option)
|
|
358
|
-
);
|
|
359
|
-
},
|
|
360
|
-
selectOption(option) {
|
|
361
|
-
if (this.isOptionSelected(option)) {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
if (this.multiple) {
|
|
365
|
-
this.optionsSelected.push(option);
|
|
366
|
-
} else {
|
|
367
|
-
this.optionsSelected = [option];
|
|
368
|
-
this.popoverShow = false;
|
|
369
|
-
}
|
|
370
|
-
this.triggerModelChange();
|
|
371
|
-
},
|
|
372
|
-
deselectOption(option) {
|
|
373
|
-
if (!this.multiple) {
|
|
374
|
-
this.popoverShow = false;
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
this.optionsSelected = this.optionsSelected.filter(
|
|
378
|
-
o => this.getOptionValue(o) !== this.getOptionValue(option)
|
|
379
|
-
);
|
|
380
|
-
this.triggerModelChange();
|
|
381
|
-
},
|
|
382
|
-
toggleOption(option) {
|
|
383
|
-
if (!this.isOptionSelected(option)) {
|
|
384
|
-
this.selectOption(option);
|
|
385
|
-
} else {
|
|
386
|
-
this.deselectOption(option);
|
|
387
|
-
}
|
|
388
|
-
},
|
|
389
|
-
getDatalistRef() {
|
|
390
|
-
return this.$refs.datalist;
|
|
391
|
-
},
|
|
392
|
-
onClick(e) {
|
|
393
|
-
this.togglePopover();
|
|
394
|
-
this.rootHasFocus = true;
|
|
395
|
-
this.$el.focus();
|
|
396
|
-
if (this.popoverShow && this.optionsSelected.length) {
|
|
397
|
-
this.dataListCursorIndex = this.getOptionIndex(this.optionsSelected[0]);
|
|
398
|
-
}
|
|
399
|
-
},
|
|
400
|
-
onDatalistSelectOption({ option }) {
|
|
401
|
-
this.toggleOption(option);
|
|
402
|
-
},
|
|
403
|
-
onKeyDown(e) {
|
|
404
|
-
let list = this.getDatalistRef();
|
|
405
|
-
if (e.key === Key.ESC) {
|
|
406
|
-
this.popoverShow = false;
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
if (e.key === Key.ENTER) {
|
|
410
|
-
if (!this.popoverShow) {
|
|
411
|
-
this.popoverShow = true;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
if (this.popoverShow) {
|
|
415
|
-
list && list.onKeyDown(e);
|
|
416
|
-
}
|
|
417
|
-
},
|
|
418
|
-
},
|
|
419
|
-
};
|
|
420
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="ui-select form-elem"
|
|
4
|
+
:class="cssClassExt"
|
|
5
|
+
@keydown.prevent="onKeyDown"
|
|
6
|
+
@click="onClick"
|
|
7
|
+
tabindex="0"
|
|
8
|
+
:data-popover="popoverTargetId"
|
|
9
|
+
>
|
|
10
|
+
<div class="ui-select-label u-select-none">
|
|
11
|
+
<template v-if="multiple">
|
|
12
|
+
<template v-if="optionsSelected.length">
|
|
13
|
+
<!--
|
|
14
|
+
@slot Label slot for multiple mode
|
|
15
|
+
@binding {Object} option option
|
|
16
|
+
@binding {any} value option's value
|
|
17
|
+
@binding {String} label option's label
|
|
18
|
+
@binding {Function} deselectOption deselects option function(option:Object)
|
|
19
|
+
-->
|
|
20
|
+
<slot
|
|
21
|
+
name="label-multiple"
|
|
22
|
+
v-for="(option, index) in optionsSelected"
|
|
23
|
+
v-bind="{
|
|
24
|
+
option,
|
|
25
|
+
value: getOptionValue(option),
|
|
26
|
+
label: getOptionLabel(option),
|
|
27
|
+
deselectOption,
|
|
28
|
+
}"
|
|
29
|
+
>
|
|
30
|
+
<ui-badge
|
|
31
|
+
class="mar-none mar-right-2"
|
|
32
|
+
theme="primary"
|
|
33
|
+
size="small"
|
|
34
|
+
:key="index"
|
|
35
|
+
removable
|
|
36
|
+
@click.native.stop
|
|
37
|
+
@remove="deselectOption(option)"
|
|
38
|
+
>
|
|
39
|
+
<span>{{ getOptionLabel(option) }}</span>
|
|
40
|
+
</ui-badge>
|
|
41
|
+
</slot>
|
|
42
|
+
</template>
|
|
43
|
+
<div class="ui-select-placeholder events-none" v-else>
|
|
44
|
+
<!--
|
|
45
|
+
@slot Placeholder slot
|
|
46
|
+
-->
|
|
47
|
+
<slot name="placeholder">
|
|
48
|
+
<input class="w-100" :placeholder="placeholder" />
|
|
49
|
+
</slot>
|
|
50
|
+
</div>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<template v-else>
|
|
54
|
+
<!--
|
|
55
|
+
@slot Label slot for single mode
|
|
56
|
+
@binding {Object} option option
|
|
57
|
+
@binding {any} value option's value
|
|
58
|
+
@binding {String} label option's label
|
|
59
|
+
-->
|
|
60
|
+
<slot
|
|
61
|
+
name="label"
|
|
62
|
+
v-bind="{
|
|
63
|
+
option: optionsSelected[0],
|
|
64
|
+
value: getOptionValue(optionsSelected[0]),
|
|
65
|
+
label: getOptionLabel(optionsSelected[0]),
|
|
66
|
+
}"
|
|
67
|
+
v-if="optionsSelected.length"
|
|
68
|
+
>
|
|
69
|
+
{{ getOptionLabel(optionsSelected[0]) }}
|
|
70
|
+
</slot>
|
|
71
|
+
<div class="ui-select-placeholder events-none" v-else>
|
|
72
|
+
<!--
|
|
73
|
+
@slot Placeholder slot
|
|
74
|
+
-->
|
|
75
|
+
<slot name="placeholder">
|
|
76
|
+
<input class="w-100" :placeholder="placeholder" />
|
|
77
|
+
</slot>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
80
|
+
</div>
|
|
81
|
+
<!--
|
|
82
|
+
@slot Open state icon slot
|
|
83
|
+
-->
|
|
84
|
+
<slot name="icon-open" v-if="popoverShow">
|
|
85
|
+
<div class="icon w-auto h-auto mar-left-2 events-none">
|
|
86
|
+
<i class="mdi mdi-chevron-up"></i>
|
|
87
|
+
</div>
|
|
88
|
+
</slot>
|
|
89
|
+
<!--
|
|
90
|
+
@slot Close state icon slot
|
|
91
|
+
-->
|
|
92
|
+
<slot name="icon-close" v-else>
|
|
93
|
+
<div class="icon w-auto h-auto mar-left-2 events-none">
|
|
94
|
+
<i class="mdi mdi-chevron-down"></i>
|
|
95
|
+
</div>
|
|
96
|
+
</slot>
|
|
97
|
+
|
|
98
|
+
<ui-popover :show.sync="popoverShow" v-bind="popoverOptions">
|
|
99
|
+
<ui-datalist
|
|
100
|
+
class="w-100 pull-left"
|
|
101
|
+
@click.native.stop
|
|
102
|
+
@select-option="onDatalistSelectOption"
|
|
103
|
+
v-bind="{ size, options }"
|
|
104
|
+
:cursorIndex.sync="dataListCursorIndex"
|
|
105
|
+
ref="datalist"
|
|
106
|
+
>
|
|
107
|
+
<template #header>
|
|
108
|
+
<!--
|
|
109
|
+
@slot Dropdown header slot
|
|
110
|
+
-->
|
|
111
|
+
<slot name="dropdown-header"></slot>
|
|
112
|
+
</template>
|
|
113
|
+
<template #option="{ option, index, cursorIndex }">
|
|
114
|
+
<!--
|
|
115
|
+
@slot Label slot for single mode
|
|
116
|
+
@binding {Object} option option
|
|
117
|
+
@binding {any} value option's value
|
|
118
|
+
@binding {String} label option's label
|
|
119
|
+
@binding {Number} index option's index
|
|
120
|
+
@binding {Boolean} isSelected option selection status
|
|
121
|
+
@binding {Number} cursorIndex current cursor index
|
|
122
|
+
@binding {Function} selectOption function that selects option
|
|
123
|
+
@binding {Function} deselectOption function that deselects option
|
|
124
|
+
@binding {Function} toggleOption function that toggles option selection
|
|
125
|
+
@binding {Number} optionIndex option's index
|
|
126
|
+
@binding {Boolean} isOptionSelected option selection status
|
|
127
|
+
-->
|
|
128
|
+
<slot
|
|
129
|
+
name="option"
|
|
130
|
+
v-bind="{
|
|
131
|
+
option,
|
|
132
|
+
value: getOptionValue(option),
|
|
133
|
+
label: getOptionLabel(option),
|
|
134
|
+
index,
|
|
135
|
+
isSelected: isOptionSelected(option),
|
|
136
|
+
cursorIndex,
|
|
137
|
+
selectOption,
|
|
138
|
+
deselectOption,
|
|
139
|
+
toggleOption,
|
|
140
|
+
// legacy
|
|
141
|
+
optionIndex: index,
|
|
142
|
+
isOptionSelected: isOptionSelected(option),
|
|
143
|
+
}"
|
|
144
|
+
>
|
|
145
|
+
<li
|
|
146
|
+
:class="{
|
|
147
|
+
active: isOptionSelected(option),
|
|
148
|
+
'bg-grey-lighter': index == cursorIndex,
|
|
149
|
+
}"
|
|
150
|
+
:key="index"
|
|
151
|
+
@click="toggleOption(option)"
|
|
152
|
+
>
|
|
153
|
+
<div class="row row-collapse" v-if="multiple">
|
|
154
|
+
<div class="col col-vmid text-truncate">
|
|
155
|
+
<div
|
|
156
|
+
class="text-truncate"
|
|
157
|
+
style="min-height: calc(var(--line-height)*1em)"
|
|
158
|
+
>
|
|
159
|
+
{{ getOptionLabel(option) }}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="col col-auto col-vmid" v-if="isOptionSelected(option)">
|
|
163
|
+
<i class="mdi mdi-check" style="line-height:1"></i>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
<div
|
|
167
|
+
class="text-truncate"
|
|
168
|
+
style="min-height: calc(var(--line-height)*1rem)"
|
|
169
|
+
v-else
|
|
170
|
+
>
|
|
171
|
+
{{ getOptionLabel(option) }}
|
|
172
|
+
</div>
|
|
173
|
+
</li>
|
|
174
|
+
</slot>
|
|
175
|
+
</template>
|
|
176
|
+
<template #footer>
|
|
177
|
+
<!--
|
|
178
|
+
@slot Dropdown footer slot
|
|
179
|
+
-->
|
|
180
|
+
<slot name="dropdown-footer"></slot>
|
|
181
|
+
</template>
|
|
182
|
+
</ui-datalist>
|
|
183
|
+
</ui-popover>
|
|
184
|
+
</div>
|
|
185
|
+
</template>
|
|
186
|
+
<style lang="less" scoped>
|
|
187
|
+
.ui-select {
|
|
188
|
+
display: inline-flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
&-label {
|
|
191
|
+
white-space: nowrap;
|
|
192
|
+
text-overflow: ellipsis;
|
|
193
|
+
overflow: hidden;
|
|
194
|
+
flex: 1 0 0;
|
|
195
|
+
}
|
|
196
|
+
&-placeholder {
|
|
197
|
+
input {
|
|
198
|
+
border: none;
|
|
199
|
+
margin: 0;
|
|
200
|
+
padding: 0;
|
|
201
|
+
color: inherit;
|
|
202
|
+
background: transparent;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
</style>
|
|
207
|
+
<script>
|
|
208
|
+
import UiBadge from './Badge.vue';
|
|
209
|
+
import UiDatalist from './Datalist.vue';
|
|
210
|
+
import UiPopover from './Popover.vue';
|
|
211
|
+
import FormComponent from './utils/FormComponent';
|
|
212
|
+
import WithPopover from './utils/WithPopover';
|
|
213
|
+
import { Key } from './utils/Helpers';
|
|
214
|
+
|
|
215
|
+
export default {
|
|
216
|
+
components: {
|
|
217
|
+
UiBadge,
|
|
218
|
+
UiDatalist,
|
|
219
|
+
UiPopover,
|
|
220
|
+
},
|
|
221
|
+
mixins: [FormComponent, WithPopover],
|
|
222
|
+
props: {
|
|
223
|
+
/**
|
|
224
|
+
* @model
|
|
225
|
+
*/
|
|
226
|
+
value: {
|
|
227
|
+
default() {
|
|
228
|
+
return null;
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
/**
|
|
232
|
+
* Options. Array of Objects (option objects)
|
|
233
|
+
*/
|
|
234
|
+
options: {
|
|
235
|
+
type: Array,
|
|
236
|
+
default() {
|
|
237
|
+
return [];
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
/**
|
|
241
|
+
* Allow multiple selection
|
|
242
|
+
*/
|
|
243
|
+
multiple: {
|
|
244
|
+
type: Boolean,
|
|
245
|
+
default: false,
|
|
246
|
+
},
|
|
247
|
+
/**
|
|
248
|
+
* Defines whether 'value' is the option value field or option object
|
|
249
|
+
* @see options
|
|
250
|
+
*/
|
|
251
|
+
valueObjects: {
|
|
252
|
+
type: Boolean,
|
|
253
|
+
default: false,
|
|
254
|
+
},
|
|
255
|
+
/**
|
|
256
|
+
* Defines the 'value' field of the option Object
|
|
257
|
+
*/
|
|
258
|
+
valueField: {
|
|
259
|
+
type: String,
|
|
260
|
+
default: 'value',
|
|
261
|
+
},
|
|
262
|
+
/**
|
|
263
|
+
* Defines the 'label' field of the option Object
|
|
264
|
+
*/
|
|
265
|
+
labelField: {
|
|
266
|
+
type: String,
|
|
267
|
+
default: 'label',
|
|
268
|
+
},
|
|
269
|
+
autoWidth: {
|
|
270
|
+
default: true,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
data() {
|
|
274
|
+
return {
|
|
275
|
+
optionsSelected: [],
|
|
276
|
+
dataListCursorIndex: -1,
|
|
277
|
+
};
|
|
278
|
+
},
|
|
279
|
+
computed: {
|
|
280
|
+
/**
|
|
281
|
+
* @return {object}
|
|
282
|
+
*/
|
|
283
|
+
cssClassExt() {
|
|
284
|
+
let obj = { 'u-select-none': this.readonly };
|
|
285
|
+
return { ...this.cssClass, ...obj };
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
watch: {
|
|
289
|
+
value: {
|
|
290
|
+
handler(model) {
|
|
291
|
+
this.importModel(model);
|
|
292
|
+
},
|
|
293
|
+
immediate: true,
|
|
294
|
+
},
|
|
295
|
+
options() {
|
|
296
|
+
this.importModel(this.value);
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
methods: {
|
|
300
|
+
importModel(model) {
|
|
301
|
+
let ci = -1;
|
|
302
|
+
let tmp = [];
|
|
303
|
+
model = this.multiple ? (Array.isArray(model) ? model : [model]) : [model];
|
|
304
|
+
model.forEach(modelItem => {
|
|
305
|
+
let modelItemValue = this.valueObjects ? this.getOptionValue(modelItem) : modelItem;
|
|
306
|
+
let optionIndex = this.options.findIndex(optionItem => {
|
|
307
|
+
let optionItemValue = this.getOptionValue(optionItem);
|
|
308
|
+
return optionItemValue === modelItemValue;
|
|
309
|
+
});
|
|
310
|
+
if (optionIndex >= 0) {
|
|
311
|
+
ci = ci < 0 ? optionIndex : ci;
|
|
312
|
+
tmp.push(this.options[optionIndex]);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
this.dataListCursorIndex = ci;
|
|
316
|
+
this.optionsSelected = tmp;
|
|
317
|
+
},
|
|
318
|
+
exportModel() {
|
|
319
|
+
let model = this.optionsSelected.map(option =>
|
|
320
|
+
this.valueObjects ? option : this.getOptionValue(option)
|
|
321
|
+
);
|
|
322
|
+
if (this.multiple) {
|
|
323
|
+
return model;
|
|
324
|
+
}
|
|
325
|
+
return model && model.length ? model[0] : null;
|
|
326
|
+
},
|
|
327
|
+
triggerModelChange() {
|
|
328
|
+
let value = this.exportModel();
|
|
329
|
+
/**
|
|
330
|
+
* Input event
|
|
331
|
+
* @property {any} value
|
|
332
|
+
*/
|
|
333
|
+
this.$emit('input', value);
|
|
334
|
+
/**
|
|
335
|
+
* Change event
|
|
336
|
+
* @property {any} model
|
|
337
|
+
* @property {Array} meta
|
|
338
|
+
*/
|
|
339
|
+
this.$emit('change', value);
|
|
340
|
+
},
|
|
341
|
+
getOptionLabel(option) {
|
|
342
|
+
let label = option ? option[this.labelField] : null;
|
|
343
|
+
return label == null ? option : label;
|
|
344
|
+
},
|
|
345
|
+
getOptionValue(option) {
|
|
346
|
+
let value = option ? option[this.valueField] : null;
|
|
347
|
+
// @NOTE option.value might be 'null'
|
|
348
|
+
return value === undefined ? option : value;
|
|
349
|
+
},
|
|
350
|
+
getOptionIndex(option) {
|
|
351
|
+
return this.options.findIndex(
|
|
352
|
+
o => this.getOptionValue(o) === this.getOptionValue(option)
|
|
353
|
+
);
|
|
354
|
+
},
|
|
355
|
+
isOptionSelected(option) {
|
|
356
|
+
return !!this.optionsSelected.find(
|
|
357
|
+
o => this.getOptionValue(o) === this.getOptionValue(option)
|
|
358
|
+
);
|
|
359
|
+
},
|
|
360
|
+
selectOption(option) {
|
|
361
|
+
if (this.isOptionSelected(option)) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (this.multiple) {
|
|
365
|
+
this.optionsSelected.push(option);
|
|
366
|
+
} else {
|
|
367
|
+
this.optionsSelected = [option];
|
|
368
|
+
this.popoverShow = false;
|
|
369
|
+
}
|
|
370
|
+
this.triggerModelChange();
|
|
371
|
+
},
|
|
372
|
+
deselectOption(option) {
|
|
373
|
+
if (!this.multiple) {
|
|
374
|
+
this.popoverShow = false;
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
this.optionsSelected = this.optionsSelected.filter(
|
|
378
|
+
o => this.getOptionValue(o) !== this.getOptionValue(option)
|
|
379
|
+
);
|
|
380
|
+
this.triggerModelChange();
|
|
381
|
+
},
|
|
382
|
+
toggleOption(option) {
|
|
383
|
+
if (!this.isOptionSelected(option)) {
|
|
384
|
+
this.selectOption(option);
|
|
385
|
+
} else {
|
|
386
|
+
this.deselectOption(option);
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
getDatalistRef() {
|
|
390
|
+
return this.$refs.datalist;
|
|
391
|
+
},
|
|
392
|
+
onClick(e) {
|
|
393
|
+
this.togglePopover();
|
|
394
|
+
this.rootHasFocus = true;
|
|
395
|
+
this.$el.focus();
|
|
396
|
+
if (this.popoverShow && this.optionsSelected.length) {
|
|
397
|
+
this.dataListCursorIndex = this.getOptionIndex(this.optionsSelected[0]);
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
onDatalistSelectOption({ option }) {
|
|
401
|
+
this.toggleOption(option);
|
|
402
|
+
},
|
|
403
|
+
onKeyDown(e) {
|
|
404
|
+
let list = this.getDatalistRef();
|
|
405
|
+
if (e.key === Key.ESC) {
|
|
406
|
+
this.popoverShow = false;
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (e.key === Key.ENTER) {
|
|
410
|
+
if (!this.popoverShow) {
|
|
411
|
+
this.popoverShow = true;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (this.popoverShow) {
|
|
415
|
+
list && list.onKeyDown(e);
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
</script>
|