evui 3.4.107 → 3.4.109

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.107",
3
+ "version": "3.4.109",
4
4
  "description": "A EXEM Library project",
5
5
  "author": "exem <dev_client@ex-em.com>",
6
6
  "license": "MIT",
@@ -957,6 +957,15 @@ class EvChart {
957
957
  * @returns {undefined}
958
958
  */
959
959
  resize(promiseRes) {
960
+ // 차트 크기가 변경될 때 저장된 스크롤 픽셀 위치를 초기화하여
961
+ // 새로운 크기에 맞춰 스크롤바 크기/위치를 재계산하도록 함
962
+ if (this.scrollbar?.x) {
963
+ delete this.scrollbar.x.savedPosition;
964
+ }
965
+ if (this.scrollbar?.y) {
966
+ delete this.scrollbar.y.savedPosition;
967
+ }
968
+
960
969
  this.clear();
961
970
  this.bufferCtx.restore();
962
971
  this.bufferCtx.save();
@@ -46,7 +46,6 @@ class Bar {
46
46
  }
47
47
 
48
48
  const { isHorizontal, showValue } = this;
49
-
50
49
  const ctx = param.ctx;
51
50
  const chartRect = param.chartRect;
52
51
  const labelOffset = param.labelOffset;
@@ -70,6 +69,7 @@ class Bar {
70
69
  [minIndex, maxIndex] = [minmaxX.minIndex, minmaxX.maxIndex];
71
70
  }
72
71
 
72
+ // minIndex, maxIndex가 유효하면 실제 그릴 데이터 개수로 보정
73
73
  if (truthyNumber(minIndex) && truthyNumber(maxIndex)) {
74
74
  totalCount = (maxIndex - minIndex) + 1;
75
75
  }
@@ -126,112 +126,117 @@ class Bar {
126
126
  this.borderRadius = param.borderRadius;
127
127
  this.filteredCount = totalCount;
128
128
 
129
- let categoryPoint = null;
130
-
131
- this.data.forEach((dataItem, index) => {
132
- ctx.beginPath();
129
+ const startIndex = truthyNumber(minIndex) ? minIndex : 0;
130
+ const endIndex = truthyNumber(maxIndex) ? maxIndex : this.data.length - 1;
133
131
 
134
- const item = dataItem;
132
+ // 스크롤 범위 내에서만 루프 돌림
133
+ for (let i = startIndex; i <= endIndex; i++) {
134
+ const screenIndex = i - startIndex; // 현재 화면상의 위치 인덱스
135
+ const item = this.data[i]; // 실제 데이터 인덱스에 해당하는 항목
136
+ if (item) {
137
+ // 스크롤 offset(minIndex)만큼 보정해서 그리기
135
138
 
136
- if (truthyNumber(minIndex) && index < minIndex) {
137
- return;
138
- } else if (truthyNumber(minIndex) && index > maxIndex) {
139
- return;
140
- }
139
+ let categoryPoint;
140
+ if (isHorizontal) {
141
+ categoryPoint = ysp - (cArea * (screenIndex)) - cPad;
142
+ } else {
143
+ categoryPoint = xsp + (cArea * (screenIndex)) + cPad;
144
+ }
141
145
 
142
- if (isHorizontal) {
143
- categoryPoint = ysp - (cArea * (index - (minIndex || 0))) - cPad;
144
- } else {
145
- categoryPoint = xsp + (cArea * (index - (minIndex || 0))) + cPad;
146
- }
146
+ if (isHorizontal) {
147
+ x = xsp;
148
+ y = Math.round(categoryPoint - ((bArea * barSeriesX) - (h + bPad)));
149
+ } else {
150
+ x = Math.round(categoryPoint + ((bArea * barSeriesX) - (w + bPad)));
151
+ y = ysp;
152
+ }
147
153
 
148
- if (isHorizontal) {
149
- x = xsp;
150
- y = Math.round(categoryPoint - ((bArea * barSeriesX) - (h + bPad)));
151
- } else {
152
- x = Math.round(categoryPoint + ((bArea * barSeriesX) - (w + bPad)));
153
- y = ysp;
154
- }
154
+ if (isHorizontal) {
155
+ if (item.b) {
156
+ w = Canvas.calculateX(item.x - item.b, minmaxX.graphMin, minmaxX.graphMax, xArea);
157
+ x = Canvas.calculateX(item.b, minmaxX.graphMin, minmaxX.graphMax, xArea, xsp);
158
+ } else {
159
+ w = Canvas.calculateX(item.x, minmaxX.graphMin, minmaxX.graphMax, xArea);
160
+ }
161
+ } else if (item.b) { // vertical stack bar chart
162
+ h = Canvas.calculateY(item.y - item.b, minmaxY.graphMin, minmaxY.graphMax, yArea);
163
+ y = Canvas.calculateY(item.b, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp);
164
+ } else { // vertical bar chart
165
+ h = Canvas.calculateY(item.y, minmaxY.graphMin, minmaxY.graphMax, yArea);
166
+ }
155
167
 
156
- if (isHorizontal) {
157
- if (item.b) {
158
- w = Canvas.calculateX(item.x - item.b, minmaxX.graphMin, minmaxX.graphMax, xArea);
159
- x = Canvas.calculateX(item.b, minmaxX.graphMin, minmaxX.graphMax, xArea, xsp);
160
- } else {
161
- w = Canvas.calculateX(item.x, minmaxX.graphMin, minmaxX.graphMax, xArea);
168
+ const barColor = item.dataColor || this.color;
169
+
170
+ const legendHitInfo = param?.legendHitInfo;
171
+ const selectLabelOption = param?.selectLabel?.option;
172
+ const selectItemOption = param?.selectItem?.option;
173
+ const selectedLabelList = param?.selectLabel?.selected?.dataIndex ?? [];
174
+ const {
175
+ dataIndex: selectedItemDataIndex,
176
+ seriesID: selectedItemSeriesId,
177
+ } = param?.selectItem?.selected ?? {};
178
+
179
+ let isDownplay = false;
180
+
181
+ if (legendHitInfo) {
182
+ isDownplay = legendHitInfo?.sId !== this.sId;
183
+ } else if (selectLabelOption?.use && selectLabelOption?.useSeriesOpacity) {
184
+ isDownplay = selectedLabelList.length && !selectedLabelList.includes(i);
185
+ } else if (truthy(selectedItemDataIndex) && selectItemOption?.useSeriesOpacity) {
186
+ if (this.isExistGrp) {
187
+ isDownplay = selectedItemDataIndex !== i;
188
+ } else {
189
+ isDownplay = selectedItemDataIndex !== i || selectedItemSeriesId !== this.sId;
190
+ }
162
191
  }
163
- } else if (item.b) { // vertical stack bar chart
164
- h = Canvas.calculateY(item.y - item.b, minmaxY.graphMin, minmaxY.graphMax, yArea);
165
- y = Canvas.calculateY(item.b, minmaxY.graphMin, minmaxY.graphMax, yArea, ysp);
166
- } else { // vertical bar chart
167
- h = Canvas.calculateY(item.y, minmaxY.graphMin, minmaxY.graphMax, yArea);
168
- }
169
192
 
170
- const barColor = item.dataColor || this.color;
171
-
172
- const legendHitInfo = param?.legendHitInfo;
173
- const selectLabelOption = param?.selectLabel?.option;
174
- const selectItemOption = param?.selectItem?.option;
175
- const selectedLabelList = param?.selectLabel?.selected?.dataIndex ?? [];
176
- const {
177
- dataIndex: selectedItemDataIndex,
178
- seriesID: selectedItemSeriesId,
179
- } = param?.selectItem?.selected ?? {};
180
-
181
- let isDownplay = false;
182
-
183
- if (legendHitInfo) {
184
- isDownplay = legendHitInfo?.sId !== this.sId;
185
- } else if (selectLabelOption?.use && selectLabelOption?.useSeriesOpacity) {
186
- isDownplay = selectedLabelList.length && !selectedLabelList.includes(index);
187
- } else if (truthy(selectedItemDataIndex) && selectItemOption?.useSeriesOpacity) {
188
- if (this.isExistGrp) {
189
- isDownplay = selectedItemDataIndex !== index;
193
+ if (typeof barColor !== 'string') {
194
+ ctx.fillStyle = Canvas.createGradient(
195
+ ctx,
196
+ isHorizontal,
197
+ { x, y, w, h },
198
+ barColor,
199
+ isDownplay,
200
+ );
190
201
  } else {
191
- isDownplay = selectedItemDataIndex !== index || selectedItemSeriesId !== this.sId;
202
+ const noneDownplayOpacity = barColor.includes('rgba') ? Util.getOpacity(barColor) : 1;
203
+ const opacity = isDownplay ? 0.1 : noneDownplayOpacity;
204
+
205
+ ctx.fillStyle = Util.colorStringToRgba(barColor, opacity);
192
206
  }
193
- }
194
207
 
195
- if (typeof barColor !== 'string') {
196
- ctx.fillStyle = Canvas.createGradient(
208
+ this.drawBar({
197
209
  ctx,
198
- isHorizontal,
199
- { x, y, w, h },
200
- barColor,
201
- isDownplay,
202
- );
203
- } else {
204
- const noneDownplayOpacity = barColor.includes('rgba') ? Util.getOpacity(barColor) : 1;
205
- const opacity = isDownplay ? 0.1 : noneDownplayOpacity;
210
+ positions: { x, y, w, h },
211
+ });
206
212
 
207
- ctx.fillStyle = Util.colorStringToRgba(barColor, opacity);
208
- }
213
+ if (showValue.use) {
214
+ this.drawValueLabels({
215
+ context: ctx,
216
+ data: item,
217
+ positions: {
218
+ x,
219
+ y,
220
+ h,
221
+ w,
222
+ },
223
+ isHighlight: false,
224
+ textColor: item.dataTextColor,
225
+ });
226
+ }
209
227
 
210
- this.drawBar({
211
- ctx,
212
- positions: { x, y, w, h },
213
- });
228
+ // 좌표 및 인덱스 정보 세팅 (툴팁/hover용)
229
+ item.xp = x; // eslint-disable-line
230
+ item.yp = y; // eslint-disable-line
231
+ item.w = w; // eslint-disable-line
232
+ item.h = isHorizontal ? -h : h; // eslint-disable-line
233
+ item.index = i; // 실제 데이터 인덱스 (스크롤 offset 포함)
214
234
 
215
- if (showValue.use) {
216
- this.drawValueLabels({
217
- context: ctx,
218
- data: item,
219
- positions: {
220
- x,
221
- y,
222
- h,
223
- w,
224
- },
225
- isHighlight: false,
226
- textColor: item.dataTextColor,
227
- });
235
+ // 검색(hitInfo) 로직은 this.data[0..filteredCount-1] 범위만 검사하므로,
236
+ // 현재 화면에 그린 항목을 배열 앞쪽으로 매핑해준다.
237
+ this.data[screenIndex] = item;
228
238
  }
229
-
230
- item.xp = x; // eslint-disable-line
231
- item.yp = y; // eslint-disable-line
232
- item.w = w; // eslint-disable-line
233
- item.h = isHorizontal ? -h : h; // eslint-disable-line
234
- });
239
+ }
235
240
  }
236
241
 
237
242
  /**
@@ -511,6 +516,8 @@ class Bar {
511
516
  return;
512
517
  }
513
518
 
519
+ ctx.save();
520
+
514
521
  if (isBorderRadius && !isStackBar) {
515
522
  try {
516
523
  this.drawRoundedRect(ctx, positions);
@@ -520,6 +527,8 @@ class Bar {
520
527
  } else {
521
528
  ctx.fillRect(x, y, w, h);
522
529
  }
530
+
531
+ ctx.restore();
523
532
  }
524
533
 
525
534
  drawRoundedRect(ctx, positions) {
@@ -540,6 +549,7 @@ class Bar {
540
549
 
541
550
  ctx.clip(squarePath);
542
551
 
552
+ ctx.beginPath();
543
553
  ctx.moveTo(x, y);
544
554
 
545
555
  if (isHorizontal) {
@@ -101,9 +101,6 @@ const modules = {
101
101
  }
102
102
 
103
103
  this.dataSet[key].length = this.options.realTimeScatter.range || 300;
104
- this.dataSet[key].toTime = Math.floor(Date.now() / 1000) * 1000;
105
- this.dataSet[key].fromTime = this.dataSet[key].toTime - this.dataSet[key].length * 1000;
106
- this.dataSet[key].endIndex = this.dataSet[key].length - 1;
107
104
 
108
105
  for (let i = 0; i < storeLength; i++) {
109
106
  const item = data[i];
@@ -114,6 +111,14 @@ const modules = {
114
111
  }
115
112
 
116
113
  lastTime = Math.floor(lastTime / 1000) * 1000;
114
+
115
+ const dataGroupLastTime = this.dataSet[key].dataGroup.at(-1)?.data?.at(-1)?.x || Date.now();
116
+
117
+ this.dataSet[key].toTime = lastTime
118
+ || (dataGroupLastTime ? Math.floor(dataGroupLastTime / 1000) * 1000 : 0);
119
+ this.dataSet[key].fromTime = this.dataSet[key].toTime - this.dataSet[key].length * 1000;
120
+ this.dataSet[key].endIndex = this.dataSet[key].length - 1;
121
+
117
122
  if (
118
123
  (this.dataSet[key].toTime - lastTime) / 1000
119
124
  > this.dataSet[key].length && key === ''
@@ -60,8 +60,25 @@ const module = {
60
60
  limitMin = +minMax.min;
61
61
  limitMax = +minMax.max;
62
62
  }
63
- scrollbarOpt.range[0] = +min < limitMin ? limitMin : +min;
64
- scrollbarOpt.range[1] = +max > limitMax ? limitMax : +max;
63
+
64
+ const originalWidth = max - min;
65
+ const availableWidth = limitMax - limitMin;
66
+
67
+ if (originalWidth >= availableWidth) {
68
+ scrollbarOpt.range[0] = limitMin;
69
+ scrollbarOpt.range[1] = limitMax;
70
+ } else {
71
+ scrollbarOpt.range[0] = +min < limitMin ? limitMin : +min;
72
+ scrollbarOpt.range[1] = +max > limitMax ? limitMax : +max;
73
+
74
+ if (scrollbarOpt.range[1] - scrollbarOpt.range[0] < originalWidth) {
75
+ scrollbarOpt.range[0] = scrollbarOpt.range[1] - originalWidth;
76
+
77
+ if (scrollbarOpt.range[0] < limitMin) {
78
+ scrollbarOpt.range[0] = limitMin;
79
+ }
80
+ }
81
+ }
65
82
  }
66
83
  }
67
84
  },
@@ -93,7 +110,16 @@ const module = {
93
110
  const axisOpt = dir === 'x' ? this.axesX : this.axesY;
94
111
  const isUpdateAxesRange = !isEqual(newOpt?.[0]?.range, axisOpt?.[0]?.range);
95
112
  if (isUpdateAxesRange || updateData) {
96
- this.scrollbar[dir].range = newOpt?.[0]?.range?.length ? [...newOpt?.[0]?.range] : null;
113
+ const isResetPosition = dir === 'x' ? this.options.axesX?.[0]?.scrollbar?.resetPosition : this.options.axesY?.[0]?.scrollbar?.resetPosition;
114
+ if (isUpdateAxesRange || isResetPosition) {
115
+ this.scrollbar[dir].range = newOpt?.[0]?.range?.length ? [...newOpt?.[0]?.range] : null;
116
+ // range가 업데이트되면 저장된 스크롤 위치를 초기화
117
+ delete this.scrollbar[dir].savedPosition;
118
+ } else if (updateData) {
119
+ // 데이터가 업데이트되면 저장된 픽셀 위치는 더 이상 유효하지 않으므로 삭제하여
120
+ // 논리적 범위에 따라 다시 계산하도록 합니다.
121
+ delete this.scrollbar[dir].savedPosition;
122
+ }
97
123
  this.initScrollbarRange(dir);
98
124
  }
99
125
  this.scrollbar[dir].use = !!newOpt?.[0].scrollbar?.use;
@@ -104,11 +130,15 @@ const module = {
104
130
  */
105
131
  updateScrollbarPosition() {
106
132
  if (this.scrollbar.x?.use && this.scrollbar.x?.isInit) {
107
- this.setScrollbarPosition('x');
133
+ // resetPosition 옵션에 따라 preservePosition 결정
134
+ const preservePosition = !this.options.axesX?.[0]?.scrollbar?.resetPosition;
135
+ this.setScrollbarPosition('x', preservePosition);
108
136
  }
109
137
 
110
138
  if (this.scrollbar.y?.use && this.scrollbar.y?.isInit) {
111
- this.setScrollbarPosition('y');
139
+ // resetPosition 옵션에 따라 preservePosition 결정
140
+ const preservePosition = !this.options.axesY?.[0]?.scrollbar?.resetPosition;
141
+ this.setScrollbarPosition('y', preservePosition);
112
142
  }
113
143
  },
114
144
 
@@ -195,8 +225,9 @@ const module = {
195
225
  /**
196
226
  * set scrollbar position
197
227
  * @param dir axis direction ('x' | 'y')
228
+ * @param preservePosition 기존 위치를 유지할지 여부
198
229
  */
199
- setScrollbarPosition(dir) {
230
+ setScrollbarPosition(dir, preservePosition = false) {
200
231
  const scrollbarOpt = this.scrollbar[dir];
201
232
  if (!scrollbarOpt.use || !scrollbarOpt.range) {
202
233
  return;
@@ -218,7 +249,17 @@ const module = {
218
249
  const fullSize = isXScroll ? (aPos.x2 - aPos.x1) : (aPos.y2 - aPos.y1);
219
250
  const buttonSize = scrollbarOpt.showButton ? scrollHeight : 0;
220
251
  const trackSize = fullSize - (buttonSize * 2);
221
- const thumbSize = this.getScrollbarThumbSize(dir, trackSize);
252
+
253
+ // 현재 위치를 보존해야 하는 경우 기존 위치를 저장
254
+ let savedThumbPosition = null;
255
+ if (preservePosition && scrollbarOpt.savedPosition !== undefined) {
256
+ savedThumbPosition = scrollbarOpt.savedPosition;
257
+ }
258
+
259
+ const thumbSize = this.getScrollbarThumbSize(dir, trackSize, savedThumbPosition);
260
+
261
+ // 새로 계산된 위치를 저장
262
+ scrollbarOpt.savedPosition = thumbSize.position;
222
263
 
223
264
  let scrollbarStyle = 'display: block;';
224
265
  let scrollbarTrackStyle;
@@ -293,8 +334,9 @@ const module = {
293
334
  * get scrollbar thumb size
294
335
  * @param dir axis direction ('x' | 'y')
295
336
  * @param trackSize scrollbar track size
337
+ * @param savedThumbPosition 기존 위치를 보존해야 하는 경우 저장된 위치
296
338
  */
297
- getScrollbarThumbSize(dir, trackSize) {
339
+ getScrollbarThumbSize(dir, trackSize, savedThumbPosition) {
298
340
  const scrollbarOpt = this.scrollbar[dir];
299
341
  const [min, max] = scrollbarOpt.range;
300
342
  const axesType = scrollbarOpt.type;
@@ -338,6 +380,11 @@ const module = {
338
380
  scrollbarOpt.steps = steps;
339
381
  scrollbarOpt.interval = interval;
340
382
 
383
+ // 기존 위치를 보존해야 하는 경우 저장된 위치를 사용
384
+ if (savedThumbPosition !== null) {
385
+ thumbPosition = savedThumbPosition;
386
+ }
387
+
341
388
  return {
342
389
  size: thumbSize,
343
390
  position: thumbPosition,
@@ -396,6 +443,9 @@ const module = {
396
443
  if (!isOutOfRange) {
397
444
  scrollbarOpt.range = [minValue, maxValue];
398
445
 
446
+ // 사용자가 스크롤할 때는 저장된 위치를 초기화
447
+ delete scrollbarOpt.savedPosition;
448
+
399
449
  this.update({
400
450
  updateSeries: false,
401
451
  updateSelTip: { update: false, keepDomain: false },
@@ -481,9 +531,60 @@ const module = {
481
531
  };
482
532
 
483
533
  this.onScrollbarWheel = (e) => {
534
+ const isTooltipVisible = this.tooltipDOM?.style?.display === 'block';
535
+ const tooltipBodyDOM = this.tooltipBodyDOM
536
+ || this.tooltipDOM?.querySelector(this.options.tooltip.htmlScrollTarget);
537
+
538
+ if (isTooltipVisible && tooltipBodyDOM) {
539
+ const { scrollTop, scrollHeight, clientHeight } = tooltipBodyDOM;
540
+ const isAtTop = scrollTop <= 0;
541
+ const isAtBottom = scrollTop + clientHeight >= scrollHeight;
542
+
543
+ const isScrollingUp = e.deltaY < 0;
544
+ const isScrollingDown = e.deltaY > 0;
545
+
546
+ if ((isAtTop && isScrollingUp) || (isAtBottom && isScrollingDown)) {
547
+ // 툴팁의 스크롤이 맨 위나 맨 아래에 닿았는데 스크롤 하면 차트 스크롤 허용
548
+ } else {
549
+ // 툴팁 내부 스크롤만 수행
550
+ return;
551
+ }
552
+ }
553
+
484
554
  e.preventDefault();
485
555
 
486
- this.updateScrollbarRange('y', e.deltaY < 0);
556
+ const threshold = 1; // 최소 스크롤 임계값
557
+
558
+ // Shift + 휠: 가로 스크롤 (일반 마우스 휠 지원)
559
+ if (this.scrollbar.x?.use && e.shiftKey && Math.abs(e.deltaY) > threshold) {
560
+ this.updateScrollbarRange('x', e.deltaY > 0);
561
+ return;
562
+ }
563
+
564
+ // 대각선 스크롤 처리: 더 큰 방향을 우선으로 처리
565
+ const absX = Math.abs(e.deltaX);
566
+ const absY = Math.abs(e.deltaY);
567
+
568
+ if (absX > threshold && absY > threshold) {
569
+ // 두 방향 모두 임계값 이상일 때: 더 큰 방향을 우선 처리
570
+ if (absX > absY && this.scrollbar.x?.use) {
571
+ this.updateScrollbarRange('x', e.deltaX > 0);
572
+ } else if (absY > absX && this.scrollbar.y?.use) {
573
+ this.updateScrollbarRange('y', e.deltaY < 0);
574
+ }
575
+ return;
576
+ }
577
+
578
+ // 가로 스크롤 처리 (deltaX - 트랙패드 좌우 스크롤)
579
+ if (this.scrollbar.x?.use && absX > threshold) {
580
+ this.updateScrollbarRange('x', e.deltaX > 0);
581
+ return;
582
+ }
583
+
584
+ // 세로 스크롤 처리 (deltaY)
585
+ if (this.scrollbar.y?.use && absY > threshold) {
586
+ this.updateScrollbarRange('y', e.deltaY < 0);
587
+ }
487
588
  };
488
589
 
489
590
  if (this.scrollbar.x.use && !this.scrollbar.x.isInit) {
@@ -498,6 +599,10 @@ const module = {
498
599
  scrollbarYDOM.addEventListener('click', this.onScrollbarClick);
499
600
  scrollbarYDOM.addEventListener('mousedown', this.onScrollbarDown);
500
601
  scrollbarYDOM.addEventListener('mouseleave', this.onScrollbarLeave);
602
+ }
603
+
604
+ // 가로 또는 세로 스크롤바가 있으면 휠 이벤트 등록
605
+ if (this.scrollbar.x?.use || this.scrollbar.y?.use) {
501
606
  this.overlayCanvas?.addEventListener('wheel', this.onScrollbarWheel, { passive: false });
502
607
  }
503
608
  },
@@ -560,6 +665,10 @@ const module = {
560
665
  }
561
666
 
562
667
  this.scrollbar[dir].range = [movedMin, movedMax];
668
+
669
+ // 사용자가 드래그로 스크롤할 때는 저장된 위치를 초기화
670
+ delete this.scrollbar[dir].savedPosition;
671
+
563
672
  this.update({
564
673
  updateSeries: false,
565
674
  updateSelTip: { update: false, keepDomain: false },
@@ -606,13 +715,14 @@ const module = {
606
715
  * @param dir axis direction ('x' | 'y')
607
716
  */
608
717
  destroyScrollbar(dir) {
609
- const scrollbarXDOM = this.scrollbar[dir].dom;
718
+ const scrollbarDOM = this.scrollbar[dir].dom;
610
719
 
611
- if (scrollbarXDOM) {
612
- scrollbarXDOM.remove();
720
+ if (scrollbarDOM) {
721
+ scrollbarDOM.remove();
613
722
  this.scrollbar[dir] = { isInit: false };
614
723
 
615
- if (dir === 'y') {
724
+ // 가로, 세로 스크롤바 모두 없어지면 휠 이벤트 제거
725
+ if (!this.scrollbar.x?.use && !this.scrollbar.y?.use) {
616
726
  this.overlayCanvas?.removeEventListener('wheel', this.onScrollbarWheel, { passive: false });
617
727
  }
618
728
  }