Style active top level section differently than ordinary active TOC sections

Bug: T316032
Change-Id: I662a03f7f7b1e1fb65c365b3db6096cae75fdeb2
This commit is contained in:
bwang 2022-09-20 16:32:53 -05:00
parent 06c243da35
commit 600dd1496a
4 changed files with 70 additions and 58 deletions

View File

@ -32,7 +32,7 @@ module.exports = {
branches: 25,
functions: 29,
lines: 32,
statements: 33
statements: 32
}
},

View File

@ -3,7 +3,8 @@
const SECTION_CLASS = 'sidebar-toc-list-item';
const ACTIVE_SECTION_CLASS = 'sidebar-toc-list-item-active';
const EXPANDED_SECTION_CLASS = 'sidebar-toc-list-item-expanded';
const PARENT_SECTION_CLASS = 'sidebar-toc-level-1';
const TOP_SECTION_CLASS = 'sidebar-toc-level-1';
const ACTIVE_TOP_SECTION_CLASS = 'sidebar-toc-level-1-active';
const LINK_CLASS = 'sidebar-toc-link';
const TOGGLE_CLASS = 'sidebar-toc-toggle';
const TOC_COLLAPSED_CLASS = 'vector-toc-collapsed';
@ -114,9 +115,9 @@ module.exports = function tableOfContents( props ) {
/**
* Sets an `ACTIVE_SECTION_CLASS` on the element with an id that matches `id`.
* If the element is not a top level heading (e.g. element with the
* `PARENT_SECTION_CLASS`), the top level heading will also have the
* `ACTIVE_SECTION_CLASS`;
* Sets an `ACTIVE_TOP_SECTION_CLASS` on the top level heading (e.g. element with the
* `TOP_SECTION_CLASS`).
* If the element is a top level heading, the element will also have both classes.
*
* @param {string} id The id of the element to be activated in the Table of Contents.
*/
@ -135,17 +136,13 @@ module.exports = function tableOfContents( props ) {
return;
}
const topSection = /** @type {HTMLElement} */ ( selectedTocSection.closest( `.${PARENT_SECTION_CLASS}` ) );
const topSection = /** @type {HTMLElement} */ ( selectedTocSection.closest( `.${TOP_SECTION_CLASS}` ) );
if ( selectedTocSection === topSection ) {
activeTopSection = topSection;
activeTopSection.classList.add( ACTIVE_SECTION_CLASS );
} else {
activeTopSection = topSection;
activeSubSection = selectedTocSection;
activeTopSection.classList.add( ACTIVE_SECTION_CLASS );
activeSubSection.classList.add( ACTIVE_SECTION_CLASS );
}
// Assign the active top and sub sections, apply classes
activeTopSection = topSection;
activeSubSection = ( selectedTocSection === topSection ) ? topSection : selectedTocSection;
activeTopSection.classList.add( ACTIVE_TOP_SECTION_CLASS );
activeSubSection.classList.add( ACTIVE_SECTION_CLASS );
}
/**
@ -158,7 +155,7 @@ module.exports = function tableOfContents( props ) {
activeSubSection = undefined;
}
if ( activeTopSection ) {
activeTopSection.classList.remove( ACTIVE_SECTION_CLASS );
activeTopSection.classList.remove( ACTIVE_TOP_SECTION_CLASS );
activeTopSection = undefined;
}
}
@ -235,7 +232,7 @@ module.exports = function tableOfContents( props ) {
return;
}
const parentSection = /** @type {HTMLElement} */ ( tocSection.closest( `.${PARENT_SECTION_CLASS}` ) );
const parentSection = /** @type {HTMLElement} */ ( tocSection.closest( `.${TOP_SECTION_CLASS}` ) );
const toggle = tocSection.querySelector( `.${TOGGLE_CLASS}` );
if ( parentSection && toggle && expandedSections.indexOf( parentSection ) < 0 ) {
@ -277,7 +274,7 @@ module.exports = function tableOfContents( props ) {
*/
function isTopLevelSection( id ) {
const section = document.getElementById( id );
return !!section && section.classList.contains( PARENT_SECTION_CLASS );
return !!section && section.classList.contains( TOP_SECTION_CLASS );
}
/**
@ -319,7 +316,7 @@ module.exports = function tableOfContents( props ) {
* Set aria-expanded attribute for all toggle buttons.
*/
function initializeExpandedStatus() {
const parentSections = props.container.querySelectorAll( `.${PARENT_SECTION_CLASS}` );
const parentSections = props.container.querySelectorAll( `.${TOP_SECTION_CLASS}` );
parentSections.forEach( ( section ) => {
const expanded = section.classList.contains( EXPANDED_SECTION_CLASS );
const toggle = section.querySelector( `.${TOGGLE_CLASS}` );
@ -530,6 +527,7 @@ module.exports = function tableOfContents( props ) {
* @property {expandSection} expandSection
* @property {toggleExpandSection} toggleExpandSection
* @property {string} ACTIVE_SECTION_CLASS
* @property {string} ACTIVE_TOP_SECTION_CLASS
* @property {string} EXPANDED_SECTION_CLASS
* @property {string} LINK_CLASS
* @property {string} TOGGLE_CLASS
@ -539,6 +537,7 @@ module.exports = function tableOfContents( props ) {
changeActiveSection,
toggleExpandSection,
ACTIVE_SECTION_CLASS,
ACTIVE_TOP_SECTION_CLASS,
EXPANDED_SECTION_CLASS,
LINK_CLASS,
TOGGLE_CLASS

View File

@ -68,6 +68,7 @@
height: @toc-subsection-toggle-icon-size;
font-size: 0.75em; // reduces size of toggle icon to 12px @ 16
transition: @transition-duration-base;
color: transparent;
cursor: pointer;
}
@ -77,12 +78,27 @@
display: block;
}
.sidebar-toc-list-item-active > .sidebar-toc-link {
// Highlight active section
// Highlight and bold active sections, active top sections that are unexpanded
// and active top sections that are the only active element.
.sidebar-toc-list-item-active,
.sidebar-toc-level-1-active:not( .sidebar-toc-list-item-expanded ),
.sidebar-toc-list-item-active.sidebar-toc-level-1-active {
> .sidebar-toc-link {
// Highlight active section
color: @color-base;
font-weight: bold;
.sidebar-toc-text {
// Increase width to prevent line wrapping due to bold text
// Avoid applying on link element to avoid focus indicator changing size
width: ~'calc( 100% + @{sidebar-toc-right-padding} )';
}
}
}
// Highlight active top sections that contain an active section
.sidebar-toc-level-1-active:not( .sidebar-toc-list-item-active ) > .sidebar-toc-link {
color: @color-base;
font-weight: bold;
// increase width to prevent line wrapping due to bold text
width: ~'calc( 100% + @{sidebar-toc-right-padding} )';
}
.sidebar-toc-text {
@ -142,7 +158,6 @@
.sidebar-toc-toggle {
display: block;
color: transparent;
}
.sidebar-toc-level-1.sidebar-toc-list-item-expanded .sidebar-toc-toggle {

View File

@ -4,7 +4,8 @@ const tableOfContentsTemplate = fs.readFileSync( 'includes/templates/TableOfCont
const tableOfContentsLineTemplate = fs.readFileSync( 'includes/templates/TableOfContents__line.mustache', 'utf8' );
const initTableOfContents = require( '../../resources/skins.vector.es6/tableOfContents.js' );
let /** @type {HTMLElement} */ fooSection,
let /** @type {HTMLElement} */ container,
/** @type {HTMLElement} */ fooSection,
/** @type {HTMLElement} */ barSection,
/** @type {HTMLElement} */ bazSection,
/** @type {HTMLElement} */ quxSection,
@ -82,19 +83,20 @@ function render( templateProps = {} ) {
*/
function mount( templateProps = {} ) {
document.body.innerHTML = render( templateProps );
const toc = initTableOfContents( {
container: /** @type {HTMLElement} */ ( document.getElementById( 'mw-panel-toc' ) ),
onHeadingClick,
onToggleClick,
onToggleCollapse
} );
container = /** @type {HTMLElement} */ ( document.getElementById( 'mw-panel-toc' ) );
fooSection = /** @type {HTMLElement} */ ( document.getElementById( 'toc-foo' ) );
barSection = /** @type {HTMLElement} */ ( document.getElementById( 'toc-bar' ) );
bazSection = /** @type {HTMLElement} */ ( document.getElementById( 'toc-baz' ) );
quxSection = /** @type {HTMLElement} */ ( document.getElementById( 'toc-qux' ) );
quuxSection = /** @type {HTMLElement} */ ( document.getElementById( 'toc-quux' ) );
return toc;
return initTableOfContents( {
container,
onHeadingClick,
onToggleClick,
onToggleCollapse
} );
}
describe( 'Table of contents', () => {
@ -146,33 +148,29 @@ describe( 'Table of contents', () => {
describe( 'applies correct classes', () => {
test( 'when changing active sections', () => {
const toc = mount( { 'vector-is-collapse-sections-enabled': true } );
toc.changeActiveSection( 'toc-foo' );
expect( fooSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( true );
expect( barSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
expect( bazSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
expect( quxSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
expect( quuxSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
let activeSections;
let activeTopSections;
toc.changeActiveSection( 'toc-bar' );
expect( fooSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
expect( barSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( true );
expect( bazSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
expect( quxSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
expect( quuxSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
/**
* @param {string} id
* @param {HTMLElement} activeSection
* @param {HTMLElement} activeTopSection
*/
function testActiveClasses( id, activeSection, activeTopSection ) {
toc.changeActiveSection( id );
activeSections = container.querySelectorAll( `.${toc.ACTIVE_SECTION_CLASS}` );
activeTopSections = container.querySelectorAll( `.${toc.ACTIVE_TOP_SECTION_CLASS}` );
expect( activeSections.length ).toEqual( 1 );
expect( activeTopSections.length ).toEqual( 1 );
expect( activeSections[ 0 ] ).toEqual( activeSection );
expect( activeTopSections[ 0 ] ).toEqual( activeTopSection );
}
toc.changeActiveSection( 'toc-baz' );
expect( fooSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
expect( barSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( true );
expect( bazSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( true );
expect( quxSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
expect( quuxSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
toc.changeActiveSection( 'toc-qux' );
expect( fooSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
expect( barSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( true );
expect( bazSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
expect( quxSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( true );
expect( quuxSection.classList.contains( toc.ACTIVE_SECTION_CLASS ) ).toEqual( false );
testActiveClasses( 'toc-foo', fooSection, fooSection );
testActiveClasses( 'toc-bar', barSection, barSection );
testActiveClasses( 'toc-baz', bazSection, barSection );
testActiveClasses( 'toc-qux', quxSection, barSection );
testActiveClasses( 'toc-quux', quuxSection, quuxSection );
} );
test( 'when expanding sections', () => {