only show calendar toolbar after load complete to avoid layout shift

This commit is contained in:
Gregor Vostrak
2026-03-11 18:09:50 +01:00
parent 19c789b78e
commit ef7569b63b
4 changed files with 216 additions and 203 deletions

View File

@@ -497,10 +497,13 @@ test.describe('Visual Snapping', () => {
await page.getByRole('option', { name: '5 min', exact: true }).click(); await page.getByRole('option', { name: '5 min', exact: true }).click();
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
// Scroll so both the event (9:00) and target (14:00) are in viewport
await scrollCalendarToTime(page, '08:00:00');
const event = page.locator('.fc-event').first(); const event = page.locator('.fc-event').first();
await expect(event).toBeVisible(); await expect(event).toBeVisible();
// Drag event // Capture target coordinates after scroll is settled
const targetSlot = page.locator('.fc-timegrid-slot-lane[data-time="14:00:00"]').first(); const targetSlot = page.locator('.fc-timegrid-slot-lane[data-time="14:00:00"]').first();
const targetBox = await targetSlot.boundingBox(); const targetBox = await targetSlot.boundingBox();
expect(targetBox).not.toBeNull(); expect(targetBox).not.toBeNull();

View File

@@ -1427,10 +1427,7 @@ test('test that bare integer in inline duration input with decimal format is int
}); });
}); });
test('test that bare integer in create modal is interpreted as minutes', async ({ test('test that bare integer in create modal is interpreted as minutes', async ({ page, ctx }) => {
page,
ctx,
}) => {
await updateOrganizationSettingViaApi(ctx, { await updateOrganizationSettingViaApi(ctx, {
interval_format: 'hours-minutes', interval_format: 'hours-minutes',
number_format: 'comma-point', number_format: 'comma-point',

View File

@@ -440,6 +440,7 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
:currency="currency" :currency="currency"
:can-create-project="canCreateProject" /> :can-create-project="canCreateProject" />
<template v-if="!loading">
<CalendarToolbar <CalendarToolbar
:view-title="viewTitle" :view-title="viewTitle"
:active-view="activeView" :active-view="activeView"
@@ -483,7 +484,9 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
<FullCalendarDayHeader <FullCalendarDayHeader
:date="day" :date="day"
:is-today="isToday(day)" :is-today="isToday(day)"
:total-seconds="dailyTotals[day.format('YYYY-MM-DD')] || 0" /> :total-seconds="
dailyTotals[day.format('YYYY-MM-DD')] || 0
" />
</div> </div>
</div> </div>
</div> </div>
@@ -501,7 +504,8 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
:key="slot.time" :key="slot.time"
class="fc-timegrid-slot fc-timegrid-slot-label relative text-right border-t border-border pr-1.5 pt-2 box-border" class="fc-timegrid-slot fc-timegrid-slot-label relative text-right border-t border-border pr-1.5 pt-2 box-border"
:class="{ :class="{
'fc-timegrid-slot-minor border-t-transparent': !slot.isHour, 'fc-timegrid-slot-minor border-t-transparent':
!slot.isHour,
}" }"
:data-time="slot.time" :data-time="slot.time"
:style="{ height: SLOT_HEIGHT + 'px' }"> :style="{ height: SLOT_HEIGHT + 'px' }">
@@ -524,7 +528,8 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
:key="'lane-' + slot.time" :key="'lane-' + slot.time"
class="fc-timegrid-slot fc-timegrid-slot-lane border-t border-border box-border" class="fc-timegrid-slot fc-timegrid-slot-lane border-t border-border box-border"
:class="{ :class="{
'fc-timegrid-slot-minor border-dotted': !slot.isHour, 'fc-timegrid-slot-minor border-dotted':
!slot.isHour,
}" }"
:data-time="slot.time" :data-time="slot.time"
:style="{ height: SLOT_HEIGHT + 'px' }"></div> :style="{ height: SLOT_HEIGHT + 'px' }"></div>
@@ -532,7 +537,8 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
<div <div
class="grid absolute inset-0 pointer-events-none min-w-0" class="grid absolute inset-0 pointer-events-none min-w-0"
:style="{ :style="{
gridTemplateColumns: 'repeat(' + viewDays.length + ', 1fr)', gridTemplateColumns:
'repeat(' + viewDays.length + ', 1fr)',
}"> }">
<CalendarDayColumn <CalendarDayColumn
v-for="day in viewDays" v-for="day in viewDays"
@@ -542,13 +548,17 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
:has-activity-status=" :has-activity-status="
dayHasActivityStatus(day.format('YYYY-MM-DD')) dayHasActivityStatus(day.format('YYYY-MM-DD'))
" "
:day-events="eventsByDay[day.format('YYYY-MM-DD')] || []" :day-events="
eventsByDay[day.format('YYYY-MM-DD')] || []
"
:get-event-style="getEventStyle" :get-event-style="getEventStyle"
:get-event-opacity-class="getEventOpacityClass" :get-event-opacity-class="getEventOpacityClass"
:get-event-duration-seconds="getEventDurationSeconds" :get-event-duration-seconds="getEventDurationSeconds"
:is-dragging="isDragging" :is-dragging="isDragging"
:drag-event-id="dragEventId" :drag-event-id="dragEventId"
:drag-preview="dragPreviewsByDay[day.format('YYYY-MM-DD')]" :drag-preview="
dragPreviewsByDay[day.format('YYYY-MM-DD')]
"
:resize-event-id="resizeEventId" :resize-event-id="resizeEventId"
:resize-cross-day-preview=" :resize-cross-day-preview="
isResizing isResizing
@@ -557,7 +567,9 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
] ]
: undefined : undefined
" "
:show-now-indicator="isToday(day) && nowIndicatorTop >= 0" :show-now-indicator="
isToday(day) && nowIndicatorTop >= 0
"
:now-indicator-top="nowIndicatorTop" :now-indicator-top="nowIndicatorTop"
:activity-boxes=" :activity-boxes="
activityBoxesForDay(day.format('YYYY-MM-DD')) activityBoxesForDay(day.format('YYYY-MM-DD'))
@@ -566,12 +578,16 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
:get-activity-box-activities="getActivityBoxActivities" :get-activity-box-activities="getActivityBoxActivities"
:get-activity-percentage="getActivityPercentage" :get-activity-percentage="getActivityPercentage"
:get-activity-text="getActivityText" :get-activity-text="getActivityText"
:show-selection="isSelecting || showCreateTimeEntryModal" :show-selection="
isSelecting || showCreateTimeEntryModal
"
:is-selection-start=" :is-selection-start="
selectionDay === day.format('YYYY-MM-DD') selectionDay === day.format('YYYY-MM-DD')
" "
:is-selection-intermediate=" :is-selection-intermediate="
selectionIntermediateDays.has(day.format('YYYY-MM-DD')) selectionIntermediateDays.has(
day.format('YYYY-MM-DD')
)
" "
:is-selection-end=" :is-selection-end="
selectionEndDay === day.format('YYYY-MM-DD') selectionEndDay === day.format('YYYY-MM-DD')
@@ -650,6 +666,7 @@ function getEventDurationSeconds(dayEvent: DayEvent, dayStr: string): number {
</template> </template>
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>
</template>
</div> </div>
</template> </template>

View File

@@ -26,11 +26,7 @@ function updateDuration() {
return; return;
} }
const seconds = parseTimeInput( const seconds = parseTimeInput(input, organization?.value?.number_format, 'hours');
input,
organization?.value?.number_format,
'hours'
);
if (seconds !== null && seconds > 0) { if (seconds !== null && seconds > 0) {
model.value = seconds; model.value = seconds;
} }