<ul class="Linklist Linklist--padded Treeview Treeview--default js-Treeview u-text-r-xs">
    <li><a href="https://example.com">Nulla rerum magni</a>
        <ul>
            <li><a href="https://example.com">Earum rem culpa</a></li>
            <li><a href="https://example.com">Numquam sit veniam</a></li>
            <li>
                <a href="https://example.com">Voluptatem quia cum</a>
                <ul>
                    <li><a href="https://example.com">Totam et amet</a></li>
                    <li><a href="https://example.com">Quo illo molestiae</a>
                        <ul>
                            <li><a href="https://example.com">Quo illo molestiae</a></li>
                            <li><a href="https://example.com">Dolorum quod quia</a></li>
                            <li><a href="https://example.com">Aliquid deleniti quae</a></li>
                        </ul>
                    </li>
                    <li><a href="https://example.com">Aut illum aliquid</a></li>
                </ul>
            </li>
            <li><a href="https://example.com">Consequatur eius molestiae</a></li>
            <li><a href="https://example.com">Nulla rerum magni</a></li>
        </ul>
    </li>
    <li><a href="https://example.com">Aut illum aliquid</a>
        <ul>
            <li><a href="https://example.com">Voluptatem quia cum</a></li>
            <li><a href="https://example.com">Numquam sit veniam</a></li>
            <li><a href="https://example.com">Earum rem culpa</a>
                <ul>
                    <li><a href="https://example.com">Dolorum quod quia</a></li>
                    <li><a href="https://example.com">Quo illo molestiae</a></li>
                    <li><a href="https://example.com">Totam et amet</a></li>
                </ul>
            </li>
            <li><a href="https://example.com">Aliquid deleniti quae</a></li>
            <li><a href="https://example.com">Aut illum aliquid</a>
                <ul>
                    <li><a href="https://example.com">Consequatur eius molestiae</a></li>
                    <li><a href="https://example.com">Earum rem culpa</a></li>
                    <li><a href="https://example.com">Numquam sit veniam</a></li>
                    <li><a href="https://example.com">Voluptatem quia cum</a></li>
                    <li><a href="https://example.com">Totam et amet</a></li>
                    <li><a href="https://example.com">Quo illo molestiae</a></li>
                    <li><a href="https://example.com">Dolorum quod quia</a></li>
                    <li><a href="https://example.com">Aliquid deleniti quae</a></li>
                    <li><a href="https://example.com">Aut illum aliquid</a></li>
                </ul>
            </li>
        </ul>
    </li>
    <li data-megamenu-class="u-hidden">
        <a class="Button u-border-none u-color-95 u-background-compl u-text-r-xxs" href="#">Accedi</a>
    </li>
</ul>
  • Content:
    /** @define Treeview; weak; */
    
    :root {
      --Treeview-link-minWidth: 4em;
      --Treeview-link-maxWidth: 6em;
      --Treeview-link-padding: 2em;
      --Treeview-link-handler-width: 15%;
      --Treeview-link-arrow-width: 1em;
      --Treeview-link-arrow-down: inline("icons/img/SVG/expand.svg");
      --Treeview-link-arrow-up: inline("icons/img/SVG/collapse.svg");
      --Treeview-link-plus: inline("icons/img/SVG/plus.svg");
      --Treeview-link-minus: inline("icons/img/SVG/minus.svg");
    }
    
    .Treeview-handler--default::after,
    .Treeview-handler--standalone {
      background-image: var(--Treeview-link-arrow-down);
      background-repeat: no-repeat;
      background-size: var(--Treeview-link-arrow-width);
    }
    
    .Treeview--plus > li > .Treeview-handler--default::after,
    .Treeview--plus > li > a > .Treeview-handler--standalone {
      background-image: var(--Treeview-link-plus);
    }
    
    .Treeview-handler--default::after,
    .Treeview-handler--standalone {
      @extend .u-zindex-30;
      @extend .u-posAbsolute;
      @extend .u-block;
      @extend .u-text-r-xs;
    
      background-position: center;
      content: "";
      height: 100%;
      max-width: var(--Treeview-link-maxWidth);
      min-width: var(--Treeview-link-minWidth);
      right: 0;
      top: 0;
      width: var(--Treeview-link-handler-width);
    }
    
    .Treeview-handler--standalone {
      border-left: 1px solid var(--Color-grey-30);
    }
    
    .Treeview [aria-expanded="true"] > .Treeview-handler--default::after,
    .Treeview [aria-expanded="true"] > a > .Treeview-handler--standalone {
      background-image: var(--Treeview-link-arrow-up);
    }
    
    .Treeview--plus [aria-expanded="true"] > .Treeview-handler--default::after,
    .Treeview--plus [aria-expanded="true"] > a > .Treeview-handler--standalone {
      background-image: var(--Treeview-link-minus);
    }
    
    .Treeview [aria-hidden="true"] {
      display: none;
    }
    
    .Treeview li {
      cursor: pointer;
    }
    
    .Treeview-parent > a {
      @extend .u-posRelative;
    
      padding-right: calc(var(--Treeview-link-padding) * 2);
    }
    
    /*
     * Treeview default style
     * -----------------------
     */
    
    .Treeview--default li[aria-expanded=true] li a,
    .Treeview--default li[aria-expanded=true] li a:hover {
      @extend .u-color-95;
      @extend .u-background-grey-20;
    }
    
    .Treeview--default li[aria-expanded=true] li li a,
    .Treeview--default li[aria-expanded=true] li li a:hover {
      @extend .u-color-95;
      @extend .u-background-grey-30;
    }
    
  • URL: /components/raw/treeview/index.css
  • Filesystem Path: src/components/treeview/index.css
  • Size: 2.3 KB
  • Content:
    import $ from 'jquery'
    
    /*
     *	TODO:
     *		- repack as a frend _component and CSS
     *		- refactor without jQuery
     */
    
    /*
     * Porting of http://www.oaa-accessibility.org/examplep/treeview1/
     */
    const Treeview = function({
      selector: selector = '.js-Treeview',
      classFocused: classFocused = 'hasFocus',
      classParent: classParent = 'Treeview-parent',
      classMenuHandler: classMenuHandler = 'js-Treeview-handler',
      styleMenuHandler: styleMenuHandler = 'Treeview-handler--default',
      styleMenuHandlerStandalone: styleMenuHandlerStandalone = 'Treeview-handler--standalone',
      ariaLabelHandler: ariaLabelHandler = 'expand',
      multiselectable: multiselectable = true,
      animationMs: animationMs = 100,
    } = {}) {
    
      const keys = {
        tab: 9,
        enter: 13,
        space: 32,
        pageup: 33,
        pagedown: 34,
        end: 35,
        home: 36,
        left: 37,
        up: 38,
        right: 39,
        down: 40,
        asterisk: 106
      }
    
      function _collapseAll(treeview) {
        treeview.$parents.each(function() {
          if ($(this).attr('aria-expanded') == 'false') {
            $(this).children('ul').attr('aria-hidden', 'true')
          }
        })
        treeview.$visibleItems = treeview.$el.find('li:visible')
      }
    
      function _expandGroup(treeview, $item) {
        let $group = $item.children('ul')
        $group.slideDown(animationMs, () => {
          $group.attr('aria-hidden', 'false')
          $item.attr('aria-expanded', 'true')
          treeview.$visibleItems = treeview.$el.find('li:visible')
        })
      }
    
      function _collapseGroup(treeview, $item) {
        let $group = $item.children('ul')
        $group.slideUp(animationMs, () => {
          $group.attr('aria-hidden', 'true')
          $item.attr('aria-expanded', 'false')
          treeview.$visibleItems = treeview.$el.find('li:visible')
        })
      }
    
      function _collapseSiblings(treeview, $item) {
        $item.closest('ul')
          .find('> .' + classParent)
          .not($item)
          .each((i, el) => {
            _collapseGroup(treeview, $(el))
          })
      }
    
      function _toggleGroup(treeview, $item) {
        if (!multiselectable) {
          _collapseSiblings(treeview, $item)
        }
        if ($item.attr('aria-expanded') == 'true') {
          _collapseGroup(treeview, $item)
        } else {
          _expandGroup(treeview, $item)
        }
      }
    
      function _updateStyling(treeview, $item) {
        treeview.$items.removeClass(classFocused)
        $item.addClass(classFocused)
      }
    
      function _handleKeyDown(treeview, $item, e) {
        let curNdx = treeview.$visibleItems.index($item)
    
        if ((e.altKey || e.ctrlKey) ||
          (e.shiftKey && e.keyCode != keys.tab)) {
          return true
        }
    
        // if (!$(e.currentTarget).is('.' + classMenuHandler)) {
        //   return true
        // }
    
        switch (e.keyCode) {
          case keys.tab:
            {
              treeview.$activeItem = null
              $item.removeClass(classFocused)
              return true
            }
    
          case keys.home:
            {
              treeview.$activeItem = treeview.$parents.first()
              treeview.$activeItem.find(':focusable:first').focus()
              e.stopPropagation()
              return false
            }
    
          case keys.end:
            {
              treeview.$activeItem = treeview.$visibleItems.last()
              treeview.$activeItem.find(':focusable:first').focus()
              e.stopPropagation()
              return false
            }
    
          case keys.enter:
          case keys.space:
            {
              if ($(e.currentTarget).is('.' + classMenuHandler)) {
                _toggleGroup(treeview, $item)
                e.stopPropagation()
                return false
              }
              return true
            }
    
          case keys.left:
            {
              if ($item.is('.' + classParent) && $item.attr('aria-expanded') == 'true') {
                _collapseGroup(treeview, $item)
              } else {
                let $itemUL = $item.parent()
                let $itemParent = $itemUL.parent()
                treeview.$activeItem = $itemParent
                treeview.$activeItem.find(':focusable:first').focus()
              }
              e.stopPropagation()
              return false
            }
    
          case keys.right:
            {
              if (!$item.is('.' + classParent)) {
                // do nothing
              } else if ($item.attr('aria-expanded') == 'false') {
                _expandGroup(treeview, $item)
              } else {
                treeview.$activeItem = $item.children('ul').children('li').first()
                treeview.$activeItem.find(':focusable:first').focus()
              }
              e.stopPropagation()
              return false
            }
    
          case keys.up:
            {
              if (curNdx > 0) {
                let $prev = treeview.$visibleItems.eq(curNdx - 1)
                treeview.$activeItem = $prev
                $prev.find(':focusable:first').focus()
              }
              e.stopPropagation()
              return false
            }
    
          case keys.down:
            {
              if (curNdx < treeview.$visibleItems.length - 1) {
                let $next = treeview.$visibleItems.eq(curNdx + 1)
                treeview.$activeItem = $next
                $next.find(':focusable:first').focus()
              }
              e.stopPropagation()
              return false
            }
    
          case keys.asterisk:
            {
              treeview.$parents.each(function() {
                _expandGroup(treeview, $(this))
              })
              e.stopPropagation()
              return false
            }
    
        }
        return true
      }
    
      function _handleKeyPress(treeview, $item, e) {
        if (e.altKey || e.ctrlKey || e.shiftKey) {
          // do nothing
          return true
        }
    
        switch (e.keyCode) {
          case keys.tab:
            {
              return true
            }
          case keys.enter:
          case keys.home:
          case keys.end:
          case keys.left:
          case keys.right:
          case keys.up:
          case keys.down:
            {
              e.stopPropagation()
              return false
            }
          default:
            {
              let chr = String.fromCharCode(e.which)
              let bMatch = false
              let itemNdx = treeview.$visibleItems.index($item)
              let itemCnt = treeview.$visibleItems.length
              let curNdx = itemNdx + 1
    
              // check if the active item was the last one on the list
              if (curNdx == itemCnt) {
                curNdx = 0
              }
    
              // Iterate through the menu items (starting from the current item and wrapping) until a match is found
              // or the loop returns to the current menu item
              while (curNdx != itemNdx) {
    
                let $curItem = treeview.$visibleItems.eq(curNdx)
                let titleChr = $curItem.text().charAt(0)
    
                if ($curItem.is('.' + classParent)) {
                  titleChr = $curItem.find('span').text().charAt(0)
                }
    
                if (titleChr.toLowerCase() == chr) {
                  bMatch = true
                  break
                }
    
                curNdx = curNdx + 1
    
                if (curNdx == itemCnt) {
                  // reached the end of the list, start again at the beginning
                  curNdx = 0
                }
              }
    
              if (bMatch == true) {
                treeview.$activeItem = treeview.$visibleItems.eq(curNdx)
                treeview.$activeItem.find(':focusable:first').focus()
              }
    
              e.stopPropagation()
              return false
            }
        }
    
        return true
      }
    
      function _handleClick(treeview, $item, e) {
        if (e.altKey || e.ctrlKey || e.shiftKey) {
          // do nothing
          return true
        }
    
        // closest('li')
        const $parent = $item.closest('li')
    
        treeview.$activeItem = $parent
        _updateStyling(treeview, $parent)
        _toggleGroup(treeview, $parent)
    
        e.stopPropagation()
        return false
      }
    
      function _bindEvents(treeview) {
        treeview.$handlers.click(function(e) {
          return _handleClick(treeview, $(this), e)
        })
    
        treeview.$items.keydown(function(e) {
          return _handleKeyDown(treeview, $(this), e)
        })
    
        treeview.$items.keypress(function(e) {
          return _handleKeyPress(treeview, $(this), e)
        })
    
        treeview.$handlers.keydown(function(e) {
          return _handleKeyDown(treeview, $(this).closest('li'), e)
        })
    
        treeview.$handlers.keypress(function(e) {
          return _handleKeyPress(treeview, $(this).closest('li'), e)
        })
    
        $(document).click(function() {
          if (treeview.$activeItem != null) {
            treeview.$activeItem.removeClass(classFocused)
            treeview.$activeItem = null
          }
          return true
        })
      }
    
      function destroy() {
        /* TODO */
      }
    
      function _addA11y($el) {
        $el.attr('role', 'tree')
    
        // Put role="treeitem" on every LI
        // Put aria-expanded="false" on every LI (if it has no aria-expanded attr)
        // Put tabindex="-1" on every LI (if it's not the first one)
        // Put class=<classParent> on every LI that contains an UL
        $el.find('li').each(function(i, li) {
          const $li = $(li)
          $li
            .attr('role', 'treeitem')
          // .attr('tabindex', (0 === i) ? '0' : '-1')
          //  .find('a[href]').not('[href^=#]').attr('tabindex', 0)
          //  .parent().attr('aria-label', function() { return $(this).text() })
          if ($li.find('ul').length !== 0) {
    
            $li.children('a').not("[href='#']")
              .append(`<span class="${classMenuHandler} ${styleMenuHandlerStandalone}"
                  aria-label="${ariaLabelHandler}" role="button" tabindex="0"></span>`)
    
            $li.children("a[href='#']")
              .addClass(classMenuHandler)
              .addClass(styleMenuHandler)
              .attr('aria-label', ariaLabelHandler)
              .attr('role', 'button')
    
            const containsExpandedLink =
              $li.find('[aria-expanded=true]').length > 0
              || $li.find('.is-current').length > 0
    
            if (!li.hasAttribute('aria-expanded') && !containsExpandedLink) {
              $li.attr('aria-expanded', 'false')
            }
            else if (containsExpandedLink) {
              $li.attr('aria-expanded', 'true')
            }
            $li.addClass(classParent)
          }
        })
        // Put role="group" on every contained UL
        $el.find('ul').attr('role', 'group')
      }
    
      function init() {
        $(selector).each((_, treeviewContainer) => {
          const $el = $(treeviewContainer)
          _addA11y($el)
          let treeview = {
            $el: $el,
            $items: $el.find('li'),
            $parents: $el.find('.' + classParent),
            $handlers: $el.find('.' + classMenuHandler),
            $visibleItems: null,
            $activeItem: null
          }
          _collapseAll(treeview)
          _bindEvents(treeview)
        })
      }
    
      init()
    
      // REVEAL API
      return {
        init,
        destroy
      }
    
    }
    
    new Treeview()
    
    export default {
      Treeview
    }
    
  • URL: /components/raw/treeview/index.js
  • Filesystem Path: src/components/treeview/index.js
  • Size: 10.4 KB

There are no notes for this item.