polarvo-layout 1.0.68 → 1.0.70

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.68",
3
+ "version": "1.0.70",
4
4
  "type": "module",
5
5
  "author": "unigence <unigencelab@gmail.com>",
6
6
  "repository": {
@@ -85,9 +85,8 @@ watch(
85
85
  if (newId && activeElement.value?.section !== props.sectionKey) return;
86
86
 
87
87
  if (!newId) {
88
- console.l;
89
88
  setActiveDesign(null);
90
- setActiveMenu(null);
89
+ // setActiveMenu(null);
91
90
  }
92
91
  _isElementSwitching = true;
93
92
  // selectedElement.value = structuredClone(toRaw(activeElement.value));
@@ -344,6 +344,7 @@ watch(
344
344
 
345
345
  if (!newId) {
346
346
  setActiveDesign(null);
347
+ // setActiveMenu(null);
347
348
  }
348
349
  _isElementSwitching = true;
349
350
  // selectedElement.value = structuredClone(toRaw(activeElement.value));
@@ -145,8 +145,6 @@ function buildCustomElements() {
145
145
  function getElements() {
146
146
  allElements.value.default = buildDefaultElements();
147
147
  allElements.value.custom = buildCustomElements();
148
-
149
- console.log("allElements.value.default", allElements.value);
150
148
  }
151
149
 
152
150
  onBeforeMount(() => {
@@ -15,9 +15,13 @@ const defaultConfig = {
15
15
  },
16
16
  };
17
17
 
18
- const createSections = (count, customConfig = {}) => {
19
- const config = { ...defaultConfig, ...customConfig };
20
- return Object.fromEntries(Array.from({ length: count }, (_, i) => [`section${i + 1}`, { ...config }]));
18
+ const createSections = (count, positions = {}) => {
19
+ return Object.fromEntries(
20
+ Array.from({ length: count }, (_, i) => {
21
+ const key = `section${i + 1}`;
22
+ return [key, { ...defaultConfig, layoutPosition: positions[key] }];
23
+ }),
24
+ );
21
25
  };
22
26
 
23
27
  // 기본 아이콘 import
@@ -34,49 +38,57 @@ export const layoutResource = (icons = {}) => ({
34
38
  name: '전체화면',
35
39
  columnNumber: 1,
36
40
  rowNumber: 1,
37
- sections: createSections(1),
41
+ sections: createSections(1, { section1: { col: 0, row: 0 } }),
38
42
  icon: markRaw(icons.NoSplitIcon || NoSplitIcon),
39
43
  },
40
44
  TopBottomSplit: {
41
45
  name: '상하 분할',
42
46
  columnNumber: 1,
43
47
  rowNumber: 2,
44
- sections: createSections(2),
48
+ sections: createSections(2, { section1: { col: 0, row: 0 }, section2: { col: 0, row: 1 } }),
45
49
  icon: markRaw(icons.TopBottomSplitIcon || TopBottomSplitIcon),
46
50
  },
47
51
  LeftRightSplit: {
48
52
  name: '좌우 분할',
49
53
  columnNumber: 2,
50
54
  rowNumber: 1,
51
- sections: createSections(2),
55
+ sections: createSections(2, { section1: { col: 0, row: 0 }, section2: { col: 1, row: 0 } }),
52
56
  icon: markRaw(icons.LeftRightSplitIcon || LeftRightSplitIcon),
53
57
  },
54
58
  ThreeRowSplit: {
55
59
  name: '수평 3분할',
56
60
  columnNumber: 1,
57
61
  rowNumber: 3,
58
- sections: createSections(3),
62
+ sections: createSections(3, { section1: { col: 0, row: 0 }, section2: { col: 0, row: 1 }, section3: { col: 0, row: 2 } }),
59
63
  icon: markRaw(icons.ThreeRowSplitIcon || ThreeRowSplitIcon),
60
64
  },
61
65
  ThreeColumnSplit: {
62
66
  name: '수직 3분할',
63
67
  columnNumber: 3,
64
68
  rowNumber: 1,
65
- sections: createSections(3),
69
+ sections: createSections(3, { section1: { col: 0, row: 0 }, section2: { col: 1, row: 0 }, section3: { col: 2, row: 0 } }),
66
70
  icon: markRaw(icons.ThreeColumnSplitIcon || ThreeColumnSplitIcon),
67
71
  },
68
72
  MainBottomSplit: {
69
73
  name: '메인-하단 분할',
70
74
  columnNumber: 2,
71
75
  rowNumber: 2,
72
- sections: createSections(3),
76
+ sections: createSections(3, {
77
+ section1: { col: 0, row: 0, colSpan: 2 },
78
+ section2: { col: 0, row: 1 },
79
+ section3: { col: 1, row: 1 },
80
+ }),
73
81
  icon: markRaw(icons.MainBottomSplitIcon || MainBottomSplitIcon),
74
82
  },
75
83
  MainTopSplit: {
76
84
  name: '메인-상단 분할',
77
85
  columnNumber: 2,
78
86
  rowNumber: 2,
79
- sections: createSections(3),
87
+ sections: createSections(3, {
88
+ section1: { col: 0, row: 0 },
89
+ section2: { col: 1, row: 0 },
90
+ section3: { col: 0, row: 1, colSpan: 2 },
91
+ }),
80
92
  icon: markRaw(icons.MainTopSplitIcon || MainTopSplitIcon),
81
93
  },
82
94
  });
@@ -62,7 +62,9 @@ class DisplayEngine {
62
62
  });
63
63
 
64
64
  this._subscribe('freeDrop:setActiveElement', ({ action }) => {
65
- if (action === 'update') return;
65
+ if (['update', 'sectionChange'].includes(action)) return;
66
+ if (action === 'click' && this.activeMenu === 'layout') return;
67
+
66
68
  this.setActiveMenu(null);
67
69
  this.setActiveDesign(null);
68
70
  });
@@ -74,7 +76,8 @@ class DisplayEngine {
74
76
  // });
75
77
 
76
78
  this._subscribe('gridDrop:setActiveElement', ({ action }) => {
77
- if (action === 'update') return;
79
+ if (['update', 'sectionChange'].includes(action)) return;
80
+ if (action === 'click' && this.activeMenu === 'layout') return;
78
81
  this.setActiveMenu(null);
79
82
  this.setActiveDesign(null);
80
83
  });
@@ -89,7 +89,7 @@ class FreeDropEngine {
89
89
  this._activeMode = $mode;
90
90
  this._activeConfig = $config;
91
91
 
92
- this.setActiveElement(null);
92
+ this.setActiveElement(null, 'sectionChange');
93
93
  });
94
94
 
95
95
  this._subscribe('system:setElements', ({ $elements }) => {
@@ -114,7 +114,7 @@ class FreeDropEngine {
114
114
  this._elements = $elements;
115
115
  this._elementsUpdate = true;
116
116
 
117
- this.setActiveElement(this._elements.find((x) => x.id === $elementId));
117
+ this.setActiveElement(this._elements.find((x) => x.id === $elementId) ?? null);
118
118
 
119
119
  this.eventBus.emit('freeDrop:requestUpdateData', {
120
120
  elements: cloneDeep(this.freeElements),
@@ -578,6 +578,22 @@ class FreeDropEngine {
578
578
  newW = this._snapToGrid(this._startSize.w + deltaX);
579
579
  }
580
580
 
581
+ // 섹션 경계 제한 (반대쪽 끝은 고정한 채 넘친 만큼만 축소)
582
+ const layoutRect = this._getLayoutRect();
583
+ if (layoutRect) {
584
+ if (newY < 0) {
585
+ newH += newY;
586
+ newY = 0;
587
+ }
588
+ if (newX < 0) {
589
+ newW += newX;
590
+ newX = 0;
591
+ }
592
+
593
+ newH = Math.round(Math.min(newH, layoutRect.height - newY));
594
+ newW = Math.round(Math.min(newW, layoutRect.width - newX));
595
+ }
596
+
581
597
  // 최소 크기 제한
582
598
  newW = Math.max(40, newW);
583
599
  newH = Math.max(20, newH);
@@ -57,7 +57,7 @@ class GridDropEngine {
57
57
  this._activeMode = $mode;
58
58
  this._activeConfig = $config;
59
59
 
60
- this.setActiveElement(null);
60
+ this.setActiveElement(null, 'sectionChange');
61
61
  });
62
62
 
63
63
  this._subscribe('system:setElements', ({ $elements }) => {
@@ -82,7 +82,8 @@ class GridDropEngine {
82
82
  this._elements = $elements;
83
83
  this._elementsUpdate = true;
84
84
 
85
- this.setActiveElement(this._elements.find((x) => x.id === $elementId));
85
+ this.setActiveElement(this._elements.find((x) => x.id === $elementId) ?? null);
86
+
86
87
  this.eventBus.emit('gridDrop:requestUpdateData', {
87
88
  elements: cloneDeep(this.gridElements),
88
89
  timestamp: Date.now(),
@@ -218,6 +218,7 @@ class LayoutEngine {
218
218
  newLayoutData[sectionKey] = {
219
219
  ...newLayoutData[sectionKey],
220
220
  ...mergeSource[sectionKey],
221
+ layoutPosition: newLayoutData[sectionKey].layoutPosition, // 항상 새 레이아웃 기준으로 고정
221
222
  };
222
223
  }
223
224
  });
@@ -121,6 +121,7 @@ class EngineManager {
121
121
  // layoutEngine
122
122
  case 'layout': {
123
123
  const prevRatio = this.getEngine('layout').gridRatio; // 복원 전 현재 비율
124
+ const prevLayoutData = this.state.layoutData; // 복원 전 layoutData (섹션 layoutPosition 비교용)
124
125
  this.getEngine('layout')?.setState(state);
125
126
  this.state.layoutData = state.layoutData;
126
127
  this.setActiveSection(null, true);
@@ -129,7 +130,7 @@ class EngineManager {
129
130
  prevRatio.column.length !== state.gridRatio.column.length || prevRatio.column.some((v, i) => v !== state.gridRatio.column[i]);
130
131
 
131
132
  if (ratioChanged) {
132
- this._updateElementsScaleByRatio('column', prevRatio, state.gridRatio);
133
+ this._updateElementsScaleByRatio('column', prevRatio, state.gridRatio, prevLayoutData);
133
134
  }
134
135
  break;
135
136
  }
@@ -249,6 +250,22 @@ class EngineManager {
249
250
  ...safeChanges,
250
251
  };
251
252
 
253
+ // free 모드 요소의 크기 직접 입력 시, 섹션 경계를 넘지 않도록 제한
254
+ if (updatedElement.mode === 'free' && safeChanges.size) {
255
+ const layoutEl = document.getElementById(`${updatedElement.section}Layout`);
256
+ if (layoutEl) {
257
+ const { width, height } = layoutEl.getBoundingClientRect();
258
+ const maxW = width - updatedElement.position.x;
259
+ const maxH = height - updatedElement.position.y;
260
+
261
+ updatedElement.size = {
262
+ ...updatedElement.size,
263
+ w: Math.round(Math.max(40, Math.min(updatedElement.size.w, maxW))),
264
+ h: Math.round(Math.max(20, Math.min(updatedElement.size.h, maxH))),
265
+ };
266
+ }
267
+ }
268
+
252
269
  this.state.elements = [...this.state.elements.slice(0, index), updatedElement, ...this.state.elements.slice(index + 1)];
253
270
 
254
271
  // 변경된 element만 전달
@@ -328,12 +345,14 @@ class EngineManager {
328
345
  });
329
346
 
330
347
  this._subscribe('layout:updateLayoutName', ({ $layoutData, $gridRatio, $prev }) => {
348
+ const prevLayoutData = this.state.layoutData; // 레이아웃 전환 전 layoutData (섹션 layoutPosition 비교용)
331
349
  this.state.layoutData = $layoutData;
350
+
332
351
  this.setActiveSection(null, true);
333
352
 
334
353
  const prevGridRatio = $prev?.gridRatio || { column: [100], row: [100] };
335
- this._updateElementsScaleByRatio('column', prevGridRatio, $gridRatio);
336
- this._updateElementsScaleByRatio('row', prevGridRatio, $gridRatio);
354
+ this._updateElementsScaleByRatio('column', prevGridRatio, $gridRatio, prevLayoutData);
355
+ this._updateElementsScaleByRatio('row', prevGridRatio, $gridRatio, prevLayoutData);
337
356
 
338
357
  nextTick(() => {
339
358
  this.eventBus.emit('system:historyCheckpoint', {
@@ -361,50 +380,51 @@ class EngineManager {
361
380
  });
362
381
  }
363
382
 
364
- _updateElementsScaleByRatio(type, oldValue, newValue) {
365
- const oldLength = oldValue[type].length;
366
- const newLength = newValue[type].length;
383
+ /** [내부함수] gridRatio 변경에 따라 요소 position/size 재계산
384
+ * @param {string} type - 'column' | 'row'
385
+ * @param {object} oldValue - 변경 전 gridRatio ({ column, row })
386
+ * @param {object} newValue - 변경 후 gridRatio ({ column, row })
387
+ * @param {object} oldLayoutData - 변경 전 layoutData.
388
+ * 생략 시: 섹션 비율만 조정하는 경우
389
+ * 레이아웃 자체가 바뀌는 호출은 변경 전 layoutData를 넘겨야 함
390
+ */
391
+ _updateElementsScaleByRatio(type, oldValue, newValue, oldLayoutData = this.state.layoutData) {
392
+ // 축(column, row)에 따라 positionKey, sizeKey 결정
393
+ const positionKey = type === 'column' ? 'x' : 'y';
394
+ const sizeKey = type === 'column' ? 'w' : 'h';
395
+
396
+ // 요소를 받아서 좌표, 크기에 대해 배율을 곱해 새 요소 객체 반환하는 헬퍼
397
+ // column이면 x, w만 수정 / row면 y, h만 수정
398
+ const scaleElement = (el, scaleFactor) => ({
399
+ ...el,
400
+ position: { ...el.position, [positionKey]: el.position[positionKey] * scaleFactor },
401
+ size: { ...el.size, [sizeKey]: el.size[sizeKey] * scaleFactor },
402
+ });
367
403
 
368
- let newDisplaySize = { ...this._displaySize };
404
+ // 섹션이 전체 너비/높이에서 차지하는 퍼센트 계산
405
+ const axisRatio = (ratios, pos) => {
406
+ const start = type === 'column' ? pos.col : pos.row; // 섹션이 시작하는 인덱스
407
+ const span = (type === 'column' ? pos.colSpan : pos.rowSpan) ?? 1; // 섹션이 차지하는 칸 수
408
+ return ratios.slice(start, start + span).reduce((sum, v) => sum + v, 0) / 100;
409
+ };
369
410
 
370
- // 열 또는 행 개수가 늘어나는 경우 - 요소 크기 줄이기
371
- if (oldLength < newLength) {
372
- newDisplaySize.px = newDisplaySize.px / newLength;
373
- this._updateElementsScale(this._displaySize, newDisplaySize);
374
- }
375
- // 열 또는 행 개수가 줄어드는 경우 - 요소 크기 늘리기
376
- else if (oldLength > newLength) {
377
- newDisplaySize.px = newDisplaySize.px / oldLength;
378
- this._updateElementsScale(newDisplaySize, this._displaySize);
379
- }
380
- // 열 또는 행 개수는 동일하지만 비율이 변경되는 경우 - 요소 크기 조정
381
- else {
382
- this.state.elements = this.state.elements.map((el) => {
383
- const sectionIndex = el.section ? parseInt(el.section.replace('section', '')) - 1 : 0;
384
- const oldRatio = oldValue[type][sectionIndex] / 100;
385
- const newRatio = newValue[type][sectionIndex] / 100;
386
-
387
- const scaleFactor = newRatio / oldRatio;
388
- return type === 'column'
389
- ? {
390
- ...el,
391
- position: { ...el.position, x: el.position.x * scaleFactor },
392
- size: { ...el.size, w: el.size.w * scaleFactor },
393
- }
394
- : {
395
- ...el,
396
- position: { ...el.position, y: el.position.y * scaleFactor },
397
- size: { ...el.size, h: el.size.h * scaleFactor },
398
- };
399
- });
411
+
412
+ this.state.elements = this.state.elements.map((el) => {
413
+ const oldPos = oldLayoutData?.[el.section]?.layoutPosition; // 변경 전 요소의 섹션 위치
414
+ const newPos = this.state.layoutData?.[el.section]?.layoutPosition; // 변경 후 요소의 섹션 위치
415
+ if (!oldPos || !newPos) return el;
400
416
 
401
- // nextTick(() => {
402
- this.eventBus.emit('system:requestElementsScale', {
403
- $elements: this.state.elements,
404
- timestamp: Date.now(),
405
- });
406
- // });
407
- }
417
+ const oldRatio = axisRatio(oldValue[type], oldPos);
418
+ if (!oldRatio) return el;
419
+ const newRatio = axisRatio(newValue[type], newPos);
420
+
421
+ return scaleElement(el, newRatio / oldRatio);
422
+ });
423
+
424
+ this.eventBus.emit('system:requestElementsScale', {
425
+ $elements: this.state.elements,
426
+ timestamp: Date.now(),
427
+ });
408
428
  }
409
429
 
410
430
  /** activeSection 변경
@@ -143,9 +143,9 @@ function useFreeDrop(api) {
143
143
  }
144
144
  };
145
145
 
146
- const setActiveElement = (element, type) => {
146
+ const setActiveElement = (element, action) => {
147
147
  try {
148
- api.freeDrop.setActiveElement(element, type);
148
+ api.freeDrop.setActiveElement(element, action);
149
149
  } catch (error) {
150
150
  console.warn('[FreeDropLibrary] setActiveElement 실행 중 오류 발생:', error);
151
151
  }
@@ -118,9 +118,9 @@ function useGridDrop(api) {
118
118
  }
119
119
  };
120
120
 
121
- const setActiveElement = (element, type) => {
121
+ const setActiveElement = (element, action) => {
122
122
  try {
123
- api.gridDrop.setActiveElement(element, type);
123
+ api.gridDrop.setActiveElement(element, action);
124
124
  } catch (error) {
125
125
  console.warn('[GridDropLibrary] setActiveElement 실행 중 오류 발생:', error);
126
126
  }