evui 3.4.8 → 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.
@@ -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
  * 필터 조건에 따라 문자열을 확인한다.
@@ -671,20 +676,23 @@ export const filterEvent = (params) => {
671
676
  const stringFilter = (item, condition) => {
672
677
  const comparison = condition.comparison;
673
678
  const conditionValue = condition.value;
674
- const value = `${item[ROW_DATA_INDEX][condition.index]}`;
679
+ let value = item[ROW_DATA_INDEX][condition.index];
680
+ if (value || value === 0) {
681
+ value = `${item[ROW_DATA_INDEX][condition.index]}`;
682
+ }
675
683
  let result;
676
684
  if (comparison === '=') {
677
685
  result = conditionValue.toLowerCase() === value.toLowerCase();
678
686
  } else if (comparison === '!=') {
679
687
  result = conditionValue.toLowerCase() !== value.toLowerCase();
680
688
  } else if (comparison === '%s%') {
681
- result = findLike(`%${conditionValue}%`, value);
689
+ result = findLike(conditionValue, value);
682
690
  } else if (comparison === 'notLike') {
683
- result = !findLike(`%${conditionValue}%`, value);
691
+ result = !findLike(conditionValue, value);
684
692
  } else if (comparison === 's%') {
685
- result = findLike(`${conditionValue}`, value, 'start');
693
+ result = findLike(conditionValue, value, 'start');
686
694
  } else if (comparison === '%s') {
687
- result = findLike(`${conditionValue}`, value, 'end');
695
+ result = findLike(conditionValue, value, 'end');
688
696
  } else if (comparison === 'isEmpty') {
689
697
  result = value === undefined || value === null || value === '';
690
698
  } else if (comparison === 'isNotEmpty') {
@@ -723,13 +731,25 @@ export const filterEvent = (params) => {
723
731
  } else if (comparison === '!=') {
724
732
  result = value !== conditionValue;
725
733
  } else if (comparison === 'isEmpty') {
726
- result = value === undefined || value === null;
734
+ result = value === undefined || value === null || isNaN(value);
727
735
  } else if (comparison === 'isNotEmpty') {
728
736
  result = !!value;
729
737
  }
730
738
 
731
739
  return result;
732
740
  };
741
+ const booleanFilter = (item, condition) => {
742
+ const comparison = condition.comparison;
743
+ const conditionValue = condition.value;
744
+ const value = `${item[ROW_DATA_INDEX][condition.index]}`;
745
+ let result;
746
+
747
+ if (comparison === '=') {
748
+ result = value === conditionValue;
749
+ }
750
+
751
+ return result;
752
+ };
733
753
  /**
734
754
  * 필터 조건이 적용된 데이터를 반환한다.
735
755
  *
@@ -739,60 +759,67 @@ export const filterEvent = (params) => {
739
759
  * @returns {boolean} 확인 결과
740
760
  */
741
761
  const getFilteringData = (data, columnType, condition) => {
742
- const filterFn = columnType === 'string' || columnType === 'stringNumber'
762
+ let filterFn = columnType === 'string' || columnType === 'stringNumber'
743
763
  ? stringFilter : numberFilter;
764
+ if (columnType === 'boolean') {
765
+ filterFn = booleanFilter;
766
+ }
744
767
  return data.filter(row => filterFn(row, condition, columnType)) || [];
745
768
  };
746
769
  /**
747
770
  * 전체 데이터에서 설정된 필터 적용 후 결과를 filterStore 에 저장한다.
748
771
  */
749
772
  const setFilter = () => {
750
- let filterStore = [];
751
- let isApply = false;
752
-
753
773
  const filteringItemsByColumn = filterInfo.filteringItemsByColumn;
754
774
  const fields = Object.keys(filteringItemsByColumn);
755
775
  const originStore = stores.originStore;
776
+ let filterStore = [];
777
+ let filteredOnce = false;
756
778
 
757
779
  fields.forEach((field) => {
758
780
  const filters = filteringItemsByColumn[field];
759
781
  const index = getColumnIndex(field);
760
782
  const columnType = props.columns[index].type;
761
783
 
762
- filters.forEach((filterItem) => {
763
- isApply = true;
764
- if (!filterStore.length && Object.keys(filteringItemsByColumn).length < 2) {
784
+ filters.forEach((item, ix) => {
785
+ if (!filterStore.length && !filteredOnce) {
765
786
  filterStore = getFilteringData(originStore, columnType, {
766
- ...filterItem,
787
+ ...item,
767
788
  index,
768
789
  });
769
- } else if (filterItem.operator === 'or' || filterInfo.columnOperator === 'or') {
790
+ } else if (ix === 0 && filterInfo.columnOperator === 'or') {
770
791
  filterStore.push(...getFilteringData(originStore, columnType, {
771
- ...filterItem,
792
+ ...item,
793
+ index,
794
+ }));
795
+ } else if (item.operator === 'or') {
796
+ filterStore.push(...getFilteringData(originStore, columnType, {
797
+ ...item,
772
798
  index,
773
799
  }));
774
800
  } else {
775
801
  filterStore = getFilteringData(filterStore, columnType, {
776
- ...filterItem,
802
+ ...item,
777
803
  index,
778
804
  });
779
805
  }
806
+ filteredOnce = true;
780
807
  });
781
808
  });
782
809
 
783
- if (!isApply) {
810
+ if (!filteredOnce) {
784
811
  stores.filterStore = originStore;
785
812
  } else {
786
813
  stores.filterStore = uniqBy(filterStore, JSON.stringify);
787
814
  }
788
815
  };
789
816
 
790
- let timer = null;
817
+ let searchTimer = null;
791
818
  const onSearch = (searchWord) => {
792
- if (timer) {
793
- clearTimeout(timer);
819
+ if (searchTimer) {
820
+ clearTimeout(searchTimer);
794
821
  }
795
- timer = setTimeout(() => {
822
+ searchTimer = setTimeout(() => {
796
823
  filterInfo.isSearch = false;
797
824
  filterInfo.searchWord = searchWord;
798
825
  if (searchWord) {
@@ -861,35 +888,65 @@ export const contextMenuEvent = (params) => {
861
888
  /**
862
889
  * 컨텍스트 메뉴를 설정한다.
863
890
  *
864
- * @param {boolean} useCustom - 사용자 지정 메뉴 사용 유무
891
+ * @param {object} event - 이벤트 객체
865
892
  */
866
- const setContextMenu = (useCustom = true) => {
893
+ let contextmenuTimer = null;
894
+ const setContextMenu = (e) => {
895
+ if (contextmenuTimer) {
896
+ clearTimeout(contextmenuTimer);
897
+ }
867
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
+ }
868
907
 
869
- if (useCustom && contextInfo.customContextMenu.length) {
870
- const customItems = contextInfo.customContextMenu.map(
871
- (item) => {
872
- const menuItem = item;
873
- if (menuItem.validate) {
874
- menuItem.disabled = !menuItem.validate(menuItem.itemId, selectInfo.selectedRow);
875
- }
908
+ menuItem.selectedRow = selectInfo.selectedRow ?? [];
909
+ menuItem.contextmenuInfo = selectInfo.contextmenuInfo ?? [];
876
910
 
877
- menuItem.selectedRow = selectInfo.selectedRow ?? [];
878
- menuItem.contextmenuInfo = selectInfo.contextmenuInfo ?? [];
911
+ return menuItem;
912
+ });
879
913
 
880
- return menuItem;
881
- });
914
+ menuItems.push(...customItems);
915
+ }
882
916
 
883
- 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);
884
937
  }
885
-
886
- contextInfo.contextMenuItems = menuItems;
887
938
  };
939
+ /**
940
+ * 컬럼 기능을 수행하는 Contextmenu 를 생성한다.
941
+ *
942
+ * @param {object} event - 이벤트 객체
943
+ * @param {object} column - 컬럼 정보
944
+ */
888
945
  const onColumnContextMenu = (event, column) => {
889
946
  if (event.target.className === 'column-name') {
890
947
  const sortable = column.sortable === undefined ? true : column.sortable;
891
948
  const filterable = filterInfo.isFiltering
892
- && column.filterable === undefined ? true : column.filterable;
949
+ && column.filterable === undefined ? true : column.filterable;
893
950
  contextInfo.columnMenuItems = [
894
951
  {
895
952
  text: 'Ascending',
@@ -933,23 +990,6 @@ export const contextMenuEvent = (params) => {
933
990
  ];
934
991
  }
935
992
  };
936
- /**
937
- * 마우스 우클릭 이벤트를 처리한다.
938
- *
939
- * @param {object} event - 이벤트 객체
940
- */
941
- const onContextMenu = (event) => {
942
- const target = event.target;
943
- const rowIndex = target.closest('.row')?.dataset?.index;
944
- let clickedRow = null;
945
- if (rowIndex) {
946
- clickedRow = stores.viewStore.find(row => row[ROW_INDEX] === +rowIndex)?.[ROW_DATA_INDEX];
947
- }
948
- if (clickedRow) {
949
- selectInfo.contextmenuInfo = [clickedRow];
950
- // setContextMenu();
951
- }
952
- };
953
993
  return {
954
994
  setContextMenu,
955
995
  onContextMenu,
@@ -54,7 +54,10 @@
54
54
  <li
55
55
  v-if="useCheckbox.use"
56
56
  :class="headerCheckboxClass"
57
- :style="`width: ${minWidth}px;`"
57
+ :style="{
58
+ width: `${minWidth}px`,
59
+ 'border-right': '1px solid #CFCFCF',
60
+ }"
58
61
  >
59
62
  <ev-checkbox
60
63
  v-if="isHeaderCheckbox"
@@ -92,6 +95,21 @@
92
95
  />
93
96
  </li>
94
97
  </template>
98
+ <!-- Row Contextmenu Column -->
99
+ <li
100
+ v-if="$props.option.customContextMenu?.length"
101
+ :class="{
102
+ column: true,
103
+ 'non-border': !!borderStyle,
104
+ }"
105
+ :style="{
106
+ width: '30px',
107
+ 'min-width': '30px',
108
+ 'margin-right': (hasVerticalScrollBar || hasHorizontalScrollBar)
109
+ ? `${scrollWidth}px` : '0px',
110
+ }"
111
+ >
112
+ </li>
95
113
  </ul>
96
114
  </div>
97
115
  <!-- Body -->
@@ -120,6 +138,8 @@
120
138
  :collapse-icon="option.collapseIcon"
121
139
  :parent-icon="option.parentIcon"
122
140
  :child-icon="option.childIcon"
141
+ :custom-context-menu="customContextMenu"
142
+ :menu-ref="menu"
123
143
  :is-resize="isResize"
124
144
  :row-height="rowHeight"
125
145
  :min-width="minWidth"
@@ -129,6 +149,7 @@
129
149
  @expand-tree-data="handleExpand"
130
150
  @click-tree-data="onRowClick"
131
151
  @dbl-click-tree-data="onRowDblClick"
152
+ @context-menu="onContextMenu"
132
153
  >
133
154
  <!-- Cell Renderer -->
134
155
  <template
@@ -152,6 +173,14 @@
152
173
  </span>
153
174
  </template>
154
175
  </template>
176
+ <template
177
+ v-if="$slots.contextmenuIcon"
178
+ #contextmenuIconNode
179
+ >
180
+ <slot
181
+ name="contextmenuIcon"
182
+ />
183
+ </template>
155
184
  </tree-grid-node>
156
185
  <tr v-if="!viewStore.length">
157
186
  <td class="is-empty">No records</td>
@@ -469,6 +498,7 @@ export default {
469
498
  isRenderer,
470
499
  updateVScroll,
471
500
  updateHScroll,
501
+ contextInfo,
472
502
  });
473
503
 
474
504
  const {
@@ -768,6 +798,7 @@ export default {
768
798
  'margin-right': (stores.orderedColumns.length - 1 === index
769
799
  && scrollInfo.hasVerticalScrollBar
770
800
  && scrollInfo.hasHorizontalScrollBar) ? `${resizeInfo.scrollWidth}px` : '0px',
801
+ 'border-right': stores.orderedColumns.length - 1 === index ? 'none' : '1px solid #CFCFCF',
771
802
  };
772
803
  };
773
804
  const getSlotName = column => `${column}Node`;
@@ -8,9 +8,11 @@
8
8
  <td
9
9
  v-if="useCheckbox.use"
10
10
  :class="checkboxClass"
11
- :style="`
12
- width: ${minWidth}px;
13
- height: ${rowHeight}px;`"
11
+ :style="{
12
+ width: `${minWidth}px`,
13
+ height: `${rowHeight}px`,
14
+ 'border-right': '1px solid #CFCFCF',
15
+ }"
14
16
  >
15
17
  <ev-checkbox
16
18
  v-model="node.checked"
@@ -28,9 +30,9 @@
28
30
  :data-name="column.field"
29
31
  :data-index="node.index"
30
32
  :class="getColumnClass(column, cellIndex)"
31
- :style="getColumnStyle(column)"
33
+ :style="getColumnStyle(column, cellIndex)"
32
34
  >
33
- <div class="td-content">
35
+ <div class="td-content__wrapper">
34
36
  <!--Level Depth-->
35
37
  <span
36
38
  v-if="cellIndex === expandColumnIdx"
@@ -76,7 +78,7 @@
76
78
  <i></i>
77
79
  </span>
78
80
  </span>
79
- <div class="slot-wrapper">
81
+ <div class="td-content">
80
82
  <!-- cell renderer -->
81
83
  <template v-if="!!$slots[column.field + 'Node']">
82
84
  <slot
@@ -90,14 +92,52 @@
90
92
  </div>
91
93
  </td>
92
94
  </template>
95
+ <!-- Row Contextmenu Button -->
96
+ <td
97
+ v-if="customContextMenu?.length"
98
+ :class="{
99
+ 'row-contextmenu': true,
100
+ 'non-border': !!borderStyle,
101
+ }"
102
+ :style="{
103
+ position: 'sticky',
104
+ right: 0,
105
+ width: '30px',
106
+ height: `${rowHeight}px`,
107
+ 'min-width': '30px',
108
+ 'line-height': `${rowHeight}px`,
109
+ }"
110
+ >
111
+ <template v-if="$slots.contextmenuIconNode">
112
+ <span
113
+ class="row-contextmenu__btn"
114
+ @click="onContextMenu($event)"
115
+ @click.prevent="menuRef.show"
116
+ >
117
+ <slot
118
+ name="contextmenuIconNode"
119
+ />
120
+ </span>
121
+ </template>
122
+ <template v-else>
123
+ <grid-option-button
124
+ icon="ev-icon-warning2"
125
+ class="row-contextmenu__btn"
126
+ @click="onContextMenu($event)"
127
+ @click.prevent="menuRef.show"
128
+ />
129
+ </template>
130
+ </td>
93
131
  </tr>
94
132
  </template>
95
133
 
96
134
  <script>
97
135
  import { computed } from 'vue';
136
+ import GridOptionButton from '@/components/grid/grid.optionButton.vue';
98
137
 
99
138
  export default {
100
139
  name: 'TreeGridNode',
140
+ components: { GridOptionButton },
101
141
  props: {
102
142
  dataIndex: {
103
143
  type: Number,
@@ -135,6 +175,14 @@ export default {
135
175
  type: String,
136
176
  default: '',
137
177
  },
178
+ customContextMenu: {
179
+ type: [Array],
180
+ default: () => [],
181
+ },
182
+ menuRef: {
183
+ type: Object,
184
+ default: null,
185
+ },
138
186
  rowHeight: {
139
187
  type: Number,
140
188
  default: 35,
@@ -157,6 +205,7 @@ export default {
157
205
  'expand-tree-data': null,
158
206
  'click-tree-data': null,
159
207
  'dbl-click-tree-data': null,
208
+ 'context-menu': null,
160
209
  },
161
210
  setup(props, { emit }) {
162
211
  const onCheck = ($event, data) => {
@@ -171,6 +220,9 @@ export default {
171
220
  const onDblClick = ($event, data) => {
172
221
  emit('dbl-click-tree-data', $event, data);
173
222
  };
223
+ const onContextMenu = ($event) => {
224
+ emit('context-menu', $event);
225
+ };
174
226
  const expandIconClasses = (node) => {
175
227
  const expandIcon = props.expandIcon ? props.expandIcon : '';
176
228
  const collapseIcon = props.expandIcon ? props.collapseIcon : '';
@@ -205,11 +257,13 @@ export default {
205
257
  [column.align]: column.align,
206
258
  'non-border': !!props.borderStyle,
207
259
  });
208
- const getColumnStyle = column => ({
260
+ const getColumnStyle = (column, cellIndex) => ({
209
261
  width: `${column.width}px`,
210
262
  height: `${props.rowHeight}px`,
211
263
  'line-height': `${props.rowHeight}px`,
212
264
  'min-width': `${props.minWidth}px`,
265
+ 'border-right': props.orderedColumns.length - 1 === cellIndex
266
+ ? 'none' : '1px solid #CFCFCF',
213
267
  });
214
268
  const getDepthStyle = (nodeLevel) => {
215
269
  const depthSize = nodeLevel * 13;
@@ -228,6 +282,7 @@ export default {
228
282
  onExpand,
229
283
  onClick,
230
284
  onDblClick,
285
+ onContextMenu,
231
286
  expandIconClasses,
232
287
  getRowClass,
233
288
  getColumnClass,
@@ -279,7 +334,7 @@ export default {
279
334
  background: url('./tree_icon.png') no-repeat -14px -35px;
280
335
  }
281
336
  }
282
- .slot-wrapper {
337
+ .td-content {
283
338
  position: relative;
284
339
  flex: 1;
285
340
  }
@@ -28,6 +28,10 @@
28
28
  border-bottom: 1px solid evThemed('grid-bottom-border');
29
29
  }
30
30
  }
31
+ .row-contextmenu__btn {
32
+ display: none;
33
+ vertical-align: middle;
34
+ }
31
35
  }
32
36
 
33
37
  .column-list {
@@ -134,6 +138,15 @@
134
138
  background: #5AB7FF;
135
139
  color: #FFFFFF;
136
140
  }
141
+ &:hover {
142
+ .row-contextmenu__btn {
143
+ display: grid;
144
+ &:hover {
145
+ cursor: pointer;
146
+ opacity: 0.6;
147
+ }
148
+ }
149
+ }
137
150
  }
138
151
 
139
152
  .cell {
@@ -184,7 +197,7 @@
184
197
  }
185
198
  &.tree-td {
186
199
  text-align: left !important;
187
- .td-content {
200
+ .td-content__wrapper {
188
201
  display: flex;
189
202
  position: relative;
190
203
  align-items: center;
@@ -178,6 +178,7 @@ export const resizeEvent = (params) => {
178
178
  isRenderer,
179
179
  updateVScroll,
180
180
  updateHScroll,
181
+ contextInfo,
181
182
  } = params;
182
183
  /**
183
184
  * 고정 너비, 스크롤 유무 등에 따른 컬럼 너비를 계산한다.
@@ -207,7 +208,7 @@ export const resizeEvent = (params) => {
207
208
  }
208
209
 
209
210
  return acc;
210
- }, { totalWidth: 0, emptyCount: 0 });
211
+ }, { totalWidth: contextInfo.customContextMenu.length ? 30 : 0, emptyCount: 0 });
211
212
 
212
213
  if (resizeInfo.rowHeight * store.length > elHeight - resizeInfo.scrollWidth) {
213
214
  elWidth -= resizeInfo.scrollWidth;
@@ -373,7 +374,8 @@ export const clickEvent = (params) => {
373
374
  let timer = null;
374
375
  const onRowClick = (event, row) => {
375
376
  if (event.target && event.target.parentElement
376
- && event.target.parentElement.classList.contains('row-checkbox-input')) {
377
+ && (event.target.parentElement.classList.contains('row-checkbox-input')
378
+ || event.target.closest('td')?.classList?.contains('row-contextmenu'))) {
377
379
  return false;
378
380
  }
379
381
  clearTimeout(timer);