evui 3.4.64 → 3.4.66

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.64",
3
+ "version": "3.4.66",
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
  }
@@ -248,69 +248,75 @@
248
248
  @dragover="onDragOver"
249
249
  @drop="onDrop"
250
250
  >
251
- <!-- Column Name -->
252
- <span
253
- :title="column.caption"
254
- class="column-name"
255
- @click="onColumnContextMenu($event, column)"
256
- @click.prevent="columnMenu.show"
257
- >
258
- {{ column.caption }}
259
- <!-- Sort Icon -->
260
- <span @click.stop="onSort(column)">
261
- <template v-if="!!$slots.sortIcon">
262
- <span
263
- v-if="column.sortable === undefined ? true : column.sortable"
264
- class="column-sort__icon column-sort__icon--basic"
265
- :style="{
266
- height: `${rowHeight}px`,
267
- 'line-height': `${rowHeight}px`,
268
- }"
269
- >
270
- <slot name="sortIcon" />
271
- </span>
272
- <span
273
- v-if="sortField === column.field"
274
- :class="[{
275
- 'column-sort__icon': true,
276
- 'column-sort__icon--asc': sortOrder === 'asc',
277
- 'column-sort__icon--desc': sortOrder === 'desc',
278
- }]"
279
- :style="{
280
- height: `${rowHeight}px`,
281
- 'line-height': `${rowHeight}px`,
282
- }"
283
- >
284
- <slot :name="`sortIcon_${sortOrder}`" />
285
- </span>
286
- </template>
287
- <template v-else>
288
- <grid-sort-button
289
- v-if="column.sortable === undefined ? true : column.sortable"
290
- class="column-sort__icon column-sort__icon--basic"
291
- :icon="'basic'"
292
- :style="{
293
- height: `${rowHeight}px`,
294
- 'line-height': `${rowHeight}px`,
295
- }"
296
- />
297
- <grid-sort-button
298
- v-if="sortField === column.field"
299
- :class="[{
300
- 'column-sort__icon': true,
301
- 'column-sort__icon--asc': sortOrder === 'asc',
302
- 'column-sort__icon--desc': sortOrder === 'desc',
303
- }]"
304
- :icon="sortOrder"
305
- :style="{
306
- height: `${rowHeight}px`,
307
- 'line-height': `${rowHeight}px`,
308
- visibility: !!sortOrder ? column.hidden : true,
309
- }"
310
- />
311
- </template>
251
+ <!-- Custom Header -->
252
+ <template v-if="column.customHeader && !!$slots.customHeader">
253
+ <slot name="customHeader" />
254
+ </template>
255
+ <template v-else>
256
+ <!-- Column Name -->
257
+ <span
258
+ :title="column.caption"
259
+ class="column-name"
260
+ @click="onColumnContextMenu($event, column)"
261
+ @click.prevent="columnMenu.show"
262
+ >
263
+ {{ column.caption }}
264
+ <!-- Sort Icon -->
265
+ <span @click.stop="onSort(column)">
266
+ <template v-if="!!$slots.sortIcon">
267
+ <span
268
+ v-if="column.sortable === undefined ? true : column.sortable"
269
+ class="column-sort__icon column-sort__icon--basic"
270
+ :style="{
271
+ height: `${rowHeight}px`,
272
+ 'line-height': `${rowHeight}px`,
273
+ }"
274
+ >
275
+ <slot name="sortIcon" />
276
+ </span>
277
+ <span
278
+ v-if="sortField === column.field"
279
+ :class="[{
280
+ 'column-sort__icon': true,
281
+ 'column-sort__icon--asc': sortOrder === 'asc',
282
+ 'column-sort__icon--desc': sortOrder === 'desc',
283
+ }]"
284
+ :style="{
285
+ height: `${rowHeight}px`,
286
+ 'line-height': `${rowHeight}px`,
287
+ }"
288
+ >
289
+ <slot :name="`sortIcon_${sortOrder}`" />
290
+ </span>
291
+ </template>
292
+ <template v-else>
293
+ <grid-sort-button
294
+ v-if="column.sortable === undefined ? true : column.sortable"
295
+ class="column-sort__icon column-sort__icon--basic"
296
+ :icon="'basic'"
297
+ :style="{
298
+ height: `${rowHeight}px`,
299
+ 'line-height': `${rowHeight}px`,
300
+ }"
301
+ />
302
+ <grid-sort-button
303
+ v-if="sortField === column.field"
304
+ :class="[{
305
+ 'column-sort__icon': true,
306
+ 'column-sort__icon--asc': sortOrder === 'asc',
307
+ 'column-sort__icon--desc': sortOrder === 'desc',
308
+ }]"
309
+ :icon="sortOrder"
310
+ :style="{
311
+ height: `${rowHeight}px`,
312
+ 'line-height': `${rowHeight}px`,
313
+ visibility: !!sortOrder ? column.hidden : true,
314
+ }"
315
+ />
316
+ </template>
317
+ </span>
312
318
  </span>
313
- </span>
319
+ </template>
314
320
  <!-- Column Resize -->
315
321
  <span
316
322
  class="column-resize"
@@ -352,12 +358,13 @@
352
358
  :style="`height: ${vScrollTopHeight}px;`"
353
359
  class="vscroll-spacer"
354
360
  />
361
+
355
362
  <table ref="table">
356
363
  <tbody>
357
364
  <!-- Row List -->
358
365
  <template
359
366
  v-for="(row, rowIndex) in viewStore"
360
- :key="rowIndex"
367
+ :key="idColIndex !== -1 ? row[2][idColIndex] : rowIndex"
361
368
  >
362
369
  <tr
363
370
  :data-index="row[0]"
@@ -418,7 +425,7 @@
418
425
  <!-- Cell -->
419
426
  <template
420
427
  v-for="(column, cellIndex) in orderedColumns"
421
- :key="cellIndex"
428
+ :key="`${idColIndex !== -1 ? row[2][idColIndex] : rowIndex}-${cellIndex}`"
422
429
  >
423
430
  <td
424
431
  v-if="!column.hide && !column.hiddenDisplay"
@@ -497,10 +504,18 @@
497
504
  <tr
498
505
  v-if="useRowDetail && $slots?.rowDetail && row[4]"
499
506
  >
507
+ <div
508
+ :style="{
509
+ height: `${detailRowHeight}px`,
510
+ 'min-height': `${detailRowHeight}px`,
511
+ 'max-height': `${detailRowHeight}px`
512
+ }">
513
+
500
514
  <slot
501
515
  name="rowDetail"
502
516
  :item="{ row }"
503
517
  />
518
+ </div>
504
519
  </tr>
505
520
  </template>
506
521
  <tr v-if="!viewStore.length">
@@ -513,6 +528,7 @@
513
528
  :style="`height: ${vScrollBottomHeight}px;`"
514
529
  class="vscroll-spacer"
515
530
  />
531
+
516
532
  <!-- Context Menu -->
517
533
  <ev-context-menu
518
534
  ref="menu"
@@ -811,6 +827,7 @@ export default {
811
827
  const expandedInfo = reactive({
812
828
  expandedRows: props.expanded,
813
829
  useRowDetail: computed(() => props.option?.rowDetail?.use ?? false),
830
+ detailRowHeight: computed(() => (props.option?.rowDetail?.detailRowHeight)),
814
831
  });
815
832
  const scrollInfo = reactive({
816
833
  lastScroll: {
@@ -1221,6 +1238,7 @@ export default {
1221
1238
  if (!expendedSize) {
1222
1239
  clearExpandedInfo();
1223
1240
  }
1241
+ updateVScroll();
1224
1242
  },
1225
1243
  );
1226
1244
  watch(
@@ -1411,7 +1429,10 @@ export default {
1411
1429
 
1412
1430
  onBeforeMount(() => initWrapperDiv());
1413
1431
 
1432
+ const idColIndex = computed(() => stores.orderedColumns.findIndex(c => c.field === 'id'));
1433
+
1414
1434
  return {
1435
+ idColIndex,
1415
1436
  summaryScroll,
1416
1437
  showHeader,
1417
1438
  stripeStyle,
@@ -114,19 +114,56 @@ export const scrollEvent = (params) => {
114
114
  const updateVScrollBase = (isScroll) => {
115
115
  const bodyEl = elementInfo.body;
116
116
  const rowHeight = resizeInfo.rowHeight;
117
+ const expandedRowHeight = expandedInfo.detailRowHeight ?? 0;
118
+
117
119
  if (bodyEl) {
118
120
  let store = stores.store;
119
121
  if (pageInfo.isClientPaging) {
120
122
  store = getPagingData();
121
123
  }
122
- const rowCount = bodyEl.clientHeight > rowHeight
123
- ? Math.ceil(bodyEl.clientHeight / rowHeight) : store.length;
124
- const totalScrollHeight = store.length * rowHeight;
125
- let firstVisibleIndex = Math.floor(bodyEl.scrollTop / rowHeight);
124
+
125
+ const expandedRowCount = expandedInfo.expandedRows?.length ?? 0;
126
+ const expandedRowIndexes = expandedInfo.expandedRows?.map(
127
+ row => store.findIndex(data => data[ROW_DATA_INDEX] === row),
128
+ ) ?? [];
129
+ const totalScrollHeight = store.length * rowHeight + expandedRowCount * expandedRowHeight;
130
+
131
+ let firstVisibleIndex = 0;
132
+ let scrollTop = 0;
133
+
134
+ if (expandedRowCount) {
135
+ for (firstVisibleIndex = 0; scrollTop + rowHeight < bodyEl.scrollTop; firstVisibleIndex++) {
136
+ if (expandedRowIndexes.includes(firstVisibleIndex)) {
137
+ scrollTop += expandedRowHeight;
138
+ }
139
+ scrollTop += rowHeight;
140
+ }
141
+ } else {
142
+ firstVisibleIndex = Math.floor(bodyEl.scrollTop / rowHeight);
143
+ scrollTop = Math.max(firstVisibleIndex, 0) * rowHeight;
144
+ }
145
+
126
146
  if (firstVisibleIndex > store.length - 1) {
127
147
  firstVisibleIndex = 0;
128
148
  }
129
149
 
150
+ let rowCount = 0;
151
+ let renderRowHeight = 0;
152
+
153
+ if (expandedRowCount) {
154
+ for (let i = firstVisibleIndex; renderRowHeight <= bodyEl.clientHeight; i++) {
155
+ if (expandedRowIndexes.includes(i)) {
156
+ renderRowHeight += expandedRowHeight;
157
+ }
158
+ renderRowHeight += rowHeight;
159
+ rowCount += 1;
160
+ }
161
+ } else {
162
+ rowCount = bodyEl.clientHeight > rowHeight
163
+ ? Math.ceil(bodyEl.clientHeight / rowHeight) : store.length;
164
+ renderRowHeight = rowCount * rowHeight;
165
+ }
166
+
130
167
  const lastVisibleIndex = firstVisibleIndex + rowCount + 1;
131
168
  const firstIndex = Math.max(firstVisibleIndex, 0);
132
169
  const lastIndex = lastVisibleIndex;
@@ -135,8 +172,8 @@ export const scrollEvent = (params) => {
135
172
  stores.viewStore = store.slice(firstIndex, lastIndex);
136
173
  scrollInfo.hasVerticalScrollBar = rowCount < store.length
137
174
  || bodyEl.clientHeight < tableEl.clientHeight;
138
- scrollInfo.vScrollTopHeight = firstIndex * rowHeight;
139
- scrollInfo.vScrollBottomHeight = totalScrollHeight - (stores.viewStore.length * rowHeight)
175
+ scrollInfo.vScrollTopHeight = scrollTop;
176
+ scrollInfo.vScrollBottomHeight = totalScrollHeight - renderRowHeight
140
177
  - scrollInfo.vScrollTopHeight;
141
178
  if (isScroll && pageInfo.isInfinite && scrollInfo.vScrollBottomHeight === 0) {
142
179
  pageInfo.prevPage = pageInfo.currentPage;
@@ -148,10 +185,10 @@ export const scrollEvent = (params) => {
148
185
  };
149
186
 
150
187
  /**
151
- * rowDetail slot 시에는 가상 스크롤을 적용하지 않는다.
188
+ * 고정 높이를 지정하지 않은 rowDetail slot 시에는 가상 스크롤을 적용하지 않는다.
152
189
  */
153
190
  const updateVScroll = (isScroll) => {
154
- if (expandedInfo.useRowDetail) {
191
+ if (expandedInfo.useRowDetail && !expandedInfo.detailRowHeight) {
155
192
  let store = stores.store;
156
193
  if (pageInfo.isClientPaging) {
157
194
  store = getPagingData();
@@ -10,9 +10,25 @@
10
10
  / {{ total }}
11
11
  </template>
12
12
  </small>
13
+
13
14
  <ul class="pagination-list" :style="listClasses">
15
+ <!-- Jump -->
16
+ <li v-if="$props.pagePerJump"
17
+ class="step-button"
18
+ :class="{'is-disabled': !hasPrev}">
19
+ <page-button
20
+ class="pagination-previous"
21
+ :disabled="!hasPrev"
22
+ :page="getPage(prevJumpPage, {isCurrent: false})"
23
+ >
24
+ <ev-icon icon="ev-icon-s-double-left"/>
25
+ </page-button>
26
+ </li>
27
+
14
28
  <!-- Previous -->
15
- <li :class="{'is-disabled': !hasPrev}">
29
+ <li
30
+ class="step-button"
31
+ :class="{'is-disabled': !hasPrev}">
16
32
  <page-button
17
33
  class="pagination-previous"
18
34
  :disabled="!hasPrev"
@@ -33,7 +49,9 @@
33
49
  </template>
34
50
 
35
51
  <!-- Next -->
36
- <li :class="{'is-disabled': !hasNext}">
52
+ <li
53
+ class="step-button"
54
+ :class="{'is-disabled': !hasNext}">
37
55
  <page-button
38
56
  class="pagination-next"
39
57
  :disabled="!hasNext"
@@ -42,6 +60,20 @@
42
60
  <ev-icon icon="ev-icon-s-arrow-right"/>
43
61
  </page-button>
44
62
  </li>
63
+
64
+ <!-- Jump -->
65
+ <li v-if="$props.pagePerJump"
66
+ class="step-button"
67
+ :class="{'is-disabled': !hasNext}">
68
+ <page-button
69
+ class="pagination-previous"
70
+ :disabled="!hasNext"
71
+ :page="getPage(nextJumpPage, {isCurrent: false})"
72
+ >
73
+ <ev-icon icon="ev-icon-s-double-right"/>
74
+ </page-button>
75
+ </li>
76
+
45
77
  </ul>
46
78
  </nav>
47
79
  </template>
@@ -83,6 +115,10 @@ export default {
83
115
  default: 'left',
84
116
  validator: val => ['left', 'right', 'center'].includes(val),
85
117
  },
118
+ pagePerJump: {
119
+ type: [Number, undefined],
120
+ default: undefined,
121
+ },
86
122
  },
87
123
  emits: {
88
124
  'update:modelValue': null,
@@ -95,6 +131,14 @@ export default {
95
131
  ? 1 : Math.ceil(props.total / props.perPage)));
96
132
  const hasPrev = computed(() => current.value > 1);
97
133
  const hasNext = computed(() => current.value < pageCount.value);
134
+
135
+ const prevJumpPage = computed(
136
+ () => Math.max(current.value - props.pagePerJump, 1),
137
+ );
138
+ const nextJumpPage = computed(
139
+ () => Math.min(current.value + props.pagePerJump, pageCount.value),
140
+ );
141
+
98
142
  const firstData = computed(() => {
99
143
  const item = current.value * props.perPage - props.perPage + 1;
100
144
  return item >= 0 ? item : 0;
@@ -109,7 +153,7 @@ export default {
109
153
  };
110
154
  const getPage = (num, options = {}) => ({
111
155
  number: num,
112
- isCurrent: current.value === num,
156
+ isCurrent: options.isCurrent ?? current.value === num,
113
157
  click: event => changePage(num, event),
114
158
  input: (event, inputNum) => changePage(+inputNum, event),
115
159
  disabled: options.disabled || false,
@@ -198,6 +242,8 @@ export default {
198
242
  current,
199
243
  changePage,
200
244
  getPage,
245
+ prevJumpPage,
246
+ nextJumpPage,
201
247
  };
202
248
  },
203
249
  };
@@ -72,18 +72,24 @@
72
72
  :class="getColumnClass(column)"
73
73
  :style="getColumnStyle(column, index)"
74
74
  >
75
- <!-- Column Name -->
76
- <span
77
- :title="column.caption"
78
- :class="[
79
- 'column-name',
80
- { 'column-name--click' : useGridSetting }
81
- ]"
82
- @click="onColumnContextMenu($event, column)"
83
- @click.prevent="columnMenu.show"
84
- >
85
- {{ column.caption }}
86
- </span>
75
+ <!-- Custom Header -->
76
+ <template v-if="column.customHeader && !!$slots.customHeader">
77
+ <slot name="customHeader" />
78
+ </template>
79
+ <template v-else>
80
+ <!-- Column Name -->
81
+ <span
82
+ :title="column.caption"
83
+ :class="[
84
+ 'column-name',
85
+ { 'column-name--click' : useGridSetting }
86
+ ]"
87
+ @click="onColumnContextMenu($event, column)"
88
+ @click.prevent="columnMenu.show"
89
+ >
90
+ {{ column.caption }}
91
+ </span>
92
+ </template>
87
93
  <!-- Column Resize -->
88
94
  <span
89
95
  class="column-resize"
@@ -125,7 +131,7 @@
125
131
  <tbody>
126
132
  <tree-grid-node
127
133
  v-for="(node, idx) in viewStore"
128
- :key="idx"
134
+ :key="node['id'] || idx"
129
135
  :selected-data="selectedRow"
130
136
  :node-data="node"
131
137
  :use-checkbox="useCheckbox"
@@ -886,6 +892,7 @@ export default {
886
892
  column: true,
887
893
  render,
888
894
  'non-border': !!styleInfo.borderStyle,
895
+ [column.field]: column.field,
889
896
  };
890
897
  };
891
898
  const getColumnStyle = (column, index) => {
@@ -256,6 +256,7 @@ export default {
256
256
  'tree-td': cellIndex === 0,
257
257
  [column.type]: column.type,
258
258
  [column.align]: column.align,
259
+ [column.field]: column.field,
259
260
  'non-border': !!props.borderStyle,
260
261
  });
261
262
  const getColumnStyle = (column, cellIndex) => ({