polarvo-layout 1.0.87 → 1.0.89
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 +1 -1
- package/src/components/Layout/BaseLayout.vue +46 -14
- package/src/components/Layout/CanvasContainer.vue +54 -6
- package/src/components/Layout/GridLayout.vue +66 -15
- package/src/components/Layout/PolarLayout.vue +2 -2
- package/src/index.js +2 -2
- package/src/utils/grid-track.js +52 -0
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<PolarLayout
|
|
12
12
|
:id="preview ? `${key}Preview` : `${key}Layout`"
|
|
13
13
|
:mode="section.mode"
|
|
14
|
-
:style="getSectionStyle(section)"
|
|
14
|
+
:style="getSectionStyle(key, section)"
|
|
15
15
|
:class="[getSectionClass(key, section), activeSection === key && preview ? 'text-blue-400 bg-blue-50' : 'text-gray-400']"
|
|
16
16
|
@click.stop="emit('click:section', key)"
|
|
17
17
|
>
|
|
@@ -19,7 +19,14 @@
|
|
|
19
19
|
{{ key }}
|
|
20
20
|
</div>
|
|
21
21
|
<template v-if="section.mode === 'grid'">
|
|
22
|
-
<GridLayout
|
|
22
|
+
<GridLayout
|
|
23
|
+
:polarvo="polarvo"
|
|
24
|
+
:preview="preview"
|
|
25
|
+
:sectionKey="key"
|
|
26
|
+
:sectionData="section"
|
|
27
|
+
:elementIds="section.elementIds"
|
|
28
|
+
:extraHeight="getSectionExtraHeight(section)"
|
|
29
|
+
/>
|
|
23
30
|
</template>
|
|
24
31
|
|
|
25
32
|
<template v-else>
|
|
@@ -37,11 +44,12 @@
|
|
|
37
44
|
</template>
|
|
38
45
|
|
|
39
46
|
<script setup>
|
|
40
|
-
import { toRefs, computed, onMounted, onUnmounted, ref, inject } from 'vue';
|
|
47
|
+
import { toRefs, computed, onMounted, onUnmounted, ref, inject, unref } from 'vue';
|
|
41
48
|
|
|
42
49
|
import PolarLayout from './PolarLayout.vue';
|
|
43
50
|
import FreeLayout from './FreeLayout.vue';
|
|
44
51
|
import GridLayout from './GridLayout.vue';
|
|
52
|
+
import { getGridRowMinPx, toGridTrackSizes, toGridRowSizes, isInLastLayoutRow } from '../../utils/grid-track.js';
|
|
45
53
|
|
|
46
54
|
// 우클릭 방지
|
|
47
55
|
// window.oncontextmenu = () => {
|
|
@@ -69,29 +77,53 @@ const layoutType = computed(() => {
|
|
|
69
77
|
});
|
|
70
78
|
const { layoutName, layoutData, activeSection, gapSize, gridNumber, gridRatio } = toRefs(props.polarvo.layout.state);
|
|
71
79
|
const { activeDesign } = toRefs(props.polarvo.display.state);
|
|
80
|
+
const { elements: gridElements } = toRefs(props.polarvo.gridDrop.state);
|
|
72
81
|
const selectedElement = inject('selectedElement');
|
|
73
82
|
const activeEditMode = inject('activeEditMode');
|
|
74
83
|
const activeLeaf = inject('activeLeaf');
|
|
84
|
+
const canvasExtraHeight = inject('canvasExtraHeight', 0); // CanvasContainer 확장 높이 (미리보기는 0)
|
|
85
|
+
|
|
86
|
+
const extraHeight = computed(() => (props.preview ? 0 : (unref(canvasExtraHeight) ?? 0)));
|
|
87
|
+
|
|
88
|
+
// 확장 높이는 마지막 행 섹션만 가져감
|
|
89
|
+
const getSectionExtraHeight = (section) => (isInLastLayoutRow(section, gridRatio.value?.row?.length) ? extraHeight.value : 0);
|
|
75
90
|
|
|
76
91
|
const getBaseStyle = computed(() => {
|
|
77
92
|
return {
|
|
78
93
|
gridTemplateColumns: gridRatio.value?.column.map((ratio) => `${ratio}fr`).join(' '),
|
|
79
|
-
|
|
94
|
+
// 확장 높이는 마지막 행만 가져감 (min은 기존 fr과 동일하게 auto - 섹션 콘텐츠 최소 높이 유지)
|
|
95
|
+
gridTemplateRows: gridRatio.value?.row
|
|
96
|
+
? toGridRowSizes(
|
|
97
|
+
gridRatio.value.row,
|
|
98
|
+
gridRatio.value.row.map(() => 'auto'),
|
|
99
|
+
gapSize.value ?? 0,
|
|
100
|
+
extraHeight.value,
|
|
101
|
+
)
|
|
102
|
+
: undefined,
|
|
80
103
|
marginRight: layoutType.value === 'no-split' ? '0' : layoutType.value === 'three-column-split' ? '14px' : '8px',
|
|
81
104
|
gap: `${gapSize.value}px`,
|
|
82
105
|
};
|
|
83
106
|
});
|
|
84
107
|
|
|
85
|
-
const getSectionStyle = (section) =>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
:
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
const getSectionStyle = (key, section) => {
|
|
109
|
+
const gridRows = section.config?.gridRows ?? 3;
|
|
110
|
+
const rowRatio = section.config?.gridRowRatio?.length ? section.config.gridRowRatio : Array(gridRows).fill(1);
|
|
111
|
+
const sectionElements = gridElements.value.filter((el) => el.section === key && section.elementIds?.includes(el.id));
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
'--grid-columns': section.config?.gridColumns ?? 3,
|
|
115
|
+
'--grid-rows': gridRows,
|
|
116
|
+
'--grid-gap': `${section.config?.gridGap ?? 5}px`,
|
|
117
|
+
'--grid-row-sizes': toGridRowSizes(
|
|
118
|
+
rowRatio,
|
|
119
|
+
getGridRowMinPx(sectionElements, gridRows),
|
|
120
|
+
section.config?.gridGap ?? 5,
|
|
121
|
+
getSectionExtraHeight(section),
|
|
122
|
+
),
|
|
123
|
+
'--grid-column-sizes': section.config?.gridColumnRatio?.length ? toGridTrackSizes(section.config.gridColumnRatio) : undefined, // 비어있으면 아예 안 넣어서 CSS 기본값으로 폴백
|
|
124
|
+
position: 'relative',
|
|
125
|
+
};
|
|
126
|
+
};
|
|
95
127
|
|
|
96
128
|
const getSectionClass = (key, section) => ({
|
|
97
129
|
[key]: true,
|
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
</template>
|
|
25
25
|
|
|
26
26
|
<script setup>
|
|
27
|
-
import { toRefs, ref, computed, watch } from 'vue';
|
|
27
|
+
import { toRefs, ref, computed, watch, provide, onMounted } from 'vue';
|
|
28
28
|
import BaseLayout from './BaseLayout.vue';
|
|
29
|
+
import { isInLastLayoutRow, getGridLastRowRequiredPx } from '../../utils/grid-track.js';
|
|
29
30
|
|
|
30
31
|
const props = defineProps({
|
|
31
32
|
polarvo: {
|
|
@@ -43,7 +44,9 @@ const DEFAULT_DISPLAY_SIZE = {
|
|
|
43
44
|
|
|
44
45
|
// height 관련 여유 공간 추가하여 계산함 (50, 200)
|
|
45
46
|
const { displaySize, canvasSize } = toRefs(props.polarvo.display.state);
|
|
46
|
-
const { elements, activeMode } = toRefs(props.polarvo.layout.state);
|
|
47
|
+
const { elements, activeMode, layoutData, gridRatio } = toRefs(props.polarvo.layout.state);
|
|
48
|
+
const { elements: gridElements } = toRefs(props.polarvo.gridDrop.state);
|
|
49
|
+
const { elements: freeElements } = toRefs(props.polarvo.freeDrop.state);
|
|
47
50
|
|
|
48
51
|
const defaultHeight = computed(() => {
|
|
49
52
|
const freeElementsBottom =
|
|
@@ -134,14 +137,59 @@ const containerStyle = computed(() => {
|
|
|
134
137
|
|
|
135
138
|
defineExpose({ defaultHeight, additionalHeight, displayHeight });
|
|
136
139
|
|
|
140
|
+
// 확장 높이는 BaseLayout/GridLayout에서 마지막 행에만 붙임
|
|
141
|
+
provide('canvasExtraHeight', additionalHeight);
|
|
142
|
+
|
|
143
|
+
const RESIZE_STEP = 200;
|
|
144
|
+
const MIN_ROW_PX = 20; // GridLayout.vue의 MIN_ROW_PX와 동일한 값
|
|
145
|
+
|
|
137
146
|
function expandContainer() {
|
|
138
|
-
additionalHeight.value +=
|
|
147
|
+
additionalHeight.value += RESIZE_STEP;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 확장 높이를 받는 마지막 행 섹션들이 요소 최소 크기를 유지하면서 줄어들 수 있는 높이(px)
|
|
151
|
+
// 확장 높이는 마지막 행만 가져가므로 마지막 행 섹션의 여유분이 곧 줄일 수 있는 최대치
|
|
152
|
+
function getShrinkableHeight() {
|
|
153
|
+
let slack = additionalHeight.value;
|
|
154
|
+
|
|
155
|
+
Object.entries(layoutData.value ?? {}).forEach(([key, section]) => {
|
|
156
|
+
if (!isInLastLayoutRow(section, gridRatio.value?.row?.length)) return;
|
|
157
|
+
const sectionEl = document.getElementById(`${key}Layout`);
|
|
158
|
+
if (!sectionEl) return;
|
|
159
|
+
|
|
160
|
+
const modeElements = (section.mode === 'grid' ? gridElements.value : freeElements.value) ?? [];
|
|
161
|
+
const sectionElements = modeElements.filter((el) => el.section === key && section.elementIds?.includes(el.id));
|
|
162
|
+
|
|
163
|
+
if (section.mode === 'grid') {
|
|
164
|
+
const gridRows = section.config?.gridRows ?? 3;
|
|
165
|
+
const trackPx = getComputedStyle(sectionEl)
|
|
166
|
+
.gridTemplateRows.split(' ')
|
|
167
|
+
.map((v) => parseFloat(v));
|
|
168
|
+
const lastTrackPx = trackPx[gridRows - 1] ?? 0;
|
|
169
|
+
const required = getGridLastRowRequiredPx(sectionElements, gridRows, trackPx, section.config?.gridGap ?? 5, MIN_ROW_PX);
|
|
170
|
+
slack = Math.min(slack, lastTrackPx - required);
|
|
171
|
+
} else {
|
|
172
|
+
const bottom = sectionElements.reduce((max, el) => Math.max(max, (el.position?.y || 0) + (el.size?.h || 0)), 0);
|
|
173
|
+
slack = Math.min(slack, sectionEl.clientHeight - bottom);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return Math.max(0, Math.floor(slack));
|
|
139
178
|
}
|
|
140
179
|
|
|
141
|
-
//
|
|
142
|
-
const
|
|
180
|
+
// 요소 크기/배치·레이아웃·확장 높이가 바뀔 때마다 DOM 반영 후 다시 계산
|
|
181
|
+
const shrinkableHeight = ref(0);
|
|
182
|
+
const updateShrinkableHeight = () => {
|
|
183
|
+
shrinkableHeight.value = getShrinkableHeight();
|
|
184
|
+
};
|
|
185
|
+
watch([displayHeight, gridElements, freeElements, layoutData, gridRatio], updateShrinkableHeight, { deep: true, flush: 'post' });
|
|
186
|
+
onMounted(updateShrinkableHeight);
|
|
187
|
+
|
|
188
|
+
// displayHeight(=defaultHeight + additionalHeight)는 항상 defaultHeight 이상, 마지막 행 요소 최소 크기 이상으로 유지
|
|
189
|
+
const canShrink = computed(() => shrinkableHeight.value > 0);
|
|
143
190
|
function shrinkContainer() {
|
|
144
|
-
|
|
191
|
+
const shrinkable = getShrinkableHeight();
|
|
192
|
+
additionalHeight.value -= Math.min(RESIZE_STEP, shrinkable);
|
|
145
193
|
}
|
|
146
194
|
</script>
|
|
147
195
|
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
|
|
75
75
|
<script setup>
|
|
76
76
|
import GridItem from './GridItem.vue';
|
|
77
|
+
import { getGridRowMinPx, toGridTrackSizes, toGridRowSizes } from '../../utils/grid-track.js';
|
|
77
78
|
|
|
78
79
|
import { ref, toRefs, getCurrentInstance, onMounted, onUnmounted, inject, watch, computed } from 'vue';
|
|
79
80
|
|
|
@@ -98,6 +99,11 @@ const props = defineProps({
|
|
|
98
99
|
type: Array,
|
|
99
100
|
required: true,
|
|
100
101
|
},
|
|
102
|
+
// 캔버스 확장 높이 - 마지막 행이 추가로 가져가는 px (BaseLayout에서 마지막 행 섹션에만 전달)
|
|
103
|
+
extraHeight: {
|
|
104
|
+
type: Number,
|
|
105
|
+
default: 0,
|
|
106
|
+
},
|
|
101
107
|
});
|
|
102
108
|
|
|
103
109
|
const { setActiveDesign, setActiveMenu } = props.polarvo.display;
|
|
@@ -133,6 +139,11 @@ const DRAG_CONFIG = {
|
|
|
133
139
|
clientKey: 'clientY',
|
|
134
140
|
positionKey: 'y',
|
|
135
141
|
cssVar: '--grid-row-sizes',
|
|
142
|
+
posKey: 'row',
|
|
143
|
+
spanKey: 'rowSpan',
|
|
144
|
+
getMinPx: (el) => el.size?.h ?? MIN_ROW_PX, // GridDropEngine.getElementStyle의 minHeight와 동일
|
|
145
|
+
extraPx: () => props.extraHeight, // 마지막 행에 붙은 확장 높이 (비율 계산에서 제외)
|
|
146
|
+
toSizes: (ratio) => toGridRowSizes(ratio, getGridRowMinPx(filteredElements.value, ratio.length), gridGap.value, props.extraHeight), // BaseLayout과 동일한 형식
|
|
136
147
|
},
|
|
137
148
|
col: {
|
|
138
149
|
ref: gridColumns,
|
|
@@ -142,6 +153,11 @@ const DRAG_CONFIG = {
|
|
|
142
153
|
clientKey: 'clientX',
|
|
143
154
|
positionKey: 'x',
|
|
144
155
|
cssVar: '--grid-column-sizes',
|
|
156
|
+
posKey: 'col',
|
|
157
|
+
spanKey: 'colSpan',
|
|
158
|
+
getMinPx: () => MIN_COL_PX,
|
|
159
|
+
extraPx: () => 0,
|
|
160
|
+
toSizes: (ratio) => toGridTrackSizes(ratio), // BaseLayout과 동일한 형식
|
|
145
161
|
},
|
|
146
162
|
};
|
|
147
163
|
|
|
@@ -157,7 +173,8 @@ function handleMouseDown(event, axis, index) {
|
|
|
157
173
|
event.preventDefault();
|
|
158
174
|
event.stopPropagation();
|
|
159
175
|
|
|
160
|
-
const { ref, minPx, ratioKey, gridTemplateKey, clientKey, positionKey, cssVar } =
|
|
176
|
+
const { ref, minPx, ratioKey, gridTemplateKey, clientKey, positionKey, cssVar, posKey, spanKey, getMinPx, extraPx, toSizes } =
|
|
177
|
+
DRAG_CONFIG[axis];
|
|
161
178
|
|
|
162
179
|
dragState.value.axis = axis;
|
|
163
180
|
dragState.value.activeIdx = index;
|
|
@@ -207,7 +224,7 @@ function handleMouseDown(event, axis, index) {
|
|
|
207
224
|
|
|
208
225
|
const configured = props.sectionData?.config?.[ratioKey];
|
|
209
226
|
const baselineRatio =
|
|
210
|
-
|
|
227
|
+
Array.isArray(configured) && configured.length === ref.value && configured.every((v) => typeof v === 'number' && v > 0)
|
|
211
228
|
? [...configured]
|
|
212
229
|
: Array(ref.value).fill(1);
|
|
213
230
|
|
|
@@ -215,24 +232,58 @@ function handleMouseDown(event, axis, index) {
|
|
|
215
232
|
[gridTemplateKey].split(' ')
|
|
216
233
|
.map((v) => parseFloat(v));
|
|
217
234
|
|
|
218
|
-
//
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
//
|
|
223
|
-
const
|
|
235
|
+
const prevIdx = index - 1; // 드래그 행 위쪽 행 인덱스
|
|
236
|
+
const nextIdx = index; // 드래그 행 인덱스
|
|
237
|
+
|
|
238
|
+
// 1px당 Fr 계산 - 드래그로 바뀌는 두 트랙 기준
|
|
239
|
+
// 마지막 트랙의 확장 높이는 비율과 무관하게 붙는 값이라 제외
|
|
240
|
+
const ratioPx = [...trackPx];
|
|
241
|
+
ratioPx[ratioPx.length - 1] -= extraPx();
|
|
242
|
+
const frPerPx = (baselineRatio[prevIdx] + baselineRatio[nextIdx]) / (ratioPx[prevIdx] + ratioPx[nextIdx]);
|
|
243
|
+
|
|
244
|
+
// 요소 최소 크기 때문에 두 트랙이 가져야 하는 최소 px
|
|
245
|
+
const { prevMinPx, nextMinPx } = _getElementMinPx(trackPx, prevIdx, nextIdx);
|
|
224
246
|
|
|
225
247
|
dragSnapShot = {
|
|
226
248
|
container,
|
|
227
249
|
baselineRatio,
|
|
228
250
|
frPerPx,
|
|
229
|
-
|
|
251
|
+
prevMinFr: Math.max(minPx, prevMinPx) * frPerPx,
|
|
252
|
+
// 마지막 트랙은 확장 높이가 이미 확보돼 있으므로 그만큼 비율 최소값에서 뺌
|
|
253
|
+
nextMinFr: Math.max(minPx, nextMinPx - (trackPx[nextIdx] - ratioPx[nextIdx])) * frPerPx,
|
|
230
254
|
};
|
|
231
255
|
|
|
232
256
|
document.addEventListener('mousemove', _startDragMove);
|
|
233
257
|
document.addEventListener('mouseup', _stopDrag);
|
|
234
258
|
}
|
|
235
259
|
|
|
260
|
+
// 드래그로 바뀌는 트랙(prev/next) 중 하나에만 걸친 요소 기준으로, 각 트랙이 가져야 하는 최소 px 계산
|
|
261
|
+
// (두 트랙 모두에 걸친 요소는 합이 그대로라 제약 없음)
|
|
262
|
+
function _getElementMinPx(trackPx, prevIdx, nextIdx) {
|
|
263
|
+
const gap = gridGap.value;
|
|
264
|
+
let prevMinPx = 0;
|
|
265
|
+
let nextMinPx = 0;
|
|
266
|
+
|
|
267
|
+
filteredElements.value.forEach((el) => {
|
|
268
|
+
const start = el.position[posKey] - 1; // 0-based 트랙 인덱스
|
|
269
|
+
const end = start + el.size[spanKey] - 1;
|
|
270
|
+
const coversPrev = start <= prevIdx && prevIdx <= end;
|
|
271
|
+
const coversNext = start <= nextIdx && nextIdx <= end;
|
|
272
|
+
if (coversPrev === coversNext) return;
|
|
273
|
+
|
|
274
|
+
const targetIdx = coversPrev ? prevIdx : nextIdx;
|
|
275
|
+
// 요소가 걸친 나머지 트랙 + gap 만큼은 이미 확보되어 있음
|
|
276
|
+
let otherPx = gap * (end - start);
|
|
277
|
+
for (let t = start; t <= end; t++) if (t !== targetIdx) otherPx += trackPx[t];
|
|
278
|
+
|
|
279
|
+
const required = getMinPx(el) - otherPx;
|
|
280
|
+
if (coversPrev) prevMinPx = Math.max(prevMinPx, required);
|
|
281
|
+
else nextMinPx = Math.max(nextMinPx, required);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return { prevMinPx, nextMinPx };
|
|
285
|
+
}
|
|
286
|
+
|
|
236
287
|
function _startDragMove(e) {
|
|
237
288
|
if (!dragSnapShot) return;
|
|
238
289
|
pendingClient = e[clientKey];
|
|
@@ -243,16 +294,16 @@ function handleMouseDown(event, axis, index) {
|
|
|
243
294
|
rafId = requestAnimationFrame(() => {
|
|
244
295
|
rafId = null;
|
|
245
296
|
|
|
246
|
-
const { baselineRatio, frPerPx,
|
|
297
|
+
const { baselineRatio, frPerPx, prevMinFr, nextMinFr, container } = dragSnapShot;
|
|
247
298
|
const prevIdx = index - 1; // 드래그 행 위쪽 행 인덱스
|
|
248
299
|
const nextIdx = index; // 드래그 행 인덱스
|
|
249
300
|
|
|
250
301
|
// 순수 이동량
|
|
251
302
|
let delta = (pendingClient - startPos.value[positionKey]) * frPerPx;
|
|
252
|
-
// 위쪽 행 작아지는 것 방지: 가장 커져도
|
|
253
|
-
delta = Math.max(
|
|
254
|
-
// 아래쪽 행 작아지는 것 방지: 가장 작아져도
|
|
255
|
-
delta = Math.min(baselineRatio[nextIdx] -
|
|
303
|
+
// 위쪽 행 작아지는 것 방지: 가장 커져도 prevMinFr - baselineRatio[prevIdx] (음수값 = 위로 이동)
|
|
304
|
+
delta = Math.max(prevMinFr - baselineRatio[prevIdx], delta);
|
|
305
|
+
// 아래쪽 행 작아지는 것 방지: 가장 작아져도 nextMinFr (양수값 = 아래로 이동)
|
|
306
|
+
delta = Math.min(baselineRatio[nextIdx] - nextMinFr, delta);
|
|
256
307
|
|
|
257
308
|
// 미리보기용
|
|
258
309
|
const preview = [...baselineRatio];
|
|
@@ -262,7 +313,7 @@ function handleMouseDown(event, axis, index) {
|
|
|
262
313
|
liveRatio = preview;
|
|
263
314
|
|
|
264
315
|
// 화면에만 즉시 반영
|
|
265
|
-
container.style.setProperty(cssVar, preview
|
|
316
|
+
container.style.setProperty(cssVar, toSizes(preview));
|
|
266
317
|
});
|
|
267
318
|
}
|
|
268
319
|
function _stopDrag() {
|
|
@@ -17,9 +17,9 @@ const props = defineProps({
|
|
|
17
17
|
<style scoped>
|
|
18
18
|
.polar-grid {
|
|
19
19
|
display: grid;
|
|
20
|
-
grid-template-columns: var(--grid-column-sizes, repeat(var(--grid-columns, 3), 1fr));
|
|
20
|
+
grid-template-columns: var(--grid-column-sizes, repeat(var(--grid-columns, 3), minmax(0, 1fr)));
|
|
21
21
|
/* grid-template-rows: repeat(var(--grid-rows, 3), 1fr); */
|
|
22
|
-
grid-template-rows: var(--grid-row-sizes, repeat(var(--grid-rows, 3), 1fr));
|
|
22
|
+
grid-template-rows: var(--grid-row-sizes, repeat(var(--grid-rows, 3), minmax(0, 1fr)));
|
|
23
23
|
gap: var(--grid-gap, 5px);
|
|
24
24
|
width: 100%;
|
|
25
25
|
height: 100%;
|
package/src/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import createPolarvoLayout from './library/index.js';
|
|
|
12
12
|
// import LayoutFastMenu from './components/FastMenu/LayoutFastMenu.vue';
|
|
13
13
|
// import DesignFastMenu from './components/FastMenu/DesignFastMenu.vue';
|
|
14
14
|
|
|
15
|
-
import ElementSidebar from './components/SideBar/ElementSidebar.vue';
|
|
15
|
+
// import ElementSidebar from './components/SideBar/ElementSidebar.vue';
|
|
16
16
|
import LayoutSideBar from './components/SideBar/LayoutSideBar.vue';
|
|
17
17
|
|
|
18
18
|
import LayoutMiniMenu from './components/MiniMenu/LayoutMiniMenu.vue';
|
|
@@ -29,7 +29,7 @@ export {
|
|
|
29
29
|
// ActionFastMenu,
|
|
30
30
|
// LayoutFastMenu,
|
|
31
31
|
// DesignFastMenu,
|
|
32
|
-
ElementSidebar,
|
|
32
|
+
// ElementSidebar,
|
|
33
33
|
LayoutSideBar,
|
|
34
34
|
LayoutMiniMenu,
|
|
35
35
|
CanvasContainer,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// 그리드 행 트랙별 최소 높이(px) - 해당 행 하나만 차지하는 요소의 size.h 중 최대값
|
|
2
|
+
// (여러 행에 걸친 요소는 GridLayout 행 크기 조절 시 별도로 제한)
|
|
3
|
+
export function getGridRowMinPx(elements, gridRows) {
|
|
4
|
+
const mins = Array(gridRows).fill(0);
|
|
5
|
+
elements.forEach((el) => {
|
|
6
|
+
const { row } = el.position;
|
|
7
|
+
if (el.size.rowSpan !== 1 || row < 1 || row > gridRows) return;
|
|
8
|
+
mins[row - 1] = Math.max(mins[row - 1], el.size.h ?? 0);
|
|
9
|
+
});
|
|
10
|
+
return mins;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// fr 비율 → grid-template 값
|
|
14
|
+
// min을 auto(콘텐츠 크기) 대신 명시값으로 고정 - 콘텐츠 크기가 트랙을 고정하면 남은 공간이 다른 트랙으로 재분배됨
|
|
15
|
+
export function toGridTrackSizes(ratio, minPx = []) {
|
|
16
|
+
return ratio.map((r, i) => `minmax(${minPx[i] ?? 0}px, ${r}fr)`).join(' ');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// fr 비율 → grid-template-rows 값 (마지막 행만 추가 높이를 가져가도록)
|
|
20
|
+
// 마지막 행을 제외한 행은 (컨테이너 높이 - 추가 높이 - gap 합) 기준 비율로 고정하고, 남는 높이는 마지막 행(1fr)이 전부 가져감
|
|
21
|
+
// → 캔버스 확장(CanvasContainer.additionalHeight) 시 전체 행에 나눠지지 않고 마지막 행만 늘어남
|
|
22
|
+
export function toGridRowSizes(ratio, minPx = [], gap = 0, extraPx = 0) {
|
|
23
|
+
const sum = ratio.reduce((acc, r) => acc + r, 0) || 1;
|
|
24
|
+
const fixedPx = extraPx + gap * (ratio.length - 1);
|
|
25
|
+
const toMin = (i) => (typeof minPx[i] === 'string' ? minPx[i] : `${minPx[i] ?? 0}px`);
|
|
26
|
+
|
|
27
|
+
return ratio
|
|
28
|
+
.map((r, i) => (i === ratio.length - 1 ? `minmax(${toMin(i)}, 1fr)` : `minmax(${toMin(i)}, calc((100% - ${fixedPx}px) * ${r / sum}))`))
|
|
29
|
+
.join(' ');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 섹션이 BaseLayout의 마지막 행에 걸쳐 있는지 (layoutPosition.row는 0-based) - 캔버스 추가 높이를 받는 섹션
|
|
33
|
+
export function isInLastLayoutRow(section, layoutRowCount) {
|
|
34
|
+
const { row = 0, rowSpan = 1 } = section?.layoutPosition ?? {};
|
|
35
|
+
return row + rowSpan >= (layoutRowCount || 1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 그리드 섹션 마지막 행이 가져야 하는 최소 높이(px) - 빈 셀/요소 minHeight(GridDropEngine.getElementStyle) 기준
|
|
39
|
+
// 여러 행에 걸친 요소는 나머지 행(trackPx) + gap 만큼은 이미 확보된 것으로 봄
|
|
40
|
+
export function getGridLastRowRequiredPx(elements, gridRows, trackPx, gap, minRowPx) {
|
|
41
|
+
let required = minRowPx;
|
|
42
|
+
elements.forEach((el) => {
|
|
43
|
+
const { row } = el.position;
|
|
44
|
+
const { rowSpan } = el.size;
|
|
45
|
+
if (row + rowSpan - 1 !== gridRows) return;
|
|
46
|
+
|
|
47
|
+
let otherPx = gap * (rowSpan - 1);
|
|
48
|
+
for (let t = row - 1; t < gridRows - 1; t++) otherPx += trackPx[t] ?? 0;
|
|
49
|
+
required = Math.max(required, (el.size.h ?? minRowPx) - otherPx);
|
|
50
|
+
});
|
|
51
|
+
return required;
|
|
52
|
+
}
|