fix: [rd] 기획디자인 연결선 렌더링 수정 (SVG namespace 문제 해결)
- Alpine.js <template x-for>가 SVG 내부에서 path 요소 생성 불가 문제 - SVG 요소를 createElementNS로 직접 생성하는 renderConnections() 도입 - x-effect + _connTick 카운터로 노드 이동/연결 변경 시 자동 리렌더
This commit is contained in:
@@ -785,21 +785,8 @@
|
||||
@contextmenu.prevent="showContextMenu($event)">
|
||||
|
||||
<div class="pc-canvas" id="canvas" :style="'transform: scale(' + zoom + ') translate(' + panX + 'px,' + panY + 'px)'">
|
||||
{{-- SVG Connections --}}
|
||||
<svg class="pc-connections" id="connectionsSvg">
|
||||
<defs>
|
||||
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill="#94a3b8"/>
|
||||
</marker>
|
||||
</defs>
|
||||
<template x-for="conn in connections" :key="conn.id">
|
||||
<path class="pc-conn" :d="getConnectionPath(conn)" marker-end="url(#arrowhead)"
|
||||
style="pointer-events: stroke; cursor: pointer;"
|
||||
@click="selectConnection(conn)"
|
||||
:style="selectedConnection?.id === conn.id ? 'stroke: #6366f1; stroke-width: 3;' : ''"/>
|
||||
</template>
|
||||
<path x-show="drawingConnection" class="pc-conn-active" :d="tempConnectionPath" marker-end="url(#arrowhead)"/>
|
||||
</svg>
|
||||
{{-- SVG Connections (프로그래밍 렌더링 — Alpine template은 SVG 네임스페이스 미지원) --}}
|
||||
<svg class="pc-connections" id="connectionsSvg" x-ref="connSvg" x-effect="renderConnections()"></svg>
|
||||
|
||||
{{-- Timeline Swimlanes --}}
|
||||
<template x-if="viewMode === 'timeline'">
|
||||
@@ -1120,6 +1107,7 @@ function planningCanvas() {
|
||||
{ key: 'done', label: '완료', color: '#10b981' },
|
||||
],
|
||||
_kanbanDragNodeId: null,
|
||||
_connTick: 0,
|
||||
|
||||
// Drag State
|
||||
dragging: false,
|
||||
@@ -1403,6 +1391,84 @@ function planningCanvas() {
|
||||
}
|
||||
},
|
||||
|
||||
// ===== Connection Rendering (SVG namespace 직접 제어) =====
|
||||
renderConnections() {
|
||||
// _connTick 읽기 → x-effect 의존성 등록 (노드 이동 시 리렌더 트리거)
|
||||
void this._connTick;
|
||||
void this.connections.length;
|
||||
void this.selectedConnection;
|
||||
void this.drawingConnection;
|
||||
void this.tempConnectionPath;
|
||||
|
||||
const svg = this.$refs.connSvg;
|
||||
if (!svg) return;
|
||||
// 현재 내용 클리어
|
||||
svg.innerHTML = '';
|
||||
|
||||
// defs (arrowhead marker)
|
||||
const NS = 'http://www.w3.org/2000/svg';
|
||||
const defs = document.createElementNS(NS, 'defs');
|
||||
|
||||
const marker = document.createElementNS(NS, 'marker');
|
||||
marker.setAttribute('id', 'arrowhead');
|
||||
marker.setAttribute('markerWidth', '10');
|
||||
marker.setAttribute('markerHeight', '7');
|
||||
marker.setAttribute('refX', '9');
|
||||
marker.setAttribute('refY', '3.5');
|
||||
marker.setAttribute('orient', 'auto');
|
||||
const poly = document.createElementNS(NS, 'polygon');
|
||||
poly.setAttribute('points', '0 0, 10 3.5, 0 7');
|
||||
poly.setAttribute('fill', '#94a3b8');
|
||||
marker.appendChild(poly);
|
||||
|
||||
const markerSel = document.createElementNS(NS, 'marker');
|
||||
markerSel.setAttribute('id', 'arrowhead-sel');
|
||||
markerSel.setAttribute('markerWidth', '10');
|
||||
markerSel.setAttribute('markerHeight', '7');
|
||||
markerSel.setAttribute('refX', '9');
|
||||
markerSel.setAttribute('refY', '3.5');
|
||||
markerSel.setAttribute('orient', 'auto');
|
||||
const polySel = document.createElementNS(NS, 'polygon');
|
||||
polySel.setAttribute('points', '0 0, 10 3.5, 0 7');
|
||||
polySel.setAttribute('fill', '#6366f1');
|
||||
markerSel.appendChild(polySel);
|
||||
|
||||
defs.appendChild(marker);
|
||||
defs.appendChild(markerSel);
|
||||
svg.appendChild(defs);
|
||||
|
||||
// 기존 연결선
|
||||
const self = this;
|
||||
this.connections.forEach(conn => {
|
||||
const d = this.getConnectionPath(conn);
|
||||
if (!d) return;
|
||||
const path = document.createElementNS(NS, 'path');
|
||||
path.setAttribute('d', d);
|
||||
const isSel = this.selectedConnection?.id === conn.id;
|
||||
path.setAttribute('stroke', isSel ? '#6366f1' : '#94a3b8');
|
||||
path.setAttribute('stroke-width', isSel ? '3' : '2');
|
||||
path.setAttribute('fill', 'none');
|
||||
path.setAttribute('marker-end', isSel ? 'url(#arrowhead-sel)' : 'url(#arrowhead)');
|
||||
path.style.pointerEvents = 'stroke';
|
||||
path.style.cursor = 'pointer';
|
||||
path.addEventListener('click', () => { self.selectConnection(conn); });
|
||||
svg.appendChild(path);
|
||||
});
|
||||
|
||||
// 드래그 중인 임시 연결선
|
||||
if (this.drawingConnection && this.tempConnectionPath) {
|
||||
const temp = document.createElementNS(NS, 'path');
|
||||
temp.setAttribute('d', this.tempConnectionPath);
|
||||
temp.setAttribute('stroke', '#6366f1');
|
||||
temp.setAttribute('stroke-width', '2');
|
||||
temp.setAttribute('stroke-dasharray', '6 3');
|
||||
temp.setAttribute('fill', 'none');
|
||||
temp.setAttribute('marker-end', 'url(#arrowhead-sel)');
|
||||
temp.style.pointerEvents = 'none';
|
||||
svg.appendChild(temp);
|
||||
}
|
||||
},
|
||||
|
||||
// ===== Connection Operations =====
|
||||
startConnection(e, node, port) {
|
||||
if (this.tool !== 'connect' && this.tool !== 'select') return;
|
||||
@@ -1465,6 +1531,7 @@ function planningCanvas() {
|
||||
const rect = wrap.getBoundingClientRect();
|
||||
this.dragNode.x = (e.clientX - rect.left - this.panX * this.zoom) / this.zoom - this.dragOffsetX;
|
||||
this.dragNode.y = (e.clientY - rect.top - this.panY * this.zoom) / this.zoom - this.dragOffsetY;
|
||||
this._connTick++;
|
||||
}
|
||||
if (this.drawingConnection && this.connSource) {
|
||||
const wrap = document.getElementById('canvasWrap');
|
||||
|
||||
Reference in New Issue
Block a user