Skip to content

Commit fefd512

Browse files
authored
fix: do not auto-select first element in detailed list (#400)
* fix: do not auto-select first element in detailed list * test: update screenshots * fix: add early returns to improve readability
1 parent 8fb51b9 commit fefd512

File tree

24 files changed

+67
-29
lines changed

24 files changed

+67
-29
lines changed

src/__test__/components/detailed-list/detailed-list.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@ describe('DetailedListWrapper Component', () => {
574574
it('should get target element', () => {
575575
detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList });
576576
document.body.appendChild(detailedListWrapper.render);
577+
detailedListWrapper.changeTarget('down', false, true);
577578

578579
const targetElement = detailedListWrapper.getTargetElement();
579580
expect(targetElement).toBeDefined();
@@ -615,6 +616,9 @@ describe('DetailedListWrapper Component', () => {
615616
detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList });
616617
document.body.appendChild(detailedListWrapper.render);
617618

619+
// First select the first item
620+
detailedListWrapper.changeTarget('down');
621+
618622
// Go up from first item (should wrap to last)
619623
detailedListWrapper.changeTarget('up', false);
620624
const targetElement = detailedListWrapper.getTargetElement();
@@ -625,7 +629,9 @@ describe('DetailedListWrapper Component', () => {
625629
detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList });
626630
document.body.appendChild(detailedListWrapper.render);
627631

628-
// Move to last item first
632+
// First select an item
633+
detailedListWrapper.changeTarget('down');
634+
// Move to last item
629635
detailedListWrapper.changeTarget('down');
630636
// Then go down (should wrap to first)
631637
detailedListWrapper.changeTarget('down', false);
@@ -637,6 +643,9 @@ describe('DetailedListWrapper Component', () => {
637643
detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList });
638644
document.body.appendChild(detailedListWrapper.render);
639645

646+
// First select the first item
647+
detailedListWrapper.changeTarget('down');
648+
640649
// Go up with snap (should stay at first)
641650
detailedListWrapper.changeTarget('up', true);
642651
let targetElement = detailedListWrapper.getTargetElement();

src/components/detailed-list/detailed-list-item.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface DetailedListItemWrapperProps {
1717
onSelect?: (detailedListItem: DetailedListItem) => void;
1818
onClick?: (detailedListItem: DetailedListItem) => void;
1919
onActionClick?: (action: ChatItemButton, detailedListItem?: DetailedListItem) => void;
20+
onShowActionMenuOverlay?: () => void;
2021
selectable?: boolean;
2122
clickable?: boolean;
2223
textDirection?: 'row' | 'column';
@@ -201,6 +202,7 @@ export class DetailedListItemWrapper {
201202
};
202203

203204
private readonly showActionMenuOverlay = (listItem?: DetailedListItem): void => {
205+
this.props.onShowActionMenuOverlay?.();
204206
this.actionMenuOverlay = new Overlay({
205207
background: true,
206208
closeOnOutsideClick: true,

src/components/detailed-list/detailed-list.ts

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class DetailedListWrapper {
3737
element: ExtendedHTMLElement;
3838
}> = [];
3939

40-
private activeTargetElementIndex: number = 0;
40+
private activeTargetElementIndex: number = -1;
4141
private allSelectableDetailedListElements: DetailedListItemWrapper[] = [];
4242
constructor (props: DetailedListWrapperProps) {
4343
this.props = props;
@@ -221,7 +221,6 @@ export class DetailedListWrapper {
221221
]
222222
});
223223
});
224-
this.allSelectableDetailedListElements[0]?.setFocus(true, true);
225224
return groups ?? [ '' ];
226225
};
227226

@@ -231,6 +230,7 @@ export class DetailedListWrapper {
231230
listItem: detailedListItem,
232231
onSelect: this.props.onItemSelect,
233232
onClick: this.props.onItemClick,
233+
onShowActionMenuOverlay: () => { this.setFocus(detailedListItem); },
234234
onActionClick: this.props.onItemActionClick,
235235
selectable: this.props.detailedList.selectable !== false && this.props.detailedList.selectable !== 'clickable',
236236
clickable: this.props.detailedList.selectable === 'clickable',
@@ -244,38 +244,64 @@ export class DetailedListWrapper {
244244
});
245245
};
246246

247-
public readonly changeTarget = (direction: 'up' | 'down', snapOnLastAndFirst?: boolean, scrollIntoView?: boolean): void => {
248-
if (this.allSelectableDetailedListElements.length > 0) {
249-
let nextElementIndex: number = this.activeTargetElementIndex;
250-
if (direction === 'up') {
251-
if (nextElementIndex > 0) {
252-
nextElementIndex--;
253-
} else if (snapOnLastAndFirst !== true) {
254-
nextElementIndex = this.allSelectableDetailedListElements.length - 1;
255-
} else {
256-
nextElementIndex = 0;
257-
}
258-
} else {
259-
if (nextElementIndex < this.allSelectableDetailedListElements.length - 1) {
260-
nextElementIndex++;
261-
} else if (snapOnLastAndFirst !== true) {
262-
nextElementIndex = 0;
263-
} else {
264-
nextElementIndex = this.allSelectableDetailedListElements.length - 1;
265-
}
266-
}
247+
public readonly changeTarget = (
248+
direction: 'up' | 'down',
249+
snapOnLastAndFirst?: boolean,
250+
scrollIntoView?: boolean
251+
): void => {
252+
if (this.allSelectableDetailedListElements.length === 0) return;
253+
254+
const lastIndex = this.allSelectableDetailedListElements.length - 1;
255+
let nextElementIndex = this.activeTargetElementIndex;
256+
257+
// Handle initial selection when no item is selected
258+
if (nextElementIndex === -1) {
259+
nextElementIndex = direction === 'up' ? lastIndex : 0;
260+
this.setFocusByIndex(nextElementIndex, scrollIntoView);
261+
return;
262+
}
263+
264+
// Calculate next index based on direction
265+
if (direction === 'up') {
266+
nextElementIndex = nextElementIndex > 0
267+
? nextElementIndex - 1
268+
: (snapOnLastAndFirst === true ? 0 : lastIndex);
269+
} else {
270+
nextElementIndex = nextElementIndex < lastIndex
271+
? nextElementIndex + 1
272+
: (snapOnLastAndFirst === true ? lastIndex : 0);
273+
}
274+
275+
this.setFocusByIndex(nextElementIndex, scrollIntoView);
276+
};
277+
278+
private readonly setFocus = (detailedListItem: DetailedListItem): void => {
279+
// Only remove focus from current item if one is selected
280+
if (this.activeTargetElementIndex >= 0) {
281+
this.allSelectableDetailedListElements[this.activeTargetElementIndex].setFocus(false, false);
282+
}
283+
const selectedItemIndex = this.allSelectableDetailedListElements.findIndex(item => item.getItem().id === detailedListItem.id);
267284

285+
this.activeTargetElementIndex = selectedItemIndex;
286+
this.allSelectableDetailedListElements[this.activeTargetElementIndex].setFocus(true, false);
287+
};
288+
289+
private readonly setFocusByIndex = (index: number, scrollIntoView?: boolean): void => {
290+
// Only remove focus from current item if one is selected
291+
if (this.activeTargetElementIndex >= 0) {
268292
this.allSelectableDetailedListElements[this.activeTargetElementIndex].setFocus(false, scrollIntoView === true);
269-
this.activeTargetElementIndex = nextElementIndex;
270-
this.allSelectableDetailedListElements[this.activeTargetElementIndex].setFocus(true, scrollIntoView === true);
271293
}
294+
295+
this.activeTargetElementIndex = index;
296+
this.allSelectableDetailedListElements[this.activeTargetElementIndex].setFocus(true, scrollIntoView === true);
272297
};
273298

274299
public readonly getTargetElement = (): DetailedListItem | null => {
275-
if (this.allSelectableDetailedListElements.length > 0) {
276-
return this.allSelectableDetailedListElements[Math.max(this.activeTargetElementIndex, 0)].getItem();
300+
if (this.allSelectableDetailedListElements.length === 0 || this.activeTargetElementIndex < 0) {
301+
return null;
277302
}
278-
return null;
303+
304+
return this.allSelectableDetailedListElements[this.activeTargetElementIndex].getItem();
279305
};
280306

281307
public readonly update = (detailedList: DetailedList, preserveScrollPosition?: boolean): void => {
@@ -310,7 +336,7 @@ export class DetailedListWrapper {
310336
// Clear and recreate the list structure
311337
this.detailedListItemsBlockData = [];
312338
this.detailedListItemGroupsContainer.clear();
313-
this.activeTargetElementIndex = 0;
339+
this.activeTargetElementIndex = -1;
314340
this.allSelectableDetailedListElements = [];
315341
this.props.detailedList.list = detailedList.list;
316342

Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)