Skip to content

Commit

Permalink
fix(carousel): improved tab page navigation (carbon-design-system#5035)
Browse files Browse the repository at this point in the history
### Related Ticket(s)
carbon-design-system#4799

### Description

Carousel now moves pages automatically when using tab navigation and focusing on the next card in a different page. Works going forward or backwards.

### Changelog

**New**

- pages now change dynamically depending on the focused card index

<!-- React and Web Component deploy previews are enabled by default. -->
<!-- To enable additional available deploy previews, apply the following -->
<!-- labels for the corresponding package: -->
<!-- *** "package: services": Services -->
<!-- *** "package: utilities": Utilities -->
<!-- *** "package: styles": Carbon Expressive -->
<!-- *** "RTL": React / Web Components (RTL) -->
<!-- *** "feature flag": React / Web Components (experimental) -->
  • Loading branch information
IgnacioBecerra committed Feb 22, 2021
1 parent 40e19bc commit ca1ac11
Showing 1 changed file with 55 additions and 1 deletion.
56 changes: 55 additions & 1 deletion packages/web-components/src/components/carousel/carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import ddsSettings from '@carbon/ibmdotcom-utilities/es/utilities/settings/setti
import ifNonNull from 'carbon-web-components/es/globals/directives/if-non-null.js';
import CaretLeft20 from 'carbon-web-components/es/icons/caret--left/20.js';
import CaretRight20 from 'carbon-web-components/es/icons/caret--right/20.js';
import HostListener from 'carbon-web-components/es/globals/decorators/host-listener';
import HostListenerMixin from 'carbon-web-components/es/globals/mixins/host-listener';
import DDSCard from '../card/card';
import styles from './carousel.scss';

const { prefix } = settings;
Expand All @@ -26,7 +29,7 @@ const { stablePrefix: ddsPrefix } = ddsSettings;
* @csspart next-button The button to go to the next page.
*/
@customElement(`${ddsPrefix}-carousel`)
class DDSCarousel extends LitElement {
class DDSCarousel extends HostListenerMixin(LitElement) {
/**
* The scrolling contents node.
*/
Expand Down Expand Up @@ -111,6 +114,55 @@ class DDSCarousel extends LitElement {
}
}

/**
* Stops the container from scrolling when focusing on a card outside of the viewport.
*
* @param event The event.
*/
// eslint-disable-next-line class-methods-use-this
private _handleScrollFocus({ target }: Event) {
(target as HTMLElement).scrollTo(0, 0);
}

/**
* Handles card focus throughout pages.
*
* @param event The event.
*/
@HostListener('shadowRoot:focusin')
// @ts-ignore: The decorator refers to this method but TS thinks this method is not referred to
private _handleFocus = async ({ target, relatedTarget }: FocusEvent) => {
const currentContains = target !== this && this.contains(target as DDSCard);
const oldNotContains = target !== this && !this.contains(relatedTarget as DDSCard);
const currentCardIndex = Array.from(this.children).indexOf(target as HTMLElement);

// Confirmed by design team; ensures the carousel remains on the current page when focusing back on the component.
// This conforms to the natural flow of the current content on the screen.
// The first card on the page should be focused if tabbing from the top,
// The last card on the page should be focused if tabbing from the bottom.
if (currentContains && oldNotContains) {
// focus coming from the top
if (currentCardIndex === 0) {
(this.children[this.start] as HTMLElement).focus();

// focus coming from bottom
} else if (currentCardIndex === this._total - 1) {
(this.children[this.start + this.pageSize - 1] as HTMLElement).focus();
}
return;
}

// Calculates proper page to display if focus is outside the current page
if (currentContains && (currentCardIndex < this.start || currentCardIndex >= this.start + this.pageSize)) {
// The `currentIndex` floored by `pageSize`
const nextStart = Math.floor(currentCardIndex / this.pageSize) * this.pageSize;
const pageOffset = this.start % this.pageSize;

// Ensures the page moves by `this.pageSize` in either direction
this.start = nextStart + pageOffset;
}
};

/**
* Handles `click` event on the next button.
*/
Expand Down Expand Up @@ -236,6 +288,7 @@ class DDSCarousel extends LitElement {
_total: total,
_handleClickNextButton: handleClickNextButton,
_handleClickPrevButton: handleClickPrevButton,
_handleScrollFocus: handleScrollFocus,
_handleSlotChange: handleSlotChange,
} = this;
// Copes with the condition where `start % pageSize` is non-zero
Expand All @@ -245,6 +298,7 @@ class DDSCarousel extends LitElement {
return html`
<div
class="${prefix}--carousel__scroll-container"
@scroll="${handleScrollFocus}"
style="${ifNonNull(pageSizeExplicit == null ? null : `${customPropertyPageSize}: ${pageSizeExplicit}`)}"
>
<div class="${prefix}--carousel__scroll-contents" style="left:${(-start * (contentsBaseWidth + gap)) / pageSize}px">
Expand Down

0 comments on commit ca1ac11

Please sign in to comment.