Pinafore-Web-Client-Frontend/src/routes/_components/compose/ComposeStickyButton.html

116 lines
3.7 KiB
HTML

<div class="compose-box-button-sentinel" ref:sentinel></div>
<div class="{computedClass}"
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: calc(var(--fab-gap-top));
}
</style>
<script>
import ComposeButton from './ComposeButton.html'
import { store } from '../../_store/store.js'
import { importShowComposeDialog } from '../dialog/asyncDialogs/importShowComposeDialog.js'
import { observe } from 'svelte-extras'
import { classname } from '../../_utils/classname.js'
export default {
oncreate () {
this.setupIntersectionObservers()
},
ondestroy () {
this.teardownIntersectionObservers()
},
store: () => store,
data: () => ({
sticky: false
}),
computed: {
timelineInitialized: ({ $timelineInitialized }) => $timelineInitialized,
computedClass: ({ showSticky, hideAndFadeIn }) => (classname(
'compose-box-button-wrapper',
showSticky && 'compose-box-button-sticky',
hideAndFadeIn
))
},
methods: {
observe,
onClickButton () {
const { 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())()
},
setupIntersectionObservers () {
const { showSticky } = this.get()
if (!showSticky) {
return // no need to set up observers if this button can never be sticky (e.g. dialogs)
}
const 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 && !this.__oneShotObserver) {
this.__oneShotObserver = new IntersectionObserver(entries => {
this.onObserve(entries)
this.__oneShotObserver.disconnect()
this.__oneShotObserver = null
})
this.__oneShotObserver.observe(sentinel)
}
}, { init: false })
},
onObserve (entries) {
this.set({ sticky: !entries[0].isIntersecting })
},
teardownIntersectionObservers () {
if (this.__stickyObserver) {
this.__stickyObserver.disconnect()
this.__stickyObserver = null
}
if (this.__oneShotObserver) {
this.__oneShotObserver.disconnect()
this.__oneShotObserver = null
}
}
},
components: {
ComposeButton
}
}
</script>