diff --git a/resources/ts/filters/filter-dropdown.ts b/resources/ts/filters/filter-dropdown.ts index 7cf4c0a..3263df1 100644 --- a/resources/ts/filters/filter-dropdown.ts +++ b/resources/ts/filters/filter-dropdown.ts @@ -1,51 +1,100 @@ import submitForm from '../form/form-handler'; +import isReducedMotion from '../common/reduced-motion-detection'; const DROPDOWN_SELECTOR = '.fau-dropdown'; const DROPDOWN_TOGGLE_SELECTOR = '.fau-dropdown__toggle'; const DROPDOWN_CONTENT_SELECTOR = '.fau-dropdown__content'; const CLICKAWAY_WINDOW_WIDTH_THRESHOLD = 768; // close on click away only works on screen sizes above this value -const closeDropDown = ( dropdown: HTMLElement ) => { - dropdown.setAttribute( 'aria-expanded', 'false' ); - submitForm(); -}; +class Dropdown { + private dropdown: HTMLElement; + private toggle: HTMLElement; + private content: HTMLElement; + private checkboxes: HTMLInputElement[]; + private initialState: boolean[] = []; -const toggleDropdown = ( dropdown: HTMLElement ) => { - const isAriaExpanded = dropdown.getAttribute( 'aria-expanded' ) === 'true'; + constructor( dropdown: HTMLElement ) { + this.dropdown = dropdown; + this.toggle = dropdown.querySelector( DROPDOWN_TOGGLE_SELECTOR )!; + this.content = dropdown.querySelector( DROPDOWN_CONTENT_SELECTOR )!; + this.checkboxes = Array.from( + this.content.querySelectorAll( 'input[type="checkbox"]' ) + ); - dropdown.setAttribute( 'aria-expanded', isAriaExpanded ? 'false' : 'true' ); + this.init(); + } - if ( isAriaExpanded ) { - submitForm(); + private recordInitialState() { + this.initialState = this.checkboxes.map( + ( checkbox ) => checkbox.checked + ); } -}; -const registerClickListeners = () => { + private hasStateChanged(): boolean { + const currentState = this.checkboxes.map( + ( checkbox ) => checkbox.checked + ); + + return this.initialState.some( + ( value, index ) => value !== currentState[ index ] + ); + } + + private close() { + this.dropdown.setAttribute( 'aria-expanded', 'false' ); + + if ( this.hasStateChanged() && isReducedMotion() ) { + submitForm(); + } + } + + private toggleDropdown() { + const isAriaExpanded = + this.dropdown.getAttribute( 'aria-expanded' ) === 'true'; + + if ( ! isAriaExpanded ) { + this.recordInitialState(); + } + + this.dropdown.setAttribute( + 'aria-expanded', + isAriaExpanded ? 'false' : 'true' + ); + + if ( isAriaExpanded && isReducedMotion() && this.hasStateChanged() ) { + submitForm(); + } + } + + private handleBodyClick( event: MouseEvent ) { + const target = event.target as Node; + + if ( this.toggle.contains( target ) ) { + this.toggleDropdown(); + return; + } + + if ( + this.dropdown.getAttribute( 'aria-expanded' ) === 'true' && + ! this.dropdown.contains( target ) && + window.innerWidth > CLICKAWAY_WINDOW_WIDTH_THRESHOLD + ) { + this.close(); + } + } + + private init() { + document.body.addEventListener( + 'click', + this.handleBodyClick.bind( this ) + ); + } +} + +const registerDropdowns = () => { document .querySelectorAll< HTMLElement >( DROPDOWN_SELECTOR ) - .forEach( ( dropdown ) => { - const toggle = dropdown.querySelector( DROPDOWN_TOGGLE_SELECTOR ); - const content = dropdown.querySelector( DROPDOWN_CONTENT_SELECTOR ); - - if ( ! toggle || ! content ) { - return; - } - - document.body.addEventListener( 'click', ( event ) => { - if ( toggle.contains( event.target as Node ) ) { - toggleDropdown( dropdown ); - return; - } - - if ( - dropdown.getAttribute( 'aria-expanded' ) === 'true' && - ! dropdown.contains( event.target as Node ) && - window.innerWidth > CLICKAWAY_WINDOW_WIDTH_THRESHOLD - ) { - closeDropDown( dropdown ); - } - } ); - } ); + .forEach( ( dropdown ) => new Dropdown( dropdown ) ); }; -registerClickListeners(); +registerDropdowns();