feat(flowchart): replace window.prompt with inline modal (C1)

The standalone flowchart generator still used window.prompt() for
editing edge kind/label and changing node kind — defeating the v4.9.9
modal work that's already in flowchart-bundle.js.

This commit switches to the existing window.FlowchartModals helpers:
  - onEdgeClick now does two sequential promptInline calls (kind,
    then label) instead of two blocking window.prompt calls.
  - onShapeMenu uses promptInline for node-kind change.
  - onEmptyClick (new) replaces the canvas's old 'auto-add a process
    node labelled Node on any empty click' behaviour. The canvas
    now hands off x/y to the controller, which prompts for kind +
    label before adding.

Canvas change is one line: drop the auto-add and call opts.onEmptyClick.

followups in this batch:
  - select/dropdown fields in the modal (currently text-only inputs)
  - edge connection geometry (centre-to-centre → boundary)
  - connect-mode preview line
  - canvas-owned selection state (DOM .selected is fragile)
  - undo/redo toolbar + counter
  - edge label auto-size + theme-safe background
  - zoom/pan, snap-to-grid, copy/paste, mermaid re-import, file IO

Amit Haridas
This commit is contained in:
Amit Haridas
2026-09-30 21:22:22 +05:30
parent d5efdfa8d9
commit 855dfcd9f5
2 changed files with 50 additions and 9 deletions
+6 -2
View File
@@ -223,12 +223,16 @@ function createCanvas(container, store, opts = {}) {
ev.preventDefault();
return;
}
// Click on empty canvas: add a process node at the click point.
// Click on empty canvas: hand off to the controller via onEmptyClick so it
// can prompt the user for the shape kind + label. v4.13.0 — the canvas
// no longer auto-creates a process node with placeholder label "Node".
if (ev.target === svg || ev.target === nodesLayer || ev.target === edgesLayer) {
const p = getSvgPoint(ev.clientX, ev.clientY);
const x = Math.max(0, p.x - DEFAULT_WIDTH / 2);
const y = Math.max(0, p.y - DEFAULT_HEIGHT / 2);
store.addNode({ kind: 'process', x, y, label: 'Node' });
if (typeof opts.onEmptyClick === 'function') {
opts.onEmptyClick(x, y, ev);
}
ev.preventDefault();
}
}
+44 -7
View File
@@ -59,6 +59,8 @@
const { create: createStore } = window.FlowchartStore;
const { createCanvas } = window.FlowchartCanvas;
const { toMermaid } = window.FlowchartMermaid;
const { promptInline, confirmInline: _confirmInline } = window.FlowchartModals || {};
void _confirmInline; // exposed by the bundle for future use (e.g. reset confirmation)
// FlowchartShapes is intentionally unused here but its presence is
// required for createCanvas() to function; the guard above guarantees it.
@@ -66,6 +68,9 @@
const PERSIST_DEBOUNCE_MS = 500;
const PERSISTENCE_FILENAME = 'flowchart-session.json';
const NODE_KIND_OPTIONS_TEXT = 'process, decision, terminator, subroutine, document';
const EDGE_KIND_OPTIONS_TEXT = 'solid, dotted, thick';
let _userDataPath = null;
let _persistenceFile = null;
let _previewTimer = null;
@@ -132,14 +137,25 @@
});
_canvas = createCanvas(els.canvasHost, _store, {
onEdgeClick: (edgeId) => {
onEdgeClick: async (edgeId) => {
const edge = _store.getGraph().edges.find((e) => e.id === edgeId);
if (!edge) return;
const nextKind = window.prompt('Edge kind (solid, dotted, thick):', edge.kind);
// v4.13.0 — replace window.prompt() with the inline modal helper.
// Two sequential prompts: kind first (with the current value as
// default), then label.
const nextKind = await promptInline({
title: 'Edge Style',
message: `Style (${EDGE_KIND_OPTIONS_TEXT}):`,
defaultValue: edge.kind,
});
if (nextKind && ['solid', 'dotted', 'thick'].includes(nextKind)) {
_store.setEdgeKind(edgeId, nextKind);
}
const nextLabel = window.prompt('Edge label (empty to clear):', edge.label || '');
const nextLabel = await promptInline({
title: 'Edge Label',
message: 'Empty to clear:',
defaultValue: edge.label || '',
});
if (nextLabel !== null) {
_store.setEdgeLabel(edgeId, nextLabel);
}
@@ -148,12 +164,33 @@
// Canvas already paints the .selected highlight; nothing else
// needed here for selection state.
},
onShapeMenu: (nodeId) => {
const next = window.prompt(
'New shape (process, decision, terminator, subroutine, document):'
);
onShapeMenu: async (nodeId) => {
const node = _store.getGraph().nodes.find((n) => n.id === nodeId);
if (!node) return;
const next = await promptInline({
title: 'Change Shape',
message: `New shape (${NODE_KIND_OPTIONS_TEXT}):`,
defaultValue: node.kind,
});
if (next) _store.setNodeKind(nodeId, next);
},
onEmptyClick: async (x, y) => {
// v4.13.0 — canvas click no longer auto-creates a process node
// with literal label "Node". User picks the shape kind first.
const kind = await promptInline({
title: 'Add Node',
message: `Shape (${NODE_KIND_OPTIONS_TEXT}):`,
defaultValue: 'process',
});
if (!kind) return;
const label = await promptInline({
title: 'Node Label',
message: 'Label (empty for "Node"):',
defaultValue: '',
});
const finalLabel = label && label.length > 0 ? label : 'Node';
_store.addNode({ kind, x, y, label: finalLabel });
},
});
_store.subscribe(() => {