01-nav.js 8.56 KB
Newer Older
hyeryung's avatar
hyeryung committed
1 2 3 4 5 6 7 8
;(function () {
  'use strict'

  var SECT_CLASS_RX = /^sect(\d)$/

  var navContainer = document.querySelector('.nav-container')
  if (!navContainer) return
  var navToggle = document.querySelector('.nav-toggle')
박민석's avatar
박민석 committed
9
  var panelToggle = document.getElementById('collapse-panel-btn')
hyeryung's avatar
hyeryung committed
10 11 12 13 14 15 16 17 18
  var nav = navContainer.querySelector('.nav')
  var navMenuToggle = navContainer.querySelector('.nav-menu-toggle')

  navToggle.addEventListener('click', showNav)
  navContainer.addEventListener('click', trapEvent)

  var menuPanel = navContainer.querySelector('[data-panel=menu]')
  if (!menuPanel) return
  var explorePanel = navContainer.querySelector('[data-panel=explore]')
19 20 21
  var backdrop = explorePanel.querySelector('.backdrop')
  var versionMenu = explorePanel.querySelector('.version-menu')
  var versions = versionMenu?.querySelectorAll('.component')
hyeryung's avatar
hyeryung committed
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

  var currentPageItem = menuPanel.querySelector('.is-current-page')
  var originalPageItem = currentPageItem
  if (currentPageItem) {
    activateCurrentPath(currentPageItem)
    scrollItemToMidpoint(menuPanel, currentPageItem.querySelector('.nav-link'))
  } else {
    menuPanel.scrollTop = 0
  }

  find(menuPanel, '.nav-item-toggle').forEach(function (btn) {
    var li = btn.parentElement
    btn.addEventListener('click', toggleActive.bind(li))
    var navItemSpan = findNextElement(btn, '.nav-text')
    if (navItemSpan) {
      navItemSpan.style.cursor = 'pointer'
      navItemSpan.addEventListener('click', toggleActive.bind(li))
    }
  })

minseok.park's avatar
minseok.park committed
42 43 44 45 46
  find(menuPanel, '.nav-link').forEach(function (link) {
    link.addEventListener('click', function (e) {
      var li = this.parentElement
      var currentDepth = parseInt(li.dataset.depth, 10) // 현재 nav-item의 depth 가져오기

minseok.park's avatar
minseok.park committed
47 48 49 50 51
      // 이미 'is-active' 클래스가 있거나 현재 navItem이 currentPageItem과 같은 경우 액션 수행 안 함
      if (li.classList.contains('is-active') || li === currentPageItem) {
        return // 액션을 수행하지 않고 종료
      }

minseok.park's avatar
minseok.park committed
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
      // href가 '#'인 경우 (클릭한 항목이 단순 펼침 액션 대상일 때)
      if (this.getAttribute('href') === '#') {
        // 다음 depth의 항목을 찾음 (현재 depth + 1)
        var nextDepth = currentDepth + 1
        var subNavList = li.querySelector('.nav-list') // 하위 리스트를 찾음
        var firstSubNavItem = subNavList
          ? subNavList.querySelector('.nav-item[data-depth="' + nextDepth + '"] .nav-link')
          : null

        // 다음 depth의 항목이 있을 경우
        if (firstSubNavItem && firstSubNavItem.href) {
          e.preventDefault() // 기본 동작 방지
          toggleActive.call(li) // 펼쳐짐 액션 수행
          window.location = firstSubNavItem.href // 다음 depth의 첫 항목으로 이동
        } else {
          toggleActive.call(li) // 다음 depth 항목이 없을 경우 펼쳐짐 액션만 수행
        }
      } else {
        toggleActive.call(li) // href가 '#'이 아닌 경우 그냥 펼쳐짐 액션만 수행
      }
    })
  })
minseok.park's avatar
minseok.park committed
74

hyeryung's avatar
hyeryung committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
  if (navMenuToggle && menuPanel.querySelector('.nav-item-toggle')) {
    navMenuToggle.style.display = ''
    navMenuToggle.addEventListener('click', function () {
      var collapse = !this.classList.toggle('is-active')
      find(menuPanel, '.nav-item > .nav-item-toggle').forEach(function (btn) {
        collapse ? btn.parentElement.classList.remove('is-active') : btn.parentElement.classList.add('is-active')
      })
      if (currentPageItem) {
        if (collapse) activateCurrentPath(currentPageItem)
        scrollItemToMidpoint(menuPanel, currentPageItem.querySelector('.nav-link'))
      } else {
        menuPanel.scrollTop = 0
      }
    })
  }

  if (explorePanel) {
92
    explorePanel.querySelector('.version-menu-toggle').addEventListener('click', function (e) {
hyeryung's avatar
hyeryung committed
93
      // NOTE logic assumes there are only two panels
94 95 96 97 98 99 100 101
      // find(nav, '[data-panel]').forEach(function (panel) {
      //   panel.classList.toggle('is-active')
      // })
      if (!versions.length) return

      versionMenu.classList.toggle('is-active')
      backdrop.classList.toggle('is-active')
      e.stopPropagation() // trap event
hyeryung's avatar
hyeryung committed
102 103 104
    })
  }

105 106 107 108 109 110 111 112 113 114
  backdrop.addEventListener('click', function () {
    versionMenu.classList.remove('is-active')
    backdrop.classList.remove('is-active')
  })

  document.documentElement.addEventListener('click', function () {
    versionMenu.classList.remove('is-active')
    backdrop.classList.remove('is-active')
  })

hyeryung's avatar
hyeryung committed
115 116 117 118 119
  // NOTE prevent text from being selected by double click
  menuPanel.addEventListener('mousedown', function (e) {
    if (e.detail > 1) e.preventDefault()
  })

박민석's avatar
박민석 committed
120 121 122 123
  panelToggle.addEventListener('click', () => {
    navContainer.classList.remove('is-active')
  })

hyeryung's avatar
hyeryung committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
  function onHashChange () {
    var navLink
    var hash = window.location.hash
    if (hash) {
      if (hash.indexOf('%')) hash = decodeURIComponent(hash)
      navLink = menuPanel.querySelector('.nav-link[href="' + hash + '"]')
      if (!navLink) {
        var targetNode = document.getElementById(hash.slice(1))
        if (targetNode) {
          var current = targetNode
          var ceiling = document.querySelector('article.doc')
          while ((current = current.parentNode) && current !== ceiling) {
            var id = current.id
            // NOTE: look for section heading
            if (!id && (id = SECT_CLASS_RX.test(current.className))) id = (current.firstElementChild || {}).id
            if (id && (navLink = menuPanel.querySelector('.nav-link[href="#' + id + '"]'))) break
          }
        }
      }
    }
    var navItem
    if (navLink) {
      navItem = navLink.parentNode
    } else if (originalPageItem) {
      navLink = (navItem = originalPageItem).querySelector('.nav-link')
    } else {
      return
    }
박민석's avatar
박민석 committed
152 153 154 155 156 157 158
    if (navItem === currentPageItem) return
    find(menuPanel, '.nav-item.is-active').forEach(function (el) {
      el.classList.remove('is-active', 'is-current-path', 'is-current-page')
    })
    navItem.classList.add('is-current-page')
    currentPageItem = navItem
    activateCurrentPath(navItem)
hyeryung's avatar
hyeryung committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
    scrollItemToMidpoint(menuPanel, navLink)
  }

  if (menuPanel.querySelector('.nav-link[href^="#"]')) {
    if (window.location.hash) onHashChange()
    window.addEventListener('hashchange', onHashChange)
  }

  function activateCurrentPath (navItem) {
    var ancestorClasses
    var ancestor = navItem.parentNode
    while (!(ancestorClasses = ancestor.classList).contains('nav-menu')) {
      if (ancestor.tagName === 'LI' && ancestorClasses.contains('nav-item')) {
        ancestorClasses.add('is-active', 'is-current-path')
      }
      ancestor = ancestor.parentNode
    }
    navItem.classList.add('is-active')
  }

  function toggleActive () {
    if (this.classList.toggle('is-active')) {
      var padding = parseFloat(window.getComputedStyle(this).marginTop)
      var rect = this.getBoundingClientRect()
      var menuPanelRect = menuPanel.getBoundingClientRect()
      var overflowY = (rect.bottom - menuPanelRect.top - menuPanelRect.height + padding).toFixed()
      if (overflowY > 0) menuPanel.scrollTop += Math.min((rect.top - menuPanelRect.top - padding).toFixed(), overflowY)
    }
  }

  function showNav (e) {
    if (navToggle.classList.contains('is-active')) return hideNav(e)
minseok.park's avatar
minseok.park committed
191 192 193
    // trapEvent(e)
    // var html = document.documentElement
    // html.classList.add('is-clipped--nav')
박민석's avatar
박민석 committed
194
    // navToggle.classList.add('is-active')
hyeryung's avatar
hyeryung committed
195
    navContainer.classList.add('is-active')
minseok.park's avatar
minseok.park committed
196 197 198 199
    // var bounds = nav.getBoundingClientRect()
    // var expectedHeight = window.innerHeight - Math.round(bounds.top)
    // if (Math.round(bounds.height) !== expectedHeight) nav.style.height = expectedHeight + 'px'
    // html.addEventListener('click', hideNav)
hyeryung's avatar
hyeryung committed
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
  }

  function hideNav (e) {
    trapEvent(e)
    var html = document.documentElement
    html.classList.remove('is-clipped--nav')
    navToggle.classList.remove('is-active')
    navContainer.classList.remove('is-active')
    html.removeEventListener('click', hideNav)
  }

  function trapEvent (e) {
    e.stopPropagation()
  }

  function scrollItemToMidpoint (panel, el) {
    var rect = panel.getBoundingClientRect()
    var effectiveHeight = rect.height
minseok.park's avatar
minseok.park committed
218 219
    var navStyle = window.getComputedStyle(nav)
    if (navStyle.position === 'sticky') effectiveHeight -= rect.top - parseFloat(navStyle.top)
hyeryung's avatar
hyeryung committed
220 221 222 223 224 225 226 227 228 229 230 231
    panel.scrollTop = Math.max(0, (el.getBoundingClientRect().height - effectiveHeight) * 0.5 + el.offsetTop)
  }

  function find (from, selector) {
    return [].slice.call(from.querySelectorAll(selector))
  }

  function findNextElement (from, selector) {
    var el = from.nextElementSibling
    return el && selector ? el[el.matches ? 'matches' : 'msMatchesSelector'](selector) && el : el
  }
})()