mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 17:29:29 +05:30
feat(flowchart): visual polish — selection ring, hover, resize grip (C16)
Adds the visual feedback that was missing from the C10 resize + C2
selection commits. The canvas previously set the .selected class but
no CSS rule styled it, so a selected node looked identical to an
unselected one. Same for hover and the resize handle.
CSS additions (src/flowchart-generator.html):
- .flowchart-node: cursor:grab + 0.12s filter transition for the
hover tint (drop-shadow blue glow).
- .flowchart-node:hover .flowchart-node-shape: blue glow.
- .flowchart-node.selected .flowchart-node-shape: 2.25px blue
stroke — replaces the (previously invisible) .selected class.
- .flowchart-edge:hover and .flowchart-edge.selected: blue
stroke + 2.5px width for the same reason.
- .flowchart-resize-handle-hit: 24px transparent rect for an
enlarged hit area (the visible grip stays 10px).
- .flowchart-resize-handle-grip: blue square with stroke, styled
via CSS instead of inline fill/stroke (which were overriding
the rest of the file's theme).
- .flowchart-edge-label-bg: opaque white pill behind edge labels,
so labels stay readable when an edge curves under them.
- .fc-modal / .fc-modal-overlay: fade + scale-in keyframes
(0.15s ease-out) so the promptInline / confirmInline modals
animate in instead of popping in instantly.
Canvas (src/flowchart/flowchart-canvas.js): the resize handle now
emits two rects — a 24px transparent hit area (data-resize-node
attribute preserved) and a smaller 10px visible grip styled via
.flowchart-resize-handle-grip. Cursor stays nwse-resize via CSS.
Tests: no new tests (visual); existing 14 canvas tests still pass.
1376 total.
Amit Haridas
This commit is contained in:
@@ -185,6 +185,77 @@
|
|||||||
.flowchart-edge {
|
.flowchart-edge {
|
||||||
stroke: #1f2328 !important;
|
stroke: #1f2328 !important;
|
||||||
}
|
}
|
||||||
|
/* v4.13.0 — visual polish. Adds a visible selection ring (was
|
||||||
|
previously only the .selected class without a distinct colour),
|
||||||
|
a hover tint, an enlarged resize-handle hit area, a smoother
|
||||||
|
modal dialog animation, and a proper opaque background behind
|
||||||
|
edge labels so they stay readable across curved connectors. */
|
||||||
|
.flowchart-node {
|
||||||
|
cursor: grab;
|
||||||
|
transition: filter 0.12s ease-out;
|
||||||
|
}
|
||||||
|
.flowchart-node:hover .flowchart-node-shape {
|
||||||
|
filter: drop-shadow(0 0 2px rgba(37, 99, 235, 0.45));
|
||||||
|
}
|
||||||
|
.flowchart-node.selected .flowchart-node-shape {
|
||||||
|
stroke: #2563eb !important;
|
||||||
|
stroke-width: 2.25;
|
||||||
|
}
|
||||||
|
.flowchart-edge {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.flowchart-edge:hover {
|
||||||
|
stroke: #2563eb !important;
|
||||||
|
}
|
||||||
|
.flowchart-edge.selected {
|
||||||
|
stroke: #2563eb !important;
|
||||||
|
stroke-width: 2.5;
|
||||||
|
}
|
||||||
|
/* Resize handle — larger invisible hit area + visible square. */
|
||||||
|
.flowchart-resize-handle {
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
.flowchart-resize-handle-hit {
|
||||||
|
fill: transparent;
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
.flowchart-resize-handle-grip {
|
||||||
|
fill: #2563eb;
|
||||||
|
stroke: #1e40af;
|
||||||
|
stroke-width: 1;
|
||||||
|
}
|
||||||
|
/* Edge label background — keeps labels legible across curves by
|
||||||
|
giving them an opaque white pill behind the text. */
|
||||||
|
.flowchart-edge-label-bg {
|
||||||
|
fill: #ffffff;
|
||||||
|
stroke: #d1d5db;
|
||||||
|
stroke-width: 0.5;
|
||||||
|
}
|
||||||
|
/* Modal dialog — fade + scale-in entrance. */
|
||||||
|
.fc-modal {
|
||||||
|
animation: fc-modal-in 0.15s ease-out;
|
||||||
|
}
|
||||||
|
@keyframes fc-modal-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, -50%) scale(0.96);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fc-modal-overlay {
|
||||||
|
animation: fc-overlay-in 0.12s ease-out;
|
||||||
|
}
|
||||||
|
@keyframes fc-overlay-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
/* v4.10.0 — preview-render info card explaining the layout */
|
/* v4.10.0 — preview-render info card explaining the layout */
|
||||||
.fc-preview-render-note {
|
.fc-preview-render-note {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
|
|||||||
@@ -232,21 +232,29 @@ function createCanvas(container, store, opts = {}) {
|
|||||||
// v4.13.0 — bottom-right resize handle on the selected node. A
|
// v4.13.0 — bottom-right resize handle on the selected node. A
|
||||||
// single square handle is enough for v1; multi-handle (4 corners
|
// single square handle is enough for v1; multi-handle (4 corners
|
||||||
// + 4 edges) is a follow-up. Width is the only mutable dimension.
|
// + 4 edges) is a follow-up. Width is the only mutable dimension.
|
||||||
|
// Two rects: a larger transparent hit area (24px) so the handle is
|
||||||
|
// easier to grab, plus a smaller visible grip (10px) styled via CSS.
|
||||||
if (node.id === selectedNodeId) {
|
if (node.id === selectedNodeId) {
|
||||||
const handleSize = 10;
|
const gripSize = 10;
|
||||||
const handle = svgEl('rect', {
|
const hitSize = 24;
|
||||||
x: w - handleSize / 2,
|
const hit = svgEl('rect', {
|
||||||
y: DEFAULT_HEIGHT - handleSize / 2,
|
x: w - hitSize / 2,
|
||||||
width: handleSize,
|
y: DEFAULT_HEIGHT - hitSize / 2,
|
||||||
height: handleSize,
|
width: hitSize,
|
||||||
fill: '#e5461f',
|
height: hitSize,
|
||||||
stroke: '#ffffff',
|
class: 'flowchart-resize-handle flowchart-resize-handle-hit',
|
||||||
'stroke-width': 1,
|
|
||||||
class: 'flowchart-resize-handle',
|
|
||||||
'data-resize-node': node.id,
|
'data-resize-node': node.id,
|
||||||
cursor: 'nwse-resize',
|
|
||||||
});
|
});
|
||||||
g.appendChild(handle);
|
const grip = svgEl('rect', {
|
||||||
|
x: w - gripSize / 2,
|
||||||
|
y: DEFAULT_HEIGHT - gripSize / 2,
|
||||||
|
width: gripSize,
|
||||||
|
height: gripSize,
|
||||||
|
rx: 2,
|
||||||
|
class: 'flowchart-resize-handle flowchart-resize-handle-grip',
|
||||||
|
});
|
||||||
|
g.appendChild(hit);
|
||||||
|
g.appendChild(grip);
|
||||||
}
|
}
|
||||||
|
|
||||||
nodesLayer.appendChild(g);
|
nodesLayer.appendChild(g);
|
||||||
|
|||||||
@@ -91,11 +91,7 @@ describe('flowchart-align: distributeHorizontally', () => {
|
|||||||
expect(out.map((node) => node.x)).toEqual([0, 300, 600, 900]);
|
expect(out.map((node) => node.x)).toEqual([0, 300, 600, 900]);
|
||||||
});
|
});
|
||||||
test('handles unsorted input by sorting first', () => {
|
test('handles unsorted input by sorting first', () => {
|
||||||
const out = distributeHorizontally([
|
const out = distributeHorizontally([n('a', 500, 0), n('b', 0, 0), n('c', 900, 0)]);
|
||||||
n('a', 500, 0),
|
|
||||||
n('b', 0, 0),
|
|
||||||
n('c', 900, 0),
|
|
||||||
]);
|
|
||||||
// sorted: 0, 500, 900 → gap = 450 → x = [0, 450, 900]
|
// sorted: 0, 500, 900 → gap = 450 → x = [0, 450, 900]
|
||||||
expect(out.map((node) => node.x)).toEqual([0, 450, 900]);
|
expect(out.map((node) => node.x)).toEqual([0, 450, 900]);
|
||||||
});
|
});
|
||||||
@@ -109,12 +105,7 @@ describe('flowchart-align: distributeHorizontally', () => {
|
|||||||
|
|
||||||
describe('flowchart-align: distributeVertically', () => {
|
describe('flowchart-align: distributeVertically', () => {
|
||||||
test('spaces 4 nodes evenly between topmost and bottommost', () => {
|
test('spaces 4 nodes evenly between topmost and bottommost', () => {
|
||||||
const out = distributeVertically([
|
const out = distributeVertically([n('a', 0, 0), n('b', 0, 60), n('c', 0, 300), n('d', 0, 900)]);
|
||||||
n('a', 0, 0),
|
|
||||||
n('b', 0, 60),
|
|
||||||
n('c', 0, 300),
|
|
||||||
n('d', 0, 900),
|
|
||||||
]);
|
|
||||||
// gap = 900 / 3 = 300
|
// gap = 900 / 3 = 300
|
||||||
expect(out.map((node) => node.y)).toEqual([0, 300, 600, 900]);
|
expect(out.map((node) => node.y)).toEqual([0, 300, 600, 900]);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user