mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-10-01 09:19:34 +05:30
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
113 lines
4.0 KiB
JavaScript
113 lines
4.0 KiB
JavaScript
/**
|
|
* @jest-environment node
|
|
*
|
|
* Pure tests for the alignment + distribution helpers.
|
|
* Each function takes nodes and returns a new array with positions
|
|
* adjusted — no store interaction, no DOM.
|
|
*/
|
|
const {
|
|
alignLeft,
|
|
alignRight,
|
|
alignTop,
|
|
alignBottom,
|
|
alignCenterHorizontal,
|
|
alignCenterVertical,
|
|
distributeHorizontally,
|
|
distributeVertically,
|
|
} = require('../src/flowchart/flowchart-align');
|
|
|
|
const n = (id, x, y, w = 120) => ({ id, x, y, width: w, label: id });
|
|
|
|
describe('flowchart-align: alignLeft', () => {
|
|
test('aligns all nodes to the leftmost x', () => {
|
|
const out = alignLeft([n('a', 100, 50), n('b', 200, 80), n('c', 300, 30)]);
|
|
expect(out.map((node) => node.x)).toEqual([100, 100, 100]);
|
|
});
|
|
test('preserves y positions and other fields', () => {
|
|
const out = alignLeft([n('a', 100, 50), n('b', 200, 80)]);
|
|
expect(out[0]).toMatchObject({ id: 'a', x: 100, y: 50 });
|
|
expect(out[1]).toMatchObject({ id: 'b', x: 100, y: 80 });
|
|
});
|
|
test('no-op for 0 or 1 nodes', () => {
|
|
expect(alignLeft([])).toEqual([]);
|
|
expect(alignLeft([n('a', 100, 50)])).toEqual([n('a', 100, 50)]);
|
|
});
|
|
test('returns a new array — does not mutate inputs', () => {
|
|
const inputs = [n('a', 100, 50), n('b', 200, 80)];
|
|
const snapshot = JSON.parse(JSON.stringify(inputs));
|
|
alignLeft(inputs);
|
|
expect(inputs).toEqual(snapshot);
|
|
});
|
|
});
|
|
|
|
describe('flowchart-align: alignRight', () => {
|
|
test('aligns all nodes to the rightmost (x + width)', () => {
|
|
const out = alignRight([n('a', 100, 50), n('b', 200, 80), n('c', 250, 30, 200)]);
|
|
// maxRight = 250 + 200 = 450
|
|
expect(out.map((node) => node.x + node.width)).toEqual([450, 450, 450]);
|
|
});
|
|
test('no-op for single node', () => {
|
|
expect(alignRight([n('a', 100, 50)])).toEqual([n('a', 100, 50)]);
|
|
});
|
|
});
|
|
|
|
describe('flowchart-align: alignTop / alignBottom', () => {
|
|
test('alignTop pins to the topmost y', () => {
|
|
const out = alignTop([n('a', 50, 100), n('b', 80, 200), n('c', 30, 50)]);
|
|
expect(out.map((node) => node.y)).toEqual([50, 50, 50]);
|
|
});
|
|
test('alignBottom pins to the bottommost y + 60', () => {
|
|
const out = alignBottom([n('a', 50, 100), n('b', 80, 200), n('c', 30, 150)]);
|
|
expect(out.map((node) => node.y)).toEqual([200, 200, 200]);
|
|
});
|
|
});
|
|
|
|
describe('flowchart-align: alignCenterHorizontal / alignCenterVertical', () => {
|
|
test('alignCenterHorizontal averages centers', () => {
|
|
const out = alignCenterHorizontal([
|
|
n('a', 0, 0, 100),
|
|
n('b', 100, 0, 100),
|
|
n('c', 200, 0, 100),
|
|
]);
|
|
// centers: 50, 150, 250 → avg = 150
|
|
expect(out.map((node) => node.x + node.width / 2)).toEqual([150, 150, 150]);
|
|
});
|
|
test('alignCenterVertical averages centers', () => {
|
|
const out = alignCenterVertical([n('a', 0, 0), n('b', 0, 60), n('c', 0, 120)]);
|
|
// centers: 30, 90, 150 → avg = 90
|
|
expect(out.map((node) => node.y + 30)).toEqual([90, 90, 90]);
|
|
});
|
|
});
|
|
|
|
describe('flowchart-align: distributeHorizontally', () => {
|
|
test('spaces 4 nodes evenly between leftmost and rightmost', () => {
|
|
const out = distributeHorizontally([
|
|
n('a', 0, 0),
|
|
n('b', 100, 0),
|
|
n('c', 500, 0),
|
|
n('d', 900, 0),
|
|
]);
|
|
// gap = (900 - 0) / 3 = 300
|
|
expect(out.map((node) => node.x)).toEqual([0, 300, 600, 900]);
|
|
});
|
|
test('handles unsorted input by sorting first', () => {
|
|
const out = distributeHorizontally([n('a', 500, 0), n('b', 0, 0), n('c', 900, 0)]);
|
|
// sorted: 0, 500, 900 → gap = 450 → x = [0, 450, 900]
|
|
expect(out.map((node) => node.x)).toEqual([0, 450, 900]);
|
|
});
|
|
test('no-op for fewer than 3 nodes', () => {
|
|
expect(distributeHorizontally([n('a', 0, 0), n('b', 100, 0)])).toEqual([
|
|
n('a', 0, 0),
|
|
n('b', 100, 0),
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('flowchart-align: distributeVertically', () => {
|
|
test('spaces 4 nodes evenly between topmost and bottommost', () => {
|
|
const out = distributeVertically([n('a', 0, 0), n('b', 0, 60), n('c', 0, 300), n('d', 0, 900)]);
|
|
// gap = 900 / 3 = 300
|
|
expect(out.map((node) => node.y)).toEqual([0, 300, 600, 900]);
|
|
});
|
|
});
|