evui 3.1.30 → 3.1.34

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.
@@ -0,0 +1,604 @@
1
+ import {
2
+ getCurrentInstance, ref, computed, reactive, watch, nextTick,
3
+ } from 'vue';
4
+
5
+ // 세로 스크롤 너비
6
+ const getVScrollWidth = () => window.innerWidth - document.documentElement.clientWidth;
7
+ // 가로 스크롤 너비
8
+ const getHScrollWidth = () => window.innerHeight - document.documentElement.clientHeight;
9
+ // 전체 문서 너비
10
+ const getDocumentWidth = () => Math.max(
11
+ document.body.scrollWidth, document.documentElement.scrollWidth,
12
+ document.body.offsetWidth, document.documentElement.offsetWidth,
13
+ document.body.clientWidth, document.documentElement.clientWidth,
14
+ );
15
+ // 전체 문서 높이
16
+ const getDocumentHeight = () => Math.max(
17
+ document.body.scrollHeight, document.documentElement.scrollHeight,
18
+ document.body.offsetHeight, document.documentElement.offsetHeight,
19
+ document.body.clientHeight, document.documentElement.clientHeight,
20
+ );
21
+
22
+ const useModel = () => {
23
+ const { props, emit } = getCurrentInstance();
24
+
25
+ const windowRef = ref();
26
+ const headerRef = ref();
27
+ const isFullExpandWindow = ref(false);
28
+ const maximizableIcon = computed(() => (isFullExpandWindow.value ? 'ev-icon-compress' : 'ev-icon-expand'));
29
+
30
+ // body에 #ev-window-modal div append
31
+ let root = document.getElementById('ev-window-modal');
32
+ const initWrapperDiv = () => {
33
+ if (!root) {
34
+ const rootDiv = document.createElement('div');
35
+ rootDiv.id = 'ev-window-modal';
36
+ document.body.appendChild(rootDiv);
37
+ root = document.getElementById('ev-window-modal');
38
+ }
39
+ };
40
+
41
+ initWrapperDiv();
42
+
43
+ const numberToUnit = (input) => {
44
+ let output;
45
+ let result;
46
+
47
+ if (typeof input === 'string' || typeof input === 'number') {
48
+ const match = (/^(normal|(-*\d+(?:\.\d+)?)(px|%|vw|vh)?)$/).exec(input);
49
+ output = match ? { value: +match[2], unit: match[3] || undefined } : undefined;
50
+ } else {
51
+ output = undefined;
52
+ }
53
+
54
+ if (output === null || output === undefined) {
55
+ result = undefined;
56
+ } else if (output.unit) {
57
+ result = `${output.value}${output.unit}`;
58
+ } else {
59
+ result = `${output.value}px`;
60
+ }
61
+
62
+ return result;
63
+ };
64
+
65
+ const removeUnit = (input, direction) => {
66
+ if (typeof input === 'number') {
67
+ return input;
68
+ } else if (!input) {
69
+ return 0;
70
+ }
71
+
72
+ let result = 0;
73
+ const match = (/^(normal|(\d+(?:\.\d+)?)(px|%|vw|vh)?)$/).exec(input);
74
+
75
+ if (direction && ['%', 'vw', 'vh'].includes(match[3]) && match[2]) {
76
+ const standard = direction === 'horizontal' ? window.innerWidth : window.innerHeight;
77
+ result = Math.floor((standard * +match[2]) / 100);
78
+ } else if (match[2]) {
79
+ result = +match[2];
80
+ }
81
+
82
+ return result;
83
+ };
84
+
85
+ // set base style
86
+ const basePosition = reactive({});
87
+ const baseStyle = computed(() => ({
88
+ ...props.style,
89
+ ...basePosition,
90
+ }));
91
+
92
+ const setBasePosition = () => {
93
+ if (props.fullscreen) {
94
+ basePosition.width = '100%';
95
+ basePosition.height = '100%';
96
+ basePosition.top = 0;
97
+ basePosition.left = 0;
98
+ return;
99
+ }
100
+
101
+ const windowWidth = window.innerWidth;
102
+ const windowHeight = window.innerHeight;
103
+ const refWidth = props.width ?? windowRef.value?.offsetWidth ?? Math.floor(windowWidth / 2);
104
+ const refHeight = props.height ?? windowRef.value?.offsetHeight ?? Math.floor(windowHeight / 2);
105
+
106
+ const tempWidth = removeUnit(refWidth, 'horizontal');
107
+ const tempHeight = removeUnit(refHeight, 'vertical');
108
+
109
+ basePosition.width = `${tempWidth}px`;
110
+ basePosition.height = `${tempHeight}px`;
111
+ basePosition.left = `${Math.floor((windowWidth - tempWidth) / 2)}px`;
112
+
113
+ if (props.hideScroll || props.isModal) {
114
+ basePosition.position = 'fixed';
115
+ basePosition.top = `${Math.floor((windowHeight - tempHeight) / 2)}px`;
116
+ } else {
117
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop
118
+ || document.body.scrollTop || 0;
119
+ basePosition.position = 'absolute';
120
+ basePosition.top = `${Math.floor(((windowHeight - tempHeight) / 2) + scrollTop)}px`;
121
+ }
122
+ };
123
+
124
+ // close window
125
+ const closeWin = (from) => {
126
+ if (from === 'layer' && !props.closeOnClickModal) {
127
+ return;
128
+ }
129
+ emit('update:visible', false);
130
+ };
131
+
132
+ const changeBodyCls = (isVisible) => {
133
+ const hideScrollWindowCnt = root?.getElementsByClassName('scroll-lock')?.length;
134
+ const allowScrollWindowCnt = root?.getElementsByClassName('scroll-allow')?.length;
135
+ const bodyElem = document.body;
136
+
137
+ if (isVisible) {
138
+ if (props.hideScroll) {
139
+ // props.hideScroll === true 시,
140
+ // body 우측 padding 추가 & overflow hidden class 추가
141
+ if (!hideScrollWindowCnt) {
142
+ const scrollWidth = getVScrollWidth();
143
+ bodyElem.style.paddingRight = `${scrollWidth}px`;
144
+ }
145
+ bodyElem.classList.add('ev-window-scroll-lock');
146
+ } else if (!props.hideScroll && !props.isModal) {
147
+ // !props.hideScroll && !props.isModal 시,
148
+ // body에 position: relative 추가, 스크롤 여부에 따라 overflow-x or y hidden 처리
149
+ const vScrollWidth = getVScrollWidth();
150
+ const hScrollWidth = getHScrollWidth();
151
+ const bodyClassName = ['ev-window-scroll-allow'];
152
+ if (vScrollWidth > 0 && hScrollWidth === 0) {
153
+ // 세로 스크롤만 있을 경우 - 가로 스크롤 막음
154
+ bodyClassName.push('horizontal-hide');
155
+ } else if (vScrollWidth === 0 && hScrollWidth > 0) {
156
+ // 가로 스크롤만 있을 경우 - 세로 스크롤 막음
157
+ bodyClassName.push('vertical-hide');
158
+ } else if (vScrollWidth === 0 && hScrollWidth === 0) {
159
+ // 둘다 없을 경우 - 가로&세로 스크롤 막음
160
+ bodyClassName.push('hide');
161
+ }
162
+
163
+ bodyElem.classList.add(...bodyClassName);
164
+ }
165
+ } else {
166
+ if (hideScrollWindowCnt === 1) {
167
+ bodyElem.style.removeProperty('padding-right');
168
+ bodyElem.classList.remove('ev-window-scroll-lock');
169
+ }
170
+ if (allowScrollWindowCnt === 1) {
171
+ bodyElem.classList.remove('ev-window-scroll-allow', 'horizontal-hide', 'vertical-hide', 'hide');
172
+ }
173
+ }
174
+ };
175
+ setBasePosition();
176
+
177
+ watch(
178
+ () => props.visible,
179
+ (newVal) => {
180
+ changeBodyCls(newVal);
181
+ if (newVal) {
182
+ nextTick(() => {
183
+ setBasePosition();
184
+ });
185
+ }
186
+ },
187
+ );
188
+
189
+ return {
190
+ windowRef,
191
+ headerRef,
192
+ isFullExpandWindow,
193
+ maximizableIcon,
194
+ baseStyle,
195
+ closeWin,
196
+ numberToUnit,
197
+ removeUnit,
198
+ };
199
+ };
200
+
201
+ const useMouseEvent = (param) => {
202
+ const { props, emit } = getCurrentInstance();
203
+ const {
204
+ windowRef,
205
+ headerRef,
206
+ isFullExpandWindow,
207
+ numberToUnit,
208
+ removeUnit,
209
+ } = param;
210
+
211
+ const draggingMinSize = 50;
212
+ const grabbingBorderSize = 5;
213
+ const dragStyle = reactive({});
214
+ const documentWidth = ref(0);
215
+ const documentHeight = ref(0);
216
+ const clickedInfo = reactive({
217
+ state: '',
218
+ pressedSpot: '',
219
+ top: 0,
220
+ left: 0,
221
+ width: 0,
222
+ height: 0,
223
+ clientX: 0,
224
+ clientY: 0,
225
+ });
226
+ const grabbingBorderPosInfo = reactive({
227
+ top: false,
228
+ right: false,
229
+ left: false,
230
+ bottom: false,
231
+ });
232
+ const beforeExpandPosInfo = reactive({
233
+ width: null,
234
+ height: null,
235
+ top: null,
236
+ left: null,
237
+ });
238
+
239
+ const isInHeader = (x, y) => {
240
+ if (x == null || y == null) {
241
+ return false;
242
+ }
243
+
244
+ const rect = windowRef.value.getBoundingClientRect();
245
+ const posX = +x - rect.left;
246
+ const posY = +y - rect.top;
247
+ const headerAreaStyleInfo = headerRef.value.style;
248
+ const headerPaddingInfo = {
249
+ top: removeUnit(headerAreaStyleInfo.paddingTop),
250
+ left: removeUnit(headerAreaStyleInfo.paddingLeft),
251
+ right: removeUnit(headerAreaStyleInfo.paddingRight),
252
+ };
253
+ const startPosX = headerPaddingInfo.left;
254
+ const endPosX = rect.width - headerPaddingInfo.right;
255
+ const startPosY = headerPaddingInfo.top;
256
+ const endPosY = startPosY + headerRef.value.offsetHeight;
257
+
258
+ return posX > startPosX && posX < endPosX && posY > startPosY && posY < endPosY;
259
+ };
260
+
261
+ const setDragStyle = (paramObj) => {
262
+ if (paramObj === null || typeof paramObj !== 'object') {
263
+ return;
264
+ }
265
+
266
+ let top;
267
+ let left;
268
+ let width;
269
+ let height;
270
+ let tMinWidth;
271
+ let tMinHeight;
272
+ const windowEl = windowRef.value;
273
+ const hasOwnProperty = Object.prototype.hasOwnProperty;
274
+
275
+ if (hasOwnProperty.call(paramObj, 'top')) {
276
+ top = paramObj.top;
277
+ } else {
278
+ top = clickedInfo.top;
279
+ }
280
+
281
+ if (hasOwnProperty.call(paramObj, 'left')) {
282
+ left = paramObj.left;
283
+ } else {
284
+ left = clickedInfo.left;
285
+ }
286
+
287
+ if (hasOwnProperty.call(paramObj, 'width')) {
288
+ width = paramObj.width;
289
+ } else {
290
+ width = windowEl.offsetWidth;
291
+ }
292
+
293
+ if (hasOwnProperty.call(paramObj, 'height')) {
294
+ height = paramObj.height;
295
+ } else {
296
+ height = windowEl.offsetHeight;
297
+ }
298
+
299
+ if (hasOwnProperty.call(paramObj, 'minWidth')) {
300
+ tMinWidth = paramObj.minWidth;
301
+ } else {
302
+ tMinWidth = removeUnit(props.minWidth, 'horizontal');
303
+ }
304
+
305
+ if (hasOwnProperty.call(paramObj, 'minHeight')) {
306
+ tMinHeight = paramObj.minHeight;
307
+ } else {
308
+ tMinHeight = removeUnit(props.minHeight, 'vertical');
309
+ }
310
+
311
+ width = Math.max(width, tMinWidth);
312
+ height = Math.max(height, tMinHeight);
313
+
314
+ dragStyle.top = numberToUnit(top);
315
+ dragStyle.left = numberToUnit(left);
316
+ dragStyle.width = numberToUnit(width);
317
+ dragStyle.height = numberToUnit(height);
318
+ dragStyle.minWidth = numberToUnit(tMinWidth);
319
+ dragStyle.minHeight = numberToUnit(tMinHeight);
320
+ };
321
+
322
+ const changeMouseCursor = (e) => {
323
+ if (!windowRef.value || clickedInfo.pressedSpot) {
324
+ return;
325
+ }
326
+
327
+ if (props.resizable) {
328
+ const rect = windowRef.value.getBoundingClientRect();
329
+ const x = e.clientX - rect.left;
330
+ const y = e.clientY - rect.top;
331
+ const top = y < grabbingBorderSize;
332
+ const left = x < grabbingBorderSize;
333
+ const right = x >= (rect.width - grabbingBorderSize);
334
+ const bottom = y >= (rect.height - grabbingBorderSize);
335
+
336
+ if ((top && left) || (bottom && right)) {
337
+ windowRef.value.style.cursor = 'nwse-resize';
338
+ } else if ((top && right) || (bottom && left)) {
339
+ windowRef.value.style.cursor = 'nesw-resize';
340
+ } else if (right || left) {
341
+ windowRef.value.style.cursor = 'ew-resize';
342
+ } else if (bottom || top) {
343
+ windowRef.value.style.cursor = 'ns-resize';
344
+ } else if (props.draggable && isInHeader(e.clientX, e.clientY)) {
345
+ windowRef.value.style.cursor = 'move';
346
+ } else {
347
+ windowRef.value.style.cursor = 'default';
348
+ }
349
+ } else if (props.draggable && isInHeader(e.clientX, e.clientY)) {
350
+ windowRef.value.style.cursor = 'move';
351
+ } else {
352
+ windowRef.value.style.cursor = 'default';
353
+ }
354
+ };
355
+
356
+ // window resize
357
+ const resizeWindow = (e) => {
358
+ const windowWidth = window.innerWidth;
359
+ const windowHeight = window.innerHeight;
360
+ const isTop = grabbingBorderPosInfo.top;
361
+ const isLeft = grabbingBorderPosInfo.left;
362
+ const isRight = grabbingBorderPosInfo.right;
363
+ const isBottom = grabbingBorderPosInfo.bottom;
364
+ const minWidth = removeUnit(props.minWidth, 'horizontal');
365
+ const minHeight = removeUnit(props.minHeight, 'vertical');
366
+ const clientX = e.clientX >= windowWidth ? windowWidth : e.clientX;
367
+ let clientY = e.clientY >= windowHeight ? windowHeight : e.clientY;
368
+ clientY = e.clientY > 0 ? clientY : 0;
369
+ const diffX = clientX - clickedInfo.clientX;
370
+ const diffY = clientY - clickedInfo.clientY;
371
+
372
+ let top = clickedInfo.top;
373
+ let left = clickedInfo.left;
374
+ let width = clickedInfo.width;
375
+ let height = clickedInfo.height;
376
+ const maxTop = (top + clickedInfo.height) - minHeight;
377
+ const maxLeft = (left + clickedInfo.width) - minWidth;
378
+
379
+ if (isTop) {
380
+ top = clickedInfo.top + diffY;
381
+ height = clickedInfo.height - diffY;
382
+
383
+ if (top > maxTop) {
384
+ top = maxTop;
385
+ }
386
+ }
387
+
388
+ if (isLeft) {
389
+ left = clickedInfo.left + diffX;
390
+ width = clickedInfo.width - diffX;
391
+
392
+ if (left > maxLeft) {
393
+ left = maxLeft;
394
+ }
395
+ }
396
+
397
+ if (isRight) {
398
+ width = clickedInfo.width + diffX;
399
+ }
400
+
401
+ if (isBottom) {
402
+ height = clickedInfo.height + diffY;
403
+ }
404
+
405
+ width = Math.min(Math.max(width, minWidth), windowWidth);
406
+ height = Math.min(Math.max(height, minHeight), windowHeight);
407
+
408
+ const positionInfo = { top, left, width, height };
409
+ setDragStyle(positionInfo);
410
+ emit('resize', e, { ...positionInfo });
411
+ };
412
+
413
+ // mousedown > mousemove: 마우스 드래그 중
414
+ const dragging = (e) => {
415
+ e.preventDefault();
416
+ clickedInfo.state = 'mousedown-mousemove';
417
+
418
+ // window header를 통해 mouseMove 됐을 경우
419
+ if (props.draggable && clickedInfo.pressedSpot === 'header') {
420
+ // 전체 문서 너비&높이 세팅 - mount 때와 드래그 시 수치 다르게 측정되기 때문에 드래그 초기 한번만 세팅
421
+ if (!documentWidth.value) {
422
+ documentWidth.value = getDocumentWidth();
423
+ }
424
+ if (!documentHeight.value) {
425
+ documentHeight.value = getDocumentHeight();
426
+ }
427
+
428
+ // 스크롤 있을 경우 전체 문서 기준, 없을 경우 clientWidth, Height 기준
429
+ const windowWidth = props.hideScroll || props.isModal
430
+ ? document.documentElement.clientWidth : documentWidth.value;
431
+ const windowHeight = props.hideScroll || props.isModal
432
+ ? document.documentElement.clientHeight : documentHeight.value;
433
+ const diffTop = e.clientY - clickedInfo.clientY;
434
+ const diffLeft = e.clientX - clickedInfo.clientX;
435
+
436
+ let tempTop = clickedInfo.top + diffTop;
437
+ let tempLeft = clickedInfo.left + diffLeft;
438
+
439
+ if (tempTop < 0) { // 상
440
+ tempTop = 0;
441
+ } else if (tempTop > windowHeight - draggingMinSize) { // 하
442
+ tempTop = Math.floor(windowHeight - draggingMinSize);
443
+ }
444
+
445
+ if (tempLeft < -(clickedInfo.width - draggingMinSize)) { // 좌
446
+ tempLeft = -Math.floor(clickedInfo.width - draggingMinSize);
447
+ } else if (tempLeft > windowWidth - draggingMinSize) { // 우
448
+ tempLeft = Math.floor(windowWidth - draggingMinSize);
449
+ }
450
+
451
+ setDragStyle({
452
+ top: `${tempTop}px`,
453
+ left: `${tempLeft}px`,
454
+ });
455
+ } else if (props.resizable && clickedInfo.pressedSpot === 'border') {
456
+ resizeWindow(e);
457
+ }
458
+
459
+ emit('mousedown-mousemove', e);
460
+ };
461
+
462
+ // mousedown > mouseup: 마우스 드래그 종료
463
+ const endDrag = (e) => {
464
+ clickedInfo.state = '';
465
+ clickedInfo.pressedSpot = '';
466
+
467
+ emit('mousedown-mouseup', e);
468
+
469
+ window.removeEventListener('mousemove', dragging);
470
+ window.removeEventListener('mouseup', endDrag);
471
+ };
472
+
473
+ // mousedown: 드래그 시작
474
+ const startDrag = (e) => {
475
+ if (!windowRef.value || (!props.resizable && !props.draggable)) {
476
+ return;
477
+ }
478
+ let pressedSpot = '';
479
+ if (props.resizable) {
480
+ const clientRect = windowRef.value.getBoundingClientRect();
481
+ const x = e.clientX - clientRect.left;
482
+ const y = e.clientY - clientRect.top;
483
+ const isGrabTop = y < grabbingBorderSize;
484
+ const isGrabLeft = x < grabbingBorderSize;
485
+ const isGrabRight = x >= (clientRect.width - grabbingBorderSize);
486
+ const isGrabBottom = y >= (clientRect.height - grabbingBorderSize);
487
+
488
+ grabbingBorderPosInfo.top = isGrabTop;
489
+ grabbingBorderPosInfo.left = isGrabLeft;
490
+ grabbingBorderPosInfo.right = isGrabRight;
491
+ grabbingBorderPosInfo.bottom = isGrabBottom;
492
+
493
+ if (isGrabTop || isGrabLeft || isGrabRight || isGrabBottom) {
494
+ pressedSpot = 'border';
495
+ }
496
+ }
497
+
498
+ if (pressedSpot !== 'border' && isInHeader(e.clientX, e.clientY)) {
499
+ pressedSpot = 'header';
500
+ }
501
+
502
+ if (!pressedSpot
503
+ || (!props.draggable && pressedSpot === 'header')
504
+ || (!props.resizable && pressedSpot === 'border')
505
+ ) {
506
+ return;
507
+ }
508
+
509
+ clickedInfo.state = 'mousedown';
510
+ clickedInfo.pressedSpot = pressedSpot;
511
+ clickedInfo.top = windowRef.value.offsetTop;
512
+ clickedInfo.left = windowRef.value.offsetLeft;
513
+ clickedInfo.width = windowRef.value.offsetWidth;
514
+ clickedInfo.height = windowRef.value.offsetHeight;
515
+ clickedInfo.clientX = e.clientX;
516
+ clickedInfo.clientY = e.clientY;
517
+
518
+ emit('mousedown', { ...clickedInfo });
519
+
520
+ window.addEventListener('mousemove', dragging);
521
+ window.addEventListener('mouseup', endDrag);
522
+ };
523
+
524
+ const moveMouse = (e) => {
525
+ if (!props.draggable && !props.resizable) {
526
+ return;
527
+ }
528
+ changeMouseCursor(e);
529
+ };
530
+
531
+ const clickExpandBtn = () => {
532
+ isFullExpandWindow.value = !isFullExpandWindow.value;
533
+ nextTick(() => {
534
+ if (!isFullExpandWindow.value) {
535
+ setDragStyle({
536
+ top: beforeExpandPosInfo.top,
537
+ left: beforeExpandPosInfo.left,
538
+ width: beforeExpandPosInfo.width,
539
+ height: beforeExpandPosInfo.height,
540
+ });
541
+ } else {
542
+ beforeExpandPosInfo.top = windowRef.value.offsetTop;
543
+ beforeExpandPosInfo.left = windowRef.value.offsetLeft;
544
+ beforeExpandPosInfo.width = windowRef.value.offsetWidth;
545
+ beforeExpandPosInfo.height = windowRef.value.offsetHeight;
546
+
547
+ setDragStyle({
548
+ top: 0,
549
+ left: 0,
550
+ width: document.body.clientWidth,
551
+ height: document.body.clientHeight,
552
+ });
553
+ }
554
+ });
555
+ };
556
+
557
+ const initWindowInfo = () => {
558
+ isFullExpandWindow.value = false;
559
+
560
+ clickedInfo.state = '';
561
+ clickedInfo.pressedSpot = '';
562
+ clickedInfo.top = 0;
563
+ clickedInfo.left = 0;
564
+ clickedInfo.width = 0;
565
+ clickedInfo.height = 0;
566
+ clickedInfo.clientX = 0;
567
+ clickedInfo.clientY = 0;
568
+
569
+ grabbingBorderPosInfo.top = false;
570
+ grabbingBorderPosInfo.left = false;
571
+ grabbingBorderPosInfo.right = false;
572
+ grabbingBorderPosInfo.bottom = false;
573
+
574
+ beforeExpandPosInfo.top = null;
575
+ beforeExpandPosInfo.left = null;
576
+ beforeExpandPosInfo.width = null;
577
+ beforeExpandPosInfo.height = null;
578
+
579
+ Object.keys(dragStyle).forEach((key) => {
580
+ delete dragStyle[key];
581
+ });
582
+ };
583
+
584
+ watch(
585
+ () => props.visible,
586
+ (newVal) => {
587
+ if (!newVal) {
588
+ initWindowInfo();
589
+ }
590
+ },
591
+ );
592
+
593
+ return {
594
+ dragStyle,
595
+ startDrag,
596
+ moveMouse,
597
+ clickExpandBtn,
598
+ };
599
+ };
600
+
601
+ export {
602
+ useModel,
603
+ useMouseEvent,
604
+ };