polarvo-layout 1.0.55 → 1.0.56
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
|
@@ -43,8 +43,7 @@ const props = defineProps({
|
|
|
43
43
|
|
|
44
44
|
const { setActiveDesign } = props.polarvo.display;
|
|
45
45
|
const { updateActiveElement } = props.polarvo.layout;
|
|
46
|
-
const { elements,
|
|
47
|
-
const { getElementStyle, setActiveElement, toggleLock, handleMouseDown, detectHoverCell, startResize } = props.polarvo.gridDrop;
|
|
46
|
+
const { elements, activeElement } = toRefs(props.polarvo.gridDrop.state);
|
|
48
47
|
|
|
49
48
|
const selectedElement = inject('selectedElement');
|
|
50
49
|
const activeEditMode = inject('activeEditMode');
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { cloneDeep } from 'lodash-es';
|
|
2
2
|
|
|
3
|
+
const ARROW_KEY_DELTA = {
|
|
4
|
+
ArrowUp: { x: 0, y: -1 },
|
|
5
|
+
ArrowDown: { x: 0, y: 1 },
|
|
6
|
+
ArrowLeft: { x: -1, y: 0 },
|
|
7
|
+
ArrowRight: { x: 1, y: 0 },
|
|
8
|
+
};
|
|
9
|
+
const KEY_MOVE_COMMIT_DELAY = 500; // keyup 후 이 시간(ms) 동안 추가 입력 없으면 커밋
|
|
10
|
+
|
|
3
11
|
// event 발행 시 'freeDrop'으로 시작하는 이름 사용
|
|
4
12
|
class FreeDropEngine {
|
|
5
13
|
constructor({ eventBus, options }) {
|
|
@@ -27,6 +35,9 @@ class FreeDropEngine {
|
|
|
27
35
|
direction: null, // 리사이징 방향
|
|
28
36
|
};
|
|
29
37
|
|
|
38
|
+
this._keyMoveActive = false; // 방향키 이동 세션 진행 여부
|
|
39
|
+
this._keyMoveCommitTimer = null;
|
|
40
|
+
|
|
30
41
|
this._startPos = { x: 0, y: 0 }; // 드래그 시작 위치
|
|
31
42
|
this._startSize = { w: 0, h: 0 }; // 드래그 시작 크기
|
|
32
43
|
this._offsetPos = { x: 0, y: 0 }; // 마우스 오프셋 위치
|
|
@@ -35,6 +46,9 @@ class FreeDropEngine {
|
|
|
35
46
|
|
|
36
47
|
this._subscriptions = []; // 구독 해제 함수 저장용
|
|
37
48
|
this._setupSubscriptions();
|
|
49
|
+
|
|
50
|
+
document.addEventListener('keydown', this._handleKeyDown);
|
|
51
|
+
document.addEventListener('keyup', this._handleKeyUp);
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
/** ---------------------------------- 구독 관련 메소드 ---------------------------------- **/
|
|
@@ -174,6 +188,11 @@ class FreeDropEngine {
|
|
|
174
188
|
|
|
175
189
|
/** 삭제 및 정리 */
|
|
176
190
|
destroy() {
|
|
191
|
+
clearTimeout(this._keyMoveCommitTimer);
|
|
192
|
+
|
|
193
|
+
document.removeEventListener('keydown', this._handleKeyDown);
|
|
194
|
+
document.removeEventListener('keyup', this._handleKeyUp);
|
|
195
|
+
|
|
177
196
|
this._subscriptions.forEach((unsubscribe) => {
|
|
178
197
|
if (typeof unsubscribe === 'function') {
|
|
179
198
|
unsubscribe();
|
|
@@ -235,6 +254,8 @@ class FreeDropEngine {
|
|
|
235
254
|
isResizing: false,
|
|
236
255
|
direction: null,
|
|
237
256
|
};
|
|
257
|
+
this._keyMoveActive = false; // 방향키 이동 세션 진행 여부
|
|
258
|
+
this._keyMoveCommitTimer = null;
|
|
238
259
|
|
|
239
260
|
this.guides = { x: null, y: null, w: null, h: null };
|
|
240
261
|
this._startPos = { x: 0, y: 0 };
|
|
@@ -455,39 +476,56 @@ class FreeDropEngine {
|
|
|
455
476
|
};
|
|
456
477
|
}
|
|
457
478
|
|
|
479
|
+
_getLayoutRect() {
|
|
480
|
+
const layoutEl = document.getElementById(`${this._activeSection}Layout`);
|
|
481
|
+
if (!layoutEl) {
|
|
482
|
+
console.error('[FreeDropEngine] 레이아웃 요소를 찾을 수 없습니다:', this._activeSection);
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
return layoutEl.getBoundingClientRect();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
_clampToLayoutBounds(rawX, rawY, layoutRect) {
|
|
489
|
+
const maxX = layoutRect.width - this._activeElement.size.w;
|
|
490
|
+
const maxY = layoutRect.height - this._activeElement.size.h;
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
x: Math.min(Math.max(0, rawX), maxX),
|
|
494
|
+
y: Math.min(Math.max(0, rawY), maxY),
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
458
498
|
/** [내부함수] 활성화 배치요소의 현재 위치 가져오기
|
|
459
499
|
* @param {MouseEvent} event - 마우스 이벤트
|
|
460
500
|
* @param {Object} offsetPos - 오프셋 위치 { x, y }
|
|
461
501
|
* @return {Object} - 활성화 배치요소의 그리드 스냅 위치 { x, y }
|
|
462
502
|
*/
|
|
463
503
|
_getActiveElementPosition(event, offsetPos) {
|
|
464
|
-
const
|
|
465
|
-
if (!
|
|
466
|
-
console.error('[FreeDropEngine] 레이아웃 요소를 찾을 수 없습니다:', this._activeSection);
|
|
467
|
-
return { x: 0, y: 0 };
|
|
468
|
-
}
|
|
504
|
+
const layoutRect = this._getLayoutRect();
|
|
505
|
+
if (!layoutRect) return { x: 0, y: 0 };
|
|
469
506
|
|
|
470
507
|
if (!this._activeElement) {
|
|
471
508
|
console.error('[FreeDropEngine] 활성화된 배치요소를 찾을 수 없습니다.');
|
|
472
509
|
return { x: 0, y: 0 };
|
|
473
510
|
}
|
|
474
511
|
|
|
475
|
-
const layoutRect = layoutEl.getBoundingClientRect();
|
|
476
512
|
const rawX = event.clientX - layoutRect.left - offsetPos.x;
|
|
477
513
|
const rawY = event.clientY - layoutRect.top - offsetPos.y;
|
|
478
514
|
|
|
479
|
-
const maxX = layoutRect.width - this._activeElement.size.w;
|
|
480
|
-
const maxY = layoutRect.height - this._activeElement.size.h;
|
|
515
|
+
// const maxX = layoutRect.width - this._activeElement.size.w;
|
|
516
|
+
// const maxY = layoutRect.height - this._activeElement.size.h;
|
|
517
|
+
|
|
518
|
+
// // 클램핑: 경계 밖으로 나가도 경계에서 멈추고 마우스를 계속 추적
|
|
519
|
+
// const clampedX = Math.min(Math.max(0, rawX), maxX);
|
|
520
|
+
// const clampedY = Math.min(Math.max(0, rawY), maxY);
|
|
481
521
|
|
|
482
|
-
|
|
483
|
-
const clampedX = Math.min(Math.max(0, rawX), maxX);
|
|
484
|
-
const clampedY = Math.min(Math.max(0, rawY), maxY);
|
|
522
|
+
const clamped = this._clampToLayoutBounds(rawX, rawY, layoutRect);
|
|
485
523
|
|
|
486
524
|
// const gridX = this._snapToGrid(rawX);
|
|
487
525
|
// const gridY = this._snapToGrid(rawY);
|
|
488
526
|
|
|
489
|
-
const gridX = this._snapToGrid(
|
|
490
|
-
const gridY = this._snapToGrid(
|
|
527
|
+
const gridX = this._snapToGrid(clamped.x);
|
|
528
|
+
const gridY = this._snapToGrid(clamped.y);
|
|
491
529
|
|
|
492
530
|
return { x: gridX, y: gridY };
|
|
493
531
|
}
|
|
@@ -604,6 +642,68 @@ class FreeDropEngine {
|
|
|
604
642
|
};
|
|
605
643
|
})();
|
|
606
644
|
|
|
645
|
+
/** ---------------------------------- 키보드 이동 관련 메소드 ---------------------------------- **/
|
|
646
|
+
_handleKeyDown = (event) => {
|
|
647
|
+
const delta = ARROW_KEY_DELTA[event.key];
|
|
648
|
+
if (!delta) return;
|
|
649
|
+
|
|
650
|
+
// 텍스트 입력 중이면 방향키를 그대로 입력창에 넘김
|
|
651
|
+
const activeTag = document.activeElement?.tagName;
|
|
652
|
+
if (activeTag === 'INPUT' || activeTag === 'TEXTAREA' || document.activeElement?.isContentEditable) return;
|
|
653
|
+
|
|
654
|
+
if (this._activeMode !== 'free') return;
|
|
655
|
+
if (!this._activeElement || this._activeElement.isLocked) return;
|
|
656
|
+
|
|
657
|
+
// 마우스 드래그/리사이즈 중이면 방향키 이동 무시
|
|
658
|
+
if (this._dragState.isDragging || this._dragState.isResizing) return;
|
|
659
|
+
|
|
660
|
+
// 방향키로 인한 페이지 스크롤 등 기본 동작 방지
|
|
661
|
+
event.preventDefault();
|
|
662
|
+
|
|
663
|
+
// 새로운 keydown -> 기존 keyMoveCommitTimer 초기화
|
|
664
|
+
clearTimeout(this._keyMoveCommitTimer);
|
|
665
|
+
|
|
666
|
+
const layoutRect = this._getLayoutRect();
|
|
667
|
+
if (!layoutRect) return;
|
|
668
|
+
|
|
669
|
+
const step = this._gridSize || 1;
|
|
670
|
+
const rawX = this._activeElement.position.x + delta.x * step;
|
|
671
|
+
const rawY = this._activeElement.position.y + delta.y * step;
|
|
672
|
+
|
|
673
|
+
const clamped = this._clampToLayoutBounds(rawX, rawY, layoutRect);
|
|
674
|
+
|
|
675
|
+
this._keyMoveActive = true;
|
|
676
|
+
this._updateElementBounds(clamped);
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
_handleKeyUp = (event) => {
|
|
680
|
+
const delta = ARROW_KEY_DELTA[event.key];
|
|
681
|
+
if (!delta) return;
|
|
682
|
+
if (!this._keyMoveActive) return;
|
|
683
|
+
|
|
684
|
+
clearTimeout(this._keyMoveCommitTimer);
|
|
685
|
+
this._keyMoveCommitTimer = setTimeout(() => {
|
|
686
|
+
this._commitKeyMove();
|
|
687
|
+
}, KEY_MOVE_COMMIT_DELAY);
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
_commitKeyMove() {
|
|
691
|
+
this._keyMoveActive = false;
|
|
692
|
+
this._keyMoveCommitTimer = null;
|
|
693
|
+
this.guides = { x: null, y: null, w: null, h: null };
|
|
694
|
+
|
|
695
|
+
if (!this._activeElement) return;
|
|
696
|
+
|
|
697
|
+
this.eventBus.emit('freeDrop:requestUpdateElement', {
|
|
698
|
+
historyEvent: true,
|
|
699
|
+
elementId: this._activeElement.id,
|
|
700
|
+
position: this._activeElement.position,
|
|
701
|
+
size: this._activeElement.size,
|
|
702
|
+
guides: this.guides,
|
|
703
|
+
timestamp: Date.now(),
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
|
|
607
707
|
/** ---------------------------------- 드래그 관련 메소드 ---------------------------------- **/
|
|
608
708
|
/** mousedown 실행
|
|
609
709
|
* @param {MouseEvent} event - 마우스 이벤트
|