feat(rflows): fix text-click drag, smart source labels, Pay by buttons

- Prevent foreignObject HTML clicks from starting node drag (select + inline edit instead)
- New source nodes get personalized "{username}'s stream to {flowName}" label
- Replace source type <select> dropdowns with clickable "Pay by" button grid
  in ICP panel, editor panel, and source modal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-09 19:19:46 -07:00
parent a28eb88140
commit f24fee942b
2 changed files with 72 additions and 10 deletions

View File

@ -576,6 +576,30 @@
.source-type-btn:hover { border-color: var(--rs-bg-surface-raised); background: var(--rs-bg-surface); }
.source-type-btn--active { border-color: #10b981; background: var(--rflows-source-hover-bg, #064e3b); color: var(--rflows-source-text, #6ee7b7); }
/* Pay-by button grid (ICP - compact) */
.icp-pay-by { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; margin-top: 4px; }
.icp-pay-btn {
display: flex; align-items: center; justify-content: center; gap: 4px;
padding: 6px 4px; border-radius: 6px; border: 1.5px solid var(--rs-border-strong);
background: var(--rs-bg-page); color: var(--rs-text-secondary); cursor: pointer;
transition: all 0.15s; font-size: 10px; font-weight: 500;
font-family: system-ui, -apple-system, sans-serif;
}
.icp-pay-btn:hover { border-color: var(--rs-bg-surface-raised); background: var(--rs-bg-surface); }
.icp-pay-btn--active { border-color: #10b981; background: var(--rflows-source-hover-bg, #064e3b); color: var(--rflows-source-text, #6ee7b7); }
/* Pay-by button grid (editor panel - larger) */
.editor-pay-by { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 4px; }
.editor-pay-btn {
display: flex; flex-direction: column; align-items: center; gap: 4px;
padding: 10px 6px; border-radius: 8px; border: 2px solid var(--rs-border-strong);
background: var(--rs-bg-page); color: var(--rs-text-secondary); cursor: pointer;
transition: all 0.15s; font-size: 11px; font-weight: 500;
font-family: system-ui, -apple-system, sans-serif;
}
.editor-pay-btn:hover { border-color: var(--rs-bg-surface-raised); background: var(--rs-bg-surface); }
.editor-pay-btn--active { border-color: #10b981; background: var(--rflows-source-hover-bg, #064e3b); color: var(--rflows-source-text, #6ee7b7); }
/* Node hover tooltip */
.flows-node-tooltip {
position: absolute; z-index: 30; pointer-events: none;

View File

@ -1343,6 +1343,13 @@ class FolkFlowsApp extends HTMLElement {
this.selectedEdgeKey = null;
this.updateSelectionHighlight();
// If click originated from HTML inside foreignObject, open inline edit but skip drag
const target = e.target as Element;
if (target instanceof HTMLElement && target.closest("foreignObject")) {
this.enterInlineEdit(nodeId);
return;
}
// Prepare drag (but don't start until threshold exceeded)
nodeDragStarted = false;
this.draggingNodeId = nodeId;
@ -2769,10 +2776,12 @@ class FolkFlowsApp extends HTMLElement {
<input class="icp-input" data-icp-field="label" value="${this.esc(d.label)}"/></div>
<div class="icp-field"><label class="icp-label">Flow Rate ($/mo)</label>
<input class="icp-input" data-icp-field="flowRate" type="number" value="${d.flowRate}"/></div>
<div class="icp-field"><label class="icp-label">Source Type</label>
<select class="icp-select" data-icp-field="sourceType">
${["card", "safe_wallet", "ridentity", "unconfigured"].map((t) => `<option value="${t}" ${d.sourceType === t ? "selected" : ""}>${t}</option>`).join("")}
</select></div>`;
<div class="icp-field"><label class="icp-label">Pay by</label>
<div class="icp-pay-by">
<button class="icp-pay-btn ${d.sourceType === "card" ? "icp-pay-btn--active" : ""}" data-icp-source-type="card">💳 Card</button>
<button class="icp-pay-btn ${d.sourceType === "safe_wallet" ? "icp-pay-btn--active" : ""}" data-icp-source-type="safe_wallet">🔒 Wallet</button>
<button class="icp-pay-btn ${d.sourceType === "ridentity" ? "icp-pay-btn--active" : ""}" data-icp-source-type="ridentity">👤 EncryptID</button>
</div></div>`;
if (d.sourceType === "card") {
html += `<button class="icp-fund-btn" data-icp-action="fund">Fund with Card</button>`;
}
@ -3124,6 +3133,19 @@ class FolkFlowsApp extends HTMLElement {
});
});
// Pay-by buttons (source nodes)
overlay.querySelectorAll("[data-icp-source-type]").forEach((btn) => {
btn.addEventListener("click", (e: Event) => {
e.stopPropagation();
(node.data as SourceNodeData).sourceType = (btn as HTMLElement).dataset.icpSourceType as any;
const body = overlay.querySelector(".icp-body") as HTMLElement;
if (body) body.innerHTML = this.renderInlineConfigContent(node);
this.attachInlineConfigFieldListeners(overlay, node);
this.redrawNodeOnly(node);
this.redrawEdges();
});
});
// Range sliders
overlay.querySelectorAll("[data-icp-range]").forEach((el) => {
const input = el as HTMLInputElement;
@ -3326,10 +3348,12 @@ class FolkFlowsApp extends HTMLElement {
<input class="editor-input" data-field="label" value="${this.esc(d.label)}"/></div>
<div class="editor-field"><label class="editor-label">Flow Rate ($/mo)</label>
<input class="editor-input" data-field="flowRate" type="number" value="${d.flowRate}"/></div>
<div class="editor-field"><label class="editor-label">Source Type</label>
<select class="editor-select" data-field="sourceType">
${["card", "safe_wallet", "ridentity", "unconfigured"].map((t) => `<option value="${t}" ${d.sourceType === t ? "selected" : ""}>${t}</option>`).join("")}
</select></div>`;
<div class="editor-field"><label class="editor-label">Pay by</label>
<div class="editor-pay-by">
<button class="editor-pay-btn ${d.sourceType === "card" ? "editor-pay-btn--active" : ""}" data-editor-source-type="card">💳<span>Card</span></button>
<button class="editor-pay-btn ${d.sourceType === "safe_wallet" ? "editor-pay-btn--active" : ""}" data-editor-source-type="safe_wallet">🔒<span>Wallet</span></button>
<button class="editor-pay-btn ${d.sourceType === "ridentity" ? "editor-pay-btn--active" : ""}" data-editor-source-type="ridentity">👤<span>EncryptID</span></button>
</div></div>`;
if (d.sourceType === "card") {
html += `<div class="editor-field" style="margin-top:12px">
<button class="editor-btn fund-card-btn" data-action="fund-with-card"
@ -3765,6 +3789,16 @@ class FolkFlowsApp extends HTMLElement {
this.openUserOnRamp(node.id);
}
});
// Pay-by buttons (source editor)
panel.querySelectorAll("[data-editor-source-type]").forEach((btn) => {
btn.addEventListener("click", () => {
(node.data as SourceNodeData).sourceType = (btn as HTMLElement).dataset.editorSourceType as any;
this.drawCanvasContent();
this.scheduleSave();
this.openEditor(node.id);
});
});
}
// ─── Node hover tooltip ──────────────────────────────
@ -4051,7 +4085,7 @@ class FolkFlowsApp extends HTMLElement {
<button class="flows-modal__close" data-modal-action="close">&times;</button>
</div>
<div style="margin-bottom:16px">
<div style="font-size:11px;font-weight:600;color:var(--rs-text-secondary);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px">Source Type</div>
<div style="font-size:11px;font-weight:600;color:var(--rs-text-secondary);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px">Pay by</div>
<div class="source-type-grid">
${["card", "safe_wallet", "ridentity"].map((t) => `
<button class="source-type-btn ${d.sourceType === t ? "source-type-btn--active" : ""}" data-source-type="${t}">
@ -4152,7 +4186,11 @@ class FolkFlowsApp extends HTMLElement {
const id = `${type}-${Date.now().toString(36)}`;
let data: any;
if (type === "source") {
data = { label: "New Source", flowRate: 1000, sourceType: "card", targetAllocations: [] } as SourceNodeData;
const username = getUsername();
const defaultLabel = username
? `${username}'s stream to ${this.flowName || "Flow"}`
: `Stream to ${this.flowName || "Flow"}`;
data = { label: defaultLabel, flowRate: 1000, sourceType: "card", targetAllocations: [] } as SourceNodeData;
} else if (type === "funnel") {
data = {
label: "New Funnel", currentValue: 0, desiredOutflow: 5000,