Skip to content
Snippets Groups Projects
accordion-item.tsx 8.57 KiB
Newer Older
  • Learn to ignore specific revisions
  • import {Component, Listen, Prop, State, Element, h} from '@stencil/core';
    
    import {AccordionVariants} from '../../utils/utils';
    
    let keys = {
      enter: 'Enter', //13
      tab: 'Tab', //9
      pageUp: 'PageUp', //33
      pageDown: 'PageDown', //34
      arrowUp: 'ArrowUp', // 38
      arrowDown: 'ArrowDown', //40
      home: 'Home', //36
      end: 'End', //35
    };
    
    Tuukka Turu's avatar
    Tuukka Turu committed
    
    @Component({
      tag: 'hy-accordion-item',
      styleUrl: 'accordion-item.scss',
    
    Tuukka Turu's avatar
    Tuukka Turu committed
    })
    export class AccordionItem {
    
    Tuukka Turu's avatar
    Tuukka Turu committed
      @Element() el: HTMLElement;
    
    Tuukka Turu's avatar
    Tuukka Turu committed
      @Prop() accordiontitle?: string;
    
      @Prop() variant: AccordionVariants = AccordionVariants.default;
    
    druid's avatar
    druid committed
      @Prop() headerstyle: string = 'common';
    
    Tuukka Turu's avatar
    Tuukka Turu committed
      @State() ready: boolean = false;
    
    
      componentDidLoad() {
        this.ready = true;
    
    druid's avatar
    druid committed
    
        let hyMainDiv = this.el.closest('.hy-main');
        if (hyMainDiv) {
          if (!hyMainDiv.classList.contains('with-sidebar')) {
            this.headerstyle = 'large';
          }
        }
    
      }
    
      componentDidRender() {
        if (window.location.hash) {
          this.expandPanelByAnchor(window.location.hash);
        }
      }
    
    
      @Listen('hashchange', {target: 'window'})
    
      onHashChange() {
    
        if (window.location.hash) {
          this.expandPanelByAnchor(window.location.hash);
        }
      }
    
      @Listen('keydown')
      handleKeyDown(event: KeyboardEvent) {
        const containerId = this.el.parentElement.id;
        let accordion = document.querySelectorAll(`#${containerId}`)[0];
    
        const triggers = Array.prototype.slice.call(accordion.querySelectorAll('.hy-accordion__button'));
        let target = event.target as HTMLButtonElement;
    
        const key = event.code;
    
        const ctrlModifier = event.ctrlKey && [keys.pageDown, keys.pageUp].includes(key); //key.match(/33|34/);
    
    
        if (target.classList.contains('hy-accordion__button')) {
          // Up/ Down arrow and Control + Page Up/ Page Down keyboard operations
    
          if ([keys.arrowDown, keys.arrowUp].includes(key) || ctrlModifier) {
    
            const index = triggers.indexOf(target);
    
            const direction = [keys.arrowDown, keys.pageDown].includes(key) ? 1 : -1;
    
            const length = triggers.length;
            const newIndex = (index + length + direction) % length;
            triggers[newIndex].focus();
    
            event.preventDefault();
    
          } else if ([keys.home, keys.end].includes(key)) {
    
            switch (key) {
              // Go to first accordion
    
              case keys.home:
    
                triggers[0].focus();
                break;
              // Go to last accordion
    
              case keys.end:
    
                triggers[triggers.length - 1].focus();
                break;
            }
            event.preventDefault();
          }
        }
      }
    
      @Listen('click')
      handleClick(event) {
        const containerId = this.el.parentElement.id;
    
        let target = event.target as HTMLTextAreaElement;
        const targetElement = target.tagName.toLowerCase();
    
        const possibleTags = [targetElement].some((r) => ['svg', 'span', 'path', 'button', 'a'].indexOf(r) >= 0);
    
    
        let accordion = document.querySelectorAll(`#${containerId}`)[0];
        const allowMultiple = accordion.hasAttribute('data-allow-multiple');
        const allowToggle = allowMultiple ? allowMultiple : accordion.hasAttribute('data-allow-toggle');
    
        if (target && possibleTags) {
          if (targetElement !== 'button') {
            target = target.closest('.hy-accordion__button');
          }
    
    
          let targetParent = target.closest('.hy-accordion__item');
          let targetContent = targetParent.querySelectorAll('.hy-accordion__content')[0];
    
          const isExpanded = target.getAttribute('aria-expanded') == 'true';
          const active = accordion.querySelector('[aria-expanded="true"]');
    
          if (!allowMultiple && active && active !== target) {
            active.setAttribute('aria-expanded', 'false');
            this.collapseSection(targetContent);
            if (targetParent.classList.contains('hy-accordion__item__is-open')) {
              targetParent.classList.remove('hy-accordion__item__is-open');
            }
            accordion.querySelector(`#${active.getAttribute('aria-controls')}`).setAttribute('aria-hidden', 'true');
    
            if (!allowToggle) {
              active.removeAttribute('aria-disabled');
            }
          }
    
          if (!isExpanded) {
            this.expandSection(targetContent);
            target.setAttribute('aria-expanded', 'true');
            targetParent.classList.add('hy-accordion__item__is-open');
    
            accordion.querySelector(`#${target.getAttribute('aria-controls')}`).setAttribute('aria-hidden', 'false');
    
            if (!allowToggle) {
              target.setAttribute('aria-disabled', 'true');
            }
          } else if (allowToggle && isExpanded) {
            target.setAttribute('aria-expanded', 'false');
            this.collapseSection(targetContent);
            if (targetParent.classList.contains('hy-accordion__item__is-open')) {
              targetParent.classList.remove('hy-accordion__item__is-open');
            }
            accordion.querySelector(`#${target.getAttribute('aria-controls')}`).setAttribute('aria-hidden', 'true');
          }
    
          event.preventDefault();
          event.stopImmediatePropagation();
        }
      }
    
      expandPanelByAnchor(anchor) {
        if (anchor.length > 0) {
          anchor = anchor.substr(1);
          let target = document.querySelectorAll(`[id=${anchor}]`)[0];
    
          if (target && target.classList.contains('hy-accordion__button')) {
    
            let targetParent = target.closest('.hy-accordion__item');
            let targetContent = targetParent.querySelectorAll('.hy-accordion__content')[0];
    
            this.expandSection(targetContent);
            target.setAttribute('aria-expanded', 'true');
            targetParent.classList.add('hy-accordion__item__is-open');
    
            const containerId = targetParent.parentElement.parentElement.id;
            if (containerId.length > 0) {
              let accordion = document.querySelectorAll(`#${containerId}`)[0];
              accordion.querySelector(`#${target.getAttribute('aria-controls')}`).setAttribute('aria-hidden', 'false');
            }
    
      collapseSection(element) {
    
        element.style.height = 0 + 'px';
        element.setAttribute('data-collapsed', 'true');
        setTimeout(() => {
          element.style.display = 'none';
        }, 250);
      }
    
    
      expandSection(element) {
    
        element.style.display = 'block';
        element.style.height = element.scrollHeight + 'px';
        element.setAttribute('data-collapsed', 'false');
      }
    
    
    Tuukka Turu's avatar
    Tuukka Turu committed
      render() {
    
    Tuukka Turu's avatar
    Tuukka Turu committed
        const containerId = this.el.parentElement.id;
    
    Tuukka Turu's avatar
    Tuukka Turu committed
        if (this.ready && containerId.length > 0) {
          document.querySelectorAll(`#${containerId}`).forEach(function (accordion) {
    
    Tuukka Turu's avatar
    Tuukka Turu committed
            if (accordion) {
              accordion.querySelectorAll('.hy-accordion__button').forEach(function (trigger) {
                trigger.addEventListener('focus', function () {
                  trigger.classList.add('focus');
                });
                trigger.addEventListener('blur', function () {
                  trigger.classList.remove('focus');
                });
              });
    
        const classAttributes = ['hy-accordion__item', this.variant].join(' ');
        const classInnerWrapper = ['hy-accordion__item--container', this.variant].join(' ');
        const classHeadingAttributes = ['hy-accordion--heading', this.variant].join(' ');
    
    druid's avatar
    druid committed
        const classContentAttributes = [
          'hy-accordion__content',
          `hy-accordion__content--${this.variant}`,
          this.variant,
        ].join(' ');
        const classContentInnerWrapper = [
          'hy-accordion__content--inner-wrapper',
          `hy-accordion__content--inner-wrapper--${this.headerstyle}`,
          `hy-accordion__content--inner-wrapper--${this.variant}`,
        ].join(' ');
    
    Tuukka Turu's avatar
    Tuukka Turu committed
        const titleAsId = this.accordiontitle.toLowerCase().replace(/\W/g, '-');
    
        const accordionItemHref = '#' + titleAsId + '--title';
    
        return (
    
          <div class={classAttributes}>
            <div class={classInnerWrapper}>
              <div class={classHeadingAttributes}>
                <a href={accordionItemHref} class="hy-accordion__link" tabindex="-1">
                  <button
                    aria-expanded="false"
                    aria-controls={`${titleAsId}--content`}
    
                    class={`hy-accordion__button hy-accordion__button--${this.headerstyle}`}
    
                    id={`${titleAsId}--title`}
                  >
                    <span class="hy-accordion--heading__icon">
    
    shamalainen's avatar
    shamalainen committed
                      <hy-icon icon={'hy-icon-caret-down'} size={16} />
    
                    <span>{this.accordiontitle}</span>
    
                  </button>
                </a>
              </div>
              <div
                aria-labelledBy={`${titleAsId}--title`}
                class={classContentAttributes}
                id={`${titleAsId}--content`}
                role="region"
                aria-hidden="true"
                style={{display: 'none'}}
              >
    
    druid's avatar
    druid committed
                <div class={classContentInnerWrapper}>
                  <slot></slot>
                </div>