From 36b084cdb2ebf7271ad5d360a173e4c5d230da46 Mon Sep 17 00:00:00 2001 From: Navneet Date: Sun, 25 Jan 2026 14:11:52 +0530 Subject: [PATCH 1/2] Add keyboard scrolling support to member list --- css/80_app.css | 7 +++ modules/ui/sections/raw_member_editor.js | 45 ++++++++++++++++++++ modules/ui/sections/raw_membership_editor.js | 45 ++++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/css/80_app.css b/css/80_app.css index 13f256df46f..dd3c7bb3968 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2877,6 +2877,13 @@ img.tag-reference-wiki-image { .section-raw-membership-editor .member-list { position: relative; /* required for drag-and-drop */ padding-top: 5px; + overflow-y: auto; + max-height: 400px; +} +.section-raw-member-editor .member-list:focus, +.section-raw-membership-editor .member-list:focus { + outline: 2px solid #0066ff; + outline-offset: -2px; } .section-raw-member-editor .member-list li, .section-raw-membership-editor .member-list li { diff --git a/modules/ui/sections/raw_member_editor.js b/modules/ui/sections/raw_member_editor.js index 8671760243d..964a8e54743 100644 --- a/modules/ui/sections/raw_member_editor.js +++ b/modules/ui/sections/raw_member_editor.js @@ -144,6 +144,12 @@ export function uiSectionRawMemberEditor(context) { list = list.enter() .append('ul') .attr('class', 'member-list') + .attr('tabindex', '0') + .on('mousedown.memberlist', function() { + this.focus(); + d3_event.stopPropagation(); + }) + .on('keydown.memberlist', handleListKeydown) .merge(list); @@ -392,6 +398,45 @@ export function uiSectionRawMemberEditor(context) { } + function handleListKeydown(d3_event) { + d3_event.preventDefault(); + d3_event.stopImmediatePropagation(); + + // Only handle navigation keys when list is focused + if (!['PageDown', 'PageUp', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(d3_event.key)) { + return; + } + + var listNode = d3_select(this).node(); + if (!listNode) return; + + var listHeight = listNode.clientHeight; + var scrollTop = listNode.scrollTop; + var scrollHeight = listNode.scrollHeight; + + switch(d3_event.key) { + case 'PageDown': + listNode.scrollTop = Math.min(scrollTop + listHeight, scrollHeight - listHeight); + break; + case 'PageUp': + listNode.scrollTop = Math.max(scrollTop - listHeight, 0); + break; + case 'End': + listNode.scrollTop = scrollHeight; + break; + case 'Home': + listNode.scrollTop = 0; + break; + case 'ArrowDown': + listNode.scrollTop = Math.min(scrollTop + 40, scrollHeight - listHeight); + break; + case 'ArrowUp': + listNode.scrollTop = Math.max(scrollTop - 40, 0); + break; + } + } + + function unbind() { var row = d3_select(this); diff --git a/modules/ui/sections/raw_membership_editor.js b/modules/ui/sections/raw_membership_editor.js index 4efda067c26..99de61f251d 100644 --- a/modules/ui/sections/raw_membership_editor.js +++ b/modules/ui/sections/raw_membership_editor.js @@ -365,6 +365,12 @@ export function uiSectionRawMembershipEditor(context) { list = list.enter() .append('ul') .attr('class', 'member-list') + .attr('tabindex', '0') + .on('mousedown.memberlist', function() { + this.focus(); + d3_event.stopPropagation(); + }) + .on('keydown.memberlist', handleListKeydown) .merge(list); @@ -659,6 +665,45 @@ export function uiSectionRawMembershipEditor(context) { } + function handleListKeydown(d3_event) { + d3_event.preventDefault(); + d3_event.stopImmediatePropagation(); + + // Only handle navigation keys when list is focused + if (!['PageDown', 'PageUp', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(d3_event.key)) { + return; + } + + var listNode = d3_select(this).node(); + if (!listNode) return; + + var listHeight = listNode.clientHeight; + var scrollTop = listNode.scrollTop; + var scrollHeight = listNode.scrollHeight; + + switch(d3_event.key) { + case 'PageDown': + listNode.scrollTop = Math.min(scrollTop + listHeight, scrollHeight - listHeight); + break; + case 'PageUp': + listNode.scrollTop = Math.max(scrollTop - listHeight, 0); + break; + case 'End': + listNode.scrollTop = scrollHeight; + break; + case 'Home': + listNode.scrollTop = 0; + break; + case 'ArrowDown': + listNode.scrollTop = Math.min(scrollTop + 40, scrollHeight - listHeight); + break; + case 'ArrowUp': + listNode.scrollTop = Math.max(scrollTop - 40, 0); + break; + } + } + + function unbind() { var row = d3_select(this); From ca14c0531b29868bddc62c212b7eb13fd4403116 Mon Sep 17 00:00:00 2001 From: Navneet Date: Sun, 25 Jan 2026 14:40:34 +0530 Subject: [PATCH 2/2] Fix lint errors in keyboard scrolling handlers --- modules/ui/sections/raw_member_editor.js | 14 +++++++------- modules/ui/sections/raw_membership_editor.js | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/ui/sections/raw_member_editor.js b/modules/ui/sections/raw_member_editor.js index 964a8e54743..34f09014bfe 100644 --- a/modules/ui/sections/raw_member_editor.js +++ b/modules/ui/sections/raw_member_editor.js @@ -145,9 +145,9 @@ export function uiSectionRawMemberEditor(context) { .append('ul') .attr('class', 'member-list') .attr('tabindex', '0') - .on('mousedown.memberlist', function() { + .on('mousedown.memberlist', function(event) { this.focus(); - d3_event.stopPropagation(); + event.stopPropagation(); }) .on('keydown.memberlist', handleListKeydown) .merge(list); @@ -398,12 +398,12 @@ export function uiSectionRawMemberEditor(context) { } - function handleListKeydown(d3_event) { - d3_event.preventDefault(); - d3_event.stopImmediatePropagation(); + function handleListKeydown(event) { + event.preventDefault(); + event.stopImmediatePropagation(); // Only handle navigation keys when list is focused - if (!['PageDown', 'PageUp', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(d3_event.key)) { + if (!['PageDown', 'PageUp', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(event.key)) { return; } @@ -414,7 +414,7 @@ export function uiSectionRawMemberEditor(context) { var scrollTop = listNode.scrollTop; var scrollHeight = listNode.scrollHeight; - switch(d3_event.key) { + switch (event.key) { case 'PageDown': listNode.scrollTop = Math.min(scrollTop + listHeight, scrollHeight - listHeight); break; diff --git a/modules/ui/sections/raw_membership_editor.js b/modules/ui/sections/raw_membership_editor.js index 99de61f251d..a0c211a73e4 100644 --- a/modules/ui/sections/raw_membership_editor.js +++ b/modules/ui/sections/raw_membership_editor.js @@ -366,9 +366,9 @@ export function uiSectionRawMembershipEditor(context) { .append('ul') .attr('class', 'member-list') .attr('tabindex', '0') - .on('mousedown.memberlist', function() { + .on('mousedown.memberlist', function(event) { this.focus(); - d3_event.stopPropagation(); + event.stopPropagation(); }) .on('keydown.memberlist', handleListKeydown) .merge(list); @@ -665,12 +665,12 @@ export function uiSectionRawMembershipEditor(context) { } - function handleListKeydown(d3_event) { - d3_event.preventDefault(); - d3_event.stopImmediatePropagation(); + function handleListKeydown(event) { + event.preventDefault(); + event.stopImmediatePropagation(); // Only handle navigation keys when list is focused - if (!['PageDown', 'PageUp', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(d3_event.key)) { + if (!['PageDown', 'PageUp', 'End', 'Home', 'ArrowDown', 'ArrowUp'].includes(event.key)) { return; } @@ -681,7 +681,7 @@ export function uiSectionRawMembershipEditor(context) { var scrollTop = listNode.scrollTop; var scrollHeight = listNode.scrollHeight; - switch(d3_event.key) { + switch (event.key) { case 'PageDown': listNode.scrollTop = Math.min(scrollTop + listHeight, scrollHeight - listHeight); break;