evui 3.4.65 → 3.4.67

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.65",
3
+ "version": "3.4.67",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -92,9 +92,11 @@ const modules = {
92
92
 
93
93
  if (maxTipOpt.use && maxArgs) {
94
94
  if (tooltipValueFormatter) {
95
- maxArgs.text = isHorizontal
96
- ? tooltipValueFormatter({ x: maxArgs.value })
97
- : tooltipValueFormatter({ y: maxArgs.value });
95
+ maxArgs.text = tooltipValueFormatter({
96
+ seriesId: seriesInfo.sId,
97
+ x: isHorizontal ? maxArgs.value : undefined,
98
+ y: !isHorizontal ? maxArgs.value : undefined,
99
+ });
98
100
  } else {
99
101
  maxArgs.text = numberWithComma(maxArgs.value);
100
102
  }
@@ -473,8 +473,6 @@
473
473
  'non-border': !!borderStyle,
474
474
  }"
475
475
  :style="{
476
- position: 'sticky',
477
- right: 0,
478
476
  width: '30px',
479
477
  height: `${rowHeight}px`,
480
478
  'min-width': '30px',
@@ -486,6 +484,10 @@
486
484
  <span
487
485
  class="row-contextmenu__btn"
488
486
  :disabled="row[ROW_DISABLED_INDEX]"
487
+ :style="{
488
+ position: 'absolute',
489
+ right: 0,
490
+ }"
489
491
  @click="onContextMenu($event)"
490
492
  >
491
493
  <slot name="contextmenuIcon"></slot>
@@ -496,6 +498,10 @@
496
498
  icon="ev-icon-warning2"
497
499
  class="row-contextmenu__btn"
498
500
  :disabled="row[ROW_DISABLED_INDEX]"
501
+ :style="{
502
+ position: 'absolute',
503
+ right: 0,
504
+ }"
499
505
  @click="onContextMenu($event)"
500
506
  />
501
507
  </template>
@@ -148,6 +148,7 @@
148
148
  }
149
149
  }
150
150
  .row {
151
+ position: relative;
151
152
  color: inherit;
152
153
  white-space: nowrap;
153
154
 
@@ -88,6 +88,52 @@
88
88
  @click.prevent="columnMenu.show"
89
89
  >
90
90
  {{ column.caption }}
91
+ <!-- Sort Icon -->
92
+ <span @click.stop="onSort(column)">
93
+ <template v-if="!!$slots.sortIcon">
94
+ <span
95
+ v-if="column.sortable === undefined ? true : column.sortable"
96
+ class="column-sort__icon column-sort__icon--basic"
97
+ :style="{
98
+ height: `${rowHeight}px`,
99
+ 'line-height': `${rowHeight}px`,
100
+ }"
101
+ >
102
+ <slot name="sortIcon" />
103
+ </span>
104
+ <span
105
+ v-if="isSortedColumn(column)"
106
+ :class="sortIconClass(column)"
107
+ :style="{
108
+ height: `${rowHeight}px`,
109
+ 'line-height': `${rowHeight}px`,
110
+ }"
111
+ >
112
+ <slot :name="`sortIcon_${sortOrder}`" />
113
+ </span>
114
+ </template>
115
+ <template v-else>
116
+ <grid-sort-button
117
+ v-if="column.sortable === undefined ? true : column.sortable"
118
+ class="column-sort__icon column-sort__icon--basic"
119
+ :icon="'basic'"
120
+ :style="{
121
+ height: `${rowHeight}px`,
122
+ 'line-height': `${rowHeight}px`,
123
+ }"
124
+ />
125
+ <grid-sort-button
126
+ v-if="isSortedColumn(column)"
127
+ :class="sortIconClass(column)"
128
+ :icon="sortOrder"
129
+ :style="{
130
+ height: `${rowHeight}px`,
131
+ 'line-height': `${rowHeight}px`,
132
+ visibility: !!sortOrder ? column.hidden : true,
133
+ }"
134
+ />
135
+ </template>
136
+ </span>
91
137
  </span>
92
138
  </template>
93
139
  <!-- Column Resize -->
@@ -131,7 +177,7 @@
131
177
  <tbody>
132
178
  <tree-grid-node
133
179
  v-for="(node, idx) in viewStore"
134
- :key="node['id'] || idx"
180
+ :key="idx"
135
181
  :selected-data="selectedRow"
136
182
  :node-data="node"
137
183
  :use-checkbox="useCheckbox"
@@ -266,11 +312,13 @@ import {
266
312
  onMounted,
267
313
  onUnmounted,
268
314
  } from 'vue';
315
+ import { cloneDeep } from 'lodash-es';
269
316
  import TreeGridNode from './TreeGridNode';
270
317
  import Toolbar from './TreeGridToolbar';
271
318
  import GridPagination from '../grid/GridPagination';
272
319
  import GridSummary from '../grid/GridSummary';
273
320
  import ColumnSetting from '../grid/GridColumnSetting.vue';
321
+ import GridSortButton from '../grid/icon/icon-sort-button';
274
322
  import GridOptionButton from '../grid/icon/icon-option-button.vue';
275
323
  import {
276
324
  commonFunctions,
@@ -283,6 +331,7 @@ import {
283
331
  filterEvent,
284
332
  pagingEvent,
285
333
  getUpdatedColumns,
334
+ sortEvent,
286
335
  } from './uses';
287
336
  import {
288
337
  columnSettingEvent,
@@ -296,6 +345,7 @@ export default {
296
345
  GridPagination,
297
346
  GridSummary,
298
347
  ColumnSetting,
348
+ GridSortButton,
299
349
  GridOptionButton,
300
350
  },
301
351
  props: {
@@ -344,6 +394,7 @@ export default {
344
394
  'check-row': null,
345
395
  'check-all': null,
346
396
  'page-change': null,
397
+ 'sort-column': null,
347
398
  'resize-column': ({ column, columns }) => ({ column, columns }),
348
399
  'change-column-status': ({ columns }) => ({ columns }),
349
400
  'change-column-info': ({ type, columns }) => ({ type, columns }),
@@ -389,6 +440,7 @@ export default {
389
440
  viewStore: [],
390
441
  filterStore: [],
391
442
  pagingStore: [],
443
+ originStore: [],
392
444
  showTreeStore: computed(() => stores.treeStore.filter(item => item.show)),
393
445
  searchStore: computed(() => stores.treeStore.filter(item => item.isFilter)),
394
446
  store: computed(() => (filterInfo.isSearch ? stores.searchStore : stores.treeStore)),
@@ -398,6 +450,9 @@ export default {
398
450
  index,
399
451
  hiddenDisplay: false,
400
452
  ...column,
453
+ sortOption: {
454
+ sortType: column?.sortOption?.sortType || 'init',
455
+ },
401
456
  }))),
402
457
  orderedColumns: computed(() => (stores.filteredColumns.length
403
458
  ? stores.filteredColumns : stores.originColumns)),
@@ -456,6 +511,13 @@ export default {
456
511
  }),
457
512
  multiple: computed(() => props.option?.useSelection?.multiple ?? false),
458
513
  });
514
+ const sortInfo = reactive({
515
+ isSorting: false,
516
+ sortField: '',
517
+ sortOrder: '',
518
+ sortColumn: {},
519
+ sortFunction: props.option.customAscFunction ?? {},
520
+ });
459
521
  const contextInfo = reactive({
460
522
  menu: null,
461
523
  contextMenuItems: [],
@@ -512,6 +574,7 @@ export default {
512
574
  } = pagingEvent({
513
575
  stores,
514
576
  pageInfo,
577
+ sortInfo,
515
578
  filterInfo,
516
579
  elementInfo,
517
580
  clearCheckInfo,
@@ -570,6 +633,10 @@ export default {
570
633
  handleExpand,
571
634
  } = treeEvent({ stores, onResize });
572
635
 
636
+ const {
637
+ onSort,
638
+ } = sortEvent({ sortInfo, stores, updatePagingInfo, setTreeNodeStore, onResize });
639
+
573
640
  const {
574
641
  onSearch,
575
642
  } = filterEvent({
@@ -608,6 +675,7 @@ export default {
608
675
  useGridSetting,
609
676
  columnSettingInfo,
610
677
  setColumnHidden,
678
+ onSort,
611
679
  });
612
680
 
613
681
  const setGridSetting = (e) => {
@@ -634,6 +702,7 @@ export default {
634
702
 
635
703
  onMounted(() => {
636
704
  stores.treeStore = setTreeNodeStore();
705
+ stores.originStore = cloneDeep(stores.treeStore);
637
706
  document.addEventListener('wheel', onMouseWheel, { capture: false });
638
707
  document.addEventListener('scroll', onMouseWheel, { capture: true });
639
708
  });
@@ -854,6 +923,15 @@ export default {
854
923
  });
855
924
  },
856
925
  );
926
+
927
+ const isSortedColumn = column => sortInfo.sortField === column.field;
928
+
929
+ const sortIconClass = () => ({
930
+ 'column-sort__icon': true,
931
+ 'column-sort__icon--asc': sortInfo.sortOrder === 'asc',
932
+ 'column-sort__icon--desc': sortInfo.sortOrder === 'desc',
933
+ });
934
+
857
935
  const gridStyle = computed(() => ({
858
936
  width: resizeInfo.gridWidth,
859
937
  height: resizeInfo.gridHeight,
@@ -929,6 +1007,7 @@ export default {
929
1007
  ...toRefs(resizeInfo),
930
1008
  ...toRefs(selectInfo),
931
1009
  ...toRefs(checkInfo),
1010
+ ...toRefs(sortInfo),
932
1011
  ...toRefs(contextInfo),
933
1012
  ...toRefs(pageInfo),
934
1013
  ...toRefs(columnSettingInfo),
@@ -958,6 +1037,9 @@ export default {
958
1037
  setGridSetting,
959
1038
  onApplyColumn,
960
1039
  onColumnContextMenu,
1040
+ onSort,
1041
+ isSortedColumn,
1042
+ sortIconClass,
961
1043
  };
962
1044
  },
963
1045
  };
@@ -79,6 +79,26 @@
79
79
  align-items: center;
80
80
  }
81
81
  }
82
+ .column-sort__icon {
83
+ position: absolute;
84
+ top: 50%;
85
+ right: 0;
86
+ width: 24px;
87
+ height: 24px;
88
+ background-size: contain;
89
+ transform: translateY(-50%);
90
+ &:hover {
91
+ cursor: pointer;
92
+ }
93
+ &--basic {
94
+ visibility: hidden;
95
+ }
96
+ }
97
+ :hover {
98
+ .column-sort__icon--basic {
99
+ visibility: visible;
100
+ }
101
+ }
82
102
  }
83
103
 
84
104
  .column-name {
@@ -1,5 +1,6 @@
1
1
  import { getCurrentInstance, nextTick } from 'vue';
2
2
  import { numberWithComma } from '@/common/utils';
3
+ import { cloneDeep } from 'lodash-es';
3
4
 
4
5
  export const commonFunctions = (params) => {
5
6
  const { props } = getCurrentInstance();
@@ -606,6 +607,7 @@ export const contextMenuEvent = (params) => {
606
607
  useGridSetting,
607
608
  columnSettingInfo,
608
609
  setColumnHidden,
610
+ onSort,
609
611
  } = params;
610
612
  /**
611
613
  * 컨텍스트 메뉴를 설정한다.
@@ -634,7 +636,8 @@ export const contextMenuEvent = (params) => {
634
636
  contextInfo.contextMenuItems = menuItems;
635
637
  };
636
638
  const onColumnContextMenu = (event, column) => {
637
- if (event.target.className.includes('column-name--click')) {
639
+ if (event.target.className.includes('column-name')) {
640
+ const sortable = column.sortable === undefined ? true : column.sortable;
638
641
  contextInfo.columnMenuItems = [
639
642
  {
640
643
  text: contextInfo.columnMenuTextInfo?.hide ?? 'Hide',
@@ -651,6 +654,20 @@ export const contextMenuEvent = (params) => {
651
654
  });
652
655
  },
653
656
  },
657
+ {
658
+ text: contextInfo.columnMenuTextInfo?.ascending ?? 'Ascending',
659
+ iconClass: 'ev-icon-allow2-up',
660
+ disabled: !sortable,
661
+ hidden: contextInfo.hiddenColumnMenuItem?.ascending,
662
+ click: () => onSort(column, 'asc'),
663
+ },
664
+ {
665
+ text: contextInfo.columnMenuTextInfo?.descending ?? 'Descending',
666
+ iconClass: 'ev-icon-allow2-down',
667
+ disabled: !sortable,
668
+ hidden: contextInfo.hiddenColumnMenuItem?.descending,
669
+ click: () => onSort(column, 'desc'),
670
+ },
654
671
  ];
655
672
  } else {
656
673
  contextInfo.columnMenuItems.length = 0;
@@ -922,6 +939,7 @@ export const pagingEvent = (params) => {
922
939
  const {
923
940
  stores,
924
941
  pageInfo,
942
+ sortInfo,
925
943
  filterInfo,
926
944
  elementInfo,
927
945
  clearCheckInfo,
@@ -941,6 +959,10 @@ export const pagingEvent = (params) => {
941
959
  total: pageInfo.pageTotal,
942
960
  perPage: pageInfo.perPage,
943
961
  },
962
+ sortInfo: {
963
+ field: sortInfo.sortField,
964
+ order: sortInfo.sortOrder,
965
+ },
944
966
  searchInfo: {
945
967
  searchWord: filterInfo.searchWord,
946
968
  searchColumns: stores.orderedColumns
@@ -971,3 +993,141 @@ export const pagingEvent = (params) => {
971
993
  };
972
994
  return { getPagingData, updatePagingInfo, changePage };
973
995
  };
996
+
997
+ export const sortEvent = ({ sortInfo, stores, updatePagingInfo, onResize }) => {
998
+ const { emit } = getCurrentInstance();
999
+
1000
+ const getDefaultSortType = (includeInit = true) => (includeInit ? ['asc', 'desc', 'init'] : ['asc', 'desc']);
1001
+
1002
+ function OrderQueue() {
1003
+ this.orders = getDefaultSortType();
1004
+ this.dequeue = () => this.orders.shift();
1005
+ this.enqueue = o => this.orders.push(o);
1006
+ }
1007
+
1008
+ const setSortOptionToOrderedColumns = (column, sortType = 'init') => {
1009
+ stores.orderedColumns.forEach((orderedColumn) => {
1010
+ if (orderedColumn.index === column?.index && sortType) {
1011
+ orderedColumn.sortOption = { sortType };
1012
+ } else {
1013
+ orderedColumn.sortOption = { sortType: 'init' };
1014
+ }
1015
+ });
1016
+ };
1017
+
1018
+ const initializeHiddenColumnsSortType = () => {
1019
+ const hiddenColumns = stores.originColumns.filter(col => col.hiddenDisplay || col.hide);
1020
+ if (hiddenColumns.length) {
1021
+ hiddenColumns.forEach((col) => {
1022
+ col.sortOption = { sortType: 'init' };
1023
+ });
1024
+ }
1025
+ };
1026
+
1027
+ const order = new OrderQueue();
1028
+
1029
+ const setSortInfo = (column, emitTriggered = true) => {
1030
+ const { sortType } = column?.sortOption || {};
1031
+ sortInfo.sortColumn = column;
1032
+ sortInfo.sortField = column?.field;
1033
+ sortInfo.sortOrder = sortType;
1034
+ sortInfo.isSorting = !!(sortType);
1035
+
1036
+ if (emitTriggered) {
1037
+ setSortOptionToOrderedColumns(column, sortType);
1038
+
1039
+ emit('change-column-info', {
1040
+ type: 'sort',
1041
+ columns: getUpdatedColumns(stores),
1042
+ });
1043
+ }
1044
+ };
1045
+
1046
+ const compareValues = (nodeA, nodeB) => {
1047
+ const valueA = nodeA.data[sortInfo.sortField];
1048
+ const valueB = nodeB.data[sortInfo.sortField];
1049
+
1050
+ if (valueA === valueB) return 0;
1051
+
1052
+ const isAscending = sortInfo.sortOrder === 'asc';
1053
+
1054
+ if (isAscending) return valueA > valueB ? 1 : -1;
1055
+
1056
+ return valueA < valueB ? 1 : -1;
1057
+ };
1058
+
1059
+ const sortTree = (nodes, depth = 0) => {
1060
+ const groupedNodes = {};
1061
+
1062
+ nodes.forEach((node) => {
1063
+ const nodeDepth = node.level || depth;
1064
+ if (!groupedNodes[nodeDepth]) {
1065
+ groupedNodes[nodeDepth] = [];
1066
+ }
1067
+ groupedNodes[nodeDepth].push(node);
1068
+ });
1069
+
1070
+ Object.keys(groupedNodes).forEach((key) => {
1071
+ groupedNodes[key].sort(compareValues);
1072
+ });
1073
+
1074
+ nodes.length = 0;
1075
+ Object.values(groupedNodes).forEach((group) => {
1076
+ group.forEach((node) => {
1077
+ nodes.push(node);
1078
+ if (node.hasChild) {
1079
+ sortTree(node.children, node.level + 1);
1080
+ }
1081
+ });
1082
+ });
1083
+ };
1084
+
1085
+ const onSort = (column, sortOrder) => {
1086
+ const sortable = column.sortable === undefined ? true : column.sortable;
1087
+ if (sortable) {
1088
+ sortInfo.sortColumn = column;
1089
+ if (sortInfo.sortField !== column?.field) {
1090
+ order.orders = getDefaultSortType();
1091
+ sortInfo.sortField = column?.field;
1092
+ }
1093
+ if (sortOrder) {
1094
+ order.orders = getDefaultSortType();
1095
+ if (sortOrder === 'desc') {
1096
+ sortInfo.sortOrder = order.dequeue();
1097
+ order.enqueue(sortInfo.sortOrder);
1098
+ }
1099
+ }
1100
+ sortInfo.sortOrder = order.dequeue();
1101
+ order.enqueue(sortInfo.sortOrder);
1102
+
1103
+ initializeHiddenColumnsSortType();
1104
+ setSortOptionToOrderedColumns(column, sortInfo.sortOrder);
1105
+
1106
+ const updatedColumInfo = getUpdatedColumns(stores);
1107
+ emit('sort-column', {
1108
+ field: sortInfo.sortField,
1109
+ order: sortInfo.sortOrder,
1110
+ column: sortInfo.sortColumn,
1111
+ columns: updatedColumInfo,
1112
+ });
1113
+
1114
+ emit('change-column-info', {
1115
+ type: 'sort',
1116
+ columns: updatedColumInfo,
1117
+ });
1118
+
1119
+ if (sortInfo.sortOrder === 'init') {
1120
+ stores.treeStore = cloneDeep(stores.originStore);
1121
+ stores.viewStore = stores.treeStore;
1122
+ onResize();
1123
+ sortInfo.isSorting = false;
1124
+ } else {
1125
+ sortTree(stores.treeRows);
1126
+ sortInfo.isSorting = true;
1127
+ }
1128
+
1129
+ updatePagingInfo({ onSort: true });
1130
+ }
1131
+ };
1132
+ return { onSort, setSortInfo };
1133
+ };