|
// Requires `ion-icon` web component |
|
|
|
const css = ` |
|
<style> |
|
:host([hidden]) { display: none; } |
|
:host { |
|
--background-color: #fff; |
|
--border-color: #eee; |
|
--link-color: dodgerblue; |
|
--link-padding: 8px; |
|
--top-offset: 0; |
|
--icon-size: 24px; |
|
display: block; |
|
height: var(--icon-size); |
|
} |
|
|
|
a { |
|
color: var(--link-color); |
|
text-decoration: none; |
|
} |
|
|
|
ion-icon { |
|
font-size: var(--icon-size); |
|
} |
|
|
|
nav { |
|
background-color: var(--background-color); |
|
border: 1px solid var(--border-color); |
|
box-sizing: border-box; |
|
display: flex; |
|
flex-direction: column; |
|
position: absolute; |
|
top: var(--top-offset); |
|
left: 0; |
|
visibility: hidden; |
|
width: 100vw; |
|
} |
|
|
|
::slotted(a) { |
|
border-top: 1px solid var(--border-color); |
|
border-bottom: 1px solid var(--border-color); |
|
font-weight: bold; |
|
padding: var(--link-padding) !important; |
|
white-space: nowrap; |
|
width: 100%; |
|
} |
|
</style> |
|
` |
|
|
|
const html = ` |
|
<a href="#"> |
|
<ion-icon name="menu"></ion-icon> |
|
</a> |
|
<nav> |
|
<slot><p>Empty mobile navigation</p></slot> |
|
</nav> |
|
` |
|
|
|
class MobileNav extends HTMLElement { |
|
static get observedAttributes () { |
|
return ['open'] |
|
} |
|
|
|
constructor () { |
|
super() |
|
|
|
this.attachShadow({mode: 'open'}) |
|
this.shadowRoot.appendChild(template.content.cloneNode(true)) |
|
|
|
this.readyToClose = false |
|
this.openMenu = (event) => { |
|
const debounceDelay = 100 |
|
|
|
event.preventDefault() |
|
if (this.hasAttribute('open')) { |
|
return |
|
} |
|
this.setAttribute('open', '') |
|
|
|
setTimeout(() => { this.readyToClose = true }, debounceDelay) |
|
} |
|
this.closeMenu = () => { |
|
if (this.readyToClose) { |
|
this.removeAttribute('open') |
|
this.readyToClose = false |
|
} |
|
} |
|
} |
|
|
|
connectedCallback () { |
|
document.addEventListener('click', this.closeMenu) |
|
this.shadowRoot.querySelector('a').addEventListener('click', this.openMenu) |
|
} |
|
disconnectedCallback () { |
|
document.removeEventListener('click', this.closeMenu) |
|
this.shadowRoot.querySelector('a').removeEventListener('click', this.openMenu) |
|
} |
|
attributeChangedCallback (name) { |
|
if (name === 'open') { |
|
const $nav = this.shadowRoot.querySelector('nav') |
|
|
|
if (this.hasAttribute('open')) { |
|
$nav.style.setProperty('visibility', 'visible') |
|
} else { |
|
$nav.style.setProperty('visibility', 'hidden') |
|
} |
|
} |
|
} |
|
|
|
get open () { |
|
return this.getAttribute('open') |
|
} |
|
set open (value) { |
|
this.setAttribute('open', value) |
|
} |
|
} |
|
|
|
const template = document.createElement('template') |
|
template.innerHTML = `${css}${html}` |
|
|
|
window.customElements.define('mobile-nav', MobileNav) |