standard-ui/components/dist/popup.js
2021-08-06 15:47:03 +05:30

373 lines
12 KiB
JavaScript

const smPopup = document.createElement('template')
smPopup.innerHTML = `
<style>
*{
padding: 0;
margin: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
:host{
position: fixed;
display: -ms-grid;
display: grid;
z-index: 10;
--accent-color: #4d2588;
--text-color: 17, 17, 17;
--background-color: 255, 255, 255;
--width: 100%;
--height: auto;
--min-width: auto;
--min-height: auto;
--body-padding: 1.5rem;
--backdrop-background: rgba(0, 0, 0, 0.6);
--border-radius: 0.8rem 0.8rem 0 0;
}
.popup-container{
display: -ms-grid;
display: grid;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
place-items: center;
background: var(--backdrop-background);
-webkit-transition: opacity 0.3s;
-o-transition: opacity 0.3s;
transition: opacity 0.3s;
z-index: 10;
touch-action: none;
}
:host(.stacked) .popup{
-webkit-transform: scale(0.9) translateY(-2rem) !important;
transform: scale(0.9) translateY(-2rem) !important;
}
.popup{
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex-direction: column;
position: relative;
-ms-flex-item-align: end;
align-self: flex-end;
-webkit-box-align: start;
-ms-flex-align: start;
align-items: flex-start;
width: var(--width);
min-width: var(--min-width);
height: var(--height);
min-height: var(--min-height);
max-height: 90vh;
border-radius: var(--border-radius);
-webkit-transform: scale(1) translateY(100%);
transform: scale(1) translateY(100%);
-webkit-transition: -webkit-transform 0.3s;
transition: -webkit-transform 0.3s;
-o-transition: transform 0.3s;
transition: transform 0.3s, -webkit-transform 0.3s;
transition: transform 0.3s;
background: rgba(var(--background-color), 1);
-webkit-box-shadow: 0 -1rem 2rem #00000020;
box-shadow: 0 -1rem 2rem #00000020;
content-visibility: auto;
}
.container-header{
display: -webkit-box;
display: flex;
width: 100%;
touch-action: none;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.popup-top{
display: -webkit-box;
display: flex;
width: 100%;
}
.popup-body{
display: -webkit-box;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
width: 100%;
padding: var(--body-padding);
overflow-y: auto;
}
.hide{
opacity: 0;
pointer-events: none;
visiblity: none;
}
@media screen and (min-width: 640px){
:host{
--border-radius: 0.4rem;
}
.popup{
-ms-flex-item-align: center;
-ms-grid-row-align: center;
align-self: center;
border-radius: var(--border-radius);
height: var(--height);
-webkit-transform: scale(1) translateY(3rem);
transform: scale(1) translateY(3rem);
-webkit-box-shadow: 0 3rem 2rem -0.5rem #00000040;
box-shadow: 0 3rem 2rem -0.5rem #00000040;
}
}
@media screen and (max-width: 640px){
.popup-top{
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex-direction: column;
-webkit-box-align: center;
align-items: center;
}
.handle{
height: 0.3rem;
width: 2rem;
background: rgba(var(--text-color), .4);
border-radius: 1rem;
margin: 0.5rem 0;
}
}
@media (any-hover: hover){
::-webkit-scrollbar{
width: 0.5rem;
}
::-webkit-scrollbar-thumb{
background: rgba(var(--text-color), 0.3);
border-radius: 1rem;
&:hover{
background: rgba(var(--text-color), 0.5);
}
}
}
</style>
<div part="background" class="popup-container hide" role="dialog">
<div part="popup" class="popup">
<div part="popup-header" class="popup-top">
<div class="handle"></div>
<slot name="header"></slot>
</div>
<div part="popup-body" class="popup-body">
<slot></slot>
</div>
</div>
</div>
`;
customElements.define('sm-popup', class extends HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
}).append(smPopup.content.cloneNode(true))
this.allowClosing = false
this.isOpen = false
this.pinned = false
this.popupStack
this.offset
this.touchStartY = 0
this.touchEndY = 0
this.touchStartTime = 0
this.touchEndTime = 0
this.touchEndAnimataion
this.popupContainer = this.shadowRoot.querySelector('.popup-container')
this.popup = this.shadowRoot.querySelector('.popup')
this.popupBodySlot = this.shadowRoot.querySelector('.popup-body slot')
this.popupHeader = this.shadowRoot.querySelector('.popup-top')
this.resumeScrolling = this.resumeScrolling.bind(this)
this.show = this.show.bind(this)
this.hide = this.hide.bind(this)
this.handleTouchStart = this.handleTouchStart.bind(this)
this.handleTouchMove = this.handleTouchMove.bind(this)
this.handleTouchEnd = this.handleTouchEnd.bind(this)
this.movePopup = this.movePopup.bind(this)
}
static get observedAttributes() {
return ['open'];
}
get open() {
return this.isOpen
}
resumeScrolling() {
const scrollY = document.body.style.top;
window.scrollTo(0, parseInt(scrollY || '0') * -1);
setTimeout(() => {
document.body.style.overflow = 'auto';
document.body.style.top = 'initial'
}, 300);
}
show(options = {}) {
const {pinned = false, popupStack = undefined} = options
if (popupStack)
this.popupStack = popupStack
if (this.popupStack && !this.hasAttribute('open')) {
this.popupStack.push({
popup: this,
permission: pinned
})
if (this.popupStack.items.length > 1) {
this.popupStack.items[this.popupStack.items.length - 2].popup.classList.add('stacked')
}
this.dispatchEvent(
new CustomEvent("popupopened", {
bubbles: true,
detail: {
popup: this,
popupStack: this.popupStack
}
})
)
this.setAttribute('open', '')
this.pinned = pinned
this.isOpen = true
}
this.popupContainer.classList.remove('hide')
this.popup.style.transform = 'none';
document.body.style.overflow = 'hidden';
document.body.style.top = `-${window.scrollY}px`
return this.popupStack
}
hide() {
if (window.innerWidth < 640)
this.popup.style.transform = 'translateY(100%)';
else
this.popup.style.transform = 'translateY(3rem)';
this.popupContainer.classList.add('hide')
this.removeAttribute('open')
if (typeof this.popupStack !== 'undefined') {
this.popupStack.pop()
if (this.popupStack.items.length) {
this.popupStack.items[this.popupStack.items.length - 1].popup.classList.remove('stacked')
} else {
this.resumeScrolling()
}
} else {
this.resumeScrolling()
}
if (this.forms.length) {
setTimeout(() => {
this.forms.forEach(form => form.reset())
}, 300);
}
setTimeout(() => {
this.dispatchEvent(
new CustomEvent("popupclosed", {
bubbles: true,
detail: {
popup: this,
popupStack: this.popupStack
}
})
)
this.isOpen = false
}, 300);
}
handleTouchStart(e) {
this.touchStartY = e.changedTouches[0].clientY
this.popup.style.transition = 'transform 0.1s'
this.touchStartTime = e.timeStamp
}
handleTouchMove(e) {
if (this.touchStartY < e.changedTouches[0].clientY) {
this.offset = e.changedTouches[0].clientY - this.touchStartY;
this.touchEndAnimataion = window.requestAnimationFrame(() => this.movePopup())
}
}
handleTouchEnd(e) {
this.touchEndTime = e.timeStamp
cancelAnimationFrame(this.touchEndAnimataion)
this.touchEndY = e.changedTouches[0].clientY
this.popup.style.transition = 'transform 0.3s'
this.threshold = this.popup.getBoundingClientRect().height * 0.3
if (this.touchEndTime - this.touchStartTime > 200) {
if (this.touchEndY - this.touchStartY > this.threshold) {
if (this.pinned) {
this.show()
return
} else
this.hide()
} else {
this.show()
}
} else {
if (this.touchEndY > this.touchStartY)
if (this.pinned) {
this.show()
return
}
else
this.hide()
}
}
movePopup() {
this.popup.style.transform = `translateY(${this.offset}px)`
}
connectedCallback() {
this.popupBodySlot.addEventListener('slotchange', () => {
this.forms = this.querySelectorAll('sm-form')
})
this.popupContainer.addEventListener('mousedown', e => {
if (e.target === this.popupContainer && !this.pinned) {
if (this.pinned) {
this.show()
} else
this.hide()
}
})
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.contentBoxSize) {
// Firefox implements `contentBoxSize` as a single content rect, rather than an array
const contentBoxSize = Array.isArray(entry.contentBoxSize) ? entry.contentBoxSize[0] : entry.contentBoxSize;
this.threshold = contentBoxSize.blockSize.height * 0.3
} else {
this.threshold = entry.contentRect.height * 0.3
}
}
});
resizeObserver.observe(this)
this.popupHeader.addEventListener('touchstart', (e) => { this.handleTouchStart(e) }, { passive: true })
this.popupHeader.addEventListener('touchmove', (e) => { this.handleTouchMove(e) }, { passive: true })
this.popupHeader.addEventListener('touchend', (e) => { this.handleTouchEnd(e) }, { passive: true })
}
disconnectedCallback() {
this.popupHeader.removeEventListener('touchstart', this.handleTouchStart, { passive: true })
this.popupHeader.removeEventListener('touchmove', this.handleTouchMove, { passive: true })
this.popupHeader.removeEventListener('touchend', this.handleTouchEnd, { passive: true })
resizeObserver.unobserve()
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === 'open') {
if (this.hasAttribute('open')) {
this.show()
}
}
}
})