evui 3.1.55 → 3.2.0

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.
@@ -418,11 +418,14 @@ export const checkEvent = (params) => {
418
418
  * @param {array} rowData - row 데이터
419
419
  */
420
420
  const onCheck = (event, rowData) => {
421
+ let store = stores.treeStore;
422
+ if (stores.searchStore.length > 0) {
423
+ store = stores.searchStore;
424
+ }
421
425
  const isSingleMode = () => checkInfo.useCheckbox.mode === 'single';
422
- const checkedHeader = (store) => {
423
- if (checkInfo.checkedRows.length === store.length) {
424
- checkInfo.isHeaderChecked = true;
425
- }
426
+ const checkedHeader = (checkStore) => {
427
+ const isCheck = checkStore.every(n => n.checked === true);
428
+ checkInfo.isHeaderChecked = isCheck;
426
429
  };
427
430
  const unCheckedHeader = () => {
428
431
  if (checkInfo.isHeaderChecked) {
@@ -455,7 +458,7 @@ export const checkEvent = (params) => {
455
458
  onSingleMode();
456
459
  if (rowData.checked) {
457
460
  addCheckedRow(rowData);
458
- checkedHeader(stores.treeStore);
461
+ checkedHeader(store);
459
462
  } else {
460
463
  unCheckedHeader();
461
464
  removeCheckedRow(rowData);
@@ -472,10 +475,13 @@ export const checkEvent = (params) => {
472
475
  * @param {object} event - 이벤트 객체
473
476
  */
474
477
  const onCheckAll = (event) => {
475
- const store = stores.treeStore;
476
478
  const status = checkInfo.isHeaderChecked;
477
479
  const checked = [];
478
480
  let item;
481
+ let store = stores.treeStore;
482
+ if (status && stores.searchStore.length > 0) {
483
+ store = stores.searchStore;
484
+ }
479
485
  for (let ix = 0; ix < store.length; ix++) {
480
486
  item = store[ix];
481
487
  if (status) {
@@ -484,6 +490,11 @@ export const checkEvent = (params) => {
484
490
  item.checked = status;
485
491
  }
486
492
  checkInfo.checkedRows = checked;
493
+ if (stores.searchStore.length > 0) {
494
+ store.forEach((node) => {
495
+ onCheckChildren(node);
496
+ });
497
+ }
487
498
  emit('update:checked', checked);
488
499
  emit('check-all', event, checked);
489
500
  };
@@ -637,7 +648,7 @@ export const treeEvent = (params) => {
637
648
  };
638
649
 
639
650
  export const filterEvent = (params) => {
640
- const { stores, getConvertValue, calculatedColumn, updateVScroll } = params;
651
+ const { checkInfo, stores, getConvertValue, calculatedColumn, updateVScroll } = params;
641
652
  const makeParentShow = (data) => {
642
653
  if (!data?.parent) {
643
654
  return;
@@ -654,26 +665,29 @@ export const filterEvent = (params) => {
654
665
  clearTimeout(timer);
655
666
  }
656
667
  timer = setTimeout(() => {
657
- stores.treeStore.forEach((row) => {
668
+ let store = stores.treeStore;
669
+ store.forEach((row) => {
658
670
  row.show = false;
659
671
  row.isFilter = false;
660
672
  });
661
673
  if (searchWord) {
662
- const filterStores = stores.treeStore.filter((row) => {
674
+ const filterStores = store.filter((row) => {
663
675
  let isSameWord = false;
664
676
  for (let ix = 0; ix < stores.orderedColumns.length; ix++) {
665
677
  const column = stores.orderedColumns[ix] || {};
666
678
  let columnValue = row[column.field];
667
679
  let columnType = column.type;
668
680
  if (columnValue) {
669
- if (!columnType) {
670
- columnType = 'string';
671
- }
672
- columnValue = getConvertValue(columnType, columnValue).toString();
673
- isSameWord = columnValue.toLowerCase()
674
- .includes(searchWord.toString().toLowerCase());
675
- if (isSameWord) {
676
- break;
681
+ if (!column.hide && (column?.searchable === undefined || column?.searchable)) {
682
+ if (!columnType) {
683
+ columnType = 'string';
684
+ }
685
+ columnValue = getConvertValue(columnType, columnValue).toString();
686
+ isSameWord = columnValue.toLowerCase()
687
+ .includes(searchWord.toString().toLowerCase());
688
+ if (isSameWord) {
689
+ break;
690
+ }
677
691
  }
678
692
  }
679
693
  }
@@ -685,11 +699,16 @@ export const filterEvent = (params) => {
685
699
  makeParentShow(row);
686
700
  });
687
701
  } else {
688
- stores.treeStore.forEach((row) => {
702
+ store.forEach((row) => {
689
703
  row.show = true;
690
704
  row.isFilter = false;
691
705
  });
692
706
  }
707
+ if (stores.searchStore.length > 0) {
708
+ store = stores.searchStore;
709
+ }
710
+ const isCheck = store.every(n => n.checked === true);
711
+ checkInfo.isHeaderChecked = isCheck;
693
712
  calculatedColumn();
694
713
  updateVScroll();
695
714
  }, 500);
@@ -27,6 +27,7 @@
27
27
  }"
28
28
  @mousedown="startDrag"
29
29
  @mousemove="moveMouse"
30
+ @click="setFocus"
30
31
  >
31
32
  <div
32
33
  v-if="$slots.header || iconClass || title"
@@ -84,11 +85,17 @@
84
85
  </template>
85
86
 
86
87
  <script>
87
- import { useEscKeydownEvent, useModel, useMouseEvent } from './uses';
88
+ import { useEscCloseAndFocusable, useModel, useMouseEvent } from './uses';
88
89
 
89
90
  export default {
90
91
  name: 'EvWindow',
91
92
  props: {
93
+ style: {
94
+ type: Object,
95
+ default() {
96
+ return {};
97
+ },
98
+ },
92
99
  visible: {
93
100
  type: Boolean,
94
101
  default: false,
@@ -153,6 +160,10 @@ export default {
153
160
  type: Boolean,
154
161
  default: false,
155
162
  },
163
+ focusable: {
164
+ type: Boolean,
165
+ default: false,
166
+ },
156
167
  },
157
168
  emits: [
158
169
  'update:visible',
@@ -187,7 +198,7 @@ export default {
187
198
  removeUnit,
188
199
  });
189
200
 
190
- useEscKeydownEvent({ closeWin, windowRef });
201
+ const { setFocus } = useEscCloseAndFocusable({ closeWin, windowRef });
191
202
 
192
203
  return {
193
204
  windowRef,
@@ -200,6 +211,8 @@ export default {
200
211
  startDrag,
201
212
  moveMouse,
202
213
  clickExpandBtn,
214
+
215
+ setFocus,
203
216
  };
204
217
  },
205
218
  };
@@ -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
  };
Binary file