evui 3.4.9 → 3.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evui",
3
- "version": "3.4.9",
3
+ "version": "3.4.10",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -1,4 +1,3 @@
1
- import throttle from '@/common/utils.throttle';
2
1
  import { mobileCheck } from '@/common/utils';
3
2
  import Model from './model';
4
3
  import TimeScale from './scale/scale.time';
@@ -134,10 +133,6 @@ class EvChart {
134
133
 
135
134
  if (tooltip.use) {
136
135
  this.createTooltipDOM();
137
-
138
- if (tooltip.throttledMove) {
139
- this.onMouseMove = throttle(this.onMouseMove, 30);
140
- }
141
136
  }
142
137
 
143
138
  this.createEventFunctions?.();
@@ -1,4 +1,5 @@
1
1
  import { numberWithComma } from '@/common/utils';
2
+ import throttle from '@/common/utils.throttle';
2
3
  import { cloneDeep, defaultsDeep, inRange } from 'lodash-es';
3
4
  import dayjs from 'dayjs';
4
5
 
@@ -33,11 +34,9 @@ const modules = {
33
34
  this.overlayClear();
34
35
 
35
36
  if (Object.keys(hitInfo.items).length) {
36
- if ((type !== 'scatter' && type !== 'heatMap') || tooltip.use) {
37
+ if (tooltip.use) {
37
38
  this.drawItemsHighlight(hitInfo, ctx);
38
- }
39
39
 
40
- if (tooltip.use) {
41
40
  if (tooltip?.formatter?.html) {
42
41
  this.drawCustomTooltip(hitInfo?.items);
43
42
  this.setCustomTooltipLayoutPosition(hitInfo, e);
@@ -349,6 +348,9 @@ const modules = {
349
348
  if (this.options?.tooltip?.useScrollbar) {
350
349
  this.overlayCanvas.addEventListener('wheel', this.onWheel, { passive: false });
351
350
  }
351
+ if (this.options?.tooltip?.throttledMove) {
352
+ this.onMouseMove = throttle(this.onMouseMove, 30);
353
+ }
352
354
 
353
355
  this.overlayCanvas.addEventListener('mousemove', this.onMouseMove);
354
356
  this.overlayCanvas.addEventListener('mouseleave', this.onMouseLeave);
@@ -33,6 +33,7 @@ const module = {
33
33
  scrollbarOpt.type = axisOpt?.[0]?.type;
34
34
  scrollbarOpt.range = axisOpt?.[0]?.range || null;
35
35
 
36
+ this.initScrollbarRange(dir);
36
37
  this.createScrollbarLayout(dir);
37
38
  this.createScrollbar(dir);
38
39
  this.createScrollEvent(dir);
@@ -40,31 +41,29 @@ const module = {
40
41
  }
41
42
  },
42
43
 
43
- checkValidRange(dir) {
44
+ initScrollbarRange(dir) {
44
45
  const scrollbarOpt = this.scrollbar[dir];
45
46
  const axesType = scrollbarOpt.type;
46
47
 
47
48
  if (scrollbarOpt.range?.length) {
48
49
  const [min, max] = scrollbarOpt.range;
49
-
50
- if (!(truthyNumber(min) && truthyNumber(max))) {
51
- return true;
52
- }
53
-
54
- if (axesType === 'step') {
55
- const labels = this.options.type === 'heatMap' ? this.data.labels[dir] : this.data.labels;
56
- if (min < 0 || max > labels.length - 1) {
57
- return true;
58
- }
59
- } else {
60
- const minMax = this.minMax[dir]?.[0];
61
- if (+min < +minMax.min || +max > +minMax.max) {
62
- return true;
50
+ let limitMin;
51
+ let limitMax;
52
+
53
+ if ((truthyNumber(min) && truthyNumber(max))) {
54
+ if (axesType === 'step') {
55
+ const labels = this.options.type === 'heatMap' ? this.data.labels[dir] : this.data.labels;
56
+ limitMin = 0;
57
+ limitMax = labels.length - 1;
58
+ } else {
59
+ const minMax = this.minMax[dir]?.[0];
60
+ limitMin = +minMax.min;
61
+ limitMax = +minMax.max;
63
62
  }
63
+ scrollbarOpt.range[0] = +min < limitMin ? limitMin : +min;
64
+ scrollbarOpt.range[1] = +max > limitMax ? limitMax : +max;
64
65
  }
65
66
  }
66
-
67
- return false;
68
67
  },
69
68
 
70
69
  /**
@@ -95,6 +94,7 @@ const module = {
95
94
  const isUpdateAxesRange = !isEqual(newOpt?.[0]?.range, axisOpt?.[0]?.range);
96
95
  if (isUpdateAxesRange || updateData) {
97
96
  this.scrollbar[dir].range = newOpt?.[0]?.range || null;
97
+ this.initScrollbarRange(dir);
98
98
  }
99
99
  this.scrollbar[dir].use = !!newOpt?.[0].scrollbar?.use;
100
100
  },
@@ -104,16 +104,10 @@ const module = {
104
104
  */
105
105
  updateScrollbarPosition() {
106
106
  if (this.scrollbar.x?.use && this.scrollbar.x?.isInit) {
107
- if (this.checkValidRange('x')) {
108
- return;
109
- }
110
107
  this.setScrollbarPosition('x');
111
108
  }
112
109
 
113
110
  if (this.scrollbar.y?.use && this.scrollbar.y?.isInit) {
114
- if (this.checkValidRange('y')) {
115
- return;
116
- }
117
111
  this.setScrollbarPosition('y');
118
112
  }
119
113
  },
@@ -74,8 +74,13 @@ class Scale {
74
74
 
75
75
  const range = scrollbarOpt?.use ? scrollbarOpt?.range : this.range;
76
76
  if (range?.length === 2) {
77
- maxValue = range[1] > +minMax.max ? +minMax.max : range[1];
78
- minValue = range[0] < +minMax.min ? +minMax.min : range[0];
77
+ if (this.options.type === 'heatMap') {
78
+ maxValue = range[1] > +minMax.max ? +minMax.max : range[1];
79
+ minValue = range[0] < +minMax.min ? +minMax.min : range[0];
80
+ } else {
81
+ maxValue = range[1];
82
+ minValue = range[0];
83
+ }
79
84
  } else {
80
85
  maxValue = minMax.max;
81
86
  minValue = minMax.min;
@@ -54,21 +54,24 @@
54
54
  />
55
55
  <div
56
56
  class="filtering-items__item"
57
- @click="onClickFilteringItem(
58
- filteringItemsByColumn[field]?.[0].caption,
59
- filteringItemsByColumn[field]
57
+ @click.stop="onClickFilteringItem(
58
+ {
59
+ caption: filteringItemsByColumn[field]?.[idx].caption,
60
+ field: field,
61
+ },
62
+ filteringItemsByColumn[field],
60
63
  )"
61
64
  >
62
65
  <span class="filtering-items__item--title">
63
- {{ filteringItemsByColumn[field]?.[0].caption }}
66
+ {{ filteringItemsByColumn[field]?.[idx].caption }}
64
67
  </span>
65
68
  <span
66
69
  v-if="filteringItemsByColumn[field].length < 2"
67
70
  class="filtering-items__item--value"
68
- :title="`${filteringItemsByColumn[field][0].value}`"
71
+ :title="`${filteringItemsByColumn[field][idx].value}`"
69
72
  >
70
- {{ filteringItemsByColumn[field]?.[0].comparison }}
71
- {{ filteringItemsByColumn[field]?.[0].value }}
73
+ {{ filteringItemsByColumn[field]?.[idx].comparison }}
74
+ {{ filteringItemsByColumn[field]?.[idx].value }}
72
75
  </span>
73
76
  <span
74
77
  v-else
@@ -108,7 +111,7 @@
108
111
  />
109
112
  <div class="filtering-items__item">
110
113
  <span class="filtering-items__item--title">
111
- {{ selectedFilteringFiled }}
114
+ {{ selectedFilteringColumn.caption }}
112
115
  </span>
113
116
  <span class="filtering-items__item--value">
114
117
  {{ field.comparison }}
@@ -117,7 +120,11 @@
117
120
  <ev-icon
118
121
  class="filtering-items__item--remove"
119
122
  icon="ev-icon-s-close"
120
- @click="removeFiltering({field: selectedFilteringFiled, idx})"
123
+ @click="removeFiltering(
124
+ {
125
+ field: selectedFilteringColumn.field,
126
+ idx,
127
+ })"
121
128
  />
122
129
  </div>
123
130
  </template>
@@ -318,7 +325,6 @@
318
325
  }"
319
326
  @scroll="onScroll"
320
327
  @contextmenu="onContextMenu($event)"
321
- @contextmenu.prevent="menu.show"
322
328
  >
323
329
  <!-- vScroll Top -->
324
330
  <div
@@ -423,7 +429,6 @@
423
429
  <span
424
430
  class="row-contextmenu__btn"
425
431
  @click="onContextMenu($event)"
426
- @click.prevent="menu.show"
427
432
  >
428
433
  <slot name="contextmenuIcon"></slot>
429
434
  </span>
@@ -433,7 +438,6 @@
433
438
  icon="ev-icon-warning2"
434
439
  class="row-contextmenu__btn"
435
440
  @click="onContextMenu($event)"
436
- @click.prevent="menu.show"
437
441
  />
438
442
  </template>
439
443
  </td>
@@ -697,6 +701,7 @@ export default {
697
701
  isSorting: false,
698
702
  sortField: '',
699
703
  sortOrder: '',
704
+ sortColumn: {},
700
705
  });
701
706
  const contextInfo = reactive({
702
707
  menu: null,
@@ -767,7 +772,7 @@ export default {
767
772
  const {
768
773
  onSort,
769
774
  setSort,
770
- } = sortEvent({ sortInfo, stores, getColumnIndex, updatePagingInfo });
775
+ } = sortEvent({ sortInfo, stores, updatePagingInfo });
771
776
 
772
777
  const {
773
778
  onSearch,
@@ -845,7 +850,7 @@ export default {
845
850
  const {
846
851
  onRowClick,
847
852
  onRowDblClick,
848
- } = clickEvent({ selectInfo, stores, setContextMenu });
853
+ } = clickEvent({ selectInfo, stores });
849
854
 
850
855
  const {
851
856
  onDragStart,
@@ -1049,7 +1054,10 @@ export default {
1049
1054
  const filteringItemsRef = ref(null);
1050
1055
  const isShowFilteringItemsBox = ref(false);
1051
1056
  const isShowColumnFilteringItems = ref(false);
1052
- const selectedFilteringFiled = ref('');
1057
+ const selectedFilteringColumn = reactive({
1058
+ caption: '',
1059
+ field: '',
1060
+ });
1053
1061
  const selectedFilteringItems = ref([]);
1054
1062
  const operatorItems = [
1055
1063
  { name: 'AND', value: 'and' },
@@ -1057,13 +1065,14 @@ export default {
1057
1065
  ];
1058
1066
  const isExpandColumnFilteringItems = ref(false);
1059
1067
 
1060
- const onClickFilteringItem = (field, filters) => {
1061
- selectedFilteringFiled.value = field;
1068
+ const onClickFilteringItem = ({ caption, field }, filters) => {
1069
+ selectedFilteringColumn.caption = caption;
1070
+ selectedFilteringColumn.field = field;
1062
1071
  selectedFilteringItems.value = filters;
1063
1072
  if (filters?.length > 1) { // open filtering items box
1064
1073
  isShowFilteringItemsBox.value = true;
1065
1074
  const x = filteringItemsRef.value.getBoundingClientRect().left;
1066
- const y = filteringItemsRef.value.getBoundingClientRect().top
1075
+ const y = window.pageYOffset + filteringItemsRef.value.getBoundingClientRect().top
1067
1076
  + filteringItemsRef.value.getBoundingClientRect().height;
1068
1077
  filteringItemsBoxPosition.boxTop = `${y}px`;
1069
1078
  filteringItemsBoxPosition.boxLeft = `${x}px`;
@@ -1120,7 +1129,7 @@ export default {
1120
1129
  setStore([], false);
1121
1130
  };
1122
1131
  const removeFiltering = ({ field, idx }) => {
1123
- filterInfo.filteringItemsByColumn[field].splice(idx, 1);
1132
+ filterInfo.filteringItemsByColumn[field]?.splice(idx, 1);
1124
1133
  if (!filterInfo.filteringItemsByColumn[field].length) {
1125
1134
  delete filterInfo.filteringItemsByColumn[field];
1126
1135
  }
@@ -1199,7 +1208,7 @@ export default {
1199
1208
  isShowColumnFilteringItems,
1200
1209
  operatorItems,
1201
1210
  selectedFilteringItems,
1202
- selectedFilteringFiled,
1211
+ selectedFilteringColumn,
1203
1212
  filteringItemsRef,
1204
1213
  isShowFilteringItemsBox,
1205
1214
  ...toRefs(filteringItemsBoxPosition),
@@ -47,6 +47,7 @@
47
47
  v-model="item.value"
48
48
  class="ev-grid-filter-setting__row--value"
49
49
  :disabled="item.comparison === 'isEmpty' || item.comparison === 'isNotEmpty'"
50
+ @input="validateValue($props.column.type, item)"
50
51
  />
51
52
  <div
52
53
  class="ev-grid-filter-setting__row--button"
@@ -80,6 +81,7 @@
80
81
  <script>
81
82
  import { clickoutside } from '@/directives/clickoutside';
82
83
  import { computed, onBeforeMount, ref, watch } from 'vue';
84
+ import { cloneDeep } from 'lodash-es';
83
85
 
84
86
  export default {
85
87
  name: 'EVGridFilterSetting',
@@ -169,6 +171,7 @@ export default {
169
171
  comparison: '=',
170
172
  operator,
171
173
  value: '',
174
+ caption: filteringColumn.value.caption,
172
175
  });
173
176
  };
174
177
  const removeRow = (idx) => {
@@ -199,8 +202,9 @@ export default {
199
202
  () => props.isShow,
200
203
  (isShow) => {
201
204
  const rowList = [];
205
+ const items = cloneDeep(props.items);
202
206
  if (isShow && filteringColumn.value.field) {
203
- if (!props.items[filteringColumn.value.field]?.length) {
207
+ if (!items[filteringColumn.value.field]?.length) {
204
208
  rowList.push({
205
209
  comparison: '=',
206
210
  operator: 'and',
@@ -208,7 +212,7 @@ export default {
208
212
  caption: filteringColumn.value.caption,
209
213
  });
210
214
  } else {
211
- props.items[filteringColumn.value.field].forEach((row) => {
215
+ items[filteringColumn.value.field].forEach((row) => {
212
216
  rowList.push(row);
213
217
  });
214
218
  }
@@ -232,6 +236,12 @@ export default {
232
236
  onBeforeMount(() => {
233
237
  initWrapperDiv();
234
238
  });
239
+
240
+ const validateValue = (type, item) => {
241
+ if (type === 'number' || type === 'float') {
242
+ item.value = item.value.trim();
243
+ }
244
+ };
235
245
  return {
236
246
  filteringItems,
237
247
  isShowFilterSetting,
@@ -244,6 +254,7 @@ export default {
244
254
  applyFiltering,
245
255
  changeComparison,
246
256
  getSelectTitle,
257
+ validateValue,
247
258
  };
248
259
  },
249
260
  };
@@ -332,7 +332,7 @@ export const resizeEvent = (params) => {
332
332
 
333
333
  export const clickEvent = (params) => {
334
334
  const { emit } = getCurrentInstance();
335
- const { selectInfo, stores, setContextMenu } = params;
335
+ const { selectInfo, stores } = params;
336
336
  const getClickedRowData = (event, row) => {
337
337
  const tagName = event.target.tagName.toLowerCase();
338
338
  let cellInfo = {};
@@ -355,7 +355,7 @@ export const clickEvent = (params) => {
355
355
  * @param {object} event - 이벤트 객체
356
356
  * @param {array} row - row 데이터
357
357
  */
358
- let timer = null;
358
+ let clickTimer = null;
359
359
  let lastIndex = -1;
360
360
  const onRowClick = (event, row, isRight) => {
361
361
  if (event.target.parentElement.classList?.contains('row-checkbox-input')) {
@@ -395,8 +395,10 @@ export const clickEvent = (params) => {
395
395
  }
396
396
  };
397
397
 
398
- clearTimeout(timer);
399
- timer = setTimeout(() => {
398
+ if (clickTimer) {
399
+ clearTimeout(clickTimer);
400
+ }
401
+ clickTimer = setTimeout(() => {
400
402
  if (selectInfo.useSelect) {
401
403
  const rowData = row[ROW_DATA_INDEX];
402
404
  const selected = row[ROW_SELECT_INDEX];
@@ -423,7 +425,6 @@ export const clickEvent = (params) => {
423
425
  lastIndex = row[ROW_INDEX];
424
426
  emit('update:selected', selectInfo.selectedRow);
425
427
  emit('click-row', getClickedRowData(event, row));
426
- setContextMenu();
427
428
  }
428
429
  }, 100);
429
430
  return true;
@@ -435,7 +436,9 @@ export const clickEvent = (params) => {
435
436
  * @param {array} row - row 데이터
436
437
  */
437
438
  const onRowDblClick = (event, row) => {
438
- clearTimeout(timer);
439
+ if (clickTimer) {
440
+ clearTimeout(clickTimer);
441
+ }
439
442
  emit('dblclick-row', getClickedRowData(event, row));
440
443
  };
441
444
  return { onRowClick, onRowDblClick };
@@ -523,7 +526,7 @@ export const checkEvent = (params) => {
523
526
  };
524
527
 
525
528
  export const sortEvent = (params) => {
526
- const { sortInfo, stores, getColumnIndex, updatePagingInfo } = params;
529
+ const { sortInfo, stores, updatePagingInfo } = params;
527
530
  function OrderQueue() {
528
531
  this.orders = ['asc', 'desc', 'init'];
529
532
  this.dequeue = () => this.orders.shift();
@@ -539,6 +542,7 @@ export const sortEvent = (params) => {
539
542
  const onSort = (column, sortOrder) => {
540
543
  const sortable = column.sortable === undefined ? true : column.sortable;
541
544
  if (sortable) {
545
+ sortInfo.sortColumn = column;
542
546
  if (sortInfo.sortField !== column?.field) {
543
547
  order.orders = ['asc', 'desc', 'init'];
544
548
  sortInfo.sortField = column?.field;
@@ -574,8 +578,8 @@ export const sortEvent = (params) => {
574
578
  });
575
579
  return;
576
580
  }
577
- const index = getColumnIndex(sortInfo.sortField);
578
- const type = stores.originColumns[index]?.type || 'string';
581
+ const index = sortInfo.sortColumn.index;
582
+ const type = sortInfo.sortColumn.type || 'string';
579
583
  const sortFn = sortInfo.sortOrder === 'desc' ? setDesc : setAsc;
580
584
  const numberSortFn = sortInfo.sortOrder === 'desc' ? numberSetDesc : numberSetAsc;
581
585
  const getColumnValue = (a, b) => {
@@ -641,25 +645,26 @@ export const filterEvent = (params) => {
641
645
  /**
642
646
  * 전달받은 문자열 내 해당 키워드가 존재하는지 확인한다.
643
647
  *
644
- * @param {string} search - 검색 키워드
645
- * @param {string} origin - 기준 문자열
648
+ * @param {string} conditionValue - 검색 키워드
649
+ * @param {string} value - 기준 문자열
650
+ * @param {string} pos - 시작, 끝나는 문자열
646
651
  * @returns {boolean} 문자열 내 키워드 존재 유무
647
652
  */
648
- const findLike = (search, origin, pos) => {
649
- if (typeof search !== 'string' || origin === null) {
653
+ const findLike = (conditionValue, value, pos) => {
654
+ if (typeof conditionValue !== 'string' || value === null) {
650
655
  return false;
651
656
  }
652
- let regx = search.replace(new RegExp('([\\.\\\\\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:\\-])', 'g'), '\\$1');
653
- regx = regx.replace(/%/g, '.*').replace(/_/g, '.');
654
- let regValue = `^${regx}$`;
657
+ const baseValueLower = value.toLowerCase();
658
+ const conditionValueLower = conditionValue.toLowerCase();
659
+ let result = baseValueLower.includes(conditionValueLower);
655
660
  if (pos) {
656
661
  if (pos === 'start') {
657
- regValue = `^${regx}`;
662
+ result = baseValueLower.startsWith(conditionValueLower);
658
663
  } else if (pos === 'end') {
659
- regValue = `${regx}$`;
664
+ result = baseValueLower.endsWith(conditionValueLower);
660
665
  }
661
666
  }
662
- return RegExp(regValue, 'gi').test(origin);
667
+ return result;
663
668
  };
664
669
  /**
665
670
  * 필터 조건에 따라 문자열을 확인한다.
@@ -681,13 +686,13 @@ export const filterEvent = (params) => {
681
686
  } else if (comparison === '!=') {
682
687
  result = conditionValue.toLowerCase() !== value.toLowerCase();
683
688
  } else if (comparison === '%s%') {
684
- result = findLike(`%${conditionValue}%`, value);
689
+ result = findLike(conditionValue, value);
685
690
  } else if (comparison === 'notLike') {
686
- result = !findLike(`%${conditionValue}%`, value);
691
+ result = !findLike(conditionValue, value);
687
692
  } else if (comparison === 's%') {
688
- result = findLike(`${conditionValue}`, value, 'start');
693
+ result = findLike(conditionValue, value, 'start');
689
694
  } else if (comparison === '%s') {
690
- result = findLike(`${conditionValue}`, value, 'end');
695
+ result = findLike(conditionValue, value, 'end');
691
696
  } else if (comparison === 'isEmpty') {
692
697
  result = value === undefined || value === null || value === '';
693
698
  } else if (comparison === 'isNotEmpty') {
@@ -765,33 +770,36 @@ export const filterEvent = (params) => {
765
770
  * 전체 데이터에서 설정된 필터 적용 후 결과를 filterStore 에 저장한다.
766
771
  */
767
772
  const setFilter = () => {
768
- let filterStore = [];
769
- let isApply = false;
770
-
771
773
  const filteringItemsByColumn = filterInfo.filteringItemsByColumn;
772
774
  const fields = Object.keys(filteringItemsByColumn);
773
775
  const originStore = stores.originStore;
776
+ let filterStore = [];
774
777
  let filteredOnce = false;
778
+
775
779
  fields.forEach((field) => {
776
780
  const filters = filteringItemsByColumn[field];
777
781
  const index = getColumnIndex(field);
778
782
  const columnType = props.columns[index].type;
779
783
 
780
- filters.forEach((filterItem) => {
781
- isApply = true;
784
+ filters.forEach((item, ix) => {
782
785
  if (!filterStore.length && !filteredOnce) {
783
786
  filterStore = getFilteringData(originStore, columnType, {
784
- ...filterItem,
787
+ ...item,
785
788
  index,
786
789
  });
787
- } else if (filterItem.operator === 'or' || filterInfo.columnOperator === 'or') {
790
+ } else if (ix === 0 && filterInfo.columnOperator === 'or') {
788
791
  filterStore.push(...getFilteringData(originStore, columnType, {
789
- ...filterItem,
792
+ ...item,
793
+ index,
794
+ }));
795
+ } else if (item.operator === 'or') {
796
+ filterStore.push(...getFilteringData(originStore, columnType, {
797
+ ...item,
790
798
  index,
791
799
  }));
792
800
  } else {
793
801
  filterStore = getFilteringData(filterStore, columnType, {
794
- ...filterItem,
802
+ ...item,
795
803
  index,
796
804
  });
797
805
  }
@@ -799,19 +807,19 @@ export const filterEvent = (params) => {
799
807
  });
800
808
  });
801
809
 
802
- if (!isApply) {
810
+ if (!filteredOnce) {
803
811
  stores.filterStore = originStore;
804
812
  } else {
805
813
  stores.filterStore = uniqBy(filterStore, JSON.stringify);
806
814
  }
807
815
  };
808
816
 
809
- let timer = null;
817
+ let searchTimer = null;
810
818
  const onSearch = (searchWord) => {
811
- if (timer) {
812
- clearTimeout(timer);
819
+ if (searchTimer) {
820
+ clearTimeout(searchTimer);
813
821
  }
814
- timer = setTimeout(() => {
822
+ searchTimer = setTimeout(() => {
815
823
  filterInfo.isSearch = false;
816
824
  filterInfo.searchWord = searchWord;
817
825
  if (searchWord) {
@@ -880,35 +888,65 @@ export const contextMenuEvent = (params) => {
880
888
  /**
881
889
  * 컨텍스트 메뉴를 설정한다.
882
890
  *
883
- * @param {boolean} useCustom - 사용자 지정 메뉴 사용 유무
891
+ * @param {object} event - 이벤트 객체
884
892
  */
885
- const setContextMenu = (useCustom = true) => {
893
+ let contextmenuTimer = null;
894
+ const setContextMenu = (e) => {
895
+ if (contextmenuTimer) {
896
+ clearTimeout(contextmenuTimer);
897
+ }
886
898
  const menuItems = [];
899
+ contextmenuTimer = setTimeout(() => {
900
+ if (contextInfo.customContextMenu.length) {
901
+ const customItems = contextInfo.customContextMenu.map(
902
+ (item) => {
903
+ const menuItem = item;
904
+ if (menuItem.validate) {
905
+ menuItem.disabled = !menuItem.validate(menuItem.itemId, selectInfo.selectedRow);
906
+ }
887
907
 
888
- if (useCustom && contextInfo.customContextMenu.length) {
889
- const customItems = contextInfo.customContextMenu.map(
890
- (item) => {
891
- const menuItem = item;
892
- if (menuItem.validate) {
893
- menuItem.disabled = !menuItem.validate(menuItem.itemId, selectInfo.selectedRow);
894
- }
908
+ menuItem.selectedRow = selectInfo.selectedRow ?? [];
909
+ menuItem.contextmenuInfo = selectInfo.contextmenuInfo ?? [];
895
910
 
896
- menuItem.selectedRow = selectInfo.selectedRow ?? [];
897
- menuItem.contextmenuInfo = selectInfo.contextmenuInfo ?? [];
911
+ return menuItem;
912
+ });
898
913
 
899
- return menuItem;
900
- });
914
+ menuItems.push(...customItems);
915
+ }
901
916
 
902
- menuItems.push(...customItems);
917
+ contextInfo.contextMenuItems = menuItems;
918
+ contextInfo.menu.show(e);
919
+ }, 200);
920
+ };
921
+ /**
922
+ * 마우스 우클릭 이벤트를 처리한다.
923
+ *
924
+ * @param {object} event - 이벤트 객체
925
+ */
926
+ const onContextMenu = (e) => {
927
+ e.preventDefault();
928
+ const target = e.target;
929
+ const rowIndex = target.closest('.row')?.dataset?.index;
930
+ let clickedRow = null;
931
+ if (rowIndex) {
932
+ clickedRow = stores.viewStore.find(row => row[ROW_INDEX] === +rowIndex)?.[ROW_DATA_INDEX];
933
+ }
934
+ if (clickedRow) {
935
+ selectInfo.contextmenuInfo = [clickedRow];
936
+ setContextMenu(e);
903
937
  }
904
-
905
- contextInfo.contextMenuItems = menuItems;
906
938
  };
939
+ /**
940
+ * 컬럼 기능을 수행하는 Contextmenu 를 생성한다.
941
+ *
942
+ * @param {object} event - 이벤트 객체
943
+ * @param {object} column - 컬럼 정보
944
+ */
907
945
  const onColumnContextMenu = (event, column) => {
908
946
  if (event.target.className === 'column-name') {
909
947
  const sortable = column.sortable === undefined ? true : column.sortable;
910
948
  const filterable = filterInfo.isFiltering
911
- && column.filterable === undefined ? true : column.filterable;
949
+ && column.filterable === undefined ? true : column.filterable;
912
950
  contextInfo.columnMenuItems = [
913
951
  {
914
952
  text: 'Ascending',
@@ -952,23 +990,6 @@ export const contextMenuEvent = (params) => {
952
990
  ];
953
991
  }
954
992
  };
955
- /**
956
- * 마우스 우클릭 이벤트를 처리한다.
957
- *
958
- * @param {object} event - 이벤트 객체
959
- */
960
- const onContextMenu = (event) => {
961
- const target = event.target;
962
- const rowIndex = target.closest('.row')?.dataset?.index;
963
- let clickedRow = null;
964
- if (rowIndex) {
965
- clickedRow = stores.viewStore.find(row => row[ROW_INDEX] === +rowIndex)?.[ROW_DATA_INDEX];
966
- }
967
- if (clickedRow) {
968
- selectInfo.contextmenuInfo = [clickedRow];
969
- // setContextMenu();
970
- }
971
- };
972
993
  return {
973
994
  setContextMenu,
974
995
  onContextMenu,