Adds fixed scrollbars for improved navigation
Implements fixed scrollbars at the browser edges to enhance navigation within the calendar view. This ensures that the scrollbars remain visible regardless of the user's scroll position, providing consistent access to horizontal and vertical scrolling. Removes the right header spacer and right column, integrating their functionality into the new fixed scrollbar components. Additionally, synchronizes the week header position with the horizontal scroll, improving the user experience. Scrollbar hiding is now handled in the CSS file.
This commit is contained in:
parent
1822fa7287
commit
1d25ab7b53
3 changed files with 332 additions and 81 deletions
|
|
@ -117,12 +117,12 @@ export class GridManager {
|
||||||
// Clear existing grid and rebuild POC structure
|
// Clear existing grid and rebuild POC structure
|
||||||
this.grid.innerHTML = '';
|
this.grid.innerHTML = '';
|
||||||
|
|
||||||
// Create POC structure: header-spacer + time-axis + week-container + right-side
|
// Create POC structure: header-spacer + time-axis + week-container + fixed scrollbars
|
||||||
this.createHeaderSpacer();
|
this.createHeaderSpacer();
|
||||||
this.createRightHeaderSpacer();
|
|
||||||
this.createTimeAxis();
|
this.createTimeAxis();
|
||||||
this.createRightColumn();
|
|
||||||
this.createWeekContainer();
|
this.createWeekContainer();
|
||||||
|
this.createBottomRow();
|
||||||
|
this.createFixedScrollbars();
|
||||||
|
|
||||||
console.log('GridManager: Grid rendered successfully with POC structure');
|
console.log('GridManager: Grid rendered successfully with POC structure');
|
||||||
}
|
}
|
||||||
|
|
@ -138,23 +138,22 @@ export class GridManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create right header spacer to align right column with week content
|
* Create fixed scrollbars at browser edges
|
||||||
*/
|
*/
|
||||||
private createRightHeaderSpacer(): void {
|
private createFixedScrollbars(): void {
|
||||||
if (!this.grid) return;
|
if (!document.body) return;
|
||||||
|
|
||||||
const rightHeaderSpacer = document.createElement('swp-right-header-spacer');
|
|
||||||
this.grid.appendChild(rightHeaderSpacer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create right column beside week container
|
|
||||||
*/
|
|
||||||
private createRightColumn(): void {
|
|
||||||
if (!this.grid) return;
|
|
||||||
|
|
||||||
|
// Create right scrollbar at browser edge
|
||||||
|
const rightScrollbar = document.createElement('swp-right-scrollbar');
|
||||||
const rightColumn = document.createElement('swp-right-column');
|
const rightColumn = document.createElement('swp-right-column');
|
||||||
this.grid.appendChild(rightColumn);
|
rightScrollbar.appendChild(rightColumn);
|
||||||
|
document.body.appendChild(rightScrollbar);
|
||||||
|
|
||||||
|
// Create bottom scrollbar at browser edge
|
||||||
|
const bottomScrollbar = document.createElement('swp-bottom-scrollbar');
|
||||||
|
const bottomColumn = document.createElement('swp-bottom-column');
|
||||||
|
bottomScrollbar.appendChild(bottomColumn);
|
||||||
|
document.body.appendChild(bottomScrollbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -210,6 +209,17 @@ export class GridManager {
|
||||||
this.grid.appendChild(weekContainer);
|
this.grid.appendChild(weekContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create bottom row with spacer
|
||||||
|
*/
|
||||||
|
private createBottomRow(): void {
|
||||||
|
if (!this.grid) return;
|
||||||
|
|
||||||
|
// Bottom spacer (left)
|
||||||
|
const bottomSpacer = document.createElement('swp-bottom-spacer');
|
||||||
|
this.grid.appendChild(bottomSpacer);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render week headers like in POC
|
* Render week headers like in POC
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { EventTypes } from '../constants/EventTypes';
|
||||||
* Manages custom scrolling functionality for the calendar
|
* Manages custom scrolling functionality for the calendar
|
||||||
*/
|
*/
|
||||||
export class ScrollManager {
|
export class ScrollManager {
|
||||||
|
// Vertical scrolling
|
||||||
private rightColumn: HTMLElement | null = null;
|
private rightColumn: HTMLElement | null = null;
|
||||||
private scrollHandle: HTMLElement | null = null;
|
private scrollHandle: HTMLElement | null = null;
|
||||||
private scrollableContent: HTMLElement | null = null;
|
private scrollableContent: HTMLElement | null = null;
|
||||||
|
|
@ -20,6 +21,16 @@ export class ScrollManager {
|
||||||
private maxScrollTop: number = 0;
|
private maxScrollTop: number = 0;
|
||||||
private handleHeight: number = 40;
|
private handleHeight: number = 40;
|
||||||
|
|
||||||
|
// Horizontal scrolling
|
||||||
|
private bottomColumn: HTMLElement | null = null;
|
||||||
|
private horizontalScrollHandle: HTMLElement | null = null;
|
||||||
|
private weekHeader: HTMLElement | null = null;
|
||||||
|
private isHorizontalDragging: boolean = false;
|
||||||
|
private dragStartX: number = 0;
|
||||||
|
private scrollStartLeft: number = 0;
|
||||||
|
private maxScrollLeft: number = 0;
|
||||||
|
private horizontalHandleWidth: number = 40;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
@ -35,7 +46,7 @@ export class ScrollManager {
|
||||||
this.setupScrolling();
|
this.setupScrolling();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle mouse events for dragging
|
// Handle mouse events for dragging (both vertical and horizontal)
|
||||||
document.addEventListener('mousemove', this.handleMouseMove.bind(this));
|
document.addEventListener('mousemove', this.handleMouseMove.bind(this));
|
||||||
document.addEventListener('mouseup', this.handleMouseUp.bind(this));
|
document.addEventListener('mouseup', this.handleMouseUp.bind(this));
|
||||||
|
|
||||||
|
|
@ -60,6 +71,14 @@ export class ScrollManager {
|
||||||
this.calculateScrollBounds();
|
this.calculateScrollBounds();
|
||||||
this.updateHandlePosition();
|
this.updateHandlePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup horizontal scrolling
|
||||||
|
if (this.bottomColumn && this.scrollableContent && this.weekHeader) {
|
||||||
|
this.createHorizontalScrollHandle();
|
||||||
|
this.setupHorizontalScrollSynchronization();
|
||||||
|
this.calculateHorizontalScrollBounds();
|
||||||
|
this.updateHorizontalHandlePosition();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -70,6 +89,17 @@ export class ScrollManager {
|
||||||
this.scrollableContent = document.querySelector('swp-scrollable-content');
|
this.scrollableContent = document.querySelector('swp-scrollable-content');
|
||||||
this.calendarContainer = document.querySelector('swp-calendar-container');
|
this.calendarContainer = document.querySelector('swp-calendar-container');
|
||||||
this.timeAxis = document.querySelector('swp-time-axis');
|
this.timeAxis = document.querySelector('swp-time-axis');
|
||||||
|
|
||||||
|
// Horizontal scrolling elements
|
||||||
|
this.bottomColumn = document.querySelector('swp-bottom-column');
|
||||||
|
this.weekHeader = document.querySelector('swp-week-header');
|
||||||
|
|
||||||
|
console.log('ScrollManager: Found elements:', {
|
||||||
|
rightColumn: !!this.rightColumn,
|
||||||
|
bottomColumn: !!this.bottomColumn,
|
||||||
|
scrollableContent: !!this.scrollableContent,
|
||||||
|
weekHeader: !!this.weekHeader
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -146,8 +176,8 @@ export class ScrollManager {
|
||||||
* Handle mouse move during drag
|
* Handle mouse move during drag
|
||||||
*/
|
*/
|
||||||
private handleMouseMove(e: MouseEvent): void {
|
private handleMouseMove(e: MouseEvent): void {
|
||||||
if (!this.isDragging || !this.scrollHandle || !this.scrollableContent) return;
|
// Handle vertical dragging
|
||||||
|
if (this.isDragging && this.scrollHandle && this.scrollableContent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const deltaY = e.clientY - this.dragStartY;
|
const deltaY = e.clientY - this.dragStartY;
|
||||||
|
|
@ -155,8 +185,7 @@ export class ScrollManager {
|
||||||
const trackHeight = this.scrollableContent.clientHeight - this.handleHeight;
|
const trackHeight = this.scrollableContent.clientHeight - this.handleHeight;
|
||||||
|
|
||||||
// Ensure trackHeight is positive to avoid division by zero
|
// Ensure trackHeight is positive to avoid division by zero
|
||||||
if (trackHeight <= 0) return;
|
if (trackHeight > 0) {
|
||||||
|
|
||||||
const scrollRatio = deltaY / trackHeight;
|
const scrollRatio = deltaY / trackHeight;
|
||||||
const newScrollTop = this.scrollStartTop + (scrollRatio * this.maxScrollTop);
|
const newScrollTop = this.scrollStartTop + (scrollRatio * this.maxScrollTop);
|
||||||
|
|
||||||
|
|
@ -169,13 +198,39 @@ export class ScrollManager {
|
||||||
// Update handle position (this will also trigger time-axis sync via scroll event)
|
// Update handle position (this will also trigger time-axis sync via scroll event)
|
||||||
this.updateHandlePosition();
|
this.updateHandlePosition();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle horizontal dragging
|
||||||
|
if (this.isHorizontalDragging && this.horizontalScrollHandle && this.scrollableContent) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const deltaX = e.clientX - this.dragStartX;
|
||||||
|
// Use container width as track width
|
||||||
|
const trackWidth = this.scrollableContent.clientWidth - this.horizontalHandleWidth;
|
||||||
|
|
||||||
|
// Ensure trackWidth is positive to avoid division by zero
|
||||||
|
if (trackWidth > 0) {
|
||||||
|
const scrollRatio = deltaX / trackWidth;
|
||||||
|
const newScrollLeft = this.scrollStartLeft + (scrollRatio * this.maxScrollLeft);
|
||||||
|
|
||||||
|
// Clamp scroll position
|
||||||
|
const clampedScrollLeft = Math.max(0, Math.min(newScrollLeft, this.maxScrollLeft));
|
||||||
|
|
||||||
|
// Apply scroll to content
|
||||||
|
this.scrollableContent.scrollLeft = clampedScrollLeft;
|
||||||
|
|
||||||
|
// Update handle position (this will also trigger week-header sync via scroll event)
|
||||||
|
this.updateHorizontalHandlePosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle mouse up to end drag
|
* Handle mouse up to end drag
|
||||||
*/
|
*/
|
||||||
private handleMouseUp(e: MouseEvent): void {
|
private handleMouseUp(e: MouseEvent): void {
|
||||||
if (!this.isDragging) return;
|
// Handle vertical drag end
|
||||||
|
if (this.isDragging) {
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
|
|
||||||
if (this.scrollHandle) {
|
if (this.scrollHandle) {
|
||||||
|
|
@ -183,6 +238,16 @@ export class ScrollManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle horizontal drag end
|
||||||
|
if (this.isHorizontalDragging) {
|
||||||
|
this.isHorizontalDragging = false;
|
||||||
|
|
||||||
|
if (this.horizontalScrollHandle) {
|
||||||
|
this.horizontalScrollHandle.classList.remove('dragging');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update handle position based on current scroll
|
* Update handle position based on current scroll
|
||||||
*/
|
*/
|
||||||
|
|
@ -286,22 +351,11 @@ export class ScrollManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide native scrollbar while keeping scroll functionality
|
* Hide native scrollbar while keeping scroll functionality
|
||||||
|
* Note: Scrollbar hiding is now handled in CSS file
|
||||||
*/
|
*/
|
||||||
private hideNativeScrollbar(): void {
|
private hideNativeScrollbar(): void {
|
||||||
if (!this.scrollableContent) return;
|
// Scrollbar hiding is now handled in CSS file
|
||||||
|
// No JavaScript needed here anymore
|
||||||
// Apply CSS to hide scrollbar
|
|
||||||
this.scrollableContent.style.scrollbarWidth = 'none'; // Firefox
|
|
||||||
(this.scrollableContent.style as any).msOverflowStyle = 'none'; // IE/Edge
|
|
||||||
|
|
||||||
// Add webkit scrollbar hiding
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.textContent = `
|
|
||||||
swp-scrollable-content::-webkit-scrollbar {
|
|
||||||
display: none; /* Chrome/Safari/Opera */
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -344,6 +398,125 @@ export class ScrollManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and add horizontal scroll handle to bottom column
|
||||||
|
*/
|
||||||
|
private createHorizontalScrollHandle(): void {
|
||||||
|
if (!this.bottomColumn) return;
|
||||||
|
|
||||||
|
// Remove existing handle if any
|
||||||
|
const existingHandle = this.bottomColumn.querySelector('swp-horizontal-scroll-handle');
|
||||||
|
if (existingHandle) {
|
||||||
|
existingHandle.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new handle
|
||||||
|
this.horizontalScrollHandle = document.createElement('swp-horizontal-scroll-handle');
|
||||||
|
this.horizontalScrollHandle.addEventListener('mousedown', this.handleHorizontalMouseDown.bind(this));
|
||||||
|
|
||||||
|
this.bottomColumn.appendChild(this.horizontalScrollHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate horizontal scroll bounds based on content and container widths
|
||||||
|
*/
|
||||||
|
private calculateHorizontalScrollBounds(): void {
|
||||||
|
if (!this.scrollableContent || !this.bottomColumn) return;
|
||||||
|
|
||||||
|
const contentWidth = this.scrollableContent.scrollWidth;
|
||||||
|
const containerWidth = this.scrollableContent.clientWidth;
|
||||||
|
const trackWidth = containerWidth;
|
||||||
|
|
||||||
|
console.log('ScrollManager Horizontal Debug:');
|
||||||
|
console.log('- contentWidth (scrollWidth):', contentWidth);
|
||||||
|
console.log('- containerWidth (clientWidth):', containerWidth);
|
||||||
|
console.log('- trackWidth (using containerWidth):', trackWidth);
|
||||||
|
|
||||||
|
this.maxScrollLeft = Math.max(0, contentWidth - containerWidth);
|
||||||
|
|
||||||
|
// Calculate proportional handle width based on content ratio
|
||||||
|
if (contentWidth > 0 && containerWidth > 0) {
|
||||||
|
const visibleRatio = containerWidth / contentWidth;
|
||||||
|
this.horizontalHandleWidth = Math.max(20, Math.min(trackWidth * visibleRatio, trackWidth - 10));
|
||||||
|
} else {
|
||||||
|
this.horizontalHandleWidth = 40; // fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('- maxScrollLeft:', this.maxScrollLeft);
|
||||||
|
console.log('- visibleRatio:', (containerWidth / contentWidth).toFixed(3));
|
||||||
|
console.log('- calculated horizontalHandleWidth:', this.horizontalHandleWidth);
|
||||||
|
|
||||||
|
// Update handle width in DOM
|
||||||
|
if (this.horizontalScrollHandle) {
|
||||||
|
this.horizontalScrollHandle.style.width = `${this.horizontalHandleWidth}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse down on horizontal scroll handle
|
||||||
|
*/
|
||||||
|
private handleHorizontalMouseDown(e: MouseEvent): void {
|
||||||
|
e.preventDefault();
|
||||||
|
this.isHorizontalDragging = true;
|
||||||
|
this.dragStartX = e.clientX;
|
||||||
|
|
||||||
|
if (this.horizontalScrollHandle && this.scrollableContent) {
|
||||||
|
this.horizontalScrollHandle.classList.add('dragging');
|
||||||
|
this.scrollStartLeft = this.scrollableContent.scrollLeft;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update horizontal handle position based on current scroll
|
||||||
|
*/
|
||||||
|
private updateHorizontalHandlePosition(): void {
|
||||||
|
if (!this.horizontalScrollHandle || !this.scrollableContent) return;
|
||||||
|
|
||||||
|
const scrollLeft = this.scrollableContent.scrollLeft;
|
||||||
|
const scrollRatio = this.maxScrollLeft > 0 ? scrollLeft / this.maxScrollLeft : 0;
|
||||||
|
const trackWidth = this.scrollableContent.clientWidth - this.horizontalHandleWidth;
|
||||||
|
const handleLeft = Math.max(0, Math.min(scrollRatio * trackWidth, trackWidth));
|
||||||
|
|
||||||
|
this.horizontalScrollHandle.style.left = `${handleLeft}px`;
|
||||||
|
|
||||||
|
// Debug logging for handle position
|
||||||
|
if (scrollLeft % 200 === 0) { // Log every 200px to avoid spam
|
||||||
|
console.log(`ScrollManager: Horizontal handle position - scrollLeft: ${scrollLeft}, ratio: ${scrollRatio.toFixed(3)}, handleLeft: ${handleLeft.toFixed(1)}, trackWidth: ${trackWidth}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup horizontal scroll synchronization between scrollable content and week header
|
||||||
|
*/
|
||||||
|
private setupHorizontalScrollSynchronization(): void {
|
||||||
|
if (!this.scrollableContent || !this.weekHeader) return;
|
||||||
|
|
||||||
|
console.log('ScrollManager: Setting up horizontal scroll synchronization');
|
||||||
|
|
||||||
|
// Listen to horizontal scroll events
|
||||||
|
this.scrollableContent.addEventListener('scroll', () => {
|
||||||
|
this.syncWeekHeaderPosition();
|
||||||
|
this.updateHorizontalHandlePosition();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize week header position with scrollable content horizontal scroll
|
||||||
|
*/
|
||||||
|
private syncWeekHeaderPosition(): void {
|
||||||
|
if (!this.scrollableContent || !this.weekHeader) return;
|
||||||
|
|
||||||
|
const scrollLeft = this.scrollableContent.scrollLeft;
|
||||||
|
|
||||||
|
// Use transform for smooth performance
|
||||||
|
this.weekHeader.style.transform = `translateX(-${scrollLeft}px)`;
|
||||||
|
|
||||||
|
// Debug logging (can be removed later)
|
||||||
|
if (scrollLeft % 100 === 0) { // Only log every 100px to avoid spam
|
||||||
|
console.log(`ScrollManager: Synced week-header to scrollLeft: ${scrollLeft}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleanup resources
|
* Cleanup resources
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ swp-calendar-nav {
|
||||||
swp-calendar-container {
|
swp-calendar-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 60px 1fr 20px;
|
grid-template-columns: 60px 1fr;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr 20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
@ -58,14 +58,17 @@ swp-header-spacer {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Right header spacer */
|
/* Right scrollbar - positioned at browser edge */
|
||||||
swp-right-header-spacer {
|
swp-right-scrollbar {
|
||||||
grid-column: 3;
|
position: fixed;
|
||||||
grid-row: 1;
|
top: 0;
|
||||||
height: 80px; /* Same as week header height */
|
right: 0;
|
||||||
background: var(--color-surface);
|
width: 20px;
|
||||||
border-left: 1px solid var(--color-border);
|
height: 100vh;
|
||||||
border-bottom: 1px solid var(--color-border);
|
background: #f0f0f0;
|
||||||
|
border-left: 2px solid #333;
|
||||||
|
z-index: 1000;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Week container for sliding */
|
/* Week container for sliding */
|
||||||
|
|
@ -93,16 +96,14 @@ swp-time-axis {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Right column */
|
/* Right column - now part of fixed scrollbar */
|
||||||
swp-right-column {
|
swp-right-column {
|
||||||
grid-column: 3;
|
position: absolute;
|
||||||
grid-row: 2;
|
top: 80px; /* Below navigation */
|
||||||
background: #f0f0f0;
|
left: 0;
|
||||||
border-left: 2px solid #333;
|
right: 0;
|
||||||
position: relative;
|
bottom: 20px; /* Above horizontal scrollbar */
|
||||||
z-index: 4;
|
background: transparent;
|
||||||
width: 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scroll handle */
|
/* Scroll handle */
|
||||||
|
|
@ -127,6 +128,60 @@ swp-scroll-handle.dragging {
|
||||||
background: #007bff;
|
background: #007bff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bottom row for horizontal scrollbar */
|
||||||
|
swp-bottom-spacer {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 3;
|
||||||
|
height: 20px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom scrollbar - positioned at browser edge */
|
||||||
|
swp-bottom-scrollbar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 20px; /* Leave space for vertical scrollbar */
|
||||||
|
height: 20px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-top: 2px solid #333;
|
||||||
|
z-index: 1000;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-bottom-column {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 60px; /* Start after time-axis */
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Horizontal scroll handle */
|
||||||
|
swp-horizontal-scroll-handle {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 2px;
|
||||||
|
width: 40px;
|
||||||
|
height: 16px;
|
||||||
|
background: #666;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: grab;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-horizontal-scroll-handle:hover {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-horizontal-scroll-handle.dragging {
|
||||||
|
background: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
swp-hour-marker {
|
swp-hour-marker {
|
||||||
height: var(--hour-height);
|
height: var(--hour-height);
|
||||||
padding: 0 8px 8px 8px;
|
padding: 0 8px 8px 8px;
|
||||||
|
|
@ -151,7 +206,8 @@ swp-hour-marker::after {
|
||||||
/* Week header */
|
/* Week header */
|
||||||
swp-week-header {
|
swp-week-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
grid-template-columns: repeat(7, minmax(250px, 1fr));
|
||||||
|
min-width: 1750px; /* Match day-columns width */
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
|
@ -203,11 +259,21 @@ swp-day-header[data-today="true"] swp-day-date {
|
||||||
/* Scrollable content */
|
/* Scrollable content */
|
||||||
swp-scrollable-content {
|
swp-scrollable-content {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: auto;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
/* Height will be set dynamically by ScrollManager via ResizeObserver */
|
/* Height will be set dynamically by ScrollManager via ResizeObserver */
|
||||||
|
width: calc(100vw - 60px - 20px); /* Viewport width minus time-axis and scrollbar */
|
||||||
|
|
||||||
|
/* Hide native scrollbars */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
-ms-overflow-style: none; /* IE/Edge */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome/Safari/Opera */
|
||||||
|
swp-scrollable-content::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Time grid */
|
/* Time grid */
|
||||||
|
|
@ -247,12 +313,14 @@ swp-day-columns {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(7, 1fr);
|
grid-template-columns: repeat(7, minmax(250px, 1fr));
|
||||||
|
min-width: 1750px; /* 7 * 250px = force horizontal scroll */
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-day-column {
|
swp-day-column {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-right: 1px solid var(--color-grid-line);
|
border-right: 1px solid var(--color-grid-line);
|
||||||
|
min-width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-day-column:last-child {
|
swp-day-column:last-child {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue