Improves grid layout and navigation
Refactors the calendar grid to support smoother navigation transitions by using separate week containers. This change introduces a GridManager to handle grid rendering and interactions within these containers, enabling scroll synchronization and click event handling for each week view. Also, configures the calendar to start at midnight and span a full 24-hour day, providing a more comprehensive view.
This commit is contained in:
parent
f06c02121c
commit
b443649ced
12 changed files with 719 additions and 302 deletions
|
|
@ -33,6 +33,7 @@ export class GridManager {
|
|||
private init(): void {
|
||||
this.findElements();
|
||||
this.subscribeToEvents();
|
||||
this.setupScrollSync();
|
||||
}
|
||||
|
||||
private findElements(): void {
|
||||
|
|
@ -64,6 +65,19 @@ export class GridManager {
|
|||
this.renderHeaders();
|
||||
});
|
||||
|
||||
// Handle week changes from NavigationManager
|
||||
eventBus.on(EventTypes.WEEK_CHANGED, (e: Event) => {
|
||||
const detail = (e as CustomEvent).detail;
|
||||
this.currentWeek = detail.weekStart;
|
||||
this.render();
|
||||
});
|
||||
|
||||
// Handle new week container creation
|
||||
eventBus.on(EventTypes.WEEK_CONTAINER_CREATED, (e: Event) => {
|
||||
const detail = (e as CustomEvent).detail;
|
||||
this.renderGridForContainer(detail.container, detail.weekStart);
|
||||
});
|
||||
|
||||
// Handle grid clicks
|
||||
this.setupGridInteractions();
|
||||
}
|
||||
|
|
@ -345,4 +359,228 @@ export class GridManager {
|
|||
|
||||
this.scrollableContent.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render grid for a specific container (used during navigation transitions)
|
||||
*/
|
||||
private renderGridForContainer(container: HTMLElement, weekStart: Date): void {
|
||||
// Find the week header and scrollable content within this container
|
||||
const weekHeader = container.querySelector('swp-week-header');
|
||||
const scrollableContent = container.querySelector('swp-scrollable-content');
|
||||
const timeGrid = container.querySelector('swp-time-grid');
|
||||
|
||||
if (!weekHeader || !scrollableContent || !timeGrid) {
|
||||
console.warn('GridManager: Required elements not found in container');
|
||||
return;
|
||||
}
|
||||
|
||||
// Render week header for this container
|
||||
this.renderWeekHeaderForContainer(weekHeader as HTMLElement, weekStart);
|
||||
|
||||
// Render grid content for this container - pass weekStart
|
||||
this.renderGridForSpecificContainer(container, weekStart);
|
||||
this.renderGridLinesForContainer(timeGrid as HTMLElement);
|
||||
this.setupGridInteractionsForContainer(container);
|
||||
|
||||
// Setup scroll sync for this new container
|
||||
this.setupScrollSyncForContainer(scrollableContent as HTMLElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render week header for a specific container
|
||||
*/
|
||||
private renderWeekHeaderForContainer(weekHeader: HTMLElement, weekStart: Date): void {
|
||||
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
|
||||
weekHeader.innerHTML = '';
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const date = new Date(weekStart);
|
||||
date.setDate(date.getDate() + i);
|
||||
|
||||
const header = document.createElement('swp-day-header');
|
||||
if (this.isToday(date)) {
|
||||
(header as any).dataset.today = 'true';
|
||||
}
|
||||
|
||||
header.innerHTML = `
|
||||
<swp-day-name>${days[date.getDay()]}</swp-day-name>
|
||||
<swp-day-date>${date.getDate()}</swp-day-date>
|
||||
`;
|
||||
(header as any).dataset.date = this.formatDate(date);
|
||||
|
||||
weekHeader.appendChild(header);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render grid structure for a specific container
|
||||
*/
|
||||
private renderGridForSpecificContainer(container: HTMLElement, weekStart?: Date): void {
|
||||
const timeGrid = container.querySelector('swp-time-grid');
|
||||
if (!timeGrid) {
|
||||
console.warn('GridManager: No time-grid found in container');
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the weekStart parameter or fall back to currentWeek
|
||||
const targetWeek = weekStart || this.currentWeek;
|
||||
if (!targetWeek) {
|
||||
console.warn('GridManager: No target week available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear existing columns
|
||||
let dayColumns = timeGrid.querySelector('swp-day-columns');
|
||||
if (!dayColumns) {
|
||||
dayColumns = document.createElement('swp-day-columns');
|
||||
timeGrid.appendChild(dayColumns);
|
||||
}
|
||||
|
||||
dayColumns.innerHTML = '';
|
||||
|
||||
const view = calendarConfig.get('view');
|
||||
const columnsCount = view === 'week' ? calendarConfig.get('weekDays') : 1;
|
||||
|
||||
// Create columns using the target week
|
||||
for (let i = 0; i < columnsCount; i++) {
|
||||
const column = document.createElement('swp-day-column');
|
||||
(column as any).dataset.columnIndex = i;
|
||||
|
||||
const dates = this.getWeekDates(targetWeek);
|
||||
if (dates[i]) {
|
||||
(column as any).dataset.date = this.formatDate(dates[i]);
|
||||
}
|
||||
|
||||
// Add events container
|
||||
const eventsLayer = document.createElement('swp-events-layer');
|
||||
column.appendChild(eventsLayer);
|
||||
|
||||
dayColumns.appendChild(column);
|
||||
}
|
||||
|
||||
// Update grid styles for this container
|
||||
const totalHeight = calendarConfig.totalHours * calendarConfig.get('hourHeight');
|
||||
(timeGrid as HTMLElement).style.height = `${totalHeight}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render grid lines for a specific time grid
|
||||
*/
|
||||
private renderGridLinesForContainer(timeGrid: HTMLElement): void {
|
||||
let gridLines = timeGrid.querySelector('swp-grid-lines');
|
||||
if (!gridLines) {
|
||||
gridLines = document.createElement('swp-grid-lines');
|
||||
timeGrid.insertBefore(gridLines, timeGrid.firstChild);
|
||||
}
|
||||
|
||||
const totalHours = calendarConfig.totalHours;
|
||||
const hourHeight = calendarConfig.get('hourHeight');
|
||||
|
||||
// Set CSS variables
|
||||
timeGrid.style.setProperty('--total-hours', totalHours.toString());
|
||||
timeGrid.style.setProperty('--hour-height', `${hourHeight}px`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup grid interactions for a specific container
|
||||
*/
|
||||
private setupGridInteractionsForContainer(container: HTMLElement): void {
|
||||
const timeGrid = container.querySelector('swp-time-grid');
|
||||
if (!timeGrid) return;
|
||||
|
||||
// Click handler
|
||||
timeGrid.addEventListener('click', (e: Event) => {
|
||||
const mouseEvent = e as MouseEvent;
|
||||
// Ignore if clicking on an event
|
||||
if ((mouseEvent.target as Element).closest('swp-event')) return;
|
||||
|
||||
const column = (mouseEvent.target as Element).closest('swp-day-column') as HTMLElement;
|
||||
if (!column) return;
|
||||
|
||||
const position = this.getClickPositionForContainer(mouseEvent, column, container);
|
||||
|
||||
eventBus.emit(EventTypes.GRID_CLICK, {
|
||||
date: (column as any).dataset.date,
|
||||
time: position.time,
|
||||
minutes: position.minutes,
|
||||
columnIndex: parseInt((column as any).dataset.columnIndex)
|
||||
});
|
||||
});
|
||||
|
||||
// Double click handler
|
||||
timeGrid.addEventListener('dblclick', (e: Event) => {
|
||||
const mouseEvent = e as MouseEvent;
|
||||
// Ignore if clicking on an event
|
||||
if ((mouseEvent.target as Element).closest('swp-event')) return;
|
||||
|
||||
const column = (mouseEvent.target as Element).closest('swp-day-column') as HTMLElement;
|
||||
if (!column) return;
|
||||
|
||||
const position = this.getClickPositionForContainer(mouseEvent, column, container);
|
||||
|
||||
eventBus.emit(EventTypes.GRID_DBLCLICK, {
|
||||
date: (column as any).dataset.date,
|
||||
time: position.time,
|
||||
minutes: position.minutes,
|
||||
columnIndex: parseInt((column as any).dataset.columnIndex)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get click position for a specific container
|
||||
*/
|
||||
private getClickPositionForContainer(event: MouseEvent, column: HTMLElement, container: HTMLElement): GridPosition {
|
||||
const rect = column.getBoundingClientRect();
|
||||
const scrollableContent = container.querySelector('swp-scrollable-content') as HTMLElement;
|
||||
const y = event.clientY - rect.top + (scrollableContent?.scrollTop || 0);
|
||||
|
||||
const minuteHeight = calendarConfig.minuteHeight;
|
||||
const snapInterval = calendarConfig.get('snapInterval');
|
||||
const dayStartHour = calendarConfig.get('dayStartHour');
|
||||
|
||||
// Calculate minutes from start of day
|
||||
let minutes = Math.floor(y / minuteHeight);
|
||||
|
||||
// Snap to interval
|
||||
minutes = Math.round(minutes / snapInterval) * snapInterval;
|
||||
|
||||
// Add day start offset
|
||||
const totalMinutes = (dayStartHour * 60) + minutes;
|
||||
|
||||
return {
|
||||
minutes: totalMinutes,
|
||||
time: this.minutesToTime(totalMinutes),
|
||||
y: minutes * minuteHeight
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup scroll synchronization between time-axis and scrollable content
|
||||
*/
|
||||
private setupScrollSync(): void {
|
||||
if (!this.scrollableContent || !this.timeAxis) return;
|
||||
|
||||
// Sync time-axis scroll with scrollable content
|
||||
this.scrollableContent.addEventListener('scroll', () => {
|
||||
if (this.timeAxis) {
|
||||
this.timeAxis.scrollTop = this.scrollableContent!.scrollTop;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup scroll synchronization for a specific container's scrollable content
|
||||
*/
|
||||
private setupScrollSyncForContainer(scrollableContent: HTMLElement): void {
|
||||
if (!this.timeAxis) return;
|
||||
|
||||
// Sync time-axis scroll with this container's scrollable content
|
||||
scrollableContent.addEventListener('scroll', () => {
|
||||
if (this.timeAxis) {
|
||||
this.timeAxis.scrollTop = scrollableContent.scrollTop;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue