Skip to content

Commit

Permalink
Sticky scrollbar for element indexes
Browse files Browse the repository at this point in the history
  • Loading branch information
brianjhanson committed May 2, 2024
1 parent e075c9c commit 10fcb31
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/web/assets/cp/src/Craft.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,4 @@ import './js/CraftGlobalSidebar.js';
import './js/CraftDisclosure.js';
import './js/CraftTooltip.js';
import './js/CraftElementLabel';
import './js/CraftProxyScrollbar';
6 changes: 6 additions & 0 deletions src/web/assets/cp/src/css/_main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3623,6 +3623,12 @@ table {
background: transparent;
}
}

craft-proxy-scrollbar {
position: sticky;
width: calc(100% + var(--xl) * 2);
margin-inline: calc(var(--xl) * -1);
}
}

.elements {
Expand Down
104 changes: 104 additions & 0 deletions src/web/assets/cp/src/js/CraftProxyScrollbar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Proxy scrollbar
*
* Display a scrollbar that is synced with another element
*
* @property {string} scroller - The selector of the element that will be scrolled
* @property {string} content - The selector of the element within the scroller containing the overflow content
* @property {boolean} hidden - Whether the scrollbar should be hidden
* @property {HTMLElement} proxy - The element that represents the scrollbar
* @property {HTMLElement} scroller - The element that will be scrolled
* @property {HTMLElement} content - The element within the scroller containing the overflow content
*/
class CraftProxyScrollbar extends HTMLElement {
static observedAttributes = ['hidden'];

get hidden() {
return this.getAttribute('hidden');
}

get hasOverflow() {
return this.content?.scrollWidth > this.scroller?.clientWidth;
}

connectedCallback() {
this.ignoreScrollEvent = false;
this.animation = false;

this.scroller = document.querySelector(this.getAttribute('scroller'));
this.content = document.querySelector(this.getAttribute('content'));

if (!this.scroller || !this.content) {
return;
}

this.proxy = document.createElement('div');
this.proxy.style.height = '1px';
this.proxy.style.width = this.content.getBoundingClientRect().width + 'px';

this.appendChild(this.proxy);

this.addEventListener('scroll', this.syncScroll(this.scroller, this));
this.scroller.addEventListener(
'scroll',
this.syncScroll(this, this.scroller)
);
window.addEventListener('resize', this.handleResize.bind(this));

Object.assign(this.style, {
display: this.hasOverflow ? 'block' : 'none',
overflowX: 'scroll',
});
}

attributeChangedCallback(name, oldValue, newValue) {
if (name === 'hidden') {
this.style.display = newValue ? 'none' : 'block';
}
}

disconnectedCallback() {
this.proxy.remove();

this.scroller.removeEventListener(
'scroll',
this.syncScroll(this.scroller, this)
);
this.scroller.removeEventListener(
'scroll',
this.syncScroll(this, this.scroller)
);

window.removeEventListener('resize', this.handleResize.bind(this));
}

handleResize() {
this.proxy.style.width = this.content.getBoundingClientRect().width + 'px';

if (this.hasOverflow) {
this.removeAttribute('hidden');
} else {
this.setAttribute('hidden', 'true');
}
}

syncScroll(a, b) {
return () => {
if (this.ignoreScrollEvent) {
return false;
}

if (this.animation) {
cancelAnimationFrame(this.animation);
}

this.animation = requestAnimationFrame(() => {
this.ignoreScrollEvent = true;
a.scrollLeft = b.scrollLeft;
this.ignoreScrollEvent = false;
});
};
}
}

customElements.define('craft-proxy-scrollbar', CraftProxyScrollbar);
29 changes: 29 additions & 0 deletions src/web/assets/cp/src/js/TableElementIndexView.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Craft.TableElementIndexView = Craft.BaseElementIndexView.extend({
// Set the sort header
this.initTableHeaders();

this.createScrollbar();

// Create the table sorter
if (
(this.settings.sortable ||
Expand Down Expand Up @@ -650,4 +652,31 @@ Craft.TableElementIndexView = Craft.BaseElementIndexView.extend({

this.base();
},

createScrollbar() {
const footer = document.querySelector('#content > #footer');
const stickyScrollbar = document.createElement('craft-proxy-scrollbar');
stickyScrollbar.setAttribute('scroller', '.tablepane');
stickyScrollbar.setAttribute('content', '.tablepane > table');

stickyScrollbar.style.bottom = `${
footer.getBoundingClientRect().height + 2
}px`;

let $scrollbar = $(stickyScrollbar);
const observer = new IntersectionObserver(
([ev]) => {
if (ev.intersectionRatio < 1) {
$scrollbar.insertAfter(this.$container);
} else {
$scrollbar.remove();
}
},
{
rootMargin: '0px 0px -1px 0px',
threshold: [1],
}
);
observer.observe(footer);
},
});

0 comments on commit 10fcb31

Please sign in to comment.