-import {onSelect} from '../services/dom.ts';
+import {findClosestScrollContainer, onSelect} from '../services/dom.ts';
import {KeyboardNavigationHandler} from '../services/keyboard-navigation.ts';
import {Component} from './component';
const menuOriginalRect = this.menu.getBoundingClientRect();
let heightOffset = 0;
const toggleHeight = this.toggle.getBoundingClientRect().height;
- const dropUpwards = menuOriginalRect.bottom > window.innerHeight;
+ const containerBounds = findClosestScrollContainer(this.menu).getBoundingClientRect();
+ const dropUpwards = menuOriginalRect.bottom > containerBounds.bottom;
const containerRect = this.container.getBoundingClientRect();
// If enabled, Move to body to prevent being trapped within scrollable sections
import {Component} from './component';
export class TriLayout extends Component {
-
- setup() {
+ private container!: HTMLElement;
+ private tabs!: HTMLElement[];
+ private sidebarScrollContainers!: HTMLElement[];
+
+ private lastLayoutType = 'none';
+ private onDestroy: (()=>void)|null = null;
+ private scrollCache: Record<string, number> = {
+ content: 0,
+ info: 0,
+ };
+ private lastTabShown = 'content';
+
+ setup(): void {
this.container = this.$refs.container;
this.tabs = this.$manyRefs.tab;
-
- this.lastLayoutType = 'none';
- this.onDestroy = null;
- this.scrollCache = {
- content: 0,
- info: 0,
- };
- this.lastTabShown = 'content';
+ this.sidebarScrollContainers = this.$manyRefs.sidebarScrollContainer;
// Bind any listeners
this.mobileTabClick = this.mobileTabClick.bind(this);
window.addEventListener('resize', () => {
this.updateLayout();
}, {passive: true});
+
+ this.setupSidebarScrollHandlers();
}
- updateLayout() {
+ updateLayout(): void {
let newLayout = 'tablet';
if (window.innerWidth <= 1000) newLayout = 'mobile';
if (window.innerWidth > 1400) newLayout = 'desktop';
};
}
- setupDesktop() {
+ setupDesktop(): void {
//
}
/**
* Action to run when the mobile info toggle bar is clicked/tapped
- * @param event
*/
- mobileTabClick(event) {
- const {tab} = event.target.dataset;
+ mobileTabClick(event: MouseEvent): void {
+ const tab = (event.target as HTMLElement).dataset.tab || '';
this.showTab(tab);
}
* Show the content tab.
* Used by the page-display component.
*/
- showContent() {
+ showContent(): void {
this.showTab('content', false);
}
/**
* Show the given tab
- * @param {String} tabName
- * @param {Boolean }scroll
*/
- showTab(tabName, scroll = true) {
+ showTab(tabName: string, scroll: boolean = true): void {
this.scrollCache[this.lastTabShown] = document.documentElement.scrollTop;
// Set tab status
// Set the scroll position from cache
if (scroll) {
- const pageHeader = document.querySelector('header');
+ const pageHeader = document.querySelector('header') as HTMLElement;
const defaultScrollTop = pageHeader.getBoundingClientRect().bottom;
document.documentElement.scrollTop = this.scrollCache[tabName] || defaultScrollTop;
setTimeout(() => {
this.lastTabShown = tabName;
}
+ setupSidebarScrollHandlers(): void {
+ for (const sidebar of this.sidebarScrollContainers) {
+ sidebar.addEventListener('scroll', () => this.handleSidebarScroll(sidebar), {
+ passive: true,
+ });
+ this.handleSidebarScroll(sidebar);
+ }
+
+ window.addEventListener('resize', () => {
+ for (const sidebar of this.sidebarScrollContainers) {
+ this.handleSidebarScroll(sidebar);
+ }
+ });
+ }
+
+ handleSidebarScroll(sidebar: HTMLElement): void {
+ const scrollable = sidebar.clientHeight !== sidebar.scrollHeight;
+ const atTop = sidebar.scrollTop === 0;
+ const atBottom = (sidebar.scrollTop + sidebar.clientHeight) === sidebar.scrollHeight;
+
+ if (sidebar.parentElement) {
+ sidebar.parentElement.classList.toggle('scroll-away-from-top', !atTop && scrollable);
+ sidebar.parentElement.classList.toggle('scroll-away-from-bottom', !atBottom && scrollable);
+ }
+ }
+
}
export function hashElement(element: HTMLElement): string {
const normalisedElemText = (element.textContent || '').replace(/\s{2,}/g, '');
return cyrb53(normalisedElemText);
+}
+
+/**
+ * Find the closest scroll container parent for the given element
+ * otherwise will default to the body element.
+ */
+export function findClosestScrollContainer(start: HTMLElement): HTMLElement {
+ let el: HTMLElement|null = start;
+ do {
+ const computed = window.getComputedStyle(el);
+ if (computed.overflowY === 'scroll') {
+ return el;
+ }
+
+ el = el.parentElement;
+ } while (el);
+
+ return document.body;
}
\ No newline at end of file
.tri-layout-right {
grid-area: c;
min-width: 0;
+ position: relative;
}
.tri-layout-left {
grid-area: a;
min-width: 0;
+ position: relative;
}
@include mixins.larger-than(vars.$bp-xxl) {
grid-template-areas: "a b b";
grid-template-columns: 1fr 3fr;
grid-template-rows: min-content min-content 1fr;
- padding-inline-end: vars.$l;
+ margin-inline-start: (vars.$m + vars.$xxs);
+ margin-inline-end: (vars.$m + vars.$xxs);
}
.tri-layout-sides {
grid-column-start: a;
height: 100%;
scrollbar-width: none;
-ms-overflow-style: none;
+ padding-inline: vars.$m;
+ margin-inline: -(vars.$m);
&::-webkit-scrollbar {
display: none;
}
margin-inline-start: 0;
margin-inline-end: 0;
}
+}
+
+/**
+ * Scroll Indicators
+ */
+.scroll-away-from-top:before,
+.scroll-away-from-bottom:after {
+ content: '';
+ display: block;
+ position: absolute;
+ @include mixins.lightDark(color, #F2F2F2, #111);
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 50px;
+ background: linear-gradient(to bottom, currentColor, transparent);
+ z-index: 2;
+}
+.scroll-away-from-bottom:after {
+ top: auto;
+ bottom: 0;
+ background: linear-gradient(to top, currentColor, transparent);
}
\ No newline at end of file
<div refs="tri-layout@container" class="tri-layout-container" @yield('container-attrs') >
<div class="tri-layout-sides print-hidden">
- <div class="tri-layout-sides-content">
+ <div refs="tri-layout@sidebar-scroll-container" class="tri-layout-sides-content">
<div class="tri-layout-right print-hidden">
- <aside class="tri-layout-right-contents">
+ <aside refs="tri-layout@sidebar-scroll-container" class="tri-layout-right-contents">
@yield('right')
</aside>
</div>
<div class="tri-layout-left print-hidden" id="sidebar">
- <aside class="tri-layout-left-contents">
+ <aside refs="tri-layout@sidebar-scroll-container" class="tri-layout-left-contents">
@yield('left')
</aside>
</div>