polarvo-layout 1.0.26 → 1.0.28

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.
Files changed (31) hide show
  1. package/package.json +1 -1
  2. package/src/components/Layout/BaseLayout.vue +4 -3
  3. package/src/components/Layout/CanvasContainer.vue +5 -7
  4. package/src/components/Layout/FreeItem.vue +7 -4
  5. package/src/components/Layout/FreeLayout.vue +2 -3
  6. package/src/components/Layout/GridLayout.vue +41 -14
  7. package/src/components/Layout/PolarLayout.vue +1 -0
  8. package/src/core/engines/DisplayEngine.js +12 -15
  9. package/src/core/engines/FreeDropEngine.js +100 -55
  10. package/src/core/engines/GridDropEngine.js +32 -12
  11. package/src/core/engines/HistoryEngine.js +27 -240
  12. package/src/core/engines/LayoutEngine.js +29 -31
  13. package/src/core/engines/originals/DisplayEngine_0423.js +247 -0
  14. package/src/core/engines/originals/{FreeDropEngine_0416.js → FreeDropEngine_0423.js} +88 -54
  15. package/src/core/engines/originals/{GridDropEngine_0402.js → GridDropEngine_0423.js} +39 -39
  16. package/src/core/engines/originals/HistoryEngine_0423.js +336 -0
  17. package/src/core/engines/originals/LayoutEngine_0423.js +346 -0
  18. package/src/core/managers/EngineManager.js +174 -36
  19. package/src/core/managers/originals/{EngineManager_0402.js → EngineManager_0423.js} +181 -151
  20. package/src/library/DisplayLibrary.js +11 -7
  21. package/src/library/FreeDropLibrary.js +7 -9
  22. package/src/library/GridDropLibrary.js +8 -9
  23. package/src/library/HistoryLibrary.js +2 -2
  24. package/src/library/LayoutLibrary.js +32 -13
  25. package/src/library/originals/{DisplayLibrary_0402.js → DisplayLibrary_0423.js} +15 -19
  26. package/src/library/originals/{FreeDropLibrary_0416.js → FreeDropLibrary_0423.js} +17 -8
  27. package/src/library/originals/{GridDropLibrary_0402.js → GridDropLibrary_0423.js} +5 -8
  28. package/src/library/originals/{HistoryLibrary_0402.js → HistoryLibrary_0423.js} +1 -2
  29. package/src/library/originals/{LayoutLibrary_0402.js → LayoutLibrary_0423.js} +34 -47
  30. package/src/components/Layout/originals/FreeLayout_0416.vue +0 -284
  31. package/src/components/Layout/originals/GridLayout_0416.vue +0 -202
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polarvo-layout",
3
- "version": "1.0.26",
3
+ "version": "1.0.28",
4
4
  "type": "module",
5
5
  "author": "unigence <unigencelab@gmail.com>",
6
6
  "repository": {
@@ -68,9 +68,9 @@ const props = defineProps({
68
68
  });
69
69
 
70
70
  const layoutType = computed(() => {
71
- if (layoutName.value.eng === null) return 'no-split';
72
- return layoutName.value.eng
73
- .replace(/([A-Z])/g, '-$1')
71
+ if (layoutName.value?.eng === null) return 'no-split';
72
+ return layoutName.value?.eng
73
+ ?.replace(/([A-Z])/g, '-$1')
74
74
  .toLowerCase()
75
75
  .substring(1);
76
76
  });
@@ -88,6 +88,7 @@ const getBaseStyle = computed(() => {
88
88
 
89
89
  const getSectionStyle = (section) => ({
90
90
  '--grid-columns': section.config?.gridColumns ?? 3,
91
+ '--grid-rows': section.config?.gridRows ?? 3,
91
92
  '--grid-gap': `${section.config?.gridGap ?? 5}px`,
92
93
  position: 'relative',
93
94
  });
@@ -13,7 +13,7 @@
13
13
  </template>
14
14
 
15
15
  <script setup>
16
- import { toRefs, ref, computed, watch, provide } from 'vue';
16
+ import { toRefs, ref, computed, watch } from 'vue';
17
17
  import BaseLayout from './BaseLayout.vue';
18
18
 
19
19
  const props = defineProps({
@@ -36,7 +36,7 @@ const { elements } = toRefs(props.polarvo.layout.state);
36
36
  const defaultHeight = computed(() => {
37
37
  return (
38
38
  elements.value?.reduce((max, el) => {
39
- const bottom = (el.position?.y || 0) + (el.size?.h || 0) ;
39
+ const bottom = (el.position?.y || 0) + (el.size?.h || 0);
40
40
  return Math.max(max, bottom);
41
41
  }, 750) || 750
42
42
  );
@@ -66,13 +66,13 @@ const containerStyle = computed(() => {
66
66
  margin: '0 auto',
67
67
  transform: `scale(${DEFAULT_DISPLAY_SIZE.percent / 100})`,
68
68
  transformOrigin: 'top center',
69
- backgroundImage: `
69
+ backgroundImage: `
70
70
  linear-gradient(rgba(0,0,0,0.05) 1px, transparent 1px),
71
71
  linear-gradient(90deg, rgba(0,0,0,0.05) 1px, transparent 1px)
72
72
  `,
73
- backgroundSize: `${DEFAULT_DISPLAY_SIZE.gridSize}px ${DEFAULT_DISPLAY_SIZE.gridSize}px`,
73
+ backgroundSize: `${DEFAULT_DISPLAY_SIZE.gridSize}px ${DEFAULT_DISPLAY_SIZE.gridSize}px`,
74
+ };
74
75
  }
75
- }
76
76
 
77
77
  return {
78
78
  aspectRatio: displaySize.value.aspectRatio,
@@ -87,8 +87,6 @@ const containerStyle = computed(() => {
87
87
  `,
88
88
  backgroundSize: `${displaySize.value.gridSize}px ${displaySize.value.gridSize}px`,
89
89
  };
90
-
91
-
92
90
  });
93
91
 
94
92
  // 전체 elements
@@ -5,7 +5,10 @@
5
5
  :style="itemStyle"
6
6
  :class="{ 'z-50': item.id === activeId, 'bg-white border-2 border-blue-400': item.id === activeId }"
7
7
  @mousedown.stop="activeEditMode ? null : handleMouseDown($event, item.id)"
8
- >
8
+ >
9
+ <!-- 크기-->
10
+ <div v-if="item.id === activeId" class="absolute bg-blue-400 px-2 py-1 text-sm text-white right-0">
11
+ 가로: {{ size.w }} 세로: {{ size.h }}</div>
9
12
 
10
13
  <!-- 리사이즈 핸들 -->
11
14
  <div v-if="item.id === activeId" class="absolute inset-0 pointer-events-none">
@@ -56,8 +59,9 @@ const props = defineProps({
56
59
  sectionKey: { type: String, required: true },
57
60
  });
58
61
 
59
- const { activeId, handles } = toRefs(props.polarvo.freeDrop.state);
62
+ const { activeId, handles, activeElement } = toRefs(props.polarvo.freeDrop.state);
60
63
  const { getElementStyle, handleMouseDown, startResize, toggleLock } = props.polarvo.freeDrop;
64
+ const size = computed(() => activeElement.value ? activeElement.value.size : { w: 0, h: 0 });
61
65
 
62
66
  const activeEditMode = inject('activeEditMode');
63
67
  function toggleEditMode() {
@@ -94,7 +98,6 @@ function setRef(el) {
94
98
  </script>
95
99
 
96
100
  <style lang="scss" scoped>
97
-
98
101
  .handle {
99
102
  // 모서리
100
103
  &.nw {
@@ -166,4 +169,4 @@ function setRef(el) {
166
169
  cursor: e-resize;
167
170
  }
168
171
  }
169
- </style>
172
+ </style>
@@ -7,7 +7,7 @@
7
7
  :polarvo="props.polarvo"
8
8
  :sectionKey="props.sectionKey"
9
9
  ></FreeItem>
10
-
10
+
11
11
  <!-- 오버레이 -->
12
12
  <div v-if="activeEditMode" class="absolute inset-0 bg-black opacity-50"></div>
13
13
 
@@ -61,7 +61,7 @@ const { setActiveDesign } = props.polarvo.display;
61
61
  const { updateActiveElement } = props.polarvo.layout;
62
62
  const { elements, guides, activeElement } = toRefs(props.polarvo.freeDrop.state);
63
63
  const filteredElements = computed(() => {
64
- return elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
64
+ return elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey) ?? [];
65
65
  });
66
66
 
67
67
  const selectedElement = inject('selectedElement');
@@ -112,7 +112,6 @@ onUnmounted(() => {
112
112
  </script>
113
113
 
114
114
  <style lang="scss" scoped>
115
-
116
115
  .guide {
117
116
  &.vertical {
118
117
  top: 0;
@@ -16,7 +16,7 @@
16
16
  <script setup>
17
17
  import GridItem from './GridItem.vue';
18
18
 
19
- import { toRefs, getCurrentInstance, onMounted, onUnmounted, inject, watch, computed } from 'vue';
19
+ import { ref, toRefs, getCurrentInstance, onMounted, onUnmounted, inject, watch, computed } from 'vue';
20
20
 
21
21
  const props = defineProps({
22
22
  polarvo: {
@@ -46,16 +46,45 @@ const { updateActiveElement } = props.polarvo.layout;
46
46
  const { elements, activeId, activeCell, activeElement } = toRefs(props.polarvo.gridDrop.state);
47
47
  const { getElementStyle, setActiveElement, toggleLock, handleMouseDown, detectHoverCell, startResize } = props.polarvo.gridDrop;
48
48
 
49
- const mergedData = computed(() => {
50
- const { gridColumns, gridRows } = props.sectionData?.config ?? { gridColumns: 3, gridRows: 3 };
51
- const grid = Array.from({ length: gridRows + 1 }, () => Array(gridColumns + 1).fill(false));
49
+ const selectedElement = inject('selectedElement');
50
+ const activeEditMode = inject('activeEditMode');
51
+
52
+ import { omit, cloneDeep, debounce } from 'lodash-es';
53
+
54
+ const gridColumns = ref(3); // 동적으로 계산
55
+ const gridRows = ref(1); // 동적으로 계산
56
+
57
+ function initData() {
58
+ gridColumns.value = props.sectionData?.config?.gridColumns || 3;
59
+ gridRows.value = props.sectionData?.config?.gridRows || 1;
52
60
 
53
- const filled = elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
61
+ setMergedData();
62
+ }
54
63
 
55
- filled.forEach(({ position: { col, row }, size: { colSpan, rowSpan } }) => {
64
+ const mergedData = ref([]); // 필드 데이터 + 데이터 통합 리스트
65
+ const filteredElements = computed(() => {
66
+ return elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey) ?? [];
67
+ });
68
+ watch(
69
+ () => filteredElements.value,
70
+ () => {
71
+ debouncedUpdate();
72
+ },
73
+ { deep: true },
74
+ );
75
+ const debouncedUpdate = debounce(() => {
76
+ setMergedData();
77
+ // gridRows 업데이트
78
+ }, 10);
79
+
80
+ function setMergedData() {
81
+ const grid = Array.from({ length: gridRows.value + 1 }, () => Array(gridColumns.value + 1).fill(false));
82
+ // const filled = elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
83
+
84
+ filteredElements.value.forEach(({ position: { col, row }, size: { colSpan, rowSpan } }) => {
56
85
  for (let r = row; r < row + rowSpan; r++) {
57
86
  for (let c = col; c < col + colSpan; c++) {
58
- if (r >= 1 && c >= 1 && r <= gridRows && c <= gridColumns) {
87
+ if (r >= 1 && c >= 1 && r <= gridRows.value && c <= gridColumns.value) {
59
88
  grid[r][c] = true;
60
89
  }
61
90
  }
@@ -63,16 +92,12 @@ const mergedData = computed(() => {
63
92
  });
64
93
 
65
94
  const empty = [];
66
- for (let r = 1; r <= gridRows; r++) for (let c = 1; c <= gridColumns; c++) if (!grid[r][c]) empty.push({ row: r, col: c });
67
-
68
- return [...filled, ...empty];
69
- });
95
+ for (let r = 1; r <= gridRows.value; r++) for (let c = 1; c <= gridColumns.value; c++) if (!grid[r][c]) empty.push({ row: r, col: c });
70
96
 
71
- const selectedElement = inject('selectedElement');
72
- const activeEditMode = inject('activeEditMode');
97
+ mergedData.value = [...filteredElements.value, ...empty];
98
+ }
73
99
 
74
100
  let _isElementSwitching = false;
75
- import { omit, cloneDeep } from 'lodash-es';
76
101
  watch(
77
102
  () => activeElement.value?.id,
78
103
  (newId, oldId) => {
@@ -108,6 +133,8 @@ watch(
108
133
  onMounted(() => {
109
134
  props.polarvo.components.register('sections', props.sectionKey, getCurrentInstance());
110
135
  activeEditMode.value = false;
136
+
137
+ initData();
111
138
  });
112
139
 
113
140
  onUnmounted(() => {
@@ -17,6 +17,7 @@ const props = defineProps({
17
17
  .polar-grid {
18
18
  display: grid;
19
19
  grid-template-columns: repeat(var(--grid-columns, 3), 1fr);
20
+ grid-template-rows: repeat(var(--grid-rows, 3), 1fr);
20
21
  gap: var(--grid-gap, 5px);
21
22
  width: 100%;
22
23
  height: 100%;
@@ -1,3 +1,5 @@
1
+ import { cloneDeep } from 'lodash';
2
+
1
3
  // event 발행 시 'display'으로 시작하는 이름 사용
2
4
  const DISPLAY_LIMITS = {
3
5
  percent: { max: 200, min: 0, unit: '%', name: '배율' },
@@ -110,12 +112,10 @@ class DisplayEngine {
110
112
  * @return {object} - 현재 상태 객체
111
113
  */
112
114
  getState() {
113
- return {
115
+ return cloneDeep({
114
116
  displayMode: this.displayMode,
115
- displaySize: { ...this.displaySize },
116
- activeMenu: this.activeMenu,
117
- activeDesign: this.activeDesign,
118
- };
117
+ displaySize: this.displaySize,
118
+ });
119
119
  }
120
120
 
121
121
  /** 상태 정보 설정
@@ -124,13 +124,6 @@ class DisplayEngine {
124
124
  setState(state) {
125
125
  this.displayMode = state.displayMode;
126
126
  this.displaySize = { ...state.displaySize };
127
- this.activeMenu = state.activeMenu;
128
- this.activeDesign = state.activeDesign;
129
-
130
- this.eventBus.emit('display:restoredState', {
131
- stateData: state,
132
- timestamp: Date.now(),
133
- });
134
127
  }
135
128
 
136
129
  /** ---------------------------------- 공통 메소드 ---------------------------------- **/
@@ -214,14 +207,18 @@ class DisplayEngine {
214
207
  }
215
208
  const numSize = Number(size);
216
209
  const limit = DISPLAY_LIMITS[type];
217
- const result = {result: true, message: '사이즈가 성공적으로 변경되었습니다.'};
210
+ const result = { result: true, message: '사이즈가 성공적으로 변경되었습니다.' };
211
+
212
+ if (isNaN(numSize)) {
213
+ return { result: false, message: '유효한 숫자를 입력해주세요.' };
214
+ }
218
215
 
219
- if (isNaN(numSize) || numSize <= limit.min) {
216
+ if (numSize <= limit.min) {
220
217
  result.result = false;
221
218
  result.message = `설정 가능한 최소 ${limit.name}는 ${limit.min}${limit.unit}입니다.`;
222
219
  }
223
220
 
224
- if (isNaN(numSize) || numSize > limit.max) {
221
+ if (numSize > limit.max) {
225
222
  result.result = false;
226
223
  result.message = `설정 가능한 최대 ${limit.name}는 ${limit.max}${limit.unit}입니다.`;
227
224
  }
@@ -52,19 +52,8 @@ class FreeDropEngine {
52
52
  /** [내부함수] 구독 설정 */
53
53
  _setupSubscriptions() {
54
54
  // 초기화 완료 이벤트 구독 - from DisplayEngine
55
- this._subscribe('display:engineInitialized', ({ $displaySize }) => {
56
- const { gridSize } = $displaySize;
57
- this._gridSize = gridSize;
58
-
59
- this._initialize();
60
- });
61
-
62
- this._subscribe('display:enginesReset', ({ $displaySize }) => {
63
- const { gridSize } = $displaySize;
64
- this._gridSize = gridSize;
65
-
66
- this._initialize();
67
- });
55
+ this._subscribe('display:engineInitialized', this._handleDisplayInit.bind(this));
56
+ this._subscribe('display:enginesReset', this._handleDisplayInit.bind(this));
68
57
 
69
58
  // displayMode 변경 감지 (displaySize가 함께 초기화됨)
70
59
  this._subscribe('display:displayChanged', ({ $displaySize }) => {
@@ -110,6 +99,7 @@ class FreeDropEngine {
110
99
  this._elementsUpdate = true;
111
100
 
112
101
  this.setActiveElement(this._elements.find((x) => x.id === $elementId));
102
+
113
103
  this.eventBus.emit('freeDrop:requestUpdateData', {
114
104
  elements: cloneDeep(this.freeElements),
115
105
  guides: this.guides,
@@ -133,20 +123,45 @@ class FreeDropEngine {
133
123
  });
134
124
  });
135
125
 
136
- // this._subscribe('system:requestElementsScale', ({ $elements }) => {
137
- // this._elements = $elements;
138
- // this._elementsUpdate = true;
139
- // });
140
-
141
- this._subscribe('system:restoredState', ({ stateData }) => {
142
- this._elements = stateData.elements;
126
+ this._subscribe('system:requestElementsScale', ({ $elements }) => {
127
+ this._elements = $elements;
143
128
  this._elementsUpdate = true;
144
- this.setActiveElement(null);
129
+ });
145
130
 
146
- this.eventBus.emit('freeDrop:restoredState', {
147
- elements: cloneDeep(this.freeElements),
148
- timestamp: Date.now(),
149
- });
131
+ this._subscribe('system:restoredState', ({ domain, state: snapShot }) => {
132
+ if (domain === 'all') {
133
+ this._elements = snapShot.manager.elements || [];
134
+ this._elementsUpdate = true;
135
+ this.setActiveElement(null);
136
+
137
+ this.eventBus.emit('freeDrop:restoredState', {
138
+ elements: cloneDeep(this.freeElements),
139
+ timestamp: Date.now(),
140
+ });
141
+
142
+
143
+ this._gridNumber = snapShot.layout.gridNumber || null;
144
+ this._gridSize = snapShot.display.displaySize?.gridSize || null;
145
+ }
146
+
147
+ if (domain === 'manager') {
148
+ this._elements = snapShot.elements || [];
149
+ this._elementsUpdate = true;
150
+ this.setActiveElement(null);
151
+
152
+ this.eventBus.emit('freeDrop:restoredState', {
153
+ elements: cloneDeep(this.freeElements),
154
+ timestamp: Date.now(),
155
+ });
156
+ }
157
+
158
+ if (domain === 'layout') {
159
+ this._gridNumber = snapShot.gridNumber || null;
160
+ }
161
+
162
+ if (domain === 'display') {
163
+ this._gridSize = snapShot.displaySize?.gridSize || null;
164
+ }
150
165
  });
151
166
  }
152
167
 
@@ -231,6 +246,13 @@ class FreeDropEngine {
231
246
  this._alreadyExist = false;
232
247
 
233
248
  this._elementsUpdate = false;
249
+ this._elements = [];
250
+ this._freeElements = [];
251
+ }
252
+
253
+ _handleDisplayInit({ $displaySize }) {
254
+ this._gridSize = $displaySize.gridSize;
255
+ this._initialize();
234
256
  }
235
257
 
236
258
  /** [내부함수] 그리드 스냅
@@ -265,7 +287,11 @@ class FreeDropEngine {
265
287
  const x = event.clientX - offsetPos.x + size.w;
266
288
  const y = event.clientY - offsetPos.y + size.h;
267
289
 
268
- const result = event.clientX >= layoutRect.left && event.clientY >= layoutRect.top && x <= layoutRect.right && y <= layoutRect.bottom;
290
+ const result =
291
+ event.clientX - offsetPos.x >= layoutRect.left &&
292
+ event.clientY - offsetPos.y >= layoutRect.top &&
293
+ x <= layoutRect.right &&
294
+ y <= layoutRect.bottom;
269
295
 
270
296
  return result;
271
297
  }
@@ -275,7 +301,7 @@ class FreeDropEngine {
275
301
  */
276
302
  _detectElementCollision() {
277
303
  if (!this._activeElement) {
278
- console.error('[FreeDropEngine] 충돌 검사를 위한 아이템 정보를 찾을 수 없습니다:', this._activeElement.id);
304
+ console.error('[FreeDropEngine] 충돌 검사를 위한 아이템 정보를 찾을 수 없습니다:', this._activeElement?.id || 'unknown');
279
305
  return false;
280
306
  }
281
307
 
@@ -359,13 +385,13 @@ class FreeDropEngine {
359
385
  this._offsetPos.x = element.size.w / 2;
360
386
  this._offsetPos.y = element.size.h / 2;
361
387
 
362
- // this.startDrag(event, null);
363
- // 신규 아이템 추가 시 바로 드래그 상태로 진입 (단, 기존 아이템과 달리 위치 기준이 마우스 포인터가 됨)
364
- this.eventBus.emit('freeDrop:startDrag', { timestamp: Date.now() });
388
+ this._startDrag();
389
+ // // 신규 아이템 추가 시 바로 드래그 상태로 진입 (단, 기존 아이템과 달리 위치 기준이 마우스 포인터가 됨)
390
+ // this.eventBus.emit('freeDrop:startDrag', { timestamp: Date.now() });
365
391
 
366
- this._dragState.isDragging = true;
367
- document.addEventListener('mousemove', this._startDragMove);
368
- document.addEventListener('mouseup', this._stopDrag);
392
+ // this._dragState.isDragging = true;
393
+ // document.addEventListener('mousemove', this._startDragMove);
394
+ // document.addEventListener('mouseup', this._stopDrag);
369
395
  }
370
396
 
371
397
  /** [내부함수] 배치요소 삭제
@@ -398,12 +424,11 @@ class FreeDropEngine {
398
424
 
399
425
  this._activeElement = element;
400
426
  this._alreadyExist = action === 'add' ? false : true;
401
- this._elementsUpdate = true;
427
+ // this._elementsUpdate = true;
402
428
 
403
429
  this.eventBus.emit('freeDrop:setActiveElement', {
404
- action: action,
430
+ $alreadyExist: this._alreadyExist,
405
431
  activeElement: this._activeElement,
406
- // elements: cloneDeep(this.freeElements),
407
432
  timestamp: Date.now(),
408
433
  });
409
434
  }
@@ -413,6 +438,7 @@ class FreeDropEngine {
413
438
  * @returns {object} - 스타일 객체
414
439
  */
415
440
  getElementStyle(element) {
441
+ if (!element?.position || !element?.size) return {};
416
442
  const { x, y } = element.position;
417
443
  const { w, h } = element.size;
418
444
 
@@ -440,8 +466,18 @@ class FreeDropEngine {
440
466
  const rawX = event.clientX - layoutRect.left - offsetPos.x;
441
467
  const rawY = event.clientY - layoutRect.top - offsetPos.y;
442
468
 
443
- const gridX = this._snapToGrid(rawX);
444
- const gridY = this._snapToGrid(rawY);
469
+ const maxX = layoutRect.width - this._activeElement.size.w;
470
+ const maxY = layoutRect.height - this._activeElement.size.h;
471
+
472
+ // 클램핑: 경계 밖으로 나가도 경계에서 멈추고 마우스를 계속 추적
473
+ const clampedX = Math.min(Math.max(0, rawX), maxX);
474
+ const clampedY = Math.min(Math.max(0, rawY), maxY);
475
+
476
+ // const gridX = this._snapToGrid(rawX);
477
+ // const gridY = this._snapToGrid(rawY);
478
+
479
+ const gridX = this._snapToGrid(clampedX);
480
+ const gridY = this._snapToGrid(clampedY);
445
481
 
446
482
  return { x: gridX, y: gridY };
447
483
  }
@@ -523,7 +559,6 @@ class FreeDropEngine {
523
559
  this.guides.h = this._activeElement.size.h;
524
560
  }
525
561
 
526
-
527
562
  // Vue 반응성 사이클 없이 직접 DOM 조작
528
563
  const el = document.getElementById(this._activeElement.id);
529
564
  if (el) {
@@ -550,7 +585,6 @@ class FreeDropEngine {
550
585
  // timestamp: Date.now(),
551
586
  // });
552
587
 
553
-
554
588
  rafId = null;
555
589
  });
556
590
  };
@@ -572,6 +606,10 @@ class FreeDropEngine {
572
606
  }
573
607
 
574
608
  const element = this.freeElements.find((el) => el.id === id);
609
+ if (!element) {
610
+ console.error('[FreeDropEngine] 배치요소를 찾을 수 없습니다. ID:', id);
611
+ return false;
612
+ }
575
613
  this.setActiveElement(element, 'click');
576
614
 
577
615
  // 배치요소 잠겨 있으면 드래그/삭제 불가
@@ -587,10 +625,13 @@ class FreeDropEngine {
587
625
  // 배치요소 위치 기준 오프셋 계산
588
626
  const elementRect = elementEl.getBoundingClientRect();
589
627
  if (elementRect) {
590
- const { column, row } = this._gridNumber;
628
+ // const { column, row } = this._gridNumber;
591
629
 
592
- this._offsetPos.x = (event.clientX - elementRect.left) / column;
593
- this._offsetPos.y = (event.clientY - elementRect.top) / row;
630
+ // this._offsetPos.x = (event.clientX - elementRect.left) / column;
631
+ // this._offsetPos.y = (event.clientY - elementRect.top) / row;
632
+
633
+ this._offsetPos.x = event.clientX - elementRect.left;
634
+ this._offsetPos.y = event.clientY - elementRect.top;
594
635
  }
595
636
 
596
637
  document.addEventListener('mousemove', this._detectDragIntent);
@@ -610,12 +651,7 @@ class FreeDropEngine {
610
651
  document.removeEventListener('mousemove', this._detectDragIntent);
611
652
  document.removeEventListener('mouseup', this._cancelDragIntent);
612
653
 
613
- // 드래그 확정 후 실제 드래그 리스너 등록
614
- this._dragState.isDragging = true;
615
-
616
- this.eventBus.emit('freeDrop:startDrag', { timestamp: Date.now() });
617
- document.addEventListener('mousemove', this._startDragMove);
618
- document.addEventListener('mouseup', this._stopDrag);
654
+ this._startDrag();
619
655
  // const position = this._getActiveElementPosition(event, this._offsetPos);
620
656
  // this._updateElementBounds(position);
621
657
  }
@@ -627,6 +663,15 @@ class FreeDropEngine {
627
663
  document.removeEventListener('mouseup', this._cancelDragIntent);
628
664
  };
629
665
 
666
+ _startDrag() {
667
+ // 드래그 확정 후 실제 드래그 리스너 등록
668
+ this._dragState.isDragging = true;
669
+
670
+ this.eventBus.emit('freeDrop:startDrag', { timestamp: Date.now() });
671
+ document.addEventListener('mousemove', this._startDragMove);
672
+ document.addEventListener('mouseup', this._stopDrag);
673
+ }
674
+
630
675
  /** [내부함수] 드래그 진행
631
676
  * @param {MouseEvent} event - 마우스 이벤트
632
677
  */
@@ -635,12 +680,12 @@ class FreeDropEngine {
635
680
  if (this._dragState.isResizing) return;
636
681
 
637
682
  // 레이아웃 내부 진입 여부
638
- const isInsideLayout = this._detectLayoutEntry(event, this._offsetPos);
683
+ // const isInsideLayout = this._detectLayoutEntry(event, this._offsetPos);
639
684
 
640
- if (isInsideLayout) {
641
- const position = this._getActiveElementPosition(event, this._offsetPos);
642
- this._updateElementBounds(position);
643
- }
685
+ // if (isInsideLayout) {
686
+ const position = this._getActiveElementPosition(event, this._offsetPos);
687
+ this._updateElementBounds(position);
688
+ // }
644
689
  };
645
690
 
646
691
  /** [내부함수] 드래그 종료 */
@@ -665,7 +710,7 @@ class FreeDropEngine {
665
710
  elementId: this._activeElement.id,
666
711
  position: this._activeElement.position,
667
712
  size: this._activeElement.size,
668
- // guides: this.guides,
713
+ guides: this.guides,
669
714
  timestamp: Date.now(),
670
715
  });
671
716
  this._resetDrag(true, true);
@@ -103,20 +103,35 @@ class GridDropEngine {
103
103
  });
104
104
  });
105
105
 
106
- // this._subscribe('system:requestElementsScale', ({ $elements }) => {
107
- // this._elements = $elements;
108
- // this._elementsUpdate = true;
109
- // });
110
-
111
- this._subscribe('system:restoredState', ({ stateData }) => {
112
- this._elements = stateData.elements;
106
+ this._subscribe('system:requestElementsScale', ({ $elements }) => {
107
+ this._elements = $elements;
113
108
  this._elementsUpdate = true;
114
- this.setActiveElement(null);
109
+ });
115
110
 
116
- this.eventBus.emit('gridDrop:restoredState', {
117
- elements: cloneDeep(this.gridElements),
118
- timestamp: Date.now(),
119
- });
111
+ this._subscribe('system:restoredState', ({ domain, state: snapShot }) => {
112
+ if (domain === 'all') {
113
+ this._elements = snapShot.manager.elements || [];
114
+ this._elementsUpdate = true;
115
+
116
+ this.setActiveElement(null);
117
+
118
+ this.eventBus.emit('gridDrop:restoredState', {
119
+ elements: cloneDeep(this.gridElements),
120
+ timestamp: Date.now(),
121
+ });
122
+ }
123
+
124
+ if (domain === 'manager') {
125
+ this._elements = snapShot.elements || [];
126
+ this._elementsUpdate = true;
127
+
128
+ this.setActiveElement(null);
129
+
130
+ this.eventBus.emit('gridDrop:restoredState', {
131
+ elements: cloneDeep(this.gridElements),
132
+ timestamp: Date.now(),
133
+ });
134
+ }
120
135
  });
121
136
  }
122
137
 
@@ -246,6 +261,7 @@ class GridDropEngine {
246
261
  return;
247
262
  }
248
263
  this._activeElement.isLocked = !this._activeElement.isLocked;
264
+ this._elementsUpdate = true;
249
265
 
250
266
  this.eventBus.emit('gridDrop:toggleLock', {
251
267
  elementId: this._activeElement.id,
@@ -436,6 +452,8 @@ class GridDropEngine {
436
452
  // 단순 클릭 시에는 위치 변경 없음
437
453
  const { col, row } = this._targetCell;
438
454
  if (this._dragState.isDragging) {
455
+ if (col === this._activeElement.position.col && row === this._activeElement.position.row) return;
456
+
439
457
  this._activeElement.position.col = col;
440
458
  this._activeElement.position.row = row;
441
459
 
@@ -500,6 +518,8 @@ class GridDropEngine {
500
518
  const { gridColumns, gridRows } = this._activeConfig;
501
519
 
502
520
  const activeSection = document.querySelector('.section-active');
521
+ if (!activeSection) return;
522
+
503
523
  const { dx, dy } = this._calculateDrag(
504
524
  { x: event.clientX, y: event.clientY },
505
525
  {