Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: scroll focused dashboard widget into view #7875

Merged
merged 1 commit into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions packages/dashboard/src/vaadin-dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,30 @@ class Dashboard extends ControllerMixin(DashboardLayoutMixin(ElementMixin(Themab
// Remove the unused wrappers
wrappers.forEach((wrapper) => wrapper.remove());

if (focusedWrapperWillBeRemoved) {
// The wrapper containing the focused element was removed. Try to focus the element in the closest wrapper.
requestAnimationFrame(() =>
this.__focusWrapperContent(wrapperClosestToRemovedFocused || this.querySelector(WRAPPER_LOCAL_NAME)),
);
}
requestAnimationFrame(() => {
if (focusedWrapperWillBeRemoved) {
// The wrapper containing the focused element was removed. Try to focus the element in the closest wrapper.
this.__focusWrapperContent(wrapperClosestToRemovedFocused || this.querySelector(WRAPPER_LOCAL_NAME));
}

const focusedItem = this.querySelector('[focused]');
if (focusedItem && !this.__insideViewport(focusedItem)) {
// If the focused wrapper is not in the viewport, scroll it into view
focusedItem.scrollIntoView();
}
});
}

/** @private */
__insideViewport(element) {
const rect = element.getBoundingClientRect();
const dashboardRect = this.getBoundingClientRect();
return (
rect.bottom >= dashboardRect.top &&
rect.right >= dashboardRect.left &&
rect.top <= dashboardRect.bottom &&
rect.left <= dashboardRect.right
);
}

/** @private */
Expand Down
46 changes: 46 additions & 0 deletions packages/dashboard/test/dashboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getParentSection,
getRemoveButton,
getResizeHandle,
getScrollingContainer,
onceResized,
setGap,
setMaximumColumnWidth,
Expand Down Expand Up @@ -569,6 +570,51 @@ describe('dashboard', () => {
expect(removeButton.getBoundingClientRect().height).to.be.above(0);
});

it('should scroll the focused item into view on render', async () => {
// Limit the dashboard height to force scrolling
dashboard.style.height = '300px';
await onceResized(dashboard);
// Focus the first item
getElementFromCell(dashboard, 0, 0)!.focus();

// Add enough items to push the focused item out of view
dashboard.items = Array.from({ length: 10 }, (_, i) => ({ id: i.toString() })).reverse();
await nextFrame();
await nextFrame();

// Expect the focused item to have been scrolled back into view
const widgetRect = document.activeElement!.getBoundingClientRect();
const dashboardRect = dashboard.getBoundingClientRect();
expect(widgetRect.bottom).to.be.above(dashboardRect.top);
expect(widgetRect.top).to.be.below(dashboardRect.bottom);
});

it('should not scroll the focused item into view if it is partially visible', async () => {
// Limit the dashboard height to force scrolling
dashboard.style.height = '300px';
await onceResized(dashboard);
// Focus the first item
getElementFromCell(dashboard, 0, 0)!.focus();

// Add enough items to make the dashboard scrollable
dashboard.items = Array.from({ length: 10 }, (_, i) => ({ id: i.toString() }));
await nextFrame();
await nextFrame();

// Scroll the dashboard to make the focused item partially visible
const scrollingContainer = getScrollingContainer(dashboard);
const scrollTop = Math.round(document.activeElement!.getBoundingClientRect().height / 2);
scrollingContainer.scrollTop = scrollTop;

// Change the items to trigger a render
dashboard.items = dashboard.items.slice(0, -1);
await nextFrame();
await nextFrame();

// Expect no scrolling to have occurred
expect(scrollingContainer.scrollTop).to.equal(scrollTop);
});

describe('focus restore on focused item removal', () => {
beforeEach(async () => {
dashboard.editable = true;
Expand Down
Loading