pinafore/routes/_components/compose/ComposeStickyButton.html
Nolan Lawson b22a1ec90c
fix(iOS): fix faux-sticky button placement (#715)
hopefully the final fix for #667 to make it actually work
2018-12-02 15:57:39 -08:00

176 lines
5.8 KiB
HTML

<div class="compose-box-button-sentinel {hideAndFadeIn}" ref:sentinel></div>
<div class="compose-box-button-wrapper {showSticky ? 'compose-box-button-sticky' : ''} {hideAndFadeIn}"
ref:wrapper >
<ComposeButton {overLimit} {sticky} on:click="onClickButton()" />
</div>
<style>
.compose-box-button-wrapper {
/*
* We want pointer-events only for the sticky button, so use fit-content so that
* the element doesn't take up the full width, and then set its left margin to
* auto so that it sticks to the right. fit-content doesn't work in Edge, but
* that just means that content that is level with the button is not clickable.
*/
width: -moz-fit-content;
width: fit-content;
margin-left: auto;
display: flex;
justify-content: flex-end;
}
.compose-box-button-sticky {
position: -webkit-sticky;
position: sticky;
}
:global(.compose-box-button-sticky, .compose-box-button-fixed) {
z-index: 5000;
top: 52px; /* padding-top for .main-content plus 10px */
}
@media (max-width: 767px) {
:global(.compose-box-button-sticky, .compose-box-button-fixed) {
top: 57px; /* padding-top for .main-content plus 5px */
}
}
@media (max-width: 991px) {
:global(.compose-box-button-sticky, .compose-box-button-fixed) {
top: 62px; /* padding-top for .main-content plus 10px */
}
}
@supports (-webkit-overflow-scrolling: touch) {
.compose-box-button-sticky {
/* disable sticky positioning on iOS due to
https://github.com/nolanlawson/pinafore/issues/667 */
position: relative;
z-index: 0;
top: 0;
}
}
</style>
<script>
import ComposeButton from './ComposeButton.html'
import { store } from '../../_store/store'
import { importShowComposeDialog } from '../dialog/asyncDialogs'
import { observe } from 'svelte-extras'
const USE_IOS_WORKAROUND = process.browser && CSS.supports('-webkit-overflow-scrolling', 'touch')
export default {
oncreate () {
this.setupStickyObserver()
this.setupIOSWorkaround()
},
ondestroy () {
this.teardownStickyObserver()
},
store: () => store,
data: () => ({
sticky: false
}),
computed: {
timelineInitialized: ({ $timelineInitialized }) => $timelineInitialized
},
methods: {
observe,
onClickButton () {
let { sticky } = this.get()
if (sticky) {
// when the button is sticky, we're scrolled down the home timeline,
// so we should launch a new compose dialog
this.showDialog()
} else {
// else we're actually posting a new toot, let our parent know
this.fire('postAction')
}
},
async showDialog () {
(await importShowComposeDialog())()
},
setupIOSWorkaround () {
// This is an elaborate fix for https://github.com/nolanlawson/pinafore/issues/667
// We detect iOS using support for -webkit-overflow-scrolling: touch
// (both here and in global.scss). Then, we set the main content element
// to be overflow-x: hidden, which normally would break the sticky button
// because its parent is now the scrolling context. So for iOS only, we
// create a fake sticky button by listening to intersecting events
// and inserting a permanently fixed-position element into the DOM.
let { showSticky } = this.get()
if (!USE_IOS_WORKAROUND || !showSticky) {
return
}
let cleanup = () => {
let existingElement = document.getElementById('the-sticky-button')
if (existingElement) {
document.body.removeChild(existingElement)
}
if (this.__fixedStickyButton) {
this.__fixedStickyButton.destroy()
this.__fixedStickyButton = null
}
}
let createFixedStickyButton = () => {
let element = document.createElement('div')
element.setAttribute('id', 'the-sticky-button')
element.classList.add('compose-box-button-wrapper')
element.classList.add('compose-box-button-fixed')
document.body.appendChild(element)
let rect = this.refs.wrapper.getBoundingClientRect()
Object.assign(element.style, {
left: `${rect.left}px`,
position: 'fixed'
})
this.__fixedStickyButton = new ComposeButton({
target: element,
data: {
sticky: true,
overLimit: false
}
})
this.__fixedStickyButton.on('click', () => this.showDialog())
}
this.observe('sticky', sticky => {
cleanup()
if (sticky) {
createFixedStickyButton()
}
})
this.on('destroy', () => cleanup())
},
setupStickyObserver () {
let sentinel = this.refs.sentinel
this.__stickyObserver = new IntersectionObserver(entries => this.onObserve(entries))
this.__stickyObserver.observe(sentinel)
// also create a one-shot observer for the $timelineInitialized event,
// due to a bug in Firefox where when the scrollTop is set
// manually, the other observer doesn't necessarily fire
this.observe('timelineInitialized', timelineInitialized => {
if (timelineInitialized) {
let observer = new IntersectionObserver(entries => {
this.onObserve(entries)
observer.disconnect()
})
observer.observe(sentinel)
}
}, { init: false })
},
onObserve (entries) {
this.set({ sticky: !entries[0].isIntersecting })
},
teardownStickyObserver () {
if (this.__stickyObserver) {
this.__stickyObserver.disconnect()
}
}
},
components: {
ComposeButton
}
}
</script>