Skip to content

Commit 63d626a

Browse files
committed
[AGILE-361] Bound row resolution to its own list
Selection resolved rows through the unbounded helper, which descends into nested lists: a section row answered with the first field of the list inside it. The rows container now bounds every lookup.
1 parent a1d23de commit 63d626a

3 files changed

Lines changed: 12 additions & 5 deletions

File tree

frontend/src/stimulus/controllers/dynamic/sortable-lists.controller.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ describe('Sortable lists controller', () => {
321321
fieldList,
322322
firstFieldItem: fieldList.querySelector<HTMLElement>('[data-sortable-lists--item-id-value="cf1"]')!,
323323
};
324+
}
325+
324326
// Layered on renderFixture: same list/item/scrollable structure, plus the
325327
// selection-enabled value on the root and tabindex on every item. The
326328
// value is set at creation time, not via a later setAttribute — Stimulus
@@ -1276,6 +1278,8 @@ describe('Sortable lists controller', () => {
12761278
expect.objectContaining({ method: 'PUT' }),
12771279
);
12781280
});
1281+
});
1282+
12791283
// "Collapse a wider selection onto the dragged card" and "select the
12801284
// dragged card" are different things; only the first is in scope. With
12811285
// nothing selected, a drag must leave the selection empty rather than

frontend/src/stimulus/controllers/dynamic/sortable-lists/item.controller.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,7 @@ describe('Sortable lists item controller', () => {
13441344
moveInDirection: vi.fn(),
13451345
moveAvailability: vi.fn(() => null),
13461346
ownerListElementOf: vi.fn(() => null),
1347+
ownerRowsContainer: vi.fn(() => null),
13471348
collapseSelectionForDrag,
13481349
};
13491350

frontend/src/stimulus/controllers/dynamic/sortable-lists/selection.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,11 @@ function listRowsContainer(list:HTMLElement):HTMLElement {
8787
}
8888

8989
// The id of the item a row holds, whether the row is the item element or
90-
// merely contains it.
91-
function rowItemId(row:Element):string|null {
92-
const item = resolveItemElement(row);
90+
// merely contains it. Bounded by the rows container: resolveItemElement's
91+
// querySelector fallback descends unbounded, so a section row would otherwise
92+
// resolve to the first field of the list nested inside it.
93+
function rowItemId(row:Element, rowsContainer:Element):string|null {
94+
const item = resolveItemElement(row, rowsContainer);
9395

9496
return item ? resolveItemId(item) : null;
9597
}
@@ -189,7 +191,7 @@ export function resolveRangeIds(
189191

190192
const rowsContainer = listRowsContainer(list);
191193
const rows = Array.from(rowsContainer.children);
192-
const anchorRow = rows.find((row) => rowItemId(row) === anchor.id);
194+
const anchorRow = rows.find((row) => rowItemId(row, rowsContainer) === anchor.id);
193195
// Meaningful only because rowsContainer came from the list rather than from
194196
// the candidate's own parent: a candidate whose item sits outside the rows
195197
// container (nested in some other part of the list) has no row here.
@@ -204,7 +206,7 @@ export function resolveRangeIds(
204206

205207
const ids:string[] = [];
206208
for (const row of span) {
207-
const item = resolveItemElement(row);
209+
const item = resolveItemElement(row, rowsContainer);
208210
const id = item ? resolveItemId(item) : null;
209211
// A structural row inside the span (a truncation marker) is a hard
210212
// boundary, and so is a card the user cannot move — but the two are not

0 commit comments

Comments
 (0)