Variables of a behavior, the fields of a struct, the ports of a start or end node and the items of a list could only be added and removed. Changing their order meant removing and adding them again. Now each of them is dragged by the mark in front of its row. - sortBlocks (ui/blockDrag.ts) makes the rows of one column sortable. A block is every row it covers, so a struct moves with its fields and a list item with its nested list. The block is dimmed while it is carried, an accent line shows where it lands, and a scrolled panel follows the pointer at its edge. Less than 4 px stays a click. - The arithmetic is pure and lives in ui/sortable.ts: blockGap, gapLineY, dropSlot and edgeScroll. dropIndex of the tile strip moved there too, so the reorder maths sits in one file. - MoveVariableCommand, MoveStructFieldCommand and MovePortCommand build on the existing MoveCommand. Undo moves back. - Lists need no command. InlineList replaces the list as a whole, as it does for every other edit of its items. - The grip is the kind mark, for a list item its number. Moving only happens among siblings: a field stays in its struct. - On a Struct asset the move runs through WithStructTypes. Typed variables, Pack/Expand ports and split outputs follow the new order. - Values and edges hang on names, so only the order changes. Nodes running a behavior read their ports from the start node, the new order reaches them without a copy. - Not sortable by design: the variables of a placed object, the fields of a typed struct (moved on the Struct asset) and the picked ports of Variables/Properties nodes. - Test hook __gm.inspectorMark(label) finds the mark of a row. Tests: - Gate on melektaus-atx: 235 files, 3572 tests, CHECK EXIT 0 (+9). - tests/core/reorder.test.ts: a variable, a field two levels deep, a Struct asset reaching a typed variable and Pack, and start ports reaching the calling node with edges and result kept. - e2e 22: ALL PASSED, 39 checks (+4, sum 1739). 22f-iii drags a variable past a struct of several rows, then a field inside it. - Counter-probes: dropSlot by top edge instead of middle and a field move that ignores its path fail 3 unit tests. With sortBlocks switched off, the three order checks of 22f-iii fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
33 lines
1.5 KiB
TypeScript
33 lines
1.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { dropIndex } from '../../src/ui/sortable';
|
||
|
||
/**
|
||
* Reordering the pictures of a tile by dragging a row (#118). The index the move command
|
||
* takes counts in the list without the dragged row, which is where the off-by-ones live.
|
||
*/
|
||
describe('dropping a picture row', () => {
|
||
it('takes the place of the row it is dropped on', () => {
|
||
expect(dropIndex(0, { at: 'onto', targetId: '1' }, 3)).toBe(1);
|
||
expect(dropIndex(2, { at: 'onto', targetId: '0' }, 3)).toBe(0);
|
||
// Dropping a row on itself is nothing to do – which is also what the list reports as no spot.
|
||
expect(dropIndex(1, { at: 'onto', targetId: '1' }, 3)).toBe(1);
|
||
});
|
||
|
||
it('counts a gap in the list the row has already left', () => {
|
||
// Downwards: the gap below row 1 is index 1 once row 0 is out of the way.
|
||
expect(dropIndex(0, { at: 'after', targetId: '1' }, 3)).toBe(1);
|
||
expect(dropIndex(0, { at: 'before', targetId: '2' }, 3)).toBe(1);
|
||
// Upwards nothing shifts: the gap above row 0 stays index 0.
|
||
expect(dropIndex(2, { at: 'before', targetId: '0' }, 3)).toBe(0);
|
||
expect(dropIndex(2, { at: 'after', targetId: '0' }, 3)).toBe(1);
|
||
expect(dropIndex(1, { at: 'end' }, 3)).toBe(2);
|
||
});
|
||
|
||
it('refuses a row or a target that is not in the list', () => {
|
||
expect(dropIndex(-1, { at: 'end' }, 3)).toBe(-1);
|
||
expect(dropIndex(3, { at: 'end' }, 3)).toBe(-1);
|
||
expect(dropIndex(0, { at: 'onto', targetId: '9' }, 3)).toBe(-1);
|
||
expect(dropIndex(0, { at: 'before', targetId: 'nonsense' }, 3)).toBe(-1);
|
||
});
|
||
});
|