evui 3.1.54 → 3.1.58

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.
@@ -1,5 +1,12 @@
1
1
  import {
2
- getCurrentInstance, ref, computed, reactive, watch, nextTick, onMounted,
2
+ getCurrentInstance,
3
+ ref,
4
+ computed,
5
+ reactive,
6
+ watch,
7
+ nextTick,
8
+ onMounted,
9
+ onBeforeUnmount,
3
10
  } from 'vue';
4
11
 
5
12
  // 세로 스크롤 너비
@@ -49,7 +56,7 @@ const useModel = () => {
49
56
  return result;
50
57
  };
51
58
 
52
- const removeUnit = (input, direction) => {
59
+ const removeUnit = (input, direction = 'horizontal') => {
53
60
  if (typeof input === 'number') {
54
61
  return input;
55
62
  } else if (!input) {
@@ -219,9 +226,9 @@ const useMouseEvent = (param) => {
219
226
  const posY = +y - rect.top;
220
227
  const headerAreaStyleInfo = headerRef.value.style;
221
228
  const headerPaddingInfo = {
222
- top: removeUnit(headerAreaStyleInfo.paddingTop),
223
- left: removeUnit(headerAreaStyleInfo.paddingLeft),
224
- right: removeUnit(headerAreaStyleInfo.paddingRight),
229
+ top: removeUnit(headerAreaStyleInfo.paddingTop, 'vertical'),
230
+ left: removeUnit(headerAreaStyleInfo.paddingLeft, 'horizontal'),
231
+ right: removeUnit(headerAreaStyleInfo.paddingRight, 'horizontal'),
225
232
  };
226
233
  const startPosX = headerPaddingInfo.left;
227
234
  const endPosX = rect.width - headerPaddingInfo.right;
@@ -272,17 +279,17 @@ const useMouseEvent = (param) => {
272
279
  if (hasOwnProperty.call(paramObj, 'minWidth')) {
273
280
  tMinWidth = paramObj.minWidth;
274
281
  } else {
275
- tMinWidth = removeUnit(props.minWidth, 'horizontal');
282
+ tMinWidth = props.minWidth;
276
283
  }
277
284
 
278
285
  if (hasOwnProperty.call(paramObj, 'minHeight')) {
279
286
  tMinHeight = paramObj.minHeight;
280
287
  } else {
281
- tMinHeight = removeUnit(props.minHeight, 'vertical');
288
+ tMinHeight = props.minHeight;
282
289
  }
283
290
 
284
- width = Math.max(width, tMinWidth);
285
- height = Math.max(height, tMinHeight);
291
+ width = removeUnit(width, 'horizontal') > removeUnit(tMinWidth, 'horizontal') ? width : tMinWidth;
292
+ height = removeUnit(height, 'vertical') > removeUnit(tMinHeight, 'vertical') ? height : tMinHeight;
286
293
 
287
294
  dragStyle.top = numberToUnit(top);
288
295
  dragStyle.left = numberToUnit(left);
@@ -426,6 +433,8 @@ const useMouseEvent = (param) => {
426
433
  setDragStyle({
427
434
  top: `${tempTop}px`,
428
435
  left: `${tempLeft}px`,
436
+ width: props.width,
437
+ height: props.height,
429
438
  });
430
439
  } else if (props.resizable && clickedInfo.pressedSpot === 'border') {
431
440
  resizeWindow(e);
@@ -601,20 +610,59 @@ const activeWindows = (() => {
601
610
  if (inactiveWindow === null || inactiveWindow === undefined) return;
602
611
  windows = windows.filter(activeWindow => activeWindow.sequence !== inactiveWindow.sequence);
603
612
  },
613
+
604
614
  get windows() {
605
615
  return windows.slice();
606
616
  },
607
617
  getWindowBySequence(targetSequence) {
608
618
  return windows.find(activeWindow => activeWindow.sequence === targetSequence);
609
619
  },
620
+
610
621
  isEmpty() {
611
622
  return windows.length <= 0;
612
623
  },
613
624
  isFirstWindowOpen() {
614
- return windows.length === 1;
625
+ return sequence === 1;
626
+ },
627
+ };
628
+ })();
629
+
630
+ const zIndexService = (() => {
631
+ const LOWER = 700;
632
+ const UPPER = 750;
633
+
634
+ const INCREMENT = 1;
635
+ const PADDING = INCREMENT * 2;
636
+
637
+ const UPPER_LIMIT = UPPER - PADDING;
638
+
639
+ let current = LOWER;
640
+
641
+ return {
642
+ getNext() {
643
+ if (current >= UPPER_LIMIT) {
644
+ return UPPER_LIMIT;
645
+ }
646
+ current += INCREMENT;
647
+ return current;
615
648
  },
616
- isLastWindowClose() {
617
- return windows.length === 0;
649
+ getNextOverLimit() {
650
+ return UPPER_LIMIT + INCREMENT;
651
+ },
652
+ getPrevFrom(index) {
653
+ const prev = current - (index * INCREMENT);
654
+
655
+ if (prev <= LOWER) return LOWER;
656
+ return prev;
657
+ },
658
+ isUpperLimitClose() {
659
+ return current >= UPPER_LIMIT;
660
+ },
661
+ resetToLower() {
662
+ current = LOWER;
663
+ },
664
+ getAllocableCount() {
665
+ return Math.floor((UPPER_LIMIT - LOWER) / INCREMENT);
618
666
  },
619
667
  };
620
668
  })();
@@ -627,18 +675,39 @@ const getZIndexFromElement = (element) => {
627
675
  return parseInt(zIndex);
628
676
  };
629
677
 
630
- const useEscKeydownEvent = ({ closeWin, windowRef }) => {
678
+ const getActiveWindowsOrderByZIndexAsc = () => {
679
+ // zIndex 클수록, 최근에 열린 것일수록(sequence 클수록) 뒤에 위치
680
+ const compareByZIndex = (window1, window2) => {
681
+ if (window1.zIndex > window2.zIndex) return 1;
682
+ if (window1.zIndex < window2.zIndex) return -1;
683
+
684
+ if (window1.sequence > window2.sequence) return 1;
685
+ return -1;
686
+ };
687
+
688
+ const activeWindowsSorted = Array.prototype.map.call(activeWindows.windows, activeWindow => ({
689
+ ...activeWindow,
690
+ zIndex: getZIndexFromElement(activeWindow.elem),
691
+ })).sort(compareByZIndex);
692
+
693
+ return activeWindowsSorted;
694
+ };
695
+
696
+ const useEscCloseAndFocusable = ({ closeWin, windowRef }) => {
631
697
  const { props } = getCurrentInstance();
632
698
 
633
699
  let sequence = null;
700
+ let timer = null;
634
701
 
702
+ // escClose 관련 로직 시작
635
703
  const addActiveWindow = () => {
636
- sequence = activeWindows.add({
704
+ const windowSequence = activeWindows.add({
637
705
  sequence,
638
706
  closeWin,
639
707
  elem: windowRef.value,
640
708
  escClose: props.escClose,
641
709
  });
710
+ return windowSequence;
642
711
  };
643
712
 
644
713
  const removeInactiveWindow = (inactiveWindow) => {
@@ -651,68 +720,180 @@ const useEscKeydownEvent = ({ closeWin, windowRef }) => {
651
720
  const { code } = event;
652
721
  if (code !== 'Escape') return;
653
722
 
654
- // zIndex 클수록, 최근에 열린 것일수록(sequence 클수록) 앞에 위치
655
- const compare = (window1, window2) => {
656
- if (window1.zIndex > window2.zIndex) return -1;
657
- if (window1.zIndex < window2.zIndex) return 1;
658
-
659
- if (window1.sequence > window2.sequence) return -1;
660
- return 1;
661
- };
662
-
663
- const activeWindowSorted = Array.prototype.map.call(activeWindows.windows, activeWindow => ({
664
- ...activeWindow,
665
- zIndex: getZIndexFromElement(activeWindow.elem),
666
- })).sort(compare);
667
-
668
- const topActiveWindow = activeWindowSorted[0];
723
+ const activeWindowsSorted = getActiveWindowsOrderByZIndexAsc();
724
+ const topActiveWindow = activeWindowsSorted[activeWindowsSorted.length - 1];
669
725
 
670
726
  // 예시 상황) Nested에서 외부 Window의 escClose는 true이고, 내부 Window의 escClose는 false인 경우,
671
727
  // esc 눌러도 외부 Window는 닫히지 않고, 가장 상단에 있는 내부 Window가 수동으로 닫힌 후에 닫히도록 하기 위해
672
- if (topActiveWindow.escClose === false) return;
728
+ if (!topActiveWindow.escClose) return;
673
729
 
674
730
  topActiveWindow.closeWin();
675
731
  };
676
732
 
677
- const addKeydownEvtHandler = () => {
733
+ const setWindowActive = () => {
734
+ sequence = addActiveWindow();
735
+ // DOM의 dataset에 sequence 값 추가해 식별 가능하도록
736
+ windowRef.value.dataset.sequence = sequence;
737
+
678
738
  if (activeWindows.isFirstWindowOpen()) {
679
739
  document.addEventListener('keydown', keydownEsc);
680
740
  }
681
741
  };
682
742
 
683
- const removeKeydownEvtHandler = () => {
684
- if (activeWindows.isLastWindowClose()) {
685
- document.removeEventListener('keydown', keydownEsc);
743
+ const setWindowInactive = () => {
744
+ const inactiveWindow = activeWindows.getWindowBySequence(sequence);
745
+ removeInactiveWindow(inactiveWindow);
746
+ };
747
+ // escClose 관련 로직 끝
748
+
749
+
750
+ // focusable 관련 로직 시작
751
+ const setZIndexToWindow = ({ elem, zIndex }) => {
752
+ // 모달인 경우에는 dim layer도 같이 z-index 높여준다.
753
+ if (props.isModal) {
754
+ const dimLayerElem = elem.parentElement.getElementsByClassName('ev-window-dim-layer')[0];
755
+ dimLayerElem.style.zIndex = zIndex;
756
+ }
757
+
758
+ elem.style.zIndex = zIndex;
759
+ };
760
+
761
+ const assignZIndex = () => {
762
+ // Window가 1번째로 열릴 때, z-index 값을 시작값으로 설정
763
+ if (activeWindows.windows.length === 1) {
764
+ zIndexService.resetToLower();
765
+ }
766
+
767
+ const nextZIndex = zIndexService.getNext();
768
+ setZIndexToWindow({ elem: windowRef.value, zIndex: nextZIndex });
769
+ };
770
+
771
+ const sameAsCurrent = windowData => String(windowData.sequence)
772
+ === windowRef.value.dataset.sequence;
773
+
774
+ // 할당하려는 z-index 값이 상한일 때
775
+ const reassignZIndex = () => {
776
+ const activeWindowsZIndexAsc = getActiveWindowsOrderByZIndexAsc();
777
+
778
+ const overCountLimit = activeWindows.windows.length > zIndexService.getAllocableCount();
779
+ // 할당 가능한 z-index 수보다 많은 Window를 띄웠을 때
780
+ if (overCountLimit) {
781
+ const activeWindowsZIndexDesc = activeWindowsZIndexAsc.reverse();
782
+
783
+ // z-index 기준 내림차순으로 정렬한 Window의 z-index 값을 UPPER에서 LOWER로 1씩 감소한 값 할당
784
+ let interval = 0;
785
+ activeWindowsZIndexDesc.forEach((activeWindow) => {
786
+ if (sameAsCurrent(activeWindow)) return;
787
+
788
+ const prevZIndex = zIndexService.getPrevFrom(interval++);
789
+ setZIndexToWindow({ elem: activeWindow.elem, zIndex: prevZIndex });
790
+ });
791
+
792
+ // 가장 상단으로 와야하는 현재 Window의 z-index 값을 최대로
793
+ const nextZIndex = zIndexService.getNextOverLimit();
794
+ setZIndexToWindow({ elem: windowRef.value, zIndex: nextZIndex });
795
+ } else {
796
+ zIndexService.resetToLower();
797
+
798
+ activeWindowsZIndexAsc.forEach((activeWindow) => {
799
+ if (sameAsCurrent(activeWindow)) return;
800
+
801
+ const nextZIndex = zIndexService.getNext();
802
+ setZIndexToWindow({ elem: activeWindow.elem, zIndex: nextZIndex });
803
+ });
804
+
805
+ // 가장 상단으로 와야하는 현재 Window의 z-index 값을 최대로
806
+ const nextZIndex = zIndexService.getNext();
807
+ setZIndexToWindow({ elem: windowRef.value, zIndex: nextZIndex });
808
+ }
809
+ };
810
+
811
+ const checkLimitAndSetZIndex = () => {
812
+ if (zIndexService.isUpperLimitClose()) {
813
+ reassignZIndex();
814
+ } else {
815
+ assignZIndex();
686
816
  }
687
817
  };
688
818
 
819
+ const setFocus = () => {
820
+ // X 버튼을 클릭했을 때
821
+ if (!windowRef.value) return;
822
+
823
+ // focusable prop이 false인 경우에는 z-index를 높이지 않는다.
824
+ if (!props.focusable) return;
825
+
826
+ const activeWindowsSorted = getActiveWindowsOrderByZIndexAsc();
827
+ const topActiveWindow = activeWindowsSorted[activeWindowsSorted.length - 1];
828
+
829
+ const isAlreadyTop = sameAsCurrent(topActiveWindow);
830
+ if (isAlreadyTop) return;
831
+
832
+ checkLimitAndSetZIndex();
833
+ };
834
+ // focusable 관련 로직 끝
835
+
836
+
837
+ /*
838
+ 실행 시점(=Window 열림):
839
+ visible 초기값이 true일 때 watch를 실행시키지 않아 onMounted hook 사용
840
+ */
689
841
  onMounted(() => {
690
- // visible 초기값이 true
691
842
  if (props.visible) {
692
- addActiveWindow();
693
- addKeydownEvtHandler();
843
+ setWindowActive();
844
+ timer = setTimeout(checkLimitAndSetZIndex, 0);
845
+ }
846
+ });
847
+
848
+ /*
849
+ 실행 시점(=Window 닫힘):
850
+ 예시. 배열을 통해 Window 여러 개 띄울 때, 배열 원소 삭제로 인해 해당 Window가 닫히는 경우
851
+ unmount가 발생해서 watch를 실행히시키지 않아 onBeforeUnmount hook 사용
852
+ */
853
+ onBeforeUnmount(() => {
854
+ if (props.visible) {
855
+ setWindowInactive();
856
+ clearTimeout(timer);
694
857
  }
695
858
  });
696
859
 
860
+ /*
861
+ 실행 시점:
862
+ 1. visible 값이 false -> true로 변했을 때(=Window 열림)
863
+ 2. visible 값이 true -> false로 변했을 때(=Window 닫힙)
864
+ */
697
865
  watch(
698
866
  () => props.visible,
699
- (newVal) => {
867
+ (visible) => {
700
868
  nextTick(() => {
701
- if (newVal) {
702
- addActiveWindow();
703
- addKeydownEvtHandler();
869
+ if (visible) {
870
+ // visible 값이 false -> true로 변경되었을 때
871
+ setWindowActive();
872
+ timer = setTimeout(checkLimitAndSetZIndex, 0);
704
873
  } else {
705
- const inactiveWindow = activeWindows.getWindowBySequence(sequence);
706
- removeInactiveWindow(inactiveWindow);
707
- removeKeydownEvtHandler();
874
+ // visible 값이 true -> false로 변경되었을 때
875
+ setWindowInactive();
876
+ clearTimeout(timer);
708
877
  }
709
878
  });
879
+ });
880
+
881
+ watch(
882
+ () => props.escClose,
883
+ (escClose) => {
884
+ if (!props.visible) return;
885
+ const currentWindow = activeWindows.getWindowBySequence(sequence);
886
+ if (currentWindow) currentWindow.escClose = escClose;
710
887
  },
711
888
  );
889
+
890
+ return {
891
+ setFocus,
892
+ };
712
893
  };
713
894
 
714
895
  export {
715
896
  useModel,
716
897
  useMouseEvent,
717
- useEscKeydownEvent,
898
+ useEscCloseAndFocusable,
718
899
  };