polarvo-layout 1.0.15 → 1.0.17

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.15",
3
+ "version": "1.0.17",
4
4
  "type": "module",
5
5
  "author": "unigence <unigencelab@gmail.com>",
6
6
  "repository": {
@@ -1,13 +1,14 @@
1
1
  <template>
2
2
  <div
3
3
  :id="preview ? 'previewLayout' : 'baseLayout'"
4
- class="grid h-full p-2 bg-transparent rounded-lg "
4
+ class="grid h-full p-2 bg-transparent rounded-lg"
5
5
  :class="[layoutType + '-layout', `grid-cols-${gridNumber?.column}`, 'mb-4' ? layoutType === 'three-column-split' : '']"
6
6
  :style="getBaseStyle"
7
7
  >
8
8
  <template v-for="(section, key) in layoutData" :key="key">
9
9
  <PolarLayout
10
10
  :id="preview ? `${key}Preview` : `${key}Layout`"
11
+ :mode="section.mode"
11
12
  :style="getSectionStyle(section)"
12
13
  :class="[getSectionClass(key, section), activeSection === key && preview ? 'text-blue-400 bg-blue-50' : 'text-gray-400']"
13
14
  @click.stop="emit('click:section', key)"
@@ -16,11 +17,19 @@
16
17
  {{ key }}
17
18
  </div>
18
19
  <template v-if="section.mode === 'grid'">
19
- <GridLayout :polarvo="polarvo" :preview="preview" :sectionKey="key" :elementIds="section.elementIds" />
20
+ <GridLayout
21
+ :id="`grid-${key}`"
22
+ :polarvo="polarvo"
23
+ :preview="preview"
24
+ :sectionKey="key"
25
+ :sectionData="section"
26
+ :elementIds="section.elementIds"
27
+ />
20
28
  </template>
21
29
 
22
30
  <template v-else>
23
31
  <FreeLayout
32
+ :id="`free-${key}`"
24
33
  :polarvo="polarvo"
25
34
  :preview="preview"
26
35
  :sectionKey="key"
@@ -31,7 +40,6 @@
31
40
  </PolarLayout>
32
41
  </template>
33
42
  </div>
34
-
35
43
  </template>
36
44
 
37
45
  <script setup>
@@ -70,8 +78,8 @@ const { layoutName, layoutData, activeSection, gapSize, gridNumber, gridRatio }
70
78
 
71
79
  const getBaseStyle = computed(() => {
72
80
  return {
73
- gridTemplateColumns: gridRatio.value?.column.map((ratio) => `${ratio}%`).join(' ') ,
74
- gridTemplateRows: gridRatio.value?.row.map((ratio) => `${ratio}%`).join(' ') ,
81
+ gridTemplateColumns: gridRatio.value?.column.map((ratio) => `${ratio}%`).join(' '),
82
+ gridTemplateRows: gridRatio.value?.row.map((ratio) => `${ratio}%`).join(' '),
75
83
  marginRight: layoutType.value === 'no-split' ? '0' : layoutType.value === 'three-column-split' ? '14px' : '8px',
76
84
  gap: `${gapSize.value}px`,
77
85
  };
@@ -89,7 +97,6 @@ const getSectionClass = (key, section) => ({
89
97
  'section-active': activeSection.value === key,
90
98
  disabled: activeSection.value != key && !props.preview,
91
99
  });
92
-
93
100
  </script>
94
101
 
95
102
  <style scoped>
@@ -2,12 +2,12 @@
2
2
  <div v-if="!preview">
3
3
  <div
4
4
  v-for="(item, index) in filteredElements"
5
- :key="index"
5
+ :key="item.id"
6
6
  :id="item.id"
7
7
  class="absolute user-select-none"
8
8
  :style="getElementStyle(item)"
9
9
  :class="{ 'z-50': item.id === activeId, 'bg-white border-2 border-blue-400': item.id === activeId }"
10
- @mousedown.stop="activeEditMode ? null : startDrag($event, item.id)"
10
+ @mousedown.stop="activeEditMode ? null : handleMouseDown($event, item.id)"
11
11
  >
12
12
  <!-- 리사이즈 핸들 -->
13
13
  <div v-if="item.id === activeId" class="absolute inset-0 pointer-events-none">
@@ -37,7 +37,7 @@
37
37
 
38
38
  <component
39
39
  :is="dynamicComponent(item.type)"
40
- :id="`${sectionKey}-${index}`"
40
+ :id="`${sectionKey}-${item.id}`"
41
41
  :rootId="item.id"
42
42
  :ref="(el) => setComponentRef(el, item.id)"
43
43
  v-bind="item"
@@ -71,7 +71,17 @@
71
71
  import LockIcon from '../../icons/action/LockIcon.vue';
72
72
  import UnlockIcon from '../../icons/action/UnlockIcon.vue';
73
73
 
74
- import { toRefs, defineAsyncComponent, markRaw, getCurrentInstance, onMounted, onUnmounted, inject, watch, toRaw, computed } from 'vue';
74
+ import {
75
+ toRefs,
76
+ defineAsyncComponent,
77
+ markRaw,
78
+ getCurrentInstance,
79
+ onMounted,
80
+ onUnmounted,
81
+ inject,
82
+ watch,
83
+ computed,
84
+ } from 'vue';
75
85
 
76
86
  const props = defineProps({
77
87
  polarvo: {
@@ -99,13 +109,19 @@ const props = defineProps({
99
109
  const { setActiveDesign } = props.polarvo.display;
100
110
  const { updateActiveElement } = props.polarvo.layout;
101
111
  const { elements, activeId, handles, guides, activeElement } = toRefs(props.polarvo.freeDrop.state);
102
- const { getElementStyle, toggleLock, startDrag, startResize } = props.polarvo.freeDrop;
112
+ const { getElementStyle, toggleLock, handleMouseDown, startResize } = props.polarvo.freeDrop;
103
113
  const filteredElements = computed(() => {
104
114
  return elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
105
115
  });
106
116
 
117
+ const _componentCache = new Map();
118
+ const modules = import.meta.glob('@/components/elements/*.vue');
119
+
107
120
  function dynamicComponent(elName) {
108
- const modules = import.meta.glob('@/components/elements/*.vue');
121
+ if (_componentCache.has(elName)) {
122
+ return _componentCache.get(elName);
123
+ }
124
+
109
125
  const matched = Object.keys(modules).find((path) => {
110
126
  return path.includes(elName);
111
127
  });
@@ -114,7 +130,9 @@ function dynamicComponent(elName) {
114
130
  return markRaw(defineAsyncComponent(async () => ({ template: '<span style="display:none"></span>' })));
115
131
  }
116
132
 
117
- return markRaw(defineAsyncComponent(modules[matched]));
133
+ const component = markRaw(defineAsyncComponent(modules[matched]));
134
+ _componentCache.set(elName, component);
135
+ return component;
118
136
  }
119
137
 
120
138
  function setComponentRef(el, elementId) {
@@ -132,6 +150,7 @@ function toggleEditMode() {
132
150
  activeEditMode.value = !activeEditMode.value;
133
151
  }
134
152
 
153
+ let _isElementSwitching = false;
135
154
  import { omit, cloneDeep } from 'lodash-es';
136
155
  watch(
137
156
  () => activeElement.value?.id,
@@ -140,6 +159,7 @@ watch(
140
159
  if (!newId) {
141
160
  setActiveDesign(null);
142
161
  }
162
+ _isElementSwitching = true;
143
163
  // selectedElement.value = structuredClone(toRaw(activeElement.value));
144
164
  selectedElement.value = cloneDeep(activeElement.value);
145
165
 
@@ -149,11 +169,15 @@ watch(
149
169
  );
150
170
 
151
171
  // 특정 키 값을 제외하고 변경 감지
152
- const selectedElementForWatch = computed(() => omit(selectedElement.value, ['position', 'size', 'id']));
172
+ const selectedElementForWatch = computed(() => omit(selectedElement.value, ['mode', 'section', 'position', 'size', 'id']));
153
173
 
154
174
  watch(
155
175
  () => selectedElementForWatch.value,
156
176
  (newElement) => {
177
+ if (_isElementSwitching) {
178
+ _isElementSwitching = false;
179
+ return;
180
+ }
157
181
  updateActiveElement(selectedElement.value?.id, newElement);
158
182
  },
159
183
  { deep: true },
@@ -0,0 +1,187 @@
1
+ <template>
2
+ <template v-if="!preview">
3
+ <div
4
+ v-for="(item, index) in filteredElements"
5
+ :key="item.id"
6
+ :id="item.id"
7
+ class="relative bg-gray-200"
8
+ :style="getElementStyle('element', item)"
9
+ :class="{ 'z-50': item.id === activeId, 'bg-white border-2 border-blue-400': item.id === activeId }"
10
+ @mousedown.stop="activeEditMode ? null : handleMouseDown($event, item)"
11
+ >
12
+ <!-- 리사이즈 핸들 -->
13
+ <div v-if="item.id === activeId">
14
+ <div class="absolute w-4 h-4 bottom-0 right-0 bg-blue-400 z-10 cursor-se-resize" @mousedown="startResize($event, item.id)"></div>
15
+ <div class="flex gap-1 m-1">
16
+ <!-- 잠금 토글-->
17
+ <div class="z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
18
+ <LockIcon v-if="item.isLocked" />
19
+ <UnlockIcon v-else />
20
+ </div>
21
+ <!-- 편집 토글-->
22
+ <div class="z-10 pointer-events-auto cursor-pointer">
23
+ <div v-if="item.type === 'inputForm'" @click="toggleEditMode">
24
+ <div v-if="activeEditMode" class="bg-red-100 text-red-400 px-2 py-1 text-sm rounded-md hover:opacity-80 animate-pulse">
25
+ 편집모드 on
26
+ </div>
27
+ <div v-else class="bg-gray-100 px-2 py-1 text-sm rounded-md hover:bg-gray-200">편집모드 off</div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+
33
+ <component
34
+ :is="dynamicComponent(item.type)"
35
+ :id="`${sectionKey}-${item.id}`"
36
+ :rootId="item.id"
37
+ :ref="(el) => setComponentRef(el, item.id)"
38
+ v-bind="item"
39
+ class="hover:opacity-80"
40
+ :class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
41
+ />
42
+ </div>
43
+
44
+ <!-- 오버레이 -->
45
+ <div v-if="activeEditMode" class="absolute inset-0 bg-black opacity-50"></div>
46
+
47
+ <!-- 빈 그리드 설정 -->
48
+ <div
49
+ v-for="(cell, index) in emptyData[sectionKey]"
50
+ :key="`${cell.row}-${cell.col}`"
51
+ class="flex items-center justify-center bg-gray-100 border border-gray-300"
52
+ :style="getElementStyle('empty', cell)"
53
+ @mouseenter="detectHoverCell(cell)"
54
+ >
55
+ <div
56
+ class="text-sm w-8 h-8 rounded-full bg-gray-300 flex items-center justify-center text-gray-500 hover:bg-blue-200 hover:text-white transition-colors cursor-pointer"
57
+ @mousedown="handleMouseDown($event, cell)"
58
+ >
59
+ +
60
+ </div>
61
+ </div>
62
+ </template>
63
+ </template>
64
+
65
+ <script setup>
66
+ import LockIcon from '../../icons/action/LockIcon.vue';
67
+ import UnlockIcon from '../../icons/action/UnlockIcon.vue';
68
+
69
+ import {
70
+ toRefs,
71
+ ref,
72
+ defineAsyncComponent,
73
+ markRaw,
74
+ getCurrentInstance,
75
+ onMounted,
76
+ onUnmounted,
77
+ inject,
78
+ watch,
79
+ computed,
80
+ } from 'vue';
81
+ const emit = defineEmits(['click:element']);
82
+ const props = defineProps({
83
+ polarvo: {
84
+ type: Object,
85
+ required: true,
86
+ },
87
+ preview: {
88
+ type: Boolean,
89
+ default: false,
90
+ },
91
+ sectionKey: {
92
+ type: String,
93
+ required: true,
94
+ },
95
+ elementIds: {
96
+ type: Array,
97
+ required: true,
98
+ },
99
+ });
100
+
101
+ const { setActiveDesign } = props.polarvo.display;
102
+ const { updateActiveElement } = props.polarvo.layout;
103
+ const { elements, emptyData, activeId, activeCell, activeElement } = toRefs(props.polarvo.gridDrop.state);
104
+ const { getElementStyle, setActiveElement, toggleLock, startDrag, handleMouseDown, detectHoverCell, startResize } = props.polarvo.gridDrop;
105
+ const filteredElements = computed(() => {
106
+ return elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
107
+ });
108
+
109
+ const _componentCache = new Map();
110
+ const modules = import.meta.glob('@/components/elements/*.vue');
111
+
112
+ function dynamicComponent(elName) {
113
+ if (_componentCache.has(elName)) {
114
+ return _componentCache.get(elName);
115
+ }
116
+
117
+ const matched = Object.keys(modules).find((path) => {
118
+ return path.includes(elName);
119
+ });
120
+
121
+ if (!matched) {
122
+ return markRaw(defineAsyncComponent(async () => ({ template: '<span style="display:none"></span>' })));
123
+ }
124
+
125
+ const component = markRaw(defineAsyncComponent(modules[matched]));
126
+ _componentCache.set(elName, component);
127
+ return component;
128
+ }
129
+
130
+ function setComponentRef(el, elementId) {
131
+ if (el) {
132
+ props.polarvo.components.register('elements', `${props.sectionKey}-${elementId}`, el);
133
+ } else {
134
+ props.polarvo.components.unregister('elements', `${props.sectionKey}-${elementId}`);
135
+ }
136
+ }
137
+
138
+ const selectedElement = inject('selectedElement');
139
+ const activeEditMode = inject('activeEditMode');
140
+
141
+ function toggleEditMode() {
142
+ activeEditMode.value = !activeEditMode.value;
143
+ }
144
+
145
+ let _isElementSwitching = false;
146
+ import { omit, cloneDeep } from 'lodash-es';
147
+ watch(
148
+ () => activeElement.value?.id,
149
+ (newId, oldId) => {
150
+ if (newId === oldId) return;
151
+ if (!newId) {
152
+ setActiveDesign(null);
153
+ }
154
+ _isElementSwitching = true;
155
+ // selectedElement.value = structuredClone(toRaw(activeElement.value));
156
+ selectedElement.value = cloneDeep(activeElement.value);
157
+
158
+ // activeElement 바뀌면 편집 모드 해제
159
+ activeEditMode.value = false;
160
+ },
161
+ { immediate: true },
162
+ );
163
+
164
+ // 특정 키 값을 제외하고 변경 감지
165
+ const selectedElementForWatch = computed(() => omit(selectedElement.value, ['mode', 'section', 'position', 'size', 'id']));
166
+
167
+ watch(
168
+ () => selectedElementForWatch.value,
169
+ (newElement) => {
170
+ if (_isElementSwitching) {
171
+ _isElementSwitching = false;
172
+ return;
173
+ }
174
+ updateActiveElement(selectedElement.value?.id, newElement);
175
+ },
176
+ { deep: true },
177
+ );
178
+
179
+ onMounted(() => {
180
+ props.polarvo.components.register('sections', props.sectionKey, getCurrentInstance());
181
+ activeEditMode.value = false;
182
+ });
183
+
184
+ onUnmounted(() => {
185
+ props.polarvo.components.unregister('sections', props.sectionKey);
186
+ });
187
+ </script>
@@ -1,63 +1,66 @@
1
1
  <template>
2
2
  <template v-if="!preview">
3
3
  <div
4
- v-for="(item, index) in filteredElements"
5
- :key="index"
6
- :id="item.id"
7
- class="relative bg-gray-200"
8
- :style="getElementStyle('element', item)"
9
- :class="{ 'z-50': item.id === activeId, 'bg-white border-2 border-blue-400': item.id === activeId }"
10
- @mousedown.stop="activeEditMode ? null : startDrag($event, item.id)"
4
+ v-for="(item, index) in mergedData"
5
+ :key="item.id ?? `${item.row}-${item.col}`"
6
+ :id="item.id ?? `${item.row}-${item.col}`"
7
+ :style="getElementStyle(item.id ? 'element' : 'empty', item)"
8
+ :class="
9
+ item.id
10
+ ? ['relative bg-gray-200', { 'z-50 bg-white border-2 border-blue-400': item.id === activeId }]
11
+ : 'flex items-center justify-center bg-gray-100 border border-gray-300'
12
+ "
13
+ @mousedown="activeEditMode ? null : handleMouseDown($event, item)"
14
+ @mouseenter="item.id ? null : detectHoverCell(item)"
11
15
  >
12
- <!-- 리사이즈 핸들 -->
13
- <div v-if="item.id === activeId">
14
- <div class="absolute w-4 h-4 bottom-0 right-0 bg-blue-400 z-10 cursor-se-resize" @mousedown="startResize($event, item.id)"></div>
15
- <div class="flex gap-1 m-1">
16
- <!-- 잠금 토글-->
17
- <div class="z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
18
- <LockIcon v-if="item.isLocked" />
19
- <UnlockIcon v-else />
20
- </div>
21
- <!-- 편집 토글-->
22
- <div class="z-10 pointer-events-auto cursor-pointer">
23
- <div v-if="item.type === 'inputForm'" @click="toggleEditMode">
24
- <div v-if="activeEditMode" class="bg-red-100 text-red-400 px-2 py-1 text-sm rounded-md hover:opacity-80 animate-pulse">
25
- 편집모드 on
16
+ <template v-if="item.id">
17
+ <div>
18
+ <!-- 리사이즈 핸들 -->
19
+ <div v-if="item.id === activeId">
20
+ <div
21
+ class="absolute w-4 h-4 bottom-0 right-0 bg-blue-400 z-10 cursor-se-resize"
22
+ @mousedown="startResize($event, item.id)"
23
+ ></div>
24
+ <div class="flex gap-1 m-1">
25
+ <!-- 잠금 토글-->
26
+ <div class="z-10 pointer-events-auto cursor-pointer" @click="toggleLock()">
27
+ <LockIcon v-if="item.isLocked" />
28
+ <UnlockIcon v-else />
29
+ </div>
30
+ <!-- 편집 토글-->
31
+ <div class="z-10 pointer-events-auto cursor-pointer">
32
+ <div v-if="item.type === 'inputForm'" @click="toggleEditMode">
33
+ <div v-if="activeEditMode" class="bg-red-100 text-red-400 px-2 py-1 text-sm rounded-md hover:opacity-80 animate-pulse">
34
+ 편집모드 on
35
+ </div>
36
+ <div v-else class="bg-gray-100 px-2 py-1 text-sm rounded-md hover:bg-gray-200">편집모드 off</div>
37
+ </div>
26
38
  </div>
27
- <div v-else class="bg-gray-100 px-2 py-1 text-sm rounded-md hover:bg-gray-200">편집모드 off</div>
28
39
  </div>
29
40
  </div>
41
+
42
+ <component
43
+ :is="dynamicComponent(item.type)"
44
+ :id="`${sectionKey}-${item.id}`"
45
+ :rootId="item.id"
46
+ :ref="(el) => setComponentRef(el, item.id)"
47
+ v-bind="item"
48
+ class="hover:opacity-80"
49
+ :class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
50
+ />
30
51
  </div>
31
- </div>
32
-
33
- <component
34
- :is="dynamicComponent(item.type)"
35
- :id="`${sectionKey}-${index}`"
36
- :rootId="item.id"
37
- :ref="(el) => setComponentRef(el, item.id)"
38
- v-bind="item"
39
- class="hover:opacity-80"
40
- :class="activeEditMode ? `pointer-events-auto ` : `pointer-events-none`"
41
- />
42
- </div>
43
52
 
44
- <!-- 오버레이 -->
45
- <div v-if="activeEditMode" class="absolute inset-0 bg-black opacity-50"></div>
53
+ <!-- 오버레이 -->
54
+ <div v-if="activeEditMode && item.id !== activeId" class="absolute inset-0 bg-black opacity-50"></div>
55
+ </template>
46
56
 
47
- <!-- 빈 그리드 설정 -->
48
- <div
49
- v-for="(cell, index) in emptyData[sectionKey]"
50
- :key="index"
51
- class="flex items-center justify-center bg-gray-100 border border-gray-300"
52
- :style="getElementStyle('empty', cell)"
53
- @mouseenter="detectHoverCell(cell)"
54
- >
55
- <div
56
- class="text-sm w-8 h-8 rounded-full bg-gray-300 flex items-center justify-center text-gray-500 hover:bg-blue-200 hover:text-white transition-colors cursor-pointer"
57
- @click="setActiveElement({ position: cell }, 'click')"
58
- >
59
- +
60
- </div>
57
+ <template v-else>
58
+ <div
59
+ class="text-sm w-8 h-8 rounded-full bg-gray-300 flex items-center justify-center text-gray-500 hover:bg-blue-200 hover:text-white transition-colors cursor-pointer"
60
+ >
61
+ +
62
+ </div>
63
+ </template>
61
64
  </div>
62
65
  </template>
63
66
  </template>
@@ -66,19 +69,7 @@
66
69
  import LockIcon from '../../icons/action/LockIcon.vue';
67
70
  import UnlockIcon from '../../icons/action/UnlockIcon.vue';
68
71
 
69
- import {
70
- nextTick,
71
- toRefs,
72
- defineAsyncComponent,
73
- markRaw,
74
- getCurrentInstance,
75
- onMounted,
76
- onUnmounted,
77
- inject,
78
- watch,
79
- toRaw,
80
- computed,
81
- } from 'vue';
72
+ import { toRefs, ref, defineAsyncComponent, markRaw, getCurrentInstance, onMounted, onUnmounted, inject, watch, computed } from 'vue';
82
73
  const emit = defineEmits(['click:element']);
83
74
  const props = defineProps({
84
75
  polarvo: {
@@ -93,6 +84,10 @@ const props = defineProps({
93
84
  type: String,
94
85
  required: true,
95
86
  },
87
+ sectionData: {
88
+ type: Object,
89
+ required: true,
90
+ },
96
91
  elementIds: {
97
92
  type: Array,
98
93
  required: true,
@@ -101,14 +96,39 @@ const props = defineProps({
101
96
 
102
97
  const { setActiveDesign } = props.polarvo.display;
103
98
  const { updateActiveElement } = props.polarvo.layout;
104
- const { elements, emptyData, activeId, activeCell, activeElement } = toRefs(props.polarvo.gridDrop.state);
105
- const { getElementStyle, setActiveElement, toggleLock, startDrag, detectHoverCell, startResize } = props.polarvo.gridDrop;
106
- const filteredElements = computed(() => {
107
- return elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
99
+ const { elements, activeId, activeCell, activeElement } = toRefs(props.polarvo.gridDrop.state);
100
+ const { getElementStyle, setActiveElement, toggleLock, handleMouseDown, detectHoverCell, startResize } = props.polarvo.gridDrop;
101
+
102
+ const mergedData = computed(() => {
103
+ const { gridColumns, gridRows } = props.sectionData?.config ?? { gridColumns: 3, gridRows: 3 };
104
+ const grid = Array.from({ length: gridRows + 1 }, () => Array(gridColumns + 1).fill(false));
105
+
106
+ const filled = elements.value.filter((el) => props.elementIds?.includes(el.id) && el.section === props.sectionKey);
107
+
108
+ filled.forEach(({ position: { col, row }, size: { colSpan, rowSpan } }) => {
109
+ for (let r = row; r < row + rowSpan; r++) {
110
+ for (let c = col; c < col + colSpan; c++) {
111
+ if (r >= 1 && c >= 1 && r <= gridRows && c <= gridColumns) {
112
+ grid[r][c] = true;
113
+ }
114
+ }
115
+ }
116
+ });
117
+
118
+ const empty = [];
119
+ for (let r = 1; r <= gridRows; r++) for (let c = 1; c <= gridColumns; c++) if (!grid[r][c]) empty.push({ row: r, col: c });
120
+
121
+ return [...filled, ...empty];
108
122
  });
109
123
 
124
+ const _componentCache = new Map();
125
+ const modules = import.meta.glob('@/components/elements/*.vue');
126
+
110
127
  function dynamicComponent(elName) {
111
- const modules = import.meta.glob('@/components/elements/*.vue');
128
+ if (_componentCache.has(elName)) {
129
+ return _componentCache.get(elName);
130
+ }
131
+
112
132
  const matched = Object.keys(modules).find((path) => {
113
133
  return path.includes(elName);
114
134
  });
@@ -117,7 +137,9 @@ function dynamicComponent(elName) {
117
137
  return markRaw(defineAsyncComponent(async () => ({ template: '<span style="display:none"></span>' })));
118
138
  }
119
139
 
120
- return markRaw(defineAsyncComponent(modules[matched]));
140
+ const component = markRaw(defineAsyncComponent(modules[matched]));
141
+ _componentCache.set(elName, component);
142
+ return component;
121
143
  }
122
144
 
123
145
  function setComponentRef(el, elementId) {
@@ -135,6 +157,7 @@ function toggleEditMode() {
135
157
  activeEditMode.value = !activeEditMode.value;
136
158
  }
137
159
 
160
+ let _isElementSwitching = false;
138
161
  import { omit, cloneDeep } from 'lodash-es';
139
162
  watch(
140
163
  () => activeElement.value?.id,
@@ -143,6 +166,7 @@ watch(
143
166
  if (!newId) {
144
167
  setActiveDesign(null);
145
168
  }
169
+ _isElementSwitching = true;
146
170
  // selectedElement.value = structuredClone(toRaw(activeElement.value));
147
171
  selectedElement.value = cloneDeep(activeElement.value);
148
172
 
@@ -153,11 +177,15 @@ watch(
153
177
  );
154
178
 
155
179
  // 특정 키 값을 제외하고 변경 감지
156
- const selectedElementForWatch = computed(() => omit(selectedElement.value, ['position', 'size', 'id']));
180
+ const selectedElementForWatch = computed(() => omit(selectedElement.value, ['mode', 'section', 'position', 'size', 'id']));
157
181
 
158
182
  watch(
159
183
  () => selectedElementForWatch.value,
160
184
  (newElement) => {
185
+ if (_isElementSwitching) {
186
+ _isElementSwitching = false;
187
+ return;
188
+ }
161
189
  updateActiveElement(selectedElement.value?.id, newElement);
162
190
  },
163
191
  { deep: true },
@@ -1,5 +1,25 @@
1
1
  <template>
2
- <div id="polarLayout" class="w-full h-full rounded-md">
2
+ <div id="polarLayout" class="w-full h-full rounded-md" :class="mode === 'grid' ? 'polar-grid' : null">
3
3
  <slot></slot>
4
4
  </div>
5
- </template>
5
+ </template>
6
+
7
+ <script setup>
8
+ const props = defineProps({
9
+ mode: {
10
+ type: String,
11
+ default: 'free',
12
+ },
13
+ });
14
+ </script>
15
+
16
+ <style scoped>
17
+ .polar-grid {
18
+ display: grid;
19
+ grid-template-columns: repeat(var(--grid-columns, 3), 1fr);
20
+ gap: var(--grid-gap, 5px);
21
+ width: 100%;
22
+ height: 100%;
23
+ border-radius: 0.375rem;
24
+ }
25
+ </style>
@@ -72,14 +72,19 @@ class DisplayEngine {
72
72
 
73
73
  this._subscribe('gridDrop:setActiveElement', ({ action }) => {
74
74
  if (action === 'update') return;
75
- if (action === 'click') {
76
- this.setActiveMenu('element');
77
- } else {
75
+ // if (action === 'click') {
76
+ // this.setActiveMenu('element');
77
+ // } else {
78
78
  this.setActiveMenu(null);
79
- }
79
+ // }
80
80
 
81
81
  this.setActiveDesign(null);
82
82
  });
83
+
84
+ this._subscribe('gridDrop:requestOpenElementBar', () => {
85
+ this.setActiveMenu('element');
86
+ this.setActiveDesign(null);
87
+ })
83
88
  }
84
89
 
85
90
  /** 삭제 및 정리 */