polarvo-layout 1.0.58 → 1.0.59

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.58",
3
+ "version": "1.0.59",
4
4
  "type": "module",
5
5
  "author": "unigence <unigencelab@gmail.com>",
6
6
  "repository": {
@@ -91,6 +91,9 @@ const getSectionStyle = (section) => ({
91
91
  '--grid-rows': section.config?.gridRows ?? 3,
92
92
  '--grid-gap': `${section.config?.gridGap ?? 5}px`,
93
93
  '--grid-row-sizes': section.config?.gridRowRatio?.length ? section.config.gridRowRatio.map((r) => `${r}fr`).join(' ') : undefined, // 비어있으면 아예 안 넣어서 CSS 기본값으로 폴백
94
+ '--grid-column-sizes': section.config?.gridColumnRatio?.length
95
+ ? section.config.gridColumnRatio.map((r) => `${r}fr`).join(' ')
96
+ : undefined,
94
97
  position: 'relative',
95
98
  });
96
99
 
@@ -8,29 +8,61 @@
8
8
  :sectionKey="props.sectionKey"
9
9
  ></GridItem>
10
10
 
11
+ <!-- 행 크기 조절 -->
11
12
  <div
12
13
  v-for="r in gridRows - 1"
13
14
  :key="`row-handle-${r}`"
14
15
  :id="`row-handle-${r}`"
15
- class="z-[60] relative self-end justify-self-stretch cursor-s-resize "
16
+ class="z-[60] relative self-end justify-self-stretch cursor-s-resize"
16
17
  :style="{
17
18
  gridRow: r,
18
19
  gridColumn: '1 / -1',
19
20
  height: `${gridGap}px`,
20
21
  transform: 'translateY(100%)',
21
22
  }"
22
- @mousedown="handleMouseDown($event, r)"
23
+ @mousedown="handleMouseDown($event, 'row', r)"
23
24
  >
24
25
  <!-- 점선 -->
25
26
  <div
26
- class="pointer-events-none absolute left-0 right-0 top-1/2 -translate-y-1/2 border-t border-dashed border-blue-700 "
27
- :class="{ 'opacity-100': rowState.activeRow === r, 'opacity-0': rowState.activeRow !== r }"
27
+ class="pointer-events-none absolute left-0 right-0 top-1/2 -translate-y-1/2 border-t border-dashed border-blue-700"
28
+ :class="{
29
+ 'opacity-100': dragState.axis === 'row' && dragState.activeIdx === r,
30
+ 'opacity-0': !(dragState.axis === 'row' && dragState.activeIdx === r),
31
+ }"
32
+ ></div>
33
+
34
+ <!-- 핸들 -->
35
+ <!-- <div
36
+ class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-3 h-3 cursor-s-resize rounded-full bg-violet-400 opacity-30 hover:opacity-100"
37
+ ></div> -->
38
+ </div>
39
+
40
+ <!-- 열 크기 조절 -->
41
+ <div
42
+ v-for="c in gridColumns - 1"
43
+ :key="`col-handle-${c}`"
44
+ :id="`col-handle-${c}`"
45
+ class="z-[60] relative self-stretch justify-self-end cursor-e-resize"
46
+ :style="{
47
+ gridRow: '1 / -1',
48
+ gridColumn: c,
49
+ width: `${gridGap}px`,
50
+ transform: 'translateX(100%)',
51
+ }"
52
+ @mousedown="handleMouseDown($event, 'col', c)"
53
+ >
54
+ <!-- 점선 -->
55
+ <div
56
+ class="pointer-events-none absolute top-0 bottom-0 left-1/2 -translate-x-1/2 border-l border-dashed border-blue-700"
57
+ :class="{
58
+ 'opacity-100': dragState.axis === 'col' && dragState.activeIdx === c,
59
+ 'opacity-0': !(dragState.axis === 'col' && dragState.activeIdx === c),
60
+ }"
28
61
  ></div>
29
62
 
30
63
  <!-- 핸들 -->
31
64
  <!-- <div
32
65
  class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-3 h-3 cursor-s-resize rounded-full bg-violet-400 opacity-30 hover:opacity-100"
33
- @mousedown="handleMouseDown($event, r)"
34
66
  ></div> -->
35
67
  </div>
36
68
 
@@ -81,36 +113,62 @@ const gridColumns = ref(3); // 동적으로 계산
81
113
  const gridRows = ref(1); // 동적으로 계산
82
114
  const gridGap = computed(() => props.sectionData?.config?.gridGap ?? 5); // BaseLayout.vue --grid-gap과 동일한 소스/기본값
83
115
 
84
- // row Resize
116
+ // row, col Resize
85
117
  const MIN_ROW_PX = 20; // 행 최소 높이(px)
118
+ const MIN_COL_PX = 20; // 열 최소 너비(px)
119
+
120
+ const DRAG_CONFIG = {
121
+ row: {
122
+ ref: gridRows,
123
+ minPx: MIN_ROW_PX,
124
+ ratioKey: 'gridRowRatio',
125
+ gridTemplateKey: 'gridTemplateRows',
126
+ clientKey: 'clientY',
127
+ positionKey: 'y',
128
+ cssVar: '--grid-row-sizes',
129
+ },
130
+ col: {
131
+ ref: gridColumns,
132
+ minPx: MIN_COL_PX,
133
+ ratioKey: 'gridColumnRatio',
134
+ gridTemplateKey: 'gridTemplateColumns',
135
+ clientKey: 'clientX',
136
+ positionKey: 'x',
137
+ cssVar: '--grid-column-sizes',
138
+ },
139
+ };
86
140
 
87
- const rowState = ref({
88
- activeRow: null,
141
+ const dragState = ref({
142
+ axis: null, // 'row' | 'col'
143
+ activeIdx: null,
89
144
  isDragging: false,
90
145
  });
91
146
  const startPos = ref({ x: 0, y: 0 });
92
147
 
93
- function handleMouseDown(event, row) {
94
- if (!row) return;
148
+ function handleMouseDown(event, axis, index) {
149
+ if (!index) return;
95
150
  event.preventDefault();
96
151
  event.stopPropagation();
97
152
 
98
- rowState.value.activeRow = row;
153
+ const { ref, minPx, ratioKey, gridTemplateKey, clientKey, positionKey, cssVar } = DRAG_CONFIG[axis];
154
+
155
+ dragState.value.axis = axis;
156
+ dragState.value.activeIdx = index;
99
157
  startPos.value = { x: event.clientX, y: event.clientY };
100
158
 
101
159
  setActiveMenu(null); // 메뉴 닫기
102
160
  setActiveDesign(null); // 디자인 닫기
103
161
 
104
162
  let dragSnapShot = null; // 드래그 시작 시 스냅샷
105
- let liveRowRatio = null; // 미리보기 중 계산된 최종 fr 배열 (stopDrag에서 커밋)
106
- let pendingClientY = null;
163
+ let liveRatio = null; // 미리보기 중 계산된 최종 fr 배열 (stopDrag에서 커밋)
164
+ let pendingClient = null;
107
165
  let rafId = null;
108
166
 
109
167
  document.addEventListener('mousemove', _detectDragIntent);
110
168
  document.addEventListener('mouseup', _cancelDragIntent);
111
169
 
112
170
  function _detectDragIntent(e) {
113
- if (rowState.value.activeRow !== row) return;
171
+ if (dragState.value.axis !== axis || dragState.value.activeIdx !== index) return;
114
172
  const deltaX = Math.abs(e.clientX - startPos.value.x);
115
173
  const deltaY = Math.abs(e.clientY - startPos.value.y);
116
174
 
@@ -127,26 +185,27 @@ function handleMouseDown(event, row) {
127
185
  }
128
186
 
129
187
  function _cancelDragIntent() {
130
- rowState.value.activeRow = null;
188
+ dragState.value.axis = null;
189
+ dragState.value.activeIdx = null;
131
190
 
132
191
  document.removeEventListener('mousemove', _detectDragIntent);
133
192
  document.removeEventListener('mouseup', _cancelDragIntent);
134
193
  }
135
194
 
136
195
  function _startDrag() {
137
- rowState.value.isDragging = true;
196
+ dragState.value.isDragging = true;
138
197
 
139
198
  const container = document.getElementById(`${props.sectionKey}Layout`);
140
199
  if (!container) return _stopDrag();
141
200
 
142
- const configured = props.sectionData?.config?.gridRowRatio;
201
+ const configured = props.sectionData?.config?.[ratioKey];
143
202
  const baselineRatio =
144
- (Array.isArray(configured) && configured.length === gridRows.value) || configured.some((v) => typeof v !== 'number' || !(v > 0))
203
+ (Array.isArray(configured) && configured.length === ref.value) || configured.some((v) => typeof v !== 'number' || !(v > 0))
145
204
  ? [...configured]
146
- : Array(gridRows.value).fill(1);
205
+ : Array(ref.value).fill(1);
147
206
 
148
207
  const trackPx = getComputedStyle(container)
149
- .gridTemplateRows.split(' ')
208
+ [gridTemplateKey].split(' ')
150
209
  .map((v) => parseFloat(v));
151
210
 
152
211
  // 총 Fr 계산
@@ -160,7 +219,7 @@ function handleMouseDown(event, row) {
160
219
  container,
161
220
  baselineRatio,
162
221
  frPerPx,
163
- minFr: MIN_ROW_PX * frPerPx, // 최소 Fr 계산
222
+ minFr: minPx * frPerPx, // 최소 Fr 계산
164
223
  };
165
224
 
166
225
  document.addEventListener('mousemove', _startDragMove);
@@ -169,7 +228,7 @@ function handleMouseDown(event, row) {
169
228
 
170
229
  function _startDragMove(e) {
171
230
  if (!dragSnapShot) return;
172
- pendingClientY = e.clientY;
231
+ pendingClient = e[clientKey];
173
232
 
174
233
  // rafId 중복 예약 방지
175
234
  if (rafId) return;
@@ -178,39 +237,40 @@ function handleMouseDown(event, row) {
178
237
  rafId = null;
179
238
 
180
239
  const { baselineRatio, frPerPx, minFr, container } = dragSnapShot;
181
- const topIdx = row - 1; // 드래그 행 위쪽 행 인덱스
182
- const bottomIdx = row; // 드래그 행 인덱스
240
+ const prevIdx = index - 1; // 드래그 행 위쪽 행 인덱스
241
+ const nextIdx = index; // 드래그 행 인덱스
183
242
 
184
243
  // 순수 이동량
185
- let delta = (pendingClientY - startPos.value.y) * frPerPx;
186
- // 위쪽 행 작아지는 것 방지: 가장 커져도 minFr - baselineRatio[topIdx] (음수값 = 위로 이동)
187
- delta = Math.max(minFr - baselineRatio[topIdx], delta);
244
+ let delta = (pendingClient - startPos.value[positionKey]) * frPerPx;
245
+ // 위쪽 행 작아지는 것 방지: 가장 커져도 minFr - baselineRatio[prevIdx] (음수값 = 위로 이동)
246
+ delta = Math.max(minFr - baselineRatio[prevIdx], delta);
188
247
  // 아래쪽 행 작아지는 것 방지: 가장 작아져도 minFr (양수값 = 아래로 이동)
189
- delta = Math.min(baselineRatio[bottomIdx] - minFr, delta);
248
+ delta = Math.min(baselineRatio[nextIdx] - minFr, delta);
190
249
 
191
250
  // 미리보기용
192
251
  const preview = [...baselineRatio];
193
- preview[topIdx] += delta;
194
- preview[bottomIdx] -= delta;
252
+ preview[prevIdx] += delta;
253
+ preview[nextIdx] -= delta;
195
254
 
196
- liveRowRatio = preview;
255
+ liveRatio = preview;
197
256
 
198
257
  // 화면에만 즉시 반영
199
- container.style.setProperty('--grid-row-sizes', preview.map((v) => `${v}fr`).join(' '));
258
+ container.style.setProperty(cssVar, preview.map((v) => `${v}fr`).join(' '));
200
259
  });
201
260
  }
202
261
  function _stopDrag() {
203
- rowState.value.isDragging = false;
204
- rowState.value.activeRow = null;
262
+ dragState.value.isDragging = false;
263
+ dragState.value.axis = null;
264
+ dragState.value.activeIdx = null;
205
265
 
206
- if (liveRowRatio) {
207
- setSectionConfig('gridRowRatio', liveRowRatio);
266
+ if (liveRatio) {
267
+ setSectionConfig(ratioKey, liveRatio);
208
268
  }
209
269
 
210
270
  startPos.value = { x: 0, y: 0 };
211
271
  dragSnapShot = null;
212
- liveRowRatio = null;
213
- pendingClientY = null;
272
+ liveRatio = null;
273
+ pendingClient = null;
214
274
  rafId = null;
215
275
 
216
276
  document.removeEventListener('mousemove', _startDragMove);
@@ -17,7 +17,7 @@ const props = defineProps({
17
17
  <style scoped>
18
18
  .polar-grid {
19
19
  display: grid;
20
- grid-template-columns: repeat(var(--grid-columns, 3), 1fr);
20
+ grid-template-columns: var(--grid-column-sizes, repeat(var(--grid-columns, 3), 1fr));
21
21
  /* grid-template-rows: repeat(var(--grid-rows, 3), 1fr); */
22
22
  grid-template-rows: var(--grid-row-sizes, repeat(var(--grid-rows, 3), 1fr));
23
23
  gap: var(--grid-gap, 5px);
@@ -460,7 +460,7 @@ class EngineManager {
460
460
  * @param {number|boolean} config - 새로운 설정 값
461
461
  */
462
462
  setSectionConfig(type, config) {
463
- if (!['column', 'row', 'gap', 'guideLine', 'gridRowRatio'].includes(type)) {
463
+ if (!['column', 'row', 'gap', 'guideLine', 'gridRowRatio', 'gridColumnRatio'].includes(type)) {
464
464
  return { result: false, message: '입력할 수 없는 config 타입입니다.' };
465
465
  }
466
466
 
@@ -486,6 +486,14 @@ class EngineManager {
486
486
  }
487
487
 
488
488
  newConfig = { gridRowRatio: value };
489
+ } else if (type === 'gridColumnRatio') {
490
+ // gridColumnRatio 배열은 gridColumns 길이와 일치해야 함
491
+ const gridColumns = this.activeData.config?.gridColumns || 1;
492
+ if (!Array.isArray(value) || value.length !== gridColumns || value.some((v) => typeof v !== 'number' || !(v > 0))) {
493
+ return { result: false, message: '잘못된 열 비율 값입니다.' };
494
+ }
495
+
496
+ newConfig = { gridColumnRatio: value };
489
497
  } else {
490
498
  const limit = CONFIG_LIMITS[type];
491
499
 
@@ -500,7 +508,8 @@ class EngineManager {
500
508
  value = Math.min(Math.max(limit.min, value), limit.max);
501
509
 
502
510
  if (type === 'column') {
503
- newConfig = { gridColumns: value };
511
+ // gridColumns 변경되면 gridColumnRatio 초기화
512
+ newConfig = { gridColumns: value, gridColumnRatio: [] };
504
513
  } else if (type === 'row') {
505
514
  // gridRows 변경되면 gridRowRatio 초기화
506
515
  newConfig = { gridRows: value, gridRowRatio: [] };