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

fix(tabs): prevent vertical auto scroll #4592

Closed
wants to merge 12 commits into from
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ executors:
parameters:
current_golden_images_hash:
type: string
default: 019c0496b4ab51e1da329cae280244c60500cb86
default: e7fc14cdcd7c7fa2b5bee0ad6cfc51a6302d6b52
wireit_cache_name:
type: string
default: wireit
Expand Down
74 changes: 72 additions & 2 deletions packages/tabs/src/Tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,38 @@ export const ScaledIndicator = {
},
};

/**
* Given that the scroll needs to be on the right side of the viewport.
* Returns the coordonate x it needs to scroll so that the tab with given index is visible.
*/
export function calculateScrollTargetForRightSide(
index: number,
direction: 'rtl' | 'ltr',
tabs: Tab[],
container: HTMLDivElement
): number {
const nextIndex = index + (direction === 'rtl' ? -1 : 1);
const nextTab = tabs[nextIndex];
const viewportEnd = container.scrollLeft + container.offsetWidth;
return nextTab ? nextTab.offsetLeft - container.offsetWidth : viewportEnd;
}

/**
* Given that the scroll needs to be on the left side of the viewport.
* Returns the coordonate x it needs to scroll so that the tab with given index is visible.
*/
export function calculateScrollTargetForLeftSide(
index: number,
direction: 'rtl' | 'ltr',
tabs: Tab[],
container: HTMLDivElement
): number {
const prevIndex = index + (direction === 'rtl' ? 1 : -1);
const prevTab = tabs[prevIndex];
const leftmostElement = direction === 'rtl' ? -container.offsetWidth : 0;
return prevTab ? prevTab.offsetLeft + prevTab.offsetWidth : leftmostElement;
}

/**
* @element sp-tabs
*
Expand Down Expand Up @@ -245,16 +277,54 @@ export class Tabs extends SizedMixin(Focusable, { noDefaultSize: true }) {
return complete;
}

private getNecessaryAutoScroll(index: number): number {
const selectedTab = this.tabs[index];
const selectionEnd = selectedTab.offsetLeft + selectedTab.offsetWidth;
const viewportEnd = this.tabList.scrollLeft + this.tabList.offsetWidth;
const selectionStart = selectedTab.offsetLeft;
const viewportStart = this.tabList.scrollLeft;

if (selectionEnd > viewportEnd) {
// Selection is on the right side, not visible.
return calculateScrollTargetForRightSide(
index,
this.dir,
this.tabs,
this.tabList
);
} else if (selectionStart < viewportStart) {
// Selection is on the left side, not visible.
return calculateScrollTargetForLeftSide(
index,
this.dir,
this.tabs,
this.tabList
);
}

return -1;
}

public async scrollToSelection(): Promise<void> {
if (!this.enableTabsScroll || !this.selected) {
return;
}

await this.updateComplete;
Rajdeepc marked this conversation as resolved.
Show resolved Hide resolved
const selectedTab = this.tabs.find(

const selectedIndex = this.tabs.findIndex(
(tab) => tab.value === this.selected
);
selectedTab?.scrollIntoView();

if (selectedIndex !== -1 && this.tabList) {
// We have a selection, calculate the scroll needed to bring it into view
const scrollTarget = this.getNecessaryAutoScroll(selectedIndex);

// scrollTarget = -1 means it is already into view.
if (scrollTarget !== -1) {
this.tabList.scrollTo({ left: scrollTarget });
}
}
}

protected override updated(
Expand Down
23 changes: 22 additions & 1 deletion packages/tabs/stories/tabs-overflow.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
import { TemplateResult } from '@spectrum-web-components/base';
import { html, TemplateResult } from '@spectrum-web-components/base';
import { OverflowProperties, renderTabsOverflowExample } from './index.js';

export default {
Expand All @@ -30,3 +30,24 @@ export const autoscroll = (args: OverflowProperties): TemplateResult => {
autoscroll.args = {
selected: 15,
};

// https://github.com/adobe/spectrum-web-components/issues/4590
export const autoscrollOnlyHorizontally = (
args: OverflowProperties
): TemplateResult => {
return html`
<style>
.container {
height: 500px;
overflow-y: scroll;
}
</style>
<div class="container">
<div style="height: 500px">There are some tabs down here!</div>
${renderTabsOverflowExample(args)}
</div>
`;
};
autoscrollOnlyHorizontally.args = {
selected: 15,
};
Loading
Loading