From ba12f0e8231e0ad7850d05979afc25bde96dfb20 Mon Sep 17 00:00:00 2001 From: Ariella Gilmore Date: Thu, 21 Sep 2023 14:34:33 -0700 Subject: [PATCH 1/3] feat(toc): v2 updates (#10932) * feat(toc): v2 updates * fix(toc): styles * fix(toc): format * chore(toc): migration guides * fix(toc): md/sm styles * feat(toc): format * fix(toc): scroll position and slide * Update packages/styles/scss/components/tableofcontents/_table-of-contents.scss Co-authored-by: Ignacio Becerra * fix(toc): adjust scrolling stop --------- Co-authored-by: Ignacio Becerra --- docs/dotcom-v2-migration.md | 1 + .../tableofcontents/_table-of-contents.scss | 343 ++++++++------- .../styles/scss/globals/_carbon-grid.scss | 7 +- .../src/components/link-list/link-list.scss | 5 + .../table-of-contents.stories.scss | 6 +- .../table-of-contents/table-of-contents.scss | 98 +---- .../table-of-contents/table-of-contents.ts | 395 +++++++----------- 7 files changed, 335 insertions(+), 520 deletions(-) diff --git a/docs/dotcom-v2-migration.md b/docs/dotcom-v2-migration.md index 9164e47e804..3f1af369bfd 100644 --- a/docs/dotcom-v2-migration.md +++ b/docs/dotcom-v2-migration.md @@ -47,6 +47,7 @@ For Carbon v11 migration guidance, see their | pictogram-item | This component is deprecated in v2 in favor for content-item (pictogram variation) componet | | quote | No API changes. | | search-with-typeahead | No API changes. | +| table-of-contents | No API changes. | | tabs-extended-media | This component is deprecated in v2 in favor for tabs-extended component | diff --git a/packages/styles/scss/components/tableofcontents/_table-of-contents.scss b/packages/styles/scss/components/tableofcontents/_table-of-contents.scss index 46474ff5fc9..07d8151cf9e 100644 --- a/packages/styles/scss/components/tableofcontents/_table-of-contents.scss +++ b/packages/styles/scss/components/tableofcontents/_table-of-contents.scss @@ -14,6 +14,7 @@ @use '@carbon/styles/scss/type' as *; @use '@carbon/styles/scss/utilities' as *; @use '../../globals/utils/flex-grid' as *; +@use '../../globals/carbon-grid' as *; @use '../../globals/vars' as *; @use '../../globals/utils/hang' as *; @use '../layout/layout'; @@ -24,31 +25,20 @@ $hover-transition-timing: 95ms; @mixin themed-items { color: $text-primary; background: $background; - .#{$prefix}--tableofcontents__mobile { - border-bottom: 1px solid $toggle-off; - box-shadow: 0 $spacing-01 $spacing-03 rgba(0, 0, 0, 0.2); - - &:hover { - background-color: $background-hover; - } - } - - .#{$prefix}--tableofcontents__desktop__item { + .#{$prefix}--tableofcontents__item { a { - border-left: $spacing-02 solid $layer-accent-01; + border-left: $spacing-02 solid $border-subtle-01; color: $text-secondary; &:hover { - border-left-color: $background-selected-hover; + border-left-color: $layer-selected-hover-01; color: $text-primary; } } } .#{$prefix}--tableofcontents__navbar { - .#{$prefix}--tableofcontents__desktop__item { - margin-bottom: 1px; - + .#{$prefix}--tableofcontents__item { a { white-space: nowrap; border-left: none; @@ -60,16 +50,27 @@ $hover-transition-timing: 95ms; } } - .#{$prefix}--tableofcontents__desktop__item--active a { + .#{$prefix}--tableofcontents__item--active a { color: $text-primary; + + &::before { + content: ''; + position: absolute; + bottom: 0; + height: rem(20px); + width: $spacing-01; + top: rem(14px); + background-color: $border-interactive; + margin-left: rem(-6px); + } } } - .#{$prefix}--tableofcontents__desktop__item--active { + .#{$prefix}--tableofcontents__item--active { a, a:hover { color: $text-primary; - border-left-color: $link-primary; + border-left-color: $border-interactive; } a:focus { @@ -77,17 +78,15 @@ $hover-transition-timing: 95ms; } &:hover { - border-left-color: $link-primary; + border-left-color: $border-interactive; } } } @mixin table-of-contents { - :host(#{$c4d-prefix}-table-of-contents), - .#{$prefix}--tableofcontents { + :host(#{$c4d-prefix}-table-of-contents) { text-size-adjust: 100%; @extend .#{$prefix}--grid; - @include flex-grid(); display: block; margin: 0; @@ -103,81 +102,32 @@ $hover-transition-timing: 95ms; .#{$prefix}--tableofcontents__sidebar { @extend .#{$prefix}--col-lg-4; - position: sticky; - top: 0; + position: inherit; + top: auto; z-index: 10; - padding-left: 0; - - @include breakpoint(lg) { - position: inherit; - top: auto; - } + padding: 0; } .#{$prefix}--tableofcontents__content { @extend .#{$prefix}--col-lg-12; - } - .#{$prefix}--tableofcontents__children__mobile { - @include breakpoint(lg) { - display: none; - } - - margin-top: $spacing-07; - } - - .#{$prefix}--tableofcontents__mobile { - margin-right: -$spacing-05; - background-color: $field-01; - padding-right: $spacing-05; - padding-left: $spacing-05; + padding: $spacing-05 0 $spacing-09 0; } - .#{$prefix}--tableofcontents__mobile__select { - border-radius: 0; - border: none; - background-color: transparent; - color: $text-primary; - height: $spacing-09; - appearance: none; - @include type-style(body-compact-02); - @include focus-outline('reset'); - - max-width: 100%; - min-width: 100%; - text-overflow: ellipsis; - padding-right: $spacing-06; + .#{$prefix}--tableofcontents { display: block; - cursor: pointer; - transition: background-color $duration-fast-01 motion(standard, productive), - outline $duration-fast-01 motion(standard, productive); - } - - .#{$prefix}--tableofcontents__mobile__select__wrapper { - height: rem(47px); - position: relative; - display: flex; - align-items: center; - flex: 1 1 100%; - } - .#{$prefix}--tableofcontents__mobile__select__icon { - position: absolute; - right: 0; - pointer-events: none; - cursor: pointer; - } - - .#{$prefix}--tableofcontents__mobile__select__option { - @include theme($white, true); - } - - .#{$prefix}--tableofcontents__desktop { padding-top: $spacing-07; margin-top: $spacing-07; @include make-col(3, 4); } - .#{$prefix}--tableofcontents__desktop__item { + .#{$prefix}--tableofcontents ul { + list-style: none; + margin: 0; + padding: 0; + } + + .#{$prefix}--tableofcontents__item { a { text-decoration: none; display: inline-block; @@ -199,20 +149,21 @@ $hover-transition-timing: 95ms; } } - .#{$prefix}--tableofcontents__desktop__children { - padding-top: $spacing-10; - margin-bottom: -$spacing-07; - - @include make-col(3, 4); + .#{$prefix}--tableofcontents__children { + @include breakpoint(lg) { + padding-top: $spacing-10; + padding-left: 0; + margin-bottom: -$spacing-07; + @include make-col(3, 4); + } - display: none; + padding-left: $spacing-05; + padding-top: $spacing-07; - @include breakpoint(lg) { - display: block; + display: block; - &[hidden] { - display: none; - } + &[hidden] { + display: none; } > .#{$prefix}--link-list { @@ -238,14 +189,6 @@ $hover-transition-timing: 95ms; &:focus { outline: none; } - - @include breakpoint(lg) { - transform: none; - - > * { - transform: none; - } - } } } @@ -262,37 +205,31 @@ $hover-transition-timing: 95ms; top: 0; width: 100%; - @include breakpoint(lg) { - filter: drop-shadow(0 $spacing-01 6px rgba(0, 0, 0, 0.3)); - border-bottom: 1px solid $toggle-off; - height: rem(49px); - background-color: $layer-01; - } + border-bottom: 1px solid $border-subtle-00; + border-top: 1px solid $border-subtle-00; + height: $spacing-09; + background-color: $background; - .#{$prefix}--tableofcontents__desktop__item { + .#{$prefix}--tableofcontents__item { a { border-left: none; color: $text-secondary; padding-left: $spacing-05; padding-right: $spacing-05; + width: auto; &:focus { outline: $spacing-01 solid $focus; - height: $spacing-09; outline-offset: -#{$spacing-01}; + position: initial; } } } - .#{$prefix}--tableofcontents__desktop__item--active a { - border-bottom: $spacing-01 solid $border-interactive; - padding-bottom: rem(10px); - } - - .#{$prefix}--tableofcontents__desktop { + .#{$prefix}--tableofcontents { flex: 1; max-width: none; - padding-top: 0; + padding: 0; margin-top: 0; position: absolute; transition: left $transition-base motion(standard, productive); @@ -320,15 +257,22 @@ $hover-transition-timing: 95ms; height: 100%; } - .#{$prefix}--toc__navbar-caret-left, - .#{$prefix}--toc__navbar-caret-right { + .#{$prefix}--toc__navbar-chevron-left-container[hidden], + .#{$prefix}--toc__navbar-chevron-right-container[hidden] { + display: none; + } + + .#{$prefix}--toc__navbar-chevron-left-container, + .#{$prefix}--toc__navbar-chevron-right-container { + display: block; width: $spacing-08; height: 100%; - display: none; position: absolute; top: 0; + bottom: 0; + z-index: 1; border: none; - background-color: $layer-01; + background-color: $background; > { svg { position: absolute; @@ -339,7 +283,7 @@ $hover-transition-timing: 95ms; } } &:hover { - background-color: $background-hover; + background-color: $layer-hover-03; transition-duration: $hover-transition-timing; > { svg { @@ -350,7 +294,7 @@ $hover-transition-timing: 95ms; &:focus, &:active { display: block; - background-color: $background-hover; + background-color: $layer-hover-03; outline: $spacing-01 solid $background-brand; outline-offset: -#{$spacing-01}; &::before, @@ -358,64 +302,63 @@ $hover-transition-timing: 95ms; display: none; } } - @media (min-width: 800px) { - display: block; - } - } - .#{$prefix}--toc__navbar-caret-left-container[hidden], - .#{$prefix}--toc__navbar-caret-right-container[hidden] { - display: none; + &::before { + content: ''; + position: absolute; + left: $spacing-08; + top: 0; + height: 100%; + width: $spacing-03; + z-index: 1; + background: linear-gradient( + to right, + $background, + rgba(255, 255, 255, 0) + ); + } } - .#{$prefix}--toc__navbar-caret-left { + .#{$prefix}--toc__navbar-chevron-left-container { left: 0; - } - - .#{$prefix}--toc__navbar-caret-left-gradient { - display: block; - position: absolute; - left: $spacing-08; - top: 0; - height: 100%; - width: $spacing-03; - z-index: 1; - background: linear-gradient(to right, $layer-01, rgba(255, 255, 255, 0)); - } - - .#{$prefix}--toc__navbar-caret-right { - right: 0; - } - - .#{$prefix}--toc__navbar-caret-right-gradient { - display: block; - position: absolute; - top: 0; - right: $spacing-08; - height: 100%; - width: $spacing-03; - background: linear-gradient(to left, $layer-01, rgba(255, 255, 255, 0)); - } + @include breakpoint(md) { + left: -$spacing-05; + } - @include breakpoint(sm) { - .#{$prefix}--tableofcontents__desktop { - display: none; + &:hover { + &::before { + background: linear-gradient( + to right, + $layer-hover-03, + rgba(255, 255, 255, 0) + ); + } } } - @include breakpoint(md) { - .#{$prefix}--tableofcontents__mobile { - padding-left: $spacing-05; + .#{$prefix}--toc__navbar-chevron-right-container { + z-index: 2; + right: -$spacing-01; + + @include breakpoint(md) { + right: -$spacing-05; } - } - @include breakpoint(lg) { - .#{$prefix}--tableofcontents__mobile { - display: none; + &::before { + content: ''; + left: auto; + right: $spacing-08; + background: linear-gradient(to left, $background, rgba(255, 255, 255, 0)); } - .#{$prefix}--tableofcontents__desktop { - display: block; + &:hover { + &::before { + background: linear-gradient( + to left, + $layer-hover-03, + rgba(255, 255, 255, 0) + ); + } } } @@ -442,12 +385,9 @@ $hover-transition-timing: 95ms; position: relative; } - .#{$prefix}--tableofcontents__desktop { + .#{$prefix}--tableofcontents { display: block; } - .#{$prefix}--tableofcontents__mobile { - display: none; - } &[toc-layout='horizontal'] { .#{$prefix}--tableofcontents__navbar { @@ -464,4 +404,61 @@ $hover-transition-timing: 95ms; } } } + + :host(#{$c4d-prefix}-table-of-contents) .#{$prefix}--tableofcontents__navbar { + &::before, + &::after { + border-bottom: 1px solid $border-subtle-00; + border-top: 1px solid $border-subtle-00; + content: ''; + background-color: $background; + position: absolute; + top: -1px; + height: 100%; + left: calc(-50vw + 50%); + right: 100%; + z-index: 1; + } + + &::after { + left: 100%; + right: calc(-50vw + 50%); + } + } + + :host(#{$c4d-prefix}-table-of-contents[toc-layout='horizontal']) + .#{$prefix}--tableofcontents-container { + position: relative; + overflow: scroll; + height: $spacing-09; + } + + :host(#{$c4d-prefix}-table-of-contents) + .#{$prefix}--tableofcontents-container { + @include breakpoint-down(lg) { + position: relative; + overflow: scroll; + height: $spacing-09; + } + } + + :host(#{$c4d-prefix}-table-of-contents[toc-layout='horizontal']) + .#{$prefix}--tableofcontents__content { + max-width: none; + flex: 1; + } + + .#{$c4d-prefix}-ce--table-of-contents__container { + // TODO: Make the layout CSS grid-based so we can remove this ruleset + ::slotted(#{$c4d-prefix}-content-block-simple), + ::slotted(#{$c4d-prefix}-content-block-segmented) { + margin-left: calc(-1 * #{$spacing-05}); + margin-right: calc(-1 * #{$spacing-05}); + } + } + + .#{$c4d-prefix}-ce--toc__navbar-chevron-container--hidden { + position: absolute; + visibility: hidden; + } } diff --git a/packages/styles/scss/globals/_carbon-grid.scss b/packages/styles/scss/globals/_carbon-grid.scss index 209c9526fe7..067dee54f9b 100644 --- a/packages/styles/scss/globals/_carbon-grid.scss +++ b/packages/styles/scss/globals/_carbon-grid.scss @@ -5,8 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -@use '@carbon/styles/scss/grid'; +@use '@carbon/grid'; -@include exports('carbon-grid') { - @include carbon--grid(); -} +// Emit the flex-grid styles +@include grid.flex-grid(); diff --git a/packages/web-components/src/components/link-list/link-list.scss b/packages/web-components/src/components/link-list/link-list.scss index c13bae485bd..3a29d84f0cf 100644 --- a/packages/web-components/src/components/link-list/link-list.scss +++ b/packages/web-components/src/components/link-list/link-list.scss @@ -70,6 +70,11 @@ :host(#{$c4d-prefix}-link-list) { padding-bottom: 0; + ul { + padding: 0; + margin: 0; + } + .#{$prefix}--link-list__list--card, .#{$prefix}--link-list__list--vertical, .#{$c4d-prefix}-ce--link-list__list--end { diff --git a/packages/web-components/src/components/table-of-contents/__stories__/table-of-contents.stories.scss b/packages/web-components/src/components/table-of-contents/__stories__/table-of-contents.stories.scss index 9fd5f40165e..f40079ef495 100644 --- a/packages/web-components/src/components/table-of-contents/__stories__/table-of-contents.stories.scss +++ b/packages/web-components/src/components/table-of-contents/__stories__/table-of-contents.stories.scss @@ -7,11 +7,9 @@ @use '@carbon/styles/scss/spacing' as *; @use '@carbon/ibmdotcom-styles/scss/globals/vars' as *; -@use '@carbon/ibmdotcom-styles/scss/components/tableofcontents'; -.cds-ce-demo--table-of-contents { - padding-top: $spacing-07; - padding-bottom: $spacing-09; +.#{$c4d-prefix}-ce-demo--table-of-contents { + padding: 0 $spacing-05; h3 { padding-top: $spacing-07; diff --git a/packages/web-components/src/components/table-of-contents/table-of-contents.scss b/packages/web-components/src/components/table-of-contents/table-of-contents.scss index 05413d51ca6..371b6db63b3 100644 --- a/packages/web-components/src/components/table-of-contents/table-of-contents.scss +++ b/packages/web-components/src/components/table-of-contents/table-of-contents.scss @@ -5,100 +5,4 @@ // LICENSE file in the root directory of this source tree. // -@use '@carbon/styles/scss/breakpoint' as *; -@use '@carbon/styles/scss/config' as *; -@use '@carbon/styles/scss/spacing' as *; -@use '@carbon/styles/scss/theme' as *; -@use '@carbon/ibmdotcom-styles/scss/components/layout'; -@use '@carbon/ibmdotcom-styles/scss/globals/vars' as *; - -:host(#{$c4d-prefix}-table-of-contents[toc-layout='horizontal']) - .#{$prefix}--tableofcontents__navbar { - @include breakpoint(lg) { - &::before { - border-bottom: 1px solid $border-strong-01; - content: ''; - background-color: $layer-01; - position: absolute; - top: 0; - height: $spacing-09; - left: calc(-50vw + 50%); - right: 100%; - z-index: 1; - } - - &::after { - border-bottom: 1px solid $border-strong-01; - content: ''; - background-color: $layer-01; - position: absolute; - top: 0; - height: $spacing-09; - left: 100%; - right: calc(-50vw + 50%); - z-index: 1; - } - } -} - -:host(#{$c4d-prefix}-table-of-contents[toc-layout='horizontal']) - .#{$prefix}--tableofcontents__desktop-container { - @include breakpoint(lg) { - position: relative; - overflow: hidden; - height: $spacing-09; - } -} - -:host(#{$c4d-prefix}-table-of-contents[toc-layout='horizontal']) - .#{$prefix}--tableofcontents__mobile { - margin: 0; -} - -:host(#{$c4d-prefix}-table-of-contents[toc-layout='horizontal']) - .#{$prefix}--tableofcontents__content { - max-width: none; - flex: 1; -} - -.#{$c4d-prefix}-ce--table-of-contents__container { - // TODO: Make the layout CSS grid-based so we can remove this ruleset - ::slotted(#{$c4d-prefix}-content-block-simple), - ::slotted(#{$c4d-prefix}-content-block-segmented) { - margin-left: calc(-1 * #{$spacing-05}); - margin-right: calc(-1 * #{$spacing-05}); - } -} - -.#{$prefix}--toc__navbar-caret-left-container, -.#{$prefix}--toc__navbar-caret-right-container { - bottom: 0; - z-index: 1; - position: absolute; - display: none; - - @include breakpoint(lg) { - display: block; - } -} - -.#{$prefix}--toc__navbar-caret-right-container { - right: -$spacing-05; - z-index: 2; -} - -.#{$prefix}--toc__navbar-caret-left-container { - left: -$spacing-05; -} - -.#{$prefix}--toc__navbar-caret-left, -.#{$prefix}--toc__navbar-caret-right { - position: relative; - // 40px caret width & 8px gradient - height: $spacing-09; -} - -.#{$c4d-prefix}-ce--toc__navbar-caret-container--hidden { - position: absolute; - visibility: hidden; -} +@use '@carbon/ibmdotcom-styles/scss/components/tableofcontents'; diff --git a/packages/web-components/src/components/table-of-contents/table-of-contents.ts b/packages/web-components/src/components/table-of-contents/table-of-contents.ts index f01bed9035a..ddcc441f935 100644 --- a/packages/web-components/src/components/table-of-contents/table-of-contents.ts +++ b/packages/web-components/src/components/table-of-contents/table-of-contents.ts @@ -9,14 +9,12 @@ import { classMap } from 'lit/directives/class-map.js'; import { ifDefined } from 'lit/directives/if-defined.js'; -import { html, LitElement, nothing } from 'lit'; +import { html, LitElement } from 'lit'; import { property, query, queryAll, state } from 'lit/decorators.js'; -import CaretLeft20 from '../../internal/vendor/@carbon/web-components/icons/caret--left/20.js'; -import CaretRight20 from '../../internal/vendor/@carbon/web-components/icons/caret--right/20.js'; -import { baseFontSize, breakpoints } from '@carbon/layout'; +import ChevronLeft20 from '../../internal/vendor/@carbon/web-components/icons/chevron--left/20.js'; +import ChevronRight20 from '../../internal/vendor/@carbon/web-components/icons/chevron--right/20.js'; import HostListener from '../../internal/vendor/@carbon/web-components/globals/decorators/host-listener.js'; import HostListenerMixin from '../../internal/vendor/@carbon/web-components/globals/mixins/host-listener.js'; -import TableOfContents20 from '../../internal/vendor/@carbon/web-components/icons/table-of-contents/20.js'; import throttle from 'lodash-es/throttle.js'; import StickyHeader from '../../internal/vendor/@carbon/ibmdotcom-utilities/utilities/StickyHeader/StickyHeader'; import settings from '../../internal/vendor/@carbon/ibmdotcom-utilities/utilities/settings/settings'; @@ -24,14 +22,16 @@ import styles from './table-of-contents.scss'; import { TOC_TYPES } from './defs'; import StableSelectorMixin from '../../globals/mixins/stable-selector'; import { carbonElement as customElement } from '../../internal/vendor/@carbon/web-components/globals/decorators/carbon-element.js'; +import MediaQueryMixin, { + MQBreakpoints, + MQDirs, +} from '../../component-mixins/media-query/media-query'; const { prefix, stablePrefix: c4dPrefix } = settings; // total button width - grid offset const buttonWidthOffset = 32; -const gridLgBreakpoint = parseFloat(breakpoints.lg.width) * baseFontSize; - interface Cancelable { cancel(): void; } @@ -64,16 +64,10 @@ function findLastIndex( * @slot menu-rule - The menu rule. */ @customElement(`${c4dPrefix}-table-of-contents`) -class C4DTableOfContents extends HostListenerMixin( - StableSelectorMixin(LitElement) +class C4DTableOfContents extends MediaQueryMixin( + HostListenerMixin(StableSelectorMixin(LitElement)), + { [MQBreakpoints.LG]: MQDirs.MAX } ) { - /** - * The formatter for the aria label text for the mobile ToC. - * Should be changed upon the locale the component is rendered with. - */ - @property({ attribute: false }) - ariaLabelFormatter = 'Table of contents'; - /** * Defines TOC type, "" for default, `horizontal` for horizontal variant. */ @@ -112,12 +106,6 @@ class C4DTableOfContents extends HostListenerMixin( @state() private _hasHeading = false; - /** - * `true` if mobile container is visible. - */ - @state() - private _hasMobileContainerVisible = false; - /** * The observer for the intersection of left-side content edge. */ @@ -126,7 +114,7 @@ class C4DTableOfContents extends HostListenerMixin( /** * The scrolling content. */ - @query(`.${prefix}--tableofcontents__desktop`) + @query(`.${prefix}--tableofcontents`) private _contentNode?: HTMLElement; /** @@ -142,29 +130,20 @@ class C4DTableOfContents extends HostListenerMixin( */ @query(`.${prefix}--sub-content-right`) private _intersectionRightSentinelNode?: HTMLElement; - - @queryAll(`.${prefix}--tableofcontents__desktop__item`) + @queryAll(`.${prefix}--tableofcontents__item`) private _itemNodes?: HTMLElement[] = []; + @query(`.${prefix}--tableofcontents__content`) + private _contentDiv?: HTMLElement; + @query(`.${prefix}--tableofcontents__navbar`) private _navBar?: HTMLElement; /** - * The container for the mobile UI. + * Whether we're viewing smaller or larger window. */ - @query(`.${prefix}--tableofcontents__mobile`) - private _mobileContainerNode?: HTMLElement; - - /** - * The ``. - * - * @param event The event. - */ - private _handleChangeSelect(event: Event) { - this._handleUserInitiatedJump((event.target as HTMLSelectElement).value); - } - /** * Handles `click` event on a menu item. * * @param event The event. */ private _handleClickItem(event: MouseEvent) { - const { selectorDesktopItem } = this - .constructor as typeof C4DTableOfContents; + const { selectorItem } = this.constructor as typeof C4DTableOfContents; const target = event.target as HTMLAnchorElement; - if (target.matches?.(selectorDesktopItem)) { + if (target.matches?.(selectorItem)) { this._handleUserInitiatedJump(target.dataset.target!); event.preventDefault(); } } /** - * Handles `click` event on a TOC navigation item. + * Handles `keyboard` event on a TOC navigation item. * * @param event The event. */ private _handleOnKeyDown(event: KeyboardEvent) { - const { selectorDesktopItem } = this - .constructor as typeof C4DTableOfContents; + const { selectorItem } = this.constructor as typeof C4DTableOfContents; const target = event.target as HTMLAnchorElement; const { _pageIsRTL: pageIsRTL } = this; - if (target.matches?.(selectorDesktopItem)) { + const paginateAccessibile = + this.layout === TOC_TYPES.HORIZONTAL || this._isMobile; + + if (target.matches?.(selectorItem)) { if (pageIsRTL) { - if (event.key === 'Tab') { - if (event.shiftKey) { - if ( - target.parentElement?.previousElementSibling && - target.parentElement?.previousElementSibling.getBoundingClientRect() - .right > - this._navBar!.getBoundingClientRect().right - buttonWidthOffset - ) { - this._paginateLeft(); - } - } else if ( + if (event.key === 'ArrowLeft') { + ( + target.parentElement?.nextElementSibling + ?.children[0] as HTMLAnchorElement + )?.focus(); + if ( + paginateAccessibile && + target.parentElement?.previousElementSibling && + target.parentElement?.previousElementSibling.getBoundingClientRect() + .right > + this._navBar!.getBoundingClientRect().right - buttonWidthOffset + ) { + this._paginateLeft(); + } + } + if (event.key === 'ArrowRight') { + ( + target.parentElement?.previousElementSibling + ?.children[0] as HTMLAnchorElement + )?.focus(); + if ( + paginateAccessibile && target.parentElement?.nextElementSibling && target.parentElement?.nextElementSibling.getBoundingClientRect() .left < @@ -278,9 +234,14 @@ class C4DTableOfContents extends HostListenerMixin( this._paginateRight(); } } - } else if (event.key === 'Tab') { - if (event.shiftKey) { + } else { + if (event.key === 'ArrowLeft') { + ( + target.parentElement?.previousElementSibling + ?.children[0] as HTMLAnchorElement + )?.focus(); if ( + paginateAccessibile && target.parentElement?.previousElementSibling && target.parentElement?.previousElementSibling!.getBoundingClientRect() .left < @@ -288,13 +249,21 @@ class C4DTableOfContents extends HostListenerMixin( ) { this._paginateLeft(); } - } else if ( - target.parentElement?.nextElementSibling && - target.parentElement?.nextElementSibling!.getBoundingClientRect() - .right > - this._navBar!.getBoundingClientRect().right - buttonWidthOffset - ) { - this._paginateRight(); + } + if (event.key === 'ArrowRight') { + ( + target.parentElement?.nextElementSibling + ?.children[0] as HTMLAnchorElement + )?.focus(); + if ( + paginateAccessibile && + target.parentElement?.nextElementSibling && + target.parentElement?.nextElementSibling!.getBoundingClientRect() + .right > + this._navBar!.getBoundingClientRect().right - buttonWidthOffset + ) { + this._paginateRight(); + } } } } @@ -436,10 +405,6 @@ class C4DTableOfContents extends HostListenerMixin( const masthead: HTMLElement | null = this.ownerDocument.querySelector( `${c4dPrefix}-masthead` ); - const mobilePadding = - window.innerWidth < gridLgBreakpoint - ? this._mobileContainerNode?.offsetHeight - : 0; if (elem instanceof HTMLElement) { const currentY = window.scrollY; @@ -448,7 +413,16 @@ class C4DTableOfContents extends HostListenerMixin( if (currentY > elem.offsetTop && masthead) { targetY = elem.offsetTop - masthead.offsetHeight; } else { - targetY = elem.offsetTop - mobilePadding!; + targetY = + elem.offsetTop - + parseInt( + window.getComputedStyle(elem).getPropertyValue('padding-top') + ) - + parseInt( + window + .getComputedStyle(this._contentDiv!) + .getPropertyValue('padding-top') + ); } window.scrollTo(0, targetY); @@ -531,7 +505,7 @@ class C4DTableOfContents extends HostListenerMixin( _itemNodes: itemNodes, _pageIsRTL: pageIsRTL, } = this; - // If the right-side intersection sentinel is in the view, it means that right-side caret button is hidden. + // If the right-side intersection sentinel is in the view, it means that right-side chevron button is hidden. // Given scrolling to left makes it shown, // `contentContainerNode!.offsetWidth` will shrink as we scroll and we need to adjust for it. const elems = Array.prototype.slice.call(itemNodes); @@ -567,8 +541,8 @@ class C4DTableOfContents extends HostListenerMixin( currentScrollPosition - navBar!.getBoundingClientRect().right + buttonWidthOffset; - // If the new scroll position is less than the width of the left caret button, - // it means that hiding the left caret button reveals the whole of the left-most nav item. + // If the new scroll position is less than the width of the left chevron button, + // it means that hiding the left chevron button reveals the whole of the left-most nav item. // Snaps the left-most nav item to the left edge of nav container in this case. this._currentScrollPosition = newScrollPosition <= 0 ? 0 : newScrollPosition; @@ -633,16 +607,9 @@ class C4DTableOfContents extends HostListenerMixin( } } - /** - * Handles resize of mobile container. - * - * @param records The resize records. - */ - private _observeResizeMobileContainer = (records) => { - const entry = records[records.length - 1]; - const { height } = entry.contentRect; - this._hasMobileContainerVisible = height > 0; - }; + mediaQueryCallbackMaxLG() { + this._isMobile = this.carbonBreakpoints.lg.matches; + } /** * The current 0px offset from the top of page. @@ -672,7 +639,6 @@ class C4DTableOfContents extends HostListenerMixin( connectedCallback() { super.connectedCallback(); - this._cleanAndCreateObserverResizeMobileContainer({ create: true }); this._cleanAndCreateIntersectionObserverContainer({ create: true }); if (!this._throttleScroll) { this._throttleScroll = throttle(this._handleOnScroll, 250); @@ -681,7 +647,6 @@ class C4DTableOfContents extends HostListenerMixin( } disconnectedCallback() { - this._cleanAndCreateObserverResizeMobileContainer(); this._cleanAndCreateIntersectionObserverContainer(); this._contentMutationObserver.disconnect(); if (this._throttleScroll) { @@ -692,35 +657,23 @@ class C4DTableOfContents extends HostListenerMixin( } firstUpdated() { - this._cleanAndCreateObserverResizeMobileContainer({ create: true }); + super.firstUpdated(); this._cleanAndCreateIntersectionObserverContainer({ create: true }); StickyHeader.global.tableOfContents = this; } - updated(changedProperties) { - if (changedProperties.has('_currentTarget')) { - const { - _currentTarget: currentTarget, - _mobileSelectNode: mobileSelectNode, - } = this; - // Ensures setting the `value` after rendering child `