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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polarvo-layout",
3
- "version": "1.0.55",
3
+ "version": "1.0.56",
4
4
  "type": "module",
5
5
  "author": "unigence <unigencelab@gmail.com>",
6
6
  "repository": {
@@ -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, activeId, activeCell, activeElement } = toRefs(props.polarvo.gridDrop.state);
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 layoutEl = document.getElementById(`${this._activeSection}Layout`);
465
- if (!layoutEl) {
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(clampedX);
490
- const gridY = this._snapToGrid(clampedY);
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 - 마우스 이벤트