Adds scroll synchronization for calendar view
Introduces ScrollManager to synchronize time axis and header scrolling Improves user experience by keeping time axis and header aligned during scrolling Enables dynamic vertical and horizontal scroll tracking for calendar components
This commit is contained in:
parent
ed5b2beb4c
commit
70e505526f
4 changed files with 65 additions and 9 deletions
|
|
@ -9,6 +9,7 @@ import { TeamRenderer } from './features/team/TeamRenderer';
|
||||||
import { RendererRegistry } from './core/RendererRegistry';
|
import { RendererRegistry } from './core/RendererRegistry';
|
||||||
import { CalendarOrchestrator } from './core/CalendarOrchestrator';
|
import { CalendarOrchestrator } from './core/CalendarOrchestrator';
|
||||||
import { TimeAxisRenderer } from './features/timeaxis/TimeAxisRenderer';
|
import { TimeAxisRenderer } from './features/timeaxis/TimeAxisRenderer';
|
||||||
|
import { ScrollManager } from './core/ScrollManager';
|
||||||
import { MockTeamStore, MockResourceStore } from './demo/MockStores';
|
import { MockTeamStore, MockResourceStore } from './demo/MockStores';
|
||||||
import { DemoApp } from './demo/DemoApp';
|
import { DemoApp } from './demo/DemoApp';
|
||||||
|
|
||||||
|
|
@ -45,6 +46,7 @@ export function createV2Container(): Container {
|
||||||
// CalendarOrchestrator modtager IGroupingStore[] automatisk (array injection)
|
// CalendarOrchestrator modtager IGroupingStore[] automatisk (array injection)
|
||||||
builder.registerType(CalendarOrchestrator).as<CalendarOrchestrator>();
|
builder.registerType(CalendarOrchestrator).as<CalendarOrchestrator>();
|
||||||
builder.registerType(TimeAxisRenderer).as<TimeAxisRenderer>();
|
builder.registerType(TimeAxisRenderer).as<TimeAxisRenderer>();
|
||||||
|
builder.registerType(ScrollManager).as<ScrollManager>();
|
||||||
|
|
||||||
// Demo app
|
// Demo app
|
||||||
builder.registerType(DemoApp).as<DemoApp>();
|
builder.registerType(DemoApp).as<DemoApp>();
|
||||||
|
|
|
||||||
23
src/v2/core/ScrollManager.ts
Normal file
23
src/v2/core/ScrollManager.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
export class ScrollManager {
|
||||||
|
private scrollableContent!: HTMLElement;
|
||||||
|
private timeAxisContent!: HTMLElement;
|
||||||
|
private calendarHeader!: HTMLElement;
|
||||||
|
|
||||||
|
init(container: HTMLElement): void {
|
||||||
|
this.scrollableContent = container.querySelector('swp-scrollable-content')!;
|
||||||
|
this.timeAxisContent = container.querySelector('swp-time-axis-content')!;
|
||||||
|
this.calendarHeader = container.querySelector('swp-calendar-header')!;
|
||||||
|
|
||||||
|
this.scrollableContent.addEventListener('scroll', () => this.onScroll());
|
||||||
|
}
|
||||||
|
|
||||||
|
private onScroll(): void {
|
||||||
|
const { scrollTop, scrollLeft } = this.scrollableContent;
|
||||||
|
|
||||||
|
// Synkroniser time-axis vertikalt
|
||||||
|
this.timeAxisContent.style.transform = `translateY(-${scrollTop}px)`;
|
||||||
|
|
||||||
|
// Synkroniser header horisontalt
|
||||||
|
this.calendarHeader.style.transform = `translateX(-${scrollLeft}px)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import { CalendarOrchestrator } from '../core/CalendarOrchestrator';
|
||||||
import { TimeAxisRenderer } from '../features/timeaxis/TimeAxisRenderer';
|
import { TimeAxisRenderer } from '../features/timeaxis/TimeAxisRenderer';
|
||||||
import { NavigationAnimator } from '../core/NavigationAnimator';
|
import { NavigationAnimator } from '../core/NavigationAnimator';
|
||||||
import { DateService } from '../core/DateService';
|
import { DateService } from '../core/DateService';
|
||||||
|
import { ScrollManager } from '../core/ScrollManager';
|
||||||
import { ViewConfig } from '../core/ViewConfig';
|
import { ViewConfig } from '../core/ViewConfig';
|
||||||
|
|
||||||
export class DemoApp {
|
export class DemoApp {
|
||||||
|
|
@ -13,7 +14,8 @@ export class DemoApp {
|
||||||
constructor(
|
constructor(
|
||||||
private orchestrator: CalendarOrchestrator,
|
private orchestrator: CalendarOrchestrator,
|
||||||
private timeAxisRenderer: TimeAxisRenderer,
|
private timeAxisRenderer: TimeAxisRenderer,
|
||||||
private dateService: DateService
|
private dateService: DateService,
|
||||||
|
private scrollManager: ScrollManager
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
|
|
@ -49,6 +51,9 @@ export class DemoApp {
|
||||||
// Render time axis
|
// Render time axis
|
||||||
this.timeAxisRenderer.render(document.getElementById('time-axis') as HTMLElement);
|
this.timeAxisRenderer.render(document.getElementById('time-axis') as HTMLElement);
|
||||||
|
|
||||||
|
// Init scroll synkronisering
|
||||||
|
this.scrollManager.init(this.container);
|
||||||
|
|
||||||
// Setup event handlers
|
// Setup event handlers
|
||||||
this.setupNavigation();
|
this.setupNavigation();
|
||||||
this.setupViewSwitchers();
|
this.setupViewSwitchers();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
--hour-height: 60px;
|
--hour-height: 60px;
|
||||||
--time-axis-width: 60px;
|
--time-axis-width: 60px;
|
||||||
--grid-columns: 5;
|
--grid-columns: 5;
|
||||||
|
--day-start-hour: 0;
|
||||||
|
--day-end-hour: 24;
|
||||||
--color-border: #e0e0e0;
|
--color-border: #e0e0e0;
|
||||||
--color-surface: #fff;
|
--color-surface: #fff;
|
||||||
--color-text-secondary: #666;
|
--color-text-secondary: #666;
|
||||||
|
|
@ -68,6 +70,7 @@ swp-calendar-container {
|
||||||
grid-template-columns: var(--time-axis-width) 1fr;
|
grid-template-columns: var(--time-axis-width) 1fr;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Time axis */
|
/* Time axis */
|
||||||
|
|
@ -78,6 +81,7 @@ swp-time-axis {
|
||||||
grid-template-rows: subgrid;
|
grid-template-rows: subgrid;
|
||||||
border-right: 1px solid var(--color-border);
|
border-right: 1px solid var(--color-border);
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-header-spacer {
|
swp-header-spacer {
|
||||||
|
|
@ -85,8 +89,8 @@ swp-header-spacer {
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-time-axis-content {
|
swp-time-axis-content {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-auto-rows: var(--hour-height);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,19 +112,31 @@ swp-grid-container {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Viewport/Track for slide animation */
|
/* Viewport/Track for slide animation */
|
||||||
swp-header-viewport,
|
swp-header-viewport {
|
||||||
swp-content-viewport {
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-header-track,
|
swp-content-viewport {
|
||||||
swp-content-track {
|
overflow: hidden;
|
||||||
|
min-height: 0; /* Tillader at krympe i grid */
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-header-track {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-header-track > swp-calendar-header,
|
swp-content-track {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-header-track > swp-calendar-header {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
}
|
||||||
|
|
||||||
swp-content-track > swp-scrollable-content {
|
swp-content-track > swp-scrollable-content {
|
||||||
flex: 0 0 100%;
|
flex: 0 0 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
|
|
@ -129,6 +145,16 @@ swp-calendar-header {
|
||||||
grid-template-columns: repeat(var(--grid-columns), 1fr);
|
grid-template-columns: repeat(var(--grid-columns), 1fr);
|
||||||
grid-auto-rows: auto;
|
grid-auto-rows: auto;
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-calendar-header::-webkit-scrollbar {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
swp-calendar-header::-webkit-scrollbar-thumb {
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-calendar-header[data-levels="date"] > swp-day-header { grid-row: 1; }
|
swp-calendar-header[data-levels="date"] > swp-day-header { grid-row: 1; }
|
||||||
|
|
@ -182,7 +208,7 @@ swp-scrollable-content {
|
||||||
swp-time-grid {
|
swp-time-grid {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: calc(15 * var(--hour-height));
|
min-height: calc((var(--day-end-hour) - var(--day-start-hour)) * var(--hour-height));
|
||||||
}
|
}
|
||||||
|
|
||||||
swp-grid-lines {
|
swp-grid-lines {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue