@@ -1,81 +0,0 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
|
||||
let classes = "";
|
||||
export { classes as class }; // export reserved keyword
|
||||
|
||||
let wrapper = null;
|
||||
let scrollClasses = "";
|
||||
let scrollTimeoutId = null;
|
||||
let observer;
|
||||
|
||||
export function refresh() {
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(scrollTimeoutId);
|
||||
|
||||
scrollTimeoutId = setTimeout(() => {
|
||||
const offsetWidth = wrapper.offsetWidth;
|
||||
const scrollWidth = wrapper.scrollWidth;
|
||||
|
||||
if (scrollWidth - offsetWidth) {
|
||||
scrollClasses = "scrollable";
|
||||
if (wrapper.scrollLeft === 0) {
|
||||
scrollClasses += " scroll-start";
|
||||
} else if (wrapper.scrollLeft + offsetWidth == scrollWidth) {
|
||||
scrollClasses += " scroll-end";
|
||||
}
|
||||
} else {
|
||||
scrollClasses = "";
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
refresh();
|
||||
|
||||
observer = new MutationObserver(() => {
|
||||
refresh();
|
||||
});
|
||||
|
||||
observer.observe(wrapper, {
|
||||
attributeFilter: ["width"],
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer?.disconnect();
|
||||
clearTimeout(scrollTimeoutId);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={refresh} />
|
||||
|
||||
<div class="horizontal-scroller-wrapper">
|
||||
<slot name="before" />
|
||||
|
||||
<div bind:this={wrapper} class="horizontal-scroller {classes} {scrollClasses}" on:scroll={refresh}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<slot name="after" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.horizontal-scroller {
|
||||
width: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.horizontal-scroller-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
:global(.horizontal-scroller-wrapper .columns-dropdown) {
|
||||
top: 40px;
|
||||
z-index: 100;
|
||||
max-height: 340px;
|
||||
}
|
||||
</style>
|
||||
@@ -11,6 +11,8 @@
|
||||
</main>
|
||||
|
||||
<footer class="page-footer">
|
||||
<slot name="footer" />
|
||||
|
||||
<a href={import.meta.env.PB_DOCS_URL} target="_blank" rel="noopener noreferrer">
|
||||
<i class="ri-book-open-line txt-sm" />
|
||||
<span class="txt">Docs</span>
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<script>
|
||||
import { onMount, createEventDispatcher } from "svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let classes = "";
|
||||
export { classes as class }; // export reserved keyword
|
||||
|
||||
export let vThreshold = 0;
|
||||
export let hThreshold = 0;
|
||||
export let dispatchOnNoScroll = true;
|
||||
|
||||
let wrapper = null;
|
||||
let scrollClasses = "";
|
||||
let throttleTimeoutId = null;
|
||||
let hDiff;
|
||||
let vDiff;
|
||||
let wrapperWidth;
|
||||
let wrapperHeight;
|
||||
let observer;
|
||||
|
||||
export function resetVerticalScroll() {
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
wrapper.scrollTop = 0;
|
||||
}
|
||||
|
||||
export function resetHorizontalScroll() {
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
wrapper.scrollLeft = 0;
|
||||
}
|
||||
|
||||
export function refresh() {
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollClasses = "";
|
||||
|
||||
// +2 extra threshold as a workaround for the lack of subpixel precision
|
||||
wrapperWidth = wrapper.clientWidth + 2;
|
||||
wrapperHeight = wrapper.clientHeight + 2;
|
||||
|
||||
hDiff = wrapper.scrollWidth - wrapperWidth;
|
||||
vDiff = wrapper.scrollHeight - wrapperHeight;
|
||||
|
||||
// vertical scroller
|
||||
if (vDiff > 0) {
|
||||
scrollClasses += " v-scroll";
|
||||
|
||||
if (vThreshold >= wrapperHeight) {
|
||||
vThreshold = 0;
|
||||
}
|
||||
|
||||
if (wrapper.scrollTop - vThreshold <= 0) {
|
||||
scrollClasses += " v-scroll-start";
|
||||
dispatch("vScrollStart");
|
||||
}
|
||||
|
||||
if (wrapper.scrollTop + vThreshold >= vDiff) {
|
||||
scrollClasses += " v-scroll-end";
|
||||
dispatch("vScrollEnd");
|
||||
}
|
||||
} else if (dispatchOnNoScroll) {
|
||||
dispatch("vScrollEnd");
|
||||
}
|
||||
|
||||
// horizontal scroller
|
||||
if (hDiff > 0) {
|
||||
scrollClasses += " h-scroll";
|
||||
|
||||
if (hThreshold >= wrapperWidth) {
|
||||
hThreshold = 0;
|
||||
}
|
||||
|
||||
if (wrapper.scrollLeft - hThreshold <= 0) {
|
||||
scrollClasses += " h-scroll-start";
|
||||
dispatch("hScrollStart");
|
||||
}
|
||||
if (wrapper.scrollLeft + hThreshold >= hDiff) {
|
||||
scrollClasses += " h-scroll-end";
|
||||
dispatch("hScrollEnd");
|
||||
}
|
||||
} else if (dispatchOnNoScroll) {
|
||||
dispatch("hScrollEnd");
|
||||
}
|
||||
}
|
||||
|
||||
export function throttleRefresh() {
|
||||
if (throttleTimeoutId) {
|
||||
return;
|
||||
}
|
||||
|
||||
throttleTimeoutId = setTimeout(() => {
|
||||
refresh();
|
||||
|
||||
throttleTimeoutId = null;
|
||||
}, 150);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
throttleRefresh();
|
||||
|
||||
observer = new MutationObserver(throttleRefresh);
|
||||
|
||||
observer.observe(wrapper, {
|
||||
attributeFilter: ["width", "height"],
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer?.disconnect();
|
||||
clearTimeout(throttleTimeoutId);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={throttleRefresh} />
|
||||
|
||||
<div class="scroller-wrapper">
|
||||
<slot name="before" />
|
||||
|
||||
<div bind:this={wrapper} class="scroller {classes} {scrollClasses}" on:scroll={throttleRefresh}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<slot name="after" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scroller {
|
||||
width: auto;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.scroller-wrapper {
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
}
|
||||
:global(.scroller-wrapper .columns-dropdown) {
|
||||
top: 40px;
|
||||
z-index: 100;
|
||||
max-height: 340px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user