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.
Files changed (141) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +40 -40
  3. package/dist/evui.common.js +1907 -1832
  4. package/dist/evui.common.js.map +1 -1
  5. package/dist/evui.umd.js +1907 -1832
  6. package/dist/evui.umd.js.map +1 -1
  7. package/dist/evui.umd.min.js +1 -1
  8. package/dist/evui.umd.min.js.map +1 -1
  9. package/dist/img/{EVUI.7f3588fb.svg → EVUI.b82ee81a.svg} +292 -292
  10. package/dist/img/{icon_mysql.7ea26d5d.svg → icon_mysql.1085fdc9.svg} +78 -78
  11. package/dist/img/{icon_oracle.9009b108.svg → icon_oracle.0572d3ee.svg} +13 -13
  12. package/dist/img/{icon_postgresql.f8fffba9.svg → icon_postgresql.ee12bde8.svg} +58 -58
  13. package/package.json +61 -61
  14. package/src/common/emitter.js +20 -20
  15. package/src/common/utils.debounce.js +223 -223
  16. package/src/common/utils.js +134 -134
  17. package/src/common/utils.table.js +78 -78
  18. package/src/common/utils.throttle.js +83 -83
  19. package/src/common/utils.tree.js +18 -18
  20. package/src/components/button/Button.vue +198 -198
  21. package/src/components/button/index.js +7 -7
  22. package/src/components/buttonGroup/ButtonGroup.vue +11 -11
  23. package/src/components/buttonGroup/index.js +7 -7
  24. package/src/components/calendar/Calendar.vue +661 -661
  25. package/src/components/calendar/index.js +7 -7
  26. package/src/components/calendar/uses.js +1272 -1272
  27. package/src/components/chart/Chart.vue +189 -192
  28. package/src/components/chart/chart.core.js +870 -870
  29. package/src/components/chart/element/element.bar.js +524 -524
  30. package/src/components/chart/element/element.bar.time.js +156 -156
  31. package/src/components/chart/element/element.heatmap.js +533 -533
  32. package/src/components/chart/element/element.line.js +339 -339
  33. package/src/components/chart/element/element.pie.js +197 -197
  34. package/src/components/chart/element/element.scatter.js +184 -184
  35. package/src/components/chart/element/element.tip.js +550 -542
  36. package/src/components/chart/helpers/helpers.canvas.js +265 -265
  37. package/src/components/chart/helpers/helpers.constant.js +206 -206
  38. package/src/components/chart/helpers/helpers.util.js +346 -338
  39. package/src/components/chart/index.js +9 -9
  40. package/src/components/chart/model/index.js +4 -4
  41. package/src/components/chart/model/model.series.js +93 -93
  42. package/src/components/chart/model/model.store.js +977 -967
  43. package/src/components/chart/plugins/plugins.interaction.js +769 -769
  44. package/src/components/chart/plugins/plugins.legend.gradient.js +602 -602
  45. package/src/components/chart/plugins/plugins.legend.js +1155 -1151
  46. package/src/components/chart/plugins/plugins.pie.js +254 -254
  47. package/src/components/chart/plugins/plugins.title.js +56 -56
  48. package/src/components/chart/plugins/plugins.tooltip.js +692 -692
  49. package/src/components/chart/scale/scale.js +848 -848
  50. package/src/components/chart/scale/scale.linear.js +38 -38
  51. package/src/components/chart/scale/scale.logarithmic.js +128 -128
  52. package/src/components/chart/scale/scale.step.js +336 -336
  53. package/src/components/chart/scale/scale.time.category.js +277 -277
  54. package/src/components/chart/scale/scale.time.js +48 -48
  55. package/src/components/chart/style/chart.scss +312 -312
  56. package/src/components/chart/uses.js +264 -252
  57. package/src/components/checkbox/Checkbox.vue +200 -200
  58. package/src/components/checkbox/index.js +7 -7
  59. package/src/components/checkboxGroup/CheckboxGroup.vue +44 -44
  60. package/src/components/checkboxGroup/index.js +7 -7
  61. package/src/components/contextMenu/ContextMenu.vue +80 -80
  62. package/src/components/contextMenu/MenuList.vue +149 -149
  63. package/src/components/contextMenu/index.js +7 -7
  64. package/src/components/contextMenu/uses.js +203 -203
  65. package/src/components/datePicker/DatePicker.vue +437 -437
  66. package/src/components/datePicker/index.js +7 -7
  67. package/src/components/datePicker/uses.js +419 -419
  68. package/src/components/grid/Grid.vue +827 -827
  69. package/src/components/grid/grid.filter.window.vue +493 -493
  70. package/src/components/grid/grid.pagination.vue +75 -75
  71. package/src/components/grid/grid.summary.vue +265 -265
  72. package/src/components/grid/grid.toolbar.vue +26 -26
  73. package/src/components/grid/index.js +11 -11
  74. package/src/components/grid/style/grid.scss +263 -263
  75. package/src/components/grid/uses.js +1002 -1007
  76. package/src/components/icon/Icon.vue +49 -49
  77. package/src/components/icon/index.js +8 -8
  78. package/src/components/inputNumber/InputNumber.vue +212 -212
  79. package/src/components/inputNumber/index.js +7 -7
  80. package/src/components/inputNumber/uses.js +217 -217
  81. package/src/components/loading/Loading.vue +125 -125
  82. package/src/components/loading/index.js +7 -7
  83. package/src/components/menu/Menu.vue +68 -68
  84. package/src/components/menu/MenuItem.vue +187 -187
  85. package/src/components/menu/index.js +7 -7
  86. package/src/components/message/Message.vue +223 -223
  87. package/src/components/message/index.js +31 -31
  88. package/src/components/messageBox/MessageBox.vue +358 -358
  89. package/src/components/messageBox/index.js +22 -22
  90. package/src/components/notification/Notification.vue +316 -316
  91. package/src/components/notification/index.js +49 -49
  92. package/src/components/pagination/Pagination.vue +271 -271
  93. package/src/components/pagination/index.js +7 -7
  94. package/src/components/pagination/pageButton.vue +30 -30
  95. package/src/components/progress/Progress.vue +139 -139
  96. package/src/components/progress/index.js +7 -7
  97. package/src/components/radio/Radio.vue +159 -159
  98. package/src/components/radio/index.js +7 -7
  99. package/src/components/radioGroup/RadioGroup.vue +41 -41
  100. package/src/components/radioGroup/index.js +7 -7
  101. package/src/components/scheduler/Scheduler.vue +149 -149
  102. package/src/components/scheduler/index.js +7 -7
  103. package/src/components/scheduler/uses.js +183 -183
  104. package/src/components/select/Select.vue +440 -440
  105. package/src/components/select/index.js +7 -7
  106. package/src/components/select/uses.js +270 -270
  107. package/src/components/slider/Slider.vue +505 -505
  108. package/src/components/slider/index.js +7 -7
  109. package/src/components/slider/uses.js +390 -390
  110. package/src/components/tabPanel/TabPanel.vue +74 -74
  111. package/src/components/tabPanel/index.js +7 -7
  112. package/src/components/tabs/Tabs.vue +517 -517
  113. package/src/components/tabs/index.js +7 -7
  114. package/src/components/textField/TextField.vue +375 -375
  115. package/src/components/textField/index.js +7 -7
  116. package/src/components/timePicker/TimePicker.vue +352 -352
  117. package/src/components/timePicker/index.js +7 -7
  118. package/src/components/toggle/Toggle.vue +115 -115
  119. package/src/components/toggle/index.js +7 -7
  120. package/src/components/tree/Tree.vue +313 -313
  121. package/src/components/tree/TreeNode.vue +293 -293
  122. package/src/components/tree/index.js +7 -7
  123. package/src/components/treeGrid/TreeGrid.vue +758 -758
  124. package/src/components/treeGrid/TreeGridNode.vue +275 -275
  125. package/src/components/treeGrid/index.js +9 -9
  126. package/src/components/treeGrid/style/treeGrid.scss +261 -261
  127. package/src/components/treeGrid/treeGrid.toolbar.vue +26 -26
  128. package/src/components/treeGrid/uses.js +867 -867
  129. package/src/components/window/Window.vue +329 -329
  130. package/src/components/window/index.js +7 -7
  131. package/src/components/window/uses.js +899 -899
  132. package/src/directives/clickoutside.js +90 -90
  133. package/src/main.js +116 -116
  134. package/src/style/components/input.scss +108 -108
  135. package/src/style/functions.scss +3 -3
  136. package/src/style/index.scss +6 -6
  137. package/src/style/lib/fonts/EVUI.svg +292 -292
  138. package/src/style/lib/icon.css +888 -888
  139. package/src/style/mixins.scss +94 -94
  140. package/src/style/themes.scss +67 -67
  141. package/src/style/variables.scss +22 -22
@@ -1,1272 +1,1272 @@
1
- import {
2
- ref, reactive, computed, getCurrentInstance, unref, onBeforeMount, watch,
3
- } from 'vue';
4
- import { throttle } from 'lodash-es';
5
-
6
- const CALENDAR_ROWS = 6;
7
- const CALENDAR_COLS = 7;
8
- const MONTH_CNT = 12;
9
- const HOUR_CNT = 24;
10
- const MIN_CNT = 60;
11
- const SEC_CNT = 60;
12
- const CELL_CNT_IN_ONE_PAGE = 12;
13
- const CELL_CNT_IN_ONE_ROW = 4;
14
- const MONTH_NAME_LIST = {
15
- fullName: ['January', 'February', 'March', 'April', 'May', 'June',
16
- 'July', 'August', 'September', 'October', 'November', 'December'],
17
- numberName: ['1', '2', '3', '4', '5', '6',
18
- '7', '8', '9', '10', '11', '12'],
19
- abbrName: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
20
- 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'],
21
- korName: ['1월', '2월', '3월', '4월', '5월', '6월',
22
- '7월', '8월', '9월', '10월', '11월', '12월'],
23
- };
24
- const DAY_OF_THE_WEEK_NAME_LIST = {
25
- abbrUpperName: ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'],
26
- abbrLowerName: ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
27
- abbrPascalName: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
28
- abbrKorName: ['일', '월', '화', '수', '목', '금', '토'],
29
- };
30
-
31
- const ONE_DAY_MS = 86400000;
32
- const dateReg = new RegExp(/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/);
33
- const dateTimeReg = new RegExp(/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9]/);
34
-
35
- /**
36
- * 배열 내 여러 날짜(eg. 'YYYY-MM-DD' || 'YYYY-MM-DD HH:MI:SS') 중 가장 끝의 날짜 텍스트 구하기
37
- * @param arr
38
- * @param sideDirection - 끝의 방향 (first: 가장 멀리 오래된 날짜, last: 가장 최근의 날짜)
39
- * @returns {String} - 날짜 텍스트
40
- */
41
- const getSideDateStr = (arr, sideDirection) => {
42
- if (!arr.length) return '';
43
- if (sideDirection === 'last') {
44
- return arr
45
- .reduce((prev, cur) => (new Date(prev).getTime() > new Date(cur).getTime() ? prev : cur));
46
- }
47
- return arr
48
- .reduce((prev, cur) => (new Date(prev).getTime() < new Date(cur).getTime() ? prev : cur));
49
- };
50
-
51
- /**
52
- * 월, 일을 두자리 숫자로 보정
53
- * @param num
54
- * @returns {string|*}
55
- */
56
- export const lpadToTwoDigits = (num) => {
57
- if (num === null) {
58
- return '00';
59
- } else if (+num < 10) {
60
- return `0${num}`;
61
- }
62
- return num;
63
- };
64
-
65
- /**
66
- * 이차원 배열 만들기
67
- * @param row
68
- * @param col
69
- * @returns {Array} - [row][col]
70
- */
71
- const getMatrixArr = (row, col) => Array.from(Array(row), () => Array(col).fill(false));
72
-
73
- /**
74
- * y년 m월 1일의 요일 구하기
75
- * @param y - 년
76
- * @param m - 월
77
- * @returns {number} - 해당 y년 m월 1일의 요일 (e.g. 0: SUN, ..., 6: SAT)
78
- * - 1주차에서 일요일부터 1일까지의 공백 개수
79
- */
80
- const getDayOfWeekOnThe1stOfMonth = (y, m) => new Date(`${y}-${m}-1`).getDay();
81
-
82
- /**
83
- * y년 m월 마지막 일자 구하기
84
- * @param y
85
- * @param m
86
- * @returns {number} - 해당 년, 월의 마지막 일자
87
- */
88
- const getLastDateOfMonth = (y, m) => {
89
- let day;
90
- switch (m) {
91
- case 4:
92
- case 6:
93
- case 9:
94
- case 11:
95
- day = 30;
96
- break;
97
- case 2:
98
- if (((y % 4 === 0) && (y % 100 !== 0)) || (y % 400 === 0)) {
99
- day = 29;
100
- } else {
101
- day = 28;
102
- }
103
- break;
104
- default:
105
- day = 31;
106
- break;
107
- }
108
- return day;
109
- };
110
-
111
- /**
112
- * date또는 time 형태로 format string으로 조합
113
- * @param year
114
- * @param month
115
- * @param date
116
- * @param hour
117
- * @param min
118
- * @param sec
119
- * @returns {string}
120
- */
121
- const formatDateTime = ({ year, month, date, hour, min, sec }) => {
122
- if (hour !== undefined && min !== undefined && sec !== undefined) {
123
- return `${year}-${lpadToTwoDigits(month)}-${lpadToTwoDigits(date)} ${lpadToTwoDigits(hour)}:${lpadToTwoDigits(min)}:${lpadToTwoDigits(sec)}`;
124
- }
125
- return `${year}-${lpadToTwoDigits(month)}-${lpadToTwoDigits(date)}`;
126
- };
127
-
128
- /**
129
- * 첫번째 인자로 받은 날짜 형식 String ('YYYY-MM-DD' || 'YYYY-MM-DD HH:MI:SS')이나
130
- * 해당 날짜형식이 들어있는 Array를 받아서 최신날짜의 정보를 추출하는 함수
131
- * typeToImport가 존재하는 경우 해당 timeType의 값을
132
- * typeToImport가 존재하지 않는 경우 최신날짜 텍스트를 timeType별로 분할한 Object를 리턴
133
- * @param param {String | Array} - 변경하려는 날짜
134
- * @param typeToImport
135
- * @returns {object|number}
136
- */
137
- const getDateTimeInfoByType = (param, typeToImport) => {
138
- let str = unref(param);
139
- if (Array.isArray(str)) {
140
- str = getSideDateStr(param, 'last');
141
- }
142
- const result = {
143
- year: +(str?.split(' ')[0]?.split('-')[0]) || null,
144
- month: +(str?.split(' ')[0]?.split('-')[1]) || null,
145
- date: +(str?.split(' ')[0]?.split('-')[2]) || null,
146
- hour: +(str?.split(' ')[1]?.split(':')[0]) || 0,
147
- min: +(str?.split(' ')[1]?.split(':')[1]) || 0,
148
- sec: +(str?.split(' ')[1]?.split(':')[2]) || 0,
149
- };
150
- if (typeToImport === 'year') return result.year;
151
- if (typeToImport === 'month') return result.month;
152
- if (typeToImport === 'date') return result.date;
153
- if (typeToImport === 'hour') return result.hour;
154
- if (typeToImport === 'min') return result.min;
155
- if (typeToImport === 'sec') return result.sec;
156
- return result;
157
- };
158
- /**
159
- * 이전달, 다음달의 달력 상 연도, 월 정보 구하기
160
- * @param prevNext - 이전, 다음 여부 ('prev'|'next')
161
- * @param year
162
- * @param month
163
- * @returns {{month: number, year: *}}
164
- */
165
- const getSideMonthCalendarInfo = (prevNext, year, month) => {
166
- if (prevNext === 'next') {
167
- return {
168
- year: month === 12 ? year + 1 : year,
169
- month: ((month + 1) % 12) || 12,
170
- };
171
- }
172
- return {
173
- year: month === 1 ? year - 1 : year,
174
- month: ((month - 1) % 12) || 12,
175
- };
176
- };
177
-
178
- /**
179
- * timeFormat을 체크하여 timeFormat이 있으면 format에 맞는 형식으로 반환
180
- * @param timeFormat -- props.option?.timeFormat
181
- * @param dateTimeValue
182
- * @param typeToImport
183
- * @returns {Object|number}
184
- */
185
- const getTimeInfoByTimeFormat = (timeFormat, dateTimeValue, typeToImport) => {
186
- const value = getDateTimeInfoByType(dateTimeValue, typeToImport);
187
- if (timeFormat) {
188
- const hour = timeFormat?.split(':')[0];
189
- const min = timeFormat?.split(':')[1];
190
- const sec = timeFormat?.split(':')[2];
191
- if (typeToImport === 'hour') {
192
- return hour === 'HH' ? value : +hour;
193
- } else if (typeToImport === 'min') {
194
- return min === 'mm' ? value : +min;
195
- } else if (typeToImport === 'sec') {
196
- return sec === 'ss' ? value : +sec;
197
- }
198
- }
199
- return value;
200
- };
201
-
202
- /**
203
- * 초기 timeFormat에 따른 modelValue update 함수
204
- * @param timeFormat - props.options.timeFormat
205
- * @param modelValue
206
- * @returns string
207
- */
208
- export const getChangedValueByTimeFormat = (timeFormat, modelValue) => {
209
- if (!modelValue) {
210
- return '';
211
- }
212
-
213
- const hourByTimeFormat = lpadToTwoDigits(getTimeInfoByTimeFormat(timeFormat, modelValue, 'hour'));
214
- const minByTimeFormat = lpadToTwoDigits(getTimeInfoByTimeFormat(timeFormat, modelValue, 'min'));
215
- const secByTimeFormat = lpadToTwoDigits(getTimeInfoByTimeFormat(timeFormat, modelValue, 'sec'));
216
-
217
- return `${modelValue.split(' ')[0]} ${hourByTimeFormat}:${minByTimeFormat}:${secByTimeFormat}`;
218
- };
219
-
220
- const compareFromAndToDateTime = (mode, calendarType, targetDate, modelValue) => {
221
- if (!modelValue.length) {
222
- return false;
223
- }
224
- let fromDate = calendarType === 'main' ? targetDate : modelValue[0];
225
- let toDate = calendarType === 'expanded' ? targetDate : modelValue[1];
226
-
227
- let fromDateTime = fromDate;
228
- let toDateTime = toDate;
229
- if (!targetDate.split(' ')[1]) {
230
- if (mode === 'dateTimeRange') {
231
- fromDate = fromDate.split(' ')[0];
232
- toDate = toDate.split(' ')[0];
233
- const fromTime = modelValue[0].split(' ')[1];
234
- const toTime = modelValue[1].split(' ')[1];
235
- fromDateTime = `${fromDate} ${fromTime}`;
236
- toDateTime = `${toDate} ${toTime}`;
237
- } else {
238
- fromDateTime = `${fromDate} 00:00:00`;
239
- toDateTime = `${toDate} 23:59:59`;
240
- }
241
- }
242
-
243
- return (fromDateTime && toDateTime)
244
- && new Date(fromDateTime).getTime() > +new Date(toDateTime).getTime();
245
- };
246
-
247
- /**
248
- * date string 값의 MS 값 구하기
249
- * @param dateStr
250
- * @returns {number}
251
- */
252
- const getDateMs = dateStr => new Date(`${dateStr}`).getTime();
253
-
254
- export const useModel = () => {
255
- const { props } = getCurrentInstance();
256
- const timeFormat = props.options?.timeFormat;
257
-
258
- /**
259
- * 현재 선택된 값, 배열인 경우 반응형을 끊기위해 rest 사용
260
- * selectValue ref로 변환하기 전 modelValue timeFormat에 따라 fetch
261
- * 1) props.mode: 'date' or 'dateTime' > String
262
- * 2) props.mode: 'dateMulti' or 'dateRange' > [...Array]
263
- */
264
- let selectedValue;
265
- if (props.mode !== 'dateMulti' && props.mode !== 'dateRange' && props.mode !== 'dateTimeRange') {
266
- if (props.modelValue
267
- && ((props.modelValue.length === 10 && dateReg.exec(props.modelValue?.toString()))
268
- || (props.modelValue.length === 19 && dateTimeReg.exec(props.modelValue?.toString())))
269
- ) {
270
- if (props.mode === 'dateTime' && timeFormat) {
271
- const modelValue = getChangedValueByTimeFormat(timeFormat, props.modelValue);
272
- selectedValue = ref(modelValue);
273
- } else {
274
- selectedValue = ref(props.modelValue);
275
- }
276
- } else {
277
- selectedValue = ref('');
278
- }
279
- } else if (Array.isArray(props.modelValue)
280
- && props.modelValue.every(v => (
281
- !v
282
- || (v.length === 10 && dateReg.exec(v))
283
- || (v.length === 19 && dateTimeReg.exec(v))
284
- ))
285
- ) {
286
- if (props.mode === 'dateTimeRange' && props.modelValue.length === 2 && timeFormat) {
287
- const modelValue = [];
288
- modelValue.push(getChangedValueByTimeFormat(timeFormat[0], props.modelValue[0]));
289
- modelValue.push(getChangedValueByTimeFormat(timeFormat[1], props.modelValue[1]));
290
- selectedValue = ref([...modelValue]);
291
- } else {
292
- selectedValue = ref([...props.modelValue]);
293
- }
294
- } else {
295
- selectedValue = ref([]);
296
- }
297
-
298
- /**
299
- * validate v-model's value
300
- */
301
- const validateModelValue = () => {
302
- if (props.mode === 'date' && props.modelValue && typeof props.modelValue !== 'string') {
303
- console.warn('[EVUI][Calendar] When mode is \'date\', v-model must be \'String\' type.');
304
- } else if (props.mode === 'dateTime' && props.modelValue && typeof props.modelValue !== 'string') {
305
- console.warn('[EVUI][Calendar] When mode is \'dateTime\', v-model must be \'String\' type.');
306
- } else if (props.mode === 'dateMulti' && props.modelValue && !Array.isArray(props.modelValue)) {
307
- console.warn('[EVUI][Calendar] When mode is \'dateMulti\', v-model must be \'Array\' type.');
308
- } else if (props.mode === 'dateRange' && props.modelValue) {
309
- if (!Array.isArray(props.modelValue)) {
310
- console.warn('[EVUI][Calendar] When mode is \'dateRange\', v-model must be \'Array\' type.');
311
- } else if (getDateMs(`${props.modelValue[0]} 00:00:00`) > getDateMs(`${props.modelValue[1]} 00:00:00`)) {
312
- console.warn('[EVUI][Calendar] When mode is \'dateRange\', fromDate must be less than toDate.');
313
- }
314
- } else if (props.mode === 'dateTimeRange' && props.modelValue) {
315
- if (!Array.isArray(props.modelValue)) {
316
- console.warn('[EVUI][Calendar] When mode is \'dateTimeRange\', v-model must be \'Array\' type.');
317
- } else if (getDateMs(props.modelValue[0]) > getDateMs(props.modelValue[1])) {
318
- console.warn('[EVUI][Calendar] When mode is \'dateRange\', fromDate must be less than toDate.');
319
- }
320
- }
321
- };
322
-
323
- // 메인(좌측) 달력(연, 월, 시, 분, 초) 페이징 정보
324
- let mainCalendarPageInfo;
325
- const mainValue = !['dateRange', 'dateTimeRange'].includes(props.mode) ? selectedValue.value : selectedValue.value[0];
326
- if (mainValue?.length) {
327
- mainCalendarPageInfo = reactive({
328
- year: getDateTimeInfoByType(mainValue, 'year'),
329
- month: getDateTimeInfoByType(mainValue, 'month'),
330
- hour: Math.floor(getDateTimeInfoByType(mainValue, 'hour') / CELL_CNT_IN_ONE_PAGE) + 1 || 1,
331
- min: Math.floor(getDateTimeInfoByType(mainValue, 'min') / CELL_CNT_IN_ONE_PAGE) + 1 || 1,
332
- sec: Math.floor(getDateTimeInfoByType(mainValue, 'sec') / CELL_CNT_IN_ONE_PAGE) + 1 || 1,
333
- });
334
- } else {
335
- mainCalendarPageInfo = reactive({
336
- year: new Date().getFullYear(),
337
- month: new Date().getMonth() + 1,
338
- hour: 1,
339
- min: 1,
340
- sec: 1,
341
- });
342
- }
343
-
344
- // 'mode: dateRange || dateTimeRange', 인 경우 확장된 달력(연, 월) 페이징 정보
345
- let expandedCalendarPageInfo;
346
- if ((['dateRange', 'dateTimeRange'].includes(props.mode))
347
- && Array.isArray(selectedValue.value)
348
- && selectedValue.value[1]
349
- ) {
350
- const expandedValue = selectedValue.value[1];
351
- const toDate = {
352
- year: getDateTimeInfoByType(expandedValue, 'year'),
353
- month: getDateTimeInfoByType(expandedValue, 'month'),
354
- };
355
- expandedCalendarPageInfo = reactive(toDate);
356
-
357
- if (props.mode === 'dateTimeRange') {
358
- expandedCalendarPageInfo.hour = Math.floor(getDateTimeInfoByType(expandedValue, 'hour') / CELL_CNT_IN_ONE_PAGE) + 1 || 1;
359
- expandedCalendarPageInfo.min = Math.floor(getDateTimeInfoByType(expandedValue, 'min') / CELL_CNT_IN_ONE_PAGE) + 1 || 1;
360
- expandedCalendarPageInfo.sec = Math.floor(getDateTimeInfoByType(expandedValue, 'sec') / CELL_CNT_IN_ONE_PAGE) + 1 || 1;
361
- }
362
- } else {
363
- expandedCalendarPageInfo = reactive({
364
- year: new Date().getFullYear(),
365
- month: new Date().getMonth() + 1,
366
- hour: 1,
367
- min: 1,
368
- sec: 1,
369
- });
370
- }
371
-
372
- // 현재 달력이 표현되는 월
373
- const mainCalendarMonth = computed(() =>
374
- MONTH_NAME_LIST[props.monthNotation][mainCalendarPageInfo.month - 1]);
375
- // 다음페이지 달력이 표현되는 월
376
- const expandedCalendarMonth = computed(() =>
377
- MONTH_NAME_LIST[props.monthNotation][expandedCalendarPageInfo.month - 1]);
378
- // 현재 달력에 표현되는 타입별 요일
379
- const dayOfTheWeekList = computed(() =>
380
- DAY_OF_THE_WEEK_NAME_LIST[props.dayOfTheWeekNotation]);
381
- // mode: dateRange에 두 달력이 연속적인 경우
382
- const isContinuousMonths = computed(
383
- () => ['dateRange', 'dateTimeRange'].includes(props.mode)
384
- && (mainCalendarPageInfo.year === expandedCalendarPageInfo.year
385
- && mainCalendarPageInfo.month === expandedCalendarPageInfo.month),
386
- );
387
-
388
- onBeforeMount(() => {
389
- validateModelValue();
390
- });
391
-
392
- return {
393
- selectedValue,
394
- mainCalendarPageInfo,
395
- expandedCalendarPageInfo,
396
- mainCalendarMonth,
397
- expandedCalendarMonth,
398
- dayOfTheWeekList,
399
- isContinuousMonths,
400
- };
401
- };
402
-
403
- export const useCalendarDate = (param) => {
404
- const { props, emit } = getCurrentInstance();
405
- const { selectedValue, mainCalendarPageInfo, expandedCalendarPageInfo } = param;
406
-
407
- // 메인 달력 테이블의 날짜 정보 (6X7, 2차원배열)
408
- const mainCalendarTableInfo = reactive(getMatrixArr(CALENDAR_ROWS, CALENDAR_COLS));
409
- // dateRange 모드의 확장된 달력 테이블의 날짜 정보
410
- const expandedCalendarTableInfo = reactive(getMatrixArr(CALENDAR_ROWS, CALENDAR_COLS));
411
- // 시간박스 정보
412
- const mainTimeTableInfo = reactive({
413
- hour: [],
414
- min: [],
415
- sec: [],
416
- });
417
- // dateTimeRange 모드의 확장된 달력 테이블의 시간 박스 정보
418
- const expandedTimeTableInfo = reactive({
419
- hour: [],
420
- min: [],
421
- sec: [],
422
- });
423
-
424
- /**
425
- * calendar setting 하기 전 선택된 날짜가 disabledDate에 포함되는지 체크
426
- * @param isRangeMode
427
- * @param calendarType
428
- * @param disabledDate
429
- */
430
- const checkDisabledDate = ({ isRangeMode, calendarType, disabledDate }) => {
431
- if (isRangeMode) {
432
- if (calendarType === 'main' && selectedValue.value[0]) {
433
- if (disabledDate && disabledDate(new Date(selectedValue.value[0]))) {
434
- selectedValue.value[0] = '';
435
- emit('update:modelValue', [...selectedValue.value]);
436
- }
437
- } else if (calendarType === 'expanded' && selectedValue.value[1]) {
438
- if (disabledDate && disabledDate(new Date(selectedValue.value[1]))) {
439
- selectedValue.value[1] = '';
440
- emit('update:modelValue', [...selectedValue.value]);
441
- }
442
- }
443
- } else if (props.mode === 'dateMulti') {
444
- let isUpdate = false;
445
- selectedValue.value.forEach((value, index) => {
446
- if (disabledDate && disabledDate(new Date(value))) {
447
- selectedValue.value.splice(index, 1);
448
- isUpdate = true;
449
- }
450
- });
451
- if (isUpdate) {
452
- emit('update:modelValue', [...selectedValue.value]);
453
- }
454
- } else if (disabledDate && disabledDate(new Date(selectedValue.value))) {
455
- selectedValue.value = '';
456
- emit('update:modelValue', selectedValue.value);
457
- }
458
- };
459
-
460
- /**
461
- * Dropdown Calendar 날짜 정보 세팅하기
462
- * @param calendarType - 달력 종류 ('main'|'expanded')
463
- */
464
- const setCalendarDate = (calendarType) => {
465
- const calendarPageInfo = calendarType === 'expanded'
466
- ? expandedCalendarPageInfo : mainCalendarPageInfo;
467
- const calendarTableInfo = calendarType === 'expanded'
468
- ? expandedCalendarTableInfo : mainCalendarTableInfo;
469
-
470
- let disabledDate = props.options.disabledDate;
471
- if (disabledDate && Array.isArray(disabledDate)) {
472
- disabledDate = calendarType === 'main' ? disabledDate[0] : disabledDate[1];
473
- }
474
- const isRangeMode = ['dateRange', 'dateTimeRange'].includes(props.mode);
475
-
476
- checkDisabledDate({
477
- isRangeMode,
478
- calendarType,
479
- disabledDate,
480
- });
481
-
482
- const TODAY_YMD = formatDateTime({
483
- year: new Date().getFullYear(),
484
- month: new Date().getMonth() + 1,
485
- date: new Date().getDate(),
486
- });
487
- const PREV_MONTH = computed(() =>
488
- ((MONTH_CNT + calendarPageInfo.month - 1) % MONTH_CNT) || MONTH_CNT);
489
- const NEXT_MONTH = computed(() =>
490
- ((calendarPageInfo.month + 1) % MONTH_CNT) || MONTH_CNT);
491
- const YEAR_OF_PREV_MONTH = computed(() => (calendarPageInfo.month === 1
492
- ? calendarPageInfo.year - 1 : calendarPageInfo.year));
493
- const YEAR_OF_NEXT_MONTH = computed(() => (calendarPageInfo.month === 12
494
- ? calendarPageInfo.year + 1 : calendarPageInfo.year));
495
- // 이번달 1일의 요일
496
- const dayOfWeekOnThe1stOfThisMonth = computed(() => getDayOfWeekOnThe1stOfMonth(
497
- calendarPageInfo.year,
498
- calendarPageInfo.month,
499
- ));
500
- // 저번달 마지막 날짜
501
- const lastDateOfPrevMonth = computed(() => getLastDateOfMonth(
502
- calendarPageInfo.month === 1
503
- ? calendarPageInfo.year - 1 : calendarPageInfo.year,
504
- (MONTH_CNT + calendarPageInfo.month - 1) % MONTH_CNT || MONTH_CNT,
505
- ));
506
- // 이번달 마지막 날짜
507
- const lastDateOfThisMonth = computed(() => getLastDateOfMonth(
508
- calendarPageInfo.year,
509
- calendarPageInfo.month,
510
- ));
511
-
512
- let modelValue = '';
513
- if (props.mode.includes('Time')) {
514
- if (props.mode === 'dateTime') {
515
- modelValue = selectedValue.value;
516
- } else {
517
- modelValue = calendarType === 'main' ? selectedValue.value[0] : selectedValue.value[1];
518
- }
519
- }
520
-
521
- let monthDate = 0;
522
- let year = 0;
523
- let month = 0;
524
- let date = 0;
525
- let currDate = '';
526
- // date 숫자 및 속성 세팅
527
- const setDateInfo = (monthType, i, j) => {
528
- currDate = formatDateTime({ year, month, date });
529
- const isInvalidDate = isRangeMode
530
- && compareFromAndToDateTime(props.mode, calendarType, currDate, selectedValue.value);
531
-
532
- // time 모드인 경우 현재 값의 시간을 가지고 테스트
533
- const timeValue = modelValue?.split(' ')[1] ?? '';
534
-
535
- const isDisabled = disabledDate
536
- ? disabledDate(new Date(`${currDate} ${timeValue}`)) : isInvalidDate;
537
-
538
- const index = +(calendarType !== 'main');
539
- const isRangeSelected = isRangeMode && selectedValue.value.length > index
540
- && selectedValue.value[index].split(' ')[0].includes(currDate);
541
- const isSelected = !isDisabled && (isRangeMode
542
- ? monthType === '' && isRangeSelected
543
- : selectedValue.value?.includes(currDate));
544
-
545
- // mode가 dateRange일 때는 이전, 다음달에 selected 를 하지 않는다.
546
- calendarTableInfo[i][j] = {
547
- monthType: `${monthType}${isDisabled ? ' disabled' : ''}`,
548
- isToday: TODAY_YMD === currDate,
549
- isSelected,
550
- year,
551
- month,
552
- date,
553
- };
554
- };
555
-
556
- for (let i = 0; i < CALENDAR_ROWS; i++) {
557
- for (let j = 0; j < CALENDAR_COLS; j++) {
558
- if (i === 0) {
559
- // 첫번째 주
560
- if (dayOfWeekOnThe1stOfThisMonth.value !== 0) {
561
- if (j < dayOfWeekOnThe1stOfThisMonth.value) {
562
- year = YEAR_OF_PREV_MONTH.value;
563
- month = PREV_MONTH.value;
564
- date = lastDateOfPrevMonth.value - dayOfWeekOnThe1stOfThisMonth.value + 1 + j;
565
- setDateInfo('prev', i, j);
566
- } else {
567
- monthDate++;
568
- year = calendarPageInfo.year;
569
- month = calendarPageInfo.month;
570
- date = monthDate;
571
- setDateInfo('', i, j);
572
- }
573
- } else {
574
- year = YEAR_OF_PREV_MONTH.value;
575
- month = PREV_MONTH.value;
576
- date = lastDateOfPrevMonth.value - 6 + j;
577
- setDateInfo('prev', i, j);
578
- }
579
- } else if (lastDateOfThisMonth.value <= monthDate) {
580
- // 마지막 -1, 마지막 주의 다음달 날짜
581
- monthDate++;
582
- year = YEAR_OF_NEXT_MONTH.value;
583
- month = NEXT_MONTH.value;
584
- date = monthDate - lastDateOfThisMonth.value;
585
- setDateInfo('next', i, j);
586
- } else {
587
- // 첫번째 주를 제외한 이번달 날짜
588
- monthDate++;
589
- year = calendarPageInfo.year;
590
- month = calendarPageInfo.month;
591
- date = monthDate;
592
- setDateInfo('', i, j);
593
- }
594
- }
595
- }
596
- };
597
-
598
- /**
599
- * Calendar 시간 정보 세팅하기
600
- */
601
- const setHmsTime = () => {
602
- const timeFormat = props.options?.timeFormat;
603
- const disabledDate = props.options?.disabledDate;
604
- const mainTimeFormat = Array.isArray(timeFormat) ? timeFormat[0] : timeFormat;
605
- const expandedTimeFormat = Array.isArray(timeFormat) ? timeFormat[1] : '';
606
- const mainDateTimeValue = props.mode === 'dateTimeRange' ? selectedValue.value[0] : selectedValue.value;
607
- const expandedDateTimeValue = props.mode === 'dateTimeRange' ? selectedValue.value[1] : '';
608
- const mainDisabledDate = Array.isArray(disabledDate) ? disabledDate[0] : disabledDate;
609
- const expandedDisabledDate = Array.isArray(disabledDate) ? disabledDate[1] : disabledDate;
610
-
611
- const compareDateTimeValue = (calendarType, timeType, value) => {
612
- const dateTimeValue = calendarType === 'main' ? mainDateTimeValue : expandedDateTimeValue;
613
- const disabledDateFunc = calendarType === 'main' ? mainDisabledDate : expandedDisabledDate;
614
- if (!dateTimeValue) {
615
- return false;
616
- }
617
-
618
- const date = dateTimeValue.split(' ')[0];
619
- let hour = getDateTimeInfoByType(dateTimeValue, 'hour');
620
- let min = getDateTimeInfoByType(dateTimeValue, 'min');
621
- let sec = getDateTimeInfoByType(dateTimeValue, 'sec');
622
-
623
- if (timeType === 'hour') {
624
- hour = value;
625
- } else if (timeType === 'min') {
626
- min = value;
627
- } else if (timeType === 'sec') {
628
- sec = value;
629
- }
630
-
631
- const targetDateTimeValue = `${date} ${lpadToTwoDigits(hour)}:${lpadToTwoDigits(min)}:${lpadToTwoDigits(sec)}`;
632
- if (disabledDateFunc && disabledDateFunc(new Date(targetDateTimeValue))) {
633
- return true;
634
- }
635
-
636
- return !disabledDateFunc && compareFromAndToDateTime(
637
- props.mode,
638
- calendarType,
639
- targetDateTimeValue,
640
- selectedValue.value,
641
- );
642
- };
643
-
644
- ['hour', 'min', 'sec'].forEach((v) => {
645
- let cnt = SEC_CNT;
646
- if (v === 'hour') {
647
- cnt = HOUR_CNT;
648
- } else if (v === 'min') {
649
- cnt = MIN_CNT;
650
- }
651
- const mainTimeValue = mainDateTimeValue && mainDateTimeValue.length > 0
652
- ? getTimeInfoByTimeFormat(mainTimeFormat, mainDateTimeValue, v) : -1;
653
- const expandedTimeValue = expandedDateTimeValue && expandedDateTimeValue.length > 0
654
- ? getTimeInfoByTimeFormat(expandedTimeFormat, expandedDateTimeValue, v) : -1;
655
- for (let i = 0; i < cnt; i++) {
656
- let isDisabled = props.mode === 'dateTimeRange' && compareDateTimeValue('main', v, i);
657
- mainTimeTableInfo[v][i] = {
658
- timeType: v,
659
- num: i,
660
- isSelected: !isDisabled && mainTimeValue === i,
661
- isDisabled,
662
- };
663
- if (props.mode === 'dateTimeRange') {
664
- isDisabled = compareDateTimeValue('expanded', v, i);
665
- expandedTimeTableInfo[v][i] = {
666
- timeType: v,
667
- num: i,
668
- isSelected: !isDisabled && expandedTimeValue === i,
669
- isDisabled,
670
- };
671
- }
672
- }
673
- });
674
- };
675
-
676
- /**
677
- * HMS 영역 내 tr, td, 페이지에 맞는 시간 정보 가져오기
678
- * @param timeType - {'hour'|'min'|'sec'}
679
- * @param i - rows
680
- * @param j - cols
681
- * @param calendarType - {'main'|'expanded'}
682
- * @returns {object} - cellInfo
683
- */
684
- const getTimeInfo = (timeType, i, j, calendarType) => {
685
- const pageInfo = calendarType === 'main' ? mainCalendarPageInfo : expandedCalendarPageInfo;
686
- const timeInfo = calendarType === 'main' ? mainTimeTableInfo : expandedTimeTableInfo;
687
- const currPage = pageInfo[timeType] - 1;
688
- const currRowIdx = i - 1;
689
- const currColIdx = j - 1;
690
- const currIdx = (currPage * CELL_CNT_IN_ONE_PAGE)
691
- + (currRowIdx * CELL_CNT_IN_ONE_ROW) + currColIdx;
692
- return timeInfo[timeType][currIdx];
693
- };
694
-
695
- return {
696
- mainCalendarTableInfo,
697
- expandedCalendarTableInfo,
698
- mainTimeTableInfo,
699
- expandedTimeTableInfo,
700
- setCalendarDate,
701
- setHmsTime,
702
- getTimeInfo,
703
- };
704
- };
705
-
706
- export const useEvent = (param) => {
707
- const { props, emit } = getCurrentInstance();
708
- const timeFormat = props.options?.timeFormat;
709
- const {
710
- selectedValue,
711
- mainCalendarPageInfo,
712
- expandedCalendarPageInfo,
713
- mainTimeTableInfo,
714
- expandedTimeTableInfo,
715
- setCalendarDate,
716
- setHmsTime,
717
- } = param;
718
-
719
- // dateRange mode에서 클릭하여 첫번째 선택된 날짜
720
- const dateRangeClickedDate = ref('');
721
- // dateRange mode에서 클릭한번 후 커서에 따라 날짜를 마우스오버하는 경우 dynamic argument로 이벤트명 설정
722
- const calendarEventName = ref(null);
723
- // dateTime 또는 dateTimeRange에서 timeFormat이 있는 경우 event 막음
724
- const mainTimeFormat = Array.isArray(timeFormat) ? timeFormat[0] : timeFormat;
725
- const expandedTimeFormat = Array.isArray(timeFormat) ? timeFormat[1] : '';
726
- const preventTimeEventType = {
727
- main: {
728
- hour: mainTimeFormat && mainTimeFormat.split(':')[0] !== 'HH',
729
- min: mainTimeFormat && mainTimeFormat.split(':')[1] !== 'mm',
730
- sec: mainTimeFormat && mainTimeFormat.split(':')[2] !== 'ss',
731
- },
732
- expanded: {
733
- hour: expandedTimeFormat && expandedTimeFormat.split(':')[0] !== 'HH',
734
- min: expandedTimeFormat && expandedTimeFormat.split(':')[1] !== 'mm',
735
- sec: expandedTimeFormat && expandedTimeFormat.split(':')[2] !== 'ss',
736
- },
737
- };
738
-
739
- /**
740
- * 입력받은 dateTime object에 calendar date, time 영역 페이지 세팅
741
- * @param calendarType - 달력 종류 ('main'|'expanded')
742
- * @param dateTime - 입력된 페이지 정보 ({ year, month, hour, min, sec })
743
- */
744
- const setCalendarPageInfo = (calendarType, dateTime) => {
745
- const calendarPageInfo = calendarType === 'expanded'
746
- ? expandedCalendarPageInfo : mainCalendarPageInfo;
747
- const { year, month, hour, min, sec } = dateTime;
748
- if (year) {
749
- calendarPageInfo.year = year;
750
- }
751
- if (month) {
752
- calendarPageInfo.month = month;
753
- }
754
- if (hour) {
755
- calendarPageInfo.hour = hour;
756
- }
757
- if (min) {
758
- calendarPageInfo.min = min;
759
- }
760
- if (sec) {
761
- calendarPageInfo.sec = sec;
762
- }
763
- };
764
-
765
- /**
766
- * value를 Array로 담아 페이지 세팅
767
- * @param valueList
768
- */
769
- const updateCalendarPage = (valueList) => {
770
- valueList?.forEach((currValue, index) => {
771
- const changeCalendarType = index === 0 ? 'main' : 'expanded';
772
- setCalendarPageInfo(changeCalendarType, {
773
- year: getDateTimeInfoByType(currValue, 'year'),
774
- month: getDateTimeInfoByType(currValue, 'month'),
775
- hour: Math.floor(getDateTimeInfoByType(currValue, 'hour') / CELL_CNT_IN_ONE_PAGE) + 1,
776
- min: Math.floor(getDateTimeInfoByType(currValue, 'min') / CELL_CNT_IN_ONE_PAGE) + 1,
777
- sec: Math.floor(getDateTimeInfoByType(currValue, 'sec') / CELL_CNT_IN_ONE_PAGE) + 1,
778
- });
779
- setCalendarDate(changeCalendarType);
780
- });
781
- };
782
-
783
- /**
784
- * Calendar 의 Month 이동시키기 (이전, 이후)
785
- * expandedCalendar가 존재하는 경우(mode: timeRange)
786
- * mainCalendar의 date는 expandedCalendar의 날짜를 넘길 수 없다
787
- * mainCalendar year, month < expandedCalendar year, month
788
- * @param calendarType - 달력 종류 ('main'|'expanded')
789
- * @param type - {'prev'|'next'}
790
- */
791
- const moveMonth = (calendarType, type) => {
792
- const isDateRangeMode = ['dateRange', 'dateTimeRange'].includes(props.mode);
793
- let calendarPageInfo = mainCalendarPageInfo;
794
- if (!isDateRangeMode) {
795
- if (type === 'prev') {
796
- if (calendarPageInfo.month === 1) {
797
- calendarPageInfo.year -= 1;
798
- calendarPageInfo.month = 12;
799
- } else {
800
- calendarPageInfo.month -= 1;
801
- }
802
- } else if (calendarPageInfo.month === 12) {
803
- calendarPageInfo.year += 1;
804
- calendarPageInfo.month = 1;
805
- } else {
806
- calendarPageInfo.month += 1;
807
- }
808
- } else {
809
- calendarPageInfo = calendarType === 'expanded'
810
- ? expandedCalendarPageInfo : mainCalendarPageInfo;
811
-
812
- // 두 달력간의 연속 여부 (메인 달력 + 1Month === 확장된 달력)
813
- // mainCalendar Month < expandedCalendar Month
814
- const isContinuousMonths = expandedCalendarPageInfo.year === mainCalendarPageInfo.year
815
- && expandedCalendarPageInfo.month === mainCalendarPageInfo.month;
816
- if (type === 'prev') {
817
- if (isContinuousMonths && calendarType === 'expanded') {
818
- return;
819
- }
820
- if (calendarPageInfo.month === 1) {
821
- calendarPageInfo.year -= 1;
822
- calendarPageInfo.month = 12;
823
- } else {
824
- calendarPageInfo.month -= 1;
825
- }
826
- } else {
827
- if (isContinuousMonths && calendarType === 'main') {
828
- return;
829
- }
830
- if (calendarPageInfo.month === 12) {
831
- calendarPageInfo.year += 1;
832
- calendarPageInfo.month = 1;
833
- } else {
834
- calendarPageInfo.month += 1;
835
- }
836
- }
837
- }
838
- };
839
-
840
- /**
841
- * Calendar Header의 prev, next 아이콘 클릭 이벤트
842
- * @param calendarType - 달력 종류 ('main'|'expanded')
843
- * @param type - 이전달, 다음달 ('prev'|'next')
844
- */
845
- const clickPrevNextBtn = (calendarType, type) => {
846
- moveMonth(calendarType, type);
847
- setCalendarDate(calendarType);
848
- };
849
-
850
- /**
851
- * Calendar Date 일자 클릭 이벤트
852
- * @param calendarType - {main|expanded}
853
- * @param dateInfo
854
- */
855
- const clickDate = (calendarType, dateInfo) => {
856
- const { year, month, date, monthType } = dateInfo;
857
- const CURR_DATE_STR = formatDateTime({ year, month, date });
858
- const isExistCurrDate = props.modelValue ? (Array.isArray(props.modelValue)
859
- ? props.modelValue?.map(v => v.split(' ')[0])
860
- : props.modelValue.split(' ')[0])
861
- .includes(CURR_DATE_STR) : false;
862
-
863
- let disabledDate = props.options.disabledDate;
864
- if (disabledDate && Array.isArray(disabledDate)) {
865
- disabledDate = calendarType === 'main' ? disabledDate[0] : disabledDate[1];
866
- }
867
- // 제한된 날짜는 선택할 수 없다.
868
- if (disabledDate && disabledDate(new Date(CURR_DATE_STR)) && !isExistCurrDate) {
869
- return;
870
- }
871
-
872
- const calendarPageInfo = calendarType === 'main' ? mainCalendarPageInfo : expandedCalendarPageInfo;
873
- const PREV_MONTH = ((MONTH_CNT + calendarPageInfo.month - 1) % MONTH_CNT) || MONTH_CNT;
874
- const YEAR_OF_PREV_MONTH = calendarPageInfo.month === 1
875
- ? calendarPageInfo.year - 1 : calendarPageInfo.year;
876
- const NEXT_MONTH = ((calendarPageInfo.month + 1) % MONTH_CNT) || MONTH_CNT;
877
- const YEAR_OF_NEXT_MONTH = calendarPageInfo.month === 12
878
- ? calendarPageInfo.year + 1 : calendarPageInfo.year;
879
-
880
- const moveDispCalendarMonth = () => {
881
- if (monthType.includes('prev')) {
882
- calendarPageInfo.year = YEAR_OF_PREV_MONTH;
883
- calendarPageInfo.month = PREV_MONTH;
884
- } else if (monthType.includes('next')) {
885
- calendarPageInfo.year = YEAR_OF_NEXT_MONTH;
886
- calendarPageInfo.month = NEXT_MONTH;
887
- }
888
- };
889
-
890
- const setRangeModeDateByIndex = (currIndex, currDate) => {
891
- if (!disabledDate
892
- && compareFromAndToDateTime(props.mode, calendarType, currDate, selectedValue.value)) {
893
- return;
894
- }
895
-
896
- selectedValue.value[currIndex] = currDate;
897
- moveDispCalendarMonth();
898
- updateCalendarPage(selectedValue.value);
899
- };
900
-
901
- switch (props.mode) {
902
- case 'date':
903
- selectedValue.value = CURR_DATE_STR;
904
- moveDispCalendarMonth();
905
- emit('update:modelValue', CURR_DATE_STR);
906
- setCalendarDate('main');
907
- break;
908
- case 'dateTime': {
909
- const isExistTime = !!(selectedValue.value?.split(' ')[1]);
910
- const CURR_TIME_HMS = isExistTime
911
- ? selectedValue.value?.split(' ')[1] : '00:00:00';
912
- selectedValue.value = getChangedValueByTimeFormat(
913
- timeFormat,
914
- `${CURR_DATE_STR} ${CURR_TIME_HMS}`,
915
- );
916
- moveDispCalendarMonth();
917
- emit('update:modelValue', selectedValue.value);
918
- setCalendarDate('main');
919
- if (!isExistTime) {
920
- const currTime = selectedValue.value.split(' ')[1].split(':');
921
- setCalendarPageInfo('main', {
922
- hour: Math.floor(currTime[0] / CELL_CNT_IN_ONE_PAGE) + 1,
923
- min: Math.floor(currTime[1] / CELL_CNT_IN_ONE_PAGE) + 1,
924
- sec: Math.floor(currTime[2] / CELL_CNT_IN_ONE_PAGE) + 1,
925
- });
926
- setHmsTime();
927
- }
928
- break;
929
- }
930
- case 'dateMulti': {
931
- const multiType = props.options.multiType;
932
- const multiDayLimit = props.options.multiDayLimit;
933
- if (multiType === 'date') {
934
- const selectedIdx = selectedValue.value.indexOf(CURR_DATE_STR);
935
- if (selectedIdx > -1) {
936
- selectedValue.value.splice(selectedIdx, 1);
937
- emit('update:modelValue', [...selectedValue.value]);
938
- } else if (selectedValue.value.length < multiDayLimit) {
939
- selectedValue.value.push(CURR_DATE_STR);
940
- moveDispCalendarMonth();
941
- emit('update:modelValue', [...selectedValue.value]);
942
- }
943
- } else if (multiType === 'week' || multiType === 'weekday') {
944
- const NUMBER_OF_DAYS_IN_RANGE = multiType === 'week' ? 7 : 5; // 범위 내 선택된 날짜 개수
945
- const DIFF_UNTIL_THE_LAST_DATE = multiType === 'week' ? 6 : 5; // 한 주의 마지막 날짜까지의 차이
946
- const exactSelectedDate = new Date(`${CURR_DATE_STR} 00:00:00`);
947
- const dayOfTheWeekOfTheSelectedDate = exactSelectedDate.getDay();
948
- const diffFromTheLastDay = DIFF_UNTIL_THE_LAST_DATE - dayOfTheWeekOfTheSelectedDate;
949
- const theLastDayTime = exactSelectedDate.getTime() + (ONE_DAY_MS * diffFromTheLastDay);
950
-
951
- for (let i = 0; i < NUMBER_OF_DAYS_IN_RANGE; i++) {
952
- const loopYear = new Date(theLastDayTime - (i * ONE_DAY_MS)).getFullYear();
953
- const loopMonth = new Date(theLastDayTime - (i * ONE_DAY_MS)).getMonth() + 1;
954
- const loopDate = new Date(theLastDayTime - (i * ONE_DAY_MS)).getDate();
955
- const dateStr = `${loopYear}-${lpadToTwoDigits(loopMonth)}-${lpadToTwoDigits(loopDate)}`;
956
- if (i === 0) {
957
- if (selectedValue.value.includes(dateStr)) {
958
- selectedValue.value.splice(0);
959
- break;
960
- } else {
961
- selectedValue.value.splice(0);
962
- moveDispCalendarMonth();
963
- }
964
- }
965
- if (!disabledDate || !disabledDate(new Date(dateStr))) {
966
- selectedValue.value.unshift(dateStr);
967
- }
968
- }
969
- emit('update:modelValue', [...selectedValue.value]);
970
- }
971
- setCalendarDate('main');
972
- break;
973
- }
974
- case 'dateRange': {
975
- if (!selectedValue.value.length) {
976
- selectedValue.value.push(CURR_DATE_STR);
977
- selectedValue.value.push(CURR_DATE_STR);
978
- updateCalendarPage(selectedValue.value);
979
- } else {
980
- setRangeModeDateByIndex(calendarType !== 'main' | 0, CURR_DATE_STR);
981
- }
982
- emit('update:modelValue', [...selectedValue.value]);
983
- break;
984
- }
985
- case 'dateTimeRange': {
986
- if (!selectedValue.value.length) {
987
- let fromDate = `${CURR_DATE_STR} 00:00:00`;
988
- let toDate = `${CURR_DATE_STR} 00:00:00`;
989
- if (timeFormat && timeFormat.length) {
990
- fromDate = getChangedValueByTimeFormat(
991
- timeFormat[0],
992
- fromDate,
993
- );
994
- toDate = getChangedValueByTimeFormat(
995
- timeFormat[1],
996
- toDate,
997
- );
998
- }
999
- selectedValue.value.push(fromDate);
1000
- selectedValue.value.push(toDate);
1001
-
1002
- updateCalendarPage(selectedValue.value);
1003
- setHmsTime();
1004
- } else {
1005
- const currIndex = calendarType !== 'main' | 0;
1006
- const CURR_TIME_HMS = selectedValue.value[currIndex]?.split(' ')[1] || '00:00:00';
1007
-
1008
- let currDate = `${CURR_DATE_STR} ${CURR_TIME_HMS}`;
1009
- if (timeFormat && timeFormat.length) {
1010
- currDate = getChangedValueByTimeFormat(
1011
- timeFormat[currIndex],
1012
- currDate,
1013
- );
1014
- }
1015
- setRangeModeDateByIndex(currIndex, currDate);
1016
- }
1017
- emit('update:modelValue', [...selectedValue.value]);
1018
- break;
1019
- }
1020
- default:
1021
- break;
1022
- }
1023
- };
1024
-
1025
- /**
1026
- * Calendar mode: dateTime인 경우 HMS 이동 화살표 클릭 이벤트
1027
- * @param calendarType - {main|expanded}
1028
- * @param timeType - {hour|min|sec}
1029
- * @param arrow - {up|down}
1030
- */
1031
- const clickHmsBtn = (calendarType, timeType, arrow) => {
1032
- if (preventTimeEventType[calendarType][timeType]) {
1033
- return;
1034
- }
1035
-
1036
- const calendarPageInfo = calendarType === 'expanded'
1037
- ? expandedCalendarPageInfo : mainCalendarPageInfo;
1038
- const FIRST_PAGE = 1;
1039
- const HOUR_MAX_PAGE = 2;
1040
- const MINUTE_MAX_PAGE = 5;
1041
- const SECOND_MAX_PAGE = 5;
1042
- if (timeType === 'hour') {
1043
- if (arrow === 'down' && calendarPageInfo.hour < HOUR_MAX_PAGE) {
1044
- calendarPageInfo.hour++;
1045
- } else if (arrow === 'up' && calendarPageInfo.hour > FIRST_PAGE) {
1046
- calendarPageInfo.hour--;
1047
- }
1048
- } else if (timeType === 'min') {
1049
- if (arrow === 'down' && calendarPageInfo.min < MINUTE_MAX_PAGE) {
1050
- calendarPageInfo.min++;
1051
- } else if (arrow === 'up' && calendarPageInfo.min > FIRST_PAGE) {
1052
- calendarPageInfo.min--;
1053
- }
1054
- } else if (timeType === 'sec') {
1055
- if (arrow === 'down' && calendarPageInfo.sec < SECOND_MAX_PAGE) {
1056
- calendarPageInfo.sec++;
1057
- } else if (arrow === 'up' && calendarPageInfo.sec > FIRST_PAGE) {
1058
- calendarPageInfo.sec--;
1059
- }
1060
- }
1061
- };
1062
-
1063
- /**
1064
- * Click cell In HMS area
1065
- * @param calendarType - {main|expanded}
1066
- * @param timeType - {hour|min|sec}
1067
- * @param i - row
1068
- * @param j - col
1069
- */
1070
- const clickTime = (calendarType, timeType, i, j) => {
1071
- if (preventTimeEventType[calendarType][timeType]) {
1072
- return;
1073
- }
1074
-
1075
- const calendarPageInfo = calendarType === 'expanded'
1076
- ? expandedCalendarPageInfo : mainCalendarPageInfo;
1077
- const timeInfo = calendarType === 'main'
1078
- ? mainTimeTableInfo : expandedTimeTableInfo;
1079
- const currPage = calendarPageInfo[timeType] - 1;
1080
- const currRowIdx = i - 1;
1081
- const currColIdx = j - 1;
1082
- const clickedNum = (currPage * CELL_CNT_IN_ONE_PAGE)
1083
- + (currRowIdx * CELL_CNT_IN_ONE_ROW) + currColIdx;
1084
-
1085
- if (timeInfo[timeType][clickedNum]?.isDisabled) {
1086
- return;
1087
- }
1088
-
1089
- const TODAY = new Date();
1090
- const TODAY_INFO = {
1091
- year: TODAY.getFullYear(),
1092
- month: TODAY.getMonth() + 1,
1093
- date: TODAY.getDate(),
1094
- };
1095
- let EXIST_MODEL = true;
1096
- let valueListByUpdatePage = [];
1097
-
1098
- const getTimeValueByType = () => {
1099
- let targetTimeValue;
1100
- if (timeType === 'hour') {
1101
- targetTimeValue = `${lpadToTwoDigits(clickedNum)}:00:00'`;
1102
- } else if (timeType === 'min') {
1103
- targetTimeValue = `00:${lpadToTwoDigits(clickedNum)}:00`;
1104
- } else if (timeType === 'sec') {
1105
- targetTimeValue = `00:00:${lpadToTwoDigits(clickedNum)}`;
1106
- }
1107
- return `${formatDateTime(TODAY_INFO)} ${targetTimeValue}`;
1108
- };
1109
-
1110
- const getChangedValue = (targetValue) => {
1111
- const HOUR_START_IDX = 11;
1112
- const MIN_START_IDX = 14;
1113
- const SEC_START_IDX = 17;
1114
- const REPLACE_TEXT_SIZE = 2;
1115
- let START_IDX = HOUR_START_IDX;
1116
- if (timeType === 'min') {
1117
- START_IDX = MIN_START_IDX;
1118
- } else if (timeType === 'sec') {
1119
- START_IDX = SEC_START_IDX;
1120
- }
1121
- return `${targetValue?.substr(0, START_IDX)}`
1122
- + `${lpadToTwoDigits(clickedNum)}${targetValue?.substr(START_IDX + REPLACE_TEXT_SIZE)}`;
1123
- };
1124
-
1125
- if (props.mode === 'dateTime') {
1126
- if (!props.modelValue) {
1127
- EXIST_MODEL = false;
1128
- selectedValue.value = getChangedValueByTimeFormat(
1129
- timeFormat,
1130
- getTimeValueByType(),
1131
- );
1132
- emit('update:modelValue', selectedValue.value);
1133
- } else {
1134
- selectedValue.value = getChangedValueByTimeFormat(
1135
- timeFormat,
1136
- getChangedValue(props.modelValue),
1137
- );
1138
- emit('update:modelValue', selectedValue.value);
1139
- }
1140
- valueListByUpdatePage.push(selectedValue.value);
1141
- } else {
1142
- const index = calendarType !== 'main' | 0;
1143
- if (!props.modelValue.length) {
1144
- const timeValue = getTimeValueByType();
1145
- selectedValue.value = [timeValue, timeValue];
1146
-
1147
- if (timeFormat && timeFormat.length) {
1148
- selectedValue.value = [...selectedValue.value
1149
- .map((v, idx) => getChangedValueByTimeFormat(timeFormat[idx], v))];
1150
- }
1151
-
1152
- EXIST_MODEL = false;
1153
- valueListByUpdatePage = selectedValue.value;
1154
- } else {
1155
- let currDateTime = getChangedValue(props.modelValue[index]);
1156
- if (timeFormat && timeFormat.length) {
1157
- currDateTime = getChangedValueByTimeFormat(
1158
- timeFormat[index],
1159
- currDateTime,
1160
- );
1161
- }
1162
-
1163
- selectedValue.value[index] = currDateTime;
1164
- }
1165
- emit('update:modelValue', [...selectedValue.value]);
1166
- }
1167
- setHmsTime();
1168
- // dateTime의 v-model값이 없는 경우 time area를 클릭하였을 때 date의 값은 today로 세팅
1169
- if (!EXIST_MODEL) {
1170
- updateCalendarPage(valueListByUpdatePage);
1171
- }
1172
- };
1173
-
1174
- /**
1175
- * Wheel up or wheel down In Calendar Month(tbody) area
1176
- * @param calendarType - {main|expanded}
1177
- * @param e
1178
- */
1179
- const wheelMonth = (calendarType, e) => {
1180
- moveMonth(calendarType, e.deltaY > 0 ? 'next' : 'prev');
1181
- setCalendarDate(calendarType);
1182
- };
1183
-
1184
- /**
1185
- * Wheel up or wheel down In Calendar Time(HMS) area
1186
- * @param calendarType - {main|expanded}
1187
- * @param timeType - {hour|min|sec}
1188
- * @param e
1189
- */
1190
- const wheelTime = (calendarType, timeType, e) => {
1191
- if (preventTimeEventType[calendarType][timeType]) {
1192
- return;
1193
- }
1194
-
1195
- clickHmsBtn(calendarType, timeType, e.deltaY > 0 ? 'down' : 'up');
1196
- };
1197
-
1198
- /**
1199
- * dateRange 모드에서 한번 클릭 후 날짜에 마우스무브하는 경우
1200
- * 커서 위에 있는 날짜까지의 selectedValue의 영역 선택한 selection 로직
1201
- * 일반적인 마우스무브 로직에 성능향상을 위한 throttle 10ms를 설정
1202
- * @param calendarType - 캘린더 종류 ('main'|'expanded')
1203
- * @param e - 마우스이벤트
1204
- * @type {function(): (*)}
1205
- */
1206
- const onMousemoveDate = throttle((calendarType, e) => {
1207
- const target = e.target.tagName === 'TD' ? e.target : e.target.parentElement;
1208
- const isDisabled = target.classList.contains('disabled');
1209
- const isPrev = target.classList.contains('prev');
1210
- const isNext = target.classList.contains('next');
1211
- if (target.classList.length > 0 && !isDisabled) {
1212
- const calendarPageInfo = calendarType === 'main' ? mainCalendarPageInfo : expandedCalendarPageInfo;
1213
- let yearMonth = {
1214
- year: +calendarPageInfo.year,
1215
- month: +calendarPageInfo.month,
1216
- date: e.target.innerText,
1217
- };
1218
- // 달력 내 이전달, 다음달 일자의 경우 연, 월 보정
1219
- if (isPrev) {
1220
- yearMonth = { ...yearMonth, ...getSideMonthCalendarInfo('prev', yearMonth.year, yearMonth.month) };
1221
- } else if (isNext) {
1222
- yearMonth = { ...yearMonth, ...getSideMonthCalendarInfo('next', yearMonth.year, yearMonth.month) };
1223
- }
1224
- const STANDARD_DATE_STR = dateRangeClickedDate.value;
1225
- const MOUSEMOVE_DATE_STR = formatDateTime({
1226
- year: yearMonth.year,
1227
- month: yearMonth.month,
1228
- date: yearMonth.date,
1229
- });
1230
-
1231
- // fromDate ~ toDate selection 순서 세팅
1232
- if (getDateMs(MOUSEMOVE_DATE_STR) < getDateMs(STANDARD_DATE_STR)) {
1233
- selectedValue.value[0] = MOUSEMOVE_DATE_STR;
1234
- selectedValue.value[1] = STANDARD_DATE_STR;
1235
- } else {
1236
- selectedValue.value[0] = STANDARD_DATE_STR;
1237
- selectedValue.value[1] = MOUSEMOVE_DATE_STR;
1238
- }
1239
- setCalendarDate('main');
1240
- setCalendarDate('expanded');
1241
- }
1242
- }, 10);
1243
-
1244
- watch(
1245
- () => props.modelValue,
1246
- (curr) => {
1247
- selectedValue.value = curr;
1248
-
1249
- if (props.mode.includes('Time')) {
1250
- let updateValue = [];
1251
- if (props.mode === 'dateTime') {
1252
- updateValue = [selectedValue.value];
1253
- } else if (props.mode === 'dateTimeRange') {
1254
- updateValue = selectedValue.value;
1255
- }
1256
- updateCalendarPage(updateValue);
1257
- setHmsTime();
1258
- }
1259
- });
1260
-
1261
- return {
1262
- clickPrevNextBtn,
1263
- clickDate,
1264
- clickHmsBtn,
1265
- clickTime,
1266
- wheelMonth,
1267
- wheelTime,
1268
- calendarEventName,
1269
- onMousemoveDate,
1270
- preventTimeEventType,
1271
- };
1272
- };
1
+ import {
2
+ ref, reactive, computed, getCurrentInstance, unref, onBeforeMount, watch,
3
+ } from 'vue';
4
+ import { throttle } from 'lodash-es';
5
+
6
+ const CALENDAR_ROWS = 6;
7
+ const CALENDAR_COLS = 7;
8
+ const MONTH_CNT = 12;
9
+ const HOUR_CNT = 24;
10
+ const MIN_CNT = 60;
11
+ const SEC_CNT = 60;
12
+ const CELL_CNT_IN_ONE_PAGE = 12;
13
+ const CELL_CNT_IN_ONE_ROW = 4;
14
+ const MONTH_NAME_LIST = {
15
+ fullName: ['January', 'February', 'March', 'April', 'May', 'June',
16
+ 'July', 'August', 'September', 'October', 'November', 'December'],
17
+ numberName: ['1', '2', '3', '4', '5', '6',
18
+ '7', '8', '9', '10', '11', '12'],
19
+ abbrName: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
20
+ 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'],
21
+ korName: ['1월', '2월', '3월', '4월', '5월', '6월',
22
+ '7월', '8월', '9월', '10월', '11월', '12월'],
23
+ };
24
+ const DAY_OF_THE_WEEK_NAME_LIST = {
25
+ abbrUpperName: ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'],
26
+ abbrLowerName: ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
27
+ abbrPascalName: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
28
+ abbrKorName: ['일', '월', '화', '수', '목', '금', '토'],
29
+ };
30
+
31
+ const ONE_DAY_MS = 86400000;
32
+ const dateReg = new RegExp(/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/);
33
+ const dateTimeReg = new RegExp(/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9]/);
34
+
35
+ /**
36
+ * 배열 내 여러 날짜(eg. 'YYYY-MM-DD' || 'YYYY-MM-DD HH:MI:SS') 중 가장 끝의 날짜 텍스트 구하기
37
+ * @param arr
38
+ * @param sideDirection - 끝의 방향 (first: 가장 멀리 오래된 날짜, last: 가장 최근의 날짜)
39
+ * @returns {String} - 날짜 텍스트
40
+ */
41
+ const getSideDateStr = (arr, sideDirection) => {
42
+ if (!arr.length) return '';
43
+ if (sideDirection === 'last') {
44
+ return arr
45
+ .reduce((prev, cur) => (new Date(prev).getTime() > new Date(cur).getTime() ? prev : cur));
46
+ }
47
+ return arr
48
+ .reduce((prev, cur) => (new Date(prev).getTime() < new Date(cur).getTime() ? prev : cur));
49
+ };
50
+
51
+ /**
52
+ * 월, 일을 두자리 숫자로 보정
53
+ * @param num
54
+ * @returns {string|*}
55
+ */
56
+ export const lpadToTwoDigits = (num) => {
57
+ if (num === null) {
58
+ return '00';
59
+ } else if (+num < 10) {
60
+ return `0${num}`;
61
+ }
62
+ return num;
63
+ };
64
+
65
+ /**
66
+ * 이차원 배열 만들기
67
+ * @param row
68
+ * @param col
69
+ * @returns {Array} - [row][col]
70
+ */
71
+ const getMatrixArr = (row, col) => Array.from(Array(row), () => Array(col).fill(false));
72
+
73
+ /**
74
+ * y년 m월 1일의 요일 구하기
75
+ * @param y - 년
76
+ * @param m - 월
77
+ * @returns {number} - 해당 y년 m월 1일의 요일 (e.g. 0: SUN, ..., 6: SAT)
78
+ * - 1주차에서 일요일부터 1일까지의 공백 개수
79
+ */
80
+ const getDayOfWeekOnThe1stOfMonth = (y, m) => new Date(`${y}-${m}-1`).getDay();
81
+
82
+ /**
83
+ * y년 m월 마지막 일자 구하기
84
+ * @param y
85
+ * @param m
86
+ * @returns {number} - 해당 년, 월의 마지막 일자
87
+ */
88
+ const getLastDateOfMonth = (y, m) => {
89
+ let day;
90
+ switch (m) {
91
+ case 4:
92
+ case 6:
93
+ case 9:
94
+ case 11:
95
+ day = 30;
96
+ break;
97
+ case 2:
98
+ if (((y % 4 === 0) && (y % 100 !== 0)) || (y % 400 === 0)) {
99
+ day = 29;
100
+ } else {
101
+ day = 28;
102
+ }
103
+ break;
104
+ default:
105
+ day = 31;
106
+ break;
107
+ }
108
+ return day;
109
+ };
110
+
111
+ /**
112
+ * date또는 time 형태로 format string으로 조합
113
+ * @param year
114
+ * @param month
115
+ * @param date
116
+ * @param hour
117
+ * @param min
118
+ * @param sec
119
+ * @returns {string}
120
+ */
121
+ const formatDateTime = ({ year, month, date, hour, min, sec }) => {
122
+ if (hour !== undefined && min !== undefined && sec !== undefined) {
123
+ return `${year}-${lpadToTwoDigits(month)}-${lpadToTwoDigits(date)} ${lpadToTwoDigits(hour)}:${lpadToTwoDigits(min)}:${lpadToTwoDigits(sec)}`;
124
+ }
125
+ return `${year}-${lpadToTwoDigits(month)}-${lpadToTwoDigits(date)}`;
126
+ };
127
+
128
+ /**
129
+ * 첫번째 인자로 받은 날짜 형식 String ('YYYY-MM-DD' || 'YYYY-MM-DD HH:MI:SS')이나
130
+ * 해당 날짜형식이 들어있는 Array를 받아서 최신날짜의 정보를 추출하는 함수
131
+ * typeToImport가 존재하는 경우 해당 timeType의 값을
132
+ * typeToImport가 존재하지 않는 경우 최신날짜 텍스트를 timeType별로 분할한 Object를 리턴
133
+ * @param param {String | Array} - 변경하려는 날짜
134
+ * @param typeToImport
135
+ * @returns {object|number}
136
+ */
137
+ const getDateTimeInfoByType = (param, typeToImport) => {
138
+ let str = unref(param);
139
+ if (Array.isArray(str)) {
140
+ str = getSideDateStr(param, 'last');
141
+ }
142
+ const result = {
143
+ year: +(str?.split(' ')[0]?.split('-')[0]) || null,
144
+ month: +(str?.split(' ')[0]?.split('-')[1]) || null,
145
+ date: +(str?.split(' ')[0]?.split('-')[2]) || null,
146
+ hour: +(str?.split(' ')[1]?.split(':')[0]) || 0,
147
+ min: +(str?.split(' ')[1]?.split(':')[1]) || 0,
148
+ sec: +(str?.split(' ')[1]?.split(':')[2]) || 0,
149
+ };
150
+ if (typeToImport === 'year') return result.year;
151
+ if (typeToImport === 'month') return result.month;
152
+ if (typeToImport === 'date') return result.date;
153
+ if (typeToImport === 'hour') return result.hour;
154
+ if (typeToImport === 'min') return result.min;
155
+ if (typeToImport === 'sec') return result.sec;
156
+ return result;
157
+ };
158
+ /**
159
+ * 이전달, 다음달의 달력 상 연도, 월 정보 구하기
160
+ * @param prevNext - 이전, 다음 여부 ('prev'|'next')
161
+ * @param year
162
+ * @param month
163
+ * @returns {{month: number, year: *}}
164
+ */
165
+ const getSideMonthCalendarInfo = (prevNext, year, month) => {
166
+ if (prevNext === 'next') {
167
+ return {
168
+ year: month === 12 ? year + 1 : year,
169
+ month: ((month + 1) % 12) || 12,
170
+ };
171
+ }
172
+ return {
173
+ year: month === 1 ? year - 1 : year,
174
+ month: ((month - 1) % 12) || 12,
175
+ };
176
+ };
177
+
178
+ /**
179
+ * timeFormat을 체크하여 timeFormat이 있으면 format에 맞는 형식으로 반환
180
+ * @param timeFormat -- props.option?.timeFormat
181
+ * @param dateTimeValue
182
+ * @param typeToImport
183
+ * @returns {Object|number}
184
+ */
185
+ const getTimeInfoByTimeFormat = (timeFormat, dateTimeValue, typeToImport) => {
186
+ const value = getDateTimeInfoByType(dateTimeValue, typeToImport);
187
+ if (timeFormat) {
188
+ const hour = timeFormat?.split(':')[0];
189
+ const min = timeFormat?.split(':')[1];
190
+ const sec = timeFormat?.split(':')[2];
191
+ if (typeToImport === 'hour') {
192
+ return hour === 'HH' ? value : +hour;
193
+ } else if (typeToImport === 'min') {
194
+ return min === 'mm' ? value : +min;
195
+ } else if (typeToImport === 'sec') {
196
+ return sec === 'ss' ? value : +sec;
197
+ }
198
+ }
199
+ return value;
200
+ };
201
+
202
+ /**
203
+ * 초기 timeFormat에 따른 modelValue update 함수
204
+ * @param timeFormat - props.options.timeFormat
205
+ * @param modelValue
206
+ * @returns string
207
+ */
208
+ export const getChangedValueByTimeFormat = (timeFormat, modelValue) => {
209
+ if (!modelValue) {
210
+ return '';
211
+ }
212
+
213
+ const hourByTimeFormat = lpadToTwoDigits(getTimeInfoByTimeFormat(timeFormat, modelValue, 'hour'));
214
+ const minByTimeFormat = lpadToTwoDigits(getTimeInfoByTimeFormat(timeFormat, modelValue, 'min'));
215
+ const secByTimeFormat = lpadToTwoDigits(getTimeInfoByTimeFormat(timeFormat, modelValue, 'sec'));
216
+
217
+ return `${modelValue.split(' ')[0]} ${hourByTimeFormat}:${minByTimeFormat}:${secByTimeFormat}`;
218
+ };
219
+
220
+ const compareFromAndToDateTime = (mode, calendarType, targetDate, modelValue) => {
221
+ if (!modelValue.length) {
222
+ return false;
223
+ }
224
+ let fromDate = calendarType === 'main' ? targetDate : modelValue[0];
225
+ let toDate = calendarType === 'expanded' ? targetDate : modelValue[1];
226
+
227
+ let fromDateTime = fromDate;
228
+ let toDateTime = toDate;
229
+ if (!targetDate.split(' ')[1]) {
230
+ if (mode === 'dateTimeRange') {
231
+ fromDate = fromDate.split(' ')[0];
232
+ toDate = toDate.split(' ')[0];
233
+ const fromTime = modelValue[0].split(' ')[1];
234
+ const toTime = modelValue[1].split(' ')[1];
235
+ fromDateTime = `${fromDate} ${fromTime}`;
236
+ toDateTime = `${toDate} ${toTime}`;
237
+ } else {
238
+ fromDateTime = `${fromDate} 00:00:00`;
239
+ toDateTime = `${toDate} 23:59:59`;
240
+ }
241
+ }
242
+
243
+ return (fromDateTime && toDateTime)
244
+ && new Date(fromDateTime).getTime() > +new Date(toDateTime).getTime();
245
+ };
246
+
247
+ /**
248
+ * date string 값의 MS 값 구하기
249
+ * @param dateStr
250
+ * @returns {number}
251
+ */
252
+ const getDateMs = dateStr => new Date(`${dateStr}`).getTime();
253
+
254
+ export const useModel = () => {
255
+ const { props } = getCurrentInstance();
256
+ const timeFormat = props.options?.timeFormat;
257
+
258
+ /**
259
+ * 현재 선택된 값, 배열인 경우 반응형을 끊기위해 rest 사용
260
+ * selectValue ref로 변환하기 전 modelValue timeFormat에 따라 fetch
261
+ * 1) props.mode: 'date' or 'dateTime' > String
262
+ * 2) props.mode: 'dateMulti' or 'dateRange' > [...Array]
263
+ */
264
+ let selectedValue;
265
+ if (props.mode !== 'dateMulti' && props.mode !== 'dateRange' && props.mode !== 'dateTimeRange') {
266
+ if (props.modelValue
267
+ && ((props.modelValue.length === 10 && dateReg.exec(props.modelValue?.toString()))
268
+ || (props.modelValue.length === 19 && dateTimeReg.exec(props.modelValue?.toString())))
269
+ ) {
270
+ if (props.mode === 'dateTime' && timeFormat) {
271
+ const modelValue = getChangedValueByTimeFormat(timeFormat, props.modelValue);
272
+ selectedValue = ref(modelValue);
273
+ } else {
274
+ selectedValue = ref(props.modelValue);
275
+ }
276
+ } else {
277
+ selectedValue = ref('');
278
+ }
279
+ } else if (Array.isArray(props.modelValue)
280
+ && props.modelValue.every(v => (
281
+ !v
282
+ || (v.length === 10 && dateReg.exec(v))
283
+ || (v.length === 19 && dateTimeReg.exec(v))
284
+ ))
285
+ ) {
286
+ if (props.mode === 'dateTimeRange' && props.modelValue.length === 2 && timeFormat) {
287
+ const modelValue = [];
288
+ modelValue.push(getChangedValueByTimeFormat(timeFormat[0], props.modelValue[0]));
289
+ modelValue.push(getChangedValueByTimeFormat(timeFormat[1], props.modelValue[1]));
290
+ selectedValue = ref([...modelValue]);
291
+ } else {
292
+ selectedValue = ref([...props.modelValue]);
293
+ }
294
+ } else {
295
+ selectedValue = ref([]);
296
+ }
297
+
298
+ /**
299
+ * validate v-model's value
300
+ */
301
+ const validateModelValue = () => {
302
+ if (props.mode === 'date' && props.modelValue && typeof props.modelValue !== 'string') {
303
+ console.warn('[EVUI][Calendar] When mode is \'date\', v-model must be \'String\' type.');
304
+ } else if (props.mode === 'dateTime' && props.modelValue && typeof props.modelValue !== 'string') {
305
+ console.warn('[EVUI][Calendar] When mode is \'dateTime\', v-model must be \'String\' type.');
306
+ } else if (props.mode === 'dateMulti' && props.modelValue && !Array.isArray(props.modelValue)) {
307
+ console.warn('[EVUI][Calendar] When mode is \'dateMulti\', v-model must be \'Array\' type.');
308
+ } else if (props.mode === 'dateRange' && props.modelValue) {
309
+ if (!Array.isArray(props.modelValue)) {
310
+ console.warn('[EVUI][Calendar] When mode is \'dateRange\', v-model must be \'Array\' type.');
311
+ } else if (getDateMs(`${props.modelValue[0]} 00:00:00`) > getDateMs(`${props.modelValue[1]} 00:00:00`)) {
312
+ console.warn('[EVUI][Calendar] When mode is \'dateRange\', fromDate must be less than toDate.');
313
+ }
314
+ } else if (props.mode === 'dateTimeRange' && props.modelValue) {
315
+ if (!Array.isArray(props.modelValue)) {
316
+ console.warn('[EVUI][Calendar] When mode is \'dateTimeRange\', v-model must be \'Array\' type.');
317
+ } else if (getDateMs(props.modelValue[0]) > getDateMs(props.modelValue[1])) {
318
+ console.warn('[EVUI][Calendar] When mode is \'dateRange\', fromDate must be less than toDate.');
319
+ }
320
+ }
321
+ };
322
+
323
+ // 메인(좌측) 달력(연, 월, 시, 분, 초) 페이징 정보
324
+ let mainCalendarPageInfo;
325
+ const mainValue = !['dateRange', 'dateTimeRange'].includes(props.mode) ? selectedValue.value : selectedValue.value[0];
326
+ if (mainValue?.length) {
327
+ mainCalendarPageInfo = reactive({
328
+ year: getDateTimeInfoByType(mainValue, 'year'),
329
+ month: getDateTimeInfoByType(mainValue, 'month'),
330
+ hour: Math.floor(getDateTimeInfoByType(mainValue, 'hour') / CELL_CNT_IN_ONE_PAGE) + 1 || 1,
331
+ min: Math.floor(getDateTimeInfoByType(mainValue, 'min') / CELL_CNT_IN_ONE_PAGE) + 1 || 1,
332
+ sec: Math.floor(getDateTimeInfoByType(mainValue, 'sec') / CELL_CNT_IN_ONE_PAGE) + 1 || 1,
333
+ });
334
+ } else {
335
+ mainCalendarPageInfo = reactive({
336
+ year: new Date().getFullYear(),
337
+ month: new Date().getMonth() + 1,
338
+ hour: 1,
339
+ min: 1,
340
+ sec: 1,
341
+ });
342
+ }
343
+
344
+ // 'mode: dateRange || dateTimeRange', 인 경우 확장된 달력(연, 월) 페이징 정보
345
+ let expandedCalendarPageInfo;
346
+ if ((['dateRange', 'dateTimeRange'].includes(props.mode))
347
+ && Array.isArray(selectedValue.value)
348
+ && selectedValue.value[1]
349
+ ) {
350
+ const expandedValue = selectedValue.value[1];
351
+ const toDate = {
352
+ year: getDateTimeInfoByType(expandedValue, 'year'),
353
+ month: getDateTimeInfoByType(expandedValue, 'month'),
354
+ };
355
+ expandedCalendarPageInfo = reactive(toDate);
356
+
357
+ if (props.mode === 'dateTimeRange') {
358
+ expandedCalendarPageInfo.hour = Math.floor(getDateTimeInfoByType(expandedValue, 'hour') / CELL_CNT_IN_ONE_PAGE) + 1 || 1;
359
+ expandedCalendarPageInfo.min = Math.floor(getDateTimeInfoByType(expandedValue, 'min') / CELL_CNT_IN_ONE_PAGE) + 1 || 1;
360
+ expandedCalendarPageInfo.sec = Math.floor(getDateTimeInfoByType(expandedValue, 'sec') / CELL_CNT_IN_ONE_PAGE) + 1 || 1;
361
+ }
362
+ } else {
363
+ expandedCalendarPageInfo = reactive({
364
+ year: new Date().getFullYear(),
365
+ month: new Date().getMonth() + 1,
366
+ hour: 1,
367
+ min: 1,
368
+ sec: 1,
369
+ });
370
+ }
371
+
372
+ // 현재 달력이 표현되는 월
373
+ const mainCalendarMonth = computed(() =>
374
+ MONTH_NAME_LIST[props.monthNotation][mainCalendarPageInfo.month - 1]);
375
+ // 다음페이지 달력이 표현되는 월
376
+ const expandedCalendarMonth = computed(() =>
377
+ MONTH_NAME_LIST[props.monthNotation][expandedCalendarPageInfo.month - 1]);
378
+ // 현재 달력에 표현되는 타입별 요일
379
+ const dayOfTheWeekList = computed(() =>
380
+ DAY_OF_THE_WEEK_NAME_LIST[props.dayOfTheWeekNotation]);
381
+ // mode: dateRange에 두 달력이 연속적인 경우
382
+ const isContinuousMonths = computed(
383
+ () => ['dateRange', 'dateTimeRange'].includes(props.mode)
384
+ && (mainCalendarPageInfo.year === expandedCalendarPageInfo.year
385
+ && mainCalendarPageInfo.month === expandedCalendarPageInfo.month),
386
+ );
387
+
388
+ onBeforeMount(() => {
389
+ validateModelValue();
390
+ });
391
+
392
+ return {
393
+ selectedValue,
394
+ mainCalendarPageInfo,
395
+ expandedCalendarPageInfo,
396
+ mainCalendarMonth,
397
+ expandedCalendarMonth,
398
+ dayOfTheWeekList,
399
+ isContinuousMonths,
400
+ };
401
+ };
402
+
403
+ export const useCalendarDate = (param) => {
404
+ const { props, emit } = getCurrentInstance();
405
+ const { selectedValue, mainCalendarPageInfo, expandedCalendarPageInfo } = param;
406
+
407
+ // 메인 달력 테이블의 날짜 정보 (6X7, 2차원배열)
408
+ const mainCalendarTableInfo = reactive(getMatrixArr(CALENDAR_ROWS, CALENDAR_COLS));
409
+ // dateRange 모드의 확장된 달력 테이블의 날짜 정보
410
+ const expandedCalendarTableInfo = reactive(getMatrixArr(CALENDAR_ROWS, CALENDAR_COLS));
411
+ // 시간박스 정보
412
+ const mainTimeTableInfo = reactive({
413
+ hour: [],
414
+ min: [],
415
+ sec: [],
416
+ });
417
+ // dateTimeRange 모드의 확장된 달력 테이블의 시간 박스 정보
418
+ const expandedTimeTableInfo = reactive({
419
+ hour: [],
420
+ min: [],
421
+ sec: [],
422
+ });
423
+
424
+ /**
425
+ * calendar setting 하기 전 선택된 날짜가 disabledDate에 포함되는지 체크
426
+ * @param isRangeMode
427
+ * @param calendarType
428
+ * @param disabledDate
429
+ */
430
+ const checkDisabledDate = ({ isRangeMode, calendarType, disabledDate }) => {
431
+ if (isRangeMode) {
432
+ if (calendarType === 'main' && selectedValue.value[0]) {
433
+ if (disabledDate && disabledDate(new Date(selectedValue.value[0]))) {
434
+ selectedValue.value[0] = '';
435
+ emit('update:modelValue', [...selectedValue.value]);
436
+ }
437
+ } else if (calendarType === 'expanded' && selectedValue.value[1]) {
438
+ if (disabledDate && disabledDate(new Date(selectedValue.value[1]))) {
439
+ selectedValue.value[1] = '';
440
+ emit('update:modelValue', [...selectedValue.value]);
441
+ }
442
+ }
443
+ } else if (props.mode === 'dateMulti') {
444
+ let isUpdate = false;
445
+ selectedValue.value.forEach((value, index) => {
446
+ if (disabledDate && disabledDate(new Date(value))) {
447
+ selectedValue.value.splice(index, 1);
448
+ isUpdate = true;
449
+ }
450
+ });
451
+ if (isUpdate) {
452
+ emit('update:modelValue', [...selectedValue.value]);
453
+ }
454
+ } else if (disabledDate && disabledDate(new Date(selectedValue.value))) {
455
+ selectedValue.value = '';
456
+ emit('update:modelValue', selectedValue.value);
457
+ }
458
+ };
459
+
460
+ /**
461
+ * Dropdown Calendar 날짜 정보 세팅하기
462
+ * @param calendarType - 달력 종류 ('main'|'expanded')
463
+ */
464
+ const setCalendarDate = (calendarType) => {
465
+ const calendarPageInfo = calendarType === 'expanded'
466
+ ? expandedCalendarPageInfo : mainCalendarPageInfo;
467
+ const calendarTableInfo = calendarType === 'expanded'
468
+ ? expandedCalendarTableInfo : mainCalendarTableInfo;
469
+
470
+ let disabledDate = props.options.disabledDate;
471
+ if (disabledDate && Array.isArray(disabledDate)) {
472
+ disabledDate = calendarType === 'main' ? disabledDate[0] : disabledDate[1];
473
+ }
474
+ const isRangeMode = ['dateRange', 'dateTimeRange'].includes(props.mode);
475
+
476
+ checkDisabledDate({
477
+ isRangeMode,
478
+ calendarType,
479
+ disabledDate,
480
+ });
481
+
482
+ const TODAY_YMD = formatDateTime({
483
+ year: new Date().getFullYear(),
484
+ month: new Date().getMonth() + 1,
485
+ date: new Date().getDate(),
486
+ });
487
+ const PREV_MONTH = computed(() =>
488
+ ((MONTH_CNT + calendarPageInfo.month - 1) % MONTH_CNT) || MONTH_CNT);
489
+ const NEXT_MONTH = computed(() =>
490
+ ((calendarPageInfo.month + 1) % MONTH_CNT) || MONTH_CNT);
491
+ const YEAR_OF_PREV_MONTH = computed(() => (calendarPageInfo.month === 1
492
+ ? calendarPageInfo.year - 1 : calendarPageInfo.year));
493
+ const YEAR_OF_NEXT_MONTH = computed(() => (calendarPageInfo.month === 12
494
+ ? calendarPageInfo.year + 1 : calendarPageInfo.year));
495
+ // 이번달 1일의 요일
496
+ const dayOfWeekOnThe1stOfThisMonth = computed(() => getDayOfWeekOnThe1stOfMonth(
497
+ calendarPageInfo.year,
498
+ calendarPageInfo.month,
499
+ ));
500
+ // 저번달 마지막 날짜
501
+ const lastDateOfPrevMonth = computed(() => getLastDateOfMonth(
502
+ calendarPageInfo.month === 1
503
+ ? calendarPageInfo.year - 1 : calendarPageInfo.year,
504
+ (MONTH_CNT + calendarPageInfo.month - 1) % MONTH_CNT || MONTH_CNT,
505
+ ));
506
+ // 이번달 마지막 날짜
507
+ const lastDateOfThisMonth = computed(() => getLastDateOfMonth(
508
+ calendarPageInfo.year,
509
+ calendarPageInfo.month,
510
+ ));
511
+
512
+ let modelValue = '';
513
+ if (props.mode.includes('Time')) {
514
+ if (props.mode === 'dateTime') {
515
+ modelValue = selectedValue.value;
516
+ } else {
517
+ modelValue = calendarType === 'main' ? selectedValue.value[0] : selectedValue.value[1];
518
+ }
519
+ }
520
+
521
+ let monthDate = 0;
522
+ let year = 0;
523
+ let month = 0;
524
+ let date = 0;
525
+ let currDate = '';
526
+ // date 숫자 및 속성 세팅
527
+ const setDateInfo = (monthType, i, j) => {
528
+ currDate = formatDateTime({ year, month, date });
529
+ const isInvalidDate = isRangeMode
530
+ && compareFromAndToDateTime(props.mode, calendarType, currDate, selectedValue.value);
531
+
532
+ // time 모드인 경우 현재 값의 시간을 가지고 테스트
533
+ const timeValue = modelValue?.split(' ')[1] ?? '';
534
+
535
+ const isDisabled = disabledDate
536
+ ? disabledDate(new Date(`${currDate} ${timeValue}`)) : isInvalidDate;
537
+
538
+ const index = +(calendarType !== 'main');
539
+ const isRangeSelected = isRangeMode && selectedValue.value.length > index
540
+ && selectedValue.value[index].split(' ')[0].includes(currDate);
541
+ const isSelected = !isDisabled && (isRangeMode
542
+ ? monthType === '' && isRangeSelected
543
+ : selectedValue.value?.includes(currDate));
544
+
545
+ // mode가 dateRange일 때는 이전, 다음달에 selected 를 하지 않는다.
546
+ calendarTableInfo[i][j] = {
547
+ monthType: `${monthType}${isDisabled ? ' disabled' : ''}`,
548
+ isToday: TODAY_YMD === currDate,
549
+ isSelected,
550
+ year,
551
+ month,
552
+ date,
553
+ };
554
+ };
555
+
556
+ for (let i = 0; i < CALENDAR_ROWS; i++) {
557
+ for (let j = 0; j < CALENDAR_COLS; j++) {
558
+ if (i === 0) {
559
+ // 첫번째 주
560
+ if (dayOfWeekOnThe1stOfThisMonth.value !== 0) {
561
+ if (j < dayOfWeekOnThe1stOfThisMonth.value) {
562
+ year = YEAR_OF_PREV_MONTH.value;
563
+ month = PREV_MONTH.value;
564
+ date = lastDateOfPrevMonth.value - dayOfWeekOnThe1stOfThisMonth.value + 1 + j;
565
+ setDateInfo('prev', i, j);
566
+ } else {
567
+ monthDate++;
568
+ year = calendarPageInfo.year;
569
+ month = calendarPageInfo.month;
570
+ date = monthDate;
571
+ setDateInfo('', i, j);
572
+ }
573
+ } else {
574
+ year = YEAR_OF_PREV_MONTH.value;
575
+ month = PREV_MONTH.value;
576
+ date = lastDateOfPrevMonth.value - 6 + j;
577
+ setDateInfo('prev', i, j);
578
+ }
579
+ } else if (lastDateOfThisMonth.value <= monthDate) {
580
+ // 마지막 -1, 마지막 주의 다음달 날짜
581
+ monthDate++;
582
+ year = YEAR_OF_NEXT_MONTH.value;
583
+ month = NEXT_MONTH.value;
584
+ date = monthDate - lastDateOfThisMonth.value;
585
+ setDateInfo('next', i, j);
586
+ } else {
587
+ // 첫번째 주를 제외한 이번달 날짜
588
+ monthDate++;
589
+ year = calendarPageInfo.year;
590
+ month = calendarPageInfo.month;
591
+ date = monthDate;
592
+ setDateInfo('', i, j);
593
+ }
594
+ }
595
+ }
596
+ };
597
+
598
+ /**
599
+ * Calendar 시간 정보 세팅하기
600
+ */
601
+ const setHmsTime = () => {
602
+ const timeFormat = props.options?.timeFormat;
603
+ const disabledDate = props.options?.disabledDate;
604
+ const mainTimeFormat = Array.isArray(timeFormat) ? timeFormat[0] : timeFormat;
605
+ const expandedTimeFormat = Array.isArray(timeFormat) ? timeFormat[1] : '';
606
+ const mainDateTimeValue = props.mode === 'dateTimeRange' ? selectedValue.value[0] : selectedValue.value;
607
+ const expandedDateTimeValue = props.mode === 'dateTimeRange' ? selectedValue.value[1] : '';
608
+ const mainDisabledDate = Array.isArray(disabledDate) ? disabledDate[0] : disabledDate;
609
+ const expandedDisabledDate = Array.isArray(disabledDate) ? disabledDate[1] : disabledDate;
610
+
611
+ const compareDateTimeValue = (calendarType, timeType, value) => {
612
+ const dateTimeValue = calendarType === 'main' ? mainDateTimeValue : expandedDateTimeValue;
613
+ const disabledDateFunc = calendarType === 'main' ? mainDisabledDate : expandedDisabledDate;
614
+ if (!dateTimeValue) {
615
+ return false;
616
+ }
617
+
618
+ const date = dateTimeValue.split(' ')[0];
619
+ let hour = getDateTimeInfoByType(dateTimeValue, 'hour');
620
+ let min = getDateTimeInfoByType(dateTimeValue, 'min');
621
+ let sec = getDateTimeInfoByType(dateTimeValue, 'sec');
622
+
623
+ if (timeType === 'hour') {
624
+ hour = value;
625
+ } else if (timeType === 'min') {
626
+ min = value;
627
+ } else if (timeType === 'sec') {
628
+ sec = value;
629
+ }
630
+
631
+ const targetDateTimeValue = `${date} ${lpadToTwoDigits(hour)}:${lpadToTwoDigits(min)}:${lpadToTwoDigits(sec)}`;
632
+ if (disabledDateFunc && disabledDateFunc(new Date(targetDateTimeValue))) {
633
+ return true;
634
+ }
635
+
636
+ return !disabledDateFunc && compareFromAndToDateTime(
637
+ props.mode,
638
+ calendarType,
639
+ targetDateTimeValue,
640
+ selectedValue.value,
641
+ );
642
+ };
643
+
644
+ ['hour', 'min', 'sec'].forEach((v) => {
645
+ let cnt = SEC_CNT;
646
+ if (v === 'hour') {
647
+ cnt = HOUR_CNT;
648
+ } else if (v === 'min') {
649
+ cnt = MIN_CNT;
650
+ }
651
+ const mainTimeValue = mainDateTimeValue && mainDateTimeValue.length > 0
652
+ ? getTimeInfoByTimeFormat(mainTimeFormat, mainDateTimeValue, v) : -1;
653
+ const expandedTimeValue = expandedDateTimeValue && expandedDateTimeValue.length > 0
654
+ ? getTimeInfoByTimeFormat(expandedTimeFormat, expandedDateTimeValue, v) : -1;
655
+ for (let i = 0; i < cnt; i++) {
656
+ let isDisabled = props.mode === 'dateTimeRange' && compareDateTimeValue('main', v, i);
657
+ mainTimeTableInfo[v][i] = {
658
+ timeType: v,
659
+ num: i,
660
+ isSelected: !isDisabled && mainTimeValue === i,
661
+ isDisabled,
662
+ };
663
+ if (props.mode === 'dateTimeRange') {
664
+ isDisabled = compareDateTimeValue('expanded', v, i);
665
+ expandedTimeTableInfo[v][i] = {
666
+ timeType: v,
667
+ num: i,
668
+ isSelected: !isDisabled && expandedTimeValue === i,
669
+ isDisabled,
670
+ };
671
+ }
672
+ }
673
+ });
674
+ };
675
+
676
+ /**
677
+ * HMS 영역 내 tr, td, 페이지에 맞는 시간 정보 가져오기
678
+ * @param timeType - {'hour'|'min'|'sec'}
679
+ * @param i - rows
680
+ * @param j - cols
681
+ * @param calendarType - {'main'|'expanded'}
682
+ * @returns {object} - cellInfo
683
+ */
684
+ const getTimeInfo = (timeType, i, j, calendarType) => {
685
+ const pageInfo = calendarType === 'main' ? mainCalendarPageInfo : expandedCalendarPageInfo;
686
+ const timeInfo = calendarType === 'main' ? mainTimeTableInfo : expandedTimeTableInfo;
687
+ const currPage = pageInfo[timeType] - 1;
688
+ const currRowIdx = i - 1;
689
+ const currColIdx = j - 1;
690
+ const currIdx = (currPage * CELL_CNT_IN_ONE_PAGE)
691
+ + (currRowIdx * CELL_CNT_IN_ONE_ROW) + currColIdx;
692
+ return timeInfo[timeType][currIdx];
693
+ };
694
+
695
+ return {
696
+ mainCalendarTableInfo,
697
+ expandedCalendarTableInfo,
698
+ mainTimeTableInfo,
699
+ expandedTimeTableInfo,
700
+ setCalendarDate,
701
+ setHmsTime,
702
+ getTimeInfo,
703
+ };
704
+ };
705
+
706
+ export const useEvent = (param) => {
707
+ const { props, emit } = getCurrentInstance();
708
+ const timeFormat = props.options?.timeFormat;
709
+ const {
710
+ selectedValue,
711
+ mainCalendarPageInfo,
712
+ expandedCalendarPageInfo,
713
+ mainTimeTableInfo,
714
+ expandedTimeTableInfo,
715
+ setCalendarDate,
716
+ setHmsTime,
717
+ } = param;
718
+
719
+ // dateRange mode에서 클릭하여 첫번째 선택된 날짜
720
+ const dateRangeClickedDate = ref('');
721
+ // dateRange mode에서 클릭한번 후 커서에 따라 날짜를 마우스오버하는 경우 dynamic argument로 이벤트명 설정
722
+ const calendarEventName = ref(null);
723
+ // dateTime 또는 dateTimeRange에서 timeFormat이 있는 경우 event 막음
724
+ const mainTimeFormat = Array.isArray(timeFormat) ? timeFormat[0] : timeFormat;
725
+ const expandedTimeFormat = Array.isArray(timeFormat) ? timeFormat[1] : '';
726
+ const preventTimeEventType = {
727
+ main: {
728
+ hour: mainTimeFormat && mainTimeFormat.split(':')[0] !== 'HH',
729
+ min: mainTimeFormat && mainTimeFormat.split(':')[1] !== 'mm',
730
+ sec: mainTimeFormat && mainTimeFormat.split(':')[2] !== 'ss',
731
+ },
732
+ expanded: {
733
+ hour: expandedTimeFormat && expandedTimeFormat.split(':')[0] !== 'HH',
734
+ min: expandedTimeFormat && expandedTimeFormat.split(':')[1] !== 'mm',
735
+ sec: expandedTimeFormat && expandedTimeFormat.split(':')[2] !== 'ss',
736
+ },
737
+ };
738
+
739
+ /**
740
+ * 입력받은 dateTime object에 calendar date, time 영역 페이지 세팅
741
+ * @param calendarType - 달력 종류 ('main'|'expanded')
742
+ * @param dateTime - 입력된 페이지 정보 ({ year, month, hour, min, sec })
743
+ */
744
+ const setCalendarPageInfo = (calendarType, dateTime) => {
745
+ const calendarPageInfo = calendarType === 'expanded'
746
+ ? expandedCalendarPageInfo : mainCalendarPageInfo;
747
+ const { year, month, hour, min, sec } = dateTime;
748
+ if (year) {
749
+ calendarPageInfo.year = year;
750
+ }
751
+ if (month) {
752
+ calendarPageInfo.month = month;
753
+ }
754
+ if (hour) {
755
+ calendarPageInfo.hour = hour;
756
+ }
757
+ if (min) {
758
+ calendarPageInfo.min = min;
759
+ }
760
+ if (sec) {
761
+ calendarPageInfo.sec = sec;
762
+ }
763
+ };
764
+
765
+ /**
766
+ * value를 Array로 담아 페이지 세팅
767
+ * @param valueList
768
+ */
769
+ const updateCalendarPage = (valueList) => {
770
+ valueList?.forEach((currValue, index) => {
771
+ const changeCalendarType = index === 0 ? 'main' : 'expanded';
772
+ setCalendarPageInfo(changeCalendarType, {
773
+ year: getDateTimeInfoByType(currValue, 'year'),
774
+ month: getDateTimeInfoByType(currValue, 'month'),
775
+ hour: Math.floor(getDateTimeInfoByType(currValue, 'hour') / CELL_CNT_IN_ONE_PAGE) + 1,
776
+ min: Math.floor(getDateTimeInfoByType(currValue, 'min') / CELL_CNT_IN_ONE_PAGE) + 1,
777
+ sec: Math.floor(getDateTimeInfoByType(currValue, 'sec') / CELL_CNT_IN_ONE_PAGE) + 1,
778
+ });
779
+ setCalendarDate(changeCalendarType);
780
+ });
781
+ };
782
+
783
+ /**
784
+ * Calendar 의 Month 이동시키기 (이전, 이후)
785
+ * expandedCalendar가 존재하는 경우(mode: timeRange)
786
+ * mainCalendar의 date는 expandedCalendar의 날짜를 넘길 수 없다
787
+ * mainCalendar year, month < expandedCalendar year, month
788
+ * @param calendarType - 달력 종류 ('main'|'expanded')
789
+ * @param type - {'prev'|'next'}
790
+ */
791
+ const moveMonth = (calendarType, type) => {
792
+ const isDateRangeMode = ['dateRange', 'dateTimeRange'].includes(props.mode);
793
+ let calendarPageInfo = mainCalendarPageInfo;
794
+ if (!isDateRangeMode) {
795
+ if (type === 'prev') {
796
+ if (calendarPageInfo.month === 1) {
797
+ calendarPageInfo.year -= 1;
798
+ calendarPageInfo.month = 12;
799
+ } else {
800
+ calendarPageInfo.month -= 1;
801
+ }
802
+ } else if (calendarPageInfo.month === 12) {
803
+ calendarPageInfo.year += 1;
804
+ calendarPageInfo.month = 1;
805
+ } else {
806
+ calendarPageInfo.month += 1;
807
+ }
808
+ } else {
809
+ calendarPageInfo = calendarType === 'expanded'
810
+ ? expandedCalendarPageInfo : mainCalendarPageInfo;
811
+
812
+ // 두 달력간의 연속 여부 (메인 달력 + 1Month === 확장된 달력)
813
+ // mainCalendar Month < expandedCalendar Month
814
+ const isContinuousMonths = expandedCalendarPageInfo.year === mainCalendarPageInfo.year
815
+ && expandedCalendarPageInfo.month === mainCalendarPageInfo.month;
816
+ if (type === 'prev') {
817
+ if (isContinuousMonths && calendarType === 'expanded') {
818
+ return;
819
+ }
820
+ if (calendarPageInfo.month === 1) {
821
+ calendarPageInfo.year -= 1;
822
+ calendarPageInfo.month = 12;
823
+ } else {
824
+ calendarPageInfo.month -= 1;
825
+ }
826
+ } else {
827
+ if (isContinuousMonths && calendarType === 'main') {
828
+ return;
829
+ }
830
+ if (calendarPageInfo.month === 12) {
831
+ calendarPageInfo.year += 1;
832
+ calendarPageInfo.month = 1;
833
+ } else {
834
+ calendarPageInfo.month += 1;
835
+ }
836
+ }
837
+ }
838
+ };
839
+
840
+ /**
841
+ * Calendar Header의 prev, next 아이콘 클릭 이벤트
842
+ * @param calendarType - 달력 종류 ('main'|'expanded')
843
+ * @param type - 이전달, 다음달 ('prev'|'next')
844
+ */
845
+ const clickPrevNextBtn = (calendarType, type) => {
846
+ moveMonth(calendarType, type);
847
+ setCalendarDate(calendarType);
848
+ };
849
+
850
+ /**
851
+ * Calendar Date 일자 클릭 이벤트
852
+ * @param calendarType - {main|expanded}
853
+ * @param dateInfo
854
+ */
855
+ const clickDate = (calendarType, dateInfo) => {
856
+ const { year, month, date, monthType } = dateInfo;
857
+ const CURR_DATE_STR = formatDateTime({ year, month, date });
858
+ const isExistCurrDate = props.modelValue ? (Array.isArray(props.modelValue)
859
+ ? props.modelValue?.map(v => v.split(' ')[0])
860
+ : props.modelValue.split(' ')[0])
861
+ .includes(CURR_DATE_STR) : false;
862
+
863
+ let disabledDate = props.options.disabledDate;
864
+ if (disabledDate && Array.isArray(disabledDate)) {
865
+ disabledDate = calendarType === 'main' ? disabledDate[0] : disabledDate[1];
866
+ }
867
+ // 제한된 날짜는 선택할 수 없다.
868
+ if (disabledDate && disabledDate(new Date(CURR_DATE_STR)) && !isExistCurrDate) {
869
+ return;
870
+ }
871
+
872
+ const calendarPageInfo = calendarType === 'main' ? mainCalendarPageInfo : expandedCalendarPageInfo;
873
+ const PREV_MONTH = ((MONTH_CNT + calendarPageInfo.month - 1) % MONTH_CNT) || MONTH_CNT;
874
+ const YEAR_OF_PREV_MONTH = calendarPageInfo.month === 1
875
+ ? calendarPageInfo.year - 1 : calendarPageInfo.year;
876
+ const NEXT_MONTH = ((calendarPageInfo.month + 1) % MONTH_CNT) || MONTH_CNT;
877
+ const YEAR_OF_NEXT_MONTH = calendarPageInfo.month === 12
878
+ ? calendarPageInfo.year + 1 : calendarPageInfo.year;
879
+
880
+ const moveDispCalendarMonth = () => {
881
+ if (monthType.includes('prev')) {
882
+ calendarPageInfo.year = YEAR_OF_PREV_MONTH;
883
+ calendarPageInfo.month = PREV_MONTH;
884
+ } else if (monthType.includes('next')) {
885
+ calendarPageInfo.year = YEAR_OF_NEXT_MONTH;
886
+ calendarPageInfo.month = NEXT_MONTH;
887
+ }
888
+ };
889
+
890
+ const setRangeModeDateByIndex = (currIndex, currDate) => {
891
+ if (!disabledDate
892
+ && compareFromAndToDateTime(props.mode, calendarType, currDate, selectedValue.value)) {
893
+ return;
894
+ }
895
+
896
+ selectedValue.value[currIndex] = currDate;
897
+ moveDispCalendarMonth();
898
+ updateCalendarPage(selectedValue.value);
899
+ };
900
+
901
+ switch (props.mode) {
902
+ case 'date':
903
+ selectedValue.value = CURR_DATE_STR;
904
+ moveDispCalendarMonth();
905
+ emit('update:modelValue', CURR_DATE_STR);
906
+ setCalendarDate('main');
907
+ break;
908
+ case 'dateTime': {
909
+ const isExistTime = !!(selectedValue.value?.split(' ')[1]);
910
+ const CURR_TIME_HMS = isExistTime
911
+ ? selectedValue.value?.split(' ')[1] : '00:00:00';
912
+ selectedValue.value = getChangedValueByTimeFormat(
913
+ timeFormat,
914
+ `${CURR_DATE_STR} ${CURR_TIME_HMS}`,
915
+ );
916
+ moveDispCalendarMonth();
917
+ emit('update:modelValue', selectedValue.value);
918
+ setCalendarDate('main');
919
+ if (!isExistTime) {
920
+ const currTime = selectedValue.value.split(' ')[1].split(':');
921
+ setCalendarPageInfo('main', {
922
+ hour: Math.floor(currTime[0] / CELL_CNT_IN_ONE_PAGE) + 1,
923
+ min: Math.floor(currTime[1] / CELL_CNT_IN_ONE_PAGE) + 1,
924
+ sec: Math.floor(currTime[2] / CELL_CNT_IN_ONE_PAGE) + 1,
925
+ });
926
+ setHmsTime();
927
+ }
928
+ break;
929
+ }
930
+ case 'dateMulti': {
931
+ const multiType = props.options.multiType;
932
+ const multiDayLimit = props.options.multiDayLimit;
933
+ if (multiType === 'date') {
934
+ const selectedIdx = selectedValue.value.indexOf(CURR_DATE_STR);
935
+ if (selectedIdx > -1) {
936
+ selectedValue.value.splice(selectedIdx, 1);
937
+ emit('update:modelValue', [...selectedValue.value]);
938
+ } else if (selectedValue.value.length < multiDayLimit) {
939
+ selectedValue.value.push(CURR_DATE_STR);
940
+ moveDispCalendarMonth();
941
+ emit('update:modelValue', [...selectedValue.value]);
942
+ }
943
+ } else if (multiType === 'week' || multiType === 'weekday') {
944
+ const NUMBER_OF_DAYS_IN_RANGE = multiType === 'week' ? 7 : 5; // 범위 내 선택된 날짜 개수
945
+ const DIFF_UNTIL_THE_LAST_DATE = multiType === 'week' ? 6 : 5; // 한 주의 마지막 날짜까지의 차이
946
+ const exactSelectedDate = new Date(`${CURR_DATE_STR} 00:00:00`);
947
+ const dayOfTheWeekOfTheSelectedDate = exactSelectedDate.getDay();
948
+ const diffFromTheLastDay = DIFF_UNTIL_THE_LAST_DATE - dayOfTheWeekOfTheSelectedDate;
949
+ const theLastDayTime = exactSelectedDate.getTime() + (ONE_DAY_MS * diffFromTheLastDay);
950
+
951
+ for (let i = 0; i < NUMBER_OF_DAYS_IN_RANGE; i++) {
952
+ const loopYear = new Date(theLastDayTime - (i * ONE_DAY_MS)).getFullYear();
953
+ const loopMonth = new Date(theLastDayTime - (i * ONE_DAY_MS)).getMonth() + 1;
954
+ const loopDate = new Date(theLastDayTime - (i * ONE_DAY_MS)).getDate();
955
+ const dateStr = `${loopYear}-${lpadToTwoDigits(loopMonth)}-${lpadToTwoDigits(loopDate)}`;
956
+ if (i === 0) {
957
+ if (selectedValue.value.includes(dateStr)) {
958
+ selectedValue.value.splice(0);
959
+ break;
960
+ } else {
961
+ selectedValue.value.splice(0);
962
+ moveDispCalendarMonth();
963
+ }
964
+ }
965
+ if (!disabledDate || !disabledDate(new Date(dateStr))) {
966
+ selectedValue.value.unshift(dateStr);
967
+ }
968
+ }
969
+ emit('update:modelValue', [...selectedValue.value]);
970
+ }
971
+ setCalendarDate('main');
972
+ break;
973
+ }
974
+ case 'dateRange': {
975
+ if (!selectedValue.value.length) {
976
+ selectedValue.value.push(CURR_DATE_STR);
977
+ selectedValue.value.push(CURR_DATE_STR);
978
+ updateCalendarPage(selectedValue.value);
979
+ } else {
980
+ setRangeModeDateByIndex(calendarType !== 'main' | 0, CURR_DATE_STR);
981
+ }
982
+ emit('update:modelValue', [...selectedValue.value]);
983
+ break;
984
+ }
985
+ case 'dateTimeRange': {
986
+ if (!selectedValue.value.length) {
987
+ let fromDate = `${CURR_DATE_STR} 00:00:00`;
988
+ let toDate = `${CURR_DATE_STR} 00:00:00`;
989
+ if (timeFormat && timeFormat.length) {
990
+ fromDate = getChangedValueByTimeFormat(
991
+ timeFormat[0],
992
+ fromDate,
993
+ );
994
+ toDate = getChangedValueByTimeFormat(
995
+ timeFormat[1],
996
+ toDate,
997
+ );
998
+ }
999
+ selectedValue.value.push(fromDate);
1000
+ selectedValue.value.push(toDate);
1001
+
1002
+ updateCalendarPage(selectedValue.value);
1003
+ setHmsTime();
1004
+ } else {
1005
+ const currIndex = calendarType !== 'main' | 0;
1006
+ const CURR_TIME_HMS = selectedValue.value[currIndex]?.split(' ')[1] || '00:00:00';
1007
+
1008
+ let currDate = `${CURR_DATE_STR} ${CURR_TIME_HMS}`;
1009
+ if (timeFormat && timeFormat.length) {
1010
+ currDate = getChangedValueByTimeFormat(
1011
+ timeFormat[currIndex],
1012
+ currDate,
1013
+ );
1014
+ }
1015
+ setRangeModeDateByIndex(currIndex, currDate);
1016
+ }
1017
+ emit('update:modelValue', [...selectedValue.value]);
1018
+ break;
1019
+ }
1020
+ default:
1021
+ break;
1022
+ }
1023
+ };
1024
+
1025
+ /**
1026
+ * Calendar mode: dateTime인 경우 HMS 이동 화살표 클릭 이벤트
1027
+ * @param calendarType - {main|expanded}
1028
+ * @param timeType - {hour|min|sec}
1029
+ * @param arrow - {up|down}
1030
+ */
1031
+ const clickHmsBtn = (calendarType, timeType, arrow) => {
1032
+ if (preventTimeEventType[calendarType][timeType]) {
1033
+ return;
1034
+ }
1035
+
1036
+ const calendarPageInfo = calendarType === 'expanded'
1037
+ ? expandedCalendarPageInfo : mainCalendarPageInfo;
1038
+ const FIRST_PAGE = 1;
1039
+ const HOUR_MAX_PAGE = 2;
1040
+ const MINUTE_MAX_PAGE = 5;
1041
+ const SECOND_MAX_PAGE = 5;
1042
+ if (timeType === 'hour') {
1043
+ if (arrow === 'down' && calendarPageInfo.hour < HOUR_MAX_PAGE) {
1044
+ calendarPageInfo.hour++;
1045
+ } else if (arrow === 'up' && calendarPageInfo.hour > FIRST_PAGE) {
1046
+ calendarPageInfo.hour--;
1047
+ }
1048
+ } else if (timeType === 'min') {
1049
+ if (arrow === 'down' && calendarPageInfo.min < MINUTE_MAX_PAGE) {
1050
+ calendarPageInfo.min++;
1051
+ } else if (arrow === 'up' && calendarPageInfo.min > FIRST_PAGE) {
1052
+ calendarPageInfo.min--;
1053
+ }
1054
+ } else if (timeType === 'sec') {
1055
+ if (arrow === 'down' && calendarPageInfo.sec < SECOND_MAX_PAGE) {
1056
+ calendarPageInfo.sec++;
1057
+ } else if (arrow === 'up' && calendarPageInfo.sec > FIRST_PAGE) {
1058
+ calendarPageInfo.sec--;
1059
+ }
1060
+ }
1061
+ };
1062
+
1063
+ /**
1064
+ * Click cell In HMS area
1065
+ * @param calendarType - {main|expanded}
1066
+ * @param timeType - {hour|min|sec}
1067
+ * @param i - row
1068
+ * @param j - col
1069
+ */
1070
+ const clickTime = (calendarType, timeType, i, j) => {
1071
+ if (preventTimeEventType[calendarType][timeType]) {
1072
+ return;
1073
+ }
1074
+
1075
+ const calendarPageInfo = calendarType === 'expanded'
1076
+ ? expandedCalendarPageInfo : mainCalendarPageInfo;
1077
+ const timeInfo = calendarType === 'main'
1078
+ ? mainTimeTableInfo : expandedTimeTableInfo;
1079
+ const currPage = calendarPageInfo[timeType] - 1;
1080
+ const currRowIdx = i - 1;
1081
+ const currColIdx = j - 1;
1082
+ const clickedNum = (currPage * CELL_CNT_IN_ONE_PAGE)
1083
+ + (currRowIdx * CELL_CNT_IN_ONE_ROW) + currColIdx;
1084
+
1085
+ if (timeInfo[timeType][clickedNum]?.isDisabled) {
1086
+ return;
1087
+ }
1088
+
1089
+ const TODAY = new Date();
1090
+ const TODAY_INFO = {
1091
+ year: TODAY.getFullYear(),
1092
+ month: TODAY.getMonth() + 1,
1093
+ date: TODAY.getDate(),
1094
+ };
1095
+ let EXIST_MODEL = true;
1096
+ let valueListByUpdatePage = [];
1097
+
1098
+ const getTimeValueByType = () => {
1099
+ let targetTimeValue;
1100
+ if (timeType === 'hour') {
1101
+ targetTimeValue = `${lpadToTwoDigits(clickedNum)}:00:00'`;
1102
+ } else if (timeType === 'min') {
1103
+ targetTimeValue = `00:${lpadToTwoDigits(clickedNum)}:00`;
1104
+ } else if (timeType === 'sec') {
1105
+ targetTimeValue = `00:00:${lpadToTwoDigits(clickedNum)}`;
1106
+ }
1107
+ return `${formatDateTime(TODAY_INFO)} ${targetTimeValue}`;
1108
+ };
1109
+
1110
+ const getChangedValue = (targetValue) => {
1111
+ const HOUR_START_IDX = 11;
1112
+ const MIN_START_IDX = 14;
1113
+ const SEC_START_IDX = 17;
1114
+ const REPLACE_TEXT_SIZE = 2;
1115
+ let START_IDX = HOUR_START_IDX;
1116
+ if (timeType === 'min') {
1117
+ START_IDX = MIN_START_IDX;
1118
+ } else if (timeType === 'sec') {
1119
+ START_IDX = SEC_START_IDX;
1120
+ }
1121
+ return `${targetValue?.substr(0, START_IDX)}`
1122
+ + `${lpadToTwoDigits(clickedNum)}${targetValue?.substr(START_IDX + REPLACE_TEXT_SIZE)}`;
1123
+ };
1124
+
1125
+ if (props.mode === 'dateTime') {
1126
+ if (!props.modelValue) {
1127
+ EXIST_MODEL = false;
1128
+ selectedValue.value = getChangedValueByTimeFormat(
1129
+ timeFormat,
1130
+ getTimeValueByType(),
1131
+ );
1132
+ emit('update:modelValue', selectedValue.value);
1133
+ } else {
1134
+ selectedValue.value = getChangedValueByTimeFormat(
1135
+ timeFormat,
1136
+ getChangedValue(props.modelValue),
1137
+ );
1138
+ emit('update:modelValue', selectedValue.value);
1139
+ }
1140
+ valueListByUpdatePage.push(selectedValue.value);
1141
+ } else {
1142
+ const index = calendarType !== 'main' | 0;
1143
+ if (!props.modelValue.length) {
1144
+ const timeValue = getTimeValueByType();
1145
+ selectedValue.value = [timeValue, timeValue];
1146
+
1147
+ if (timeFormat && timeFormat.length) {
1148
+ selectedValue.value = [...selectedValue.value
1149
+ .map((v, idx) => getChangedValueByTimeFormat(timeFormat[idx], v))];
1150
+ }
1151
+
1152
+ EXIST_MODEL = false;
1153
+ valueListByUpdatePage = selectedValue.value;
1154
+ } else {
1155
+ let currDateTime = getChangedValue(props.modelValue[index]);
1156
+ if (timeFormat && timeFormat.length) {
1157
+ currDateTime = getChangedValueByTimeFormat(
1158
+ timeFormat[index],
1159
+ currDateTime,
1160
+ );
1161
+ }
1162
+
1163
+ selectedValue.value[index] = currDateTime;
1164
+ }
1165
+ emit('update:modelValue', [...selectedValue.value]);
1166
+ }
1167
+ setHmsTime();
1168
+ // dateTime의 v-model값이 없는 경우 time area를 클릭하였을 때 date의 값은 today로 세팅
1169
+ if (!EXIST_MODEL) {
1170
+ updateCalendarPage(valueListByUpdatePage);
1171
+ }
1172
+ };
1173
+
1174
+ /**
1175
+ * Wheel up or wheel down In Calendar Month(tbody) area
1176
+ * @param calendarType - {main|expanded}
1177
+ * @param e
1178
+ */
1179
+ const wheelMonth = (calendarType, e) => {
1180
+ moveMonth(calendarType, e.deltaY > 0 ? 'next' : 'prev');
1181
+ setCalendarDate(calendarType);
1182
+ };
1183
+
1184
+ /**
1185
+ * Wheel up or wheel down In Calendar Time(HMS) area
1186
+ * @param calendarType - {main|expanded}
1187
+ * @param timeType - {hour|min|sec}
1188
+ * @param e
1189
+ */
1190
+ const wheelTime = (calendarType, timeType, e) => {
1191
+ if (preventTimeEventType[calendarType][timeType]) {
1192
+ return;
1193
+ }
1194
+
1195
+ clickHmsBtn(calendarType, timeType, e.deltaY > 0 ? 'down' : 'up');
1196
+ };
1197
+
1198
+ /**
1199
+ * dateRange 모드에서 한번 클릭 후 날짜에 마우스무브하는 경우
1200
+ * 커서 위에 있는 날짜까지의 selectedValue의 영역 선택한 selection 로직
1201
+ * 일반적인 마우스무브 로직에 성능향상을 위한 throttle 10ms를 설정
1202
+ * @param calendarType - 캘린더 종류 ('main'|'expanded')
1203
+ * @param e - 마우스이벤트
1204
+ * @type {function(): (*)}
1205
+ */
1206
+ const onMousemoveDate = throttle((calendarType, e) => {
1207
+ const target = e.target.tagName === 'TD' ? e.target : e.target.parentElement;
1208
+ const isDisabled = target.classList.contains('disabled');
1209
+ const isPrev = target.classList.contains('prev');
1210
+ const isNext = target.classList.contains('next');
1211
+ if (target.classList.length > 0 && !isDisabled) {
1212
+ const calendarPageInfo = calendarType === 'main' ? mainCalendarPageInfo : expandedCalendarPageInfo;
1213
+ let yearMonth = {
1214
+ year: +calendarPageInfo.year,
1215
+ month: +calendarPageInfo.month,
1216
+ date: e.target.innerText,
1217
+ };
1218
+ // 달력 내 이전달, 다음달 일자의 경우 연, 월 보정
1219
+ if (isPrev) {
1220
+ yearMonth = { ...yearMonth, ...getSideMonthCalendarInfo('prev', yearMonth.year, yearMonth.month) };
1221
+ } else if (isNext) {
1222
+ yearMonth = { ...yearMonth, ...getSideMonthCalendarInfo('next', yearMonth.year, yearMonth.month) };
1223
+ }
1224
+ const STANDARD_DATE_STR = dateRangeClickedDate.value;
1225
+ const MOUSEMOVE_DATE_STR = formatDateTime({
1226
+ year: yearMonth.year,
1227
+ month: yearMonth.month,
1228
+ date: yearMonth.date,
1229
+ });
1230
+
1231
+ // fromDate ~ toDate selection 순서 세팅
1232
+ if (getDateMs(MOUSEMOVE_DATE_STR) < getDateMs(STANDARD_DATE_STR)) {
1233
+ selectedValue.value[0] = MOUSEMOVE_DATE_STR;
1234
+ selectedValue.value[1] = STANDARD_DATE_STR;
1235
+ } else {
1236
+ selectedValue.value[0] = STANDARD_DATE_STR;
1237
+ selectedValue.value[1] = MOUSEMOVE_DATE_STR;
1238
+ }
1239
+ setCalendarDate('main');
1240
+ setCalendarDate('expanded');
1241
+ }
1242
+ }, 10);
1243
+
1244
+ watch(
1245
+ () => props.modelValue,
1246
+ (curr) => {
1247
+ selectedValue.value = curr;
1248
+
1249
+ if (props.mode.includes('Time')) {
1250
+ let updateValue = [];
1251
+ if (props.mode === 'dateTime') {
1252
+ updateValue = [selectedValue.value];
1253
+ } else if (props.mode === 'dateTimeRange') {
1254
+ updateValue = selectedValue.value;
1255
+ }
1256
+ updateCalendarPage(updateValue);
1257
+ setHmsTime();
1258
+ }
1259
+ });
1260
+
1261
+ return {
1262
+ clickPrevNextBtn,
1263
+ clickDate,
1264
+ clickHmsBtn,
1265
+ clickTime,
1266
+ wheelMonth,
1267
+ wheelTime,
1268
+ calendarEventName,
1269
+ onMousemoveDate,
1270
+ preventTimeEventType,
1271
+ };
1272
+ };