evui 3.3.36 → 3.3.39
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/LICENSE +21 -21
- package/README.md +40 -40
- package/dist/evui.common.js +1907 -1832
- package/dist/evui.common.js.map +1 -1
- package/dist/evui.umd.js +1907 -1832
- package/dist/evui.umd.js.map +1 -1
- package/dist/evui.umd.min.js +1 -1
- package/dist/evui.umd.min.js.map +1 -1
- package/dist/img/{EVUI.7f3588fb.svg → EVUI.b82ee81a.svg} +292 -292
- package/dist/img/{icon_mysql.7ea26d5d.svg → icon_mysql.1085fdc9.svg} +78 -78
- package/dist/img/{icon_oracle.9009b108.svg → icon_oracle.0572d3ee.svg} +13 -13
- package/dist/img/{icon_postgresql.f8fffba9.svg → icon_postgresql.ee12bde8.svg} +58 -58
- package/package.json +61 -61
- package/src/common/emitter.js +20 -20
- package/src/common/utils.debounce.js +223 -223
- package/src/common/utils.js +134 -134
- package/src/common/utils.table.js +78 -78
- package/src/common/utils.throttle.js +83 -83
- package/src/common/utils.tree.js +18 -18
- package/src/components/button/Button.vue +198 -198
- package/src/components/button/index.js +7 -7
- package/src/components/buttonGroup/ButtonGroup.vue +11 -11
- package/src/components/buttonGroup/index.js +7 -7
- package/src/components/calendar/Calendar.vue +661 -661
- package/src/components/calendar/index.js +7 -7
- package/src/components/calendar/uses.js +1272 -1272
- package/src/components/chart/Chart.vue +189 -192
- package/src/components/chart/chart.core.js +870 -870
- package/src/components/chart/element/element.bar.js +524 -524
- package/src/components/chart/element/element.bar.time.js +156 -156
- package/src/components/chart/element/element.heatmap.js +533 -533
- package/src/components/chart/element/element.line.js +339 -339
- package/src/components/chart/element/element.pie.js +197 -197
- package/src/components/chart/element/element.scatter.js +184 -184
- package/src/components/chart/element/element.tip.js +550 -542
- package/src/components/chart/helpers/helpers.canvas.js +265 -265
- package/src/components/chart/helpers/helpers.constant.js +206 -206
- package/src/components/chart/helpers/helpers.util.js +346 -338
- package/src/components/chart/index.js +9 -9
- package/src/components/chart/model/index.js +4 -4
- package/src/components/chart/model/model.series.js +93 -93
- package/src/components/chart/model/model.store.js +977 -967
- package/src/components/chart/plugins/plugins.interaction.js +769 -769
- package/src/components/chart/plugins/plugins.legend.gradient.js +602 -602
- package/src/components/chart/plugins/plugins.legend.js +1155 -1151
- package/src/components/chart/plugins/plugins.pie.js +254 -254
- package/src/components/chart/plugins/plugins.title.js +56 -56
- package/src/components/chart/plugins/plugins.tooltip.js +692 -692
- package/src/components/chart/scale/scale.js +848 -848
- package/src/components/chart/scale/scale.linear.js +38 -38
- package/src/components/chart/scale/scale.logarithmic.js +128 -128
- package/src/components/chart/scale/scale.step.js +336 -336
- package/src/components/chart/scale/scale.time.category.js +277 -277
- package/src/components/chart/scale/scale.time.js +48 -48
- package/src/components/chart/style/chart.scss +312 -312
- package/src/components/chart/uses.js +264 -252
- package/src/components/checkbox/Checkbox.vue +200 -200
- package/src/components/checkbox/index.js +7 -7
- package/src/components/checkboxGroup/CheckboxGroup.vue +44 -44
- package/src/components/checkboxGroup/index.js +7 -7
- package/src/components/contextMenu/ContextMenu.vue +80 -80
- package/src/components/contextMenu/MenuList.vue +149 -149
- package/src/components/contextMenu/index.js +7 -7
- package/src/components/contextMenu/uses.js +203 -203
- package/src/components/datePicker/DatePicker.vue +437 -437
- package/src/components/datePicker/index.js +7 -7
- package/src/components/datePicker/uses.js +419 -419
- package/src/components/grid/Grid.vue +827 -827
- package/src/components/grid/grid.filter.window.vue +493 -493
- package/src/components/grid/grid.pagination.vue +75 -75
- package/src/components/grid/grid.summary.vue +265 -265
- package/src/components/grid/grid.toolbar.vue +26 -26
- package/src/components/grid/index.js +11 -11
- package/src/components/grid/style/grid.scss +263 -263
- package/src/components/grid/uses.js +1002 -1007
- package/src/components/icon/Icon.vue +49 -49
- package/src/components/icon/index.js +8 -8
- package/src/components/inputNumber/InputNumber.vue +212 -212
- package/src/components/inputNumber/index.js +7 -7
- package/src/components/inputNumber/uses.js +217 -217
- package/src/components/loading/Loading.vue +125 -125
- package/src/components/loading/index.js +7 -7
- package/src/components/menu/Menu.vue +68 -68
- package/src/components/menu/MenuItem.vue +187 -187
- package/src/components/menu/index.js +7 -7
- package/src/components/message/Message.vue +223 -223
- package/src/components/message/index.js +31 -31
- package/src/components/messageBox/MessageBox.vue +358 -358
- package/src/components/messageBox/index.js +22 -22
- package/src/components/notification/Notification.vue +316 -316
- package/src/components/notification/index.js +49 -49
- package/src/components/pagination/Pagination.vue +271 -271
- package/src/components/pagination/index.js +7 -7
- package/src/components/pagination/pageButton.vue +30 -30
- package/src/components/progress/Progress.vue +139 -139
- package/src/components/progress/index.js +7 -7
- package/src/components/radio/Radio.vue +159 -159
- package/src/components/radio/index.js +7 -7
- package/src/components/radioGroup/RadioGroup.vue +41 -41
- package/src/components/radioGroup/index.js +7 -7
- package/src/components/scheduler/Scheduler.vue +149 -149
- package/src/components/scheduler/index.js +7 -7
- package/src/components/scheduler/uses.js +183 -183
- package/src/components/select/Select.vue +440 -440
- package/src/components/select/index.js +7 -7
- package/src/components/select/uses.js +270 -270
- package/src/components/slider/Slider.vue +505 -505
- package/src/components/slider/index.js +7 -7
- package/src/components/slider/uses.js +390 -390
- package/src/components/tabPanel/TabPanel.vue +74 -74
- package/src/components/tabPanel/index.js +7 -7
- package/src/components/tabs/Tabs.vue +517 -517
- package/src/components/tabs/index.js +7 -7
- package/src/components/textField/TextField.vue +375 -375
- package/src/components/textField/index.js +7 -7
- package/src/components/timePicker/TimePicker.vue +352 -352
- package/src/components/timePicker/index.js +7 -7
- package/src/components/toggle/Toggle.vue +115 -115
- package/src/components/toggle/index.js +7 -7
- package/src/components/tree/Tree.vue +313 -313
- package/src/components/tree/TreeNode.vue +293 -293
- package/src/components/tree/index.js +7 -7
- package/src/components/treeGrid/TreeGrid.vue +758 -758
- package/src/components/treeGrid/TreeGridNode.vue +275 -275
- package/src/components/treeGrid/index.js +9 -9
- package/src/components/treeGrid/style/treeGrid.scss +261 -261
- package/src/components/treeGrid/treeGrid.toolbar.vue +26 -26
- package/src/components/treeGrid/uses.js +867 -867
- package/src/components/window/Window.vue +329 -329
- package/src/components/window/index.js +7 -7
- package/src/components/window/uses.js +899 -899
- package/src/directives/clickoutside.js +90 -90
- package/src/main.js +116 -116
- package/src/style/components/input.scss +108 -108
- package/src/style/functions.scss +3 -3
- package/src/style/index.scss +6 -6
- package/src/style/lib/fonts/EVUI.svg +292 -292
- package/src/style/lib/icon.css +888 -888
- package/src/style/mixins.scss +94 -94
- package/src/style/themes.scss +67 -67
- package/src/style/variables.scss +22 -22
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import EvSelect from './Select';
|
|
2
|
-
|
|
3
|
-
EvSelect.install = (app) => {
|
|
4
|
-
app.component(EvSelect.name, EvSelect);
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export default EvSelect;
|
|
1
|
+
import EvSelect from './Select';
|
|
2
|
+
|
|
3
|
+
EvSelect.install = (app) => {
|
|
4
|
+
app.component(EvSelect.name, EvSelect);
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export default EvSelect;
|
|
@@ -1,270 +1,270 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ref, reactive, computed, watch,
|
|
3
|
-
nextTick, getCurrentInstance,
|
|
4
|
-
} from 'vue';
|
|
5
|
-
|
|
6
|
-
export const useModel = () => {
|
|
7
|
-
const { props, emit } = getCurrentInstance();
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Select 컴포넌트의 v-model 값
|
|
11
|
-
* single 모드 : modelValue(String), 없는 경우 null
|
|
12
|
-
* multiple 모드 : modelValue(Array), 없는 경우 []
|
|
13
|
-
*/
|
|
14
|
-
const singleMv = {
|
|
15
|
-
get: () => {
|
|
16
|
-
if (props.items.some(v => v.value === props.modelValue)) {
|
|
17
|
-
return props.modelValue;
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
},
|
|
21
|
-
set: value => emit('update:modelValue', value),
|
|
22
|
-
};
|
|
23
|
-
const multiMv = {
|
|
24
|
-
get: () => {
|
|
25
|
-
if (Array.isArray(props.modelValue)) {
|
|
26
|
-
return props.modelValue;
|
|
27
|
-
}
|
|
28
|
-
return [];
|
|
29
|
-
},
|
|
30
|
-
set: value => emit('update:modelValue', value),
|
|
31
|
-
};
|
|
32
|
-
const mv = computed(!props.multiple ? singleMv : multiMv);
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 현재 select에서 선택된 항목들
|
|
36
|
-
* single 모드 : { name: 'name', value: 'value' }
|
|
37
|
-
* multiple 모드 : [{ name: 'name', value: 'value' }, {...}]
|
|
38
|
-
*/
|
|
39
|
-
const singleSm = () => props.items.find(v => v.value === mv.value)?.name;
|
|
40
|
-
const multipleSm = () => props.items.filter(v => props.modelValue.includes(v.value));
|
|
41
|
-
const selectedModel = computed(!props.multiple ? singleSm : multipleSm);
|
|
42
|
-
|
|
43
|
-
const computedPlaceholder = computed(() => {
|
|
44
|
-
if (!props.multiple) {
|
|
45
|
-
return props.placeholder;
|
|
46
|
-
}
|
|
47
|
-
return mv.value.length ? null : props.placeholder;
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* clearable 모드일 때, 항목(mv) 전체 삭제 아이콘 존재여부
|
|
52
|
-
*/
|
|
53
|
-
const singleIci = () => mv.value;
|
|
54
|
-
const multipleIci = () => mv.value.length;
|
|
55
|
-
const isClearableIcon = computed(!props.multiple ? singleIci : multipleIci);
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* clearable모드일 때 [x] 아이콘 클릭 시 mv값을 초기화
|
|
59
|
-
*/
|
|
60
|
-
const removeAllMv = () => {
|
|
61
|
-
if (!props.disabled) {
|
|
62
|
-
if (!props.multiple) {
|
|
63
|
-
mv.value = null;
|
|
64
|
-
} else {
|
|
65
|
-
mv.value.splice(0);
|
|
66
|
-
mv.value = [...mv.value];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 해당 컴포넌트의 v-model값이 변경(change)되는 이벤트
|
|
73
|
-
*/
|
|
74
|
-
const changeMv = async () => {
|
|
75
|
-
await nextTick();
|
|
76
|
-
emit('change', mv.value);
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* multiple 모드인 경우 선택된 value를 mv에서 삭제하는 로직
|
|
81
|
-
* @param val - tagWrapper에서 [x]클릭된 목록의 value
|
|
82
|
-
*/
|
|
83
|
-
const removeMv = async (val) => {
|
|
84
|
-
if (!props.disabled) {
|
|
85
|
-
const idx = mv.value.indexOf(val);
|
|
86
|
-
mv.value.splice(idx, 1);
|
|
87
|
-
mv.value = [...mv.value];
|
|
88
|
-
await changeMv();
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
mv,
|
|
94
|
-
selectedModel,
|
|
95
|
-
computedPlaceholder,
|
|
96
|
-
isClearableIcon,
|
|
97
|
-
removeAllMv,
|
|
98
|
-
removeMv,
|
|
99
|
-
changeMv,
|
|
100
|
-
};
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export const useDropdown = (param) => {
|
|
104
|
-
const { props } = getCurrentInstance();
|
|
105
|
-
const { mv, changeMv } = param;
|
|
106
|
-
|
|
107
|
-
const isDropbox = ref(false);
|
|
108
|
-
const filterTextRef = ref(props.filterText);
|
|
109
|
-
const select = ref(null);
|
|
110
|
-
const selectWrapper = ref(null);
|
|
111
|
-
const dropbox = ref(null);
|
|
112
|
-
const itemWrapper = ref(null);
|
|
113
|
-
const dropboxPosition = reactive({
|
|
114
|
-
top: 0,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* filterable 모드 시 인풋박스에 입력된 텍스트가 포함된 목록 가져오기
|
|
119
|
-
* @param val - filterable 모드 시 인풋박스에 입력된 텍스트
|
|
120
|
-
* @returns [] - 필터링 결과의 목록
|
|
121
|
-
*/
|
|
122
|
-
const filteredItems = computed(() => {
|
|
123
|
-
if (!filterTextRef.value || !props.filterable) {
|
|
124
|
-
return props.items;
|
|
125
|
-
}
|
|
126
|
-
const trimText = filterTextRef.value?.trim();
|
|
127
|
-
return props.items.filter(v => v.name.toUpperCase().includes(trimText.toUpperCase())) || [];
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* filterable 에서 text input 이벤트 핸들러
|
|
132
|
-
*/
|
|
133
|
-
const changeFilterText = (e) => {
|
|
134
|
-
filterTextRef.value = e?.target?.value;
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* dropdown box 위치 변경하는 메소드
|
|
139
|
-
*/
|
|
140
|
-
const changeDropboxPosition = async () => {
|
|
141
|
-
await nextTick();
|
|
142
|
-
const selectHeight = selectWrapper.value?.getBoundingClientRect().height;
|
|
143
|
-
const selectY = selectWrapper.value?.getBoundingClientRect().y;
|
|
144
|
-
const dropboxHeight = dropbox.value?.getBoundingClientRect().height;
|
|
145
|
-
const docHeight = document.documentElement.clientHeight;
|
|
146
|
-
if (docHeight < selectY + selectHeight + dropboxHeight) {
|
|
147
|
-
dropboxPosition.top = `-${dropboxHeight}px`; // dropTop
|
|
148
|
-
} else {
|
|
149
|
-
dropboxPosition.top = `${selectHeight}px`; // dropDown
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* dropdown box 내 선택한 첫번째 아이템을 스크롤 가장 위로 올리는 메소드
|
|
155
|
-
*/
|
|
156
|
-
const scrollToSelectedItem = () => {
|
|
157
|
-
if (!itemWrapper.value?.children[0]?.children?.length) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
const SELECTED_CLS = 'selected';
|
|
161
|
-
const childEls = itemWrapper.value.children[0].children;
|
|
162
|
-
const wrapperOffsetTop = itemWrapper.value.offsetTop;
|
|
163
|
-
let childEl = null;
|
|
164
|
-
for (let i = 0; i < childEls.length; i++) {
|
|
165
|
-
childEl = childEls[i];
|
|
166
|
-
if (childEl.classList.contains(SELECTED_CLS)) {
|
|
167
|
-
if (!childEl.offsetTop) {
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
itemWrapper.value.scrollTop = childEl.offsetTop - wrapperOffsetTop;
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
watch(
|
|
177
|
-
() => isDropbox.value,
|
|
178
|
-
(cur) => {
|
|
179
|
-
if (cur) {
|
|
180
|
-
scrollToSelectedItem();
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
if (props.filterable) {
|
|
186
|
-
watch(
|
|
187
|
-
() => filteredItems.value,
|
|
188
|
-
() => changeDropboxPosition(),
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* 인풋박스 클릭 이벤트
|
|
194
|
-
* props로 받는 항목이 없는 경우 return처리
|
|
195
|
-
* 인풋박스 위 클릭된 이벤트위치로 드롭박스의 사이즈, 위치를 계산
|
|
196
|
-
*/
|
|
197
|
-
const clickSelectInput = async () => {
|
|
198
|
-
if (props.items.length && !props.disabled) {
|
|
199
|
-
isDropbox.value = !isDropbox.value;
|
|
200
|
-
if (isDropbox.value) {
|
|
201
|
-
await changeDropboxPosition();
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* 드롭박스 외부 클릭 이벤트
|
|
208
|
-
* filterable 모드인 경우는 필터링텍스트를 비운다.
|
|
209
|
-
*/
|
|
210
|
-
const clickOutsideDropbox = () => {
|
|
211
|
-
if (props.filterable) {
|
|
212
|
-
filterTextRef.value = '';
|
|
213
|
-
}
|
|
214
|
-
isDropbox.value = false;
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* 항목 클릭하여 선택하는 이벤트
|
|
219
|
-
* 항목 내 disabled인 경우 클릭 로직을 타지 않게 한다.
|
|
220
|
-
* multiple 모드가 아닌경우 리스트 클릭 시 드롭박스를 닫는다.
|
|
221
|
-
* @param val - clicked item value
|
|
222
|
-
*/
|
|
223
|
-
const singleClickItem = (val) => {
|
|
224
|
-
if (props.filterable) {
|
|
225
|
-
filterTextRef.value = '';
|
|
226
|
-
}
|
|
227
|
-
mv.value = val;
|
|
228
|
-
isDropbox.value = false;
|
|
229
|
-
changeMv();
|
|
230
|
-
};
|
|
231
|
-
const multipleClickItem = (val) => {
|
|
232
|
-
if (props.filterable) {
|
|
233
|
-
filterTextRef.value = '';
|
|
234
|
-
}
|
|
235
|
-
if (!mv.value.includes(val)) {
|
|
236
|
-
mv.value.push(val);
|
|
237
|
-
} else {
|
|
238
|
-
const idx = mv.value.indexOf(val);
|
|
239
|
-
mv.value.splice(idx, 1);
|
|
240
|
-
}
|
|
241
|
-
changeMv();
|
|
242
|
-
};
|
|
243
|
-
const clickItem = !props.multiple ? singleClickItem : multipleClickItem;
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* 선택된 아이템을 구별하는 메소드
|
|
247
|
-
* @param val
|
|
248
|
-
* @returns {boolean | array}
|
|
249
|
-
*/
|
|
250
|
-
const singleSelectedCls = val => val === mv.value;
|
|
251
|
-
const multipleSelectedCls = val => mv.value.includes(val);
|
|
252
|
-
const selectedItemClass = !props.multiple ? singleSelectedCls : multipleSelectedCls;
|
|
253
|
-
|
|
254
|
-
return {
|
|
255
|
-
select,
|
|
256
|
-
selectWrapper,
|
|
257
|
-
dropbox,
|
|
258
|
-
itemWrapper,
|
|
259
|
-
isDropbox,
|
|
260
|
-
dropboxPosition,
|
|
261
|
-
filterTextRef,
|
|
262
|
-
filteredItems,
|
|
263
|
-
clickSelectInput,
|
|
264
|
-
clickOutsideDropbox,
|
|
265
|
-
changeFilterText,
|
|
266
|
-
changeDropboxPosition,
|
|
267
|
-
clickItem,
|
|
268
|
-
selectedItemClass,
|
|
269
|
-
};
|
|
270
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
ref, reactive, computed, watch,
|
|
3
|
+
nextTick, getCurrentInstance,
|
|
4
|
+
} from 'vue';
|
|
5
|
+
|
|
6
|
+
export const useModel = () => {
|
|
7
|
+
const { props, emit } = getCurrentInstance();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Select 컴포넌트의 v-model 값
|
|
11
|
+
* single 모드 : modelValue(String), 없는 경우 null
|
|
12
|
+
* multiple 모드 : modelValue(Array), 없는 경우 []
|
|
13
|
+
*/
|
|
14
|
+
const singleMv = {
|
|
15
|
+
get: () => {
|
|
16
|
+
if (props.items.some(v => v.value === props.modelValue)) {
|
|
17
|
+
return props.modelValue;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
},
|
|
21
|
+
set: value => emit('update:modelValue', value),
|
|
22
|
+
};
|
|
23
|
+
const multiMv = {
|
|
24
|
+
get: () => {
|
|
25
|
+
if (Array.isArray(props.modelValue)) {
|
|
26
|
+
return props.modelValue;
|
|
27
|
+
}
|
|
28
|
+
return [];
|
|
29
|
+
},
|
|
30
|
+
set: value => emit('update:modelValue', value),
|
|
31
|
+
};
|
|
32
|
+
const mv = computed(!props.multiple ? singleMv : multiMv);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 현재 select에서 선택된 항목들
|
|
36
|
+
* single 모드 : { name: 'name', value: 'value' }
|
|
37
|
+
* multiple 모드 : [{ name: 'name', value: 'value' }, {...}]
|
|
38
|
+
*/
|
|
39
|
+
const singleSm = () => props.items.find(v => v.value === mv.value)?.name;
|
|
40
|
+
const multipleSm = () => props.items.filter(v => props.modelValue.includes(v.value));
|
|
41
|
+
const selectedModel = computed(!props.multiple ? singleSm : multipleSm);
|
|
42
|
+
|
|
43
|
+
const computedPlaceholder = computed(() => {
|
|
44
|
+
if (!props.multiple) {
|
|
45
|
+
return props.placeholder;
|
|
46
|
+
}
|
|
47
|
+
return mv.value.length ? null : props.placeholder;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* clearable 모드일 때, 항목(mv) 전체 삭제 아이콘 존재여부
|
|
52
|
+
*/
|
|
53
|
+
const singleIci = () => mv.value;
|
|
54
|
+
const multipleIci = () => mv.value.length;
|
|
55
|
+
const isClearableIcon = computed(!props.multiple ? singleIci : multipleIci);
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* clearable모드일 때 [x] 아이콘 클릭 시 mv값을 초기화
|
|
59
|
+
*/
|
|
60
|
+
const removeAllMv = () => {
|
|
61
|
+
if (!props.disabled) {
|
|
62
|
+
if (!props.multiple) {
|
|
63
|
+
mv.value = null;
|
|
64
|
+
} else {
|
|
65
|
+
mv.value.splice(0);
|
|
66
|
+
mv.value = [...mv.value];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 해당 컴포넌트의 v-model값이 변경(change)되는 이벤트
|
|
73
|
+
*/
|
|
74
|
+
const changeMv = async () => {
|
|
75
|
+
await nextTick();
|
|
76
|
+
emit('change', mv.value);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* multiple 모드인 경우 선택된 value를 mv에서 삭제하는 로직
|
|
81
|
+
* @param val - tagWrapper에서 [x]클릭된 목록의 value
|
|
82
|
+
*/
|
|
83
|
+
const removeMv = async (val) => {
|
|
84
|
+
if (!props.disabled) {
|
|
85
|
+
const idx = mv.value.indexOf(val);
|
|
86
|
+
mv.value.splice(idx, 1);
|
|
87
|
+
mv.value = [...mv.value];
|
|
88
|
+
await changeMv();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
mv,
|
|
94
|
+
selectedModel,
|
|
95
|
+
computedPlaceholder,
|
|
96
|
+
isClearableIcon,
|
|
97
|
+
removeAllMv,
|
|
98
|
+
removeMv,
|
|
99
|
+
changeMv,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const useDropdown = (param) => {
|
|
104
|
+
const { props } = getCurrentInstance();
|
|
105
|
+
const { mv, changeMv } = param;
|
|
106
|
+
|
|
107
|
+
const isDropbox = ref(false);
|
|
108
|
+
const filterTextRef = ref(props.filterText);
|
|
109
|
+
const select = ref(null);
|
|
110
|
+
const selectWrapper = ref(null);
|
|
111
|
+
const dropbox = ref(null);
|
|
112
|
+
const itemWrapper = ref(null);
|
|
113
|
+
const dropboxPosition = reactive({
|
|
114
|
+
top: 0,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* filterable 모드 시 인풋박스에 입력된 텍스트가 포함된 목록 가져오기
|
|
119
|
+
* @param val - filterable 모드 시 인풋박스에 입력된 텍스트
|
|
120
|
+
* @returns [] - 필터링 결과의 목록
|
|
121
|
+
*/
|
|
122
|
+
const filteredItems = computed(() => {
|
|
123
|
+
if (!filterTextRef.value || !props.filterable) {
|
|
124
|
+
return props.items;
|
|
125
|
+
}
|
|
126
|
+
const trimText = filterTextRef.value?.trim();
|
|
127
|
+
return props.items.filter(v => v.name.toUpperCase().includes(trimText.toUpperCase())) || [];
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* filterable 에서 text input 이벤트 핸들러
|
|
132
|
+
*/
|
|
133
|
+
const changeFilterText = (e) => {
|
|
134
|
+
filterTextRef.value = e?.target?.value;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* dropdown box 위치 변경하는 메소드
|
|
139
|
+
*/
|
|
140
|
+
const changeDropboxPosition = async () => {
|
|
141
|
+
await nextTick();
|
|
142
|
+
const selectHeight = selectWrapper.value?.getBoundingClientRect().height;
|
|
143
|
+
const selectY = selectWrapper.value?.getBoundingClientRect().y;
|
|
144
|
+
const dropboxHeight = dropbox.value?.getBoundingClientRect().height;
|
|
145
|
+
const docHeight = document.documentElement.clientHeight;
|
|
146
|
+
if (docHeight < selectY + selectHeight + dropboxHeight) {
|
|
147
|
+
dropboxPosition.top = `-${dropboxHeight}px`; // dropTop
|
|
148
|
+
} else {
|
|
149
|
+
dropboxPosition.top = `${selectHeight}px`; // dropDown
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* dropdown box 내 선택한 첫번째 아이템을 스크롤 가장 위로 올리는 메소드
|
|
155
|
+
*/
|
|
156
|
+
const scrollToSelectedItem = () => {
|
|
157
|
+
if (!itemWrapper.value?.children[0]?.children?.length) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const SELECTED_CLS = 'selected';
|
|
161
|
+
const childEls = itemWrapper.value.children[0].children;
|
|
162
|
+
const wrapperOffsetTop = itemWrapper.value.offsetTop;
|
|
163
|
+
let childEl = null;
|
|
164
|
+
for (let i = 0; i < childEls.length; i++) {
|
|
165
|
+
childEl = childEls[i];
|
|
166
|
+
if (childEl.classList.contains(SELECTED_CLS)) {
|
|
167
|
+
if (!childEl.offsetTop) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
itemWrapper.value.scrollTop = childEl.offsetTop - wrapperOffsetTop;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
watch(
|
|
177
|
+
() => isDropbox.value,
|
|
178
|
+
(cur) => {
|
|
179
|
+
if (cur) {
|
|
180
|
+
scrollToSelectedItem();
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (props.filterable) {
|
|
186
|
+
watch(
|
|
187
|
+
() => filteredItems.value,
|
|
188
|
+
() => changeDropboxPosition(),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 인풋박스 클릭 이벤트
|
|
194
|
+
* props로 받는 항목이 없는 경우 return처리
|
|
195
|
+
* 인풋박스 위 클릭된 이벤트위치로 드롭박스의 사이즈, 위치를 계산
|
|
196
|
+
*/
|
|
197
|
+
const clickSelectInput = async () => {
|
|
198
|
+
if (props.items.length && !props.disabled) {
|
|
199
|
+
isDropbox.value = !isDropbox.value;
|
|
200
|
+
if (isDropbox.value) {
|
|
201
|
+
await changeDropboxPosition();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 드롭박스 외부 클릭 이벤트
|
|
208
|
+
* filterable 모드인 경우는 필터링텍스트를 비운다.
|
|
209
|
+
*/
|
|
210
|
+
const clickOutsideDropbox = () => {
|
|
211
|
+
if (props.filterable) {
|
|
212
|
+
filterTextRef.value = '';
|
|
213
|
+
}
|
|
214
|
+
isDropbox.value = false;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 항목 클릭하여 선택하는 이벤트
|
|
219
|
+
* 항목 내 disabled인 경우 클릭 로직을 타지 않게 한다.
|
|
220
|
+
* multiple 모드가 아닌경우 리스트 클릭 시 드롭박스를 닫는다.
|
|
221
|
+
* @param val - clicked item value
|
|
222
|
+
*/
|
|
223
|
+
const singleClickItem = (val) => {
|
|
224
|
+
if (props.filterable) {
|
|
225
|
+
filterTextRef.value = '';
|
|
226
|
+
}
|
|
227
|
+
mv.value = val;
|
|
228
|
+
isDropbox.value = false;
|
|
229
|
+
changeMv();
|
|
230
|
+
};
|
|
231
|
+
const multipleClickItem = (val) => {
|
|
232
|
+
if (props.filterable) {
|
|
233
|
+
filterTextRef.value = '';
|
|
234
|
+
}
|
|
235
|
+
if (!mv.value.includes(val)) {
|
|
236
|
+
mv.value.push(val);
|
|
237
|
+
} else {
|
|
238
|
+
const idx = mv.value.indexOf(val);
|
|
239
|
+
mv.value.splice(idx, 1);
|
|
240
|
+
}
|
|
241
|
+
changeMv();
|
|
242
|
+
};
|
|
243
|
+
const clickItem = !props.multiple ? singleClickItem : multipleClickItem;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 선택된 아이템을 구별하는 메소드
|
|
247
|
+
* @param val
|
|
248
|
+
* @returns {boolean | array}
|
|
249
|
+
*/
|
|
250
|
+
const singleSelectedCls = val => val === mv.value;
|
|
251
|
+
const multipleSelectedCls = val => mv.value.includes(val);
|
|
252
|
+
const selectedItemClass = !props.multiple ? singleSelectedCls : multipleSelectedCls;
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
select,
|
|
256
|
+
selectWrapper,
|
|
257
|
+
dropbox,
|
|
258
|
+
itemWrapper,
|
|
259
|
+
isDropbox,
|
|
260
|
+
dropboxPosition,
|
|
261
|
+
filterTextRef,
|
|
262
|
+
filteredItems,
|
|
263
|
+
clickSelectInput,
|
|
264
|
+
clickOutsideDropbox,
|
|
265
|
+
changeFilterText,
|
|
266
|
+
changeDropboxPosition,
|
|
267
|
+
clickItem,
|
|
268
|
+
selectedItemClass,
|
|
269
|
+
};
|
|
270
|
+
};
|